From ae6f40460dcd56af6c5f60ba52c883854c3bac55 Mon Sep 17 00:00:00 2001
From: wangzhibo <wangzhibo@shsening.com>
Date: 星期日, 16 八月 2026 15:30:04 +0800
Subject: [PATCH] first commit

---
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/WebViewSingleton.java                                                                         |  284 
 app/src/main/res/xml/devicesettings_device_name.xml                                                                                                       |    9 
 app/src/main/res/xml/devicesettings_fossilhybridhr_calibration.xml                                                                                        |   13 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgtr2/AmazfitGTR2eFWInstallHandler.java                                        |   49 
 app/src/main/assets/ic_device_sony_800n_disabled.svg                                                                                                      |  268 
 app/src/main/res/anim/flyright.xml                                                                                                                        |    8 
 app/src/main/res/drawable/ic_activity_obstacle_race.xml                                                                                                   |   17 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBEnvironment.java                                                                                 |   59 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/bandwpseries/BandWPSeriesDeviceSupport.java                                        |  315 
 app/src/main/res/xml/devicesettings_vitality_score.xml                                                                                                    |   25 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/deviceinfo/DeviceInfoProfile.java                                            |  186 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/SetDeviceSettingsMessage.java                                      |   53 
 app/src/main/res/drawable/gadgetbridge_img.xml                                                                                                            |   12 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/fenix/GarminFenix7SCoordinator.java                                         |   18 
 src/mainline/fastlane/metadata/android/en-US/changelogs/175.txt                                                                                           |    9 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/soundcore/liberty3_pro/SoundcoreLiberty3ProCoordinator.java                                |   83 
 app/src/main/res/drawable/ic_activity_cricket.xml                                                                                                         |   16 
 app/src/main/res/drawable/ic_access_alarms.xml                                                                                                            |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/DashboardActiveTimeWidget.java                                                |   74 
 app/src/main/res/mipmap-mdpi/ic_launcher.png                                                                                                              |    0 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/watch9/Watch9CalibrationActivity.java                                                      |  123 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/moondrop/MoondropSpaceTravelCoordinator.java                                               |   82 
 app/src/main/res/xml/devicesettings_header_sound_vibration.xml                                                                                            |    6 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/SetCurrentTimeServiceRequest.java                          |   45 
 app/src/main/assets/logback.xml                                                                                                                           |   19 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/file/FileEncryptedGetRequest.java                       |  244 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/RecordHeader.java                                                       |   87 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/ActivityHeartrate.java                |   47 
 app/src/main/java/lineageos/weatherservice/ServiceRequestResult.java                                                                                      |  197 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/FeaturesCommand.java                                                        |   94 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/StopFindPhoneRequest.java                                          |   40 
 app/src/main/res/drawable/ic_timer.xml                                                                                                                    |    3 
 app/src/main/res/drawable/ic_nothing_ear_l.xml                                                                                                            |   11 
 app/src/main/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactory.java                                                                             |  338 
 renovate.json                                                                                                                                             |   46 
 app/src/main/res/layout/activity_configure_reminders.xml                                                                                                  |   27 
 src/mainline/fastlane/metadata/android/en-US/changelogs/105.txt                                                                                           |    5 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/jyou/y5/Y5Support.java                                                             |  234 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/redmibuds5pro/RedmiBuds5ProCoordinator.java                                         |  103 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSettingsUtils.java                                                 |  212 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/services/XiaomiHealthService.java                                           |  952 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/headphones/protocol/impl/v1/params/BatteryType.java                           |   24 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/huaweiwatchfit3/HuaweiWatchFit3Coordinator.java                                     |   57 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitpoppro/AmazfitPopProCoordinator.java                                          |   54 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/id115/SendNotificationOperation.java                                               |  209 
 app/src/main/res/drawable/ic_activity_watertubing.xml                                                                                                     |   24 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_85.java                                                         |   22 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/CzechTransliterator.java                                                        |   31 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/DBHandler.java                                                                            |   53 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/GetPpgDataCommand.java                                                      |  135 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband5/MiBand5FWInstallHandler.java                                                 |   50 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SetTimeZoneIdRequest.java                                          |   55 
 app/src/main/java/org/bouncycastle/shaded/crypto/InvalidCipherTextException.java                                                                          |   40 
 src/mainline/fastlane/metadata/android/de-DE/images/icon.png                                                                                              |    0 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/AbstractPebbleMisfitActivitySample.java                                                   |   70 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/GPSCoordinate.java                                                                           |  143 
 app/src/main/res/xml/devicesettings_automatic_power_off_when_taken_off.xml                                                                                |   11 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/EstonianTransliterator.java                                                     |   32 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/GetSleepTimeCommand.java                                                    |   73 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/FindMyPhoneRequestMessage.java                                     |   36 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/smaq2oss/SMAQ2OSSSupport.java                                                              |  345 
 app/src/main/res/drawable/ic_activity_rowing.xml                                                                                                          |   10 
 app/src/main/res/drawable/ic_device_miwatch_disabled.xml                                                                                                  |   53 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/ItemWithDetailsAdapter.java                                                                |   55 
 app/src/main/res/drawable/ic_battery_full.xml                                                                                                             |    9 
 src/mainline/fastlane/metadata/android/en-US/changelogs/184.txt                                                                                           |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/miband5/MiBand5Support.java                                                  |   73 
 app/src/main/res/drawable/ic_galaxy_buds_r.xml                                                                                                            |   25 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventCameraRemote.java                                                        |   57 
 app/src/main/res/drawable/ic_calendar_to.xml                                                                                                              |    3 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivitySummaryData.java                                                                     |  164 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/ServerTransaction.java                                                                |   76 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/oppo/OppoHeadphonesIoThread.java                                                   |   75 
 src/mainline/fastlane/metadata/android/en-US/changelogs/166.txt                                                                                           |    4 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/prefs/EqualizerPreset.java                                                 |   70 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitTimestampCorrelation.java                                   |   32 
 app/src/main/res/values-vi/strings.xml                                                                                                                    |  403 
 app/src/main/res/drawable/ic_device_banglejs_disabled.xml                                                                                                 |   35 
 app/src/main/res/xml/devicesettings_device_internet_access.xml                                                                                            |   10 
 app/src/main/res/xml/devicesettings_root_calls_and_notifications.xml                                                                                      |    9 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/GetCurrentStepCountRequest.java                            |   46 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/test/samples/TestBodyEnergySampleProvider.java                                             |   71 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivityTrack.java                                                                           |   91 
 app/src/main/res/drawable/ic_pageview.xml                                                                                                                 |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/UnhandledMessage.java                                              |   20 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/DaySleepChartFragment.java                                                       |  616 
 app/src/main/res/drawable/ic_checklist.xml                                                                                                                |    5 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbip3pro/AmazfitBip3ProFWHelper.java                                           |   39 
 app/src/main/res/drawable/ic_history.xml                                                                                                                  |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/FindDeviceCommand.java                                                      |   41 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/logic/parsers/StepsPacketParser.java                           |   79 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitStressLevel.java                                            |   64 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/BondingUtil.java                                                                              |  441 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/headphones/SonyHeadphonesProtocol.java                                        |  361 
 src/mainline/fastlane/metadata/android/en-US/changelogs/202.txt                                                                                           |    3 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendGpsAndTimeToDeviceRequest.java                                 |   52 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendRunPaceConfigRequest.java                                      |   48 
 app/src/main/res/xml/devicesettings_force_connection_type.xml                                                                                             |   12 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitx/AmazfitXCoordinator.java                                                    |  151 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/tlw64/TLW64Support.java                                                            |  598 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/QHybridConfigActivity.java                                                         |  671 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/AuthenticationHandler.java                                         |  128 
 app/src/main/res/drawable/ic_circle.xml                                                                                                                   |   10 
 app/src/main/res/drawable/ic_phone_outline.xml                                                                                                            |    9 
 app/src/main/res/drawable/ic_font_download.xml                                                                                                            |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/baseTypes/BaseTypeFloat.java                                            |   51 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/zetime/ZeTimeDeviceSupport.java                                                    | 2251 
 src/mainline/fastlane/metadata/android/cs-CZ/short_description.txt                                                                                        |    1 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/RespiratoryRateCollectionFragment.java                                           |   29 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/file/FileLookupAndGetRequest.java                          |   38 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/control/ControlPointWithValue.java                              |   37 
 app/src/main/java/net/osmand/aidlapi/search/SearchResult.java                                                                                             |   94 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/services/XiaomiCalendarService.java                                         |  147 
 app/src/test/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/TestCalls.java                                                              |   68 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband6/MiBand6FWInstallHandler.java                                                 |   50 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/DashboardBodyEnergyWidget.java                                                |  191 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/asteroidos/AsteroidOSConstants.java                                                        |  137 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/soundcore/liberty/SoundcoreLibertyIOThread.java                                    |   57 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/withingssteelhr/WithingsSteelHRDeviceCoordinator.java                                      |  212 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/GattCallback.java                                                                     |  116 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/colmi/samples/ColmiSleepSessionSampleProvider.java                                         |   56 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/prefs/AudioUpsampling.java                                                 |   46 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/deviceevents/FileDownloadedDeviceEvent.java                                 |    9 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/UriHelper.java                                                                                |  198 
 app/src/main/res/drawable/ic_device_supercars.xml                                                                                                         |   22 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/zetime/ZeTimeCoordinator.java                                                              |  209 
 app/src/main/res/layout/item_app_specific_notification_app_list.xml                                                                                       |   59 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/flipper/zero/FlipperZeroCoordinator.java                                                   |  150 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/logic/ActivityPacketParser.java                                |   30 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/LanguageUtils.java                                                                   |  149 
 app/src/main/res/drawable/ic_u_turn_right.xml                                                                                                             |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/fieldDefinitions/FieldDefinitionHrZoneHighBoundary.java                 |   10 
 app/src/main/res/xml/devicesettings_fake_ring_duration.xml                                                                                                |    9 
 app/src/main/res/xml/devicesettings_root_activity_info.xml                                                                                                |    9 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/banglejs/BangleJSActivityPoint.java                                                        |  241 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivitySummary.java                                                                         |   51 
 app/src/main/res/layout/fragment_charts.xml                                                                                                               |   13 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/enums/GarminSport.java                                                  |  153 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendFileUploadChunk.java                                           |   52 
 app/src/nopebble/res/mipmap-xxxhdpi/ic_launcher_foreground.png                                                                                            |    0 
 app/src/main/java/lineageos/weather/util/WeatherUtils.java                                                                                                |   84 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/EventStreamRequest.java                                    |   47 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sonyswr12/SonySWR12DeviceCoordinator.java                                                  |  150 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/message/incoming/LiveWorkoutHandler.java             |  147 
 app/src/main/res/layout/item_with_details.xml                                                                                                             |   67 
 app/src/main/res/drawable/ic_surround.xml                                                                                                                 |   10 
 app/src/main/res/xml/devicesettings_amazfitbipu.xml                                                                                                       |   13 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiPaiSampleProvider.java                                                          |   56 
 app/src/main/res/xml/devicesettings_sony_headphones_button_modes_left_right.xml                                                                           |   87 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/GoalTrackingSetRequest.java                                |   35 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/file/FileCloseRequest.java                                 |   98 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/AbstractTransaction.java                                                              |   46 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/binary_sensor/protocol/message/Response.java                                       |   38 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendWatchfaceConfirm.java                                          |   46 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendWeatherStartRequest.java                                       |   69 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/MediaManager.java                                                                             |  183 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/nothing/Ear1Coordinator.java                                                               |   44 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/DashboardVO2MaxWidgetInterface.java                                           |    8 
 app/src/main/res/drawable/ic_device_default_disabled.xml                                                                                                  |   24 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleKitSupport.java                                                       |  201 
 app/src/main/assets/fossil_hr/widgetBattery_preview.png                                                                                                   |    0 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/prefs/WideAreaTap.java                                                     |   46 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiActivitySummaryParser.java                                                      |  391 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/Mi1SFirmwareInfoFW2.java                                                    |   79 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandler.java                                                      |   91 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/instinct/GarminInstinct2Coordinator.java                                    |   18 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/GarminUtils.java                                                            |   35 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitbipu/AmazfitBipUSupport.java                                          |   58 
 app/src/main/res/xml/about_user.xml                                                                                                                       |  132 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/NonSwipeableViewPager.java                                                       |   53 
 app/src/main/res/drawable/ic_list_numbered.xml                                                                                                            |   10 
 app/src/main/res/xml/devicesettings_ftp_server.xml                                                                                                        |   55 
 app/src/main/res/drawable/ic_lock_open.xml                                                                                                                |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/authentication/ConfirmOnDeviceRequest.java              |   51 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/backup/ZipBackupMetadata.java                                                                 |   60 
 app/src/main/res/drawable/ic_battery_charging_80.xml                                                                                                      |   13 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/id115/ID115SampleProvider.java                                                             |   77 
 app/src/main/aidl/net/osmand/aidlapi/navigation/ANavigationUpdateParams.aidl                                                                              |    4 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiP2PManager.java                                                       |   71 
 app/src/main/assets/ic_launcher_neon.svg                                                                                                                  |  751 
 src/mainline/fastlane/metadata/android/de-DE/full_description.txt                                                                                         |    8 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/AbstractHrvValueSample.java                                                               |   35 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/logic/parsers/EnergyPacketParser.java                          |   68 
 app/src/main/res/drawable/ic_invert_colors.xml                                                                                                            |   10 
 app/src/test/java/nodomain/freeyourgadget/gadgetbridge/test/TestBase.java                                                                                 |   95 
 app/src/test/java/nodomain/freeyourgadget/gadgetbridge/util/ImportExportSharedPreferencesTest.java                                                        |   65 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/SaveCalibrationRequest.java                                |   26 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/notification/NotificationFilterPutRequest.java             |  107 
 external/build_fossil_hr_gbapps.sh                                                                                                                        |   29 
 app/src/main/res/layout/fragment_welcome_intro.xml                                                                                                        |   61 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/pebble/GBDeviceEventDataLogging.java                                                  |   33 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/wena3/SonyWena3BehaviorSampleProvider.java                                            |   56 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/activity/ActivitySyncStartPacket.java                  |   29 
 app/src/main/res/layout/qhybrid_activity_widget_settings.xml                                                                                              |   73 
 app/src/main/res/xml/devicesettings_fossilhybridhr_post_fw300.xml                                                                                         |   59 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/enduro/GarminEnduro3Coordinator.java                                        |   18 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiSpo2SampleProvider.java                                                       |  230 
 src/mainline/fastlane/metadata/android/cs-CZ/full_description.txt                                                                                         |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/vivomovehr/VivomoveHrSampleProvider.java                                                   |  235 
 app/src/main/assets/womp/appointments.html                                                                                                                |   73 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/test/samples/TestStressSampleProvider.java                                                 |   89 
 app/src/main/res/layout/activity_cycling_live_data.xml                                                                                                    |   51 
 app/src/main/res/drawable/ic_translate.xml                                                                                                                |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/banglejs/AppsManagementActivity.java                                                       |  346 
 app/src/main/java/lineageos/weather/LineageWeatherManager.java                                                                                            |  435 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/miband4/MiBand4Support.java                                                  |   66 
 app/src/main/res/drawable/ic_activity_bowling.xml                                                                                                         |   11 
 app/src/main/res/layout/fragment_steps_list.xml                                                                                                           |   44 
 app/src/main/res/layout/activity_welcome.xml                                                                                                              |   25 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendDndAddRequest.java                                             |   92 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pinetime/PineTimeInstallHandler.java                                                       |  132 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/FetchSleepRespiratoryRateOperation.java                     |  118 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ActivitySummariesChartFragment.java                                                     |  313 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pinetime/PineTimeJFSupport.java                                                    | 1274 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/configuration/ConfigurationPutRequest.java              |   51 
 app/src/main/res/drawable-mdpi/ic_notification_qhybrid.png                                                                                                |    0 
 app/src/main/res/xml/devicesettings_zetime_vibrationpatterns.xml                                                                                          |  112 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/casio/gwb5600/CasioGWB5600DeviceCoordinator.java                                           |  116 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/epix/GarminEpixProCoordinator.java                                          |   18 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/Watchface.java                                                              |  274 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/GpxReceiverActivity.java                                                                |  162 
 app/src/main/aidl/lineageos/weather/RequestInfo.aidl                                                                                                      |   19 
 app/src/main/res/layout/fragment_gps.xml                                                                                                                  |   28 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/DoNotDisturbSettings.java                     |   51 
 app/src/main/res/layout/layout_widget_generic.xml                                                                                                         |   45 
 app/src/main/res/drawable/ic_always_on_display.xml                                                                                                        |    5 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitband5/AmazfitBand5Coordinator.java                                            |  188 
 app/src/main/res/drawable-xxhdpi/ic_notification_disconnected_qhybrid.png                                                                                 |    0 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/miligatt.txt                                                                        |   34 
 src/mainline/fastlane/metadata/android/en-US/changelogs/211.txt                                                                                           |   24 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/PpgResultCommand.java                                                       |   71 
 app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/headphones/protocol/impl/v2/SonyProtocolImplV2Test.java                       |  525 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgtr/AmazfitGTRFWInstallHandler.java                                           |   49 
 src/mainline/fastlane/metadata/android/en-US/changelogs/110.txt                                                                                           |    5 
 app/src/main/res/layout/activity_qhybrid_hr_settings.xml                                                                                                  |  109 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband4/MiBand4FWInstallHandler.java                                                 |   50 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/honorband5/HonorBand5Coordinator.java                                               |   46 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebbleMisfitSampleProvider.java                                                     |   77 
 app/src/main/res/layout/activity_reminder_details.xml                                                                                                     |  176 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiReportThreshold.java                                                          |   41 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbip/AmazfitBipService.java                                                    |   33 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/DashboardVO2MaxRunningWidget.java                                             |   36 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/HRVStatusFragment.java                                                           |  415 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivityUser.java                                                                            |  213 
 app/src/main/res/drawable/ic_wb_twilight.xml                                                                                                              |    9 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppe/ZeppEFirmwareInfo.java                                                 |  116 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/FitImporter.java                                                        |  661 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/nothing/NothingProtocol.java                                                       |  341 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetSleepDataCountRequest.java                                      |   84 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/TimestampValueFormatter.java                                                     |   50 
 app/src/main/res/xml/devicesettings_pairingkey_explanation.xml                                                                                            |   15 
 app/src/main/assets/ic_device_sony_800n.svg                                                                                                               |  215 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/file/FileCloseAndPutRequest.java                           |   54 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/control/ControlPointLowVibration.java                           |   44 
 app/src/main/res/layout/dashboard_widget_goals.xml                                                                                                        |   35 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband3/MiBand3FWInstallHandler.java                                                 |   49 
 src/mainline/fastlane/metadata/android/en-US/changelogs/132.txt                                                                                           |   11 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/DisplaySetting.java                           |   63 
 app/src/main/res/drawable/ic_play.xml                                                                                                                     |   10 
 app/src/test/resources/gpx-parser-test-multiple-segments.gpx                                                                                              |   57 
 app/src/main/res/xml/devicesettings_morning_updates.xml                                                                                                   |   29 
 src/mainline/fastlane/metadata/android/en-US/changelogs/179.txt                                                                                           |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetStepDataRequest.java                                            |   96 
 app/src/main/java/org/bouncycastle/shaded/crypto/modes/SICBlockCipher.java                                                                                |  381 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventWearState.java                                                           |   30 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/SetRemoteCameraCommand.java                                                 |   52 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/cmfwatchpro/CmfActivityType.java                                                   |  181 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/WeightChartFragment.java                                                         |  257 
 app/src/main/res/layout/widget.xml                                                                                                                        |  215 
 app/src/main/assets/womp/home.html                                                                                                                        |  100 
 app/src/main/res/layout/activity_um25_data.xml                                                                                                            |  101 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitSport.java                                                  |   37 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BtLEAction.java                                                                       |   78 
 app/src/main/res/xml/devicesettings_miband3.xml                                                                                                           |   13 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitgtr/AmazfitGTRLiteSupport.java                                        |   65 
 app/src/main/assets/fossil_hr/widgetWeather_preview.png                                                                                                   |    0 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SetDateFormatRequest.java                                          |   83 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/watch9/Watch9DeviceSupport.java                                                    |  497 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetActivityTypeRequest.java                                        |   62 
 app/src/main/res/xml/preferences.xml                                                                                                                      |  483 
 app/src/main/res/layout/fragment_steps_period.xml                                                                                                         |  120 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/miband3/MiBand3Support.java                                                  |   76 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiCharacteristic.java                                                   |  570 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/miwatch/MiWatchLiteCoordinator.java                                                 |   70 
 app/src/main/res/drawable/ic_file_open.xml                                                                                                                |   12 
 src/mainline/fastlane/metadata/android/en-US/changelogs/180.txt                                                                                           |    8 
 app/src/test/java/nodomain/freeyourgadget/gadgetbridge/test/ArrayUtilsTest.java                                                                           |  157 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/forerunner/GarminForerunner165Coordinator.java                              |   18 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/vibratissimo/VibratissimoCoordinator.java                                                  |  156 
 src/mainline/fastlane/metadata/android/en-US/changelogs/148.txt                                                                                           |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/PhoneCallReceiver.java                                                              |  172 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/activity/ActivitySyncTimePacketTypeB.java              |   27 
 app/src/main/assets/sports_badmington.svg                                                                                                                 |   83 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitcor2/AmazfitCor2FirmwareInfo.java                                     |   87 
 app/src/main/assets/nothing_ear_r.svg                                                                                                                     |   73 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/Mi1SFirmwareInfo.java                                                       |   72 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband5/MiBand5FWHelper.java                                                         |   40 
 app/src/main/res/drawable/ic_gps_location.xml                                                                                                             |    5 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/headphones/protocol/impl/v2/PayloadTypeV2.java                                |   68 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/VibrationProfile.java                                                               |  110 
 app/src/main/res/drawable/ic_insights.xml                                                                                                                 |    4 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventFindPhone.java                                                           |   31 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsFileTransferService.java                               |  615 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/FetchHeartRateMaxOperation.java                             |  118 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/WithingsStructure.java                |  103 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminRealtimeSettingsFragment.java                                                 |  897 
 app/src/main/res/xml/devicesettings_huami2021_shortcuts.xml                                                                                               |   13 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitRecordDataFactory.java                                      |  100 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/huaweiband8/HuaweiBand8Coordinator.java                                             |   41 
 app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/FirmwareTest.java                                                           |  190 
 app/src/main/assets/fossil_hr/menu_layout.json                                                                                                            |  207 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/StepStreaksDashboard.java                                                        |  496 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/itag/ITagCoordinator.java                                                                  |  172 
 app/src/main/assets/womp/app.js                                                                                                                           |   70 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/vivoactive/GarminVivoActive4Coordinator.java                                |   18 
 app/src/main/res/drawable/ic_device_sony_overhead.xml                                                                                                     |   23 
 src/mainline/fastlane/metadata/android/en-US/changelogs/206.txt                                                                                           |   16 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitbip/AmazfitBipLiteFirmwareInfo.java                                   |  107 
 app/src/main/res/xml/devicesettings_workout_keep_screen_on.xml                                                                                            |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/opentracks/OpenTracksContentObserver.java                                           |  100 
 app/src/test/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/TestDisconnectNotification.java                                             |  104 
 app/src/main/res/drawable/ic_device_headphones.xml                                                                                                        |   28 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/FileManagementActivity.java                                                        |  194 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/communicator/CobsCoDec.java                                                 |  128 
 app/src/main/res/xml/devicesettings_disconnectnotification.xml                                                                                            |   32 
 src/mainline/fastlane/metadata/android/en-US/changelogs/123.txt                                                                                           |    3 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/DBOpenHelper.java                                                                         |   50 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiPreferences.java                                                      |   94 
 app/src/main/res/xml/devicesettings_redmibuds5pro_sound.xml                                                                                               |  144 
 app/src/nopebble/res/mipmap-hdpi/ic_launcher_round.png                                                                                                    |    0 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/ActivityPointGetRequest.java                               |   46 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsAlarmsService.java                                     |  220 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendNotifyRestHeartRateCapabilityRequest.java                      |   56 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/HeartRate.java                        |   47 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/colmi/ColmiR03Coordinator.java                                                             |   38 
 app/src/main/res/layout/activity_supercars_control.xml                                                                                                    |  142 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/SetProgressAction.java                                                        |   69 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/status/WeatherType.java                                |   25 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/XTimePreference.java                                                                          |  128 
 .gitignore                                                                                                                                                |   43 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/battery/BatteryInfo.java                                                     |   62 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SetSkinTemperatureMeasurement.java                                 |   39 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PBWReader.java                                                                      |  358 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/redmiwatch4/RedmiWatch4Coordinator.java                                             |   61 
 src/mainline/fastlane/metadata/android/en-US/changelogs/139.txt                                                                                           |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/vivomove/GarminVivomoveStyleCoordinator.java                                |   18 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/wena3/SonyWena3SettingKeys.java                                                       |   51 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/gpx/GpxParser.java                                                                            |  295 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/instinct/GarminInstinctCrossoverCoordinator.java                            |   18 
 src/mainline/fastlane/metadata/android/en-US/changelogs/162.txt                                                                                           |    2 
 app/src/main/res/drawable/baseline_merge_24.xml                                                                                                           |    8 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/DownloadRequestMessage.java                                        |   45 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AppCompatPreferenceActivity.java                                                        |  130 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/notification/defines/LedColor.java                     |   53 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/prefs/QuickAccess.java                                                     |   83 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/ZeppOsMenuType.java                                                   |  142 
 app/src/main/aidl/com/android/internal/telephony/ITelephony.aidl                                                                                          |    8 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/AbstractActivityChartFragment.java                                               |  462 
 app/src/main/res/drawable/ic_device_vesc_disabled.xml                                                                                                     |   31 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/deviceinfo/DeviceInfo.java                                                   |  164 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/operations/ZeppOsGpxRouteFile.java                                    |  170 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/makibeshr3/MakibesHR3DeviceSupport.java                                            | 1083 
 app/src/main/assets/fossil_hr/widgetHR.bin                                                                                                                |    0 
 app/src/main/assets/ic_activity_threadmill.svg                                                                                                            |   76 
 app/src/main/res/layout/preference_checkbox.xml                                                                                                           |   77 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitFileCreator.java                                            |   32 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/redmibuds5pro/prefs/Gestures.java                                                   |   22 
 app/src/main/res/layout/activity_notification_filter.xml                                                                                                  |   86 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/RequestConnectionPriorityAction.java                                          |   42 
 app/src/main/res/drawable/ic_select_all.xml                                                                                                               |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetMusicList.java                                                  |   45 
 app/src/main/res/drawable/ic_activity_graphs.xml                                                                                                          |   10 
 app/src/main/res/xml/devicesettings_battery_minimum_charge.xml                                                                                            |   10 
 app/src/nightly/res/mipmap-xhdpi/ic_launcher_round.png                                                                                                    |    0 
 src/mainline/fastlane/metadata/android/en-US/changelogs/150.txt                                                                                           |    5 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/status/GenericStatusMessage.java                                   |   39 
 src/mainline/fastlane/metadata/android/en-US/changelogs/193.txt                                                                                           |   14 
 app/src/main/res/values-sr/strings.xml                                                                                                                    | 2016 
 src/banglejs/fastlane/metadata/android/en-US/changelogs                                                                                                   |    1 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivitySample.java                                                                          |  101 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendGpsDataRequest.java                                            |   94 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiBRSupport.java                                                        |  215 
 app/src/main/res/drawable/ic_navigation.xml                                                                                                               |   12 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/logic/parsers/LinearSamplePacketParser.java                    |   65 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/conversation/BatteryStateHandler.java                |   57 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/EventWithValue.java                                    |   34 
 app/src/main/java/org/bouncycastle/shaded/crypto/engines/AESEngine.java                                                                                   |  609 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiBRCoordinator.java                                                            |  260 
 app/src/main/res/drawable/ic_music_note.xml                                                                                                               |    3 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/fieldDefinitions/FieldDefinitionTimestamp.java                          |   26 
 app/src/nopebble/res/mipmap-mdpi/ic_launcher.png                                                                                                          |    0 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/soundcore/liberty4_nc/SoundcoreLiberty4NCCoordinator.java                                  |   83 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBException.java                                                                                   |   35 
 app/src/main/res/values-zh-rCN/strings.xml                                                                                                                | 3701 
 app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/nut/NutUtilsTest.java                                                              |  247 
 app/src/main/res/layout/activity_open_fw_app_installer.xml                                                                                                |   88 
 app/src/main/res/drawable/ic_activity_snorkeling.xml                                                                                                      |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/WeekSleepChartFragment.java                                                      |  499 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SetAlarmRequest.java                                                |  116 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/file/FirmwareFilePutRequest.java                           |   50 
 app/src/main/res/layout/fossil_hr_row_installed_app.xml                                                                                                   |   21 
 app/src/main/res/menu/menu_file_manager.xml                                                                                                               |   11 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/AbstractMi1SFirmwareInfo.java                                               |   38 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/ActivityWithData.java                                  |   29 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/util/UIntBitWriter.java                                                  |   53 
 app/src/main/res/xml/devicesettings_header_display.xml                                                                                                    |    6 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casio/gbx100/SetConfigurationOperation.java                                        |  326 
 app/src/main/res/xml/devicesettings_disable_find_phone_with_dnd.xml                                                                                       |    9 
 app/src/main/aidl/net/osmand/aidlapi/IOsmAndAidlCallback.aidl                                                                                             |   59 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitcheetahpro/AmazfitCheetahProCoordinator.java                                  |   85 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/WithingsServerAction.java                            |   43 
 app/src/main/res/layout/item_musicmanager_song.xml                                                                                                        |   63 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgts4mini/AmazfitGTS4MiniCoordinator.java                                      |   66 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/WithingsStructureType.java            |   72 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/AbstractWeekChartFragment.java                                                   |  392 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/translation/TranslationItem.java                        |   51 
 app/src/main/res/xml/devicesettings_reserve_reminders_calendar.xml                                                                                        |   15 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/ActivitySampleSwim.java               |   36 
 app/src/main/res/drawable/ic_link.xml                                                                                                                     |    5 
 app/src/main/res/xml/devicesettings_root_display.xml                                                                                                      |    9 
 app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/TestDeviceSupport.java                                                                     |   49 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/Workout.java                                                                |  740 
 src/mainline/fastlane/metadata/android/en-US/changelogs/220.txt                                                                                           |   23 
 app/src/main/assets/ic_activity_bike_trainer.svg                                                                                                          |   86 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ActivitySummaryDetail.java                                                              |  729 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbipupro/AmazfitBipUProFWHelper.java                                           |   40 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/StringUtils.java                                                                              |  226 
 app/src/main/res/drawable/ic_nights_stay.xml                                                                                                              |   13 
 app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/communicator/CobsCoDecTest.java                                             |  113 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/itag/ITagSupport.java                                                              |  158 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/miband2/MiBand2Support.java                                                  |   94 
 app/src/main/res/xml/devicesettings_canned_dismisscall_16.xml                                                                                             |  122 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebbleColor.java                                                                    |   88 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/jyou/BFH16Constants.java                                                                   |  125 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SendCallNotificationRequest.java                                    |   65 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/message/GlyphRequestHandler.java                     |   92 
 app/src/main/res/drawable/ic_activity_bike_trainer.xml                                                                                                    |   34 
 app/src/main/assets/fossil_hr/icWthRainy.rle                                                                                                              |    1 
 app/src/main/res/layout/fragment_steps.xml                                                                                                                |  101 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BtLEServerAction.java                                                                 |   75 
 app/src/main/res/menu/menu_discovery.xml                                                                                                                  |   11 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/ActivitySummariesAdapter.java                                                              |  394 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgtr/AmazfitGTRLiteFWInstallHandler.java                                       |   49 
 app/src/main/res/xml/devicesettings_liftwrist_display_sensitivity.xml                                                                                     |   39 
 src/mainline/fastlane/metadata/android/en-US/changelogs/109.txt                                                                                           |    1 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetWorkoutPaceRequest.java                                         |  127 
 app/src/main/java/com/mobeta/android/dslv/DragSortItemView.java                                                                                           |   95 
 app/src/main/res/xml/devicesettings_inactivity_noshed.xml                                                                                                 |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/WithingsUUID.java                                    |   34 
 app/src/main/res/drawable/ic_loyalty.xml                                                                                                                  |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/configuration/ConfigurationGetRequest.java                 |   54 
 app/src/main/res/xml/fossil_hr_widget_settings.xml                                                                                                        |  109 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/prefs/AutomaticPowerOff.java                                               |   64 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/FormatUtils.java                                                                              |   62 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/device_info/DeviceSecurityVersionInfo.java                 |   43 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/coordinators/SonyWH1000XM4Coordinator.java                                 |   58 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_17.java                                                         |   38 
 app/src/main/assets/fossil_hr/widgetUV_preview.png                                                                                                        |    0 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/wena3/SonyWena3ActivitySampleCombiner.java                                            |   78 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitgts2/AmazfitGTS2eSupport.java                                         |   34 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiSampleProvider.java                                                           |  211 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/time/BandDaylightSavingTime.java                                |   40 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendWeatherUnitRequest.java                                        |   49 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/SonySWR12Constants.java                                                  |   34 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/SwipeEvents.java                                                                              |   74 
 app/src/main/res/drawable-xhdpi/ic_watchface.png                                                                                                          |    0 
 app/src/main/res/values-zh-rTW/strings.xml                                                                                                                | 3411 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceMusicUpdate.java                                                              |   11 
 src/mainline/fastlane/metadata/android/en-US/images/icon.png                                                                                              |    0 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miscale/MiCompositionScaleCoordinator.java                                                 |  142 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/nothing/EarSettingsCustomizer.java                                                         |   92 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/gpx/model/GpxTrackSegment.java                                                                |   49 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/galaxy_buds/GalaxyBudsDeviceCoordinator.java                                               |   58 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/moondrop/MoondropSpaceTravelProtocol.java                                          |  187 
 app/src/main/assets/fossil_hr/widget_bg_dashed_circle.png                                                                                                 |    0 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/AccountRelated.java                                                         |   82 
 app/src/main/java/org/bouncycastle/shaded/util/Arrays.java                                                                                                | 1227 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/HybridHRWatchfaceWidgetActivity.java                                               |  273 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/divoom/PixooIOThread.java                                                          |   62 
 app/src/main/assets/ic_sony_wf_800n_right.svg                                                                                                             |   95 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventUpdatePreferences.java                                                   |   94 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendSetUpDeviceStatusRequest.java                                  |   58 
 app/src/main/res/xml/devicesettings_sony_headphones_equalizer_with_custom_bands.xml                                                                       |   58 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiFirmwareType.java                                                       |   67 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceType.java                                                                              |  619 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/notification/GetNotificationAttributes.java          |   76 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitgtr2/AmazfitGTR2FirmwareInfo.java                                     |  120 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/ImageData.java                        |   47 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/LiveWorkoutPauseState.java            |   66 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/CompositeMiFirmwareInfo.java                                                |  112 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiUuids.java                                                            |  121 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/FossilHRInstallHandler.java                                                        |  189 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/vesc/CommandType.java                                                              |   63 
 app/src/main/assets/ic_sony_wf_800n_left.svg                                                                                                              |   95 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/ActivitySampleWalk.java               |   50 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/SetCountdownSettings.java                                  |   44 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsContactsService.java                                   |  118 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitEvent.java                                                  |   47 
 app/src/main/res/drawable/ic_done_all.xml                                                                                                                 |   10 
 app/src/main/res/xml/devicesettings_mismartscale.xml                                                                                                      |   18 
 src/banglejs/fastlane/metadata/android/pt-PT/full_description.txt                                                                                         |    6 
 src/mainline/fastlane/metadata/android/en-US/changelogs/209.txt                                                                                           |   16 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/headphones/protocol/impl/v1/PayloadTypeV1.java                                |  128 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/huaweiband4pro/HuaweiBand4ProCoordinator.java                                       |   41 
 app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/fitpro/FitProDeviceSupportTest.java                                                |   69 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/StepsFragment.java                                                               |  134 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/Earphones.java                                                              |  119 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/MoveHand.java                         |   49 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/BluetoothPairingRequestReceiver.java                                                |   72 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/TimeSample.java                                                                              |   24 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casio/gwb5600/CasioGWB5600DeviceSupport.java                                       |   59 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BLETypeConversions.java                                                               |  422 
 app/src/main/res/values-v14/dimens.xml                                                                                                                    |   10 
 app/src/main/res/layout/activity_main.xml                                                                                                                 |   26 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitpop/AmazfitPopFWInstallHandler.java                                           |   49 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetAuthRequest.java                                                |  122 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/ActivitySampleSleep.java              |   50 
 src/mainline/fastlane/metadata/android/en-US/changelogs/136.txt                                                                                           |   17 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/SourceAppId.java                      |   52 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hplus/HPlusHandlerThread.java                                                      |  650 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/miband4/MiBand4FirmwareInfo.java                                             |   99 
 app/src/main/res/drawable/ic_activity_rope_jump.xml                                                                                                       |   14 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AbstractSettingsActivityV2.java                                                         |   92 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ControlCenterv2.java                                                                    |  524 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsFtpServerService.java                                  |  177 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/operations/ZeppOsFirmwareUpdateOperation.java                         |  376 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/buttonconfig/ConfigPayload.java                                            |  105 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/DashboardCaloriesTotalSegmentedWidget.java                                    |   83 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/soundcore/liberty/TapFunction.java                                                 |   22 
 app/src/main/assets/fossil_hr/icSteps.rle                                                                                                                 |    5 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/AuthNegotiationMessage.java                                        |   61 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/SleepScoreSample.java                                                                        |   37 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AbstractGBFragmentActivity.java                                                         |   72 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/watch9/operations/InitOperation.java                                               |   93 
 app/src/main/res/layout/activity_list.xml                                                                                                                 |   46 
 app/src/main/proto/garmin/gdi_calendar_service.proto                                                                                                      |   48 
 app/src/main/assets/fossil_hr/navigationApp.wapp                                                                                                          |    0 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/operations/ZeppOsAgpsUpdateOperation.java                             |  162 
 app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/TestResponseManager.java                                                    |  485 
 app/src/nightly/res/mipmap-hdpi/ic_launcher_round.png                                                                                                     |    0 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitcor2/AmazfitCor2Support.java                                          |   40 
 app/src/main/assets/ic_intensity.svg                                                                                                                      |   20 
 app/src/main/res/drawable/ic_file_upload.xml                                                                                                              |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/fitpro/FitProConstants.java                                                                |  250 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/MessageWriter.java                                                 |   88 
 app/src/main/assets/womp/wastesale.html                                                                                                                   |   87 
 app/src/main/res/drawable/ic_chair.xml                                                                                                                    |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitCourse.java                                                 |   32 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/venu/GarminVenuCoordinator.java                                             |   18 
 app/src/main/res/xml/devicesettings_sony_headphones_ambient_sound_control.xml                                                                             |   32 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitneo/AmazfitNeoCoordinator.java                                                |  153 
 app/src/main/res/drawable/ic_device_flipper_disabled.xml                                                                                                  |  243 
 .woodpecker/nightly.yml                                                                                                                                   |  140 
 app/src/main/res/xml/devicesettings_sony_headphones_protocol_version.xml                                                                                  |   15 
 app/src/main/res/xml/devicesettings_screenorientation.xml                                                                                                 |   12 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/soundcore/SoundcorePacket.java                                                     |  112 
 app/src/main/res/xml/devicesettings_root_generic.xml                                                                                                      |    9 
 app/src/main/res/drawable/ic_verified.xml                                                                                                                 |   13 
 app/src/main/res/values-et/strings.xml                                                                                                                    |  856 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/PaiChartFragment.java                                                            |  477 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/ActivityBase.java                                      |   38 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/BatteryInfoChartFragment.java                                                           |  292 
 app/src/main/res/drawable/ic_activity_running.xml                                                                                                         |   10 
 app/src/main/res/drawable/ic_activity_surfing.xml                                                                                                         |   14 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBSummaryOfDay.java                                                                           |   44 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitcor/AmazfitCorFirmwareInfo.java                                       |  104 
 src/mainline/fastlane/metadata/android/en-US/changelogs/224.txt                                                                                           |    1 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/BatteryState.java                                                                            |   27 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiWorkoutType.java                                                              |   89 
 app/src/main/res/layout/layout_widget_heartrate_average.xml                                                                                               |   31 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/watchs1active/XiaomiWatchS1ActiveCoordinator.java                                   |   66 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/honorband4/HonorBand4Coordinator.java                                               |   46 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/divoom/PixooInstallHandler.java                                                            |   50 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitMonitoringHrData.java                                       |   37 
 LICENSE                                                                                                                                                   |  661 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/activity/impl/WorkoutGpsParser.java                                         |  180 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/venu/GarminVenuSq2Coordinator.java                                          |   18 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/RecordData.java                                                         |  291 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/honormagicwatch2/HonorMagicWatch2Coordinator.java                                   |   51 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/GoalStepsSetting.java                         |   42 
 app/src/main/res/layout/item_app_blacklist.xml                                                                                                            |   83 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetFileDownloadCompleteRequest.java                                |   54 
 app/src/nightly/res/mipmap-anydpi-v26/ic_launcher.xml                                                                                                     |    5 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/fitpro/FitProDeviceSupport.java                                                    | 1461 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/fieldDefinitions/FieldDefinitionCoordinate.java                         |   31 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitpop/AmazfitPopFWHelper.java                                                   |   40 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendDeviceReportThreshold.java                                     |   41 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSpecificSettingsFragment.java                                      | 1472 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casio/gbx100/GetConfigurationOperation.java                                        |  174 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/DownloadFileRequest.java                                   |  117 
 app/src/main/res/xml/devicesettings_prefix_notification_with_app.xml                                                                                      |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/cmfwatchpro/samples/CmfHeartRateSampleProvider.java                                        |   56 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiSupportProvider.java                                                  | 2514 
 app/src/main/res/xml/devicesettings_keep_activity_data_on_device.xml                                                                                      |   10 
 app/src/nopebble/res/mipmap-anydpi-v26/ic_launcher.xml                                                                                                    |    5 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/Notifications.java                                                          |  300 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/honorband6/HonorBand6Coordinator.java                                               |   46 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsLoyaltyCardService.java                                |  264 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btbr/actions/SetProgressAction.java                                                        |   73 
 app/src/main/res/drawable/ic_create.xml                                                                                                                   |   10 
 app/src/main/res/values-fi/strings.xml                                                                                                                    |  664 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgtr2/AmazfitGTR2eCoordinator.java                                             |  148 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/WaitAction.java                                                               |   43 
 app/src/main/res/drawable-hdpi/ic_languagepack.png                                                                                                        |    0 
 app/src/main/res/xml/devicesettings_app_logs_start_stop.xml                                                                                               |   26 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminSettingsCustomizer.java                                                       |  262 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/Ephemeris.java                                                              |  114 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/GetVibrationStrengthRequest.java                           |   45 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/ArrayUtils.java                                                                               |  123 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/liveview/LiveviewCoordinator.java                                                          |  138 
 app/src/main/res/xml/devicesettings_donotdisturb_withauto.xml                                                                                             |   34 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/zetime/ZeTimeSampleProvider.java                                                           |   85 
 app/src/main/res/layout/layout_widget_intensity_total2.xml                                                                                                |   47 
 src/mainline/fastlane/metadata/android/en-US/changelogs/145.txt                                                                                           |    5 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qc35/QC35Coordinator.java                                                                  |   91 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/forerunner/GarminForerunner955Coordinator.java                              |   18 
 src/banglejs/fastlane/metadata/android/en-US/title.txt                                                                                                    |    1 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/PackageConfigHelper.java                                                           |  156 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiUploadManager.java                                                    |  211 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitZonesTarget.java                                            |   47 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiWorkoutGbParser.java                                                  | 1004 
 app/src/main/res/layout/activity_qhybrid_calibration.xml                                                                                                  |   96 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/GenericItem.java                                                                             |  153 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SetActivateOnLiftRequest.java                                      |   60 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/ZeppOsSupport.java                                                    | 1700 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendWeatherCurrentRequest.java                                     |   76 
 app/src/main/res/drawable/ic_device_lovetoy.xml                                                                                                           |   35 
 src/mainline/fastlane/metadata/android/en-US/changelogs/126.txt                                                                                           |    2 
 app/src/main/res/layout/activity_commute_actions.xml                                                                                                      |   36 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/protobuf/messagefields/Int32MessageField.java                                                 |   24 
 app/src/main/assets/fossil_hr/icRainChance.rle                                                                                                            |    1 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiSppPacketV2.java                                                      |  514 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/alertnotification/OverflowStrategy.java                                      |   22 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/fieldDefinitions/FieldDefinitionArray.java                              |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitDeviceSettings.java                                         |  102 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/util/TimeUtil.java                                             |   31 
 app/src/main/res/xml/devicesettings_root_calendar.xml                                                                                                     |    9 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminHrvSummarySampleProvider.java                                                 |   56 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitgts2/AmazfitGTS2Support.java                                          |   66 
 app/src/main/res/xml/devicesettings_custom_deviceicon.xml                                                                                                 |   10 
 app/src/main/res/xml/charts_preferences.xml                                                                                                               |  151 
 src/mainline/fastlane/metadata/android/ru/title.txt                                                                                                       |    1 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/cmfwatchpro/samples/CmfSpo2SampleProvider.java                                             |   56 
 app/src/main/res/xml/devicesettings_huami2021_shortcut_cards.xml                                                                                          |   13 
 app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml                                                                                                        |    6 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/musicmanager/MusicManagerActivity.java                                                  |  720 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/asteroidos/AsteroidOSDeviceCoordinator.java                                                |   98 
 app/src/main/res/drawable/ic_calendar_from.xml                                                                                                            |    3 
 app/src/main/res/drawable/ic_activity_waterskiing.xml                                                                                                     |   16 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/time/BandTime.java                                              |   78 
 app/src/main/aidl/lineageos/weatherservice/IWeatherProviderServiceClient.aidl                                                                             |   25 
 app/src/main/res/drawable-hdpi/ic_watchapp.png                                                                                                            |    0 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractBLEDeviceCoordinator.java                                                          |   24 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiConnectionSupport.java                                                |   37 
 app/src/main/res/xml/devicesettings_moondrop_space_travel_equalizer.xml                                                                                   |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/roidmi/Roidmi3Coordinator.java                                                             |   68 
 app/src/main/res/xml/changelog_master.xml                                                                                                                 |  738 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/SedentaryReminderIntervalCommand.java                                       |   85 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetDeviceStatusRequest.java                                        |   59 
 src/mainline/fastlane/metadata/android/en-US/changelogs/219.txt                                                                                           |    9 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/SerbianTransliterator.java                                                      |   68 
 app/src/main/res/drawable/ic_brightness_2.xml                                                                                                             |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminPaiSampleProvider.java                                                        |  241 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/RequestBondingCommand.java                                                  |   44 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetStepDataCountRequest.java                                       |   61 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btbr/TransactionBuilder.java                                                               |   86 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/WorkoutScreen.java                    |   67 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitAlarmSettings.java                                          |   29 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/oppo/OppoEncoAir2Coordinator.java                                                          |   33 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/SonyHeadphonesCapabilities.java                                            |   49 
 app/src/main/res/xml/devicesettings_relax_firmware_checks.xml                                                                                             |   10 
 src/mainline/fastlane/metadata/android/en-US/changelogs/154.txt                                                                                           |    5 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/SonyWena3DeviceSupport.java                                             | 1091 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventSendBytes.java                                                           |   28 
 src/mainline/fastlane/metadata/android/en-US/changelogs/197.txt                                                                                           |    7 
 app/src/main/res/drawable/level_list_battery.xml                                                                                                          |   30 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/data/DashboardStressData.java                                                 |   85 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/tlw64/TLW64SampleProvider.java                                                             |   85 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/WidgetAlarmsActivity.java                                                               |  144 
 app/src/main/res/drawable-xxhdpi/ic_watchface.png                                                                                                         |    0 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebbleInstallable.java                                                              |   47 
 app/src/main/res/xml/devicesettings_redmibuds5pro_headphones.xml                                                                                          |   50 
 app/src/main/aidl/net/osmand/aidlapi/gpx/AGpxBitmap.aidl                                                                                                  |    3 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/forerunner/GarminForerunner245Coordinator.java                              |   18 
 app/src/main/res/xml/devicesettings_connect_two_devices.xml                                                                                               |   11 
 app/src/main/res/xml/devicesettings_soundcore_touch_options.xml                                                                                           |  107 
 src/banglejs/fastlane/metadata/android/en-US/images/phoneScreenshots/B1-AppManager.png                                                                    |    0 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbip5/AmazfitBip5Coordinator.java                                              |   81 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/fenix/GarminFenix6Coordinator.java                                          |   18 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/GetFirmwareInfoRequest.java                                         |   64 
 app/src/main/res/drawable/ic_activity_curling.xml                                                                                                         |   15 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/SystemEventMessage.java                                            |   49 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/SetTimeRequest.java                                        |   42 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/AlarmSettings.java                    |  111 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/colmi/AbstractColmiR0xCoordinator.java                                                     |  233 
 app/src/main/assets/app_config/configure.html                                                                                                             |  102 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/AppNotificationType.java                                                                     |  197 
 app/src/main/res/values-my/strings.xml                                                                                                                    |   79 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/SetStepGoalRequest.java                                    |   44 
 src/mainline/fastlane/metadata/android/en-US/changelogs/232.txt                                                                                           |   77 
 app/src/main/res/mipmap-hdpi/ic_launcher_round.png                                                                                                        |    0 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/GBWorldClockListAdapter.java                                                               |  132 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/services/XiaomiPhonebookService.java                                        |  165 
 app/src/main/res/layout/item_with_details_horizontal.xml                                                                                                  |   51 
 app/src/main/res/values-eu/strings.xml                                                                                                                    |    2 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/bandwpseries/BandWBLEProfile.java                                                  |   88 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/EventBase.java                                         |   37 
 app/src/main/res/drawable/ic_device_default.xml                                                                                                           |   24 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/galaxy_buds/GalaxyBudsGenericCoordinator.java                                              |  144 
 app/src/test/java/nodomain/freeyourgadget/gadgetbridge/test/HexToBinaryInputStream.java                                                                   |   52 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/commute/CommuteConfigPutRequest.java                    |   49 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ActivitySummariesFilter.java                                                            |  481 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/discovery/GBScanEventProcessor.java                                                     |  262 
 app/src/main/res/xml/devicesettings_donotdisturb_withauto_and_always.xml                                                                                  |   31 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiSampleProvider.java                                                           |  540 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/protobuf/messagefields/NestedMessageField.java                                                |   43 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiWorkoutStatus.java                                                      |   45 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/message/incoming/IncomingMessageHandlerFactory.java  |   86 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/workouts/entries/ActivitySummaryProgressEntry.java                                      |   78 
 app/src/test/java/nodomain/freeyourgadget/gadgetbridge/database/EntitiesTest.java                                                                         |  205 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband6/MiBand6FWHelper.java                                                         |   40 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/protobuf/messagefields/Int64MessageField.java                                                 |   24 
 src/banglejs/fastlane/metadata/android/en-US/full_description.txt                                                                                         |    6 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/SampleProvider.java                                                                        |  122 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/instinct/GarminInstinct2SSolarCoordinator.java                              |   18 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/status/DeviceInfo.java                                 |   43 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/huaweiwatchgt4/HuaweiWatchGT4Coordinator.java                                       |   52 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/hama/fit6900/Message.java                                                                  |  440 
 app/src/main/res/values-fr/strings.xml                                                                                                                    | 3663 
 app/src/main/res/menu/file_manager_file.xml                                                                                                               |    6 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/welcome/WelcomeFragmentOverview.java                                                    |   42 
 app/src/main/res/xml/devicesettings_sony_headphones_ambient_sound_control_button_modes.xml                                                                |   11 
 app/src/main/res/drawable/ic_mood_bad.xml                                                                                                                 |    5 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/http/WeatherHandler.java                                                    |  439 
 app/src/main/res/layout/activity_configure_world_clocks.xml                                                                                               |   27 
 app/src/main/res/drawable/ic_help_outline.xml                                                                                                             |   11 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SetMusicRequest.java                                               |  103 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/wena3/SonyWena3HeartRateSampleProvider.java                                           |   58 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/agps/GarminAgpsDataType.java                                                |   25 
 app/lint-baseline.xml                                                                                                                                     | 16179 +
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/redmibuds5pro/protocol/MessageType.java                                            |   35 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/FitFile.java                                                            |  247 
 app/src/main/assets/womp/checkin.html                                                                                                                     |   50 
 app/src/nopebble/res/values/strings.xml                                                                                                                   |    9 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitvergel/AmazfitVergeLFirmwareInfo.java                                 |  118 
 app/src/main/res/layout/activity_qhybrid_file_management.xml                                                                                              |   51 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitLap.java                                                    |   72 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitbips/AmazfitBipSFirmwareInfo.java                                     |  132 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ActivityListingDashboard.java                                                    |  329 
 app/src/main/java/ru/gelin/android/weather/notification/ParcelableWeather2.java                                                                           |  291 
 app/src/main/res/xml/devicesettings_notifications_enable.xml                                                                                              |   11 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/NotificationsHandler.java                                                   |  521 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminPreferences.java                                                              |   27 
 app/src/main/res/drawable/ic_activity_badmington.xml                                                                                                      |   32 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/p2p/HuaweiP2PTrackService.java                                              |  106 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendCameraRemoteSetupEvent.java                                    |   54 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HeartRateZonesConfig.java                                                           |  287 
 app/src/main/aidl/lineageos/weather/WeatherLocation.aidl                                                                                                  |   19 
 app/src/main/res/xml/devicesettings_device_intents.xml                                                                                                    |   10 
 app/src/main/res/xml/devicesettings_fitpro.xml                                                                                                            |   12 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/GBReminderListAdapter.java                                                                 |  156 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/vesc/VescDeviceSupport.java                                                        |  338 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AboutActivity.java                                                                      |   65 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/VibrateRequest.java                                        |   44 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/defines/DisplayOrientation.java               |   22 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/colmi/samples/ColmiSleepStageSampleProvider.java                                           |   56 
 app/src/main/res/drawable/ic_engineering.xml                                                                                                              |   19 
 app/src/main/res/values/values.xml                                                                                                                        |  148 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSpecificSettingsScreen.java                                        |   59 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_42.java                                                         |   38 
 app/src/main/res/.gitignore                                                                                                                               |    1 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/application/ApplicationInformation.java                 |   47 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/LatvianTransliterator.java                                                      |   30 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/PendingFileProvider.java                                                                   |  100 
 app/src/main/res/xml/devicesettings_wear_sensor_toggle.xml                                                                                                |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/jyou/y5/Y5Coordinator.java                                                                 |  161 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetProductInformationRequest.java                                  |   57 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/notification/NotificationRemoval.java                  |   44 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/Logging.java                                                                                       |  208 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendFileUploadComplete.java                                        |   47 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsAgpsService.java                                       |  108 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hplus/HPlusDataRecord.java                                                         |   79 
 app/src/main/res/xml/devicesettings_sony_headphones_audio_upsampling.xml                                                                                  |    9 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceMusicPlaylist.java                                                                    |   41 
 app/src/main/res/xml/devicesettings_dateformat.xml                                                                                                        |   12 
 app/src/main/res/layout/fragment_welcome_docs_source.xml                                                                                                  |   43 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/casio/gbx100/CasioGBX100SampleProvider.java                                                |   80 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiHeartRateRestingSampleProvider.java                                           |  113 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/supercars/SuperCarsConstants.java                                                          |  101 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SetTemperatureUnitSetting.java                                     |   38 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/ServerTransactionBuilder.java                                                         |   87 
 app/src/main/res/drawable/ic_search.xml                                                                                                                   |   12 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/NotificationListener.java                                                           | 1113 
 src/mainline/fastlane/metadata/android/en-US/changelogs/171.txt                                                                                           |    8 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/UploadFileRequest.java                                     |  113 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/image/AssetImage.java                                   |   90 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DashboardFragment.java                                                                  |  532 
 app/src/main/res/xml/devicesettings_galaxy_buds.xml                                                                                                       |  112 
 app/src/main/res/xml/devicesettings_soundcore_motion300.xml                                                                                               |   25 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/CheckInitializedAction.java                                                   |   46 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/WorkoutScreenList.java                |   48 
 src/mainline/fastlane/metadata/android/pt-PT/short_description.txt                                                                                        |    1 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetMusicInfoParams.java                                            |   44 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/vibratissimo/VibratissimoSupport.java                                              |  162 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/TinyWeatherForecastGermanyReceiver.java                                             |   55 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SetTimeRequest.java                                                 |   64 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/HybridHRActivitySampleProvider.java                                                |   80 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/Contact.java                                                                                 |   32 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/SonySWR12Util.java                                                       |   38 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/vesc/VescBaseDeviceSupport.java                                                    |   45 
 app/src/main/res/xml/devicesettings_key_vibration.xml                                                                                                     |    9 
 app/src/main/res/drawable/ic_activity_outdoor_walking.xml                                                                                                 |   10 
 app/src/main/assets/ic_galaxy_buds_live_l.svg                                                                                                             |   66 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/banglejs/BangleJSSettingsCustomizer.java                                                   |  141 
 app/src/main/res/drawable/ic_device_vesc.xml                                                                                                              |   26 
 src/mainline/fastlane/metadata/android/en-US/changelogs/117.txt                                                                                           |    9 
 src/mainline/fastlane/metadata/android/en-US/images/phoneScreenshots/70-SportActivities.png                                                               |    0 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/activity/impl/XiaomiComplexActivityParser.java                              |  116 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/ble/PebbleLESupport.java                                                    |  265 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsServicesService.java                                   |   96 
 app/src/main/res/drawable/ic_music.xml                                                                                                                    |   21 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/wena3/SonyWena3StressSampleProvider.java                                              |   73 
 app/src/main/res/drawable/ic_buds_pro_left.xml                                                                                                            |   17 
 src/mainline/fastlane/metadata/android/en-US/changelogs/141.txt                                                                                           |    3 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/message/WithingsMessage.java                         |   64 
 app/src/main/res/drawable/ic_wena_icon_left.xml                                                                                                           |   24 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/redmibuds5pro/RedmiBuds5ProDeviceSupport.java                                      |   46 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandFWHelper.java                                                                 |  151 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/roidmi/RoidmiConst.java                                                                    |   36 
 app/src/main/res/xml/devicesettings_root_sound.xml                                                                                                        |    9 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/binary_sensor/protocol/constants/ResultCode.java                                   |   35 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/ActivityType.java                                      |   39 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ItemWithDetails.java                                                                         |   38 
 app/src/main/res/drawable/ic_tabs.xml                                                                                                                     |   10 
 src/banglejs/fastlane/metadata/android/pt-PT/short_description.txt                                                                                        |    1 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendAppDelete.java                                                 |   28 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/translation/TranslationsGetRequest.java                 |   71 
 app/src/main/res/xml/devicesettings_header_connection.xml                                                                                                 |    6 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgtr3/AmazfitGTR3Coordinator.java                                              |   56 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/HeartPulseSampleProvider.java                                                              |   55 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/TimeSetting.java                              |   41 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/conversation/SetupFinishedHandler.java               |   35 
 app/src/main/res/xml/devicesettings_lock_unlock.xml                                                                                                       |    9 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/redmibuds5pro/RedmiBuds5ProIOThread.java                                           |   66 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/notification/DismissTextNotificationRequest.java           |   25 
 app/src/main/res/drawable/ic_horizontal_rule.xml                                                                                                          |   11 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/fenix/GarminFenix7XCoordinator.java                                         |   18 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lenovo/watchxplus/WatchXPlusSampleProvider.java                                            |  541 
 app/src/main/res/layout/activity_backup_restore_progress.xml                                                                                              |   62 
 src/mainline/fastlane/metadata/android/en-US/changelogs/228.txt                                                                                           |   28 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbip/AmazfitBipFWHelper.java                                                   |   43 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/http/HttpHandler.java                                                       |  140 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/logic/parsers/StressPacketParser.java                          |   81 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendEphemerisFileUploadChunk.java                                  |   36 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/HybridHRWatchfaceWidget.java                                                       |  217 
 app/src/main/res/xml/devicesettings_root_location.xml                                                                                                     |    9 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/CaloriesDailyFragment.java                                                       |  253 
 app/src/main/res/drawable-hdpi/ic_watchface.png                                                                                                           |    0 
 src/mainline/fastlane/metadata/android/en-US/changelogs/188.txt                                                                                           |    3 
 src/mainline/fastlane/metadata/android/en-US/changelogs/102.txt                                                                                           |    7 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/nut/NutCoordinator.java                                                                    |  172 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetFileDownloadInitRequest.java                                    |  122 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendEphemerisFileConsultResponse.java                              |   28 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitcheetahsquare/AmazfitCheetahSquareCoordinator.java                            |   95 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/cmfwatchpro/CmfCharacteristic.java                                                 |  329 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/itag/ITagConstants.java                                                                    |   26 
 app/src/main/aidl/lineageos/weather/IRequestInfoListener.aidl                                                                                             |   30 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendFileUploadHash.java                                            |   50 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/status/SetFileFlagsStatusMessage.java                              |   58 
 app/src/main/res/drawable/ic_auto_awesome.xml                                                                                                             |    5 
 app/src/main/assets/fossil_hr/widgetDate_preview.png                                                                                                      |    0 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/coordinators/SonyWH1000XM2Coordinator.java                                 |   56 
 app/src/main/res/xml/devicesettings_inactivity_extended.xml                                                                                               |   41 
 app/src/main/assets/fossil_hr/icHeart.rle                                                                                                                 |    2 
 app/src/test/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/TestHuaweiCrypto.java                                                               |   77 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/BarChartStackedTimeValueFormatter.java                                           |   55 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/wena3/SonyWena3ActivitySampleProvider.java                                            |  123 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivitySummaryParser.java                                                                   |   76 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/soflow/SoFlowSupport.java                                                          |  240 
 app/src/main/res/xml/devicesettings_amazfitbip3pro.xml                                                                                                    |   13 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/Time.java                             |   98 
 app/src/main/aidl/net/osmand/aidlapi/IOsmAndAidlInterface.aidl                                                                                            |   25 
 app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/notification/NotificationAttributeTest.java          |   56 
 src/mainline/fastlane/metadata/android/en-US/changelogs/215.txt                                                                                           |   23 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/femometer/FemometerVinca2SampleProvider.java                                               |   55 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/galaxy_buds/GalaxyBudsLiveDeviceCoordinator.java                                           |   72 
 app/src/main/res/values-cs/strings.xml                                                                                                                    | 3699 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/TimestampTranslation.java                                                        |   48 
 app/src/main/res/drawable/ic_voice.xml                                                                                                                    |    3 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/GetFirmwareInfoCommand.java                                                 |   76 
 app/src/main/res/values-sk/strings.xml                                                                                                                    |  566 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/RespiratoryRateFragmentAdapter.java                                                        |   32 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/activity/BehaviorSample.java                           |   57 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/PlayNotificationRequest.java                               |   86 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/widget/CustomWidget.java                                |   75 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/AlarmListSettings.java                        |   60 
 app/src/main/res/drawable/ic_device_thermometer.xml                                                                                                       |    6 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/AbstractFetchOperation.java                                 |  376 
 app/src/main/res/drawable/ic_more_vert.xml                                                                                                                |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/alarm/BandAlarms.java                                           |   51 
 app/src/main/res/xml/devicesettings_send_app_notifications.xml                                                                                            |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BleIntentApi.java                                                                     |  241 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/authentication/PerformDevicePairingRequest.java         |   24 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/colmi/ColmiR0xDeviceSupport.java                                                   |  715 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/waspos/WaspOSCoordinator.java                                                              |  173 
 app/src/main/res/drawable/ic_events.xml                                                                                                                   |   10 
 app/src/main/res/drawable/ic_intensity_hollow.xml                                                                                                         |    3 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PBWInstallHandler.java                                                              |  213 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SetWearMessagePushRequest.java                                     |   60 
 app/src/nightly/res/mipmap-xxxhdpi/ic_launcher.png                                                                                                        |    0 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/soundcore/motion300/SoundcoreMotion300IOThread.java                                |   75 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/receivers/AutoConnectIntervalReceiver.java                                                 |  121 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiBinAppParser.java                                                             |  111 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiHeartRateMaxSampleProvider.java                                                 |   56 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebbleHealthSampleProvider.java                                                     |  149 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/file/FileLookupRequest.java                                |  150 
 app/src/main/res/drawable/ic_email.xml                                                                                                                    |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/forerunner/GarminForerunner255SMusicCoordinator.java                        |   18 
 app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/TestBtBRQueue.java                                                                         |   68 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/soundcore/liberty/TapAction.java                                                   |   19 
 app/src/main/res/layout/fragment_nested_tabs.xml                                                                                                          |   17 
 app/src/main/assets/fossil_hr/icSpO2.rle                                                                                                                  |    4 
 app/src/main/res/menu/activity_main_drawer.xml                                                                                                            |   50 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/MusicControl.java                                                           |  555 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendWeatherErrorRequest.java                                       |   62 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/dialogs/MaterialListPreferenceDialogFragment.java                                             |  117 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SetLanguageRequest.java                                             |   63 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/quickreply/QuickReplyConfirmationPutRequest.java        |   55 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSettingsActivity.java                                              |   45 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/workouts/entries/ActivitySummaryEntry.java                                              |   23 
 src/mainline/fastlane/metadata/android/en-US/full_description.txt                                                                                         |    9 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/prefs/PauseWhenTakenOff.java                                               |   46 
 app/src/main/res/drawable/ic_device_hplus_disabled.xml                                                                                                    |   35 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/huaweiwatchfit/HuaweiWatchFitCoordinator.java                                       |   41 
 app/src/main/res/drawable/ic_settings.xml                                                                                                                 |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/TextReceiverActivity.java                                                               |   80 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/liveview/LiveviewIoThread.java                                                     |   98 
 app/src/main/assets/fossil_hr/widgetActiveMins.bin                                                                                                        |    0 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/GlobalFITMessage.java                                                   |  602 
 .idea/icon.svg                                                                                                                                            |  574 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/fieldDefinitions/FieldDefinitionLanguage.java                           |   57 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/binary_sensor/activity/DataActivity.java                                                   |   86 
 app/src/main/res/values-ml/strings.xml                                                                                                                    |   89 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/WorkMode.java                                                               |   57 
 app/src/main/res/xml/devicesettings_goal_notification.xml                                                                                                 |   10 
 app/src/main/res/xml/devicesettings_time_sync.xml                                                                                                         |    9 
 app/src/main/res/xml/devicesettings_custom_emoji_font.xml                                                                                                 |   10 
 .artifacts/5fb7b17c-2744-457a-b1a3-a3b9fbd1ae6d/implementation_plan.artifact.md                                                                           |   24 
 app/src/main/res/drawable/ic_distance_total.xml                                                                                                           |    6 
 app/src/main/res/mipmap-xxxhdpi/ic_launcher.png                                                                                                           |    0 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/SleepAsAndroidFeature.java                                                                 |   11 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/Prefs.java                                                                                    |  255 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/alertnotification/AlertCategory.java                                         |   97 
 app/src/main/res/xml/devicesettings_root_audio.xml                                                                                                        |    9 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbipu/AmazfitBipUFWInstallHandler.java                                         |   49 
 app/src/main/res/layout/activity_discovery.xml                                                                                                            |   68 
 app/src/main/res/drawable/ic_galaxy_buds_live_r.xml                                                                                                       |   16 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/huaweiwatchgtcyber/HuaweiWatchGTCyberCoordinator.java                               |   47 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/conversation/ResponseHandler.java                    |   23 
 app/src/main/java/com/mobeta/android/dslv/DragSortCursorAdapter.java                                                                                      |  233 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/StatusPageOrderSetting.java                   |   41 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_35.java                                                         |   54 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/FileUtils.java                                                                                |  445 
 app/src/main/res/xml/devicesettings_dateformat_day_month_order.xml                                                                                        |   12 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/vesc/VescControlActivity.java                                                              |  251 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/AbstractSpo2Sample.java                                                                   |   45 
 app/src/main/res/drawable/ic_camera_remote.xml                                                                                                            |   16 
 app/src/main/assets/fossil_hr/widget_bg_double_circle.png                                                                                                 |    0 
 app/src/main/res/xml/devicesettings_allow_accept_reject_calls.xml                                                                                         |   17 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_45.java                                                         |   44 
 app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiSupportTest.java                                                        |   71 
 app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/TestDebugRequestParser.java                                                 |  401 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiDailySummarySampleProvider.java                                               |   56 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/jyou/JYouSampleProvider.java                                                               |   87 
 app/src/main/res/drawable/ic_activity_eliptical.xml                                                                                                       |   20 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/PreformattedXIndexLabelFormatter.java                                            |   38 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiLECoordinator.java                                                            |  269 
 app/src/main/res/drawable/ic_shortcut.xml                                                                                                                 |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/SimpleTransliterator.java                                                            |   69 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/agps/AgpsHandler.java                                                       |  143 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/cmfwatchpro/samples/CmfStressSampleProvider.java                                           |   56 
 app/src/main/java/org/bouncycastle/shaded/crypto/macs/CBCBlockCipherMac.java                                                                              |  229 
 src/mainline/fastlane/metadata/android/en-US/changelogs/158.txt                                                                                           |   12 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/HomeIconOrderSetting.java                     |   47 
 .gitea/pull_request_template.md                                                                                                                           |   16 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/GeorgianTransliterator.java                                                     |   40 
 app/src/main/res/xml/devicesettings_preview_message_in_title.xml                                                                                          |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/SilentModeReceiver.java                                                             |   48 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ContactDetails.java                                                                     |  154 
 app/src/main/res/layout/activity_list_dashboard_item.xml                                                                                                  |  132 
 app/src/main/res/xml/devicesettings_zetime_activity_info.xml                                                                                              |   22 
 src/mainline/fastlane/metadata/android/en-US/changelogs/96.txt                                                                                            |    8 
 app/src/main/res/drawable/ic_pressure.xml                                                                                                                 |    5 
 app/src/main/res/layout/activity_widget_screen_details.xml                                                                                                |  254 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pinetime/PineTimeDFUService.java                                                           |   35 
 app/src/nopebble/res/mipmap-xxhdpi/ic_launcher.png                                                                                                        |    0 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/widget/CustomWidgetElement.java                         |  108 
 app/src/main/res/menu/bottom_nav_menu.xml                                                                                                                 |   13 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/logic/parsers/HeartRatePacketParser.java                       |   78 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/welcome/WelcomeFragmentIntro.java                                                       |   74 
 app/src/main/res/drawable/ic_videogame.xml                                                                                                                |    4 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/ZeppOsActivityType.java                                               |  224 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/gatt_client/BleGattClientCoordinator.java                                          |   51 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/file/FilePutRawRequest.java                                |  241 
 app/src/main/res/drawable/ic_equalizer.xml                                                                                                                |    3 
 app/src/nightly/res/mipmap-xxhdpi/ic_launcher.png                                                                                                         |    0 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSpecificSettings.java                                              |  193 
 app/src/main/res/layout/item_alarm.xml                                                                                                                    |  143 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/AbstractHybridHRActivitySample.java                                                       |   40 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/deviceevents/SupportedFileTypesDeviceEvent.java                             |   19 
 app/src/main/res/xml/devicesettings_workout_activity_types.xml                                                                                            |   13 
 app/src/main/res/drawable/ic_activity_kitesurfing.xml                                                                                                     |   23 
 app/src/main/res/xml/devicesettings_galaxy_buds_2_pro.xml                                                                                                 |  214 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitSleepDataRaw.java                                           |   27 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_27.java                                                         |   38 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/honorband7/HonorBand7Coordinator.java                                               |   46 
 src/mainline/fastlane/metadata/android/en-US/images/phoneScreenshots/40-SleepPerDay.png                                                                   |    0 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceSupportFactory.java                                                                  |  159 
 src/mainline/fastlane/metadata/android/en-US/changelogs/167.txt                                                                                           |    6 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendWeatherForecastRequest.java                                    |   94 
 app/src/main/res/drawable/ic_password.xml                                                                                                                 |    5 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/waspos/WaspOSConstants.java                                                                |   28 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/casio/gbx100/CasioGBX100DeviceCoordinator.java                                             |  159 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/DisconnectNotification.java                                                 |   45 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/casio/gb6900/CasioGB6900DeviceCoordinator.java                                             |  152 
 app/src/main/proto/garmin/gdi_data_transfer_service.proto                                                                                                 |   30 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/LiveHeartRate.java                    |   48 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/oppo/OppoHeadphonesProtocol.java                                                   |  363 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/honorwatchgs3/HonorWatchGS3Coordinator.java                                         |   51 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/wena3/SonyWena3CaloriesSampleProvider.java                                            |   56 
 app/src/main/java/org/bouncycastle/shaded/crypto/params/KeyParameter.java                                                                                 |   56 
 src/mainline/fastlane/metadata/android/en-US/changelogs/201.txt                                                                                           |    2 
 app/src/main/res/xml/devicesettings_pebble_calls_notifications.xml                                                                                        |   19 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lenovo/LenovoWatchPairingActivity.java                                                     |  166 
 app/src/main/res/layout/activity_widget_screens_list.xml                                                                                                  |   27 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GBChangeLog.java                                                                              |  115 
 src/mainline/fastlane/metadata/android/en-US/changelogs/210.txt                                                                                           |   19 
 app/src/nightly/res/mipmap-xhdpi/ic_launcher_foreground.png                                                                                               |    0 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/conversation/ConversationQueue.java                  |  100 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/CheckSums.java                                                                                |  193 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandPairingActivity.java                                                          |  266 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbip3/AmazfitBip3FWHelper.java                                                 |   39 
 app/src/main/assets/womp/siteweight.html                                                                                                                  |   65 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiSettingsCustomizer.java                                                       |   92 
 app/src/main/res/drawable/ic_device_thermometer_disabled.xml                                                                                              |    6 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/HrvSummarySample.java                                                                        |   80 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendEphemerisFileListResponse.java                                 |   30 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/SetVibrationStrengthRequest.java                           |   45 
 app/src/main/java/org/bouncycastle/shaded/crypto/modes/CCMModeCipher.java                                                                                 |    6 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/SchemaMigration.java                                                               |   80 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/womp/WompKeepAliveService.java                                                                     |  329 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiSppSupport.java                                                       |  303 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandDateConverter.java                                                            |   98 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/huaweiwatchgt3/HuaweiWatchGT3Coordinator.java                                       |   47 
 app/src/main/res/drawable/ic_block.xml                                                                                                                    |   10 
 app/src/main/res/drawable/ic_filter_3.xml                                                                                                                 |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/prefs/SpeakToChatConfig.java                                               |  124 
 app/src/main/res/drawable/ic_device_galaxy_buds_disabled.xml                                                                                              |   45 
 app/src/main/res/layout/fragment_vo2max.xml                                                                                                               |  122 
 app/src/main/res/xml/shared_paths.xml                                                                                                                     |    8 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/ActivateDisplayOnLift.java                                                           |   24 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/FetchHeartRateManualOperation.java                          |  118 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/prefs/TouchSensor.java                                                     |   46 
 app/src/main/res/layout/heart_rate_dialog.xml                                                                                                             |   84 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/logic/StepsSampleCollection.java                               |   30 
 app/src/main/res/xml/devicesettings_always_on_display.xml                                                                                                 |   45 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/capabilities/WorkoutDetectionCapability.java                                                       |   36 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/json/JsonPutRequest.java                                |   32 
 src/mainline/fastlane/metadata/android/en-US/changelogs/176.txt                                                                                           |    8 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsPhoneService.java                                      |  211 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SetSedentaryReminderIntervalRequest.java                            |   65 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/deviceevents/WeatherRequestDeviceEvent.java                                 |   18 
 src/mainline/fastlane/metadata/android/de-DE/images/featureGraphic.jpg                                                                                    |    0 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/soundcore/motion300/SoundcoreMotion300Coordinator.java                                     |   93 
 app/src/main/res/layout/activity_world_clock_details.xml                                                                                                  |  161 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/GBDeviceAdapterv2.java                                                                     | 1520 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/FitDataMessage.java                                                |   61 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BleNamesResolver.java                                                                 |  234 
 app/src/main/java/org/bouncycastle/shaded/crypto/CryptoException.java                                                                                     |   48 
 app/src/main/res/xml/devicesettings_test_features.xml                                                                                                     |   12 
 app/src/main/res/values-uk/strings.xml                                                                                                                    | 2958 
 app/src/test/java/nodomain/freeyourgadget/gadgetbridge/util/gpx/GPXParserTest.java                                                                        |   82 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/idasen/IdasenDeviceSupport.java                                                    |  208 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/binary_sensor/protocol/message/Message.java                                        |   49 
 app/src/main/res/drawable/ic_save.xml                                                                                                                     |    9 
 app/src/main/res/layout/activity_permissions.xml                                                                                                          |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/communicator/v1/CommunicatorV1.java                                         |  119 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/Huami2021Handler.java                                                        |   21 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbalance/AmazfitBalanceCoordinator.java                                        |   86 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsShortcutCardsService.java                              |  275 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/SetCurrentStepCountRequest.java                            |   40 
 app/src/main/assets/fossil_hr/complication_layout.json                                                                                                    |    1 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiGpsParser.java                                                                |  101 
 app/src/main/res/drawable/ic_developer_mode.xml                                                                                                           |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/BitmapUtil.java                                                                               |  397 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/SleepFragmentAdapter.java                                                                  |   30 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/alertnotification/AlertLevel.java                                            |   43 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ActivitySleepChartFragment.java                                                  |  203 
 app/src/main/res/drawable/ic_phone_msg.xml                                                                                                                |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/WorkoutGpsState.java                  |   43 
 src/mainline/fastlane/metadata/android/en-US/changelogs/190.txt                                                                                           |    9 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventAppInfo.java                                                             |   24 
 app/src/main/res/xml/devicesettings_transliteration.xml                                                                                                   |   13 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/adapter/WatchAdapter.java                                                  |  179 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/notification/RequestedNotificationAttribute.java     |   58 
 src/mainline/fastlane/metadata/android/en-US/changelogs/113.txt                                                                                           |    4 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/GBAlarmListAdapter.java                                                                    |  166 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/GarminTimeUtils.java                                                        |   29 
 app/src/main/res/values-tr/strings.xml                                                                                                                    | 3546 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/SonySWR12HandlerThread.java                                              |  152 
 app/src/main/assets/fossil_hr/icActiveMins.rle                                                                                                            |    3 
 app/src/main/res/xml/sleep_alarm_widget_info.xml                                                                                                          |   11 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/sleepasandroid/SleepAsAndroidReceiver.java                                          |   23 
 app/src/main/res/layout/activity_weather_notification.xml                                                                                                 |   26 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/AbstractDashboardVO2MaxWidget.java                                            |  124 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/forerunner/GarminForerunner255Coordinator.java                              |   18 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitFieldDescription.java                                       |   47 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/redmismartband2/RedmiSmartBand2Coordinator.java                                     |   49 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitvergel/AmazfitVergeLCoordinator.java                                          |  134 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/opentracks/Track.java                                                               |  205 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/SettingsActivity.java                                                                   |  588 
 app/src/main/assets/fossil_hr/widget2ndTZ.bin                                                                                                             |    0 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/notification/NotificationFilterPutHRRequest.java        |  149 
 app/src/main/res/drawable/ic_device_galaxy_buds_live.xml                                                                                                  |   36 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/casio/Casio2C2DDeviceCoordinator.java                                                      |   28 
 app/src/main/assets/ic_no_watch.svg                                                                                                                       |  121 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/cycling_sensor/support/CyclingSensorSupport.java                                   |  268 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiLanguageType.java                                                       |   52 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgts2/AmazfitGTS2eFWInstallHandler.java                                        |   49 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventVersionInfo.java                                                         |   39 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/prefs/AmbientSoundControl.java                                             |   87 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btbr/SocketCallback.java                                                                   |   34 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/welcome/WelcomePageIndicator.java                                                       |  135 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminSpo2SampleProvider.java                                                       |   56 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/wena3/SonyWena3Vo2SampleProvider.java                                                 |   56 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/DashboardStressSimpleWidget.java                                              |   70 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/activity/Vo2MaxSample.java                             |   29 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfittrexpro/AmazfitTRexProSupport.java                                    |   43 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/fitpro/FitProSampleProvider.java                                                           |   99 
 src/mainline/fastlane/metadata/android/en-US/changelogs/106.txt                                                                                           |    1 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSettingsPreferenceConst.java                                       |  602 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetBondParamsRequest.java                                          |   64 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/notification/PlayNotificationRequest.java                  |   94 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/OpenFwAppInstallerActivity.java                                                         |   93 
 app/src/main/res/xml/devicesettings_withingssteelhr.xml                                                                                                   |   22 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/notification/defines/NotificationFlags.java            |   29 
 app/src/main/res/xml/devicesettings_zetime_display.xml                                                                                                    |   31 
 app/src/main/assets/ic_device_galaxy_buds_live.svg                                                                                                        |  166 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/OmniJawsObserver.java                                                               |  194 
 app/src/main/res/xml/devicesettings_world_clocks.xml                                                                                                      |    8 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/redmiwatch3/RedmiWatch3Coordinator.java                                             |   60 
 app/src/test/java/nodomain/freeyourgadget/gadgetbridge/util/RangeMapTest.java                                                                             |   51 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventLEDColor.java                                                            |   25 
 app/src/main/res/values-w820dp/dimens.xml                                                                                                                 |    6 
 app/src/test/java/nodomain/freeyourgadget/gadgetbridge/test/StringUtilsTest.java                                                                          |   70 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/capabilities/password/PasswordCapabilityImpl.java                                                  |  153 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/CustomBarChart.java                                                              |   76 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/FetchPaiOperation.java                                      |  147 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/activity/XiaomiActivityParser.java                                          |  132 
 app/src/main/res/xml/devicesettings_find_phone.xml                                                                                                        |   24 
 app/src/main/res/drawable/ic_delete.xml                                                                                                                   |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandSettingsCustomizer.java                                                       |   96 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/protobuf/ProtobufUtils.java                                                                   |   40 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/GBContactListAdapter.java                                                                  |  110 
 app/src/main/res/xml/devicesettings_miband2_display.xml                                                                                                   |   21 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgtr2/AmazfitGTR2eFWHelper.java                                                |   40 
 app/src/main/res/drawable/ic_launcher_foreground.xml                                                                                                      |   21 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/ActivateDisplayOnLiftSensitivity.java                                                |   22 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/nut/NutSupport.java                                                                |  488 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/jyou/JYouConstants.java                                                                    |   62 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/GarminByteBufferReader.java                                                 |   82 
 app/src/main/res/xml/devicesettings_device_actions.xml                                                                                                    |   65 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GBToStringBuilder.java                                                                        |   50 
 app/src/main/res/drawable-xxhdpi/ic_languagepack.png                                                                                                      |    0 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/SilentMode.java                                                                               |   99 
 app/src/main/res/drawable-hdpi/ic_notification_low_battery.png                                                                                            |    0 
 app/src/main/res/layout/fragment_pai_chart.xml                                                                                                            |  192 
 app/src/main/res/values/dslv_attrs.xml                                                                                                                    |   39 
 app/src/main/res/drawable/ic_settings_remote.xml                                                                                                          |   10 
 app/src/main/res/layout/activity_summary_dashboard_item.xml                                                                                               |  476 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitbipupro/AmazfitBipUProSupport.java                                    |   34 
 app/src/main/res/drawable/ic_humidity_mid.xml                                                                                                             |   10 
 app/src/main/res/xml/devicesettings_fake_timeoffset.xml                                                                                                   |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiFwHelper.java                                                         |  354 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/NavigationInfoSpec.java                                                                      |   44 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitSleepStage.java                                             |   33 
 app/src/main/res/drawable/ic_heart.xml                                                                                                                    |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/ConditionalWriteAction.java                                                   |   46 
 app/src/main/res/xml/devicesettings_loyalty_cards.xml                                                                                                     |    7 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/receivers/GBCallControlReceiver.java                                                       |   92 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventUpdateDeviceInfo.java                                                    |   32 
 app/src/main/res/drawable/ic_device_pinetime_disabled.xml                                                                                                 |   67 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/SpeedZonesFragment.java                                                          |  174 
 src/mainline/fastlane/metadata/android/en-US/changelogs/131.txt                                                                                           |    5 
 app/src/main/res/xml/devicesettings_battery_discharge_5.xml                                                                                               |  169 
 app/src/main/res/mipmap-hdpi/ic_launcher.png                                                                                                              |    0 
 app/src/main/assets/sports_pingpong.svg                                                                                                                   |  167 
 app/src/nopebble/res/mipmap-xhdpi/ic_launcher_foreground.png                                                                                              |    0 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/OTAResetRequest.java                                       |   26 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pinetime/PineTimeActivitySampleProvider.java                                               |   94 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/DeviceConfig.java                                                           | 1756 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgts2/AmazfitGTS2MiniFWInstallHandler.java                                     |   49 
 app/src/main/res/layout/steps_streak_current_line_layout.xml                                                                                              |   52 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/supercars/ControlActivity.java                                                             |  294 
 app/src/main/res/xml/devicesettings_amazfitgtsgtr2.xml                                                                                                    |   13 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgtr3pro/AmazfitGTR3ProCoordinator.java                                        |   58 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/FieldDefinitionFactory.java                                             |   86 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/test/samples/AbstractTestSampleProvider.java                                               |   48 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/services/XiaomiNotificationService.java                                     |  570 
 app/src/main/res/layout/steps_streak_total_line_layout.xml                                                                                                |   50 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/file/FilePutRequest.java                                   |   61 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/colmi/samples/ColmiHrvValueSampleProvider.java                                             |   56 
 app/src/nightly/res/mipmap-hdpi/ic_launcher.png                                                                                                           |    0 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/NotificationSubscriptionMessage.java                               |   42 
 src/mainline/fastlane/metadata/android/pt-PT/full_description.txt                                                                                         |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/roidmi/Roidmi3Protocol.java                                                        |  156 
 app/src/main/res/drawable/ic_calories.xml                                                                                                                 |    3 
 app/src/main/res/drawable/ic_touch.xml                                                                                                                    |    3 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/fenix/GarminFenix6SProCoordinator.java                                      |   18 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casio/gb6900/InitOperation.java                                                    |  168 
 app/src/main/res/layout/sleep_alarm_widget.xml                                                                                                            |   18 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/Vo2MaxSampleProvider.java                                                                  |   15 
 app/src/test/java/nodomain/freeyourgadget/gadgetbridge/externalevents/NotificationListenerTest.java                                                       |  111 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/binary_sensor/protocol/constants/SensorState.java                                  |   35 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminIntensityMinutesSampleProvider.java                                           |   56 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/update/UpdateFirmwareOperation2020.java                           |  303 
 app/src/test/java/nodomain/freeyourgadget/gadgetbridge/test/HexToBinaryInputStreamTest.java                                                               |   50 
 app/src/test/resources/GPXExporterTest-SampleTracksHR.csv                                                                                                 |   14 
 app/src/main/res/drawable/ic_arrow_upward.xml                                                                                                             |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/CyclingChartFragment.java                                                        |  295 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/status/WeatherDay.java                                 |   46 
 app/src/main/res/xml/devicesettings_autoheartrate.xml                                                                                                     |   45 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/status/NotificationDataStatusMessage.java                          |   51 
 app/src/main/res/layout/activity_binary_sensor_data.xml                                                                                                   |   32 
 app/src/main/res/layout/device_itemv2.xml                                                                                                                 |  546 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casio/gb6900/SetAlarmOperation.java                                                |  135 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/AlwaysOnDisplay.java                                                                 |   24 
 app/src/main/res/drawable-mdpi/ic_notification.png                                                                                                        |    0 
 src/mainline/fastlane/metadata/android/en-US/changelogs/181.txt                                                                                           |    8 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/IntentListener.java                                                          |   26 
 app/src/main/res/drawable/ic_star_gray.xml                                                                                                                |    9 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/AlarmsRequest.java                                                 |   95 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/HttpUtils.java                                                                                |   42 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitpop/AmazfitPopSupport.java                                            |   33 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/headphones/protocol/impl/v1/params/VirtualSoundParam.java                     |   43 
 app/src/main/res/layout/fragment_cycling.xml                                                                                                              |   16 
 app/src/main/res/xml/devicesettings_root_workout.xml                                                                                                      |    9 
 app/src/main/res/xml/devicesettings_inactivity_dnd_no_threshold.xml                                                                                       |   50 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiTemperatureSampleProvider.java                                                |  126 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ActivitySummariesGpsFragment.java                                                       |  180 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/SpinnerWithIconItem.java                                                                   |   48 
 app/src/main/res/xml/devicesettings_header_workout.xml                                                                                                    |    6 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/agps/GarminAgpsFile.java                                                    |   97 
 app/src/main/res/drawable-xhdpi/ic_notification_low_battery.png                                                                                           |    0 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/defines/MenuIconId.java                       |   36 
 app/src/main/res/xml/devicesettings_battery_allow_pass_through.xml                                                                                        |    9 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/conversation/SyncFinishedHandler.java                |   37 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/AbstractHuamiActivityDetailsParser.java                                      |   47 
 app/src/main/assets/ic_device_flipper.svg                                                                                                                 | 1772 
 app/src/main/proto/garmin/gdi_notifications_service.proto                                                                                                 |   37 
 app/src/main/res/values-pl/strings.xml                                                                                                                    | 3718 
 app/src/nopebble/res/values/ic_launcher_background.xml                                                                                                    |    4 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband3/MiBand3FWHelper.java                                                         |   40 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/services/XiaomiScheduleService.java                                         |  630 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/CalendarReceiver.java                                                               |  281 
 app/src/main/res/drawable/ic_events_gold.xml                                                                                                              |   21 
 src/mainline/fastlane/metadata/android/de-DE/title.txt                                                                                                    |    1 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/zeppos/ZeppOsSettingsCustomizer.java                                                 |  457 
 app/src/main/res/drawable/ic_device_miband6_disabled.xml                                                                                                  |   32 
 app/src/main/res/drawable/ic_device_pebble_disabled.xml                                                                                                   |   43 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiBatteryInfo.java                                                        |  121 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/oppo/OppoEncoAirCoordinator.java                                                           |   75 
 src/mainline/fastlane/metadata/android/en-US/changelogs/205.txt                                                                                           |    7 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiSyncState.java                                                        |  106 
 app/src/main/res/drawable/ic_activity_wakeboarding.xml                                                                                                    |   16 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/Status.java                           |   36 
 app/src/main/res/drawable/ic_activity_soccer.xml                                                                                                          |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/flipper/zero/support/FlipperZeroSupport.java                                       |  249 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/colmi/ColmiR0xPacketHandler.java                                                           |  491 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/prefs/ButtonModes.java                                                     |   65 
 src/mainline/fastlane/metadata/android/en-US/images/phoneScreenshots/52-StepsPerMonth.png                                                                 |    0 
 src/mainline/fastlane/metadata/android/en-US/changelogs/163.txt                                                                                           |    4 
 app/src/main/res/drawable/ic_hearing.xml                                                                                                                  |    3 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/cmfwatchpro/samples/CmfSleepSessionSampleProvider.java                                     |   56 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/binary_sensor/protocol/constants/MessageId.java                                    |   38 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivityAmount.java                                                                          |   95 
 app/src/test/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceTypeTest.java                                                                          |   37 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbipupro/AmazfitBipUProFWInstallHandler.java                                   |   49 
 app/src/main/res/drawable/ic_sync.xml                                                                                                                     |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/AbstractActivityListingAdapter.java                                                        |  181 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/notification/EndOfIncomingCall.java                    |   30 
 app/src/main/assets/fossil_hr/battery_layout.json                                                                                                         |    1 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitbipupro/AmazfitBipUProFirmwareInfo.java                               |   93 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendNotificationRequest.java                                       |  108 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiBleSupport.java                                                       |  341 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/ArmenianTransliterator.java                                                     |  305 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_66.java                                                         |   56 
 src/banglejs/fastlane/metadata/android/cz-CZ/short_description.txt                                                                                        |    1 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiConst.java                                                                      |  184 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/ScreenSettings.java                   |   67 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/Spo2ChartFragment.java                                                           |  287 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitband5/AmazfitBand5FWHelper.java                                               |   40 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xwatch/XWatchCoordinator.java                                                              |  141 
 app/src/main/res/xml/devicesettings_amazfitband5.xml                                                                                                      |   33 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/appmanager/AppManagerFragmentInstalledApps.java                                         |   83 
 app/src/main/res/drawable/ic_battery_50.xml                                                                                                               |   13 
 app/src/main/res/xml/devicesettings_musicmanagement.xml                                                                                                   |    8 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/Request.java                                                      |   81 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminHeartRateRestingSampleProvider.java                                           |   56 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/file/ResultCode.java                                    |   72 
 app/src/main/assets/ic_device_vesc.svg                                                                                                                    |  146 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitpoppro/AmazfitPopProSupport.java                                      |   33 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/file/FileGetRequest.java                                   |   40 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/AbstractFitProActivitySample.java                                                         |   60 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DevicesFragment.java                                                                    |  282 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/util/ByteArrayReader.java                                                |   69 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/jyou/JYouSupport.java                                                              |  339 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/WorldClockDetails.java                                                                  |  220 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/WorkoutType.java                      |   50 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/StressSample.java                                                                            |   56 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/alertnotification/AlertNotificationProfile.java                              |  118 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ChartsData.java                                                                  |   20 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/operations/OperationStatus.java                                             |   24 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/test/activity/TestActivitySummaryParser.java                                               |   27 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/RespiratoryRatePeriodFragment.java                                               |  239 
 src/banglejs/fastlane/metadata/android/en-US/images/phoneScreenshots/10-MainScreen.png                                                                    |    0 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/Mi1SFirmwareInfoFW1.java                                                    |   80 
 app/src/main/assets/fossil_hr/widgetChanceOfRain.bin                                                                                                      |    0 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/DeviceCandidateAdapter.java                                                                |  100 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiFileDownloadManager.java                                              |  686 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/baseTypes/BaseTypeLong.java                                             |   59 
 app/src/main/res/values-pt/strings.xml                                                                                                                    | 2383 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/GetHydrationReminderIntervalRequest.java                            |   55 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/fieldDefinitions/FieldDefinitionHrvStatus.java                          |   58 
 app/src/main/res/layout/fragment_devices.xml                                                                                                              |   37 
 app/src/main/res/layout/steps_streak_total.xml                                                                                                            |   40 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/LithuanianTransliterator.java                                                   |   29 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/PendingIntentUtils.java                                                                       |   77 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/no1f1/No1F1Coordinator.java                                                                |  173 
 app/src/main/res/drawable/ic_vpn_key.xml                                                                                                                  |   10 
 app/src/main/java/lineageos/weather/WeatherLocation.java                                                                                                  |  274 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/preferences/MinMaxTextWatcher.java                                                            |   59 
 app/src/nopebble/res/mipmap-mdpi/ic_launcher_foreground.png                                                                                               |    0 
 build.gradle                                                                                                                                              |   28 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiMusicUtils.java                                                               |  130 
 app/src/main/assets/fossil_hr/icWthClearNite.rle                                                                                                          |    7 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/headphones/protocol/impl/v1/params/NoiseCancellingOptimizerStatus.java        |   56 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/MenuIconSetting.java                          |   42 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/App.java                                                                    |  144 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/SonyHeadphonesSettingsCustomizer.java                                      |  226 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btbr/actions/WaitAction.java                                                               |   43 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/agps/GarminAgpsStatus.java                                                  |   23 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfittrex/AmazfitTRexSupport.java                                          |   43 
 app/src/main/proto/garmin/garmin_contacts.proto                                                                                                           |   46 
 src/mainline/fastlane/metadata/android/en-US/changelogs/194.txt                                                                                           |    7 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/XTimePreferenceFragment.java                                                                  |   72 
 app/src/main/res/layout/activity_legacy_external_pebble_js.xml                                                                                            |    5 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/FileTransferDataMessage.java                                       |   60 
 src/mainline/fastlane/metadata/android/en-US/changelogs/223.txt                                                                                           |   49 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/FitDefinitionMessage.java                                          |   50 
 app/src/main/res/xml/devicesettings_galaxy_buds_live.xml                                                                                                  |   71 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerYWeather.java                                              |  244 
 app/src/main/res/drawable/ic_screenshot.xml                                                                                                               |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetExtendedMusicInfoParams.java                                    |   44 
 app/src/test/java/nodomain/freeyourgadget/gadgetbridge/test/CalendarEventTest.java                                                                        |   62 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/RestingMetabolicRateSample.java                                                              |   23 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventAppMessage.java                                                          |   40 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventUpdateDeviceState.java                                                   |   27 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitbip3pro/AmazfitBip3ProFirmwareInfo.java                               |   82 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/GaugeDrawer.java                                                              |  356 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgts3/AmazfitGTS3Coordinator.java                                              |   66 
 app/src/main/res/layout/activity_fragmentappmanager.xml                                                                                                   |   52 
 app/src/main/res/layout/item_contact.xml                                                                                                                  |   53 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitactive/AmazfitActiveCoordinator.java                                          |   86 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/gpx/model/GpxFile.java                                                                        |  106 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/WithingsSteelHRDeviceSupport.java                                  |  748 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/GetGeneralSettingsRequest.java                                      |   58 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/um25/Support/UM25Support.java                                                      |  286 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/VibrationActivity.java                                                                  |   59 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebbleMorpheuzSampleProvider.java                                                   |   80 
 app/src/main/assets/ic_launcher.svg                                                                                                                       |  574 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/gps/GBLocationService.java                                                          |  186 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/MusicSpec.java                                                                               |   92 
 src/mainline/fastlane/metadata/android/en-US/changelogs/138.txt                                                                                           |   16 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/vivoactive/GarminVivoActive3Coordinator.java                                |   20 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/vivomove/GarminVivomoveTrendCoordinator.java                                |   18 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/gson/GsonUtcDateAdapter.java                                                                  |   56 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/makibeshr3/MakibesHR3Coordinator.java                                                      |  255 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/liveview/LiveviewProtocol.java                                                     |  148 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/baseTypes/BaseTypeShort.java                                            |   56 
 app/src/main/res/xml/devicesettings_galaxy_buds_2.xml                                                                                                     |  212 
 app/src/main/res/xml/devicesettings_huawei_freebuds.xml                                                                                                   |   18 
 src/mainline/fastlane/metadata/android/en-US/changelogs/122.txt                                                                                           |    6 
 app/src/main/res/drawable/ic_device_miscale.xml                                                                                                           |   41 
 app/src/test/java/nodomain/freeyourgadget/gadgetbridge/test/FitProTests.java                                                                              |   26 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/venu/GarminVenu3Coordinator.java                                            |   18 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/huaweiband2pro/HuaweiBand2ProCoordinator.java                                       |   41 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/communicator/v2/CommunicatorV2.java                                         |  500 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitcor/AmazfitCorFWInstallHandler.java                                           |   49 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/mijia_lywsd/MijiaLywsd03Coordinator.java                                                   |   48 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/StressChartFragment.java                                                         |  571 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/RecordedDataTypes.java                                                                       |   39 
 app/src/main/res/xml/devicesettings_sony_headphones_pause_when_taken_off.xml                                                                              |    9 
 app/src/main/res/menu/menu_charts.xml                                                                                                                     |   23 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BTLEOperation.java                                                                    |   28 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/soundcore/motion300/SoundcoreMotion300SettingsCustomizer.java                              |  117 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/message/Message.java                                 |   36 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/vivomovehr/GarminCapability.java                                                           |  183 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/soflow/SoFlowCoordinator.java                                                              |  166 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/alertnotification/NewAlert.java                                              |   81 
 app/src/main/res/values-el/strings.xml                                                                                                                    | 1004 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendEphemerisFileUploadDoneResponse.java                           |   28 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/gatt_client/BleGattClientSupport.java                                              |   71 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/FossilAppWriter.java                                                               |  170 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/BatteryLevelRequest.java                                   |   45 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casio/gbx100/InitOperation.java                                                    |  385 
 app/src/main/assets/ic_device_miband6_disabled.svg                                                                                                        |  198 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/ServiceDeviceSupport.java                                                                  |  544 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ActivityListingDetail.java                                                       |  106 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiTemperatureSampleProvider.java                                                |  213 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/AbstractInfo.java                                                           |   26 
 app/src/main/java/org/bouncycastle/shaded/crypto/OutputLengthException.java                                                                               |   10 
 app/src/main/res/drawable/ic_warning.xml                                                                                                                  |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/gpx/model/GpxWaypoint.java                                                                    |   67 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/adapter/fossil_hr/FossilHRWatchAdapter.java                                | 2160 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/fitpro/colacao/ColaCao21Coordinator.java                                                   |   56 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/logic/parsers/CaloriesPacketParser.java                        |   80 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/venu/GarminVenu2PlusCoordinator.java                                        |   18 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/PlainAction.java                                                              |   40 
 app/src/main/assets/womp/login.html                                                                                                                       |   53 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/CameraAppTypeSetting.java                     |   52 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/AngledLabelsChartRenderer.java                                                   |   42 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiWeatherConditions.java                                                          |  160 
 app/src/main/res/drawable/ic_device_banglejs.xml                                                                                                          |   34 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractSampleToTimeSampleProvider.java                                                    |  104 
 app/src/main/res/xml/devicesettings_screen_on_on_notifications.xml                                                                                        |   10 
 app/src/main/assets/fossil_hr/widgetSteps_preview.png                                                                                                     |    0 
 app/src/main/res/xml/devicesettings_fossilhybridhr_navigation.xml                                                                                         |   27 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SendNotificationRequest.java                                        |  109 
 app/src/main/res/xml/devicesettings_sony_headphones_quick_access.xml                                                                                      |   20 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitneo/AmazfitNeoFWInstallHandler.java                                           |   50 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/honorband3/HonorBand3Coordinator.java                                               |   52 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/CroatianTransliterator.java                                                     |   34 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/forerunner/GarminForerunner265Coordinator.java                              |   18 
 app/src/main/java/lineageos/weather/WeatherInfo.java                                                                                                      |  642 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/activity/ActivitySyncTimePacketTemplate.java           |   76 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/ZeppOsActivityDetailsParser.java                                      |  357 
 app/src/main/res/xml/devicesettings_amazfitx.xml                                                                                                          |   23 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miscale/MiCompositionScaleDeviceSupport.java                                       |  164 
 app/src/main/res/menu/app_blacklist_menu.xml                                                                                                              |   11 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetAppInfoParams.java                                              |   40 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/id115/FetchActivityOperation.java                                                  |  176 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/backup/ZipBackupCallback.java                                                                 |   27 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetFileBlockRequest.java                                           |   69 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/banglejs/BangleJSWorkoutParser.java                                                        |  170 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/asteroidos/AsteroidOSMediaCommand.java                                                     |   61 
 app/src/main/res/drawable-xhdpi/ic_notification_disconnected_qhybrid.png                                                                                  |    0 
 app/src/main/res/values-nl/strings.xml                                                                                                                    | 3563 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetSmartAlarmList.java                                             |   96 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/conversation/SimpleConversation.java                 |   42 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/casio/gwb5600/CasioGMWB5000DeviceCoordinator.java                                          |   38 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/OsmandEventReceiver.java                                                            |  237 
 app/src/main/res/layout/activity_solar_equipment_status.xml                                                                                               |   21 
 app/src/main/res/drawable/ic_beep.xml                                                                                                                     |   10 
 app/src/main/res/xml/devicesettings_fossilhybridhr_dev.xml                                                                                                |   51 
 app/src/main/res/xml/devicesettings_pixoo.xml                                                                                                             |   17 
 app/src/main/aidl/lineageos/weatherservice/ServiceRequestResult.aidl                                                                                      |   19 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/MusicListAdapter.java                                                                      |  118 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/huaweiwatchd2/HuaweiWatchD2Coordinator.java                                         |   46 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/redmibuds5pro/RedmiBuds5ProProtocol.java                                           |  548 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/ResponseManager.java                                                        |  137 
 app/src/main/res/drawable-xxhdpi/ic_notification_full_battery.png                                                                                         |    0 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/Request.java                                                        |  151 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgts2/AmazfitGTS2eFWHelper.java                                                |   40 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_73.java                                                         |   43 
 app/src/main/aidl/lineageos/weather/ILineageWeatherManager.aidl                                                                                           |   31 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/DashboardHrvWidget.java                                                       |  157 
 app/src/main/res/layout/fragment_welcome_get_started.xml                                                                                                  |   52 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lenovo/DataType.java                                                                       |   47 
 app/src/main/proto/xiaomi.proto                                                                                                                           |  974 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/DBUpdateScript.java                                                                       |   33 
 app/src/test/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/TestVarInt.java                                                                     |   72 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/VO2MaxFragment.java                                                              |  312 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/NotificationCommand.java                                                    |  123 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/DashboardStressSegmentedWidget.java                                           |   96 
 app/src/main/assets/tws_case.svg                                                                                                                          |   61 
 src/mainline/fastlane/metadata/android/en-US/changelogs/144.txt                                                                                           |    1 
 app/src/main/assets/ic_device_supercars_disabled.svg                                                                                                      |  179 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/test/samples/TestHrvValueSampleProvider.java                                               |   82 
 src/mainline/fastlane/metadata/android/en-US/changelogs/233.txt                                                                                           |  138 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/SonySWR12DeviceSupport.java                                              |  246 
 app/src/main/assets/ic_device_flipper_disabled.svg                                                                                                        | 1825 
 app/src/main/assets/fossil_hr/widgetCalories.bin                                                                                                          |    0 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebbleSettingsCustomizer.java                                                       |   94 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/Transaction.java                                                                      |   83 
 app/src/main/res/drawable/ic_device_galaxy_buds_live_disabled.xml                                                                                         |   41 
 app/src/main/res/drawable/ic_bluetooth_searching.xml                                                                                                      |   11 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/forerunner/GarminForerunner255MusicCoordinator.java                         |   18 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/buttons/ButtonConfiguration.java                        |   45 
 app/src/main/res/xml/devicesettings_liftwrist_display_sensitivity_with_smart.xml                                                                          |   39 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/operations/ZeppOsAgpsFile.java                                        |   82 
 app/src/main/res/xml/devicesettings_amazfittrex.xml                                                                                                       |   13 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/zeppe/ZeppEFWHelper.java                                                             |   40 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/cmfwatchpro/CmfFwHelper.java                                                       |  187 
 app/src/main/assets/ic_galaxy_buds_l.svg                                                                                                                  |   78 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandCoordinator.java                                                              |  309 
 app/src/main/res/drawable/ic_phone_missed_outline.xml                                                                                                     |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/asteroidos/AsteroidOSWeather.java                                                          |  136 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/HybridHRWatchfaceDesignerActivity.java                                             |  676 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/test/samples/TestPaiSampleProvider.java                                                    |  147 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/ActivitySampleTime.java               |   52 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/Huami2021Service.java                                                                |  136 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetSupportedCommandsRequest.java                                   |  105 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/TimePreference.java                                                                           |  120 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/capabilities/loyaltycards/CatimaContentProvider.java                                               |  264 
 app/src/main/proto/garmin/gdi_smart_proto.proto                                                                                                           |   63 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/ZipFileException.java                                                                         |   27 
 app/src/main/res/xml/devicesettings_antilost.xml                                                                                                          |   10 
 app/src/main/res/xml/devicesettings_operating_sounds.xml                                                                                                  |    9 
 app/src/main/res/xml/devicesettings_workout_detection.xml                                                                                                 |   36 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/soundcore/liberty/SoundcoreLiberty3ProDeviceSupport.java                           |   39 
 app/src/main/res/xml/devicesettings_scannable.xml                                                                                                         |   29 
 app/src/main/res/xml/devicesettings_lefun_interface_language.xml                                                                                          |   11 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiWeatherManager.java                                                   |  302 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/colmi/samples/ColmiHrvSummarySampleProvider.java                                           |   56 
 app/src/test/java/nodomain/freeyourgadget/gadgetbridge/test/Tryout.java                                                                                   |   42 
 app/src/main/res/layout/activity_list_detail.xml                                                                                                          |   28 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitactiveedge/AmazfitActiveEdgeCoordinator.java                                  |   90 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitcor/AmazfitCorFWHelper.java                                                   |   40 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetGpsParameterRequest.java                                        |   48 
 app/src/main/res/drawable/ic_info.xml                                                                                                                     |   10 
 app/src/nopebble/res/mipmap-xhdpi/ic_launcher_round.png                                                                                                   |    0 
 app/src/main/res/xml/devicesettings_sony_headphones_speak_to_chat_with_settings.xml                                                                       |   52 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/BackupRestoreProgressActivity.java                                                      |  241 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbip3/AmazfitBip3Coordinator.java                                              |  191 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/InstallActivity.java                                                                    |   38 
 src/mainline/fastlane/metadata/android/ru/short_description.txt                                                                                           |    1 
 app/src/main/java/org/bouncycastle/shaded/crypto/SkippingStreamCipher.java                                                                                |    9 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/ble/PebbleGATTServer.java                                                   |  195 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/TypeVersion.java                      |   39 
 app/src/main/assets/fossil_hr/icBattCharging.rle                                                                                                          |    1 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/QHybridConstants.java                                                              |   54 
 app/src/main/aidl/net/osmand/aidlapi/navigation/ADirectionInfo.aidl                                                                                       |    3 
 app/src/main/res/xml/devicesettings_donotdisturb_allday_liftwirst.xml                                                                                     |   76 
 app/src/main/res/layout/activity_watch9_pairing.xml                                                                                                       |   33 
 src/mainline/fastlane/metadata/android/en-US/changelogs/135.txt                                                                                           |    5 
 app/src/main/res/layout/list_item_checkable.xml                                                                                                           |   28 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/activity/ActivitySyncDataPacket.java                   |   94 
 app/src/main/res/xml/devicesettings_soundcore_motion300_audio.xml                                                                                         |  202 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/QHybridBaseSupport.java                                                    |   42 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/um25/Activity/DataActivity.java                                                            |  151 
 app/src/main/res/values-fa/strings.xml                                                                                                                    |  232 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivitySummaryJsonSummary.java                                                              |  217 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GBPrefs.java                                                                                  |  155 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitgts/AmazfitGTSFirmwareInfo.java                                       |  120 
 app/src/main/res/layout/activity_banglejs_apps_management.xml                                                                                             |   16 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/FileType.java                                                               |  118 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/logic/parsers/BehaviorPacketParser.java                        |  104 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/discovery/DiscoveryActivityV2.java                                                      |  932 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitx/AmazfitXSupport.java                                                |   52 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/soundcore/liberty/SoundcoreLiberty4NCDeviceSupport.java                            |   39 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/DashboardTodayWidget.java                                                     |  600 
 src/mainline/fastlane/metadata/android/en-US/changelogs/218.txt                                                                                           |    2 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiWorkoutScreenActivityType.java                                          |   75 
 app/src/main/res/color/btn_dark.xml                                                                                                                       |    5 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/GetSedentaryReminderIntervalRequest.java                            |   55 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/huaweiband3pro/HuaweiBand3ProCoordinator.java                                       |   41 
 app/src/main/res/values-ia/strings.xml                                                                                                                    |    2 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/DashboardDistanceWidget.java                                                  |   64 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/QHybridAppChoserActivity.java                                                      |  197 
 src/mainline/fastlane/metadata/android/en-US/changelogs/198.txt                                                                                           |    5 
 app/src/main/res/values-hr/strings.xml                                                                                                                    | 1609 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband2/MiBand2HRXCoordinator.java                                                   |  131 
 src/mainline/fastlane/metadata/android/ru/full_description.txt                                                                                            |    9 
 app/src/main/res/xml/devicesettings_miband2_dateformat.xml                                                                                                |   11 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/flipper/zero/support/FlipperZeroBaseSupport.java                                   |   42 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfittrexpro/AmazfitTRexProCoordinator.java                                        |  143 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_14.java                                                         |   42 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/DashboardSleepWidget.java                                                     |   79 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/roidmi/RoidmiIoThread.java                                                         |   70 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/GBActivity.java                                                                         |   25 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/huaweiwatchfit2/HuaweiWatchFit2Coordinator.java                                     |   46 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java                                                            | 1603 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/fieldDefinitions/FieldDefinitionWeatherCondition.java                   |  171 
 app/src/test/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/TestHuaweiPacket.java                                                               |  269 
 app/src/main/res/xml/devicesettings_sony_headphones_equalizer.xml                                                                                         |   11 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/AbstractPebbleHealthActivitySample.java                                                   |   35 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/status/SupportedFileTypesStatusMessage.java                        |   49 
 app/src/main/res/layout-land/activity_notification_filter.xml                                                                                             |   96 
 app/src/main/res/layout/activity_pebble_pairing.xml                                                                                                       |   33 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/AbstractRepeatingFetchOperation.java                        |  140 
 app/src/main/res/drawable/ic_mtu.xml                                                                                                                      |   10 
 app/src/main/res/xml/devicesettings_sony_warning_wh1000xm3.xml                                                                                            |    7 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/GetActivityDataRequest.java                                         |   85 
 app/src/main/res/xml/devicesettings_activity_info_header.xml                                                                                              |    5 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitbips/AmazfitBipSSupport.java                                          |  110 
 src/mainline/fastlane/metadata/android/en-US/changelogs/99.txt                                                                                            |    2 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/wena3/SonyWena3EnergySampleProvider.java                                              |   56 
 app/src/main/res/drawable/ic_device_roidmi_disabled.xml                                                                                                   |   39 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/AbstractNotificationPattern.java                                                             |   24 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/test/TestDeviceSupport.java                                                        |   84 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitband5/AmazfitBand5FirmwareInfo.java                                   |   82 
 app/src/main/res/values-ca/strings.xml                                                                                                                    | 1527 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/EventCode.java                                         |   37 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/id115/ID115Constants.java                                                                  |  107 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/file/FileVerifyRequest.java                                |   94 
 app/src/main/res/layout/activity_calblacklist.xml                                                                                                         |   14 
 app/src/main/assets/fossil_hr/icWthPartCloudyDay.rle                                                                                                      |    2 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AbstractFragmentPagerAdapter.java                                                       |   68 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/buttons/ButtonConfigurationPutRequest.java              |   53 
 app/src/main/res/drawable/ic_device_sony_wf_800n_disabled.xml                                                                                             |   69 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBExceptionHandler.java                                                                            |  103 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/vivosmart/GarminVivosmart5Coordinator.java                                  |   18 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/vivomove/GarminVivomoveHrCoordinator.java                                   |   18 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xwatch/XWatchService.java                                                                  |   53 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/coordinators/SonyWF1000XM5Coordinator.java                                 |   85 
 app/src/main/res/drawable/ic_battery_charging_50.xml                                                                                                      |   13 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgtr/AmazfitGTRCoordinator.java                                                |  143 
 app/src/main/res/drawable/ic_wena_icon_center.xml                                                                                                         |   24 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/LefunSampleProvider.java                                                             |  100 
 app/src/banglejs/AndroidManifest.xml                                                                                                                      |    5 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/prefs/SpeakToChatEnabled.java                                              |   53 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/coordinators/SonyWISP600NCoordinator.java                                  |   56 
 src/mainline/fastlane/metadata/android/en-US/changelogs/227.txt                                                                                           |   78 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/forerunner/GarminForerunner620Coordinator.java                              |   34 
 app/src/main/res/xml/devicesettings_header_authentication.xml                                                                                             |    6 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/p2p/HuaweiBaseP2PService.java                                               |  114 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/p2p/HuaweiP2PCalendarService.java                                           |  491 
 app/src/main/res/menu/dashboard_menu.xml                                                                                                                  |   18 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/ValueDecoder.java                                                            |   41 
 app/src/main/res/drawable/ic_activity_threadmill.xml                                                                                                      |   20 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/hplus/HPlusHealthSampleProvider.java                                                       |  240 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbip3/AmazfitBip3FWInstallHandler.java                                         |   49 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionAnalytics.java                                                |   68 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/DashboardStressBreakdownWidget.java                                           |   89 
 app/src/main/res/drawable/ic_activity_hiking.xml                                                                                                          |   10 
 app/src/nightly/res/values/ic_launcher_background.xml                                                                                                     |    4 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiWidgetManager.java                                                            |  525 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendEphemerisSingleFileInfoResponse.java                           |   32 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitgts2/AmazfitGTS2eFirmwareInfo.java                                    |  120 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiCoordinatorSupplier.java                                                      |   47 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btbr/Transaction.java                                                                      |   70 
 app/src/main/res/xml/devicesettings_display_caller.xml                                                                                                    |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendFitnessUserInfoRequest.java                                    |   86 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetSecurityNegotiationRequest.java                                 |   65 
 app/src/main/res/xml/devicesettings_contacts.xml                                                                                                          |    8 
 app/src/main/res/xml/devicesettings_weardirection.xml                                                                                                     |   11 
 app/src/main/assets/ic_shoe_prints_many.svg                                                                                                               |   70 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminSleepStageSampleProvider.java                                                 |   56 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/AndroidUtils.java                                                                             |  355 
 gradle.properties                                                                                                                                         |   25 
 app/src/main/res/layout/item_widget_screen.xml                                                                                                            |   52 
 src/mainline/fastlane/metadata/android/en-US/changelogs/189.txt                                                                                           |    2 
 app/src/main/res/layout/qhybrid_app_view.xml                                                                                                              |   22 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendWeatherDeviceRequest.java                                      |   43 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/logic/parsers/OneBytePerSamplePacketParser.java                |   37 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerTimeStylePebble.java                                       |  171 
 app/src/main/res/drawable-hdpi/ic_systemapp.png                                                                                                           |    0 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminSleepStatsSampleProvider.java                                                 |   56 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ActivityAnalysis.java                                                            |  157 
 app/src/main/res/drawable/ic_drag_handle.xml                                                                                                              |   10 
 app/src/main/res/xml/devicesettings_phone_calls_watch_pair.xml                                                                                            |   77 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_79.java                                                         |   38 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/MusicStateSpec.java                                                                          |   91 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitRespirationRate.java                                        |   32 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitbip/AmazfitBipLiteSupport.java                                        |   43 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/miband8active/MiBand8ActiveCoordinator.java                                         |   49 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/alarm/AlarmState.java                                           |   29 
 app/src/test/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/TestHuaweiTLV.java                                                                  |  532 
 app/src/main/res/xml/devicesettings_canned_reply_16.xml                                                                                                   |  131 
 app/src/main/java/org/bouncycastle/shaded/crypto/Mac.java                                                                                                 |   71 
 app/src/main/res/xml/devicesettings_sony_headphones_speak_to_chat_simple.xml                                                                              |   12 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/music/MusicInfoSetRequest.java                          |   63 
 app/src/main/res/values-id/strings.xml                                                                                                                    | 3157 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/DashboardGoalsWidget.java                                                     |  177 
 app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BtLEQueueTest.java                                                                    |   23 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/widget/Widget.java                                      |  139 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/StepsCollectionFragment.java                                                     |   45 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/ExtendedAsciiTransliterator.java                                                |   29 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hplus/HPlusDataRecordSleep.java                                                    |  145 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/LefunDeviceSupport.java                                                      | 1036 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgts2/AmazfitGTS2MiniCoordinator.java                                          |   93 
 app/src/main/res/xml/devicesettings_reserve_alarms_calendar.xml                                                                                           |   13 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/miband2/Mi2TextNotificationStrategy.java                                     |   94 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiSleepTimeSampleProvider.java                                                  |   56 
 app/src/main/assets/app_config/js/gadgetbridge_boilerplate.js                                                                                             |  297 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/FetchHeartRateRestingOperation.java                         |  119 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/cmfwatchpro/CmfPreferences.java                                                    |  391 
 app/src/nightly/res/drawable-xxxhdpi/ic_launcher.png                                                                                                      |    0 
 src/mainline/fastlane/metadata/android/en-US/changelogs/125.txt                                                                                           |    4 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerMisfit.java                                                |  129 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/control/CommandCode.java                                        |   31 
 external/patches/jerryscript-gcc-12-build-fix.patch                                                                                                       |   35 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AbstractListActivity.java                                                               |   78 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/capabilities/widgets/WidgetType.java                                                               |   26 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfittrexpro/AmazfitTRexProFWInstallHandler.java                                   |   49 
 app/src/main/res/drawable/ic_activity_biking.xml                                                                                                          |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/WriteAction.java                                                              |   74 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetConnectStatusRequest.java                                       |   55 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/util/IntFormat.java                                                      |   51 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/GFDIMessage.java                                                   |  221 
 app/src/main/res/layout/custom_chart_marker.xml                                                                                                           |   28 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgtr2/AmazfitGTR2FWInstallHandler.java                                         |   49 
 app/src/main/res/values-de/strings.xml                                                                                                                    | 3414 
 app/src/main/assets/migrations/devicetype.json                                                                                                            |  122 
 app/src/main/assets/fossil_hr/icWthPartCloudyNite.rle                                                                                                     |    2 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/forerunner/GarminForerunner245MusicCoordinator.java                         |   24 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitfalcon/AmazfitFalconCoordinator.java                                          |   71 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/cmfwatchpro/CmfCommand.java                                                        |  125 
 src/mainline/fastlane/metadata/android/en-US/changelogs/153.txt                                                                                           |    1 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendNotifyHeartRateCapabilityRequest.java                          |   56 
 app/src/main/java/lineageos/weatherservice/ServiceRequest.java                                                                                            |  161 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GBZipFile.java                                                                                |  131 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/jyou/BFH16DeviceSupport.java                                                       |  552 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/colmi/samples/ColmiSpo2SampleProvider.java                                                 |   56 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qc35/QC35BaseSupport.java                                                          |   46 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetSleepDataRequest.java                                           |   97 
 app/src/main/res/menu/menu_garmin_realtime_settings.xml                                                                                                   |   17 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/huaweiwatchultimate/HuaweiWatchUltimateCoordinator.java                             |   52 
 app/src/main/res/xml/devicesettings_sony_headphones_surround_mode.xml                                                                                     |   11 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/SleepState.java                                                                              |   23 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/gps/GBLocationProvider.java                                                         |   51 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/IconHelper.java                                                    |   71 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/Cmd22Command.java                                                           |   83 
 app/src/main/res/drawable/ic_action_find_lost_device.xml                                                                                                  |   10 
 app/src/main/res/drawable/ic_graphic_eq.xml                                                                                                               |    3 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleProtocol.java                                                         | 2723 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AlarmDetails.java                                                                       |  297 
 app/src/main/res/xml/dashboard_preferences.xml                                                                                                            |  137 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitband5/AmazfitBand5FWInstallHandler.java                                       |   49 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btbr/actions/SetDeviceBusyAction.java                                                      |   53 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/asteroidos/AsteroidOSDeviceSupport.java                                            |  291 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/um25/Support/UM25BaseSupport.java                                                  |   42 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgts/AmazfitGTSFWInstallHandler.java                                           |   49 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendHeartRateZonesConfig.java                                      |   57 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/GattCharacteristic.java                                                               |  414 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/Contacts.java                                                               |   93 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceSupport.java                                                                         |  142 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ShowDurationDialog.java                                                          |   51 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lenovo/operations/InitOperation.java                                               |   92 
 src/mainline/fastlane/metadata/android/en-US/changelogs/140.txt                                                                                           |    4 
 app/src/nightly/res/mipmap-xxhdpi/ic_launcher_foreground.png                                                                                              |    0 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/huaweiwatch3/HuaweiWatch3Coordinator.java                                           |   47 
 app/src/main/res/xml/devicesettings_amazfitgtsgtr.xml                                                                                                     |   13 
 app/src/main/res/drawable/ic_filter_9_plus.xml                                                                                                            |   10 
 app/src/main/res/xml/devicesettings_amazfitneo_display.xml                                                                                                |   13 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/.gitignore                                                                                    |    2 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/AbstractDeviceSupport.java                                                                 | 1299 
 app/src/main/assets/olive_laurel.svg                                                                                                                      |  340 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfittrexpro/AmazfitTRexProFirmwareInfo.java                               |  113 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleSupport.java                                                          |  263 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/test/TestDeviceRand.java                                                                   |   47 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/GBDeviceProtocol.java                                                               |  187 
 app/src/main/res/drawable/ic_bluetooth.xml                                                                                                                |   10 
 app/src/main/res/xml/devicesettings_pairingkey.xml                                                                                                        |   11 
 app/src/main/res/xml/devicesettings_sony_headphones_sound_position.xml                                                                                    |   11 
 app/src/main/res/xml/devicesettings_huami2021_displayitems.xml                                                                                            |   13 
 app/src/main/assets/activity_stacked.svg                                                                                                                  |  102 
 app/src/main/res/xml/devicesettings_mijia_lywsd.xml                                                                                                       |   45 
 app/src/main/res/layout/simple_spinner_item_themed.xml                                                                                                    |   11 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/supercars/SuperCarsSupport.java                                                    |  224 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/AbstractPaiSample.java                                                                    |   42 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitgts2/AmazfitGTS2MiniFirmwareInfo.java                                 |  120 
 .gitea/issue_template/feature_request.yml                                                                                                                 |   76 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbips/AmazfitBipSLiteFWHelper.java                                             |   40 
 app/src/main/res/xml/devicesettings_bt_connected_advertisement.xml                                                                                        |   10 
 app/src/main/res/drawable-xhdpi/ic_notification_qhybrid.png                                                                                               |    0 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/contentprovider/PebbleContentProvider.java                                                         |  117 
 external/pack.py                                                                                                                                          |  124 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/configuration/ConfigurationPutRequest.java                 |  529 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/update/UpdateFirmwareOperation.java                               |  293 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/TimeSampleProvider.java                                                                    |   99 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/RealtimeSamplesSupport.java                                                 |  125 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/appmanager/AppManagerActivity.java                                                      |  217 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SetNavigateOnRotateRequest.java                                    |   60 
 app/src/main/res/drawable/ic_message_outline.xml                                                                                                          |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ActivityChartsActivity.java                                                      |  277 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/BatteryInfo.java                                                            |   82 
 app/src/main/res/drawable/ic_emergency.xml                                                                                                                |   12 
 app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml                                                                                                  |    6 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/ReleaseHandsControlRequest.java                            |   45 
 app/src/main/res/layout/fragment_welcome_permissions.xml                                                                                                  |   38 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/test/TestDeviceConst.java                                                                  |   25 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/ServerResponseAction.java                                                     |   71 
 src/mainline/fastlane/metadata/android/en-US/changelogs/116.txt                                                                                           |    2 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/marstek/MarstekB2500DeviceSupport.java                                             |  346 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractBLClassicDeviceCoordinator.java                                                    |   24 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgtr/AmazfitGTRLiteCoordinator.java                                            |  136 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/ECDH_B163.java                                                                                |  515 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/protobuf/messagefields/StringMessageField.java                                                |   40 
 src/mainline/fastlane/metadata/android/en-US/changelogs/214.txt                                                                                           |   26 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/conversation/WorkoutScreenListHandler.java           |   65 
 app/src/main/res/drawable/ic_radio.xml                                                                                                                    |   10 
 app/src/main/res/values-en-rGB/strings.xml                                                                                                                | 1580 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiVibrationPatternNotificationType.java                                   |   45 
 app/src/main/res/drawable/ic_star_gold.xml                                                                                                                |    9 
 app/src/main/res/drawable/ic_wifi_tethering.xml                                                                                                           |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandService.java                                                                  |  275 
 app/src/main/res/drawable-xxhdpi/ic_watchapp.png                                                                                                          |    0 
 app/src/main/res/xml/devicesettings_wena3_auto_power_off.xml                                                                                              |   34 
 app/src/main/res/xml/devicesettings_zetime_date_format.xml                                                                                                |   15 
 app/src/main/proto/garmin/gdi_sms_notification.proto                                                                                                      |   50 
 app/src/main/res/values/dimens.xml                                                                                                                        |   19 
 app/src/main/res/layout/activity_android_pairing.xml                                                                                                      |   16 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/colmi/ColmiR06Coordinator.java                                                             |   38 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pinetime/PineTimeJFCoordinator.java                                                        |  186 
 app/src/main/assets/ic_device_vesc_disabled.svg                                                                                                           |  199 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetWorkoutSwimSegmentsRequest.java                                 |  102 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/activity/impl/XiaomiSimpleDataEntry.java                                    |   47 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/notification/NotificationAttribute.java              |   88 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/prefs/AmbientSoundControlButtonMode.java                                   |   63 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgts2/AmazfitGTS2Coordinator.java                                              |  158 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiEphemerisManager.java                                                 |  563 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/XDatePreference.java                                                                          |  114 
 app/src/main/assets/nothing_ear_l.svg                                                                                                                     |   73 
 app/src/nopebble/res/mipmap-hdpi/ic_launcher.png                                                                                                          |    0 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitvergel/AmazfitVergeLFWHelper.java                                             |   40 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/notification/GetNotificationAttributesResponse.java  |   57 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/coordinators/SonyWFC700NCoordinator.java                                   |   66 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/withingssteelhr/WithingsSteelHRSampleProvider.java                                         |   98 
 app/src/main/res/drawable/ic_activity_sleep.xml                                                                                                           |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerRealWeather.java                                           |  146 
 app/src/main/res/layout/fragment_heart_rate.xml                                                                                                           |  132 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiSupport.java                                                            | 4358 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/smaq2oss/SMAQ2OSSConstants.java                                                            |   60 
 app/src/main/res/xml/devicesettings_casio_connection_duration.xml                                                                                         |   11 
 app/src/main/res/drawable-xxhdpi/ic_notification_low_battery.png                                                                                          |    0 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/adapter/misfit/MisfitWatchAdapter.java                                     |  510 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/STM32CRC.java                                                                       |  180 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/http/GarminHttpRequest.java                                                 |   64 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitband5/AmazfitBand5Support.java                                        |   47 
 app/src/main/res/values-lv/strings.xml                                                                                                                    |   22 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/FileDownloadService0A.java                                                  |  279 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AppBlacklistActivity.java                                                               |   86 
 src/mainline/fastlane/metadata/android/en-US/changelogs/172.txt                                                                                           |    5 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiIcon.java                                                               |  147 
 app/src/main/aidl/net/osmand/aidlapi/navigation/ANavigationVoiceRouterMessageParams.aidl                                                                  |    3 
 app/src/main/res/drawable/ic_device_scannable_disabled.xml                                                                                                |   26 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/CurrentTimeRequestMessage.java                                     |   79 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/GBDeviceAppAdapter.java                                                                    |  175 
 app/src/main/res/layout/layout_widget_duration_selector.xml                                                                                               |  161 
 app/src/main/res/drawable/splash.xml                                                                                                                      |    4 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceApp.java                                                                              |  163 
 app/src/main/res/drawable/brand_speedtest.xml                                                                                                             |   20 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/FileTransferHandler.java                                                    |  382 
 app/src/main/res/layout/fragment_weightchart.xml                                                                                                          |   78 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/WidgetSettingsActivity.java                                                        |  220 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casio/CasioSupport.java                                                            |   66 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/galaxy_buds/GalaxyBudsIOThread.java                                                |   68 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DailyTotals.java                                                                             |  207 
 app/src/main/res/drawable/ic_wb_sunny.xml                                                                                                                 |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/HrvValueSample.java                                                                          |   24 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/ConfigurationMessage.java                                          |   60 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/prefs/VoiceNotifications.java                                              |   46 
 app/src/test/resources/GPXExporterTest-SampleTracks.csv                                                                                                   |   14 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsDisplayItemsService.java                               |  510 
 app/src/main/res/drawable/ic_filter_alt.xml                                                                                                               |    3 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DashboardPreferencesActivity.java                                                       |  104 
 app/src/nightly/res/drawable-hdpi/ic_launcher.png                                                                                                         |    0 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/redmiwatch5active/RedmiWatch5ActiveCoordinator.java                                 |   55 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/AlarmReceiver.java                                                                  |  113 
 app/src/main/res/values-fr-rCA/strings.xml                                                                                                                |  895 
 app/src/main/res/xml/devicesettings_zetime_health.xml                                                                                                     |  139 
 app/src/main/java/org/bouncycastle/shaded/crypto/params/AEADParameters.java                                                                               |   61 
 src/mainline/fastlane/metadata/android/en-US/changelogs/185.txt                                                                                           |   11 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/RecordDefinition.java                                                   |  126 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiFWHelper.java                                                                   |  109 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/huaweiband7/HuaweiBand7Coordinator.java                                             |   41 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/operations/UpdateFirmwareOperation.java                                     |  490 
 app/src/main/aidl/lineageos/weather/WeatherInfo.aidl                                                                                                      |   19 
 app/src/main/res/values-ko/strings.xml                                                                                                                    |  768 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/AbstractHeadphoneDeviceSupport.java                                                        |   65 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/widgets/WidgetScreenDetailsActivity.java                                                |  280 
 app/src/test/resources/gpx.xsd                                                                                                                            |  784 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/instinct/GarminInstinctCoordinator.java                                     |   18 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/baseTypes/BaseTypeByte.java                                             |   59 
 app/src/nightly/res/mipmap-mdpi/ic_launcher_foreground.png                                                                                                |    0 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/vivosport/GarminVivosportCoordinator.java                                   |   18 
 app/src/main/res/anim/flyleft.xml                                                                                                                         |    8 
 app/src/main/res/drawable/widget_preview.png                                                                                                              |    0 
 app/src/main/assets/fossil_hr/openSourceWatchface.bin                                                                                                     |    0 
 app/src/main/res/drawable/ic_activity_billiard_pool.xml                                                                                                   |   15 
 app/src/main/java/net/osmand/aidlapi/navigation/OnVoiceNavigationParams.java                                                                              |   70 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/cmfwatchpro/samples/CmfSleepStageSampleProvider.java                                       |   56 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventBatteryInfo.java                                                         |   39 
 app/src/test/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/TestFitnessData.java                                                        |  502 
 app/src/test/java/nodomain/freeyourgadget/gadgetbridge/export/GPXExporterTest.java                                                                        |  124 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ExternalPebbleJSActivity.java                                                           |  242 
 app/src/main/res/xml/devicesettings_ble_api.xml                                                                                                           |   27 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/test/samples/TestTemperatureSampleProvider.java                                            |   87 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/parser/ActivityEntry.java                                                  |   78 
 app/src/main/assets/ic_device_unknown.svg                                                                                                                 |  144 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/P2P.java                                                                    |   80 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiInstallHandler.java                                                           |   93 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitgts2/AmazfitGTS2FirmwareInfo.java                                     |  120 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsWatchfaceService.java                                  |  266 
 app/src/main/java/org/bouncycastle/shaded/crypto/modes/AEADBlockCipher.java                                                                               |   17 
 app/src/main/java/org/bouncycastle/shaded/crypto/DefaultMultiBlockCipher.java                                                                             |   33 
 app/src/main/res/anim/bounceleft.xml                                                                                                                      |   11 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/smaq2oss/SMAQ2OSSCoordinator.java                                                          |  165 
 app/src/main/res/layout/layout_widget_duration.xml                                                                                                        |   47 
 src/mainline/fastlane/metadata/android/en-US/changelogs/157.txt                                                                                           |    3 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSession.java                                                         |   95 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitDeviceInfo.java                                             |   47 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/file/FileEncryptedLookupAndGetRequest.java              |   39 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/BluetoothConnectReceiver.java                                                       |   77 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/huaweibandaw70/HuaweiBandAw70Coordinator.java                                       |   46 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitSleepStats.java                                             |  107 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/ActivitySampleUnknown.java            |   36 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/fieldDefinitions/FieldDefinitionTemperature.java                        |   13 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/GetActivityDataCommand.java                                                 |  108 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/TimeCommand.java                                                            |  148 
 src/mainline/fastlane/metadata/android/en-US/changelogs/129.txt                                                                                           |    4 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/GetStepGoalRequest.java                                    |   45 
 app/src/test/java/nodomain/freeyourgadget/gadgetbridge/test/LoggingTest.java                                                                              |   82 
 .woodpecker/run_lint.yml                                                                                                                                  |   19 
 app/src/main/res/xml/devicesettings_garmin_realtime_settings.xml                                                                                          |    7 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/Probe.java                            |   51 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventNotificationControl.java                                                 |   34 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/idasen/IdasenControlActivity.java                                                          |  105 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SetMusicStatusRequest.java                                         |   44 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiSleepRespiratoryRateSampleProvider.java                                         |   56 
 src/mainline/fastlane/metadata/android/en-US/images/phoneScreenshots/71-SportActivitiesFilter.png                                                         |    0 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/SampleXLabelFormatter.java                                                       |   53 
 src/mainline/fastlane/metadata/android/en-US/short_description.txt                                                                                        |    1 
 app/src/main/res/drawable/ic_activity_basketball.xml                                                                                                      |   10 
 app/src/main/res/layout/fragment_live_activity.xml                                                                                                        |   45 
 app/src/main/res/drawable-mdpi/ic_launcher.png                                                                                                            |    0 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/binary_sensor/protocol/parameter/SensorType.java                                   |   34 
 app/src/main/res/xml/devicesettings_root_notifications.xml                                                                                                |    9 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/TestMi1AFirmwareInfo.java                                                   |   92 
 app/src/main/res/layout/dashboard_widget_generic_gauge.xml                                                                                                |   43 
 app/src/mainline/res/values/strings.xml                                                                                                                   |    9 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/capabilities/loyaltycards/CatimaManager.java                                                       |  128 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/watch9/Watch9DeviceCoordinator.java                                                        |  181 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/AbstractChartsActivity.java                                                      |  361 
 src/mainline/fastlane/metadata/android/en-US/images/phoneScreenshots/10-Dashboard.png                                                                     |    0 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiCoordinator.java                                                              |  608 
 src/banglejs/fastlane/metadata/android/en-US/short_description.txt                                                                                        |    1 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/makibeshr3/MakibesHR3Constants.java                                                        |  338 
 app/src/main/res/xml/devicesettings_volume.xml                                                                                                            |    9 
 app/src/main/res/menu/musicmanager_context.xml                                                                                                            |   12 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerHealthify.java                                             |   85 
 app/src/test/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/TestMusicControl.java                                                       |  389 
 app/src/test/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/TestFindPhone.java                                                          |  120 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/NotificationType.java                                                                        |  172 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/IcelandicTransliterator.java                                                    |   29 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiStressSampleProvider.java                                                       |   56 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/heartrate/BodySensorLocation.java                                            |   39 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/GattListenerAction.java                                                               |   21 
 app/src/main/assets/fossil_hr/widgetDate.bin                                                                                                              |    0 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/Weather.java                                                                |  829 
 app/src/main/res/drawable/ic_device_sony_wf_800n.xml                                                                                                      |   64 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/GattService.java                                                                      |   80 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DataManagementActivity.java                                                             |  518 
 app/src/main/assets/ic_device_supercars.svg                                                                                                               |  134 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/notification/PlayCallNotificationRequest.java              |   44 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/receivers/GBMusicControlReceiver.java                                                      |  220 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendExtendedAccountRequest.java                                    |   65 
 app/src/main/res/drawable-xxhdpi/ic_notification_qhybrid.png                                                                                              |    0 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/galaxy_buds/GalaxyBuds2ProDeviceCoordinator.java                                           |   77 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbipu/AmazfitBipUCoordinator.java                                              |  192 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgtr2/AmazfitGTR2FWHelper.java                                                 |   41 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerM7S.java                                                   |  162 
 app/src/main/res/values-hi/strings.xml                                                                                                                    |  162 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiSupport.java                                                          |  760 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/common/SimpleNotification.java                                                     |   44 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/status/NotificationControlStatusMessage.java                       |   76 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AboutUserPreferencesActivity.java                                                       |  117 
 app/src/main/res/xml/devicesettings_header_calendar.xml                                                                                                   |    6 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btclassic/BtClassicIoThread.java                                                           |  214 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/EndOfTransmission.java                |   44 
 app/src/main/res/drawable/ic_device_itag_disabled.xml                                                                                                     |   40 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivityPoint.java                                                                           |  103 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/coordinators/SonyWF1000XM3Coordinator.java                                 |   81 
 app/src/main/res/drawable-hdpi/ic_notification_disconnected_qhybrid.png                                                                                   |    0 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casio/gbx100/FetchStepCountDataOperation.java                                      |  310 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/activity/ActivitySyncTimePacketTypeA.java              |   27 
 app/src/test/java/nodomain/freeyourgadget/gadgetbridge/test/ArmenianTransliteratorTest.java                                                               |  168 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lenovo/LenovoWatchConstants.java                                                           |   79 
 app/src/nopebble/res/mipmap-xxhdpi/ic_launcher_round.png                                                                                                  |    0 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/instinct/GarminInstinct2XSolarCoordinator.java                              |   19 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/no1f1/No1F1SampleProvider.java                                                             |   84 
 app/src/main/java/org/bouncycastle/shaded/util/Strings.java                                                                                               |   38 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/asteroidos/AsteroidOSNotification.java                                                     |  176 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/soundcore/liberty/SoundcoreLibertyProtocol.java                                    |  348 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/welcome/WelcomeFragmentGetStarted.java                                                  |   61 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/colmi/ColmiR02Coordinator.java                                                             |   38 
 app/src/main/res/drawable/ic_smart_button.xml                                                                                                             |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/HybridHRWidgetPosition.java                                                        |   27 
 app/src/main/res/xml/devicesettings_headphones.xml                                                                                                        |   32 
 app/src/main/proto/garmin/gdi_settings_service.proto                                                                                                      |  228 
 app/src/main/res/xml/devicesettings_buttonactions_with_longpress.xml                                                                                      |  102 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/LiveActivityFragment.java                                                        |  547 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/coordinators/SonyWH1000XM3Coordinator.java                                 |   59 
 app/src/main/java/org/bouncycastle/shaded/crypto/modes/CBCModeCipher.java                                                                                 |   15 
 app/src/main/res/drawable/ic_led_color.xml                                                                                                                |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/DashboardStepsWidget.java                                                     |   63 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/ChallengeResponse.java                |   54 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/prefs/AdaptiveVolumeControl.java                                           |   46 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/alarm/AlarmRepeat.java                                          |   69 
 app/src/main/res/drawable/ic_extension.xml                                                                                                                |   10 
 app/src/main/res/xml/devicesettings_active_noise_cancelling_toggle.xml                                                                                    |   10 
 src/mainline/fastlane/metadata/android/en-US/changelogs/191.txt                                                                                           |   10 
 .gitea/issue_template/device_request.yml                                                                                                                  |   79 
 app/src/main/assets/womp/orders.html                                                                                                                      |   52 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiStressSampleProvider.java                                                     |   66 
 app/src/main/res/layout/activity_charts_durationdialog.xml                                                                                                |   13 
 app/src/test/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/TestLocaleConfig.java                                                       |   87 
 app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/MessageBuilderTest.java                              |  151 
 app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png                                                                                                      |    0 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ChartsPreferencesActivity.java                                                   |  131 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/AlarmUtils.java                                                                               |  194 
 app/src/main/res/drawable-hdpi/ic_notification_disconnected.png                                                                                           |    0 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/AbstractBleProfile.java                                                      |  115 
 app/src/main/res/layout/preference_material_switch.xml                                                                                                    |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/FieldDefinition.java                                                    |   93 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/Menstrual.java                                                              |   75 
 src/mainline/fastlane/metadata/android/en-US/changelogs/112.txt                                                                                           |    4 
 app/src/main/res/drawable/ic_device_headphones_disabled.xml                                                                                               |   28 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/GetPpgDataRequest.java                                              |   85 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/binary_sensor/BinarySensorSupport.java                                             |  203 
 app/src/main/res/layout/activity_withings_calibration.xml                                                                                                 |   58 
 app/src/main/res/drawable-xhdpi/ic_systemapp.png                                                                                                          |    0 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/DashboardSleepScoreWidget.java                                                |  105 
 app/src/main/res/values/colors.xml                                                                                                                        |   90 
 app/src/main/assets/fossil_hr/icBattery.rle                                                                                                               |    1 
 app/src/main/res/drawable/ic_widgets.xml                                                                                                                  |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/vivoactive/GarminVivoActive5Coordinator.java                                |   18 
 app/src/main/res/layout/activity_idasen_control.xml                                                                                                       |  122 
 app/src/main/aidl/net/osmand/aidlapi/navigation/OnVoiceNavigationParams.aidl                                                                              |    3 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/AbstractSendNotificationRequest.java                                |   99 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/application/ApplicationsListRequest.java                |   79 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/FunctionAction.java                                                           |   47 
 app/src/main/res/xml/devicesettings_amazfitbips.xml                                                                                                       |   23 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_44.java                                                         |   38 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/NotificationFilterActivity.java                                                         |  233 
 app/src/test/java/nodomain/freeyourgadget/gadgetbridge/test/PebbleUtilsTest.java                                                                          |   54 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/Request.java                                                       |  377 
 src/mainline/fastlane/metadata/android/en-US/changelogs/159.txt                                                                                           |    1 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/GetActivitySamples.java               |   47 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/PaiSample.java                                                                               |   35 
 app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/ProbeReplyTest.java                   |   32 
 app/src/main/res/xml/devicesettings_root_touch_options.xml                                                                                                |    9 
 app/src/main/res/drawable/ic_filter_2.xml                                                                                                                 |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/PersianTransliterator.java                                                      |   33 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitcor2/AmazfitCor2Coordinator.java                                              |  152 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/LocaleConfig.java                                                           |   72 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SetTruSleepRequest.java                                            |   60 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetWorkoutTotalsRequest.java                                       |  141 
 app/src/main/res/drawable/ic_activity_free_training.xml                                                                                                   |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/AbstractTemperatureSample.java                                                            |   36 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/backup/ZipBackupExportJob.java                                                                |  249 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/workouts/entries/ActivitySummarySimpleEntry.java                                        |   57 
 app/src/main/res/drawable/ic_activity_swimming.xml                                                                                                        |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/file/FileHandle.java                                                       |   80 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/StopNotificationRequest.java                                       |   50 
 app/src/main/res/drawable/ic_firmware.xml                                                                                                                 |   42 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/Transliterator.java                                                                  |   21 
 app/src/main/assets/activity.svg                                                                                                                          |   83 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/webview/CurrentPosition.java                                                |   93 
 app/src/main/res/drawable/ic_pause.xml                                                                                                                    |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/logic/ActivityPacketCrc.java                                   |   90 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/DeviceManager.java                                                                         |  196 
 app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/headphones/protocol/impl/SonyTestUtils.java                                   |  121 
 app/src/main/res/drawable/ic_paint.xml                                                                                                                    |   12 
 app/src/main/res/drawable-xhdpi/ic_notification_full_battery.png                                                                                          |    0 
 app/src/main/res/drawable/ic_gps_edit.xml                                                                                                                 |   13 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/MultiTransliterator.java                                                             |   42 
 app/src/main/assets/ic_distance.svg                                                                                                                       |   81 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/nothing/AbstractEarCoordinator.java                                                        |  103 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbips/AmazfitBipSFWInstallHandler.java                                         |   49 
 app/src/test/java/nodomain/freeyourgadget/gadgetbridge/test/HuamiActivityDetailsParserTest.java                                                           |  107 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/SpinnerWithIconAdapter.java                                                                |   78 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/womp/WompJsBridge.java                                                                             |  116 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitbip/AmazfitBipSupport.java                                            |   93 
 app/src/main/res/drawable/ic_device_hplus.xml                                                                                                             |   34 
 app/src/main/res/drawable-mdpi/ic_notification_disconnected.png                                                                                           |    0 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetBondRequest.java                                                |   77 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/GetTripleTapEnabledRequest.java                            |   26 
 app/src/main/res/xml/devicesettings_liftwrist_display_noshed.xml                                                                                          |    9 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/GetStepsDataCommand.java                                                    |   84 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/zeppe/ZeppECoordinator.java                                                          |  143 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/FossilFileReader.java                                                              |  328 
 app/src/main/res/drawable/ic_activity_frisbee.xml                                                                                                         |   14 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/services/XiaomiWeatherService.java                                          |  651 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/FossilInstallHandler.java                                                          |  111 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/MiBandSupport.java                                                          | 1287 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/FetchSpo2NormalOperation.java                               |  128 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetAppNames.java                                                   |   33 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/miband7pro/MiBand7ProCoordinator.java                                               |   56 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/BodyPropertiesSetting.java                    |   56 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/calendar/CalendarEntry.java                            |   81 
 src/mainline/fastlane/metadata/android/en-US/changelogs/177.txt                                                                                           |    2 
 app/src/main/res/xml/devicesettings_expose_hr_thirdparty.xml                                                                                              |   10 
 .gitattributes                                                                                                                                            |   17 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/ChecksumCalculator.java                                                     |   50 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiChannelHandler.java                                                   |   30 
 app/src/main/res/drawable/ic_alert_circle_outline.xml                                                                                                     |    9 
 app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/oppo/OppoHeadphonesProtocolTest.java                                               |   44 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_26.java                                                         |   43 
 app/src/main/res/drawable/ic_device_amazfit_bip.xml                                                                                                       |   26 
 app/src/main/res/xml/devicesettings_hplus.xml                                                                                                             |   18 
 app/src/main/res/drawable/ic_no_watch.xml                                                                                                                 |   15 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/HeartRateDialog.java                                                                    |  153 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetBatteryLevelRequest.java                                        |   99 
 app/src/main/res/values-ta/strings.xml                                                                                                                    |    2 
 app/src/main/res/xml/devicesettings_miband5.xml                                                                                                           |   33 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/notification/NotificationFilterGetRequest.java             |   36 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/TurkishTransliterator.java                                                      |   29 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/nothing/NothingIOThread.java                                                       |   69 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/QHybridSettingsCustomizer.java                                                     |   60 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/CreateFileMessage.java                                             |   62 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/MultiFetchRequest.java                                              |  110 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/headphones/protocol/Message.java                                              |  213 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminFitFileInstallHandler.java                                                    |  231 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/TemperatureSample.java                                                                       |   28 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/async/ConfirmAppStatusRequest.java                      |   48 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/dialogs/MaterialDialogFragment.java                                                           |   45 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendEphemerisOperatorResponse.java                                 |   27 
 src/mainline/fastlane/metadata/android/en-US/changelogs/130.txt                                                                                           |    4 
 app/src/main/res/drawable/ic_activity_sailing.xml                                                                                                         |   12 
 app/src/main/res/mipmap-mdpi/ic_launcher_round.png                                                                                                        |    0 
 src/mainline/fastlane/metadata/android/en-US/changelogs/173.txt                                                                                           |   14 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/ImageMetaData.java                    |   66 
 app/src/main/res/drawable/ic_notifications.xml                                                                                                            |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitbips/AmazfitBipSLiteFirmwareInfo.java                                 |   78 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/WeatherSpec.java                                                                             |  505 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/jyou/TeclastH30/TeclastH30Coordinator.java                                                 |  184 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetSupportedServicesRequest.java                                   |   68 
 app/src/main/res/xml/devicesettings_wearmode.xml                                                                                                          |   12 
 app/src/main/assets/fossil_hr/widgetCustom.bin                                                                                                            |    0 
 app/src/nightly/res/mipmap-anydpi-v26/ic_launcher_round.xml                                                                                               |    5 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/coordinators/SonyWFC500Coordinator.java                                    |   59 
 app/src/main/res/drawable/ic_u_turn_left.xml                                                                                                              |   10 
 app/src/nopebble/res/mipmap-anydpi-v26/ic_launcher_round.xml                                                                                              |    5 
 app/src/main/res/xml/devicesettings_nutmini.xml                                                                                                           |   23 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/MusicControlEntityUpdateMessage.java                               |  112 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetEventAlarmList.java                                             |  109 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/AbstractHeartRateSample.java                                                              |   35 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/cmfwatchpro/CmfWatchPro2Coordinator.java                                                   |   54 
 app/src/main/res/drawable/list_selector.xml                                                                                                               |    5 
 app/src/main/res/layout/activity_qhybrid_apps_management.xml                                                                                              |   17 
 app/src/main/res/drawable/ic_battery_unknown.xml                                                                                                          |    4 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/casio/ecbs100/CasioECBS100DeviceCoordinator.java                                           |   67 
 app/src/main/java/org/bouncycastle/shaded/crypto/CipherParameters.java                                                                                    |    8 
 src/mainline/fastlane/metadata/android/en-US/changelogs/182.txt                                                                                           |   11 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/ProtocolBufferHandler.java                                                  |  601 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiCoordinator.java                                                                |  587 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SetEnabledFeaturesRequest.java                                      |   61 
 app/src/main/res/drawable/ic_activity_hula_hoop.xml                                                                                                       |   14 
 app/src/main/res/drawable/ic_activity_windsurfing.xml                                                                                                     |   16 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/file/FileEncryptedPutRequest.java                       |  279 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DashboardUtils.java                                                                           |  235 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiMenuType.java                                                           |   67 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/InstallHandler.java                                                                        |   50 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/LiveWorkoutStart.java                 |   50 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminWorkoutParser.java                                                            |  369 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qc35/QC35Protocol.java                                                             |   92 
 src/mainline/fastlane/metadata/android/en-US/changelogs/103.txt                                                                                           |    6 
 app/src/main/java/lineageos/weather/RequestInfo.java                                                                                                      |  379 
 app/src/main/res/drawable/ic_device_unknown.xml                                                                                                           |   22 
 app/src/main/res/drawable/ic_device_pinetime.xml                                                                                                          |   66 
 app/src/main/assets/buds_pro_left.svg                                                                                                                     |   67 
 app/src/main/res/menu/activity_take_screenshot_menu.xml                                                                                                   |   78 
 app/src/main/res/menu/fragment_devices_device_submenu.xml                                                                                                 |   25 
 app/src/main/java/net/osmand/aidlapi/gpx/AGpxBitmap.java                                                                                                  |   49 
 app/src/test/java/nodomain/freeyourgadget/gadgetbridge/test/BLETypeConversionsTest.java                                                                   |  218 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/UiPagesCommand.java                                                         |   90 
 app/src/main/res/drawable-mdpi/ic_notification_disconnected_qhybrid.png                                                                                   |    0 
 app/src/main/res/xml/devicesettings_temperature_automatic_enable.xml                                                                                      |    9 
 app/src/main/res/drawable/ic_activity_indoor_running.xml                                                                                                  |   14 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/cmfwatchpro/samples/CmfWorkoutGpsSampleProvider.java                                       |   56 
 app/src/main/res/xml/devicesettings_hourly_chime.xml                                                                                                      |   30 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/instinct/GarminInstinct2SolarCoordinator.java                               |   19 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/FileUpload.java                                                             |  239 
 app/src/banglejs/res/values/strings.xml                                                                                                                   |    9 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/status/NotificationServiceStatusRequest.java           |   26 
 app/src/main/res/xml/devicesettings_device_card_activity_card_preferences.xml                                                                             |   50 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/AbstractMiFirmwareInfo.java                                                 |  141 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbipupro/AmazfitBipUProCoordinator.java                                        |  183 
 src/mainline/fastlane/metadata/android/en-US/changelogs/204.txt                                                                                           |    9 
 src/mainline/fastlane/metadata/android/it-IT/title.txt                                                                                                    |    1 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/um25/Coordinator/UM25Coordinator.java                                                      |  120 
 app/src/main/res/xml/devicesettings_buttonactions_lower_short.xml                                                                                         |   11 
 app/src/main/res/drawable/ic_power_settings_new.xml                                                                                                       |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/UnknownDeviceCoordinator.java                                                              |  177 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/nothing/CmfBudsPro2Coordinator.java                                                        |   43 
 app/src/main/res/drawable/ic_drink.xml                                                                                                                    |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/StepsPeriodFragment.java                                                         |  231 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiPacket.java                                                                   | 1006 
 app/src/main/res/drawable/ic_buds_pro_right.xml                                                                                                           |   17 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/DevFieldDefinition.java                                                 |   60 
 app/src/main/java/org/bouncycastle/shaded/crypto/BlockCipher.java                                                                                         |   56 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendMenstrualCapabilityRequest.java                                |   56 
 src/mainline/fastlane/metadata/android/en-US/changelogs/168.txt                                                                                           |    2 
 app/src/nightly/res/mipmap-xxxhdpi/ic_launcher_round.png                                                                                                  |    0 
 app/src/main/res/layout/layout_widget_distance.xml                                                                                                        |   47 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/colmi/ColmiR0xConstants.java                                                               |   67 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SetMediumToStrengthThresholdRequest.java                           |   79 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerMorpheuz.java                                              |  185 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/FileDownloadService2C.java                                                  |  223 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceCandidate.java                                                                        |  248 
 app/src/main/res/xml/devicesettings_root_date_time.xml                                                                                                    |    9 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitSkinTempRaw.java                                            |   32 
 app/src/main/assets/fossil_hr/widgetSpO2.bin                                                                                                              |    0 
 app/src/main/res/layout/sort_list_array_dialog_preference.xml                                                                                             |   17 
 app/src/test/java/nodomain/freeyourgadget/gadgetbridge/util/EmojiConverterTest.java                                                                       |   53 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitSpo2.java                                                   |   42 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetHiChainRequest.java                                             |  256 
 app/src/main/res/xml/devicesettings_text_bitmaps.xml                                                                                                      |   18 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/FitAsyncProcessor.java                                                  |   78 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/binary_sensor/protocol/parameter/SensorState.java                                  |   48 
 app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/headphones/protocol/impl/MockSonyCoordinator.java                             |   53 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbip3pro/AmazfitBip3ProCoordinator.java                                        |  191 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/redmiwatch2/RedmiWatch2Coordinator.java                                             |   49 
 app/src/main/res/layout/fragment_respiratory_rate_period.xml                                                                                              |   78 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/AbstractZeppOsService.java                                            |  130 
 app/src/main/res/xml/devicesettings_garmin_agps.xml                                                                                                       |   20 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/gpx/GpxParseException.java                                                                    |   27 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/util/UIntBitReader.java                                                  |   43 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casio/gbx100/CasioGBX100DeviceSupport.java                                         |  727 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitDeveloperData.java                                          |   32 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/coordinators/SonyLinkBudsSCoordinator.java                                 |   72 
 app/src/main/res/drawable/ic_device_watchxplus_disabled.xml                                                                                               |   39 
 app/src/nightly/res/mipmap-hdpi/ic_launcher_foreground.png                                                                                                |    0 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/galaxy_buds/GalaxyBudsProDeviceCoordinator.java                                            |   77 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/AbstractGattCallback.java                                                             |   67 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/no1f1/No1F1Support.java                                                            |  602 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/HuamiFetchDataType.java                                     |   46 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hplus/HPlusDataRecordDaySummary.java                                               |  117 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionHealthOverlayData.java                                        |  127 
 app/src/main/res/values/ic_launcher_background.xml                                                                                                        |    4 
 app/src/test/java/nodomain/freeyourgadget/gadgetbridge/test/MiscTest.java                                                                                 |   14 
 app/src/main/assets/fossil_hr/image_layout.json                                                                                                           |    1 
 app/src/main/res/xml/devicesettings_huami2021_control_center.xml                                                                                          |   13 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/widgets/WidgetScreenListAdapter.java                                                    |  124 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/huaweiwatchgt2e/HuaweiWatchGT2eCoordinator.java                                     |   47 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/CameraActivity.java                                                                     |  218 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DebugActivity.java                                                                      | 1260 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/miband8pro/MiBand8ProCoordinator.java                                               |   55 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/marstek/MarstekB2500DeviceCoordinator.java                                                 |  102 
 app/src/main/res/layout/fragment_womp_workbench.xml                                                                                                       |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/message/WithingsMessageType.java                     |   68 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitbip3/AmazfitBip3FirmwareInfo.java                                     |   82 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiSppPacketV1.java                                                      |  331 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/SingleAlarmSetting.java                       |   64 
 app/src/main/assets/womp/recycle.html                                                                                                                     |  152 
 app/src/main/res/xml/devicesettings_wifi_hotspot.xml                                                                                                      |   47 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractSampleProvider.java                                                                |  399 
 app/src/main/res/drawable/ic_font_size.xml                                                                                                                |    5 
 app/src/main/assets/fossil_hr/icWthStormy.rle                                                                                                             |    2 
 app/src/main/res/layout/item_file_manager.xml                                                                                                             |   64 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/mijia_lywsd/MijiaLywsd02Coordinator.java                                                   |   48 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/mijia_lywsd/MijiaLywsdSupport.java                                                 |  329 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/HybridHRWatchfaceSettingsActivity.java                                             |  207 
 app/src/main/java/org/bouncycastle/shaded/crypto/SkippingCipher.java                                                                                      |   31 
 app/src/main/java/com/mobeta/android/dslv/DragSortItemViewCheckable.java                                                                                  |   53 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetWorkoutDataRequest.java                                         |  150 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/widget/WidgetsPutRequest.java                           |   55 
 gradle/wrapper/gradle-wrapper.properties                                                                                                                  |    7 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/AbstractActivitySample.java                                                               |  127 
 app/src/main/res/drawable/ic_battery_20.xml                                                                                                               |   13 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/casio/CasioConstants.java                                                                  |  154 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/fieldDefinitions/FieldDefinitionHrTimeInZone.java                       |   10 
 app/src/main/res/xml/devicesettings_timeformat.xml                                                                                                        |   12 
 app/src/main/res/drawable/ic_activity_ice_skating.xml                                                                                                     |   18 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/LefunConstants.java                                                                  |   86 
 app/src/main/res/xml/devicesettings_inactivity_sheduled.xml                                                                                               |   74 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendFactoryResetRequest.java                                       |   50 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/AppsManagementActivity.java                                                        |  162 
 app/src/main/res/layout/activity_alarm_details.xml                                                                                                        |  170 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbip/AmazfitBipLiteCoordinator.java                                            |   98 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/hplus/HPlusWeatherCode.java                                                                |  197 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/GetSleepDataCommand.java                                                    |  100 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/ble/PebbleGATTClient.java                                                   |  300 
 app/src/main/assets/fossil_hr/widgetSpO2_preview.png                                                                                                      |    0 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetContactsCount.java                                              |   45 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiDictTypes.java                                                                |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbip/AmazfitBipLiteFWInstallHandler.java                                       |   49 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/mijia_lywsd/MijiaLywsdSettingsCustomizer.java                                              |   95 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/nut/NutConstants.java                                                                      |  113 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitRecord.java                                                 |  146 
 app/src/main/res/drawable/ic_device_galaxy_buds_pro_disabled.xml                                                                                          |   36 
 src/mainline/fastlane/metadata/android/en-US/changelogs/195.txt                                                                                           |    7 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/DashboardCaloriesActiveGoalWidget.java                                        |   58 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casio/gb6900/CasioGB6900DeviceSupport.java                                         |  724 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/zeppos/ZeppOsCoordinator.java                                                        |  629 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/battery/BatteryInfoProfile.java                                              |   94 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lenovo/watchxplus/WatchXPlusDeviceCoordinator.java                                         |  319 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ConfigureWorldClocks.java                                                               |  220 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/zeppos/ZeppOsAgpsInstallHandler.java                                                 |  138 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/TimePicker.java                                                                    |  297 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/zeppe/ZeppEFWInstallHandler.java                                                     |   49 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/CameraRemote.java                                                           |  110 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsAppsService.java                                       |  225 
 app/src/main/res/xml/devicesettings_heartrate_sleep.xml                                                                                                   |   26 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbip/AmazfitBipCoordinator.java                                                |  142 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/Cmd25Command.java                                                           |   85 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/soundcore/motion300/SoundcoreMotion300DeviceSupport.java                           |   43 
 app/src/main/res/layout/activity_dashboard_calendar.xml                                                                                                   |   78 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/Calls.java                                                                  |   60 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/withingssteelhr/WithingsCalibrationActivity.java                                           |  159 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/Alarm.java                                                                                   |   64 
 app/src/main/res/drawable/ic_delete_forever.xml                                                                                                           |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivityAmounts.java                                                                         |   45 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/WorldClock.java                                                                              |   32 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/cycling_sensor/support/CyclingSensorBaseSupport.java                               |   26 
 app/src/main/res/layout/activity_main_app_bar.xml                                                                                                         |   61 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitgtr2/AmazfitGTR2eFirmwareInfo.java                                    |  120 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/SetDeviceStateAction.java                                                     |   53 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsNotificationService.java                               |  600 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfittrex/AmazfitTRexFWInstallHandler.java                                         |   49 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiManualSampleProvider.java                                                     |  125 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/test/TestDeviceSpecificSettingsCustomizer.java                                             |  134 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/id115/ID115Coordinator.java                                                                |  179 
 app/src/main/assets/ic_device_unknown_disabled.svg                                                                                                        |  197 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitgtr/AmazfitGTRFirmwareInfo.java                                       |  120 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/NestedFragmentAdapter.java                                                                 |   52 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/colmi/ColmiR09Coordinator.java                                                             |   38 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiAuthService.java                                                      |  395 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/SleepAlarmWidgetConfigurationActivity.java                                              |  154 
 app/src/main/res/xml/devicesettings_sony_headphones_device_info.xml                                                                                       |   27 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/DBAccess.java                                                                             |   71 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SetHydrationReminderIntervalRequest.java                            |   65 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/LiveWorkoutEnd.java                   |   50 
 app/src/main/res/drawable/ic_show_chart.xml                                                                                                               |   11 
 app/src/test/resources/gpx-exporter-test-SampleTrack.gpx                                                                                                  |   73 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/venu/GarminVenu2SCoordinator.java                                           |   18 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/AbstractGBX100ActivitySample.java                                                         |   27 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GBTarFile.java                                                                                |   79 
 src/mainline/fastlane/metadata/android/en-US/changelogs/107.txt                                                                                           |    4 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/Huami2021ChunkedDecoder.java                                                 |  148 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiLESupport.java                                                        |  228 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgtr/AmazfitGTRFWHelper.java                                                   |   40 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hama/fit6900/HamaFit6900DeviceSupport.java                                         |  633 
 app/src/main/res/drawable/ic_activity_trail_run.xml                                                                                                       |   10 
 app/src/main/res/layout/sport_activity_filter.xml                                                                                                         |  244 
 app/src/main/res/drawable/ic_warning_gray.xml                                                                                                             |    5 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/honorwatchgspro/HonorWatchGSProCoordinator.java                                     |   51 
 app/src/main/res/drawable/ic_menu.xml                                                                                                                     |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/RespiratoryRateFragment.java                                                     |  155 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/fenix/GarminFenix5Coordinator.java                                          |   18 
 app/src/main/res/drawable/ic_device_pebble.xml                                                                                                            |   42 
 app/src/main/res/drawable/ic_activity_unknown_small.xml                                                                                                   |    4 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/casio/CasioDeviceCoordinator.java                                                          |   45 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBand2SampleProvider.java                                                          |  101 
 app/src/main/assets/ic_activity_rope_jump.svg                                                                                                             |   90 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleIoThread.java                                                         |  767 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsRemindersService.java                                  |  379 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/headphones/deviceevents/SonyHeadphonesEnqueueRequestEvent.java                |   40 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitcheetahround/AmazfitCheetahRoundCoordinator.java                              |   86 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/GarminSupport.java                                                          |  959 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/galaxy_buds/GalaxyBuds2DeviceCoordinator.java                                              |   76 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_80.java                                                         |   44 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/AbstractStressSample.java                                                                 |   45 
 app/src/main/res/drawable-xhdpi/ic_launcher.png                                                                                                           |    0 
 app/src/nightly/res/mipmap-xhdpi/ic_launcher.png                                                                                                          |    0 
 app/src/main/assets/ic_device_galaxy_buds_live_disabled.svg                                                                                               |  218 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/.gitignore                                                                                |    2 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SetProfileRequest.java                                              |   72 
 app/src/main/res/xml/devicesettings_buttonactions_upper_long.xml                                                                                          |   11 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/protobuf/messagefields/VarintMessageField.java                                                |   40 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiSleepStageSampleProvider.java                                                 |   56 
 app/src/main/res/xml/devicesettings_liftwrist_display_no_on.xml                                                                                           |   31 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/ZeppOsWeather.java                                                    |  813 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiConstants.java                                                                |   99 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendAccountRequest.java                                            |   56 
 app/src/main/res/xml/loyalty_cards.xml                                                                                                                    |   89 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiFWHelper.java                                                                 |  368 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/VivitarHrBpMonitorActivityTrackerCoordinator.java                                    |   61 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/cmfwatchpro/CmfDataUploader.java                                                   |  198 
 app/src/main/res/layout/activity_appmanager.xml                                                                                                           |   14 
 app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/FitImporterTest.java                                                    |   16 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/message/incoming/IncomingMessageHandler.java         |   23 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/cmfwatchpro/workout/CmfWorkoutSummaryParser.java                                           |   74 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/message/ExpectedResponse.java                        |   23 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/FlattenToAsciiTransliterator.java                                               |   40 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/notification/AncsConstants.java                      |   45 
 app/src/main/java/org/bouncycastle/shaded/crypto/modes/AEADCipher.java                                                                                    |  139 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SetWorkModeRequest.java                                            |   56 
 app/src/nightly/res/values/strings.xml                                                                                                                    |    8 
 app/src/main/assets/ic_galaxy_buds_live_r.svg                                                                                                             |   61 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/MusicControlCapabilitiesMessage.java                               |   48 
 app/src/main/res/xml/devicesettings_power_mode.xml                                                                                                        |   11 
 app/src/main/assets/fossil_hr/widgetCalories_preview.png                                                                                                  |    0 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitWorkout.java                                                |   52 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/NotificationStrategy.java                                                   |   46 
 app/src/main/res/drawable/ic_calendar_sync.xml                                                                                                            |   14 
 app/src/main/aidl/net/osmand/aidlapi/search/SearchResult.aidl                                                                                             |    3 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/AbstractTimeSample.java                                                                   |   49 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/notification/defines/VibrationOptions.java             |   30 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SetPauseWhenRemovedFromEarRequest.java                             |   31 
 app/src/main/res/drawable/ic_sony_wf_800n_case.xml                                                                                                        |   17 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AbstractGBFragment.java                                                                 |   74 
 app/src/test/java/nodomain/freeyourgadget/gadgetbridge/test/ProtobufTest.java                                                                             |  129 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ConfigureReminders.java                                                                 |  210 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/fieldDefinitions/FieldDefinitionGoalType.java                           |   56 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/activity/WithingsActivityType.java                                 |  168 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/GreekTransliterator.java                                                        |   36 
 app/src/main/res/color/alarm_dow.xml                                                                                                                      |    5 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/fenix/GarminFenix7ProCoordinator.java                                       |   18 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/redmibuds5pro/protocol/Authentication.java                                         |  181 
 app/src/main/java/com/mobeta/android/dslv/SimpleDragSortCursorAdapter.java                                                                                |  427 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/waspos/WaspOSDeviceSupport.java                                                    |  406 
 app/src/main/res/xml/notifications_preferences.xml                                                                                                        |  213 
 app/src/main/assets/fossil_hr/icWthSnowy.rle                                                                                                              |    1 
 app/src/main/lint.xml                                                                                                                                     |    6 
 app/src/main/res/drawable-mdpi/ic_notification_full_battery.png                                                                                           |    0 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/cmfwatchpro/CmfActivitySync.java                                                   |  668 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hplus/HPlusDataRecordDaySlot.java                                                  |  122 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/GetSleepDataRequest.java                                            |   85 
 src/banglejs/fastlane/metadata/android/en-US/images/icon.png                                                                                              |    0 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/AlarmName.java                        |   44 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/DeviceInfo.java                                                             |  155 
 GBDaoGenerator/.gitignore                                                                                                                                 |    2 
 app/src/main/res/xml/devicesettings_overwrite_settings_on_connection.xml                                                                                  |   10 
 app/src/main/assets/ic_activity_eliptical.svg                                                                                                             |   86 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/miband3/MiBand3FirmwareInfo.java                                             |  127 
 app/src/main/res/drawable-xhdpi/ic_watchapp.png                                                                                                           |    0 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerSquare.java                                                |   94 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitpoppro/AmazfitPopProFWHelper.java                                             |   40 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/miband8/MiBand8Coordinator.java                                                     |   49 
 app/src/main/res/xml/devicesettings_sony_headphones_notifications_voice_guide.xml                                                                         |    9 
 app/src/main/res/mipmap-xxhdpi/ic_launcher.png                                                                                                            |    0 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/Reminder.java                                                                                |   38 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitgts2/AmazfitGTS2MiniSupport.java                                      |   52 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/activity/impl/DailyDetailsParser.java                                       |  198 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/headphones/SonyHeadphonesSupport.java                                         |   64 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/FactoryResetRequest.java                                   |   26 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminBodyEnergySampleProvider.java                                                 |   56 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/ProbeReply.java                       |  111 
 app/src/main/res/layout/fossil_hr_row_commute_action.xml                                                                                                  |   25 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/cmfwatchpro/CmfWatchProCoordinator.java                                                    |  377 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/defines/StatusPageId.java                     |   34 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/RtlUtils.java                                                                                 |  612 
 app/src/main/res/layout/activity_charts.xml                                                                                                               |  115 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ActivityListingChartFragment.java                                                |  244 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsLogsService.java                                       |  224 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/HybridHRSpo2SampleProvider.java                                                    |   56 
 app/src/main/res/layout/activity_app_specific_notification_settings.xml                                                                                   |   25 
 app/src/main/res/drawable/ic_folder.xml                                                                                                                   |   10 
 app/src/main/res/layout/activity_find_phone.xml                                                                                                           |   14 
 app/src/main/res/drawable/ic_sensor_calibration.xml                                                                                                       |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventSilentMode.java                                                          |   29 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitHrvSummary.java                                             |   63 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/huaweiband6/HuaweiBand6Coordinator.java                                             |   41 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/connection/SetConnectionParametersRequest.java             |   53 
 app/src/nightly/res/mipmap-mdpi/ic_launcher.png                                                                                                           |    0 
 src/mainline/fastlane/metadata/android/en-US/changelogs/137.txt                                                                                           |   10 
 app/src/main/res/drawable/ic_vibration.xml                                                                                                                |   10 
 src/mainline/fastlane/metadata/android/en-US/changelogs/222.txt                                                                                           |   23 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/galaxy_buds/GalaxyBudsProtocol.java                                                |  548 
 app/src/main/res/xml/devicesettings_pebble_developer.xml                                                                                                  |   60 
 .gitmodules                                                                                                                                               |    6 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/BodyEnergyFragment.java                                                          |  305 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/zetime/ZeTimeConstants.java                                                                |  156 
 app/src/main/res/values-hu/strings.xml                                                                                                                    | 2710 
 app/src/main/res/xml/devicesettings_header_other.xml                                                                                                      |    6 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitband7/AmazfitBand7Coordinator.java                                            |   68 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/DebugRequest.java                                                  |  241 
 app/src/main/res/drawable/ic_arrow_circle_left.xml                                                                                                        |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/workouts/WorkoutValueFormatter.java                                                     |  159 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/headphones/protocol/impl/v1/SonyProtocolImplV1.java                           | 1340 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/DisconnectNotificationSetting.java                                                   |   23 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_30.java                                                         |   38 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_83.java                                                         |   47 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/fieldDefinitions/FieldDefinitionGoalSource.java                         |   48 
 app/src/main/res/drawable-xhdpi/ic_languagepack.png                                                                                                       |    0 
 app/src/main/res/layout/fragment_body_energy.xml                                                                                                          |   92 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitbipu/AmazfitBipUFirmwareInfo.java                                     |   85 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/gps/providers/MockLocationProvider.java                                             |   97 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/huaweitalkbandb6/HuaweiTalkBandB6Coordinator.java                                   |   46 
 app/src/test/java/nodomain/freeyourgadget/gadgetbridge/test/FileUtilsTest.java                                                                            |   44 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/SleepAsAndroidSender.java                                                                  |  573 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/AutoPowerOffSettings.java                     |   40 
 app/src/main/res/layout/item_world_clock.xml                                                                                                              |   66 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/status/StatusRequestType.java                          |   34 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/TrailingActivitySample.java                                                      |   55 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/binary_sensor/protocol/constants/ReportState.java                                  |   35 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/prefs/EqualizerCustomBands.java                                            |   86 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/oppo/commands/TouchConfigSide.java                                                 |   47 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/Locale.java                           |   44 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/wena3/SonyWena3SettingsCustomizer.java                                                |   80 
 app/src/main/res/xml/devicesettings_xiaomi_displayitems.xml                                                                                               |   13 
 src/mainline/fastlane/metadata/android/en-US/changelogs/121.txt                                                                                           |    1 
 src/mainline/fastlane/metadata/android/en-US/changelogs/164.txt                                                                                           |    4 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/Mi1AFirmwareInfo.java                                                       |   52 
 app/src/main/res/drawable/ic_devices_other.xml                                                                                                            |   10 
 app/src/main/res/drawable/ic_galaxy_buds_live_l.xml                                                                                                       |   16 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/domyos/DomyosT540Support.java                                                      |  314 
 app/src/main/res/drawable/ic_device_flipper.xml                                                                                                           |  238 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/redmibuds5pro/protocol/Message.java                                                |  124 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/calendar/CalendarEvent.java                                                                   |  189 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/status/NotificationSubscriptionStatusMessage.java                  |   50 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/LockHandler.java                                                                                   |  116 
 app/src/main/res/xml/widget_info.xml                                                                                                                      |   15 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitx/AmazfitXFWInstallHandler.java                                               |   50 
 app/src/main/res/drawable/ic_create_new_folder.xml                                                                                                        |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitHrvValue.java                                               |   32 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/conversation/HeartRateHandler.java                   |   89 
 app/src/main/res/xml/devicesettings_fossilhybridhr_workout_detection.xml                                                                                  |  125 
 app/src/main/res/xml/devicesettings_galaxy_buds_pro.xml                                                                                                   |  247 
 app/src/main/res/xml/devicesettings_gps_agps.xml                                                                                                          |   71 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/binary_sensor/protocol/parameter/ResultCode.java                                   |   33 
 app/src/main/res/values-he/strings.xml                                                                                                                    | 3093 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/miband9pro/MiBand9ProCoordinator.java                                               |   55 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/image/ImageConverter.java                               |  170 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sonyswr12/SonySWR12SampleProvider.java                                                     |   99 
 src/mainline/fastlane/metadata/android/it-IT/full_description.txt                                                                                         |    8 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/control/ControlPoint.java                                       |   33 
 src/mainline/fastlane/metadata/android/en-US/changelogs/217.txt                                                                                           |    4 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiWorkoutUtils.java                                                     |   48 
 src/mainline/fastlane/metadata/android/en-US/changelogs/160.txt                                                                                           |    8 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/gps/GBLocationProviderType.java                                                     |   47 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsHttpService.java                                       |  139 
 app/src/main/res/drawable/ic_device_sony_overhead_disabled.xml                                                                                            |   28 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/hplus/MakibesF68Coordinator.java                                                           |   56 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiSppProtocolV1.java                                                    |  120 
 app/src/main/res/xml/devicesettings_lowlatency_fwupdate.xml                                                                                               |   10 
 app/src/main/assets/tws_bud.svg                                                                                                                           |   76 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btbr/BtBRQueue.java                                                                        |  279 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/banglejs/BangleJSConstants.java                                                            |   32 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/logic/parsers/SamplePacketParser.java                          |   98 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/ActivitySummaryUtils.java                                                                     |   87 
 app/src/main/res/xml/devicesettings_sony_headphones_anc_optimizer.xml                                                                                     |   31 
 app/src/main/res/drawable/ic_folder_delete.xml                                                                                                            |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/WidgetConfigurationActivity.java                                                        |  147 
 app/src/main/res/drawable-xhdpi/ic_notification_disconnected.png                                                                                          |    0 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/BodyEnergySample.java                                                                        |   24 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/NotificationSpec.java                                                                        |   89 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/TimeZoneSetting.java                          |   46 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/ProtobufMessage.java                                               |   95 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SetGeneralSettingsRequest.java                                      |   76 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/DoNotDisturb.java                                                                   |   24 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/oppo/commands/OppoCommand.java                                                     |   55 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiSportsActivityType.java                                                 |  151 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/Mi1FirmwareInfo.java                                                        |   52 
 app/src/main/res/drawable-hdpi/ic_notification_full_battery.png                                                                                           |    0 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/tlw64/TLW64Coordinator.java                                                                |  151 
 app/src/main/res/drawable-mdpi/ic_notification_low_battery.png                                                                                            |    0 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendWeatherSupportRequest.java                                     |   64 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/status/Weather.java                                    |  135 
 app/src/main/res/xml/devicesettings_sound_and_vibration.xml                                                                                               |   51 
 app/src/main/assets/fossil_hr/widget_bg_error.rle                                                                                                         |   15 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/RequestHandControlRequest.java                             |   52 
 src/mainline/fastlane/metadata/android/en-US/changelogs/152.txt                                                                                           |    1 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lenovo/LenovoWatchCalibrationActivity.java                                                 |  124 
 src/mainline/fastlane/metadata/android/en-US/changelogs/208.txt                                                                                           |   16 
 app/src/main/assets/ic_heartrate.svg                                                                                                                      |   85 
 .woodpecker/can_master_build.yml                                                                                                                          |   19 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/PermissionsActivity.java                                                                |   38 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_15.java                                                         |   42 
 app/src/main/assets/ic_device_galaxy_buds_disabled.svg                                                                                                    |  242 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/fenix/GarminFenix5PlusCoordinator.java                                      |   24 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitgtr/AmazfitGTRSupport.java                                            |   69 
 app/src/main/res/layout/activity_list_item.xml                                                                                                            |  289 
 app/src/main/res/android-logger.properties                                                                                                                |   28 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/vesc/VescCoordinator.java                                                                  |  184 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/binary_sensor/protocol/parameter/ReportStatus.java                                 |   34 
 app/src/main/res/drawable/ic_add.xml                                                                                                                      |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/QHybridCoordinator.java                                                            |  353 
 app/src/main/res/drawable/ic_nothing_ear_r.xml                                                                                                            |   11 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/notification/NotificationImage.java                     |   55 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casio/Casio2C2DSupport.java                                                        |  997 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitWatchfaceSettings.java                                      |   32 
 app/src/main/res/drawable/ic_activity_rock_climbing.xml                                                                                                   |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/MoveHandsRequest.java                                      |  136 
 src/mainline/fastlane/metadata/android/en-US/changelogs/98.txt                                                                                            |    9 
 app/build.gradle                                                                                                                                          |  303 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/divoom/PixooSupport.java                                                           |   69 
 app/src/test/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/TestNotifications.java                                                      |  195 
 app/src/main/java/com/mobeta/android/dslv/DragSortListPreference.java                                                                                     |  185 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/authentication/AuthenticationRequest.java               |   28 
 src/mainline/fastlane/metadata/android/en-US/changelogs/234.txt                                                                                           |    4 
 app/src/main/res/drawable/ic_heartrate.xml                                                                                                                |    4 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/AbstractPebbleMorpheuzActivitySample.java                                                 |   33 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/operations/ZeppOsGpxRouteUploadOperation.java                         |  106 
 app/src/main/assets/womp/css.css                                                                                                                          |   34 
 app/src/main/res/drawable/ic_settings_applications.xml                                                                                                    |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/conversation/AbstractConversation.java               |  107 
 app/src/main/res/layout/steps_streak_average.xml                                                                                                          |   38 
 app/src/main/res/xml/devicesettings_screen_timeout.xml                                                                                                    |   11 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/femometer/FemometerVinca2DeviceCoordinator.java                                            |  140 
 app/src/main/java/org/bouncycastle/shaded/crypto/MultiBlockCipher.java                                                                                    |   31 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/DeviceCoordinator.java                                                                     |  818 
 app/src/main/res/drawable/ic_donate.xml                                                                                                                   |   13 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/conversation/AbstractResponseHandler.java            |   30 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/StepAnalysis.java                                                                |  260 
 src/mainline/fastlane/metadata/android/en-US/changelogs/199.txt                                                                                           |    6 
 app/src/main/proto/garmin/gdi_core.proto                                                                                                                  |  112 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/nothing/EarStickCoordinator.java                                                           |   43 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/HeadphoneHelper.java                                                                       |  161 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceMusic.java                                                                            |   33 
 app/src/main/res/values/arrays.xml                                                                                                                        | 4490 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/AbstractColmiActivitySample.java                                                          |   37 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivitySummaryItems.java                                                                    |   62 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/VibrationSetting.java                         |   40 
 app/src/main/assets/ic_device_sony_overhead_disabled.svg                                                                                                  |  192 
 app/src/main/res/xml/devicesettings_nightmode.xml                                                                                                         |   33 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbips/AmazfitBipSCoordinator.java                                              |  163 
 app/src/nopebble/res/mipmap-xxhdpi/ic_launcher_foreground.png                                                                                             |    0 
 settings.gradle                                                                                                                                           |    1 
 app/src/main/res/xml/devicesettings_light_duration_longer.xml                                                                                             |    9 
 app/src/main/res/drawable/ic_map.xml                                                                                                                      |    9 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/DefaultRestingMetabolicRateProvider.java                                                   |  190 
 app/src/main/res/drawable/ic_volume_up.xml                                                                                                                |    3 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/miwatchcolorsport/MiWatchColorSportCoordinator.java                                 |   50 
 app/src/main/aidl/lineageos/weather/IWeatherServiceProviderChangeListener.aidl                                                                            |   22 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/test/samples/TestSampleProvider.java                                                       |  262 
 src/mainline/fastlane/metadata/android/en-US/changelogs/128.txt                                                                                           |    1 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/RespiratoryRateSample.java                                                                   |   24 
 app/src/main/res/xml/devicesettings_banglejs_activity.xml                                                                                                 |   23 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/AbstractGaugeWidget.java                                                      |  164 
 app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/notification/RequestedNotificationAttributeTest.java |   29 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/FossilRequest.java                                         |   34 
 app/src/main/res/xml/devicesettings_buttonactions.xml                                                                                                     |   93 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SetTimeRequest.java                                                |   64 
 app/src/main/res/values-pt-rBR/strings.xml                                                                                                                | 2542 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetWorkoutCountRequest.java                                        |   90 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/defines/DisplayDesign.java                    |   22 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/services/XiaomiSystemService.java                                           | 1005 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/activity/SleepActivitySampleHelper.java                            |   86 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminActivitySampleProvider.java                                                   |  217 
 src/mainline/fastlane/metadata/android/en-US/changelogs/134.txt                                                                                           |    7 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsConfigService.java                                     | 1850 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/zeppos/ZeppOsFwHelper.java                                                           |  538 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/workouts/entries/ActivitySummaryValue.java                                              |   22 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendEphemerisDataRequestResponse.java                              |   33 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/cmfwatchpro/CmfWatchProSettingsCustomizer.java                                             |   82 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/HybridHRWatchfaceFactory.java                                                      |  408 
 app/src/main/res/xml/devicesettings_goal_secondary.xml                                                                                                    |   12 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_72.java                                                         |   43 
 app/src/main/res/drawable/ic_activity_exercise.xml                                                                                                        |   10 
 app/src/main/res/drawable/ic_battery_charging_full.xml                                                                                                    |    9 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsCannedMessagesService.java                             |  240 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/HeartRateUtils.java                                                                     |   69 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgtr4/AmazfitGTR4Coordinator.java                                              |   94 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/FindPhone.java                                                              |   62 
 app/src/main/res/xml/devicesettings_automatic_power_off_by_time.xml                                                                                       |   11 
 app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationServiceTestCase.java                                                    |  135 
 app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/activity/XiaomiActivityParserTest.java                                      |   22 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/LineageOsWeatherReceiver.java                                                       |  229 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/jyou/RealtimeSamplesSupport.java                                                   |  111 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/fenix/GarminFenix5XPlusCoordinator.java                                     |   24 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/fenix/GarminFenix6SapphireCoordinator.java                                  |   18 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/AbstractHuamiOperation.java                                                  |   33 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/init/InitOperation.java                                           |  181 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/nothing/Ear1Support.java                                                           |   58 
 app/src/main/res/xml/devicesettings_huawei_account.xml                                                                                                    |    9 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitneo/AmazfitNeoFWHelper.java                                                   |   41 
 app/src/main/res/values-ka/strings.xml                                                                                                                    |  190 
 app/src/test/resources/ActivityDetailsDump1.txt                                                                                                           |  861 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/domyos/DomyosT540Coordinator.java                                                          |  155 
 app/src/main/res/drawable/ic_filter_alt_plus.xml                                                                                                          |    4 
 app/src/banglejs/res/drawable/ic_launcher_foreground.xml                                                                                                  |   72 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/venu/GarminVenuSqCoordinator.java                                           |   18 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitcor2/AmazfitCor2FWInstallHandler.java                                         |   49 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/files/FileManagerActivity.java                                                          |  135 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/forerunner/GarminForerunner55Coordinator.java                               |   34 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/oppo/OppoHeadphonesSettingsCustomizer.java                                                 |  152 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/AbstractRespiratoryRateSample.java                                                        |   35 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/ActivitySampleCalories2.java          |   25 
 app/src/test/resources/gpx-parser-test-order.gpx                                                                                                          |   36 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/hplus/SG2Coordinator.java                                                                  |   69 
 app/src/main/res/xml/devicesettings_donotdisturb_lift_wrist.xml                                                                                           |   11 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/alarm/AlarmsSetRequest.java                                |   88 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btbr/actions/AbortTransactionAction.java                                                   |   49 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionHealthSteps.java                                              |  164 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/status/UploadRequestStatusMessage.java                             |   70 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/fieldDefinitions/FieldDefinitionDayOfWeek.java                          |   31 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppe/ZeppESupport.java                                                      |   52 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/message/incoming/SyncRequestHandler.java             |   38 
 app/src/main/res/drawable/ic_share.xml                                                                                                                    |   10 
 app/src/main/res/xml/devicesettings_spo_automatic_enable.xml                                                                                              |   10 
 app/src/nightly/res/drawable-xhdpi/ic_launcher.png                                                                                                        |    0 
 app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/message/AbstractMessageTest.java                     |   60 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/status/ProtobufStatusMessage.java                                  |  141 
 src/mainline/fastlane/metadata/android/en-US/images/phoneScreenshots/50-StepsPerDay.png                                                                   |    0 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/hplus/HPlusConstants.java                                                                  |  191 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitGpsMetadata.java                                            |   32 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/instinct/GarminInstinct2SCoordinator.java                                   |   18 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/heartrate/HeartRateProfile.java                                              |   77 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/status/BatteryLevelInfo.java                           |   33 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/UkranianTransliterator.java                                                     |   29 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/StartPpgSensingCommand.java                                                 |   55 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceFolder.java                                                                           |   25 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/soundcore/motion300/SoundcoreMotion300Protocol.java                                |  385 
 src/mainline/fastlane/metadata/android/en-US/changelogs/119.txt                                                                                           |    4 
 app/src/main/res/xml/devicesettings_header_apps.xml                                                                                                       |    6 
 app/src/main/res/layout/activity_battery_info.xml                                                                                                         |   95 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btbr/actions/CheckInitializedAction.java                                                   |   46 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/notification/NotificationType.java                         |   37 
 app/src/main/res/layout/activity_qhybrid_app_choser.xml                                                                                                   |   28 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AbstractPreferenceFragment.java                                                         |  259 
 app/src/main/res/xml/devicesettings_reconnect_bl_classic.xml                                                                                              |   12 
 app/src/main/assets/sony_800n.svg                                                                                                                         |   76 
 app/src/main/res/xml/devicesettings_header_notifications.xml                                                                                              |    6 
 src/mainline/fastlane/metadata/android/en-US/changelogs/147.txt                                                                                           |    6 
 app/src/main/res/drawable/side_nav_bar.xml                                                                                                                |    9 
 app/src/main/res/xml/devicesettings_amazfittrexpro.xml                                                                                                    |   13 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/GpxRouteFileConverter.java                                              |  196 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/roidmi/RoidmiProtocol.java                                                         |   93 
 app/src/main/res/xml/devicesettings_heartrate_sleep_activity.xml                                                                                          |   37 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/GetEnabledFeaturesRequest.java                                      |   55 
 app/src/main/res/drawable/ic_device_unknown_disabled.xml                                                                                                  |   27 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/defines/HomeIconId.java                       |   50 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitbip/AmazfitBipTextNotificationStrategy.java                           |   65 
 app/src/main/res/drawable/ic_wifi.xml                                                                                                                     |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ValidByDate.java                                                                             |   24 
 app/src/main/res/xml/sleepasandroid_preferences.xml                                                                                                       |   83 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitFileId.java                                                 |   63 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/forerunner/GarminForerunner265SCoordinator.java                             |   19 
 src/mainline/fastlane/metadata/android/en-US/changelogs/226.txt                                                                                           |    5 
 app/src/main/res/drawable/ic_archive.xml                                                                                                                  |   10 
 src/mainline/fastlane/metadata/android/en-US/images/phoneScreenshots/41-SleepPerWeek.png                                                                  |    0 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitPhysiologicalMetrics.java                                   |   52 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceMusicData.java                                                                |   15 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btbr/actions/WriteAction.java                                                              |   82 
 app/src/main/res/drawable/ic_volume_off.xml                                                                                                               |    5 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/welcome/WelcomeActivity.java                                                            |   83 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/HebrewTransliterator.java                                                       |   32 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiExtendedSampleProvider.java                                                     |  144 
 app/src/main/res/xml/devicesettings_wearlocation.xml                                                                                                      |   13 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/file/FileEncryptedInterface.java                        |   20 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/activity/impl/XiaomiSimpleActivityParser.java                               |  253 
 GBDaoGenerator/build.gradle                                                                                                                               |   70 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionPebbleHealth.java                                             |   48 
 app/src/main/res/drawable/ic_focus.xml                                                                                                                    |   12 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/RussianTransliterator.java                                                      |   35 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/baseTypes/BaseTypeDouble.java                                           |   50 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/webview/GBChromeClient.java                                                 |   45 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/WidgetPreferenceStorage.java                                                                  |  181 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/update/UpdateFirmwareOperationNew.java                            |  100 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/GlyphId.java                          |   50 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/DeviceSettingsReceiver.java                                                         |  110 
 app/src/main/res/xml/devicesettings_inactivity_dnd.xml                                                                                                    |   58 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/activity/impl/WorkoutSummaryParser.java                                     |  879 
 app/src/main/java/lineageos/os/Concierge.java                                                                                                             |  153 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitneo/AmazfitNeoSupport.java                                            |  184 
 app/src/main/res/layout/activity_list_total_dashboard.xml                                                                                                 |   87 
 app/src/main/res/layout/fragment_temperaturechart.xml                                                                                                     |   19 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/music/MusicControlRequest.java                          |   92 
 app/src/main/res/drawable/ic_device_lovetoy_disabled.xml                                                                                                  |   36 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/huaweiband9/HuaweiBand9Coordinator.java                                             |   46 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/cycling_sensor/activity/CyclingLiveDataActivity.java                                       |  170 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/FetchStressAutoOperation.java                               |  119 
 app/src/main/res/xml/devicesettings_widgets.xml                                                                                                           |    7 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/CheckAuthenticationNeededAction.java                                        |   41 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitcor/AmazfitCorCoordinator.java                                                |  152 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/FetchStatisticsOperation.java                               |   57 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband2/MiBand2FWHelper.java                                                         |   42 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventMusicControl.java                                                        |   35 
 app/src/main/res/drawable/ic_activity_archery.xml                                                                                                         |   15 
 app/src/main/res/drawable/ic_device_miband.xml                                                                                                            |   27 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/activity/impl/SleepDetailsParser.java                                       |  391 
 app/src/main/res/xml/devicesettings_temperature_scale_cf.xml                                                                                              |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/oppo/OppoHeadphonesSupport.java                                                    |   44 
 app/src/main/res/values/wpi_attrs.xml                                                                                                                     |    6 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/SettingsFilePutRequest.java                                |   54 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/um25/Data/MeasurementData.java                                                     |  103 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/activity/XiaomiActivityFileId.java                                          |  276 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/appmanager/AppManagerFragmentCache.java                                                 |   51 
 app/src/main/assets/fossil_hr/widgetBattery.bin                                                                                                           |    0 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitWeather.java                                                |  121 
 app/src/main/res/drawable/ic_stop.xml                                                                                                                     |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/HybridHRWatchfaceSettings.java                                                     |  145 
 app/src/main/java/org/bouncycastle/shaded/util/Objects.java                                                                                               |   14 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/http/GarminHttpResponse.java                                                |   41 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SetNotificationRequest.java                                        |   60 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/SingleEntryValueAnimator.java                                                    |   70 
 app/src/main/res/xml/devicesettings_sleep_mode_schedule.xml                                                                                               |   27 
 app/src/main/assets/buds_pro_right.svg                                                                                                                    |   73 
 app/src/main/res/layout/fragment_respiratory_rate.xml                                                                                                     |  114 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitgts/AmazfitGTSSupport.java                                            |   86 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/app_specific_notifications/AppSpecificNotificationSettingsActivity.java                 |   76 
 app/src/main/res/xml/devicesettings_bandw_pseries.xml                                                                                                     |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/coordinators/SonyLinkBudsCoordinator.java                                  |   73 
 src/mainline/fastlane/metadata/android/en-US/changelogs/230.txt                                                                                           |    8 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionHealthHR.java                                                 |   44 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/freebuds5i/HuaweiFreebuds5iCoordinator.java                                         |   35 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/HRConfigActivity.java                                                              |  426 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SetDisconnectNotification.java                                     |   60 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ReminderDetails.java                                                                    |  232 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/notification/NotificationImagePutRequest.java           |   66 
 app/src/main/res/drawable/ic_filter_b_and_w.xml                                                                                                           |   10 
 app/src/test/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/TestHuaweiTruSleepParser.java                                                       |  229 
 .gitea/issue_template/config.yml                                                                                                                          |   11 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/fieldDefinitions/FieldDefinitionMeasurementSystem.java                  |   44 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/protobuf/messagefields/RootMessageField.java                                                  |   39 
 app/src/main/res/xml/devicesettings_um25.xml                                                                                                              |   14 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerObsidian.java                                              |  176 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/capabilities/widgets/WidgetPartSubtype.java                                                        |   48 
 app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/GarminSupportTest.java                                                      |  374 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendUploadMusicFileInfoResponse.java                               |   33 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/liveview/LiveviewConstants.java                                                            |  126 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_62.java                                                         |   38 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/preferences/PreferenceCategoryMultiline.java                                                  |   35 
 app/src/main/res/drawable/ic_hourglass_empty.xml                                                                                                          |    5 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/configuration/ConfigurationGetRequest.java              |   87 
 app/src/main/res/layout/activity_vibration.xml                                                                                                            |   16 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/fenix/GarminFenix8Coordinator.java                                          |   18 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/mijia_lywsd/AbstractMijiaLywsdCoordinator.java                                             |   80 
 app/src/main/res/xml/devicesettings_fetch_unknown_files.xml                                                                                               |   10 
 app/src/main/res/drawable/ic_activity_stacked.xml                                                                                                         |    6 
 app/src/main/res/drawable/ic_remove_image.xml                                                                                                             |   10 
 src/mainline/fastlane/metadata/android/en-US/changelogs/156.txt                                                                                           |   11 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/DashboardCalendarActivity.java                                                |  374 
 app/src/main/java/org/bouncycastle/shaded/crypto/modes/CBCBlockCipher.java                                                                                |  266 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/nut/NutKey.java                                                                            |  265 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/headphones/protocol/MessageType.java                                          |   45 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminGpxRouteInstallHandler.java                                                   |  139 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/moondrop/MoondropSpaceTravelIOThread.java                                          |   64 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/prefs/SurroundMode.java                                                    |   53 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/protobuf/messagefields/MessageField.java                                                      |   74 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/watchs3/XiaomiWatchS3Coordinator.java                                               |   55 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/ActivitySampleMovement.java           |   69 
 app/src/main/res/xml/devicesettings_nothing_ear1.xml                                                                                                      |   18 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GB.java                                                                                       |  700 
 app/src/main/res/anim/bounceright.xml                                                                                                                     |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/FileRequest.java                                           |   35 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/GetCountdownSettingsRequest.java                           |   55 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/device_info/GetDeviceInfoRequest.java                      |   93 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/redmibuds5pro/prefs/Configuration.java                                              |   47 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/activity/XiaomiActivityFileFetcher.java                                     |  218 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/DeviceButtonActionSetting.java                |   43 
 app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/TestDeviceService.java                                                                     |   60 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/fitpro/colacao/ColaCao23Coordinator.java                                                   |   56 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/BondingInterface.java                                                                         |   54 
 src/mainline/fastlane/metadata/android/en-US/changelogs/213.txt                                                                                           |   47 
 src/mainline/fastlane/metadata/android/it-IT/short_description.txt                                                                                        |    1 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/healthThermometer/HealthThermometerProfile.java                              |  140 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiPaiSampleProvider.java                                                        |  154 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgts/AmazfitGTSCoordinator.java                                                |  153 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandFWInstallHandler.java                                                         |   46 
 app/src/main/res/xml/devicesettings_sync_calendar.xml                                                                                                     |   34 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/NotificationCollectorMonitorService.java                                                   |   95 
 app/src/main/res/xml/devicesettings_huami2021_fetch_operation_time_unit.xml                                                                               |   10 
 app/src/main/java/org/bouncycastle/shaded/crypto/DataLengthException.java                                                                                 |   29 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/activity/impl/ManualSamplesParser.java                                      |  132 
 app/src/main/res/layout/fragment_hrv_status.xml                                                                                                           |  183 
 app/src/main/res/values/strings.xml                                                                                                                       | 3580 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/deviceevents/CapabilitiesDeviceEvent.java                                   |   14 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/FindMyPhoneCancelMessage.java                                      |   32 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsCalendarService.java                                   |  258 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/widget/CustomTextWidgetElement.java                     |   27 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/ProbeOsVersion.java                   |   43 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/watchs1pro/XiaomiWatchS1ProCoordinator.java                                         |   66 
 app/src/main/res/drawable/ic_pending_actions.xml                                                                                                          |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/authentication/VerifyPrivateKeyRequest.java             |  135 
 app/src/main/res/layout/fragment_sleepchart.xml                                                                                                           |  250 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/DBHelper.java                                                                             |  777 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetLinkParamsRequest.java                                          |   89 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/withingssteelhr/RotaryControl.java                                                         |  201 
 app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/ChallengeTest.java                    |   67 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/notification/PlayTextNotificationRequest.java              |   30 
 src/mainline/fastlane/take_app_screenshots.sh                                                                                                             |  646 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminRealtimeSettingsActivity.java                                                 |   94 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetNotificationConstraintsRequest.java                             |   63 
 app/src/main/res/layout/activity_womp_scan.xml                                                                                                            |   35 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SetLanguageSettingRequest.java                                     |   81 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/dialogs/MaterialMultiSelectListPreferenceDialogFragment.java                                  |  136 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_22.java                                                         |   39 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/fieldDefinitions/FieldDefinitionAlarm.java                              |   31 
 app/src/test/java/nodomain/freeyourgadget/gadgetbridge/model/ActivitySummaryDataTest.java                                                                 |  122 
 app/src/main/res/layout/activity_external_pebble_js.xml                                                                                                   |    5 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/TransactionBuilder.java                                                               |  167 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/roidmi/RoidmiSupport.java                                                          |  146 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventScreenshot.java                                                          |   29 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/notification/defines/NotificationKind.java             |   22 
 app/src/main/res/layout-land/fragment_weeksteps_chart.xml                                                                                                 |   58 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/baseTypes/BaseType.java                                                 |  100 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/redmiwatch2lite/RedmiWatch2LiteCoordinator.java                                     |   49 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SetActivityReminderRequest.java                                    |   86 
 app/src/main/res/drawable/ic_device_h30_h10_disabled.xml                                                                                                  |   32 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miscale/MiSmartScaleDeviceSupport.java                                             |  266 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitx/AmazfitXFirmwareInfo.java                                           |   85 
 app/src/main/res/xml/devicesettings_developer_add_test_activities.xml                                                                                     |    8 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/TimeChangeReceiver.java                                                             |  165 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/capabilities/GpsCapability.java                                                                    |   45 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/KoreanTransliterator.java                                                       |  344 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandConst.java                                                                    |   95 
 app/src/main/res/drawable/ic_device_folder_disabled.xml                                                                                                   |   32 
 app/src/main/java/net/osmand/aidlapi/AidlParams.java                                                                                                      |   35 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/DashboardVO2MaxAnyWidget.java                                                 |   36 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/huaweiwatchgt/HuaweiWatchGTCoordinator.java                                         |   47 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/headphones/protocol/impl/v2/SonyProtocolImplV2.java                           | 1090 
 app/src/main/res/layout/activity_data_management.xml                                                                                                      |  252 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendMenstrualModifyTimeRequest.java                                |   51 
 app/src/main/res/xml/devicesettings_cycling_sensor.xml                                                                                                    |   21 
 src/mainline/fastlane/metadata/android/en-US/changelogs/100.txt                                                                                           |    6 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/MessageHandler.java                                                         |    7 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/http/ContactsHandler.java                                                   |  111 
 app/src/main/assets/ic_intensity_hollow.svg                                                                                                               |   59 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/AbstractMi1FirmwareInfo.java                                                |  126 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/hplus/EXRIZUK8Coordinator.java                                                             |   47 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/test/TestFeature.java                                                                      |   77 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/FindDeviceRequest.java                                              |   51 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetWatchfacesList.java                                             |   57 
 app/src/main/res/layout/activity_mi_band_pairing.xml                                                                                                      |   33 
 app/src/main/res/xml/devicesettings_autoremove_notifications.xml                                                                                          |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgts4/AmazfitGTS4Coordinator.java                                              |   91 
 app/src/main/res/drawable/ic_device_zetime_disabled.xml                                                                                                   |   51 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/gps/providers/PhoneLocationProvider.java                                            |   78 
 app/src/main/java/org/bouncycastle/shaded/crypto/StreamBlockCipher.java                                                                                   |   58 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/DataStructureFactory.java             |  168 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/V2NotificationStrategy.java                                                 |   83 
 app/src/main/res/drawable/ic_arrow_circle_right.xml                                                                                                       |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/defines/FontSize.java                         |   22 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/BondAction.java                                                               |   83 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerGBPebble.java                                              |   72 
 app/src/main/res/xml/devicesettings_vesc.xml                                                                                                              |   18 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendFitnessGoalRequest.java                                        |   68 
 src/mainline/fastlane/metadata/android/en-US/changelogs/143.txt                                                                                           |   10 
 src/mainline/fastlane/metadata/android/en-US/changelogs/186.txt                                                                                           |    8 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/ActivitySleep.java                                     |   28 
 app/src/main/res/drawable/ic_activity_yoga.xml                                                                                                            |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/notification/defines/VibrationKind.java                |   55 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/NotificationHRConfiguration.java                                                   |   60 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SetAutomaticSpoRequest.java                                        |   55 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btbr/actions/PlainAction.java                                                              |   39 
 app/src/main/res/drawable/ic_galaxy_buds_l.xml                                                                                                            |   25 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbip/AmazfitBipFWInstallHandler.java                                           |   49 
 app/src/main/res/drawable/ic_sony_wf_800n_right.xml                                                                                                       |   39 
 gradle/wrapper/gradle-wrapper.jar                                                                                                                         |    0 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/AppSpecificNotificationSettingsRepository.java                                            |   85 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/jyou/TeclastH30/TeclastH30Support.java                                             |  120 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/nothing/Ear2Coordinator.java                                                               |   43 
 app/src/main/res/xml/devicesettings_wena3.xml                                                                                                             |  222 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/alertnotification/Command.java                                               |   40 
 app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/WithingsTestStructure.java            |   30 
 app/src/main/java/lineageos/app/LineageContextConstants.java                                                                                              |   31 
 app/src/main/res/xml/devicesettings_offline_voice.xml                                                                                                     |   39 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/huaweiwatchgtrunner/HuaweiWatchGTRunnerCoordinator.java                             |   47 
 app/src/main/res/xml/devicesettings_disconnectnotification_noshed.xml                                                                                     |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/message/incoming/NotificationRequestHandler.java     |  101 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/time/BandTimeZone.java                                          |   77 
 app/src/main/res/xml/devicesettings_phone_silent_mode.xml                                                                                                 |   12 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiDeviceEvent.java                                                        |   38 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/WearingState.java                                                                            |   23 
 src/mainline/fastlane/metadata/android/en-US/changelogs/115.txt                                                                                           |    3 
 app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/DataStructureFactoryTest.java         |  133 
 app/src/main/res/xml/devicesettings_screen_brightness.xml                                                                                                 |    9 
 src/mainline/fastlane/metadata/android/en-US/changelogs/200.txt                                                                                           |    7 
 app/src/main/res/drawable/ic_tws_case.xml                                                                                                                 |   15 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/hplus/Q8Coordinator.java                                                                   |   51 
 app/src/main/res/layout/layout_widget_active_steps.xml                                                                                                    |   47 
 src/mainline/fastlane/metadata/android/en-US/images/phoneScreenshots/20-Activity.png                                                                      |    0 
 app/src/main/assets/buds_pro_case.svg                                                                                                                     |   51 
 app/src/main/res/drawable/ic_activity_tracks.xml                                                                                                          |    9 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/ImageEditActivity.java                                                             |  276 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/Huami2021ChunkedEncoder.java                                                 |  163 
 app/src/main/res/xml/devicesettings_root_connection.xml                                                                                                   |    9 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/alertnotification/SupportedNewAlertCategory.java                             |   74 
 app/src/main/assets/fossil_hr/widget_bg_thin_circle.png                                                                                                   |    0 
 app/src/main/res/xml/devicesettings_workout_send_gps_to_band.xml                                                                                          |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/WorkoutVo2MaxSampleProvider.java                                                           |  273 
 app/src/main/res/drawable/ic_temperature.xml                                                                                                              |   12 
 app/src/main/res/xml/devicesettings_miband6_new_protocol.xml                                                                                              |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/oppo/commands/TouchConfigValue.java                                                |   53 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/fenix/GarminFenix7Coordinator.java                                          |   18 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/oppo/commands/TouchConfigType.java                                                 |   48 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/galaxy_buds/GalaxyBudsSettingsCustomizer.java                                              |  282 
 app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/activity/XiaomiActivityFileIdTest.java                                      |   90 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/discovery/GBScanEvent.java                                                              |   53 
 src/mainline/fastlane/metadata/android/en-US/changelogs/183.txt                                                                                           |   20 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/no1f1/No1F1Constants.java                                                                  |   52 
 app/src/main/res/xml/devicesettings_header_system.xml                                                                                                     |    6 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/wena3/SonyWena3Coordinator.java                                                       |  297 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GBTextToSpeech.java                                                                           |   77 
 app/src/main/res/drawable/ic_device_miband2.xml                                                                                                           |   31 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/WeatherNotificationReceiver.java                                                    |   64 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/EventWithActivity.java                                 |   68 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/SetTripleTapEnabledRequest.java                            |   26 
 app/src/main/res/xml/devicesettings_amazfitvergel.xml                                                                                                     |   13 
 app/src/main/res/drawable/ic_health.xml                                                                                                                   |   10 
 app/src/main/res/xml/devicesettings_vibrations_enable.xml                                                                                                 |   11 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/test/samples/TestSpo2SampleProvider.java                                                   |   89 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/PebbleReceiver.java                                                                 |  100 
 app/src/main/res/layout/layout_widget_activities_count.xml                                                                                                |   47 
 app/src/main/res/xml/devicesettings_header_developer.xml                                                                                                  |    6 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/status/WeatherReport.java                              |   51 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/womp/WompSession.java                                                                              |  107 
 app/src/main/res/drawable/ic_battery_charging_20.xml                                                                                                      |   13 
 app/src/main/assets/ic_device_miband6.svg                                                                                                                 |  150 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/preferences/GBSimpleSummaryProvider.java                                                      |   40 
 src/mainline/fastlane/metadata/android/en-US/changelogs/120.txt                                                                                           |    5 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/webview/GBWebClient.java                                                    |  245 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/bandwpseries/BandWPSeriesDeviceCoordinator.java                                            |   63 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/util/ByteArrayWriter.java                                                |   77 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/coordinators/SonyWF1000XM4Coordinator.java                                 |   74 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/logic/ActivitySyncPacketProcessor.java                         |  115 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/healthThermometer/TemperatureInfo.java                                       |   84 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/miband9/MiBand9Coordinator.java                                                     |   54 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/AbstractDashboardWidget.java                                                  |  136 
 app/src/main/res/raw/ping_tone.rtttl                                                                                                                      |    1 
 app/src/test/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/TestDeviceConfig.java                                                       |  505 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GridAutoFitLayoutManager.java                                                                 |   77 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventFmFrequency.java                                                         |   25 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/SetFileFlagsMessage.java                                           |   41 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgtr/AmazfitGTRLiteFWHelper.java                                               |   40 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/CRC32C.java                                                                                   |  633 
 app/src/main/res/layout/item_with_details_small.xml                                                                                                       |   50 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/EphemerisFileUpload.java                                                    |  269 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/WeatherNotificationConfig.java                                                      |   31 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/FrenchTransliterator.java                                                       |   32 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/supercars/SuperCarsCoordinator.java                                                        |  157 
 app/src/main/res/layout/activity_configure_contacts.xml                                                                                                   |   27 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/AbstractMiBandFWHelper.java                                                         |  155 
 app/src/main/assets/devintro.png                                                                                                                          |    0 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/widgets/WidgetScreensListActivity.java                                                  |  183 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfittrex3/AmazfitTRex3Coordinator.java                                            |   92 
 app/src/main/assets/ic_device_sony_overhead.svg                                                                                                           |  140 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEvent.java                                                                    |   27 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/file/FileGetRawRequest.java                                |  151 
 app/src/main/res/layout/activity_summary_details.xml                                                                                                      |  111 
 app/src/main/res/drawable/ic_switch_left.xml                                                                                                              |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiUtil.java                                                                     |   59 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/HydrationReminderIntervalCommand.java                                       |   85 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/dialogs/MaterialEditTextPreferenceDialogFragment.java                                         |  119 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/operations/AbstractMiBand1Operation.java                                    |   33 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/makibeshr3/MakibesHR3SampleProvider.java                                                   |   78 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetWatchfacesNames.java                                            |   60 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/Version.java                                                                                  |  100 
 app/src/main/res/xml/devicesettings_workmode.xml                                                                                                          |   13 
 app/src/main/res/layout/activity_camera.xml                                                                                                               |   11 
 app/src/main/assets/fossil_hr/default_background.png                                                                                                      |    0 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/StepsFragmentAdapter.java                                                                  |   32 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/binary_sensor/protocol/message/GetSensorRequest.java                               |   32 
 .woodpecker/run_tests.yml                                                                                                                                 |   19 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiCrypto.java                                                                   |  273 
 app/src/main/res/xml/devicesettings_inactivity.xml                                                                                                        |   37 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/CryptoUtils.java                                                                              |  142 
 app/src/main/res/anim/slidefromtop.xml                                                                                                                    |   11 
 app/src/main/res/drawable/ic_device_folder.xml                                                                                                            |   23 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/operations/AbstractMiBandOperation.java                                     |   82 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/mijia_lywsd/MijiaXmwsdj04Coordinator.java                                                  |   48 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/Weather.java                                                                                 | 1213 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/roidmi/Roidmi1Protocol.java                                                        |  148 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/BcdUtil.java                                                                                  |   33 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/PermissionsUtils.java                                                                         |  328 
 app/src/main/res/drawable/ic_music_item.xml                                                                                                               |    5 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/redmiwatch3active/RedmiWatch3ActiveCoordinator.java                                 |   60 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerPebStyle.java                                              |  137 
 app/src/main/java/com/mobeta/android/dslv/ResourceDragSortCursorAdapter.java                                                                              |  136 
 app/src/main/assets/ic_galaxy_buds_r.svg                                                                                                                  |   82 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiTLV.java                                                                      |  452 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/Spo2Sample.java                                                                              |   56 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/UserInfo.java                                                                       |  119 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetMusicPlaylist.java                                              |   39 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/notification/NotificationProvider.java               |  184 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/alarm/BandAlarm.java                                            |   76 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/roidmi/Roidmi1Coordinator.java                                                             |   65 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventSleepStateDetection.java                                                 |   30 
 app/src/nightly/res/mipmap-xxhdpi/ic_launcher_round.png                                                                                                   |    0 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiWorkoutTrackActivityType.java                                           |   85 
 app/src/main/res/drawable/ic_dashboard.xml                                                                                                                |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/womp/WompLocation.java                                                                             |  145 
 app/src/main/res/drawable/ic_device_amazfit_bip_disabled.xml                                                                                              |   26 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/banglejs/BangleJSDeviceSupport.java                                                | 2156 
 app/src/main/res/xml/devicesettings_miband6.xml                                                                                                           |   33 
 .gitea/issue_template/bug_report.yml                                                                                                                      |   77 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetFitnessTotalsRequest.java                                       |   56 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/webview/JSInterface.java                                                    |  254 
 app/src/main/java/org/bouncycastle/shaded/crypto/modes/CTRModeCipher.java                                                                                 |   16 
 app/src/main/res/drawable/ic_device_watchxplus.xml                                                                                                        |   38 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/instinct/GarminInstinct2SolTacCoordinator.java                              |   18 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/AsynchronousResponse.java                                                   |  728 
 app/src/nightly/res/drawable-mdpi/ic_launcher.png                                                                                                         |    0 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendP2PCommand.java                                                |   59 
 app/src/main/res/drawable/ic_filter_1.xml                                                                                                                 |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/UserSecret.java                       |   39 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/GattDescriptor.java                                                                   |   41 
 app/src/main/assets/fossil_hr/widgetWeather.bin                                                                                                           |    0 
 src/mainline/fastlane/metadata/android/en-US/changelogs/169.txt                                                                                           |    6 
 app/src/main/res/drawable-xxhdpi/ic_notification_disconnected.png                                                                                         |    0 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetDndLiftWristTypeRequest.java                                    |   68 
 src/mainline/fastlane/metadata/android/en-US/changelogs/149.txt                                                                                           |    2 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/CallSpec.java                                                                                |   41 
 app/src/main/res/color/blacklist_checkboxes.xml                                                                                                           |    5 
 app/src/main/res/xml/devicesettings_casio_alert.xml                                                                                                       |   38 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btbr/actions/SetDeviceStateAction.java                                                     |   53 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/Accumulator.java                                                                              |   47 
 app/src/main/res/xml/devicesettings_hydration_reminder_dnd.xml                                                                                            |   41 
 app/src/test/java/nodomain/freeyourgadget/gadgetbridge/util/language/LanguageUtilsTest.java                                                               |  305 
 app/src/main/res/xml/fossil_hr_watchface_settings.xml                                                                                                     |  114 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/status/FileTransferDataStatusMessage.java                          |   80 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiService.java                                                                    |  260 
 src/mainline/fastlane/metadata/android/en-US/changelogs/111.txt                                                                                           |    3 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/HeartRateSample.java                                                                         |   24 
 app/src/main/res/xml/devicesettings_language_generic.xml                                                                                                  |   11 
 app/src/main/res/xml/devicesettings_heartrate_sleep_alert_activity_stress_spo2.xml                                                                        |  132 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiActivityDetailsParser.java                                              |  295 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/WeightSample.java                                                                            |   24 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/GetBatteryLevelRequest.java                                         |   53 
 app/src/main/assets/gps_edit.svg                                                                                                                          |   67 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/AbstractSerialDeviceSupport.java                                                    |  315 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/forerunner/GarminForerunner255SCoordinator.java                             |   18 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/ListFilesRequest.java                                      |   58 
 app/src/main/res/drawable/ic_activity_stairs.xml                                                                                                          |   14 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventDisplayMessage.java                                                      |   38 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbip3pro/AmazfitBip3ProFWInstallHandler.java                                   |   49 
 app/src/main/res/drawable/ic_watch_bw.xml                                                                                                                 |   10 
 app/src/main/assets/fossil_hr/widgetCustom_preview.png                                                                                                    |    0 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventAppManagement.java                                                       |   42 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiSpo2SampleProvider.java                                                         |   56 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/loyaltycards/LoyaltyCardsSettingsFragment.java                                          |  260 
 src/mainline/fastlane/metadata/android/en-US/changelogs/203.txt                                                                                           |    9 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/RequestMtuAction.java                                                         |   45 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/SleepAnalysis.java                                                               |  153 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/WorkoutScreenData.java                |   52 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitcor2/AmazfitCor2FWHelper.java                                                 |   40 
 app/src/main/res/drawable-mdpi/ic_watchface.png                                                                                                           |    0 
 app/src/main/assets/ic_device_galaxy_buds.svg                                                                                                             |  189 
 src/mainline/fastlane/metadata/android/en-US/changelogs/212.txt                                                                                           |    4 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/MusicControlMessage.java                                           |   53 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/BaseCommand.java                                                            |  235 
 app/src/main/res/drawable/ic_device_roidmi.xml                                                                                                            |   38 
 app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/ZeppOsSupportTest.java                                                |   71 
 app/src/main/res/xml/devicesettings_root_battery.xml                                                                                                      |    9 
 app/src/main/assets/ic_distance_total.svg                                                                                                                 |   80 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/backup/AbstractZipBackupJob.java                                                              |  100 
 app/src/nopebble/res/mipmap-mdpi/ic_launcher_round.png                                                                                                    |    0 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/vivoactive/GarminVivoActive4SCoordinator.java                               |   18 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/NotificationConfiguration.java                                                     |  131 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/capabilities/widgets/WidgetManager.java                                                            |   70 
 app/src/main/java/net/osmand/aidlapi/navigation/ANavigationUpdateParams.java                                                                              |   60 
 app/src/main/assets/ic_galaxy_buds_live.svg                                                                                                               |   72 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/NotificationManagementActivity.java                                                     |  149 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/file/FileDeleteRequest.java                                |   83 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbips/AmazfitBipSLiteCoordinator.java                                          |   53 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ChartsHost.java                                                                  |   51 
 app/src/main/java/lineageos/providers/WeatherContract.java                                                                                                |  245 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminStressSampleProvider.java                                                     |   56 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/redmibuds5pro/protocol/AuthData.java                                               |   25 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/NoNotificationStrategy.java                                                 |   47 
 app/src/main/java/net/osmand/aidlapi/navigation/ADirectionInfo.java                                                                                       |   73 
 app/src/main/res/xml/devicesettings_amazfitbipupro.xml                                                                                                    |   13 
 app/src/main/res/xml/devicesettings_banglejs_location.xml                                                                                                 |   30 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/buttonconfig/ConfigFileBuilder.java                                        |   98 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/venu/GarminVenu2Coordinator.java                                            |   18 
 src/mainline/fastlane/metadata/android/en-US/changelogs/174.txt                                                                                           |    6 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendWatchfaceOperation.java                                        |   46 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/ZeppOsFwInstallHandler.java                                           |  177 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/backup/JsonBackupPreferences.java                                                             |  203 
 app/src/nopebble/res/mipmap-xxxhdpi/ic_launcher_round.png                                                                                                 |    0 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/backup/ZipBackupImportJob.java                                                                |  243 
 app/src/main/assets/fossil_hr/icTrophy.rle                                                                                                                |    1 
 app/src/main/res/drawable-xxhdpi/ic_notification.png                                                                                                      |    0 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/redmibuds5pro/protocol/Opcode.java                                                 |   33 
 app/src/main/assets/fossil_hr/widgetHR_preview.png                                                                                                        |    0 
 app/src/main/res/xml/devicesettings_idasen.xml                                                                                                            |   27 
 app/src/main/res/xml/devicesettings_watchxplus.xml                                                                                                        |  112 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitGoals.java                                                  |   39 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/GenericWeatherReceiver.java                                                         |  216 
 src/mainline/fastlane/metadata/android/en-US/changelogs/104.txt                                                                                           |    1 
 app/src/main/res/mipmap-xhdpi/ic_launcher.png                                                                                                             |    0 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/cmfwatchpro/samples/CmfActivitySampleProvider.java                                         |  187 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/hama/fit6900/HamaFit6900Constants.java                                                     |   28 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/authentication/CheckDevicePairingRequest.java           |   60 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/AbstractBodyEnergySample.java                                                             |   36 
 app/src/main/res/drawable/ic_device_galaxy_buds.xml                                                                                                       |   40 
 app/src/main/res/xml/devicesettings_header_time.xml                                                                                                       |    6 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_84.java                                                         |   79 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/id115/AbstractID115Operation.java                                                  |   96 
 app/src/main/res/drawable-hdpi/ic_launcher.png                                                                                                            |    0 
 src/mainline/fastlane/metadata/android/en-US/title.txt                                                                                                    |    1 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casio/ecbs100/CasioECBS100DeviceSupport.java                                       |   63 
 app/src/main/res/layout/activity_appinstaller.xml                                                                                                         |  102 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/device_info/SupportedFileVersionsInfo.java                 |   52 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiBitmapUtils.java                                                      |  362 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SetAudioModeRequest.java                                           |   41 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/RangeMap.java                                                                                 |   87 
 app/src/main/res/xml/devicesettings_reject_call_method.xml                                                                                                |   12 
 CONTRIBUTORS.rst                                                                                                                                          |  553 
 app/src/main/res/mipmap-xhdpi/ic_launcher_round.png                                                                                                       |    0 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/hama/fit6900/HamaFit6900DeviceCoordinator.java                                             |  105 
 app/src/main/res/xml/devicesettings_sleep_time.xml                                                                                                        |   32 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/alertnotification/AlertNotificationControl.java                              |   54 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitgtr2/AmazfitGTR2Support.java                                          |   66 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/womp/WompWebActivity.java                                                                          |  152 
 app/src/main/res/layout/item_reminder.xml                                                                                                                 |   53 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfittrex/AmazfitTRexFirmwareInfo.java                                     |  116 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetPincodeRequest.java                                             |   56 
 app/src/main/res/xml/devicesettings_amazfitgts2mini.xml                                                                                                   |   13 
 GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java                                                                        | 1591 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/gpx/model/GpxTrack.java                                                                       |   80 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/moondrop/MoondropSpaceTravelDeviceSupport.java                                     |   43 
 app/src/nopebble/res/mipmap-xhdpi/ic_launcher.png                                                                                                         |    0 
 app/src/main/res/xml/devicesettings_huawei_debug.xml                                                                                                      |   83 
 app/src/main/res/xml/devicesettings_banglejs_apploader.xml                                                                                                |    9 
 app/src/main/assets/fossil_hr/widgetSteps.bin                                                                                                             |    0 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/AbstractGattListenerWriteAction.java                                          |   49 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/Vo2MaxSample.java                                                                            |   32 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/V1NotificationStrategy.java                                                 |  127 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/services/XiaomiDataUploadService.java                                       |  191 
 app/src/main/res/drawable/ic_bolt.xml                                                                                                                     |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitSleepDataInfo.java                                          |   52 
 app/src/main/res/xml/devicesettings_heartrate_automatic_enable.xml                                                                                        |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitcor/AmazfitCorSupport.java                                            |   63 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/export/GPXExporter.java                                                                            |  246 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hplus/HPlusSupport.java                                                            |  929 
 app/src/main/res/xml/devicesettings_sony_headphones_adaptive_volume_control.xml                                                                           |   15 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/alarm/AlarmsGetRequest.java                                |   56 
 app/src/main/res/layout/activity_gpx_receiver.xml                                                                                                         |   69 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgtr2/AmazfitGTR2Coordinator.java                                              |  153 
 src/mainline/fastlane/metadata/android/en-US/changelogs/221.txt                                                                                           |   23 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/marstek/SolarEquipmentStatusActivity.java                                                  |  190 
 app/src/main/res/layout/layout_widget_total_steps.xml                                                                                                     |   47 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/appmanager/AbstractAppManagerFragment.java                                              |  787 
 src/mainline/fastlane/metadata/android/en-US/images/phoneScreenshots/51-StepsPerWeek.png                                                                  |    0 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendWeatherSunMoonSupportRequest.java                              |   58 
 src/mainline/fastlane/metadata/android/en-US/changelogs/124.txt                                                                                           |    6 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitvergel/AmazfitVergeLSupport.java                                      |   68 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebbleIconID.java                                                                   |  125 
 src/mainline/fastlane/metadata/android/en-US/changelogs/108.txt                                                                                           |    6 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/test/samples/TestHrvSummarySampleProvider.java                                             |  115 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/AncsStatus.java                       |   45 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btbr/BtBRAction.java                                                                       |   64 
 app/src/main/res/xml/devicesettings_swipeunlock.xml                                                                                                       |   10 
 app/src/main/res/drawable-mdpi/ic_systemapp.png                                                                                                           |    0 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/notification/defines/VibrationCount.java               |   49 
 app/src/main/java/org/bouncycastle/shaded/util/Pack.java                                                                                                  |   40 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/status/GFDIStatusMessage.java                                      |   58 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerTrekVolle.java                                             |  112 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/ArabicTransliterator.java                                                       |   37 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/CannedMessagesSpec.java                                                                      |   26 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/opentracks/TrackStatistics.java                                                     |  130 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/headphones/protocol/impl/v1/params/AudioCodec.java                            |   46 
 app/src/main/res/layout/activity_qhybrid_image_edit.xml                                                                                                   |   35 
 app/src/main/res/drawable/ic_device_nothingear_disabled.xml                                                                                               |   34 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/FindPhoneCommand.java                                                       |   33 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/DefaultChartsData.java                                                           |   38 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/FitnessData.java                                                            |  850 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/translation/TranslationsPutRequest.java                 |   57 
 app/src/main/res/layout/fragment_weeksleep_chart.xml                                                                                                      |  190 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/services/XiaomiWatchfaceService.java                                        |  279 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/SetDeviceStateRequest.java                                 |   41 
 app/src/main/res/values/attrs.xml                                                                                                                         |   25 
 app/src/main/java/com/mobeta/android/dslv/DragSortController.java                                                                                         |  474 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiAppManager.java                                                       |  118 
 app/src/main/res/xml/devicesettings_liftwrist_display.xml                                                                                                 |   31 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionHealthSleep.java                                              |  130 
 app/src/main/res/xml/devicesettings_fossilhybridhr_pre_fw220.xml                                                                                          |   28 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/HeartRateDailyFragment.java                                                      |  322 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/capabilities/widgets/WidgetLayout.java                                                             |   59 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/Measurement.java                                                                             |   49 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/NotifyAction.java                                                             |  136 
 app/src/main/res/drawable/ic_activity_pingpong.xml                                                                                                        |   90 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiInstallHandler.java                                                           |  272 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/alexa/AlexaMessageSetRequest.java                       |   46 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/calendar/CalendarManager.java                                                                 |  326 
 app/src/main/res/xml/devicesettings_pebble_calendar.xml                                                                                                   |   16 
 src/mainline/fastlane/metadata/android/en-US/changelogs/133.txt                                                                                           |    5 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miscale/WeightMeasurement.java                                                     |   87 
 app/src/main/res/drawable/ic_phone.xml                                                                                                                    |    5 
 app/src/main/proto/huami.proto                                                                                                                            |  111 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/baseTypes/BaseTypeInt.java                                              |   57 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceService.java                                                                          |  588 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/XDatePreferenceFragment.java                                                                  |   80 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitgtr2/AmazfitGTR2eSupport.java                                         |   33 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/watch9/Watch9PairingActivity.java                                                          |  166 
 app/src/main/res/xml/devicesettings_femometer.xml                                                                                                         |   11 
 src/mainline/fastlane/metadata/android/en-US/changelogs/178.txt                                                                                           |    8 
 app/src/main/res/values/rc_attrs.xml                                                                                                                      |   11 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/services/AbstractXiaomiService.java                                         |   72 
 src/banglejs/fastlane/metadata/android/cz-CZ/full_description.txt                                                                                         |    6 
 app/src/main/res/xml/devicesettings_rotatewrist_cycleinfo.xml                                                                                             |    9 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/notification/NotificationArrival.java                  |  106 
 app/src/main/res/xml/devicesettings_moondrop_space_travel_touch.xml                                                                                       |  115 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lenovo/watchxplus/WatchXPlusDeviceSupport.java                                     | 2135 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/GBDeviceIoThread.java                                                               |   53 
 app/src/main/res/drawable/ic_speaker.xml                                                                                                                  |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/ReadAction.java                                                               |   49 
 app/src/main/res/values-ar/strings.xml                                                                                                                    | 3548 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/status/AuthNegotiationStatusMessage.java                           |   83 
 app/src/main/res/layout/activity_notification_per_app_setting_detail.xml                                                                                  |   86 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miscale/MiSmartScaleCoordinator.java                                                       |  131 
 app/src/main/assets/devintro.xmi                                                                                                                          |  175 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AndroidPairingActivity.java                                                             |   30 
 CHANGELOG.md                                                                                                                                              | 2480 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/status/FitDataStatusMessage.java                                   |   51 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/AcceptAgreementsRequest.java                                       |   53 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/http/DataTransferHandler.java                                               |  178 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/binary_sensor/protocol/constants/ParameterId.java                                  |   46 
 app/src/main/res/xml/devicesettings_settings_third_party_apps.xml                                                                                         |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/test/samples/TestRespiratoryRateSampleProvider.java                                        |   71 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/coordinators/SonyWH1000XM5Coordinator.java                                 |   59 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/AlarmStatus.java                      |   51 
 app/src/main/res/values-lt/strings.xml                                                                                                                    |  562 
 app/src/main/res/drawable/ic_device_h30_h10.xml                                                                                                           |   32 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/AbstractHuamiFirmwareInfo.java                                               |  127 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/baseTypes/BaseTypeInterface.java                                        |   13 
 app/src/main/res/layout/activity_debug.xml                                                                                                                |  332 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/NotificationControlMessage.java                                    |  150 
 app/src/main/res/xml/devicesettings_camera_remote.xml                                                                                                     |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/PebbleUtils.java                                                                              |  187 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/UserUnit.java                         |   52 
 app/src/main/java/com/mobeta/android/dslv/DragSortListPreferenceFragment.java                                                                             |  147 
 src/mainline/fastlane/metadata/android/en-US/images/phoneScreenshots/72-SportActivitiesSummary.png                                                        |    0 
 app/src/main/res/xml/devicesettings_amazfitneo_sound.xml                                                                                                  |   13 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractTimeSampleProvider.java                                                            |  199 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitgtr/AmazfitGTRLiteFirmwareInfo.java                                   |   85 
 app/src/test/java/nodomain/freeyourgadget/gadgetbridge/test/GBZipFileTest.java                                                                            |  185 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitbips/AmazfitBipSLiteSupport.java                                      |   38 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfittrex/AmazfitTRexCoordinator.java                                              |  143 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/oppo/OppoHeadphonesPreferences.java                                                        |   33 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsMorningUpdatesService.java                             |  231 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/ActivityTarget.java                   |   51 
 app/src/main/res/xml/devicesettings_oppo_headphones_touch_options.xml                                                                                     |   78 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/Widget.java                                                                                        |  258 
 src/mainline/fastlane/metadata/android/en-US/images/featureGraphic.jpg                                                                                    |    0 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/ActivitySampleRun.java                |   36 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/UploadRequestMessage.java                                          |   45 
 app/src/main/res/xml/devicesettings_root_authentication.xml                                                                                               |    9 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgtrmini/AmazfitGTRMiniCoordinator.java                                        |   87 
 app/src/main/res/drawable/ic_device_smartring.xml                                                                                                         |   22 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/AbstractBTLEOperation.java                                                            |  218 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/PredefinedLocalMessage.java                                             |   58 
 app/src/main/java/com/mobeta/android/dslv/SimpleFloatViewManager.java                                                                                     |   85 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/divoom/PixooCoordinator.java                                                               |  109 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ActivityListingAdapter.java                                                      |  286 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendFileUploadAck.java                                             |   46 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendGpsStatusRequest.java                                          |   42 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiWatchfaceManager.java                                                 |  304 
 app/src/test/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/TestWorkMode.java                                                           |   94 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/colmi/samples/ColmiStressSampleProvider.java                                               |   56 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/coordinators/SonyWIC100Coordinator.java                                    |   59 
 app/src/main/java/com/mobeta/android/dslv/DragSortListView.java                                                                                           | 2999 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/StartPpgRequest.java                                                |   62 
 app/src/nopebble/res/mipmap-hdpi/ic_launcher_foreground.png                                                                                               |    0 
 src/mainline/fastlane/metadata/android/en-US/changelogs/207.txt                                                                                           |   11 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/roidmi/RoidmiCoordinator.java                                                              |  147 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/redmiwatch5lite/RedmiWatch5LiteCoordinator.java                                     |   50 
 app/src/main/res/xml/devicesettings_amazfitbip.xml                                                                                                        |   23 
 app/src/main/assets/fossil_hr/icWthClearDay.rle                                                                                                           |    7 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/fitpro/FitProDeviceCoordinator.java                                                        |  180 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/status/MusicInfo.java                                  |   44 
 app/src/main/res/drawable/ic_device_miwatch.xml                                                                                                           |   52 
 app/src/main/res/drawable/ic_distance.xml                                                                                                                 |   20 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/OTAEnterRequest.java                                       |   36 
 app/src/main/res/values-th/strings.xml                                                                                                                    |  138 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiHeartRateManualSampleProvider.java                                              |   56 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbip5unity/AmazfitBip5UnityCoordinator.java                                    |   83 
 app/src/main/res/drawable/ic_activity_sup.xml                                                                                                             |   26 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/BatteryValues.java                    |   73 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/RespiratoryRateDailyFragment.java                                                |  208 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/CMWeatherReceiver.java                                                              |  224 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiSettingsCustomizer.java                                                         |  135 
 src/mainline/fastlane/metadata/android/en-US/changelogs/235.txt                                                                                           |   57 
 app/proguard-rules.pro                                                                                                                                    |   79 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/FetchSpo2SleepOperation.java                                |   96 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendSetContactsRequest.java                                        |   49 
 app/src/main/res/drawable/ic_date_range.xml                                                                                                               |    3 
 app/src/main/assets/calendar_sync.svg                                                                                                                     |   62 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/zeppos/ZeppOsGpxRouteInstallHandler.java                                             |  135 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/cycling_sensor/db/CyclingSampleProvider.java                                               |   41 
 app/src/main/res/values/styles.xml                                                                                                                        |  224 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/zeppos/ZeppOsActivitySummaryParser.java                                              |  212 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/headphones/protocol/Request.java                                              |   58 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsMusicService.java                                      |  137 
 app/src/main/java/lineageos/weatherservice/WeatherProviderService.java                                                                                    |  181 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/cycling_sensor/coordinator/CyclingSensorCoordinator.java                                   |  121 
 app/src/main/res/drawable/ic_activitytracker.xml                                                                                                          |   34 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/AbstractMiBandFWInstallHandler.java                                                 |  158 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/venu/GarminVenu3SCoordinator.java                                           |   18 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/GetBatteryLevelCommand.java                                                 |   41 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/cmfwatchpro/CmfNotificationIcon.java                                               |   75 
 app/src/main/res/xml/devicesettings_power_saving.xml                                                                                                      |    9 
 app/src/main/res/xml/devicesettings_password.xml                                                                                                          |   25 
 src/mainline/fastlane/metadata/android/en-US/changelogs/165.txt                                                                                           |    5 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/LimitedQueue.java                                                                             |   66 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SetWearLocationRequest.java                                        |   61 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiFreebudsCoordinator.java                                                      |   94 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/deviceevents/NotificationSubscriptionDeviceEvent.java                       |    9 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/UserUnitConstants.java                |   29 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/CommuteActionsActivity.java                                                        |  213 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/womp/WompApi.java                                                                                  |   85 
 app/src/main/assets/galaxy_buds.svg                                                                                                                       |   86 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/banglejs/BangleJSActivityTrack.java                                                |  437 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitbip3pro/AmazfitBip3ProSupport.java                                    |   86 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/headphones/SonyHeadphonesIoThread.java                                        |  157 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/binary_sensor/coordinator/BinarySensorCoordinator.java                                     |  188 
 app/src/main/res/xml/devicesettings_huami2021_alexa.xml                                                                                                   |   83 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/OTAEraseRequest.java                                       |   61 
 app/src/main/res/drawable-xxhdpi/ic_systemapp.png                                                                                                         |    0 
 app/src/main/res/drawable/ic_activity_pilates.xml                                                                                                         |   14 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/adapter/fossil/FossilWatchAdapter.java                                     |  911 
 app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/enums/GarminSportTest.java                                              |   31 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/BatteryConfig.java                                                                           |   89 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbips/AmazfitBipSLiteFWInstallHandler.java                                     |   49 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DateTimeUtils.java                                                                            |  322 
 src/mainline/fastlane/metadata/android/en-US/changelogs/192.txt                                                                                           |    7 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivitySession.java                                                                         |  127 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xwatch/XWatchSampleProvider.java                                                           |   80 
 app/src/main/res/layout/activity_configure_alarms.xml                                                                                                     |   17 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btbr/AbstractBTBRDeviceSupport.java                                                        |  154 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/CalendarNotificationEnableSetting.java        |   38 
 app/src/main/assets/fossil_hr/icWthWindy.rle                                                                                                              |    2 
 app/src/main/res/xml/devicesettings_trusleep.xml                                                                                                          |   26 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/NotificationUpdateMessage.java                                     |  140 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/receivers/GBAutoFetchReceiver.java                                                         |   56 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/liveview/LiveviewSupport.java                                                      |   67 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/womp/WompConfig.java                                                                               |   19 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/StepsDailyFragment.java                                                          |  243 
 app/src/main/res/drawable/ic_intensity.xml                                                                                                                |    3 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/quickreply/QuickReplyConfigurationPutRequest.java       |   72 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgts2/AmazfitGTS2FWInstallHandler.java                                         |   49 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/microapp/MicroAppCommand.java                              |  176 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/CommonSymbolsTransliterator.java                                                |   35 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitMonitoring.java                                             |  121 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/UIHHContainer.java                                                           |  234 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/capabilities/loyaltycards/LoyaltyCard.java                                                         |  153 
 app/src/main/res/layout/activity_device_settings.xml                                                                                                      |   11 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivityListItem.java                                                                        |  171 
 app/src/main/res/xml/devicesettings_autolight.xml                                                                                                         |    9 
 app/src/main/res/xml/devicesettings_fossilqhybrid_legacy.xml                                                                                              |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/DeviceInformationMessage.java                                      |  113 
 app/src/main/assets/fossil_hr/widgetChanceOfRain_preview.png                                                                                              |    0 
 app/src/main/res/drawable/ic_device_zetime.xml                                                                                                            |   50 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/SleepAlarmWidget.java                                                                              |  159 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/BluetoothStateChangeReceiver.java                                                   |   70 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendDndDeleteRequest.java                                          |   55 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetWatchfaceParams.java                                            |   57 
 app/src/main/res/xml/devicesettings_colmi_r0x.xml                                                                                                         |   32 
 app/src/main/res/menu/activity_hybridhr_watchface_designer_actionbar.xml                                                                                  |   17 
 app/src/main/res/layout/fragment_dashboard.xml                                                                                                            |   54 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitMonitoringInfo.java                                         |   67 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/miband5/MiBand5FirmwareInfo.java                                             |   97 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/SleepCollectionFragment.java                                                     |   44 
 app/src/main/res/xml/devicesettings_sony_headphones_ambient_sound_control_wind_noise_reduction.xml                                                        |   32 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/capabilities/widgets/WidgetScreen.java                                                             |   65 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/AbortTransactionAction.java                                                   |   49 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/FetchStressManualOperation.java                             |  123 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/button/ButtonConfigurationGetRequest.java                  |   63 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ConfigureContacts.java                                                                  |  173 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/fenix/GarminFenix6SSapphireCoordinator.java                                 |   18 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/JavaExtensions.java                                                                           |   31 
 app/src/main/res/xml/devicesettings_sony_headphones_wide_area_tap.xml                                                                                     |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/SleepLevel.java                                        |   37 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceService.java                                                                           |  196 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/AlarmCommand.java                                                           |  173 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/RemoteCameraTriggeredCommand.java                                           |   34 
 app/src/main/assets/ic_device_galaxy_buds_pro_disabled.svg                                                                                                |  200 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetFileParametersRequest.java                                      |   63 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/hplus/HPlusCoordinator.java                                                                |  273 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/swim/GarminSwim2Coordinator.java                                            |   18 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendEphemerisFileStatusRequest.java                                |   38 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/instinct/GarminInstinctSolarCoordinator.java                                |   24 
 app/src/nightly/res/drawable-xxhdpi/ic_launcher.png                                                                                                       |    0 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/capabilities/HeartRateCapability.java                                                              |  163 
 app/src/main/res/drawable/ic_activity_dumbbell.xml                                                                                                        |   33 
 app/src/main/res/layout/activity_contact_details.xml                                                                                                      |   96 
 src/mainline/fastlane/metadata/android/en-US/images/phoneScreenshots/11-Devices.png                                                                       |    0 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/SetLanguageCommand.java                                                     |   52 
 app/src/nopebble/res/mipmap-xxxhdpi/ic_launcher.png                                                                                                       |    0 
 app/src/main/res/layout/spinner_with_image_layout.xml                                                                                                     |   21 
 app/src/main/res/layout/steps_streak_days.xml                                                                                                             |   40 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/binary_sensor/protocol/constants/SensorType.java                                   |   36 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/unknown/UnknownDeviceSupport.java                                                  |   35 
 app/src/main/res/xml/devicesettings_soundcore_headphones.xml                                                                                              |   77 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsWifiService.java                                       |  174 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/export/ActivityTrackExporter.java                                                                  |   29 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/headphones/protocol/impl/AbstractSonyProtocolImpl.java                        |  145 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/bandwpseries/BandWMessageType.java                                                 |   27 
 app/src/main/res/xml/devicesettings_notification_wake_on_open.xml                                                                                         |   10 
 app/src/test/java/nodomain/freeyourgadget/gadgetbridge/devices/banglejs/BangleJSWorkoutParserTest.java                                                    |   23 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_89.java                                                         |   93 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/banglejs/BangleJSCoordinator.java                                                          |  287 
 app/src/main/res/layout/fragment_weeksteps_chart.xml                                                                                                      |   44 
 src/mainline/fastlane/metadata/android/en-US/images/phoneScreenshots/60-Stress.png                                                                        |    0 
 app/src/main/res/drawable/arrow_autofit_height.xml                                                                                                        |   41 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/EraseFileRequest.java                                      |   59 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/AbstractItemAdapter.java                                                                   |  135 
 app/src/main/res/layout/fragment_calories.xml                                                                                                             |  102 
 app/src/main/res/menu/activity_list_context_menu.xml                                                                                                      |   28 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BLEScanService.java                                                                   |  430 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/colmi/samples/ColmiActivitySampleProvider.java                                             |  191 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiSettingsCustomizer.java                                                       |  164 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/device_info/DeviceInfo.java                                |   21 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pinetime/weather/WeatherData.java                                                          | 1059 
 app/src/main/res/xml/devicesettings_donotdisturb_allday_liftwirst_notwear.xml                                                                             |   78 
 app/src/main/assets/ic_shoe.svg                                                                                                                           |   20 
 app/src/main/res/drawable/ic_activity_stretching.xml                                                                                                      |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/AlarmClockReceiver.java                                                             |   94 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/womp/WompWorkbenchFragment.java                                                                    |  189 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/FetchSportsSummaryOperation.java                            |  119 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/RequestMtuRequest.java                                     |   48 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/gps/GBLocationListener.java                                                         |   90 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/watchs1/XiaomiWatchS1Coordinator.java                                               |   61 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/SleepAsAndroidPreferencesActivity.java                                                  |  138 
 app/src/main/res/layout/activity_qhybrid_settings.xml                                                                                                     |  132 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerMarioTime.java                                             |   71 
 app/src/main/res/xml/devicesettings_screen_brightness_withauto.xml                                                                                        |   24 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/Wena3Packetable.java                                           |   21 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/AbstractHrvSummarySample.java                                                             |   50 
 src/mainline/fastlane/metadata/android/en-US/changelogs/225.txt                                                                                           |   56 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/AbstractMiBandSampleProvider.java                                                   |   73 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ConfigureAlarms.java                                                                    |  194 
 app/src/main/res/layout/fragment_statschart.xml                                                                                                           |   39 
 app/src/main/res/xml/devicesettings_hydration_reminder.xml                                                                                                |   21 
 src/mainline/fastlane/metadata/android/en-US/changelogs/97.txt                                                                                            |    6 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/ScandinavianTransliterator.java                                                 |   31 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminRestingMetabolicRateSampleProvider.java                                       |   56 
 app/src/main/res/values-gl/strings.xml                                                                                                                    |  533 
 LICENSE.artwork                                                                                                                                           |   36 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/fieldDefinitions/FieldDefinitionSleepStage.java                         |   58 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/cmfwatchpro/CmfWatchProSupport.java                                                |  854 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/SupportedFileTypesMessage.java                                     |   16 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/activity/impl/SleepStagesParser.java                                        |  176 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/idasen/IdasenCoordinator.java                                                              |   70 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pinetime/PineTimeJFConstants.java                                                          |   66 
 app/src/main/res/layout/fragment_stresschart.xml                                                                                                          |  148 
 app/src/main/res/drawable/ic_device_scannable.xml                                                                                                         |   28 
 app/src/main/res/values-it/strings.xml                                                                                                                    | 3674 
 app/src/main/res/layout/activity_vesc_control.xml                                                                                                         |   71 
 app/src/main/res/drawable/ic_device_miband_disabled.xml                                                                                                   |   28 
 app/src/main/res/drawable/ic_shoe.xml                                                                                                                     |   11 
 gradlew.bat                                                                                                                                               |   94 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/defines/DeviceButtonActionId.java             |   50 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/femometer/FemometerVinca2DeviceSupport.java                                        |  309 
 src/mainline/fastlane/metadata/android/en-US/changelogs/127.txt                                                                                           |    1 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/womp/WompScanActivity.java                                                                         |  149 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/SettingsCommand.java                                                        |  116 
 src/mainline/fastlane/metadata/android/en-US/changelogs/161.txt                                                                                           |    3 
 app/src/main/res/menu/activity_list_menu.xml                                                                                                              |   28 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/status/FitDefinitionStatusMessage.java                             |   51 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiSpo2SampleProvider.java                                                       |   66 
 app/src/main/res/drawable/ic_activity_unknown.xml                                                                                                         |   10 
 app/src/main/res/layout/activity_musicmanager.xml                                                                                                         |  151 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/ActivityHeartRate.java                                 |   29 
 app/src/main/res/drawable/ic_device_set_reminders.xml                                                                                                     |    9 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/TemperatureChartFragment.java                                                    |  245 
 app/src/main/res/values-ja/strings.xml                                                                                                                    | 2707 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetPhoneInfoRequest.java                                           |   55 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/message/incoming/LiveHeartrateHandler.java           |   90 
 app/src/test/java/nodomain/freeyourgadget/gadgetbridge/test/RtlUtilsTest.java                                                                             |  369 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_23.java                                                         |   40 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/GpsAndTime.java                                                             |  238 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminRespiratoryRateSampleProvider.java                                            |   56 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitSession.java                                                |  212 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/huaweiwatchgt2/HuaweiWatchGT2Coordinator.java                                       |   41 
 app/src/test/java/nodomain/freeyourgadget/gadgetbridge/model/ActivityKindTest.java                                                                        |   20 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/miband2/Mi2NotificationStrategy.java                                         |   78 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/amazfitbip/BipActivitySummary.java                                                         |  219 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/Challenge.java                        |   80 
 app/src/main/res/xml/devicesettings_reconnect_ble.xml                                                                                                     |   13 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/colmi/samples/ColmiHeartRateSampleProvider.java                                            |   56 
 app/src/main/res/layout/activity_file_manager.xml                                                                                                         |   25 
 app/src/main/res/xml/devicesettings_root_developer.xml                                                                                                    |    9 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/adapter/WatchAdapterFactory.java                                           |   41 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/cmfwatchpro/CmfInstallHandler.java                                                 |   90 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/menu/SetCommuteMenuMessage.java                         |   47 
 app/src/main/assets/ic_device_galaxy_buds_pro.svg                                                                                                         |  147 
 app/src/main/res/xml/devicesettings_workout_start_on_phone.xml                                                                                            |   10 
 app/src/main/AndroidManifest.xml                                                                                                                          |  947 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/file/AssetFile.java                                     |   58 
 app/src/main/res/drawable/ic_watch.xml                                                                                                                    |   10 
 app/src/main/res/xml/devicesettings_vibrationpatterns.xml                                                                                                 |  323 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband3/MiBand3Service.java                                                          |   28 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/welcome/WelcomeFragmentDocsSource.java                                                  |   42 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/SetDeviceBusyAction.java                                                      |   53 
 src/mainline/fastlane/metadata/android/en-US/changelogs/151.txt                                                                                           |    4 
 app/src/main/res/drawable/ic_shoe_print.xml                                                                                                               |   10 
 app/src/main/res/layout/activity_watch9_calibration.xml                                                                                                   |  124 
 app/src/test/java/nodomain/freeyourgadget/gadgetbridge/database/SampleProviderTest.java                                                                   |  216 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/GoalTrackingGetRequest.java                                |   44 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/workouts/entries/ActivitySummaryTableRowEntry.java                                      |   72 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/message/MessageBuilder.java                          |   89 
 app/src/main/res/drawable/baseline_bloodtype_24.xml                                                                                                       |    9 
 app/src/main/res/layout/fragment_welcome_permission_row.xml                                                                                               |   48 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/conversation/Conversation.java                       |   28 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/encoder/RLEEncoder.java                                                    |   50 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/FetchActivityOperation.java                                 |  194 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/BengaliTransliterator.java                                                      |  322 
 app/src/main/res/drawable/ic_monochrome_photos.xml                                                                                                        |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitUserProfile.java                                            |  139 
 app/src/main/res/xml/devicesettings_dateformat_2.xml                                                                                                      |   11 
 src/mainline/fastlane/metadata/android/en-US/changelogs/118.txt                                                                                           |    2 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/binary_sensor/protocol/parameter/Parameter.java                                    |   65 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/dialogs/MaterialPreferenceDialogFragment.java                                                 |  269 
 app/src/main/res/xml/devicesettings_autoremove_message.xml                                                                                                |    9 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/microapp/PlayCrazyShitRequest.java                         |   76 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/CommuteActionsListAdapter.java                                                     |  111 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitSet.java                                                    |   62 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDevice.java                                                                                 |  754 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_24.java                                                         |   35 
 app/src/main/res/xml/devicesettings_root_health.xml                                                                                                       |    9 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbip/AmazfitBipLiteFWHelper.java                                               |   43 
 app/src/main/res/layout/fragment_welcome_overview.xml                                                                                                     |   64 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btbr/AbstractTransaction.java                                                              |   46 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitpop/AmazfitPopFirmwareInfo.java                                       |   81 
 app/src/main/assets/shortcut_black.svg                                                                                                                    |    1 
 app/src/main/res/drawable/ic_sony_wf_800n_left.xml                                                                                                        |   39 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/preferences/DevicePrefs.java                                                                  |  118 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SetAutomaticHeartrateRequest.java                                  |   55 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/galaxy_buds/GalaxyBudsDeviceSupport.java                                           |   58 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerSimplyLight.java                                           |  139 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/opentracks/OpenTracksController.java                                                |  154 
 app/src/main/res/drawable/ic_activity_not_measured.xml                                                                                                    |   10 
 app/src/main/res/drawable/ic_screen_rotation.xml                                                                                                          |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiFreebudsSupport.java                                                  |  135 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendMusicOperation.java                                            |   51 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/PutSettingsFileRequest.java                                |   26 
 app/src/main/res/drawable/ic_battery_80.xml                                                                                                               |   13 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/redmismartbandpro/RedmiSmartBandProCoordinator.java                                 |   68 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/activity/ActivityEntry.java                                        |  101 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/colmi/ColmiR10Coordinator.java                                                             |   38 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivityKind.java                                                                            |  360 
 app/src/main/res/xml/devicesettings_miband6_new_auth_protocol_explanation.xml                                                                             |   15 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitpoppro/AmazfitPopProFirmwareInfo.java                                 |   81 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/communicator/ICommunicator.java                                             |   26 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiFirmwareInfo.java                                                       |  194 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/alertnotification/AlertStatus.java                                           |   37 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/alarm/Alarm.java                                           |  121 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfittrexpro/AmazfitTRexProFWHelper.java                                           |   40 
 app/src/main/res/drawable/ic_shoe_prints_many.xml                                                                                                         |    4 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/bandwpseries/BandWPSeriesResponse.java                                             |   84 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lenovo/watchxplus/WatchXPlusConstants.java                                                 |  103 
 app/src/main/res/drawable/ic_action_manage_apps.xml                                                                                                       |   10 
 src/mainline/fastlane/metadata/android/en-US/changelogs/231.txt                                                                                           |   28 
 app/src/main/java/org/bouncycastle/shaded/crypto/params/ParametersWithIV.java                                                                             |   39 
 app/src/main/res/values-nb-rNO/strings.xml                                                                                                                | 1701 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DeviceHelper.java                                                                             |  225 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/EmojiConverter.java                                                                           |  131 
 app/src/main/res/xml/devicesettings_device_actions_without_not_wear.xml                                                                                   |   47 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/actions/StopNotificationAction.java                                          |   44 
 app/src/main/project.properties                                                                                                                           |   15 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitpop/AmazfitPopCoordinator.java                                                |   64 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/idasen/IdasenSettingsCustomizer.java                                                       |   83 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgts2/AmazfitGTS2FWHelper.java                                                 |   40 
 app/src/main/res/drawable-xxhdpi/ic_launcher.png                                                                                                          |    0 
 gradlew                                                                                                                                                   |  252 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfittrex/AmazfitTRexFWHelper.java                                                 |   40 
 app/src/main/proto/garmin/gdi_http_service.proto                                                                                                          |   53 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgts2/AmazfitGTS2eCoordinator.java                                             |  158 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/scannable/ScannableDeviceCoordinator.java                                                  |   68 
 app/src/main/res/drawable/ic_signal_cellular_connected_no_internet_4_bar.xml                                                                              |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/notifications/GoogleMapsNotificationHandler.java                                    | 1301 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/DashboardVO2MaxCyclingWidget.java                                             |   36 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/AbstractCollectionFragment.java                                                  |  120 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/loyaltycards/LoyaltyCardsSettingsConst.java                                             |   37 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/init/InitOperation2021.java                                       |  209 
 app/src/main/res/drawable/ic_edit.xml                                                                                                                     |   10 
 app/src/main/res/menu/appmanager_context.xml                                                                                                              |   54 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/p2p/HuaweiP2PDataDictionarySyncService.java                                 |  312 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/PeriodicExporter.java                                                                     |  149 
 app/src/main/res/xml/devicesettings_huami2021_watchface.xml                                                                                               |   11 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/operations/FetchActivityOperation.java                                      |  460 
 app/src/main/res/drawable/ic_activity_walking.xml                                                                                                         |   10 
 app/src/test/java/nodomain/freeyourgadget/gadgetbridge/devices/banglejs/BangleJSActivityPointTest.java                                                    |  143 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/FetchTemperatureOperation.java                              |   70 
 app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png                                                                                                     |    0 
 app/src/test/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/TestAlarms.java                                                             |  167 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsAlexaService.java                                      |  391 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/theme/SelectedThemePutRequest.java                      |   48 
 app/src/main/java/net/osmand/aidlapi/navigation/ANavigationVoiceRouterMessageParams.java                                                                  |   60 
 app/src/main/res/layout/widget_alarms_activity_list.xml                                                                                                   |  103 
 app/src/main/res/drawable/ic_rotate_left.xml                                                                                                              |   10 
 app/src/main/res/layout/activity_womp_web.xml                                                                                                             |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/idasen/IdasenConstants.java                                                                |   30 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitTimeInZone.java                                             |   77 
 app/src/main/res/drawable/ic_refresh.xml                                                                                                                  |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/image/ImagesSetRequest.java                             |   51 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiRunPaceConfig.java                                                            |   62 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetSettingRelatedRequest.java                                      |   61 
 src/mainline/fastlane/metadata/android/en-US/changelogs/196.txt                                                                                           |    6 
 app/src/main/res/layout/qhybrid_package_settings_item.xml                                                                                                 |   29 
 app/src/nightly/res/mipmap-xxxhdpi/ic_launcher_foreground.png                                                                                             |    0 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/AbstractWeightSample.java                                                                 |   35 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/AbstractChartFragment.java                                                       |  403 
 app/src/main/java/org/bouncycastle/shaded/crypto/RuntimeCryptoException.java                                                                              |   26 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/capabilities/widgets/WidgetPart.java                                                               |   97 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbipu/AmazfitBipUFWHelper.java                                                 |   40 
 app/src/main/res/drawable-hdpi/ic_notification_qhybrid.png                                                                                                |    0 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/app_specific_notifications/AppSpecificNotificationSettingsDetailActivity.java           |  184 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/FetchDebugLogsOperation.java                                |  109 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband4/MiBand4FWHelper.java                                                         |   40 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/workout/WorkoutRequestHandler.java                      |   84 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/FindPhoneActivity.java                                                                  |  218 
 app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/cmfwatchpro/CmfCommandTest.java                                                    |   39 
 app/src/main/res/drawable-xhdpi/ic_notification.png                                                                                                       |    0 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitvergel/AmazfitVergeLFWInstallHandler.java                                     |   49 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/logic/parsers/Vo2MaxPacketParser.java                          |   93 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/AbstractXiaomiSppProtocol.java                                              |   49 
 app/src/main/res/drawable/ic_calendar_today.xml                                                                                                           |   10 
 app/src/main/res/xml/discovery_pairing_preferences.xml                                                                                                    |   30 
 app/src/main/res/drawable/ic_device_itag.xml                                                                                                              |   40 
 app/src/main/res/drawable/ic_device_miband6.xml                                                                                                           |   27 
 src/mainline/fastlane/metadata/android/en-US/changelogs/101.txt                                                                                           |   12 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/tlw64/TLW64Constants.java                                                                  |   62 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/message/SimpleHexToByteMessage.java                  |   71 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/notification/NotificationSource.java                 |   55 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/status/DownloadRequestStatusMessage.java                           |   60 
 src/mainline/fastlane/metadata/android/en-US/changelogs/187.txt                                                                                           |   16 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitSkinTempOvernight.java                                      |   47 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miscale/MiScaleSampleProvider.java                                                         |   54 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiTruSleepParser.java                                                           |  474 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/status/CreateFileStatusMessage.java                                |   72 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/supercars/JoystickView.java                                                                |  126 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AbstractGBActivity.java                                                                 |  144 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/realme/RealmeBudsT110Coordinator.java                                                      |   73 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qc35/QC35IOThread.java                                                             |   78 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casio/BasicCasio2C2DSupport.java                                                   |  114 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/moondrop/GaiaPacket.java                                                           |  136 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitneo/AmazfitNeoFirmwareInfo.java                                       |   79 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendWeatherExtendedSupportRequest.java                             |   59 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetMusicPlaylistMusics.java                                        |   44 
 app/src/nightly/res/mipmap-mdpi/ic_launcher_round.png                                                                                                     |    0 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/wena3/SonyWena3Constants.java                                                         |   64 
 app/src/main/res/xml/devicesettings_redmibuds5pro_gestures.xml                                                                                            |  123 
 app/src/main/res/drawable/ic_calculate.xml                                                                                                                |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/message/AbstractMessage.java                         |   98 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/huaweiwatch4pro/HuaweiWatch4ProCoordinator.java                                     |   52 
 app/src/main/res/xml/network_security_config.xml                                                                                                          |    4 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/binary_sensor/protocol/message/SetSensorRequest.java                               |   34 
 app/src/main/res/drawable/ic_switch_right.xml                                                                                                             |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/id115/ID115Support.java                                                            |  241 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/activity/impl/DailySummaryParser.java                                       |  126 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/LefunFeatureSupport.java                                                             |   59 
 app/src/main/res/xml/devicesettings_sleep_mode.xml                                                                                                        |   25 
 app/src/main/res/layout/fragment_temperature.xml                                                                                                          |  101 
 src/mainline/fastlane/metadata/android/en-US/changelogs/229.txt                                                                                           |   23 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitConnectivity.java                                           |   67 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitbip/AmazfitBipFirmwareInfo.java                                       |  186 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/ProfileCommand.java                                                         |  129 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/um25/Data/CaptureGroup.java                                                        |   55 
 app/src/main/res/drawable/ic_open_in_browser.xml                                                                                                          |   10 
 app/src/main/res/drawable/ic_report.xml                                                                                                                   |   12 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/loyaltycards/LoyaltyCardsSettingsActivity.java                                          |   87 
 .idea/dictionaries/t.xml                                                                                                                                  |  189 
 app/src/main/res/drawable/ic_battery.xml                                                                                                                  |   12 
 app/src/main/res/layout/steps_streak_maximum_line_layout.xml                                                                                              |   52 
 app/src/main/java/com/mobeta/android/dslv/CheckableLinearLayout.java                                                                                      |   38 
 app/src/main/res/drawable/ic_person.xml                                                                                                                   |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/LefunDeviceCoordinator.java                                                          |  166 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/divoom/PixooProtocol.java                                                          |  492 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerZalewszczak.java                                           |   99 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventCallControl.java                                                         |   40 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/DayStartHourSetting.java                      |   34 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BtLEQueue.java                                                                        |  828 
 app/src/main/assets/fossil_hr/widgetUV.bin                                                                                                                |    0 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/file/AssetFilePutRequest.java                           |   61 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/miband6/MiBand6FirmwareInfo.java                                             |   82 
 app/src/main/res/drawable/ic_reply.xml                                                                                                                    |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/services/XiaomiMusicService.java                                            |  163 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/MapUtils.java                                                                                 |   42 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiSportHRZones.java                                                             |  131 
 app/src/main/res/layout/activity_watchxplus_calibration.xml                                                                                               |  124 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/AppSpecificNotificationSettingsAppListAdapter.java                                         |  252 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminHrvValueSampleProvider.java                                                   |   56 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/MusicPlaybackReceiver.java                                                          |  117 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/conversation/ActivitySampleHandler.java              |  269 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/fieldDefinitions/FieldDefinitionWeatherAqi.java                         |   56 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/conversation/ConversationObserver.java               |   21 
 app/src/main/proto/garmin/gdi_device_status.proto                                                                                                         |   44 
 app/src/main/assets/fossil_hr/icBattEmpty.rle                                                                                                             |    0 
 app/src/main/res/drawable/ic_activity_stair_stepper.xml                                                                                                   |   21 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/gpx/model/GpxTrackPoint.java                                                                  |  115 
 app/src/main/res/drawable-v21/splash.xml                                                                                                                  |   11 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/Alarms.java                                                                 |  257 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSpecificSettingsHandler.java                                       |   83 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractDeviceCoordinator.java                                                             |  912 
 app/src/main/res/drawable-mdpi/ic_languagepack.png                                                                                                        |    0 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/TimeStamped.java                                                                             |   24 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/codegen/FitCodeGen.java                                                 |  364 
 app/src/main/res/drawable/ic_thermometer.xml                                                                                                              |   10 
 app/src/main/aidl/lineageos/weatherservice/IWeatherProviderService.aidl                                                                                   |   28 
 app/src/main/res/drawable/ic_device_nothingear.xml                                                                                                        |   34 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/ActivitySampleCalories.java           |   54 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSpecificSettingsCustomizer.java                                    |   52 
 app/src/main/res/drawable-mdpi/ic_watchapp.png                                                                                                            |    0 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/GattServerCallback.java                                                               |   76 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/oppo/OppoHeadphonesCoordinator.java                                                        |  101 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/image/AssetImageFactory.java                            |   40 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/prefs/SoundPosition.java                                                   |   54 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetWearStatusRequest.java                                          |   56 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/watch9/Watch9Constants.java                                                                |   92 
 app/src/main/res/xml/devicesettings_fossilhybridhr_vibration.xml                                                                                          |   13 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_51.java                                                         |   38 
 src/mainline/fastlane/metadata/android/en-US/changelogs/155.txt                                                                                           |   13 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/BohemicSmartBraceletDeviceCoordinator.java                                           |   63 
 app/src/main/res/xml/devicesettings_chartstabs.xml                                                                                                        |   14 
 app/src/main/res/xml/devicesettings_header_health.xml                                                                                                     |    6 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_29.java                                                         |   38 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/authentication/CheckDeviceNeedsConfirmationRequest.java |   50 
 app/src/main/res/layout/activity_appblacklist.xml                                                                                                         |   25 
 app/src/main/res/values-ru/strings.xml                                                                                                                    | 3680 
 app/src/main/res/xml/devicesettings_qc35.xml                                                                                                              |    9 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/parser/ActivityFileParser.java                                             |  246 
 app/src/main/res/drawable/ic_device_galaxy_buds_pro.xml                                                                                                   |   31 
 app/src/main/res/xml/devicesettings_donotdisturb_no_auto.xml                                                                                              |   31 
 app/src/main/res/drawable/ic_wena_icon_right.xml                                                                                                          |   24 
 app/src/main/res/xml/devicesettings_amazfitcor.xml                                                                                                        |   13 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/defines/VibrationStrength.java                |   29 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/message/MessageFactory.java                          |   55 
 app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/headphones/protocol/impl/v1/SonyProtocolImplV1Test.java                       |  538 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiWeatherConditions.java                                                        |  186 
 app/src/main/res/drawable/ic_done.xml                                                                                                                     |   10 
 app/src/banglejsNightly/res/values/strings.xml                                                                                                            |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfittrexultra/AmazfitTRexUltraCoordinator.java                                    |   81 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiMusicManager.java                                                     |  377 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitx/AmazfitXFWHelper.java                                                       |   40 
 app/src/main/java/lineageos/os/Build.java                                                                                                                 |   32 
 app/src/test/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/TestWorkout.java                                                            |  445 
 app/src/main/res/values-ro/strings.xml                                                                                                                    | 1181 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/AbstractBTLEDeviceSupport.java                                                        |  478 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbips/AmazfitBipSFWHelper.java                                                 |   40 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleActiveAppTracker.java                                                 |   56 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/EventHandler.java                                                                          |  162 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/redmibuds5pro/RedmiBuds5ProSettingsCustomizer.java                                  |  161 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetNotificationCapabilitiesRequest.java                            |   61 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_71.java                                                         |   38 
 app/src/main/res/xml/devicesettings_high_mtu.xml                                                                                                          |   10 
 app/src/main/res/layout/dashboard_widget_today.xml                                                                                                        |   35 
 app/src/main/res/values-bg/strings.xml                                                                                                                    |  601 
 app/src/main/res/drawable/ic_access_time.xml                                                                                                              |   10 
 app/src/main/res/layout/activity_about.xml                                                                                                                |  175 
 app/src/main/res/xml/devicesettings_sony_headphones_touch_sensor_single.xml                                                                               |    9 
 app/src/main/java/org/bouncycastle/shaded/crypto/StreamCipher.java                                                                                        |   54 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/huaweiwatchgt5/HuaweiWatchGT5Coordinator.java                                       |   47 
 src/mainline/fastlane/metadata/android/en-US/changelogs/142.txt                                                                                           |    1 
 app/src/main/res/layout/layout_widget_intensity_total.xml                                                                                                 |   31 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband2/MiBand2FWInstallHandler.java                                                 |  122 
 app/src/main/res/values-sv/strings.xml                                                                                                                    |  371 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/DateTimeDisplay.java                                                                |   22 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/CalibrationActivity.java                                                           |  158 
 app/src/main/res/layout/qhybrid_element_popup_view.xml                                                                                                    |   79 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/bandwpseries/BandWPSeriesRequest.java                                              |   80 
 app/src/main/assets/fossil_hr/widget2ndTZ_preview.png                                                                                                     |    0 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/translation/TranslationData.java                        |   61 
 app/src/main/assets/fossil_hr/widgetActiveMins_preview.png                                                                                                |    0 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/banglejs/BangleJSSampleProvider.java                                                       |  127 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/forerunner/GarminForerunner235Coordinator.java                              |   18 
 app/src/main/assets/fossil_hr/icCalories.rle                                                                                                              |    2 
 app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/TimeTest.java                         |   33 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/NotificationDataMessage.java                                       |   39 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/jyou/BFH16DeviceCoordinator.java                                                           |  202 
 app/src/main/res/drawable/ic_activity_boating.xml                                                                                                         |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiSppProtocolV2.java                                                    |  150 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/coordinators/SonyWFSP800NCoordinator.java                                  |   78 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/HungarianTransliterator.java                                                    |   36 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/ActivitySampleDuration.java           |   51 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitbip3/AmazfitBip3Support.java                                          |   79 
 app/src/main/res/drawable/ic_device_miband2_disabled.xml                                                                                                  |   32 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgts2/AmazfitGTS2MiniFWHelper.java                                             |   40 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband5/MiBand5Coordinator.java                                                      |  204 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/SMSReceiver.java                                                                    |  114 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/welcome/WelcomeFragmentPermissions.java                                                 |  173 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfittrex2/AmazfitTRex2Coordinator.java                                            |   66 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pinetime/InfiniTimeDFUPackage.java                                                         |   59 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/capabilities/loyaltycards/BarcodeFormat.java                                                       |   36 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband7/MiBand7Coordinator.java                                                      |   87 
 app/src/main/assets/app_config/js/Uri.js                                                                                                                  |  458 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/EventFactory.java                                      |   42 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/User.java                             |  102 
 app/src/main/res/layout/main_drawer_header.xml                                                                                                            |   31 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/CalBlacklistActivity.java                                                               |  171 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/fieldDefinitions/FieldDefinitionFileType.java                           |   30 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/defines/Language.java                         |   22 
 src/mainline/fastlane/metadata/android/en-US/changelogs/114.txt                                                                                           |    3 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casio/gb6900/CasioGB6900HandlerThread.java                                         |  109 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband6/MiBand6Coordinator.java                                                      |  226 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/sleepasandroid/SleepAsAndroidAction.java                                            |   16 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/binary_sensor/BinarySensorBaseSupport.java                                         |   42 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/CalendarEventSpec.java                                                                       |   38 
 app/src/main/res/drawable/ic_device_smartring_disabled.xml                                                                                                |   22 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/NotificationUtils.java                                                                        |  162 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/BatteryInfoActivity.java                                                                |  241 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgts/AmazfitGTSFWHelper.java                                                   |   40 
 app/src/main/proto/garmin/gdi_find_my_watch.proto                                                                                                         |   33 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/SonyHeadphonesCoordinator.java                                             |  232 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/FetchSportsDetailsOperation.java                            |  217 
 app/src/main/res/xml/devicesettings_sonyswr12.xml                                                                                                         |   25 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetExpandCapabilityRequest.java                                    |   67 
 app/src/main/res/xml/devicesettings_hourly_chime_enable.xml                                                                                               |    9 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/WeatherMessage.java                                                |   37 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminEventSampleProvider.java                                                      |  101 
 app/src/main/res/layout/dialog_frequency_picker.xml                                                                                                       |   79 
 app/src/test/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactoryTest.java                                                                         |   70 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband2/MiBand2Coordinator.java                                                      |  141 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/PolishTransliterator.java                                                       |   30 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/protobuf/messagefields/BytesMessageField.java                                                 |   34 
 app/src/main/res/layout/steps_streaks_dashboard.xml                                                                                                       |   78 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/SummaryOfDay.java                                                                            |   28 
 app/src/main/res/layout/item_appmanager_watchapp.xml                                                                                                      |  101 
 app/src/main/assets/sony_800n_case.svg                                                                                                                    |   72 
 app/src/main/res/values-es/strings.xml                                                                                                                    | 3693 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/miband6/MiBand6Support.java                                                  |   79 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/mijia_lywsd/MijiaMhoC303Coordinator.java                                                   |   48 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiPhoneGpsStatus.java                                                     |   47 
 app/src/main/res/drawable/ic_earbuds_battery.xml                                                                                                          |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/ImportExportSharedPreferences.java                                                            |  186 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/TemperatureDailyFragment.java                                                    |  234 
 src/mainline/fastlane/metadata/android/en-US/changelogs/216.txt                                                                                           |   34 
 app/src/main/proto/smaq2oss.proto                                                                                                                         |   60 
 app/src/main/res/xml/devicesettings_pebble_activity.xml                                                                                                   |   43 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/AppBlacklistAdapter.java                                                                   |  307 
 app/src/main/res/drawable-hdpi/ic_notification.png                                                                                                        |    0 
 app/src/main/res/drawable/ic_device_miscale_disabled.xml                                                                                                  |   41 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/miband2/Mi2FirmwareInfo.java                                                 |  107 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivitySummaryEntries.java                                                                  |  183 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendFileUploadInfo.java                                            |   55 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebbleCoordinator.java                                                              |  292 
 app/src/main/res/drawable/ic_buds_pro_case.xml                                                                                                            |    3 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/forerunner/GarminForerunner965Coordinator.java                              |   18 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/defines/GenderSetting.java                    |   22 
 app/src/main/res/drawable/ic_device_supercars_disabled.xml                                                                                                |   27 
 app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/TestHuaweiSupportProvider.java                                              |   71 
 app/src/main/res/xml/devicesettings_heartrate_sleep_alert_activity_stress.xml                                                                             |   66 
 app/src/main/java/org/bouncycastle/shaded/crypto/modes/CCMBlockCipher.java                                                                                |  480 
 app/src/main/res/drawable/ic_add_gray.xml                                                                                                                 |   12 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiHeartRateRestingSampleProvider.java                                             |   56 
 app/src/main/res/drawable/ic_activity_climbing.xml                                                                                                        |   10 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiCoordinator.java                                                              |  843 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/AnimationRequest.java                                      |   26 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xwatch/XWatchSupport.java                                                          |  491 
 app/src/main/res/xml/devicesettings_miband_vibrationpatterns.xml                                                                                          |  248 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendEphemerisParameterConsultRequest.java                          |   38 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitpoppro/AmazfitPopProFWInstallHandler.java                                     |   49 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/FieldInterface.java                                                     |   12 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/QHybridSupport.java                                                        |  854 
 app/src/main/java/org/bouncycastle/shaded/crypto/paddings/BlockCipherPadding.java                                                                         |   48 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/IntentApiReceiver.java                                                              |  302 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/test/TestDeviceCoordinator.java                                                            |  697 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/files/FileManagerAdapter.java                                                           |  206 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hplus/HPlusDataRecordRealtime.java                                                 |  137 
 app/src/main/res/layout/item_cal_blacklist.xml                                                                                                            |   60 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband4/MiBand4Coordinator.java                                                      |  185 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBApplication.java                                                                                 | 2096 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminCoordinator.java                                                              |  414 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ActivitySummariesActivity.java                                                          |  472 
 app/src/main/res/xml/garmin_realtime_settings.xml                                                                                                         |    5 
 src/mainline/fastlane/metadata/android/en-US/changelogs/170.txt                                                                                           |    7 
 app/src/main/assets/fossil_hr/icWthCloudy.rle                                                                                                             |    1 
 app/src/main/res/layout/activity_hybridhr_watchface_designer.xml                                                                                          |  164 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/appmanager/AppManagerFragmentInstalledWatchfaces.java                                   |   62 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/WakeActivity.java                                                                       |   66 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/discovery/DiscoveryPairingPreferenceActivity.java                                       |   54 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/widget/CustomBackgroundWidgetElement.java               |   27 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/GermanTransliterator.java                                                       |   31 
 app/src/main/res/xml/devicesettings_fossilhybridhr_pre_fw300.xml                                                                                          |   67 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casio/CasioTimeZone.java                                                           |  410 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandSampleProvider.java                                                           |   79 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebblePairingActivity.java                                                          |  269 
 app/src/main/assets/filter_alt_plus.svg                                                                                                                   |   77 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/AutoStartReceiver.java                                                              |   47 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband3/MiBand3Coordinator.java                                                      |  175 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/FwAppInstallerActivity.java                                                             |  351 
 app/src/main/res/drawable/ic_language.xml                                                                                                                 |   10 
 src/mainline/fastlane/metadata/android/de-DE/short_description.txt                                                                                        |    1 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetFileInfoRequest.java                                            |   81 
 src/mainline/fastlane/metadata/android/en-US/images/phoneScreenshots/70-LiveActivity.png                                                                  |    0 
 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/zetime/ZeTimeSettingsCustomizer.java                                                       |  111 
 3,954 files changed, 530,654 insertions(+), 0 deletions(-)

diff --git a/.artifacts/5fb7b17c-2744-457a-b1a3-a3b9fbd1ae6d/implementation_plan.artifact.md b/.artifacts/5fb7b17c-2744-457a-b1a3-a3b9fbd1ae6d/implementation_plan.artifact.md
new file mode 100644
index 0000000..86f5df8
--- /dev/null
+++ b/.artifacts/5fb7b17c-2744-457a-b1a3-a3b9fbd1ae6d/implementation_plan.artifact.md
@@ -0,0 +1,24 @@
+# Fix missing BaseActivitySummary symbol
+
+The build error `閿欒: 鎵句笉鍒扮鍙� 绫� BaseActivitySummary` occurs because the greenDAO generated entity `BaseActivitySummary` is missing from the project's source tree. These entities are generated by the `GBDaoGenerator` module, which is currently not configured to run automatically during the build process.
+
+## Proposed Changes
+
+### Build Configuration
+
+#### [MODIFY] [settings.gradle](file:///E:/mfa/jingshen-womp/cool-admin-shop-v8.0.0/cool-admin-android/settings.gradle)
+Include the `:GBDaoGenerator` module so it can be used to generate the necessary entities. (Already partially completed, will verify).
+
+#### [MODIFY] [app/build.gradle](file:///E:/mfa/jingshen-womp/cool-admin-shop-v8.0.0/cool-admin-android/app/build.gradle)
+Uncomment the task dependency to ensure that the DAO generator runs before the app is built.
+
+```gradle
+preBuild.dependsOn(":GBDaoGenerator:genSources")
+```
+
+## Verification Plan
+
+### Automated Tests
+- Run `./gradlew :GBDaoGenerator:genSources` to manually trigger source generation.
+- Verify the existence of `app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/BaseActivitySummary.java`.
+- Run `./gradlew :app:compileMainlineDebugJavaWithJavac` to verify that the compilation error is resolved.
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..42736ee
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,17 @@
+# Set the default behavior, in case people don't have core.autocrlf set.
+* text=auto
+
+# Explicitly declare text files you want to always be normalized and converted
+# to native line endings on checkout.
+*.java text
+*.gradle text
+*.md text
+*.properties text
+*.xml text
+
+# Declare files that will always have CRLF line endings on checkout.
+#*.sln text eol=crlf
+
+# Denote all files that are truly binary and should not be modified.
+#*.png binary
+#*.jpg binary
diff --git a/.gitea/issue_template/bug_report.yml b/.gitea/issue_template/bug_report.yml
new file mode 100644
index 0000000..a4c186a
--- /dev/null
+++ b/.gitea/issue_template/bug_report.yml
@@ -0,0 +1,77 @@
+name: Bug report
+about: Create a report to help us improve.
+body:
+  - type: markdown
+    attributes:
+      value: |
+        Thanks for taking the time to make Gadgetbridge better!
+
+        If you just have a question, please ask first in the user chatroom in Matrix at [`#gadgetbridge:matrix.org`](https://matrix.to/#/#gadgetbridge:matrix.org)
+  - type: checkboxes
+    attributes:
+      label: Please confirm that;
+      options:
+        - label: I have checked the [website](https://gadgetbridge.org), and I didn't find a solution to my problem / an answer to my question.
+          required: true
+        - label: I have searched the [issues](https://codeberg.org/Freeyourgadget/Gadgetbridge/issues), and I didn't find a solution to my problem / an answer to my question.
+          required: true
+        - label: I have read and understood the [Codeberg Terms of Use](https://codeberg.org/Codeberg/org/src/branch/main/TermsOfUse.md) for images or other type of content that I included here.
+          required: true
+  - type: dropdown
+    id: source
+    attributes:
+      label: Where did you get Gadgetbridge from?
+      options:
+        - F-Droid
+        - Gadgetbridge Nightly F-Droid repository
+        - Bangle.js Gadgetbridge from the Play Store
+        - I built it myself from source code
+        - I previously used Gadgetbridge from other sources and then updated to F-Droid version
+    validations:
+      required: true
+  - type: input
+    id: version
+    attributes:
+      label: What is your Gadgetbridge version?
+      description: |
+        This can be found in "Menu > About > Version" in Gadgetbridge. 
+        Also include tag / commit SHA if you built Gadgetbridge from the source. 
+      placeholder: e.g. "0.77.0" or "0.77.0-2618adac1" to include commit
+    validations:
+      required: true
+  - type: textarea
+    id: content
+    attributes:
+      label: What happened?
+      description: >
+        Please note that new issues about already solved/documented topics
+        **could be closed without further comments.** Same for too generic or incomplete reports.
+      placeholder: If you want to include logs, don't include it here. Use the next text field for that.
+    validations:
+      required: true
+  - type: textarea
+    id: logs
+    attributes:
+      label: Do you have logs?
+      description: >
+        If possible, please attach [logs](https://gadgetbridge.org/internals/topics/logs/)
+        that might help identifying the problem. This will be automatically formatted into 
+        code, so no need for backticks.
+      render: shell
+  - type: input
+    id: gadget
+    attributes:
+      label: What gadget do you use?
+      description: >
+        Please specify model and firmware version if possible. Leave blank if you believe the
+        issue is not specific to the gadget that you currently use with Gadgetbridge.
+      placeholder: e.g. ExampleWatch A1 with 0.1 firmware
+  - type: input
+    id: android
+    attributes:
+      label: What is your Android version/manufacturer flavor?
+      description: >
+        Android phone manufacturers may customise the Android source code as they wish, so
+        if you are using a phone that running a vendor-exclusive system (like MIUI) or if 
+        you use a custom ROM, make sure to also include the name of the OS/ROM.
+      placeholder: e.g. LineageOS 20 based on Android 13
\ No newline at end of file
diff --git a/.gitea/issue_template/config.yml b/.gitea/issue_template/config.yml
new file mode 100644
index 0000000..4abd84b
--- /dev/null
+++ b/.gitea/issue_template/config.yml
@@ -0,0 +1,11 @@
+blank_issues_enabled: false
+contact_links:
+  - name: Ask a question
+    url: https://matrix.to/#/#gadgetbridge:matrix.org
+    about: If you just have a question, please ask in the user chatroom in Matrix.
+  - name: Website
+    url: https://gadgetbridge.org
+    about: Visit Gadgetbridge website for FAQ, getting started and common troubleshooting.
+  - name: See supported gadgets
+    url: https://gadgetbridge.org/gadgets/
+    about: List of all supported gadget vendors and models in Gadgetbridge.
\ No newline at end of file
diff --git a/.gitea/issue_template/device_request.yml b/.gitea/issue_template/device_request.yml
new file mode 100644
index 0000000..5d50f59
--- /dev/null
+++ b/.gitea/issue_template/device_request.yml
@@ -0,0 +1,79 @@
+name: Device request
+about: Request for a new device/gadget.
+labels:
+  - device request
+body:
+  - type: markdown
+    attributes:
+      value: >
+        You are trying to submit a request for a device implementation into Gadgetbridge. 
+        **We cannot make any promise that the device will be implemented**, as adding an implementation 
+        for a new device requires either an experienced or a willing to learn developer, 
+        ideally with the device at hand. Without that, you may try to submit a device request 
+        and see if anyone steps up and implements it and you are basically indicating that you 
+        have the device and are willing to help with testing, should anyone try to add 
+        this device to Gadgetbridge.
+  - type: markdown
+    attributes:
+      value: |
+        Thanks for taking the time to make Gadgetbridge better!
+
+        If you just have a question, please ask first in the user chatroom in Matrix at [`#gadgetbridge:matrix.org`](https://matrix.to/#/#gadgetbridge:matrix.org)
+  - type: checkboxes
+    attributes:
+      label: Please confirm that;
+      options:
+        - label: I have checked the [website](https://gadgetbridge.org) and [gadget list](https://gadgetbridge.org/gadgets/), and I didn't find this device mentioned there.
+          required: true
+        - label: I have searched the [issues](https://codeberg.org/Freeyourgadget/Gadgetbridge/issues), and I didn't find this device mentioned there.
+          required: true
+        - label: I have read and understood the [Codeberg Terms of Use](https://codeberg.org/Codeberg/org/src/branch/main/TermsOfUse.md) for images or other type of content that I included here.
+          required: true
+  - type: input
+    id: gadget
+    attributes:
+      label: Which gadget are you requesting support for?
+      description: Please specify name, manufacturer, model and firmware version if possible.
+      placeholder: e.g. ExampleWatch A1 with 0.1 firmware
+    validations:
+      required: true
+  - type: input
+    id: version
+    attributes:
+      label: Does this gadget have similarities to other gadgets?
+      description: >
+        If the gadget you request support for has similarities to other gadgets that we currently support,
+        it is more likely for us (still, not guaranteed) to add support for your gadget too.
+  - type: textarea
+    id: info
+    attributes:
+      label: Device information
+      description: >
+        Include all details about the gadget that will be useful to us when implementing a support for this gadget. 
+
+        If possible, please include the exact bluetooth name, as displayed while searching for the device.
+
+        Ideally, use an Android Bluetooth scanner app like nRF Connect or BLExplorer and provide screenshots 
+        of the scanned gadget from that app. This provides a name and some available UUIDs, which are needed 
+        for implementation. You may want to blur MAC addresses for privacy reasons.
+      placeholder: If you want to include logs, don't include it here. Use the next text field for that.
+    validations:
+      required: true
+  - type: textarea
+    id: logs
+    attributes:
+      label: Do you have logs?
+      description: >
+        If possible, please attach [logs](https://gadgetbridge.org/internals/topics/logs/)
+        which might help to implement the device support into Gadgetbridge. This will be automatically 
+        formatted into code, so no need for backticks.
+      render: shell
+  - type: dropdown
+    id: source
+    attributes:
+      label: Do you have the gadget and are you able to help us for testing?
+      options:
+        - Yes, I have the gadget and I'm willing to provide further feedback and help testing.
+        - No, I don't have the gadget or/and I'm not able to help during the development.
+    validations:
+      required: true
diff --git a/.gitea/issue_template/feature_request.yml b/.gitea/issue_template/feature_request.yml
new file mode 100644
index 0000000..cdabcef
--- /dev/null
+++ b/.gitea/issue_template/feature_request.yml
@@ -0,0 +1,76 @@
+name: Feature request
+about: Suggest a feature or an idea.
+body:
+  - type: markdown
+    attributes:
+      value: |
+        Thanks for taking the time to make Gadgetbridge better!
+
+        If you just have a question, please ask first in the user chatroom in Matrix at [`#gadgetbridge:matrix.org`](https://matrix.to/#/#gadgetbridge:matrix.org)
+  - type: checkboxes
+    attributes:
+      label: Please confirm that;
+      options:
+        - label: I have checked the [website](https://gadgetbridge.org), and I didn't find a solution to my problem / an answer to my question.
+          required: true
+        - label: I have searched the [issues](https://codeberg.org/Freeyourgadget/Gadgetbridge/issues), and I didn't find a solution to my problem / an answer to my question.
+          required: true
+        - label: I have read and understood the [Codeberg Terms of Use](https://codeberg.org/Codeberg/org/src/branch/main/TermsOfUse.md) for images or other type of content that I included here.
+          required: true
+  - type: dropdown
+    id: source
+    attributes:
+      label: Where did you get Gadgetbridge from?
+      options:
+        - F-Droid
+        - Gadgetbridge Nightly F-Droid repository
+        - Bangle.js Gadgetbridge from the Play Store
+        - I built it myself from source code
+        - I previously used Gadgetbridge from other sources and then updated to F-Droid version
+    validations:
+      required: true
+  - type: input
+    id: version
+    attributes:
+      label: What is your Gadgetbridge version?
+      description: |
+        This can be found in "Menu > About > Version" in Gadgetbridge. 
+        Also include tag / commit SHA if you built Gadgetbridge from the source. 
+      placeholder: e.g. "0.77.0" or "0.77.0-2618adac1" to include commit
+    validations:
+      required: true
+  - type: textarea
+    id: content
+    attributes:
+      label: What is your suggestion/idea?
+      description: >
+        Please note that new requests about already solved/documented topics
+        **could be closed without further comments.** Same for too generic or incomplete reports.
+      placeholder: If you want to include logs, don't include it here. Use the next text field for that.
+    validations:
+      required: true
+  - type: textarea
+    id: logs
+    attributes:
+      label: Do you have logs?
+      description: >
+        If possible, please attach [logs.](https://gadgetbridge.org/internals/topics/logs/)
+        This will be automatically formatted into code, so no need for backticks.
+      render: shell
+  - type: input
+    id: gadget
+    attributes:
+      label: What gadget do you use?
+      description: >
+        Please specify model and firmware version if possible. Leave blank if you believe the
+        issue is not specific to the gadget that you currently use with Gadgetbridge.
+      placeholder: e.g. ExampleWatch A1 with 0.1 firmware
+  - type: input
+    id: android
+    attributes:
+      label: What is your Android version/manufacturer flavor?
+      description: >
+        Android phone manufacturers may customise the Android source code as they wish, so
+        if you are using a phone that running a vendor-exclusive system (like MIUI) or if 
+        you use a custom ROM, make sure to also include the name of the OS/ROM.
+      placeholder: e.g. LineageOS 20 based on Android 13
\ No newline at end of file
diff --git a/.gitea/pull_request_template.md b/.gitea/pull_request_template.md
new file mode 100644
index 0000000..021ab4b
--- /dev/null
+++ b/.gitea/pull_request_template.md
@@ -0,0 +1,16 @@
+<!-- 
+    Thank you for your contribution! 馃帀 
+
+    Please make sure that you:
+
+    - Use `git rebase` to bring your branch up to date, do not use a merge commit for that.
+
+    - Do not add translations by editing the language variants of strings.xml as that 
+      creates merge conflicts between Codeberg git repo and Weblate git repo,
+      use Weblate https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/
+
+    - Do not use `e.printStacktrace()`, use slf4j logger or `GB.toast` for logging
+      as per https://gadgetbridge.org/internals/development/project-overview/#logging
+    
+    You can erase this comment from PR description, thank you!
+-->
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..89324dd
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,43 @@
+# Built application files
+*.apk
+*.ap_
+
+# Files for the Dalvik VM
+*.dex
+
+# Java class files
+*.class
+
+# Generated files
+bin/
+gen/
+
+# Gradle files
+.gradle/
+build/
+
+# Local configuration file (sdk path, etc)
+local.properties
+
+# Proguard folder generated by Eclipse
+proguard/
+
+# Log Files
+*.log
+
+.idea/*
+!.idea/icon.svg
+!.idea/dictionaries
+.idea/dictionaries/*
+!.idea/dictionaries/t.xml
+*.iml
+
+MPChartLib
+
+fw.dirs
+**/.project
+**/.settings
+**/.classpath
+app/release/output-metadata.json
+app/.cxx/
+.attach_pid*
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..cc9db98
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,6 @@
+[submodule "jerryscript"]
+	path = external/jerryscript
+	url = https://github.com/jerryscript-project/jerryscript
+[submodule "fossil-hr-gbapps"]
+	path = external/fossil-hr-gbapps
+	url = https://codeberg.org/Freeyourgadget/fossil-hr-gbapps
diff --git a/.idea/dictionaries/t.xml b/.idea/dictionaries/t.xml
new file mode 100644
index 0000000..c82ca81
--- /dev/null
+++ b/.idea/dictionaries/t.xml
@@ -0,0 +1,189 @@
+<component name="ProjectDictionaryState">
+  <dictionary name="t">
+    <words>
+      <w>abettenburg</w>
+      <w>adhikary</w>
+      <w>afanasev</w>
+      <w>affero</w>
+      <w>alaoui</w>
+      <w>alexey</w>
+      <w>alipay</w>
+      <w>amazfit</w>
+      <w>andrewlytvyn</w>
+      <w>aniruddha</w>
+      <w>aplite</w>
+      <w>apre</w>
+      <w>atkyritsis</w>
+      <w>autocrlf</w>
+      <w>avamander</w>
+      <w>baerts</w>
+      <w>baos</w>
+      <w>barraca</w>
+      <w>bedscastle</w>
+      <w>blo脽</w>
+      <w>boun</w>
+      <w>breathwork</w>
+      <w>b枚hler</w>
+      <w>carsten</w>
+      <w>chamorro</w>
+      <w>codeberg</w>
+      <w>criogenic</w>
+      <w>croisez</w>
+      <w>dakhno</w>
+      <w>dantas</w>
+      <w>dikay</w>
+      <w>diorite</w>
+      <w>dodgeball</w>
+      <w>dougal</w>
+      <w>dreamwalker</w>
+      <w>drobni膷</w>
+      <w>dulfo</w>
+      <w>elagin</w>
+      <w>eom盲e</w>
+      <w>ericsson</w>
+      <w>exrizu</w>
+      <w>findphone</w>
+      <w>fran莽ois</w>
+      <w>freeyourgadget</w>
+      <w>gadgetbridge</w>
+      <w>gateball</w>
+      <w>gbapplication</w>
+      <w>getpebble</w>
+      <w>gfdi</w>
+      <w>gide茫o</w>
+      <w>girolamo</w>
+      <w>gobbetti</w>
+      <w>greenberg</w>
+      <w>greenrobot</w>
+      <w>greffier</w>
+      <w>guedes</w>
+      <w>handcycling</w>
+      <w>hasants</w>
+      <w>hauck</w>
+      <w>hiit</w>
+      <w>hplus</w>
+      <w>huami</w>
+      <w>ieee</w>
+      <w>infini</w>
+      <w>inkscape</w>
+      <w>irul</w>
+      <w>itag</w>
+      <w>ivanovlev</w>
+      <w>jcrode</w>
+      <w>jes煤s</w>
+      <w>jianxin</w>
+      <w>jonnsoft</w>
+      <w>jos茅</w>
+      <w>jo茫o</w>
+      <w>julien</w>
+      <w>jumpmaster</w>
+      <w>junginger</w>
+      <w>jyou</w>
+      <w>kabaddi</w>
+      <w>kasha</w>
+      <w>kaushan</w>
+      <w>keeshii</w>
+      <w>kitesurfing</w>
+      <w>kompact</w>
+      <w>kranz</w>
+      <w>kromke</w>
+      <w>kronoz</w>
+      <w>lacross</w>
+      <w>ladbsoft</w>
+      <w>ladera</w>
+      <w>lenovo</w>
+      <w>liebergeld</w>
+      <w>lightforce</w>
+      <w>lineageos</w>
+      <w>liveview</w>
+      <w>lywsd</w>
+      <w>makibes</w>
+      <w>matej</w>
+      <w>maufl</w>
+      <w>maurer</w>
+      <w>maxirnilian</w>
+      <w>miband</w>
+      <w>mijia</w>
+      <w>morpheuz</w>
+      <w>mosenkovs</w>
+      <w>multisport</w>
+      <w>nephiel</w>
+      <w>nodomain</w>
+      <w>nordh酶y</w>
+      <w>normano</w>
+      <w>novotny</w>
+      <w>oraclejdk</w>
+      <w>paddleboarding</w>
+      <w>padel</w>
+      <w>pebblekit</w>
+      <w>pfeiffer</w>
+      <w>pickleball</w>
+      <w>pinetime</w>
+      <w>pivotto</w>
+      <w>postsorino</w>
+      <w>protomors</w>
+      <w>qhybrid</w>
+      <w>quallenauge</w>
+      <w>realme</w>
+      <w>rebelo</w>
+      <w>roidmi</w>
+      <w>romanization</w>
+      <w>rssi</w>
+      <w>sami</w>
+      <w>schrecker</w>
+      <w>sepak</w>
+      <w>sergey</w>
+      <w>sevostyanova</w>
+      <w>shahrabani</w>
+      <w>shimokawa</w>
+      <w>skype</w>
+      <w>slezak</w>
+      <w>sophanimus</w>
+      <w>spalding</w>
+      <w>spotify</w>
+      <w>stacktraces</w>
+      <w>stefanek</w>
+      <w>stringset</w>
+      <w>subsport</w>
+      <w>szymon</w>
+      <w>taavi</w>
+      <w>tablename</w>
+      <w>takraw</w>
+      <w>teclast</w>
+      <w>tiparega</w>
+      <w>toleda</w>
+      <w>tomasz</w>
+      <w>tomer</w>
+      <w>transifex</w>
+      <w>trekvolle</w>
+      <w>trofimov</w>
+      <w>tysiak</w>
+      <w>uart</w>
+      <w>uint</w>
+      <w>utsob</w>
+      <w>uuids</w>
+      <w>vadim</w>
+      <w>vanous</w>
+      <w>vebryn</w>
+      <w>veneziano</w>
+      <w>vibratissimo</w>
+      <w>wakeboarding</w>
+      <w>wakesurfing</w>
+      <w>walkjivefly</w>
+      <w>watchapp</w>
+      <w>watchapps</w>
+      <w>watchface</w>
+      <w>watchfaces</w>
+      <w>wechat</w>
+      <w>whatsapp</w>
+      <w>xiaomi</w>
+      <w>xplus</w>
+      <w>xwatch</w>
+      <w>yaron</w>
+      <w>zalewszczak</w>
+      <w>zepp</w>
+      <w>zetime</w>
+      <w>zhong</w>
+    </words>
+  </dictionary>
+</component>
\ No newline at end of file
diff --git a/.idea/icon.svg b/.idea/icon.svg
new file mode 100644
index 0000000..248c776
--- /dev/null
+++ b/.idea/icon.svg
@@ -0,0 +1,574 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="5.6444445mm"
+   height="5.6444445mm"
+   viewBox="-1 -1 22 22"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.92.1 r15371"
+   sodipodi:docname="template1.svg"
+   inkscape:export-xdpi="648"
+   inkscape:export-ydpi="648">
+  <title
+     id="title4560">Gadgetbridge Icon</title>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="11.2"
+     inkscape:cx="-40.722947"
+     inkscape:cy="18.815618"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     fit-margin-top="0"
+     fit-margin-left="0"
+     fit-margin-right="0"
+     fit-margin-bottom="0"
+     showguides="true"
+     inkscape:guide-bbox="true"
+     inkscape:window-width="1920"
+     inkscape:window-height="1017"
+     inkscape:window-x="-8"
+     inkscape:window-y="-8"
+     inkscape:window-maximized="1"
+     inkscape:snap-grids="true"
+     inkscape:snap-to-guides="false"
+     inkscape:snap-page="true">
+    <inkscape:grid
+       type="xygrid"
+       id="grid4138" />
+    <sodipodi:guide
+       position="10,18"
+       orientation="0,1"
+       id="guide4140"
+       inkscape:locked="false" />
+    <sodipodi:guide
+       position="10,2"
+       orientation="0,1"
+       id="guide4142"
+       inkscape:locked="false" />
+    <sodipodi:guide
+       position="2,10"
+       orientation="1,0"
+       id="guide4144"
+       inkscape:locked="false" />
+    <sodipodi:guide
+       position="0,0"
+       orientation="0,20"
+       id="guide4146"
+       inkscape:locked="false" />
+    <sodipodi:guide
+       position="20,0"
+       orientation="-20,0"
+       id="guide4148"
+       inkscape:locked="false" />
+    <sodipodi:guide
+       position="20,20"
+       orientation="0,-20"
+       id="guide4150"
+       inkscape:locked="false" />
+    <sodipodi:guide
+       position="0,20"
+       orientation="20,0"
+       id="guide4152"
+       inkscape:locked="false" />
+    <sodipodi:guide
+       position="17.934153,10.890707"
+       orientation="1,0"
+       id="guide4154"
+       inkscape:locked="false" />
+    <sodipodi:guide
+       position="8.5170198,7.5753346"
+       orientation="-0.70710678,0.70710678"
+       id="guide4156"
+       inkscape:locked="false" />
+    <sodipodi:guide
+       position="4,10"
+       orientation="1,0"
+       id="guide4160"
+       inkscape:locked="false" />
+    <sodipodi:guide
+       position="16,10"
+       orientation="1,0"
+       id="guide4162"
+       inkscape:locked="false" />
+    <sodipodi:guide
+       position="10,4"
+       orientation="0,1"
+       id="guide4166"
+       inkscape:locked="false" />
+    <sodipodi:guide
+       position="10,15"
+       orientation="0,1"
+       id="guide4168"
+       inkscape:locked="false" />
+    <sodipodi:guide
+       position="10,5"
+       orientation="0,1"
+       id="guide4170"
+       inkscape:locked="false" />
+    <sodipodi:guide
+       position="5,10"
+       orientation="1,0"
+       id="guide4172"
+       inkscape:locked="false" />
+    <sodipodi:guide
+       position="15,10"
+       orientation="1,0"
+       id="guide4174"
+       inkscape:locked="false" />
+  </sodipodi:namedview>
+  <defs
+     id="defs4">
+    <linearGradient
+       inkscape:collect="always"
+       id="linearGradient13455">
+      <stop
+         style="stop-color:#000000;stop-opacity:0.2"
+         offset="0"
+         id="stop13457" />
+      <stop
+         style="stop-color:#000000;stop-opacity:0;"
+         offset="1"
+         id="stop13459" />
+    </linearGradient>
+    <clipPath
+       id="clipPath12478"
+       clipPathUnits="userSpaceOnUse">
+      <circle
+         r="10"
+         cy="10.000005"
+         cx="9.9999781"
+         id="circle12480"
+         style="opacity:1;fill:#2196f3;fill-opacity:1;stroke:none;stroke-width:0.05069799;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+    </clipPath>
+    <clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath5790">
+      <circle
+         r="10"
+         cy="10.000005"
+         cx="9.9999781"
+         id="circle5792"
+         style="opacity:1;fill:#2196f3;fill-opacity:1;stroke:none;stroke-width:0.05069799;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         inkscape:export-filename="/home/juergen/path4136.png"
+         inkscape:export-xdpi="648"
+         inkscape:export-ydpi="648" />
+    </clipPath>
+    <clipPath
+       id="clipPath5055"
+       clipPathUnits="userSpaceOnUse">
+      <circle
+         r="10"
+         cy="10.000005"
+         cx="9.9999781"
+         id="circle5057"
+         style="opacity:1;fill:#2196f3;fill-opacity:1;stroke:none;stroke-width:0.05069799;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         inkscape:export-filename="/home/juergen/path4136.png"
+         inkscape:export-xdpi="648"
+         inkscape:export-ydpi="648" />
+    </clipPath>
+    <clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath5119">
+      <circle
+         r="10"
+         cy="10.000005"
+         cx="9.9999781"
+         id="circle5121"
+         style="opacity:1;fill:#2196f3;fill-opacity:1;stroke:none;stroke-width:0.05069799;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         inkscape:export-filename="/home/juergen/Schreibtisch/path4136.png"
+         inkscape:export-xdpi="648"
+         inkscape:export-ydpi="648" />
+    </clipPath>
+    <clipPath
+       id="clipPath5194"
+       clipPathUnits="userSpaceOnUse">
+      <circle
+         r="10"
+         cy="10.000005"
+         cx="9.9999781"
+         id="circle5196"
+         style="opacity:1;fill:#cddc39;fill-opacity:1;stroke:none;stroke-width:0.05069799;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         inkscape:export-filename="/home/juergen/Schreibtisch/path4136.png"
+         inkscape:export-xdpi="648"
+         inkscape:export-ydpi="648" />
+    </clipPath>
+    <clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath6175">
+      <circle
+         r="10"
+         cy="10.000005"
+         cx="9.9999781"
+         id="circle6177"
+         style="opacity:1;fill:#f44336;fill-opacity:1;stroke:none;stroke-width:0.05069799;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         inkscape:export-filename="/home/juergen/Schreibtisch/path4136.png"
+         inkscape:export-xdpi="648"
+         inkscape:export-ydpi="648" />
+    </clipPath>
+    <clipPath
+       id="clipPath7176"
+       clipPathUnits="userSpaceOnUse">
+      <circle
+         r="10"
+         cy="10.000005"
+         cx="9.9999781"
+         id="circle7178"
+         style="opacity:1;fill:#9c27b0;fill-opacity:1;stroke:none;stroke-width:0.05069799;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         inkscape:export-filename="/home/juergen/Schreibtisch/path4136.png"
+         inkscape:export-xdpi="648"
+         inkscape:export-ydpi="648" />
+    </clipPath>
+    <clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath8165">
+      <circle
+         r="10"
+         cy="10.000005"
+         cx="9.9999781"
+         id="circle8167"
+         style="opacity:1;fill:#ffc107;fill-opacity:1;stroke:none;stroke-width:0.05069799;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         inkscape:export-filename="/home/juergen/Schreibtisch/path4136.png"
+         inkscape:export-xdpi="648"
+         inkscape:export-ydpi="648" />
+    </clipPath>
+    <clipPath
+       id="clipPath9162"
+       clipPathUnits="userSpaceOnUse">
+      <circle
+         r="10"
+         cy="10.000005"
+         cx="9.9999781"
+         id="circle9164"
+         style="opacity:1;fill:#ff9800;fill-opacity:1;stroke:none;stroke-width:0.05069799;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         inkscape:export-filename="/home/juergen/Schreibtisch/path4136.png"
+         inkscape:export-xdpi="648"
+         inkscape:export-ydpi="648" />
+    </clipPath>
+    <clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath5147">
+      <circle
+         r="10"
+         cy="10.000005"
+         cx="9.9999781"
+         id="circle5149"
+         style="opacity:1;fill:#ff9800;fill-opacity:1;stroke:none;stroke-width:0.05069799;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         inkscape:export-filename="/home/juergen/Schreibtisch/path4136.png"
+         inkscape:export-xdpi="648"
+         inkscape:export-ydpi="648" />
+    </clipPath>
+    <clipPath
+       id="clipPath6144"
+       clipPathUnits="userSpaceOnUse">
+      <circle
+         r="10"
+         cy="10.000005"
+         cx="9.9999781"
+         id="circle6146"
+         style="opacity:1;fill:#e91e63;fill-opacity:1;stroke:none;stroke-width:0.05069799;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         inkscape:export-filename="/home/juergen/Schreibtisch/path4136.png"
+         inkscape:export-xdpi="648"
+         inkscape:export-ydpi="648" />
+    </clipPath>
+    <clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath7143">
+      <circle
+         r="10"
+         cy="10.000005"
+         cx="9.9999781"
+         id="circle7145"
+         style="opacity:1;fill:#ffeb3b;fill-opacity:1;stroke:none;stroke-width:0.05069799;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         inkscape:export-filename="/home/juergen/Schreibtisch/path4136.png"
+         inkscape:export-xdpi="648"
+         inkscape:export-ydpi="648" />
+    </clipPath>
+    <clipPath
+       id="clipPath8148"
+       clipPathUnits="userSpaceOnUse">
+      <circle
+         r="10"
+         cy="10.000005"
+         cx="9.9999781"
+         id="circle8150"
+         style="opacity:1;fill:#9c27b0;fill-opacity:1;stroke:none;stroke-width:0.05069799;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         inkscape:export-filename="/home/juergen/Schreibtisch/path4136.png"
+         inkscape:export-xdpi="648"
+         inkscape:export-ydpi="648" />
+    </clipPath>
+    <clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath5332">
+      <circle
+         r="10"
+         cy="10.000005"
+         cx="9.9999781"
+         id="circle5334"
+         style="opacity:1;fill:#f44336;fill-opacity:1;stroke:none;stroke-width:0.05069799;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         inkscape:export-filename="/home/juergen/Schreibtisch/path4136.png"
+         inkscape:export-xdpi="648"
+         inkscape:export-ydpi="648" />
+    </clipPath>
+    <clipPath
+       id="clipPath6341"
+       clipPathUnits="userSpaceOnUse">
+      <circle
+         r="10"
+         cy="10.000005"
+         cx="9.9999781"
+         id="circle6343"
+         style="opacity:1;fill:#4caf50;fill-opacity:1;stroke:none;stroke-width:0.05069799;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         inkscape:export-filename="/home/juergen/Schreibtisch/path4136.png"
+         inkscape:export-xdpi="648"
+         inkscape:export-ydpi="648" />
+    </clipPath>
+    <clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath7354">
+      <circle
+         r="10"
+         cy="10.000005"
+         cx="9.9999781"
+         id="circle7356"
+         style="opacity:1;fill:#f44336;fill-opacity:1;stroke:none;stroke-width:0.05069799;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         inkscape:export-filename="/home/juergen/Schreibtisch/path4136.png"
+         inkscape:export-xdpi="648"
+         inkscape:export-ydpi="648" />
+    </clipPath>
+    <clipPath
+       id="clipPath8344"
+       clipPathUnits="userSpaceOnUse">
+      <circle
+         r="10"
+         cy="10.000005"
+         cx="9.9999781"
+         id="circle8346"
+         style="opacity:1;fill:#673ab7;fill-opacity:1;stroke:none;stroke-width:0.05069799;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         inkscape:export-filename="/home/juergen/Schreibtisch/path4136.png"
+         inkscape:export-xdpi="648"
+         inkscape:export-ydpi="648" />
+    </clipPath>
+    <clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath9436">
+      <circle
+         r="10"
+         cy="10.000005"
+         cx="9.9999781"
+         id="circle9438"
+         style="opacity:1;fill:#9e9e9e;fill-opacity:1;stroke:none;stroke-width:0.05069799;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         inkscape:export-filename="/home/juergen/Schreibtisch/path4136.png"
+         inkscape:export-xdpi="648"
+         inkscape:export-ydpi="648" />
+    </clipPath>
+    <clipPath
+       id="clipPath10432"
+       clipPathUnits="userSpaceOnUse">
+      <circle
+         r="10"
+         cy="10.000005"
+         cx="9.9999781"
+         id="circle10434"
+         style="opacity:1;fill:#795548;fill-opacity:1;stroke:none;stroke-width:0.05069799;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         inkscape:export-filename="/home/juergen/Schreibtisch/path4136.png"
+         inkscape:export-xdpi="648"
+         inkscape:export-ydpi="648" />
+    </clipPath>
+    <clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath11433">
+      <circle
+         r="10"
+         cy="10.000005"
+         cx="9.9999781"
+         id="circle11435"
+         style="opacity:1;fill:#03a9f4;fill-opacity:1;stroke:none;stroke-width:0.05069799;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         inkscape:export-filename="/home/juergen/Schreibtisch/path4136.png"
+         inkscape:export-xdpi="648"
+         inkscape:export-ydpi="648" />
+    </clipPath>
+    <clipPath
+       id="clipPath12452"
+       clipPathUnits="userSpaceOnUse">
+      <circle
+         r="10"
+         cy="10.000005"
+         cx="9.9999781"
+         id="circle12454"
+         style="opacity:1;fill:#ffc107;fill-opacity:1;stroke:none;stroke-width:0.05069799;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         inkscape:export-filename="/home/juergen/Schreibtisch/path4136.png"
+         inkscape:export-xdpi="648"
+         inkscape:export-ydpi="648" />
+    </clipPath>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient13455"
+       id="linearGradient13461"
+       x1="9.9553576"
+       y1="9.9553566"
+       x2="20"
+       y2="20"
+       gradientUnits="userSpaceOnUse" />
+    <clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath13465">
+      <circle
+         r="10"
+         cy="10.000005"
+         cx="9.9999781"
+         id="circle13467"
+         style="opacity:1;fill:#e91e63;fill-opacity:1;stroke:none;stroke-width:0.05069799;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         inkscape:export-filename="/home/juergen/Schreibtisch/path4136.png"
+         inkscape:export-xdpi="648"
+         inkscape:export-ydpi="648" />
+    </clipPath>
+  </defs>
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title>Gadgetbridge Icon</dc:title>
+        <cc:license
+           rdf:resource="http://creativecommons.org/licenses/by-sa/4.0/" />
+        <dc:date>18/07/2017</dc:date>
+        <dc:creator>
+          <cc:Agent>
+            <dc:title>xphnx</dc:title>
+          </cc:Agent>
+        </dc:creator>
+        <dc:rights>
+          <cc:Agent>
+            <dc:title>CC (BY-SA)</dc:title>
+          </cc:Agent>
+        </dc:rights>
+        <dc:description>Launcher icon for Gadgetbridge. Based on Blue Minimal Template (GPLv3)</dc:description>
+        <dc:source>https://github.com/scoute-dich/Blue-Minimal/tree/master/IconTemplates</dc:source>
+        <dc:contributor>
+          <cc:Agent>
+            <dc:title>xphnx based on a work of scoute-dich</dc:title>
+          </cc:Agent>
+        </dc:contributor>
+      </cc:Work>
+      <cc:License
+         rdf:about="http://creativecommons.org/licenses/by-sa/4.0/">
+        <cc:permits
+           rdf:resource="http://creativecommons.org/ns#Reproduction" />
+        <cc:permits
+           rdf:resource="http://creativecommons.org/ns#Distribution" />
+        <cc:requires
+           rdf:resource="http://creativecommons.org/ns#Notice" />
+        <cc:requires
+           rdf:resource="http://creativecommons.org/ns#Attribution" />
+        <cc:permits
+           rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
+        <cc:requires
+           rdf:resource="http://creativecommons.org/ns#ShareAlike" />
+      </cc:License>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Hintergrund"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(1058.1428,-861.93365)">
+    <circle
+       inkscape:export-ydpi="648"
+       inkscape:export-xdpi="648"
+       inkscape:export-filename="/home/juergen/Schreibtisch/path4136.png"
+       style="opacity:1;fill:#0091ea;fill-opacity:1;stroke:none;stroke-width:0.05069799;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       id="path4136"
+       cx="-1048.1428"
+       cy="871.93365"
+       r="10" />
+    <path
+       style="fill:url(#linearGradient13461);fill-opacity:1;stroke-width:0.2;stroke-linejoin:round"
+       d="M 6.134855,7.8575793 C 5.5887444,7.7737975 8.9732,11.36764 8.9732,11.91964 c 0.7720204,2.953627 -0.3190754,2.368775 -1.3911929,4.54893 0,0.314113 2.8336269,3.63544 3.0589809,3.818814 0.04391,0.05518 0.09008,0.103734 0.144531,0.148437 0.04494,0.0568 0.09237,0.106597 0.148438,0.152344 0.04462,0.05589 0.09299,0.10335 0.148437,0.148437 0.0433,0.05399 0.08913,0.10065 0.142578,0.144532 0.04401,0.05535 0.08991,0.103622 0.144532,0.148437 0.04462,0.05589 0.09299,0.10335 0.148437,0.148438 0.04281,0.05303 0.08799,0.09936 0.140625,0.142578 0.0433,0.05399 0.08913,0.100649 0.142578,0.144531 0.0429,0.05343 0.08778,0.101002 0.140625,0.144531 0.04305,0.0535 0.08955,0.09904 0.142578,0.142578 0.04259,0.05283 0.08633,0.09947 0.138672,0.142579 0.04241,0.05266 0.08853,0.09762 0.140625,0.140624 0.04231,0.05246 0.08674,0.09775 0.138672,0.140626 0.04224,0.05233 0.08685,0.09785 0.138672,0.140624 0.04196,0.05198 0.0853,0.09806 0.136719,0.140626 0.04204,0.05191 0.218042,0.230956 0.269531,0.273437 0.04036,0.0496 -0.04904,-0.04097 0,0 0.04207,0.05223 0.08706,0.09788 0.138672,0.140625 0.04161,0.05109 1.939454,1.964844 0.136719,0.136719 0.0402,0.04908 0.673987,-1.491056 0.722706,-1.450484 0.0407,0.05002 0.08329,0.09352 0.132812,0.134766 0.03972,0.04878 -0.511067,1.675676 -0.46294,1.716108 0.04056,0.05008 0.08344,0.09344 0.132812,0.134766 0.04069,0.04997 0.08332,0.09357 0.132813,0.134766 0.03936,0.0479 0.08141,0.08913 0.128906,0.128906 0.03997,0.04887 0.0805,0.09236 0.128907,0.132812 0.0394,0.04798 0.208293,0.219929 0.255859,0.259766 0.03933,0.04803 -0.0475,-0.0399 0,0 0.03941,0.04797 0.08133,0.08909 0.128907,0.128906 0.03805,0.04609 0.07742,0.08645 0.123046,0.125 0.03972,0.04853 0.08085,0.09063 0.128907,0.13086 0.03812,0.04614 0.07734,0.08643 0.123047,0.125 0.03891,0.04709 0.08015,0.08776 0.126953,0.126953 0.03709,0.04468 0.0749,0.08353 0.11914,0.121094 0.03888,0.04724 0.07819,0.08957 0.125,0.128906 0.03698,0.0444 0.0751,0.08376 0.119141,0.121094 0.03744,0.0452 0.07636,0.08512 0.121094,0.123046 0.939453,0.953125 0.07678,0.08344 0.121094,0.121094 0.03702,0.04459 0.075,0.0836 0.11914,0.121094 0.03695,0.04448 0.0751,0.08368 0.119141,0.121094 0.03643,0.04385 0.07387,0.08215 0.117187,0.11914 0.03701,0.04454 0.07503,0.08364 0.119141,0.121094 0.03593,0.0429 0.07267,0.08091 0.115234,0.117188 0.03646,0.04374 0.07386,0.08225 0.117188,0.11914 0.03472,0.04132 0.07043,0.07812 0.111328,0.113281 0.03672,0.04408 0.07545,0.08202 0.119141,0.119141 0.159451,0.01975 0.495139,0.310768 0.33789,0.34375 0.03367,0.03982 0.06799,0.0753 0.107422,0.109375 0.03567,0.04259 0.07302,0.07917 0.115234,0.115234 0.03376,0.03984 0.06789,0.07531 0.107422,0.109375 0.03482,0.04131 0.07033,0.07814 0.111328,0.113282 0.03318,0.03905 0.06673,0.07392 0.105469,0.107422 1.997761,0.694544 9.103709,2.908451 10.44526,2.898372 h 0.002 c 0.275418,0 0.52417,-0.112641 0.705078,-0.292969 C 28.886751,29.72516 29,29.4758 29,29.199219 L 27.879654,21.07464 h -0.002 l -0.002,-0.002 -0.002,-0.002 v -0.002 l -0.0039,-0.002 -0.0039,-0.0059 -0.0039,-0.0039 -0.0059,-0.0059 -0.0059,-0.0059 -0.0078,-0.0078 -0.0078,-0.0078 -0.0078,-0.0078 -0.0098,-0.0098 -0.0098,-0.0098 -0.01172,-0.01172 -0.01172,-0.01172 -0.01172,-0.01172 -0.01367,-0.01367 -0.01367,-0.01367 -0.01367,-0.01563 -0.01562,-0.01563 -0.01563,-0.01563 -0.01758,-0.01563 -0.01758,-0.01953 -0.01758,-0.01758 -0.01953,-0.01953 -0.01953,-0.01953 -0.01953,-0.01953 v -0.002 l -0.02148,-0.02148 -0.02148,-0.02148 -0.02344,-0.02344 -0.02344,-0.02344 -0.02344,-0.02344 -0.02539,-0.02539 -0.02539,-0.02734 -0.02539,-0.02539 -0.02734,-0.02734 -0.02734,-0.0293 -0.0293,-0.0293 -0.0293,-0.0293 -0.03125,-0.0293 -0.0293,-0.03125 -0.03125,-0.0332 -0.0332,-0.0332 -0.0332,-0.0332 -0.0332,-0.0332 -0.03516,-0.03516 -0.03516,-0.03516 -0.03516,-0.03711 -0.03711,-0.03711 -0.03711,-0.03906 -0.03906,-0.03711 -0.03906,-0.04102 -0.03906,-0.03906 -0.04102,-0.04102 -0.04102,-0.04297 -0.04297,-0.04102 -0.04102,-0.04102 v -0.002 -0.002 l -0.04492,-0.04297 -0.04297,-0.04492 -0.04492,-0.04492 -0.04687,-0.04687 -0.04492,-0.04687 -0.04883,-0.04883 -0.04687,-0.04687 -0.04883,-0.05078 -0.04883,-0.04883 -0.05078,-0.05078 -0.05078,-0.05273 -0.05078,-0.05078 -0.05273,-0.05469 -0.05273,-0.05273 -0.05469,-0.05469 -0.05469,-0.05469 -0.05469,-0.05664 -0.05664,-0.05664 -0.05664,-0.05859 -0.05859,-0.05859 -0.05664,-0.05859 -0.06055,-0.05859 -0.05859,-0.06055 -0.06055,-0.0625 -0.0625,-0.0625 -0.06055,-0.0625 -0.0625,-0.0625 -0.06445,-0.06445 -0.06445,-0.06641 -0.06445,-0.06445 -0.06641,-0.06641 -0.06641,-0.06836 -0.06641,-0.06836 -0.06836,-0.06836 -0.06836,-0.07031 -0.07031,-0.07031 -0.07031,-0.07031 -0.07031,-0.07227 -0.07227,-0.07227 -0.07227,-0.07422 -0.07227,-0.07422 -0.07422,-0.07422 -0.07422,-0.07617 -0.07617,-0.07617 -0.07617,-0.07617 -0.07617,-0.07813 -0.07813,-0.07813 -0.07813,-0.08008 -0.07813,-0.08008 -0.08008,-0.08008 -0.08203,-0.08203 -0.08008,-0.08203 -0.08203,-0.08398 -0.08398,-0.08398 -0.08203,-0.08398 -0.08398,-0.08594 -0.08594,-0.08594 -0.08594,-0.08789 -0.08594,-0.08594 -0.08789,-0.08984 -0.08789,-0.08789 -0.08789,-0.08984 -0.08984,-0.0918 -0.08984,-0.0918 -0.0918,-0.0918 -0.0918,-0.0918 -0.0918,-0.09375 -0.09375,-0.0957 -0.09375,-0.0957 -0.09375,-0.0957 -0.0957,-0.0957 -0.09766,-0.09766 -0.0957,-0.09766 -0.09766,-0.09961 -0.09961,-0.09961 -0.09766,-0.09961 -0.09961,-0.101562 L 21.012211,14.117126 20.910649,14.01361 20.809087,13.910094 20.705571,13.806579 20.602055,13.70111 20.49854,13.595641 20.393071,13.488219 20.287602,13.380797 20.18018,13.273376 20.072758,13.164 19.965337,13.054626 19.855961,12.94525 19.851219,12.854848 19.741843,12.74352 19.630515,12.630239 19.519187,12.516958 19.405906,12.403676 19.292625,12.288442 19.179343,12.173208 19.064109,12.05602 18.948875,11.940786 18.831687,11.821645 18.7145,11.704458 18.597312,11.585317 18.480125,11.46813 v -0.0039 l -0.119141,-0.121093 -0.121094,-0.121094 -0.121093,-0.123047 -0.121094,-0.123047 -0.121094,-0.123047 -0.123047,-0.125 -0.125,-0.125 -0.123047,-0.125 -0.125,-0.126953 -0.126953,-0.128906 -0.126953,-0.126953 -0.126953,-0.1289072 -0.126953,-0.126953 v -0.0039 L 16.744052,9.7049408 16.613192,9.5740803 16.484286,9.4432213 16.353427,9.3104083 16.220614,9.1756433 16.087802,9.0408773 15.954989,8.9061124 15.820224,8.7713464 15.685458,8.6346274 15.550692,8.4998624 v -0.0039 l -0.136718,-0.136725 -0.136719,-0.138672 -0.136719,-0.136719 v -0.0039 L 15.001864,7.9393154 14.861239,7.7986902 14.722567,7.6580652 14.581942,7.5174402 v -0.00391 L 14.439364,7.3709558 14.296786,7.2264245 14.154208,7.0818933 v 0 L 14.007009,6.9923893 13.395553,6.3374386 12.445852,5.7116899 11.682337,4.9970274 11.461514,4.8293121 10.294776,3.8349929 C 9.7457728,3.3921062 9.0255387,4.1644713 9.0255387,4.1644713 L 7.8983224,5.0493128 C 7.7279474,5.1830537 9.7681952,6.5418811 9.4189571,6.5258336 9.1783033,6.5147756 7.9346571,5.7850447 7.4996252,5.5958611 6.9046896,5.3371397 6.6803727,7.9412701 6.134855,7.8575793 Z"
+       transform="translate(-1058.1428,861.93365)"
+       id="path12581"
+       clip-path="url(#clipPath13465)"
+       inkscape:connector-curvature="0"
+       sodipodi:nodetypes="sccccccccccccccccccccccccccccccccccccccccccccccccccccccccccsccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccssss" />
+    <path
+       inkscape:connector-curvature="0"
+       id="path12456"
+       d="m -1050.6874,868.40686 0.1339,3.79465 0.2678,0.89285 1.1161,0.75893 1.5179,0.0446 1.6071,-0.40178 0.5357,-1.25 -0.1339,-3.83929 -4.9107,0 z"
+       style="fill:none;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+    <g
+       style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-opacity:0.5098038"
+       id="g6427"
+       transform="matrix(0.10900039,0,0,0.10900039,-1058.4564,854.81407)">
+      <g
+         transform="translate(-0.19198408,15.28344)"
+         id="g5859"
+         style="fill:#ff5000;fill-opacity:1">
+        <g
+           id="g4591"
+           transform="matrix(0.96469159,0,0,0.96469159,3.3476544,3.8272566)">
+          <g
+             style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-opacity:0.5098038"
+             id="g4600"
+             transform="matrix(2.0186569,0,0,2.0186569,394.57822,-108.82609)">
+            <path
+               style="fill:#ffffff;fill-opacity:1;stroke-width:0.26458332"
+               d="m -136.77732,155.22239 -0.52821,-12.65678 c -0.18542,-8.88992 7.31203,-16.43236 9.31124,-23.07234 -2.12299,2.02696 -6.52318,11.59925 -11.09114,10.79579 -2.54776,-0.85577 -3.35187,-2.05496 -4.38456,-4.23069 -3.42275,0.6668 -6.14301,-1.35553 -7.13076,-3.79658 l 0.19658,9.86107 -4.46061,-12.79348 c -3.48621,-1.12814 -6.68377,-6.4807 -3.47994,-5.86765 l 5.75274,1.10079 c 3.93852,0.82896 6.15619,-4.4975 2.63121,-7.26229 -0.7499,-0.53152 -8.27096,-4.04173 -9.68588,-4.44078 -1.26974,-0.3581 -2.78089,0.14533 -3.75024,1.24936 l -5.85918,10.88112 c -0.60845,0.8393 -0.61164,1.71753 -0.25253,2.49043 l 10.72151,21.51532 c 0.34668,0.65416 0.17252,2.20422 0.17823,2.73721 l -1.28045,13.4895 z m 8.9707,-38.19102 c 1.9434,-2.20392 -1.65635,-8.33602 -3.1421,-6.55824 l -9.61026,12.58549 c -2.81396,3.5023 1.43178,6.96399 4.448,4.80543 0.26564,-0.24803 0.41253,-0.37935 0.60397,-0.63064 z m -4.69458,-9.5376 c -1.42924,-1.48182 -4.34931,-3.63245 -4.73609,-3.48403 l -10.71091,14.46057 c -2.88854,2.81736 0.36005,6.17635 3.51209,5.84536 0.43402,-0.12765 0.58795,-0.21146 0.85842,-0.46309 l 11.1656,-14.42916 c 0.4913,-0.6349 0.32485,-1.41907 -0.0891,-1.92965 z m -6.36258,-4.6579 c -0.10642,-0.74107 -4.82207,-4.542483 -5.18836,-4.06928 l -5.44048,7.02851 c 6.87574,7.48663 -1.14912,12.14096 -5.91293,9.41949 0.59683,1.91529 4.16535,3.90113 5.27266,2.44458 z m -6.5656,-5.245518 c -1.03574,-2.192098 -5.14677,-3.858126 -7.00157,-3.15132 l -5.88314,7.251058 7.38969,3.05534 z"
+               id="path4606"
+               inkscape:connector-curvature="0"
+               sodipodi:nodetypes="ccccccccscccccccccccccccccccccscccsccscccccc" />
+          </g>
+          <rect
+             ry="6.5778751"
+             y="175.19803"
+             x="69.227318"
+             height="23.844799"
+             width="52.825401"
+             id="rect4610"
+             style="opacity:1;fill:#ff5000;fill-opacity:1;stroke:none;stroke-width:16.03646088;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.5098038" />
+          <rect
+             ry="4.0110912"
+             y="179.85034"
+             x="82.062675"
+             height="14.540206"
+             width="27.154684"
+             id="rect4610-3"
+             style="opacity:1;fill:#1359ff;fill-opacity:1;stroke:none;stroke-width:8.97837925;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.5098038" />
+        </g>
+      </g>
+    </g>
+  </g>
+  <g
+     style="display:inline"
+     inkscape:groupmode="layer"
+     id="layer2"
+     inkscape:label="Icon">
+    <path
+       d="m 3.9999777,4.0333382 12.0000003,0 0,11.9333328 -12.0000003,0 z"
+       id="path4"
+       inkscape:connector-curvature="0"
+       style="fill:none" />
+    <path
+       style="fill:none"
+       inkscape:connector-curvature="0"
+       id="path9956"
+       d="m 18.999978,19.033339 12,0 0,11.933333 -12,0 z" />
+  </g>
+</svg>
diff --git a/.woodpecker/can_master_build.yml b/.woodpecker/can_master_build.yml
new file mode 100644
index 0000000..fb239d3
--- /dev/null
+++ b/.woodpecker/can_master_build.yml
@@ -0,0 +1,19 @@
+steps:
+  build:
+    image: codeberg.org/freeyourgadget/android-fdroid-tools:33.0.0
+    commands:
+      - pwd #bump
+      - ./gradlew assembleDebug --stacktrace
+
+#when:
+#repo: Freeyourgadget/Gadgetbridge
+#branch: master
+#this doesn't work yet:
+#https://github.com/woodpecker-ci/woodpecker/issues/687
+
+when:
+  # Everything except cron (nightly) and deployment
+  - event: push
+  - event: pull_request
+  - event: tag
+  - event: manual
diff --git a/.woodpecker/nightly.yml b/.woodpecker/nightly.yml
new file mode 100644
index 0000000..2cd24f4
--- /dev/null
+++ b/.woodpecker/nightly.yml
@@ -0,0 +1,140 @@
+clone:
+   git:
+     image: woodpeckerci/plugin-git
+     settings:
+       partial: false
+
+steps:
+  check-if-released:
+    image: codeberg.org/freeyourgadget/android-fdroid-tools:33.0.0
+    secrets: 
+      - hc_url_nightly_deploy
+    commands: |
+      curl -fsS -m 10 --retry 5 "$${HC_URL_NIGHTLY_DEPLOY}/start"
+
+      RELEASED_VERSION_CODE=$(curl https://freeyourgadget.codeberg.page/fdroid/repo/index-v1.json | jq '.packages."nodomain.freeyourgadget.gadgetbridge.nightly"[0].versionCode')
+      CURRENT_VERSION_CODE=$(git rev-list HEAD --count)
+
+      echo $CURRENT_VERSION_CODE
+      echo $RELEASED_VERSION_CODE
+      if [ "$RELEASED_VERSION_CODE" = "$CURRENT_VERSION_CODE" ]
+      then
+        echo "This version is already released, we quit!"
+        touch .do_not_build
+        curl -fsS -m 10 --retry 5 "$${HC_URL_NIGHTLY_DEPLOY}"
+      fi
+
+
+  build-apks:
+    image: codeberg.org/freeyourgadget/android-fdroid-tools:33.0.0
+    commands:
+      - if [ -f .do_not_build ]; then return 0; fi
+      # build the apks
+      - echo "$${SIGNING_KEY}" | base64 -d > app/keystore.p12
+      - ./gradlew assembleMainlineNightly -Dnightly_store_file="keystore.p12" -Dnightly_store_password="$${KEYSTOREPASS}" -Dnightly_key_alias="gadgetbridge" -Dnightly_key_password="$${KEYPASS}"
+      - ./gradlew assembleMainlineNopebble -Dnightly_store_file="keystore.p12" -Dnightly_store_password="$${KEYSTOREPASS}" -Dnightly_key_alias="gadgetbridge" -Dnightly_key_password="$${KEYPASS}"
+      - ./gradlew assembleBanglejsNightly -Dnightly_store_file="keystore.p12" -Dnightly_store_password="$${KEYSTOREPASS}" -Dnightly_key_alias="gadgetbridge" -Dnightly_key_password="$${KEYPASS}"
+    secrets: [ signing_key, keystorepass, keypass ]
+
+  pull-repo-config:
+    image: codeberg.org/freeyourgadget/android-fdroid-tools:33.0.0
+    commands:
+      - if [ -f .do_not_build ]; then return 0; fi
+      - cd ..
+      - pwd
+      - mkdir repoconfig
+      - cd repoconfig
+      - pwd
+      - git clone --depth 1 https://codeberg.org/Freeyourgadget/fdroid-repo-config.git
+      - ./fdroid-repo-config/repoconfig/copy_files_to_repo_config.sh
+    secrets: [ commit_token ]
+
+  pull-pages:
+    image: codeberg.org/freeyourgadget/android-fdroid-tools:33.0.0
+    commands:
+      - if [ -f .do_not_build ]; then return 0; fi
+      # prepare the pages repo, so fdroidclient can push files into it later
+      - cd ..
+      - pwd
+      - mkdir pages
+      - cd pages
+      - pwd
+      - git clone --depth 2 https://codeberg.org/Freeyourgadget/pages.git
+      - cd pages
+      - git config user.name "CODEBERG CI"
+      - git config user.email "noreply@nodomain.nodomain"
+        # reset and remove, later force push, to keep repo size small
+      - git reset --soft HEAD~1
+      - git status
+        #- rm -f fdroid/repo/*apk
+
+  update-fdroid-data:
+    image: codeberg.org/freeyourgadget/android-fdroid-tools:33.0.0
+    commands:
+      - if [ -f .do_not_build ]; then return 0; fi
+      - pwd
+      - ../repoconfig/fdroid-repo-config/repoconfig/prepare_changelog.sh
+      - cd ../repoconfig/fdroid-repo-config/repoconfig
+      # get secrets from storage  
+      - echo "$${SIGNING_KEY}" | base64 -d > keystore.p12
+      - sed -i "s#KEYSTOREPASS_PLACEHOLDER#$${KEYSTOREPASS}#g" config.yml
+      - sed -i "s#KEYPASS_PLACEHOLDER#$${KEYPASS}#g" config.yml
+      - fdroid update
+      - cp ./repo/nodomain.freeyourgadget.gadgetbridge.nightly/en-US/icon.png ./repo/icons/icon.png
+      - python3 repo_listing.py > listing.html
+      - sed -i "s#</body>#LISTING\n</body>#" ./repo/index.html
+      - sed -i -e "/LISTING/r listing.html" -e '//d' ./repo/index.html
+      - fdroid deploy
+      - ls -laR repo
+    secrets: [ signing_key, keystorepass, keypass ]
+
+  push-pages-update-repo:
+    image: codeberg.org/freeyourgadget/android-fdroid-tools:33.0.0
+    commands:
+      - if [ -f .do_not_build ]; then return 0; fi
+      - cd ../pages/pages/
+      - git add -A
+      - git status
+      - git diff
+      - ls -lsR fdroid/repo
+      - git commit -m "autodeploy, $${DRONE_COMMIT_MESSAGE}"
+      - git remote remove origin
+      - git remote add origin https://"$${COMMIT_TOKEN}"@codeberg.org/Freeyourgadget/pages.git
+        # force push as we removed all data
+      - git push -f origin master
+      - cd ../../
+      - pwd
+      - ls -la
+      - ./repoconfig/fdroid-repo-config/repoconfig/hyperlink.sh https://freeyourgadget.codeberg.page/fdroid/repo/
+        # bump
+    secrets: [ commit_token ]
+
+  on-success:
+    image: codeberg.org/freeyourgadget/android-fdroid-tools:33.0.0
+    commands:
+      - curl -fsS -m 10 --retry 5 "$${HC_URL_NIGHTLY_DEPLOY}"
+    when:
+      status: success
+    secrets: 
+      - hc_url_nightly_deploy
+
+  on-error:
+    image: codeberg.org/freeyourgadget/android-fdroid-tools:33.0.0
+    commands:
+      - curl -fsS -m 10 --retry 5 "$${HC_URL_NIGHTLY_DEPLOY}/fail"
+      - cd ..
+      - mkdir -p repoconfig
+      - cd repoconfig
+      - git clone --depth 1 https://codeberg.org/Freeyourgadget/fdroid-repo-config.git || true
+      - ./fdroid-repo-config/repoconfig/create_issue_comment.sh "CI failed"
+    when:
+      status: failure
+    secrets: 
+      - hc_url_nightly_deploy
+      - commit_token
+
+when:
+  - event: cron
+  - event: deployment
+    branch: master
+    evaluate: 'CI_PIPELINE_DEPLOY_TARGET == "nightly"'
diff --git a/.woodpecker/run_lint.yml b/.woodpecker/run_lint.yml
new file mode 100644
index 0000000..83a6501
--- /dev/null
+++ b/.woodpecker/run_lint.yml
@@ -0,0 +1,19 @@
+steps:
+  lint:
+    image: codeberg.org/freeyourgadget/android-fdroid-tools:33.0.0
+    commands:
+      - pwd #bump
+      - ./gradlew lint
+
+#when:
+#repo: Freeyourgadget/Gadgetbridge
+#branch: master
+#this doesn't work yet:
+#https://github.com/woodpecker-ci/woodpecker/issues/687
+
+when:
+  # Everything except cron (nightly) and deployment
+  - event: push
+  - event: pull_request
+  - event: tag
+  - event: manual
diff --git a/.woodpecker/run_tests.yml b/.woodpecker/run_tests.yml
new file mode 100644
index 0000000..cf5717f
--- /dev/null
+++ b/.woodpecker/run_tests.yml
@@ -0,0 +1,19 @@
+steps:
+  tests:
+    image: codeberg.org/freeyourgadget/android-fdroid-tools:33.0.0
+    commands:
+      - pwd #bump
+      - ./gradlew :app:testBangleDebugUnitTest :app:testMainDebugUnitTest
+
+#when:
+#repo: Freeyourgadget/Gadgetbridge
+#branch: master
+#this doesn't work yet:
+#https://github.com/woodpecker-ci/woodpecker/issues/687
+
+when:
+  # Everything except cron (nightly) and deployment
+  - event: push
+  - event: pull_request
+  - event: tag
+  - event: manual
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..bc6582c
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,2480 @@
+### Changelog
+
+#### 0.83.0
+* Add first start onboarding screen
+* Initial support for Bowers and Wilkins P Series
+* Initial support for Casio ECB-S100
+* Initial support for Colmi R09
+* Initial support for Freebuds 5i
+* Initial support for Garmin Fenix 6S Pro / 7X, Forerunner 55 / 235 / 620, Instinct 2
+* Initial support for Huawei Band 2 / 2 Pro / 3 Pro
+* Initial support for Oppo Enco Air / Air2
+* Initial support for Realme Buds T110
+* Initial support for Redmi Buds 5 Pro
+* Initial support for Xiaomi Smart Band 9 Pro
+* Initial support for Marstek B2500
+* Add calories charts and widgets
+* Add more workout icons
+* About screen: Copy build details on tap
+* Amazfit Bip 3 Pro: Fix title and sender on some notifications
+* Bangle.js: Fix calendar sync
+* Bangle.js: Fix call notification in Turkish locale
+* Bangle.js: Support sending activity type from Bangle
+* Casio GBX-100: Fix notification title
+* Charts: Display HR measurement gaps
+* Colmi R0x: Fix occasional crash on disconnection
+* Fix crash in some chart pages
+* Fix heart rate charts average and maximum value
+* Fix imperial unit on steps charts
+* Fix notifications after a notification is received with a time in the future
+* Fossil/Skagen Hybrids: Add SpO2 support
+* Fossil/Skagen Hybrids: Fix erroneous watchface downgrade
+* Garmin: Add intensity minutes, respiratory rate, sleep score
+* Garmin: Display AGPS age
+* Garmin: Fix weather temperature conversion to celsius
+* Garmin: Persist sleep score and metabolic rate
+* Garmin: Send notification pictures
+* Huawei Band 3 pro: Fix notifications
+* Huawei: Add HR zones configuration for non-P2P devices
+* Huawei: Add temperature chart
+* Huawei: Allow more languages to be set on the watch
+* Huawei: Display active calories
+* Huawei: Display high-resolution heart rate
+* Huawei: Fix watchface upload for some watches
+* Huawei: Improve activity parsing
+* Huawei: Music management
+* Huawei: Send weather error if there is no data
+* Huawei: Sync blood pressure if supported
+* Huawei: Sync skin temperature
+* Restore sleep balance on weekly and monthly charts
+* Test device: Add dummy activities
+* Workout page: Add colors to HR zones
+* Xiaomi Smart Band 8 Active: Fix freestyle and walking workout parsing 
+* Xiaomi Smart Band 9: Fix outdoor cycling parsing
+* Xiaomi SPPv2: Fix message processing getting stuck after exception
+* Xiaomi-protobuf: Add resting heart rate
+* Xiaomi-protobuf: Fix activity sync stuck on duplicated or invalid files
+* Xiaomi-protobuf: Improve workout parsing
+* Xiaomi-protobuf: Persist RR intervals during sleep
+* Zepp OS: Add sleep respiratory rate chart
+* Zepp OS: Send notification pictures
+
+#### 0.82.1
+* Huawei: Improve activity parsing
+* Huawei Watch GT: Fix connection failure
+* Withings: Fix crash on connection
+* Improve Armenian transliterator for mixed-case words
+
+#### 0.82.0
+* Initial support for Anker Soundcore Liberty 4 NC
+* Initial support for CMF Buds Pro 2 / Watch Pro 2
+* Initial support for Colmi R02/R03/R06/R10 smart rings
+* Initial support for Garmin Enduro 3, Fenix 5/5 Plus/5X Plus/6/6S Sapphire/7/8, Forerunner 165/255/255S Music/245 Music/265S/955/965, Venu/Venu Sq/Venu Sq 2/Venu 2S, Vivoactive 3, Vivomove Trend, Vivosport
+* Initial support for Huawei Watch 3 / 3 Pro / 4 Pro / D2 / GT 3 SE / GT 5 / GT 5 Pro / GT Cyber / GT Runner
+* Initial support for Honor Watch GS 3 / Watch GS Pro
+* Initial support for IKEA desk controller
+* Initial support for Moondrop Space Travel
+* Initial support for Mijia XMWSDJ04MMC
+* Initial support for Mi Smart Scale 2
+* Initial support for Sony WF-C500 / WF-C700N
+* Initial support for Soundcore Motion 300
+* Initial support for Vivitar HR & BP Monitor Activity Tracker
+* Experimental support for Amazfit T-Rex 3
+* Experimental support for Redmi Watch 5 Active
+* Experimental support for Xiaomi Smart Band 9
+* Experimental support for Xiaomi Watch S3
+* Add all languages supported in weblate
+* Add BLE intent API
+* Add configuration for calendar lookahead
+* Add month and day to date of birth
+* Add more activity types (CMF, Garmin, Huawei, Zepp OS)
+* Allow configuration of notification times
+* Allow syncing birthdays with calendar events
+* Amazfit GTR 2: Enable PAI support
+* AsteroidOS: Fix missing weather day and set-time on connection
+* Bangle.js: Add canned responses
+* Bangle.js: Fix calendar sync setting
+* Bangle.js: Fix distance in activity details
+* Bluetooth Intent API: Add disconnect action
+* Casio GW-B5600: Alarms, find phone, reminders, watch settings
+* Casio: Fix notifications on long messages
+* Change device icons to use theme colors
+* Charts: Add button to pick date
+* Charts: Add charts for HRV, body energy, heart rate, steps, VO2 max, weight
+* Charts: Fix heart rate charts when min is set to 0
+* Charts: Re-design sleep, stress, PAI, workout details
+* Charts: Use HR from workout track file if available
+* CMF Watch Pro 2: Negotiate authentication key
+* CMF Watch Pro: Fix activity transfer
+* Cycling sensor: added live data view
+* Cycling sensor: Improve cycling data display
+* Dashboard: Add new widgets for stress, HRV, body energy
+* Dashboard: Add option to show yesterday's data in Today widget
+* Dashboard: Improve widget gauge resolution
+* Data Management: Add file manager
+* Data Management: Allow browse folders, open and share files
+* Data Management: Allow full backup/restore from a zip file
+* Data Management: Fix import of some preference from a backup
+* Fix activity charts generation for devices that do not report intensity
+* Fix crash when companion pairing
+* Fix discovery of connected devices
+* Fix emoji when some connected device does not support them
+* Fix language not being respected in some situations
+* Fix media controls not working for some apps
+* Fix notification text not being sent for some apps
+* Fix reconnection when device connects back during BLE scan
+* Fossil HR: Fix crash on disconnection
+* Fossil HR: Minor watchface fixes
+* Garmin: Display awake time during sleep
+* Garmin: Display HRV and body energy
+* Garmin: Display resting heart rate
+* Garmin: Fetch SKIP_TEMP files
+* Garmin: Fix agps upload for some URLs
+* Garmin: Fix all-day events
+* Garmin: Fix auto-activity fetch on some devices
+* Garmin: Fix canned replies reset to defaults
+* Garmin: Fix crash on call with privacy mode on
+* Garmin: Fix crash on timezones without DST
+* Garmin: Fix daily weather missing current day
+* Garmin: Fix weather temperature and speed units
+* Garmin: Improve activity, sleep and workout parsing
+* Garmin: Infer sleep time for devices that do not send sleep stages
+* Garmin: Manual HR measurements and live activity
+* Garmin: Map some unknown realtime settings
+* Garmin: Parse workout physiological metrics, strength training workout sets
+* Garmin: Re-parse workout summary when opening details page
+* Garmin: Upload gpx and workout fit files to watch
+* Garmin: Use distance and calories provided by the watch
+* Garmin: View and share gpx files
+* Huami: Fetch workouts during normal sync
+* Huami: Migrate all device settings to sub-screens
+* Huawei Band 9: Improved support
+* Huawei: Add battery polling
+* Huawei: Basic support for the installation of the applications
+* Huawei: Calendar sync support
+* Huawei: Contacts uploading support
+* Huawei: Continuous skin temperature measurement switch
+* Huawei: Enable emoji for HarmonyOS watches
+* Huawei: Fix crash when notification has no text
+* Huawei: Fix initialization issues on some watches
+* Huawei: Fix notifications for Huawei Band 4e
+* Huawei: Fix some reconnection issues
+* Huawei: Fix watchface upload, activity sync, event alarms, weather for some devices
+* Huawei: Fix workout altitude, pace, workout re-parsing
+* Huawei: Improve device initialization
+* Huawei: Improve watchface install support
+* Huawei: Improve weather support
+* Huawei: Initial ephemeris update support
+* Huawei: Map more workout types
+* Huawei: Music upload support
+* Huawei: Provide an activity sample every minute
+* Huawei: Re-parse workout details when opening details
+* Huawei: Send default HR zones
+* Huawei: Workout GPS synchronization
+* Huawei: Simple TruSleep support
+* Huawei: Use distance and calories provided by the watch
+* Improve calendar change detection
+* Mi Band: Migrate global preferences to device-specific
+* Mi Composition Scale: Add alternative bluetooth name
+* Mi Composition Scale: Persist and display weight samples
+* Mijia LYWSD/XMWSDJ: Add comfort level preference
+* Pebble: Migrate global preferences to device-specific
+* Redmi Smart Band Pro: Fix crash on connection and activity sync issues
+* Sony Headphones: Enable read aloud incoming notifications and auto call pickup
+* UI: Add new activity icons
+* UI: Fix changelog on device rotation
+* UI: Fix HR samples displayed on wrong device
+* UI: Fix light navbar buttons on light themes for Android 8+
+* UI: Fix pull-down to refresh for some devices
+* UI: Improvements for large screen resolutions, font sizes, landscape
+* UI: Reduce stutters on device changes / data fetch / scrolling
+* UI: Refactor preferences screen
+* UM25C: Fix some disconnection issues
+* Use default system TTS language
+* Xiaomi Protobuf: Allow re-parse activity from storage
+* Xiaomi Protobuf: Enable watchface upload for all devices
+* Xiaomi Protobuf: Show watchface preview
+* Xiaomi Protobuf: Fix watchface install on some watches
+* Xiaomi Protobuf: Fix deleting first widget screen
+* Xiaomi Protobuf: Fix naps
+* Xiaomi Protobuf: Improve workout parsing for some devices
+* Zepp OS 3: Fix file transfer (notification icons, gpx upload, agps updates)
+* Zepp OS 3.5 / 4: Fix shortcuts, shortcut cards, menu items
+* Zepp OS: Add VO2 Max support
+* Zepp OS: Display resting heart rate
+* Zepp OS: Fix reminder creation in some cases
+* ZeTime: Migrate global preferences to device-specific
+
+#### 0.81.0
+* Experimental support for Amazfit Bip 5 Unity
+* Experimental support for Redmi Watch 4
+* Initial support for cycling sensor
+* Initial support for more Garmin watches
+* Initial support for Hama Fit6900
+* Initial support for Huawei Watch Fit 2, Watch Fit 3, Watch 4 Pro
+* Initial support for Soundcore Liberty 3 Pro
+* Introduce new Dashboard view
+* AsteroidOS: Added icons to the notifications
+* Bangle.js: Add Sleep as Android support
+* Bangle.js: Add screenshot support
+* Bangle.js: Add setting to disable notifications
+* Bangle.js: Allow wake phone when opening notification response from watch
+* Bangle.js: Fix activity intensity normalization
+* Bangle.js: Fix message reply
+* Bangle.js: Improve text rendering
+* Fossil/Skagen Hybrids: Update device settings to new structure
+* Galaxy Buds Live: Update device settings to new structure
+* Galaxy Buds 2: Fix recognition of some versions
+* HPlus: Migrate global preferences to device-specific
+* Huami: Fix reminder message encoding
+* Huawei: Add cycling workout type
+* Huawei: Add enable HeartRate and SpO2 force option
+* Huawei: Add huawei account support (pair without resetting watch)
+* Huawei: Add support for workout calories and cycling power
+* Huawei: Add remote camera shutter
+* Huawei: Ask pincode only on first connection
+* Huawei: Enable sleep detection
+* Huawei: File upload and watchface management
+* Huawei: Fix force DND support
+* Huawei: Fix long notification
+* Huawei: Fix TimeZone offset calculation
+* Huawei: Improve connection and reconnection
+* Huawei: Improve music controls
+* Huawei: Improve notification icons
+* Huawei: Improve weather, HR and SpO2 support
+* Huawei: Improve workout parsing
+* Huawei: Rework settings menu with sub-screens
+* Huawei: Send user info to device
+* Huawei: Support sending GPS to band
+* Huawei Watch GT4: Add HR and SpO support
+* Huawei Watch Ultimate: Add HR and SpO support
+* Intent API: Add broadcast on activity sync finish
+* Intent API: Added debug end call
+* Mi Band 6: Add menu items for NFC shortcuts
+* Nothing CMF Watch Pro: Add weather support
+* Nothing Earbuds: Add adjustable delay for auto-pick-up of calls
+* Nothing Earbuds: Add option to auto-reply to incoming phone calls
+* Nothing Earbuds: Add option to read aloud incoming notifications
+* Sony LinkBuds S: Enable some missing features
+* Xiaomi Smart Band 8 Active: Fix discovery
+* Xiaomi: Add swimming workout type
+* Xiaomi: Allow transliteration
+* Xiaomi: Fix barometer
+* Xiaomi: Fix notification for apps in work profile
+* Xiaomi: Fix some crashes
+* Xiaomi: Improve reconnection
+* Xiaomi: Improve sleep and activity parsing
+* Xiaomi: Improve weather support, add multiple locations
+* Xiaomi: Sync calendar event reminders
+* Zepp OS: Add support for Sleep as Android
+* Zepp OS: Sync calendar event reminders
+* Add Armenian and Serbian transliterators
+* Add GENERIC_PHONE and GENERIC_CALENDAR NotificationType handling
+* Add support for scannable-only devices
+* Fix crash when connecting on some phones
+* Fix crash when enabling bluetooth
+* Fix receiving shared gpx files
+* Fix text cutoff on all checkbox preferences
+* Format pace as mm:ss
+* Make battery threshold notifications configurable
+* Prevent some bluetooth events from starting Gadgetbridge
+* Recognize "Delta Chat" as generic chat
+* Remove deprecated general auto-reconnect preference
+* Refactor location service
+* Set navbar color to match theme
+* Simplify pairing of bonded and companion devices
+
+#### 0.80.0
+* Initial support for Amazfit Bip 3
+* Initial support for Huawei Band 8
+* Initial support for Huawei Watch GT 4
+* Initial support for Huawei Watch Ultimate
+* Initial support for Sony LinkBuds
+* Initial support for Xiaomi Smart Band 8 Active
+* Bangle.js: Allow saving files on phone from watch
+* Bangle.js: Fix crash when file save is cancelled
+* Bangle.js: Set filename on save file dialogs
+* Bangle.js: Improve communication stability
+* Bangle.js: Sync activity tracks
+* Bangle.js: remove unwanted charaters from calendar events
+* Femometer Vinca II: Add temperature charts
+* Fossil/Skagen Hybrids: Remove activity fetching toasts and add finished signal
+* Fossil/Skagen Hybrids: Use steps instead of calories for activity intensity
+* Fossil/Skagen Hybrids: Mark device busy and show transfer notification while syncing
+* Huami/Zepp OS: Fix activity sync getting stuck sometimes
+* Mi Band 1/2: Fix vibration settings preference screens
+* Huawei: Add cycling workout type 
+* Huawei: Add smart wakeup interval
+* Pebble: Fix pairing with LE counterpart
+* Xiaomi Watch S1 Pro: Add temperature charts
+* Xiaomi: Fix sleep sync failing when sleep stages are not found
+* Xiaomi: Improve activity sync
+* Nothing CMF Watch Pro: Fix music playback status
+* Allow for device settings sub-screens
+* Device connection: Add support for scan before BLE connection
+* Misc UI improvements (alarms, chart settings)
+
+#### 0.79.1
+* Initial support for Huawei Watch Fit
+* Initial support for Xiaomi Redmi Watch 3
+* Fossil/Skagen Hybrids: Fix crash on multi-byte unicode characters in menu
+* Huawei: Add weather support
+* Bangle.js: Support higher MTU
+* Test Device: Add fake features and data
+* Periodically (around every 2 days) synchronize time on connected devices
+* Set alarm as used and enabled if time has changed
+
+#### 0.79.0
+* Initial support for Honor Magic Watch 2
+* Initial support for Mijia MHO-C303
+* Initial support for Nothing CMF Watch Pro
+* Initial support for Sony WI-SP600N
+* Experimental support for Redmi Watch 2
+* Experimental support for Xiaomi Smart Band 8 Pro
+* Experimental support for Xiaomi Watch S1 Pro
+* Experimental support for Xiaomi Watch S1
+* Experimental support for Xiaomi Watch S3
+* Galaxy Buds2 Pro: Fix recognition of some versions
+* Huawei Watch GT 2: Fix pairing
+* Redmi Smart Band Pro: Fix password digits
+* Pebble: Fix app configuration page
+* Pebble 2: Fix pairing issue
+* PineTime: Fix weather forecast on InfiniTime's new simple weather
+* Xiaomi: Fix sleep sometimes extending past the wakeup time
+* Xiaomi: Request battery level and charging state periodically
+* Xiaomi: Fix sleep stage parsing for some devices
+* Zepp OS: Improve device discovery
+* Zepp OS: Fix weather not working on some devices
+* Zepp OS: Prevent crash when installing large firmware updates
+* Fix sport activity summary group order
+* Fix reconnection to devices failing occasionally
+
+#### 0.78.0
+* Initial support for Honor Band 3,4,5,6
+* Initial support for Huawei Band 4, 4 Pro, 6, 7, 3e, 4e
+* Initial support for Huawei Talk Band B6
+* Initial support for Huawei Watch GT, GT 2
+* Initial support for Mijia LYWSD03MMC
+* Initial support for Nothing Ear (2)
+* Initial support for Nothing Ear (Stick)
+* Experimental support for Honor Band 7
+* Experimental support for Redmi Watch 2 Lite
+* Experimental support for Redmi Smart Band Pro
+* Casio GBX100: Add support for snooze alarm
+* Fossil/Skagen Hybrids: Update navigationApp to 1.1
+* Huami: Fetch SpO2 on devices that support it
+* Pebble: Attempt to fix app configuration webview
+* PineTime: Add support for InfiniTime's new simple weather
+* PineTime: Fix freeze and reboot when upgrading firmware
+* Pixoo: Enable sending images (non-persistent)
+* Pixoo: Get and send alarms
+* Pixoo: Set custom device name
+* Pixoo: support "clap hands to turn off screen" and "sleep after silence" settings
+* Xiaomi: Improve activity and workout parsing
+* Xiaomi: Improve stability and fix some crashes
+* Xiaomi: Improve weather
+* Xiaomi: Parse sleep stages
+* Add a notifications channel for connection status notifications
+* Improve automatic connection to all or previous devices
+* Fix devices sometimes staying stuck in a "Connecting" state
+* Map some missing Google Maps navigation actions
+
+#### 0.77.0
+* Initial support for Amazfit Balance
+* Initial support for Amazfit Active
+* Initial support for ColaCao 2021
+* Initial support for ColaCao 2023
+* Initial support for Femometer Vinca II
+* Initial support for Mijia LYWSD02MMC variant
+* Initial support for Sony Wena 3
+* Experimental support for Divoom Pixoo
+* Experimental support for Sony WF-1000XM5
+* Experimental support for Amazfit Active Edge
+* Experimental support for Mi Band 7 Pro (Xiaomi Smart Band 7 Pro)
+* Experimental support for Mi Band 8 (Xiaomi Smart Band 8)
+* Experimental support for Mi Watch Lite
+* Experimental support for Mi Watch Color Sport
+* Experimental support for Redmi Smart Band 2
+* Experimental support for Redmi Watch 3 Active
+* Experimental support for Xiaomi Watch S1 Active
+* Amazfit Band 7: Add alexa menu entries
+* Amazfit GTR 3 Pro: Fix firmware and watchface upload
+* Amazfit T-Rex: Fix activity summary parsing
+* Amazfit T-Rex Pro: Add activate display on lift sensitivity
+* AsteroidOS: Add more supported watch models
+* AsteroidOS: Fix media info
+* AsteroidOS: Fix notification dismissal
+* Bangle.js: Add loyalty cards integration with Catima
+* Bangle.js: Ensure SMS messages have src field set to "SMS Message"
+* Bangle.js: Fix GPS speed
+* Bangle.js: Improve handling of chinese characters
+* Bangle.js: Lower threshold for low battery warning
+* Bangle.js: Recover from device initialization failure
+* Casio GBX100/GBD-200: Fix first connect
+* Casio GB5600/6900/STB-1000: Fix pairing
+* Casio GDB-200: Fix notification timestamp
+* Casio GDB-200: Fixed notification categories and default category
+* Casio GDB-200: Allow preview of notification message alongside title
+* Casio GDB-200: Fixed find my phone feature
+* Intent API: Add debug action for test new function
+* Fossil/Skagen Hybrids: Add new navigation app
+* Fossil/Skagen Hybrids: Allow configuring call rejection method
+* Fossil/Skagen Hybrids: Fix some preference crashes on the nightly
+* Fossil/Skagen Hybrids: Reduce toasts on release builds
+* Fossil/Skagen Hybrids: Show device specific settings in more logical order
+* Huami: Toggle phone silent mode from band
+* Message privacy: Add mode Hide only body
+* Mijia LYWSD02: Add battery
+* Mijia LYWSD02: Add low battery notification
+* Mijia LYWSD02: Set temperature unit
+* Mijia LYWSD02: Fix battery drain while connected
+* PineTime: Display app name for VoIP app calls
+* PineTime: Honor Sync time setting on connect
+* PineTime: Improve notification handling
+* PineTime: Reduce weather memory usage
+* Withings Steel HR: Fix crash when calibrating hands on the nightly
+* Zepp OS: Add blood oxygen graph
+* Zepp OS: Add workout codes for hiking and outdoor swimming
+* Zepp OS: Allow disabling app notifications per device
+* Zepp OS: Attempt to fix activity fetch operation getting stuck
+* Zepp OS: Display swimming activity data
+* Zepp OS: Fix health settings on older Zepp OS versions
+* Zepp OS: Fix setting of unknown button press apps
+* Zepp OS: Fix sunrise and moon dates being off by local time + UTC offset
+* Zepp OS: Map hiking, outdoor swimming, climbing and table tennis activity types
+* Zepp OS: Toggle phone silent mode from band
+* Add transliteration for Latvian, Hungarian, Common Symbols
+* Allow multiple device actions to be triggered for the same event
+* Allow toggling DND through device actions
+* Autodetect OsmAnd package name and make it configurable
+* Improve ASCII transliterator
+* Make GMaps navigation handler follow the "navigation forwarding" setting
+* Support selecting enabled navigation apps
+* Allow ignore notifications from work profile apps
+* Display alias in low battery notification
+* Fix crash when pairing current device as companion
+* Fix emoji when a transliterator is enabled
+* Fix UV Index and rain probability for some weather apps
+* Improve device discovery stability and fix freezes
+* Improve Telegram and COL Reminder notifications
+* Replace old-style preference switch with Material 3 switch
+
+#### 0.76.1
+* Amazfit GTR Mini: Mark as not experimental
+* Bangle.js: Improve file downloads
+* Bangle.js: Fix app interfaces
+* Allow text to be shared to devices
+* Fix connection to some Amazfit devices 
+
+#### 0.76.0
+* Upgrade UI to Material 3 and add dynamic colors theme
+* Initial support for Amazfit Bip 3 Pro
+* Initial support for Amazfit Cheetah Pro
+* Initial support for Bohemic Smart Bracelet
+* Initial support for Casio GW-B5600
+* Initial support for Garmin Vivomove HR
+* Initial support for Withings Steel HR
+* Experimental support for Amazfit Bip 5
+* Experimental support for Amazfit Falcon
+* Experimental support for Amazfit GTR Mini
+* Experimental support for Amazfit Cheetah (Round/Square)
+* Experimental support for Amazfit T-Rex Ultra
+* Amazfit GTS 2e: Add activate display on lift sensitivity
+* Amazfit GTR 3 / GTS 3: Enable AGPS Updates
+* Amazfit Neo: Enable PAI support
+* Bangle.js: Allow enable/disable of alarms
+* Bangle.js: Fetch activity data
+* Bangle.js: Fix GB integration when watch is not programmable
+* Bangle.js: Put JSON keys in quotes
+* Bangle.js: Reorganize device settings
+* Fossil/Skagen Hybrids: Embed custom menu in watchface, fixes lost menu on reset
+* Fossil/Skagen Hybrids: Fix unused alarms being pushed to the device
+* Fossil/Skagen Hybrids: Strip unicode characters that the watch can't display
+* Fossil/Skagen Hybrids: Sunrise/sunset follows weather location
+* Huami/Zepp OS: Add PAI charts
+* Huami/Zepp OS: Improve music info stability
+* Huami/Zepp OS: Improve reconnection and device initialization
+* Huami: Persist workout raw details even if gpx has no points
+* InfiniTime: Add heart rate measurement support
+* Mi Band 5: Fix activity fetch error toast when stress monitoring is enabled
+* Mi Band 6: Enable PAI and stress support
+* LeFun: Fix heart rate popup when measurement is triggered from phone
+* Sony WH-1000XM3/WF-SP800N: Add volume setting
+* Sony WH-1000XM5: Fix speak-to-chat enable/disable
+* Zepp OS: Add loyalty cards integration with Catima
+* Zepp OS: Enable AGPS updates for all devices
+* Zepp OS: Fix calendar sync on Zepp OS 2, send event location
+* Zepp OS: Fix reminder creation
+* Zepp OS: Fix shortcut cards setting on Zepp OS 2.1
+* Zepp OS: Fix weather, add hourly information
+* Zepp OS: Map barcode types for ITF, PDF_417 and DATA_MATRIX
+* Add preference to display changelog on startup
+* Add Termux RUN_COMMAND permission
+* Allow filtering notifications from work profile apps
+* Fix daylight saving time not being transmitted to the watch
+* Fix media button control for some applications
+* Fix notification filters by title if notification does not contain a body
+* Fix opening screenshots from notification on external apps
+* Fix reconnect delay reset after all devices are initialized
+* Fix some security error crashes when permissions are not granted
+* Fix transliteration of emoji
+* Fix transliteration of non-ASCII accented characters
+* Force gps speed calculation on some phones that do not report it correctly
+* Make application list sorting case-insensitive
+* Introduce native app shortcuts for android > 30
+* Update device settings action bar title depending on current screen
+
+#### 0.75.1
+* Fix Weather Notification integration
+
+#### 0.75.0
+* Bangle.js: Add message size limitation to Calendar and Messages
+* Bangle.js: Add switch to control if the GPS chip should be used to locate the location
+* Bangle.js: Send more weather data to watch
+* Bangle.js: Allow an activity sample to have a timestamp
+* Bangle.js: Send last received activity timestamp on connect (to allow sync of activity samples)
+* Bangle.js: Allow connecting HW keyboard without closing app loader
+* Bangle.js: Bump flavor target SDK version to 31
+* Bangle.js: Fix convertion of emoji/unicode to bitmap without width/height
+* Bangle.js: Fix location listener not being cleaned up when waiting for reconnect
+* Bangle.js: Fix memory leak from HTTP requests
+* Bangle.js: Fix orientation changes closing app loader
+* Bangle.js: Fix return to applications management activity after having opened another window
+* Bangle.js: Set default value for GPS event interval to 1 second
+* Bangle.js: Support navigation instructions
+* Bangle.js: Escape characters that fall in the Unicode codepoint area (for Espruino ~2v18.20 and later)
+* Bangle.js: HTTP request XPath can now return Arrays
+* Fossil/Skagen Hybrids: Add support for ultraviolet index and rain probability
+* Fossil/Skagen Hybrids: Add UV index and chance of rain widgets
+* Fossil/Skagen Hybrids: Allow launching the calibration activity on any Gadgetbridge variant
+* Fossil/Skagen Hybrids: Increase accuracy of workout distance calculation
+* Fossil/Skagen Hybrids: Fix weather icons day/night status
+* InfiniTime: Fix weather expiry time
+* InfiniTime: Support navigation instructions
+* Mi Band 6: Allow making device discoverable via Bluetooth when connected
+* Mi Band 7: Add preference to display call contact information
+* Zepp OS: Add gpx route file upload
+* Zepp OS: Add screenshot support
+* Zepp OS: Add stress charts
+* Zepp OS: Add watch app logs developer option
+* Zepp OS: Display watchface and app preview on install
+* Zepp OS: Fix update operations on Zepp OS 2.1+
+* Zepp OS: Manage contacts on watch
+* Zepp OS: Start new GPX segments on pause/resume
+* Zepp OS: Support flashing zab files
+* App Manager: Fix cached apps sorting
+* App Manager: Hide drag handle if app reorder is not supported
+* App Manager: Add confirmation before deleting app
+* Add menus to share GPX, raw summary, raw details
+* Debug Activity: Allow pairing current device as companion
+* Fix some null pointer exception crashes
+* Intent API: Add command to set device mac address
+* Intent API: Add dataTypes parameter for activity sync
+* Intent API: Add debug actions for notifications and incoming calls
+* OsmAnd: Add support for navigation instructions
+* Scrape navigation instructions from Google Maps notifications
+* Fix lag when a folder has a lot of devices
+* Fix transliteration returning non-ASCII characters
+* Enable "allow high MTU" setting by default
+* Make some hardcoded english strings translatable
+
+#### 0.74.0
+* Initial support for Amazfit GTR 3 Pro
+* Initial support for Sony WH-1000XM5
+* Amazfit Bip U: Remove alarm snooze option
+* Amazfit GTR 4 / GTS 4: Add watch Wi-Fi Hotspot and FTP Server
+* Amazfit GTR 4 / GTS 4: Perform and receive phone calls on watch
+* Amazfit GTS 2 Mini: Add missing alexa menu item
+* Bangle.js: Fix updating timezone in settings.json if the timezone is zero
+* Fossil/Skagen Hybrids: Pair watch to phone, fixes repeating confirmation request
+* Huami: Implement repeated activity fetching
+* Sony WH-1000XM4: Add speak-to-chat
+* Sony Headphones: Add button modes help
+* Zepp OS: Add shortcut cards preference
+* Zepp OS: Add support for morning updates
+* Zepp OS: Add preference to keep screen on during workout
+* Zepp OS: Add preference for camera remote
+* Zepp OS: Fix activate display upon lift wrist smart mode
+* Zepp OS: Fix Cards and MI AI display item and shortcuts
+* Zepp OS: Fix setting of control center
+* Zepp OS: Fix setting of unknown configuration values
+* Zepp OS: Set watchface from phone
+* Add Croatian transliterator
+* Fix restoring app notification/pebble blacklist preferences on import
+* Cache notifications while devices are out of range (opt-in)
+
+#### 0.73.0
+* Initial support for Amazfit T-Rex 2
+* Initial support for AsteroidOS watches
+* Initial support for Sony LinkBuds S
+* Initial support for Galaxy Buds2 Pro
+* Initial support for SoFlow S06(just for lock and unlock, needs key)
+* Fossil/Skagen Hybrids: Fix truncation of notifications
+* Fossil/Skagen Hybrids: Fix washed out colors in imported watchfaces
+* Fossil/Skagen Hybrids: Allow launching watch apps from app manager
+* Fossil/Skagen Hybrids: Fix activity parser
+* Fossil/Skagen Hybrids: Add app/watchface downloading from watch to app manager
+* Fossil/Skagen Hybrids: Fix crash on empty or multi-byte unicode alarm texts
+* Fossil/Skagen Hybrids: Implement inactivity warnings
+* Fossil/Skagen Hybrids: Remove obsolete debug message
+* Mi Band 6: Add NFC display item
+* Zepp OS: Fix Alipay and WeChat Pay display item and shortcuts
+* Amazfit GTR 4/GTS 4: Support for AGPS Updates
+* Bangle.js: Stop sending bitmaps for common characters that already have good enough equivalents on the watch
+* Bangle.js: Stop toast warning message appearing when starting the app loader
+* Bangle.js: Increase default realtime HRM/step interval to 10 seconds
+* Bangle.js: Support additional values for GPS event
+* Sony WF-1000XM4: Fix battery updates while connected
+* Sony WF-1000XM4: Fix audio codec
+* Add Georgian Transliteration
+
+
+#### 0.72.0
+* Initial support for Amazfit GTR 4/GTS 4/GTS 4 Mini
+* Initial support for Amazfit Band 7
+* Initial support for Galaxy Buds 2
+* Initial Support for Sony WH-1000XM2/WF-1000XM4
+* Sony headphones: Fix pause when taken off
+* Sony Headphones: Fix setting surround mode
+* Zepp OS: Map strength training, basketball and cricket activity types
+* Zepp OS: Add World Clocks
+* Zepp OS: Fix notification icons larger than 56x56px
+* Zepp OS: Fix notification icons for work profile apps
+* Zepp OS: Fix notification icon for SMS
+* Zepp OS: Fix app and watchface install
+* Zepp OS: Fix NPE when no weather data is available
+* Amazfit GTR 2: Fix activate display upon lift only working when scheduled
+* Bangle.js: Fix calendar sync
+* InfiniTime: Fix weather
+* InfiniTime: Add support for local time service
+* InfiniTime: Add world clock support
+* Skagen Gen 6 Hybrid HR: Add support for 38mm watches
+* Complete rewrite of new device discovery
+* Add Intent API to trigger activity sync and DB export
+* Allow media notifications to bypass app list
+* Debug Activity: Add confirmation dialog before removing device preferences
+
+#### 0.71.3
+* Fossil/Skagen Hybrids: Update known watch app versions
+* Skagen Hybrids: Allow firmware installation
+* Fossil Hybrid HR: Request menu config upon app connection
+* Amazfit GTS3: Fix crash when fetching workouts
+* Zepp OS: Fix fetching workouts shorter than 1 minute
+* Zepp OS: Decode workout elevation and altitude
+* Huami: Do not crash when failing to parse activity summary
+* Re-connect after update, especially useful for users of nightly releases
+* Make number of not scrollable sleep sessions lines configurable
+
+#### 0.71.2
+* Zepp OS: Display HR zones and Training Effect in Activity Details
+* Remove shortcut feature due to non-free dependencies (#2918)
+
+#### 0.71.1
+* Try to exclude non-free stuff from shortcuts library (#2918)
+* SuperCars: fix periodicDataSender, add tricks
+* Zepp OS: Fix crash when user attempts to disable Settings display item
+* Fix crash when opening Gadgetbridge from the notification
+
+#### 0.71.0
+* Remove KitKat support, Gadgetbridge now requires Android 5.0
+* Initial support for Amazfit GTR 3
+* Initial support for SuperCars (Shell Racing Cars)
+* Huami: Add preference to overwrite band settings on connection
+* Huami: Fix crash when selecting automatic Always On Display
+* Huami: Set OpenTracks track category and icon
+* Huami: Implement proper find device
+* Huami: Change default find band vibration pattern
+* Flipper Zero: added duration to Intent API
+* Flipper Zero: fixed crash due to unregistered boradcast receiver
+* Flipper Zero: fetch firmware version from flipper
+* Fossil Hybrid HR: Correctly initialize watchface after reset or crash
+* Fossil Hybrid HR: Set OpenTracks track category and icon to workout type selected on watch
+* Fossil Hybrid HR: Allow flick_away as custom event and add move_hands event 
+* InfiniTime: Add weather support
+* Amazfit Neo: Fix world clock
+* Amazfit Neo: Fix long caller name display
+* Amazfit Neo: Remove activity tracks (unsupported)
+* Amazfit GTS 3: Fix battery drain due to unanswered weather requests
+* Mi Band 7: Fix Weather
+* Mi Band 6: Add support for workout activity types
+* Mi Band 6: Enable adding workout activity types to the "more" section
+* Amazfit GTR: Enable button actions
+* Zepp OS: Implement activity, sleep and workout fetching
+* Zepp OS: Improve firmware upgrades
+* Bangle.js: Add PATCH HTTP request type, and fix for VolleyError UnsupportedOperationException when supplying custom headers.
+* Bangle.js: Add ability to start services on the Android device via intents.
+* Bangle.js: Flags and multiple categories can now be specified for intents initiated on the watch.
+* Bangle.js: Add ability to wake and unlock the Android device via a special intent.
+* Allow 3rd party apps to set device settings
+* Re-implement C code in Java and remove Android NDK dependency entirely
+* Fix crashes on older Android versions when using some devices
+* Add support for REM sleep
+* App shortcuts support (long press on the launcher icon for directly connecting a device)
+
+#### 0.70.0
+* Initial support for Amazfit GTS 3
+* Initial support for Fossil Hybrid Gen6
+* Initial support for Flipper Zero
+* Huami: fix default vibration pattern
+* Huami: Enable vibration patterns for all compatible devices
+* Huami: Improve large firmware zip file handling
+* Bangle.js: Fix null pointer issue if headers not supplied for HTTP request
+* Bangle.js: Support calendar color and name
+* Mi Band 7: Fix crash if reminder, calendar or canned messages contain non-ascii characters
+* Mi Band 7: Fix NPE when acknowledging that icon was sent
+* Mi Band 7: Fix and enable firmware upgrades
+* Mi Band 7: Support for watchapps
+* Amazfit Neo: remove 1 hour heartrate interval (not supported)
+* Fossil Hybrid HR: Fix watchface redraw after powersave and after wrist flick
+* Fossil Hybrid HR: Enable configuring middle long press on FW 3.0 and newer
+* InfiniTime: Fix firmware update
+* Make heart rate measurement intervals configurable per-device
+* Add option to ignore low priority notifications
+* Fix Skype notifications
+* Prefer long notification text by default
+* Prefer big text when scraping gmail notifications
+* Do not remove newline and whitespace characters from notification content
+* Debug: Add companion device list to debug activity
+
+#### 0.69.0
+* Initial Support for Mi Band 7
+* Initial support for devices using Binary Sensor Service
+* Mi Band 4: Enable heartrate activity monitoring support
+* Mi Band 4: Enable activate display on lift sensitivity setting
+* Mi Band 6: Enable password support
+* Mi Band 4/6, Amazfit Bip U: Enable sending GPS coordinates to band during workout
+* Mi Band 4/6, Amazfit Bip U: Enable start fitness tracking on phone when workout starts on band setting
+* Amazfit Neo: Support hourly chime which was added in firmware 1.1.2.46
+* Amazfit Neo: Fix daily steps goal and notification
+* Amazfit Neo: Fix heartrate sleep detection setting
+* Amazfit Neo: Enable heartrate activity monitoring support
+* Amazfit Neo: Fix alarms setting to "unused" on connect.
+* Bangle.js: Make text as bitmaps have transparent background, and allow font size to be specified
+* Bangle.js: Allow starting Activties on the Android device
+* Fossil Hybrid HR: Add support for Hybrid HR 38mm watches
+* Fossil Hybrid HR: Add optional circle backgrounds to widgets
+* Fossil Hybrid HR: Add toggling of widgets with physical button event
+* Fossil Hybrid HR: Add missing physical button options
+* Fossil Q Hybrid: Fix config activity after multi-device merge
+* InfiniTine: Fixes for steps count sync
+* Add steps/sleep streaks screen
+* Add French transliteration
+* Refactor file logging initialization logic
+* Add alert to Log Sharing if logging has not been enabled yet
+* Fix crash on some phones for find android device (add fallback tone)
+* Fix regression since 0.68.0 with active auto connect
+
+#### 0.68.0
+* Multi device support (experimental), allows connecting to multiple devices simultaneously
+* Fossil Hybrid HR: Allow installation of newer watch apps
+* Fossil Hybrid HR: Allow workout app the be added as a shortcut
+* Fossil Hybrid HR: Generate watchface preview image and show it in the app manager
+* Fossil Hybrid HR: Request custom menu config on watchface initialization
+* Fossil Hybrid HR: Invert widgets color when the background image is inverted
+* Fossil Hybrid HR: Show app versions in app manager
+* Fossil Hybrid HR: Make 2nd TZ widget clock duration configurable and fix wrong offset
+* Fossil Hybrid HR: Add option to share a cached watchface/app to another app
+* Fossil Hybrid HR: Allow switching already uploaded watchfaces with Intent
+* Mi Band 6: Fix night mode on latest firmware
+* Mi Band 6: add sleep menu item (also to shortcuts)
+* Mi Band 5: Send GPS location to band during workout
+* Mi Band 5: Start fitness tracking on phone when workout starts on band
+* Mi Band 5: Fix missing Portuguese language
+* Mi Band 5: Add missing breathing shortcut
+* Mi Band 4: Add password support
+* Huami: Fix setting heart rate measurement interval on connection
+* Huami: Fix track name being replaced by album
+* Huami: Display native alarm notification
+* Huami: Fix MTU update on device connection
+* Roidmi 3: Recognize "Roidmi C BLE" as Roidmi 3
+* Bangle.js fix message REPLY option
+* Bangle.js: Keep a log of data sent from the watch, and allow it to be saved with from the debug menu
+* Bangle.js: Support for color dithered bitmaps, and converting emoji->bitmaps
+* Bangle.js: Adding built-in app-loader view. (Only available on internet-enabled builds)
+* Bangle.js: fix null pointer issue for debug messages
+* Bangle.js: Enable calendar sync for bangle
+* Bangle.js: Add icon
+* Pebble: fix configuration of some watchfaces (might break other again)
+* FitPro: add MTU based chunking, add more device names (Sunset 6, Watch7)
+* UM25: fix missing firmware version
+* Support for incoming call notification delay
+* Make calendar blacklist configurable per device
+* Support folders in device list
+* Separate device settings which are specific to the application into Set preferences in device card
+* When pairing devices with auth key requirements, only show Auth key menu related items on long press
+* Provide access to the FW/App Installer via Set preferences in device card
+* Animate card movement in device list
+* Make transliteration configurable per-language
+* Widget: do not show sleep if not recorded
+* Pop up a dialog asking about Location permissions
+* Fix sharing log files on newer android versions
+* Allow to set Bluetooth discovery scanning level to prevent freezing
+* Various UI tweaks and fixes
+* Add monochrome themed icon
+* Add device menu item to get to the FW/App Installer via an explanation activity
+
+#### 0.67.1
+* Huami: Fix long music track names not displaying
+* Amazfit Bip U/Pro/Band 5: Enable extended HR/stress monitoring setting
+* Pebble: Fix calendar blacklist, view and storage
+* FitPro: Fix crash, inactivity warning preference to string
+
+#### 0.67.0
+* Initial Support for Sony WF-1000XM3
+* Initial Support for Galaxy Buds Pro
+* Huami: Add Toggle function for Open Tracks tracking to button actions
+* Huami: Move inactivity warnings, goal notification and HR monitoring to device-specific settings
+* Mi Band 6: set time on connect
+* Mi Band 5/6, Amazfit Bip S/U/Pro: Add world clock configuration
+* Mi Band 5/6: support sensitivity setting for lift wrist configuration
+* Mi Band 5: Add support for configuring workout menu on device
+* Mi Band 4/5/6, Amazfit Bip U/Pro: Add support for vibration patterns
+* Mi Band 5: Increase number of reminder slots to 50
+* Mi Band 5/6: Add setting for HR activity monitoring, HR alerts, stress monitoring
+* Amazfit Neo: Allow to disable beeps for email notifications
+* Bangle.js: Fix incoming calls in release builds
+* Bangle.js build: Add option for enabling/disabling internet access
+* Bangle.js: Add ability to receive intents to com.banglejs.uart.tx
+* Fossil Hybrid HR: Support flexible custom menu on watch
+* Fossil Hybrid HR: Add support for native DND Call/SMS functionality
+* VESC: added battery indicator
+* UM25: Add reset option to current accumulation
+* UM25: Add notification on below current threshold
+* Fix crash when calendar is accessed but permission is denied
+* Add com.asus.asusincallui and com.samsung.android.incallui to blacklist
+* New icons for Sony overhead headphones, Sony WF 800n and Mi Band 6
+* When Gadgetbridge needs permissions, pop up a dialog asking nicely and explaining why
+
+#### 0.66.0
+* Add basic support for Casio GBD-H1000
+* Add support for Hama Fit Track 1900 - via FitPro device support
+* Add OpenTracksController for interactions with OpenTracks
+* Fossil Hybrid HR: Start/stop track in OpenTracks from GPS workout on watch
+* Fossil Hybrid HR: Try guessing new widget position
+* Fossil Hybrid HR: Allow assigning no function to a button
+* Add Huami button/device action to control fitness tracking via OpenTracksController
+* Mi Band 6: Sync alarms set on the watch like on Amazfit Bip U and others
+* Bangle.js: Handle battery charging status and fix battery chart.
+* Bangle.js: Prevent exception in case UART RX line is empty
+* Bangle.js: Add repetitions in alarm JSON
+* WaspOS: Fix battery chart.
+* WaspOS: Add condition code to weather JSON
+* XWatch: Add notifications and calls support
+* UM-25: Make cumulative values resettable
+* VESC: Fixed crash when loading a saved value
+* Allow to open Android notification settings from Notification settings
+* AutoExporter changes for better operation and troubleshooting
+* Change Nightly icons background color
+
+#### 0.65.0
+* Amazfit Pop/Pro: Initial Support (probably the same as Bip U but has a different firmware)
+* Sony WH-1000XM4: Initial Support
+* Sony WH-1000XM3: Disable equalizer, surround and sound position while in SBC codec
+* Sony Headphones: Improve initialization on connection
+* Sony Headphones: Implement Noise Cancelling Optimizer
+* Casio: Fix accidentally disabled time synchronization and pairing of new Casio GBX/GBD-series watches
+* Fossil Hybrid HR: Improve Device Applications List handling
+* Fossil Hybrid HR: Added ability to change activity recognition settings on the watch
+* Fossil Hybrid HR: Make width of custom widget configurable
+* Fossil Hybrid HR: Disable non-configurable buttons preferences
+* Amazfip Bip U: Read alarm from the watch on connect and update in Gadgetbrige when changing alarms on the watch (might work on other Huami devices)
+* Add icon for VESC devices
+* Add commit id into About screen
+* Make debug activity notification test to persist text while switching apps
+* Add Portuguese to the list of language options
+* Update configuration button icon in app notification settings
+
+#### 0.64.0
+* Initial support for VESC NRF/HM10 devices
+* Initial support vor Bose QC35
+* Initial support for Sony WF-SP800N
+* Fossil Hybrid HR: Fix on-device confirmation for older firmwares
+* Sony WH-1000XM3: Fix Ambient Sound Control commands, potentially improving ANC quality
+* Sony WH-1000XM3: Read configuration from device
+* InfiniTime: Remove debug Toast and subscription to motion raw XYZ values characteristic
+* Roidmi: Fix frequency configuration on some non-english languages
+* Roidmi 3: Add support for Mojietu 3 rebrand
+* Huami: Support hiking and climbing activities, decode some more activity details
+* Amazfit GTS 2 mini: Fix notification title not appearing for non-chat apps
+* Amazfit Bip U/Pro: Disable event reminder feature, it is not supported by the device.
+* Amazfit Bip U/Pro: Allow enabling Todo List menu (feature still not supported)
+* Bangle.js: send weather condition code to device
+* Allow adding test device directly from the discovery screen
+* Keep device info on the correct device during reordering
+
+#### 0.63.1
+* Huami: Support native reminders
+* InfiniTime: Initial support for step counting (currently very limited by the device firmware)
+* Bangle.js: Fix Gadgetbridge crashes when playing music with some players
+* Fossil Hybrid HR: Add support for on-device paring confirmation, for watches that are in a state which makes this neccessary
+* Fossil Hybrid HR: Fix widget configuration bug
+* Mi Band 3: Support lift wrist during DND setting
+* Amazfit GTS 2 Mini: Fix language setting
+* Amazfit GTS 2 Mini: Fix setting menu items on the watch
+* Activity card: Open specific Charts tab for each activity
+* Activity Card: React to User settings, unify step length
+* Activity card: simplify the layout and only show each chart if there is data (Cleaner layout for users with multiple devices)
+
+#### 0.63.0
+* Galaxy Buds Live: Initial Support
+* Sony WH-1000XM3: Initial Support
+* Add support for Casio GBD-200 and untested support for GBD-100
+* Casio: Fix alarm handling on all devices
+* Fossil Hybrid HR: Add button for removing the watchface background image
+* Fossil Hybrid HR: Support multiple 2nd-TZ and Custom widgets
+* Fossil Hybrid HR: Add support for native DND functionality
+* Nothing Ear (1): Add multiple batteries support
+* Galaxy Buds: Add multiple batteries support
+* Roidmi: New FM Frequency selector with presets
+* Mi Band 6: Try to add Alipay to menu settings, untested
+* FitPro: Support more bands with different bluetooth names
+* Add activity info to device cards
+* Add Nekogram X to Telegram notifications
+* Move location settings out of pebble menu
+* Sort devices by alias, if available
+
+#### 0.62.0
+* Iniital support for Galaxy Buds 2019
+* Huami: Fix syncing of data in non-DST time
+* InfiniTime: Fix wrong time zone being sent in non-DST mode
+* Amazfit Bip U Pro: Support flashing AGPS updates
+* FitPro: recognize LH716 devices
+* Add support for Casio STB-1000. Limitations of GB-5600B/GB-6900B apply
+* Prevent crash when receiving broken weather data from TinyWeatherForecastGermany
+
+#### 0.61.0
+* Initial support for Nothing Ear(1)
+* Amazfit Bip U/Pro: Fix flashing firmware and watchfaces
+* Amazfit Bip U/Pro: Fix language setting
+* Amazfit Bip U/Pro: Allow unicode emoji
+* Huami: fix supported languages list on many devices
+* Fossil Hybrid HR: Support rotation of backgrounds in watchface editor
+* UM25: Show more measured data
+* Improved notification management including blacklist or whitelist apps settings or discrete notifications with removed text
+* Fix default daily target distance from 5 to 5000 meters
+
+#### 0.60.0
+* Initial support for FitPro bands
+* Mi Band 6: really fix weather on new firmware
+* Casio GBX-100: Fix connection
+* Fossil Hybrid HR: Lower battery level warning threshold to 10%
+* Add ringtine preference setting for find your phone feature
+* Fix a bug where GB_LOGFILES_DIR_IS_UNDEFINED is used as a logfile directory
+* Remove per-device preferences upon device removal (backup your keys if you do that)
+* Exclude not worn sleep times from sleep sessions
+* Add Icelandic and Czech transliteration
+
+#### 0.59.3
+* Mi Band 6: Properly support firmware 1.0.4.38
+* Mi Band 6: Add Flashlight to menu items
+* ZeTime: Fix corruption on long notifications
+
+#### 0.59.2
+* Mi Band 6: Support firmware 1.0.4.38 (experimental, still missing features)
+* InfiniTime: Fix null being displayed as notification title
+
+#### 0.59.1
+* Fossil Hybrid HR: Add power saving feature and many new widgets for the official Gadgetbridge watchface (battery, calories, 2nd TZ, chance of rain)
+* Fossil Hybrid HR: Support setting metric/imperial mode
+* Amazfit T-Rex Pro: Try to fix some menu items
+* Huami: Re-enable setting the timezone correctly with included DST (fixes world time)
+* Bangle.js: Add functions for pushing bitmaps
+* Bangle.js: Aadd configurable MTU
+* Add Heart Rate measurement screen, accessible via heart icon in device action icons
+
+#### 0.59.0
+* Initial support for SMA Q2 OSS firmware (Emeryth)
+* Fix broken UM25 support
+* Fossil Hybrid HR: Add watchface designer
+* use '_' instead ':' in exported and imported file names to fix problems with some Android versions
+* Fix applying theme as set by the system
+* Try to improve behavior when quitting Gadgetbridge
+
+#### 0.58.2
+* InfiniTime: Support notification for battery level
+* Allow importing GPX tracks via Android Intent/Share system
+* Add option for black background to dark theme
+* BangleJS, WaspOS: Support for transliteration
+* Add missing icons to settings items
+
+#### 0.58.1
+* Pebble: Fix broken app manager (regression from 0.57.1)
+* Bangle.js: Try to fix crash when attempting to load activity chart
+* Amazfit T-Rex Pro: Add barometer to menu items
+* Remove battery data when device is being removed
+* Add transliteration to PineTime
+* Debug: Allow adding fake testing devices manually
+* Reduce margin of icons in device_item to keep one row for typical amount of icons
+
+#### 0.58.0
+* Initial experimental support for Amazfit T-Rex Pro
+* InfiniTime: Try to fix firmware upgrade by fiddling with optimization rules
+* Huami: Fix lost samples bug with timezones that are have a 30 minute offset
+* Fossil Hybrid HR: Block dangerous intents by default and move some settings to developer settings submenu
+* Improved logging in bonding and tried to recover from weirdness
+* Use a separate notification channel for low battery warnings
+
+#### 0.57.1
+* Mi Band 6: Add Pomodoro to menu items
+* Mi Band 6: Support flashing firmware, res and watchfaces
+* Mi Band 6: Enable Unicode Emoji support
+* Fossil Hybrid HR: Move commute actions to device specific settings
+* Fossil Hybrid HR: Use Gadgetbridge App Manager (same as Pebble), allowing quick switching of watchfaces and caching apps for later re-(installation)
+* Huami: Send changed weight, birthday and height data to watch immediately
+* Use flexbox layout for icons in device cards, allowing line breaks for small phones
+
+#### 0.57.0
+* Initial limited support for Mi Band 6
+* Amazfit GTR2/GTS2: Fix for flashing watchfaces
+* Amazfit GTR/GTS/GTR2/GTS2/Bip S: Allow flashing AGPS bundles
+* Amazfit Neo: Add lift wrist setting
+* Fossil Hybrid HR: Restructure settings menus for calibration, file management and physical buttons
+* Fossil Hybrid HR: Hide old settings on newer firmwares
+* Improved PineTime/InfiniTime firmware DFU metadata parsing and checks
+
+#### 0.56.2
+* Amazfit GTR2: Improve firmware update support (still partly broken)
+* Amazfit GTR2/GTS2: Prevent emoji transliterating (Seems to support emoji)
+* Amazfit GTS2/GTR2: Fixes for setting menu items (Probably still partly broken)
+* Amazfit GTS2/GTR2: Send wind speed and sunrise/sunset
+* Fossil Hybrid HR: Send actual application icons for notification on the watch
+* Fossil Hybrid HR: Support dismissing incoming calls with a quick SMS reply
+* Huami: Remove unused calendar slots
+* Huami: Send wind speed in Beaufort for some devices
+* InfiniTime: Add support for battery info
+* InfiniTime: Support title for notifications on firmware 0.15
+* InfiniTime: Implement find device by simulating a phone call
+* Adjust Steps Charts steps values offset to account for large phone screen sizes
+* Make Activity Charts dates move as calendar months and to go to now if jumping past today
+* Make Battery Info time span jump as calendar months and not as days
+
+#### 0.56.1
+* Fossil Hybrid HR: Fix compatibility for oder firmware revisions
+* Amazfit Neo: Implement firmware update
+* Amazfit Neo: Support setting all menu items and fix menu cycling bug
+* Amazfit Neo: Fix notifications
+* Amazfit Neo: Support sound settings
+* Allow OpenTracks and FitoTrack to send ongoing notifications
+* Make transliteration a per-device setting
+
+#### 0.56.0
+* Initial support for UM25 voltage meters
+* Pebble: Remove read/dismissed notifications on watch (can be disabled)
+* Fossil Hybrid HR: Remove read/dismissed notifications on watch (can be disabled)
+* Fossil Hybrid HR: Fixes for running firmware DN1.0.2.20 or newer (disable widget features, currently not supported)
+* Fossil Hybrid HR: Fix up/back navigation and add titles to Fossil specific menus
+* Huami: Fix displaying title for calendar events
+* Use requestLegacyExternalStorage in manifest to fix file access problems in some Android 10 roms
+* Add chart to each item in activity list
+* Add scroll view to activity detail to handle landscape view
+* Add header title to activity detail
+
+#### 0.55.0
+* InfiniTime: Fix music control for newer firmware releases
+* InfiniTime: Support call control and notification
+* Sony SWR12: Fix broken support since 0.53.0
+* Wasp-OS: Fix crash on Android 7 and lower
+* Add activity list dashboard summary calculations
+* Add battery level logging and graph activity
+* Use distinct icons for total step and distance values in widget
+* Flip and scale GPX canvas
+* Try to fix call notification on outgoing VoIP calls
+
+#### 0.54.1
+* Amazfit GTS2e: Really fix broken support
+* Amazfit Bip S Lite: Fix broken support (probably)
+
+#### 0.54.0
+* Initial support for Amazfit X
+* Fix missing menu items for GTS 2 Mini (some improvements also for other GTR2/GTS2 models) 
+* Amazfit GTS2e: Fix broken support
+
+#### 0.53.0
+* Initial support for wasp-os on nRF52 devices
+* Initial support for Zepp E
+* Initial support for Amazfit GTS 2 Mini
+* Initial support for Amazfit Neo
+* Initial support for Amazfit GTR/GTS 2e
+* Fossil Hybrid HR: Fix bug with unknown data
+* Fossil Hybrid HR: allow app management on watch from GB
+* Fossil Hybrid HR: enumerate apps on watch on every connect
+* Fossil Hybrid HR: Do not configure buttons in unauthenticated mode
+* Fossil Classic: Fix unknown wearing state
+* Fossil Classic: Allow synchronizing activity data
+* Amazfit Bip U: Fix sports activity summary
+* Huami: Add Strength Training activity type
+* Honor Imperial units settings in widget, Activity list and workouts
+* Show all eligible devices in Widget Configuration Activity
+* Also include step of not-worn samples in weekly step statistics
+
+#### 0.52.0
+* Amazfit Bip U Pro: Initial support
+* Amazfit GTS2: fix pairing
+* Amazfit GTS/GTR2: Fix incoming call display
+* Fossil Hybrid HR: avoid unnecessary widget rendering
+* A lot of Data(base) Management screen clarifications and improvements
+* Fix a crash when when forecastConditionType length is 0 in weather notification data
+* Change Do Not Disturb support to allow priority notifications
+* Fix problems when pairing some devices which require a pin to be entered
+
+#### 0.51.0
+* Amazfit Bip U: Initial support
+* Amazfit Verge Lite: Initial Support
+* Amazfit T-Rex: Add missing menu items, remove non-existent
+* Amazfit Bip S: Fix crash with notifications with only a title (GitNex does this)
+* Casio GBX-100: Add step count data and more device settings
+* Fossil Hybrid Q: Support firmware upgrade
+* Bangle.js: Support for HRM and steps activity recording
+* Huami: Add new option to properly distinguish connected advertising and 3rd party hr exposure
+* Huami: Use blue icon instead of rainbow color icon for Signal (the rainbow icon was blue in early days)
+* Complete overhaul of the daily stats widget
+* Better error message for invalid authentication keys
+
+#### 0.50.0
+* Initial support for Casio GBX-100
+* Mi/Amazfit Band 5: Support watchface installation
+* Mi Band 5: Add missing NFC menu item
+* Casio GB-5600B/GB-6900B: Add configurable disconnect notification
+* Casio: Add support for synchronizing profile settings
+* Fossil Hybrid HR: Keep widget values after widget reload
+* Allow sorting and disabling charts/statistics tabs in per-device settings
+* Improve Sports Activities dashboard and Filter
+* Bug fixes for notification removal
+* Allow to clear activity labels
+
+#### 0.49.0
+* Initial support for Amazfit Bip S Lite
+* Initial support for Amazfit GTR/GTS 2
+* Huami: allow sorting of shortcuts and menus (all except Mi Band 2)
+* Amazfit Band 5: Allow enabling SpO2 menu
+* Mi/Amazfit Band 5: Support shortcuts (right/left swipe)
+* Amazfit GTS: Fix firmware flashing on Firmware >=0.1.1.16
+* Amazfit GTR: Fix firmware flashing on Firmware >=1.3.7.16 or >=1.7.0.10
+* Amazfit GTR/GTS: Add missing settings menu item in preferences
+* Fossil Hybrid HR: Remove Android notifications when deleting them from the watch
+* Fossil Hybrid HR: Enable rejecting calls on newer firmwares
+* Fossil Hybrid HR: Support hands calibration
+* Fossil Hybrid HR: Support factory reset
+* InfiniTime: Improve notification support for firmware >=0.9
+* Add version to About screen
+* Show GPS track in Sport Activity detail screen
+* Add Activity List Dashboard/Summary view to charts
+* Add heart rate average to Activity and Sleep charts
+* Add intensity to Sleep charts
+* Recognize Wire messenger as a chat application
+* Add confirmation dialog for find device button
+
+#### 0.48.0
+* Initial support for Sony SWR12
+* Initial support for Lefun Smart Bands
+* Initial support for Nut devices
+* InfiniTime: Improved music support for latest firmware
+* Fossil Hybrid HR: Fixes and better support for newer firmwares
+* Fossil Hybrid HR: Debug activity for dumping and sending resources to the watch
+* Huami: Improve style of sports activity lists
+* Add sport activity list tab in charts
+* Allow sharing of sports activity summaries as image (full scroll view)
+* Weather: Fix wind speed and direction not being passed properly
+* Fix find your phone feature on Android 10 (need companion device pairing)
+
+#### 0.47.2
+* Amazfit Bip S: Send sunrise and sunset on latest firmware if enabled
+* Huami: Support new firmware update protocol (fixes firmware flashing with firmware 2.1.1.50/4.1.5.55 on Amazfit Bip S)
+* Huami: Allow flashing latest GPS firmware
+* InfiniTime: Add support for music control
+* Pebble: Fix steps on home screen widget
+* Bangle.js: Fix issue where call state reporting was corrupted
+* Add charts to sport activity summary view
+* Add missing icons for new sport activity types
+
+#### 0.47.1
+* Huami: Add new activity types found in recent Bip S firmware
+* Huami: Many improvements to the activity summary view, including a global view for all devices, filtering per activity type and much more
+* Huami: Prevent generating broken elevation data when they are not sent by the device
+* Amazfit Bip S: Allow flashing more font files and GPS almanac (only cep worked before)
+* Pinetime-JF: Recognize device if it announces itself as InfiniTime
+* ZeTime: Fix weather forecast icons on older firmwares, try to send weather even if no firmware version was detected
+* HPlus: Improve Unicode, notification lenth and  weather support
+* Fix warnings and colors for AboutScreen
+
+#### 0.47.0
+* Initial experimental support for Pinetime-JF (not yet usable)
+* HPlus: Recognize Lemfo SG2
+* Huami: Support events forwarding via intents or direct triggering of certain actions (eg. stop music when fall asleep)
+* Huami: Add Sports Activity Detail screen from decoded sports summary values
+* Huami: Recogize and decode lot more activity (workout) types
+* Amazfit Cor/Cor2: Allow workout syncing
+* Add Sports Activity Summary filtering and statistics
+* Many icons have been re-drawn as vectors, also several new device and sports activity icons added
+* Many improvements to the Bluetooth discovery and scanning
+* Fix crash when opening GPX files
+
+#### 0.46.0
+* Initial support for Mi Band 5
+* Initial support for TLW64
+* Amazfit GTR/GTS: Fix broken activity data on newer firmwares
+* Big refactoring of the device discovery activity (See PR #1927 description for details)
+* Add about screen
+* New icon for Amazfit Bip
+* Avoid duplicated entries in preferred media player selection
+* Avoid a lot of crashes and improve error handling in various places
+
+#### 0.45.1
+* Amazfit GTR/GTS: Fix connection issue with latest firmwares (probably other Huami devices also affected)
+* Add experimental support for TinyWeatherForecastGermany
+
+#### 0.45.0
+* Initial support for Amazfit T-Rex
+* Amazfit Bip S: Support installation of latest .res
+* Amazfit Bip S: Support longer notification messages
+* Huami: Limit weather forecast to 7 days to fix problems with "Weather Notification" 0.3.11
+* Huami: Improve music playback information
+* Huami: Ensure cutting strings on UTF-8 border
+* Stop incoming call notification when VoIP calls are missed
+* Fix a crash when with Farsi translation
+
+#### 0.44.2
+* Huami: Support flashing newer GPS firmware and GPS ALM
+* Amazfit Bip S: Support music control
+* Amazfit Bip S: Support flashing firmware, res, gps firmware, watchfaces, fonts and GPS CEP
+* Amazfit Bip S: Allow setting high MTU (much faster firmware installation, default off since it does not work for some)
+* Amazfit Bip S: remove disconnect notification and button action settings (they do not work)
+* Mi Band 4 (possibly others): Fix detected RES version being always 69 for non-whitelisted res files
+* Fossil Hybrid HR: Add last notification widget
+* Try to fix vanishing incoming call information when VoIP call support is enabled
+* Allow setting device aliases (useful if you manage multiple ones of the same type)
+
+#### Version 0.44.1
+* Amazfit Bip S: Support setting shortcuts
+* Amazfit Bip S: Fix setting display items
+* Amazfit Bip S: Fix incoming call notification
+* Huami: Fix menu items vanishing from the device when they were never configured through Gadgetbridge
+* Lenovo Watch9: Fix launch of wrong calibration activity
+* Reduce calls to onSetMusicInfo/onSetMusicState when playing music
+
+#### Version 0.44.0
+* Initial support for WatchX(Plus)
+* Add support for Amazfit GTR Lite (untested and incomplete)
+* Fossil Hybrid HR: Fix some issues with custom widgets
+* Fossil Hybrid HR: Allow setting alarm titles and descriptions
+* Fossil Hybrid HR: Fix step data parsing
+* Amazfit GTS: Fix setting menu items with low MTU
+* Amazfit GTR: Allow setting menu item like GTS
+* ZeTime: Support setting the watch language
+* ZeTime: Support rejecting calls
+* ZeTime: Try to fix weather conditions on newer firmware
+* ZeTime: Fix could not synchronize calendar on connect
+* ZeTime: Fix calendar event time and date
+* ZeTime: Send up to 16 upcoming calendar events on connect if option is enabled
+* Allow set light/dark theme according to system settings (new default)
+
+#### Version 0.43.3
+* Fossil Hybrid HR: Initial support for activity tracking (no sleep yet)
+* Fossil Hybrid HR: Support setting alarms on newer firmware
+* Amazfit GTR/GTS: Fix flashing watchfaces and maybe firmware/res update (still untested)
+* Amazfit GTS: Support enabling/disabling menu items on the watch
+* Implement transliteration for Korean
+
+#### Version 0.43.2
+* Fossil Hybrid HR: Allow choosing and cropping image to be set as watch background
+* Fossil Hybrid HR: Option to draw circles around widgets
+* Fossil Hybrid HR: Experimental firmware update support
+* Fossil Hybrid HR: Fix vibration strength setting
+* Huami: Do not display firmware information and whitelist information when flashing watchfaces
+* Huami: Disable air quality indicator on Huami devices instead of showing 0
+* Bangle.js: Change encoded char set to match Espruino's 8 bit fonts
+* Steps/Sleep averages: Skip days with zero data
+
+#### Version 0.43.1
+* Initial support for Amazfit Bip S (incomplete, needs the official app once to obtain the pairing key)
+* Amazfit Bip Lite: Allow relaxing firmware checks to allow flashing of the regular Bip firmware (for the brave)
+* Fossil Hybrid HR: Fix notification history on newer firmwares
+* Fossil Hybrid HR: Add option to disable widget circle
+* Bangle.js: Don't set time if the option is turned off in settings
+* Bangle.js: DST and time zone fixes
+* Add Arabic-style Eastern Arabic numerals to transliteration
+
+#### Version 0.43.0
+* Initial support for Fossil Hybrid HR (needs complicated key extraction, read wiki)
+* Fossil: Allow switching off the Q Icon and use the default Gadgetbridge icon
+* Fix VoIP call handling during DND
+* Fix find-my-phone for Android 10
+* Huami: Fix crash when calendar event description or title was null
+* Huami: Ignore all-day events when syncing calendar events
+
+#### Version 0.42.1
+* Fix accepting/rejecting calls on Android 9
+* Mi Band 3/4, Amazfit Bip/Cor/GTS/GTR: Option to sync calendar events as reminder
+
+#### Version 0.42.0
+* Initial iTag support
+* Fix indefinitely lasting Bluetooth scans when location permission has not been granted
+* Try to stop incoming VoIP call notification when the call is answered
+* Vectorize some icons and add a new Mi Scale 2 icon
+* Mi Band 4: Make high MTU optional, fixes problems on some phones
+* ZeTime: Fix probably broken support (duplicate id used by Fossil)
+
+#### Version 0.41.1
+* Huami: allow to have alarms without snooze feature
+* Mi Band 2: Properly stop a call notification when text notifications are disabled
+* VoIP calls: ignore notifications with only one action, assuming it is an outgoing call
+* Try to fix notifications from Business Calendar
+
+#### Version 0.41.0
+* JYou Y5: Initial support
+* Mi Band 2/Amazfit Bip: Redesign button actions for easy music control setup and support long presses on Bip
+* Amazfit Bip: Remove RES file limit (for BipOS)
+* Huami: Automatically toggle alarm switch when toggling on the Band/Watch while in Alarm settings in Gadgetbridge
+* Recognize Pixart-Messenger as Chat App
+
+#### Version 0.40.1
+* Mi Band/Amazfit: Recognize changes when toggling alarm on device (immediately when connected, else when connecting)
+* Mi Band/Amazfit: Fix some bugs with stuck connection when re-connecting
+* Mi Band 4: Support higher MTU for multiple times faster firmware transfer (probably also Amazfit GTR/GTS)
+* Amazfit Cor: Fix setting language to Chinese manually
+
+#### Version 0.40.0
+* Fossil Q Hybrid: Initial support
+* Bangle.js: Initial support
+* Reserve Alarm for Calendar feature restricted to Mi Band 1/2 and moved to per-device settings
+* New icon for App Manager
+
+#### Version 0.39.1
+* Try to actively re-connect when a connection gets interrupted (interval grows up to 64 seconds)
+* Mi Band2/Amazfit Bip: Make button action settings per-device and enable for Amazfit Bip
+
+#### Version 0.39.0
+* Amazfit GTS: Initial and incomplete support, mostly untested
+* Add forward/backward buttons to charts for faster navigation
+* Debug: allow to reset last fetch date for Huami devices
+
+#### Version 0.38.0
+* Amazfit GTR: Initial and incomplete support, mostly untested
+* Amazfit Bip: add Portuguese to the list of selectable languages
+* Mi Band 4: Enable emoji font setting
+* Makibes HR3: Support the English version
+* Makibes HR3: Enable Bluetooth pairing for working re-connection
+* Work around crash when trying to display changelog
+* Sleep detection settings: Rolling 24 hours (existing style) or Noon to noon
+* Add alternative color to heart rate in chart settings
+
+#### Version 0.37.1
+* Amazfit Bip Lite: Support flashing firmware and watch faces
+
+#### Version 0.37.0
+* Initial Makibes HR3 support
+* Amazfit Bip Lite: Initial working support, firmware update is disabled for now (we do not have any firmware for testing)
+* Amazfit Cor 2: Enable Emoji Font setting and 3rd party HR access
+* Find Phone now also vibration in addition to playing the ring tone
+* ID115: All settings are now per-device
+* Time format settings are now per-device for all supported devices
+* Wrist location settings are now per-device for all supported devices
+* Work around broken layout in database management activity
+* Show toast in case no app is installed which can handle GPX files
+* Mi Band 4/Amazfit Bip Lite: Trim white spaces and new lines from auth key
+* Mi Band 4/Amazfit Bip Lite: Display a toast and do not try to pair if there was no auth key supplied
+* Skip service scan if supported device could be recognized without UUIDs during discovery
+
+#### Version 0.36.2
+* Amazfit Bip: Untested support for Lite variant 
+* Force Lineage OS to ask for permission when Trust is used to fix non-working incoming calls
+* Charts: List multiple sleep sessions per day
+
+#### Version 0.36.1
+* Mi Band 2/3/4, Amazfit Bip/Cor: Add setting to expose the HR sensor to 3rd party apps
+* Mi Band 4: Really fix weather location not being updated on the Band
+* Mi Band 4: Fix call notification not stopping when call gets answered or rejected on the phone
+* Amazfit Bip/Cor: Support for custom emoji font
+* ZeTime: Enable emoji support
+* ZeTime: Make watch language the same as the phone language by default
+* New status and alarms widget
+* Fix crash when entering notification filter settings
+* Make diagram settings accessible from charts activity
+* Add option to hide the floating plus button in the main activity
+* Fix a potential crash on Android 4.4 KitKat
+
+#### Version 0.36.0
+* Initial Mijia LYWSD02 support (Smart Clock with Humidity and Temperature Sensor), just for setting the time
+* Mi Band 3/4: Allow enabling the NFC menu where supported (useless for now)
+* Mi Band 3/4, Amazfit Cor/Bip: Set language immediately when changing it (not only on connect)
+* Mi Band 3/4, Amazfit Cor/Bip: Add icons for "swimming" and "exercise"
+* Mi Band 4: Support flashing the V2 font
+* Mi Band 4: Fix weather location not being updated on the Band
+* Mi Band 4: remove unsupported DND setting from settings menu
+* Amazfit Bip/Cor: Fix resetting of last fetched date for sports activities
+* Amazfit Bip: Fix sharing GPX files for some Apps
+* Pebble: Use Rebble Store URI
+* Support LineageOS 16.0 weather provider
+* Add Averages to Charts
+* Allow togging between weekly and monthly charts
+
+#### Version 0.35.2
+* Mi Band 1/2: Crash when updating firmware while phone is set to Spanish
+* Mi Band 4: Enable music info support (displays now on the band)
+* Mi Band 4: Support setting date format (for built-in watch-faces)
+* Amazfit Cor 2: Try to fix empty menu on device
+
+#### Version 0.35.1
+* Mi Band 4: Support flashing watch-faces, res and firmware (.ft untested)
+
+#### Version 0.35.0
+* Mi Band 4: Initial support (WARNING: INITIAL SETUP NEEDS MI FIT WITH ACCOUNT AND ROOT, NOT A RECOMMENDED DEVICE FOR GADGETBRIDGE)
+
+#### Version 0.34.1
+* Mi Band 1: Fix crash when entering per-device settings
+* Mi Band 3: Allow setting date format in per-device settings
+* ZeTime: Fix time stamps
+* Fix a crash when flashing an non-whitelisted firmware while using Gadgetbridge in Spanish
+
+#### Version 0.34.0
+* Mi Band 1/2/3/Bip/Cor: Migrate many settings to per-device settings (new settings icon in device card in main activity)
+* Mi Band 3: Fix setting menu items with 2.4 firmware and add support for the new timer menu
+* Amazfit Bip/Cor, Casio: Add support for muting incoming calls
+* ZeTime: Remove endless recursion in ZeTime settings
+* Recognize FairEmail notifications as generic email notifications
+
+#### Version 0.33.1
+* Mi Band 3: Recognize "Xiaomi Band 3"
+* Amazfit Bip: Add German, Italian, French and Turkish to language settings
+
+#### Version 0.33.0
+* BFH-16: Initial support
+* Mi Band 2/3/Bip/Cor: Generate random per-device security keys when pairing, allow manual override to still support multiple android devices connecting to the same device
+* Mi Band 3: Add Indonesian, Thai, Arabic, Vietnamese, Portuguese, Dutch, Turkish and Ukrainian to language settings
+* Mi Band 3: Support flashing latest Japanese-Korean font
+* Amazfit Cor 2: Initial experimental support (untested)
+* Pebble: Add PebbleKit extension for reopening last app
+* Casio: Bug fixes and improvements
+* Lookup contacts also in work profile
+* Fix searching in application name when blacklisting
+* Remove misleading title from database management activity when no legacy database is available
+
+#### Version 0.32.4
+* Make VoIP call support optional (disabled by default)
+* Amazfit Bip: GPX export corrections
+* ZeTime: Fix setting alarms
+* ZeTime: Fix wrong activity timestamps
+* ZeTime: Set HR alarm limits when changed, not only on connect
+* ZeTime: Sync preferences from the watch to Gadgetbridge settings
+
+#### Version 0.32.3
+* Fix a crash in charts due to a broken German translation
+* Fix a crash when transliterating emoji
+* Amazfit Bip/Cor: Support disconnect notification (must be configured in Bip settings for Cor also for now)
+
+#### Version 0.32.2
+* Fix setting alarms under some circumstances
+* Support calls notifications for some VoIP apps
+* Mi Band 3: Enable fetching sports activities (currently only useful for flushing activities)
+* Casio: Improve stability
+* Casio: Add explicit support for GB-6900B, GB-X6900B and GB-5600B
+
+#### Version 0.32.1
+* Fix db deadlock on alarm migration
+
+#### Version 0.32.0
+* Initial support for Casio GB-6900B
+* Increase number of alarms and store them per-device 
+* Support factory reset in debug activity (Mi Band 1/2/3, Bip, Cor)
+* Filter out Unicode control sequences (fixes problems with Telegram and probably others)
+* Fix endless loop resulting in OOM when RTL support is enabled
+* Recognize p鈮 as an email app
+* No longer display Android paired devices in that were not a paired with Gadgetbridge
+* Amazfit Bip: Allow flashing latest GPS firmware
+* Pebble: Native support for M7S watch face
+* No1 F1: Support for a Chinese clone
+
+#### Version 0.31.3
+* Pebble: Fix crash with DISMISS and OPEN actions
+
+#### Version 0.31.2
+* Pebble: Fix a regression that caused non-working mute, open and dismiss actions
+* Fix setting language to Czech manually
+* Ignore summary notification from K-9 Mail (caused notification spamming)
+
+#### Version 0.31.1
+* Pebble: Fix crash when no canned replies have been set
+* Pebble: Let the firmware show localized default canned replies if none have been set
+* Amazfit Bip: Fix importing GPS tracks that have been recorded with Firmware 1.1.5.02
+* Display measured hr value in debug screen
+
+#### Version 0.31.0
+* Pebble: Send all wearable notification actions (not only reply)
+* Pebble: Always allow reply action even if untested features are turned off
+* Pebble: Temporarily disable broken auto remove notification feature
+* Amazfit Bip: Allow flashing latest GPS firmware (Mili_dth.gps)
+* Mi Band 3/Amazfit Bip/Amazfit Cor: Send Fahrenheit if units are set to imperial
+* Roidmi 3: Fix and enable support
+* Mi Band 3/Amazfit Bip: fix find phone crash
+* Prevent re-sending old notifications to the wearable
+* Enhancement and Fixes for Bengali Transliteration
+* Disable excessive logging in RTL support
+
+#### Version 0.30.0
+* Amazfit Bip + Mi Band 3: Support for right to left display (configurable) (#976)
+* Add Arabic, Bengali Farsi, Persian, Scandinavian transliteration
+* Add support for some Roidmi FM receivers
+* Mi Band 3: Allow enabling the "Workout" menu item
+* Mi Band 3: Support for night mode configuration
+* Huami devices: fix seldom activity/sports synchronization problem (#1264)
+* Preferences: Make minimum heart rate configurable (lower values will be disregarded) 
+* Preferences: Configure minimum time between notifications
+* Preferences: Group language settings
+* Attempt to fix BLE connection issues on Samsung S devices
+* Week sleep and steps charts: display balance (actual value vs. desired value) 
+* Live Activity: show current/maximum heart rate, display minute steps and total steps and more improvements
+* Live Activity: fix discrepancy between number of steps in Gadgetbridge and wearable device
+* Fix missing caller ID for incoming calls on Android 9
+* Support for easy sharing of log files via the Debug screen
+* Misc small bugfixes
+
+#### Version 0.29.1
+* Mi Band 3: Support setting language to to German, Italian, French, Polish, Japanese, Korean (read wiki)
+* Mi Band 3: Support flashing latest RES files
+* Mi Band 3: Fix notification text not being displayed
+* Mi Band 3/Cor/Bip: Display app name when no app specific icon is available
+* Teclast: add/improve H1 and H3 watch recognition
+* Support transliteration for Lithuanian and Bengali
+* Fix BLE reconnect issues in certain conditions
+* Various fixes for display issues on small screens
+* Fix some potential NPEs
+* WIP: Display start and end of sleep in statistics
+
+#### Version 0.29.0
+* New Device: Initial support for ID115
+* New Device: Initial support for Lenovo Watch9
+* Show splash screen during startup
+* Vertically align device icon in main activity
+* Try to support the google clock application (untested)
+* Amazfit Cor: Allow to configure displayed menu items
+* Amazfit Cor: Support basic music control
+* Amazfit Cor: Fix flashing font files
+* Amazfit Bip: improved GPX export
+* Amazfit Bip: Fix exported GPX file names for *FAT storage
+* Amazfit Bip: Fix current weather not being displayed with later firmwares
+* Amazfit Bip/Cor: Try to fix device being sometimes stuck in connecting state
+* Mi Band 2: Put some device specific settings into its own settings category
+* Mi Band 3: Support disabling of on-device menu items
+* Mi Band 3: Support locking the Mi Band screen (swipe up to unlock)
+* Mi Band 2/3: New icon
+* NO1 F1: Set time during initialization
+
+#### Version 0.28.1
+* Fix wrong weather icon mapping in rare cases
+* Fix device discovery on Android 4.4
+* Amazfit Bip: Use UTC in gpx tracks for better compatibility with external software
+* Amazfit Bip: Add the (localized) activity type to the gpx filename
+* Amazfit Bip: Fix weather on latest firmwares
+
+#### Version 0.28.0
+* Initial support for ZeTime: time, weather and activity data sync, notification support and music playback control is working
+* Amazfit Bip/Cor: Rework firmware detection to cope with new version scheme
+* Amazfit Bip: Support setting language to Russian
+* Amazfit Cor: Support language switching on newer firmwares
+* Mi Band 3: support setting language (english and spanish tested)
+* Mi Band 3: Fix pairing
+* Mi Band 3: Send AQI to enable display of current temperature
+
+#### Version 0.27.1
+* Pebble: Change app store search to point to RomanPort's Pebble app store
+* Mi Band 3: Allow flashing fonts (untested)
+* Amazfit Bip: Allow flashing latest firmwares
+* Amazfit Cor: Allow flashing Bip fonts (untested)
+* Allow to limit auto fetch to a user configurable time interval
+
+#### Version 0.27.0
+* Initial support for Mi Band 3 (largely untested, needs to be connected to Mi Fit once)
+* Option for automatic activity sync after screen unlock
+* Allow hiding activity transfer notification on Android Oreo and above
+* Allow blacklisting of PebbleKit notifications for individual apps
+* Allow blacklisting all application at once
+* Forward Skype notifications to wearable even if "local only" flag is set
+* Show Gadgetbridge logo behind cards in main activity
+* Always stop BT/BTLE discovery when exiting the discovery activity
+* Amazfit Bip/Cor: Fix scheduled setting for "display on lift wrist" preference
+* Amazfit Bip/Cor: add recent firmwares to whitelist
+* Pebble: Fix a rare crash in WebView
+
+#### Version 0.26.5
+* Fix autoreconnect at boot on recent Android versions
+* Bluetooth connection is more stable on Oreo
+* Potentially fix the watch continuously vibrating after call pickup
+* Amazfit Bip: Add setting to configure shortcuts (swipe to right from watchface)
+* Recognize Q8 as a HPlus device
+
+#### Version 0.26.4
+* Fix a bug with Toasts appearing every time a notification arrives when bluetooth is disabled
+* Pebble 2: Add optional GATT client only mode that might help with connection stability
+* Amazfit Cor: Fix detection of newer firmwares
+* Mi Band 2: Fix text notifications not appearing with short vibration patterns
+
+#### Version 0.26.3
+* Amazfit Bip: Add proper mime type to shared gpx files
+* Amazfit Bip: allow to set displayed menu items
+* Amazfit Bip: fix fetching logs from device via debug menu
+* Amazfit Bip: Raise .res limit to 700000 bytes for modded files
+
+#### Version 0.26.2
+* Amazfit Bip: Time and timezone fixes for Android <=6 when exporting GPX
+
+#### Version 0.26.1
+* Fix crashes and connection problems on Android 6 and lower
+
+#### Version 0.26.0
+* Amazfit Bip: Initial support for GPS tracks
+* Pebble: Wind speed/direction support and bugfixes for weather when using background javascript
+
+#### Version 0.25.1
+* Amazfit Cor: Try to send weather location instead of AQI
+* Amazfit Bip: Support setting start end end time for background light when lifting the arm
+* Pebble: various fixes and improvements for background javascript
+* Explicitly ask for RECEIVE_SMS permission to fix problems with Android 8
+
+#### Version 0.25.0
+* Initial support for Xwatch
+* Move the connected device to top in control center
+* Add adaptive launcher icon for Android 8.x
+* No longer plot heart rate graph when device was detected as not worn
+* Pebble: Small fixes for background js (e.g. Pebble-Casio-WV58DE)
+* Pebble: native (non bg js) support for weather in Simply Light watchface
+
+#### Version 0.24.6
+* Display the chat icon for notifications coming from Kontalk and Antox
+* Pebble: Fix for background js which try to send floats (e.g. TrekVolle)
+* Mi Band 2: Change the way vibration patterns work, also fixes problems with missing text on newer firmwares
+
+#### Version 0.24.5
+* Fix crash in settings activity with export location
+* Fix notification deletion regression
+* Add '艁' and '艂' to transliteration map
+* Omnijaws Weather: correctly pick today's min and max temperature
+* Fix alarm details activity on small screen
+* Pebble: mimic online check of TrekVolle when using background js
+
+#### Version 0.24.4
+* Amazfit Bip: Fix language setting on new firmwares
+
+#### Version 0.24.3
+* Charts: Try to fix another crash
+* Pebble: Fix weather for some watchfaces when using background JS
+* Amazfit Cor: Allow watchfaces to be flashed (untested)
+* Amazfit Bip: Better detection for flashable font types
+* Fix number only privacy option
+
+#### Version 0.24.2
+* Fix crash when changing the periodic database export interval
+* Amazfit Bip: Allow fonts and new res format to be flashed
+* Amazfit Cor: Allow new res format to be flashed
+* Pebble: Background js fixes
+
+#### Version 0.24.1
+* Amazfit Bip: prevent menu icons from vanishing when using firmware 0.1.0.51
+* Pebble: "find phone" feature for upcoming pebble helper app
+
+#### Version 0.24.0
+* Fix logs sometimes not containing stacktraces
+* Support periodic database export
+* Support transliteration for Arabic and Farsi
+* Try to make alarm details scrollable (for small devices)
+* Amazfit Bip: Implement find phone feature
+* Amazfit Bip: Support flashing latest GPS firmware
+* Amazfit Cor: Support flashing latest firmware
+* Pebble: Fix crash with experimental background javascript
+* Charts: Several fixes to the MPAndroidChart library
+
+#### Version 0.23.2
+* Mi Band 1S: Fix sync problem with firmware 4.16.11.15 (probably also Mi Band 1.0.15.0 and Mi Band 1A 5.16.11.15)
+* Amazfit Cor: Fix problem with firmware >=1.0.6.27 being detected as Mi Band 2
+
+#### Version 0.23.1
+* Initial support for Omnijaws weather service
+* Amazfit Bip: Allow installation of latest gps firmware
+* Amazfit Cor: Fixes for installing newer firmware versions
+
+#### Version 0.23.0
+* Initial support for LineageOS/CyanogenMod weather provider
+* Amazfit Bip/Cor: Support for current weather temperature
+* Amazfit Bip/Cor: Display firmware version and type also for non-whitelisted firmware files
+
+#### Version 0.22.5
+* Unlock Teclast H10 support using the same code as H30
+* Amazfit Bip: Fix installation of 0.1.0.11 Firmware
+* Amazfit Bip/Cor: Send three days of weather forecast including (untranslated) conditions
+* Workaround for a crash on Android 4.4 when connecting
+
+#### Version 0.22.4
+* Mi Band 2/Bip/Cor: Whole day HR support
+* Mi Band 2/Bip/Cor: Prevent writing a lot of HR samples to the database when not using the live activity feature
+* Pebble: Fix some nasty crashes which occur since 0.22.0
+* Workaround for non-working notifications from wechat and outlook
+
+#### Version 0.22.3
+* Amazfit Bip: Allow flashing watchfaces
+* Amazfit Cor: Fix flashing new .res files
+* Mi Band 2/HRX/Bip/Cor: Try to fix stuck activity sync
+
+#### Version 0.22.2
+* Charts: Add setting to disable swiping charts left/right and some UI changes
+* Pebble: Use the configured unit system also for system weather app
+* Mi Band 2: Fix HR being absent in charts
+* Amazfit Bip: Allow manual language selection in settings
+* Amazfit Cor: Fix firmware update
+
+#### Version 0.22.1
+* Mi Band 2: Fix being detected as Amazfit Bip which lead to various problems especially on newly paired devices
+
+#### Version 0.22.0
+* Pebble: Experimental support for background javascript, allows weather and other features for watchapps without special Gadgetbridge support
+* Add experimental support for Amazfit Cor and Mi Band HRX (no firmware update on the latter)
+* Mi Band 2: Support more icons and textual notifications for more apps
+* Add some quick action buttons to Gadgetbridge's notification
+* Add transliteration support for ukrainian cyrillic characters
+* Fix annoying toast in Mi Band settings
+
+#### Version 0.21.6
+* Amazfit Bip: Fix non-working notifications from Outlook, Yahoo Mail and GMail
+* HPlus: Fix Unicode encoding
+* No.1 F1: Alarms support
+* No.1 F1: Show data fetching progress
+
+#### Version 0.21.5
+* Mi2/Bip: Support setting distance units (metric/imperial)
+
+#### Version 0.21.4
+* Mi2/Bip: Fix sleep detection for newer firmwares
+* Mi2/Bip: Fix ancient bug resulting in wrong activity data at the beginning in diagrams and aggregate data
+* No.1 F1: Support setting time format and distance units (metric/imperial)
+* Pebble: Support setting distance units to miles for Health (need to reactivate Health in App Manager after toggling)
+* HPlus: Make changing distance unit system effective immediately on toggling
+
+#### Version 0.21.3
+* Amazfit Bip: Auto-switch language on connect (English, Simplified Chinese, Traditional Chinese), requires FW 0.0.9.14+
+
+#### Version 0.21.2
+* Amazfit Bip: Support flashing CEP and ALM files for AGPS
+* Amazfit Bip: Initial experimental support for fetching logs from the watch
+* Mi2/Bip: Send user info to the device (fixes calories and distance display)
+* Mi2/Bip: Fix firmware update progressbar being stuck at the end
+* Pebble/Bip: Support more notification icons
+* Pebble: Automatically determine color for unknown notifications on Pebble Time
+
+#### Version 0.21.1
+* Initial support for EXRIZU K8 (HPLus variant)
+* Amazfit Bip: fix long messages not being displayed at all
+* Mi Band 2: Support multiple button actions
+* NO.1 F1: Fetch sleep data
+* NO.1 F1: Heart rate support
+* Pebble: Support controlling the current active media playback application
+* Fix suspended activities coming to front when rotating the screen
+
+#### Version 0.21.0
+* Initial NO.1 F1 support
+* Initial Teclast H30 support
+* Amazfit Bip: Display GPS firmware version
+* Amazfit Bip: Fix E-Mail notifications
+* Amazfit Bip: Fix call notification with unknown caller
+* Amazfit Bip: Fix crash when weather is updated and device reconnecting
+* Mi2/Bip: Fix crash when synchronizing calendar to alarms
+* Pebble: Fix crash when taking screenshots on Android 8.0 (Oreo)
+* Pebble: Support some google app icons
+* Pebble: try to support spotify
+* Mi Band 2: Support configurable button actions
+* Fix language being reset to system default
+
+#### Version 0.20.2
+* Amazfit Bip: Various fixes regarding weather, add condition string support for FW 0.0.8.74
+* Amazfit Bip: enable caller display in later firmwares
+* Amazfit Bip: initial firmware update support (EXPERIMENTAL, AT YOUR OWN RISK)
+* Re-enable improved speed zones tab
+* Probably fix crash with certain music players
+* Improve theme and add changelog icon
+
+#### Version 0.20.1
+* Amazfit Bip: Support icons and text body for notifications
+* Mi Band: Fix setting smart alarms
+
+#### Version 0.20.0
+* Initial Amazfit Bip support (WIP)
+* Various theming fixes
+* Add workaround for blacklist not properly persisting
+* Handle resetting language to default properly
+* Pebble: Pass booleans from Javascript Appmessage correctly
+* Pebble: Make local configuration pages work on most recent WebView implementation
+* Pebble: Allow to blacklist calendars
+* Add Greek and German transliteration support
+* Various visual improvements to charts
+
+#### Version 0.19.4
+* Replace or relicense CC-NC licensed icons to satisfy F-Droid
+* Mi Band 2: Make info to display on the Band configurable
+* Mi Band 2: Support wrist rotation to switch info setting
+* Mi Band 2: Support goal notification setting
+* Mi Band 2: Support do not disturb setting
+* Mi Band 2: Support inactivity warning setting 
+
+#### Version 0.19.3
+* Pebble: Fix crash when calendar access permission has been denied
+* Pebble: Fix wrong timestamps with Morpheuz running on Firmware >=3
+* Mi Band 2: Improve reliability when fetching activity data
+* HPlus: Fix intensity calculation without continuous connectivity
+* HPlus: Fix Unicode handling
+* HPlus: Initial not work detection
+* Fix memory leak
+* Only show Realtime Chart on devices supporting it
+
+#### Version 0.19.2
+* Pebble: Fix recurring calendar events only appearing once per week
+* HPlus: Fix crash when receiving calls without phone number
+* HPlus: Detect unicode support on Zeband Plus
+* No longer quit Gadgetbridge when bluetooth gets turned off
+
+#### Version 0.19.1
+* Fix crash at startup
+* HPlus: Improve reconnection to device
+* Improve transliteration
+
+#### Version 0.19.0
+* Pebble: allow calendar sync with Timeline (Title, Location, Description)
+* Pebble: display calendar icon for reminders from AOSP Calendar
+* HPlus: try to fix latin characters showing as random Chinese text
+* Improve reconnection with BLE devices
+* Improve generic notification reliability by trying to restart the notification listener when stale/crashed
+* Other small bugfixes
+
+#### Version 0.18.5
+* Applied some material design guidelines to Charts and Pebble app management
+* Changed colours: deep sleep is now dark blue, light sleep is now light blue
+* Support for exporting and importing of preferences in addition to the database
+* Visual improvements of the pie charts
+* Add filter by name in the App blacklist activity
+* Pebble: improve compatibility with watch app configuration pages
+* Pebble: display battery percentage (will only update once an hour)
+* HPlus: users can now decide whether they want to pair the device or not, hopefully fixing some connection problems (#642)
+* HPlus: display battery state and warn on low battery
+
+#### Version 0.18.4
+* Mi Band 2: Display realtime steps in Live Activity
+* Mi Band: Attempt to recognize Mi Band model with hwVersion = 8
+* Alarms activity improvements and fixes
+* Make Buttons in the main activity easier to hit
+
+#### Version 0.18.3
+* Fix bug that caused the same value in weekly charts for every day on Android 6 and older
+
+#### Version 0.18.2
+* Mi Band 2: Fix crash on "chat" or "social network" text notification (#603)
+
+#### Version 0.18.1
+* Pebble: Fix Firmware installation on Pebble Time Round (broken since 0.16.0)
+* Start VibrationActivity when using "find device" button with Vibratissimo
+* Support material fork of K9
+
+#### Version 0.18.0
+* All new GUI for the control center
+* Add Portuguese pt_PT and pt_BR translations
+* Add Czech translation
+* Add Hebrew translation and transliteration
+* Consistently display device specific icons already during discovery
+* Add sleep chart displaying the last week of sleep
+* Huge speedup for weekly charts when changing days
+* Drop support for importing pre Gadgetbridge 0.12.0 database
+* Pebble: allow configuration web pages (clay) to access device location
+* Mi Band 2: Initial support for text notifications, caller ID, and icons (requires font installation) (#560)
+* Mi Band 2: Support for flashing Mili_pro.ft* font files
+* Mi Band 2: Improved firmware/font updated
+* Mi Band 2: Set 12h/24h time format, following the Android configuration (#573)
+* Improved BLE discovery and connectivity
+
+#### Version 0.17.5
+* Automatically start the service on boot (can be turned off)
+* Pebble: PebbleKit compatibility improvements (Data logging)
+* Pebble: Display music shuffle and repeat states for some players
+* Pebble 2/LE: Speed up data transfer
+
+#### Version 0.17.4
+* Better integration with android music players
+* Privacy options for calls (hide caller name/number)
+* Send a notification to the connected if the Android Alarm Clock rings (com.android.deskclock)
+* Fixes for Cyrillic transliteration
+* Pebble: Implement notification privacy modes
+* Pebble: Support weather for Obsidian watchface
+* Pebble: add a dev option to always and immediately ACK PebbleKit messages to the watch
+* HPlus: Support alarms
+* HPlus: Fix time and date sync and time format (12/24)
+* HPlus: Add device specific preferences and icon
+* HPlus: Support for Makibes F68
+
+#### Version 0.17.3
+* HPlus: Improve display of new messages and phone calls
+* HPlus: Fix bug related to steps and heart rate
+* Pebble: Support dynamic keys for natively supported watchfaces and watchapps (more stability across versions)
+* Pebble: Fix error Toast being displayed when TimeStyle watchface is not installed
+* Mi Band 1+2: Support for connecting without BT pairing (workaround for certain connection problems)
+
+#### Version 0.17.2
+* Pebble: Fix temperature unit in Timestyle Pebble watchface
+* Add optional Cyrillic transliteration (for devices lacking the font)
+
+#### Version 0.17.1
+* Pebble: Fix installation of some watchapps
+* Pebble: Try to improve PebbleKit compatibility
+* HPlus: Fix bug setting current date
+
+#### Version 0.17.0
+* Add weather support through "Weather Notification" app
+* Various fixes for K9 mail when using the generic notification receiver
+* Add a preference to hide the persistent notification icon of Gadgetbridge
+* Pebble: Support for build-in weather system app (FW 4.x)
+* Pebble: Add weather support for various watchfaces
+* Pebble: Add option to disable call display
+* Pebble: Add option to automatically delete notifications that got dismissed on the phone
+* Pebble: Bug fix for some PebbleKit enabled 3rd party apps (TCW and maybe other)
+* Pebble 2/LE: Improve reliability and transfer speed
+* HPlus: Improved discovery and pairing
+* HPlus: Improved notifications (display + vibration)
+* HPlus: Synchronize time and date
+* HPlus: Display firmware version and battery charge
+* HPlus: Near real time Heart rate measurement
+* HPlus: Experimental synchronization of activity data (only sleep, steps and intensity)
+* HPlus: Fix some disconnection issues
+
+#### Version 0.16.0
+* New devices: HPlus (e.g. Zeblaze ZeBand), contributed by Joa虄o Paulo Barraca
+* ZeBand: Initial support: notifications, heart rate, sleep monitoring, user configuration, date+time
+* Pebble 2: Fix Pebble Classic FW 3.x app variant being prioritized over native Pebble 2 app variant
+* Charts (Live Activity): Fix axis labels color in dark theme
+* Mi Band: Fix ginormous step count when using Live Activity
+* Mi Band: Improved performance during activity sync
+* Mi Band 2: Fix activity data missing after doing manual hr measurements or live activity
+* Support sharing firmwares/watchapps/watchfaces to Gadgetbridge
+* Support for the "Subsonic" music player (#474)
+
+#### Version 0.15.2
+* Mi Band: Fix crash with unknown notification sources
+
+#### Version 0.15.1
+* Improved handling of notifications for some apps
+* Pebble 2/LE: Add setting to limit GATT MTU for debugging broken BLE stacks
+* Mi Band 2: Display battery status
+
+#### Version 0.15.0
+* New device: Liveview
+* Liveview: initial support (set the time and receive notifications)
+* Pebble: log pebble app logs if option is enabled in pebble development settings
+* Pebble: notification icons for more apps
+* Pebble: Further improve compatibility for watchface configuration
+* Mi Band 2: Initial support for firmware update (tested so far: 1.0.0.39)
+
+#### Version 0.14.4
+* Pebble 2/LE: Fix multiple bugs in re-connection code, honor reconnect tries from settings
+* Mi Band 2: Experimental support for activity recognition
+* Mi Band 2: Fix time setting code
+
+#### Version 0.14.3
+* Pebble: Experimental support for pairing and using all Pebble models via BLE
+* Mi Band 1: Fix regression causing display of wrong activity data (#440)
+* Mi Band 2: Support for continuous heart rate measurements in live activity view
+
+#### Version 0.14.2
+* Pebble 2: Fix a bug where the Pebble got disconnected by other unrelated LE devices
+
+#### Version 0.14.1
+* Mi Band 2: Initial experimental support for activity data
+* Mi Band 2: Send the fitness goal (steps) to the band
+* Pebble 2: Work around firmware installation issues (tested with upgrading 4.2 to 4.3)
+* Pebble: Further improve compatibility for watchface configuration
+* Pebble: add Kickstart watch face to app manager on FW 4.x
+* Charts: display the total time range, not just the range with available data
+
+#### Version 0.14.0
+* Pebble 2: Initial experimental support for P2/PT2 using BLE
+* Pebble: Special support in device discovery activity (MUST be used to get Pebble 2 working)
+* Pebble: Improve compatibility for watchface configuration
+* Mi Band 2: support for heart rate measurement during sleep
+* Mi Band 2: configuration option to activate the display on lift
+* Mi Band 2: configuration option to display the time + date or just the time
+* Mi Band 2: honor the wear location configuration option
+
+#### Version 0.13.9
+* Pebble: use the last known location for setting sunrise and sunset
+* Pebble: fix Health disappearing forever when deactivating through app manager (and get it back for affected users)
+* Mi Band 2: More fixes for connection issues (#408)
+
+#### Version 0.13.8
+* Mi Band 2: fix connection issues for users of Mi Fit (#408, #425)
+* Mi Band 1A: fix firmware update for certain 1A models
+
+#### Version 0.13.7
+* Pebble: Fix configuration of certain pebble apps (eg. QR Generator, Squared 4.0)
+* Pebble: Add context menu option in app manager to search a watchapp in the Pebble app store
+* Mi Band: allow to delete Mi Band address from development settings
+* Mi Band 2: Initial support for heart rate readings (Debug activity only)
+* Mi Band 2: Support disabled alarms
+* Attempt to fix spurious device discovery problems
+* Correctly recognize Toffeed, Slimsocial and MaterialFBook as Facebook notification sources 
+
+#### Version 0.13.6
+* Mi Band 2: Support for multiple alarms (3 at the moment)
+* Mi Band 2: Fix for alarms not working when just one is enabled
+
+#### Version 0.13.5
+* Mi Band 2: Support setting one alarm
+* Pebble: Health compatibility for Firmware 4.2
+* Improve support for K9 when generic notifications are used (K9 notifications set to never)
+
+#### Version 0.13.4
+* Mi Band: Initial support for recording heart and displaying rate values
+* Mi Band: Support for testing vibration patterns directly from the preferences
+* Mi Band: Clean up vibration preferences
+* Possibly fix logging to file on certain devices (#406)
+* Mi Band 2: Possibly fix weird connection interdependence between Mi 1 and 2 (#323)
+* Mi Band 1S: White list firmware 4.16.4.22
+* Mi Band: try application level pairing again, in order to support data sharing with Mi Fit (#250)
+* Pebble: new icons and colors for certain apps
+* Debug-screen: added button to test "new functionality", currently live sensor data for Mi Band 1
+
+#### Version 0.13.3
+* Fix regressions with missing bars and labels in charts
+* Allow to set notification type in Debug activity
+* Move "Disconnect" back to the bottom of the context menu
+* Mi Band 2: Display Message and Phone icons
+
+#### Version 0.13.2
+* Support deleting devices (and their data) in control center
+* Sort devices lexicographically in control center
+* Do not forward group summary notifications (could fix some duplicate notifications)
+* Pebble: Support for health on FW 4.1
+* Mi Band: Fix offline charts not displaying heartrate for Mi 1S
+
+#### Version 0.13.1
+* Improved BLE scanning for Android 5.0+
+* Pebble: try to work around duplicate Telegram messages and support Telegram icon
+* Pebble: fix some incompatibilities with certain PebbleKit Android apps
+
+#### Version 0.13.0
+* Initial working Mi Band 2 support (only notifications, no activity and heart rate support)
+* Experimental support for Vibratissimo devices
+
+#### Version 0.12.2
+* Fix for user attribute database table getting spammed and store sleep and steps goals properly
+
+#### Version 0.12.1 (release withdrawn)
+* Pebble: Fix activity data being associated with the wrong device and/or user in some cases causing them to invisible in charts
+* Remove special handling for Conversations notifications since upstream dropped special pebble support
+
+#### Version 0.12.0 (release withdrawn)
+* NB: User action needed to migrate existing data! 
+* Store activity data per device and provider to allow multiple devices of the same kind with separate data. Migration is available, except for Pebble Misfit data. Existing data from multiple devices of the same kind (eg. multiple Mi Bands) will get merged while importing.
+* In Control Center, display known devices even when Bluetooth is off
+* In Control center, new menu point to launch the new "Database management" activity
+* Pebble: Support for Pebble Health on Firmware 4.0
+* Pebble: Optionally allow raw Pebble Health data to be stored in database completely (for later interpretation, when we are able to decode it)
+* Mi Band: fix displaying of deep sleep vs. light sleep (was inverted)
+
+#### Version 0.11.2
+* Mi Band: support for devices that cannot pair with the band (#349)
+
+#### Version 0.11.1
+* Various fixes (including crashes) for location settings
+* Pebble: Support Pebble Time 2 emulator (needs recompilation of Gadgetbridge)
+* Fix a rare crash when, due to Bluetooth problems, when a device has no name
+* Fix activity fetching getting stuck when double tapping (#333)
+* Mi Band: in the Device Discovery activity, do not display devices that are already paired
+* Mi Band: only allow automatic re-connection on disconnect when the device was previously fully connected
+* Mi Band: fix a rare crash when reading data fails due to Bluetooth problems
+* Mi Band: log full activity sample to help deciphering activity kinds (#341)
+* Mi Band 2: improved discovery mechanism to not rely on MAC addresses (#323)
+* Charts: only display heart rate samples on devices that support that
+* Add more logging to detect problems with external directories (#343)
+
+#### Version 0.11.0
+* Pebble: new App Manager (keeps track of installed apps and allows app sorting on FW 3.x)
+* Pebble: call dismissal with canned SMS (FW 3.x)
+* Pebble: watchapp configuration presets
+* Pebble: fix regression with FW 2.x (almost everything was broken in 0.10.2)
+
+#### Version 0.10.2
+* Pebble: allow to manually paste configuration data for legacy configuration pages
+* Pebble: various improvements to the configuration page
+* Pebble: Support FW 4.0-dp1 and Pebble2 emulator (needs recompilation of Gadgetbridge)
+* Pebble: Fix a problem with key events when using the Pebble music player
+
+#### Version 0.10.1
+* Pebble: set extended music info by dissecting notifications on Android 5.0+
+* Pebble: various other improvements to music playback
+* Pebble: allow ignoring activity trackers individually (to keep the data on the pebble)
+* Mi Band: support for shifting the device time by N hours (for people who sleep at daytime)
+* Mi Band: initial and untested support for Mi Band 2
+* Allow setting the application language
+
+#### Version 0.10.0
+* Pebble: option to send sunrise and sunset events to timeline
+* Pebble: fix problems with unknown app keys while configuring watchfaces
+* Mi Band: BLE connection fixes
+* Fixes for enabling logging at without restarting Gadgetbridge
+* Re-enable device paring activity on Android 6 (BLE scanning needs the location preference)
+* Display device address in device info
+
+#### Version 0.9.8
+* Pebble: fix more reconnect issues
+* Pebble: fix deep sleep not being detected with Firmware 3.12 when using Pebble Health
+* Pebble: option in AppManager to delete files from cache
+* Pebble: enable pbw cache and watchface configuration for Firmware 2.x
+* Pebble: allow enabling of Pebble Health without "untested features" being enabled
+* Pebble: fix music information being messed up
+* Honour "Do Not Disturb" for phone calls and SMS
+
+#### Version 0.9.7
+* Pebble: hopefully fix some reconnect issues
+* Mi Band: fix live activity monitoring running forever if back button pressed
+* Mi Band: allow low latency firmware updates, fixes update with some phones
+* Mi Band: initial experimental and probably broken support for Amazfit
+* Show aliases for BT Devices if they had been renamed in BT Settings
+* Do not show a hint about App Manager when a Mi Band is connected
+
+#### Version 0.9.6
+* Again some UI/theme improvements
+* New preference to reconnect after connection loss (defaults to true)
+* Fix crash when dealing with certain old preference values
+* Mi Band: automatically reconnect when back in range after connection loss
+* Mi Band 1S: display heart rate value again when invoked via the Debug view
+
+#### Version 0.9.5
+* Several UI Improvements
+* Easier First-time setup by using a FAB
+* Optional Dark Theme
+* Notification App Blacklist is now sorted
+* Gadgetbridge Icon in the notification bar displays connection state
+* Logging is now configurable without restart
+* Mi Band 1S: Initial live heartrate tracking
+* Fix certain crash in charts activity on slower devices (#277)
+
+#### Version 0.9.4
+* Pebble: support pebble health datalog messages of firmware 3.11 (this adds support for deep sleep!)
+* Pebble: try to reconnect on new notifications and phone calls when connection was lost unexpectedly
+* Pebble: delay between re-connection attempts (from 1 up to 64 seconds)
+* Fix crash in charts activities when changing the date, quickly (#277)
+* Mi Band: preference to enable heart rate measurement during sleep (#232, thanks computerlyrik!)
+* Mi Band: display measured heart rate in charts (#232)
+* Mi Band 1S: full support for firmware upgrade/downgrade (both for Mi Band and heart rate sensor) (#234)
+* Mi Band 1S: fix device detection for certain versions
+
+#### Version 0.9.3
+* Pebble: Fix Pebble Health activation (was not available in the App Manager)
+* Simplify connection state display (only connecting->connected)
+* Small improvements to the pairing activity
+* Mi Band 1S: Fix for mi band firmware update
+
+#### Version 0.9.2
+* Mi Band: Fix update of second (HR) firmware on Mi1S (#234)
+* Fix ordering issue of device info being displayed
+
+#### Version 0.9.1
+* Mi Band: fix sporadic connection problems (stuck on "Initializing" #249)
+* Mi Band: enable low latency connection (faster) during initialization and activity sync
+* Mi Band: better feedback for firmware update
+* Device Item is now clickable also when the information entries are visible
+* Fix enabling log file writing #261
+
+#### Version 0.9.0
+* Pebble: Support for configuring watchfaces/apps locally (clay) or though web browser (some do not work)
+* Pebble: hide the alarm management activity as it's unsupported
+* Mi Band: Improve firmware detection and updates, including 1S support
+* Mi Band: Display HR FW for 1S
+* FW and HW versions are only displayed after tapping on the "info" button in Control Center
+* Do not display activity samples when navigating too far in the past
+* Fix auto connect which was broken under some circumstances
+
+#### Version 0.8.2
+* Fix database creation and updates (thanks @feclare)
+* Add experimental widget to set the alarm time to a configurable number of hours in the future (thanks @0nse)
+* Use ckChangeLog to display the Changelog within Gadgetbridge
+* Workaround to fix logfile rotation (bug in logback-android)
+
+#### Version 0.8.1
+* Pebble: install (and start) freshly-installed apps on the watch instead of showing a Toast that tells the user to do so. (only applies to firmware 3.x)
+* Pebble: fix crash while receiving Health data
+* Mi Band 1S: support for synchronizing activity data (#205)
+* Mi Band 1S: support for reading the heart rate via the "Debug Screen" #178
+
+#### Version 0.8.0
+* Pebble: Support Pebble Health: steps/activity data are stored correctly. Sleep time is considered as light sleep. Deep sleep is discarded. The pebble will send data where it seems appropriate, there is no action to perform on the watch for this to happen.
+* Pebble: Fix support for newer version of morpheuz (>=3.3?)
+* Pebble: Allow to select the preferred activity tracker via settings activity (Health, Misfit, Morpheuz)
+* Pebble: Fix wrong(previous) contact being displayed on the pebble 
+* Mi Band: improvements to pairing and connecting
+* Fix a problem related to shared preferences storage of activity settings
+* Very basic support Android 6 runtime permission
+* Fix layout of the alarms activity
+
+#### Version 0.7.4
+* Refactored the settings activity: User details are now generic instead of Mi Band specific. Old settings are preserved.
+* Pebble: Fix regression with broken active reconnect since 0.7.0
+* Pebble: Support activation and deactivation of Pebble Health. Activation uses the User details as seen above. Insights are NOT activated.
+  Please be aware that deactivation does NOT delete the data stored on the watch (but it seems to stop the tracking), and we do not know how to switch to metric length units.
+
+#### Version 0.7.3
+* Pebble: Report connection state to PebbleKit companion apps via content provider. NOTE: Makes Gadgetbridge mutual exclusive with the original Pebble app.
+* Ignore generic notification when from SMSSecure when SMS Notifications are on
+
+#### Version 0.7.2
+* Pebble: Allow replying to generic notifications that contain a wearable reply action (tested with Signal)
+* Pebble: Support setting up a common suffix for canned replies (defaults to " (canned reply)")
+* Mi Band: Avoid NPEs when aborting an erroneous sync #205
+* Mi Band: Fix discovery of Mi Band 1S
+* Add a confirmation dialog when performing a db import
+* Sort blacklist by package names
+
+#### Version 0.7.1
+* Pebble: allow re-installation of apps in pbw-cache from App Manager (long press menu)
+* Pebble: Fix regression which freezes Gadgetbridge when disconnecting via long-press menu
+
+#### Version 0.7.0
+* Read upcoming events (up to 7 days in the future). Requires READ_CALENDAR permission 
+* Fix double SMS on Sony Android and Android 6.0
+* Pebble: Support replying to SMS form the watch (canned replies)
+* Pebble: Allow installing apps compiled with SDK 2.x also on the basalt platform (Time, Time Steel)
+* Pebble: Fix decoding strings in appmessages from the pebble (fixes sending SMS from "Dialer for Pebble")
+* Pebble: Support incoming re-connections when device returns from "Airplane Mode" or "Stand-By Mode"
+* Pebble: Fix crash when turning off Bluetooth when connected on Android 6.0
+* Mi Band: reserve some alarm slots for alerting when upcoming events begin. NB: the band will vibrate at the start time of the event, android reminders are ignored
+* Mi Band: Display unique devices Names, not just "MI"
+* Some new and updated icons
+
+#### Version 0.6.9
+* Pebble: Store app details in pbw-cache and display them in app manager on firmware 3.x 
+* Pebble: Increase maximum notification body length from 255 to 512 bytes on firmware 3.x
+* Pebble: Support installing .pbl (language files) on firmware 3.x
+* Pebble: Correct setting the timezone on firmware 3.x (pebble expects the "ID" eg. Europe/Berlin)
+* Pebble: Show correct icon for activity tracker and watchfaces in app installer (language and fw icons still missing)
+* Pebble: Fix crash when trying to install files though a file manager which are located inside the pbw-cache on firmware 3.x
+* Support for deleting all activity data (in the 'Debug' screen)
+* Don't pop up the virtual keyboard when entering the Debug screen
+* Remove all pending notifications on quit
+* Mi Band: KitKat: hopefully fixed showing the progress bar during activity data synchronization (#155)
+* Mi Band 1S: hopefully fixed connection errors (#178) Notifications probably do not work yet, though
+
+#### Version 0.6.8
+* Mi Band: support for Firmware upgrade/downgrade on Mi Band 1A (white LEDs, no heartrate sensor)
+* Pebble: fix regression in 0.6.7 when installing pbw/pbz files from content providers (eg. download manager)
+* Pebble: fix installation of pbw files on firmware 3.x when using content providers (eg. download manager)   
+* Pebble: fix crash on firmware 3.x when pebble requests a pbw that is not in Gadgetbridge's cache 
++ Treat Signal notifications as chat notifications
+* Fix crash when contacts cannot be read on Android 6.0 (non-granted permissions)
+
+#### Version 0.6.7
+* Pebble: Allow installation of 3.x apps on OG Pebble (FW will be released soon)
+* Fix crashes on startup when logging is enabled or when entering the app manager on some phones
++ Fix Pebble being detected as MI when unpaired and auto-connect is enabled
+* Fix Crash when not having K9 Mail permissions (happens when installing K9 after Gadgetbridge) (#175)
+
+#### Version 0.6.6
+* Mi Band: Huge performance improvement fetching activity data
+* Mi Band: attempt at fixing connection problems (#156)
+* Pebble: Try to interpret sleep data from Misfit data
+* Fix exporting the activity database on devices with read-only external storage (#153)
+* Fix totally wrong sleep time in the sleep chart
+
+#### Version 0.6.5
+* Mi Band: Support "Locate Device" with Mi Band 1A (and Mi Band 1 with new firmware)
+* Pebble: Support syncing steps from Misfit (untested features must be turned on to see them), intensity=steps, no sleep support yet
+* Disable activity fetching when not supported
+* Small improvements to live activity charts
+
+#### Version 0.6.4
+* Support pull down to synchronize activity data (#138)
+* Display tabs in the Charts activity (#139)
+* Mi Band: initial support for Mi Band 1a (the one with white LEDs) (thanks @sarg) (#136)
+* Mi Band: Attempt at fixing problem with never finishing activity data fetching (#141, #142)
+* Register/unregister BroadcastReceivers instead of enabling/disabling them with PackageManager (#134)
+  (should fix disconnection because the service is being killed)
+
+#### Version 0.6.3
+* Pebble: support installation of language files (.pbl) on FW 2.x
+* Try to prevent service being killed by disallowing backups
+
+#### Version 0.6.2
+* Mi Band: support firmware version 1.0.10.14 (and onward?) vibration
+* Mi Band: get device name from official BT SIG endpoint
+* Mi Band: initial support for displaying live activity data, screen stays on
+
+#### Version 0.6.1
+* Pebble: Allow muting (blacklisting) Apps from within generic notifications on the watch
+* Pebble: Detect all known Pebble Versions including new "chalk" platform (Pebble Time Round)
+* Option to ignore phone calls (useful for Pebble Dialer)
+* Mi Band: Added progress bar for activity data transfer and fixes for firmware transfer progress bar
+* Bug fix for app blacklist (some checkboxes where wrongly drawn as checked)
+
+#### Version 0.6.0
+* Pebble: WIP implementation of PebbleKit Intents to make some 3rd party Android apps work with the Pebble (eg. Ventoo)
+* Pebble: Option to set re-connection attempts in settings (one attempt usually takes about 5 seconds)
+* Support controlling all audio players that react to media buttons (can be chosen in settings)
+* Treat SMS as generic notification if set to "never" (can be blacklisted there also if desired)
+* Treat Conversations messages as chat messages, even if arrived via Pebble Intents (nice icon for Pebble FW 3.x)
+* Allow opening firmware / app files from the download manager "app" (technically a content provider)
+* Mi Band: whitelisted a few firmware versions
+
+#### Version 0.5.4
+* Mi Band: allow the transfer of activity data without clearing MiBand's memory
+* Pebble: for generic notifications use generic icon instead of SMS icons on FW 3.x (thanks @roidelapluie)
+* Pebble: use different icons and background colors for specific groups of applications (chat, mail, etc) (thanks @roidelapluie)
+* In settings, support blacklisting apps for generic notifications
+
+#### Version 0.5.3
+* Pebble: For generic notifications, support dismissing individual notifications and "Open on Phone" feature (OG & PT)
+* Pebble: Allow to treat K9 notifications as generic notifications (if notification mode is set to never)
+* Ignore QKSMS notifications to avoid double notification for incoming SMS
+* Improved UI of Firmware/App installer
+* Device state again visible on lock screen
+* Date display and navigation now working properly for all charts
+
+#### Version 0.5.2
+* Pebble: support "dismiss all" action also on Pebble Time/FW 3.x notifications
+* Mi Band: show a notification when the battery is below 10%
+* Graphs are now using the same theme as the rest of the application
+* Graphs now show when the device was not worn by the user (for devices that send this information)
+* Remove unused settings option in charts view
+* Build target is now Android SDK 23 (Marshmallow)
+
+#### Version 0.5.1
+* Pebble: support taking screenshot from Pebble Time
+* Fix broken "find lost device" which was broken in 0.5.0
+
+#### Version 0.5.0
+* Mi Band: fix setting wear location
+* Pebble: experimental watchapp installation support for FW 3.x/Pebble Time
+* Pebble: support Pebble emulator via TCP connection (needs rebuild with INTERNET permission)
+* Pebble: use SMS/EMAIL icons for FW 3.x/Pebble Time
+* Pebble: do not throttle notifications
+* Support going forward/backwards in time in the activity charts
+* Various small bugfixes to the App/FW Installation Activity
+
+#### Version 0.4.6
+* Mi Band: Fixed negative number of steps displayed (#91)
+* Mi Band: fixed (re-) connection problems after band getting disconnected
+* Pebble: new option to enable untested code (enable only if you like bad surprises)
+* Pebble: always enable 2.x notifications with "dismiss all" action on FW 2.x (except for K9)
+* Fixed slight steps graph distortion through black text labels
+* Fixed control center activity and notification showing different device connection state
+* Small firmware installation improvements
+* Various refactoring and code cleanups
+
+#### Version 0.4.5
+* Enhancement to activity graphs: new graph showing the number of steps done today and in the last week
+* New preference to set the desired fitness goal (number of steps to walk in one day)
+* Mi Band: support for setting the fitness goal (the band will show the progress to the goal with the LEDs and vibrates when the goal is reached)
+* Mi Band: send the wear location (left / right hand) to the device
+* Mi Band: support for flashing firmware from .fw files (upgrades and downgrades are possible)
+* Fixed crash when synchronizing activity data in the graphs activity and changing device orientation
+
+#### Version 0.4.4
+* Set Gadgetbridge notification visibility to public, to show the connection status on the lock screen
+* Support for backup up and restoring of the activity database (via Debug activity)
+* Support for graceful upgrades and downgrades, keeping your activity database intact
+* Enhancement to activity graphs: new graphs for sleep data (only last night) accessible swiping right from the main graph
+* Enhancement to graphs activity: it is now possible to fetch the activity data directly from this activity
+* Pebble: experimental support for dismissing (all) notifications via actionable notifications (disabled by default)
+* Pebble: make FW 3.x notifications available by default
+* Mi Band: Set the graphs activity as the default action available with a single tap on the connected device
+
+#### Version 0.4.3
+* Mi Band: Support for setting alarms
+* Mi Band: Bug fix for activity data synchronization
+
+#### Version 0.4.2
+* Material style for Lollipop
+* Support for finding a lost device (vibrate until cancelled)
+* Mi Band: Support for vibration profiles, configurable for notifications
+* Pebble: Support taking screenshots from the device context menu (Pebble Time not supported yet)
+
+#### Version 0.4.1
+* New icons, thanks xphnx!
+* Improvements to Sleep Monitor charts
+* Pebble: use new Sleep Monitor for Morpheuz (previously Mi Band only)
+* Pebble: experimental support for FW 3.x notification protocol
+* Pebble: dev option to force latest notification protocol
+
+#### Version 0.4.0
+* Pebble: Initial Morpheuz protocol support for getting sleep data
+* Pebble: Support launching of watchapps though the AppManager Activity
+* Pebble: Support CM 12.1 default music app (Eleven)
+* Pebble: Fix firmware installation when all 8 app slots are in use
+* Pebble: Fix firmware installation when Pebble is in recovery mode
+* Pebble: Fix error when reinstalling apps, useful for upgrading/downgrading
+* Mi Band: Make vibration count configurable for different kinds of Notifications
+* Mi Band: Initial support for fetching activity data
+* Support rebooting Mi Band/Pebble through the Debug Activity
+* Add highly experimental sleep monitor (accessible via long press on a device)
+* Fix Debug activity (SMS and E-Mail buttons were broken)
+* Add Turkish translation contributed by Tarik Sekmen
+
+#### Version 0.3.5
+* Add discovery and pairing Activity for Pebble and Mi Band
+* Listen for Pebble Message Intents and forward notifications (used by Conversations)
+* Make strings translatable and add German, Italian, Russian, Spanish and Korean translations
+* Mi Band: Display battery status
+
+#### Version 0.3.4
+* Pebble: Huge speedup for app/firmware installation.
+* Pebble: Use a separate notification with progress bar for installation procedure
+* Pebble: Bug fix for being stuck while waiting for a slot, when none is available
+* Mi Band: Display connection status in notification (previously Pebble only)
+
+#### Version 0.3.3
+* Pebble: Try to reduce battery usage by acknowledging datalog packets
+* Mi Band: Set current time on the device (thanks to PR by @danielegobbetti)
+* More robust connection state handling and display
+
+#### Version 0.3.2
+* Mi Band: Fix for notifications only working after manual connection
+* Mi Band: Display firmware version
+* Pebble: Display hardware revision
+* Pebble: Check if firmware is compatible before allowing installation
+
+#### Version 0.3.1
+* Mi Band: Fix for notifications only working in Debug
+
+#### Version 0.3.0
+* Mi Band: Initial support (see README.md)
+* Pebble: Firmware installation (USE AT YOUR OWN RISK)
+* Pebble: Fix installation problems with certain .pbw files
+* Pebble: Volume control
+* Add icon for activity tracker apps (icon by xphnx)
+* Let the application quit when in reconnecting state
+
+#### Version 0.2.0
+* Experimental pbw installation support (watchfaces/apps)
+* New icons for device and app lists
+* Fix for device list not refreshing when Bluetooth gets turned on
+* Filter out annoying low battery notifications
+* Fix for crash on some devices when creating a debug notification
+* Lots of internal changes preparing multi device support
+
+#### Version 0.1.5
+* Fix for DST (summer time)
+* Option to sync time on connect (enabled by default)
+* Opening .pbw files with Gadgetbridge prints some package information
+  (This was not meant to be released yet, but the DST fix made a new release necessary)
+
+#### Version 0.1.4
+* New AppManager shows installed Apps/Watchfaces (removal possible via context menu)
+* Allow back navigation in ActionBar (Debug and AppManager Activities)
+* Make sure Intent broadcasts do not leave Gadgetbridge
+* Show hint in the Main Activity (tap to connect etc)
+
+#### Version 0.1.3
+* Remove the connect button, list all supported devices and connect on tap instead
+* Display connection status and firmware of connected devices in the device list
+* Remove quit button from the service notification, put a quit item in the context menu instead
+
+#### Version 0.1.2
+* Added option to start Gadgetbridge and connect automatically when Bluetooth is turned on
+* stop service if Bluetooth is turned off
+* try to reconnect if connection was lost
+
+#### Version 0.1.1
+* Fixed various bugs regarding K-9 Mail notifications.
+* "Generic notification support" in Setting now opens Androids "Notification access" dialog.
+
+#### Version 0.1.0
+* Initial release
diff --git a/CONTRIBUTORS.rst b/CONTRIBUTORS.rst
new file mode 100644
index 0000000..62a05ff
--- /dev/null
+++ b/CONTRIBUTORS.rst
@@ -0,0 +1,553 @@
+.. 2>/dev/null
+ names () 
+ { 
+ echo -e "\n exit;\n**Contributors (sorted by number of commits):**\n";
+ git log --format='%aN:%aE' origin/master | grep -Ev "(anonymous:|FYG_.*_bot_ignore_me|reformat-bot@freeyourgadget.org)" | sed 's/@users.github.com/@users.noreply.github.com/g' | awk 'BEGIN{FS=":"}{ct[$1]+=1;e[$1]=$2}END{for (i in e)  { n[i]=e[i];c[i]+=ct[i] }; for (a in e) print c[a]"\t* "a" <"n[a]">";}' | sort -n -r | cut -f 2-
+ }
+ quine () 
+ { 
+ { 
+ echo ".. 2>/dev/null";
+ declare -f names | sed -e 's/^[[:space:]]*/ /';
+ declare -f quine | sed -e 's/^[[:space:]]*/ /';
+ echo -e " quine\n";
+ names;
+ echo -e "\nAnd all the former Transifex translators, who cannot be listed automatically.\n\n*To update the contributors list just run this file with bash. Prefix a name with % in .mailmap to set a contact as preferred*"
+ } > CONTRIBUTORS.rst;
+ exit
+ }
+ quine
+
+
+ exit;
+**Contributors (sorted by number of commits):**
+
+* Andreas Shimokawa <shimokawa@fsfe.org>
+* Carsten Pfeiffer <cpfeiffer@users.noreply.github.com>
+* Jos茅 Rebelo <joserebelo@outlook.com>
+* Daniele Gobbetti <daniele+github@gobbetti.name>
+* Petr Van臎k <petr@linuks.cz>
+* Yaron Shahrabani <sh.yaron@gmail.com>
+* Daniel Dakhno <dakhnod@gmail.com>
+* 闄堝皯涓� <oshirisu.red@gmail.com>
+* Arjan Schrijver <arjan5@noreply.codeberg.org>
+* Vinc猫n PUJOL <vincen@vincen.org>
+* O臒uz Ersen <oguzersen@protonmail.com>
+* Allan Nordh酶y <epost@anotheragency.no>
+* Ihor Hordiichuk <igor_ck@outlook.com>
+* nautilusx <mail.ka@mailbox.org>
+* Taavi Eom盲e <taavi.eomae+github@gmail.com>
+* Gordon Williams <gw@pur3.co.uk>
+* Rafael Fontenelle <rafaelff@gnome.org>
+* Michal L <michalrmsmi@wp.pl>
+* Sebastian Kranz <lightwars@web.de>
+* Joa虄o Paulo Barraca <jpbarraca@gmail.com>
+* Linerly <linerly@protonmail.com>
+* Rex_sa <rex.sa@pm.me>
+* mamucho <mamut_killer@yahoo.com>
+* postsorino <postsorino@krutt.org>
+* Manuel-Senpai <senpai99@hotmail.com>
+* Andreas B枚hler <dev@aboehler.at>
+* FransM <fransmeulenbroeks@yahoo.com>
+* Jonas <jonasdcdm@posteo.net>
+* HenRy <helge1o1o1@gmail.com>
+* Yukai Li <yukaili.geek@gmail.com>
+* Roi Greenberg <roigreenberg@gmail.com>
+* gallegonovato <fran-carro@hotmail.es>
+* Nikita Epifanov <nikgreens@protonmail.com>
+* kirill blaze <kirillblaze2@gmail.com>
+* 脫scar Fern谩ndez D铆az <oscfdezdz@tuta.io>
+* Jeannette L <j.lavoie@net-c.ca>
+* Vadim Kaushan <admin@disasm.info>
+* protomors <protomors@gmail.com>
+* Cre3per <lukas96s@web.de>
+* Davis Mosenkovs <davikovs@gmail.com>
+* ssantos <ssantos@web.de>
+* Michael <quelbs@gmail.com>
+* glemco <glemco@codeberg>
+* 115ek <e.blosz@hotmail.de>
+* 0que <0que@users.noreply.hosted.weblate.org>
+* 小邪褕邪 袩械褌褉芯胁懈褯 <salepetronije@gmail.com>
+* naofum <naofum@gmail.com>
+* My Random Thoughts <weblate@myrandomthoughts.co.uk>
+* Damien 'Psolyca' Gaignon <damien.gaignon@gmail.com>
+* 0eoc <0eoc@users.noreply.hosted.weblate.org>
+* mesnevi <shams@airpost.net>
+* Kintu <kintukp@gmail.com>
+* youzhiran <2668760098@qq.com>
+* mueller-ma <mueller-ma@users.noreply.github.com>
+* ivanovlev <ivanovlev@mail.ru>
+* Tijl Schepens <tijl.schepens@hotmail.com>
+* Sophanimus <sennamails@googlemail.com>
+* Pavel Elagin <elagin.pasha@gmail.com>
+* NekoBox <nekobox@noreply.codeberg.org>
+* MPeter <>
+* MrYoranimo <yvulker@gmail.com>
+* mondstern <hello@mondstern.tk>
+* Hadri谩n Candela <hadrian.candela@gmail.com>
+* 脕cs Zolt谩n <acszoltan111@gmail.com>
+* Zhong Jianxin <azuwis@gmail.com>
+* Milo Ivir <mail@milotype.de>
+* Gabriele Monaco <monaco@eit.uni-kl.de>
+* foxstidious <foxstidious@gmail.com>
+* Andy Yang <a962702@yahoo.com>
+* Abdullah Manaz <manaz@noreply.codeberg.org>
+* Richard de Boer <git@tubul.net>
+* mkusnierz <>
+* Julien Pivotto <roidelapluie@inuits.eu>
+* tomechio <tomasz@salamon.fi>
+* Steffen Liebergeld <perl@gmx.org>
+* Skrripy <rozihrash.ya6w7@simplelogin.com>
+* Petr Kadlec <mormegil@centrum.cz>
+* Pavel <pavel.gorbanj@gmail.com>
+* Lem Dulfo <lemuel.dulfo@gmail.com>
+* Dmitriy Bogdanov <di72nn@gmail.com>
+* Olexandr Nesterenko <olexn@ukr.net>
+* Nevena Mircheva <nevena.mircheva@gmail.com>
+* musover <meoberto@mthree.es>
+* Matthieu Baerts <matttbe@gmail.com>
+* Felix Konstantin Maurer <maufl@maufl.de>
+* Axus Wizix <aw.ts@bk.ru>
+* Xtremo3 <a.lewicki95@gmail.com>
+* Utsob Roy <uroybd@gmail.com>
+* taras3333 <taras3333@gmail.com>
+* Sergey Trofimov <sarg@sarg.org.ru>
+* Sebastian Krey <sebastian@skrey.net>
+* Noodlez <contact@nathanielbarragan.xyz>
+* M. Hadi <hhhadddi@yahoo.com>
+* Martin Boonk <martin@boonk.info>
+* Lukas <lukas.edi@gmx.net>
+* Ganblejs <ganblejs@noreply.codeberg.org>
+* Deixondit <jperals@protonmail.com>
+* akasaka / Genjitsu Labs <vladkorotnev@gmail.com>
+* Szylu <chipolade@gmail.com>
+* Robert Barat <rbarat07@gmail.com>
+* Reza Almanda <rezaalmanda27@gmail.com>
+* Mario <mariomobla@gmail.com>
+* ksiwczynski <k.siwczynski@gmail.com>
+* JohnnySun <bmy001@gmail.com>
+* Gilles 脡milien MOREL <contact@gilles-morel.fr>
+* firekonstantin <firekonstantin@mail.ru>
+* bruh <quangtrung02hn16@gmail.com>
+* Uwe Hermann <uwe@hermann-uwe.de>
+* Patric Gruber <me@patric-gruber.at>
+* opavlov <forpoststuff@gmail.com>
+* Michalis <michalisntovas@yahoo.gr>
+* Mario Rossi <kk1o2n+61euckrwqwqecz3pme3@sharklasers.com>
+* ifurther <i.further.5.4@gmail.com>
+* Edoardo Rosa <edoardo.rosa90@gmail.com>
+* d <dmanye@gmail.com>
+* Bo偶ydar <trening302@o2.pl>
+* Alberto <albertsal83@gmail.com>
+* AiLab <vpuhoff92@gmail.com>
+* zsolt3991 <zsolt_93@yahoo.com>
+* winver <kirillstuzhuk@gmail.com>
+* Vladislav Serkov <vladserkoff@protonmail.com>
+* Vebryn <vebryn@gmail.com>
+* uli <cybuzuma@vnxs.de>
+* Ted Stein <me@tedstein.net>
+* sinore <sinoren263@niekie.com>
+* Shimon <simonfarm0@gmail.com>
+* Reiner Herrmann <reiner@reiner-h.de>
+* NicoBuntu <nicolas__du95@hotmail.fr>
+* Nee Sorry <sven.fasterding@posteo.de>
+* Marc Nause <marc.nause@audioattack.de>
+* Louis-Marie Croisez <louis.croisez@gmail.com>
+* Kry拧tof 膶ern媒 <cleverline1mc@gmail.com>
+* Johannes Krude <johannes@krude.de>
+* Jean-Fran莽ois Greffier <jf.greffier@gmail.com>
+* Hasan Ammar <ammarh@gmail.com>
+* Giuseppe Caliendo <giuseppe.caliendo@gmail.com>
+* Gergely Peidl <gergely@peidl.net>
+* Fabio Parri <parrif_ibb@yahoo.com>
+* Evo <weblate@verahawk.com>
+* Emre <wenigerpluesch@mailbox.org>
+* Elwood <elwood21@gmail.com>
+* Dmitry Markin <dmitry@markin.tech>
+* CE4 <chregger@gmail.com>
+* ce4 <ce4@posteo.de>
+* Baka Gaijin <lewdwarrior@waifu.club>
+* AndrewBedscastle <1462953+AndrewBedscastle@users.noreply.github.com>
+* abettenburg <a.bettenburg@gmail.com>
+* 0nse <0nse@users.noreply.github.com>
+* 袦邪泻褋懈屑 携泻懈屑褔褍泻 <xpinovo@gmail.com>
+* Ye Wint Htut Kyaw <oxygen.2521998@gmail.com>
+* xaos <xaos@noreply.codeberg.org>
+* Thomas <tutonis@gmail.com>
+* TheScientistPT <joao.ed.reis.gomes@gmail.com>
+* SnowCat <kittythesnowcat@protonmail.com>
+* Sergio Varela <sergitroll9@gmail.com>
+* Sebastian Obrusiewicz <sobrus2@o2.pl>
+* Sebastian Espinosa <hipypuff@gmail.com>
+* Robbert Gurdeep Singh <git@beardhatcode.be>
+* Rimas Raguli奴nas <rarimas@gmail.com>
+* mvn23 <schopdiedwaas@gmail.com>
+* Minori Hiraoka (氙鸽吀毽�) <minori@mnetwork.co.kr>
+* MASVA <masva10@gmail.com>
+* masakoodaa <masakoodaa@protonmail.com>
+* Marius Cornescu <marius_cornescu@yahoo.com>
+* mantas-p <megaliuz@gmail.com>
+* Lukas Veneziano <fs@venezilu.de>
+* LL <lu.lecocq@free.fr>
+* LizardWithHat <martin.betcher@gmail.com>
+* Lesur Frederic <contact@memiks.fr>
+* leela <53352@protonmail.com>
+* kukuruzka <anton.dan1657@gmail.com>
+* Kompact <joaorafael123@hotmail.com>
+* Kalle <hiwokey596@vip4e.com>
+* K0L0B0G <github@gorobav.ru>
+* Johann C. Rode <jcrode@ece.ucsb.edu>
+* jimman2003 <jim41825@gmail.com>
+* jfgreffier <jfgreffier@users.noreply.github.com>
+* Jasper <jespiex456@hotmail.com>
+* ITCactus <itcactus@noreply.codeberg.org>
+* illis <illis@beyond8labs.com>
+* Francesco Marinucci <francesco.marinucci@posteo.net>
+* FintasticMan <finlay.neon.kid@gmail.com>
+* Doma Gerg艖 <domag02@gmail.com>
+* Dikay900 <dark900@xyz.de>
+* Denis <korden@sky-play.ru>
+* Christian Fischer <sw-dev@computerlyrik.de>
+* Benjamin Swartley <reep236@gmail.com>
+* Asbesbopispa <c.alfano+github@outlook.it>
+* Adolfo Jayme Barrientos <fitojb@ubuntu.com>
+* 6arms1leg <m.brnsfld@googlemail.com>
+* Your Name <you@example.com>
+* XqweX <xqwex@seznam.cz>
+* walkjivefly <mark@walkjivefly.com>
+* WaldiS <admin@sto.ugu.pl>
+* Vytenis <slivinskasvytenis@gmail.com>
+* Vladislav Glinsky <cl0ne@mithril.org.ua>
+* vishnu <vishnu012@protonmail.com>
+* Vianney le Cl茅ment de Saint-Marcq <vianney@noreply.codeberg.org>
+* Toby Murray <toby.murray+gitkraken@protonmail.com>
+* thyttan <6uuxstm66@mozmail.com>
+* Thorsten <js1_531b3pm29ng2@byom.de>
+* Stephan Lachnit <stephanlachnit@protonmail.com>
+* Sebastian Reichel <sre@ring0.de>
+* Saul Nunez <saul.nunez99@gmail.com>
+* Rui Mendes <xz9@protonmail.com>
+* roolx <rbzikadze@gmail.com>
+* rarder44 <rarder44@noreply.codeberg.org>
+* rany <ranygh@riseup.net>
+* Ranved Sticon <the7bulk@gmail.com>
+* Rajesh Kumbhakar <sssraj.sssraj@gmail.com>
+* Ptilopsis Leucotis <PtilopsisLeucotis@yandex.com>
+* petronovak <petro.novak@gmail.com>
+* Pascal <pascal.tannich@gmail.com>
+* odavo32nof <odavo32nof@noreply.codeberg.org>
+* octospacc <octo@tutamail.com>
+* NotAFIle <nota@notafile.com>
+* Normano64 <per.bergqwist@gmail.com>
+* Nikolay Korotkiy <sikmir@gmail.com>
+* Nick Spacek <peter.spacek@siemens.com>
+* Nekromanser <ari.taitto@protonmail.com>
+* Nathan <bonnemainsnathan@gmail.com>
+* narektor <narektor@noreply.codeberg.org>
+* MyTimeKill <26295589+MyTimeKill@users.noreply.github.com>
+* Moln谩r Barnab谩s <nsd4rkn3ss@gmail.com>
+* Moarc <aldwulf@gmail.com>
+* Michal Novotny <mignov@gmail.com>
+* maxvel <maxvel@noreply.codeberg.org>
+* Maxime Reyrolle <dev@reyrolle.fr>
+* Mattias M眉nster <mattiasmun@gmail.com>
+* Mattherix <mattherix@protonmail.com>
+* Martin <ritualz@users.noreply.github.com>
+* marco.altomonte <marco.altomonte@gmail.com>
+* Le Poisson Libre <services@spika.xyz>
+* Krzysztof Marcinek <krzymar2002@gmail.com>
+* krzys_h <krzys_h@interia.pl>
+* Konrad Iturbe <KonradIT@users.noreply.github.com>
+* Kamalei Zestri <38802353+KamaleiZestri@users.noreply.github.com>
+* Joel Beckmeyer <joel@beckmeyer.us>
+* Jes煤s <zaagur@gmail.com>
+* Jes煤s F <jfmiguel@wanadoo.es>
+* Irul <wedesignthing@gmail.com>
+* Igor Polyakov <igorpolyakov@protonmail.com>
+* homocomputeris <homocomputeris+git@gmail.com>
+* Grzegorz <grzesjam@duck.com>
+* GeekosaurusR3x <skad@protonmail.com>
+* Francesco Franchina <cescus92@gmail.com>
+* fparri <fparri@noreply.codeberg.org>
+* Fabien Brachere <fabien@brachere.net>
+* exit-failure <hakrala@web.de>
+* Ertu (Er2, Err) <er2official@outlook.com>
+* Er2 <er2@dismail.de>
+* Dreamwalker <aristojeff@gmail.com>
+* DAWID <aggregate_diffused400@simplelogin.com>
+* Dario Lopez-K盲sten <cl2dlope@gmail.com>
+* Da Pa <da.pa@noreply.codeberg.org>
+* DanialHanif <danialhanif@outlook.com>
+* Cristian Alfano <c.alfano@outlook.it>
+* criogenic <criogenic@gmail.com>
+* chabotsi <chabotsi+github@chabotsi.fr>
+* bowornsin <bowornsin@gmail.com>
+* Avamander <Avamander@users.noreply.github.com>
+* Artem <KovalevArtem.ru@gmail.com>
+* AnthonyDiGirolamo <anthony.digirolamo@gmail.com>
+* Anonymous <noreply@weblate.org>
+* Andreas Kromke <Andreas.Kromke@web.de>
+* Alex <leha-bot@yandex.ru>
+* Albert <albert@avdm043>
+* Ain膩rs <ainars71@inbox.lv>
+* 毓亘丿丕賱乇卅賵賮 毓丕亘丿蹖 <abdolraoofabedi@gmail.com>
+* 袝谐芯褉 袝褉屑邪泻芯胁 <eg.ermakov2016@yandex.ru>
+* 獠嗏瞾獠涒矒 桅i <daniphii@outlook.com>
+* Yusuf Cihan <yusufcihandemirbas@gmail.com>
+* yk <bivol21883@cyadp.com>
+* Yar <yaroslav.isakov@gmail.com>
+* xzovy <caleb@caleb-cooper.net>
+* xphnx <xphnx@users.noreply.github.com>
+* Xos茅 M <xosem@disroot.org>
+* Xeoy <arventh@pm.me>
+* Xavier RENE-CORAIL <xavier.renecorail@gmail.com>
+* x29a <x29a@noreply.codeberg.org>
+* w2q <w2q@noreply.codeberg.org>
+* Vitaliy Shuruta <vshuruta@gmail.com>
+* veecue <veecue@ventos.tk>
+* Unixware <csynt@hotmail.com>
+* TylerWilliamson <tyler.williamson51@gmail.com>
+* Triet Pham <triet.phm@gmail.com>
+* Traladarer <Traladarer@users.noreply.hosted.weblate.org>
+* Tomer Rosenfeld <tomerosenfeld007@gmail.com>
+* Tomas Radej <tradej@redhat.com>
+* t-m-w <t-m-w-codeberg@galac.tk>
+* tiparega <11555126+tiparega@users.noreply.github.com>
+* TinfoilSubmarine <tinfoilsubmarine@noreply.codeberg.org>
+* Tim <tim.w1995@gmail.com>
+* thirschbuechler <thirschbuechler@github.com>
+* Thiago Rodrigues <thiaguinho.the@gmail.com>
+* thermatk <thermatk@noreply.codeberg.org>
+* theghostofheathledger <theghostofheathledger@noreply.codeberg.org>
+* Temuri Doghonadze <temuri.doghonadze@gmail.com>
+* Tarik Sekmen <tarik@ilixi.org>
+* Szymon Tomasz Stefanek <s.stefanek@gmail.com>
+* szilardx <15869670+szilardx@users.noreply.github.com>
+* Swann Martinet <swann.ranskassa@laposte.net>
+* Stefan Bora <stefan.bora@outlook.de>
+* Stan Gomin <stan@gomin.me>
+* ssilverr <ssilverr@noreply.codeberg.org>
+* Sky233ml <sky233@sky233.ml>
+* SinMan <emilio.galvan@gmail.com>
+* Simon Sievert <ssievert@protonmail.com>
+* Sergio Lopez <slp@sinrega.org>
+* Sergey Vasilyev <koniponi@gmail.com>
+* sedy89 <sedy89@noreply.codeberg.org>
+* Sebastian Nilsson <sebbe_kompis@hotmail.com>
+* S Dantas <dantasosteney@gmail.com>
+* Santiago Benalc谩zar <santiagosdbc@gmail.com>
+* Samuel Carvalho de Ara煤jo <samuelnegro12345@gmail.com>
+* Sami Alaoui <4ndroidgeek@gmail.com>
+* Saman rsh <sasan.r75@gmail.com>
+* Salif Mehmed <mail@salif.eu>
+* SalavatR <salavat.rakhmanov@gmail.com>
+* Roxystar <roxystar@arcor.de>
+* Roman Plevka <rplevka@redhat.com>
+* rom4nik <codeberg@rom4nik.pl>
+* Robin Davidsson <robindavidsson@outlook.com>
+* Roberto P. Rubio <outtakes_handgun090@familiapm.com>
+* rober <rober@prtl.nodomain.net>
+* Rivo Z盲ngov <rivozangov@gmail.com>
+* rimasx <riks_12@hot.ee>
+* rikka356 <rikka356@outlook.com>
+* Richard Finegold <goldfndr+git@gmail.com>
+* Retew <salau@tutanota.com>
+* redking <redking974@gmail.com>
+* Ray <RayL-er@outlook.com>
+* RandomItalianGuy <donatoperruso@protonmail.com>
+* Raghd Hamzeh <raghd@rhamzeh.com>
+* Quang Ng么 <ngoquang2708@gmail.com>
+* Quallenauge <Hamsi2k@freenet.de>
+* Q-er <9142398+Q-er@users.noreply.github.com>
+* pommes <pommes@noreply.codeberg.org>
+* pishite <pishite@meta.ua>
+* Perflyst <mail@perflyst.de>
+* Pavel Motyrev <legioner.r@gmail.com>
+* Pauli Salmenrinne <susundberg@gmail.com>
+* pangwalla <pangwalla@noreply.codeberg.org>
+* Pander <pander@users.sourceforge.net>
+* ozkanpakdil <ozkan.pakdil@gmail.com>
+* opcode <luigi@sciolla.net>
+* Ond艡ej Sedl谩膷ek <ond.sedlacek@gmail.com>
+* Olivier Bloch <blochjunior@gmail.com>
+* Oleg Vasilev <oleg.vasilev@virtuozzo.com>
+* Oleg <oleg.invisibleman@protonmail.com>
+* Nur Aiman Fadel <nuraiman@gmail.com>
+* Nikolai Sinyov <nikolay.sinyov@yandex.ru>
+* Nicol貌 Balzarotti <anothersms@gmail.com>
+* Nephiel <Nephiel@users.noreply.github.com>
+* Nathan Philipp Bo Seddig <natpbs@gmail.com>
+* Natanael Arndt <arndtn@gmail.com>
+* Nabil BENDAFI <nabil@bendafi.fr>
+* myxor <myxor@noreply.codeberg.org>
+* Morten Rieger Hannemose <mohan@dtu.dk>
+* Mirko Covizzi <mrkcvzz@gmail.com>
+* Milan 艩alka <salka.milan@googlemail.com>
+* Mike van Rossum <mike@vanrossum.net>
+* mika laka <Mikhaila.Eaddy@easymailer.live>
+* Michal Novak <michal.novak@post.cz>
+* Michael Wiesinger <michw2014@gmail.com>
+* michaelneu <git@michaeln.eu>
+* MedusasSphinx <medusassphinx@noreply.codeberg.org>
+* McSym28 <McSym28@users.noreply.github.com>
+* MaxL <z60loa8qw3umzu3@my10minutemail.com>
+* maxirnilian <maxirnilian@users.noreply.github.com>
+* Maxim Baz <git@maximbaz.com>
+* Mave95 <mave95@noreply.codeberg.org>
+* Matej Drobni膷 <matejdro@gmail.com>
+* Marvin D <mave95@posteo.de>
+* Martin Piatka <chachacha2323@gmail.com>
+* Martin.JM <>
+* Margreet <margreetkeelan@gmail.com>
+* Marc Schlaich <marc.schlaich@googlemail.com>
+* Marco Alberto Diosdado Nava <betoxxdiosnava@gmail.com>
+* Marco A <35718078+TomasCartman@users.noreply.github.com>
+* Marc Laporte <marc@laporte.name>
+* Marcin <ml.cichy@gmail.com>
+* Marcel pl (m4rcel) <marcel.garbarczyk@gmail.com>
+* Manuel Soler <vg8020@gmail.com>
+* Manuel Ru脽 <manuel_russ@dismail.de>
+* mangel <mangelcursos@gmail.com>
+* magimel.francois <magimel.francois@gmail.com>
+* Maciej Ku艣nierz <>
+* m4sk1n <me@m4sk.in>
+* LukasEdl <lukasedl@noreply.codeberg.org>
+* LuK1337 <priv.luk@gmail.com>
+* Luiz Felipe das Neves Lopes <androidfelipe23@gmail.com>
+* Luis zas <dalues@gmail.com>
+* Ludovic Jozeau <unifai@protonmail.com>
+* luca sain <luca.sain@outlook.com>
+* lucanomax <lucano.valo@gmail.com>
+* Liao junchao <liaojunchao@outlook.com>
+* Leon Omelan <rozpierog@gmail.com>
+* Leonardo Amaral <contato@leonardoamaral.com.br>
+* Leo bonilla <leo_lf9@hotmail.com>
+* LeJun <lejun@gmx.fr>
+* Lejun <adrienzhang@hotmail.com>
+* lazarosfs <lazarosfs@csd.auth.gr>
+* Lars Vogdt <lars.vogdt@suse.com>
+* ladbsoft <30509719+ladbsoft@users.noreply.github.com>
+* Kyaw Min Khant <kyawmink@gmail.com>
+* Kriszti谩n G谩ncs <990024@gmail.com>
+* Kristjan R盲ts <kristjanrats@gmail.com>
+* Korn茅l Schmidt <kornel.schmidt@clubspot.app>
+* kirk1984 <kirk1984@noreply.codeberg.org>
+* kieranc001 <kieranc001@noreply.codeberg.org>
+* kevlarcade <kevlarcade@gmail.com>
+* Kevin Richter <me@kevinrichter.nl>
+* Kevin MacMartin <prurigro@gmail.com>
+* keeshii <keeshii@ptcg.eu>
+* Kaz Wolfe <root@kazwolfe.io>
+* Kasha <kasha_malaga@hotmail.com>
+* kalaee <alex.kalaee@gmail.com>
+* Julien Winning <heijulien@web.de>
+* Julian Lam <julian@nodebb.org>
+* jugendhacker <jugendhacker@gmail.com>
+* Joseph Kim <official.jkim@gmail.com>
+* jonnsoft <>
+* Johannes Tysiak <vinyl@users.sf.net>
+* Jochen S <tsalin@noreply.codeberg.org>
+* joaquim.org <joaquim.org@gmail.com>
+* jhey <jhey@noreply.codeberg.org>
+* JF <jf@codingfield.com>
+* Jean-Fran莽ois Milants <jf@codingfield.com>
+* jcrode <46062294+jcrode@users.noreply.github.com>
+* Jan Lolek <janlolek@seznam.cz>
+* Jakub Jel铆nek <jakub.jelinek@gmail.com>
+* Jacque Fresco <aidter@use.startmail.com>
+* Izzy <izzy@qumran.org>
+* iwonder <hosted.weblate.org@heychris.eu>
+* Ivan <ivan_tizhanin@mail.ru>
+* InternalErrorX <internalerrorx@noreply.codeberg.org>
+* H眉seyin Aslan <ha098784@gmail.com>
+* Hugel <qihu@nfschina.com>
+* hr-sales <hericsonregis@hotmail.com>
+* Hirnchirurg <anonymous11@posteo.net>
+* Hen Ry <nobo@go4more.de>
+* HelloCodeberg <hellocodeberg@noreply.codeberg.org>
+* HardLight <hardlightxda@gmail.com>
+* Hanhan Husna <matcherapy@gmail.com>
+* halemmerich <halemmerich@noreply.codeberg.org>
+* hackoder <hackoder@noreply.codeberg.org>
+* Gustavo Ramires <gustavo.nramires@gmail.com>
+* gsbhat <>
+* Grzegorz Dznsk <grantmlody96@gmail.com>
+* Golbinex <2061409-Golbinex@users.noreply.gitlab.com>
+* gnufella <gnufella@noreply.codeberg.org>
+* gnu-ewm <gnu.ewm@protonmail.com>
+* Gleb Chekushin <mail@glebchek.com>
+* Giuseppe <giuseppe.parasilitipalumbo@studium.unict.it>
+* Gide茫o Gomes Ferreira <trjctr@gmail.com>
+* gfwilliams <gfwilliams@noreply.codeberg.org>
+* GabO <gabosuelto@gmail.com>
+* Gabe Schrecker <gabe@pbrb.co.uk>
+* freezed-or-frozen <freezed.or.frozen@gmail.com>
+* Frank Slezak <KazWolfe@users.noreply.github.com>
+* Frank Ertl <hrglpfrmpf@noreply.codeberg.org>
+* Florian Beuscher <florianbeuscher@gmail.com>
+* Fabian Hof <weblate@fabian-hof.de>
+* 脡tienne Deparis <etienne@depar.is>
+* Est茅bastien Robespi <estebastien@mailbox.org>
+* Ernst <ernst@seebens.de>
+* Enrico Brambilla <enricobilla@noreply.codeberg.org>
+* Edoardo Tronconi <edoardo.tronconi@gmail.com>
+* Dougal19 <4662351+Dougal19@users.noreply.github.com>
+* Donato <pread.xa4mx@simplelogin.com>
+* Dmytro Bielik <mitrandir.hex@gmail.com>
+* djurik <dirceu.semighini@protonmail.com>
+* DerFetzer <kontakt@der-fetzer.de>
+* Dean <3114661520@qq.com>
+* Deactivated Account <diastasis@gmail.com>
+* David Gir贸n <contacto@duhowpi.net>
+* Davide Corradini <updates+weblate.org@corradinidavi.de>
+* Daniel Thompson <daniel@redfelineninja.org.uk>
+* Daniel Hauck <maill@dhauck.eu>
+* Dam BOND <dambond2001@gmail.com>
+* 鞚挫爼頋� <daemul72@gmail.com>
+* Dachi G <duchy007@yahoo.com>
+* C <weblate@wolki.de>
+* cokecodecock <lights1140977891@163.com>
+* CodeSpoof <nao.s_l_t_e_e_l@protonmail.com>
+* C O <cosmin.oprisan@gmail.com>
+* clach04 <Chris.Clark@actian.com>
+* Chris Perelstein <chris.perelstein@gmail.com>
+* chklump <chklump@noreply.codeberg.org>
+* C茅dric Bellegarde <cedric.bellegarde@adishatz.org>
+* Carlos Ferreira <calbertoferreira@gmail.com>
+* C0rn3j <spleefer90@gmail.com>
+* ButterflyOfFire <ButterflyOfFire@protonmail.com>
+* bucala <marcel.bucala@gmail.com>
+* boun <boun@gmx.de>
+* BobIsMyManager <bobismymanager@noreply.codeberg.org>
+* Bilel MEDIMEGH <bilel.medimegh@gmail.com>
+* Benjamin Kahlau <nyhkkbjyek@roanapur.de>
+* Ben <ben.david.wallner@gmail.com>
+* beardhatcode <beardhatcode@noreply.codeberg.org>
+* batataspt@gmail.com <batataspt@gmail.com>
+* atkyritsis <at.kyritsis@gmail.com>
+* Ascense <ascense@noreply.codeberg.org>
+* Aprilhoomie <Aprilhoomie@gmail.com>
+* apre <adrienpre+github@gmail.com>
+* Ann Test <testkimochiaz@gmail.com>
+* Aniruddha Adhikary <aniruddha@adhikary.net>
+* angelpup <angelpup@noreply.codeberg.org>
+* Anemograph <dyraybn@gmail.com>
+* Andrzej Surowiec <emeryth@gmail.com>
+* Andrew Watkins <randnv20@noreply.codeberg.org>
+* andrewlytvyn <indusfreelancer@gmail.com>
+* AndrewH <36428679+andrewheadricke@users.noreply.github.com>
+* andre <andre.buesgen@yahoo.de>
+* Andrea Lepori <mafaldo@hotmail.it>
+* Allen B <28495335+Allen-B1@users.noreply.github.com>
+* Alicia Hormann <ahormann@gmx.net>
+* Alfeu Lucas Guedes dos Santos <alfeugds@gmail.com>
+* Alexey Afanasev <avafanasiev@gmail.com>
+* Alexandra Sevostyanova <asevostyanova@gmail.com>
+* Aidan Crane <aidancrane78@gmail.com>
+* aerowolf <aerowolf@tom.com>
+* Adam B眉chner <buechner.adam@gmx.de>
+* a b <65567823+abb128@users.noreply.github.com>
+
+And all the former Transifex translators, who cannot be listed automatically.
+
+*To update the contributors list just run this file with bash. Prefix a name with % in .mailmap to set a contact as preferred*
diff --git a/GBDaoGenerator/.gitignore b/GBDaoGenerator/.gitignore
new file mode 100644
index 0000000..81631c6
--- /dev/null
+++ b/GBDaoGenerator/.gitignore
@@ -0,0 +1,2 @@
+/bin
+/build
diff --git a/GBDaoGenerator/build.gradle b/GBDaoGenerator/build.gradle
new file mode 100644
index 0000000..89d2029
--- /dev/null
+++ b/GBDaoGenerator/build.gradle
@@ -0,0 +1,70 @@
+apply plugin: 'java'
+apply plugin: 'maven-publish'
+apply plugin:'application'
+
+archivesBaseName = 'gadgetbridge-daogenerator'
+
+dependencies {
+    // https://github.com/Freeyourgadget/greenDAO/tree/fyg
+    implementation 'org.greenrobot:greendao-generator:2.2.0'
+}
+
+sourceSets {
+    main {
+        java {
+            srcDir 'src'
+        }
+    }
+}
+
+mainClassName = "nodomain.freeyourgadget.gadgetbridge.daogen.GBDaoGenerator"
+
+tasks.register('genSources', JavaExec) {
+    mainClass = mainClassName
+    classpath = sourceSets.main.runtimeClasspath
+    workingDir = '../'
+    doLast {
+        // 瀹樻柟 Gadgetbridge 浣跨敤 Freeyourgadget/greenDAO 鍒嗘敮锛屼細鐢熸垚
+        // PRIMARY KEY ("TIMESTAMP","DEVICE_ID")銆傛湰浠撳簱浠嶇敤鍘熺増 greendao-generator锛�
+        // 浼氭妸澶嶅悎涓婚敭鍐欐垚涓ゅ垪鍚勮嚜 PRIMARY KEY锛屾柊绯荤粺 SQLite锛堝惈鍗庝负锛変細鐩存帴鎶ラ敊闂��銆�
+        def dir = file("${project.rootDir}/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities")
+        int patched = 0
+        dir.eachFileMatch(~/.*Dao\.java/) { f ->
+            def src = f.getText('UTF-8')
+            def methodRe = ~/(?s)(public static void createTable\(SQLiteDatabase db, boolean ifNotExists\) \{.*?\n    \})/
+            def next = src.replaceAll(methodRe) { all, method ->
+                def pks = []
+                def colRe = ~/"\\"([A-Z0-9_]+)\\" (?:INTEGER|TEXT|REAL|BLOB) PRIMARY KEY/
+                (method =~ colRe).each { m -> pks << m[1] }
+                if (pks.size() <= 1) return method
+                def out = method.replaceAll(/ PRIMARY KEY/, '')
+                def pkSql = 'PRIMARY KEY (\\"' + pks.join('\\", \\"') + '\\")'
+                def closeIdx = out.indexOf(');");')
+                if (closeIdx < 0) return method
+                return out.substring(0, closeIdx) + ', ' + pkSql + ');");' + out.substring(closeIdx + 5)
+            }
+            if (next != src) {
+                f.write(next, 'UTF-8')
+                patched++
+            }
+        }
+        println "fixCompositePrimaryKeys: patched ${patched} DAO files"
+    }
+}
+
+artifacts {
+    archives jar
+}
+
+java {
+    sourceCompatibility = JavaVersion.VERSION_1_8
+    targetCompatibility = JavaVersion.VERSION_1_8
+}
+
+tasks.named("distTar") {
+    duplicatesStrategy = 'include'
+}
+
+tasks.named("distZip") {
+    duplicatesStrategy = 'include'
+}
diff --git a/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java b/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java
new file mode 100644
index 0000000..73cd6e2
--- /dev/null
+++ b/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java
@@ -0,0 +1,1591 @@
+/*
+ * Copyright (C) 2011 Markus Junginger, greenrobot (http://greenrobot.de)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package nodomain.freeyourgadget.gadgetbridge.daogen;
+
+import de.greenrobot.daogenerator.DaoGenerator;
+import de.greenrobot.daogenerator.Entity;
+import de.greenrobot.daogenerator.Index;
+import de.greenrobot.daogenerator.Property;
+import de.greenrobot.daogenerator.Schema;
+
+/**
+ * Generates entities and DAOs for the example project DaoExample.
+ * Automatically run during build.
+ */
+public class GBDaoGenerator {
+
+    private static final String VALID_FROM_UTC = "validFromUTC";
+    private static final String VALID_TO_UTC = "validToUTC";
+    private static final String MAIN_PACKAGE = "nodomain.freeyourgadget.gadgetbridge";
+    private static final String MODEL_PACKAGE = MAIN_PACKAGE + ".model";
+    private static final String VALID_BY_DATE = MODEL_PACKAGE + ".ValidByDate";
+    private static final String ACTIVITY_SUMMARY = MODEL_PACKAGE + ".ActivitySummary";
+    private static final String OVERRIDE = "@Override";
+    private static final String SAMPLE_RAW_INTENSITY = "rawIntensity";
+    private static final String SAMPLE_STEPS = "steps";
+    private static final String SAMPLE_RAW_KIND = "rawKind";
+    private static final String SAMPLE_HEART_RATE = "heartRate";
+    private static final String SAMPLE_HRV_WEEKLY_AVERAGE = "weeklyAverage";
+    private static final String SAMPLE_HRV_LAST_NIGHT_AVERAGE = "lastNightAverage";
+    private static final String SAMPLE_HRV_LAST_NIGHT_5MIN_HIGH = "lastNight5MinHigh";
+    private static final String SAMPLE_HRV_BASELINE_LOW_UPPER = "baselineLowUpper";
+    private static final String SAMPLE_HRV_BASELINE_BALANCED_LOWER = "baselineBalancedLower";
+    private static final String SAMPLE_HRV_BASELINE_BALANCED_UPPER = "baselineBalancedUpper";
+    private static final String SAMPLE_HRV_STATUS_NUM = "statusNum";
+    private static final String SAMPLE_HRV_VALUE = "value";
+    private static final String SAMPLE_TEMPERATURE = "temperature";
+    private static final String SAMPLE_TEMPERATURE_TYPE = "temperatureType";
+    private static final String SAMPLE_WEIGHT_KG = "weightKg";
+    private static final String TIMESTAMP_FROM = "timestampFrom";
+    private static final String TIMESTAMP_TO = "timestampTo";
+
+
+    public static void main(String[] args) throws Exception {
+        final Schema schema = new Schema(92, MAIN_PACKAGE + ".entities");
+
+        Entity userAttributes = addUserAttributes(schema);
+        Entity user = addUserInfo(schema, userAttributes);
+
+        Entity deviceAttributes = addDeviceAttributes(schema);
+        Entity device = addDevice(schema, deviceAttributes);
+
+        // yeah deep shit, has to be here (after device) for db upgrade and column order
+        // because addDevice adds a property to deviceAttributes also....
+        deviceAttributes.addStringProperty("volatileIdentifier");
+
+        Entity tag = addTag(schema);
+        Entity userDefinedActivityOverlay = addActivityDescription(schema, tag, user);
+
+        addMakibesHR3ActivitySample(schema, user, device);
+        addMiBandActivitySample(schema, user, device);
+        addHuamiExtendedActivitySample(schema, user, device);
+        addHuamiStressSample(schema, user, device);
+        addHuamiSpo2Sample(schema, user, device);
+        addHuamiHeartRateManualSample(schema, user, device);
+        addHuamiHeartRateMaxSample(schema, user, device);
+        addHuamiHeartRateRestingSample(schema, user, device);
+        addHuamiPaiSample(schema, user, device);
+        addHuamiSleepRespiratoryRateSample(schema, user, device);
+        addXiaomiActivitySample(schema, user, device);
+        addXiaomiSleepTimeSamples(schema, user, device);
+        addHeartPulseSamples(schema, user, device);
+        addXiaomiSleepStageSamples(schema, user, device);
+        addXiaomiManualSamples(schema, user, device);
+        addXiaomiDailySummarySamples(schema, user, device);
+        addCmfActivitySample(schema, user, device);
+        addCmfStressSample(schema, user, device);
+        addCmfSpo2Sample(schema, user, device);
+        addCmfSleepSessionSample(schema, user, device);
+        addCmfSleepStageSample(schema, user, device);
+        addCmfHeartRateSample(schema, user, device);
+        addCmfWorkoutGpsSample(schema, user, device);
+        addPebbleHealthActivitySample(schema, user, device);
+        addPebbleHealthActivityKindOverlay(schema, user, device);
+        addPebbleMisfitActivitySample(schema, user, device);
+        addPebbleMorpheuzActivitySample(schema, user, device);
+        addHPlusHealthActivityKindOverlay(schema, user, device);
+        addHPlusHealthActivitySample(schema, user, device);
+        addNo1F1ActivitySample(schema, user, device);
+        addXWatchActivitySample(schema, user, device);
+        addZeTimeActivitySample(schema, user, device);
+        addID115ActivitySample(schema, user, device);
+        addJYouActivitySample(schema, user, device);
+        addWatchXPlusHealthActivitySample(schema, user, device);
+        addWatchXPlusHealthActivityKindOverlay(schema, user, device);
+        addTLW64ActivitySample(schema, user, device);
+        addLefunActivitySample(schema, user, device);
+        addLefunBiometricSample(schema, user, device);
+        addLefunSleepSample(schema, user, device);
+        addSonySWR12Sample(schema, user, device);
+        addBangleJSActivitySample(schema, user, device);
+        addCasioGBX100Sample(schema, user, device);
+        addFitProActivitySample(schema, user, device);
+        addPineTimeActivitySample(schema, user, device);
+        addWithingsSteelHRActivitySample(schema, user, device);
+        addHybridHRActivitySample(schema, user, device);
+        addHybridHRSpo2Sample(schema, user, device);
+        addVivomoveHrActivitySample(schema, user, device);
+        addGarminFitFile(schema, user, device);
+        addGarminActivitySample(schema, user, device);
+        addGarminStressSample(schema, user, device);
+        addGarminBodyEnergySample(schema, user, device);
+        addGarminSpo2Sample(schema, user, device);
+        addGarminSleepStageSample(schema, user, device);
+        addGarminEventSample(schema, user, device);
+        addGarminHrvSummarySample(schema, user, device);
+        addGarminHrvValueSample(schema, user, device);
+        addGarminRespiratoryRateSample(schema, user, device);
+        addGarminHeartRateRestingSample(schema, user, device);
+        addGarminRestingMetabolicRateSample(schema, user, device);
+        addGarminSleepStatsSample(schema, user, device);
+        addGarminIntensityMinutesSample(schema, user, device);
+        addPendingFile(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);
+        addFemometerVinca2TemperatureSample(schema, user, device);
+        addMiScaleWeightSample(schema, user, device);
+        addColmiActivitySample(schema, user, device);
+        addColmiHeartRateSample(schema, user, device);
+        addColmiSpo2Sample(schema, user, device);
+        addColmiStressSample(schema, user, device);
+        addColmiSleepSessionSample(schema, user, device);
+        addColmiSleepStageSample(schema, user, device);
+        addColmiHrvValueSample(schema, user, device);
+        addColmiHrvSummarySample(schema, user, device);
+
+        addHuaweiActivitySample(schema, user, device);
+
+        Entity huaweiWorkoutSummary = addHuaweiWorkoutSummarySample(schema, user, device);
+        addHuaweiWorkoutDataSample(schema, huaweiWorkoutSummary);
+        addHuaweiWorkoutPaceSample(schema, huaweiWorkoutSummary);
+        addHuaweiWorkoutSwimSegmentsSample(schema, huaweiWorkoutSummary);
+
+        Entity huaweiDictData = addHuaweiDictData(schema, user, device);
+        addHuaweiDictDataValues(schema, huaweiDictData);
+
+        addCalendarSyncState(schema, device);
+        addAlarms(schema, user, device);
+        addReminders(schema, user, device);
+        addWorldClocks(schema, user, device);
+        addContacts(schema, user, device);
+        addAppSpecificNotificationSettings(schema, device);
+        addCyclingSample(schema, user, device);
+
+        Entity notificationFilter = addNotificationFilters(schema);
+
+        addNotificationFilterEntry(schema, notificationFilter);
+
+        addActivitySummary(schema, user, device);
+        addBatteryLevel(schema, device);
+        new DaoGenerator().generateAll(schema, "app/src/main/java");
+    }
+
+    private static Entity addTag(Schema schema) {
+        Entity tag = addEntity(schema, "Tag");
+        tag.addIdProperty();
+        tag.addStringProperty("name").notNull();
+        tag.addStringProperty("description").javaDocGetterAndSetter("An optional description of this tag.");
+        tag.addLongProperty("userId").notNull();
+
+        return tag;
+    }
+
+    private static Entity addActivityDescription(Schema schema, Entity tag, Entity user) {
+        Entity activityDesc = addEntity(schema, "ActivityDescription");
+        activityDesc.setJavaDoc("A user may further specify his activity with a detailed description and the help of tags.\nOne or more tags can be added to a given activity range.");
+        activityDesc.addIdProperty();
+        activityDesc.addIntProperty(TIMESTAMP_FROM).notNull();
+        activityDesc.addIntProperty(TIMESTAMP_TO).notNull();
+        activityDesc.addStringProperty("details").javaDocGetterAndSetter("An optional detailed description, specific to this very activity occurrence.");
+
+        Property userId = activityDesc.addLongProperty("userId").notNull().getProperty();
+        activityDesc.addToOne(user, userId);
+
+        Entity activityDescTagLink = addEntity(schema, "ActivityDescTagLink");
+        activityDescTagLink.addIdProperty();
+        Property sourceId = activityDescTagLink.addLongProperty("activityDescriptionId").notNull().getProperty();
+        Property targetId = activityDescTagLink.addLongProperty("tagId").notNull().getProperty();
+
+        activityDesc.addToMany(tag, activityDescTagLink, sourceId, targetId);
+
+        return activityDesc;
+    }
+
+    private static Entity addUserInfo(Schema schema, Entity userAttributes) {
+        Entity user = addEntity(schema, "User");
+        user.addIdProperty();
+        user.addStringProperty("name").notNull();
+        user.addDateProperty("birthday").notNull();
+        user.addIntProperty("gender").notNull();
+        Property userId = userAttributes.addLongProperty("userId").notNull().getProperty();
+
+        // sorted by the from-date, newest first
+        Property userAttributesSortProperty = getPropertyByName(userAttributes, VALID_FROM_UTC);
+        user.addToMany(userAttributes, userId).orderDesc(userAttributesSortProperty);
+
+        return user;
+    }
+
+    private static Property getPropertyByName(Entity entity, String propertyName) {
+        for (Property prop : entity.getProperties()) {
+            if (propertyName.equals(prop.getPropertyName())) {
+                return prop;
+            }
+        }
+        throw new IllegalStateException("Could not find property " + propertyName + " in entity " + entity.getClassName());
+    }
+
+    private static Entity addUserAttributes(Schema schema) {
+        // additional properties of a user, which may change during the lifetime of a user
+        // this allows changing attributes while preserving user identity
+        Entity userAttributes = addEntity(schema, "UserAttributes");
+        userAttributes.addIdProperty();
+        userAttributes.addIntProperty("heightCM").notNull();
+        userAttributes.addIntProperty("weightKG").notNull();
+        userAttributes.addIntProperty("sleepGoalHPD").javaDocGetterAndSetter("Desired number of hours of sleep per day.");
+        userAttributes.addIntProperty("stepsGoalSPD").javaDocGetterAndSetter("Desired number of steps per day.");
+        addDateValidityTo(userAttributes);
+
+        return userAttributes;
+    }
+
+    private static void addDateValidityTo(Entity entity) {
+        entity.addDateProperty(VALID_FROM_UTC).codeBeforeGetter(OVERRIDE);
+        entity.addDateProperty(VALID_TO_UTC).codeBeforeGetter(OVERRIDE);
+
+        entity.implementsInterface(VALID_BY_DATE);
+    }
+
+    private static Entity addDevice(Schema schema, Entity deviceAttributes) {
+        Entity device = addEntity(schema, "Device");
+        device.addIdProperty();
+        device.addStringProperty("name").notNull();
+        device.addStringProperty("manufacturer").notNull();
+        device.addStringProperty("identifier").notNull().unique().javaDocGetterAndSetter("The fixed identifier, i.e. MAC address of the device.");
+        device.addIntProperty("type").notNull().javaDocGetterAndSetter("The DeviceType key, i.e. the GBDevice's type.").codeBeforeGetterAndSetter("@Deprecated");
+        device.addStringProperty("typeName").notNull().javaDocGetterAndSetter("The DeviceType enum name, for example SONY_WH_1000XM3");
+        device.addStringProperty("model").javaDocGetterAndSetter("An optional model, further specifying the kind of device.");
+        device.addStringProperty("alias");
+        device.addStringProperty("parentFolder").javaDocGetterAndSetter("Folder name containing this device.");
+        Property deviceId = deviceAttributes.addLongProperty("deviceId").notNull().getProperty();
+        // sorted by the from-date, newest first
+        Property deviceAttributesSortProperty = getPropertyByName(deviceAttributes, VALID_FROM_UTC);
+        device.addToMany(deviceAttributes, deviceId).orderDesc(deviceAttributesSortProperty);
+
+        return device;
+    }
+
+    private static Entity addDeviceAttributes(Schema schema) {
+        Entity deviceAttributes = addEntity(schema, "DeviceAttributes");
+        deviceAttributes.addIdProperty();
+        deviceAttributes.addStringProperty("firmwareVersion1").notNull();
+        deviceAttributes.addStringProperty("firmwareVersion2");
+        addDateValidityTo(deviceAttributes);
+
+        return deviceAttributes;
+    }
+
+    private static Entity addMakibesHR3ActivitySample(Schema schema, Entity user, Entity device) {
+        Entity activitySample = addEntity(schema, "MakibesHR3ActivitySample");
+        activitySample.implementsSerializable();
+        addCommonActivitySampleProperties("AbstractActivitySample", activitySample, user, device);
+        activitySample.addIntProperty(SAMPLE_STEPS).notNull().codeBeforeGetterAndSetter(OVERRIDE);
+        activitySample.addIntProperty(SAMPLE_RAW_KIND).notNull().codeBeforeGetterAndSetter(OVERRIDE);
+        addHeartRateProperties(activitySample);
+        return activitySample;
+    }
+
+    private static Entity addMiBandActivitySample(Schema schema, Entity user, Entity device) {
+        Entity activitySample = addEntity(schema, "MiBandActivitySample");
+        activitySample.implementsSerializable();
+        addCommonActivitySampleProperties("AbstractActivitySample", activitySample, user, device);
+        activitySample.addIntProperty(SAMPLE_RAW_INTENSITY).notNull().codeBeforeGetterAndSetter(OVERRIDE);
+        activitySample.addIntProperty(SAMPLE_STEPS).notNull().codeBeforeGetterAndSetter(OVERRIDE);
+        activitySample.addIntProperty(SAMPLE_RAW_KIND).notNull().codeBeforeGetterAndSetter(OVERRIDE);
+        addHeartRateProperties(activitySample);
+        return activitySample;
+    }
+
+    private static Entity addHuamiExtendedActivitySample(Schema schema, Entity user, Entity device) {
+        Entity activitySample = addEntity(schema, "HuamiExtendedActivitySample");
+        addCommonActivitySampleProperties("MiBandActivitySample", activitySample, user, device);
+        activitySample.addIntProperty(SAMPLE_RAW_INTENSITY).notNull().codeBeforeGetterAndSetter(OVERRIDE);
+        activitySample.addIntProperty(SAMPLE_STEPS).notNull().codeBeforeGetterAndSetter(OVERRIDE);
+        activitySample.addIntProperty(SAMPLE_RAW_KIND).notNull().codeBeforeGetterAndSetter(OVERRIDE);
+        addHeartRateProperties(activitySample);
+        activitySample.addIntProperty("unknown1");
+        activitySample.addIntProperty("sleep");
+        activitySample.addIntProperty("deepSleep");
+        activitySample.addIntProperty("remSleep");
+        return activitySample;
+    }
+
+    private static Entity addHuamiStressSample(Schema schema, Entity user, Entity device) {
+        Entity stressSample = addEntity(schema, "HuamiStressSample");
+        addCommonTimeSampleProperties("AbstractStressSample", stressSample, user, device);
+        stressSample.addIntProperty("typeNum").notNull().codeBeforeGetter(OVERRIDE);
+        stressSample.addIntProperty("stress").notNull().codeBeforeGetter(OVERRIDE);
+        return stressSample;
+    }
+
+    private static Entity addHuamiSpo2Sample(Schema schema, Entity user, Entity device) {
+        Entity spo2sample = addEntity(schema, "HuamiSpo2Sample");
+        addCommonTimeSampleProperties("AbstractSpo2Sample", spo2sample, user, device);
+        spo2sample.addIntProperty("typeNum").notNull().codeBeforeGetter(OVERRIDE);
+        spo2sample.addIntProperty("spo2").notNull().codeBeforeGetter(OVERRIDE);
+        return spo2sample;
+    }
+
+    private static Entity addHuamiHeartRateManualSample(Schema schema, Entity user, Entity device) {
+        Entity hrManualSample = addEntity(schema, "HuamiHeartRateManualSample");
+        addCommonTimeSampleProperties("AbstractHeartRateSample", hrManualSample, user, device);
+        hrManualSample.addIntProperty("utcOffset").notNull();
+        hrManualSample.addIntProperty(SAMPLE_HEART_RATE).notNull().codeBeforeGetter(OVERRIDE);
+        return hrManualSample;
+    }
+
+    private static Entity addHuamiHeartRateMaxSample(Schema schema, Entity user, Entity device) {
+        Entity hrMaxSample = addEntity(schema, "HuamiHeartRateMaxSample");
+        addCommonTimeSampleProperties("AbstractHeartRateSample", hrMaxSample, user, device);
+        hrMaxSample.addIntProperty("utcOffset").notNull();
+        hrMaxSample.addIntProperty(SAMPLE_HEART_RATE).notNull().codeBeforeGetter(OVERRIDE);
+        return hrMaxSample;
+    }
+
+    private static Entity addHuamiHeartRateRestingSample(Schema schema, Entity user, Entity device) {
+        Entity hrRestingSample = addEntity(schema, "HuamiHeartRateRestingSample");
+        addCommonTimeSampleProperties("AbstractHeartRateSample", hrRestingSample, user, device);
+        hrRestingSample.addIntProperty("utcOffset").notNull();
+        hrRestingSample.addIntProperty(SAMPLE_HEART_RATE).notNull().codeBeforeGetter(OVERRIDE);
+        return hrRestingSample;
+    }
+
+    private static Entity addHuamiPaiSample(Schema schema, Entity user, Entity device) {
+        Entity paiSample = addEntity(schema, "HuamiPaiSample");
+        addCommonTimeSampleProperties("AbstractPaiSample", paiSample, user, device);
+        paiSample.addIntProperty("utcOffset").notNull();
+        paiSample.addFloatProperty("paiLow").notNull().codeBeforeGetter(OVERRIDE);
+        paiSample.addFloatProperty("paiModerate").notNull().codeBeforeGetter(OVERRIDE);
+        paiSample.addFloatProperty("paiHigh").notNull().codeBeforeGetter(OVERRIDE);
+        paiSample.addIntProperty("timeLow").notNull().codeBeforeGetter(OVERRIDE);
+        paiSample.addIntProperty("timeModerate").notNull().codeBeforeGetter(OVERRIDE);
+        paiSample.addIntProperty("timeHigh").notNull().codeBeforeGetter(OVERRIDE);
+        paiSample.addFloatProperty("paiToday").notNull().codeBeforeGetter(OVERRIDE);
+        paiSample.addFloatProperty("paiTotal").notNull().codeBeforeGetter(OVERRIDE);
+        return paiSample;
+    }
+
+    private static Entity addHuamiSleepRespiratoryRateSample(Schema schema, Entity user, Entity device) {
+        Entity sleepRespiratoryRateSample = addEntity(schema, "HuamiSleepRespiratoryRateSample");
+        addCommonTimeSampleProperties("AbstractRespiratoryRateSample", sleepRespiratoryRateSample, user, device);
+        sleepRespiratoryRateSample.addIntProperty("utcOffset").notNull();
+        sleepRespiratoryRateSample.addIntProperty("rate").notNull().codeBeforeGetter(
+                "@Override\n" +
+                        "    public float getRespiratoryRate() {\n" +
+                        "        return (float) getRate();\n" +
+                        "    }\n\n"
+        );
+        return sleepRespiratoryRateSample;
+    }
+
+    private static Entity addXiaomiActivitySample(Schema schema, Entity user, Entity device) {
+        Entity activitySample = addEntity(schema, "XiaomiActivitySample");
+        addCommonActivitySampleProperties("AbstractActivitySample", activitySample, user, device);
+        activitySample.implementsSerializable();
+        activitySample.addIntProperty(SAMPLE_RAW_INTENSITY).notNull().codeBeforeGetterAndSetter(OVERRIDE);
+        activitySample.addIntProperty(SAMPLE_STEPS).notNull().codeBeforeGetterAndSetter(OVERRIDE);
+        activitySample.addIntProperty(SAMPLE_RAW_KIND).notNull().codeBeforeGetterAndSetter(OVERRIDE);
+        addHeartRateProperties(activitySample);
+        activitySample.addIntProperty("stress");
+        activitySample.addIntProperty("spo2");
+        return activitySample;
+    }
+
+    private static Entity addXiaomiSleepTimeSamples(Schema schema, Entity user, Entity device) {
+        Entity sample = addEntity(schema, "XiaomiSleepTimeSample");
+        addCommonTimeSampleProperties("AbstractTimeSample", sample, user, device);
+        sample.addLongProperty("wakeupTime");
+        sample.addBooleanProperty("isAwake");
+        sample.addIntProperty("totalDuration");
+        sample.addIntProperty("deepSleepDuration");
+        sample.addIntProperty("lightSleepDuration");
+        sample.addIntProperty("remSleepDuration");
+        sample.addIntProperty("awakeDuration");
+        return sample;
+    }
+
+    private static Entity addHeartPulseSamples(Schema schema, Entity user, Entity device) {
+        Entity sample = addEntity(schema, "HeartPulseSample");
+        addCommonTimeSampleProperties("AbstractTimeSample", sample, user, device);
+        return sample;
+    }
+
+    private static Entity addXiaomiSleepStageSamples(Schema schema, Entity user, Entity device) {
+        Entity sample = addEntity(schema, "XiaomiSleepStageSample");
+        addCommonTimeSampleProperties("AbstractTimeSample", sample, user, device);
+        sample.addIntProperty("stage");
+        return sample;
+    }
+
+    private static Entity addXiaomiManualSamples(Schema schema, Entity user, Entity device) {
+        Entity sample = addEntity(schema, "XiaomiManualSample");
+        addCommonTimeSampleProperties("AbstractTimeSample", sample, user, device);
+        sample.addIntProperty("type");
+        sample.addIntProperty("value");
+        return sample;
+    }
+
+    private static Entity addXiaomiDailySummarySamples(Schema schema, Entity user, Entity device) {
+        Entity sample = addEntity(schema, "XiaomiDailySummarySample");
+        addCommonTimeSampleProperties("AbstractTimeSample", sample, user, device);
+        sample.addIntProperty("timezone");
+        sample.addIntProperty("steps");
+        sample.addIntProperty("hrResting");
+        sample.addIntProperty("hrMax");
+        sample.addIntProperty("hrMaxTs");
+        sample.addIntProperty("hrMin");
+        sample.addIntProperty("hrMinTs");
+        sample.addIntProperty("hrAvg");
+        sample.addIntProperty("stressAvg");
+        sample.addIntProperty("stressMax");
+        sample.addIntProperty("stressMin");
+        sample.addIntProperty("standing");
+        sample.addIntProperty("calories");
+        sample.addIntProperty("spo2Max");
+        sample.addIntProperty("spo2MaxTs");
+        sample.addIntProperty("spo2Min");
+        sample.addIntProperty("spo2MinTs");
+        sample.addIntProperty("spo2Avg");
+        sample.addIntProperty("trainingLoadDay");
+        sample.addIntProperty("trainingLoadWeek");
+        sample.addIntProperty("trainingLoadLevel");
+        sample.addIntProperty("vitalityIncreaseLight");
+        sample.addIntProperty("vitalityIncreaseModerate");
+        sample.addIntProperty("vitalityIncreaseHigh");
+        sample.addIntProperty("vitalityCurrent");
+        return sample;
+    }
+
+    private static Entity addCmfActivitySample(Schema schema, Entity user, Entity device) {
+        Entity activitySample = addEntity(schema, "CmfActivitySample");
+        addCommonActivitySampleProperties("AbstractActivitySample", activitySample, user, device);
+        activitySample.implementsSerializable();
+        activitySample.addIntProperty(SAMPLE_RAW_INTENSITY).notNull().codeBeforeGetterAndSetter(OVERRIDE);
+        activitySample.addIntProperty(SAMPLE_STEPS).notNull().codeBeforeGetterAndSetter(OVERRIDE);
+        activitySample.addIntProperty(SAMPLE_RAW_KIND).notNull().codeBeforeGetterAndSetter(OVERRIDE);
+        addHeartRateProperties(activitySample);
+        activitySample.addIntProperty("distance");
+        activitySample.addIntProperty("calories");
+        return activitySample;
+    }
+
+    private static Entity addCmfStressSample(Schema schema, Entity user, Entity device) {
+        Entity stressSample = addEntity(schema, "CmfStressSample");
+        addCommonTimeSampleProperties("AbstractStressSample", stressSample, user, device);
+        stressSample.addIntProperty("stress").notNull().codeBeforeGetter(OVERRIDE);
+        return stressSample;
+    }
+
+    private static Entity addCmfSpo2Sample(Schema schema, Entity user, Entity device) {
+        Entity spo2sample = addEntity(schema, "CmfSpo2Sample");
+        addCommonTimeSampleProperties("AbstractSpo2Sample", spo2sample, user, device);
+        spo2sample.addIntProperty("spo2").notNull().codeBeforeGetter(OVERRIDE);
+        return spo2sample;
+    }
+
+    private static Entity addCmfSleepSessionSample(Schema schema, Entity user, Entity device) {
+        Entity sleepSessionSample = addEntity(schema, "CmfSleepSessionSample");
+        addCommonTimeSampleProperties("AbstractTimeSample", sleepSessionSample, user, device);
+        sleepSessionSample.addLongProperty("wakeupTime");
+        sleepSessionSample.addByteArrayProperty("metadata");
+        return sleepSessionSample;
+    }
+
+    private static Entity addCmfSleepStageSample(Schema schema, Entity user, Entity device) {
+        Entity sleepStageSample = addEntity(schema, "CmfSleepStageSample");
+        addCommonTimeSampleProperties("AbstractTimeSample", sleepStageSample, user, device);
+        sleepStageSample.addIntProperty("duration").notNull();
+        sleepStageSample.addIntProperty("stage").notNull();
+        return sleepStageSample;
+    }
+
+    private static Entity addCmfHeartRateSample(Schema schema, Entity user, Entity device) {
+        Entity heartRateSample = addEntity(schema, "CmfHeartRateSample");
+        addCommonTimeSampleProperties("AbstractHeartRateSample", heartRateSample, user, device);
+        heartRateSample.addIntProperty(SAMPLE_HEART_RATE).notNull().codeBeforeGetter(OVERRIDE);
+        return heartRateSample;
+    }
+
+    private static Entity addCmfWorkoutGpsSample(Schema schema, Entity user, Entity device) {
+        Entity sample = addEntity(schema, "CmfWorkoutGpsSample");
+        addCommonTimeSampleProperties("AbstractTimeSample", sample, user, device);
+        sample.addIntProperty("latitude");
+        sample.addIntProperty("longitude");
+        return sample;
+    }
+
+    private static Entity addColmiActivitySample(Schema schema, Entity user, Entity device) {
+        Entity activitySample = addEntity(schema, "ColmiActivitySample");
+        addCommonActivitySampleProperties("AbstractColmiActivitySample", activitySample, user, device);
+        activitySample.implementsSerializable();
+        activitySample.addIntProperty(SAMPLE_RAW_KIND).notNull().codeBeforeGetterAndSetter(OVERRIDE);
+        activitySample.addIntProperty(SAMPLE_STEPS).notNull().codeBeforeGetterAndSetter(OVERRIDE);
+        addHeartRateProperties(activitySample);
+        activitySample.addIntProperty("distance").notNull();
+        activitySample.addIntProperty("calories").notNull();
+        return activitySample;
+    }
+
+    private static Entity addColmiHeartRateSample(Schema schema, Entity user, Entity device) {
+        Entity heartRateSample = addEntity(schema, "ColmiHeartRateSample");
+        heartRateSample.implementsSerializable();
+        addCommonTimeSampleProperties("AbstractHeartRateSample", heartRateSample, user, device);
+        heartRateSample.addIntProperty(SAMPLE_HEART_RATE).notNull();
+        return heartRateSample;
+    }
+
+    private static Entity addColmiStressSample(Schema schema, Entity user, Entity device) {
+        Entity stressSample = addEntity(schema, "ColmiStressSample");
+        addCommonTimeSampleProperties("AbstractStressSample", stressSample, user, device);
+        stressSample.addIntProperty("stress").notNull().codeBeforeGetter(OVERRIDE);
+        return stressSample;
+    }
+
+    private static Entity addColmiSpo2Sample(Schema schema, Entity user, Entity device) {
+        Entity spo2sample = addEntity(schema, "ColmiSpo2Sample");
+        addCommonTimeSampleProperties("AbstractSpo2Sample", spo2sample, user, device);
+        spo2sample.addIntProperty("spo2").notNull().codeBeforeGetter(OVERRIDE);
+        return spo2sample;
+    }
+
+    private static Entity addColmiSleepSessionSample(Schema schema, Entity user, Entity device) {
+        Entity sleepSessionSample = addEntity(schema, "ColmiSleepSessionSample");
+        addCommonTimeSampleProperties("AbstractTimeSample", sleepSessionSample, user, device);
+        sleepSessionSample.addLongProperty("wakeupTime");
+        return sleepSessionSample;
+    }
+
+    private static Entity addColmiSleepStageSample(Schema schema, Entity user, Entity device) {
+        Entity sleepStageSample = addEntity(schema, "ColmiSleepStageSample");
+        addCommonTimeSampleProperties("AbstractTimeSample", sleepStageSample, user, device);
+        sleepStageSample.addIntProperty("duration").notNull();
+        sleepStageSample.addIntProperty("stage").notNull();
+        return sleepStageSample;
+    }
+
+    private static Entity addColmiHrvValueSample(Schema schema, Entity user, Entity device) {
+        Entity hrvValueSample = addEntity(schema, "ColmiHrvValueSample");
+        addCommonTimeSampleProperties("AbstractHrvValueSample", hrvValueSample, user, device);
+        hrvValueSample.addIntProperty(SAMPLE_HRV_VALUE).notNull().codeBeforeGetter(OVERRIDE);
+        return hrvValueSample;
+    }
+
+    private static Entity addColmiHrvSummarySample(Schema schema, Entity user, Entity device) {
+        Entity hrvSummarySample = addEntity(schema, "ColmiHrvSummarySample");
+        addCommonTimeSampleProperties("AbstractHrvSummarySample", hrvSummarySample, user, device);
+        hrvSummarySample.addIntProperty(SAMPLE_HRV_WEEKLY_AVERAGE).codeBeforeGetter(OVERRIDE);
+        hrvSummarySample.addIntProperty(SAMPLE_HRV_LAST_NIGHT_AVERAGE).codeBeforeGetter(OVERRIDE);
+        hrvSummarySample.addIntProperty(SAMPLE_HRV_LAST_NIGHT_5MIN_HIGH).codeBeforeGetter(OVERRIDE);
+        hrvSummarySample.addIntProperty(SAMPLE_HRV_BASELINE_LOW_UPPER).codeBeforeGetter(OVERRIDE);
+        hrvSummarySample.addIntProperty(SAMPLE_HRV_BASELINE_BALANCED_LOWER).codeBeforeGetter(OVERRIDE);
+        hrvSummarySample.addIntProperty(SAMPLE_HRV_BASELINE_BALANCED_UPPER).codeBeforeGetter(OVERRIDE);
+        hrvSummarySample.addIntProperty(SAMPLE_HRV_STATUS_NUM).codeBeforeGetter(OVERRIDE);
+        return hrvSummarySample;
+    }
+
+    private static void addHeartRateProperties(Entity activitySample) {
+        activitySample.addIntProperty(SAMPLE_HEART_RATE).notNull().codeBeforeGetterAndSetter(OVERRIDE);
+    }
+
+    private static Entity addPebbleHealthActivitySample(Schema schema, Entity user, Entity device) {
+        Entity activitySample = addEntity(schema, "PebbleHealthActivitySample");
+        addCommonActivitySampleProperties("AbstractPebbleHealthActivitySample", activitySample, user, device);
+        activitySample.addByteArrayProperty("rawPebbleHealthData").codeBeforeGetter(OVERRIDE);
+        activitySample.addIntProperty(SAMPLE_RAW_INTENSITY).notNull().codeBeforeGetterAndSetter(OVERRIDE);
+        activitySample.addIntProperty(SAMPLE_STEPS).notNull().codeBeforeGetterAndSetter(OVERRIDE);
+        addHeartRateProperties(activitySample);
+        return activitySample;
+    }
+
+    private static Entity addPebbleHealthActivityKindOverlay(Schema schema, Entity user, Entity device) {
+        Entity activityOverlay = addEntity(schema, "PebbleHealthActivityOverlay");
+
+        activityOverlay.addIntProperty(TIMESTAMP_FROM).notNull().primaryKey();
+        activityOverlay.addIntProperty(TIMESTAMP_TO).notNull().primaryKey();
+        activityOverlay.addIntProperty(SAMPLE_RAW_KIND).notNull().primaryKey();
+        Property deviceId = activityOverlay.addLongProperty("deviceId").primaryKey().notNull().getProperty();
+        activityOverlay.addToOne(device, deviceId);
+
+        Property userId = activityOverlay.addLongProperty("userId").notNull().getProperty();
+        activityOverlay.addToOne(user, userId);
+        activityOverlay.addByteArrayProperty("rawPebbleHealthData");
+
+        return activityOverlay;
+    }
+
+    private static Entity addPebbleMisfitActivitySample(Schema schema, Entity user, Entity device) {
+        Entity activitySample = addEntity(schema, "PebbleMisfitSample");
+        addCommonActivitySampleProperties("AbstractPebbleMisfitActivitySample", activitySample, user, device);
+        activitySample.addIntProperty("rawPebbleMisfitSample").notNull().codeBeforeGetter(OVERRIDE);
+        return activitySample;
+    }
+
+    private static Entity addPebbleMorpheuzActivitySample(Schema schema, Entity user, Entity device) {
+        Entity activitySample = addEntity(schema, "PebbleMorpheuzSample");
+        addCommonActivitySampleProperties("AbstractPebbleMorpheuzActivitySample", activitySample, user, device);
+        activitySample.addIntProperty(SAMPLE_RAW_INTENSITY).notNull().codeBeforeGetterAndSetter(OVERRIDE);
+        return activitySample;
+    }
+
+    private static Entity addHPlusHealthActivitySample(Schema schema, Entity user, Entity device) {
+        Entity activitySample = addEntity(schema, "HPlusHealthActivitySample");
+        activitySample.implementsSerializable();
+        addCommonActivitySampleProperties("AbstractActivitySample", activitySample, user, device);
+        activitySample.addByteArrayProperty("rawHPlusHealthData");
+        activitySample.addIntProperty(SAMPLE_RAW_KIND).notNull().primaryKey();
+        activitySample.addIntProperty(SAMPLE_RAW_INTENSITY).notNull().codeBeforeGetterAndSetter(OVERRIDE);
+        activitySample.addIntProperty(SAMPLE_STEPS).notNull().codeBeforeGetterAndSetter(OVERRIDE);
+        addHeartRateProperties(activitySample);
+        activitySample.addIntProperty("distance");
+        activitySample.addIntProperty("calories");
+
+        return activitySample;
+    }
+
+    private static Entity addHPlusHealthActivityKindOverlay(Schema schema, Entity user, Entity device) {
+        Entity activityOverlay = addEntity(schema, "HPlusHealthActivityOverlay");
+
+        activityOverlay.addIntProperty(TIMESTAMP_FROM).notNull().primaryKey();
+        activityOverlay.addIntProperty(TIMESTAMP_TO).notNull().primaryKey();
+        activityOverlay.addIntProperty(SAMPLE_RAW_KIND).notNull().primaryKey();
+        Property deviceId = activityOverlay.addLongProperty("deviceId").primaryKey().notNull().getProperty();
+        activityOverlay.addToOne(device, deviceId);
+
+        Property userId = activityOverlay.addLongProperty("userId").notNull().getProperty();
+        activityOverlay.addToOne(user, userId);
+        activityOverlay.addByteArrayProperty("rawHPlusHealthData");
+        return activityOverlay;
+    }
+
+    private static Entity addNo1F1ActivitySample(Schema schema, Entity user, Entity device) {
+        Entity activitySample = addEntity(schema, "No1F1ActivitySample");
+        activitySample.implementsSerializable();
+        addCommonActivitySampleProperties("AbstractActivitySample", activitySample, user, device);
+        activitySample.addIntProperty(SAMPLE_STEPS).notNull().codeBeforeGetterAndSetter(OVERRIDE);
+        activitySample.addIntProperty(SAMPLE_RAW_KIND).notNull().codeBeforeGetterAndSetter(OVERRIDE);
+        activitySample.addIntProperty(SAMPLE_RAW_INTENSITY).notNull().codeBeforeGetterAndSetter(OVERRIDE);
+        addHeartRateProperties(activitySample);
+        return activitySample;
+    }
+
+    private static Entity addXWatchActivitySample(Schema schema, Entity user, Entity device) {
+        Entity activitySample = addEntity(schema, "XWatchActivitySample");
+        activitySample.implementsSerializable();
+        addCommonActivitySampleProperties("AbstractActivitySample", activitySample, user, device);
+        activitySample.addIntProperty(SAMPLE_STEPS).notNull().codeBeforeGetterAndSetter(OVERRIDE);
+        activitySample.addIntProperty(SAMPLE_RAW_KIND).notNull().codeBeforeGetterAndSetter(OVERRIDE);
+        activitySample.addIntProperty(SAMPLE_RAW_INTENSITY).notNull().codeBeforeGetterAndSetter(OVERRIDE);
+        addHeartRateProperties(activitySample);
+        return activitySample;
+    }
+
+    private static Entity addZeTimeActivitySample(Schema schema, Entity user, Entity device) {
+        Entity activitySample = addEntity(schema, "ZeTimeActivitySample");
+        activitySample.implementsSerializable();
+        addCommonActivitySampleProperties("AbstractActivitySample", activitySample, user, device);
+        activitySample.addIntProperty(SAMPLE_STEPS).notNull().codeBeforeGetterAndSetter(OVERRIDE);
+        activitySample.addIntProperty(SAMPLE_RAW_KIND).notNull().codeBeforeGetterAndSetter(OVERRIDE);
+        activitySample.addIntProperty(SAMPLE_RAW_INTENSITY).notNull().codeBeforeGetterAndSetter(OVERRIDE);
+        addHeartRateProperties(activitySample);
+        activitySample.addIntProperty("caloriesBurnt");
+        activitySample.addIntProperty("distanceMeters");
+        activitySample.addIntProperty("activeTimeMinutes");
+        return activitySample;
+    }
+
+    private static Entity addID115ActivitySample(Schema schema, Entity user, Entity device) {
+        Entity activitySample = addEntity(schema, "ID115ActivitySample");
+        activitySample.implementsSerializable();
+        addCommonActivitySampleProperties("AbstractActivitySample", activitySample, user, device);
+        activitySample.addIntProperty(SAMPLE_STEPS).notNull().codeBeforeGetterAndSetter(OVERRIDE);
+        activitySample.addIntProperty(SAMPLE_RAW_KIND).notNull().codeBeforeGetterAndSetter(OVERRIDE);
+        activitySample.addIntProperty("caloriesBurnt");
+        activitySample.addIntProperty("distanceMeters");
+        activitySample.addIntProperty("activeTimeMinutes");
+        return activitySample;
+    }
+
+    private static Entity addJYouActivitySample(Schema schema, Entity user, Entity device) {
+        Entity activitySample = addEntity(schema, "JYouActivitySample");
+        activitySample.implementsSerializable();
+        addCommonActivitySampleProperties("AbstractActivitySample", activitySample, user, device);
+        activitySample.addIntProperty(SAMPLE_STEPS).notNull().codeBeforeGetterAndSetter(OVERRIDE);
+        activitySample.addIntProperty(SAMPLE_RAW_KIND).notNull().codeBeforeGetterAndSetter(OVERRIDE);
+        activitySample.addIntProperty("caloriesBurnt");
+        activitySample.addIntProperty("distanceMeters");
+        activitySample.addIntProperty("activeTimeMinutes");
+        addHeartRateProperties(activitySample);
+        return activitySample;
+    }
+
+    private static Entity addHybridHRActivitySample(Schema schema, Entity user, Entity device) {
+        Entity activitySample = addEntity(schema, "HybridHRActivitySample");
+        activitySample.implementsSerializable();
+
+        addCommonActivitySampleProperties("AbstractHybridHRActivitySample", activitySample, user, device);
+
+        activitySample.addIntProperty(SAMPLE_STEPS).notNull().codeBeforeGetterAndSetter(OVERRIDE);
+        activitySample.addIntProperty("calories").notNull();
+        activitySample.addIntProperty("variability").notNull();
+        activitySample.addIntProperty("max_variability").notNull();
+        activitySample.addIntProperty("heartrate_quality").notNull();
+        activitySample.addBooleanProperty("active").notNull();
+        activitySample.addByteProperty("wear_type").notNull();
+        addHeartRateProperties(activitySample);
+        return activitySample;
+    }
+
+    private static Entity addHybridHRSpo2Sample(Schema schema, Entity user, Entity device) {
+        Entity spo2sample = addEntity(schema, "HybridHRSpo2Sample");
+        addCommonTimeSampleProperties("AbstractSpo2Sample", spo2sample, user, device);
+        spo2sample.addIntProperty("spo2").notNull().codeBeforeGetter(OVERRIDE);
+        return spo2sample;
+    }
+
+    private static Entity addCyclingSample(Schema schema, Entity user, Entity device) {
+        Entity cyclingSample = addEntity(schema, "CyclingSample");
+        addCommonTimeSampleProperties("AbstractTimeSample", cyclingSample, user, device);
+
+        cyclingSample.implementsSerializable();
+
+        cyclingSample.addIntProperty("RevolutionCount");
+        cyclingSample.addFloatProperty("Distance");
+        cyclingSample.addFloatProperty("Speed");
+        return cyclingSample;
+    }
+
+    private static Entity addVivomoveHrActivitySample(Schema schema, Entity user, Entity device) {
+        final Entity activitySample = addEntity(schema, "VivomoveHrActivitySample");
+        activitySample.implementsSerializable();
+        addCommonActivitySampleProperties("AbstractActivitySample", activitySample, user, device);
+        activitySample.addIntProperty(SAMPLE_STEPS).notNull().codeBeforeGetterAndSetter(OVERRIDE);
+        activitySample.addIntProperty(SAMPLE_RAW_KIND).notNull().codeBeforeGetterAndSetter(OVERRIDE);
+        activitySample.addIntProperty(SAMPLE_RAW_INTENSITY).notNull().codeBeforeGetterAndSetter(OVERRIDE);
+        activitySample.addIntProperty(SAMPLE_HEART_RATE).notNull().codeBeforeGetterAndSetter(OVERRIDE);
+        activitySample.addIntProperty("caloriesBurnt");
+        activitySample.addIntProperty("floorsClimbed");
+        return activitySample;
+    }
+
+    private static Entity addGarminFitFile(Schema schema, Entity user, Entity device) {
+        final Entity downloadedFitFile = addEntity(schema, "GarminFitFile");
+        downloadedFitFile.implementsSerializable();
+        downloadedFitFile.setJavaDoc("This class represents a single FIT file downloaded from a FIT-compatible Garmin device.");
+        downloadedFitFile.addIdProperty().autoincrement();
+        downloadedFitFile.addLongProperty("downloadTimestamp").notNull();
+        final Property deviceId = downloadedFitFile.addLongProperty("deviceId").notNull().getProperty();
+        downloadedFitFile.addToOne(device, deviceId);
+        final Property userId = downloadedFitFile.addLongProperty("userId").notNull().getProperty();
+        downloadedFitFile.addToOne(user, userId);
+        final Property fileNumber = downloadedFitFile.addIntProperty("fileNumber").notNull().getProperty();
+        downloadedFitFile.addIntProperty("fileDataType").notNull();
+        downloadedFitFile.addIntProperty("fileSubType").notNull();
+        downloadedFitFile.addLongProperty("fileTimestamp").notNull();
+        downloadedFitFile.addIntProperty("specificFlags").notNull();
+        downloadedFitFile.addIntProperty("fileSize").notNull();
+        downloadedFitFile.addByteArrayProperty("fileData");
+
+        final Index indexUnique = new Index();
+        indexUnique.addProperty(deviceId);
+        indexUnique.addProperty(userId);
+        indexUnique.addProperty(fileNumber);
+        indexUnique.makeUnique();
+
+        downloadedFitFile.addIndex(indexUnique);
+
+        return downloadedFitFile;
+    }
+
+    private static Entity addGarminActivitySample(Schema schema, Entity user, Entity device) {
+        Entity activitySample = addEntity(schema, "GarminActivitySample");
+        addCommonActivitySampleProperties("AbstractActivitySample", activitySample, user, device);
+        activitySample.implementsSerializable();
+        activitySample.addIntProperty(SAMPLE_RAW_INTENSITY).notNull().codeBeforeGetterAndSetter(OVERRIDE);
+        activitySample.addIntProperty(SAMPLE_STEPS).notNull().codeBeforeGetterAndSetter(OVERRIDE);
+        activitySample.addIntProperty(SAMPLE_RAW_KIND).notNull().codeBeforeGetterAndSetter(OVERRIDE);
+        addHeartRateProperties(activitySample);
+        activitySample.addIntProperty("distanceCm").notNull().codeBeforeGetterAndSetter(OVERRIDE);
+        activitySample.addIntProperty("activeCalories").notNull().codeBeforeGetterAndSetter(OVERRIDE);
+
+        return activitySample;
+    }
+
+    private static Entity addGarminStressSample(Schema schema, Entity user, Entity device) {
+        Entity stressSample = addEntity(schema, "GarminStressSample");
+        addCommonTimeSampleProperties("AbstractStressSample", stressSample, user, device);
+        stressSample.addIntProperty("stress").notNull().codeBeforeGetter(OVERRIDE);
+        return stressSample;
+    }
+
+    private static Entity addGarminBodyEnergySample(Schema schema, Entity user, Entity device) {
+        Entity stressSample = addEntity(schema, "GarminBodyEnergySample");
+        addCommonTimeSampleProperties("AbstractBodyEnergySample", stressSample, user, device);
+        stressSample.addIntProperty("energy").notNull().codeBeforeGetter(OVERRIDE);
+        return stressSample;
+    }
+
+    private static Entity addGarminSpo2Sample(Schema schema, Entity user, Entity device) {
+        Entity spo2sample = addEntity(schema, "GarminSpo2Sample");
+        addCommonTimeSampleProperties("AbstractSpo2Sample", spo2sample, user, device);
+        spo2sample.addIntProperty("spo2").notNull().codeBeforeGetter(OVERRIDE);
+        return spo2sample;
+    }
+
+    private static Entity addGarminSleepStageSample(Schema schema, Entity user, Entity device) {
+        Entity sleepStageSample = addEntity(schema, "GarminSleepStageSample");
+        addCommonTimeSampleProperties("AbstractTimeSample", sleepStageSample, user, device);
+        sleepStageSample.addIntProperty("stage").notNull();
+        return sleepStageSample;
+    }
+
+    private static Entity addGarminEventSample(Schema schema, Entity user, Entity device) {
+        Entity sleepStageSample = addEntity(schema, "GarminEventSample");
+        addCommonTimeSampleProperties("AbstractTimeSample", sleepStageSample, user, device);
+        sleepStageSample.addIntProperty("event").notNull().primaryKey();
+        sleepStageSample.addIntProperty("eventType");
+        sleepStageSample.addLongProperty("data");
+        return sleepStageSample;
+    }
+
+    private static Entity addGarminHrvSummarySample(Schema schema, Entity user, Entity device) {
+        Entity hrvSummarySample = addEntity(schema, "GarminHrvSummarySample");
+        addCommonTimeSampleProperties("AbstractHrvSummarySample", hrvSummarySample, user, device);
+        hrvSummarySample.addIntProperty(SAMPLE_HRV_WEEKLY_AVERAGE).codeBeforeGetter(OVERRIDE);
+        hrvSummarySample.addIntProperty(SAMPLE_HRV_LAST_NIGHT_AVERAGE).codeBeforeGetter(OVERRIDE);
+        hrvSummarySample.addIntProperty(SAMPLE_HRV_LAST_NIGHT_5MIN_HIGH).codeBeforeGetter(OVERRIDE);
+        hrvSummarySample.addIntProperty(SAMPLE_HRV_BASELINE_LOW_UPPER).codeBeforeGetter(OVERRIDE);
+        hrvSummarySample.addIntProperty(SAMPLE_HRV_BASELINE_BALANCED_LOWER).codeBeforeGetter(OVERRIDE);
+        hrvSummarySample.addIntProperty(SAMPLE_HRV_BASELINE_BALANCED_UPPER).codeBeforeGetter(OVERRIDE);
+        hrvSummarySample.addIntProperty(SAMPLE_HRV_STATUS_NUM).codeBeforeGetter(OVERRIDE);
+        return hrvSummarySample;
+    }
+
+    private static Entity addGarminHrvValueSample(Schema schema, Entity user, Entity device) {
+        Entity hrvValueSample = addEntity(schema, "GarminHrvValueSample");
+        addCommonTimeSampleProperties("AbstractHrvValueSample", hrvValueSample, user, device);
+        hrvValueSample.addIntProperty("value").notNull().codeBeforeGetter(OVERRIDE);
+        return hrvValueSample;
+    }
+
+    private static Entity addGarminRespiratoryRateSample(Schema schema, Entity user, Entity device) {
+        Entity garminRespiratoryRateSample = addEntity(schema, "GarminRespiratoryRateSample");
+        addCommonTimeSampleProperties("AbstractRespiratoryRateSample", garminRespiratoryRateSample, user, device);
+        garminRespiratoryRateSample.addFloatProperty("respiratoryRate").notNull().codeBeforeGetter(OVERRIDE);
+        return garminRespiratoryRateSample;
+    }
+
+    private static Entity addGarminHeartRateRestingSample(Schema schema, Entity user, Entity device) {
+        Entity hrRestingSample = addEntity(schema, "GarminHeartRateRestingSample");
+        addCommonTimeSampleProperties("AbstractHeartRateSample", hrRestingSample, user, device);
+        hrRestingSample.addIntProperty(SAMPLE_HEART_RATE).notNull().codeBeforeGetter(OVERRIDE);
+        return hrRestingSample;
+    }
+
+    private static Entity addGarminRestingMetabolicRateSample(Schema schema, Entity user, Entity device) {
+        Entity sample = addEntity(schema, "GarminRestingMetabolicRateSample");
+        sample.addImport(MAIN_PACKAGE + ".model.RestingMetabolicRateSample");
+        addCommonTimeSampleProperties("RestingMetabolicRateSample", sample, user, device);
+        sample.addIntProperty("restingMetabolicRate").notNull().codeBeforeGetter(OVERRIDE);
+        return sample;
+    }
+
+    private static Entity addGarminSleepStatsSample(Schema schema, Entity user, Entity device) {
+        Entity sample = addEntity(schema, "GarminSleepStatsSample");
+        sample.addImport(MAIN_PACKAGE + ".model.SleepScoreSample");
+        addCommonTimeSampleProperties("SleepScoreSample", sample, user, device);
+        sample.addIntProperty("sleepScore").notNull().codeBeforeGetter(OVERRIDE);
+        return sample;
+    }
+
+    private static Entity addGarminIntensityMinutesSample(Schema schema, Entity user, Entity device) {
+        Entity sample = addEntity(schema, "GarminIntensityMinutesSample");
+        addCommonTimeSampleProperties("AbstractTimeSample", sample, user, device);
+        sample.addIntProperty("moderate");
+        sample.addIntProperty("vigorous");
+        return sample;
+    }
+
+    private static Entity addPendingFile(Schema schema, Entity user, Entity device) {
+        Entity pendingFile = addEntity(schema, "PendingFile");
+        pendingFile.setJavaDoc(
+                "This class represents a file that was fetched from the device and is pending processing."
+        );
+
+        // We need a single-column primary key so that we can delete records
+        pendingFile.addIdProperty().autoincrement();
+
+        Property path = pendingFile.addStringProperty("path").notNull().getProperty();
+        Property deviceId = pendingFile.addLongProperty("deviceId").notNull().getProperty();
+        pendingFile.addToOne(device, deviceId);
+
+        final Index indexUnique = new Index();
+        indexUnique.addProperty(deviceId);
+        indexUnique.addProperty(path);
+        indexUnique.makeUnique();
+        pendingFile.addIndex(indexUnique);
+
+        return pendingFile;
+    }
+
+    private static Entity addWatchXPlusHealthActivitySample(Schema schema, Entity user, Entity device) {
+        Entity activitySample = addEntity(schema, "WatchXPlusActivitySample");
+        activitySample.implementsSerializable();
+        addCommonActivitySampleProperties("AbstractActivitySample", activitySample, user, device);
+        activitySample.addByteArrayProperty("rawWatchXPlusHealthData");
+        activitySample.addIntProperty(SAMPLE_RAW_KIND).notNull().primaryKey();
+        activitySample.addIntProperty(SAMPLE_RAW_INTENSITY).notNull().codeBeforeGetterAndSetter(OVERRIDE);
+        activitySample.addIntProperty(SAMPLE_STEPS).notNull().codeBeforeGetterAndSetter(OVERRIDE);
+        addHeartRateProperties(activitySample);
+        activitySample.addIntProperty("distance");
+        activitySample.addIntProperty("calories");
+        return activitySample;
+    }
+
+    private static Entity addWatchXPlusHealthActivityKindOverlay(Schema schema, Entity user, Entity device) {
+        Entity activityOverlay = addEntity(schema, "WatchXPlusHealthActivityOverlay");
+
+        activityOverlay.addIntProperty(TIMESTAMP_FROM).notNull().primaryKey();
+        activityOverlay.addIntProperty(TIMESTAMP_TO).notNull().primaryKey();
+        activityOverlay.addIntProperty(SAMPLE_RAW_KIND).notNull().primaryKey();
+        Property deviceId = activityOverlay.addLongProperty("deviceId").primaryKey().notNull().getProperty();
+        activityOverlay.addToOne(device, deviceId);
+
+        Property userId = activityOverlay.addLongProperty("userId").notNull().getProperty();
+        activityOverlay.addToOne(user, userId);
+        activityOverlay.addByteArrayProperty("rawWatchXPlusHealthData");
+
+        return activityOverlay;
+    }
+
+    private static Entity addTLW64ActivitySample(Schema schema, Entity user, Entity device) {
+        Entity activitySample = addEntity(schema, "TLW64ActivitySample");
+        activitySample.implementsSerializable();
+        addCommonActivitySampleProperties("AbstractActivitySample", activitySample, user, device);
+        activitySample.addIntProperty(SAMPLE_STEPS).notNull().codeBeforeGetterAndSetter(OVERRIDE);
+        activitySample.addIntProperty(SAMPLE_RAW_KIND).notNull().codeBeforeGetterAndSetter(OVERRIDE);
+        activitySample.addIntProperty(SAMPLE_RAW_INTENSITY).notNull().codeBeforeGetterAndSetter(OVERRIDE);
+        return activitySample;
+    }
+
+    private static Entity addSonySWR12Sample(Schema schema, Entity user, Entity device) {
+        Entity activitySample = addEntity(schema, "SonySWR12Sample");
+        activitySample.implementsSerializable();
+        addCommonActivitySampleProperties("AbstractActivitySample", activitySample, user, device);
+        addHeartRateProperties(activitySample);
+        activitySample.addIntProperty(SAMPLE_STEPS).notNull().codeBeforeGetterAndSetter(OVERRIDE);
+        activitySample.addIntProperty(SAMPLE_RAW_KIND).notNull().codeBeforeGetterAndSetter(OVERRIDE);
+        activitySample.addIntProperty(SAMPLE_RAW_INTENSITY).notNull().codeBeforeGetterAndSetter(OVERRIDE);
+        return activitySample;
+    }
+
+    private static Entity addCasioGBX100Sample(Schema schema, Entity user, Entity device) {
+        Entity activitySample = addEntity(schema, "CasioGBX100ActivitySample");
+        activitySample.implementsSerializable();
+        addCommonActivitySampleProperties("AbstractGBX100ActivitySample", activitySample, user, device);
+        activitySample.addIntProperty(SAMPLE_RAW_KIND).notNull().codeBeforeGetterAndSetter(OVERRIDE);
+        activitySample.addIntProperty(SAMPLE_STEPS).notNull().codeBeforeGetterAndSetter(OVERRIDE);
+        activitySample.addIntProperty("calories").notNull();
+        return activitySample;
+    }
+
+    private static Entity addLefunActivitySample(Schema schema, Entity user, Entity device) {
+        Entity activitySample = addEntity(schema, "LefunActivitySample");
+        activitySample.implementsSerializable();
+        addCommonActivitySampleProperties("AbstractActivitySample", activitySample, user, device);
+        activitySample.addIntProperty(SAMPLE_RAW_KIND).notNull().codeBeforeGetterAndSetter(OVERRIDE);
+        activitySample.addIntProperty(SAMPLE_STEPS).notNull().codeBeforeGetterAndSetter(OVERRIDE);
+        activitySample.addIntProperty("distance").notNull();
+        activitySample.addIntProperty("calories").notNull();
+        addHeartRateProperties(activitySample);
+        return activitySample;
+    }
+
+    private static Entity addLefunBiometricSample(Schema schema, Entity user, Entity device) {
+        Entity biometricSample = addEntity(schema, "LefunBiometricSample");
+        biometricSample.implementsSerializable();
+
+        biometricSample.addIntProperty("timestamp").notNull().primaryKey();
+        Property deviceId = biometricSample.addLongProperty("deviceId").primaryKey().notNull().getProperty();
+        biometricSample.addToOne(device, deviceId);
+        Property userId = biometricSample.addLongProperty("userId").notNull().getProperty();
+        biometricSample.addToOne(user, userId);
+
+        biometricSample.addIntProperty("type").notNull();
+        biometricSample.addIntProperty("value1").notNull();
+        biometricSample.addIntProperty("value2");
+        return biometricSample;
+    }
+
+    private static Entity addLefunSleepSample(Schema schema, Entity user, Entity device) {
+        Entity sleepSample = addEntity(schema, "LefunSleepSample");
+        sleepSample.implementsSerializable();
+
+        sleepSample.addIntProperty("timestamp").notNull().primaryKey();
+        Property deviceId = sleepSample.addLongProperty("deviceId").primaryKey().notNull().getProperty();
+        sleepSample.addToOne(device, deviceId);
+        Property userId = sleepSample.addLongProperty("userId").notNull().getProperty();
+        sleepSample.addToOne(user, userId);
+
+        sleepSample.addIntProperty("type").notNull();
+        return sleepSample;
+    }
+
+    private static Entity addBangleJSActivitySample(Schema schema, Entity user, Entity device) {
+        Entity activitySample = addEntity(schema, "BangleJSActivitySample");
+        activitySample.implementsSerializable();
+        addCommonActivitySampleProperties("AbstractActivitySample", activitySample, user, device);
+        activitySample.addIntProperty(SAMPLE_RAW_INTENSITY).notNull().codeBeforeGetterAndSetter(OVERRIDE);
+        activitySample.addIntProperty(SAMPLE_STEPS).notNull().codeBeforeGetterAndSetter(OVERRIDE);
+        activitySample.addIntProperty(SAMPLE_RAW_KIND).notNull().codeBeforeGetterAndSetter(OVERRIDE);
+        addHeartRateProperties(activitySample);
+        return activitySample;
+    }
+
+    private static void addCommonActivitySampleProperties(String superClass, Entity activitySample, Entity user, Entity device) {
+        activitySample.setSuperclass(superClass);
+        activitySample.addImport(MAIN_PACKAGE + ".devices.SampleProvider");
+        activitySample.setJavaDoc(
+                "This class represents a sample specific to the device. Values like activity kind or\n" +
+                        "intensity, are device specific. Normalized values can be retrieved through the\n" +
+                        "corresponding {@link SampleProvider}.");
+        activitySample.addIntProperty("timestamp").notNull().codeBeforeGetterAndSetter(OVERRIDE).primaryKey();
+        Property deviceId = activitySample.addLongProperty("deviceId").primaryKey().notNull().codeBeforeGetterAndSetter(OVERRIDE).getProperty();
+        activitySample.addToOne(device, deviceId);
+        Property userId = activitySample.addLongProperty("userId").notNull().codeBeforeGetterAndSetter(OVERRIDE).getProperty();
+        activitySample.addToOne(user, userId);
+    }
+
+    private static void addCommonTimeSampleProperties(String superClass, Entity timeSample, Entity user, Entity device) {
+        timeSample.setSuperclass(superClass);
+        timeSample.addImport(MAIN_PACKAGE + ".devices.TimeSampleProvider");
+        timeSample.setJavaDoc(
+                "This class represents a sample specific to the device. Values might be device specific, depending on the sample type.\n" +
+                        "Normalized values can be retrieved through the corresponding {@link TimeSampleProvider}.");
+        timeSample.addLongProperty("timestamp").notNull().codeBeforeGetterAndSetter(OVERRIDE).primaryKey();
+        Property deviceId = timeSample.addLongProperty("deviceId").primaryKey().notNull().codeBeforeGetterAndSetter(OVERRIDE).getProperty();
+        timeSample.addToOne(device, deviceId);
+        Property userId = timeSample.addLongProperty("userId").notNull().codeBeforeGetterAndSetter(OVERRIDE).getProperty();
+        timeSample.addToOne(user, userId);
+    }
+
+    private static void addCalendarSyncState(Schema schema, Entity device) {
+        Entity calendarSyncState = addEntity(schema, "CalendarSyncState");
+        calendarSyncState.addIdProperty();
+        Property deviceId = calendarSyncState.addLongProperty("deviceId").notNull().getProperty();
+        Property calendarEntryId = calendarSyncState.addLongProperty("calendarEntryId").notNull().getProperty();
+        Index indexUnique = new Index();
+        indexUnique.addProperty(deviceId);
+        indexUnique.addProperty(calendarEntryId);
+        indexUnique.makeUnique();
+        calendarSyncState.addIndex(indexUnique);
+        calendarSyncState.addToOne(device, deviceId);
+        calendarSyncState.addIntProperty("hash").notNull();
+    }
+
+    private static void addAlarms(Schema schema, Entity user, Entity device) {
+        Entity alarm = addEntity(schema, "Alarm");
+        alarm.implementsInterface("nodomain.freeyourgadget.gadgetbridge.model.Alarm");
+        Property deviceId = alarm.addLongProperty("deviceId").notNull().getProperty();
+        Property userId = alarm.addLongProperty("userId").notNull().getProperty();
+        Property position = alarm.addIntProperty("position").notNull().getProperty();
+        Index indexUnique = new Index();
+        indexUnique.addProperty(deviceId);
+        indexUnique.addProperty(userId);
+        indexUnique.addProperty(position);
+        indexUnique.makeUnique();
+        alarm.addIndex(indexUnique);
+        alarm.addBooleanProperty("enabled").notNull();
+        alarm.addBooleanProperty("smartWakeup").notNull();
+        alarm.addIntProperty("smartWakeupInterval");
+        alarm.addBooleanProperty("snooze").notNull();
+        alarm.addIntProperty("repetition").notNull().codeBeforeGetter(
+                "public boolean isRepetitive() { return getRepetition() != ALARM_ONCE; } " +
+                        "public boolean getRepetition(int dow) { return (this.repetition & dow) > 0; }"
+        );
+        alarm.addIntProperty("hour").notNull();
+        alarm.addIntProperty("minute").notNull();
+        alarm.addBooleanProperty("unused").notNull();
+        alarm.addStringProperty("title");
+        alarm.addStringProperty("description");
+        alarm.addToOne(user, userId);
+        alarm.addToOne(device, deviceId);
+    }
+
+    private static void addReminders(Schema schema, Entity user, Entity device) {
+        Entity reminder = addEntity(schema, "Reminder");
+        reminder.implementsInterface("nodomain.freeyourgadget.gadgetbridge.model.Reminder");
+        Property deviceId = reminder.addLongProperty("deviceId").notNull().getProperty();
+        Property userId = reminder.addLongProperty("userId").notNull().getProperty();
+        Property reminderId = reminder.addStringProperty("reminderId").notNull().primaryKey().getProperty();
+        Index indexUnique = new Index();
+        indexUnique.addProperty(deviceId);
+        indexUnique.addProperty(userId);
+        indexUnique.addProperty(reminderId);
+        indexUnique.makeUnique();
+        reminder.addIndex(indexUnique);
+        reminder.addStringProperty("message").notNull();
+        reminder.addDateProperty("date").notNull();
+        reminder.addIntProperty("repetition").notNull();
+        reminder.addToOne(user, userId);
+        reminder.addToOne(device, deviceId);
+    }
+
+    private static void addWorldClocks(Schema schema, Entity user, Entity device) {
+        Entity worldClock = addEntity(schema, "WorldClock");
+        worldClock.implementsInterface("nodomain.freeyourgadget.gadgetbridge.model.WorldClock");
+        Property deviceId = worldClock.addLongProperty("deviceId").notNull().getProperty();
+        Property userId = worldClock.addLongProperty("userId").notNull().getProperty();
+        Property worldClockId = worldClock.addStringProperty("worldClockId").notNull().primaryKey().getProperty();
+        Index indexUnique = new Index();
+        indexUnique.addProperty(deviceId);
+        indexUnique.addProperty(userId);
+        indexUnique.addProperty(worldClockId);
+        indexUnique.makeUnique();
+        worldClock.addIndex(indexUnique);
+        worldClock.addStringProperty("label").notNull();
+        worldClock.addBooleanProperty("enabled");
+        worldClock.addStringProperty("code");
+        worldClock.addStringProperty("timeZoneId").notNull();
+        worldClock.addToOne(user, userId);
+        worldClock.addToOne(device, deviceId);
+    }
+
+    private static void addContacts(Schema schema, Entity user, Entity device) {
+        Entity contact = addEntity(schema, "Contact");
+        contact.implementsInterface("nodomain.freeyourgadget.gadgetbridge.model.Contact");
+        Property deviceId = contact.addLongProperty("deviceId").notNull().getProperty();
+        Property userId = contact.addLongProperty("userId").notNull().getProperty();
+        Property contactId = contact.addStringProperty("contactId").notNull().primaryKey().getProperty();
+        Index indexUnique = new Index();
+        indexUnique.addProperty(deviceId);
+        indexUnique.addProperty(userId);
+        indexUnique.addProperty(contactId);
+        indexUnique.makeUnique();
+        contact.addIndex(indexUnique);
+        contact.addStringProperty("name").notNull();
+        contact.addStringProperty("number").notNull();
+        contact.addToOne(user, userId);
+        contact.addToOne(device, deviceId);
+    }
+
+    private static void addNotificationFilterEntry(Schema schema, Entity notificationFilterEntity) {
+        Entity notificatonFilterEntry = addEntity(schema, "NotificationFilterEntry");
+        notificatonFilterEntry.addIdProperty().autoincrement();
+        Property notificationFilterId = notificatonFilterEntry.addLongProperty("notificationFilterId").notNull().getProperty();
+        notificatonFilterEntry.addStringProperty("notificationFilterContent").notNull().getProperty();
+        notificatonFilterEntry.addToOne(notificationFilterEntity, notificationFilterId);
+    }
+
+    private static Entity addNotificationFilters(Schema schema) {
+        Entity notificatonFilter = addEntity(schema, "NotificationFilter");
+        Property appIdentifier = notificatonFilter.addStringProperty("appIdentifier").notNull().getProperty();
+
+        notificatonFilter.addIdProperty().autoincrement();
+
+        Index indexUnique = new Index();
+        indexUnique.addProperty(appIdentifier);
+        indexUnique.makeUnique();
+        notificatonFilter.addIndex(indexUnique);
+
+        Property notificationFilterMode = notificatonFilter.addIntProperty("notificationFilterMode").notNull().getProperty();
+        Property notificationFilterSubMode = notificatonFilter.addIntProperty("notificationFilterSubMode").notNull().getProperty();
+        return notificatonFilter;
+    }
+
+    private static void addActivitySummary(Schema schema, Entity user, Entity device) {
+        Entity summary = addEntity(schema, "BaseActivitySummary");
+        summary.implementsInterface(ACTIVITY_SUMMARY);
+        summary.addIdProperty();
+
+        summary.setJavaDoc(
+                "This class represents the summary of a user's activity event. I.e. a walk, hike, a bicycle tour, etc.");
+
+        summary.addStringProperty("name").codeBeforeGetter(OVERRIDE);
+        summary.addDateProperty("startTime").notNull().codeBeforeGetter(OVERRIDE);
+        summary.addDateProperty("endTime").notNull().codeBeforeGetter(OVERRIDE);
+        summary.addIntProperty("activityKind").notNull().codeBeforeGetter(OVERRIDE);
+
+        summary.addIntProperty("baseLongitude").javaDocGetterAndSetter("Temporary, bip-specific");
+        summary.addIntProperty("baseLatitude").javaDocGetterAndSetter("Temporary, bip-specific");
+        summary.addIntProperty("baseAltitude").javaDocGetterAndSetter("Temporary, bip-specific");
+
+        summary.addStringProperty("gpxTrack").codeBeforeGetter(OVERRIDE);
+        summary.addStringProperty("rawDetailsPath");
+
+        Property deviceId = summary.addLongProperty("deviceId").notNull().codeBeforeGetter(OVERRIDE).getProperty();
+        summary.addToOne(device, deviceId);
+        Property userId = summary.addLongProperty("userId").notNull().codeBeforeGetter(OVERRIDE).getProperty();
+        summary.addToOne(user, userId);
+        summary.addStringProperty("summaryData");
+        summary.addByteArrayProperty("rawSummaryData");
+    }
+
+    private static Property findProperty(Entity entity, String propertyName) {
+        for (Property prop : entity.getProperties()) {
+            if (propertyName.equals(prop.getPropertyName())) {
+                return prop;
+            }
+        }
+        throw new IllegalArgumentException("Property " + propertyName + " not found in Entity " + entity.getClassName());
+    }
+
+    private static Entity addEntity(Schema schema, String className) {
+        Entity entity = schema.addEntity(className);
+        entity.addImport("de.greenrobot.dao.AbstractDao");
+        return entity;
+    }
+
+    private static Entity addBatteryLevel(Schema schema, Entity device) {
+        Entity batteryLevel = addEntity(schema, "BatteryLevel");
+        batteryLevel.implementsSerializable();
+        batteryLevel.addIntProperty("timestamp").notNull().primaryKey();
+        Property deviceId = batteryLevel.addLongProperty("deviceId").primaryKey().notNull().getProperty();
+        batteryLevel.addToOne(device, deviceId);
+        batteryLevel.addIntProperty("level").notNull();
+        batteryLevel.addIntProperty("batteryIndex").notNull().primaryKey();
+        return batteryLevel;
+    }
+
+    private static Entity addFitProActivitySample(Schema schema, Entity user, Entity device) {
+        Entity activitySample = addEntity(schema, "FitProActivitySample");
+        activitySample.implementsSerializable();
+        addCommonActivitySampleProperties("AbstractFitProActivitySample", activitySample, user, device);
+        activitySample.addIntProperty(SAMPLE_STEPS).notNull().codeBeforeGetterAndSetter(OVERRIDE);
+        activitySample.addIntProperty(SAMPLE_RAW_KIND).notNull().codeBeforeGetterAndSetter(OVERRIDE);
+        addHeartRateProperties(activitySample);
+        activitySample.addIntProperty("caloriesBurnt");
+        activitySample.addIntProperty("distanceMeters");
+        activitySample.addIntProperty("spo2Percent");
+        activitySample.addIntProperty("pressureLowMmHg");
+        activitySample.addIntProperty("pressureHighMmHg");
+        activitySample.addIntProperty("activeTimeMinutes");
+        return activitySample;
+    }
+
+    private static Entity addPineTimeActivitySample(Schema schema, Entity user, Entity device) {
+        Entity activitySample = addEntity(schema, "PineTimeActivitySample");
+        activitySample.implementsSerializable();
+        addCommonActivitySampleProperties("AbstractActivitySample", activitySample, user, device);
+        activitySample.addIntProperty(SAMPLE_RAW_KIND).notNull().codeBeforeGetterAndSetter(OVERRIDE);
+        activitySample.addIntProperty(SAMPLE_STEPS).notNull().codeBeforeGetterAndSetter(OVERRIDE);
+        addHeartRateProperties(activitySample);
+        return activitySample;
+    }
+
+
+    private static Entity addWithingsSteelHRActivitySample(Schema schema, Entity user, Entity device) {
+        Entity activitySample = addEntity(schema, "WithingsSteelHRActivitySample");
+        activitySample.implementsSerializable();
+        addCommonActivitySampleProperties("AbstractActivitySample", activitySample, user, device);
+        activitySample.addIntProperty("duration").notNull();
+        activitySample.addIntProperty(SAMPLE_RAW_KIND).notNull().codeBeforeGetterAndSetter(OVERRIDE);
+        activitySample.addIntProperty(SAMPLE_STEPS).notNull().codeBeforeGetterAndSetter(OVERRIDE);
+        activitySample.addIntProperty("distance").notNull();
+        activitySample.addIntProperty("calories").notNull();
+        addHeartRateProperties(activitySample);
+        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().codeBeforeGetter(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;
+    }
+
+    private static Entity addHuaweiActivitySample(Schema schema, Entity user, Entity device) {
+        Entity activitySample = addEntity(schema, "HuaweiActivitySample");
+        addCommonActivitySampleProperties("AbstractActivitySample", activitySample, user, device);
+        activitySample.addIntProperty("otherTimestamp").notNull().primaryKey();
+        activitySample.addByteProperty("source").notNull().primaryKey();
+        activitySample.addIntProperty(SAMPLE_RAW_KIND).notNull().codeBeforeGetterAndSetter(OVERRIDE);
+        activitySample.addIntProperty(SAMPLE_RAW_INTENSITY).notNull().codeBeforeGetterAndSetter(OVERRIDE);
+        activitySample.addIntProperty(SAMPLE_STEPS).notNull().codeBeforeGetterAndSetter(OVERRIDE);
+        activitySample.addIntProperty("calories").notNull().codeBeforeGetter(
+                "@Override\n" +
+                "    public int getActiveCalories() {\n" +
+                "        return getCalories();\n" +
+                "    }\n"
+        );
+        activitySample.addIntProperty("distance").notNull().codeBeforeGetter(
+                "@Override\n" +
+                "    public int getDistanceCm() {\n" +
+                "        return getDistance() == HuaweiActivitySample.NOT_MEASURED ? HuaweiActivitySample.NOT_MEASURED : getDistance() * 100;\n" +
+                "    }\n"
+        );
+        activitySample.addIntProperty("spo").notNull();
+        activitySample.addIntProperty("heartRate").notNull();
+        return activitySample;
+    }
+
+    private static Entity addHuaweiWorkoutSummarySample(Schema schema, Entity user, Entity device) {
+        Entity workoutSummary = addEntity(schema, "HuaweiWorkoutSummarySample");
+
+        workoutSummary.setJavaDoc("Contains Huawei Workout Summary samples (one per workout)");
+
+        workoutSummary.addLongProperty("workoutId").primaryKey().autoincrement();
+
+        Property deviceId = workoutSummary.addLongProperty("deviceId").notNull().getProperty();
+        workoutSummary.addToOne(device, deviceId);
+        Property userId = workoutSummary.addLongProperty("userId").notNull().getProperty();
+        workoutSummary.addToOne(user, userId);
+
+        workoutSummary.addShortProperty("workoutNumber").notNull();
+        workoutSummary.addByteProperty("status").notNull();
+        workoutSummary.addIntProperty("startTimestamp").notNull();
+        workoutSummary.addIntProperty("endTimestamp").notNull();
+        workoutSummary.addIntProperty("calories").notNull();
+        workoutSummary.addIntProperty("distance").notNull();
+        workoutSummary.addIntProperty("stepCount").notNull();
+        workoutSummary.addIntProperty("totalTime").notNull();
+        workoutSummary.addIntProperty("duration").notNull();
+        workoutSummary.addByteProperty("type").notNull();
+        workoutSummary.addShortProperty("strokes").notNull();
+        workoutSummary.addShortProperty("avgStrokeRate").notNull();
+        workoutSummary.addShortProperty("poolLength").notNull();
+        workoutSummary.addShortProperty("laps").notNull();
+        workoutSummary.addShortProperty("avgSwolf").notNull();
+
+        workoutSummary.addByteArrayProperty("rawData");
+
+        workoutSummary.addStringProperty("gpxFileLocation");
+
+        workoutSummary.addIntProperty("maxAltitude");
+        workoutSummary.addIntProperty("minAltitude");
+        workoutSummary.addIntProperty("elevationGain");
+        workoutSummary.addIntProperty("elevationLoss");
+
+        workoutSummary.addIntProperty("workoutLoad").notNull();
+        workoutSummary.addIntProperty("workoutAerobicEffect").notNull();
+        workoutSummary.addByteProperty("workoutAnaerobicEffect").notNull();
+        workoutSummary.addShortProperty("recoveryTime").notNull();
+
+        workoutSummary.addByteProperty("minHeartRatePeak").notNull();
+        workoutSummary.addByteProperty("maxHeartRatePeak").notNull();
+
+        workoutSummary.addByteArrayProperty("recoveryHeartRates");
+
+        workoutSummary.addByteProperty("swimType").notNull();
+
+        workoutSummary.addIntProperty("maxMET").notNull();
+
+        workoutSummary.addByteProperty("hrZoneType").notNull();
+
+        workoutSummary.addShortProperty("runPaceZone1Min").notNull();
+        workoutSummary.addShortProperty("runPaceZone2Min").notNull();
+        workoutSummary.addShortProperty("runPaceZone3Min").notNull();
+        workoutSummary.addShortProperty("runPaceZone4Min").notNull();
+        workoutSummary.addShortProperty("runPaceZone5Min").notNull();
+        workoutSummary.addShortProperty("runPaceZone5Max").notNull();
+
+        workoutSummary.addShortProperty("runPaceZone1Time").notNull();
+        workoutSummary.addShortProperty("runPaceZone2Time").notNull();
+        workoutSummary.addShortProperty("runPaceZone3Time").notNull();
+        workoutSummary.addShortProperty("runPaceZone4Time").notNull();
+        workoutSummary.addShortProperty("runPaceZone5Time").notNull();
+
+        workoutSummary.addByteProperty("algType").notNull();
+        workoutSummary.addIntProperty("trainingPoints").notNull();
+
+        return workoutSummary;
+    }
+
+    private static Entity addHuaweiWorkoutDataSample(Schema schema, Entity summaryEntity) {
+        Entity workoutDataSample = addEntity(schema, "HuaweiWorkoutDataSample");
+
+        workoutDataSample.setJavaDoc("Contains Huawei Workout data samples (multiple per workout)");
+
+        Property id = workoutDataSample.addLongProperty("workoutId").primaryKey().notNull().getProperty();
+        workoutDataSample.addToOne(summaryEntity, id);
+
+        workoutDataSample.addIntProperty("timestamp").notNull().primaryKey();
+        workoutDataSample.addByteProperty("heartRate").notNull();
+        workoutDataSample.addShortProperty("speed").notNull();
+        workoutDataSample.addByteProperty("stepRate").notNull();
+        workoutDataSample.addShortProperty("cadence").notNull();
+        workoutDataSample.addShortProperty("stepLength").notNull();
+        workoutDataSample.addShortProperty("groundContactTime").notNull();
+        workoutDataSample.addByteProperty("impact").notNull();
+        workoutDataSample.addShortProperty("swingAngle").notNull();
+        workoutDataSample.addByteProperty("foreFootLanding").notNull();
+        workoutDataSample.addByteProperty("midFootLanding").notNull();
+        workoutDataSample.addByteProperty("backFootLanding").notNull();
+        workoutDataSample.addByteProperty("eversionAngle").notNull();
+        workoutDataSample.addShortProperty("swolf").notNull();
+        workoutDataSample.addShortProperty("strokeRate").notNull();
+
+        workoutDataSample.addByteArrayProperty("dataErrorHex");
+
+        workoutDataSample.addShortProperty("calories").notNull();
+        workoutDataSample.addShortProperty("cyclingPower").notNull();
+
+        workoutDataSample.addShortProperty("frequency").notNull();
+        workoutDataSample.addIntProperty("altitude");
+
+        return workoutDataSample;
+    }
+
+    private static Entity addHuaweiWorkoutPaceSample(Schema schema, Entity summaryEntity) {
+        Entity workoutPaceSample = addEntity(schema, "HuaweiWorkoutPaceSample");
+
+        workoutPaceSample.setJavaDoc("Contains Huawei Workout pace data samples (one per workout)");
+
+        Property id = workoutPaceSample.addLongProperty("workoutId").primaryKey().notNull().getProperty();
+        workoutPaceSample.addToOne(summaryEntity, id);
+
+        workoutPaceSample.addIntProperty("paceIndex").notNull().primaryKey();
+        workoutPaceSample.addIntProperty("distance").notNull().primaryKey();
+        workoutPaceSample.addByteProperty("type").notNull().primaryKey();
+        workoutPaceSample.addIntProperty("pace").notNull();
+        workoutPaceSample.addIntProperty("pointIndex").notNull();
+        workoutPaceSample.addIntProperty("correction");
+
+        return workoutPaceSample;
+    }
+
+    private static Entity addHuaweiWorkoutSwimSegmentsSample(Schema schema, Entity summaryEntity) {
+        Entity workoutSwimSegmentsSample = addEntity(schema, "HuaweiWorkoutSwimSegmentsSample");
+
+        workoutSwimSegmentsSample.setJavaDoc("Contains Huawei Workout swim segments data samples");
+
+        Property id = workoutSwimSegmentsSample.addLongProperty("workoutId").primaryKey().notNull().getProperty();
+        workoutSwimSegmentsSample.addToOne(summaryEntity, id);
+
+        workoutSwimSegmentsSample.addIntProperty("segmentIndex").notNull().primaryKey();
+        workoutSwimSegmentsSample.addIntProperty("distance").notNull().primaryKey();
+        workoutSwimSegmentsSample.addByteProperty("type").notNull().primaryKey();
+        workoutSwimSegmentsSample.addIntProperty("pace").notNull();
+        workoutSwimSegmentsSample.addIntProperty("pointIndex").notNull();
+        workoutSwimSegmentsSample.addIntProperty("segment").notNull();
+        workoutSwimSegmentsSample.addByteProperty("swimType").notNull();
+        workoutSwimSegmentsSample.addIntProperty("strokes").notNull();
+        workoutSwimSegmentsSample.addIntProperty("avgSwolf").notNull();
+        workoutSwimSegmentsSample.addIntProperty("time").notNull();
+
+        return workoutSwimSegmentsSample;
+    }
+
+    private static Entity addHuaweiDictData(Schema schema, Entity user, Entity device) {
+        Entity dictData = addEntity(schema, "HuaweiDictData");
+
+        dictData.setJavaDoc("Contains Huawei Dict Data");
+
+        dictData.addLongProperty("dictId").primaryKey().autoincrement();
+
+        Property deviceId = dictData.addLongProperty("deviceId").notNull().getProperty();
+        dictData.addToOne(device, deviceId);
+        Property userId = dictData.addLongProperty("userId").notNull().getProperty();
+        dictData.addToOne(user, userId);
+
+        dictData.addIntProperty("dictClass").notNull();
+        dictData.addLongProperty("startTimestamp").notNull();
+        dictData.addLongProperty("endTimestamp");
+        dictData.addLongProperty("modifyTimestamp");
+
+        return dictData;
+    }
+
+    private static Entity addHuaweiDictDataValues(Schema schema, Entity summaryEntity) {
+        Entity dictDataValues = addEntity(schema, "HuaweiDictDataValues");
+
+        dictDataValues.setJavaDoc("Contains Huawei Dict data values");
+
+        Property id = dictDataValues.addLongProperty("dictId").primaryKey().notNull().getProperty();
+        dictDataValues.addToOne(summaryEntity, id);
+
+        dictDataValues.addIntProperty("dictType").notNull().primaryKey();
+        dictDataValues.addByteProperty("tag").notNull().primaryKey();
+        dictDataValues.addByteArrayProperty("value");
+
+        return dictDataValues;
+    }
+
+
+    private static void addTemperatureProperties(Entity activitySample) {
+        activitySample.addFloatProperty(SAMPLE_TEMPERATURE).notNull().codeBeforeGetter(OVERRIDE);
+        activitySample.addIntProperty(SAMPLE_TEMPERATURE_TYPE).notNull().codeBeforeGetter(OVERRIDE);
+    }
+
+    private static Entity addFemometerVinca2TemperatureSample(Schema schema, Entity user, Entity device) {
+        Entity sample = addEntity(schema, "FemometerVinca2TemperatureSample");
+        addCommonTimeSampleProperties("AbstractTemperatureSample", sample, user, device);
+        addTemperatureProperties(sample);
+        return sample;
+    }
+
+    private static Entity addMiScaleWeightSample(Schema schema, Entity user, Entity device) {
+        Entity sample = addEntity(schema, "MiScaleWeightSample");
+        addCommonTimeSampleProperties("AbstractWeightSample", sample, user, device);
+        sample.addFloatProperty(SAMPLE_WEIGHT_KG).notNull().codeBeforeGetter(OVERRIDE);
+        return sample;
+    }
+}
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..dbbe355
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,661 @@
+                    GNU AFFERO GENERAL PUBLIC LICENSE
+                       Version 3, 19 November 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The GNU Affero General Public License is a free, copyleft license for
+software and other kinds of works, specifically designed to ensure
+cooperation with the community in the case of network server software.
+
+  The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works.  By contrast,
+our General Public Licenses are intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+  Developers that use our General Public Licenses protect your rights
+with two steps: (1) assert copyright on the software, and (2) offer
+you this License which gives you legal permission to copy, distribute
+and/or modify the software.
+
+  A secondary benefit of defending all users' freedom is that
+improvements made in alternate versions of the program, if they
+receive widespread use, become available for other developers to
+incorporate.  Many developers of free software are heartened and
+encouraged by the resulting cooperation.  However, in the case of
+software used on network servers, this result may fail to come about.
+The GNU General Public License permits making a modified version and
+letting the public access it on a server without ever releasing its
+source code to the public.
+
+  The GNU Affero General Public License is designed specifically to
+ensure that, in such cases, the modified source code becomes available
+to the community.  It requires the operator of a network server to
+provide the source code of the modified version running there to the
+users of that server.  Therefore, public use of a modified version, on
+a publicly accessible server, gives the public access to the source
+code of the modified version.
+
+  An older license, called the Affero General Public License and
+published by Affero, was designed to accomplish similar goals.  This is
+a different license, not a version of the Affero GPL, but Affero has
+released a new version of the Affero GPL which permits relicensing under
+this license.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                       TERMS AND CONDITIONS
+
+  0. Definitions.
+
+  "This License" refers to version 3 of the GNU Affero General Public License.
+
+  "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+  "The Program" refers to any copyrightable work licensed under this
+License.  Each licensee is addressed as "you".  "Licensees" and
+"recipients" may be individuals or organizations.
+
+  To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy.  The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+  A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+  To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy.  Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+  To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies.  Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+  An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License.  If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+  1. Source Code.
+
+  The "source code" for a work means the preferred form of the work
+for making modifications to it.  "Object code" means any non-source
+form of a work.
+
+  A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+  The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form.  A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+  The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities.  However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work.  For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+  The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+  The Corresponding Source for a work in source code form is that
+same work.
+
+  2. Basic Permissions.
+
+  All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met.  This License explicitly affirms your unlimited
+permission to run the unmodified Program.  The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work.  This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+  You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force.  You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright.  Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+  Conveying under any other circumstances is permitted solely under
+the conditions stated below.  Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+  No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+  When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+  4. Conveying Verbatim Copies.
+
+  You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+  You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+  5. Conveying Modified Source Versions.
+
+  You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+    a) The work must carry prominent notices stating that you modified
+    it, and giving a relevant date.
+
+    b) The work must carry prominent notices stating that it is
+    released under this License and any conditions added under section
+    7.  This requirement modifies the requirement in section 4 to
+    "keep intact all notices".
+
+    c) You must license the entire work, as a whole, under this
+    License to anyone who comes into possession of a copy.  This
+    License will therefore apply, along with any applicable section 7
+    additional terms, to the whole of the work, and all its parts,
+    regardless of how they are packaged.  This License gives no
+    permission to license the work in any other way, but it does not
+    invalidate such permission if you have separately received it.
+
+    d) If the work has interactive user interfaces, each must display
+    Appropriate Legal Notices; however, if the Program has interactive
+    interfaces that do not display Appropriate Legal Notices, your
+    work need not make them do so.
+
+  A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit.  Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+  6. Conveying Non-Source Forms.
+
+  You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+    a) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by the
+    Corresponding Source fixed on a durable physical medium
+    customarily used for software interchange.
+
+    b) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by a
+    written offer, valid for at least three years and valid for as
+    long as you offer spare parts or customer support for that product
+    model, to give anyone who possesses the object code either (1) a
+    copy of the Corresponding Source for all the software in the
+    product that is covered by this License, on a durable physical
+    medium customarily used for software interchange, for a price no
+    more than your reasonable cost of physically performing this
+    conveying of source, or (2) access to copy the
+    Corresponding Source from a network server at no charge.
+
+    c) Convey individual copies of the object code with a copy of the
+    written offer to provide the Corresponding Source.  This
+    alternative is allowed only occasionally and noncommercially, and
+    only if you received the object code with such an offer, in accord
+    with subsection 6b.
+
+    d) Convey the object code by offering access from a designated
+    place (gratis or for a charge), and offer equivalent access to the
+    Corresponding Source in the same way through the same place at no
+    further charge.  You need not require recipients to copy the
+    Corresponding Source along with the object code.  If the place to
+    copy the object code is a network server, the Corresponding Source
+    may be on a different server (operated by you or a third party)
+    that supports equivalent copying facilities, provided you maintain
+    clear directions next to the object code saying where to find the
+    Corresponding Source.  Regardless of what server hosts the
+    Corresponding Source, you remain obligated to ensure that it is
+    available for as long as needed to satisfy these requirements.
+
+    e) Convey the object code using peer-to-peer transmission, provided
+    you inform other peers where the object code and Corresponding
+    Source of the work are being offered to the general public at no
+    charge under subsection 6d.
+
+  A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+  A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling.  In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage.  For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product.  A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+  "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source.  The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+  If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information.  But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+  The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed.  Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+  Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+  7. Additional Terms.
+
+  "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law.  If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+  When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it.  (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.)  You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+  Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+    a) Disclaiming warranty or limiting liability differently from the
+    terms of sections 15 and 16 of this License; or
+
+    b) Requiring preservation of specified reasonable legal notices or
+    author attributions in that material or in the Appropriate Legal
+    Notices displayed by works containing it; or
+
+    c) Prohibiting misrepresentation of the origin of that material, or
+    requiring that modified versions of such material be marked in
+    reasonable ways as different from the original version; or
+
+    d) Limiting the use for publicity purposes of names of licensors or
+    authors of the material; or
+
+    e) Declining to grant rights under trademark law for use of some
+    trade names, trademarks, or service marks; or
+
+    f) Requiring indemnification of licensors and authors of that
+    material by anyone who conveys the material (or modified versions of
+    it) with contractual assumptions of liability to the recipient, for
+    any liability that these contractual assumptions directly impose on
+    those licensors and authors.
+
+  All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10.  If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term.  If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+  If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+  Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+  8. Termination.
+
+  You may not propagate or modify a covered work except as expressly
+provided under this License.  Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+  However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+  Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+  Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License.  If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+  9. Acceptance Not Required for Having Copies.
+
+  You are not required to accept this License in order to receive or
+run a copy of the Program.  Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance.  However,
+nothing other than this License grants you permission to propagate or
+modify any covered work.  These actions infringe copyright if you do
+not accept this License.  Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+  10. Automatic Licensing of Downstream Recipients.
+
+  Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License.  You are not responsible
+for enforcing compliance by third parties with this License.
+
+  An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations.  If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+  You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License.  For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+  11. Patents.
+
+  A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based.  The
+work thus licensed is called the contributor's "contributor version".
+
+  A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version.  For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+  Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+  In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement).  To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+  If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients.  "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+  If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+  A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License.  You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+  Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+  12. No Surrender of Others' Freedom.
+
+  If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all.  For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+  13. Remote Network Interaction; Use with the GNU General Public License.
+
+  Notwithstanding any other provision of this License, if you modify the
+Program, your modified version must prominently offer all users
+interacting with it remotely through a computer network (if your version
+supports such interaction) an opportunity to receive the Corresponding
+Source of your version by providing access to the Corresponding Source
+from a network server at no charge, through some standard or customary
+means of facilitating copying of software.  This Corresponding Source
+shall include the Corresponding Source for any work covered by version 3
+of the GNU General Public License that is incorporated pursuant to the
+following paragraph.
+
+  Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU General Public License into a single
+combined work, and to convey the resulting work.  The terms of this
+License will continue to apply to the part which is the covered work,
+but the work with which it is combined will remain governed by version
+3 of the GNU General Public License.
+
+  14. Revised Versions of this License.
+
+  The Free Software Foundation may publish revised and/or new versions of
+the GNU Affero General Public License from time to time.  Such new versions
+will be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+  Each version is given a distinguishing version number.  If the
+Program specifies that a certain numbered version of the GNU Affero General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation.  If the Program does not specify a version number of the
+GNU Affero General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+  If the Program specifies that a proxy can decide which future
+versions of the GNU Affero General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+  Later license versions may give you additional or different
+permissions.  However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+  15. Disclaimer of Warranty.
+
+  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. Limitation of Liability.
+
+  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+  17. Interpretation of Sections 15 and 16.
+
+  If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program 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.
+
+    This program 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 <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+  If your software can interact with users remotely through a computer
+network, you should also make sure that it provides a way for users to
+get its source.  For example, if your program is a web application, its
+interface could display a "Source" link that leads users to an archive
+of the code.  There are many ways you could offer source, and different
+solutions will be better for different programs; see section 13 for the
+specific requirements.
+
+  You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU AGPL, see
+<http://www.gnu.org/licenses/>.
diff --git a/LICENSE.artwork b/LICENSE.artwork
new file mode 100644
index 0000000..71e1fe2
--- /dev/null
+++ b/LICENSE.artwork
@@ -0,0 +1,36 @@
+The following artwork is licensed under the following licenses
+
+Creative Commons Attribution-ShareAlike 4.0 International (CC BY-SA 4.0):
+   ic_launcher.png
+   ic_device_pebble.png
+   ic_device_miband.png
+   ic_device_lovetoy.png
+   ic_device_hplus.png
+   ic_device_default.png
+   ic_activitytracker.png
+   ic_watchface.png
+   ic_languagepack.png
+   ic_firmware.png
+   ic_watchapp.png
+   ic_systemapp.png
+   icon.png (fastlane metadata directories)
+   featureGraphic.png (fastlane metadata directories)
+(All of the above by xphnx)
+
+Creative Commons Attribution-ShareAlike 4.0 International (CC BY-SA 4.0):
+   "GET IT ON F-Droid" button by Laura Kalbag. Source: https://ind.ie/about/blog/f-droid-button/
+
+Creative Commons Attribution 3.0 Unported license (CC BY-3.0):
+   ic_notification_battery_low.png by Picol.org. Source: https://commons.wikimedia.org/wiki/File:Battery_1_Picol_icon.svg
+
+Creative Commons Attribution 3.0 United States   (CC BY-3.0 US):
+   ic_donate by Peter van Driel https://thenounproject.com/term/donate/239009/
+
+Creative Commons Attribution-ShareAlike 4.0 International (CC BY-SA 4.0):
+   ic_device_amazfit_bip by Michael quelbs@gmail.com
+
+MIT License as per https://github.com/flipperdevices/Flipper-iOS-App/blob/dev/LICENSE
+    Flipper device icons, Source SVG https://raw.githubusercontent.com/flipperdevices/Flipper-iOS-App/main/Flipper/Shared/Assets.xcassets/Device/FlipperBlack.imageset/FlipperBlack.svg
+
+Apache License Version 2.0 as per https://github.com/google/material-design-icons/blob/master/LICENSE
+   Material Symbols & Icons. Source: https://github.com/google/material-design-icons
diff --git a/app/build.gradle b/app/build.gradle
new file mode 100644
index 0000000..6d1ae31
--- /dev/null
+++ b/app/build.gradle
@@ -0,0 +1,303 @@
+import java.nio.file.Files
+
+apply plugin: "com.android.application"
+apply plugin: 'com.google.protobuf'
+
+tasks.withType(Test) {
+    systemProperty "MiFirmwareDir", System.getProperty("MiFirmwareDir", null)
+    systemProperty "logback.configurationFile", System.getProperty("user.dir", null) + "/app/src/main/assets/logback.xml"
+    systemProperty "GB_LOGFILES_DIR", Files.createTempDirectory("gblog").toString()
+}
+
+def getVersionCode = { ->
+    try {
+        def commitCount = providers.exec {
+            commandLine('git', 'rev-list', 'HEAD', '--count')
+        }.standardOutput.asText.get().trim()
+        return Integer.valueOf(commitCount)
+    } catch (ignored) {
+        return null
+    }
+}
+
+def buildGitChangelog = {
+    def allCommits = providers.exec {
+        commandLine('git', 'log', '--pretty=format:%h %s')
+    }.standardOutput.asText.get()
+
+    def commitVersionCode = getVersionCode()
+    def includedCommits = 0
+    def changelogNode = new Node(null, 'changelog')
+
+    allCommits.trim().eachLine { line ->
+        if (includedCommits > 100) {
+            return true;
+        }
+
+        def (commitHash, commitMessage) = line.split(" ", 2)
+        if (commitMessage.contains("Translated using Weblate")) {
+            return true;
+        }
+
+        def releaseNode = new Node(changelogNode, 'release', [version: commitHash, versioncode: commitVersionCode--])
+        def _ = new Node(releaseNode, 'change', [:], commitMessage)
+        includedCommits++
+    }
+
+    def changelogFile = new File("${project.rootDir}/app/build/generated/res/changelog/xml/changelog_git.xml")
+    changelogFile.getParentFile().mkdirs()
+    changelogFile.write(groovy.xml.XmlUtil.serialize(changelogNode))
+}
+
+def getGitHashShort = { ->
+    try {
+        return providers.exec {
+            commandLine('git', 'rev-parse', '--short', 'HEAD')
+        }.standardOutput.asText.get().trim()
+    } catch (ignored) {
+        return null
+    }
+}
+
+android {
+    compileOptions {
+        coreLibraryDesugaringEnabled true
+
+        // for Android 5+
+        sourceCompatibility JavaVersion.VERSION_1_8
+        targetCompatibility JavaVersion.VERSION_1_8
+    }
+
+    namespace 'nodomain.freeyourgadget.gadgetbridge'
+
+    defaultConfig {
+        applicationId "com.jsbogang.womp.android"
+
+        //noinspection OldTargetApi
+        targetSdkVersion 34
+        compileSdk 34
+        minSdkVersion 21
+
+        // Note: always bump BOTH versionCode and versionName!
+        versionName "0.83.0"
+        versionCode 235
+        vectorDrawables.useSupportLibrary = true
+        buildConfigField "String", "GIT_HASH_SHORT", "\"${getGitHashShort()}\""
+        buildConfigField "boolean", "INTERNET_ACCESS", "true"
+    }
+
+    signingConfigs {
+        nightly {
+            if (System.getProperty("nightly_store_file") != null) {
+                storeFile file(System.getProperty("nightly_store_file"))
+                storePassword System.getProperty("nightly_store_password")
+                keyAlias System.getProperty("nightly_key_alias")
+                keyPassword System.getProperty("nightly_key_password")
+            }
+        }
+    }
+
+    flavorDimensions "device_type"
+    productFlavors {
+        mainline {
+            // Ensure that when starting from scratch, 'mainline' is selected, not 'banglejs'
+            getIsDefault().set(true)
+            // the default build product flavor
+            dimension "device_type"
+            //applicationIdSuffix ""
+            //versionNameSuffix ""
+        }
+
+        banglejs {
+            dimension "device_type"
+            applicationId "com.espruino.gadgetbridge"
+            applicationIdSuffix ".banglejs"
+            versionNameSuffix "-banglejs"
+            buildConfigField "boolean", "INTERNET_ACCESS", "true"
+            targetSdkVersion 34
+            // Note: app/src/banglejs/AndroidManifest.xml contains some extra permissions
+        }
+    }
+
+    sourceSets {
+        main {
+            res.srcDirs += "build/generated/res/changelog"
+        }
+    }
+
+    buildTypes {
+        release {
+            minifyEnabled true
+            //proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
+        }
+
+        nightly {
+            applicationIdSuffix ".nightly"
+            versionNameSuffix "-${getGitHashShort}"
+            //proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
+            minifyEnabled true
+            debuggable false
+
+            if (System.getProperty("nightly_store_file") != null) {
+                signingConfig signingConfigs.nightly
+            } else {
+                signingConfig signingConfigs.debug
+            }
+        }
+
+        nopebble {
+            applicationIdSuffix ".nightly_nopebble"
+            versionNameSuffix "-${getGitHashShort}"
+            //proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
+            minifyEnabled true
+            debuggable false
+
+            if (System.getProperty("nightly_store_file") != null) {
+                signingConfig signingConfigs.nightly
+            } else {
+                signingConfig signingConfigs.debug
+            }
+        }
+
+        applicationVariants.all { variant ->
+            variant.resValue "string", "applicationId", variant.applicationId
+            buildGitChangelog()
+
+            if (variant.buildType.name == 'nightly' || variant.buildType.name == 'nopebble') {
+                variant.outputs.all {
+                    setVersionCodeOverride(getVersionCode())
+                    //setVersionNameOverride(getGitHashShort())
+                    setVersionNameOverride(variant.versionName)
+                    outputFileName = "${applicationId}_${variant.versionName}.apk"
+                }
+            }
+        }
+    }
+
+    lint {
+        abortOnError true
+        lintConfig file("$rootDir/app/src/main/lint.xml")
+        // If true, generate an HTML report (with issue explanations, sourcecode, etc)
+        htmlReport true
+        // Optional path to report (default will be lint-results.html in the builddir)
+        htmlOutput file("$project.buildDir/reports/lint/lint.html")
+        // Ignore checks present in the snapshot
+        baseline file("lint-baseline.xml")
+    }
+
+    testOptions {
+        unitTests {
+            returnDefaultValues = true
+            includeAndroidResources = true
+        }
+    }
+    buildFeatures {
+        aidl true
+        buildConfig true
+    }
+}
+
+dependencies {
+    coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.3'
+
+    implementation 'androidx.constraintlayout:constraintlayout:2.2.0'
+    implementation "androidx.camera:camera-core:1.4.1"
+    implementation "androidx.camera:camera-camera2:1.4.1"
+    implementation 'androidx.camera:camera-view:1.4.1'
+    implementation 'androidx.camera:camera-lifecycle:1.4.1'
+
+    testImplementation "junit:junit:4.13.2"
+    testImplementation "org.mockito:mockito-core:5.14.2"
+    testImplementation "org.robolectric:robolectric:4.14.1"
+    testImplementation "org.hamcrest:hamcrest-library:3.0"
+
+    implementation "androidx.appcompat:appcompat:1.7.0"
+    implementation "androidx.preference:preference:1.2.1"
+    implementation "androidx.cardview:cardview:1.0.0"
+    implementation "androidx.recyclerview:recyclerview:1.3.2"
+    implementation "androidx.legacy:legacy-support-v4:1.0.0"
+    implementation "androidx.gridlayout:gridlayout:1.0.0"
+    implementation "androidx.palette:palette:1.0.0"
+    implementation "androidx.activity:activity:1.9.3"
+    implementation "androidx.fragment:fragment:1.8.5"
+    implementation "androidx.viewpager2:viewpager2:1.1.0"
+
+    // Not latest version because of https://github.com/material-components/material-components-android/issues/3924
+    implementation "com.google.android.material:material:1.10.0"
+    implementation 'com.google.android.flexbox:flexbox:3.0.0'
+    implementation "com.google.code.gson:gson:2.11.0"
+
+    implementation "no.nordicsemi.android:dfu:2.5.0"
+    implementation "com.github.tony19:logback-android:3.0.0"
+    implementation "org.slf4j:slf4j-api:2.0.16"
+    implementation "com.github.PhilJay:MPAndroidChart:3.1.0"
+    implementation "com.github.pfichtner:durationformatter:0.1.1"
+    implementation "de.cketti.library.changelog:ckchangelog:1.2.2"
+    implementation "net.e175.klaus:solarpositioning:0.1.10"
+    implementation "co.nstant.in:cbor:0.9"
+    // use pristine greendao instead of our custom version, since our custom jitpack-packaged
+    // version contains way too much and our custom patches are in the generator only.
+    implementation "org.greenrobot:greendao:2.2.1"
+    implementation "org.apache.commons:commons-lang3:3.17.0"
+    implementation "org.cyanogenmod:platform.sdk:6.0"
+    implementation 'com.jaredrummler:colorpicker:1.1.0'
+    implementation 'com.github.wax911.android-emojify:contract:1.9.5'
+    implementation 'com.github.wax911.android-emojify:emojify:1.9.5'
+    implementation 'com.github.wax911.android-emojify:gson:1.9.5'
+    implementation 'com.google.protobuf:protobuf-javalite:4.29.1'
+    implementation 'com.android.volley:volley:1.2.1'
+    implementation 'com.google.zxing:core:3.5.3'
+    implementation 'org.msgpack:msgpack-core:0.9.8'
+
+    // Bouncy Castle is included directly in GB, to avoid pulling the entire dependency
+    // It's included in the org.bouncycastle.shaded package, to fix conflicts with roboelectric
+    //implementation 'org.bouncycastle:bcpkix-jdk18on:1.76'
+    //implementation 'org.bouncycastle:bcprov-jdk18on:1.76'
+
+    // Android SDK bundles org.json, but we need an actual implementation to replace the stubs in tests
+    testImplementation 'org.json:json:20240303'
+
+    // Fix Duplicate class build error for conflicting kotlin-stdlib versions
+    // does not seem to be currently needed, as it uses the latest across all transitive
+    //implementation(platform("org.jetbrains.kotlin:kotlin-bom:1.8.0"))
+
+    // Needed for Armenian transliteration
+    implementation 'org.ahocorasick:ahocorasick:0.6.3'
+}
+
+preBuild.dependsOn(":GBDaoGenerator:genSources")
+
+//gradle.beforeProject {
+//    preBuild.dependsOn(":GBDaoGenerator:genSources")
+//}
+
+sourceSets {
+    main {
+        java.srcDirs += "${protobuf.generatedFilesBaseDir}"
+        java.srcDirs += "build/generated/source/buildConfig"
+    }
+}
+
+tasks.register('cleanGenerated', Delete) {
+    delete fileTree('src/main/java/nodomain/freeyourgadget/gadgetbridge/entities') {
+        include '**/*.java'
+        exclude '**/Abstract*.java'
+    }
+}
+
+tasks.clean.dependsOn(tasks.cleanGenerated)
+
+protobuf {
+    protoc {
+        artifact = 'com.google.protobuf:protoc:4.29.1'
+    }
+    generateProtoTasks {
+        all().each { task ->
+            task.builtins {
+                java {
+                    option 'lite'
+                }
+            }
+        }
+    }
+}
diff --git a/app/lint-baseline.xml b/app/lint-baseline.xml
new file mode 100644
index 0000000..f5af860
--- /dev/null
+++ b/app/lint-baseline.xml
@@ -0,0 +1,16179 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="6" by="lint 7.4.2" type="baseline" client="gradle" dependencies="false" name="AGP (7.4.2)" variant="all" version="7.4.2">
+
+    <issue
+        id="MissingPermission"
+        message="Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with `checkPermission`) or explicitly handle a potential `SecurityException`"
+        errorLine1="        LOG.info(&quot;connection attempt detected from &quot; + device.getAddress() + &quot;(&quot; + device.getName() + &quot;)&quot;);"
+        errorLine2="                                                                                   ~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/BluetoothConnectReceiver.java"
+            line="53"
+            column="84"/>
+    </issue>
+
+    <issue
+        id="MissingPermission"
+        message="Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with `checkPermission`) or explicitly handle a potential `SecurityException`"
+        errorLine1="                    LOG.info(&quot;Bond state changed: &quot; + device + &quot;, state: &quot; + device.getBondState() + &quot;, expected address: &quot; + bondingMacAddress);"
+        errorLine2="                                                                             ~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/util/BondingUtil.java"
+            line="96"
+            column="78"/>
+    </issue>
+
+    <issue
+        id="MissingPermission"
+        message="Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with `checkPermission`) or explicitly handle a potential `SecurityException`"
+        errorLine1="        if (device.createBond()) {"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/util/BondingUtil.java"
+            line="229"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="MissingPermission"
+        message="Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with `checkPermission`) or explicitly handle a potential `SecurityException`"
+        errorLine1="                    device.getName(),"
+        errorLine2="                    ~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/util/BondingUtil.java"
+            line="235"
+            column="21"/>
+    </issue>
+
+    <issue
+        id="MissingPermission"
+        message="Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with `checkPermission`) or explicitly handle a potential `SecurityException`"
+        errorLine1="                    device.getType())"
+        errorLine2="                    ~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/util/BondingUtil.java"
+            line="237"
+            column="21"/>
+    </issue>
+
+    <issue
+        id="MissingPermission"
+        message="Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with `checkPermission`) or explicitly handle a potential `SecurityException`"
+        errorLine1="            BluetoothClass bluetoothClass = device.getBluetoothClass();"
+        errorLine2="                                            ~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/util/BondingUtil.java"
+            line="240"
+            column="45"/>
+    </issue>
+
+    <issue
+        id="MissingPermission"
+        message="Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with `checkPermission`) or explicitly handle a potential `SecurityException`"
+        errorLine1="            if (device.getBondState() == BluetoothDevice.BOND_BONDED) {"
+        errorLine2="                ~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/util/BondingUtil.java"
+            line="250"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="MissingPermission"
+        message="Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with `checkPermission`) or explicitly handle a potential `SecurityException`"
+        errorLine1="            } else if (device.getBondState() == BluetoothDevice.BOND_BONDING) {"
+        errorLine2="                       ~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/util/BondingUtil.java"
+            line="253"
+            column="24"/>
+    </issue>
+
+    <issue
+        id="MissingPermission"
+        message="Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with `checkPermission`) or explicitly handle a potential `SecurityException`"
+        errorLine1="                toast(context.getContext(), context.getContext().getString(R.string.discovery_bonding_failed_immediately, device.getName()), Toast.LENGTH_SHORT, GB.ERROR);"
+        errorLine2="                                                                                                                          ~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/util/BondingUtil.java"
+            line="258"
+            column="123"/>
+    </issue>
+
+    <issue
+        id="MissingPermission"
+        message="Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with `checkPermission`) or explicitly handle a potential `SecurityException`"
+        errorLine1="                    if (deviceToPair.getBondState() != BluetoothDevice.BOND_BONDED) {"
+        errorLine2="                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/util/BondingUtil.java"
+            line="276"
+            column="25"/>
+    </issue>
+
+    <issue
+        id="MissingPermission"
+        message="Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with `checkPermission`) or explicitly handle a potential `SecurityException`"
+        errorLine1="        return (device.getType() == BluetoothDevice.DEVICE_TYPE_DUAL || device.getType() == BluetoothDevice.DEVICE_TYPE_LE) &amp;&amp;"
+        errorLine2="                ~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/util/BondingUtil.java"
+            line="292"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="MissingPermission"
+        message="Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with `checkPermission`) or explicitly handle a potential `SecurityException`"
+        errorLine1="        return (device.getType() == BluetoothDevice.DEVICE_TYPE_DUAL || device.getType() == BluetoothDevice.DEVICE_TYPE_LE) &amp;&amp;"
+        errorLine2="                                                                        ~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/util/BondingUtil.java"
+            line="292"
+            column="73"/>
+    </issue>
+
+    <issue
+        id="MissingPermission"
+        message="Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with `checkPermission`) or explicitly handle a potential `SecurityException`"
+        errorLine1="                (device.getName().startsWith(&quot;Pebble-LE &quot;) || device.getName().startsWith(&quot;Pebble Time LE &quot;));"
+        errorLine2="                 ~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/util/BondingUtil.java"
+            line="293"
+            column="18"/>
+    </issue>
+
+    <issue
+        id="MissingPermission"
+        message="Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with `checkPermission`) or explicitly handle a potential `SecurityException`"
+        errorLine1="                (device.getName().startsWith(&quot;Pebble-LE &quot;) || device.getName().startsWith(&quot;Pebble Time LE &quot;));"
+        errorLine2="                                                              ~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/util/BondingUtil.java"
+            line="293"
+            column="63"/>
+    </issue>
+
+    <issue
+        id="MissingPermission"
+        message="Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with `checkPermission`) or explicitly handle a potential `SecurityException`"
+        errorLine1="        return device.getType() == BluetoothDevice.DEVICE_TYPE_LE &amp;&amp;"
+        errorLine2="               ~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/util/BondingUtil.java"
+            line="300"
+            column="16"/>
+    </issue>
+
+    <issue
+        id="MissingPermission"
+        message="Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with `checkPermission`) or explicitly handle a potential `SecurityException`"
+        errorLine1="                device.getName().startsWith(&quot;Pebble &quot;) &amp;&amp;"
+        errorLine2="                ~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/util/BondingUtil.java"
+            line="301"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="MissingPermission"
+        message="Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with `checkPermission`) or explicitly handle a potential `SecurityException`"
+        errorLine1="                !device.getName().startsWith(&quot;Pebble Time LE &quot;);"
+        errorLine2="                 ~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/util/BondingUtil.java"
+            line="302"
+            column="18"/>
+    </issue>
+
+    <issue
+        id="MissingPermission"
+        message="Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with `checkPermission`) or explicitly handle a potential `SecurityException`"
+        errorLine1="        int bondState = device.getBondState();"
+        errorLine2="                        ~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/util/BondingUtil.java"
+            line="371"
+            column="25"/>
+    </issue>
+
+    <issue
+        id="MissingPermission"
+        message="Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with `checkPermission`) or explicitly handle a potential `SecurityException`"
+        errorLine1="            GB.toast(bondingInterface.getContext().getString(R.string.pairing_already_bonded, device.getName(), device.getAddress()), Toast.LENGTH_SHORT, GB.INFO);"
+        errorLine2="                                                                                              ~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/util/BondingUtil.java"
+            line="373"
+            column="95"/>
+    </issue>
+
+    <issue
+        id="MissingPermission"
+        message="Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with `checkPermission`) or explicitly handle a potential `SecurityException`"
+        errorLine1="            GB.toast(bondingInterface.getContext(), bondingInterface.getContext().getString(R.string.pairing_in_progress, device.getName(), device.getAddress()), Toast.LENGTH_LONG, GB.INFO);"
+        errorLine2="                                                                                                                          ~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/util/BondingUtil.java"
+            line="385"
+            column="123"/>
+    </issue>
+
+    <issue
+        id="MissingPermission"
+        message="Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with `checkPermission`) or explicitly handle a potential `SecurityException`"
+        errorLine1="        GB.toast(bondingInterface.getContext(), bondingInterface.getContext().getString(R.string.pairing_creating_bond_with, device.getName(), device.getAddress()), Toast.LENGTH_LONG, GB.INFO);"
+        errorLine2="                                                                                                                             ~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/util/BondingUtil.java"
+            line="389"
+            column="126"/>
+    </issue>
+
+    <issue
+        id="MissingPermission"
+        message="Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with `checkPermission`) or explicitly handle a potential `SecurityException`"
+        errorLine1="            ParcelUuid uuids[] = btDevice.getUuids();"
+        errorLine2="                                 ~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btclassic/BtClassicIoThread.java"
+            line="150"
+            column="34"/>
+    </issue>
+
+    <issue
+        id="MissingPermission"
+        message="Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with `checkPermission`) or explicitly handle a potential `SecurityException`"
+        errorLine1="            mBtSocket = btDevice.createRfcommSocketToServiceRecord(getUuidToConnect(uuids));"
+        errorLine2="                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btclassic/BtClassicIoThread.java"
+            line="158"
+            column="25"/>
+    </issue>
+
+    <issue
+        id="MissingPermission"
+        message="Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with `checkPermission`) or explicitly handle a potential `SecurityException`"
+        errorLine1="            mBtSocket.connect();"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btclassic/BtClassicIoThread.java"
+            line="159"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="MissingPermission"
+        message="Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with `checkPermission`) or explicitly handle a potential `SecurityException`"
+        errorLine1="        mBluetoothAdapter.cancelDiscovery();"
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BtLEQueue.java"
+            line="260"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="MissingPermission"
+        message="Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with `checkPermission`) or explicitly handle a potential `SecurityException`"
+        errorLine1="            mBluetoothGattServer = bluetoothManager.openGattServer(mContext, internalGattServerCallback);"
+        errorLine2="                                   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BtLEQueue.java"
+            line="268"
+            column="36"/>
+    </issue>
+
+    <issue
+        id="MissingPermission"
+        message="Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with `checkPermission`) or explicitly handle a potential `SecurityException`"
+        errorLine1="                mBluetoothGattServer.addService(service);"
+        errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BtLEQueue.java"
+            line="274"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="MissingPermission"
+        message="Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with `checkPermission`) or explicitly handle a potential `SecurityException`"
+        errorLine1="                mBluetoothGatt = remoteDevice.connectGatt(mContext, false, internalGattCallback, BluetoothDevice.TRANSPORT_LE);"
+        errorLine2="                                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BtLEQueue.java"
+            line="281"
+            column="34"/>
+    </issue>
+
+    <issue
+        id="MissingPermission"
+        message="Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with `checkPermission`) or explicitly handle a potential `SecurityException`"
+        errorLine1="                mBluetoothGatt = remoteDevice.connectGatt(mContext, false, internalGattCallback);"
+        errorLine2="                                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BtLEQueue.java"
+            line="283"
+            column="34"/>
+    </issue>
+
+    <issue
+        id="MissingPermission"
+        message="Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with `checkPermission`) or explicitly handle a potential `SecurityException`"
+        errorLine1="                gatt.disconnect();"
+        errorLine2="                ~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BtLEQueue.java"
+            line="308"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="MissingPermission"
+        message="Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with `checkPermission`) or explicitly handle a potential `SecurityException`"
+        errorLine1="                gatt.close();"
+        errorLine2="                ~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BtLEQueue.java"
+            line="309"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="MissingPermission"
+        message="Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with `checkPermission`) or explicitly handle a potential `SecurityException`"
+        errorLine1="                gattServer.clearServices();"
+        errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BtLEQueue.java"
+            line="316"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="MissingPermission"
+        message="Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with `checkPermission`) or explicitly handle a potential `SecurityException`"
+        errorLine1="                gattServer.close();"
+        errorLine2="                ~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BtLEQueue.java"
+            line="317"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="MissingPermission"
+        message="Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with `checkPermission`) or explicitly handle a potential `SecurityException`"
+        errorLine1="            boolean result = mBluetoothGatt.connect();"
+        errorLine2="                             ~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BtLEQueue.java"
+            line="359"
+            column="30"/>
+    </issue>
+
+    <issue
+        id="MissingPermission"
+        message="Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with `checkPermission`) or explicitly handle a potential `SecurityException`"
+        errorLine1="                                    mBluetoothGatt.discoverServices();"
+        errorLine2="                                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BtLEQueue.java"
+            line="534"
+            column="37"/>
+    </issue>
+
+    <issue
+        id="MissingPermission"
+        message="Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with `checkPermission`) or explicitly handle a potential `SecurityException`"
+        errorLine1="                mBluetoothGattServer.sendResponse(device, requestId, success ? BluetoothGatt.GATT_SUCCESS : BluetoothGatt.GATT_FAILURE, 0, new byte[0]);"
+        errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BtLEQueue.java"
+            line="777"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="MissingPermission"
+        message="Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with `checkPermission`) or explicitly handle a potential `SecurityException`"
+        errorLine1="                mBluetoothGattServer.sendResponse(device, requestId, success ? BluetoothGatt.GATT_SUCCESS : BluetoothGatt.GATT_FAILURE, 0, new byte[0]);"
+        errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BtLEQueue.java"
+            line="803"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="MissingPermission"
+        message="Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with `checkPermission`) or explicitly handle a potential `SecurityException`"
+        errorLine1="                if (deviceToPair.getBondState() != BluetoothDevice.BOND_BONDED) {"
+        errorLine2="                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DebugActivity.java"
+            line="760"
+            column="21"/>
+    </issue>
+
+    <issue
+        id="MissingPermission"
+        message="Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with `checkPermission`) or explicitly handle a potential `SecurityException`"
+        errorLine1="                    deviceToPair.createBond();"
+        errorLine2="                    ~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DebugActivity.java"
+            line="762"
+            column="21"/>
+    </issue>
+
+    <issue
+        id="MissingPermission"
+        message="Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with `checkPermission`) or explicitly handle a potential `SecurityException`"
+        errorLine1="        GBDeviceCandidate candidate = new GBDeviceCandidate(device, GBDevice.RSSI_UNKNOWN, device.getUuids());"
+        errorLine2="                                                                                           ~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DeviceHelper.java"
+            line="120"
+            column="92"/>
+    </issue>
+
+    <issue
+        id="MissingPermission"
+        message="Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with `checkPermission`) or explicitly handle a potential `SecurityException`"
+        errorLine1="            if (device != null &amp;&amp; device.getBondState() == BluetoothDevice.BOND_NONE) {"
+        errorLine2="                                  ~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandPairingActivity.java"
+            line="172"
+            column="35"/>
+    </issue>
+
+    <issue
+        id="MissingPermission"
+        message="Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with `checkPermission`) or explicitly handle a potential `SecurityException`"
+        errorLine1="        boolean result = gatt.setCharacteristicNotification(getCharacteristic(), enableFlag);"
+        errorLine2="                         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/NotifyAction.java"
+            line="48"
+            column="26"/>
+    </issue>
+
+    <issue
+        id="MissingPermission"
+        message="Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with `checkPermission`) or explicitly handle a potential `SecurityException`"
+        errorLine1="                    result = gatt.writeDescriptor(notifyDescriptor);"
+        errorLine2="                             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/NotifyAction.java"
+            line="56"
+            column="30"/>
+    </issue>
+
+    <issue
+        id="MissingPermission"
+        message="Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with `checkPermission`) or explicitly handle a potential `SecurityException`"
+        errorLine1="                    result = gatt.writeDescriptor(notifyDescriptor);"
+        errorLine2="                             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/NotifyAction.java"
+            line="60"
+            column="30"/>
+    </issue>
+
+    <issue
+        id="MissingPermission"
+        message="Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with `checkPermission`) or explicitly handle a potential `SecurityException`"
+        errorLine1="            gatt.discoverServices();"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/ble/PebbleGATTClient.java"
+            line="115"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="MissingPermission"
+        message="Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with `checkPermission`) or explicitly handle a potential `SecurityException`"
+        errorLine1="            gatt.requestMtu(339);"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/ble/PebbleGATTClient.java"
+            line="143"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="MissingPermission"
+        message="Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with `checkPermission`) or explicitly handle a potential `SecurityException`"
+        errorLine1="                    gatt.writeCharacteristic(characteristic);"
+        errorLine2="                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/ble/PebbleGATTClient.java"
+            line="200"
+            column="21"/>
+    </issue>
+
+    <issue
+        id="MissingPermission"
+        message="Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with `checkPermission`) or explicitly handle a potential `SecurityException`"
+        errorLine1="                    gatt.readCharacteristic(characteristic);"
+        errorLine2="                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/ble/PebbleGATTClient.java"
+            line="203"
+            column="21"/>
+    </issue>
+
+    <issue
+        id="MissingPermission"
+        message="Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with `checkPermission`) or explicitly handle a potential `SecurityException`"
+        errorLine1="        mBluetoothGatt = btDevice.connectGatt(mContext, false, this);"
+        errorLine2="                         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/ble/PebbleGATTClient.java"
+            line="232"
+            column="26"/>
+    </issue>
+
+    <issue
+        id="MissingPermission"
+        message="Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with `checkPermission`) or explicitly handle a potential `SecurityException`"
+        errorLine1="        gatt.writeDescriptor(descriptor);"
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/ble/PebbleGATTClient.java"
+            line="239"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="MissingPermission"
+        message="Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with `checkPermission`) or explicitly handle a potential `SecurityException`"
+        errorLine1="        gatt.setCharacteristicNotification(gatt.getService(SERVICE_UUID).getCharacteristic(CONNECTIVITY_CHARACTERISTIC), true);"
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/ble/PebbleGATTClient.java"
+            line="240"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="MissingPermission"
+        message="Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with `checkPermission`) or explicitly handle a potential `SecurityException`"
+        errorLine1="        gatt.writeDescriptor(descriptor);"
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/ble/PebbleGATTClient.java"
+            line="247"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="MissingPermission"
+        message="Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with `checkPermission`) or explicitly handle a potential `SecurityException`"
+        errorLine1="        gatt.setCharacteristicNotification(gatt.getService(SERVICE_UUID).getCharacteristic(MTU_CHARACTERISTIC), true);"
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/ble/PebbleGATTClient.java"
+            line="248"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="MissingPermission"
+        message="Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with `checkPermission`) or explicitly handle a potential `SecurityException`"
+        errorLine1="        gatt.writeDescriptor(descriptor);"
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/ble/PebbleGATTClient.java"
+            line="255"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="MissingPermission"
+        message="Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with `checkPermission`) or explicitly handle a potential `SecurityException`"
+        errorLine1="        gatt.setCharacteristicNotification(gatt.getService(SERVICE_UUID).getCharacteristic(CONNECTION_PARAMETERS_CHARACTERISTIC), true);"
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/ble/PebbleGATTClient.java"
+            line="256"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="MissingPermission"
+        message="Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with `checkPermission`) or explicitly handle a potential `SecurityException`"
+        errorLine1="        gatt.writeCharacteristic(characteristic);"
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/ble/PebbleGATTClient.java"
+            line="264"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="MissingPermission"
+        message="Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with `checkPermission`) or explicitly handle a potential `SecurityException`"
+        errorLine1="        gatt.writeDescriptor(descriptor);"
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/ble/PebbleGATTClient.java"
+            line="271"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="MissingPermission"
+        message="Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with `checkPermission`) or explicitly handle a potential `SecurityException`"
+        errorLine1="        gatt.setCharacteristicNotification(gatt.getService(PPOGATT_SERVICE_UUID).getCharacteristic(PPOGATT_CHARACTERISTIC_READ), true);"
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/ble/PebbleGATTClient.java"
+            line="272"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="MissingPermission"
+        message="Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with `checkPermission`) or explicitly handle a potential `SecurityException`"
+        errorLine1="        boolean success = mBluetoothGatt.writeCharacteristic(writeCharacteristics);"
+        errorLine2="                          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/ble/PebbleGATTClient.java"
+            line="280"
+            column="27"/>
+    </issue>
+
+    <issue
+        id="MissingPermission"
+        message="Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with `checkPermission`) or explicitly handle a potential `SecurityException`"
+        errorLine1="            mBluetoothGatt.disconnect();"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/ble/PebbleGATTClient.java"
+            line="295"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="MissingPermission"
+        message="Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with `checkPermission`) or explicitly handle a potential `SecurityException`"
+        errorLine1="            mBluetoothGatt.close();"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/ble/PebbleGATTClient.java"
+            line="296"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="MissingPermission"
+        message="Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with `checkPermission`) or explicitly handle a potential `SecurityException`"
+        errorLine1="        mBluetoothGattServer = bluetoothManager.openGattServer(mContext, this);"
+        errorLine2="                               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/ble/PebbleGATTServer.java"
+            line="60"
+            column="32"/>
+    </issue>
+
+    <issue
+        id="MissingPermission"
+        message="Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with `checkPermission`) or explicitly handle a potential `SecurityException`"
+        errorLine1="        mBluetoothGattServer.addService(pebbleGATTService);"
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/ble/PebbleGATTServer.java"
+            line="72"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="MissingPermission"
+        message="Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with `checkPermission`) or explicitly handle a potential `SecurityException`"
+        errorLine1="        boolean success = mBluetoothGattServer.notifyCharacteristicChanged(mBtDevice, writeCharacteristics, false);"
+        errorLine2="                          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/ble/PebbleGATTServer.java"
+            line="81"
+            column="27"/>
+    </issue>
+
+    <issue
+        id="MissingPermission"
+        message="Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with `checkPermission`) or explicitly handle a potential `SecurityException`"
+        errorLine1="        if (!this.mBluetoothGattServer.sendResponse(device, requestId, 0, offset, new byte[]{0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1})) {"
+        errorLine2="             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/ble/PebbleGATTServer.java"
+            line="105"
+            column="14"/>
+    </issue>
+
+    <issue
+        id="MissingPermission"
+        message="Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with `checkPermission`) or explicitly handle a potential `SecurityException`"
+        errorLine1="        if (!this.mBluetoothGattServer.sendResponse(device, requestId, 0, offset, value)) {"
+        errorLine2="             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/ble/PebbleGATTServer.java"
+            line="148"
+            column="14"/>
+    </issue>
+
+    <issue
+        id="MissingPermission"
+        message="Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with `checkPermission`) or explicitly handle a potential `SecurityException`"
+        errorLine1="            mBluetoothGattServer.addService(badbadService);"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/ble/PebbleGATTServer.java"
+            line="158"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="MissingPermission"
+        message="Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with `checkPermission`) or explicitly handle a potential `SecurityException`"
+        errorLine1="            mBluetoothGattServer.cancelConnection(mBtDevice);"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/ble/PebbleGATTServer.java"
+            line="190"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="MissingPermission"
+        message="Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with `checkPermission`) or explicitly handle a potential `SecurityException`"
+        errorLine1="            mBluetoothGattServer.clearServices();"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/ble/PebbleGATTServer.java"
+            line="191"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="MissingPermission"
+        message="Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with `checkPermission`) or explicitly handle a potential `SecurityException`"
+        errorLine1="            mBluetoothGattServer.close();"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/ble/PebbleGATTServer.java"
+            line="192"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="MissingPermission"
+        message="Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with `checkPermission`) or explicitly handle a potential `SecurityException`"
+        errorLine1="                if (btDevice.getType() == BluetoothDevice.DEVICE_TYPE_LE) {"
+        errorLine2="                    ~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleIoThread.java"
+            line="188"
+            column="21"/>
+    </issue>
+
+    <issue
+        id="MissingPermission"
+        message="Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with `checkPermission`) or explicitly handle a potential `SecurityException`"
+        errorLine1="                    ParcelUuid[] uuids = btDevice.getUuids();"
+        errorLine2="                                         ~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleIoThread.java"
+            line="194"
+            column="42"/>
+    </issue>
+
+    <issue
+        id="MissingPermission"
+        message="Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with `checkPermission`) or explicitly handle a potential `SecurityException`"
+        errorLine1="                    mBtSocket = btDevice.createRfcommSocketToServiceRecord(UuidSDP);"
+        errorLine2="                                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleIoThread.java"
+            line="203"
+            column="33"/>
+    </issue>
+
+    <issue
+        id="MissingPermission"
+        message="Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with `checkPermission`) or explicitly handle a potential `SecurityException`"
+        errorLine1="                    mBtSocket.connect();"
+        errorLine2="                    ~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleIoThread.java"
+            line="207"
+            column="21"/>
+    </issue>
+
+    <issue
+        id="MissingPermission"
+        message="Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with `checkPermission`) or explicitly handle a potential `SecurityException`"
+        errorLine1="        if (btDevice.getBondState() == BluetoothDevice.BOND_BONDED ||"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebblePairingActivity.java"
+            line="124"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="MissingPermission"
+        message="Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with `checkPermission`) or explicitly handle a potential `SecurityException`"
+        errorLine1="                btDevice.getBondState() == BluetoothDevice.BOND_BONDING) {"
+        errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebblePairingActivity.java"
+            line="125"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="MissingPermission"
+        message="Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with `checkPermission`) or explicitly handle a potential `SecurityException`"
+        errorLine1="        String expectedSuffix = btDevice.getName();"
+        errorLine2="                                ~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebblePairingActivity.java"
+            line="138"
+            column="33"/>
+    </issue>
+
+    <issue
+        id="MissingPermission"
+        message="Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with `checkPermission`) or explicitly handle a potential `SecurityException`"
+        errorLine1="            return gatt.readCharacteristic(getCharacteristic());"
+        errorLine2="                   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/ReadAction.java"
+            line="40"
+            column="20"/>
+    </issue>
+
+    <issue
+        id="MissingPermission"
+        message="Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with `checkPermission`) or explicitly handle a potential `SecurityException`"
+        errorLine1="        return gatt.requestMtu(this.mtu);"
+        errorLine2="               ~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/RequestMtuAction.java"
+            line="43"
+            column="16"/>
+    </issue>
+
+    <issue
+        id="MissingPermission"
+        message="Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with `checkPermission`) or explicitly handle a potential `SecurityException`"
+        errorLine1="        return gattServer.sendResponse(device, requestId, 0, offset, value);"
+        errorLine2="               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/ServerResponseAction.java"
+            line="60"
+            column="16"/>
+    </issue>
+
+    <issue
+        id="MissingPermission"
+        message="Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with `checkPermission`) or explicitly handle a potential `SecurityException`"
+        errorLine1="            gatt.writeCharacteristic(alertLevelCharacteristic);"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/actions/StopNotificationAction.java"
+            line="38"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="MissingPermission"
+        message="Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with `checkPermission`) or explicitly handle a potential `SecurityException`"
+        errorLine1="        final DeviceInformationResponseMessage deviceInformationResponseMessage = new DeviceInformationResponseMessage(VivomoveConstants.STATUS_ACK, 112, -1, VivomoveConstants.GADGETBRIDGE_UNIT_NUMBER, BuildConfig.VERSION_CODE, 16384, getBluetoothAdapter().getName(), Build.MANUFACTURER, Build.DEVICE, protocolFlags);"
+        errorLine2="                                                                                                                                                                                                                                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/vivomovehr/VivomoveHrSupport.java"
+            line="585"
+            column="236"/>
+    </issue>
+
+    <issue
+        id="MissingPermission"
+        message="Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with `checkPermission`) or explicitly handle a potential `SecurityException`"
+        errorLine1="        return server.notifyCharacteristicChanged(getDevice(), characteristic, false);"
+        errorLine2="               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/WithingsServerAction.java"
+            line="41"
+            column="16"/>
+    </issue>
+
+    <issue
+        id="MissingPermission"
+        message="Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with `checkPermission`) or explicitly handle a potential `SecurityException`"
+        errorLine1="            return gatt.writeCharacteristic(characteristic);"
+        errorLine2="                   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/WriteAction.java"
+            line="61"
+            column="20"/>
+    </issue>
+
+    <issue
+        id="MissingPermission"
+        message="Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with `checkPermission`) or explicitly handle a potential `SecurityException`"
+        errorLine1="        return bluetoothAdapter.getName();"
+        errorLine2="               ~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsPhoneService.java"
+            line="205"
+            column="16"/>
+    </issue>
+
+    <issue
+        id="NestedScrolling"
+        message="The vertically scrolling `ScrollView` should not contain another vertically scrolling widget (`ListView`)"
+        errorLine1="        &lt;ListView"
+        errorLine2="         ~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_appinstaller.xml"
+            line="26"
+            column="10"/>
+    </issue>
+
+    <issue
+        id="NestedScrolling"
+        message="The vertically scrolling `ScrollView` should not contain another vertically scrolling widget (`ListView`)"
+        errorLine1="        &lt;ListView"
+        errorLine2="         ~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_appinstaller.xml"
+            line="87"
+            column="10"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `String.format(Locale, ...)` instead"
+        errorLine1="        pieChart.setCenterText(String.format(&quot;%d%%\n%s&quot;, (int) (value * 100 / target), label));"
+        errorLine2="                               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ActivityListingAdapter.java"
+            line="173"
+            column="32"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `toUpperCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
+        errorLine1="        snackbar.setAction(getString(R.string.dialog_hide).toUpperCase(), new View.OnClickListener() {"
+        errorLine2="                                                           ~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ActivityListingChartFragment.java"
+            line="201"
+            column="60"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `toLowerCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
+        errorLine1="                return file.getPath().toLowerCase().endsWith(&quot;.gpx&quot;);"
+        errorLine2="                                      ~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ActivitySummaryDetail.java"
+            line="395"
+            column="39"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `toLowerCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
+        errorLine1="        String fileName = FileUtils.makeValidFileName(&quot;Screenshot-&quot; + ActivityKind.asString(currentItem.getActivityKind(), context).toLowerCase() + &quot;-&quot; + DateTimeUtils.formatIso8601(currentItem.getStartTime()) + &quot;.png&quot;);"
+        errorLine2="                                                                                                                                    ~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ActivitySummaryDetail.java"
+            line="588"
+            column="133"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `String.format(Locale, ...)` instead"
+        errorLine1="        return String.format(&quot;&lt;ASDP type %d, Seq.No: %d, CRC: %x, Valid: %s, Data: %s>&quot;, type.ordinal(), sequenceNo, crc, isCrcValid ? &quot;yes&quot; : String.format(&quot;NO [expected %x]&quot;, expectedCrc), toHexString(data));"
+        errorLine2="               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/activity/ActivitySyncDataPacket.java"
+            line="76"
+            column="16"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `toUpperCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
+        errorLine1="        return sb.toString().toUpperCase();"
+        errorLine2="                             ~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/activity/ActivitySyncDataPacket.java"
+            line="88"
+            column="30"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `toUpperCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
+        errorLine1="        return new AmbientSoundControl(AmbientSoundControl.Mode.valueOf(soundControl.toUpperCase()), focusVoice, ambientSound);"
+        errorLine2="                                                                                     ~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/prefs/AmbientSoundControl.java"
+            line="85"
+            column="86"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `toUpperCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
+        errorLine1="        return AmbientSoundControlButtonMode.valueOf(prefs.getString(DeviceSettingsPreferenceConst.PREF_SONY_AMBIENT_SOUND_CONTROL_BUTTON_MODE, &quot;nc_as_off&quot;).toUpperCase());"
+        errorLine2="                                                                                                                                                             ~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/prefs/AmbientSoundControlButtonMode.java"
+            line="61"
+            column="158"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `toLowerCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
+        errorLine1="                final String filterPattern = filter.toString().toLowerCase().trim();"
+        errorLine2="                                                               ~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/AppBlacklistAdapter.java"
+            line="283"
+            column="64"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `toLowerCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
+        errorLine1="                    if (name.toString().toLowerCase().contains(filterPattern) ||"
+        errorLine2="                                        ~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/AppBlacklistAdapter.java"
+            line="287"
+            column="41"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `toUpperCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
+        errorLine1="        return (!isNight) ? iconToLoad : iconToLoad.toUpperCase();"
+        errorLine2="                                                    ~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerObsidian.java"
+            line="142"
+            column="53"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `toLowerCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
+        errorLine1="                final String filterPattern = filter.toString().toLowerCase().trim();"
+        errorLine2="                                                               ~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/AppSpecificNotificationSettingsAppListAdapter.java"
+            line="228"
+            column="64"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `toLowerCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
+        errorLine1="                    if (name.toString().toLowerCase().contains(filterPattern) ||"
+        errorLine2="                                        ~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/AppSpecificNotificationSettingsAppListAdapter.java"
+            line="232"
+            column="41"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `toLowerCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
+        errorLine1="        return this.appName.toLowerCase().compareTo(o.getAppName().toLowerCase());"
+        errorLine2="                            ~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/application/ApplicationInformation.java"
+            line="45"
+            column="29"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `toLowerCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
+        errorLine1="        return this.appName.toLowerCase().compareTo(o.getAppName().toLowerCase());"
+        errorLine2="                                                                   ~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/application/ApplicationInformation.java"
+            line="45"
+            column="68"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `String.format(Locale, ...)` instead"
+        errorLine1="            String version = String.format(&quot;%d.%d&quot;, buffer.get(), buffer.get());"
+        errorLine2="                             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/application/ApplicationsListRequest.java"
+            line="57"
+            column="30"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `toUpperCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
+        errorLine1="        return AutomaticPowerOff.valueOf(prefs.getString(DeviceSettingsPreferenceConst.PREF_SONY_AUTOMATIC_POWER_OFF, &quot;off&quot;).toUpperCase());"
+        errorLine2="                                                                                                                             ~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/prefs/AutomaticPowerOff.java"
+            line="62"
+            column="126"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `toUpperCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
+        errorLine1="                deviceEventMusicControl.event = GBDeviceEventMusicControl.Event.valueOf(json.getString(&quot;n&quot;).toUpperCase());"
+        errorLine2="                                                                                                            ~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/banglejs/BangleJSDeviceSupport.java"
+            line="540"
+            column="109"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `toUpperCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
+        errorLine1="                deviceEventCallControl.event = GBDeviceEventCallControl.Event.valueOf(json.getString(&quot;n&quot;).toUpperCase());"
+        errorLine2="                                                                                                          ~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/banglejs/BangleJSDeviceSupport.java"
+            line="545"
+            column="107"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `toUpperCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
+        errorLine1="        deviceEvtNotificationControl.event = GBDeviceEventNotificationControl.Event.valueOf(json.getString(&quot;n&quot;).toUpperCase());"
+        errorLine2="                                                                                                                ~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/banglejs/BangleJSDeviceSupport.java"
+            line="619"
+            column="113"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `toLowerCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
+        errorLine1="            String m = json.getString(&quot;method&quot;).toLowerCase();"
+        errorLine2="                                                ~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/banglejs/BangleJSDeviceSupport.java"
+            line="732"
+            column="49"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `toLowerCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
+        errorLine1="                        cmdName = field.getName().substring(5).toLowerCase();"
+        errorLine2="                                                               ~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/banglejs/BangleJSDeviceSupport.java"
+            line="1313"
+            column="64"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `toUpperCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
+        errorLine1="                Mode.valueOf(prefs.getString(DeviceSettingsPreferenceConst.PREF_SONY_BUTTON_MODE_LEFT, &quot;off&quot;).toUpperCase()),"
+        errorLine2="                                                                                                              ~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/prefs/ButtonModes.java"
+            line="61"
+            column="111"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `toUpperCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
+        errorLine1="                Mode.valueOf(prefs.getString(DeviceSettingsPreferenceConst.PREF_SONY_BUTTON_MODE_RIGHT, &quot;off&quot;).toUpperCase())"
+        errorLine2="                                                                                                               ~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/prefs/ButtonModes.java"
+            line="62"
+            column="112"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `toLowerCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
+        errorLine1="            return name().substring(0, 1).toUpperCase() + name().substring(1).toLowerCase();"
+        errorLine2="                                                                              ~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/CalibrationActivity.java"
+            line="48"
+            column="79"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `toUpperCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
+        errorLine1="            return name().substring(0, 1).toUpperCase() + name().substring(1).toLowerCase();"
+        errorLine2="                                          ~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/CalibrationActivity.java"
+            line="48"
+            column="43"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `String.format(Locale, ...)` instead"
+        errorLine1="        String timeString = String.format(&quot;%02d:%02d:%02d&quot;, hours, minutes, seconds);"
+        errorLine2="                            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/um25/Activity/DataActivity.java"
+            line="135"
+            column="29"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `toLowerCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
+        errorLine1="                                        (!FileUtils.getExtension(file.toString()).toLowerCase().equals(&quot;gpx&quot;)) &amp;&amp; //keep GPX files"
+        errorLine2="                                                                                  ~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DataManagementActivity.java"
+            line="452"
+            column="83"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `toLowerCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
+        errorLine1="                        deviceTypeCache.get(deviceCandidate.getMacAddress().toLowerCase());"
+        errorLine2="                                                                            ~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DeviceHelper.java"
+            line="148"
+            column="77"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `toLowerCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
+        errorLine1="                    deviceTypeCache.put(deviceCandidate.getMacAddress().toLowerCase(), type);"
+        errorLine2="                                                                        ~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DeviceHelper.java"
+            line="156"
+            column="73"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `toLowerCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
+        errorLine1="            deviceTypeCache.put(deviceCandidate.getMacAddress().toLowerCase(), DeviceType.UNKNOWN);"
+        errorLine2="                                                                ~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DeviceHelper.java"
+            line="160"
+            column="65"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `toUpperCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
+        errorLine1="        return EqualizerPreset.valueOf(prefs.getString(DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_MODE, &quot;off&quot;).toUpperCase());"
+        errorLine2="                                                                                                                      ~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/prefs/EqualizerPreset.java"
+            line="68"
+            column="119"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `toLowerCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
+        errorLine1="                String fileName = FileUtils.makeValidFileName(&quot;gadgetbridge-&quot; + trackType.toLowerCase() + &quot;-&quot; + DateTimeUtils.formatIso8601(summary.getStartTime()) + &quot;.gpx&quot;);"
+        errorLine2="                                                                                          ~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/FetchSportsDetailsOperation.java"
+            line="128"
+            column="91"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `toUpperCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
+        errorLine1="            localeString = language + &quot;_&quot; + country.toUpperCase();"
+        errorLine2="                                                    ~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/fitpro/FitProDeviceSupport.java"
+            line="662"
+            column="53"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `String.format(Locale, ...)` instead"
+        errorLine1="        return String.format(&quot;%s (at %s: %d)&quot;, message.message(), sourceId, message.lineNumber());"
+        errorLine2="               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/webview/GBChromeClient.java"
+            line="41"
+            column="16"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `toLowerCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
+        errorLine1="            return fieldName.toLowerCase();"
+        errorLine2="                             ~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/GattCharacteristic.java"
+            line="396"
+            column="30"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `toLowerCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
+        errorLine1="            builder.append(word.toLowerCase());"
+        errorLine2="                                ~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/GattCharacteristic.java"
+            line="406"
+            column="33"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `String.format(Locale, ...)` instead"
+        errorLine1="        versionInfo.fwVersion = String.format(&quot;%d.%d&quot;, softwareVersion >> 8, softwareVersion &amp; 0xff);"
+        errorLine2="                                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/GetFirmwareInfoRequest.java"
+            line="47"
+            column="33"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `String.format(Locale, ...)` instead"
+        errorLine1="        device.setFirmwareVersion2(String.format(&quot;%d.%d&quot;, hardwareVersion >> 8, hardwareVersion &amp; 0xff));"
+        errorLine2="                                   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/GetFirmwareInfoRequest.java"
+            line="53"
+            column="36"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `toLowerCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
+        errorLine1="                if (uri!=null &amp;&amp; uri.getPath().toLowerCase().endsWith(&quot;.gpx&quot;)) {"
+        errorLine2="                                               ~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/GpxReceiverActivity.java"
+            line="88"
+            column="48"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `toUpperCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
+        errorLine1="                baos.write(StringUtils.truncate(clock.getCode(), 3).toUpperCase().getBytes(StandardCharsets.UTF_8));"
+        errorLine2="                                                                    ~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiSupport.java"
+            line="1195"
+            column="69"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `toUpperCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
+        errorLine1="                baos.write(StringUtils.truncate(clock.getLabel(), 3).toUpperCase().getBytes(StandardCharsets.UTF_8));"
+        errorLine2="                                                                     ~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiSupport.java"
+            line="1197"
+            column="70"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `toUpperCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
+        errorLine1="            localeString = language + &quot;_&quot; + country.toUpperCase();"
+        errorLine2="                                                    ~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiSupport.java"
+            line="3932"
+            column="53"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `toUpperCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
+        errorLine1="            localeString = language + &quot;_&quot; + country.toUpperCase();"
+        errorLine2="                                                    ~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiSupport.java"
+            line="3991"
+            column="53"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `toUpperCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
+        errorLine1="            macAddress = mac.toString().toUpperCase();"
+        errorLine2="                                        ~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiSupportProvider.java"
+            line="534"
+            column="41"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `toLowerCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
+        errorLine1="            return sb.toString().toLowerCase();"
+        errorLine2="                                 ~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/webview/JSInterface.java"
+            line="207"
+            column="34"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `toUpperCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
+        errorLine1="        String macAddress = candidate.getMacAddress().toUpperCase();"
+        errorLine2="                                                      ~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandCoordinator.java"
+            line="79"
+            column="55"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `toUpperCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
+        errorLine1="                if (name != null &amp;&amp; name.toUpperCase().startsWith(MiBandConst.MI_GENERAL_NAME_PREFIX.toUpperCase())) {"
+        errorLine2="                                         ~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandCoordinator.java"
+            line="93"
+            column="42"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `toUpperCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
+        errorLine1="                if (name != null &amp;&amp; name.toUpperCase().startsWith(MiBandConst.MI_GENERAL_NAME_PREFIX.toUpperCase())) {"
+        errorLine2="                                                                                                     ~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandCoordinator.java"
+            line="93"
+            column="102"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `String.format(Locale, ...)` instead"
+        errorLine1="                LOG.info(&quot;READ SENSOR DATA VALUES: counter:&quot;+counter+&quot; step:&quot;+step+&quot; x-axis:&quot;+ String.format(&quot;%.03f&quot;,xAxis)+&quot; y-axis:&quot;+String.format(&quot;%.03f&quot;,yAxis)+&quot; z-axis:&quot;+String.format(&quot;%.03f&quot;,zAxis)+&quot;;&quot;);"
+        errorLine2="                                                                                               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/MiBandSupport.java"
+            line="1283"
+            column="96"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `String.format(Locale, ...)` instead"
+        errorLine1="                LOG.info(&quot;READ SENSOR DATA VALUES: counter:&quot;+counter+&quot; step:&quot;+step+&quot; x-axis:&quot;+ String.format(&quot;%.03f&quot;,xAxis)+&quot; y-axis:&quot;+String.format(&quot;%.03f&quot;,yAxis)+&quot; z-axis:&quot;+String.format(&quot;%.03f&quot;,zAxis)+&quot;;&quot;);"
+        errorLine2="                                                                                                                                       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/MiBandSupport.java"
+            line="1283"
+            column="136"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `String.format(Locale, ...)` instead"
+        errorLine1="                LOG.info(&quot;READ SENSOR DATA VALUES: counter:&quot;+counter+&quot; step:&quot;+step+&quot; x-axis:&quot;+ String.format(&quot;%.03f&quot;,xAxis)+&quot; y-axis:&quot;+String.format(&quot;%.03f&quot;,yAxis)+&quot; z-axis:&quot;+String.format(&quot;%.03f&quot;,zAxis)+&quot;;&quot;);"
+        errorLine2="                                                                                                                                                                               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/MiBandSupport.java"
+            line="1283"
+            column="176"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `toLowerCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
+        errorLine1="        packageName = packageName.toLowerCase();"
+        errorLine2="                                  ~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/NotificationFilterActivity.java"
+            line="77"
+            column="35"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `toLowerCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
+        errorLine1="            if (!checkNotificationContentForWhiteAndBlackList(sbn.getPackageName().toLowerCase(), textToCheck)) {"
+        errorLine2="                                                                                   ~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/NotificationListener.java"
+            line="395"
+            column="84"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `toLowerCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
+        errorLine1="            Query&lt;NotificationFilter> query = notificationFilterDao.queryBuilder().where(NotificationFilterDao.Properties.AppIdentifier.eq(packageName.toLowerCase())).build();"
+        errorLine2="                                                                                                                                                       ~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/NotificationListener.java"
+            line="481"
+            column="152"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `toLowerCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
+        errorLine1="        return name().toLowerCase();"
+        errorLine2="                      ~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/model/NotificationType.java"
+            line="94"
+            column="23"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `toLowerCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
+        errorLine1="        return new String(hexChars).toLowerCase();"
+        errorLine2="                                    ~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/nut/NutKey.java"
+            line="71"
+            column="37"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `String.format(Locale, ...)` instead"
+        errorLine1="        logDebug(String.format(&quot;onReceiveStepsSample: \ndayStepCount=%d, \nsteps=%d, \ndiff=%d, \nprevoiusDayStepCount=%d, &quot;, dayStepCount, steps, diff, prevoiusDayStepCount));"
+        errorLine2="                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pinetime/PineTimeJFSupport.java"
+            line="1114"
+            column="18"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `String.format(Locale, ...)` instead"
+        errorLine1="        logDebug(String.format(&quot;onReceiveHeartRateMeasurement: \nheartrate=%d&quot;, heartrate));"
+        errorLine2="                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pinetime/PineTimeJFSupport.java"
+            line="1157"
+            column="18"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `String.format(Locale, ...)` instead"
+        errorLine1="        logDebug(String.format(&quot;getDayStartEnd: \ntimeStamp=%d (%s), \ntimeStampStartUtc=%d (-offset=%s), \ntimeStampEndUtc=%d (-offset=%s)&quot;, timeStampUtc, calTimeStamp.getTime(), timeStampStartUtc, start.getTime(), timeStampEndUtc, end.getTime()));"
+        errorLine2="                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pinetime/PineTimeJFSupport.java"
+            line="1223"
+            column="18"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `toUpperCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
+        errorLine1="                Mode.valueOf(prefs.getString(DeviceSettingsPreferenceConst.PREF_SONY_QUICK_ACCESS_DOUBLE_TAP, &quot;off&quot;).toUpperCase()),"
+        errorLine2="                                                                                                                     ~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/prefs/QuickAccess.java"
+            line="79"
+            column="118"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `toUpperCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
+        errorLine1="                Mode.valueOf(prefs.getString(DeviceSettingsPreferenceConst.PREF_SONY_QUICK_ACCESS_TRIPLE_TAP, &quot;off&quot;).toUpperCase())"
+        errorLine2="                                                                                                                     ~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/prefs/QuickAccess.java"
+            line="80"
+            column="118"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `toUpperCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
+        errorLine1="            localeString = language + &quot;-&quot; + country.toUpperCase();"
+        errorLine2="                                                    ~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SetLanguageSettingRequest.java"
+            line="55"
+            column="53"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `toUpperCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
+        errorLine1="                LedColor led = LedColor.valueOf(prefs.getString(SonyWena3SettingKeys.DEFAULT_CALL_LED_COLOR, LedColor.WHITE.name()).toUpperCase());"
+        errorLine2="                                                                                                                                    ~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/SonyWena3DeviceSupport.java"
+            line="452"
+            column="133"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `toUpperCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
+        errorLine1="                VibrationKind vibra = VibrationKind.valueOf(prefs.getString(SonyWena3SettingKeys.DEFAULT_CALL_VIBRATION_PATTERN, VibrationKind.CONTINUOUS.name()).toUpperCase());"
+        errorLine2="                                                                                                                                                                  ~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/SonyWena3DeviceSupport.java"
+            line="453"
+            column="163"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `toUpperCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
+        errorLine1="            LedColor led = LedColor.valueOf(prefs.getString(SonyWena3SettingKeys.DEFAULT_LED_COLOR, LedColor.BLUE.name()).toUpperCase());"
+        errorLine2="                                                                                                                          ~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/SonyWena3DeviceSupport.java"
+            line="534"
+            column="123"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `toUpperCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
+        errorLine1="            VibrationKind vibra = VibrationKind.valueOf(prefs.getString(SonyWena3SettingKeys.DEFAULT_VIBRATION_PATTERN, VibrationKind.BASIC.name()).toUpperCase());"
+        errorLine2="                                                                                                                                                    ~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/SonyWena3DeviceSupport.java"
+            line="535"
+            column="149"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `toUpperCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
+        errorLine1="                        led = LedColor.valueOf(appSpecificSetting.getLedPattern().toUpperCase());"
+        errorLine2="                                                                                  ~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/SonyWena3DeviceSupport.java"
+            line="543"
+            column="83"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `toUpperCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
+        errorLine1="                        vibra = VibrationKind.valueOf(appSpecificSetting.getVibrationPattern().toUpperCase());"
+        errorLine2="                                                                                               ~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/SonyWena3DeviceSupport.java"
+            line="547"
+            column="96"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `toUpperCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
+        errorLine1="            localeString = language + &quot;_&quot; + country.toUpperCase();"
+        errorLine2="                                                    ~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/SonyWena3DeviceSupport.java"
+            line="681"
+            column="53"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `toUpperCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
+        errorLine1="        VibrationStrength strength = VibrationStrength.valueOf(prefs.getString(SonyWena3SettingKeys.VIBRATION_STRENGTH, VibrationStrength.NORMAL.name()).toUpperCase());"
+        errorLine2="                                                                                                                                                         ~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/SonyWena3DeviceSupport.java"
+            line="777"
+            column="154"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `toUpperCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
+        errorLine1="        String leftIdName = prefs.getString(SonyWena3SettingKeys.LEFT_HOME_ICON, HomeIconId.MUSIC.name()).toUpperCase();"
+        errorLine2="                                                                                                          ~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/SonyWena3DeviceSupport.java"
+            line="788"
+            column="107"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `toUpperCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
+        errorLine1="        String centerIdName = prefs.getString(SonyWena3SettingKeys.CENTER_HOME_ICON, HomeIconId.PEDOMETER.name()).toUpperCase();"
+        errorLine2="                                                                                                                  ~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/SonyWena3DeviceSupport.java"
+            line="789"
+            column="115"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `toUpperCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
+        errorLine1="        String rightIdName = prefs.getString(SonyWena3SettingKeys.RIGHT_HOME_ICON, HomeIconId.CALORIES.name()).toUpperCase();"
+        errorLine2="                                                                                                               ~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/SonyWena3DeviceSupport.java"
+            line="790"
+            column="112"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `toUpperCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
+        errorLine1="                .toUpperCase()"
+        errorLine2="                 ~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/SonyWena3DeviceSupport.java"
+            line="806"
+            column="18"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `toUpperCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
+        errorLine1="                .toUpperCase()"
+        errorLine2="                 ~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/SonyWena3DeviceSupport.java"
+            line="828"
+            column="18"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `toUpperCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
+        errorLine1="        String doubleIdName = prefs.getString(SonyWena3SettingKeys.BUTTON_DOUBLE_PRESS_ACTION, DeviceButtonActionId.NONE.name()).toUpperCase();"
+        errorLine2="                                                                                                                                 ~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/SonyWena3DeviceSupport.java"
+            line="880"
+            column="130"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `toUpperCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
+        errorLine1="        String longIdName = prefs.getString(SonyWena3SettingKeys.BUTTON_LONG_PRESS_ACTION, DeviceButtonActionId.NONE.name()).toUpperCase();"
+        errorLine2="                                                                                                                             ~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/SonyWena3DeviceSupport.java"
+            line="881"
+            column="126"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `toUpperCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
+        errorLine1="        return SoundPosition.valueOf(prefs.getString(DeviceSettingsPreferenceConst.PREF_SONY_SOUND_POSITION, &quot;off&quot;).toUpperCase());"
+        errorLine2="                                                                                                                    ~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/prefs/SoundPosition.java"
+            line="52"
+            column="117"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `toUpperCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
+        errorLine1="        final Sensitivity sensitivity = Sensitivity.valueOf(prefs.getString(DeviceSettingsPreferenceConst.PREF_SONY_SPEAK_TO_CHAT_SENSITIVITY, &quot;auto&quot;).toUpperCase());"
+        errorLine2="                                                                                                                                                       ~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/prefs/SpeakToChatConfig.java"
+            line="119"
+            column="152"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `toUpperCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
+        errorLine1="        final Timeout timeout = Timeout.valueOf(prefs.getString(DeviceSettingsPreferenceConst.PREF_SONY_SPEAK_TO_CHAT_TIMEOUT, &quot;standard&quot;).toUpperCase());"
+        errorLine2="                                                                                                                                           ~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/prefs/SpeakToChatConfig.java"
+            line="120"
+            column="140"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `String.format(Locale, ...)` instead"
+        errorLine1="            days_total.setText(String.format(&quot;%.1f%%&quot;, 0.0));"
+        errorLine2="                               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/StepStreaksDashboard.java"
+            line="220"
+            column="32"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `String.format(Locale, ...)` instead"
+        errorLine1="                days_total.setText(String.format(&quot;%.1f%%&quot;, (float) stepsStreaks.total.days / stepsStreaks.total.total_days * 100));"
+        errorLine2="                                   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/StepStreaksDashboard.java"
+            line="222"
+            column="36"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `toUpperCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
+        errorLine1="        return SurroundMode.valueOf(prefs.getString(DeviceSettingsPreferenceConst.PREF_SONY_SURROUND_MODE, &quot;off&quot;).toUpperCase());"
+        errorLine2="                                                                                                                  ~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/prefs/SurroundMode.java"
+            line="51"
+            column="115"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `String.format(Locale, ...)` instead"
+        errorLine1="        return String.format(&quot;%02d&quot;, hour) + &quot;:&quot; + String.format(&quot;%02d&quot;, minute);"
+        errorLine2="               ~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/util/TimePreference.java"
+            line="111"
+            column="16"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `String.format(Locale, ...)` instead"
+        errorLine1="        return String.format(&quot;%02d&quot;, hour) + &quot;:&quot; + String.format(&quot;%02d&quot;, minute);"
+        errorLine2="                                                   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/util/TimePreference.java"
+            line="111"
+            column="52"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `String.format(Locale, ...)` instead"
+        errorLine1="        return String.valueOf(h) + &quot;:&quot; + String.format(&quot;%02d&quot;, minute) + suffix;"
+        errorLine2="                                         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/util/TimePreference.java"
+            line="118"
+            column="42"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `toLowerCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
+        errorLine1="        if(!candidate.getName().toLowerCase().contains(&quot;vesc&quot;)){"
+        errorLine2="                                ~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/vesc/VescCoordinator.java"
+            line="74"
+            column="33"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `toUpperCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
+        errorLine1="                deviceEventMusicControl.event = GBDeviceEventMusicControl.Event.valueOf(json.getString(&quot;n&quot;).toUpperCase());"
+        errorLine2="                                                                                                            ~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/waspos/WaspOSDeviceSupport.java"
+            line="177"
+            column="109"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `toUpperCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
+        errorLine1="                deviceEventCallControl.event = GBDeviceEventCallControl.Event.valueOf(json.getString(&quot;n&quot;).toUpperCase());"
+        errorLine2="                                                                                                          ~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/waspos/WaspOSDeviceSupport.java"
+            line="182"
+            column="107"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `toUpperCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
+        errorLine1="                deviceEvtNotificationControl.event = GBDeviceEventNotificationControl.Event.valueOf(json.getString(&quot;n&quot;).toUpperCase());"
+        errorLine2="                                                                                                                        ~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/waspos/WaspOSDeviceSupport.java"
+            line="188"
+            column="121"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `toUpperCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
+        errorLine1="        String macAddress = candidate.getMacAddress().toUpperCase();"
+        errorLine2="                                                      ~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/watch9/Watch9DeviceCoordinator.java"
+            line="64"
+            column="55"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `toUpperCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
+        errorLine1="        String deviceName = candidate.getName().toUpperCase();"
+        errorLine2="                                                ~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/watch9/Watch9DeviceCoordinator.java"
+            line="65"
+            column="49"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `toUpperCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
+        errorLine1="        String macAddress = candidate.getMacAddress().toUpperCase();"
+        errorLine2="                                                      ~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lenovo/watchxplus/WatchXPlusDeviceCoordinator.java"
+            line="91"
+            column="55"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `toUpperCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
+        errorLine1="        String deviceName = candidate.getName().toUpperCase();"
+        errorLine2="                                                ~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lenovo/watchxplus/WatchXPlusDeviceCoordinator.java"
+            line="92"
+            column="49"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `toLowerCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
+        errorLine1="            if (contentType != null &amp;&amp; contentType.toLowerCase().trim().contains(&quot;charset=&quot;)) {"
+        errorLine2="                                                   ~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/util/WebViewSingleton.java"
+            line="129"
+            column="52"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `toLowerCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
+        errorLine1="                String[] parts = contentType.toLowerCase().trim().split(&quot;=&quot;);"
+        errorLine2="                                             ~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/util/WebViewSingleton.java"
+            line="130"
+            column="46"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `String.format(Locale, ...)` instead"
+        errorLine1="        return String.format(&quot;%.0f&quot;, value);"
+        errorLine2="               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/WeekStepsChartFragment.java"
+            line="123"
+            column="16"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `toUpperCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
+        errorLine1="            final String newCode = StringUtils.truncate(newLabel, 3).toUpperCase();"
+        errorLine2="                                                                     ~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/WorldClockDetails.java"
+            line="206"
+            column="70"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `toUpperCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
+        errorLine1="            final String oldCode = StringUtils.truncate(oldLabel, 3).toUpperCase();"
+        errorLine2="                                                                     ~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/WorldClockDetails.java"
+            line="207"
+            column="70"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `String.format(Locale, ...)` instead"
+        errorLine1="        return String.format(&quot;%02d&quot;, hour) + &quot;:&quot; + String.format(&quot;%02d&quot;, minute);"
+        errorLine2="               ~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/util/XTimePreference.java"
+            line="73"
+            column="16"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `String.format(Locale, ...)` instead"
+        errorLine1="        return String.format(&quot;%02d&quot;, hour) + &quot;:&quot; + String.format(&quot;%02d&quot;, minute);"
+        errorLine2="                                                   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/util/XTimePreference.java"
+            line="73"
+            column="52"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `String.format(Locale, ...)` instead"
+        errorLine1="        return h + &quot;:&quot; + String.format(&quot;%02d&quot;, minute) + suffix;"
+        errorLine2="                         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/util/XTimePreference.java"
+            line="80"
+            column="26"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `toUpperCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
+        errorLine1="            localeString = language + &quot;_&quot; + country.toUpperCase();"
+        errorLine2="                                                    ~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/services/XiaomiSystemService.java"
+            line="267"
+            column="53"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `String.format(Locale, ...)` instead"
+        errorLine1="        String starttime = String.format(&quot;%02d:%02d&quot;, msg[6], msg[7]);"
+        errorLine2="                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/zetime/ZeTimeDeviceSupport.java"
+            line="2007"
+            column="28"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `String.format(Locale, ...)` instead"
+        errorLine1="        String endtime = String.format(&quot;%02d:%02d&quot;, msg[8], msg[9]);"
+        errorLine2="                         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/zetime/ZeTimeDeviceSupport.java"
+            line="2008"
+            column="26"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `String.format(Locale, ...)` instead"
+        errorLine1="        String starttime = String.format(&quot;%02d:%02d&quot;, msg[7], msg[8]);"
+        errorLine2="                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/zetime/ZeTimeDeviceSupport.java"
+            line="2069"
+            column="28"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `String.format(Locale, ...)` instead"
+        errorLine1="        String endtime = String.format(&quot;%02d:%02d&quot;, msg[9], msg[10]);"
+        errorLine2="                         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/zetime/ZeTimeDeviceSupport.java"
+            line="2070"
+            column="26"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `toUpperCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
+        errorLine1="            localeString = language + &quot;_&quot; + country.toUpperCase();"
+        errorLine2="                                                    ~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/zetime/ZeTimeDeviceSupport.java"
+            line="2167"
+            column="53"/>
+    </issue>
+
+    <issue
+        id="DefaultLocale"
+        message="Implicitly using the default locale is a common source of bugs: Use `String.format(Locale, ...)` instead"
+        errorLine1="            final TransactionBuilder builder = performInitialized(String.format(&quot;set heart rate interval to: %d minutes&quot;, minuteInterval));"
+        errorLine2="                                                                  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/ZeppOsSupport.java"
+            line="343"
+            column="67"/>
+    </issue>
+
+    <issue
+        id="InlinedApi"
+        message="Field requires API level 29 (current min is 21): `android.Manifest.permission#ACCESS_BACKGROUND_LOCATION`"
+        errorLine1="            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_BACKGROUND_LOCATION}, 0);"
+        errorLine2="                                                                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ControlCenterv2.java"
+            line="510"
+            column="66"/>
+    </issue>
+
+    <issue
+        id="InlinedApi"
+        message="Field requires API level 31 (current min is 21): `android.app.PendingIntent#FLAG_MUTABLE`"
+        errorLine1="            flags |= PendingIntent.FLAG_MUTABLE;"
+        errorLine2="                     ~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/util/PendingIntentUtils.java"
+            line="33"
+            column="22"/>
+    </issue>
+
+    <issue
+        id="InlinedApi"
+        message="Field requires API level 31 (current min is 21): `android.app.PendingIntent#FLAG_MUTABLE`"
+        errorLine1="            flags |= PendingIntent.FLAG_MUTABLE;"
+        errorLine2="                     ~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/util/PendingIntentUtils.java"
+            line="50"
+            column="22"/>
+    </issue>
+
+    <issue
+        id="InlinedApi"
+        message="Field requires API level 31 (current min is 21): `android.app.PendingIntent#FLAG_MUTABLE`"
+        errorLine1="            flags |= PendingIntent.FLAG_MUTABLE;"
+        errorLine2="                     ~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/util/PendingIntentUtils.java"
+            line="67"
+            column="22"/>
+    </issue>
+
+    <issue
+        id="InlinedApi"
+        message="Field requires API level 23 (current min is 21): `COMPLETED`"
+        errorLine1="                                    LineageWeatherManager.RequestStatus.COMPLETED);"
+        errorLine2="                                                                        ~~~~~~~~~">
+        <location
+            file="src/main/java/lineageos/weatherservice/ServiceRequest.java"
+            line="71"
+            column="73"/>
+    </issue>
+
+    <issue
+        id="InlinedApi"
+        message="Field requires API level 23 (current min is 21): `NO_MATCH_FOUND`"
+        errorLine1="                                        LineageWeatherManager.RequestStatus.NO_MATCH_FOUND);"
+        errorLine2="                                                                            ~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/lineageos/weatherservice/ServiceRequest.java"
+            line="79"
+            column="77"/>
+    </issue>
+
+    <issue
+        id="InlinedApi"
+        message="Field requires API level 23 (current min is 21): `COMPLETED`"
+        errorLine1="                                        LineageWeatherManager.RequestStatus.COMPLETED);"
+        errorLine2="                                                                            ~~~~~~~~~">
+        <location
+            file="src/main/java/lineageos/weatherservice/ServiceRequest.java"
+            line="82"
+            column="77"/>
+    </issue>
+
+    <issue
+        id="InlinedApi"
+        message="Field requires API level 23 (current min is 21): `FAILED`"
+        errorLine1="                                    LineageWeatherManager.RequestStatus.FAILED);"
+        errorLine2="                                                                        ~~~~~~">
+        <location
+            file="src/main/java/lineageos/weatherservice/ServiceRequest.java"
+            line="106"
+            column="73"/>
+    </issue>
+
+    <issue
+        id="InlinedApi"
+        message="Field requires API level 23 (current min is 21): `FAILED`"
+        errorLine1="                                    LineageWeatherManager.RequestStatus.FAILED);"
+        errorLine2="                                                                        ~~~~~~">
+        <location
+            file="src/main/java/lineageos/weatherservice/ServiceRequest.java"
+            line="110"
+            column="73"/>
+    </issue>
+
+    <issue
+        id="LocaleFolder"
+        message="The locale folder &quot;`he`&quot; should be called &quot;`iw`&quot; instead; see the `java.util.Locale` documentation">
+        <location
+            file="src/main/res/values-he"/>
+    </issue>
+
+    <issue
+        id="LocaleFolder"
+        message="The locale folder &quot;`id`&quot; should be called &quot;`in`&quot; instead; see the `java.util.Locale` documentation">
+        <location
+            file="src/main/res/values-id"/>
+    </issue>
+
+    <issue
+        id="Range"
+        message="Value must be 鈮� 0 but `getColumnIndex` can be -1"
+        errorLine1="                    return cursor.getString(cursor.getColumnIndex(DocumentsContract.Document.COLUMN_DISPLAY_NAME));"
+        errorLine2="                                            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DataManagementActivity.java"
+            line="238"
+            column="45"/>
+    </issue>
+
+    <issue
+        id="Range"
+        message="Value must be 鈮� 0 but `getColumnIndex` can be -1"
+        errorLine1="                name = contactLookup.getString(contactLookup.getColumnIndex(ContactsContract.Data.DISPLAY_NAME));"
+        errorLine2="                                               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceService.java"
+            line="521"
+            column="48"/>
+    </issue>
+
+    <issue
+        id="Range"
+        message="Value must be 鈮� 0 but `getColumnIndex` can be -1"
+        errorLine1="        int orientation = c.getInt(c.getColumnIndex(MediaStore.Images.ImageColumns.ORIENTATION));"
+        errorLine2="                                   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/HybridHRWatchfaceDesignerActivity.java"
+            line="588"
+            column="36"/>
+    </issue>
+
+    <issue
+        id="Range"
+        message="Value must be 鈮� 0 but `getColumnIndex` can be -1"
+        errorLine1="        int orientation = c.getInt(c.getColumnIndex(MediaStore.Images.ImageColumns.ORIENTATION));"
+        errorLine2="                                   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/ImageEditActivity.java"
+            line="169"
+            column="36"/>
+    </issue>
+
+    <issue
+        id="Range"
+        message="Value must be 鈮� 0 but `getColumnIndex` can be -1"
+        errorLine1="                        (short) c.getInt(c.getColumnIndex(DB_MINUTE)),"
+        errorLine2="                                         ~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/PackageConfigHelper.java"
+            line="119"
+            column="42"/>
+    </issue>
+
+    <issue
+        id="Range"
+        message="Value must be 鈮� 0 but `getColumnIndex` can be -1"
+        errorLine1="                        (short) c.getInt(c.getColumnIndex(DB_HOUR)),"
+        errorLine2="                                         ~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/PackageConfigHelper.java"
+            line="120"
+            column="42"/>
+    </issue>
+
+    <issue
+        id="Range"
+        message="Value must be 鈮� 0 but `getColumnIndex` can be -1"
+        errorLine1="                        c.getString(c.getColumnIndex(DB_PACKAGE)),"
+        errorLine2="                                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/PackageConfigHelper.java"
+            line="121"
+            column="37"/>
+    </issue>
+
+    <issue
+        id="Range"
+        message="Value must be 鈮� 0 but `getColumnIndex` can be -1"
+        errorLine1="                        c.getString(c.getColumnIndex(DB_APPNAME)),"
+        errorLine2="                                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/PackageConfigHelper.java"
+            line="122"
+            column="37"/>
+    </issue>
+
+    <issue
+        id="Range"
+        message="Value must be 鈮� 0 but `getColumnIndex` can be -1"
+        errorLine1="                        c.getInt(c.getColumnIndex(DB_RESPECT_SILENT)) == 1,"
+        errorLine2="                                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/PackageConfigHelper.java"
+            line="123"
+            column="34"/>
+    </issue>
+
+    <issue
+        id="Range"
+        message="Value must be 鈮� 0 but `getColumnIndex` can be -1"
+        errorLine1="                        PlayNotificationRequest.VibrationType.fromValue((byte) c.getInt(c.getColumnIndex(DB_VIBRATION))),"
+        errorLine2="                                                                                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/PackageConfigHelper.java"
+            line="124"
+            column="89"/>
+    </issue>
+
+    <issue
+        id="Range"
+        message="Value must be 鈮� 0 but `getColumnIndex` can be -1"
+        errorLine1="                        c.getInt(c.getColumnIndex(DB_ID))"
+        errorLine2="                                 ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/PackageConfigHelper.java"
+            line="125"
+            column="34"/>
+    </issue>
+
+    <issue
+        id="Range"
+        message="Value must be 鈮� 0 but `getColumnIndex` can be -1"
+        errorLine1="                        return cursor.getString(cursor.getColumnIndex(DocumentsContract.Document.COLUMN_DISPLAY_NAME));"
+        errorLine2="                                                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/SettingsActivity.java"
+            line="558"
+            column="49"/>
+    </issue>
+
+    <issue
+        id="SimpleDateFormat"
+        message="To get local formatting use `getDateInstance()`, `getDateTimeInstance()`, or `getTimeInstance()`, or use `new SimpleDateFormat(String template, Locale locale)` with for example `Locale.US` for ASCII dates."
+        errorLine1="        final SimpleDateFormat dateFormat = new SimpleDateFormat(&quot;dd.MM.yyyy HH:mm&quot;);"
+        errorLine2="                                            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/AbstractChartsActivity.java"
+            line="194"
+            column="45"/>
+    </issue>
+
+    <issue
+        id="SimpleDateFormat"
+        message="To get local formatting use `getDateInstance()`, `getDateTimeInstance()`, or `getTimeInstance()`, or use `new SimpleDateFormat(String template, Locale locale)` with for example `Locale.US` for ASCII dates."
+        errorLine1="        SimpleDateFormat annotationDateFormat = new SimpleDateFormat(&quot;dd.MM HH:mm&quot;);"
+        errorLine2="                                                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/BatteryInfoChartFragment.java"
+            line="196"
+            column="49"/>
+    </issue>
+
+    <issue
+        id="SimpleDateFormat"
+        message="To get local formatting use `getDateInstance()`, `getDateTimeInstance()`, or `getTimeInstance()`, or use `new SimpleDateFormat(String template, Locale locale)` with for example `Locale.US` for ASCII dates."
+        errorLine1="        SimpleDateFormat dateFormat = new SimpleDateFormat(&quot;yyyyMMdd&apos;T&apos;HHmmss&quot;);"
+        errorLine2="                                      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casio/gbx100/CasioGBX100DeviceSupport.java"
+            line="367"
+            column="39"/>
+    </issue>
+
+    <issue
+        id="SimpleDateFormat"
+        message="To get local formatting use `getDateInstance()`, `getDateTimeInstance()`, or `getTimeInstance()`, or use `new SimpleDateFormat(String template, Locale locale)` with for example `Locale.US` for ASCII dates."
+        errorLine1="        DateFormat df = new SimpleDateFormat(&quot;HH:mm&quot;);"
+        errorLine2="                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/fitpro/FitProDeviceSupport.java"
+            line="1010"
+            column="25"/>
+    </issue>
+
+    <issue
+        id="SimpleDateFormat"
+        message="To get local formatting use `getDateInstance()`, `getDateTimeInstance()`, or `getTimeInstance()`, or use `new SimpleDateFormat(String template, Locale locale)` with for example `Locale.US` for ASCII dates."
+        errorLine1="            DateFormat df = new SimpleDateFormat(&quot;HH:mm&quot;);"
+        errorLine2="                            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/fitpro/FitProDeviceSupport.java"
+            line="1050"
+            column="29"/>
+    </issue>
+
+    <issue
+        id="SimpleDateFormat"
+        message="To get local formatting use `getDateInstance()`, `getDateTimeInstance()`, or `getTimeInstance()`, or use `new SimpleDateFormat(String template, Locale locale)` with for example `Locale.US` for ASCII dates."
+        errorLine1="        DateFormat df = new SimpleDateFormat(&quot;HH:mm&quot;);"
+        errorLine2="                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/fitpro/FitProDeviceSupport.java"
+            line="1092"
+            column="25"/>
+    </issue>
+
+    <issue
+        id="SimpleDateFormat"
+        message="To get local formatting use `getDateInstance()`, `getDateTimeInstance()`, or `getTimeInstance()`, or use `new SimpleDateFormat(String template, Locale locale)` with for example `Locale.US` for ASCII dates."
+        errorLine1="        DateFormat df = new SimpleDateFormat(&quot;HH:mm&quot;);"
+        errorLine2="                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/fitpro/FitProDeviceSupport.java"
+            line="1132"
+            column="25"/>
+    </issue>
+
+    <issue
+        id="SimpleDateFormat"
+        message="To get local formatting use `getDateInstance()`, `getDateTimeInstance()`, or `getTimeInstance()`, or use `new SimpleDateFormat(String template, Locale locale)` with for example `Locale.US` for ASCII dates."
+        errorLine1="        DateFormat df = new SimpleDateFormat(&quot;HH:mm&quot;);"
+        errorLine2="                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/fitpro/FitProDeviceSupport.java"
+            line="1182"
+            column="25"/>
+    </issue>
+
+    <issue
+        id="SimpleDateFormat"
+        message="To get local formatting use `getDateInstance()`, `getDateTimeInstance()`, or `getTimeInstance()`, or use `new SimpleDateFormat(String template, Locale locale)` with for example `Locale.US` for ASCII dates."
+        errorLine1="            DateFormat df = new SimpleDateFormat(&quot;HH:mm&quot;);"
+        errorLine2="                            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/makibeshr3/MakibesHR3Coordinator.java"
+            line="108"
+            column="29"/>
+    </issue>
+
+    <issue
+        id="SimpleDateFormat"
+        message="To get local formatting use `getDateInstance()`, `getDateTimeInstance()`, or `getTimeInstance()`, or use `new SimpleDateFormat(String template, Locale locale)` with for example `Locale.US` for ASCII dates."
+        errorLine1="        SimpleDateFormat format = new SimpleDateFormat(&quot;MM-dd HH:mm:ss&quot;);"
+        errorLine2="                                  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/SonySWR12Util.java"
+            line="35"
+            column="35"/>
+    </issue>
+
+    <issue
+        id="SimpleDateFormat"
+        message="To get local formatting use `getDateInstance()`, `getDateTimeInstance()`, or `getTimeInstance()`, or use `new SimpleDateFormat(String template, Locale locale)` with for example `Locale.US` for ASCII dates."
+        errorLine1="        DateFormat df = new SimpleDateFormat(&quot;HH:mm&quot;);"
+        errorLine2="                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/SonyWena3DeviceSupport.java"
+            line="723"
+            column="25"/>
+    </issue>
+
+    <issue
+        id="SimpleDateFormat"
+        message="To get local formatting use `getDateInstance()`, `getDateTimeInstance()`, or `getTimeInstance()`, or use `new SimpleDateFormat(String template, Locale locale)` with for example `Locale.US` for ASCII dates."
+        errorLine1="        DateFormat df = new SimpleDateFormat(&quot;HH:mm&quot;);"
+        errorLine2="                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/SonyWena3DeviceSupport.java"
+            line="753"
+            column="25"/>
+    </issue>
+
+    <issue
+        id="SimpleDateFormat"
+        message="To get local formatting use `getDateInstance()`, `getDateTimeInstance()`, or `getTimeInstance()`, or use `new SimpleDateFormat(String template, Locale locale)` with for example `Locale.US` for ASCII dates."
+        errorLine1="        this(new SimpleDateFormat(&quot;HH:mm&quot;));"
+        errorLine2="             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/TimestampValueFormatter.java"
+            line="33"
+            column="14"/>
+    </issue>
+
+    <issue
+        id="SimpleDateFormat"
+        message="To get local formatting use `getDateInstance()`, `getDateTimeInstance()`, or `getTimeInstance()`, or use `new SimpleDateFormat(String template, Locale locale)` with for example `Locale.US` for ASCII dates."
+        errorLine1="            DateFormat df = new SimpleDateFormat(&quot;HH:mm&quot;);"
+        errorLine2="                            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lenovo/watchxplus/WatchXPlusDeviceCoordinator.java"
+            line="255"
+            column="29"/>
+    </issue>
+
+    <issue
+        id="SimpleDateFormat"
+        message="To get local formatting use `getDateInstance()`, `getDateTimeInstance()`, or `getTimeInstance()`, or use `new SimpleDateFormat(String template, Locale locale)` with for example `Locale.US` for ASCII dates."
+        errorLine1="            DateFormat df = new SimpleDateFormat(&quot;HH:mm&quot;);"
+        errorLine2="                            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lenovo/watchxplus/WatchXPlusDeviceCoordinator.java"
+            line="284"
+            column="29"/>
+    </issue>
+
+    <issue
+        id="SimpleDateFormat"
+        message="To get local formatting use `getDateInstance()`, `getDateTimeInstance()`, or `getTimeInstance()`, or use `new SimpleDateFormat(String template, Locale locale)` with for example `Locale.US` for ASCII dates."
+        errorLine1="        String time = new SimpleDateFormat(&quot;yyyyMMddHHmmss&quot;).format(new Date());"
+        errorLine2="                      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xwatch/XWatchSupport.java"
+            line="136"
+            column="23"/>
+    </issue>
+
+    <issue
+        id="SimpleDateFormat"
+        message="To get local formatting use `getDateInstance()`, `getDateTimeInstance()`, or `getTimeInstance()`, or use `new SimpleDateFormat(String template, Locale locale)` with for example `Locale.US` for ASCII dates."
+        errorLine1="        DateFormat df_start = new SimpleDateFormat(&quot;HH:mm&quot;);"
+        errorLine2="                              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/zetime/ZeTimeDeviceSupport.java"
+            line="1642"
+            column="31"/>
+    </issue>
+
+    <issue
+        id="SimpleDateFormat"
+        message="To get local formatting use `getDateInstance()`, `getDateTimeInstance()`, or `getTimeInstance()`, or use `new SimpleDateFormat(String template, Locale locale)` with for example `Locale.US` for ASCII dates."
+        errorLine1="        DateFormat df_end = new SimpleDateFormat(&quot;HH:mm&quot;);"
+        errorLine2="                            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/zetime/ZeTimeDeviceSupport.java"
+            line="1643"
+            column="29"/>
+    </issue>
+
+    <issue
+        id="SimpleDateFormat"
+        message="To get local formatting use `getDateInstance()`, `getDateTimeInstance()`, or `getTimeInstance()`, or use `new SimpleDateFormat(String template, Locale locale)` with for example `Locale.US` for ASCII dates."
+        errorLine1="            DateFormat df_start = new SimpleDateFormat(&quot;HH:mm&quot;);"
+        errorLine2="                                  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/zetime/ZeTimeDeviceSupport.java"
+            line="1770"
+            column="35"/>
+    </issue>
+
+    <issue
+        id="SimpleDateFormat"
+        message="To get local formatting use `getDateInstance()`, `getDateTimeInstance()`, or `getTimeInstance()`, or use `new SimpleDateFormat(String template, Locale locale)` with for example `Locale.US` for ASCII dates."
+        errorLine1="            DateFormat df_end = new SimpleDateFormat(&quot;HH:mm&quot;);"
+        errorLine2="                                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/zetime/ZeTimeDeviceSupport.java"
+            line="1771"
+            column="33"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             user__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/ActivityDescription.java"
+            line="112"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/ActivityDescription.java"
+            line="111"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             user__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/Alarm.java"
+            line="172"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/Alarm.java"
+            line="171"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             device__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/Alarm.java"
+            line="200"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/Alarm.java"
+            line="199"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             device__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/AppSpecificNotificationSetting.java"
+            line="106"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/AppSpecificNotificationSetting.java"
+            line="105"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             device__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/BangleJSActivitySample.java"
+            line="144"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/BangleJSActivitySample.java"
+            line="143"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             user__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/BangleJSActivitySample.java"
+            line="172"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/BangleJSActivitySample.java"
+            line="171"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             device__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/BaseActivitySummary.java"
+            line="227"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/BaseActivitySummary.java"
+            line="226"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             user__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/BaseActivitySummary.java"
+            line="255"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/BaseActivitySummary.java"
+            line="254"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             device__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/BatteryLevel.java"
+            line="94"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/BatteryLevel.java"
+            line="93"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             device__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/CalendarSyncState.java"
+            line="92"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/CalendarSyncState.java"
+            line="91"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             device__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/CasioGBX100ActivitySample.java"
+            line="130"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/CasioGBX100ActivitySample.java"
+            line="129"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             user__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/CasioGBX100ActivitySample.java"
+            line="158"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/CasioGBX100ActivitySample.java"
+            line="157"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             device__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/CmfActivitySample.java"
+            line="164"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/CmfActivitySample.java"
+            line="163"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             user__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/CmfActivitySample.java"
+            line="192"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/CmfActivitySample.java"
+            line="191"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             device__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/CmfHeartRateSample.java"
+            line="106"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/CmfHeartRateSample.java"
+            line="105"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             user__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/CmfHeartRateSample.java"
+            line="134"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/CmfHeartRateSample.java"
+            line="133"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             device__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/CmfSleepSessionSample.java"
+            line="115"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/CmfSleepSessionSample.java"
+            line="114"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             user__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/CmfSleepSessionSample.java"
+            line="143"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/CmfSleepSessionSample.java"
+            line="142"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             device__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/CmfSleepStageSample.java"
+            line="115"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/CmfSleepStageSample.java"
+            line="114"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             user__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/CmfSleepStageSample.java"
+            line="143"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/CmfSleepStageSample.java"
+            line="142"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             device__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/CmfSpo2Sample.java"
+            line="106"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/CmfSpo2Sample.java"
+            line="105"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             user__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/CmfSpo2Sample.java"
+            line="134"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/CmfSpo2Sample.java"
+            line="133"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             device__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/CmfStressSample.java"
+            line="106"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/CmfStressSample.java"
+            line="105"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             user__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/CmfStressSample.java"
+            line="134"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/CmfStressSample.java"
+            line="133"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             device__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/CmfWorkoutGpsSample.java"
+            line="115"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/CmfWorkoutGpsSample.java"
+            line="114"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             user__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/CmfWorkoutGpsSample.java"
+            line="143"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/CmfWorkoutGpsSample.java"
+            line="142"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             user__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/Contact.java"
+            line="114"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/Contact.java"
+            line="113"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             device__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/Contact.java"
+            line="142"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/Contact.java"
+            line="141"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             device__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/FemometerVinca2TemperatureSample.java"
+            line="117"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/FemometerVinca2TemperatureSample.java"
+            line="116"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             user__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/FemometerVinca2TemperatureSample.java"
+            line="145"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/FemometerVinca2TemperatureSample.java"
+            line="144"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             device__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/FitProActivitySample.java"
+            line="192"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/FitProActivitySample.java"
+            line="191"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             user__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/FitProActivitySample.java"
+            line="220"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/FitProActivitySample.java"
+            line="219"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             device__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/GarminFitFile.java"
+            line="166"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/GarminFitFile.java"
+            line="165"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             user__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/GarminFitFile.java"
+            line="194"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/GarminFitFile.java"
+            line="193"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             device__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/HPlusHealthActivityOverlay.java"
+            line="118"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/HPlusHealthActivityOverlay.java"
+            line="117"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             user__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/HPlusHealthActivityOverlay.java"
+            line="146"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/HPlusHealthActivityOverlay.java"
+            line="145"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             device__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/HPlusHealthActivitySample.java"
+            line="173"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/HPlusHealthActivitySample.java"
+            line="172"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             user__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/HPlusHealthActivitySample.java"
+            line="201"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/HPlusHealthActivitySample.java"
+            line="200"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             device__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/HuamiExtendedActivitySample.java"
+            line="184"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/HuamiExtendedActivitySample.java"
+            line="183"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             user__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/HuamiExtendedActivitySample.java"
+            line="212"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/HuamiExtendedActivitySample.java"
+            line="211"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             device__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/HuamiHeartRateManualSample.java"
+            line="116"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/HuamiHeartRateManualSample.java"
+            line="115"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             user__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/HuamiHeartRateManualSample.java"
+            line="144"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/HuamiHeartRateManualSample.java"
+            line="143"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             device__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/HuamiHeartRateMaxSample.java"
+            line="116"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/HuamiHeartRateMaxSample.java"
+            line="115"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             user__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/HuamiHeartRateMaxSample.java"
+            line="144"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/HuamiHeartRateMaxSample.java"
+            line="143"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             device__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/HuamiHeartRateRestingSample.java"
+            line="116"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/HuamiHeartRateRestingSample.java"
+            line="115"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             user__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/HuamiHeartRateRestingSample.java"
+            line="144"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/HuamiHeartRateRestingSample.java"
+            line="143"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             device__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/HuamiPaiSample.java"
+            line="193"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/HuamiPaiSample.java"
+            line="192"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             user__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/HuamiPaiSample.java"
+            line="221"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/HuamiPaiSample.java"
+            line="220"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             device__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/HuamiSleepRespiratoryRateSample.java"
+            line="116"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/HuamiSleepRespiratoryRateSample.java"
+            line="115"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             user__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/HuamiSleepRespiratoryRateSample.java"
+            line="144"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/HuamiSleepRespiratoryRateSample.java"
+            line="143"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             device__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/HuamiSpo2Sample.java"
+            line="117"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/HuamiSpo2Sample.java"
+            line="116"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             user__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/HuamiSpo2Sample.java"
+            line="145"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/HuamiSpo2Sample.java"
+            line="144"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             device__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/HuamiStressSample.java"
+            line="117"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/HuamiStressSample.java"
+            line="116"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             user__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/HuamiStressSample.java"
+            line="145"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/HuamiStressSample.java"
+            line="144"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             device__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/HuaweiActivitySample.java"
+            line="194"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/HuaweiActivitySample.java"
+            line="193"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             user__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/HuaweiActivitySample.java"
+            line="222"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/HuaweiActivitySample.java"
+            line="221"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             huaweiWorkoutSummarySample__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/HuaweiWorkoutDataSample.java"
+            line="224"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/HuaweiWorkoutDataSample.java"
+            line="223"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             huaweiWorkoutSummarySample__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/HuaweiWorkoutPaceSample.java"
+            line="105"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/HuaweiWorkoutPaceSample.java"
+            line="104"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             device__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/HuaweiWorkoutSummarySample.java"
+            line="246"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/HuaweiWorkoutSummarySample.java"
+            line="245"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             user__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/HuaweiWorkoutSummarySample.java"
+            line="274"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/HuaweiWorkoutSummarySample.java"
+            line="273"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             device__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/HybridHRActivitySample.java"
+            line="180"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/HybridHRActivitySample.java"
+            line="179"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             user__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/HybridHRActivitySample.java"
+            line="208"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/HybridHRActivitySample.java"
+            line="207"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             device__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/ID115ActivitySample.java"
+            line="150"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/ID115ActivitySample.java"
+            line="149"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             user__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/ID115ActivitySample.java"
+            line="178"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/ID115ActivitySample.java"
+            line="177"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             device__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/JYouActivitySample.java"
+            line="162"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/JYouActivitySample.java"
+            line="161"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             user__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/JYouActivitySample.java"
+            line="190"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/JYouActivitySample.java"
+            line="189"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             device__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/LefunActivitySample.java"
+            line="152"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/LefunActivitySample.java"
+            line="151"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             user__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/LefunActivitySample.java"
+            line="180"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/LefunActivitySample.java"
+            line="179"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             device__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/LefunBiometricSample.java"
+            line="116"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/LefunBiometricSample.java"
+            line="115"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             user__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/LefunBiometricSample.java"
+            line="144"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/LefunBiometricSample.java"
+            line="143"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             device__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/LefunSleepSample.java"
+            line="96"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/LefunSleepSample.java"
+            line="95"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             user__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/LefunSleepSample.java"
+            line="124"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/LefunSleepSample.java"
+            line="123"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             device__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/MakibesHR3ActivitySample.java"
+            line="132"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/MakibesHR3ActivitySample.java"
+            line="131"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             user__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/MakibesHR3ActivitySample.java"
+            line="160"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/MakibesHR3ActivitySample.java"
+            line="159"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             device__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/MiBandActivitySample.java"
+            line="144"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/MiBandActivitySample.java"
+            line="143"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             user__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/MiBandActivitySample.java"
+            line="172"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/MiBandActivitySample.java"
+            line="171"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             device__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/No1F1ActivitySample.java"
+            line="144"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/No1F1ActivitySample.java"
+            line="143"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             user__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/No1F1ActivitySample.java"
+            line="172"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/No1F1ActivitySample.java"
+            line="171"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             notificationFilter__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/NotificationFilterEntry.java"
+            line="85"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/NotificationFilterEntry.java"
+            line="84"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             device__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/PebbleHealthActivityOverlay.java"
+            line="118"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/PebbleHealthActivityOverlay.java"
+            line="117"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             user__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/PebbleHealthActivityOverlay.java"
+            line="146"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/PebbleHealthActivityOverlay.java"
+            line="145"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             device__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/PebbleHealthActivitySample.java"
+            line="143"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/PebbleHealthActivitySample.java"
+            line="142"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             user__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/PebbleHealthActivitySample.java"
+            line="171"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/PebbleHealthActivitySample.java"
+            line="170"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             device__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/PebbleMisfitSample.java"
+            line="107"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/PebbleMisfitSample.java"
+            line="106"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             user__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/PebbleMisfitSample.java"
+            line="135"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/PebbleMisfitSample.java"
+            line="134"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             device__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/PebbleMorpheuzSample.java"
+            line="108"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/PebbleMorpheuzSample.java"
+            line="107"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             user__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/PebbleMorpheuzSample.java"
+            line="136"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/PebbleMorpheuzSample.java"
+            line="135"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             device__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/PineTimeActivitySample.java"
+            line="132"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/PineTimeActivitySample.java"
+            line="131"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             user__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/PineTimeActivitySample.java"
+            line="160"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/PineTimeActivitySample.java"
+            line="159"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             user__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/Reminder.java"
+            line="124"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/Reminder.java"
+            line="123"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             device__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/Reminder.java"
+            line="152"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/Reminder.java"
+            line="151"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             device__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/SonySWR12Sample.java"
+            line="144"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/SonySWR12Sample.java"
+            line="143"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             user__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/SonySWR12Sample.java"
+            line="172"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/SonySWR12Sample.java"
+            line="171"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             device__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/TLW64ActivitySample.java"
+            line="132"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/TLW64ActivitySample.java"
+            line="131"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             user__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/TLW64ActivitySample.java"
+            line="160"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/TLW64ActivitySample.java"
+            line="159"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             device__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/VivomoveHrActivitySample.java"
+            line="164"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/VivomoveHrActivitySample.java"
+            line="163"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             user__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/VivomoveHrActivitySample.java"
+            line="192"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/VivomoveHrActivitySample.java"
+            line="191"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             device__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/WatchXPlusActivitySample.java"
+            line="173"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/WatchXPlusActivitySample.java"
+            line="172"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             user__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/WatchXPlusActivitySample.java"
+            line="201"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/WatchXPlusActivitySample.java"
+            line="200"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             device__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/WatchXPlusHealthActivityOverlay.java"
+            line="118"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/WatchXPlusHealthActivityOverlay.java"
+            line="117"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             user__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/WatchXPlusHealthActivityOverlay.java"
+            line="146"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/WatchXPlusHealthActivityOverlay.java"
+            line="145"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             device__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/Wena3ActivitySample.java"
+            line="129"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/Wena3ActivitySample.java"
+            line="128"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             user__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/Wena3ActivitySample.java"
+            line="157"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/Wena3ActivitySample.java"
+            line="156"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             device__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/Wena3BehaviorSample.java"
+            line="125"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/Wena3BehaviorSample.java"
+            line="124"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             user__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/Wena3BehaviorSample.java"
+            line="153"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/Wena3BehaviorSample.java"
+            line="152"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             device__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/Wena3CaloriesSample.java"
+            line="105"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/Wena3CaloriesSample.java"
+            line="104"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             user__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/Wena3CaloriesSample.java"
+            line="133"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/Wena3CaloriesSample.java"
+            line="132"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             device__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/Wena3EnergySample.java"
+            line="105"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/Wena3EnergySample.java"
+            line="104"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             user__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/Wena3EnergySample.java"
+            line="133"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/Wena3EnergySample.java"
+            line="132"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             device__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/Wena3HeartRateSample.java"
+            line="105"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/Wena3HeartRateSample.java"
+            line="104"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             user__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/Wena3HeartRateSample.java"
+            line="133"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/Wena3HeartRateSample.java"
+            line="132"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             device__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/Wena3StressSample.java"
+            line="117"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/Wena3StressSample.java"
+            line="116"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             user__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/Wena3StressSample.java"
+            line="145"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/Wena3StressSample.java"
+            line="144"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             device__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/Wena3Vo2Sample.java"
+            line="116"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/Wena3Vo2Sample.java"
+            line="115"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             user__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/Wena3Vo2Sample.java"
+            line="144"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/Wena3Vo2Sample.java"
+            line="143"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             device__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/WithingsSteelHRActivitySample.java"
+            line="174"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/WithingsSteelHRActivitySample.java"
+            line="173"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             user__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/WithingsSteelHRActivitySample.java"
+            line="202"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/WithingsSteelHRActivitySample.java"
+            line="201"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             user__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/WorldClock.java"
+            line="134"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/WorldClock.java"
+            line="133"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             device__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/WorldClock.java"
+            line="162"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/WorldClock.java"
+            line="161"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             device__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/XWatchActivitySample.java"
+            line="144"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/XWatchActivitySample.java"
+            line="143"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             user__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/XWatchActivitySample.java"
+            line="172"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/XWatchActivitySample.java"
+            line="171"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             device__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/XiaomiActivitySample.java"
+            line="164"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/XiaomiActivitySample.java"
+            line="163"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             user__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/XiaomiActivitySample.java"
+            line="192"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/XiaomiActivitySample.java"
+            line="191"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             device__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/XiaomiDailySummarySample.java"
+            line="345"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/XiaomiDailySummarySample.java"
+            line="344"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             user__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/XiaomiDailySummarySample.java"
+            line="373"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/XiaomiDailySummarySample.java"
+            line="372"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             device__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/XiaomiManualSample.java"
+            line="115"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/XiaomiManualSample.java"
+            line="114"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             user__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/XiaomiManualSample.java"
+            line="143"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/XiaomiManualSample.java"
+            line="142"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             device__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/XiaomiSleepStageSample.java"
+            line="105"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/XiaomiSleepStageSample.java"
+            line="104"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             user__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/XiaomiSleepStageSample.java"
+            line="133"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/XiaomiSleepStageSample.java"
+            line="132"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             device__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/XiaomiSleepTimeSample.java"
+            line="165"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/XiaomiSleepTimeSample.java"
+            line="164"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             user__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/XiaomiSleepTimeSample.java"
+            line="193"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/XiaomiSleepTimeSample.java"
+            line="192"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             device__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/ZeTimeActivitySample.java"
+            line="174"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/ZeTimeActivitySample.java"
+            line="173"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="SuspiciousIndentation"
+        message="The indentation string here is different from on the previous line (`&quot; &quot;` vs `\t`)"
+        errorLine1="             user__resolvedKey = __key;"
+        errorLine2="            ~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/ZeTimeActivitySample.java"
+            line="202"
+            column="13"/>
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/ZeTimeActivitySample.java"
+            line="201"
+            column="13"
+            message="Previous line indentation here"/>
+    </issue>
+
+    <issue
+        id="UnusedAttribute"
+        message="Attribute `iconTint` is only used in API level 26 and higher (current min is 21)"
+        errorLine1="            android:iconTint=&quot;#7E7E7E&quot;"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/menu/activity_controlcenterv2_main_drawer.xml"
+            line="21"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedAttribute"
+        message="Attribute `min` is only used in API level 26 and higher (current min is 21)"
+        errorLine1="                android:min=&quot;0&quot; />"
+        errorLine2="                ~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_qhybrid_settings.xml"
+            line="34"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="UnusedAttribute"
+        message="Attribute `tooltipText` is only used in API level 26 and higher (current min is 21)"
+        errorLine1="                android:tooltipText=&quot;@string/candidate_item_device_image&quot;"
+        errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/device_itemv2.xml"
+            line="57"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="UnusedAttribute"
+        message="Attribute `tooltipText` is only used in API level 26 and higher (current min is 21)"
+        errorLine1="                android:tooltipText=&quot;@string/candidate_item_device_image&quot;"
+        errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/device_itemv2.xml"
+            line="99"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="UnusedAttribute"
+        message="Attribute `tooltipText` is only used in API level 26 and higher (current min is 21)"
+        errorLine1="                    android:tooltipText=&quot;@string/battery_detail_activity_title&quot;"
+        errorLine2="                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/device_itemv2.xml"
+            line="117"
+            column="21"/>
+    </issue>
+
+    <issue
+        id="UnusedAttribute"
+        message="Attribute `tooltipText` is only used in API level 26 and higher (current min is 21)"
+        errorLine1="                    android:tooltipText=&quot;@string/title_activity_device_specific_settings&quot;"
+        errorLine2="                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/device_itemv2.xml"
+            line="219"
+            column="21"/>
+    </issue>
+
+    <issue
+        id="UnusedAttribute"
+        message="Attribute `tooltipText` is only used in API level 26 and higher (current min is 21)"
+        errorLine1="                    android:tooltipText=&quot;@string/controlcenter_change_fm_frequency&quot;"
+        errorLine2="                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/device_itemv2.xml"
+            line="231"
+            column="21"/>
+    </issue>
+
+    <issue
+        id="UnusedAttribute"
+        message="Attribute `tooltipText` is only used in API level 26 and higher (current min is 21)"
+        errorLine1="                    android:tooltipText=&quot;@string/controlcenter_change_led_color&quot;"
+        errorLine2="                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/device_itemv2.xml"
+            line="267"
+            column="21"/>
+    </issue>
+
+    <issue
+        id="UnusedAttribute"
+        message="Attribute `tooltipText` is only used in API level 26 and higher (current min is 21)"
+        errorLine1="                        android:tooltipText=&quot;@string/controlcenter_fetch_activity_data&quot;"
+        errorLine2="                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/device_itemv2.xml"
+            line="291"
+            column="25"/>
+    </issue>
+
+    <issue
+        id="UnusedAttribute"
+        message="Attribute `tooltipText` is only used in API level 26 and higher (current min is 21)"
+        errorLine1="                    android:tooltipText=&quot;@string/controlcenter_take_screenshot&quot;"
+        errorLine2="                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/device_itemv2.xml"
+            line="319"
+            column="21"/>
+    </issue>
+
+    <issue
+        id="UnusedAttribute"
+        message="Attribute `tooltipText` is only used in API level 26 and higher (current min is 21)"
+        errorLine1="                    android:tooltipText=&quot;@string/title_activity_appmanager&quot;"
+        errorLine2="                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/device_itemv2.xml"
+            line="336"
+            column="21"/>
+    </issue>
+
+    <issue
+        id="UnusedAttribute"
+        message="Attribute `tooltipText` is only used in API level 26 and higher (current min is 21)"
+        errorLine1="                    android:tooltipText=&quot;@string/controlcenter_start_configure_alarms&quot;"
+        errorLine2="                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/device_itemv2.xml"
+            line="353"
+            column="21"/>
+    </issue>
+
+    <issue
+        id="UnusedAttribute"
+        message="Attribute `tooltipText` is only used in API level 26 and higher (current min is 21)"
+        errorLine1="                    android:tooltipText=&quot;@string/controlcenter_start_configure_reminders&quot;"
+        errorLine2="                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/device_itemv2.xml"
+            line="370"
+            column="21"/>
+    </issue>
+
+    <issue
+        id="UnusedAttribute"
+        message="Attribute `tooltipText` is only used in API level 26 and higher (current min is 21)"
+        errorLine1="                    android:tooltipText=&quot;@string/controlcenter_start_activitymonitor&quot;"
+        errorLine2="                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/device_itemv2.xml"
+            line="387"
+            column="21"/>
+    </issue>
+
+    <issue
+        id="UnusedAttribute"
+        message="Attribute `tooltipText` is only used in API level 26 and higher (current min is 21)"
+        errorLine1="                    android:tooltipText=&quot;@string/controlcenter_start_activity_tracks&quot;"
+        errorLine2="                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/device_itemv2.xml"
+            line="404"
+            column="21"/>
+    </issue>
+
+    <issue
+        id="UnusedAttribute"
+        message="Attribute `tooltipText` is only used in API level 26 and higher (current min is 21)"
+        errorLine1="                    android:tooltipText=&quot;@string/controlcenter_find_device&quot;"
+        errorLine2="                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/device_itemv2.xml"
+            line="420"
+            column="21"/>
+    </issue>
+
+    <issue
+        id="UnusedAttribute"
+        message="Attribute `tooltipText` is only used in API level 26 and higher (current min is 21)"
+        errorLine1="                    android:tooltipText=&quot;@string/controlcenter_calibrate_device&quot;"
+        errorLine2="                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/device_itemv2.xml"
+            line="436"
+            column="21"/>
+    </issue>
+
+    <issue
+        id="UnusedAttribute"
+        message="Attribute `tooltipText` is only used in API level 26 and higher (current min is 21)"
+        errorLine1="                    android:tooltipText=&quot;@string/controlcenter_get_heartrate_measurement&quot;"
+        errorLine2="                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/device_itemv2.xml"
+            line="448"
+            column="21"/>
+    </issue>
+
+    <issue
+        id="UnusedAttribute"
+        message="Attribute `tooltipText` is only used in API level 26 and higher (current min is 21)"
+        errorLine1="                    android:tooltipText=&quot;@string/controlcenter_power_off&quot;"
+        errorLine2="                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/device_itemv2.xml"
+            line="483"
+            column="21"/>
+    </issue>
+
+    <issue
+        id="UnusedAttribute"
+        message="Attribute `tooltipText` is only used in API level 26 and higher (current min is 21)"
+        errorLine1="                        android:tooltipText=&quot;@string/live_activity_total_steps&quot;"
+        errorLine2="                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/device_itemv2.xml"
+            line="513"
+            column="25"/>
+    </issue>
+
+    <issue
+        id="UnusedAttribute"
+        message="Attribute `tooltipText` is only used in API level 26 and higher (current min is 21)"
+        errorLine1="                        android:tooltipText=&quot;@string/activity_list_summary_distance&quot;"
+        errorLine2="                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/device_itemv2.xml"
+            line="522"
+            column="25"/>
+    </issue>
+
+    <issue
+        id="UnusedAttribute"
+        message="Attribute `tooltipText` is only used in API level 26 and higher (current min is 21)"
+        errorLine1="                        android:tooltipText=&quot;@string/sleepchart_your_sleep&quot;"
+        errorLine2="                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/device_itemv2.xml"
+            line="532"
+            column="25"/>
+    </issue>
+
+    <issue
+        id="UnusedAttribute"
+        message="Attribute `min` is only used in API level 26 and higher (current min is 21)"
+        errorLine1="                android:min=&quot;0&quot;"
+        errorLine2="                ~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/xml/devicesettings_galaxy_buds_2.xml"
+            line="25"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="UnusedAttribute"
+        message="Attribute `min` is only used in API level 26 and higher (current min is 21)"
+        errorLine1="                android:min=&quot;0&quot;"
+        errorLine2="                ~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/xml/devicesettings_galaxy_buds_2.xml"
+            line="58"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="UnusedAttribute"
+        message="Attribute `min` is only used in API level 26 and higher (current min is 21)"
+        errorLine1="                android:min=&quot;0&quot;"
+        errorLine2="                ~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/xml/devicesettings_galaxy_buds_2.xml"
+            line="68"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="UnusedAttribute"
+        message="Attribute `min` is only used in API level 26 and higher (current min is 21)"
+        errorLine1="                android:min=&quot;0&quot;"
+        errorLine2="                ~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/xml/devicesettings_galaxy_buds_2.xml"
+            line="78"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="UnusedAttribute"
+        message="Attribute `min` is only used in API level 26 and higher (current min is 21)"
+        errorLine1="            android:min=&quot;0&quot;"
+        errorLine2="            ~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/xml/devicesettings_galaxy_buds_2.xml"
+            line="182"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedAttribute"
+        message="Attribute `min` is only used in API level 26 and higher (current min is 21)"
+        errorLine1="                android:min=&quot;0&quot;"
+        errorLine2="                ~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/xml/devicesettings_galaxy_buds_2_pro.xml"
+            line="58"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="UnusedAttribute"
+        message="Attribute `min` is only used in API level 26 and higher (current min is 21)"
+        errorLine1="                android:min=&quot;0&quot;"
+        errorLine2="                ~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/xml/devicesettings_galaxy_buds_2_pro.xml"
+            line="68"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="UnusedAttribute"
+        message="Attribute `min` is only used in API level 26 and higher (current min is 21)"
+        errorLine1="                android:min=&quot;0&quot;"
+        errorLine2="                ~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/xml/devicesettings_galaxy_buds_2_pro.xml"
+            line="78"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="UnusedAttribute"
+        message="Attribute `min` is only used in API level 26 and higher (current min is 21)"
+        errorLine1="            android:min=&quot;0&quot;"
+        errorLine2="            ~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/xml/devicesettings_galaxy_buds_2_pro.xml"
+            line="181"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedAttribute"
+        message="Attribute `min` is only used in API level 26 and higher (current min is 21)"
+        errorLine1="                android:min=&quot;0&quot;"
+        errorLine2="                ~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/xml/devicesettings_galaxy_buds_pro.xml"
+            line="35"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="UnusedAttribute"
+        message="Attribute `min` is only used in API level 26 and higher (current min is 21)"
+        errorLine1="                android:min=&quot;0&quot;"
+        errorLine2="                ~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/xml/devicesettings_galaxy_buds_pro.xml"
+            line="88"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="UnusedAttribute"
+        message="Attribute `min` is only used in API level 26 and higher (current min is 21)"
+        errorLine1="                android:min=&quot;0&quot;"
+        errorLine2="                ~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/xml/devicesettings_galaxy_buds_pro.xml"
+            line="98"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="UnusedAttribute"
+        message="Attribute `min` is only used in API level 26 and higher (current min is 21)"
+        errorLine1="                android:min=&quot;0&quot;"
+        errorLine2="                ~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/xml/devicesettings_galaxy_buds_pro.xml"
+            line="108"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="UnusedAttribute"
+        message="Attribute `min` is only used in API level 26 and higher (current min is 21)"
+        errorLine1="            android:min=&quot;0&quot;"
+        errorLine2="            ~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/xml/devicesettings_galaxy_buds_pro.xml"
+            line="216"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedAttribute"
+        message="Attribute `singleLineTitle` is only used in API level 26 and higher (current min is 21)"
+        errorLine1="            android:singleLineTitle=&quot;false&quot;"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/xml/fossil_hr_watchface_settings.xml"
+            line="29"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedAttribute"
+        message="Attribute `singleLineTitle` is only used in API level 26 and higher (current min is 21)"
+        errorLine1="            android:singleLineTitle=&quot;false&quot;"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/xml/fossil_hr_watchface_settings.xml"
+            line="35"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedAttribute"
+        message="Attribute `singleLineTitle` is only used in API level 26 and higher (current min is 21)"
+        errorLine1="            android:singleLineTitle=&quot;false&quot;"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/xml/fossil_hr_watchface_settings.xml"
+            line="97"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedAttribute"
+        message="Attribute `singleLineTitle` is only used in API level 26 and higher (current min is 21)"
+        errorLine1="            android:singleLineTitle=&quot;false&quot;"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/xml/fossil_hr_watchface_settings.xml"
+            line="103"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedAttribute"
+        message="Attribute `singleLineTitle` is only used in API level 26 and higher (current min is 21)"
+        errorLine1="            android:singleLineTitle=&quot;false&quot;"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/xml/fossil_hr_watchface_settings.xml"
+            line="109"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedAttribute"
+        message="Attribute `singleLineTitle` is only used in API level 26 and higher (current min is 21)"
+        errorLine1="            android:singleLineTitle=&quot;false&quot;"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/xml/fossil_hr_widget_settings.xml"
+            line="98"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedAttribute"
+        message="Attribute `singleLineTitle` is only used in API level 26 and higher (current min is 21)"
+        errorLine1="            android:singleLineTitle=&quot;false&quot;"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/xml/fossil_hr_widget_settings.xml"
+            line="104"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedAttribute"
+        message="Attribute `singleLineTitle` is only used in API level 26 and higher (current min is 21)"
+        errorLine1="                android:singleLineTitle=&quot;false&quot;"
+        errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/xml/preferences.xml"
+            line="82"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="UnusedAttribute"
+        message="Attribute `tooltipText` is only used in API level 26 and higher (current min is 21)"
+        errorLine1="        android:tooltipText=&quot;@string/step_streak_average_steps_hint&quot; />"
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/steps_streak_average.xml"
+            line="23"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="UnusedAttribute"
+        message="Attribute `tooltipText` is only used in API level 26 and higher (current min is 21)"
+        errorLine1="        android:tooltipText=&quot;@string/step_streak_average_steps_hint&quot; />"
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/steps_streak_average.xml"
+            line="36"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="UnusedAttribute"
+        message="Attribute `tooltipText` is only used in API level 26 and higher (current min is 21)"
+        errorLine1="        android:tooltipText=&quot;@string/step_streak_days_hint&quot; />"
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/steps_streak_days.xml"
+            line="25"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="UnusedAttribute"
+        message="Attribute `tooltipText` is only used in API level 26 and higher (current min is 21)"
+        errorLine1="        android:tooltipText=&quot;@string/step_streak_days_hint&quot; />"
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/steps_streak_days.xml"
+            line="38"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="UnusedAttribute"
+        message="Attribute `tooltipText` is only used in API level 26 and higher (current min is 21)"
+        errorLine1="        android:tooltipText=&quot;@string/steps_streaks_total_steps_hint&quot; />"
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/steps_streak_total.xml"
+            line="24"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="UnusedAttribute"
+        message="Attribute `tooltipText` is only used in API level 26 and higher (current min is 21)"
+        errorLine1="        android:tooltipText=&quot;@string/steps_streaks_total_steps_hint&quot; />"
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/steps_streak_total.xml"
+            line="37"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="UnusedAttribute"
+        message="Attribute `tooltipText` is only used in API level 26 and higher (current min is 21)"
+        errorLine1="                android:tooltipText=&quot;@string/steps_streaks_hint&quot; />"
+        errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/steps_streaks_dashboard.xml"
+            line="33"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="AppBundleLocaleChanges"
+        message="Found dynamic locale changes, but did not find corresponding Play Core library calls for downloading languages and splitting by language is not disabled in the `bundle` configuration"
+        errorLine1="        config.setLocale(language);"
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/util/AndroidUtils.java"
+            line="110"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="InflateParams"
+        message="Avoid passing `null` as the view root (needed to resolve layout parameters on the inflated layout&apos;s root element)"
+        errorLine1="                convertView = inflater.inflate(R.layout.fossil_hr_row_installed_app, null);"
+        errorLine2="                                                                                     ~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/AppsManagementActivity.java"
+            line="113"
+            column="86"/>
+    </issue>
+
+    <issue
+        id="InflateParams"
+        message="Avoid passing `null` as the view root (needed to resolve layout parameters on the inflated layout&apos;s root element)"
+        errorLine1="                view = ((LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE)).inflate(R.layout.qhybrid_package_settings_item, null);"
+        errorLine2="                                                                                                                                    ~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/ConfigActivity.java"
+            line="574"
+            column="133"/>
+    </issue>
+
+    <issue
+        id="InflateParams"
+        message="Avoid passing `null` as the view root (needed to resolve layout parameters on the inflated layout&apos;s root element)"
+        errorLine1="                view = ((LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE)).inflate(R.layout.qhybrid_app_view, null);"
+        errorLine2="                                                                                                                       ~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/QHybridAppChoserActivity.java"
+            line="188"
+            column="120"/>
+    </issue>
+
+    <issue
+        id="NonConstantResourceId"
+        message="Resource IDs will be non-final by default in Android Gradle Plugin version 8.0, avoid using them in switch case statements"
+        errorLine1="            case R.id.appmanager_app_delete_cache:"
+        errorLine2="                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/appmanager/AbstractAppManagerFragment.java"
+            line="589"
+            column="18"/>
+    </issue>
+
+    <issue
+        id="NonConstantResourceId"
+        message="Resource IDs will be non-final by default in Android Gradle Plugin version 8.0, avoid using them in switch case statements"
+        errorLine1="            case R.id.appmanager_app_delete:"
+        errorLine2="                 ~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/appmanager/AbstractAppManagerFragment.java"
+            line="592"
+            column="18"/>
+    </issue>
+
+    <issue
+        id="NonConstantResourceId"
+        message="Resource IDs will be non-final by default in Android Gradle Plugin version 8.0, avoid using them in switch case statements"
+        errorLine1="            case R.id.appmanager_app_start:"
+        errorLine2="                 ~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/appmanager/AbstractAppManagerFragment.java"
+            line="595"
+            column="18"/>
+    </issue>
+
+    <issue
+        id="NonConstantResourceId"
+        message="Resource IDs will be non-final by default in Android Gradle Plugin version 8.0, avoid using them in switch case statements"
+        errorLine1="            case R.id.appmanager_app_download:"
+        errorLine2="                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/appmanager/AbstractAppManagerFragment.java"
+            line="599"
+            column="18"/>
+    </issue>
+
+    <issue
+        id="NonConstantResourceId"
+        message="Resource IDs will be non-final by default in Android Gradle Plugin version 8.0, avoid using them in switch case statements"
+        errorLine1="            case R.id.appmanager_app_reinstall:"
+        errorLine2="                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/appmanager/AbstractAppManagerFragment.java"
+            line="603"
+            column="18"/>
+    </issue>
+
+    <issue
+        id="NonConstantResourceId"
+        message="Resource IDs will be non-final by default in Android Gradle Plugin version 8.0, avoid using them in switch case statements"
+        errorLine1="            case R.id.appmanager_app_share:"
+        errorLine2="                 ~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/appmanager/AbstractAppManagerFragment.java"
+            line="607"
+            column="18"/>
+    </issue>
+
+    <issue
+        id="NonConstantResourceId"
+        message="Resource IDs will be non-final by default in Android Gradle Plugin version 8.0, avoid using them in switch case statements"
+        errorLine1="            case R.id.appmanager_health_activate:"
+        errorLine2="                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/appmanager/AbstractAppManagerFragment.java"
+            line="627"
+            column="18"/>
+    </issue>
+
+    <issue
+        id="NonConstantResourceId"
+        message="Resource IDs will be non-final by default in Android Gradle Plugin version 8.0, avoid using them in switch case statements"
+        errorLine1="            case R.id.appmanager_hrm_activate:"
+        errorLine2="                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/appmanager/AbstractAppManagerFragment.java"
+            line="630"
+            column="18"/>
+    </issue>
+
+    <issue
+        id="NonConstantResourceId"
+        message="Resource IDs will be non-final by default in Android Gradle Plugin version 8.0, avoid using them in switch case statements"
+        errorLine1="            case R.id.appmanager_weather_activate:"
+        errorLine2="                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/appmanager/AbstractAppManagerFragment.java"
+            line="633"
+            column="18"/>
+    </issue>
+
+    <issue
+        id="NonConstantResourceId"
+        message="Resource IDs will be non-final by default in Android Gradle Plugin version 8.0, avoid using them in switch case statements"
+        errorLine1="            case R.id.appmanager_health_deactivate:"
+        errorLine2="                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/appmanager/AbstractAppManagerFragment.java"
+            line="636"
+            column="18"/>
+    </issue>
+
+    <issue
+        id="NonConstantResourceId"
+        message="Resource IDs will be non-final by default in Android Gradle Plugin version 8.0, avoid using them in switch case statements"
+        errorLine1="            case R.id.appmanager_weather_install_provider:"
+        errorLine2="                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/appmanager/AbstractAppManagerFragment.java"
+            line="641"
+            column="18"/>
+    </issue>
+
+    <issue
+        id="NonConstantResourceId"
+        message="Resource IDs will be non-final by default in Android Gradle Plugin version 8.0, avoid using them in switch case statements"
+        errorLine1="            case R.id.appmanager_app_configure:"
+        errorLine2="                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/appmanager/AbstractAppManagerFragment.java"
+            line="644"
+            column="18"/>
+    </issue>
+
+    <issue
+        id="NonConstantResourceId"
+        message="Resource IDs will be non-final by default in Android Gradle Plugin version 8.0, avoid using them in switch case statements"
+        errorLine1="            case R.id.appmanager_app_openinstore:"
+        errorLine2="                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/appmanager/AbstractAppManagerFragment.java"
+            line="653"
+            column="18"/>
+    </issue>
+
+    <issue
+        id="NonConstantResourceId"
+        message="Resource IDs will be non-final by default in Android Gradle Plugin version 8.0, avoid using them in switch case statements"
+        errorLine1="            case R.id.appmanager_app_edit:"
+        errorLine2="                 ~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/appmanager/AbstractAppManagerFragment.java"
+            line="659"
+            column="18"/>
+    </issue>
+
+    <issue
+        id="NonConstantResourceId"
+        message="Resource IDs will be non-final by default in Android Gradle Plugin version 8.0, avoid using them in switch case statements"
+        errorLine1="            case R.id.charts_fetch_activity_data:"
+        errorLine2="                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/AbstractChartsActivity.java"
+            line="273"
+            column="18"/>
+    </issue>
+
+    <issue
+        id="NonConstantResourceId"
+        message="Resource IDs will be non-final by default in Android Gradle Plugin version 8.0, avoid using them in switch case statements"
+        errorLine1="            case R.id.prefs_charts_menu:"
+        errorLine2="                 ~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/AbstractChartsActivity.java"
+            line="276"
+            column="18"/>
+    </issue>
+
+    <issue
+        id="NonConstantResourceId"
+        message="Resource IDs will be non-final by default in Android Gradle Plugin version 8.0, avoid using them in switch case statements"
+        errorLine1="            case R.id.activity_action_manage_timestamp:"
+        errorLine2="                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ActivitySummariesActivity.java"
+            line="135"
+            column="18"/>
+    </issue>
+
+    <issue
+        id="NonConstantResourceId"
+        message="Resource IDs will be non-final by default in Android Gradle Plugin version 8.0, avoid using them in switch case statements"
+        errorLine1="            case R.id.activity_action_filter:"
+        errorLine2="                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ActivitySummariesActivity.java"
+            line="139"
+            column="18"/>
+    </issue>
+
+    <issue
+        id="NonConstantResourceId"
+        message="Resource IDs will be non-final by default in Android Gradle Plugin version 8.0, avoid using them in switch case statements"
+        errorLine1="                    case R.id.activity_action_delete:"
+        errorLine2="                         ~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ActivitySummariesActivity.java"
+            line="236"
+            column="26"/>
+    </issue>
+
+    <issue
+        id="NonConstantResourceId"
+        message="Resource IDs will be non-final by default in Android Gradle Plugin version 8.0, avoid using them in switch case statements"
+        errorLine1="                    case R.id.activity_action_export:"
+        errorLine2="                         ~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ActivitySummariesActivity.java"
+            line="258"
+            column="26"/>
+    </issue>
+
+    <issue
+        id="NonConstantResourceId"
+        message="Resource IDs will be non-final by default in Android Gradle Plugin version 8.0, avoid using them in switch case statements"
+        errorLine1="                    case R.id.activity_action_select_all:"
+        errorLine2="                         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ActivitySummariesActivity.java"
+            line="278"
+            column="26"/>
+    </issue>
+
+    <issue
+        id="NonConstantResourceId"
+        message="Resource IDs will be non-final by default in Android Gradle Plugin version 8.0, avoid using them in switch case statements"
+        errorLine1="                    case R.id.activity_action_addto_filter:"
+        errorLine2="                         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ActivitySummariesActivity.java"
+            line="283"
+            column="26"/>
+    </issue>
+
+    <issue
+        id="NonConstantResourceId"
+        message="Resource IDs will be non-final by default in Android Gradle Plugin version 8.0, avoid using them in switch case statements"
+        errorLine1="            case R.id.activity_action_take_screenshot:"
+        errorLine2="                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ActivitySummaryDetail.java"
+            line="563"
+            column="18"/>
+    </issue>
+
+    <issue
+        id="NonConstantResourceId"
+        message="Resource IDs will be non-final by default in Android Gradle Plugin version 8.0, avoid using them in switch case statements"
+        errorLine1="            case R.id.activity_action_show_gpx:"
+        errorLine2="                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ActivitySummaryDetail.java"
+            line="566"
+            column="18"/>
+    </issue>
+
+    <issue
+        id="NonConstantResourceId"
+        message="Resource IDs will be non-final by default in Android Gradle Plugin version 8.0, avoid using them in switch case statements"
+        errorLine1="            case R.id.activity_action_share_gpx:"
+        errorLine2="                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ActivitySummaryDetail.java"
+            line="569"
+            column="18"/>
+    </issue>
+
+    <issue
+        id="NonConstantResourceId"
+        message="Resource IDs will be non-final by default in Android Gradle Plugin version 8.0, avoid using them in switch case statements"
+        errorLine1="            case R.id.activity_action_dev_share_raw_summary:"
+        errorLine2="                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ActivitySummaryDetail.java"
+            line="572"
+            column="18"/>
+    </issue>
+
+    <issue
+        id="NonConstantResourceId"
+        message="Resource IDs will be non-final by default in Android Gradle Plugin version 8.0, avoid using them in switch case statements"
+        errorLine1="            case R.id.activity_action_dev_share_raw_details:"
+        errorLine2="                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ActivitySummaryDetail.java"
+            line="575"
+            column="18"/>
+    </issue>
+
+    <issue
+        id="NonConstantResourceId"
+        message="Resource IDs will be non-final by default in Android Gradle Plugin version 8.0, avoid using them in switch case statements"
+        errorLine1="            case R.id.check_all_applications:"
+        errorLine2="                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AppBlacklistActivity.java"
+            line="82"
+            column="18"/>
+    </issue>
+
+    <issue
+        id="NonConstantResourceId"
+        message="Resource IDs will be non-final by default in Android Gradle Plugin version 8.0, avoid using them in switch case statements"
+        errorLine1="            case R.id.uncheck_all_applications:"
+        errorLine2="                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AppBlacklistActivity.java"
+            line="85"
+            column="18"/>
+    </issue>
+
+    <issue
+        id="NonConstantResourceId"
+        message="Resource IDs will be non-final by default in Android Gradle Plugin version 8.0, avoid using them in switch case statements"
+        errorLine1="            case R.id.action_settings:"
+        errorLine2="                 ~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ControlCenterv2.java"
+            line="430"
+            column="18"/>
+    </issue>
+
+    <issue
+        id="NonConstantResourceId"
+        message="Resource IDs will be non-final by default in Android Gradle Plugin version 8.0, avoid using them in switch case statements"
+        errorLine1="            case R.id.action_debug:"
+        errorLine2="                 ~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ControlCenterv2.java"
+            line="434"
+            column="18"/>
+    </issue>
+
+    <issue
+        id="NonConstantResourceId"
+        message="Resource IDs will be non-final by default in Android Gradle Plugin version 8.0, avoid using them in switch case statements"
+        errorLine1="            case R.id.action_data_management:"
+        errorLine2="                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ControlCenterv2.java"
+            line="438"
+            column="18"/>
+    </issue>
+
+    <issue
+        id="NonConstantResourceId"
+        message="Resource IDs will be non-final by default in Android Gradle Plugin version 8.0, avoid using them in switch case statements"
+        errorLine1="            case R.id.action_notification_management:"
+        errorLine2="                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ControlCenterv2.java"
+            line="442"
+            column="18"/>
+    </issue>
+
+    <issue
+        id="NonConstantResourceId"
+        message="Resource IDs will be non-final by default in Android Gradle Plugin version 8.0, avoid using them in switch case statements"
+        errorLine1="            case R.id.device_action_discover:"
+        errorLine2="                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ControlCenterv2.java"
+            line="446"
+            column="18"/>
+    </issue>
+
+    <issue
+        id="NonConstantResourceId"
+        message="Resource IDs will be non-final by default in Android Gradle Plugin version 8.0, avoid using them in switch case statements"
+        errorLine1="            case R.id.action_quit:"
+        errorLine2="                 ~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ControlCenterv2.java"
+            line="449"
+            column="18"/>
+    </issue>
+
+    <issue
+        id="NonConstantResourceId"
+        message="Resource IDs will be non-final by default in Android Gradle Plugin version 8.0, avoid using them in switch case statements"
+        errorLine1="            case R.id.donation_link:"
+        errorLine2="                 ~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ControlCenterv2.java"
+            line="452"
+            column="18"/>
+    </issue>
+
+    <issue
+        id="NonConstantResourceId"
+        message="Resource IDs will be non-final by default in Android Gradle Plugin version 8.0, avoid using them in switch case statements"
+        errorLine1="            case R.id.external_changelog:"
+        errorLine2="                 ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ControlCenterv2.java"
+            line="457"
+            column="18"/>
+    </issue>
+
+    <issue
+        id="NonConstantResourceId"
+        message="Resource IDs will be non-final by default in Android Gradle Plugin version 8.0, avoid using them in switch case statements"
+        errorLine1="            case R.id.about:"
+        errorLine2="                 ~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ControlCenterv2.java"
+            line="469"
+            column="18"/>
+    </issue>
+
+    <issue
+        id="NonConstantResourceId"
+        message="Resource IDs will be non-final by default in Android Gradle Plugin version 8.0, avoid using them in switch case statements"
+        errorLine1="                    case R.id.controlcenter_device_submenu_connect:"
+        errorLine2="                         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/GBDeviceAdapterv2.java"
+            line="853"
+            column="26"/>
+    </issue>
+
+    <issue
+        id="NonConstantResourceId"
+        message="Resource IDs will be non-final by default in Android Gradle Plugin version 8.0, avoid using them in switch case statements"
+        errorLine1="                    case R.id.controlcenter_device_submenu_disconnect:"
+        errorLine2="                         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/GBDeviceAdapterv2.java"
+            line="859"
+            column="26"/>
+    </issue>
+
+    <issue
+        id="NonConstantResourceId"
+        message="Resource IDs will be non-final by default in Android Gradle Plugin version 8.0, avoid using them in switch case statements"
+        errorLine1="                    case R.id.controlcenter_device_submenu_set_alias:"
+        errorLine2="                         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/GBDeviceAdapterv2.java"
+            line="866"
+            column="26"/>
+    </issue>
+
+    <issue
+        id="NonConstantResourceId"
+        message="Resource IDs will be non-final by default in Android Gradle Plugin version 8.0, avoid using them in switch case statements"
+        errorLine1="                    case R.id.controlcenter_device_submenu_set_preferences:"
+        errorLine2="                         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/GBDeviceAdapterv2.java"
+            line="869"
+            column="26"/>
+    </issue>
+
+    <issue
+        id="NonConstantResourceId"
+        message="Resource IDs will be non-final by default in Android Gradle Plugin version 8.0, avoid using them in switch case statements"
+        errorLine1="                    case R.id.controlcenter_device_submenu_remove:"
+        errorLine2="                         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/GBDeviceAdapterv2.java"
+            line="872"
+            column="26"/>
+    </issue>
+
+    <issue
+        id="NonConstantResourceId"
+        message="Resource IDs will be non-final by default in Android Gradle Plugin version 8.0, avoid using them in switch case statements"
+        errorLine1="                    case R.id.controlcenter_device_submenu_show_details:"
+        errorLine2="                         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/GBDeviceAdapterv2.java"
+            line="875"
+            column="26"/>
+    </issue>
+
+    <issue
+        id="NonConstantResourceId"
+        message="Resource IDs will be non-final by default in Android Gradle Plugin version 8.0, avoid using them in switch case statements"
+        errorLine1="                    case R.id.controlcenter_device_submenu_set_parent_folder:"
+        errorLine2="                         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/GBDeviceAdapterv2.java"
+            line="893"
+            column="26"/>
+    </issue>
+
+    <issue
+        id="NonConstantResourceId"
+        message="Resource IDs will be non-final by default in Android Gradle Plugin version 8.0, avoid using them in switch case statements"
+        errorLine1="                    case R.id.controlcenter_device_submenu_installer:"
+        errorLine2="                         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/GBDeviceAdapterv2.java"
+            line="896"
+            column="26"/>
+    </issue>
+
+    <issue
+        id="NonConstantResourceId"
+        message="Resource IDs will be non-final by default in Android Gradle Plugin version 8.0, avoid using them in switch case statements"
+        errorLine1="            case R.id.alarm1:"
+        errorLine2="                 ~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/WidgetAlarmsActivity.java"
+            line="87"
+            column="18"/>
+    </issue>
+
+    <issue
+        id="NonConstantResourceId"
+        message="Resource IDs will be non-final by default in Android Gradle Plugin version 8.0, avoid using them in switch case statements"
+        errorLine1="            case R.id.alarm2:"
+        errorLine2="                 ~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/WidgetAlarmsActivity.java"
+            line="90"
+            column="18"/>
+    </issue>
+
+    <issue
+        id="NonConstantResourceId"
+        message="Resource IDs will be non-final by default in Android Gradle Plugin version 8.0, avoid using them in switch case statements"
+        errorLine1="            case R.id.alarm3:"
+        errorLine2="                 ~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/WidgetAlarmsActivity.java"
+            line="93"
+            column="18"/>
+    </issue>
+
+    <issue
+        id="NonConstantResourceId"
+        message="Resource IDs will be non-final by default in Android Gradle Plugin version 8.0, avoid using them in switch case statements"
+        errorLine1="            case R.id.alarm4:"
+        errorLine2="                 ~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/WidgetAlarmsActivity.java"
+            line="96"
+            column="18"/>
+    </issue>
+
+    <issue
+        id="NonConstantResourceId"
+        message="Resource IDs will be non-final by default in Android Gradle Plugin version 8.0, avoid using them in switch case statements"
+        errorLine1="            case R.id.alarm5:"
+        errorLine2="                 ~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/WidgetAlarmsActivity.java"
+            line="99"
+            column="18"/>
+    </issue>
+
+    <issue
+        id="VectorRaster"
+        message="Limit vector icons sizes to 200脳200 to keep icon drawing fast; see https://developer.android.com/studio/write/vector-asset-studio#when for more"
+        errorLine1="    android:height=&quot;300dp&quot;"
+        errorLine2="                    ~~~~~">
+        <location
+            file="src/main/res/drawable/gadgetbridge_img.xml"
+            line="4"
+            column="21"/>
+    </issue>
+
+    <issue
+        id="LockedOrientationActivity"
+        message="Expecting `android:screenOrientation=&quot;unspecified&quot;` or `&quot;fullSensor&quot;` for this activity so the user can use the application in any orientation and provide a great experience on Chrome OS devices"
+        errorLine1="            android:screenOrientation=&quot;portrait&quot;"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/AndroidManifest.xml"
+            line="513"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="LockedOrientationActivity"
+        message="Expecting `android:screenOrientation=&quot;unspecified&quot;` or `&quot;fullSensor&quot;` for this activity so the user can use the application in any orientation and provide a great experience on Chrome OS devices"
+        errorLine1="            android:screenOrientation=&quot;portrait&quot; />"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/AndroidManifest.xml"
+            line="614"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="LockedOrientationActivity"
+        message="Expecting `android:screenOrientation=&quot;unspecified&quot;` or `&quot;fullSensor&quot;` for this activity so the user can use the application in any orientation and provide a great experience on Chrome OS devices"
+        errorLine1="            android:screenOrientation=&quot;portrait&quot;"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/AndroidManifest.xml"
+            line="619"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="LockedOrientationActivity"
+        message="Expecting `android:screenOrientation=&quot;unspecified&quot;` or `&quot;fullSensor&quot;` for this activity so the user can use the application in any orientation and provide a great experience on Chrome OS devices"
+        errorLine1="            android:screenOrientation=&quot;portrait&quot;"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/AndroidManifest.xml"
+            line="625"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="LockedOrientationActivity"
+        message="Expecting `android:screenOrientation=&quot;unspecified&quot;` or `&quot;fullSensor&quot;` for this activity so the user can use the application in any orientation and provide a great experience on Chrome OS devices"
+        errorLine1="            android:screenOrientation=&quot;portrait&quot;"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/AndroidManifest.xml"
+            line="631"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="LockedOrientationActivity"
+        message="Expecting `android:screenOrientation=&quot;unspecified&quot;` or `&quot;fullSensor&quot;` for this activity so the user can use the application in any orientation and provide a great experience on Chrome OS devices"
+        errorLine1="            android:screenOrientation=&quot;portrait&quot;"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/AndroidManifest.xml"
+            line="637"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="LockedOrientationActivity"
+        message="Expecting `android:screenOrientation=&quot;unspecified&quot;` or `&quot;fullSensor&quot;` for this activity so the user can use the application in any orientation and provide a great experience on Chrome OS devices"
+        errorLine1="            android:screenOrientation=&quot;portrait&quot;"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/AndroidManifest.xml"
+            line="786"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="LockedOrientationActivity"
+        message="Expecting `android:screenOrientation=&quot;unspecified&quot;` or `&quot;fullSensor&quot;` for this activity so the user can use the application in any orientation and provide a great experience on Chrome OS devices"
+        errorLine1="            android:screenOrientation=&quot;portrait&quot;"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/AndroidManifest.xml"
+            line="813"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="RestrictedApi"
+        message="MenuItemImpl.setVisible can only be called from within the same library group prefix (referenced groupId=`androidx.appcompat` with prefix androidx from groupId=`Gadgetbridge`)"
+        errorLine1="            if (v != null) v.setVisible(false);"
+        errorLine2="                             ~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ControlCenterv2.java"
+            line="215"
+            column="30"/>
+    </issue>
+
+    <issue
+        id="PrivateResource"
+        message="Overriding `@string/not_set` which is marked as private in androidx.preference:preference:1.2.1. If deliberate, use tools:override=&quot;true&quot;, otherwise pick a different name."
+        errorLine1="    &lt;string name=&quot;not_set&quot;>鏈缃�&lt;/string>"
+        errorLine2="                  ~~~~~~~">
+        <location
+            file="src/main/res/values-zh-rCN/strings.xml"
+            line="2578"
+            column="19"/>
+    </issue>
+
+    <issue
+        id="PrivateResource"
+        message="Overriding `@string/not_set` which is marked as private in androidx.preference:preference:1.2.1. If deliberate, use tools:override=&quot;true&quot;, otherwise pick a different name."
+        errorLine1="    &lt;string name=&quot;not_set&quot;>No fijado&lt;/string>"
+        errorLine2="                  ~~~~~~~">
+        <location
+            file="src/main/res/values-es/strings.xml"
+            line="2580"
+            column="19"/>
+    </issue>
+
+    <issue
+        id="PrivateResource"
+        message="Overriding `@string/not_set` which is marked as private in androidx.preference:preference:1.2.1. If deliberate, use tools:override=&quot;true&quot;, otherwise pick a different name."
+        errorLine1="    &lt;string name=&quot;not_set&quot;>Non configur茅&lt;/string>"
+        errorLine2="                  ~~~~~~~">
+        <location
+            file="src/main/res/values-fr/strings.xml"
+            line="2585"
+            column="19"/>
+    </issue>
+
+    <issue
+        id="PrivateResource"
+        message="Overriding `@string/not_set` which is marked as private in androidx.preference:preference:1.2.1. If deliberate, use tools:override=&quot;true&quot;, otherwise pick a different name."
+        errorLine1="    &lt;string name=&quot;not_set&quot;>Niet ingesteld&lt;/string>"
+        errorLine2="                  ~~~~~~~">
+        <location
+            file="src/main/res/values-nl/strings.xml"
+            line="2587"
+            column="19"/>
+    </issue>
+
+    <issue
+        id="PrivateResource"
+        message="Overriding `@string/not_set` which is marked as private in androidx.preference:preference:1.2.1. If deliberate, use tools:override=&quot;true&quot;, otherwise pick a different name."
+        errorLine1="    &lt;string name=&quot;not_set&quot;>賱賲 賷鬲賲 鬲毓賷賷賳賴&lt;/string>"
+        errorLine2="                  ~~~~~~~">
+        <location
+            file="src/main/res/values-ar/strings.xml"
+            line="2591"
+            column="19"/>
+    </issue>
+
+    <issue
+        id="PrivateResource"
+        message="Overriding `@string/not_set` which is marked as private in androidx.preference:preference:1.2.1. If deliberate, use tools:override=&quot;true&quot;, otherwise pick a different name."
+        errorLine1="    &lt;string name=&quot;not_set&quot;>Nicht festgelegt&lt;/string>"
+        errorLine2="                  ~~~~~~~">
+        <location
+            file="src/main/res/values-de/strings.xml"
+            line="2592"
+            column="19"/>
+    </issue>
+
+    <issue
+        id="PrivateResource"
+        message="Overriding `@string/not_set` which is marked as private in androidx.preference:preference:1.2.1. If deliberate, use tools:override=&quot;true&quot;, otherwise pick a different name."
+        errorLine1="    &lt;string name=&quot;not_set&quot;>袧械 蟹邪写邪薪芯&lt;/string>"
+        errorLine2="                  ~~~~~~~">
+        <location
+            file="src/main/res/values-ru/strings.xml"
+            line="2597"
+            column="19"/>
+    </issue>
+
+    <issue
+        id="PrivateResource"
+        message="Overriding `@string/not_set` which is marked as private in androidx.preference:preference:1.2.1. If deliberate, use tools:override=&quot;true&quot;, otherwise pick a different name."
+        errorLine1="    &lt;string name=&quot;not_set&quot;>袧械 薪邪谢邪褕褌芯胁邪薪芯&lt;/string>"
+        errorLine2="                  ~~~~~~~">
+        <location
+            file="src/main/res/values-uk/strings.xml"
+            line="2604"
+            column="19"/>
+    </issue>
+
+    <issue
+        id="PrivateResource"
+        message="Overriding `@string/not_set` which is marked as private in androidx.preference:preference:1.2.1. If deliberate, use tools:override=&quot;true&quot;, otherwise pick a different name."
+        errorLine1="    &lt;string name=&quot;not_set&quot;>Nie ustawiono&lt;/string>"
+        errorLine2="                  ~~~~~~~">
+        <location
+            file="src/main/res/values-pl/strings.xml"
+            line="2618"
+            column="19"/>
+    </issue>
+
+    <issue
+        id="PrivateResource"
+        message="Overriding `@string/not_set` which is marked as private in androidx.preference:preference:1.2.1. If deliberate, use tools:override=&quot;true&quot;, otherwise pick a different name."
+        errorLine1="    &lt;string name=&quot;not_set&quot;>Not set&lt;/string>"
+        errorLine2="                  ~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="2638"
+            column="19"/>
+    </issue>
+
+    <issue
+        id="SpUsage"
+        message="Should use &quot;`sp`&quot; instead of &quot;`dp`&quot; for text sizes"
+        errorLine1="            android:textSize=&quot;20dp&quot; />"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_qhybrid_hr_settings.xml"
+            line="19"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="SpUsage"
+        message="Should use &quot;`sp`&quot; instead of &quot;`dp`&quot; for text sizes"
+        errorLine1="            android:textSize=&quot;20dp&quot; />"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_qhybrid_hr_settings.xml"
+            line="26"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="SpUsage"
+        message="Should use &quot;`sp`&quot; instead of &quot;`dp`&quot; for text sizes"
+        errorLine1="            android:textSize=&quot;20dp&quot; />"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_qhybrid_hr_settings.xml"
+            line="33"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="SpUsage"
+        message="Should use &quot;`sp`&quot; instead of &quot;`dp`&quot; for text sizes"
+        errorLine1="        android:textSize=&quot;20dp&quot; />"
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_qhybrid_hr_settings.xml"
+            line="46"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="SpUsage"
+        message="Should use &quot;`sp`&quot; instead of &quot;`dp`&quot; for text sizes"
+        errorLine1="        android:textSize=&quot;20dp&quot; />"
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_qhybrid_hr_settings.xml"
+            line="53"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="SpUsage"
+        message="Should use &quot;`sp`&quot; instead of &quot;`dp`&quot; for text sizes"
+        errorLine1="        android:textSize=&quot;20dp&quot; />"
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_qhybrid_hr_settings.xml"
+            line="60"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="SpUsage"
+        message="Should use &quot;`sp`&quot; instead of &quot;`dp`&quot; for text sizes"
+        errorLine1="        android:textSize=&quot;20dp&quot; />"
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_qhybrid_hr_settings.xml"
+            line="67"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="SpUsage"
+        message="Should use &quot;`sp`&quot; instead of &quot;`dp`&quot; for text sizes (`@dimen/um25_value_text_size` is defined as `60dp` in `values/dimens.xml`"
+        errorLine1="            android:textSize=&quot;@dimen/um25_value_text_size&quot; />"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_um25_data.xml"
+            line="18"
+            column="13"/>
+        <location
+            file="src/main/res/values/dimens.xml"
+            line="17"
+            column="40"
+            message="This dp dimension is used as a text size"/>
+    </issue>
+
+    <issue
+        id="SpUsage"
+        message="Should use &quot;`sp`&quot; instead of &quot;`dp`&quot; for text sizes (`@dimen/um25_value_text_size` is defined as `60dp` in `values/dimens.xml`"
+        errorLine1="                android:textSize=&quot;@dimen/um25_value_text_size&quot; />"
+        errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_um25_data.xml"
+            line="32"
+            column="17"/>
+        <location
+            file="src/main/res/values/dimens.xml"
+            line="17"
+            column="40"
+            message="This dp dimension is used as a text size"/>
+    </issue>
+
+    <issue
+        id="SpUsage"
+        message="Should use &quot;`sp`&quot; instead of &quot;`dp`&quot; for text sizes (`@dimen/um25_value_text_size_small` is defined as `30dp` in `values/dimens.xml`"
+        errorLine1="                android:textSize=&quot;@dimen/um25_value_text_size_small&quot; />"
+        errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_um25_data.xml"
+            line="39"
+            column="17"/>
+        <location
+            file="src/main/res/values/dimens.xml"
+            line="18"
+            column="46"
+            message="This dp dimension is used as a text size"/>
+    </issue>
+
+    <issue
+        id="SpUsage"
+        message="Should use &quot;`sp`&quot; instead of &quot;`dp`&quot; for text sizes (`@dimen/um25_value_text_size` is defined as `60dp` in `values/dimens.xml`"
+        errorLine1="                android:textSize=&quot;@dimen/um25_value_text_size&quot; />"
+        errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_um25_data.xml"
+            line="56"
+            column="17"/>
+        <location
+            file="src/main/res/values/dimens.xml"
+            line="17"
+            column="40"
+            message="This dp dimension is used as a text size"/>
+    </issue>
+
+    <issue
+        id="SpUsage"
+        message="Should use &quot;`sp`&quot; instead of &quot;`dp`&quot; for text sizes (`@dimen/um25_value_text_size_small` is defined as `30dp` in `values/dimens.xml`"
+        errorLine1="                android:textSize=&quot;@dimen/um25_value_text_size_small&quot; />"
+        errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_um25_data.xml"
+            line="63"
+            column="17"/>
+        <location
+            file="src/main/res/values/dimens.xml"
+            line="18"
+            column="46"
+            message="This dp dimension is used as a text size"/>
+    </issue>
+
+    <issue
+        id="SpUsage"
+        message="Should use &quot;`sp`&quot; instead of &quot;`dp`&quot; for text sizes (`@dimen/um25_value_text_size` is defined as `60dp` in `values/dimens.xml`"
+        errorLine1="            android:textSize=&quot;@dimen/um25_value_text_size&quot; />"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_um25_data.xml"
+            line="74"
+            column="13"/>
+        <location
+            file="src/main/res/values/dimens.xml"
+            line="17"
+            column="40"
+            message="This dp dimension is used as a text size"/>
+    </issue>
+
+    <issue
+        id="SpUsage"
+        message="Should use &quot;`sp`&quot; instead of &quot;`dp`&quot; for text sizes (`@dimen/um25_value_text_size_small` is defined as `30dp` in `values/dimens.xml`"
+        errorLine1="                android:textSize=&quot;@dimen/um25_value_text_size_small&quot;"
+        errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_um25_data.xml"
+            line="87"
+            column="17"/>
+        <location
+            file="src/main/res/values/dimens.xml"
+            line="18"
+            column="46"
+            message="This dp dimension is used as a text size"/>
+    </issue>
+
+    <issue
+        id="SpUsage"
+        message="Should use &quot;`sp`&quot; instead of &quot;`dp`&quot; for text sizes (`@dimen/um25_value_text_size_small` is defined as `30dp` in `values/dimens.xml`"
+        errorLine1="                android:textSize=&quot;@dimen/um25_value_text_size_small&quot; />"
+        errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_um25_data.xml"
+            line="95"
+            column="17"/>
+        <location
+            file="src/main/res/values/dimens.xml"
+            line="18"
+            column="46"
+            message="This dp dimension is used as a text size"/>
+    </issue>
+
+    <issue
+        id="SpUsage"
+        message="Should use &quot;`sp`&quot; instead of &quot;`dp`&quot; for text sizes"
+        errorLine1="        android:textSize=&quot;20dp&quot; />"
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_weather_notification.xml"
+            line="14"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="SpUsage"
+        message="Should use &quot;`sp`&quot; instead of &quot;`dp`&quot; for text sizes"
+        errorLine1="        android:textSize=&quot;25dp&quot;"
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/fossil_hr_row_installed_app.xml"
+            line="10"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="SpUsage"
+        message="Should use &quot;`sp`&quot; instead of &quot;`dp`&quot; for text sizes"
+        errorLine1="        android:textSize=&quot;20dp&quot;"
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/qhybrid_app_view.xml"
+            line="19"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="SpUsage"
+        message="Should use &quot;`sp`&quot; instead of &quot;`dp`&quot; for text sizes"
+        errorLine1="        android:textSize=&quot;20dp&quot;"
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/qhybrid_package_settings_item.xml"
+            line="19"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="SwitchIntDef"
+        message="Switch statement on an `int` with known associated constant missing case `PlaybackState.STATE_BUFFERING`, `PlaybackState.STATE_CONNECTING`, `PlaybackState.STATE_ERROR`, `PlaybackState.STATE_FAST_FORWARDING`, `PlaybackState.STATE_NONE`, `PlaybackState.STATE_REWINDING`, `PlaybackState.STATE_SKIPPING_TO_NEXT`, `PlaybackState.STATE_SKIPPING_TO_PREVIOUS`, `PlaybackState.STATE_SKIPPING_TO_QUEUE_ITEM`"
+        errorLine1="            switch (s.getState()) {"
+        errorLine2="            ~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/util/MediaManager.java"
+            line="151"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="DiscouragedApi"
+        message="Use of this function is discouraged because resource reflection makes it harder to perform build optimizations and compile-time verification of code. It is much more efficient to retrieve resources by identifier (e.g. `R.foo.bar`) than by name (e.g. `getIdentifier(&quot;bar&quot;, &quot;foo&quot;, null)`)."
+        errorLine1="        int resId = getResources().getIdentifier(aString, &quot;string&quot;, packageName);"
+        errorLine2="                                   ~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ActivitySummaryDetail.java"
+            line="547"
+            column="36"/>
+    </issue>
+
+    <issue
+        id="DiscouragedApi"
+        message="Use of this function is discouraged because resource reflection makes it harder to perform build optimizations and compile-time verification of code. It is much more efficient to retrieve resources by identifier (e.g. `R.foo.bar`) than by name (e.g. `getIdentifier(&quot;bar&quot;, &quot;foo&quot;, null)`)."
+        errorLine1="        final int stringId = context.getResources().getIdentifier(stringName, &quot;string&quot;, context.getPackageName());"
+        errorLine2="                                                    ~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/headphones/protocol/impl/v1/params/NoiseCancellingOptimizerStatus.java"
+            line="43"
+            column="53"/>
+    </issue>
+
+    <issue
+        id="DiscouragedApi"
+        message="Use of this function is discouraged because resource reflection makes it harder to perform build optimizations and compile-time verification of code. It is much more efficient to retrieve resources by identifier (e.g. `R.foo.bar`) than by name (e.g. `getIdentifier(&quot;bar&quot;, &quot;foo&quot;, null)`)."
+        errorLine1="        final int stringId = getContext().getResources().getIdentifier(&quot;activity_type_&quot; + workoutType, &quot;string&quot;, getContext().getPackageName());"
+        errorLine2="                                                         ~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/WithingsSteelHRDeviceSupport.java"
+            line="692"
+            column="58"/>
+    </issue>
+
+    <issue
+        id="UseCompatLoadingForDrawables"
+        message="Use `ResourcesCompat.getDrawable()`"
+        errorLine1="        entries.add(new PieEntry((float) value, context.getResources().getDrawable(R.drawable.ic_star_gold)));"
+        errorLine2="                                                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ActivityListingAdapter.java"
+            line="165"
+            column="49"/>
+    </issue>
+
+    <issue
+        id="UseCompatLoadingForDrawables"
+        message="Use `ResourcesCompat.getDrawable()`"
+        errorLine1="        images.add(new NotificationImage(&quot;icIncomingCall.icon&quot;, NotificationImage.getEncodedIconFromDrawable(getContext().getResources().getDrawable(R.drawable.ic_phone_outline)), 24, 24));"
+        errorLine2="                                                                                                             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/adapter/fossil_hr/FossilHRWatchAdapter.java"
+            line="580"
+            column="110"/>
+    </issue>
+
+    <issue
+        id="UseCompatLoadingForDrawables"
+        message="Use `ResourcesCompat.getDrawable()`"
+        errorLine1="        images.add(new NotificationImage(&quot;icMissedCall.icon&quot;, NotificationImage.getEncodedIconFromDrawable(getContext().getResources().getDrawable(R.drawable.ic_phone_missed_outline)), 24, 24));"
+        errorLine2="                                                                                                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/adapter/fossil_hr/FossilHRWatchAdapter.java"
+            line="581"
+            column="108"/>
+    </issue>
+
+    <issue
+        id="UseCompatLoadingForDrawables"
+        message="Use `ResourcesCompat.getDrawable()`"
+        errorLine1="        images.add(new NotificationImage(&quot;icMessage.icon&quot;, NotificationImage.getEncodedIconFromDrawable(getContext().getResources().getDrawable(R.drawable.ic_message_outline)), 24, 24));"
+        errorLine2="                                                                                                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/adapter/fossil_hr/FossilHRWatchAdapter.java"
+            line="582"
+            column="105"/>
+    </issue>
+
+    <issue
+        id="UseCompatLoadingForDrawables"
+        message="Use `ResourcesCompat.getDrawable()`"
+        errorLine1="        images.add(new NotificationImage(&quot;general_white.bin&quot;, NotificationImage.getEncodedIconFromDrawable(getContext().getResources().getDrawable(R.drawable.ic_alert_circle_outline)), 24, 24));"
+        errorLine2="                                                                                                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/adapter/fossil_hr/FossilHRWatchAdapter.java"
+            line="583"
+            column="108"/>
+    </issue>
+
+    <issue
+        id="UseCompatLoadingForDrawables"
+        message="Use `ResourcesCompat.getDrawable()`"
+        errorLine1="            NotificationImage quickReplyIcon = new NotificationImage(&quot;icMessage.icon&quot;, NotificationImage.getEncodedIconFromDrawable(getContext().getResources().getDrawable(R.drawable.ic_message_outline)), 24, 24);"
+        errorLine2="                                                                                                                                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/adapter/fossil_hr/FossilHRWatchAdapter.java"
+            line="625"
+            column="133"/>
+    </issue>
+
+    <issue
+        id="UseCompatLoadingForDrawables"
+        message="Use `ResourcesCompat.getDrawable()`"
+        errorLine1="        entries.add(new PieEntry((float) value, context.getResources().getDrawable(R.drawable.ic_star_gold)));"
+        errorLine2="                                                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/GBDeviceAdapterv2.java"
+            line="1388"
+            column="49"/>
+    </issue>
+
+    <issue
+        id="UseCompatLoadingForDrawables"
+        message="Use `ResourcesCompat.getDrawable()`"
+        errorLine1="                        icon = sourcePackageContext.getResources().getDrawable(notificationSpec.iconId);"
+        errorLine2="                               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/message/incoming/NotificationRequestHandler.java"
+            line="81"
+            column="32"/>
+    </issue>
+
+    <issue
+        id="UseCompatLoadingForDrawables"
+        message="Use `AppCompatResources.getDrawable()`"
+        errorLine1="        Drawable drawable = getContext().getDrawable(drawableId);"
+        errorLine2="                            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/WithingsSteelHRDeviceSupport.java"
+            line="703"
+            column="29"/>
+    </issue>
+
+    <issue
+        id="UsingOnClickInXml"
+        message="Use databinding or explicit wiring of click listener in code"
+        errorLine1="            android:onClick=&quot;onClick&quot;"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/widget_alarms_activity_list.xml"
+            line="63"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UsingOnClickInXml"
+        message="Use databinding or explicit wiring of click listener in code"
+        errorLine1="            android:onClick=&quot;onClick&quot;"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/widget_alarms_activity_list.xml"
+            line="71"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UsingOnClickInXml"
+        message="Use databinding or explicit wiring of click listener in code"
+        errorLine1="            android:onClick=&quot;onClick&quot;"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/widget_alarms_activity_list.xml"
+            line="79"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UsingOnClickInXml"
+        message="Use databinding or explicit wiring of click listener in code"
+        errorLine1="            android:onClick=&quot;onClick&quot;"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/widget_alarms_activity_list.xml"
+            line="87"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UsingOnClickInXml"
+        message="Use databinding or explicit wiring of click listener in code"
+        errorLine1="            android:onClick=&quot;onClick&quot;"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/widget_alarms_activity_list.xml"
+            line="95"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="StringFormatInvalid"
+        message="Format string &apos;`fw_upgrade_notice_amazfitcor2`&apos; is not a valid format string so it should not be passed to `String.format`"
+        errorLine1="        return mContext.getString(R.string.fw_upgrade_notice_amazfitcor2, helper.getHumanFirmwareVersion());"
+        errorLine2="               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitcor2/AmazfitCor2FWInstallHandler.java"
+            line="37"
+            column="16"/>
+        <location
+            file="src/main/res/values-ja/strings.xml"
+            line="612"
+            column="5"
+            message="This definition does not require arguments"/>
+    </issue>
+
+    <issue
+        id="StringFormatInvalid"
+        message="Incorrect formatting string `notif_battery_low_extended`; missing conversion character in &apos;`%1$ a`&apos;?"
+        errorLine1="    &lt;string name=&quot;notif_battery_low_extended&quot;>%1$ akkumul谩tora mer眉l: %2$s&lt;/string>"
+        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values-hu/strings.xml"
+            line="462"
+            column="5"/>
+    </issue>
+
+    <issue
+        id="StringFormatInvalid"
+        message="Incorrect formatting string `withings_calibration_text_activity_target`; missing conversion character in &apos;`% a`&apos;?"
+        errorLine1="    &lt;string name=&quot;withings_calibration_text_activity_target&quot;>Zum Schluss richte den Aktivit盲tszeiger auf 100% aus. Bitte beachte, dass sich dieser Zeiger nur im Uhrzeigersinn bewegt.&lt;/string>"
+        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values-de/strings.xml"
+            line="2340"
+            column="5"/>
+    </issue>
+
+    <issue
+        id="StringFormatMatches"
+        message="Wrong argument type for formatting argument &apos;#1&apos; in `notif_battery_low_extended`: conversion is &apos;`a`&apos;, received `String` (argument #2 in method call)"
+        errorLine1="                                context.getString(R.string.notif_battery_low_extended, gbDevice.getAliasOrName(),"
+        errorLine2="                                                                                       ~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/AbstractDeviceSupport.java"
+            line="515"
+            column="88"/>
+        <location
+            file="src/main/res/values-hu/strings.xml"
+            line="462"
+            column="5"
+            message="Conflicting argument declaration here"/>
+    </issue>
+
+    <issue
+        id="Typos"
+        message="Did you mean &quot;custom_!&quot; instead of &quot;custom_1&quot;?"
+        errorLine1="        &lt;item>custom_1&lt;/item>"
+        errorLine2="              ^">
+        <location
+            file="src/main/res/values/arrays.xml"
+            line="3347"
+            column="15"/>
+    </issue>
+
+    <issue
+        id="Typos"
+        message="Did you mean &quot;after_!&quot; instead of &quot;after_1&quot;?"
+        errorLine1="        &lt;item>after_1_hour&lt;/item>"
+        errorLine2="              ^">
+        <location
+            file="src/main/res/values/arrays.xml"
+            line="3363"
+            column="15"/>
+    </issue>
+
+    <issue
+        id="Typos"
+        message="&quot;pertubar&quot; is a common misspelling; did you mean &quot;perturbar&quot;?"
+        errorLine1="    &lt;string name=&quot;pref_summary_notification_filter&quot;>Bloqueia todas as notifica莽玫es quando o modo N茫o pertubar est谩 ativo no telefone&lt;/string>"
+        errorLine2="                                                                                                     ^">
+        <location
+            file="src/main/res/values-pt-rBR/strings.xml"
+            line="71"
+            column="102"/>
+    </issue>
+
+    <issue
+        id="Typos"
+        message="&quot;Ziffernblatt&quot; is a common misspelling; did you mean &quot;Zifferblatt&quot;?"
+        errorLine1="    &lt;string name=&quot;kind_watchface&quot;>Ziffernblatt&lt;/string>"
+        errorLine2="                                  ^">
+        <location
+            file="src/main/res/values-de/strings.xml"
+            line="515"
+            column="35"/>
+    </issue>
+
+    <issue
+        id="Typos"
+        message="&quot;despues&quot; is a common misspelling; did you mean &quot;despu茅s&quot;?"
+        errorLine1="\nAseg煤rese de instalar el archivo .fw y, a continuaci贸n, el archivo .res. La pulsera se reiniciar谩 despues de instalar el archivo .fw. "
+        errorLine2="                                                                                                    ^">
+        <location
+            file="src/main/res/values-es/strings.xml"
+            line="677"
+            column="101"/>
+    </issue>
+
+    <issue
+        id="Typos"
+        message="&quot;ultimo&quot; is a common misspelling; did you mean &quot;煤ltimo&quot;?"
+        errorLine1="\nPor favor, aseg煤rese de instalar el archivo .fw primero, el archivo .res a continuaci贸n y por ultimo el archivo .gps. Su reloj se reiniciara despu茅s de instalar el archivo .fw."
+        errorLine2="                                                                                                ^">
+        <location
+            file="src/main/res/values-es/strings.xml"
+            line="793"
+            column="97"/>
+    </issue>
+
+    <issue
+        id="Typos"
+        message="&quot;Inicio&quot; is a common misspelling; did you mean &quot;In铆cio&quot;?"
+        errorLine1="    &lt;string name=&quot;activity_detail_start_label&quot;>Inicio&lt;/string>"
+        errorLine2="                                               ^">
+        <location
+            file="src/main/res/values-pt/strings.xml"
+            line="939"
+            column="48"/>
+    </issue>
+
+    <issue
+        id="Typos"
+        message="&quot;Ukranian&quot; is a common misspelling; did you mean &quot;Ukrainian&quot;?"
+        errorLine1="    &lt;string name=&quot;ukranian&quot;>Ukranian&lt;/string>"
+        errorLine2="                            ^">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1055"
+            column="29"/>
+    </issue>
+
+    <issue
+        id="Typos"
+        message="&quot;camera&quot; is a common misspelling; did you mean &quot;c芒mera&quot;?"
+        errorLine1="    &lt;string name=&quot;menuitem_takephoto&quot;>Controlo remoto de camera&lt;/string>"
+        errorLine2="                                                         ^">
+        <location
+            file="src/main/res/values-pt/strings.xml"
+            line="1110"
+            column="58"/>
+    </issue>
+
+    <issue
+        id="Typos"
+        message="&quot;construida&quot; is a common misspelling; did you mean &quot;constru铆da&quot;?"
+        errorLine1="    &lt;string name=&quot;about_description_banglejs_nopebble&quot;>App de Android para Bangle.js construida sobre o projeto Gadgetbridge, com acesso a Internet adicionado."
+        errorLine2="                                                                                     ^">
+        <location
+            file="src/main/res/values-pt-rBR/strings.xml"
+            line="1484"
+            column="86"/>
+    </issue>
+
+    <issue
+        id="Typos"
+        message="&quot;construida&quot; is a common misspelling; did you mean &quot;constru铆da&quot;?"
+        errorLine1="    &lt;string name=&quot;about_description_banglejs_main&quot;>App de Android para Bangle.js construida sobre o projeto Gadgetbridge, com acesso a Internet adicionado."
+        errorLine2="                                                                                 ^">
+        <location
+            file="src/main/res/values-pt-rBR/strings.xml"
+            line="1487"
+            column="82"/>
+    </issue>
+
+    <issue
+        id="Typos"
+        message="&quot;construida&quot; is a common misspelling; did you mean &quot;constru铆da&quot;?"
+        errorLine1="    &lt;string name=&quot;about_description_banglejs_main&quot;>Aplica莽茫o Android para Bangle.js, construida com base no projecto Gadgetbridge, com acesso 脿 Internet."
+        errorLine2="                                                                                     ^">
+        <location
+            file="src/main/res/values-pt/strings.xml"
+            line="1576"
+            column="86"/>
+    </issue>
+
+    <issue
+        id="Typos"
+        message="&quot;construida&quot; is a common misspelling; did you mean &quot;constru铆da&quot;?"
+        errorLine1="    &lt;string name=&quot;about_description_banglejs_nightly&quot;>Aplica莽茫o Android para Bangle.js, construida com base no projecto Gadgetbridge, com acesso 脿 Internet."
+        errorLine2="                                                                                        ^">
+        <location
+            file="src/main/res/values-pt/strings.xml"
+            line="1586"
+            column="89"/>
+    </issue>
+
+    <issue
+        id="Untranslatable"
+        message="The resource string &quot;mi2_prefs_button_press_broadcast_default_value&quot; is marked as translatable=&quot;false&quot;, but is translated to &quot;hr&quot; (Croatian) here"
+        errorLine1="    &lt;string name=&quot;mi2_prefs_button_press_broadcast_default_value&quot; translatable=&quot;false&quot;>nodomain.freeyourgadget.gadgetbridge.ButtonPressed&lt;/string>"
+        errorLine2="                                                                  ~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values-hr/strings.xml"
+            line="8"
+            column="67"/>
+    </issue>
+
+    <issue
+        id="Untranslatable"
+        message="The resource string &quot;mi2_prefs_button_press_broadcast_default_value&quot; is marked as translatable=&quot;false&quot;, but is translated to &quot;id&quot; (Indonesian) here"
+        errorLine1="    &lt;string name=&quot;mi2_prefs_button_press_broadcast_default_value&quot; translatable=&quot;false&quot;>nodomain.freeyourgadget.gadgetbridge.ButtonPressed&lt;/string>"
+        errorLine2="                                                                  ~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values-id/strings.xml"
+            line="27"
+            column="67"/>
+    </issue>
+
+    <issue
+        id="Untranslatable"
+        message="The resource string &quot;mi2_prefs_button_press_broadcast_default_value&quot; is marked as translatable=&quot;false&quot;, but is translated to &quot;zh&quot; (Chinese) here"
+        errorLine1="    &lt;string name=&quot;mi2_prefs_button_press_broadcast_default_value&quot; translatable=&quot;false&quot;>nodomain.freeyourgadget.gadgetbridge.ButtonPressed&lt;/string>"
+        errorLine2="                                                                  ~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values-zh-rTW/strings.xml"
+            line="28"
+            column="67"/>
+    </issue>
+
+    <issue
+        id="Untranslatable"
+        message="The resource string &quot;mi2_prefs_button_press_broadcast_default_value&quot; is marked as translatable=&quot;false&quot;, but is translated to &quot;fi&quot; (Finnish) here"
+        errorLine1="    &lt;string name=&quot;mi2_prefs_button_press_broadcast_default_value&quot; translatable=&quot;false&quot;>nodomain.freeyourgadget.gadgetbridge.ButtonPressed&lt;/string>"
+        errorLine2="                                                                  ~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values-fi/strings.xml"
+            line="38"
+            column="67"/>
+    </issue>
+
+    <issue
+        id="Untranslatable"
+        message="The resource string &quot;mi2_prefs_button_press_broadcast_default_value&quot; is marked as translatable=&quot;false&quot;, but is translated to &quot;my&quot; (Burmese) here"
+        errorLine1="    &lt;string name=&quot;mi2_prefs_button_press_broadcast_default_value&quot; translatable=&quot;false&quot;>nodomain.freeyourgadget.gadgetbridge.ButtonPressed&lt;/string>"
+        errorLine2="                                                                  ~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values-my/strings.xml"
+            line="39"
+            column="67"/>
+    </issue>
+
+    <issue
+        id="Untranslatable"
+        message="The resource string &quot;mi2_prefs_button_press_broadcast_default_value&quot; is marked as translatable=&quot;false&quot;, but is translated to &quot;sv&quot; (Swedish) here"
+        errorLine1="    &lt;string name=&quot;mi2_prefs_button_press_broadcast_default_value&quot; translatable=&quot;false&quot;>nodomain.freeyourgadget.gadgetbridge.ButtonPressed&lt;/string>"
+        errorLine2="                                                                  ~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values-sv/strings.xml"
+            line="46"
+            column="67"/>
+    </issue>
+
+    <issue
+        id="Untranslatable"
+        message="The resource string &quot;mi2_prefs_button_press_broadcast_default_value&quot; is marked as translatable=&quot;false&quot;, but is translated to &quot;lt&quot; (Lithuanian) here"
+        errorLine1="    &lt;string name=&quot;mi2_prefs_button_press_broadcast_default_value&quot; translatable=&quot;false&quot;>nodomain.freeyourgadget.gadgetbridge.ButtonPressed&lt;/string>"
+        errorLine2="                                                                  ~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values-lt/strings.xml"
+            line="62"
+            column="67"/>
+    </issue>
+
+    <issue
+        id="Untranslatable"
+        message="The resource string &quot;mi2_prefs_button_press_broadcast_default_value&quot; is marked as translatable=&quot;false&quot;, but is translated to &quot;fa&quot; (Persian) here"
+        errorLine1="    &lt;string name=&quot;mi2_prefs_button_press_broadcast_default_value&quot; translatable=&quot;false&quot;>nodomain.freeyourgadget.gadgetbridge.ButtonPressed&lt;/string>"
+        errorLine2="                                                                  ~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values-fa/strings.xml"
+            line="125"
+            column="67"/>
+    </issue>
+
+    <issue
+        id="Untranslatable"
+        message="The resource string &quot;mi2_prefs_button_press_broadcast_default_value&quot; is marked as translatable=&quot;false&quot;, but is translated to &quot;ro&quot; (Romanian) here"
+        errorLine1="    &lt;string name=&quot;mi2_prefs_button_press_broadcast_default_value&quot; translatable=&quot;false&quot;>nodomain.freeyourgadget.gadgetbridge.ButtonPressed&lt;/string>"
+        errorLine2="                                                                  ~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values-ro/strings.xml"
+            line="129"
+            column="67"/>
+    </issue>
+
+    <issue
+        id="Untranslatable"
+        message="The resource string &quot;mi2_prefs_button_press_broadcast_default_value&quot; is marked as translatable=&quot;false&quot;, but is translated to &quot;vi&quot; (Vietnamese) here"
+        errorLine1="    &lt;string name=&quot;mi2_prefs_button_press_broadcast_default_value&quot; translatable=&quot;false&quot;>nodomain.freeyourgadget.gadgetbridge.ButtonPressed&lt;/string>"
+        errorLine2="                                                                  ~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values-vi/strings.xml"
+            line="189"
+            column="67"/>
+    </issue>
+
+    <issue
+        id="Untranslatable"
+        message="The resource string &quot;mi2_prefs_button_press_broadcast_default_value&quot; is marked as translatable=&quot;false&quot;, but is translated to &quot;et&quot; (Estonian) here"
+        errorLine1="    &lt;string name=&quot;mi2_prefs_button_press_broadcast_default_value&quot; translatable=&quot;false&quot;>nodomain.freeyourgadget.gadgetbridge.ButtonPressed&lt;/string>"
+        errorLine2="                                                                  ~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values-et/strings.xml"
+            line="279"
+            column="67"/>
+    </issue>
+
+    <issue
+        id="Untranslatable"
+        message="The resource string &quot;icon_placeholder&quot; has been marked as translatable=&quot;false&quot; elsewhere (usually in the `values` folder), but is translated to &quot;tr&quot; (Turkish) here"
+        errorLine1="    &lt;string name=&quot;icon_placeholder&quot;>Simge&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values-tr/strings.xml"
+            line="333"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="Untranslatable"
+        message="The resource string &quot;mi2_prefs_button_press_broadcast_default_value&quot; has been marked as translatable=&quot;false&quot; elsewhere (usually in the `values` folder), but is translated to &quot;tr&quot; (Turkish) here"
+        errorLine1="    &lt;string name=&quot;mi2_prefs_button_press_broadcast_default_value&quot;>nodomain.freeyourgadget.gadgetbridge.ButtonPressed&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values-tr/strings.xml"
+            line="402"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="Untranslatable"
+        message="The resource string &quot;mi2_prefs_button_press_broadcast_default_value&quot; is marked as translatable=&quot;false&quot;, but is translated to &quot;hu&quot; (Hungarian) here"
+        errorLine1="    &lt;string name=&quot;mi2_prefs_button_press_broadcast_default_value&quot; translatable=&quot;false&quot;>nodomain.freeyourgadget.gadgetbridge.ButtonPressed&lt;/string>"
+        errorLine2="                                                                  ~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values-hu/strings.xml"
+            line="464"
+            column="67"/>
+    </issue>
+
+    <issue
+        id="Untranslatable"
+        message="The resource string &quot;mi2_prefs_button_press_broadcast_default_value&quot; is marked as translatable=&quot;false&quot;, but is translated to &quot;uk&quot; (Ukrainian) here"
+        errorLine1="    &lt;string name=&quot;mi2_prefs_button_press_broadcast_default_value&quot; translatable=&quot;false&quot;>nodomain.freeyourgadget.gadgetbridge.ButtonPressed&lt;/string>"
+        errorLine2="                                                                  ~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values-uk/strings.xml"
+            line="473"
+            column="67"/>
+    </issue>
+
+    <issue
+        id="Untranslatable"
+        message="The resource string &quot;mi2_prefs_button_press_broadcast_default_value&quot; is marked as translatable=&quot;false&quot;, but is translated to &quot;bg&quot; (Bulgarian) here"
+        errorLine1="    &lt;string name=&quot;mi2_prefs_button_press_broadcast_default_value&quot; translatable=&quot;false&quot;>nodomain.freeyourgadget.gadgetbridge.ButtonPressed&lt;/string>"
+        errorLine2="                                                                  ~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values-bg/strings.xml"
+            line="483"
+            column="67"/>
+    </issue>
+
+    <issue
+        id="Untranslatable"
+        message="The resource string &quot;mi2_prefs_button_press_broadcast_default_value&quot; is marked as translatable=&quot;false&quot;, but is translated to &quot;sk&quot; (Slovak) here"
+        errorLine1="    &lt;string name=&quot;mi2_prefs_button_press_broadcast_default_value&quot; translatable=&quot;false&quot;>nodomain.freeyourgadget.gadgetbridge.ButtonPressed&lt;/string>"
+        errorLine2="                                                                  ~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values-sk/strings.xml"
+            line="491"
+            column="67"/>
+    </issue>
+
+    <issue
+        id="Untranslatable"
+        message="The resource string &quot;pref_button_action_disabled_value&quot; has been marked as translatable=&quot;false&quot; elsewhere (usually in the `values` folder), but is translated to &quot;tr&quot; (Turkish) here"
+        errorLine1="    &lt;string name=&quot;pref_button_action_disabled_value&quot;>Bilinmeyen&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values-tr/strings.xml"
+            line="491"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="Untranslatable"
+        message="The resource string &quot;mi2_prefs_button_press_broadcast_default_value&quot; is marked as translatable=&quot;false&quot;, but is translated to &quot;gl&quot; (Galician) here"
+        errorLine1="    &lt;string name=&quot;mi2_prefs_button_press_broadcast_default_value&quot; translatable=&quot;false&quot;>nodomain.freeyourgadget.gadgetbridge.ButtonPressed&lt;/string>"
+        errorLine2="                                                                  ~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values-gl/strings.xml"
+            line="515"
+            column="67"/>
+    </issue>
+
+    <issue
+        id="Untranslatable"
+        message="The resource string &quot;pref_media_forward_value&quot; has been marked as translatable=&quot;false&quot; elsewhere (usually in the `values` folder), but is translated to &quot;tr&quot; (Turkish) here"
+        errorLine1="    &lt;string name=&quot;pref_media_forward_value&quot;>SONRAK陌&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values-tr/strings.xml"
+            line="524"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="Untranslatable"
+        message="The resource string &quot;pref_media_next_value&quot; has been marked as translatable=&quot;false&quot; elsewhere (usually in the `values` folder), but is translated to &quot;tr&quot; (Turkish) here"
+        errorLine1="    &lt;string name=&quot;pref_media_next_value&quot;>SONRAK陌&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values-tr/strings.xml"
+            line="526"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="Untranslatable"
+        message="The resource string &quot;pref_media_pause_value&quot; has been marked as translatable=&quot;false&quot; elsewhere (usually in the `values` folder), but is translated to &quot;tr&quot; (Turkish) here"
+        errorLine1="    &lt;string name=&quot;pref_media_pause_value&quot;>DURAKLAT&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values-tr/strings.xml"
+            line="528"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="Untranslatable"
+        message="The resource string &quot;pref_media_play_value&quot; has been marked as translatable=&quot;false&quot; elsewhere (usually in the `values` folder), but is translated to &quot;tr&quot; (Turkish) here"
+        errorLine1="    &lt;string name=&quot;pref_media_play_value&quot;>OYNAT&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values-tr/strings.xml"
+            line="530"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="Untranslatable"
+        message="The resource string &quot;pref_media_playpause_value&quot; has been marked as translatable=&quot;false&quot; elsewhere (usually in the `values` folder), but is translated to &quot;tr&quot; (Turkish) here"
+        errorLine1="    &lt;string name=&quot;pref_media_playpause_value&quot;>A脟KAPAT&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values-tr/strings.xml"
+            line="532"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="Untranslatable"
+        message="The resource string &quot;pref_media_previous_value&quot; has been marked as translatable=&quot;false&quot; elsewhere (usually in the `values` folder), but is translated to &quot;tr&quot; (Turkish) here"
+        errorLine1="    &lt;string name=&quot;pref_media_previous_value&quot;>脰NCEK陌&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values-tr/strings.xml"
+            line="534"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="Untranslatable"
+        message="The resource string &quot;pref_media_rewind_value&quot; has been marked as translatable=&quot;false&quot; elsewhere (usually in the `values` folder), but is translated to &quot;tr&quot; (Turkish) here"
+        errorLine1="    &lt;string name=&quot;pref_media_rewind_value&quot;>GER陌SAR&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values-tr/strings.xml"
+            line="536"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="Untranslatable"
+        message="The resource string &quot;pref_media_volumedown_value&quot; has been marked as translatable=&quot;false&quot; elsewhere (usually in the `values` folder), but is translated to &quot;tr&quot; (Turkish) here"
+        errorLine1="    &lt;string name=&quot;pref_media_volumedown_value&quot;>SESD脺艦脺R&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values-tr/strings.xml"
+            line="538"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="Untranslatable"
+        message="The resource string &quot;pref_media_volumeup_value&quot; has been marked as translatable=&quot;false&quot; elsewhere (usually in the `values` folder), but is translated to &quot;tr&quot; (Turkish) here"
+        errorLine1="    &lt;string name=&quot;pref_media_volumeup_value&quot;>SESY脺KSELT&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values-tr/strings.xml"
+            line="540"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="Untranslatable"
+        message="The resource string &quot;mi2_prefs_button_press_broadcast_default_value&quot; is marked as translatable=&quot;false&quot;, but is translated to &quot;pl&quot; (Polish) here"
+        errorLine1="    &lt;string name=&quot;mi2_prefs_button_press_broadcast_default_value&quot; translatable=&quot;false&quot;>nodomain.freeyourgadget.gadgetbridge.ButtonPressed&lt;/string>"
+        errorLine2="                                                                  ~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values-pl/strings.xml"
+            line="551"
+            column="67"/>
+    </issue>
+
+    <issue
+        id="Untranslatable"
+        message="The resource string &quot;mi2_prefs_button_press_broadcast_default_value&quot; is marked as translatable=&quot;false&quot;, but is translated to &quot;pt&quot; (Portuguese) here"
+        errorLine1="    &lt;string name=&quot;mi2_prefs_button_press_broadcast_default_value&quot; translatable=&quot;false&quot;>nodomain.freeyourgadget.gadgetbridge.ButtonPressed&lt;/string>"
+        errorLine2="                                                                  ~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values-pt/strings.xml"
+            line="556"
+            column="67"/>
+    </issue>
+
+    <issue
+        id="Untranslatable"
+        message="The resource string &quot;pref_theme_value_dark&quot; has been marked as translatable=&quot;false&quot; elsewhere (usually in the `values` folder), but is translated to &quot;tr&quot; (Turkish) here"
+        errorLine1="    &lt;string name=&quot;pref_theme_value_dark&quot;>karanl谋k&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values-tr/strings.xml"
+            line="599"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="Untranslatable"
+        message="The resource string &quot;pref_theme_value_light&quot; has been marked as translatable=&quot;false&quot; elsewhere (usually in the `values` folder), but is translated to &quot;tr&quot; (Turkish) here"
+        errorLine1="    &lt;string name=&quot;pref_theme_value_light&quot;>ayd谋nl谋k&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values-tr/strings.xml"
+            line="600"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="Untranslatable"
+        message="The resource string &quot;mi2_prefs_button_press_broadcast_default_value&quot; is marked as translatable=&quot;false&quot;, but is translated to &quot;ko&quot; (Korean) here"
+        errorLine1="    &lt;string name=&quot;mi2_prefs_button_press_broadcast_default_value&quot; translatable=&quot;false&quot;>nodomain.freeyourgadget.gadgetbridge.ButtonPressed&lt;/string>"
+        errorLine2="                                                                  ~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values-ko/strings.xml"
+            line="606"
+            column="67"/>
+    </issue>
+
+    <issue
+        id="Untranslatable"
+        message="The resource string &quot;mi2_prefs_button_press_broadcast_default_value&quot; is marked as translatable=&quot;false&quot;, but is translated to &quot;ru&quot; (Russian) here"
+        errorLine1="    &lt;string name=&quot;mi2_prefs_button_press_broadcast_default_value&quot; translatable=&quot;false&quot;>nodomain.freeyourgadget.gadgetbridge.ButtonPressed&lt;/string>"
+        errorLine2="                                                                  ~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values-ru/strings.xml"
+            line="626"
+            column="67"/>
+    </issue>
+
+    <issue
+        id="Untranslatable"
+        message="The resource string &quot;mi2_prefs_button_press_broadcast_default_value&quot; is marked as translatable=&quot;false&quot;, but is translated to &quot;ca&quot; (Catalan) here"
+        errorLine1="    &lt;string name=&quot;mi2_prefs_button_press_broadcast_default_value&quot; translatable=&quot;false&quot;>nodomain.freeyourgadget.gadgetbridge.ButtonPressed&lt;/string>"
+        errorLine2="                                                                  ~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values-ca/strings.xml"
+            line="704"
+            column="67"/>
+    </issue>
+
+    <issue
+        id="Untranslatable"
+        message="The resource string &quot;mi2_prefs_button_press_broadcast_default_value&quot; is marked as translatable=&quot;false&quot;, but is translated to &quot;it&quot; (Italian) here"
+        errorLine1="    &lt;string name=&quot;mi2_prefs_button_press_broadcast_default_value&quot; translatable=&quot;false&quot;>nodomain.freeyourgadget.gadgetbridge.ButtonPressed&lt;/string>"
+        errorLine2="                                                                  ~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values-it/strings.xml"
+            line="715"
+            column="67"/>
+    </issue>
+
+    <issue
+        id="Untranslatable"
+        message="The resource string &quot;mi2_prefs_button_press_broadcast_default_value&quot; is marked as translatable=&quot;false&quot;, but is translated to &quot;el&quot; (Greek) here"
+        errorLine1="    &lt;string name=&quot;mi2_prefs_button_press_broadcast_default_value&quot; translatable=&quot;false&quot;>nodomain.freeyourgadget.gadgetbridge.ButtonPressed&lt;/string>"
+        errorLine2="                                                                  ~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values-el/strings.xml"
+            line="741"
+            column="67"/>
+    </issue>
+
+    <issue
+        id="Untranslatable"
+        message="The resource string &quot;mi2_prefs_button_press_broadcast_default_value&quot; is marked as translatable=&quot;false&quot;, but is translated to &quot;he&quot; (Hebrew) here"
+        errorLine1="    &lt;string name=&quot;mi2_prefs_button_press_broadcast_default_value&quot; translatable=&quot;false&quot;>nodomain.freeyourgadget.gadgetbridge.ButtonPressed&lt;/string>"
+        errorLine2="                                                                  ~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values-he/strings.xml"
+            line="790"
+            column="67"/>
+    </issue>
+
+    <issue
+        id="Untranslatable"
+        message="The resource string &quot;mi2_prefs_button_press_broadcast_default_value&quot; is marked as translatable=&quot;false&quot;, but is translated to &quot;cs&quot; (Czech) here"
+        errorLine1="    &lt;string name=&quot;mi2_prefs_button_press_broadcast_default_value&quot; translatable=&quot;false&quot;>nodomain.freeyourgadget.gadgetbridge.ButtonPressed&lt;/string>"
+        errorLine2="                                                                  ~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values-cs/strings.xml"
+            line="791"
+            column="67"/>
+    </issue>
+
+    <issue
+        id="Untranslatable"
+        message="The resource string &quot;mi2_prefs_button_press_broadcast_default_value&quot; is marked as translatable=&quot;false&quot;, but is translated to &quot;zh&quot; (Chinese) here"
+        errorLine1="    &lt;string name=&quot;mi2_prefs_button_press_broadcast_default_value&quot; translatable=&quot;false&quot;>nodomain.freeyourgadget.gadgetbridge.ButtonPressed&lt;/string>"
+        errorLine2="                                                                  ~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values-zh-rCN/strings.xml"
+            line="791"
+            column="67"/>
+    </issue>
+
+    <issue
+        id="Untranslatable"
+        message="The resource string &quot;mi2_prefs_button_press_broadcast_default_value&quot; is marked as translatable=&quot;false&quot;, but is translated to &quot;nl&quot; (Dutch) here"
+        errorLine1="    &lt;string name=&quot;mi2_prefs_button_press_broadcast_default_value&quot; translatable=&quot;false&quot;>nodomain.freeyourgadget.gadgetbridge.ButtonPressed&lt;/string>"
+        errorLine2="                                                                  ~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values-nl/strings.xml"
+            line="792"
+            column="67"/>
+    </issue>
+
+    <issue
+        id="Untranslatable"
+        message="The resource string &quot;mi2_prefs_button_press_broadcast_default_value&quot; is marked as translatable=&quot;false&quot;, but is translated to &quot;nb&quot; (Norwegian Bokm氓l) here"
+        errorLine1="    &lt;string name=&quot;mi2_prefs_button_press_broadcast_default_value&quot; translatable=&quot;false&quot;>nodomain.freeyourgadget.gadgetbridge.ButtonPressed&lt;/string>"
+        errorLine2="                                                                  ~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values-nb-rNO/strings.xml"
+            line="795"
+            column="67"/>
+    </issue>
+
+    <issue
+        id="Untranslatable"
+        message="The resource string &quot;mi2_prefs_button_press_broadcast_default_value&quot; is marked as translatable=&quot;false&quot;, but is translated to &quot;pt&quot; (Portuguese) here"
+        errorLine1="    &lt;string name=&quot;mi2_prefs_button_press_broadcast_default_value&quot; translatable=&quot;false&quot;>nodomain.freeyourgadget.gadgetbridge.ButtonPressed&lt;/string>"
+        errorLine2="                                                                  ~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values-pt-rBR/strings.xml"
+            line="798"
+            column="67"/>
+    </issue>
+
+    <issue
+        id="Untranslatable"
+        message="The resource string &quot;mi2_prefs_button_press_broadcast_default_value&quot; is marked as translatable=&quot;false&quot;, but is translated to &quot;de&quot; (German) here"
+        errorLine1="    &lt;string name=&quot;mi2_prefs_button_press_broadcast_default_value&quot; translatable=&quot;false&quot;>nodomain.freeyourgadget.gadgetbridge.ButtonPressed&lt;/string>"
+        errorLine2="                                                                  ~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values-de/strings.xml"
+            line="801"
+            column="67"/>
+    </issue>
+
+    <issue
+        id="PluralsCandidate"
+        message="Formatting %d followed by words (&quot;minutes&quot;): This should probably be a plural rather than a string"
+        errorLine1="    &lt;string name=&quot;pref_auto_fetch_limit_fetches_summary&quot;>Fetches every %d minutes&lt;/string>"
+        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values-en-rGB/strings.xml"
+            line="307"
+            column="5"/>
+    </issue>
+
+    <issue
+        id="PluralsCandidate"
+        message="Formatting %d followed by words (&quot;hour&quot;): This should probably be a plural rather than a string"
+        errorLine1="    &lt;string name=&quot;pref_summary_auto_export_interval&quot;>Export every %d hour&lt;/string>"
+        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values-en-rGB/strings.xml"
+            line="359"
+            column="5"/>
+    </issue>
+
+    <issue
+        id="PluralsCandidate"
+        message="Formatting %d followed by words (&quot;hour&quot;): This should probably be a plural rather than a string"
+        errorLine1="    &lt;string name=&quot;pref_summary_auto_export_interval&quot;>Export every %d hour&lt;/string>"
+        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="564"
+            column="5"/>
+    </issue>
+
+    <issue
+        id="PluralsCandidate"
+        message="Formatting %d followed by words (&quot;minutes&quot;): This should probably be a plural rather than a string"
+        errorLine1="    &lt;string name=&quot;pref_auto_fetch_limit_fetches_summary&quot;>Fetches every %d minutes&lt;/string>"
+        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="570"
+            column="5"/>
+    </issue>
+
+    <issue
+        id="PluralsCandidate"
+        message="Formatting %d followed by words (&quot;devices&quot;): This should probably be a plural rather than a string"
+        errorLine1="    &lt;string name=&quot;notification_gps_text&quot;>Sending GPS location to %1$d devices&lt;/string>"
+        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1554"
+            column="5"/>
+    </issue>
+
+    <issue
+        id="PluralsCandidate"
+        message="Formatting %d followed by words (&quot;activities&quot;): This should probably be a plural rather than a string"
+        errorLine1="    &lt;string name=&quot;sports_activity_confirm_delete_title&quot;>Delete %d activities&lt;/string>"
+        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1948"
+            column="5"/>
+    </issue>
+
+    <issue
+        id="PluralsCandidate"
+        message="Formatting %d followed by words (&quot;activities&quot;): This should probably be a plural rather than a string"
+        errorLine1="    &lt;string name=&quot;sports_activity_confirm_delete_description&quot;>Are you sure you want to delete %d activities?&lt;/string>"
+        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1949"
+            column="5"/>
+    </issue>
+
+    <issue
+        id="PluralsCandidate"
+        message="Formatting %d followed by words (&quot;devices&quot;): This should probably be a plural rather than a string"
+        errorLine1="    &lt;string name=&quot;info_connected_count&quot;>%d devices connected&lt;/string>"
+        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="2378"
+            column="5"/>
+    </issue>
+
+    <issue
+        id="PluralsCandidate"
+        message="Formatting %d followed by words (&quot;steps&quot;): This should probably be a plural rather than a string"
+        errorLine1="    &lt;string name=&quot;steps_streaks_total_steps_average_hint&quot;>Total average %d steps per day&lt;/string>"
+        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="2411"
+            column="5"/>
+    </issue>
+
+    <issue
+        id="PluralsCandidate"
+        message="Formatting %d followed by words (&quot;loyalty&quot;): This should probably be a plural rather than a string"
+        errorLine1="    &lt;string name=&quot;loyalty_cards_syncing&quot;>Syncing %d loyalty cards to device&lt;/string>"
+        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="2499"
+            column="5"/>
+    </issue>
+
+    <issue
+        id="StringFormatCount"
+        message="Inconsistent number of arguments in formatting string `fw_upgrade_notice_amazfitcor2`; found both 1 here and 0 in values-ja/strings.xml"
+        errorLine1="    &lt;string name=&quot;fw_upgrade_notice_amazfitcor2&quot;>You are about to install the %s firmware on your Amazfit Cor 2."
+        errorLine2="    ^">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="142"
+            column="5"/>
+        <location
+            file="src/main/res/values-ja/strings.xml"
+            line="612"
+            column="5"
+            message="Conflicting number of arguments (0) here"/>
+    </issue>
+
+    <issue
+        id="StringFormatCount"
+        message="Inconsistent number of arguments in formatting string `withings_calibration_text_activity_target`; found both 1 here and 0 in values-zh-rCN/strings.xml"
+        errorLine1="    &lt;string name=&quot;withings_calibration_text_activity_target&quot;>Zum Schluss richte den Aktivit盲tszeiger auf 100% aus. Bitte beachte, dass sich dieser Zeiger nur im Uhrzeigersinn bewegt.&lt;/string>"
+        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values-de/strings.xml"
+            line="2340"
+            column="5"/>
+        <location
+            file="src/main/res/values-zh-rCN/strings.xml"
+            line="2327"
+            column="5"
+            message="Conflicting number of arguments (0) here"/>
+    </issue>
+
+    <issue
+        id="UnusedQuantity"
+        message="For language &quot;zh&quot; (Chinese) the following quantities are not relevant: `one`"
+        errorLine1="    &lt;plurals name=&quot;widget_alarm_target_hours&quot;>"
+        errorLine2="    ^">
+        <location
+            file="src/main/res/values-zh-rTW/strings.xml"
+            line="119"
+            column="5"/>
+    </issue>
+
+    <issue
+        id="UnusedQuantity"
+        message="For language &quot;pt&quot; (Portuguese) the following quantities are not relevant: `many`"
+        errorLine1="    &lt;plurals name=&quot;widget_alarm_target_hours&quot;>"
+        errorLine2="    ^">
+        <location
+            file="src/main/res/values-pt/strings.xml"
+            line="557"
+            column="5"/>
+    </issue>
+
+    <issue
+        id="UnusedQuantity"
+        message="For language &quot;es&quot; (Spanish) the following quantities are not relevant: `many`"
+        errorLine1="    &lt;plurals name=&quot;widget_alarm_target_hours&quot;>"
+        errorLine2="    ^">
+        <location
+            file="src/main/res/values-es/strings.xml"
+            line="750"
+            column="5"/>
+    </issue>
+
+    <issue
+        id="UnusedQuantity"
+        message="For language &quot;pt&quot; (Portuguese) the following quantities are not relevant: `many`"
+        errorLine1="    &lt;plurals name=&quot;widget_alarm_target_hours&quot;>"
+        errorLine2="    ^">
+        <location
+            file="src/main/res/values-pt-rBR/strings.xml"
+            line="754"
+            column="5"/>
+    </issue>
+
+    <issue
+        id="UnusedQuantity"
+        message="For language &quot;it&quot; (Italian) the following quantities are not relevant: `many`"
+        errorLine1="    &lt;plurals name=&quot;widget_alarm_target_hours&quot;>"
+        errorLine2="    ^">
+        <location
+            file="src/main/res/values-it/strings.xml"
+            line="825"
+            column="5"/>
+    </issue>
+
+    <issue
+        id="SetJavaScriptEnabled"
+        message="Using `setJavaScriptEnabled` can introduce XSS vulnerabilities into your application, review carefully"
+        errorLine1="        settings.setJavaScriptEnabled(true);"
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/banglejs/AppsManagementActivity.java"
+            line="214"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="ExportedContentProvider"
+        message="Exported content providers can provide access to potentially sensitive data"
+        errorLine1="        &lt;provider"
+        errorLine2="         ~~~~~~~~">
+        <location
+            file="src/main/AndroidManifest.xml"
+            line="652"
+            column="10"/>
+    </issue>
+
+    <issue
+        id="ExportedReceiver"
+        message="Exported receiver does not require permission"
+        errorLine1="        &lt;receiver"
+        errorLine2="         ~~~~~~~~">
+        <location
+            file="src/main/AndroidManifest.xml"
+            line="446"
+            column="10"/>
+    </issue>
+
+    <issue
+        id="SystemPermissionTypo"
+        message="Did you mean `android.permission.BLUETOOTH_ADMIN`?"
+        errorLine1="            android:permission=&quot;android.permission.BLUETOOTH,android.permission.BLUETOOTH_ADMIN&quot;>"
+        errorLine2="                                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/AndroidManifest.xml"
+            line="476"
+            column="33"/>
+    </issue>
+
+    <issue
+        id="DataExtractionRules"
+        message="The attribute `android:allowBackup` is deprecated from Android 12 and higher and may be removed in future versions. Consider adding the attribute `android:dataExtractionRules` specifying an `@xml` resource which configures cloud backups and device transfers on Android 12 and higher."
+        errorLine1="        android:allowBackup=&quot;false&quot;"
+        errorLine2="                             ~~~~~">
+        <location
+            file="src/main/AndroidManifest.xml"
+            line="105"
+            column="30"/>
+    </issue>
+
+    <issue
+        id="NotifyDataSetChanged"
+        message="It will always be more efficient to use more specific change events if you can. Rely on `notifyDataSetChanged` as a last resort."
+        errorLine1="                    mGBDeviceAppAdapter.notifyDataSetChanged();"
+        errorLine2="                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/appmanager/AbstractAppManagerFragment.java"
+            line="224"
+            column="21"/>
+    </issue>
+
+    <issue
+        id="NotifyDataSetChanged"
+        message="It will always be more efficient to use more specific change events if you can. Rely on `notifyDataSetChanged` as a last resort."
+        errorLine1="                            mGBDeviceAppAdapter.notifyDataSetChanged();"
+        errorLine2="                            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/appmanager/AbstractAppManagerFragment.java"
+            line="251"
+            column="29"/>
+    </issue>
+
+    <issue
+        id="NotifyDataSetChanged"
+        message="It will always be more efficient to use more specific change events if you can. Rely on `notifyDataSetChanged` as a last resort."
+        errorLine1="        notifyDataSetChanged();"
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/AppBlacklistAdapter.java"
+            line="219"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="NotifyDataSetChanged"
+        message="It will always be more efficient to use more specific change events if you can. Rely on `notifyDataSetChanged` as a last resort."
+        errorLine1="        notifyDataSetChanged();"
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/AppBlacklistAdapter.java"
+            line="225"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="NotifyDataSetChanged"
+        message="It will always be more efficient to use more specific change events if you can. Rely on `notifyDataSetChanged` as a last resort."
+        errorLine1="            adapter.notifyDataSetChanged();"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/AppBlacklistAdapter.java"
+            line="303"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="NotifyDataSetChanged"
+        message="It will always be more efficient to use more specific change events if you can. Rely on `notifyDataSetChanged` as a last resort."
+        errorLine1="            adapter.notifyDataSetChanged();"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/AppSpecificNotificationSettingsAppListAdapter.java"
+            line="248"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="NotifyDataSetChanged"
+        message="It will always be more efficient to use more specific change events if you can. Rely on `notifyDataSetChanged` as a last resort."
+        errorLine1="            actionsListAdapter.notifyDataSetChanged();"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/CommuteActionsActivity.java"
+            line="86"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="NotifyDataSetChanged"
+        message="It will always be more efficient to use more specific change events if you can. Rely on `notifyDataSetChanged` as a last resort."
+        errorLine1="        mGBAlarmListAdapter.notifyDataSetChanged();"
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ConfigureAlarms.java"
+            line="113"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="NotifyDataSetChanged"
+        message="It will always be more efficient to use more specific change events if you can. Rely on `notifyDataSetChanged` as a last resort."
+        errorLine1="        mGBContactListAdapter.notifyDataSetChanged();"
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ConfigureContacts.java"
+            line="142"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="NotifyDataSetChanged"
+        message="It will always be more efficient to use more specific change events if you can. Rely on `notifyDataSetChanged` as a last resort."
+        errorLine1="        mGBReminderListAdapter.notifyDataSetChanged();"
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ConfigureReminders.java"
+            line="173"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="NotifyDataSetChanged"
+        message="It will always be more efficient to use more specific change events if you can. Rely on `notifyDataSetChanged` as a last resort."
+        errorLine1="                    mGBWorldClockListAdapter.notifyDataSetChanged();"
+        errorLine2="                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ConfigureWorldClocks.java"
+            line="131"
+            column="21"/>
+    </issue>
+
+    <issue
+        id="NotifyDataSetChanged"
+        message="It will always be more efficient to use more specific change events if you can. Rely on `notifyDataSetChanged` as a last resort."
+        errorLine1="        mGBWorldClockListAdapter.notifyDataSetChanged();"
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ConfigureWorldClocks.java"
+            line="152"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="NotifyDataSetChanged"
+        message="It will always be more efficient to use more specific change events if you can. Rely on `notifyDataSetChanged` as a last resort."
+        errorLine1="        mGBWorldClockListAdapter.notifyDataSetChanged();"
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ConfigureWorldClocks.java"
+            line="188"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="NotifyDataSetChanged"
+        message="It will always be more efficient to use more specific change events if you can. Rely on `notifyDataSetChanged` as a last resort."
+        errorLine1="        mGBDeviceAdapter.notifyDataSetChanged();"
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ControlCenterv2.java"
+            line="491"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="NotifyDataSetChanged"
+        message="It will always be more efficient to use more specific change events if you can. Rely on `notifyDataSetChanged` as a last resort."
+        errorLine1="                notifyDataSetChanged();"
+        errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/GBDeviceAdapterv2.java"
+            line="230"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="Recycle"
+        message="This `Cursor` should be freed up after use with `#close()`"
+        errorLine1="                    .query(uri, projection, selection, selectionArgs, null);"
+        errorLine2="                     ~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/util/AndroidUtils.java"
+            line="257"
+            column="22"/>
+    </issue>
+
+    <issue
+        id="Recycle"
+        message="This `Cursor` should be freed up after use with `#close()`"
+        errorLine1="                Cursor cursor = getContentResolver().query("
+        errorLine2="                                                     ~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DataManagementActivity.java"
+            line="232"
+            column="54"/>
+    </issue>
+
+    <issue
+        id="Recycle"
+        message="This `Cursor` should be freed up after use with `#close()`"
+        errorLine1="        final Cursor c = mContext.getContentResolver().query(SETTINGS_URI, SETTINGS_PROJECTION,"
+        errorLine2="                                                       ~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/OmniJawsObserver.java"
+            line="166"
+            column="56"/>
+    </issue>
+
+    <issue
+        id="Recycle"
+        message="This `Cursor` should be freed up after use with `#close()`"
+        errorLine1="                    Cursor cursor = requireContext().getContentResolver().query("
+        errorLine2="                                                                          ~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/SettingsActivity.java"
+            line="552"
+            column="75"/>
+    </issue>
+
+    <issue
+        id="ObsoleteLayoutParam"
+        message="Invalid layout param in a `RelativeLayout`: `layout_weight`"
+        errorLine1="        android:layout_weight=&quot;1&quot;"
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_commute_actions.xml"
+            line="17"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="ObsoleteLayoutParam"
+        message="Invalid layout param in a `LinearLayout`: `layout_alignStart`"
+        errorLine1="            android:layout_alignStart=&quot;@+id/watchface_invert_colors&quot;"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_hybridhr_watchface_designer.xml"
+            line="60"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="ObsoleteLayoutParam"
+        message="Invalid layout param in a `LinearLayout`: `layout_alignStart`"
+        errorLine1="            android:layout_alignStart=&quot;@+id/watchface_rotate_left&quot;"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_hybridhr_watchface_designer.xml"
+            line="67"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="ObsoleteLayoutParam"
+        message="Invalid layout param in a `LinearLayout`: `layout_alignStart`"
+        errorLine1="            android:layout_alignStart=&quot;@+id/watchface_rotate_right&quot;"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_hybridhr_watchface_designer.xml"
+            line="75"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="ObsoleteLayoutParam"
+        message="Invalid layout param in a `RelativeLayout`: `layout_weight`"
+        errorLine1="        android:layout_weight=&quot;20&quot;"
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/fragment_statschart.xml"
+            line="11"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="ObsoleteSdkInt"
+        message="Unnecessary; SDK_INT is always >= 21"
+        errorLine1="                &quot;\&quot;DEVICE_ID\&quot; ) ON CONFLICT REPLACE)&quot; + ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) ? &quot; WITHOUT ROWID;&quot; : &quot;;&quot;)"
+        errorLine2="                                                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/AppSpecificNotificationSettingDao.java"
+            line="59"
+            column="60"/>
+    </issue>
+
+    <issue
+        id="ObsoleteSdkInt"
+        message="Unnecessary; SDK_INT is always >= 21"
+        errorLine1="                &quot;\&quot;DEVICE_ID\&quot; ) ON CONFLICT REPLACE)&quot; + ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) ? &quot; WITHOUT ROWID;&quot; : &quot;;&quot;)"
+        errorLine2="                                                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/BangleJSActivitySampleDao.java"
+            line="63"
+            column="60"/>
+    </issue>
+
+    <issue
+        id="ObsoleteSdkInt"
+        message="Unnecessary; SDK_INT is always >= 21"
+        errorLine1="                &quot;\&quot;BATTERY_INDEX\&quot; ) ON CONFLICT REPLACE)&quot; + ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) ? &quot; WITHOUT ROWID;&quot; : &quot;;&quot;)"
+        errorLine2="                                                               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/BatteryLevelDao.java"
+            line="58"
+            column="64"/>
+    </issue>
+
+    <issue
+        id="ObsoleteSdkInt"
+        message="Unnecessary; SDK_INT is always >= 21"
+        errorLine1="                &quot;\&quot;DEVICE_ID\&quot; ) ON CONFLICT REPLACE)&quot; + ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) ? &quot; WITHOUT ROWID;&quot; : &quot;;&quot;)"
+        errorLine2="                                                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/CasioGBX100ActivitySampleDao.java"
+            line="61"
+            column="60"/>
+    </issue>
+
+    <issue
+        id="ObsoleteSdkInt"
+        message="Unnecessary; SDK_INT is always >= 21"
+        errorLine1="                &quot;\&quot;DEVICE_ID\&quot; ) ON CONFLICT REPLACE)&quot; + ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) ? &quot; WITHOUT ROWID;&quot; : &quot;;&quot;)"
+        errorLine2="                                                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/CmfActivitySampleDao.java"
+            line="67"
+            column="60"/>
+    </issue>
+
+    <issue
+        id="ObsoleteSdkInt"
+        message="Unnecessary; SDK_INT is always >= 21"
+        errorLine1="                &quot;\&quot;DEVICE_ID\&quot; ) ON CONFLICT REPLACE)&quot; + ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) ? &quot; WITHOUT ROWID;&quot; : &quot;;&quot;)"
+        errorLine2="                                                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/CmfHeartRateSampleDao.java"
+            line="57"
+            column="60"/>
+    </issue>
+
+    <issue
+        id="ObsoleteSdkInt"
+        message="Unnecessary; SDK_INT is always >= 21"
+        errorLine1="                &quot;\&quot;DEVICE_ID\&quot; ) ON CONFLICT REPLACE)&quot; + ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) ? &quot; WITHOUT ROWID;&quot; : &quot;;&quot;)"
+        errorLine2="                                                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/CmfSleepSessionSampleDao.java"
+            line="59"
+            column="60"/>
+    </issue>
+
+    <issue
+        id="ObsoleteSdkInt"
+        message="Unnecessary; SDK_INT is always >= 21"
+        errorLine1="                &quot;\&quot;DEVICE_ID\&quot; ) ON CONFLICT REPLACE)&quot; + ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) ? &quot; WITHOUT ROWID;&quot; : &quot;;&quot;)"
+        errorLine2="                                                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/CmfSleepStageSampleDao.java"
+            line="59"
+            column="60"/>
+    </issue>
+
+    <issue
+        id="ObsoleteSdkInt"
+        message="Unnecessary; SDK_INT is always >= 21"
+        errorLine1="                &quot;\&quot;DEVICE_ID\&quot; ) ON CONFLICT REPLACE)&quot; + ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) ? &quot; WITHOUT ROWID;&quot; : &quot;;&quot;)"
+        errorLine2="                                                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/CmfSpo2SampleDao.java"
+            line="57"
+            column="60"/>
+    </issue>
+
+    <issue
+        id="ObsoleteSdkInt"
+        message="Unnecessary; SDK_INT is always >= 21"
+        errorLine1="                &quot;\&quot;DEVICE_ID\&quot; ) ON CONFLICT REPLACE)&quot; + ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) ? &quot; WITHOUT ROWID;&quot; : &quot;;&quot;)"
+        errorLine2="                                                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/CmfStressSampleDao.java"
+            line="57"
+            column="60"/>
+    </issue>
+
+    <issue
+        id="ObsoleteSdkInt"
+        message="Unnecessary; SDK_INT is always >= 21"
+        errorLine1="                &quot;\&quot;DEVICE_ID\&quot; ) ON CONFLICT REPLACE)&quot; + ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) ? &quot; WITHOUT ROWID;&quot; : &quot;;&quot;)"
+        errorLine2="                                                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/CmfWorkoutGpsSampleDao.java"
+            line="59"
+            column="60"/>
+    </issue>
+
+    <issue
+        id="ObsoleteSdkInt"
+        message="Unnecessary; SDK_INT is always >= 21"
+        errorLine1="                &quot;\&quot;DEVICE_ID\&quot; ) ON CONFLICT REPLACE)&quot; + ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) ? &quot; WITHOUT ROWID;&quot; : &quot;;&quot;)"
+        errorLine2="                                                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/FemometerVinca2TemperatureSampleDao.java"
+            line="59"
+            column="60"/>
+    </issue>
+
+    <issue
+        id="ObsoleteSdkInt"
+        message="Unnecessary; SDK_INT is always >= 21"
+        errorLine1="                &quot;\&quot;DEVICE_ID\&quot; ) ON CONFLICT REPLACE)&quot; + ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) ? &quot; WITHOUT ROWID;&quot; : &quot;;&quot;)"
+        errorLine2="                                                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/FitProActivitySampleDao.java"
+            line="73"
+            column="60"/>
+    </issue>
+
+    <issue
+        id="ObsoleteSdkInt"
+        message="Unnecessary; SDK_INT is always >= 21"
+        errorLine1="                &quot;\&quot;DEVICE_ID\&quot; ) ON CONFLICT REPLACE)&quot; + ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) ? &quot; WITHOUT ROWID;&quot; : &quot;;&quot;)"
+        errorLine2="                                                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/HPlusHealthActivityOverlayDao.java"
+            line="63"
+            column="60"/>
+    </issue>
+
+    <issue
+        id="ObsoleteSdkInt"
+        message="Unnecessary; SDK_INT is always >= 21"
+        errorLine1="                &quot;\&quot;RAW_KIND\&quot; ) ON CONFLICT REPLACE)&quot; + ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) ? &quot; WITHOUT ROWID;&quot; : &quot;;&quot;)"
+        errorLine2="                                                          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/HPlusHealthActivitySampleDao.java"
+            line="70"
+            column="59"/>
+    </issue>
+
+    <issue
+        id="ObsoleteSdkInt"
+        message="Unnecessary; SDK_INT is always >= 21"
+        errorLine1="                &quot;\&quot;DEVICE_ID\&quot; ) ON CONFLICT REPLACE)&quot; + ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) ? &quot; WITHOUT ROWID;&quot; : &quot;;&quot;)"
+        errorLine2="                                                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/HuamiExtendedActivitySampleDao.java"
+            line="71"
+            column="60"/>
+    </issue>
+
+    <issue
+        id="ObsoleteSdkInt"
+        message="Unnecessary; SDK_INT is always >= 21"
+        errorLine1="                &quot;\&quot;DEVICE_ID\&quot; ) ON CONFLICT REPLACE)&quot; + ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) ? &quot; WITHOUT ROWID;&quot; : &quot;;&quot;)"
+        errorLine2="                                                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/HuamiHeartRateManualSampleDao.java"
+            line="59"
+            column="60"/>
+    </issue>
+
+    <issue
+        id="ObsoleteSdkInt"
+        message="Unnecessary; SDK_INT is always >= 21"
+        errorLine1="                &quot;\&quot;DEVICE_ID\&quot; ) ON CONFLICT REPLACE)&quot; + ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) ? &quot; WITHOUT ROWID;&quot; : &quot;;&quot;)"
+        errorLine2="                                                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/HuamiHeartRateMaxSampleDao.java"
+            line="59"
+            column="60"/>
+    </issue>
+
+    <issue
+        id="ObsoleteSdkInt"
+        message="Unnecessary; SDK_INT is always >= 21"
+        errorLine1="                &quot;\&quot;DEVICE_ID\&quot; ) ON CONFLICT REPLACE)&quot; + ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) ? &quot; WITHOUT ROWID;&quot; : &quot;;&quot;)"
+        errorLine2="                                                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/HuamiHeartRateRestingSampleDao.java"
+            line="59"
+            column="60"/>
+    </issue>
+
+    <issue
+        id="ObsoleteSdkInt"
+        message="Unnecessary; SDK_INT is always >= 21"
+        errorLine1="                &quot;\&quot;DEVICE_ID\&quot; ) ON CONFLICT REPLACE)&quot; + ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) ? &quot; WITHOUT ROWID;&quot; : &quot;;&quot;)"
+        errorLine2="                                                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/HuamiPaiSampleDao.java"
+            line="73"
+            column="60"/>
+    </issue>
+
+    <issue
+        id="ObsoleteSdkInt"
+        message="Unnecessary; SDK_INT is always >= 21"
+        errorLine1="                &quot;\&quot;DEVICE_ID\&quot; ) ON CONFLICT REPLACE)&quot; + ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) ? &quot; WITHOUT ROWID;&quot; : &quot;;&quot;)"
+        errorLine2="                                                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/HuamiSleepRespiratoryRateSampleDao.java"
+            line="59"
+            column="60"/>
+    </issue>
+
+    <issue
+        id="ObsoleteSdkInt"
+        message="Unnecessary; SDK_INT is always >= 21"
+        errorLine1="                &quot;\&quot;DEVICE_ID\&quot; ) ON CONFLICT REPLACE)&quot; + ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) ? &quot; WITHOUT ROWID;&quot; : &quot;;&quot;)"
+        errorLine2="                                                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/HuamiSpo2SampleDao.java"
+            line="59"
+            column="60"/>
+    </issue>
+
+    <issue
+        id="ObsoleteSdkInt"
+        message="Unnecessary; SDK_INT is always >= 21"
+        errorLine1="                &quot;\&quot;DEVICE_ID\&quot; ) ON CONFLICT REPLACE)&quot; + ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) ? &quot; WITHOUT ROWID;&quot; : &quot;;&quot;)"
+        errorLine2="                                                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/HuamiStressSampleDao.java"
+            line="59"
+            column="60"/>
+    </issue>
+
+    <issue
+        id="ObsoleteSdkInt"
+        message="Unnecessary; SDK_INT is always >= 21"
+        errorLine1="                &quot;\&quot;SOURCE\&quot; ) ON CONFLICT REPLACE)&quot; + ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) ? &quot; WITHOUT ROWID;&quot; : &quot;;&quot;)"
+        errorLine2="                                                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/HuaweiActivitySampleDao.java"
+            line="75"
+            column="57"/>
+    </issue>
+
+    <issue
+        id="ObsoleteSdkInt"
+        message="Unnecessary; SDK_INT is always >= 21"
+        errorLine1="                &quot;\&quot;TIMESTAMP\&quot; ) ON CONFLICT REPLACE)&quot; + ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) ? &quot; WITHOUT ROWID;&quot; : &quot;;&quot;)"
+        errorLine2="                                                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/HuaweiWorkoutDataSampleDao.java"
+            line="83"
+            column="60"/>
+    </issue>
+
+    <issue
+        id="ObsoleteSdkInt"
+        message="Unnecessary; SDK_INT is always >= 21"
+        errorLine1="                &quot;\&quot;TYPE\&quot; ) ON CONFLICT REPLACE)&quot; + ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) ? &quot; WITHOUT ROWID;&quot; : &quot;;&quot;)"
+        errorLine2="                                                      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/HuaweiWorkoutPaceSampleDao.java"
+            line="60"
+            column="55"/>
+    </issue>
+
+    <issue
+        id="ObsoleteSdkInt"
+        message="Unnecessary; SDK_INT is always >= 21"
+        errorLine1="                &quot;\&quot;DEVICE_ID\&quot; ) ON CONFLICT REPLACE)&quot; + ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) ? &quot; WITHOUT ROWID;&quot; : &quot;;&quot;)"
+        errorLine2="                                                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/HybridHRActivitySampleDao.java"
+            line="71"
+            column="60"/>
+    </issue>
+
+    <issue
+        id="ObsoleteSdkInt"
+        message="Unnecessary; SDK_INT is always >= 21"
+        errorLine1="                &quot;\&quot;DEVICE_ID\&quot; ) ON CONFLICT REPLACE)&quot; + ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) ? &quot; WITHOUT ROWID;&quot; : &quot;;&quot;)"
+        errorLine2="                                                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/ID115ActivitySampleDao.java"
+            line="65"
+            column="60"/>
+    </issue>
+
+    <issue
+        id="ObsoleteSdkInt"
+        message="Unnecessary; SDK_INT is always >= 21"
+        errorLine1="                &quot;\&quot;DEVICE_ID\&quot; ) ON CONFLICT REPLACE)&quot; + ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) ? &quot; WITHOUT ROWID;&quot; : &quot;;&quot;)"
+        errorLine2="                                                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/JYouActivitySampleDao.java"
+            line="67"
+            column="60"/>
+    </issue>
+
+    <issue
+        id="ObsoleteSdkInt"
+        message="Unnecessary; SDK_INT is always >= 21"
+        errorLine1="                &quot;\&quot;DEVICE_ID\&quot; ) ON CONFLICT REPLACE)&quot; + ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) ? &quot; WITHOUT ROWID;&quot; : &quot;;&quot;)"
+        errorLine2="                                                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/LefunActivitySampleDao.java"
+            line="65"
+            column="60"/>
+    </issue>
+
+    <issue
+        id="ObsoleteSdkInt"
+        message="Unnecessary; SDK_INT is always >= 21"
+        errorLine1="                &quot;\&quot;DEVICE_ID\&quot; ) ON CONFLICT REPLACE)&quot; + ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) ? &quot; WITHOUT ROWID;&quot; : &quot;;&quot;)"
+        errorLine2="                                                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/LefunBiometricSampleDao.java"
+            line="61"
+            column="60"/>
+    </issue>
+
+    <issue
+        id="ObsoleteSdkInt"
+        message="Unnecessary; SDK_INT is always >= 21"
+        errorLine1="                &quot;\&quot;DEVICE_ID\&quot; ) ON CONFLICT REPLACE)&quot; + ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) ? &quot; WITHOUT ROWID;&quot; : &quot;;&quot;)"
+        errorLine2="                                                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/LefunSleepSampleDao.java"
+            line="57"
+            column="60"/>
+    </issue>
+
+    <issue
+        id="ObsoleteSdkInt"
+        message="Unnecessary; SDK_INT is always >= 21"
+        errorLine1="                &quot;\&quot;DEVICE_ID\&quot; ) ON CONFLICT REPLACE)&quot; + ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) ? &quot; WITHOUT ROWID;&quot; : &quot;;&quot;)"
+        errorLine2="                                                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/MakibesHR3ActivitySampleDao.java"
+            line="61"
+            column="60"/>
+    </issue>
+
+    <issue
+        id="ObsoleteSdkInt"
+        message="Unnecessary; SDK_INT is always >= 21"
+        errorLine1="                &quot;\&quot;DEVICE_ID\&quot; ) ON CONFLICT REPLACE)&quot; + ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) ? &quot; WITHOUT ROWID;&quot; : &quot;;&quot;)"
+        errorLine2="                                                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/MiBandActivitySampleDao.java"
+            line="63"
+            column="60"/>
+    </issue>
+
+    <issue
+        id="ObsoleteSdkInt"
+        message="Unnecessary; SDK_INT is always >= 21"
+        errorLine1="                &quot;\&quot;DEVICE_ID\&quot; ) ON CONFLICT REPLACE)&quot; + ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) ? &quot; WITHOUT ROWID;&quot; : &quot;;&quot;)"
+        errorLine2="                                                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/No1F1ActivitySampleDao.java"
+            line="63"
+            column="60"/>
+    </issue>
+
+    <issue
+        id="ObsoleteSdkInt"
+        message="Unnecessary; SDK_INT is always >= 21"
+        errorLine1="                &quot;\&quot;DEVICE_ID\&quot; ) ON CONFLICT REPLACE)&quot; + ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) ? &quot; WITHOUT ROWID;&quot; : &quot;;&quot;)"
+        errorLine2="                                                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/PebbleHealthActivityOverlayDao.java"
+            line="63"
+            column="60"/>
+    </issue>
+
+    <issue
+        id="ObsoleteSdkInt"
+        message="Unnecessary; SDK_INT is always >= 21"
+        errorLine1="                &quot;\&quot;DEVICE_ID\&quot; ) ON CONFLICT REPLACE)&quot; + ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) ? &quot; WITHOUT ROWID;&quot; : &quot;;&quot;)"
+        errorLine2="                                                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/PebbleHealthActivitySampleDao.java"
+            line="63"
+            column="60"/>
+    </issue>
+
+    <issue
+        id="ObsoleteSdkInt"
+        message="Unnecessary; SDK_INT is always >= 21"
+        errorLine1="                &quot;\&quot;DEVICE_ID\&quot; ) ON CONFLICT REPLACE)&quot; + ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) ? &quot; WITHOUT ROWID;&quot; : &quot;;&quot;)"
+        errorLine2="                                                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/PebbleMisfitSampleDao.java"
+            line="57"
+            column="60"/>
+    </issue>
+
+    <issue
+        id="ObsoleteSdkInt"
+        message="Unnecessary; SDK_INT is always >= 21"
+        errorLine1="                &quot;\&quot;DEVICE_ID\&quot; ) ON CONFLICT REPLACE)&quot; + ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) ? &quot; WITHOUT ROWID;&quot; : &quot;;&quot;)"
+        errorLine2="                                                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/PebbleMorpheuzSampleDao.java"
+            line="57"
+            column="60"/>
+    </issue>
+
+    <issue
+        id="ObsoleteSdkInt"
+        message="Unnecessary; SDK_INT is always >= 21"
+        errorLine1="                &quot;\&quot;DEVICE_ID\&quot; ) ON CONFLICT REPLACE)&quot; + ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) ? &quot; WITHOUT ROWID;&quot; : &quot;;&quot;)"
+        errorLine2="                                                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/PineTimeActivitySampleDao.java"
+            line="61"
+            column="60"/>
+    </issue>
+
+    <issue
+        id="ObsoleteSdkInt"
+        message="Unnecessary; SDK_INT is always >= 21"
+        errorLine1="        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pinetime/PineTimeJFSupport.java"
+            line="524"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="ObsoleteSdkInt"
+        message="Unnecessary; SDK_INT is always >= 21"
+        errorLine1="                &quot;\&quot;DEVICE_ID\&quot; ) ON CONFLICT REPLACE)&quot; + ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) ? &quot; WITHOUT ROWID;&quot; : &quot;;&quot;)"
+        errorLine2="                                                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/SonySWR12SampleDao.java"
+            line="63"
+            column="60"/>
+    </issue>
+
+    <issue
+        id="ObsoleteSdkInt"
+        message="Unnecessary; SDK_INT is always >= 21"
+        errorLine1="                &quot;\&quot;DEVICE_ID\&quot; ) ON CONFLICT REPLACE)&quot; + ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) ? &quot; WITHOUT ROWID;&quot; : &quot;;&quot;)"
+        errorLine2="                                                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/TLW64ActivitySampleDao.java"
+            line="61"
+            column="60"/>
+    </issue>
+
+    <issue
+        id="ObsoleteSdkInt"
+        message="Unnecessary; SDK_INT is always >= 21"
+        errorLine1="    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)"
+        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/TransactionBuilder.java"
+            line="79"
+            column="5"/>
+    </issue>
+
+    <issue
+        id="ObsoleteSdkInt"
+        message="Unnecessary; SDK_INT is always >= 21"
+        errorLine1="                &quot;\&quot;DEVICE_ID\&quot; ) ON CONFLICT REPLACE)&quot; + ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) ? &quot; WITHOUT ROWID;&quot; : &quot;;&quot;)"
+        errorLine2="                                                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/VivomoveHrActivitySampleDao.java"
+            line="67"
+            column="60"/>
+    </issue>
+
+    <issue
+        id="ObsoleteSdkInt"
+        message="Unnecessary; SDK_INT is always >= 21"
+        errorLine1="                &quot;\&quot;RAW_KIND\&quot; ) ON CONFLICT REPLACE)&quot; + ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) ? &quot; WITHOUT ROWID;&quot; : &quot;;&quot;)"
+        errorLine2="                                                          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/WatchXPlusActivitySampleDao.java"
+            line="70"
+            column="59"/>
+    </issue>
+
+    <issue
+        id="ObsoleteSdkInt"
+        message="Unnecessary; SDK_INT is always >= 21"
+        errorLine1="                &quot;\&quot;DEVICE_ID\&quot; ) ON CONFLICT REPLACE)&quot; + ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) ? &quot; WITHOUT ROWID;&quot; : &quot;;&quot;)"
+        errorLine2="                                                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/WatchXPlusHealthActivityOverlayDao.java"
+            line="63"
+            column="60"/>
+    </issue>
+
+    <issue
+        id="ObsoleteSdkInt"
+        message="Unnecessary; SDK_INT is always >= 21"
+        errorLine1="                &quot;\&quot;DEVICE_ID\&quot; ) ON CONFLICT REPLACE)&quot; + ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) ? &quot; WITHOUT ROWID;&quot; : &quot;;&quot;)"
+        errorLine2="                                                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/Wena3ActivitySampleDao.java"
+            line="61"
+            column="60"/>
+    </issue>
+
+    <issue
+        id="ObsoleteSdkInt"
+        message="Unnecessary; SDK_INT is always >= 21"
+        errorLine1="                &quot;\&quot;DEVICE_ID\&quot; ) ON CONFLICT REPLACE)&quot; + ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) ? &quot; WITHOUT ROWID;&quot; : &quot;;&quot;)"
+        errorLine2="                                                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/Wena3BehaviorSampleDao.java"
+            line="61"
+            column="60"/>
+    </issue>
+
+    <issue
+        id="ObsoleteSdkInt"
+        message="Unnecessary; SDK_INT is always >= 21"
+        errorLine1="                &quot;\&quot;DEVICE_ID\&quot; ) ON CONFLICT REPLACE)&quot; + ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) ? &quot; WITHOUT ROWID;&quot; : &quot;;&quot;)"
+        errorLine2="                                                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/Wena3CaloriesSampleDao.java"
+            line="57"
+            column="60"/>
+    </issue>
+
+    <issue
+        id="ObsoleteSdkInt"
+        message="Unnecessary; SDK_INT is always >= 21"
+        errorLine1="                &quot;\&quot;DEVICE_ID\&quot; ) ON CONFLICT REPLACE)&quot; + ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) ? &quot; WITHOUT ROWID;&quot; : &quot;;&quot;)"
+        errorLine2="                                                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/Wena3EnergySampleDao.java"
+            line="57"
+            column="60"/>
+    </issue>
+
+    <issue
+        id="ObsoleteSdkInt"
+        message="Unnecessary; SDK_INT is always >= 21"
+        errorLine1="                &quot;\&quot;DEVICE_ID\&quot; ) ON CONFLICT REPLACE)&quot; + ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) ? &quot; WITHOUT ROWID;&quot; : &quot;;&quot;)"
+        errorLine2="                                                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/Wena3HeartRateSampleDao.java"
+            line="57"
+            column="60"/>
+    </issue>
+
+    <issue
+        id="ObsoleteSdkInt"
+        message="Unnecessary; SDK_INT is always >= 21"
+        errorLine1="                &quot;\&quot;DEVICE_ID\&quot; ) ON CONFLICT REPLACE)&quot; + ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) ? &quot; WITHOUT ROWID;&quot; : &quot;;&quot;)"
+        errorLine2="                                                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/Wena3StressSampleDao.java"
+            line="59"
+            column="60"/>
+    </issue>
+
+    <issue
+        id="ObsoleteSdkInt"
+        message="Unnecessary; SDK_INT is always >= 21"
+        errorLine1="                &quot;\&quot;DATAPOINT\&quot; ) ON CONFLICT REPLACE)&quot; + ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) ? &quot; WITHOUT ROWID;&quot; : &quot;;&quot;)"
+        errorLine2="                                                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/Wena3Vo2SampleDao.java"
+            line="60"
+            column="60"/>
+    </issue>
+
+    <issue
+        id="ObsoleteSdkInt"
+        message="Unnecessary; SDK_INT is always >= 21"
+        errorLine1="                &quot;\&quot;DEVICE_ID\&quot; ) ON CONFLICT REPLACE)&quot; + ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) ? &quot; WITHOUT ROWID;&quot; : &quot;;&quot;)"
+        errorLine2="                                                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/WithingsSteelHRActivitySampleDao.java"
+            line="69"
+            column="60"/>
+    </issue>
+
+    <issue
+        id="ObsoleteSdkInt"
+        message="Unnecessary; SDK_INT is always >= 21"
+        errorLine1="                &quot;\&quot;DEVICE_ID\&quot; ) ON CONFLICT REPLACE)&quot; + ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) ? &quot; WITHOUT ROWID;&quot; : &quot;;&quot;)"
+        errorLine2="                                                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/XWatchActivitySampleDao.java"
+            line="63"
+            column="60"/>
+    </issue>
+
+    <issue
+        id="ObsoleteSdkInt"
+        message="Unnecessary; SDK_INT is always >= 21"
+        errorLine1="                &quot;\&quot;DEVICE_ID\&quot; ) ON CONFLICT REPLACE)&quot; + ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) ? &quot; WITHOUT ROWID;&quot; : &quot;;&quot;)"
+        errorLine2="                                                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/XiaomiActivitySampleDao.java"
+            line="67"
+            column="60"/>
+    </issue>
+
+    <issue
+        id="ObsoleteSdkInt"
+        message="Unnecessary; SDK_INT is always >= 21"
+        errorLine1="                &quot;\&quot;DEVICE_ID\&quot; ) ON CONFLICT REPLACE)&quot; + ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) ? &quot; WITHOUT ROWID;&quot; : &quot;;&quot;)"
+        errorLine2="                                                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/XiaomiDailySummarySampleDao.java"
+            line="105"
+            column="60"/>
+    </issue>
+
+    <issue
+        id="ObsoleteSdkInt"
+        message="Unnecessary; SDK_INT is always >= 21"
+        errorLine1="                &quot;\&quot;DEVICE_ID\&quot; ) ON CONFLICT REPLACE)&quot; + ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) ? &quot; WITHOUT ROWID;&quot; : &quot;;&quot;)"
+        errorLine2="                                                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/XiaomiManualSampleDao.java"
+            line="59"
+            column="60"/>
+    </issue>
+
+    <issue
+        id="ObsoleteSdkInt"
+        message="Unnecessary; SDK_INT is always >= 21"
+        errorLine1="                &quot;\&quot;DEVICE_ID\&quot; ) ON CONFLICT REPLACE)&quot; + ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) ? &quot; WITHOUT ROWID;&quot; : &quot;;&quot;)"
+        errorLine2="                                                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/XiaomiSleepStageSampleDao.java"
+            line="57"
+            column="60"/>
+    </issue>
+
+    <issue
+        id="ObsoleteSdkInt"
+        message="Unnecessary; SDK_INT is always >= 21"
+        errorLine1="                &quot;\&quot;DEVICE_ID\&quot; ) ON CONFLICT REPLACE)&quot; + ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) ? &quot; WITHOUT ROWID;&quot; : &quot;;&quot;)"
+        errorLine2="                                                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/XiaomiSleepTimeSampleDao.java"
+            line="69"
+            column="60"/>
+    </issue>
+
+    <issue
+        id="ObsoleteSdkInt"
+        message="Unnecessary; SDK_INT is always >= 21"
+        errorLine1="                &quot;\&quot;DEVICE_ID\&quot; ) ON CONFLICT REPLACE)&quot; + ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) ? &quot; WITHOUT ROWID;&quot; : &quot;;&quot;)"
+        errorLine2="                                                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/ZeTimeActivitySampleDao.java"
+            line="69"
+            column="60"/>
+    </issue>
+
+    <issue
+        id="ObsoleteSdkInt"
+        message="This folder configuration (`v21`) is unnecessary; `minSdkVersion` is 21. Merge all the resources in this folder into `drawable`.">
+        <location
+            file="src/main/res/drawable-v21"/>
+    </issue>
+
+    <issue
+        id="ObsoleteSdkInt"
+        message="This folder configuration (`v14`) is unnecessary; `minSdkVersion` is 21. Merge all the resources in this folder into `values`.">
+        <location
+            file="src/main/res/values-v14"/>
+    </issue>
+
+    <issue
+        id="StaticFieldLeak"
+        message="This `AsyncTask` class should be static or leaks might occur (nodomain.freeyourgadget.gadgetbridge.service.AbstractDeviceSupport.StoreDataTask)"
+        errorLine1="    public class StoreDataTask extends DBAccess {"
+        errorLine2="                 ~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/service/AbstractDeviceSupport.java"
+            line="704"
+            column="18"/>
+    </issue>
+
+    <issue
+        id="StaticFieldLeak"
+        message="This `AsyncTask` class should be static or leaks might occur (nodomain.freeyourgadget.gadgetbridge.activities.charts.ActivityListingDashboard.RefreshTask)"
+        errorLine1="    public class RefreshTask extends DBAccess {"
+        errorLine2="                 ~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ActivityListingDashboard.java"
+            line="291"
+            column="18"/>
+    </issue>
+
+    <issue
+        id="StaticFieldLeak"
+        message="This `AsyncTask` class should be static or leaks might occur (nodomain.freeyourgadget.gadgetbridge.activities.ActivitySummariesChartFragment.RefreshTask)"
+        errorLine1="    public class RefreshTask extends DBAccess {"
+        errorLine2="                 ~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ActivitySummariesChartFragment.java"
+            line="179"
+            column="18"/>
+    </issue>
+
+    <issue
+        id="StaticFieldLeak"
+        message="This `AsyncTask` class should be static or leaks might occur (nodomain.freeyourgadget.gadgetbridge.activities.BatteryInfoChartFragment.RefreshTask)"
+        errorLine1="    public class RefreshTask extends DBAccess {"
+        errorLine2="                 ~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/BatteryInfoChartFragment.java"
+            line="213"
+            column="18"/>
+    </issue>
+
+    <issue
+        id="StaticFieldLeak"
+        message="This `AsyncTask` class should be static or leaks might occur (nodomain.freeyourgadget.gadgetbridge.activities.ControlCenterv2.RefreshTask)"
+        errorLine1="    public class RefreshTask extends DBAccess {"
+        errorLine2="                 ~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ControlCenterv2.java"
+            line="674"
+            column="18"/>
+    </issue>
+
+    <issue
+        id="StaticFieldLeak"
+        message="This field leaks a context object"
+        errorLine1="    private final Context mContext;"
+        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/database/DBAccess.java"
+            line="29"
+            column="5"/>
+    </issue>
+
+    <issue
+        id="StaticFieldLeak"
+        message="Do not place Android context classes in static fields (static reference to `LineageWeatherManager` which has field `mContext` pointing to `Context`); this is a memory leak"
+        errorLine1="    private static LineageWeatherManager sInstance;"
+        errorLine2="            ~~~~~~">
+        <location
+            file="src/main/java/lineageos/weather/LineageWeatherManager.java"
+            line="51"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="StaticFieldLeak"
+        message="This `AsyncTask` class should be static or leaks might occur (nodomain.freeyourgadget.gadgetbridge.database.PeriodicExporter.RefreshTask)"
+        errorLine1="    public class RefreshTask extends DBAccess {"
+        errorLine2="                 ~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/database/PeriodicExporter.java"
+            line="95"
+            column="18"/>
+    </issue>
+
+    <issue
+        id="StaticFieldLeak"
+        message="This `AsyncTask` class should be static or leaks might occur (nodomain.freeyourgadget.gadgetbridge.activities.charts.StepStreaksDashboard.TaskCalculateLatestStepsStreak)"
+        errorLine1="    public class TaskCalculateLatestStepsStreak extends DBAccess {"
+        errorLine2="                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/StepStreaksDashboard.java"
+            line="241"
+            column="18"/>
+    </issue>
+
+    <issue
+        id="StaticFieldLeak"
+        message="Do not place Android context classes in static fields (static reference to `WebViewSingleton` which has field `webView` pointing to `WebView`); this is a memory leak"
+        errorLine1="    private static final WebViewSingleton instance = new WebViewSingleton();"
+        errorLine2="            ~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/util/WebViewSingleton.java"
+            line="59"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UseCompoundDrawables"
+        message="This tag and its children can be replaced by one `&lt;TextView/>` and a compound drawable"
+        errorLine1="                &lt;LinearLayout"
+        errorLine2="                 ~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_list_item.xml"
+            line="86"
+            column="18"/>
+    </issue>
+
+    <issue
+        id="UseCompoundDrawables"
+        message="This tag and its children can be replaced by one `&lt;TextView/>` and a compound drawable"
+        errorLine1="                    &lt;LinearLayout"
+        errorLine2="                     ~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_list_item.xml"
+            line="123"
+            column="22"/>
+    </issue>
+
+    <issue
+        id="UseCompoundDrawables"
+        message="This tag and its children can be replaced by one `&lt;TextView/>` and a compound drawable"
+        errorLine1="                    &lt;LinearLayout"
+        errorLine2="                     ~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_list_item.xml"
+            line="153"
+            column="22"/>
+    </issue>
+
+    <issue
+        id="UseCompoundDrawables"
+        message="This tag and its children can be replaced by one `&lt;TextView/>` and a compound drawable"
+        errorLine1="                    &lt;LinearLayout"
+        errorLine2="                     ~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_list_item.xml"
+            line="183"
+            column="22"/>
+    </issue>
+
+    <issue
+        id="UseCompoundDrawables"
+        message="This tag and its children can be replaced by one `&lt;TextView/>` and a compound drawable"
+        errorLine1="                    &lt;LinearLayout"
+        errorLine2="                     ~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_list_item.xml"
+            line="213"
+            column="22"/>
+    </issue>
+
+    <issue
+        id="UseCompoundDrawables"
+        message="This tag and its children can be replaced by one `&lt;TextView/>` and a compound drawable"
+        errorLine1="    &lt;LinearLayout"
+        errorLine2="     ~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/fragment_gps.xml"
+            line="7"
+            column="6"/>
+    </issue>
+
+    <issue
+        id="UseCompoundDrawables"
+        message="This tag and its children can be replaced by one `&lt;TextView/>` and a compound drawable"
+        errorLine1="&lt;LinearLayout"
+        errorLine2=" ~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/spinner_with_image_layout.xml"
+            line="1"
+            column="2"/>
+    </issue>
+
+    <issue
+        id="VectorPath"
+        message="Very long vector path (3438 characters), which is bad for performance. Considering reducing precision, removing minor details or rasterizing vector."
+        errorLine1="        android:pathData=&quot;M391.439 251.5c-16.53-0.137-32.726-5.052-49-7.576-31.323-4.857-62.605-10.178-94-14.554-11.34-1.581-22.657-3.315-34-4.87-3.544-0.486-9.985-0.271-11.683-4.148-2.661-6.074 3.215-15.79 5.989-20.852 9.17-16.735 19.424-32.9 29.64-49 26.59-41.901 56.624-85.123 95.054-117.08 25.306-21.043 54.596-33.544 87-20.467 13.073 5.276 24.874 14.036 34.155 24.548 7.445 8.431 14.372 18.176 17.816 29 4.549 14.293 11.03 39.431 4.977 53.999-5.502 13.241-20.4 23.207-29.662 34-15.78 18.385-28.437 38.888-40.147 60-5.488 9.893-15.517 24.17-14.96 35.981 0.352 7.455 22.806 13.067 28.82 16.328 21.17 11.476 40.696 28.364 50.689 50.691 18.396 41.106-0.666 100.512-34.69 128.535-10.353 8.529-23.217 13.179-35.998 16.72-35.687 9.887-74.001 0.29-110-1.255-1.397 17.704-4.591 36.596 0.774 54 9.793 31.77 49.168 55.388 82.226 47.402 32.92-7.953 54.022-37.177 77.089-59.294 4.322-4.143 10.976-4.135 15.068-8.032 5.05-4.808 6.756-15.863 10.027-22.076 5.03-9.552 12.956-18.21 19.237-27 38.268-53.553 71.679-110.568 103.719-168 8.826-15.82 17.512-31.797 25.613-48 2.81-5.62 4.46-16.206 8.76-20.486 4.169-4.15 13.16-3.887 18.487-6.306 37.106-16.857 75.799-10.058 105 18.796 6.41 6.338 13.31 12.628 17.1 20.996 16.27 36.011-0.86 79.314-13.68 113-22.504 59.17-57.511 114.154-93.395 166-5.988 8.652-6.82 20.868-12.868 28.725-3.998 5.193-12.115 6-16.718 11.29-10.773 12.382-20.651 27.824-34.44 37.135-26.993 18.228-73.226 17.881-101 2.538-26.892-14.858-32.244-45.823-33.624-73.688-0.5-10.078 3.588-22.7-1.375-32m12-379c17.823-3.679 32.752-13.717 51-16.56 38.387-5.984 78.04 16.747 92.547 52.56 7.349 18.142 5.142 37.032 10.453 55m21 344c8.3 11.537 3.86 33.142 7.746 47 11.464 40.89 48.099 57.862 88.254 49.331 56.57-12.018 97.41-63.857 127.67-109.33 6.99-10.508 12.44-24.387 20.96-33.61 2.88-3.113 10.21-7.579 14.05-4.03 5.94 5.48 3.32 26.012 3.32 33.64 0 32.984 0.29 66.192-2.83 99-5.99 62.92-7.24 134.76-42.22 190-11.86 18.727-29.51 34.486-45.95 49.165-32.43 28.959-66.94 55.856-103 80.164-15.844 10.68-34.304 20-48.891 32.28-5.808 4.89-4.11 13.58-4.11 20.39v54c0 5.29 2.608 20.69-2.317 23.97-4.938 3.3-16.896 1.03-22.682 1.03h-342c-6.208 0-19.404 2.49-24.682-1.03-5.401-3.6-2.318-23.03-2.318-28.97 0-21.69 5.279-56.45-8.804-74-71.457-89.076-142.18-180.028-199.62-279-24.185-41.671-53.67-88.059-58.406-137-9.294-96.034 42.012-191.579 91.777-270 12.162-19.166 25.178-38.033 39.064-56 4.758-6.156 9.917-16.188 17.004-19.821 11.35-5.818 31.627 1.813 43.985 1.821m576 99c44.15-9.108 86.54-30.774 128 0.375 7.04 5.292 12.9 11.496 18.08 18.625 6.29 8.669 10.42 17.756 13.34 28 3.2 11.222 6.24 23.209 5.49 35-1.67 26.166-15.3 46.19-24.44 70-3.25 8.446-3.75 18.156-5.47 27m-746-75c27.09 12.008 57.317 19.456 86 26.627 12.377 3.094 29.002 4.03 40 10.373m-16 622c-12.048 6.43-35.402 2-49 2-3.938 0-14.254-1.83-16.972 1.6-1.8 2.28-1.028 6.71-1.028 9.4v149c0 2.88-0.841 7.84 2.318 9.4 9.22 4.53 29.38 0.6 39.682 0.6h458c11.273 0 27.059 2.89 37.852-0.32 4.03-1.19 3.147-6.39 3.148-9.68v-149c0-2.69 0.772-7.12-1.028-9.4-3.055-3.85-16.446-1.6-20.972-1.6-17.678 0-41.071 4-58-1m-324 40.42c20.064-2.63 41.76-0.42 62-0.42h185c12.293 0 26.03-1.26 38 2.06 8.262 2.29 11.04 9.28 12.385 16.94 2.832 16.13 2.326 37.74 0.329 54-0.937 7.63-2.603 15.93-10.714 18.97-8.04 3.01-17.597 2.03-26 2.03h-251c-8.672 0-18.87 1.13-26.985-2.65-11.262-5.25-9.015-23.28-9.015-33.35 0-13.97-5.538-43.55 7.105-53.2 5.35-4.08 12.57-3.54 18.895-4.38m-63 131.58v127l1.028 10.4 20.972 1.6h343l20.397-1.6 1.603-10.4v-127&quot;"
+        errorLine2="                          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/drawable/gadgetbridge_img.xml"
+            line="9"
+            column="27"/>
+    </issue>
+
+    <issue
+        id="VectorPath"
+        message="Very long vector path (902 characters), which is bad for performance. Considering reducing precision, removing minor details or rasterizing vector."
+        errorLine1="        android:pathData=&quot;m21.9184,3.7795c-1.7312,0 -3.1175,1.3863 -3.1175,3.1175 0,1.7312 1.3863,3.1175 3.1175,3.1175 1.7312,0 3.1175,-1.3863 3.1175,-3.1175 0,-1.7312 -1.3863,-3.1175 -3.1175,-3.1175zM11.3487,9.7812C10.5363,9.8097 9.8221,10.1506 9.5939,10.9207L6.5508,21.9333c-0.613,2.3209 4.2012,3.7931 4.9501,1.4641L14.3851,12.2833C14.8084,10.8183 12.8998,9.7267 11.3487,9.7812ZM18.3174,10.4101c-0.3116,0 -0.5886,0.0521 -0.8656,0.1386 -1.82,0.2388 -2.6137,4.4328 -2.6137,4.4328L8.069,41.5207h3.1175l4.967,-14.0388 4.6627,0.8622 1.7075,7.8072 2.9518,-0.6221 -1.5452,-9.6263 -4.9366,-3.3373 1.2612,-4.9704 1.8698,3.1513 6.6948,-2.0761 3.415,15.1411 0.7134,-0.1454 -3.5165,-15.1851 0.4666,-0.1454 -0.6695,-2.8774 -0.4632,0.142 -0.186,-0.798 -0.6019,0.142 0.1893,0.8419 -4.1285,1.2713 -3.1919,-5.1936c-0.5194,-0.8656 -1.4558,-1.4539 -2.5292,-1.4539zM38.8583,29.9603 L33.5194,34.5419 21.462,37.0981 15.8931,41.5748 38.8583,41.5207z&quot; />"
+        errorLine2="                          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/drawable/ic_activity_climbing.xml"
+            line="9"
+            column="27"/>
+    </issue>
+
+    <issue
+        id="VectorPath"
+        message="Very long vector path (1632 characters), which is bad for performance. Considering reducing precision, removing minor details or rasterizing vector."
+        errorLine1="        android:pathData=&quot;M22 21c-1.11 0-1.73-0.37-2.18-0.64-0.37-0.22-0.6-0.36-1.15-0.36-0.56 0-0.78 0.13-1.15 0.36-0.46 0.27-1.07 0.64-2.18 0.64s-1.73-0.37-2.18-0.64c-0.37-0.22-0.6-0.36-1.15-0.36-0.56 0-0.78 0.13-1.15 0.36C10.4 20.63 9.78 21 8.67 21c-1.11 0-1.73-0.37-2.18-0.64C6.12 20.13 5.89 20 5.34 20s-0.78 0.13-1.15 0.36C3.73 20.63 3.11 21 2 21v-2c0.56 0 0.78-0.13 1.15-0.36C3.61 18.37 4.23 18 5.34 18s1.73 0.37 2.18 0.64C7.89 18.87 8.11 19 8.67 19c0.56 0 0.78-0.13 1.15-0.36 0.46-0.27 1.08-0.64 2.19-0.64 1.11 0 1.73 0.37 2.18 0.64 0.37 0.22 0.6 0.36 1.15 0.36s0.78-0.13 1.15-0.36c0.45-0.27 1.07-0.64 2.18-0.64s1.73 0.37 2.18 0.64C21.22 18.87 21.44 19 22 19v2zm0-4.5c-1.11 0-1.73-0.37-2.18-0.64-0.37-0.22-0.6-0.36-1.15-0.36-0.56 0-0.78 0.13-1.15 0.36-0.45 0.27-1.07 0.64-2.18 0.64s-1.73-0.37-2.18-0.64c-0.37-0.22-0.6-0.36-1.15-0.36-0.56 0-0.78 0.13-1.15 0.36-0.45 0.27-1.07 0.64-2.18 0.64s-1.73-0.37-2.18-0.64c-0.37-0.22-0.6-0.36-1.15-0.36s-0.78 0.13-1.15 0.36C3.73 16.13 3.11 16.5 2 16.5v-2c0.56 0 0.78-0.13 1.15-0.36 0.45-0.27 1.07-0.64 2.18-0.64s1.73 0.37 2.18 0.64c0.37 0.22 0.6 0.36 1.15 0.36 0.56 0 0.78-0.13 1.15-0.36 0.45-0.27 1.07-0.64 2.18-0.64s1.73 0.37 2.18 0.64c0.37 0.22 0.6 0.36 1.15 0.36s0.78-0.13 1.15-0.36c0.45-0.27 1.07-0.64 2.18-0.64s1.73 0.37 2.18 0.64c0.37 0.22 0.6 0.36 1.15 0.36v2zM8.67 12c0.56 0 0.78-0.13 1.15-0.36 0.46-0.27 1.08-0.64 2.19-0.64 1.11 0 1.73 0.37 2.18 0.64 0.37 0.22 0.6 0.36 1.15 0.36s0.78-0.13 1.15-0.36c0.12-0.07 0.26-0.15 0.41-0.23L10.48 5C8.93 3.45 7.5 2.99 5 3v2.5C6.82 5.49 7.89 5.89 9 7l1 1-3.25 3.25c0.31 0.12 0.56 0.27 0.77 0.39C7.89 11.87 8.11 12 8.67 12zM14 5.5a2.5 2.5 0 1 1 5 0 2.5 2.5 0 1 1-5 0&quot; />"
+        errorLine2="                          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/drawable/ic_activity_swimming.xml"
+            line="9"
+            column="27"/>
+    </issue>
+
+    <issue
+        id="VectorPath"
+        message="Very long vector path (871 characters), which is bad for performance. Considering reducing precision, removing minor details or rasterizing vector."
+        errorLine1="    &lt;path android:fillColor=&quot;#000000&quot; android:pathData=&quot;M2.873 9.063C0.861 8.728-0.35 6.263 0.452 4.14c0.698-1.85 1.22-2.386 2.716-2.793C3.791 1.177 6.401 1.146 7.05 1.3c1.278 0.302 1.887 0.728 2.37 1.655 0.707 1.36 0.896 2.223 0.719 3.275C9.88 7.76 8.846 8.841 7.416 9.075 6.858 9.166 3.437 9.157 2.872 9.063zm4.644-0.155c0.604-0.11 1.305-0.487 1.655-0.887 0.307-0.353 0.686-1.108 0.768-1.532l0.053-0.274-0.229 0.244c-0.332 0.355-0.774 0.623-1.34 0.81-1.136 0.378-5.214 0.387-6.41 0.015C1.456 7.11 0.82 6.728 0.58 6.422L0.417 6.215l0.047 0.274c0.07 0.417 0.462 1.196 0.773 1.54 0.331 0.367 0.958 0.724 1.5 0.856 0.506 0.123 4.14 0.14 4.78 0.023zM8.082 7.24c0.917-0.232 1.683-0.775 1.9-1.348 0.209-0.547-0.02-1.558-0.622-2.756-0.47-0.93-1.06-1.376-2.2-1.665-0.677-0.172-3.41-0.148-4.066 0.036-1.07 0.3-1.649 0.765-2.081 1.677-0.995 2.096-0.885 3.04 0.434 3.73 0.83 0.434 1.469 0.509 4.07 0.476C7.078 7.37 7.711 7.333 8.08 7.24z&quot; android:strokeWidth=&quot;0.4&quot; android:strokeColor=&quot;#000000&quot;/>"
+        errorLine2="                                                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/drawable/ic_buds_pro_case.xml"
+            line="2"
+            column="57"/>
+    </issue>
+
+    <issue
+        id="VectorPath"
+        message="Very long vector path (928 characters), which is bad for performance. Considering reducing precision, removing minor details or rasterizing vector."
+        errorLine1="        android:pathData=&quot;M4.654 2.265h0.368v2.423h1.324V5H4.654zM4.97 9.917c0.357-0.15 0.823-0.536 0.969-0.805C6.01 8.978 5.964 8.977 5.463 9.1 4.48 9.34 3.784 9.109 3.157 8.33c-0.26-0.324-0.296-0.445-0.34-1.178l-0.05-0.817L2.512 6.64C1.69 7.62 2.051 9.288 3.213 9.862c0.489 0.243 1.251 0.266 1.757 0.055zm-1.33 0.329c-0.542-0.13-0.867-0.328-1.226-0.747-0.606-0.708-0.727-1.776-0.3-2.636C2.25 6.592 2.438 6.329 2.53 6.279c0.158-0.085 0.16-0.12 0.026-0.496-0.352-0.992-0.21-2.516 0.328-3.504 0.33-0.604 1.043-1.243 1.604-1.434 1.115-0.38 2.68 0.042 3.461 0.935 1.212 1.384 1.528 3.218 0.817 4.731C8.528 7.02 7.551 8.008 6.84 8.461c-0.343 0.22-0.564 0.444-0.677 0.69-0.372 0.803-1.569 1.322-2.523 1.095zm2.199-1.497c0.978-0.372 2.47-1.672 2.795-2.435 0.783-1.84-0.119-4.304-1.87-5.11-0.807-0.37-1.79-0.385-2.471-0.037C2.842 1.907 2.187 4.02 2.829 5.89c0.096 0.281 0.176 0.771 0.177 1.088 0.005 1.232 0.672 1.948 1.818 1.949 0.305 0 0.75-0.078 1.015-0.179z&quot;"
+        errorLine2="                          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/drawable/ic_buds_pro_left.xml"
+            line="9"
+            column="27"/>
+    </issue>
+
+    <issue
+        id="VectorPath"
+        message="Very long vector path (1231 characters), which is bad for performance. Considering reducing precision, removing minor details or rasterizing vector."
+        errorLine1="        android:pathData=&quot;M5.69 3.701q0.117 0.04 0.228 0.172 0.113 0.132 0.226 0.363l0.373 0.748H6.122L5.775 4.282Q5.64 4.007 5.513 3.917q-0.126-0.09-0.344-0.09h-0.4v1.157H4.4V2.248h0.83q0.466 0 0.695 0.196 0.229 0.196 0.229 0.592 0 0.258-0.12 0.429Q5.917 3.635 5.689 3.7zM4.768 2.552v0.971H5.23q0.265 0 0.4-0.122 0.137-0.125 0.137-0.365T5.63 2.676q-0.135-0.124-0.4-0.124zm1.26 7.365C5.672 9.767 5.206 9.381 5.06 9.112 4.99 8.978 5.036 8.977 5.537 9.1c0.984 0.24 1.68 0.009 2.306-0.77 0.26-0.324 0.296-0.445 0.34-1.178l0.05-0.817 0.256 0.306c0.822 0.98 0.46 2.647-0.702 3.221-0.489 0.243-1.251 0.266-1.757 0.055zM5.162 8.749c-0.978-0.372-2.47-1.672-2.795-2.435-0.783-1.84 0.119-4.304 1.87-5.11 0.807-0.37 1.79-0.385 2.471-0.037 1.45 0.74 2.105 2.853 1.463 4.723-0.096 0.282-0.176 0.77-0.177 1.089-0.005 1.232-0.672 1.947-1.818 1.949-0.305 0-0.75-0.078-1.015-0.18zm2.2 1.497c0.541-0.13 0.866-0.328 1.225-0.747 0.606-0.708 0.727-1.776 0.3-2.636C8.75 6.592 8.562 6.329 8.47 6.279c-0.158-0.085-0.16-0.12-0.026-0.496 0.352-0.992 0.21-2.516-0.328-3.504-0.33-0.604-1.043-1.243-1.604-1.434-1.115-0.38-2.68 0.042-3.461 0.935-1.212 1.384-1.528 3.218-0.817 4.731 0.238 0.508 1.215 1.497 1.926 1.95 0.343 0.22 0.564 0.444 0.677 0.69 0.372 0.803 1.569 1.322 2.523 1.095z&quot;"
+        errorLine2="                          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/drawable/ic_buds_pro_right.xml"
+            line="9"
+            column="27"/>
+    </issue>
+
+    <issue
+        id="VectorPath"
+        message="Very long vector path (945 characters), which is bad for performance. Considering reducing precision, removing minor details or rasterizing vector."
+        errorLine1="        android:pathData=&quot;M19 3H5C3.9 3 3 3.9 3 5v14c0 1.1 0.9 2 2 2h14c1.1 0 2-0.9 2-2V5c0-1.1-0.9-2-2-2zm-5.44 3.53c0.29-0.29 0.77-0.29 1.06 0l0.88 0.88 0.88-0.88c0.29-0.29 0.77-0.29 1.06 0 0.29 0.29 0.29 0.77 0 1.06l-0.88 0.88 0.88 0.88c0.29 0.29 0.29 0.77 0 1.06-0.29 0.29-0.77 0.29-1.06 0L15.5 9.54l-0.88 0.88c-0.29 0.29-0.77 0.29-1.06 0-0.29-0.29-0.29-0.77 0-1.06l0.88-0.88-0.88-0.88c-0.3-0.3-0.3-0.78 0-1.07zM7 7.72h3.5c0.41 0 0.75 0.34 0.75 0.75s-0.34 0.75-0.75 0.75H7c-0.41 0-0.75-0.34-0.75-0.75S6.59 7.72 7 7.72zM10.75 16H9.5v1.25C9.5 17.66 9.16 18 8.75 18S8 17.66 8 17.25V16H6.75C6.34 16 6 15.66 6 15.25s0.34-0.75 0.75-0.75H8v-1.25c0-0.41 0.34-0.75 0.75-0.75s0.75 0.34 0.75 0.75v1.25h1.25c0.41 0 0.75 0.34 0.75 0.75S11.16 16 10.75 16zm6.5 1.25h-3.5c-0.41 0-0.75-0.34-0.75-0.75s0.34-0.75 0.75-0.75h3.5c0.41 0 0.75 0.34 0.75 0.75s-0.34 0.75-0.75 0.75zm0-2.5h-3.5C13.34 14.75 13 14.41 13 14s0.34-0.75 0.75-0.75h3.5c0.41 0 0.75 0.34 0.75 0.75s-0.34 0.75-0.75 0.75z&quot; />"
+        errorLine2="                          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/drawable/ic_calculate.xml"
+            line="9"
+            column="27"/>
+    </issue>
+
+    <issue
+        id="VectorPath"
+        message="Very long vector path (1901 characters), which is bad for performance. Considering reducing precision, removing minor details or rasterizing vector."
+        errorLine1="        android:pathData=&quot;M18.895 15.63l0.248 0.042v-0.25h0.208l0.041-0.248H19.6v-0.207h0.25v-0.25h0.207v-0.206h0.248v-0.25h0.208v-0.207h0.248v-0.248h0.25v-0.208h0.207V13.35h0.248v-0.208h0.208v-0.249h0.248v-0.207h0.208v-0.249h0.249v-0.912H22.17v-0.207h-0.249l-0.248-0.042h-0.208V11.07h-2.073v0.207h-0.456v0.25H18.48v0.206h-0.456v-0.207h-0.249v-0.497h-0.207V10.57h-0.249v-0.207h-0.207v-0.249h-0.25V9.908h-0.206V9.659H16.2V9.41h-0.457V9.203H15.04V8.954h-1.161V8.747L13.67 8.706h-3.234v0.248H9.524v0.208H8.818V9.41H8.613v0.208H8.156L8.114 9.867H7.907l-0.25-0.042v0.249H7.452v0.249H7.2v0.207H6.996v0.249h-0.25v0.207H6.539l-0.041 0.249v0.207H6.29v0.456H6.04v0.456H5.835v0.706H6.04v-0.458h0.25v-0.455h0.207v-0.456h0.249v-0.456h0.249v-0.249h0.207v-0.207H7.45v-0.25h0.207v-0.206h0.249v-0.25h0.457V9.868h0.456v-0.25h2.778V9.41H9.731V9.162h0.954V8.954h2.986v0.25h0.207V9.41h0.248V9.203h0.208V9.41h1.161v0.457h0.207l0.249 0.041h0.456v0.207h0.249v0.25h0.207v0.206h0.249v0.25h0.207v0.662h-0.207l-0.042 0.248h0.705v0.459h0.456v-0.21h0.456v-0.249h0.456v-0.207h0.498v-0.249h1.576v0.25h0.704v0.248h0.208v0.207h-0.912v0.249h-0.664v0.207h-0.497v0.25h-0.208L19.6 12.644v0.248h-0.207v0.25h-0.249v0.207h-0.207v0.248h-0.249v0.208H18.48l-0.042 0.248h-0.207v0.208h-0.249v0.249h-0.207v0.207h-0.249v0.249H17.32v0.207h-0.25v0.249h-0.249v0.207h-0.207v0.249h-0.249v0.207h-0.207v0.249h-0.249v0.207h-0.207v0.249h-0.456l-0.042 0.207h-0.456v0.25h-0.663v0.206h-1.162v0.25h5.972v-0.208h-2.737v-0.498h0.207V16.79h0.25v-0.249h0.206v-0.207h1.618v0.249h0.911v-0.25h2.529l0.249 0.042v0.208h0.457v0.705h-0.249v0.207H22.13v0.249h-0.25v0.207h-0.207V18.2h-0.456l-0.042 0.207h-0.663v0.249h-0.706v0.207h-0.662l-0.041 0.249h-0.914v0.207h-0.91v0.25H16.82l-0.248-0.042v0.248h-0.208v0.25h1.617v-0.25h0.459V19.32h0.91v-0.207h0.705v-0.249h0.664v-0.207h0.705v-0.249h0.456v-0.207h0.249v-0.249h0.207v-0.207h0.249v-0.25h0.207v-0.662l0.042-0.248v-0.208h-0.25v-0.248h-3.896v-0.25h0.208z&quot;"
+        errorLine2="                          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/drawable/ic_device_flipper.xml"
+            line="56"
+            column="27"/>
+    </issue>
+
+    <issue
+        id="VectorPath"
+        message="Very long vector path (1901 characters), which is bad for performance. Considering reducing precision, removing minor details or rasterizing vector."
+        errorLine1="        android:pathData=&quot;M20.056 14.718V14.51h0.248v-0.25h0.208v-0.207h0.248v-0.248h0.25v-0.208h0.207V13.35h0.248v-0.208h0.208v-0.249h0.248v-0.207h0.208v-0.249h0.249v-0.912H22.17v-0.207h-0.249l-0.248-0.042h-0.208V11.07h-2.073v0.207h-0.456v0.25H18.48v0.206h-0.456v-0.207h-0.249v-0.497h-0.207V10.57h-0.249v-0.207h-0.207v-0.249h-0.25V9.908h-0.206V9.659H16.2V9.41h-0.457V9.203H15.04V8.954h-1.161V8.747L13.67 8.706h-3.234v0.248H9.524v0.208H8.818V9.41H8.613v0.208H8.156L8.114 9.867H7.907l-0.25-0.042v0.249H7.452v0.249H7.2v0.207H6.996v0.249h-0.25v0.207H6.539l-0.041 0.249v0.207H6.29v0.456H6.04v0.456H5.835v0.706H6.04v-0.458h0.25v-0.455h0.207v-0.456h0.249v-0.456h0.249v-0.249h0.207v-0.207H7.45v-0.25h0.207v-0.206h0.249v-0.25h0.457V9.868h0.456v-0.25h2.778V9.41H9.731V9.162h0.954V8.954h2.986v0.25h0.207V9.41h0.248V9.203h0.208V9.41h1.161v0.457h0.207l0.249 0.041h0.456v0.207h0.249v0.25h0.207v0.206h0.249v0.25h0.207v0.662h-0.207l-0.042 0.248h0.705v0.459h0.456v-0.21h0.456v-0.249h0.456v-0.207h0.498v-0.249h1.576v0.25h0.704v0.248h0.208v0.207h-0.912v0.249h-0.664v0.207h-0.497v0.25h-0.208L19.6 12.644v0.248h-0.207v0.25h-0.249v0.207h-0.207v0.248h-0.249v0.208H18.48l-0.042 0.248h-0.207v0.208h-0.249v0.249h-0.207v0.207h-0.249v0.249H17.32v0.207h-0.25v0.249h-0.249v0.207h-0.207v0.249h-0.249v0.207h-0.207v0.249h-0.249v0.207h-0.207v0.249h-0.456l-0.042 0.207h-0.456v0.25h-0.663v0.206h-1.162v0.25h5.972v-0.208h-2.737v-0.498h0.207V16.79h0.25v-0.249h0.206v-0.207h1.618v0.249h0.911v-0.25h2.529l0.249 0.042v0.208h0.457v0.705h-0.249v0.207H22.13v0.249h-0.25v0.207h-0.207V18.2h-0.456l-0.042 0.207h-0.663v0.249h-0.706v0.207h-0.662l-0.041 0.249h-0.914v0.207h-0.91v0.25H16.82l-0.248-0.042v0.248h-0.208v0.25h1.617v-0.25h0.459V19.32h0.91v-0.207h0.705v-0.249h0.664v-0.207h0.705v-0.249h0.456v-0.207h0.249v-0.249h0.207v-0.207h0.249v-0.25h0.207v-0.662l0.042-0.248v-0.208h-0.25v-0.248h-3.896v-0.25h0.208V15.63l0.248 0.041v-0.248h0.208l0.041-0.25H19.6v-0.206h0.25v-0.25z&quot;"
+        errorLine2="                          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/drawable/ic_device_flipper_disabled.xml"
+            line="61"
+            column="27"/>
+    </issue>
+
+    <issue
+        id="VectorPath"
+        message="Very long vector path (1309 characters), which is bad for performance. Considering reducing precision, removing minor details or rasterizing vector."
+        errorLine1="        android:pathData=&quot;M6.8,6.95C6.22,6.95 5.75,7.42 5.75,8L5.75,20.92C5.73,21 5.74,21.08 5.77,21.16C5.87,21.63 6.3,22 6.8,22L20.45,22C20.97,22 21.4,21.61 21.48,21.1C21.48,21.08 21.49,21.06 21.49,21.04C21.49,21.03 21.49,21.02 21.49,21.02L21.5,20.96C21.5,20.96 21.5,20.95 21.5,20.95L23.24,11.57L23.25,11.53L23.25,11.5C23.25,10.92 22.78,10.45 22.2,10.45L22.2,9.4C22.2,8.82 21.73,8.35 21.15,8.35L12.06,8.35C12.06,8.35 12.06,8.35 12.05,8.35C12.04,8.34 12.01,8.32 11.95,8.25C11.87,8.15 11.77,7.99 11.66,7.81C11.55,7.64 11.43,7.45 11.28,7.29C11.14,7.13 10.94,6.95 10.65,6.95ZM6.8,7.65L10.65,7.65C10.63,7.65 10.67,7.65 10.76,7.75C10.85,7.85 10.95,8.01 11.05,8.19C11.16,8.36 11.28,8.55 11.43,8.71C11.57,8.88 11.76,9.05 12.05,9.05L21.15,9.05C21.35,9.05 21.5,9.2 21.5,9.4L21.5,10.45L8.55,10.45C8,10.45 7.56,10.89 7.52,11.43L7.51,11.43L7.5,11.5L6.45,17.17L6.45,8C6.45,7.8 6.6,7.65 6.8,7.65ZM8.55,11.15L22.2,11.15C22.4,11.15 22.55,11.3 22.55,11.5L20.83,20.79L20.82,20.81C20.82,20.82 20.81,20.84 20.81,20.85C20.81,20.87 20.8,20.88 20.8,20.9C20.8,20.9 20.8,20.91 20.8,20.92C20.8,20.92 20.8,20.92 20.8,20.93C20.79,20.95 20.79,20.96 20.79,20.98C20.79,20.99 20.79,21 20.79,21.02C20.79,21.03 20.79,21.04 20.79,21.06C20.75,21.2 20.61,21.3 20.45,21.3L6.8,21.3C6.6,21.3 6.45,21.15 6.45,20.95L8.19,11.57L8.2,11.53L8.2,11.5C8.2,11.3 8.35,11.15 8.55,11.15Z&quot;"
+        errorLine2="                          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/drawable/ic_device_folder.xml"
+            line="21"
+            column="27"/>
+    </issue>
+
+    <issue
+        id="VectorPath"
+        message="Very long vector path (1309 characters), which is bad for performance. Considering reducing precision, removing minor details or rasterizing vector."
+        errorLine1="      android:pathData=&quot;M6.8,6.95C6.22,6.95 5.75,7.42 5.75,8L5.75,20.92C5.73,21 5.74,21.08 5.77,21.16C5.87,21.63 6.3,22 6.8,22L20.45,22C20.97,22 21.4,21.61 21.48,21.1C21.48,21.08 21.49,21.06 21.49,21.04C21.49,21.03 21.49,21.02 21.49,21.02L21.5,20.96C21.5,20.96 21.5,20.95 21.5,20.95L23.24,11.57L23.25,11.53L23.25,11.5C23.25,10.92 22.78,10.45 22.2,10.45L22.2,9.4C22.2,8.82 21.73,8.35 21.15,8.35L12.06,8.35C12.06,8.35 12.06,8.35 12.05,8.35C12.04,8.34 12.01,8.32 11.95,8.25C11.87,8.15 11.77,7.99 11.66,7.81C11.55,7.64 11.43,7.45 11.28,7.29C11.14,7.13 10.94,6.95 10.65,6.95ZM6.8,7.65L10.65,7.65C10.63,7.65 10.67,7.65 10.76,7.75C10.85,7.85 10.95,8.01 11.05,8.19C11.16,8.36 11.28,8.55 11.43,8.71C11.57,8.88 11.76,9.05 12.05,9.05L21.15,9.05C21.35,9.05 21.5,9.2 21.5,9.4L21.5,10.45L8.55,10.45C8,10.45 7.56,10.89 7.52,11.43L7.51,11.43L7.5,11.5L6.45,17.17L6.45,8C6.45,7.8 6.6,7.65 6.8,7.65ZM8.55,11.15L22.2,11.15C22.4,11.15 22.55,11.3 22.55,11.5L20.83,20.79L20.82,20.81C20.82,20.82 20.81,20.84 20.81,20.85C20.81,20.87 20.8,20.88 20.8,20.9C20.8,20.9 20.8,20.91 20.8,20.92C20.8,20.92 20.8,20.92 20.8,20.93C20.79,20.95 20.79,20.96 20.79,20.98C20.79,20.99 20.79,21 20.79,21.02C20.79,21.03 20.79,21.04 20.79,21.06C20.75,21.2 20.61,21.3 20.45,21.3L6.8,21.3C6.6,21.3 6.45,21.15 6.45,20.95L8.19,11.57L8.2,11.53L8.2,11.5C8.2,11.3 8.35,11.15 8.55,11.15Z&quot;"
+        errorLine2="                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/drawable/ic_device_folder_disabled.xml"
+            line="30"
+            column="25"/>
+    </issue>
+
+    <issue
+        id="VectorPath"
+        message="Very long vector path (1379 characters), which is bad for performance. Considering reducing precision, removing minor details or rasterizing vector."
+        errorLine1="        android:pathData=&quot;m21.2919,23.2015c0.6342,-0.1548 1.0149,-0.392 1.4344,-0.894 0.7094,-0.8486 0.8512,-2.1283 0.3502,-3.158 -0.1582,-0.3251 -0.377,-0.6401 -0.4861,-0.6999 -0.1847,-0.1012 -0.1868,-0.1424 -0.0305,-0.5933 0.4121,-1.1887 0.2451,-3.014 -0.384,-4.1977 -0.3848,-0.724 -1.2208,-1.4895 -1.8766,-1.7183 -1.3054,-0.4555 -3.136,0.051 -4.0509,1.1204 -1.418,1.6577 -1.7887,3.8542 -0.9565,5.6672 0.2793,0.6085 1.4226,1.7934 2.2541,2.3362 0.402,0.2624 0.6604,0.5313 0.7933,0.8255 0.4347,0.9623 1.8352,1.5845 2.9527,1.3118zM19.7347,22.8078c-0.4177,-0.1787 -0.9625,-0.6424 -1.1335,-0.9646 -0.0846,-0.1596 -0.0299,-0.161 0.5561,-0.0144 1.1521,0.2882 1.9675,0.0109 2.6995,-0.9215 0.3051,-0.3883 0.346,-0.5332 0.3978,-1.4115l0.0577,-0.9789 0.3002,0.3663c0.9623,1.1744 0.5377,3.1707 -0.821,3.8594 -0.5724,0.2901 -1.4649,0.3184 -2.0568,0.0651zM18.7182,21.4086c-1.1454,-0.4457 -2.8916,-2.0023 -3.2716,-2.9163 -0.9165,-2.2045 0.1396,-5.1562 2.19,-6.1208 0.9436,-0.4439 2.0937,-0.462 2.8908,-0.0454 1.6982,0.8873 2.465,3.4189 1.7138,5.6589 -0.1131,0.3371 -0.2067,0.9238 -0.208,1.3038 -0.0056,1.476 -0.786,2.3326 -2.1276,2.3341 -0.3572,0.0004 -0.8771,-0.0933 -1.1874,-0.2141zM18.7696,18.7483c0.0941,-0.1471 0.1413,-0.347 0.1049,-0.4442 -0.0365,-0.0973 -0.4674,-0.3424 -0.9577,-0.5448 -0.994,-0.4103 -1.2506,-0.3717 -1.2506,0.1881 0,0.2954 0.0735,0.3564 0.8303,0.6882 1.0117,0.4436 1.0587,0.4478 1.2731,0.1126z&quot;"
+        errorLine2="                          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/drawable/ic_device_galaxy_buds_pro.xml"
+            line="23"
+            column="27"/>
+    </issue>
+
+    <issue
+        id="VectorPath"
+        message="Very long vector path (1376 characters), which is bad for performance. Considering reducing precision, removing minor details or rasterizing vector."
+        errorLine1="        android:pathData=&quot;m7.8637,17.6849c-0.6433,-0.1548 -1.0294,-0.392 -1.455,-0.894 -0.7195,-0.8486 -0.8635,-2.1283 -0.3552,-3.158 0.1605,-0.3251 0.3824,-0.6401 0.4931,-0.6999 0.1874,-0.1012 0.1895,-0.1424 0.031,-0.5932 -0.418,-1.1887 -0.2486,-3.014 0.3895,-4.1977 0.3903,-0.724 1.2383,-1.4895 1.9035,-1.7183 1.3241,-0.4555 3.181,0.0509 4.1091,1.1204 1.4383,1.6578 1.8143,3.8542 0.9702,5.6672 -0.2833,0.6085 -1.443,1.7934 -2.2864,2.3362 -0.4078,0.2624 -0.6699,0.5313 -0.8047,0.8255 -0.4409,0.9623 -1.8616,1.5845 -2.995,1.3118zM9.4431,17.2912c0.4237,-0.1787 0.9763,-0.6423 1.1497,-0.9646 0.0858,-0.1596 0.0303,-0.161 -0.5641,-0.0144 -1.1687,0.2882 -1.9957,0.0108 -2.7382,-0.9215 -0.3095,-0.3882 -0.3509,-0.5332 -0.4035,-1.4115l-0.0586,-0.9789 -0.3045,0.3663c-0.9761,1.1744 -0.5454,3.1707 0.8327,3.8594 0.5806,0.2901 1.4859,0.3184 2.0864,0.0652zM10.4743,15.892c1.1618,-0.4457 2.9331,-2.0023 3.3186,-2.9163C14.7225,10.7711 13.6513,7.8195 11.5715,6.8549 10.6144,6.411 9.4477,6.3929 8.6392,6.8094c-1.7226,0.8873 -2.5003,3.419 -1.7384,5.6589 0.1147,0.3371 0.2096,0.9238 0.211,1.3038 0.0052,1.4759 0.7973,2.3326 2.1581,2.3341 0.3623,0.0004 0.8897,-0.0933 1.2044,-0.2141zM10.4222,13.2317c-0.0955,-0.1471 -0.1434,-0.347 -0.1064,-0.4442 0.037,-0.0973 0.4741,-0.3424 0.9715,-0.5448 1.0082,-0.4103 1.2686,-0.3717 1.2686,0.1881 0,0.2954 -0.0746,0.3564 -0.8422,0.6882 -1.0262,0.4436 -1.0739,0.4478 -1.2914,0.1126z&quot;"
+        errorLine2="                          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/drawable/ic_device_galaxy_buds_pro.xml"
+            line="28"
+            column="27"/>
+    </issue>
+
+    <issue
+        id="VectorPath"
+        message="Very long vector path (1379 characters), which is bad for performance. Considering reducing precision, removing minor details or rasterizing vector."
+        errorLine1="        android:pathData=&quot;m21.2919,23.2015c0.6342,-0.1548 1.0149,-0.392 1.4344,-0.894 0.7094,-0.8486 0.8512,-2.1283 0.3502,-3.158 -0.1582,-0.3251 -0.377,-0.6401 -0.4861,-0.6999 -0.1847,-0.1012 -0.1868,-0.1424 -0.0305,-0.5933 0.4121,-1.1887 0.2451,-3.014 -0.384,-4.1977 -0.3848,-0.724 -1.2208,-1.4895 -1.8766,-1.7183 -1.3054,-0.4555 -3.136,0.051 -4.0509,1.1204 -1.418,1.6577 -1.7887,3.8542 -0.9565,5.6672 0.2793,0.6085 1.4226,1.7934 2.2541,2.3362 0.402,0.2624 0.6604,0.5313 0.7933,0.8255 0.4347,0.9623 1.8352,1.5845 2.9527,1.3118zM19.7347,22.8078c-0.4177,-0.1787 -0.9625,-0.6424 -1.1335,-0.9646 -0.0846,-0.1596 -0.0299,-0.161 0.5561,-0.0144 1.1521,0.2882 1.9675,0.0109 2.6995,-0.9215 0.3051,-0.3883 0.346,-0.5332 0.3978,-1.4115l0.0577,-0.9789 0.3002,0.3663c0.9623,1.1744 0.5377,3.1707 -0.821,3.8594 -0.5724,0.2901 -1.4649,0.3184 -2.0568,0.0651zM18.7182,21.4086c-1.1454,-0.4457 -2.8916,-2.0023 -3.2716,-2.9163 -0.9165,-2.2045 0.1396,-5.1562 2.19,-6.1208 0.9436,-0.4439 2.0937,-0.462 2.8908,-0.0454 1.6982,0.8873 2.465,3.4189 1.7138,5.6589 -0.1131,0.3371 -0.2067,0.9238 -0.208,1.3038 -0.0056,1.476 -0.786,2.3326 -2.1276,2.3341 -0.3572,0.0004 -0.8771,-0.0933 -1.1874,-0.2141zM18.7696,18.7483c0.0941,-0.1471 0.1413,-0.347 0.1049,-0.4442 -0.0365,-0.0973 -0.4674,-0.3424 -0.9577,-0.5448 -0.994,-0.4103 -1.2506,-0.3717 -1.2506,0.1881 0,0.2954 0.0735,0.3564 0.8303,0.6882 1.0117,0.4436 1.0587,0.4478 1.2731,0.1126z&quot;"
+        errorLine2="                          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/drawable/ic_device_galaxy_buds_pro_disabled.xml"
+            line="28"
+            column="27"/>
+    </issue>
+
+    <issue
+        id="VectorPath"
+        message="Very long vector path (1376 characters), which is bad for performance. Considering reducing precision, removing minor details or rasterizing vector."
+        errorLine1="        android:pathData=&quot;m7.8637,17.6849c-0.6433,-0.1548 -1.0294,-0.392 -1.455,-0.894 -0.7195,-0.8486 -0.8635,-2.1283 -0.3552,-3.158 0.1605,-0.3251 0.3824,-0.6401 0.4931,-0.6999 0.1874,-0.1012 0.1895,-0.1424 0.031,-0.5932 -0.418,-1.1887 -0.2486,-3.014 0.3895,-4.1977 0.3903,-0.724 1.2383,-1.4895 1.9035,-1.7183 1.3241,-0.4555 3.181,0.0509 4.1091,1.1204 1.4383,1.6578 1.8143,3.8542 0.9702,5.6672 -0.2833,0.6085 -1.443,1.7934 -2.2864,2.3362 -0.4078,0.2624 -0.6699,0.5313 -0.8047,0.8255 -0.4409,0.9623 -1.8616,1.5845 -2.995,1.3118zM9.4431,17.2912c0.4237,-0.1787 0.9763,-0.6423 1.1497,-0.9646 0.0858,-0.1596 0.0303,-0.161 -0.5641,-0.0144 -1.1687,0.2882 -1.9957,0.0108 -2.7382,-0.9215 -0.3095,-0.3882 -0.3509,-0.5332 -0.4035,-1.4115l-0.0586,-0.9789 -0.3045,0.3663c-0.9761,1.1744 -0.5454,3.1707 0.8327,3.8594 0.5806,0.2901 1.4859,0.3184 2.0864,0.0652zM10.4743,15.892c1.1618,-0.4457 2.9331,-2.0023 3.3186,-2.9163C14.7225,10.7711 13.6513,7.8195 11.5715,6.8549 10.6144,6.411 9.4477,6.3929 8.6392,6.8094c-1.7226,0.8873 -2.5003,3.419 -1.7384,5.6589 0.1147,0.3371 0.2096,0.9238 0.211,1.3038 0.0052,1.4759 0.7973,2.3326 2.1581,2.3341 0.3623,0.0004 0.8897,-0.0933 1.2044,-0.2141zM10.4222,13.2317c-0.0955,-0.1471 -0.1434,-0.347 -0.1064,-0.4442 0.037,-0.0973 0.4741,-0.3424 0.9715,-0.5448 1.0082,-0.4103 1.2686,-0.3717 1.2686,0.1881 0,0.2954 -0.0746,0.3564 -0.8422,0.6882 -1.0262,0.4436 -1.0739,0.4478 -1.2914,0.1126z&quot;"
+        errorLine2="                          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/drawable/ic_device_galaxy_buds_pro_disabled.xml"
+            line="33"
+            column="27"/>
+    </issue>
+
+    <issue
+        id="VectorPath"
+        message="Very long vector path (905 characters), which is bad for performance. Considering reducing precision, removing minor details or rasterizing vector."
+        errorLine1="        android:pathData=&quot;M5.762 14.02l0.023-0.226c0.052-0.503 0.266-1.351 0.391-1.55 0.025-0.038 0.077-0.09 0.116-0.115 0.138-0.089 0.468-0.16 1.016-0.223 0.155-0.018 0.304-0.04 0.331-0.05 0.07-0.028 0.27-0.068 0.33-0.068 0.03 0 0.153-0.05 0.29-0.117 0.465-0.23 0.971-0.357 1.597-0.403 0.32-0.024 8.726-0.024 9.05 0 0.652 0.047 1.177 0.187 1.687 0.45l0.244 0.125 0.31 0.03c0.597 0.06 1.044 0.138 1.217 0.215 0.098 0.043 0.19 0.128 0.235 0.219 0.086 0.17 0.226 0.687 0.304 1.121 0.153 0.855 0.104 1.63-0.163 2.59-0.084 0.303-0.14 0.436-0.219 0.52-0.138 0.147-0.523 0.238-1.349 0.32l-0.335 0.032-0.277 0.14c-0.497 0.252-0.966 0.378-1.612 0.436-0.35 0.032-8.734 0.032-9.118 0-0.66-0.053-1.201-0.2-1.68-0.453C8.052 16.96 8 16.945 7.906 16.936c-0.066-0.006-0.168-0.025-0.227-0.043-0.059-0.017-0.265-0.051-0.46-0.075-0.767-0.095-0.967-0.168-1.08-0.392-0.052-0.103-0.187-0.563-0.247-0.844L5.851 15.39c-0.075-0.336-0.121-1.042-0.09-1.368z&quot;"
+        errorLine2="                          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/drawable/ic_device_miband6.xml"
+            line="20"
+            column="27"/>
+    </issue>
+
+    <issue
+        id="VectorPath"
+        message="Very long vector path (905 characters), which is bad for performance. Considering reducing precision, removing minor details or rasterizing vector."
+        errorLine1="        android:pathData=&quot;M5.762 14.02l0.023-0.226c0.052-0.503 0.266-1.351 0.391-1.55 0.025-0.038 0.077-0.09 0.116-0.115 0.138-0.089 0.468-0.16 1.016-0.223 0.155-0.018 0.304-0.04 0.331-0.05 0.07-0.028 0.27-0.068 0.33-0.068 0.03 0 0.153-0.05 0.29-0.117 0.465-0.23 0.971-0.357 1.597-0.403 0.32-0.024 8.726-0.024 9.05 0 0.652 0.047 1.177 0.187 1.687 0.45l0.244 0.125 0.31 0.03c0.597 0.06 1.044 0.138 1.217 0.215 0.098 0.043 0.19 0.128 0.235 0.219 0.086 0.17 0.226 0.687 0.304 1.121 0.153 0.855 0.104 1.63-0.163 2.59-0.084 0.303-0.14 0.436-0.219 0.52-0.138 0.147-0.523 0.238-1.349 0.32l-0.335 0.032-0.277 0.14c-0.497 0.252-0.966 0.378-1.612 0.436-0.35 0.032-8.734 0.032-9.118 0-0.66-0.053-1.201-0.2-1.68-0.453C8.052 16.96 8 16.945 7.906 16.936c-0.066-0.006-0.168-0.025-0.227-0.043-0.059-0.017-0.266-0.051-0.46-0.075-0.767-0.095-0.967-0.168-1.08-0.392-0.052-0.103-0.187-0.563-0.247-0.844L5.851 15.39c-0.075-0.336-0.121-1.042-0.09-1.368z&quot;"
+        errorLine2="                          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/drawable/ic_device_miband6_disabled.xml"
+            line="25"
+            column="27"/>
+    </issue>
+
+    <issue
+        id="VectorPath"
+        message="Very long vector path (1432 characters), which is bad for performance. Considering reducing precision, removing minor details or rasterizing vector."
+        errorLine1="        android:pathData=&quot;M7.291 6.092L7.194 6.145C7.162 6.163 7.13 6.183 7.099 6.203l-0.1 0.067L6.89 6.35 6.815 6.405 6.74 6.466C6.691 6.507 6.642 6.55 6.595 6.595 6.547 6.64 6.5 6.685 6.455 6.734 6.41 6.78 6.365 6.83 6.323 6.88 6.28 6.93 6.24 6.983 6.2 7.035c-0.04 0.053-0.078 0.106-0.114 0.16C6.05 7.25 6.015 7.304 5.982 7.36L5.935 7.442C5.92 7.47 5.905 7.498 5.892 7.526c-0.037 0.071-0.07 0.14-0.1 0.206-0.03 0.065-0.058 0.128-0.083 0.19-0.025 0.061-0.047 0.12-0.067 0.18-0.01 0.03-0.02 0.059-0.028 0.088-0.01 0.03-0.017 0.058-0.025 0.088L5.567 8.366l-0.02 0.09c-0.011 0.06-0.021 0.12-0.03 0.184-0.008 0.063-0.015 0.13-0.02 0.198-0.007 0.069-0.01 0.14-0.014 0.217L5.477 9.219v0.157C5.478 9.428 5.48 9.478 5.483 9.527c0.003 0.05 0.007 0.099 0.012 0.147C5.5 9.722 5.505 9.77 5.513 9.818 5.52 9.866 5.529 9.914 5.538 9.961l0.033 0.144 0.042 0.146c0.015 0.05 0.031 0.1 0.05 0.15 0.018 0.051 0.037 0.103 0.059 0.156 0.02 0.053 0.044 0.108 0.068 0.163l0.079 0.173c0.028 0.059 0.058 0.12 0.09 0.182 0.031 0.063 0.065 0.128 0.1 0.196l0.113 0.209 0.125 0.224 0.19 0.325 0.188 0.312 0.184 0.3 0.182 0.287 0.18 0.275c0.059 0.09 0.117 0.177 0.176 0.262 0.058 0.086 0.116 0.17 0.173 0.251l0.17 0.24 0.169 0.227c0.055 0.074 0.11 0.146 0.165 0.217 0.054 0.07 0.108 0.138 0.162 0.205l0.008 0.01C8.02 14.582 7.132 13.792 6.67 13.21c-1.06-1.33-1.604-2.907-1.441-4.18 0.148-1.165 0.664-2.116 1.624-2.991C7.09 5.821 7.117 5.85 7.187 5.871 7.424 6.016 7.452 6.003 7.29 6.092z&quot;"
+        errorLine2="                          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/drawable/ic_device_sony_wf_800n.xml"
+            line="19"
+            column="27"/>
+    </issue>
+
+    <issue
+        id="VectorPath"
+        message="Very long vector path (1981 characters), which is bad for performance. Considering reducing precision, removing minor details or rasterizing vector."
+        errorLine1="        android:pathData=&quot;M12.193 17.066c0.625-0.132 1.334-0.604 1.68-1.12 0.53-0.787 0.661-1.615 0.413-2.61-0.445-1.784-1.737-5.003-2.422-6.035-0.49-0.738-1.147-1.226-1.985-1.474-0.284-0.084-0.55-0.118-0.97-0.127-0.244-0.005-0.653 0.037-1.041 0.174-0.486 0.169-0.64 0.23-0.95 0.458-0.406 0.296-0.789 0.73-1.013 1.175-0.29 0.576-0.392 0.938-0.419 1.547-0.04 0.9 0.118 1.412 0.816 2.655 1.003 1.681 1.225 2.02 2.372 3.434 0.232 0.3 0.63 0.757 0.883 1.016 0.373 0.381 0.524 0.502 0.787 0.633 0.599 0.296 1.271 0.396 1.85 0.274zm9.319-4.474c0.034 0.017 0.066 0.034 0.097 0.053 0.032 0.018 0.064 0.037 0.096 0.058 0.032 0.02 0.064 0.043 0.099 0.067l0.109 0.079 0.075 0.057 0.075 0.06c0.05 0.041 0.098 0.084 0.146 0.129 0.047 0.044 0.094 0.09 0.14 0.138 0.045 0.048 0.089 0.097 0.131 0.148 0.043 0.05 0.084 0.102 0.124 0.154 0.04 0.052 0.078 0.106 0.114 0.16 0.036 0.054 0.07 0.109 0.103 0.164l0.047 0.083 0.044 0.083c0.036 0.072 0.07 0.14 0.1 0.206 0.03 0.066 0.057 0.129 0.082 0.19 0.025 0.062 0.047 0.121 0.067 0.18l0.029 0.088 0.025 0.088 0.022 0.089 0.019 0.089c0.012 0.06 0.022 0.121 0.03 0.185 0.009 0.063 0.016 0.129 0.021 0.198 0.006 0.068 0.01 0.14 0.014 0.217l0.005 0.164v0.157c0 0.051-0.003 0.102-0.006 0.151-0.002 0.05-0.006 0.098-0.011 0.147-0.005 0.048-0.011 0.096-0.019 0.144-0.007 0.048-0.015 0.095-0.025 0.143-0.01 0.048-0.02 0.096-0.033 0.144-0.012 0.048-0.026 0.097-0.041 0.146-0.015 0.05-0.032 0.1-0.05 0.15-0.018 0.051-0.038 0.103-0.06 0.156-0.02 0.053-0.043 0.107-0.068 0.163-0.024 0.056-0.05 0.113-0.079 0.172-0.028 0.06-0.058 0.12-0.09 0.183-0.03 0.063-0.064 0.128-0.1 0.195l-0.113 0.21-0.124 0.224-0.19 0.325-0.188 0.312-0.185 0.3-0.182 0.286-0.179 0.275-0.176 0.263c-0.059 0.086-0.116 0.17-0.174 0.251l-0.17 0.24-0.168 0.227c-0.056 0.074-0.11 0.146-0.165 0.216-0.055 0.07-0.109 0.139-0.163 0.205l-0.008 0.01c0.224-0.032 1.112-0.822 1.575-1.403 1.06-1.332 1.603-2.909 1.44-4.182-0.148-1.165-0.664-2.116-1.623-2.991-0.239-0.218-0.265-0.188-0.334-0.167-0.238 0.144-0.265 0.132-0.105 0.22z&quot;"
+        errorLine2="                          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/drawable/ic_device_sony_wf_800n.xml"
+            line="40"
+            column="27"/>
+    </issue>
+
+    <issue
+        id="VectorPath"
+        message="Very long vector path (1432 characters), which is bad for performance. Considering reducing precision, removing minor details or rasterizing vector."
+        errorLine1="        android:pathData=&quot;M7.291 6.092L7.194 6.145C7.162 6.163 7.13 6.183 7.099 6.203l-0.1 0.067L6.89 6.35 6.815 6.405 6.74 6.466C6.691 6.507 6.642 6.55 6.595 6.595 6.547 6.64 6.5 6.685 6.455 6.734 6.41 6.78 6.365 6.83 6.323 6.88 6.28 6.93 6.24 6.983 6.2 7.035c-0.04 0.053-0.078 0.106-0.114 0.16C6.05 7.25 6.015 7.304 5.982 7.36L5.935 7.442C5.92 7.47 5.905 7.498 5.892 7.526c-0.037 0.071-0.07 0.14-0.1 0.206-0.03 0.065-0.058 0.128-0.083 0.19-0.025 0.061-0.047 0.12-0.067 0.18-0.01 0.03-0.02 0.059-0.028 0.088-0.01 0.03-0.017 0.058-0.025 0.088L5.567 8.366l-0.02 0.09c-0.011 0.06-0.021 0.12-0.03 0.184-0.008 0.063-0.015 0.13-0.02 0.198-0.007 0.069-0.01 0.14-0.014 0.217L5.477 9.219v0.157C5.478 9.428 5.48 9.478 5.483 9.527c0.003 0.05 0.007 0.099 0.012 0.147C5.5 9.722 5.505 9.77 5.513 9.818 5.52 9.866 5.529 9.914 5.538 9.961l0.033 0.144 0.042 0.146c0.015 0.05 0.031 0.1 0.05 0.15 0.018 0.051 0.037 0.103 0.059 0.156 0.02 0.053 0.044 0.108 0.068 0.163l0.079 0.173c0.028 0.059 0.058 0.12 0.09 0.182 0.031 0.063 0.065 0.128 0.1 0.196l0.113 0.209 0.125 0.224 0.19 0.325 0.188 0.312 0.184 0.3 0.182 0.287 0.18 0.275c0.059 0.09 0.117 0.177 0.176 0.262 0.058 0.086 0.116 0.17 0.173 0.251l0.17 0.24 0.169 0.227c0.055 0.074 0.11 0.146 0.165 0.217 0.054 0.07 0.108 0.138 0.162 0.205l0.008 0.01C8.02 14.582 7.132 13.792 6.67 13.21c-1.06-1.33-1.604-2.907-1.441-4.18 0.148-1.165 0.664-2.116 1.624-2.991C7.09 5.821 7.117 5.85 7.187 5.871 7.424 6.016 7.452 6.003 7.29 6.092z&quot;"
+        errorLine2="                          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/drawable/ic_device_sony_wf_800n_disabled.xml"
+            line="24"
+            column="27"/>
+    </issue>
+
+    <issue
+        id="VectorPath"
+        message="Very long vector path (1981 characters), which is bad for performance. Considering reducing precision, removing minor details or rasterizing vector."
+        errorLine1="        android:pathData=&quot;M12.193 17.066c0.625-0.132 1.334-0.604 1.68-1.12 0.53-0.787 0.661-1.615 0.413-2.61-0.445-1.784-1.737-5.003-2.422-6.035-0.49-0.738-1.147-1.226-1.985-1.474-0.284-0.084-0.55-0.118-0.97-0.127-0.244-0.005-0.653 0.037-1.041 0.174-0.486 0.169-0.64 0.23-0.95 0.458-0.406 0.296-0.789 0.73-1.013 1.175-0.29 0.576-0.392 0.938-0.419 1.547-0.04 0.9 0.118 1.412 0.816 2.655 1.003 1.681 1.225 2.02 2.372 3.434 0.232 0.3 0.63 0.757 0.883 1.016 0.373 0.381 0.524 0.502 0.787 0.633 0.599 0.296 1.271 0.396 1.85 0.274zm9.319-4.474c0.034 0.017 0.066 0.034 0.097 0.053 0.032 0.018 0.064 0.037 0.096 0.058 0.032 0.02 0.064 0.043 0.099 0.067l0.109 0.079 0.075 0.057 0.075 0.06c0.05 0.041 0.098 0.084 0.146 0.129 0.047 0.044 0.094 0.09 0.14 0.138 0.045 0.048 0.089 0.097 0.131 0.148 0.043 0.05 0.084 0.102 0.124 0.154 0.04 0.052 0.078 0.106 0.114 0.16 0.036 0.054 0.07 0.109 0.103 0.164l0.047 0.083 0.044 0.083c0.036 0.072 0.07 0.14 0.1 0.206 0.03 0.066 0.057 0.129 0.082 0.19 0.025 0.062 0.047 0.121 0.067 0.18l0.029 0.088 0.025 0.088 0.022 0.089 0.019 0.089c0.012 0.06 0.022 0.121 0.03 0.185 0.009 0.063 0.016 0.129 0.021 0.198 0.006 0.068 0.01 0.14 0.014 0.217l0.005 0.164v0.157c0 0.051-0.003 0.102-0.006 0.151-0.002 0.05-0.006 0.098-0.011 0.147-0.005 0.048-0.011 0.096-0.019 0.144-0.007 0.048-0.015 0.095-0.025 0.143-0.01 0.048-0.02 0.096-0.033 0.144-0.012 0.048-0.026 0.097-0.041 0.146-0.015 0.05-0.032 0.1-0.05 0.15-0.018 0.051-0.038 0.103-0.06 0.156-0.02 0.053-0.043 0.107-0.068 0.163-0.024 0.056-0.05 0.113-0.079 0.172-0.028 0.06-0.058 0.12-0.09 0.183-0.03 0.063-0.064 0.128-0.1 0.195l-0.113 0.21-0.124 0.224-0.19 0.325-0.188 0.312-0.185 0.3-0.182 0.286-0.179 0.275-0.176 0.263c-0.059 0.086-0.116 0.17-0.174 0.251l-0.17 0.24-0.168 0.227c-0.056 0.074-0.11 0.146-0.165 0.216-0.055 0.07-0.109 0.139-0.163 0.205l-0.008 0.01c0.224-0.032 1.112-0.822 1.575-1.403 1.06-1.332 1.603-2.909 1.44-4.182-0.148-1.165-0.664-2.116-1.623-2.991-0.239-0.218-0.265-0.188-0.334-0.167-0.238 0.144-0.265 0.132-0.105 0.22z&quot;"
+        errorLine2="                          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/drawable/ic_device_sony_wf_800n_disabled.xml"
+            line="45"
+            column="27"/>
+    </issue>
+
+    <issue
+        id="VectorPath"
+        message="Very long vector path (1424 characters), which is bad for performance. Considering reducing precision, removing minor details or rasterizing vector."
+        errorLine1="        android:pathData=&quot;M17.266 33.844c0-2.401 1.482-4.458 3.581-5.31l-0.098 0.005c-2.717-2.464-2.5-3.827-2.547-4.84v-0.511c-4.804 2.836-10.196 6.529-11.886 4.839L5.892 27.01c5.654-3.903 11.31-7.806 15.96-11.714 0.397-0.434 1.05-0.699 2.122-0.681 4.933 0.049 9.609 0.436 14.096 0.934l5.518-2.378-0.171 7.047c-4.53 4.225-11.256 6.388-16.326 9.616 1.014 1.034 1.641 2.451 1.641 4.01 0 3.164-2.568 5.733-5.734 5.733-3.163 0-5.732-2.569-5.732-5.733zm9.98 0c0-2.34-1.905-4.243-4.248-4.243s-4.246 1.903-4.246 4.243c0 2.343 1.903 4.245 4.246 4.245 2.343 0 4.248-1.902 4.248-4.245zm-6.049 2.308c-0.355-0.375-0.589-0.841-0.7-1.401h-0.612l0.133-0.639h0.405c-0.003-0.066-0.008-0.139-0.008-0.213 0-0.128 0.005-0.237 0.011-0.333h-0.541l0.133-0.641h0.491c0.116-0.55 0.345-1.009 0.689-1.377 0.523-0.557 1.205-0.833 2.055-0.833 0.463 0 0.865 0.083 1.205 0.249l-0.242 1.147c-0.236-0.235-0.576-0.351-1.026-0.351-0.45 0-0.812 0.161-1.089 0.484-0.158 0.18-0.269 0.409-0.335 0.681h2.274l-0.135 0.641h-2.222c-0.003 0.061-0.004 0.143-0.004 0.242 0 0.096 0.001 0.198 0.007 0.304h2.105l-0.132 0.639h-1.887c0.07 0.296 0.174 0.528 0.321 0.693 0.274 0.326 0.635 0.489 1.073 0.489 0.528 0 0.951-0.163 1.265-0.489v1.266c-0.358 0.18-0.775 0.269-1.252 0.269-0.804 0-1.466-0.277-1.982-0.827zm8.297-11.86c0.709-1.712 0.594-4.211-4.755-2.971-2.961 1.242-3.613 2.611-2.547 4.076 0.595 0.911 0.932 1.816 1.137 2.725 0.602 0.035 1.178 0.162 1.718 0.37 1.429-1.572 3.837-2.733 4.447-4.2z&quot;"
+        errorLine2="                          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/drawable/ic_donate.xml"
+            line="9"
+            column="27"/>
+    </issue>
+
+    <issue
+        id="VectorPath"
+        message="Very long vector path (6628 characters), which is bad for performance. Considering reducing precision, removing minor details or rasterizing vector."
+        errorLine1="        android:pathData=&quot;M5.7484,0C4.909,0.4048 3.9699,0.7895 3.4635,1.8877 3.1075,2.4815 3.0984,3.2545 2.8474,3.9012 3.3122,3.359 4.0318,2.8038 4.4418,2.1731 4.8992,1.3809 5.1318,0.6712 5.7484,0ZM18.2516,0c0.6166,0.6712 0.8493,1.3809 1.3067,2.1731 0.41,0.6308 1.1294,1.186 1.5943,1.7281C20.9016,3.2545 20.8925,2.4815 20.5365,1.8877 20.0301,0.7895 19.091,0.4048 18.2516,0ZM3.4844,1.4146C3.0566,1.5498 2.6763,1.8713 2.3764,2.3345 1.6854,3.2346 1.5669,4.4154 1.8533,5.8391 1.9518,6.2156 1.8117,6.5683 1.8316,6.9198 2.0346,6.4032 2.2645,5.9282 2.7115,5.5956 3.7081,4.8955 3.9823,4.5248 4.2373,3.8647 4.5371,3.0886 4.5929,2.9733 4.5995,2.2768 4.3103,3.2786 3.4374,3.6991 2.8474,4.3755 2.5442,4.7446 2.3013,5.1724 2.1631,5.6254L2.1419,5.6046C2.2145,5.2038 2.3604,4.8436 2.4444,4.4526 2.7078,3.3954 2.6537,2.1228 3.4844,1.4146ZM20.4876,1.4146c0.8307,0.7082 0.7766,1.9808 1.04,3.038 0.084,0.391 0.2299,0.7511 0.3025,1.1519l-0.0212,0.0208C21.6706,5.1724 21.4277,4.7446 21.1245,4.3755 20.5345,3.6991 19.6616,3.2786 19.3725,2.2768c0.0066,0.6965 0.0625,0.8118 0.3623,1.5879 0.255,0.66 0.529,1.0308 1.5257,1.7309 0.447,0.3326 0.6769,0.8075 0.8798,1.3242C22.1602,6.5683 22.0201,6.2156 22.1186,5.8391 22.405,4.4154 22.2865,3.2346 21.5955,2.3345 21.2956,1.8713 20.9154,1.5498 20.4876,1.4146ZM1.7114,3.3085c-0.0217,-0.0051 -0.0335,0.0158 -0.0503,0.0262C1.0818,4.2517 0.5761,4.9453 0.6011,6.1777 0.5231,7.6942 1.4002,9.0766 1.4882,10.5069 1.6212,9.412 2.3095,8.6385 2.879,7.7899 2.804,8.1525 2.7168,8.5038 2.5663,8.8193 1.9424,9.9924 1.3214,11.385 1.7515,12.886 1.7415,12.9018 1.7275,12.9227 1.7114,12.9119 1.5386,12.4159 1.509,11.8485 1.3881,11.3201 1.0992,9.9073 0.2186,8.7316 0.474,7.1031 0.3392,7.4564 0.2846,7.8552 0.2132,8.2405 0.0625,9.2361 -0.0178,9.8503 0.2035,10.7649 0.4725,11.912 1.6056,13.1775 2.0631,14.228l0.0625,0.0622C2.0589,13.7202 2.0249,13.1067 2.1547,12.5632 2.3518,11.6934 2.8936,10.8433 3.0453,9.9468 3.1244,9.2437 2.9481,8.4642 2.8814,7.7863 3.087,7.4797 3.2774,7.1634 3.4198,6.8182 3.6078,6.2973 3.529,5.5561 3.5853,4.9925 3.5096,5.2325 3.3793,5.44 3.2319,5.6443 2.5417,6.4252 1.7515,7.2268 1.6013,8.3586 1.5761,8.5194 1.568,8.6866 1.5303,8.8429 1.5007,8.8591 1.5007,8.817 1.4882,8.8068L1.5723,7.4034C1.6014,6.0326 1.0737,4.5128 1.7114,3.3085ZM22.2886,3.3085c0.6377,1.2042 0.1102,2.7241 0.1392,4.0948l0.084,1.4034c-0.0125,0.0102 -0.0124,0.0523 -0.042,0.0362 -0.0377,-0.1563 -0.0459,-0.3235 -0.0711,-0.4844C22.2485,7.2268 21.4583,6.4252 20.7681,5.6443 20.6207,5.44 20.4904,5.2325 20.4147,4.9925c0.0564,0.5636 -0.0223,1.3048 0.1657,1.8257 0.1424,0.3452 0.3327,0.6614 0.5382,0.9681 -0.0667,0.678 -0.2428,1.4574 -0.1637,2.1605 0.1517,0.8965 0.6935,1.7466 0.8906,2.6164 0.1297,0.5435 0.0956,1.1571 0.029,1.727l0.0625,-0.0622C22.3944,13.1775 23.5276,11.912 23.7966,10.7649 24.018,9.8503 23.9375,9.2361 23.7868,8.2405 23.7154,7.8552 23.6608,7.4564 23.526,7.1031c0.2554,1.6286 -0.6252,2.8043 -0.9141,4.217 -0.1209,0.5284 -0.1505,1.0958 -0.3233,1.5918 -0.0161,0.0109 -0.0301,-0.01 -0.0401,-0.0259 0.4301,-1.501 -0.1907,-2.8935 -0.8146,-4.0667 -0.1505,-0.3155 -0.2378,-0.6668 -0.3129,-1.0295 0.5695,0.8487 1.2578,1.6221 1.3909,2.717 0.088,-1.4303 0.9651,-2.8126 0.8871,-4.3292 0.025,-1.2324 -0.4806,-1.926 -1.0599,-2.8429 -0.0168,-0.0104 -0.0288,-0.0313 -0.0505,-0.0262zM2.7953,11.0137c0.1056,1.1638 -0.3008,2.1508 -0.2722,3.3188 0.0285,0.7055 0.2136,1.3308 0.5114,1.8936l-0.0505,0.0002C2.5817,15.5698 2.2675,14.8712 1.7974,14.246 1.0675,13.372 0.1364,12.4758 0.0406,11.2609 -0.0834,12.2755 0.0833,13.2266 0.4503,14.0674 0.8172,14.9082 1.3843,15.6386 2.061,16.2113l0.3153,0.2422c0.5579,0.4531 0.9973,0.6309 1.5334,1.1462v-0.0633C3.4991,16.6883 3.5518,15.6808 3.4863,14.652 3.4208,13.6233 3.2369,12.573 2.984,11.6393ZM21.2047,11.0137 L21.016,11.6393c-0.253,0.9337 -0.4366,1.984 -0.5022,3.0128 -0.0655,1.0288 -0.0128,2.0362 -0.4234,2.8843v0.0633c0.5361,-0.5153 0.9754,-0.6931 1.5332,-1.1462l0.3153,-0.2422c0.6767,-0.5727 1.2438,-1.3031 1.6107,-2.1439 0.3669,-0.8408 0.5336,-1.7919 0.4097,-2.8064 -0.0958,1.2149 -1.027,2.1111 -1.7568,2.9851 -0.4702,0.6252 -0.7843,1.3237 -1.1866,1.9802l-0.0505,-0.0002c0.2978,-0.5628 0.483,-1.1881 0.5115,-1.8936 0.0285,-1.168 -0.378,-2.155 -0.2724,-3.3188zM3.5004,14.1213c0.5128,1.2745 0.272,2.9326 0.9564,4.1181 0.188,0.3919 0.4985,0.7364 0.767,1.0391l-0.0125,0.0158C4.9684,19.1529 4.743,18.9508 4.5278,18.713 3.3584,17.5169 1.6144,17.3507 0.918,15.3892 1.0125,16.1089 1.1273,16.8312 1.5024,17.4358 2.7623,19.4588 4.1024,19.494 6.2178,20.0338 5.2496,19.0609 5.0897,17.61 4.6713,16.3248 4.39,15.5375 3.9663,14.8158 3.5004,14.1213ZM20.4996,14.1213c-0.4659,0.6945 -0.8896,1.4162 -1.1709,2.2035 -0.4184,1.2852 -0.5783,2.7361 -1.5465,3.709 2.188,-0.5087 3.4283,-0.5189 4.7154,-2.598 0.3751,-0.6046 0.4899,-1.327 0.5844,-2.0467 -0.6964,1.9616 -2.4403,2.1278 -3.6096,3.3239 -0.2152,0.2378 -0.4408,0.4399 -0.6837,0.5813l-0.0125,-0.0158c0.2685,-0.3026 0.579,-0.6472 0.767,-1.0391 0.6844,-1.1855 0.4436,-2.8436 0.9564,-4.1181zM5.1744,17.229c0.6329,1.0603 0.804,2.6309 1.9698,3.1431 0.1935,0.1273 0.4395,0.1128 0.6168,0.2528 -0.2227,0.042 -0.4149,-0.0632 -0.6249,-0.0993 -1.4873,-0.4168 -3.3117,0.1065 -4.4283,-1.4759 0.4238,1.0333 1.2928,1.8637 2.135,2.1955 1.2955,0.5497 2.6527,-0.0582 3.8743,-0.4293 0.1754,-0.0568 0.3897,-0.0359 0.5531,-0.1098C8.5902,20.5326 7.9449,20.2615 7.4087,19.676 6.6628,18.8013 5.9829,17.8988 5.1744,17.229ZM18.8256,17.229c-0.8085,0.6698 -1.4883,1.5723 -2.2342,2.447 -0.5361,0.5855 -1.1816,0.8566 -1.8616,1.0301 0.1634,0.0739 0.3777,0.053 0.5531,0.1098 1.2217,0.3711 2.5789,0.979 3.8745,0.4293 0.8423,-0.3318 1.7113,-1.1622 2.135,-2.1955 -1.1165,1.5824 -2.941,1.0591 -4.4283,1.4759 -0.2101,0.0361 -0.4024,0.1414 -0.6251,0.0993 0.1773,-0.14 0.4234,-0.1255 0.6169,-0.2528 1.1658,-0.5121 1.3368,-2.0828 1.9697,-3.1431zM13.3061,20.6618c-0.3586,-0.0002 -0.7166,0.0516 -1.0603,0.1619 -0.2182,0.2432 -0.4427,-0.0435 -0.6736,-0.049 -1.48,-0.4048 -2.7556,0.4321 -3.9833,1.092 -0.9149,0.4459 -2.1131,0.371 -2.9176,-0.3203 0.3981,0.6219 0.9306,1.0051 1.4804,1.2658 1.0222,0.5092 2.2388,0.1667 3.0846,-0.5663 0.7259,-0.5054 1.3501,-1.5007 2.3063,-1.1218 -0.9731,0.5607 -1.8627,1.3468 -2.6669,2.3573l0.5038,0.5187c0.5864,-0.9759 1.3664,-1.7724 2.1552,-2.3911 0.1711,-0.114 0.3515,-0.2556 0.5412,-0.2858 0.9689,0.591 1.8343,1.6013 2.6157,2.6506 0.1667,-0.1671 0.3437,-0.3483 0.4804,-0.5422 -0.8108,-0.9763 -1.6977,-1.7404 -2.6523,-2.2952 0.1586,-0.0567 0.3387,-0.0677 0.5253,-0.0629 0.5146,-0.0389 0.8652,0.4149 1.2631,0.7304 0.9827,0.9293 2.2958,1.5431 3.554,0.9617 0.564,-0.2501 1.1103,-0.6925 1.5101,-1.3109 -0.7777,0.6861 -1.9507,0.799 -2.8579,0.3796L15.5382,21.3453c-0.6511,-0.4315 -1.4432,-0.6831 -2.2321,-0.6835z&quot;"
+        errorLine2="                          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/drawable/ic_events_gold.xml"
+            line="9"
+            column="27"/>
+    </issue>
+
+    <issue
+        id="VectorPath"
+        message="Very long vector path (4281 characters), which is bad for performance. Considering reducing precision, removing minor details or rasterizing vector."
+        errorLine1="        android:pathData=&quot;m11.7472,8.175c-0.0503,-0.0004 -0.0996,-0.0157 -0.1492,-0.0235 -0.0954,-0.0151 -0.1906,-0.0315 -0.2862,-0.0451 -0.0345,-0.0049 -0.069,-0.0102 -0.1035,-0.0151 -0.0108,-0.0015 -0.0304,-0.0008 -0.0356,-0.0128 -0.0082,-0.0188 0.0099,-0.0489 0.0182,-0.0646 0.0279,-0.0519 0.0591,-0.102 0.0903,-0.1519 0.081,-0.1299 0.1724,-0.2638 0.2894,-0.3629 0.077,-0.0652 0.1662,-0.104 0.2649,-0.0634 0.0398,0.0163 0.0757,0.0435 0.104,0.0761 0.0227,0.0261 0.0438,0.0563 0.0542,0.0899 0.0139,0.0443 0.0336,0.1222 0.0151,0.1674 -0.0168,0.041 -0.0621,0.0719 -0.0903,0.1054 -0.0481,0.057 -0.0866,0.1205 -0.1222,0.1859 -0.0167,0.0307 -0.0472,0.0749 -0.0455,0.1115 0.0011,0.0231 0.0694,0.0405 0.0878,0.0506 0.0645,0.0356 0.1239,0.0879 0.1543,0.1571 0.056,0.1274 -0.002,0.3115 -0.1056,0.3984 -0.0315,0.0264 -0.0707,0.0408 -0.1096,0.0518 -0.1087,0.0306 -0.2253,0.0009 -0.3349,-0.0038 -0.0042,0.0549 -0.014,0.1134 0.0023,0.1674 0.0298,0.0985 0.1497,0.1717 0.2504,0.1469 0.1002,-0.0246 0.1645,-0.1152 0.2347,-0.1838 0.0131,-0.0128 0.0334,-0.0128 0.0459,-0.0249 0.0154,-0.015 0.0206,-0.0492 0.0305,-0.0684 0.0153,-0.0296 0.0394,-0.0564 0.0586,-0.0837 0.1165,-0.166 0.2182,-0.3427 0.3158,-0.5207 0.0269,-0.049 0.0533,-0.0985 0.078,-0.1488 0.0085,-0.0174 0.0136,-0.0502 0.0267,-0.0635 0.0126,-0.0128 0.0401,-0.0121 0.0563,-0.0195 0.113,-0.0522 0.2308,-0.0312 0.3197,0.0582 0.0195,0.0196 0.0405,0.0391 0.0521,0.0651 0.0495,0.1116 -0.0026,0.2458 -0.0417,0.3502 -0.0685,0.1834 -0.1751,0.3538 -0.2844,0.5145 -0.0182,0.0268 -0.0208,0.0647 -0.0392,0.089 -0.0122,0.0161 -0.0369,0.0186 -0.0509,0.035 -0.0328,0.0384 -0.0629,0.0862 -0.1049,0.1151 -0.0822,0.0565 -0.223,0.0554 -0.3075,0.0079 -0.0819,-0.0461 -0.0982,-0.142 -0.1024,-0.2284 -0.0015,-0.0312 0.011,-0.0703 -0.0042,-0.0992m0.0365,-1.1746c0.0543,-0.0115 0.0997,-0.0425 0.1553,-0.0513 0.1169,-0.0185 0.2376,0.0519 0.2818,0.1629 0.0224,0.0562 0.0157,0.1148 0.0318,0.1705m0.0639,1.0661c0.0253,0.0358 0.0117,0.1027 0.0236,0.1457 0.0349,0.1267 0.1465,0.1793 0.2687,0.1529 0.1722,-0.0372 0.2966,-0.1979 0.3887,-0.3388 0.0213,-0.0326 0.0379,-0.0756 0.0638,-0.1042 0.0088,-0.0096 0.0311,-0.0235 0.0428,-0.0125 0.0181,0.017 0.0102,0.0806 0.0102,0.1043 0,0.1022 0.0009,0.2051 -0.0086,0.3068 -0.0182,0.195 -0.022,0.4176 -0.1285,0.5888 -0.0361,0.058 -0.0899,0.1069 -0.1399,0.1524 -0.0987,0.0897 -0.2038,0.1731 -0.3136,0.2484 -0.0483,0.0331 -0.1045,0.062 -0.1489,0.1 -0.0177,0.0151 -0.0125,0.0421 -0.0125,0.0632v0.1673c0,0.0164 0.0079,0.0641 -0.0071,0.0743 -0.015,0.0102 -0.0514,0.0032 -0.069,0.0032h-1.0413c-0.0189,0 -0.0591,0.0076 -0.0751,-0.0032 -0.0164,-0.0111 -0.0071,-0.0714 -0.0071,-0.0898 0,-0.0672 0.0161,-0.1749 -0.0268,-0.2293C11.1496,10.1614 10.9343,9.8796 10.7594,9.5728 10.6858,9.4437 10.596,9.2999 10.5816,9.1482 10.5533,8.8506 10.7095,8.5545 10.861,8.3115c0.037,-0.0594 0.0767,-0.1179 0.1189,-0.1736 0.0145,-0.0191 0.0302,-0.0502 0.0518,-0.0614 0.0345,-0.018 0.0963,0.0057 0.1339,0.0057m1.7538,0.3068c0.1344,-0.0282 0.2635,-0.0954 0.3897,0.0012 0.0214,0.0164 0.0393,0.0356 0.0551,0.0577 0.0192,0.0269 0.0317,0.055 0.0406,0.0868 0.0097,0.0348 0.019,0.0719 0.0167,0.1085 -0.0051,0.0811 -0.0466,0.1431 -0.0744,0.2169 -0.0099,0.0262 -0.0114,0.0563 -0.0167,0.0837M11.0591,8.7113c0.0825,0.0372 0.1745,0.0603 0.2618,0.0825 0.0377,0.0096 0.0883,0.0125 0.1218,0.0321m-0.0487,1.9277c-0.0367,0.0199 -0.1078,0.0063 -0.1492,0.0063 -0.012,0 -0.0434,-0.0057 -0.0517,0.005 -0.0054,0.007 -0.0031,0.0208 -0.0031,0.0291v0.4618c0,0.0089 -0.0026,0.0243 0.0071,0.0291 0.0281,0.0141 0.0895,0.0018 0.1208,0.0018h1.3945c0.0343,0 0.0824,0.009 0.1152,-0.001 0.0123,-0.0037 0.0096,-0.0198 0.0096,-0.03v-0.4618c0,-0.0084 0.0023,-0.0221 -0.0031,-0.0291 -0.0093,-0.0119 -0.0501,-0.0049 -0.0639,-0.0049 -0.0538,0 -0.125,0.0124 -0.1766,-0.0031m-0.9865,0.1253c0.0611,-0.0081 0.1271,-0.0013 0.1888,-0.0013h0.5632c0.0374,0 0.0793,-0.004 0.1157,0.0064 0.0252,0.007 0.0336,0.0288 0.0377,0.0525 0.0086,0.05 0.0071,0.117 0.001,0.1674 -0.0029,0.0236 -0.0079,0.0494 -0.0326,0.0588 -0.0245,0.0093 -0.0536,0.0063 -0.0792,0.0063H11.6376c-0.0264,0 -0.0575,0.0035 -0.0822,-0.0082 -0.0343,-0.0163 -0.0274,-0.0721 -0.0274,-0.1034 0,-0.0433 -0.0169,-0.135 0.0216,-0.1649 0.0163,-0.0127 0.0383,-0.011 0.0575,-0.0136m-0.1918,0.4078v0.3936l0.0031,0.0322 0.0639,0.0049h1.0443l0.0621,-0.0049 0.0049,-0.0322v-0.3936&quot;"
+        errorLine2="                          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/drawable/ic_events_gold.xml"
+            line="18"
+            column="27"/>
+    </issue>
+
+    <issue
+        id="VectorPath"
+        message="Very long vector path (1367 characters), which is bad for performance. Considering reducing precision, removing minor details or rasterizing vector."
+        errorLine1="        android:pathData=&quot;M10.734 15.568h0.672a0.172 0.172 0 0 1 0.172 0.172v0.625a0.172 0.172 0 0 1-0.172 0.172h-0.672a0.172 0.172 0 0 1-0.171-0.172V15.74a0.172 0.172 0 0 1 0.171-0.172zm0 1.719h0.672a0.172 0.172 0 0 1 0.172 0.172v0.625a0.172 0.172 0 0 1-0.172 0.172h-0.672a0.172 0.172 0 0 1-0.171-0.172v-0.625a0.172 0.172 0 0 1 0.171-0.172zm6.579 0h0.671a0.172 0.172 0 0 1 0.172 0.172v0.625a0.172 0.172 0 0 1-0.172 0.172h-0.672a0.172 0.172 0 0 1-0.171-0.172v-0.625a0.172 0.172 0 0 1 0.172-0.172zm0-1.719h0.671a0.172 0.172 0 0 1 0.172 0.172v0.625a0.172 0.172 0 0 1-0.172 0.172h-0.672a0.172 0.172 0 0 1-0.171-0.172V15.74a0.172 0.172 0 0 1 0.172-0.172zm-2.485-2.453H15.5a0.172 0.172 0 0 1 0.172 0.172v0.625a0.172 0.172 0 0 1-0.172 0.172h-0.672a0.172 0.172 0 0 1-0.172-0.172v-0.625a0.172 0.172 0 0 1 0.172-0.172zm-1.601 0h0.671a0.172 0.172 0 0 1 0.172 0.172v0.625a0.172 0.172 0 0 1-0.172 0.172h-0.671a0.172 0.172 0 0 1-0.172-0.172v-0.625a0.172 0.172 0 0 1 0.172-0.172zm0 6.504h0.671a0.172 0.172 0 0 1 0.172 0.172v0.625a0.172 0.172 0 0 1-0.172 0.172h-0.671a0.172 0.172 0 0 1-0.172-0.172V19.79a0.172 0.172 0 0 1 0.172-0.172zm1.589 0h0.672a0.172 0.172 0 0 1 0.172 0.172v0.625a0.172 0.172 0 0 1-0.172 0.172h-0.672a0.172 0.172 0 0 1-0.171-0.172V19.79a0.172 0.172 0 0 1 0.171-0.172zm5.451-7.929c-5.543-5.552 0.98 0.98-4.564-4.571l4.571 0.002-0.007 4.569zm-5.187-3.5l4.033 4.066-4.026 0.012-0.007-4.077z&quot;"
+        errorLine2="                          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/drawable/ic_firmware.xml"
+            line="40"
+            column="27"/>
+    </issue>
+
+    <issue
+        id="VectorPath"
+        message="Very long vector path (849 characters), which is bad for performance. Considering reducing precision, removing minor details or rasterizing vector."
+        errorLine1="        android:pathData=&quot;M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zm6.93 6h-2.95c-0.32-1.25-0.78-2.45-1.38-3.56 1.84 0.63 3.37 1.91 4.33 3.56zM12 4.04c0.83 1.2 1.48 2.53 1.91 3.96h-3.82c0.43-1.43 1.08-2.76 1.91-3.96zM4.26 14C4.1 13.36 4 12.69 4 12s0.1-1.36 0.26-2h3.38c-0.08 0.66-0.14 1.32-0.14 2s0.06 1.34 0.14 2H4.26zm0.82 2h2.95c0.32 1.25 0.78 2.45 1.38 3.56-1.84-0.63-3.37-1.9-4.33-3.56zm2.95-8H5.08c0.96-1.66 2.49-2.93 4.33-3.56C8.81 5.55 8.35 6.75 8.03 8zM12 19.96c-0.83-1.2-1.48-2.53-1.91-3.96h3.82c-0.43 1.43-1.08 2.76-1.91 3.96zM14.34 14H9.66c-0.09-0.66-0.16-1.32-0.16-2s0.07-1.35 0.16-2h4.68c0.09 0.65 0.16 1.32 0.16 2s-0.07 1.34-0.16 2zm0.25 5.56c0.6-1.11 1.06-2.31 1.38-3.56h2.95c-0.96 1.65-2.49 2.93-4.33 3.56zM16.36 14c0.08-0.66 0.14-1.32 0.14-2s-0.06-1.34-0.14-2h3.38C19.9 10.64 20 11.31 20 12s-0.1 1.36-0.26 2h-3.38z&quot; />"
+        errorLine2="                          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/drawable/ic_language.xml"
+            line="9"
+            column="27"/>
+    </issue>
+
+    <issue
+        id="VectorPath"
+        message="Very long vector path (823 characters), which is bad for performance. Considering reducing precision, removing minor details or rasterizing vector."
+        errorLine1="        android:pathData=&quot;M72.12 87.5l-0.43-10.38c-0.15-7.29 6-13.48 7.64-18.93-1.74 1.66-5.35 9.52-9.1 8.86-2.09-0.7-2.75-1.69-3.6-3.47-2.81 0.55-5.04-1.11-5.85-3.11l0.16 8.09-3.66-10.5c-2.86-0.93-5.48-5.32-2.85-4.81l4.72 0.9c3.23 0.68 5.05-3.69 2.16-5.96-0.62-0.44-6.79-3.32-7.95-3.64-1.04-0.29-2.28 0.12-3.08 1.02l-4.81 8.93c-0.5 0.69-0.5 1.41-0.21 2.04l8.8 17.65c0.28 0.54 0.14 1.81 0.15 2.25l-1.05 11.07zm7.35-31.33c1.59-1.81-1.36-6.84-2.58-5.38l-7.88 10.32c-2.31 2.87 1.17 5.71 3.65 3.94 0.22-0.2 0.34-0.31 0.5-0.52zm-3.85-7.83c-1.17-1.22-3.57-2.98-3.89-2.86l-8.79 11.86c-2.37 2.31 0.3 5.07 2.88 4.8 0.36-0.1 0.48-0.17 0.7-0.38l9.16-11.84c0.4-0.52 0.27-1.16-0.07-1.58zm-5.22-3.82c-0.09-0.61-3.96-3.73-4.26-3.34l-4.46 5.77c5.64 6.14-0.94 9.96-4.85 7.73 0.49 1.57 3.42 3.2 4.33 2.01zm-5.38-4.3c-0.85-1.8-4.22-3.17-5.74-2.59l-4.83 5.95 6.06 2.51z&quot;"
+        errorLine2="                          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/drawable/ic_launcher_foreground.xml"
+            line="8"
+            column="27"/>
+    </issue>
+
+    <issue
+        id="VectorPath"
+        message="Very long vector path (851 characters), which is bad for performance. Considering reducing precision, removing minor details or rasterizing vector."
+        errorLine1="        android:pathData=&quot;M6.56 7.98C6.1 7.52 5.31 7.6 5 8.17 4.72 8.68 4.5 9.2 4.33 9.75 4.14 10.38 4.64 11 5.29 11H5.3c0.43 0 0.82-0.28 0.94-0.7 0.12-0.4 0.28-0.79 0.48-1.17 0.22-0.37 0.15-0.84-0.16-1.15zM5.31 13H5.29c-0.65 0-1.15 0.62-0.96 1.25 0.16 0.54 0.38 1.07 0.66 1.58 0.31 0.57 1.11 0.66 1.57 0.2 0.3-0.31 0.38-0.77 0.17-1.15-0.2-0.37-0.36-0.76-0.48-1.16C6.13 13.28 5.74 13 5.31 13zm2.85 6.02c0.51 0.28 1.04 0.5 1.59 0.66 0.62 0.18 1.24-0.32 1.24-0.96v-0.03c0-0.43-0.28-0.82-0.7-0.94-0.4-0.12-0.78-0.28-1.15-0.48-0.38-0.21-0.86-0.14-1.16 0.17l-0.03 0.03c-0.45 0.45-0.36 1.24 0.21 1.55zM13 4.07V3.41c0-0.89-1.08-1.34-1.71-0.71L9.17 4.83c-0.4 0.4-0.4 1.04 0 1.43l2.13 2.08C11.93 8.96 13 8.51 13 7.62V6.09c2.84 0.48 5 2.94 5 5.91 0 2.73-1.82 5.02-4.32 5.75-0.41 0.12-0.68 0.51-0.68 0.94v0.02c0 0.65 0.61 1.14 1.23 0.96C17.57 18.71 20 15.64 20 12c0-4.08-3.05-7.44-7-7.93z&quot; />"
+        errorLine2="                          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/drawable/ic_rotate_left.xml"
+            line="9"
+            column="27"/>
+    </issue>
+
+    <issue
+        id="VectorPath"
+        message="Very long vector path (802 characters), which is bad for performance. Considering reducing precision, removing minor details or rasterizing vector."
+        errorLine1="        android:pathData=&quot;M19.43 12.98c0.04-0.32 0.07-0.64 0.07-0.98s-0.03-0.66-0.07-0.98l2.11-1.65c0.19-0.15 0.24-0.42 0.12-0.64l-2-3.46c-0.12-0.22-0.39-0.3-0.61-0.22l-2.49 1c-0.52-0.4-1.08-0.73-1.69-0.98l-0.38-2.65C14.46 2.18 14.25 2 14 2h-4C9.75 2 9.54 2.18 9.51 2.42L9.13 5.07C8.52 5.32 7.96 5.66 7.44 6.05l-2.49-1c-0.23-0.09-0.49 0-0.61 0.22l-2 3.46C2.21 8.95 2.27 9.22 2.46 9.37l2.11 1.65C4.53 11.34 4.5 11.67 4.5 12s0.03 0.66 0.07 0.98l-2.11 1.65c-0.19 0.15-0.24 0.42-0.12 0.64l2 3.46c0.12 0.22 0.39 0.3 0.61 0.22l2.49-1c0.52 0.4 1.08 0.73 1.69 0.98l0.38 2.65C9.54 21.82 9.75 22 10 22h4c0.25 0 0.46-0.18 0.49-0.42l0.38-2.65c0.61-0.25 1.17-0.59 1.69-0.98l2.49 1c0.23 0.09 0.49 0 0.61-0.22l2-3.46c0.12-0.22 0.07-0.49-0.12-0.64l-2.11-1.65zM12 15.5c-1.93 0-3.5-1.57-3.5-3.5s1.57-3.5 3.5-3.5 3.5 1.57 3.5 3.5-1.57 3.5-3.5 3.5z&quot; />"
+        errorLine2="                          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/drawable/ic_settings.xml"
+            line="9"
+            column="27"/>
+    </issue>
+
+    <issue
+        id="VectorPath"
+        message="Very long vector path (1045 characters), which is bad for performance. Considering reducing precision, removing minor details or rasterizing vector."
+        errorLine1="      android:pathData=&quot;M6.21,13.97l1.2,2.07c0.08,0.13 0.23,0.18 0.37,0.13l1.49,-0.6c0.31,0.24 0.64,0.44 1.01,0.59l0.22,1.59c0.03,0.14 0.15,0.25 0.3,0.25h2.4c0.15,0 0.27,-0.11 0.3,-0.26l0.22,-1.59c0.36,-0.15 0.7,-0.35 1.01,-0.59l1.49,0.6c0.14,0.05 0.29,0 0.37,-0.13l1.2,-2.07c0.08,-0.13 0.04,-0.29 -0.07,-0.39l-1.27,-0.99c0.03,-0.19 0.04,-0.39 0.04,-0.58 0,-0.2 -0.02,-0.39 -0.04,-0.59l1.27,-0.99c0.11,-0.09 0.15,-0.26 0.07,-0.39l-1.2,-2.07c-0.08,-0.13 -0.23,-0.18 -0.37,-0.13l-1.49,0.6c-0.31,-0.24 -0.64,-0.44 -1.01,-0.59l-0.22,-1.59c-0.03,-0.14 -0.15,-0.25 -0.3,-0.25h-2.4c-0.15,0 -0.27,0.11 -0.3,0.26l-0.22,1.59c-0.36,0.15 -0.71,0.34 -1.01,0.58l-1.49,-0.6c-0.14,-0.05 -0.29,0 -0.37,0.13l-1.2,2.07c-0.08,0.13 -0.04,0.29 0.07,0.39l1.27,0.99c-0.03,0.2 -0.05,0.39 -0.05,0.59 0,0.2 0.02,0.39 0.04,0.59l-1.27,0.99c-0.11,0.1 -0.14,0.26 -0.06,0.39zM12,10.29c0.94,0 1.71,0.77 1.71,1.71s-0.77,1.71 -1.71,1.71 -1.71,-0.77 -1.71,-1.71 0.77,-1.71 1.71,-1.71zM19,3L5,3c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h14c1.11,0 2,-0.9 2,-2L21,5c0,-1.1 -0.89,-2 -2,-2zM19,19L5,19L5,5h14v14z&quot;/>"
+        errorLine2="                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/drawable/ic_settings_applications.xml"
+            line="9"
+            column="25"/>
+    </issue>
+
+    <issue
+        id="VectorPath"
+        message="Very long vector path (947 characters), which is bad for performance. Considering reducing precision, removing minor details or rasterizing vector."
+        errorLine1="        android:pathData=&quot;M20.01,16.94 L18.6,15.4517 23.08,10.7228 24.5,12.2111ZM15.6852,14.1733 L19.55,9.9733 15.3,5.4767 11.4252,9.6766 10.0052,8.1778 15.3,2.5v0l7.08,7.4733 -5.2748,5.6883zM16.74,16.4967 L15.33,17.985v0l-3.33,3.515L2.5,21.5L2.5,18.7239c0,-0.8867 0.52,-1.6572 1.3,-1.9844 0.58,-0.2428 1.28,-0.5911 1.97,-1.0767l1.38,1.4567c0.09,0.1056 0.22,0.1583 0.35,0.1583 0.13,0 0.26,-0.0528 0.36,-0.1583 0.2,-0.2111 0.2,-0.5383 0,-0.7494L6.58,15.0189c0.27,-0.2533 0.53,-0.5383 0.77,-0.8444l1.27,1.3406c0.09,0.1056 0.23,0.1583 0.35,0.1583 0.12,0 0.25,-0.0528 0.35,-0.1583 0.2,-0.2111 0.2,-0.5383 0,-0.7494l-1.4,-1.4778c0.19,-0.3589 0.34,-0.76 0.45,-1.1822l1.71,1.8156c0.09,0.1056 0.23,0.1583 0.35,0.1583 0.12,0 0.25,-0.0528 0.35,-0.1583 0.19,-0.2111 0.19,-0.5278 0.01,-0.7389l-2.3711,-2.5044 1.42,-1.4883zM13.92,16.4967 L12.25,14.7233 8.92,18.2278C8.14,19.0511 6.87,19.0511 6.09,18.2172L5.9,18.0378 5.43,18.2911c-0.29,0.1478 -0.59,0.285 -0.89,0.4117L4.53,19.3889h6.64z&quot;"
+        errorLine2="                          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/drawable/ic_shoe.xml"
+            line="9"
+            column="27"/>
+    </issue>
+
+    <issue
+        id="VectorPath"
+        message="Very long vector path (2472 characters), which is bad for performance. Considering reducing precision, removing minor details or rasterizing vector."
+        errorLine1="      android:pathData=&quot;m8.3489,1.4897c0.0293,0.0131 0.0571,0.0295 0.0848,0.0455 0.0277,0.0164 0.055,0.0328 0.0829,0.0508 0.028,0.0164 0.0564,0.0373 0.0864,0.0583 0.03,0.0196 0.0614,0.0436 0.0952,0.0681 0.0221,0.0164 0.044,0.0328 0.0658,0.0495 0.0217,0.0164 0.0435,0.0344 0.065,0.0521 0.043,0.0357 0.0853,0.0727 0.1268,0.1114 0.0414,0.0386 0.082,0.0789 0.1216,0.1202 0.0395,0.0413 0.078,0.0839 0.1152,0.1274 0.0373,0.0436 0.0733,0.0881 0.1079,0.1336 0.0346,0.0455 0.0678,0.0917 0.0993,0.1385 0.0316,0.0468 0.0615,0.0943 0.0897,0.1421 0.0142,0.0229 0.0277,0.0478 0.041,0.0721 0.0132,0.0229 0.026,0.0482 0.0381,0.0724 0.0316,0.0622 0.0604,0.1215 0.0868,0.1785 0.0264,0.057 0.0503,0.1117 0.072,0.1647 0.0217,0.0531 0.0411,0.1048 0.0586,0.1562 0.0086,0.0262 0.0168,0.0511 0.0247,0.0763 0.0079,0.0262 0.0152,0.0508 0.0217,0.076 0.0069,0.0262 0.0132,0.0508 0.0191,0.0763 0.0059,0.0262 0.0115,0.0514 0.0168,0.0773 0.0102,0.0521 0.0191,0.1051 0.0264,0.1602 0.0076,0.055 0.0135,0.112 0.0184,0.1716 0.0049,0.0596 0.0089,0.1222 0.0119,0.188 0.0022,0.0488 0.0036,0.0963 0.0046,0.1425 0.0008,0.0465 0.0008,0.0917 0.0001,0.1359 -0.0008,0.0442 -0.0024,0.0878 -0.0049,0.1307 -0.0025,0.0429 -0.0059,0.0852 -0.0102,0.1271 -0.0043,0.0419 -0.0096,0.0835 -0.0158,0.1248 -0.0063,0.0413 -0.0135,0.0825 -0.0221,0.1241 -0.0086,0.0413 -0.0181,0.0829 -0.029,0.1248 -0.0109,0.0419 -0.0227,0.0839 -0.0359,0.1268 -0.0132,0.0429 -0.0277,0.0861 -0.0434,0.1304 -0.0158,0.0442 -0.0329,0.0891 -0.0514,0.1349 -0.0184,0.0459 -0.0385,0.093 -0.0599,0.1415 -0.0214,0.0485 -0.0443,0.0979 -0.0687,0.1494 -0.0244,0.0511 -0.0505,0.1038 -0.0781,0.1585 -0.0277,0.0544 -0.0568,0.1107 -0.0878,0.169 -0.031,0.0583 -0.0636,0.1186 -0.098,0.1811 -0.0344,0.0626 -0.0706,0.1274 -0.1087,0.1949 -0.0557,0.0957 -0.1111,0.1893 -0.166,0.2814 -0.0549,0.092 -0.1094,0.1821 -0.1635,0.2702 -0.0541,0.0884 -0.1077,0.1749 -0.161,0.2594 -0.0532,0.0848 -0.1061,0.1677 -0.1585,0.2489 -0.0524,0.0812 -0.1045,0.1605 -0.1561,0.2381 -0.0516,0.0776 -0.1028,0.1536 -0.1536,0.2276 -0.0508,0.074 -0.1012,0.1467 -0.1511,0.2175 -0.05,0.0708 -0.0996,0.1399 -0.1487,0.2073 -0.0491,0.0675 -0.0979,0.1333 -0.1462,0.1972 -0.0483,0.0642 -0.0963,0.1268 -0.1438,0.1874 -0.0475,0.0609 -0.0946,0.1199 -0.1413,0.1775 -0.0024,0.003 -0.0046,0.007 -0.0069,0.01 0.195,-0.0262 0.9686,-0.712 1.3717,-1.2158 0.9235,-1.154 1.3969,-2.5207 1.2556,-3.6246 -0.1292,-1.0093 -0.5792,-1.8334 -1.4153,-2.592 -0.2076,-0.1883 -0.2305,-0.1631 -0.2909,-0.1444 -0.2071,0.1251 -0.2308,0.1146 -0.0911,0.1916z&quot;"
+        errorLine2="                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/drawable/ic_sony_wf_800n_left.xml"
+            line="8"
+            column="25"/>
+    </issue>
+
+    <issue
+        id="VectorPath"
+        message="Very long vector path (1366 characters), which is bad for performance. Considering reducing precision, removing minor details or rasterizing vector."
+        errorLine1="      android:pathData=&quot;m6.7195,6.9789v2.5781h0.5762v-0.9785h0.209c0.106,0 0.175,0.023 0.2168,0.0527 0.0383,0.0268 0.11,0.1125 0.1816,0.2578v0.002l0.332,0.666h0.6328l-0.4141,-0.8223v-0.002c-0.0668,-0.1356 -0.1349,-0.2457 -0.209,-0.332 -0.0229,-0.027 -0.0533,-0.036 -0.0781,-0.0586 0.0619,-0.0418 0.1407,-0.0643 0.1836,-0.125 0.0865,-0.1218 0.127,-0.2731 0.127,-0.4394 0,-0.249 -0.0785,-0.4626 -0.2422,-0.6016 -0.1639,-0.1392 -0.3947,-0.1973 -0.6777,-0.1973zM6.9832,7.2426h0.0488v0.125,0.9473 0.008,0.9707h-0.0488zM7.6941,7.2797c0.1482,0.0194 0.3006,0.0378 0.3711,0.0977 0.0967,0.0822 0.1484,0.2029 0.1484,0.4004 0,0.1251 -0.0282,0.2148 -0.0781,0.2852L8.1355,8.0649c-0.0301,0.0431 -0.0878,0.0643 -0.1367,0.0918 0.0941,-0.098 0.1484,-0.2292 0.1484,-0.3789 0,-0.16 -0.051,-0.3074 -0.1602,-0.4043 -0.074,-0.067 -0.1869,-0.0752 -0.293,-0.0937zM7.2957,7.5004h0.2617c0.1307,0 0.2076,0.0301 0.252,0.0703h0.002c0.0449,0.0395 0.0723,0.0969 0.0723,0.207 0,0.1102 -0.028,0.1671 -0.0742,0.209 -0.0444,0.0402 -0.1213,0.0703 -0.252,0.0703h-0.2617zM7.9285,8.2035c-0.0069,0.002 -0.0083,0.008 -0.0156,0.01l-0.0957,0.0273v0.1582c-0.083,-0.0446 -0.1767,-0.0726 -0.2832,-0.0762h0.0234c0.1477,0 0.2679,-0.0478 0.3711,-0.1191zM7.9168,8.4731c0.0361,0.0144 0.0784,0.0403 0.127,0.0977v0.002c0.0534,0.062 0.1131,0.1558 0.1738,0.2793l0.2227,0.4414h-0.041l-0.2598,-0.5215c-0.068,-0.1379 -0.1406,-0.2243 -0.2227,-0.2988z&quot;"
+        errorLine2="                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/drawable/ic_sony_wf_800n_right.xml"
+            line="13"
+            column="25"/>
+    </issue>
+
+    <issue
+        id="VectorPath"
+        message="Very long vector path (2469 characters), which is bad for performance. Considering reducing precision, removing minor details or rasterizing vector."
+        errorLine1="      android:pathData=&quot;m3.9723,1.4897c-0.0293,0.0131 -0.0571,0.0295 -0.0848,0.0455 -0.0277,0.0164 -0.055,0.0328 -0.0829,0.0508 -0.028,0.0164 -0.0564,0.0373 -0.0864,0.0583 -0.03,0.0196 -0.0614,0.0436 -0.0952,0.0681 -0.0221,0.0164 -0.044,0.0328 -0.0658,0.0495 -0.0217,0.0164 -0.0435,0.0344 -0.065,0.0521 -0.043,0.0357 -0.0853,0.0727 -0.1268,0.1114 -0.0414,0.0386 -0.082,0.0789 -0.1216,0.1202 -0.0395,0.0413 -0.078,0.0839 -0.1152,0.1274 -0.0373,0.0436 -0.0733,0.0881 -0.1079,0.1336 -0.0346,0.0455 -0.0678,0.0917 -0.0993,0.1385 -0.0316,0.0468 -0.0615,0.0943 -0.0897,0.1421 -0.0142,0.0229 -0.0277,0.0478 -0.041,0.0721 -0.0132,0.0229 -0.026,0.0482 -0.0381,0.0724 -0.0316,0.0622 -0.0604,0.1215 -0.0868,0.1785 -0.0264,0.057 -0.0503,0.1117 -0.072,0.1647 -0.0217,0.0531 -0.0411,0.1048 -0.0586,0.1562 -0.0086,0.0262 -0.0168,0.0511 -0.0247,0.0763 -0.0079,0.0262 -0.0152,0.0508 -0.0217,0.076 -0.0069,0.0262 -0.0132,0.0508 -0.0191,0.0763 -0.0059,0.0262 -0.0115,0.0514 -0.0168,0.0773 -0.0102,0.0521 -0.0191,0.1051 -0.0264,0.1602 -0.0076,0.055 -0.0135,0.112 -0.0184,0.1716 -0.0049,0.0596 -0.0089,0.1222 -0.0119,0.188 -0.0022,0.0488 -0.0036,0.0963 -0.0046,0.1425 -0.0008,0.0465 -0.0008,0.0917 -0.0001,0.1359 0.0008,0.0442 0.0024,0.0878 0.0049,0.1307 0.0025,0.0429 0.0059,0.0852 0.0102,0.1271 0.0043,0.0419 0.0096,0.0835 0.0158,0.1248 0.0063,0.0413 0.0135,0.0825 0.0221,0.1241 0.0086,0.0413 0.0181,0.0829 0.029,0.1248 0.0109,0.0419 0.0227,0.0839 0.0359,0.1268 0.0132,0.0429 0.0277,0.0861 0.0434,0.1304 0.0158,0.0442 0.0329,0.0891 0.0514,0.1349 0.0184,0.0459 0.0385,0.093 0.0599,0.1415 0.0214,0.0485 0.0443,0.0979 0.0687,0.1494 0.0244,0.0511 0.0505,0.1038 0.0781,0.1585 0.0277,0.0544 0.0568,0.1107 0.0878,0.169 0.031,0.0583 0.0636,0.1186 0.098,0.1811 0.0344,0.0626 0.0706,0.1274 0.1087,0.1949 0.0557,0.0957 0.1111,0.1893 0.166,0.2814 0.0549,0.092 0.1094,0.1821 0.1635,0.2702 0.0541,0.0884 0.1077,0.1749 0.161,0.2594 0.0532,0.0848 0.1061,0.1677 0.1585,0.2489 0.0524,0.0812 0.1045,0.1605 0.1561,0.2381 0.0516,0.0776 0.1028,0.1536 0.1536,0.2276 0.0508,0.074 0.1012,0.1467 0.1511,0.2175 0.05,0.0708 0.0996,0.1399 0.1487,0.2073 0.0491,0.0675 0.0979,0.1333 0.1462,0.1972 0.0483,0.0642 0.0963,0.1268 0.1438,0.1874 0.0475,0.0609 0.0946,0.1199 0.1413,0.1775 0.0024,0.003 0.0046,0.007 0.0069,0.01 -0.195,-0.0262 -0.9686,-0.712 -1.3717,-1.2158 -0.9235,-1.154 -1.3969,-2.5207 -1.2556,-3.6246 0.1292,-1.0093 0.5792,-1.8334 1.4153,-2.592 0.2076,-0.1883 0.2305,-0.1631 0.2909,-0.1444 0.2071,0.1251 0.2308,0.1146 0.0911,0.1916z&quot;"
+        errorLine2="                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/drawable/ic_sony_wf_800n_right.xml"
+            line="18"
+            column="25"/>
+    </issue>
+
+    <issue
+        id="VectorPath"
+        message="Very long vector path (1194 characters), which is bad for performance. Considering reducing precision, removing minor details or rasterizing vector."
+        errorLine1="        android:pathData=&quot;M6.05 4.14L5.66 3.75c-0.39-0.39-1.02-0.38-1.4 0L4.25 3.76c-0.39 0.39-0.39 1.02 0 1.4l0.39 0.39c0.39 0.39 1.01 0.39 1.4 0l0.01-0.01c0.39-0.38 0.39-1.02 0-1.4zM3.01 10.5H1.99C1.44 10.5 1 10.94 1 11.49v0.01c0 0.55 0.44 0.99 0.99 0.99H3c0.56 0.01 1-0.43 1-0.98V11.5c0-0.56-0.44-1-0.99-1zm9-9.95H12c-0.56 0-1 0.44-1 0.99V2.5c0 0.55 0.44 0.99 0.99 0.99H12c0.56 0.01 1-0.43 1-0.98V1.54c0-0.55-0.44-0.99-0.99-0.99zm7.74 3.21c-0.39-0.39-1.02-0.39-1.41-0.01l-0.39 0.39c-0.39 0.39-0.39 1.02 0 1.4l0.01 0.01c0.39 0.39 1.02 0.39 1.4 0l0.39-0.39c0.39-0.39 0.39-1.01 0-1.4zm-1.81 15.1l0.39 0.39c0.39 0.39 1.02 0.39 1.41 0 0.39-0.39 0.39-1.02 0-1.41l-0.39-0.39c-0.39-0.39-1.02-0.38-1.4 0-0.4 0.4-0.4 1.02-0.01 1.41zM20 11.49v0.01c0 0.55 0.44 0.99 0.99 0.99H22c0.55 0 0.99-0.44 0.99-0.99v-0.01c0-0.55-0.44-0.99-0.99-0.99h-1.01c-0.55 0-0.99 0.44-0.99 0.99zM12 5.5c-3.31 0-6 2.69-6 6s2.69 6 6 6 6-2.69 6-6-2.69-6-6-6zm-0.01 16.95H12c0.55 0 0.99-0.44 0.99-0.99V20.5c0-0.55-0.44-0.99-0.99-0.99h-0.01c-0.55 0-0.99 0.44-0.99 0.99v0.96c0 0.55 0.44 0.99 0.99 0.99zm-7.74-3.21c0.39 0.39 1.02 0.39 1.41 0l0.39-0.39c0.39-0.39 0.38-1.02 0-1.4l-0.01-0.01c-0.39-0.39-1.02-0.39-1.41 0l-0.39 0.39c-0.38 0.4-0.38 1.02 0.01 1.41z&quot; />"
+        errorLine2="                          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/drawable/ic_wb_sunny.xml"
+            line="9"
+            column="27"/>
+    </issue>
+
+    <issue
+        id="ViewHolder"
+        message="Unconditional layout inflation from view adapter: Should use View Holder pattern (use recycled view passed into this method as the second parameter) for smoother scrolling"
+        errorLine1="        View itemView = inflater.inflate(groupid, parent, false);"
+        errorLine2="                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/SpinnerWithIconAdapter.java"
+            line="51"
+            column="25"/>
+    </issue>
+
+    <issue
+        id="HandlerLeak"
+        message="This `Handler` class should be static or leaks might occur (nodomain.freeyourgadget.gadgetbridge.util.WebViewSingleton.IncomingHandler)"
+        errorLine1="    private class IncomingHandler extends Handler {"
+        errorLine2="                  ~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/util/WebViewSingleton.java"
+            line="126"
+            column="19"/>
+    </issue>
+
+    <issue
+        id="MergeRootFrame"
+        message="This `&lt;FrameLayout>` can be replaced with a `&lt;merge>` tag"
+        errorLine1="&lt;FrameLayout android:id=&quot;@+id/webview_placeholder&quot;"
+        errorLine2="^">
+        <location
+            file="src/main/res/layout/activity_external_pebble_js.xml"
+            line="2"
+            column="1"/>
+    </issue>
+
+    <issue
+        id="UseValueOf"
+        message="Use `Long.valueOf(item.getValue())` instead"
+        errorLine1="            kindArray.add(new SpinnerWithIconItem(item.getKey(), new Long(item.getValue()), ActivityKind.getIconId(item.getValue())));"
+        errorLine2="                                                                 ~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ActivitySummariesFilter.java"
+            line="125"
+            column="66"/>
+    </issue>
+
+    <issue
+        id="UseValueOf"
+        message="Use `Long.valueOf(0)` instead"
+        errorLine1="        SpinnerWithIconItem allActivities = new SpinnerWithIconItem(getString(R.string.activity_summaries_all_activities), new Long(0), ActivityKind.getIconId(0));"
+        errorLine2="                                                                                                                           ~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ActivitySummariesFilter.java"
+            line="129"
+            column="124"/>
+    </issue>
+
+    <issue
+        id="UseValueOf"
+        message="Use `Long.valueOf(entry.getValue())` instead"
+        errorLine1="                        new Long(entry.getValue()),"
+        errorLine2="                        ~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ActivitySummariesFilter.java"
+            line="414"
+            column="25"/>
+    </issue>
+
+    <issue
+        id="DisableBaselineAlignment"
+        message="Set `android:baselineAligned=&quot;false&quot;` on this element for better performance"
+        errorLine1="        &lt;LinearLayout"
+        errorLine2="         ~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_qhybrid_calibration.xml"
+            line="22"
+            column="10"/>
+    </issue>
+
+    <issue
+        id="InefficientWeight"
+        message="Use a `layout_width` of `0dp` instead of `match_parent` for better performance"
+        errorLine1="                android:layout_width=&quot;match_parent&quot;"
+        errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_summary_dashboard_item.xml"
+            line="28"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="InefficientWeight"
+        message="Use a `layout_height` of `0dp` instead of `fill_parent` for better performance"
+        errorLine1="            android:layout_height=&quot;fill_parent&quot;"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/fragment_pai_chart.xml"
+            line="288"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="InefficientWeight"
+        message="Use a `layout_height` of `0dp` instead of `match_parent` for better performance"
+        errorLine1="            android:layout_height=&quot;match_parent&quot;"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout-land/fragment_stresschart.xml"
+            line="15"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="InefficientWeight"
+        message="Use a `layout_height` of `0dp` instead of `fill_parent` for better performance"
+        errorLine1="                android:layout_height=&quot;fill_parent&quot;"
+        errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout-land/fragment_weeksteps_chart.xml"
+            line="27"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="InefficientWeight"
+        message="Use a `layout_height` of `0dp` instead of `3dp` for better performance"
+        errorLine1="                    android:layout_height=&quot;3dp&quot;"
+        errorLine2="                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/widget.xml"
+            line="122"
+            column="21"/>
+    </issue>
+
+    <issue
+        id="InefficientWeight"
+        message="Use a `layout_height` of `0dp` instead of `3dp` for better performance"
+        errorLine1="                    android:layout_height=&quot;3dp&quot;"
+        errorLine2="                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/widget.xml"
+            line="164"
+            column="21"/>
+    </issue>
+
+    <issue
+        id="InefficientWeight"
+        message="Use a `layout_height` of `0dp` instead of `3dp` for better performance"
+        errorLine1="                    android:layout_height=&quot;3dp&quot;"
+        errorLine2="                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/widget.xml"
+            line="205"
+            column="21"/>
+    </issue>
+
+    <issue
+        id="NestedWeights"
+        message="Nested weights are bad for performance"
+        errorLine1="                android:layout_weight=&quot;40&quot; />"
+        errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout-land/fragment_sleepchart.xml"
+            line="32"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="NestedWeights"
+        message="Nested weights are bad for performance"
+        errorLine1="                android:layout_weight=&quot;40&quot; />"
+        errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout-land/fragment_stresschart.xml"
+            line="23"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="NestedWeights"
+        message="Nested weights are bad for performance"
+        errorLine1="                android:layout_weight=&quot;40&quot; />"
+        errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout-land/fragment_weeksteps_chart.xml"
+            line="28"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="Overdraw"
+        message="Possible overdraw: Root element paints background `?android:attr/activatedBackgroundIndicator` with a theme that also paints a background (inferred theme is `@style/GadgetbridgeTheme`)"
+        errorLine1="    android:background=&quot;?android:attr/activatedBackgroundIndicator&quot;"
+        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/item_app_blacklist.xml"
+            line="6"
+            column="5"/>
+    </issue>
+
+    <issue
+        id="Overdraw"
+        message="Possible overdraw: Root element paints background `?android:attr/activatedBackgroundIndicator` with a theme that also paints a background (inferred theme is `@style/GadgetbridgeTheme`)"
+        errorLine1="    android:background=&quot;?android:attr/activatedBackgroundIndicator&quot;"
+        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/item_app_specific_notification_app_list.xml"
+            line="6"
+            column="5"/>
+    </issue>
+
+    <issue
+        id="Overdraw"
+        message="Possible overdraw: Root element paints background `?android:attr/activatedBackgroundIndicator` with a theme that also paints a background (inferred theme is `@style/GadgetbridgeTheme`)"
+        errorLine1="    android:background=&quot;?android:attr/activatedBackgroundIndicator&quot;"
+        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/item_cal_blacklist.xml"
+            line="5"
+            column="5"/>
+    </issue>
+
+    <issue
+        id="Overdraw"
+        message="Possible overdraw: Root element paints background `?android:attr/activatedBackgroundIndicator` with a theme that also paints a background (inferred theme is `@style/GadgetbridgeTheme`)"
+        errorLine1="    android:background=&quot;?android:attr/activatedBackgroundIndicator&quot;>"
+        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/item_with_details.xml"
+            line="5"
+            column="5"/>
+    </issue>
+
+    <issue
+        id="Overdraw"
+        message="Possible overdraw: Root element paints background `?android:attr/activatedBackgroundIndicator` with a theme that also paints a background (inferred theme is `@style/GadgetbridgeTheme`)"
+        errorLine1="    android:background=&quot;?android:attr/activatedBackgroundIndicator&quot;"
+        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/item_with_details_horizontal.xml"
+            line="5"
+            column="5"/>
+    </issue>
+
+    <issue
+        id="Overdraw"
+        message="Possible overdraw: Root element paints background `?android:attr/activatedBackgroundIndicator` with a theme that also paints a background (inferred theme is `@style/GadgetbridgeTheme`)"
+        errorLine1="    android:background=&quot;?android:attr/activatedBackgroundIndicator&quot;"
+        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/item_with_details_small.xml"
+            line="5"
+            column="5"/>
+    </issue>
+
+    <issue
+        id="Overdraw"
+        message="Possible overdraw: Root element paints background `?attr/drawer_header_color` with a theme that also paints a background (inferred theme is `@style/GadgetbridgeTheme`)"
+        errorLine1="    android:background=&quot;?attr/drawer_header_color&quot;"
+        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/nav_header_main.xml"
+            line="6"
+            column="5"/>
+    </issue>
+
+    <issue
+        id="Overdraw"
+        message="Possible overdraw: Root element paints background `?selectableItemBackground` with a theme that also paints a background (inferred theme is `@style/GadgetbridgeTheme`)"
+        errorLine1="    android:background=&quot;?selectableItemBackground&quot;"
+        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/preference_checkbox.xml"
+            line="6"
+            column="5"/>
+    </issue>
+
+    <issue
+        id="Overdraw"
+        message="Possible overdraw: Root element paints background `@color/primary_light` with a theme that also paints a background (inferred theme is `@style/GadgetbridgeTheme`)"
+        errorLine1="    android:background=&quot;@color/primary_light&quot;"
+        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/sleep_alarm_widget.xml"
+            line="4"
+            column="5"/>
+    </issue>
+
+    <issue
+        id="Overdraw"
+        message="Possible overdraw: Root element paints background `@color/primary_dark` with a theme that also paints a background (inferred theme is `@style/Theme_AppCompat_Light_Dialog`)"
+        errorLine1="    android:background=&quot;@color/primary_dark&quot;"
+        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/widget_alarms_activity_list.xml"
+            line="6"
+            column="5"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.array.autoheartrate_interval_hours` appears to be unused"
+        errorLine1="    &lt;string-array name=&quot;autoheartrate_interval_hours&quot;>"
+        errorLine2="                  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/arrays.xml"
+            line="3076"
+            column="19"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.color.blacklist_checkboxes` appears to be unused"
+        errorLine1="&lt;selector xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;>"
+        errorLine2="^">
+        <location
+            file="src/main/res/color/blacklist_checkboxes.xml"
+            line="2"
+            column="1"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.color.btn_dark` appears to be unused"
+        errorLine1="&lt;selector xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;>"
+        errorLine2="^">
+        <location
+            file="src/main/res/color/btn_dark.xml"
+            line="2"
+            column="1"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.xml.changelog_master` appears to be unused"
+        errorLine1="&lt;changelog>"
+        errorLine2="^">
+        <location
+            file="src/main/res/xml/changelog_master.xml"
+            line="2"
+            column="1"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.color.tertiarytext_black` appears to be unused"
+        errorLine1="    &lt;color name=&quot;tertiarytext_black&quot; type=&quot;color&quot;>#000000&lt;/color>"
+        errorLine2="           ~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/colors.xml"
+            line="17"
+            column="12"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.color.divider` appears to be unused"
+        errorLine1="    &lt;color name=&quot;divider&quot; type=&quot;color&quot;>#1f000000&lt;/color>"
+        errorLine2="           ~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/colors.xml"
+            line="19"
+            column="12"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.xml.devicesettings_autoheartrate` appears to be unused"
+        errorLine1="&lt;androidx.preference.PreferenceScreen xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;>"
+        errorLine2="^">
+        <location
+            file="src/main/res/xml/devicesettings_autoheartrate.xml"
+            line="2"
+            column="1"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.xml.devicesettings_connect_two_devices` appears to be unused"
+        errorLine1="&lt;androidx.preference.PreferenceScreen xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;>"
+        errorLine2="^">
+        <location
+            file="src/main/res/xml/devicesettings_connect_two_devices.xml"
+            line="2"
+            column="1"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.xml.devicesettings_donotdisturb_allday_liftwirst` appears to be unused"
+        errorLine1="&lt;androidx.preference.PreferenceScreen xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;>"
+        errorLine2="^">
+        <location
+            file="src/main/res/xml/devicesettings_donotdisturb_allday_liftwirst.xml"
+            line="2"
+            column="1"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.xml.devicesettings_force_options` appears to be unused"
+        errorLine1="&lt;androidx.preference.PreferenceScreen xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;>"
+        errorLine2="^">
+        <location
+            file="src/main/res/xml/devicesettings_force_options.xml"
+            line="2"
+            column="1"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.xml.devicesettings_header_sound_vibration` appears to be unused"
+        errorLine1="&lt;androidx.preference.PreferenceScreen xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;>"
+        errorLine2="^">
+        <location
+            file="src/main/res/xml/devicesettings_header_sound_vibration.xml"
+            line="2"
+            column="1"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.xml.devicesettings_inactivity_sheduled` appears to be unused"
+        errorLine1="&lt;androidx.preference.PreferenceScreen xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;>"
+        errorLine2="^">
+        <location
+            file="src/main/res/xml/devicesettings_inactivity_sheduled.xml"
+            line="2"
+            column="1"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.dimen.nav_header_height` appears to be unused"
+        errorLine1="    &lt;dimen name=&quot;nav_header_height&quot;>160dp&lt;/dimen>"
+        errorLine2="           ~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/dimens.xml"
+            line="14"
+            column="12"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.dimen.fab_margin` appears to be unused"
+        errorLine1="    &lt;dimen name=&quot;fab_margin&quot;>16dp&lt;/dimen>"
+        errorLine2="           ~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/dimens.xml"
+            line="15"
+            column="12"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.drawable.ic_bluetooth_searching` appears to be unused"
+        errorLine1="&lt;vector xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;"
+        errorLine2="^">
+        <location
+            file="src/main/res/drawable/ic_bluetooth_searching.xml"
+            line="1"
+            column="1"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.drawable.ic_calculate` appears to be unused"
+        errorLine1="&lt;vector xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;"
+        errorLine2="^">
+        <location
+            file="src/main/res/drawable/ic_calculate.xml"
+            line="1"
+            column="1"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.drawable.ic_calendar_from` appears to be unused"
+        errorLine1="&lt;vector xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot; android:width=&quot;24dp&quot; android:height=&quot;24dp&quot; android:tint=&quot;#7E7E7E&quot; android:viewportWidth=&quot;24&quot; android:viewportHeight=&quot;24&quot;>"
+        errorLine2="^">
+        <location
+            file="src/main/res/drawable/ic_calendar_from.xml"
+            line="1"
+            column="1"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.drawable.ic_calendar_today` appears to be unused"
+        errorLine1="&lt;vector xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;"
+        errorLine2="^">
+        <location
+            file="src/main/res/drawable/ic_calendar_today.xml"
+            line="1"
+            column="1"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.drawable.ic_create` appears to be unused"
+        errorLine1="&lt;vector xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;"
+        errorLine2="^">
+        <location
+            file="src/main/res/drawable/ic_create.xml"
+            line="1"
+            column="1"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.drawable.ic_delete` appears to be unused"
+        errorLine1="&lt;vector xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;"
+        errorLine2="^">
+        <location
+            file="src/main/res/drawable/ic_delete.xml"
+            line="1"
+            column="1"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.drawable.ic_earbuds_battery` appears to be unused"
+        errorLine1="&lt;vector xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;"
+        errorLine2="^">
+        <location
+            file="src/main/res/drawable/ic_earbuds_battery.xml"
+            line="1"
+            column="1"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.drawable.ic_filter_9_plus` appears to be unused"
+        errorLine1="&lt;vector xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;"
+        errorLine2="^">
+        <location
+            file="src/main/res/drawable/ic_filter_9_plus.xml"
+            line="1"
+            column="1"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.drawable.ic_monochrome_photos` appears to be unused"
+        errorLine1="&lt;vector xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;"
+        errorLine2="^">
+        <location
+            file="src/main/res/drawable/ic_monochrome_photos.xml"
+            line="1"
+            column="1"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.drawable.ic_shoe` appears to be unused"
+        errorLine1="&lt;vector xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;"
+        errorLine2="^">
+        <location
+            file="src/main/res/drawable/ic_shoe.xml"
+            line="1"
+            column="1"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.drawable.ic_show_chart` appears to be unused"
+        errorLine1="&lt;vector xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;"
+        errorLine2="^">
+        <location
+            file="src/main/res/drawable/ic_show_chart.xml"
+            line="1"
+            column="1"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.drawable.ic_wifi` appears to be unused"
+        errorLine1="&lt;vector xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;"
+        errorLine2="^">
+        <location
+            file="src/main/res/drawable/ic_wifi.xml"
+            line="1"
+            column="1"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.drawable.side_nav_bar` appears to be unused"
+        errorLine1="&lt;shape xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;"
+        errorLine2="^">
+        <location
+            file="src/main/res/drawable/side_nav_bar.xml"
+            line="1"
+            column="1"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.anim.slidefromtop` appears to be unused"
+        errorLine1="&lt;set xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;"
+        errorLine2="^">
+        <location
+            file="src/main/res/anim/slidefromtop.xml"
+            line="2"
+            column="1"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.application_name_banglejs_main` appears to be unused"
+        errorLine1="    &lt;string name=&quot;application_name_banglejs_main&quot;>Bangle.js Gadgetbridge&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="9"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.title_activity_controlcenter_banglejs_main` appears to be unused"
+        errorLine1="    &lt;string name=&quot;title_activity_controlcenter_banglejs_main&quot;>Bangle.js Gadgetbridge&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="10"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.about_activity_title_banglejs_main` appears to be unused"
+        errorLine1="    &lt;string name=&quot;about_activity_title_banglejs_main&quot;>About Bangle.js Gadgetbridge&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="11"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.about_description_banglejs_main` appears to be unused"
+        errorLine1="    &lt;string name=&quot;about_description_banglejs_main&quot;>Android companion app for Bangle.js built on top of the Gadgetbridge project, with added Internet Access.\n\nDue to Google Play Store policies, we are not allowed a donation link in the app itself, but if you like this app please consider donating via the Gadgetbridge homepage below.&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="12"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.gadgetbridge_running_banglejs_main` appears to be unused"
+        errorLine1="    &lt;string name=&quot;gadgetbridge_running_banglejs_main&quot;>Bangle.js running&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="13"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.application_name_banglejs_nightly` appears to be unused"
+        errorLine1="    &lt;string name=&quot;application_name_banglejs_nightly&quot;>Bangle.js Gadgetbridge (Nightly)&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="15"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.title_activity_controlcenter_banglejs_nightly` appears to be unused"
+        errorLine1="    &lt;string name=&quot;title_activity_controlcenter_banglejs_nightly&quot;>Bangle.js Gadgetbridge (Nightly)&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="16"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.about_activity_title_banglejs_nightly` appears to be unused"
+        errorLine1="    &lt;string name=&quot;about_activity_title_banglejs_nightly&quot;>About Bangle.js Gadgetbridge (Nightly)&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="17"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.about_description_banglejs_nightly` appears to be unused"
+        errorLine1="    &lt;string name=&quot;about_description_banglejs_nightly&quot;>Android companion app for Bangle.js built on top of the Gadgetbridge project, with added Internet Access.\n\nDue to Google Play Store policies, we are not allowed a donation link in the app itself, but if you like this app please consider donating via the Gadgetbridge homepage below.&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="18"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.gadgetbridge_running_banglejs_nightly` appears to be unused"
+        errorLine1="    &lt;string name=&quot;gadgetbridge_running_banglejs_nightly&quot;>Nightly Bangle.js running&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="19"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.application_name_main_nightly` appears to be unused"
+        errorLine1="    &lt;string name=&quot;application_name_main_nightly&quot;>Gadgetbridge (Nightly)&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="21"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.title_activity_controlcenter_main_nightly` appears to be unused"
+        errorLine1="    &lt;string name=&quot;title_activity_controlcenter_main_nightly&quot;>Gadgetbridge Nightly&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="22"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.about_activity_title_main_nightly` appears to be unused"
+        errorLine1="    &lt;string name=&quot;about_activity_title_main_nightly&quot;>About Gadgetbridge Nightly&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="23"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.about_description_main_nightly` appears to be unused"
+        errorLine1="    &lt;string name=&quot;about_description_main_nightly&quot;>Cloudless copylefted libre replacement for closed source Android gadget apps from vendors. Nightly releases of Gadgetbridge. It cannot be installed if you already have either the Gadgetbridge or the Pebble app installed, due to a conflict in the Pebble provider.&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="24"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.gadgetbridge_running_main_nightly` appears to be unused"
+        errorLine1="    &lt;string name=&quot;gadgetbridge_running_main_nightly&quot;>Nightly GB running&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="25"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.application_name_main_nopebble` appears to be unused"
+        errorLine1="    &lt;string name=&quot;application_name_main_nopebble&quot;>Gadgetbridge (Nightly, No Pebble provider)&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="27"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.title_activity_controlcenter_main_nopebble` appears to be unused"
+        errorLine1="    &lt;string name=&quot;title_activity_controlcenter_main_nopebble&quot;>Gadgetbridge Nightly No Pebble &lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="28"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.about_activity_title_main_nopebble` appears to be unused"
+        errorLine1="    &lt;string name=&quot;about_activity_title_main_nopebble&quot;>About Gadgetbridge Nightly No Pebble&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="29"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.about_description_main_nopebble` appears to be unused"
+        errorLine1="    &lt;string name=&quot;about_description_main_nopebble&quot;>Cloudless copylefted libre replacement for closed source Android gadget apps from vendors. Nightly releases of Gadgetbridge. This version has the Pebble provider renamed to prevent conflicts, so some Pebble related integrations will not work, but it can be installed alongside existing Gadgetbridge installation.&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="30"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.gadgetbridge_running_main_nopebble` appears to be unused"
+        errorLine1="    &lt;string name=&quot;gadgetbridge_running_main_nopebble&quot;>Nightly NoPebble GB running&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="31"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.app_move_to_top` appears to be unused"
+        errorLine1="    &lt;string name=&quot;app_move_to_top&quot;>Move to top&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="122"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.blacklist_all_for_notifications` appears to be unused"
+        errorLine1="    &lt;string name=&quot;blacklist_all_for_notifications&quot;>Blacklist all for notifications&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="129"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.whitelist_all_for_notifications` appears to be unused"
+        errorLine1="    &lt;string name=&quot;whitelist_all_for_notifications&quot;>Whitelist all for notifications&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="130"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.fw_upgrade_notice_miband7` appears to be unused"
+        errorLine1="    &lt;string name=&quot;fw_upgrade_notice_miband7&quot;>You are about to install the %s firmware on your Xiaomi Smart Band 7.\n\nYour band will reboot after installing the .zip file.\n\nPROCEED AT YOUR OWN RISK!&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="159"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.fw_upgrade_notice_amazfit_gts3` appears to be unused"
+        errorLine1="    &lt;string name=&quot;fw_upgrade_notice_amazfit_gts3&quot;>You are about to install the %s firmware on your Amazfit GTS 3.\n\nYour band will reboot after installing the .zip file.\n\nPROCEED AT YOUR OWN RISK!&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="160"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.fw_upgrade_notice_amazfit_gts4` appears to be unused"
+        errorLine1="    &lt;string name=&quot;fw_upgrade_notice_amazfit_gts4&quot;>You are about to install the %s firmware on your Amazfit GTS 4.\n\nYour band will reboot after installing the .zip file.\n\nPROCEED AT YOUR OWN RISK!&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="161"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.fw_upgrade_notice_amazfit_gts4_mini` appears to be unused"
+        errorLine1="    &lt;string name=&quot;fw_upgrade_notice_amazfit_gts4_mini&quot;>You are about to install the %s firmware on your Amazfit GTS 4 Mini.\n\nYour band will reboot after installing the .zip file.\n\nPROCEED AT YOUR OWN RISK!&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="162"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.fw_upgrade_notice_amazfit_gtr3` appears to be unused"
+        errorLine1="    &lt;string name=&quot;fw_upgrade_notice_amazfit_gtr3&quot;>You are about to install the %s firmware on your Amazfit GTR 3.\n\nYour band will reboot after installing the .zip file.\n\nPROCEED AT YOUR OWN RISK!&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="163"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.fw_upgrade_notice_amazfit_gtr3_pro` appears to be unused"
+        errorLine1="    &lt;string name=&quot;fw_upgrade_notice_amazfit_gtr3_pro&quot;>You are about to install the %s firmware on your Amazfit GTR 3 Pro.\n\nYour band will reboot after installing the .zip file.\n\nPROCEED AT YOUR OWN RISK!&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="164"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.fw_upgrade_notice_amazfit_gtr4` appears to be unused"
+        errorLine1="    &lt;string name=&quot;fw_upgrade_notice_amazfit_gtr4&quot;>You are about to install the %s firmware on your Amazfit GTR 4.\n\nYour band will reboot after installing the .zip file.\n\nPROCEED AT YOUR OWN RISK!&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="165"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.fw_upgrade_notice_amazfit_trex2` appears to be unused"
+        errorLine1="    &lt;string name=&quot;fw_upgrade_notice_amazfit_trex2&quot;>You are about to install the %s firmware on your Amazfit T-Rex 2.\n\nYour band will reboot after installing the .zip file.\n\nPROCEED AT YOUR OWN RISK!&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="166"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.fw_upgrade_notice_amazfit_band7` appears to be unused"
+        errorLine1="    &lt;string name=&quot;fw_upgrade_notice_amazfit_band7&quot;>You are about to install the %s firmware on your Amazfit Band 7.\n\nYour band will reboot after installing the .zip file.\n\nPROCEED AT YOUR OWN RISK!&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="167"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.fw_upgrade_notice_amazfit_cheetah_pro` appears to be unused"
+        errorLine1="    &lt;string name=&quot;fw_upgrade_notice_amazfit_cheetah_pro&quot;>You are about to install the %s firmware on your Amazfit Cheetah Pro.\n\nYour band will reboot after installing the .zip file.\n\nPROCEED AT YOUR OWN RISK!&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="168"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.open_fw_installer_ensure_device_connected` appears to be unused"
+        errorLine1="    &lt;string name=&quot;open_fw_installer_ensure_device_connected&quot;>Make sure that the device %s is connected&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="191"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.pref_pair_bluetooth_calls_help_title` appears to be unused"
+        errorLine1="    &lt;string name=&quot;pref_pair_bluetooth_calls_help_title&quot;>How to receive bluetooth calls&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="241"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.pref_title_enable_calendar_sync` appears to be unused"
+        errorLine1="    &lt;string name=&quot;pref_title_enable_calendar_sync&quot;>Sync calendar&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="333"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.pref_summary_enable_calendar_sync` appears to be unused"
+        errorLine1="    &lt;string name=&quot;pref_summary_enable_calendar_sync&quot;>Send calendar events to the timeline&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="334"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.preferences_makibes_hr3_settings` appears to be unused"
+        errorLine1="    &lt;string name=&quot;preferences_makibes_hr3_settings&quot;>Makibes HR3 settings&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="454"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.zetime_title_alarm_signaling` appears to be unused"
+        errorLine1="    &lt;string name=&quot;zetime_title_alarm_signaling&quot;>Set type of signaling for the alarm&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="488"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.prefs_hr_alarm_activity` appears to be unused"
+        errorLine1="    &lt;string name=&quot;prefs_hr_alarm_activity&quot;>Heart rate alarm during sports activity&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="508"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.prefs_hr_alarm_low` appears to be unused"
+        errorLine1="    &lt;string name=&quot;prefs_hr_alarm_low&quot;>Low limit&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="509"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.prefs_hr_alarm_high` appears to be unused"
+        errorLine1="    &lt;string name=&quot;prefs_hr_alarm_high&quot;>High limit&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="510"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.pref_workout_detection_enabled_summary` appears to be unused"
+        errorLine1="    &lt;string name=&quot;pref_workout_detection_enabled_summary&quot;>Enable automatic detection of this workout&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="549"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.pref_disable_new_ble_scanning` appears to be unused"
+        errorLine1="    &lt;string name=&quot;pref_disable_new_ble_scanning&quot;>Disable new BLE scanning&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="572"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.pref_summary_disable_new_ble_scanning` appears to be unused"
+        errorLine1="    &lt;string name=&quot;pref_summary_disable_new_ble_scanning&quot;>Check this option if your device cannot be found during discovery&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="573"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.test` appears to be unused"
+        errorLine1="    &lt;string name=&quot;test&quot;>Test&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="580"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.tap_connected_device_for_app_mananger` appears to be unused"
+        errorLine1="    &lt;string name=&quot;tap_connected_device_for_app_mananger&quot;>Tap connected device for App manager&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="586"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.tap_connected_device_for_activity` appears to be unused"
+        errorLine1="    &lt;string name=&quot;tap_connected_device_for_activity&quot;>Tap connected device for activity&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="587"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.tap_connected_device_for_vibration` appears to be unused"
+        errorLine1="    &lt;string name=&quot;tap_connected_device_for_vibration&quot;>Tap connected device for vibration&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="588"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.tap_a_device_to_connect` appears to be unused"
+        errorLine1="    &lt;string name=&quot;tap_a_device_to_connect&quot;>Tap a device to connect&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="589"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.pairing_unable_to_pair_with` appears to be unused"
+        errorLine1="    &lt;string name=&quot;pairing_unable_to_pair_with&quot;>&quot;Unable to pair with %1$s (%2$s)&quot;&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="612"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.pref_header_vibration_count` appears to be unused"
+        errorLine1="    &lt;string name=&quot;pref_header_vibration_count&quot;>Vibration count&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="640"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.title_activity_sleepmonitor` appears to be unused"
+        errorLine1="    &lt;string name=&quot;title_activity_sleepmonitor&quot;>Sleep monitor&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="642"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.pref_screen_vibration_profile` appears to be unused"
+        errorLine1="    &lt;string name=&quot;pref_screen_vibration_profile&quot;>Vibration profile&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="663"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.control_center_cancel_to_stop_vibration` appears to be unused"
+        errorLine1="    &lt;string name=&quot;control_center_cancel_to_stop_vibration&quot;>Cancel to stop vibration.&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="730"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.chart_no_active_data` appears to be unused"
+        errorLine1="    &lt;string name=&quot;chart_no_active_data&quot;>No activities detected.&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="786"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.chart_get_active_and_synchronize` appears to be unused"
+        errorLine1="    &lt;string name=&quot;chart_get_active_and_synchronize&quot;>Do some activity and synchronize device.&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="787"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.clock` appears to be unused"
+        errorLine1="    &lt;string name=&quot;clock&quot;>Clock&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="879"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.discovery_scanning_intensity_warning` appears to be unused"
+        errorLine1="    &lt;string name=&quot;discovery_scanning_intensity_warning&quot;>廿匕丕 賵丕噩賴鬲 鬲噩賲賷丿賸丕 兀賵 毓丿賲 丕爻鬲噩丕亘丞 貙 賮丨丕賵賱 囟亘胤 賰孬丕賮丞 丕賱賲爻丨 毓賱賶 賲爻鬲賵賶 兀賯賱. 廿匕丕 賱賲 賷鬲賲 丕賰鬲卮丕賮 噩賴丕夭賰 貙 賮丨丕賵賱 囟亘胤 賰孬丕賮丞 丕賱賲爻丨 毓賱賶 賲爻鬲賵賶 兀毓賱賶.&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values-ar/strings.xml"
+            line="904"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.pref_title_dont_ack_transfer` appears to be unused"
+        errorLine1="    &lt;string name=&quot;pref_title_dont_ack_transfer&quot;>Do not ACK activity data transfer&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="912"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.pref_summary_dont_ack_transfers` appears to be unused"
+        errorLine1="    &lt;string name=&quot;pref_summary_dont_ack_transfers&quot;>If not ACKed to the band, activity data is not cleared. Useful if GB is used together with other apps.&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="913"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.pref_enable_unsupported_settings_title` appears to be unused"
+        errorLine1="    &lt;string name=&quot;pref_enable_unsupported_settings_title&quot;>Enable unsupported settings&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="915"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.pref_enable_unsupported_settings_summary` appears to be unused"
+        errorLine1="    &lt;string name=&quot;pref_enable_unsupported_settings_summary&quot;>This will enable access to all available settings, even if unsupported by the device. This can cause instability and crashes on the device.&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="916"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.preferences_miband_settings` appears to be unused"
+        errorLine1="    &lt;string name=&quot;preferences_miband_settings&quot;>廿毓丿丕丿丕鬲 Mi Band / Amazfit&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values-ar/strings.xml"
+            line="970"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.device_hw` appears to be unused"
+        errorLine1="    &lt;string name=&quot;device_hw&quot;>Hardware revision: %1$s&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1117"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.device_fw` appears to be unused"
+        errorLine1="    &lt;string name=&quot;device_fw&quot;>Firmware version: %1$s&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1118"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.DEVINFO_HR_VER` appears to be unused"
+        errorLine1="    &lt;string name=&quot;DEVINFO_HR_VER&quot;>&quot;HR: &quot;&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1120"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.watchface_dialog_title_add_widget` appears to be unused"
+        errorLine1="    &lt;string name=&quot;watchface_dialog_title_add_widget&quot;>Afegeix un giny&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values-ca/strings.xml"
+            line="1150"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.dbmanagementactivity_error_importing_shared` appears to be unused"
+        errorLine1="    &lt;string name=&quot;dbmanagementactivity_error_importing_shared&quot;>&quot;Error importing preference: %1$s&quot;&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1152"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.activity_data_management_directory_content_title` appears to be unused"
+        errorLine1="    &lt;string name=&quot;activity_data_management_directory_content_title&quot;>Export/Import directory content&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1187"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.activity_web_view` appears to be unused"
+        errorLine1="    &lt;string name=&quot;activity_web_view&quot;>Web View Activity&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1207"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.miband2_prefs_timeformat` appears to be unused"
+        errorLine1="    &lt;string name=&quot;miband2_prefs_timeformat&quot;>Mi2: Time format&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1211"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.mi3_night_mode_sunset` appears to be unused"
+        errorLine1="    &lt;string name=&quot;mi3_night_mode_sunset&quot;>At sunset&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1222"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string._pebble_watch_reply` appears to be unused"
+        errorLine1="    &lt;string name=&quot;_pebble_watch_reply&quot;>Reply&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1239"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.watchface_toast_settings_incomplete` appears to be unused"
+        errorLine1="    &lt;string name=&quot;watchface_toast_settings_incomplete&quot;>Configuraci贸 incompleta, giny no afegit&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values-ca/strings.xml"
+            line="1360"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.activity_type_kickboxing` appears to be unused"
+        errorLine1="    &lt;string name=&quot;activity_type_kickboxing&quot;>Kickboxing&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1368"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.application_name_banglejs_nopebble` appears to be unused"
+        errorLine1="    &lt;string name=&quot;application_name_banglejs_nopebble&quot;>Bangle.js Gadgetbridge&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values-ca/strings.xml"
+            line="1458"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.title_activity_controlcenter_banglejs_nopebble` appears to be unused"
+        errorLine1="    &lt;string name=&quot;title_activity_controlcenter_banglejs_nopebble&quot;>Bangle.js Gadgetbridge&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values-ca/strings.xml"
+            line="1459"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.about_activity_title_banglejs_nopebble` appears to be unused"
+        errorLine1="    &lt;string name=&quot;about_activity_title_banglejs_nopebble&quot;>Sobre Bangle.js Gadgetbridge&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values-ca/strings.xml"
+            line="1460"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.devicetype_watchx` appears to be unused"
+        errorLine1="    &lt;string name=&quot;devicetype_watchx&quot;>Watch X&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1485"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.about_description_banglejs_nopebble` appears to be unused"
+        errorLine1="    &lt;string name=&quot;about_description_banglejs_nopebble&quot;>Doprovodn谩 aplikace pro Bangle.js pro Android vytvo艡en谩 na z谩klad臎 projektu Gadgetbridge s p艡idan媒m p艡铆stupem k internetu."
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values-cs/strings.xml"
+            line="1584"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.gadgetbridge_running_banglejs_nopebble` appears to be unused"
+        errorLine1="    &lt;string name=&quot;gadgetbridge_running_banglejs_nopebble&quot;>Bangle.js b臎啪铆&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values-cs/strings.xml"
+            line="1597"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.menuitem_unknown` appears to be unused"
+        errorLine1="    &lt;string name=&quot;menuitem_unknown&quot;>Unknown&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1599"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.heartrate_bpm_112` appears to be unused"
+        errorLine1="    &lt;string name=&quot;heartrate_bpm_112&quot;>112 tep暖/min&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values-cs/strings.xml"
+            line="1640"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.menuitem_unsupported` appears to be unused"
+        errorLine1="    &lt;string name=&quot;menuitem_unsupported&quot;>[UNSUPPORTED] %s&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1640"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.pref_title_upper_button_function_double` appears to be unused"
+        errorLine1="    &lt;string name=&quot;pref_title_upper_button_function_double&quot;>Upper Button double&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1749"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.pref_title_middle_button_function_double` appears to be unused"
+        errorLine1="    &lt;string name=&quot;pref_title_middle_button_function_double&quot;>Middle Button double&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1750"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.pref_title_lower_button_function_double` appears to be unused"
+        errorLine1="    &lt;string name=&quot;pref_title_lower_button_function_double&quot;>Lower Button double&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1751"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.watchface_setting_title_button_toggle_widgets` appears to be unused"
+        errorLine1="    &lt;string name=&quot;watchface_setting_title_button_toggle_widgets&quot;>P艡ep铆n谩n铆 viditelnosti widget暖&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values-cs/strings.xml"
+            line="1781"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.error_exporting_device_preferences` appears to be unused"
+        errorLine1="    &lt;string name=&quot;error_exporting_device_preferences&quot;>Error exporting device specific preferences&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1801"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.sonyswr12_settings_title` appears to be unused"
+        errorLine1="    &lt;string name=&quot;sonyswr12_settings_title&quot;>Sony SWR12 Settings&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1809"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.distanceMeters` appears to be unused"
+        errorLine1="    &lt;string name=&quot;distanceMeters&quot;>Distance&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1817"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.ascentMeters` appears to be unused"
+        errorLine1="    &lt;string name=&quot;ascentMeters&quot;>Uphill&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1818"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.descentMeters` appears to be unused"
+        errorLine1="    &lt;string name=&quot;descentMeters&quot;>Downhill&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1819"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.ascentDistance` appears to be unused"
+        errorLine1="    &lt;string name=&quot;ascentDistance&quot;>Uphill distance&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1820"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.descentDistance` appears to be unused"
+        errorLine1="    &lt;string name=&quot;descentDistance&quot;>Downhill distance&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1821"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.flatDistance` appears to be unused"
+        errorLine1="    &lt;string name=&quot;flatDistance&quot;>Flat distance&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1822"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.elevationGain` appears to be unused"
+        errorLine1="    &lt;string name=&quot;elevationGain&quot;>Elevation gain&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1823"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.elevationLoss` appears to be unused"
+        errorLine1="    &lt;string name=&quot;elevationLoss&quot;>Elevation loss&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1824"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.maxAltitude` appears to be unused"
+        errorLine1="    &lt;string name=&quot;maxAltitude&quot;>Maximum&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1825"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.minAltitude` appears to be unused"
+        errorLine1="    &lt;string name=&quot;minAltitude&quot;>Minimum&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1826"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.averageAltitude` appears to be unused"
+        errorLine1="    &lt;string name=&quot;averageAltitude&quot;>Average&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1827"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.activeSeconds` appears to be unused"
+        errorLine1="    &lt;string name=&quot;activeSeconds&quot;>Active&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1829"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.maxSpeed` appears to be unused"
+        errorLine1="    &lt;string name=&quot;maxSpeed&quot;>Maximum&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1831"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.minSpeed` appears to be unused"
+        errorLine1="    &lt;string name=&quot;minSpeed&quot;>Minimum&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1832"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.minPace` appears to be unused"
+        errorLine1="    &lt;string name=&quot;minPace&quot;>Slowest Pace&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1833"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.maxPace` appears to be unused"
+        errorLine1="    &lt;string name=&quot;maxPace&quot;>Fastest Pace&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1834"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.totalStride` appears to be unused"
+        errorLine1="    &lt;string name=&quot;totalStride&quot;>Total stride&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1835"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.averageHR` appears to be unused"
+        errorLine1="    &lt;string name=&quot;averageHR&quot;>Heartrate&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1836"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.maxHR` appears to be unused"
+        errorLine1="    &lt;string name=&quot;maxHR&quot;>Max Heartrate&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1837"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.minHR` appears to be unused"
+        errorLine1="    &lt;string name=&quot;minHR&quot;>Min Heartrate&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1838"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.averageKMPaceSeconds` appears to be unused"
+        errorLine1="    &lt;string name=&quot;averageKMPaceSeconds&quot;>Pace&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1839"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.HeartRateZones` appears to be unused"
+        errorLine1="    &lt;string name=&quot;HeartRateZones&quot;>Heart Rate Zones&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1840"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.hrZoneNa` appears to be unused"
+        errorLine1="    &lt;string name=&quot;hrZoneNa&quot;>N/A&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1841"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.hrZoneWarmUp` appears to be unused"
+        errorLine1="    &lt;string name=&quot;hrZoneWarmUp&quot;>Warm-Up&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1842"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.hrZoneFatBurn` appears to be unused"
+        errorLine1="    &lt;string name=&quot;hrZoneFatBurn&quot;>Fat Burn&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1843"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.hrZoneAerobic` appears to be unused"
+        errorLine1="    &lt;string name=&quot;hrZoneAerobic&quot;>Aerobic&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1844"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.hrZoneAnaerobic` appears to be unused"
+        errorLine1="    &lt;string name=&quot;hrZoneAnaerobic&quot;>Anaerobic&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1845"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.hrZoneExtreme` appears to be unused"
+        errorLine1="    &lt;string name=&quot;hrZoneExtreme&quot;>Extreme&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1846"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.TrainingEffect` appears to be unused"
+        errorLine1="    &lt;string name=&quot;TrainingEffect&quot;>Training Effect&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1847"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.aerobicTrainingEffect` appears to be unused"
+        errorLine1="    &lt;string name=&quot;aerobicTrainingEffect&quot;>Aerobic Effect&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1848"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.anaerobicTrainingEffect` appears to be unused"
+        errorLine1="    &lt;string name=&quot;anaerobicTrainingEffect&quot;>Anaerobic Effect&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1849"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.currentWorkoutLoad` appears to be unused"
+        errorLine1="    &lt;string name=&quot;currentWorkoutLoad&quot;>Workout Load&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1850"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.maximumOxygenUptake` appears to be unused"
+        errorLine1="    &lt;string name=&quot;maximumOxygenUptake&quot;>Maximum Oxygen Uptake&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1851"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.averageStride` appears to be unused"
+        errorLine1="    &lt;string name=&quot;averageStride&quot;>Average Stride&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1852"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.maxStride` appears to be unused"
+        errorLine1="    &lt;string name=&quot;maxStride&quot;>Max Stride&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1853"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.minStride` appears to be unused"
+        errorLine1="    &lt;string name=&quot;minStride&quot;>Min Stride&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1854"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.averageCadence` appears to be unused"
+        errorLine1="    &lt;string name=&quot;averageCadence&quot;>Average Cadence&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1855"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.maxCadence` appears to be unused"
+        errorLine1="    &lt;string name=&quot;maxCadence&quot;>Max Cadence&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1856"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.minCadence` appears to be unused"
+        errorLine1="    &lt;string name=&quot;minCadence&quot;>Min Cadence&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1857"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.averageStrokeDistance` appears to be unused"
+        errorLine1="    &lt;string name=&quot;averageStrokeDistance&quot;>Average Stroke Distance&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1858"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.averageStrokesPerSecond` appears to be unused"
+        errorLine1="    &lt;string name=&quot;averageStrokesPerSecond&quot;>Average Strokes&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1859"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.avgStrokeRate` appears to be unused"
+        errorLine1="    &lt;string name=&quot;avgStrokeRate&quot;>Average Stroke Rate&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1860"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.maxStrokeRate` appears to be unused"
+        errorLine1="    &lt;string name=&quot;maxStrokeRate&quot;>Max Stroke Rate&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1861"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.strokes` appears to be unused"
+        errorLine1="    &lt;string name=&quot;strokes&quot;>Total Strokes&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1862"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.averageLapPace` appears to be unused"
+        errorLine1="    &lt;string name=&quot;averageLapPace&quot;>Average Lap Pace&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1863"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.swolfIndex` appears to be unused"
+        errorLine1="    &lt;string name=&quot;swolfIndex&quot;>SWOLF&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1864"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.swimStyle` appears to be unused"
+        errorLine1="    &lt;string name=&quot;swimStyle&quot;>Swim Style&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1865"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.laneLength` appears to be unused"
+        errorLine1="    &lt;string name=&quot;laneLength&quot;>Lane Length&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1866"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.laps` appears to be unused"
+        errorLine1="    &lt;string name=&quot;laps&quot;>Laps&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1867"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.ascentSeconds` appears to be unused"
+        errorLine1="    &lt;string name=&quot;ascentSeconds&quot;>Ascending&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1868"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.descentSeconds` appears to be unused"
+        errorLine1="    &lt;string name=&quot;descentSeconds&quot;>Descending&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1869"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.flatSeconds` appears to be unused"
+        errorLine1="    &lt;string name=&quot;flatSeconds&quot;>Flat&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1870"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.baseAltitude` appears to be unused"
+        errorLine1="    &lt;string name=&quot;baseAltitude&quot;>Base Elevation&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1871"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.averageSpeed` appears to be unused"
+        errorLine1="    &lt;string name=&quot;averageSpeed&quot;>Average Speed&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1872"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.meters` appears to be unused"
+        errorLine1="    &lt;string name=&quot;meters&quot;>m&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1874"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.cm` appears to be unused"
+        errorLine1="    &lt;string name=&quot;cm&quot;>cm&lt;/string>"
+        errorLine2="            ~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1875"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.yard` appears to be unused"
+        errorLine1="    &lt;string name=&quot;yard&quot;>yard&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1876"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.ft` appears to be unused"
+        errorLine1="    &lt;string name=&quot;ft&quot;>ft&lt;/string>"
+        errorLine2="            ~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1877"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.steps_unit` appears to be unused"
+        errorLine1="    &lt;string name=&quot;steps_unit&quot;>steps&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1878"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.meters_second` appears to be unused"
+        errorLine1="    &lt;string name=&quot;meters_second&quot;>m/s&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1879"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.km_h` appears to be unused"
+        errorLine1="    &lt;string name=&quot;km_h&quot;>km/h&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1880"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.mi_h` appears to be unused"
+        errorLine1="    &lt;string name=&quot;mi_h&quot;>mi/h&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1881"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.minutes_mi` appears to be unused"
+        errorLine1="    &lt;string name=&quot;minutes_mi&quot;>min/mi&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1882"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.strokes_second` appears to be unused"
+        errorLine1="    &lt;string name=&quot;strokes_second&quot;>str/s&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1883"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.strokes_minute` appears to be unused"
+        errorLine1="    &lt;string name=&quot;strokes_minute&quot;>str/min&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1884"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.strokes_unit` appears to be unused"
+        errorLine1="    &lt;string name=&quot;strokes_unit&quot;>str&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1885"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.seconds` appears to be unused"
+        errorLine1="    &lt;string name=&quot;seconds&quot;>sec&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1886"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.swolf_index` appears to be unused"
+        errorLine1="    &lt;string name=&quot;swolf_index&quot;>swolf index&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1887"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.swim_style` appears to be unused"
+        errorLine1="    &lt;string name=&quot;swim_style&quot;>swim style&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1888"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.laps_unit` appears to be unused"
+        errorLine1="    &lt;string name=&quot;laps_unit&quot;>laps&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1889"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.seconds_km` appears to be unused"
+        errorLine1="    &lt;string name=&quot;seconds_km&quot;>sec/km&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1891"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.seconds_m` appears to be unused"
+        errorLine1="    &lt;string name=&quot;seconds_m&quot;>sec/m&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1892"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.minutes_km` appears to be unused"
+        errorLine1="    &lt;string name=&quot;minutes_km&quot;>min/km&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1893"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.discovery_scanning_intensity` appears to be unused"
+        errorLine1="    &lt;string name=&quot;discovery_scanning_intensity&quot;>賰孬丕賮丞 丕賱賮丨氐&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values-ar/strings.xml"
+            line="1894"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.spm` appears to be unused"
+        errorLine1="    &lt;string name=&quot;spm&quot;>steps/min&lt;/string>"
+        errorLine2="            ~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1894"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.bpm` appears to be unused"
+        errorLine1="    &lt;string name=&quot;bpm&quot;>bpm&lt;/string>"
+        errorLine2="            ~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1895"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.mi` appears to be unused"
+        errorLine1="    &lt;string name=&quot;mi&quot;>mi&lt;/string>"
+        errorLine2="            ~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1897"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.Strokes` appears to be unused"
+        errorLine1="    &lt;string name=&quot;Strokes&quot;>Strokes&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1899"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.Swimming` appears to be unused"
+        errorLine1="    &lt;string name=&quot;Swimming&quot;>Swimming&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1900"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.Elevation` appears to be unused"
+        errorLine1="    &lt;string name=&quot;Elevation&quot;>Elevation&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1902"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.Speed` appears to be unused"
+        errorLine1="    &lt;string name=&quot;Speed&quot;>Speed&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1903"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.pref_header_statistics` appears to be unused"
+        errorLine1="    &lt;string name=&quot;pref_header_statistics&quot;>Sports Activities Statistics&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1928"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.activity_summaries_statistics` appears to be unused"
+        errorLine1="    &lt;string name=&quot;activity_summaries_statistics&quot;>Statistics&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1939"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.breaststroke` appears to be unused"
+        errorLine1="    &lt;string name=&quot;breaststroke&quot;>Breaststroke&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1954"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.freestyle` appears to be unused"
+        errorLine1="    &lt;string name=&quot;freestyle&quot;>Freestyle&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1955"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.backstroke` appears to be unused"
+        errorLine1="    &lt;string name=&quot;backstroke&quot;>Backstroke&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1956"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.medley` appears to be unused"
+        errorLine1="    &lt;string name=&quot;medley&quot;>Medley&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1957"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.fossil_hr_commute_processing` appears to be unused"
+        errorLine1="    &lt;string name=&quot;fossil_hr_commute_processing&quot;>Processing request鈥�&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1973"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.prefs_autoheartrate` appears to be unused"
+        errorLine1="    &lt;string name=&quot;prefs_autoheartrate&quot;>Automatic Heart Rate&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="2074"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.prefs_autoheartrate_summary` appears to be unused"
+        errorLine1="    &lt;string name=&quot;prefs_autoheartrate_summary&quot;>Periodical Heart Rate measurements during the day and also while asleep&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="2075"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.prefs_autoheartrate_measurement` appears to be unused"
+        errorLine1="    &lt;string name=&quot;prefs_autoheartrate_measurement&quot;>Automatic Heart Rate measurements&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="2076"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.prefs_autoheartrate_sleep` appears to be unused"
+        errorLine1="    &lt;string name=&quot;prefs_autoheartrate_sleep&quot;>Take measurements during sleep&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="2077"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.prefs_autoheartrate_interval` appears to be unused"
+        errorLine1="    &lt;string name=&quot;prefs_autoheartrate_interval&quot;>Frequency of measurements&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="2078"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.pref_title_touch_volume` appears to be unused"
+        errorLine1="    &lt;string name=&quot;pref_title_touch_volume&quot;>Volume&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="2143"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.pref_title_touch_spotify_galaxy_app` appears to be unused"
+        errorLine1="    &lt;string name=&quot;pref_title_touch_spotify_galaxy_app&quot;>Spotify (Galaxy Wearable app only)&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="2147"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.sony_anc_optimizer_status_not_running` appears to be unused"
+        errorLine1="    &lt;string name=&quot;sony_anc_optimizer_status_not_running&quot;>Not Running&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="2209"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.sony_anc_optimizer_status_wearing_condition` appears to be unused"
+        errorLine1="    &lt;string name=&quot;sony_anc_optimizer_status_wearing_condition&quot;>Measuring wearing condition鈥�&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="2210"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.sony_anc_optimizer_status_atmospheric_pressure` appears to be unused"
+        errorLine1="    &lt;string name=&quot;sony_anc_optimizer_status_atmospheric_pressure&quot;>Measuring atmospheric pressure鈥�&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="2211"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.sony_anc_optimizer_status_analyzing` appears to be unused"
+        errorLine1="    &lt;string name=&quot;sony_anc_optimizer_status_analyzing&quot;>Analyzing鈥�&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="2212"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.sony_anc_optimizer_status_finished` appears to be unused"
+        errorLine1="    &lt;string name=&quot;sony_anc_optimizer_status_finished&quot;>Finishing鈥�&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="2213"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.pref_header_sony_equalizer_bands` appears to be unused"
+        errorLine1="    &lt;string name=&quot;pref_header_sony_equalizer_bands&quot;>Bands&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="2226"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.pref_header_sony_equalizer_preset_custom_1` appears to be unused"
+        errorLine1="    &lt;string name=&quot;pref_header_sony_equalizer_preset_custom_1&quot;>Custom Preset 1&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="2227"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.pref_header_sony_equalizer_preset_custom_2` appears to be unused"
+        errorLine1="    &lt;string name=&quot;pref_header_sony_equalizer_preset_custom_2&quot;>Custom Preset 2&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="2228"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.sony_connect_two_devices` appears to be unused"
+        errorLine1="    &lt;string name=&quot;sony_connect_two_devices&quot;>Connect to 2 devices simultaneously&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="2250"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.prefs_activity_recognition` appears to be unused"
+        errorLine1="    &lt;string name=&quot;prefs_activity_recognition&quot;>Activity recognition settings&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="2334"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.pref_activity_recognize_running` appears to be unused"
+        errorLine1="    &lt;string name=&quot;pref_activity_recognize_running&quot;>recognize running&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="2335"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.pref_activity_recognize_biking` appears to be unused"
+        errorLine1="    &lt;string name=&quot;pref_activity_recognize_biking&quot;>recognize biking&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="2336"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.pref_activity_recognize_walking` appears to be unused"
+        errorLine1="    &lt;string name=&quot;pref_activity_recognize_walking&quot;>recognize walking&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="2337"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.pref_activity_recognize_rowing` appears to be unused"
+        errorLine1="    &lt;string name=&quot;pref_activity_recognize_rowing&quot;>recognize rowing&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="2338"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.pref_activity_recognition_mode_none` appears to be unused"
+        errorLine1="    &lt;string name=&quot;pref_activity_recognition_mode_none&quot;>none&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="2339"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.pref_activity_recognition_mode_ask` appears to be unused"
+        errorLine1="    &lt;string name=&quot;pref_activity_recognition_mode_ask&quot;>ask&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="2340"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.pref_activity_recognition_mode_auto` appears to be unused"
+        errorLine1="    &lt;string name=&quot;pref_activity_recognition_mode_auto&quot;>auto&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="2341"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.huawei_reparse_workout_data` appears to be unused"
+        errorLine1="    &lt;string name=&quot;huawei_reparse_workout_data&quot;>&quot;Reparse workout data&quot;&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="2374"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.huawei_reparse_workout_data_description` appears to be unused"
+        errorLine1="    &lt;string name=&quot;huawei_reparse_workout_data_description&quot;>&quot;This will only do something after certain updates&quot;&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="2375"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.new_discover_activity_description` appears to be unused"
+        errorLine1="    &lt;string name=&quot;new_discover_activity_description&quot;>MTU 丕賱丨丕賱賷 丕賱亘丕賱睾 %1$賯賲 亘鬲賲賰賷賳 賳卮丕胤 丕賱丕賰鬲卮丕賮 丕賱噩丿賷丿貙 賵丕賱匕賷 賲賳 丕賱賲賮鬲乇囟 兀賳 賷丨賱 賲卮賰賱丕鬲 丕賰鬲卮丕賮 丕賱噩賴丕夭. 賯賲 亘鬲毓胤賷賱 賴匕丕 廿匕丕 賵丕噩賴鬲 兀賷 賲卮丕賰賱 兀孬賳丕亍 丕賱亘丨孬 毓賳 噩賴丕夭賰 兀賵 丕賱丕賯鬲乇丕賳 亘賴. 賲爻鬲賵賶 丕賱廿乇爻丕賱 賲賳禺賮囟 噩丿賸丕貙 賷乇噩賶 鬲賲賰賷賳 MTU 丕賱毓丕賱賷 賮賷 廿毓丿丕丿丕鬲 丕賱噩賴丕夭 賵賮氐賱/廿毓丕丿丞 鬲賵氐賷賱 丕賱噩賴丕夭.&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values-ar/strings.xml"
+            line="2389"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.new_discover_activity_title` appears to be unused"
+        errorLine1="    &lt;string name=&quot;new_discover_activity_title&quot;>鬲賲賰賷賳 兀賳卮胤丞 丕賱丕賰鬲卮丕賮/胤胤賵噩丿賷丿丞&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values-ar/strings.xml"
+            line="2390"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.prefs_wena3_home_icon_name_wena_pay` appears to be unused"
+        errorLine1="    &lt;string name=&quot;prefs_wena3_home_icon_name_wena_pay&quot;>Wena Pay&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="2554"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.prefs_wena3_home_icon_name_qrio` appears to be unused"
+        errorLine1="    &lt;string name=&quot;prefs_wena3_home_icon_name_qrio&quot;>Qrio&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="2555"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.prefs_wena3_home_icon_name_riiiver` appears to be unused"
+        errorLine1="    &lt;string name=&quot;prefs_wena3_home_icon_name_riiiver&quot;>Riiiver&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="2561"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.prefs_wena3_button_action_name_qrio_unlock` appears to be unused"
+        errorLine1="    &lt;string name=&quot;prefs_wena3_button_action_name_qrio_unlock&quot;>Qrio Unlock&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="2574"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.prefs_wena3_button_action_name_qrio_lock` appears to be unused"
+        errorLine1="    &lt;string name=&quot;prefs_wena3_button_action_name_qrio_lock&quot;>Qrio Lock&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="2575"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.serial_number` appears to be unused"
+        errorLine1="    &lt;string name=&quot;serial_number&quot;>Serial Number&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="2623"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.menuitem_stats` appears to be unused"
+        errorLine1="    &lt;string name=&quot;menuitem_stats&quot;>Stats&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="2624"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.menuitem_running` appears to be unused"
+        errorLine1="    &lt;string name=&quot;menuitem_running&quot;>Running&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="2625"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.menuitem_alerts` appears to be unused"
+        errorLine1="    &lt;string name=&quot;menuitem_alerts&quot;>Alerts&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="2626"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.menuitem_focus` appears to be unused"
+        errorLine1="    &lt;string name=&quot;menuitem_focus&quot;>Focus&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="2627"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.widget_move_up` appears to be unused"
+        errorLine1="    &lt;string name=&quot;widget_move_up&quot;>Move up&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="2658"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.widget_move_down` appears to be unused"
+        errorLine1="    &lt;string name=&quot;widget_move_down&quot;>Move down&lt;/string>"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="2659"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.p_clock` appears to be unused"
+        errorLine1="    &lt;item name=&quot;p_clock&quot; type=&quot;string&quot;>clock&lt;/item>"
+        errorLine2="          ~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/values.xml"
+            line="16"
+            column="11"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.p_menuitem_countdown` appears to be unused"
+        errorLine1="    &lt;item name=&quot;p_menuitem_countdown&quot; type=&quot;string&quot;>countdown&lt;/item>"
+        errorLine2="          ~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/values.xml"
+            line="59"
+            column="11"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.p_menuitem_personal_activity_intelligence` appears to be unused"
+        errorLine1="    &lt;item name=&quot;p_menuitem_personal_activity_intelligence&quot; type=&quot;string&quot;>personal_activity_intelligence&lt;/item>"
+        errorLine2="          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/values.xml"
+            line="60"
+            column="11"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.p_menuitem_workout_history` appears to be unused"
+        errorLine1="    &lt;item name=&quot;p_menuitem_workout_history&quot; type=&quot;string&quot;>workout_history&lt;/item>"
+        errorLine2="          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/values.xml"
+            line="61"
+            column="11"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.p_menuitem_female_health` appears to be unused"
+        errorLine1="    &lt;item name=&quot;p_menuitem_female_health&quot; type=&quot;string&quot;>female_health&lt;/item>"
+        errorLine2="          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/values.xml"
+            line="62"
+            column="11"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.p_menuitem_workout_status` appears to be unused"
+        errorLine1="    &lt;item name=&quot;p_menuitem_workout_status&quot; type=&quot;string&quot;>workout_status&lt;/item>"
+        errorLine2="          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/values.xml"
+            line="63"
+            column="11"/>
+    </issue>
+
+    <issue
+        id="UnusedResources"
+        message="The resource `R.string.p_sunset` appears to be unused"
+        errorLine1="    &lt;item name=&quot;p_sunset&quot; type=&quot;string&quot;>sunset&lt;/item>"
+        errorLine2="          ~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/values.xml"
+            line="74"
+            column="11"/>
+    </issue>
+
+    <issue
+        id="UselessLeaf"
+        message="This `LinearLayout` view is unnecessary (no children, no `background`, no `id`, no `style`)"
+        errorLine1="        &lt;LinearLayout"
+        errorLine2="         ~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_list.xml"
+            line="13"
+            column="10"/>
+    </issue>
+
+    <issue
+        id="UselessParent"
+        message="This `ScrollView` layout or its `RelativeLayout` parent is possibly unnecessary"
+        errorLine1="    &lt;ScrollView"
+        errorLine2="     ~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_alarm_details.xml"
+            line="8"
+            column="6"/>
+    </issue>
+
+    <issue
+        id="UselessParent"
+        message="This `LinearLayout` layout or its `RelativeLayout` parent is unnecessary"
+        errorLine1="    &lt;LinearLayout"
+        errorLine2="     ~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_binary_sensor_data.xml"
+            line="8"
+            column="6"/>
+    </issue>
+
+    <issue
+        id="UselessParent"
+        message="This `ScrollView` layout or its `RelativeLayout` parent is possibly unnecessary"
+        errorLine1="    &lt;ScrollView"
+        errorLine2="     ~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_data_management.xml"
+            line="8"
+            column="6"/>
+    </issue>
+
+    <issue
+        id="UselessParent"
+        message="This `ScrollView` layout or its `RelativeLayout` parent is possibly unnecessary"
+        errorLine1="    &lt;ScrollView"
+        errorLine2="     ~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_debug.xml"
+            line="8"
+            column="6"/>
+    </issue>
+
+    <issue
+        id="UselessParent"
+        message="This `LinearLayout` layout or its `LinearLayout` parent is unnecessary"
+        errorLine1="    &lt;LinearLayout"
+        errorLine2="     ~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_discovery.xml"
+            line="11"
+            column="6"/>
+    </issue>
+
+    <issue
+        id="UselessParent"
+        message="This `RelativeLayout` layout or its `RelativeLayout` parent is possibly unnecessary"
+        errorLine1="        &lt;RelativeLayout"
+        errorLine2="         ~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_hybridhr_watchface_designer.xml"
+            line="83"
+            column="10"/>
+    </issue>
+
+    <issue
+        id="UselessParent"
+        message="This `LinearLayout` layout or its `RelativeLayout` parent is unnecessary"
+        errorLine1="    &lt;LinearLayout"
+        errorLine2="     ~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_list_dashboard_item.xml"
+            line="10"
+            column="6"/>
+    </issue>
+
+    <issue
+        id="UselessParent"
+        message="This `LinearLayout` layout or its `RelativeLayout` parent is possibly unnecessary; transfer the `background` attribute to the other view"
+        errorLine1="    &lt;LinearLayout"
+        errorLine2="     ~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_list_item.xml"
+            line="11"
+            column="6"/>
+    </issue>
+
+    <issue
+        id="UselessParent"
+        message="This `LinearLayout` layout or its `LinearLayout` parent is unnecessary; transfer the `background` attribute to the other view"
+        errorLine1="        &lt;LinearLayout"
+        errorLine2="         ~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_list_item.xml"
+            line="22"
+            column="10"/>
+    </issue>
+
+    <issue
+        id="UselessParent"
+        message="This `ScrollView` layout or its `RelativeLayout` parent is possibly unnecessary"
+        errorLine1="    &lt;ScrollView"
+        errorLine2="     ~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_open_fw_app_installer.xml"
+            line="8"
+            column="6"/>
+    </issue>
+
+    <issue
+        id="UselessParent"
+        message="This `LinearLayout` layout or its `RelativeLayout` parent is unnecessary"
+        errorLine1="    &lt;LinearLayout"
+        errorLine2="     ~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_summary_dashboard_item.xml"
+            line="11"
+            column="6"/>
+    </issue>
+
+    <issue
+        id="UselessParent"
+        message="This `LinearLayout` layout or its `LinearLayout` parent is possibly unnecessary"
+        errorLine1="            &lt;LinearLayout"
+        errorLine2="             ~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_summary_dashboard_item.xml"
+            line="26"
+            column="14"/>
+    </issue>
+
+    <issue
+        id="UselessParent"
+        message="This `LinearLayout` layout or its `LinearLayout` parent is unnecessary"
+        errorLine1="                &lt;LinearLayout"
+        errorLine2="                 ~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/device_itemv2.xml"
+            line="500"
+            column="18"/>
+    </issue>
+
+    <issue
+        id="UselessParent"
+        message="This `LinearLayout` layout or its `RelativeLayout` parent is unnecessary"
+        errorLine1="    &lt;LinearLayout"
+        errorLine2="     ~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/fragment_gps.xml"
+            line="7"
+            column="6"/>
+    </issue>
+
+    <issue
+        id="UselessParent"
+        message="This `LinearLayout` layout or its `RelativeLayout` parent is unnecessary"
+        errorLine1="    &lt;LinearLayout"
+        errorLine2="     ~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout-land/fragment_stresschart.xml"
+            line="8"
+            column="6"/>
+    </issue>
+
+    <issue
+        id="UselessParent"
+        message="This `LinearLayout` layout or its `RelativeLayout` parent is unnecessary"
+        errorLine1="    &lt;LinearLayout"
+        errorLine2="     ~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/fragment_stresschart.xml"
+            line="8"
+            column="6"/>
+    </issue>
+
+    <issue
+        id="UselessParent"
+        message="This `LinearLayout` layout or its `LinearLayout` parent is unnecessary"
+        errorLine1="        &lt;LinearLayout"
+        errorLine2="         ~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout-land/fragment_stresschart.xml"
+            line="13"
+            column="10"/>
+    </issue>
+
+    <issue
+        id="UselessParent"
+        message="This `LinearLayout` layout or its `LinearLayout` parent is unnecessary"
+        errorLine1="        &lt;LinearLayout"
+        errorLine2="         ~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/heart_rate_dialog.xml"
+            line="59"
+            column="10"/>
+    </issue>
+
+    <issue
+        id="RedundantNamespace"
+        message="This namespace declaration is redundant"
+        errorLine1="        xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;"
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_hybridhr_watchface_designer.xml"
+            line="144"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="RedundantNamespace"
+        message="This namespace declaration is redundant"
+        errorLine1="        xmlns:app=&quot;http://schemas.android.com/apk/res-auto&quot;"
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_hybridhr_watchface_designer.xml"
+            line="145"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="RedundantNamespace"
+        message="This namespace declaration is redundant"
+        errorLine1="    &lt;PreferenceCategory xmlns:app=&quot;http://schemas.android.com/apk/res-auto&quot;"
+        errorLine2="                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/xml/notifications_preferences.xml"
+            line="3"
+            column="25"/>
+    </issue>
+
+    <issue
+        id="TypographyDashes"
+        message="Replace &quot;-&quot; with an &quot;en dash&quot; character (鈥�, &amp;#8211;) ?"
+        errorLine1="    &lt;string name=&quot;pref_summary_pebble_mtu_limit&quot;>N岷縰 Pebble 2/Pebble LE c峄 b岷 kh么ng ho岷 膽峄檔g nh瓢 mong 膽峄, h茫y th峄� c脿i 膽岷穞 n脿y 膽峄� gi峄沬 h岷 MTU (ph岷 vi h峄 l峄� 20-512)&lt;/string>"
+        errorLine2="                                                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values-vi/strings.xml"
+            line="350"
+            column="50"/>
+    </issue>
+
+    <issue
+        id="TypographyDashes"
+        message="Replace &quot;-&quot; with an &quot;en dash&quot; character (鈥�, &amp;#8211;) ?"
+        errorLine1="    &lt;string name=&quot;pref_summary_pebble_mtu_limit&quot;>携泻褖芯 胁邪褕 Pebble 2/LE 薪械 锌褉邪褑褞褦, 褋锌褉芯斜褍泄褌械 褑械 薪邪谢邪褕褌褍胁邪薪薪褟, 褖芯斜 芯斜屑械卸懈褌懈 MTU (胁 写褨邪锌邪蟹芯薪褨 20-512)&lt;/string>"
+        errorLine2="                                                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values-uk/strings.xml"
+            line="476"
+            column="50"/>
+    </issue>
+
+    <issue
+        id="TypographyDashes"
+        message="Replace &quot;-&quot; with an &quot;en dash&quot; character (鈥�, &amp;#8211;) ?"
+        errorLine1="    &lt;string name=&quot;pref_summary_pebble_mtu_limit&quot;>廿匕丕 賱賲 賷毓賲賱 Pebble 2 / Pebble LE 賰賲丕 賴賵 賲鬲賵賯毓 貙 賮噩乇賾亘 賴匕丕 丕賱廿毓丿丕丿 賱鬲賯賷賷丿 MTU (丕賱賳胤丕賯 丕賱氐丕賱丨 20-512)&lt;/string>"
+        errorLine2="                                                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values-ar/strings.xml"
+            line="569"
+            column="50"/>
+    </issue>
+
+    <issue
+        id="TypographyDashes"
+        message="Replace &quot;-&quot; with an &quot;en dash&quot; character (鈥�, &amp;#8211;) ?"
+        errorLine1="    &lt;string name=&quot;pref_summary_pebble_mtu_limit&quot;>袗泻芯 谢懈 袙邪褕 袩械斜谢 2/袩械斜谢 袥袝 薪械 褉邪写懈 芯褔械泻懈胁邪薪芯, 锌芯泻褍褕邪褬褌械 芯胁褍 锌芯褋褌邪胁泻褍 褉邪写懈 芯谐褉邪薪懈褔邪胁邪褮邪 袦孝校-邪 (懈褋锌褉邪胁邪薪 芯锌褋械谐 褬械 20-512)&lt;/string>"
+        errorLine2="                                                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values-sr/strings.xml"
+            line="1686"
+            column="50"/>
+    </issue>
+
+    <issue
+        id="TypographyDashes"
+        message="Replace &quot;-&quot; with an &quot;en dash&quot; character (鈥�, &amp;#8211;) ?"
+        errorLine1="    &lt;string name=&quot;femometer_measurement_mode_normal&quot;>袧芯褉屑邪谢褜薪褘泄 (60-90 褋械泻.)&lt;/string>"
+        errorLine2="                                                     ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values-ru/strings.xml"
+            line="2499"
+            column="54"/>
+    </issue>
+
+    <issue
+        id="TypographyEllipsis"
+        message="Replace &quot;...&quot; with ellipsis character (鈥�, &amp;#8230;) ?"
+        errorLine1="    &lt;string name=&quot;pref_title_whenscreenon&quot;>...褌邪泻卸械 锌褉懈 胁泻谢褞褔械薪薪芯屑 褝泻褉邪薪械&lt;/string>"
+        errorLine2="                                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values-ru/strings.xml"
+            line="75"
+            column="44"/>
+    </issue>
+
+    <issue
+        id="TypographyEllipsis"
+        message="Replace &quot;...&quot; with ellipsis character (鈥�, &amp;#8230;) ?"
+        errorLine1="    &lt;string name=&quot;open_fw_installer_info_text&quot;>The Firmware/Watchface/App/File Installer allows you to upload/install supported files (firmware, watchfaces, applications, GPS, resources, fonts...) to the device. See some more information in the wiki: https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Firmware-Update&lt;/string>"
+        errorLine2="                                               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="181"
+            column="48"/>
+    </issue>
+
+    <issue
+        id="TypographyEllipsis"
+        message="Replace &quot;...&quot; with ellipsis character (鈥�, &amp;#8230;) ?"
+        errorLine1="    &lt;string name=&quot;open_fw_installer_info_text&quot;>賷爻賲丨 賱賰 亘乇賳丕賲噩 Firmware / Watchface / App / File Installer 亘鬲丨賲賷賱 / 鬲孬亘賷鬲 丕賱賲賱賮丕鬲 丕賱賲丿毓賵賲丞 (丕賱亘乇丕賲噩 丕賱孬丕亘鬲丞 貙 賵賵丕噩賴丕鬲 丕賱賲乇丕賯亘丞 貙 賵丕賱鬲胤亘賷賯丕鬲 貙 賵賳馗丕賲 鬲丨丿賷丿 丕賱賲賵丕賯毓 丕賱毓丕賱賲賷 (GPS) 貙 賵丕賱賲賵丕乇丿 貙 賵丕賱禺胤賵胤 ...) 毓賱賶 丕賱噩賴丕夭. 丕胤賱毓 毓賱賶 賲夭賷丿 賲賳 丕賱賲毓賱賵賲丕鬲 賮賷 丕賱賵賷賰賷: https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Firmware-Update&lt;/string>"
+        errorLine2="                                               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values-ar/strings.xml"
+            line="442"
+            column="48"/>
+    </issue>
+
+    <issue
+        id="TypographyEllipsis"
+        message="Replace &quot;...&quot; with ellipsis character (鈥�, &amp;#8230;) ?"
+        errorLine1="    &lt;string name=&quot;pref_summary_banglejs_webview_url&quot;>廿匕丕 賰賳鬲 鬲乇賷丿 兀丿丕丞 鬲丨賲賷賱 鬲胤亘賷賯丕鬲 賲禺氐氐丞 貙 賮囟毓 毓賳賵丕賳 URL https://.../android.html 賴賳丕. 賵廿賱丕 丕鬲乇賰賴 賮丕乇睾丕 賱賭 https://banglejs.com/apps&lt;/string>"
+        errorLine2="                                                     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values-ar/strings.xml"
+            line="456"
+            column="54"/>
+    </issue>
+
+    <issue
+        id="TypographyEllipsis"
+        message="Replace &quot;...&quot; with ellipsis character (鈥�, &amp;#8230;) ?"
+        errorLine1="    &lt;string name=&quot;about_additional_contributions&quot;>卮賰乇丕賸 噩夭賷賱丕賸 賱噩賲賷毓 丕賱賲爻丕賴賲賷賳 睾賷乇 丕賱賲丿乇噩賷賳 賮賷 丕賱賯丕卅賲丞 賱賱賲爻丕賴賲丞 賮賷 丕賱鬲毓賱賷賲丕鬲 丕賱亘乇賲噩賷丞 賵丕賱鬲乇噩賲丕鬲 賵丕賱丿毓賲 賵丕賱兀賮賰丕乇 賵丕賱鬲丨賮賷夭 賵鬲賯丕乇賷乇 丕賱兀禺胤丕亍 賵丕賱賲丕賱 ... 鉁�&lt;/string>"
+        errorLine2="                                                  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values-ar/strings.xml"
+            line="914"
+            column="51"/>
+    </issue>
+
+    <issue
+        id="TypographyEllipsis"
+        message="Replace &quot;...&quot; with ellipsis character (鈥�, &amp;#8230;) ?"
+        errorLine1="    &lt;string name=&quot;open_fw_installer_info_text&quot;>Pemasang Firmware/Muka Jam/Aplikasi/Berkas memungkinkan Anda untuk mengunggah/memasang berkas yang didukung (perangkat tegar, muka jam, GPS, sumber daya, fon...) ke perangkat. Lihat beberapa informasi lebih lanjut di wiki: https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Firmware-Update&lt;/string>"
+        errorLine2="                                               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values-id/strings.xml"
+            line="1118"
+            column="48"/>
+    </issue>
+
+    <issue
+        id="TypographyEllipsis"
+        message="Replace &quot;...&quot; with ellipsis character (鈥�, &amp;#8230;) ?"
+        errorLine1="    &lt;string name=&quot;open_fw_installer_info_text&quot;>袩褉芯谐褉邪屑屑邪 褍褋褌邪薪芯胁泻懈 袩褉芯褕懈胁泻懈/效邪褋芯胁芯谐芯 懈薪褌械褉褎械泄褋邪/袩褉懈谢芯卸械薪懈褟/肖邪泄谢邪 锌芯蟹胁芯谢褟械褌 蟹邪谐褉褍卸邪褌褜/褍褋褌邪薪邪胁谢懈胁邪褌褜 锌芯写写械褉卸懈胁邪械屑褘械 褎邪泄谢褘 (锌褉芯褕懈胁泻褍, 褔邪褋芯胁褘械 懈薪褌械褉褎械泄褋褘, 锌褉懈谢芯卸械薪懈褟, GPS, 褉械褋褍褉褋褘, 褕褉懈褎褌褘...) 薪邪 褍褋褌褉芯泄褋褌胁芯. 袛芯锌芯谢薪懈褌械谢褜薪褍褞 懈薪褎芯褉屑邪褑懈褞 屑芯卸薪芯 薪邪泄褌懈 胁 胁懈泻懈: https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Firmware-Update&lt;/string>"
+        errorLine2="                                               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values-ru/strings.xml"
+            line="1494"
+            column="48"/>
+    </issue>
+
+    <issue
+        id="TypographyEllipsis"
+        message="Replace &quot;...&quot; with ellipsis character (鈥�, &amp;#8230;) ?"
+        errorLine1="    &lt;string name=&quot;pref_summary_banglejs_webview_url&quot;>Se vuoi usare un app loader personalizzato, inserisci l\&apos;URL di https://.../android.html. Altrimenti lascia vuoto per https://banglejs.com/apps&lt;/string>"
+        errorLine2="                                                     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values-it/strings.xml"
+            line="1517"
+            column="54"/>
+    </issue>
+
+    <issue
+        id="TypographyEllipsis"
+        message="Replace &quot;...&quot; with ellipsis character (鈥�, &amp;#8230;) ?"
+        errorLine1="    &lt;string name=&quot;open_fw_installer_info_text&quot;>L\&apos;installer di Firmware/Quadrante/App/File consente di caricare/installare i file supportati (firmware, quadranti, applicazioni, GPS, risorse, font...) sul dispositivo. Per ulteriori informazioni, consultare la wiki: https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Firmware-Update&lt;/string>"
+        errorLine2="                                               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values-it/strings.xml"
+            line="1578"
+            column="48"/>
+    </issue>
+
+    <issue
+        id="TypographyEllipsis"
+        message="Replace &quot;...&quot; with ellipsis character (鈥�, &amp;#8230;) ?"
+        errorLine1="    &lt;string name=&quot;open_fw_installer_info_text&quot;>Instalator oprogramowania uk艂adowego/tarcz/aplikacji/plik贸w pozwala na wgranie/instalacj臋 obs艂ugiwanych plik贸w (oprogramowanie uk艂adowe, tarcze, aplikacje, GPS, zasoby, czcionki...) do urz膮dzenia. Zobacz wi臋cej informacji w wiki: https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Firmware-Update&lt;/string>"
+        errorLine2="                                               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values-pl/strings.xml"
+            line="1688"
+            column="48"/>
+    </issue>
+
+    <issue
+        id="TypographyEllipsis"
+        message="Replace &quot;...&quot; with ellipsis character (鈥�, &amp;#8230;) ?"
+        errorLine1="    &lt;string name=&quot;open_fw_installer_info_text&quot;>El Instalador de Firmware/Watchface/App/Archivos le permite cargar/instalar archivos compatibles (firmware, watchfaces, aplicaciones, GPS, recursos, fuentes...) en el dispositivo. M谩s informaci贸n en la wiki: https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Firmware-Update&lt;/string>"
+        errorLine2="                                               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values-es/strings.xml"
+            line="1717"
+            column="48"/>
+    </issue>
+
+    <issue
+        id="TypographyEllipsis"
+        message="Replace &quot;...&quot; with ellipsis character (鈥�, &amp;#8230;) ?"
+        errorLine1="    &lt;string name=&quot;pref_summary_banglejs_webview_url&quot;>Je艣li chcesz mie膰 niestandardowy program 艂aduj膮cy aplikacje, umie艣膰 sw贸j adres URL https://.../android.html tutaj. W przeciwnym razie pozostaw puste miejsce dla https://banglejs.com/apps&lt;/string>"
+        errorLine2="                                                     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values-pl/strings.xml"
+            line="1717"
+            column="54"/>
+    </issue>
+
+    <issue
+        id="TypographyEllipsis"
+        message="Replace &quot;...&quot; with ellipsis character (鈥�, &amp;#8230;) ?"
+        errorLine1="    &lt;string name=&quot;pref_summary_banglejs_webview_url&quot;>Pokud chcete vlastn铆 zavad臎膷 aplikac铆, vlo啪te adresu ve form谩tu URL https://.../android.html. V opa膷n茅m p艡铆pad臎 zanechte pr谩zdn茅 pro https://banglejs.com/apps&lt;/string>"
+        errorLine2="                                                     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values-cs/strings.xml"
+            line="1721"
+            column="54"/>
+    </issue>
+
+    <issue
+        id="TypographyEllipsis"
+        message="Replace &quot;...&quot; with ellipsis character (鈥�, &amp;#8230;) ?"
+        errorLine1="    &lt;string name=&quot;pref_summary_banglejs_webview_url&quot;>Si quiere un cargador de aplicaciones personalizado ponga aqu铆 su URL https://.../android.html. De lo contrario, deje en blanco https://banglejs.com/apps&lt;/string>"
+        errorLine2="                                                     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values-es/strings.xml"
+            line="1723"
+            column="54"/>
+    </issue>
+
+    <issue
+        id="TypographyEllipsis"
+        message="Replace &quot;...&quot; with ellipsis character (鈥�, &amp;#8230;) ?"
+        errorLine1="    &lt;string name=&quot;open_fw_installer_info_text&quot;>L\&apos;installateur de firmware/fa莽ade de montre/application/fichier vous permet d\&apos;envoyer/installer des fichiers compatibles (firmwares, fa莽ades de montre, applications, GPS, ressources, polices,...) dans l\&apos;appareil. Pour plus d\&apos;information se reporter au wiki鈥�: https鈥�://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Firmware-Update&lt;/string>"
+        errorLine2="                                               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values-fr/strings.xml"
+            line="1770"
+            column="48"/>
+    </issue>
+
+    <issue
+        id="TypographyEllipsis"
+        message="Replace &quot;...&quot; with ellipsis character (鈥�, &amp;#8230;) ?"
+        errorLine1="    &lt;string name=&quot;open_fw_installer_info_text&quot;>Met de Firmware-/Wijzerplaat-/App-/Bestand-installer kunt u ondersteunde bestanden (firmware, wijzerplaten, toepassingen, GPS, bronnen, lettertypen...) naar het apparaat uploaden/installeren. Kijk voor meer informatie op de wiki: https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Firmware-Update&lt;/string>"
+        errorLine2="                                               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values-nl/strings.xml"
+            line="1770"
+            column="48"/>
+    </issue>
+
+    <issue
+        id="TypographyEllipsis"
+        message="Replace &quot;...&quot; with ellipsis character (鈥�, &amp;#8230;) ?"
+        errorLine1="    &lt;string name=&quot;open_fw_installer_info_text&quot;>袙褋褌邪薪芯胁谢褞胁邪褔 袦褨泻褉芯锌褉芯谐褉邪屑/笑懈褎械褉斜谢邪褌褨胁/袟邪褋褌芯褋褍薪泻褨胁/肖邪泄谢褨胁 写邪褦 蟹屑芯谐褍 蟹邪胁邪薪褌邪卸懈褌懈/褍褋褌邪薪芯胁懈褌懈 锌褨写褌褉懈屑褍胁邪薪褨 褎邪泄谢懈 (屑褨泻褉芯锌褉芯谐褉邪屑懈, 褑懈褎械褉斜谢邪褌懈, 蟹邪褋褌芯褋褍薪泻懈, GPS, 褉械褋褍褉褋懈, 褕褉懈褎褌懈...) 薪邪 锌褉懈褋褌褉褨泄. 袘褨谢褜褕械 褨薪褎芯褉屑邪褑褨褩 写懈胁褨褌褜褋褟 褍 胁褨泻褨: https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Firmware-Update&lt;/string>"
+        errorLine2="                                               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values-uk/strings.xml"
+            line="1772"
+            column="48"/>
+    </issue>
+
+    <issue
+        id="TypographyEllipsis"
+        message="Replace &quot;...&quot; with ellipsis character (鈥�, &amp;#8230;) ?"
+        errorLine1="    &lt;string name=&quot;open_fw_installer_info_text&quot;>Instal谩tor Firmware/Watchface/App/Soubor暖 umo啪艌uje nahr谩t/instalovat do za艡铆zen铆 podporovan茅 soubory (firmware, watchfaces, aplikace, GPS, zdroje, fonty...). Dal拧铆 informace naleznete ve wiki: https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Firmware-Update&lt;/string>"
+        errorLine2="                                               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values-cs/strings.xml"
+            line="1773"
+            column="48"/>
+    </issue>
+
+    <issue
+        id="TypographyEllipsis"
+        message="Replace &quot;...&quot; with ellipsis character (鈥�, &amp;#8230;) ?"
+        errorLine1="    &lt;string name=&quot;open_fw_installer_info_text&quot;>Mit dem Firmware/Watchface/App/File Installer kannst du unterst眉tzte Dateien (Firmware, Zifferbl盲tter, Anwendungen, GPS, Ressourcen, Schriftarten...) auf das Ger盲t hochladen/installieren. Weitere Informationen findest du im Wiki: https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Firmware-Update&lt;/string>"
+        errorLine2="                                               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values-de/strings.xml"
+            line="1778"
+            column="48"/>
+    </issue>
+
+    <issue
+        id="TypographyEllipsis"
+        message="Replace &quot;...&quot; with ellipsis character (鈥�, &amp;#8230;) ?"
+        errorLine1="    &lt;string name=&quot;open_fw_installer_info_text&quot;>脺r眉n Yaz谋l谋m谋/Saat Aray眉z眉/Uygulama/Dosya Kurucusu, desteklenen dosyalar谋 (眉r眉n yaz谋l谋m谋, saat aray眉zleri, uygulamalar, GPS, kaynaklar, yaz谋 tipleri...) ayg谋ta y眉klemenizi/kurman谋z谋 sa臒lar. Wiki sayfas谋nda daha fazla bilgi edinin: https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Firmware-Update&lt;/string>"
+        errorLine2="                                               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/values-tr/strings.xml"
+            line="1779"
+            column="48"/>
+    </issue>
+
+    <issue
+        id="IconLocation"
+        message="Found bitmap drawable `res/drawable/widget_preview.png` in densityless folder">
+        <location
+            file="src/main/res/drawable/widget_preview.png"/>
+    </issue>
+
+    <issue
+        id="AppLinkUrlError"
+        message="Missing URL"
+        errorLine1="            &lt;intent-filter>"
+        errorLine2="            ^">
+        <location
+            file="src/main/AndroidManifest.xml"
+            line="378"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="AppLinkUrlError"
+        message="Missing URL"
+        errorLine1="            &lt;intent-filter>"
+        errorLine2="            ^">
+        <location
+            file="src/main/AndroidManifest.xml"
+            line="387"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="ButtonStyle"
+        message="Buttons in button bars should be borderless; use `style=&quot;?android:attr/buttonBarButtonStyle&quot;` (and `?android:attr/buttonBarStyle` on the parent)"
+        errorLine1="            &lt;Button"
+        errorLine2="             ~~~~~~">
+        <location
+            file="src/main/res/layout/activity_charts.xml"
+            line="54"
+            column="14"/>
+    </issue>
+
+    <issue
+        id="ButtonStyle"
+        message="Buttons in button bars should be borderless; use `style=&quot;?android:attr/buttonBarButtonStyle&quot;` (and `?android:attr/buttonBarStyle` on the parent)"
+        errorLine1="            &lt;Button"
+        errorLine2="             ~~~~~~">
+        <location
+            file="src/main/res/layout/activity_charts.xml"
+            line="63"
+            column="14"/>
+    </issue>
+
+    <issue
+        id="ButtonStyle"
+        message="Buttons in button bars should be borderless; use `style=&quot;?android:attr/buttonBarButtonStyle&quot;` (and `?android:attr/buttonBarStyle` on the parent)"
+        errorLine1="            &lt;Button"
+        errorLine2="             ~~~~~~">
+        <location
+            file="src/main/res/layout/activity_charts.xml"
+            line="72"
+            column="14"/>
+    </issue>
+
+    <issue
+        id="ButtonStyle"
+        message="Buttons in button bars should be borderless; use `style=&quot;?android:attr/buttonBarButtonStyle&quot;` (and `?android:attr/buttonBarStyle` on the parent)"
+        errorLine1="            &lt;Button"
+        errorLine2="             ~~~~~~">
+        <location
+            file="src/main/res/layout/activity_charts.xml"
+            line="81"
+            column="14"/>
+    </issue>
+
+    <issue
+        id="ButtonStyle"
+        message="Buttons in button bars should be borderless; use `style=&quot;?android:attr/buttonBarButtonStyle&quot;` (and `?android:attr/buttonBarStyle` on the parent)"
+        errorLine1="            &lt;Button"
+        errorLine2="             ~~~~~~">
+        <location
+            file="src/main/res/layout/activity_charts.xml"
+            line="89"
+            column="14"/>
+    </issue>
+
+    <issue
+        id="ButtonStyle"
+        message="Buttons in button bars should be borderless; use `style=&quot;?android:attr/buttonBarButtonStyle&quot;` (and `?android:attr/buttonBarStyle` on the parent)"
+        errorLine1="            &lt;Button"
+        errorLine2="             ~~~~~~">
+        <location
+            file="src/main/res/layout/activity_charts.xml"
+            line="98"
+            column="14"/>
+    </issue>
+
+    <issue
+        id="ButtonStyle"
+        message="Buttons in button bars should be borderless; use `style=&quot;?android:attr/buttonBarButtonStyle&quot;` (and `?android:attr/buttonBarStyle` on the parent)"
+        errorLine1="                &lt;Button"
+        errorLine2="                 ~~~~~~">
+        <location
+            file="src/main/res/layout/activity_data_management.xml"
+            line="50"
+            column="18"/>
+    </issue>
+
+    <issue
+        id="ButtonStyle"
+        message="Buttons in button bars should be borderless; use `style=&quot;?android:attr/buttonBarButtonStyle&quot;` (and `?android:attr/buttonBarStyle` on the parent)"
+        errorLine1="                &lt;Button"
+        errorLine2="                 ~~~~~~">
+        <location
+            file="src/main/res/layout/activity_data_management.xml"
+            line="58"
+            column="18"/>
+    </issue>
+
+    <issue
+        id="ButtonStyle"
+        message="Buttons in button bars should be borderless; use `style=&quot;?android:attr/buttonBarButtonStyle&quot;` (and `?android:attr/buttonBarStyle` on the parent)"
+        errorLine1="            &lt;Button"
+        errorLine2="             ~~~~~~">
+        <location
+            file="src/main/res/layout/activity_gpx_receiver.xml"
+            line="54"
+            column="14"/>
+    </issue>
+
+    <issue
+        id="ButtonStyle"
+        message="Buttons in button bars should be borderless; use `style=&quot;?android:attr/buttonBarButtonStyle&quot;` (and `?android:attr/buttonBarStyle` on the parent)"
+        errorLine1="            &lt;Button"
+        errorLine2="             ~~~~~~">
+        <location
+            file="src/main/res/layout/activity_gpx_receiver.xml"
+            line="61"
+            column="14"/>
+    </issue>
+
+    <issue
+        id="ButtonStyle"
+        message="Buttons in button bars should be borderless; use `style=&quot;?android:attr/buttonBarButtonStyle&quot;` (and `?android:attr/buttonBarStyle` on the parent)"
+        errorLine1="        &lt;Button"
+        errorLine2="         ~~~~~~">
+        <location
+            file="src/main/res/layout/activity_qhybrid_hr_settings.xml"
+            line="93"
+            column="10"/>
+    </issue>
+
+    <issue
+        id="ButtonStyle"
+        message="Buttons in button bars should be borderless; use `style=&quot;?android:attr/buttonBarButtonStyle&quot;` (and `?android:attr/buttonBarStyle` on the parent)"
+        errorLine1="        &lt;Button"
+        errorLine2="         ~~~~~~">
+        <location
+            file="src/main/res/layout/activity_qhybrid_hr_settings.xml"
+            line="100"
+            column="10"/>
+    </issue>
+
+    <issue
+        id="ButtonStyle"
+        message="Buttons in button bars should be borderless; use `style=&quot;?android:attr/buttonBarButtonStyle&quot;` (and `?android:attr/buttonBarStyle` on the parent)"
+        errorLine1="        &lt;Button"
+        errorLine2="         ~~~~~~">
+        <location
+            file="src/main/res/layout/activity_vesc_control.xml"
+            line="49"
+            column="10"/>
+    </issue>
+
+    <issue
+        id="ButtonStyle"
+        message="Buttons in button bars should be borderless; use `style=&quot;?android:attr/buttonBarButtonStyle&quot;` (and `?android:attr/buttonBarStyle` on the parent)"
+        errorLine1="        &lt;Button"
+        errorLine2="         ~~~~~~">
+        <location
+            file="src/main/res/layout/activity_vesc_control.xml"
+            line="56"
+            column="10"/>
+    </issue>
+
+    <issue
+        id="ButtonStyle"
+        message="Buttons in button bars should be borderless; use `style=&quot;?android:attr/buttonBarButtonStyle&quot;` (and `?android:attr/buttonBarStyle` on the parent)"
+        errorLine1="        &lt;Button"
+        errorLine2="         ~~~~~~">
+        <location
+            file="src/main/res/layout/dialog_frequency_picker.xml"
+            line="46"
+            column="10"/>
+    </issue>
+
+    <issue
+        id="ButtonStyle"
+        message="Buttons in button bars should be borderless; use `style=&quot;?android:attr/buttonBarButtonStyle&quot;` (and `?android:attr/buttonBarStyle` on the parent)"
+        errorLine1="        &lt;Button"
+        errorLine2="         ~~~~~~">
+        <location
+            file="src/main/res/layout/dialog_frequency_picker.xml"
+            line="54"
+            column="10"/>
+    </issue>
+
+    <issue
+        id="ButtonStyle"
+        message="Buttons in button bars should be borderless; use `style=&quot;?android:attr/buttonBarButtonStyle&quot;` (and `?android:attr/buttonBarStyle` on the parent)"
+        errorLine1="        &lt;Button"
+        errorLine2="         ~~~~~~">
+        <location
+            file="src/main/res/layout/dialog_frequency_picker.xml"
+            line="61"
+            column="10"/>
+    </issue>
+
+    <issue
+        id="ButtonStyle"
+        message="Buttons in button bars should be borderless; use `style=&quot;?android:attr/buttonBarButtonStyle&quot;` (and `?android:attr/buttonBarStyle` on the parent)"
+        errorLine1="            &lt;Button"
+        errorLine2="             ~~~~~~">
+        <location
+            file="src/main/res/layout/qhybrid_activity_widget_settings.xml"
+            line="55"
+            column="14"/>
+    </issue>
+
+    <issue
+        id="ButtonStyle"
+        message="Buttons in button bars should be borderless; use `style=&quot;?android:attr/buttonBarButtonStyle&quot;` (and `?android:attr/buttonBarStyle` on the parent)"
+        errorLine1="            &lt;Button"
+        errorLine2="             ~~~~~~">
+        <location
+            file="src/main/res/layout/qhybrid_activity_widget_settings.xml"
+            line="62"
+            column="14"/>
+    </issue>
+
+    <issue
+        id="TextFields"
+        message="This text field does not specify an `inputType`"
+        errorLine1="            &lt;EditText"
+        errorLine2="             ~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_contact_details.xml"
+            line="38"
+            column="14"/>
+    </issue>
+
+    <issue
+        id="TextFields"
+        message="This text field does not specify an `inputType`"
+        errorLine1="            &lt;EditText"
+        errorLine2="             ~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_contact_details.xml"
+            line="76"
+            column="14"/>
+    </issue>
+
+    <issue
+        id="TextFields"
+        message="This text field does not specify an `inputType`"
+        errorLine1="            &lt;EditText"
+        errorLine2="             ~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_reminder_details.xml"
+            line="155"
+            column="14"/>
+    </issue>
+
+    <issue
+        id="TextFields"
+        message="This text field does not specify an `inputType`"
+        errorLine1="            &lt;EditText"
+        errorLine2="             ~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_world_clock_details.xml"
+            line="103"
+            column="14"/>
+    </issue>
+
+    <issue
+        id="TextFields"
+        message="This text field does not specify an `inputType`"
+        errorLine1="            &lt;EditText"
+        errorLine2="             ~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_world_clock_details.xml"
+            line="141"
+            column="14"/>
+    </issue>
+
+    <issue
+        id="TextFields"
+        message="This text field does not specify an `inputType`"
+        errorLine1="            &lt;EditText"
+        errorLine2="             ~~~~~~~~">
+        <location
+            file="src/main/res/layout/qhybrid_activity_widget_settings.xml"
+            line="24"
+            column="14"/>
+    </issue>
+
+    <issue
+        id="TextFields"
+        message="This text field does not specify an `inputType`"
+        errorLine1="    &lt;EditText"
+        errorLine2="     ~~~~~~~~">
+        <location
+            file="src/main/res/layout/qhybrid_element_popup_view.xml"
+            line="14"
+            column="6"/>
+    </issue>
+
+    <issue
+        id="TextFields"
+        message="This text field does not specify an `inputType`"
+        errorLine1="    &lt;EditText"
+        errorLine2="     ~~~~~~~~">
+        <location
+            file="src/main/res/layout/qhybrid_element_popup_view.xml"
+            line="24"
+            column="6"/>
+    </issue>
+
+    <issue
+        id="AlwaysShowAction"
+        message="Prefer &quot;`ifRoom`&quot; instead of &quot;`always`&quot;"
+        errorLine1="        app:showAsAction=&quot;always&quot;"
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/menu/activity_hybridhr_watchface_designer_actionbar.xml"
+            line="8"
+            column="9"/>
+        <location
+            file="src/main/res/menu/activity_hybridhr_watchface_designer_actionbar.xml"
+            line="15"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="AlwaysShowAction"
+        message="Prefer &quot;`ifRoom`&quot; instead of &quot;`always`&quot;"
+        errorLine1="        app:showAsAction=&quot;always&quot; />"
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/menu/activity_list_menu.xml"
+            line="10"
+            column="9"/>
+        <location
+            file="src/main/res/menu/activity_list_menu.xml"
+            line="17"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="AlwaysShowAction"
+        message="Prefer &quot;`ifRoom`&quot; instead of &quot;`always`&quot;"
+        errorLine1="        app:showAsAction=&quot;always&quot;>"
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/menu/activity_take_screenshot_menu.xml"
+            line="10"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="Autofill"
+        message="Missing `autofillHints` attribute"
+        errorLine1="            &lt;EditText"
+        errorLine2="             ~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_alarm_details.xml"
+            line="46"
+            column="14"/>
+    </issue>
+
+    <issue
+        id="Autofill"
+        message="Missing `autofillHints` attribute"
+        errorLine1="            &lt;EditText"
+        errorLine2="             ~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_alarm_details.xml"
+            line="55"
+            column="14"/>
+    </issue>
+
+    <issue
+        id="Autofill"
+        message="Missing `autofillHints` attribute"
+        errorLine1="            &lt;EditText"
+        errorLine2="             ~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_contact_details.xml"
+            line="38"
+            column="14"/>
+    </issue>
+
+    <issue
+        id="Autofill"
+        message="Missing `autofillHints` attribute"
+        errorLine1="            &lt;EditText"
+        errorLine2="             ~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_contact_details.xml"
+            line="76"
+            column="14"/>
+    </issue>
+
+    <issue
+        id="Autofill"
+        message="Missing `autofillHints` attribute"
+        errorLine1="            &lt;EditText"
+        errorLine2="             ~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_debug.xml"
+            line="36"
+            column="14"/>
+    </issue>
+
+    <issue
+        id="Autofill"
+        message="Missing `autofillHints` attribute"
+        errorLine1="    &lt;EditText"
+        errorLine2="     ~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_notification_filter.xml"
+            line="54"
+            column="6"/>
+    </issue>
+
+    <issue
+        id="Autofill"
+        message="Missing `autofillHints` attribute"
+        errorLine1="    &lt;EditText"
+        errorLine2="     ~~~~~~~~">
+        <location
+            file="src/main/res/layout-land/activity_notification_filter.xml"
+            line="58"
+            column="6"/>
+    </issue>
+
+    <issue
+        id="Autofill"
+        message="Missing `autofillHints` attribute"
+        errorLine1="        &lt;EditText"
+        errorLine2="         ~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_qhybrid_settings.xml"
+            line="43"
+            column="10"/>
+    </issue>
+
+    <issue
+        id="Autofill"
+        message="Missing `autofillHints` attribute"
+        errorLine1="            &lt;EditText"
+        errorLine2="             ~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_reminder_details.xml"
+            line="155"
+            column="14"/>
+    </issue>
+
+    <issue
+        id="Autofill"
+        message="Missing `autofillHints` attribute"
+        errorLine1="        &lt;EditText"
+        errorLine2="         ~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_vesc_control.xml"
+            line="19"
+            column="10"/>
+    </issue>
+
+    <issue
+        id="Autofill"
+        message="Missing `autofillHints` attribute"
+        errorLine1="        &lt;EditText"
+        errorLine2="         ~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_vesc_control.xml"
+            line="37"
+            column="10"/>
+    </issue>
+
+    <issue
+        id="Autofill"
+        message="Missing `autofillHints` attribute"
+        errorLine1="            &lt;EditText"
+        errorLine2="             ~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_world_clock_details.xml"
+            line="103"
+            column="14"/>
+    </issue>
+
+    <issue
+        id="Autofill"
+        message="Missing `autofillHints` attribute"
+        errorLine1="            &lt;EditText"
+        errorLine2="             ~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_world_clock_details.xml"
+            line="141"
+            column="14"/>
+    </issue>
+
+    <issue
+        id="Autofill"
+        message="Missing `autofillHints` attribute"
+        errorLine1="            &lt;EditText"
+        errorLine2="             ~~~~~~~~">
+        <location
+            file="src/main/res/layout/qhybrid_activity_widget_settings.xml"
+            line="24"
+            column="14"/>
+    </issue>
+
+    <issue
+        id="Autofill"
+        message="Missing `autofillHints` attribute"
+        errorLine1="    &lt;EditText"
+        errorLine2="     ~~~~~~~~">
+        <location
+            file="src/main/res/layout/qhybrid_element_popup_view.xml"
+            line="14"
+            column="6"/>
+    </issue>
+
+    <issue
+        id="Autofill"
+        message="Missing `autofillHints` attribute"
+        errorLine1="    &lt;EditText"
+        errorLine2="     ~~~~~~~~">
+        <location
+            file="src/main/res/layout/qhybrid_element_popup_view.xml"
+            line="24"
+            column="6"/>
+    </issue>
+
+    <issue
+        id="ButtonCase"
+        message="The standard Android way to capitalize cancel is &quot;Cancel&quot; (tip: use `@android:string/cancel` instead)"
+        errorLine1="    &lt;string name=&quot;fossil_hr_new_action_cancel&quot;>cancel&lt;/string>"
+        errorLine2="                                               ~~~~~~">
+        <location
+            file="src/main/res/values/strings.xml"
+            line="1993"
+            column="48"/>
+    </issue>
+
+    <issue
+        id="ClickableViewAccessibility"
+        message="Custom view ``ImageView`` has `setOnTouchListener` called on it but does not override `performClick`"
+        errorLine1="        holder.mDragHandle.setOnTouchListener(new View.OnTouchListener() {"
+        errorLine2="        ^">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/CommuteActionsListAdapter.java"
+            line="59"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="ClickableViewAccessibility"
+        message="`onTouch` should call `View#performClick` when a click is detected"
+        errorLine1="            public boolean onTouch(View view, MotionEvent motionEvent) {"
+        errorLine2="                           ~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/CommuteActionsListAdapter.java"
+            line="61"
+            column="28"/>
+    </issue>
+
+    <issue
+        id="ClickableViewAccessibility"
+        message="`DragSortController#onTouch` should call `View#performClick` when a click is detected"
+        errorLine1="    public boolean onTouch(View v, MotionEvent ev) {"
+        errorLine2="                   ~~~~~~~">
+        <location
+            file="src/main/java/com/mobeta/android/dslv/DragSortController.java"
+            line="244"
+            column="20"/>
+    </issue>
+
+    <issue
+        id="ClickableViewAccessibility"
+        message="Custom view `DragSortListView` overrides `onTouchEvent` but not `performClick`"
+        errorLine1="    public boolean onTouchEvent(MotionEvent ev) {"
+        errorLine2="                   ~~~~~~~~~~~~">
+        <location
+            file="src/main/java/com/mobeta/android/dslv/DragSortListView.java"
+            line="1583"
+            column="20"/>
+    </issue>
+
+    <issue
+        id="ClickableViewAccessibility"
+        message="Custom view ``ImageView`` has `setOnTouchListener` called on it but does not override `performClick`"
+        errorLine1="        holder.mDragHandle.setOnTouchListener(new View.OnTouchListener() {"
+        errorLine2="        ^">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/GBDeviceAppAdapter.java"
+            line="140"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="ClickableViewAccessibility"
+        message="`onTouch` should call `View#performClick` when a click is detected"
+        errorLine1="            public boolean onTouch(View view, MotionEvent motionEvent) {"
+        errorLine2="                           ~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/GBDeviceAppAdapter.java"
+            line="142"
+            column="28"/>
+    </issue>
+
+    <issue
+        id="ClickableViewAccessibility"
+        message="Custom view ``ImageView`` has `setOnTouchListener` called on it but does not override `performClick`"
+        errorLine1="        overlay.setOnTouchListener(this);"
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/ImageEditActivity.java"
+            line="73"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="ClickableViewAccessibility"
+        message="`ImageEditActivity#onTouch` should call `View#performClick` when a click is detected"
+        errorLine1="    public boolean onTouch(View view, MotionEvent motionEvent) {"
+        errorLine2="                   ~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/ImageEditActivity.java"
+            line="202"
+            column="20"/>
+    </issue>
+
+    <issue
+        id="ClickableViewAccessibility"
+        message="Custom view `NonSwipeableViewPager` overrides `onTouchEvent` but not `performClick`"
+        errorLine1="    public boolean onTouchEvent(final MotionEvent ev) {"
+        errorLine2="                   ~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/NonSwipeableViewPager.java"
+            line="42"
+            column="20"/>
+    </issue>
+
+    <issue
+        id="ClickableViewAccessibility"
+        message="Custom view `RotaryControl` overrides `onTouchEvent` but not `performClick`"
+        errorLine1="    public boolean onTouchEvent(MotionEvent ev) {"
+        errorLine2="                   ~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/withingssteelhr/RotaryControl.java"
+            line="169"
+            column="20"/>
+    </issue>
+
+    <issue
+        id="ClickableViewAccessibility"
+        message="`SwipeEvents#onTouch` should call `View#performClick` when a click is detected"
+        errorLine1="    public boolean onTouch(View v, MotionEvent event) {"
+        errorLine2="                   ~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/util/SwipeEvents.java"
+            line="39"
+            column="20"/>
+    </issue>
+
+    <issue
+        id="ClickableViewAccessibility"
+        message="Custom view ``ImageView`` has `setOnTouchListener` called on it but does not override `performClick`"
+        errorLine1="        pickerView.setOnTouchListener(new View.OnTouchListener() {"
+        errorLine2="        ^">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/TimePicker.java"
+            line="161"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="ClickableViewAccessibility"
+        message="`onTouch` should call `View#performClick` when a click is detected"
+        errorLine1="            public boolean onTouch(View view, MotionEvent motionEvent) {"
+        errorLine2="                           ~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/TimePicker.java"
+            line="163"
+            column="28"/>
+    </issue>
+
+    <issue
+        id="ClickableViewAccessibility"
+        message="`onTouch` should call `View#performClick` when a click is detected"
+        errorLine1="            public boolean onTouch(View v, MotionEvent event) {"
+        errorLine2="                           ~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/vesc/VescControlActivity.java"
+            line="197"
+            column="28"/>
+    </issue>
+
+    <issue
+        id="ContentDescription"
+        message="Missing `contentDescription` attribute on image"
+        errorLine1="    &lt;com.google.android.material.floatingactionbutton.FloatingActionButton"
+        errorLine2="     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_commute_actions.xml"
+            line="27"
+            column="6"/>
+    </issue>
+
+    <issue
+        id="ContentDescription"
+        message="Missing `contentDescription` attribute on image"
+        errorLine1="    &lt;com.google.android.material.floatingactionbutton.FloatingActionButton"
+        errorLine2="     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_configure_contacts.xml"
+            line="18"
+            column="6"/>
+    </issue>
+
+    <issue
+        id="ContentDescription"
+        message="Missing `contentDescription` attribute on image"
+        errorLine1="    &lt;com.google.android.material.floatingactionbutton.FloatingActionButton"
+        errorLine2="     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_configure_reminders.xml"
+            line="18"
+            column="6"/>
+    </issue>
+
+    <issue
+        id="ContentDescription"
+        message="Missing `contentDescription` attribute on image"
+        errorLine1="    &lt;com.google.android.material.floatingactionbutton.FloatingActionButton"
+        errorLine2="     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_configure_world_clocks.xml"
+            line="18"
+            column="6"/>
+    </issue>
+
+    <issue
+        id="ContentDescription"
+        message="Missing `contentDescription` attribute on image"
+        errorLine1="    &lt;com.google.android.material.floatingactionbutton.FloatingActionButton"
+        errorLine2="     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_contact_details.xml"
+            line="87"
+            column="6"/>
+    </issue>
+
+    <issue
+        id="ContentDescription"
+        message="Missing `contentDescription` attribute on image"
+        errorLine1="    &lt;com.google.android.material.floatingactionbutton.FloatingActionButton"
+        errorLine2="     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_controlcenterv2_app_bar_main.xml"
+            line="25"
+            column="6"/>
+    </issue>
+
+    <issue
+        id="ContentDescription"
+        message="Missing `contentDescription` attribute on image"
+        errorLine1="    &lt;ImageView"
+        errorLine2="     ~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_controlcenterv2_content_main.xml"
+            line="13"
+            column="6"/>
+    </issue>
+
+    <issue
+        id="ContentDescription"
+        message="Missing `contentDescription` attribute on image"
+        errorLine1="    &lt;com.google.android.material.floatingactionbutton.FloatingActionButton"
+        errorLine2="     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_fragmentappmanager.xml"
+            line="29"
+            column="6"/>
+    </issue>
+
+    <issue
+        id="ContentDescription"
+        message="Missing `contentDescription` attribute on image"
+        errorLine1="    &lt;com.google.android.material.floatingactionbutton.FloatingActionButton"
+        errorLine2="     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_fragmentappmanager.xml"
+            line="39"
+            column="6"/>
+    </issue>
+
+    <issue
+        id="ContentDescription"
+        message="Missing `contentDescription` attribute on image"
+        errorLine1="        &lt;ImageView"
+        errorLine2="         ~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_hybridhr_watchface_designer.xml"
+            line="50"
+            column="10"/>
+    </issue>
+
+    <issue
+        id="ContentDescription"
+        message="Missing `contentDescription` attribute on image"
+        errorLine1="        &lt;ImageView"
+        errorLine2="         ~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_hybridhr_watchface_designer.xml"
+            line="56"
+            column="10"/>
+    </issue>
+
+    <issue
+        id="ContentDescription"
+        message="Missing `contentDescription` attribute on image"
+        errorLine1="        &lt;ImageView"
+        errorLine2="         ~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_hybridhr_watchface_designer.xml"
+            line="63"
+            column="10"/>
+    </issue>
+
+    <issue
+        id="ContentDescription"
+        message="Missing `contentDescription` attribute on image"
+        errorLine1="        &lt;ImageView"
+        errorLine2="         ~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_hybridhr_watchface_designer.xml"
+            line="71"
+            column="10"/>
+    </issue>
+
+    <issue
+        id="ContentDescription"
+        message="Missing `contentDescription` attribute on image"
+        errorLine1="            &lt;ImageView"
+        errorLine2="             ~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_hybridhr_watchface_designer.xml"
+            line="90"
+            column="14"/>
+    </issue>
+
+    <issue
+        id="ContentDescription"
+        message="Missing `contentDescription` attribute on image"
+        errorLine1="        &lt;ImageView"
+        errorLine2="         ~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_hybridhr_watchface_designer.xml"
+            line="105"
+            column="10"/>
+    </issue>
+
+    <issue
+        id="ContentDescription"
+        message="Missing `contentDescription` attribute on image"
+        errorLine1="    &lt;com.google.android.material.floatingactionbutton.FloatingActionButton"
+        errorLine2="     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_list.xml"
+            line="34"
+            column="6"/>
+    </issue>
+
+    <issue
+        id="ContentDescription"
+        message="Missing `contentDescription` attribute on image"
+        errorLine1="        &lt;ImageView"
+        errorLine2="         ~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_list_total_dashboard.xml"
+            line="23"
+            column="10"/>
+    </issue>
+
+    <issue
+        id="ContentDescription"
+        message="Missing `contentDescription` attribute on image"
+        errorLine1="            &lt;ImageView"
+        errorLine2="             ~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_list_total_dashboard.xml"
+            line="71"
+            column="14"/>
+    </issue>
+
+    <issue
+        id="ContentDescription"
+        message="Missing `contentDescription` attribute on image"
+        errorLine1="        &lt;ImageView"
+        errorLine2="         ~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_qhybrid_image_edit.xml"
+            line="15"
+            column="10"/>
+    </issue>
+
+    <issue
+        id="ContentDescription"
+        message="Missing `contentDescription` attribute on image"
+        errorLine1="        &lt;ImageView"
+        errorLine2="         ~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_qhybrid_image_edit.xml"
+            line="21"
+            column="10"/>
+    </issue>
+
+    <issue
+        id="ContentDescription"
+        message="Missing `contentDescription` attribute on image"
+        errorLine1="    &lt;com.google.android.material.floatingactionbutton.FloatingActionButton"
+        errorLine2="     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_reminder_details.xml"
+            line="166"
+            column="6"/>
+    </issue>
+
+    <issue
+        id="ContentDescription"
+        message="Missing `contentDescription` attribute on image"
+        errorLine1="            &lt;ImageButton"
+        errorLine2="             ~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_supercars_control.xml"
+            line="70"
+            column="14"/>
+    </issue>
+
+    <issue
+        id="ContentDescription"
+        message="Missing `contentDescription` attribute on image"
+        errorLine1="            &lt;ImageButton"
+        errorLine2="             ~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_supercars_control.xml"
+            line="86"
+            column="14"/>
+    </issue>
+
+    <issue
+        id="ContentDescription"
+        message="Missing `contentDescription` attribute on image"
+        errorLine1="            &lt;ImageButton"
+        errorLine2="             ~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_supercars_control.xml"
+            line="108"
+            column="14"/>
+    </issue>
+
+    <issue
+        id="ContentDescription"
+        message="Missing `contentDescription` attribute on image"
+        errorLine1="            &lt;ImageButton"
+        errorLine2="             ~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_supercars_control.xml"
+            line="124"
+            column="14"/>
+    </issue>
+
+    <issue
+        id="ContentDescription"
+        message="Missing `contentDescription` attribute on image"
+        errorLine1="    &lt;com.google.android.material.floatingactionbutton.FloatingActionButton"
+        errorLine2="     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_widget_screen_details.xml"
+            line="245"
+            column="6"/>
+    </issue>
+
+    <issue
+        id="ContentDescription"
+        message="Missing `contentDescription` attribute on image"
+        errorLine1="    &lt;com.google.android.material.floatingactionbutton.FloatingActionButton"
+        errorLine2="     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_widget_screens_list.xml"
+            line="18"
+            column="6"/>
+    </issue>
+
+    <issue
+        id="ContentDescription"
+        message="Missing `contentDescription` attribute on image"
+        errorLine1="    &lt;com.google.android.material.floatingactionbutton.FloatingActionButton"
+        errorLine2="     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_world_clock_details.xml"
+            line="152"
+            column="6"/>
+    </issue>
+
+    <issue
+        id="ContentDescription"
+        message="Missing `contentDescription` attribute on image"
+        errorLine1="                    &lt;ImageView"
+        errorLine2="                     ~~~~~~~~~">
+        <location
+            file="src/main/res/layout/device_itemv2.xml"
+            line="122"
+            column="22"/>
+    </issue>
+
+    <issue
+        id="ContentDescription"
+        message="Missing `contentDescription` attribute on image"
+        errorLine1="                    &lt;ImageView"
+        errorLine2="                     ~~~~~~~~~">
+        <location
+            file="src/main/res/layout/device_itemv2.xml"
+            line="152"
+            column="22"/>
+    </issue>
+
+    <issue
+        id="ContentDescription"
+        message="Missing `contentDescription` attribute on image"
+        errorLine1="                    &lt;ImageView"
+        errorLine2="                     ~~~~~~~~~">
+        <location
+            file="src/main/res/layout/device_itemv2.xml"
+            line="182"
+            column="22"/>
+    </issue>
+
+    <issue
+        id="ContentDescription"
+        message="Missing `contentDescription` attribute on image"
+        errorLine1="                    &lt;ImageView"
+        errorLine2="                     ~~~~~~~~~">
+        <location
+            file="src/main/res/layout/device_itemv2.xml"
+            line="236"
+            column="22"/>
+    </issue>
+
+    <issue
+        id="ContentDescription"
+        message="Missing `contentDescription` attribute on image"
+        errorLine1="                    &lt;ImageView"
+        errorLine2="                     ~~~~~~~~~">
+        <location
+            file="src/main/res/layout/device_itemv2.xml"
+            line="453"
+            column="22"/>
+    </issue>
+
+    <issue
+        id="ContentDescription"
+        message="Missing `contentDescription` attribute on image"
+        errorLine1="    &lt;ImageView"
+        errorLine2="     ~~~~~~~~~">
+        <location
+            file="src/main/res/layout/fossil_hr_row_commute_action.xml"
+            line="9"
+            column="6"/>
+    </issue>
+
+    <issue
+        id="ContentDescription"
+        message="Missing `contentDescription` attribute on image"
+        errorLine1="    &lt;ImageView"
+        errorLine2="     ~~~~~~~~~">
+        <location
+            file="src/main/res/layout/fossil_hr_row_installed_app.xml"
+            line="15"
+            column="6"/>
+    </issue>
+
+    <issue
+        id="ContentDescription"
+        message="Missing `contentDescription` attribute on image"
+        errorLine1="        &lt;ImageView"
+        errorLine2="         ~~~~~~~~~">
+        <location
+            file="src/main/res/layout/fragment_gps.xml"
+            line="21"
+            column="10"/>
+    </issue>
+
+    <issue
+        id="ContentDescription"
+        message="Missing `contentDescription` attribute on image"
+        errorLine1="    &lt;com.google.android.material.floatingactionbutton.FloatingActionButton"
+        errorLine2="     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/fragment_steps_list.xml"
+            line="29"
+            column="6"/>
+    </issue>
+
+    <issue
+        id="ContentDescription"
+        message="Missing `contentDescription` attribute on image"
+        errorLine1="    &lt;ImageView"
+        errorLine2="     ~~~~~~~~~">
+        <location
+            file="src/main/res/layout/fragment_weeksteps_chart.xml"
+            line="32"
+            column="6"/>
+    </issue>
+
+    <issue
+        id="ContentDescription"
+        message="Missing `contentDescription` attribute on image"
+        errorLine1="    &lt;ImageView"
+        errorLine2="     ~~~~~~~~~">
+        <location
+            file="src/main/res/layout-land/fragment_weeksteps_chart.xml"
+            line="46"
+            column="6"/>
+    </issue>
+
+    <issue
+        id="ContentDescription"
+        message="Missing `contentDescription` attribute on image"
+        errorLine1="    &lt;ImageView"
+        errorLine2="     ~~~~~~~~~">
+        <location
+            file="src/main/res/layout/item_app_blacklist.xml"
+            line="34"
+            column="6"/>
+    </issue>
+
+    <issue
+        id="ContentDescription"
+        message="Missing `contentDescription` attribute on image"
+        errorLine1="    &lt;ImageView"
+        errorLine2="     ~~~~~~~~~">
+        <location
+            file="src/main/res/layout/item_app_blacklist.xml"
+            line="73"
+            column="6"/>
+    </issue>
+
+    <issue
+        id="ContentDescription"
+        message="Missing `contentDescription` attribute on image"
+        errorLine1="    &lt;ImageView"
+        errorLine2="     ~~~~~~~~~">
+        <location
+            file="src/main/res/layout/item_app_specific_notification_app_list.xml"
+            line="10"
+            column="6"/>
+    </issue>
+
+    <issue
+        id="ContentDescription"
+        message="Missing `contentDescription` attribute on image"
+        errorLine1="    &lt;ImageView"
+        errorLine2="     ~~~~~~~~~">
+        <location
+            file="src/main/res/layout/item_app_specific_notification_app_list.xml"
+            line="49"
+            column="6"/>
+    </issue>
+
+    <issue
+        id="ContentDescription"
+        message="Missing `contentDescription` attribute on image"
+        errorLine1="        &lt;ImageView"
+        errorLine2="         ~~~~~~~~~">
+        <location
+            file="src/main/res/layout/item_appmanager_watchapp.xml"
+            line="20"
+            column="10"/>
+    </issue>
+
+    <issue
+        id="ContentDescription"
+        message="Missing `contentDescription` attribute on image"
+        errorLine1="    &lt;ImageView"
+        errorLine2="     ~~~~~~~~~">
+        <location
+            file="src/main/res/layout/nav_header_main.xml"
+            line="15"
+            column="6"/>
+    </issue>
+
+    <issue
+        id="ContentDescription"
+        message="Missing `contentDescription` attribute on image"
+        errorLine1="    &lt;ImageView"
+        errorLine2="     ~~~~~~~~~">
+        <location
+            file="src/main/res/layout/qhybrid_app_view.xml"
+            line="8"
+            column="6"/>
+    </issue>
+
+    <issue
+        id="ContentDescription"
+        message="Missing `contentDescription` attribute on image"
+        errorLine1="    &lt;ImageView"
+        errorLine2="     ~~~~~~~~~">
+        <location
+            file="src/main/res/layout/qhybrid_package_settings_item.xml"
+            line="8"
+            column="6"/>
+    </issue>
+
+    <issue
+        id="ContentDescription"
+        message="Missing `contentDescription` attribute on image"
+        errorLine1="    &lt;ImageView"
+        errorLine2="     ~~~~~~~~~">
+        <location
+            file="src/main/res/layout/qhybrid_package_settings_item.xml"
+            line="22"
+            column="6"/>
+    </issue>
+
+    <issue
+        id="ContentDescription"
+        message="Missing `contentDescription` attribute on image"
+        errorLine1="    &lt;ImageView"
+        errorLine2="     ~~~~~~~~~">
+        <location
+            file="src/main/res/layout/spinner_with_image_layout.xml"
+            line="7"
+            column="6"/>
+    </issue>
+
+    <issue
+        id="ContentDescription"
+        message="Missing `contentDescription` attribute on image"
+        errorLine1="                &lt;ImageView"
+        errorLine2="                 ~~~~~~~~~">
+        <location
+            file="src/main/res/layout/steps_streaks_dashboard.xml"
+            line="55"
+            column="18"/>
+    </issue>
+
+    <issue
+        id="ContentDescription"
+        message="Missing `contentDescription` attribute on image"
+        errorLine1="        &lt;ImageButton"
+        errorLine2="         ~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/steps_streaks_dashboard.xml"
+            line="70"
+            column="10"/>
+    </issue>
+
+    <issue
+        id="LabelFor"
+        message="Missing accessibility label: provide either a view with an `android:labelFor` that references this view or provide an `android:hint`"
+        errorLine1="            &lt;EditText"
+        errorLine2="             ~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_contact_details.xml"
+            line="38"
+            column="14"/>
+    </issue>
+
+    <issue
+        id="LabelFor"
+        message="Missing accessibility label: provide either a view with an `android:labelFor` that references this view or provide an `android:hint`"
+        errorLine1="            &lt;EditText"
+        errorLine2="             ~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_contact_details.xml"
+            line="76"
+            column="14"/>
+    </issue>
+
+    <issue
+        id="LabelFor"
+        message="Missing accessibility label: provide either a view with an `android:labelFor` that references this view or provide an `android:hint`"
+        errorLine1="            &lt;EditText"
+        errorLine2="             ~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_debug.xml"
+            line="36"
+            column="14"/>
+    </issue>
+
+    <issue
+        id="LabelFor"
+        message="Missing accessibility label: provide either a view with an `android:labelFor` that references this view or provide an `android:hint`"
+        errorLine1="        &lt;EditText"
+        errorLine2="         ~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_qhybrid_settings.xml"
+            line="43"
+            column="10"/>
+    </issue>
+
+    <issue
+        id="LabelFor"
+        message="Missing accessibility label: provide either a view with an `android:labelFor` that references this view or provide an `android:hint`"
+        errorLine1="            &lt;EditText"
+        errorLine2="             ~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_reminder_details.xml"
+            line="155"
+            column="14"/>
+    </issue>
+
+    <issue
+        id="LabelFor"
+        message="Missing accessibility label: provide either a view with an `android:labelFor` that references this view or provide an `android:hint`"
+        errorLine1="            &lt;EditText"
+        errorLine2="             ~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_world_clock_details.xml"
+            line="103"
+            column="14"/>
+    </issue>
+
+    <issue
+        id="LabelFor"
+        message="Missing accessibility label: provide either a view with an `android:labelFor` that references this view or provide an `android:hint`"
+        errorLine1="            &lt;EditText"
+        errorLine2="             ~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_world_clock_details.xml"
+            line="141"
+            column="14"/>
+    </issue>
+
+    <issue
+        id="LabelFor"
+        message="Missing accessibility label: provide either a view with an `android:labelFor` that references this view or provide an `android:hint`"
+        errorLine1="            &lt;EditText"
+        errorLine2="             ~~~~~~~~">
+        <location
+            file="src/main/res/layout/qhybrid_activity_widget_settings.xml"
+            line="24"
+            column="14"/>
+    </issue>
+
+    <issue
+        id="LabelFor"
+        message="Missing accessibility label: provide either a view with an `android:labelFor` that references this view or provide an `android:hint`"
+        errorLine1="    &lt;EditText"
+        errorLine2="     ~~~~~~~~">
+        <location
+            file="src/main/res/layout/qhybrid_element_popup_view.xml"
+            line="14"
+            column="6"/>
+    </issue>
+
+    <issue
+        id="LabelFor"
+        message="Missing accessibility label: provide either a view with an `android:labelFor` that references this view or provide an `android:hint`"
+        errorLine1="    &lt;EditText"
+        errorLine2="     ~~~~~~~~">
+        <location
+            file="src/main/res/layout/qhybrid_element_popup_view.xml"
+            line="24"
+            column="6"/>
+    </issue>
+
+    <issue
+        id="SetTextI18n"
+        message="Do not concatenate text displayed with `setText`. Use resource string with placeholders."
+        errorLine1="                format.format(timeOffset / 60) + &quot;:&quot; +"
+        errorLine2="                ^">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/ConfigActivity.java"
+            line="327"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="SetTextI18n"
+        message="Do not concatenate text displayed with `setText`. Use resource string with placeholders."
+        errorLine1="                format.format(timeOffset / 60) + &quot;:&quot; +"
+        errorLine2="                ^">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/ConfigActivity.java"
+            line="337"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="SetTextI18n"
+        message="Do not concatenate text displayed with `setText`. Use resource string with placeholders."
+        errorLine1="                            buttonTextView.setText(&quot;Button &quot; + (i + 1) + &quot;: &quot; + payload.getDescription());"
+        errorLine2="                                                   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/ConfigActivity.java"
+            line="447"
+            column="52"/>
+    </issue>
+
+    <issue
+        id="SetTextI18n"
+        message="Do not concatenate text displayed with `setText`. Use resource string with placeholders."
+        errorLine1="                            buttonTextView.setText(&quot;Button &quot; + (i + 1) + &quot;: &quot; + payload.getDescription());"
+        errorLine2="                                                                ~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/ConfigActivity.java"
+            line="447"
+            column="65"/>
+    </issue>
+
+    <issue
+        id="SetTextI18n"
+        message="String literal in `setText` can not be translated. Use Android resources instead."
+        errorLine1="                            buttonTextView.setText(&quot;Button &quot; + (i + 1) + &quot;: &quot; + payload.getDescription());"
+        errorLine2="                                                   ~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/ConfigActivity.java"
+            line="447"
+            column="52"/>
+    </issue>
+
+    <issue
+        id="SetTextI18n"
+        message="Do not concatenate text displayed with `setText`. Use resource string with placeholders."
+        errorLine1="                            buttonTextView.setText(&quot;Button &quot; + (i + 1) + &quot;: Unknown&quot;);"
+        errorLine2="                                                   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/ConfigActivity.java"
+            line="449"
+            column="52"/>
+    </issue>
+
+    <issue
+        id="SetTextI18n"
+        message="Do not concatenate text displayed with `setText`. Use resource string with placeholders."
+        errorLine1="                            buttonTextView.setText(&quot;Button &quot; + (i + 1) + &quot;: Unknown&quot;);"
+        errorLine2="                                                                ~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/ConfigActivity.java"
+            line="449"
+            column="65"/>
+    </issue>
+
+    <issue
+        id="SetTextI18n"
+        message="String literal in `setText` can not be translated. Use Android resources instead."
+        errorLine1="                            buttonTextView.setText(&quot;Button &quot; + (i + 1) + &quot;: Unknown&quot;);"
+        errorLine2="                                                   ~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/ConfigActivity.java"
+            line="449"
+            column="52"/>
+    </issue>
+
+    <issue
+        id="SetTextI18n"
+        message="String literal in `setText` can not be translated. Use Android resources instead."
+        errorLine1="                            buttonTextView.setText(&quot;Button &quot; + (i + 1) + &quot;: Unknown&quot;);"
+        errorLine2="                                                                         ~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/ConfigActivity.java"
+            line="449"
+            column="74"/>
+    </issue>
+
+    <issue
+        id="SetTextI18n"
+        message="Do not concatenate text displayed with `setText`. Use resource string with placeholders."
+        errorLine1="            countView.setText(&quot;Count: &quot; + count);"
+        errorLine2="                              ~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/binary_sensor/activity/DataActivity.java"
+            line="50"
+            column="31"/>
+    </issue>
+
+    <issue
+        id="SetTextI18n"
+        message="String literal in `setText` can not be translated. Use Android resources instead."
+        errorLine1="            countView.setText(&quot;Count: &quot; + count);"
+        errorLine2="                              ~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/binary_sensor/activity/DataActivity.java"
+            line="50"
+            column="31"/>
+    </issue>
+
+    <issue
+        id="SetTextI18n"
+        message="Do not concatenate text displayed with `setText`. Use resource string with placeholders."
+        errorLine1="        autoExportLocation_path.setText(getAutoExportLocationUserString() + &quot; (&quot; + getAutoExportLocationPreferenceString() + &quot;)&quot; );"
+        errorLine2="                                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DataManagementActivity.java"
+            line="165"
+            column="41"/>
+    </issue>
+
+    <issue
+        id="SetTextI18n"
+        message="String literal in `setText` can not be translated. Use Android resources instead."
+        errorLine1="                textView.setText(&quot;MAC Address: &quot;);"
+        errorLine2="                                 ~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DebugActivity.java"
+            line="562"
+            column="34"/>
+    </issue>
+
+    <issue
+        id="SetTextI18n"
+        message="String literal in `setText` can not be translated. Use Android resources instead."
+        errorLine1="            editContent.setText(&quot;Test&quot;);"
+        errorLine2="                                ~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DebugActivity.java"
+            line="846"
+            column="33"/>
+    </issue>
+
+    <issue
+        id="SetTextI18n"
+        message="Do not concatenate text displayed with `setText`. Use resource string with placeholders."
+        errorLine1="                batteryStatusLabels[batteryIndex].setText(device.getBatteryLevel(batteryIndex) + &quot;%&quot;);"
+        errorLine2="                                                          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/GBDeviceAdapterv2.java"
+            line="353"
+            column="59"/>
+    </issue>
+
+    <issue
+        id="SetTextI18n"
+        message="Do not concatenate text displayed with `setText`. Use resource string with placeholders."
+        errorLine1="                ((TextView) findViewById(widgetButtonsMapping.keyAt(i))).setText(widgetButtonsMapping.valueAt(i) + &quot; widget&quot;);"
+        errorLine2="                                                                                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/HRConfigActivity.java"
+            line="359"
+            column="82"/>
+    </issue>
+
+    <issue
+        id="SetTextI18n"
+        message="String literal in `setText` can not be translated. Use Android resources instead."
+        errorLine1="                ((TextView) findViewById(widgetButtonsMapping.keyAt(i))).setText(widgetButtonsMapping.valueAt(i) + &quot; widget&quot;);"
+        errorLine2="                                                                                                                   ~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/HRConfigActivity.java"
+            line="359"
+            column="116"/>
+    </issue>
+
+    <issue
+        id="SetTextI18n"
+        message="Do not concatenate text displayed with `setText`. Use resource string with placeholders."
+        errorLine1="                                    position + &quot; widget: &quot; + function.substring(7)"
+        errorLine2="                                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/HRConfigActivity.java"
+            line="377"
+            column="37"/>
+    </issue>
+
+    <issue
+        id="SetTextI18n"
+        message="String literal in `setText` can not be translated. Use Android resources instead."
+        errorLine1="                                    position + &quot; widget: &quot; + function.substring(7)"
+        errorLine2="                                               ~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/HRConfigActivity.java"
+            line="377"
+            column="48"/>
+    </issue>
+
+    <issue
+        id="SetTextI18n"
+        message="Do not concatenate text displayed with `setText`. Use resource string with placeholders."
+        errorLine1="                                        position + &quot; widget: &quot;"
+        errorLine2="                                        ^">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/HRConfigActivity.java"
+            line="385"
+            column="41"/>
+    </issue>
+
+    <issue
+        id="SetTextI18n"
+        message="String literal in `setText` can not be translated. Use Android resources instead."
+        errorLine1="                                        position + &quot; widget: &quot;"
+        errorLine2="                                                   ~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/HRConfigActivity.java"
+            line="385"
+            column="52"/>
+    </issue>
+
+    <issue
+        id="SetTextI18n"
+        message="Number formatting does not take into account locale settings. Consider using `String.format` instead."
+        errorLine1="            days_current.setText(Integer.toString(stepsStreaks.current.days));"
+        errorLine2="                                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/StepStreaksDashboard.java"
+            line="190"
+            column="34"/>
+    </issue>
+
+    <issue
+        id="SetTextI18n"
+        message="Number formatting does not take into account locale settings. Consider using `String.format` instead."
+        errorLine1="            average_current.setText(Integer.toString(stepsStreaks.current.steps / stepsStreaks.current.days));"
+        errorLine2="                                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/StepStreaksDashboard.java"
+            line="191"
+            column="37"/>
+    </issue>
+
+    <issue
+        id="SetTextI18n"
+        message="Number formatting does not take into account locale settings. Consider using `String.format` instead."
+        errorLine1="            total_current.setText(Integer.toString(stepsStreaks.current.steps));"
+        errorLine2="                                  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/StepStreaksDashboard.java"
+            line="192"
+            column="35"/>
+    </issue>
+
+    <issue
+        id="SetTextI18n"
+        message="Number formatting does not take into account locale settings. Consider using `String.format` instead."
+        errorLine1="            days_maximum.setText(Integer.toString(stepsStreaks.maximum.days));"
+        errorLine2="                                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/StepStreaksDashboard.java"
+            line="201"
+            column="34"/>
+    </issue>
+
+    <issue
+        id="SetTextI18n"
+        message="Number formatting does not take into account locale settings. Consider using `String.format` instead."
+        errorLine1="            average_maximum.setText(Integer.toString(stepsStreaks.maximum.steps / stepsStreaks.maximum.days));"
+        errorLine2="                                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/StepStreaksDashboard.java"
+            line="202"
+            column="37"/>
+    </issue>
+
+    <issue
+        id="SetTextI18n"
+        message="Number formatting does not take into account locale settings. Consider using `String.format` instead."
+        errorLine1="            total_maximum.setText(Integer.toString(stepsStreaks.maximum.steps));"
+        errorLine2="                                  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/StepStreaksDashboard.java"
+            line="203"
+            column="35"/>
+    </issue>
+
+    <issue
+        id="SetTextI18n"
+        message="Number formatting does not take into account locale settings. Consider using `String.format` instead."
+        errorLine1="            total_total.setText(Integer.toString(stepsStreaks.total.steps));"
+        errorLine2="                                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/StepStreaksDashboard.java"
+            line="233"
+            column="33"/>
+    </issue>
+
+    <issue
+        id="SetTextI18n"
+        message="String literal in `setText` can not be translated. Use Android resources instead."
+        errorLine1="        box.setText(&quot;Respect silent mode&quot;);"
+        errorLine2="                    ~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/TimePicker.java"
+            line="102"
+            column="21"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;Today&quot;, should use `@string` resource"
+        errorLine1="            android:text=&quot;Today&quot; />"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_charts.xml"
+            line="43"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;&lt;&lt;&lt;&quot;, should use `@string` resource"
+        errorLine1="                android:text=&quot;&amp;lt;&amp;lt;&amp;lt;&quot; />"
+        errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_charts.xml"
+            line="61"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;&lt;&lt;&quot;, should use `@string` resource"
+        errorLine1="                android:text=&quot;&amp;lt;&amp;lt;&quot; />"
+        errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_charts.xml"
+            line="70"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;&lt;&quot;, should use `@string` resource"
+        errorLine1="                android:text=&quot;&amp;lt;&quot; />"
+        errorLine2="                ~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_charts.xml"
+            line="79"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;>&quot;, should use `@string` resource"
+        errorLine1="                android:text=&quot;>&quot; />"
+        errorLine2="                ~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_charts.xml"
+            line="88"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;>>&quot;, should use `@string` resource"
+        errorLine1="                android:text=&quot;>>&quot; />"
+        errorLine2="                ~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_charts.xml"
+            line="96"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;>>>&quot;, should use `@string` resource"
+        errorLine1="                android:text=&quot;>>>&quot; />"
+        errorLine2="                ~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_charts.xml"
+            line="105"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;Message / Caller&quot;, should use `@string` resource"
+        errorLine1="                android:text=&quot;Message / Caller&quot;"
+        errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_debug.xml"
+            line="33"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;Test&quot;, should use `@string` resource"
+        errorLine1="                android:text=&quot;Test&quot; />"
+        errorLine2="                ~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_debug.xml"
+            line="44"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;send as SMS&quot;, should use `@string` resource"
+        errorLine1="                android:text=&quot;send as SMS&quot; />"
+        errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_debug.xml"
+            line="52"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;Send&quot;, should use `@string` resource"
+        errorLine1="                android:text=&quot;Send&quot; />"
+        errorLine2="                ~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_debug.xml"
+            line="60"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;incoming call&quot;, should use `@string` resource"
+        errorLine1="                android:text=&quot;incoming call&quot; />"
+        errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_debug.xml"
+            line="68"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;outgoing call&quot;, should use `@string` resource"
+        errorLine1="                android:text=&quot;outgoing call&quot; />"
+        errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_debug.xml"
+            line="76"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;start call&quot;, should use `@string` resource"
+        errorLine1="                android:text=&quot;start call&quot; />"
+        errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_debug.xml"
+            line="84"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;end call&quot;, should use `@string` resource"
+        errorLine1="                android:text=&quot;end call&quot; />"
+        errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_debug.xml"
+            line="92"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;set time&quot;, should use `@string` resource"
+        errorLine1="                android:text=&quot;set time&quot; />"
+        errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_debug.xml"
+            line="100"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;set music info&quot;, should use `@string` resource"
+        errorLine1="                android:text=&quot;set music info&quot; />"
+        errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_debug.xml"
+            line="108"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;Heart Rate Test&quot;, should use `@string` resource"
+        errorLine1="                android:text=&quot;Heart Rate Test&quot; />"
+        errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_debug.xml"
+            line="116"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;reboot&quot;, should use `@string` resource"
+        errorLine1="                android:text=&quot;reboot&quot; />"
+        errorLine2="                ~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_debug.xml"
+            line="124"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;Set Activity Fetch Time&quot;, should use `@string` resource"
+        errorLine1="                android:text=&quot;Set Activity Fetch Time&quot; />"
+        errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_debug.xml"
+            line="132"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;Set Weather&quot;, should use `@string` resource"
+        errorLine1="                android:text=&quot;Set Weather&quot; />"
+        errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_debug.xml"
+            line="140"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;Show Cached Weather&quot;, should use `@string` resource"
+        errorLine1="                android:text=&quot;Show Cached Weather&quot; />"
+        errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_debug.xml"
+            line="148"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;factory reset&quot;, should use `@string` resource"
+        errorLine1="                android:text=&quot;factory reset&quot; />"
+        errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_debug.xml"
+            line="156"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;create test notification&quot;, should use `@string` resource"
+        errorLine1="                android:text=&quot;create test notification&quot; />"
+        errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_debug.xml"
+            line="164"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;create PebbleKit test notification&quot;, should use `@string` resource"
+        errorLine1="                android:text=&quot;create PebbleKit test notification&quot; />"
+        errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_debug.xml"
+            line="171"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;Fetch device Debug Logs&quot;, should use `@string` resource"
+        errorLine1="                android:text=&quot;Fetch device Debug Logs&quot; />"
+        errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_debug.xml"
+            line="178"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;Test New Functionality&quot;, should use `@string` resource"
+        errorLine1="                android:text=&quot;Test New Functionality&quot; />"
+        errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_debug.xml"
+            line="185"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;Show all registered app widgets IDs&quot;, should use `@string` resource"
+        errorLine1="                android:text=&quot;Show all registered app widgets IDs&quot; />"
+        errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_debug.xml"
+            line="199"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;Delete all registered app widgets IDs&quot;, should use `@string` resource"
+        errorLine1="                android:text=&quot;Delete all registered app widgets IDs&quot; />"
+        errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_debug.xml"
+            line="206"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;Show app Widgets Preferences&quot;, should use `@string` resource"
+        errorLine1="                android:text=&quot;Show app Widgets Preferences&quot; />"
+        errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_debug.xml"
+            line="213"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;Delete app Widgets Preferences&quot;, should use `@string` resource"
+        errorLine1="                android:text=&quot;Delete app Widgets Preferences&quot; />"
+        errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_debug.xml"
+            line="220"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;Activity summary extra date range (takes long to generate when used)&quot;, should use `@string` resource"
+        errorLine1="                android:text=&quot;Activity summary extra date range (takes long to generate when used)&quot;"
+        errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_debug.xml"
+            line="230"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;Add test device manually&quot;, should use `@string` resource"
+        errorLine1="                android:text=&quot;Add test device manually&quot; />"
+        errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_debug.xml"
+            line="239"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;Remove device preferences&quot;, should use `@string` resource"
+        errorLine1="                android:text=&quot;Remove device preferences&quot;"
+        errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_debug.xml"
+            line="245"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;Run Debug Function&quot;, should use `@string` resource"
+        errorLine1="                android:text=&quot;Run Debug Function&quot;"
+        errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_debug.xml"
+            line="253"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;Show Fit.App.Track. Status&quot;, should use `@string` resource"
+        errorLine1="                android:text=&quot;Show Fit.App.Track. Status&quot;"
+        errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_debug.xml"
+            line="277"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;NewWatchface&quot;, should use `@string` resource"
+        errorLine1="            android:text=&quot;NewWatchface&quot;"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_hybridhr_watchface_designer.xml"
+            line="34"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;14:30&quot;, should use `@string` resource"
+        errorLine1="                    android:text=&quot;14:30&quot; />"
+        errorLine2="                    ~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_list_item.xml"
+            line="40"
+            column="21"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;16:30&quot;, should use `@string` resource"
+        errorLine1="                    android:text=&quot;16:30&quot; />"
+        errorLine2="                    ~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_list_item.xml"
+            line="51"
+            column="21"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;Running&quot;, should use `@string` resource"
+        errorLine1="                    android:text=&quot;Running&quot;"
+        errorLine2="                    ~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_list_item.xml"
+            line="83"
+            column="21"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;25min&quot;, should use `@string` resource"
+        errorLine1="                        android:text=&quot;25min&quot; />"
+        errorLine2="                        ~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_list_item.xml"
+            line="104"
+            column="25"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;15000&quot;, should use `@string` resource"
+        errorLine1="                            android:text=&quot;15000&quot; />"
+        errorLine2="                            ~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_list_item.xml"
+            line="149"
+            column="29"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;101&quot;, should use `@string` resource"
+        errorLine1="                            android:text=&quot;101&quot; />"
+        errorLine2="                            ~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_list_item.xml"
+            line="179"
+            column="29"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;15.1km&quot;, should use `@string` resource"
+        errorLine1="                            android:text=&quot;15.1km&quot; />"
+        errorLine2="                            ~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_list_item.xml"
+            line="209"
+            column="29"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;122&quot;, should use `@string` resource"
+        errorLine1="                            android:text=&quot;122&quot; />"
+        errorLine2="                            ~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_list_item.xml"
+            line="239"
+            column="29"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;1.1.1973&quot;, should use `@string` resource"
+        errorLine1="                            android:text=&quot;1.1.1973&quot; />"
+        errorLine2="                            ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_list_item.xml"
+            line="269"
+            column="29"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;encrypted file&quot;, should use `@string` resource"
+        errorLine1="                android:text=&quot;encrypted file&quot;"
+        errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_qhybrid_file_management.xml"
+            line="26"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;generate file header&quot;, should use `@string` resource"
+        errorLine1="                android:text=&quot;generate file header&quot;"
+        errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_qhybrid_file_management.xml"
+            line="32"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;download file&quot;, should use `@string` resource"
+        errorLine1="                android:text=&quot;download file&quot;"
+        errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_qhybrid_file_management.xml"
+            line="38"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;upload file&quot;, should use `@string` resource"
+        errorLine1="                android:text=&quot;upload file&quot;"
+        errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_qhybrid_file_management.xml"
+            line="44"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;top button single press&quot;, should use `@string` resource"
+        errorLine1="            android:text=&quot;top button single press&quot;"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_qhybrid_hr_settings.xml"
+            line="18"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;middle button single press&quot;, should use `@string` resource"
+        errorLine1="            android:text=&quot;middle button single press&quot;"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_qhybrid_hr_settings.xml"
+            line="25"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;bottom button single press&quot;, should use `@string` resource"
+        errorLine1="            android:text=&quot;bottom button single press&quot;"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_qhybrid_hr_settings.xml"
+            line="32"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;top widget&quot;, should use `@string` resource"
+        errorLine1="        android:text=&quot;top widget&quot;"
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_qhybrid_hr_settings.xml"
+            line="45"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;right widget&quot;, should use `@string` resource"
+        errorLine1="        android:text=&quot;right widget&quot;"
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_qhybrid_hr_settings.xml"
+            line="52"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;bottom widget&quot;, should use `@string` resource"
+        errorLine1="        android:text=&quot;bottom widget&quot;"
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_qhybrid_hr_settings.xml"
+            line="59"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;left widget&quot;, should use `@string` resource"
+        errorLine1="        android:text=&quot;left widget&quot;"
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_qhybrid_hr_settings.xml"
+            line="66"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;custom widgets&quot;, should use `@string` resource"
+        errorLine1="        android:text=&quot;custom widgets&quot; />"
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_qhybrid_hr_settings.xml"
+            line="73"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;add widget&quot;, should use `@string` resource"
+        errorLine1="        android:text=&quot;add widget&quot; />"
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_qhybrid_hr_settings.xml"
+            line="85"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;delete background&quot;, should use `@string` resource"
+        errorLine1="            android:text=&quot;delete background&quot;"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_qhybrid_hr_settings.xml"
+            line="97"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;set background&quot;, should use `@string` resource"
+        errorLine1="            android:text=&quot;set background&quot;"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_qhybrid_hr_settings.xml"
+            line="104"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;ok&quot;, should use `@string` resource"
+        errorLine1="        android:text=&quot;ok&quot;"
+        errorLine2="        ~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_qhybrid_image_edit.xml"
+            line="31"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;Once&quot;, should use `@string` resource"
+        errorLine1="                android:text=&quot;Once&quot;"
+        errorLine2="                ~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_reminder_details.xml"
+            line="43"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;2019-08-17&quot;, should use `@string` resource"
+        errorLine1="                android:text=&quot;2019-08-17&quot;"
+        errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_reminder_details.xml"
+            line="82"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;16:35&quot;, should use `@string` resource"
+        errorLine1="                android:text=&quot;16:35&quot;"
+        errorLine2="                ~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_reminder_details.xml"
+            line="121"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;15000&quot;, should use `@string` resource"
+        errorLine1="                    android:text=&quot;15000&quot;"
+        errorLine2="                    ~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_summary_dashboard_item.xml"
+            line="100"
+            column="21"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;30000&quot;, should use `@string` resource"
+        errorLine1="                    android:text=&quot;30000&quot;"
+        errorLine2="                    ~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_summary_dashboard_item.xml"
+            line="147"
+            column="21"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;1d 20h 35m&quot;, should use `@string` resource"
+        errorLine1="                    android:text=&quot;1d 20h 35m&quot;"
+        errorLine2="                    ~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_summary_dashboard_item.xml"
+            line="206"
+            column="21"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;1d 13h 30m&quot;, should use `@string` resource"
+        errorLine1="                    android:text=&quot;1d 13h 30m&quot;"
+        errorLine2="                    ~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_summary_dashboard_item.xml"
+            line="252"
+            column="21"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;All Activities&quot;, should use `@string` resource"
+        errorLine1="                    android:text=&quot;All Activities&quot;"
+        errorLine2="                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_summary_dashboard_item.xml"
+            line="311"
+            column="21"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;15&quot;, should use `@string` resource"
+        errorLine1="                    android:text=&quot;15&quot;"
+        errorLine2="                    ~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_summary_dashboard_item.xml"
+            line="358"
+            column="21"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;January 1, 1970&quot;, should use `@string` resource"
+        errorLine1="                    android:text=&quot;January 1, 1970&quot;"
+        errorLine2="                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_summary_dashboard_item.xml"
+            line="415"
+            column="21"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;December 1, 2020&quot;, should use `@string` resource"
+        errorLine1="                    android:text=&quot;December 1, 2020&quot;"
+        errorLine2="                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_summary_dashboard_item.xml"
+            line="461"
+            column="21"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;RPM: &quot;, should use `@string` resource"
+        errorLine1="            android:text=&quot;RPM: &quot;"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_vesc_control.xml"
+            line="16"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;Break current (A): &quot;, should use `@string` resource"
+        errorLine1="            android:text=&quot;Break current (A): &quot; />"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_vesc_control.xml"
+            line="35"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;break&quot;, should use `@string` resource"
+        errorLine1="            android:text=&quot;break&quot;"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_vesc_control.xml"
+            line="53"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;fwd&quot;, should use `@string` resource"
+        errorLine1="            android:text=&quot;fwd&quot;"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_vesc_control.xml"
+            line="60"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;Volume keys control&quot;, should use `@string` resource"
+        errorLine1="        android:text=&quot;Volume keys control&quot;"
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_vesc_control.xml"
+            line="68"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;Weather on your Pebble&quot;, should use `@string` resource"
+        errorLine1="        android:text=&quot;Weather on your Pebble&quot;"
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_weather_notification.xml"
+            line="9"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;2x1&quot;, should use `@string` resource"
+        errorLine1="                android:text=&quot;2x1&quot;"
+        errorLine2="                ~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_widget_screen_details.xml"
+            line="43"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;TOP LEFT&quot;, should use `@string` resource"
+        errorLine1="                android:text=&quot;TOP LEFT&quot;"
+        errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_widget_screen_details.xml"
+            line="82"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;TOP RIGHT&quot;, should use `@string` resource"
+        errorLine1="                android:text=&quot;TOP RIGHT&quot;"
+        errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_widget_screen_details.xml"
+            line="121"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;CENTER&quot;, should use `@string` resource"
+        errorLine1="                android:text=&quot;CENTER&quot;"
+        errorLine2="                ~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_widget_screen_details.xml"
+            line="160"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;BOT LEFT&quot;, should use `@string` resource"
+        errorLine1="                android:text=&quot;BOT LEFT&quot;"
+        errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_widget_screen_details.xml"
+            line="199"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;BOT RIGHT&quot;, should use `@string` resource"
+        errorLine1="                android:text=&quot;BOT RIGHT&quot;"
+        errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_widget_screen_details.xml"
+            line="238"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;.&quot;, should use `@string` resource"
+        errorLine1="            android:text=&quot;.&quot;"
+        errorLine2="            ~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/dialog_frequency_picker.xml"
+            line="22"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;+10&quot;, should use `@string` resource"
+        errorLine1="                        android:text=&quot;+10&quot;"
+        errorLine2="                        ~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/fragment_pai_chart.xml"
+            line="85"
+            column="25"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;100&quot;, should use `@string` resource"
+        errorLine1="                        android:text=&quot;100&quot;"
+        errorLine2="                        ~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/fragment_pai_chart.xml"
+            line="121"
+            column="25"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;5&quot;, should use `@string` resource"
+        errorLine1="                        android:text=&quot;5&quot;"
+        errorLine2="                        ~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/fragment_pai_chart.xml"
+            line="168"
+            column="25"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;10 min&quot;, should use `@string` resource"
+        errorLine1="                        android:text=&quot;10 min&quot;"
+        errorLine2="                        ~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/fragment_pai_chart.xml"
+            line="180"
+            column="25"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;5&quot;, should use `@string` resource"
+        errorLine1="                        android:text=&quot;5&quot;"
+        errorLine2="                        ~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/fragment_pai_chart.xml"
+            line="216"
+            column="25"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;10 min&quot;, should use `@string` resource"
+        errorLine1="                        android:text=&quot;10 min&quot;"
+        errorLine2="                        ~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/fragment_pai_chart.xml"
+            line="228"
+            column="25"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;5&quot;, should use `@string` resource"
+        errorLine1="                        android:text=&quot;5&quot;"
+        errorLine2="                        ~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/fragment_pai_chart.xml"
+            line="264"
+            column="25"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;10 min&quot;, should use `@string` resource"
+        errorLine1="                        android:text=&quot;10 min&quot;"
+        errorLine2="                        ~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/fragment_pai_chart.xml"
+            line="276"
+            column="25"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;00:00&quot;, should use `@string` resource"
+        errorLine1="                android:text=&quot;00:00&quot;"
+        errorLine2="                ~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/item_alarm.xml"
+            line="33"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;(Smart)&quot;, should use `@string` resource"
+        errorLine1="                android:text=&quot;(Smart)&quot;"
+        errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/item_alarm.xml"
+            line="42"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;NOTIF&quot;, should use `@string` resource"
+        errorLine1="        android:text=&quot;NOTIF&quot;"
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/item_app_blacklist.xml"
+            line="19"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;PEBBLE&quot;, should use `@string` resource"
+        errorLine1="        android:text=&quot;PEBBLE&quot;"
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/item_app_blacklist.xml"
+            line="31"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;Item Name&quot;, should use `@string` resource"
+        errorLine1="            android:text=&quot;Item Name&quot;"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/item_app_blacklist.xml"
+            line="60"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;Item Description&quot;, should use `@string` resource"
+        errorLine1="            android:text=&quot;Item Description&quot;"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/item_app_blacklist.xml"
+            line="67"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;Item Name&quot;, should use `@string` resource"
+        errorLine1="            android:text=&quot;Item Name&quot;"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/item_app_specific_notification_app_list.xml"
+            line="36"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;Item Description&quot;, should use `@string` resource"
+        errorLine1="            android:text=&quot;Item Description&quot;"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/item_app_specific_notification_app_list.xml"
+            line="43"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;Item Name&quot;, should use `@string` resource"
+        errorLine1="                    android:text=&quot;Item Name&quot;"
+        errorLine2="                    ~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/item_appmanager_watchapp.xml"
+            line="61"
+            column="21"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;Item Description&quot;, should use `@string` resource"
+        errorLine1="                        android:text=&quot;Item Description&quot;"
+        errorLine2="                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/item_appmanager_watchapp.xml"
+            line="72"
+            column="25"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;drag handle&quot;, should use `@string` resource"
+        errorLine1="                android:contentDescription=&quot;drag handle&quot;"
+        errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/item_appmanager_watchapp.xml"
+            line="95"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;TextView&quot;, should use `@string` resource"
+        errorLine1="            android:text=&quot;TextView&quot;"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/item_cal_blacklist.xml"
+            line="45"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;Item Name&quot;, should use `@string` resource"
+        errorLine1="            android:text=&quot;Item Name&quot; />"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/item_cal_blacklist.xml"
+            line="56"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;John Smith&quot;, should use `@string` resource"
+        errorLine1="                android:text=&quot;John Smith&quot;"
+        errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/item_contact.xml"
+            line="34"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;+1 000 000 000&quot;, should use `@string` resource"
+        errorLine1="                android:text=&quot;+1 000 000 000&quot;"
+        errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/item_contact.xml"
+            line="47"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;Every ?, at ??:??&quot;, should use `@string` resource"
+        errorLine1="                android:text=&quot;Every ?, at ??:??&quot;"
+        errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/item_reminder.xml"
+            line="34"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;Screen 1&quot;, should use `@string` resource"
+        errorLine1="                android:text=&quot;Screen 1&quot;"
+        errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/item_widget_screen.xml"
+            line="33"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;x widgets&quot;, should use `@string` resource"
+        errorLine1="                android:text=&quot;x widgets&quot;"
+        errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/item_widget_screen.xml"
+            line="46"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;Item preview&quot;, should use `@string` resource"
+        errorLine1="        android:contentDescription=&quot;Item preview&quot; />"
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/item_with_details.xml"
+            line="14"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;Item Name&quot;, should use `@string` resource"
+        errorLine1="            android:text=&quot;Item Name&quot;"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/item_with_details.xml"
+            line="40"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;Item Description&quot;, should use `@string` resource"
+        errorLine1="                android:text=&quot;Item Description&quot; />"
+        errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/item_with_details.xml"
+            line="54"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;Item preview&quot;, should use `@string` resource"
+        errorLine1="        android:contentDescription=&quot;Item preview&quot; />"
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/item_with_details_horizontal.xml"
+            line="14"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;Item Name&quot;, should use `@string` resource"
+        errorLine1="            android:text=&quot;Item Name&quot;"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/item_with_details_horizontal.xml"
+            line="39"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;Item Description&quot;, should use `@string` resource"
+        errorLine1="            android:text=&quot;Item Description&quot; />"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/item_with_details_horizontal.xml"
+            line="48"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;Item preview&quot;, should use `@string` resource"
+        errorLine1="        android:contentDescription=&quot;Item preview&quot; />"
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/item_with_details_small.xml"
+            line="15"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;Item Name&quot;, should use `@string` resource"
+        errorLine1="            android:text=&quot;Item Name&quot; />"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/item_with_details_small.xml"
+            line="40"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;Item Description&quot;, should use `@string` resource"
+        errorLine1="            android:text=&quot;Item Description&quot; />"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/item_with_details_small.xml"
+            line="47"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;Middle-earth/Gondor&quot;, should use `@string` resource"
+        errorLine1="                android:text=&quot;Middle-earth/Gondor&quot;"
+        errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/item_world_clock.xml"
+            line="34"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;00:00&quot;, should use `@string` resource"
+        errorLine1="                android:text=&quot;00:00&quot;"
+        errorLine2="                ~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/item_world_clock.xml"
+            line="60"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;15000&quot;, should use `@string` resource"
+        errorLine1="        android:text=&quot;15000&quot;"
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/layout_widget_active_steps.xml"
+            line="44"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;15&quot;, should use `@string` resource"
+        errorLine1="        android:text=&quot;15&quot;"
+        errorLine2="        ~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/layout_widget_activities_count.xml"
+            line="44"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;15.1km&quot;, should use `@string` resource"
+        errorLine1="        android:text=&quot;15.1km&quot;"
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/layout_widget_distance.xml"
+            line="44"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;122&quot;, should use `@string` resource"
+        errorLine1="        android:text=&quot;122&quot;"
+        errorLine2="        ~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/layout_widget_duration.xml"
+            line="44"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;15&quot;, should use `@string` resource"
+        errorLine1="        android:text=&quot;15&quot;"
+        errorLine2="        ~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/layout_widget_intensity_total2.xml"
+            line="44"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;15000&quot;, should use `@string` resource"
+        errorLine1="        android:text=&quot;15000&quot;"
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/layout_widget_total_steps.xml"
+            line="44"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;widget name&quot;, should use `@string` resource"
+        errorLine1="                android:text=&quot;widget name&quot; />"
+        errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/qhybrid_activity_widget_settings.xml"
+            line="22"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;elements&quot;, should use `@string` resource"
+        errorLine1="                android:text=&quot;elements&quot; />"
+        errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/qhybrid_activity_widget_settings.xml"
+            line="32"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;add element&quot;, should use `@string` resource"
+        errorLine1="                android:text=&quot;add element&quot; />"
+        errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/qhybrid_activity_widget_settings.xml"
+            line="44"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;delete&quot;, should use `@string` resource"
+        errorLine1="                android:text=&quot;delete&quot;"
+        errorLine2="                ~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/qhybrid_activity_widget_settings.xml"
+            line="59"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;save&quot;, should use `@string` resource"
+        errorLine1="                android:text=&quot;save&quot;"
+        errorLine2="                ~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/qhybrid_activity_widget_settings.xml"
+            line="66"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;element id&quot;, should use `@string` resource"
+        errorLine1="        android:text=&quot;element id&quot; />"
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/qhybrid_element_popup_view.xml"
+            line="12"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;default value&quot;, should use `@string` resource"
+        errorLine1="        android:text=&quot;default value&quot; />"
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/qhybrid_element_popup_view.xml"
+            line="22"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;type&quot;, should use `@string` resource"
+        errorLine1="        android:text=&quot;type&quot; />"
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/qhybrid_element_popup_view.xml"
+            line="32"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;text&quot;, should use `@string` resource"
+        errorLine1="            android:text=&quot;text&quot;"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/qhybrid_element_popup_view.xml"
+            line="43"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;background image&quot;, should use `@string` resource"
+        errorLine1="            android:text=&quot;background image&quot;"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/qhybrid_element_popup_view.xml"
+            line="49"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;position&quot;, should use `@string` resource"
+        errorLine1="        android:text=&quot;position&quot; />"
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/qhybrid_element_popup_view.xml"
+            line="57"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;upper half&quot;, should use `@string` resource"
+        errorLine1="            android:text=&quot;upper half&quot;"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/qhybrid_element_popup_view.xml"
+            line="68"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;lower half&quot;, should use `@string` resource"
+        errorLine1="            android:text=&quot;lower half&quot;"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/qhybrid_element_popup_view.xml"
+            line="74"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;15000&quot;, should use `@string` resource"
+        errorLine1="        android:text=&quot;15000&quot;"
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/steps_streak_average.xml"
+            line="34"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;9150000&quot;, should use `@string` resource"
+        errorLine1="        android:text=&quot;9150000&quot;"
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/steps_streak_days.xml"
+            line="36"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="HardcodedText"
+        message="Hardcoded string &quot;9150000&quot;, should use `@string` resource"
+        errorLine1="        android:text=&quot;9150000&quot;"
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/steps_streak_total.xml"
+            line="35"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="RelativeOverlap"
+        message="`@id/button_edit_name` can overlap `@id/watchface_name` if @string/button_watchface_edit_name grows due to localized text expansion"
+        errorLine1="        &lt;Button"
+        errorLine2="         ~~~~~~">
+        <location
+            file="src/main/res/layout/activity_hybridhr_watchface_designer.xml"
+            line="37"
+            column="10"/>
+    </issue>
+
+    <issue
+        id="RelativeOverlap"
+        message="`@id/qhybridTimeOffset` can overlap `TextView-1` if @string/qhybrid_time_shift, @id/qhybridTimeOffset grow due to localized text expansion"
+        errorLine1="            &lt;TextView"
+        errorLine2="             ~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_qhybrid_settings.xml"
+            line="59"
+            column="14"/>
+    </issue>
+
+    <issue
+        id="RelativeOverlap"
+        message="`@id/timezoneOffset` can overlap `TextView-1` if @string/qhybrid_second_timezone_offset_relative_to_utc, @id/timezoneOffset grow due to localized text expansion"
+        errorLine1="            &lt;TextView"
+        errorLine2="             ~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_qhybrid_settings.xml"
+            line="77"
+            column="14"/>
+    </issue>
+
+    <issue
+        id="RelativeOverlap"
+        message="`@id/withings_calibration_button_next` can overlap `@id/withings_calibration_button_ok` if @string/ok, @string/withings_bt_calibration_next grow due to localized text expansion"
+        errorLine1="    &lt;Button"
+        errorLine2="     ~~~~~~">
+        <location
+            file="src/main/res/layout/activity_withings_calibration.xml"
+            line="37"
+            column="6"/>
+    </issue>
+
+    <issue
+        id="RelativeOverlap"
+        message="`@id/withings_calibration_button_next` can overlap `@id/withings_calibration_button_previous` if @string/withings_bt_calibration_previous, @string/withings_bt_calibration_next grow due to localized text expansion"
+        errorLine1="    &lt;Button"
+        errorLine2="     ~~~~~~">
+        <location
+            file="src/main/res/layout/activity_withings_calibration.xml"
+            line="37"
+            column="6"/>
+    </issue>
+
+    <issue
+        id="RelativeOverlap"
+        message="`@id/fossil_hr_row_app_name` can overlap `ImageView-3` if @id/fossil_hr_row_app_name grows due to localized text expansion"
+        errorLine1="    &lt;TextView"
+        errorLine2="     ~~~~~~~~">
+        <location
+            file="src/main/res/layout/fossil_hr_row_installed_app.xml"
+            line="7"
+            column="6"/>
+    </issue>
+
+    <issue
+        id="RelativeOverlap"
+        message="`@id/world_clock_item_label` can overlap `@id/world_clock_current_time` if ? grows due to localized text expansion"
+        errorLine1="            &lt;TextView"
+        errorLine2="             ~~~~~~~~">
+        <location
+            file="src/main/res/layout/item_world_clock.xml"
+            line="37"
+            column="14"/>
+    </issue>
+
+    <issue
+        id="RelativeOverlap"
+        message="`@id/packageName` can overlap `@id/packageClock` if @id/packageName grows due to localized text expansion"
+        errorLine1="    &lt;TextView"
+        errorLine2="     ~~~~~~~~">
+        <location
+            file="src/main/res/layout/qhybrid_package_settings_item.xml"
+            line="15"
+            column="6"/>
+    </issue>
+
+    <issue
+        id="RtlSymmetry"
+        message="When you define `paddingLeft` you should probably also define `paddingRight` for right-to-left symmetry"
+        errorLine1="                android:paddingLeft=&quot;10dp&quot;"
+        errorLine2="                ~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_debug.xml"
+            line="229"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="RtlSymmetry"
+        message="When you define `paddingLeft` you should probably also define `paddingRight` for right-to-left symmetry"
+        errorLine1="        android:paddingLeft=&quot;10dp&quot;"
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/fragment_statschart.xml"
+            line="23"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="RtlSymmetry"
+        message="When you define `paddingStart` you should probably also define `paddingEnd` for right-to-left symmetry"
+        errorLine1="    android:paddingStart=&quot;1dp&quot;>"
+        errorLine2="    ~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/item_with_details_horizontal.xml"
+            line="6"
+            column="5"/>
+    </issue>
+
+    <issue
+        id="RtlSymmetry"
+        message="When you define `paddingStart` you should probably also define `paddingEnd` for right-to-left symmetry"
+        errorLine1="        android:paddingStart=&quot;16dp&quot;"
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/list_item_checkable.xml"
+            line="15"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="RtlHardcoded"
+        message="Consider replacing `android:paddingLeft` with `android:paddingStart=&quot;10dp&quot;` to better support right-to-left layouts"
+        errorLine1="                android:paddingLeft=&quot;10dp&quot;"
+        errorLine2="                ~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/activity_debug.xml"
+            line="229"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="RtlHardcoded"
+        message="Consider replacing `android:layout_alignParentRight` with `android:layout_alignParentEnd=&quot;true&quot;` to better support right-to-left layouts"
+        errorLine1="        android:layout_alignParentRight=&quot;true&quot;"
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/fragment_sleepchart.xml"
+            line="41"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="RtlHardcoded"
+        message="Consider replacing `android:layout_alignParentRight` with `android:layout_alignParentEnd=&quot;true&quot;` to better support right-to-left layouts"
+        errorLine1="        android:layout_alignParentRight=&quot;true&quot;"
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/fragment_sleepchart.xml"
+            line="49"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="RtlHardcoded"
+        message="Consider replacing `android:layout_alignParentLeft` with `android:layout_alignParentStart=&quot;true&quot;` to better support right-to-left layouts"
+        errorLine1="        android:layout_alignParentLeft=&quot;true&quot;"
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout-land/fragment_sleepchart.xml"
+            line="54"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="RtlHardcoded"
+        message="Consider replacing `android:layout_alignParentLeft` with `android:layout_alignParentStart=&quot;true&quot;` to better support right-to-left layouts"
+        errorLine1="        android:layout_alignParentLeft=&quot;true&quot;"
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout-land/fragment_sleepchart.xml"
+            line="64"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="RtlHardcoded"
+        message="Consider replacing `android:paddingLeft` with `android:paddingStart=&quot;10dp&quot;` to better support right-to-left layouts"
+        errorLine1="        android:paddingLeft=&quot;10dp&quot;"
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/fragment_statschart.xml"
+            line="23"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="RtlHardcoded"
+        message="Consider replacing `android:layout_alignParentRight` with `android:layout_alignParentEnd=&quot;true&quot;` to better support right-to-left layouts"
+        errorLine1="            android:layout_alignParentRight=&quot;true&quot;"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/res/layout/steps_streaks_dashboard.xml"
+            line="74"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="RtlEnabled"
+        message="The project references RTL attributes, but does not explicitly enable or disable RTL support with `android:supportsRtl` in the manifest">
+        <location
+            file="src/main/AndroidManifest.xml"/>
+    </issue>
+
+</issues>
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
new file mode 100644
index 0000000..27cc1f4
--- /dev/null
+++ b/app/proguard-rules.pro
@@ -0,0 +1,79 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in /home/andi/Android/Sdk/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+-dontobfuscate
+
+# Pebble BG-JS
+-keepclassmembers class * {
+    @android.webkit.JavascriptInterface <methods>;
+}
+-keepclassmembers class nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.webview.JSInterface {
+    public *;
+}
+# Required for reflection in BangleJSDeviceSupport
+-keepclassmembers class nodomain.freeyourgadget.gadgetbridge.model.CallSpec {
+    public static *;
+}
+# Required for reflection in method GattCharacteristic.initDebugMap()
+-keepclassmembers class nodomain.freeyourgadget.gadgetbridge.service.btle.GattCharacteristic {
+    public static *;
+}
+# Keep constructors for support classes, as they're called by reflection in DeviceSupportFactory#createServiceDeviceSupport
+-keep public class * extends nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport
+-keepclassmembers class * extends nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport {
+    public <init>(nodomain.freeyourgadget.gadgetbridge.model.DeviceType);
+    public <init>();
+}
+-keep class nodomain.freeyourgadget.gadgetbridge.womp.** { *; }
+
+# Keep coordinators, they're only referenced from DeviceType
+-keep public class * implements nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator
+
+# Keep parseIncoming for GFDIMessage classes, as it is called by reflection in GFDIMessage#parseIncoming
+-keep public class * extends nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.GFDIMessage
+-keepclassmembers class * extends nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.GFDIMessage {
+    public static *** parseIncoming(...);
+}
+
+# https://github.com/tony19/logback-android/issues/29
+-dontwarn javax.mail.**
+
+# To avoid any stacktrace ambiguity
+-keepattributes SourceFile,LineNumberTable
+
+# GreenDAO 2 - http://greenrobot.org/greendao/documentation/technical-faq/
+-keepclassmembers class * extends de.greenrobot.dao.AbstractDao {
+    public static java.lang.String TABLENAME;
+}
+
+-keep class **$Properties
+
+-keep class **$Properties { *; }
+
+# Keep database migration classes accessed trough reflection
+-keep class **.gadgetbridge.database.schema.* { *; }
+
+# Keep Nordic DFU library
+-keep class no.nordicsemi.android.dfu.** { *; }
+
+# Keep logback classes
+-keep class ch.qos.** { *; }
+-keep class org.slf4j.** { *; }
+
+# Keep data classes
+-keepclassmembers,allowobfuscation class * {
+  @com.google.gson.annotations.SerializedName <fields>;
+}
+# Somehow the rule above was not enough for some
+-keep class nodomain.freeyourgadget.gadgetbridge.devices.pinetime.InfiniTimeDFU* { *; }
+
+# Keep generated protobuf classes
+-keep class nodomain.freeyourgadget.gadgetbridge.proto.** { *; }
diff --git a/app/src/banglejs/AndroidManifest.xml b/app/src/banglejs/AndroidManifest.xml
new file mode 100644
index 0000000..3cb3262
--- /dev/null
+++ b/app/src/banglejs/AndroidManifest.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+    <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+</manifest>
diff --git a/app/src/banglejs/res/drawable/ic_launcher_foreground.xml b/app/src/banglejs/res/drawable/ic_launcher_foreground.xml
new file mode 100644
index 0000000..92a4552
--- /dev/null
+++ b/app/src/banglejs/res/drawable/ic_launcher_foreground.xml
@@ -0,0 +1,72 @@
+<vector android:height="652dp" android:viewportHeight="651.75745"
+    android:viewportWidth="651.75745" android:width="652dp" xmlns:android="http://schemas.android.com/apk/res/android">
+    <path android:fillColor="#000000"
+        android:pathData="M4.395,0L647.362,0A5.273,4.395 90,0 1,651.757 5.273L651.757,646.485A5.273,4.395 90,0 1,647.362 651.758L4.395,651.758A5.273,4.395 90,0 1,-0 646.485L-0,5.273A5.273,4.395 90,0 1,4.395 0z"
+        android:strokeColor="#00000000" android:strokeWidth="1.31811"/>
+    <group>
+        <clip-path android:pathData="M137.032,998.55L1528.284,998.55L1528.284,215.971L137.032,215.971Z"/>
+        <path android:fillColor="#ffffff" android:fillType="nonZero"
+            android:pathData="M488.99,417.594L484.347,417.594L484.347,402.013L488.99,402.013c0.852,-0 1.543,0.691 1.543,1.543L490.533,416.051C490.533,416.903 489.842,417.594 488.99,417.594"
+            android:strokeColor="#00000000" android:strokeWidth="1.84007"/>
+        <path android:fillColor="#ffffff" android:fillType="nonZero"
+            android:pathData="m476.084,380.673 l-1.312,1.312 -7.454,-7.454 1.312,-1.312c1.032,-1.033 2.706,-1.033 3.738,-0L476.084,376.935C477.116,377.968 477.116,379.641 476.084,380.673"
+            android:strokeColor="#00000000" android:strokeWidth="1.84007"/>
+        <path android:fillColor="#ffffff" android:fillType="nonZero"
+            android:pathData="m468.63,446.387 l-1.312,-1.312 7.454,-7.454 1.312,1.312c1.033,1.033 1.033,2.706 0,3.738L472.368,446.387C471.335,447.42 469.662,447.42 468.63,446.387"
+            android:strokeColor="#00000000" android:strokeWidth="1.84007"/>
+        <path android:fillColor="#ffffff" android:fillType="nonZero"
+            android:pathData="m462.755,452.39l0,-3.575c0,-0 -17.441,6.416 -23.255,6.416 -5.814,-0 -23.255,-6.416 -23.255,-6.416L416.245,452.39c0,1.966 0.644,3.696 1.622,4.716 1.038,5.563 2.264,19.093 2.667,23.64 0,3.14 1.638,5.685 3.661,5.685l30.611,-0c2.022,-0 3.66,-2.545 3.66,-5.685l0,-0.001c0.403,-4.547 1.629,-18.077 2.668,-23.64C462.111,456.086 462.755,454.356 462.755,452.39"
+            android:strokeColor="#00000000" android:strokeWidth="1.84007"/>
+        <path android:fillColor="#ffffff" android:fillType="nonZero"
+            android:pathData="m416.245,367.217l0,3.575c0,-0 17.441,-6.416 23.255,-6.416 5.814,-0 23.255,6.416 23.255,6.416L462.755,367.217c0,-1.966 -0.644,-3.696 -1.622,-4.716 -1.038,-5.563 -2.264,-19.093 -2.667,-23.64 0,-3.14 -1.638,-5.685 -3.66,-5.685L424.194,333.175c-2.022,-0 -3.66,2.546 -3.66,5.685l0,-0C420.131,343.408 418.905,356.938 417.866,362.5 416.889,363.521 416.245,365.25 416.245,367.217"
+            android:strokeColor="#00000000" android:strokeWidth="1.84007"/>
+        <path android:fillColor="#ffffff" android:fillType="nonZero"
+            android:pathData="m406.365,376.668c-18.3,18.3 -18.3,47.97 0,66.27 18.3,18.3 47.97,18.3 66.27,-0 18.301,-18.3 18.3,-47.97 0,-66.27C454.335,358.368 424.665,358.368 406.365,376.668m62.269,62.269c-16.091,16.09 -42.178,16.09 -58.268,-0 -16.091,-16.091 -16.091,-42.178 -0.001,-58.269 16.091,-16.09 42.178,-16.09 58.269,-0 16.091,16.091 16.091,42.178 0,58.269"
+            android:strokeColor="#00000000" android:strokeWidth="1.84007"/>
+        <path android:fillColor="#ffffff" android:fillType="nonZero"
+            android:pathData="M195.008,427.752C191.39,430.98 186.965,432.594 181.734,432.594L163.118,432.594l0,-59.606l14.526,-0c4.619,-0 8.292,1.198 11.02,3.59 2.893,2.561 4.341,6.123 4.341,10.686 0,5.232 -2.143,9.072 -6.428,11.521C190.751,399.618 194.117,401.511 196.678,404.461 199.182,407.411 200.434,411.028 200.434,415.313 200.434,420.434 198.625,424.579 195.008,427.752m-8.766,-48.336c-2.283,-2.004 -5.065,-3.006 -8.348,-3.006L166.457,376.41L166.457,398.116l11.937,-0c3.227,-0 5.899,-1.002 8.015,-3.006 2.114,-2.004 3.172,-4.619 3.172,-7.848 0,-3.339 -1.114,-5.955 -3.34,-7.847m6.261,25.795c-2.838,-2.448 -6.401,-3.673 -10.686,-3.673L166.457,401.538L166.457,429.171l15.36,-0c4.229,-0 7.819,-1.28 10.769,-3.84 3.005,-2.559 4.508,-5.899 4.508,-10.018 0,-4.174 -1.531,-7.541 -4.591,-10.102"
+            android:strokeColor="#00000000" android:strokeWidth="1.84007"/>
+        <path android:fillColor="#ffffff" android:fillType="nonZero"
+            android:pathData="m237.917,432.594l0,-5.927c-3.618,4.341 -8.21,6.511 -13.774,6.511 -5.009,-0 -9.212,-1.696 -12.606,-5.092 -3.451,-3.451 -5.177,-7.681 -5.177,-12.689 0,-5.009 1.698,-9.211 5.093,-12.606 3.45,-3.45 7.68,-5.176 12.689,-5.176 5.509,-0 10.101,2.17 13.774,6.512l0,-6.011L241.34,398.115L241.34,432.594ZM234.327,405.212c-2.838,-2.838 -6.233,-4.257 -10.184,-4.257 -4.007,-0 -7.403,1.391 -10.185,4.174 -2.838,2.838 -4.258,6.261 -4.258,10.268 0,3.952 1.42,7.346 4.258,10.184 2.838,2.839 6.232,4.258 10.185,4.258 4.006,-0 7.401,-1.391 10.184,-4.174 2.838,-2.839 4.258,-6.261 4.258,-10.268 0,-3.951 -1.42,-7.347 -4.258,-10.185"
+            android:strokeColor="#00000000" android:strokeWidth="1.84007"/>
+        <path android:fillColor="#ffffff" android:fillType="nonZero"
+            android:pathData="m274.732,432.594l0,-20.62c0,-7.29 -3.285,-10.937 -9.85,-10.937 -3.229,-0 -5.677,0.948 -7.347,2.839 -1.725,1.893 -2.588,4.48 -2.588,7.763L254.947,432.594l-3.423,-0l0,-34.478l3.423,-0l0,4.174c1.558,-3.061 4.869,-4.592 9.935,-4.592 4.285,-0 7.568,1.225 9.85,3.673 2.282,2.45 3.423,5.873 3.423,10.268L278.155,432.594Z"
+            android:strokeColor="#00000000" android:strokeWidth="1.84007"/>
+        <path android:fillColor="#ffffff" android:fillType="nonZero"
+            android:pathData="m316.89,444.114c-3.117,3.117 -7.208,4.675 -12.272,4.675 -9.406,-0 -15.026,-3.702 -16.863,-11.103l3.422,-0c0.612,2.726 2.366,4.759 5.26,6.094 2.281,1.056 5.009,1.586 8.181,1.586 4.285,-0 7.597,-1.197 9.934,-3.59 2.393,-2.393 3.59,-5.733 3.59,-10.018l0,-5.093c-3.618,4.341 -8.21,6.511 -13.774,6.511 -5.009,-0 -9.212,-1.696 -12.606,-5.092 -3.451,-3.451 -5.176,-7.681 -5.176,-12.689 0,-5.009 1.697,-9.211 5.093,-12.606 3.45,-3.45 7.681,-5.176 12.689,-5.176 5.509,-0 10.101,2.17 13.774,6.512L318.142,398.116L321.565,398.116L321.565,431.842C321.565,436.906 320.007,440.997 316.89,444.114m-2.338,-38.903c-2.838,-2.838 -6.233,-4.257 -10.184,-4.257 -4.007,-0 -7.403,1.391 -10.185,4.174 -2.838,2.838 -4.258,6.261 -4.258,10.268 0,3.952 1.42,7.346 4.258,10.185 2.838,2.838 6.232,4.257 10.185,4.257 4.006,-0 7.401,-1.391 10.184,-4.174 2.838,-2.839 4.258,-6.261 4.258,-10.268 0,-3.951 -1.42,-7.347 -4.258,-10.185"
+            android:strokeColor="#00000000" android:strokeWidth="1.84007"/>
+    </group>
+    <path android:fillColor="#ffffff" android:fillType="nonZero"
+        android:pathData="m329.326,367.888l3.34,-0L332.666,429.665l-3.34,-0z"
+        android:strokeColor="#00000000" android:strokeWidth="1.84007"/>
+    <group>
+        <clip-path android:pathData="M133.69,995.622L1524.942,995.622L1524.942,213.043L133.69,213.043Z"/>
+        <path android:fillColor="#ffffff" android:fillType="nonZero"
+            android:pathData="m345.103,411.717c0,4.675 1.252,8.377 3.757,11.103 2.615,2.783 6.205,4.174 10.769,4.174 2.338,-0 4.452,-0.388 6.345,-1.168 2.392,-1.002 4.535,-3.033 6.428,-6.095l3.089,1.42c-2.171,4.119 -5.51,6.901 -10.018,8.348 -1.726,0.556 -3.674,0.835 -5.844,0.835 -5.176,-0 -9.434,-1.697 -12.773,-5.093 -3.396,-3.394 -5.093,-7.652 -5.093,-12.773 0,-5.176 1.67,-9.434 5.009,-12.773 3.393,-3.394 7.68,-5.093 12.856,-5.093 5.12,-0 9.35,1.586 12.689,4.759C375.712,402.534 377.411,406.653 377.411,411.717ZM368.645,400.78C366.029,398.889 363.023,397.942 359.629,397.942 352.505,397.942 347.829,401.448 345.604,408.461l27.966,-0c-0.557,-3.172 -2.199,-5.732 -4.925,-7.681"
+            android:strokeColor="#00000000" android:strokeWidth="1.84007"/>
+    </group>
+    <path android:fillColor="#ffffff" android:fillType="nonZero"
+        android:pathData="m435.285,411.261l1.746,-0l0,24.973l-1.746,-0z"
+        android:strokeColor="#00000000" android:strokeWidth="1.84007"/>
+    <group>
+        <clip-path android:pathData="M133.69,995.622L1524.942,995.622L1524.942,213.043L133.69,213.043Z"/>
+        <path android:fillColor="#ffffff" android:fillType="nonZero"
+            android:pathData="m451.556,427.744c-1.136,0.966 -2.498,1.448 -4.089,1.448 -2.725,-0 -4.77,-1.235 -6.133,-3.705l1.491,-0.895c0.483,0.88 1.064,1.562 1.746,2.045 0.71,0.539 1.675,0.809 2.896,0.809 2.669,-0 4.003,-1.135 4.003,-3.408 0,-0.993 -0.468,-1.859 -1.405,-2.598 -0.285,-0.17 -0.689,-0.362 -1.214,-0.575 -0.526,-0.213 -1.172,-0.462 -1.938,-0.746 -1.42,-0.511 -2.442,-1.022 -3.066,-1.533 -0.909,-0.738 -1.363,-1.803 -1.363,-3.194 0,-1.193 0.454,-2.215 1.363,-3.067 0.88,-0.88 2.029,-1.32 3.449,-1.32 1.959,-0 3.62,0.681 4.983,2.044L451.003,414.158c-0.965,-0.965 -2.201,-1.448 -3.706,-1.448 -2.044,-0 -3.067,0.894 -3.067,2.683 0,0.795 0.312,1.448 0.938,1.959 0.511,0.399 1.32,0.782 2.427,1.15 0.964,0.341 1.76,0.66 2.385,0.959 0.625,0.298 1.093,0.561 1.406,0.788C452.606,421.185 453.217,422.448 453.217,424.038 453.217,425.572 452.664,426.807 451.556,427.744"
+            android:strokeColor="#00000000" android:strokeWidth="1.84007"/>
+    </group>
+    <path android:fillColor="#ffffff" android:fillType="nonZero"
+        android:pathData="m437.646,406.875c0,0.822 -0.666,1.488 -1.488,1.488 -0.822,-0 -1.488,-0.666 -1.488,-1.488 0,-0.822 0.667,-1.488 1.488,-1.488 0.822,-0 1.488,0.666 1.488,1.488"
+        android:strokeColor="#00000000" android:strokeWidth="1.84007"/>
+    <path android:fillColor="#ffffff" android:fillType="nonZero"
+        android:pathData="m438.109,402.347 l1.46,0.957 15.692,-23.932 -1.46,-0.958z"
+        android:strokeColor="#00000000" android:strokeWidth="1.84007"/>
+    <path android:fillColor="#ffffff"
+        android:pathData="M262.574,286.308m-70.193,0a70.193,70.193 45,1 1,140.385 0a70.193,70.193 45,1 1,-140.385 0"
+        android:strokeColor="#00000000" android:strokeWidth="3.30771"/>
+    <path android:fillColor="#008de4"
+        android:pathData="m256.715,379.575c-19.655,-1.374 -38.812,-8.771 -53.855,-20.794 -3.999,-3.196 -11.197,-10.361 -14.382,-14.315 -11.11,-13.796 -18.274,-30.626 -20.677,-48.575 -0.67,-5.009 -0.67,-17.926 0,-22.935 2.448,-18.29 10.142,-36.068 21.399,-49.447 15.453,-18.366 35.936,-29.741 60.348,-33.514 5.552,-0.858 18.768,-0.979 24.368,-0.223 24.914,3.363 47.223,15.725 62.535,34.653 6.785,8.387 12.88,19.485 16.21,29.513 3.67,11.051 4.921,18.804 4.921,30.485 0,15.435 -3.055,28.599 -9.795,42.207 -10.586,21.373 -28.391,37.719 -50.732,46.576 -5.482,2.173 -15.632,5.003 -17.944,5.003 -0.266,-0 18.503,-12.673 37.207,-33.536l33.599,-37.476 -6.692,-5.016c-29.624,-22.207 -39.184,-43.496 -39.485,-43.311 -0.197,0.122 -0.307,0.053 -0.246,-0.153 0.132,-0.438 -7.522,-8.072 -7.976,-7.956 -0.169,0.043 -0.307,-0.194 -0.307,-0.527 0,-1.136 -8.362,-7.586 -9.835,-7.586 -0.365,-0 -2.034,-1.399 -3.708,-3.108 -1.674,-1.709 -4.242,-4.01 -5.706,-5.112 -1.464,-1.102 -2.85,-2.16 -3.08,-2.35 -0.23,-0.19 -0.67,-0.261 -0.978,-0.159 -0.311,0.104 -0.444,-0 -0.3,-0.235 0.325,-0.526 -2.508,-3.069 -3.048,-2.735 -0.217,0.134 -0.345,0.073 -0.283,-0.135 0.179,-0.603 -3.374,-3.401 -5.651,-4.451 -2.433,-1.122 -5.685,-1.806 -7.137,-1.502 -0.715,0.15 -2.692,2.295 -6.553,7.111 -3.041,3.793 -5.759,6.906 -6.041,6.919 -0.619,0.028 -0.695,0.842 -0.078,0.842 0.239,-0 0.325,0.176 0.192,0.391 -0.133,0.215 1.128,1.551 2.802,2.969 1.674,1.418 3.003,2.622 2.954,2.674 -0.049,0.052 -1.92,-0.708 -4.157,-1.69 -2.238,-0.982 -4.652,-1.912 -5.365,-2.066 -1.772,-0.383 -4.427,0.401 -5.871,1.735 -1.519,1.403 -13.509,23.553 -13.793,25.481 -0.187,1.265 1.165,4.197 11.091,24.067 11.82,23.66 12.4,25.095 10.382,25.705 -1.32,0.399 -3.182,2.221 -3.839,3.757 -0.401,0.937 -0.564,3.222 -0.564,7.897 0,7.137 0.282,8.439 2.214,10.221 1.061,0.979 1.084,1.098 0.821,4.331 -0.256,3.153 -0.211,3.418 0.863,5.137 2.885,4.614 21.556,27.429 25.029,30.583l1.015,0.921 -4.016,-0.072c-2.209,-0.039 -4.845,-0.13 -5.859,-0.2zM270.75,379.153c0.398,-0.104 0.951,-0.097 1.229,0.015 0.277,0.112 -0.049,0.197 -0.725,0.189 -0.676,-0.008 -0.903,-0.1 -0.504,-0.204zM274.223,378.756c0.282,-0.114 0.742,-0.114 1.024,-0 0.282,0.114 0.051,0.207 -0.512,0.207 -0.563,-0 -0.793,-0.093 -0.512,-0.207zM277.525,378.338c0.296,-0.118 0.649,-0.104 0.785,0.032 0.136,0.136 -0.106,0.233 -0.538,0.215 -0.477,-0.02 -0.574,-0.117 -0.247,-0.247zM285.873,341.144c0.008,-0.676 0.1,-0.903 0.204,-0.504 0.104,0.398 0.097,0.951 -0.015,1.229 -0.112,0.277 -0.197,-0.049 -0.189,-0.724zM285.416,314.49c0.02,-0.477 0.117,-0.574 0.247,-0.247 0.118,0.296 0.104,0.649 -0.032,0.785 -0.136,0.136 -0.233,-0.106 -0.215,-0.538zM258.418,297.937c0,-0.563 0.093,-0.794 0.207,-0.512 0.114,0.282 0.114,0.742 0,1.024 -0.114,0.282 -0.207,0.051 -0.207,-0.512zM282.44,295.61c0.296,-0.118 0.649,-0.104 0.785,0.032 0.136,0.136 -0.106,0.233 -0.538,0.215 -0.477,-0.02 -0.574,-0.117 -0.247,-0.247zM272.278,287.289c-0.369,-0.239 -0.42,-0.4 -0.127,-0.403 0.268,-0.003 0.602,0.178 0.741,0.403 0.314,0.508 0.172,0.508 -0.614,-0zM276.438,285.651c0,-0.563 0.093,-0.793 0.207,-0.512 0.114,0.282 0.114,0.742 0,1.024 -0.114,0.282 -0.207,0.051 -0.207,-0.512zM257.944,280.917c0,-0.802 0.154,-1.363 0.341,-1.247 0.323,0.2 0.153,2.704 -0.183,2.704 -0.087,-0 -0.158,-0.656 -0.158,-1.458zM256.203,271.045c0.282,-0.114 0.742,-0.114 1.024,-0 0.282,0.114 0.051,0.207 -0.512,0.207 -0.563,-0 -0.793,-0.093 -0.512,-0.207zM258.353,270.752c0,-0.085 0.323,-0.408 0.717,-0.717 0.65,-0.509 0.664,-0.495 0.155,0.155 -0.535,0.682 -0.871,0.899 -0.871,0.562zM253.746,266.522c0.507,-0.098 1.336,-0.098 1.843,-0 0.507,0.098 0.092,0.177 -0.921,0.177 -1.014,-0 -1.428,-0.08 -0.921,-0.177zM262.953,256.163c0,-0.788 0.084,-1.111 0.187,-0.717 0.103,0.394 0.103,1.039 0,1.433 -0.103,0.394 -0.187,0.072 -0.187,-0.717z" android:strokeWidth="2.50943"/>
+    <path android:fillColor="#fb4f02"
+        android:pathData="m240.419,339.853c-3.201,-1.591 -3.892,-3.868 -3.699,-12.194 0.159,-6.865 0.503,-8.021 2.891,-9.724l1.337,-0.954 22.08,-0.113 22.08,-0.113 1.56,1.033c2.764,1.829 3.017,2.739 3.017,10.865 0,7.984 -0.242,8.924 -2.758,10.718l-1.337,0.954 -21.911,0.099c-19.59,0.088 -22.054,0.028 -23.259,-0.571zM274.743,335.406c1.786,-0.924 2.04,-1.763 2.04,-6.753 0,-7.503 0.241,-7.372 -13.515,-7.372 -13.776,-0 -13.521,-0.14 -13.506,7.447 0.014,7.366 -0.166,7.268 13.424,7.284 8.459,0.01 10.581,-0.101 11.558,-0.606z" android:strokeWidth="2.50943"/>
+    <path android:fillColor="#0276cb"
+        android:pathData="m262.196,375.239c-10.425,-11.902 -22.682,-27.444 -22.682,-28.763 0,-0.117 10.505,-0.213 23.344,-0.213l23.344,-0l0,-3.099c0,-2.994 0.047,-3.144 1.393,-4.413 2.002,-1.889 2.389,-3.822 2.221,-11.093 -0.161,-6.954 -0.539,-8.171 -3.026,-9.761 -1.403,-0.897 -1.428,-0.961 -1.168,-2.964 0.781,-6.011 4.082,-13.816 10.844,-25.641 3.856,-6.744 6.667,-12.421 7.177,-14.495l0.302,-1.229 -1.023,1.229c-0.563,0.676 -2.004,2.703 -3.203,4.505 -9.492,14.265 -14.606,18.102 -20.394,15.3 -2.299,-1.113 -4.322,-3.131 -5.539,-5.527l-0.953,-1.875 -3.042,-0.102c-4.508,-0.151 -7.967,-2.022 -10.248,-5.542 -0.73,-1.127 -1.349,-2.049 -1.375,-2.049 -0.026,-0 0.007,4.193 0.074,9.317l0.121,9.317 -4.322,-12.286c-2.377,-6.758 -4.489,-12.345 -4.693,-12.416 -0.204,-0.071 -1.471,-0.84 -2.816,-1.71 -4.29,-2.773 -7.666,-8.132 -6.106,-9.692 0.584,-0.584 1.239,-0.526 7.171,0.632 7.945,1.552 9.531,1.591 11.749,0.291 4.453,-2.61 4.971,-9.515 0.993,-13.222 -0.529,-0.493 -3.296,-2.024 -6.149,-3.403 -3.738,-1.807 -6.064,-3.255 -8.324,-5.183 -1.725,-1.472 -3.049,-2.763 -2.942,-2.87 0.107,-0.107 3.36,1.119 7.229,2.724 3.869,1.605 7.188,2.861 7.375,2.792 0.398,-0.148 10.644,-13.396 10.949,-14.158 0.114,-0.284 -0.044,-0.834 -0.351,-1.221 -0.307,-0.387 0.197,-0.115 1.12,0.604 0.923,0.719 1.865,1.545 2.094,1.835 0.309,0.391 -1.051,2.409 -5.264,7.81l-5.681,7.283 1.627,2.155c2.113,2.798 3.304,5.54 3.583,8.243 0.43,4.169 -2.327,8.052 -6.801,9.577 -1.975,0.673 -6.749,0.592 -8.513,-0.145 -1.847,-0.772 -2.155,-0.508 -1.087,0.934 2.054,2.772 6.245,4.934 8.477,4.374 0.706,-0.177 4.32,-4.615 12.404,-15.235 6.274,-8.241 11.54,-15.184 11.703,-15.427 0.186,-0.279 0.815,0.088 1.7,0.991l1.404,1.433 -5.94,7.986c-3.267,4.392 -8.547,11.488 -11.734,15.768 -5.704,7.661 -5.794,7.814 -5.794,9.901 0,2.45 0.802,3.786 3.354,5.592 1.942,1.374 6.004,1.957 7.148,1.026 0.37,-0.301 5.853,-7.258 12.184,-15.461 9.256,-11.991 11.511,-15.156 11.511,-16.153l0,-1.24l2.002,2.228c1.697,1.888 1.888,2.259 1.256,2.433 -1.133,0.311 -21.618,27.334 -22.121,29.18 -1.06,3.894 2.404,8.089 6.678,8.089 3.103,-0 4.128,-1.01 13.208,-13.006l8.6,-11.362l0,-2.868c0,-2.275 -0.226,-3.333 -1.096,-5.119 -0.995,-2.044 0.164,-0.971 12.604,11.673 7.535,7.659 18.245,18.59 23.8,24.292l13.475,14.878c-5.84,15.359 -12.375,30.53 -26.942,43.512 -13.179,12.611 -29.511,20.35 -48.477,24.021l-4.891,0.607c-8.074,1.002 -7.284,1.248 -11.523,-3.591zM252.005,335.55c-1.951,-1.073 -2.234,-1.932 -2.243,-6.821 -0.015,-7.588 -0.27,-7.447 13.506,-7.447 13.756,-0 13.515,-0.131 13.515,7.372 0,7.512 0.252,7.376 -13.597,7.359 -6.742,-0.008 -10.648,-0.17 -11.181,-0.463zM242.381,237.324c0,-0.225 0.195,-0.41 0.434,-0.41 0.239,-0 0.32,0.184 0.181,0.41 -0.139,0.225 -0.334,0.41 -0.434,0.41 -0.099,-0 -0.181,-0.184 -0.181,-0.41z" android:strokeWidth="2.50943"/>
+</vector>
diff --git a/app/src/banglejs/res/values/strings.xml b/app/src/banglejs/res/values/strings.xml
new file mode 100644
index 0000000..2756be0
--- /dev/null
+++ b/app/src/banglejs/res/values/strings.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <string translatable="false" name="pebble_content_provider">com.getpebble.android.nopebble.bangle.provider</string>
+    <string translatable="false" name="app_name">@string/application_name_banglejs_main</string>
+    <string translatable="false" name="title_activity_controlcenter">@string/title_activity_controlcenter_banglejs_main</string>
+    <string translatable="false" name="about_activity_title">@string/about_activity_title_banglejs_main</string>
+    <string translatable="false" name="about_description">@string/about_description_banglejs_main</string>
+    <string translatable="false" name="gadgetbridge_running">@string/gadgetbridge_running_banglejs_main</string>
+</resources>
diff --git a/app/src/banglejsNightly/res/values/strings.xml b/app/src/banglejsNightly/res/values/strings.xml
new file mode 100644
index 0000000..bec569b
--- /dev/null
+++ b/app/src/banglejsNightly/res/values/strings.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <!-- enable Pebble content provider for Bangle.js nightly, so can that it can be installed besides regular Bangle.js release -->
+    <string translatable="false" name="pebble_content_provider">com.getpebble.android.provider</string>
+    <string translatable="false" name="app_name">@string/application_name_banglejs_nightly</string>
+    <string translatable="false" name="title_activity_controlcenter">@string/title_activity_controlcenter_banglejs_nightly</string>
+    <string translatable="false" name="about_activity_title">@string/about_activity_title_banglejs_nightly</string>
+    <string translatable="false" name="about_description">@string/about_description_banglejs_nightly</string>
+    <string translatable="false" name="gadgetbridge_running">@string/gadgetbridge_running_banglejs_nightly</string>
+</resources>
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..8a5579d
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,947 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools">
+
+    <!-- 鍒嗘嫞鍛樹笟鍔℃帴鍙c�佸仴搴蜂笌浣嶇疆涓婃姤 -->
+    <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+    <!-- Used for Bluetooth access -->
+    <!-- Request legacy Bluetooth permissions on older devices. -->
+    <uses-permission android:name="android.permission.BLUETOOTH"
+        android:maxSdkVersion="30"/>
+    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"
+        android:maxSdkVersion="30"/>
+    <!-- Needed to looks for Bluetooth devices. -->
+    <uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
+    <!-- Needed to communicate with already-paired Bluetooth devices. -->
+    <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
+    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+
+    <!-- Since Android 10 -->
+    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+    <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
+
+    <uses-permission android:name="android.permission.READ_CALL_LOG" />
+    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
+    <uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS" />
+    <uses-permission android:name="android.permission.ACCESS_NOTIFICATION_POLICY" />
+    <uses-permission android:name="android.permission.CALL_PHONE" />
+    <uses-permission android:name="android.permission.ANSWER_PHONE_CALLS" />
+    <uses-permission android:name="android.permission.RECEIVE_SMS" />
+    <uses-permission android:name="android.permission.SEND_SMS" />
+    <uses-permission android:name="android.permission.READ_CONTACTS" />
+
+    <!-- Schedule exact alarms (eg. for DST changes) -->
+    <uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
+
+    <!-- Take wake locks (e.g. for time sync) -->
+    <uses-permission android:name="android.permission.WAKE_LOCK" />
+
+    <!-- Read loyalty cards from Catima -->
+    <uses-permission android:name="me.hackerchick.catima.READ_CARDS"/>
+    <uses-permission android:name="me.hackerchick.catima.debug.READ_CARDS"/>
+
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
+        android:maxSdkVersion="32" />
+    <uses-permission android:name="android.permission.READ_CALENDAR" />
+    <uses-permission
+        android:name="android.permission.MEDIA_CONTENT_CONTROL"
+        tools:ignore="ProtectedPermissions" />
+
+    <!--
+        Allow Gadgetbridge to run Termux commands. Permission must be granted manually through the
+        permissions page in Android settings.
+        More information: https://github.com/termux/termux-app/wiki/RUN_COMMAND-Intent
+    -->
+    <uses-permission android:name="com.termux.permission.RUN_COMMAND"/>
+
+    <!-- Used for background service -->
+    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE" />
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
+    <uses-permission android:name="android.permission.REQUEST_COMPANION_RUN_IN_BACKGROUND" />
+
+    <!-- Used for reverse find device -->
+    <uses-permission android:name="android.permission.VIBRATE" />
+
+    <!-- Used for weather provider access -->
+    <uses-permission android:name="cyanogenmod.permission.ACCESS_WEATHER_MANAGER" />
+    <uses-permission android:name="cyanogenmod.permission.READ_WEATHER" />
+    <uses-permission android:name="lineageos.permission.ACCESS_WEATHER_MANAGER" />
+    <uses-permission android:name="lineageos.permission.READ_WEATHER" />
+    <uses-permission android:name="org.omnirom.omnijaws.READ_WEATHER" />
+
+    <!-- Used for starting activities from the background with intents -->
+    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
+
+    <uses-permission android:name="android.permission.CAMERA"/>
+
+    <!--
+    SDK 30 & Android 11 - Used for getting app name from notifications, and for starting
+    services from other packages via intents, when targeting Android API level 30 or later
+    on devices running Android 11 or later.
+    https://developer.android.com/training/package-visibility
+    https://support.google.com/googleplay/android-developer/answer/10158779?hl=en#zippy=%2Cpermitted-uses-of-the-query-all-packages-permission%2Cexceptions
+    -->
+    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"
+        tools:ignore="QueryAllPackagesPermission" />
+
+    <!-- Needed to show notification with targetSdkVersion 33 with Android 13 -->
+    <uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
+
+    <uses-feature
+        android:name="android.hardware.bluetooth"
+        android:required="true" />
+    <uses-feature
+        android:name="android.hardware.bluetooth_le"
+        android:required="false" />
+    <uses-feature
+        android:name="android.hardware.telephony"
+        android:required="false" />
+    <uses-feature
+        android:name="android.software.companion_device_setup"
+        android:required="false" />
+    <uses-feature
+        android:name="android.hardware.camera"
+        android:required="false" />
+
+    <application
+        android:name=".GBApplication"
+        android:allowBackup="false"
+        android:supportsRtl="true"
+        android:fullBackupContent="false"
+        android:icon="@mipmap/ic_launcher"
+        android:label="@string/app_name"
+        android:roundIcon="@mipmap/ic_launcher_round"
+        android:requestLegacyExternalStorage="true"
+        android:theme="@style/GadgetbridgeTheme"
+        android:usesCleartextTraffic="true"
+        android:networkSecurityConfig="@xml/network_security_config"
+        tools:replace="android:label">
+        <receiver
+            android:name=".externalevents.sleepasandroid.SleepAsAndroidReceiver"
+            android:enabled="true"
+            android:exported="true" >
+            <intent-filter>
+                <action android:name="com.urbandroid.sleep.watch.START_TRACKING" />
+                <action android:name="com.urbandroid.sleep.watch.STOP_TRACKING" />
+                <action android:name="com.urbandroid.sleep.watch.SET_PAUSE" />
+                <action android:name="com.urbandroid.sleep.watch.SET_SUSPENDED" />
+                <action android:name="com.urbandroid.sleep.watch.SET_BATCH_SIZE" />
+                <action android:name="com.urbandroid.sleep.watch.START_ALARM" />
+                <action android:name="com.urbandroid.sleep.watch.STOP_ALARM" />
+                <action android:name="com.urbandroid.sleep.watch.UPDATE_ALARM" />
+                <action android:name="com.urbandroid.sleep.watch.SHOW_NOTIFICATION" />
+                <action android:name="com.urbandroid.sleep.watch.HINT" />
+                <action android:name="com.urbandroid.sleep.watch.CHECK_CONNECTED" />
+            </intent-filter>
+        </receiver>
+        <activity
+            android:name=".activities.ControlCenterv2"
+            android:label="@string/title_activity_controlcenter"
+            android:theme="@style/SplashTheme"
+            android:launchMode="singleTop"
+            android:exported="true">
+            <intent-filter>
+                <category android:name="android.intent.category.DEFAULT" />
+
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+        <activity
+            android:name=".womp.WompWebActivity"
+            android:label="@string/womp_workbench"
+            android:exported="false" />
+        <activity
+            android:name=".womp.WompScanActivity"
+            android:label="@string/womp_scan"
+            android:screenOrientation="portrait"
+            android:exported="false" />
+        <activity
+            android:name=".activities.welcome.WelcomeActivity"
+            android:label="@string/first_start_welcome_title"
+            android:parentActivityName=".activities.ControlCenterv2" />
+        <activity
+            android:name=".activities.PermissionsActivity"
+            android:label="@string/first_start_permissions_title"
+            android:parentActivityName=".activities.ControlCenterv2" />
+        <activity
+            android:name=".activities.SettingsActivity"
+            android:label="@string/title_activity_settings"
+            android:parentActivityName=".activities.ControlCenterv2" />
+        <activity
+            android:name=".activities.DashboardPreferencesActivity"
+            android:label="@string/dashboard_settings"
+            android:parentActivityName=".activities.SettingsActivity" />
+        <activity
+            android:name=".activities.SleepAsAndroidPreferencesActivity"
+            android:label="@string/sleepasandroid_settings"
+            android:parentActivityName=".activities.SettingsActivity" />
+        <activity
+            android:name=".activities.AboutUserPreferencesActivity"
+            android:label="@string/activity_prefs_about_you"
+            android:parentActivityName=".activities.AboutUserPreferencesActivity" />
+        <activity
+            android:name=".activities.charts.ChartsPreferencesActivity"
+            android:label="@string/activity_prefs_charts"
+            android:parentActivityName=".activities.charts.ChartsPreferencesActivity" />
+        <activity
+            android:name=".activities.files.FileManagerActivity"
+            android:label="@string/activity_data_management_directory_content_title"
+            android:parentActivityName=".activities.DataManagementActivity" />
+        <activity
+            android:name=".activities.BackupRestoreProgressActivity"
+            android:label="@string/activity_db_management_backup_restore_label"
+            android:parentActivityName=".activities.DataManagementActivity" />
+        <activity
+            android:name=".activities.discovery.DiscoveryPairingPreferenceActivity"
+            android:label="@string/activity_prefs_discovery_pairing"
+            android:parentActivityName=".activities.SettingsActivity" />
+        <activity
+            android:name=".activities.loyaltycards.LoyaltyCardsSettingsActivity"
+            android:label="@string/loyalty_cards"
+            android:parentActivityName=".activities.devicesettings.DeviceSettingsActivity" />
+        <activity
+            android:name=".devices.garmin.GarminRealtimeSettingsActivity"
+            android:label="@string/loading"
+            android:parentActivityName=".activities.devicesettings.DeviceSettingsActivity" />
+        <activity
+            android:name=".activities.ActivitySummariesActivity"
+            android:label="@string/activity_summaries"
+            android:parentActivityName=".activities.ControlCenterv2" />
+        <activity
+            android:name=".activities.appmanager.AppManagerActivity"
+            android:label="@string/title_activity_appmanager"
+            android:launchMode="singleTop"
+            android:parentActivityName=".activities.ControlCenterv2" />
+        <activity
+            android:name=".activities.musicmanager.MusicManagerActivity"
+            android:label="@string/title_activity_musicmanager"
+            android:parentActivityName=".activities.SettingsActivity" />
+        <activity
+            android:name=".activities.AppBlacklistActivity"
+            android:label="@string/title_activity_notification_management"
+            android:parentActivityName=".activities.SettingsActivity" />
+        <activity
+            android:name=".activities.CalBlacklistActivity"
+            android:label="@string/title_activity_calblacklist"
+            android:parentActivityName=".activities.SettingsActivity" />
+        <activity
+            android:name=".devices.vesc.VescControlActivity"
+            android:label="@string/devicetype_vesc"
+            android:parentActivityName=".activities.ControlCenterv2" />
+        <activity
+            android:name=".devices.idasen.IdasenControlActivity"
+            android:label="@string/devicetype_idasen"
+            android:parentActivityName=".activities.ControlCenterv2" />
+        <activity
+            android:name=".activities.FwAppInstallerActivity"
+            android:label="@string/title_activity_fw_app_insaller"
+            android:parentActivityName=".activities.ControlCenterv2"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.VIEW" />
+
+                <category android:name="android.intent.category.DEFAULT" />
+
+                <data android:mimeType="*/*" />
+                <!-- needed for aosp-derived ROMs? -->
+
+                <data android:host="*" />
+                <data android:scheme="file" />
+
+                <!-- as seen on openkeychain repo: https://github.com/open-keychain/open-keychain/blob/master/OpenKeychain/src/main/AndroidManifest.xml -->
+
+                <data android:pathPattern="/.*\\.fw" />
+                <data android:pathPattern="/.*\\..*\\.fw" />
+                <data android:pathPattern="/.*\\..*\\..*\\.fw" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\.fw" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\.fw" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\.fw" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\.fw" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.fw" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.fw" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.fw" />
+                <data android:pathPattern="/.*\\.ft.en" />
+                <data android:pathPattern="/.*\\..*\\.ft.en" />
+                <data android:pathPattern="/.*\\..*\\..*\\.ft.en" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\.ft.en" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\.ft.en" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\.ft.en" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\.ft.en" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.ft.en" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.ft.en" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.ft.en" />
+                <data android:pathPattern="/.*\\.ft" />
+                <data android:pathPattern="/.*\\..*\\.ft" />
+                <data android:pathPattern="/.*\\..*\\..*\\.ft" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\.ft" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\.ft" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\.ft" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\.ft" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.ft" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.ft" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.ft" />
+                <data android:pathPattern="/.*\\.res" />
+                <data android:pathPattern="/.*\\..*\\.res" />
+                <data android:pathPattern="/.*\\..*\\..*\\.res" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\.res" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\.res" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\.res" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\.res" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.res" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.res" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.res" />
+                <data android:pathPattern="/.*\\.gps" />
+                <data android:pathPattern="/.*\\..*\\.gps" />
+                <data android:pathPattern="/.*\\..*\\..*\\.gps" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\.gps" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\.gps" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\.gps" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\.gps" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.gps" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.gps" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.gps" />
+                <data android:pathPattern="/.*\\.pbw" />
+                <data android:pathPattern="/.*\\..*\\.pbw" />
+                <data android:pathPattern="/.*\\..*\\..*\\.pbw" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\.pbw" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\.pbw" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\.pbw" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\.pbw" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pbw" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pbw" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pbw" />
+                <data android:pathPattern="/.*\\.pbz" />
+                <data android:pathPattern="/.*\\..*\\.pbz" />
+                <data android:pathPattern="/.*\\..*\\..*\\.pbz" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\.pbz" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\.pbz" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\.pbz" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\.pbz" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pbz" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pbz" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pbz" />
+                <data android:pathPattern="/.*\\.pbl" />
+                <data android:pathPattern="/.*\\..*\\.pbl" />
+                <data android:pathPattern="/.*\\..*\\..*\\.pbl" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\.pbl" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\.pbl" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\.pbl" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\.pbl" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pbl" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pbl" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pbl" />
+            </intent-filter>
+            <intent-filter>
+                <action android:name="android.intent.action.VIEW" />
+
+                <category android:name="android.intent.category.DEFAULT" />
+
+                <!-- no mimeType filter, needed for CM-derived ROMs? -->
+
+                <data android:host="*" />
+                <data android:scheme="file" />
+
+                <!-- as seen on openkeychain repo: https://github.com/open-keychain/open-keychain/blob/master/OpenKeychain/src/main/AndroidManifest.xml -->
+
+                <data android:pathPattern="/.*\\.fw" />
+                <data android:pathPattern="/.*\\..*\\.fw" />
+                <data android:pathPattern="/.*\\..*\\..*\\.fw" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\.fw" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\.fw" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\.fw" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\.fw" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.fw" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.fw" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.fw" />
+                <data android:pathPattern="/.*\\.ft.en" />
+                <data android:pathPattern="/.*\\..*\\.ft.en" />
+                <data android:pathPattern="/.*\\..*\\..*\\.ft.en" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\.ft.en" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\.ft.en" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\.ft.en" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\.ft.en" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.ft.en" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.ft.en" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.ft.en" />
+                <data android:pathPattern="/.*\\.ft" />
+                <data android:pathPattern="/.*\\..*\\.ft" />
+                <data android:pathPattern="/.*\\..*\\..*\\.ft" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\.ft" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\.ft" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\.ft" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\.ft" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.ft" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.ft" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.ft" />
+                <data android:pathPattern="/.*\\.res" />
+                <data android:pathPattern="/.*\\..*\\.res" />
+                <data android:pathPattern="/.*\\..*\\..*\\.res" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\.res" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\.res" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\.res" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\.res" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.res" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.res" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.res" />
+                <data android:pathPattern="/.*\\.gps" />
+                <data android:pathPattern="/.*\\..*\\.gps" />
+                <data android:pathPattern="/.*\\..*\\..*\\.gps" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\.gps" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\.gps" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\.gps" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\.gps" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.gps" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.gps" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.gps" />
+                <data android:pathPattern="/.*\\.pbw" />
+                <data android:pathPattern="/.*\\..*\\.pbw" />
+                <data android:pathPattern="/.*\\..*\\..*\\.pbw" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\.pbw" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\.pbw" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\.pbw" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\.pbw" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pbw" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pbw" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pbw" />
+                <data android:pathPattern="/.*\\.pbz" />
+                <data android:pathPattern="/.*\\..*\\.pbz" />
+                <data android:pathPattern="/.*\\..*\\..*\\.pbz" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\.pbz" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\.pbz" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\.pbz" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\.pbz" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pbz" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pbz" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pbz" />
+                <data android:pathPattern="/.*\\.pbl" />
+                <data android:pathPattern="/.*\\..*\\.pbl" />
+                <data android:pathPattern="/.*\\..*\\..*\\.pbl" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\.pbl" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\.pbl" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\.pbl" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\.pbl" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pbl" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pbl" />
+                <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pbl" />
+            </intent-filter>
+
+            <!-- to receive the firmwares from the download content provider -->
+            <intent-filter>
+                <action android:name="android.intent.action.VIEW" />
+
+                <category android:name="android.intent.category.DEFAULT" />
+
+                <data android:mimeType="application/octet-stream" />
+            </intent-filter>
+
+            <!-- to receive firmwares from the download content provider if recognized as zip -->
+            <intent-filter>
+                <action android:name="android.intent.action.VIEW" />
+
+                <category android:name="android.intent.category.DEFAULT" />
+
+                <data android:mimeType="application/zip" />
+                <data android:mimeType="application/x-zip-compressed" />
+            </intent-filter>
+
+            <!-- to receive files from the "share" intent -->
+            <intent-filter>
+                <action android:name="android.intent.action.SEND" />
+
+                <category android:name="android.intent.category.DEFAULT" />
+
+                <data android:mimeType="*/*" />
+            </intent-filter>
+        </activity>
+        <activity
+            android:exported="false"
+            android:name=".activities.app_specific_notifications.AppSpecificNotificationSettingsActivity"
+            android:label="@string/prefs_wena3_notification_per_app_settings_title"
+            android:parentActivityName=".activities.devicesettings.DeviceSettingsActivity">
+            <intent-filter>
+                <action android:name=".SonyWena3PerAppNotificationSettingsActivity" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+        <activity
+            android:exported="false"
+            android:name=".activities.app_specific_notifications.AppSpecificNotificationSettingsDetailActivity"
+            android:label="@string/prefs_wena3_notification_per_app_settings_title"
+            android:parentActivityName=".activities.app_specific_notifications.AppSpecificNotificationSettingsActivity"
+            />
+
+        <service
+            android:name=".externalevents.NotificationListener"
+            android:label="@string/app_name"
+            android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE"
+            android:exported="false"
+            android:foregroundServiceType="specialUse">
+            <property
+                android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
+                android:value="Bind to notification listener for capturing and forwarding notifications" />
+            <intent-filter>
+                <action android:name="android.service.notification.NotificationListenerService" />
+            </intent-filter>
+        </service>
+        <service
+            android:name=".service.NotificationCollectorMonitorService"
+            android:foregroundServiceType="specialUse">
+            <property
+                android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
+                android:value="Service for capturing and forwarding notifications" />
+        </service>
+        <service
+            android:name=".service.DeviceCommunicationService"
+            android:foregroundServiceType="connectedDevice" />
+        <service
+            android:name=".womp.WompKeepAliveService"
+            android:exported="false"
+            android:foregroundServiceType="location" />
+        <service
+            android:name=".devices.pinetime.PineTimeDFUService"
+            android:label="PineTime Nordic DFU service"
+            android:foregroundServiceType="connectedDevice" />
+
+        <service
+            android:name=".service.btle.BLEScanService"
+            android:enabled="true"
+            android:exported="false"
+            android:foregroundServiceType="connectedDevice" />
+
+        <receiver
+            android:name=".externalevents.GenericWeatherReceiver"
+            android:enabled="true"
+            android:exported="false">
+            <intent-filter>
+                <action android:name="nodomain.freeyourgadget.gadgetbridge.ACTION_GENERIC_WEATHER" />
+            </intent-filter>
+        </receiver>
+
+        <receiver
+            android:name=".externalevents.WeatherNotificationReceiver"
+            android:enabled="true"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="ru.gelin.android.weather.notification.ACTION_WEATHER_UPDATE_2" />
+            </intent-filter>
+        </receiver>
+
+        <activity android:name=".externalevents.WeatherNotificationConfig"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="ru.gelin.android.weather.notification.ACTION_WEATHER_SKIN_PREFERENCES" />
+            </intent-filter>
+        </activity>
+
+        <receiver
+            android:name=".externalevents.AutoStartReceiver"
+            android:permission="android.permission.RECEIVE_BOOT_COMPLETED"
+            android:exported="false">
+            <intent-filter>
+                <action android:name="android.intent.action.BOOT_COMPLETED" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="android.intent.action.MY_PACKAGE_REPLACED"/>
+            </intent-filter>
+        </receiver>
+
+        <receiver
+            android:name=".externalevents.BluetoothStateChangeReceiver"
+            android:exported="true"
+            android:permission="android.permission.BLUETOOTH,android.permission.BLUETOOTH_ADMIN">
+            <intent-filter>
+                <action android:name="android.bluetooth.adapter.action.STATE_CHANGED" />
+                <action android:name="android.bluetooth.adapter.action.CONNECTION_STATE_CHANGED" />
+
+                <action android:name="android.bluetooth.device.action.ACL_CONNECTED" />
+                <action android:name="android.bluetooth.device.action.ACL_DISCONNECTED" />
+                <action android:name="android.bluetooth.device.action.ACL_DISCONNECT_REQUESTED" />
+            </intent-filter>
+        </receiver>
+
+        <receiver
+            android:name=".service.receivers.GBMusicControlReceiver"
+            android:exported="false">
+            <intent-filter>
+                <action android:name="nodomain.freeyourgadget.gadgetbridge.musiccontrol" />
+            </intent-filter>
+        </receiver>
+        <receiver
+            android:name=".service.receivers.GBCallControlReceiver"
+            android:exported="false">
+            <intent-filter>
+                <action android:name="nodomain.freeyourgadget.gadgetbridge.callcontrol" />
+            </intent-filter>
+        </receiver>
+        <receiver
+            android:name=".database.PeriodicExporter"
+            android:enabled="true"
+            android:exported="false" />
+        <!--
+            forcing the DebugActivity to portrait mode avoids crashes with the progress
+            dialog when changing orientation
+        -->
+        <activity
+            android:name=".activities.DebugActivity"
+            android:label="@string/title_activity_debug"
+            android:parentActivityName=".activities.ControlCenterv2"
+            android:screenOrientation="portrait"
+            android:windowSoftInputMode="stateHidden" />
+        <activity
+            android:name=".activities.AboutActivity"
+            android:label="@string/about_activity_title"
+            android:parentActivityName=".activities.ControlCenterv2"
+            android:windowSoftInputMode="stateHidden" />
+        <activity
+            android:name=".activities.OpenFwAppInstallerActivity"
+            android:label="@string/open_fw_installer_info_text_title"
+            android:parentActivityName=".activities.ControlCenterv2"
+            android:windowSoftInputMode="stateHidden" />
+        <activity
+            android:name=".activities.BatteryInfoActivity"
+            android:label="@string/battery_detail_activity_title"
+            android:parentActivityName=".activities.ControlCenterv2" />
+
+        <activity
+            android:name=".activities.ActivitySummaryDetail"
+            android:label="@string/activity_summary_detail"
+            android:parentActivityName=".activities.ActivitySummariesActivity"
+            android:windowSoftInputMode="stateHidden" />
+        <activity
+            android:name=".activities.ActivitySummariesFilter"
+            android:label="@string/activity_filter_filter_title"
+            android:parentActivityName=".activities.ActivitySummariesActivity"
+            android:excludeFromRecents="true"/>
+
+        <activity
+            android:name=".activities.DataManagementActivity"
+            android:label="@string/title_activity_db_management"
+            android:parentActivityName=".activities.ControlCenterv2"
+            android:windowSoftInputMode="stateHidden" />
+        <activity
+            android:name=".activities.NotificationManagementActivity"
+            android:label="@string/title_activity_notification_management"
+            android:parentActivityName=".activities.ControlCenterv2"
+            android:windowSoftInputMode="stateHidden" />
+        <activity
+            android:name=".activities.discovery.DiscoveryActivityV2"
+            android:label="@string/title_activity_discovery"
+            android:parentActivityName=".activities.ControlCenterv2" />
+        <activity
+            android:name=".activities.AndroidPairingActivity"
+            android:label="@string/title_activity_android_pairing" />
+        <activity
+            android:name=".devices.banglejs.AppsManagementActivity"
+            android:label="@string/title_activity_appmanager"
+            android:launchMode="singleTop"
+            android:configChanges="keyboard|keyboardHidden|orientation|screenSize|layoutDirection|uiMode"
+            android:parentActivityName=".activities.ControlCenterv2" />
+        <activity
+            android:name=".devices.miband.MiBandPairingActivity"
+            android:label="@string/title_activity_mi_band_pairing" />
+        <activity
+            android:name=".devices.pebble.PebblePairingActivity"
+            android:label="@string/title_activity_pebble_pairing" />
+        <activity
+            android:name=".devices.watch9.Watch9PairingActivity"
+            android:label="@string/title_activity_watch9_pairing" />
+        <activity
+            android:name=".devices.watch9.Watch9CalibrationActivity"
+            android:label="@string/title_activity_watch9_calibration" />
+        <activity
+            android:name=".devices.lenovo.LenovoWatchPairingActivity"
+            android:label="@string/title_activity_watch9_pairing" />
+        <activity
+            android:name=".devices.lenovo.LenovoWatchCalibrationActivity"
+            android:label="@string/title_activity_LenovoWatch_calibration" />
+        <activity
+            android:name=".activities.charts.ActivityChartsActivity"
+            android:label="@string/title_activity_charts"
+            android:parentActivityName=".activities.ControlCenterv2" />
+        <activity
+            android:name=".activities.ConfigureAlarms"
+            android:label="@string/title_activity_set_alarm"
+            android:parentActivityName=".activities.ControlCenterv2" />
+        <activity
+            android:name=".activities.ConfigureReminders"
+            android:label="@string/title_activity_set_reminders"
+            android:parentActivityName=".activities.ControlCenterv2" />
+        <activity
+            android:name=".activities.ConfigureContacts"
+            android:label="@string/title_activity_set_contacts"
+            android:parentActivityName=".activities.ControlCenterv2" />
+        <activity
+            android:name=".activities.widgets.WidgetScreensListActivity"
+            android:label="@string/menuitem_widgets"
+            android:parentActivityName=".activities.ControlCenterv2" />
+        <activity
+            android:name=".activities.ConfigureWorldClocks"
+            android:label="@string/pref_world_clocks_title"
+            android:parentActivityName=".activities.ControlCenterv2" />
+        <activity
+            android:name=".activities.devicesettings.DeviceSettingsActivity"
+            android:label="@string/title_activity_device_specific_settings"
+            android:parentActivityName=".activities.ControlCenterv2" />
+        <activity
+            android:name=".activities.AlarmDetails"
+            android:label="@string/title_activity_alarm_details"
+            android:parentActivityName=".activities.ConfigureAlarms"
+            android:screenOrientation="portrait" />
+        <activity
+            android:name=".activities.ReminderDetails"
+            android:label="@string/title_activity_reminder_details"
+            android:parentActivityName=".activities.ConfigureReminders"
+            android:screenOrientation="portrait"
+            android:windowSoftInputMode="adjustResize" />
+        <activity
+            android:name=".activities.ContactDetails"
+            android:label="@string/title_activity_contact_details"
+            android:parentActivityName=".activities.ConfigureReminders"
+            android:screenOrientation="portrait"
+            android:windowSoftInputMode="adjustResize" />
+        <activity
+            android:name=".activities.widgets.WidgetScreenDetailsActivity"
+            android:label="@string/widget_screen"
+            android:parentActivityName=".activities.widgets.WidgetScreensListActivity"
+            android:screenOrientation="portrait"
+            android:windowSoftInputMode="adjustResize" />
+        <activity
+            android:name=".activities.WorldClockDetails"
+            android:label="@string/title_activity_world_clock_details"
+            android:parentActivityName=".activities.ConfigureWorldClocks"
+            android:screenOrientation="portrait"
+            android:windowSoftInputMode="adjustResize" />
+        <activity
+            android:name=".activities.VibrationActivity"
+            android:label="@string/title_activity_vibration"
+            android:parentActivityName=".activities.ControlCenterv2" />
+        <activity
+            android:name=".activities.NotificationFilterActivity"
+            android:label="@string/title_activity_notification_filter"
+            android:parentActivityName=".activities.AppBlacklistActivity"
+            android:windowSoftInputMode="stateHidden|adjustPan" />
+        <activity
+            android:name=".activities.FindPhoneActivity"
+            android:label="Find Phone" />
+
+        <provider
+            android:name=".contentprovider.PebbleContentProvider"
+            android:authorities="@string/pebble_content_provider"
+            android:exported="true" />
+
+        <provider
+            android:name="androidx.core.content.FileProvider"
+            android:authorities="${applicationId}.screenshot_provider"
+            android:exported="false"
+            android:grantUriPermissions="true">
+            <meta-data
+                android:name="android.support.FILE_PROVIDER_PATHS"
+                android:resource="@xml/shared_paths" />
+        </provider>
+
+        <receiver
+            android:name=".SleepAlarmWidget"
+            android:label="@string/appwidget_sleep_alarm_widget_label"
+            android:exported="false">
+            <intent-filter>
+                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+                <action android:name="nodomain.freeyourgadget.gadgetbridge.SLEEP_ALARM_WIDGET_CLICK" />
+            </intent-filter>
+
+            <meta-data
+                android:name="android.appwidget.provider"
+                android:resource="@xml/sleep_alarm_widget_info" />
+        </receiver>
+        <receiver
+            android:name=".Widget"
+            android:label="@string/widget_listing_label"
+            android:exported="false">
+            <intent-filter>
+                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+                <action android:name="android.appwidget.action.APPWIDGET_DELETED" />
+                <action android:name="android.appwidget.action.APPWIDGET_DISABLED" />
+                <action android:name="nodomain.freeyourgadget.gadgetbridge.WidgetClick" />
+            </intent-filter>
+
+            <meta-data
+                android:name="android.appwidget.provider"
+                android:resource="@xml/widget_info" />
+        </receiver>
+
+        <activity
+            android:name=".activities.WidgetAlarmsActivity"
+            android:excludeFromRecents="true"
+            android:launchMode="singleInstance"
+            android:theme="@style/Theme.AppCompat.Light.Dialog" />
+
+        <activity android:name=".activities.WidgetConfigurationActivity"
+            android:exported="false">
+            <intent-filter>
+                <action android:name="android.appwidget.action.APPWIDGET_CONFIGURE"/>
+            </intent-filter>
+        </activity>
+
+        <activity android:name=".activities.SleepAlarmWidgetConfigurationActivity"
+            android:exported="false">
+            <intent-filter>
+                <action android:name="android.appwidget.action.APPWIDGET_CONFIGURE"/>
+            </intent-filter>
+        </activity>
+
+        <activity
+            android:name=".activities.ExternalPebbleJSActivity"
+            android:allowTaskReparenting="true"
+            android:clearTaskOnLaunch="true"
+            android:label="@string/app_configure"
+            android:launchMode="singleTask"
+            android:parentActivityName=".activities.appmanager.AppManagerActivity"
+            android:exported="true">
+            <meta-data
+                android:name="android.support.PARENT_ACTIVITY"
+                android:value="nodomain.freeyourgadget.gadgetbridge.activities.ControlCenterv2" />
+
+            <intent-filter>
+                <action android:name="android.intent.action.VIEW" />
+
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.BROWSABLE" />
+
+                <data android:scheme="gadgetbridge" />
+            </intent-filter>
+        </activity>
+
+        <activity
+            android:name=".devices.qhybrid.QHybridConfigActivity"
+            android:label="@string/qhybrid_title_watchface"
+            android:exported="true"
+            android:parentActivityName=".activities.devicesettings.DeviceSettingsActivity" />
+        <activity
+            android:name=".devices.qhybrid.QHybridAppChoserActivity"
+            android:label="@string/qhybrid_title_apps"
+            android:exported="true"
+            android:parentActivityName=".devices.qhybrid.QHybridConfigActivity" />
+        <activity
+            android:name=".devices.qhybrid.HRConfigActivity"
+            android:label="@string/qhybrid_title_watchface"
+            android:exported="true"
+            android:parentActivityName=".activities.ControlCenterv2" />
+        <activity android:name=".devices.qhybrid.WidgetSettingsActivity"
+            android:label="@string/add_widget"
+            android:parentActivityName=".devices.qhybrid.HRConfigActivity" />
+        <activity
+            android:name=".devices.qhybrid.ImageEditActivity"
+            android:label="@string/qhybrid_title_background_image"
+            android:exported="true"
+            android:parentActivityName=".devices.qhybrid.HRConfigActivity" />
+        <activity
+            android:name=".devices.qhybrid.FileManagementActivity"
+            android:label="@string/qhybrid_title_file_management"
+            android:exported="true"
+            android:parentActivityName=".devices.qhybrid.HRConfigActivity"/>
+        <activity
+            android:name=".devices.qhybrid.AppsManagementActivity"
+            android:label="@string/qhybrid_title_apps_management"
+            android:exported="true"
+            android:parentActivityName=".devices.qhybrid.HRConfigActivity" />
+        <activity
+            android:name=".devices.qhybrid.CalibrationActivity"
+            android:label="@string/qhybrid_title_calibration"
+            android:parentActivityName=".devices.qhybrid.HRConfigActivity" />
+        <activity
+            android:name=".devices.withingssteelhr.WithingsCalibrationActivity"
+            android:label="@string/qhybrid_title_calibration"
+            android:parentActivityName=".devices.withingssteelhr.WithingsCalibrationActivity" />
+        <activity
+            android:name=".devices.qhybrid.CommuteActionsActivity"
+            android:label="@string/qhybrid_pref_title_actions"
+            android:parentActivityName=".devices.qhybrid.HRConfigActivity" />
+        <activity
+            android:name=".devices.qhybrid.HybridHRWatchfaceDesignerActivity"
+            android:label="@string/qhybrid_title_watchface_designer"
+            android:screenOrientation="portrait"
+            android:launchMode="singleTop"
+            android:parentActivityName=".activities.appmanager.AppManagerActivity" />
+        <activity
+            android:name=".devices.qhybrid.HybridHRWatchfaceSettingsActivity"
+            android:label="@string/watchface_dialog_title_settings"
+            android:parentActivityName=".devices.qhybrid.HybridHRWatchfaceDesignerActivity" />
+        <activity
+            android:name=".devices.qhybrid.HybridHRWatchfaceWidgetActivity"
+            android:label="@string/watchface_dialog_title_widget"
+            android:parentActivityName=".devices.qhybrid.HybridHRWatchfaceDesignerActivity" />
+
+        <activity
+            android:name=".devices.um25.Activity.DataActivity"
+            android:exported="true" />
+
+        <activity
+            android:name=".devices.supercars.ControlActivity"
+            android:exported="true" />
+
+        <activity
+            android:name=".devices.marstek.SolarEquipmentStatusActivity"
+            android:label="Solar Equipment Status"
+            android:exported="true" />
+
+        <activity
+            android:name=".devices.binary_sensor.activity.DataActivity"
+            android:exported="true" />
+
+        <activity
+            android:name=".activities.GpxReceiverActivity"
+            android:label="@string/gpx_receiver_activity_title"
+            android:screenOrientation="portrait"
+            android:windowSoftInputMode="stateHidden"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.SEND" />
+                <action android:name="android.intent.action.SEND_MULTIPLE" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:mimeType="*/*" />
+            </intent-filter>
+        </activity>
+
+        <activity
+            android:name=".activities.TextReceiverActivity"
+            android:excludeFromRecents="true"
+            android:label="@string/text_receiver_activity_title"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.SEND" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:mimeType="text/plain" />
+            </intent-filter>
+        </activity>
+
+        <activity
+            android:name=".activities.WakeActivity"
+            android:label="WakeActivity">
+        </activity>
+
+        <activity
+            android:name=".externalevents.opentracks.OpenTracksController"
+            android:label="OpenTracks controller and intent receiver"
+            android:exported="true"/>
+
+        <activity
+            android:name=".activities.dashboard.DashboardCalendarActivity"
+            android:label="@string/menuitem_calendar"
+            android:parentActivityName=".activities.ControlCenterv2" />
+
+        <activity
+            android:name=".activities.CameraActivity"
+            android:launchMode="singleInstance"
+            android:exported="false" />
+
+        <activity
+            android:name=".devices.cycling_sensor.activity.CyclingLiveDataActivity"
+            android:launchMode="singleInstance"
+            android:exported="true" />
+    </application>
+
+</manifest>
diff --git a/app/src/main/aidl/com/android/internal/telephony/ITelephony.aidl b/app/src/main/aidl/com/android/internal/telephony/ITelephony.aidl
new file mode 100644
index 0000000..5eeb60e
--- /dev/null
+++ b/app/src/main/aidl/com/android/internal/telephony/ITelephony.aidl
@@ -0,0 +1,8 @@
+package com.android.internal.telephony;
+
+
+interface ITelephony {
+    boolean endCall();
+    void answerRingingCall();
+    void silenceRinger();
+}
diff --git a/app/src/main/aidl/lineageos/weather/ILineageWeatherManager.aidl b/app/src/main/aidl/lineageos/weather/ILineageWeatherManager.aidl
new file mode 100644
index 0000000..d12e8bb
--- /dev/null
+++ b/app/src/main/aidl/lineageos/weather/ILineageWeatherManager.aidl
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2016 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package lineageos.weather;
+
+import lineageos.weather.IWeatherServiceProviderChangeListener;
+import lineageos.weather.RequestInfo;
+
+interface ILineageWeatherManager {
+    oneway void updateWeather(in RequestInfo info);
+    oneway void lookupCity(in RequestInfo info);
+    oneway void registerWeatherServiceProviderChangeListener(
+        in IWeatherServiceProviderChangeListener listener);
+    oneway void unregisterWeatherServiceProviderChangeListener(
+        in IWeatherServiceProviderChangeListener listener);
+    String getActiveWeatherServiceProviderLabel();
+    oneway void cancelRequest(int requestId);
+}
\ No newline at end of file
diff --git a/app/src/main/aidl/lineageos/weather/IRequestInfoListener.aidl b/app/src/main/aidl/lineageos/weather/IRequestInfoListener.aidl
new file mode 100644
index 0000000..11916f3
--- /dev/null
+++ b/app/src/main/aidl/lineageos/weather/IRequestInfoListener.aidl
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2016 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package lineageos.weather;
+
+import lineageos.weather.RequestInfo;
+import lineageos.weather.WeatherInfo;
+import lineageos.weather.WeatherLocation;
+
+import java.util.List;
+
+interface IRequestInfoListener {
+    void onWeatherRequestCompleted(in RequestInfo requestInfo, int status,
+        in WeatherInfo weatherInfo);
+    void onLookupCityRequestCompleted(in RequestInfo requestInfo, int status,
+        in List<WeatherLocation> weatherLocation);
+}
\ No newline at end of file
diff --git a/app/src/main/aidl/lineageos/weather/IWeatherServiceProviderChangeListener.aidl b/app/src/main/aidl/lineageos/weather/IWeatherServiceProviderChangeListener.aidl
new file mode 100644
index 0000000..12ad2ff
--- /dev/null
+++ b/app/src/main/aidl/lineageos/weather/IWeatherServiceProviderChangeListener.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2016 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package lineageos.weather;
+
+/** @hide */
+oneway interface IWeatherServiceProviderChangeListener {
+    void onWeatherServiceProviderChanged(String providerLabel);
+}
\ No newline at end of file
diff --git a/app/src/main/aidl/lineageos/weather/RequestInfo.aidl b/app/src/main/aidl/lineageos/weather/RequestInfo.aidl
new file mode 100644
index 0000000..bdc3ecc
--- /dev/null
+++ b/app/src/main/aidl/lineageos/weather/RequestInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2016 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package lineageos.weather;
+
+parcelable RequestInfo;
\ No newline at end of file
diff --git a/app/src/main/aidl/lineageos/weather/WeatherInfo.aidl b/app/src/main/aidl/lineageos/weather/WeatherInfo.aidl
new file mode 100644
index 0000000..16cbb59
--- /dev/null
+++ b/app/src/main/aidl/lineageos/weather/WeatherInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2016 The CyanongenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package lineageos.weather;
+
+parcelable WeatherInfo;
\ No newline at end of file
diff --git a/app/src/main/aidl/lineageos/weather/WeatherLocation.aidl b/app/src/main/aidl/lineageos/weather/WeatherLocation.aidl
new file mode 100644
index 0000000..d19e8bc
--- /dev/null
+++ b/app/src/main/aidl/lineageos/weather/WeatherLocation.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2016 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package lineageos.weather;
+
+parcelable WeatherLocation;
\ No newline at end of file
diff --git a/app/src/main/aidl/lineageos/weatherservice/IWeatherProviderService.aidl b/app/src/main/aidl/lineageos/weatherservice/IWeatherProviderService.aidl
new file mode 100644
index 0000000..fb3bc5e
--- /dev/null
+++ b/app/src/main/aidl/lineageos/weatherservice/IWeatherProviderService.aidl
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2016 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package lineageos.weatherservice;
+
+import lineageos.weatherservice.IWeatherProviderServiceClient;
+import lineageos.weather.RequestInfo;
+
+interface IWeatherProviderService {
+    void processWeatherUpdateRequest(in RequestInfo request);
+    void processCityNameLookupRequest(in RequestInfo request);
+    void setServiceClient(in IWeatherProviderServiceClient client);
+    void cancelOngoingRequests();
+    void cancelRequest(int requestId);
+}
\ No newline at end of file
diff --git a/app/src/main/aidl/lineageos/weatherservice/IWeatherProviderServiceClient.aidl b/app/src/main/aidl/lineageos/weatherservice/IWeatherProviderServiceClient.aidl
new file mode 100644
index 0000000..2f54edd
--- /dev/null
+++ b/app/src/main/aidl/lineageos/weatherservice/IWeatherProviderServiceClient.aidl
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2016 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package lineageos.weatherservice;
+
+import lineageos.weather.RequestInfo;
+import lineageos.weatherservice.ServiceRequestResult;
+
+interface IWeatherProviderServiceClient {
+    void setServiceRequestState(in RequestInfo requestInfo, in ServiceRequestResult result,
+        int state);
+}
\ No newline at end of file
diff --git a/app/src/main/aidl/lineageos/weatherservice/ServiceRequestResult.aidl b/app/src/main/aidl/lineageos/weatherservice/ServiceRequestResult.aidl
new file mode 100644
index 0000000..ece4f47
--- /dev/null
+++ b/app/src/main/aidl/lineageos/weatherservice/ServiceRequestResult.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2016 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package lineageos.weatherservice;
+
+parcelable ServiceRequestResult;
\ No newline at end of file
diff --git a/app/src/main/aidl/net/osmand/aidlapi/IOsmAndAidlCallback.aidl b/app/src/main/aidl/net/osmand/aidlapi/IOsmAndAidlCallback.aidl
new file mode 100644
index 0000000..0598f31
--- /dev/null
+++ b/app/src/main/aidl/net/osmand/aidlapi/IOsmAndAidlCallback.aidl
@@ -0,0 +1,59 @@
+package net.osmand.aidlapi;
+
+import net.osmand.aidlapi.search.SearchResult;
+import net.osmand.aidlapi.gpx.AGpxBitmap;
+import net.osmand.aidlapi.navigation.ADirectionInfo;
+import net.osmand.aidlapi.navigation.OnVoiceNavigationParams;
+
+interface IOsmAndAidlCallback {
+
+    /**
+     *  Callback for search requests.
+     *
+     *  @return resultSet - set of SearchResult
+     */
+    void onSearchComplete(in List<SearchResult> resultSet);
+
+    /**
+     *  Callback for {@link IOsmAndAidlInterface} registerForUpdates() method.
+     */
+    void onUpdate();
+
+    /**
+     *  Callback for {@link IOsmAndAidlInterface} registerForOsmandInitListener() method.
+     */
+    void onAppInitialized();
+
+    /**
+     *  Callback for {@link IOsmAndAidlInterface} getBitmapForGpx() method.
+     *
+     *  @return bitmap - snapshot image of gpx track on map
+     */
+    void onGpxBitmapCreated(in AGpxBitmap bitmap);
+
+    /**
+     *  Callback for {@link IOsmAndAidlInterface} registerForNavigationUpdates() method.
+     *
+     *  @return directionInfo - update on distance to next turn and turns type.
+     */
+    void updateNavigationInfo(in ADirectionInfo directionInfo);
+
+    /**
+     *  Callback for {@link IOsmAndAidlInterface} buttons set with addContextMenuButtons() method.
+     *
+     *  @param buttonId - id of custom button
+     *  @param pointId - id of point button associated with
+     *  @param layerId - id of layer point and button associated with
+     */
+    void onContextMenuButtonClicked(in int buttonId, String pointId, String layerId);
+    
+    /**
+     *  Callback for {@link IOsmAndAidlInterface} registerForVoiceRouterMessages() method.
+     */
+    void onVoiceRouterNotify(in OnVoiceNavigationParams params);
+
+    /**
+     *  Callback for {@link IOsmAndAidlInterface} registerForKeyEvents() method.
+     */
+    void onKeyEvent(in KeyEvent params);
+}
\ No newline at end of file
diff --git a/app/src/main/aidl/net/osmand/aidlapi/IOsmAndAidlInterface.aidl b/app/src/main/aidl/net/osmand/aidlapi/IOsmAndAidlInterface.aidl
new file mode 100644
index 0000000..c22a9db
--- /dev/null
+++ b/app/src/main/aidl/net/osmand/aidlapi/IOsmAndAidlInterface.aidl
@@ -0,0 +1,25 @@
+package net.osmand.aidlapi;
+
+import net.osmand.aidlapi.navigation.ANavigationUpdateParams;
+import net.osmand.aidlapi.navigation.ANavigationVoiceRouterMessageParams;
+import net.osmand.aidlapi.IOsmAndAidlCallback;
+
+interface IOsmAndAidlInterface {
+    /**
+     * Method to register for updates during navgation. Notifies user about distance to the next turn and its type.
+     *
+     * @param subscribeToUpdates (boolean) - subscribe or unsubscribe from updates
+     * @param callbackId (long) - id of callback, needed to unsubscribe from updates
+     * @param callback (IOsmAndAidlCallback) - callback to notify user on navigation data change
+     */
+    long registerForNavigationUpdates(in ANavigationUpdateParams params, IOsmAndAidlCallback callback) = 65;
+
+    /**
+     * Method to register for Voice Router voice messages during navigation. Notifies user about voice messages.
+     *
+     * @params subscribeToUpdates (boolean) - boolean flag to subscribe or unsubscribe from messages
+     * @params callbackId (long) - id of callback, needed to unsubscribe from messages
+     * @params callback (IOsmAndAidlCallback) - callback to notify user on voice message
+     */
+    long registerForVoiceRouterMessages(in ANavigationVoiceRouterMessageParams params, IOsmAndAidlCallback callback) = 71;
+}
diff --git a/app/src/main/aidl/net/osmand/aidlapi/gpx/AGpxBitmap.aidl b/app/src/main/aidl/net/osmand/aidlapi/gpx/AGpxBitmap.aidl
new file mode 100644
index 0000000..abbc036
--- /dev/null
+++ b/app/src/main/aidl/net/osmand/aidlapi/gpx/AGpxBitmap.aidl
@@ -0,0 +1,3 @@
+package net.osmand.aidlapi.gpx;
+
+parcelable AGpxBitmap;
\ No newline at end of file
diff --git a/app/src/main/aidl/net/osmand/aidlapi/navigation/ADirectionInfo.aidl b/app/src/main/aidl/net/osmand/aidlapi/navigation/ADirectionInfo.aidl
new file mode 100644
index 0000000..19d7a7e
--- /dev/null
+++ b/app/src/main/aidl/net/osmand/aidlapi/navigation/ADirectionInfo.aidl
@@ -0,0 +1,3 @@
+package net.osmand.aidlapi.navigation;
+
+parcelable ADirectionInfo;
diff --git a/app/src/main/aidl/net/osmand/aidlapi/navigation/ANavigationUpdateParams.aidl b/app/src/main/aidl/net/osmand/aidlapi/navigation/ANavigationUpdateParams.aidl
new file mode 100644
index 0000000..ccdbc4b
--- /dev/null
+++ b/app/src/main/aidl/net/osmand/aidlapi/navigation/ANavigationUpdateParams.aidl
@@ -0,0 +1,4 @@
+
+package net.osmand.aidlapi.navigation;
+
+parcelable ANavigationUpdateParams;
\ No newline at end of file
diff --git a/app/src/main/aidl/net/osmand/aidlapi/navigation/ANavigationVoiceRouterMessageParams.aidl b/app/src/main/aidl/net/osmand/aidlapi/navigation/ANavigationVoiceRouterMessageParams.aidl
new file mode 100644
index 0000000..84fd5c2
--- /dev/null
+++ b/app/src/main/aidl/net/osmand/aidlapi/navigation/ANavigationVoiceRouterMessageParams.aidl
@@ -0,0 +1,3 @@
+package net.osmand.aidlapi.navigation;
+
+parcelable ANavigationVoiceRouterMessageParams;
diff --git a/app/src/main/aidl/net/osmand/aidlapi/navigation/OnVoiceNavigationParams.aidl b/app/src/main/aidl/net/osmand/aidlapi/navigation/OnVoiceNavigationParams.aidl
new file mode 100644
index 0000000..107f69c
--- /dev/null
+++ b/app/src/main/aidl/net/osmand/aidlapi/navigation/OnVoiceNavigationParams.aidl
@@ -0,0 +1,3 @@
+package net.osmand.aidlapi.navigation;
+
+parcelable OnVoiceNavigationParams;
\ No newline at end of file
diff --git a/app/src/main/aidl/net/osmand/aidlapi/search/SearchResult.aidl b/app/src/main/aidl/net/osmand/aidlapi/search/SearchResult.aidl
new file mode 100644
index 0000000..54b7487
--- /dev/null
+++ b/app/src/main/aidl/net/osmand/aidlapi/search/SearchResult.aidl
@@ -0,0 +1,3 @@
+package net.osmand.aidlapi.search;
+
+parcelable SearchResult;
\ No newline at end of file
diff --git a/app/src/main/assets/activity.svg b/app/src/main/assets/activity.svg
new file mode 100644
index 0000000..fe5a484
--- /dev/null
+++ b/app/src/main/assets/activity.svg
@@ -0,0 +1,83 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="24"
+   height="24"
+   tint="#7E7E7E"
+   viewportWidth="25"
+   viewportHeight="25"
+   version="1.1"
+   id="svg1967"
+   sodipodi:docname="activity.svg"
+   inkscape:version="1.0.1 (3bc2e813f5, 2020-09-07)">
+  <metadata
+     id="metadata1973">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs1971" />
+  <sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="1920"
+     inkscape:window-height="1042"
+     id="namedview1969"
+     showgrid="false"
+     showguides="true"
+     inkscape:guide-bbox="true"
+     inkscape:zoom="20.08"
+     inkscape:cx="19.684708"
+     inkscape:cy="14.278117"
+     inkscape:window-x="0"
+     inkscape:window-y="38"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="svg1967"
+     inkscape:document-rotation="0">
+    <sodipodi:guide
+       position="20.217787,166.09221"
+       orientation="0,-1"
+       id="guide2551" />
+    <sodipodi:guide
+       position="8.775067,164.28813"
+       orientation="1,0"
+       id="guide2553" />
+  </sodipodi:namedview>
+  <g
+     id="g5608"
+     transform="matrix(0.8841733,0,0,0.8841733,1.3899206,1.3899206)"
+     style="stroke-width:1.131">
+    <path
+       fill="#000000"
+       d="m 12,18 a 6,6 0 0 1 -6,-6 6,6 0 0 1 6,-6 6,6 0 0 1 6,6 6,6 0 0 1 -6,6 M 20,15.31 23.31,12 20,8.69 V 4 H 15.31 L 12,0.69 8.69,4 H 4 V 8.69 L 0.69,12 4,15.31 V 20 H 8.69 L 12,23.31 15.31,20 H 20 Z"
+       id="path1965"
+       style="stroke-width:1.95392" />
+    <path
+       fill="currentColor"
+       d="m 4.4762317,12.388718 h 3.561751 l 2.0837443,-5.0286723 1.932068,5.4893343 1.823648,-2.726785 h 6.48905 l -0.488068,1.38717 H 14.61543 l -2.938854,4.509156 -1.60202,-4.54569 -0.9505983,2.403348 -3.506487,-4.01e-4 z"
+       id="path2361"
+       sodipodi:nodetypes="ccccccccccccc"
+       style="stroke-width:1.131"
+       inkscape:transform-center-x="1.0957646"
+       inkscape:transform-center-y="-1.4792654" />
+  </g>
+</svg>
diff --git a/app/src/main/assets/activity_stacked.svg b/app/src/main/assets/activity_stacked.svg
new file mode 100644
index 0000000..882ecdd
--- /dev/null
+++ b/app/src/main/assets/activity_stacked.svg
@@ -0,0 +1,102 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="26"
+   height="26"
+   tint="#7E7E7E"
+   viewportWidth="25"
+   viewportHeight="25"
+   version="1.1"
+   id="svg1967"
+   sodipodi:docname="activity_stacked.svg"
+   inkscape:version="1.0.1 (3bc2e813f5, 2020-09-07)">
+  <metadata
+     id="metadata1973">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs1971" />
+  <sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="1920"
+     inkscape:window-height="1042"
+     id="namedview1969"
+     showgrid="false"
+     showguides="true"
+     inkscape:guide-bbox="true"
+     inkscape:zoom="16"
+     inkscape:cx="1.5628297"
+     inkscape:cy="3.351769"
+     inkscape:window-x="0"
+     inkscape:window-y="38"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="g5608"
+     inkscape:document-rotation="0"
+     inkscape:snap-nodes="false">
+    <sodipodi:guide
+       position="18.131447,189.15957"
+       orientation="0,-1"
+       id="guide2551" />
+    <sodipodi:guide
+       position="6.8118842,20.241473"
+       orientation="1,0"
+       id="guide2553" />
+  </sodipodi:namedview>
+  <g
+     id="g5608"
+     transform="matrix(0.6107681,0,0,0.6107681,-0.50776959,-2.4214301)"
+     style="stroke-width:1.63728">
+    <g
+       id="g2806"
+       transform="matrix(1.8819341,0,-1.0509142,4.0780968,7.7001407,-9.1852646)"
+       style="stroke-width:0.591009">
+      <g
+         id="g2123"
+         transform="matrix(1,0,0,0.33594705,0,3.7327618)"
+         style="stroke-width:1.01966;stroke-miterlimit:4;stroke-dasharray:none">
+        <path
+           fill="#000000"
+           d="m 12,18 a 6,6 0 0 1 -6,-6 6,6 0 0 1 6,-6 6,6 0 0 1 6,6 6,6 0 0 1 -6,6 M 20,15.31 23.31,12 20,8.69 V 4 H 15.31 L 12,0.69 8.69,4 H 4 V 8.69 L 0.69,12 4,15.31 V 20 H 8.69 L 12,23.31 15.31,20 H 20 Z"
+           id="path1965"
+           style="stroke-width:1.01966;stroke-miterlimit:4;stroke-dasharray:none" />
+        <path
+           fill="currentColor"
+           d="m 4.4762317,12.388718 h 3.561751 l 2.0837443,-5.0286723 1.932068,5.4893343 1.823648,-2.726785 h 6.48905 l -0.488068,1.38717 H 14.61543 l -2.938854,4.509156 -1.60202,-4.54569 -0.9505983,2.403348 -3.506487,-4.01e-4 z"
+           id="path2361"
+           sodipodi:nodetypes="ccccccccccccc"
+           style="stroke-width:1.01966;stroke-miterlimit:4;stroke-dasharray:none"
+           inkscape:transform-center-x="1.0957646"
+           inkscape:transform-center-y="-1.4792654" />
+      </g>
+      <path
+         style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-variant-east-asian:normal;font-feature-settings:normal;font-variation-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;shape-margin:0;inline-size:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.591009;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate;stop-color:#000000"
+         d="m 3.9815232,10.751128 v 0.490234 h 4.6484375 l 3.3515623,1.126953 3.349609,-1.126953 h 4.650391 v -0.490234 h -4.730469 l -3.269531,1.097656 -3.2714842,-1.097656 z"
+         id="path2783" />
+      <path
+         style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-variant-east-asian:normal;font-feature-settings:normal;font-variation-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;shape-margin:0;inline-size:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.591009;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate;stop-color:#000000"
+         d="m 3.9892109,11.549912 v 0.490234 h 4.6503907 l 3.3496094,1.126953 3.349609,-1.126953 h 4.650391 v -0.490234 h -4.730469 l -3.269531,1.097656 -3.2695313,-1.097656 z"
+         id="path2787" />
+    </g>
+  </g>
+</svg>
diff --git a/app/src/main/assets/app_config/configure.html b/app/src/main/assets/app_config/configure.html
new file mode 100644
index 0000000..a27544b
--- /dev/null
+++ b/app/src/main/assets/app_config/configure.html
@@ -0,0 +1,102 @@
+<!DOCTYPE html>
+<head>
+    <meta charset="utf-8"/>
+    <meta name='viewport' content='initial-scale=1.0, maximum-scale=1.0'>
+    <script type="text/javascript" src="js/Uri.js">
+    </script>
+    <script type="text/javascript" src="js/gadgetbridge_boilerplate.js">
+    </script>
+    <script type="text/javascript">
+    </script>
+    <style>
+        html, body {
+            width: 100%;
+            height: 100%;
+            margin: 0;
+            padding: 0;
+        }
+        iframe {
+            display: block;
+            width: 100%;
+            height: 100%;
+            border: none;
+        }
+        body {
+        -webkit-user-select: none;
+        -moz-user-select: none;
+        -ms-user-select: none;
+        user-select: none;
+        -webkit-tap-highlight-color: rgba(0,0,0,0);
+        -webkit-touch-callout: none;
+        }
+        #config_url,#jsondata {
+        word-wrap: break-word;
+        margin: 20px 0;
+        width: 90%;
+        }
+        .btn {
+        display: inline-block;
+        position: relative;
+        height: 32px;
+        line-height: 32px;
+        border-radius: 2px;
+        font-size: 0.9em;
+        background-color: #eee;
+        color: #646464;
+        text-align: center;
+        border-style: none;
+        }
+        .btn:active {
+        border-style: none;
+        box-shadow: 0 8px 17px 0 rgba(0, 0, 0, 0.2)inset;
+        transition-delay: 0s;
+        }
+        p {
+        width: 90%;
+        }
+        #pastereturn {
+        width: 90%;
+        min-height: 3em;
+        }
+        #step1compat, #step2 {
+        display: none;
+        }
+        <!-- TODO -->
+    </style>
+</head>
+<body>
+<div id="step1" class="step">
+    <h2>URL of the configuration:</h2>
+    <div id="config_url"></div>
+    <!--<button class="btn" name="show config" value="show config" onclick="Pebble.showConfiguration()" >Show config / URL</button>-->
+    <button class="btn" name="open config" value="open config" onclick="Pebble.actuallyOpenURL()">
+        Open configuration website
+    </button>
+    <h2 class="load_presets">App presets:</h2>
+    <button class="btn load_presets" name="read config" value="read config"
+            onclick="Pebble.loadPreset()">
+        Load saved configuration
+    </button>
+</div>
+<div id="step1compat" class="step">
+    <p>In case of "network error" after saving settings in the watchapp, copy the "network error"
+        URL and paste it here:</p>
+    <textarea id="pastereturn"></textarea><br/>
+    <button class="btn" name="parse" onclick="Pebble.parseReturnedPebbleJS()">Parse legacy app
+        configuration
+    </button>
+</div>
+<div id="step2" class="step">
+    <h2>Incoming configuration data:</h2>
+    <div id="jsondata"></div>
+    <button class="btn" name="send config" value="send config" onclick="Pebble.sendConfiguration()">
+        Send data to pebble
+    </button>
+    <h2 class="store_presets">App Presets:</h2>
+    <button class="btn store_presets" name="store config" value="store config"
+            onclick="Pebble.savePreset()">
+        Store incoming configuration
+    </button>
+    <p class="store_presets">Existing presets will be deleted.</p>
+</div>
+</body>
diff --git a/app/src/main/assets/app_config/js/Uri.js b/app/src/main/assets/app_config/js/Uri.js
new file mode 100644
index 0000000..a3fe499
--- /dev/null
+++ b/app/src/main/assets/app_config/js/Uri.js
@@ -0,0 +1,458 @@
+/*!
+ * jsUri
+ * https://github.com/derek-watson/jsUri
+ *
+ * Copyright 2013, Derek Watson
+ * Released under the MIT license.
+ *
+ * Includes parseUri regular expressions
+ * http://blog.stevenlevithan.com/archives/parseuri
+ * Copyright 2007, Steven Levithan
+ * Released under the MIT license.
+ */
+
+ /*globals define, module */
+
+(function(global) {
+
+  var re = {
+    starts_with_slashes: /^\/+/,
+    ends_with_slashes: /\/+$/,
+    pluses: /\+/g,
+    query_separator: /[&;]/,
+    uri_parser: /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@\/]*)(?::([^:@\/]*))?)?@)?(\[[0-9a-fA-F:.]+\]|[^:\/?#]*)(?::(\d+|(?=:)))?(:)?)((((?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/
+  };
+
+  /**
+   * Define forEach for older js environments
+   * @see https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/forEach#Compatibility
+   */
+  if (!Array.prototype.forEach) {
+    Array.prototype.forEach = function(callback, thisArg) {
+      var T, k;
+
+      if (this == null) {
+        throw new TypeError(' this is null or not defined');
+      }
+
+      var O = Object(this);
+      var len = O.length >>> 0;
+
+      if (typeof callback !== "function") {
+        throw new TypeError(callback + ' is not a function');
+      }
+
+      if (arguments.length > 1) {
+        T = thisArg;
+      }
+
+      k = 0;
+
+      while (k < len) {
+        var kValue;
+        if (k in O) {
+          kValue = O[k];
+          callback.call(T, kValue, k, O);
+        }
+        k++;
+      }
+    };
+  }
+
+  /**
+   * unescape a query param value
+   * @param  {string} s encoded value
+   * @return {string}   decoded value
+   */
+  function decode(s) {
+    if (s) {
+        s = s.toString().replace(re.pluses, '%20');
+        s = decodeURIComponent(s);
+    }
+    return s;
+  }
+
+  /**
+   * Breaks a uri string down into its individual parts
+   * @param  {string} str uri
+   * @return {object}     parts
+   */
+  function parseUri(str) {
+    var parser = re.uri_parser;
+    var parserKeys = ["source", "protocol", "authority", "userInfo", "user", "password", "host", "port", "isColonUri", "relative", "path", "directory", "file", "query", "anchor"];
+    var m = parser.exec(str || '');
+    var parts = {};
+
+    parserKeys.forEach(function(key, i) {
+      parts[key] = m[i] || '';
+    });
+
+    return parts;
+  }
+
+  /**
+   * Breaks a query string down into an array of key/value pairs
+   * @param  {string} str query
+   * @return {array}      array of arrays (key/value pairs)
+   */
+  function parseQuery(str) {
+    var i, ps, p, n, k, v, l;
+    var pairs = [];
+
+    if (typeof(str) === 'undefined' || str === null || str === '') {
+      return pairs;
+    }
+
+    if (str.indexOf('?') === 0) {
+      str = str.substring(1);
+    }
+
+    ps = str.toString().split(re.query_separator);
+
+    for (i = 0, l = ps.length; i < l; i++) {
+      p = ps[i];
+      n = p.indexOf('=');
+
+      if (n !== 0) {
+        k = decode(p.substring(0, n));
+        v = decode(p.substring(n + 1));
+        pairs.push(n === -1 ? [p, null] : [k, v]);
+      }
+
+    }
+    return pairs;
+  }
+
+  /**
+   * Creates a new Uri object
+   * @constructor
+   * @param {string} str
+   */
+  function Uri(str) {
+    this.uriParts = parseUri(str);
+    this.queryPairs = parseQuery(this.uriParts.query);
+    this.hasAuthorityPrefixUserPref = null;
+  }
+
+  /**
+   * Define getter/setter methods
+   */
+  ['protocol', 'userInfo', 'host', 'port', 'path', 'anchor'].forEach(function(key) {
+    Uri.prototype[key] = function(val) {
+      if (typeof val !== 'undefined') {
+        this.uriParts[key] = val;
+      }
+      return this.uriParts[key];
+    };
+  });
+
+  /**
+   * if there is no protocol, the leading // can be enabled or disabled
+   * @param  {Boolean}  val
+   * @return {Boolean}
+   */
+  Uri.prototype.hasAuthorityPrefix = function(val) {
+    if (typeof val !== 'undefined') {
+      this.hasAuthorityPrefixUserPref = val;
+    }
+
+    if (this.hasAuthorityPrefixUserPref === null) {
+      return (this.uriParts.source.indexOf('//') !== -1);
+    } else {
+      return this.hasAuthorityPrefixUserPref;
+    }
+  };
+
+  Uri.prototype.isColonUri = function (val) {
+    if (typeof val !== 'undefined') {
+      this.uriParts.isColonUri = !!val;
+    } else {
+      return !!this.uriParts.isColonUri;
+    }
+  };
+
+  /**
+   * Serializes the internal state of the query pairs
+   * @param  {string} [val]   set a new query string
+   * @return {string}         query string
+   */
+  Uri.prototype.query = function(val) {
+    var s = '', i, param, l;
+
+    if (typeof val !== 'undefined') {
+      this.queryPairs = parseQuery(val);
+    }
+
+    for (i = 0, l = this.queryPairs.length; i < l; i++) {
+      param = this.queryPairs[i];
+      if (s.length > 0) {
+        s += '&';
+      }
+      if (param[1] === null) {
+        s += param[0];
+      } else {
+        s += param[0];
+        s += '=';
+        if (typeof param[1] !== 'undefined') {
+          s += encodeURIComponent(param[1]);
+        }
+      }
+    }
+    return s.length > 0 ? '?' + s : s;
+  };
+
+  /**
+   * returns the first query param value found for the key
+   * @param  {string} key query key
+   * @return {string}     first value found for key
+   */
+  Uri.prototype.getQueryParamValue = function (key) {
+    var param, i, l;
+    for (i = 0, l = this.queryPairs.length; i < l; i++) {
+      param = this.queryPairs[i];
+      if (key === param[0]) {
+        return param[1];
+      }
+    }
+  };
+
+  /**
+   * returns an array of query param values for the key
+   * @param  {string} key query key
+   * @return {array}      array of values
+   */
+  Uri.prototype.getQueryParamValues = function (key) {
+    var arr = [], i, param, l;
+    for (i = 0, l = this.queryPairs.length; i < l; i++) {
+      param = this.queryPairs[i];
+      if (key === param[0]) {
+        arr.push(param[1]);
+      }
+    }
+    return arr;
+  };
+
+  /**
+   * removes query parameters
+   * @param  {string} key     remove values for key
+   * @param  {val}    [val]   remove a specific value, otherwise removes all
+   * @return {Uri}            returns self for fluent chaining
+   */
+  Uri.prototype.deleteQueryParam = function (key, val) {
+    var arr = [], i, param, keyMatchesFilter, valMatchesFilter, l;
+
+    for (i = 0, l = this.queryPairs.length; i < l; i++) {
+
+      param = this.queryPairs[i];
+      keyMatchesFilter = decode(param[0]) === decode(key);
+      valMatchesFilter = param[1] === val;
+
+      if ((arguments.length === 1 && !keyMatchesFilter) || (arguments.length === 2 && (!keyMatchesFilter || !valMatchesFilter))) {
+        arr.push(param);
+      }
+    }
+
+    this.queryPairs = arr;
+
+    return this;
+  };
+
+  /**
+   * adds a query parameter
+   * @param  {string}  key        add values for key
+   * @param  {string}  val        value to add
+   * @param  {integer} [index]    specific index to add the value at
+   * @return {Uri}                returns self for fluent chaining
+   */
+  Uri.prototype.addQueryParam = function (key, val, index) {
+    if (arguments.length === 3 && index !== -1) {
+      index = Math.min(index, this.queryPairs.length);
+      this.queryPairs.splice(index, 0, [key, val]);
+    } else if (arguments.length > 0) {
+      this.queryPairs.push([key, val]);
+    }
+    return this;
+  };
+
+  /**
+   * test for the existence of a query parameter
+   * @param  {string}  key        check values for key
+   * @return {Boolean}            true if key exists, otherwise false
+   */
+  Uri.prototype.hasQueryParam = function (key) {
+    var i, len = this.queryPairs.length;
+    for (i = 0; i < len; i++) {
+      if (this.queryPairs[i][0] == key)
+        return true;
+    }
+    return false;
+  };
+
+  /**
+   * replaces query param values
+   * @param  {string} key         key to replace value for
+   * @param  {string} newVal      new value
+   * @param  {string} [oldVal]    replace only one specific value (otherwise replaces all)
+   * @return {Uri}                returns self for fluent chaining
+   */
+  Uri.prototype.replaceQueryParam = function (key, newVal, oldVal) {
+    var index = -1, len = this.queryPairs.length, i, param;
+
+    if (arguments.length === 3) {
+      for (i = 0; i < len; i++) {
+        param = this.queryPairs[i];
+        if (decode(param[0]) === decode(key) && decodeURIComponent(param[1]) === decode(oldVal)) {
+          index = i;
+          break;
+        }
+      }
+      if (index >= 0) {
+        this.deleteQueryParam(key, decode(oldVal)).addQueryParam(key, newVal, index);
+      }
+    } else {
+      for (i = 0; i < len; i++) {
+        param = this.queryPairs[i];
+        if (decode(param[0]) === decode(key)) {
+          index = i;
+          break;
+        }
+      }
+      this.deleteQueryParam(key);
+      this.addQueryParam(key, newVal, index);
+    }
+    return this;
+  };
+
+  /**
+   * Define fluent setter methods (setProtocol, setHasAuthorityPrefix, etc)
+   */
+  ['protocol', 'hasAuthorityPrefix', 'isColonUri', 'userInfo', 'host', 'port', 'path', 'query', 'anchor'].forEach(function(key) {
+    var method = 'set' + key.charAt(0).toUpperCase() + key.slice(1);
+    Uri.prototype[method] = function(val) {
+      this[key](val);
+      return this;
+    };
+  });
+
+  /**
+   * Scheme name, colon and doubleslash, as required
+   * @return {string} http:// or possibly just //
+   */
+  Uri.prototype.scheme = function() {
+    var s = '';
+
+    if (this.protocol()) {
+      s += this.protocol();
+      if (this.protocol().indexOf(':') !== this.protocol().length - 1) {
+        s += ':';
+      }
+      s += '//';
+    } else {
+      if (this.hasAuthorityPrefix() && this.host()) {
+        s += '//';
+      }
+    }
+
+    return s;
+  };
+
+  /**
+   * Same as Mozilla nsIURI.prePath
+   * @return {string} scheme://user:password@host:port
+   * @see  https://developer.mozilla.org/en/nsIURI
+   */
+  Uri.prototype.origin = function() {
+    var s = this.scheme();
+
+    if (this.userInfo() && this.host()) {
+      s += this.userInfo();
+      if (this.userInfo().indexOf('@') !== this.userInfo().length - 1) {
+        s += '@';
+      }
+    }
+
+    if (this.host()) {
+      s += this.host();
+      if (this.port() || (this.path() && this.path().substr(0, 1).match(/[0-9]/))) {
+        s += ':' + this.port();
+      }
+    }
+
+    return s;
+  };
+
+  /**
+   * Adds a trailing slash to the path
+   */
+  Uri.prototype.addTrailingSlash = function() {
+    var path = this.path() || '';
+
+    if (path.substr(-1) !== '/') {
+      this.path(path + '/');
+    }
+
+    return this;
+  };
+
+  /**
+   * Serializes the internal state of the Uri object
+   * @return {string}
+   */
+  Uri.prototype.toString = function() {
+    var path, s = this.origin();
+
+    if (this.isColonUri()) {
+      if (this.path()) {
+        s += ':'+this.path();
+      }
+    } else if (this.path()) {
+      path = this.path();
+      if (!(re.ends_with_slashes.test(s) || re.starts_with_slashes.test(path))) {
+        s += '/';
+      } else {
+        if (s) {
+          s.replace(re.ends_with_slashes, '/');
+        }
+        path = path.replace(re.starts_with_slashes, '/');
+      }
+      s += path;
+    } else {
+      if (this.host() && (this.query().toString() || this.anchor())) {
+        s += '/';
+      }
+    }
+    if (this.query().toString()) {
+      s += this.query().toString();
+    }
+
+    if (this.anchor()) {
+      if (this.anchor().indexOf('#') !== 0) {
+        s += '#';
+      }
+      s += this.anchor();
+    }
+
+    return s;
+  };
+
+  /**
+   * Clone a Uri object
+   * @return {Uri} duplicate copy of the Uri
+   */
+  Uri.prototype.clone = function() {
+    return new Uri(this.toString());
+  };
+
+  /**
+   * export via AMD or CommonJS, otherwise leak a global
+   */
+  if (typeof define === 'function' && define.amd) {
+    define(function() {
+      return Uri;
+    });
+  } else if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
+    module.exports = Uri;
+  } else {
+    global.Uri = Uri;
+  }
+}(this));
diff --git a/app/src/main/assets/app_config/js/gadgetbridge_boilerplate.js b/app/src/main/assets/app_config/js/gadgetbridge_boilerplate.js
new file mode 100644
index 0000000..679eeef
--- /dev/null
+++ b/app/src/main/assets/app_config/js/gadgetbridge_boilerplate.js
@@ -0,0 +1,297 @@
+var reportedPositionFailures = 0;
+navigator.geolocation.getCurrentPosition = function(success, failure, options) { //override because default implementation requires GPS permission
+    geoposition = JSON.parse(GBjs.getCurrentPosition());
+
+    if(options && options.maximumAge && (geoposition.timestamp < Date.now() - options.maximumAge) && reportedPositionFailures <= 10 ) {
+        reportedPositionFailures++;
+    	failure({ code: 2, message: "POSITION_UNAVAILABLE"});
+    } else {
+        reportedPositionFailures = 0;
+        success(geoposition);
+    }
+};
+
+if (window.Storage){
+    var prefix = GBjs.getAppLocalstoragePrefix();
+    GBjs.gbLog("redefining local storage with prefix: " + prefix);
+
+    Storage.prototype.setItem = (function(key, value) {
+        this.call(localStorage,prefix + key, value);
+    }).bind(Storage.prototype.setItem);
+
+    Storage.prototype.getItem = (function(key) {
+        //GBjs.gbLog("I am about to return " + prefix + key);
+        return this.call(localStorage,prefix + key);
+    }).bind(Storage.prototype.getItem);
+}
+
+function loadScript(url, callback) {
+    // Adding the script tag to the head as suggested before
+    var head = document.getElementsByTagName('head')[0];
+    var script = document.createElement('script');
+    script.type = 'text/javascript';
+    script.src = url;
+
+    // Then bind the event to the callback function.
+    // There are several events for cross browser compatibility.
+    script.onreadystatechange = callback;
+    script.onload = callback;
+
+    // Fire the loading
+    head.appendChild(script);
+}
+
+function getURLVariable(variable, defaultValue) {
+  // Find all URL parameters
+  var query = location.search.substring(1);
+  var vars = query.split('&');
+  for (var i = 0; i < vars.length; i++) {
+    var pair = vars[i].split('=');
+
+    // If the query variable parameter is found, decode it to use and return it for use
+    if (pair[0] === variable) {
+      return decodeURIComponent(pair[1]);
+    }
+  }
+  return defaultValue || false;
+}
+
+function showStep(desiredStep) {
+    var steps = document.getElementsByClassName("step");
+    var testStep = null;
+    for (var i = 0; i < steps.length; i ++) {
+        if (steps[i].id == desiredStep)
+            testStep = steps[i].id;
+    }
+    if (testStep !== null) {
+        for (var i = 0; i < steps.length; i ++) {
+            steps[i].style.display = 'none';
+        }
+        document.getElementById(desiredStep).style.display="block";
+    }
+}
+
+function hideSteps() {
+    var steps = document.getElementsByClassName("step");
+        for (var i = 0; i < steps.length; i ++) {
+            steps[i].style.display = 'none';
+        }
+}
+
+function gbPebble() {
+    this.configurationURL = null;
+    this.configurationValues = null;
+
+    this.appMessageCallbackACK = {};
+    this.appMessageCallbackNACK = {};
+
+    var self = this;
+
+    self.appMessageCallbackProcessed = function (transactionId) {
+        if (self.appMessageCallbackACK[transactionId]) {
+            self.removeEventListener("ACK"+transactionId, self.appMessageCallbackACK[transactionId]);
+            self.appMessageCallbackACK[transactionId] = undefined;
+        }
+        if (self.appMessageCallbackNACK[transactionId]) {
+            self.removeEventListener("NACK"+transactionId, self.appMessageCallbackNACK[transactionId]);
+            self.appMessageCallbackNACK[transactionId] = undefined;
+        }
+    }
+    self.events = {};
+    //events processing: see http://stackoverflow.com/questions/10978311/implementing-events-in-my-own-object
+    self.addEventListener = function(name, handler) {
+        if (self.events.hasOwnProperty(name))
+            self.events[name].push(handler);
+        else
+            self.events[name] = [handler];
+    }
+
+    self.removeEventListener = function(name, handler) {
+        if (!self.events.hasOwnProperty(name))
+            return;
+
+        var index = self.events[name].indexOf(handler);
+        if (index != -1)
+            self.events[name].splice(index, 1);
+    }
+
+    self.evaluate = function(name, args) {
+        if (!self.events.hasOwnProperty(name))
+            return;
+
+        if (!args || !args.length)
+            args = [];
+
+        var evs = self.events[name], l = evs.length;
+        for (var i = 0; i < l; i++) {
+            evs[i].apply(null, args);
+        }
+        GBjs.eventFinished(name);
+    }
+
+    this.actuallyOpenURL = function() {
+        showStep("step1compat");
+        window.open(self.configurationURL.toString(), "config");
+    }
+
+    this.sendConfiguration = function() {
+        GBjs.sendAppMessage(document.getElementById("jsondata").innerHTML, false );
+        showStep("step1");
+        GBActivity.closeActivity();
+    }
+
+    this.savePreset = function() {
+        GBjs.saveAppStoredPreset(self.configurationValues);
+    }
+
+    this.loadPreset = function() {
+        showStep("step2");
+        var presetElements = document.getElementsByClassName("store_presets");
+        for (var i = 0; i < presetElements.length; i ++) {
+            presetElements[i].style.display = 'none';
+        }
+        self.configurationValues = GBjs.getAppStoredPreset();
+        document.getElementById("jsondata").innerHTML=self.configurationValues;
+    }
+
+    //needs to be called like this because of original Pebble function name
+    this.openURL = function(url) {
+            if (url.lastIndexOf("http", 0) === 0) {
+                    document.getElementById("config_url").innerHTML=url;
+                    var UUID = GBjs.getAppUUID();
+                    self.configurationURL = new Uri(url).addQueryParam("return_to", "gadgetbridge://"+UUID+"?config=true&json=");
+            } else {
+                //TODO: add custom return_to
+                var iframe = document.getElementsByTagName('iframe')[0];
+                var oldbody = document.getElementsByTagName("body")[0];
+                if (iframe === undefined && oldbody !== undefined) {
+                    iframe = document.createElement("iframe");
+                    oldbody.parentNode.replaceChild(iframe,oldbody);
+                } else {
+                    hideSteps();
+                    document.documentElement.appendChild(iframe);
+                }
+
+                iframe.src = url;
+
+            }
+
+    }
+
+    this.getActiveWatchInfo = function() {
+        return JSON.parse(GBjs.getActiveWatchInfo());
+    }
+
+    this.sendAppMessage = function (dict, callbackAck, callbackNack){
+        try {
+            self.configurationValues = JSON.stringify(dict);
+            if (document.getElementById("step2").style.display == 'block' && document.getElementById("jsondata").innerHTML == "") { //intercept the values
+                document.getElementById("jsondata").innerHTML=self.configurationValues;
+            } else { //pass them silently
+                var needsTransaction = false;
+                if (callbackAck != undefined || callbackNack != undefined) {
+                    needsTransaction = true;
+                }
+                var transactionId = GBjs.sendAppMessage(JSON.stringify(dict), needsTransaction);
+                if (needsTransaction) {
+                    if (typeof callbackAck != "undefined") {
+                        self.appMessageCallbackACK[transactionId] = function(e) {
+//                            console.log("ACK FOR " + JSON.stringify(e));
+                            callbackAck(e);
+                            self.appMessageCallbackProcessed(transactionId);
+                        };
+
+                        this.addEventListener("ACK"+transactionId, self.appMessageCallbackACK[transactionId]);
+
+                    }
+                    if (typeof callbackNack != "undefined") {
+                        self.appMessageCallbackNACK[transactionId] = function(e) {
+//                            console.log("NACK FOR " + JSON.stringify(e));
+                            callbackNack(e);
+                            self.appMessageCallbackProcessed(transactionId);
+                        };
+
+                        this.addEventListener("NACK"+transactionId, self.appMessageCallbackNACK[transactionId]);
+                    }
+                }
+            }
+        }
+        catch (e) {
+            GBjs.gbLog("sendAppMessage failed: " + e);
+        }
+    }
+
+    this.getAccountToken = function() {
+        return '';
+    }
+
+    this.getWatchToken = function() {
+        return GBjs.getWatchToken();
+    }
+
+    this.getTimelineToken = function() {
+        return '';
+    }
+
+    this.showSimpleNotificationOnPebble = function(title, body) {
+        GBjs.gbLog("app wanted to show: " + title + " body: "+ body);
+    }
+
+
+    this.showConfiguration = function() {
+        console.error("This watchapp doesn't support configuration");
+        GBActivity.closeActivity();
+    }
+
+    this.parseReturnedPebbleJS = function() {
+        var str = document.getElementById('pastereturn').value;
+        var needle = "pebblejs://close#";
+
+        if (str.split(needle)[1] !== undefined) {
+            var t = new Object();
+            t.response = decodeURIComponent(str.split(needle)[1]);
+            showStep("step2");
+            self.evaluate('webviewclosed',[t]);
+        } else {
+            console.error("No valid configuration found in the entered string.");
+        }
+    }
+}
+
+var Pebble = new gbPebble();
+
+var jsConfigFile = GBjs.getAppConfigurationFile();
+var storedPreset = GBjs.getAppStoredPreset();
+
+document.addEventListener('DOMContentLoaded', function(){
+if (jsConfigFile != null) {
+    loadScript(jsConfigFile, function() {
+        Pebble.evaluate('ready', [{'type': "ready"}]); //callback object apparently needed by some watchfaces
+        if(document.hasFocus() && !(getURLVariable('config') == 'true')) {
+            Pebble.evaluate('showConfiguration');
+        } else {
+            window.onfocus = function () {
+                showStep("step1");
+                GBjs.gbLog("window focused!!!");
+                Pebble.evaluate('showConfiguration');
+            };
+        }
+        if (getURLVariable('config') == 'true') {
+            showStep("step2");
+            var json_string = getURLVariable('json');
+            var t = new Object();
+            t.response = json_string;
+            if (json_string != '') {
+                Pebble.evaluate('webviewclosed',[t]);
+            }
+        } else {
+            if (storedPreset === undefined) {
+                var presetElements = document.getElementsByClassName("load_presets");
+                    for (var i = 0; i < presetElements.length; i ++) {
+                        presetElements[i].style.display = 'none';
+                    }
+            }
+        }
+    });
+}
+}, false);
\ No newline at end of file
diff --git a/app/src/main/assets/buds_pro_case.svg b/app/src/main/assets/buds_pro_case.svg
new file mode 100644
index 0000000..985c459
--- /dev/null
+++ b/app/src/main/assets/buds_pro_case.svg
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   width="10.399478mm"
+   height="10.338884mm"
+   viewBox="0 0 10.399477 10.338885"
+   version="1.1"
+   id="svg5"
+   inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
+   sodipodi:docname="buds_pro_case.svg"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg">
+  <sodipodi:namedview
+     id="namedview7"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageshadow="2"
+     inkscape:pageopacity="0.0"
+     inkscape:pagecheckerboard="0"
+     inkscape:document-units="mm"
+     showgrid="false"
+     fit-margin-left="0"
+     fit-margin-right="0"
+     fit-margin-bottom="1"
+     fit-margin-top="1"
+     inkscape:zoom="8"
+     inkscape:cx="39.6875"
+     inkscape:cy="37.9375"
+     inkscape:window-width="1920"
+     inkscape:window-height="1035"
+     inkscape:window-x="0"
+     inkscape:window-y="45"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="layer1" />
+  <defs
+     id="defs2" />
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(-95.852619,-146.01635)">
+    <path
+       style="fill:#000000;stroke:#000000;stroke-width:0.4;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       d="m 98.725767,155.07946 c -2.011931,-0.33512 -3.224531,-2.80083 -2.421739,-4.92438 0.698696,-1.8482 1.220768,-2.38464 2.716612,-2.79141 0.622809,-0.16937 3.23294,-0.2015 3.88217,-0.0478 1.27757,0.30245 1.88675,0.72802 2.36901,1.65499 0.70733,1.35957 0.89693,2.22299 0.71935,3.27582 -0.25793,1.52921 -1.29315,2.61077 -2.72307,2.84497 -0.55609,0.0911 -3.977465,0.0819 -4.542333,-0.0122 z m 4.644383,-0.15465 c 0.60321,-0.1105 1.30489,-0.48711 1.65427,-0.88788 0.30716,-0.35233 0.68597,-1.10792 0.76801,-1.53187 l 0.053,-0.27397 -0.22851,0.24421 c -0.33209,0.35491 -0.77469,0.62265 -1.34022,0.81074 -1.13605,0.37784 -5.214307,0.38683 -6.410497,0.0141 -0.55739,-0.17367 -1.193139,-0.55571 -1.433201,-0.86126 l -0.163268,-0.2078 0.04663,0.27396 c 0.07099,0.4171 0.462077,1.19611 0.773559,1.54089 0.330733,0.36608 0.957492,0.72348 1.500185,0.85545 0.505093,0.12283 4.140102,0.14063 4.780022,0.0234 z m 0.5638,-1.66846 c 0.91653,-0.23153 1.68237,-0.77465 1.90019,-1.34755 0.20798,-0.54704 -0.0205,-1.55801 -0.623,-2.75628 -0.4674,-0.9296 -1.05748,-1.37665 -2.19843,-1.66553 -0.67764,-0.17157 -3.410689,-0.14747 -4.065651,0.0359 -1.070946,0.29976 -1.649067,0.76556 -2.081871,1.67737 -0.99471,2.09563 -0.884667,3.03994 0.434692,3.73021 0.829113,0.43377 1.468822,0.5086 4.06904,0.47594 1.56307,-0.0196 2.19526,-0.0566 2.56503,-0.15002 z"
+       id="path1716" />
+  </g>
+</svg>
diff --git a/app/src/main/assets/buds_pro_left.svg b/app/src/main/assets/buds_pro_left.svg
new file mode 100644
index 0000000..3fcbe94
--- /dev/null
+++ b/app/src/main/assets/buds_pro_left.svg
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   width="10.999999mm"
+   height="10.999998mm"
+   viewBox="0 0 11 10.999998"
+   version="1.1"
+   id="svg4576"
+   inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
+   sodipodi:docname="buds_pro_left.svg"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg">
+  <sodipodi:namedview
+     id="namedview4578"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageshadow="2"
+     inkscape:pageopacity="0.0"
+     inkscape:pagecheckerboard="0"
+     inkscape:document-units="mm"
+     showgrid="false"
+     fit-margin-top="0"
+     fit-margin-bottom="0"
+     fit-margin-left="0"
+     fit-margin-right="0"
+     inkscape:zoom="16"
+     inkscape:cx="32.09375"
+     inkscape:cy="39.15625"
+     inkscape:window-width="1920"
+     inkscape:window-height="1035"
+     inkscape:window-x="0"
+     inkscape:window-y="45"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="layer1"
+     showguides="true"
+     inkscape:guide-bbox="true">
+    <sodipodi:guide
+       position="-47.790315,6.0004159"
+       orientation="0,-1"
+       id="guide34420" />
+  </sodipodi:namedview>
+  <defs
+     id="defs4573" />
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(-93.982649,-153.80574)">
+    <path
+       id="path61748"
+       style="font-size:4.23333px;line-height:124%;font-family:Sans;-inkscape-font-specification:Sans;letter-spacing:0px;word-spacing:0px;stroke:#000000;stroke-width:0.265"
+       d="m 98.636978,156.07032 h 0.367759 v 2.42358 h 1.323583 v 0.31142 h -1.691342 z m 0.315833,7.6524 c 0.356876,-0.14916 0.822437,-0.53623 0.968511,-0.80526 0.07231,-0.13321 0.02551,-0.13439 -0.475169,-0.012 -0.984441,0.24062 -1.681127,0.009 -2.306585,-0.76927 -0.26071,-0.32411 -0.295617,-0.44513 -0.339874,-1.17836 l -0.04933,-0.81715 -0.256509,0.30582 c -0.822265,0.98037 -0.459417,2.6469 0.70147,3.22181 0.48908,0.24221 1.251667,0.26583 1.757481,0.0544 z m -1.330485,0.32864 c -0.541904,-0.12922 -0.867149,-0.32727 -1.225662,-0.74633 -0.606112,-0.70845 -0.727348,-1.77669 -0.299201,-2.6363 0.135175,-0.2714 0.322096,-0.53432 0.415381,-0.58427 0.157826,-0.0845 0.159638,-0.11891 0.0261,-0.49524 -0.352113,-0.99233 -0.20943,-2.51608 0.328134,-3.50425 0.328783,-0.60438 1.0431,-1.2434 1.603455,-1.43443 1.115376,-0.38024 2.679607,0.0425 3.461337,0.93534 1.21161,1.38389 1.52834,3.21746 0.81726,4.73098 -0.23868,0.50801 -1.21551,1.49715 -1.92601,1.95027 -0.34353,0.21907 -0.56429,0.44351 -0.67787,0.68916 -0.371437,0.80334 -1.568126,1.32275 -2.522924,1.09507 z m 2.19911,-1.4967 c 0.978664,-0.37204 2.470754,-1.6715 2.795454,-2.43453 0.78312,-1.84035 -0.11926,-4.30442 -1.87124,-5.10964 -0.806242,-0.37056 -1.788981,-0.3857 -2.470056,-0.038 -1.451067,0.74068 -2.106198,2.85414 -1.464365,4.72405 0.0966,0.28145 0.176572,0.77122 0.177715,1.08843 0.0044,1.23212 0.671638,1.94724 1.817915,1.94848 0.305191,3.3e-4 0.749449,-0.0779 1.014577,-0.17873 z" />
+    <g
+       id="g1961"
+       transform="translate(5.7478236e-5,3.0300929e-4)">
+      <path
+         style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.4;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         d="m 99.687906,159.96304 c 0.03116,-0.0812 0.399384,-0.2858 0.818354,-0.45477 0.84931,-0.34253 1.0686,-0.3103 1.0686,0.15703 0,0.24663 -0.0628,0.29752 -0.70948,0.57453 -0.86444,0.37031 -0.952978,0.55914 -1.060626,0.26518 -0.107648,-0.29395 -0.14798,-0.46082 -0.116848,-0.54197 z"
+         id="path1775"
+         sodipodi:nodetypes="ccsccc" />
+    </g>
+  </g>
+</svg>
diff --git a/app/src/main/assets/buds_pro_right.svg b/app/src/main/assets/buds_pro_right.svg
new file mode 100644
index 0000000..cd0075f
--- /dev/null
+++ b/app/src/main/assets/buds_pro_right.svg
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   width="10.999999mm"
+   height="10.999999mm"
+   viewBox="0 0 11 10.999999"
+   version="1.1"
+   id="svg4576"
+   inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
+   sodipodi:docname="buds_pro_right.svg"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg">
+  <sodipodi:namedview
+     id="namedview4578"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageshadow="2"
+     inkscape:pageopacity="0.0"
+     inkscape:pagecheckerboard="0"
+     inkscape:document-units="mm"
+     showgrid="false"
+     fit-margin-top="0"
+     fit-margin-bottom="0"
+     fit-margin-left="0"
+     fit-margin-right="0"
+     inkscape:zoom="11.313709"
+     inkscape:cx="36.63697"
+     inkscape:cy="29.831067"
+     inkscape:window-width="1920"
+     inkscape:window-height="1035"
+     inkscape:window-x="0"
+     inkscape:window-y="45"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="layer1"
+     showguides="true"
+     inkscape:guide-bbox="true">
+    <sodipodi:guide
+       position="1.2413708,6.0162603"
+       orientation="0,-1"
+       id="guide34430" />
+  </sodipodi:namedview>
+  <defs
+     id="defs4573" />
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(-85.996183,-154.42373)">
+    <g
+       aria-label="R"
+       id="text28286"
+       style="font-style:italic;font-size:4.23333px;line-height:124%;font-family:Sans;-inkscape-font-specification:'Sans Italic';letter-spacing:0px;word-spacing:0px;stroke-width:0.299542px"
+       transform="matrix(0.88023484,0,0,0.88636119,10.922244,18.114874)">
+      <path
+         id="path61769"
+         style="font-size:4.23333px;line-height:124%;font-family:Sans;-inkscape-font-specification:Sans;letter-spacing:0px;word-spacing:0px;stroke:#000000;stroke-width:0.299542px"
+         d="m 91.7518,157.96053 q 0.134359,0.0455 0.260449,0.1943 0.128158,0.14883 0.256315,0.40928 l 0.423747,0.84336 h -0.448552 l -0.394807,-0.79169 q -0.152962,-0.31005 -0.297656,-0.41134 -0.142627,-0.10128 -0.390674,-0.10128 H 90.70587 v 1.30431 h -0.417545 v -3.08612 h 0.942577 q 0.529166,0 0.789615,0.22118 0.260449,0.22117 0.260449,0.66766 0,0.29145 -0.136425,0.48369 -0.134359,0.19223 -0.392741,0.26665 z m -1.04593,-1.29605 v 1.09554 h 0.525032 q 0.30179,0 0.454752,-0.13849 0.15503,-0.14056 0.15503,-0.41134 0,-0.27079 -0.15503,-0.40721 -0.152962,-0.1385 -0.454752,-0.1385 z m 1.432917,8.30872 c -0.405432,-0.16828 -0.934338,-0.60499 -1.100286,-0.9085 -0.08215,-0.15028 -0.02898,-0.15162 0.53982,-0.0135 1.118385,0.27146 1.909867,0.0101 2.620416,-0.8679 0.296193,-0.36567 0.335853,-0.50221 0.386124,-1.32944 l 0.05601,-0.92191 0.291411,0.34503 c 0.934148,1.10606 0.521917,2.98625 -0.796913,3.63487 -0.555624,0.27326 -1.421972,0.29991 -1.996603,0.0614 z m -0.98681,-1.31781 c -1.111824,-0.41974 -2.806926,-1.8858 -3.175801,-2.74666 -0.889675,-2.0763 0.135483,-4.85628 2.125836,-5.76475 0.915942,-0.41805 2.032389,-0.43513 2.806137,-0.0429 1.648492,0.83564 2.39276,3.22007 1.663613,5.32971 -0.109743,0.31753 -0.200605,0.8701 -0.2019,1.22797 -0.0057,1.3901 -0.763024,2.1969 -2.065267,2.19829 -0.346714,3.8e-4 -0.851416,-0.0879 -1.152618,-0.20163 z m 2.498327,1.68859 c 0.615631,-0.14579 0.985134,-0.36923 1.392423,-0.84202 0.688578,-0.79928 0.826314,-2.00447 0.33991,-2.97429 -0.153562,-0.3062 -0.365914,-0.60283 -0.471897,-0.65918 -0.179304,-0.0953 -0.181361,-0.13416 -0.02965,-0.55873 0.400029,-1.11956 0.237925,-2.83867 -0.372776,-3.95353 -0.373514,-0.68188 -1.185013,-1.40282 -1.821627,-1.61833 -1.267129,-0.42899 -3.044181,0.048 -3.932276,1.05525 -1.376461,1.56132 -1.736294,3.62997 -0.928459,5.33754 0.271153,0.57314 1.380895,1.68909 2.188066,2.20031 0.390272,0.24715 0.641063,0.50037 0.770097,0.77751 0.421974,0.90634 1.781479,1.49234 2.86619,1.23547 z" />
+    </g>
+    <g
+       id="g3227"
+       transform="translate(2.1600298e-4,2.9611944e-4)">
+      <path
+         style="fill:#000000;stroke:#000000;stroke-width:0.4;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         d="m 91.29092,160.58104 c -0.03116,-0.0812 -0.399381,-0.2858 -0.818345,-0.45477 -0.849311,-0.34253 -1.068608,-0.31031 -1.068608,0.15703 0,0.24663 0.06281,0.29752 0.709484,0.57453 0.864443,0.37031 0.947778,0.40745 1.058024,0.18934 0.110245,-0.21811 0.150577,-0.38498 0.119445,-0.46613 z"
+         id="path3121"
+         sodipodi:nodetypes="cssccc" />
+    </g>
+  </g>
+</svg>
diff --git a/app/src/main/assets/calendar_sync.svg b/app/src/main/assets/calendar_sync.svg
new file mode 100644
index 0000000..a64ad7b
--- /dev/null
+++ b/app/src/main/assets/calendar_sync.svg
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
+   sodipodi:docname="calendar_today-24px-1.svg"
+   id="svg6"
+   version="1.1"
+   width="24"
+   viewBox="0 0 24 24"
+   height="24">
+  <metadata
+     id="metadata12">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs10" />
+  <sodipodi:namedview
+     inkscape:current-layer="svg6"
+     inkscape:window-maximized="1"
+     inkscape:window-y="38"
+     inkscape:window-x="0"
+     inkscape:cy="14.262503"
+     inkscape:cx="14.320308"
+     inkscape:zoom="22.185475"
+     showgrid="false"
+     id="namedview8"
+     inkscape:window-height="1042"
+     inkscape:window-width="1920"
+     inkscape:pageshadow="2"
+     inkscape:pageopacity="0"
+     guidetolerance="10"
+     gridtolerance="10"
+     objecttolerance="10"
+     borderopacity="1"
+     bordercolor="#666666"
+     pagecolor="#ffffff" />
+  <path
+     id="path2"
+     fill="none"
+     d="M0 0h24v24H0V0z" />
+  <path
+     id="path4"
+     d="m 20.018677,3 h -1 V 1 h -2 V 3 H 7.0186774 V 1 h -2 v 2 h -1 c -1.1,0 -2,0.9 -2,2 v 16 c 0,1.1 0.9,2 2,2 H 20.018677 c 1.1,0 2,-0.9 2,-2 V 5 c 0,-1.1 -0.9,-2 -2,-2 z m 0,18 H 4.0186774 V 10 H 20.018677 Z m 0,-13 H 4.0186774 V 5 H 20.018677 Z" />
+  <path
+     style="stroke-width:0.999998"
+     d="m 12.018677,11.629269 v -1.465936 l -1.954583,1.954582 1.954583,1.954582 v -1.465936 c 1.617417,0 2.931873,1.314456 2.931873,2.931873 0,0.493532 -0.122161,0.962632 -0.342052,1.368207 l 0.713423,0.713423 c 0.381143,-0.601034 0.60592,-1.314457 0.60592,-2.08163 0,-2.159814 -1.749351,-3.909165 -3.909164,-3.909165 z m 0,6.841038 c -1.617417,0 -2.9318731,-1.314456 -2.9318731,-2.931873 0,-0.493532 0.1221614,-0.962632 0.3420519,-1.368208 L 8.7154333,13.456804 c -0.3811435,0.601034 -0.6059204,1.314456 -0.6059204,2.08163 0,2.159813 1.7493508,3.909164 3.9091641,3.909164 v 1.465937 l 1.954582,-1.954583 -1.954582,-1.954582 z"
+     id="path4-3" />
+</svg>
diff --git a/app/src/main/assets/devintro.png b/app/src/main/assets/devintro.png
new file mode 100644
index 0000000..859cfde
--- /dev/null
+++ b/app/src/main/assets/devintro.png
Binary files differ
diff --git a/app/src/main/assets/devintro.xmi b/app/src/main/assets/devintro.xmi
new file mode 100644
index 0000000..97ec137
--- /dev/null
+++ b/app/src/main/assets/devintro.xmi
@@ -0,0 +1,175 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<XMI timestamp="2016-01-26T23:02:14" verified="false" xmi.version="1.2" xmlns:UML="http://schema.omg.org/spec/UML/1.3">
+ <XMI.header>
+  <XMI.documentation>
+   <XMI.exporter>umbrello uml modeller http://umbrello.kde.org</XMI.exporter>
+   <XMI.exporterVersion>1.6.9</XMI.exporterVersion>
+   <XMI.exporterEncoding>UnicodeUTF8</XMI.exporterEncoding>
+  </XMI.documentation>
+  <XMI.metamodel xmi.name="UML" href="UML.xml" xmi.version="1.3"/>
+ </XMI.header>
+ <XMI.content>
+  <UML:Model isAbstract="false" isRoot="false" isSpecification="false" name="UML Model" isLeaf="false" xmi.id="m1">
+   <UML:Namespace.ownedElement>
+    <UML:Stereotype isRoot="false" isAbstract="false" isSpecification="false" name="folder" isLeaf="false" namespace="m1" visibility="public" xmi.id="folder"/>
+    <UML:Stereotype isRoot="false" isAbstract="false" isSpecification="false" name="datatype" isLeaf="false" namespace="m1" visibility="public" xmi.id="datatype"/>
+    <UML:Stereotype isRoot="false" isAbstract="false" isSpecification="false" name="interface" isLeaf="false" namespace="m1" visibility="public" xmi.id="interface"/>
+    <UML:Model isRoot="false" isAbstract="false" isSpecification="false" name="Logical View" isLeaf="false" namespace="m1" visibility="public" xmi.id="Logical View">
+     <UML:Namespace.ownedElement>
+      <UML:Package isRoot="false" isAbstract="false" isSpecification="false" name="Datatypes" isLeaf="false" stereotype="folder" namespace="Logical View" visibility="public" xmi.id="Datatypes">
+       <UML:Namespace.ownedElement>
+        <UML:DataType isRoot="false" isAbstract="false" isSpecification="false" name="int" isLeaf="false" stereotype="datatype" namespace="Datatypes" visibility="public" xmi.id="tM1MfT3dGbDn"/>
+        <UML:DataType isRoot="false" isAbstract="false" isSpecification="false" name="char" isLeaf="false" stereotype="datatype" namespace="Datatypes" visibility="public" xmi.id="MZCr2zI6yZo6"/>
+        <UML:DataType isRoot="false" isAbstract="false" isSpecification="false" name="bool" isLeaf="false" stereotype="datatype" namespace="Datatypes" visibility="public" xmi.id="mtFc0pEuADEp"/>
+        <UML:DataType isRoot="false" isAbstract="false" isSpecification="false" name="float" isLeaf="false" stereotype="datatype" namespace="Datatypes" visibility="public" xmi.id="hIRvrcjDBt2B"/>
+        <UML:DataType isRoot="false" isAbstract="false" isSpecification="false" name="double" isLeaf="false" stereotype="datatype" namespace="Datatypes" visibility="public" xmi.id="tIHrr3vdvBFv"/>
+        <UML:DataType isRoot="false" isAbstract="false" isSpecification="false" name="short" isLeaf="false" stereotype="datatype" namespace="Datatypes" visibility="public" xmi.id="yRn8f2wKINF9"/>
+        <UML:DataType isRoot="false" isAbstract="false" isSpecification="false" name="long" isLeaf="false" stereotype="datatype" namespace="Datatypes" visibility="public" xmi.id="xwL6qLHheXWT"/>
+        <UML:DataType isRoot="false" isAbstract="false" isSpecification="false" name="unsigned int" isLeaf="false" stereotype="datatype" namespace="Datatypes" visibility="public" xmi.id="aS7VdKRbkCiP"/>
+        <UML:DataType isRoot="false" isAbstract="false" isSpecification="false" name="unsigned short" isLeaf="false" stereotype="datatype" namespace="Datatypes" visibility="public" xmi.id="EejU1wcwrx2h"/>
+        <UML:DataType isRoot="false" isAbstract="false" isSpecification="false" name="unsigned long" isLeaf="false" stereotype="datatype" namespace="Datatypes" visibility="public" xmi.id="GJMsqsMRIRuv"/>
+        <UML:DataType isRoot="false" isAbstract="false" isSpecification="false" name="string" isLeaf="false" stereotype="datatype" namespace="Datatypes" visibility="public" xmi.id="jLVTWBskpZFo"/>
+       </UML:Namespace.ownedElement>
+      </UML:Package>
+      <UML:Interface isRoot="false" isAbstract="true" isSpecification="false" name="DeviceService" isLeaf="false" stereotype="interface" namespace="Logical View" visibility="public" xmi.id="VA6qSCiBtNc5"/>
+      <UML:Interface isRoot="false" isAbstract="true" isSpecification="false" name="DeviceSupport" isLeaf="false" stereotype="interface" namespace="Logical View" visibility="public" xmi.id="AKxHpDCgOPhk"/>
+     </UML:Namespace.ownedElement>
+     <XMI.extension xmi.extender="umbrello">
+      <diagrams>
+       <diagram showgrid="0" snapgrid="0" backgroundcolor="#ffffff" zoom="100" type="1" linecolor="#ff0000" usefillcolor="1" griddotcolor="#d3d3d3" showops="1" showscope="1" localid="-1" showpackage="1" showpubliconly="0" canvaswidth="0" isopen="0" showatts="1" xmi.id="31Hk7F5Bq9ac" snapcsgrid="0" snapx="25" name="class diagram" showattsig="1" textcolor="#000000" showstereotype="1" showattribassocs="1" linewidth="0" fillcolor="#ffff00" documentation="" font="Liberation Sans,10,-1,5,50,0,0,0,0,0" canvasheight="0" showopsig="1" snapy="25">
+        <widgets/>
+        <messages/>
+        <associations/>
+       </diagram>
+      </diagrams>
+     </XMI.extension>
+    </UML:Model>
+    <UML:Model isRoot="false" isAbstract="false" isSpecification="false" name="Use Case View" isLeaf="false" namespace="m1" visibility="public" xmi.id="Use Case View">
+     <UML:Namespace.ownedElement/>
+    </UML:Model>
+    <UML:Model isRoot="false" isAbstract="false" isSpecification="false" name="Component View" isLeaf="false" namespace="m1" visibility="public" xmi.id="Component View">
+     <UML:Namespace.ownedElement>
+      <UML:Component isRoot="false" isAbstract="false" isSpecification="false" name="Activities" executable="0" isLeaf="false" namespace="Component View" visibility="public" xmi.id="CDLTeFMNZrp5"/>
+      <UML:Component isRoot="false" isAbstract="false" isSpecification="false" name="DeviceCommunicationService" executable="0" isLeaf="false" namespace="Component View" visibility="public" xmi.id="72YBMP5WcQzE">
+       <UML:Namespace.ownedElement>
+        <UML:Port isRoot="false" isAbstract="false" isSpecification="false" name="pin" isLeaf="false" namespace="72YBMP5WcQzE" visibility="public" xmi.id="xhXzgXGeAGP8"/>
+        <UML:Port isRoot="false" isAbstract="false" isSpecification="false" name="pin2" isLeaf="false" namespace="72YBMP5WcQzE" visibility="public" xmi.id="LcpVBJq9yM8d"/>
+       </UML:Namespace.ownedElement>
+      </UML:Component>
+      <UML:Component isRoot="false" isAbstract="false" isSpecification="false" name="Frontend2" executable="0" isLeaf="false" namespace="Component View" visibility="public" xmi.id="zd0ny2K62qHM"/>
+      <UML:Artifact isRoot="false" isAbstract="false" drawas="0" isSpecification="false" name="Service" isLeaf="false" namespace="Component View" visibility="public" xmi.id="jjfTCGwVsjy4"/>
+      <UML:Abstraction supplier="VA6qSCiBtNc5" isSpecification="false" name="" namespace="Component View" client="72YBMP5WcQzE" visibility="public" xmi.id="3ZwKCCl82Cel"/>
+      <UML:Association isSpecification="false" name="invoke" namespace="Component View" visibility="public" xmi.id="ZmBbwqKcFFuU">
+       <UML:Association.connection>
+        <UML:AssociationEnd isNavigable="false" changeability="changeable" isSpecification="false" type="CDLTeFMNZrp5" name="" aggregation="none" visibility="public" xmi.id="8FlknwQC5gyi"/>
+        <UML:AssociationEnd isNavigable="true" changeability="changeable" isSpecification="false" type="VA6qSCiBtNc5" name="" aggregation="none" visibility="public" xmi.id="0RPtmPzL9Au6"/>
+       </UML:Association.connection>
+      </UML:Association>
+      <UML:Component isRoot="false" isAbstract="false" isSpecification="false" name="Concrete Device Impl." executable="0" isLeaf="false" namespace="Component View" visibility="public" xmi.id="tDF2L96KKnVj"/>
+      <UML:Association isSpecification="false" name="invoke" namespace="Component View" visibility="public" xmi.id="60vlquxGX0xa">
+       <UML:Association.connection>
+        <UML:AssociationEnd isNavigable="false" changeability="changeable" isSpecification="false" type="72YBMP5WcQzE" name="" aggregation="none" visibility="public" xmi.id="EElpRDBsrFht"/>
+        <UML:AssociationEnd isNavigable="true" changeability="changeable" isSpecification="false" type="AKxHpDCgOPhk" name="" aggregation="none" visibility="public" xmi.id="zEQUpqXtpTfd"/>
+       </UML:Association.connection>
+      </UML:Association>
+      <UML:Abstraction supplier="AKxHpDCgOPhk" isSpecification="false" name="" namespace="Component View" client="tDF2L96KKnVj" visibility="public" xmi.id="LcusapQo1BbT"/>
+     </UML:Namespace.ownedElement>
+     <XMI.extension xmi.extender="umbrello">
+      <diagrams>
+       <diagram showgrid="0" snapgrid="0" backgroundcolor="#ffffff" zoom="105" type="7" linecolor="#ff0000" usefillcolor="1" griddotcolor="#d3d3d3" showops="1" showscope="1" localid="-1" showpackage="1" showpubliconly="0" canvaswidth="1931" isopen="1" showatts="1" xmi.id="UpkLkm005Mtw" snapcsgrid="0" snapx="25" name="Gadgetbridge" showattsig="1" textcolor="#000000" showstereotype="1" showattribassocs="1" linewidth="0" fillcolor="#ffff00" documentation="" font="Liberation Sans,10,-1,5,50,0,0,0,0,0" canvasheight="1133" showopsig="1" snapy="25">
+        <widgets>
+         <componentwidget textcolor="#000000" fillcolor="#ffff00" font="Liberation Sans,10,-1,5,50,0,0,0,0,0" height="102" linewidth="0" width="165" showstereotype="1" linecolor="none" usefillcolor="1" usesdiagramusefillcolor="0" x="-1652,09756097561" y="-881,8487804878049" localid="2hnpF0djpL2Z" usesdiagramfillcolor="0" xmi.id="CDLTeFMNZrp5" isinstance="0"/>
+         <componentwidget textcolor="#000000" fillcolor="#ffff00" font="Liberation Sans,10,-1,5,50,0,0,0,0,0" height="60" linewidth="0" width="246" showstereotype="1" linecolor="none" usefillcolor="1" usesdiagramusefillcolor="0" x="-1654" y="-645" localid="dMZABODC4z1H" usesdiagramfillcolor="0" xmi.id="72YBMP5WcQzE" isinstance="0"/>
+         <interfacewidget linecolor="none" usefillcolor="1" usesdiagramfillcolor="0" x="-1589,839897589076" isinstance="0" width="40" localid="bZYTIWVHJeGR" showscope="1" height="40" drawascircle="1" showpubliconly="0" showpackage="0" xmi.id="VA6qSCiBtNc5" showattributes="0" textcolor="#000000" showstereotype="1" linewidth="0" usesdiagramusefillcolor="0" fillcolor="#ffff00" font="Liberation Sans,10,-1,5,50,0,0,0,0,0" showopsigs="601" showattsigs="601" showoperations="1" y="-716,9293791337742">
+          <floatingtext linecolor="none" usefillcolor="1" usesdiagramfillcolor="1" x="-91,07317073170748" isinstance="0" width="88" localid="wsTYjTmCdq05" height="19" posttext="" xmi.id="3iFu3ryeOW6f" role="700" textcolor="none" showstereotype="1" linewidth="none" text="DeviceService" usesdiagramusefillcolor="1" fillcolor="none" font="Liberation Sans,10,-1,5,50,0,0,0,0,0" pretext="" y="10,97560975609758"/>
+         </interfacewidget>
+         <componentwidget textcolor="#000000" fillcolor="#ffff00" font="Liberation Sans,10,-1,5,50,0,0,0,0,0" height="60" linewidth="0" width="195" showstereotype="1" linecolor="none" usefillcolor="1" usesdiagramusefillcolor="0" x="-1374,768315059297" y="-548,2509733721189" localid="L9H7YahrGQFd" usesdiagramfillcolor="0" xmi.id="tDF2L96KKnVj" isinstance="0"/>
+         <interfacewidget linecolor="none" usefillcolor="1" usesdiagramfillcolor="0" x="-1320,4" isinstance="0" width="40" localid="Xn4rYXasfszD" showscope="1" height="40" drawascircle="1" showpubliconly="0" showpackage="0" xmi.id="AKxHpDCgOPhk" showattributes="0" textcolor="#000000" showstereotype="1" linewidth="0" usesdiagramusefillcolor="0" fillcolor="#ffff00" font="Liberation Sans,10,-1,5,50,0,0,0,0,0" showopsigs="601" showattsigs="601" showoperations="1" y="-640,1853658536585">
+          <floatingtext linecolor="none" usefillcolor="1" usesdiagramfillcolor="1" x="-6,602439024390151" isinstance="0" width="91" localid="Qe3g5GJDaFUO" height="19" posttext="" xmi.id="Qe4p2lZZXbQ3" role="700" textcolor="none" showstereotype="1" linewidth="none" text="DeviceSupport" usesdiagramusefillcolor="1" fillcolor="none" font="Liberation Sans,10,-1,5,50,0,0,0,0,0" pretext="" y="-15,0634146341464"/>
+         </interfacewidget>
+        </widgets>
+        <messages/>
+        <associations>
+         <assocwidget totalcountb="2" textcolor="none" fillcolor="#ffff00" font="Liberation Sans,10,-1,5,50,0,0,0,0,0" widgetaid="72YBMP5WcQzE" linewidth="none" totalcounta="2" linecolor="none" usefillcolor="1" usesdiagramusefillcolor="94" type="511" indexb="1" seqnum="" usesdiagramfillcolor="0" widgetbid="VA6qSCiBtNc5" xmi.id="3ZwKCCl82Cel" indexa="1">
+          <linepath layout="Polyline">
+           <startpoint startx="-1569,718796671645" starty="-645"/>
+           <endpoint endx="-1569,718796671645" endy="-676,9293791337742"/>
+          </linepath>
+         </assocwidget>
+         <assocwidget totalcountb="2" textcolor="none" fillcolor="#ffff00" font="Liberation Sans,10,-1,5,50,0,0,0,0,0" widgetaid="CDLTeFMNZrp5" linewidth="none" totalcounta="2" linecolor="none" usefillcolor="1" usesdiagramusefillcolor="96" type="512" indexb="1" seqnum="" usesdiagramfillcolor="0" widgetbid="VA6qSCiBtNc5" xmi.id="ZmBbwqKcFFuU" indexa="1">
+          <linepath layout="Polyline">
+           <startpoint startx="-1571,551409184105" starty="-779,8487804878049"/>
+           <endpoint endx="-1571,551409184105" endy="-716,9293791337742"/>
+          </linepath>
+          <floatingtext linecolor="none" usefillcolor="1" usesdiagramfillcolor="1" x="-1567,112384793861" isinstance="0" width="44" localid="yCgBPagYnPIa" height="19" posttext="" xmi.id="LilOMj6M9VGv" role="703" textcolor="none" showstereotype="1" linewidth="none" text="invoke" usesdiagramusefillcolor="1" fillcolor="none" font="Liberation Sans,10,-1,5,50,0,0,0,0,0" pretext="" y="-758,1110310303017"/>
+         </assocwidget>
+         <assocwidget totalcountb="2" textcolor="none" fillcolor="#ffff00" font="Liberation Sans,10,-1,5,50,0,0,0,0,0" widgetaid="72YBMP5WcQzE" linewidth="none" totalcounta="2" linecolor="none" usefillcolor="1" usesdiagramusefillcolor="2" type="512" indexb="1" seqnum="" usesdiagramfillcolor="0" widgetbid="AKxHpDCgOPhk" xmi.id="60vlquxGX0xa" indexa="1">
+          <linepath layout="Polyline">
+           <startpoint startx="-1408" starty="-617,0146341463413"/>
+           <endpoint endx="-1320,4" endy="-617,0146341463413"/>
+          </linepath>
+          <floatingtext linecolor="none" usefillcolor="1" usesdiagramfillcolor="1" x="-1382,614634146342" isinstance="0" width="44" localid="S2UTD5uasDos" height="19" posttext="" xmi.id="BrpNNGMiEFrg" role="703" textcolor="none" showstereotype="1" linewidth="none" text="invoke" usesdiagramusefillcolor="1" fillcolor="none" font="Liberation Sans,10,-1,5,50,0,0,0,0,0" pretext="" y="-613,3439024390243"/>
+         </assocwidget>
+         <assocwidget totalcountb="2" textcolor="none" fillcolor="#ffff00" font="Liberation Sans,10,-1,5,50,0,0,0,0,0" widgetaid="tDF2L96KKnVj" linewidth="none" totalcounta="2" linecolor="none" usefillcolor="1" usesdiagramusefillcolor="53" type="511" indexb="1" seqnum="" usesdiagramfillcolor="0" widgetbid="AKxHpDCgOPhk" xmi.id="LcusapQo1BbT" indexa="1">
+          <linepath layout="Polyline">
+           <startpoint startx="-1299,912195121952" starty="-548,2509733721189"/>
+           <endpoint endx="-1299,912195121952" endy="-600,1853658536585"/>
+          </linepath>
+         </assocwidget>
+        </associations>
+       </diagram>
+      </diagrams>
+     </XMI.extension>
+    </UML:Model>
+    <UML:Model isRoot="false" isAbstract="false" isSpecification="false" name="Deployment View" isLeaf="false" namespace="m1" visibility="public" xmi.id="Deployment View">
+     <UML:Namespace.ownedElement/>
+    </UML:Model>
+    <UML:Model isRoot="false" isAbstract="false" isSpecification="false" name="Entity Relationship Model" isLeaf="false" namespace="m1" visibility="public" xmi.id="Entity Relationship Model">
+     <UML:Namespace.ownedElement/>
+    </UML:Model>
+   </UML:Namespace.ownedElement>
+  </UML:Model>
+ </XMI.content>
+ <XMI.extensions xmi.extender="umbrello">
+  <docsettings viewid="UpkLkm005Mtw" uniqueid="BrpNNGMiEFrg" documentation=""/>
+  <listview>
+   <listitem type="800" id="Views" open="1">
+    <listitem type="821" id="Component View" open="1">
+     <listitem type="822" id="CDLTeFMNZrp5" open="1"/>
+     <listitem type="822" id="tDF2L96KKnVj" open="1"/>
+     <listitem type="822" id="72YBMP5WcQzE" open="1">
+      <listitem type="845" id="xhXzgXGeAGP8" open="1"/>
+      <listitem type="845" id="LcpVBJq9yM8d" open="1"/>
+     </listitem>
+     <listitem type="822" id="zd0ny2K62qHM" open="1"/>
+     <listitem type="819" label="Gadgetbridge" id="UpkLkm005Mtw" open="0"/>
+     <listitem type="824" id="jjfTCGwVsjy4" open="1"/>
+    </listitem>
+    <listitem type="827" id="Deployment View" open="1"/>
+    <listitem type="836" id="Entity Relationship Model" open="1"/>
+    <listitem type="801" id="Logical View" open="1">
+     <listitem type="807" label="class diagram" id="31Hk7F5Bq9ac" open="0"/>
+     <listitem type="830" id="Datatypes" open="0">
+      <listitem type="829" id="mtFc0pEuADEp" open="1"/>
+      <listitem type="829" id="MZCr2zI6yZo6" open="1"/>
+      <listitem type="829" id="tIHrr3vdvBFv" open="1"/>
+      <listitem type="829" id="hIRvrcjDBt2B" open="1"/>
+      <listitem type="829" id="tM1MfT3dGbDn" open="1"/>
+      <listitem type="829" id="xwL6qLHheXWT" open="1"/>
+      <listitem type="829" id="yRn8f2wKINF9" open="1"/>
+      <listitem type="829" id="jLVTWBskpZFo" open="1"/>
+      <listitem type="829" id="aS7VdKRbkCiP" open="1"/>
+      <listitem type="829" id="GJMsqsMRIRuv" open="1"/>
+      <listitem type="829" id="EejU1wcwrx2h" open="1"/>
+     </listitem>
+     <listitem type="817" id="VA6qSCiBtNc5" open="1"/>
+     <listitem type="817" id="AKxHpDCgOPhk" open="1"/>
+    </listitem>
+    <listitem type="802" id="Use Case View" open="1"/>
+   </listitem>
+  </listview>
+  <codegeneration>
+   <codegenerator language="C++"/>
+  </codegeneration>
+ </XMI.extensions>
+</XMI>
diff --git a/app/src/main/assets/filter_alt_plus.svg b/app/src/main/assets/filter_alt_plus.svg
new file mode 100644
index 0000000..691fe9d
--- /dev/null
+++ b/app/src/main/assets/filter_alt_plus.svg
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   enable-background="new 0 0 24 24"
+   height="24"
+   viewBox="0 0 24 24"
+   width="24"
+   version="1.1"
+   id="svg10"
+   sodipodi:docname="filter_alt_plus.svg"
+   inkscape:version="1.0 (4035a4fb49, 2020-05-01)">
+  <metadata
+     id="metadata16">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs14" />
+  <sodipodi:namedview
+     inkscape:document-rotation="0"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="1920"
+     inkscape:window-height="1042"
+     id="namedview12"
+     showgrid="false"
+     inkscape:zoom="22.185475"
+     inkscape:cx="11.10688"
+     inkscape:cy="13.735124"
+     inkscape:window-x="0"
+     inkscape:window-y="38"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="svg10" />
+  <g
+     id="g8">
+    <path
+       d="M0,0h24 M24,24H0"
+       fill="none"
+       id="path2" />
+    <path
+       d="M 5.0390625 4 C 4.2090625 4 3.74 4.949375 4.25 5.609375 C 6.27 8.199375 10 13 10 13 L 10 19 C 10 19.55 10.45 20 11 20 L 13 20 C 13.55 20 14 19.55 14 19 L 14 13 C 14 13 14.561178 12.276632 14.648438 12.164062 A 6.0985484 6.0985484 0 0 1 13.175781 10.810547 L 11.990234 12.300781 L 7 6 L 11.978516 6 A 6.0985484 6.0985484 0 0 1 12.707031 4 L 5.0390625 4 z "
+       id="path4" />
+    <path
+       d="M0,0h24v24H0V0z"
+       fill="none"
+       id="path6" />
+  </g>
+  <g
+     aria-label="+"
+     transform="matrix(2.3775617,0,0,2.3738594,-12.403443,-12.287874)"
+     id="text843"
+     style="font-style:italic;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.38504px;line-height:124%;font-family:Sans;-inkscape-font-specification:'Sans Italic';letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.420925px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1">
+    <path
+       d="m 12.25254,6.4481113 v 1.7365562 h 1.736556 V 8.7146757 H 12.25254 V 10.451232 H 11.728767 V 8.7146757 H 9.9922108 V 8.1846675 H 11.728767 V 6.4481113 Z"
+       style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.38504px;font-family:Sans;-inkscape-font-specification:'Sans, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;stroke-width:0.420925px"
+       id="path835" />
+  </g>
+</svg>
diff --git a/app/src/main/assets/fossil_hr/battery_layout.json b/app/src/main/assets/fossil_hr/battery_layout.json
new file mode 100644
index 0000000..4d35d4c
--- /dev/null
+++ b/app/src/main/assets/fossil_hr/battery_layout.json
@@ -0,0 +1 @@
+[{"id":0,"type":"complication_background","background":"#background","goal_ring":{"is_enable":"#goal_ring","end_angle":"#fi","is_invert":"#$e"},"dimension":{"type":"rigid","width":"#size.w","height":"#size.h"},"placement":{"type":"absolute","left":"#pos.Ue","top":"#pos.Qe"},"visible":true,"inversion":false},{"id":1,"parent_id":0,"type":"complication_content","icon":"icBattery","text_low":"#ci","dimension":{"type":"rigid","width":76,"height":76},"placement":{"type":"relative"},"visible":true,"inversion":"#$e"},{"id":2,"parent_id":1,"type":"solid","placement":{"type":"absolute","left":29,"top":23},"color":"#nt","dimension":{"type":"rigid","height":6,"width":"#it"},"visible":true,"inversion":false},{"id":3,"parent_id":1,"type":"image","image_name":"icBattCharging","draw_mode":1,"placement":{"type":"absolute","left":34,"top":21},"dimension":{"width":6,"height":9},"visible":"#et","inversion":false}]
diff --git a/app/src/main/assets/fossil_hr/complication_layout.json b/app/src/main/assets/fossil_hr/complication_layout.json
new file mode 100644
index 0000000..42be572
--- /dev/null
+++ b/app/src/main/assets/fossil_hr/complication_layout.json
@@ -0,0 +1 @@
+[{"id":0,"type":"complication_background","background":"#background","goal_ring":{"is_enable":"#goal_ring","end_angle":"#fi","is_invert":"#$e"},"dimension":{"type":"rigid","width":"#size.w","height":"#size.h"},"placement":{"type":"absolute","left":"#pos.Ue","top":"#pos.Qe"},"visible":true,"inversion":false},{"id":1,"parent_id":0,"type":"complication_content","icon":"#icon","text_high":"#dt","text_low":"#ci","dimension":{"type":"rigid","width":76,"height":76},"placement":{"type":"relative"},"visible":true,"inversion":"#$e"}]
\ No newline at end of file
diff --git a/app/src/main/assets/fossil_hr/default_background.png b/app/src/main/assets/fossil_hr/default_background.png
new file mode 100644
index 0000000..f50a37b
--- /dev/null
+++ b/app/src/main/assets/fossil_hr/default_background.png
Binary files differ
diff --git a/app/src/main/assets/fossil_hr/icActiveMins.rle b/app/src/main/assets/fossil_hr/icActiveMins.rle
new file mode 100644
index 0000000..847f4ab
--- /dev/null
+++ b/app/src/main/assets/fossil_hr/icActiveMins.rle
@@ -0,0 +1,3 @@
+!)
+	
+		8��
\ No newline at end of file
diff --git a/app/src/main/assets/fossil_hr/icBattCharging.rle b/app/src/main/assets/fossil_hr/icBattCharging.rle
new file mode 100644
index 0000000..5f9a848
--- /dev/null
+++ b/app/src/main/assets/fossil_hr/icBattCharging.rle
@@ -0,0 +1 @@
+	��
\ No newline at end of file
diff --git a/app/src/main/assets/fossil_hr/icBattEmpty.rle b/app/src/main/assets/fossil_hr/icBattEmpty.rle
new file mode 100644
index 0000000..1101489
--- /dev/null
+++ b/app/src/main/assets/fossil_hr/icBattEmpty.rle
Binary files differ
diff --git a/app/src/main/assets/fossil_hr/icBattery.rle b/app/src/main/assets/fossil_hr/icBattery.rle
new file mode 100644
index 0000000..f198c75
--- /dev/null
+++ b/app/src/main/assets/fossil_hr/icBattery.rle
@@ -0,0 +1 @@
+xz��
\ No newline at end of file
diff --git a/app/src/main/assets/fossil_hr/icCalories.rle b/app/src/main/assets/fossil_hr/icCalories.rle
new file mode 100644
index 0000000..3c604ab
--- /dev/null
+++ b/app/src/main/assets/fossil_hr/icCalories.rle
@@ -0,0 +1,2 @@
+9

+			6��
\ No newline at end of file
diff --git a/app/src/main/assets/fossil_hr/icHeart.rle b/app/src/main/assets/fossil_hr/icHeart.rle
new file mode 100644
index 0000000..ef8af6a
--- /dev/null
+++ b/app/src/main/assets/fossil_hr/icHeart.rle
@@ -0,0 +1,2 @@
+L
+	
;��
\ No newline at end of file
diff --git a/app/src/main/assets/fossil_hr/icRainChance.rle b/app/src/main/assets/fossil_hr/icRainChance.rle
new file mode 100644
index 0000000..8f77bd9
--- /dev/null
+++ b/app/src/main/assets/fossil_hr/icRainChance.rle
@@ -0,0 +1 @@
+S

R��
\ No newline at end of file
diff --git a/app/src/main/assets/fossil_hr/icSpO2.rle b/app/src/main/assets/fossil_hr/icSpO2.rle
new file mode 100644
index 0000000..3841e25
--- /dev/null
+++ b/app/src/main/assets/fossil_hr/icSpO2.rle
@@ -0,0 +1,4 @@
+

+	
+
+
	��
\ No newline at end of file
diff --git a/app/src/main/assets/fossil_hr/icSteps.rle b/app/src/main/assets/fossil_hr/icSteps.rle
new file mode 100644
index 0000000..745e082
--- /dev/null
+++ b/app/src/main/assets/fossil_hr/icSteps.rle
@@ -0,0 +1,5 @@
+5
+

+
+
+6��
\ No newline at end of file
diff --git a/app/src/main/assets/fossil_hr/icTrophy.rle b/app/src/main/assets/fossil_hr/icTrophy.rle
new file mode 100644
index 0000000..c53ab42
--- /dev/null
+++ b/app/src/main/assets/fossil_hr/icTrophy.rle
@@ -0,0 +1 @@
+2
8��
\ No newline at end of file
diff --git a/app/src/main/assets/fossil_hr/icWthClearDay.rle b/app/src/main/assets/fossil_hr/icWthClearDay.rle
new file mode 100644
index 0000000..5862d0e
--- /dev/null
+++ b/app/src/main/assets/fossil_hr/icWthClearDay.rle
@@ -0,0 +1,7 @@
+
+	

+
+
+
+
	
+#��
\ No newline at end of file
diff --git a/app/src/main/assets/fossil_hr/icWthClearNite.rle b/app/src/main/assets/fossil_hr/icWthClearNite.rle
new file mode 100644
index 0000000..870f958
--- /dev/null
+++ b/app/src/main/assets/fossil_hr/icWthClearNite.rle
@@ -0,0 +1,7 @@
+%

+
+
+
+	
+
+
7��
\ No newline at end of file
diff --git a/app/src/main/assets/fossil_hr/icWthCloudy.rle b/app/src/main/assets/fossil_hr/icWthCloudy.rle
new file mode 100644
index 0000000..63cc91f
--- /dev/null
+++ b/app/src/main/assets/fossil_hr/icWthCloudy.rle
@@ -0,0 +1 @@
+h

b��
\ No newline at end of file
diff --git a/app/src/main/assets/fossil_hr/icWthPartCloudyDay.rle b/app/src/main/assets/fossil_hr/icWthPartCloudyDay.rle
new file mode 100644
index 0000000..66d337d
--- /dev/null
+++ b/app/src/main/assets/fossil_hr/icWthPartCloudyDay.rle
@@ -0,0 +1,2 @@
+&


	
+
2��
\ No newline at end of file
diff --git a/app/src/main/assets/fossil_hr/icWthPartCloudyNite.rle b/app/src/main/assets/fossil_hr/icWthPartCloudyNite.rle
new file mode 100644
index 0000000..9880060
--- /dev/null
+++ b/app/src/main/assets/fossil_hr/icWthPartCloudyNite.rle
@@ -0,0 +1,2 @@
+#	
+
2��
\ No newline at end of file
diff --git a/app/src/main/assets/fossil_hr/icWthRainy.rle b/app/src/main/assets/fossil_hr/icWthRainy.rle
new file mode 100644
index 0000000..693adaa
--- /dev/null
+++ b/app/src/main/assets/fossil_hr/icWthRainy.rle
@@ -0,0 +1 @@
+8
	


<��
\ No newline at end of file
diff --git a/app/src/main/assets/fossil_hr/icWthSnowy.rle b/app/src/main/assets/fossil_hr/icWthSnowy.rle
new file mode 100644
index 0000000..5d1bf68
--- /dev/null
+++ b/app/src/main/assets/fossil_hr/icWthSnowy.rle
@@ -0,0 +1 @@
+ 
	
		��
\ No newline at end of file
diff --git a/app/src/main/assets/fossil_hr/icWthStormy.rle b/app/src/main/assets/fossil_hr/icWthStormy.rle
new file mode 100644
index 0000000..0d3ab1f
--- /dev/null
+++ b/app/src/main/assets/fossil_hr/icWthStormy.rle
@@ -0,0 +1,2 @@
+9
+53��
\ No newline at end of file
diff --git a/app/src/main/assets/fossil_hr/icWthWindy.rle b/app/src/main/assets/fossil_hr/icWthWindy.rle
new file mode 100644
index 0000000..d0697ee
--- /dev/null
+++ b/app/src/main/assets/fossil_hr/icWthWindy.rle
@@ -0,0 +1,2 @@
+8
		
+
53��
\ No newline at end of file
diff --git a/app/src/main/assets/fossil_hr/image_layout.json b/app/src/main/assets/fossil_hr/image_layout.json
new file mode 100644
index 0000000..1ed7322
--- /dev/null
+++ b/app/src/main/assets/fossil_hr/image_layout.json
@@ -0,0 +1 @@
+[{"id":0,"type":"container","direction":1,"main_alignment":1,"cross_alignment":1,"dimension":{"type":"rigid","width":240,"height":240},"placement":{"type":"absolute","left":0,"top":0},"visible":true,"inversion":false},{"id":1,"parent_id":0,"type":"image","image_name":"#name","draw_mode":1,"placement":{"type":"absolute","left":"#pos.Ue","top":"#pos.Qe"},"dimension":{"width":"#size.w","height":"#size.h"},"visible":true,"inversion":false}]
\ No newline at end of file
diff --git a/app/src/main/assets/fossil_hr/menu_layout.json b/app/src/main/assets/fossil_hr/menu_layout.json
new file mode 100644
index 0000000..6be3d62
--- /dev/null
+++ b/app/src/main/assets/fossil_hr/menu_layout.json
@@ -0,0 +1,207 @@
+[
+    {
+      "id": 0,
+      "type": "container",
+      "direction": 1,
+      "main_alignment": 0,
+      "cross_alignment": 1,
+      "dimension": {
+        "type": "rigid",
+        "width": 240,
+        "height": 240
+      },
+      "placement": {
+        "type": "absolute",
+        "left": 0,
+        "top": 0
+      },
+      "visible": true,
+      "inversion": false
+    },
+    {
+      "id": 1,
+      "parent_id": 0,
+      "type": "container",
+      "direction": 1,
+      "main_alignment": 1,
+      "cross_alignment": 2,
+      "dimension": {
+        "type": "rigid",
+        "width": 130,
+        "height": 34
+      },
+      "placement": {
+        "type": "absolute",
+        "left": 75,
+        "top": 45
+      },
+      "visible": true,
+      "inversion": false
+    },
+    {
+      "id": 2,
+      "parent_id": 1,
+      "type": "text",
+      "text": "#top_short_press_label",
+      "ppem": 17,
+      "color": 3,
+      "placement": {
+        "type": "relative"
+      },
+      "visible": true,
+      "inversion": false
+    },
+    {
+      "id": 3,
+      "parent_id": 1,
+      "type": "text",
+      "text": "#top_long_press_label",
+      "ppem": 17,
+      "color": 3,
+      "ascent": 17,
+      "placement": {
+        "type": "relative"
+      },
+      "visible": true,
+      "inversion": false
+    },
+    {
+      "id": 4,
+      "parent_id": 0,
+      "type": "container",
+      "direction": 1,
+      "main_alignment": 1,
+      "cross_alignment": 2,
+      "dimension": {
+        "type": "rigid",
+        "width": 80,
+        "height": 34
+      },
+      "placement": {
+        "type": "absolute",
+        "left": 135,
+        "top": 103
+      },
+      "visible": true,
+      "inversion": false
+    },
+    {
+      "id": 5,
+      "parent_id": 4,
+      "type": "text",
+      "text": "#middle_short_press_label",
+      "ppem": 17,
+      "color": 3,
+      "placement": {
+        "type": "relative"
+      },
+      "visible": true,
+      "inversion": false
+    },
+    {
+      "id": 6,
+      "parent_id": 4,
+      "type": "text",
+      "text": "#middle_long_press_label",
+      "ppem": 17,
+      "color": 3,
+      "ascent": 17,
+      "placement": {
+        "type": "relative"
+      },
+      "visible": true,
+      "inversion": false
+    },
+    {
+      "id": 7,
+      "parent_id": 0,
+      "type": "container",
+      "direction": 1,
+      "main_alignment": 1,
+      "cross_alignment": 2,
+      "dimension": {
+        "type": "rigid",
+        "width": 130,
+        "height": 34
+      },
+      "placement": {
+        "type": "absolute",
+        "left": 75,
+        "top": 161
+      },
+      "visible": true,
+      "inversion": false
+    },
+    {
+      "id": 8,
+      "parent_id": 7,
+      "type": "text",
+      "text": "#bottom_short_press_label",
+      "ppem": 17,
+      "color": 3,
+      "placement": {
+        "type": "relative"
+      },
+      "visible": true,
+      "inversion": false
+    },
+    {
+      "id": 9,
+      "parent_id": 7,
+      "type": "text",
+      "text": "#bottom_long_press_label",
+      "ppem": 17,
+      "color": 3,
+      "ascent": 17,
+      "placement": {
+        "type": "relative"
+      },
+      "visible": true,
+      "inversion": false
+    },
+    {
+      "id": 10,
+      "parent_id": 0,
+      "type": "text",
+      "text": "#menu_title",
+      "ppem": 25,
+      "color": 3,
+      "ascent": 35,
+      "placement": {
+        "type": "relative"
+      },
+      "visible": true,
+      "inversion": false
+    },
+    {
+        "id": 11,
+        "parent_id": 0,
+        "type": "text_page",
+        "text": "#message_to_display",
+        "ppem": 17,
+        "color": 3,
+        "ascent": 17,
+        "cross_alignment": 2,
+        "line_width": [
+            80,
+            85,
+            90,
+            90,
+            85,
+            80
+        ],
+        "dimension": {
+            "type": "rigid",
+            "width": 80,
+            "height": 100
+        },
+        "placement": {
+            "type": "absolute",
+            "left": 20,
+            "top": 60
+        },
+        "visible": true,
+        "inversion": false
+    }
+  ]
+  
\ No newline at end of file
diff --git a/app/src/main/assets/fossil_hr/navigationApp.wapp b/app/src/main/assets/fossil_hr/navigationApp.wapp
new file mode 100644
index 0000000..d581a7f
--- /dev/null
+++ b/app/src/main/assets/fossil_hr/navigationApp.wapp
Binary files differ
diff --git a/app/src/main/assets/fossil_hr/openSourceWatchface.bin b/app/src/main/assets/fossil_hr/openSourceWatchface.bin
new file mode 100644
index 0000000..6f444e6
--- /dev/null
+++ b/app/src/main/assets/fossil_hr/openSourceWatchface.bin
Binary files differ
diff --git a/app/src/main/assets/fossil_hr/widget2ndTZ.bin b/app/src/main/assets/fossil_hr/widget2ndTZ.bin
new file mode 100644
index 0000000..4291447
--- /dev/null
+++ b/app/src/main/assets/fossil_hr/widget2ndTZ.bin
Binary files differ
diff --git a/app/src/main/assets/fossil_hr/widget2ndTZ_preview.png b/app/src/main/assets/fossil_hr/widget2ndTZ_preview.png
new file mode 100644
index 0000000..0879bac
--- /dev/null
+++ b/app/src/main/assets/fossil_hr/widget2ndTZ_preview.png
Binary files differ
diff --git a/app/src/main/assets/fossil_hr/widgetActiveMins.bin b/app/src/main/assets/fossil_hr/widgetActiveMins.bin
new file mode 100644
index 0000000..8972ee5
--- /dev/null
+++ b/app/src/main/assets/fossil_hr/widgetActiveMins.bin
Binary files differ
diff --git a/app/src/main/assets/fossil_hr/widgetActiveMins_preview.png b/app/src/main/assets/fossil_hr/widgetActiveMins_preview.png
new file mode 100644
index 0000000..d99afe3
--- /dev/null
+++ b/app/src/main/assets/fossil_hr/widgetActiveMins_preview.png
Binary files differ
diff --git a/app/src/main/assets/fossil_hr/widgetBattery.bin b/app/src/main/assets/fossil_hr/widgetBattery.bin
new file mode 100644
index 0000000..ccf4540
--- /dev/null
+++ b/app/src/main/assets/fossil_hr/widgetBattery.bin
Binary files differ
diff --git a/app/src/main/assets/fossil_hr/widgetBattery_preview.png b/app/src/main/assets/fossil_hr/widgetBattery_preview.png
new file mode 100644
index 0000000..801e8bf
--- /dev/null
+++ b/app/src/main/assets/fossil_hr/widgetBattery_preview.png
Binary files differ
diff --git a/app/src/main/assets/fossil_hr/widgetCalories.bin b/app/src/main/assets/fossil_hr/widgetCalories.bin
new file mode 100644
index 0000000..e15ba04
--- /dev/null
+++ b/app/src/main/assets/fossil_hr/widgetCalories.bin
Binary files differ
diff --git a/app/src/main/assets/fossil_hr/widgetCalories_preview.png b/app/src/main/assets/fossil_hr/widgetCalories_preview.png
new file mode 100644
index 0000000..e703877
--- /dev/null
+++ b/app/src/main/assets/fossil_hr/widgetCalories_preview.png
Binary files differ
diff --git a/app/src/main/assets/fossil_hr/widgetChanceOfRain.bin b/app/src/main/assets/fossil_hr/widgetChanceOfRain.bin
new file mode 100644
index 0000000..ba16b7b
--- /dev/null
+++ b/app/src/main/assets/fossil_hr/widgetChanceOfRain.bin
Binary files differ
diff --git a/app/src/main/assets/fossil_hr/widgetChanceOfRain_preview.png b/app/src/main/assets/fossil_hr/widgetChanceOfRain_preview.png
new file mode 100644
index 0000000..c45fe90
--- /dev/null
+++ b/app/src/main/assets/fossil_hr/widgetChanceOfRain_preview.png
Binary files differ
diff --git a/app/src/main/assets/fossil_hr/widgetCustom.bin b/app/src/main/assets/fossil_hr/widgetCustom.bin
new file mode 100644
index 0000000..b41fb05
--- /dev/null
+++ b/app/src/main/assets/fossil_hr/widgetCustom.bin
Binary files differ
diff --git a/app/src/main/assets/fossil_hr/widgetCustom_preview.png b/app/src/main/assets/fossil_hr/widgetCustom_preview.png
new file mode 100644
index 0000000..abdc909
--- /dev/null
+++ b/app/src/main/assets/fossil_hr/widgetCustom_preview.png
Binary files differ
diff --git a/app/src/main/assets/fossil_hr/widgetDate.bin b/app/src/main/assets/fossil_hr/widgetDate.bin
new file mode 100644
index 0000000..aeb1440
--- /dev/null
+++ b/app/src/main/assets/fossil_hr/widgetDate.bin
Binary files differ
diff --git a/app/src/main/assets/fossil_hr/widgetDate_preview.png b/app/src/main/assets/fossil_hr/widgetDate_preview.png
new file mode 100644
index 0000000..afca5ee
--- /dev/null
+++ b/app/src/main/assets/fossil_hr/widgetDate_preview.png
Binary files differ
diff --git a/app/src/main/assets/fossil_hr/widgetHR.bin b/app/src/main/assets/fossil_hr/widgetHR.bin
new file mode 100644
index 0000000..2cf4965
--- /dev/null
+++ b/app/src/main/assets/fossil_hr/widgetHR.bin
Binary files differ
diff --git a/app/src/main/assets/fossil_hr/widgetHR_preview.png b/app/src/main/assets/fossil_hr/widgetHR_preview.png
new file mode 100644
index 0000000..5e50907
--- /dev/null
+++ b/app/src/main/assets/fossil_hr/widgetHR_preview.png
Binary files differ
diff --git a/app/src/main/assets/fossil_hr/widgetSpO2.bin b/app/src/main/assets/fossil_hr/widgetSpO2.bin
new file mode 100644
index 0000000..78bb18e
--- /dev/null
+++ b/app/src/main/assets/fossil_hr/widgetSpO2.bin
Binary files differ
diff --git a/app/src/main/assets/fossil_hr/widgetSpO2_preview.png b/app/src/main/assets/fossil_hr/widgetSpO2_preview.png
new file mode 100644
index 0000000..0638b80
--- /dev/null
+++ b/app/src/main/assets/fossil_hr/widgetSpO2_preview.png
Binary files differ
diff --git a/app/src/main/assets/fossil_hr/widgetSteps.bin b/app/src/main/assets/fossil_hr/widgetSteps.bin
new file mode 100644
index 0000000..c7ea156
--- /dev/null
+++ b/app/src/main/assets/fossil_hr/widgetSteps.bin
Binary files differ
diff --git a/app/src/main/assets/fossil_hr/widgetSteps_preview.png b/app/src/main/assets/fossil_hr/widgetSteps_preview.png
new file mode 100644
index 0000000..6bcf611
--- /dev/null
+++ b/app/src/main/assets/fossil_hr/widgetSteps_preview.png
Binary files differ
diff --git a/app/src/main/assets/fossil_hr/widgetUV.bin b/app/src/main/assets/fossil_hr/widgetUV.bin
new file mode 100644
index 0000000..0947a8d
--- /dev/null
+++ b/app/src/main/assets/fossil_hr/widgetUV.bin
Binary files differ
diff --git a/app/src/main/assets/fossil_hr/widgetUV_preview.png b/app/src/main/assets/fossil_hr/widgetUV_preview.png
new file mode 100644
index 0000000..38b25d8
--- /dev/null
+++ b/app/src/main/assets/fossil_hr/widgetUV_preview.png
Binary files differ
diff --git a/app/src/main/assets/fossil_hr/widgetWeather.bin b/app/src/main/assets/fossil_hr/widgetWeather.bin
new file mode 100644
index 0000000..7cd0913
--- /dev/null
+++ b/app/src/main/assets/fossil_hr/widgetWeather.bin
Binary files differ
diff --git a/app/src/main/assets/fossil_hr/widgetWeather_preview.png b/app/src/main/assets/fossil_hr/widgetWeather_preview.png
new file mode 100644
index 0000000..b6d54f4
--- /dev/null
+++ b/app/src/main/assets/fossil_hr/widgetWeather_preview.png
Binary files differ
diff --git a/app/src/main/assets/fossil_hr/widget_bg_dashed_circle.png b/app/src/main/assets/fossil_hr/widget_bg_dashed_circle.png
new file mode 100644
index 0000000..ef10529
--- /dev/null
+++ b/app/src/main/assets/fossil_hr/widget_bg_dashed_circle.png
Binary files differ
diff --git a/app/src/main/assets/fossil_hr/widget_bg_double_circle.png b/app/src/main/assets/fossil_hr/widget_bg_double_circle.png
new file mode 100644
index 0000000..ec18cf8
--- /dev/null
+++ b/app/src/main/assets/fossil_hr/widget_bg_double_circle.png
Binary files differ
diff --git a/app/src/main/assets/fossil_hr/widget_bg_error.rle b/app/src/main/assets/fossil_hr/widget_bg_error.rle
new file mode 100644
index 0000000..0c34028
--- /dev/null
+++ b/app/src/main/assets/fossil_hr/widget_bg_error.rle
@@ -0,0 +1,15 @@
+LLi
	
+	
6
		
0		,	 	(	$	$
	
	
!		

		

+	

	
+

	

	





	 	


"


	&	

	%

		#

+		!
	


	

		
		
	
		
+

	
	
			
	
			
	
		

	
	
		
	
	
+

+
	
	

	
	

	
		

	
		

		
	


	
	
		
	
			

	
	

	
	
			

			
		

		


	


	


		
	
			

							
	
		
	
				
			
		
	

			
	
	
		
					

	
	

+		
	

			
	
+	
		
	
			
					
				
		
	

+			
			
	

	


		!
			#

+
	%


	&	


"


	 		





	

	


+	

	
+
		

		!
	
	
$	$	(	 	,		0
		
6
	
+	
i��
\ No newline at end of file
diff --git a/app/src/main/assets/fossil_hr/widget_bg_thin_circle.png b/app/src/main/assets/fossil_hr/widget_bg_thin_circle.png
new file mode 100644
index 0000000..1fec781
--- /dev/null
+++ b/app/src/main/assets/fossil_hr/widget_bg_thin_circle.png
Binary files differ
diff --git a/app/src/main/assets/galaxy_buds.svg b/app/src/main/assets/galaxy_buds.svg
new file mode 100644
index 0000000..1c3dd35
--- /dev/null
+++ b/app/src/main/assets/galaxy_buds.svg
@@ -0,0 +1,86 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   width="35.69236mm"
+   height="42.231304mm"
+   viewBox="0 0 35.69236 42.231304"
+   version="1.1"
+   id="svg5"
+   inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)"
+   sodipodi:docname="galaxy_buds.svg"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg">
+  <sodipodi:namedview
+     id="namedview7"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageshadow="2"
+     inkscape:pageopacity="0.0"
+     inkscape:pagecheckerboard="0"
+     inkscape:document-units="mm"
+     showgrid="false"
+     inkscape:zoom="2.5407127"
+     inkscape:cx="24.599397"
+     inkscape:cy="100.56234"
+     inkscape:window-width="1920"
+     inkscape:window-height="1035"
+     inkscape:window-x="0"
+     inkscape:window-y="45"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="layer1" />
+  <defs
+     id="defs2" />
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(-58.009475,-114.68386)">
+    <g
+       id="g11302">
+      <path
+         style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         d="m 62.481984,130.19859 c -1.422876,1.97773 -0.400592,5.02874 3.539949,5.03091"
+         id="path10585"
+         sodipodi:nodetypes="cc" />
+      <path
+         style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+         d="m 66.103138,121.7957 c 2.666757,-1.07007 4.567397,-1.03074 7.019236,-0.32613 1.148282,0.32999 2.049576,1.68227 2.142569,2.8734 0.428995,5.49489 -2.903496,8.17341 -6.263367,7.93085 -2.05934,-0.14867 -4.130587,-2.74489 -4.627575,-5.01288 -0.41816,-1.90826 -0.08389,-4.73774 1.729137,-5.46524 z"
+         id="path5256"
+         sodipodi:nodetypes="ssssss" />
+      <path
+         style="fill:none;stroke:#000000;stroke-width:0.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         d="m 68.424274,119.2465 c 3.534371,-0.50931 7.153563,0.99406 8.625608,4.36799 1.972926,4.52216 -0.871682,11.86623 -6.124093,12.64208 -9.667201,1.42779 -12.807218,-15.52506 -2.501515,-17.01007"
+         id="path3567" />
+      <path
+         style="fill:none;stroke:#000000;stroke-width:0.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         d="m 70.54094,115.0684 c -1.528272,0.40147 -2.479848,2.50023 -3.65606,3.48042 -1.57278,1.31064 -3.329209,2.02976 -4.365471,3.91113 -0.788477,1.43151 -0.572308,3.47576 -1.409777,4.73562 -0.43788,0.65873 -1.309196,0.96445 -1.803823,1.61735 -1.540471,2.03338 -1.370542,5.10243 0.479425,6.88871 1.219739,1.17764 3.141402,1.6612 4.790555,1.28077 0.702868,-0.16221 1.380432,-0.74583 2.116667,-0.68541 1.719445,0.14105 2.786803,0.97944 4.618181,0.50261 6.467763,-1.6839 8.890577,-9.24373 7.458556,-15.10934 -0.757382,-3.10205 -4.540443,-7.59068 -8.228253,-6.62186"
+         id="path3559" />
+    </g>
+    <g
+       id="g11302-3"
+       transform="matrix(-1,0,0,1,151.71131,19.553843)">
+      <path
+         style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         d="m 62.481984,130.19859 c -1.422876,1.97773 -0.400592,5.02874 3.539949,5.03091"
+         id="path10585-6"
+         sodipodi:nodetypes="cc" />
+      <path
+         style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+         d="m 66.103138,121.7957 c 2.666757,-1.07007 4.567397,-1.03074 7.019236,-0.32613 1.148282,0.32999 2.049576,1.68227 2.142569,2.8734 0.428995,5.49489 -2.903496,8.17341 -6.263367,7.93085 -2.05934,-0.14867 -4.130587,-2.74489 -4.627575,-5.01288 -0.41816,-1.90826 -0.08389,-4.73774 1.729137,-5.46524 z"
+         id="path5256-7"
+         sodipodi:nodetypes="ssssss" />
+      <path
+         style="fill:none;stroke:#000000;stroke-width:0.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         d="m 68.424274,119.2465 c 3.534371,-0.50931 7.153563,0.99406 8.625608,4.36799 1.972926,4.52216 -0.871682,11.86623 -6.124093,12.64208 -9.667201,1.42779 -12.807218,-15.52506 -2.501515,-17.01007"
+         id="path3567-5" />
+      <path
+         style="fill:none;stroke:#000000;stroke-width:0.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         d="m 70.54094,115.0684 c -1.528272,0.40147 -2.479848,2.50023 -3.65606,3.48042 -1.57278,1.31064 -3.329209,2.02976 -4.365471,3.91113 -0.788477,1.43151 -0.572308,3.47576 -1.409777,4.73562 -0.43788,0.65873 -1.309196,0.96445 -1.803823,1.61735 -1.540471,2.03338 -1.370542,5.10243 0.479425,6.88871 1.219739,1.17764 3.141402,1.6612 4.790555,1.28077 0.702868,-0.16221 1.380432,-0.74583 2.116667,-0.68541 1.719445,0.14105 2.786803,0.97944 4.618181,0.50261 6.467763,-1.6839 8.890577,-9.24373 7.458556,-15.10934 -0.757382,-3.10205 -4.540443,-7.59068 -8.228253,-6.62186"
+         id="path3559-3" />
+    </g>
+  </g>
+</svg>
diff --git a/app/src/main/assets/gps_edit.svg b/app/src/main/assets/gps_edit.svg
new file mode 100644
index 0000000..f189dbf
--- /dev/null
+++ b/app/src/main/assets/gps_edit.svg
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   height="24"
+   viewBox="0 0 23.999999 24"
+   width="24"
+   fill="#000000"
+   version="1.1"
+   id="svg1360"
+   sodipodi:docname="gps_edit.svg"
+   inkscape:version="1.0.2 (e86c870879, 2021-01-15)">
+  <metadata
+     id="metadata1366">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs1364" />
+  <sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="1920"
+     inkscape:window-height="1042"
+     id="namedview1362"
+     showgrid="false"
+     inkscape:zoom="22.627417"
+     inkscape:cx="16.91793"
+     inkscape:cy="12.554585"
+     inkscape:window-x="0"
+     inkscape:window-y="38"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="svg1360"
+     inkscape:document-rotation="0" />
+  <path
+     d="M 0,0 H 24 V 23.999999 H 0 Z"
+     fill="none"
+     id="path1356" />
+  <path
+     id="path1358"
+     d="M 11 1 L 11 3.0605469 C 6.83 3.5205469 3.5205469 6.8300004 3.0605469 11 L 1 11 L 1 13 L 3.0605469 13 C 3.1662398 13.958129 3.428073 14.867751 3.8105469 15.710938 L 5.3476562 14.171875 C 5.1249836 13.487952 5 12.758847 5 12 C 5 8.1300004 8.13 5 12 5 C 12.757962 5 13.486688 5.1235288 14.169922 5.3457031 L 15.707031 3.8085938 C 14.864949 3.4271153 13.956627 3.166074 13 3.0605469 L 13 1 L 11 1 z M 20.1875 8.2851562 L 18.650391 9.8203125 C 18.874661 10.50644 19 11.238321 19 12 C 19 15.869999 15.87 19 12 19 C 11.235489 19 10.500828 18.87431 9.8125 18.648438 L 8.2753906 20.183594 C 9.1223377 20.569471 10.036747 20.833195 11 20.939453 L 11 23 L 13 23 L 13 20.939453 C 17.17 20.479453 20.479453 17.17 20.939453 13 L 23 13 L 23 11 L 20.939453 11 C 20.833594 10.040369 20.57097 9.1294462 20.1875 8.2851562 z " />
+  <path
+     d="M 4.6773789,-4.5166116 H 28.677379 V 19.483388 H 4.6773789 Z"
+     fill="none"
+     id="path1344" />
+  <path
+     d="m 14.05875,9.0199994 0.92,0.92 -9.0600003,9.0599996 h -0.92 v -0.92 L 14.05875,9.0199993 m 3.6,-6.0199997 c -0.25,0 -0.51,0.1 -0.7,0.29 l -1.83,1.83 3.75,3.7499997 1.83,-1.8299997 c 0.39,-0.39 0.39,-1.02 0,-1.41 l -2.34,-2.34 c -0.2,-0.2 -0.45,-0.29 -0.71,-0.29 z m -3.6,3.19 L 2.9987497,17.249999 v 3.75 h 3.75 L 17.80875,9.9399994 Z"
+     id="path948" />
+</svg>
diff --git a/app/src/main/assets/ic_activity_bike_trainer.svg b/app/src/main/assets/ic_activity_bike_trainer.svg
new file mode 100644
index 0000000..9c4cebc
--- /dev/null
+++ b/app/src/main/assets/ic_activity_bike_trainer.svg
@@ -0,0 +1,86 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
+   sodipodi:docname="ic_activity_bike_trainer.svg"
+   version="1.1"
+   id="vector"
+   viewBox="0 0 24 24">
+  <metadata
+     id="metadata8">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs6" />
+  <sodipodi:namedview
+     inkscape:current-layer="vector"
+     inkscape:window-maximized="1"
+     inkscape:window-y="38"
+     inkscape:window-x="0"
+     inkscape:cy="10.532374"
+     inkscape:cx="4.6563454"
+     inkscape:zoom="22.185475"
+     inkscape:guide-bbox="true"
+     showguides="true"
+     showgrid="false"
+     id="namedview4"
+     inkscape:window-height="1042"
+     inkscape:window-width="1920"
+     inkscape:pageshadow="2"
+     inkscape:pageopacity="0"
+     guidetolerance="10"
+     gridtolerance="10"
+     objecttolerance="10"
+     borderopacity="1"
+     bordercolor="#666666"
+     pagecolor="#ffffff">
+    <sodipodi:guide
+       id="guide833"
+       orientation="0,-1"
+       position="12.722976,2.3032605" />
+  </sodipodi:namedview>
+  <path
+     sodipodi:nodetypes="cccccsccscccccccsssss"
+     fill="#000000"
+     d="m 13.748344,10.511707 h 4.2 V 8.7117075 h -3.2 l -1.94,-3.27 c -0.29,-0.5 -0.86,-0.83 -1.46,-0.83 -0.47,0 -0.9,0.19 -1.2,0.5 l -3.6999997,3.69 c -0.31,0.31 -0.5,0.71 -0.5,1.2099995 0,0.63 0.33,1.16 0.85,1.47 l 3.3499997,2.03 0.08147,2.922976 h 1.8 l -0.08147,-4.422976 -2.2499997,-1.65 2.3199997,-2.3499995 m 2.93,-2.7 c 1,0 1.8,-0.8 1.8,-1.8 0,-1 -0.8,-1.8 -1.8,-1.8 -1,0 -1.8,0.8 -1.8,1.8 0,1 0.8,1.8 1.8,1.8 z"
+     id="path" />
+  <path
+     sodipodi:nodetypes="ccccc"
+     id="path835"
+     d="M 1.8662052,21.696739 H 21.648561 l -6.442351,-10.036462 3.446621,-0.06429 1.618603,-1.9916461"
+     style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+  <path
+     id="path837"
+     d="M 4.4629733,21.696739 16.703565,14.024667"
+     style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+  <circle
+     r="2.5"
+     cy="17.809311"
+     cx="19.149721"
+     id="path855"
+     style="opacity:1;fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.997028;paint-order:fill markers stroke" />
+  <path
+     id="path857"
+     d="m 5.832492,11.992861 2.739362,0.106092"
+     style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+  <circle
+     r="1"
+     style="opacity:1;fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.997028;paint-order:fill markers stroke"
+     id="path855-3"
+     cx="9.7781563"
+     cy="18.29324" />
+</svg>
diff --git a/app/src/main/assets/ic_activity_eliptical.svg b/app/src/main/assets/ic_activity_eliptical.svg
new file mode 100644
index 0000000..91809b1
--- /dev/null
+++ b/app/src/main/assets/ic_activity_eliptical.svg
@@ -0,0 +1,86 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
+   sodipodi:docname="ic_activity_eliptical.svg"
+   version="1.1"
+   id="vector"
+   viewBox="0 0 24 24">
+  <metadata
+     id="metadata8">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs6" />
+  <sodipodi:namedview
+     inkscape:current-layer="vector"
+     inkscape:window-maximized="1"
+     inkscape:window-y="38"
+     inkscape:window-x="0"
+     inkscape:cy="18.099999"
+     inkscape:cx="19.838246"
+     inkscape:zoom="15.6875"
+     inkscape:guide-bbox="true"
+     showguides="true"
+     showgrid="false"
+     id="namedview4"
+     inkscape:window-height="1042"
+     inkscape:window-width="1920"
+     inkscape:pageshadow="2"
+     inkscape:pageopacity="0"
+     guidetolerance="10"
+     gridtolerance="10"
+     objecttolerance="10"
+     borderopacity="1"
+     bordercolor="#666666"
+     pagecolor="#ffffff">
+    <sodipodi:guide
+       id="guide835"
+       orientation="0,-1"
+       position="10.705202,0.53870408" />
+    <sodipodi:guide
+       id="guide837"
+       orientation="1,0"
+       position="0.48455126,0.20064505" />
+  </sodipodi:namedview>
+  <path
+     sodipodi:nodetypes="cssscccccccccccccsccccc"
+     style="stroke-width:0.999999"
+     fill="#000000"
+     d="M 12.567421,5.2119378 C 13.537916,5.3830625 14.492582,4.7067939 14.662138,3.7452018 14.837972,2.7479951 14.165897,1.8216099 13.195401,1.6504853 12.207098,1.4762208 11.276519,2.1200148 11.100686,3.1172216 10.93113,4.0788137 11.579118,5.0376734 12.567421,5.2119378 M 7.1741201,17.003424 8.7521215,13.26063 l 1.5646755,2.111986 -0.9419703,5.342178 1.7807263,0.31399 1.177463,-6.677723 -1.564676,-2.111986 1.014106,-2.5753205 c 0.913076,1.5380665 4.42584,0.3836265 6.360238,-0.3904418 l -0.0021,-1.7019002 C 16.393083,8.0559499 13.64167,9.5325619 13.143408,8.1410804 L 12.501098,6.5773115 C 12.242289,5.9624884 11.76773,5.5299534 11.153379,5.4216269 10.877367,5.3729584 10.695638,5.4143579 10.419625,5.3656903 8.9321383,5.7066865 7.2727228,11.675325 6.034151,14.617469 l -3.093174,7.077475 1.9665017,0.144008 z"
+     id="path" />
+  <path
+     sodipodi:nodetypes="ccc"
+     id="path839"
+     d="M 21.159329,22.868639 17.313949,11.569004 21.332334,6.8627307"
+     style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+  <path
+     style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+     d="M 22.604212,23.183313 15.844554,11.825186 19.319953,5.7636432"
+     id="path839-3"
+     sodipodi:nodetypes="ccc" />
+  <path
+     sodipodi:nodetypes="cc"
+     id="path874"
+     d="M 22.604212,23.183313 7.1729532,20.181342"
+     style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+  <path
+     sodipodi:nodetypes="cc"
+     id="path876"
+     d="M 22.604212,23.183313 2.6116474,22.118514"
+     style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+</svg>
diff --git a/app/src/main/assets/ic_activity_rope_jump.svg b/app/src/main/assets/ic_activity_rope_jump.svg
new file mode 100644
index 0000000..05f0ddf
--- /dev/null
+++ b/app/src/main/assets/ic_activity_rope_jump.svg
@@ -0,0 +1,90 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
+   sodipodi:docname="ic_activity_rope_jump.svg"
+   version="1.1"
+   id="vector"
+   viewBox="0 0 24 24">
+  <metadata
+     id="metadata8">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs6" />
+  <sodipodi:namedview
+     inkscape:current-layer="vector"
+     inkscape:window-maximized="1"
+     inkscape:window-y="38"
+     inkscape:window-x="0"
+     inkscape:cy="14.667682"
+     inkscape:cx="11.804698"
+     inkscape:zoom="22.185475"
+     inkscape:guide-bbox="true"
+     showguides="true"
+     showgrid="false"
+     id="namedview4"
+     inkscape:window-height="1042"
+     inkscape:window-width="1920"
+     inkscape:pageshadow="2"
+     inkscape:pageopacity="0"
+     guidetolerance="10"
+     gridtolerance="10"
+     objecttolerance="10"
+     borderopacity="1"
+     bordercolor="#666666"
+     pagecolor="#ffffff">
+    <sodipodi:guide
+       id="guide833"
+       orientation="1,0"
+       position="5.9920319,5.2589641" />
+    <sodipodi:guide
+       id="guide835"
+       orientation="1,0"
+       position="7.7768924,2.7729084" />
+    <sodipodi:guide
+       id="guide837"
+       orientation="0,-1"
+       position="8.3187251,10.199203" />
+    <sodipodi:guide
+       id="guide841"
+       orientation="1,0"
+       position="10.533068,1.904977" />
+    <sodipodi:guide
+       id="guide843"
+       orientation="0,-1"
+       position="5.9920319,16.953232" />
+    <sodipodi:guide
+       id="guide845"
+       orientation="0,-1"
+       position="8.1912351,20.270916" />
+    <sodipodi:guide
+       id="guide847"
+       orientation="0,-1"
+       position="15.618327,4.7080987" />
+  </sodipodi:namedview>
+  <path
+     sodipodi:nodetypes="ccccscccccccccccccccsssss"
+     fill="#000000"
+     d="M 18.984981,12.410176 19.73807,11.048671 16.950341,9.4438816 14.113239,5.3276854 c -0.3,-0.5 -1.911998,-0.4887596 -2.531878,-0.5009799 -0.640961,-0.012636 -1.508142,0.0073 -2.0091581,0.5009799 L 7.4123098,9.2551763 4.157218,11.49146 5.1898016,12.474086 8.7091471,10.546488 9.5722029,8.6453695 9.5407575,22.105654 l 1.7848605,0.01321 v -8.318067 h 0.956175 l 0.05554,5.491104 h 1.800001 l -0.05554,-5.491099 0.03144,-5.1554326 1.32336,1.7364416 M 11.688422,4.3955121 c 1,0 1.8,-0.8 1.8,-1.8 0,-1 -0.8,-1.79999996 -1.8,-1.79999996 -1,0 -1.7999996,0.79999996 -1.7999996,1.79999996 0,1 0.7999996,1.8 1.7999996,1.8 z"
+     id="path" />
+  <path
+     sodipodi:nodetypes="czc"
+     id="path851"
+     d="m 4.4844489,12.560347 c 0,0 3.9068788,7.966617 7.7302321,8.362194 3.823353,0.395577 7.636681,-8.779329 7.636681,-8.779329"
+     style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+</svg>
diff --git a/app/src/main/assets/ic_activity_threadmill.svg b/app/src/main/assets/ic_activity_threadmill.svg
new file mode 100644
index 0000000..ad5cff2
--- /dev/null
+++ b/app/src/main/assets/ic_activity_threadmill.svg
@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
+   sodipodi:docname="ic_activity_threadmill.svg"
+   version="1.1"
+   id="vector"
+   viewBox="0 0 24 24">
+  <metadata
+     id="metadata8">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs6" />
+  <sodipodi:namedview
+     inkscape:current-layer="vector"
+     inkscape:window-maximized="1"
+     inkscape:window-y="38"
+     inkscape:window-x="0"
+     inkscape:cy="18.620603"
+     inkscape:cx="19.661134"
+     inkscape:zoom="15.6875"
+     inkscape:guide-bbox="true"
+     showguides="true"
+     showgrid="false"
+     id="namedview4"
+     inkscape:window-height="1042"
+     inkscape:window-width="1920"
+     inkscape:pageshadow="2"
+     inkscape:pageopacity="0"
+     guidetolerance="10"
+     gridtolerance="10"
+     objecttolerance="10"
+     borderopacity="1"
+     bordercolor="#666666"
+     pagecolor="#ffffff">
+    <sodipodi:guide
+       id="guide835"
+       orientation="0,-1"
+       position="10.705202,0.53870408" />
+    <sodipodi:guide
+       id="guide837"
+       orientation="1,0"
+       position="0.48455126,0.20064505" />
+  </sodipodi:namedview>
+  <path
+     sodipodi:nodetypes="cssscccccccccccccscccccccccc"
+     style="stroke-width:0.999999"
+     fill="#000000"
+     d="M 12.567421,5.2119378 C 13.537916,5.3830625 14.492582,4.7067939 14.662138,3.7452018 14.837972,2.7479951 14.165897,1.8216099 13.195401,1.6504853 12.207098,1.4762208 11.276519,2.1200148 11.100686,3.1172216 10.93113,4.0788137 11.579118,5.0376734 12.567421,5.2119378 M 7.1741201,17.003424 8.7521215,13.26063 l 1.5646755,2.111986 -0.9419703,5.342178 1.7807263,0.31399 1.177463,-6.677723 -1.564676,-2.111986 1.014106,-2.5753205 c 0.913076,1.5380665 4.42584,0.3836265 6.360238,-0.3904418 l -0.0021,-1.7019002 C 16.393083,8.0559499 13.64167,9.5325619 13.143408,8.1410804 L 12.501098,6.5773115 C 12.242289,5.9624884 11.76773,5.5299534 11.153379,5.4216269 10.877367,5.3729584 10.695638,5.4143579 10.419625,5.3656903 L 5.4532525,6.5096846 4.712236,10.712199 6.4929622,11.026189 7.0298852,7.9811466 8.7335316,7.6389135 6.034151,14.617469 1.8283669,12.95783 1.1582315,14.67576 Z"
+     id="path" />
+  <path
+     sodipodi:nodetypes="ccccc"
+     id="path833"
+     d="M 0.48455126,22.642102 20.905809,19.702714 23.383671,23.461296 H 0.48455126 Z"
+     style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+  <path
+     sodipodi:nodetypes="ccc"
+     id="path839"
+     d="m 22.604212,23.183313 -6.253574,-9.471005 3.452827,-1.399809"
+     style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+</svg>
diff --git a/app/src/main/assets/ic_device_flipper.svg b/app/src/main/assets/ic_device_flipper.svg
new file mode 100644
index 0000000..1712dcc
--- /dev/null
+++ b/app/src/main/assets/ic_device_flipper.svg
@@ -0,0 +1,1772 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   version="1.1"
+   id="svg2"
+   width="28.740126"
+   height="28.740126"
+   viewBox="0 0 28.740126 28.740126"
+   sodipodi:docname="ic_device_flipper.svg"
+   inkscape:version="1.2.1 (9c6d41e410, 2022-07-14)"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:dc="http://purl.org/dc/elements/1.1/">
+  <metadata
+     id="metadata8">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs6">
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 14.370063 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="28.740126 : 14.370063 : 1"
+       inkscape:persp3d-origin="14.370063 : 9.580042 : 1"
+       id="perspective903" />
+  </defs>
+  <sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="1920"
+     inkscape:window-height="1035"
+     id="namedview4"
+     showgrid="false"
+     showguides="true"
+     inkscape:guide-bbox="true"
+     inkscape:zoom="22.627418"
+     inkscape:cx="16.793785"
+     inkscape:cy="13.589708"
+     inkscape:window-x="0"
+     inkscape:window-y="45"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="layer2"
+     inkscape:pagecheckerboard="0"
+     inkscape:showpageshadow="2"
+     inkscape:deskcolor="#d1d1d1">
+    <inkscape:grid
+       type="xygrid"
+       id="grid972" />
+  </sodipodi:namedview>
+  <g
+     inkscape:groupmode="layer"
+     id="layer1"
+     inkscape:label="Background"
+     style="display:inline">
+    <g
+       inkscape:groupmode="layer"
+       id="layer4"
+       inkscape:label="Bottom"
+       style="display:inline"
+       sodipodi:insensitive="true">
+      <rect
+         style="display:inline;fill:#1f7fdb;fill-opacity:1;stroke:none;stroke-width:3.57115;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         id="rect817-5"
+         width="22.819572"
+         height="21.904638"
+         x="2.9239759"
+         y="3.8768768"
+         ry="0.9472276" />
+    </g>
+    <g
+       inkscape:groupmode="layer"
+       id="layer3"
+       inkscape:label="Middle"
+       style="display:inline"
+       sodipodi:insensitive="true">
+      <rect
+         style="display:inline;fill:#4dabf5;fill-opacity:1;stroke:none;stroke-width:3.57115;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         id="rect817-7"
+         width="22.819572"
+         height="21.904638"
+         x="2.9317887"
+         y="3.0349438"
+         ry="0.9472276" />
+    </g>
+    <g
+       inkscape:groupmode="layer"
+       id="layer2"
+       inkscape:label="Top"
+       style="display:inline">
+      <rect
+         style="fill:#2196f3;fill-opacity:1;stroke:none;stroke-width:3.57115;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         id="rect817"
+         width="22.819572"
+         height="21.904638"
+         x="2.9238882"
+         y="3.4130721"
+         ry="0.9472276" />
+      <g
+         id="dolphin_00000004540763674616766980000002934822554439179943_"
+         transform="matrix(0.41463415,0,0,0.41463415,-16.349253,2.403196)"
+         style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+	<g
+   id="g1524"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<polygon
+   points="87.2,36.4 87.8,36.4 87.8,35.8 87.2,35.8 86.7,35.8 86.1,35.8 85.6,35.8 85,35.8 85,36.4 85.6,36.4 86.1,36.4 86.7,36.4 "
+   id="polygon1522"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1528"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<polygon
+   points="69.5,36.3 70,36.3 70.6,36.3 70.6,35.8 70,35.8 69.5,35.8 "
+   id="polygon1526"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1532"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<rect
+   x="59"
+   y="35.700001"
+   transform="matrix(0.00513464,-1,1,0.00513464,22.9529,95.0224)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1530"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1536"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<rect
+   x="57.799999"
+   y="35.700001"
+   transform="matrix(0.00513464,-1,1,0.00513464,21.8553,93.9078)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1534"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1540"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<rect
+   x="56.700001"
+   y="35.700001"
+   transform="matrix(0.00513464,-1,1,0.00513464,20.7578,92.7932)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1538"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1544"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<polygon
+   points="87.8,35.3 87.8,35.8 88.3,35.8 88.9,35.9 89.5,35.9 90,35.9 90,35.3 89.5,35.3 88.9,35.3 88.3,35.3 88.3,34.7 87.8,34.7 87.2,34.7 87.2,35.3 "
+   id="polygon1542"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1548"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<rect
+   x="68.900002"
+   y="35.200001"
+   transform="matrix(0.00513464,-1,1,0.00513464,33.3884,104.5052)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1546"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1552"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<rect
+   x="59.5"
+   y="35.099998"
+   transform="matrix(0.00513464,-1,1,0.00513464,24.059,95.031)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1550"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1556"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<rect
+   x="58.400002"
+   y="35.099998"
+   transform="matrix(0.00513464,-1,1,0.00513464,22.9614,93.9163)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1554"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1560"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<rect
+   x="57.299999"
+   y="35.099998"
+   transform="matrix(0.00513464,-1,1,0.00513464,21.8638,92.8017)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1558"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1564"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<rect
+   x="56.200001"
+   y="35.099998"
+   transform="matrix(0.00513464,-1,1,0.00513464,20.7663,91.6871)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1562"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1568"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<polygon
+   points="91.1,35.3 91.7,35.3 91.7,34.8 91.1,34.8 90.6,34.8 90,34.7 90,35.3 90.6,35.3 "
+   id="polygon1566"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1572"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<rect
+   x="60.099998"
+   y="34.599998"
+   transform="matrix(0.00513464,-1,1,0.00513464,25.1651,95.0395)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1570"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1576"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<rect
+   x="59"
+   y="34.599998"
+   transform="matrix(0.00513464,-1,1,0.00513464,24.0675,93.9249)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1574"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1580"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<rect
+   x="57.900002"
+   y="34.599998"
+   transform="matrix(0.00513464,-1,1,0.00513464,22.9699,92.8102)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1578"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1584"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<rect
+   x="56.700001"
+   y="34.599998"
+   transform="matrix(0.00513464,-1,1,0.00513464,21.8724,91.6956)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1582"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1588"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<rect
+   x="55.599998"
+   y="34.599998"
+   transform="matrix(0.00513464,-1,1,0.00513464,20.7748,90.581)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1586"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1592"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<polygon
+   points="92.2,34.8 92.8,34.8 92.8,34.2 92.2,34.2 91.7,34.2 91.7,34.8 "
+   id="polygon1590"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1596"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<polygon
+   points="87.2,34.2 86.7,34.2 86.1,34.2 86.1,34.7 86.7,34.7 87.2,34.7 "
+   id="polygon1594"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1600"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<rect
+   x="59.5"
+   y="34"
+   transform="matrix(0.00513464,-1,1,0.00513464,25.1736,93.9334)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1598"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1604"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<rect
+   x="58.400002"
+   y="34"
+   transform="matrix(0.00513464,-1,1,0.00513464,24.076,92.8188)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1602"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1608"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<rect
+   x="57.299999"
+   y="34"
+   transform="matrix(0.00513464,-1,1,0.00513464,22.9785,91.7041)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1606"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1612"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<rect
+   x="56.200001"
+   y="34"
+   transform="matrix(0.00513464,-1,1,0.00513464,21.8809,90.5895)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1610"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1616"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<rect
+   x="55.099998"
+   y="34"
+   transform="matrix(0.00513464,-1,1,0.00513464,20.7833,89.4749)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1614"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1620"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<rect
+   x="59"
+   y="33.5"
+   transform="matrix(0.00513464,-1,1,0.00513464,25.1821,92.8273)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1618"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1624"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<rect
+   x="57.900002"
+   y="33.5"
+   transform="matrix(0.00513464,-1,1,0.00513464,24.0846,91.7127)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1622"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1628"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<rect
+   x="56.700001"
+   y="33.5"
+   transform="matrix(0.00513464,-1,1,0.00513464,22.987,90.598)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1626"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1632"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<rect
+   x="55.599998"
+   y="33.5"
+   transform="matrix(0.00513464,-1,1,0.00513464,21.8894,89.4834)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1630"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1636"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<polygon
+   points="85,31.9 85.6,32 85.6,31.4 86.1,31.4 86.2,30.8 86.7,30.8 86.7,30.3 87.3,30.3 87.3,29.7 87.8,29.7 87.8,29.2 88.4,29.2 88.4,28.6 88.9,28.6 88.9,28.1 89.5,28.1 89.5,27.5 90.1,27.5 90.1,27 90.6,27 90.6,26.4 91.2,26.4 91.2,25.9 91.7,25.9 91.7,25.3 92.3,25.3 92.3,24.8 92.8,24.8 92.8,24.2 93.4,24.2 93.4,23.7 93.4,23.1 93.4,22.6 93.4,22 92.9,22 92.9,21.5 92.3,21.5 91.7,21.4 91.2,21.4 91.2,20.9 90.6,20.9 90.1,20.9 89.5,20.9 89,20.9 88.4,20.9 87.9,20.9 87.3,20.9 86.8,20.9 86.2,20.9 86.2,21.4 85.6,21.4 85.1,21.4 85.1,22 84.5,22 84,22 84,22.5 83.4,22.5 82.9,22.5 82.9,22 82.3,22 82.3,21.4 82.3,20.8 81.8,20.8 81.8,20.3 81.8,19.7 81.2,19.7 81.2,19.2 80.7,19.2 80.7,18.6 80.1,18.6 80.1,18.1 79.6,18.1 79.6,17.5 79,17.5 78.5,17.5 78.5,16.9 77.9,16.9 77.4,16.9 77.4,16.4 76.8,16.4 76.2,16.4 75.7,16.4 75.7,15.8 75.1,15.8 74.6,15.8 74,15.8 73.5,15.8 72.9,15.8 72.9,15.3 72.4,15.2 71.8,15.2 71.3,15.2 70.7,15.2 70.2,15.2 69.6,15.2 69,15.2 68.5,15.2 67.9,15.2 67.4,15.2 66.8,15.2 66.3,15.2 65.7,15.2 65.2,15.2 64.6,15.2 64.6,15.8 64,15.8 63.5,15.8 62.9,15.8 62.4,15.8 62.4,16.3 61.8,16.3 61.3,16.3 60.7,16.3 60.7,16.9 60.2,16.9 60.2,17.4 59.6,17.4 59.1,17.4 59,18 58.5,18 57.9,17.9 57.9,18.5 57.4,18.5 57.4,19.1 56.8,19.1 56.8,19.6 56.3,19.6 56.3,20.2 55.7,20.2 55.7,20.7 55.2,20.7 55.1,21.3 55.1,21.8 54.6,21.8 54.6,22.4 54.6,22.9 54,22.9 54,23.5 54,24 53.5,24 53.5,24.6 53.5,25.1 53.5,25.7 54,25.7 54,25.1 54,24.6 54.6,24.6 54.6,24 54.6,23.5 55.1,23.5 55.1,22.9 55.1,22.4 55.7,22.4 55.7,21.8 55.7,21.3 56.3,21.3 56.3,20.7 56.8,20.7 56.8,20.2 57.4,20.2 57.4,19.6 57.9,19.6 57.9,19.1 58.5,19.1 58.5,18.5 59,18.5 59.6,18.5 59.6,18 60.2,18 60.7,18 60.7,17.4 61.3,17.4 61.8,17.4 62.4,17.4 62.9,17.4 63.5,17.4 64,17.4 64.6,17.4 65.1,17.4 65.7,17.4 66.3,17.4 66.8,17.4 67.4,17.4 67.4,16.9 66.8,16.9 66.3,16.9 65.7,16.9 65.2,16.9 64.6,16.9 64,16.9 63.5,16.9 62.9,16.9 62.9,16.3 63.5,16.3 64,16.3 64.6,16.3 65.2,16.3 65.2,15.8 65.7,15.8 66.3,15.8 66.8,15.8 67.4,15.8 67.9,15.8 68.5,15.8 69,15.8 69.6,15.8 70.1,15.8 70.7,15.8 71.3,15.8 71.8,15.8 72.4,15.8 72.4,16.4 72.9,16.4 72.9,16.9 73.5,16.9 73.5,16.4 74,16.4 74,16.9 74.6,16.9 75.1,16.9 75.7,16.9 76.2,16.9 76.8,16.9 76.8,17.5 76.8,18 77.3,18 77.9,18.1 78.5,18.1 79,18.1 79,18.6 79.6,18.6 79.6,19.2 80.1,19.2 80.1,19.7 80.7,19.7 80.7,20.3 81.2,20.3 81.2,20.8 81.2,21.4 81.2,21.9 80.7,21.9 80.6,22.5 81.2,22.5 81.8,22.5 82.3,22.5 82.3,23.1 82.3,23.6 82.9,23.6 83.4,23.6 83.4,23.1 84,23.1 84.5,23.1 84.5,22.5 85.1,22.5 85.6,22.5 85.6,22 86.2,22 86.8,22 86.8,21.4 87.3,21.4 87.9,21.4 88.4,21.4 89,21.4 89.5,21.4 90.1,21.4 90.6,21.4 90.6,22 91.2,22 91.7,22 92.3,22 92.3,22.6 92.8,22.6 92.8,23.1 92.3,23.1 91.7,23.1 91.2,23.1 90.6,23.1 90.6,23.7 90.1,23.7 89.5,23.7 89,23.7 89,24.2 88.4,24.2 87.8,24.2 87.8,24.8 87.3,24.8 86.7,24.7 86.7,25.3 86.2,25.3 86.2,25.9 85.6,25.9 85.6,26.4 85.1,26.4 85.1,27 84.5,27 84.5,27.5 84,27.5 83.9,28.1 83.4,28.1 83.4,28.6 82.8,28.6 82.8,29.2 82.3,29.2 82.3,29.7 81.7,29.7 81.7,30.3 81.2,30.3 81.2,30.8 80.6,30.8 80.6,31.4 80,31.4 80,31.9 79.5,31.9 79.5,32.5 78.9,32.5 78.9,33 78.4,33 78.4,33.6 77.8,33.6 77.8,34.1 77.3,34.1 77.3,34.7 76.7,34.7 76.2,34.7 76.1,35.2 75.6,35.2 75,35.2 75,35.8 74.5,35.8 73.9,35.8 73.4,35.8 73.4,36.3 72.8,36.3 72.3,36.3 71.7,36.3 71.2,36.3 70.6,36.3 70.6,36.9 71.1,36.9 71.7,36.9 72.3,36.9 72.8,36.9 73.4,36.9 73.9,36.9 74.5,36.9 75,36.9 75.6,36.9 76.1,36.9 76.7,36.9 77.2,36.9 77.8,36.9 78.4,36.9 78.9,36.9 79.5,36.9 80,36.9 80.6,36.9 81.1,36.9 81.7,36.9 82.2,36.9 82.8,36.9 83.3,36.9 83.9,36.9 84.5,36.9 85,36.9 85,36.4 84.5,36.4 83.9,36.4 83.4,36.4 82.8,36.4 82.2,36.4 81.7,36.4 81.1,36.4 80.6,36.4 80,36.4 79.5,36.4 78.9,36.4 78.4,36.4 78.4,35.8 78.4,35.2 78.9,35.2 78.9,34.7 79.5,34.7 79.5,34.1 80,34.1 80,33.6 80.6,33.6 81.1,33.6 81.7,33.6 82.3,33.6 82.8,33.6 83.4,33.6 83.9,33.6 83.9,34.2 84.5,34.2 85,34.2 85.6,34.2 86.1,34.2 86.1,33.6 86.7,33.6 87.2,33.6 87.8,33.6 88.4,33.6 88.9,33.6 89.5,33.6 90,33.6 90.6,33.6 91.1,33.6 91.7,33.6 92.2,33.6 92.8,33.7 92.8,34.2 93.3,34.2 93.9,34.2 93.9,34.8 93.9,35.3 93.9,35.9 93.3,35.9 93.3,36.4 92.8,36.4 92.8,37 92.2,37 92.2,37.5 91.7,37.5 91.7,38.1 91.1,38.1 90.6,38.1 90.5,38.6 90,38.6 89.4,38.6 88.9,38.6 88.9,39.2 88.3,39.2 87.8,39.2 87.2,39.2 87.2,39.7 86.7,39.7 86.1,39.7 85.6,39.7 85.5,40.3 85,40.3 84.4,40.3 83.9,40.3 83.3,40.3 83.3,40.8 82.8,40.8 82.2,40.8 81.7,40.8 81.1,40.8 81.1,41.4 80.6,41.4 80,41.4 79.4,41.3 79.4,41.9 78.9,41.9 78.9,42.5 79.4,42.5 80,42.5 80.5,42.5 81.1,42.5 81.7,42.5 82.2,42.5 82.8,42.5 82.8,41.9 83.3,41.9 83.9,41.9 83.9,41.4 83.9,40.8 84.4,40.8 85,40.8 85.5,40.8 86.1,40.8 86.1,40.3 86.7,40.3 87.2,40.3 87.8,40.3 87.8,39.7 88.3,39.7 88.9,39.7 89.4,39.7 89.4,39.2 90,39.2 90.5,39.2 91.1,39.2 91.1,38.6 91.7,38.6 92.2,38.6 92.2,38.1 92.8,38.1 92.8,37.5 93.3,37.5 93.3,37 93.9,37 93.9,36.4 94.4,36.4 94.4,35.9 94.4,35.3 94.4,34.8 94.5,34.2 94.5,33.7 93.9,33.7 93.9,33.1 93.3,33.1 92.8,33.1 92.2,33.1 91.7,33.1 91.1,33.1 90.6,33.1 90,33.1 89.5,33.1 88.9,33.1 88.4,33.1 87.8,33.1 87.2,33.1 86.7,33.1 86.1,33.1 85.6,33.1 85,33.1 84.5,33.1 84.5,32.5 85,32.5 "
+   id="polygon1634"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1640"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<rect
+   x="58.400002"
+   y="32.900002"
+   transform="matrix(0.00513464,-1,1,0.00513464,25.1907,91.7212)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1638"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1644"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<rect
+   x="57.299999"
+   y="32.900002"
+   transform="matrix(0.00513464,-1,1,0.00513464,24.0931,90.6066)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1642"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1648"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<rect
+   x="56.200001"
+   y="32.900002"
+   transform="matrix(0.00513464,-1,1,0.00513464,22.9955,89.492)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1646"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1652"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<rect
+   x="55.099998"
+   y="32.900002"
+   transform="matrix(0.00513464,-1,1,0.00513464,21.8979,88.3773)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1650"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1656"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<rect
+   x="57.900002"
+   y="32.400002"
+   transform="matrix(0.00513464,-1,1,0.00513464,25.1992,90.6151)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1654"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1660"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<rect
+   x="56.799999"
+   y="32.400002"
+   transform="matrix(0.00513464,-1,1,0.00513464,24.1016,89.5005)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1658"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1664"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<rect
+   x="55.599998"
+   y="32.400002"
+   transform="matrix(0.00513464,-1,1,0.00513464,23.004,88.3858)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1662"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1668"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<rect
+   x="60.599998"
+   y="31.799999"
+   transform="matrix(0.00513464,-1,1,0.00513464,28.5004,92.8528)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1666"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1672"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<polygon
+   points="61.2,32.4 61.2,32.9 61.2,33.5 60.6,33.5 60.6,32.9 60.1,32.9 60.1,32.4 59.5,32.4 59.5,31.8 59.5,31.3 59,31.3 59,31.8 58.4,31.8 58.4,32.4 59,32.4 59,32.9 59.5,32.9 59.5,33.5 60.1,33.5 60.1,34 60.6,34 60.6,34.6 61.2,34.6 61.2,35.2 61.7,35.2 61.7,34.6 61.7,34 61.7,33.5 62.3,33.5 62.3,32.9 62.3,32.4 61.7,32.4 "
+   id="polygon1670"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1676"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<rect
+   x="57.299999"
+   y="31.799999"
+   transform="matrix(0.00513464,-1,1,0.00513464,25.2077,89.509)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1674"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1680"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<rect
+   x="56.200001"
+   y="31.799999"
+   transform="matrix(0.00513464,-1,1,0.00513464,24.1101,88.3944)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1678"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1684"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<rect
+   x="55.099998"
+   y="31.799999"
+   transform="matrix(0.00513464,-1,1,0.00513464,23.0125,87.2798)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1682"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1688"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<rect
+   x="57.900002"
+   y="31.299999"
+   transform="matrix(0.00513464,-1,1,0.00513464,26.3138,89.5175)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1686"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1692"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<rect
+   x="56.799999"
+   y="31.299999"
+   transform="matrix(0.00513464,-1,1,0.00513464,25.2162,88.4029)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1690"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1696"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<rect
+   x="55.700001"
+   y="31.200001"
+   transform="matrix(0.00513464,-1,1,0.00513464,24.1186,87.2883)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1694"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1700"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<polygon
+   points="60.1,31.3 60.1,31.8 60.6,31.8 60.6,31.3 60.6,30.7 60.1,30.7 "
+   id="polygon1698"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1704"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<rect
+   x="57.299999"
+   y="30.700001"
+   transform="matrix(0.00513464,-1,1,0.00513464,26.3223,88.4114)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1702"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1708"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<rect
+   x="56.200001"
+   y="30.700001"
+   transform="matrix(0.00513464,-1,1,0.00513464,25.2247,87.2968)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1706"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1712"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<path
+   d="M 68.4,30.2 V 29.6 29 28.4 27.8 27.2 H 67.8 V 26.6 26 H 67.2 V 25.4 H 66.6 66 V 24.8 H 65.4 64.8 64.2 63.6 63 v 0.6 H 62.4 61.8 V 26 h -0.6 v 0.6 0.6 h -0.6 v 0.6 0.6 0.6 0.6 0.6 h 0.6 v 0.6 0.6 h 0.6 v 0.6 0.6 h 0.6 V 32 h 0.6 0.6 v -0.6 h 0.6 0.6 0.6 v -0.6 h 0.6 0.6 0.6 0.6 0.6 0.6 V 30.2 Z M 63.4,28 V 27.4 H 64 64.6 V 28 28.6 H 64 63.4 Z"
+   id="path1710"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1716"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<rect
+   x="56.799999"
+   y="30.1"
+   transform="matrix(0.00513464,-1,1,0.00513464,26.3308,87.3053)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1714"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1720"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<polygon
+   points="71.2,29.7 71.2,29.1 71.2,28.6 71.2,28 71.2,27.4 71.2,26.9 71.2,26.3 70.6,26.3 70.6,26.9 70.6,27.4 70.6,28 70.6,28.5 70.6,29.1 70.6,29.7 70.6,30.2 71.2,30.2 71.7,30.2 72.3,30.2 72.3,29.7 71.7,29.7 "
+   id="polygon1718"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1724"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<polygon
+   points="60.1,27.4 60.1,26.8 59.6,26.8 59.6,27.4 59.6,27.9 59.5,28.5 59.5,29 59.5,29.6 59.5,30.2 59.5,30.7 60.1,30.7 60.1,30.2 60.1,29.6 60.1,29 60.1,28.5 60.1,27.9 "
+   id="polygon1722"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1728"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<polygon
+   points="72.8,29.7 73.4,29.7 73.4,29.1 72.9,29.1 72.3,29.1 72.3,29.7 "
+   id="polygon1726"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1732"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<polygon
+   points="74,29.1 74.5,29.1 74.5,28.6 74,28.6 73.4,28.6 73.4,29.1 "
+   id="polygon1730"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1736"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<polygon
+   points="75.1,28.6 75.6,28.6 75.6,28 75.1,28 74.5,28 74.5,28.6 "
+   id="polygon1734"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1740"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<polygon
+   points="76.2,28 76.7,28 76.7,27.5 76.2,27.5 75.6,27.5 75.6,28 "
+   id="polygon1738"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1744"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<polygon
+   points="69.5,28 69.5,28.5 69.5,29.1 69.5,29.7 69.5,30.2 69.5,30.8 69,30.8 69,31.3 69.5,31.3 70.1,31.3 70.1,30.8 70.1,30.2 70.1,29.7 70.1,29.1 70.1,28.5 70.1,28 70.1,27.4 70.1,26.9 69.5,26.9 69.5,27.4 "
+   id="polygon1742"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1748"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<rect
+   x="76.699997"
+   y="26.9"
+   transform="matrix(0.00513464,-1,1,0.00513464,49.4311,104.0757)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1746"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1752"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<polygon
+   points="77.9,26.9 78.4,26.9 78.4,26.4 77.9,26.4 77.3,26.4 77.3,26.9 "
+   id="polygon1750"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1756"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<polygon
+   points="69.5,26.3 69.5,25.8 69,25.8 69,26.3 69,26.9 69.5,26.9 "
+   id="polygon1754"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1760"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<polygon
+   points="60.1,25.7 60.1,26.3 60.1,26.8 60.7,26.8 60.7,26.3 60.7,25.7 "
+   id="polygon1758"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1764"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<polygon
+   points="59,26.3 58.5,26.3 58.4,26.8 58.4,27.4 58.4,27.9 58.4,28.5 58.4,29 58.4,29.6 58.4,30.1 57.9,30.1 57.9,30.7 58.4,30.7 58.4,31.3 59,31.3 59,30.7 59,30.2 59,29.6 59,29 59,28.5 59,27.9 59,27.4 59,26.8 "
+   id="polygon1762"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1768"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<rect
+   x="78.400002"
+   y="25.799999"
+   transform="matrix(0.00513464,-1,1,0.00513464,52.1921,104.65)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1766"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1772"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<polygon
+   points="79.5,25.8 80.1,25.8 80.1,25.3 79.5,25.3 79,25.3 79,25.8 "
+   id="polygon1770"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1776"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<polygon
+   points="75.1,25.2 74.5,25.2 74,25.2 74,25.8 74.5,25.8 75.1,25.8 75.6,25.8 76.2,25.8 76.2,25.3 75.6,25.2 "
+   id="polygon1774"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1780"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<polygon
+   points="70.1,25.8 70.1,26.3 70.6,26.3 70.7,25.8 70.7,25.2 70.1,25.2 "
+   id="polygon1778"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1784"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<rect
+   x="68.400002"
+   y="25.200001"
+   transform="matrix(0.00513464,-1,1,0.00513464,42.8712,94.0697)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1782"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1788"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<rect
+   x="60.700001"
+   y="25.200001"
+   transform="matrix(0.00513464,-1,1,0.00513464,35.1881,86.2674)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1786"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1792"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<polygon
+   points="59.6,26.3 59.6,25.7 59.6,25.2 59,25.2 59,25.7 59,26.3 "
+   id="polygon1790"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1796"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<rect
+   x="80.099998"
+   y="24.700001"
+   transform="matrix(0.00513464,-1,1,0.00513464,54.9531,105.2244)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1794"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1800"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<polygon
+   points="76.8,25.3 77.3,25.3 77.3,24.7 76.8,24.7 76.2,24.7 76.2,25.3 "
+   id="polygon1798"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1804"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<rect
+   x="73.400002"
+   y="24.700001"
+   transform="matrix(0.00513464,-1,1,0.00513464,48.3676,98.5367)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1802"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1808"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<rect
+   x="69.5"
+   y="24.700001"
+   transform="matrix(0.00513464,-1,1,0.00513464,44.5261,94.6355)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1806"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1812"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<rect
+   x="67.900002"
+   y="24.700001"
+   transform="matrix(0.00513464,-1,1,0.00513464,42.8797,92.9636)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1810"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1816"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<rect
+   x="61.200001"
+   y="24.6"
+   transform="matrix(0.00513464,-1,1,0.00513464,36.2942,86.2759)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1814"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1820"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<rect
+   x="59.599998"
+   y="24.6"
+   transform="matrix(0.00513464,-1,1,0.00513464,34.6479,84.604)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1818"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1824"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<polygon
+   points="81.2,24.7 81.7,24.7 81.8,24.2 81.2,24.2 80.6,24.2 80.6,24.7 "
+   id="polygon1822"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1828"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<polygon
+   points="77.9,24.7 78.4,24.7 78.4,24.2 77.9,24.1 77.3,24.1 77.3,24.7 "
+   id="polygon1826"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1832"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<rect
+   x="72.900002"
+   y="24.1"
+   transform="matrix(0.00513464,-1,1,0.00513464,48.3761,97.4306)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1830"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1836"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<rect
+   x="69"
+   y="24.1"
+   transform="matrix(0.00513464,-1,1,0.00513464,44.5346,93.5294)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1834"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1840"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<polygon
+   points="67.9,24.1 67.3,24.1 66.8,24.1 66.8,24.6 67.3,24.7 67.9,24.7 "
+   id="polygon1838"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1844"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<polygon
+   points="62.3,24.1 61.8,24.1 61.8,24.6 62.3,24.6 62.9,24.6 62.9,24.1 "
+   id="polygon1842"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1848"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<rect
+   x="60.099998"
+   y="24.1"
+   transform="matrix(0.00513464,-1,1,0.00513464,35.754,84.6125)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1846"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1852"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<rect
+   x="81.800003"
+   y="23.6"
+   transform="matrix(0.00513464,-1,1,0.00513464,57.7141,105.7987)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1850"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1856"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<rect
+   x="78.400002"
+   y="23.6"
+   transform="matrix(0.00513464,-1,1,0.00513464,54.4213,102.4549)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1854"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1860"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<polygon
+   points="66.2,24.1 66.8,24.1 66.8,23.5 66.2,23.5 65.7,23.5 65.1,23.5 64.6,23.5 64,23.5 63.5,23.5 62.9,23.5 62.9,24.1 63.5,24.1 64,24.1 64.6,24.1 65.1,24.1 65.7,24.1 "
+   id="polygon1858"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1864"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<rect
+   x="60.700001"
+   y="23.5"
+   transform="matrix(0.00513464,-1,1,0.00513464,36.8601,84.621)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1862"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1868"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<polygon
+   points="79.5,23.6 80.1,23.6 80.1,23.1 79.5,23 79,23 79,23.6 "
+   id="polygon1866"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1872"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<rect
+   x="76.199997"
+   y="23"
+   transform="matrix(0.00513464,-1,1,0.00513464,52.7835,99.6768)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1870"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1876"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<polygon
+   points="72.3,23.6 72.3,24.1 72.9,24.1 72.9,23.6 72.9,23 72.9,22.5 72.3,22.5 72.3,23 "
+   id="polygon1874"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1880"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<polygon
+   points="62.3,23 62.9,23 63.5,23 64,23 64.6,23 65.1,23 65.7,23 66.2,23 66.8,23 67.3,23 67.3,23.5 67.9,23.5 68.4,23.5 68.4,24.1 69,24.1 69,23.5 69.6,23.6 69.6,23 69.6,22.4 69,22.4 69,23 68.4,23 68.5,22.4 67.9,22.4 67.9,21.9 67.3,21.9 67.3,22.4 66.8,22.4 66.2,22.4 65.7,22.4 65.1,22.4 64.6,22.4 64,22.4 63.5,22.4 62.9,22.4 62.4,22.4 "
+   id="polygon1878"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1884"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<polygon
+   points="61.8,23.5 62.3,23.5 62.3,23 61.8,23 61.2,23 61.2,23.5 "
+   id="polygon1882"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1888"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<rect
+   x="80.099998"
+   y="22.5"
+   transform="matrix(0.00513464,-1,1,0.00513464,57.1823,103.0292)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1886"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1892"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<rect
+   x="75.699997"
+   y="22.5"
+   transform="matrix(0.00513464,-1,1,0.00513464,52.792,98.5708)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1890"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1896"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<rect
+   x="77.900002"
+   y="21.9"
+   transform="matrix(0.00513464,-1,1,0.00513464,55.5445,100.2512)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1894"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1900"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<rect
+   x="75.099998"
+   y="21.9"
+   transform="matrix(0.00513464,-1,1,0.00513464,52.8005,97.4647)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1898"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1904"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<polygon
+   points="72.9,21.4 72.9,21.9 72.9,22.5 73.4,22.5 73.4,21.9 73.4,21.4 "
+   id="polygon1902"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1908"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<rect
+   x="68.5"
+   y="21.9"
+   transform="matrix(0.00513464,-1,1,0.00513464,46.215,90.777)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1906"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1912"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<rect
+   x="77.300003"
+   y="21.4"
+   transform="matrix(0.00513464,-1,1,0.00513464,55.553,99.1451)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1910"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1916"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<rect
+   x="67.900002"
+   y="21.299999"
+   transform="matrix(0.00513464,-1,1,0.00513464,46.2236,89.6709)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1914"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1920"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<rect
+   x="79.5"
+   y="20.799999"
+   transform="matrix(0.00513464,-1,1,0.00513464,58.3054,100.8255)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1918"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1924"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<rect
+   x="76.800003"
+   y="20.799999"
+   transform="matrix(0.00513464,-1,1,0.00513464,55.5615,98.039)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1922"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1928"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<rect
+   x="73.400002"
+   y="20.799999"
+   transform="matrix(0.00513464,-1,1,0.00513464,52.2688,94.6952)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1926"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1932"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<rect
+   x="68.5"
+   y="20.799999"
+   transform="matrix(0.00513464,-1,1,0.00513464,47.3297,89.6794)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1930"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1936"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<rect
+   x="79"
+   y="20.299999"
+   transform="matrix(0.00513464,-1,1,0.00513464,58.314,99.7194)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1934"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1940"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<rect
+   x="76.199997"
+   y="20.299999"
+   transform="matrix(0.00513464,-1,1,0.00513464,55.57,96.9329)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1938"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1944"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<rect
+   x="74"
+   y="20.200001"
+   transform="matrix(0.00513464,-1,1,0.00513464,53.3749,94.7037)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1942"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1948"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<polygon
+   points="69.6,22.4 70.1,22.4 70.1,21.9 70.1,21.3 70.7,21.3 70.7,20.8 70.7,20.2 70.1,20.2 70.1,20.8 69.6,20.8 69.6,21.3 69,21.3 69,21.9 69.6,21.9 "
+   id="polygon1946"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1952"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<rect
+   x="69"
+   y="20.200001"
+   transform="matrix(0.00513464,-1,1,0.00513464,48.4358,89.6879)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1950"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1956"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<rect
+   x="67.900002"
+   y="20.200001"
+   transform="matrix(0.00513464,-1,1,0.00513464,47.3382,88.5733)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1954"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1960"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<rect
+   x="78.400002"
+   y="19.700001"
+   transform="matrix(0.00513464,-1,1,0.00513464,58.3225,98.6133)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1958"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1964"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<rect
+   x="74.599998"
+   y="19.700001"
+   transform="matrix(0.00513464,-1,1,0.00513464,54.481,94.7122)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1962"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1968"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<rect
+   x="70.699997"
+   y="19.700001"
+   transform="matrix(0.00513464,-1,1,0.00513464,50.6394,90.811)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1966"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1972"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<rect
+   x="69.599998"
+   y="19.700001"
+   transform="matrix(0.00513464,-1,1,0.00513464,49.5419,89.6964)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1970"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1976"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<rect
+   x="68.5"
+   y="19.700001"
+   transform="matrix(0.00513464,-1,1,0.00513464,48.4443,88.5818)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1974"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1980"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<rect
+   x="77.900002"
+   y="19.200001"
+   transform="matrix(0.00513464,-1,1,0.00513464,58.331,97.5073)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1978"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1984"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<rect
+   x="75.099998"
+   y="19.1"
+   transform="matrix(0.00513464,-1,1,0.00513464,55.5871,94.7207)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1982"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1988"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<rect
+   x="70.099998"
+   y="19.1"
+   transform="matrix(0.00513464,-1,1,0.00513464,50.6479,89.7049)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1986"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1992"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<rect
+   x="69"
+   y="19.1"
+   transform="matrix(0.00513464,-1,1,0.00513464,49.5504,88.5903)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1990"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g1996"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<rect
+   x="75.699997"
+   y="18.6"
+   transform="matrix(0.00513464,-1,1,0.00513464,56.6931,94.7292)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1994"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g2000"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<polygon
+   points="70.1,18.6 70.7,18.6 70.7,18 70.1,18 69.6,18 69,18 69,18.6 69.6,18.6 69.6,19.1 70.1,19.1 "
+   id="polygon1998"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g2004"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<rect
+   x="76.199997"
+   y="18"
+   transform="matrix(0.00513464,-1,1,0.00513464,57.7992,94.7377)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect2002"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g2008"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<rect
+   x="72.400002"
+   y="18"
+   transform="matrix(0.00513464,-1,1,0.00513464,53.9577,90.8366)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect2006"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g2012"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<polygon
+   points="71.8,19.1 72.3,19.1 72.4,18.6 71.8,18.6 71.8,18 71.2,18 71.2,18.6 70.7,18.6 70.7,19.1 71.2,19.1 71.2,19.7 71.8,19.7 "
+   id="polygon2010"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g2016"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<rect
+   x="72.900002"
+   y="17.5"
+   transform="matrix(0.00513464,-1,1,0.00513464,55.0638,90.8451)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect2014"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g2020"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<rect
+   x="71.800003"
+   y="17.5"
+   transform="matrix(0.00513464,-1,1,0.00513464,53.9662,89.7305)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect2018"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g2024"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<polygon
+   points="67.9,18 68.5,18 69,18 69,17.5 68.5,17.4 67.9,17.4 67.4,17.4 67.4,18 "
+   id="polygon2022"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g2028"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<rect
+   x="73.5"
+   y="16.9"
+   transform="matrix(0.00513464,-1,1,0.00513464,56.1699,90.8536)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect2026"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+	<g
+   id="g2032"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1">
+		<rect
+   x="72.400002"
+   y="16.9"
+   transform="matrix(0.00513464,-1,1,0.00513464,55.0723,89.739)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect2030"
+   style="stroke-width:2.41176;fill:#fcfeff;fill-opacity:1" />
+
+	</g>
+
+</g>
+    </g>
+  </g>
+  <g
+     inkscape:groupmode="layer"
+     id="layer5"
+     inkscape:label="Original"
+     style="display:none">
+    <image
+       y="0.0079936879"
+       x="0.010951031"
+       id="image4124"
+       xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGAAAABgCAMAAADVRocKAAAAA3NCSVQICAjb4U/gAAAACXBIWXMA ADFOAAAxTgGacD5yAAAAGXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAABF0RVh0 VGl0bGUAUGViYmxlIEljb26KvwD9AAAADHRFWHRBdXRob3IAeHBobnh/4OzMAAAAY3RFWHRDb3B5 cmlnaHQAQ0MgQXR0cmlidXRpb24tTm9uQ29tbWVyY2lhbC1TaGFyZUFsaWtlIGh0dHA6Ly9jcmVh dGl2ZWNvbW1vbnMub3JnL2xpY2Vuc2VzL2J5LW5jLXNhLzMuMC/eBBrlAAABy1BMVEUAAAAAAAAA AP8AAIAAAFUAAKoAVaoAAIAAQIAAM2YAM5kzM2YzM5krK4AkJG0kJJIgIIAcHHEcHI4aGoAXF3QX F4sXLnQXLosVK4AUJ3YUJ4kSJIAkJIARInciInciIogQIIAgIIAeHngeHoccHIAcK4AbKHkbKIYa GoAaJoAYJHkXI4AXI4sWIXoWIYUVIIAfH3ofH4UdJ4ATHIQcJnsbJIAaI4QaIoAZIXsZIYQYIHgY IIAXH4MXJ3wXHoAXJoBLpfAWJHwdJHwdJIMcI4Acas0VIoMbIoAaIXwaIIAZH3wZH4MZJXwYHoAY HoYYJIAehtsYHoIefdtHoO0jetZGovNEn+4gg+EfgN5Ep/VKqfREpvQgfdkgfttKqvQggd4hjesw nPMwnPRJqfUhgd4hjutJqfUgfttNq/VNq/QhlPEhlPIhlvMkmPMglvIff9sgjeoglPAhk/AhlPEh lPIhlvMkl/MkmPMlmPMmmPMnmfMpmvMzn/Q0n/Q1n/Q2oPRIqfVNq/VasfZbsvZhtPZitfdtuveQ yvmXzvmj0/qk1Pqu2fu83/vR6f3T6v3Z7f3i8f3j8v3l8/7m8/7p9f7q9f7x+P7y+f7////ougeB AAAAb3RSTlMAAQECAwMDBAQFBQUFBgcHCAkJCgsLCwsMDQ0ODg8PDxAQERESEhMTFBQVFhYXFxgZ GRobGxwdHh8fICAhISIiIiMjIyQkJSYnKCkpKSoqKiorKyssLC2xs7O0tba2tu7u7u7u7+/w8fHy +/v7+/ywJhX9AAAC0klEQVRo3u2a+VfTQBCAm00iW0nSTVNKbKQYbIMUC6jU4q2IB2rF+74vPEAR LSjIIXgAAiqef65py6MNJe17y1RF8/26L/N1N9O8ZGZcLof/DiYFoiV1cZHwiGVZjh7zalRAYYbn sFsQ6RFwGccyBeJjwSMrPn8lHX6fQqRy3tbAsFiU1So9FA4bNIRD63XNTwTO5pSYc9fuvwbgwaW9 SxqYs/eGBgcAGBy+fRrlpRPDsJdfJoEYuMgvzibz/vK3kmDcFMuxNZvM/BG74QTdFV6PJZsYlhe9 XXCCrlCVStwcyhG45QCkoCmiV0o4uwXEib4aQMHjbc0RTXYvCMx/mGdNHaSgraWxRhEXzsgUEC0C KWjfszlUYRWsrYcUHGttNlSRzxUEo5CCjrZYrSqVVlD3Nwj6vqcZsovzKrPeTy3o/5lmxE4wnFl/ 4QhKLvjy2YY5IEExHMH0B1umQQSj9o+EUUewIBh/m8c4qOBbfu58dQRLCmYnJmZLKphKJicdwR8X vJsymSmhIM37FSv4OJlhxnlUOAJHAC8Ye5PHmPNe9PsEJX/5dT5AigvmPtkw53wnr3xB34809uWc zDp9OWflV7xgBaBlzY79MWORALhuui+vbhoArfwe3rUp5MtWfl2Il1QDUtAaj+rKakv1XdEhBfGm Wo1kq+/pM1Ih+wdRI6iIq3IFvEAewgke6QHFg1mU26PjhbtwghsKEbG1X8ew3PmnUPGfXRAwv7hr yqAD13t6nwPQ23PnBMeivG4gg85c7XwCQOeVU+avZ5bslcrrNm7dfehIInGSguOJo+0Hd27ZoOam j3ULvOjTI02x+PYddLTEY42GJpfnpo/L0g50k8pqI1IfbWikoSFaXxfSFKnMrqFsZpKbKKoWDFbT EdQCqlfCdhtIG7AoESLTQogk4EJd/VRTn8fLgecKTg3MjyUsA4SKDD7MT1bQ8++NmvwCRmydh2Fg UvEAAAAASUVORK5CYII= "
+       style="opacity:0.490741;image-rendering:optimizeQuality"
+       preserveAspectRatio="none"
+       height="28.710279"
+       width="28.710279" />
+  </g>
+</svg>
diff --git a/app/src/main/assets/ic_device_flipper_disabled.svg b/app/src/main/assets/ic_device_flipper_disabled.svg
new file mode 100644
index 0000000..5550696
--- /dev/null
+++ b/app/src/main/assets/ic_device_flipper_disabled.svg
@@ -0,0 +1,1825 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   version="1.1"
+   id="svg2"
+   width="28.740126"
+   height="28.740126"
+   viewBox="0 0 28.740126 28.740126"
+   sodipodi:docname="ic_device_flipper_disabled.svg"
+   inkscape:version="1.2.1 (9c6d41e410, 2022-07-14)"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:android="http://schemas.android.com/apk/res/android">
+  <metadata
+     id="metadata8">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs6">
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 14.370063 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="28.740126 : 14.370063 : 1"
+       inkscape:persp3d-origin="14.370063 : 9.580042 : 1"
+       id="perspective903" />
+  </defs>
+  <sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="1920"
+     inkscape:window-height="1035"
+     id="namedview4"
+     showgrid="false"
+     showguides="true"
+     inkscape:guide-bbox="true"
+     inkscape:zoom="22.627417"
+     inkscape:cx="0"
+     inkscape:cy="10.938058"
+     inkscape:window-x="0"
+     inkscape:window-y="45"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="layer2"
+     inkscape:pagecheckerboard="0"
+     inkscape:showpageshadow="2"
+     inkscape:deskcolor="#d1d1d1">
+    <inkscape:grid
+       type="xygrid"
+       id="grid972" />
+  </sodipodi:namedview>
+  <g
+     inkscape:groupmode="layer"
+     id="layer1"
+     inkscape:label="Background"
+     style="display:inline">
+    <g
+       inkscape:groupmode="layer"
+       id="layer4"
+       inkscape:label="Bottom"
+       style="display:inline"
+       sodipodi:insensitive="true">
+      <rect
+         style="display:inline;fill:#7a7a7a;fill-opacity:1;stroke:none;stroke-width:3.57115;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         id="rect817-5"
+         width="22.819572"
+         height="21.904638"
+         x="2.9239759"
+         y="3.8768768"
+         ry="0.9472276" />
+    </g>
+    <g
+       inkscape:groupmode="layer"
+       id="layer3"
+       inkscape:label="Middle"
+       style="display:inline"
+       sodipodi:insensitive="true">
+      <rect
+         style="display:inline;fill:#9f9f9f;fill-opacity:0.941176;stroke:none;stroke-width:3.57115;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         id="rect817-7"
+         width="22.819572"
+         height="21.904638"
+         x="2.9317887"
+         y="3.0349438"
+         ry="0.9472276" />
+    </g>
+    <g
+       inkscape:groupmode="layer"
+       id="layer2"
+       inkscape:label="Top"
+       style="display:inline">
+      <rect
+         style="fill:#8a8a8a;fill-opacity:1;stroke:none;stroke-width:3.57115;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         id="rect817"
+         width="22.819572"
+         height="21.904638"
+         x="2.9238882"
+         y="3.4130721"
+         ry="0.9472276" />
+    </g>
+  </g>
+  <g
+     inkscape:groupmode="layer"
+     id="layer6"
+     inkscape:label="Foreground"
+     style="display:inline">
+    <g
+       inkscape:groupmode="layer"
+       id="layer7"
+       inkscape:label="Backplane"
+       style="display:inline" />
+    <g
+       inkscape:groupmode="layer"
+       id="layer8"
+       inkscape:label="Frontplane"
+       style="display:inline">
+      <g
+         inkscape:groupmode="layer"
+         id="layer9"
+         inkscape:label="Base"
+         style="display:inline" />
+      <g
+         inkscape:groupmode="layer"
+         id="layer10"
+         inkscape:label="Top"
+         style="display:inline" />
+      <g
+         inkscape:groupmode="layer"
+         id="layer11"
+         inkscape:label="Toptop"
+         style="display:inline" />
+      <g
+         inkscape:groupmode="layer"
+         id="layer12"
+         inkscape:label="Toptoptop"
+         style="display:inline" />
+    </g>
+    <path
+       inkscape:connector-curvature="0"
+       style="fill:#8a8a8a;fill-opacity:0.94117647"
+       d="m 12.48275,10.865376 h 3.907 v 6.978 h -3.907 z"
+       android:strokeWidth="1"
+       id="path2735" />
+    <g
+       id="dolphin_00000004540763674616766980000002934822554439179943_"
+       transform="matrix(0.41463415,0,0,0.41463415,-16.349253,2.403196)"
+       style="display:inline;fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+	<g
+   id="g1524"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<polygon
+   points="85.6,36.4 86.1,36.4 86.7,36.4 87.2,36.4 87.8,36.4 87.8,35.8 87.2,35.8 86.7,35.8 86.1,35.8 85.6,35.8 85,35.8 85,36.4 "
+   id="polygon1522"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1528"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<polygon
+   points="70.6,35.8 70,35.8 69.5,35.8 69.5,36.3 70,36.3 70.6,36.3 "
+   id="polygon1526"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1532"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<rect
+   x="59"
+   y="35.700001"
+   transform="matrix(0.00513464,-1,1,0.00513464,22.9529,95.0224)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1530"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1536"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<rect
+   x="57.799999"
+   y="35.700001"
+   transform="matrix(0.00513464,-1,1,0.00513464,21.8553,93.9078)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1534"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1540"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<rect
+   x="56.700001"
+   y="35.700001"
+   transform="matrix(0.00513464,-1,1,0.00513464,20.7578,92.7932)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1538"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1544"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<polygon
+   points="88.3,35.3 88.3,34.7 87.8,34.7 87.2,34.7 87.2,35.3 87.8,35.3 87.8,35.8 88.3,35.8 88.9,35.9 89.5,35.9 90,35.9 90,35.3 89.5,35.3 88.9,35.3 "
+   id="polygon1542"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1548"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<rect
+   x="68.900002"
+   y="35.200001"
+   transform="matrix(0.00513464,-1,1,0.00513464,33.3884,104.5052)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1546"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1552"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<rect
+   x="59.5"
+   y="35.099998"
+   transform="matrix(0.00513464,-1,1,0.00513464,24.059,95.031)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1550"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1556"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<rect
+   x="58.400002"
+   y="35.099998"
+   transform="matrix(0.00513464,-1,1,0.00513464,22.9614,93.9163)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1554"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1560"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<rect
+   x="57.299999"
+   y="35.099998"
+   transform="matrix(0.00513464,-1,1,0.00513464,21.8638,92.8017)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1558"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1564"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<rect
+   x="56.200001"
+   y="35.099998"
+   transform="matrix(0.00513464,-1,1,0.00513464,20.7663,91.6871)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1562"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1568"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<polygon
+   points="91.7,35.3 91.7,34.8 91.1,34.8 90.6,34.8 90,34.7 90,35.3 90.6,35.3 91.1,35.3 "
+   id="polygon1566"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1572"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<rect
+   x="60.099998"
+   y="34.599998"
+   transform="matrix(0.00513464,-1,1,0.00513464,25.1651,95.0395)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1570"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1576"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<rect
+   x="59"
+   y="34.599998"
+   transform="matrix(0.00513464,-1,1,0.00513464,24.0675,93.9249)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1574"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1580"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<rect
+   x="57.900002"
+   y="34.599998"
+   transform="matrix(0.00513464,-1,1,0.00513464,22.9699,92.8102)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1578"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1584"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<rect
+   x="56.700001"
+   y="34.599998"
+   transform="matrix(0.00513464,-1,1,0.00513464,21.8724,91.6956)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1582"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1588"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<rect
+   x="55.599998"
+   y="34.599998"
+   transform="matrix(0.00513464,-1,1,0.00513464,20.7748,90.581)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1586"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1592"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<polygon
+   points="92.2,34.2 91.7,34.2 91.7,34.8 92.2,34.8 92.8,34.8 92.8,34.2 "
+   id="polygon1590"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1596"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<polygon
+   points="86.1,34.7 86.7,34.7 87.2,34.7 87.2,34.2 86.7,34.2 86.1,34.2 "
+   id="polygon1594"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1600"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<rect
+   x="59.5"
+   y="34"
+   transform="matrix(0.00513464,-1,1,0.00513464,25.1736,93.9334)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1598"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1604"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<rect
+   x="58.400002"
+   y="34"
+   transform="matrix(0.00513464,-1,1,0.00513464,24.076,92.8188)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1602"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1608"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<rect
+   x="57.299999"
+   y="34"
+   transform="matrix(0.00513464,-1,1,0.00513464,22.9785,91.7041)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1606"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1612"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<rect
+   x="56.200001"
+   y="34"
+   transform="matrix(0.00513464,-1,1,0.00513464,21.8809,90.5895)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1610"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1616"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<rect
+   x="55.099998"
+   y="34"
+   transform="matrix(0.00513464,-1,1,0.00513464,20.7833,89.4749)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1614"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1620"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<rect
+   x="59"
+   y="33.5"
+   transform="matrix(0.00513464,-1,1,0.00513464,25.1821,92.8273)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1618"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1624"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<rect
+   x="57.900002"
+   y="33.5"
+   transform="matrix(0.00513464,-1,1,0.00513464,24.0846,91.7127)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1622"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1628"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<rect
+   x="56.700001"
+   y="33.5"
+   transform="matrix(0.00513464,-1,1,0.00513464,22.987,90.598)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1626"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1632"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<rect
+   x="55.599998"
+   y="33.5"
+   transform="matrix(0.00513464,-1,1,0.00513464,21.8894,89.4834)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1630"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1636"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<polygon
+   points="87.8,29.7 87.8,29.2 88.4,29.2 88.4,28.6 88.9,28.6 88.9,28.1 89.5,28.1 89.5,27.5 90.1,27.5 90.1,27 90.6,27 90.6,26.4 91.2,26.4 91.2,25.9 91.7,25.9 91.7,25.3 92.3,25.3 92.3,24.8 92.8,24.8 92.8,24.2 93.4,24.2 93.4,23.7 93.4,23.1 93.4,22.6 93.4,22 92.9,22 92.9,21.5 92.3,21.5 91.7,21.4 91.2,21.4 91.2,20.9 90.6,20.9 90.1,20.9 89.5,20.9 89,20.9 88.4,20.9 87.9,20.9 87.3,20.9 86.8,20.9 86.2,20.9 86.2,21.4 85.6,21.4 85.1,21.4 85.1,22 84.5,22 84,22 84,22.5 83.4,22.5 82.9,22.5 82.9,22 82.3,22 82.3,21.4 82.3,20.8 81.8,20.8 81.8,20.3 81.8,19.7 81.2,19.7 81.2,19.2 80.7,19.2 80.7,18.6 80.1,18.6 80.1,18.1 79.6,18.1 79.6,17.5 79,17.5 78.5,17.5 78.5,16.9 77.9,16.9 77.4,16.9 77.4,16.4 76.8,16.4 76.2,16.4 75.7,16.4 75.7,15.8 75.1,15.8 74.6,15.8 74,15.8 73.5,15.8 72.9,15.8 72.9,15.3 72.4,15.2 71.8,15.2 71.3,15.2 70.7,15.2 70.2,15.2 69.6,15.2 69,15.2 68.5,15.2 67.9,15.2 67.4,15.2 66.8,15.2 66.3,15.2 65.7,15.2 65.2,15.2 64.6,15.2 64.6,15.8 64,15.8 63.5,15.8 62.9,15.8 62.4,15.8 62.4,16.3 61.8,16.3 61.3,16.3 60.7,16.3 60.7,16.9 60.2,16.9 60.2,17.4 59.6,17.4 59.1,17.4 59,18 58.5,18 57.9,17.9 57.9,18.5 57.4,18.5 57.4,19.1 56.8,19.1 56.8,19.6 56.3,19.6 56.3,20.2 55.7,20.2 55.7,20.7 55.2,20.7 55.1,21.3 55.1,21.8 54.6,21.8 54.6,22.4 54.6,22.9 54,22.9 54,23.5 54,24 53.5,24 53.5,24.6 53.5,25.1 53.5,25.7 54,25.7 54,25.1 54,24.6 54.6,24.6 54.6,24 54.6,23.5 55.1,23.5 55.1,22.9 55.1,22.4 55.7,22.4 55.7,21.8 55.7,21.3 56.3,21.3 56.3,20.7 56.8,20.7 56.8,20.2 57.4,20.2 57.4,19.6 57.9,19.6 57.9,19.1 58.5,19.1 58.5,18.5 59,18.5 59.6,18.5 59.6,18 60.2,18 60.7,18 60.7,17.4 61.3,17.4 61.8,17.4 62.4,17.4 62.9,17.4 63.5,17.4 64,17.4 64.6,17.4 65.1,17.4 65.7,17.4 66.3,17.4 66.8,17.4 67.4,17.4 67.4,16.9 66.8,16.9 66.3,16.9 65.7,16.9 65.2,16.9 64.6,16.9 64,16.9 63.5,16.9 62.9,16.9 62.9,16.3 63.5,16.3 64,16.3 64.6,16.3 65.2,16.3 65.2,15.8 65.7,15.8 66.3,15.8 66.8,15.8 67.4,15.8 67.9,15.8 68.5,15.8 69,15.8 69.6,15.8 70.1,15.8 70.7,15.8 71.3,15.8 71.8,15.8 72.4,15.8 72.4,16.4 72.9,16.4 72.9,16.9 73.5,16.9 73.5,16.4 74,16.4 74,16.9 74.6,16.9 75.1,16.9 75.7,16.9 76.2,16.9 76.8,16.9 76.8,17.5 76.8,18 77.3,18 77.9,18.1 78.5,18.1 79,18.1 79,18.6 79.6,18.6 79.6,19.2 80.1,19.2 80.1,19.7 80.7,19.7 80.7,20.3 81.2,20.3 81.2,20.8 81.2,21.4 81.2,21.9 80.7,21.9 80.6,22.5 81.2,22.5 81.8,22.5 82.3,22.5 82.3,23.1 82.3,23.6 82.9,23.6 83.4,23.6 83.4,23.1 84,23.1 84.5,23.1 84.5,22.5 85.1,22.5 85.6,22.5 85.6,22 86.2,22 86.8,22 86.8,21.4 87.3,21.4 87.9,21.4 88.4,21.4 89,21.4 89.5,21.4 90.1,21.4 90.6,21.4 90.6,22 91.2,22 91.7,22 92.3,22 92.3,22.6 92.8,22.6 92.8,23.1 92.3,23.1 91.7,23.1 91.2,23.1 90.6,23.1 90.6,23.7 90.1,23.7 89.5,23.7 89,23.7 89,24.2 88.4,24.2 87.8,24.2 87.8,24.8 87.3,24.8 86.7,24.7 86.7,25.3 86.2,25.3 86.2,25.9 85.6,25.9 85.6,26.4 85.1,26.4 85.1,27 84.5,27 84.5,27.5 84,27.5 83.9,28.1 83.4,28.1 83.4,28.6 82.8,28.6 82.8,29.2 82.3,29.2 82.3,29.7 81.7,29.7 81.7,30.3 81.2,30.3 81.2,30.8 80.6,30.8 80.6,31.4 80,31.4 80,31.9 79.5,31.9 79.5,32.5 78.9,32.5 78.9,33 78.4,33 78.4,33.6 77.8,33.6 77.8,34.1 77.3,34.1 77.3,34.7 76.7,34.7 76.2,34.7 76.1,35.2 75.6,35.2 75,35.2 75,35.8 74.5,35.8 73.9,35.8 73.4,35.8 73.4,36.3 72.8,36.3 72.3,36.3 71.7,36.3 71.2,36.3 70.6,36.3 70.6,36.9 71.1,36.9 71.7,36.9 72.3,36.9 72.8,36.9 73.4,36.9 73.9,36.9 74.5,36.9 75,36.9 75.6,36.9 76.1,36.9 76.7,36.9 77.2,36.9 77.8,36.9 78.4,36.9 78.9,36.9 79.5,36.9 80,36.9 80.6,36.9 81.1,36.9 81.7,36.9 82.2,36.9 82.8,36.9 83.3,36.9 83.9,36.9 84.5,36.9 85,36.9 85,36.4 84.5,36.4 83.9,36.4 83.4,36.4 82.8,36.4 82.2,36.4 81.7,36.4 81.1,36.4 80.6,36.4 80,36.4 79.5,36.4 78.9,36.4 78.4,36.4 78.4,35.8 78.4,35.2 78.9,35.2 78.9,34.7 79.5,34.7 79.5,34.1 80,34.1 80,33.6 80.6,33.6 81.1,33.6 81.7,33.6 82.3,33.6 82.8,33.6 83.4,33.6 83.9,33.6 83.9,34.2 84.5,34.2 85,34.2 85.6,34.2 86.1,34.2 86.1,33.6 86.7,33.6 87.2,33.6 87.8,33.6 88.4,33.6 88.9,33.6 89.5,33.6 90,33.6 90.6,33.6 91.1,33.6 91.7,33.6 92.2,33.6 92.8,33.7 92.8,34.2 93.3,34.2 93.9,34.2 93.9,34.8 93.9,35.3 93.9,35.9 93.3,35.9 93.3,36.4 92.8,36.4 92.8,37 92.2,37 92.2,37.5 91.7,37.5 91.7,38.1 91.1,38.1 90.6,38.1 90.5,38.6 90,38.6 89.4,38.6 88.9,38.6 88.9,39.2 88.3,39.2 87.8,39.2 87.2,39.2 87.2,39.7 86.7,39.7 86.1,39.7 85.6,39.7 85.5,40.3 85,40.3 84.4,40.3 83.9,40.3 83.3,40.3 83.3,40.8 82.8,40.8 82.2,40.8 81.7,40.8 81.1,40.8 81.1,41.4 80.6,41.4 80,41.4 79.4,41.3 79.4,41.9 78.9,41.9 78.9,42.5 79.4,42.5 80,42.5 80.5,42.5 81.1,42.5 81.7,42.5 82.2,42.5 82.8,42.5 82.8,41.9 83.3,41.9 83.9,41.9 83.9,41.4 83.9,40.8 84.4,40.8 85,40.8 85.5,40.8 86.1,40.8 86.1,40.3 86.7,40.3 87.2,40.3 87.8,40.3 87.8,39.7 88.3,39.7 88.9,39.7 89.4,39.7 89.4,39.2 90,39.2 90.5,39.2 91.1,39.2 91.1,38.6 91.7,38.6 92.2,38.6 92.2,38.1 92.8,38.1 92.8,37.5 93.3,37.5 93.3,37 93.9,37 93.9,36.4 94.4,36.4 94.4,35.9 94.4,35.3 94.4,34.8 94.5,34.2 94.5,33.7 93.9,33.7 93.9,33.1 93.3,33.1 92.8,33.1 92.2,33.1 91.7,33.1 91.1,33.1 90.6,33.1 90,33.1 89.5,33.1 88.9,33.1 88.4,33.1 87.8,33.1 87.2,33.1 86.7,33.1 86.1,33.1 85.6,33.1 85,33.1 84.5,33.1 84.5,32.5 85,32.5 85,31.9 85.6,32 85.6,31.4 86.1,31.4 86.2,30.8 86.7,30.8 86.7,30.3 87.3,30.3 87.3,29.7 "
+   id="polygon1634"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1640"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<rect
+   x="58.400002"
+   y="32.900002"
+   transform="matrix(0.00513464,-1,1,0.00513464,25.1907,91.7212)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1638"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1644"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<rect
+   x="57.299999"
+   y="32.900002"
+   transform="matrix(0.00513464,-1,1,0.00513464,24.0931,90.6066)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1642"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1648"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<rect
+   x="56.200001"
+   y="32.900002"
+   transform="matrix(0.00513464,-1,1,0.00513464,22.9955,89.492)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1646"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1652"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<rect
+   x="55.099998"
+   y="32.900002"
+   transform="matrix(0.00513464,-1,1,0.00513464,21.8979,88.3773)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1650"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1656"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<rect
+   x="57.900002"
+   y="32.400002"
+   transform="matrix(0.00513464,-1,1,0.00513464,25.1992,90.6151)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1654"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1660"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<rect
+   x="56.799999"
+   y="32.400002"
+   transform="matrix(0.00513464,-1,1,0.00513464,24.1016,89.5005)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1658"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1664"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<rect
+   x="55.599998"
+   y="32.400002"
+   transform="matrix(0.00513464,-1,1,0.00513464,23.004,88.3858)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1662"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1668"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<rect
+   x="60.599998"
+   y="31.799999"
+   transform="matrix(0.00513464,-1,1,0.00513464,28.5004,92.8528)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1666"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1672"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<polygon
+   points="59.5,31.3 59,31.3 59,31.8 58.4,31.8 58.4,32.4 59,32.4 59,32.9 59.5,32.9 59.5,33.5 60.1,33.5 60.1,34 60.6,34 60.6,34.6 61.2,34.6 61.2,35.2 61.7,35.2 61.7,34.6 61.7,34 61.7,33.5 62.3,33.5 62.3,32.9 62.3,32.4 61.7,32.4 61.2,32.4 61.2,32.9 61.2,33.5 60.6,33.5 60.6,32.9 60.1,32.9 60.1,32.4 59.5,32.4 59.5,31.8 "
+   id="polygon1670"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1676"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<rect
+   x="57.299999"
+   y="31.799999"
+   transform="matrix(0.00513464,-1,1,0.00513464,25.2077,89.509)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1674"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1680"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<rect
+   x="56.200001"
+   y="31.799999"
+   transform="matrix(0.00513464,-1,1,0.00513464,24.1101,88.3944)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1678"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1684"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<rect
+   x="55.099998"
+   y="31.799999"
+   transform="matrix(0.00513464,-1,1,0.00513464,23.0125,87.2798)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1682"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1688"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<rect
+   x="57.900002"
+   y="31.299999"
+   transform="matrix(0.00513464,-1,1,0.00513464,26.3138,89.5175)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1686"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1692"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<rect
+   x="56.799999"
+   y="31.299999"
+   transform="matrix(0.00513464,-1,1,0.00513464,25.2162,88.4029)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1690"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1696"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<rect
+   x="55.700001"
+   y="31.200001"
+   transform="matrix(0.00513464,-1,1,0.00513464,24.1186,87.2883)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1694"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1700"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<polygon
+   points="60.6,31.3 60.6,30.7 60.1,30.7 60.1,31.3 60.1,31.8 60.6,31.8 "
+   id="polygon1698"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1704"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<rect
+   x="57.299999"
+   y="30.700001"
+   transform="matrix(0.00513464,-1,1,0.00513464,26.3223,88.4114)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1702"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1708"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<rect
+   x="56.200001"
+   y="30.700001"
+   transform="matrix(0.00513464,-1,1,0.00513464,25.2247,87.2968)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1706"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1712"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<path
+   d="M 68.4,30.2 V 29.6 29 28.4 27.8 27.2 H 67.8 V 26.6 26 H 67.2 V 25.4 H 66.6 66 V 24.8 H 65.4 64.8 64.2 63.6 63 v 0.6 H 62.4 61.8 V 26 h -0.6 v 0.6 0.6 h -0.6 v 0.6 0.6 0.6 0.6 0.6 h 0.6 v 0.6 0.6 h 0.6 v 0.6 0.6 h 0.6 V 32 h 0.6 0.6 v -0.6 h 0.6 0.6 0.6 v -0.6 h 0.6 0.6 0.6 0.6 0.6 0.6 V 30.2 Z M 63.4,28 V 27.4 H 64 64.6 V 28 28.6 H 64 63.4 Z"
+   id="path1710"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1716"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<rect
+   x="56.799999"
+   y="30.1"
+   transform="matrix(0.00513464,-1,1,0.00513464,26.3308,87.3053)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1714"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1720"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<polygon
+   points="70.6,27.4 70.6,28 70.6,28.5 70.6,29.1 70.6,29.7 70.6,30.2 71.2,30.2 71.7,30.2 72.3,30.2 72.3,29.7 71.7,29.7 71.2,29.7 71.2,29.1 71.2,28.6 71.2,28 71.2,27.4 71.2,26.9 71.2,26.3 70.6,26.3 70.6,26.9 "
+   id="polygon1718"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1724"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<polygon
+   points="59.5,30.7 60.1,30.7 60.1,30.2 60.1,29.6 60.1,29 60.1,28.5 60.1,27.9 60.1,27.4 60.1,26.8 59.6,26.8 59.6,27.4 59.6,27.9 59.5,28.5 59.5,29 59.5,29.6 59.5,30.2 "
+   id="polygon1722"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1728"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<polygon
+   points="72.9,29.1 72.3,29.1 72.3,29.7 72.8,29.7 73.4,29.7 73.4,29.1 "
+   id="polygon1726"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1732"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<polygon
+   points="74,28.6 73.4,28.6 73.4,29.1 74,29.1 74.5,29.1 74.5,28.6 "
+   id="polygon1730"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1736"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<polygon
+   points="75.1,28 74.5,28 74.5,28.6 75.1,28.6 75.6,28.6 75.6,28 "
+   id="polygon1734"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1740"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<polygon
+   points="76.2,27.5 75.6,27.5 75.6,28 76.2,28 76.7,28 76.7,27.5 "
+   id="polygon1738"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1744"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<polygon
+   points="70.1,31.3 70.1,30.8 70.1,30.2 70.1,29.7 70.1,29.1 70.1,28.5 70.1,28 70.1,27.4 70.1,26.9 69.5,26.9 69.5,27.4 69.5,28 69.5,28.5 69.5,29.1 69.5,29.7 69.5,30.2 69.5,30.8 69,30.8 69,31.3 69.5,31.3 "
+   id="polygon1742"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1748"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<rect
+   x="76.699997"
+   y="26.9"
+   transform="matrix(0.00513464,-1,1,0.00513464,49.4311,104.0757)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1746"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1752"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<polygon
+   points="77.9,26.4 77.3,26.4 77.3,26.9 77.9,26.9 78.4,26.9 78.4,26.4 "
+   id="polygon1750"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1756"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<polygon
+   points="69,26.3 69,26.9 69.5,26.9 69.5,26.3 69.5,25.8 69,25.8 "
+   id="polygon1754"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1760"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<polygon
+   points="60.7,26.8 60.7,26.3 60.7,25.7 60.1,25.7 60.1,26.3 60.1,26.8 "
+   id="polygon1758"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1764"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<polygon
+   points="57.9,30.1 57.9,30.7 58.4,30.7 58.4,31.3 59,31.3 59,30.7 59,30.2 59,29.6 59,29 59,28.5 59,27.9 59,27.4 59,26.8 59,26.3 58.5,26.3 58.4,26.8 58.4,27.4 58.4,27.9 58.4,28.5 58.4,29 58.4,29.6 58.4,30.1 "
+   id="polygon1762"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1768"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<rect
+   x="78.400002"
+   y="25.799999"
+   transform="matrix(0.00513464,-1,1,0.00513464,52.1921,104.65)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1766"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1772"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<polygon
+   points="79.5,25.3 79,25.3 79,25.8 79.5,25.8 80.1,25.8 80.1,25.3 "
+   id="polygon1770"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1776"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<polygon
+   points="75.6,25.2 75.1,25.2 74.5,25.2 74,25.2 74,25.8 74.5,25.8 75.1,25.8 75.6,25.8 76.2,25.8 76.2,25.3 "
+   id="polygon1774"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1780"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<polygon
+   points="70.7,25.8 70.7,25.2 70.1,25.2 70.1,25.8 70.1,26.3 70.6,26.3 "
+   id="polygon1778"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1784"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<rect
+   x="68.400002"
+   y="25.200001"
+   transform="matrix(0.00513464,-1,1,0.00513464,42.8712,94.0697)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1782"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1788"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<rect
+   x="60.700001"
+   y="25.200001"
+   transform="matrix(0.00513464,-1,1,0.00513464,35.1881,86.2674)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1786"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1792"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<polygon
+   points="59,25.2 59,25.7 59,26.3 59.6,26.3 59.6,25.7 59.6,25.2 "
+   id="polygon1790"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1796"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<rect
+   x="80.099998"
+   y="24.700001"
+   transform="matrix(0.00513464,-1,1,0.00513464,54.9531,105.2244)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1794"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1800"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<polygon
+   points="76.8,24.7 76.2,24.7 76.2,25.3 76.8,25.3 77.3,25.3 77.3,24.7 "
+   id="polygon1798"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1804"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<rect
+   x="73.400002"
+   y="24.700001"
+   transform="matrix(0.00513464,-1,1,0.00513464,48.3676,98.5367)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1802"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1808"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<rect
+   x="69.5"
+   y="24.700001"
+   transform="matrix(0.00513464,-1,1,0.00513464,44.5261,94.6355)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1806"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1812"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<rect
+   x="67.900002"
+   y="24.700001"
+   transform="matrix(0.00513464,-1,1,0.00513464,42.8797,92.9636)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1810"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1816"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<rect
+   x="61.200001"
+   y="24.6"
+   transform="matrix(0.00513464,-1,1,0.00513464,36.2942,86.2759)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1814"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1820"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<rect
+   x="59.599998"
+   y="24.6"
+   transform="matrix(0.00513464,-1,1,0.00513464,34.6479,84.604)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1818"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1824"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<polygon
+   points="81.2,24.2 80.6,24.2 80.6,24.7 81.2,24.7 81.7,24.7 81.8,24.2 "
+   id="polygon1822"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1828"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<polygon
+   points="77.9,24.1 77.3,24.1 77.3,24.7 77.9,24.7 78.4,24.7 78.4,24.2 "
+   id="polygon1826"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1832"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<rect
+   x="72.900002"
+   y="24.1"
+   transform="matrix(0.00513464,-1,1,0.00513464,48.3761,97.4306)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1830"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1836"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<rect
+   x="69"
+   y="24.1"
+   transform="matrix(0.00513464,-1,1,0.00513464,44.5346,93.5294)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1834"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1840"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<polygon
+   points="66.8,24.6 67.3,24.7 67.9,24.7 67.9,24.1 67.3,24.1 66.8,24.1 "
+   id="polygon1838"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1844"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<polygon
+   points="62.3,24.6 62.9,24.6 62.9,24.1 62.3,24.1 61.8,24.1 61.8,24.6 "
+   id="polygon1842"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1848"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<rect
+   x="60.099998"
+   y="24.1"
+   transform="matrix(0.00513464,-1,1,0.00513464,35.754,84.6125)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1846"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1852"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<rect
+   x="81.800003"
+   y="23.6"
+   transform="matrix(0.00513464,-1,1,0.00513464,57.7141,105.7987)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1850"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1856"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<rect
+   x="78.400002"
+   y="23.6"
+   transform="matrix(0.00513464,-1,1,0.00513464,54.4213,102.4549)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1854"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1860"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<polygon
+   points="62.9,23.5 62.9,24.1 63.5,24.1 64,24.1 64.6,24.1 65.1,24.1 65.7,24.1 66.2,24.1 66.8,24.1 66.8,23.5 66.2,23.5 65.7,23.5 65.1,23.5 64.6,23.5 64,23.5 63.5,23.5 "
+   id="polygon1858"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1864"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<rect
+   x="60.700001"
+   y="23.5"
+   transform="matrix(0.00513464,-1,1,0.00513464,36.8601,84.621)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1862"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1868"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<polygon
+   points="79.5,23 79,23 79,23.6 79.5,23.6 80.1,23.6 80.1,23.1 "
+   id="polygon1866"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1872"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<rect
+   x="76.199997"
+   y="23"
+   transform="matrix(0.00513464,-1,1,0.00513464,52.7835,99.6768)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1870"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1876"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<polygon
+   points="72.3,24.1 72.9,24.1 72.9,23.6 72.9,23 72.9,22.5 72.3,22.5 72.3,23 72.3,23.6 "
+   id="polygon1874"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1880"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<polygon
+   points="67.3,23 67.3,23.5 67.9,23.5 68.4,23.5 68.4,24.1 69,24.1 69,23.5 69.6,23.6 69.6,23 69.6,22.4 69,22.4 69,23 68.4,23 68.5,22.4 67.9,22.4 67.9,21.9 67.3,21.9 67.3,22.4 66.8,22.4 66.2,22.4 65.7,22.4 65.1,22.4 64.6,22.4 64,22.4 63.5,22.4 62.9,22.4 62.4,22.4 62.3,23 62.9,23 63.5,23 64,23 64.6,23 65.1,23 65.7,23 66.2,23 66.8,23 "
+   id="polygon1878"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1884"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<polygon
+   points="61.8,23 61.2,23 61.2,23.5 61.8,23.5 62.3,23.5 62.3,23 "
+   id="polygon1882"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1888"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<rect
+   x="80.099998"
+   y="22.5"
+   transform="matrix(0.00513464,-1,1,0.00513464,57.1823,103.0292)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1886"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1892"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<rect
+   x="75.699997"
+   y="22.5"
+   transform="matrix(0.00513464,-1,1,0.00513464,52.792,98.5708)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1890"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1896"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<rect
+   x="77.900002"
+   y="21.9"
+   transform="matrix(0.00513464,-1,1,0.00513464,55.5445,100.2512)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1894"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1900"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<rect
+   x="75.099998"
+   y="21.9"
+   transform="matrix(0.00513464,-1,1,0.00513464,52.8005,97.4647)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1898"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1904"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<polygon
+   points="73.4,22.5 73.4,21.9 73.4,21.4 72.9,21.4 72.9,21.9 72.9,22.5 "
+   id="polygon1902"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1908"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<rect
+   x="68.5"
+   y="21.9"
+   transform="matrix(0.00513464,-1,1,0.00513464,46.215,90.777)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1906"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1912"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<rect
+   x="77.300003"
+   y="21.4"
+   transform="matrix(0.00513464,-1,1,0.00513464,55.553,99.1451)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1910"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1916"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<rect
+   x="67.900002"
+   y="21.299999"
+   transform="matrix(0.00513464,-1,1,0.00513464,46.2236,89.6709)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1914"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1920"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<rect
+   x="79.5"
+   y="20.799999"
+   transform="matrix(0.00513464,-1,1,0.00513464,58.3054,100.8255)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1918"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1924"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<rect
+   x="76.800003"
+   y="20.799999"
+   transform="matrix(0.00513464,-1,1,0.00513464,55.5615,98.039)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1922"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1928"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<rect
+   x="73.400002"
+   y="20.799999"
+   transform="matrix(0.00513464,-1,1,0.00513464,52.2688,94.6952)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1926"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1932"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<rect
+   x="68.5"
+   y="20.799999"
+   transform="matrix(0.00513464,-1,1,0.00513464,47.3297,89.6794)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1930"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1936"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<rect
+   x="79"
+   y="20.299999"
+   transform="matrix(0.00513464,-1,1,0.00513464,58.314,99.7194)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1934"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1940"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<rect
+   x="76.199997"
+   y="20.299999"
+   transform="matrix(0.00513464,-1,1,0.00513464,55.57,96.9329)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1938"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1944"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<rect
+   x="74"
+   y="20.200001"
+   transform="matrix(0.00513464,-1,1,0.00513464,53.3749,94.7037)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1942"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1948"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<polygon
+   points="69.6,20.8 69.6,21.3 69,21.3 69,21.9 69.6,21.9 69.6,22.4 70.1,22.4 70.1,21.9 70.1,21.3 70.7,21.3 70.7,20.8 70.7,20.2 70.1,20.2 70.1,20.8 "
+   id="polygon1946"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1952"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<rect
+   x="69"
+   y="20.200001"
+   transform="matrix(0.00513464,-1,1,0.00513464,48.4358,89.6879)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1950"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1956"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<rect
+   x="67.900002"
+   y="20.200001"
+   transform="matrix(0.00513464,-1,1,0.00513464,47.3382,88.5733)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1954"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1960"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<rect
+   x="78.400002"
+   y="19.700001"
+   transform="matrix(0.00513464,-1,1,0.00513464,58.3225,98.6133)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1958"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1964"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<rect
+   x="74.599998"
+   y="19.700001"
+   transform="matrix(0.00513464,-1,1,0.00513464,54.481,94.7122)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1962"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1968"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<rect
+   x="70.699997"
+   y="19.700001"
+   transform="matrix(0.00513464,-1,1,0.00513464,50.6394,90.811)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1966"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1972"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<rect
+   x="69.599998"
+   y="19.700001"
+   transform="matrix(0.00513464,-1,1,0.00513464,49.5419,89.6964)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1970"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1976"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<rect
+   x="68.5"
+   y="19.700001"
+   transform="matrix(0.00513464,-1,1,0.00513464,48.4443,88.5818)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1974"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1980"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<rect
+   x="77.900002"
+   y="19.200001"
+   transform="matrix(0.00513464,-1,1,0.00513464,58.331,97.5073)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1978"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1984"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<rect
+   x="75.099998"
+   y="19.1"
+   transform="matrix(0.00513464,-1,1,0.00513464,55.5871,94.7207)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1982"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1988"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<rect
+   x="70.099998"
+   y="19.1"
+   transform="matrix(0.00513464,-1,1,0.00513464,50.6479,89.7049)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1986"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1992"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<rect
+   x="69"
+   y="19.1"
+   transform="matrix(0.00513464,-1,1,0.00513464,49.5504,88.5903)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1990"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g1996"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<rect
+   x="75.699997"
+   y="18.6"
+   transform="matrix(0.00513464,-1,1,0.00513464,56.6931,94.7292)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect1994"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g2000"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<polygon
+   points="70.1,19.1 70.1,18.6 70.7,18.6 70.7,18 70.1,18 69.6,18 69,18 69,18.6 69.6,18.6 69.6,19.1 "
+   id="polygon1998"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g2004"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<rect
+   x="76.199997"
+   y="18"
+   transform="matrix(0.00513464,-1,1,0.00513464,57.7992,94.7377)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect2002"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g2008"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<rect
+   x="72.400002"
+   y="18"
+   transform="matrix(0.00513464,-1,1,0.00513464,53.9577,90.8366)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect2006"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g2012"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<polygon
+   points="71.2,19.1 71.2,19.7 71.8,19.7 71.8,19.1 72.3,19.1 72.4,18.6 71.8,18.6 71.8,18 71.2,18 71.2,18.6 70.7,18.6 70.7,19.1 "
+   id="polygon2010"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g2016"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<rect
+   x="72.900002"
+   y="17.5"
+   transform="matrix(0.00513464,-1,1,0.00513464,55.0638,90.8451)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect2014"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g2020"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<rect
+   x="71.800003"
+   y="17.5"
+   transform="matrix(0.00513464,-1,1,0.00513464,53.9662,89.7305)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect2018"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g2024"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<polygon
+   points="68.5,18 69,18 69,17.5 68.5,17.4 67.9,17.4 67.4,17.4 67.4,18 67.9,18 "
+   id="polygon2022"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g2028"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<rect
+   x="73.5"
+   y="16.9"
+   transform="matrix(0.00513464,-1,1,0.00513464,56.1699,90.8536)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect2026"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+	<g
+   id="g2032"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176">
+		<rect
+   x="72.400002"
+   y="16.9"
+   transform="matrix(0.00513464,-1,1,0.00513464,55.0723,89.739)"
+   width="0.60000002"
+   height="0.60000002"
+   id="rect2030"
+   style="fill:#fcfeff;fill-opacity:1;stroke-width:2.41176" />
+
+	</g>
+
+</g>
+  </g>
+  <g
+     inkscape:groupmode="layer"
+     id="layer5"
+     inkscape:label="Original"
+     style="display:none">
+    <image
+       y="0.028144173"
+       x="0.013794855"
+       id="image1291"
+       xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJAAAACQCAYAAADnRuK4AAAABHNCSVQICAgIfAhkiAAAAAlwSFlz AABO4wAATuMBYdZxSQAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAAZdEVY dFRpdGxlAFR3ZWxmIEljb24gVGVtcGxhdGXyQOONAAAADHRFWHRBdXRob3IAeHBobnh/4OzMAAAA Y3RFWHRDb3B5cmlnaHQAQ0MgQXR0cmlidXRpb24tTm9uQ29tbWVyY2lhbC1TaGFyZUFsaWtlIGh0 dHA6Ly9jcmVhdGl2ZWNvbW1vbnMub3JnL2xpY2Vuc2VzL2J5LW5jLXNhLzQuMC9D0yJcAAANW0lE QVR4nO2dS4xc2VnH/98591VVXd3TbntC92ggwZ5R1BWRRZMoIQG1UIQIs0BCcpINESyyQIqySlgl 2I6yQ2xmtpEQilgQI4EUiAQaQQtCFAiNQjLVUWbsjII19iS2u931vI9zzseiqtxP21V9b7vrlr/f pp/33HPr/O53vvO4VYAgCIIgCIIgCIIgCILwLEBnUz6f8mmfZR7ZpKfyop+GQPvKHNX52mmLKjyS K8NGONAEhclUdMMOy2PsSdMcfr08+LK+JTKdNhurQ0GuD3/RGP58hfc1eSESeUUUMuSQPE0CLgPr ILRv0xp2EMd3CHf3DkiS+yJTQYTh0p4QjS1E0TJvYhGorzA2VjGQ6RoAHklEKECiIhuQDsiz3qC1 9jLF8R1KknNkzDYZs0vAi3CuJeKcEkrNM3ALnrfAnneOw3Cbo2iZN+t3GBtNHkSjh5FoagQ6Ik/j LlSSnKM4NirLbit3fo7Y9AhYgrP9vTjq+iJTTkhVHoqgdIWB+yCvyupeh31/xUWR58Jwm5sX4Abd 21ZhEhUo0NUD8nS7UGkKlS1C2QRq3kA5F5KrJ8Q2IaAOdqnIUxCkAgbaIB2yaoesVMItD06HcP4O XBDA1WpwA4lGkegqYwoEok++ygE/3/+CJv6MVrjIwEIB5QpFw9xyUG9ZuL+Jt+6+tnH1vUneKJRX IPrEN/nFiHrfJqBhHJBYwLqcpQqnglZAqAFPAc7xG0miX3n9s9GtPCOzkwpEAPDJVznQy73NxKBx p8PoZjJBWAbmAsJyjeArfqP747sf2rj6V+mhIf6IJzaomvDchIcJM4Of73+hm6Lx0wdO5CkRnZRx 84FDz9AHgpeWPj+Ycrk2zGOv0j5vnhhgJhHo0DzPNXJwn77VcnDiTulwDNxqOZDWn8LaosI6FLBK B2Vi4GHQOJ5xJxKPThKuN+h+D5esyFNaLAPbfXfp0m6owhgcre0MJx8xnDN68sTjBDPRI3lWCeug xl2oTsbVgq5FOCNaKeba7dCL43nX7d7hS17IYQxurjccNpo4JNERxunC6KA8Ww/neTJb4JUIZ0Jm gbSmvbj2rtefr+leb0l3u1CNu1BYb+zr0h52ZwcYMwcadVt78vT7HS29V/lhANVAedX+olfrWC+t 93SaDtr4oETH8ySB9mXkl7HWXqbRDLN97sKkIzhhGmGmNA79xE/8JHDeXFLVab2i03SwFLXWvj1Y FN+LQgcYX4L1LYrjO2TMLmWLUHNJXxd4GcIZ4uvMDzx4WdLxsijQc0lfZ4tQcfy2iuMVOrQF50A0 GiOJHnZf7UVKkpCcWyKbQJkalF/whQhng/XIQwYKgwBpljIZsE2gnFtwgy03Sxh2Y0dC0JgR6DLW sAZjlsier9G8gXLZxJOQwpTiZ/Csb7Q1mY5Mql0V6jkXkT3fIWOWht0YcNzO0oklcLZLzsVkLRSY ZTV9BnAM5RtPs4NyUUDOBeRcTM7ODdt37ZHHji3QKP9h0yPnIpKtGLMDe1BgQy4g5SwUc0rOxYfa 9/Kxx55gS+sSsNAHerUTVFWYRpg9YoDAIOaM2PEgSJgeGZNRHIOAxWOPHTMCjbLwF/edNJMINDNY YjYUPvx5/OBwghyoP9xRKMwWIdgZAioTBQcZSQkD2J4oKIhAQi5EICEXRT5Y+FR56ZzC5z7o46Mv eHi+RvBLditkDvhFl/Hddwy+/oMMb+2UcyN5KQX67Ad8fOXjIXSJU3lfAS/UCZff7+MPXvZx9TsJ /rqZnXW1JqZk9y3wykUPV36z3PIcRivgq78V4nd/tXz3c6kECjTw5Y+Fp/6eNGcBAfjKx8LSdcWl qu5HVjTeU5tFfQYszxE+vFKuXTKlEuilc6Wq7ol4uWTXWKra1vzZjT4j5kp2jaUSSJg+RCAhFyKQ kIvyTTw8ht2kHA8aLYSPznNePqfwexc97MSM/3nXIpnyZ+9mSqBf/8supv1RawJw80/mHvn3Vy55 eOXSoFluPnD4o3/o45329F6UdGFTzMXnFL78G+GT//EMEYGeMnrCV/xDy9M9sSgCPWUmXaqoTPm8 kAgk5EIEespM+mZcjqc3gQZmbBR2HH/+2xF+7fmzuU9++AuHL/1LfOB36YTD8p9sT/dGs5kX6MU6 4aXFsxFop58vemQO+Iv/TAuqzekw8wKVjTe3HW7sOPxfy+Hv3zR4UyKQMAn/eMPgtc3pjjr7kSRa yIUIJORCBBJyIQIJuRCBhFyIQEIuRCAhFyKQkAsRSMiFCCTkQgQSciECCbkQgYRciEBCLkQgIRci kJALEUjIhQgk5EIEEnIhAgm5EIGEXIhAQi5EICEXIpCQCxFIyIUIJORCBBJyIQIJuRCBhFyIQEIu RCAhFyKQkAsRSMjFzL9D2a02Y/GMPhH51hR/REFRzLxAh98lVSgW6cKEXIhAQi5EICEXIpCQCxFI yMVMjcL++49rZ12FZ46ZEuhxn0UqnA7ShQm5EIGEXIhAQi5KJVAnm/21pXZarmsslUA3pvyzs4rg rTNa+D0ppRLoe7ct3u2W6w6dhDsdxn/dnvAzMc+YUgmUWuBr/5FgFhViAF/9ToKsXAGoXAIBwLdv Glz99wS2ZC/047AO+LN/S/BPb5uzrsrElHIi8RtvZPjebYvPfdDHR1/w8Es1gi7ZrWAc8PMu47vv GHz9B1npcp8RpRQIAN7advjTf00AJGddlWeakt23wrQhAgm5EIGEXIhAwgDSJ5odmVggpStMOpzF qZhnnASkPAb6Ex01pkCrQ2FuTVgpoRxoJvJ4NJ4lGn9B7gRd2H1gd7KTCNMNkWGCZpBhIp9JBaxU zORV2fMWOIqWH9nWYwsURcvseQtMXnVQuApEoBmBDBzIskojRypjooCVig617/Vjjz1BDlRjpSLW Gg5EItEMoAgu87Ql1Xc6YadUykpFrHRn2L6bjz52vFNcxyY24XnnWN/rcsuDI5WIPDNC5sPozFql 4foKTvXgHqiY9b059rz7vFlfGbb1lSNtPoZAVxhoMOorHIbbrNQ86xDOj8Ny7TsQHok2bJSX2TRj 4/mB9Tw4HcIptcthuDSUpnFswBi/C9tY5UEe1GJ/B67loZyrf8IRUsMms37mh3MmSGE6YcX6O3BR 9D4XRbcZG6v75Tkg0pMEYmD0qMx1bNbvcK0GFwRwfrVjwdKLzQAchEHmJzBhqkwn7Fn94K4LArgw 3B52X6ME+uhjU2NGoGE3trHKzeg212pwldacBUsQKj3s0Df9LK5VsnYltkG7aiuVOVurwTUvwGGj OWj7Y/IfYDyBhlHoCgNbjM1PuOYFuFoNDnHaK/ZqhKcNx0k76FoTtPu21oWpVu/vk2d1nzwE4Ohm 0AmG8fsk2mhy8wKc6/duzOT+0mcFZnC//9N6/ZfN4iJMrQZ3YyHZF3m29stzLONuKGMANCiIGbgG bDTBH8+uc2t3jebnAZLHiksFM7jdBiW9v72x8IZDfWWYLDcBrDDQwCF5jg0Vk+xIPCKR+8ndV+n9 5/8QzjWoXge80m5wfLYwGbjdAZLkR+n/3nwNmztuIM0W9sQBniTPgf+YEBqV+ytf/OF7qVL5Fitq kO8D2gdkgno6YQJsBs4yENOP2HV//2df+7ufDf54RBrgMeKMyNvvEMB4z+98o1r5yIc/D8efBqmL rNR8znKFU4Act8C4odhe737/+6/9/J+/FQ+S5KsjUSa+8wsRCLhGAFSjAXXvXsdPa+e9auK81E/9 wKt4Jkt9Z632fa3ZQTFbAhQhAJidJE85IVKMFAAcE2kmBZdl1iqtrfbIZNZkfgoTV5FW491sYWHB DhJluMeNsMYhb9LCg+z5KoBVjqLXuVJZsejeQ78+xxUvQZakHEaBsybTzkJzpCmwRgEAsyfyFAE7 UGAYAFKtHSWW/TCz2oNN0tQGEZtQOWPb1nqV4faMjcVhznNyeYD8EWhYBgP4lMJ6gxp3oXZ353Wy 0NK1LrwsCrTJUh2ZVNuQlLNQQAVgQ8yZCFQQRP5AAspYaTidsIu9wHp+YP04td0aTLg7bxcWWnZv mP5Nl1egAodNDcbGKpprO3wpSZzbAXXqPTsXA2TAWTVw1kKx84k5I+aU5A3piqOLFEQBE1WZkLEL 4fwenGdT1wl7Ntipuqjqub3oU8wMXhEC7evGrgP1Bocx2Bg4tKvoLoJtAjWfQjkXk3MgnksIAAzq BZxeAIAQbQAJqANWKmZlI25Fg1X1YKe6b20LDLyOwXA9X/QBiunChuWMkukmjbqyJDlHcfy2cm6B 7PkaOdslNj1ytipd1ymhdG+wa1TXWN/rslK7HEXvc2G4zXvLEwdmmHMJVGAXdnCWurnecGvtkMJw gZPkPpkWyJjdoTjzcK4lEhWMUvMM7ALYheeBvep9DsMljqJwEHk2msOu6/HLE5NQlEDHLnVsYpWx fofQTmgNy4jjUa1bBZ1WOEgLwAKAwR72TdwG6hjmPK/j0MIoUEAeVHQUeDhDPZwbGtIk4PLw+y3C esFnFfbYAPYew7qOgzsJi5UHKF6gQ+WO6nhNuqszY/x1rZNw2g0r4kwPskApCIIgCIJQEP8P03/B 430mofMAAAAASUVORK5CYII= "
+       style="opacity:0.49074079;image-rendering:optimizeQuality"
+       preserveAspectRatio="none"
+       height="28.670942"
+       width="28.670942" />
+    <image
+       y="0.0055872202"
+       x="-0.031073861"
+       id="image3082"
+       xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJAAAACQCAYAAADnRuK4AAAABHNCSVQICAgIfAhkiAAAAAlwSFlz AABO4wAATuMBYdZxSQAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAAZdEVY dFRpdGxlAFR3ZWxmIEljb24gVGVtcGxhdGXyQOONAAAADHRFWHRBdXRob3IAeHBobnh/4OzMAAAA Y3RFWHRDb3B5cmlnaHQAQ0MgQXR0cmlidXRpb24tTm9uQ29tbWVyY2lhbC1TaGFyZUFsaWtlIGh0 dHA6Ly9jcmVhdGl2ZWNvbW1vbnMub3JnL2xpY2Vuc2VzL2J5LW5jLXNhLzQuMC9D0yJcAAAL6klE QVR4nO3dTYvcRhrA8adKJZW6Zc0LmTEmmYUF5zYOJjQLOSTQEMhlA7uHsNlPsCHg5DP4MzghHyC3 xZvTGh8CgYaQXMwEkngOZo1f2JAYz2ReLHePSqqqZy+t2Z5xj6c1pbZa7ecHxo09LdVEf0rVkjIG IIQQQgghhBBCCCEvA9bw7ZPJ4TQ2Oo0DPG6bFFJ9xoVTWUxVH1g25vXxfTAAgG63W/GuSa/XK14e DwTH/HklEVUZ0PF4ilBYkiQMACBN0yP7U0rRzFQRKeWRIMIwRACAOI6x1+uNBlRpRNMIiAEAK8JJ 05QppZjWmmmtGQCAtZbCmRLOOQIACCFQCIFSSgzDEEdCGo1oZgJ6Jp6trS2ulGJpmvI8z/nKysph QMaYw/1STO6KaAAAPM87DGh7ext937dhGFopJa6urtqqI6oyoCPx9Pt9nmUZX15e5koprrXm1lpm rWWIyOI4pngqxDnHJEmAMYacc+ScoxDCSint7u6uDYLARlFkx0RUe0Ds2rVrQRRFn3qe93fO+UVE XKxgu6R6TxDxP4j4z4cPH3529epVNfzzM0fkGhD78ssv/2CtvckYW7fWgtYaEKdyyYE4YoyBEAI4 54CIt5VSf/7oo4/+O/IlpQ/cWQNiAADXrl0LFhYWNowx60mSQJZlZ9wceZGCIIA4jsHzvNsPHjz4 09WrVzM44/UiXnLfhx/PAQCiKPo0z/P1nZ0diqdBsiyDnZ0dyPP80tra2hUYWcPC+Gt5JyoT0DPX eRhjH+7t7dEpq4EQEfb29oAx9rdOp8O73S6H8SEdj+oIMeH+xl0kZP1+/3WKp7kQEQ4ODl7f39/n aZpip9NBAICNjY1xH/MZjDmlTRpQ4chH9SzL2mcdPJkNSqlzSZKINE1tv99HIQSur68X14yKLztx lpjkFDb2ImG/3+fGGLfRk9oZYyCKIhFFkVhYWPAGg4HX7/f51tYW73a7405nR0y6BnomnoODA6+i 74HUTCklDg4OxNOnT0Ucx16WZXxMRGOddgo78sYkSVhxhXlpaansJzgygxCR+b7ve55nfd83aZqa OI4hSRIQQuDwRjjCCWugiSPodrssTVOmtWbD2xM0A80Jz/P8LMuEUkqEYegppbzl5WWepilP05QN Z6HCkUllkkU0AwCWJAlTSjFrLSvubVX6XZDaaK2F7/ssz3PgnKPWGpVS3FprRx65cZuBhjtiw7vq vN1uU0BzwvM8YYzxgiDwtNZeu93m1lpmjGFa68PnucBhEX3IGMOGG+eISHfT5wAiciGEZ609fGKi +HXaeycOqFj/aK0n3jhpBmstR0RmreVSyuI1O3/+/KnvpdMQgeGZhCEiK+Kx1h5OGMcfRR5V5jrQ uJ2SORAEAUNEFoZh6feeaQ1E8cynYllS5vjSKYwAwNnPKBQQcUIBESdlH+eYGa+++ip0Oh1YW1uD KIqAsWYty4wxkCQJ3L9/H27dugX7+/t1D+lMGhnQO++8A2+99Vbdw3DieR4sLS3Bm2++CW+88Qbc vHkT7ty5U/ewSmvcKazT6TQ+nuOEEPD+++/D2tpa3UMprVEBtVotePvtt+sexlRwzuHdd9+texil NSqgixcvQhAEdQ9jas6fPw8rKyt1D6OURgXUtP+4Z/HKK6/UPYRSGhWQ7/t1D2HqpJR1D6GURgVE Zg8FRJxQQMRJIy8kjoOIoJQ6/Qtrxhh77jrnwoULoJSCfr8Pv/76K1hrX+DoypubgAaDAXzxxRd1 D+NU586dg48//vjEv798+TJcvnwZAAAePXoE169fhzRNX9TwSqNT2Ay7cOHCzF84pYBesHa73I8T mPXbGxTQC1b2WtasX/uigIgTCugFK/upatZ//tLcfAo7yQcffAALCwu17PvevXuj//wAAAA8ffq0 1Da2trYqHFH15j6gpaUlWF5ermXf4w5+mRklyzL4/vvvqxxS5eY+oKZ59OgR7O3twc7ODty+fXvm H3WlgGbMjz/+CD/99FPdw5gYLaKJEwqIOKGAiBMKiDihgIgTCog4oYCIEwqIOKGAiBMKiDihgIgT Cog4oYCIEwqIOKGAiBMKiDihgIgTCog4oYCIEwqIOKGAiBMKiDihgIgTCog4oYCIEwqIOKGAiBMK iDihgIgTCog4oYCIEwqIOKGAiJO5/wllu7u7YIypZd9JktSy3xdp7gP66quv6h7CXKNTGHFCAREn FBBxQgERJxQQcTI3n8La7TZ88skndQ/jVIyxuodQqbkJiDEGYRjWPYyXDp3CiBMKiDihgIiTRgWU ZVndQ5g6pVTdQyilUQH9/vvvdQ9h6pr2PTYqoLt37871LPT48WPY3t6uexilNCqgNE3h22+/rXsY U2GthW+++abuYZTWqIAAAH744YeZ/3dEy8rzHG7cuAG//PJL3UMprZEXEr/77ju4d+8edDodWFtb g3PnzjXuCq8xBp48eQL379+HW7duwZMnT+oe0pk0MiAAgN9++w1u3LhR9zBeeo07hZHZQgERJxQQ cUIBEQAAYIzhWd5XOiDP8/CsOyOzjXNe+rhOGhAFM8eyLEPGGKZpCgDlZqMzn8JoFpofw2OJjDFk jCHnHDnnKIRAIQSGYXjisZ44oDAMDzdY7KCS0ZPacc7tMBybpulhRI8fPz79vWV35nkecs7R8zxL s9B8YIxZrbXhnFvP82wxQUwySZQKSAiB29vbKISw/X6f4pkTxhjteZ4ZxmMHg4EdThIohMA4jotj /cwxnyQgBACM4xillMg5Ryml9X2/np9YQConhNBZlpk8z7Xv+0YIYaWUdnisT4wHoMQM1Ov1sFgH 7e7uWiGErWT0pHZaay2lzKWUOssyLaU0u7u7NgxDG4Yh9nq90XiOhHTazVQEgMPb3HEcY5qmFgAg SRKDiI27C06egVLKfDAYWEQ0Ukq7t7dnW62WlVKOnr7GKnMdCItZKIoi22q1DCItg5oOEUFrnUdR lLdaLZMkiWm1WiaKIru6umqHs0/x6xmTroGK33FjY8Ourq7aKIpslmWDir4PUhOlVNLv93WSJKbf 7+t2u/28eJ6JqOzzQAgA0Ov1oNvt2sFgcFdK2aHTWDMhIgwGg3txHOtisRyGIW5sbIyGU9kpbPQ1 9no91FpfT5IE6FTWPIgISZKAMeZfi4uLdnV11W5ubpqNjQ0LABaejWfsQfZK7vfIVPPaa69tRFH0 V2PMed/3gXO6ud8EWmtIkgSyLPv5zp07/+j1evrBgwfPi+XEGeKs557D9125cuWPUsp/c87XhRAg RGOfkn0paK1Baw3W2p+VUn/5/PPPHw7/alwkp55aXBcvDADgvffea1+6dOkKAHzIGLvIOV9w3C6Z AmvtE0S8i4jXNzc3P/v6669TOGWRfJpKAhr+ztfX1/n29rYfRZFQSgnf9/08z4UQwjfGeL7ve9Za joiseO/wNXEwck+yuBFq8zw3nueZPM91EAR5lmW63W5naZrmi4uLZnNzc3StA3CGeADc/6+M0QuN GIYhtlot0+/3IY5jPDg4gCAIUGttpZSeMcYDABYEAQegeKpURJTnuUVElFKaLMvM8HcdhqEeXuMp Hs84dYE8iaoWLAgArLhSrbVmUkqmtTZSSuCco9YajTFWSskRERCRUUDVKQISQqBSyiKiDYLA+L5v ENG0Wi2TZZmNosg+7+Zo6f26bgCOnsZYp9Ph+/v7fDAYeHEce0opT2vN2+02N8Zway2jeKZj9IEw z/PsYDCwwxujJkkS0263zeLioh1+VHda+xSqmIHG3S9DrbVNkgSWl5dRKcWzLOPWWlYENPzaCnZP AP7/zyoUARljMAxDK6W0u7u7NgiCk+5tOc1CVc0CR2ahbrfLtra2uFKKpWnKrbXMGMNWVlaY1poZ Y2j2mZLiGZ7t7W0sHv4bhoST3p4oo+qAites2+2yJElYmqZMKcW01kxrffh11lqKqGKjTxAWjx9L KTEMQ4zjGE+4MToTAR3f1uHH9CIkAIA0TSmaF6R4EH4kHICK4wGoNqDj25vkNZmOkyKpNB6A6R3M 49ulaOpT6aL5uGkfWApndtAjE4QQQgghFfkfLnfEbGu/P8gAAAAASUVORK5CYII= "
+       style="display:inline;image-rendering:optimizeQuality"
+       preserveAspectRatio="none"
+       height="28.697369"
+       width="28.697369" />
+  </g>
+</svg>
diff --git a/app/src/main/assets/ic_device_galaxy_buds.svg b/app/src/main/assets/ic_device_galaxy_buds.svg
new file mode 100644
index 0000000..efd5e73
--- /dev/null
+++ b/app/src/main/assets/ic_device_galaxy_buds.svg
@@ -0,0 +1,189 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   version="1.1"
+   id="svg2"
+   width="28.740126"
+   height="28.740126"
+   viewBox="0 0 28.740126 28.740126"
+   sodipodi:docname="ic_device_galaxy_buds.svg"
+   inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:dc="http://purl.org/dc/elements/1.1/">
+  <metadata
+     id="metadata8">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs6">
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 14.370063 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="28.740126 : 14.370063 : 1"
+       inkscape:persp3d-origin="14.370063 : 9.580042 : 1"
+       id="perspective903" />
+  </defs>
+  <sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="1920"
+     inkscape:window-height="1035"
+     id="namedview4"
+     showgrid="false"
+     showguides="true"
+     inkscape:guide-bbox="true"
+     inkscape:zoom="22.627418"
+     inkscape:cx="8.1096305"
+     inkscape:cy="10.982252"
+     inkscape:window-x="0"
+     inkscape:window-y="45"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="layer1"
+     inkscape:pagecheckerboard="0">
+    <inkscape:grid
+       type="xygrid"
+       id="grid972" />
+  </sodipodi:namedview>
+  <g
+     inkscape:groupmode="layer"
+     id="layer1"
+     inkscape:label="Background"
+     style="display:inline">
+    <g
+       inkscape:groupmode="layer"
+       id="layer4"
+       inkscape:label="Bottom"
+       style="display:inline"
+       sodipodi:insensitive="true">
+      <rect
+         style="display:inline;fill:#1f7fdb;fill-opacity:1;stroke:none;stroke-width:3.57115;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         id="rect817-5"
+         width="22.819572"
+         height="21.904638"
+         x="2.9239759"
+         y="3.8768768"
+         ry="0.9472276" />
+    </g>
+    <g
+       inkscape:groupmode="layer"
+       id="layer3"
+       inkscape:label="Middle"
+       style="display:inline"
+       sodipodi:insensitive="true">
+      <rect
+         style="display:inline;fill:#4dabf5;fill-opacity:1;stroke:none;stroke-width:3.57115;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         id="rect817-7"
+         width="22.819572"
+         height="21.904638"
+         x="2.9317887"
+         y="3.0349438"
+         ry="0.9472276" />
+    </g>
+    <g
+       inkscape:groupmode="layer"
+       id="layer2"
+       inkscape:label="Top"
+       style="display:inline"
+       sodipodi:insensitive="true">
+      <rect
+         style="fill:#2196f3;fill-opacity:1;stroke:none;stroke-width:3.57115;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         id="rect817"
+         width="22.819572"
+         height="21.904638"
+         x="2.9238882"
+         y="3.4130721"
+         ry="0.9472276" />
+    </g>
+    <g
+       id="g2507"
+       transform="matrix(0.99999822,0,0,1,0.29668164,-0.00953878)">
+      <g
+         id="g12091"
+         transform="matrix(0.43308665,0,0,0.43752425,-17.418784,-45.717514)"
+         style="stroke-width:1.14863;stroke-miterlimit:4;stroke-dasharray:none">
+        <g
+           id="g12078"
+           style="stroke-width:1.14863;stroke-miterlimit:4;stroke-dasharray:none">
+          <path
+             style="fill:none;fill-rule:evenodd;stroke:#fcffff;stroke-width:1.14863;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+             d="m 89.229326,149.75243 c 1.422876,1.97773 0.400592,5.02874 -3.539949,5.03091"
+             id="path10585-6"
+             sodipodi:nodetypes="cc" />
+          <path
+             style="fill:none;fill-rule:evenodd;stroke:#fcffff;stroke-width:1.14863;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+             d="m 85.608172,141.34954 c -2.666757,-1.07007 -4.567397,-1.03074 -7.019236,-0.32613 -1.148282,0.32999 -2.049576,1.68227 -2.142569,2.8734 -0.428995,5.49489 2.903496,8.17341 6.263367,7.93085 2.05934,-0.14867 4.130587,-2.74489 4.627575,-5.01288 0.41816,-1.90826 0.08389,-4.73774 -1.729137,-5.46524 z"
+             id="path5256-7"
+             sodipodi:nodetypes="ssssss" />
+          <path
+             style="fill:none;stroke:#fcffff;stroke-width:1.14863;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+             d="m 83.287036,138.80034 c -3.534371,-0.50931 -7.153563,0.99406 -8.625608,4.36799 -1.972926,4.52216 0.871682,11.86623 6.124093,12.64208 9.667201,1.42779 12.807218,-15.52506 2.501515,-17.01007"
+             id="path3567-5" />
+        </g>
+        <path
+           style="fill:none;stroke:#fcffff;stroke-width:1.14863;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+           d="m 81.17037,134.62224 c 1.528272,0.40147 2.479848,2.50023 3.65606,3.48042 1.57278,1.31064 3.329209,2.02976 4.365471,3.91113 0.788477,1.43151 0.572308,3.47576 1.409777,4.73562 0.43788,0.65873 1.309196,0.96445 1.803823,1.61735 1.540471,2.03338 1.370542,5.10243 -0.479425,6.88871 -1.219739,1.17764 -3.141402,1.6612 -4.790555,1.28077 -0.702868,-0.16221 -1.380432,-0.74583 -2.116667,-0.68541 -1.719445,0.14105 -2.786803,0.97944 -4.618181,0.50261 -6.467763,-1.6839 -8.890577,-9.24373 -7.458556,-15.10934 0.757382,-3.10205 4.540443,-7.59068 8.228253,-6.62186"
+           id="path3559-3" />
+      </g>
+      <g
+         id="g1406"
+         transform="matrix(0.64963,0,0,0.65628639,-41.657575,-73.118779)"
+         style="stroke-width:0.765756;stroke-miterlimit:4;stroke-dasharray:none">
+        <path
+           style="fill:none;stroke:#fcffff;stroke-width:0.765756;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+           d="m 80.707855,121.04264 c -1.01885,0.26765 -1.65323,1.66683 -2.43737,2.32029 -1.04852,0.87375 -2.21948,1.35316 -2.91032,2.60741 -0.52565,0.95434 -0.38154,2.31718 -0.93985,3.15709 -0.29192,0.43915 -0.8728,0.64296 -1.20255,1.07824 -1.02698,1.35557 -0.91369,3.40161 0.31962,4.59246 0.81316,0.78509 2.09427,1.10747 3.1937,0.85385 0.46858,-0.10813 0.92029,-0.49721 1.41111,-0.45694 1.1463,0.094 1.85787,0.65296 3.07879,0.33508 4.31184,-1.1226 5.92705,-6.16249 4.97237,-10.0729 -0.50492,-2.06804 -3.02696,-5.06045 -5.4855,-4.41458"
+           id="path3559" />
+        <path
+           style="fill:none;fill-rule:evenodd;stroke:#fcffff;stroke-width:0.765756;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+           d="m 75.335215,131.12944 c -0.94858,1.31848 -0.26706,3.35249 2.35997,3.35394"
+           id="path10585"
+           sodipodi:nodetypes="cc" />
+        <path
+           style="fill:none;fill-rule:evenodd;stroke:#fcffff;stroke-width:0.765756;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+           d="m 77.749325,125.52752 c 1.77783,-0.71339 3.04493,-0.68717 4.67949,-0.21743 0.76552,0.22 1.36638,1.12151 1.42838,1.9156 0.28599,3.66326 -1.93567,5.44894 -4.17558,5.28724 -1.3729,-0.0991 -2.75373,-1.82993 -3.08505,-3.34193 -0.27878,-1.27217 -0.0559,-3.1585 1.15276,-3.64348 z"
+           id="path5256"
+           sodipodi:nodetypes="ssssss" />
+        <path
+           style="fill:none;stroke:#fcffff;stroke-width:0.765756;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+           d="m 79.296745,123.82804 c 2.35625,-0.33954 4.76904,0.66271 5.7504,2.912 1.31529,3.01478 -0.58112,7.91081 -4.08272,8.42805 -6.4448,0.95186 -8.53815,-10.35004 -1.66768,-11.34005"
+           id="path3567" />
+      </g>
+    </g>
+  </g>
+  <g
+     inkscape:groupmode="layer"
+     id="layer5"
+     inkscape:label="Original"
+     style="display:none"
+     sodipodi:insensitive="true">
+    <image
+       y="0.0079936879"
+       x="0.010951031"
+       id="image4124"
+       xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGAAAABgCAMAAADVRocKAAAAA3NCSVQICAjb4U/gAAAACXBIWXMA ADFOAAAxTgGacD5yAAAAGXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAABF0RVh0 VGl0bGUAUGViYmxlIEljb26KvwD9AAAADHRFWHRBdXRob3IAeHBobnh/4OzMAAAAY3RFWHRDb3B5 cmlnaHQAQ0MgQXR0cmlidXRpb24tTm9uQ29tbWVyY2lhbC1TaGFyZUFsaWtlIGh0dHA6Ly9jcmVh dGl2ZWNvbW1vbnMub3JnL2xpY2Vuc2VzL2J5LW5jLXNhLzMuMC/eBBrlAAABy1BMVEUAAAAAAAAA AP8AAIAAAFUAAKoAVaoAAIAAQIAAM2YAM5kzM2YzM5krK4AkJG0kJJIgIIAcHHEcHI4aGoAXF3QX F4sXLnQXLosVK4AUJ3YUJ4kSJIAkJIARInciInciIogQIIAgIIAeHngeHoccHIAcK4AbKHkbKIYa GoAaJoAYJHkXI4AXI4sWIXoWIYUVIIAfH3ofH4UdJ4ATHIQcJnsbJIAaI4QaIoAZIXsZIYQYIHgY IIAXH4MXJ3wXHoAXJoBLpfAWJHwdJHwdJIMcI4Acas0VIoMbIoAaIXwaIIAZH3wZH4MZJXwYHoAY HoYYJIAehtsYHoIefdtHoO0jetZGovNEn+4gg+EfgN5Ep/VKqfREpvQgfdkgfttKqvQggd4hjesw nPMwnPRJqfUhgd4hjutJqfUgfttNq/VNq/QhlPEhlPIhlvMkmPMglvIff9sgjeoglPAhk/AhlPEh lPIhlvMkl/MkmPMlmPMmmPMnmfMpmvMzn/Q0n/Q1n/Q2oPRIqfVNq/VasfZbsvZhtPZitfdtuveQ yvmXzvmj0/qk1Pqu2fu83/vR6f3T6v3Z7f3i8f3j8v3l8/7m8/7p9f7q9f7x+P7y+f7////ougeB AAAAb3RSTlMAAQECAwMDBAQFBQUFBgcHCAkJCgsLCwsMDQ0ODg8PDxAQERESEhMTFBQVFhYXFxgZ GRobGxwdHh8fICAhISIiIiMjIyQkJSYnKCkpKSoqKiorKyssLC2xs7O0tba2tu7u7u7u7+/w8fHy +/v7+/ywJhX9AAAC0klEQVRo3u2a+VfTQBCAm00iW0nSTVNKbKQYbIMUC6jU4q2IB2rF+74vPEAR LSjIIXgAAiqef65py6MNJe17y1RF8/26L/N1N9O8ZGZcLof/DiYFoiV1cZHwiGVZjh7zalRAYYbn sFsQ6RFwGccyBeJjwSMrPn8lHX6fQqRy3tbAsFiU1So9FA4bNIRD63XNTwTO5pSYc9fuvwbgwaW9 SxqYs/eGBgcAGBy+fRrlpRPDsJdfJoEYuMgvzibz/vK3kmDcFMuxNZvM/BG74QTdFV6PJZsYlhe9 XXCCrlCVStwcyhG45QCkoCmiV0o4uwXEib4aQMHjbc0RTXYvCMx/mGdNHaSgraWxRhEXzsgUEC0C KWjfszlUYRWsrYcUHGttNlSRzxUEo5CCjrZYrSqVVlD3Nwj6vqcZsovzKrPeTy3o/5lmxE4wnFl/ 4QhKLvjy2YY5IEExHMH0B1umQQSj9o+EUUewIBh/m8c4qOBbfu58dQRLCmYnJmZLKphKJicdwR8X vJsymSmhIM37FSv4OJlhxnlUOAJHAC8Ye5PHmPNe9PsEJX/5dT5AigvmPtkw53wnr3xB34809uWc zDp9OWflV7xgBaBlzY79MWORALhuui+vbhoArfwe3rUp5MtWfl2Il1QDUtAaj+rKakv1XdEhBfGm Wo1kq+/pM1Ih+wdRI6iIq3IFvEAewgke6QHFg1mU26PjhbtwghsKEbG1X8ew3PmnUPGfXRAwv7hr yqAD13t6nwPQ23PnBMeivG4gg85c7XwCQOeVU+avZ5bslcrrNm7dfehIInGSguOJo+0Hd27ZoOam j3ULvOjTI02x+PYddLTEY42GJpfnpo/L0g50k8pqI1IfbWikoSFaXxfSFKnMrqFsZpKbKKoWDFbT EdQCqlfCdhtIG7AoESLTQogk4EJd/VRTn8fLgecKTg3MjyUsA4SKDD7MT1bQ8++NmvwCRmydh2Fg UvEAAAAASUVORK5CYII= "
+       style="opacity:0.49074079;image-rendering:optimizeQuality"
+       preserveAspectRatio="none"
+       height="28.710279"
+       width="28.710279" />
+  </g>
+</svg>
diff --git a/app/src/main/assets/ic_device_galaxy_buds_disabled.svg b/app/src/main/assets/ic_device_galaxy_buds_disabled.svg
new file mode 100644
index 0000000..9144019
--- /dev/null
+++ b/app/src/main/assets/ic_device_galaxy_buds_disabled.svg
@@ -0,0 +1,242 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   version="1.1"
+   id="svg2"
+   width="28.740126"
+   height="28.740126"
+   viewBox="0 0 28.740126 28.740126"
+   sodipodi:docname="ic_device_galaxy_buds_disabled.svg"
+   inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:android="http://schemas.android.com/apk/res/android">
+  <metadata
+     id="metadata8">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs6">
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 14.370063 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="28.740126 : 14.370063 : 1"
+       inkscape:persp3d-origin="14.370063 : 9.580042 : 1"
+       id="perspective903" />
+  </defs>
+  <sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="1920"
+     inkscape:window-height="1035"
+     id="namedview4"
+     showgrid="false"
+     showguides="true"
+     inkscape:guide-bbox="true"
+     inkscape:zoom="16"
+     inkscape:cx="7.34375"
+     inkscape:cy="7.65625"
+     inkscape:window-x="0"
+     inkscape:window-y="45"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="layer6"
+     inkscape:pagecheckerboard="0">
+    <inkscape:grid
+       type="xygrid"
+       id="grid972" />
+  </sodipodi:namedview>
+  <g
+     inkscape:groupmode="layer"
+     id="layer1"
+     inkscape:label="Background"
+     style="display:inline">
+    <g
+       inkscape:groupmode="layer"
+       id="layer4"
+       inkscape:label="Bottom"
+       style="display:inline"
+       sodipodi:insensitive="true">
+      <rect
+         style="display:inline;fill:#7a7a7a;fill-opacity:1;stroke:none;stroke-width:3.57115173;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         id="rect817-5"
+         width="22.819572"
+         height="21.904638"
+         x="2.9239759"
+         y="3.8768768"
+         ry="0.9472276" />
+    </g>
+    <g
+       inkscape:groupmode="layer"
+       id="layer3"
+       inkscape:label="Middle"
+       style="display:inline"
+       sodipodi:insensitive="true">
+      <rect
+         style="display:inline;fill:#9f9f9f;fill-opacity:0.94117647;stroke:none;stroke-width:3.57115173;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         id="rect817-7"
+         width="22.819572"
+         height="21.904638"
+         x="2.9317887"
+         y="3.0349438"
+         ry="0.9472276" />
+    </g>
+    <g
+       inkscape:groupmode="layer"
+       id="layer2"
+       inkscape:label="Top"
+       style="display:inline"
+       sodipodi:insensitive="true">
+      <rect
+         style="fill:#8a8a8a;fill-opacity:1;stroke:none;stroke-width:3.57115173;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         id="rect817"
+         width="22.819572"
+         height="21.904638"
+         x="2.9238882"
+         y="3.4130721"
+         ry="0.9472276" />
+    </g>
+  </g>
+  <g
+     inkscape:groupmode="layer"
+     id="layer6"
+     inkscape:label="Foreground"
+     style="display:inline">
+    <g
+       inkscape:groupmode="layer"
+       id="layer7"
+       inkscape:label="Backplane"
+       style="display:inline" />
+    <g
+       inkscape:groupmode="layer"
+       id="layer8"
+       inkscape:label="Frontplane"
+       style="display:inline">
+      <g
+         inkscape:groupmode="layer"
+         id="layer9"
+         inkscape:label="Base"
+         style="display:inline" />
+      <g
+         inkscape:groupmode="layer"
+         id="layer10"
+         inkscape:label="Top"
+         style="display:inline" />
+      <g
+         inkscape:groupmode="layer"
+         id="layer11"
+         inkscape:label="Toptop"
+         style="display:inline" />
+      <g
+         inkscape:groupmode="layer"
+         id="layer12"
+         inkscape:label="Toptoptop"
+         style="display:inline" />
+    </g>
+    <path
+       inkscape:connector-curvature="0"
+       style="fill:#8a8a8a;fill-opacity:0.94117647"
+       d="m 12.48275,10.865376 h 3.907 v 6.978 h -3.907 z"
+       android:strokeWidth="1"
+       id="path2735" />
+    <g
+       id="g2507"
+       transform="matrix(0.99999822,0,0,1,0.29668164,-0.00953878)"
+       style="display:inline">
+      <g
+         id="g12091"
+         transform="matrix(0.43308665,0,0,0.43752425,-17.418784,-45.717514)"
+         style="stroke-width:1.14863;stroke-miterlimit:4;stroke-dasharray:none">
+        <g
+           id="g12078"
+           style="stroke-width:1.14863;stroke-miterlimit:4;stroke-dasharray:none">
+          <path
+             style="fill:none;fill-rule:evenodd;stroke:#fcffff;stroke-width:1.14863;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+             d="m 89.229326,149.75243 c 1.422876,1.97773 0.400592,5.02874 -3.539949,5.03091"
+             id="path10585-6"
+             sodipodi:nodetypes="cc" />
+          <path
+             style="fill:none;fill-rule:evenodd;stroke:#fcffff;stroke-width:1.14863;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+             d="m 85.608172,141.34954 c -2.666757,-1.07007 -4.567397,-1.03074 -7.019236,-0.32613 -1.148282,0.32999 -2.049576,1.68227 -2.142569,2.8734 -0.428995,5.49489 2.903496,8.17341 6.263367,7.93085 2.05934,-0.14867 4.130587,-2.74489 4.627575,-5.01288 0.41816,-1.90826 0.08389,-4.73774 -1.729137,-5.46524 z"
+             id="path5256-7"
+             sodipodi:nodetypes="ssssss" />
+          <path
+             style="fill:none;stroke:#fcffff;stroke-width:1.14863;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+             d="m 83.287036,138.80034 c -3.534371,-0.50931 -7.153563,0.99406 -8.625608,4.36799 -1.972926,4.52216 0.871682,11.86623 6.124093,12.64208 9.667201,1.42779 12.807218,-15.52506 2.501515,-17.01007"
+             id="path3567-5" />
+        </g>
+        <path
+           style="fill:none;stroke:#fcffff;stroke-width:1.14863;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+           d="m 81.17037,134.62224 c 1.528272,0.40147 2.479848,2.50023 3.65606,3.48042 1.57278,1.31064 3.329209,2.02976 4.365471,3.91113 0.788477,1.43151 0.572308,3.47576 1.409777,4.73562 0.43788,0.65873 1.309196,0.96445 1.803823,1.61735 1.540471,2.03338 1.370542,5.10243 -0.479425,6.88871 -1.219739,1.17764 -3.141402,1.6612 -4.790555,1.28077 -0.702868,-0.16221 -1.380432,-0.74583 -2.116667,-0.68541 -1.719445,0.14105 -2.786803,0.97944 -4.618181,0.50261 -6.467763,-1.6839 -8.890577,-9.24373 -7.458556,-15.10934 0.757382,-3.10205 4.540443,-7.59068 8.228253,-6.62186"
+           id="path3559-3" />
+      </g>
+      <g
+         id="g1406"
+         transform="matrix(0.64963,0,0,0.65628639,-41.657575,-73.118779)"
+         style="stroke-width:0.765756;stroke-miterlimit:4;stroke-dasharray:none">
+        <path
+           style="fill:none;stroke:#fcffff;stroke-width:0.765756;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+           d="m 80.707855,121.04264 c -1.01885,0.26765 -1.65323,1.66683 -2.43737,2.32029 -1.04852,0.87375 -2.21948,1.35316 -2.91032,2.60741 -0.52565,0.95434 -0.38154,2.31718 -0.93985,3.15709 -0.29192,0.43915 -0.8728,0.64296 -1.20255,1.07824 -1.02698,1.35557 -0.91369,3.40161 0.31962,4.59246 0.81316,0.78509 2.09427,1.10747 3.1937,0.85385 0.46858,-0.10813 0.92029,-0.49721 1.41111,-0.45694 1.1463,0.094 1.85787,0.65296 3.07879,0.33508 4.31184,-1.1226 5.92705,-6.16249 4.97237,-10.0729 -0.50492,-2.06804 -3.02696,-5.06045 -5.4855,-4.41458"
+           id="path3559" />
+        <path
+           style="fill:none;fill-rule:evenodd;stroke:#fcffff;stroke-width:0.765756;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+           d="m 75.335215,131.12944 c -0.94858,1.31848 -0.26706,3.35249 2.35997,3.35394"
+           id="path10585"
+           sodipodi:nodetypes="cc" />
+        <path
+           style="fill:none;fill-rule:evenodd;stroke:#fcffff;stroke-width:0.765756;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+           d="m 77.749325,125.52752 c 1.77783,-0.71339 3.04493,-0.68717 4.67949,-0.21743 0.76552,0.22 1.36638,1.12151 1.42838,1.9156 0.28599,3.66326 -1.93567,5.44894 -4.17558,5.28724 -1.3729,-0.0991 -2.75373,-1.82993 -3.08505,-3.34193 -0.27878,-1.27217 -0.0559,-3.1585 1.15276,-3.64348 z"
+           id="path5256"
+           sodipodi:nodetypes="ssssss" />
+        <path
+           style="fill:none;stroke:#fcffff;stroke-width:0.765756;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+           d="m 79.296745,123.82804 c 2.35625,-0.33954 4.76904,0.66271 5.7504,2.912 1.31529,3.01478 -0.58112,7.91081 -4.08272,8.42805 -6.4448,0.95186 -8.53815,-10.35004 -1.66768,-11.34005"
+           id="path3567" />
+      </g>
+    </g>
+  </g>
+  <g
+     inkscape:groupmode="layer"
+     id="layer5"
+     inkscape:label="Original"
+     style="display:none">
+    <image
+       y="0.028144173"
+       x="0.013794855"
+       id="image1291"
+       xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJAAAACQCAYAAADnRuK4AAAABHNCSVQICAgIfAhkiAAAAAlwSFlz AABO4wAATuMBYdZxSQAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAAZdEVY dFRpdGxlAFR3ZWxmIEljb24gVGVtcGxhdGXyQOONAAAADHRFWHRBdXRob3IAeHBobnh/4OzMAAAA Y3RFWHRDb3B5cmlnaHQAQ0MgQXR0cmlidXRpb24tTm9uQ29tbWVyY2lhbC1TaGFyZUFsaWtlIGh0 dHA6Ly9jcmVhdGl2ZWNvbW1vbnMub3JnL2xpY2Vuc2VzL2J5LW5jLXNhLzQuMC9D0yJcAAANW0lE QVR4nO2dS4xc2VnH/98591VVXd3TbntC92ggwZ5R1BWRRZMoIQG1UIQIs0BCcpINESyyQIqySlgl 2I6yQ2xmtpEQilgQI4EUiAQaQQtCFAiNQjLVUWbsjII19iS2u931vI9zzseiqtxP21V9b7vrlr/f pp/33HPr/O53vvO4VYAgCIIgCIIgCIIgCILwLEBnUz6f8mmfZR7ZpKfyop+GQPvKHNX52mmLKjyS K8NGONAEhclUdMMOy2PsSdMcfr08+LK+JTKdNhurQ0GuD3/RGP58hfc1eSESeUUUMuSQPE0CLgPr ILRv0xp2EMd3CHf3DkiS+yJTQYTh0p4QjS1E0TJvYhGorzA2VjGQ6RoAHklEKECiIhuQDsiz3qC1 9jLF8R1KknNkzDYZs0vAi3CuJeKcEkrNM3ALnrfAnneOw3Cbo2iZN+t3GBtNHkSjh5FoagQ6Ik/j LlSSnKM4NirLbit3fo7Y9AhYgrP9vTjq+iJTTkhVHoqgdIWB+yCvyupeh31/xUWR58Jwm5sX4Abd 21ZhEhUo0NUD8nS7UGkKlS1C2QRq3kA5F5KrJ8Q2IaAOdqnIUxCkAgbaIB2yaoesVMItD06HcP4O XBDA1WpwA4lGkegqYwoEok++ygE/3/+CJv6MVrjIwEIB5QpFw9xyUG9ZuL+Jt+6+tnH1vUneKJRX IPrEN/nFiHrfJqBhHJBYwLqcpQqnglZAqAFPAc7xG0miX3n9s9GtPCOzkwpEAPDJVznQy73NxKBx p8PoZjJBWAbmAsJyjeArfqP747sf2rj6V+mhIf6IJzaomvDchIcJM4Of73+hm6Lx0wdO5CkRnZRx 84FDz9AHgpeWPj+Ycrk2zGOv0j5vnhhgJhHo0DzPNXJwn77VcnDiTulwDNxqOZDWn8LaosI6FLBK B2Vi4GHQOJ5xJxKPThKuN+h+D5esyFNaLAPbfXfp0m6owhgcre0MJx8xnDN68sTjBDPRI3lWCeug xl2oTsbVgq5FOCNaKeba7dCL43nX7d7hS17IYQxurjccNpo4JNERxunC6KA8Ww/neTJb4JUIZ0Jm gbSmvbj2rtefr+leb0l3u1CNu1BYb+zr0h52ZwcYMwcadVt78vT7HS29V/lhANVAedX+olfrWC+t 93SaDtr4oETH8ySB9mXkl7HWXqbRDLN97sKkIzhhGmGmNA79xE/8JHDeXFLVab2i03SwFLXWvj1Y FN+LQgcYX4L1LYrjO2TMLmWLUHNJXxd4GcIZ4uvMDzx4WdLxsijQc0lfZ4tQcfy2iuMVOrQF50A0 GiOJHnZf7UVKkpCcWyKbQJkalF/whQhng/XIQwYKgwBpljIZsE2gnFtwgy03Sxh2Y0dC0JgR6DLW sAZjlsier9G8gXLZxJOQwpTiZ/Csb7Q1mY5Mql0V6jkXkT3fIWOWht0YcNzO0oklcLZLzsVkLRSY ZTV9BnAM5RtPs4NyUUDOBeRcTM7ODdt37ZHHji3QKP9h0yPnIpKtGLMDe1BgQy4g5SwUc0rOxYfa 9/Kxx55gS+sSsNAHerUTVFWYRpg9YoDAIOaM2PEgSJgeGZNRHIOAxWOPHTMCjbLwF/edNJMINDNY YjYUPvx5/OBwghyoP9xRKMwWIdgZAioTBQcZSQkD2J4oKIhAQi5EICEXRT5Y+FR56ZzC5z7o46Mv eHi+RvBLditkDvhFl/Hddwy+/oMMb+2UcyN5KQX67Ad8fOXjIXSJU3lfAS/UCZff7+MPXvZx9TsJ /rqZnXW1JqZk9y3wykUPV36z3PIcRivgq78V4nd/tXz3c6kECjTw5Y+Fp/6eNGcBAfjKx8LSdcWl qu5HVjTeU5tFfQYszxE+vFKuXTKlEuilc6Wq7ol4uWTXWKra1vzZjT4j5kp2jaUSSJg+RCAhFyKQ kIvyTTw8ht2kHA8aLYSPznNePqfwexc97MSM/3nXIpnyZ+9mSqBf/8supv1RawJw80/mHvn3Vy55 eOXSoFluPnD4o3/o45329F6UdGFTzMXnFL78G+GT//EMEYGeMnrCV/xDy9M9sSgCPWUmXaqoTPm8 kAgk5EIEespM+mZcjqc3gQZmbBR2HH/+2xF+7fmzuU9++AuHL/1LfOB36YTD8p9sT/dGs5kX6MU6 4aXFsxFop58vemQO+Iv/TAuqzekw8wKVjTe3HW7sOPxfy+Hv3zR4UyKQMAn/eMPgtc3pjjr7kSRa yIUIJORCBBJyIQIJuRCBhFyIQEIuRCAhFyKQkAsRSMiFCCTkQgQSciECCbkQgYRciEBCLkQgIRci kJALEUjIhQgk5EIEEnIhAgm5EIGEXIhAQi5EICEXIpCQCxFIyIUIJORCBBJyIQIJuRCBhFyIQEIu RCAhFyKQkAsRSMjFzL9D2a02Y/GMPhH51hR/REFRzLxAh98lVSgW6cKEXIhAQi5EICEXIpCQCxFI yMVMjcL++49rZ12FZ46ZEuhxn0UqnA7ShQm5EIGEXIhAQi5KJVAnm/21pXZarmsslUA3pvyzs4rg rTNa+D0ppRLoe7ct3u2W6w6dhDsdxn/dnvAzMc+YUgmUWuBr/5FgFhViAF/9ToKsXAGoXAIBwLdv Glz99wS2ZC/047AO+LN/S/BPb5uzrsrElHIi8RtvZPjebYvPfdDHR1/w8Es1gi7ZrWAc8PMu47vv GHz9B1npcp8RpRQIAN7advjTf00AJGddlWeakt23wrQhAgm5EIGEXIhAwgDSJ5odmVggpStMOpzF qZhnnASkPAb6Ex01pkCrQ2FuTVgpoRxoJvJ4NJ4lGn9B7gRd2H1gd7KTCNMNkWGCZpBhIp9JBaxU zORV2fMWOIqWH9nWYwsURcvseQtMXnVQuApEoBmBDBzIskojRypjooCVig617/Vjjz1BDlRjpSLW Gg5EItEMoAgu87Ql1Xc6YadUykpFrHRn2L6bjz52vFNcxyY24XnnWN/rcsuDI5WIPDNC5sPozFql 4foKTvXgHqiY9b059rz7vFlfGbb1lSNtPoZAVxhoMOorHIbbrNQ86xDOj8Ny7TsQHok2bJSX2TRj 4/mB9Tw4HcIptcthuDSUpnFswBi/C9tY5UEe1GJ/B67loZyrf8IRUsMms37mh3MmSGE6YcX6O3BR 9D4XRbcZG6v75Tkg0pMEYmD0qMx1bNbvcK0GFwRwfrVjwdKLzQAchEHmJzBhqkwn7Fn94K4LArgw 3B52X6ME+uhjU2NGoGE3trHKzeg212pwldacBUsQKj3s0Df9LK5VsnYltkG7aiuVOVurwTUvwGGj OWj7Y/IfYDyBhlHoCgNbjM1PuOYFuFoNDnHaK/ZqhKcNx0k76FoTtPu21oWpVu/vk2d1nzwE4Ohm 0AmG8fsk2mhy8wKc6/duzOT+0mcFZnC//9N6/ZfN4iJMrQZ3YyHZF3m29stzLONuKGMANCiIGbgG bDTBH8+uc2t3jebnAZLHiksFM7jdBiW9v72x8IZDfWWYLDcBrDDQwCF5jg0Vk+xIPCKR+8ndV+n9 5/8QzjWoXge80m5wfLYwGbjdAZLkR+n/3nwNmztuIM0W9sQBniTPgf+YEBqV+ytf/OF7qVL5Fitq kO8D2gdkgno6YQJsBs4yENOP2HV//2df+7ufDf54RBrgMeKMyNvvEMB4z+98o1r5yIc/D8efBqmL rNR8znKFU4Act8C4odhe737/+6/9/J+/FQ+S5KsjUSa+8wsRCLhGAFSjAXXvXsdPa+e9auK81E/9 wKt4Jkt9Z632fa3ZQTFbAhQhAJidJE85IVKMFAAcE2kmBZdl1iqtrfbIZNZkfgoTV5FW491sYWHB DhJluMeNsMYhb9LCg+z5KoBVjqLXuVJZsejeQ78+xxUvQZakHEaBsybTzkJzpCmwRgEAsyfyFAE7 UGAYAFKtHSWW/TCz2oNN0tQGEZtQOWPb1nqV4faMjcVhznNyeYD8EWhYBgP4lMJ6gxp3oXZ353Wy 0NK1LrwsCrTJUh2ZVNuQlLNQQAVgQ8yZCFQQRP5AAspYaTidsIu9wHp+YP04td0aTLg7bxcWWnZv mP5Nl1egAodNDcbGKpprO3wpSZzbAXXqPTsXA2TAWTVw1kKx84k5I+aU5A3piqOLFEQBE1WZkLEL 4fwenGdT1wl7Ntipuqjqub3oU8wMXhEC7evGrgP1Bocx2Bg4tKvoLoJtAjWfQjkXk3MgnksIAAzq BZxeAIAQbQAJqANWKmZlI25Fg1X1YKe6b20LDLyOwXA9X/QBiunChuWMkukmjbqyJDlHcfy2cm6B 7PkaOdslNj1ytipd1ymhdG+wa1TXWN/rslK7HEXvc2G4zXvLEwdmmHMJVGAXdnCWurnecGvtkMJw gZPkPpkWyJjdoTjzcK4lEhWMUvMM7ALYheeBvep9DsMljqJwEHk2msOu6/HLE5NQlEDHLnVsYpWx fofQTmgNy4jjUa1bBZ1WOEgLwAKAwR72TdwG6hjmPK/j0MIoUEAeVHQUeDhDPZwbGtIk4PLw+y3C esFnFfbYAPYew7qOgzsJi5UHKF6gQ+WO6nhNuqszY/x1rZNw2g0r4kwPskApCIIgCIJQEP8P03/B 430mofMAAAAASUVORK5CYII= "
+       style="opacity:0.49074079;image-rendering:optimizeQuality"
+       preserveAspectRatio="none"
+       height="28.670942"
+       width="28.670942" />
+    <image
+       y="0.0055872202"
+       x="-0.031073861"
+       id="image3082"
+       xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJAAAACQCAYAAADnRuK4AAAABHNCSVQICAgIfAhkiAAAAAlwSFlz AABO4wAATuMBYdZxSQAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAAZdEVY dFRpdGxlAFR3ZWxmIEljb24gVGVtcGxhdGXyQOONAAAADHRFWHRBdXRob3IAeHBobnh/4OzMAAAA Y3RFWHRDb3B5cmlnaHQAQ0MgQXR0cmlidXRpb24tTm9uQ29tbWVyY2lhbC1TaGFyZUFsaWtlIGh0 dHA6Ly9jcmVhdGl2ZWNvbW1vbnMub3JnL2xpY2Vuc2VzL2J5LW5jLXNhLzQuMC9D0yJcAAAL6klE QVR4nO3dTYvcRhrA8adKJZW6Zc0LmTEmmYUF5zYOJjQLOSTQEMhlA7uHsNlPsCHg5DP4MzghHyC3 xZvTGh8CgYaQXMwEkngOZo1f2JAYz2ReLHePSqqqZy+t2Z5xj6c1pbZa7ecHxo09LdVEf0rVkjIG IIQQQgghhBBCCCEvA9bw7ZPJ4TQ2Oo0DPG6bFFJ9xoVTWUxVH1g25vXxfTAAgG63W/GuSa/XK14e DwTH/HklEVUZ0PF4ilBYkiQMACBN0yP7U0rRzFQRKeWRIMIwRACAOI6x1+uNBlRpRNMIiAEAK8JJ 05QppZjWmmmtGQCAtZbCmRLOOQIACCFQCIFSSgzDEEdCGo1oZgJ6Jp6trS2ulGJpmvI8z/nKysph QMaYw/1STO6KaAAAPM87DGh7ext937dhGFopJa6urtqqI6oyoCPx9Pt9nmUZX15e5koprrXm1lpm rWWIyOI4pngqxDnHJEmAMYacc+ScoxDCSint7u6uDYLARlFkx0RUe0Ds2rVrQRRFn3qe93fO+UVE XKxgu6R6TxDxP4j4z4cPH3529epVNfzzM0fkGhD78ssv/2CtvckYW7fWgtYaEKdyyYE4YoyBEAI4 54CIt5VSf/7oo4/+O/IlpQ/cWQNiAADXrl0LFhYWNowx60mSQJZlZ9wceZGCIIA4jsHzvNsPHjz4 09WrVzM44/UiXnLfhx/PAQCiKPo0z/P1nZ0diqdBsiyDnZ0dyPP80tra2hUYWcPC+Gt5JyoT0DPX eRhjH+7t7dEpq4EQEfb29oAx9rdOp8O73S6H8SEdj+oIMeH+xl0kZP1+/3WKp7kQEQ4ODl7f39/n aZpip9NBAICNjY1xH/MZjDmlTRpQ4chH9SzL2mcdPJkNSqlzSZKINE1tv99HIQSur68X14yKLztx lpjkFDb2ImG/3+fGGLfRk9oZYyCKIhFFkVhYWPAGg4HX7/f51tYW73a7405nR0y6BnomnoODA6+i 74HUTCklDg4OxNOnT0Ucx16WZXxMRGOddgo78sYkSVhxhXlpaansJzgygxCR+b7ve55nfd83aZqa OI4hSRIQQuDwRjjCCWugiSPodrssTVOmtWbD2xM0A80Jz/P8LMuEUkqEYegppbzl5WWepilP05QN Z6HCkUllkkU0AwCWJAlTSjFrLSvubVX6XZDaaK2F7/ssz3PgnKPWGpVS3FprRx65cZuBhjtiw7vq vN1uU0BzwvM8YYzxgiDwtNZeu93m1lpmjGFa68PnucBhEX3IGMOGG+eISHfT5wAiciGEZ609fGKi +HXaeycOqFj/aK0n3jhpBmstR0RmreVSyuI1O3/+/KnvpdMQgeGZhCEiK+Kx1h5OGMcfRR5V5jrQ uJ2SORAEAUNEFoZh6feeaQ1E8cynYllS5vjSKYwAwNnPKBQQcUIBESdlH+eYGa+++ip0Oh1YW1uD KIqAsWYty4wxkCQJ3L9/H27dugX7+/t1D+lMGhnQO++8A2+99Vbdw3DieR4sLS3Bm2++CW+88Qbc vHkT7ty5U/ewSmvcKazT6TQ+nuOEEPD+++/D2tpa3UMprVEBtVotePvtt+sexlRwzuHdd9+texil NSqgixcvQhAEdQ9jas6fPw8rKyt1D6OURgXUtP+4Z/HKK6/UPYRSGhWQ7/t1D2HqpJR1D6GURgVE Zg8FRJxQQMRJIy8kjoOIoJQ6/Qtrxhh77jrnwoULoJSCfr8Pv/76K1hrX+DoypubgAaDAXzxxRd1 D+NU586dg48//vjEv798+TJcvnwZAAAePXoE169fhzRNX9TwSqNT2Ay7cOHCzF84pYBesHa73I8T mPXbGxTQC1b2WtasX/uigIgTCugFK/upatZ//tLcfAo7yQcffAALCwu17PvevXuj//wAAAA8ffq0 1Da2trYqHFH15j6gpaUlWF5ermXf4w5+mRklyzL4/vvvqxxS5eY+oKZ59OgR7O3twc7ODty+fXvm H3WlgGbMjz/+CD/99FPdw5gYLaKJEwqIOKGAiBMKiDihgIgTCog4oYCIEwqIOKGAiBMKiDihgIgT Cog4oYCIEwqIOKGAiBMKiDihgIgTCog4oYCIEwqIOKGAiBMKiDihgIgTCog4oYCIEwqIOKGAiBMK iDihgIgTCog4oYCIEwqIOKGAiJO5/wllu7u7YIypZd9JktSy3xdp7gP66quv6h7CXKNTGHFCAREn FBBxQgERJxQQcTI3n8La7TZ88skndQ/jVIyxuodQqbkJiDEGYRjWPYyXDp3CiBMKiDihgIiTRgWU ZVndQ5g6pVTdQyilUQH9/vvvdQ9h6pr2PTYqoLt37871LPT48WPY3t6uexilNCqgNE3h22+/rXsY U2GthW+++abuYZTWqIAAAH744YeZ/3dEy8rzHG7cuAG//PJL3UMprZEXEr/77ju4d+8edDodWFtb g3PnzjXuCq8xBp48eQL379+HW7duwZMnT+oe0pk0MiAAgN9++w1u3LhR9zBeeo07hZHZQgERJxQQ cUIBEQAAYIzhWd5XOiDP8/CsOyOzjXNe+rhOGhAFM8eyLEPGGKZpCgDlZqMzn8JoFpofw2OJjDFk jCHnHDnnKIRAIQSGYXjisZ44oDAMDzdY7KCS0ZPacc7tMBybpulhRI8fPz79vWV35nkecs7R8zxL s9B8YIxZrbXhnFvP82wxQUwySZQKSAiB29vbKISw/X6f4pkTxhjteZ4ZxmMHg4EdThIohMA4jotj /cwxnyQgBACM4xillMg5Ryml9X2/np9YQConhNBZlpk8z7Xv+0YIYaWUdnisT4wHoMQM1Ov1sFgH 7e7uWiGErWT0pHZaay2lzKWUOssyLaU0u7u7NgxDG4Yh9nq90XiOhHTazVQEgMPb3HEcY5qmFgAg SRKDiI27C06egVLKfDAYWEQ0Ukq7t7dnW62WlVKOnr7GKnMdCItZKIoi22q1DCItg5oOEUFrnUdR lLdaLZMkiWm1WiaKIru6umqHs0/x6xmTroGK33FjY8Ourq7aKIpslmWDir4PUhOlVNLv93WSJKbf 7+t2u/28eJ6JqOzzQAgA0Ov1oNvt2sFgcFdK2aHTWDMhIgwGg3txHOtisRyGIW5sbIyGU9kpbPQ1 9no91FpfT5IE6FTWPIgISZKAMeZfi4uLdnV11W5ubpqNjQ0LABaejWfsQfZK7vfIVPPaa69tRFH0 V2PMed/3gXO6ud8EWmtIkgSyLPv5zp07/+j1evrBgwfPi+XEGeKs557D9125cuWPUsp/c87XhRAg RGOfkn0paK1Baw3W2p+VUn/5/PPPHw7/alwkp55aXBcvDADgvffea1+6dOkKAHzIGLvIOV9w3C6Z AmvtE0S8i4jXNzc3P/v6669TOGWRfJpKAhr+ztfX1/n29rYfRZFQSgnf9/08z4UQwjfGeL7ve9Za joiseO/wNXEwck+yuBFq8zw3nueZPM91EAR5lmW63W5naZrmi4uLZnNzc3StA3CGeADc/6+M0QuN GIYhtlot0+/3IY5jPDg4gCAIUGttpZSeMcYDABYEAQegeKpURJTnuUVElFKaLMvM8HcdhqEeXuMp Hs84dYE8iaoWLAgArLhSrbVmUkqmtTZSSuCco9YajTFWSskRERCRUUDVKQISQqBSyiKiDYLA+L5v ENG0Wi2TZZmNosg+7+Zo6f26bgCOnsZYp9Ph+/v7fDAYeHEce0opT2vN2+02N8Zway2jeKZj9IEw z/PsYDCwwxujJkkS0263zeLioh1+VHda+xSqmIHG3S9DrbVNkgSWl5dRKcWzLOPWWlYENPzaCnZP AP7/zyoUARljMAxDK6W0u7u7NgiCk+5tOc1CVc0CR2ahbrfLtra2uFKKpWnKrbXMGMNWVlaY1poZ Y2j2mZLiGZ7t7W0sHv4bhoST3p4oo+qAites2+2yJElYmqZMKcW01kxrffh11lqKqGKjTxAWjx9L KTEMQ4zjGE+4MToTAR3f1uHH9CIkAIA0TSmaF6R4EH4kHICK4wGoNqDj25vkNZmOkyKpNB6A6R3M 49ulaOpT6aL5uGkfWApndtAjE4QQQgghFfkfLnfEbGu/P8gAAAAASUVORK5CYII= "
+       style="display:inline;image-rendering:optimizeQuality"
+       preserveAspectRatio="none"
+       height="28.697369"
+       width="28.697369" />
+  </g>
+</svg>
diff --git a/app/src/main/assets/ic_device_galaxy_buds_live.svg b/app/src/main/assets/ic_device_galaxy_buds_live.svg
new file mode 100644
index 0000000..acb6313
--- /dev/null
+++ b/app/src/main/assets/ic_device_galaxy_buds_live.svg
@@ -0,0 +1,166 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   version="1.1"
+   id="svg2"
+   width="28.740126"
+   height="28.740126"
+   viewBox="0 0 28.740126 28.740126"
+   sodipodi:docname="ic_device_galaxy_buds_live.svg"
+   inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:dc="http://purl.org/dc/elements/1.1/">
+  <metadata
+     id="metadata8">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs6">
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 14.370063 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="28.740126 : 14.370063 : 1"
+       inkscape:persp3d-origin="14.370063 : 9.580042 : 1"
+       id="perspective903" />
+  </defs>
+  <sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="1920"
+     inkscape:window-height="1035"
+     id="namedview4"
+     showgrid="false"
+     showguides="true"
+     inkscape:guide-bbox="true"
+     inkscape:zoom="16.000001"
+     inkscape:cx="7.5312497"
+     inkscape:cy="18.906249"
+     inkscape:window-x="0"
+     inkscape:window-y="45"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="layer1"
+     inkscape:pagecheckerboard="0">
+    <inkscape:grid
+       type="xygrid"
+       id="grid972" />
+  </sodipodi:namedview>
+  <g
+     inkscape:groupmode="layer"
+     id="layer1"
+     inkscape:label="Background"
+     style="display:inline">
+    <g
+       inkscape:groupmode="layer"
+       id="layer4"
+       inkscape:label="Bottom"
+       style="display:inline"
+       sodipodi:insensitive="true">
+      <rect
+         style="display:inline;fill:#1f7fdb;fill-opacity:1;stroke:none;stroke-width:3.57115;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         id="rect817-5"
+         width="22.819572"
+         height="21.904638"
+         x="2.9239759"
+         y="3.8768768"
+         ry="0.9472276" />
+    </g>
+    <g
+       inkscape:groupmode="layer"
+       id="layer3"
+       inkscape:label="Middle"
+       style="display:inline"
+       sodipodi:insensitive="true">
+      <rect
+         style="display:inline;fill:#4dabf5;fill-opacity:1;stroke:none;stroke-width:3.57115;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         id="rect817-7"
+         width="22.819572"
+         height="21.904638"
+         x="2.9317887"
+         y="3.0349438"
+         ry="0.9472276" />
+    </g>
+    <g
+       inkscape:groupmode="layer"
+       id="layer2"
+       inkscape:label="Top"
+       style="display:inline"
+       sodipodi:insensitive="true">
+      <rect
+         style="fill:#2196f3;fill-opacity:1;stroke:none;stroke-width:3.57115;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         id="rect817"
+         width="22.819572"
+         height="21.904638"
+         x="2.9238882"
+         y="3.4130721"
+         ry="0.9472276" />
+    </g>
+    <g
+       id="g3814-7"
+       transform="matrix(-0.87287371,0,0,0.8814596,23.235344,11.404728)"
+       style="fill:none;fill-opacity:1;stroke:#fffffd;stroke-width:0.861768;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1">
+      <g
+         id="g1577">
+        <g
+           id="g3814"
+           transform="matrix(-1,0,0,1,19.724468,-6.6444907)"
+           style="display:inline;fill:none;fill-opacity:1;stroke:#fffffd;stroke-width:0.861768;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1">
+          <path
+             style="fill:none;fill-opacity:1;stroke:#fffffd;stroke-width:0.861768;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+             d="M 6.3534766,13.150028 C 5.2590543,14.102574 3.7589552,14.39697 2.6009409,13.886465 1.3699015,13.343766 0.52234461,12.049487 0.50706651,10.688971 0.49818111,9.8974897 0.68425351,9.3259096 1.2026925,8.5521587 1.6467421,7.8894314 1.7546703,7.5633672 1.7383195,6.9339544 1.7242951,6.3940166 1.5887612,6.0540278 1.1304302,5.409047 0.69461341,4.7957481 0.51189951,4.3487514 0.43456241,3.7066569 0.29489991,2.5470862 0.74072801,1.4253568 1.6710145,0.59567225 2.8470953,-0.45322535 4.5920415,-0.46138405 5.9962565,0.57544755 7.6247819,1.7779049 8.5957069,4.0874446 8.6379302,6.8591974 8.6637127,8.551633 8.3637849,10.047519 7.7501356,11.287081 7.5146695,11.762713 7.3453654,12.031594 7.0219979,12.443468 6.7260442,12.820427 6.6974699,12.850626 6.3534853,13.150033 Z"
+             id="path11168-8" />
+          <path
+             style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#fffffd;stroke-width:0.861768;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+             d="m 6.5908811,5.6846666 c 0,0 0.5148355,0.502981 0.476276,1.6957252 -0.03255,1.0068473 -0.6176138,1.4771457 -0.6176138,1.4771457"
+             id="path2468"
+             sodipodi:nodetypes="csc" />
+        </g>
+        <path
+           style="fill:none;fill-opacity:1;stroke:#fffffd;stroke-width:0.861768;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+           d="M 6.3534766,13.150028 C 5.2590543,14.102574 3.7589552,14.39697 2.6009409,13.886465 1.3699015,13.343766 0.52234461,12.049487 0.50706651,10.688971 0.49818111,9.8974897 0.68425351,9.3259096 1.2026925,8.5521587 1.6467421,7.8894314 1.7546703,7.5633672 1.7383195,6.9339544 1.7242951,6.3940166 1.5887612,6.0540278 1.1304302,5.409047 0.69461341,4.7957481 0.51189951,4.3487514 0.43456241,3.7066569 0.29489991,2.5470862 0.74072801,1.4253568 1.6710145,0.59567225 2.8470953,-0.45322535 4.5920415,-0.46138405 5.9962565,0.57544755 7.6247819,1.7779049 8.5957069,4.0874446 8.6379302,6.8591974 8.6637127,8.551633 8.3637849,10.047519 7.7501356,11.287081 7.5146695,11.762713 7.3453654,12.031594 7.0219979,12.443468 6.7260442,12.820427 6.6974699,12.850626 6.3534853,13.150033 Z"
+           id="path11168-8-1" />
+      </g>
+      <path
+         style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#fffffd;stroke-width:0.861768;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         d="m 6.5908811,5.6846666 c 0,0 0.5148355,0.502981 0.476276,1.6957252 -0.03255,1.0068473 -0.6176138,1.4771457 -0.6176138,1.4771457"
+         id="path2468-7"
+         sodipodi:nodetypes="csc" />
+    </g>
+  </g>
+  <g
+     inkscape:groupmode="layer"
+     id="layer5"
+     inkscape:label="Original"
+     style="display:none"
+     sodipodi:insensitive="true">
+    <image
+       y="0.0079936879"
+       x="0.010951031"
+       id="image4124"
+       xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGAAAABgCAMAAADVRocKAAAAA3NCSVQICAjb4U/gAAAACXBIWXMA ADFOAAAxTgGacD5yAAAAGXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAABF0RVh0 VGl0bGUAUGViYmxlIEljb26KvwD9AAAADHRFWHRBdXRob3IAeHBobnh/4OzMAAAAY3RFWHRDb3B5 cmlnaHQAQ0MgQXR0cmlidXRpb24tTm9uQ29tbWVyY2lhbC1TaGFyZUFsaWtlIGh0dHA6Ly9jcmVh dGl2ZWNvbW1vbnMub3JnL2xpY2Vuc2VzL2J5LW5jLXNhLzMuMC/eBBrlAAABy1BMVEUAAAAAAAAA AP8AAIAAAFUAAKoAVaoAAIAAQIAAM2YAM5kzM2YzM5krK4AkJG0kJJIgIIAcHHEcHI4aGoAXF3QX F4sXLnQXLosVK4AUJ3YUJ4kSJIAkJIARInciInciIogQIIAgIIAeHngeHoccHIAcK4AbKHkbKIYa GoAaJoAYJHkXI4AXI4sWIXoWIYUVIIAfH3ofH4UdJ4ATHIQcJnsbJIAaI4QaIoAZIXsZIYQYIHgY IIAXH4MXJ3wXHoAXJoBLpfAWJHwdJHwdJIMcI4Acas0VIoMbIoAaIXwaIIAZH3wZH4MZJXwYHoAY HoYYJIAehtsYHoIefdtHoO0jetZGovNEn+4gg+EfgN5Ep/VKqfREpvQgfdkgfttKqvQggd4hjesw nPMwnPRJqfUhgd4hjutJqfUgfttNq/VNq/QhlPEhlPIhlvMkmPMglvIff9sgjeoglPAhk/AhlPEh lPIhlvMkl/MkmPMlmPMmmPMnmfMpmvMzn/Q0n/Q1n/Q2oPRIqfVNq/VasfZbsvZhtPZitfdtuveQ yvmXzvmj0/qk1Pqu2fu83/vR6f3T6v3Z7f3i8f3j8v3l8/7m8/7p9f7q9f7x+P7y+f7////ougeB AAAAb3RSTlMAAQECAwMDBAQFBQUFBgcHCAkJCgsLCwsMDQ0ODg8PDxAQERESEhMTFBQVFhYXFxgZ GRobGxwdHh8fICAhISIiIiMjIyQkJSYnKCkpKSoqKiorKyssLC2xs7O0tba2tu7u7u7u7+/w8fHy +/v7+/ywJhX9AAAC0klEQVRo3u2a+VfTQBCAm00iW0nSTVNKbKQYbIMUC6jU4q2IB2rF+74vPEAR LSjIIXgAAiqef65py6MNJe17y1RF8/26L/N1N9O8ZGZcLof/DiYFoiV1cZHwiGVZjh7zalRAYYbn sFsQ6RFwGccyBeJjwSMrPn8lHX6fQqRy3tbAsFiU1So9FA4bNIRD63XNTwTO5pSYc9fuvwbgwaW9 SxqYs/eGBgcAGBy+fRrlpRPDsJdfJoEYuMgvzibz/vK3kmDcFMuxNZvM/BG74QTdFV6PJZsYlhe9 XXCCrlCVStwcyhG45QCkoCmiV0o4uwXEib4aQMHjbc0RTXYvCMx/mGdNHaSgraWxRhEXzsgUEC0C KWjfszlUYRWsrYcUHGttNlSRzxUEo5CCjrZYrSqVVlD3Nwj6vqcZsovzKrPeTy3o/5lmxE4wnFl/ 4QhKLvjy2YY5IEExHMH0B1umQQSj9o+EUUewIBh/m8c4qOBbfu58dQRLCmYnJmZLKphKJicdwR8X vJsymSmhIM37FSv4OJlhxnlUOAJHAC8Ye5PHmPNe9PsEJX/5dT5AigvmPtkw53wnr3xB34809uWc zDp9OWflV7xgBaBlzY79MWORALhuui+vbhoArfwe3rUp5MtWfl2Il1QDUtAaj+rKakv1XdEhBfGm Wo1kq+/pM1Ih+wdRI6iIq3IFvEAewgke6QHFg1mU26PjhbtwghsKEbG1X8ew3PmnUPGfXRAwv7hr yqAD13t6nwPQ23PnBMeivG4gg85c7XwCQOeVU+avZ5bslcrrNm7dfehIInGSguOJo+0Hd27ZoOam j3ULvOjTI02x+PYddLTEY42GJpfnpo/L0g50k8pqI1IfbWikoSFaXxfSFKnMrqFsZpKbKKoWDFbT EdQCqlfCdhtIG7AoESLTQogk4EJd/VRTn8fLgecKTg3MjyUsA4SKDD7MT1bQ8++NmvwCRmydh2Fg UvEAAAAASUVORK5CYII= "
+       style="opacity:0.49074079;image-rendering:optimizeQuality"
+       preserveAspectRatio="none"
+       height="28.710279"
+       width="28.710279" />
+  </g>
+</svg>
diff --git a/app/src/main/assets/ic_device_galaxy_buds_live_disabled.svg b/app/src/main/assets/ic_device_galaxy_buds_live_disabled.svg
new file mode 100644
index 0000000..daf6955
--- /dev/null
+++ b/app/src/main/assets/ic_device_galaxy_buds_live_disabled.svg
@@ -0,0 +1,218 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   version="1.1"
+   id="svg2"
+   width="28.740126"
+   height="28.740126"
+   viewBox="0 0 28.740126 28.740126"
+   sodipodi:docname="ic_device_galaxy_buds_live_disabled.svg"
+   inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:android="http://schemas.android.com/apk/res/android">
+  <metadata
+     id="metadata8">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs6">
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 14.370063 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="28.740126 : 14.370063 : 1"
+       inkscape:persp3d-origin="14.370063 : 9.580042 : 1"
+       id="perspective903" />
+  </defs>
+  <sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="1920"
+     inkscape:window-height="1035"
+     id="namedview4"
+     showgrid="false"
+     showguides="true"
+     inkscape:guide-bbox="true"
+     inkscape:zoom="16"
+     inkscape:cx="7.34375"
+     inkscape:cy="7.65625"
+     inkscape:window-x="0"
+     inkscape:window-y="45"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="layer6"
+     inkscape:pagecheckerboard="0">
+    <inkscape:grid
+       type="xygrid"
+       id="grid972" />
+  </sodipodi:namedview>
+  <g
+     inkscape:groupmode="layer"
+     id="layer1"
+     inkscape:label="Background"
+     style="display:inline">
+    <g
+       inkscape:groupmode="layer"
+       id="layer4"
+       inkscape:label="Bottom"
+       style="display:inline"
+       sodipodi:insensitive="true">
+      <rect
+         style="display:inline;fill:#7a7a7a;fill-opacity:1;stroke:none;stroke-width:3.57115173;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         id="rect817-5"
+         width="22.819572"
+         height="21.904638"
+         x="2.9239759"
+         y="3.8768768"
+         ry="0.9472276" />
+    </g>
+    <g
+       inkscape:groupmode="layer"
+       id="layer3"
+       inkscape:label="Middle"
+       style="display:inline"
+       sodipodi:insensitive="true">
+      <rect
+         style="display:inline;fill:#9f9f9f;fill-opacity:0.94117647;stroke:none;stroke-width:3.57115173;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         id="rect817-7"
+         width="22.819572"
+         height="21.904638"
+         x="2.9317887"
+         y="3.0349438"
+         ry="0.9472276" />
+    </g>
+    <g
+       inkscape:groupmode="layer"
+       id="layer2"
+       inkscape:label="Top"
+       style="display:inline"
+       sodipodi:insensitive="true">
+      <rect
+         style="fill:#8a8a8a;fill-opacity:1;stroke:none;stroke-width:3.57115173;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         id="rect817"
+         width="22.819572"
+         height="21.904638"
+         x="2.9238882"
+         y="3.4130721"
+         ry="0.9472276" />
+    </g>
+  </g>
+  <g
+     inkscape:groupmode="layer"
+     id="layer6"
+     inkscape:label="Foreground"
+     style="display:inline">
+    <g
+       inkscape:groupmode="layer"
+       id="layer7"
+       inkscape:label="Backplane"
+       style="display:inline" />
+    <g
+       inkscape:groupmode="layer"
+       id="layer8"
+       inkscape:label="Frontplane"
+       style="display:inline">
+      <g
+         inkscape:groupmode="layer"
+         id="layer9"
+         inkscape:label="Base"
+         style="display:inline" />
+      <g
+         inkscape:groupmode="layer"
+         id="layer10"
+         inkscape:label="Top"
+         style="display:inline" />
+      <g
+         inkscape:groupmode="layer"
+         id="layer11"
+         inkscape:label="Toptop"
+         style="display:inline" />
+      <g
+         inkscape:groupmode="layer"
+         id="layer12"
+         inkscape:label="Toptoptop"
+         style="display:inline" />
+    </g>
+    <path
+       inkscape:connector-curvature="0"
+       style="fill:#8a8a8a;fill-opacity:0.94117647"
+       d="m 12.48275,10.865376 h 3.907 v 6.978 h -3.907 z"
+       android:strokeWidth="1"
+       id="path2735" />
+    <g
+       id="g3814-7"
+       transform="matrix(-0.87287371,0,0,0.8814596,23.235344,11.404728)"
+       style="display:inline;fill:none;fill-opacity:1;stroke:#fffffd;stroke-width:0.861768;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1">
+      <g
+         id="g1577">
+        <g
+           id="g3814"
+           transform="matrix(-1,0,0,1,19.724468,-6.6444907)"
+           style="display:inline;fill:none;fill-opacity:1;stroke:#fffffd;stroke-width:0.861768;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1">
+          <path
+             style="fill:none;fill-opacity:1;stroke:#fffffd;stroke-width:0.861768;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+             d="M 6.3534766,13.150028 C 5.2590543,14.102574 3.7589552,14.39697 2.6009409,13.886465 1.3699015,13.343766 0.52234461,12.049487 0.50706651,10.688971 0.49818111,9.8974897 0.68425351,9.3259096 1.2026925,8.5521587 1.6467421,7.8894314 1.7546703,7.5633672 1.7383195,6.9339544 1.7242951,6.3940166 1.5887612,6.0540278 1.1304302,5.409047 0.69461341,4.7957481 0.51189951,4.3487514 0.43456241,3.7066569 0.29489991,2.5470862 0.74072801,1.4253568 1.6710145,0.59567225 2.8470953,-0.45322535 4.5920415,-0.46138405 5.9962565,0.57544755 7.6247819,1.7779049 8.5957069,4.0874446 8.6379302,6.8591974 8.6637127,8.551633 8.3637849,10.047519 7.7501356,11.287081 7.5146695,11.762713 7.3453654,12.031594 7.0219979,12.443468 6.7260442,12.820427 6.6974699,12.850626 6.3534853,13.150033 Z"
+             id="path11168-8" />
+          <path
+             style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#fffffd;stroke-width:0.861768;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+             d="m 6.5908811,5.6846666 c 0,0 0.5148355,0.502981 0.476276,1.6957252 -0.03255,1.0068473 -0.6176138,1.4771457 -0.6176138,1.4771457"
+             id="path2468"
+             sodipodi:nodetypes="csc" />
+        </g>
+        <path
+           style="fill:none;fill-opacity:1;stroke:#fffffd;stroke-width:0.861768;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+           d="M 6.3534766,13.150028 C 5.2590543,14.102574 3.7589552,14.39697 2.6009409,13.886465 1.3699015,13.343766 0.52234461,12.049487 0.50706651,10.688971 0.49818111,9.8974897 0.68425351,9.3259096 1.2026925,8.5521587 1.6467421,7.8894314 1.7546703,7.5633672 1.7383195,6.9339544 1.7242951,6.3940166 1.5887612,6.0540278 1.1304302,5.409047 0.69461341,4.7957481 0.51189951,4.3487514 0.43456241,3.7066569 0.29489991,2.5470862 0.74072801,1.4253568 1.6710145,0.59567225 2.8470953,-0.45322535 4.5920415,-0.46138405 5.9962565,0.57544755 7.6247819,1.7779049 8.5957069,4.0874446 8.6379302,6.8591974 8.6637127,8.551633 8.3637849,10.047519 7.7501356,11.287081 7.5146695,11.762713 7.3453654,12.031594 7.0219979,12.443468 6.7260442,12.820427 6.6974699,12.850626 6.3534853,13.150033 Z"
+           id="path11168-8-1" />
+      </g>
+      <path
+         style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#fffffd;stroke-width:0.861768;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         d="m 6.5908811,5.6846666 c 0,0 0.5148355,0.502981 0.476276,1.6957252 -0.03255,1.0068473 -0.6176138,1.4771457 -0.6176138,1.4771457"
+         id="path2468-7"
+         sodipodi:nodetypes="csc" />
+    </g>
+  </g>
+  <g
+     inkscape:groupmode="layer"
+     id="layer5"
+     inkscape:label="Original"
+     style="display:none">
+    <image
+       y="0.028144173"
+       x="0.013794855"
+       id="image1291"
+       xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJAAAACQCAYAAADnRuK4AAAABHNCSVQICAgIfAhkiAAAAAlwSFlz AABO4wAATuMBYdZxSQAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAAZdEVY dFRpdGxlAFR3ZWxmIEljb24gVGVtcGxhdGXyQOONAAAADHRFWHRBdXRob3IAeHBobnh/4OzMAAAA Y3RFWHRDb3B5cmlnaHQAQ0MgQXR0cmlidXRpb24tTm9uQ29tbWVyY2lhbC1TaGFyZUFsaWtlIGh0 dHA6Ly9jcmVhdGl2ZWNvbW1vbnMub3JnL2xpY2Vuc2VzL2J5LW5jLXNhLzQuMC9D0yJcAAANW0lE QVR4nO2dS4xc2VnH/98591VVXd3TbntC92ggwZ5R1BWRRZMoIQG1UIQIs0BCcpINESyyQIqySlgl 2I6yQ2xmtpEQilgQI4EUiAQaQQtCFAiNQjLVUWbsjII19iS2u931vI9zzseiqtxP21V9b7vrlr/f pp/33HPr/O53vvO4VYAgCIIgCIIgCIIgCILwLEBnUz6f8mmfZR7ZpKfyop+GQPvKHNX52mmLKjyS K8NGONAEhclUdMMOy2PsSdMcfr08+LK+JTKdNhurQ0GuD3/RGP58hfc1eSESeUUUMuSQPE0CLgPr ILRv0xp2EMd3CHf3DkiS+yJTQYTh0p4QjS1E0TJvYhGorzA2VjGQ6RoAHklEKECiIhuQDsiz3qC1 9jLF8R1KknNkzDYZs0vAi3CuJeKcEkrNM3ALnrfAnneOw3Cbo2iZN+t3GBtNHkSjh5FoagQ6Ik/j LlSSnKM4NirLbit3fo7Y9AhYgrP9vTjq+iJTTkhVHoqgdIWB+yCvyupeh31/xUWR58Jwm5sX4Abd 21ZhEhUo0NUD8nS7UGkKlS1C2QRq3kA5F5KrJ8Q2IaAOdqnIUxCkAgbaIB2yaoesVMItD06HcP4O XBDA1WpwA4lGkegqYwoEok++ygE/3/+CJv6MVrjIwEIB5QpFw9xyUG9ZuL+Jt+6+tnH1vUneKJRX IPrEN/nFiHrfJqBhHJBYwLqcpQqnglZAqAFPAc7xG0miX3n9s9GtPCOzkwpEAPDJVznQy73NxKBx p8PoZjJBWAbmAsJyjeArfqP747sf2rj6V+mhIf6IJzaomvDchIcJM4Of73+hm6Lx0wdO5CkRnZRx 84FDz9AHgpeWPj+Ycrk2zGOv0j5vnhhgJhHo0DzPNXJwn77VcnDiTulwDNxqOZDWn8LaosI6FLBK B2Vi4GHQOJ5xJxKPThKuN+h+D5esyFNaLAPbfXfp0m6owhgcre0MJx8xnDN68sTjBDPRI3lWCeug xl2oTsbVgq5FOCNaKeba7dCL43nX7d7hS17IYQxurjccNpo4JNERxunC6KA8Ww/neTJb4JUIZ0Jm gbSmvbj2rtefr+leb0l3u1CNu1BYb+zr0h52ZwcYMwcadVt78vT7HS29V/lhANVAedX+olfrWC+t 93SaDtr4oETH8ySB9mXkl7HWXqbRDLN97sKkIzhhGmGmNA79xE/8JHDeXFLVab2i03SwFLXWvj1Y FN+LQgcYX4L1LYrjO2TMLmWLUHNJXxd4GcIZ4uvMDzx4WdLxsijQc0lfZ4tQcfy2iuMVOrQF50A0 GiOJHnZf7UVKkpCcWyKbQJkalF/whQhng/XIQwYKgwBpljIZsE2gnFtwgy03Sxh2Y0dC0JgR6DLW sAZjlsier9G8gXLZxJOQwpTiZ/Csb7Q1mY5Mql0V6jkXkT3fIWOWht0YcNzO0oklcLZLzsVkLRSY ZTV9BnAM5RtPs4NyUUDOBeRcTM7ODdt37ZHHji3QKP9h0yPnIpKtGLMDe1BgQy4g5SwUc0rOxYfa 9/Kxx55gS+sSsNAHerUTVFWYRpg9YoDAIOaM2PEgSJgeGZNRHIOAxWOPHTMCjbLwF/edNJMINDNY YjYUPvx5/OBwghyoP9xRKMwWIdgZAioTBQcZSQkD2J4oKIhAQi5EICEXRT5Y+FR56ZzC5z7o46Mv eHi+RvBLditkDvhFl/Hddwy+/oMMb+2UcyN5KQX67Ad8fOXjIXSJU3lfAS/UCZff7+MPXvZx9TsJ /rqZnXW1JqZk9y3wykUPV36z3PIcRivgq78V4nd/tXz3c6kECjTw5Y+Fp/6eNGcBAfjKx8LSdcWl qu5HVjTeU5tFfQYszxE+vFKuXTKlEuilc6Wq7ol4uWTXWKra1vzZjT4j5kp2jaUSSJg+RCAhFyKQ kIvyTTw8ht2kHA8aLYSPznNePqfwexc97MSM/3nXIpnyZ+9mSqBf/8supv1RawJw80/mHvn3Vy55 eOXSoFluPnD4o3/o45329F6UdGFTzMXnFL78G+GT//EMEYGeMnrCV/xDy9M9sSgCPWUmXaqoTPm8 kAgk5EIEespM+mZcjqc3gQZmbBR2HH/+2xF+7fmzuU9++AuHL/1LfOB36YTD8p9sT/dGs5kX6MU6 4aXFsxFop58vemQO+Iv/TAuqzekw8wKVjTe3HW7sOPxfy+Hv3zR4UyKQMAn/eMPgtc3pjjr7kSRa yIUIJORCBBJyIQIJuRCBhFyIQEIuRCAhFyKQkAsRSMiFCCTkQgQSciECCbkQgYRciEBCLkQgIRci kJALEUjIhQgk5EIEEnIhAgm5EIGEXIhAQi5EICEXIpCQCxFIyIUIJORCBBJyIQIJuRCBhFyIQEIu RCAhFyKQkAsRSMjFzL9D2a02Y/GMPhH51hR/REFRzLxAh98lVSgW6cKEXIhAQi5EICEXIpCQCxFI yMVMjcL++49rZ12FZ46ZEuhxn0UqnA7ShQm5EIGEXIhAQi5KJVAnm/21pXZarmsslUA3pvyzs4rg rTNa+D0ppRLoe7ct3u2W6w6dhDsdxn/dnvAzMc+YUgmUWuBr/5FgFhViAF/9ToKsXAGoXAIBwLdv Glz99wS2ZC/047AO+LN/S/BPb5uzrsrElHIi8RtvZPjebYvPfdDHR1/w8Es1gi7ZrWAc8PMu47vv GHz9B1npcp8RpRQIAN7advjTf00AJGddlWeakt23wrQhAgm5EIGEXIhAwgDSJ5odmVggpStMOpzF qZhnnASkPAb6Ex01pkCrQ2FuTVgpoRxoJvJ4NJ4lGn9B7gRd2H1gd7KTCNMNkWGCZpBhIp9JBaxU zORV2fMWOIqWH9nWYwsURcvseQtMXnVQuApEoBmBDBzIskojRypjooCVig617/Vjjz1BDlRjpSLW Gg5EItEMoAgu87Ql1Xc6YadUykpFrHRn2L6bjz52vFNcxyY24XnnWN/rcsuDI5WIPDNC5sPozFql 4foKTvXgHqiY9b059rz7vFlfGbb1lSNtPoZAVxhoMOorHIbbrNQ86xDOj8Ny7TsQHok2bJSX2TRj 4/mB9Tw4HcIptcthuDSUpnFswBi/C9tY5UEe1GJ/B67loZyrf8IRUsMms37mh3MmSGE6YcX6O3BR 9D4XRbcZG6v75Tkg0pMEYmD0qMx1bNbvcK0GFwRwfrVjwdKLzQAchEHmJzBhqkwn7Fn94K4LArgw 3B52X6ME+uhjU2NGoGE3trHKzeg212pwldacBUsQKj3s0Df9LK5VsnYltkG7aiuVOVurwTUvwGGj OWj7Y/IfYDyBhlHoCgNbjM1PuOYFuFoNDnHaK/ZqhKcNx0k76FoTtPu21oWpVu/vk2d1nzwE4Ohm 0AmG8fsk2mhy8wKc6/duzOT+0mcFZnC//9N6/ZfN4iJMrQZ3YyHZF3m29stzLONuKGMANCiIGbgG bDTBH8+uc2t3jebnAZLHiksFM7jdBiW9v72x8IZDfWWYLDcBrDDQwCF5jg0Vk+xIPCKR+8ndV+n9 5/8QzjWoXge80m5wfLYwGbjdAZLkR+n/3nwNmztuIM0W9sQBniTPgf+YEBqV+ytf/OF7qVL5Fitq kO8D2gdkgno6YQJsBs4yENOP2HV//2df+7ufDf54RBrgMeKMyNvvEMB4z+98o1r5yIc/D8efBqmL rNR8znKFU4Act8C4odhe737/+6/9/J+/FQ+S5KsjUSa+8wsRCLhGAFSjAXXvXsdPa+e9auK81E/9 wKt4Jkt9Z632fa3ZQTFbAhQhAJidJE85IVKMFAAcE2kmBZdl1iqtrfbIZNZkfgoTV5FW491sYWHB DhJluMeNsMYhb9LCg+z5KoBVjqLXuVJZsejeQ78+xxUvQZakHEaBsybTzkJzpCmwRgEAsyfyFAE7 UGAYAFKtHSWW/TCz2oNN0tQGEZtQOWPb1nqV4faMjcVhznNyeYD8EWhYBgP4lMJ6gxp3oXZ353Wy 0NK1LrwsCrTJUh2ZVNuQlLNQQAVgQ8yZCFQQRP5AAspYaTidsIu9wHp+YP04td0aTLg7bxcWWnZv mP5Nl1egAodNDcbGKpprO3wpSZzbAXXqPTsXA2TAWTVw1kKx84k5I+aU5A3piqOLFEQBE1WZkLEL 4fwenGdT1wl7Ntipuqjqub3oU8wMXhEC7evGrgP1Bocx2Bg4tKvoLoJtAjWfQjkXk3MgnksIAAzq BZxeAIAQbQAJqANWKmZlI25Fg1X1YKe6b20LDLyOwXA9X/QBiunChuWMkukmjbqyJDlHcfy2cm6B 7PkaOdslNj1ytipd1ymhdG+wa1TXWN/rslK7HEXvc2G4zXvLEwdmmHMJVGAXdnCWurnecGvtkMJw gZPkPpkWyJjdoTjzcK4lEhWMUvMM7ALYheeBvep9DsMljqJwEHk2msOu6/HLE5NQlEDHLnVsYpWx fofQTmgNy4jjUa1bBZ1WOEgLwAKAwR72TdwG6hjmPK/j0MIoUEAeVHQUeDhDPZwbGtIk4PLw+y3C esFnFfbYAPYew7qOgzsJi5UHKF6gQ+WO6nhNuqszY/x1rZNw2g0r4kwPskApCIIgCIJQEP8P03/B 430mofMAAAAASUVORK5CYII= "
+       style="opacity:0.49074079;image-rendering:optimizeQuality"
+       preserveAspectRatio="none"
+       height="28.670942"
+       width="28.670942" />
+    <image
+       y="0.0055872202"
+       x="-0.031073861"
+       id="image3082"
+       xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJAAAACQCAYAAADnRuK4AAAABHNCSVQICAgIfAhkiAAAAAlwSFlz AABO4wAATuMBYdZxSQAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAAZdEVY dFRpdGxlAFR3ZWxmIEljb24gVGVtcGxhdGXyQOONAAAADHRFWHRBdXRob3IAeHBobnh/4OzMAAAA Y3RFWHRDb3B5cmlnaHQAQ0MgQXR0cmlidXRpb24tTm9uQ29tbWVyY2lhbC1TaGFyZUFsaWtlIGh0 dHA6Ly9jcmVhdGl2ZWNvbW1vbnMub3JnL2xpY2Vuc2VzL2J5LW5jLXNhLzQuMC9D0yJcAAAL6klE QVR4nO3dTYvcRhrA8adKJZW6Zc0LmTEmmYUF5zYOJjQLOSTQEMhlA7uHsNlPsCHg5DP4MzghHyC3 xZvTGh8CgYaQXMwEkngOZo1f2JAYz2ReLHePSqqqZy+t2Z5xj6c1pbZa7ecHxo09LdVEf0rVkjIG IIQQQgghhBBCCCEvA9bw7ZPJ4TQ2Oo0DPG6bFFJ9xoVTWUxVH1g25vXxfTAAgG63W/GuSa/XK14e DwTH/HklEVUZ0PF4ilBYkiQMACBN0yP7U0rRzFQRKeWRIMIwRACAOI6x1+uNBlRpRNMIiAEAK8JJ 05QppZjWmmmtGQCAtZbCmRLOOQIACCFQCIFSSgzDEEdCGo1oZgJ6Jp6trS2ulGJpmvI8z/nKysph QMaYw/1STO6KaAAAPM87DGh7ext937dhGFopJa6urtqqI6oyoCPx9Pt9nmUZX15e5koprrXm1lpm rWWIyOI4pngqxDnHJEmAMYacc+ScoxDCSint7u6uDYLARlFkx0RUe0Ds2rVrQRRFn3qe93fO+UVE XKxgu6R6TxDxP4j4z4cPH3529epVNfzzM0fkGhD78ssv/2CtvckYW7fWgtYaEKdyyYE4YoyBEAI4 54CIt5VSf/7oo4/+O/IlpQ/cWQNiAADXrl0LFhYWNowx60mSQJZlZ9wceZGCIIA4jsHzvNsPHjz4 09WrVzM44/UiXnLfhx/PAQCiKPo0z/P1nZ0diqdBsiyDnZ0dyPP80tra2hUYWcPC+Gt5JyoT0DPX eRhjH+7t7dEpq4EQEfb29oAx9rdOp8O73S6H8SEdj+oIMeH+xl0kZP1+/3WKp7kQEQ4ODl7f39/n aZpip9NBAICNjY1xH/MZjDmlTRpQ4chH9SzL2mcdPJkNSqlzSZKINE1tv99HIQSur68X14yKLztx lpjkFDb2ImG/3+fGGLfRk9oZYyCKIhFFkVhYWPAGg4HX7/f51tYW73a7405nR0y6BnomnoODA6+i 74HUTCklDg4OxNOnT0Ucx16WZXxMRGOddgo78sYkSVhxhXlpaansJzgygxCR+b7ve55nfd83aZqa OI4hSRIQQuDwRjjCCWugiSPodrssTVOmtWbD2xM0A80Jz/P8LMuEUkqEYegppbzl5WWepilP05QN Z6HCkUllkkU0AwCWJAlTSjFrLSvubVX6XZDaaK2F7/ssz3PgnKPWGpVS3FprRx65cZuBhjtiw7vq vN1uU0BzwvM8YYzxgiDwtNZeu93m1lpmjGFa68PnucBhEX3IGMOGG+eISHfT5wAiciGEZ609fGKi +HXaeycOqFj/aK0n3jhpBmstR0RmreVSyuI1O3/+/KnvpdMQgeGZhCEiK+Kx1h5OGMcfRR5V5jrQ uJ2SORAEAUNEFoZh6feeaQ1E8cynYllS5vjSKYwAwNnPKBQQcUIBESdlH+eYGa+++ip0Oh1YW1uD KIqAsWYty4wxkCQJ3L9/H27dugX7+/t1D+lMGhnQO++8A2+99Vbdw3DieR4sLS3Bm2++CW+88Qbc vHkT7ty5U/ewSmvcKazT6TQ+nuOEEPD+++/D2tpa3UMprVEBtVotePvtt+sexlRwzuHdd9+texil NSqgixcvQhAEdQ9jas6fPw8rKyt1D6OURgXUtP+4Z/HKK6/UPYRSGhWQ7/t1D2HqpJR1D6GURgVE Zg8FRJxQQMRJIy8kjoOIoJQ6/Qtrxhh77jrnwoULoJSCfr8Pv/76K1hrX+DoypubgAaDAXzxxRd1 D+NU586dg48//vjEv798+TJcvnwZAAAePXoE169fhzRNX9TwSqNT2Ay7cOHCzF84pYBesHa73I8T mPXbGxTQC1b2WtasX/uigIgTCugFK/upatZ//tLcfAo7yQcffAALCwu17PvevXuj//wAAAA8ffq0 1Da2trYqHFH15j6gpaUlWF5ermXf4w5+mRklyzL4/vvvqxxS5eY+oKZ59OgR7O3twc7ODty+fXvm H3WlgGbMjz/+CD/99FPdw5gYLaKJEwqIOKGAiBMKiDihgIgTCog4oYCIEwqIOKGAiBMKiDihgIgT Cog4oYCIEwqIOKGAiBMKiDihgIgTCog4oYCIEwqIOKGAiBMKiDihgIgTCog4oYCIEwqIOKGAiBMK iDihgIgTCog4oYCIEwqIOKGAiJO5/wllu7u7YIypZd9JktSy3xdp7gP66quv6h7CXKNTGHFCAREn FBBxQgERJxQQcTI3n8La7TZ88skndQ/jVIyxuodQqbkJiDEGYRjWPYyXDp3CiBMKiDihgIiTRgWU ZVndQ5g6pVTdQyilUQH9/vvvdQ9h6pr2PTYqoLt37871LPT48WPY3t6uexilNCqgNE3h22+/rXsY U2GthW+++abuYZTWqIAAAH744YeZ/3dEy8rzHG7cuAG//PJL3UMprZEXEr/77ju4d+8edDodWFtb g3PnzjXuCq8xBp48eQL379+HW7duwZMnT+oe0pk0MiAAgN9++w1u3LhR9zBeeo07hZHZQgERJxQQ cUIBEQAAYIzhWd5XOiDP8/CsOyOzjXNe+rhOGhAFM8eyLEPGGKZpCgDlZqMzn8JoFpofw2OJjDFk jCHnHDnnKIRAIQSGYXjisZ44oDAMDzdY7KCS0ZPacc7tMBybpulhRI8fPz79vWV35nkecs7R8zxL s9B8YIxZrbXhnFvP82wxQUwySZQKSAiB29vbKISw/X6f4pkTxhjteZ4ZxmMHg4EdThIohMA4jotj /cwxnyQgBACM4xillMg5Ryml9X2/np9YQConhNBZlpk8z7Xv+0YIYaWUdnisT4wHoMQM1Ov1sFgH 7e7uWiGErWT0pHZaay2lzKWUOssyLaU0u7u7NgxDG4Yh9nq90XiOhHTazVQEgMPb3HEcY5qmFgAg SRKDiI27C06egVLKfDAYWEQ0Ukq7t7dnW62WlVKOnr7GKnMdCItZKIoi22q1DCItg5oOEUFrnUdR lLdaLZMkiWm1WiaKIru6umqHs0/x6xmTroGK33FjY8Ourq7aKIpslmWDir4PUhOlVNLv93WSJKbf 7+t2u/28eJ6JqOzzQAgA0Ov1oNvt2sFgcFdK2aHTWDMhIgwGg3txHOtisRyGIW5sbIyGU9kpbPQ1 9no91FpfT5IE6FTWPIgISZKAMeZfi4uLdnV11W5ubpqNjQ0LABaejWfsQfZK7vfIVPPaa69tRFH0 V2PMed/3gXO6ud8EWmtIkgSyLPv5zp07/+j1evrBgwfPi+XEGeKs557D9125cuWPUsp/c87XhRAg RGOfkn0paK1Baw3W2p+VUn/5/PPPHw7/alwkp55aXBcvDADgvffea1+6dOkKAHzIGLvIOV9w3C6Z AmvtE0S8i4jXNzc3P/v6669TOGWRfJpKAhr+ztfX1/n29rYfRZFQSgnf9/08z4UQwjfGeL7ve9Za joiseO/wNXEwck+yuBFq8zw3nueZPM91EAR5lmW63W5naZrmi4uLZnNzc3StA3CGeADc/6+M0QuN GIYhtlot0+/3IY5jPDg4gCAIUGttpZSeMcYDABYEAQegeKpURJTnuUVElFKaLMvM8HcdhqEeXuMp Hs84dYE8iaoWLAgArLhSrbVmUkqmtTZSSuCco9YajTFWSskRERCRUUDVKQISQqBSyiKiDYLA+L5v ENG0Wi2TZZmNosg+7+Zo6f26bgCOnsZYp9Ph+/v7fDAYeHEce0opT2vN2+02N8Zway2jeKZj9IEw z/PsYDCwwxujJkkS0263zeLioh1+VHda+xSqmIHG3S9DrbVNkgSWl5dRKcWzLOPWWlYENPzaCnZP AP7/zyoUARljMAxDK6W0u7u7NgiCk+5tOc1CVc0CR2ahbrfLtra2uFKKpWnKrbXMGMNWVlaY1poZ Y2j2mZLiGZ7t7W0sHv4bhoST3p4oo+qAites2+2yJElYmqZMKcW01kxrffh11lqKqGKjTxAWjx9L KTEMQ4zjGE+4MToTAR3f1uHH9CIkAIA0TSmaF6R4EH4kHICK4wGoNqDj25vkNZmOkyKpNB6A6R3M 49ulaOpT6aL5uGkfWApndtAjE4QQQgghFfkfLnfEbGu/P8gAAAAASUVORK5CYII= "
+       style="display:inline;image-rendering:optimizeQuality"
+       preserveAspectRatio="none"
+       height="28.697369"
+       width="28.697369" />
+  </g>
+</svg>
diff --git a/app/src/main/assets/ic_device_galaxy_buds_pro.svg b/app/src/main/assets/ic_device_galaxy_buds_pro.svg
new file mode 100644
index 0000000..17cdc49
--- /dev/null
+++ b/app/src/main/assets/ic_device_galaxy_buds_pro.svg
@@ -0,0 +1,147 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   version="1.1"
+   id="svg2"
+   width="28.740126"
+   height="28.740126"
+   viewBox="0 0 28.740126 28.740126"
+   sodipodi:docname="ic_device_galaxy_buds_pro.svg"
+   inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:dc="http://purl.org/dc/elements/1.1/">
+  <metadata
+     id="metadata8">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs6">
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 14.370063 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="28.740126 : 14.370063 : 1"
+       inkscape:persp3d-origin="14.370063 : 9.580042 : 1"
+       id="perspective903" />
+  </defs>
+  <sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="1920"
+     inkscape:window-height="1035"
+     id="namedview4"
+     showgrid="false"
+     showguides="true"
+     inkscape:guide-bbox="true"
+     inkscape:zoom="16"
+     inkscape:cx="9.59375"
+     inkscape:cy="19.78125"
+     inkscape:window-x="0"
+     inkscape:window-y="45"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="layer1"
+     inkscape:pagecheckerboard="0">
+    <inkscape:grid
+       type="xygrid"
+       id="grid972" />
+  </sodipodi:namedview>
+  <g
+     inkscape:groupmode="layer"
+     id="layer1"
+     inkscape:label="Background"
+     style="display:inline">
+    <g
+       inkscape:groupmode="layer"
+       id="layer4"
+       inkscape:label="Bottom"
+       style="display:inline"
+       sodipodi:insensitive="true">
+      <rect
+         style="display:inline;fill:#1f7fdb;fill-opacity:1;stroke:none;stroke-width:3.57115;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         id="rect817-5"
+         width="22.819572"
+         height="21.904638"
+         x="2.9239759"
+         y="3.8768768"
+         ry="0.9472276" />
+    </g>
+    <g
+       inkscape:groupmode="layer"
+       id="layer3"
+       inkscape:label="Middle"
+       style="display:inline"
+       sodipodi:insensitive="true">
+      <rect
+         style="display:inline;fill:#4dabf5;fill-opacity:1;stroke:none;stroke-width:3.57115;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         id="rect817-7"
+         width="22.819572"
+         height="21.904638"
+         x="2.9317887"
+         y="3.0349438"
+         ry="0.9472276" />
+    </g>
+    <g
+       inkscape:groupmode="layer"
+       id="layer2"
+       inkscape:label="Top"
+       style="display:inline"
+       sodipodi:insensitive="true">
+      <rect
+         style="fill:#2196f3;fill-opacity:1;stroke:none;stroke-width:3.57115;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         id="rect817"
+         width="22.819572"
+         height="21.904638"
+         x="2.9238882"
+         y="3.4130721"
+         ry="0.9472276" />
+    </g>
+    <g
+       id="g63739"
+       transform="translate(0.05413571,0.02254964)">
+      <path
+         style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0.499999;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         d="m 21.237726,23.178927 c 0.634206,-0.154792 1.014861,-0.392033 1.434432,-0.894023 0.709355,-0.848648 0.851246,-2.128284 0.35017,-3.158004 -0.158194,-0.325102 -0.376956,-0.640059 -0.486135,-0.699886 -0.184713,-0.101225 -0.186831,-0.142449 -0.0305,-0.593252 0.4121,-1.188704 0.245104,-3.013982 -0.384023,-4.197709 -0.384785,-0.723994 -1.220769,-1.489466 -1.876589,-1.718287 -1.305362,-0.455487 -3.136032,0.05098 -4.050919,1.120434 -1.417995,1.657746 -1.788681,3.854161 -0.956476,5.667197 0.279331,0.608544 1.422559,1.793423 2.254089,2.336206 0.402046,0.262429 0.660398,0.531288 0.79333,0.825545 0.434706,0.962313 1.83523,1.584515 2.952669,1.311779 z m -1.557124,-0.393681 c -0.417663,-0.178671 -0.962525,-0.642359 -1.13348,-0.964615 -0.08464,-0.159567 -0.02985,-0.160978 0.556108,-0.01438 1.152123,0.288237 1.967488,0.0109 2.699481,-0.921499 0.305122,-0.388252 0.34598,-0.533223 0.397766,-1.411541 l 0.05768,-0.978857 0.300204,0.366331 c 0.962332,1.174379 0.537663,3.170699 -0.820959,3.859379 -0.572392,0.290149 -1.464875,0.318438 -2.056846,0.0651 z M 18.664017,21.38604 c -1.145368,-0.445668 -2.891616,-2.002277 -3.271619,-2.916302 -0.916517,-2.204544 0.139572,-5.156228 2.189977,-6.12081 0.943576,-0.443884 2.09371,-0.462014 2.890806,-0.04544 1.698228,0.887254 2.464953,3.418948 1.713805,5.658899 -0.113052,0.337133 -0.206657,0.923837 -0.207992,1.30381 -0.0056,1.475953 -0.786045,2.332595 -2.127581,2.334067 -0.357171,4.12e-4 -0.877106,-0.09332 -1.187396,-0.214086 z m 0.05143,-2.660283 c 0.09413,-0.1471 0.141331,-0.346993 0.104904,-0.444199 -0.03648,-0.09727 -0.46741,-0.342356 -0.95774,-0.544765 -0.99398,-0.410316 -1.250633,-0.37172 -1.250633,0.188105 0,0.29544 0.0735,0.356398 0.830335,0.688221 1.011691,0.443601 1.058716,0.44776 1.273129,0.112618 z"
+         id="path4913" />
+      <path
+         style="fill:#ffffff;fill-opacity:1;stroke:#fffffd;stroke-width:0.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         d="m 7.8095555,17.66233 c -0.643309,-0.154792 -1.029417,-0.392034 -1.455018,-0.894023 -0.719533,-0.848647 -0.863456,-2.128283 -0.35519,-3.158003 0.16047,-0.325108 0.382369,-0.640058 0.493111,-0.699893 0.187359,-0.101222 0.189511,-0.142441 0.03099,-0.593244 -0.418003,-1.188704 -0.24862,-3.0139926 0.389538,-4.1977137 0.390307,-0.7239821 1.238294,-1.4894593 1.903507,-1.7182926 1.3240955,-0.4554866 3.1810385,0.05091 4.1090525,1.1204365 1.438337,1.6577511 1.814337,3.8541708 0.970193,5.6672038 -0.283344,0.608542 -1.442967,1.793425 -2.286421,2.336214 -0.407815,0.262422 -0.669885,0.531277 -0.804719,0.825539 -0.440944,0.962315 -1.8615685,1.584512 -2.9950365,1.311776 z m 1.579457,-0.393675 c 0.423658,-0.178678 0.9763395,-0.642346 1.1497485,-0.964615 0.08584,-0.159571 0.03028,-0.160985 -0.5640875,-0.01437 -1.168658,0.288237 -1.995714,0.01078 -2.738213,-0.921502 -0.309496,-0.388249 -0.350936,-0.533218 -0.403474,-1.411549 l -0.05856,-0.978857 -0.30451,0.366339 c -0.976134,1.174378 -0.545387,3.170701 0.832735,3.859381 0.580601,0.290142 1.48589,0.318436 2.086356,0.06516 z m 1.0311705,-1.39921 c 1.1618,-0.445664 2.933103,-2.002277 3.318564,-2.916305 C 14.668411,10.748599 13.59717,7.7969088 11.517344,6.832342 10.560231,6.3884511 9.3935935,6.370315 8.5850695,6.786822 c -1.722603,0.8872548 -2.500328,3.418952 -1.73839,5.658903 0.114677,0.337147 0.209614,0.923838 0.210971,1.303822 0.0052,1.475947 0.79732,2.332583 2.158099,2.334069 0.362301,3.8e-4 0.8896925,-0.09332 1.2044335,-0.214099 z m -0.05212,-2.660279 c -0.09548,-0.147102 -0.14336,-0.346994 -0.106402,-0.444203 0.03699,-0.09727 0.47412,-0.342357 0.971491,-0.544765 1.008241,-0.410314 1.268566,-0.371706 1.268566,0.188105 0,0.295436 -0.07455,0.356397 -0.842244,0.688225 -1.026201,0.443591 -1.073911,0.44776 -1.291402,0.112602 z"
+         id="path4913-6" />
+    </g>
+  </g>
+  <g
+     inkscape:groupmode="layer"
+     id="layer5"
+     inkscape:label="Original"
+     style="display:none"
+     sodipodi:insensitive="true">
+    <image
+       y="0.0079936879"
+       x="0.010951031"
+       id="image4124"
+       xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGAAAABgCAMAAADVRocKAAAAA3NCSVQICAjb4U/gAAAACXBIWXMA ADFOAAAxTgGacD5yAAAAGXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAABF0RVh0 VGl0bGUAUGViYmxlIEljb26KvwD9AAAADHRFWHRBdXRob3IAeHBobnh/4OzMAAAAY3RFWHRDb3B5 cmlnaHQAQ0MgQXR0cmlidXRpb24tTm9uQ29tbWVyY2lhbC1TaGFyZUFsaWtlIGh0dHA6Ly9jcmVh dGl2ZWNvbW1vbnMub3JnL2xpY2Vuc2VzL2J5LW5jLXNhLzMuMC/eBBrlAAABy1BMVEUAAAAAAAAA AP8AAIAAAFUAAKoAVaoAAIAAQIAAM2YAM5kzM2YzM5krK4AkJG0kJJIgIIAcHHEcHI4aGoAXF3QX F4sXLnQXLosVK4AUJ3YUJ4kSJIAkJIARInciInciIogQIIAgIIAeHngeHoccHIAcK4AbKHkbKIYa GoAaJoAYJHkXI4AXI4sWIXoWIYUVIIAfH3ofH4UdJ4ATHIQcJnsbJIAaI4QaIoAZIXsZIYQYIHgY IIAXH4MXJ3wXHoAXJoBLpfAWJHwdJHwdJIMcI4Acas0VIoMbIoAaIXwaIIAZH3wZH4MZJXwYHoAY HoYYJIAehtsYHoIefdtHoO0jetZGovNEn+4gg+EfgN5Ep/VKqfREpvQgfdkgfttKqvQggd4hjesw nPMwnPRJqfUhgd4hjutJqfUgfttNq/VNq/QhlPEhlPIhlvMkmPMglvIff9sgjeoglPAhk/AhlPEh lPIhlvMkl/MkmPMlmPMmmPMnmfMpmvMzn/Q0n/Q1n/Q2oPRIqfVNq/VasfZbsvZhtPZitfdtuveQ yvmXzvmj0/qk1Pqu2fu83/vR6f3T6v3Z7f3i8f3j8v3l8/7m8/7p9f7q9f7x+P7y+f7////ougeB AAAAb3RSTlMAAQECAwMDBAQFBQUFBgcHCAkJCgsLCwsMDQ0ODg8PDxAQERESEhMTFBQVFhYXFxgZ GRobGxwdHh8fICAhISIiIiMjIyQkJSYnKCkpKSoqKiorKyssLC2xs7O0tba2tu7u7u7u7+/w8fHy +/v7+/ywJhX9AAAC0klEQVRo3u2a+VfTQBCAm00iW0nSTVNKbKQYbIMUC6jU4q2IB2rF+74vPEAR LSjIIXgAAiqef65py6MNJe17y1RF8/26L/N1N9O8ZGZcLof/DiYFoiV1cZHwiGVZjh7zalRAYYbn sFsQ6RFwGccyBeJjwSMrPn8lHX6fQqRy3tbAsFiU1So9FA4bNIRD63XNTwTO5pSYc9fuvwbgwaW9 SxqYs/eGBgcAGBy+fRrlpRPDsJdfJoEYuMgvzibz/vK3kmDcFMuxNZvM/BG74QTdFV6PJZsYlhe9 XXCCrlCVStwcyhG45QCkoCmiV0o4uwXEib4aQMHjbc0RTXYvCMx/mGdNHaSgraWxRhEXzsgUEC0C KWjfszlUYRWsrYcUHGttNlSRzxUEo5CCjrZYrSqVVlD3Nwj6vqcZsovzKrPeTy3o/5lmxE4wnFl/ 4QhKLvjy2YY5IEExHMH0B1umQQSj9o+EUUewIBh/m8c4qOBbfu58dQRLCmYnJmZLKphKJicdwR8X vJsymSmhIM37FSv4OJlhxnlUOAJHAC8Ye5PHmPNe9PsEJX/5dT5AigvmPtkw53wnr3xB34809uWc zDp9OWflV7xgBaBlzY79MWORALhuui+vbhoArfwe3rUp5MtWfl2Il1QDUtAaj+rKakv1XdEhBfGm Wo1kq+/pM1Ih+wdRI6iIq3IFvEAewgke6QHFg1mU26PjhbtwghsKEbG1X8ew3PmnUPGfXRAwv7hr yqAD13t6nwPQ23PnBMeivG4gg85c7XwCQOeVU+avZ5bslcrrNm7dfehIInGSguOJo+0Hd27ZoOam j3ULvOjTI02x+PYddLTEY42GJpfnpo/L0g50k8pqI1IfbWikoSFaXxfSFKnMrqFsZpKbKKoWDFbT EdQCqlfCdhtIG7AoESLTQogk4EJd/VRTn8fLgecKTg3MjyUsA4SKDD7MT1bQ8++NmvwCRmydh2Fg UvEAAAAASUVORK5CYII= "
+       style="opacity:0.49074079;image-rendering:optimizeQuality"
+       preserveAspectRatio="none"
+       height="28.710279"
+       width="28.710279" />
+  </g>
+</svg>
diff --git a/app/src/main/assets/ic_device_galaxy_buds_pro_disabled.svg b/app/src/main/assets/ic_device_galaxy_buds_pro_disabled.svg
new file mode 100644
index 0000000..4f40a69
--- /dev/null
+++ b/app/src/main/assets/ic_device_galaxy_buds_pro_disabled.svg
@@ -0,0 +1,200 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   version="1.1"
+   id="svg2"
+   width="28.740126"
+   height="28.740126"
+   viewBox="0 0 28.740126 28.740126"
+   sodipodi:docname="ic_device_galaxy_buds_pro_disabled.svg"
+   inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:android="http://schemas.android.com/apk/res/android">
+  <metadata
+     id="metadata8">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs6">
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 14.370063 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="28.740126 : 14.370063 : 1"
+       inkscape:persp3d-origin="14.370063 : 9.580042 : 1"
+       id="perspective903" />
+  </defs>
+  <sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="1920"
+     inkscape:window-height="1035"
+     id="namedview4"
+     showgrid="false"
+     showguides="true"
+     inkscape:guide-bbox="true"
+     inkscape:zoom="16"
+     inkscape:cx="7.34375"
+     inkscape:cy="7.65625"
+     inkscape:window-x="0"
+     inkscape:window-y="45"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="layer6"
+     inkscape:pagecheckerboard="0">
+    <inkscape:grid
+       type="xygrid"
+       id="grid972" />
+  </sodipodi:namedview>
+  <g
+     inkscape:groupmode="layer"
+     id="layer1"
+     inkscape:label="Background"
+     style="display:inline">
+    <g
+       inkscape:groupmode="layer"
+       id="layer4"
+       inkscape:label="Bottom"
+       style="display:inline"
+       sodipodi:insensitive="true">
+      <rect
+         style="display:inline;fill:#7a7a7a;fill-opacity:1;stroke:none;stroke-width:3.57115173;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         id="rect817-5"
+         width="22.819572"
+         height="21.904638"
+         x="2.9239759"
+         y="3.8768768"
+         ry="0.9472276" />
+    </g>
+    <g
+       inkscape:groupmode="layer"
+       id="layer3"
+       inkscape:label="Middle"
+       style="display:inline"
+       sodipodi:insensitive="true">
+      <rect
+         style="display:inline;fill:#9f9f9f;fill-opacity:0.94117647;stroke:none;stroke-width:3.57115173;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         id="rect817-7"
+         width="22.819572"
+         height="21.904638"
+         x="2.9317887"
+         y="3.0349438"
+         ry="0.9472276" />
+    </g>
+    <g
+       inkscape:groupmode="layer"
+       id="layer2"
+       inkscape:label="Top"
+       style="display:inline"
+       sodipodi:insensitive="true">
+      <rect
+         style="fill:#8a8a8a;fill-opacity:1;stroke:none;stroke-width:3.57115173;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         id="rect817"
+         width="22.819572"
+         height="21.904638"
+         x="2.9238882"
+         y="3.4130721"
+         ry="0.9472276" />
+    </g>
+  </g>
+  <g
+     inkscape:groupmode="layer"
+     id="layer6"
+     inkscape:label="Foreground"
+     style="display:inline">
+    <g
+       inkscape:groupmode="layer"
+       id="layer7"
+       inkscape:label="Backplane"
+       style="display:inline" />
+    <g
+       inkscape:groupmode="layer"
+       id="layer8"
+       inkscape:label="Frontplane"
+       style="display:inline">
+      <g
+         inkscape:groupmode="layer"
+         id="layer9"
+         inkscape:label="Base"
+         style="display:inline" />
+      <g
+         inkscape:groupmode="layer"
+         id="layer10"
+         inkscape:label="Top"
+         style="display:inline" />
+      <g
+         inkscape:groupmode="layer"
+         id="layer11"
+         inkscape:label="Toptop"
+         style="display:inline" />
+      <g
+         inkscape:groupmode="layer"
+         id="layer12"
+         inkscape:label="Toptoptop"
+         style="display:inline" />
+    </g>
+    <path
+       inkscape:connector-curvature="0"
+       style="fill:#8a8a8a;fill-opacity:0.94117647"
+       d="m 12.48275,10.865376 h 3.907 v 6.978 h -3.907 z"
+       android:strokeWidth="1"
+       id="path2735" />
+    <g
+       id="g63739"
+       transform="translate(0.05413571,0.02254964)"
+       style="display:inline">
+      <path
+         style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0.499999;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         d="m 21.237726,23.178927 c 0.634206,-0.154792 1.014861,-0.392033 1.434432,-0.894023 0.709355,-0.848648 0.851246,-2.128284 0.35017,-3.158004 -0.158194,-0.325102 -0.376956,-0.640059 -0.486135,-0.699886 -0.184713,-0.101225 -0.186831,-0.142449 -0.0305,-0.593252 0.4121,-1.188704 0.245104,-3.013982 -0.384023,-4.197709 -0.384785,-0.723994 -1.220769,-1.489466 -1.876589,-1.718287 -1.305362,-0.455487 -3.136032,0.05098 -4.050919,1.120434 -1.417995,1.657746 -1.788681,3.854161 -0.956476,5.667197 0.279331,0.608544 1.422559,1.793423 2.254089,2.336206 0.402046,0.262429 0.660398,0.531288 0.79333,0.825545 0.434706,0.962313 1.83523,1.584515 2.952669,1.311779 z m -1.557124,-0.393681 c -0.417663,-0.178671 -0.962525,-0.642359 -1.13348,-0.964615 -0.08464,-0.159567 -0.02985,-0.160978 0.556108,-0.01438 1.152123,0.288237 1.967488,0.0109 2.699481,-0.921499 0.305122,-0.388252 0.34598,-0.533223 0.397766,-1.411541 l 0.05768,-0.978857 0.300204,0.366331 c 0.962332,1.174379 0.537663,3.170699 -0.820959,3.859379 -0.572392,0.290149 -1.464875,0.318438 -2.056846,0.0651 z M 18.664017,21.38604 c -1.145368,-0.445668 -2.891616,-2.002277 -3.271619,-2.916302 -0.916517,-2.204544 0.139572,-5.156228 2.189977,-6.12081 0.943576,-0.443884 2.09371,-0.462014 2.890806,-0.04544 1.698228,0.887254 2.464953,3.418948 1.713805,5.658899 -0.113052,0.337133 -0.206657,0.923837 -0.207992,1.30381 -0.0056,1.475953 -0.786045,2.332595 -2.127581,2.334067 -0.357171,4.12e-4 -0.877106,-0.09332 -1.187396,-0.214086 z m 0.05143,-2.660283 c 0.09413,-0.1471 0.141331,-0.346993 0.104904,-0.444199 -0.03648,-0.09727 -0.46741,-0.342356 -0.95774,-0.544765 -0.99398,-0.410316 -1.250633,-0.37172 -1.250633,0.188105 0,0.29544 0.0735,0.356398 0.830335,0.688221 1.011691,0.443601 1.058716,0.44776 1.273129,0.112618 z"
+         id="path4913" />
+      <path
+         style="fill:#ffffff;fill-opacity:1;stroke:#fffffd;stroke-width:0.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         d="m 7.8095555,17.66233 c -0.643309,-0.154792 -1.029417,-0.392034 -1.455018,-0.894023 -0.719533,-0.848647 -0.863456,-2.128283 -0.35519,-3.158003 0.16047,-0.325108 0.382369,-0.640058 0.493111,-0.699893 0.187359,-0.101222 0.189511,-0.142441 0.03099,-0.593244 -0.418003,-1.188704 -0.24862,-3.0139926 0.389538,-4.1977137 0.390307,-0.7239821 1.238294,-1.4894593 1.903507,-1.7182926 1.3240955,-0.4554866 3.1810385,0.05091 4.1090525,1.1204365 1.438337,1.6577511 1.814337,3.8541708 0.970193,5.6672038 -0.283344,0.608542 -1.442967,1.793425 -2.286421,2.336214 -0.407815,0.262422 -0.669885,0.531277 -0.804719,0.825539 -0.440944,0.962315 -1.8615685,1.584512 -2.9950365,1.311776 z m 1.579457,-0.393675 c 0.423658,-0.178678 0.9763395,-0.642346 1.1497485,-0.964615 0.08584,-0.159571 0.03028,-0.160985 -0.5640875,-0.01437 -1.168658,0.288237 -1.995714,0.01078 -2.738213,-0.921502 -0.309496,-0.388249 -0.350936,-0.533218 -0.403474,-1.411549 l -0.05856,-0.978857 -0.30451,0.366339 c -0.976134,1.174378 -0.545387,3.170701 0.832735,3.859381 0.580601,0.290142 1.48589,0.318436 2.086356,0.06516 z m 1.0311705,-1.39921 c 1.1618,-0.445664 2.933103,-2.002277 3.318564,-2.916305 C 14.668411,10.748599 13.59717,7.7969088 11.517344,6.832342 10.560231,6.3884511 9.3935935,6.370315 8.5850695,6.786822 c -1.722603,0.8872548 -2.500328,3.418952 -1.73839,5.658903 0.114677,0.337147 0.209614,0.923838 0.210971,1.303822 0.0052,1.475947 0.79732,2.332583 2.158099,2.334069 0.362301,3.8e-4 0.8896925,-0.09332 1.2044335,-0.214099 z m -0.05212,-2.660279 c -0.09548,-0.147102 -0.14336,-0.346994 -0.106402,-0.444203 0.03699,-0.09727 0.47412,-0.342357 0.971491,-0.544765 1.008241,-0.410314 1.268566,-0.371706 1.268566,0.188105 0,0.295436 -0.07455,0.356397 -0.842244,0.688225 -1.026201,0.443591 -1.073911,0.44776 -1.291402,0.112602 z"
+         id="path4913-6" />
+    </g>
+  </g>
+  <g
+     inkscape:groupmode="layer"
+     id="layer5"
+     inkscape:label="Original"
+     style="display:none">
+    <image
+       y="0.028144173"
+       x="0.013794855"
+       id="image1291"
+       xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJAAAACQCAYAAADnRuK4AAAABHNCSVQICAgIfAhkiAAAAAlwSFlz AABO4wAATuMBYdZxSQAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAAZdEVY dFRpdGxlAFR3ZWxmIEljb24gVGVtcGxhdGXyQOONAAAADHRFWHRBdXRob3IAeHBobnh/4OzMAAAA Y3RFWHRDb3B5cmlnaHQAQ0MgQXR0cmlidXRpb24tTm9uQ29tbWVyY2lhbC1TaGFyZUFsaWtlIGh0 dHA6Ly9jcmVhdGl2ZWNvbW1vbnMub3JnL2xpY2Vuc2VzL2J5LW5jLXNhLzQuMC9D0yJcAAANW0lE QVR4nO2dS4xc2VnH/98591VVXd3TbntC92ggwZ5R1BWRRZMoIQG1UIQIs0BCcpINESyyQIqySlgl 2I6yQ2xmtpEQilgQI4EUiAQaQQtCFAiNQjLVUWbsjII19iS2u931vI9zzseiqtxP21V9b7vrlr/f pp/33HPr/O53vvO4VYAgCIIgCIIgCIIgCILwLEBnUz6f8mmfZR7ZpKfyop+GQPvKHNX52mmLKjyS K8NGONAEhclUdMMOy2PsSdMcfr08+LK+JTKdNhurQ0GuD3/RGP58hfc1eSESeUUUMuSQPE0CLgPr ILRv0xp2EMd3CHf3DkiS+yJTQYTh0p4QjS1E0TJvYhGorzA2VjGQ6RoAHklEKECiIhuQDsiz3qC1 9jLF8R1KknNkzDYZs0vAi3CuJeKcEkrNM3ALnrfAnneOw3Cbo2iZN+t3GBtNHkSjh5FoagQ6Ik/j LlSSnKM4NirLbit3fo7Y9AhYgrP9vTjq+iJTTkhVHoqgdIWB+yCvyupeh31/xUWR58Jwm5sX4Abd 21ZhEhUo0NUD8nS7UGkKlS1C2QRq3kA5F5KrJ8Q2IaAOdqnIUxCkAgbaIB2yaoesVMItD06HcP4O XBDA1WpwA4lGkegqYwoEok++ygE/3/+CJv6MVrjIwEIB5QpFw9xyUG9ZuL+Jt+6+tnH1vUneKJRX IPrEN/nFiHrfJqBhHJBYwLqcpQqnglZAqAFPAc7xG0miX3n9s9GtPCOzkwpEAPDJVznQy73NxKBx p8PoZjJBWAbmAsJyjeArfqP747sf2rj6V+mhIf6IJzaomvDchIcJM4Of73+hm6Lx0wdO5CkRnZRx 84FDz9AHgpeWPj+Ycrk2zGOv0j5vnhhgJhHo0DzPNXJwn77VcnDiTulwDNxqOZDWn8LaosI6FLBK B2Vi4GHQOJ5xJxKPThKuN+h+D5esyFNaLAPbfXfp0m6owhgcre0MJx8xnDN68sTjBDPRI3lWCeug xl2oTsbVgq5FOCNaKeba7dCL43nX7d7hS17IYQxurjccNpo4JNERxunC6KA8Ww/neTJb4JUIZ0Jm gbSmvbj2rtefr+leb0l3u1CNu1BYb+zr0h52ZwcYMwcadVt78vT7HS29V/lhANVAedX+olfrWC+t 93SaDtr4oETH8ySB9mXkl7HWXqbRDLN97sKkIzhhGmGmNA79xE/8JHDeXFLVab2i03SwFLXWvj1Y FN+LQgcYX4L1LYrjO2TMLmWLUHNJXxd4GcIZ4uvMDzx4WdLxsijQc0lfZ4tQcfy2iuMVOrQF50A0 GiOJHnZf7UVKkpCcWyKbQJkalF/whQhng/XIQwYKgwBpljIZsE2gnFtwgy03Sxh2Y0dC0JgR6DLW sAZjlsier9G8gXLZxJOQwpTiZ/Csb7Q1mY5Mql0V6jkXkT3fIWOWht0YcNzO0oklcLZLzsVkLRSY ZTV9BnAM5RtPs4NyUUDOBeRcTM7ODdt37ZHHji3QKP9h0yPnIpKtGLMDe1BgQy4g5SwUc0rOxYfa 9/Kxx55gS+sSsNAHerUTVFWYRpg9YoDAIOaM2PEgSJgeGZNRHIOAxWOPHTMCjbLwF/edNJMINDNY YjYUPvx5/OBwghyoP9xRKMwWIdgZAioTBQcZSQkD2J4oKIhAQi5EICEXRT5Y+FR56ZzC5z7o46Mv eHi+RvBLditkDvhFl/Hddwy+/oMMb+2UcyN5KQX67Ad8fOXjIXSJU3lfAS/UCZff7+MPXvZx9TsJ /rqZnXW1JqZk9y3wykUPV36z3PIcRivgq78V4nd/tXz3c6kECjTw5Y+Fp/6eNGcBAfjKx8LSdcWl qu5HVjTeU5tFfQYszxE+vFKuXTKlEuilc6Wq7ol4uWTXWKra1vzZjT4j5kp2jaUSSJg+RCAhFyKQ kIvyTTw8ht2kHA8aLYSPznNePqfwexc97MSM/3nXIpnyZ+9mSqBf/8supv1RawJw80/mHvn3Vy55 eOXSoFluPnD4o3/o45329F6UdGFTzMXnFL78G+GT//EMEYGeMnrCV/xDy9M9sSgCPWUmXaqoTPm8 kAgk5EIEespM+mZcjqc3gQZmbBR2HH/+2xF+7fmzuU9++AuHL/1LfOB36YTD8p9sT/dGs5kX6MU6 4aXFsxFop58vemQO+Iv/TAuqzekw8wKVjTe3HW7sOPxfy+Hv3zR4UyKQMAn/eMPgtc3pjjr7kSRa yIUIJORCBBJyIQIJuRCBhFyIQEIuRCAhFyKQkAsRSMiFCCTkQgQSciECCbkQgYRciEBCLkQgIRci kJALEUjIhQgk5EIEEnIhAgm5EIGEXIhAQi5EICEXIpCQCxFIyIUIJORCBBJyIQIJuRCBhFyIQEIu RCAhFyKQkAsRSMjFzL9D2a02Y/GMPhH51hR/REFRzLxAh98lVSgW6cKEXIhAQi5EICEXIpCQCxFI yMVMjcL++49rZ12FZ46ZEuhxn0UqnA7ShQm5EIGEXIhAQi5KJVAnm/21pXZarmsslUA3pvyzs4rg rTNa+D0ppRLoe7ct3u2W6w6dhDsdxn/dnvAzMc+YUgmUWuBr/5FgFhViAF/9ToKsXAGoXAIBwLdv Glz99wS2ZC/047AO+LN/S/BPb5uzrsrElHIi8RtvZPjebYvPfdDHR1/w8Es1gi7ZrWAc8PMu47vv GHz9B1npcp8RpRQIAN7advjTf00AJGddlWeakt23wrQhAgm5EIGEXIhAwgDSJ5odmVggpStMOpzF qZhnnASkPAb6Ex01pkCrQ2FuTVgpoRxoJvJ4NJ4lGn9B7gRd2H1gd7KTCNMNkWGCZpBhIp9JBaxU zORV2fMWOIqWH9nWYwsURcvseQtMXnVQuApEoBmBDBzIskojRypjooCVig617/Vjjz1BDlRjpSLW Gg5EItEMoAgu87Ql1Xc6YadUykpFrHRn2L6bjz52vFNcxyY24XnnWN/rcsuDI5WIPDNC5sPozFql 4foKTvXgHqiY9b059rz7vFlfGbb1lSNtPoZAVxhoMOorHIbbrNQ86xDOj8Ny7TsQHok2bJSX2TRj 4/mB9Tw4HcIptcthuDSUpnFswBi/C9tY5UEe1GJ/B67loZyrf8IRUsMms37mh3MmSGE6YcX6O3BR 9D4XRbcZG6v75Tkg0pMEYmD0qMx1bNbvcK0GFwRwfrVjwdKLzQAchEHmJzBhqkwn7Fn94K4LArgw 3B52X6ME+uhjU2NGoGE3trHKzeg212pwldacBUsQKj3s0Df9LK5VsnYltkG7aiuVOVurwTUvwGGj OWj7Y/IfYDyBhlHoCgNbjM1PuOYFuFoNDnHaK/ZqhKcNx0k76FoTtPu21oWpVu/vk2d1nzwE4Ohm 0AmG8fsk2mhy8wKc6/duzOT+0mcFZnC//9N6/ZfN4iJMrQZ3YyHZF3m29stzLONuKGMANCiIGbgG bDTBH8+uc2t3jebnAZLHiksFM7jdBiW9v72x8IZDfWWYLDcBrDDQwCF5jg0Vk+xIPCKR+8ndV+n9 5/8QzjWoXge80m5wfLYwGbjdAZLkR+n/3nwNmztuIM0W9sQBniTPgf+YEBqV+ytf/OF7qVL5Fitq kO8D2gdkgno6YQJsBs4yENOP2HV//2df+7ufDf54RBrgMeKMyNvvEMB4z+98o1r5yIc/D8efBqmL rNR8znKFU4Act8C4odhe737/+6/9/J+/FQ+S5KsjUSa+8wsRCLhGAFSjAXXvXsdPa+e9auK81E/9 wKt4Jkt9Z632fa3ZQTFbAhQhAJidJE85IVKMFAAcE2kmBZdl1iqtrfbIZNZkfgoTV5FW491sYWHB DhJluMeNsMYhb9LCg+z5KoBVjqLXuVJZsejeQ78+xxUvQZakHEaBsybTzkJzpCmwRgEAsyfyFAE7 UGAYAFKtHSWW/TCz2oNN0tQGEZtQOWPb1nqV4faMjcVhznNyeYD8EWhYBgP4lMJ6gxp3oXZ353Wy 0NK1LrwsCrTJUh2ZVNuQlLNQQAVgQ8yZCFQQRP5AAspYaTidsIu9wHp+YP04td0aTLg7bxcWWnZv mP5Nl1egAodNDcbGKpprO3wpSZzbAXXqPTsXA2TAWTVw1kKx84k5I+aU5A3piqOLFEQBE1WZkLEL 4fwenGdT1wl7Ntipuqjqub3oU8wMXhEC7evGrgP1Bocx2Bg4tKvoLoJtAjWfQjkXk3MgnksIAAzq BZxeAIAQbQAJqANWKmZlI25Fg1X1YKe6b20LDLyOwXA9X/QBiunChuWMkukmjbqyJDlHcfy2cm6B 7PkaOdslNj1ytipd1ymhdG+wa1TXWN/rslK7HEXvc2G4zXvLEwdmmHMJVGAXdnCWurnecGvtkMJw gZPkPpkWyJjdoTjzcK4lEhWMUvMM7ALYheeBvep9DsMljqJwEHk2msOu6/HLE5NQlEDHLnVsYpWx fofQTmgNy4jjUa1bBZ1WOEgLwAKAwR72TdwG6hjmPK/j0MIoUEAeVHQUeDhDPZwbGtIk4PLw+y3C esFnFfbYAPYew7qOgzsJi5UHKF6gQ+WO6nhNuqszY/x1rZNw2g0r4kwPskApCIIgCIJQEP8P03/B 430mofMAAAAASUVORK5CYII= "
+       style="opacity:0.49074079;image-rendering:optimizeQuality"
+       preserveAspectRatio="none"
+       height="28.670942"
+       width="28.670942" />
+    <image
+       y="0.0055872202"
+       x="-0.031073861"
+       id="image3082"
+       xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJAAAACQCAYAAADnRuK4AAAABHNCSVQICAgIfAhkiAAAAAlwSFlz AABO4wAATuMBYdZxSQAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAAZdEVY dFRpdGxlAFR3ZWxmIEljb24gVGVtcGxhdGXyQOONAAAADHRFWHRBdXRob3IAeHBobnh/4OzMAAAA Y3RFWHRDb3B5cmlnaHQAQ0MgQXR0cmlidXRpb24tTm9uQ29tbWVyY2lhbC1TaGFyZUFsaWtlIGh0 dHA6Ly9jcmVhdGl2ZWNvbW1vbnMub3JnL2xpY2Vuc2VzL2J5LW5jLXNhLzQuMC9D0yJcAAAL6klE QVR4nO3dTYvcRhrA8adKJZW6Zc0LmTEmmYUF5zYOJjQLOSTQEMhlA7uHsNlPsCHg5DP4MzghHyC3 xZvTGh8CgYaQXMwEkngOZo1f2JAYz2ReLHePSqqqZy+t2Z5xj6c1pbZa7ecHxo09LdVEf0rVkjIG IIQQQgghhBBCCCEvA9bw7ZPJ4TQ2Oo0DPG6bFFJ9xoVTWUxVH1g25vXxfTAAgG63W/GuSa/XK14e DwTH/HklEVUZ0PF4ilBYkiQMACBN0yP7U0rRzFQRKeWRIMIwRACAOI6x1+uNBlRpRNMIiAEAK8JJ 05QppZjWmmmtGQCAtZbCmRLOOQIACCFQCIFSSgzDEEdCGo1oZgJ6Jp6trS2ulGJpmvI8z/nKysph QMaYw/1STO6KaAAAPM87DGh7ext937dhGFopJa6urtqqI6oyoCPx9Pt9nmUZX15e5koprrXm1lpm rWWIyOI4pngqxDnHJEmAMYacc+ScoxDCSint7u6uDYLARlFkx0RUe0Ds2rVrQRRFn3qe93fO+UVE XKxgu6R6TxDxP4j4z4cPH3529epVNfzzM0fkGhD78ssv/2CtvckYW7fWgtYaEKdyyYE4YoyBEAI4 54CIt5VSf/7oo4/+O/IlpQ/cWQNiAADXrl0LFhYWNowx60mSQJZlZ9wceZGCIIA4jsHzvNsPHjz4 09WrVzM44/UiXnLfhx/PAQCiKPo0z/P1nZ0diqdBsiyDnZ0dyPP80tra2hUYWcPC+Gt5JyoT0DPX eRhjH+7t7dEpq4EQEfb29oAx9rdOp8O73S6H8SEdj+oIMeH+xl0kZP1+/3WKp7kQEQ4ODl7f39/n aZpip9NBAICNjY1xH/MZjDmlTRpQ4chH9SzL2mcdPJkNSqlzSZKINE1tv99HIQSur68X14yKLztx lpjkFDb2ImG/3+fGGLfRk9oZYyCKIhFFkVhYWPAGg4HX7/f51tYW73a7405nR0y6BnomnoODA6+i 74HUTCklDg4OxNOnT0Ucx16WZXxMRGOddgo78sYkSVhxhXlpaansJzgygxCR+b7ve55nfd83aZqa OI4hSRIQQuDwRjjCCWugiSPodrssTVOmtWbD2xM0A80Jz/P8LMuEUkqEYegppbzl5WWepilP05QN Z6HCkUllkkU0AwCWJAlTSjFrLSvubVX6XZDaaK2F7/ssz3PgnKPWGpVS3FprRx65cZuBhjtiw7vq vN1uU0BzwvM8YYzxgiDwtNZeu93m1lpmjGFa68PnucBhEX3IGMOGG+eISHfT5wAiciGEZ609fGKi +HXaeycOqFj/aK0n3jhpBmstR0RmreVSyuI1O3/+/KnvpdMQgeGZhCEiK+Kx1h5OGMcfRR5V5jrQ uJ2SORAEAUNEFoZh6feeaQ1E8cynYllS5vjSKYwAwNnPKBQQcUIBESdlH+eYGa+++ip0Oh1YW1uD KIqAsWYty4wxkCQJ3L9/H27dugX7+/t1D+lMGhnQO++8A2+99Vbdw3DieR4sLS3Bm2++CW+88Qbc vHkT7ty5U/ewSmvcKazT6TQ+nuOEEPD+++/D2tpa3UMprVEBtVotePvtt+sexlRwzuHdd9+texil NSqgixcvQhAEdQ9jas6fPw8rKyt1D6OURgXUtP+4Z/HKK6/UPYRSGhWQ7/t1D2HqpJR1D6GURgVE Zg8FRJxQQMRJIy8kjoOIoJQ6/Qtrxhh77jrnwoULoJSCfr8Pv/76K1hrX+DoypubgAaDAXzxxRd1 D+NU586dg48//vjEv798+TJcvnwZAAAePXoE169fhzRNX9TwSqNT2Ay7cOHCzF84pYBesHa73I8T mPXbGxTQC1b2WtasX/uigIgTCugFK/upatZ//tLcfAo7yQcffAALCwu17PvevXuj//wAAAA8ffq0 1Da2trYqHFH15j6gpaUlWF5ermXf4w5+mRklyzL4/vvvqxxS5eY+oKZ59OgR7O3twc7ODty+fXvm H3WlgGbMjz/+CD/99FPdw5gYLaKJEwqIOKGAiBMKiDihgIgTCog4oYCIEwqIOKGAiBMKiDihgIgT Cog4oYCIEwqIOKGAiBMKiDihgIgTCog4oYCIEwqIOKGAiBMKiDihgIgTCog4oYCIEwqIOKGAiBMK iDihgIgTCog4oYCIEwqIOKGAiJO5/wllu7u7YIypZd9JktSy3xdp7gP66quv6h7CXKNTGHFCAREn FBBxQgERJxQQcTI3n8La7TZ88skndQ/jVIyxuodQqbkJiDEGYRjWPYyXDp3CiBMKiDihgIiTRgWU ZVndQ5g6pVTdQyilUQH9/vvvdQ9h6pr2PTYqoLt37871LPT48WPY3t6uexilNCqgNE3h22+/rXsY U2GthW+++abuYZTWqIAAAH744YeZ/3dEy8rzHG7cuAG//PJL3UMprZEXEr/77ju4d+8edDodWFtb g3PnzjXuCq8xBp48eQL379+HW7duwZMnT+oe0pk0MiAAgN9++w1u3LhR9zBeeo07hZHZQgERJxQQ cUIBEQAAYIzhWd5XOiDP8/CsOyOzjXNe+rhOGhAFM8eyLEPGGKZpCgDlZqMzn8JoFpofw2OJjDFk jCHnHDnnKIRAIQSGYXjisZ44oDAMDzdY7KCS0ZPacc7tMBybpulhRI8fPz79vWV35nkecs7R8zxL s9B8YIxZrbXhnFvP82wxQUwySZQKSAiB29vbKISw/X6f4pkTxhjteZ4ZxmMHg4EdThIohMA4jotj /cwxnyQgBACM4xillMg5Ryml9X2/np9YQConhNBZlpk8z7Xv+0YIYaWUdnisT4wHoMQM1Ov1sFgH 7e7uWiGErWT0pHZaay2lzKWUOssyLaU0u7u7NgxDG4Yh9nq90XiOhHTazVQEgMPb3HEcY5qmFgAg SRKDiI27C06egVLKfDAYWEQ0Ukq7t7dnW62WlVKOnr7GKnMdCItZKIoi22q1DCItg5oOEUFrnUdR lLdaLZMkiWm1WiaKIru6umqHs0/x6xmTroGK33FjY8Ourq7aKIpslmWDir4PUhOlVNLv93WSJKbf 7+t2u/28eJ6JqOzzQAgA0Ov1oNvt2sFgcFdK2aHTWDMhIgwGg3txHOtisRyGIW5sbIyGU9kpbPQ1 9no91FpfT5IE6FTWPIgISZKAMeZfi4uLdnV11W5ubpqNjQ0LABaejWfsQfZK7vfIVPPaa69tRFH0 V2PMed/3gXO6ud8EWmtIkgSyLPv5zp07/+j1evrBgwfPi+XEGeKs557D9125cuWPUsp/c87XhRAg RGOfkn0paK1Baw3W2p+VUn/5/PPPHw7/alwkp55aXBcvDADgvffea1+6dOkKAHzIGLvIOV9w3C6Z AmvtE0S8i4jXNzc3P/v6669TOGWRfJpKAhr+ztfX1/n29rYfRZFQSgnf9/08z4UQwjfGeL7ve9Za joiseO/wNXEwck+yuBFq8zw3nueZPM91EAR5lmW63W5naZrmi4uLZnNzc3StA3CGeADc/6+M0QuN GIYhtlot0+/3IY5jPDg4gCAIUGttpZSeMcYDABYEAQegeKpURJTnuUVElFKaLMvM8HcdhqEeXuMp Hs84dYE8iaoWLAgArLhSrbVmUkqmtTZSSuCco9YajTFWSskRERCRUUDVKQISQqBSyiKiDYLA+L5v ENG0Wi2TZZmNosg+7+Zo6f26bgCOnsZYp9Ph+/v7fDAYeHEce0opT2vN2+02N8Zway2jeKZj9IEw z/PsYDCwwxujJkkS0263zeLioh1+VHda+xSqmIHG3S9DrbVNkgSWl5dRKcWzLOPWWlYENPzaCnZP AP7/zyoUARljMAxDK6W0u7u7NgiCk+5tOc1CVc0CR2ahbrfLtra2uFKKpWnKrbXMGMNWVlaY1poZ Y2j2mZLiGZ7t7W0sHv4bhoST3p4oo+qAites2+2yJElYmqZMKcW01kxrffh11lqKqGKjTxAWjx9L KTEMQ4zjGE+4MToTAR3f1uHH9CIkAIA0TSmaF6R4EH4kHICK4wGoNqDj25vkNZmOkyKpNB6A6R3M 49ulaOpT6aL5uGkfWApndtAjE4QQQgghFfkfLnfEbGu/P8gAAAAASUVORK5CYII= "
+       style="display:inline;image-rendering:optimizeQuality"
+       preserveAspectRatio="none"
+       height="28.697369"
+       width="28.697369" />
+  </g>
+</svg>
diff --git a/app/src/main/assets/ic_device_miband6.svg b/app/src/main/assets/ic_device_miband6.svg
new file mode 100644
index 0000000..c5622af
--- /dev/null
+++ b/app/src/main/assets/ic_device_miband6.svg
@@ -0,0 +1,150 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   version="1.1"
+   id="svg2"
+   width="28.740126"
+   height="28.740126"
+   viewBox="0 0 28.740126 28.740126"
+   sodipodi:docname="ic_device_miband6.svg"
+   inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:dc="http://purl.org/dc/elements/1.1/">
+  <metadata
+     id="metadata8">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs6">
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 14.370063 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="28.740126 : 14.370063 : 1"
+       inkscape:persp3d-origin="14.370063 : 9.580042 : 1"
+       id="perspective903" />
+  </defs>
+  <sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="1920"
+     inkscape:window-height="1035"
+     id="namedview4"
+     showgrid="false"
+     showguides="true"
+     inkscape:guide-bbox="true"
+     inkscape:zoom="16.000001"
+     inkscape:cx="7.5312495"
+     inkscape:cy="18.906249"
+     inkscape:window-x="0"
+     inkscape:window-y="45"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="layer1"
+     inkscape:pagecheckerboard="0">
+    <inkscape:grid
+       type="xygrid"
+       id="grid972" />
+  </sodipodi:namedview>
+  <g
+     inkscape:groupmode="layer"
+     id="layer1"
+     inkscape:label="Background"
+     style="display:inline">
+    <g
+       inkscape:groupmode="layer"
+       id="layer4"
+       inkscape:label="Bottom"
+       style="display:inline"
+       sodipodi:insensitive="true">
+      <rect
+         style="display:inline;fill:#1f7fdb;fill-opacity:1;stroke:none;stroke-width:3.57115;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         id="rect817-5"
+         width="22.819572"
+         height="21.904638"
+         x="2.9239759"
+         y="3.8768768"
+         ry="0.9472276" />
+    </g>
+    <g
+       inkscape:groupmode="layer"
+       id="layer3"
+       inkscape:label="Middle"
+       style="display:inline"
+       sodipodi:insensitive="true">
+      <rect
+         style="display:inline;fill:#4dabf5;fill-opacity:1;stroke:none;stroke-width:3.57115;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         id="rect817-7"
+         width="22.819572"
+         height="21.904638"
+         x="2.9317887"
+         y="3.0349438"
+         ry="0.9472276" />
+    </g>
+    <g
+       inkscape:groupmode="layer"
+       id="layer2"
+       inkscape:label="Top"
+       style="display:inline"
+       sodipodi:insensitive="true">
+      <rect
+         style="fill:#2196f3;fill-opacity:1;stroke:none;stroke-width:3.57115;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         id="rect817"
+         width="22.819572"
+         height="21.904638"
+         x="2.9238882"
+         y="3.4130721"
+         ry="0.9472276" />
+    </g>
+    <g
+       id="g3290"
+       transform="translate(0.06106299,-0.04193669)">
+      <path
+         style="fill:#ffffff;fill-opacity:1;stroke-width:3.77953"
+         d="m 5.7005371,14.062732 c 0.00345,-0.03232 0.013618,-0.134324 0.023098,-0.226674 0.052057,-0.502923 0.2664409,-1.351244 0.3915769,-1.549467 0.024477,-0.03878 0.076621,-0.09101 0.1157675,-0.116009 0.1381934,-0.0882 0.4683357,-0.160533 1.0160246,-0.222623 0.1544916,-0.0175 0.3033982,-0.04042 0.3308835,-0.05094 0.069898,-0.02672 0.2694919,-0.0674 0.330556,-0.0674 0.029045,0 0.1528109,-0.05016 0.2885308,-0.116957 0.4663707,-0.229536 0.9722843,-0.357145 1.597637,-0.40298 0.3197226,-0.02344 8.7265726,-0.02361 9.0500356,-1.89e-4 0.652071,0.04723 1.177696,0.187449 1.687436,0.450227 l 0.243463,0.125507 0.310621,0.0306 c 0.596954,0.05887 1.04326,0.137305 1.216256,0.213823 0.0981,0.04344 0.189561,0.12854 0.235198,0.21896 0.08601,0.170462 0.226243,0.687839 0.303984,1.121803 0.153044,0.854198 0.104262,1.629242 -0.16305,2.590391 -0.08412,0.302605 -0.139822,0.434653 -0.218589,0.518609 -0.138246,0.147338 -0.522565,0.238473 -1.348615,0.319808 l -0.335805,0.0331 -0.277042,0.14009 c -0.496631,0.251134 -0.965794,0.377985 -1.611884,0.435808 -0.35012,0.03129 -8.733467,0.03189 -9.1175547,5.69e-4 -0.6601552,-0.05378 -1.2013714,-0.200007 -1.6801359,-0.453951 -0.098125,-0.05206 -0.1498116,-0.06826 -0.2442131,-0.07662 -0.065847,-0.006 -0.1678593,-0.02499 -0.2266738,-0.04275 -0.05878,-0.01767 -0.2655963,-0.0518 -0.4595188,-0.07585 C 6.3912287,16.764537 6.1913765,16.692028 6.0776776,16.4675 6.0255336,16.364497 5.8912448,15.904677 5.8314304,15.624247 5.8166924,15.554947 5.7978174,15.468092 5.7896294,15.431152 5.7145594,15.094537 5.6685873,14.38872 5.7005374,14.06274 Z"
+         id="path1062" />
+      <path
+         id="path_3"
+         d="m 9.388,13.204 h 9.841 c 0.29,0 0.568,0.115 0.774,0.32 0.205,0.206 0.32,0.484 0.32,0.774 v 0.228 c 0,0.29 -0.115,0.568 -0.32,0.774 -0.206,0.205 -0.484,0.32 -0.774,0.32 H 9.39 C 9.198,15.62 9.009,15.57 8.843,15.474 8.676,15.378 8.538,15.24 8.442,15.073 8.346,14.907 8.295,14.718 8.295,14.526 v -0.228 c 0,-0.29 0.115,-0.568 0.32,-0.774 0.206,-0.205 0.484,-0.32 0.774,-0.32 z"
+         fill="#ffffff"
+         stroke="#ffffff"
+         stroke-width="2.22514"
+         style="fill:#4dabf5;fill-opacity:1;stroke:#4dabf5;stroke-opacity:1" />
+    </g>
+  </g>
+  <g
+     inkscape:groupmode="layer"
+     id="layer5"
+     inkscape:label="Original"
+     style="display:none"
+     sodipodi:insensitive="true">
+    <image
+       y="0.0079936879"
+       x="0.010951031"
+       id="image4124"
+       xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGAAAABgCAMAAADVRocKAAAAA3NCSVQICAjb4U/gAAAACXBIWXMA ADFOAAAxTgGacD5yAAAAGXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAABF0RVh0 VGl0bGUAUGViYmxlIEljb26KvwD9AAAADHRFWHRBdXRob3IAeHBobnh/4OzMAAAAY3RFWHRDb3B5 cmlnaHQAQ0MgQXR0cmlidXRpb24tTm9uQ29tbWVyY2lhbC1TaGFyZUFsaWtlIGh0dHA6Ly9jcmVh dGl2ZWNvbW1vbnMub3JnL2xpY2Vuc2VzL2J5LW5jLXNhLzMuMC/eBBrlAAABy1BMVEUAAAAAAAAA AP8AAIAAAFUAAKoAVaoAAIAAQIAAM2YAM5kzM2YzM5krK4AkJG0kJJIgIIAcHHEcHI4aGoAXF3QX F4sXLnQXLosVK4AUJ3YUJ4kSJIAkJIARInciInciIogQIIAgIIAeHngeHoccHIAcK4AbKHkbKIYa GoAaJoAYJHkXI4AXI4sWIXoWIYUVIIAfH3ofH4UdJ4ATHIQcJnsbJIAaI4QaIoAZIXsZIYQYIHgY IIAXH4MXJ3wXHoAXJoBLpfAWJHwdJHwdJIMcI4Acas0VIoMbIoAaIXwaIIAZH3wZH4MZJXwYHoAY HoYYJIAehtsYHoIefdtHoO0jetZGovNEn+4gg+EfgN5Ep/VKqfREpvQgfdkgfttKqvQggd4hjesw nPMwnPRJqfUhgd4hjutJqfUgfttNq/VNq/QhlPEhlPIhlvMkmPMglvIff9sgjeoglPAhk/AhlPEh lPIhlvMkl/MkmPMlmPMmmPMnmfMpmvMzn/Q0n/Q1n/Q2oPRIqfVNq/VasfZbsvZhtPZitfdtuveQ yvmXzvmj0/qk1Pqu2fu83/vR6f3T6v3Z7f3i8f3j8v3l8/7m8/7p9f7q9f7x+P7y+f7////ougeB AAAAb3RSTlMAAQECAwMDBAQFBQUFBgcHCAkJCgsLCwsMDQ0ODg8PDxAQERESEhMTFBQVFhYXFxgZ GRobGxwdHh8fICAhISIiIiMjIyQkJSYnKCkpKSoqKiorKyssLC2xs7O0tba2tu7u7u7u7+/w8fHy +/v7+/ywJhX9AAAC0klEQVRo3u2a+VfTQBCAm00iW0nSTVNKbKQYbIMUC6jU4q2IB2rF+74vPEAR LSjIIXgAAiqef65py6MNJe17y1RF8/26L/N1N9O8ZGZcLof/DiYFoiV1cZHwiGVZjh7zalRAYYbn sFsQ6RFwGccyBeJjwSMrPn8lHX6fQqRy3tbAsFiU1So9FA4bNIRD63XNTwTO5pSYc9fuvwbgwaW9 SxqYs/eGBgcAGBy+fRrlpRPDsJdfJoEYuMgvzibz/vK3kmDcFMuxNZvM/BG74QTdFV6PJZsYlhe9 XXCCrlCVStwcyhG45QCkoCmiV0o4uwXEib4aQMHjbc0RTXYvCMx/mGdNHaSgraWxRhEXzsgUEC0C KWjfszlUYRWsrYcUHGttNlSRzxUEo5CCjrZYrSqVVlD3Nwj6vqcZsovzKrPeTy3o/5lmxE4wnFl/ 4QhKLvjy2YY5IEExHMH0B1umQQSj9o+EUUewIBh/m8c4qOBbfu58dQRLCmYnJmZLKphKJicdwR8X vJsymSmhIM37FSv4OJlhxnlUOAJHAC8Ye5PHmPNe9PsEJX/5dT5AigvmPtkw53wnr3xB34809uWc zDp9OWflV7xgBaBlzY79MWORALhuui+vbhoArfwe3rUp5MtWfl2Il1QDUtAaj+rKakv1XdEhBfGm Wo1kq+/pM1Ih+wdRI6iIq3IFvEAewgke6QHFg1mU26PjhbtwghsKEbG1X8ew3PmnUPGfXRAwv7hr yqAD13t6nwPQ23PnBMeivG4gg85c7XwCQOeVU+avZ5bslcrrNm7dfehIInGSguOJo+0Hd27ZoOam j3ULvOjTI02x+PYddLTEY42GJpfnpo/L0g50k8pqI1IfbWikoSFaXxfSFKnMrqFsZpKbKKoWDFbT EdQCqlfCdhtIG7AoESLTQogk4EJd/VRTn8fLgecKTg3MjyUsA4SKDD7MT1bQ8++NmvwCRmydh2Fg UvEAAAAASUVORK5CYII= "
+       style="opacity:0.49074079;image-rendering:optimizeQuality"
+       preserveAspectRatio="none"
+       height="28.710279"
+       width="28.710279" />
+  </g>
+</svg>
diff --git a/app/src/main/assets/ic_device_miband6_disabled.svg b/app/src/main/assets/ic_device_miband6_disabled.svg
new file mode 100644
index 0000000..3796c8d
--- /dev/null
+++ b/app/src/main/assets/ic_device_miband6_disabled.svg
@@ -0,0 +1,198 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   version="1.1"
+   id="svg2"
+   width="28.740126"
+   height="28.740126"
+   viewBox="0 0 28.740126 28.740126"
+   sodipodi:docname="ic_device_miband6_disabled.svg"
+   inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:android="http://schemas.android.com/apk/res/android">
+  <metadata
+     id="metadata8">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs6">
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 14.370063 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="28.740126 : 14.370063 : 1"
+       inkscape:persp3d-origin="14.370063 : 9.580042 : 1"
+       id="perspective903" />
+  </defs>
+  <sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="1920"
+     inkscape:window-height="1035"
+     id="namedview4"
+     showgrid="false"
+     showguides="true"
+     inkscape:guide-bbox="true"
+     inkscape:zoom="16"
+     inkscape:cx="7.34375"
+     inkscape:cy="7.65625"
+     inkscape:window-x="0"
+     inkscape:window-y="45"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="layer6"
+     inkscape:pagecheckerboard="0">
+    <inkscape:grid
+       type="xygrid"
+       id="grid972" />
+  </sodipodi:namedview>
+  <g
+     inkscape:groupmode="layer"
+     id="layer1"
+     inkscape:label="Background"
+     style="display:inline">
+    <g
+       inkscape:groupmode="layer"
+       id="layer4"
+       inkscape:label="Bottom"
+       style="display:inline"
+       sodipodi:insensitive="true">
+      <rect
+         style="display:inline;fill:#7a7a7a;fill-opacity:1;stroke:none;stroke-width:3.57115173;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         id="rect817-5"
+         width="22.819572"
+         height="21.904638"
+         x="2.9239759"
+         y="3.8768768"
+         ry="0.9472276" />
+    </g>
+    <g
+       inkscape:groupmode="layer"
+       id="layer3"
+       inkscape:label="Middle"
+       style="display:inline"
+       sodipodi:insensitive="true">
+      <rect
+         style="display:inline;fill:#9f9f9f;fill-opacity:0.94117647;stroke:none;stroke-width:3.57115173;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         id="rect817-7"
+         width="22.819572"
+         height="21.904638"
+         x="2.9317887"
+         y="3.0349438"
+         ry="0.9472276" />
+    </g>
+    <g
+       inkscape:groupmode="layer"
+       id="layer2"
+       inkscape:label="Top"
+       style="display:inline"
+       sodipodi:insensitive="true">
+      <rect
+         style="fill:#8a8a8a;fill-opacity:1;stroke:none;stroke-width:3.57115173;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         id="rect817"
+         width="22.819572"
+         height="21.904638"
+         x="2.9238882"
+         y="3.4130721"
+         ry="0.9472276" />
+    </g>
+  </g>
+  <g
+     inkscape:groupmode="layer"
+     id="layer6"
+     inkscape:label="Foreground"
+     style="display:inline">
+    <g
+       inkscape:groupmode="layer"
+       id="layer7"
+       inkscape:label="Backplane"
+       style="display:inline" />
+    <g
+       inkscape:groupmode="layer"
+       id="layer8"
+       inkscape:label="Frontplane"
+       style="display:inline">
+      <g
+         inkscape:groupmode="layer"
+         id="layer9"
+         inkscape:label="Base"
+         style="display:inline" />
+      <g
+         inkscape:groupmode="layer"
+         id="layer10"
+         inkscape:label="Top"
+         style="display:inline" />
+      <g
+         inkscape:groupmode="layer"
+         id="layer11"
+         inkscape:label="Toptop"
+         style="display:inline" />
+      <g
+         inkscape:groupmode="layer"
+         id="layer12"
+         inkscape:label="Toptoptop"
+         style="display:inline" />
+    </g>
+    <path
+       inkscape:connector-curvature="0"
+       style="fill:#8a8a8a;fill-opacity:0.94117647"
+       d="m 12.48275,10.865376 h 3.907 v 6.978 h -3.907 z"
+       android:strokeWidth="1"
+       id="path2735" />
+    <path
+       style="fill:#ffffff;fill-opacity:1;stroke-width:3.77953"
+       d="m 5.7616001,14.020795 c 0.00345,-0.03232 0.013618,-0.134324 0.023098,-0.226674 0.052057,-0.502923 0.2664409,-1.351244 0.3915769,-1.549467 0.024477,-0.03878 0.076621,-0.09101 0.1157675,-0.116009 0.1381934,-0.0882 0.4683357,-0.160533 1.0160246,-0.222623 0.1544916,-0.0175 0.3033982,-0.04042 0.3308835,-0.05094 0.069898,-0.02672 0.2694919,-0.0674 0.330556,-0.0674 0.029045,0 0.1528109,-0.05016 0.2885308,-0.116957 0.4663707,-0.229536 0.9722843,-0.357145 1.597637,-0.40298 0.3197226,-0.02344 8.7265726,-0.02361 9.0500356,-1.89e-4 0.652071,0.04723 1.177696,0.187449 1.687436,0.450227 l 0.243463,0.125507 0.310621,0.0306 c 0.596954,0.05887 1.04326,0.137305 1.216256,0.213823 0.0981,0.04344 0.189561,0.12854 0.235198,0.21896 0.08601,0.170462 0.226243,0.687839 0.303984,1.121803 0.153044,0.854198 0.104262,1.629242 -0.16305,2.590391 -0.08412,0.302605 -0.139822,0.434653 -0.218589,0.518609 -0.138246,0.147338 -0.522565,0.238473 -1.348615,0.319808 l -0.335805,0.0331 -0.277042,0.14009 c -0.496631,0.251134 -0.965794,0.377985 -1.611884,0.435808 -0.35012,0.03129 -8.733467,0.03189 -9.1175547,5.69e-4 C 9.1699731,17.413071 8.6287569,17.266844 8.1499924,17.0129 8.0518674,16.96084 8.0001808,16.94464 7.9057793,16.93628 7.8399323,16.93028 7.73792,16.91129 7.6791055,16.89353 7.6203255,16.87586 7.4135092,16.84173 7.2195867,16.81768 6.4522917,16.7226 6.2524395,16.650091 6.1387406,16.425563 6.0865966,16.32256 5.9523078,15.86274 5.8924934,15.58231 5.8777554,15.51301 5.8588804,15.426155 5.8506924,15.389215 5.7756224,15.0526 5.7296503,14.346783 5.7616004,14.020803 Z"
+       id="path1062" />
+    <path
+       id="path_3"
+       d="m 9.449063,13.162063 h 9.841 c 0.29,0 0.568,0.115 0.774,0.32 0.205,0.206 0.32,0.484 0.32,0.774 v 0.228 c 0,0.29 -0.115,0.568 -0.32,0.774 -0.206,0.205 -0.484,0.32 -0.774,0.32 h -9.839 c -0.192,0 -0.381,-0.05 -0.547,-0.146 -0.167,-0.096 -0.305,-0.234 -0.401,-0.401 -0.096,-0.166 -0.147,-0.355 -0.147,-0.547 v -0.228 c 0,-0.29 0.115,-0.568 0.32,-0.774 0.206,-0.205 0.484,-0.32 0.774,-0.32 z"
+       fill="#ffffff"
+       stroke="#ffffff"
+       stroke-width="2.22514"
+       style="fill:#9f9f9f;fill-opacity:1;stroke:#9f9f9f;stroke-opacity:1" />
+  </g>
+  <g
+     inkscape:groupmode="layer"
+     id="layer5"
+     inkscape:label="Original"
+     style="display:none">
+    <image
+       y="0.028144173"
+       x="0.013794855"
+       id="image1291"
+       xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJAAAACQCAYAAADnRuK4AAAABHNCSVQICAgIfAhkiAAAAAlwSFlz AABO4wAATuMBYdZxSQAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAAZdEVY dFRpdGxlAFR3ZWxmIEljb24gVGVtcGxhdGXyQOONAAAADHRFWHRBdXRob3IAeHBobnh/4OzMAAAA Y3RFWHRDb3B5cmlnaHQAQ0MgQXR0cmlidXRpb24tTm9uQ29tbWVyY2lhbC1TaGFyZUFsaWtlIGh0 dHA6Ly9jcmVhdGl2ZWNvbW1vbnMub3JnL2xpY2Vuc2VzL2J5LW5jLXNhLzQuMC9D0yJcAAANW0lE QVR4nO2dS4xc2VnH/98591VVXd3TbntC92ggwZ5R1BWRRZMoIQG1UIQIs0BCcpINESyyQIqySlgl 2I6yQ2xmtpEQilgQI4EUiAQaQQtCFAiNQjLVUWbsjII19iS2u931vI9zzseiqtxP21V9b7vrlr/f pp/33HPr/O53vvO4VYAgCIIgCIIgCIIgCILwLEBnUz6f8mmfZR7ZpKfyop+GQPvKHNX52mmLKjyS K8NGONAEhclUdMMOy2PsSdMcfr08+LK+JTKdNhurQ0GuD3/RGP58hfc1eSESeUUUMuSQPE0CLgPr ILRv0xp2EMd3CHf3DkiS+yJTQYTh0p4QjS1E0TJvYhGorzA2VjGQ6RoAHklEKECiIhuQDsiz3qC1 9jLF8R1KknNkzDYZs0vAi3CuJeKcEkrNM3ALnrfAnneOw3Cbo2iZN+t3GBtNHkSjh5FoagQ6Ik/j LlSSnKM4NirLbit3fo7Y9AhYgrP9vTjq+iJTTkhVHoqgdIWB+yCvyupeh31/xUWR58Jwm5sX4Abd 21ZhEhUo0NUD8nS7UGkKlS1C2QRq3kA5F5KrJ8Q2IaAOdqnIUxCkAgbaIB2yaoesVMItD06HcP4O XBDA1WpwA4lGkegqYwoEok++ygE/3/+CJv6MVrjIwEIB5QpFw9xyUG9ZuL+Jt+6+tnH1vUneKJRX IPrEN/nFiHrfJqBhHJBYwLqcpQqnglZAqAFPAc7xG0miX3n9s9GtPCOzkwpEAPDJVznQy73NxKBx p8PoZjJBWAbmAsJyjeArfqP747sf2rj6V+mhIf6IJzaomvDchIcJM4Of73+hm6Lx0wdO5CkRnZRx 84FDz9AHgpeWPj+Ycrk2zGOv0j5vnhhgJhHo0DzPNXJwn77VcnDiTulwDNxqOZDWn8LaosI6FLBK B2Vi4GHQOJ5xJxKPThKuN+h+D5esyFNaLAPbfXfp0m6owhgcre0MJx8xnDN68sTjBDPRI3lWCeug xl2oTsbVgq5FOCNaKeba7dCL43nX7d7hS17IYQxurjccNpo4JNERxunC6KA8Ww/neTJb4JUIZ0Jm gbSmvbj2rtefr+leb0l3u1CNu1BYb+zr0h52ZwcYMwcadVt78vT7HS29V/lhANVAedX+olfrWC+t 93SaDtr4oETH8ySB9mXkl7HWXqbRDLN97sKkIzhhGmGmNA79xE/8JHDeXFLVab2i03SwFLXWvj1Y FN+LQgcYX4L1LYrjO2TMLmWLUHNJXxd4GcIZ4uvMDzx4WdLxsijQc0lfZ4tQcfy2iuMVOrQF50A0 GiOJHnZf7UVKkpCcWyKbQJkalF/whQhng/XIQwYKgwBpljIZsE2gnFtwgy03Sxh2Y0dC0JgR6DLW sAZjlsier9G8gXLZxJOQwpTiZ/Csb7Q1mY5Mql0V6jkXkT3fIWOWht0YcNzO0oklcLZLzsVkLRSY ZTV9BnAM5RtPs4NyUUDOBeRcTM7ODdt37ZHHji3QKP9h0yPnIpKtGLMDe1BgQy4g5SwUc0rOxYfa 9/Kxx55gS+sSsNAHerUTVFWYRpg9YoDAIOaM2PEgSJgeGZNRHIOAxWOPHTMCjbLwF/edNJMINDNY YjYUPvx5/OBwghyoP9xRKMwWIdgZAioTBQcZSQkD2J4oKIhAQi5EICEXRT5Y+FR56ZzC5z7o46Mv eHi+RvBLditkDvhFl/Hddwy+/oMMb+2UcyN5KQX67Ad8fOXjIXSJU3lfAS/UCZff7+MPXvZx9TsJ /rqZnXW1JqZk9y3wykUPV36z3PIcRivgq78V4nd/tXz3c6kECjTw5Y+Fp/6eNGcBAfjKx8LSdcWl qu5HVjTeU5tFfQYszxE+vFKuXTKlEuilc6Wq7ol4uWTXWKra1vzZjT4j5kp2jaUSSJg+RCAhFyKQ kIvyTTw8ht2kHA8aLYSPznNePqfwexc97MSM/3nXIpnyZ+9mSqBf/8supv1RawJw80/mHvn3Vy55 eOXSoFluPnD4o3/o45329F6UdGFTzMXnFL78G+GT//EMEYGeMnrCV/xDy9M9sSgCPWUmXaqoTPm8 kAgk5EIEespM+mZcjqc3gQZmbBR2HH/+2xF+7fmzuU9++AuHL/1LfOB36YTD8p9sT/dGs5kX6MU6 4aXFsxFop58vemQO+Iv/TAuqzekw8wKVjTe3HW7sOPxfy+Hv3zR4UyKQMAn/eMPgtc3pjjr7kSRa yIUIJORCBBJyIQIJuRCBhFyIQEIuRCAhFyKQkAsRSMiFCCTkQgQSciECCbkQgYRciEBCLkQgIRci kJALEUjIhQgk5EIEEnIhAgm5EIGEXIhAQi5EICEXIpCQCxFIyIUIJORCBBJyIQIJuRCBhFyIQEIu RCAhFyKQkAsRSMjFzL9D2a02Y/GMPhH51hR/REFRzLxAh98lVSgW6cKEXIhAQi5EICEXIpCQCxFI yMVMjcL++49rZ12FZ46ZEuhxn0UqnA7ShQm5EIGEXIhAQi5KJVAnm/21pXZarmsslUA3pvyzs4rg rTNa+D0ppRLoe7ct3u2W6w6dhDsdxn/dnvAzMc+YUgmUWuBr/5FgFhViAF/9ToKsXAGoXAIBwLdv Glz99wS2ZC/047AO+LN/S/BPb5uzrsrElHIi8RtvZPjebYvPfdDHR1/w8Es1gi7ZrWAc8PMu47vv GHz9B1npcp8RpRQIAN7advjTf00AJGddlWeakt23wrQhAgm5EIGEXIhAwgDSJ5odmVggpStMOpzF qZhnnASkPAb6Ex01pkCrQ2FuTVgpoRxoJvJ4NJ4lGn9B7gRd2H1gd7KTCNMNkWGCZpBhIp9JBaxU zORV2fMWOIqWH9nWYwsURcvseQtMXnVQuApEoBmBDBzIskojRypjooCVig617/Vjjz1BDlRjpSLW Gg5EItEMoAgu87Ql1Xc6YadUykpFrHRn2L6bjz52vFNcxyY24XnnWN/rcsuDI5WIPDNC5sPozFql 4foKTvXgHqiY9b059rz7vFlfGbb1lSNtPoZAVxhoMOorHIbbrNQ86xDOj8Ny7TsQHok2bJSX2TRj 4/mB9Tw4HcIptcthuDSUpnFswBi/C9tY5UEe1GJ/B67loZyrf8IRUsMms37mh3MmSGE6YcX6O3BR 9D4XRbcZG6v75Tkg0pMEYmD0qMx1bNbvcK0GFwRwfrVjwdKLzQAchEHmJzBhqkwn7Fn94K4LArgw 3B52X6ME+uhjU2NGoGE3trHKzeg212pwldacBUsQKj3s0Df9LK5VsnYltkG7aiuVOVurwTUvwGGj OWj7Y/IfYDyBhlHoCgNbjM1PuOYFuFoNDnHaK/ZqhKcNx0k76FoTtPu21oWpVu/vk2d1nzwE4Ohm 0AmG8fsk2mhy8wKc6/duzOT+0mcFZnC//9N6/ZfN4iJMrQZ3YyHZF3m29stzLONuKGMANCiIGbgG bDTBH8+uc2t3jebnAZLHiksFM7jdBiW9v72x8IZDfWWYLDcBrDDQwCF5jg0Vk+xIPCKR+8ndV+n9 5/8QzjWoXge80m5wfLYwGbjdAZLkR+n/3nwNmztuIM0W9sQBniTPgf+YEBqV+ytf/OF7qVL5Fitq kO8D2gdkgno6YQJsBs4yENOP2HV//2df+7ufDf54RBrgMeKMyNvvEMB4z+98o1r5yIc/D8efBqmL rNR8znKFU4Act8C4odhe737/+6/9/J+/FQ+S5KsjUSa+8wsRCLhGAFSjAXXvXsdPa+e9auK81E/9 wKt4Jkt9Z632fa3ZQTFbAhQhAJidJE85IVKMFAAcE2kmBZdl1iqtrfbIZNZkfgoTV5FW491sYWHB DhJluMeNsMYhb9LCg+z5KoBVjqLXuVJZsejeQ78+xxUvQZakHEaBsybTzkJzpCmwRgEAsyfyFAE7 UGAYAFKtHSWW/TCz2oNN0tQGEZtQOWPb1nqV4faMjcVhznNyeYD8EWhYBgP4lMJ6gxp3oXZ353Wy 0NK1LrwsCrTJUh2ZVNuQlLNQQAVgQ8yZCFQQRP5AAspYaTidsIu9wHp+YP04td0aTLg7bxcWWnZv mP5Nl1egAodNDcbGKpprO3wpSZzbAXXqPTsXA2TAWTVw1kKx84k5I+aU5A3piqOLFEQBE1WZkLEL 4fwenGdT1wl7Ntipuqjqub3oU8wMXhEC7evGrgP1Bocx2Bg4tKvoLoJtAjWfQjkXk3MgnksIAAzq BZxeAIAQbQAJqANWKmZlI25Fg1X1YKe6b20LDLyOwXA9X/QBiunChuWMkukmjbqyJDlHcfy2cm6B 7PkaOdslNj1ytipd1ymhdG+wa1TXWN/rslK7HEXvc2G4zXvLEwdmmHMJVGAXdnCWurnecGvtkMJw gZPkPpkWyJjdoTjzcK4lEhWMUvMM7ALYheeBvep9DsMljqJwEHk2msOu6/HLE5NQlEDHLnVsYpWx fofQTmgNy4jjUa1bBZ1WOEgLwAKAwR72TdwG6hjmPK/j0MIoUEAeVHQUeDhDPZwbGtIk4PLw+y3C esFnFfbYAPYew7qOgzsJi5UHKF6gQ+WO6nhNuqszY/x1rZNw2g0r4kwPskApCIIgCIJQEP8P03/B 430mofMAAAAASUVORK5CYII= "
+       style="opacity:0.49074079;image-rendering:optimizeQuality"
+       preserveAspectRatio="none"
+       height="28.670942"
+       width="28.670942" />
+    <image
+       y="0.0055872202"
+       x="-0.031073861"
+       id="image3082"
+       xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJAAAACQCAYAAADnRuK4AAAABHNCSVQICAgIfAhkiAAAAAlwSFlz AABO4wAATuMBYdZxSQAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAAZdEVY dFRpdGxlAFR3ZWxmIEljb24gVGVtcGxhdGXyQOONAAAADHRFWHRBdXRob3IAeHBobnh/4OzMAAAA Y3RFWHRDb3B5cmlnaHQAQ0MgQXR0cmlidXRpb24tTm9uQ29tbWVyY2lhbC1TaGFyZUFsaWtlIGh0 dHA6Ly9jcmVhdGl2ZWNvbW1vbnMub3JnL2xpY2Vuc2VzL2J5LW5jLXNhLzQuMC9D0yJcAAAL6klE QVR4nO3dTYvcRhrA8adKJZW6Zc0LmTEmmYUF5zYOJjQLOSTQEMhlA7uHsNlPsCHg5DP4MzghHyC3 xZvTGh8CgYaQXMwEkngOZo1f2JAYz2ReLHePSqqqZy+t2Z5xj6c1pbZa7ecHxo09LdVEf0rVkjIG IIQQQgghhBBCCCEvA9bw7ZPJ4TQ2Oo0DPG6bFFJ9xoVTWUxVH1g25vXxfTAAgG63W/GuSa/XK14e DwTH/HklEVUZ0PF4ilBYkiQMACBN0yP7U0rRzFQRKeWRIMIwRACAOI6x1+uNBlRpRNMIiAEAK8JJ 05QppZjWmmmtGQCAtZbCmRLOOQIACCFQCIFSSgzDEEdCGo1oZgJ6Jp6trS2ulGJpmvI8z/nKysph QMaYw/1STO6KaAAAPM87DGh7ext937dhGFopJa6urtqqI6oyoCPx9Pt9nmUZX15e5koprrXm1lpm rWWIyOI4pngqxDnHJEmAMYacc+ScoxDCSint7u6uDYLARlFkx0RUe0Ds2rVrQRRFn3qe93fO+UVE XKxgu6R6TxDxP4j4z4cPH3529epVNfzzM0fkGhD78ssv/2CtvckYW7fWgtYaEKdyyYE4YoyBEAI4 54CIt5VSf/7oo4/+O/IlpQ/cWQNiAADXrl0LFhYWNowx60mSQJZlZ9wceZGCIIA4jsHzvNsPHjz4 09WrVzM44/UiXnLfhx/PAQCiKPo0z/P1nZ0diqdBsiyDnZ0dyPP80tra2hUYWcPC+Gt5JyoT0DPX eRhjH+7t7dEpq4EQEfb29oAx9rdOp8O73S6H8SEdj+oIMeH+xl0kZP1+/3WKp7kQEQ4ODl7f39/n aZpip9NBAICNjY1xH/MZjDmlTRpQ4chH9SzL2mcdPJkNSqlzSZKINE1tv99HIQSur68X14yKLztx lpjkFDb2ImG/3+fGGLfRk9oZYyCKIhFFkVhYWPAGg4HX7/f51tYW73a7405nR0y6BnomnoODA6+i 74HUTCklDg4OxNOnT0Ucx16WZXxMRGOddgo78sYkSVhxhXlpaansJzgygxCR+b7ve55nfd83aZqa OI4hSRIQQuDwRjjCCWugiSPodrssTVOmtWbD2xM0A80Jz/P8LMuEUkqEYegppbzl5WWepilP05QN Z6HCkUllkkU0AwCWJAlTSjFrLSvubVX6XZDaaK2F7/ssz3PgnKPWGpVS3FprRx65cZuBhjtiw7vq vN1uU0BzwvM8YYzxgiDwtNZeu93m1lpmjGFa68PnucBhEX3IGMOGG+eISHfT5wAiciGEZ609fGKi +HXaeycOqFj/aK0n3jhpBmstR0RmreVSyuI1O3/+/KnvpdMQgeGZhCEiK+Kx1h5OGMcfRR5V5jrQ uJ2SORAEAUNEFoZh6feeaQ1E8cynYllS5vjSKYwAwNnPKBQQcUIBESdlH+eYGa+++ip0Oh1YW1uD KIqAsWYty4wxkCQJ3L9/H27dugX7+/t1D+lMGhnQO++8A2+99Vbdw3DieR4sLS3Bm2++CW+88Qbc vHkT7ty5U/ewSmvcKazT6TQ+nuOEEPD+++/D2tpa3UMprVEBtVotePvtt+sexlRwzuHdd9+texil NSqgixcvQhAEdQ9jas6fPw8rKyt1D6OURgXUtP+4Z/HKK6/UPYRSGhWQ7/t1D2HqpJR1D6GURgVE Zg8FRJxQQMRJIy8kjoOIoJQ6/Qtrxhh77jrnwoULoJSCfr8Pv/76K1hrX+DoypubgAaDAXzxxRd1 D+NU586dg48//vjEv798+TJcvnwZAAAePXoE169fhzRNX9TwSqNT2Ay7cOHCzF84pYBesHa73I8T mPXbGxTQC1b2WtasX/uigIgTCugFK/upatZ//tLcfAo7yQcffAALCwu17PvevXuj//wAAAA8ffq0 1Da2trYqHFH15j6gpaUlWF5ermXf4w5+mRklyzL4/vvvqxxS5eY+oKZ59OgR7O3twc7ODty+fXvm H3WlgGbMjz/+CD/99FPdw5gYLaKJEwqIOKGAiBMKiDihgIgTCog4oYCIEwqIOKGAiBMKiDihgIgT Cog4oYCIEwqIOKGAiBMKiDihgIgTCog4oYCIEwqIOKGAiBMKiDihgIgTCog4oYCIEwqIOKGAiBMK iDihgIgTCog4oYCIEwqIOKGAiJO5/wllu7u7YIypZd9JktSy3xdp7gP66quv6h7CXKNTGHFCAREn FBBxQgERJxQQcTI3n8La7TZ88skndQ/jVIyxuodQqbkJiDEGYRjWPYyXDp3CiBMKiDihgIiTRgWU ZVndQ5g6pVTdQyilUQH9/vvvdQ9h6pr2PTYqoLt37871LPT48WPY3t6uexilNCqgNE3h22+/rXsY U2GthW+++abuYZTWqIAAAH744YeZ/3dEy8rzHG7cuAG//PJL3UMprZEXEr/77ju4d+8edDodWFtb g3PnzjXuCq8xBp48eQL379+HW7duwZMnT+oe0pk0MiAAgN9++w1u3LhR9zBeeo07hZHZQgERJxQQ cUIBEQAAYIzhWd5XOiDP8/CsOyOzjXNe+rhOGhAFM8eyLEPGGKZpCgDlZqMzn8JoFpofw2OJjDFk jCHnHDnnKIRAIQSGYXjisZ44oDAMDzdY7KCS0ZPacc7tMBybpulhRI8fPz79vWV35nkecs7R8zxL s9B8YIxZrbXhnFvP82wxQUwySZQKSAiB29vbKISw/X6f4pkTxhjteZ4ZxmMHg4EdThIohMA4jotj /cwxnyQgBACM4xillMg5Ryml9X2/np9YQConhNBZlpk8z7Xv+0YIYaWUdnisT4wHoMQM1Ov1sFgH 7e7uWiGErWT0pHZaay2lzKWUOssyLaU0u7u7NgxDG4Yh9nq90XiOhHTazVQEgMPb3HEcY5qmFgAg SRKDiI27C06egVLKfDAYWEQ0Ukq7t7dnW62WlVKOnr7GKnMdCItZKIoi22q1DCItg5oOEUFrnUdR lLdaLZMkiWm1WiaKIru6umqHs0/x6xmTroGK33FjY8Ourq7aKIpslmWDir4PUhOlVNLv93WSJKbf 7+t2u/28eJ6JqOzzQAgA0Ov1oNvt2sFgcFdK2aHTWDMhIgwGg3txHOtisRyGIW5sbIyGU9kpbPQ1 9no91FpfT5IE6FTWPIgISZKAMeZfi4uLdnV11W5ubpqNjQ0LABaejWfsQfZK7vfIVPPaa69tRFH0 V2PMed/3gXO6ud8EWmtIkgSyLPv5zp07/+j1evrBgwfPi+XEGeKs557D9125cuWPUsp/c87XhRAg RGOfkn0paK1Baw3W2p+VUn/5/PPPHw7/alwkp55aXBcvDADgvffea1+6dOkKAHzIGLvIOV9w3C6Z AmvtE0S8i4jXNzc3P/v6669TOGWRfJpKAhr+ztfX1/n29rYfRZFQSgnf9/08z4UQwjfGeL7ve9Za joiseO/wNXEwck+yuBFq8zw3nueZPM91EAR5lmW63W5naZrmi4uLZnNzc3StA3CGeADc/6+M0QuN GIYhtlot0+/3IY5jPDg4gCAIUGttpZSeMcYDABYEAQegeKpURJTnuUVElFKaLMvM8HcdhqEeXuMp Hs84dYE8iaoWLAgArLhSrbVmUkqmtTZSSuCco9YajTFWSskRERCRUUDVKQISQqBSyiKiDYLA+L5v ENG0Wi2TZZmNosg+7+Zo6f26bgCOnsZYp9Ph+/v7fDAYeHEce0opT2vN2+02N8Zway2jeKZj9IEw z/PsYDCwwxujJkkS0263zeLioh1+VHda+xSqmIHG3S9DrbVNkgSWl5dRKcWzLOPWWlYENPzaCnZP AP7/zyoUARljMAxDK6W0u7u7NgiCk+5tOc1CVc0CR2ahbrfLtra2uFKKpWnKrbXMGMNWVlaY1poZ Y2j2mZLiGZ7t7W0sHv4bhoST3p4oo+qAites2+2yJElYmqZMKcW01kxrffh11lqKqGKjTxAWjx9L KTEMQ4zjGE+4MToTAR3f1uHH9CIkAIA0TSmaF6R4EH4kHICK4wGoNqDj25vkNZmOkyKpNB6A6R3M 49ulaOpT6aL5uGkfWApndtAjE4QQQgghFfkfLnfEbGu/P8gAAAAASUVORK5CYII= "
+       style="display:inline;image-rendering:optimizeQuality"
+       preserveAspectRatio="none"
+       height="28.697369"
+       width="28.697369" />
+  </g>
+</svg>
diff --git a/app/src/main/assets/ic_device_sony_800n.svg b/app/src/main/assets/ic_device_sony_800n.svg
new file mode 100644
index 0000000..1c80954
--- /dev/null
+++ b/app/src/main/assets/ic_device_sony_800n.svg
@@ -0,0 +1,215 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   version="1.1"
+   id="svg2"
+   width="28.740126"
+   height="28.740126"
+   viewBox="0 0 28.740126 28.740126"
+   sodipodi:docname="ic_device_sony_800n.svg"
+   inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:dc="http://purl.org/dc/elements/1.1/">
+  <metadata
+     id="metadata8">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs6">
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 14.370063 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="28.740126 : 14.370063 : 1"
+       inkscape:persp3d-origin="14.370063 : 9.580042 : 1"
+       id="perspective903" />
+  </defs>
+  <sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="1920"
+     inkscape:window-height="1035"
+     id="namedview4"
+     showgrid="false"
+     showguides="true"
+     inkscape:guide-bbox="true"
+     inkscape:zoom="22.627418"
+     inkscape:cx="8.8167373"
+     inkscape:cy="16.373941"
+     inkscape:window-x="0"
+     inkscape:window-y="45"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="layer1"
+     inkscape:pagecheckerboard="0">
+    <inkscape:grid
+       type="xygrid"
+       id="grid972" />
+  </sodipodi:namedview>
+  <g
+     inkscape:groupmode="layer"
+     id="layer1"
+     inkscape:label="Background"
+     style="display:inline">
+    <g
+       inkscape:groupmode="layer"
+       id="layer4"
+       inkscape:label="Bottom"
+       style="display:inline"
+       sodipodi:insensitive="true">
+      <rect
+         style="display:inline;fill:#1f7fdb;fill-opacity:1;stroke:none;stroke-width:3.57115;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         id="rect817-5"
+         width="22.819572"
+         height="21.904638"
+         x="2.9239759"
+         y="3.8768768"
+         ry="0.9472276" />
+    </g>
+    <g
+       inkscape:groupmode="layer"
+       id="layer3"
+       inkscape:label="Middle"
+       style="display:inline"
+       sodipodi:insensitive="true">
+      <rect
+         style="display:inline;fill:#4dabf5;fill-opacity:1;stroke:none;stroke-width:3.57115;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         id="rect817-7"
+         width="22.819572"
+         height="21.904638"
+         x="2.9317887"
+         y="3.0349438"
+         ry="0.9472276" />
+    </g>
+    <g
+       inkscape:groupmode="layer"
+       id="layer2"
+       inkscape:label="Top"
+       style="display:inline"
+       sodipodi:insensitive="true">
+      <rect
+         style="fill:#2196f3;fill-opacity:1;stroke:none;stroke-width:3.57115;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         id="rect817"
+         width="22.819572"
+         height="21.904638"
+         x="2.9238882"
+         y="3.4130721"
+         ry="0.9472276" />
+    </g>
+    <g
+       id="g2458"
+       transform="translate(0.01135305,-0.15009041)">
+      <g
+         id="g2347">
+        <path
+           id="path13615-2"
+           style="fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:0.4;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+           d="m 7.279849,6.2422412 c -0.03346,0.01692 -0.06557,0.03428 -0.09731,0.05262 -0.03173,0.01835 -0.06309,0.03768 -0.09514,0.05843 -0.03205,0.02076 -0.06477,0.04295 -0.09916,0.0671 -0.03438,0.02415 -0.07046,0.05023 -0.109229,0.07876 -0.02534,0.01848 -0.05048,0.03753 -0.07551,0.05707 -0.02503,0.01954 -0.0499,0.0396 -0.07456,0.06013 -0.04934,0.04106 -0.09793,0.084 -0.145486,0.128648 -0.04756,0.04465 -0.09414,0.091 -0.139497,0.138751 -0.04536,0.04774 -0.08948,0.09688 -0.132231,0.147218 -0.04275,0.05034 -0.08411,0.101873 -0.123825,0.15432 -0.0397,0.05244 -0.07777,0.105815 -0.114003,0.159856 -0.03625,0.05404 -0.07062,0.108751 -0.102975,0.163896 -0.01618,0.02756 -0.0319,0.05524 -0.04703,0.08299 -0.01513,0.02775 -0.02969,0.05557 -0.04371,0.08346 -0.03618,0.07192 -0.06927,0.14029 -0.09954,0.205974 -0.03028,0.0657 -0.05776,0.1287 -0.08266,0.190082 -0.02488,0.06138 -0.04721,0.121101 -0.06723,0.180116 -0.01,0.02952 -0.01941,0.05886 -0.0283,0.08813 -0.009,0.02927 -0.01728,0.05852 -0.0251,0.08781 -0.0078,0.02929 -0.01514,0.05865 -0.02198,0.0882 -0.0069,0.02954 -0.01321,0.05927 -0.01911,0.0893 -0.01181,0.06007 -0.02184,0.12132 -0.03033,0.184805 -0.0086,0.06349 -0.01548,0.129192 -0.02116,0.198028 -0.0057,0.06884 -0.01012,0.140872 -0.01351,0.216987 -0.0025,0.05629 -0.0043,0.110992 -0.0052,0.16448 -8.64e-4,0.05349 -9.69e-4,0.105698 -6.4e-5,0.156926 8.64e-4,0.05122 0.0027,0.101473 0.0056,0.150996 0.0029,0.04952 0.0067,0.09832 0.01168,0.146697 0.0049,0.04837 0.01103,0.09631 0.01829,0.144095 0.0073,0.04777 0.01565,0.09538 0.02537,0.1431138 0.0097,0.04773 0.02073,0.09559 0.03307,0.143831 0.01233,0.04823 0.02603,0.09688 0.04117,0.146177 0.01514,0.04931 0.03172,0.0993 0.04983,0.150215 0.01811,0.05092 0.03775,0.102736 0.05902,0.155817 0.02125,0.05309 0.04414,0.107386 0.0687,0.163179 0.02456,0.0558 0.05079,0.113155 0.07883,0.172233 0.02805,0.05908 0.0579,0.119891 0.0896,0.182787 0.03169,0.0629 0.06523,0.127824 0.100756,0.195098 0.03552,0.06727 0.07298,0.136893 0.112485,0.209101 0.03951,0.07221 0.08105,0.147045 0.124707,0.224738 0.06395,0.110373 0.12747,0.218614 0.190476,0.324728 0.063,0.10611 0.12555,0.210119 0.187612,0.312027 0.06207,0.101911 0.123617,0.201698 0.184741,0.299454 0.06112,0.09775 0.121762,0.19342 0.181938,0.287076 0.06017,0.09366 0.119894,0.185283 0.179133,0.274895 0.05924,0.08961 0.118038,0.177222 0.176331,0.262845 0.05829,0.08562 0.116105,0.1693 0.173462,0.250989 0.05736,0.08169 0.114249,0.161388 0.170659,0.239198 0.05641,0.0778 0.112317,0.153684 0.167791,0.227668 0.05546,0.07399 0.110513,0.14612 0.165051,0.216333 0.05453,0.07022 0.108593,0.138504 0.162182,0.204999 0.0027,0.0033 0.0053,0.0064 0.008,0.0098 C 8.00872,14.732698 7.120877,13.942779 6.658257,13.361473 5.598455,12.029821 5.055189,10.452858 5.217346,9.1790022 5.365603,8.0143522 5.882,7.0633652 6.841532,6.1881132 7.079748,5.9708212 7.106058,5.9999142 7.175394,6.0213522 7.413036,6.1657852 7.440231,6.1534602 7.2799,6.2422692 Z"
+           sodipodi:nodetypes="cssscssssssssssssssssssssssssssssssssssscsssssssssssccsscc" />
+        <path
+           style="fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:0.3;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+           d="m 11.577603,11.492866 c 0.322335,-0.01399 0.357232,0.03938 0.593611,-0.181212 0.221983,-0.240393 0.274315,-0.351823 0.276651,-0.568905 0.0025,-0.225226 -0.0071,-0.251559 -0.197844,-0.709979 C 11.842608,9.0532972 11.354715,8.0707792 11.126601,7.7704282 10.78476,7.3203352 10.29549,6.9914762 9.74677,6.8222662 9.333842,6.6949312 8.625467,6.7136062 8.182495,6.8635162 6.779112,7.3384312 6.027958,8.8521302 6.491449,10.27125 c 0.271605,0.831606 0.705445,1.281983 1.214295,1.281983 z"
+           id="path3272-6"
+           sodipodi:nodetypes="ccsssssscc" />
+        <path
+           style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0.3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke;stop-color:#000000"
+           id="path16756-1"
+           sodipodi:type="arc"
+           sodipodi:cx="-12.009243"
+           sodipodi:cy="12.377947"
+           sodipodi:rx="0.19409445"
+           sodipodi:ry="0.19840427"
+           sodipodi:start="0.57990659"
+           sodipodi:end="0.54828965"
+           sodipodi:arc-type="arc"
+           sodipodi:open="true"
+           d="m -11.84688,12.486662 a 0.19409445,0.19840427 0 0 1 -0.26743,0.05811 0.19409445,0.19840427 0 0 1 -0.05896,-0.2729 0.19409445,0.19840427 0 0 1 0.266498,-0.06242 0.19409445,0.19840427 0 0 1 0.06317,0.271913"
+           transform="scale(-1,1)" />
+        <path
+           id="rect20090-0"
+           style="fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:0.3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke;stop-color:#000000"
+           d="m 11.857757,10.952689 -3.825845,0.06844 c -0.144962,0.0026 -0.261704,0.119311 -0.261704,0.267512 0,0.148204 0.11674,0.270106 0.261704,0.267514 l 3.825845,-0.06844 c 0.144962,-0.0026 0.261704,-0.119312 0.261704,-0.267515 0,-0.148201 -0.116741,-0.270105 -0.261704,-0.267511 z"
+           sodipodi:nodetypes="sssssss" />
+        <path
+           style="fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:0.4;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+           d="m 12.18194,17.216199 c 0.624312,-0.131607 1.333297,-0.60414 1.680554,-1.120063 0.529917,-0.787312 0.660533,-1.614976 0.412007,-2.610703 -0.445153,-1.783509 -1.737094,-5.0026358 -2.421826,-6.0344618 -0.48947,-0.737586 -1.146722,-1.225567 -1.985164,-1.473899 -0.284388,-0.08423 -0.549789,-0.118307 -0.970098,-0.126588 -0.24378,-0.0048 -0.652706,0.03674 -1.040841,0.17381 -0.485591,0.168758 -0.640212,0.230083 -0.950323,0.458278 -0.405441,0.295742 -0.788291,0.729017 -1.012688,1.175025 -0.289449,0.575304 -0.391516,0.937594 -0.418687,1.546511 -0.04019,0.9006898 0.117467,1.4116638 0.816102,2.6547258 1.00275,1.681382 1.225081,2.020621 2.371836,3.434141 0.232245,0.299614 0.629643,0.75695 0.883104,1.016312 0.372595,0.381249 0.523321,0.502467 0.787118,0.633023 0.598301,0.296089 1.270931,0.395727 1.848906,0.273889 z"
+           id="path5762-6"
+           sodipodi:nodetypes="ssssssccssccsss" />
+      </g>
+      <g
+         id="g2347-1"
+         style="display:inline"
+         transform="matrix(-1,0,0,1,28.780726,6.499701)">
+        <path
+           id="path13615-2-5"
+           style="fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:0.4;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+           d="m 7.279849,6.2422412 c -0.03346,0.01692 -0.06557,0.03428 -0.09731,0.05262 -0.03173,0.01835 -0.06309,0.03768 -0.09514,0.05843 -0.03205,0.02076 -0.06477,0.04295 -0.09916,0.0671 -0.03438,0.02415 -0.07046,0.05023 -0.109229,0.07876 -0.02534,0.01848 -0.05048,0.03753 -0.07551,0.05707 -0.02503,0.01954 -0.0499,0.0396 -0.07456,0.06013 -0.04934,0.04106 -0.09793,0.084 -0.145486,0.128648 -0.04756,0.04465 -0.09414,0.091 -0.139497,0.138751 -0.04536,0.04774 -0.08948,0.09688 -0.132231,0.147218 -0.04275,0.05034 -0.08411,0.101873 -0.123825,0.15432 -0.0397,0.05244 -0.07777,0.105815 -0.114003,0.159856 -0.03625,0.05404 -0.07062,0.108751 -0.102975,0.163896 -0.01618,0.02756 -0.0319,0.05524 -0.04703,0.08299 -0.01513,0.02775 -0.02969,0.05557 -0.04371,0.08346 -0.03618,0.07192 -0.06927,0.14029 -0.09954,0.205974 -0.03028,0.0657 -0.05776,0.1287 -0.08266,0.190082 -0.02488,0.06138 -0.04721,0.121101 -0.06723,0.180116 -0.01,0.02952 -0.01941,0.05886 -0.0283,0.08813 -0.009,0.02927 -0.01728,0.05852 -0.0251,0.08781 -0.0078,0.02929 -0.01514,0.05865 -0.02198,0.0882 -0.0069,0.02954 -0.01321,0.05927 -0.01911,0.0893 -0.01181,0.06007 -0.02184,0.12132 -0.03033,0.184805 -0.0086,0.06349 -0.01548,0.129192 -0.02116,0.198028 -0.0057,0.06884 -0.01012,0.140872 -0.01351,0.216987 -0.0025,0.05629 -0.0043,0.110992 -0.0052,0.16448 -8.64e-4,0.05349 -9.69e-4,0.105698 -6.4e-5,0.156926 8.64e-4,0.05122 0.0027,0.101473 0.0056,0.150996 0.0029,0.04952 0.0067,0.09832 0.01168,0.146697 0.0049,0.04837 0.01103,0.09631 0.01829,0.144095 0.0073,0.04777 0.01565,0.09538 0.02537,0.1431138 0.0097,0.04773 0.02073,0.09559 0.03307,0.143831 0.01233,0.04823 0.02603,0.09688 0.04117,0.146177 0.01514,0.04931 0.03172,0.0993 0.04983,0.150215 0.01811,0.05092 0.03775,0.102736 0.05902,0.155817 0.02125,0.05309 0.04414,0.107386 0.0687,0.163179 0.02456,0.0558 0.05079,0.113155 0.07883,0.172233 0.02805,0.05908 0.0579,0.119891 0.0896,0.182787 0.03169,0.0629 0.06523,0.127824 0.100756,0.195098 0.03552,0.06727 0.07298,0.136893 0.112485,0.209101 0.03951,0.07221 0.08105,0.147045 0.124707,0.224738 0.06395,0.110373 0.12747,0.218614 0.190476,0.324728 0.063,0.10611 0.12555,0.210119 0.187612,0.312027 0.06207,0.101911 0.123617,0.201698 0.184741,0.299454 0.06112,0.09775 0.121762,0.19342 0.181938,0.287076 0.06017,0.09366 0.119894,0.185283 0.179133,0.274895 0.05924,0.08961 0.118038,0.177222 0.176331,0.262845 0.05829,0.08562 0.116105,0.1693 0.173462,0.250989 0.05736,0.08169 0.114249,0.161388 0.170659,0.239198 0.05641,0.0778 0.112317,0.153684 0.167791,0.227668 0.05546,0.07399 0.110513,0.14612 0.165051,0.216333 0.05453,0.07022 0.108593,0.138504 0.162182,0.204999 0.0027,0.0033 0.0053,0.0064 0.008,0.0098 C 8.00872,14.732698 7.120877,13.942779 6.658257,13.361473 5.598455,12.029821 5.055189,10.452858 5.217346,9.1790022 5.365603,8.0143522 5.882,7.0633652 6.841532,6.1881132 7.079748,5.9708212 7.106058,5.9999142 7.175394,6.0213522 7.413036,6.1657852 7.440231,6.1534602 7.2799,6.2422692 Z"
+           sodipodi:nodetypes="cssscssssssssssssssssssssssssssssssssssscsssssssssssccsscc" />
+        <path
+           style="fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:0.3;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+           d="m 11.577603,11.492866 c 0.322335,-0.01399 0.357232,0.03938 0.593611,-0.181212 0.221983,-0.240393 0.274315,-0.351823 0.276651,-0.568905 0.0025,-0.225226 -0.0071,-0.251559 -0.197844,-0.709979 C 11.842608,9.0532972 11.354715,8.0707792 11.126601,7.7704282 10.78476,7.3203352 10.29549,6.9914762 9.74677,6.8222662 9.333842,6.6949312 8.625467,6.7136062 8.182495,6.8635162 6.779112,7.3384312 6.027958,8.8521302 6.491449,10.27125 c 0.271605,0.831606 0.705445,1.281983 1.214295,1.281983 z"
+           id="path3272-6-9"
+           sodipodi:nodetypes="ccsssssscc" />
+        <path
+           style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0.3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke;stop-color:#000000"
+           id="path16756-1-4"
+           sodipodi:type="arc"
+           sodipodi:cx="-12.009243"
+           sodipodi:cy="12.377947"
+           sodipodi:rx="0.19409445"
+           sodipodi:ry="0.19840427"
+           sodipodi:start="0.57990659"
+           sodipodi:end="0.54828965"
+           sodipodi:arc-type="arc"
+           sodipodi:open="true"
+           transform="scale(-1,1)"
+           d="m -11.84688,12.486662 a 0.19409445,0.19840427 0 0 1 -0.26743,0.05811 0.19409445,0.19840427 0 0 1 -0.05896,-0.2729 0.19409445,0.19840427 0 0 1 0.266498,-0.06242 0.19409445,0.19840427 0 0 1 0.06317,0.271913" />
+        <path
+           id="rect20090-0-9"
+           style="fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:0.3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke;stop-color:#000000"
+           d="m 11.857757,10.952689 -3.825845,0.06844 c -0.144962,0.0026 -0.261704,0.119311 -0.261704,0.267512 0,0.148204 0.11674,0.270106 0.261704,0.267514 l 3.825845,-0.06844 c 0.144962,-0.0026 0.261704,-0.119312 0.261704,-0.267515 0,-0.148201 -0.116741,-0.270105 -0.261704,-0.267511 z"
+           sodipodi:nodetypes="sssssss" />
+        <path
+           style="fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:0.4;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+           d="m 12.18194,17.216199 c 0.624312,-0.131607 1.333297,-0.60414 1.680554,-1.120063 0.529917,-0.787312 0.660533,-1.614976 0.412007,-2.610703 -0.445153,-1.783509 -1.737094,-5.0026358 -2.421826,-6.0344618 -0.48947,-0.737586 -1.146722,-1.225567 -1.985164,-1.473899 -0.284388,-0.08423 -0.549789,-0.118307 -0.970098,-0.126588 -0.24378,-0.0048 -0.652706,0.03674 -1.040841,0.17381 -0.485591,0.168758 -0.640212,0.230083 -0.950323,0.458278 -0.405441,0.295742 -0.788291,0.729017 -1.012688,1.175025 -0.289449,0.575304 -0.391516,0.937594 -0.418687,1.546511 -0.04019,0.9006898 0.117467,1.4116638 0.816102,2.6547258 1.00275,1.681382 1.225081,2.020621 2.371836,3.434141 0.232245,0.299614 0.629643,0.75695 0.883104,1.016312 0.372595,0.381249 0.523321,0.502467 0.787118,0.633023 0.598301,0.296089 1.270931,0.395727 1.848906,0.273889 z"
+           id="path5762-6-0"
+           sodipodi:nodetypes="ssssssccssccsss" />
+      </g>
+    </g>
+  </g>
+  <g
+     inkscape:groupmode="layer"
+     id="layer5"
+     inkscape:label="Original"
+     style="display:none"
+     sodipodi:insensitive="true">
+    <image
+       y="0.0079936879"
+       x="0.010951031"
+       id="image4124"
+       xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGAAAABgCAMAAADVRocKAAAAA3NCSVQICAjb4U/gAAAACXBIWXMA ADFOAAAxTgGacD5yAAAAGXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAABF0RVh0 VGl0bGUAUGViYmxlIEljb26KvwD9AAAADHRFWHRBdXRob3IAeHBobnh/4OzMAAAAY3RFWHRDb3B5 cmlnaHQAQ0MgQXR0cmlidXRpb24tTm9uQ29tbWVyY2lhbC1TaGFyZUFsaWtlIGh0dHA6Ly9jcmVh dGl2ZWNvbW1vbnMub3JnL2xpY2Vuc2VzL2J5LW5jLXNhLzMuMC/eBBrlAAABy1BMVEUAAAAAAAAA AP8AAIAAAFUAAKoAVaoAAIAAQIAAM2YAM5kzM2YzM5krK4AkJG0kJJIgIIAcHHEcHI4aGoAXF3QX F4sXLnQXLosVK4AUJ3YUJ4kSJIAkJIARInciInciIogQIIAgIIAeHngeHoccHIAcK4AbKHkbKIYa GoAaJoAYJHkXI4AXI4sWIXoWIYUVIIAfH3ofH4UdJ4ATHIQcJnsbJIAaI4QaIoAZIXsZIYQYIHgY IIAXH4MXJ3wXHoAXJoBLpfAWJHwdJHwdJIMcI4Acas0VIoMbIoAaIXwaIIAZH3wZH4MZJXwYHoAY HoYYJIAehtsYHoIefdtHoO0jetZGovNEn+4gg+EfgN5Ep/VKqfREpvQgfdkgfttKqvQggd4hjesw nPMwnPRJqfUhgd4hjutJqfUgfttNq/VNq/QhlPEhlPIhlvMkmPMglvIff9sgjeoglPAhk/AhlPEh lPIhlvMkl/MkmPMlmPMmmPMnmfMpmvMzn/Q0n/Q1n/Q2oPRIqfVNq/VasfZbsvZhtPZitfdtuveQ yvmXzvmj0/qk1Pqu2fu83/vR6f3T6v3Z7f3i8f3j8v3l8/7m8/7p9f7q9f7x+P7y+f7////ougeB AAAAb3RSTlMAAQECAwMDBAQFBQUFBgcHCAkJCgsLCwsMDQ0ODg8PDxAQERESEhMTFBQVFhYXFxgZ GRobGxwdHh8fICAhISIiIiMjIyQkJSYnKCkpKSoqKiorKyssLC2xs7O0tba2tu7u7u7u7+/w8fHy +/v7+/ywJhX9AAAC0klEQVRo3u2a+VfTQBCAm00iW0nSTVNKbKQYbIMUC6jU4q2IB2rF+74vPEAR LSjIIXgAAiqef65py6MNJe17y1RF8/26L/N1N9O8ZGZcLof/DiYFoiV1cZHwiGVZjh7zalRAYYbn sFsQ6RFwGccyBeJjwSMrPn8lHX6fQqRy3tbAsFiU1So9FA4bNIRD63XNTwTO5pSYc9fuvwbgwaW9 SxqYs/eGBgcAGBy+fRrlpRPDsJdfJoEYuMgvzibz/vK3kmDcFMuxNZvM/BG74QTdFV6PJZsYlhe9 XXCCrlCVStwcyhG45QCkoCmiV0o4uwXEib4aQMHjbc0RTXYvCMx/mGdNHaSgraWxRhEXzsgUEC0C KWjfszlUYRWsrYcUHGttNlSRzxUEo5CCjrZYrSqVVlD3Nwj6vqcZsovzKrPeTy3o/5lmxE4wnFl/ 4QhKLvjy2YY5IEExHMH0B1umQQSj9o+EUUewIBh/m8c4qOBbfu58dQRLCmYnJmZLKphKJicdwR8X vJsymSmhIM37FSv4OJlhxnlUOAJHAC8Ye5PHmPNe9PsEJX/5dT5AigvmPtkw53wnr3xB34809uWc zDp9OWflV7xgBaBlzY79MWORALhuui+vbhoArfwe3rUp5MtWfl2Il1QDUtAaj+rKakv1XdEhBfGm Wo1kq+/pM1Ih+wdRI6iIq3IFvEAewgke6QHFg1mU26PjhbtwghsKEbG1X8ew3PmnUPGfXRAwv7hr yqAD13t6nwPQ23PnBMeivG4gg85c7XwCQOeVU+avZ5bslcrrNm7dfehIInGSguOJo+0Hd27ZoOam j3ULvOjTI02x+PYddLTEY42GJpfnpo/L0g50k8pqI1IfbWikoSFaXxfSFKnMrqFsZpKbKKoWDFbT EdQCqlfCdhtIG7AoESLTQogk4EJd/VRTn8fLgecKTg3MjyUsA4SKDD7MT1bQ8++NmvwCRmydh2Fg UvEAAAAASUVORK5CYII= "
+       style="opacity:0.49074079;image-rendering:optimizeQuality"
+       preserveAspectRatio="none"
+       height="28.710279"
+       width="28.710279" />
+  </g>
+</svg>
diff --git a/app/src/main/assets/ic_device_sony_800n_disabled.svg b/app/src/main/assets/ic_device_sony_800n_disabled.svg
new file mode 100644
index 0000000..e43bffb
--- /dev/null
+++ b/app/src/main/assets/ic_device_sony_800n_disabled.svg
@@ -0,0 +1,268 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   version="1.1"
+   id="svg2"
+   width="28.740126"
+   height="28.740126"
+   viewBox="0 0 28.740126 28.740126"
+   sodipodi:docname="ic_device_sony_800n_disabled.svg"
+   inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:android="http://schemas.android.com/apk/res/android">
+  <metadata
+     id="metadata8">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs6">
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 14.370063 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="28.740126 : 14.370063 : 1"
+       inkscape:persp3d-origin="14.370063 : 9.580042 : 1"
+       id="perspective903" />
+  </defs>
+  <sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="1920"
+     inkscape:window-height="1035"
+     id="namedview4"
+     showgrid="false"
+     showguides="true"
+     inkscape:guide-bbox="true"
+     inkscape:zoom="16"
+     inkscape:cx="7.34375"
+     inkscape:cy="7.65625"
+     inkscape:window-x="0"
+     inkscape:window-y="45"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="layer6"
+     inkscape:pagecheckerboard="0">
+    <inkscape:grid
+       type="xygrid"
+       id="grid972" />
+  </sodipodi:namedview>
+  <g
+     inkscape:groupmode="layer"
+     id="layer1"
+     inkscape:label="Background"
+     style="display:inline">
+    <g
+       inkscape:groupmode="layer"
+       id="layer4"
+       inkscape:label="Bottom"
+       style="display:inline"
+       sodipodi:insensitive="true">
+      <rect
+         style="display:inline;fill:#7a7a7a;fill-opacity:1;stroke:none;stroke-width:3.57115173;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         id="rect817-5"
+         width="22.819572"
+         height="21.904638"
+         x="2.9239759"
+         y="3.8768768"
+         ry="0.9472276" />
+    </g>
+    <g
+       inkscape:groupmode="layer"
+       id="layer3"
+       inkscape:label="Middle"
+       style="display:inline"
+       sodipodi:insensitive="true">
+      <rect
+         style="display:inline;fill:#9f9f9f;fill-opacity:0.94117647;stroke:none;stroke-width:3.57115173;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         id="rect817-7"
+         width="22.819572"
+         height="21.904638"
+         x="2.9317887"
+         y="3.0349438"
+         ry="0.9472276" />
+    </g>
+    <g
+       inkscape:groupmode="layer"
+       id="layer2"
+       inkscape:label="Top"
+       style="display:inline"
+       sodipodi:insensitive="true">
+      <rect
+         style="fill:#8a8a8a;fill-opacity:1;stroke:none;stroke-width:3.57115173;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         id="rect817"
+         width="22.819572"
+         height="21.904638"
+         x="2.9238882"
+         y="3.4130721"
+         ry="0.9472276" />
+    </g>
+  </g>
+  <g
+     inkscape:groupmode="layer"
+     id="layer6"
+     inkscape:label="Foreground"
+     style="display:inline">
+    <g
+       inkscape:groupmode="layer"
+       id="layer7"
+       inkscape:label="Backplane"
+       style="display:inline" />
+    <g
+       inkscape:groupmode="layer"
+       id="layer8"
+       inkscape:label="Frontplane"
+       style="display:inline">
+      <g
+         inkscape:groupmode="layer"
+         id="layer9"
+         inkscape:label="Base"
+         style="display:inline" />
+      <g
+         inkscape:groupmode="layer"
+         id="layer10"
+         inkscape:label="Top"
+         style="display:inline" />
+      <g
+         inkscape:groupmode="layer"
+         id="layer11"
+         inkscape:label="Toptop"
+         style="display:inline" />
+      <g
+         inkscape:groupmode="layer"
+         id="layer12"
+         inkscape:label="Toptoptop"
+         style="display:inline" />
+    </g>
+    <path
+       inkscape:connector-curvature="0"
+       style="fill:#8a8a8a;fill-opacity:0.94117647"
+       d="m 12.48275,10.865376 h 3.907 v 6.978 h -3.907 z"
+       android:strokeWidth="1"
+       id="path2735" />
+    <g
+       id="g2458"
+       transform="translate(0.01135305,-0.15009041)"
+       style="display:inline">
+      <g
+         id="g2347">
+        <path
+           id="path13615-2"
+           style="fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:0.4;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+           d="m 7.279849,6.2422412 c -0.03346,0.01692 -0.06557,0.03428 -0.09731,0.05262 -0.03173,0.01835 -0.06309,0.03768 -0.09514,0.05843 -0.03205,0.02076 -0.06477,0.04295 -0.09916,0.0671 -0.03438,0.02415 -0.07046,0.05023 -0.109229,0.07876 -0.02534,0.01848 -0.05048,0.03753 -0.07551,0.05707 -0.02503,0.01954 -0.0499,0.0396 -0.07456,0.06013 -0.04934,0.04106 -0.09793,0.084 -0.145486,0.128648 -0.04756,0.04465 -0.09414,0.091 -0.139497,0.138751 -0.04536,0.04774 -0.08948,0.09688 -0.132231,0.147218 -0.04275,0.05034 -0.08411,0.101873 -0.123825,0.15432 -0.0397,0.05244 -0.07777,0.105815 -0.114003,0.159856 -0.03625,0.05404 -0.07062,0.108751 -0.102975,0.163896 -0.01618,0.02756 -0.0319,0.05524 -0.04703,0.08299 -0.01513,0.02775 -0.02969,0.05557 -0.04371,0.08346 -0.03618,0.07192 -0.06927,0.14029 -0.09954,0.205974 -0.03028,0.0657 -0.05776,0.1287 -0.08266,0.190082 -0.02488,0.06138 -0.04721,0.121101 -0.06723,0.180116 -0.01,0.02952 -0.01941,0.05886 -0.0283,0.08813 -0.009,0.02927 -0.01728,0.05852 -0.0251,0.08781 -0.0078,0.02929 -0.01514,0.05865 -0.02198,0.0882 -0.0069,0.02954 -0.01321,0.05927 -0.01911,0.0893 -0.01181,0.06007 -0.02184,0.12132 -0.03033,0.184805 -0.0086,0.06349 -0.01548,0.129192 -0.02116,0.198028 -0.0057,0.06884 -0.01012,0.140872 -0.01351,0.216987 -0.0025,0.05629 -0.0043,0.110992 -0.0052,0.16448 -8.64e-4,0.05349 -9.69e-4,0.105698 -6.4e-5,0.156926 8.64e-4,0.05122 0.0027,0.101473 0.0056,0.150996 0.0029,0.04952 0.0067,0.09832 0.01168,0.146697 0.0049,0.04837 0.01103,0.09631 0.01829,0.144095 0.0073,0.04777 0.01565,0.09538 0.02537,0.1431138 0.0097,0.04773 0.02073,0.09559 0.03307,0.143831 0.01233,0.04823 0.02603,0.09688 0.04117,0.146177 0.01514,0.04931 0.03172,0.0993 0.04983,0.150215 0.01811,0.05092 0.03775,0.102736 0.05902,0.155817 0.02125,0.05309 0.04414,0.107386 0.0687,0.163179 0.02456,0.0558 0.05079,0.113155 0.07883,0.172233 0.02805,0.05908 0.0579,0.119891 0.0896,0.182787 0.03169,0.0629 0.06523,0.127824 0.100756,0.195098 0.03552,0.06727 0.07298,0.136893 0.112485,0.209101 0.03951,0.07221 0.08105,0.147045 0.124707,0.224738 0.06395,0.110373 0.12747,0.218614 0.190476,0.324728 0.063,0.10611 0.12555,0.210119 0.187612,0.312027 0.06207,0.101911 0.123617,0.201698 0.184741,0.299454 0.06112,0.09775 0.121762,0.19342 0.181938,0.287076 0.06017,0.09366 0.119894,0.185283 0.179133,0.274895 0.05924,0.08961 0.118038,0.177222 0.176331,0.262845 0.05829,0.08562 0.116105,0.1693 0.173462,0.250989 0.05736,0.08169 0.114249,0.161388 0.170659,0.239198 0.05641,0.0778 0.112317,0.153684 0.167791,0.227668 0.05546,0.07399 0.110513,0.14612 0.165051,0.216333 0.05453,0.07022 0.108593,0.138504 0.162182,0.204999 0.0027,0.0033 0.0053,0.0064 0.008,0.0098 C 8.00872,14.732698 7.120877,13.942779 6.658257,13.361473 5.598455,12.029821 5.055189,10.452858 5.217346,9.1790022 5.365603,8.0143522 5.882,7.0633652 6.841532,6.1881132 7.079748,5.9708212 7.106058,5.9999142 7.175394,6.0213522 7.413036,6.1657852 7.440231,6.1534602 7.2799,6.2422692 Z"
+           sodipodi:nodetypes="cssscssssssssssssssssssssssssssssssssssscsssssssssssccsscc" />
+        <path
+           style="fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:0.3;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+           d="m 11.577603,11.492866 c 0.322335,-0.01399 0.357232,0.03938 0.593611,-0.181212 0.221983,-0.240393 0.274315,-0.351823 0.276651,-0.568905 0.0025,-0.225226 -0.0071,-0.251559 -0.197844,-0.709979 C 11.842608,9.0532972 11.354715,8.0707792 11.126601,7.7704282 10.78476,7.3203352 10.29549,6.9914762 9.74677,6.8222662 9.333842,6.6949312 8.625467,6.7136062 8.182495,6.8635162 6.779112,7.3384312 6.027958,8.8521302 6.491449,10.27125 c 0.271605,0.831606 0.705445,1.281983 1.214295,1.281983 z"
+           id="path3272-6"
+           sodipodi:nodetypes="ccsssssscc" />
+        <path
+           style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0.3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke;stop-color:#000000"
+           id="path16756-1"
+           sodipodi:type="arc"
+           sodipodi:cx="-12.009243"
+           sodipodi:cy="12.377947"
+           sodipodi:rx="0.19409445"
+           sodipodi:ry="0.19840427"
+           sodipodi:start="0.57990659"
+           sodipodi:end="0.54828965"
+           sodipodi:arc-type="arc"
+           sodipodi:open="true"
+           transform="scale(-1,1)"
+           d="m -11.84688,12.486662 a 0.19409445,0.19840427 0 0 1 -0.26743,0.05811 0.19409445,0.19840427 0 0 1 -0.05896,-0.2729 0.19409445,0.19840427 0 0 1 0.266498,-0.06242 0.19409445,0.19840427 0 0 1 0.06317,0.271913" />
+        <path
+           id="rect20090-0"
+           style="fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:0.3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke;stop-color:#000000"
+           d="m 11.857757,10.952689 -3.825845,0.06844 c -0.144962,0.0026 -0.261704,0.119311 -0.261704,0.267512 0,0.148204 0.11674,0.270106 0.261704,0.267514 l 3.825845,-0.06844 c 0.144962,-0.0026 0.261704,-0.119312 0.261704,-0.267515 0,-0.148201 -0.116741,-0.270105 -0.261704,-0.267511 z"
+           sodipodi:nodetypes="sssssss" />
+        <path
+           style="fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:0.4;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+           d="m 12.18194,17.216199 c 0.624312,-0.131607 1.333297,-0.60414 1.680554,-1.120063 0.529917,-0.787312 0.660533,-1.614976 0.412007,-2.610703 -0.445153,-1.783509 -1.737094,-5.0026358 -2.421826,-6.0344618 -0.48947,-0.737586 -1.146722,-1.225567 -1.985164,-1.473899 -0.284388,-0.08423 -0.549789,-0.118307 -0.970098,-0.126588 -0.24378,-0.0048 -0.652706,0.03674 -1.040841,0.17381 -0.485591,0.168758 -0.640212,0.230083 -0.950323,0.458278 -0.405441,0.295742 -0.788291,0.729017 -1.012688,1.175025 -0.289449,0.575304 -0.391516,0.937594 -0.418687,1.546511 -0.04019,0.9006898 0.117467,1.4116638 0.816102,2.6547258 1.00275,1.681382 1.225081,2.020621 2.371836,3.434141 0.232245,0.299614 0.629643,0.75695 0.883104,1.016312 0.372595,0.381249 0.523321,0.502467 0.787118,0.633023 0.598301,0.296089 1.270931,0.395727 1.848906,0.273889 z"
+           id="path5762-6"
+           sodipodi:nodetypes="ssssssccssccsss" />
+      </g>
+      <g
+         id="g2347-1"
+         style="display:inline"
+         transform="matrix(-1,0,0,1,28.780726,6.499701)">
+        <path
+           id="path13615-2-5"
+           style="fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:0.4;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+           d="m 7.279849,6.2422412 c -0.03346,0.01692 -0.06557,0.03428 -0.09731,0.05262 -0.03173,0.01835 -0.06309,0.03768 -0.09514,0.05843 -0.03205,0.02076 -0.06477,0.04295 -0.09916,0.0671 -0.03438,0.02415 -0.07046,0.05023 -0.109229,0.07876 -0.02534,0.01848 -0.05048,0.03753 -0.07551,0.05707 -0.02503,0.01954 -0.0499,0.0396 -0.07456,0.06013 -0.04934,0.04106 -0.09793,0.084 -0.145486,0.128648 -0.04756,0.04465 -0.09414,0.091 -0.139497,0.138751 -0.04536,0.04774 -0.08948,0.09688 -0.132231,0.147218 -0.04275,0.05034 -0.08411,0.101873 -0.123825,0.15432 -0.0397,0.05244 -0.07777,0.105815 -0.114003,0.159856 -0.03625,0.05404 -0.07062,0.108751 -0.102975,0.163896 -0.01618,0.02756 -0.0319,0.05524 -0.04703,0.08299 -0.01513,0.02775 -0.02969,0.05557 -0.04371,0.08346 -0.03618,0.07192 -0.06927,0.14029 -0.09954,0.205974 -0.03028,0.0657 -0.05776,0.1287 -0.08266,0.190082 -0.02488,0.06138 -0.04721,0.121101 -0.06723,0.180116 -0.01,0.02952 -0.01941,0.05886 -0.0283,0.08813 -0.009,0.02927 -0.01728,0.05852 -0.0251,0.08781 -0.0078,0.02929 -0.01514,0.05865 -0.02198,0.0882 -0.0069,0.02954 -0.01321,0.05927 -0.01911,0.0893 -0.01181,0.06007 -0.02184,0.12132 -0.03033,0.184805 -0.0086,0.06349 -0.01548,0.129192 -0.02116,0.198028 -0.0057,0.06884 -0.01012,0.140872 -0.01351,0.216987 -0.0025,0.05629 -0.0043,0.110992 -0.0052,0.16448 -8.64e-4,0.05349 -9.69e-4,0.105698 -6.4e-5,0.156926 8.64e-4,0.05122 0.0027,0.101473 0.0056,0.150996 0.0029,0.04952 0.0067,0.09832 0.01168,0.146697 0.0049,0.04837 0.01103,0.09631 0.01829,0.144095 0.0073,0.04777 0.01565,0.09538 0.02537,0.1431138 0.0097,0.04773 0.02073,0.09559 0.03307,0.143831 0.01233,0.04823 0.02603,0.09688 0.04117,0.146177 0.01514,0.04931 0.03172,0.0993 0.04983,0.150215 0.01811,0.05092 0.03775,0.102736 0.05902,0.155817 0.02125,0.05309 0.04414,0.107386 0.0687,0.163179 0.02456,0.0558 0.05079,0.113155 0.07883,0.172233 0.02805,0.05908 0.0579,0.119891 0.0896,0.182787 0.03169,0.0629 0.06523,0.127824 0.100756,0.195098 0.03552,0.06727 0.07298,0.136893 0.112485,0.209101 0.03951,0.07221 0.08105,0.147045 0.124707,0.224738 0.06395,0.110373 0.12747,0.218614 0.190476,0.324728 0.063,0.10611 0.12555,0.210119 0.187612,0.312027 0.06207,0.101911 0.123617,0.201698 0.184741,0.299454 0.06112,0.09775 0.121762,0.19342 0.181938,0.287076 0.06017,0.09366 0.119894,0.185283 0.179133,0.274895 0.05924,0.08961 0.118038,0.177222 0.176331,0.262845 0.05829,0.08562 0.116105,0.1693 0.173462,0.250989 0.05736,0.08169 0.114249,0.161388 0.170659,0.239198 0.05641,0.0778 0.112317,0.153684 0.167791,0.227668 0.05546,0.07399 0.110513,0.14612 0.165051,0.216333 0.05453,0.07022 0.108593,0.138504 0.162182,0.204999 0.0027,0.0033 0.0053,0.0064 0.008,0.0098 C 8.00872,14.732698 7.120877,13.942779 6.658257,13.361473 5.598455,12.029821 5.055189,10.452858 5.217346,9.1790022 5.365603,8.0143522 5.882,7.0633652 6.841532,6.1881132 7.079748,5.9708212 7.106058,5.9999142 7.175394,6.0213522 7.413036,6.1657852 7.440231,6.1534602 7.2799,6.2422692 Z"
+           sodipodi:nodetypes="cssscssssssssssssssssssssssssssssssssssscsssssssssssccsscc" />
+        <path
+           style="fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:0.3;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+           d="m 11.577603,11.492866 c 0.322335,-0.01399 0.357232,0.03938 0.593611,-0.181212 0.221983,-0.240393 0.274315,-0.351823 0.276651,-0.568905 0.0025,-0.225226 -0.0071,-0.251559 -0.197844,-0.709979 C 11.842608,9.0532972 11.354715,8.0707792 11.126601,7.7704282 10.78476,7.3203352 10.29549,6.9914762 9.74677,6.8222662 9.333842,6.6949312 8.625467,6.7136062 8.182495,6.8635162 6.779112,7.3384312 6.027958,8.8521302 6.491449,10.27125 c 0.271605,0.831606 0.705445,1.281983 1.214295,1.281983 z"
+           id="path3272-6-9"
+           sodipodi:nodetypes="ccsssssscc" />
+        <path
+           style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0.3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke;stop-color:#000000"
+           id="path16756-1-4"
+           sodipodi:type="arc"
+           sodipodi:cx="-12.009243"
+           sodipodi:cy="12.377947"
+           sodipodi:rx="0.19409445"
+           sodipodi:ry="0.19840427"
+           sodipodi:start="0.57990659"
+           sodipodi:end="0.54828965"
+           sodipodi:arc-type="arc"
+           sodipodi:open="true"
+           transform="scale(-1,1)"
+           d="m -11.84688,12.486662 a 0.19409445,0.19840427 0 0 1 -0.26743,0.05811 0.19409445,0.19840427 0 0 1 -0.05896,-0.2729 0.19409445,0.19840427 0 0 1 0.266498,-0.06242 0.19409445,0.19840427 0 0 1 0.06317,0.271913" />
+        <path
+           id="rect20090-0-9"
+           style="fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:0.3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke;stop-color:#000000"
+           d="m 11.857757,10.952689 -3.825845,0.06844 c -0.144962,0.0026 -0.261704,0.119311 -0.261704,0.267512 0,0.148204 0.11674,0.270106 0.261704,0.267514 l 3.825845,-0.06844 c 0.144962,-0.0026 0.261704,-0.119312 0.261704,-0.267515 0,-0.148201 -0.116741,-0.270105 -0.261704,-0.267511 z"
+           sodipodi:nodetypes="sssssss" />
+        <path
+           style="fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:0.4;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+           d="m 12.18194,17.216199 c 0.624312,-0.131607 1.333297,-0.60414 1.680554,-1.120063 0.529917,-0.787312 0.660533,-1.614976 0.412007,-2.610703 -0.445153,-1.783509 -1.737094,-5.0026358 -2.421826,-6.0344618 -0.48947,-0.737586 -1.146722,-1.225567 -1.985164,-1.473899 -0.284388,-0.08423 -0.549789,-0.118307 -0.970098,-0.126588 -0.24378,-0.0048 -0.652706,0.03674 -1.040841,0.17381 -0.485591,0.168758 -0.640212,0.230083 -0.950323,0.458278 -0.405441,0.295742 -0.788291,0.729017 -1.012688,1.175025 -0.289449,0.575304 -0.391516,0.937594 -0.418687,1.546511 -0.04019,0.9006898 0.117467,1.4116638 0.816102,2.6547258 1.00275,1.681382 1.225081,2.020621 2.371836,3.434141 0.232245,0.299614 0.629643,0.75695 0.883104,1.016312 0.372595,0.381249 0.523321,0.502467 0.787118,0.633023 0.598301,0.296089 1.270931,0.395727 1.848906,0.273889 z"
+           id="path5762-6-0"
+           sodipodi:nodetypes="ssssssccssccsss" />
+      </g>
+    </g>
+  </g>
+  <g
+     inkscape:groupmode="layer"
+     id="layer5"
+     inkscape:label="Original"
+     style="display:none">
+    <image
+       y="0.028144173"
+       x="0.013794855"
+       id="image1291"
+       xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJAAAACQCAYAAADnRuK4AAAABHNCSVQICAgIfAhkiAAAAAlwSFlz AABO4wAATuMBYdZxSQAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAAZdEVY dFRpdGxlAFR3ZWxmIEljb24gVGVtcGxhdGXyQOONAAAADHRFWHRBdXRob3IAeHBobnh/4OzMAAAA Y3RFWHRDb3B5cmlnaHQAQ0MgQXR0cmlidXRpb24tTm9uQ29tbWVyY2lhbC1TaGFyZUFsaWtlIGh0 dHA6Ly9jcmVhdGl2ZWNvbW1vbnMub3JnL2xpY2Vuc2VzL2J5LW5jLXNhLzQuMC9D0yJcAAANW0lE QVR4nO2dS4xc2VnH/98591VVXd3TbntC92ggwZ5R1BWRRZMoIQG1UIQIs0BCcpINESyyQIqySlgl 2I6yQ2xmtpEQilgQI4EUiAQaQQtCFAiNQjLVUWbsjII19iS2u931vI9zzseiqtxP21V9b7vrlr/f pp/33HPr/O53vvO4VYAgCIIgCIIgCIIgCILwLEBnUz6f8mmfZR7ZpKfyop+GQPvKHNX52mmLKjyS K8NGONAEhclUdMMOy2PsSdMcfr08+LK+JTKdNhurQ0GuD3/RGP58hfc1eSESeUUUMuSQPE0CLgPr ILRv0xp2EMd3CHf3DkiS+yJTQYTh0p4QjS1E0TJvYhGorzA2VjGQ6RoAHklEKECiIhuQDsiz3qC1 9jLF8R1KknNkzDYZs0vAi3CuJeKcEkrNM3ALnrfAnneOw3Cbo2iZN+t3GBtNHkSjh5FoagQ6Ik/j LlSSnKM4NirLbit3fo7Y9AhYgrP9vTjq+iJTTkhVHoqgdIWB+yCvyupeh31/xUWR58Jwm5sX4Abd 21ZhEhUo0NUD8nS7UGkKlS1C2QRq3kA5F5KrJ8Q2IaAOdqnIUxCkAgbaIB2yaoesVMItD06HcP4O XBDA1WpwA4lGkegqYwoEok++ygE/3/+CJv6MVrjIwEIB5QpFw9xyUG9ZuL+Jt+6+tnH1vUneKJRX IPrEN/nFiHrfJqBhHJBYwLqcpQqnglZAqAFPAc7xG0miX3n9s9GtPCOzkwpEAPDJVznQy73NxKBx p8PoZjJBWAbmAsJyjeArfqP747sf2rj6V+mhIf6IJzaomvDchIcJM4Of73+hm6Lx0wdO5CkRnZRx 84FDz9AHgpeWPj+Ycrk2zGOv0j5vnhhgJhHo0DzPNXJwn77VcnDiTulwDNxqOZDWn8LaosI6FLBK B2Vi4GHQOJ5xJxKPThKuN+h+D5esyFNaLAPbfXfp0m6owhgcre0MJx8xnDN68sTjBDPRI3lWCeug xl2oTsbVgq5FOCNaKeba7dCL43nX7d7hS17IYQxurjccNpo4JNERxunC6KA8Ww/neTJb4JUIZ0Jm gbSmvbj2rtefr+leb0l3u1CNu1BYb+zr0h52ZwcYMwcadVt78vT7HS29V/lhANVAedX+olfrWC+t 93SaDtr4oETH8ySB9mXkl7HWXqbRDLN97sKkIzhhGmGmNA79xE/8JHDeXFLVab2i03SwFLXWvj1Y FN+LQgcYX4L1LYrjO2TMLmWLUHNJXxd4GcIZ4uvMDzx4WdLxsijQc0lfZ4tQcfy2iuMVOrQF50A0 GiOJHnZf7UVKkpCcWyKbQJkalF/whQhng/XIQwYKgwBpljIZsE2gnFtwgy03Sxh2Y0dC0JgR6DLW sAZjlsier9G8gXLZxJOQwpTiZ/Csb7Q1mY5Mql0V6jkXkT3fIWOWht0YcNzO0oklcLZLzsVkLRSY ZTV9BnAM5RtPs4NyUUDOBeRcTM7ODdt37ZHHji3QKP9h0yPnIpKtGLMDe1BgQy4g5SwUc0rOxYfa 9/Kxx55gS+sSsNAHerUTVFWYRpg9YoDAIOaM2PEgSJgeGZNRHIOAxWOPHTMCjbLwF/edNJMINDNY YjYUPvx5/OBwghyoP9xRKMwWIdgZAioTBQcZSQkD2J4oKIhAQi5EICEXRT5Y+FR56ZzC5z7o46Mv eHi+RvBLditkDvhFl/Hddwy+/oMMb+2UcyN5KQX67Ad8fOXjIXSJU3lfAS/UCZff7+MPXvZx9TsJ /rqZnXW1JqZk9y3wykUPV36z3PIcRivgq78V4nd/tXz3c6kECjTw5Y+Fp/6eNGcBAfjKx8LSdcWl qu5HVjTeU5tFfQYszxE+vFKuXTKlEuilc6Wq7ol4uWTXWKra1vzZjT4j5kp2jaUSSJg+RCAhFyKQ kIvyTTw8ht2kHA8aLYSPznNePqfwexc97MSM/3nXIpnyZ+9mSqBf/8supv1RawJw80/mHvn3Vy55 eOXSoFluPnD4o3/o45329F6UdGFTzMXnFL78G+GT//EMEYGeMnrCV/xDy9M9sSgCPWUmXaqoTPm8 kAgk5EIEespM+mZcjqc3gQZmbBR2HH/+2xF+7fmzuU9++AuHL/1LfOB36YTD8p9sT/dGs5kX6MU6 4aXFsxFop58vemQO+Iv/TAuqzekw8wKVjTe3HW7sOPxfy+Hv3zR4UyKQMAn/eMPgtc3pjjr7kSRa yIUIJORCBBJyIQIJuRCBhFyIQEIuRCAhFyKQkAsRSMiFCCTkQgQSciECCbkQgYRciEBCLkQgIRci kJALEUjIhQgk5EIEEnIhAgm5EIGEXIhAQi5EICEXIpCQCxFIyIUIJORCBBJyIQIJuRCBhFyIQEIu RCAhFyKQkAsRSMjFzL9D2a02Y/GMPhH51hR/REFRzLxAh98lVSgW6cKEXIhAQi5EICEXIpCQCxFI yMVMjcL++49rZ12FZ46ZEuhxn0UqnA7ShQm5EIGEXIhAQi5KJVAnm/21pXZarmsslUA3pvyzs4rg rTNa+D0ppRLoe7ct3u2W6w6dhDsdxn/dnvAzMc+YUgmUWuBr/5FgFhViAF/9ToKsXAGoXAIBwLdv Glz99wS2ZC/047AO+LN/S/BPb5uzrsrElHIi8RtvZPjebYvPfdDHR1/w8Es1gi7ZrWAc8PMu47vv GHz9B1npcp8RpRQIAN7advjTf00AJGddlWeakt23wrQhAgm5EIGEXIhAwgDSJ5odmVggpStMOpzF qZhnnASkPAb6Ex01pkCrQ2FuTVgpoRxoJvJ4NJ4lGn9B7gRd2H1gd7KTCNMNkWGCZpBhIp9JBaxU zORV2fMWOIqWH9nWYwsURcvseQtMXnVQuApEoBmBDBzIskojRypjooCVig617/Vjjz1BDlRjpSLW Gg5EItEMoAgu87Ql1Xc6YadUykpFrHRn2L6bjz52vFNcxyY24XnnWN/rcsuDI5WIPDNC5sPozFql 4foKTvXgHqiY9b059rz7vFlfGbb1lSNtPoZAVxhoMOorHIbbrNQ86xDOj8Ny7TsQHok2bJSX2TRj 4/mB9Tw4HcIptcthuDSUpnFswBi/C9tY5UEe1GJ/B67loZyrf8IRUsMms37mh3MmSGE6YcX6O3BR 9D4XRbcZG6v75Tkg0pMEYmD0qMx1bNbvcK0GFwRwfrVjwdKLzQAchEHmJzBhqkwn7Fn94K4LArgw 3B52X6ME+uhjU2NGoGE3trHKzeg212pwldacBUsQKj3s0Df9LK5VsnYltkG7aiuVOVurwTUvwGGj OWj7Y/IfYDyBhlHoCgNbjM1PuOYFuFoNDnHaK/ZqhKcNx0k76FoTtPu21oWpVu/vk2d1nzwE4Ohm 0AmG8fsk2mhy8wKc6/duzOT+0mcFZnC//9N6/ZfN4iJMrQZ3YyHZF3m29stzLONuKGMANCiIGbgG bDTBH8+uc2t3jebnAZLHiksFM7jdBiW9v72x8IZDfWWYLDcBrDDQwCF5jg0Vk+xIPCKR+8ndV+n9 5/8QzjWoXge80m5wfLYwGbjdAZLkR+n/3nwNmztuIM0W9sQBniTPgf+YEBqV+ytf/OF7qVL5Fitq kO8D2gdkgno6YQJsBs4yENOP2HV//2df+7ufDf54RBrgMeKMyNvvEMB4z+98o1r5yIc/D8efBqmL rNR8znKFU4Act8C4odhe737/+6/9/J+/FQ+S5KsjUSa+8wsRCLhGAFSjAXXvXsdPa+e9auK81E/9 wKt4Jkt9Z632fa3ZQTFbAhQhAJidJE85IVKMFAAcE2kmBZdl1iqtrfbIZNZkfgoTV5FW491sYWHB DhJluMeNsMYhb9LCg+z5KoBVjqLXuVJZsejeQ78+xxUvQZakHEaBsybTzkJzpCmwRgEAsyfyFAE7 UGAYAFKtHSWW/TCz2oNN0tQGEZtQOWPb1nqV4faMjcVhznNyeYD8EWhYBgP4lMJ6gxp3oXZ353Wy 0NK1LrwsCrTJUh2ZVNuQlLNQQAVgQ8yZCFQQRP5AAspYaTidsIu9wHp+YP04td0aTLg7bxcWWnZv mP5Nl1egAodNDcbGKpprO3wpSZzbAXXqPTsXA2TAWTVw1kKx84k5I+aU5A3piqOLFEQBE1WZkLEL 4fwenGdT1wl7Ntipuqjqub3oU8wMXhEC7evGrgP1Bocx2Bg4tKvoLoJtAjWfQjkXk3MgnksIAAzq BZxeAIAQbQAJqANWKmZlI25Fg1X1YKe6b20LDLyOwXA9X/QBiunChuWMkukmjbqyJDlHcfy2cm6B 7PkaOdslNj1ytipd1ymhdG+wa1TXWN/rslK7HEXvc2G4zXvLEwdmmHMJVGAXdnCWurnecGvtkMJw gZPkPpkWyJjdoTjzcK4lEhWMUvMM7ALYheeBvep9DsMljqJwEHk2msOu6/HLE5NQlEDHLnVsYpWx fofQTmgNy4jjUa1bBZ1WOEgLwAKAwR72TdwG6hjmPK/j0MIoUEAeVHQUeDhDPZwbGtIk4PLw+y3C esFnFfbYAPYew7qOgzsJi5UHKF6gQ+WO6nhNuqszY/x1rZNw2g0r4kwPskApCIIgCIJQEP8P03/B 430mofMAAAAASUVORK5CYII= "
+       style="opacity:0.49074079;image-rendering:optimizeQuality"
+       preserveAspectRatio="none"
+       height="28.670942"
+       width="28.670942" />
+    <image
+       y="0.0055872202"
+       x="-0.031073861"
+       id="image3082"
+       xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJAAAACQCAYAAADnRuK4AAAABHNCSVQICAgIfAhkiAAAAAlwSFlz AABO4wAATuMBYdZxSQAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAAZdEVY dFRpdGxlAFR3ZWxmIEljb24gVGVtcGxhdGXyQOONAAAADHRFWHRBdXRob3IAeHBobnh/4OzMAAAA Y3RFWHRDb3B5cmlnaHQAQ0MgQXR0cmlidXRpb24tTm9uQ29tbWVyY2lhbC1TaGFyZUFsaWtlIGh0 dHA6Ly9jcmVhdGl2ZWNvbW1vbnMub3JnL2xpY2Vuc2VzL2J5LW5jLXNhLzQuMC9D0yJcAAAL6klE QVR4nO3dTYvcRhrA8adKJZW6Zc0LmTEmmYUF5zYOJjQLOSTQEMhlA7uHsNlPsCHg5DP4MzghHyC3 xZvTGh8CgYaQXMwEkngOZo1f2JAYz2ReLHePSqqqZy+t2Z5xj6c1pbZa7ecHxo09LdVEf0rVkjIG IIQQQgghhBBCCCEvA9bw7ZPJ4TQ2Oo0DPG6bFFJ9xoVTWUxVH1g25vXxfTAAgG63W/GuSa/XK14e DwTH/HklEVUZ0PF4ilBYkiQMACBN0yP7U0rRzFQRKeWRIMIwRACAOI6x1+uNBlRpRNMIiAEAK8JJ 05QppZjWmmmtGQCAtZbCmRLOOQIACCFQCIFSSgzDEEdCGo1oZgJ6Jp6trS2ulGJpmvI8z/nKysph QMaYw/1STO6KaAAAPM87DGh7ext937dhGFopJa6urtqqI6oyoCPx9Pt9nmUZX15e5koprrXm1lpm rWWIyOI4pngqxDnHJEmAMYacc+ScoxDCSint7u6uDYLARlFkx0RUe0Ds2rVrQRRFn3qe93fO+UVE XKxgu6R6TxDxP4j4z4cPH3529epVNfzzM0fkGhD78ssv/2CtvckYW7fWgtYaEKdyyYE4YoyBEAI4 54CIt5VSf/7oo4/+O/IlpQ/cWQNiAADXrl0LFhYWNowx60mSQJZlZ9wceZGCIIA4jsHzvNsPHjz4 09WrVzM44/UiXnLfhx/PAQCiKPo0z/P1nZ0diqdBsiyDnZ0dyPP80tra2hUYWcPC+Gt5JyoT0DPX eRhjH+7t7dEpq4EQEfb29oAx9rdOp8O73S6H8SEdj+oIMeH+xl0kZP1+/3WKp7kQEQ4ODl7f39/n aZpip9NBAICNjY1xH/MZjDmlTRpQ4chH9SzL2mcdPJkNSqlzSZKINE1tv99HIQSur68X14yKLztx lpjkFDb2ImG/3+fGGLfRk9oZYyCKIhFFkVhYWPAGg4HX7/f51tYW73a7405nR0y6BnomnoODA6+i 74HUTCklDg4OxNOnT0Ucx16WZXxMRGOddgo78sYkSVhxhXlpaansJzgygxCR+b7ve55nfd83aZqa OI4hSRIQQuDwRjjCCWugiSPodrssTVOmtWbD2xM0A80Jz/P8LMuEUkqEYegppbzl5WWepilP05QN Z6HCkUllkkU0AwCWJAlTSjFrLSvubVX6XZDaaK2F7/ssz3PgnKPWGpVS3FprRx65cZuBhjtiw7vq vN1uU0BzwvM8YYzxgiDwtNZeu93m1lpmjGFa68PnucBhEX3IGMOGG+eISHfT5wAiciGEZ609fGKi +HXaeycOqFj/aK0n3jhpBmstR0RmreVSyuI1O3/+/KnvpdMQgeGZhCEiK+Kx1h5OGMcfRR5V5jrQ uJ2SORAEAUNEFoZh6feeaQ1E8cynYllS5vjSKYwAwNnPKBQQcUIBESdlH+eYGa+++ip0Oh1YW1uD KIqAsWYty4wxkCQJ3L9/H27dugX7+/t1D+lMGhnQO++8A2+99Vbdw3DieR4sLS3Bm2++CW+88Qbc vHkT7ty5U/ewSmvcKazT6TQ+nuOEEPD+++/D2tpa3UMprVEBtVotePvtt+sexlRwzuHdd9+texil NSqgixcvQhAEdQ9jas6fPw8rKyt1D6OURgXUtP+4Z/HKK6/UPYRSGhWQ7/t1D2HqpJR1D6GURgVE Zg8FRJxQQMRJIy8kjoOIoJQ6/Qtrxhh77jrnwoULoJSCfr8Pv/76K1hrX+DoypubgAaDAXzxxRd1 D+NU586dg48//vjEv798+TJcvnwZAAAePXoE169fhzRNX9TwSqNT2Ay7cOHCzF84pYBesHa73I8T mPXbGxTQC1b2WtasX/uigIgTCugFK/upatZ//tLcfAo7yQcffAALCwu17PvevXuj//wAAAA8ffq0 1Da2trYqHFH15j6gpaUlWF5ermXf4w5+mRklyzL4/vvvqxxS5eY+oKZ59OgR7O3twc7ODty+fXvm H3WlgGbMjz/+CD/99FPdw5gYLaKJEwqIOKGAiBMKiDihgIgTCog4oYCIEwqIOKGAiBMKiDihgIgT Cog4oYCIEwqIOKGAiBMKiDihgIgTCog4oYCIEwqIOKGAiBMKiDihgIgTCog4oYCIEwqIOKGAiBMK iDihgIgTCog4oYCIEwqIOKGAiJO5/wllu7u7YIypZd9JktSy3xdp7gP66quv6h7CXKNTGHFCAREn FBBxQgERJxQQcTI3n8La7TZ88skndQ/jVIyxuodQqbkJiDEGYRjWPYyXDp3CiBMKiDihgIiTRgWU ZVndQ5g6pVTdQyilUQH9/vvvdQ9h6pr2PTYqoLt37871LPT48WPY3t6uexilNCqgNE3h22+/rXsY U2GthW+++abuYZTWqIAAAH744YeZ/3dEy8rzHG7cuAG//PJL3UMprZEXEr/77ju4d+8edDodWFtb g3PnzjXuCq8xBp48eQL379+HW7duwZMnT+oe0pk0MiAAgN9++w1u3LhR9zBeeo07hZHZQgERJxQQ cUIBEQAAYIzhWd5XOiDP8/CsOyOzjXNe+rhOGhAFM8eyLEPGGKZpCgDlZqMzn8JoFpofw2OJjDFk jCHnHDnnKIRAIQSGYXjisZ44oDAMDzdY7KCS0ZPacc7tMBybpulhRI8fPz79vWV35nkecs7R8zxL s9B8YIxZrbXhnFvP82wxQUwySZQKSAiB29vbKISw/X6f4pkTxhjteZ4ZxmMHg4EdThIohMA4jotj /cwxnyQgBACM4xillMg5Ryml9X2/np9YQConhNBZlpk8z7Xv+0YIYaWUdnisT4wHoMQM1Ov1sFgH 7e7uWiGErWT0pHZaay2lzKWUOssyLaU0u7u7NgxDG4Yh9nq90XiOhHTazVQEgMPb3HEcY5qmFgAg SRKDiI27C06egVLKfDAYWEQ0Ukq7t7dnW62WlVKOnr7GKnMdCItZKIoi22q1DCItg5oOEUFrnUdR lLdaLZMkiWm1WiaKIru6umqHs0/x6xmTroGK33FjY8Ourq7aKIpslmWDir4PUhOlVNLv93WSJKbf 7+t2u/28eJ6JqOzzQAgA0Ov1oNvt2sFgcFdK2aHTWDMhIgwGg3txHOtisRyGIW5sbIyGU9kpbPQ1 9no91FpfT5IE6FTWPIgISZKAMeZfi4uLdnV11W5ubpqNjQ0LABaejWfsQfZK7vfIVPPaa69tRFH0 V2PMed/3gXO6ud8EWmtIkgSyLPv5zp07/+j1evrBgwfPi+XEGeKs557D9125cuWPUsp/c87XhRAg RGOfkn0paK1Baw3W2p+VUn/5/PPPHw7/alwkp55aXBcvDADgvffea1+6dOkKAHzIGLvIOV9w3C6Z AmvtE0S8i4jXNzc3P/v6669TOGWRfJpKAhr+ztfX1/n29rYfRZFQSgnf9/08z4UQwjfGeL7ve9Za joiseO/wNXEwck+yuBFq8zw3nueZPM91EAR5lmW63W5naZrmi4uLZnNzc3StA3CGeADc/6+M0QuN GIYhtlot0+/3IY5jPDg4gCAIUGttpZSeMcYDABYEAQegeKpURJTnuUVElFKaLMvM8HcdhqEeXuMp Hs84dYE8iaoWLAgArLhSrbVmUkqmtTZSSuCco9YajTFWSskRERCRUUDVKQISQqBSyiKiDYLA+L5v ENG0Wi2TZZmNosg+7+Zo6f26bgCOnsZYp9Ph+/v7fDAYeHEce0opT2vN2+02N8Zway2jeKZj9IEw z/PsYDCwwxujJkkS0263zeLioh1+VHda+xSqmIHG3S9DrbVNkgSWl5dRKcWzLOPWWlYENPzaCnZP AP7/zyoUARljMAxDK6W0u7u7NgiCk+5tOc1CVc0CR2ahbrfLtra2uFKKpWnKrbXMGMNWVlaY1poZ Y2j2mZLiGZ7t7W0sHv4bhoST3p4oo+qAites2+2yJElYmqZMKcW01kxrffh11lqKqGKjTxAWjx9L KTEMQ4zjGE+4MToTAR3f1uHH9CIkAIA0TSmaF6R4EH4kHICK4wGoNqDj25vkNZmOkyKpNB6A6R3M 49ulaOpT6aL5uGkfWApndtAjE4QQQgghFfkfLnfEbGu/P8gAAAAASUVORK5CYII= "
+       style="display:inline;image-rendering:optimizeQuality"
+       preserveAspectRatio="none"
+       height="28.697369"
+       width="28.697369" />
+  </g>
+</svg>
diff --git a/app/src/main/assets/ic_device_sony_overhead.svg b/app/src/main/assets/ic_device_sony_overhead.svg
new file mode 100644
index 0000000..b4a7c5a
--- /dev/null
+++ b/app/src/main/assets/ic_device_sony_overhead.svg
@@ -0,0 +1,140 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   version="1.1"
+   id="svg2"
+   width="28.740126"
+   height="28.740126"
+   viewBox="0 0 28.740126 28.740126"
+   sodipodi:docname="ic_device_sony_overhead.svg"
+   inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:dc="http://purl.org/dc/elements/1.1/">
+  <metadata
+     id="metadata8">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs6">
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 14.370063 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="28.740126 : 14.370063 : 1"
+       inkscape:persp3d-origin="14.370063 : 9.580042 : 1"
+       id="perspective903" />
+  </defs>
+  <sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="1920"
+     inkscape:window-height="1035"
+     id="namedview4"
+     showgrid="false"
+     showguides="true"
+     inkscape:guide-bbox="true"
+     inkscape:zoom="14.060255"
+     inkscape:cx="16.038116"
+     inkscape:cy="16.820463"
+     inkscape:window-x="0"
+     inkscape:window-y="45"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="layer1"
+     inkscape:pagecheckerboard="0">
+    <inkscape:grid
+       type="xygrid"
+       id="grid972" />
+  </sodipodi:namedview>
+  <g
+     inkscape:groupmode="layer"
+     id="layer1"
+     inkscape:label="Background"
+     style="display:inline">
+    <g
+       inkscape:groupmode="layer"
+       id="layer4"
+       inkscape:label="Bottom"
+       style="display:inline"
+       sodipodi:insensitive="true">
+      <rect
+         style="display:inline;fill:#1f7fdb;fill-opacity:1;stroke:none;stroke-width:3.57115;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         id="rect817-5"
+         width="22.819572"
+         height="21.904638"
+         x="2.9239759"
+         y="3.8768768"
+         ry="0.9472276" />
+    </g>
+    <g
+       inkscape:groupmode="layer"
+       id="layer3"
+       inkscape:label="Middle"
+       style="display:inline"
+       sodipodi:insensitive="true">
+      <rect
+         style="display:inline;fill:#4dabf5;fill-opacity:1;stroke:none;stroke-width:3.57115;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         id="rect817-7"
+         width="22.819572"
+         height="21.904638"
+         x="2.9317887"
+         y="3.0349438"
+         ry="0.9472276" />
+    </g>
+    <g
+       inkscape:groupmode="layer"
+       id="layer2"
+       inkscape:label="Top"
+       style="display:inline"
+       sodipodi:insensitive="true">
+      <rect
+         style="fill:#2196f3;fill-opacity:1;stroke:none;stroke-width:3.57115;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         id="rect817"
+         width="22.819572"
+         height="21.904638"
+         x="2.9238882"
+         y="3.4130721"
+         ry="0.9472276" />
+    </g>
+    <path
+       style="fill:#fffffe;fill-opacity:1;stroke:#ffffff;stroke-width:0.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       d="m 11.787869,22.120059 c 0,0 -2.0910928,-0.292225 -2.6575991,-1.224839 C 8.591619,20.070641 8.1561259,19.180865 7.631892,18.347822 7.1090996,17.567626 7.597652,16.75833 7.689044,15.972138 7.681324,14.958429 7.4045423,13.938261 7.660211,12.932578 8.0157417,10.868804 9.1613178,8.9354471 10.817587,7.6863456 c 2.688723,-1.872765 6.803457,-1.2142137 8.663673,1.5537297 1.346536,1.7828427 2.026474,4.1325137 1.553678,6.3603877 -0.08676,0.923119 0.697056,1.927541 0.06127,2.790481 -0.596062,0.923479 -1.055471,1.947842 -1.712391,2.823233 -0.755807,0.595235 -2.691274,0.772608 -2.691274,0.772608 0,0 -1.514132,-0.618389 -1.649831,-1.431339 0.04222,-0.63115 0.500806,-1.13731 0.745842,-1.700974 0.841894,-1.549407 1.744777,-3.066705 2.737174,-4.521276 0.490354,-0.443872 1.602437,0.740266 1.564181,-0.407131 0.0762,-1.293714 -0.5099,-2.515494 -1.130701,-3.606183 C 18.440464,9.4639663 17.592752,8.8615802 16.642705,8.6047311 14.609615,7.9145591 12.211932,8.1319408 10.47655,9.4725077 9.3167635,10.728865 8.5844629,12.446821 8.637239,14.181989 c 0.3008752,0.859132 1.1571611,-0.488382 1.685694,0.352971 0.605742,0.619625 0.904223,1.470703 1.405699,2.171243 0.728953,1.200475 1.437492,2.419372 1.999338,3.711723 -0.01029,0.972211 -1.940049,1.702133 -1.940049,1.702133 z"
+       id="path4466"
+       sodipodi:nodetypes="cccccccccccccccccccccccc" />
+  </g>
+  <g
+     inkscape:groupmode="layer"
+     id="layer5"
+     inkscape:label="Original"
+     style="display:none"
+     sodipodi:insensitive="true">
+    <image
+       y="0.0079936879"
+       x="0.010951031"
+       id="image4124"
+       xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGAAAABgCAMAAADVRocKAAAAA3NCSVQICAjb4U/gAAAACXBIWXMA ADFOAAAxTgGacD5yAAAAGXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAABF0RVh0 VGl0bGUAUGViYmxlIEljb26KvwD9AAAADHRFWHRBdXRob3IAeHBobnh/4OzMAAAAY3RFWHRDb3B5 cmlnaHQAQ0MgQXR0cmlidXRpb24tTm9uQ29tbWVyY2lhbC1TaGFyZUFsaWtlIGh0dHA6Ly9jcmVh dGl2ZWNvbW1vbnMub3JnL2xpY2Vuc2VzL2J5LW5jLXNhLzMuMC/eBBrlAAABy1BMVEUAAAAAAAAA AP8AAIAAAFUAAKoAVaoAAIAAQIAAM2YAM5kzM2YzM5krK4AkJG0kJJIgIIAcHHEcHI4aGoAXF3QX F4sXLnQXLosVK4AUJ3YUJ4kSJIAkJIARInciInciIogQIIAgIIAeHngeHoccHIAcK4AbKHkbKIYa GoAaJoAYJHkXI4AXI4sWIXoWIYUVIIAfH3ofH4UdJ4ATHIQcJnsbJIAaI4QaIoAZIXsZIYQYIHgY IIAXH4MXJ3wXHoAXJoBLpfAWJHwdJHwdJIMcI4Acas0VIoMbIoAaIXwaIIAZH3wZH4MZJXwYHoAY HoYYJIAehtsYHoIefdtHoO0jetZGovNEn+4gg+EfgN5Ep/VKqfREpvQgfdkgfttKqvQggd4hjesw nPMwnPRJqfUhgd4hjutJqfUgfttNq/VNq/QhlPEhlPIhlvMkmPMglvIff9sgjeoglPAhk/AhlPEh lPIhlvMkl/MkmPMlmPMmmPMnmfMpmvMzn/Q0n/Q1n/Q2oPRIqfVNq/VasfZbsvZhtPZitfdtuveQ yvmXzvmj0/qk1Pqu2fu83/vR6f3T6v3Z7f3i8f3j8v3l8/7m8/7p9f7q9f7x+P7y+f7////ougeB AAAAb3RSTlMAAQECAwMDBAQFBQUFBgcHCAkJCgsLCwsMDQ0ODg8PDxAQERESEhMTFBQVFhYXFxgZ GRobGxwdHh8fICAhISIiIiMjIyQkJSYnKCkpKSoqKiorKyssLC2xs7O0tba2tu7u7u7u7+/w8fHy +/v7+/ywJhX9AAAC0klEQVRo3u2a+VfTQBCAm00iW0nSTVNKbKQYbIMUC6jU4q2IB2rF+74vPEAR LSjIIXgAAiqef65py6MNJe17y1RF8/26L/N1N9O8ZGZcLof/DiYFoiV1cZHwiGVZjh7zalRAYYbn sFsQ6RFwGccyBeJjwSMrPn8lHX6fQqRy3tbAsFiU1So9FA4bNIRD63XNTwTO5pSYc9fuvwbgwaW9 SxqYs/eGBgcAGBy+fRrlpRPDsJdfJoEYuMgvzibz/vK3kmDcFMuxNZvM/BG74QTdFV6PJZsYlhe9 XXCCrlCVStwcyhG45QCkoCmiV0o4uwXEib4aQMHjbc0RTXYvCMx/mGdNHaSgraWxRhEXzsgUEC0C KWjfszlUYRWsrYcUHGttNlSRzxUEo5CCjrZYrSqVVlD3Nwj6vqcZsovzKrPeTy3o/5lmxE4wnFl/ 4QhKLvjy2YY5IEExHMH0B1umQQSj9o+EUUewIBh/m8c4qOBbfu58dQRLCmYnJmZLKphKJicdwR8X vJsymSmhIM37FSv4OJlhxnlUOAJHAC8Ye5PHmPNe9PsEJX/5dT5AigvmPtkw53wnr3xB34809uWc zDp9OWflV7xgBaBlzY79MWORALhuui+vbhoArfwe3rUp5MtWfl2Il1QDUtAaj+rKakv1XdEhBfGm Wo1kq+/pM1Ih+wdRI6iIq3IFvEAewgke6QHFg1mU26PjhbtwghsKEbG1X8ew3PmnUPGfXRAwv7hr yqAD13t6nwPQ23PnBMeivG4gg85c7XwCQOeVU+avZ5bslcrrNm7dfehIInGSguOJo+0Hd27ZoOam j3ULvOjTI02x+PYddLTEY42GJpfnpo/L0g50k8pqI1IfbWikoSFaXxfSFKnMrqFsZpKbKKoWDFbT EdQCqlfCdhtIG7AoESLTQogk4EJd/VRTn8fLgecKTg3MjyUsA4SKDD7MT1bQ8++NmvwCRmydh2Fg UvEAAAAASUVORK5CYII= "
+       style="opacity:0.49074079;image-rendering:optimizeQuality"
+       preserveAspectRatio="none"
+       height="28.710279"
+       width="28.710279" />
+  </g>
+</svg>
diff --git a/app/src/main/assets/ic_device_sony_overhead_disabled.svg b/app/src/main/assets/ic_device_sony_overhead_disabled.svg
new file mode 100644
index 0000000..3cd7f09
--- /dev/null
+++ b/app/src/main/assets/ic_device_sony_overhead_disabled.svg
@@ -0,0 +1,192 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   version="1.1"
+   id="svg2"
+   width="28.740126"
+   height="28.740126"
+   viewBox="0 0 28.740126 28.740126"
+   sodipodi:docname="ic_device_sony_overhead_disabled.svg"
+   inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:android="http://schemas.android.com/apk/res/android">
+  <metadata
+     id="metadata8">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs6">
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 14.370063 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="28.740126 : 14.370063 : 1"
+       inkscape:persp3d-origin="14.370063 : 9.580042 : 1"
+       id="perspective903" />
+  </defs>
+  <sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="1920"
+     inkscape:window-height="1035"
+     id="namedview4"
+     showgrid="false"
+     showguides="true"
+     inkscape:guide-bbox="true"
+     inkscape:zoom="16"
+     inkscape:cx="7.34375"
+     inkscape:cy="7.65625"
+     inkscape:window-x="0"
+     inkscape:window-y="45"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="layer6"
+     inkscape:pagecheckerboard="0">
+    <inkscape:grid
+       type="xygrid"
+       id="grid972" />
+  </sodipodi:namedview>
+  <g
+     inkscape:groupmode="layer"
+     id="layer1"
+     inkscape:label="Background"
+     style="display:inline">
+    <g
+       inkscape:groupmode="layer"
+       id="layer4"
+       inkscape:label="Bottom"
+       style="display:inline"
+       sodipodi:insensitive="true">
+      <rect
+         style="display:inline;fill:#7a7a7a;fill-opacity:1;stroke:none;stroke-width:3.57115173;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         id="rect817-5"
+         width="22.819572"
+         height="21.904638"
+         x="2.9239759"
+         y="3.8768768"
+         ry="0.9472276" />
+    </g>
+    <g
+       inkscape:groupmode="layer"
+       id="layer3"
+       inkscape:label="Middle"
+       style="display:inline"
+       sodipodi:insensitive="true">
+      <rect
+         style="display:inline;fill:#9f9f9f;fill-opacity:0.94117647;stroke:none;stroke-width:3.57115173;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         id="rect817-7"
+         width="22.819572"
+         height="21.904638"
+         x="2.9317887"
+         y="3.0349438"
+         ry="0.9472276" />
+    </g>
+    <g
+       inkscape:groupmode="layer"
+       id="layer2"
+       inkscape:label="Top"
+       style="display:inline"
+       sodipodi:insensitive="true">
+      <rect
+         style="fill:#8a8a8a;fill-opacity:1;stroke:none;stroke-width:3.57115173;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         id="rect817"
+         width="22.819572"
+         height="21.904638"
+         x="2.9238882"
+         y="3.4130721"
+         ry="0.9472276" />
+    </g>
+  </g>
+  <g
+     inkscape:groupmode="layer"
+     id="layer6"
+     inkscape:label="Foreground"
+     style="display:inline">
+    <g
+       inkscape:groupmode="layer"
+       id="layer7"
+       inkscape:label="Backplane"
+       style="display:inline" />
+    <g
+       inkscape:groupmode="layer"
+       id="layer8"
+       inkscape:label="Frontplane"
+       style="display:inline">
+      <g
+         inkscape:groupmode="layer"
+         id="layer9"
+         inkscape:label="Base"
+         style="display:inline" />
+      <g
+         inkscape:groupmode="layer"
+         id="layer10"
+         inkscape:label="Top"
+         style="display:inline" />
+      <g
+         inkscape:groupmode="layer"
+         id="layer11"
+         inkscape:label="Toptop"
+         style="display:inline" />
+      <g
+         inkscape:groupmode="layer"
+         id="layer12"
+         inkscape:label="Toptoptop"
+         style="display:inline" />
+    </g>
+    <path
+       inkscape:connector-curvature="0"
+       style="fill:#8a8a8a;fill-opacity:0.94117647"
+       d="m 12.48275,10.865376 h 3.907 v 6.978 h -3.907 z"
+       android:strokeWidth="1"
+       id="path2735" />
+    <path
+       style="display:inline;fill:#fffffe;fill-opacity:1;stroke:#ffffff;stroke-width:0.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       d="m 11.787869,22.120059 c 0,0 -2.0910935,-0.292225 -2.6575995,-1.224839 -0.538651,-0.824579 -0.974144,-1.714355 -1.498378,-2.547398 -0.522792,-0.780196 -0.03424,-1.589492 0.05715,-2.375684 -0.0077,-1.013709 -0.284501,-2.033877 -0.02883,-3.03956 0.355531,-2.063774 1.501107,-3.99713 3.1573765,-5.246232 2.688723,-1.872765 6.803457,-1.2142137 8.663673,1.55373 1.346536,1.782842 2.026474,4.132513 1.553678,6.360387 -0.08676,0.923119 0.697056,1.927541 0.06127,2.790481 -0.596062,0.923479 -1.055471,1.947842 -1.712391,2.823233 -0.755807,0.595235 -2.691274,0.772608 -2.691274,0.772608 0,0 -1.514132,-0.618389 -1.649831,-1.431339 0.04222,-0.63115 0.500806,-1.13731 0.745842,-1.700974 0.841894,-1.549407 1.744777,-3.066705 2.737174,-4.521276 0.490354,-0.443872 1.602437,0.740266 1.564181,-0.407131 0.0762,-1.293714 -0.5099,-2.515494 -1.130701,-3.606183 -0.518744,-0.855915 -1.366456,-1.458301 -2.316503,-1.71515 -2.03309,-0.690172 -4.430773,-0.472791 -6.166155,0.867776 -1.1597865,1.256357 -1.8920875,2.974313 -1.8393115,4.709481 0.300875,0.859132 1.157161,-0.488382 1.6856945,0.352971 0.605742,0.619625 0.904223,1.470703 1.405699,2.171243 0.728953,1.200475 1.437492,2.419372 1.999338,3.711723 -0.01029,0.972211 -1.940049,1.702133 -1.940049,1.702133 z"
+       id="path4466"
+       sodipodi:nodetypes="cccccccccccccccccccccccc" />
+  </g>
+  <g
+     inkscape:groupmode="layer"
+     id="layer5"
+     inkscape:label="Original"
+     style="display:none">
+    <image
+       y="0.028144173"
+       x="0.013794855"
+       id="image1291"
+       xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJAAAACQCAYAAADnRuK4AAAABHNCSVQICAgIfAhkiAAAAAlwSFlz AABO4wAATuMBYdZxSQAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAAZdEVY dFRpdGxlAFR3ZWxmIEljb24gVGVtcGxhdGXyQOONAAAADHRFWHRBdXRob3IAeHBobnh/4OzMAAAA Y3RFWHRDb3B5cmlnaHQAQ0MgQXR0cmlidXRpb24tTm9uQ29tbWVyY2lhbC1TaGFyZUFsaWtlIGh0 dHA6Ly9jcmVhdGl2ZWNvbW1vbnMub3JnL2xpY2Vuc2VzL2J5LW5jLXNhLzQuMC9D0yJcAAANW0lE QVR4nO2dS4xc2VnH/98591VVXd3TbntC92ggwZ5R1BWRRZMoIQG1UIQIs0BCcpINESyyQIqySlgl 2I6yQ2xmtpEQilgQI4EUiAQaQQtCFAiNQjLVUWbsjII19iS2u931vI9zzseiqtxP21V9b7vrlr/f pp/33HPr/O53vvO4VYAgCIIgCIIgCIIgCILwLEBnUz6f8mmfZR7ZpKfyop+GQPvKHNX52mmLKjyS K8NGONAEhclUdMMOy2PsSdMcfr08+LK+JTKdNhurQ0GuD3/RGP58hfc1eSESeUUUMuSQPE0CLgPr ILRv0xp2EMd3CHf3DkiS+yJTQYTh0p4QjS1E0TJvYhGorzA2VjGQ6RoAHklEKECiIhuQDsiz3qC1 9jLF8R1KknNkzDYZs0vAi3CuJeKcEkrNM3ALnrfAnneOw3Cbo2iZN+t3GBtNHkSjh5FoagQ6Ik/j LlSSnKM4NirLbit3fo7Y9AhYgrP9vTjq+iJTTkhVHoqgdIWB+yCvyupeh31/xUWR58Jwm5sX4Abd 21ZhEhUo0NUD8nS7UGkKlS1C2QRq3kA5F5KrJ8Q2IaAOdqnIUxCkAgbaIB2yaoesVMItD06HcP4O XBDA1WpwA4lGkegqYwoEok++ygE/3/+CJv6MVrjIwEIB5QpFw9xyUG9ZuL+Jt+6+tnH1vUneKJRX IPrEN/nFiHrfJqBhHJBYwLqcpQqnglZAqAFPAc7xG0miX3n9s9GtPCOzkwpEAPDJVznQy73NxKBx p8PoZjJBWAbmAsJyjeArfqP747sf2rj6V+mhIf6IJzaomvDchIcJM4Of73+hm6Lx0wdO5CkRnZRx 84FDz9AHgpeWPj+Ycrk2zGOv0j5vnhhgJhHo0DzPNXJwn77VcnDiTulwDNxqOZDWn8LaosI6FLBK B2Vi4GHQOJ5xJxKPThKuN+h+D5esyFNaLAPbfXfp0m6owhgcre0MJx8xnDN68sTjBDPRI3lWCeug xl2oTsbVgq5FOCNaKeba7dCL43nX7d7hS17IYQxurjccNpo4JNERxunC6KA8Ww/neTJb4JUIZ0Jm gbSmvbj2rtefr+leb0l3u1CNu1BYb+zr0h52ZwcYMwcadVt78vT7HS29V/lhANVAedX+olfrWC+t 93SaDtr4oETH8ySB9mXkl7HWXqbRDLN97sKkIzhhGmGmNA79xE/8JHDeXFLVab2i03SwFLXWvj1Y FN+LQgcYX4L1LYrjO2TMLmWLUHNJXxd4GcIZ4uvMDzx4WdLxsijQc0lfZ4tQcfy2iuMVOrQF50A0 GiOJHnZf7UVKkpCcWyKbQJkalF/whQhng/XIQwYKgwBpljIZsE2gnFtwgy03Sxh2Y0dC0JgR6DLW sAZjlsier9G8gXLZxJOQwpTiZ/Csb7Q1mY5Mql0V6jkXkT3fIWOWht0YcNzO0oklcLZLzsVkLRSY ZTV9BnAM5RtPs4NyUUDOBeRcTM7ODdt37ZHHji3QKP9h0yPnIpKtGLMDe1BgQy4g5SwUc0rOxYfa 9/Kxx55gS+sSsNAHerUTVFWYRpg9YoDAIOaM2PEgSJgeGZNRHIOAxWOPHTMCjbLwF/edNJMINDNY YjYUPvx5/OBwghyoP9xRKMwWIdgZAioTBQcZSQkD2J4oKIhAQi5EICEXRT5Y+FR56ZzC5z7o46Mv eHi+RvBLditkDvhFl/Hddwy+/oMMb+2UcyN5KQX67Ad8fOXjIXSJU3lfAS/UCZff7+MPXvZx9TsJ /rqZnXW1JqZk9y3wykUPV36z3PIcRivgq78V4nd/tXz3c6kECjTw5Y+Fp/6eNGcBAfjKx8LSdcWl qu5HVjTeU5tFfQYszxE+vFKuXTKlEuilc6Wq7ol4uWTXWKra1vzZjT4j5kp2jaUSSJg+RCAhFyKQ kIvyTTw8ht2kHA8aLYSPznNePqfwexc97MSM/3nXIpnyZ+9mSqBf/8supv1RawJw80/mHvn3Vy55 eOXSoFluPnD4o3/o45329F6UdGFTzMXnFL78G+GT//EMEYGeMnrCV/xDy9M9sSgCPWUmXaqoTPm8 kAgk5EIEespM+mZcjqc3gQZmbBR2HH/+2xF+7fmzuU9++AuHL/1LfOB36YTD8p9sT/dGs5kX6MU6 4aXFsxFop58vemQO+Iv/TAuqzekw8wKVjTe3HW7sOPxfy+Hv3zR4UyKQMAn/eMPgtc3pjjr7kSRa yIUIJORCBBJyIQIJuRCBhFyIQEIuRCAhFyKQkAsRSMiFCCTkQgQSciECCbkQgYRciEBCLkQgIRci kJALEUjIhQgk5EIEEnIhAgm5EIGEXIhAQi5EICEXIpCQCxFIyIUIJORCBBJyIQIJuRCBhFyIQEIu RCAhFyKQkAsRSMjFzL9D2a02Y/GMPhH51hR/REFRzLxAh98lVSgW6cKEXIhAQi5EICEXIpCQCxFI yMVMjcL++49rZ12FZ46ZEuhxn0UqnA7ShQm5EIGEXIhAQi5KJVAnm/21pXZarmsslUA3pvyzs4rg rTNa+D0ppRLoe7ct3u2W6w6dhDsdxn/dnvAzMc+YUgmUWuBr/5FgFhViAF/9ToKsXAGoXAIBwLdv Glz99wS2ZC/047AO+LN/S/BPb5uzrsrElHIi8RtvZPjebYvPfdDHR1/w8Es1gi7ZrWAc8PMu47vv GHz9B1npcp8RpRQIAN7advjTf00AJGddlWeakt23wrQhAgm5EIGEXIhAwgDSJ5odmVggpStMOpzF qZhnnASkPAb6Ex01pkCrQ2FuTVgpoRxoJvJ4NJ4lGn9B7gRd2H1gd7KTCNMNkWGCZpBhIp9JBaxU zORV2fMWOIqWH9nWYwsURcvseQtMXnVQuApEoBmBDBzIskojRypjooCVig617/Vjjz1BDlRjpSLW Gg5EItEMoAgu87Ql1Xc6YadUykpFrHRn2L6bjz52vFNcxyY24XnnWN/rcsuDI5WIPDNC5sPozFql 4foKTvXgHqiY9b059rz7vFlfGbb1lSNtPoZAVxhoMOorHIbbrNQ86xDOj8Ny7TsQHok2bJSX2TRj 4/mB9Tw4HcIptcthuDSUpnFswBi/C9tY5UEe1GJ/B67loZyrf8IRUsMms37mh3MmSGE6YcX6O3BR 9D4XRbcZG6v75Tkg0pMEYmD0qMx1bNbvcK0GFwRwfrVjwdKLzQAchEHmJzBhqkwn7Fn94K4LArgw 3B52X6ME+uhjU2NGoGE3trHKzeg212pwldacBUsQKj3s0Df9LK5VsnYltkG7aiuVOVurwTUvwGGj OWj7Y/IfYDyBhlHoCgNbjM1PuOYFuFoNDnHaK/ZqhKcNx0k76FoTtPu21oWpVu/vk2d1nzwE4Ohm 0AmG8fsk2mhy8wKc6/duzOT+0mcFZnC//9N6/ZfN4iJMrQZ3YyHZF3m29stzLONuKGMANCiIGbgG bDTBH8+uc2t3jebnAZLHiksFM7jdBiW9v72x8IZDfWWYLDcBrDDQwCF5jg0Vk+xIPCKR+8ndV+n9 5/8QzjWoXge80m5wfLYwGbjdAZLkR+n/3nwNmztuIM0W9sQBniTPgf+YEBqV+ytf/OF7qVL5Fitq kO8D2gdkgno6YQJsBs4yENOP2HV//2df+7ufDf54RBrgMeKMyNvvEMB4z+98o1r5yIc/D8efBqmL rNR8znKFU4Act8C4odhe737/+6/9/J+/FQ+S5KsjUSa+8wsRCLhGAFSjAXXvXsdPa+e9auK81E/9 wKt4Jkt9Z632fa3ZQTFbAhQhAJidJE85IVKMFAAcE2kmBZdl1iqtrfbIZNZkfgoTV5FW491sYWHB DhJluMeNsMYhb9LCg+z5KoBVjqLXuVJZsejeQ78+xxUvQZakHEaBsybTzkJzpCmwRgEAsyfyFAE7 UGAYAFKtHSWW/TCz2oNN0tQGEZtQOWPb1nqV4faMjcVhznNyeYD8EWhYBgP4lMJ6gxp3oXZ353Wy 0NK1LrwsCrTJUh2ZVNuQlLNQQAVgQ8yZCFQQRP5AAspYaTidsIu9wHp+YP04td0aTLg7bxcWWnZv mP5Nl1egAodNDcbGKpprO3wpSZzbAXXqPTsXA2TAWTVw1kKx84k5I+aU5A3piqOLFEQBE1WZkLEL 4fwenGdT1wl7Ntipuqjqub3oU8wMXhEC7evGrgP1Bocx2Bg4tKvoLoJtAjWfQjkXk3MgnksIAAzq BZxeAIAQbQAJqANWKmZlI25Fg1X1YKe6b20LDLyOwXA9X/QBiunChuWMkukmjbqyJDlHcfy2cm6B 7PkaOdslNj1ytipd1ymhdG+wa1TXWN/rslK7HEXvc2G4zXvLEwdmmHMJVGAXdnCWurnecGvtkMJw gZPkPpkWyJjdoTjzcK4lEhWMUvMM7ALYheeBvep9DsMljqJwEHk2msOu6/HLE5NQlEDHLnVsYpWx fofQTmgNy4jjUa1bBZ1WOEgLwAKAwR72TdwG6hjmPK/j0MIoUEAeVHQUeDhDPZwbGtIk4PLw+y3C esFnFfbYAPYew7qOgzsJi5UHKF6gQ+WO6nhNuqszY/x1rZNw2g0r4kwPskApCIIgCIJQEP8P03/B 430mofMAAAAASUVORK5CYII= "
+       style="opacity:0.49074079;image-rendering:optimizeQuality"
+       preserveAspectRatio="none"
+       height="28.670942"
+       width="28.670942" />
+    <image
+       y="0.0055872202"
+       x="-0.031073861"
+       id="image3082"
+       xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJAAAACQCAYAAADnRuK4AAAABHNCSVQICAgIfAhkiAAAAAlwSFlz AABO4wAATuMBYdZxSQAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAAZdEVY dFRpdGxlAFR3ZWxmIEljb24gVGVtcGxhdGXyQOONAAAADHRFWHRBdXRob3IAeHBobnh/4OzMAAAA Y3RFWHRDb3B5cmlnaHQAQ0MgQXR0cmlidXRpb24tTm9uQ29tbWVyY2lhbC1TaGFyZUFsaWtlIGh0 dHA6Ly9jcmVhdGl2ZWNvbW1vbnMub3JnL2xpY2Vuc2VzL2J5LW5jLXNhLzQuMC9D0yJcAAAL6klE QVR4nO3dTYvcRhrA8adKJZW6Zc0LmTEmmYUF5zYOJjQLOSTQEMhlA7uHsNlPsCHg5DP4MzghHyC3 xZvTGh8CgYaQXMwEkngOZo1f2JAYz2ReLHePSqqqZy+t2Z5xj6c1pbZa7ecHxo09LdVEf0rVkjIG IIQQQgghhBBCCCEvA9bw7ZPJ4TQ2Oo0DPG6bFFJ9xoVTWUxVH1g25vXxfTAAgG63W/GuSa/XK14e DwTH/HklEVUZ0PF4ilBYkiQMACBN0yP7U0rRzFQRKeWRIMIwRACAOI6x1+uNBlRpRNMIiAEAK8JJ 05QppZjWmmmtGQCAtZbCmRLOOQIACCFQCIFSSgzDEEdCGo1oZgJ6Jp6trS2ulGJpmvI8z/nKysph QMaYw/1STO6KaAAAPM87DGh7ext937dhGFopJa6urtqqI6oyoCPx9Pt9nmUZX15e5koprrXm1lpm rWWIyOI4pngqxDnHJEmAMYacc+ScoxDCSint7u6uDYLARlFkx0RUe0Ds2rVrQRRFn3qe93fO+UVE XKxgu6R6TxDxP4j4z4cPH3529epVNfzzM0fkGhD78ssv/2CtvckYW7fWgtYaEKdyyYE4YoyBEAI4 54CIt5VSf/7oo4/+O/IlpQ/cWQNiAADXrl0LFhYWNowx60mSQJZlZ9wceZGCIIA4jsHzvNsPHjz4 09WrVzM44/UiXnLfhx/PAQCiKPo0z/P1nZ0diqdBsiyDnZ0dyPP80tra2hUYWcPC+Gt5JyoT0DPX eRhjH+7t7dEpq4EQEfb29oAx9rdOp8O73S6H8SEdj+oIMeH+xl0kZP1+/3WKp7kQEQ4ODl7f39/n aZpip9NBAICNjY1xH/MZjDmlTRpQ4chH9SzL2mcdPJkNSqlzSZKINE1tv99HIQSur68X14yKLztx lpjkFDb2ImG/3+fGGLfRk9oZYyCKIhFFkVhYWPAGg4HX7/f51tYW73a7405nR0y6BnomnoODA6+i 74HUTCklDg4OxNOnT0Ucx16WZXxMRGOddgo78sYkSVhxhXlpaansJzgygxCR+b7ve55nfd83aZqa OI4hSRIQQuDwRjjCCWugiSPodrssTVOmtWbD2xM0A80Jz/P8LMuEUkqEYegppbzl5WWepilP05QN Z6HCkUllkkU0AwCWJAlTSjFrLSvubVX6XZDaaK2F7/ssz3PgnKPWGpVS3FprRx65cZuBhjtiw7vq vN1uU0BzwvM8YYzxgiDwtNZeu93m1lpmjGFa68PnucBhEX3IGMOGG+eISHfT5wAiciGEZ609fGKi +HXaeycOqFj/aK0n3jhpBmstR0RmreVSyuI1O3/+/KnvpdMQgeGZhCEiK+Kx1h5OGMcfRR5V5jrQ uJ2SORAEAUNEFoZh6feeaQ1E8cynYllS5vjSKYwAwNnPKBQQcUIBESdlH+eYGa+++ip0Oh1YW1uD KIqAsWYty4wxkCQJ3L9/H27dugX7+/t1D+lMGhnQO++8A2+99Vbdw3DieR4sLS3Bm2++CW+88Qbc vHkT7ty5U/ewSmvcKazT6TQ+nuOEEPD+++/D2tpa3UMprVEBtVotePvtt+sexlRwzuHdd9+texil NSqgixcvQhAEdQ9jas6fPw8rKyt1D6OURgXUtP+4Z/HKK6/UPYRSGhWQ7/t1D2HqpJR1D6GURgVE Zg8FRJxQQMRJIy8kjoOIoJQ6/Qtrxhh77jrnwoULoJSCfr8Pv/76K1hrX+DoypubgAaDAXzxxRd1 D+NU586dg48//vjEv798+TJcvnwZAAAePXoE169fhzRNX9TwSqNT2Ay7cOHCzF84pYBesHa73I8T mPXbGxTQC1b2WtasX/uigIgTCugFK/upatZ//tLcfAo7yQcffAALCwu17PvevXuj//wAAAA8ffq0 1Da2trYqHFH15j6gpaUlWF5ermXf4w5+mRklyzL4/vvvqxxS5eY+oKZ59OgR7O3twc7ODty+fXvm H3WlgGbMjz/+CD/99FPdw5gYLaKJEwqIOKGAiBMKiDihgIgTCog4oYCIEwqIOKGAiBMKiDihgIgT Cog4oYCIEwqIOKGAiBMKiDihgIgTCog4oYCIEwqIOKGAiBMKiDihgIgTCog4oYCIEwqIOKGAiBMK iDihgIgTCog4oYCIEwqIOKGAiJO5/wllu7u7YIypZd9JktSy3xdp7gP66quv6h7CXKNTGHFCAREn FBBxQgERJxQQcTI3n8La7TZ88skndQ/jVIyxuodQqbkJiDEGYRjWPYyXDp3CiBMKiDihgIiTRgWU ZVndQ5g6pVTdQyilUQH9/vvvdQ9h6pr2PTYqoLt37871LPT48WPY3t6uexilNCqgNE3h22+/rXsY U2GthW+++abuYZTWqIAAAH744YeZ/3dEy8rzHG7cuAG//PJL3UMprZEXEr/77ju4d+8edDodWFtb g3PnzjXuCq8xBp48eQL379+HW7duwZMnT+oe0pk0MiAAgN9++w1u3LhR9zBeeo07hZHZQgERJxQQ cUIBEQAAYIzhWd5XOiDP8/CsOyOzjXNe+rhOGhAFM8eyLEPGGKZpCgDlZqMzn8JoFpofw2OJjDFk jCHnHDnnKIRAIQSGYXjisZ44oDAMDzdY7KCS0ZPacc7tMBybpulhRI8fPz79vWV35nkecs7R8zxL s9B8YIxZrbXhnFvP82wxQUwySZQKSAiB29vbKISw/X6f4pkTxhjteZ4ZxmMHg4EdThIohMA4jotj /cwxnyQgBACM4xillMg5Ryml9X2/np9YQConhNBZlpk8z7Xv+0YIYaWUdnisT4wHoMQM1Ov1sFgH 7e7uWiGErWT0pHZaay2lzKWUOssyLaU0u7u7NgxDG4Yh9nq90XiOhHTazVQEgMPb3HEcY5qmFgAg SRKDiI27C06egVLKfDAYWEQ0Ukq7t7dnW62WlVKOnr7GKnMdCItZKIoi22q1DCItg5oOEUFrnUdR lLdaLZMkiWm1WiaKIru6umqHs0/x6xmTroGK33FjY8Ourq7aKIpslmWDir4PUhOlVNLv93WSJKbf 7+t2u/28eJ6JqOzzQAgA0Ov1oNvt2sFgcFdK2aHTWDMhIgwGg3txHOtisRyGIW5sbIyGU9kpbPQ1 9no91FpfT5IE6FTWPIgISZKAMeZfi4uLdnV11W5ubpqNjQ0LABaejWfsQfZK7vfIVPPaa69tRFH0 V2PMed/3gXO6ud8EWmtIkgSyLPv5zp07/+j1evrBgwfPi+XEGeKs557D9125cuWPUsp/c87XhRAg RGOfkn0paK1Baw3W2p+VUn/5/PPPHw7/alwkp55aXBcvDADgvffea1+6dOkKAHzIGLvIOV9w3C6Z AmvtE0S8i4jXNzc3P/v6669TOGWRfJpKAhr+ztfX1/n29rYfRZFQSgnf9/08z4UQwjfGeL7ve9Za joiseO/wNXEwck+yuBFq8zw3nueZPM91EAR5lmW63W5naZrmi4uLZnNzc3StA3CGeADc/6+M0QuN GIYhtlot0+/3IY5jPDg4gCAIUGttpZSeMcYDABYEAQegeKpURJTnuUVElFKaLMvM8HcdhqEeXuMp Hs84dYE8iaoWLAgArLhSrbVmUkqmtTZSSuCco9YajTFWSskRERCRUUDVKQISQqBSyiKiDYLA+L5v ENG0Wi2TZZmNosg+7+Zo6f26bgCOnsZYp9Ph+/v7fDAYeHEce0opT2vN2+02N8Zway2jeKZj9IEw z/PsYDCwwxujJkkS0263zeLioh1+VHda+xSqmIHG3S9DrbVNkgSWl5dRKcWzLOPWWlYENPzaCnZP AP7/zyoUARljMAxDK6W0u7u7NgiCk+5tOc1CVc0CR2ahbrfLtra2uFKKpWnKrbXMGMNWVlaY1poZ Y2j2mZLiGZ7t7W0sHv4bhoST3p4oo+qAites2+2yJElYmqZMKcW01kxrffh11lqKqGKjTxAWjx9L KTEMQ4zjGE+4MToTAR3f1uHH9CIkAIA0TSmaF6R4EH4kHICK4wGoNqDj25vkNZmOkyKpNB6A6R3M 49ulaOpT6aL5uGkfWApndtAjE4QQQgghFfkfLnfEbGu/P8gAAAAASUVORK5CYII= "
+       style="display:inline;image-rendering:optimizeQuality"
+       preserveAspectRatio="none"
+       height="28.697369"
+       width="28.697369" />
+  </g>
+</svg>
diff --git a/app/src/main/assets/ic_device_supercars.svg b/app/src/main/assets/ic_device_supercars.svg
new file mode 100644
index 0000000..596f4e1
--- /dev/null
+++ b/app/src/main/assets/ic_device_supercars.svg
@@ -0,0 +1,134 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   version="1.1"
+   id="svg2"
+   width="28.740126"
+   height="28.740126"
+   viewBox="0 0 28.740126 28.740126"
+   sodipodi:docname="ic_device_supercars.svg"
+   inkscape:version="1.2.1 (9c6d41e410, 2022-07-14)"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:dc="http://purl.org/dc/elements/1.1/">
+  <metadata
+     id="metadata8">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs6">
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 14.370063 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="28.740126 : 14.370063 : 1"
+       inkscape:persp3d-origin="14.370063 : 9.580042 : 1"
+       id="perspective903" />
+  </defs>
+  <sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="1920"
+     inkscape:window-height="1035"
+     id="namedview4"
+     showgrid="false"
+     showguides="true"
+     inkscape:guide-bbox="true"
+     inkscape:zoom="11.313709"
+     inkscape:cx="-18.826717"
+     inkscape:cy="11.357902"
+     inkscape:window-x="0"
+     inkscape:window-y="45"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="layer1"
+     inkscape:pagecheckerboard="0"
+     inkscape:showpageshadow="2"
+     inkscape:deskcolor="#d1d1d1">
+    <inkscape:grid
+       type="xygrid"
+       id="grid972" />
+  </sodipodi:namedview>
+  <g
+     inkscape:groupmode="layer"
+     id="layer1"
+     inkscape:label="Background"
+     style="display:inline">
+    <g
+       inkscape:groupmode="layer"
+       id="layer4"
+       inkscape:label="Bottom"
+       style="display:inline"
+       sodipodi:insensitive="true">
+      <rect
+         style="display:inline;fill:#1f7fdb;fill-opacity:1;stroke:none;stroke-width:3.57115;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         id="rect817-5"
+         width="22.819572"
+         height="21.904638"
+         x="2.9239759"
+         y="3.8768768"
+         ry="0.9472276" />
+    </g>
+    <g
+       inkscape:groupmode="layer"
+       id="layer3"
+       inkscape:label="Middle"
+       style="display:inline"
+       sodipodi:insensitive="true">
+      <rect
+         style="display:inline;fill:#4dabf5;fill-opacity:1;stroke:none;stroke-width:3.57115;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         id="rect817-7"
+         width="22.819572"
+         height="21.904638"
+         x="2.9317887"
+         y="3.0349438"
+         ry="0.9472276" />
+    </g>
+    <g
+       inkscape:groupmode="layer"
+       id="layer2"
+       inkscape:label="Top"
+       style="display:inline"
+       sodipodi:insensitive="true">
+      <rect
+         style="fill:#2196f3;fill-opacity:1;stroke:none;stroke-width:3.57115;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         id="rect817"
+         width="22.819572"
+         height="21.904638"
+         x="2.9238882"
+         y="3.4130721"
+         ry="0.9472276" />
+    </g>
+    <g
+       id="g8"
+       transform="matrix(0.75,0,0,0.75,5.3712747,5.3700628)"
+       style="fill:#ffffff;fill-opacity:1;stroke-width:1.33333">
+      <path
+         d="M 21.96,11.22 C 21.57,7.01 17.76,4 13.56,4 13.37,4 13.18,4.01 12.99,4.02 2,4.74 2,17.2 2,17.2 V 18 c 0,1.1 0.9,2 2,2 h 10 c 4.67,0 8.41,-4.01 7.96,-8.78 z M 5.26,11.56 C 5.83,10.27 6.54,9.21 7.4,8.37 l 3.62,1.53 c 0.6,0.25 0.98,0.83 0.98,1.48 0,0.89 -0.72,1.61 -1.61,1.61 H 4.72 c 0.15,-0.46 0.32,-0.94 0.54,-1.43 z m 13.18,4.48 C 17.3,17.29 15.68,18 14,18 H 4 V 17.2 C 4,17.18 4.01,16.28 4.24,15 h 6.15 C 12.38,15 14,13.38 14,11.39 14,9.94 13.13,8.63 11.8,8.07 L 9.3,7.01 C 10.4,6.44 11.67,6.11 13.12,6.02 13.27,6 13.42,6 13.56,6 c 3.31,0 6.13,2.37 6.41,5.41 0.16,1.72 -0.38,3.36 -1.53,4.63 z"
+         id="path6"
+         style="fill:#ffffff;fill-opacity:1;stroke-width:1.33333" />
+    </g>
+  </g>
+  <g
+     inkscape:groupmode="layer"
+     id="layer5"
+     inkscape:label="Original"
+     style="display:inline" />
+</svg>
diff --git a/app/src/main/assets/ic_device_supercars_disabled.svg b/app/src/main/assets/ic_device_supercars_disabled.svg
new file mode 100644
index 0000000..234fc3f
--- /dev/null
+++ b/app/src/main/assets/ic_device_supercars_disabled.svg
@@ -0,0 +1,179 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   version="1.1"
+   id="svg2"
+   width="28.740126"
+   height="28.740126"
+   viewBox="0 0 28.740126 28.740126"
+   sodipodi:docname="ic_device_supercars_disabled.svg"
+   inkscape:version="1.2.1 (9c6d41e410, 2022-07-14)"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:android="http://schemas.android.com/apk/res/android">
+  <metadata
+     id="metadata8">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs6">
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 14.370063 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="28.740126 : 14.370063 : 1"
+       inkscape:persp3d-origin="14.370063 : 9.580042 : 1"
+       id="perspective903" />
+  </defs>
+  <sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="1920"
+     inkscape:window-height="1035"
+     id="namedview4"
+     showgrid="false"
+     showguides="true"
+     inkscape:guide-bbox="true"
+     inkscape:zoom="16"
+     inkscape:cx="0.125"
+     inkscape:cy="7.65625"
+     inkscape:window-x="0"
+     inkscape:window-y="45"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="layer6"
+     inkscape:pagecheckerboard="0"
+     inkscape:showpageshadow="2"
+     inkscape:deskcolor="#d1d1d1">
+    <inkscape:grid
+       type="xygrid"
+       id="grid972" />
+  </sodipodi:namedview>
+  <g
+     inkscape:groupmode="layer"
+     id="layer1"
+     inkscape:label="Background"
+     style="display:inline"
+     sodipodi:insensitive="true">
+    <g
+       inkscape:groupmode="layer"
+       id="layer4"
+       inkscape:label="Bottom"
+       style="display:inline"
+       sodipodi:insensitive="true">
+      <rect
+         style="display:inline;fill:#7a7a7a;fill-opacity:1;stroke:none;stroke-width:3.57115;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         id="rect817-5"
+         width="22.819572"
+         height="21.904638"
+         x="2.9239759"
+         y="3.8768768"
+         ry="0.9472276" />
+    </g>
+    <g
+       inkscape:groupmode="layer"
+       id="layer3"
+       inkscape:label="Middle"
+       style="display:inline"
+       sodipodi:insensitive="true">
+      <rect
+         style="display:inline;fill:#9f9f9f;fill-opacity:0.941176;stroke:none;stroke-width:3.57115;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         id="rect817-7"
+         width="22.819572"
+         height="21.904638"
+         x="2.9317887"
+         y="3.0349438"
+         ry="0.9472276" />
+    </g>
+    <g
+       inkscape:groupmode="layer"
+       id="layer2"
+       inkscape:label="Top"
+       style="display:inline"
+       sodipodi:insensitive="true">
+      <rect
+         style="fill:#8a8a8a;fill-opacity:1;stroke:none;stroke-width:3.57115;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         id="rect817"
+         width="22.819572"
+         height="21.904638"
+         x="2.9238882"
+         y="3.4130721"
+         ry="0.9472276" />
+    </g>
+  </g>
+  <g
+     inkscape:groupmode="layer"
+     id="layer6"
+     inkscape:label="Foreground"
+     style="display:inline">
+    <g
+       inkscape:groupmode="layer"
+       id="layer7"
+       inkscape:label="Backplane"
+       style="display:inline" />
+    <g
+       inkscape:groupmode="layer"
+       id="layer8"
+       inkscape:label="Frontplane"
+       style="display:inline">
+      <g
+         inkscape:groupmode="layer"
+         id="layer9"
+         inkscape:label="Base"
+         style="display:inline" />
+      <g
+         inkscape:groupmode="layer"
+         id="layer10"
+         inkscape:label="Top"
+         style="display:inline" />
+      <g
+         inkscape:groupmode="layer"
+         id="layer11"
+         inkscape:label="Toptop"
+         style="display:inline" />
+      <g
+         inkscape:groupmode="layer"
+         id="layer12"
+         inkscape:label="Toptoptop"
+         style="display:inline" />
+    </g>
+    <path
+       inkscape:connector-curvature="0"
+       style="fill:#8a8a8a;fill-opacity:0.94117647"
+       d="m 12.48275,10.865376 h 3.907 v 6.978 h -3.907 z"
+       android:strokeWidth="1"
+       id="path2735" />
+    <g
+       id="g8"
+       transform="matrix(0.75,0,0,0.75,5.3712747,5.3700628)"
+       style="display:inline;fill:#ffffff;fill-opacity:1;stroke-width:1.33333">
+      <path
+         d="M 21.96,11.22 C 21.57,7.01 17.76,4 13.56,4 13.37,4 13.18,4.01 12.99,4.02 2,4.74 2,17.2 2,17.2 V 18 c 0,1.1 0.9,2 2,2 h 10 c 4.67,0 8.41,-4.01 7.96,-8.78 z M 5.26,11.56 C 5.83,10.27 6.54,9.21 7.4,8.37 l 3.62,1.53 c 0.6,0.25 0.98,0.83 0.98,1.48 0,0.89 -0.72,1.61 -1.61,1.61 H 4.72 c 0.15,-0.46 0.32,-0.94 0.54,-1.43 z m 13.18,4.48 C 17.3,17.29 15.68,18 14,18 H 4 V 17.2 C 4,17.18 4.01,16.28 4.24,15 h 6.15 C 12.38,15 14,13.38 14,11.39 14,9.94 13.13,8.63 11.8,8.07 L 9.3,7.01 C 10.4,6.44 11.67,6.11 13.12,6.02 13.27,6 13.42,6 13.56,6 c 3.31,0 6.13,2.37 6.41,5.41 0.16,1.72 -0.38,3.36 -1.53,4.63 z"
+         id="path6"
+         style="fill:#ffffff;fill-opacity:1;stroke-width:1.33333" />
+    </g>
+  </g>
+  <g
+     inkscape:groupmode="layer"
+     id="layer5"
+     inkscape:label="Original"
+     style="display:inline" />
+</svg>
diff --git a/app/src/main/assets/ic_device_unknown.svg b/app/src/main/assets/ic_device_unknown.svg
new file mode 100644
index 0000000..303eabe
--- /dev/null
+++ b/app/src/main/assets/ic_device_unknown.svg
@@ -0,0 +1,144 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   version="1.1"
+   id="svg2"
+   width="28.740126"
+   height="28.740126"
+   viewBox="0 0 28.740126 28.740126"
+   sodipodi:docname="ic_device_unknown.svg"
+   inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:dc="http://purl.org/dc/elements/1.1/">
+  <metadata
+     id="metadata8">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs6">
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 14.370063 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="28.740126 : 14.370063 : 1"
+       inkscape:persp3d-origin="14.370063 : 9.580042 : 1"
+       id="perspective903" />
+  </defs>
+  <sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="1920"
+     inkscape:window-height="1035"
+     id="namedview4"
+     showgrid="false"
+     showguides="true"
+     inkscape:guide-bbox="true"
+     inkscape:zoom="11.313709"
+     inkscape:cx="-17.89864"
+     inkscape:cy="11.269514"
+     inkscape:window-x="0"
+     inkscape:window-y="45"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="layer2"
+     inkscape:pagecheckerboard="0">
+    <inkscape:grid
+       type="xygrid"
+       id="grid972" />
+  </sodipodi:namedview>
+  <g
+     inkscape:groupmode="layer"
+     id="layer1"
+     inkscape:label="Background"
+     style="display:inline"
+     sodipodi:insensitive="true">
+    <g
+       inkscape:groupmode="layer"
+       id="layer4"
+       inkscape:label="Bottom"
+       style="display:inline"
+       sodipodi:insensitive="true">
+      <rect
+         style="display:inline;fill:#1f7fdb;fill-opacity:1;stroke:none;stroke-width:3.57115;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         id="rect817-5"
+         width="22.819572"
+         height="21.904638"
+         x="2.9239759"
+         y="3.8768768"
+         ry="0.9472276" />
+    </g>
+    <g
+       inkscape:groupmode="layer"
+       id="layer3"
+       inkscape:label="Middle"
+       style="display:inline"
+       sodipodi:insensitive="true">
+      <rect
+         style="display:inline;fill:#4dabf5;fill-opacity:1;stroke:none;stroke-width:3.57115;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         id="rect817-7"
+         width="22.819572"
+         height="21.904638"
+         x="2.9317887"
+         y="3.0349438"
+         ry="0.9472276" />
+    </g>
+    <g
+       inkscape:groupmode="layer"
+       id="layer2"
+       inkscape:label="Top"
+       style="display:inline"
+       sodipodi:insensitive="true">
+      <rect
+         style="fill:#2196f3;fill-opacity:1;stroke:none;stroke-width:3.57115;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         id="rect817"
+         width="22.819572"
+         height="21.904638"
+         x="2.9238882"
+         y="3.4130721"
+         ry="0.9472276" />
+    </g>
+    <g
+       aria-label="?"
+       id="text2312"
+       style="font-style:italic;font-size:24px;line-height:124%;font-family:Sans;-inkscape-font-specification:'Sans Italic';letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1px">
+      <path
+         d="m 15.634456,17.365391 h -4.230469 v -0.574219 q 0,-0.960937 0.386719,-1.699219 0.386718,-0.75 1.628906,-1.898437 l 0.75,-0.679688 q 0.667969,-0.609375 0.972656,-1.148437 0.316406,-0.539063 0.316406,-1.078125 0,-0.8203127 -0.5625,-1.277344 -0.5625,-0.46875 -1.570312,-0.46875 -0.949219,0 -2.050781,0.3984375 -1.101563,0.3867188 -2.2968753,1.1601565 V 6.4200783 q 1.4179683,-0.4921875 2.5898433,-0.7265625 1.171875,-0.234375 2.261719,-0.234375 2.859375,0 4.359375,1.171875 1.5,1.1601562 1.5,3.3984372 0,1.148438 -0.457031,2.0625 -0.457031,0.902344 -1.558594,1.945313 l -0.75,0.667969 q -0.796875,0.726562 -1.042969,1.171875 -0.246093,0.433593 -0.246093,0.960937 z m -4.230469,1.734375 h 4.230469 v 4.171875 h -4.230469 z"
+         style="font-style:normal;font-weight:bold;-inkscape-font-specification:'Sans Bold';fill:#ffffff;fill-opacity:1;stroke:none"
+         id="path14366" />
+    </g>
+  </g>
+  <g
+     inkscape:groupmode="layer"
+     id="layer5"
+     inkscape:label="Original"
+     style="display:none">
+    <image
+       y="0.0079936879"
+       x="0.010951031"
+       id="image4124"
+       xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGAAAABgCAMAAADVRocKAAAAA3NCSVQICAjb4U/gAAAACXBIWXMA ADFOAAAxTgGacD5yAAAAGXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAABF0RVh0 VGl0bGUAUGViYmxlIEljb26KvwD9AAAADHRFWHRBdXRob3IAeHBobnh/4OzMAAAAY3RFWHRDb3B5 cmlnaHQAQ0MgQXR0cmlidXRpb24tTm9uQ29tbWVyY2lhbC1TaGFyZUFsaWtlIGh0dHA6Ly9jcmVh dGl2ZWNvbW1vbnMub3JnL2xpY2Vuc2VzL2J5LW5jLXNhLzMuMC/eBBrlAAABy1BMVEUAAAAAAAAA AP8AAIAAAFUAAKoAVaoAAIAAQIAAM2YAM5kzM2YzM5krK4AkJG0kJJIgIIAcHHEcHI4aGoAXF3QX F4sXLnQXLosVK4AUJ3YUJ4kSJIAkJIARInciInciIogQIIAgIIAeHngeHoccHIAcK4AbKHkbKIYa GoAaJoAYJHkXI4AXI4sWIXoWIYUVIIAfH3ofH4UdJ4ATHIQcJnsbJIAaI4QaIoAZIXsZIYQYIHgY IIAXH4MXJ3wXHoAXJoBLpfAWJHwdJHwdJIMcI4Acas0VIoMbIoAaIXwaIIAZH3wZH4MZJXwYHoAY HoYYJIAehtsYHoIefdtHoO0jetZGovNEn+4gg+EfgN5Ep/VKqfREpvQgfdkgfttKqvQggd4hjesw nPMwnPRJqfUhgd4hjutJqfUgfttNq/VNq/QhlPEhlPIhlvMkmPMglvIff9sgjeoglPAhk/AhlPEh lPIhlvMkl/MkmPMlmPMmmPMnmfMpmvMzn/Q0n/Q1n/Q2oPRIqfVNq/VasfZbsvZhtPZitfdtuveQ yvmXzvmj0/qk1Pqu2fu83/vR6f3T6v3Z7f3i8f3j8v3l8/7m8/7p9f7q9f7x+P7y+f7////ougeB AAAAb3RSTlMAAQECAwMDBAQFBQUFBgcHCAkJCgsLCwsMDQ0ODg8PDxAQERESEhMTFBQVFhYXFxgZ GRobGxwdHh8fICAhISIiIiMjIyQkJSYnKCkpKSoqKiorKyssLC2xs7O0tba2tu7u7u7u7+/w8fHy +/v7+/ywJhX9AAAC0klEQVRo3u2a+VfTQBCAm00iW0nSTVNKbKQYbIMUC6jU4q2IB2rF+74vPEAR LSjIIXgAAiqef65py6MNJe17y1RF8/26L/N1N9O8ZGZcLof/DiYFoiV1cZHwiGVZjh7zalRAYYbn sFsQ6RFwGccyBeJjwSMrPn8lHX6fQqRy3tbAsFiU1So9FA4bNIRD63XNTwTO5pSYc9fuvwbgwaW9 SxqYs/eGBgcAGBy+fRrlpRPDsJdfJoEYuMgvzibz/vK3kmDcFMuxNZvM/BG74QTdFV6PJZsYlhe9 XXCCrlCVStwcyhG45QCkoCmiV0o4uwXEib4aQMHjbc0RTXYvCMx/mGdNHaSgraWxRhEXzsgUEC0C KWjfszlUYRWsrYcUHGttNlSRzxUEo5CCjrZYrSqVVlD3Nwj6vqcZsovzKrPeTy3o/5lmxE4wnFl/ 4QhKLvjy2YY5IEExHMH0B1umQQSj9o+EUUewIBh/m8c4qOBbfu58dQRLCmYnJmZLKphKJicdwR8X vJsymSmhIM37FSv4OJlhxnlUOAJHAC8Ye5PHmPNe9PsEJX/5dT5AigvmPtkw53wnr3xB34809uWc zDp9OWflV7xgBaBlzY79MWORALhuui+vbhoArfwe3rUp5MtWfl2Il1QDUtAaj+rKakv1XdEhBfGm Wo1kq+/pM1Ih+wdRI6iIq3IFvEAewgke6QHFg1mU26PjhbtwghsKEbG1X8ew3PmnUPGfXRAwv7hr yqAD13t6nwPQ23PnBMeivG4gg85c7XwCQOeVU+avZ5bslcrrNm7dfehIInGSguOJo+0Hd27ZoOam j3ULvOjTI02x+PYddLTEY42GJpfnpo/L0g50k8pqI1IfbWikoSFaXxfSFKnMrqFsZpKbKKoWDFbT EdQCqlfCdhtIG7AoESLTQogk4EJd/VRTn8fLgecKTg3MjyUsA4SKDD7MT1bQ8++NmvwCRmydh2Fg UvEAAAAASUVORK5CYII= "
+       style="opacity:0.490741;image-rendering:optimizeQuality"
+       preserveAspectRatio="none"
+       height="28.710279"
+       width="28.710279" />
+  </g>
+</svg>
diff --git a/app/src/main/assets/ic_device_unknown_disabled.svg b/app/src/main/assets/ic_device_unknown_disabled.svg
new file mode 100644
index 0000000..8c15510
--- /dev/null
+++ b/app/src/main/assets/ic_device_unknown_disabled.svg
@@ -0,0 +1,197 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   version="1.1"
+   id="svg2"
+   width="28.740126"
+   height="28.740126"
+   viewBox="0 0 28.740126 28.740126"
+   sodipodi:docname="ic_device_unknown_disabled.svg"
+   inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:android="http://schemas.android.com/apk/res/android">
+  <metadata
+     id="metadata8">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs6">
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 14.370063 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="28.740126 : 14.370063 : 1"
+       inkscape:persp3d-origin="14.370063 : 9.580042 : 1"
+       id="perspective903" />
+  </defs>
+  <sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="1920"
+     inkscape:window-height="1035"
+     id="namedview4"
+     showgrid="false"
+     showguides="true"
+     inkscape:guide-bbox="true"
+     inkscape:zoom="16"
+     inkscape:cx="7.34375"
+     inkscape:cy="7.65625"
+     inkscape:window-x="0"
+     inkscape:window-y="45"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="layer6"
+     inkscape:pagecheckerboard="0">
+    <inkscape:grid
+       type="xygrid"
+       id="grid972" />
+  </sodipodi:namedview>
+  <g
+     inkscape:groupmode="layer"
+     id="layer1"
+     inkscape:label="Background"
+     style="display:inline"
+     sodipodi:insensitive="true">
+    <g
+       inkscape:groupmode="layer"
+       id="layer4"
+       inkscape:label="Bottom"
+       style="display:inline"
+       sodipodi:insensitive="true">
+      <rect
+         style="display:inline;fill:#7a7a7a;fill-opacity:1;stroke:none;stroke-width:3.57115;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         id="rect817-5"
+         width="22.819572"
+         height="21.904638"
+         x="2.9239759"
+         y="3.8768768"
+         ry="0.9472276" />
+    </g>
+    <g
+       inkscape:groupmode="layer"
+       id="layer3"
+       inkscape:label="Middle"
+       style="display:inline"
+       sodipodi:insensitive="true">
+      <rect
+         style="display:inline;fill:#9f9f9f;fill-opacity:0.941176;stroke:none;stroke-width:3.57115;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         id="rect817-7"
+         width="22.819572"
+         height="21.904638"
+         x="2.9317887"
+         y="3.0349438"
+         ry="0.9472276" />
+    </g>
+    <g
+       inkscape:groupmode="layer"
+       id="layer2"
+       inkscape:label="Top"
+       style="display:inline"
+       sodipodi:insensitive="true">
+      <rect
+         style="fill:#8a8a8a;fill-opacity:1;stroke:none;stroke-width:3.57115;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         id="rect817"
+         width="22.819572"
+         height="21.904638"
+         x="2.9238882"
+         y="3.4130721"
+         ry="0.9472276" />
+    </g>
+  </g>
+  <g
+     inkscape:groupmode="layer"
+     id="layer6"
+     inkscape:label="Foreground"
+     style="display:inline">
+    <g
+       inkscape:groupmode="layer"
+       id="layer7"
+       inkscape:label="Backplane"
+       style="display:inline" />
+    <g
+       inkscape:groupmode="layer"
+       id="layer8"
+       inkscape:label="Frontplane"
+       style="display:inline">
+      <g
+         inkscape:groupmode="layer"
+         id="layer9"
+         inkscape:label="Base"
+         style="display:inline" />
+      <g
+         inkscape:groupmode="layer"
+         id="layer10"
+         inkscape:label="Top"
+         style="display:inline" />
+      <g
+         inkscape:groupmode="layer"
+         id="layer11"
+         inkscape:label="Toptop"
+         style="display:inline" />
+      <g
+         inkscape:groupmode="layer"
+         id="layer12"
+         inkscape:label="Toptoptop"
+         style="display:inline" />
+    </g>
+    <path
+       inkscape:connector-curvature="0"
+       style="fill:#8a8a8a;fill-opacity:0.94117647"
+       d="m 12.48275,10.865376 h 3.907 v 6.978 h -3.907 z"
+       android:strokeWidth="1"
+       id="path2735" />
+    <g
+       aria-label="?"
+       id="text2312"
+       style="font-style:italic;font-size:24px;line-height:124%;font-family:Sans;-inkscape-font-specification:'Sans Italic';letter-spacing:0px;word-spacing:0px;display:inline;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1px">
+      <path
+         d="m 15.634456,17.365391 h -4.230469 v -0.574219 q 0,-0.960937 0.386719,-1.699219 0.386718,-0.75 1.628906,-1.898437 l 0.75,-0.679688 q 0.667969,-0.609375 0.972656,-1.148437 0.316406,-0.539063 0.316406,-1.078125 0,-0.8203127 -0.5625,-1.277344 -0.5625,-0.46875 -1.570312,-0.46875 -0.949219,0 -2.050781,0.3984375 -1.101563,0.3867188 -2.2968753,1.1601565 V 6.4200783 q 1.4179683,-0.4921875 2.5898433,-0.7265625 1.171875,-0.234375 2.261719,-0.234375 2.859375,0 4.359375,1.171875 1.5,1.1601562 1.5,3.3984372 0,1.148438 -0.457031,2.0625 -0.457031,0.902344 -1.558594,1.945313 l -0.75,0.667969 q -0.796875,0.726562 -1.042969,1.171875 -0.246093,0.433593 -0.246093,0.960937 z m -4.230469,1.734375 h 4.230469 v 4.171875 h -4.230469 z"
+         style="font-style:normal;font-weight:bold;-inkscape-font-specification:'Sans Bold';fill:#ffffff;fill-opacity:1;stroke:none"
+         id="path14366" />
+    </g>
+  </g>
+  <g
+     inkscape:groupmode="layer"
+     id="layer5"
+     inkscape:label="Original"
+     style="display:none">
+    <image
+       y="0.028144173"
+       x="0.013794855"
+       id="image1291"
+       xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJAAAACQCAYAAADnRuK4AAAABHNCSVQICAgIfAhkiAAAAAlwSFlz AABO4wAATuMBYdZxSQAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAAZdEVY dFRpdGxlAFR3ZWxmIEljb24gVGVtcGxhdGXyQOONAAAADHRFWHRBdXRob3IAeHBobnh/4OzMAAAA Y3RFWHRDb3B5cmlnaHQAQ0MgQXR0cmlidXRpb24tTm9uQ29tbWVyY2lhbC1TaGFyZUFsaWtlIGh0 dHA6Ly9jcmVhdGl2ZWNvbW1vbnMub3JnL2xpY2Vuc2VzL2J5LW5jLXNhLzQuMC9D0yJcAAANW0lE QVR4nO2dS4xc2VnH/98591VVXd3TbntC92ggwZ5R1BWRRZMoIQG1UIQIs0BCcpINESyyQIqySlgl 2I6yQ2xmtpEQilgQI4EUiAQaQQtCFAiNQjLVUWbsjII19iS2u931vI9zzseiqtxP21V9b7vrlr/f pp/33HPr/O53vvO4VYAgCIIgCIIgCIIgCILwLEBnUz6f8mmfZR7ZpKfyop+GQPvKHNX52mmLKjyS K8NGONAEhclUdMMOy2PsSdMcfr08+LK+JTKdNhurQ0GuD3/RGP58hfc1eSESeUUUMuSQPE0CLgPr ILRv0xp2EMd3CHf3DkiS+yJTQYTh0p4QjS1E0TJvYhGorzA2VjGQ6RoAHklEKECiIhuQDsiz3qC1 9jLF8R1KknNkzDYZs0vAi3CuJeKcEkrNM3ALnrfAnneOw3Cbo2iZN+t3GBtNHkSjh5FoagQ6Ik/j LlSSnKM4NirLbit3fo7Y9AhYgrP9vTjq+iJTTkhVHoqgdIWB+yCvyupeh31/xUWR58Jwm5sX4Abd 21ZhEhUo0NUD8nS7UGkKlS1C2QRq3kA5F5KrJ8Q2IaAOdqnIUxCkAgbaIB2yaoesVMItD06HcP4O XBDA1WpwA4lGkegqYwoEok++ygE/3/+CJv6MVrjIwEIB5QpFw9xyUG9ZuL+Jt+6+tnH1vUneKJRX IPrEN/nFiHrfJqBhHJBYwLqcpQqnglZAqAFPAc7xG0miX3n9s9GtPCOzkwpEAPDJVznQy73NxKBx p8PoZjJBWAbmAsJyjeArfqP747sf2rj6V+mhIf6IJzaomvDchIcJM4Of73+hm6Lx0wdO5CkRnZRx 84FDz9AHgpeWPj+Ycrk2zGOv0j5vnhhgJhHo0DzPNXJwn77VcnDiTulwDNxqOZDWn8LaosI6FLBK B2Vi4GHQOJ5xJxKPThKuN+h+D5esyFNaLAPbfXfp0m6owhgcre0MJx8xnDN68sTjBDPRI3lWCeug xl2oTsbVgq5FOCNaKeba7dCL43nX7d7hS17IYQxurjccNpo4JNERxunC6KA8Ww/neTJb4JUIZ0Jm gbSmvbj2rtefr+leb0l3u1CNu1BYb+zr0h52ZwcYMwcadVt78vT7HS29V/lhANVAedX+olfrWC+t 93SaDtr4oETH8ySB9mXkl7HWXqbRDLN97sKkIzhhGmGmNA79xE/8JHDeXFLVab2i03SwFLXWvj1Y FN+LQgcYX4L1LYrjO2TMLmWLUHNJXxd4GcIZ4uvMDzx4WdLxsijQc0lfZ4tQcfy2iuMVOrQF50A0 GiOJHnZf7UVKkpCcWyKbQJkalF/whQhng/XIQwYKgwBpljIZsE2gnFtwgy03Sxh2Y0dC0JgR6DLW sAZjlsier9G8gXLZxJOQwpTiZ/Csb7Q1mY5Mql0V6jkXkT3fIWOWht0YcNzO0oklcLZLzsVkLRSY ZTV9BnAM5RtPs4NyUUDOBeRcTM7ODdt37ZHHji3QKP9h0yPnIpKtGLMDe1BgQy4g5SwUc0rOxYfa 9/Kxx55gS+sSsNAHerUTVFWYRpg9YoDAIOaM2PEgSJgeGZNRHIOAxWOPHTMCjbLwF/edNJMINDNY YjYUPvx5/OBwghyoP9xRKMwWIdgZAioTBQcZSQkD2J4oKIhAQi5EICEXRT5Y+FR56ZzC5z7o46Mv eHi+RvBLditkDvhFl/Hddwy+/oMMb+2UcyN5KQX67Ad8fOXjIXSJU3lfAS/UCZff7+MPXvZx9TsJ /rqZnXW1JqZk9y3wykUPV36z3PIcRivgq78V4nd/tXz3c6kECjTw5Y+Fp/6eNGcBAfjKx8LSdcWl qu5HVjTeU5tFfQYszxE+vFKuXTKlEuilc6Wq7ol4uWTXWKra1vzZjT4j5kp2jaUSSJg+RCAhFyKQ kIvyTTw8ht2kHA8aLYSPznNePqfwexc97MSM/3nXIpnyZ+9mSqBf/8supv1RawJw80/mHvn3Vy55 eOXSoFluPnD4o3/o45329F6UdGFTzMXnFL78G+GT//EMEYGeMnrCV/xDy9M9sSgCPWUmXaqoTPm8 kAgk5EIEespM+mZcjqc3gQZmbBR2HH/+2xF+7fmzuU9++AuHL/1LfOB36YTD8p9sT/dGs5kX6MU6 4aXFsxFop58vemQO+Iv/TAuqzekw8wKVjTe3HW7sOPxfy+Hv3zR4UyKQMAn/eMPgtc3pjjr7kSRa yIUIJORCBBJyIQIJuRCBhFyIQEIuRCAhFyKQkAsRSMiFCCTkQgQSciECCbkQgYRciEBCLkQgIRci kJALEUjIhQgk5EIEEnIhAgm5EIGEXIhAQi5EICEXIpCQCxFIyIUIJORCBBJyIQIJuRCBhFyIQEIu RCAhFyKQkAsRSMjFzL9D2a02Y/GMPhH51hR/REFRzLxAh98lVSgW6cKEXIhAQi5EICEXIpCQCxFI yMVMjcL++49rZ12FZ46ZEuhxn0UqnA7ShQm5EIGEXIhAQi5KJVAnm/21pXZarmsslUA3pvyzs4rg rTNa+D0ppRLoe7ct3u2W6w6dhDsdxn/dnvAzMc+YUgmUWuBr/5FgFhViAF/9ToKsXAGoXAIBwLdv Glz99wS2ZC/047AO+LN/S/BPb5uzrsrElHIi8RtvZPjebYvPfdDHR1/w8Es1gi7ZrWAc8PMu47vv GHz9B1npcp8RpRQIAN7advjTf00AJGddlWeakt23wrQhAgm5EIGEXIhAwgDSJ5odmVggpStMOpzF qZhnnASkPAb6Ex01pkCrQ2FuTVgpoRxoJvJ4NJ4lGn9B7gRd2H1gd7KTCNMNkWGCZpBhIp9JBaxU zORV2fMWOIqWH9nWYwsURcvseQtMXnVQuApEoBmBDBzIskojRypjooCVig617/Vjjz1BDlRjpSLW Gg5EItEMoAgu87Ql1Xc6YadUykpFrHRn2L6bjz52vFNcxyY24XnnWN/rcsuDI5WIPDNC5sPozFql 4foKTvXgHqiY9b059rz7vFlfGbb1lSNtPoZAVxhoMOorHIbbrNQ86xDOj8Ny7TsQHok2bJSX2TRj 4/mB9Tw4HcIptcthuDSUpnFswBi/C9tY5UEe1GJ/B67loZyrf8IRUsMms37mh3MmSGE6YcX6O3BR 9D4XRbcZG6v75Tkg0pMEYmD0qMx1bNbvcK0GFwRwfrVjwdKLzQAchEHmJzBhqkwn7Fn94K4LArgw 3B52X6ME+uhjU2NGoGE3trHKzeg212pwldacBUsQKj3s0Df9LK5VsnYltkG7aiuVOVurwTUvwGGj OWj7Y/IfYDyBhlHoCgNbjM1PuOYFuFoNDnHaK/ZqhKcNx0k76FoTtPu21oWpVu/vk2d1nzwE4Ohm 0AmG8fsk2mhy8wKc6/duzOT+0mcFZnC//9N6/ZfN4iJMrQZ3YyHZF3m29stzLONuKGMANCiIGbgG bDTBH8+uc2t3jebnAZLHiksFM7jdBiW9v72x8IZDfWWYLDcBrDDQwCF5jg0Vk+xIPCKR+8ndV+n9 5/8QzjWoXge80m5wfLYwGbjdAZLkR+n/3nwNmztuIM0W9sQBniTPgf+YEBqV+ytf/OF7qVL5Fitq kO8D2gdkgno6YQJsBs4yENOP2HV//2df+7ufDf54RBrgMeKMyNvvEMB4z+98o1r5yIc/D8efBqmL rNR8znKFU4Act8C4odhe737/+6/9/J+/FQ+S5KsjUSa+8wsRCLhGAFSjAXXvXsdPa+e9auK81E/9 wKt4Jkt9Z632fa3ZQTFbAhQhAJidJE85IVKMFAAcE2kmBZdl1iqtrfbIZNZkfgoTV5FW491sYWHB DhJluMeNsMYhb9LCg+z5KoBVjqLXuVJZsejeQ78+xxUvQZakHEaBsybTzkJzpCmwRgEAsyfyFAE7 UGAYAFKtHSWW/TCz2oNN0tQGEZtQOWPb1nqV4faMjcVhznNyeYD8EWhYBgP4lMJ6gxp3oXZ353Wy 0NK1LrwsCrTJUh2ZVNuQlLNQQAVgQ8yZCFQQRP5AAspYaTidsIu9wHp+YP04td0aTLg7bxcWWnZv mP5Nl1egAodNDcbGKpprO3wpSZzbAXXqPTsXA2TAWTVw1kKx84k5I+aU5A3piqOLFEQBE1WZkLEL 4fwenGdT1wl7Ntipuqjqub3oU8wMXhEC7evGrgP1Bocx2Bg4tKvoLoJtAjWfQjkXk3MgnksIAAzq BZxeAIAQbQAJqANWKmZlI25Fg1X1YKe6b20LDLyOwXA9X/QBiunChuWMkukmjbqyJDlHcfy2cm6B 7PkaOdslNj1ytipd1ymhdG+wa1TXWN/rslK7HEXvc2G4zXvLEwdmmHMJVGAXdnCWurnecGvtkMJw gZPkPpkWyJjdoTjzcK4lEhWMUvMM7ALYheeBvep9DsMljqJwEHk2msOu6/HLE5NQlEDHLnVsYpWx fofQTmgNy4jjUa1bBZ1WOEgLwAKAwR72TdwG6hjmPK/j0MIoUEAeVHQUeDhDPZwbGtIk4PLw+y3C esFnFfbYAPYew7qOgzsJi5UHKF6gQ+WO6nhNuqszY/x1rZNw2g0r4kwPskApCIIgCIJQEP8P03/B 430mofMAAAAASUVORK5CYII= "
+       style="opacity:0.49074079;image-rendering:optimizeQuality"
+       preserveAspectRatio="none"
+       height="28.670942"
+       width="28.670942" />
+    <image
+       y="0.0055872202"
+       x="-0.031073861"
+       id="image3082"
+       xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJAAAACQCAYAAADnRuK4AAAABHNCSVQICAgIfAhkiAAAAAlwSFlz AABO4wAATuMBYdZxSQAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAAZdEVY dFRpdGxlAFR3ZWxmIEljb24gVGVtcGxhdGXyQOONAAAADHRFWHRBdXRob3IAeHBobnh/4OzMAAAA Y3RFWHRDb3B5cmlnaHQAQ0MgQXR0cmlidXRpb24tTm9uQ29tbWVyY2lhbC1TaGFyZUFsaWtlIGh0 dHA6Ly9jcmVhdGl2ZWNvbW1vbnMub3JnL2xpY2Vuc2VzL2J5LW5jLXNhLzQuMC9D0yJcAAAL6klE QVR4nO3dTYvcRhrA8adKJZW6Zc0LmTEmmYUF5zYOJjQLOSTQEMhlA7uHsNlPsCHg5DP4MzghHyC3 xZvTGh8CgYaQXMwEkngOZo1f2JAYz2ReLHePSqqqZy+t2Z5xj6c1pbZa7ecHxo09LdVEf0rVkjIG IIQQQgghhBBCCCEvA9bw7ZPJ4TQ2Oo0DPG6bFFJ9xoVTWUxVH1g25vXxfTAAgG63W/GuSa/XK14e DwTH/HklEVUZ0PF4ilBYkiQMACBN0yP7U0rRzFQRKeWRIMIwRACAOI6x1+uNBlRpRNMIiAEAK8JJ 05QppZjWmmmtGQCAtZbCmRLOOQIACCFQCIFSSgzDEEdCGo1oZgJ6Jp6trS2ulGJpmvI8z/nKysph QMaYw/1STO6KaAAAPM87DGh7ext937dhGFopJa6urtqqI6oyoCPx9Pt9nmUZX15e5koprrXm1lpm rWWIyOI4pngqxDnHJEmAMYacc+ScoxDCSint7u6uDYLARlFkx0RUe0Ds2rVrQRRFn3qe93fO+UVE XKxgu6R6TxDxP4j4z4cPH3529epVNfzzM0fkGhD78ssv/2CtvckYW7fWgtYaEKdyyYE4YoyBEAI4 54CIt5VSf/7oo4/+O/IlpQ/cWQNiAADXrl0LFhYWNowx60mSQJZlZ9wceZGCIIA4jsHzvNsPHjz4 09WrVzM44/UiXnLfhx/PAQCiKPo0z/P1nZ0diqdBsiyDnZ0dyPP80tra2hUYWcPC+Gt5JyoT0DPX eRhjH+7t7dEpq4EQEfb29oAx9rdOp8O73S6H8SEdj+oIMeH+xl0kZP1+/3WKp7kQEQ4ODl7f39/n aZpip9NBAICNjY1xH/MZjDmlTRpQ4chH9SzL2mcdPJkNSqlzSZKINE1tv99HIQSur68X14yKLztx lpjkFDb2ImG/3+fGGLfRk9oZYyCKIhFFkVhYWPAGg4HX7/f51tYW73a7405nR0y6BnomnoODA6+i 74HUTCklDg4OxNOnT0Ucx16WZXxMRGOddgo78sYkSVhxhXlpaansJzgygxCR+b7ve55nfd83aZqa OI4hSRIQQuDwRjjCCWugiSPodrssTVOmtWbD2xM0A80Jz/P8LMuEUkqEYegppbzl5WWepilP05QN Z6HCkUllkkU0AwCWJAlTSjFrLSvubVX6XZDaaK2F7/ssz3PgnKPWGpVS3FprRx65cZuBhjtiw7vq vN1uU0BzwvM8YYzxgiDwtNZeu93m1lpmjGFa68PnucBhEX3IGMOGG+eISHfT5wAiciGEZ609fGKi +HXaeycOqFj/aK0n3jhpBmstR0RmreVSyuI1O3/+/KnvpdMQgeGZhCEiK+Kx1h5OGMcfRR5V5jrQ uJ2SORAEAUNEFoZh6feeaQ1E8cynYllS5vjSKYwAwNnPKBQQcUIBESdlH+eYGa+++ip0Oh1YW1uD KIqAsWYty4wxkCQJ3L9/H27dugX7+/t1D+lMGhnQO++8A2+99Vbdw3DieR4sLS3Bm2++CW+88Qbc vHkT7ty5U/ewSmvcKazT6TQ+nuOEEPD+++/D2tpa3UMprVEBtVotePvtt+sexlRwzuHdd9+texil NSqgixcvQhAEdQ9jas6fPw8rKyt1D6OURgXUtP+4Z/HKK6/UPYRSGhWQ7/t1D2HqpJR1D6GURgVE Zg8FRJxQQMRJIy8kjoOIoJQ6/Qtrxhh77jrnwoULoJSCfr8Pv/76K1hrX+DoypubgAaDAXzxxRd1 D+NU586dg48//vjEv798+TJcvnwZAAAePXoE169fhzRNX9TwSqNT2Ay7cOHCzF84pYBesHa73I8T mPXbGxTQC1b2WtasX/uigIgTCugFK/upatZ//tLcfAo7yQcffAALCwu17PvevXuj//wAAAA8ffq0 1Da2trYqHFH15j6gpaUlWF5ermXf4w5+mRklyzL4/vvvqxxS5eY+oKZ59OgR7O3twc7ODty+fXvm H3WlgGbMjz/+CD/99FPdw5gYLaKJEwqIOKGAiBMKiDihgIgTCog4oYCIEwqIOKGAiBMKiDihgIgT Cog4oYCIEwqIOKGAiBMKiDihgIgTCog4oYCIEwqIOKGAiBMKiDihgIgTCog4oYCIEwqIOKGAiBMK iDihgIgTCog4oYCIEwqIOKGAiJO5/wllu7u7YIypZd9JktSy3xdp7gP66quv6h7CXKNTGHFCAREn FBBxQgERJxQQcTI3n8La7TZ88skndQ/jVIyxuodQqbkJiDEGYRjWPYyXDp3CiBMKiDihgIiTRgWU ZVndQ5g6pVTdQyilUQH9/vvvdQ9h6pr2PTYqoLt37871LPT48WPY3t6uexilNCqgNE3h22+/rXsY U2GthW+++abuYZTWqIAAAH744YeZ/3dEy8rzHG7cuAG//PJL3UMprZEXEr/77ju4d+8edDodWFtb g3PnzjXuCq8xBp48eQL379+HW7duwZMnT+oe0pk0MiAAgN9++w1u3LhR9zBeeo07hZHZQgERJxQQ cUIBEQAAYIzhWd5XOiDP8/CsOyOzjXNe+rhOGhAFM8eyLEPGGKZpCgDlZqMzn8JoFpofw2OJjDFk jCHnHDnnKIRAIQSGYXjisZ44oDAMDzdY7KCS0ZPacc7tMBybpulhRI8fPz79vWV35nkecs7R8zxL s9B8YIxZrbXhnFvP82wxQUwySZQKSAiB29vbKISw/X6f4pkTxhjteZ4ZxmMHg4EdThIohMA4jotj /cwxnyQgBACM4xillMg5Ryml9X2/np9YQConhNBZlpk8z7Xv+0YIYaWUdnisT4wHoMQM1Ov1sFgH 7e7uWiGErWT0pHZaay2lzKWUOssyLaU0u7u7NgxDG4Yh9nq90XiOhHTazVQEgMPb3HEcY5qmFgAg SRKDiI27C06egVLKfDAYWEQ0Ukq7t7dnW62WlVKOnr7GKnMdCItZKIoi22q1DCItg5oOEUFrnUdR lLdaLZMkiWm1WiaKIru6umqHs0/x6xmTroGK33FjY8Ourq7aKIpslmWDir4PUhOlVNLv93WSJKbf 7+t2u/28eJ6JqOzzQAgA0Ov1oNvt2sFgcFdK2aHTWDMhIgwGg3txHOtisRyGIW5sbIyGU9kpbPQ1 9no91FpfT5IE6FTWPIgISZKAMeZfi4uLdnV11W5ubpqNjQ0LABaejWfsQfZK7vfIVPPaa69tRFH0 V2PMed/3gXO6ud8EWmtIkgSyLPv5zp07/+j1evrBgwfPi+XEGeKs557D9125cuWPUsp/c87XhRAg RGOfkn0paK1Baw3W2p+VUn/5/PPPHw7/alwkp55aXBcvDADgvffea1+6dOkKAHzIGLvIOV9w3C6Z AmvtE0S8i4jXNzc3P/v6669TOGWRfJpKAhr+ztfX1/n29rYfRZFQSgnf9/08z4UQwjfGeL7ve9Za joiseO/wNXEwck+yuBFq8zw3nueZPM91EAR5lmW63W5naZrmi4uLZnNzc3StA3CGeADc/6+M0QuN GIYhtlot0+/3IY5jPDg4gCAIUGttpZSeMcYDABYEAQegeKpURJTnuUVElFKaLMvM8HcdhqEeXuMp Hs84dYE8iaoWLAgArLhSrbVmUkqmtTZSSuCco9YajTFWSskRERCRUUDVKQISQqBSyiKiDYLA+L5v ENG0Wi2TZZmNosg+7+Zo6f26bgCOnsZYp9Ph+/v7fDAYeHEce0opT2vN2+02N8Zway2jeKZj9IEw z/PsYDCwwxujJkkS0263zeLioh1+VHda+xSqmIHG3S9DrbVNkgSWl5dRKcWzLOPWWlYENPzaCnZP AP7/zyoUARljMAxDK6W0u7u7NgiCk+5tOc1CVc0CR2ahbrfLtra2uFKKpWnKrbXMGMNWVlaY1poZ Y2j2mZLiGZ7t7W0sHv4bhoST3p4oo+qAites2+2yJElYmqZMKcW01kxrffh11lqKqGKjTxAWjx9L KTEMQ4zjGE+4MToTAR3f1uHH9CIkAIA0TSmaF6R4EH4kHICK4wGoNqDj25vkNZmOkyKpNB6A6R3M 49ulaOpT6aL5uGkfWApndtAjE4QQQgghFfkfLnfEbGu/P8gAAAAASUVORK5CYII= "
+       style="display:inline;image-rendering:optimizeQuality"
+       preserveAspectRatio="none"
+       height="28.697369"
+       width="28.697369" />
+  </g>
+</svg>
diff --git a/app/src/main/assets/ic_device_vesc.svg b/app/src/main/assets/ic_device_vesc.svg
new file mode 100644
index 0000000..d0287e2
--- /dev/null
+++ b/app/src/main/assets/ic_device_vesc.svg
@@ -0,0 +1,146 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   version="1.1"
+   id="svg2"
+   width="28.740126"
+   height="28.740126"
+   viewBox="0 0 28.740126 28.740126"
+   sodipodi:docname="ic_device_vesc.svg"
+   inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:dc="http://purl.org/dc/elements/1.1/">
+  <metadata
+     id="metadata8">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs6">
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 14.370063 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="28.740126 : 14.370063 : 1"
+       inkscape:persp3d-origin="14.370063 : 9.580042 : 1"
+       id="perspective903" />
+  </defs>
+  <sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="1920"
+     inkscape:window-height="1035"
+     id="namedview4"
+     showgrid="false"
+     showguides="true"
+     inkscape:guide-bbox="true"
+     inkscape:zoom="22.627418"
+     inkscape:cx="1.3037281"
+     inkscape:cy="13.50132"
+     inkscape:window-x="0"
+     inkscape:window-y="45"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="layer2"
+     inkscape:pagecheckerboard="0">
+    <inkscape:grid
+       type="xygrid"
+       id="grid972" />
+  </sodipodi:namedview>
+  <g
+     inkscape:groupmode="layer"
+     id="layer1"
+     inkscape:label="Background"
+     style="display:inline">
+    <g
+       inkscape:groupmode="layer"
+       id="layer4"
+       inkscape:label="Bottom"
+       style="display:inline"
+       sodipodi:insensitive="true">
+      <rect
+         style="display:inline;fill:#1f7fdb;fill-opacity:1;stroke:none;stroke-width:3.57115;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         id="rect817-5"
+         width="22.819572"
+         height="21.904638"
+         x="2.9239759"
+         y="3.8768768"
+         ry="0.9472276" />
+    </g>
+    <g
+       inkscape:groupmode="layer"
+       id="layer3"
+       inkscape:label="Middle"
+       style="display:inline"
+       sodipodi:insensitive="true">
+      <rect
+         style="display:inline;fill:#4dabf5;fill-opacity:1;stroke:none;stroke-width:3.57115;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         id="rect817-7"
+         width="22.819572"
+         height="21.904638"
+         x="2.9317887"
+         y="3.0349438"
+         ry="0.9472276" />
+    </g>
+    <g
+       inkscape:groupmode="layer"
+       id="layer2"
+       inkscape:label="Top"
+       style="display:inline">
+      <rect
+         style="fill:#2196f3;fill-opacity:1;stroke:none;stroke-width:3.57115;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         id="rect817"
+         width="22.819572"
+         height="21.904638"
+         x="2.9238882"
+         y="3.4130721"
+         ry="0.9472276" />
+    </g>
+    <g
+       id="g870"
+       transform="matrix(0.851388,0,0,0.851388,4.1035946,4.1325617)"
+       style="fill:#fffffe;fill-opacity:1;stroke:none;stroke-width:1.17455">
+      <path
+         d="m 20.38,8.57 -1.23,1.85 A 8,8 0 0 1 18.93,18 H 5.07 A 8,8 0 0 1 15.58,6.85 L 17.43,5.62 A 10,10 0 0 0 3.35,19 2,2 0 0 0 5.07,20 H 18.92 A 2,2 0 0 0 20.66,19 10,10 0 0 0 20.39,8.56 Z"
+         id="path4"
+         style="fill:#fffffe;fill-opacity:1;stroke:none;stroke-width:1.17455" />
+      <path
+         d="m 10.59,15.41 a 2,2 0 0 0 2.83,0 l 5.66,-8.49 -8.49,5.66 a 2,2 0 0 0 0,2.83 z"
+         id="path6"
+         style="fill:#fffffe;fill-opacity:1;stroke:none;stroke-width:1.17455" />
+    </g>
+  </g>
+  <g
+     inkscape:groupmode="layer"
+     id="layer5"
+     inkscape:label="Original"
+     style="display:none">
+    <image
+       y="0.0079936879"
+       x="0.010951031"
+       id="image4124"
+       xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGAAAABgCAMAAADVRocKAAAAA3NCSVQICAjb4U/gAAAACXBIWXMA ADFOAAAxTgGacD5yAAAAGXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAABF0RVh0 VGl0bGUAUGViYmxlIEljb26KvwD9AAAADHRFWHRBdXRob3IAeHBobnh/4OzMAAAAY3RFWHRDb3B5 cmlnaHQAQ0MgQXR0cmlidXRpb24tTm9uQ29tbWVyY2lhbC1TaGFyZUFsaWtlIGh0dHA6Ly9jcmVh dGl2ZWNvbW1vbnMub3JnL2xpY2Vuc2VzL2J5LW5jLXNhLzMuMC/eBBrlAAABy1BMVEUAAAAAAAAA AP8AAIAAAFUAAKoAVaoAAIAAQIAAM2YAM5kzM2YzM5krK4AkJG0kJJIgIIAcHHEcHI4aGoAXF3QX F4sXLnQXLosVK4AUJ3YUJ4kSJIAkJIARInciInciIogQIIAgIIAeHngeHoccHIAcK4AbKHkbKIYa GoAaJoAYJHkXI4AXI4sWIXoWIYUVIIAfH3ofH4UdJ4ATHIQcJnsbJIAaI4QaIoAZIXsZIYQYIHgY IIAXH4MXJ3wXHoAXJoBLpfAWJHwdJHwdJIMcI4Acas0VIoMbIoAaIXwaIIAZH3wZH4MZJXwYHoAY HoYYJIAehtsYHoIefdtHoO0jetZGovNEn+4gg+EfgN5Ep/VKqfREpvQgfdkgfttKqvQggd4hjesw nPMwnPRJqfUhgd4hjutJqfUgfttNq/VNq/QhlPEhlPIhlvMkmPMglvIff9sgjeoglPAhk/AhlPEh lPIhlvMkl/MkmPMlmPMmmPMnmfMpmvMzn/Q0n/Q1n/Q2oPRIqfVNq/VasfZbsvZhtPZitfdtuveQ yvmXzvmj0/qk1Pqu2fu83/vR6f3T6v3Z7f3i8f3j8v3l8/7m8/7p9f7q9f7x+P7y+f7////ougeB AAAAb3RSTlMAAQECAwMDBAQFBQUFBgcHCAkJCgsLCwsMDQ0ODg8PDxAQERESEhMTFBQVFhYXFxgZ GRobGxwdHh8fICAhISIiIiMjIyQkJSYnKCkpKSoqKiorKyssLC2xs7O0tba2tu7u7u7u7+/w8fHy +/v7+/ywJhX9AAAC0klEQVRo3u2a+VfTQBCAm00iW0nSTVNKbKQYbIMUC6jU4q2IB2rF+74vPEAR LSjIIXgAAiqef65py6MNJe17y1RF8/26L/N1N9O8ZGZcLof/DiYFoiV1cZHwiGVZjh7zalRAYYbn sFsQ6RFwGccyBeJjwSMrPn8lHX6fQqRy3tbAsFiU1So9FA4bNIRD63XNTwTO5pSYc9fuvwbgwaW9 SxqYs/eGBgcAGBy+fRrlpRPDsJdfJoEYuMgvzibz/vK3kmDcFMuxNZvM/BG74QTdFV6PJZsYlhe9 XXCCrlCVStwcyhG45QCkoCmiV0o4uwXEib4aQMHjbc0RTXYvCMx/mGdNHaSgraWxRhEXzsgUEC0C KWjfszlUYRWsrYcUHGttNlSRzxUEo5CCjrZYrSqVVlD3Nwj6vqcZsovzKrPeTy3o/5lmxE4wnFl/ 4QhKLvjy2YY5IEExHMH0B1umQQSj9o+EUUewIBh/m8c4qOBbfu58dQRLCmYnJmZLKphKJicdwR8X vJsymSmhIM37FSv4OJlhxnlUOAJHAC8Ye5PHmPNe9PsEJX/5dT5AigvmPtkw53wnr3xB34809uWc zDp9OWflV7xgBaBlzY79MWORALhuui+vbhoArfwe3rUp5MtWfl2Il1QDUtAaj+rKakv1XdEhBfGm Wo1kq+/pM1Ih+wdRI6iIq3IFvEAewgke6QHFg1mU26PjhbtwghsKEbG1X8ew3PmnUPGfXRAwv7hr yqAD13t6nwPQ23PnBMeivG4gg85c7XwCQOeVU+avZ5bslcrrNm7dfehIInGSguOJo+0Hd27ZoOam j3ULvOjTI02x+PYddLTEY42GJpfnpo/L0g50k8pqI1IfbWikoSFaXxfSFKnMrqFsZpKbKKoWDFbT EdQCqlfCdhtIG7AoESLTQogk4EJd/VRTn8fLgecKTg3MjyUsA4SKDD7MT1bQ8++NmvwCRmydh2Fg UvEAAAAASUVORK5CYII= "
+       style="opacity:0.490741;image-rendering:optimizeQuality"
+       preserveAspectRatio="none"
+       height="28.710279"
+       width="28.710279" />
+  </g>
+</svg>
diff --git a/app/src/main/assets/ic_device_vesc_disabled.svg b/app/src/main/assets/ic_device_vesc_disabled.svg
new file mode 100644
index 0000000..b9bead5
--- /dev/null
+++ b/app/src/main/assets/ic_device_vesc_disabled.svg
@@ -0,0 +1,199 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   version="1.1"
+   id="svg2"
+   width="28.740126"
+   height="28.740126"
+   viewBox="0 0 28.740126 28.740126"
+   sodipodi:docname="ic_device_vesc_disabled.svg"
+   inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:android="http://schemas.android.com/apk/res/android">
+  <metadata
+     id="metadata8">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs6">
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 14.370063 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="28.740126 : 14.370063 : 1"
+       inkscape:persp3d-origin="14.370063 : 9.580042 : 1"
+       id="perspective903" />
+  </defs>
+  <sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="1920"
+     inkscape:window-height="1035"
+     id="namedview4"
+     showgrid="false"
+     showguides="true"
+     inkscape:guide-bbox="true"
+     inkscape:zoom="16"
+     inkscape:cx="7.34375"
+     inkscape:cy="7.65625"
+     inkscape:window-x="0"
+     inkscape:window-y="45"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="layer2"
+     inkscape:pagecheckerboard="0">
+    <inkscape:grid
+       type="xygrid"
+       id="grid972" />
+  </sodipodi:namedview>
+  <g
+     inkscape:groupmode="layer"
+     id="layer1"
+     inkscape:label="Background"
+     style="display:inline">
+    <g
+       inkscape:groupmode="layer"
+       id="layer4"
+       inkscape:label="Bottom"
+       style="display:inline"
+       sodipodi:insensitive="true">
+      <rect
+         style="display:inline;fill:#7a7a7a;fill-opacity:1;stroke:none;stroke-width:3.57115;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         id="rect817-5"
+         width="22.819572"
+         height="21.904638"
+         x="2.9239759"
+         y="3.8768768"
+         ry="0.9472276" />
+    </g>
+    <g
+       inkscape:groupmode="layer"
+       id="layer3"
+       inkscape:label="Middle"
+       style="display:inline"
+       sodipodi:insensitive="true">
+      <rect
+         style="display:inline;fill:#9f9f9f;fill-opacity:0.941176;stroke:none;stroke-width:3.57115;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         id="rect817-7"
+         width="22.819572"
+         height="21.904638"
+         x="2.9317887"
+         y="3.0349438"
+         ry="0.9472276" />
+    </g>
+    <g
+       inkscape:groupmode="layer"
+       id="layer2"
+       inkscape:label="Top"
+       style="display:inline">
+      <rect
+         style="fill:#8a8a8a;fill-opacity:1;stroke:none;stroke-width:3.57115;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         id="rect817"
+         width="22.819572"
+         height="21.904638"
+         x="2.9238882"
+         y="3.4130721"
+         ry="0.9472276" />
+    </g>
+  </g>
+  <g
+     inkscape:groupmode="layer"
+     id="layer6"
+     inkscape:label="Foreground"
+     style="display:inline">
+    <g
+       inkscape:groupmode="layer"
+       id="layer7"
+       inkscape:label="Backplane"
+       style="display:inline" />
+    <g
+       inkscape:groupmode="layer"
+       id="layer8"
+       inkscape:label="Frontplane"
+       style="display:inline">
+      <g
+         inkscape:groupmode="layer"
+         id="layer9"
+         inkscape:label="Base"
+         style="display:inline" />
+      <g
+         inkscape:groupmode="layer"
+         id="layer10"
+         inkscape:label="Top"
+         style="display:inline" />
+      <g
+         inkscape:groupmode="layer"
+         id="layer11"
+         inkscape:label="Toptop"
+         style="display:inline" />
+      <g
+         inkscape:groupmode="layer"
+         id="layer12"
+         inkscape:label="Toptoptop"
+         style="display:inline" />
+    </g>
+    <path
+       inkscape:connector-curvature="0"
+       style="fill:#8a8a8a;fill-opacity:0.94117647"
+       d="m 12.48275,10.865376 h 3.907 v 6.978 h -3.907 z"
+       android:strokeWidth="1"
+       id="path2735" />
+    <g
+       id="g870"
+       transform="matrix(0.851388,0,0,0.851388,4.1035946,4.1325617)"
+       style="display:inline;fill:#fffffe;fill-opacity:1;stroke:none;stroke-width:1.17455">
+      <path
+         d="m 20.38,8.57 -1.23,1.85 A 8,8 0 0 1 18.93,18 H 5.07 A 8,8 0 0 1 15.58,6.85 L 17.43,5.62 A 10,10 0 0 0 3.35,19 2,2 0 0 0 5.07,20 H 18.92 A 2,2 0 0 0 20.66,19 10,10 0 0 0 20.39,8.56 Z"
+         id="path4"
+         style="fill:#fffffe;fill-opacity:1;stroke:none;stroke-width:1.17455" />
+      <path
+         d="m 10.59,15.41 a 2,2 0 0 0 2.83,0 l 5.66,-8.49 -8.49,5.66 a 2,2 0 0 0 0,2.83 z"
+         id="path6"
+         style="fill:#fffffe;fill-opacity:1;stroke:none;stroke-width:1.17455" />
+    </g>
+  </g>
+  <g
+     inkscape:groupmode="layer"
+     id="layer5"
+     inkscape:label="Original"
+     style="display:none">
+    <image
+       y="0.028144173"
+       x="0.013794855"
+       id="image1291"
+       xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJAAAACQCAYAAADnRuK4AAAABHNCSVQICAgIfAhkiAAAAAlwSFlz AABO4wAATuMBYdZxSQAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAAZdEVY dFRpdGxlAFR3ZWxmIEljb24gVGVtcGxhdGXyQOONAAAADHRFWHRBdXRob3IAeHBobnh/4OzMAAAA Y3RFWHRDb3B5cmlnaHQAQ0MgQXR0cmlidXRpb24tTm9uQ29tbWVyY2lhbC1TaGFyZUFsaWtlIGh0 dHA6Ly9jcmVhdGl2ZWNvbW1vbnMub3JnL2xpY2Vuc2VzL2J5LW5jLXNhLzQuMC9D0yJcAAANW0lE QVR4nO2dS4xc2VnH/98591VVXd3TbntC92ggwZ5R1BWRRZMoIQG1UIQIs0BCcpINESyyQIqySlgl 2I6yQ2xmtpEQilgQI4EUiAQaQQtCFAiNQjLVUWbsjII19iS2u931vI9zzseiqtxP21V9b7vrlr/f pp/33HPr/O53vvO4VYAgCIIgCIIgCIIgCILwLEBnUz6f8mmfZR7ZpKfyop+GQPvKHNX52mmLKjyS K8NGONAEhclUdMMOy2PsSdMcfr08+LK+JTKdNhurQ0GuD3/RGP58hfc1eSESeUUUMuSQPE0CLgPr ILRv0xp2EMd3CHf3DkiS+yJTQYTh0p4QjS1E0TJvYhGorzA2VjGQ6RoAHklEKECiIhuQDsiz3qC1 9jLF8R1KknNkzDYZs0vAi3CuJeKcEkrNM3ALnrfAnneOw3Cbo2iZN+t3GBtNHkSjh5FoagQ6Ik/j LlSSnKM4NirLbit3fo7Y9AhYgrP9vTjq+iJTTkhVHoqgdIWB+yCvyupeh31/xUWR58Jwm5sX4Abd 21ZhEhUo0NUD8nS7UGkKlS1C2QRq3kA5F5KrJ8Q2IaAOdqnIUxCkAgbaIB2yaoesVMItD06HcP4O XBDA1WpwA4lGkegqYwoEok++ygE/3/+CJv6MVrjIwEIB5QpFw9xyUG9ZuL+Jt+6+tnH1vUneKJRX IPrEN/nFiHrfJqBhHJBYwLqcpQqnglZAqAFPAc7xG0miX3n9s9GtPCOzkwpEAPDJVznQy73NxKBx p8PoZjJBWAbmAsJyjeArfqP747sf2rj6V+mhIf6IJzaomvDchIcJM4Of73+hm6Lx0wdO5CkRnZRx 84FDz9AHgpeWPj+Ycrk2zGOv0j5vnhhgJhHo0DzPNXJwn77VcnDiTulwDNxqOZDWn8LaosI6FLBK B2Vi4GHQOJ5xJxKPThKuN+h+D5esyFNaLAPbfXfp0m6owhgcre0MJx8xnDN68sTjBDPRI3lWCeug xl2oTsbVgq5FOCNaKeba7dCL43nX7d7hS17IYQxurjccNpo4JNERxunC6KA8Ww/neTJb4JUIZ0Jm gbSmvbj2rtefr+leb0l3u1CNu1BYb+zr0h52ZwcYMwcadVt78vT7HS29V/lhANVAedX+olfrWC+t 93SaDtr4oETH8ySB9mXkl7HWXqbRDLN97sKkIzhhGmGmNA79xE/8JHDeXFLVab2i03SwFLXWvj1Y FN+LQgcYX4L1LYrjO2TMLmWLUHNJXxd4GcIZ4uvMDzx4WdLxsijQc0lfZ4tQcfy2iuMVOrQF50A0 GiOJHnZf7UVKkpCcWyKbQJkalF/whQhng/XIQwYKgwBpljIZsE2gnFtwgy03Sxh2Y0dC0JgR6DLW sAZjlsier9G8gXLZxJOQwpTiZ/Csb7Q1mY5Mql0V6jkXkT3fIWOWht0YcNzO0oklcLZLzsVkLRSY ZTV9BnAM5RtPs4NyUUDOBeRcTM7ODdt37ZHHji3QKP9h0yPnIpKtGLMDe1BgQy4g5SwUc0rOxYfa 9/Kxx55gS+sSsNAHerUTVFWYRpg9YoDAIOaM2PEgSJgeGZNRHIOAxWOPHTMCjbLwF/edNJMINDNY YjYUPvx5/OBwghyoP9xRKMwWIdgZAioTBQcZSQkD2J4oKIhAQi5EICEXRT5Y+FR56ZzC5z7o46Mv eHi+RvBLditkDvhFl/Hddwy+/oMMb+2UcyN5KQX67Ad8fOXjIXSJU3lfAS/UCZff7+MPXvZx9TsJ /rqZnXW1JqZk9y3wykUPV36z3PIcRivgq78V4nd/tXz3c6kECjTw5Y+Fp/6eNGcBAfjKx8LSdcWl qu5HVjTeU5tFfQYszxE+vFKuXTKlEuilc6Wq7ol4uWTXWKra1vzZjT4j5kp2jaUSSJg+RCAhFyKQ kIvyTTw8ht2kHA8aLYSPznNePqfwexc97MSM/3nXIpnyZ+9mSqBf/8supv1RawJw80/mHvn3Vy55 eOXSoFluPnD4o3/o45329F6UdGFTzMXnFL78G+GT//EMEYGeMnrCV/xDy9M9sSgCPWUmXaqoTPm8 kAgk5EIEespM+mZcjqc3gQZmbBR2HH/+2xF+7fmzuU9++AuHL/1LfOB36YTD8p9sT/dGs5kX6MU6 4aXFsxFop58vemQO+Iv/TAuqzekw8wKVjTe3HW7sOPxfy+Hv3zR4UyKQMAn/eMPgtc3pjjr7kSRa yIUIJORCBBJyIQIJuRCBhFyIQEIuRCAhFyKQkAsRSMiFCCTkQgQSciECCbkQgYRciEBCLkQgIRci kJALEUjIhQgk5EIEEnIhAgm5EIGEXIhAQi5EICEXIpCQCxFIyIUIJORCBBJyIQIJuRCBhFyIQEIu RCAhFyKQkAsRSMjFzL9D2a02Y/GMPhH51hR/REFRzLxAh98lVSgW6cKEXIhAQi5EICEXIpCQCxFI yMVMjcL++49rZ12FZ46ZEuhxn0UqnA7ShQm5EIGEXIhAQi5KJVAnm/21pXZarmsslUA3pvyzs4rg rTNa+D0ppRLoe7ct3u2W6w6dhDsdxn/dnvAzMc+YUgmUWuBr/5FgFhViAF/9ToKsXAGoXAIBwLdv Glz99wS2ZC/047AO+LN/S/BPb5uzrsrElHIi8RtvZPjebYvPfdDHR1/w8Es1gi7ZrWAc8PMu47vv GHz9B1npcp8RpRQIAN7advjTf00AJGddlWeakt23wrQhAgm5EIGEXIhAwgDSJ5odmVggpStMOpzF qZhnnASkPAb6Ex01pkCrQ2FuTVgpoRxoJvJ4NJ4lGn9B7gRd2H1gd7KTCNMNkWGCZpBhIp9JBaxU zORV2fMWOIqWH9nWYwsURcvseQtMXnVQuApEoBmBDBzIskojRypjooCVig617/Vjjz1BDlRjpSLW Gg5EItEMoAgu87Ql1Xc6YadUykpFrHRn2L6bjz52vFNcxyY24XnnWN/rcsuDI5WIPDNC5sPozFql 4foKTvXgHqiY9b059rz7vFlfGbb1lSNtPoZAVxhoMOorHIbbrNQ86xDOj8Ny7TsQHok2bJSX2TRj 4/mB9Tw4HcIptcthuDSUpnFswBi/C9tY5UEe1GJ/B67loZyrf8IRUsMms37mh3MmSGE6YcX6O3BR 9D4XRbcZG6v75Tkg0pMEYmD0qMx1bNbvcK0GFwRwfrVjwdKLzQAchEHmJzBhqkwn7Fn94K4LArgw 3B52X6ME+uhjU2NGoGE3trHKzeg212pwldacBUsQKj3s0Df9LK5VsnYltkG7aiuVOVurwTUvwGGj OWj7Y/IfYDyBhlHoCgNbjM1PuOYFuFoNDnHaK/ZqhKcNx0k76FoTtPu21oWpVu/vk2d1nzwE4Ohm 0AmG8fsk2mhy8wKc6/duzOT+0mcFZnC//9N6/ZfN4iJMrQZ3YyHZF3m29stzLONuKGMANCiIGbgG bDTBH8+uc2t3jebnAZLHiksFM7jdBiW9v72x8IZDfWWYLDcBrDDQwCF5jg0Vk+xIPCKR+8ndV+n9 5/8QzjWoXge80m5wfLYwGbjdAZLkR+n/3nwNmztuIM0W9sQBniTPgf+YEBqV+ytf/OF7qVL5Fitq kO8D2gdkgno6YQJsBs4yENOP2HV//2df+7ufDf54RBrgMeKMyNvvEMB4z+98o1r5yIc/D8efBqmL rNR8znKFU4Act8C4odhe737/+6/9/J+/FQ+S5KsjUSa+8wsRCLhGAFSjAXXvXsdPa+e9auK81E/9 wKt4Jkt9Z632fa3ZQTFbAhQhAJidJE85IVKMFAAcE2kmBZdl1iqtrfbIZNZkfgoTV5FW491sYWHB DhJluMeNsMYhb9LCg+z5KoBVjqLXuVJZsejeQ78+xxUvQZakHEaBsybTzkJzpCmwRgEAsyfyFAE7 UGAYAFKtHSWW/TCz2oNN0tQGEZtQOWPb1nqV4faMjcVhznNyeYD8EWhYBgP4lMJ6gxp3oXZ353Wy 0NK1LrwsCrTJUh2ZVNuQlLNQQAVgQ8yZCFQQRP5AAspYaTidsIu9wHp+YP04td0aTLg7bxcWWnZv mP5Nl1egAodNDcbGKpprO3wpSZzbAXXqPTsXA2TAWTVw1kKx84k5I+aU5A3piqOLFEQBE1WZkLEL 4fwenGdT1wl7Ntipuqjqub3oU8wMXhEC7evGrgP1Bocx2Bg4tKvoLoJtAjWfQjkXk3MgnksIAAzq BZxeAIAQbQAJqANWKmZlI25Fg1X1YKe6b20LDLyOwXA9X/QBiunChuWMkukmjbqyJDlHcfy2cm6B 7PkaOdslNj1ytipd1ymhdG+wa1TXWN/rslK7HEXvc2G4zXvLEwdmmHMJVGAXdnCWurnecGvtkMJw gZPkPpkWyJjdoTjzcK4lEhWMUvMM7ALYheeBvep9DsMljqJwEHk2msOu6/HLE5NQlEDHLnVsYpWx fofQTmgNy4jjUa1bBZ1WOEgLwAKAwR72TdwG6hjmPK/j0MIoUEAeVHQUeDhDPZwbGtIk4PLw+y3C esFnFfbYAPYew7qOgzsJi5UHKF6gQ+WO6nhNuqszY/x1rZNw2g0r4kwPskApCIIgCIJQEP8P03/B 430mofMAAAAASUVORK5CYII= "
+       style="opacity:0.49074079;image-rendering:optimizeQuality"
+       preserveAspectRatio="none"
+       height="28.670942"
+       width="28.670942" />
+    <image
+       y="0.0055872202"
+       x="-0.031073861"
+       id="image3082"
+       xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJAAAACQCAYAAADnRuK4AAAABHNCSVQICAgIfAhkiAAAAAlwSFlz AABO4wAATuMBYdZxSQAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAAZdEVY dFRpdGxlAFR3ZWxmIEljb24gVGVtcGxhdGXyQOONAAAADHRFWHRBdXRob3IAeHBobnh/4OzMAAAA Y3RFWHRDb3B5cmlnaHQAQ0MgQXR0cmlidXRpb24tTm9uQ29tbWVyY2lhbC1TaGFyZUFsaWtlIGh0 dHA6Ly9jcmVhdGl2ZWNvbW1vbnMub3JnL2xpY2Vuc2VzL2J5LW5jLXNhLzQuMC9D0yJcAAAL6klE QVR4nO3dTYvcRhrA8adKJZW6Zc0LmTEmmYUF5zYOJjQLOSTQEMhlA7uHsNlPsCHg5DP4MzghHyC3 xZvTGh8CgYaQXMwEkngOZo1f2JAYz2ReLHePSqqqZy+t2Z5xj6c1pbZa7ecHxo09LdVEf0rVkjIG IIQQQgghhBBCCCEvA9bw7ZPJ4TQ2Oo0DPG6bFFJ9xoVTWUxVH1g25vXxfTAAgG63W/GuSa/XK14e DwTH/HklEVUZ0PF4ilBYkiQMACBN0yP7U0rRzFQRKeWRIMIwRACAOI6x1+uNBlRpRNMIiAEAK8JJ 05QppZjWmmmtGQCAtZbCmRLOOQIACCFQCIFSSgzDEEdCGo1oZgJ6Jp6trS2ulGJpmvI8z/nKysph QMaYw/1STO6KaAAAPM87DGh7ext937dhGFopJa6urtqqI6oyoCPx9Pt9nmUZX15e5koprrXm1lpm rWWIyOI4pngqxDnHJEmAMYacc+ScoxDCSint7u6uDYLARlFkx0RUe0Ds2rVrQRRFn3qe93fO+UVE XKxgu6R6TxDxP4j4z4cPH3529epVNfzzM0fkGhD78ssv/2CtvckYW7fWgtYaEKdyyYE4YoyBEAI4 54CIt5VSf/7oo4/+O/IlpQ/cWQNiAADXrl0LFhYWNowx60mSQJZlZ9wceZGCIIA4jsHzvNsPHjz4 09WrVzM44/UiXnLfhx/PAQCiKPo0z/P1nZ0diqdBsiyDnZ0dyPP80tra2hUYWcPC+Gt5JyoT0DPX eRhjH+7t7dEpq4EQEfb29oAx9rdOp8O73S6H8SEdj+oIMeH+xl0kZP1+/3WKp7kQEQ4ODl7f39/n aZpip9NBAICNjY1xH/MZjDmlTRpQ4chH9SzL2mcdPJkNSqlzSZKINE1tv99HIQSur68X14yKLztx lpjkFDb2ImG/3+fGGLfRk9oZYyCKIhFFkVhYWPAGg4HX7/f51tYW73a7405nR0y6BnomnoODA6+i 74HUTCklDg4OxNOnT0Ucx16WZXxMRGOddgo78sYkSVhxhXlpaansJzgygxCR+b7ve55nfd83aZqa OI4hSRIQQuDwRjjCCWugiSPodrssTVOmtWbD2xM0A80Jz/P8LMuEUkqEYegppbzl5WWepilP05QN Z6HCkUllkkU0AwCWJAlTSjFrLSvubVX6XZDaaK2F7/ssz3PgnKPWGpVS3FprRx65cZuBhjtiw7vq vN1uU0BzwvM8YYzxgiDwtNZeu93m1lpmjGFa68PnucBhEX3IGMOGG+eISHfT5wAiciGEZ609fGKi +HXaeycOqFj/aK0n3jhpBmstR0RmreVSyuI1O3/+/KnvpdMQgeGZhCEiK+Kx1h5OGMcfRR5V5jrQ uJ2SORAEAUNEFoZh6feeaQ1E8cynYllS5vjSKYwAwNnPKBQQcUIBESdlH+eYGa+++ip0Oh1YW1uD KIqAsWYty4wxkCQJ3L9/H27dugX7+/t1D+lMGhnQO++8A2+99Vbdw3DieR4sLS3Bm2++CW+88Qbc vHkT7ty5U/ewSmvcKazT6TQ+nuOEEPD+++/D2tpa3UMprVEBtVotePvtt+sexlRwzuHdd9+texil NSqgixcvQhAEdQ9jas6fPw8rKyt1D6OURgXUtP+4Z/HKK6/UPYRSGhWQ7/t1D2HqpJR1D6GURgVE Zg8FRJxQQMRJIy8kjoOIoJQ6/Qtrxhh77jrnwoULoJSCfr8Pv/76K1hrX+DoypubgAaDAXzxxRd1 D+NU586dg48//vjEv798+TJcvnwZAAAePXoE169fhzRNX9TwSqNT2Ay7cOHCzF84pYBesHa73I8T mPXbGxTQC1b2WtasX/uigIgTCugFK/upatZ//tLcfAo7yQcffAALCwu17PvevXuj//wAAAA8ffq0 1Da2trYqHFH15j6gpaUlWF5ermXf4w5+mRklyzL4/vvvqxxS5eY+oKZ59OgR7O3twc7ODty+fXvm H3WlgGbMjz/+CD/99FPdw5gYLaKJEwqIOKGAiBMKiDihgIgTCog4oYCIEwqIOKGAiBMKiDihgIgT Cog4oYCIEwqIOKGAiBMKiDihgIgTCog4oYCIEwqIOKGAiBMKiDihgIgTCog4oYCIEwqIOKGAiBMK iDihgIgTCog4oYCIEwqIOKGAiJO5/wllu7u7YIypZd9JktSy3xdp7gP66quv6h7CXKNTGHFCAREn FBBxQgERJxQQcTI3n8La7TZ88skndQ/jVIyxuodQqbkJiDEGYRjWPYyXDp3CiBMKiDihgIiTRgWU ZVndQ5g6pVTdQyilUQH9/vvvdQ9h6pr2PTYqoLt37871LPT48WPY3t6uexilNCqgNE3h22+/rXsY U2GthW+++abuYZTWqIAAAH744YeZ/3dEy8rzHG7cuAG//PJL3UMprZEXEr/77ju4d+8edDodWFtb g3PnzjXuCq8xBp48eQL379+HW7duwZMnT+oe0pk0MiAAgN9++w1u3LhR9zBeeo07hZHZQgERJxQQ cUIBEQAAYIzhWd5XOiDP8/CsOyOzjXNe+rhOGhAFM8eyLEPGGKZpCgDlZqMzn8JoFpofw2OJjDFk jCHnHDnnKIRAIQSGYXjisZ44oDAMDzdY7KCS0ZPacc7tMBybpulhRI8fPz79vWV35nkecs7R8zxL s9B8YIxZrbXhnFvP82wxQUwySZQKSAiB29vbKISw/X6f4pkTxhjteZ4ZxmMHg4EdThIohMA4jotj /cwxnyQgBACM4xillMg5Ryml9X2/np9YQConhNBZlpk8z7Xv+0YIYaWUdnisT4wHoMQM1Ov1sFgH 7e7uWiGErWT0pHZaay2lzKWUOssyLaU0u7u7NgxDG4Yh9nq90XiOhHTazVQEgMPb3HEcY5qmFgAg SRKDiI27C06egVLKfDAYWEQ0Ukq7t7dnW62WlVKOnr7GKnMdCItZKIoi22q1DCItg5oOEUFrnUdR lLdaLZMkiWm1WiaKIru6umqHs0/x6xmTroGK33FjY8Ourq7aKIpslmWDir4PUhOlVNLv93WSJKbf 7+t2u/28eJ6JqOzzQAgA0Ov1oNvt2sFgcFdK2aHTWDMhIgwGg3txHOtisRyGIW5sbIyGU9kpbPQ1 9no91FpfT5IE6FTWPIgISZKAMeZfi4uLdnV11W5ubpqNjQ0LABaejWfsQfZK7vfIVPPaa69tRFH0 V2PMed/3gXO6ud8EWmtIkgSyLPv5zp07/+j1evrBgwfPi+XEGeKs557D9125cuWPUsp/c87XhRAg RGOfkn0paK1Baw3W2p+VUn/5/PPPHw7/alwkp55aXBcvDADgvffea1+6dOkKAHzIGLvIOV9w3C6Z AmvtE0S8i4jXNzc3P/v6669TOGWRfJpKAhr+ztfX1/n29rYfRZFQSgnf9/08z4UQwjfGeL7ve9Za joiseO/wNXEwck+yuBFq8zw3nueZPM91EAR5lmW63W5naZrmi4uLZnNzc3StA3CGeADc/6+M0QuN GIYhtlot0+/3IY5jPDg4gCAIUGttpZSeMcYDABYEAQegeKpURJTnuUVElFKaLMvM8HcdhqEeXuMp Hs84dYE8iaoWLAgArLhSrbVmUkqmtTZSSuCco9YajTFWSskRERCRUUDVKQISQqBSyiKiDYLA+L5v ENG0Wi2TZZmNosg+7+Zo6f26bgCOnsZYp9Ph+/v7fDAYeHEce0opT2vN2+02N8Zway2jeKZj9IEw z/PsYDCwwxujJkkS0263zeLioh1+VHda+xSqmIHG3S9DrbVNkgSWl5dRKcWzLOPWWlYENPzaCnZP AP7/zyoUARljMAxDK6W0u7u7NgiCk+5tOc1CVc0CR2ahbrfLtra2uFKKpWnKrbXMGMNWVlaY1poZ Y2j2mZLiGZ7t7W0sHv4bhoST3p4oo+qAites2+2yJElYmqZMKcW01kxrffh11lqKqGKjTxAWjx9L KTEMQ4zjGE+4MToTAR3f1uHH9CIkAIA0TSmaF6R4EH4kHICK4wGoNqDj25vkNZmOkyKpNB6A6R3M 49ulaOpT6aL5uGkfWApndtAjE4QQQgghFfkfLnfEbGu/P8gAAAAASUVORK5CYII= "
+       style="display:inline;image-rendering:optimizeQuality"
+       preserveAspectRatio="none"
+       height="28.697369"
+       width="28.697369" />
+  </g>
+</svg>
diff --git a/app/src/main/assets/ic_distance.svg b/app/src/main/assets/ic_distance.svg
new file mode 100644
index 0000000..64b6a40
--- /dev/null
+++ b/app/src/main/assets/ic_distance.svg
@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   height="24"
+   viewBox="0 0 24.170443 24"
+   width="24.170443"
+   version="1.1"
+   id="svg6"
+   sodipodi:docname="ic_distance.svg"
+   inkscape:version="1.0.1 (3bc2e813f5, 2020-09-07)">
+  <metadata
+     id="metadata12">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs10" />
+  <sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="1920"
+     inkscape:window-height="1042"
+     id="namedview8"
+     showgrid="false"
+     inkscape:snap-others="false"
+     inkscape:object-nodes="false"
+     inkscape:zoom="26.103965"
+     inkscape:cx="22.380713"
+     inkscape:cy="6.9428897"
+     inkscape:window-x="0"
+     inkscape:window-y="38"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="svg6"
+     inkscape:document-rotation="0"
+     lock-margins="true"
+     fit-margin-top="2.5"
+     fit-margin-left="2.5"
+     fit-margin-right="2.5"
+     fit-margin-bottom="2.5" />
+  <path
+     d="M 1.7281728,0.0307995 H 25.728173 V 24.030799 H 1.7281728 Z"
+     fill="none"
+     id="path2" />
+  <g
+     id="g3324"
+     transform="matrix(1,0,0,1.0133493,2.5,2.097751)"
+     style="stroke-width:0.993391">
+    <path
+       d="m 6.0804285,11.546119 c -0.8360589,0 -1.5201071,-0.684048 -1.5201071,-1.520107 0,-0.8360578 0.6840483,-1.5201055 1.5201071,-1.5201055 0.836059,0 1.5201069,0.6840477 1.5201069,1.5201055 0,0.836059 -0.6840479,1.520107 -1.5201069,1.520107 z M 10.64075,10.178023 c 0,-2.7589943 -2.0141423,-4.712332 -4.5603215,-4.712332 -2.5461794,0 -4.5603214,1.9533377 -4.5603214,4.712332 0,1.778525 1.4821045,4.134691 4.5603214,6.94689 3.0782168,-2.812199 4.5603215,-5.168365 4.5603215,-6.94689 z M 6.0804285,3.9455838 c 3.1922253,0 6.0804285,2.4473729 6.0804285,6.2324392 0,2.523378 -2.029343,5.510388 -6.0804285,8.968632 C 2.029343,15.688411 0,12.701401 0,10.178023 0,6.3929567 2.8882037,3.9455838 6.0804285,3.9455838 Z"
+       id="path4"
+       style="stroke-width:0.993391" />
+    <path
+       d="m 16.105923,4.2275996 c -0.421371,0 -0.76613,-0.344759 -0.76613,-0.76613 0,-0.4213716 0.344759,-0.7661296 0.76613,-0.7661296 0.421372,0 0.76613,0.344758 0.76613,0.7661296 0,0.421371 -0.344758,0.76613 -0.76613,0.76613 z m 2.29839,-0.689517 c 0,-1.3905256 -1.015122,-2.3750026 -2.29839,-2.3750026 -1.283268,0 -2.29839,0.984477 -2.29839,2.3750026 0,0.896372 0.746977,2.083873 2.29839,3.501214 1.551413,-1.417341 2.29839,-2.604842 2.29839,-3.501214 z m -2.29839,-3.14113259 c 1.608873,0 3.06452,1.23346899 3.06452,3.14113259 0,1.271776 -1.022784,2.777221 -3.06452,4.520167 -2.041736,-1.742946 -3.06452,-3.248391 -3.06452,-4.520167 0,-1.9076636 1.455647,-3.14113259 3.06452,-3.14113259 z"
+       id="path4-3"
+       style="stroke-width:0.99339" />
+    <path
+       style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.993391;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       d="M 8.399119,18.046615 C 25.927241,13.211774 8.3489042,11.251336 15.01982,7.7851976"
+       id="path852"
+       sodipodi:nodetypes="cc" />
+  </g>
+</svg>
diff --git a/app/src/main/assets/ic_distance_total.svg b/app/src/main/assets/ic_distance_total.svg
new file mode 100644
index 0000000..330795e
--- /dev/null
+++ b/app/src/main/assets/ic_distance_total.svg
@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   height="24"
+   viewBox="0 0 24.170443 24"
+   width="24.170443"
+   version="1.1"
+   id="svg6"
+   sodipodi:docname="ic_distance_total.svg"
+   inkscape:version="1.0.2 (e86c870879, 2021-01-15)">
+  <metadata
+     id="metadata12">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs10" />
+  <sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="1920"
+     inkscape:window-height="1042"
+     id="namedview8"
+     showgrid="false"
+     inkscape:snap-others="false"
+     inkscape:object-nodes="false"
+     inkscape:zoom="26.103965"
+     inkscape:cx="22.380713"
+     inkscape:cy="13.072227"
+     inkscape:window-x="0"
+     inkscape:window-y="38"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="svg6"
+     inkscape:document-rotation="0"
+     lock-margins="true"
+     fit-margin-top="2.5"
+     fit-margin-left="2.5"
+     fit-margin-right="2.5"
+     fit-margin-bottom="2.5" />
+  <path
+     d="M 1.7281728,0.0307995 H 25.728173 V 24.030799 H 1.7281728 Z"
+     fill="none"
+     id="path2" />
+  <path
+     d="m 11.238544,10.768935 c -0.836058,0 -1.5201071,-0.693181 -1.5201071,-1.5404014 0,-0.8472175 0.6840491,-1.5403975 1.5201071,-1.5403975 0.836059,0 1.520107,0.69318 1.520107,1.5403975 0,0.8472204 -0.684048,1.5404014 -1.520107,1.5404014 z m 4.560322,-1.3863614 c 0,-2.795824 -2.014142,-4.7752374 -4.560322,-4.7752374 -2.5461791,0 -4.5603211,1.9794134 -4.5603211,4.7752374 0,1.8022694 1.4821045,4.1898874 4.5603211,7.0396274 3.078217,-2.84974 4.560322,-5.237358 4.560322,-7.0396274 z M 11.238544,3.0669367 c 3.192226,0 6.080429,2.4800436 6.080429,6.3156369 0,2.5570654 -2.029343,5.5839484 -6.080429,9.0883574 -4.0510852,-3.504409 -6.0804282,-6.531292 -6.0804282,-9.0883574 0,-3.8355933 2.8882037,-6.3156369 6.0804282,-6.3156369 z"
+     id="path4"
+     style="stroke-width:1" />
+  <path
+     d="m 20.107749,4.7842441 c -0.421371,0 -0.76613,-0.3493613 -0.76613,-0.7763573 0,-0.4269966 0.344759,-0.7763569 0.76613,-0.7763569 0.421372,0 0.76613,0.3493603 0.76613,0.7763569 0,0.426996 -0.344758,0.7763573 -0.76613,0.7763573 z m 2.29839,-0.6987216 c 0,-1.4090881 -1.015122,-2.4067071 -2.29839,-2.4067071 -1.283268,0 -2.29839,0.997619 -2.29839,2.4067071 0,0.908338 0.746977,2.1116913 2.29839,3.5479528 1.551413,-1.4362615 2.29839,-2.6396148 2.29839,-3.5479528 z m -2.29839,-3.18306443 c 1.608873,0 3.06452,1.24993493 3.06452,3.18306443 0,1.2887533 -1.022784,2.814295 -3.06452,4.5805085 -2.041736,-1.7662135 -3.06452,-3.2917552 -3.06452,-4.5805085 0,-1.9331295 1.455647,-3.18306443 3.06452,-3.18306443 z"
+     id="path4-3"
+     style="stroke-width:0.999999" />
+  <path
+     style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+     d="M 6.24625,21.451735 C 24.781737,20.731762 10.875735,11.944143 22.329352,9.1467685"
+     id="path852"
+     sodipodi:nodetypes="cc" />
+  <path
+     d="m 4.0025394,17.725181 c -0.421371,0 -0.76613,-0.349362 -0.76613,-0.776358 0,-0.426996 0.344759,-0.776356 0.76613,-0.776356 0.421372,0 0.76613,0.34936 0.76613,0.776356 0,0.426996 -0.344758,0.776358 -0.76613,0.776358 z m 2.29839,-0.698722 c 0,-1.409088 -1.015122,-2.406707 -2.29839,-2.406707 -1.283268,0 -2.29839,0.997619 -2.29839,2.406707 0,0.908338 0.746977,2.111691 2.29839,3.547953 1.551413,-1.436262 2.29839,-2.639615 2.29839,-3.547953 z m -2.29839,-3.183064 c 1.608873,0 3.06452,1.249935 3.06452,3.183064 0,1.288753 -1.022784,2.814295 -3.06452,4.580509 -2.041736,-1.766214 -3.06451998,-3.291756 -3.06451998,-4.580509 0,-1.933129 1.45564698,-3.183064 3.06451998,-3.183064 z"
+     id="path4-3-5"
+     style="stroke-width:0.999999" />
+</svg>
diff --git a/app/src/main/assets/ic_galaxy_buds_l.svg b/app/src/main/assets/ic_galaxy_buds_l.svg
new file mode 100644
index 0000000..7db1cba
--- /dev/null
+++ b/app/src/main/assets/ic_galaxy_buds_l.svg
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   width="22.722658mm"
+   height="24.000002mm"
+   viewBox="0 0 22.722658 24.000001"
+   version="1.1"
+   id="svg7609"
+   inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)"
+   sodipodi:docname="ic_galaxy_buds_l.svg"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg">
+  <sodipodi:namedview
+     id="namedview7611"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageshadow="2"
+     inkscape:pageopacity="0.0"
+     inkscape:pagecheckerboard="0"
+     inkscape:document-units="mm"
+     showgrid="false"
+     fit-margin-left="2"
+     fit-margin-right="2"
+     fit-margin-bottom="2"
+     fit-margin-top="2"
+     inkscape:zoom="5.0814254"
+     inkscape:cx="74.093384"
+     inkscape:cy="61.104902"
+     inkscape:window-width="1920"
+     inkscape:window-height="1035"
+     inkscape:window-x="0"
+     inkscape:window-y="45"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="layer1" />
+  <defs
+     id="defs7606" />
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(4.0001844,3.9998056)">
+    <g
+       id="g1406"
+       transform="matrix(1.3339122,0,0,1.3438174,-98.67174,-164.47229)"
+       style="display:inline;stroke:#000000;stroke-width:0.74690652;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1">
+      <path
+         style="fill:none;stroke:#000000;stroke-width:0.74690652;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         d="m 80.707855,121.04264 c -1.01885,0.26765 -1.65323,1.66683 -2.43737,2.32029 -1.04852,0.87375 -2.21948,1.35316 -2.91032,2.60741 -0.52565,0.95434 -0.38154,2.31718 -0.93985,3.15709 -0.29192,0.43915 -0.8728,0.64296 -1.20255,1.07824 -1.02698,1.35557 -0.91369,3.40161 0.31962,4.59246 0.81316,0.78509 2.09427,1.10747 3.1937,0.85385 0.46858,-0.10813 0.92029,-0.49721 1.41111,-0.45694 1.1463,0.094 1.85787,0.65296 3.07879,0.33508 4.31184,-1.1226 5.92705,-6.16249 4.97237,-10.0729 -0.50492,-2.06804 -3.02696,-5.06045 -5.4855,-4.41458"
+         id="path3559" />
+      <path
+         style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.74690652;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         d="m 75.335215,131.12944 c -0.94858,1.31848 -0.26706,3.35249 2.35997,3.35394"
+         id="path10585"
+         sodipodi:nodetypes="cc" />
+      <path
+         style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.74690652;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         d="m 77.749325,125.52752 c 1.77783,-0.71339 3.04493,-0.68717 4.67949,-0.21743 0.76552,0.22 1.36638,1.12151 1.42838,1.9156 0.28599,3.66326 -1.93567,5.44894 -4.17558,5.28724 -1.3729,-0.0991 -2.75373,-1.82993 -3.08505,-3.34193 -0.27878,-1.27217 -0.0559,-3.1585 1.15276,-3.64348 z"
+         id="path5256"
+         sodipodi:nodetypes="ssssss" />
+      <path
+         style="fill:none;stroke:#000000;stroke-width:0.74690652;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         d="m 79.296745,123.82804 c 2.35625,-0.33954 4.76904,0.66271 5.7504,2.912 1.31529,3.01478 -0.58112,7.91081 -4.08272,8.42805 -6.4448,0.95186 -8.53815,-10.35004 -1.66768,-11.34005"
+         id="path3567" />
+    </g>
+    <g
+       aria-label="L"
+       id="text23582"
+       style="font-size:7.05556px;line-height:124%;font-family:Sans;-inkscape-font-specification:Sans;letter-spacing:0px;word-spacing:0px;stroke:#000000;stroke-width:0.264583px">
+      <path
+         d="M 6.9078682,5.6385593 H 7.6037779 V 10.196424 H 10.108364 V 10.78209 H 6.9078682 Z"
+         id="path24331" />
+    </g>
+  </g>
+</svg>
diff --git a/app/src/main/assets/ic_galaxy_buds_live.svg b/app/src/main/assets/ic_galaxy_buds_live.svg
new file mode 100644
index 0000000..48c148d
--- /dev/null
+++ b/app/src/main/assets/ic_galaxy_buds_live.svg
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   width="22.722673mm"
+   height="24.000002mm"
+   viewBox="0 0 22.722673 24.000002"
+   version="1.1"
+   id="svg1002"
+   inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)"
+   sodipodi:docname="ic_galaxy_buds_live.svg"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg">
+  <sodipodi:namedview
+     id="namedview1004"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageshadow="2"
+     inkscape:pageopacity="0.0"
+     inkscape:pagecheckerboard="0"
+     inkscape:document-units="mm"
+     showgrid="false"
+     fit-margin-left="2"
+     fit-margin-top="2"
+     fit-margin-right="2"
+     fit-margin-bottom="2"
+     inkscape:zoom="5.0814254"
+     inkscape:cx="-11.709313"
+     inkscape:cy="29.617674"
+     inkscape:window-width="1920"
+     inkscape:window-height="1035"
+     inkscape:window-x="0"
+     inkscape:window-y="45"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="layer1" />
+  <defs
+     id="defs999" />
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(2.0000914,1.9999036)">
+    <g
+       id="g3814">
+      <path
+         style="fill:none;stroke:#000000;stroke-width:0.500001;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         d="M 6.3534766,13.150028 C 5.2590543,14.102574 3.7589552,14.39697 2.6009409,13.886465 1.3699015,13.343766 0.52234461,12.049487 0.50706651,10.688971 0.49818111,9.8974897 0.68425351,9.3259096 1.2026925,8.5521587 1.6467421,7.8894314 1.7546703,7.5633672 1.7383195,6.9339544 1.7242951,6.3940166 1.5887612,6.0540278 1.1304302,5.409047 0.69461341,4.7957481 0.51189951,4.3487514 0.43456241,3.7066569 0.29489991,2.5470862 0.74072801,1.4253568 1.6710145,0.59567225 2.8470953,-0.45322535 4.5920415,-0.46138405 5.9962565,0.57544755 7.6247819,1.7779049 8.5957069,4.0874446 8.6379302,6.8591974 8.6637127,8.551633 8.3637849,10.047519 7.7501356,11.287081 7.5146695,11.762713 7.3453654,12.031594 7.0219979,12.443468 6.7260442,12.820427 6.6974699,12.850626 6.3534853,13.150033 Z"
+         id="path11168-8" />
+      <path
+         style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+         d="m 6.5908811,5.6846666 c 0,0 0.5148355,0.502981 0.476276,1.6957252 -0.03255,1.0068473 -0.6176138,1.4771457 -0.6176138,1.4771457"
+         id="path2468"
+         sodipodi:nodetypes="csc" />
+    </g>
+    <g
+       id="g3814-7"
+       transform="matrix(-1,0,0,1,19.129886,6.2222493)">
+      <path
+         style="fill:none;stroke:#000000;stroke-width:0.500001;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         d="M 6.3534766,13.150028 C 5.2590543,14.102574 3.7589552,14.39697 2.6009409,13.886465 1.3699015,13.343766 0.52234461,12.049487 0.50706651,10.688971 0.49818111,9.8974897 0.68425351,9.3259096 1.2026925,8.5521587 1.6467421,7.8894314 1.7546703,7.5633672 1.7383195,6.9339544 1.7242951,6.3940166 1.5887612,6.0540278 1.1304302,5.409047 0.69461341,4.7957481 0.51189951,4.3487514 0.43456241,3.7066569 0.29489991,2.5470862 0.74072801,1.4253568 1.6710145,0.59567225 2.8470953,-0.45322535 4.5920415,-0.46138405 5.9962565,0.57544755 7.6247819,1.7779049 8.5957069,4.0874446 8.6379302,6.8591974 8.6637127,8.551633 8.3637849,10.047519 7.7501356,11.287081 7.5146695,11.762713 7.3453654,12.031594 7.0219979,12.443468 6.7260442,12.820427 6.6974699,12.850626 6.3534853,13.150033 Z"
+         id="path11168-8-1" />
+      <path
+         style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+         d="m 6.5908811,5.6846666 c 0,0 0.5148355,0.502981 0.476276,1.6957252 -0.03255,1.0068473 -0.6176138,1.4771457 -0.6176138,1.4771457"
+         id="path2468-7"
+         sodipodi:nodetypes="csc" />
+    </g>
+  </g>
+</svg>
diff --git a/app/src/main/assets/ic_galaxy_buds_live_l.svg b/app/src/main/assets/ic_galaxy_buds_live_l.svg
new file mode 100644
index 0000000..1114bb3
--- /dev/null
+++ b/app/src/main/assets/ic_galaxy_buds_live_l.svg
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   width="24mm"
+   height="24mm"
+   viewBox="0 0 24 23.999999"
+   version="1.1"
+   id="svg7609"
+   inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)"
+   sodipodi:docname="ic_galaxy_buds_live_l.svg"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg">
+  <sodipodi:namedview
+     id="namedview7611"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageshadow="2"
+     inkscape:pageopacity="0.0"
+     inkscape:pagecheckerboard="0"
+     inkscape:document-units="mm"
+     showgrid="false"
+     fit-margin-left="3"
+     fit-margin-right="3"
+     fit-margin-bottom="2"
+     fit-margin-top="2"
+     inkscape:zoom="4.3195774"
+     inkscape:cx="13.311487"
+     inkscape:cy="62.390362"
+     inkscape:window-width="1920"
+     inkscape:window-height="1035"
+     inkscape:window-x="0"
+     inkscape:window-y="45"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="layer1" />
+  <defs
+     id="defs7606" />
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(1.1500002,4.0943479)">
+    <g
+       aria-label="L"
+       id="text23582"
+       style="font-size:7.05556px;line-height:124%;font-family:Sans;-inkscape-font-specification:Sans;letter-spacing:0px;word-spacing:0px;stroke:#000000;stroke-width:0.5;stroke-miterlimit:4;stroke-dasharray:none"
+       transform="translate(3.1921316,-0.30467255)">
+      <path
+         d="M 6.9078682,5.6385593 H 7.6037779 V 10.196424 H 10.108364 V 10.78209 H 6.9078682 Z"
+         id="path24331"
+         style="stroke-width:0.5;stroke-miterlimit:4;stroke-dasharray:none" />
+    </g>
+    <path
+       style="fill:none;stroke:#000000;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       d="M 13.383555,16.454104 C 11.867284,17.773042 9.788966,18.180675 8.1845922,17.473808 6.4790457,16.722363 5.3047953,14.930245 5.2836284,13.046414 5.2713197,11.950494 5.529112,11.159059 6.2473848,10.087689 6.862595,9.1700478 7.0121239,8.7185645 6.9894715,7.8470523 6.9700367,7.0994299 6.7822655,6.6286664 6.1472693,5.7355971 5.5434653,4.8863961 5.2903238,4.2674645 5.1831776,3.3783918 4.9896817,1.7727986 5.6073548,0.21960185 6.896223,-0.92921641 c 1.629404,-1.45235029 4.046946,-1.46364719 5.99242,-0.028004 2.256246,1.66497608 3.601416,4.86286791 3.659915,8.70076011 0.03572,2.3434213 -0.379816,4.4146913 -1.229998,6.1310443 -0.326227,0.658582 -0.56079,1.030886 -1.0088,1.601185 -0.41003,0.521954 -0.449618,0.563769 -0.926193,0.978342 z"
+       id="path11168" />
+  </g>
+  <g
+     inkscape:groupmode="layer"
+     id="layer2"
+     inkscape:label="trace"
+     transform="translate(-2.8501842,0.0945423)" />
+</svg>
diff --git a/app/src/main/assets/ic_galaxy_buds_live_r.svg b/app/src/main/assets/ic_galaxy_buds_live_r.svg
new file mode 100644
index 0000000..0e59629
--- /dev/null
+++ b/app/src/main/assets/ic_galaxy_buds_live_r.svg
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   width="24mm"
+   height="24mm"
+   viewBox="0 0 23.999999 24"
+   version="1.1"
+   id="svg1002"
+   inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)"
+   sodipodi:docname="ic_galaxy_buds_live_r.svg"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg">
+  <sodipodi:namedview
+     id="namedview1004"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageshadow="2"
+     inkscape:pageopacity="0.0"
+     inkscape:pagecheckerboard="0"
+     inkscape:document-units="mm"
+     showgrid="false"
+     fit-margin-left="3"
+     fit-margin-top="2"
+     fit-margin-right="3"
+     fit-margin-bottom="2"
+     inkscape:zoom="6.1088053"
+     inkscape:cx="37.077626"
+     inkscape:cy="50.009779"
+     inkscape:window-width="1920"
+     inkscape:window-height="1035"
+     inkscape:window-x="0"
+     inkscape:window-y="45"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="layer1" />
+  <defs
+     id="defs999" />
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0.55009208,2.2464716)">
+    <g
+       aria-label="R"
+       id="text20823"
+       style="font-size:7.05556px;line-height:124%;font-family:Sans;-inkscape-font-specification:Sans;letter-spacing:0px;word-spacing:0px;stroke:#000000;stroke-width:0.5;stroke-miterlimit:4;stroke-dasharray:none"
+       transform="translate(2.5829786,-0.5449331)">
+      <path
+         d="m 9.0560585,10.458658 q 0.2239314,0.07579 0.4340823,0.32384 0.2135961,0.248047 0.4271921,0.682129 l 0.7062451,1.4056 H 9.8759918 L 9.2179781,11.550754 Q 8.9630409,11.03399 8.721884,10.86518 8.4841723,10.69637 8.0707606,10.69637 H 7.3128391 v 2.173857 H 6.6169293 V 7.726696 h 1.5709646 q 0.881945,0 1.3160273,0.3686254 0.4340823,0.3686255 0.4340823,1.1127666 0,0.4857588 -0.2273764,0.806153 -0.2239314,0.320394 -0.6545686,0.444417 z M 7.3128391,8.2985822 v 1.8259018 h 0.8750548 q 0.5029843,0 0.7579215,-0.2308216 0.2583823,-0.2342666 0.2583823,-0.6855744 0,-0.4513078 -0.2583823,-0.6786843 Q 8.6908782,8.2985822 8.1878939,8.2985822 Z"
+         id="path21707"
+         style="stroke-width:0.5;stroke-miterlimit:4;stroke-dasharray:none" />
+    </g>
+    <path
+       style="fill:none;stroke:#000000;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       d="m 8.9163768,18.30198 c 1.5162552,1.318938 3.5945542,1.726571 5.1989132,1.019704 1.705531,-0.751445 2.87977,-2.543563 2.900937,-4.427394 0.01231,-1.09592 -0.245482,-1.887355 -0.963748,-2.958725 C 15.437275,11.017923 15.287747,10.56644 15.3104,9.6949274 15.32983,8.9473054 15.517604,8.4765419 16.152594,7.5834726 16.756392,6.7342716 17.009531,6.11534 17.116677,5.2262673 17.310171,3.6206741 16.692503,2.0674774 15.403647,0.91865918 13.774258,-0.53369127 11.356739,-0.54498817 9.4112835,0.89065518 7.1550587,2.5556312 5.8099017,5.753523 5.7514033,9.5914154 c -0.03572,2.3434216 0.3798124,4.4146916 1.2299864,6.1310446 0.326224,0.658582 0.5607847,1.030886 1.0087907,1.601185 0.4100257,0.521954 0.4496137,0.563769 0.9261844,0.978342 z"
+       id="path11168" />
+  </g>
+</svg>
diff --git a/app/src/main/assets/ic_galaxy_buds_r.svg b/app/src/main/assets/ic_galaxy_buds_r.svg
new file mode 100644
index 0000000..437a846
--- /dev/null
+++ b/app/src/main/assets/ic_galaxy_buds_r.svg
@@ -0,0 +1,82 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   width="22.722673mm"
+   height="24.000002mm"
+   viewBox="0 0 22.722673 24.000002"
+   version="1.1"
+   id="svg1002"
+   inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)"
+   sodipodi:docname="ic_galaxy_buds_r.svg"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg">
+  <sodipodi:namedview
+     id="namedview1004"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageshadow="2"
+     inkscape:pageopacity="0.0"
+     inkscape:pagecheckerboard="0"
+     inkscape:document-units="mm"
+     showgrid="false"
+     fit-margin-left="2"
+     fit-margin-top="2"
+     fit-margin-right="2"
+     fit-margin-bottom="2"
+     inkscape:zoom="5.0814254"
+     inkscape:cx="66.024782"
+     inkscape:cy="37.292686"
+     inkscape:window-width="1920"
+     inkscape:window-height="1035"
+     inkscape:window-x="0"
+     inkscape:window-y="45"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="layer1" />
+  <defs
+     id="defs999" />
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(2.0000914,1.9999036)">
+    <g
+       id="g12091"
+       transform="matrix(0.88927548,0,0,0.8958785,-64.445881,-120.41835)"
+       style="display:inline;stroke:#000000;stroke-width:1.1203592;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1">
+      <g
+         id="g12078"
+         style="stroke:#000000;stroke-width:1.1203592;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1">
+        <path
+           style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.1203592;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+           d="m 89.229326,149.75243 c 1.422876,1.97773 0.400592,5.02874 -3.539949,5.03091"
+           id="path10585-6"
+           sodipodi:nodetypes="cc" />
+        <path
+           style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.1203592;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+           d="m 85.608172,141.34954 c -2.666757,-1.07007 -4.567397,-1.03074 -7.019236,-0.32613 -1.148282,0.32999 -2.049576,1.68227 -2.142569,2.8734 -0.428995,5.49489 2.903496,8.17341 6.263367,7.93085 2.05934,-0.14867 4.130587,-2.74489 4.627575,-5.01288 0.41816,-1.90826 0.08389,-4.73774 -1.729137,-5.46524 z"
+           id="path5256-7"
+           sodipodi:nodetypes="ssssss" />
+        <path
+           style="fill:none;stroke:#000000;stroke-width:1.1203592;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+           d="m 83.287036,138.80034 c -3.534371,-0.50931 -7.153563,0.99406 -8.625608,4.36799 -1.972926,4.52216 0.871682,11.86623 6.124093,12.64208 9.667201,1.42779 12.807218,-15.52506 2.501515,-17.01007"
+           id="path3567-5" />
+      </g>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:1.1203592;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         d="m 81.17037,134.62224 c 1.528272,0.40147 2.479848,2.50023 3.65606,3.48042 1.57278,1.31064 3.329209,2.02976 4.365471,3.91113 0.788477,1.43151 0.572308,3.47576 1.409777,4.73562 0.43788,0.65873 1.309196,0.96445 1.803823,1.61735 1.540471,2.03338 1.370542,5.10243 -0.479425,6.88871 -1.219739,1.17764 -3.141402,1.6612 -4.790555,1.28077 -0.702868,-0.16221 -1.380432,-0.74583 -2.116667,-0.68541 -1.719445,0.14105 -2.786803,0.97944 -4.618181,0.50261 -6.467763,-1.6839 -8.890577,-9.24373 -7.458556,-15.10934 0.757382,-3.10205 4.540443,-7.59068 8.228253,-6.62186"
+         id="path3559-3" />
+    </g>
+    <g
+       aria-label="R"
+       id="text20823"
+       style="font-size:7.05556px;line-height:124%;font-family:Sans;-inkscape-font-specification:Sans;letter-spacing:0px;word-spacing:0px;stroke:#000000;stroke-width:0.264583px">
+      <path
+         d="m 9.0560585,10.458658 q 0.2239314,0.07579 0.4340823,0.32384 0.2135961,0.248047 0.4271921,0.682129 l 0.7062451,1.4056 H 9.8759918 L 9.2179781,11.550754 Q 8.9630409,11.03399 8.721884,10.86518 8.4841723,10.69637 8.0707606,10.69637 H 7.3128391 v 2.173857 H 6.6169293 V 7.726696 h 1.5709646 q 0.881945,0 1.3160273,0.3686254 0.4340823,0.3686255 0.4340823,1.1127666 0,0.4857588 -0.2273764,0.806153 -0.2239314,0.320394 -0.6545686,0.444417 z M 7.3128391,8.2985822 v 1.8259018 h 0.8750548 q 0.5029843,0 0.7579215,-0.2308216 0.2583823,-0.2342666 0.2583823,-0.6855744 0,-0.4513078 -0.2583823,-0.6786843 Q 8.6908782,8.2985822 8.1878939,8.2985822 Z"
+         id="path21707" />
+    </g>
+  </g>
+</svg>
diff --git a/app/src/main/assets/ic_heartrate.svg b/app/src/main/assets/ic_heartrate.svg
new file mode 100644
index 0000000..ee74236
--- /dev/null
+++ b/app/src/main/assets/ic_heartrate.svg
@@ -0,0 +1,85 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   height="24"
+   viewBox="0 0 24 24"
+   width="24"
+   version="1.1"
+   id="svg6"
+   sodipodi:docname="ic_heartrate.svg"
+   inkscape:version="1.0.1 (3bc2e813f5, 2020-09-07)">
+  <metadata
+     id="metadata12">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs10" />
+  <sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="1920"
+     inkscape:window-height="1042"
+     id="namedview8"
+     showgrid="false"
+     showguides="true"
+     inkscape:guide-bbox="true"
+     inkscape:zoom="22.185475"
+     inkscape:cx="15.252703"
+     inkscape:cy="16.702097"
+     inkscape:window-x="0"
+     inkscape:window-y="38"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="svg6">
+    <sodipodi:guide
+       position="13.81042,14.118536"
+       orientation="0,-1"
+       id="guide843" />
+    <sodipodi:guide
+       position="12.237736,12.731366"
+       orientation="0,-1"
+       id="guide845" />
+    <sodipodi:guide
+       position="10.840426,11.852413"
+       orientation="0,-1"
+       id="guide847" />
+    <sodipodi:guide
+       position="10.569979,10.364953"
+       orientation="0,-1"
+       id="guide849" />
+  </sodipodi:namedview>
+  <path
+     d="M0 0h24v24H0V0z"
+     fill="none"
+     id="path2" />
+  <path
+     d="M16.5 3c-1.74 0-3.41.81-4.5 2.09C10.91 3.81 9.24 3 7.5 3 4.42 3 2 5.42 2 8.5c0 3.78 3.4 6.86 8.55 11.54L12 21.35l1.45-1.32C18.6 15.36 22 12.28 22 8.5 22 5.42 19.58 3 16.5 3zm-4.4 15.55l-.1.1-.1-.1C7.14 14.24 4 11.39 4 8.5 4 6.5 5.5 5 7.5 5c1.54 0 3.04.99 3.57 2.36h1.87C13.46 5.99 14.96 5 16.5 5c2 0 3.5 1.5 3.5 3.5 0 2.89-3.14 5.74-7.9 10.05z"
+     id="path4" />
+  <path
+     fill="currentColor"
+     d="m 4.4092095,12.147587 3.5617512,0 2.0837433,-5.0286723 1.932068,5.4893343 1.823648,-2.726785 h 6.48905 l -0.488067,1.38717 -5.262995,0 -2.938855,4.509156 -1.60202,-4.54569 -0.9505981,2.403348 -3.5064865,-4.01e-4 z"
+     id="path2361"
+     sodipodi:nodetypes="ccccccccccccc"
+     style="stroke-width:1"
+     inkscape:transform-center-x="1.0957646"
+     inkscape:transform-center-y="-1.4792654" />
+</svg>
diff --git a/app/src/main/assets/ic_intensity.svg b/app/src/main/assets/ic_intensity.svg
new file mode 100644
index 0000000..c5ab1b9
--- /dev/null
+++ b/app/src/main/assets/ic_intensity.svg
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" height="24" viewBox="-2.7755576e-17 -2.7755576e-17 21.367055 24" width="21.367056" version="1.1" id="svg6" sodipodi:docname="ic_intensity.svg" inkscape:version="1.0.1 (3bc2e813f5, 2020-09-07)">
+    <g id="g3949"/>
+    <metadata id="metadata12">
+        <rdf:RDF>
+            <cc:Work rdf:about="">
+                <dc:format/>
+                <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
+                <dc:title/>
+            </cc:Work>
+        </rdf:RDF>
+    </metadata>
+    <defs id="defs10"/>
+    <sodipodi:namedview pagecolor="#ffffff" bordercolor="#666666" borderopacity="1" objecttolerance="10" gridtolerance="10" guidetolerance="10" inkscape:pageopacity="0" inkscape:pageshadow="2" inkscape:window-width="1920" inkscape:window-height="1042" id="namedview8" showgrid="false" showguides="true" inkscape:guide-bbox="true" inkscape:zoom="22.185475" inkscape:cx="23.811004" inkscape:cy="13.427636" inkscape:window-x="0" inkscape:window-y="38" inkscape:window-maximized="1" inkscape:current-layer="svg6" inkscape:document-rotation="0" inkscape:snap-others="false" fit-margin-top="2.5" fit-margin-left="2.5" fit-margin-right="2.5" fit-margin-bottom="2.5" lock-margins="true" viewbox-height="25.46567" inkscape:pagecheckerboard="true" scale-x="1" viewbox-x="0" viewbox-y="0">
+        <sodipodi:guide position="15.732031,12.43419" orientation="0,-1" id="guide843"/>
+        <sodipodi:guide position="8.6890316,12.859034" orientation="0,-1" id="guide845"/>
+        <sodipodi:guide position="7.2917227,11.980081" orientation="0,-1" id="guide847"/>
+        <sodipodi:guide position="7.0212757,10.49262" orientation="0,-1" id="guide849"/>
+    </sodipodi:namedview>
+</svg>
diff --git a/app/src/main/assets/ic_intensity_hollow.svg b/app/src/main/assets/ic_intensity_hollow.svg
new file mode 100644
index 0000000..0fa7d66
--- /dev/null
+++ b/app/src/main/assets/ic_intensity_hollow.svg
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   height="24"
+   viewBox="0 0 24 24"
+   width="24"
+   version="1.1"
+   id="svg6"
+   sodipodi:docname="ic_intensity_hollow.svg"
+   inkscape:version="1.0.1 (3bc2e813f5, 2020-09-07)">
+  <metadata
+     id="metadata12">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs10" />
+  <sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="1920"
+     inkscape:window-height="1042"
+     id="namedview8"
+     showgrid="false"
+     inkscape:zoom="32.125"
+     inkscape:cx="12"
+     inkscape:cy="12"
+     inkscape:window-x="0"
+     inkscape:window-y="38"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="svg6" />
+  <path
+     d="M0 0h24v24H0V0z"
+     fill="none"
+     id="path2" />
+  <path
+     d="m 13.5,0.67 c 0,0 0.74,2.65 0.74,4.8 0,2.06 -1.35,3.73 -3.41,3.73 C 8.76,9.2 7.2,7.53 7.2,5.47 L 7.23,5.11 C 5.21,7.51 4,10.62 4,14 c 0,4.42 3.58,8 8,8 4.42,0 8,-3.58 8,-8 C 20,8.61 17.41,3.8 13.5,0.67 Z M 12,20 C 8.69,20 6,17.31 6,14 6,12.47 6.3,10.96 6.86,9.57 c 1.01,1.01 2.41,1.63 3.97,1.63 2.66,0 4.75,-1.83 5.28,-4.43 1.23,2.2 1.89,4.67 1.89,7.23 0,3.31 -2.69,6 -6,6 z"
+     id="path4"
+     sodipodi:nodetypes="cssccssscsscscss" />
+</svg>
diff --git a/app/src/main/assets/ic_launcher.svg b/app/src/main/assets/ic_launcher.svg
new file mode 100644
index 0000000..248c776
--- /dev/null
+++ b/app/src/main/assets/ic_launcher.svg
@@ -0,0 +1,574 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="5.6444445mm"
+   height="5.6444445mm"
+   viewBox="-1 -1 22 22"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.92.1 r15371"
+   sodipodi:docname="template1.svg"
+   inkscape:export-xdpi="648"
+   inkscape:export-ydpi="648">
+  <title
+     id="title4560">Gadgetbridge Icon</title>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="11.2"
+     inkscape:cx="-40.722947"
+     inkscape:cy="18.815618"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     fit-margin-top="0"
+     fit-margin-left="0"
+     fit-margin-right="0"
+     fit-margin-bottom="0"
+     showguides="true"
+     inkscape:guide-bbox="true"
+     inkscape:window-width="1920"
+     inkscape:window-height="1017"
+     inkscape:window-x="-8"
+     inkscape:window-y="-8"
+     inkscape:window-maximized="1"
+     inkscape:snap-grids="true"
+     inkscape:snap-to-guides="false"
+     inkscape:snap-page="true">
+    <inkscape:grid
+       type="xygrid"
+       id="grid4138" />
+    <sodipodi:guide
+       position="10,18"
+       orientation="0,1"
+       id="guide4140"
+       inkscape:locked="false" />
+    <sodipodi:guide
+       position="10,2"
+       orientation="0,1"
+       id="guide4142"
+       inkscape:locked="false" />
+    <sodipodi:guide
+       position="2,10"
+       orientation="1,0"
+       id="guide4144"
+       inkscape:locked="false" />
+    <sodipodi:guide
+       position="0,0"
+       orientation="0,20"
+       id="guide4146"
+       inkscape:locked="false" />
+    <sodipodi:guide
+       position="20,0"
+       orientation="-20,0"
+       id="guide4148"
+       inkscape:locked="false" />
+    <sodipodi:guide
+       position="20,20"
+       orientation="0,-20"
+       id="guide4150"
+       inkscape:locked="false" />
+    <sodipodi:guide
+       position="0,20"
+       orientation="20,0"
+       id="guide4152"
+       inkscape:locked="false" />
+    <sodipodi:guide
+       position="17.934153,10.890707"
+       orientation="1,0"
+       id="guide4154"
+       inkscape:locked="false" />
+    <sodipodi:guide
+       position="8.5170198,7.5753346"
+       orientation="-0.70710678,0.70710678"
+       id="guide4156"
+       inkscape:locked="false" />
+    <sodipodi:guide
+       position="4,10"
+       orientation="1,0"
+       id="guide4160"
+       inkscape:locked="false" />
+    <sodipodi:guide
+       position="16,10"
+       orientation="1,0"
+       id="guide4162"
+       inkscape:locked="false" />
+    <sodipodi:guide
+       position="10,4"
+       orientation="0,1"
+       id="guide4166"
+       inkscape:locked="false" />
+    <sodipodi:guide
+       position="10,15"
+       orientation="0,1"
+       id="guide4168"
+       inkscape:locked="false" />
+    <sodipodi:guide
+       position="10,5"
+       orientation="0,1"
+       id="guide4170"
+       inkscape:locked="false" />
+    <sodipodi:guide
+       position="5,10"
+       orientation="1,0"
+       id="guide4172"
+       inkscape:locked="false" />
+    <sodipodi:guide
+       position="15,10"
+       orientation="1,0"
+       id="guide4174"
+       inkscape:locked="false" />
+  </sodipodi:namedview>
+  <defs
+     id="defs4">
+    <linearGradient
+       inkscape:collect="always"
+       id="linearGradient13455">
+      <stop
+         style="stop-color:#000000;stop-opacity:0.2"
+         offset="0"
+         id="stop13457" />
+      <stop
+         style="stop-color:#000000;stop-opacity:0;"
+         offset="1"
+         id="stop13459" />
+    </linearGradient>
+    <clipPath
+       id="clipPath12478"
+       clipPathUnits="userSpaceOnUse">
+      <circle
+         r="10"
+         cy="10.000005"
+         cx="9.9999781"
+         id="circle12480"
+         style="opacity:1;fill:#2196f3;fill-opacity:1;stroke:none;stroke-width:0.05069799;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+    </clipPath>
+    <clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath5790">
+      <circle
+         r="10"
+         cy="10.000005"
+         cx="9.9999781"
+         id="circle5792"
+         style="opacity:1;fill:#2196f3;fill-opacity:1;stroke:none;stroke-width:0.05069799;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         inkscape:export-filename="/home/juergen/path4136.png"
+         inkscape:export-xdpi="648"
+         inkscape:export-ydpi="648" />
+    </clipPath>
+    <clipPath
+       id="clipPath5055"
+       clipPathUnits="userSpaceOnUse">
+      <circle
+         r="10"
+         cy="10.000005"
+         cx="9.9999781"
+         id="circle5057"
+         style="opacity:1;fill:#2196f3;fill-opacity:1;stroke:none;stroke-width:0.05069799;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         inkscape:export-filename="/home/juergen/path4136.png"
+         inkscape:export-xdpi="648"
+         inkscape:export-ydpi="648" />
+    </clipPath>
+    <clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath5119">
+      <circle
+         r="10"
+         cy="10.000005"
+         cx="9.9999781"
+         id="circle5121"
+         style="opacity:1;fill:#2196f3;fill-opacity:1;stroke:none;stroke-width:0.05069799;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         inkscape:export-filename="/home/juergen/Schreibtisch/path4136.png"
+         inkscape:export-xdpi="648"
+         inkscape:export-ydpi="648" />
+    </clipPath>
+    <clipPath
+       id="clipPath5194"
+       clipPathUnits="userSpaceOnUse">
+      <circle
+         r="10"
+         cy="10.000005"
+         cx="9.9999781"
+         id="circle5196"
+         style="opacity:1;fill:#cddc39;fill-opacity:1;stroke:none;stroke-width:0.05069799;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         inkscape:export-filename="/home/juergen/Schreibtisch/path4136.png"
+         inkscape:export-xdpi="648"
+         inkscape:export-ydpi="648" />
+    </clipPath>
+    <clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath6175">
+      <circle
+         r="10"
+         cy="10.000005"
+         cx="9.9999781"
+         id="circle6177"
+         style="opacity:1;fill:#f44336;fill-opacity:1;stroke:none;stroke-width:0.05069799;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         inkscape:export-filename="/home/juergen/Schreibtisch/path4136.png"
+         inkscape:export-xdpi="648"
+         inkscape:export-ydpi="648" />
+    </clipPath>
+    <clipPath
+       id="clipPath7176"
+       clipPathUnits="userSpaceOnUse">
+      <circle
+         r="10"
+         cy="10.000005"
+         cx="9.9999781"
+         id="circle7178"
+         style="opacity:1;fill:#9c27b0;fill-opacity:1;stroke:none;stroke-width:0.05069799;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         inkscape:export-filename="/home/juergen/Schreibtisch/path4136.png"
+         inkscape:export-xdpi="648"
+         inkscape:export-ydpi="648" />
+    </clipPath>
+    <clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath8165">
+      <circle
+         r="10"
+         cy="10.000005"
+         cx="9.9999781"
+         id="circle8167"
+         style="opacity:1;fill:#ffc107;fill-opacity:1;stroke:none;stroke-width:0.05069799;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         inkscape:export-filename="/home/juergen/Schreibtisch/path4136.png"
+         inkscape:export-xdpi="648"
+         inkscape:export-ydpi="648" />
+    </clipPath>
+    <clipPath
+       id="clipPath9162"
+       clipPathUnits="userSpaceOnUse">
+      <circle
+         r="10"
+         cy="10.000005"
+         cx="9.9999781"
+         id="circle9164"
+         style="opacity:1;fill:#ff9800;fill-opacity:1;stroke:none;stroke-width:0.05069799;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         inkscape:export-filename="/home/juergen/Schreibtisch/path4136.png"
+         inkscape:export-xdpi="648"
+         inkscape:export-ydpi="648" />
+    </clipPath>
+    <clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath5147">
+      <circle
+         r="10"
+         cy="10.000005"
+         cx="9.9999781"
+         id="circle5149"
+         style="opacity:1;fill:#ff9800;fill-opacity:1;stroke:none;stroke-width:0.05069799;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         inkscape:export-filename="/home/juergen/Schreibtisch/path4136.png"
+         inkscape:export-xdpi="648"
+         inkscape:export-ydpi="648" />
+    </clipPath>
+    <clipPath
+       id="clipPath6144"
+       clipPathUnits="userSpaceOnUse">
+      <circle
+         r="10"
+         cy="10.000005"
+         cx="9.9999781"
+         id="circle6146"
+         style="opacity:1;fill:#e91e63;fill-opacity:1;stroke:none;stroke-width:0.05069799;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         inkscape:export-filename="/home/juergen/Schreibtisch/path4136.png"
+         inkscape:export-xdpi="648"
+         inkscape:export-ydpi="648" />
+    </clipPath>
+    <clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath7143">
+      <circle
+         r="10"
+         cy="10.000005"
+         cx="9.9999781"
+         id="circle7145"
+         style="opacity:1;fill:#ffeb3b;fill-opacity:1;stroke:none;stroke-width:0.05069799;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         inkscape:export-filename="/home/juergen/Schreibtisch/path4136.png"
+         inkscape:export-xdpi="648"
+         inkscape:export-ydpi="648" />
+    </clipPath>
+    <clipPath
+       id="clipPath8148"
+       clipPathUnits="userSpaceOnUse">
+      <circle
+         r="10"
+         cy="10.000005"
+         cx="9.9999781"
+         id="circle8150"
+         style="opacity:1;fill:#9c27b0;fill-opacity:1;stroke:none;stroke-width:0.05069799;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         inkscape:export-filename="/home/juergen/Schreibtisch/path4136.png"
+         inkscape:export-xdpi="648"
+         inkscape:export-ydpi="648" />
+    </clipPath>
+    <clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath5332">
+      <circle
+         r="10"
+         cy="10.000005"
+         cx="9.9999781"
+         id="circle5334"
+         style="opacity:1;fill:#f44336;fill-opacity:1;stroke:none;stroke-width:0.05069799;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         inkscape:export-filename="/home/juergen/Schreibtisch/path4136.png"
+         inkscape:export-xdpi="648"
+         inkscape:export-ydpi="648" />
+    </clipPath>
+    <clipPath
+       id="clipPath6341"
+       clipPathUnits="userSpaceOnUse">
+      <circle
+         r="10"
+         cy="10.000005"
+         cx="9.9999781"
+         id="circle6343"
+         style="opacity:1;fill:#4caf50;fill-opacity:1;stroke:none;stroke-width:0.05069799;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         inkscape:export-filename="/home/juergen/Schreibtisch/path4136.png"
+         inkscape:export-xdpi="648"
+         inkscape:export-ydpi="648" />
+    </clipPath>
+    <clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath7354">
+      <circle
+         r="10"
+         cy="10.000005"
+         cx="9.9999781"
+         id="circle7356"
+         style="opacity:1;fill:#f44336;fill-opacity:1;stroke:none;stroke-width:0.05069799;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         inkscape:export-filename="/home/juergen/Schreibtisch/path4136.png"
+         inkscape:export-xdpi="648"
+         inkscape:export-ydpi="648" />
+    </clipPath>
+    <clipPath
+       id="clipPath8344"
+       clipPathUnits="userSpaceOnUse">
+      <circle
+         r="10"
+         cy="10.000005"
+         cx="9.9999781"
+         id="circle8346"
+         style="opacity:1;fill:#673ab7;fill-opacity:1;stroke:none;stroke-width:0.05069799;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         inkscape:export-filename="/home/juergen/Schreibtisch/path4136.png"
+         inkscape:export-xdpi="648"
+         inkscape:export-ydpi="648" />
+    </clipPath>
+    <clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath9436">
+      <circle
+         r="10"
+         cy="10.000005"
+         cx="9.9999781"
+         id="circle9438"
+         style="opacity:1;fill:#9e9e9e;fill-opacity:1;stroke:none;stroke-width:0.05069799;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         inkscape:export-filename="/home/juergen/Schreibtisch/path4136.png"
+         inkscape:export-xdpi="648"
+         inkscape:export-ydpi="648" />
+    </clipPath>
+    <clipPath
+       id="clipPath10432"
+       clipPathUnits="userSpaceOnUse">
+      <circle
+         r="10"
+         cy="10.000005"
+         cx="9.9999781"
+         id="circle10434"
+         style="opacity:1;fill:#795548;fill-opacity:1;stroke:none;stroke-width:0.05069799;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         inkscape:export-filename="/home/juergen/Schreibtisch/path4136.png"
+         inkscape:export-xdpi="648"
+         inkscape:export-ydpi="648" />
+    </clipPath>
+    <clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath11433">
+      <circle
+         r="10"
+         cy="10.000005"
+         cx="9.9999781"
+         id="circle11435"
+         style="opacity:1;fill:#03a9f4;fill-opacity:1;stroke:none;stroke-width:0.05069799;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         inkscape:export-filename="/home/juergen/Schreibtisch/path4136.png"
+         inkscape:export-xdpi="648"
+         inkscape:export-ydpi="648" />
+    </clipPath>
+    <clipPath
+       id="clipPath12452"
+       clipPathUnits="userSpaceOnUse">
+      <circle
+         r="10"
+         cy="10.000005"
+         cx="9.9999781"
+         id="circle12454"
+         style="opacity:1;fill:#ffc107;fill-opacity:1;stroke:none;stroke-width:0.05069799;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         inkscape:export-filename="/home/juergen/Schreibtisch/path4136.png"
+         inkscape:export-xdpi="648"
+         inkscape:export-ydpi="648" />
+    </clipPath>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient13455"
+       id="linearGradient13461"
+       x1="9.9553576"
+       y1="9.9553566"
+       x2="20"
+       y2="20"
+       gradientUnits="userSpaceOnUse" />
+    <clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath13465">
+      <circle
+         r="10"
+         cy="10.000005"
+         cx="9.9999781"
+         id="circle13467"
+         style="opacity:1;fill:#e91e63;fill-opacity:1;stroke:none;stroke-width:0.05069799;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         inkscape:export-filename="/home/juergen/Schreibtisch/path4136.png"
+         inkscape:export-xdpi="648"
+         inkscape:export-ydpi="648" />
+    </clipPath>
+  </defs>
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title>Gadgetbridge Icon</dc:title>
+        <cc:license
+           rdf:resource="http://creativecommons.org/licenses/by-sa/4.0/" />
+        <dc:date>18/07/2017</dc:date>
+        <dc:creator>
+          <cc:Agent>
+            <dc:title>xphnx</dc:title>
+          </cc:Agent>
+        </dc:creator>
+        <dc:rights>
+          <cc:Agent>
+            <dc:title>CC (BY-SA)</dc:title>
+          </cc:Agent>
+        </dc:rights>
+        <dc:description>Launcher icon for Gadgetbridge. Based on Blue Minimal Template (GPLv3)</dc:description>
+        <dc:source>https://github.com/scoute-dich/Blue-Minimal/tree/master/IconTemplates</dc:source>
+        <dc:contributor>
+          <cc:Agent>
+            <dc:title>xphnx based on a work of scoute-dich</dc:title>
+          </cc:Agent>
+        </dc:contributor>
+      </cc:Work>
+      <cc:License
+         rdf:about="http://creativecommons.org/licenses/by-sa/4.0/">
+        <cc:permits
+           rdf:resource="http://creativecommons.org/ns#Reproduction" />
+        <cc:permits
+           rdf:resource="http://creativecommons.org/ns#Distribution" />
+        <cc:requires
+           rdf:resource="http://creativecommons.org/ns#Notice" />
+        <cc:requires
+           rdf:resource="http://creativecommons.org/ns#Attribution" />
+        <cc:permits
+           rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
+        <cc:requires
+           rdf:resource="http://creativecommons.org/ns#ShareAlike" />
+      </cc:License>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Hintergrund"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(1058.1428,-861.93365)">
+    <circle
+       inkscape:export-ydpi="648"
+       inkscape:export-xdpi="648"
+       inkscape:export-filename="/home/juergen/Schreibtisch/path4136.png"
+       style="opacity:1;fill:#0091ea;fill-opacity:1;stroke:none;stroke-width:0.05069799;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       id="path4136"
+       cx="-1048.1428"
+       cy="871.93365"
+       r="10" />
+    <path
+       style="fill:url(#linearGradient13461);fill-opacity:1;stroke-width:0.2;stroke-linejoin:round"
+       d="M 6.134855,7.8575793 C 5.5887444,7.7737975 8.9732,11.36764 8.9732,11.91964 c 0.7720204,2.953627 -0.3190754,2.368775 -1.3911929,4.54893 0,0.314113 2.8336269,3.63544 3.0589809,3.818814 0.04391,0.05518 0.09008,0.103734 0.144531,0.148437 0.04494,0.0568 0.09237,0.106597 0.148438,0.152344 0.04462,0.05589 0.09299,0.10335 0.148437,0.148437 0.0433,0.05399 0.08913,0.10065 0.142578,0.144532 0.04401,0.05535 0.08991,0.103622 0.144532,0.148437 0.04462,0.05589 0.09299,0.10335 0.148437,0.148438 0.04281,0.05303 0.08799,0.09936 0.140625,0.142578 0.0433,0.05399 0.08913,0.100649 0.142578,0.144531 0.0429,0.05343 0.08778,0.101002 0.140625,0.144531 0.04305,0.0535 0.08955,0.09904 0.142578,0.142578 0.04259,0.05283 0.08633,0.09947 0.138672,0.142579 0.04241,0.05266 0.08853,0.09762 0.140625,0.140624 0.04231,0.05246 0.08674,0.09775 0.138672,0.140626 0.04224,0.05233 0.08685,0.09785 0.138672,0.140624 0.04196,0.05198 0.0853,0.09806 0.136719,0.140626 0.04204,0.05191 0.218042,0.230956 0.269531,0.273437 0.04036,0.0496 -0.04904,-0.04097 0,0 0.04207,0.05223 0.08706,0.09788 0.138672,0.140625 0.04161,0.05109 1.939454,1.964844 0.136719,0.136719 0.0402,0.04908 0.673987,-1.491056 0.722706,-1.450484 0.0407,0.05002 0.08329,0.09352 0.132812,0.134766 0.03972,0.04878 -0.511067,1.675676 -0.46294,1.716108 0.04056,0.05008 0.08344,0.09344 0.132812,0.134766 0.04069,0.04997 0.08332,0.09357 0.132813,0.134766 0.03936,0.0479 0.08141,0.08913 0.128906,0.128906 0.03997,0.04887 0.0805,0.09236 0.128907,0.132812 0.0394,0.04798 0.208293,0.219929 0.255859,0.259766 0.03933,0.04803 -0.0475,-0.0399 0,0 0.03941,0.04797 0.08133,0.08909 0.128907,0.128906 0.03805,0.04609 0.07742,0.08645 0.123046,0.125 0.03972,0.04853 0.08085,0.09063 0.128907,0.13086 0.03812,0.04614 0.07734,0.08643 0.123047,0.125 0.03891,0.04709 0.08015,0.08776 0.126953,0.126953 0.03709,0.04468 0.0749,0.08353 0.11914,0.121094 0.03888,0.04724 0.07819,0.08957 0.125,0.128906 0.03698,0.0444 0.0751,0.08376 0.119141,0.121094 0.03744,0.0452 0.07636,0.08512 0.121094,0.123046 0.939453,0.953125 0.07678,0.08344 0.121094,0.121094 0.03702,0.04459 0.075,0.0836 0.11914,0.121094 0.03695,0.04448 0.0751,0.08368 0.119141,0.121094 0.03643,0.04385 0.07387,0.08215 0.117187,0.11914 0.03701,0.04454 0.07503,0.08364 0.119141,0.121094 0.03593,0.0429 0.07267,0.08091 0.115234,0.117188 0.03646,0.04374 0.07386,0.08225 0.117188,0.11914 0.03472,0.04132 0.07043,0.07812 0.111328,0.113281 0.03672,0.04408 0.07545,0.08202 0.119141,0.119141 0.159451,0.01975 0.495139,0.310768 0.33789,0.34375 0.03367,0.03982 0.06799,0.0753 0.107422,0.109375 0.03567,0.04259 0.07302,0.07917 0.115234,0.115234 0.03376,0.03984 0.06789,0.07531 0.107422,0.109375 0.03482,0.04131 0.07033,0.07814 0.111328,0.113282 0.03318,0.03905 0.06673,0.07392 0.105469,0.107422 1.997761,0.694544 9.103709,2.908451 10.44526,2.898372 h 0.002 c 0.275418,0 0.52417,-0.112641 0.705078,-0.292969 C 28.886751,29.72516 29,29.4758 29,29.199219 L 27.879654,21.07464 h -0.002 l -0.002,-0.002 -0.002,-0.002 v -0.002 l -0.0039,-0.002 -0.0039,-0.0059 -0.0039,-0.0039 -0.0059,-0.0059 -0.0059,-0.0059 -0.0078,-0.0078 -0.0078,-0.0078 -0.0078,-0.0078 -0.0098,-0.0098 -0.0098,-0.0098 -0.01172,-0.01172 -0.01172,-0.01172 -0.01172,-0.01172 -0.01367,-0.01367 -0.01367,-0.01367 -0.01367,-0.01563 -0.01562,-0.01563 -0.01563,-0.01563 -0.01758,-0.01563 -0.01758,-0.01953 -0.01758,-0.01758 -0.01953,-0.01953 -0.01953,-0.01953 -0.01953,-0.01953 v -0.002 l -0.02148,-0.02148 -0.02148,-0.02148 -0.02344,-0.02344 -0.02344,-0.02344 -0.02344,-0.02344 -0.02539,-0.02539 -0.02539,-0.02734 -0.02539,-0.02539 -0.02734,-0.02734 -0.02734,-0.0293 -0.0293,-0.0293 -0.0293,-0.0293 -0.03125,-0.0293 -0.0293,-0.03125 -0.03125,-0.0332 -0.0332,-0.0332 -0.0332,-0.0332 -0.0332,-0.0332 -0.03516,-0.03516 -0.03516,-0.03516 -0.03516,-0.03711 -0.03711,-0.03711 -0.03711,-0.03906 -0.03906,-0.03711 -0.03906,-0.04102 -0.03906,-0.03906 -0.04102,-0.04102 -0.04102,-0.04297 -0.04297,-0.04102 -0.04102,-0.04102 v -0.002 -0.002 l -0.04492,-0.04297 -0.04297,-0.04492 -0.04492,-0.04492 -0.04687,-0.04687 -0.04492,-0.04687 -0.04883,-0.04883 -0.04687,-0.04687 -0.04883,-0.05078 -0.04883,-0.04883 -0.05078,-0.05078 -0.05078,-0.05273 -0.05078,-0.05078 -0.05273,-0.05469 -0.05273,-0.05273 -0.05469,-0.05469 -0.05469,-0.05469 -0.05469,-0.05664 -0.05664,-0.05664 -0.05664,-0.05859 -0.05859,-0.05859 -0.05664,-0.05859 -0.06055,-0.05859 -0.05859,-0.06055 -0.06055,-0.0625 -0.0625,-0.0625 -0.06055,-0.0625 -0.0625,-0.0625 -0.06445,-0.06445 -0.06445,-0.06641 -0.06445,-0.06445 -0.06641,-0.06641 -0.06641,-0.06836 -0.06641,-0.06836 -0.06836,-0.06836 -0.06836,-0.07031 -0.07031,-0.07031 -0.07031,-0.07031 -0.07031,-0.07227 -0.07227,-0.07227 -0.07227,-0.07422 -0.07227,-0.07422 -0.07422,-0.07422 -0.07422,-0.07617 -0.07617,-0.07617 -0.07617,-0.07617 -0.07617,-0.07813 -0.07813,-0.07813 -0.07813,-0.08008 -0.07813,-0.08008 -0.08008,-0.08008 -0.08203,-0.08203 -0.08008,-0.08203 -0.08203,-0.08398 -0.08398,-0.08398 -0.08203,-0.08398 -0.08398,-0.08594 -0.08594,-0.08594 -0.08594,-0.08789 -0.08594,-0.08594 -0.08789,-0.08984 -0.08789,-0.08789 -0.08789,-0.08984 -0.08984,-0.0918 -0.08984,-0.0918 -0.0918,-0.0918 -0.0918,-0.0918 -0.0918,-0.09375 -0.09375,-0.0957 -0.09375,-0.0957 -0.09375,-0.0957 -0.0957,-0.0957 -0.09766,-0.09766 -0.0957,-0.09766 -0.09766,-0.09961 -0.09961,-0.09961 -0.09766,-0.09961 -0.09961,-0.101562 L 21.012211,14.117126 20.910649,14.01361 20.809087,13.910094 20.705571,13.806579 20.602055,13.70111 20.49854,13.595641 20.393071,13.488219 20.287602,13.380797 20.18018,13.273376 20.072758,13.164 19.965337,13.054626 19.855961,12.94525 19.851219,12.854848 19.741843,12.74352 19.630515,12.630239 19.519187,12.516958 19.405906,12.403676 19.292625,12.288442 19.179343,12.173208 19.064109,12.05602 18.948875,11.940786 18.831687,11.821645 18.7145,11.704458 18.597312,11.585317 18.480125,11.46813 v -0.0039 l -0.119141,-0.121093 -0.121094,-0.121094 -0.121093,-0.123047 -0.121094,-0.123047 -0.121094,-0.123047 -0.123047,-0.125 -0.125,-0.125 -0.123047,-0.125 -0.125,-0.126953 -0.126953,-0.128906 -0.126953,-0.126953 -0.126953,-0.1289072 -0.126953,-0.126953 v -0.0039 L 16.744052,9.7049408 16.613192,9.5740803 16.484286,9.4432213 16.353427,9.3104083 16.220614,9.1756433 16.087802,9.0408773 15.954989,8.9061124 15.820224,8.7713464 15.685458,8.6346274 15.550692,8.4998624 v -0.0039 l -0.136718,-0.136725 -0.136719,-0.138672 -0.136719,-0.136719 v -0.0039 L 15.001864,7.9393154 14.861239,7.7986902 14.722567,7.6580652 14.581942,7.5174402 v -0.00391 L 14.439364,7.3709558 14.296786,7.2264245 14.154208,7.0818933 v 0 L 14.007009,6.9923893 13.395553,6.3374386 12.445852,5.7116899 11.682337,4.9970274 11.461514,4.8293121 10.294776,3.8349929 C 9.7457728,3.3921062 9.0255387,4.1644713 9.0255387,4.1644713 L 7.8983224,5.0493128 C 7.7279474,5.1830537 9.7681952,6.5418811 9.4189571,6.5258336 9.1783033,6.5147756 7.9346571,5.7850447 7.4996252,5.5958611 6.9046896,5.3371397 6.6803727,7.9412701 6.134855,7.8575793 Z"
+       transform="translate(-1058.1428,861.93365)"
+       id="path12581"
+       clip-path="url(#clipPath13465)"
+       inkscape:connector-curvature="0"
+       sodipodi:nodetypes="sccccccccccccccccccccccccccccccccccccccccccccccccccccccccccsccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccssss" />
+    <path
+       inkscape:connector-curvature="0"
+       id="path12456"
+       d="m -1050.6874,868.40686 0.1339,3.79465 0.2678,0.89285 1.1161,0.75893 1.5179,0.0446 1.6071,-0.40178 0.5357,-1.25 -0.1339,-3.83929 -4.9107,0 z"
+       style="fill:none;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+    <g
+       style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-opacity:0.5098038"
+       id="g6427"
+       transform="matrix(0.10900039,0,0,0.10900039,-1058.4564,854.81407)">
+      <g
+         transform="translate(-0.19198408,15.28344)"
+         id="g5859"
+         style="fill:#ff5000;fill-opacity:1">
+        <g
+           id="g4591"
+           transform="matrix(0.96469159,0,0,0.96469159,3.3476544,3.8272566)">
+          <g
+             style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-opacity:0.5098038"
+             id="g4600"
+             transform="matrix(2.0186569,0,0,2.0186569,394.57822,-108.82609)">
+            <path
+               style="fill:#ffffff;fill-opacity:1;stroke-width:0.26458332"
+               d="m -136.77732,155.22239 -0.52821,-12.65678 c -0.18542,-8.88992 7.31203,-16.43236 9.31124,-23.07234 -2.12299,2.02696 -6.52318,11.59925 -11.09114,10.79579 -2.54776,-0.85577 -3.35187,-2.05496 -4.38456,-4.23069 -3.42275,0.6668 -6.14301,-1.35553 -7.13076,-3.79658 l 0.19658,9.86107 -4.46061,-12.79348 c -3.48621,-1.12814 -6.68377,-6.4807 -3.47994,-5.86765 l 5.75274,1.10079 c 3.93852,0.82896 6.15619,-4.4975 2.63121,-7.26229 -0.7499,-0.53152 -8.27096,-4.04173 -9.68588,-4.44078 -1.26974,-0.3581 -2.78089,0.14533 -3.75024,1.24936 l -5.85918,10.88112 c -0.60845,0.8393 -0.61164,1.71753 -0.25253,2.49043 l 10.72151,21.51532 c 0.34668,0.65416 0.17252,2.20422 0.17823,2.73721 l -1.28045,13.4895 z m 8.9707,-38.19102 c 1.9434,-2.20392 -1.65635,-8.33602 -3.1421,-6.55824 l -9.61026,12.58549 c -2.81396,3.5023 1.43178,6.96399 4.448,4.80543 0.26564,-0.24803 0.41253,-0.37935 0.60397,-0.63064 z m -4.69458,-9.5376 c -1.42924,-1.48182 -4.34931,-3.63245 -4.73609,-3.48403 l -10.71091,14.46057 c -2.88854,2.81736 0.36005,6.17635 3.51209,5.84536 0.43402,-0.12765 0.58795,-0.21146 0.85842,-0.46309 l 11.1656,-14.42916 c 0.4913,-0.6349 0.32485,-1.41907 -0.0891,-1.92965 z m -6.36258,-4.6579 c -0.10642,-0.74107 -4.82207,-4.542483 -5.18836,-4.06928 l -5.44048,7.02851 c 6.87574,7.48663 -1.14912,12.14096 -5.91293,9.41949 0.59683,1.91529 4.16535,3.90113 5.27266,2.44458 z m -6.5656,-5.245518 c -1.03574,-2.192098 -5.14677,-3.858126 -7.00157,-3.15132 l -5.88314,7.251058 7.38969,3.05534 z"
+               id="path4606"
+               inkscape:connector-curvature="0"
+               sodipodi:nodetypes="ccccccccscccccccccccccccccccccscccsccscccccc" />
+          </g>
+          <rect
+             ry="6.5778751"
+             y="175.19803"
+             x="69.227318"
+             height="23.844799"
+             width="52.825401"
+             id="rect4610"
+             style="opacity:1;fill:#ff5000;fill-opacity:1;stroke:none;stroke-width:16.03646088;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.5098038" />
+          <rect
+             ry="4.0110912"
+             y="179.85034"
+             x="82.062675"
+             height="14.540206"
+             width="27.154684"
+             id="rect4610-3"
+             style="opacity:1;fill:#1359ff;fill-opacity:1;stroke:none;stroke-width:8.97837925;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.5098038" />
+        </g>
+      </g>
+    </g>
+  </g>
+  <g
+     style="display:inline"
+     inkscape:groupmode="layer"
+     id="layer2"
+     inkscape:label="Icon">
+    <path
+       d="m 3.9999777,4.0333382 12.0000003,0 0,11.9333328 -12.0000003,0 z"
+       id="path4"
+       inkscape:connector-curvature="0"
+       style="fill:none" />
+    <path
+       style="fill:none"
+       inkscape:connector-curvature="0"
+       id="path9956"
+       d="m 18.999978,19.033339 12,0 0,11.933333 -12,0 z" />
+  </g>
+</svg>
diff --git a/app/src/main/assets/ic_launcher_neon.svg b/app/src/main/assets/ic_launcher_neon.svg
new file mode 100644
index 0000000..f86d8fc
--- /dev/null
+++ b/app/src/main/assets/ic_launcher_neon.svg
@@ -0,0 +1,751 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   width="5.6444445mm"
+   height="5.6444445mm"
+   viewBox="-1 -1 22 22"
+   id="svg2"
+   version="1.1"
+   inkscape:version="1.2.1 (9c6d41e410, 2022-07-14)"
+   sodipodi:docname="ic_launcher_neon.svg"
+   inkscape:export-xdpi="648"
+   inkscape:export-ydpi="648"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:dc="http://purl.org/dc/elements/1.1/">
+  <title
+     id="title4560">Gadgetbridge Icon</title>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="40.651137"
+     inkscape:cx="12.22598"
+     inkscape:cy="11.352696"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     fit-margin-top="0"
+     fit-margin-left="0"
+     fit-margin-right="0"
+     fit-margin-bottom="0"
+     showguides="true"
+     inkscape:guide-bbox="true"
+     inkscape:window-width="1920"
+     inkscape:window-height="1035"
+     inkscape:window-x="0"
+     inkscape:window-y="45"
+     inkscape:window-maximized="1"
+     inkscape:snap-grids="true"
+     inkscape:snap-to-guides="false"
+     inkscape:snap-page="true"
+     inkscape:showpageshadow="2"
+     inkscape:pagecheckerboard="0"
+     inkscape:deskcolor="#d1d1d1">
+    <inkscape:grid
+       type="xygrid"
+       id="grid4138" />
+    <sodipodi:guide
+       position="10,18"
+       orientation="0,1"
+       id="guide4140"
+       inkscape:locked="false" />
+    <sodipodi:guide
+       position="10,2"
+       orientation="0,1"
+       id="guide4142"
+       inkscape:locked="false" />
+    <sodipodi:guide
+       position="2,10"
+       orientation="1,0"
+       id="guide4144"
+       inkscape:locked="false" />
+    <sodipodi:guide
+       position="0,0"
+       orientation="0,20"
+       id="guide4146"
+       inkscape:locked="false" />
+    <sodipodi:guide
+       position="20,0"
+       orientation="-20,0"
+       id="guide4148"
+       inkscape:locked="false" />
+    <sodipodi:guide
+       position="20,20"
+       orientation="0,-20"
+       id="guide4150"
+       inkscape:locked="false" />
+    <sodipodi:guide
+       position="0,20"
+       orientation="20,0"
+       id="guide4152"
+       inkscape:locked="false" />
+    <sodipodi:guide
+       position="17.934153,10.890707"
+       orientation="1,0"
+       id="guide4154"
+       inkscape:locked="false" />
+    <sodipodi:guide
+       position="8.5170198,7.5753346"
+       orientation="-0.70710678,0.70710678"
+       id="guide4156"
+       inkscape:locked="false" />
+    <sodipodi:guide
+       position="4,10"
+       orientation="1,0"
+       id="guide4160"
+       inkscape:locked="false" />
+    <sodipodi:guide
+       position="16,10"
+       orientation="1,0"
+       id="guide4162"
+       inkscape:locked="false" />
+    <sodipodi:guide
+       position="10,4"
+       orientation="0,1"
+       id="guide4166"
+       inkscape:locked="false" />
+    <sodipodi:guide
+       position="10,15"
+       orientation="0,1"
+       id="guide4168"
+       inkscape:locked="false" />
+    <sodipodi:guide
+       position="10,5"
+       orientation="0,1"
+       id="guide4170"
+       inkscape:locked="false" />
+    <sodipodi:guide
+       position="5,10"
+       orientation="1,0"
+       id="guide4172"
+       inkscape:locked="false" />
+    <sodipodi:guide
+       position="15,10"
+       orientation="1,0"
+       id="guide4174"
+       inkscape:locked="false" />
+  </sodipodi:namedview>
+  <defs
+     id="defs4">
+    <linearGradient
+       inkscape:collect="always"
+       id="linearGradient13455">
+      <stop
+         style="stop-color:#000000;stop-opacity:0.2"
+         offset="0"
+         id="stop13457" />
+      <stop
+         style="stop-color:#000000;stop-opacity:0;"
+         offset="1"
+         id="stop13459" />
+    </linearGradient>
+    <clipPath
+       id="clipPath12478"
+       clipPathUnits="userSpaceOnUse">
+      <circle
+         r="10"
+         cy="10.000005"
+         cx="9.9999781"
+         id="circle12480"
+         style="opacity:1;fill:#2196f3;fill-opacity:1;stroke:none;stroke-width:0.05069799;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+    </clipPath>
+    <clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath5790">
+      <circle
+         r="10"
+         cy="10.000005"
+         cx="9.9999781"
+         id="circle5792"
+         style="opacity:1;fill:#2196f3;fill-opacity:1;stroke:none;stroke-width:0.05069799;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         inkscape:export-filename="/home/juergen/path4136.png"
+         inkscape:export-xdpi="648"
+         inkscape:export-ydpi="648" />
+    </clipPath>
+    <clipPath
+       id="clipPath5055"
+       clipPathUnits="userSpaceOnUse">
+      <circle
+         r="10"
+         cy="10.000005"
+         cx="9.9999781"
+         id="circle5057"
+         style="opacity:1;fill:#2196f3;fill-opacity:1;stroke:none;stroke-width:0.05069799;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         inkscape:export-filename="/home/juergen/path4136.png"
+         inkscape:export-xdpi="648"
+         inkscape:export-ydpi="648" />
+    </clipPath>
+    <clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath5119">
+      <circle
+         r="10"
+         cy="10.000005"
+         cx="9.9999781"
+         id="circle5121"
+         style="opacity:1;fill:#2196f3;fill-opacity:1;stroke:none;stroke-width:0.05069799;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         inkscape:export-filename="/home/juergen/Schreibtisch/path4136.png"
+         inkscape:export-xdpi="648"
+         inkscape:export-ydpi="648" />
+    </clipPath>
+    <clipPath
+       id="clipPath5194"
+       clipPathUnits="userSpaceOnUse">
+      <circle
+         r="10"
+         cy="10.000005"
+         cx="9.9999781"
+         id="circle5196"
+         style="opacity:1;fill:#cddc39;fill-opacity:1;stroke:none;stroke-width:0.05069799;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         inkscape:export-filename="/home/juergen/Schreibtisch/path4136.png"
+         inkscape:export-xdpi="648"
+         inkscape:export-ydpi="648" />
+    </clipPath>
+    <clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath6175">
+      <circle
+         r="10"
+         cy="10.000005"
+         cx="9.9999781"
+         id="circle6177"
+         style="opacity:1;fill:#f44336;fill-opacity:1;stroke:none;stroke-width:0.05069799;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         inkscape:export-filename="/home/juergen/Schreibtisch/path4136.png"
+         inkscape:export-xdpi="648"
+         inkscape:export-ydpi="648" />
+    </clipPath>
+    <clipPath
+       id="clipPath7176"
+       clipPathUnits="userSpaceOnUse">
+      <circle
+         r="10"
+         cy="10.000005"
+         cx="9.9999781"
+         id="circle7178"
+         style="opacity:1;fill:#9c27b0;fill-opacity:1;stroke:none;stroke-width:0.05069799;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         inkscape:export-filename="/home/juergen/Schreibtisch/path4136.png"
+         inkscape:export-xdpi="648"
+         inkscape:export-ydpi="648" />
+    </clipPath>
+    <clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath8165">
+      <circle
+         r="10"
+         cy="10.000005"
+         cx="9.9999781"
+         id="circle8167"
+         style="opacity:1;fill:#ffc107;fill-opacity:1;stroke:none;stroke-width:0.05069799;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         inkscape:export-filename="/home/juergen/Schreibtisch/path4136.png"
+         inkscape:export-xdpi="648"
+         inkscape:export-ydpi="648" />
+    </clipPath>
+    <clipPath
+       id="clipPath9162"
+       clipPathUnits="userSpaceOnUse">
+      <circle
+         r="10"
+         cy="10.000005"
+         cx="9.9999781"
+         id="circle9164"
+         style="opacity:1;fill:#ff9800;fill-opacity:1;stroke:none;stroke-width:0.05069799;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         inkscape:export-filename="/home/juergen/Schreibtisch/path4136.png"
+         inkscape:export-xdpi="648"
+         inkscape:export-ydpi="648" />
+    </clipPath>
+    <clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath5147">
+      <circle
+         r="10"
+         cy="10.000005"
+         cx="9.9999781"
+         id="circle5149"
+         style="opacity:1;fill:#ff9800;fill-opacity:1;stroke:none;stroke-width:0.05069799;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         inkscape:export-filename="/home/juergen/Schreibtisch/path4136.png"
+         inkscape:export-xdpi="648"
+         inkscape:export-ydpi="648" />
+    </clipPath>
+    <clipPath
+       id="clipPath6144"
+       clipPathUnits="userSpaceOnUse">
+      <circle
+         r="10"
+         cy="10.000005"
+         cx="9.9999781"
+         id="circle6146"
+         style="opacity:1;fill:#e91e63;fill-opacity:1;stroke:none;stroke-width:0.05069799;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         inkscape:export-filename="/home/juergen/Schreibtisch/path4136.png"
+         inkscape:export-xdpi="648"
+         inkscape:export-ydpi="648" />
+    </clipPath>
+    <clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath7143">
+      <circle
+         r="10"
+         cy="10.000005"
+         cx="9.9999781"
+         id="circle7145"
+         style="opacity:1;fill:#ffeb3b;fill-opacity:1;stroke:none;stroke-width:0.05069799;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         inkscape:export-filename="/home/juergen/Schreibtisch/path4136.png"
+         inkscape:export-xdpi="648"
+         inkscape:export-ydpi="648" />
+    </clipPath>
+    <clipPath
+       id="clipPath8148"
+       clipPathUnits="userSpaceOnUse">
+      <circle
+         r="10"
+         cy="10.000005"
+         cx="9.9999781"
+         id="circle8150"
+         style="opacity:1;fill:#9c27b0;fill-opacity:1;stroke:none;stroke-width:0.05069799;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         inkscape:export-filename="/home/juergen/Schreibtisch/path4136.png"
+         inkscape:export-xdpi="648"
+         inkscape:export-ydpi="648" />
+    </clipPath>
+    <clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath5332">
+      <circle
+         r="10"
+         cy="10.000005"
+         cx="9.9999781"
+         id="circle5334"
+         style="opacity:1;fill:#f44336;fill-opacity:1;stroke:none;stroke-width:0.05069799;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         inkscape:export-filename="/home/juergen/Schreibtisch/path4136.png"
+         inkscape:export-xdpi="648"
+         inkscape:export-ydpi="648" />
+    </clipPath>
+    <clipPath
+       id="clipPath6341"
+       clipPathUnits="userSpaceOnUse">
+      <circle
+         r="10"
+         cy="10.000005"
+         cx="9.9999781"
+         id="circle6343"
+         style="opacity:1;fill:#4caf50;fill-opacity:1;stroke:none;stroke-width:0.05069799;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         inkscape:export-filename="/home/juergen/Schreibtisch/path4136.png"
+         inkscape:export-xdpi="648"
+         inkscape:export-ydpi="648" />
+    </clipPath>
+    <clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath7354">
+      <circle
+         r="10"
+         cy="10.000005"
+         cx="9.9999781"
+         id="circle7356"
+         style="opacity:1;fill:#f44336;fill-opacity:1;stroke:none;stroke-width:0.05069799;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         inkscape:export-filename="/home/juergen/Schreibtisch/path4136.png"
+         inkscape:export-xdpi="648"
+         inkscape:export-ydpi="648" />
+    </clipPath>
+    <clipPath
+       id="clipPath8344"
+       clipPathUnits="userSpaceOnUse">
+      <circle
+         r="10"
+         cy="10.000005"
+         cx="9.9999781"
+         id="circle8346"
+         style="opacity:1;fill:#673ab7;fill-opacity:1;stroke:none;stroke-width:0.05069799;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         inkscape:export-filename="/home/juergen/Schreibtisch/path4136.png"
+         inkscape:export-xdpi="648"
+         inkscape:export-ydpi="648" />
+    </clipPath>
+    <clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath9436">
+      <circle
+         r="10"
+         cy="10.000005"
+         cx="9.9999781"
+         id="circle9438"
+         style="opacity:1;fill:#9e9e9e;fill-opacity:1;stroke:none;stroke-width:0.05069799;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         inkscape:export-filename="/home/juergen/Schreibtisch/path4136.png"
+         inkscape:export-xdpi="648"
+         inkscape:export-ydpi="648" />
+    </clipPath>
+    <clipPath
+       id="clipPath10432"
+       clipPathUnits="userSpaceOnUse">
+      <circle
+         r="10"
+         cy="10.000005"
+         cx="9.9999781"
+         id="circle10434"
+         style="opacity:1;fill:#795548;fill-opacity:1;stroke:none;stroke-width:0.05069799;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         inkscape:export-filename="/home/juergen/Schreibtisch/path4136.png"
+         inkscape:export-xdpi="648"
+         inkscape:export-ydpi="648" />
+    </clipPath>
+    <clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath11433">
+      <circle
+         r="10"
+         cy="10.000005"
+         cx="9.9999781"
+         id="circle11435"
+         style="opacity:1;fill:#03a9f4;fill-opacity:1;stroke:none;stroke-width:0.05069799;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         inkscape:export-filename="/home/juergen/Schreibtisch/path4136.png"
+         inkscape:export-xdpi="648"
+         inkscape:export-ydpi="648" />
+    </clipPath>
+    <clipPath
+       id="clipPath12452"
+       clipPathUnits="userSpaceOnUse">
+      <circle
+         r="10"
+         cy="10.000005"
+         cx="9.9999781"
+         id="circle12454"
+         style="opacity:1;fill:#ffc107;fill-opacity:1;stroke:none;stroke-width:0.05069799;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         inkscape:export-filename="/home/juergen/Schreibtisch/path4136.png"
+         inkscape:export-xdpi="648"
+         inkscape:export-ydpi="648" />
+    </clipPath>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient13455"
+       id="linearGradient13461"
+       x1="9.9553576"
+       y1="9.9553566"
+       x2="20"
+       y2="20"
+       gradientUnits="userSpaceOnUse" />
+    <clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath13465">
+      <circle
+         r="10"
+         cy="10.000005"
+         cx="9.9999781"
+         id="circle13467"
+         style="opacity:1;fill:#e91e63;fill-opacity:1;stroke:none;stroke-width:0.05069799;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         inkscape:export-filename="/home/juergen/Schreibtisch/path4136.png"
+         inkscape:export-xdpi="648"
+         inkscape:export-ydpi="648" />
+    </clipPath>
+    <filter
+       inkscape:collect="always"
+       style="color-interpolation-filters:sRGB"
+       id="filter19962"
+       x="-0.29186323"
+       y="-0.20359246"
+       width="1.5848434"
+       height="1.4061885">
+      <feGaussianBlur
+         inkscape:collect="always"
+         stdDeviation="8.2518751"
+         id="feGaussianBlur19964" />
+    </filter>
+    <filter
+       inkscape:collect="always"
+       style="color-interpolation-filters:sRGB"
+       id="filter21418"
+       x="-0.14902796"
+       y="-0.10438991"
+       width="1.2987264"
+       height="1.208183">
+      <feGaussianBlur
+         inkscape:collect="always"
+         stdDeviation="4.0460295"
+         id="feGaussianBlur21420" />
+    </filter>
+    <filter
+       inkscape:collect="always"
+       style="color-interpolation-filters:sRGB"
+       id="filter25052"
+       x="-0.049952583"
+       y="-0.035255832"
+       width="1.1002407"
+       height="1.070215">
+      <feGaussianBlur
+         inkscape:collect="always"
+         stdDeviation="1.1619187"
+         id="feGaussianBlur25054" />
+    </filter>
+    <filter
+       inkscape:collect="always"
+       style="color-interpolation-filters:sRGB"
+       id="filter30363"
+       x="-0.26535899"
+       y="-0.58787221"
+       width="1.530718"
+       height="2.1757444">
+      <feGaussianBlur
+         inkscape:collect="always"
+         stdDeviation="4.4104817"
+         id="feGaussianBlur30365" />
+    </filter>
+    <filter
+       inkscape:collect="always"
+       style="color-interpolation-filters:sRGB"
+       id="filter31093"
+       x="-0.044116035"
+       y="-0.097733983"
+       width="1.0882321"
+       height="1.195468">
+      <feGaussianBlur
+         inkscape:collect="always"
+         stdDeviation="0.76670202"
+         id="feGaussianBlur31095" />
+    </filter>
+    <filter
+       inkscape:collect="always"
+       style="color-interpolation-filters:sRGB"
+       id="filter33109"
+       x="-0.52248213"
+       y="-0.97576567"
+       width="2.0449643"
+       height="2.9515313">
+      <feGaussianBlur
+         inkscape:collect="always"
+         stdDeviation="5.0943279"
+         id="feGaussianBlur33111" />
+    </filter>
+    <filter
+       inkscape:collect="always"
+       style="color-interpolation-filters:sRGB"
+       id="filter33113"
+       x="-0.084551713"
+       y="-0.15790523"
+       width="1.1691034"
+       height="1.3158105">
+      <feGaussianBlur
+         inkscape:collect="always"
+         stdDeviation="0.75233853"
+         id="feGaussianBlur33115" />
+    </filter>
+  </defs>
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title>Gadgetbridge Icon</dc:title>
+        <cc:license
+           rdf:resource="http://creativecommons.org/licenses/by-sa/4.0/" />
+        <dc:date>18/07/2017</dc:date>
+        <dc:creator>
+          <cc:Agent>
+            <dc:title>xphnx</dc:title>
+          </cc:Agent>
+        </dc:creator>
+        <dc:rights>
+          <cc:Agent>
+            <dc:title>CC (BY-SA)</dc:title>
+          </cc:Agent>
+        </dc:rights>
+        <dc:description>Launcher icon for Gadgetbridge. Based on Blue Minimal Template (GPLv3)</dc:description>
+        <dc:source>https://github.com/scoute-dich/Blue-Minimal/tree/master/IconTemplates</dc:source>
+        <dc:contributor>
+          <cc:Agent>
+            <dc:title>xphnx based on a work of scoute-dich</dc:title>
+          </cc:Agent>
+        </dc:contributor>
+      </cc:Work>
+      <cc:License
+         rdf:about="http://creativecommons.org/licenses/by-sa/4.0/">
+        <cc:permits
+           rdf:resource="http://creativecommons.org/ns#Reproduction" />
+        <cc:permits
+           rdf:resource="http://creativecommons.org/ns#Distribution" />
+        <cc:requires
+           rdf:resource="http://creativecommons.org/ns#Notice" />
+        <cc:requires
+           rdf:resource="http://creativecommons.org/ns#Attribution" />
+        <cc:permits
+           rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
+        <cc:requires
+           rdf:resource="http://creativecommons.org/ns#ShareAlike" />
+      </cc:License>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Hintergrund"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(1058.1428,-861.93365)"
+     style="display:inline">
+    <circle
+       inkscape:export-ydpi="648"
+       inkscape:export-xdpi="648"
+       inkscape:export-filename="/home/juergen/Schreibtisch/path4136.png"
+       style="opacity:1;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.050698;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       id="path4136"
+       cx="-1048.1428"
+       cy="871.93365"
+       r="10" />
+    <path
+       style="fill:url(#linearGradient13461);fill-opacity:1;stroke-width:0.2;stroke-linejoin:round"
+       d="M 6.134855,7.8575793 C 5.5887444,7.7737975 8.9732,11.36764 8.9732,11.91964 c 0.7720204,2.953627 -0.3190754,2.368775 -1.3911929,4.54893 0,0.314113 2.8336269,3.63544 3.0589809,3.818814 0.04391,0.05518 0.09008,0.103734 0.144531,0.148437 0.04494,0.0568 0.09237,0.106597 0.148438,0.152344 0.04462,0.05589 0.09299,0.10335 0.148437,0.148437 0.0433,0.05399 0.08913,0.10065 0.142578,0.144532 0.04401,0.05535 0.08991,0.103622 0.144532,0.148437 0.04462,0.05589 0.09299,0.10335 0.148437,0.148438 0.04281,0.05303 0.08799,0.09936 0.140625,0.142578 0.0433,0.05399 0.08913,0.100649 0.142578,0.144531 0.0429,0.05343 0.08778,0.101002 0.140625,0.144531 0.04305,0.0535 0.08955,0.09904 0.142578,0.142578 0.04259,0.05283 0.08633,0.09947 0.138672,0.142579 0.04241,0.05266 0.08853,0.09762 0.140625,0.140624 0.04231,0.05246 0.08674,0.09775 0.138672,0.140626 0.04224,0.05233 0.08685,0.09785 0.138672,0.140624 0.04196,0.05198 0.0853,0.09806 0.136719,0.140626 0.04204,0.05191 0.218042,0.230956 0.269531,0.273437 0.04036,0.0496 -0.04904,-0.04097 0,0 0.04207,0.05223 0.08706,0.09788 0.138672,0.140625 0.04161,0.05109 1.939454,1.964844 0.136719,0.136719 0.0402,0.04908 0.673987,-1.491056 0.722706,-1.450484 0.0407,0.05002 0.08329,0.09352 0.132812,0.134766 0.03972,0.04878 -0.511067,1.675676 -0.46294,1.716108 0.04056,0.05008 0.08344,0.09344 0.132812,0.134766 0.04069,0.04997 0.08332,0.09357 0.132813,0.134766 0.03936,0.0479 0.08141,0.08913 0.128906,0.128906 0.03997,0.04887 0.0805,0.09236 0.128907,0.132812 0.0394,0.04798 0.208293,0.219929 0.255859,0.259766 0.03933,0.04803 -0.0475,-0.0399 0,0 0.03941,0.04797 0.08133,0.08909 0.128907,0.128906 0.03805,0.04609 0.07742,0.08645 0.123046,0.125 0.03972,0.04853 0.08085,0.09063 0.128907,0.13086 0.03812,0.04614 0.07734,0.08643 0.123047,0.125 0.03891,0.04709 0.08015,0.08776 0.126953,0.126953 0.03709,0.04468 0.0749,0.08353 0.11914,0.121094 0.03888,0.04724 0.07819,0.08957 0.125,0.128906 0.03698,0.0444 0.0751,0.08376 0.119141,0.121094 0.03744,0.0452 0.07636,0.08512 0.121094,0.123046 0.939453,0.953125 0.07678,0.08344 0.121094,0.121094 0.03702,0.04459 0.075,0.0836 0.11914,0.121094 0.03695,0.04448 0.0751,0.08368 0.119141,0.121094 0.03643,0.04385 0.07387,0.08215 0.117187,0.11914 0.03701,0.04454 0.07503,0.08364 0.119141,0.121094 0.03593,0.0429 0.07267,0.08091 0.115234,0.117188 0.03646,0.04374 0.07386,0.08225 0.117188,0.11914 0.03472,0.04132 0.07043,0.07812 0.111328,0.113281 0.03672,0.04408 0.07545,0.08202 0.119141,0.119141 0.159451,0.01975 0.495139,0.310768 0.33789,0.34375 0.03367,0.03982 0.06799,0.0753 0.107422,0.109375 0.03567,0.04259 0.07302,0.07917 0.115234,0.115234 0.03376,0.03984 0.06789,0.07531 0.107422,0.109375 0.03482,0.04131 0.07033,0.07814 0.111328,0.113282 0.03318,0.03905 0.06673,0.07392 0.105469,0.107422 1.997761,0.694544 9.103709,2.908451 10.44526,2.898372 h 0.002 c 0.275418,0 0.52417,-0.112641 0.705078,-0.292969 C 28.886751,29.72516 29,29.4758 29,29.199219 L 27.879654,21.07464 h -0.002 l -0.002,-0.002 -0.002,-0.002 v -0.002 l -0.0039,-0.002 -0.0039,-0.0059 -0.0039,-0.0039 -0.0059,-0.0059 -0.0059,-0.0059 -0.0078,-0.0078 -0.0078,-0.0078 -0.0078,-0.0078 -0.0098,-0.0098 -0.0098,-0.0098 -0.01172,-0.01172 -0.01172,-0.01172 -0.01172,-0.01172 -0.01367,-0.01367 -0.01367,-0.01367 -0.01367,-0.01563 -0.01562,-0.01563 -0.01563,-0.01563 -0.01758,-0.01563 -0.01758,-0.01953 -0.01758,-0.01758 -0.01953,-0.01953 -0.01953,-0.01953 -0.01953,-0.01953 v -0.002 l -0.02148,-0.02148 -0.02148,-0.02148 -0.02344,-0.02344 -0.02344,-0.02344 -0.02344,-0.02344 -0.02539,-0.02539 -0.02539,-0.02734 -0.02539,-0.02539 -0.02734,-0.02734 -0.02734,-0.0293 -0.0293,-0.0293 -0.0293,-0.0293 -0.03125,-0.0293 -0.0293,-0.03125 -0.03125,-0.0332 -0.0332,-0.0332 -0.0332,-0.0332 -0.0332,-0.0332 -0.03516,-0.03516 -0.03516,-0.03516 -0.03516,-0.03711 -0.03711,-0.03711 -0.03711,-0.03906 -0.03906,-0.03711 -0.03906,-0.04102 -0.03906,-0.03906 -0.04102,-0.04102 -0.04102,-0.04297 -0.04297,-0.04102 -0.04102,-0.04102 v -0.002 -0.002 l -0.04492,-0.04297 -0.04297,-0.04492 -0.04492,-0.04492 -0.04687,-0.04687 -0.04492,-0.04687 -0.04883,-0.04883 -0.04687,-0.04687 -0.04883,-0.05078 -0.04883,-0.04883 -0.05078,-0.05078 -0.05078,-0.05273 -0.05078,-0.05078 -0.05273,-0.05469 -0.05273,-0.05273 -0.05469,-0.05469 -0.05469,-0.05469 -0.05469,-0.05664 -0.05664,-0.05664 -0.05664,-0.05859 -0.05859,-0.05859 -0.05664,-0.05859 -0.06055,-0.05859 -0.05859,-0.06055 -0.06055,-0.0625 -0.0625,-0.0625 -0.06055,-0.0625 -0.0625,-0.0625 -0.06445,-0.06445 -0.06445,-0.06641 -0.06445,-0.06445 -0.06641,-0.06641 -0.06641,-0.06836 -0.06641,-0.06836 -0.06836,-0.06836 -0.06836,-0.07031 -0.07031,-0.07031 -0.07031,-0.07031 -0.07031,-0.07227 -0.07227,-0.07227 -0.07227,-0.07422 -0.07227,-0.07422 -0.07422,-0.07422 -0.07422,-0.07617 -0.07617,-0.07617 -0.07617,-0.07617 -0.07617,-0.07813 -0.07813,-0.07813 -0.07813,-0.08008 -0.07813,-0.08008 -0.08008,-0.08008 -0.08203,-0.08203 -0.08008,-0.08203 -0.08203,-0.08398 -0.08398,-0.08398 -0.08203,-0.08398 -0.08398,-0.08594 -0.08594,-0.08594 -0.08594,-0.08789 -0.08594,-0.08594 -0.08789,-0.08984 -0.08789,-0.08789 -0.08789,-0.08984 -0.08984,-0.0918 -0.08984,-0.0918 -0.0918,-0.0918 -0.0918,-0.0918 -0.0918,-0.09375 -0.09375,-0.0957 -0.09375,-0.0957 -0.09375,-0.0957 -0.0957,-0.0957 -0.09766,-0.09766 -0.0957,-0.09766 -0.09766,-0.09961 -0.09961,-0.09961 -0.09766,-0.09961 -0.09961,-0.101562 L 21.012211,14.117126 20.910649,14.01361 20.809087,13.910094 20.705571,13.806579 20.602055,13.70111 20.49854,13.595641 20.393071,13.488219 20.287602,13.380797 20.18018,13.273376 20.072758,13.164 19.965337,13.054626 19.855961,12.94525 19.851219,12.854848 19.741843,12.74352 19.630515,12.630239 19.519187,12.516958 19.405906,12.403676 19.292625,12.288442 19.179343,12.173208 19.064109,12.05602 18.948875,11.940786 18.831687,11.821645 18.7145,11.704458 18.597312,11.585317 18.480125,11.46813 v -0.0039 l -0.119141,-0.121093 -0.121094,-0.121094 -0.121093,-0.123047 -0.121094,-0.123047 -0.121094,-0.123047 -0.123047,-0.125 -0.125,-0.125 -0.123047,-0.125 -0.125,-0.126953 -0.126953,-0.128906 -0.126953,-0.126953 -0.126953,-0.1289072 -0.126953,-0.126953 v -0.0039 L 16.744052,9.7049408 16.613192,9.5740803 16.484286,9.4432213 16.353427,9.3104083 16.220614,9.1756433 16.087802,9.0408773 15.954989,8.9061124 15.820224,8.7713464 15.685458,8.6346274 15.550692,8.4998624 v -0.0039 l -0.136718,-0.136725 -0.136719,-0.138672 -0.136719,-0.136719 v -0.0039 L 15.001864,7.9393154 14.861239,7.7986902 14.722567,7.6580652 14.581942,7.5174402 v -0.00391 L 14.439364,7.3709558 14.296786,7.2264245 14.154208,7.0818933 v 0 L 14.007009,6.9923893 13.395553,6.3374386 12.445852,5.7116899 11.682337,4.9970274 11.461514,4.8293121 10.294776,3.8349929 C 9.7457728,3.3921062 9.0255387,4.1644713 9.0255387,4.1644713 L 7.8983224,5.0493128 C 7.7279474,5.1830537 9.7681952,6.5418811 9.4189571,6.5258336 9.1783033,6.5147756 7.9346571,5.7850447 7.4996252,5.5958611 6.9046896,5.3371397 6.6803727,7.9412701 6.134855,7.8575793 Z"
+       id="path12581"
+       clip-path="url(#clipPath13465)"
+       inkscape:connector-curvature="0"
+       sodipodi:nodetypes="sccccccccccccccccccccccccccccccccccccccccccccccccccccccccccsccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccssss"
+       transform="translate(-1058.1428,861.93365)" />
+    <path
+       inkscape:connector-curvature="0"
+       id="path12456"
+       d="m -1050.6874,868.40686 0.1339,3.79465 0.2678,0.89285 1.1161,0.75893 1.5179,0.0446 1.6071,-0.40178 0.5357,-1.25 -0.1339,-3.83929 h -4.9107 z"
+       style="fill:none;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+    <g
+       style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-opacity:0.509804"
+       id="g6427"
+       transform="matrix(0.10900039,0,0,0.10900039,-1058.4564,854.81407)">
+      <g
+         transform="translate(-0.19198408,15.28344)"
+         id="g5859"
+         style="fill:#ff5000;fill-opacity:1">
+        <g
+           id="g4591"
+           transform="matrix(0.96469159,0,0,0.96469159,3.3476544,3.8272566)">
+          <g
+             style="display:none;fill:#ffffff;fill-opacity:1;stroke:none;stroke-opacity:0.509804"
+             id="g4600"
+             transform="matrix(2.0186569,0,0,2.0186569,394.57822,-108.82609)"
+             inkscape:label="hand"
+             sodipodi:insensitive="true">
+            <path
+               style="display:inline;fill:#ffffff;fill-opacity:1;stroke-width:0.264583"
+               d="m -136.77732,155.22241 -0.52821,-12.65678 c -0.18542,-8.88992 7.31203,-16.43236 9.31124,-23.07234 -2.12299,2.02696 -6.52318,11.59925 -11.09114,10.79579 -2.54776,-0.85577 -3.35187,-2.05496 -4.38456,-4.23069 -3.42275,0.6668 -6.14301,-1.35553 -7.13076,-3.79658 l 0.19658,9.86107 -4.46061,-12.79348 c -3.48621,-1.12814 -6.68377,-6.4807 -3.47994,-5.86765 l 5.75274,1.10079 c 3.93852,0.82896 6.15619,-4.49751 2.63121,-7.2623 -0.7499,-0.53152 -8.27096,-4.04174 -9.68588,-4.44079 -1.26974,-0.3581 -2.78089,0.14533 -3.75024,1.24936 l -5.85918,10.88114 c -0.60845,0.8393 -0.61164,1.71753 -0.25253,2.49043 l 10.72151,21.51532 c 0.34668,0.65416 0.17252,2.20422 0.17823,2.73721 l -1.28045,13.4895 z m 8.9707,-38.19102 c 1.9434,-2.20392 -1.65635,-8.33603 -3.1421,-6.55825 l -9.61026,12.5855 c -2.81396,3.5023 1.43178,6.96399 4.448,4.80543 0.26564,-0.24803 0.41253,-0.37935 0.60397,-0.63064 z m -4.69458,-9.53761 c -1.42924,-1.48182 -4.34931,-3.63246 -4.73609,-3.48404 l -10.71091,14.46059 c -2.88854,2.81736 0.36005,6.17635 3.51209,5.84536 0.43402,-0.12765 0.58795,-0.21146 0.85842,-0.46309 l 11.1656,-14.42917 c 0.4913,-0.6349 0.32485,-1.41907 -0.0891,-1.92965 z m -6.36258,-4.65791 c -0.10642,-0.74107 -4.82207,-4.542486 -5.18836,-4.069285 l -5.44048,7.028525 c 6.87574,7.48664 -1.14912,12.14097 -5.91293,9.4195 0.59683,1.91529 4.16535,3.90113 5.27266,2.44458 z m -6.5656,-5.245517 c -1.03574,-2.192099 -5.14677,-3.858127 -7.00157,-3.151321 l -5.88314,7.251058 7.38969,3.05534 z"
+               id="path4606"
+               inkscape:connector-curvature="0"
+               sodipodi:nodetypes="ccccccccscccccccccccccccccccccscccsccscccccc"
+               inkscape:label="path4606" />
+          </g>
+          <g
+             id="g25785"
+             inkscape:label="g25785">
+            <path
+               d="m 118.564,204.31556 -1.05929,-25.33777 v -0.001 c -0.37115,-17.79617 14.66956,-32.89497 18.68001,-46.18729 v 6.2e-4 c -4.25923,4.05909 -13.08664,23.21997 -22.2511,21.61163 l -0.002,-6e-4 c -5.10987,-1.71303 -6.72288,-4.11372 -8.79531,-8.46888 v 2e-4 c -6.867333,1.33428 -12.32428,-2.71419 -14.30443,-7.60065 v 8e-4 l 0.39322,19.74023 v -10e-4 l -8.94779,-25.61044 -0.002,-3.5e-4 C 75.281593,130.20189 68.867683,119.48769 75.295643,120.7149 v 8e-5 l 11.539827,2.2036 0.002,2.6e-4 c 7.90054,1.65816 12.348357,-9.00414 5.27839,-14.53872 l -0.002,-6.5e-4 C 90.60517,107.3132 75.520333,100.28876 72.683523,99.490058 l -0.002,-4.4e-4 c -2.54791,-0.71603 -5.57732,0.29183 -7.52336,2.501542 v 8.4e-4 l -11.754509,21.78222 v 0.001 c -1.21979,1.6799 -1.22581,3.43764 -0.50557,4.98459 v 4.2e-4 l 21.508799,43.07133 v 8e-4 c 0.69616,1.31005 0.34708,4.41201 0.35711,5.47882 v 0.001 l -2.56798,27.00376 v 0 z m 14.99858,-63.71237 m 2.99931,-12.74259 c 0.65002,-0.73557 -0.55372,-2.78178 -1.05126,-2.18885 3.24808,-3.67727 -2.7686,-13.90579 -5.25231,-10.94017 l -3.21399,4.20012 -16.06789,20.99484 c -0.94093,1.16892 0.47948,2.32442 1.48661,1.60453 -4.7026,5.84238 2.39544,11.61648 7.4371,8.01565 0.0903,-0.0831 0.13843,-0.12712 0.20263,-0.211 0.44538,-0.4134 0.69014,-0.6326 1.00913,-1.0516 l 2.576,-3.40465 z m -7.84837,-15.9112 m -1.57088,-3.18224 c -0.47748,-0.49471 -1.45452,-1.21257 -1.58492,-1.16335 -2.38942,-2.47203 -7.27058,-6.05896 -7.91659,-5.81138 l -3.58312,4.8259 -17.905613,24.12289 c -0.965007,0.94032 0.12037,2.06133 1.17364,1.95118 -4.826997,4.69993 0.60388,10.30287 5.872243,9.75074 0.14645,-0.0426 0.19862,-0.0709 0.2889,-0.15494 0.72425,-0.21261 0.98104,-0.35254 1.43445,-0.77214 l 3.7336,-4.81539 18.66596,-24.07052 c 0.16452,-0.21208 0.11035,-0.47399 -0.0281,-0.6446 0.82055,-1.05905 0.54168,-2.36681 -0.15047,-3.21839 z m -10.63703,-7.77059 M 114.379,99.442448 c -0.0361,-0.24756 -1.61301,-1.51621 -1.73539,-1.35864 -0.18256,-1.23848 -8.06304,-7.577006 -8.67494,-6.787653 l -1.81965,2.345637 -9.09425,11.724788 c 2.29914,2.49832 -0.38319,4.05162 -1.97614,3.14398 11.49371,12.48897 -1.92197,20.25298 -9.8867,15.71301 0.19862,0.63943 1.39232,1.3024 1.76348,0.81682 0.9991,3.19482 6.96362,6.50662 8.81337,4.077 l 3.7697,-4.94693 z m -10.97809,-8.75087 m -2.19482,-1.750269 C 100.85902,88.209611 99.484747,87.65328 98.864817,87.888705 97.131437,84.232217 90.26009,81.453767 87.15846,82.632691 L 85.19235,85.0526 c -0.0762,0.09369 -9.836547,12.096118 -9.836547,12.096158 l 2.471677,1.01937 12.35437,5.096982 1.8377,-2.38719 z"
+               style="display:inline;mix-blend-mode:normal;fill:none;fill-opacity:0;stroke:#3a74ff;stroke-width:9.80725;stroke-dasharray:none;stroke-opacity:1;filter:url(#filter19962)"
+               id="path14632" />
+            <path
+               d="m 118.4887,204.26369 -1.05929,-25.33776 v -0.001 c -0.37115,-17.79638 14.66955,-32.89538 18.68001,-46.18766 v 4e-4 c -4.25923,4.05887 -13.08664,23.22015 -22.2511,21.61182 v -6e-4 c -5.11188,-1.71304 -6.72489,-4.11373 -8.79532,-8.46888 h -0.002 c -6.865323,1.33447 -12.32228,-2.71399 -14.30444,-7.60045 v 6e-4 l 0.39323,19.74043 v -8e-4 L 82.202,132.40916 82.2,132.40876 C 75.206283,130.1498 68.792363,119.43538 75.220333,120.66255 v 2e-4 l 11.541837,2.20351 v 2e-4 c 7.90054,1.65838 12.350357,-9.00399 5.27839,-14.53867 l -0.002,-4e-4 c -1.50869,-1.06582 -16.591527,-8.09092 -19.430347,-8.889682 l -0.002,-2e-4 c -2.54591,-0.71628 -5.57732,0.29148 -7.52136,2.501392 l -0.002,6e-4 -11.754499,21.78237 v 8e-4 c -1.21979,1.68001 -1.22581,3.43788 -0.50557,4.98496 v 2e-4 l 21.508799,43.07148 0.002,6e-4 c 0.69416,1.30985 0.34507,4.41221 0.35711,5.47903 v 8e-4 l -2.56998,27.00395 v 0 z m 11.99926,-50.97001 m 5.99863,-25.48491 c 1.30004,-1.471 -1.10744,-5.56311 -2.10052,-4.37697 2.59807,-2.94181 -2.21689,-11.12461 -4.20306,-8.75215 l -6.42796,8.39921 -12.85191,16.79582 c -1.88385,2.33743 0.95697,4.64763 2.97323,3.20766 -3.76168,4.67386 1.91595,9.29306 5.95048,6.41232 0.17655,-0.16556 0.27486,-0.25344 0.40326,-0.4212 0.3551,-0.33072 0.55171,-0.50589 0.80851,-0.84121 l 5.14999,-6.80849 z m -6.2795,-12.72894 m -3.13975,-6.36447 c -0.95497,-0.98914 -2.90904,-2.42451 -3.16784,-2.32582 -1.91194,-1.97748 -5.81606,-4.84702 -6.33367,-4.64903 l -7.16224,9.6506 -14.326493,19.29821 c -1.932007,1.88019 0.24075,4.12193 2.34929,3.90132 -3.861997,3.75999 0.4815,8.24226 4.696593,7.80064 0.2909,-0.0853 0.39523,-0.14133 0.57579,-0.30929 0.5798,-0.17037 0.78644,-0.28227 1.14756,-0.61779 l 7.46719,-9.62959 14.93237,-19.25637 c 0.32902,-0.4238 0.21868,-0.9473 -0.0582,-1.28823 0.65604,-0.8472 0.43335,-1.8934 -0.12037,-2.57465 z m -8.50842,-6.21653 m -4.25522,-3.108362 c -0.0722,-0.49468 -3.22602,-3.0317 -3.47078,-2.716 -0.14445,-0.99094 -6.44802,-6.061578 -6.93955,-5.430176 l -3.63729,4.690476 -7.27661,9.379952 c 4.598287,4.99616 -0.76839,8.10232 -3.95428,6.28659 9.194557,9.99113 -1.53677,16.20226 -7.90856,12.57039 0.39924,1.27842 2.78665,2.60389 3.52695,1.63216 0.79848,2.55584 5.5713,5.20536 7.0519,3.26171 l 7.535407,-9.89264 z m -8.78127,-7.000683 m -4.39164,-3.500279 C 100.43864,87.42635 97.688097,86.314249 96.448247,86.785539 95.06194,83.860489 89.56486,81.637649 87.08516,82.580848 l -3.93622,4.839096 c -0.0602,0.07501 -7.866427,9.676934 -7.866427,9.676934 l 4.941347,2.03874 9.88269,4.077492 3.67541,-4.774352 z"
+               style="fill:none;fill-opacity:0;stroke:#6190ff;stroke-width:5.88435;stroke-dasharray:none;stroke-opacity:1;filter:url(#filter21418)"
+               id="path14634" />
+            <path
+               d="m 118.40878,204.21796 -1.05929,-25.33776 v -8e-4 c -0.37116,-17.79658 14.66955,-32.89558 18.68001,-46.18807 v 4e-4 c -4.25923,4.05867 -13.08664,23.22036 -22.2511,21.61183 v -2e-4 c -5.11188,-1.71324 -6.72489,-4.11393 -8.79532,-8.46928 l -0.002,2e-4 c -6.865323,1.33447 -12.32228,-2.714 -14.30444,-7.60046 v 4e-4 l 0.39523,19.74044 v -6e-4 l -8.94979,-25.61064 v -2e-4 c -6.993717,-2.25895 -13.409647,-12.97357 -6.981677,-11.7464 v 0 l 11.541837,2.2037 v 2e-4 c 7.90053,1.65858 12.350357,-9.00399 5.27839,-14.53867 v -4e-4 c -1.50868,-1.06542 -16.593527,-8.09092 -19.432347,-8.889682 v -2e-4 c -2.54791,-0.71648 -5.57932,0.29148 -7.52336,2.501392 v 4e-4 l -11.754499,21.78257 -0.002,6e-4 c -1.21979,1.68001 -1.22581,3.43788 -0.50557,4.98496 v 4e-4 l 21.510799,43.07149 v 4e-4 c 0.69416,1.30985 0.34507,4.41221 0.35711,5.47923 v 6e-4 l -2.56998,27.00415 h 0.002 z m 8.99995,-38.22745 m 8.99794,-38.22768 c 1.95005,-2.2061 -1.66116,-8.34416 -3.15179,-6.56486 1.95006,-2.2063 -1.66116,-8.34336 -3.15179,-6.56406 l -9.63993,12.59802 -9.63994,12.59701 c -2.82277,3.50595 1.43646,6.97106 4.46186,4.8106 -2.82277,3.50554 1.43646,6.96985 4.46185,4.80939 0.26683,-0.24824 0.41329,-0.37997 0.60588,-0.63161 0.26683,-0.24803 0.41329,-0.37936 0.60588,-0.6308 l 7.72399,-10.21235 z m -4.70862,-9.5467 m -4.70863,-9.54671 c -1.43445,-1.48341 -4.36355,-3.63627 -4.75276,-3.48793 -1.43244,-1.48321 -4.36154,-3.63526 -4.75075,-3.48672 l -10.74336,14.475 -10.743373,14.47381 c -2.899007,2.82029 0.35912,6.1825 3.52094,5.85138 -2.89499,2.82009 0.36312,6.1817 3.524943,5.85038 0.43536,-0.12772 0.58984,-0.2116 0.86068,-0.46364 0.43535,-0.12772 0.58983,-0.2116 0.86067,-0.46324 l 11.20078,-14.44378 11.20079,-14.44217 c 0.49152,-0.63561 0.32501,-1.42076 -0.0903,-1.93205 0.49353,-0.6354 0.32701,-1.41995 -0.0883,-1.93104 z m -6.38382,-4.66225 m -6.38182,-4.662442 c -0.10633,-0.74191 -4.83702,-4.54714 -5.20416,-4.07369 -0.11034,-0.743112 -4.83702,-4.546138 -5.20416,-4.072686 l -5.458963,7.035716 -5.456947,7.034912 c 6.897427,7.49395 -1.15158,12.15299 -5.93042,9.42919 6.89541,7.49335 -1.15358,12.15179 -5.93242,9.42779 0.59986,1.91723 4.17897,3.90533 5.29043,2.44754 0.59785,1.91683 4.17697,3.90392 5.28842,2.44613 l 11.30512,-14.83795 z m -6.58645,-5.250413 m -6.58646,-5.250609 c -1.03923,-2.194494 -5.162033,-3.862284 -7.02382,-3.15521 -1.03923,-2.193692 -5.16203,-3.860883 -7.02181,-3.153408 l -5.90233,7.258323 c -0.0461,0.05625 -5.900317,7.257727 -5.900317,7.257727 l 7.413017,3.058122 7.41302,3.05791 5.51112,-7.161632 z"
+               style="fill:none;fill-opacity:0;stroke:#89acff;stroke-width:2.94218;stroke-dasharray:none;stroke-opacity:1;filter:url(#filter25052)"
+               id="path14636" />
+            <path
+               d="m 120.08988,205.06635 -1.05929,-25.33817 v 0 c -0.37316,-17.79698 14.66956,-32.89538 18.68001,-46.18806 v 0 c -4.25923,4.05787 -13.08664,23.22015 -22.2511,21.61262 v -0.002 c -5.11188,-1.71164 -6.72488,-4.11193 -8.79732,-8.46808 v 0 c -6.867327,1.33527 -12.32428,-2.71459 -14.30644,-7.60125 v 0 l 0.39523,19.74083 v 0 l -8.9498,-25.61043 v 0 C 76.807463,130.95285 70.393543,120.23763 76.821503,121.465 v 0 l 11.539837,2.20371 v 0 c 7.90254,1.65918 12.35036,-9.00379 5.27839,-14.53847 v -2e-4 c -1.50467,-1.06442 -16.593527,-8.09112 -19.430337,-8.88988 l -0.002,-2e-4 c -2.54591,-0.716691 -5.57732,0.29128 -7.52336,2.50118 v 2e-4 l -11.754509,21.78298 v 2e-4 c -1.21979,1.68 -1.2258,3.43808 -0.50557,4.98536 v 0 l 21.508799,43.07148 v 0 c 0.69616,1.30925 0.34708,4.41221 0.35711,5.47923 v 0 l -2.56798,27.00576 v 0 z m 2.99931,-12.74216 m 14.99858,-63.71317 c 3.24809,-3.67671 -2.7706,-13.90666 -5.25431,-10.94104 0.65001,-0.7353 -0.55372,-2.78105 -1.04926,-2.18788 l -16.0679,20.99682 -3.21398,4.19801 c -4.70461,5.84357 2.39343,11.61708 7.43709,8.01765 -0.94092,1.16711 0.47949,2.32221 1.48662,1.60152 0.44538,-0.41239 0.69014,-0.6326 1.01114,-1.051 0.0883,-0.0821 0.13642,-0.12612 0.20062,-0.2102 l 12.87398,-17.02023 z m -1.57088,-3.18224 m -7.84837,-15.91117 c -2.38942,-2.47216 -7.27058,-6.05998 -7.91859,-5.81255 -0.47748,-0.49427 -1.45452,-1.21175 -1.58292,-1.1621 l -17.90761,24.12421 -3.581123,4.8246 c -4.829007,4.70088 0.60187,10.30423 5.872243,9.7517 -0.96701,0.9409 0.12037,2.05996 1.17364,1.94986 0.72626,-0.2122 0.98305,-0.35234 1.43646,-0.77274 0.14445,-0.042 0.19461,-0.0701 0.28689,-0.15415 l 18.66597,-24.07176 3.7336,-4.81399 c 0.82255,-1.05941 0.54368,-2.36766 -0.14847,-3.21947 0.16452,-0.2118 0.10834,-0.47325 -0.0301,-0.64361 z m -2.1266,-1.55408 m -10.63904,-7.77062 c -0.17655,-1.236385 -8.06104,-7.578263 -8.67294,-6.788915 -0.0361,-0.24764 -1.61301,-1.515248 -1.73539,-1.357498 l -9.09626,11.725633 -1.81764,2.34483 c 11.4937,12.48972 -1.92197,20.25452 -9.8867,15.71439 2.29914,2.49779 -0.38319,4.05067 -1.97614,3.1426 0.9971,3.19545 6.96363,6.50841 8.81538,4.07849 0.19861,0.63901 1.39232,1.30124 1.76147,0.81538 l 18.84052,-24.72939 z m -2.19482,-1.750285 m -10.97609,-8.750758 c -1.73137,-3.65709 -8.60473,-6.436541 -11.70636,-5.25762 -0.34708,-0.73109 -1.72135,-1.286831 -2.34127,-1.051001 L 78.8498,95.479995 c -0.0161,0.0186 -1.968107,2.41932 -1.968107,2.41932 l 12.354357,5.096875 2.47168,1.01897 9.18654,-11.936033 z"
+               style="display:none;opacity:0.425;fill:none;fill-opacity:0;stroke:#000000;stroke-width:0.980725;stroke-dasharray:none;stroke-opacity:1"
+               id="path25896"
+               inkscape:label="shadow" />
+            <path
+               d="m 118.23062,204.15112 -1.05929,-25.33817 v 0 c -0.37316,-17.79698 14.66956,-32.89538 18.68001,-46.18806 v 0 c -4.25923,4.05787 -13.08664,23.22015 -22.2511,21.61262 v -0.002 c -5.11188,-1.71164 -6.72488,-4.11193 -8.79732,-8.46808 v 0 c -6.867333,1.33527 -12.32428,-2.71459 -14.30644,-7.60125 v 0 l 0.39523,19.74083 v 0 l -8.9498,-25.61043 v 0 C 74.948203,130.03762 68.534283,119.3224 74.962243,120.54977 v 0 l 11.539837,2.20371 v 0 c 7.90254,1.65918 12.350357,-9.00379 5.27839,-14.53847 v -2e-4 C 90.2758,107.15039 75.186943,100.12369 72.350133,99.32492 l -0.002,-2e-4 c -2.54591,-0.71669 -5.57732,0.29128 -7.52336,2.50119 v 2e-4 l -11.754509,21.78298 v 2e-4 c -1.21979,1.68 -1.2258,3.43808 -0.50557,4.98536 v 0 l 21.508799,43.07148 v 0 c 0.69616,1.30925 0.34708,4.41221 0.35711,5.47923 v 0 l -2.56798,27.00576 v 0 z m 2.99931,-12.74216 m 14.99858,-63.71317 c 3.24809,-3.67671 -2.7706,-13.90666 -5.25431,-10.94104 0.65001,-0.7353 -0.55372,-2.78105 -1.04926,-2.18788 l -16.0679,20.99682 -3.21398,4.19801 c -4.70461,5.84357 2.39343,11.61708 7.43709,8.01765 -0.94092,1.16711 0.47949,2.32221 1.48662,1.60152 0.44538,-0.41239 0.69014,-0.6326 1.01114,-1.051 0.0883,-0.0821 0.13642,-0.12612 0.20062,-0.2102 l 12.87398,-17.02023 z m -1.57088,-3.18224 m -7.84837,-15.91117 c -2.38942,-2.47216 -7.27058,-6.05998 -7.91859,-5.81255 -0.47748,-0.49427 -1.45452,-1.21175 -1.58292,-1.1621 l -17.907613,24.12421 -3.58112,4.8246 c -4.829007,4.70088 0.60187,10.30423 5.872243,9.7517 -0.96701,0.9409 0.12037,2.05996 1.17364,1.94986 0.72626,-0.2122 0.98305,-0.35234 1.43646,-0.77274 0.14445,-0.042 0.19461,-0.0701 0.28689,-0.15415 l 18.66597,-24.07176 3.7336,-4.81399 c 0.82255,-1.05941 0.54368,-2.36766 -0.14847,-3.21947 0.16452,-0.2118 0.10834,-0.47325 -0.0301,-0.64361 z m -2.1266,-1.55408 m -10.63904,-7.77063 c -0.17655,-1.23638 -8.06104,-7.578258 -8.67294,-6.78891 -0.0361,-0.24764 -1.61301,-1.515248 -1.73539,-1.357498 l -9.09626,11.725638 -1.81764,2.34483 c 11.4937,12.48972 -1.92197,20.25452 -9.8867,15.71439 2.29914,2.49779 -0.38319,4.05067 -1.97614,3.1426 0.9971,3.19545 6.96363,6.50841 8.81538,4.07849 0.19861,0.63901 1.39232,1.30124 1.76147,0.81538 L 110.27592,104.2232 Z M 111.8488,97.52739 M 100.87271,88.776632 C 99.141337,85.119542 92.26798,82.340091 89.16635,83.519012 88.81927,82.787922 87.445,82.232181 86.82508,82.468011 L 76.99054,94.56476 c -0.0161,0.0186 -1.968107,2.41932 -1.968107,2.41932 l 12.354357,5.09688 2.47168,1.01897 9.186537,-11.936038 z"
+               style="display:none;fill:none;fill-opacity:0;stroke:#ffffff;stroke-width:0.980725;stroke-dasharray:none;stroke-opacity:1"
+               id="path14640" />
+          </g>
+          <path
+             style="display:inline;opacity:0.42;fill:none;fill-opacity:1;stroke:#000000;stroke-width:0.980725;stroke-dasharray:none;stroke-opacity:1"
+             d="m 120.40041,206.54569 -1.06605,-25.54964 c -0.37087,-17.94578 14.76008,-33.1713 18.79506,-46.57516 -4.28509,4.0917 -13.16718,23.41492 -22.38783,21.79298 -5.14287,-1.72751 -6.76618,-4.14829 -8.85072,-8.54033 -6.908843,1.34606 -12.400778,-2.73633 -14.394039,-7.66397 l 0.399413,19.90609 -9.003919,-25.82563 C 76.85504,131.81275 70.399755,121.0077 76.867402,122.24525 l 11.612473,2.22212 c 7.950225,1.67339 12.427465,-9.07888 5.311244,-14.66005 -1.513965,-1.07292 -16.695481,-8.15886 -19.551288,-8.96437 -2.563854,-0.72276 -5.613661,0.29291 -7.570787,2.52198 l -11.827388,21.96529 c -1.227721,1.69422 -1.234377,3.46708 -0.513531,5.0273 l 21.6425,43.43199 c 0.703728,1.32057 0.351863,4.44957 0.361373,5.52554 l -2.584776,27.23064 z m 18.10845,-77.09457 c 3.92278,-4.4489 -3.34362,-16.82758 -6.343,-13.23877 l -19.39892,25.40576 c -5.68017,7.06997 2.89001,14.05787 8.97909,9.70045 0.53255,-0.50023 0.83687,-0.76556 1.21916,-1.27302 z m -9.47645,-19.25313 c -2.88527,-2.99129 -8.77941,-7.33262 -9.56015,-7.03305 l -21.621391,29.19093 c -5.830482,5.68731 0.732258,12.46789 7.089551,11.79981 0.87489,-0.25773 1.18681,-0.42701 1.73267,-0.93484 l 22.53905,-29.12752 c 0.99187,-1.28167 0.65617,-2.86462 -0.18069,-3.89533 z m -12.84387,-9.40269 c -0.21873,-1.495929 -9.73322,-9.169699 -10.47307,-8.214509 L 94.733545,106.76895 c 13.879545,15.11292 -2.31945,24.50848 -11.935803,19.01471 1.204897,3.86632 8.407652,7.87509 10.643416,4.93477 z M 102.93577,90.206411 c -2.0912,-4.425124 -10.389488,-7.788263 -14.133511,-6.361468 l -11.875896,14.637418 14.917141,6.167659 z"
+             id="path39861"
+             inkscape:connector-curvature="0"
+             sodipodi:nodetypes="ccccccccscccccccccccccccccccccscccsccscccccc"
+             inkscape:label="hand shadow" />
+          <path
+             style="display:inline;fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:0.98072536;stroke-dasharray:none;stroke-opacity:1"
+             d="m 118.27655,204.80468 -1.06608,-25.54964 c -0.37089,-17.94578 14.76057,-33.1713 18.79569,-46.57516 -4.28523,4.0917 -13.16763,23.41492 -22.38859,21.79298 -5.14304,-1.72751 -6.76641,-4.14829 -8.85102,-8.54033 -6.90906,1.34606 -12.401122,-2.73633 -14.394431,-7.66397 l 0.399422,19.90609 -9.004129,-25.82563 c -7.037447,-2.27728 -13.492879,-13.08233 -7.025084,-11.84478 l 11.612739,2.22212 c 7.950414,1.67339 12.427753,-9.07888 5.311371,-14.66005 -1.514002,-1.07292 -16.695869,-8.158864 -19.551741,-8.964364 -2.563913,-0.72277 -5.613791,0.29291 -7.570962,2.521974 l -11.827667,21.96529 c -1.22775,1.69422 -1.234407,3.46708 -0.513544,5.0273 l 21.643005,43.43199 c 0.703745,1.32057 0.351872,4.44957 0.361382,5.52554 l -2.584835,27.23064 z m 18.10906,-77.09457 c 3.92291,-4.4489 -3.34373,-16.82758 -6.34321,-13.23877 l -19.39958,25.40576 c -5.68036,7.06997 2.89011,14.05787 8.9794,9.70045 0.53257,-0.50023 0.83689,-0.76556 1.21919,-1.27302 z m -9.47677,-19.25313 c -2.88536,-2.99129 -8.7797,-7.33262 -9.56047,-7.03305 l -21.622085,29.19093 c -5.830621,5.68731 0.732275,12.46789 7.089755,11.79981 0.87492,-0.25773 1.18685,-0.42701 1.73273,-0.93484 l 22.53981,-29.12752 c 0.9919,-1.28167 0.65619,-2.86462 -0.1807,-3.89533 z m -12.8443,-9.402684 c -0.21873,-1.49593 -9.73355,-9.169696 -10.47343,-8.214505 L 92.608886,105.02794 c 13.879944,15.11292 -2.319505,24.50848 -11.936082,19.01471 1.204925,3.86632 8.407848,7.87509 10.643664,4.93477 z M 100.81132,88.465414 C 98.720053,84.040285 90.421571,80.677146 86.677459,82.103941 L 74.80129,96.741357 89.718777,102.90901 Z"
+             id="path4606-3"
+             inkscape:connector-curvature="0"
+             sodipodi:nodetypes="ccccccccscccccccccccccccccccccscccsccscccccc"
+             inkscape:label="hand white" />
+          <g
+             id="g31229">
+            <path
+               id="path27449"
+               style="display:none;opacity:1;fill:#ff5000;fill-opacity:1;stroke:#ffffff;stroke-width:0.980725;stroke-dasharray:none;stroke-opacity:1"
+               inkscape:label="band"
+               d="m 75.805193,175.19803 h 39.669647 c 3.64415,0 6.57788,2.93373 6.57788,6.57787 v 10.68905 c 0,3.64415 -2.93373,6.57788 -6.57788,6.57788 H 75.805193 c -3.644143,0 -6.577875,-2.93373 -6.577875,-6.57788 V 181.7759 c 0,-3.64414 2.933732,-6.57787 6.577875,-6.57787 z" />
+            <path
+               id="path30353"
+               style="display:inline;opacity:1;fill:#ff5000;fill-opacity:1;stroke:#ff5000;stroke-width:6.8650775;stroke-dasharray:none;stroke-opacity:1;filter:url(#filter30363)"
+               inkscape:label="band"
+               d="m 75.805193,175.19803 h 39.669647 c 3.64415,0 6.57788,2.93373 6.57788,6.57787 v 10.68905 c 0,3.64415 -2.93373,6.57788 -6.57788,6.57788 H 75.805193 c -3.644143,0 -6.577875,-2.93373 -6.577875,-6.57788 V 181.7759 c 0,-3.64414 2.933732,-6.57787 6.577875,-6.57787 z" />
+            <path
+               id="path30355"
+               style="display:inline;opacity:1;fill:#ff5000;fill-opacity:1;stroke:#ff5000;stroke-width:0.980725;stroke-dasharray:none;stroke-opacity:1;filter:url(#filter31093)"
+               inkscape:label="band"
+               d="m 75.805193,175.19803 h 39.669647 c 3.64415,0 6.57788,2.93373 6.57788,6.57787 v 10.68905 c 0,3.64415 -2.93373,6.57788 -6.57788,6.57788 H 75.805193 c -3.644143,0 -6.577875,-2.93373 -6.577875,-6.57788 V 181.7759 c 0,-3.64414 2.933732,-6.57787 6.577875,-6.57787 z" />
+            <path
+               id="path31097"
+               style="display:inline;opacity:0.513416;fill:none;fill-opacity:1;stroke:#000000;stroke-width:0.78458;stroke-dasharray:none;stroke-opacity:1"
+               inkscape:label="band shadow"
+               d="m 77.193928,176.53331 h 39.669642 c 3.64415,0 6.57788,2.93373 6.57788,6.57787 v 10.68905 c 0,3.64415 -2.93373,6.57788 -6.57788,6.57788 H 77.193928 c -3.644143,0 -6.577875,-2.93373 -6.577875,-6.57788 v -10.68905 c 0,-3.64414 2.933732,-6.57787 6.577875,-6.57787 z" />
+            <path
+               id="path30360"
+               style="display:inline;opacity:1;fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:0.78458;stroke-dasharray:none;stroke-opacity:1"
+               inkscape:label="band"
+               d="m 75.805193,175.19803 h 39.669647 c 3.64415,0 6.57788,2.93373 6.57788,6.57787 v 10.68905 c 0,3.64415 -2.93373,6.57788 -6.57788,6.57788 H 75.805193 c -3.644143,0 -6.577875,-2.93373 -6.577875,-6.57788 V 181.7759 c 0,-3.64414 2.933732,-6.57787 6.577875,-6.57787 z" />
+          </g>
+          <g
+             id="g31222">
+            <path
+               id="rect4610-3"
+               style="display:none;opacity:1;fill:#1359ff;fill-opacity:1;stroke:none;stroke-width:8.97838;stroke-opacity:0.509804"
+               inkscape:label="watch"
+               d="m 86.073767,179.85034 h 19.132503 c 2.22214,0 4.01109,1.78895 4.01109,4.01109 v 6.51803 c 0,2.22214 -1.78895,4.01109 -4.01109,4.01109 H 86.073767 c -2.222145,0 -4.011092,-1.78895 -4.011092,-4.01109 v -6.51803 c 0,-2.22214 1.788947,-4.01109 4.011092,-4.01109 z" />
+            <path
+               id="path31154"
+               style="display:inline;opacity:1;fill:#1359ff;fill-opacity:1;stroke:#1359ff;stroke-width:3.92290143;stroke-opacity:1;stroke-dasharray:none;filter:url(#filter33109)"
+               inkscape:label="watch"
+               d="m 86.073767,179.85034 h 19.132503 c 2.22214,0 4.01109,1.78895 4.01109,4.01109 v 6.51803 c 0,2.22214 -1.78895,4.01109 -4.01109,4.01109 H 86.073767 c -2.222145,0 -4.011092,-1.78895 -4.011092,-4.01109 v -6.51803 c 0,-2.22214 1.788947,-4.01109 4.011092,-4.01109 z" />
+            <path
+               id="path31156"
+               style="display:inline;opacity:1;fill:none;fill-opacity:1;stroke:#1359ff;stroke-width:0.98072536;stroke-opacity:1;stroke-dasharray:none;filter:url(#filter33113)"
+               inkscape:label="watch"
+               d="m 86.073767,179.85034 h 19.132503 c 2.22214,0 4.01109,1.78895 4.01109,4.01109 v 6.51803 c 0,2.22214 -1.78895,4.01109 -4.01109,4.01109 H 86.073767 c -2.222145,0 -4.011092,-1.78895 -4.011092,-4.01109 v -6.51803 c 0,-2.22214 1.788947,-4.01109 4.011092,-4.01109 z" />
+            <path
+               style="display:inline;opacity:0.42;fill:none;fill-opacity:1;stroke:#000000;stroke-width:0.686508;stroke-dasharray:none;stroke-opacity:1"
+               d="m 87.439354,181.05234 h 19.132496 c 2.22214,0 4.01109,1.78895 4.01109,4.01109 v 6.51803 c 0,2.22214 -1.78895,4.01109 -4.01109,4.01109 H 87.439354 c -2.222145,0 -4.011092,-1.78895 -4.011092,-4.01109 v -6.51803 c 0,-2.22214 1.788947,-4.01109 4.011092,-4.01109 z"
+               id="path31160"
+               inkscape:label="watch shadow" />
+            <path
+               id="path31214"
+               style="display:inline;opacity:1;fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:0.686508;stroke-dasharray:none;stroke-opacity:1"
+               inkscape:label="watch"
+               d="m 86.073767,179.85034 h 19.132503 c 2.22214,0 4.01109,1.78895 4.01109,4.01109 v 6.51803 c 0,2.22214 -1.78895,4.01109 -4.01109,4.01109 H 86.073767 c -2.222145,0 -4.011092,-1.78895 -4.011092,-4.01109 v -6.51803 c 0,-2.22214 1.788947,-4.01109 4.011092,-4.01109 z" />
+          </g>
+        </g>
+      </g>
+    </g>
+  </g>
+  <g
+     style="display:inline"
+     inkscape:groupmode="layer"
+     id="layer2"
+     inkscape:label="Icon">
+    <path
+       d="m 3.9999777,4.0333382 12.0000003,0 0,11.9333328 -12.0000003,0 z"
+       id="path4"
+       inkscape:connector-curvature="0"
+       style="fill:none" />
+    <path
+       style="fill:none"
+       inkscape:connector-curvature="0"
+       id="path9956"
+       d="m 18.999978,19.033339 12,0 0,11.933333 -12,0 z" />
+  </g>
+</svg>
diff --git a/app/src/main/assets/ic_no_watch.svg b/app/src/main/assets/ic_no_watch.svg
new file mode 100644
index 0000000..1257e6b
--- /dev/null
+++ b/app/src/main/assets/ic_no_watch.svg
@@ -0,0 +1,121 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
+   sodipodi:docname="ic_no_watch.svg"
+   id="svg6"
+   version="1.1"
+   width="24"
+   viewBox="0 0 24 24"
+   height="24">
+  <metadata
+     id="metadata12">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs10">
+    <inkscape:path-effect
+       simplify_just_coalesce="false"
+       simplify_individual_paths="false"
+       helper_size="0"
+       smooth_angles="360"
+       threshold="0.00108696"
+       steps="1"
+       lpeversion="1"
+       is_visible="true"
+       id="path-effect3556"
+       effect="simplify" />
+  </defs>
+  <sodipodi:namedview
+     inkscape:current-layer="svg6"
+     inkscape:window-maximized="1"
+     inkscape:window-y="38"
+     inkscape:window-x="0"
+     inkscape:cy="11.716102"
+     inkscape:cx="18.443347"
+     inkscape:zoom="32"
+     inkscape:guide-bbox="true"
+     showguides="true"
+     showgrid="false"
+     id="namedview8"
+     inkscape:window-height="1042"
+     inkscape:window-width="1920"
+     inkscape:pageshadow="2"
+     inkscape:pageopacity="0"
+     guidetolerance="10"
+     gridtolerance="10"
+     objecttolerance="10"
+     borderopacity="1"
+     bordercolor="#666666"
+     pagecolor="#ffffff">
+    <sodipodi:guide
+       id="guide837"
+       orientation="0,-1"
+       position="-8.5737052,20.015936" />
+    <sodipodi:guide
+       id="guide839"
+       orientation="0,-1"
+       position="-9.7211155,4.0159363" />
+    <sodipodi:guide
+       id="guide845"
+       orientation="0,-1"
+       position="4.1752988,24" />
+    <sodipodi:guide
+       id="guide847"
+       orientation="0,-1"
+       position="7.6494024,22.007968" />
+    <sodipodi:guide
+       id="guide3420"
+       orientation="0,-1"
+       position="5.7695406,6.0152602" />
+    <sodipodi:guide
+       id="guide3422"
+       orientation="0,-1"
+       position="4.0567082,2.0036265" />
+    <sodipodi:guide
+       id="guide3550"
+       orientation="1,0"
+       position="2.032932,17.149903" />
+    <sodipodi:guide
+       id="guide3552"
+       orientation="1,0"
+       position="22.185475,15.824078" />
+    <sodipodi:guide
+       id="guide3558"
+       orientation="0,-1"
+       position="2.9610096,18.696699" />
+    <sodipodi:guide
+       id="guide3560"
+       orientation="1,0"
+       position="11.534679,13.084039" />
+    <sodipodi:guide
+       id="guide3562"
+       orientation="0,-1"
+       position="22.516932,5.0848936" />
+  </sodipodi:namedview>
+  <path
+     id="path2"
+     fill="none"
+     d="M0 0h24v24H0V0z" />
+  <path
+     d="M 8 0 L 7.0507812 5.7304688 C 6.8273015 5.9058883 6.6255018 6.1054426 6.4238281 6.3046875 L 8.1308594 7.4375 C 8.5054322 7.1202052 8.9128027 6.8403522 9.3574219 6.6210938 C 10.15599 6.2272895 11.050552 6 12 6 C 13.173766 6 14.265876 6.3435073 15.191406 6.9277344 C 16.875856 7.991018 18 9.8637657 18 12 C 18 12.628035 17.902201 13.23348 17.722656 13.802734 L 19.431641 14.935547 C 19.792399 14.025062 20 13.037464 20 12 C 20 10.148663 19.361897 8.4452049 18.302734 7.0898438 C 17.908745 6.5856742 17.462523 6.1263145 16.960938 5.7304688 L 16 0 L 8 0 z M 9.6992188 2 L 14.310547 2 L 14.720703 4.4804688 C 13.870703 4.1704687 12.96 4 12 4 C 11.05 4 10.129063 4.1707031 9.2890625 4.4707031 L 9.6992188 2 z M 5.3867188 7.5253906 C 4.515003 8.7993422 4 10.335029 4 12 C 4 14.55 5.1907813 16.809531 7.0507812 18.269531 L 8 24 L 16 24 L 16.960938 18.269531 C 17.62693 17.743937 18.203786 17.110661 18.673828 16.400391 L 17.011719 15.291016 C 15.938243 16.920962 14.094668 18 12 18 C 8.69 18 6 15.31 6 12 C 6 10.749526 6.3915691 9.5950783 7.0488281 8.6347656 L 5.3867188 7.5253906 z M 14.720703 19.519531 L 14.310547 22 L 9.6992188 22 L 9.2890625 19.529297 C 10.129062 19.829297 11.05 20 12 20 C 12.96 20 13.870703 19.829531 14.720703 19.519531 z "
+     id="path4" />
+  <path
+     id="path3564"
+     d="M 2.032932,5.303301 22.185475,18.915106"
+     style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+</svg>
diff --git a/app/src/main/assets/ic_shoe.svg b/app/src/main/assets/ic_shoe.svg
new file mode 100644
index 0000000..000a501
--- /dev/null
+++ b/app/src/main/assets/ic_shoe.svg
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" enable-background="new 0 0 24 24" height="24" viewBox="0 0 27 24" width="27" version="1.1" id="svg1685" sodipodi:docname="ic_shoe.svg" inkscape:version="1.0.1 (3bc2e813f5, 2020-09-07)">
+    <metadata id="metadata1691">
+        <rdf:RDF>
+            <cc:Work rdf:about="">
+                <dc:format/>
+                <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
+                <dc:title/>
+            </cc:Work>
+        </rdf:RDF>
+    </metadata>
+    <defs id="defs1689"/>
+    <sodipodi:namedview pagecolor="#ffffff" bordercolor="#666666" borderopacity="1" objecttolerance="10" gridtolerance="10" guidetolerance="10" inkscape:pageopacity="0" inkscape:pageshadow="2" inkscape:window-width="1920" inkscape:window-height="1042" id="namedview1687" showgrid="false" showguides="true" inkscape:guide-bbox="true" inkscape:zoom="22.185475" inkscape:cx="11.936505" inkscape:cy="9.4419739" inkscape:window-x="0" inkscape:window-y="38" inkscape:window-maximized="1" inkscape:current-layer="g1683" inkscape:document-rotation="0" lock-margins="false" fit-margin-left="2.5" fit-margin-right="2.5" fit-margin-top="2.5" fit-margin-bottom="2.5" inkscape:pagecheckerboard="true">
+        <sodipodi:guide position="8.418941,13.042708" orientation="1,0" id="guide2254"/>
+        <sodipodi:guide position="8.914761,13.752632" orientation="0,-1" id="guide2256"/>
+    </sodipodi:namedview>
+    <g id="g1683" transform="translate(1.5,0.4999999)">
+        <rect fill="none" height="24" width="24" id="rect1679" x="0" y="0"/>
+    </g>
+</svg>
diff --git a/app/src/main/assets/ic_shoe_prints_many.svg b/app/src/main/assets/ic_shoe_prints_many.svg
new file mode 100644
index 0000000..3462b38
--- /dev/null
+++ b/app/src/main/assets/ic_shoe_prints_many.svg
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   version="1.1"
+   width="24"
+   height="24"
+   viewBox="0 0 24 24"
+   id="svg4"
+   sodipodi:docname="ic_shoe_prints_many.svg"
+   inkscape:version="1.0.1 (3bc2e813f5, 2020-09-07)">
+  <metadata
+     id="metadata10">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs8" />
+  <sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="1920"
+     inkscape:window-height="1042"
+     id="namedview6"
+     showgrid="true"
+     showguides="true"
+     inkscape:guide-bbox="true"
+     inkscape:zoom="16.0625"
+     inkscape:cx="20.274468"
+     inkscape:cy="17.69706"
+     inkscape:window-x="0"
+     inkscape:window-y="38"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="svg4"
+     inkscape:document-rotation="0">
+    <inkscape:grid
+       type="xygrid"
+       id="grid880" />
+    <sodipodi:guide
+       position="16,24"
+       orientation="0,-1"
+       id="guide882" />
+  </sodipodi:namedview>
+  <path
+     d="m 6.4801431,14.379265 c 0.3823082,1.031063 0.3416371,2.10404 -0.8052874,2.531554 C 3.3159331,17.807761 2.787209,15.452241 2.7546721,15.334884 l 3.725471,-0.955619 M 2.3886324,13.700271 5.9026138,12.80333 c -0.1545501,-0.880176 0.081342,-1.760353 0.081342,-2.799798 0,-1.4082825 -1.0818507,-4.1661681 -2.8063045,-3.7218887 -1.9684802,0.5029579 -2.2531778,2.7578856 -2.1799698,3.8476277 0.097611,1.089742 1.3340114,3.420113 1.3909509,3.571 m 9.8749386,7.494073 C 12.239171,21.320083 11.70231,23.66722 9.343387,22.778661 8.204597,22.342764 8.155792,21.269787 8.5381,20.238724 l 3.725471,0.95562 m 1.748856,-5.197232 c 0.08134,-1.089742 -0.195221,-3.353052 -2.171836,-3.85601 -1.716319,-0.461045 -2.79817,2.313606 -2.79817,3.730271 0,1.031064 0.227758,1.91124 0.08948,2.791416 l 3.497713,0.896942 c 0.06507,-0.150888 1.293341,-2.481259 1.382817,-3.562619 z"
+     id="path2"
+     style="stroke-width:0.999999" />
+  <path
+     d="M 14.730838,7.4902842 C 14.986006,8.2347895 14.958862,9.00956 14.193361,9.3182576 12.618928,9.9659169 12.266038,8.2650539 12.244321,8.1803133 L 14.730838,7.4902842 M 12.000011,7 14.345374,6.3523407 c -0.103162,-0.6355538 0.05428,-1.2711071 0.05428,-2.0216657 0,-1.0168858 -0.722068,-3.0082871 -1.873034,-2.6874837 -1.313836,0.3631733 -1.503856,1.9914011 -1.454993,2.778277 C 11.136774,5.2083444 11.961996,6.8910478 12,7 m 6.590901,5.411286 c -0.0162,0.09079 -0.374605,1.785603 -1.949038,1.143996 -0.760065,-0.314751 -0.79264,-1.089521 -0.537473,-1.834026 l 2.486519,0.69003 m 1.16726,-3.7527936 c 0.05428,-0.7868766 -0.130305,-2.4211567 -1.449564,-2.78433 -1.145536,-0.3329091 -1.867604,1.670598 -1.867604,2.6935367 0,0.7445054 0.152015,1.3800583 0.05971,2.0156119 l 2.334511,0.647659 c 0.04345,-0.108954 0.863221,-1.7916547 0.922943,-2.5724776 z"
+     id="path2-3"
+     style="stroke-width:0.999998" />
+</svg>
diff --git a/app/src/main/assets/ic_sony_wf_800n_left.svg b/app/src/main/assets/ic_sony_wf_800n_left.svg
new file mode 100644
index 0000000..d31ea35
--- /dev/null
+++ b/app/src/main/assets/ic_sony_wf_800n_left.svg
@@ -0,0 +1,95 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   width="12.321217mm"
+   height="12.3mm"
+   viewBox="0 0 12.321217 12.3"
+   version="1.1"
+   id="svg3988"
+   sodipodi:docname="ic_sony_wf_800n_right.svg"
+   inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg">
+  <sodipodi:namedview
+     id="namedview3990"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageshadow="2"
+     inkscape:pageopacity="0.0"
+     inkscape:pagecheckerboard="0"
+     inkscape:document-units="mm"
+     showgrid="false"
+     showguides="true"
+     inkscape:guide-bbox="true"
+     fit-margin-left="2"
+     fit-margin-right="2"
+     fit-margin-bottom="1"
+     fit-margin-top="1"
+     inkscape:zoom="10.248373"
+     inkscape:cx="5.7082231"
+     inkscape:cy="32.639327"
+     inkscape:window-width="1920"
+     inkscape:window-height="1035"
+     inkscape:window-x="0"
+     inkscape:window-y="45"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="layer1">
+    <sodipodi:guide
+       position="7.864604,2.6305704"
+       orientation="0,-1"
+       id="guide4472" />
+    <sodipodi:guide
+       position="3.8004999,2.3743318"
+       orientation="1,0"
+       id="guide14199" />
+  </sodipodi:namedview>
+  <defs
+     id="defs3985" />
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(-81.704318,-153.92826)">
+    <path
+       id="path13615-2-5"
+       style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:0.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       d="m 90.053204,155.41797 c 0.02931,0.0131 0.05714,0.0295 0.0848,0.0455 0.02766,0.0164 0.05496,0.0328 0.08289,0.0508 0.02799,0.0164 0.05645,0.0373 0.08642,0.0583 0.02997,0.0196 0.06139,0.0436 0.09518,0.0681 0.02207,0.0164 0.044,0.0328 0.0658,0.0495 0.02174,0.0164 0.04347,0.0344 0.06498,0.0521 0.04298,0.0357 0.08533,0.0727 0.12676,0.11136 0.04143,0.0386 0.08204,0.0789 0.121557,0.12021 0.03952,0.0413 0.07795,0.0839 0.115234,0.12741 0.03725,0.0436 0.07328,0.0881 0.10789,0.13364 0.03458,0.0455 0.06778,0.0917 0.09933,0.13855 0.03162,0.0468 0.06155,0.0943 0.08974,0.14215 0.01416,0.0229 0.02766,0.0478 0.04097,0.0721 0.01317,0.0229 0.02602,0.0482 0.0381,0.0724 0.03162,0.0622 0.06037,0.12152 0.08675,0.17851 0.02635,0.057 0.05032,0.11169 0.07203,0.16475 0.02174,0.0531 0.04113,0.10482 0.05859,0.15624 0.0086,0.0262 0.0168,0.0511 0.0247,0.0763 0.0079,0.0262 0.01515,0.0508 0.02174,0.076 0.0069,0.0262 0.01317,0.0508 0.0191,0.0763 0.0059,0.0262 0.01153,0.0514 0.0168,0.0773 0.01021,0.0521 0.0191,0.10514 0.02635,0.16017 0.0076,0.055 0.0135,0.11202 0.01844,0.17163 0.0049,0.0596 0.0089,0.12218 0.01186,0.18801 0.0022,0.0488 0.0036,0.0963 0.0046,0.14248 7.51e-4,0.0465 8.43e-4,0.0917 5.6e-5,0.13593 -7.54e-4,0.0442 -0.0024,0.0878 -0.0049,0.13069 -0.0025,0.0429 -0.0059,0.0852 -0.01021,0.12709 -0.0043,0.0419 -0.0096,0.0835 -0.01581,0.12479 -0.0063,0.0413 -0.0135,0.0825 -0.02206,0.12414 -0.0086,0.0413 -0.01811,0.0829 -0.02898,0.12479 -0.01087,0.0419 -0.02272,0.0839 -0.03586,0.12676 -0.01317,0.0429 -0.02766,0.0861 -0.04341,0.13036 -0.01581,0.0442 -0.03293,0.0891 -0.05141,0.13495 -0.01844,0.0459 -0.03847,0.093 -0.05987,0.1415 -0.02141,0.0485 -0.04426,0.0979 -0.0687,0.14936 -0.02437,0.0511 -0.05045,0.10383 -0.07809,0.15853 -0.02766,0.0544 -0.05684,0.1107 -0.0878,0.16901 -0.03096,0.0583 -0.06359,0.11857 -0.09801,0.18113 -0.03441,0.0626 -0.07061,0.12741 -0.10868,0.19488 -0.05572,0.0957 -0.111084,0.18932 -0.165984,0.28136 -0.0549,0.092 -0.109405,0.18211 -0.163481,0.27022 -0.05408,0.0884 -0.107725,0.17491 -0.160978,0.25942 -0.05325,0.0848 -0.106112,0.1677 -0.158542,0.24893 -0.05243,0.0812 -0.104464,0.16049 -0.156104,0.23812 -0.05161,0.0776 -0.10285,0.15362 -0.153634,0.22764 -0.05078,0.074 -0.101171,0.14674 -0.151131,0.21749 -0.04999,0.0708 -0.09956,0.13986 -0.148694,0.20733 -0.04914,0.0675 -0.09788,0.13331 -0.146191,0.19718 -0.04831,0.0642 -0.0963,0.12676 -0.14382,0.18736 -0.04752,0.0609 -0.09462,0.11988 -0.141317,0.17752 -0.0024,0.003 -0.0046,0.007 -0.0069,0.01 0.194966,-0.0262 0.968613,-0.71204 1.37172,-1.21583 0.923471,-1.15403 1.396852,-2.52066 1.255554,-3.62461 -0.129197,-1.00932 -0.579152,-1.83344 -1.415251,-2.59196 -0.207579,-0.18834 -0.2305,-0.16312 -0.290933,-0.14445 -0.207085,0.12512 -0.230764,0.11464 -0.09106,0.19161 z"
+       sodipodi:nodetypes="cssscssssssssssssssssssssssssssssssssssscsssssssssssccsscc" />
+    <path
+       style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:0.2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       d="m 86.308307,159.96828 c -0.280855,-0.0131 -0.311286,0.0341 -0.517248,-0.1569 -0.193417,-0.20831 -0.23903,-0.30494 -0.241072,-0.49305 -0.0022,-0.19521 0.0063,-0.21814 0.172406,-0.61528 0.355002,-0.84882 0.780135,-1.7003 0.978905,-1.96057 0.297882,-0.39006 0.724197,-0.67506 1.20233,-0.8217 0.35981,-0.11038 0.97706,-0.0943 1.363049,0.0357 1.222855,0.41159 1.877382,1.7234 1.473514,2.95321 -0.236659,0.72069 -0.614698,1.11099 -1.05809,1.11099 z"
+       id="path3272-6-9"
+       sodipodi:nodetypes="ccsssssscc" />
+    <path
+       id="rect20090-0-9"
+       style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:0.2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke;stop-color:#000000"
+       d="m 86.064192,159.50015 3.333695,0.0593 c 0.126299,0.002 0.22803,0.1035 0.22803,0.2319 0,0.12839 -0.101731,0.23419 -0.22803,0.2319 l -3.333695,-0.0593 c -0.126299,-0.002 -0.22803,-0.1035 -0.22803,-0.2319 0,-0.12839 0.101731,-0.23419 0.22803,-0.2319 z"
+       sodipodi:nodetypes="sssssss" />
+    <path
+       style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:0.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       d="m 85.781713,164.92823 c -0.544003,-0.11398 -1.161783,-0.52357 -1.464372,-0.97067 -0.461748,-0.6823 -0.575563,-1.39958 -0.359006,-2.26249 0.387889,-1.54563 1.513636,-4.33537 2.110287,-5.22959 0.426503,-0.63919 0.999207,-1.06208 1.729794,-1.27731 0.247791,-0.073 0.479065,-0.10252 0.845307,-0.10973 0.21242,-0.003 0.568742,0.0328 0.906948,0.15067 0.423124,0.14609 0.557855,0.19948 0.828076,0.39715 0.353286,0.25613 0.686884,0.63179 0.882416,1.01829 0.252204,0.49858 0.341154,0.81253 0.364829,1.34023 0.03501,0.78057 -0.102357,1.2234 -0.71112,2.30065 -0.873758,1.45714 -1.067488,1.7511 -2.066729,2.97611 -0.202375,0.25974 -0.548646,0.65597 -0.7695,0.88076 -0.324657,0.33039 -0.456002,0.43543 -0.685867,0.54856 -0.521335,0.25647 -1.10744,0.34297 -1.611063,0.23747 z"
+       id="path5762-6-0"
+       sodipodi:nodetypes="ssssssccssccsss" />
+    <g
+       aria-label="L"
+       id="text6720"
+       style="font-size:3.175px;font-family:Sans;-inkscape-font-specification:Sans;letter-spacing:0px;word-spacing:0px;stroke:#000000;stroke-width:0.264583px"
+       transform="translate(-1.0010411,0.02155759)">
+      <g
+         id="path14201">
+        <path
+           style="color:#000000;-inkscape-font-specification:Sans;fill:#000000;stroke:none;-inkscape-stroke:none"
+           d="m 86.637918,161.02382 h 0.313159 v 2.05104 h 1.127063 v 0.26355 h -1.440222 z"
+           id="path14206" />
+        <path
+           style="color:#000000;-inkscape-font-specification:Sans;fill:#000000;stroke:none;-inkscape-stroke:none"
+           d="m 86.505859,160.89062 v 0.13282 2.44726 h 1.705078 v -0.52734 h -1.126953 v -2.05274 z m 0.263672,0.26563 h 0.04883 v 2.05078 h -0.04883 z"
+           id="path14208" />
+      </g>
+    </g>
+  </g>
+</svg>
diff --git a/app/src/main/assets/ic_sony_wf_800n_right.svg b/app/src/main/assets/ic_sony_wf_800n_right.svg
new file mode 100644
index 0000000..c987dbe
--- /dev/null
+++ b/app/src/main/assets/ic_sony_wf_800n_right.svg
@@ -0,0 +1,95 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   width="12.321217mm"
+   height="12.3mm"
+   viewBox="0 0 12.321217 12.3"
+   version="1.1"
+   id="svg3988"
+   inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
+   sodipodi:docname="ic_sony_wf_800n_left.svg"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg">
+  <sodipodi:namedview
+     id="namedview3990"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageshadow="2"
+     inkscape:pageopacity="0.0"
+     inkscape:pagecheckerboard="0"
+     inkscape:document-units="mm"
+     showgrid="false"
+     showguides="true"
+     inkscape:guide-bbox="true"
+     fit-margin-left="2"
+     fit-margin-right="2"
+     fit-margin-bottom="1"
+     fit-margin-top="1"
+     inkscape:zoom="28.986776"
+     inkscape:cx="25.201147"
+     inkscape:cy="32.44583"
+     inkscape:window-width="1920"
+     inkscape:window-height="1035"
+     inkscape:window-x="0"
+     inkscape:window-y="45"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="layer1">
+    <sodipodi:guide
+       position="8.0623995,2.7429643"
+       orientation="0,-1"
+       id="guide4472" />
+    <sodipodi:guide
+       position="4.9027758,5.5559637"
+       orientation="1,0"
+       id="guide14199" />
+  </sodipodi:namedview>
+  <defs
+     id="defs3985" />
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(-81.704318,-153.92826)">
+    <g
+       aria-label="R"
+       id="text19879"
+       style="font-size:3.175px;font-family:Sans;-inkscape-font-specification:Sans;letter-spacing:0px;word-spacing:0px;stroke:#000000;stroke-width:0.264583px"
+       transform="translate(0.9003844,-0.00688411)">
+      <g
+         id="path20419">
+        <path
+           style="color:#000000;-inkscape-font-specification:Sans;fill:#000000;stroke:none;-inkscape-stroke:none"
+           d="m 88.752478,162.27504 q 0.100769,0.0341 0.195337,0.14573 0.09612,0.11162 0.192236,0.30696 l 0.31781,0.63251 h -0.336413 l -0.296106,-0.59376 q -0.114722,-0.23254 -0.223242,-0.30851 -0.106971,-0.076 -0.293006,-0.076 H 87.96803 v 0.97823 h -0.313159 v -2.31458 h 0.706933 q 0.396875,0 0.592212,0.16588 0.195337,0.16588 0.195337,0.50074 0,0.21859 -0.102319,0.36277 -0.100769,0.14418 -0.294556,0.19999 z m -0.784448,-0.97203 v 0.82165 h 0.393774 q 0.226343,0 0.341065,-0.10387 0.116272,-0.10542 0.116272,-0.30851 0,-0.20309 -0.116272,-0.3054 -0.114722,-0.10387 -0.341065,-0.10387 z"
+           id="path20424" />
+        <path
+           style="color:#000000;-inkscape-font-specification:Sans;fill:#000000;stroke:none;-inkscape-stroke:none"
+           d="m 87.523437,160.91406 v 2.57813 h 0.576172 v -0.97852 h 0.208985 c 0.106008,0 0.175,0.023 0.216797,0.0527 0.03834,0.0268 0.109953,0.1125 0.18164,0.25781 v 0.002 l 0.332031,0.66602 h 0.632813 l -0.414063,-0.82227 v -0.002 c -0.06683,-0.13558 -0.13491,-0.24575 -0.208984,-0.33203 -0.02289,-0.027 -0.05326,-0.036 -0.07813,-0.0586 0.06187,-0.0418 0.140739,-0.0643 0.183594,-0.125 0.08647,-0.12185 0.126953,-0.27308 0.126953,-0.43945 0,-0.24896 -0.07849,-0.46255 -0.242188,-0.60156 -0.163919,-0.1392 -0.39472,-0.19727 -0.677734,-0.19727 z m 0.263672,0.26367 h 0.04883 v 0.125 0.94727 0.008 0.97071 h -0.04883 z m 0.710938,0.0371 c 0.148175,0.0194 0.300592,0.0378 0.371094,0.0977 0.09675,0.0822 0.148437,0.20287 0.148437,0.40039 0,0.12509 -0.02817,0.21477 -0.07813,0.28516 V 162 c -0.03013,0.0431 -0.08781,0.0643 -0.136719,0.0918 0.0941,-0.098 0.148438,-0.22921 0.148438,-0.37891 0,-0.15997 -0.051,-0.3074 -0.160156,-0.4043 -0.07397,-0.067 -0.186918,-0.0752 -0.292969,-0.0937 z m -0.398438,0.22071 h 0.261719 c 0.130659,0 0.207561,0.0301 0.251953,0.0703 h 0.002 c 0.04492,0.0395 0.07227,0.0969 0.07227,0.20703 0,0.11015 -0.02802,0.1671 -0.07422,0.20898 -0.04439,0.0402 -0.121294,0.0703 -0.251953,0.0703 h -0.261719 z m 0.632813,0.70312 c -0.0069,0.002 -0.0083,0.008 -0.01563,0.01 l -0.0957,0.0273 v 0.1582 c -0.08301,-0.0446 -0.176683,-0.0726 -0.283203,-0.0762 h 0.02344 c 0.147723,0 0.267887,-0.0478 0.371094,-0.11914 z m -0.01172,0.26953 c 0.03608,0.0144 0.07838,0.0403 0.126953,0.0977 v 0.002 c 0.05341,0.062 0.113057,0.15579 0.173828,0.2793 l 0.222657,0.44141 h -0.04102 l -0.259766,-0.52149 c -0.06801,-0.13786 -0.140605,-0.22429 -0.222656,-0.29883 z"
+           id="path20426" />
+      </g>
+    </g>
+    <path
+       id="path13615-2-5"
+       style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:0.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       d="m 85.676646,155.41797 c -0.02931,0.0131 -0.05714,0.0295 -0.0848,0.0455 -0.02766,0.0164 -0.05496,0.0328 -0.08289,0.0508 -0.02799,0.0164 -0.05645,0.0373 -0.08642,0.0583 -0.02997,0.0196 -0.06139,0.0436 -0.09518,0.0681 -0.02207,0.0164 -0.044,0.0328 -0.0658,0.0495 -0.02174,0.0164 -0.04347,0.0344 -0.06498,0.0521 -0.04298,0.0357 -0.08533,0.0727 -0.12676,0.11136 -0.04143,0.0386 -0.08204,0.0789 -0.121557,0.12021 -0.03952,0.0413 -0.07795,0.0839 -0.115234,0.12741 -0.03725,0.0436 -0.07328,0.0881 -0.10789,0.13364 -0.03458,0.0455 -0.06778,0.0917 -0.09933,0.13855 -0.03162,0.0468 -0.06155,0.0943 -0.08974,0.14215 -0.01416,0.0229 -0.02766,0.0478 -0.04097,0.0721 -0.01317,0.0229 -0.02602,0.0482 -0.0381,0.0724 -0.03162,0.0622 -0.06037,0.12152 -0.08675,0.17851 -0.02635,0.057 -0.05032,0.11169 -0.07203,0.16475 -0.02174,0.0531 -0.04113,0.10482 -0.05859,0.15624 -0.0086,0.0262 -0.0168,0.0511 -0.0247,0.0763 -0.0079,0.0262 -0.01515,0.0508 -0.02174,0.076 -0.0069,0.0262 -0.01317,0.0508 -0.0191,0.0763 -0.0059,0.0262 -0.01153,0.0514 -0.0168,0.0773 -0.01021,0.0521 -0.0191,0.10514 -0.02635,0.16017 -0.0076,0.055 -0.0135,0.11202 -0.01844,0.17163 -0.0049,0.0596 -0.0089,0.12218 -0.01186,0.18801 -0.0022,0.0488 -0.0036,0.0963 -0.0046,0.14248 -7.51e-4,0.0465 -8.43e-4,0.0917 -5.6e-5,0.13593 7.54e-4,0.0442 0.0024,0.0878 0.0049,0.13069 0.0025,0.0429 0.0059,0.0852 0.01021,0.12709 0.0043,0.0419 0.0096,0.0835 0.01581,0.12479 0.0063,0.0413 0.0135,0.0825 0.02206,0.12414 0.0086,0.0413 0.01811,0.0829 0.02898,0.12479 0.01087,0.0419 0.02272,0.0839 0.03586,0.12676 0.01317,0.0429 0.02766,0.0861 0.04341,0.13036 0.01581,0.0442 0.03293,0.0891 0.05141,0.13495 0.01844,0.0459 0.03847,0.093 0.05987,0.1415 0.02141,0.0485 0.04426,0.0979 0.0687,0.14936 0.02437,0.0511 0.05045,0.10383 0.07809,0.15853 0.02766,0.0544 0.05684,0.1107 0.0878,0.16901 0.03096,0.0583 0.06359,0.11857 0.09801,0.18113 0.03441,0.0626 0.07061,0.12741 0.10868,0.19488 0.05572,0.0957 0.111084,0.18932 0.165984,0.28136 0.0549,0.092 0.109405,0.18211 0.163481,0.27022 0.05408,0.0884 0.107725,0.17491 0.160978,0.25942 0.05325,0.0848 0.106112,0.1677 0.158542,0.24893 0.05243,0.0812 0.104464,0.16049 0.156104,0.23812 0.05161,0.0776 0.10285,0.15362 0.153634,0.22764 0.05078,0.074 0.101171,0.14674 0.151131,0.21749 0.04999,0.0708 0.09956,0.13986 0.148694,0.20733 0.04914,0.0675 0.09788,0.13331 0.146191,0.19718 0.04831,0.0642 0.0963,0.12676 0.14382,0.18736 0.04752,0.0609 0.09462,0.11988 0.141317,0.17752 0.0024,0.003 0.0046,0.007 0.0069,0.01 -0.194966,-0.0262 -0.968613,-0.71204 -1.37172,-1.21583 -0.923471,-1.15403 -1.396852,-2.52066 -1.255554,-3.62461 0.129197,-1.00932 0.579152,-1.83344 1.415251,-2.59196 0.207579,-0.18834 0.2305,-0.16312 0.290933,-0.14445 0.207085,0.12512 0.230764,0.11464 0.09106,0.19161 z"
+       sodipodi:nodetypes="cssscssssssssssssssssssssssssssssssssssscsssssssssssccsscc" />
+    <path
+       style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:0.2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       d="m 89.421543,159.96828 c 0.280855,-0.0131 0.311286,0.0341 0.517248,-0.1569 0.193417,-0.20831 0.23903,-0.30494 0.241072,-0.49305 0.0022,-0.19521 -0.0063,-0.21814 -0.172406,-0.61528 -0.355002,-0.84882 -0.780135,-1.7003 -0.978905,-1.96057 -0.297882,-0.39006 -0.724197,-0.67506 -1.20233,-0.8217 -0.35981,-0.11038 -0.97706,-0.0943 -1.363049,0.0357 -1.222855,0.41159 -1.877382,1.7234 -1.473514,2.95321 0.236659,0.72069 0.614698,1.11099 1.05809,1.11099 z"
+       id="path3272-6-9"
+       sodipodi:nodetypes="ccsssssscc" />
+    <path
+       id="rect20090-0-9"
+       style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:0.2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke;stop-color:#000000"
+       d="m 89.665658,159.50015 -3.333695,0.0593 c -0.126299,0.002 -0.22803,0.1035 -0.22803,0.2319 0,0.12839 0.101731,0.23419 0.22803,0.2319 l 3.333695,-0.0593 c 0.126299,-0.002 0.22803,-0.1035 0.22803,-0.2319 0,-0.12839 -0.101731,-0.23419 -0.22803,-0.2319 z"
+       sodipodi:nodetypes="sssssss" />
+    <path
+       style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:0.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       d="m 89.948137,164.92823 c 0.544003,-0.11398 1.161783,-0.52357 1.464372,-0.97067 0.461748,-0.6823 0.575563,-1.39958 0.359006,-2.26249 -0.387889,-1.54563 -1.513636,-4.33537 -2.110287,-5.22959 -0.426503,-0.63919 -0.999207,-1.06208 -1.729794,-1.27731 -0.247791,-0.073 -0.479065,-0.10252 -0.845307,-0.10973 -0.21242,-0.003 -0.568742,0.0328 -0.906948,0.15067 -0.423124,0.14609 -0.557855,0.19948 -0.828076,0.39715 -0.353286,0.25613 -0.686884,0.63179 -0.882416,1.01829 -0.252204,0.49858 -0.341154,0.81253 -0.364829,1.34023 -0.03501,0.78057 0.102357,1.2234 0.71112,2.30065 0.873758,1.45714 1.067488,1.7511 2.066729,2.97611 0.202375,0.25974 0.548646,0.65597 0.7695,0.88076 0.324657,0.33039 0.456002,0.43543 0.685867,0.54856 0.521335,0.25647 1.10744,0.34297 1.611063,0.23747 z"
+       id="path5762-6-0"
+       sodipodi:nodetypes="ssssssccssccsss" />
+  </g>
+</svg>
diff --git a/app/src/main/assets/logback.xml b/app/src/main/assets/logback.xml
new file mode 100644
index 0000000..2169f3c
--- /dev/null
+++ b/app/src/main/assets/logback.xml
@@ -0,0 +1,19 @@
+<configuration debug="true">
+
+    <appender name="STDOUT" class="ch.qos.logback.classic.android.LogcatAppender">
+        <!-- encoders are  by default assigned the type
+             ch.qos.logback.classic.encoder.PatternLayoutEncoder -->
+        <tagEncoder>
+            <pattern>%logger{80}</pattern>
+        </tagEncoder>
+        <!-- include [%-20thread] to print thread -->
+        <encoder>
+            <pattern>%msg</pattern>
+        </encoder>
+    </appender>
+
+    <root level="DEBUG">
+        <appender-ref ref="STDOUT" />
+        <!-- The FILE appender is added programmatically by nodomain.freeyourgadget.gadgetbridge.Logging -->
+    </root>
+</configuration>
\ No newline at end of file
diff --git a/app/src/main/assets/migrations/devicetype.json b/app/src/main/assets/migrations/devicetype.json
new file mode 100644
index 0000000..e4eb7fc
--- /dev/null
+++ b/app/src/main/assets/migrations/devicetype.json
@@ -0,0 +1,122 @@
+{
+  "by-id": {
+    "-1": "UNKNOWN",
+    "1": "PEBBLE",
+    "10": "MIBAND",
+    "11": "MIBAND2",
+    "1001": "MIBAND2_HRX",
+    "12": "AMAZFITBIP",
+    "13": "AMAZFITCOR",
+    "14": "MIBAND3",
+    "15": "AMAZFITCOR2",
+    "16": "MIBAND4",
+    "17": "AMAZFITBIP_LITE",
+    "18": "AMAZFITGTR",
+    "19": "AMAZFITGTS",
+    "20": "AMAZFITBIPS",
+    "21": "AMAZFITGTR_LITE",
+    "22": "AMAZFITTREX",
+    "23": "MIBAND5",
+    "24": "AMAZFITBAND5",
+    "25": "AMAZFITBIPS_LITE",
+    "26": "AMAZFITGTR2",
+    "27": "AMAZFITGTS2",
+    "28": "AMAZFITBIPU",
+    "29": "AMAZFITVERGEL",
+    "30": "AMAZFITBIPUPRO",
+    "31": "AMAZFITNEO",
+    "32": "AMAZFITGTS2_MINI",
+    "33": "ZEPP_E",
+    "34": "AMAZFITGTR2E",
+    "35": "AMAZFITGTS2E",
+    "36": "AMAZFITX",
+    "37": "MIBAND6",
+    "38": "AMAZFITTREXPRO",
+    "39": "AMAZFITPOP",
+    "10040": "AMAZFITPOPPRO",
+    "10041": "MIBAND7",
+    "10042": "AMAZFITGTS3",
+    "10043": "AMAZFITGTR3",
+    "10044": "AMAZFITGTR4",
+    "10045": "AMAZFITBAND7",
+    "10046": "AMAZFITGTS4",
+    "10047": "AMAZFITGTS4MINI",
+    "10048": "AMAZFITTREX2",
+    "10049": "AMAZFITGTR3PRO",
+    "10051": "AMAZFITBIP3PRO",
+    "10050": "AMAZFITCHEETAHPRO",
+    "10052": "AMAZFITCHEETAHSQUARE",
+    "10053": "AMAZFITCHEETAHROUND",
+    "10054": "AMAZFITBIP5",
+    "10055": "AMAZFITTREXULTRA",
+    "10056": "AMAZFITGTRMINI",
+    "10057": "AMAZFITFALCON",
+    "10058": "AMAZFITBALANCE",
+    "40": "HPLUS",
+    "41": "MAKIBESF68",
+    "42": "EXRIZUK8",
+    "43": "Q8",
+    "44": "SG2",
+    "50": "NO1F1",
+    "60": "TECLASTH30",
+    "61": "Y5",
+    "70": "XWATCH",
+    "80": "ZETIME",
+    "90": "ID115",
+    "100": "WATCH9",
+    "102": "WATCHXPLUS",
+    "110": "ROIDMI",
+    "112": "ROIDMI3",
+    "120": "CASIOGB6900",
+    "121": "CASIOGBX100",
+    "122": "CASIOGWB5600",
+    "123": "CASIOGMWB5000",
+    "131": "MISCALE2",
+    "140": "BFH16",
+    "150": "MAKIBESHR3",
+    "160": "BANGLEJS",
+    "170": "FOSSILQHYBRID",
+    "180": "TLW64",
+    "190": "PINETIME_JF",
+    "200": "MIJIA_LYWSD02",
+    "210": "LEFUN",
+    "211": "BOHEMIC_SMART_BRACELET",
+    "220": "SMAQ2OSS",
+    "230": "FITPRO",
+    "250": "ITAG",
+    "251": "NUTMINI",
+    "260": "VIVOMOVE_HR",
+    "300": "VIBRATISSIMO",
+    "310": "SONY_SWR12",
+    "320": "LIVEVIEW",
+    "330": "WASPOS",
+    "350": "UM25",
+    "400": "DOMYOS_T540",
+    "410": "NOTHING_EAR1",
+    "418": "GALAXY_BUDS_PRO",
+    "419": "GALAXY_BUDS_LIVE",
+    "420": "GALAXY_BUDS",
+    "421": "GALAXY_BUDS2",
+    "422": "GALAXY_BUDS2_PRO",
+    "430": "SONY_WH_1000XM3",
+    "431": "SONY_WF_SP800N",
+    "432": "SONY_WH_1000XM4",
+    "433": "SONY_WF_1000XM3",
+    "434": "SONY_WH_1000XM2",
+    "435": "SONY_WF_1000XM4",
+    "436": "SONY_LINKBUDS_S",
+    "437": "SONY_WH_1000XM5",
+    "438": "SONY_WF_1000XM5",
+    "440": "BOSE_QC35",
+    "500": "VESC",
+    "510": "BINARY_SENSOR",
+    "520": "FLIPPER_ZERO",
+    "530": "SUPER_CARS",
+    "540": "ASTEROIDOS",
+    "550": "SOFLOW_SO6",
+    "560": "WITHINGS_STEEL_HR",
+    "570": "SONY_WENA_3",
+    "580": "FEMOMETER_VINCA2",
+    "1000": "TEST"
+  }
+}
\ No newline at end of file
diff --git a/app/src/main/assets/nothing_ear_l.svg b/app/src/main/assets/nothing_ear_l.svg
new file mode 100644
index 0000000..f80a3c8
--- /dev/null
+++ b/app/src/main/assets/nothing_ear_l.svg
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   width="45pt"
+   height="45pt"
+   viewBox="0 0 25 25"
+   version="1.1"
+   id="svg1584"
+   sodipodi:docname="tws_bud_l.svg"
+   inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg">
+  <sodipodi:namedview
+     id="namedview1586"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageshadow="2"
+     inkscape:pageopacity="0.0"
+     inkscape:pagecheckerboard="0"
+     inkscape:document-units="mm"
+     showgrid="false"
+     showguides="true"
+     inkscape:guide-bbox="true"
+     fit-margin-top="5"
+     fit-margin-left="5"
+     fit-margin-right="5"
+     fit-margin-bottom="5"
+     inkscape:zoom="7.2407734"
+     inkscape:cx="32.93847"
+     inkscape:cy="47.094417"
+     inkscape:window-width="1920"
+     inkscape:window-height="1043"
+     inkscape:window-x="0"
+     inkscape:window-y="0"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="layer1"
+     inkscape:snap-nodes="false"
+     units="pt">
+    <sodipodi:guide
+       position="60.230277,-77.001526"
+       orientation="0,-1"
+       id="guide12345" />
+    <sodipodi:guide
+       position="48.368846,-82.602765"
+       orientation="1,0"
+       id="guide12613" />
+    <sodipodi:guide
+       position="66.270257,-85.540793"
+       orientation="1,0"
+       id="guide12653" />
+    <sodipodi:guide
+       position="59.029537,-82.554635"
+       orientation="0,-1"
+       id="guide13329" />
+  </sodipodi:namedview>
+  <defs
+     id="defs1581" />
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(-54.112633,-52.186932)">
+    <path
+       style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       d="m 66.205353,71.516731 c -0.241039,3.18099 4.357921,3.724035 4.261474,-0.840271 l -0.05402,-11.075736 c -0.0096,-1.958838 -0.926881,-4.724551 -4.068115,-4.689489 -2.488808,0.02778 -5.036384,0.642953 -5.046106,1.875752 l -0.03294,4.176733 c -0.01523,1.930747 3.744189,3.676223 4.867724,2.994344 1.009905,-0.612914 0.05948,7.676196 0.05948,7.676196"
+       id="path1690"
+       sodipodi:nodetypes="ccsssssc" />
+  </g>
+</svg>
diff --git a/app/src/main/assets/nothing_ear_r.svg b/app/src/main/assets/nothing_ear_r.svg
new file mode 100644
index 0000000..35c9c16
--- /dev/null
+++ b/app/src/main/assets/nothing_ear_r.svg
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   width="45pt"
+   height="45pt"
+   viewBox="0 0 25 25"
+   version="1.1"
+   id="svg1584"
+   sodipodi:docname="tws_bud_r.svg"
+   inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg">
+  <sodipodi:namedview
+     id="namedview1586"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageshadow="2"
+     inkscape:pageopacity="0.0"
+     inkscape:pagecheckerboard="0"
+     inkscape:document-units="mm"
+     showgrid="false"
+     showguides="true"
+     inkscape:guide-bbox="true"
+     fit-margin-top="5"
+     fit-margin-left="5"
+     fit-margin-right="5"
+     fit-margin-bottom="5"
+     inkscape:zoom="7.2407734"
+     inkscape:cx="1.4501213"
+     inkscape:cy="47.16347"
+     inkscape:window-width="1890"
+     inkscape:window-height="1038"
+     inkscape:window-x="26"
+     inkscape:window-y="23"
+     inkscape:window-maximized="0"
+     inkscape:current-layer="layer1"
+     inkscape:snap-nodes="false"
+     units="pt">
+    <sodipodi:guide
+       position="60.230277,-77.001526"
+       orientation="0,-1"
+       id="guide12345" />
+    <sodipodi:guide
+       position="48.368846,-82.602765"
+       orientation="1,0"
+       id="guide12613" />
+    <sodipodi:guide
+       position="66.270257,-85.540793"
+       orientation="1,0"
+       id="guide12653" />
+    <sodipodi:guide
+       position="59.029537,-82.554635"
+       orientation="0,-1"
+       id="guide13329" />
+  </sodipodi:namedview>
+  <defs
+     id="defs1581" />
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(-54.112633,-52.186932)">
+    <path
+       style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       d="m 65.874055,71.516708 c 0.241037,3.181086 -4.357905,3.724148 -4.261458,-0.840294 l 0.05402,-11.076068 c 0.0096,-1.958896 0.926875,-4.724692 4.0681,-4.689628 2.488799,0.02778 5.036366,0.642972 5.046088,1.875807 l 0.03293,4.176859 c 0.01522,1.930805 -3.744175,3.676332 -4.867707,2.994433 -1.009902,-0.612932 -0.05948,7.676426 -0.05948,7.676426"
+       id="path1690"
+       sodipodi:nodetypes="ccsssssc" />
+  </g>
+</svg>
diff --git a/app/src/main/assets/olive_laurel.svg b/app/src/main/assets/olive_laurel.svg
new file mode 100644
index 0000000..1e66711
--- /dev/null
+++ b/app/src/main/assets/olive_laurel.svg
@@ -0,0 +1,340 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   version="1.0"
+   width="24"
+   height="23.999998"
+   id="svg36"
+   sodipodi:docname="olive_laurel.svg"
+   inkscape:version="1.2.1 (9c6d41e410, 2022-07-14)"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:dc="http://purl.org/dc/elements/1.1/">
+  <metadata
+     id="metadata42">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs40">
+    <linearGradient
+       gradientTransform="matrix(0.22116326,0,0,0.22116326,48.742492,-29.402522)"
+       id="SVGID_82_"
+       gradientUnits="userSpaceOnUse"
+       x1="1386.7239"
+       y1="1403.7546"
+       x2="1901.9294"
+       y2="1403.7546">
+      <stop
+         offset="0"
+         style="stop-color:#FFE964"
+         id="stop2661" />
+      <stop
+         offset="0.0123"
+         style="stop-color:#F6DA5C"
+         id="stop2663" />
+      <stop
+         offset="0.0525"
+         style="stop-color:#DCAE43"
+         id="stop2665" />
+      <stop
+         offset="0.0886"
+         style="stop-color:#CA8E31"
+         id="stop2667" />
+      <stop
+         offset="0.1188"
+         style="stop-color:#BE7A26"
+         id="stop2669" />
+      <stop
+         offset="0.1394"
+         style="stop-color:#BA7322"
+         id="stop2671" />
+      <stop
+         offset="0.1711"
+         style="stop-color:#AA6118"
+         id="stop2673" />
+      <stop
+         offset="0.2182"
+         style="stop-color:#8B3E04"
+         id="stop2675" />
+      <stop
+         offset="0.2412"
+         style="stop-color:#A05B17"
+         id="stop2677" />
+      <stop
+         offset="0.2998"
+         style="stop-color:#D3A045"
+         id="stop2679" />
+      <stop
+         offset="0.3421"
+         style="stop-color:#F3CB62"
+         id="stop2681" />
+      <stop
+         offset="0.3636"
+         style="stop-color:#FFDC6D"
+         id="stop2683" />
+      <stop
+         offset="0.4545"
+         style="stop-color:#FFFFB5"
+         id="stop2685" />
+      <stop
+         offset="0.5576"
+         style="stop-color:#FCFDF6"
+         id="stop2687" />
+      <stop
+         offset="0.6545"
+         style="stop-color:#FFFF9C"
+         id="stop2689" />
+      <stop
+         offset="0.7758"
+         style="stop-color:#FDDC53"
+         id="stop2691" />
+      <stop
+         offset="0.7981"
+         style="stop-color:#F5CF4A"
+         id="stop2693" />
+      <stop
+         offset="0.8393"
+         style="stop-color:#E1AD31"
+         id="stop2695" />
+      <stop
+         offset="0.8944"
+         style="stop-color:#C0770A"
+         id="stop2697" />
+      <stop
+         offset="0.903"
+         style="stop-color:#BB6E03"
+         id="stop2699" />
+      <stop
+         offset="1"
+         style="stop-color:#5B1F03"
+         id="stop2701" />
+    </linearGradient>
+    <linearGradient
+       gradientTransform="matrix(0.22116326,0,0,0.22116326,48.742492,-29.402522)"
+       id="SVGID_81_"
+       gradientUnits="userSpaceOnUse"
+       x1="1386.7239"
+       y1="1403.7546"
+       x2="1901.9294"
+       y2="1403.7546">
+      <stop
+         offset="0"
+         style="stop-color:#2E1300"
+         id="stop2642" />
+      <stop
+         offset="0.1265"
+         style="stop-color:#734200"
+         id="stop2644" />
+      <stop
+         offset="0.3198"
+         style="stop-color:#D0C459"
+         id="stop2646" />
+      <stop
+         offset="0.4146"
+         style="stop-color:#FFFF8A"
+         id="stop2648" />
+      <stop
+         offset="0.6111"
+         style="stop-color:#FFEB5C"
+         id="stop2650" />
+      <stop
+         offset="0.8827"
+         style="stop-color:#A67C21"
+         id="stop2652" />
+      <stop
+         offset="1"
+         style="stop-color:#5C2C00"
+         id="stop2654" />
+      <stop
+         offset="1"
+         style="stop-color:#725316"
+         id="stop2656" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient4195">
+      <stop
+         offset="0"
+         stop-color="#f3ed6d"
+         id="stop4197" />
+      <stop
+         offset=".071429"
+         stop-color="#ede77c"
+         id="stop4199" />
+      <stop
+         offset=".14286"
+         stop-color="#f5ef72"
+         id="stop4201" />
+      <stop
+         offset=".21429"
+         stop-color="#f4e65c"
+         id="stop4203" />
+      <stop
+         offset=".28571"
+         stop-color="#eed756"
+         id="stop4205" />
+      <stop
+         offset=".35714"
+         stop-color="#e9c952"
+         id="stop4207" />
+      <stop
+         offset=".42857"
+         stop-color="#eac74d"
+         id="stop4209" />
+      <stop
+         offset=".5"
+         stop-color="#eac848"
+         id="stop4211" />
+      <stop
+         offset=".57143"
+         stop-color="#e6c842"
+         id="stop4213" />
+      <stop
+         offset=".64286"
+         stop-color="#e1c540"
+         id="stop4215" />
+      <stop
+         offset=".71429"
+         stop-color="#dbbd3c"
+         id="stop4217" />
+      <stop
+         offset=".78571"
+         stop-color="#d1ae39"
+         id="stop4219" />
+      <stop
+         offset=".85714"
+         stop-color="#c7a534"
+         id="stop4221" />
+      <stop
+         offset=".92857"
+         stop-color="#c4a92f"
+         id="stop4223" />
+      <stop
+         offset="1"
+         stop-color="#c1ac2d"
+         id="stop4225" />
+    </linearGradient>
+    <filter
+       style="color-interpolation-filters:sRGB"
+       inkscape:label="Emboss Shader"
+       inkscape:menu-tooltip="Combination of satiny and emboss effect"
+       inkscape:menu="Non realistic 3D shaders"
+       height="1.5"
+       y="-0.25"
+       width="1.5"
+       x="-0.25"
+       id="filter8631-6">
+      <feGaussianBlur
+         result="result8"
+         stdDeviation="5"
+         in="SourceGraphic"
+         id="feGaussianBlur8633-0" />
+      <feComposite
+         result="result19"
+         operator="xor"
+         in2="result8"
+         id="feComposite8635-6" />
+      <feComposite
+         in2="result8"
+         result="result17"
+         operator="arithmetic"
+         k2="1"
+         id="feComposite8637-2"
+         k1="0"
+         k3="0"
+         k4="0" />
+      <feOffset
+         in="result8"
+         result="result18"
+         id="feOffset8639-6" />
+      <feDisplacementMap
+         result="result4"
+         scale="100"
+         yChannelSelector="A"
+         xChannelSelector="A"
+         in="result18"
+         in2="result17"
+         id="feDisplacementMap8641-1" />
+      <feComposite
+         k3="0.5"
+         k1="0.5"
+         in="result4"
+         operator="arithmetic"
+         result="result2"
+         in2="result4"
+         id="feComposite8643-8"
+         k2="0"
+         k4="0" />
+      <feComposite
+         operator="over"
+         in="result2"
+         result="fbSourceGraphic"
+         in2="result2"
+         id="feComposite8645-7" />
+      <feComposite
+         result="result15"
+         operator="in"
+         in2="fbSourceGraphic"
+         id="feComposite8647-9" />
+      <feComposite
+         in="result15"
+         operator="in"
+         in2="result8"
+         result="result16"
+         id="feComposite8649-2" />
+    </filter>
+  </defs>
+  <sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="1920"
+     inkscape:window-height="1035"
+     id="namedview38"
+     showgrid="false"
+     inkscape:zoom="32.802228"
+     inkscape:cx="9.389606"
+     inkscape:cy="13.246051"
+     inkscape:window-x="0"
+     inkscape:window-y="45"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="svg36"
+     inkscape:snap-global="false"
+     showguides="true"
+     inkscape:showpageshadow="2"
+     inkscape:pagecheckerboard="0"
+     inkscape:deskcolor="#d1d1d1" />
+  <g
+     id="g563"
+     transform="translate(0,-0.7181305)">
+    <path
+       inkscape:connector-curvature="0"
+       style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.939045;stroke-opacity:1"
+       d="M 5.748407,0.7181305 C 4.9089718,1.1229642 3.9698914,1.5076507 3.463455,2.6058643 3.1075486,3.199615 3.0983522,3.9726613 2.8474248,4.6193276 3.3122366,4.0771565 4.031798,3.5219633 4.4418292,2.8911986 4.8992452,2.099059 5.131822,1.3893232 5.748407,0.7181305 Z m 12.503186,0 c 0.616585,0.6711927 0.84931,1.3809285 1.306726,2.1730681 0.410032,0.6307647 1.129445,1.1859579 1.594257,1.728129 C 20.901648,3.9726615 20.892452,3.199615 20.536546,2.6058643 20.030108,1.5076507 19.091028,1.1229642 18.251593,0.7181305 Z M 3.4843852,2.1327501 C 3.056552,2.2679277 2.6763044,2.5894184 2.3764212,3.0525949 1.6854215,3.9526906 1.5669308,5.1335587 1.8533152,6.5572228 1.9518452,6.9337625 1.8117472,7.286476 1.8316432,7.6379752 2.0345981,7.1212906 2.2644769,6.6463614 2.7114527,6.313744 3.7081487,5.613623 3.9822567,5.2428965 4.2372785,4.5828465 4.5371457,3.8067255 4.5928641,3.6914478 4.5994741,2.9949417 4.3103253,3.9967668 3.4374385,4.417201 2.8474253,5.0935819 2.5442051,5.4627796 2.3013381,5.8905643 2.1631121,6.3435478 L 2.1418851,6.322702 C 2.2145314,5.9218945 2.3604376,5.5617487 2.4444072,5.1707789 2.70777,4.1135646 2.6536544,2.8409742 3.4843852,2.1327505 Z m 17.0031748,0 c 0.83073,0.7082236 0.776614,1.980814 1.039978,3.0380283 0.08397,0.3909698 0.229876,0.7511156 0.302522,1.1519232 l -0.02123,0.020846 C 21.670604,5.890564 21.427738,5.4627794 21.124518,5.0935816 20.534504,4.417201 19.661618,3.9967666 19.372469,2.9949414 c 0.0066,0.6965061 0.06248,0.8117839 0.362344,1.5879049 0.255022,0.66005 0.528981,1.0307765 1.525677,1.7308974 0.446976,0.3326175 0.676856,0.8075467 0.87981,1.3242312 C 22.1602,7.2864755 22.020098,6.933762 22.11863,6.5572224 22.405014,5.1335583 22.286524,3.9526901 21.595524,3.0525945 21.29564,2.5894184 20.915392,2.2679277 20.48756,2.1327501 Z M 1.7114055,4.0266727 c -0.021664,-0.0051 -0.033502,0.015795 -0.050321,0.026221 C 1.0818043,4.9698043 0.57606596,5.6634628 0.60106728,6.8958129 0.52305348,8.4123765 1.4001886,9.7947276 1.4881507,11.225012 1.6212002,10.130098 2.309539,9.3566618 2.8790426,8.5079841 2.8039716,8.870642 2.7168274,9.2219603 2.5662776,9.5374355 1.9423623,10.710552 1.321361,12.10312 1.751485,13.6041 1.741535,13.619958 1.727506,13.640861 1.711406,13.629994 1.5385569,13.134028 1.5089834,12.566606 1.3881011,12.038181 1.0992135,10.625456 0.2185583,9.4497466 0.47400138,7.821195 0.33917854,8.1745554 0.28459434,8.5733663 0.21319058,8.9586231 0.06246353,9.9542772 -0.01780287,10.568438 0.20354158,11.482985 0.47253244,12.630121 1.6056459,13.895601 2.0630618,14.946083 l 0.062494,0.06221 C 2.05893,14.438377 2.0249322,13.82482 2.1546504,13.281307 2.35178,12.411484 2.8935868,11.561478 3.0452962,10.664929 3.1243722,9.9618435 2.9480686,9.1823532 2.8814174,8.5044012 3.0869908,8.1977979 3.277417,7.8815499 3.4198132,7.5363492 3.6077794,7.0154376 3.5289738,6.2742488 3.5853246,5.7106659 3.5096016,5.9506405 3.3792746,6.1581256 3.2318866,6.3624399 2.5416724,7.1433229 1.7514654,7.9448917 1.6012623,9.0766988 1.5760643,9.2375517 1.5680053,9.4047036 1.5303073,9.5610503 1.5007253,9.5771916 1.5006513,9.5351016 1.4881503,9.5248953 L 1.5723163,8.1215126 C 1.6013749,6.750725 1.0737461,5.2309077 1.7114055,4.0266727 Z m 20.5771885,0 c 0.637658,1.204235 0.11018,2.7240523 0.139238,4.0948399 l 0.08402,1.4033827 c -0.0125,0.010206 -0.01242,0.052296 -0.04201,0.036155 -0.0377,-0.1563467 -0.0459,-0.3234986 -0.0711,-0.4843515 C 22.24854,7.9448917 21.458332,7.1433229 20.768118,6.3624399 20.62073,6.1581256 20.490402,5.9506405 20.41468,5.7106659 c 0.05635,0.5635829 -0.02231,1.3047717 0.16566,1.8256833 0.142396,0.3452007 0.332674,0.6614487 0.538246,0.968052 -0.06665,0.6779511 -0.242806,1.4574423 -0.16373,2.1605278 0.15171,0.896549 0.693516,1.746555 0.890646,2.616378 0.129718,0.543513 0.09557,1.15707 0.02895,1.726989 l 0.06249,-0.06221 C 22.394358,13.895601 23.52762,12.630121 23.79661,11.482985 24.017956,10.568438 23.937542,9.9542772 23.78681,8.9586231 23.71541,8.5733663 23.660822,8.1745554 23.526,7.821195 c 0.255442,1.6285516 -0.625214,2.80426 -0.914102,4.216986 -0.120882,0.528425 -0.150454,1.095847 -0.323304,1.591813 -0.0161,0.01087 -0.03013,-0.01004 -0.04008,-0.02589 0.430124,-1.50098 -0.19073,-2.893548 -0.814644,-4.0666654 -0.15055,-0.3154743 -0.237844,-0.6667926 -0.312914,-1.0294505 0.569504,0.8486768 1.257844,1.6221129 1.390892,2.7170269 0.08796,-1.4302842 0.965098,-2.8126345 0.887084,-4.3291981 0.025,-1.2323501 -0.48059,-1.9260086 -1.05987,-2.8429194 -0.01682,-0.010427 -0.02881,-0.031316 -0.05047,-0.026221 z M 2.7953218,11.731838 c 0.105568,1.163826 -0.300776,2.150816 -0.2722408,3.318802 0.028535,0.705537 0.213591,1.330824 0.5113788,1.893597 l -0.05047,1.67e-4 C 2.5816814,16.287889 2.2675308,15.589342 1.7973529,14.964164 1.0675428,14.09013 0.13636965,13.193971 0.04055366,11.979067 -0.08337197,12.9936 0.08334145,13.944717 0.45025082,14.785505 0.8171601,15.626293 1.3842521,16.356687 2.0609842,16.92942 l 0.3152886,0.242177 c 0.5578784,0.453086 0.9973108,0.630876 1.5333956,1.146222 v -0.06335 C 3.4991136,17.406403 3.5518444,16.39898 3.4863148,15.370179 3.4207854,14.341381 3.2369462,13.291142 2.9839902,12.357395 Z m 18.4093562,0 -0.188668,0.625553 c -0.252956,0.933747 -0.436646,1.983986 -0.502176,3.012784 -0.06553,1.0288 -0.0128,2.036224 -0.423354,2.884287 v 0.06335 c 0.536084,-0.515346 0.97537,-0.693135 1.533248,-1.146222 l 0.315288,-0.242176 c 0.676732,-0.572732 1.243824,-1.303126 1.610734,-2.143915 0.366908,-0.840788 0.533622,-1.791905 0.409696,-2.806438 -0.09582,1.214903 -1.026988,2.111063 -1.756798,2.985097 -0.470178,0.625178 -0.78433,1.323726 -1.186638,1.980239 l -0.05047,-1.67e-4 c 0.297788,-0.562772 0.482992,-1.18806 0.511528,-1.893597 0.02854,-1.167985 -0.377958,-2.154976 -0.27239,-3.318802 z m -17.7042618,3.10757 c 0.512784,1.274538 0.2720172,2.932627 0.9564058,4.118128 0.1879668,0.391851 0.4985276,0.736447 0.7669948,1.03906 l -0.01247,0.0158 C 4.9684044,19.871069 4.7429688,19.6689 4.5277766,19.431141 3.3584174,18.235053 1.614355,18.068853 0.9179884,16.107289 1.0124627,16.827018 1.1272775,17.549345 1.5024006,18.153976 2.762261,20.176912 4.1023968,20.212088 6.2177774,20.75195 5.2496058,19.779057 5.0897276,18.328092 4.6713188,17.04293 4.3900232,16.255611 3.9663398,15.533892 3.5004162,14.839408 Z m 16.9991678,0 c -0.465924,0.694483 -0.889608,1.416203 -1.170903,2.203522 -0.418409,1.285162 -0.578286,2.736127 -1.546458,3.70902 2.188044,-0.50869 3.428255,-0.518889 4.715377,-2.597974 0.375122,-0.604631 0.489936,-1.326958 0.584412,-2.046687 -0.696368,1.961564 -2.44028,2.127764 -3.60964,3.323852 -0.215192,0.237759 -0.440777,0.439928 -0.68372,0.581254 l -0.01247,-0.0158 c 0.268467,-0.302614 0.579028,-0.647211 0.766995,-1.03906 0.684387,-1.185501 0.443621,-2.843591 0.956405,-4.11813 z M 5.1743854,17.94714 c 0.6328812,1.060311 0.803988,2.630933 1.969812,3.143075 0.1935296,0.127291 0.4394738,0.112801 0.6167734,0.252761 -0.2226552,0.04202 -0.4148502,-0.06322 -0.6249374,-0.09935 -1.487305,-0.416751 -3.3117492,0.106548 -4.4282916,-1.475855 0.4237754,1.033348 1.292776,1.863746 2.1350264,2.195543 1.2955418,0.549719 2.6526596,-0.0582 3.8743112,-0.429303 0.1754008,-0.05678 0.3896682,-0.03586 0.553091,-0.109769 C 8.5901664,21.250778 7.9448694,20.979665 7.4087194,20.394144 6.6628082,19.519452 5.9829288,18.616966 5.1743854,17.94714 Z m 13.6512286,0 c -0.808543,0.669826 -1.488274,1.572313 -2.234185,2.447002 -0.536149,0.585521 -1.181594,0.856636 -1.861598,1.030104 0.163422,0.07391 0.37769,0.05299 0.55309,0.109769 1.221652,0.3711 2.578918,0.979023 3.874459,0.429303 0.842251,-0.331797 1.711252,-1.162195 2.135026,-2.195543 -1.116542,1.582403 -2.940986,1.059104 -4.428291,1.475855 -0.210087,0.03613 -0.40243,0.141368 -0.625085,0.09935 0.177299,-0.13996 0.423392,-0.12547 0.616921,-0.252761 1.165824,-0.512142 1.336782,-2.082764 1.969663,-3.143075 z m -5.519482,3.432806 c -0.358595,-1.67e-4 -0.716568,0.05158 -1.060314,0.161885 -0.218203,0.243166 -0.442659,-0.04348 -0.673625,-0.04902 -1.479974,-0.404849 -2.755593,0.432148 -3.9832654,1.091989 -0.9148978,0.445874 -2.113123,0.370966 -2.9176088,-0.320349 0.3980548,0.621864 0.930639,1.005124 1.4804024,1.265762 1.0222318,0.509223 2.2387558,0.166717 3.0846048,-0.566269 0.7258832,-0.505406 1.350068,-1.500744 2.306328,-1.121794 -0.973146,0.560728 -1.8626668,1.346833 -2.6668912,2.357264 l 0.5038084,0.518715 c 0.5864138,-0.975892 1.3664358,-1.772359 2.1552148,-2.391141 0.171081,-0.113956 0.351482,-0.255617 0.541216,-0.285822 0.968892,0.591024 1.834296,1.601344 2.615679,2.650579 0.166696,-0.167058 0.3437,-0.348334 0.480355,-0.542166 -0.810835,-0.976338 -1.697654,-1.740377 -2.652344,-2.295216 0.158646,-0.05665 0.338675,-0.06767 0.525333,-0.06286 0.514617,-0.03894 0.865227,0.414918 1.263085,0.730437 0.9827,0.929319 2.295802,1.543139 3.553974,0.9617 0.564031,-0.250065 1.110269,-0.692547 1.51009,-1.310878 -0.777652,0.686071 -1.950695,0.799028 -2.857935,0.379633 L 15.53824,22.063479 c -0.651084,-0.431489 -1.443196,-0.68312 -2.232108,-0.683533 z"
+       id="path4708" />
+    <path
+       d="M 17.833333,6.8847962 H 16.166667 V 5.2181295 H 7.8333331 V 6.8847962 H 6.1666665 c -0.9166667,0 -1.6666667,0.7500002 -1.6666667,1.6666669 v 0.8333333 c 0,2.1250006 1.6,3.8583336 3.6583333,4.1166666 0.525,1.25 1.6499999,2.191667 3.0083339,2.466667 v 2.583333 H 7.8333331 V 20.21813 H 16.166667 V 18.551463 H 12.833333 V 15.96813 c 1.358334,-0.275 2.483334,-1.216667 3.008334,-2.466667 C 17.9,13.24313 19.5,11.509797 19.5,9.3847963 V 8.551463 C 19.5,7.6347963 18.75,6.8847961 17.833333,6.8847962 Z M 6.1666665,9.3847964 V 8.5514631 H 7.8333331 V 11.734797 C 6.8666665,11.384797 6.1666665,10.46813 6.1666665,9.3847964 Z M 12,14.384797 c -1.375,0 -2.5000003,-1.125 -2.5000003,-2.5 V 6.8847962 H 14.5 v 5.0000008 c 0,1.375 -1.125,2.5 -2.5,2.5 z m 5.833333,-5.0000006 c 0,1.0833336 -0.7,2.0000006 -1.666666,2.3500006 V 8.5514631 h 1.666666 z"
+       id="path231"
+       style="stroke-width:1" />
+  </g>
+  <path
+     fill="#fcab29"
+     d="m 11.747167,8.1749904 c -0.05033,-4.399e-4 -0.09964,-0.015657 -0.149198,-0.023479 -0.09537,-0.015123 -0.190615,-0.031544 -0.286203,-0.045109 -0.03452,-0.00489 -0.06898,-0.010235 -0.103511,-0.015123 -0.0108,-0.00149 -0.0304,-8.463e-4 -0.03557,-0.012831 -0.0082,-0.01882 0.0099,-0.048943 0.01823,-0.064631 0.02792,-0.05186 0.05914,-0.1019641 0.09025,-0.151854 0.08096,-0.1298573 0.172403,-0.2638099 0.289412,-0.3628503 0.07704,-0.065211 0.166228,-0.10395 0.264889,-0.063424 0.03981,0.016345 0.07573,0.043505 0.103989,0.076072 0.02267,0.026136 0.04376,0.056336 0.05425,0.089881 0.01388,0.044299 0.03358,0.1222042 0.01512,0.1673526 -0.01675,0.04103 -0.06211,0.071917 -0.09031,0.1053706 -0.04805,0.056978 -0.08658,0.1205239 -0.122236,0.185949 -0.0167,0.030658 -0.04724,0.074911 -0.04555,0.1115113 0.0011,0.023097 0.06944,0.040496 0.08775,0.050608 0.06446,0.035561 0.123903,0.087911 0.154334,0.1570981 0.05601,0.1273979 -0.002,0.3115031 -0.105624,0.3983506 -0.03153,0.026427 -0.07069,0.040847 -0.109604,0.051815 -0.108663,0.030643 -0.225313,9.043e-4 -0.334919,-0.00382 -0.0042,0.05487 -0.01404,0.1134208 0.0023,0.1673541 0.02982,0.098466 0.149707,0.1716557 0.250354,0.1469048 0.100226,-0.024639 0.164482,-0.1152233 0.234714,-0.1837616 0.01311,-0.012831 0.03342,-0.012831 0.04587,-0.024899 0.01542,-0.01497 0.02058,-0.049157 0.03053,-0.068419 0.01527,-0.029604 0.03944,-0.056443 0.05858,-0.083679 0.116514,-0.1659686 0.218241,-0.3426668 0.315794,-0.5206572 0.02687,-0.049035 0.05332,-0.098543 0.07798,-0.1487531 0.0085,-0.017414 0.01357,-0.050226 0.02667,-0.063485 0.01265,-0.012831 0.04007,-0.012068 0.05628,-0.019537 0.112982,-0.052242 0.230787,-0.031177 0.319695,0.058246 0.01951,0.019644 0.04054,0.039136 0.05206,0.065074 0.04954,0.111603 -0.0026,0.2458062 -0.04166,0.3502037 -0.06851,0.1833766 -0.175104,0.3537813 -0.28436,0.51446 -0.01823,0.026808 -0.02076,0.064676 -0.03918,0.089026 -0.01219,0.0161 -0.0369,0.01859 -0.0509,0.034996 -0.03281,0.038372 -0.06288,0.08623 -0.104853,0.1150858 -0.08218,0.056489 -0.222953,0.05542 -0.307516,0.00794 -0.08187,-0.046056 -0.09817,-0.1420166 -0.10237,-0.2283707 -0.0015,-0.031238 0.01095,-0.070344 -0.0042,-0.099169 m 0.03654,-1.1745797 c 0.05426,-0.011457 0.09972,-0.042512 0.155281,-0.051326 0.116885,-0.018544 0.237609,0.051906 0.281778,0.1628922 0.02238,0.056229 0.01566,0.114765 0.03182,0.1704535 m 0.06393,1.0661081 c 0.02527,0.03576 0.01172,0.1027126 0.02358,0.1456674 0.0349,0.1267258 0.146452,0.179324 0.268707,0.1528852 0.17224,-0.037242 0.296585,-0.1979021 0.388718,-0.3388311 0.02128,-0.032567 0.03788,-0.075583 0.06382,-0.1041638 0.0088,-0.00962 0.03108,-0.023494 0.04279,-0.012526 0.01809,0.016986 0.01018,0.080609 0.01018,0.1042555 0,0.1022238 8.8e-4,0.2051397 -0.0086,0.3068166 -0.01823,0.1949982 -0.02204,0.4176421 -0.128545,0.5888399 -0.03611,0.058047 -0.08985,0.1068831 -0.139897,0.1523731 -0.09874,0.08974 -0.203813,0.173106 -0.313605,0.24844 -0.04825,0.0331 -0.104453,0.06199 -0.148859,0.100039 -0.01768,0.01512 -0.01249,0.04208 -0.01249,0.0632 v 0.167349 c 0,0.01639 0.0079,0.06413 -0.0071,0.07428 -0.01496,0.01023 -0.05144,0.0032 -0.06905,0.0032 h -1.041256 c -0.01889,0 -0.05907,0.0076 -0.07515,-0.0032 -0.01644,-0.01115 -0.0071,-0.07137 -0.0071,-0.08979 0,-0.06721 0.01607,-0.174946 -0.02681,-0.229337 C 11.14962,10.16143 10.934289,9.8795567 10.759401,9.5728269 10.685771,9.4436876 10.59599,9.2999189 10.581572,9.1482421 10.553272,8.8506183 10.709484,8.5545098 10.861006,8.31147 c 0.03703,-0.059391 0.07666,-0.117866 0.118936,-0.1735514 0.0145,-0.019079 0.0302,-0.050165 0.05176,-0.061423 0.03455,-0.018025 0.09629,0.00565 0.133928,0.00565 m 1.753754,0.3068151 c 0.134422,-0.028229 0.263489,-0.095365 0.389724,0.00117 0.02144,0.016406 0.03927,0.035622 0.05505,0.057726 0.01916,0.02687 0.03173,0.055022 0.04061,0.08678 0.0097,0.034782 0.019,0.071932 0.01672,0.1084715 -0.0051,0.081098 -0.04658,0.143147 -0.07441,0.21694 -0.0099,0.026182 -0.01141,0.05626 -0.01666,0.083679 M 11.059074,8.7112989 c 0.08249,0.037211 0.174513,0.060293 0.261845,0.082518 0.03768,0.00962 0.0883,0.012526 0.121789,0.03214 m -0.04871,1.9276741 c -0.03668,0.01992 -0.107784,0.0063 -0.149198,0.0063 -0.01203,0 -0.0434,-0.0057 -0.05167,0.005 -0.0054,0.007 -0.0031,0.02079 -0.0031,0.02913 v 0.461773 c 0,0.0089 -0.0026,0.0243 0.0071,0.02913 0.02807,0.01405 0.08946,0.0018 0.120818,0.0018 h 1.394479 c 0.03432,0 0.08238,0.009 0.115249,-10e-4 0.01234,-0.0037 0.0096,-0.0198 0.0096,-0.03 v -0.461772 c 0,-0.0084 0.0023,-0.02207 -0.0031,-0.02913 -0.0093,-0.01192 -0.05007,-0.0049 -0.06386,-0.0049 -0.05383,0 -0.125044,0.01237 -0.176594,-0.0031 m -0.986487,0.125275 c 0.06109,-0.0081 0.127141,-0.0013 0.188773,-0.0013 h 0.563213 c 0.03743,0 0.07925,-0.004 0.115696,0.0064 0.02516,0.007 0.03361,0.02876 0.03771,0.0525 0.0086,0.05 0.0071,0.116965 9.98e-4,0.167354 -0.0029,0.02365 -0.0079,0.04937 -0.03262,0.0588 -0.02448,0.0093 -0.05358,0.0063 -0.07916,0.0063 H 11.6376 c -0.02641,0 -0.05745,0.0035 -0.08216,-0.0082 -0.03429,-0.01627 -0.02744,-0.07215 -0.02744,-0.103354 0,-0.04329 -0.01686,-0.134975 0.02162,-0.164875 0.01629,-0.01268 0.03827,-0.011 0.05753,-0.0136 m -0.191816,0.407786 v 0.393594 l 0.0031,0.03223 0.06386,0.0049 h 1.044335 l 0.0621,-0.0049 0.0049,-0.03223 v -0.393597"
+     id="path3081"
+     style="fill:none;stroke:#000000;stroke-width:0.15625;stroke-opacity:1" />
+</svg>
diff --git a/app/src/main/assets/shortcut_black.svg b/app/src/main/assets/shortcut_black.svg
new file mode 100644
index 0000000..0de5b30
--- /dev/null
+++ b/app/src/main/assets/shortcut_black.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><g><path d="M0,0h24v24H0V0z" fill="none"/></g><g><path d="M15,5l-1.41,1.41L15,7.83L17.17,10H8c-2.76,0-5,2.24-5,5v4h2v-4c0-1.65,1.35-3,3-3h9.17L15,14.17l-1.41,1.41L15,17l6-6 L15,5z"/></g></svg>
\ No newline at end of file
diff --git a/app/src/main/assets/sony_800n.svg b/app/src/main/assets/sony_800n.svg
new file mode 100644
index 0000000..9811d47
--- /dev/null
+++ b/app/src/main/assets/sony_800n.svg
@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   width="75.157791mm"
+   height="91.070755mm"
+   viewBox="0 0 75.157791 91.070755"
+   version="1.1"
+   id="svg5"
+   inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
+   sodipodi:docname="sony_800n.svg"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg">
+  <sodipodi:namedview
+     id="namedview7"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageshadow="2"
+     inkscape:pageopacity="0.0"
+     inkscape:pagecheckerboard="0"
+     inkscape:document-units="mm"
+     showgrid="false"
+     inkscape:zoom="0.90583672"
+     inkscape:cx="7.1756861"
+     inkscape:cy="221.34232"
+     inkscape:window-width="1920"
+     inkscape:window-height="1035"
+     inkscape:window-x="0"
+     inkscape:window-y="45"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="layer1" />
+  <defs
+     id="defs2" />
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(-77.71272,-89.542777)">
+    <path
+       id="path13615"
+       style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:0.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       d="m 135.66527,92.903517 c 0.27135,0.13426 0.53173,0.27201 0.7891,0.41755 0.25737,0.14554 0.51166,0.2989 0.77153,0.46353 0.25987,0.16464 0.52522,0.34072 0.80408,0.53227 0.27886,0.19155 0.57139,0.39849 0.88574,0.62477 0.20548,0.14663 0.4094,0.29769 0.61236,0.45268 0.20297,0.155 0.4046,0.31412 0.60462,0.47698 0.40003,0.32571 0.79411,0.6664 1.17977,1.02061 0.38566,0.35421 0.76333,0.72195 1.1312,1.10071 0.36786,0.37876 0.72564,0.76852 1.07228,1.16788 0.34664,0.399359 0.68209,0.808199 1.00408,1.224223 0.32198,0.41601 0.63058,0.83942 0.92449,1.26814 0.2939,0.42871 0.57269,0.86271 0.83509,1.30018 0.13119,0.21873 0.25869,0.43817 0.38137,0.65835 0.12268,0.22019 0.24077,0.44084 0.3545,0.66198 0.2934,0.57048 0.56166,1.11292 0.80719,1.634 0.24552,0.52109 0.4684,1.02093 0.67024,1.50792 0.20184,0.48699 0.38286,0.96066 0.54519,1.42886 0.0812,0.23409 0.15736,0.46691 0.22944,0.69918 0.0721,0.23227 0.1401,0.46424 0.2036,0.6966 0.0635,0.23235 0.12283,0.46534 0.17829,0.6997 0.0555,0.23435 0.10709,0.47021 0.15503,0.70848 0.0959,0.47653 0.17707,0.9624 0.24598,1.46606 0.0689,0.50366 0.12543,1.02488 0.17156,1.57096 0.0461,0.54609 0.082,1.11753 0.10956,1.72135 0.0204,0.44657 0.0346,0.88054 0.0419,1.30482 0.007,0.42429 0.008,0.8385 5.1e-4,1.24489 -0.007,0.40639 -0.022,0.80498 -0.045,1.19786 -0.023,0.39288 -0.0544,0.78 -0.0946,1.16375 -0.0402,0.38375 -0.0895,0.76407 -0.14831,1.14309 -0.0588,0.37901 -0.12693,0.75667 -0.20567,1.13533 -0.0787,0.37865 -0.16812,0.75833 -0.2682,1.14101 -0.10008,0.38269 -0.21104,0.76852 -0.33383,1.15962 -0.12279,0.39111 -0.25722,0.78775 -0.40411,1.19166 -0.14688,0.40391 -0.30617,0.81501 -0.47852,1.2361 -0.17236,0.4211 -0.35788,0.85183 -0.55707,1.2945 -0.1992,0.44266 -0.41182,0.89769 -0.63924,1.36632 -0.22742,0.46863 -0.46955,0.95107 -0.72657,1.45004 -0.25702,0.49898 -0.52901,1.01401 -0.817,1.54771 -0.288,0.5337 -0.59174,1.08599 -0.9121,1.65881 -0.32035,0.57283 -0.65722,1.16651 -1.0113,1.78284 -0.51856,0.87562 -1.03369,1.73426 -1.54461,2.57607 -0.51092,0.84181 -1.01808,1.66687 -1.52136,2.4753 -0.50327,0.80843 -1.00246,1.60008 -1.4981,2.37557 -0.49564,0.77548 -0.98736,1.5344 -1.47536,2.27738 -0.488,0.74297 -0.97226,1.46985 -1.45262,2.18074 -0.48037,0.7109 -0.95716,1.4059 -1.42989,2.08515 -0.47273,0.67925 -0.94154,1.34305 -1.40663,1.99109 -0.4651,0.64804 -0.92644,1.2803 -1.3839,1.89756 -0.45746,0.61726 -0.91081,1.21918 -1.36064,1.80609 -0.44982,0.58691 -0.89622,1.15918 -1.33842,1.71617 -0.44219,0.557 -0.8806,1.09875 -1.31516,1.62626 -0.0219,0.0266 -0.0432,0.051 -0.0651,0.0775 1.81453,-0.25165 9.01417,-6.51807 12.76563,-11.12956 8.5941,-10.56399 12.99952,-23.07404 11.68456,-33.17953 -1.20223,-9.23918 -5.38978,-16.783353 -13.17077,-23.726723 -1.93172,-1.72377 -2.14508,-1.49298 -2.70733,-1.32291 -1.92707,1.14579 -2.14761,1.048007 -0.84748,1.75253 z"
+       sodipodi:nodetypes="cssscssssssssssssssssssssssssssssssssssscsssssssssssccsscc" />
+    <path
+       style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:0.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       d="m 100.81415,134.55672 c -2.613864,-0.11093 -2.896844,0.31251 -4.813676,-1.43755 -1.8001,-1.90704 -2.224465,-2.79101 -2.24341,-4.51313 -0.01966,-1.78671 0.0581,-1.99561 1.60434,-5.63226 3.30378,-7.77016 7.260176,-15.56447 9.109986,-17.94715 2.77204,-3.57059 6.7396,-6.179423 11.18926,-7.521773 3.34849,-1.01016 9.09281,-0.86199 12.68493,0.32721 11.38024,3.767503 17.47147,15.775673 13.71295,27.033553 -2.20249,6.59712 -5.72057,10.16997 -9.8469,10.16997 z"
+       id="path3272"
+       sodipodi:nodetypes="ccsssssscc" />
+    <path
+       style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:0.377953;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke;stop-color:#000000"
+       id="path16756"
+       sodipodi:type="arc"
+       sodipodi:cx="97.313911"
+       sodipodi:cy="141.57806"
+       sodipodi:rx="1.5739406"
+       sodipodi:ry="1.5739406"
+       sodipodi:start="0.57990659"
+       sodipodi:end="0.54828965"
+       sodipodi:arc-type="arc"
+       d="m 98.630535,142.4405 a 1.5739406,1.5739406 0 0 1 -2.168624,0.46096 1.5739406,1.5739406 0 0 1 -0.478091,-2.16491 1.5739406,1.5739406 0 0 1 2.161065,-0.49519 1.5739406,1.5739406 0 0 1 0.512255,2.15709"
+       sodipodi:open="true" />
+    <path
+       id="rect20090"
+       style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1;paint-order:fill markers stroke;stop-color:#000000"
+       d="m 98.542336,130.27149 31.024354,0.54286 c 1.17552,0.0206 2.12219,0.94649 2.12219,2.12218 0,1.1757 -0.94667,2.14276 -2.12219,2.12219 l -31.024354,-0.54286 c -1.175513,-0.0206 -2.122189,-0.94649 -2.122189,-2.12219 0,-1.17569 0.946676,-2.14275 2.122189,-2.12218 z"
+       sodipodi:nodetypes="sssssss" />
+    <path
+       style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:0.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       d="m 95.913493,179.95992 c -5.06264,-1.04405 -10.8119,-4.79262 -13.62787,-8.88549 -4.297169,-6.24572 -5.356359,-12.81158 -3.341019,-20.71068 3.609809,-14.14857 14.086349,-39.6859 19.638949,-47.87138 3.969187,-5.851261 9.298937,-9.722411 16.097997,-11.692431 2.30613,-0.6682 4.45831,-0.93852 7.86666,-1.00422 1.97685,-0.03811 5.29289,0.29142 8.44033,1.37883 3.93773,1.33876 5.19157,1.825243 7.70631,3.635523 3.28778,2.34611 6.39237,5.783278 8.21204,9.321468 2.34718,4.56389 3.17486,7.43793 3.3952,12.26847 0.32592,7.14516 -0.95256,11.19873 -6.6179,21.05994 -8.13144,13.33839 -9.93436,16.02958 -19.23356,27.24303 -1.88331,2.37684 -5.10587,6.0049 -7.16123,8.06236 -3.02143,3.0245 -4.24368,3.98612 -6.38286,5.02178 -4.8517,2.34889 -10.30616,3.13935 -14.993047,2.1728 z"
+       id="path5762"
+       sodipodi:nodetypes="ssssssccssccsss" />
+  </g>
+</svg>
diff --git a/app/src/main/assets/sony_800n_case.svg b/app/src/main/assets/sony_800n_case.svg
new file mode 100644
index 0000000..3fd7343
--- /dev/null
+++ b/app/src/main/assets/sony_800n_case.svg
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   width="12.321217mm"
+   height="12.3mm"
+   viewBox="0 0 12.321217 12.3"
+   version="1.1"
+   id="svg3988"
+   inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
+   sodipodi:docname="sony_800n_case.svg"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg">
+  <sodipodi:namedview
+     id="namedview3990"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageshadow="2"
+     inkscape:pageopacity="0.0"
+     inkscape:pagecheckerboard="0"
+     inkscape:document-units="mm"
+     showgrid="false"
+     showguides="true"
+     inkscape:guide-bbox="true"
+     fit-margin-left="2"
+     fit-margin-right="2"
+     fit-margin-bottom="1"
+     fit-margin-top="1"
+     inkscape:zoom="10.248373"
+     inkscape:cx="27.662928"
+     inkscape:cy="19.076199"
+     inkscape:window-width="1920"
+     inkscape:window-height="1035"
+     inkscape:window-x="0"
+     inkscape:window-y="45"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="layer1">
+    <sodipodi:guide
+       position="14.349871,-1.5160295"
+       orientation="0,-1"
+       id="guide4472" />
+    <sodipodi:guide
+       position="-3.9933874,14.528046"
+       orientation="1,0"
+       id="guide14199" />
+  </sodipodi:namedview>
+  <defs
+     id="defs3985" />
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(-81.704318,-153.92826)">
+    <g
+       id="g26612"
+       transform="matrix(0.10330033,0,0,0.09696175,75.13823,137.28434)"
+       style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:4.99595;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1">
+      <path
+         style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:4.99595;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         d="m 72.96681,225.65437 c 0.223576,0.58263 2.250534,1.71014 4.308817,2.39697 2.122774,0.70836 5.258828,1.34722 8.366393,1.70453 2.48165,0.28534 37.1587,1.1115 37.1587,1.1115 h 3.21119 c 0,0 34.67708,-0.82616 37.15873,-1.1115 3.10756,-0.35731 6.24361,-0.99617 8.36639,-1.70453 2.05828,-0.68683 4.08524,-1.81434 4.30882,-2.39697"
+         id="path23413"
+         sodipodi:nodetypes="cssccssc" />
+      <path
+         style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:4.99595;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         d="m 116.98218,265.2035 c -12.08548,-0.56413 -19.153432,-1.85996 -24.277133,-4.45093 -9.494531,-4.80122 -16.040643,-14.2392 -18.553206,-26.74943 -0.997929,-4.96876 -1.79383,-13.53037 -1.363114,-14.66323 1.002805,-2.63759 7.912285,-7.1158 15.890039,-10.29877 5.230526,-2.08687 12.919394,-3.81264 21.636084,-4.85622 5.58344,-0.66846 22.56789,-0.66846 28.15133,0 8.59247,1.02871 16.20658,2.72438 21.46386,4.78001 7.8805,3.08133 15.05476,7.72504 16.07321,10.40376 0.27311,0.71833 -0.11096,6.58993 -0.70631,10.79799 -1.49912,10.59604 -5.14752,18.6992 -11.07883,24.60625 -7.38953,7.3593 -14.41912,9.57903 -33.08072,10.44589 -6.13715,0.28508 -7.75734,0.28333 -14.15521,-0.0153 z"
+         id="path21760" />
+    </g>
+  </g>
+</svg>
diff --git a/app/src/main/assets/sports_badmington.svg b/app/src/main/assets/sports_badmington.svg
new file mode 100644
index 0000000..ddb6743
--- /dev/null
+++ b/app/src/main/assets/sports_badmington.svg
@@ -0,0 +1,83 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   enable-background="new 0 0 24 24"
+   height="24"
+   viewBox="0 0 24 24"
+   width="24"
+   version="1.1"
+   id="svg12"
+   sodipodi:docname="sports_badmington.svg"
+   inkscape:version="1.0.1 (3bc2e813f5, 2020-09-07)">
+  <metadata
+     id="metadata18">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs16" />
+  <sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="1920"
+     inkscape:window-height="1042"
+     id="namedview14"
+     showgrid="false"
+     inkscape:zoom="11.092738"
+     inkscape:cx="37.162384"
+     inkscape:cy="25.090418"
+     inkscape:window-x="0"
+     inkscape:window-y="38"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="svg12" />
+  <g
+     id="g4">
+    <rect
+       fill="none"
+       height="24"
+       width="24"
+       id="rect2" />
+  </g>
+  <path
+     d="m 19.950218,3.6501062 c -2.34,-2.34 -6.62,-1.87 -9.55,1.06 -1.6,1.6 -2.52,3.87 -2.54,5.4599998 -0.02,1.580001 0.26,3.890001 -1.35,5.500001 l -4.24,4.24 1.42,1.42 4.24,-4.24 c 1.61,-1.61 3.92,-1.33 5.5,-1.35 1.58,-0.02 3.86,-0.94 5.46,-2.54 2.92,-2.930001 3.4,-7.2100008 1.06,-9.5500008 z m -9.2,9.1900008 c -1.53,-1.53 -1.05,-4.6100008 1.06,-6.7200008 2.11,-2.11 5.18,-2.59 6.72,-1.06 1.53,1.53 1.05,4.61 -1.06,6.7200008 -2.11,2.11 -5.18,2.59 -6.72,1.06 z"
+     id="path6"
+     style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+  <g
+     id="g912"
+     transform="translate(0.23401207,-0.05573492)"
+     style="stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none">
+    <path
+       d="m 21.377568,21.02156 c 0.09302,0 0.182529,0.03675 0.247467,0.10325 0.136896,0.1365 0.136896,0.35875 0,0.49525 -0.06494,0.06475 -0.154447,0.1015 -0.247467,0.1015 -0.09302,0 -0.182529,-0.03675 -0.247467,-0.10325 -0.136896,-0.1365 -0.136896,-0.35875 0,-0.49525 0.06494,-0.06475 0.154448,-0.1015 0.247467,-0.1015 m 0,-0.35 c -0.179019,0 -0.359793,0.06825 -0.496689,0.20475 -0.273793,0.273 -0.273793,0.71575 0,0.9905 0.136896,0.1365 0.31767,0.20475 0.496689,0.20475 0.179019,0 0.359793,-0.06825 0.496689,-0.20475 0.273793,-0.273 0.273793,-0.71575 0,-0.9905 -0.136896,-0.1365 -0.31767,-0.20475 -0.496689,-0.20475 z"
+       id="path8"
+       style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+    <path
+       style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       d="M 20.944499,21.92275 15.722053,19.209022"
+       id="path892"
+       sodipodi:nodetypes="cc" />
+    <path
+       style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       d="M 22.004584,21.06064 18.758901,15.993056"
+       id="path892-5"
+       sodipodi:nodetypes="cc" />
+  </g>
+</svg>
diff --git a/app/src/main/assets/sports_pingpong.svg b/app/src/main/assets/sports_pingpong.svg
new file mode 100644
index 0000000..9c84021
--- /dev/null
+++ b/app/src/main/assets/sports_pingpong.svg
@@ -0,0 +1,167 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   enable-background="new 0 0 24 24"
+   height="24"
+   viewBox="0 0 24 24"
+   width="24"
+   version="1.1"
+   id="svg12"
+   sodipodi:docname="sports_pingpong.svg"
+   inkscape:version="1.0.1 (3bc2e813f5, 2020-09-07)">
+  <metadata
+     id="metadata18">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs16" />
+  <sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="1920"
+     inkscape:window-height="1042"
+     id="namedview14"
+     showgrid="false"
+     showguides="true"
+     inkscape:guide-bbox="true"
+     inkscape:object-nodes="false"
+     inkscape:snap-nodes="false"
+     inkscape:snap-others="false"
+     inkscape:zoom="22.627417"
+     inkscape:cx="23.738095"
+     inkscape:cy="10.519266"
+     inkscape:window-x="0"
+     inkscape:window-y="38"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="svg12"
+     inkscape:document-rotation="0">
+    <sodipodi:guide
+       position="17.574052,26.877722"
+       orientation="0,-1"
+       id="guide883" />
+    <sodipodi:guide
+       position="5.3138494,18.987309"
+       orientation="0,-1"
+       id="guide885" />
+    <sodipodi:guide
+       position="16.549801,18.358566"
+       orientation="1,0"
+       id="guide887" />
+    <sodipodi:guide
+       position="3.265625,18"
+       orientation="0,-1"
+       id="guide891" />
+    <sodipodi:guide
+       position="10.949107,18.751942"
+       orientation="1,0"
+       id="guide895" />
+    <sodipodi:guide
+       position="12.650582,18.409437"
+       orientation="1,0"
+       id="guide897" />
+    <sodipodi:guide
+       position="13.542061,11.233501"
+       orientation="1,0"
+       id="guide918" />
+    <sodipodi:guide
+       position="11.468388,20.840117"
+       orientation="0,-1"
+       id="guide1057" />
+  </sodipodi:namedview>
+  <g
+     id="g4">
+    <rect
+       fill="none"
+       height="24"
+       width="24"
+       id="rect2" />
+  </g>
+  <g
+     id="g1460">
+    <path
+       id="path879"
+       style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-linecap:square;stroke-miterlimit:4;stroke-dasharray:none;paint-order:fill markers stroke"
+       d="M 8.9746094 3.6386719 A 4.9498253 4.9496808 70 0 0 7.3242188 3.9335938 A 4.9498253 4.9496808 70 0 0 4.0683594 8.875 A 4.9498253 4.9496808 70 0 0 7.8769531 13.40625 L 12.964844 11.564453 A 4.9498253 4.9496808 70 0 0 12.990234 5.6445312 A 4.9498253 4.9496808 70 0 0 8.9746094 3.6386719 z M 6.5664062 6.8300781 A 1.5 1.5 0 0 1 8.0664062 8.3300781 A 1.5 1.5 0 0 1 6.5664062 9.8300781 A 1.5 1.5 0 0 1 5.0664062 8.3300781 A 1.5 1.5 0 0 1 6.5664062 6.8300781 z " />
+    <path
+       style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       d="M 9.9193244,14.187583 11.413996,18.07844 13.27163,17.402316 11.896836,13.443232"
+       id="path893"
+       sodipodi:nodetypes="cccc" />
+    <path
+       style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       d="m 12.968016,11.552689 c -1.382892,1.73803 -0.963028,1.977724 -1.029997,2.18556"
+       id="path899"
+       sodipodi:nodetypes="cc" />
+    <path
+       style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       d="m 7.8824612,13.403679 c 2.1538728,0.450753 2.0286568,0.932477 2.2119478,1.049226"
+       id="path899-4"
+       sodipodi:nodetypes="cc" />
+    <path
+       style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       d="m 12.968016,11.552689 -5.0855548,1.85099"
+       id="path916" />
+    <path
+       style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       d="M 13.572397,10.488971 6.7541886,12.970596"
+       id="path920" />
+    <path
+       style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-linecap:square;stroke-miterlimit:4;stroke-dasharray:none;paint-order:fill markers stroke"
+       id="path879-7"
+       sodipodi:type="arc"
+       sodipodi:arc-type="arc"
+       sodipodi:open="true"
+       sodipodi:start="0.57990613"
+       sodipodi:end="5.706593"
+       sodipodi:ry="4.9496808"
+       sodipodi:rx="4.9498253"
+       sodipodi:cy="-18.507776"
+       sodipodi:cx="2.4408588"
+       transform="rotate(110)"
+       d="m 6.5814574,-15.795621 a 4.9498253,4.9496808 0 0 1 -5.5597185,2.029738 4.9498253,4.9496808 0 0 1 -3.5306986,-4.750094 4.9498253,4.9496808 0 0 1 3.5464206,-4.73837 4.9498253,4.9496808 0 0 1 5.5529616,2.048152" />
+    <g
+       id="g1016"
+       style="fill:none;fill-opacity:1;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none"
+       transform="translate(2.7714608,-1.6578345)">
+      <path
+         style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         d="m 10.885327,15.160729 -1.3593007,3.936457 1.8576347,0.676124 1.500166,-3.928054"
+         id="path893-2"
+         sodipodi:nodetypes="cccc" />
+      <path
+         style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         d="m 14.911147,15.097075 c -2.176542,0.442503 -2.00898,0.896002 -2.193875,1.012167"
+         id="path899-2"
+         sodipodi:nodetypes="cc" />
+      <path
+         style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         d="m 9.8255917,13.246085 c 1.3602233,1.729779 0.9546563,2.018314 1.0200213,2.225566"
+         id="path899-4-6"
+         sodipodi:nodetypes="cc" />
+      <path
+         style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         d="M 14.911147,15.097075 9.8255917,13.246085"
+         id="path916-1" />
+    </g>
+  </g>
+</svg>
diff --git a/app/src/main/assets/tws_bud.svg b/app/src/main/assets/tws_bud.svg
new file mode 100644
index 0000000..1cd983e
--- /dev/null
+++ b/app/src/main/assets/tws_bud.svg
@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   width="34.999996mm"
+   height="67.499672mm"
+   viewBox="0 0 34.999996 67.499672"
+   version="1.1"
+   id="svg1584"
+   inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)"
+   sodipodi:docname="tws_bud.svg"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg">
+  <sodipodi:namedview
+     id="namedview1586"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageshadow="2"
+     inkscape:pageopacity="0.0"
+     inkscape:pagecheckerboard="0"
+     inkscape:document-units="mm"
+     showgrid="false"
+     showguides="true"
+     inkscape:guide-bbox="true"
+     fit-margin-top="5"
+     fit-margin-left="5"
+     fit-margin-right="5"
+     fit-margin-bottom="5"
+     inkscape:zoom="1.7965552"
+     inkscape:cx="20.873281"
+     inkscape:cy="96.017091"
+     inkscape:window-width="1920"
+     inkscape:window-height="1035"
+     inkscape:window-x="0"
+     inkscape:window-y="45"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="layer1">
+    <sodipodi:guide
+       position="56.822358,-42.871949"
+       orientation="0,-1"
+       id="guide12345" />
+    <sodipodi:guide
+       position="44.960927,-48.473189"
+       orientation="1,0"
+       id="guide12613" />
+    <sodipodi:guide
+       position="62.862338,-51.411219"
+       orientation="1,0"
+       id="guide12653" />
+    <sodipodi:guide
+       position="55.621618,-48.425059"
+       orientation="0,-1"
+       id="guide13329" />
+  </sodipodi:namedview>
+  <defs
+     id="defs1581" />
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(-57.520553,-52.18693)">
+    <path
+       style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       d="m 66.393093,113.75676 c 0,0 -2.756884,-1.09093 -2.872459,-3.68865 l 0.142728,-39.164751 c 0.01936,-5.31142 3.361305,-12.810634 11.661174,-12.715563 6.576003,0.07532 11.083155,1.743367 11.108851,5.086109 l 0.08706,11.325221 c 0.04024,5.235233 -11.549838,8.121118 -14.518479,6.272204 -2.668401,-1.661919 -2.100534,7.911615 -2.100534,7.911615"
+       id="path1690"
+       sodipodi:nodetypes="ccsssssc" />
+    <path
+       id="rect9302"
+       style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;paint-order:fill markers stroke;stop-color:#000000"
+       d="m 72.801341,91.173713 h 2.007465 c 0,0 0,-1.120076 0,-1.680114 h 5.186597 v 1.665678 l 2.076559,0.01443 c 1.462007,0.01017 2.63906,1.218552 2.63906,2.732177 v 15.226926 c 0,1.51363 -1.177022,2.73218 -2.63906,2.73218 h -9.270621 c -1.462039,0 -2.63906,-1.21855 -2.63906,-2.73218 V 93.905881 c 0,-1.513625 1.177021,-2.732177 2.63906,-2.732177 z"
+       sodipodi:nodetypes="sccccssssssss" />
+  </g>
+</svg>
diff --git a/app/src/main/assets/tws_case.svg b/app/src/main/assets/tws_case.svg
new file mode 100644
index 0000000..dffa098
--- /dev/null
+++ b/app/src/main/assets/tws_case.svg
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   width="45"
+   height="45"
+   viewBox="0 0 25 25"
+   version="1.1"
+   id="svg5"
+   inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)"
+   sodipodi:docname="tws_case.svg"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg">
+  <sodipodi:namedview
+     id="namedview7"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageshadow="2"
+     inkscape:pageopacity="0.0"
+     inkscape:pagecheckerboard="0"
+     inkscape:document-units="mm"
+     showgrid="false"
+     inkscape:zoom="2.5407127"
+     inkscape:cx="-124.17776"
+     inkscape:cy="59.235348"
+     inkscape:window-width="1890"
+     inkscape:window-height="1038"
+     inkscape:window-x="26"
+     inkscape:window-y="23"
+     inkscape:window-maximized="0"
+     inkscape:current-layer="layer1"
+     fit-margin-top="5"
+     fit-margin-left="5"
+     fit-margin-right="5"
+     fit-margin-bottom="5"
+     units="px"
+     viewbox-height="25" />
+  <defs
+     id="defs2" />
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(-35.169857,-38.996456)">
+    <rect
+       style="fill:none;stroke:#000000;stroke-width:2.66666667;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke;stop-color:#000000"
+       id="rect846"
+       width="20.062387"
+       height="19"
+       x="37.638664"
+       y="41.996456"
+       ry="5.7287159" />
+    <path
+       style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2.66666667;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       d="M 37.583681,47.610107 H 57.756032"
+       id="path1465" />
+  </g>
+</svg>
diff --git a/app/src/main/assets/womp/app.js b/app/src/main/assets/womp/app.js
new file mode 100644
index 0000000..666aebf
--- /dev/null
+++ b/app/src/main/assets/womp/app.js
@@ -0,0 +1,70 @@
+window.__cbs = {};
+window.__wompCb = function (id, ok, body) {
+  var fn = window.__cbs[id];
+  delete window.__cbs[id];
+  if (!fn) return;
+  var data = body;
+  if (typeof body === "string") {
+    try { data = JSON.parse(body); } catch (e) { data = body; }
+  }
+  fn(ok, data);
+};
+function callNative(name, a, b, c) {
+  return new Promise(function (resolve, reject) {
+    var id = "cb_" + Date.now() + "_" + Math.random().toString(16).slice(2);
+    window.__cbs[id] = function (ok, data) {
+      if (ok === false) reject(data);
+      else resolve(data);
+    };
+    if (name === "request") Womp.request(a, b || "{}", id);
+    else if (name === "scan") Womp.scan(id);
+    else if (name === "getLocation") Womp.getLocation(id);
+  });
+}
+function api(path, body) {
+  var payload = body || {};
+  try {
+    var sorter = JSON.parse(Womp.getSorter() || "{}");
+    if (sorter.sorterId && payload.sorterId == null) payload.sorterId = sorter.sorterId;
+  } catch (e) {}
+  return callNative("request", path, JSON.stringify(payload)).then(function (res) {
+    if (res && typeof res === "object" && res.code && res.code !== 1000) {
+      var err = new Error(res.message || "璇锋眰澶辫触");
+      err.code = res.code;
+      throw err;
+    }
+    return res && res.data !== undefined ? res.data : res;
+  });
+}
+function errMsg(e, fallback) {
+  if (!e) return fallback || "鎿嶄綔澶辫触";
+  if (typeof e === "string") return e;
+  if (e.message) return e.message;
+  return fallback || "鎿嶄綔澶辫触";
+}
+function loginErrMsg(e) {
+  var msg = errMsg(e, "鐧诲綍澶辫触锛岃绋嶅悗閲嶈瘯");
+  if (msg.length > 80 || /<html/i.test(msg)) return "鐧诲綍澶辫触锛岃绋嶅悗閲嶈瘯";
+  if (/璐﹀彿鎴栧瘑鐮侀敊璇瘄鐢ㄦ埛涓嶅瓨鍦▅unauthorized|password/i.test(msg)) {
+    return "鎵嬫満鍙锋垨瀵嗙爜閿欒锛岃閲嶆柊杈撳叆";
+  }
+  return msg;
+}
+function scan() { return callNative("scan"); }
+function getLocation() { return callNative("getLocation"); }
+function toast(msg) { Womp.toast(String(msg || "")); }
+function go(page) { location.href = page; }
+function qs(k) {
+  var m = location.search.match(new RegExp("[?&]" + k + "=([^&]*)"));
+  return m ? decodeURIComponent(m[1]) : "";
+}
+function $(id) { return document.getElementById(id); }
+function html(el, s) { el.innerHTML = s; }
+function money(n) { return Number(n || 0).toFixed(2); }
+function userHtml(u, extra) {
+  if (!u) return "<div class='muted'>鏆傛棤鐢ㄦ埛淇℃伅</div>";
+  return "<div><b>" + (u.nickName || "-") + "</b> 路 " + (u.role || "") + "</div>" +
+    "<div class='muted'>鎵嬫満 " + (u.phone || "-") + " 路 绉垎 " + (u.carbonBalance == null ? "-" : u.carbonBalance) + "</div>" +
+    (u.collegeName || u.className ? "<div class='muted'>" + (u.collegeName || "") + " " + (u.className || "") + "</div>" : "") +
+    (extra ? "<div class='muted'>" + extra + "</div>" : "");
+}
diff --git a/app/src/main/assets/womp/appointments.html b/app/src/main/assets/womp/appointments.html
new file mode 100644
index 0000000..e21acd2
--- /dev/null
+++ b/app/src/main/assets/womp/appointments.html
@@ -0,0 +1,73 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+<head>
+  <meta charset="utf-8"/>
+  <meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1"/>
+  <title>棰勭害</title>
+  <link rel="stylesheet" href="css.css"/>
+</head>
+<body>
+<div class="wrap">
+  <div class="page-head">
+    <h1>棰勭害璁㈠崟</h1>
+    <a class="back-link" href="home.html">杩斿洖宸ヤ綔鍙�</a>
+  </div>
+  <div class="card">
+    <input id="kw" placeholder="濮撳悕/鐢佃瘽/棰勭害鍙�"/>
+    <button class="btn ghost" onclick="search()">鏌ヨ</button>
+    <div id="searchList"></div>
+  </div>
+  <div class="card">
+    <div class="sec-title">鏂板棰勭害锛堝緟棰嗗彇锛�</div>
+    <div id="newList" class="muted">鍔犺浇涓��</div>
+  </div>
+  <div class="card">
+    <div class="sec-title">鎴戠殑鏈畬鎴愰绾�</div>
+    <div id="mineList" class="muted">鍔犺浇涓��</div>
+  </div>
+</div>
+<script src="app.js"></script>
+<script>
+function rowHtml(a, action) {
+  return "<div class='item'><b>" + (a.contactName || "") + "</b> " + (a.contactPhone || "") +
+    "<div class='muted'>" + (a.appointmentNo || "") + " 路 " + (a.status || "") + "</div>" +
+    "<div class='muted'>" + (a.address || "") + "</div>" +
+    "<div class='muted'>" + (a.appointmentDate || "") + " " + (a.appointmentSlot || "") + "</div>" +
+    action + "</div>";
+}
+function renderList(el, list, empty, btnText, fn) {
+  if (!list.length) { $(el).innerHTML = "<div class='muted'>" + empty + "</div>"; return; }
+  $(el).innerHTML = list.map(function (a) {
+    var action = btnText ? "<button class='btn sm' onclick='" + fn + "(" + a.id + ")'>" + btnText + "</button>" : "";
+    return rowHtml(a, action);
+  }).join("");
+}
+function loadNew() {
+  api("/app/shop/sorter/appointmentPage", { page: 1, size: 50, pool: "new" }).then(function (res) {
+    renderList("newList", (res && res.list) || [], "鏆傛棤鏂板棰勭害", "棰嗗彇鍒版垜鍚嶄笅", "claimApt");
+  }).catch(function (e) { $("newList").innerHTML = "<div class='err'>" + (e.message || "鍔犺浇澶辫触") + "</div>"; });
+}
+function loadMine() {
+  api("/app/shop/sorter/appointmentPage", { page: 1, size: 50, pool: "mine" }).then(function (res) {
+    renderList("mineList", (res && res.list) || [], "鏆傛棤鏈畬鎴愰绾�", "", "");
+  }).catch(function (e) { $("mineList").innerHTML = "<div class='err'>" + (e.message || "鍔犺浇澶辫触") + "</div>"; });
+}
+function search() {
+  api("/app/shop/sorter/appointmentPage", { page: 1, size: 50, keyWord: $("kw").value }).then(function (res) {
+    var list = (res && res.list) || [];
+    if (!list.length) { $("searchList").innerHTML = "<div class='muted'>娌℃湁鍖归厤缁撴灉</div>"; return; }
+    $("searchList").innerHTML = list.map(function (a) { return rowHtml(a, ""); }).join("");
+  }).catch(function (e) { toast(e.message); });
+}
+function claimApt(id) {
+  api("/app/shop/sorter/claimAppointment", { id: id }).then(function () {
+    toast("宸查鍙�");
+    loadNew();
+    loadMine();
+  }).catch(function (e) { toast(e.message); });
+}
+loadNew();
+loadMine();
+</script>
+</body>
+</html>
diff --git a/app/src/main/assets/womp/checkin.html b/app/src/main/assets/womp/checkin.html
new file mode 100644
index 0000000..876ae64
--- /dev/null
+++ b/app/src/main/assets/womp/checkin.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+<head>
+  <meta charset="utf-8"/>
+  <meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1"/>
+  <title>鎵撳崱</title>
+  <link rel="stylesheet" href="css.css"/>
+</head>
+<body>
+<div class="wrap">
+  <div class="page-head">
+    <h1>鎵爜鎵撳崱</h1>
+    <a class="back-link" href="home.html">杩斿洖宸ヤ綔鍙�</a>
+  </div>
+  <div class="card">
+    <div id="msg" class="muted">鐐瑰嚮寮�濮嬪悗鎸佺画鎵爜锛屾壂鍒板洖鏀剁爜鍗虫墦鍗°�傛壂鐮侀〉鍙偣銆屽仠姝� / 杩斿洖銆嶇粨鏉熴��</div>
+    <div id="result"></div>
+    <button class="btn" id="btn" onclick="toggle()">寮�濮嬫壂鐮�</button>
+  </div>
+</div>
+<script src="app.js"></script>
+<script>
+var running = false, timer = null;
+function toggle() {
+  running = !running;
+  $("btn").textContent = running ? "鍋滄鎵爜" : "寮�濮嬫壂鐮�";
+  if (running) loop();
+  else if (timer) { clearTimeout(timer); timer = null; }
+}
+function loop() {
+  if (!running) return;
+  scan().then(function (code) {
+    if (!running) return;
+    if (!code) { running = false; $("btn").textContent = "寮�濮嬫壂鐮�"; return; }
+    return api("/app/shop/sorter/checkUser", { unionid: code }).then(function (user) {
+      return api("/app/shop/sorter/checkIn", { unionid: user.unionid }).then(function (r) {
+        $("result").innerHTML = "<div class='ok'>鎵撳崱鎴愬姛 路 " + (user.nickName || "") + " +" + r.score + "鍒�</div>";
+        toast("鎵撳崱鎴愬姛");
+        timer = setTimeout(function () { $("result").innerHTML = ""; loop(); }, 3000);
+      });
+    });
+  }).catch(function (e) {
+    $("result").innerHTML = "<div class='err'>" + (e.message || "鎵撳崱澶辫触") + "</div>";
+    toast(e.message || "鎵撳崱澶辫触");
+    timer = setTimeout(function () { if (running) loop(); }, 1800);
+  });
+}
+</script>
+</body>
+</html>
diff --git a/app/src/main/assets/womp/css.css b/app/src/main/assets/womp/css.css
new file mode 100644
index 0000000..1defe67
--- /dev/null
+++ b/app/src/main/assets/womp/css.css
@@ -0,0 +1,34 @@
+:root{--bg:#f4f7f4;--card:#fff;--primary:#2e7d32;--danger:#c62828;--text:#1b1b1b;--muted:#667066}
+*{box-sizing:border-box}
+body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"PingFang SC","Microsoft YaHei",sans-serif;background:var(--bg);color:var(--text)}
+.wrap{padding:12px 16px 32px}
+h1{font-size:18px;margin:0}
+.card{background:var(--card);border-radius:12px;padding:14px;margin-bottom:12px;box-shadow:0 1px 4px rgba(0,0,0,.06)}
+label{display:block;font-size:13px;color:var(--muted);margin:10px 0 4px}
+.req:after{content:" *";color:var(--danger)}
+input,select,textarea{width:100%;padding:10px 12px;border:1px solid #d7ddd7;border-radius:8px;font-size:16px;background:#fff}
+.btn{display:block;width:100%;border:0;border-radius:10px;padding:12px;font-size:16px;margin-top:12px;background:var(--primary);color:#fff}
+.btn.ghost{background:#e8f5e9;color:var(--primary)}
+.btn.danger{background:var(--danger)}
+.btn.sm{width:auto;display:inline-block;padding:6px 12px;font-size:13px;margin:0;border-radius:8px}
+.grid{display:grid;grid-template-columns:1fr 1fr;gap:10px}
+.tile{background:var(--card);border-radius:12px;padding:18px 12px;text-align:center;box-shadow:0 1px 4px rgba(0,0,0,.06);font-size:15px}
+.item{padding:10px 0;border-bottom:1px solid #eee}
+.item:last-child{border-bottom:0}
+.muted{color:var(--muted);font-size:13px}
+.ok{color:var(--primary);font-size:18px;text-align:center;padding:24px 0}
+.err{color:var(--danger);text-align:center;padding:8px}
+.row{display:flex;gap:8px;align-items:center}
+.row input{flex:1}
+.page-head{display:flex;align-items:center;justify-content:space-between;gap:8px;margin:4px 0 12px}
+.back-link{font-size:13px;color:var(--primary);text-decoration:none;white-space:nowrap}
+.who-bar{display:flex;align-items:center;gap:8px;background:var(--card);border-radius:12px;padding:12px;margin-bottom:12px;box-shadow:0 1px 4px rgba(0,0,0,.06)}
+.who-center{flex:1;text-align:center}
+.who-name{font-size:17px;font-weight:700}
+.sec{margin:16px 0 8px}
+.sec-title{font-size:14px;font-weight:700;color:var(--primary);padding:4px 2px 8px;border-bottom:2px solid #c8e6c9;margin-bottom:10px}
+.chip-wrap{display:flex;flex-wrap:wrap;gap:8px}
+.chip{border:1px solid #c8e6c9;background:#fff;border-radius:20px;padding:8px 12px;font-size:13px}
+.chip.on{background:var(--primary);color:#fff;border-color:var(--primary)}
+.preview{background:#f1f8f2;border-radius:8px;padding:8px 10px;margin-top:8px;font-size:13px}
+.totals{background:#e8f5e9;border-radius:8px;padding:10px;margin-top:10px;font-size:14px}
diff --git a/app/src/main/assets/womp/home.html b/app/src/main/assets/womp/home.html
new file mode 100644
index 0000000..8250ff3
--- /dev/null
+++ b/app/src/main/assets/womp/home.html
@@ -0,0 +1,100 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+<head>
+  <meta charset="utf-8"/>
+  <meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1"/>
+  <title>宸ヤ綔鍙�</title>
+  <link rel="stylesheet" href="css.css"/>
+</head>
+<body>
+<div class="wrap">
+  <div class="who-bar">
+    <div class="who-center" id="who"></div>
+    <button class="btn sm ghost" onclick="Womp.logout()">閫�鍑虹櫥褰�</button>
+  </div>
+
+  <div class="sec">
+    <div class="sec-title">宸ヤ綔绛惧埌</div>
+    <div class="muted" id="attendStatus" style="margin:-4px 2px 8px">褰撳墠锛氭湭绛惧埌</div>
+    <div class="grid">
+      <div class="tile" onclick="attend('in')">涓婄彮绛惧埌</div>
+      <div class="tile" onclick="attend('out')">涓嬬彮绛鹃��</div>
+    </div>
+    <div id="attendMsg"></div>
+  </div>
+
+  <div class="sec">
+    <div class="sec-title">鍥炴敹浣滀笟</div>
+    <div class="grid">
+      <div class="tile" onclick="go('checkin.html')">鎵爜鎵撳崱</div>
+      <div class="tile" onclick="go('recycle.html')">鐜板満鍥炴敹</div>
+      <div class="tile" onclick="go('appointments.html')">棰勭害璁㈠崟</div>
+      <div class="tile" onclick="go('recycle.html?mode=apt')">棰勭害鍥炴敹</div>
+    </div>
+  </div>
+
+  <div class="sec">
+    <div class="sec-title">璁㈠崟鍙戣揣</div>
+    <div class="grid">
+      <div class="tile" onclick="go('orders.html')">璁㈠崟鍙戣揣</div>
+    </div>
+  </div>
+
+  <div class="sec">
+    <div class="sec-title">閿�鍞笌璁¢噺</div>
+    <div class="grid">
+      <div class="tile" onclick="go('wastesale.html')">鍙洖鏀剁墿閿�鍞�</div>
+      <div class="tile" onclick="go('siteweight.html')">鍘嬬缉绔欒閲�</div>
+    </div>
+  </div>
+</div>
+<script src="app.js"></script>
+<script>
+try {
+  var s = JSON.parse(Womp.getSorter() || "{}");
+  $("who").innerHTML = "<div class='who-name'>" + (s.businessName || "鍒嗘嫞鍛�") + "</div>" +
+    "<div class='muted'>璐﹀彿 " + (s.sorterId || "-") + " 路 鎵嬫満 " + (s.phone || "-") + "</div>";
+} catch (e) {}
+function refreshAttend() {
+  var on = false;
+  try { on = !!Womp.isCheckedIn(); } catch (e) {}
+  $("attendStatus").textContent = on ? "褰撳墠锛氬凡涓婄彮绛惧埌" : "褰撳墠锛氭湭绛惧埌";
+}
+refreshAttend();
+var attendBusy = false;
+function attend(type) {
+  if (attendBusy) return;
+  attendBusy = true;
+  var checkIn = type === "in";
+  $("attendMsg").innerHTML = "<div class='muted'>姝e湪鑾峰彇瀹氫綅鈥�</div>";
+  toast("姝e湪鑾峰彇瀹氫綅鈥�");
+  getLocation().then(function (loc) {
+    if (!loc || loc.longitude == null || loc.latitude == null) {
+      throw new Error("鏃犳硶鑾峰彇瀹氫綅锛岃寮�鍚疓PS鍚庨噸璇�");
+    }
+    var path = checkIn ? "/app/push/sorterattendance/checkIn" : "/app/push/sorterattendance/checkOut";
+    return api(path, { longitude: loc.longitude, latitude: loc.latitude });
+  }).then(function (data) {
+    try { Womp.setCheckedIn(checkIn); } catch (e) {}
+    var msg = (data && data.message) || (checkIn ? "绛惧埌鎴愬姛" : "绛鹃��鎴愬姛");
+    $("attendMsg").innerHTML = "<div class='ok' style='padding:12px 0;font-size:16px'>" + msg + "</div>";
+    toast(msg);
+    refreshAttend();
+  }).catch(function (e) {
+    var msg = errMsg(e, checkIn ? "绛惧埌澶辫触" : "绛鹃��澶辫触");
+    if (/浠婃棩宸茬鍒�/.test(msg)) {
+      try { Womp.setCheckedIn(true); } catch (x) {}
+      refreshAttend();
+    } else if (/浠婃棩宸茬閫�|璇峰厛涓婄彮绛惧埌/.test(msg)) {
+      try { Womp.setCheckedIn(false); } catch (x) {}
+      refreshAttend();
+    }
+    $("attendMsg").innerHTML = "<div class='err'>" + msg + "</div>";
+    toast(msg);
+  }).then(function () {
+    attendBusy = false;
+  });
+}
+</script>
+</body>
+</html>
diff --git a/app/src/main/assets/womp/login.html b/app/src/main/assets/womp/login.html
new file mode 100644
index 0000000..df551b1
--- /dev/null
+++ b/app/src/main/assets/womp/login.html
@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+<head>
+  <meta charset="utf-8"/>
+  <meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1"/>
+  <title>鐧诲綍</title>
+  <link rel="stylesheet" href="css.css"/>
+</head>
+<body>
+<div class="wrap">
+  <h1>鍒嗘嫞鍛樼櫥褰�</h1>
+  <div class="card">
+    <label>鎵嬫満鍙�</label>
+    <input id="phone" type="tel" placeholder="璇疯緭鍏ユ墜鏈哄彿" autocomplete="username"/>
+    <label>瀵嗙爜</label>
+    <input id="password" type="password" placeholder="璇疯緭鍏ュ瘑鐮�" autocomplete="current-password"/>
+    <div id="err" class="err"></div>
+    <button class="btn" id="loginBtn" onclick="login()">鐧诲綍</button>
+    <p class="muted">鎵嬫満鍙峰拰瀵嗙爜浼氫繚瀛樺湪鏈満锛屼笅娆℃墦寮�鑷姩濉叆銆傜櫥褰曞悗 7 澶╁唴鏃犻渶閲嶆柊鐧诲綍銆�</p>
+  </div>
+</div>
+<script src="app.js"></script>
+<script>
+function fillSaved() {
+  try {
+    $("phone").value = Womp.getSavedPhone() || "";
+    $("password").value = Womp.getSavedPassword() || "";
+  } catch (e) {}
+}
+fillSaved();
+function login() {
+  var phone = $("phone").value.trim();
+  var password = $("password").value;
+  var btn = $("loginBtn");
+  $("err").textContent = "";
+  if (!phone || !password) { $("err").textContent = "璇疯緭鍏ユ墜鏈哄彿鍜屽瘑鐮�"; return; }
+  try { Womp.saveCredentials(phone, password); } catch (e) {}
+  btn.disabled = true;
+  btn.textContent = "鐧诲綍涓��";
+  api("/app/user/login/sorterPassword", { phone: phone, password: password }).then(function (data) {
+    data.phone = phone;
+    data.password = password;
+    Womp.loginSuccess(JSON.stringify(data));
+  }).catch(function (e) {
+    $("err").textContent = loginErrMsg(e);
+  }).then(function () {
+    btn.disabled = false;
+    btn.textContent = "鐧诲綍";
+  });
+}
+</script>
+</body>
+</html>
diff --git a/app/src/main/assets/womp/orders.html b/app/src/main/assets/womp/orders.html
new file mode 100644
index 0000000..08ff4bf
--- /dev/null
+++ b/app/src/main/assets/womp/orders.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+<head>
+  <meta charset="utf-8"/>
+  <meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1"/>
+  <title>璁㈠崟鍙戣揣</title>
+  <link rel="stylesheet" href="css.css"/>
+</head>
+<body>
+<div class="wrap">
+  <div class="page-head">
+    <h1>璁㈠崟鍙戣揣</h1>
+    <a class="back-link" href="home.html">杩斿洖宸ヤ綔鍙�</a>
+  </div>
+  <div class="card">
+    <select id="status" onchange="load()">
+      <option value="1">寰呭彂璐�</option>
+      <option value="">鍏ㄩ儴</option>
+      <option value="2">寰呮敹璐�</option>
+      <option value="3">寰呰瘎浠�</option>
+      <option value="4">宸插畬鎴�</option>
+    </select>
+    <div id="list"></div>
+  </div>
+</div>
+<script src="app.js"></script>
+<script>
+var names = ["寰呬粯娆�","寰呭彂璐�","寰呮敹璐�","寰呰瘎浠�","浜ゆ槗瀹屾垚","閫�娆句腑","宸查��娆�","宸插叧闂�"];
+function load() {
+  api("/app/order/sorter/orderPage", { page: 1, size: 50, status: $("status").value }).then(function (res) {
+    var list = (res && res.list) || [];
+    if (!list.length) { $("list").innerHTML = "<div class='muted'>鏆傛棤璁㈠崟</div>"; return; }
+    $("list").innerHTML = list.map(function (o) {
+      var goods = (o.goodsList || []).map(function (g) { return (g.title || g.goodsInfo && g.goodsInfo.title || "") + " x" + (g.count || 1); }).join("锛�");
+      var addr = o.address ? ((o.address.contact || "") + " " + (o.address.phone || "") + "<br>" + (o.address.address || o.address.detail || "")) : "";
+      return "<div class='item'><b>" + (o.nickName || "") + "</b> " + (o.orderNum || "") +
+        "<div class='muted'>" + (names[o.status] || o.status) + " 路 锟�" + o.price + "</div>" +
+        "<div class='muted'>" + goods + "</div><div class='muted'>" + addr + "</div>" +
+        (o.status == 1 ? "<button class='btn' onclick='deliver(" + o.id + ")'>鐜板満鍙戞斁</button>" : "") +
+        "</div>";
+    }).join("");
+  }).catch(function (e) { toast(e.message); });
+}
+function deliver(id) {
+  api("/app/order/sorter/deliver", { orderId: id }).then(function () {
+    toast("宸茬幇鍦哄彂鏀�"); load();
+  }).catch(function (e) { toast(e.message); });
+}
+load();
+</script>
+</body>
+</html>
diff --git a/app/src/main/assets/womp/recycle.html b/app/src/main/assets/womp/recycle.html
new file mode 100644
index 0000000..17846d0
--- /dev/null
+++ b/app/src/main/assets/womp/recycle.html
@@ -0,0 +1,152 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+<head>
+  <meta charset="utf-8"/>
+  <meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1"/>
+  <title>鍥炴敹娓呭崟</title>
+  <link rel="stylesheet" href="css.css"/>
+</head>
+<body>
+<div class="wrap">
+  <div class="page-head">
+    <h1 id="title">鐜板満鍥炴敹</h1>
+    <a class="back-link" href="home.html">杩斿洖宸ヤ綔鍙�</a>
+  </div>
+  <div class="card">
+    <div id="userBox" class="muted">璇峰厛鎵弿</div>
+    <button class="btn" id="scanBtn" onclick="doScan()">鎵弿鍥炴敹鐮�</button>
+  </div>
+  <div class="card">
+    <div class="sec-title">閫夋嫨鍝佺被</div>
+    <div id="cats" class="muted">姝e湪鍔犺浇鍝佺被鈥�</div>
+    <label>璁¢噺(kg/浠�)</label>
+    <input id="weight" type="number" step="0.01" oninput="preview()"/>
+    <div id="preview" class="preview">璇烽�夋嫨鍝佺被骞跺~鍐欒閲�</div>
+    <button class="btn ghost" onclick="addItem()">娣诲姞涓�鏉�</button>
+    <div id="list"></div>
+    <div id="totals" class="totals" style="display:none"></div>
+    <button class="btn" onclick="save()">淇濆瓨璁㈠崟</button>
+  </div>
+</div>
+<script src="app.js"></script>
+<script>
+var mode = qs("mode");
+var user = null, appointmentId = null, items = [], catalog = [], selectedId = "";
+if (mode === "apt") {
+  $("title").textContent = "棰勭害鍥炴敹";
+  $("userBox").textContent = "璇锋壂鎻忛绾﹀崟浜岀淮鐮�";
+  $("scanBtn").textContent = "鎵弿棰勭害鍗�";
+}
+api("/app/shop/sorter/itemList", {}).then(function (list) {
+  catalog = list || [];
+  renderCats();
+}).catch(function (e) {
+  $("cats").innerHTML = "<div class='err'>" + (e.message || "鍝佺被鍔犺浇澶辫触") + "</div>";
+});
+
+function renderCats() {
+  if (!catalog.length) { $("cats").innerHTML = "<div class='err'>鏆傛棤鍥炴敹鍝佺被锛岃鍦ㄥ悗鍙扮淮鎶�</div>"; return; }
+  var groups = {};
+  catalog.forEach(function (i) {
+    var g = i.category || "鍏朵粬";
+    if (!groups[g]) groups[g] = [];
+    groups[g].push(i);
+  });
+  $("cats").innerHTML = Object.keys(groups).map(function (g) {
+    return "<div class='muted' style='margin:8px 0 4px'>" + g + "</div><div class='chip-wrap'>" +
+      groups[g].map(function (i) {
+        return "<div class='chip" + (selectedId == i.itemId ? " on" : "") + "' onclick='pick(\"" + i.itemId + "\")'>" +
+          i.itemName + " 锟�" + money(i.price) + "/" + (i.unit || "kg") + "</div>";
+      }).join("") + "</div>";
+  }).join("");
+}
+function pick(id) { selectedId = String(id); renderCats(); preview(); }
+function currentCat() {
+  return catalog.filter(function (i) { return String(i.itemId) === String(selectedId); })[0];
+}
+function preview() {
+  var cat = currentCat();
+  var w = Number($("weight").value);
+  if (!cat) { $("preview").textContent = "璇烽�夋嫨鍝佺被骞跺~鍐欒閲�"; return; }
+  if (!w) {
+    $("preview").innerHTML = "宸查�� <b>" + cat.itemName + "</b>锛屽崟浠� 锟�" + money(cat.price) + "/" + (cat.unit || "kg") +
+      "锛岀⒊绉垎 " + money(cat.carbonPoint) + "/" + (cat.unit || "kg");
+    return;
+  }
+  $("preview").innerHTML = cat.itemName + " 脳 " + w + (cat.unit || "") +
+    "銆�鍗曚环 锟�" + money(cat.price) + "銆�灏忚 锟�" + money(w * cat.price) +
+    "銆�纰崇Н鍒� " + money(w * cat.carbonPoint);
+}
+function doScan() {
+  if (mode === "apt") scanApt();
+  else scanUser();
+}
+function scanUser() {
+  scan().then(function (code) {
+    if (!code) return;
+    return api("/app/shop/sorter/checkUser", { unionid: code });
+  }).then(function (u) {
+    if (!u) return;
+    user = u;
+    $("userBox").innerHTML = userHtml(u, "鍥炴敹鐮佸凡璇嗗埆");
+  }).catch(function (e) { toast(e.message || "涓嶆槸骞冲彴鐢ㄦ埛"); });
+}
+function scanApt() {
+  scan().then(function (code) {
+    if (!code) return;
+    return api("/app/shop/sorter/scanAppointment", { appointmentNo: code });
+  }).then(function (apt) {
+    if (!apt) return;
+    appointmentId = apt.id;
+    if (!apt.campusUser) { toast("棰勭害鍗曠己灏戠敤鎴蜂俊鎭�"); return; }
+    user = apt.campusUser;
+    $("userBox").innerHTML = userHtml(user, "棰勭害鍙� " + (apt.appointmentNo || "") + " 路 " + (apt.contactName || "") + " " + (apt.contactPhone || ""));
+  }).catch(function (e) { toast(e.message || "棰勭害鍗曟棤鏁�"); });
+}
+function addItem() {
+  var cat = currentCat();
+  var w = Number($("weight").value);
+  if (!cat || !w) { toast("璇烽�夋嫨鍝佺被骞跺~鍐欒閲�"); return; }
+  items.push({
+    itemId: cat.itemId,
+    itemName: cat.itemName,
+    unit: cat.unit || "kg",
+    weight: w,
+    unitPrice: Number(cat.price || 0),
+    amount: Number((w * Number(cat.price || 0)).toFixed(2)),
+    carbonPoint: Number((w * Number(cat.carbonPoint || 0)).toFixed(2))
+  });
+  render();
+  $("weight").value = "";
+  preview();
+}
+function render() {
+  $("list").innerHTML = items.map(function (i, idx) {
+    return "<div class='item'><b>" + i.itemName + "</b> " + i.weight + (i.unit || "") +
+      "銆�锟�" + money(i.unitPrice) + "銆�灏忚 锟�" + money(i.amount) +
+      "銆�纰崇Н鍒� " + money(i.carbonPoint) +
+      " <span class='muted' onclick='del(" + idx + ")'>鍒犻櫎</span></div>";
+  }).join("");
+  var tw = 0, ta = 0, tc = 0;
+  items.forEach(function (i) { tw += Number(i.weight); ta += Number(i.amount); tc += Number(i.carbonPoint); });
+  if (!items.length) { $("totals").style.display = "none"; return; }
+  $("totals").style.display = "block";
+  $("totals").innerHTML = "鍚堣璁¢噺 " + money(tw) + "銆�鎬婚噾棰� 锟�" + money(ta) + "銆�鎬荤⒊绉垎 " + money(tc);
+}
+function del(i) { items.splice(i, 1); render(); }
+function save() {
+  if (!user) { toast(mode === "apt" ? "璇峰厛鎵弿棰勭害鍗�" : "璇峰厛鎵弿鐢ㄦ埛"); return; }
+  if (mode === "apt" && !appointmentId) { toast("璇峰厛鎵弿棰勭害鍗�"); return; }
+  if (!items.length) { toast("璇锋坊鍔犳竻鍗�"); return; }
+  api("/app/shop/sorter/saveOrder", {
+    unionid: user.unionid,
+    appointmentId: appointmentId,
+    items: items
+  }).then(function (r) {
+    toast("宸蹭繚瀛� " + r.orderNo);
+    items = []; render();
+  }).catch(function (e) { toast(e.message || "淇濆瓨澶辫触"); });
+}
+</script>
+</body>
+</html>
diff --git a/app/src/main/assets/womp/siteweight.html b/app/src/main/assets/womp/siteweight.html
new file mode 100644
index 0000000..550a852
--- /dev/null
+++ b/app/src/main/assets/womp/siteweight.html
@@ -0,0 +1,65 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+<head>
+  <meta charset="utf-8"/>
+  <meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1"/>
+  <title>鍘嬬缉绔欒閲�</title>
+  <link rel="stylesheet" href="css.css"/>
+</head>
+<body>
+<div class="wrap">
+  <div class="page-head">
+    <h1>鍘嬬缉绔欒閲�</h1>
+    <a class="back-link" href="home.html">杩斿洖宸ヤ綔鍙�</a>
+  </div>
+  <div class="card">
+    <label class="req">鍘嬬缉绔�</label>
+    <select id="businessId"></select>
+    <label>鍨冨溇绫诲瀷</label>
+    <input value="骞插瀮鍦� SW64" disabled/>
+    <label class="req">閲嶉噺(鍚�/鎴栭瀹氳浇鑽�)</label>
+    <input id="weight" type="number" step="0.01"/>
+    <label>澶囨敞</label>
+    <textarea id="remark" rows="3"></textarea>
+    <button class="btn" onclick="save()">涓婃姤</button>
+  </div>
+  <div class="card" id="list"></div>
+</div>
+<script src="app.js"></script>
+<script>
+api("/app/basicdata/options/sites", { siteType: "鍘嬬缉绔�" }).then(function (rows) {
+  if (!(rows || []).length) {
+    $("businessId").innerHTML = "<option value=''>鏆傛棤鏈儴闂ㄥ帇缂╃珯</option>";
+    toast("鏈壘鍒版湰閮ㄩ棬鍘嬬缉绔�");
+    return;
+  }
+  $("businessId").innerHTML = (rows || []).map(function (r) {
+    return "<option value='" + r.businessId + "'>" + r.businessName + "</option>";
+  }).join("");
+}).catch(function (e) { toast(e.message); });
+
+function save() {
+  if (!$("businessId").value) { toast("璇烽�夋嫨鍘嬬缉绔�"); return; }
+  api("/app/push/sorter/siteweightAdd", {
+    businessId: $("businessId").value,
+    deviceNo: "浜哄伐濉姤",
+    garbageType: "SW64",
+    weight: Number($("weight").value),
+    remark: $("remark").value
+  }).then(function () {
+    toast("宸蹭笂鎶�"); $("weight").value = ""; load();
+  }).catch(function (e) { toast(e.message); });
+}
+function load() {
+  api("/app/push/sorter/siteweightPage", { page: 1, size: 30 }).then(function (res) {
+    var list = (res && res.list) || [];
+    $("list").innerHTML = list.length ? list.map(function (r) {
+      return "<div class='item'>" + (r.businessName || r.businessId) + " " + r.weight +
+        "<div class='muted'>" + (r.uploadTime || "") + " 路 " + (r.deviceNo || "") + "</div></div>";
+    }).join("") : "<div class='muted'>鏆傛棤璁板綍</div>";
+  }).catch(function (e) { toast(e.message); });
+}
+load();
+</script>
+</body>
+</html>
diff --git a/app/src/main/assets/womp/wastesale.html b/app/src/main/assets/womp/wastesale.html
new file mode 100644
index 0000000..8263576
--- /dev/null
+++ b/app/src/main/assets/womp/wastesale.html
@@ -0,0 +1,87 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+<head>
+  <meta charset="utf-8"/>
+  <meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1"/>
+  <title>鍙洖鏀剁墿閿�鍞�</title>
+  <link rel="stylesheet" href="css.css"/>
+</head>
+<body>
+<div class="wrap">
+  <div class="page-head">
+    <h1>鍙洖鏀剁墿閿�鍞�</h1>
+    <a class="back-link" href="home.html">杩斿洖宸ヤ綔鍙�</a>
+  </div>
+  <div class="card">
+    <label>鏀堕泦鐐�</label>
+    <select id="siteId"></select>
+    <label class="req">鍨冨溇绉嶇被</label>
+    <select id="wasteCode"></select>
+    <label class="req">鏀惰喘鏂�</label>
+    <select id="buyerCode"></select>
+    <label class="req">閲嶉噺(kg)</label>
+    <input id="weight" type="number" step="0.01" oninput="calc()"/>
+    <label>鍗曚环</label>
+    <input id="unitPrice" type="number" step="0.01" oninput="calc()"/>
+    <div id="subtotal" class="preview">灏忚閲戦 锟�0.00</div>
+    <button class="btn" onclick="save()">淇濆瓨</button>
+  </div>
+  <div class="sec-title">鎴戠殑閿�鍞褰�</div>
+  <div class="card" id="list"></div>
+</div>
+<script src="app.js"></script>
+<script>
+function fill(sel, rows, v, l, placeholder) {
+  var opts = placeholder ? "<option value=''>" + placeholder + "</option>" : "";
+  opts += (rows || []).map(function (r) {
+    return "<option value='" + r[v] + "'>" + r[l] + "</option>";
+  }).join("");
+  $(sel).innerHTML = opts;
+}
+function calc() {
+  var n = Number($("weight").value || 0) * Number($("unitPrice").value || 0);
+  $("subtotal").textContent = "灏忚閲戦 锟�" + money(n);
+}
+Promise.all([
+  api("/app/basicdata/options/sites", { siteType: "鏀堕泦鐐�" }),
+  api("/app/basicdata/options/wastetypes", {}),
+  api("/app/basicdata/options/buyers", {})
+]).then(function (arr) {
+  fill("siteId", arr[0], "businessId", "businessName", "鍙笉閫�");
+  fill("wasteCode", arr[1], "code", "name", "璇烽�夋嫨");
+  fill("buyerCode", arr[2], "code", "name", "璇烽�夋嫨");
+  if (!(arr[2] || []).length) toast("鏆傛棤鏀惰喘鏂癸紝璇峰湪鍚庡彴缁存姢");
+  $("wasteCode").onchange = function () {
+    var w = (arr[1] || []).filter(function (i) { return i.code == $("wasteCode").value; })[0];
+    if (w) $("unitPrice").value = w.price;
+    calc();
+  };
+}).catch(function (e) { toast(e.message); });
+
+function save() {
+  if (!$("buyerCode").value) { toast("璇烽�夋嫨鏀惰喘鏂�"); return; }
+  if (!$("wasteCode").value) { toast("璇烽�夋嫨鍨冨溇绉嶇被"); return; }
+  if (!Number($("weight").value)) { toast("璇峰~鍐欓噸閲�"); return; }
+  api("/app/operation/wastesale/saleAdd", {
+    siteId: $("siteId").value || null,
+    wasteCode: $("wasteCode").value,
+    buyerCode: $("buyerCode").value,
+    weight: Number($("weight").value),
+    unitPrice: Number($("unitPrice").value)
+  }).then(function () {
+    toast("宸蹭繚瀛�"); $("weight").value = ""; calc(); load();
+  }).catch(function (e) { toast(e.message); });
+}
+function load() {
+  api("/app/operation/wastesale/salePage", { page: 1, size: 30 }).then(function (res) {
+    var list = (res && res.list) || [];
+    $("list").innerHTML = list.length ? list.map(function (r) {
+      return "<div class='item'>" + (r.wasteName || r.wasteCode) + " " + r.weight + "kg 路 锟�" + r.totalPrice +
+        "<div class='muted'>" + (r.siteName || "") + " 路 " + (r.buyerName || "") + " 路 " + (r.dateTime || "") + "</div></div>";
+    }).join("") : "<div class='muted'>鏆傛棤璁板綍</div>";
+  }).catch(function (e) { toast(e.message); });
+}
+load();
+</script>
+</body>
+</html>
diff --git a/app/src/main/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactory.java b/app/src/main/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactory.java
new file mode 100644
index 0000000..9566b18
--- /dev/null
+++ b/app/src/main/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactory.java
@@ -0,0 +1,338 @@
+/*
+ * Copyright (C) 2011 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * From 0eb681b, adapted for Gadgetbridge to support null labels.
+ */
+
+package com.google.gson.typeadapters;
+
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
+import com.google.gson.Gson;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonNull;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParseException;
+import com.google.gson.JsonPrimitive;
+import com.google.gson.TypeAdapter;
+import com.google.gson.TypeAdapterFactory;
+import com.google.gson.reflect.TypeToken;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonWriter;
+import java.io.IOException;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * Adapts values whose runtime type may differ from their declaration type. This is necessary when a
+ * field's type is not the same type that GSON should create when deserializing that field. For
+ * example, consider these types:
+ *
+ * <pre>{@code
+ * abstract class Shape {
+ *   int x;
+ *   int y;
+ * }
+ * class Circle extends Shape {
+ *   int radius;
+ * }
+ * class Rectangle extends Shape {
+ *   int width;
+ *   int height;
+ * }
+ * class Diamond extends Shape {
+ *   int width;
+ *   int height;
+ * }
+ * class Drawing {
+ *   Shape bottomShape;
+ *   Shape topShape;
+ * }
+ * }</pre>
+ *
+ * <p>Without additional type information, the serialized JSON is ambiguous. Is the bottom shape in
+ * this drawing a rectangle or a diamond?
+ *
+ * <pre>{@code
+ * {
+ *   "bottomShape": {
+ *     "width": 10,
+ *     "height": 5,
+ *     "x": 0,
+ *     "y": 0
+ *   },
+ *   "topShape": {
+ *     "radius": 2,
+ *     "x": 4,
+ *     "y": 1
+ *   }
+ * }
+ * }</pre>
+ *
+ * This class addresses this problem by adding type information to the serialized JSON and honoring
+ * that type information when the JSON is deserialized:
+ *
+ * <pre>{@code
+ * {
+ *   "bottomShape": {
+ *     "type": "Diamond",
+ *     "width": 10,
+ *     "height": 5,
+ *     "x": 0,
+ *     "y": 0
+ *   },
+ *   "topShape": {
+ *     "type": "Circle",
+ *     "radius": 2,
+ *     "x": 4,
+ *     "y": 1
+ *   }
+ * }
+ * }</pre>
+ *
+ * Both the type field name ({@code "type"}) and the type labels ({@code "Rectangle"}) are
+ * configurable.
+ *
+ * <h2>Registering Types</h2>
+ *
+ * Create a {@code RuntimeTypeAdapterFactory} by passing the base type and type field name to the
+ * {@link #of} factory method. If you don't supply an explicit type field name, {@code "type"} will
+ * be used.
+ *
+ * <pre>{@code
+ * RuntimeTypeAdapterFactory<Shape> shapeAdapterFactory
+ *     = RuntimeTypeAdapterFactory.of(Shape.class, "type");
+ * }</pre>
+ *
+ * Next register all of your subtypes. Every subtype must be explicitly registered. This protects
+ * your application from injection attacks. If you don't supply an explicit type label, the type's
+ * simple name will be used.
+ *
+ * <pre>{@code
+ * shapeAdapterFactory.registerSubtype(Rectangle.class, "Rectangle");
+ * shapeAdapterFactory.registerSubtype(Circle.class, "Circle");
+ * shapeAdapterFactory.registerSubtype(Diamond.class, "Diamond");
+ * }</pre>
+ *
+ * Finally, register the type adapter factory in your application's GSON builder:
+ *
+ * <pre>{@code
+ * Gson gson = new GsonBuilder()
+ *     .registerTypeAdapterFactory(shapeAdapterFactory)
+ *     .create();
+ * }</pre>
+ *
+ * Like {@code GsonBuilder}, this API supports chaining:
+ *
+ * <pre>{@code
+ * RuntimeTypeAdapterFactory<Shape> shapeAdapterFactory = RuntimeTypeAdapterFactory.of(Shape.class)
+ *     .registerSubtype(Rectangle.class)
+ *     .registerSubtype(Circle.class)
+ *     .registerSubtype(Diamond.class);
+ * }</pre>
+ *
+ * <h2>Serialization and deserialization</h2>
+ *
+ * In order to serialize and deserialize a polymorphic object, you must specify the base type
+ * explicitly.
+ *
+ * <pre>{@code
+ * Diamond diamond = new Diamond();
+ * String json = gson.toJson(diamond, Shape.class);
+ * }</pre>
+ *
+ * And then:
+ *
+ * <pre>{@code
+ * Shape shape = gson.fromJson(json, Shape.class);
+ * }</pre>
+ */
+public final class RuntimeTypeAdapterFactory<T> implements TypeAdapterFactory {
+    private final Class<?> baseType;
+    private final String typeFieldName;
+    private final Map<String, Class<?>> labelToSubtype = new LinkedHashMap<>();
+    private final Map<Class<?>, String> subtypeToLabel = new LinkedHashMap<>();
+    private final boolean maintainType;
+    private boolean recognizeSubtypes;
+
+    private RuntimeTypeAdapterFactory(Class<?> baseType, String typeFieldName, boolean maintainType) {
+        if (typeFieldName == null || baseType == null) {
+            throw new NullPointerException();
+        }
+        this.baseType = baseType;
+        this.typeFieldName = typeFieldName;
+        this.maintainType = maintainType;
+    }
+
+    /**
+     * Creates a new runtime type adapter for {@code baseType} using {@code typeFieldName} as the type
+     * field name. Type field names are case sensitive.
+     *
+     * @param maintainType true if the type field should be included in deserialized objects
+     */
+    public static <T> RuntimeTypeAdapterFactory<T> of(
+            Class<T> baseType, String typeFieldName, boolean maintainType) {
+        return new RuntimeTypeAdapterFactory<>(baseType, typeFieldName, maintainType);
+    }
+
+    /**
+     * Creates a new runtime type adapter for {@code baseType} using {@code typeFieldName} as the type
+     * field name. Type field names are case sensitive.
+     */
+    public static <T> RuntimeTypeAdapterFactory<T> of(Class<T> baseType, String typeFieldName) {
+        return new RuntimeTypeAdapterFactory<>(baseType, typeFieldName, false);
+    }
+
+    /**
+     * Creates a new runtime type adapter for {@code baseType} using {@code "type"} as the type field
+     * name.
+     */
+    public static <T> RuntimeTypeAdapterFactory<T> of(Class<T> baseType) {
+        return new RuntimeTypeAdapterFactory<>(baseType, "type", false);
+    }
+
+    /**
+     * Ensures that this factory will handle not just the given {@code baseType}, but any subtype of
+     * that type.
+     */
+    @CanIgnoreReturnValue
+    public RuntimeTypeAdapterFactory<T> recognizeSubtypes() {
+        this.recognizeSubtypes = true;
+        return this;
+    }
+
+    /**
+     * Registers {@code type} identified by {@code label}. Labels are case sensitive.
+     *
+     * @throws IllegalArgumentException if either {@code type} or {@code label} have already been
+     *     registered on this type adapter.
+     */
+    @CanIgnoreReturnValue
+    public RuntimeTypeAdapterFactory<T> registerSubtype(Class<? extends T> type, String label) {
+        if (type == null) {
+            throw new NullPointerException();
+        }
+        if (subtypeToLabel.containsKey(type) || labelToSubtype.containsKey(label)) {
+            throw new IllegalArgumentException("types and labels must be unique");
+        }
+        labelToSubtype.put(label, type);
+        subtypeToLabel.put(type, label);
+        return this;
+    }
+
+    /**
+     * Registers {@code type} identified by its {@link Class#getSimpleName simple name}. Labels are
+     * case sensitive.
+     *
+     * @throws IllegalArgumentException if either {@code type} or its simple name have already been
+     *     registered on this type adapter.
+     */
+    @CanIgnoreReturnValue
+    public RuntimeTypeAdapterFactory<T> registerSubtype(Class<? extends T> type) {
+        return registerSubtype(type, type.getSimpleName());
+    }
+
+    @Override
+    public <R> TypeAdapter<R> create(Gson gson, TypeToken<R> type) {
+        if (type == null) {
+            return null;
+        }
+        Class<?> rawType = type.getRawType();
+        boolean handle =
+                recognizeSubtypes ? baseType.isAssignableFrom(rawType) : baseType.equals(rawType);
+        if (!handle) {
+            return null;
+        }
+
+        final TypeAdapter<JsonElement> jsonElementAdapter = gson.getAdapter(JsonElement.class);
+        final Map<String, TypeAdapter<?>> labelToDelegate = new LinkedHashMap<>();
+        final Map<Class<?>, TypeAdapter<?>> subtypeToDelegate = new LinkedHashMap<>();
+        for (Map.Entry<String, Class<?>> entry : labelToSubtype.entrySet()) {
+            TypeAdapter<?> delegate = gson.getDelegateAdapter(this, TypeToken.get(entry.getValue()));
+            labelToDelegate.put(entry.getKey(), delegate);
+            subtypeToDelegate.put(entry.getValue(), delegate);
+        }
+
+        return new TypeAdapter<R>() {
+            @Override
+            public R read(JsonReader in) throws IOException {
+                JsonElement jsonElement = jsonElementAdapter.read(in);
+                JsonElement labelJsonElement;
+                if (maintainType) {
+                    labelJsonElement = jsonElement.getAsJsonObject().get(typeFieldName);
+                } else {
+                    labelJsonElement = jsonElement.getAsJsonObject().remove(typeFieldName);
+                }
+
+                if (labelJsonElement == null && !labelToDelegate.containsKey(null)) {
+                    throw new JsonParseException(
+                            "cannot deserialize "
+                                    + baseType
+                                    + " because it does not define a field named "
+                                    + typeFieldName);
+                }
+                String label = labelJsonElement != null ? labelJsonElement.getAsString() : null;
+                @SuppressWarnings("unchecked") // registration requires that subtype extends T
+                TypeAdapter<R> delegate = (TypeAdapter<R>) labelToDelegate.get(label);
+                if (delegate == null) {
+                    throw new JsonParseException(
+                            "cannot deserialize "
+                                    + baseType
+                                    + " subtype named "
+                                    + label
+                                    + "; did you forget to register a subtype?");
+                }
+                return delegate.fromJsonTree(jsonElement);
+            }
+
+            @Override
+            public void write(JsonWriter out, R value) throws IOException {
+                Class<?> srcType = value.getClass();
+                String label = subtypeToLabel.get(srcType);
+                @SuppressWarnings("unchecked") // registration requires that subtype extends T
+                TypeAdapter<R> delegate = (TypeAdapter<R>) subtypeToDelegate.get(srcType);
+                if (delegate == null) {
+                    throw new JsonParseException(
+                            "cannot serialize " + srcType.getName() + "; did you forget to register a subtype?");
+                }
+                JsonObject jsonObject = delegate.toJsonTree(value).getAsJsonObject();
+
+                if (maintainType) {
+                    jsonElementAdapter.write(out, jsonObject);
+                    return;
+                }
+
+                JsonObject clone = new JsonObject();
+
+                if (jsonObject.has(typeFieldName)) {
+                    throw new JsonParseException(
+                            "cannot serialize "
+                                    + srcType.getName()
+                                    + " because it already defines a field named "
+                                    + typeFieldName);
+                }
+                if (label != null) {
+                    clone.add(typeFieldName, new JsonPrimitive(label));
+                } else {
+                    clone.add(typeFieldName, JsonNull.INSTANCE);
+                }
+
+                for (Map.Entry<String, JsonElement> e : jsonObject.entrySet()) {
+                    clone.add(e.getKey(), e.getValue());
+                }
+                jsonElementAdapter.write(out, clone);
+            }
+        }.nullSafe();
+    }
+}
diff --git a/app/src/main/java/com/mobeta/android/dslv/CheckableLinearLayout.java b/app/src/main/java/com/mobeta/android/dslv/CheckableLinearLayout.java
new file mode 100644
index 0000000..1c3c716
--- /dev/null
+++ b/app/src/main/java/com/mobeta/android/dslv/CheckableLinearLayout.java
@@ -0,0 +1,38 @@
+package com.mobeta.android.dslv;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.Checkable;
+import android.widget.LinearLayout;
+
+public class CheckableLinearLayout extends LinearLayout implements Checkable {
+
+    private static final int CHECKABLE_CHILD_INDEX = 0;
+    private Checkable child;
+
+    public CheckableLinearLayout(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        child = (Checkable) getChildAt(CHECKABLE_CHILD_INDEX);
+    }
+
+    @Override
+    public boolean isChecked() {
+        return child.isChecked(); 
+    }
+
+    @Override
+    public void setChecked(boolean checked) {
+        child.setChecked(checked);
+    }
+
+    @Override
+    public void toggle() {
+        child.toggle();
+    }
+    
+}
diff --git a/app/src/main/java/com/mobeta/android/dslv/DragSortController.java b/app/src/main/java/com/mobeta/android/dslv/DragSortController.java
new file mode 100644
index 0000000..06f91cd
--- /dev/null
+++ b/app/src/main/java/com/mobeta/android/dslv/DragSortController.java
@@ -0,0 +1,474 @@
+package com.mobeta.android.dslv;
+
+import android.graphics.Point;
+import android.view.GestureDetector;
+import android.view.HapticFeedbackConstants;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.widget.AdapterView;
+
+/**
+ * Class that starts and stops item drags on a {@link DragSortListView}
+ * based on touch gestures. This class also inherits from
+ * {@link SimpleFloatViewManager}, which provides basic float View
+ * creation.
+ *
+ * An instance of this class is meant to be passed to the methods
+ * {@link DragSortListView#setTouchListener()} and
+ * {@link DragSortListView#setFloatViewManager()} of your
+ * {@link DragSortListView} instance.
+ */
+public class DragSortController extends SimpleFloatViewManager implements View.OnTouchListener, 
+		GestureDetector.OnGestureListener {
+
+    /**
+     * Drag init mode enum.
+     */
+    public static final int ON_DOWN = 0;
+    public static final int ON_DRAG = 1;
+    public static final int ON_LONG_PRESS = 2;
+
+    private int mDragInitMode = ON_DOWN;
+
+    private boolean mSortEnabled = true;
+
+    /**
+     * Remove mode enum.
+     */
+    public static final int CLICK_REMOVE = 0;
+    public static final int FLING_REMOVE = 1;
+
+    /**
+     * The current remove mode.
+     */
+    private int mRemoveMode;
+
+    private boolean mRemoveEnabled = false;
+    private boolean mIsRemoving = false;
+
+    private GestureDetector mDetector;
+
+    private GestureDetector mFlingRemoveDetector;
+
+    private int mTouchSlop;
+
+    public static final int MISS = -1;
+
+    private int mHitPos = MISS;
+    private int mFlingHitPos = MISS;
+
+    private int mClickRemoveHitPos = MISS;
+
+    private int[] mTempLoc = new int[2];
+
+    private int mItemX;
+    private int mItemY;
+
+    private int mCurrX;
+    private int mCurrY;
+
+    private boolean mDragging = false;
+
+    private float mFlingSpeed = 500f;
+
+    private int mDragHandleId;
+
+    private int mClickRemoveId;
+
+    private int mFlingHandleId;
+    private boolean mCanDrag;
+
+    private DragSortListView mDslv;
+    private int mPositionX;
+
+    /**
+     * Calls {@link #DragSortController(DragSortListView, int)} with a
+     * 0 drag handle id, FLING_RIGHT_REMOVE remove mode,
+     * and ON_DOWN drag init. By default, sorting is enabled, and
+     * removal is disabled.
+     *
+     * @param dslv The DSLV instance
+     */
+    public DragSortController(DragSortListView dslv) {
+        this(dslv, 0, ON_DOWN, FLING_REMOVE);
+    }
+
+    public DragSortController(DragSortListView dslv, int dragHandleId, int dragInitMode, 
+            int removeMode) {
+
+        this(dslv, dragHandleId, dragInitMode, removeMode, 0);
+    }
+
+    public DragSortController(DragSortListView dslv, int dragHandleId, int dragInitMode, 
+            int removeMode, int clickRemoveId) {
+
+        this(dslv, dragHandleId, dragInitMode, removeMode, clickRemoveId, 0);
+    }
+
+    /**
+     * By default, sorting is enabled, and removal is disabled.
+     *
+     * @param dslv The DSLV instance
+     * @param dragHandleId The resource id of the View that represents
+     * the drag handle in a list item.
+     */
+    public DragSortController(DragSortListView dslv, int dragHandleId, int dragInitMode,
+            int removeMode, int clickRemoveId, int flingHandleId) {
+
+        super(dslv);
+        mDslv = dslv;
+        mDetector = new GestureDetector(dslv.getContext(), this);
+        mFlingRemoveDetector = new GestureDetector(dslv.getContext(), mFlingRemoveListener);
+        mFlingRemoveDetector.setIsLongpressEnabled(false);
+        mTouchSlop = ViewConfiguration.get(dslv.getContext()).getScaledTouchSlop();
+        mDragHandleId = dragHandleId;
+        mClickRemoveId = clickRemoveId;
+        mFlingHandleId = flingHandleId;
+        setRemoveMode(removeMode);
+        setDragInitMode(dragInitMode);
+    }
+
+
+    public int getDragInitMode() {
+        return mDragInitMode;
+    }
+
+    /**
+     * Set how a drag is initiated. Needs to be one of
+     * {@link ON_DOWN}, {@link ON_DRAG}, or {@link ON_LONG_PRESS}.
+     *
+     * @param mode The drag init mode.
+     */
+    public void setDragInitMode(int mode) {
+        mDragInitMode = mode;
+    }
+
+    /**
+     * Enable/Disable list item sorting. Disabling is useful if only item
+     * removal is desired. Prevents drags in the vertical direction.
+     *
+     * @param enabled Set <code>true</code> to enable list
+     * item sorting.
+     */
+    public void setSortEnabled(boolean enabled) {
+        mSortEnabled = enabled;
+    }
+
+    public boolean isSortEnabled() {
+        return mSortEnabled;
+    }
+
+    /**
+     * One of {@link CLICK_REMOVE}, {@link FLING_RIGHT_REMOVE},
+     * {@link FLING_LEFT_REMOVE},
+     * {@link SLIDE_RIGHT_REMOVE}, or {@link SLIDE_LEFT_REMOVE}.
+     */
+    public void setRemoveMode(int mode) {
+        mRemoveMode = mode;
+    }
+
+    public int getRemoveMode() {
+        return mRemoveMode;
+    }
+
+    /**
+     * Enable/Disable item removal without affecting remove mode.
+     */
+    public void setRemoveEnabled(boolean enabled) {
+        mRemoveEnabled = enabled;
+    }
+
+    public boolean isRemoveEnabled() {
+        return mRemoveEnabled;
+    }
+
+    /**
+     * Set the resource id for the View that represents the drag
+     * handle in a list item.
+     *
+     * @param id An android resource id.
+     */
+    public void setDragHandleId(int id) {
+        mDragHandleId = id;
+    }
+
+    /**
+     * Set the resource id for the View that represents the fling
+     * handle in a list item.
+     *
+     * @param id An android resource id.
+     */
+    public void setFlingHandleId(int id) {
+        mFlingHandleId = id;
+    }
+
+    /**
+     * Set the resource id for the View that represents click
+     * removal button.
+     *
+     * @param id An android resource id.
+     */
+    public void setClickRemoveId(int id) {
+        mClickRemoveId = id;
+    }
+
+    /**
+     * Sets flags to restrict certain motions of the floating View
+     * based on DragSortController settings (such as remove mode).
+     * Starts the drag on the DragSortListView.
+     *
+     * @param position The list item position (includes headers).
+     * @param deltaX Touch x-coord minus left edge of floating View.
+     * @param deltaY Touch y-coord minus top edge of floating View.
+     *
+     * @return True if drag started, false otherwise.
+     */
+    public boolean startDrag(int position, int deltaX, int deltaY) {
+
+        int dragFlags = 0;
+        if (mSortEnabled && !mIsRemoving) {
+            dragFlags |= DragSortListView.DRAG_POS_Y | DragSortListView.DRAG_NEG_Y;
+        }
+        if (mRemoveEnabled && mIsRemoving) {
+            dragFlags |= DragSortListView.DRAG_POS_X;
+            dragFlags |= DragSortListView.DRAG_NEG_X;
+        }
+
+        mDragging = mDslv.startDrag(position - mDslv.getHeaderViewsCount(), dragFlags, deltaX,
+                deltaY);
+        return mDragging;
+    }
+
+    @Override
+    public boolean onTouch(View v, MotionEvent ev) {
+        if (!mDslv.isDragEnabled() || mDslv.listViewIntercepted()) {
+            return false;
+        }
+
+        mDetector.onTouchEvent(ev);
+        if (mRemoveEnabled && mDragging && mRemoveMode == FLING_REMOVE) {
+            mFlingRemoveDetector.onTouchEvent(ev);
+        }
+
+        int action = ev.getAction() & MotionEvent.ACTION_MASK;
+        switch (action) {
+            case MotionEvent.ACTION_DOWN:
+                mCurrX = (int) ev.getX();
+                mCurrY = (int) ev.getY();
+                break;
+            case MotionEvent.ACTION_UP:
+                if (mRemoveEnabled && mIsRemoving) {
+                    int x = mPositionX >= 0 ? mPositionX : -mPositionX;
+                    int removePoint = mDslv.getWidth() / 2;
+                    if (x > removePoint) {
+                        mDslv.stopDragWithVelocity(true, 0);
+                    }
+                }
+            case MotionEvent.ACTION_CANCEL:
+                mIsRemoving = false;
+                mDragging = false;
+                break;
+        }
+
+        return false;
+    }
+
+    /**
+     * Overrides to provide fading when slide removal is enabled.
+     */
+    @Override
+    public void onDragFloatView(View floatView, Point position, Point touch) {
+
+        if (mRemoveEnabled && mIsRemoving) {
+            mPositionX = position.x;
+        }
+    }
+
+    /**
+     * Get the position to start dragging based on the ACTION_DOWN
+     * MotionEvent. This function simply calls
+     * {@link #dragHandleHitPosition(MotionEvent)}. Override
+     * to change drag handle behavior;
+     * this function is called internally when an ACTION_DOWN
+     * event is detected.
+     *
+     * @param ev The ACTION_DOWN MotionEvent.
+     *
+     * @return The list position to drag if a drag-init gesture is
+     * detected; MISS if unsuccessful.
+     */
+    public int startDragPosition(MotionEvent ev) {
+        return dragHandleHitPosition(ev);
+    }
+
+    public int startFlingPosition(MotionEvent ev) {
+        return mRemoveMode == FLING_REMOVE ? flingHandleHitPosition(ev) : MISS;
+    }
+
+    /**
+     * Checks for the touch of an item's drag handle (specified by
+     * {@link #setDragHandleId(int)}), and returns that item's position
+     * if a drag handle touch was detected.
+     *
+     * @param ev The ACTION_DOWN MotionEvent.
+     * @return The list position of the item whose drag handle was
+     * touched; MISS if unsuccessful.
+     */
+    public int dragHandleHitPosition(MotionEvent ev) {
+        return viewIdHitPosition(ev, mDragHandleId);
+    }
+
+    public int flingHandleHitPosition(MotionEvent ev) {
+        return viewIdHitPosition(ev, mFlingHandleId);
+    }
+
+    public int viewIdHitPosition(MotionEvent ev, int id) {
+        final int x = (int) ev.getX();
+        final int y = (int) ev.getY();
+
+        int touchPos = mDslv.pointToPosition(x, y); // includes headers/footers
+
+        final int numHeaders = mDslv.getHeaderViewsCount();
+        final int numFooters = mDslv.getFooterViewsCount();
+        final int count = mDslv.getCount();
+
+        // We're only interested if the touch was on an
+        // item that's not a header or footer.
+        if (touchPos != AdapterView.INVALID_POSITION && touchPos >= numHeaders
+                && touchPos < (count - numFooters)) {
+            final View item = mDslv.getChildAt(touchPos - mDslv.getFirstVisiblePosition());
+            final int rawX = (int) ev.getRawX();
+            final int rawY = (int) ev.getRawY();
+
+            View dragBox = id == 0 ? item : (View) item.findViewById(id);
+            if (dragBox != null) {
+                dragBox.getLocationOnScreen(mTempLoc);
+
+                if (rawX > mTempLoc[0] && rawY > mTempLoc[1] &&
+                        rawX < mTempLoc[0] + dragBox.getWidth() &&
+                        rawY < mTempLoc[1] + dragBox.getHeight()) {
+
+                    mItemX = item.getLeft();
+                    mItemY = item.getTop();
+
+                    return touchPos;
+                }
+            }
+        }
+
+        return MISS;
+    }
+
+    @Override
+    public boolean onDown(MotionEvent ev) {
+        if (mRemoveEnabled && mRemoveMode == CLICK_REMOVE) {
+            mClickRemoveHitPos = viewIdHitPosition(ev, mClickRemoveId);
+        }
+
+        mHitPos = startDragPosition(ev);
+        if (mHitPos != MISS && mDragInitMode == ON_DOWN) {
+            startDrag(mHitPos, (int) ev.getX() - mItemX, (int) ev.getY() - mItemY);
+        }
+
+        mIsRemoving = false;
+        mCanDrag = true;
+        mPositionX = 0;
+        mFlingHitPos = startFlingPosition(ev);
+
+        return true;
+    }
+
+    @Override
+    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
+
+        if (e1 == null || e2 == null) {
+            return false;
+        }
+
+        final int x1 = (int) e1.getX();
+        final int y1 = (int) e1.getY();
+        final int x2 = (int) e2.getX();
+        final int y2 = (int) e2.getY();
+        final int deltaX = x2 - mItemX;
+        final int deltaY = y2 - mItemY;
+
+        if (mCanDrag && !mDragging && (mHitPos != MISS || mFlingHitPos != MISS)) {
+            if (mHitPos != MISS) {
+                if (mDragInitMode == ON_DRAG && Math.abs(y2 - y1) > mTouchSlop && mSortEnabled) {
+                    startDrag(mHitPos, deltaX, deltaY);
+                }
+                else if (mDragInitMode != ON_DOWN && Math.abs(x2 - x1) > mTouchSlop 
+                        && mRemoveEnabled)
+                {
+                    mIsRemoving = true;
+                    startDrag(mFlingHitPos, deltaX, deltaY);
+                }
+            } else if (mFlingHitPos != MISS) {
+                if (Math.abs(x2 - x1) > mTouchSlop && mRemoveEnabled) {
+                    mIsRemoving = true;
+                    startDrag(mFlingHitPos, deltaX, deltaY);
+                } else if (Math.abs(y2 - y1) > mTouchSlop) {
+                    mCanDrag = false; // if started to scroll the list then
+                                      // don't allow sorting nor fling-removing
+                }
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public void onLongPress(MotionEvent e) {
+        if (mHitPos != MISS && mDragInitMode == ON_LONG_PRESS) {
+            mDslv.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
+            startDrag(mHitPos, mCurrX - mItemX, mCurrY - mItemY);
+        }
+    }
+
+    // complete the OnGestureListener interface
+    @Override
+    public final boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
+        return false;
+    }
+
+    // complete the OnGestureListener interface
+    @Override
+    public boolean onSingleTapUp(MotionEvent ev) {
+        if (mRemoveEnabled && mRemoveMode == CLICK_REMOVE) {
+            if (mClickRemoveHitPos != MISS) {
+                mDslv.removeItem(mClickRemoveHitPos - mDslv.getHeaderViewsCount());
+            }
+        }
+        return true;
+    }
+
+    // complete the OnGestureListener interface
+    @Override
+    public void onShowPress(MotionEvent ev) {
+        // do nothing
+    }
+
+    private GestureDetector.OnGestureListener mFlingRemoveListener =
+            new GestureDetector.SimpleOnGestureListener() {
+                @Override
+                public final boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
+                        float velocityY) {
+                    if (mRemoveEnabled && mIsRemoving) {
+                        int w = mDslv.getWidth();
+                        int minPos = w / 5;
+                        if (velocityX > mFlingSpeed) {
+                            if (mPositionX > -minPos) {
+                                mDslv.stopDragWithVelocity(true, velocityX);
+                            }
+                        } else if (velocityX < -mFlingSpeed) {
+                            if (mPositionX < minPos) {
+                                mDslv.stopDragWithVelocity(true, velocityX);
+                            }
+                        }
+                        mIsRemoving = false;
+                    }
+                    return false;
+                }
+            };
+
+}
diff --git a/app/src/main/java/com/mobeta/android/dslv/DragSortCursorAdapter.java b/app/src/main/java/com/mobeta/android/dslv/DragSortCursorAdapter.java
new file mode 100644
index 0000000..41454c7
--- /dev/null
+++ b/app/src/main/java/com/mobeta/android/dslv/DragSortCursorAdapter.java
@@ -0,0 +1,233 @@
+package com.mobeta.android.dslv;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import android.content.Context;
+import android.database.Cursor;
+import androidx.cursoradapter.widget.CursorAdapter;
+import android.util.SparseIntArray;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ListAdapter;
+
+
+/**
+ * A subclass of {@link android.widget.CursorAdapter} that provides
+ * reordering of the elements in the Cursor based on completed
+ * drag-sort operations. The reordering is a simple mapping of
+ * list positions into Cursor positions (the Cursor is unchanged).
+ * To persist changes made by drag-sorts, one can retrieve the
+ * mapping with the {@link #getCursorPositions()} method, which
+ * returns the reordered list of Cursor positions.
+ *
+ * An instance of this class is passed
+ * to {@link DragSortListView#setAdapter(ListAdapter)} and, since
+ * this class implements the {@link DragSortListView.DragSortListener}
+ * interface, it is automatically set as the DragSortListener for
+ * the DragSortListView instance.
+ */
+public abstract class DragSortCursorAdapter extends CursorAdapter implements 
+        DragSortListView.DragSortListener {
+
+    private static final int REMOVED = -1;
+
+    /**
+     * Key is ListView position, value is Cursor position
+     */
+    private SparseIntArray mListMapping = new SparseIntArray();
+
+    private List<Integer> mRemovedCursorPositions = new ArrayList<Integer>();
+    
+    @Deprecated
+    public DragSortCursorAdapter(Context context, Cursor c) {
+        super(context, c);
+    }
+
+    public DragSortCursorAdapter(Context context, Cursor c, boolean autoRequery) {
+        super(context, c, autoRequery);
+    }
+
+    public DragSortCursorAdapter(Context context, Cursor c, int flags) {
+        super(context, c, flags);
+    }
+
+    /**
+     * Swaps Cursor and clears list-Cursor mapping.
+     *
+     * @see android.widget.CursorAdapter#swapCursor(android.database.Cursor)
+     */
+    @Override
+    public Cursor swapCursor(Cursor newCursor) {
+        Cursor old = super.swapCursor(newCursor);
+        resetMappings();
+        return old;
+    }
+
+    /**
+     * Resets list-cursor mapping.
+     */
+    public void reset() {
+        resetMappings();
+        notifyDataSetChanged();
+    }
+
+    private void resetMappings() {
+        mListMapping.clear();
+        mRemovedCursorPositions.clear();
+    }
+
+    @Override
+    public Object getItem(int position) {
+        return super.getItem(mListMapping.get(position, position));
+    }
+
+    @Override
+    public long getItemId(int position) {
+        return super.getItemId(mListMapping.get(position, position));
+    }
+
+    @Override
+    public View getDropDownView(int position, View convertView, ViewGroup parent) {
+        return super.getDropDownView(mListMapping.get(position, position), convertView, parent);
+    }
+
+    @Override
+    public View getView(int position, View convertView, ViewGroup parent) {
+        return super.getView(mListMapping.get(position, position), convertView, parent);
+    }
+
+    /**
+     * On drop, this updates the mapping between Cursor positions
+     * and ListView positions. The Cursor is unchanged. Retrieve
+     * the current mapping with {@link getCursorPositions()}.
+     *
+     * @see DragSortListView.DropListener#drop(int, int)
+     */
+    @Override
+    public void drop(int from, int to) {
+        if (from != to) {
+            int cursorFrom = mListMapping.get(from, from);
+
+            if (from > to) {
+                for (int i = from; i > to; i--) {
+                    mListMapping.put(i, mListMapping.get(i - 1, i - 1));
+                }
+            } else {
+                for (int i = from; i < to; i++) {
+                    mListMapping.put(i, mListMapping.get(i + 1, i + 1));
+                }
+            }
+            mListMapping.put(to, cursorFrom);
+
+            cleanMapping();        
+            notifyDataSetChanged();
+        }
+    }
+
+    /**
+     * On remove, this updates the mapping between Cursor positions
+     * and ListView positions. The Cursor is unchanged. Retrieve
+     * the current mapping with {@link getCursorPositions()}.
+     *
+     * @see DragSortListView.RemoveListener#remove(int)
+     */
+    @Override
+    public void remove(int which) {
+        int cursorPos = mListMapping.get(which, which);
+        if (!mRemovedCursorPositions.contains(cursorPos)) {
+            mRemovedCursorPositions.add(cursorPos);
+        }
+
+        int newCount = getCount();
+        for (int i = which; i < newCount; i++) {
+            mListMapping.put(i, mListMapping.get(i + 1, i + 1));
+        }
+
+        mListMapping.delete(newCount);
+
+        cleanMapping();
+        notifyDataSetChanged();
+    }
+
+    /**
+     * Does nothing. Just completes DragSortListener interface.
+     */
+    @Override
+    public void drag(int from, int to) {
+        // do nothing
+    }
+
+    /**
+     * Remove unnecessary mappings from sparse array.
+     */
+    private void cleanMapping() {
+        List<Integer> toRemove = new ArrayList<Integer>();
+
+        final int size = mListMapping.size();
+        for (int i = 0; i < size; i++) {
+            if (mListMapping.keyAt(i) == mListMapping.valueAt(i)) {
+                toRemove.add(mListMapping.keyAt(i));
+            }
+        }
+
+        for (int position : toRemove) {
+        	mListMapping.delete(position);
+        }
+    }
+
+    @Override
+    public int getCount() {
+        return super.getCount() - mRemovedCursorPositions.size();
+    }
+
+    /**
+     * Get the Cursor position mapped to by the provided list position
+     * (given all previously handled drag-sort
+     * operations).
+     *
+     * @param position List position
+     *
+     * @return The mapped-to Cursor position
+     */
+    public int getCursorPosition(int position) {
+        return mListMapping.get(position, position);
+    }
+
+    /**
+     * Get the current order of Cursor positions presented by the
+     * list.
+     */
+    public List<Integer> getCursorPositions() {
+        List<Integer> result = new ArrayList<Integer>();
+
+        for (int i = 0; i < getCount(); i++) {
+            result.add(mListMapping.get(i, i));
+        }
+
+        return result;
+    }
+
+    /**
+     * Get the list position mapped to by the provided Cursor position.
+     * If the provided Cursor position has been removed by a drag-sort,
+     * this returns {@link #REMOVED}.
+     *
+     * @param cursorPosition A Cursor position
+     * @return The mapped-to list position or REMOVED
+     */
+    public int getListPosition(int cursorPosition) {
+        if (mRemovedCursorPositions.contains(cursorPosition)) {
+            return REMOVED;
+        }
+
+        int index = mListMapping.indexOfValue(cursorPosition);
+        if (index < 0) {
+            return cursorPosition;
+        } else {
+            return mListMapping.keyAt(index);
+        }
+    }
+
+
+}
diff --git a/app/src/main/java/com/mobeta/android/dslv/DragSortItemView.java b/app/src/main/java/com/mobeta/android/dslv/DragSortItemView.java
new file mode 100644
index 0000000..c8f0359
--- /dev/null
+++ b/app/src/main/java/com/mobeta/android/dslv/DragSortItemView.java
@@ -0,0 +1,95 @@
+package com.mobeta.android.dslv;
+
+import android.content.Context;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AbsListView;
+
+/**
+ * Lightweight ViewGroup that wraps list items obtained from user's
+ * ListAdapter. ItemView expects a single child that has a definite
+ * height (i.e. the child's layout height is not MATCH_PARENT).
+ * The width of
+ * ItemView will always match the width of its child (that is,
+ * the width MeasureSpec given to ItemView is passed directly
+ * to the child, and the ItemView measured width is set to the
+ * child's measured width). The height of ItemView can be anything;
+ * the 
+ * 
+ *
+ * The purpose of this class is to optimize slide
+ * shuffle animations.
+ */
+public class DragSortItemView extends ViewGroup {
+
+    private int mGravity = Gravity.TOP;
+
+    public DragSortItemView(Context context) {
+        super(context);
+
+        // always init with standard ListView layout params
+        setLayoutParams(new AbsListView.LayoutParams(
+                ViewGroup.LayoutParams.MATCH_PARENT,
+                ViewGroup.LayoutParams.WRAP_CONTENT));
+
+        //setClipChildren(true);
+    }
+
+    public void setGravity(int gravity) {
+        mGravity = gravity;
+    }
+
+    public int getGravity() {
+        return mGravity;
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        final View child = getChildAt(0);
+
+        if (child == null) {
+            return;
+        }
+
+        if (mGravity == Gravity.TOP) {
+            child.layout(0, 0, getMeasuredWidth(), child.getMeasuredHeight());
+        } else {
+            child.layout(0, getMeasuredHeight() - child.getMeasuredHeight(), 
+            		getMeasuredWidth(), getMeasuredHeight());
+        }
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        
+        int height = MeasureSpec.getSize(heightMeasureSpec);
+        int width = MeasureSpec.getSize(widthMeasureSpec);
+
+        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+
+        final View child = getChildAt(0);
+        if (child == null) {
+            setMeasuredDimension(0, width);
+            return;
+        }
+
+        if (child.isLayoutRequested()) {
+            // Always let child be as tall as it wants.
+            measureChild(child, widthMeasureSpec,
+                    MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
+        }
+
+        if (heightMode == MeasureSpec.UNSPECIFIED) {
+            ViewGroup.LayoutParams lp = getLayoutParams();
+
+            if (lp.height > 0) {
+                height = lp.height;
+            } else {
+                height = child.getMeasuredHeight();
+            }
+        }
+
+        setMeasuredDimension(width, height);
+    }
+
+}
diff --git a/app/src/main/java/com/mobeta/android/dslv/DragSortItemViewCheckable.java b/app/src/main/java/com/mobeta/android/dslv/DragSortItemViewCheckable.java
new file mode 100644
index 0000000..9f19c0a
--- /dev/null
+++ b/app/src/main/java/com/mobeta/android/dslv/DragSortItemViewCheckable.java
@@ -0,0 +1,53 @@
+package com.mobeta.android.dslv;
+
+import android.content.Context;
+import android.view.View;
+import android.widget.Checkable;
+
+/**
+ * Lightweight ViewGroup that wraps list items obtained from user's
+ * ListAdapter. ItemView expects a single child that has a definite
+ * height (i.e. the child's layout height is not MATCH_PARENT).
+ * The width of
+ * ItemView will always match the width of its child (that is,
+ * the width MeasureSpec given to ItemView is passed directly
+ * to the child, and the ItemView measured width is set to the
+ * child's measured width). The height of ItemView can be anything;
+ * the 
+ * 
+ *
+ * The purpose of this class is to optimize slide
+ * shuffle animations.
+ */
+public class DragSortItemViewCheckable extends DragSortItemView implements Checkable {
+
+    public DragSortItemViewCheckable(Context context) {
+        super(context);
+    }
+
+    @Override
+    public boolean isChecked() {
+        View child = getChildAt(0);
+        if (child instanceof Checkable) {
+            return ((Checkable) child).isChecked();
+        } else {
+            return false;
+        }
+    }
+
+    @Override
+    public void setChecked(boolean checked) {
+        View child = getChildAt(0);
+        if (child instanceof Checkable) {
+            ((Checkable) child).setChecked(checked);
+        }
+    }
+
+    @Override
+    public void toggle() {
+        View child = getChildAt(0);
+        if (child instanceof Checkable) {
+            ((Checkable) child).toggle();
+        }
+    }
+}
diff --git a/app/src/main/java/com/mobeta/android/dslv/DragSortListPreference.java b/app/src/main/java/com/mobeta/android/dslv/DragSortListPreference.java
new file mode 100644
index 0000000..ae2f757
--- /dev/null
+++ b/app/src/main/java/com/mobeta/android/dslv/DragSortListPreference.java
@@ -0,0 +1,185 @@
+/*
+ * Sortable Preference ListView. Allows for sorting items in a view,
+ * and selecting which ones to use.
+ *
+ * Example Usage (In a preference file)
+ *
+ * 	<com.mobeta.android.demodslv.SortableListPreference
+ * 		android:defaultValue="@array/pref_name_defaults"
+ * 		android:entries="@array/pref_name_titles"
+ * 		android:entryValues="@array/pref_name_values"
+ * 		android:key="name_order"
+ * 		android:persistent="true"
+ * 		android:title="@string/pref_name_selection" />
+ *
+ * Original Source: https://github.com/kd7uiy/drag-sort-listview
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2013 The Making of a Ham, http://www.kd7uiy.com
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * Code snippets copied from the following sources:
+ * https://gist.github.com/cardil/4754571
+ *
+ *
+ */
+
+package com.mobeta.android.dslv;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+
+import androidx.preference.ListPreference;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+
+
+public class DragSortListPreference extends ListPreference {
+    public HashMap<CharSequence, Boolean> getEntryChecked() {
+        return entryChecked;
+    }
+
+    private final HashMap<CharSequence, Boolean> entryChecked;
+
+    public DragSortListPreference(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        setDialogLayoutResource(R.layout.sort_list_array_dialog_preference);
+        entryChecked = new HashMap<>();
+    }
+
+    public static CharSequence[] decodeValue(String input) {
+        if (input == null) {
+            return null;
+        }
+        if (input.equals("")) {
+            return new CharSequence[0];
+        }
+        return input.split(",");
+    }
+
+
+    void setValueAndEvent(String value) {
+        if (callChangeListener(decodeValue(value))) {
+            setValue(value);
+        }
+    }
+
+    @Override
+    protected Object onGetDefaultValue(TypedArray typedArray, int index) {
+        return typedArray.getTextArray(index);
+    }
+
+    @Override
+    protected void onSetInitialValue(boolean restoreValue,
+                                     Object rawDefaultValue) {
+        String value;
+        CharSequence[] defaultValue;
+        if (rawDefaultValue == null) {
+            defaultValue = new CharSequence[0];
+        } else {
+            defaultValue = (CharSequence[]) rawDefaultValue;
+        }
+        List<CharSequence> joined = Arrays.asList(defaultValue);
+        String joinedDefaultValue = join(joined);
+        if (restoreValue) {
+            value = getPersistedString(joinedDefaultValue);
+        } else {
+            value = joinedDefaultValue;
+        }
+
+        setValueAndEvent(value);
+    }
+
+
+    public int getValueIndex(CharSequence item) {
+        CharSequence[] entryValues = getEntryValues();
+        for (int i = 0; i < entryValues.length; i++) {
+            if (entryValues[i].equals(item)) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    CharSequence[] restoreEntries() {
+
+        ArrayList<CharSequence> orderedList = new ArrayList<>();
+
+        // Initially populated with all of the values in the determined list.
+        CharSequence[] values = decodeValue(getValue());
+        for (CharSequence value : values) {
+            orderedList.add(value);
+            entryChecked.put(value, true);
+        }
+
+        // This loop sets the default states, and adds to the name list if not
+        // on the list.
+        for (CharSequence value : getEntryValues()) {
+            if (!orderedList.contains(value)) {
+                orderedList.add(value);
+                entryChecked.put(value, false);
+            }
+        }
+
+        return orderedList.toArray(new CharSequence[0]);
+    }
+
+    public int getValueTitleIndex(CharSequence item) {
+        CharSequence[] entries = getEntries();
+        for (int i = 0; i < entries.length; i++) {
+            if (entries[i].equals(item)) {
+                return i;
+            }
+        }
+        throw new IllegalStateException(item + " not found in value title list");
+    }
+
+
+    /**
+     * Joins array of object to single string by separator
+     * <p>
+     * Credits to kurellajunior on this post
+     * http://snippets.dzone.com/posts/show/91
+     *
+     * @param iterable any kind of iterable ex.: <code>["a", "b", "c"]</code>
+     * @return joined string ex.: <code>"a,b,c"</code>
+     */
+    protected static String join(Iterable<?> iterable) {
+        Iterator<?> oIter;
+        if (iterable == null || (!(oIter = iterable.iterator()).hasNext()))
+            return "";
+        StringBuilder oBuilder = new StringBuilder(String.valueOf(oIter.next()));
+        while (oIter.hasNext())
+            oBuilder.append(",").append(oIter.next());
+        return oBuilder.toString();
+    }
+
+    void persistStringValue(String value) {
+        persistString(value);
+    }
+}
diff --git a/app/src/main/java/com/mobeta/android/dslv/DragSortListPreferenceFragment.java b/app/src/main/java/com/mobeta/android/dslv/DragSortListPreferenceFragment.java
new file mode 100644
index 0000000..21f82bd
--- /dev/null
+++ b/app/src/main/java/com/mobeta/android/dslv/DragSortListPreferenceFragment.java
@@ -0,0 +1,147 @@
+/*
+ * Sortable Preference ListView. Allows for sorting items in a view,
+ * and selecting which ones to use.
+ *
+ * Example Usage (In a preference file)
+ *
+ * 	<com.mobeta.android.demodslv.SortableListPreference
+ * 		android:defaultValue="@array/pref_name_defaults"
+ * 		android:entries="@array/pref_name_titles"
+ * 		android:entryValues="@array/pref_name_values"
+ * 		android:key="name_order"
+ * 		android:persistent="true"
+ * 		android:title="@string/pref_name_selection" />
+ *
+ * Original Source: https://github.com/kd7uiy/drag-sort-listview
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2013 The Making of a Ham, http://www.kd7uiy.com
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * Code snippets copied from the following sources:
+ * https://gist.github.com/cardil/4754571
+ *
+ *
+ */
+
+package com.mobeta.android.dslv;
+
+
+import android.view.View;
+import android.widget.ArrayAdapter;
+
+import androidx.annotation.NonNull;
+import androidx.preference.ListPreference;
+import androidx.preference.Preference;
+
+import com.google.android.material.dialog.MaterialAlertDialogBuilder;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.util.dialogs.MaterialListPreferenceDialogFragment;
+
+
+public class DragSortListPreferenceFragment extends MaterialListPreferenceDialogFragment implements ListPreference.TargetFragment {
+	protected DragSortListView mListView;
+	protected ArrayAdapter<CharSequence> mAdapter;
+
+
+	@Override
+	protected void onBindDialogView(View view) {
+		super.onBindDialogView(view);
+
+		mListView = (DragSortListView) view.findViewById(android.R.id.list);
+		mAdapter = new ArrayAdapter<>(mListView.getContext(),
+				R.layout.list_item_checkable, R.id.text);
+		mListView.setAdapter(mAdapter);
+		// This will drop the item in the new location
+		mListView.setDropListener(new DragSortListView.DropListener() {
+			@Override
+			public void drop(int from, int to) {
+				CharSequence item = mAdapter.getItem(from);
+				mAdapter.remove(item);
+				mAdapter.insert(item, to);
+				// Updates checked states
+				mListView.moveCheckState(from, to);
+			}
+		});
+
+
+		DragSortListPreference dslp = ((DragSortListPreference)getPreference());
+		CharSequence[] entries = dslp.getEntries();
+		CharSequence[] entryValues = dslp.getEntryValues();
+		if (entries == null || entryValues == null
+				|| entries.length != entryValues.length) {
+			throw new IllegalStateException(
+					"SortableListPreference requires an entries array and an entryValues "
+							+ "array which are both the same length");
+		}
+
+		CharSequence[] restoredValues = ((DragSortListPreference)getPreference()).restoreEntries();
+		int i = 0;
+		for (CharSequence value : restoredValues) {
+			int index = dslp.getValueIndex(value);
+			if (index >=0) {
+				mAdapter.add(entries[index]);
+				Boolean checked = dslp.getEntryChecked().get(value);
+				if (checked != null && checked.equals(true)) {
+					mListView.setItemChecked(i, true);
+				}
+			}
+			i++;
+		}
+	}
+
+	@Override
+	protected void onPrepareDialogBuilder(MaterialAlertDialogBuilder builder) {
+		// must be empty
+	}
+
+	public void onDialogClosed(boolean positiveResult) {
+		DragSortListPreference dslp = ((DragSortListPreference)getPreference());
+
+		List<CharSequence> values = new ArrayList<>();
+
+		CharSequence[] entryValues = dslp.getEntryValues();
+		if (positiveResult && entryValues != null) {
+			for (int i = 0; i < entryValues.length; i++) {
+				String val = (String) mAdapter.getItem(i);
+				boolean isChecked = mListView.isItemChecked(i);
+				if (isChecked) {
+					values.add(entryValues[dslp.getValueTitleIndex(val)]);
+				}
+			}
+
+			String value = DragSortListPreference.join(values);
+			dslp.setValueAndEvent(value);
+			dslp.persistStringValue(value);
+		}
+
+	}
+
+	@Override
+	public Preference findPreference(@NonNull CharSequence key) {
+		return getPreference();
+	}
+
+}
diff --git a/app/src/main/java/com/mobeta/android/dslv/DragSortListView.java b/app/src/main/java/com/mobeta/android/dslv/DragSortListView.java
new file mode 100644
index 0000000..606de84
--- /dev/null
+++ b/app/src/main/java/com/mobeta/android/dslv/DragSortListView.java
@@ -0,0 +1,2999 @@
+/*
+ * DragSortListView.
+ *
+ * A subclass of the Android ListView component that enables drag
+ * and drop re-ordering of list items.
+ *
+ * Copyright 2012 Carl Bauer
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.mobeta.android.dslv;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.database.DataSetObserver;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Point;
+import android.graphics.drawable.Drawable;
+import android.os.Environment;
+import android.os.SystemClock;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.SparseBooleanArray;
+import android.util.SparseIntArray;
+import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AbsListView;
+import android.widget.BaseAdapter;
+import android.widget.Checkable;
+import android.widget.ListAdapter;
+import android.widget.ListView;
+import android.widget.WrapperListAdapter;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+
+/**
+ * ListView subclass that mediates drag and drop resorting of items.
+ * 
+ * 
+ * @author heycosmo
+ *
+ */
+public class DragSortListView extends ListView {
+    
+    
+    /**
+     * The View that floats above the ListView and represents
+     * the dragged item.
+     */
+    private View mFloatView;
+
+    /**
+     * The float View location. First based on touch location
+     * and given deltaX and deltaY. Then restricted by callback
+     * to FloatViewManager.onDragFloatView(). Finally restricted
+     * by bounds of DSLV.
+     */
+    private Point mFloatLoc = new Point();
+
+    private Point mTouchLoc = new Point();
+
+    /**
+     * The middle (in the y-direction) of the floating View.
+     */
+    private int mFloatViewMid;
+
+    /**
+     * Flag to make sure float View isn't measured twice
+     */
+    private boolean mFloatViewOnMeasured = false;
+
+    /**
+     * Watch the Adapter for data changes. Cancel a drag if
+     * coincident with a change.
+     */ 
+    private DataSetObserver mObserver;
+
+    /**
+     * Transparency for the floating View (XML attribute).
+     */
+    private float mFloatAlpha = 1.0f;
+    private float mCurrFloatAlpha = 1.0f;
+
+    /**
+     * While drag-sorting, the current position of the floating
+     * View. If dropped, the dragged item will land in this position.
+     */
+    private int mFloatPos;
+
+    /**
+     * The first expanded ListView position that helps represent
+     * the drop slot tracking the floating View.
+     */
+    private int mFirstExpPos;
+
+    /**
+     * The second expanded ListView position that helps represent
+     * the drop slot tracking the floating View. This can equal
+     * mFirstExpPos if there is no slide shuffle occurring; otherwise
+     * it is equal to mFirstExpPos + 1.
+     */
+    private int mSecondExpPos;
+
+    /**
+     * Flag set if slide shuffling is enabled.
+     */
+    private boolean mAnimate = false;
+
+    /**
+     * The user dragged from this position.
+     */
+    private int mSrcPos;
+
+    /**
+     * Offset (in x) within the dragged item at which the user
+     * picked it up (or first touched down with the digitalis).
+     */
+    private int mDragDeltaX;
+
+    /**
+     * Offset (in y) within the dragged item at which the user
+     * picked it up (or first touched down with the digitalis).
+     */
+    private int mDragDeltaY;
+
+
+    /**
+     * The difference (in x) between screen coordinates and coordinates
+     * in this view.
+     */
+    //private int mOffsetX;
+
+    /**
+     * The difference (in y) between screen coordinates and coordinates
+     * in this view.
+     */
+    //private int mOffsetY;
+
+    /**
+     * A listener that receives callbacks whenever the floating View
+     * hovers over a new position.
+     */
+    private DragListener mDragListener;
+
+    /**
+     * A listener that receives a callback when the floating View
+     * is dropped.
+     */
+    private DropListener mDropListener;
+
+    /**
+     * A listener that receives a callback when the floating View
+     * (or more precisely the originally dragged item) is removed
+     * by one of the provided gestures.
+     */
+    private RemoveListener mRemoveListener;
+
+    /**
+     * Enable/Disable item dragging
+     * 
+     * @attr name dslv:drag_enabled
+     */
+    private boolean mDragEnabled = true;
+
+    /**
+     * Drag state enum.
+     */
+    private final static int IDLE = 0;
+    private final static int REMOVING = 1;
+    private final static int DROPPING = 2;
+    private final static int STOPPED = 3;
+    private final static int DRAGGING = 4;
+
+    private int mDragState = IDLE;
+
+    /**
+     * Height in pixels to which the originally dragged item
+     * is collapsed during a drag-sort. Currently, this value
+     * must be greater than zero.
+     */
+    private int mItemHeightCollapsed = 1;
+
+    /**
+     * Height of the floating View. Stored for the purpose of
+     * providing the tracking drop slot.
+     */
+    private int mFloatViewHeight;
+
+    /**
+     * Convenience member. See above.
+     */
+    private int mFloatViewHeightHalf;
+
+    /**
+     * Save the given width spec for use in measuring children
+     */
+    private int mWidthMeasureSpec = 0;
+
+    /**
+     * Sample Views ultimately used for calculating the height
+     * of ListView items that are off-screen.
+     */
+    private View[] mSampleViewTypes = new View[1];
+
+    /**
+     * Drag-scroll encapsulator!
+     */
+    private DragScroller mDragScroller;
+
+    /**
+     * Determines the start of the upward drag-scroll region
+     * at the top of the ListView. Specified by a fraction
+     * of the ListView height, thus screen resolution agnostic.
+     */
+    private float mDragUpScrollStartFrac = 1.0f / 3.0f;
+
+    /**
+     * Determines the start of the downward drag-scroll region
+     * at the bottom of the ListView. Specified by a fraction
+     * of the ListView height, thus screen resolution agnostic.
+     */
+    private float mDragDownScrollStartFrac = 1.0f / 3.0f;
+
+    /**
+     * The following are calculated from the above fracs.
+     */
+    private int mUpScrollStartY;
+    private int mDownScrollStartY;
+    private float mDownScrollStartYF;
+    private float mUpScrollStartYF;
+
+    /**
+     * Calculated from above above and current ListView height.
+     */
+    private float mDragUpScrollHeight;
+
+    /**
+     * Calculated from above above and current ListView height.
+     */
+    private float mDragDownScrollHeight;
+
+    /**
+     * Maximum drag-scroll speed in pixels per ms. Only used with
+     * default linear drag-scroll profile.
+     */
+    private float mMaxScrollSpeed = 0.5f;
+
+    /**
+     * Defines the scroll speed during a drag-scroll. User can
+     * provide their own; this default is a simple linear profile
+     * where scroll speed increases linearly as the floating View
+     * nears the top/bottom of the ListView.
+     */
+    private DragScrollProfile mScrollProfile = new DragScrollProfile() {
+        @Override
+        public float getSpeed(float w, long t) {
+            return mMaxScrollSpeed * w;
+        }
+    };
+
+    /**
+     * Current touch x.
+     */
+    private int mX;
+
+    /**
+     * Current touch y.
+     */
+    private int mY;
+
+    /**
+     * Last touch x.
+     */
+    //private int mLastX;
+
+    /**
+     * Last touch y.
+     */
+    private int mLastY;
+
+    /**
+     * The touch y-coord at which drag started
+     */
+    //private int mDragStartY;
+
+    /**
+     * Drag flag bit. Floating View can move in the positive
+     * x direction.
+     */
+    public final static int DRAG_POS_X = 0x1;
+
+    /**
+     * Drag flag bit. Floating View can move in the negative
+     * x direction.
+     */
+    public final static int DRAG_NEG_X = 0x2;
+
+    /**
+     * Drag flag bit. Floating View can move in the positive
+     * y direction. This is subtle. What this actually means is
+     * that, if enabled, the floating View can be dragged below its starting
+     * position. Remove in favor of upper-bounding item position?
+     */
+    public final static int DRAG_POS_Y = 0x4;
+
+    /**
+     * Drag flag bit. Floating View can move in the negative
+     * y direction. This is subtle. What this actually means is
+     * that the floating View can be dragged above its starting
+     * position. Remove in favor of lower-bounding item position?
+     */
+    public final static int DRAG_NEG_Y = 0x8;
+
+    /**
+     * Flags that determine limits on the motion of the
+     * floating View. See flags above.
+     */
+    private int mDragFlags = 0;
+
+    /**
+     * Last call to an on*TouchEvent was a call to
+     * onInterceptTouchEvent.
+     */
+    private boolean mLastCallWasIntercept = false;
+
+    /**
+     * A touch event is in progress.
+     */
+    private boolean mInTouchEvent = false;
+
+    /**
+     * Let the user customize the floating View.
+     */
+    private FloatViewManager mFloatViewManager = null;
+
+    /**
+     * Given to ListView to cancel its action when a drag-sort
+     * begins.
+     */
+    private MotionEvent mCancelEvent;
+
+    /**
+     * Enum telling where to cancel the ListView action when a
+     * drag-sort begins
+     */
+    private static final int NO_CANCEL = 0;
+    private static final int ON_TOUCH_EVENT = 1;
+    private static final int ON_INTERCEPT_TOUCH_EVENT = 2;
+
+    /**
+     * Where to cancel the ListView action when a
+     * drag-sort begins
+     */ 
+    private int mCancelMethod = NO_CANCEL;
+
+    /**
+     * Determines when a slide shuffle animation starts. That is,
+     * defines how close to the edge of the drop slot the floating
+     * View must be to initiate the slide.
+     */
+    private float mSlideRegionFrac = 0.25f;
+
+    /**
+     * Number between 0 and 1 indicating the relative location of
+     * a sliding item (only used if drag-sort animations
+     * are turned on). Nearly 1 means the item is 
+     * at the top of the slide region (nearly full blank item
+     * is directly below).
+     */
+    private float mSlideFrac = 0.0f;
+
+    /**
+     * Wraps the user-provided ListAdapter. This is used to wrap each
+     * item View given by the user inside another View (currenly
+     * a RelativeLayout) which
+     * expands and collapses to simulate the item shuffling.
+     */
+    private AdapterWrapper mAdapterWrapper;
+
+    /**
+     * Turn on custom debugger.
+     */
+    private boolean mTrackDragSort = false;
+
+    /**
+     * Debugging class.
+     */
+    private DragSortTracker mDragSortTracker;
+
+    /**
+     * Needed for adjusting item heights from within layoutChildren
+     */
+    private boolean mBlockLayoutRequests = false;
+
+    /**
+     * Set to true when a down event happens during drag sort;
+     * for example, when drag finish animations are
+     * playing.
+     */
+    private boolean mIgnoreTouchEvent = false;
+
+    /**
+     * Caches DragSortItemView child heights. Sometimes DSLV has to
+     * know the height of an offscreen item. Since ListView virtualizes
+     * these, DSLV must get the item from the ListAdapter to obtain
+     * its height. That process can be expensive, but often the same
+     * offscreen item will be requested many times in a row. Once an
+     * offscreen item height is calculated, we cache it in this guy.
+     * Actually, we cache the height of the child of the
+     * DragSortItemView since the item height changes often during a
+     * drag-sort.
+     */
+    private static final int sCacheSize = 3;
+    private HeightCache mChildHeightCache = new HeightCache(sCacheSize);
+
+    private RemoveAnimator mRemoveAnimator;
+
+    private LiftAnimator mLiftAnimator;
+
+    private DropAnimator mDropAnimator;
+
+    private boolean mUseRemoveVelocity;
+    private float mRemoveVelocityX = 0;
+
+    public DragSortListView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        int defaultDuration = 150;
+        int removeAnimDuration = defaultDuration; // ms
+        int dropAnimDuration = defaultDuration; // ms
+
+        if (attrs != null) {
+            TypedArray a = getContext().obtainStyledAttributes(attrs,
+                    R.styleable.DragSortListView, 0, 0);
+
+            mItemHeightCollapsed = Math.max(1, a.getDimensionPixelSize(
+                    R.styleable.DragSortListView_collapsed_height, 1));
+
+            mTrackDragSort = a.getBoolean(
+                    R.styleable.DragSortListView_track_drag_sort, false);
+
+            if (mTrackDragSort) {
+                mDragSortTracker = new DragSortTracker();
+            }
+
+            // alpha between 0 and 255, 0=transparent, 255=opaque
+            mFloatAlpha = a.getFloat(R.styleable.DragSortListView_float_alpha, mFloatAlpha);
+            mCurrFloatAlpha = mFloatAlpha;
+
+            mDragEnabled = a.getBoolean(R.styleable.DragSortListView_drag_enabled, mDragEnabled);
+
+            mSlideRegionFrac = Math.max(0.0f,
+                    Math.min(1.0f, 1.0f - a.getFloat(
+                            R.styleable.DragSortListView_slide_shuffle_speed,
+                            0.75f)));
+
+            mAnimate = mSlideRegionFrac > 0.0f;
+
+            float frac = a.getFloat(
+                    R.styleable.DragSortListView_drag_scroll_start,
+                    mDragUpScrollStartFrac);
+
+            setDragScrollStart(frac);
+
+            mMaxScrollSpeed = a.getFloat(
+                    R.styleable.DragSortListView_max_drag_scroll_speed,
+                    mMaxScrollSpeed);
+
+            removeAnimDuration = a.getInt(
+                    R.styleable.DragSortListView_remove_animation_duration,
+                    removeAnimDuration);
+
+            dropAnimDuration = a.getInt(
+                    R.styleable.DragSortListView_drop_animation_duration,
+                    dropAnimDuration);
+
+            boolean useDefault = a.getBoolean(
+                    R.styleable.DragSortListView_use_default_controller,
+                    true);
+
+            if (useDefault) {
+                boolean removeEnabled = a.getBoolean(
+                        R.styleable.DragSortListView_remove_enabled,
+                        false);
+                int removeMode = a.getInt(
+                        R.styleable.DragSortListView_remove_mode,
+                        DragSortController.FLING_REMOVE);
+                boolean sortEnabled = a.getBoolean(
+                        R.styleable.DragSortListView_sort_enabled,
+                        true);
+                int dragInitMode = a.getInt(
+                        R.styleable.DragSortListView_drag_start_mode,
+                        DragSortController.ON_DOWN);
+                int dragHandleId = a.getResourceId(
+                        R.styleable.DragSortListView_drag_handle_id,
+                        0);
+                int flingHandleId = a.getResourceId(
+                        R.styleable.DragSortListView_fling_handle_id,
+                        0);
+                int clickRemoveId = a.getResourceId(
+                        R.styleable.DragSortListView_click_remove_id,
+                        0);
+                int bgColor = a.getColor(
+                        R.styleable.DragSortListView_float_background_color,
+                        Color.BLACK);
+
+                DragSortController controller = new DragSortController(
+                        this, dragHandleId, dragInitMode, removeMode,
+                        clickRemoveId, flingHandleId);
+                controller.setRemoveEnabled(removeEnabled);
+                controller.setSortEnabled(sortEnabled);
+                controller.setBackgroundColor(bgColor);
+
+                mFloatViewManager = controller;
+                setOnTouchListener(controller);
+            }
+
+            a.recycle();
+        }
+
+        mDragScroller = new DragScroller();
+
+        float smoothness = 0.5f;
+        if (removeAnimDuration > 0) {
+            mRemoveAnimator = new RemoveAnimator(smoothness, removeAnimDuration);
+        }
+        // mLiftAnimator = new LiftAnimator(smoothness, 100);
+        if (dropAnimDuration > 0) {
+            mDropAnimator = new DropAnimator(smoothness, dropAnimDuration);
+        }
+
+        mCancelEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_CANCEL, 0f, 0f, 0f, 0f, 0, 0f,
+                0f, 0, 0);
+
+        // construct the dataset observer
+        mObserver = new DataSetObserver() {
+            private void cancel() {
+                if (mDragState == DRAGGING) {
+                    cancelDrag();
+                }
+            }
+
+            @Override
+            public void onChanged() {
+                cancel();
+            }
+
+            @Override
+            public void onInvalidated() {
+                cancel();
+            }
+        };
+    }
+
+    /**
+     * Usually called from a FloatViewManager. The float alpha
+     * will be reset to the xml-defined value every time a drag
+     * is stopped.
+     */
+    public void setFloatAlpha(float alpha) {
+        mCurrFloatAlpha = alpha;
+    }
+
+    public float getFloatAlpha() {
+        return mCurrFloatAlpha;
+    }
+
+    /**
+     * Set maximum drag scroll speed in positions/second. Only applies
+     * if using default ScrollSpeedProfile.
+     * 
+     * @param max Maximum scroll speed.
+     */
+    public void setMaxScrollSpeed(float max) {
+        mMaxScrollSpeed = max;
+    }
+
+    /**
+     * For each DragSortListView Listener interface implemented by
+     * <code>adapter</code>, this method calls the appropriate
+     * set*Listener method with <code>adapter</code> as the argument.
+     * 
+     * @param adapter The ListAdapter providing data to back
+     * DragSortListView.
+     *
+     * @see android.widget.ListView#setAdapter(android.widget.ListAdapter)
+     */
+    @Override
+    public void setAdapter(ListAdapter adapter) {
+        if (adapter != null) {
+            mAdapterWrapper = new AdapterWrapper(adapter);
+            adapter.registerDataSetObserver(mObserver);
+
+            if (adapter instanceof DropListener) {
+                setDropListener((DropListener) adapter);
+            }
+            if (adapter instanceof DragListener) {
+                setDragListener((DragListener) adapter);
+            }
+            if (adapter instanceof RemoveListener) {
+                setRemoveListener((RemoveListener) adapter);
+            }
+        } else {
+            mAdapterWrapper = null;
+        }
+
+        super.setAdapter(mAdapterWrapper);
+    }
+
+    /**
+     * As opposed to {@link ListView#getAdapter()}, which returns
+     * a heavily wrapped ListAdapter (DragSortListView wraps the
+     * input ListAdapter {\emph and} ListView wraps the wrapped one).
+     *
+     * @return The ListAdapter set as the argument of {@link setAdapter()}
+     */
+    public ListAdapter getInputAdapter() {
+        if (mAdapterWrapper == null) {
+            return null;
+        } else {
+            return mAdapterWrapper.getWrappedAdapter();
+        }
+    }
+
+    private class AdapterWrapper extends BaseAdapter implements WrapperListAdapter {
+    	private ListAdapter mAdapter;
+
+        public AdapterWrapper(ListAdapter adapter) {
+            mAdapter = adapter;
+        }
+
+        @Override
+        public ListAdapter getWrappedAdapter() {
+            return mAdapter;
+        }
+
+        @Override
+        public long getItemId(int position) {
+            return mAdapter.getItemId(position);
+        }
+
+        @Override
+        public Object getItem(int position) {
+            return mAdapter.getItem(position);
+        }
+
+        @Override
+        public int getCount() {
+            return mAdapter.getCount();
+        }
+
+        @Override
+        public boolean areAllItemsEnabled() {
+            return mAdapter.areAllItemsEnabled();
+        }
+
+        @Override
+        public boolean isEnabled(int position) {
+            return mAdapter.isEnabled(position);
+        }
+        
+        @Override
+        public int getItemViewType(int position) {
+            return mAdapter.getItemViewType(position);
+        }
+
+        @Override
+        public int getViewTypeCount() {
+            return mAdapter.getViewTypeCount();
+        }
+        
+        @Override
+        public boolean hasStableIds() {
+            return mAdapter.hasStableIds();
+        }
+        
+        @Override
+        public boolean isEmpty() {
+            return mAdapter.isEmpty();
+        }
+
+
+        @Override
+        public View getView(int position, View convertView, ViewGroup parent) {
+            DragSortItemView v;
+            View child;
+            if (convertView != null) {
+                v = (DragSortItemView) convertView;
+                View oldChild = v.getChildAt(0);
+
+                child = mAdapter.getView(position, oldChild, DragSortListView.this);
+                if (child != oldChild) {
+                    // shouldn't get here if user is reusing convertViews
+                    // properly
+                    if (oldChild != null) {
+                        v.removeViewAt(0);
+                    }
+                    v.addView(child);
+                }
+            } else {
+                child = mAdapter.getView(position, null, DragSortListView.this);
+                if (child instanceof Checkable) {
+                    v = new DragSortItemViewCheckable(getContext());
+                } else {
+                    v = new DragSortItemView(getContext());
+                }
+                v.setLayoutParams(new AbsListView.LayoutParams(
+                        ViewGroup.LayoutParams.MATCH_PARENT,
+                        ViewGroup.LayoutParams.WRAP_CONTENT));
+                v.addView(child);
+            }
+
+            // Set the correct item height given drag state; passed
+            // View needs to be measured if measurement is required.
+            adjustItem(position + getHeaderViewsCount(), v, true);
+
+            return v;
+        }
+
+		@Override
+		public void registerDataSetObserver(DataSetObserver observer) {
+			mAdapter.registerDataSetObserver(observer);			
+		}
+
+		@Override
+		public void unregisterDataSetObserver(DataSetObserver observer) {
+			mAdapter.unregisterDataSetObserver(observer);			
+		}
+    }
+
+    private void drawDivider(int expPosition, Canvas canvas) {
+        final Drawable divider = getDivider();
+        final int dividerHeight = getDividerHeight();
+
+        if (divider != null && dividerHeight != 0) {
+            final ViewGroup expItem = (ViewGroup) getChildAt(expPosition
+                    - getFirstVisiblePosition());
+            if (expItem != null) {
+                final int left = getPaddingLeft();
+                final int right = getWidth() - getPaddingRight();
+                final int top;
+                final int bottom;
+
+                final int childHeight = expItem.getChildAt(0).getHeight();
+
+                if (expPosition > mSrcPos) {
+                    top = expItem.getTop() + childHeight;
+                    bottom = top + dividerHeight;
+                } else {
+                    bottom = expItem.getBottom() - childHeight;
+                    top = bottom - dividerHeight;
+                }
+
+                // Have to clip to support ColorDrawable on <= Gingerbread
+                canvas.save();
+                canvas.clipRect(left, top, right, bottom);
+                divider.setBounds(left, top, right, bottom);
+                divider.draw(canvas);
+                canvas.restore();
+            }
+        }
+    }
+
+    @Override
+    protected void dispatchDraw(Canvas canvas) {
+        super.dispatchDraw(canvas);
+
+        if (mDragState != IDLE) {
+            // draw the divider over the expanded item
+            if (mFirstExpPos != mSrcPos) {
+                drawDivider(mFirstExpPos, canvas);
+            }
+            if (mSecondExpPos != mFirstExpPos && mSecondExpPos != mSrcPos) {
+                drawDivider(mSecondExpPos, canvas);
+            }
+        }
+
+        if (mFloatView != null) {
+            // draw the float view over everything
+            final int floatViewWidth = mFloatView.getWidth();
+            final int floatViewHeight = mFloatView.getHeight();
+
+            int x = mFloatLoc.x;
+
+            final int listViewWidth = getWidth();
+            if (x < 0)
+                x = -x;
+            float alphaMod;
+            if (x < listViewWidth) {
+                alphaMod = ((float) (listViewWidth - x)) / ((float) listViewWidth);
+                alphaMod *= alphaMod;
+            } else {
+                alphaMod = 0;
+            }
+
+            final int alpha = (int) (255f * mCurrFloatAlpha * alphaMod);
+
+            canvas.save();
+            canvas.translate(mFloatLoc.x, mFloatLoc.y);
+            canvas.clipRect(0, 0, floatViewWidth, floatViewHeight);
+
+            canvas.saveLayerAlpha(0, 0, floatViewWidth, floatViewHeight, alpha, 
+                    Canvas.ALL_SAVE_FLAG);
+
+            mFloatView.draw(canvas);
+            canvas.restore();
+            canvas.restore();
+        }
+    }
+
+    private int getItemHeight(int position) {
+        View v = getChildAt(position - getFirstVisiblePosition());
+
+        if (v != null) {
+            // item is onscreen, just get the height of the View
+            return v.getHeight();
+        } else {
+            // item is offscreen. get child height and calculate
+            // item height based on current shuffle state
+            return calcItemHeight(position, getChildHeight(position));
+        }
+    }
+
+    private class HeightCache {
+
+        private SparseIntArray mMap;
+        private List<Integer> mOrder;
+        private int mMaxSize;
+
+        public HeightCache(int size) {
+            mMap = new SparseIntArray(size);
+            mOrder = new ArrayList<Integer>(size);
+            mMaxSize = size;
+        }
+
+        /**
+         * Add item height at position if doesn't already exist.
+         */
+        public void add(int position, int height) {
+            int currentHeight = mMap.get(position, -1);
+            if (currentHeight != height) {
+                if (currentHeight == -1 && mMap.size() == mMaxSize) {
+                	// remove oldest entry
+                    mMap.delete(mOrder.remove(0));
+                } else {
+                    // move position to newest slot
+                    mOrder.remove((Integer) position);
+                }
+                mMap.put(position, height);
+                mOrder.add(position);
+            }
+        }
+
+        public int get(int position) {
+            return mMap.get(position, -1);
+        }
+
+        public void clear() {
+            mMap.clear();
+            mOrder.clear();
+        }
+
+    }
+
+    /**
+     * Get the shuffle edge for item at position when top of
+     * item is at y-coord top. Assumes that current item heights
+     * are consistent with current float view location and
+     * thus expanded positions and slide fraction. i.e. Should not be
+     * called between update of expanded positions/slide fraction
+     * and layoutChildren.
+     *
+     * @param position 
+     * @param top
+     * @param height Height of item at position. If -1, this function
+     * calculates this height.
+     *
+     * @return Shuffle line between position-1 and position (for
+     * the given view of the list; that is, for when top of item at
+     * position has y-coord of given `top`). If
+     * floating View (treated as horizontal line) is dropped
+     * immediately above this line, it lands in position-1. If
+     * dropped immediately below this line, it lands in position.
+     */
+    private int getShuffleEdge(int position, int top) {
+        final int numHeaders = getHeaderViewsCount();
+        final int numFooters = getFooterViewsCount();
+
+        // shuffle edges are defined between items that can be
+        // dragged; there are N-1 of them if there are N draggable
+        // items.
+
+        if (position <= numHeaders || (position >= getCount() - numFooters)) {
+            return top;
+        }
+
+        int divHeight = getDividerHeight();
+
+        int edge;
+
+        int maxBlankHeight = mFloatViewHeight - mItemHeightCollapsed;
+        int childHeight = getChildHeight(position);
+        int itemHeight = getItemHeight(position);
+
+        // first calculate top of item given that floating View is
+        // centered over src position
+        int otop = top;
+        if (mSecondExpPos <= mSrcPos) {
+            // items are expanded on and/or above the source position
+
+            if (position == mSecondExpPos && mFirstExpPos != mSecondExpPos) {
+                if (position == mSrcPos) {
+                    otop = top + itemHeight - mFloatViewHeight;
+                } else {
+                    int blankHeight = itemHeight - childHeight;
+                    otop = top + blankHeight - maxBlankHeight;
+                }
+            } else if (position > mSecondExpPos && position <= mSrcPos) {
+                otop = top - maxBlankHeight;
+            }
+
+        } else {
+            // items are expanded on and/or below the source position
+
+            if (position > mSrcPos && position <= mFirstExpPos) {
+                otop = top + maxBlankHeight;
+            } else if (position == mSecondExpPos && mFirstExpPos != mSecondExpPos) {
+                int blankHeight = itemHeight - childHeight;
+                otop = top + blankHeight;
+            }
+        }
+
+        // otop is set
+        if (position <= mSrcPos) {
+            edge = otop + (mFloatViewHeight - divHeight - getChildHeight(position - 1)) / 2;
+        } else {
+            edge = otop + (childHeight - divHeight - mFloatViewHeight) / 2;
+        }
+
+        return edge;
+    }
+
+    private boolean updatePositions() {
+        final int first = getFirstVisiblePosition();
+        int startPos = mFirstExpPos;
+        View startView = getChildAt(startPos - first);
+
+        if (startView == null) {
+            startPos = first + getChildCount() / 2;
+            startView = getChildAt(startPos - first);
+        }
+        int startTop = startView.getTop();
+
+        int itemHeight = startView.getHeight();
+
+        int edge = getShuffleEdge(startPos, startTop);
+        int lastEdge = edge;
+
+        int divHeight = getDividerHeight();
+
+        // Log.d("mobeta", "float mid="+mFloatViewMid);
+
+        int itemPos = startPos;
+        int itemTop = startTop;
+        if (mFloatViewMid < edge) {
+            // scanning up for float position
+            while (itemPos >= 0) {
+                itemPos--;
+                itemHeight = getItemHeight(itemPos);
+
+                if (itemPos == 0) {
+                    edge = itemTop - divHeight - itemHeight;
+                    break;
+                }
+
+                itemTop -= itemHeight + divHeight;
+                edge = getShuffleEdge(itemPos, itemTop);
+
+                if (mFloatViewMid >= edge) {
+                    break;
+                }
+
+                lastEdge = edge;
+            }
+        } else {
+            // scanning down for float position
+            final int count = getCount();
+            while (itemPos < count) {
+                if (itemPos == count - 1) {
+                    edge = itemTop + divHeight + itemHeight;
+                    break;
+                }
+
+                itemTop += divHeight + itemHeight;
+                itemHeight = getItemHeight(itemPos + 1);
+                edge = getShuffleEdge(itemPos + 1, itemTop);
+
+                // test for hit
+                if (mFloatViewMid < edge) {
+                    break;
+                }
+
+                lastEdge = edge;
+                itemPos++;
+            }
+        }
+
+        final int numHeaders = getHeaderViewsCount();
+        final int numFooters = getFooterViewsCount();
+
+        boolean updated = false;
+
+        int oldFirstExpPos = mFirstExpPos;
+        int oldSecondExpPos = mSecondExpPos;
+        float oldSlideFrac = mSlideFrac;
+
+        if (mAnimate) {
+            int edgeToEdge = Math.abs(edge - lastEdge);
+
+            int edgeTop, edgeBottom;
+            if (mFloatViewMid < edge) {
+            	edgeTop = lastEdge;
+            	edgeBottom = edge;   
+            } else {
+                edgeTop = edge;
+                edgeBottom = lastEdge;
+            }
+
+            int slideRgnHeight = (int) (0.5f * mSlideRegionFrac * edgeToEdge);
+            float slideRgnHeightF = (float) slideRgnHeight;
+            int slideEdgeTop = edgeTop + slideRgnHeight;
+            int slideEdgeBottom = edgeBottom - slideRgnHeight;
+
+            // Three regions
+            if (mFloatViewMid < slideEdgeTop) {
+                mFirstExpPos = itemPos - 1;
+                mSecondExpPos = itemPos;
+                mSlideFrac = 0.5f * ((float) (slideEdgeTop - mFloatViewMid)) / slideRgnHeightF;
+            } else if (mFloatViewMid < slideEdgeBottom) {
+                mFirstExpPos = itemPos;
+                mSecondExpPos = itemPos;
+            } else {
+                mFirstExpPos = itemPos;
+                mSecondExpPos = itemPos + 1;
+                mSlideFrac = 0.5f * (1.0f + ((float) (edgeBottom - mFloatViewMid))
+                        / slideRgnHeightF);
+            }
+
+        } else {
+            mFirstExpPos = itemPos;
+            mSecondExpPos = itemPos;
+        }
+
+        // correct for headers and footers
+        if (mFirstExpPos < numHeaders) {
+            itemPos = numHeaders;
+            mFirstExpPos = itemPos;
+            mSecondExpPos = itemPos;
+        } else if (mSecondExpPos >= getCount() - numFooters) {
+            itemPos = getCount() - numFooters - 1;
+            mFirstExpPos = itemPos;
+            mSecondExpPos = itemPos;
+        }
+
+        if (mFirstExpPos != oldFirstExpPos || mSecondExpPos != oldSecondExpPos
+                || mSlideFrac != oldSlideFrac) {
+            updated = true;
+        }
+
+        if (itemPos != mFloatPos) {
+            if (mDragListener != null) {
+                mDragListener.drag(mFloatPos - numHeaders, itemPos - numHeaders);
+            }
+
+            mFloatPos = itemPos;
+            updated = true;
+        }
+
+        return updated;
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+
+        if (mTrackDragSort) {
+            mDragSortTracker.appendState();
+        }
+    }
+
+    private class SmoothAnimator implements Runnable {
+        protected long mStartTime;
+
+        private float mDurationF;
+
+        private float mAlpha;
+        private float mA, mB, mC, mD;
+
+        private boolean mCanceled;
+
+        public SmoothAnimator(float smoothness, int duration) {
+            mAlpha = smoothness;
+            mDurationF = (float) duration;
+            mA = mD = 1f / (2f * mAlpha * (1f - mAlpha));
+            mB = mAlpha / (2f * (mAlpha - 1f));
+            mC = 1f / (1f - mAlpha);
+        }
+
+        public float transform(float frac) {
+            if (frac < mAlpha) {
+                return mA * frac * frac;
+            } else if (frac < 1f - mAlpha) {
+                return mB + mC * frac;
+            } else {
+                return 1f - mD * (frac - 1f) * (frac - 1f);
+            }
+        }
+
+        public void start() {
+            mStartTime = SystemClock.uptimeMillis();
+            mCanceled = false;
+            onStart();
+            post(this);
+        }
+
+        public void cancel() {
+            mCanceled = true;
+        }
+
+        public void onStart() {
+            // stub
+        }
+
+        public void onUpdate(float frac, float smoothFrac) {
+            // stub
+        }
+
+        public void onStop() {
+            // stub
+        }
+
+        @Override
+        public void run() {
+            if (mCanceled) {
+                return;
+            }
+
+            float fraction = ((float) (SystemClock.uptimeMillis() - mStartTime)) / mDurationF;
+
+            if (fraction >= 1f) {
+                onUpdate(1f, 1f);
+                onStop();
+            } else {
+                onUpdate(fraction, transform(fraction));
+                post(this);
+            }
+        }
+    }
+
+    /**
+     * Centers floating View under touch point.
+     */
+    private class LiftAnimator extends SmoothAnimator {
+
+        private float mInitDragDeltaY;
+        private float mFinalDragDeltaY;
+
+        public LiftAnimator(float smoothness, int duration) {
+            super(smoothness, duration);
+        }
+
+        @Override
+        public void onStart() {
+            mInitDragDeltaY = mDragDeltaY;
+            mFinalDragDeltaY = mFloatViewHeightHalf;
+        }
+
+        @Override
+        public void onUpdate(float frac, float smoothFrac) {
+            if (mDragState != DRAGGING) {
+                cancel();
+            } else {
+                mDragDeltaY = (int) (smoothFrac * mFinalDragDeltaY + (1f - smoothFrac)
+                        * mInitDragDeltaY);
+                mFloatLoc.y = mY - mDragDeltaY;
+                doDragFloatView(true);
+            }
+        }
+    }
+
+    /**
+     * Centers floating View over drop slot before destroying.
+     */
+    private class DropAnimator extends SmoothAnimator {
+
+        private int mDropPos;
+        private int srcPos;
+        private float mInitDeltaY;
+        private float mInitDeltaX;
+
+        public DropAnimator(float smoothness, int duration) {
+            super(smoothness, duration);
+        }
+
+        @Override
+        public void onStart() {
+            mDropPos = mFloatPos;
+            srcPos = mSrcPos;
+            mDragState = DROPPING;
+            mInitDeltaY = mFloatLoc.y - getTargetY();
+            mInitDeltaX = mFloatLoc.x - getPaddingLeft();
+        }
+
+        private int getTargetY() {
+            final int first = getFirstVisiblePosition();
+            final int otherAdjust = (mItemHeightCollapsed + getDividerHeight()) / 2;
+            View v = getChildAt(mDropPos - first);
+            int targetY = -1;
+            if (v != null) {
+                if (mDropPos == srcPos) {
+                    targetY = v.getTop();
+                } else if (mDropPos < srcPos) {
+                    // expanded down
+                    targetY = v.getTop() - otherAdjust;
+                } else {
+                    // expanded up
+                    targetY = v.getBottom() + otherAdjust - mFloatViewHeight;
+                }
+            } else {
+                // drop position is not on screen?? no animation
+                cancel();
+            }
+
+            return targetY;
+        }
+
+        @Override
+        public void onUpdate(float frac, float smoothFrac) {
+            final int targetY = getTargetY();
+            final int targetX = getPaddingLeft();
+            final float deltaY = mFloatLoc.y - targetY;
+            final float deltaX = mFloatLoc.x - targetX;
+            final float f = 1f - smoothFrac;
+            if (f < Math.abs(deltaY / mInitDeltaY) || f < Math.abs(deltaX / mInitDeltaX)) {
+                mFloatLoc.y = targetY + (int) (mInitDeltaY * f);
+                mFloatLoc.x = getPaddingLeft() + (int) (mInitDeltaX * f);
+                doDragFloatView(true);
+            }
+        }
+
+        @Override
+        public void onStop() {
+            dropFloatView();
+        }
+
+    }
+
+    /**
+     * Collapses expanded items.
+     */
+    private class RemoveAnimator extends SmoothAnimator {
+
+        private float mFloatLocX;
+        private float mFirstStartBlank;
+        private float mSecondStartBlank;
+
+        private int mFirstChildHeight = -1;
+        private int mSecondChildHeight = -1;
+
+        private int mFirstPos;
+        private int mSecondPos;
+
+        public RemoveAnimator(float smoothness, int duration) {
+            super(smoothness, duration);
+        }
+
+        @Override
+        public void onStart() {
+            mFirstChildHeight = -1;
+            mSecondChildHeight = -1;
+            mFirstPos = mFirstExpPos;
+            mSecondPos = mSecondExpPos;
+            mDragState = REMOVING;
+
+            mFloatLocX = mFloatLoc.x;
+            if (mUseRemoveVelocity) {
+                float minVelocity = 2f * getWidth();
+                if (mRemoveVelocityX == 0) {
+                    mRemoveVelocityX = (mFloatLocX < 0 ? -1 : 1) * minVelocity;
+                } else {
+                    minVelocity *= 2;
+                    if (mRemoveVelocityX < 0 && mRemoveVelocityX > -minVelocity)
+                        mRemoveVelocityX = -minVelocity;
+                    else if (mRemoveVelocityX > 0 && mRemoveVelocityX < minVelocity)
+                        mRemoveVelocityX = minVelocity;
+                }
+            } else {
+                destroyFloatView();
+            }
+        }
+
+        @Override
+        public void onUpdate(float frac, float smoothFrac) {
+            float f = 1f - smoothFrac;
+
+            final int firstVis = getFirstVisiblePosition();
+            View item = getChildAt(mFirstPos - firstVis);
+            ViewGroup.LayoutParams lp;
+            int blank;
+
+            if (mUseRemoveVelocity) {
+                float dt = (float) (SystemClock.uptimeMillis() - mStartTime) / 1000;
+                if (dt == 0)
+                    return;
+                float dx = mRemoveVelocityX * dt;
+                int w = getWidth();
+                mRemoveVelocityX += (mRemoveVelocityX > 0 ? 1 : -1) * dt * w;
+                mFloatLocX += dx;
+                mFloatLoc.x = (int) mFloatLocX;
+                if (mFloatLocX < w && mFloatLocX > -w) {
+                    mStartTime = SystemClock.uptimeMillis();
+                    doDragFloatView(true);
+                    return;
+                }
+            }
+
+            if (item != null) {
+                if (mFirstChildHeight == -1) {
+                    mFirstChildHeight = getChildHeight(mFirstPos, item, false);
+                    mFirstStartBlank = (float) (item.getHeight() - mFirstChildHeight);
+                }
+                blank = Math.max((int) (f * mFirstStartBlank), 1);
+                lp = item.getLayoutParams();
+                lp.height = mFirstChildHeight + blank;
+                item.setLayoutParams(lp);
+            }
+            if (mSecondPos != mFirstPos) {
+                item = getChildAt(mSecondPos - firstVis);
+                if (item != null) {
+                    if (mSecondChildHeight == -1) {
+                        mSecondChildHeight = getChildHeight(mSecondPos, item, false);
+                        mSecondStartBlank = (float) (item.getHeight() - mSecondChildHeight);
+                    }
+                    blank = Math.max((int) (f * mSecondStartBlank), 1);
+                    lp = item.getLayoutParams();
+                    lp.height = mSecondChildHeight + blank;
+                    item.setLayoutParams(lp);
+                }
+            }
+        }
+
+        @Override
+        public void onStop() {
+            doRemoveItem();
+        }
+    }
+
+    public void removeItem(int which) {
+        mUseRemoveVelocity = false;
+        removeItem(which, 0);
+    }
+
+    /**
+     * Removes an item from the list and animates the removal.
+     *
+     * @param which Position to remove (NOTE: headers/footers ignored!
+     * this is a position in your input ListAdapter).
+     * @param velocityX 
+     */
+    public void removeItem(int which, float velocityX) {
+        if (mDragState == IDLE || mDragState == DRAGGING) {
+            if (mDragState == IDLE) {
+                // called from outside drag-sort
+                mSrcPos = getHeaderViewsCount() + which;
+                mFirstExpPos = mSrcPos;
+                mSecondExpPos = mSrcPos;
+                mFloatPos = mSrcPos;
+                View v = getChildAt(mSrcPos - getFirstVisiblePosition());
+                if (v != null) {
+                    v.setVisibility(View.INVISIBLE);
+                }
+            }
+
+            mDragState = REMOVING;
+            mRemoveVelocityX = velocityX;
+
+            if (mInTouchEvent) {
+                switch (mCancelMethod) {
+                    case ON_TOUCH_EVENT:
+                        super.onTouchEvent(mCancelEvent);
+                        break;
+                    case ON_INTERCEPT_TOUCH_EVENT:
+                        super.onInterceptTouchEvent(mCancelEvent);
+                        break;
+                }
+            }
+
+            if (mRemoveAnimator != null) {
+                mRemoveAnimator.start();
+            } else {
+                doRemoveItem(which);
+            }
+        }
+    }
+
+    /**
+     * Move an item, bypassing the drag-sort process. Simply calls
+     * through to {@link DropListener#drop(int, int)}.
+     * 
+     * @param from Position to move (NOTE: headers/footers ignored!
+     * this is a position in your input ListAdapter).
+     * @param to Target position (NOTE: headers/footers ignored!
+     * this is a position in your input ListAdapter).
+     */
+    public void moveItem(int from, int to) {
+        if (mDropListener != null) {
+            final int count = getInputAdapter().getCount();
+            if (from >= 0 && from < count && to >= 0 && to < count) {
+                mDropListener.drop(from, to);
+            }
+        }
+    }
+
+    /**
+     * Cancel a drag. Calls {@link #stopDrag(boolean, boolean)} with
+     * <code>true</code> as the first argument.
+     */
+    public void cancelDrag() {
+        if (mDragState == DRAGGING) {
+            mDragScroller.stopScrolling(true);
+            destroyFloatView();
+            clearPositions();
+            adjustAllItems();
+
+            if (mInTouchEvent) {
+                mDragState = STOPPED;
+            } else {
+                mDragState = IDLE;
+            }
+        }
+    }
+
+    private void clearPositions() {
+        mSrcPos = -1;
+        mFirstExpPos = -1;
+        mSecondExpPos = -1;
+        mFloatPos = -1;
+    }
+
+    private void dropFloatView() {
+        // must set to avoid cancelDrag being called from the DataSetObserver
+        mDragState = DROPPING;
+
+        if (mDropListener != null && mFloatPos >= 0 && mFloatPos < getCount()) {
+            final int numHeaders = getHeaderViewsCount();
+            mDropListener.drop(mSrcPos - numHeaders, mFloatPos - numHeaders);
+        }
+
+        destroyFloatView();
+
+        adjustOnReorder();
+        clearPositions();
+        adjustAllItems();
+
+        // now the drag is done
+        if (mInTouchEvent) {
+            mDragState = STOPPED;
+        } else {
+            mDragState = IDLE;
+        }
+    }
+
+    private void doRemoveItem() {
+        doRemoveItem(mSrcPos - getHeaderViewsCount());
+    }
+
+    /**
+     * Removes dragged item from the list. Calls RemoveListener.
+     */
+    private void doRemoveItem(int which) {
+        // must set to avoid cancelDrag being called from the DataSetObserver
+        mDragState = REMOVING;
+
+        // end it
+        if (mRemoveListener != null) {
+            mRemoveListener.remove(which);
+        }
+
+        destroyFloatView();
+
+        adjustOnReorder();
+        clearPositions();
+
+        // now the drag is done
+        if (mInTouchEvent) {
+            mDragState = STOPPED;
+        } else {
+            mDragState = IDLE;
+        }
+    }
+
+    private void adjustOnReorder() {
+        final int firstPos = getFirstVisiblePosition();
+        if (mSrcPos < firstPos) {
+            // collapsed src item is off screen;
+            // adjust the scroll after item heights have been fixed
+            View v = getChildAt(0);
+            int top = 0;
+            if (v != null) {
+                top = v.getTop();
+            }
+            setSelectionFromTop(firstPos - 1, top - getPaddingTop());
+        }
+    }
+
+    /**
+     * Stop a drag in progress. Pass <code>true</code> if you would
+     * like to remove the dragged item from the list.
+     *
+     * @param remove Remove the dragged item from the list. Calls
+     * a registered RemoveListener, if one exists. Otherwise, calls
+     * the DropListener, if one exists.
+     *
+     * @return True if the stop was successful. False if there is
+     * no floating View.
+     */
+    public boolean stopDrag(boolean remove) {
+        mUseRemoveVelocity = false;
+        return stopDrag(remove, 0);
+    }
+
+    public boolean stopDragWithVelocity(boolean remove, float velocityX) {
+        mUseRemoveVelocity = true;
+        return stopDrag(remove, velocityX);
+    }
+
+    public boolean stopDrag(boolean remove, float velocityX) {
+        if (mFloatView != null) {
+            mDragScroller.stopScrolling(true);
+
+            if (remove) {
+                removeItem(mSrcPos - getHeaderViewsCount(), velocityX);
+            } else {
+                if (mDropAnimator != null) {
+                    mDropAnimator.start();
+                } else {
+                    dropFloatView();
+                }
+            }
+
+            if (mTrackDragSort) {
+                mDragSortTracker.stopTracking();
+            }
+
+            return true;
+        } else {
+            // stop failed
+            return false;
+        }
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent ev) {
+        if (mIgnoreTouchEvent) {
+            mIgnoreTouchEvent = false;
+            return false;
+        }
+
+        if (!mDragEnabled) {
+            return super.onTouchEvent(ev);
+        }
+
+        boolean more = false;
+
+        boolean lastCallWasIntercept = mLastCallWasIntercept;
+        mLastCallWasIntercept = false;
+
+        if (!lastCallWasIntercept) {
+            saveTouchCoords(ev);
+        }
+
+        if (mDragState == DRAGGING) {
+            onDragTouchEvent(ev);
+            more = true; // give us more!
+        } else {
+            // TODO: what if float view is null because we dropped in middle
+            // of drag touch event?
+
+            if (mDragState == IDLE) {
+                if (super.onTouchEvent(ev)) {
+                    more = true;
+                }
+            }
+
+            int action = ev.getAction() & MotionEvent.ACTION_MASK;
+
+            switch (action) {
+                case MotionEvent.ACTION_CANCEL:
+                case MotionEvent.ACTION_UP:
+                    doActionUpOrCancel();
+                    break;
+                default:
+                    if (more) {
+                        mCancelMethod = ON_TOUCH_EVENT;
+                    }
+            }
+        }
+
+        return more;
+    }
+
+    private void doActionUpOrCancel() {
+        mCancelMethod = NO_CANCEL;
+        mInTouchEvent = false;
+        if (mDragState == STOPPED) {
+            mDragState = IDLE;
+        }
+        mCurrFloatAlpha = mFloatAlpha;
+        mListViewIntercepted = false;
+        mChildHeightCache.clear();
+    }
+
+    private void saveTouchCoords(MotionEvent ev) {
+        int action = ev.getAction() & MotionEvent.ACTION_MASK;
+        if (action != MotionEvent.ACTION_DOWN) {
+            //mLastX = mX;
+            mLastY = mY;
+        }
+        mX = (int) ev.getX();
+        mY = (int) ev.getY();
+        if (action == MotionEvent.ACTION_DOWN) {
+            //mLastX = mX;
+            mLastY = mY;
+        }
+        //mOffsetX = (int) ev.getRawX() - mX;
+        //mOffsetY = (int) ev.getRawY() - mY;
+    }
+
+    public boolean listViewIntercepted() {
+        return mListViewIntercepted;
+    }
+
+    private boolean mListViewIntercepted = false;
+
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent ev) {
+        if (!mDragEnabled) {
+            return super.onInterceptTouchEvent(ev);
+        }
+
+        saveTouchCoords(ev);
+        mLastCallWasIntercept = true;
+
+        int action = ev.getAction() & MotionEvent.ACTION_MASK;
+
+        if (action == MotionEvent.ACTION_DOWN) {
+            if (mDragState != IDLE) {
+                // intercept and ignore
+                mIgnoreTouchEvent = true;
+                return true;
+            }
+            mInTouchEvent = true;
+        }
+
+        boolean intercept = false;
+
+        // the following deals with calls to super.onInterceptTouchEvent
+        if (mFloatView != null) {
+            // super's touch event cancelled in startDrag
+            intercept = true;
+        } else {
+            if (super.onInterceptTouchEvent(ev)) {
+                mListViewIntercepted = true;
+                intercept = true;
+            }
+
+            switch (action) {
+                case MotionEvent.ACTION_CANCEL:
+                case MotionEvent.ACTION_UP:
+                    doActionUpOrCancel();
+                    break;
+                default:
+                    if (intercept) {
+                        mCancelMethod = ON_TOUCH_EVENT;
+                    } else {
+                        mCancelMethod = ON_INTERCEPT_TOUCH_EVENT;
+                    }
+            }
+        }
+
+        if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
+            mInTouchEvent = false;
+        }
+
+        return intercept;
+    }
+
+    /**
+     * Set the width of each drag scroll region by specifying
+     * a fraction of the ListView height.
+     *
+     * @param heightFraction Fraction of ListView height. Capped at
+     * 0.5f.
+     * 
+     */
+    public void setDragScrollStart(float heightFraction) {
+        setDragScrollStarts(heightFraction, heightFraction);
+    }
+
+    /**
+     * Set the width of each drag scroll region by specifying
+     * a fraction of the ListView height.
+     *
+     * @param upperFrac Fraction of ListView height for up-scroll bound.
+     * Capped at 0.5f.
+     * @param lowerFrac Fraction of ListView height for down-scroll bound.
+     * Capped at 0.5f.
+     * 
+     */
+    public void setDragScrollStarts(float upperFrac, float lowerFrac) {
+        if (lowerFrac > 0.5f) {
+            mDragDownScrollStartFrac = 0.5f;
+        } else {
+            mDragDownScrollStartFrac = lowerFrac;
+        }
+
+        if (upperFrac > 0.5f) {
+            mDragUpScrollStartFrac = 0.5f;
+        } else {
+            mDragUpScrollStartFrac = upperFrac;
+        }
+
+        if (getHeight() != 0) {
+            updateScrollStarts();
+        }
+    }
+
+    private void continueDrag(int x, int y) {
+        // proposed position
+        mFloatLoc.x = x - mDragDeltaX;
+        mFloatLoc.y = y - mDragDeltaY;
+
+        doDragFloatView(true);
+
+        int minY = Math.min(y, mFloatViewMid + mFloatViewHeightHalf);
+        int maxY = Math.max(y, mFloatViewMid - mFloatViewHeightHalf);
+
+        // get the current scroll direction
+        int currentScrollDir = mDragScroller.getScrollDir();
+
+        if (minY > mLastY && minY > mDownScrollStartY && currentScrollDir != DragScroller.DOWN) {
+            // dragged down, it is below the down scroll start and it is not
+            // scrolling up
+
+            if (currentScrollDir != DragScroller.STOP) {
+                // moved directly from up scroll to down scroll
+                mDragScroller.stopScrolling(true);
+            }
+
+            // start scrolling down
+            mDragScroller.startScrolling(DragScroller.DOWN);
+        } else if (maxY < mLastY && maxY < mUpScrollStartY && currentScrollDir != DragScroller.UP) {
+            // dragged up, it is above the up scroll start and it is not
+            // scrolling up
+
+            if (currentScrollDir != DragScroller.STOP) {
+                // moved directly from down scroll to up scroll
+                mDragScroller.stopScrolling(true);
+            }
+
+            // start scrolling up
+            mDragScroller.startScrolling(DragScroller.UP);
+        }
+        else if (maxY >= mUpScrollStartY && minY <= mDownScrollStartY
+                && mDragScroller.isScrolling()) {
+            // not in the upper nor in the lower drag-scroll regions but it is
+            // still scrolling
+
+            mDragScroller.stopScrolling(true);
+        }
+    }
+
+    private void updateScrollStarts() {
+        final int padTop = getPaddingTop();
+        final int listHeight = getHeight() - padTop - getPaddingBottom();
+        float heightF = (float) listHeight;
+
+        mUpScrollStartYF = padTop + mDragUpScrollStartFrac * heightF;
+        mDownScrollStartYF = padTop + (1.0f - mDragDownScrollStartFrac) * heightF;
+
+        mUpScrollStartY = (int) mUpScrollStartYF;
+        mDownScrollStartY = (int) mDownScrollStartYF;
+
+        mDragUpScrollHeight = mUpScrollStartYF - padTop;
+        mDragDownScrollHeight = padTop + listHeight - mDownScrollStartYF;
+    }
+
+    @Override
+    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+        super.onSizeChanged(w, h, oldw, oldh);
+        updateScrollStarts();
+    }
+
+    private void adjustAllItems() {
+        final int first = getFirstVisiblePosition();
+        final int last = getLastVisiblePosition();
+
+        int begin = Math.max(0, getHeaderViewsCount() - first);
+        int end = Math.min(last - first, getCount() - 1 - getFooterViewsCount() - first);
+
+        for (int i = begin; i <= end; ++i) {
+            View v = getChildAt(i);
+            if (v != null) {
+                adjustItem(first + i, v, false);
+            }
+        }
+    }
+
+    /**
+     * Sets layout param height, gravity, and visibility  on
+     * wrapped item.
+     */
+    private void adjustItem(int position, View v, boolean invalidChildHeight) {
+        // Adjust item height
+        ViewGroup.LayoutParams lp = v.getLayoutParams();
+        int height;
+        if (position != mSrcPos && position != mFirstExpPos && position != mSecondExpPos) {
+            height = ViewGroup.LayoutParams.WRAP_CONTENT;
+        } else {
+            height = calcItemHeight(position, v, invalidChildHeight);
+        }
+
+        if (height != lp.height) {
+            lp.height = height;
+            v.setLayoutParams(lp);
+        }
+
+        // Adjust item gravity
+        if (position == mFirstExpPos || position == mSecondExpPos) {
+            if (position < mSrcPos) {
+                ((DragSortItemView) v).setGravity(Gravity.BOTTOM);
+            } else if (position > mSrcPos) {
+                ((DragSortItemView) v).setGravity(Gravity.TOP);
+            }
+        }
+
+        // Finally adjust item visibility
+        int oldVis = v.getVisibility();
+        int vis = View.VISIBLE;
+
+        if (position == mSrcPos && mFloatView != null) {
+            vis = View.INVISIBLE;
+        }
+
+        if (vis != oldVis) {
+            v.setVisibility(vis);
+        }
+    }
+
+    private int getChildHeight(int position) {
+        if (position == mSrcPos) {
+            return 0;
+        }
+
+        View v = getChildAt(position - getFirstVisiblePosition());
+
+        if (v != null) {
+            // item is onscreen, therefore child height is valid,
+            // hence the "true"
+            return getChildHeight(position, v, false);
+        } else {
+            // item is offscreen
+            // first check cache for child height at this position
+            int childHeight = mChildHeightCache.get(position);
+            if (childHeight != -1) {
+                return childHeight;
+            }
+
+            final ListAdapter adapter = getAdapter();
+            int type = adapter.getItemViewType(position);
+
+            // There might be a better place for checking for the following
+            final int typeCount = adapter.getViewTypeCount();
+            if (typeCount != mSampleViewTypes.length) {
+                mSampleViewTypes = new View[typeCount];
+            }
+
+            if (type >= 0) {
+                if (mSampleViewTypes[type] == null) {
+                    v = adapter.getView(position, null, this);
+                    mSampleViewTypes[type] = v;
+                } else {
+                    v = adapter.getView(position, mSampleViewTypes[type], this);
+                }
+            } else {
+                // type is HEADER_OR_FOOTER or IGNORE
+                v = adapter.getView(position, null, this);
+            }
+
+            // current child height is invalid, hence "true" below
+            childHeight = getChildHeight(position, v, true);
+
+            // cache it because this could have been expensive
+            mChildHeightCache.add(position, childHeight);
+
+            return childHeight;
+        }
+    }
+
+    private int getChildHeight(int position, View item, boolean invalidChildHeight) {
+        if (position == mSrcPos) {
+            return 0;
+        }
+
+        View child;
+        if (position < getHeaderViewsCount() || position >= getCount() - getFooterViewsCount()) {
+            child = item;
+        } else {
+            child = ((ViewGroup) item).getChildAt(0);
+        }
+
+        ViewGroup.LayoutParams lp = child.getLayoutParams();
+        if (lp != null) {
+            if (lp.height > 0) {
+                return lp.height;
+            }
+        }
+
+        int childHeight = child.getHeight();
+        if (childHeight == 0 || invalidChildHeight) {
+            measureItem(child);
+            childHeight = child.getMeasuredHeight();
+        }
+
+        return childHeight;
+    }
+
+    private int calcItemHeight(int position, View item, boolean invalidChildHeight) {
+        return calcItemHeight(position, getChildHeight(position, item, invalidChildHeight));
+    }
+
+    private int calcItemHeight(int position, int childHeight) {
+        boolean isSliding = mAnimate && mFirstExpPos != mSecondExpPos;
+        int maxNonSrcBlankHeight = mFloatViewHeight - mItemHeightCollapsed;
+        int slideHeight = (int) (mSlideFrac * maxNonSrcBlankHeight);
+
+        int height;
+
+        if (position == mSrcPos) {
+            if (mSrcPos == mFirstExpPos) {
+                if (isSliding) {
+                    height = slideHeight + mItemHeightCollapsed;
+                } else {
+                    height = mFloatViewHeight;
+                }
+            } else if (mSrcPos == mSecondExpPos) {
+                // if gets here, we know an item is sliding
+                height = mFloatViewHeight - slideHeight;
+            } else {
+                height = mItemHeightCollapsed;
+            }
+        } else if (position == mFirstExpPos) {
+            if (isSliding) {
+                height = childHeight + slideHeight;
+            } else {
+                height = childHeight + maxNonSrcBlankHeight;
+            }
+        } else if (position == mSecondExpPos) {
+            // we know an item is sliding (b/c 2ndPos != 1stPos)
+            height = childHeight + maxNonSrcBlankHeight - slideHeight;
+        } else {
+            height = childHeight;
+        }
+
+        return height;
+    }
+
+    @Override
+    public void requestLayout() {
+        if (!mBlockLayoutRequests) {
+            super.requestLayout();
+        }
+    }
+
+    private int adjustScroll(int movePos, View moveItem, int oldFirstExpPos, int oldSecondExpPos) {
+        int adjust = 0;
+
+        final int childHeight = getChildHeight(movePos);
+
+        int moveHeightBefore = moveItem.getHeight();
+        int moveHeightAfter = calcItemHeight(movePos, childHeight);
+
+        int moveBlankBefore = moveHeightBefore;
+        int moveBlankAfter = moveHeightAfter;
+        if (movePos != mSrcPos) {
+            moveBlankBefore -= childHeight;
+            moveBlankAfter -= childHeight;
+        }
+
+        int maxBlank = mFloatViewHeight;
+        if (mSrcPos != mFirstExpPos && mSrcPos != mSecondExpPos) {
+            maxBlank -= mItemHeightCollapsed;
+        }
+
+        if (movePos <= oldFirstExpPos) {
+            if (movePos > mFirstExpPos) {
+                adjust += maxBlank - moveBlankAfter;
+            }
+        } else if (movePos == oldSecondExpPos) {
+            if (movePos <= mFirstExpPos) {
+                adjust += moveBlankBefore - maxBlank;
+            } else if (movePos == mSecondExpPos) {
+                adjust += moveHeightBefore - moveHeightAfter;
+            } else {
+                adjust += moveBlankBefore;
+            }
+        } else {
+            if (movePos <= mFirstExpPos) {
+                adjust -= maxBlank;
+            } else if (movePos == mSecondExpPos) {
+                adjust -= moveBlankAfter;
+            }
+        }
+
+        return adjust;
+    }
+
+    private void measureItem(View item) {
+        ViewGroup.LayoutParams lp = item.getLayoutParams();
+        if (lp == null) {
+            lp = new AbsListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 
+            		ViewGroup.LayoutParams.WRAP_CONTENT);
+            item.setLayoutParams(lp);
+        }
+
+        int wspec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec, getListPaddingLeft()
+                + getListPaddingRight(), lp.width);
+        int hspec;
+        if (lp.height > 0) {
+            hspec = MeasureSpec.makeMeasureSpec(lp.height, MeasureSpec.EXACTLY);
+        } else {
+            hspec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+        }
+        item.measure(wspec, hspec);
+    }
+
+    private void measureFloatView() {
+        if (mFloatView != null) {
+            measureItem(mFloatView);
+            mFloatViewHeight = mFloatView.getMeasuredHeight();
+            mFloatViewHeightHalf = mFloatViewHeight / 2;
+        }
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        if (mFloatView != null) {
+            if (mFloatView.isLayoutRequested()) {
+                measureFloatView();
+            }
+            mFloatViewOnMeasured = true; // set to false after layout
+        }
+        mWidthMeasureSpec = widthMeasureSpec;
+    }
+
+    @Override
+    protected void layoutChildren() {
+        super.layoutChildren();
+
+        if (mFloatView != null) {
+            if (mFloatView.isLayoutRequested() && !mFloatViewOnMeasured) {
+                // Have to measure here when usual android measure
+                // pass is skipped. This happens during a drag-sort
+                // when layoutChildren is called directly.
+                measureFloatView();
+            }
+            mFloatView.layout(0, 0, mFloatView.getMeasuredWidth(), mFloatView.getMeasuredHeight());
+            mFloatViewOnMeasured = false;
+        }
+    }
+
+    protected boolean onDragTouchEvent(MotionEvent ev) {
+        int action = ev.getAction() & MotionEvent.ACTION_MASK;
+        switch (action) {
+            case MotionEvent.ACTION_CANCEL:
+                if (mDragState == DRAGGING) {
+                    cancelDrag();
+                }
+                doActionUpOrCancel();
+                break;
+            case MotionEvent.ACTION_UP:
+                if (mDragState == DRAGGING) {
+                    stopDrag(false);
+                }
+                doActionUpOrCancel();
+                break;
+            case MotionEvent.ACTION_MOVE:
+                continueDrag((int) ev.getX(), (int) ev.getY());
+                break;
+        }
+
+        return true;
+    }
+
+    /**
+     * Start a drag of item at <code>position</code> using the
+     * registered FloatViewManager. Calls through
+     * to {@link #startDrag(int,View,int,int,int)} after obtaining
+     * the floating View from the FloatViewManager.
+     *
+     * @param position Item to drag.
+     * @param dragFlags Flags that restrict some movements of the
+     * floating View. For example, set <code>dragFlags |= 
+     * ~{@link #DRAG_NEG_X}</code> to allow dragging the floating
+     * View in all directions except off the screen to the left.
+     * @param deltaX Offset in x of the touch coordinate from the
+     * left edge of the floating View (i.e. touch-x minus float View
+     * left).
+     * @param deltaY Offset in y of the touch coordinate from the
+     * top edge of the floating View (i.e. touch-y minus float View
+     * top).
+     *
+     * @return True if the drag was started, false otherwise. This
+     * <code>startDrag</code> will fail if we are not currently in
+     * a touch event, there is no registered FloatViewManager,
+     * or the FloatViewManager returns a null View.
+     */
+    public boolean startDrag(int position, int dragFlags, int deltaX, int deltaY) {
+        if (!mInTouchEvent || mFloatViewManager == null) {
+            return false;
+        }
+
+        View v = mFloatViewManager.onCreateFloatView(position);
+
+        if (v == null) {
+            return false;
+        } else {
+            return startDrag(position, v, dragFlags, deltaX, deltaY);
+        }
+
+    }
+
+    /**
+     * Start a drag of item at <code>position</code> without using
+     * a FloatViewManager.
+     *
+     * @param position Item to drag.
+     * @param floatView Floating View.
+     * @param dragFlags Flags that restrict some movements of the
+     * floating View. For example, set <code>dragFlags |= 
+     * ~{@link #DRAG_NEG_X}</code> to allow dragging the floating
+     * View in all directions except off the screen to the left.
+     * @param deltaX Offset in x of the touch coordinate from the
+     * left edge of the floating View (i.e. touch-x minus float View
+     * left).
+     * @param deltaY Offset in y of the touch coordinate from the
+     * top edge of the floating View (i.e. touch-y minus float View
+     * top).
+     *
+     * @return True if the drag was started, false otherwise. This
+     * <code>startDrag</code> will fail if we are not currently in
+     * a touch event, <code>floatView</code> is null, or there is
+     * a drag in progress.
+     */
+    public boolean startDrag(int position, View floatView, int dragFlags, int deltaX, int deltaY) {
+        if (mDragState != IDLE || !mInTouchEvent || mFloatView != null || floatView == null
+                || !mDragEnabled) {
+            return false;
+        }
+
+        if (getParent() != null) {
+            getParent().requestDisallowInterceptTouchEvent(true);
+        }
+
+        int pos = position + getHeaderViewsCount();
+        mFirstExpPos = pos;
+        mSecondExpPos = pos;
+        mSrcPos = pos;
+        mFloatPos = pos;
+
+        mDragState = DRAGGING;
+        mDragFlags = 0;
+        mDragFlags |= dragFlags;
+
+        mFloatView = floatView;
+        measureFloatView(); // sets mFloatViewHeight
+
+        mDragDeltaX = deltaX;
+        mDragDeltaY = deltaY;
+
+        mFloatLoc.x = mX - mDragDeltaX;
+        mFloatLoc.y = mY - mDragDeltaY;
+
+        // set src item invisible
+        final View srcItem = getChildAt(mSrcPos - getFirstVisiblePosition());
+
+        if (srcItem != null) {
+            srcItem.setVisibility(View.INVISIBLE);
+        }
+
+        if (mTrackDragSort) {
+            mDragSortTracker.startTracking();
+        }
+
+        // once float view is created, events are no longer passed
+        // to ListView
+        switch (mCancelMethod) {
+            case ON_TOUCH_EVENT:
+                super.onTouchEvent(mCancelEvent);
+                break;
+            case ON_INTERCEPT_TOUCH_EVENT:
+                super.onInterceptTouchEvent(mCancelEvent);
+                break;
+        }
+
+        requestLayout();
+
+        if (mLiftAnimator != null) {
+            mLiftAnimator.start();
+        }
+
+        return true;
+    }
+
+    private void doDragFloatView(boolean forceInvalidate) {
+        int movePos = getFirstVisiblePosition() + getChildCount() / 2;
+        View moveItem = getChildAt(getChildCount() / 2);
+
+        if (moveItem == null) {
+            return;
+        }
+
+        doDragFloatView(movePos, moveItem, forceInvalidate);
+    }
+
+    private void doDragFloatView(int movePos, View moveItem, boolean forceInvalidate) {
+        mBlockLayoutRequests = true;
+
+        updateFloatView();
+
+        int oldFirstExpPos = mFirstExpPos;
+        int oldSecondExpPos = mSecondExpPos;
+
+        boolean updated = updatePositions();
+
+        if (updated) {
+            adjustAllItems();
+            int scroll = adjustScroll(movePos, moveItem, oldFirstExpPos, oldSecondExpPos);
+
+            setSelectionFromTop(movePos, moveItem.getTop() + scroll - getPaddingTop());
+            layoutChildren();
+        }
+
+        if (updated || forceInvalidate) {
+            invalidate();
+        }
+
+        mBlockLayoutRequests = false;
+    }
+
+    /**
+     * Sets float View location based on suggested values and
+     * constraints set in mDragFlags.
+     */
+    private void updateFloatView() {
+
+        if (mFloatViewManager != null) {
+            mTouchLoc.set(mX, mY);
+            mFloatViewManager.onDragFloatView(mFloatView, mFloatLoc, mTouchLoc);
+        }
+
+        final int floatX = mFloatLoc.x;
+        final int floatY = mFloatLoc.y;
+
+        // restrict x motion
+        int padLeft = getPaddingLeft();
+        if ((mDragFlags & DRAG_POS_X) == 0 && floatX > padLeft) {
+            mFloatLoc.x = padLeft;
+        } else if ((mDragFlags & DRAG_NEG_X) == 0 && floatX < padLeft) {
+            mFloatLoc.x = padLeft;
+        }
+
+        // keep floating view from going past bottom of last header view
+        final int numHeaders = getHeaderViewsCount();
+        final int numFooters = getFooterViewsCount();
+        final int firstPos = getFirstVisiblePosition();
+        final int lastPos = getLastVisiblePosition();
+
+        int topLimit = getPaddingTop();
+        if (firstPos < numHeaders) {
+            topLimit = getChildAt(numHeaders - firstPos - 1).getBottom();
+        }
+        if ((mDragFlags & DRAG_NEG_Y) == 0) {
+            if (firstPos <= mSrcPos) {
+                topLimit = Math.max(getChildAt(mSrcPos - firstPos).getTop(), topLimit);
+            }
+        }
+        // bottom limit is top of first footer View or
+        // bottom of last item in list
+        int bottomLimit = getHeight() - getPaddingBottom();
+        if (lastPos >= getCount() - numFooters - 1) {
+            bottomLimit = getChildAt(getCount() - numFooters - 1 - firstPos).getBottom();
+        }
+        if ((mDragFlags & DRAG_POS_Y) == 0) {
+            if (lastPos >= mSrcPos) {
+                bottomLimit = Math.min(getChildAt(mSrcPos - firstPos).getBottom(), bottomLimit);
+            }
+        }
+
+        if (floatY < topLimit) {
+            mFloatLoc.y = topLimit;
+        } else if (floatY + mFloatViewHeight > bottomLimit) {
+            mFloatLoc.y = bottomLimit - mFloatViewHeight;
+        }
+
+        // get y-midpoint of floating view (constrained to ListView bounds)
+        mFloatViewMid = mFloatLoc.y + mFloatViewHeightHalf;
+    }
+
+    private void destroyFloatView() {
+        if (mFloatView != null) {
+            mFloatView.setVisibility(GONE);
+            if (mFloatViewManager != null) {
+                mFloatViewManager.onDestroyFloatView(mFloatView);
+            }
+            mFloatView = null;
+            invalidate();
+        }
+    }
+
+    /**
+     * Interface for customization of the floating View appearance
+     * and dragging behavior. Implement
+     * your own and pass it to {@link #setFloatViewManager}. If
+     * your own is not passed, the default {@link SimpleFloatViewManager}
+     * implementation is used.
+     */
+    public interface FloatViewManager {
+        /**
+         * Return the floating View for item at <code>position</code>.
+         * DragSortListView will measure and layout this View for you,
+         * so feel free to just inflate it. You can help DSLV by
+         * setting some {@link ViewGroup.LayoutParams} on this View;
+         * otherwise it will set some for you (with a width of FILL_PARENT
+         * and a height of WRAP_CONTENT).
+         *
+         * @param position Position of item to drag (NOTE:
+         * <code>position</code> excludes header Views; thus, if you
+         * want to call {@link ListView#getChildAt(int)}, you will need
+         * to add {@link ListView#getHeaderViewsCount()} to the index).
+         *
+         * @return The View you wish to display as the floating View.
+         */
+        public View onCreateFloatView(int position);
+
+        /**
+         * Called whenever the floating View is dragged. Float View
+         * properties can be changed here. Also, the upcoming location
+         * of the float View can be altered by setting
+         * <code>location.x</code> and <code>location.y</code>.
+         *
+         * @param floatView The floating View.
+         * @param location The location (top-left; relative to DSLV
+         * top-left) at which the float
+         * View would like to appear, given the current touch location
+         * and the offset provided in {@link DragSortListView#startDrag}.
+         * @param touch The current touch location (relative to DSLV
+         * top-left).
+         * @param pendingScroll 
+         */
+        public void onDragFloatView(View floatView, Point location, Point touch);
+
+        /**
+         * Called when the float View is dropped; lets you perform
+         * any necessary cleanup. The internal DSLV floating View
+         * reference is set to null immediately after this is called.
+         *
+         * @param floatView The floating View passed to
+         * {@link #onCreateFloatView(int)}.
+         */
+        public void onDestroyFloatView(View floatView);
+    }
+
+    public void setFloatViewManager(FloatViewManager manager) {
+        mFloatViewManager = manager;
+    }
+
+    public void setDragListener(DragListener l) {
+        mDragListener = l;
+    }
+
+    /**
+     * Allows for easy toggling between a DragSortListView
+     * and a regular old ListView. If enabled, items are
+     * draggable, where the drag init mode determines how
+     * items are lifted (see {@link setDragInitMode(int)}).
+     * If disabled, items cannot be dragged.
+     *
+     * @param enabled Set <code>true</code> to enable list
+     * item dragging
+     */
+    public void setDragEnabled(boolean enabled) {
+        mDragEnabled = enabled;
+    }
+
+    public boolean isDragEnabled() {
+        return mDragEnabled;
+    }
+
+    /**
+     * This better reorder your ListAdapter! DragSortListView does not do this
+     * for you; doesn't make sense to. Make sure
+     * {@link BaseAdapter#notifyDataSetChanged()} or something like it is called
+     * in your implementation. Furthermore, if you have a choiceMode other than
+     * none and the ListAdapter does not return true for
+     * {@link ListAdapter#hasStableIds()}, you will need to call
+     * {@link #moveCheckState(int, int)} to move the check boxes along with the
+     * list items.
+     * 
+     * @param l
+     */
+    public void setDropListener(DropListener l) {
+        mDropListener = l;
+    }
+
+    /**
+     * Probably a no-brainer, but make sure that your remove listener
+     * calls {@link BaseAdapter#notifyDataSetChanged()} or something like it.
+     * When an item removal occurs, DragSortListView
+     * relies on a redraw of all the items to recover invisible views
+     * and such. Strictly speaking, if you remove something, your dataset
+     * has changed...
+     * 
+     * @param l
+     */
+    public void setRemoveListener(RemoveListener l) {
+        mRemoveListener = l;
+    }
+
+    public interface DragListener {
+        public void drag(int from, int to);
+    }
+
+    /**
+     * Your implementation of this has to reorder your ListAdapter! 
+     * Make sure to call
+     * {@link BaseAdapter#notifyDataSetChanged()} or something like it
+     * in your implementation.
+     * 
+     * @author heycosmo
+     *
+     */
+    public interface DropListener {
+        public void drop(int from, int to);
+    }
+
+    /**
+     * Make sure to call
+     * {@link BaseAdapter#notifyDataSetChanged()} or something like it
+     * in your implementation.
+     * 
+     * @author heycosmo
+     *
+     */
+    public interface RemoveListener {
+        public void remove(int which);
+    }
+
+    public interface DragSortListener extends DropListener, DragListener, RemoveListener {
+    }
+
+    public void setDragSortListener(DragSortListener listener) {
+        setDropListener(listener);
+        setDragListener(listener);
+        setRemoveListener(listener);
+    }
+
+    /**
+     * Completely custom scroll speed profile. Default increases linearly
+     * with position and is constant in time. Create your own by implementing
+     * {@link DragSortListView.DragScrollProfile}.
+     * 
+     * @param scrollProfile
+     */
+    public void setDragScrollProfile(DragScrollProfile scrollProfile) {
+        if (scrollProfile != null) {
+            mScrollProfile = scrollProfile;
+        }
+    }
+
+    /**
+     * Use this to move the check state of an item from one position to another
+     * in a drop operation. If you have a choiceMode which is not none, this
+     * method must be called when the order of items changes in an underlying
+     * adapter which does not have stable IDs (see
+     * {@link ListAdapter#hasStableIds()}). This is because without IDs, the
+     * ListView has no way of knowing which items have moved where, and cannot
+     * update the check state accordingly.
+     * <p>
+     * A word of warning about a "feature" in Android that you may run into when
+     * dealing with movable list items: for an adapter that <em>does</em> have
+     * stable IDs, ListView will attempt to locate each item based on its ID and
+     * move the check state from the item's old position to the new position 鈥�
+     * which is all fine and good (and removes the need for calling this
+     * function), except for the half-baked approach. Apparently to save time in
+     * the naive algorithm used, ListView will only search for an ID in the
+     * close neighborhood of the old position. If the user moves an item too far
+     * (specifically, more than 20 rows away), ListView will give up and just
+     * force the item to be unchecked. So if there is a reasonable chance that
+     * the user will move items more than 20 rows away from the original
+     * position, you may wish to use an adapter with unstable IDs and call this
+     * method manually instead.
+     * 
+     * @param from
+     * @param to
+     */
+    public void moveCheckState(int from, int to) {
+        // This method runs in O(n log n) time (n being the number of list
+        // items). The bottleneck is the call to AbsListView.setItemChecked,
+        // which is O(log n) because of the binary search involved in calling
+        // SparseBooleanArray.put().
+        //
+        // To improve on the average time, we minimize the number of calls to
+        // setItemChecked by only calling it for items that actually have a
+        // changed state. This is achieved by building a list containing the
+        // start and end of the "runs" of checked items, and then moving the
+        // runs. Note that moving an item from A to B is essentially a rotation
+        // of the range of items in [A, B]. Let's say we have
+        // . . U V X Y Z . .
+        // and move U after Z. This is equivalent to a rotation one step to the
+        // left within the range you are moving across:
+        // . . V X Y Z U . .
+        //
+        // So, to perform the move we enumerate all the runs within the move
+        // range, then rotate each run one step to the left or right (depending
+        // on move direction). For example, in the list:
+        // X X . X X X . X
+        // we have two runs. One begins at the last item of the list and wraps
+        // around to the beginning, ending at position 1. The second begins at
+        // position 3 and ends at position 5. To rotate a run, regardless of
+        // length, we only need to set a check mark at one end of the run, and
+        // clear a check mark at the other end:
+        // X . X X X . X X
+        SparseBooleanArray cip = getCheckedItemPositions();
+        int rangeStart = from;
+        int rangeEnd = to;
+        if (to < from) {
+            rangeStart = to;
+            rangeEnd = from;
+        }
+        rangeEnd += 1;
+
+        int[] runStart = new int[cip.size()];
+        int[] runEnd = new int[cip.size()];
+        int runCount = buildRunList(cip, rangeStart, rangeEnd, runStart, runEnd);
+        if (runCount == 1 && (runStart[0] == runEnd[0])) {
+            // Special case where all items are checked, we can never set any
+            // item to false like we do below.
+            return;
+        }
+
+        if (from < to) {
+            for (int i = 0; i != runCount; i++) {
+                setItemChecked(rotate(runStart[i], -1, rangeStart, rangeEnd), true);
+                setItemChecked(rotate(runEnd[i], -1, rangeStart, rangeEnd), false);
+            }
+
+        } else {
+            for (int i = 0; i != runCount; i++) {
+                setItemChecked(runStart[i], false);
+                setItemChecked(runEnd[i], true);
+            }
+        }
+    }
+
+    /**
+     * Use this when an item has been deleted, to move the check state of all
+     * following items up one step. If you have a choiceMode which is not none,
+     * this method must be called when the order of items changes in an
+     * underlying adapter which does not have stable IDs (see
+     * {@link ListAdapter#hasStableIds()}). This is because without IDs, the
+     * ListView has no way of knowing which items have moved where, and cannot
+     * update the check state accordingly.
+     * 
+     * See also further comments on {@link #moveCheckState(int, int)}.
+     * 
+     * @param position
+     */
+    public void removeCheckState(int position) {
+        SparseBooleanArray cip = getCheckedItemPositions();
+
+        if (cip.size() == 0)
+            return;
+        int[] runStart = new int[cip.size()];
+        int[] runEnd = new int[cip.size()];
+        int rangeStart = position;
+        int rangeEnd = cip.keyAt(cip.size() - 1) + 1;
+        int runCount = buildRunList(cip, rangeStart, rangeEnd, runStart, runEnd);
+        for (int i = 0; i != runCount; i++) {
+            if (!(runStart[i] == position || (runEnd[i] < runStart[i] && runEnd[i] > position))) {
+                // Only set a new check mark in front of this run if it does
+                // not contain the deleted position. If it does, we only need
+                // to make it one check mark shorter at the end.
+                setItemChecked(rotate(runStart[i], -1, rangeStart, rangeEnd), true);
+            }
+            setItemChecked(rotate(runEnd[i], -1, rangeStart, rangeEnd), false);
+        }
+    }
+
+    private static int buildRunList(SparseBooleanArray cip, int rangeStart, int rangeEnd, 
+            int[] runStart, int[] runEnd) {
+        
+        int runCount = 0;
+
+        int i = findFirstSetIndex(cip, rangeStart, rangeEnd);
+        if (i == -1)
+            return 0;
+
+        int position = cip.keyAt(i);
+        int currentRunStart = position;
+        int currentRunEnd = currentRunStart + 1;
+        for (i++; i < cip.size() && (position = cip.keyAt(i)) < rangeEnd; i++) {
+            if (!cip.valueAt(i)) // not checked => not interesting
+                continue;
+            if (position == currentRunEnd) {
+                currentRunEnd++;
+            } else {
+                runStart[runCount] = currentRunStart;
+                runEnd[runCount] = currentRunEnd;
+                runCount++;
+                currentRunStart = position;
+                currentRunEnd = position + 1;
+            }
+        }
+
+        if (currentRunEnd == rangeEnd) {
+            // rangeStart and rangeEnd are equivalent positions so to be
+            // consistent we translate them to the same integer value. That way
+            // we can check whether a run covers the entire range by just
+            // checking if the start equals the end position.
+            currentRunEnd = rangeStart;
+        }
+        runStart[runCount] = currentRunStart;
+        runEnd[runCount] = currentRunEnd;
+        runCount++;
+
+        if (runCount > 1) {
+            if (runStart[0] == rangeStart && runEnd[runCount - 1] == rangeStart) {
+                // The last run ends at the end of the range, and the first run
+                // starts at the beginning of the range. So they are actually
+                // part of the same run, except they wrap around the end of the
+                // range. To avoid adjacent runs, we need to merge them.
+                runStart[0] = runStart[runCount - 1];
+                runCount--;
+            }
+        }
+        return runCount;
+    }
+
+    private static int rotate(int value, int offset, int lowerBound, int upperBound) {
+        int windowSize = upperBound - lowerBound;
+
+        value += offset;
+        if (value < lowerBound) {
+            value += windowSize;
+        } else if (value >= upperBound) {
+            value -= windowSize;
+        }
+        return value;
+    }
+
+    private static int findFirstSetIndex(SparseBooleanArray sba, int rangeStart, int rangeEnd) {
+        int size = sba.size();
+        int i = insertionIndexForKey(sba, rangeStart);
+        while (i < size && sba.keyAt(i) < rangeEnd && !sba.valueAt(i))
+            i++;
+        if (i == size || sba.keyAt(i) >= rangeEnd)
+            return -1;
+        return i;
+    }
+
+    private static int insertionIndexForKey(SparseBooleanArray sba, int key) {
+        int low = 0;
+        int high = sba.size();
+        while (high - low > 0) {
+            int middle = (low + high) >> 1;
+            if (sba.keyAt(middle) < key)
+                low = middle + 1;
+            else
+                high = middle;
+        }
+        return low;
+    }
+
+    /**
+     * Interface for controlling
+     * scroll speed as a function of touch position and time. Use
+     * {@link DragSortListView#setDragScrollProfile(DragScrollProfile)} to
+     * set custom profile.
+     * 
+     * @author heycosmo
+     *
+     */
+    public interface DragScrollProfile {
+        /**
+         * Return a scroll speed in pixels/millisecond. Always return a
+         * positive number.
+         * 
+         * @param w Normalized position in scroll region (i.e. w \in [0,1]).
+         * Small w typically means slow scrolling.
+         * @param t Time (in milliseconds) since start of scroll (handy if you
+         * want scroll acceleration).
+         * @return Scroll speed at position w and time t in pixels/ms.
+         */
+        float getSpeed(float w, long t);
+    }
+
+    private class DragScroller implements Runnable {
+
+        private boolean mAbort;
+
+        private long mPrevTime;
+        private long mCurrTime;
+
+        private int dy;
+        private float dt;
+        private long tStart;
+        private int scrollDir;
+
+        public final static int STOP = -1;
+        public final static int UP = 0;
+        public final static int DOWN = 1;
+
+        private float mScrollSpeed; // pixels per ms
+
+        private boolean mScrolling = false;
+
+        public boolean isScrolling() {
+            return mScrolling;
+        }
+
+        public int getScrollDir() {
+            return mScrolling ? scrollDir : STOP;
+        }
+
+        public DragScroller() {
+        }
+
+        public void startScrolling(int dir) {
+            if (!mScrolling) {
+                mAbort = false;
+                mScrolling = true;
+                tStart = SystemClock.uptimeMillis();
+                mPrevTime = tStart;
+                scrollDir = dir;
+                post(this);
+            }
+        }
+
+        public void stopScrolling(boolean now) {
+            if (now) {
+                DragSortListView.this.removeCallbacks(this);
+                mScrolling = false;
+            } else {
+                mAbort = true;
+            }
+        }
+
+        @Override
+        public void run() {
+            if (mAbort) {
+                mScrolling = false;
+                return;
+            }
+
+            final int first = getFirstVisiblePosition();
+            final int last = getLastVisiblePosition();
+            final int count = getCount();
+            final int padTop = getPaddingTop();
+            final int listHeight = getHeight() - padTop - getPaddingBottom();
+
+            int minY = Math.min(mY, mFloatViewMid + mFloatViewHeightHalf);
+            int maxY = Math.max(mY, mFloatViewMid - mFloatViewHeightHalf);
+
+            if (scrollDir == UP) {
+                View v = getChildAt(0);
+                if (v == null) {
+                    mScrolling = false;
+                    return;
+                } else {
+                    if (first == 0 && v.getTop() == padTop) {
+                        mScrolling = false;
+                        return;
+                    }
+                }
+                mScrollSpeed = mScrollProfile.getSpeed((mUpScrollStartYF - maxY)
+                        / mDragUpScrollHeight, mPrevTime);
+            } else {
+                View v = getChildAt(last - first);
+                if (v == null) {
+                    mScrolling = false;
+                    return;
+                } else {
+                    if (last == count - 1 && v.getBottom() <= listHeight + padTop) {
+                        mScrolling = false;
+                        return;
+                    }
+                }
+                mScrollSpeed = -mScrollProfile.getSpeed((minY - mDownScrollStartYF)
+                        / mDragDownScrollHeight, mPrevTime);
+            }
+
+            mCurrTime = SystemClock.uptimeMillis();
+            dt = (float) (mCurrTime - mPrevTime);
+
+            // dy is change in View position of a list item; i.e. positive dy
+            // means user is scrolling up (list item moves down the screen,
+            // remember
+            // y=0 is at top of View).
+            dy = (int) Math.round(mScrollSpeed * dt);
+
+            int movePos;
+            if (dy >= 0) {
+                dy = Math.min(listHeight, dy);
+                movePos = first;
+            } else {
+                dy = Math.max(-listHeight, dy);
+                movePos = last;
+            }
+
+            final View moveItem = getChildAt(movePos - first);
+            int top = moveItem.getTop() + dy;
+
+            if (movePos == 0 && top > padTop) {
+                top = padTop;
+            }
+
+            // always do scroll
+            mBlockLayoutRequests = true;
+
+            setSelectionFromTop(movePos, top - padTop);
+            DragSortListView.this.layoutChildren();
+            invalidate();
+
+            mBlockLayoutRequests = false;
+
+            // scroll means relative float View movement
+            doDragFloatView(movePos, moveItem, false);
+
+            mPrevTime = mCurrTime;
+
+            post(this);
+        }
+    }
+
+    // TODO: Bluuurgh... switch to SharedPreferences
+    private class DragSortTracker {
+        StringBuilder mBuilder = new StringBuilder();
+
+        File mFile;
+
+        private int mNumInBuffer = 0;
+        private int mNumFlushes = 0;
+
+        private boolean mTracking = false;
+
+        public DragSortTracker() {
+            File root = Environment.getExternalStorageDirectory();
+            mFile = new File(root, "dslv_state.txt");
+
+            if (!mFile.exists()) {
+                try {
+                    mFile.createNewFile();
+                    Log.d("mobeta", "file created");
+                } catch (IOException e) {
+                    Log.w("mobeta", "Could not create dslv_state.txt");
+                    Log.d("mobeta", e.getMessage());
+                }
+            }
+
+        }
+
+        public void startTracking() {
+            mBuilder.append("<DSLVStates>\n");
+            mNumFlushes = 0;
+            mTracking = true;
+        }
+
+        public void appendState() {
+            if (!mTracking) {
+                return;
+            }
+
+            mBuilder.append("<DSLVState>\n");
+            final int children = getChildCount();
+            final int first = getFirstVisiblePosition();
+            mBuilder.append("    <Positions>");
+            for (int i = 0; i < children; ++i) {
+                mBuilder.append(first + i).append(",");
+            }
+            mBuilder.append("</Positions>\n");
+
+            mBuilder.append("    <Tops>");
+            for (int i = 0; i < children; ++i) {
+                mBuilder.append(getChildAt(i).getTop()).append(",");
+            }
+            mBuilder.append("</Tops>\n");
+            mBuilder.append("    <Bottoms>");
+            for (int i = 0; i < children; ++i) {
+                mBuilder.append(getChildAt(i).getBottom()).append(",");
+            }
+            mBuilder.append("</Bottoms>\n");
+
+            mBuilder.append("    <FirstExpPos>").append(mFirstExpPos).append("</FirstExpPos>\n");
+            mBuilder.append("    <FirstExpBlankHeight>")
+                    .append(getItemHeight(mFirstExpPos) - getChildHeight(mFirstExpPos))
+                    .append("</FirstExpBlankHeight>\n");
+            mBuilder.append("    <SecondExpPos>").append(mSecondExpPos).append("</SecondExpPos>\n");
+            mBuilder.append("    <SecondExpBlankHeight>")
+                    .append(getItemHeight(mSecondExpPos) - getChildHeight(mSecondExpPos))
+                    .append("</SecondExpBlankHeight>\n");
+            mBuilder.append("    <SrcPos>").append(mSrcPos).append("</SrcPos>\n");
+            mBuilder.append("    <SrcHeight>").append(mFloatViewHeight + getDividerHeight())
+                    .append("</SrcHeight>\n");
+            mBuilder.append("    <ViewHeight>").append(getHeight()).append("</ViewHeight>\n");
+            mBuilder.append("    <LastY>").append(mLastY).append("</LastY>\n");
+            mBuilder.append("    <FloatY>").append(mFloatViewMid).append("</FloatY>\n");
+            mBuilder.append("    <ShuffleEdges>");
+            for (int i = 0; i < children; ++i) {
+                mBuilder.append(getShuffleEdge(first + i, getChildAt(i).getTop())).append(",");
+            }
+            mBuilder.append("</ShuffleEdges>\n");
+
+            mBuilder.append("</DSLVState>\n");
+            mNumInBuffer++;
+
+            if (mNumInBuffer > 1000) {
+                flush();
+                mNumInBuffer = 0;
+            }
+        }
+
+        public void flush() {
+            if (!mTracking) {
+                return;
+            }
+
+            // save to file on sdcard
+            try {
+                boolean append = true;
+                if (mNumFlushes == 0) {
+                    append = false;
+                }
+                FileWriter writer = new FileWriter(mFile, append);
+
+                writer.write(mBuilder.toString());
+                mBuilder.delete(0, mBuilder.length());
+
+                writer.flush();
+                writer.close();
+
+                mNumFlushes++;
+            } catch (IOException e) {
+                // do nothing
+            }
+        }
+
+        public void stopTracking() {
+            if (mTracking) {
+                mBuilder.append("</DSLVStates>\n");
+                flush();
+                mTracking = false;
+            }
+        }
+
+    }
+
+}
diff --git a/app/src/main/java/com/mobeta/android/dslv/ResourceDragSortCursorAdapter.java b/app/src/main/java/com/mobeta/android/dslv/ResourceDragSortCursorAdapter.java
new file mode 100644
index 0000000..b589447
--- /dev/null
+++ b/app/src/main/java/com/mobeta/android/dslv/ResourceDragSortCursorAdapter.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.mobeta.android.dslv;
+
+import android.content.Context;
+import android.database.Cursor;
+import androidx.cursoradapter.widget.CursorAdapter;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+// taken from v4 rev. 10 ResourceCursorAdapter.java
+
+/**
+ * Static library support version of the framework's {@link android.widget.ResourceCursorAdapter}.
+ * Used to write apps that run on platforms prior to Android 3.0.  When running
+ * on Android 3.0 or above, this implementation is still used; it does not try
+ * to switch to the framework's implementation.  See the framework SDK
+ * documentation for a class overview.
+ */
+public abstract class ResourceDragSortCursorAdapter extends DragSortCursorAdapter {
+    private int mLayout;
+
+    private int mDropDownLayout;
+    
+    private LayoutInflater mInflater;
+    
+    /**
+     * Constructor the enables auto-requery.
+     *
+     * @deprecated This option is discouraged, as it results in Cursor queries
+     * being performed on the application's UI thread and thus can cause poor
+     * responsiveness or even Application Not Responding errors.  As an alternative,
+     * use {@link android.app.LoaderManager} with a {@link android.content.CursorLoader}.
+     *
+     * @param context The context where the ListView associated with this adapter is running
+     * @param layout resource identifier of a layout file that defines the views
+     *            for this list item.  Unless you override them later, this will
+     *            define both the item views and the drop down views.
+     */
+    @Deprecated
+    public ResourceDragSortCursorAdapter(Context context, int layout, Cursor c) {
+        super(context, c);
+        mLayout = mDropDownLayout = layout;
+        mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+    }
+    
+    /**
+     * Constructor with default behavior as per
+     * {@link CursorAdapter#CursorAdapter(Context, Cursor, boolean)}; it is recommended
+     * you not use this, but instead {@link #ResourceCursorAdapter(Context, int, Cursor, int)}.
+     * When using this constructor, {@link #FLAG_REGISTER_CONTENT_OBSERVER}
+     * will always be set.
+     *
+     * @param context The context where the ListView associated with this adapter is running
+     * @param layout resource identifier of a layout file that defines the views
+     *            for this list item.  Unless you override them later, this will
+     *            define both the item views and the drop down views.
+     * @param c The cursor from which to get the data.
+     * @param autoRequery If true the adapter will call requery() on the
+     *                    cursor whenever it changes so the most recent
+     *                    data is always displayed.  Using true here is discouraged.
+     */
+    public ResourceDragSortCursorAdapter(Context context, int layout, Cursor c, 
+            boolean autoRequery) {
+        
+        super(context, c, autoRequery);
+        mLayout = mDropDownLayout = layout;
+        mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+    }
+
+    /**
+     * Standard constructor.
+     *
+     * @param context The context where the ListView associated with this adapter is running
+     * @param layout Resource identifier of a layout file that defines the views
+     *            for this list item.  Unless you override them later, this will
+     *            define both the item views and the drop down views.
+     * @param c The cursor from which to get the data.
+     * @param flags Flags used to determine the behavior of the adapter,
+     * as per {@link CursorAdapter#CursorAdapter(Context, Cursor, int)}.
+     */
+    public ResourceDragSortCursorAdapter(Context context, int layout, Cursor c, int flags) {
+        super(context, c, flags);
+        mLayout = mDropDownLayout = layout;
+        mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+    }
+
+    /**
+     * Inflates view(s) from the specified XML file.
+     * 
+     * @see android.widget.CursorAdapter#newView(android.content.Context,
+     *      android.database.Cursor, ViewGroup)
+     */
+    @Override
+    public View newView(Context context, Cursor cursor, ViewGroup parent) {
+        return mInflater.inflate(mLayout, parent, false);
+    }
+
+    @Override
+    public View newDropDownView(Context context, Cursor cursor, ViewGroup parent) {
+        return mInflater.inflate(mDropDownLayout, parent, false);
+    }
+
+    /**
+     * <p>Sets the layout resource of the item views.</p>
+     *
+     * @param layout the layout resources used to create item views
+     */
+    public void setViewResource(int layout) {
+        mLayout = layout;
+    }
+    
+    /**
+     * <p>Sets the layout resource of the drop down views.</p>
+     *
+     * @param dropDownLayout the layout resources used to create drop down views
+     */
+    public void setDropDownViewResource(int dropDownLayout) {
+        mDropDownLayout = dropDownLayout;
+    }
+}
diff --git a/app/src/main/java/com/mobeta/android/dslv/SimpleDragSortCursorAdapter.java b/app/src/main/java/com/mobeta/android/dslv/SimpleDragSortCursorAdapter.java
new file mode 100644
index 0000000..fa4e669
--- /dev/null
+++ b/app/src/main/java/com/mobeta/android/dslv/SimpleDragSortCursorAdapter.java
@@ -0,0 +1,427 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.mobeta.android.dslv;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import androidx.cursoradapter.widget.CursorAdapter;
+import androidx.cursoradapter.widget.SimpleCursorAdapter;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+// taken from sdk/sources/android-16/android/widget/SimpleCursorAdapter.java
+
+/**
+ * An easy adapter to map columns from a cursor to TextViews or ImageViews
+ * defined in an XML file. You can specify which columns you want, which
+ * views you want to display the columns, and the XML file that defines
+ * the appearance of these views.
+ *
+ * Binding occurs in two phases. First, if a
+ * {@link android.widget.SimpleCursorAdapter.ViewBinder} is available,
+ * {@link ViewBinder#setViewValue(android.view.View, android.database.Cursor, int)}
+ * is invoked. If the returned value is true, binding has occured. If the
+ * returned value is false and the view to bind is a TextView,
+ * {@link #setViewText(TextView, String)} is invoked. If the returned value
+ * is false and the view to bind is an ImageView,
+ * {@link #setViewImage(ImageView, String)} is invoked. If no appropriate
+ * binding can be found, an {@link IllegalStateException} is thrown.
+ *
+ * If this adapter is used with filtering, for instance in an
+ * {@link android.widget.AutoCompleteTextView}, you can use the
+ * {@link android.widget.SimpleCursorAdapter.CursorToStringConverter} and the
+ * {@link android.widget.FilterQueryProvider} interfaces
+ * to get control over the filtering process. You can refer to
+ * {@link #convertToString(android.database.Cursor)} and
+ * {@link #runQueryOnBackgroundThread(CharSequence)} for more information.
+ */
+public class SimpleDragSortCursorAdapter extends ResourceDragSortCursorAdapter {
+    /**
+     * A list of columns containing the data to bind to the UI.
+     * This field should be made private, so it is hidden from the SDK.
+     * {@hide}
+     */
+    protected int[] mFrom;
+    /**
+     * A list of View ids representing the views to which the data must be bound.
+     * This field should be made private, so it is hidden from the SDK.
+     * {@hide}
+     */
+    protected int[] mTo;
+
+    private int mStringConversionColumn = -1;
+    private CursorToStringConverter mCursorToStringConverter;
+    private ViewBinder mViewBinder;
+
+    String[] mOriginalFrom;
+
+    /**
+     * Constructor the enables auto-requery.
+     *
+     * @deprecated This option is discouraged, as it results in Cursor queries
+     * being performed on the application's UI thread and thus can cause poor
+     * responsiveness or even Application Not Responding errors.  As an alternative,
+     * use {@link android.app.LoaderManager} with a {@link android.content.CursorLoader}.
+     */
+    @Deprecated
+    public SimpleDragSortCursorAdapter(Context context, int layout, Cursor c, String[] from, 
+            int[] to) {
+
+        super(context, layout, c);
+        mTo = to;
+        mOriginalFrom = from;
+        findColumns(c, from);
+    }
+
+    /**
+     * Standard constructor.
+     * 
+     * @param context The context where the ListView associated with this
+     *            SimpleListItemFactory is running
+     * @param layout resource identifier of a layout file that defines the views
+     *            for this list item. The layout file should include at least
+     *            those named views defined in "to"
+     * @param c The database cursor.  Can be null if the cursor is not available yet.
+     * @param from A list of column names representing the data to bind to the UI.  Can be null 
+     *            if the cursor is not available yet.
+     * @param to The views that should display column in the "from" parameter.
+     *            These should all be TextViews. The first N views in this list
+     *            are given the values of the first N columns in the from
+     *            parameter.  Can be null if the cursor is not available yet.
+     * @param flags Flags used to determine the behavior of the adapter,
+     * as per {@link CursorAdapter#CursorAdapter(Context, Cursor, int)}.
+     */
+    public SimpleDragSortCursorAdapter(Context context, int layout, Cursor c, String[] from, 
+            int[] to, int flags) {
+        
+        super(context, layout, c, flags);
+        mTo = to;
+        mOriginalFrom = from;
+        findColumns(c, from);
+    }
+
+    /**
+     * Binds all of the field names passed into the "to" parameter of the
+     * constructor with their corresponding cursor columns as specified in the
+     * "from" parameter.
+     *
+     * Binding occurs in two phases. First, if a
+     * {@link android.widget.SimpleCursorAdapter.ViewBinder} is available,
+     * {@link ViewBinder#setViewValue(android.view.View, android.database.Cursor, int)}
+     * is invoked. If the returned value is true, binding has occured. If the
+     * returned value is false and the view to bind is a TextView,
+     * {@link #setViewText(TextView, String)} is invoked. If the returned value is
+     * false and the view to bind is an ImageView,
+     * {@link #setViewImage(ImageView, String)} is invoked. If no appropriate
+     * binding can be found, an {@link IllegalStateException} is thrown.
+     *
+     * @throws IllegalStateException if binding cannot occur
+     * 
+     * @see android.widget.CursorAdapter#bindView(android.view.View,
+     *      android.content.Context, android.database.Cursor)
+     * @see #getViewBinder()
+     * @see #setViewBinder(android.widget.SimpleCursorAdapter.ViewBinder)
+     * @see #setViewImage(ImageView, String)
+     * @see #setViewText(TextView, String)
+     */
+    @Override
+    public void bindView(View view, Context context, Cursor cursor) {
+        final ViewBinder binder = mViewBinder;
+        final int count = mTo.length;
+        final int[] from = mFrom;
+        final int[] to = mTo;
+
+        for (int i = 0; i < count; i++) {
+            final View v = view.findViewById(to[i]);
+            if (v != null) {
+                boolean bound = false;
+                if (binder != null) {
+                    bound = binder.setViewValue(v, cursor, from[i]);
+                }
+
+                if (!bound) {
+                    String text = cursor.getString(from[i]);
+                    if (text == null) {
+                        text = "";
+                    }
+
+                    if (v instanceof TextView) {
+                        setViewText((TextView) v, text);
+                    } else if (v instanceof ImageView) {
+                        setViewImage((ImageView) v, text);
+                    } else {
+                        throw new IllegalStateException(v.getClass().getName() + " is not a " +
+                                " view that can be bounds by this SimpleCursorAdapter");
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Returns the {@link ViewBinder} used to bind data to views.
+     *
+     * @return a ViewBinder or null if the binder does not exist
+     *
+     * @see #bindView(android.view.View, android.content.Context, android.database.Cursor)
+     * @see #setViewBinder(android.widget.SimpleCursorAdapter.ViewBinder)
+     */
+    public ViewBinder getViewBinder() {
+        return mViewBinder;
+    }
+
+    /**
+     * Sets the binder used to bind data to views.
+     *
+     * @param viewBinder the binder used to bind data to views, can be null to
+     *        remove the existing binder
+     *
+     * @see #bindView(android.view.View, android.content.Context, android.database.Cursor)
+     * @see #getViewBinder()
+     */
+    public void setViewBinder(ViewBinder viewBinder) {
+        mViewBinder = viewBinder;
+    }
+
+    /**
+     * Called by bindView() to set the image for an ImageView but only if
+     * there is no existing ViewBinder or if the existing ViewBinder cannot
+     * handle binding to an ImageView.
+     *
+     * By default, the value will be treated as an image resource. If the
+     * value cannot be used as an image resource, the value is used as an
+     * image Uri.
+     *
+     * Intended to be overridden by Adapters that need to filter strings
+     * retrieved from the database.
+     *
+     * @param v ImageView to receive an image
+     * @param value the value retrieved from the cursor
+     */
+    public void setViewImage(ImageView v, String value) {
+        try {
+            v.setImageResource(Integer.parseInt(value));
+        } catch (NumberFormatException nfe) {
+            v.setImageURI(Uri.parse(value));
+        }
+    }
+
+    /**
+     * Called by bindView() to set the text for a TextView but only if
+     * there is no existing ViewBinder or if the existing ViewBinder cannot
+     * handle binding to a TextView.
+     *
+     * Intended to be overridden by Adapters that need to filter strings
+     * retrieved from the database.
+     * 
+     * @param v TextView to receive text
+     * @param text the text to be set for the TextView
+     */    
+    public void setViewText(TextView v, String text) {
+        v.setText(text);
+    }
+
+    /**
+     * Return the index of the column used to get a String representation
+     * of the Cursor.
+     *
+     * @return a valid index in the current Cursor or -1
+     *
+     * @see android.widget.CursorAdapter#convertToString(android.database.Cursor)
+     * @see #setStringConversionColumn(int) 
+     * @see #setCursorToStringConverter(android.widget.SimpleCursorAdapter.CursorToStringConverter)
+     * @see #getCursorToStringConverter()
+     */
+    public int getStringConversionColumn() {
+        return mStringConversionColumn;
+    }
+
+    /**
+     * Defines the index of the column in the Cursor used to get a String
+     * representation of that Cursor. The column is used to convert the
+     * Cursor to a String only when the current CursorToStringConverter
+     * is null.
+     *
+     * @param stringConversionColumn a valid index in the current Cursor or -1 to use the default
+     *        conversion mechanism
+     *
+     * @see android.widget.CursorAdapter#convertToString(android.database.Cursor)
+     * @see #getStringConversionColumn()
+     * @see #setCursorToStringConverter(android.widget.SimpleCursorAdapter.CursorToStringConverter)
+     * @see #getCursorToStringConverter()
+     */
+    public void setStringConversionColumn(int stringConversionColumn) {
+        mStringConversionColumn = stringConversionColumn;
+    }
+
+    /**
+     * Returns the converter used to convert the filtering Cursor
+     * into a String.
+     *
+     * @return null if the converter does not exist or an instance of
+     *         {@link android.widget.SimpleCursorAdapter.CursorToStringConverter}
+     *
+     * @see #setCursorToStringConverter(android.widget.SimpleCursorAdapter.CursorToStringConverter)
+     * @see #getStringConversionColumn()
+     * @see #setStringConversionColumn(int)
+     * @see android.widget.CursorAdapter#convertToString(android.database.Cursor)
+     */
+    public CursorToStringConverter getCursorToStringConverter() {
+        return mCursorToStringConverter;
+    }
+
+    /**
+     * Sets the converter  used to convert the filtering Cursor
+     * into a String.
+     *
+     * @param cursorToStringConverter the Cursor to String converter, or
+     *        null to remove the converter
+     *
+     * @see #setCursorToStringConverter(android.widget.SimpleCursorAdapter.CursorToStringConverter) 
+     * @see #getStringConversionColumn()
+     * @see #setStringConversionColumn(int)
+     * @see android.widget.CursorAdapter#convertToString(android.database.Cursor)
+     */
+    public void setCursorToStringConverter(CursorToStringConverter cursorToStringConverter) {
+        mCursorToStringConverter = cursorToStringConverter;
+    }
+
+    /**
+     * Returns a CharSequence representation of the specified Cursor as defined
+     * by the current CursorToStringConverter. If no CursorToStringConverter
+     * has been set, the String conversion column is used instead. If the
+     * conversion column is -1, the returned String is empty if the cursor
+     * is null or Cursor.toString().
+     *
+     * @param cursor the Cursor to convert to a CharSequence
+     *
+     * @return a non-null CharSequence representing the cursor
+     */
+    @Override
+    public CharSequence convertToString(Cursor cursor) {
+        if (mCursorToStringConverter != null) {
+            return mCursorToStringConverter.convertToString(cursor);
+        } else if (mStringConversionColumn > -1) {
+            return cursor.getString(mStringConversionColumn);
+        }
+
+        return super.convertToString(cursor);
+    }
+
+    /**
+     * Create a map from an array of strings to an array of column-id integers in cursor c.
+     * If c is null, the array will be discarded.
+     *
+     * @param c the cursor to find the columns from
+     * @param from the Strings naming the columns of interest
+     */
+    private void findColumns(Cursor c, String[] from) {
+        if (c != null) {
+            int i;
+            int count = from.length;
+            if (mFrom == null || mFrom.length != count) {
+                mFrom = new int[count];
+            }
+            for (i = 0; i < count; i++) {
+                mFrom[i] = c.getColumnIndexOrThrow(from[i]);
+            }
+        } else {
+            mFrom = null;
+        }
+    }
+
+    @Override
+    public Cursor swapCursor(Cursor c) {
+        // super.swapCursor() will notify observers before we have
+        // a valid mapping, make sure we have a mapping before this
+        // happens
+        findColumns(c, mOriginalFrom);
+        return super.swapCursor(c);
+    }
+    
+    /**
+     * Change the cursor and change the column-to-view mappings at the same time.
+     *  
+     * @param c The database cursor.  Can be null if the cursor is not available yet.
+     * @param from A list of column names representing the data to bind to the UI.  Can be null 
+     *            if the cursor is not available yet.
+     * @param to The views that should display column in the "from" parameter.
+     *            These should all be TextViews. The first N views in this list
+     *            are given the values of the first N columns in the from
+     *            parameter.  Can be null if the cursor is not available yet.
+     */
+    public void changeCursorAndColumns(Cursor c, String[] from, int[] to) {
+        mOriginalFrom = from;
+        mTo = to;
+        // super.changeCursor() will notify observers before we have
+        // a valid mapping, make sure we have a mapping before this
+        // happens
+        findColumns(c, mOriginalFrom);
+        super.changeCursor(c);
+    }
+
+    /**
+     * This class can be used by external clients of SimpleCursorAdapter
+     * to bind values fom the Cursor to views.
+     *
+     * You should use this class to bind values from the Cursor to views
+     * that are not directly supported by SimpleCursorAdapter or to
+     * change the way binding occurs for views supported by
+     * SimpleCursorAdapter.
+     *
+     * @see SimpleCursorAdapter#bindView(android.view.View, android.content.Context, android.database.Cursor)
+     * @see SimpleCursorAdapter#setViewImage(ImageView, String) 
+     * @see SimpleCursorAdapter#setViewText(TextView, String)
+     */
+    public static interface ViewBinder {
+        /**
+         * Binds the Cursor column defined by the specified index to the specified view.
+         *
+         * When binding is handled by this ViewBinder, this method must return true.
+         * If this method returns false, SimpleCursorAdapter will attempts to handle
+         * the binding on its own.
+         *
+         * @param view the view to bind the data to
+         * @param cursor the cursor to get the data from
+         * @param columnIndex the column at which the data can be found in the cursor
+         *
+         * @return true if the data was bound to the view, false otherwise
+         */
+        boolean setViewValue(View view, Cursor cursor, int columnIndex);
+    }
+
+    /**
+     * This class can be used by external clients of SimpleCursorAdapter
+     * to define how the Cursor should be converted to a String.
+     *
+     * @see android.widget.CursorAdapter#convertToString(android.database.Cursor)
+     */
+    public static interface CursorToStringConverter {
+        /**
+         * Returns a CharSequence representing the specified Cursor.
+         *
+         * @param cursor the cursor for which a CharSequence representation
+         *        is requested
+         *
+         * @return a non-null CharSequence representing the cursor
+         */
+        CharSequence convertToString(Cursor cursor);
+    }
+
+}
diff --git a/app/src/main/java/com/mobeta/android/dslv/SimpleFloatViewManager.java b/app/src/main/java/com/mobeta/android/dslv/SimpleFloatViewManager.java
new file mode 100644
index 0000000..49cea14
--- /dev/null
+++ b/app/src/main/java/com/mobeta/android/dslv/SimpleFloatViewManager.java
@@ -0,0 +1,85 @@
+package com.mobeta.android.dslv;
+
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.graphics.Point;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.ListView;
+
+/**
+ * Simple implementation of the FloatViewManager class. Uses list
+ * items as they appear in the ListView to create the floating View.
+ */
+public class SimpleFloatViewManager implements DragSortListView.FloatViewManager {
+
+    private Bitmap mFloatBitmap;
+
+    private ImageView mImageView;
+
+    private int mFloatBGColor = Color.BLACK;
+
+    private ListView mListView;
+
+    public SimpleFloatViewManager(ListView lv) {
+        mListView = lv;
+    }
+
+    public void setBackgroundColor(int color) {
+        mFloatBGColor = color;
+    }
+
+    /**
+     * This simple implementation creates a Bitmap copy of the
+     * list item currently shown at ListView <code>position</code>.
+     */
+    @Override
+    public View onCreateFloatView(int position) {
+        // Guaranteed that this will not be null? I think so. Nope, got
+        // a NullPointerException once...
+        View v = mListView.getChildAt(position + mListView.getHeaderViewsCount() 
+        		- mListView.getFirstVisiblePosition());
+
+        if (v == null) {
+            return null;
+        }
+
+        v.setPressed(false);
+
+        // Create a copy of the drawing cache so that it does not get
+        // recycled by the framework when the list tries to clean up memory
+        v.setDrawingCacheEnabled(true);
+        mFloatBitmap = Bitmap.createBitmap(v.getDrawingCache());
+        v.setDrawingCacheEnabled(false);
+
+        if (mImageView == null) {
+            mImageView = new ImageView(mListView.getContext());
+        }
+        mImageView.setBackgroundColor(mFloatBGColor);
+        mImageView.setPadding(0, 0, 0, 0);
+        mImageView.setImageBitmap(mFloatBitmap);
+        mImageView.setLayoutParams(new ViewGroup.LayoutParams(v.getWidth(), v.getHeight()));
+
+        return mImageView;
+    }
+
+    @Override
+    public void onDragFloatView(View floatView, Point position, Point touch) {
+        // Do nothing so we have a concrete class
+    }
+
+    /**
+     * Removes the Bitmap from the ImageView created in
+     * onCreateFloatView() and tells the system to recycle it.
+     */
+    @Override
+    public void onDestroyFloatView(View floatView) {
+        ((ImageView) floatView).setImageDrawable(null);
+
+        mFloatBitmap.recycle();
+        mFloatBitmap = null;
+    }
+
+}
+
diff --git a/app/src/main/java/lineageos/app/LineageContextConstants.java b/app/src/main/java/lineageos/app/LineageContextConstants.java
new file mode 100644
index 0000000..a5ab9d3
--- /dev/null
+++ b/app/src/main/java/lineageos/app/LineageContextConstants.java
@@ -0,0 +1,31 @@
+/**
+ * Copyright (c) 2015, The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package lineageos.app;
+
+
+public final class LineageContextConstants {
+
+    private LineageContextConstants() {
+        // Empty constructor
+    }
+
+    public static final String LINEAGE_WEATHER_SERVICE = "lineageweather";
+
+    public static class Features {
+        public static final String WEATHER_SERVICES = "org.lineageos.weather";
+    }
+}
diff --git a/app/src/main/java/lineageos/os/Build.java b/app/src/main/java/lineageos/os/Build.java
new file mode 100644
index 0000000..61d475b
--- /dev/null
+++ b/app/src/main/java/lineageos/os/Build.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2015 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package lineageos.os;
+
+
+public class Build {
+    public static class LINEAGE_VERSION_CODES {
+        public static final int APRICOT = 1;
+        public static final int BOYSENBERRY = 2;
+        public static final int CANTALOUPE = 3;
+        public static final int DRAGON_FRUIT = 4;
+        public static final int ELDERBERRY = 5;
+        public static final int FIG = 6;
+        public static final int GUAVA = 7;
+        public static final int HACKBERRY = 8;
+        public static final int ILAMA = 9;
+    }
+}
diff --git a/app/src/main/java/lineageos/os/Concierge.java b/app/src/main/java/lineageos/os/Concierge.java
new file mode 100644
index 0000000..d4f9b4e
--- /dev/null
+++ b/app/src/main/java/lineageos/os/Concierge.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2016 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package lineageos.os;
+
+import android.os.Parcel;
+
+import lineageos.os.Build.LINEAGE_VERSION_CODES;
+
+/**
+ * Simply, Concierge handles your parcels and makes sure they get marshalled and unmarshalled
+ * correctly when cross IPC boundaries even when there is a version mismatch between the client
+ * sdk level and the framework implementation.
+ *
+ * <p>On incoming parcel (to be unmarshalled):
+ *
+ * <pre class="prettyprint">
+ *     ParcelInfo incomingParcelInfo = Concierge.receiveParcel(incomingParcel);
+ *     int parcelableVersion = incomingParcelInfo.getParcelVersion();
+ *
+ *     // Do unmarshalling steps here iterating over every plausible version
+ *
+ *     // Complete the process
+ *     incomingParcelInfo.complete();
+ * </pre>
+ *
+ * <p>On outgoing parcel (to be marshalled):
+ *
+ * <pre class="prettyprint">
+ *     ParcelInfo outgoingParcelInfo = Concierge.prepareParcel(incomingParcel);
+ *
+ *     // Do marshalling steps here iterating over every plausible version
+ *
+ *     // Complete the process
+ *     outgoingParcelInfo.complete();
+ * </pre>
+ */
+public final class Concierge {
+
+    /** Not instantiable */
+    private Concierge() {
+        // Don't instantiate
+    }
+
+    /**
+     * Since there might be a case where new versions of the lineage framework use applications running
+     * old versions of the protocol (and thus old versions of this class), we need a versioning
+     * system for the parcels sent between the core framework and its sdk users.
+     *
+     * This parcelable version should be the latest version API version listed in
+     * {@link LINEAGE_VERSION_CODES}
+     * @hide
+     */
+    public static final int PARCELABLE_VERSION = LINEAGE_VERSION_CODES.ILAMA;
+
+    /**
+     * Tell the concierge to receive our parcel, so we can get information from it.
+     *
+     * MUST CALL {@link ParcelInfo#complete()} AFTER UNMARSHALLING.
+     *
+     * @param parcel Incoming parcel to be unmarshalled
+     * @return {@link ParcelInfo} containing parcel information, specifically the version.
+     */
+    public static ParcelInfo receiveParcel(Parcel parcel) {
+        return new ParcelInfo(parcel);
+    }
+
+    /**
+     * Prepare a parcel for the Concierge.
+     *
+     * MUST CALL {@link ParcelInfo#complete()} AFTER MARSHALLING.
+     *
+     * @param parcel Outgoing parcel to be marshalled
+     * @return {@link ParcelInfo} containing parcel information, specifically the version.
+     */
+    public static ParcelInfo prepareParcel(Parcel parcel) {
+        return new ParcelInfo(parcel, PARCELABLE_VERSION);
+    }
+
+    /**
+     * Parcel header info specific to the Parcel object that is passed in via
+     * {@link #prepareParcel(Parcel)} or {@link #receiveParcel(Parcel)}. The exposed method
+     * of {@link #getParcelVersion()} gets the api level of the parcel object.
+     */
+    public final static class ParcelInfo {
+        private Parcel mParcel;
+        private int mParcelableVersion;
+        private int mParcelableSize;
+        private int mStartPosition;
+        private int mSizePosition;
+        private boolean mCreation = false;
+
+        ParcelInfo(Parcel parcel) {
+            mCreation = false;
+            mParcel = parcel;
+            mParcelableVersion = parcel.readInt();
+            mParcelableSize = parcel.readInt();
+            mStartPosition = parcel.dataPosition();
+        }
+
+        ParcelInfo(Parcel parcel, int parcelableVersion) {
+            mCreation = true;
+            mParcel = parcel;
+            mParcelableVersion = parcelableVersion;
+
+            // Write parcelable version, make sure to define explicit changes
+            // within {@link #PARCELABLE_VERSION);
+            mParcel.writeInt(mParcelableVersion);
+
+            // Inject a placeholder that will store the parcel size from this point on
+            // (not including the size itself).
+            mSizePosition = parcel.dataPosition();
+            mParcel.writeInt(0);
+            mStartPosition = parcel.dataPosition();
+        }
+
+        /**
+         * Get the parcel version from the {@link Parcel} received by the Concierge.
+         * @return {@link #PARCELABLE_VERSION} of the {@link Parcel}
+         */
+        public int getParcelVersion() {
+            return mParcelableVersion;
+        }
+
+        /**
+         * Complete the {@link ParcelInfo} for the Concierge.
+         */
+        public void complete() {
+            if (mCreation) {
+                // Go back and write size
+                mParcelableSize = mParcel.dataPosition() - mStartPosition;
+                mParcel.setDataPosition(mSizePosition);
+                mParcel.writeInt(mParcelableSize);
+                mParcel.setDataPosition(mStartPosition + mParcelableSize);
+            } else {
+                mParcel.setDataPosition(mStartPosition + mParcelableSize);
+            }
+        }
+    }
+}
diff --git a/app/src/main/java/lineageos/providers/WeatherContract.java b/app/src/main/java/lineageos/providers/WeatherContract.java
new file mode 100644
index 0000000..b352db5
--- /dev/null
+++ b/app/src/main/java/lineageos/providers/WeatherContract.java
@@ -0,0 +1,245 @@
+/*
+ * Copyright (C) 2016 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package lineageos.providers;
+
+import android.net.Uri;
+
+/**
+ * The contract between the weather provider and applications.
+ */
+public class WeatherContract {
+
+    /**
+     * The authority of the weather content provider
+     */
+    public static final String AUTHORITY = "org.lineageos.weather";
+
+    /**
+     * A content:// style uri to the authority for the weather provider
+     */
+    public static final Uri AUTHORITY_URI = Uri.parse("content://" + AUTHORITY);
+
+    public static class WeatherColumns {
+        public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "weather");
+
+        public static final Uri CURRENT_AND_FORECAST_WEATHER_URI
+                = Uri.withAppendedPath(CONTENT_URI, "current_and_forecast");
+        public static final Uri CURRENT_WEATHER_URI
+                = Uri.withAppendedPath(CONTENT_URI, "current");
+        public static final Uri FORECAST_WEATHER_URI
+                = Uri.withAppendedPath(CONTENT_URI, "forecast");
+
+        /**
+         * The city name
+         * <P>Type: TEXT</P>
+         */
+        public static final String CURRENT_CITY = "city";
+
+        /**
+         * A Valid {@link WeatherCode}
+         * <P>Type: INTEGER</P>
+         */
+        public static final String CURRENT_CONDITION_CODE = "condition_code";
+
+
+        /**
+         * A localized string mapped to the current weather condition code. Note that, if no
+         * locale is found, the string will be in english
+         * <P>Type: TEXT</P>
+         */
+        public static final String CURRENT_CONDITION = "condition";
+
+        /**
+         * The current weather temperature
+         * <P>Type: DOUBLE</P>
+         */
+        public static final String CURRENT_TEMPERATURE = "temperature";
+
+        /**
+         * The unit in which current temperature is reported
+         * <P>Type: INTEGER</P>
+         * Can be one of the following:
+         * <ul>
+         * <li>{@link TempUnit#CELSIUS}</li>
+         * <li>{@link TempUnit#FAHRENHEIT}</li>
+         * </ul>
+         */
+        public static final String CURRENT_TEMPERATURE_UNIT = "temperature_unit";
+
+        /**
+         * The current weather humidity
+         * <P>Type: DOUBLE</P>
+         */
+        public static final String CURRENT_HUMIDITY = "humidity";
+
+        /**
+         * The current wind direction (in degrees)
+         * <P>Type: DOUBLE</P>
+         */
+        public static final String CURRENT_WIND_DIRECTION = "wind_direction";
+
+        /**
+         * The current wind speed
+         * <P>Type: DOUBLE</P>
+         */
+        public static final String CURRENT_WIND_SPEED = "wind_speed";
+
+        /**
+         * The unit in which the wind speed is reported
+         * <P>Type: INTEGER</P>
+         * Can be one of the following:
+         * <ul>
+         * <li>{@link WindSpeedUnit#KPH}</li>
+         * <li>{@link WindSpeedUnit#MPH}</li>
+         * </ul>
+         */
+        public static final String CURRENT_WIND_SPEED_UNIT = "wind_speed_unit";
+
+        /**
+         * The timestamp when this weather was reported
+         * <P>Type: LONG</P>
+         */
+        public static final String CURRENT_TIMESTAMP = "timestamp";
+
+        /**
+         * Today's high temperature.
+         * <p>Type: DOUBLE</p>
+         */
+        public static final String TODAYS_HIGH_TEMPERATURE = "todays_high";
+
+        /**
+         * Today's low temperature.
+         * <p>Type: DOUBLE</p>
+         */
+        public static final String TODAYS_LOW_TEMPERATURE = "todays_low";
+
+        /**
+         * The forecasted low temperature
+         * <P>Type: DOUBLE</P>
+         */
+        public static final String FORECAST_LOW = "forecast_low";
+
+        /**
+         * The forecasted high temperature
+         * <P>Type: DOUBLE</P>
+         */
+        public static final String FORECAST_HIGH = "forecast_high";
+
+        /**
+         * A localized string mapped to the forecasted weather condition code. Note that, if no
+         * locale is found, the string will be in english
+         * <P>Type: TEXT</P>
+         */
+        public static final String FORECAST_CONDITION = "forecast_condition";
+
+        /**
+         * The code identifying the forecasted weather condition.
+         * @see #CURRENT_CONDITION_CODE
+         */
+        public static final String FORECAST_CONDITION_CODE = "forecast_condition_code";
+
+        /**
+         * Temperature units
+         */
+        public static final class TempUnit {
+            private TempUnit() {}
+            public final static int CELSIUS = 1;
+            public final static int FAHRENHEIT = 2;
+        }
+
+        /**
+         * Wind speed units
+         */
+        public static final class WindSpeedUnit {
+            private WindSpeedUnit() {}
+            /**
+             * Kilometers per hour
+             */
+            public final static int KPH = 1;
+
+            /**
+             * Miles per hour
+             */
+            public final static int MPH = 2;
+        }
+
+        /**
+         * Weather condition codes
+         */
+        public static final class WeatherCode {
+            private WeatherCode() {}
+
+            /**
+             * @hide
+             */
+            public final static int WEATHER_CODE_MIN = 0;
+
+            public final static int TORNADO = 0;
+            public final static int TROPICAL_STORM = 1;
+            public final static int HURRICANE = 2;
+            public final static int SEVERE_THUNDERSTORMS = 3;
+            public final static int THUNDERSTORMS = 4;
+            public final static int MIXED_RAIN_AND_SNOW = 5;
+            public final static int MIXED_RAIN_AND_SLEET = 6;
+            public final static int MIXED_SNOW_AND_SLEET = 7;
+            public final static int FREEZING_DRIZZLE = 8;
+            public final static int DRIZZLE = 9;
+            public final static int FREEZING_RAIN = 10;
+            public final static int SHOWERS = 11;
+            public final static int SNOW_FLURRIES = 12;
+            public final static int LIGHT_SNOW_SHOWERS = 13;
+            public final static int BLOWING_SNOW = 14;
+            public final static int SNOW = 15;
+            public final static int HAIL = 16;
+            public final static int SLEET = 17;
+            public final static int DUST = 18;
+            public final static int FOGGY = 19;
+            public final static int HAZE = 20;
+            public final static int SMOKY = 21;
+            public final static int BLUSTERY = 22;
+            public final static int WINDY = 23;
+            public final static int COLD = 24;
+            public final static int CLOUDY = 25;
+            public final static int MOSTLY_CLOUDY_NIGHT = 26;
+            public final static int MOSTLY_CLOUDY_DAY = 27;
+            public final static int PARTLY_CLOUDY_NIGHT = 28;
+            public final static int PARTLY_CLOUDY_DAY = 29;
+            public final static int CLEAR_NIGHT = 30;
+            public final static int SUNNY = 31;
+            public final static int FAIR_NIGHT = 32;
+            public final static int FAIR_DAY = 33;
+            public final static int MIXED_RAIN_AND_HAIL = 34;
+            public final static int HOT = 35;
+            public final static int ISOLATED_THUNDERSTORMS = 36;
+            public final static int SCATTERED_THUNDERSTORMS = 37;
+            public final static int SCATTERED_SHOWERS = 38;
+            public final static int HEAVY_SNOW = 39;
+            public final static int SCATTERED_SNOW_SHOWERS = 40;
+            public final static int PARTLY_CLOUDY = 41;
+            public final static int THUNDERSHOWER = 42;
+            public final static int SNOW_SHOWERS = 43;
+            public final static int ISOLATED_THUNDERSHOWERS = 44;
+
+            /**
+             * @hide
+             */
+            public final static int WEATHER_CODE_MAX = 44;
+
+            public final static int NOT_AVAILABLE = 3200;
+        }
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/lineageos/weather/LineageWeatherManager.java b/app/src/main/java/lineageos/weather/LineageWeatherManager.java
new file mode 100644
index 0000000..48c767d
--- /dev/null
+++ b/app/src/main/java/lineageos/weather/LineageWeatherManager.java
@@ -0,0 +1,435 @@
+/*
+ * Copyright (C) 2016 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package lineageos.weather;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.location.Location;
+import android.os.Build;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.ArraySet;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import lineageos.app.LineageContextConstants;
+import lineageos.providers.WeatherContract;
+
+/**
+ * Provides access to the weather services in the device.
+ */
+@RequiresApi(api = Build.VERSION_CODES.M)
+public class LineageWeatherManager {
+
+    private static ILineageWeatherManager sWeatherManagerService;
+    private static LineageWeatherManager sInstance;
+    private Context mContext;
+    private Map<RequestInfo,WeatherUpdateRequestListener> mWeatherUpdateRequestListeners
+            = Collections.synchronizedMap(new HashMap<RequestInfo,WeatherUpdateRequestListener>());
+    private Map<RequestInfo,LookupCityRequestListener> mLookupNameRequestListeners
+            = Collections.synchronizedMap(new HashMap<RequestInfo,LookupCityRequestListener>());
+    private Handler mHandler;
+    private Set<WeatherServiceProviderChangeListener> mProviderChangedListeners = new ArraySet<>();
+
+    private static final String TAG = LineageWeatherManager.class.getSimpleName();
+
+
+    /**
+     * The different request statuses
+     */
+    public static final class RequestStatus {
+
+        private RequestStatus() {}
+
+        /**
+         * Request successfully completed
+         */
+        public static final int COMPLETED = 1;
+        /**
+         * An error occurred while trying to honor the request
+         */
+        public static final int FAILED = -1;
+        /**
+         * The request can't be processed at this time
+         */
+        public static final int SUBMITTED_TOO_SOON = -2;
+        /**
+         * Another request is already in progress
+         */
+        public static final int ALREADY_IN_PROGRESS = -3;
+        /**
+         * No match found for the query
+         */
+        public static final int NO_MATCH_FOUND = -4;
+    }
+
+    private LineageWeatherManager(Context context) {
+        Context appContext = context.getApplicationContext();
+        mContext = (appContext != null) ? appContext : context;
+        sWeatherManagerService = getService();
+
+        if (context.getPackageManager().hasSystemFeature(
+                LineageContextConstants.Features.WEATHER_SERVICES) && (sWeatherManagerService == null)) {
+            Log.wtf(TAG, "Unable to bind the LineageWeatherManagerService");
+        }
+        mHandler = new Handler(appContext.getMainLooper());
+    }
+
+    /**
+     * Gets or creates an instance of the {@link lineageos.weather.LineageWeatherManager}
+     * @param context
+     * @return {@link LineageWeatherManager}
+     */
+    public static LineageWeatherManager getInstance(Context context) {
+        if (sInstance == null) {
+            sInstance = new LineageWeatherManager(context);
+        }
+        return sInstance;
+    }
+
+    /**
+     * @hide
+     */
+    @SuppressLint("PrivateApi")
+    public static ILineageWeatherManager getService() {
+        if (sWeatherManagerService != null) {
+            return sWeatherManagerService;
+        }
+
+        // This is a Gadgetbridge hack
+        IBinder binder = null;
+        try {
+            Class localClass = Class.forName("android.os.ServiceManager");
+            Method getService = localClass.getMethod("getService", String.class);
+            Object result = getService.invoke(localClass, LineageContextConstants.LINEAGE_WEATHER_SERVICE);
+            if (result != null) {
+                binder = (IBinder) result;
+            }
+        } catch (NoSuchMethodException e) {
+            e.printStackTrace();
+        } catch (IllegalAccessException e) {
+            e.printStackTrace();
+        } catch (InvocationTargetException e) {
+            e.printStackTrace();
+        } catch (ClassNotFoundException e) {
+            e.printStackTrace();
+        }
+        if (binder != null) {
+            sWeatherManagerService = ILineageWeatherManager.Stub.asInterface(binder);
+            return sWeatherManagerService;
+        }
+
+        return null;
+    }
+
+    /**
+     * Forces the weather service to request the latest available weather information for
+     * the supplied {@link android.location.Location} location.
+     *
+     * @param location The location you want to get the latest weather data from.
+     * @param listener {@link WeatherUpdateRequestListener} To be notified once the active weather
+     *                                                     service provider has finished
+     *                                                     processing your request
+     * @return An integer that identifies the request submitted to the weather service
+     * Note that this method might return -1 if an error occurred while trying to submit
+     * the request.
+     */
+    public int requestWeatherUpdate(@NonNull Location location,
+            @NonNull WeatherUpdateRequestListener listener) {
+        if (sWeatherManagerService == null) {
+            return -1;
+        }
+
+        try {
+            int tempUnit = WeatherContract.WeatherColumns.TempUnit.CELSIUS;
+
+            RequestInfo info = new RequestInfo
+                    .Builder(mRequestInfoListener)
+                    .setLocation(location)
+                    .setTemperatureUnit(tempUnit)
+                    .build();
+            if (listener != null) mWeatherUpdateRequestListeners.put(info, listener);
+            sWeatherManagerService.updateWeather(info);
+            return info.hashCode();
+        } catch (RemoteException e) {
+            return -1;
+        }
+    }
+
+    /**
+     * Forces the weather service to request the latest weather information for the provided
+     * WeatherLocation. This is the preferred method for requesting a weather update.
+     *
+     * @param weatherLocation A {@link lineageos.weather.WeatherLocation} that was previously
+     *                        obtained by calling
+     *                        {@link #lookupCity(String, LookupCityRequestListener)}
+     * @param listener {@link WeatherUpdateRequestListener} To be notified once the active weather
+     *                                                     service provider has finished
+     *                                                     processing your request
+     * @return An integer that identifies the request submitted to the weather service.
+     * Note that this method might return -1 if an error occurred while trying to submit
+     * the request.
+     */
+    public int requestWeatherUpdate(@NonNull WeatherLocation weatherLocation,
+            @NonNull WeatherUpdateRequestListener listener) {
+        if (sWeatherManagerService == null) {
+            return -1;
+        }
+
+        try {
+            int tempUnit = WeatherContract.WeatherColumns.TempUnit.CELSIUS;
+
+            RequestInfo info = new RequestInfo
+                    .Builder(mRequestInfoListener)
+                    .setWeatherLocation(weatherLocation)
+                    .setTemperatureUnit(tempUnit)
+                    .build();
+            if (listener != null) mWeatherUpdateRequestListeners.put(info, listener);
+            sWeatherManagerService.updateWeather(info);
+            return info.hashCode();
+        } catch (RemoteException e) {
+            return -1;
+        }
+    }
+
+    /**
+     * Request the active weather provider service to lookup the supplied city name.
+     *
+     * @param city The city name
+     * @param listener {@link LookupCityRequestListener} To be notified once the request has been
+     *                                                  completed. Upon success, a list of
+     *                                                  {@link lineageos.weather.WeatherLocation}
+     *                                                  will be provided
+     * @return An integer that identifies the request submitted to the weather service.
+     * Note that this method might return -1 if an error occurred while trying to submit
+     * the request.
+     */
+    public int lookupCity(@NonNull String city, @NonNull LookupCityRequestListener listener) {
+        if (sWeatherManagerService == null) {
+            return -1;
+        }
+        try {
+            RequestInfo info = new RequestInfo
+                    .Builder(mRequestInfoListener)
+                    .setCityName(city)
+                    .build();
+            if (listener != null) mLookupNameRequestListeners.put(info, listener);
+            sWeatherManagerService.lookupCity(info);
+            return info.hashCode();
+        } catch (RemoteException e) {
+            return -1;
+        }
+    }
+
+    /**
+     * Cancels a request that was previously submitted to the weather service.
+     * @param requestId The ID that you received when the request was submitted
+     */
+    public void cancelRequest(int requestId) {
+        if (sWeatherManagerService == null) {
+            return;
+        }
+
+        try {
+            sWeatherManagerService.cancelRequest(requestId);
+        }catch (RemoteException e){
+        }
+    }
+
+    /**
+     * Registers a {@link WeatherServiceProviderChangeListener} to be notified when a new weather
+     * service provider becomes active.
+     * @param listener {@link WeatherServiceProviderChangeListener} to register
+     */
+    public void registerWeatherServiceProviderChangeListener(
+            @NonNull WeatherServiceProviderChangeListener listener) {
+        if (sWeatherManagerService == null) return;
+
+        synchronized (mProviderChangedListeners) {
+            if (mProviderChangedListeners.contains(listener)) {
+                throw new IllegalArgumentException("Listener already registered");
+            }
+            if (mProviderChangedListeners.size() == 0) {
+                try {
+                    sWeatherManagerService.registerWeatherServiceProviderChangeListener(
+                            mProviderChangeListener);
+                } catch (RemoteException e){
+                }
+            }
+            mProviderChangedListeners.add(listener);
+        }
+    }
+
+    /**
+     * Unregisters a listener
+     * @param listener A previously registered {@link WeatherServiceProviderChangeListener}
+     */
+    public void unregisterWeatherServiceProviderChangeListener(
+            @NonNull WeatherServiceProviderChangeListener listener) {
+        if (sWeatherManagerService == null) return;
+
+        synchronized (mProviderChangedListeners) {
+            if (!mProviderChangedListeners.contains(listener)) {
+                throw new IllegalArgumentException("Listener was never registered");
+            }
+            mProviderChangedListeners.remove(listener);
+            if (mProviderChangedListeners.size() == 0) {
+                try {
+                    sWeatherManagerService.unregisterWeatherServiceProviderChangeListener(
+                            mProviderChangeListener);
+                } catch(RemoteException e){
+                }
+            }
+        }
+    }
+
+    /**
+     * Gets the service's label as declared by the active weather service provider in its manifest
+     * @return the service's label
+     */
+    public String getActiveWeatherServiceProviderLabel() {
+        if (sWeatherManagerService == null) return null;
+
+        try {
+            return sWeatherManagerService.getActiveWeatherServiceProviderLabel();
+        } catch(RemoteException e){
+        }
+        return null;
+    }
+
+    private final IWeatherServiceProviderChangeListener mProviderChangeListener =
+            new IWeatherServiceProviderChangeListener.Stub() {
+        @Override
+        public void onWeatherServiceProviderChanged(final String providerName) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    synchronized (mProviderChangedListeners) {
+                        List<WeatherServiceProviderChangeListener> deadListeners
+                                = new ArrayList<>();
+                        for (WeatherServiceProviderChangeListener listener
+                                : mProviderChangedListeners) {
+                            try {
+                                listener.onWeatherServiceProviderChanged(providerName);
+                            } catch (Throwable e) {
+                                deadListeners.add(listener);
+                            }
+                        }
+                        if (deadListeners.size() > 0) {
+                            for (WeatherServiceProviderChangeListener listener : deadListeners) {
+                                mProviderChangedListeners.remove(listener);
+                            }
+                        }
+                    }
+                }
+            });
+        }
+    };
+
+    private final IRequestInfoListener mRequestInfoListener = new IRequestInfoListener.Stub() {
+
+        @Override
+        public void onWeatherRequestCompleted(final RequestInfo requestInfo, final int status,
+                final WeatherInfo weatherInfo) {
+            final WeatherUpdateRequestListener listener
+                    = mWeatherUpdateRequestListeners.remove(requestInfo);
+            if (listener != null) {
+                mHandler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        listener.onWeatherRequestCompleted(status, weatherInfo);
+                    }
+                });
+            }
+        }
+
+        @Override
+        public void onLookupCityRequestCompleted(RequestInfo requestInfo, final int status,
+            final List<WeatherLocation> weatherLocations) {
+
+            final LookupCityRequestListener listener
+                    = mLookupNameRequestListeners.remove(requestInfo);
+            if (listener != null) {
+                mHandler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        listener.onLookupCityRequestCompleted(status, weatherLocations);
+                    }
+                });
+            }
+        }
+    };
+
+    /**
+     * Interface used to receive notifications upon completion of a weather update request
+     */
+    public interface WeatherUpdateRequestListener {
+        /**
+         * This method will be called when the weather service provider has finished processing the
+         * request
+         *
+         * @param status See {@link RequestStatus}
+         *
+         * @param weatherInfo A fully populated {@link WeatherInfo} if state is
+         *                    {@link RequestStatus#COMPLETED}, null otherwise
+         */
+        void onWeatherRequestCompleted(int status, WeatherInfo weatherInfo);
+    }
+
+    /**
+     * Interface used to receive notifications upon completion of a request to lookup a city name
+     */
+    public interface LookupCityRequestListener {
+        /**
+         * This method will be called when the weather service provider has finished processing the
+         * request.
+         *
+         * @param status See {@link RequestStatus}
+         *
+         * @param locations A list of {@link WeatherLocation} if the status is
+         * {@link RequestStatus#COMPLETED}, null otherwise
+         */
+        void onLookupCityRequestCompleted(int status, List<WeatherLocation> locations);
+    }
+
+    /**
+     * Interface used to be notified when the user changes the weather service provider
+     */
+    public interface WeatherServiceProviderChangeListener {
+        /**
+         * This method will be called when a new weather service provider becomes active in the
+         * system. The parameter can be null when
+         * <p>The user removed the active weather service provider from the system </p>
+         * <p>The active weather provider was disabled.</p>
+         *
+         * @param providerLabel The label as declared on the weather service provider manifest
+         */
+        void onWeatherServiceProviderChanged(String providerLabel);
+    }
+}
diff --git a/app/src/main/java/lineageos/weather/RequestInfo.java b/app/src/main/java/lineageos/weather/RequestInfo.java
new file mode 100644
index 0000000..a236bef
--- /dev/null
+++ b/app/src/main/java/lineageos/weather/RequestInfo.java
@@ -0,0 +1,379 @@
+/*
+ * Copyright (C) 2016 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package lineageos.weather;
+
+import android.location.Location;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import android.text.TextUtils;
+
+import lineageos.os.Build;
+import lineageos.os.Concierge;
+import lineageos.os.Concierge.ParcelInfo;
+import lineageos.providers.WeatherContract;
+
+import java.util.UUID;
+
+/**
+ * This class holds the information of a request submitted to the active weather provider service
+ */
+public final class RequestInfo implements Parcelable {
+
+    private Location mLocation;
+    private String mCityName;
+    private WeatherLocation mWeatherLocation;
+    private int mRequestType;
+    private IRequestInfoListener mListener;
+    private int mTempUnit;
+    private String mKey;
+    private boolean mIsQueryOnly;
+
+    /**
+     * A request to update the weather data using a geographical {@link android.location.Location}
+     */
+    public static final int TYPE_WEATHER_BY_GEO_LOCATION_REQ = 1;
+    /**
+     * A request to update the weather data using a {@link WeatherLocation}
+     */
+    public static final int TYPE_WEATHER_BY_WEATHER_LOCATION_REQ = 2;
+
+    /**
+     * A request to look up a city name
+     */
+    public static final int TYPE_LOOKUP_CITY_NAME_REQ = 3;
+
+    private RequestInfo() {}
+
+    /* package */ static class Builder {
+        private Location mLocation;
+        private String mCityName;
+        private WeatherLocation mWeatherLocation;
+        private int mRequestType;
+        private IRequestInfoListener mListener;
+        private int mTempUnit = WeatherContract.WeatherColumns.TempUnit.FAHRENHEIT;
+        private boolean mIsQueryOnly = false;
+
+        public Builder(IRequestInfoListener listener) {
+            this.mListener = listener;
+        }
+
+        /**
+         * Sets the city name and identifies this request as a {@link #TYPE_LOOKUP_CITY_NAME_REQ}
+         * request. If set, will null out the location and weather location. Attempting to set
+         * a null city name will get you an IllegalArgumentException
+         */
+        public Builder setCityName(String cityName) {
+            if (cityName == null) {
+                throw new IllegalArgumentException("City name can't be null");
+            }
+            this.mCityName = cityName;
+            this.mRequestType = TYPE_LOOKUP_CITY_NAME_REQ;
+            this.mLocation = null;
+            this.mWeatherLocation = null;
+            return this;
+        }
+
+        /**
+         * Sets the Location and identifies this request as a
+         * {@link #TYPE_WEATHER_BY_GEO_LOCATION_REQ}. If set, will null out the city name and
+         * weather location. Attempting to set a null location will get you an
+         * IllegalArgumentException
+         */
+        public Builder setLocation(Location location) {
+            if (location == null) {
+                throw new IllegalArgumentException("Location can't be null");
+            }
+            this.mLocation = new Location(location);
+            this.mCityName = null;
+            this.mWeatherLocation = null;
+            this.mRequestType = TYPE_WEATHER_BY_GEO_LOCATION_REQ;
+            return this;
+        }
+
+        /**
+         * Sets the weather location and identifies this request as a
+         * {@link #TYPE_WEATHER_BY_WEATHER_LOCATION_REQ}. If set, will null out the location and
+         * city name. Attempting to set a null weather location will get you an
+         * IllegalArgumentException
+         */
+        public Builder setWeatherLocation(WeatherLocation weatherLocation) {
+            if (weatherLocation == null) {
+                throw new IllegalArgumentException("WeatherLocation can't be null");
+            }
+            this.mWeatherLocation = weatherLocation;
+            this.mLocation = null;
+            this.mCityName = null;
+            this.mRequestType = TYPE_WEATHER_BY_WEATHER_LOCATION_REQ;
+            return this;
+        }
+
+        /**
+         * Sets the unit in which the temperature will be reported if the request is honored.
+         * Valid values are:
+         * <ul>
+         * {@link lineageos.providers.WeatherContract.WeatherColumns.TempUnit#CELSIUS}
+         * {@link lineageos.providers.WeatherContract.WeatherColumns.TempUnit#FAHRENHEIT}
+         * </ul>
+         * Any other value will generate an IllegalArgumentException. If the temperature unit is not
+         * set, the default will be degrees Fahrenheit
+         * @param unit A valid temperature unit
+         */
+        public Builder setTemperatureUnit(int unit) {
+            if (!isValidTempUnit(unit)) {
+                throw new IllegalArgumentException("Invalid temperature unit");
+            }
+            this.mTempUnit = unit;
+            return this;
+        }
+
+        /**
+         * If this is a weather request, marks the request as a query only, meaning that the
+         * content provider won't be updated after the active weather service has finished
+         * processing the request.
+         */
+        public Builder queryOnly() {
+            switch (mRequestType) {
+                case TYPE_WEATHER_BY_GEO_LOCATION_REQ:
+                case TYPE_WEATHER_BY_WEATHER_LOCATION_REQ:
+                    this.mIsQueryOnly = true;
+                    break;
+                default:
+                    this.mIsQueryOnly = false;
+                    break;
+            }
+            return this;
+        }
+
+        /**
+         * Combine all of the options that have been set and return a new {@link RequestInfo} object
+         * @return {@link RequestInfo}
+         */
+        public RequestInfo build() {
+            RequestInfo info = new RequestInfo();
+            info.mListener = this.mListener;
+            info.mRequestType = this.mRequestType;
+            info.mCityName = this.mCityName;
+            info.mWeatherLocation = this.mWeatherLocation;
+            info.mLocation = this.mLocation;
+            info.mTempUnit = this.mTempUnit;
+            info.mIsQueryOnly = this.mIsQueryOnly;
+            info.mKey = UUID.randomUUID().toString();
+            return info;
+        }
+
+        private boolean isValidTempUnit(int unit) {
+            switch (unit) {
+                case WeatherContract.WeatherColumns.TempUnit.CELSIUS:
+                case WeatherContract.WeatherColumns.TempUnit.FAHRENHEIT:
+                    return true;
+                default:
+                    return false;
+            }
+        }
+
+    }
+
+    private RequestInfo(Parcel parcel) {
+        // Read parcelable version via the Concierge
+        ParcelInfo parcelInfo = Concierge.receiveParcel(parcel);
+        int parcelableVersion = parcelInfo.getParcelVersion();
+
+        if (parcelableVersion >= Build.LINEAGE_VERSION_CODES.ELDERBERRY) {
+            mKey = parcel.readString();
+            mRequestType = parcel.readInt();
+            switch (mRequestType) {
+                case TYPE_WEATHER_BY_GEO_LOCATION_REQ:
+                    mLocation = Location.CREATOR.createFromParcel(parcel);
+                    mTempUnit = parcel.readInt();
+                    break;
+                case TYPE_WEATHER_BY_WEATHER_LOCATION_REQ:
+                    mWeatherLocation = WeatherLocation.CREATOR.createFromParcel(parcel);
+                    mTempUnit = parcel.readInt();
+                    break;
+                case TYPE_LOOKUP_CITY_NAME_REQ:
+                    mCityName = parcel.readString();
+                    break;
+            }
+            mIsQueryOnly = (parcel.readInt() == 1);
+            mListener = IRequestInfoListener.Stub.asInterface(parcel.readStrongBinder());
+        }
+
+        // Complete parcel info for the concierge
+        parcelInfo.complete();
+    }
+
+
+    /**
+     * @return The request type
+     */
+    public int getRequestType() {
+        return mRequestType;
+    }
+
+    /**
+     * @return the {@link android.location.Location} if this is a request by location, null
+     * otherwise
+     */
+    public Location getLocation() {
+        return new Location(mLocation);
+    }
+
+    /**
+     * @return the {@link lineageos.weather.WeatherLocation} if this is a request by weather
+     * location, null otherwise
+     */
+    public WeatherLocation getWeatherLocation() {
+        return mWeatherLocation;
+    }
+
+    /**
+     * @hide
+     */
+    public IRequestInfoListener getRequestListener() {
+        return mListener;
+    }
+
+    /**
+     * @return the city name if this is a lookup request, null otherwise
+     */
+    public String getCityName() {
+        return mCityName;
+    }
+
+    /**
+     * @return the temperature unit if this is a weather request, -1 otherwise
+     */
+    public int getTemperatureUnit() {
+        switch (mRequestType) {
+            case TYPE_WEATHER_BY_GEO_LOCATION_REQ:
+            case TYPE_WEATHER_BY_WEATHER_LOCATION_REQ:
+                return mTempUnit;
+            default:
+                return -1;
+        }
+    }
+
+    /**
+     * @return if this is a weather request, whether the request will update the content provider.
+     * False for other kind of requests
+     * @hide
+     */
+    public boolean isQueryOnlyWeatherRequest() {
+        switch (mRequestType) {
+            case TYPE_WEATHER_BY_GEO_LOCATION_REQ:
+            case TYPE_WEATHER_BY_WEATHER_LOCATION_REQ:
+                return mIsQueryOnly;
+            default:
+                return false;
+        }
+    }
+
+    public static final Creator<RequestInfo> CREATOR = new Creator<RequestInfo>() {
+        @Override
+        public RequestInfo createFromParcel(Parcel in) {
+            return new RequestInfo(in);
+        }
+
+        @Override
+        public RequestInfo[] newArray(int size) {
+            return new RequestInfo[size];
+        }
+    };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        // Tell the concierge to prepare the parcel
+        ParcelInfo parcelInfo = Concierge.prepareParcel(dest);
+
+        // ==== ELDERBERRY =====
+        dest.writeString(mKey);
+        dest.writeInt(mRequestType);
+        switch (mRequestType) {
+            case TYPE_WEATHER_BY_GEO_LOCATION_REQ:
+                mLocation.writeToParcel(dest, 0);
+                dest.writeInt(mTempUnit);
+                break;
+            case TYPE_WEATHER_BY_WEATHER_LOCATION_REQ:
+                mWeatherLocation.writeToParcel(dest, 0);
+                dest.writeInt(mTempUnit);
+                break;
+            case TYPE_LOOKUP_CITY_NAME_REQ:
+                dest.writeString(mCityName);
+                break;
+        }
+        dest.writeInt(mIsQueryOnly == true ? 1 : 0);
+        dest.writeStrongBinder(mListener.asBinder());
+
+        // Complete parcel info for the concierge
+        parcelInfo.complete();
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder builder = new StringBuilder();
+        builder.append("{ Request for ");
+        switch (mRequestType) {
+            case TYPE_WEATHER_BY_GEO_LOCATION_REQ:
+                builder.append("Location: ").append(mLocation);
+                builder.append(" Temp Unit: ");
+                if (mTempUnit == WeatherContract.WeatherColumns.TempUnit.FAHRENHEIT) {
+                    builder.append("Fahrenheit");
+                } else {
+                    builder.append(" Celsius");
+                }
+                break;
+            case TYPE_WEATHER_BY_WEATHER_LOCATION_REQ:
+                builder.append("WeatherLocation: ").append(mWeatherLocation);
+                builder.append(" Temp Unit: ");
+                if (mTempUnit == WeatherContract.WeatherColumns.TempUnit.FAHRENHEIT) {
+                    builder.append("Fahrenheit");
+                } else {
+                    builder.append(" Celsius");
+                }
+                break;
+            case TYPE_LOOKUP_CITY_NAME_REQ:
+                builder.append("Lookup City: ").append(mCityName);
+                break;
+        }
+        return builder.append(" }").toString();
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((mKey != null) ? mKey.hashCode() : 0);
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == null) return false;
+
+        if (getClass() == obj.getClass()) {
+            RequestInfo info = (RequestInfo) obj;
+            return (TextUtils.equals(mKey, info.mKey));
+        }
+        return false;
+    }
+}
diff --git a/app/src/main/java/lineageos/weather/WeatherInfo.java b/app/src/main/java/lineageos/weather/WeatherInfo.java
new file mode 100644
index 0000000..91d8b34
--- /dev/null
+++ b/app/src/main/java/lineageos/weather/WeatherInfo.java
@@ -0,0 +1,642 @@
+/*
+ * Copyright (C) 2016 The CyanongenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package lineageos.weather;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import androidx.annotation.NonNull;
+
+import lineageos.os.Build;
+import lineageos.os.Concierge;
+import lineageos.os.Concierge.ParcelInfo;
+import lineageos.providers.WeatherContract;
+import lineageos.weatherservice.ServiceRequest;
+import lineageos.weatherservice.ServiceRequestResult;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+/**
+ * This class represents the weather information that a
+ * {@link lineageos.weatherservice.WeatherProviderService} will use to update the weather content
+ * provider. A weather provider service will be called by the system to process an update
+ * request at any time. If the service successfully processes the request, then the weather provider
+ * service is responsible of calling
+ * {@link ServiceRequest#complete(ServiceRequestResult)} to notify the
+ * system that the request was completed and that the weather content provider should be updated
+ * with the supplied weather information.
+ */
+public final class WeatherInfo implements Parcelable {
+
+    private String mCity;
+    private int mConditionCode;
+    private double mTemperature;
+    private int mTempUnit;
+    private double mTodaysHighTemp;
+    private double mTodaysLowTemp;
+    private double mHumidity;
+    private double mWindSpeed;
+    private double mWindDirection;
+    private int mWindSpeedUnit;
+    private long mTimestamp;
+    private List<DayForecast> mForecastList;
+    private String mKey;
+
+    private WeatherInfo() {}
+
+    /**
+     * Builder class for {@link WeatherInfo}
+     */
+    public static class Builder {
+        private String mCity;
+        private int mConditionCode = WeatherContract.WeatherColumns.WeatherCode.NOT_AVAILABLE;
+        private double mTemperature;
+        private int mTempUnit;
+        private double mTodaysHighTemp = Double.NaN;
+        private double mTodaysLowTemp = Double.NaN;
+        private double mHumidity = Double.NaN;
+        private double mWindSpeed = Double.NaN;
+        private double mWindDirection = Double.NaN;
+        private int mWindSpeedUnit = WeatherContract.WeatherColumns.WindSpeedUnit.MPH;
+        private long mTimestamp = -1;
+        private List<DayForecast> mForecastList = new ArrayList<>(0);
+
+        /**
+         * @param cityName A valid city name. Attempting to pass null will get you an
+         *                 IllegalArgumentException
+         * @param temperature A valid temperature value. Attempting pass an invalid double value,
+         *                    will get you an IllegalArgumentException
+         * @param tempUnit A valid temperature unit value. See
+         *                 {@link lineageos.providers.WeatherContract.WeatherColumns.TempUnit} for
+         *                 valid values. Attempting to pass an invalid temperature unit will get you
+         *                 an IllegalArgumentException
+         */
+        public Builder(@NonNull String cityName, double temperature, int tempUnit) {
+            if (cityName == null) {
+                throw new IllegalArgumentException("City name can't be null");
+            }
+            if (Double.isNaN(temperature)) {
+                throw new IllegalArgumentException("Invalid temperature");
+            }
+            if (!isValidTempUnit(tempUnit)) {
+                throw new IllegalArgumentException("Invalid temperature unit");
+            }
+            this.mCity = cityName;
+            this.mTemperature = temperature;
+            this.mTempUnit = tempUnit;
+        }
+
+        /**
+         * @param timeStamp A timestamp indicating when this data was generated. If timestamps is
+         *                  not set, then the builder will set it to the time of object creation
+         * @return The {@link Builder} instance
+         */
+        public Builder setTimestamp(long timeStamp) {
+            mTimestamp = timeStamp;
+            return this;
+        }
+
+        /**
+         * @param humidity The weather humidity. Attempting to pass an invalid double value will get
+         *                 you an IllegalArgumentException
+         * @return The {@link Builder} instance
+         */
+        public Builder setHumidity(double humidity) {
+            if (Double.isNaN(humidity)) {
+                throw new IllegalArgumentException("Invalid humidity value");
+            }
+
+            mHumidity = humidity;
+            return this;
+        }
+
+        /**
+         * @param windSpeed The wind speed. Attempting to pass an invalid double value will get you
+         *                  an IllegalArgumentException
+         * @param windDirection The wind direction. Attempting to pass an invalid double value will
+         *                      get you an IllegalArgumentException
+         * @param windSpeedUnit A valid wind speed direction unit. See
+         *                      {@link lineageos.providers.WeatherContract.WeatherColumns.WindSpeedUnit}
+         *                      for valid values. Attempting to pass an invalid speed unit will get
+         *                      you an IllegalArgumentException
+         * @return The {@link Builder} instance
+         */
+        public Builder setWind(double windSpeed, double windDirection, int windSpeedUnit) {
+            if (Double.isNaN(windSpeed)) {
+                throw new IllegalArgumentException("Invalid wind speed value");
+            }
+            if (Double.isNaN(windDirection)) {
+                throw new IllegalArgumentException("Invalid wind direction value");
+            }
+            if (!isValidWindSpeedUnit(windSpeedUnit)) {
+                throw new IllegalArgumentException("Invalid speed unit");
+            }
+            mWindSpeed = windSpeed;
+            mWindSpeedUnit = windSpeedUnit;
+            mWindDirection = windDirection;
+            return this;
+        }
+
+        /**
+         * @param conditionCode A valid weather condition code. See
+         *                      {@link lineageos.providers.WeatherContract.WeatherColumns.WeatherCode}
+         *                      for valid codes. Attempting to pass an invalid code will get you an
+         *                      IllegalArgumentException.
+         * @return The {@link Builder} instance
+         */
+        public Builder setWeatherCondition(int conditionCode) {
+            if (!isValidWeatherCode(conditionCode)) {
+                throw new IllegalArgumentException("Invalid weather condition code");
+            }
+            mConditionCode = conditionCode;
+            return this;
+        }
+
+        /**
+         * @param forecasts A valid array list of {@link DayForecast} objects. Attempting to pass
+         *                  null will get you an IllegalArgumentException'
+         * @return The {@link Builder} instance
+         */
+        public Builder setForecast(@NonNull List<DayForecast> forecasts) {
+            if (forecasts == null) {
+                throw new IllegalArgumentException("Forecast list can't be null");
+            }
+            mForecastList = forecasts;
+            return this;
+        }
+
+        /**
+         *
+         * @param todaysHigh Today's high temperature. Attempting to pass an invalid double value
+         *                   will get you an IllegalArgumentException
+         * @return The {@link Builder} instance
+         */
+        public Builder setTodaysHigh(double todaysHigh) {
+            if (Double.isNaN(todaysHigh)) {
+                throw new IllegalArgumentException("Invalid temperature value");
+            }
+            mTodaysHighTemp = todaysHigh;
+            return this;
+        }
+
+        /**
+         * @param todaysLow Today's low temperature. Attempting to pass an invalid double value will
+         *                  get you an IllegalArgumentException
+         * @return
+         */
+        public Builder setTodaysLow(double todaysLow) {
+            if (Double.isNaN(todaysLow)) {
+                throw new IllegalArgumentException("Invalid temperature value");
+            }
+            mTodaysLowTemp = todaysLow;
+            return this;
+        }
+
+        /**
+         * Combine all of the options that have been set and return a new {@link WeatherInfo} object
+         * @return {@link WeatherInfo}
+         */
+        public WeatherInfo build() {
+            WeatherInfo info = new WeatherInfo();
+            info.mCity = this.mCity;
+            info.mConditionCode = this.mConditionCode;
+            info.mTemperature = this.mTemperature;
+            info.mTempUnit = this.mTempUnit;
+            info.mHumidity = this.mHumidity;
+            info.mWindSpeed = this.mWindSpeed;
+            info.mWindDirection = this.mWindDirection;
+            info.mWindSpeedUnit = this.mWindSpeedUnit;
+            info.mTimestamp = this.mTimestamp == -1 ? System.currentTimeMillis() : this.mTimestamp;
+            info.mForecastList = this.mForecastList;
+            info.mTodaysHighTemp = this.mTodaysHighTemp;
+            info.mTodaysLowTemp = this.mTodaysLowTemp;
+            info.mKey = UUID.randomUUID().toString();
+            return info;
+        }
+
+        private boolean isValidTempUnit(int unit) {
+            switch (unit) {
+                case WeatherContract.WeatherColumns.TempUnit.CELSIUS:
+                case WeatherContract.WeatherColumns.TempUnit.FAHRENHEIT:
+                    return true;
+                default:
+                    return false;
+            }
+        }
+
+        private boolean isValidWindSpeedUnit(int unit) {
+            switch (unit) {
+                case WeatherContract.WeatherColumns.WindSpeedUnit.KPH:
+                case WeatherContract.WeatherColumns.WindSpeedUnit.MPH:
+                    return true;
+                default:
+                    return false;
+            }
+        }
+    }
+
+
+    private static boolean isValidWeatherCode(int code) {
+        if (code < WeatherContract.WeatherColumns.WeatherCode.WEATHER_CODE_MIN
+                || code > WeatherContract.WeatherColumns.WeatherCode.WEATHER_CODE_MAX) {
+            if (code != WeatherContract.WeatherColumns.WeatherCode.NOT_AVAILABLE) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * @return city name
+     */
+    public String getCity() {
+        return mCity;
+    }
+
+    /**
+     * @return An implementation specific weather condition code
+     */
+    public int getConditionCode() {
+        return mConditionCode;
+    }
+
+    /**
+     * @return humidity
+     */
+    public double getHumidity() {
+        return mHumidity;
+    }
+
+    /**
+     * @return time stamp when the request was processed
+     */
+    public long getTimestamp() {
+        return mTimestamp;
+    }
+
+    /**
+     * @return wind direction (degrees)
+     */
+    public double getWindDirection() {
+        return mWindDirection;
+    }
+
+    /**
+     * @return wind speed
+     */
+    public double getWindSpeed() {
+        return mWindSpeed;
+    }
+
+    /**
+     * @return wind speed unit
+     */
+    public int getWindSpeedUnit() {
+        return mWindSpeedUnit;
+    }
+
+    /**
+     * @return current temperature
+     */
+    public double getTemperature() {
+        return mTemperature;
+    }
+
+    /**
+     * @return temperature unit
+     */
+    public int getTemperatureUnit() {
+        return mTempUnit;
+    }
+
+    /**
+     * @return today's high temperature
+     */
+    public double getTodaysHigh() {
+        return mTodaysHighTemp;
+    }
+
+    /**
+     * @return today's low temperature
+     */
+    public double getTodaysLow() {
+        return mTodaysLowTemp;
+    }
+
+    /**
+     * @return List of {@link lineageos.weather.WeatherInfo.DayForecast}. This list will contain
+     * the forecast weather for the upcoming days. If you want to know today's high and low
+     * temperatures, use {@link WeatherInfo#getTodaysHigh()} and {@link WeatherInfo#getTodaysLow()}
+     */
+    public List<DayForecast> getForecasts() {
+        return new ArrayList<>(mForecastList);
+    }
+
+    private WeatherInfo(Parcel parcel) {
+        // Read parcelable version via the Concierge
+        ParcelInfo parcelInfo = Concierge.receiveParcel(parcel);
+        int parcelableVersion = parcelInfo.getParcelVersion();
+
+        if (parcelableVersion >= Build.LINEAGE_VERSION_CODES.ELDERBERRY) {
+            mKey = parcel.readString();
+            mCity = parcel.readString();
+            mConditionCode = parcel.readInt();
+            mTemperature = parcel.readDouble();
+            mTempUnit = parcel.readInt();
+            mHumidity = parcel.readDouble();
+            mWindSpeed = parcel.readDouble();
+            mWindDirection = parcel.readDouble();
+            mWindSpeedUnit = parcel.readInt();
+            mTodaysHighTemp = parcel.readDouble();
+            mTodaysLowTemp = parcel.readDouble();
+            mTimestamp = parcel.readLong();
+            int forecastListSize = parcel.readInt();
+            mForecastList = new ArrayList<>();
+            while (forecastListSize > 0) {
+                mForecastList.add(DayForecast.CREATOR.createFromParcel(parcel));
+                forecastListSize--;
+            }
+        }
+
+        // Complete parcel info for the concierge
+        parcelInfo.complete();
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        // Tell the concierge to prepare the parcel
+        ParcelInfo parcelInfo = Concierge.prepareParcel(dest);
+
+        // ==== ELDERBERRY =====
+        dest.writeString(mKey);
+        dest.writeString(mCity);
+        dest.writeInt(mConditionCode);
+        dest.writeDouble(mTemperature);
+        dest.writeInt(mTempUnit);
+        dest.writeDouble(mHumidity);
+        dest.writeDouble(mWindSpeed);
+        dest.writeDouble(mWindDirection);
+        dest.writeInt(mWindSpeedUnit);
+        dest.writeDouble(mTodaysHighTemp);
+        dest.writeDouble(mTodaysLowTemp);
+        dest.writeLong(mTimestamp);
+        dest.writeInt(mForecastList.size());
+        for (DayForecast dayForecast : mForecastList) {
+            dayForecast.writeToParcel(dest, 0);
+        }
+
+        // Complete parcel info for the concierge
+        parcelInfo.complete();
+    }
+
+    public static final Parcelable.Creator<WeatherInfo> CREATOR =
+            new Parcelable.Creator<WeatherInfo>() {
+
+                @Override
+                public WeatherInfo createFromParcel(Parcel source) {
+                    return new WeatherInfo(source);
+                }
+
+                @Override
+                public WeatherInfo[] newArray(int size) {
+                    return new WeatherInfo[size];
+                }
+            };
+
+    /**
+     * This class represents the weather forecast for a given day. Do not add low and high
+     * temperatures for the current day in this list. Use
+     * {@link WeatherInfo.Builder#setTodaysHigh(double)} and
+     * {@link WeatherInfo.Builder#setTodaysLow(double)} instead.
+     */
+    public static class DayForecast implements Parcelable{
+        double mLow;
+        double mHigh;
+        int mConditionCode;
+        String mKey;
+
+        private DayForecast() {}
+
+        /**
+         * Builder class for {@link DayForecast}
+         */
+        public static class Builder {
+            double mLow = Double.NaN;
+            double mHigh = Double.NaN;
+            int mConditionCode;
+
+            /**
+             * @param conditionCode A valid weather condition code. See
+             * {@link lineageos.providers.WeatherContract.WeatherColumns.WeatherCode} for valid
+             *                      values. Attempting to pass an invalid code will get you an
+             *                      IllegalArgumentException
+             */
+            public Builder(int conditionCode) {
+                if (!isValidWeatherCode(conditionCode)) {
+                    throw new IllegalArgumentException("Invalid weather condition code");
+                }
+                mConditionCode = conditionCode;
+            }
+
+            /**
+             * @param high Forecast high temperature for this day. Attempting to pass an invalid
+             *             double value will get you an IllegalArgumentException
+             * @return The {@link Builder} instance
+             */
+            public Builder setHigh(double high) {
+                if (Double.isNaN(high)) {
+                    throw new IllegalArgumentException("Invalid high forecast temperature");
+                }
+                mHigh = high;
+                return this;
+            }
+
+            /**
+             * @param low Forecast low temperate for this day. Attempting to pass an invalid double
+             *            value will get you an IllegalArgumentException
+             * @return The {@link Builder} instance
+             */
+            public Builder setLow(double low) {
+                if (Double.isNaN(low)) {
+                    throw new IllegalArgumentException("Invalid low forecast temperature");
+                }
+                mLow = low;
+                return this;
+            }
+
+
+            /**
+             * Combine all of the options that have been set and return a new {@link DayForecast}
+             * object
+             * @return {@link DayForecast}
+             */
+            public DayForecast build() {
+                DayForecast forecast = new DayForecast();
+                forecast.mLow = this.mLow;
+                forecast.mHigh = this.mHigh;
+                forecast.mConditionCode = this.mConditionCode;
+                forecast.mKey = UUID.randomUUID().toString();
+                return forecast;
+            }
+        }
+
+        /**
+         * @return forecasted low temperature
+         */
+        public double getLow() {
+            return mLow;
+        }
+
+        /**
+         * @return not what you think. Returns the forecasted high temperature
+         */
+        public double getHigh() {
+            return mHigh;
+        }
+
+        /**
+         * @return forecasted weather condition code. Implementation specific
+         */
+        public int getConditionCode() {
+            return mConditionCode;
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            // Tell the concierge to prepare the parcel
+            ParcelInfo parcelInfo = Concierge.prepareParcel(dest);
+
+            // ==== ELDERBERRY =====
+            dest.writeString(mKey);
+            dest.writeDouble(mLow);
+            dest.writeDouble(mHigh);
+            dest.writeInt(mConditionCode);
+
+            // Complete parcel info for the concierge
+            parcelInfo.complete();
+        }
+
+        public static final Parcelable.Creator<DayForecast> CREATOR =
+                new Parcelable.Creator<DayForecast>() {
+                    @Override
+                    public DayForecast createFromParcel(Parcel source) {
+                        return new DayForecast(source);
+                    }
+
+                    @Override
+                    public DayForecast[] newArray(int size) {
+                        return new DayForecast[size];
+                    }
+                };
+
+        private DayForecast(Parcel parcel) {
+            // Read parcelable version via the Concierge
+            ParcelInfo parcelInfo = Concierge.receiveParcel(parcel);
+            int parcelableVersion = parcelInfo.getParcelVersion();
+
+            if (parcelableVersion >= Build.LINEAGE_VERSION_CODES.ELDERBERRY) {
+                mKey = parcel.readString();
+                mLow = parcel.readDouble();
+                mHigh = parcel.readDouble();
+                mConditionCode = parcel.readInt();
+            }
+
+            // Complete parcel info for the concierge
+            parcelInfo.complete();
+        }
+
+        @Override
+        public String toString() {
+            return new StringBuilder()
+                    .append("{Low temp: ").append(mLow)
+                    .append(" High temp: ").append(mHigh)
+                    .append(" Condition code: ").append(mConditionCode)
+                    .append("}").toString();
+        }
+
+        @Override
+        public int hashCode() {
+            final int prime = 31;
+            int result = 1;
+            result = prime * result + ((mKey != null) ? mKey.hashCode() : 0);
+            return result;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (obj == null) return false;
+
+            if (getClass() == obj.getClass()) {
+                DayForecast forecast = (DayForecast) obj;
+                return (TextUtils.equals(mKey, forecast.mKey));
+            }
+            return false;
+        }
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder builder = new StringBuilder()
+            .append(" City Name: ").append(mCity)
+            .append(" Condition Code: ").append(mConditionCode)
+            .append(" Temperature: ").append(mTemperature)
+            .append(" Temperature Unit: ").append(mTempUnit)
+            .append(" Humidity: ").append(mHumidity)
+            .append(" Wind speed: ").append(mWindSpeed)
+            .append(" Wind direction: ").append(mWindDirection)
+            .append(" Wind Speed Unit: ").append(mWindSpeedUnit)
+            .append(" Today's high temp: ").append(mTodaysHighTemp)
+            .append(" Today's low temp: ").append(mTodaysLowTemp)
+            .append(" Timestamp: ").append(mTimestamp).append(" Forecasts: [");
+        for (DayForecast dayForecast : mForecastList) {
+            builder.append(dayForecast.toString());
+        }
+        return builder.append("]}").toString();
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((mKey != null) ? mKey.hashCode() : 0);
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == null) return false;
+
+        if (getClass() == obj.getClass()) {
+            WeatherInfo info = (WeatherInfo) obj;
+            return (TextUtils.equals(mKey, info.mKey));
+        }
+        return false;
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/lineageos/weather/WeatherLocation.java b/app/src/main/java/lineageos/weather/WeatherLocation.java
new file mode 100644
index 0000000..21a6b07
--- /dev/null
+++ b/app/src/main/java/lineageos/weather/WeatherLocation.java
@@ -0,0 +1,274 @@
+/*
+ * Copyright (C) 2016 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package lineageos.weather;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import android.text.TextUtils;
+import lineageos.os.Build;
+import lineageos.os.Concierge;
+import lineageos.os.Concierge.ParcelInfo;
+
+import java.util.UUID;
+
+/**
+ * A class representing a geographical location that a weather service provider can use to
+ * get weather data from. Each service provider will potentially populate objects of this class
+ * with different content, so make sure you don't preserve the values when a service provider
+ * is changed
+ */
+public final class WeatherLocation implements Parcelable{
+    private String mCityId;
+    private String mCity;
+    private String mState;
+    private String mPostal;
+    private String mCountryId;
+    private String mCountry;
+    private String mKey;
+
+    private WeatherLocation() {}
+
+    /**
+     * Builder class for {@link WeatherLocation}
+     */
+    public static class Builder {
+        String mCityId = "";
+        String mCity = "";
+        String mState = "";
+        String mPostal = "";
+        String mCountryId = "";
+        String mCountry = "";
+
+        /**
+         * @param cityId An identifier for the city (for example WOEID - Where On Earth IDentifier)
+         * @param cityName The name of the city
+         */
+        public Builder(String cityId, String cityName) {
+            if (cityId == null || cityName == null) {
+                throw new IllegalArgumentException("Illegal to set city id AND city to null");
+            }
+            this.mCityId = cityId;
+            this.mCity = cityName;
+        }
+
+        /**
+         * @param cityName The name of the city
+         */
+        public Builder(String cityName) {
+            if (cityName == null) {
+                throw new IllegalArgumentException("City name can't be null");
+            }
+            this.mCity = cityName;
+        }
+
+        /**
+         * @param countryId An identifier for the country (for example ISO alpha-2, ISO alpha-3,
+         *                 ISO 3166-1 numeric-3, etc)
+         * @return The {@link Builder} instance
+         */
+        public Builder setCountryId(String countryId) {
+            if (countryId == null) {
+                throw new IllegalArgumentException("Country ID can't be null");
+            }
+            this.mCountryId = countryId;
+            return this;
+        }
+
+        /**
+         * @param country The country name
+         * @return The {@link Builder} instance
+         */
+        public Builder setCountry(String country) {
+            if (country == null) {
+                throw new IllegalArgumentException("Country can't be null");
+            }
+            this.mCountry = country;
+            return this;
+        }
+
+        /**
+         * @param postalCode The postal/ZIP code
+         * @return The {@link Builder} instance
+         */
+        public Builder setPostalCode(String postalCode) {
+            if (postalCode == null) {
+                throw new IllegalArgumentException("Postal code/ZIP can't be null");
+            }
+            this.mPostal = postalCode;
+            return this;
+        }
+
+        /**
+         * @param state The state or territory where the city is located
+         * @return The {@link Builder} instance
+         */
+        public Builder setState(String state) {
+            if (state == null) {
+                throw new IllegalArgumentException("State can't be null");
+            }
+            this.mState = state;
+            return this;
+        }
+
+        /**
+         * Combine all of the options that have been set and return a new {@link WeatherLocation}
+         * object
+         * @return {@link WeatherLocation}
+         */
+        public WeatherLocation build() {
+            WeatherLocation weatherLocation = new WeatherLocation();
+            weatherLocation.mCityId = this.mCityId;
+            weatherLocation.mCity = this.mCity;
+            weatherLocation.mState = this.mState;
+            weatherLocation.mPostal = this.mPostal;
+            weatherLocation.mCountryId = this.mCountryId;
+            weatherLocation.mCountry = this.mCountry;
+            weatherLocation.mKey = UUID.randomUUID().toString();
+            return weatherLocation;
+        }
+    }
+
+    /**
+     * @return The city ID. This method will return an empty string if the city ID was not set
+     */
+    public String getCityId() {
+        return mCityId;
+    }
+
+    /**
+     * @return The city name. This method will return an empty string if the city name was not set
+     */
+    public String getCity() {
+        return mCity;
+    }
+
+    /**
+     * @return The state name. This method will return an empty string if the state was not set
+     */
+    public String getState() {
+        return mState;
+    }
+
+    /**
+     * @return The postal/ZIP code. This method will return an empty string if the postal/ZIP code
+     * was not set
+     */
+    public String getPostalCode() {
+        return mPostal;
+    }
+
+    /**
+     * @return The country ID. This method will return an empty string if the country ID was not set
+     */
+    public String getCountryId() {
+        return mCountryId;
+    }
+
+    /**
+     * @return The country name. This method will return an empty string if the country ID was not
+     * set
+     */
+    public String getCountry() {
+        return mCountry;
+    }
+
+    private WeatherLocation(Parcel in) {
+        // Read parcelable version via the Concierge
+        ParcelInfo parcelInfo = Concierge.receiveParcel(in);
+        int parcelableVersion = parcelInfo.getParcelVersion();
+
+        if (parcelableVersion >= Build.LINEAGE_VERSION_CODES.ELDERBERRY) {
+            mKey = in.readString();
+            mCityId = in.readString();
+            mCity = in.readString();
+            mState = in.readString();
+            mPostal = in.readString();
+            mCountryId = in.readString();
+            mCountry = in.readString();
+        }
+
+        // Complete parcel info for the concierge
+        parcelInfo.complete();
+    }
+
+    public static final Creator<WeatherLocation> CREATOR = new Creator<WeatherLocation>() {
+        @Override
+        public WeatherLocation createFromParcel(Parcel in) {
+            return new WeatherLocation(in);
+        }
+
+        @Override
+        public WeatherLocation[] newArray(int size) {
+            return new WeatherLocation[size];
+        }
+    };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        // Tell the concierge to prepare the parcel
+        ParcelInfo parcelInfo = Concierge.prepareParcel(dest);
+
+        // ==== ELDERBERRY =====
+        dest.writeString(mKey);
+        dest.writeString(mCityId);
+        dest.writeString(mCity);
+        dest.writeString(mState);
+        dest.writeString(mPostal);
+        dest.writeString(mCountryId);
+        dest.writeString(mCountry);
+
+        // Complete parcel info for the concierge
+        parcelInfo.complete();
+    }
+
+    @Override
+    public String toString() {
+        return new StringBuilder()
+                .append("{ City ID: ").append(mCityId)
+                .append(" City: ").append(mCity)
+                .append(" State: ").append(mState)
+                .append(" Postal/ZIP Code: ").append(mPostal)
+                .append(" Country Id: ").append(mCountryId)
+                .append(" Country: ").append(mCountry).append("}")
+                .toString();
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((mKey != null) ? mKey.hashCode() : 0);
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == null) return false;
+
+        if (getClass() == obj.getClass()) {
+            WeatherLocation location = (WeatherLocation) obj;
+            return (TextUtils.equals(mKey, location.mKey));
+        }
+        return false;
+    }
+}
diff --git a/app/src/main/java/lineageos/weather/util/WeatherUtils.java b/app/src/main/java/lineageos/weather/util/WeatherUtils.java
new file mode 100644
index 0000000..dd8418f
--- /dev/null
+++ b/app/src/main/java/lineageos/weather/util/WeatherUtils.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2016 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package lineageos.weather.util;
+
+
+import lineageos.providers.WeatherContract;
+
+import java.text.DecimalFormat;
+
+/**
+ * Helper class to perform operations and formatting of weather data
+ */
+public class WeatherUtils {
+
+    /**
+     * Converts a temperature expressed in degrees Celsius to degrees Fahrenheit
+     * @param celsius temperature in Celsius
+     * @return the temperature in degrees Fahrenheit
+     */
+    public static double celsiusToFahrenheit(double celsius) {
+        return ((celsius * (9d/5d)) + 32d);
+    }
+
+    /**
+     * Converts a temperature expressed in degrees Fahrenheit to degrees Celsius
+     * @param fahrenheit temperature in Fahrenheit
+     * @return the temperature in degrees Celsius
+     */
+    public static double fahrenheitToCelsius(double fahrenheit) {
+        return  ((fahrenheit - 32d) * (5d/9d));
+    }
+
+    /**
+     * Returns a string representation of the temperature and unit supplied. The temperature value
+     * will be half-even rounded.
+     * @param temperature the temperature value
+     * @param tempUnit A valid {@link WeatherContract.WeatherColumns.TempUnit}
+     * @return A string with the format XX&deg;F or XX&deg;C (where XX is the temperature)
+     * depending on the temperature unit that was provided or null if an invalid unit is supplied
+     */
+    public static String formatTemperature(double temperature, int tempUnit) {
+        if (!isValidTempUnit(tempUnit)) return null;
+        if (Double.isNaN(temperature)) return "-";
+
+        DecimalFormat noDigitsFormat = new DecimalFormat("0");
+        String noDigitsTemp = noDigitsFormat.format(temperature);
+        if (noDigitsTemp.equals("-0")) {
+            noDigitsTemp = "0";
+        }
+
+        StringBuilder formatted = new StringBuilder()
+                .append(noDigitsTemp).append("\u00b0");
+        if (tempUnit == WeatherContract.WeatherColumns.TempUnit.CELSIUS) {
+            formatted.append("C");
+        } else if (tempUnit == WeatherContract.WeatherColumns.TempUnit.FAHRENHEIT) {
+            formatted.append("F");
+        }
+        return formatted.toString();
+    }
+
+    private static boolean isValidTempUnit(int unit) {
+        switch (unit) {
+            case WeatherContract.WeatherColumns.TempUnit.CELSIUS:
+            case WeatherContract.WeatherColumns.TempUnit.FAHRENHEIT:
+                return true;
+            default:
+                return false;
+        }
+    }
+}
diff --git a/app/src/main/java/lineageos/weatherservice/ServiceRequest.java b/app/src/main/java/lineageos/weatherservice/ServiceRequest.java
new file mode 100644
index 0000000..7f45e56
--- /dev/null
+++ b/app/src/main/java/lineageos/weatherservice/ServiceRequest.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2016 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package lineageos.weatherservice;
+
+
+import android.os.RemoteException;
+
+import androidx.annotation.NonNull;
+
+import lineageos.weatherservice.IWeatherProviderServiceClient;
+import lineageos.weather.LineageWeatherManager;
+import lineageos.weather.RequestInfo;
+
+/**
+ * This class represents a request submitted by the system to the active weather provider service
+ */
+public final class ServiceRequest {
+
+    private final RequestInfo mInfo;
+    private final IWeatherProviderServiceClient mClient;
+
+    private enum Status {
+        IN_PROGRESS, COMPLETED, CANCELLED, FAILED, REJECTED
+    }
+    private Status mStatus;
+
+    /* package */ ServiceRequest(RequestInfo info, IWeatherProviderServiceClient client) {
+        mInfo = info;
+        mClient = client;
+        mStatus = Status.IN_PROGRESS;
+    }
+
+    /**
+     * Obtains the request information
+     * @return {@link lineageos.weather.RequestInfo}
+     */
+    public RequestInfo getRequestInfo() {
+        return mInfo;
+    }
+
+    /**
+     * This method should be called once the request has been completed
+     */
+    public void complete(@NonNull ServiceRequestResult result) {
+        synchronized (this) {
+            if (mStatus.equals(Status.IN_PROGRESS)) {
+                try {
+                    final int requestType = mInfo.getRequestType();
+                    switch (requestType) {
+                        case RequestInfo.TYPE_WEATHER_BY_GEO_LOCATION_REQ:
+                        case RequestInfo.TYPE_WEATHER_BY_WEATHER_LOCATION_REQ:
+                            if (result.getWeatherInfo() == null) {
+                                throw new IllegalStateException("The service request result doesn't"
+                                        + " contain a valid WeatherInfo object");
+                            }
+                            mClient.setServiceRequestState(mInfo, result,
+                                    LineageWeatherManager.RequestStatus.COMPLETED);
+                            break;
+                        case RequestInfo.TYPE_LOOKUP_CITY_NAME_REQ:
+                            if (result.getLocationLookupList() == null
+                                    || result.getLocationLookupList().size() <= 0) {
+                                //In case the user decided to mark this request as completed with
+                                //null or empty list. It's not necessarily a failure
+                                mClient.setServiceRequestState(mInfo, null,
+                                        LineageWeatherManager.RequestStatus.NO_MATCH_FOUND);
+                            } else {
+                                mClient.setServiceRequestState(mInfo, result,
+                                        LineageWeatherManager.RequestStatus.COMPLETED);
+                            }
+                            break;
+                    }
+                } catch (RemoteException e) {
+                }
+                mStatus = Status.COMPLETED;
+            }
+        }
+    }
+
+    /**
+     * This method should be called if the service failed to process the request
+     * (no internet connection, time out, etc.)
+     */
+    public void fail() {
+        synchronized (this) {
+            if (mStatus.equals(Status.IN_PROGRESS)) {
+                try {
+                    final int requestType = mInfo.getRequestType();
+                    switch (requestType) {
+                        case RequestInfo.TYPE_WEATHER_BY_GEO_LOCATION_REQ:
+                        case RequestInfo.TYPE_WEATHER_BY_WEATHER_LOCATION_REQ:
+                            mClient.setServiceRequestState(mInfo, null,
+                                    LineageWeatherManager.RequestStatus.FAILED);
+                            break;
+                        case RequestInfo.TYPE_LOOKUP_CITY_NAME_REQ:
+                            mClient.setServiceRequestState(mInfo, null,
+                                    LineageWeatherManager.RequestStatus.FAILED);
+                            break;
+                    }
+                } catch (RemoteException e) {
+                }
+                mStatus = Status.FAILED;
+            }
+        }
+    }
+
+    /**
+     * This method should be called if the service decides not to honor the request. Note this
+     * method will accept only the following values.
+     * <ul>
+     * <li>{@link lineageos.weather.LineageWeatherManager.RequestStatus#SUBMITTED_TOO_SOON}</li>
+     * <li>{@link lineageos.weather.LineageWeatherManager.RequestStatus#ALREADY_IN_PROGRESS}</li>
+     * </ul>
+     * Attempting to pass any other value will get you an IllegalArgumentException
+     * @param status
+     */
+    public void reject(int status) {
+        synchronized (this) {
+            if (mStatus.equals(Status.IN_PROGRESS)) {
+                switch (status) {
+                    case LineageWeatherManager.RequestStatus.ALREADY_IN_PROGRESS:
+                    case LineageWeatherManager.RequestStatus.SUBMITTED_TOO_SOON:
+                        try {
+                            mClient.setServiceRequestState(mInfo, null, status);
+                        } catch (RemoteException e) {
+                            e.printStackTrace();
+                        }
+                        break;
+                    default:
+                        throw new IllegalArgumentException("Can't reject with status " + status);
+                }
+                mStatus = Status.REJECTED;
+            }
+        }
+    }
+
+    /**
+     * Called by the WeatherProviderService base class to notify we don't want this request anymore.
+     * The service implementing the WeatherProviderService will be notified of this action
+     * via onRequestCancelled()
+     * @hide
+     */
+    public void cancel() {
+        synchronized (this) {
+            mStatus = Status.CANCELLED;
+        }
+    }
+}
diff --git a/app/src/main/java/lineageos/weatherservice/ServiceRequestResult.java b/app/src/main/java/lineageos/weatherservice/ServiceRequestResult.java
new file mode 100644
index 0000000..cb1b2a0
--- /dev/null
+++ b/app/src/main/java/lineageos/weatherservice/ServiceRequestResult.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2016 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package lineageos.weatherservice;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import android.text.TextUtils;
+
+import androidx.annotation.NonNull;
+
+import lineageos.os.Build;
+import lineageos.os.Concierge;
+import lineageos.os.Concierge.ParcelInfo;
+import lineageos.weather.WeatherLocation;
+import lineageos.weather.WeatherInfo;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+/**
+ * Use this class to build a request result.
+ */
+public final class ServiceRequestResult implements Parcelable {
+
+    private WeatherInfo mWeatherInfo;
+    private List<WeatherLocation> mLocationLookupList;
+    private String mKey;
+
+    private ServiceRequestResult() {}
+
+    private ServiceRequestResult(Parcel in) {
+        // Read parcelable version via the Concierge
+        ParcelInfo parcelInfo = Concierge.receiveParcel(in);
+        int parcelableVersion = parcelInfo.getParcelVersion();
+
+        if (parcelableVersion >= Build.LINEAGE_VERSION_CODES.ELDERBERRY) {
+            mKey = in.readString();
+            int hasWeatherInfo = in.readInt();
+            if (hasWeatherInfo == 1) {
+                mWeatherInfo = WeatherInfo.CREATOR.createFromParcel(in);
+            }
+            int hasLocationLookupList = in.readInt();
+            if (hasLocationLookupList == 1) {
+                mLocationLookupList = new ArrayList<>();
+                int listSize = in.readInt();
+                while (listSize > 0) {
+                    mLocationLookupList.add(WeatherLocation.CREATOR.createFromParcel(in));
+                    listSize--;
+                }
+            }
+        }
+
+        // Complete parcel info for the concierge
+        parcelInfo.complete();
+    }
+
+    public static final Creator<ServiceRequestResult> CREATOR
+            = new Creator<ServiceRequestResult>() {
+        @Override
+        public ServiceRequestResult createFromParcel(Parcel in) {
+            return new ServiceRequestResult(in);
+        }
+
+        @Override
+        public ServiceRequestResult[] newArray(int size) {
+            return new ServiceRequestResult[size];
+        }
+    };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        // Tell the concierge to prepare the parcel
+        ParcelInfo parcelInfo = Concierge.prepareParcel(dest);
+
+        // ==== ELDERBERRY =====
+        dest.writeString(mKey);
+        if (mWeatherInfo != null) {
+            dest.writeInt(1);
+            mWeatherInfo.writeToParcel(dest, 0);
+        } else {
+            dest.writeInt(0);
+        }
+        if (mLocationLookupList != null) {
+            dest.writeInt(1);
+            dest.writeInt(mLocationLookupList.size());
+            for (WeatherLocation lookup : mLocationLookupList) {
+                lookup.writeToParcel(dest, 0);
+            }
+        } else {
+            dest.writeInt(0);
+        }
+
+        // Complete parcel info for the concierge
+        parcelInfo.complete();
+    }
+
+    /**
+     * Builder class for {@link ServiceRequestResult}
+     */
+    public static class Builder {
+        private WeatherInfo mWeatherInfo;
+        private List<WeatherLocation> mLocationLookupList;
+        public Builder() {
+            this.mWeatherInfo = null;
+            this.mLocationLookupList = null;
+        }
+
+        /**
+         * @param weatherInfo The WeatherInfo object holding the data that will be used to update
+         *                    the weather content provider
+         */
+        public Builder(@NonNull WeatherInfo weatherInfo) {
+            if (weatherInfo == null) {
+                throw new IllegalArgumentException("WeatherInfo can't be null");
+            }
+
+            mWeatherInfo = weatherInfo;
+        }
+
+        /**
+         * @param locations The list of WeatherLocation objects. The list should not be null
+         */
+        public Builder(@NonNull List<WeatherLocation> locations) {
+            if (locations == null) {
+                throw new IllegalArgumentException("Weather location list can't be null");
+            }
+            mLocationLookupList = locations;
+        }
+
+        /**
+         * Creates a {@link ServiceRequestResult} with the arguments
+         * supplied to this builder
+         * @return {@link ServiceRequestResult}
+         */
+        public ServiceRequestResult build() {
+            ServiceRequestResult result = new ServiceRequestResult();
+            result.mWeatherInfo = this.mWeatherInfo;
+            result.mLocationLookupList = this.mLocationLookupList;
+            result.mKey = UUID.randomUUID().toString();
+            return result;
+        }
+    }
+
+    /**
+     * @return The WeatherInfo object supplied by the weather provider service
+     */
+    public WeatherInfo getWeatherInfo() {
+        return mWeatherInfo;
+    }
+
+    /**
+     * @return The list of WeatherLocation objects supplied by the weather provider service
+     */
+    public List<WeatherLocation> getLocationLookupList() {
+        return new ArrayList<>(mLocationLookupList);
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((mKey != null) ? mKey.hashCode() : 0);
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == null) return false;
+
+        if (getClass() == obj.getClass()) {
+            ServiceRequestResult request = (ServiceRequestResult) obj;
+            return (TextUtils.equals(mKey, request.mKey));
+        }
+        return false;
+    }
+}
diff --git a/app/src/main/java/lineageos/weatherservice/WeatherProviderService.java b/app/src/main/java/lineageos/weatherservice/WeatherProviderService.java
new file mode 100644
index 0000000..2e3a535
--- /dev/null
+++ b/app/src/main/java/lineageos/weatherservice/WeatherProviderService.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2016 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package lineageos.weatherservice;
+
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import lineageos.weather.RequestInfo;
+
+import java.util.Collections;
+import java.util.Set;
+import java.util.WeakHashMap;
+
+
+public abstract class WeatherProviderService extends Service {
+
+    private Handler mHandler;
+    private IWeatherProviderServiceClient mClient;
+    private Set<ServiceRequest> mWeakRequestsSet
+            = Collections.newSetFromMap(new WeakHashMap<ServiceRequest, Boolean>());
+
+    /**
+     * The {@link android.content.Intent} action that must be declared as handled by a service in
+     * its manifest for the system to recognize it as a weather provider service
+     */
+    public static final String SERVICE_INTERFACE
+            = "lineageos.weatherservice.WeatherProviderService";
+
+    /**
+     * Name under which a {@link WeatherProviderService} publishes information about itself.
+     * This meta-data must reference an XML resource containing
+     * a <code>&lt;weather-provider-service&gt;</code>
+     * tag.
+     */
+    public static final String SERVICE_META_DATA = "lineageos.weatherservice";
+
+    @Override
+    protected final void attachBaseContext(Context base) {
+        super.attachBaseContext(base);
+        mHandler = new ServiceHandler(base.getMainLooper());
+    }
+
+    @Override
+    public final IBinder onBind(Intent intent) {
+        return mBinder;
+    }
+
+    private final IWeatherProviderService.Stub mBinder = new IWeatherProviderService.Stub() {
+
+        @Override
+        public void processWeatherUpdateRequest(final RequestInfo info) {
+            mHandler.obtainMessage(ServiceHandler.MSG_ON_NEW_REQUEST, info).sendToTarget();
+        }
+
+        @Override
+        public void processCityNameLookupRequest(final RequestInfo info) {
+            mHandler.obtainMessage(ServiceHandler.MSG_ON_NEW_REQUEST, info).sendToTarget();
+        }
+
+        @Override
+        public void setServiceClient(IWeatherProviderServiceClient client) {
+            mHandler.obtainMessage(ServiceHandler.MSG_SET_CLIENT, client).sendToTarget();
+        }
+
+        @Override
+        public void cancelOngoingRequests() {
+            synchronized (mWeakRequestsSet) {
+                for (final ServiceRequest request : mWeakRequestsSet) {
+                    request.cancel();
+                    mWeakRequestsSet.remove(request);
+                    mHandler.obtainMessage(ServiceHandler.MSG_CANCEL_REQUEST, request)
+                            .sendToTarget();
+                }
+            }
+        }
+
+        @Override
+        public void cancelRequest(int requestId) {
+            synchronized (mWeakRequestsSet) {
+                for (final ServiceRequest request : mWeakRequestsSet) {
+                    if (request.getRequestInfo().hashCode() == requestId) {
+                        mWeakRequestsSet.remove(request);
+                        request.cancel();
+                        mHandler.obtainMessage(ServiceHandler.MSG_CANCEL_REQUEST, request)
+                                .sendToTarget();
+                        break;
+                    }
+                }
+            }
+        }
+    };
+
+    private class ServiceHandler extends Handler {
+
+        public ServiceHandler(Looper looper) {
+            super(looper);
+        }
+        public static final int MSG_SET_CLIENT = 1;
+        public static final int MSG_ON_NEW_REQUEST = 2;
+        public static final int MSG_CANCEL_REQUEST = 3;
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_SET_CLIENT: {
+                    mClient = (IWeatherProviderServiceClient) msg.obj;
+                    if (mClient != null) {
+                        onConnected();
+                    } else {
+                        onDisconnected();
+                    }
+                    return;
+                }
+                case MSG_ON_NEW_REQUEST: {
+                    RequestInfo info = (RequestInfo) msg.obj;
+                    if (info != null) {
+                        ServiceRequest request = new ServiceRequest(info, mClient);
+                        synchronized (mWeakRequestsSet) {
+                            mWeakRequestsSet.add(request);
+                        }
+                        onRequestSubmitted(request);
+                    }
+                    return;
+                }
+                case MSG_CANCEL_REQUEST: {
+                    ServiceRequest request = (ServiceRequest) msg.obj;
+                    onRequestCancelled(request);
+                    return;
+                }
+            }
+        }
+    }
+
+    /**
+     * The system has connected to this service.
+     */
+    protected void onConnected() {
+        /* Do nothing */
+    }
+
+    /**
+     * The system has disconnected from this service.
+     */
+    protected void onDisconnected() {
+        /* Do nothing */
+    }
+
+    /**
+     * A new request has been submitted to this service
+     * @param request The service request to be processed by this service
+     */
+    protected abstract void onRequestSubmitted(ServiceRequest request);
+
+    /**
+     * Called when the system is not interested on this request anymore. Note that the service
+     * <b>has marked the request as cancelled</b> and you must stop any ongoing operation
+     * (such as pulling data from internet) that this service could've been performing to honor the
+     * request.
+     *
+     * @param request The request cancelled by the system
+     */
+    protected abstract void onRequestCancelled(ServiceRequest request);
+}
\ No newline at end of file
diff --git a/app/src/main/java/net/osmand/aidlapi/AidlParams.java b/app/src/main/java/net/osmand/aidlapi/AidlParams.java
new file mode 100644
index 0000000..6024c5f
--- /dev/null
+++ b/app/src/main/java/net/osmand/aidlapi/AidlParams.java
@@ -0,0 +1,35 @@
+package net.osmand.aidlapi;
+
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+public abstract class AidlParams implements Parcelable {
+
+	@Override
+	public final void writeToParcel(Parcel dest, int flags) {
+		Bundle bundle = new Bundle();
+		writeToBundle(bundle);
+		dest.writeBundle(bundle);
+	}
+
+	public final void readFromParcel(Parcel in) {
+		Bundle bundle = in.readBundle(getClass().getClassLoader());
+		if (bundle != null) {
+			readFromBundle(bundle);
+		}
+	}
+
+	protected void writeToBundle(Bundle bundle) {
+
+	}
+
+	protected void readFromBundle(Bundle bundle) {
+
+	}
+
+	@Override
+	public int describeContents() {
+		return 0;
+	}
+}
\ No newline at end of file
diff --git a/app/src/main/java/net/osmand/aidlapi/gpx/AGpxBitmap.java b/app/src/main/java/net/osmand/aidlapi/gpx/AGpxBitmap.java
new file mode 100644
index 0000000..fd0d527
--- /dev/null
+++ b/app/src/main/java/net/osmand/aidlapi/gpx/AGpxBitmap.java
@@ -0,0 +1,49 @@
+package net.osmand.aidlapi.gpx;
+
+import android.graphics.Bitmap;
+import android.os.Bundle;
+import android.os.Parcel;
+
+import androidx.annotation.NonNull;
+
+import net.osmand.aidlapi.AidlParams;
+
+public class AGpxBitmap extends AidlParams {
+
+	private Bitmap bitmap;
+
+	public AGpxBitmap(@NonNull Bitmap bitmap) {
+		this.bitmap = bitmap;
+	}
+
+	public AGpxBitmap(Parcel in) {
+		readFromParcel(in);
+	}
+
+	public Bitmap getBitmap() {
+		return bitmap;
+	}
+
+	public static final Creator<AGpxBitmap> CREATOR = new Creator<AGpxBitmap>() {
+		@Override
+		public AGpxBitmap createFromParcel(Parcel in) {
+			return new AGpxBitmap(in);
+		}
+
+		@Override
+		public AGpxBitmap[] newArray(int size) {
+			return new AGpxBitmap[size];
+		}
+	};
+
+	@Override
+	public void writeToBundle(Bundle bundle) {
+		bundle.putParcelable("bitmap", bitmap);
+	}
+
+	@Override
+	protected void readFromBundle(Bundle bundle) {
+		bundle.setClassLoader(Bitmap.class.getClassLoader());
+		bitmap = bundle.getParcelable("bitmap");
+	}
+}
\ No newline at end of file
diff --git a/app/src/main/java/net/osmand/aidlapi/navigation/ADirectionInfo.java b/app/src/main/java/net/osmand/aidlapi/navigation/ADirectionInfo.java
new file mode 100644
index 0000000..91c59df
--- /dev/null
+++ b/app/src/main/java/net/osmand/aidlapi/navigation/ADirectionInfo.java
@@ -0,0 +1,73 @@
+package net.osmand.aidlapi.navigation;
+
+import android.os.Bundle;
+import android.os.Parcel;
+
+import net.osmand.aidlapi.AidlParams;
+
+public class ADirectionInfo extends AidlParams {
+
+	private int distanceTo; //distance to next turn
+	private int turnType; //turn type
+	private boolean isLeftSide; //is movement left-sided
+
+	public ADirectionInfo(int distanceTo, int turnType, boolean isLeftSide) {
+		this.distanceTo = distanceTo;
+		this.turnType = turnType;
+		this.isLeftSide = isLeftSide;
+	}
+
+	protected ADirectionInfo(Parcel in) {
+		readFromParcel(in);
+	}
+
+	public static final Creator<ADirectionInfo> CREATOR = new Creator<ADirectionInfo>() {
+		@Override
+		public ADirectionInfo createFromParcel(Parcel in) {
+			return new ADirectionInfo(in);
+		}
+
+		@Override
+		public ADirectionInfo[] newArray(int size) {
+			return new ADirectionInfo[size];
+		}
+	};
+
+	public int getDistanceTo() {
+		return distanceTo;
+	}
+
+	public int getTurnType() {
+		return turnType;
+	}
+
+	public boolean isLeftSide() {
+		return isLeftSide;
+	}
+
+	public void setDistanceTo(int distanceTo) {
+		this.distanceTo = distanceTo;
+	}
+
+	public void setTurnType(int turnType) {
+		this.turnType = turnType;
+	}
+
+	public void setLeftSide(boolean leftSide) {
+		isLeftSide = leftSide;
+	}
+
+	@Override
+	protected void readFromBundle(Bundle bundle) {
+		distanceTo = bundle.getInt("distanceTo");
+		turnType = bundle.getInt("turnType");
+		isLeftSide = bundle.getBoolean("isLeftSide");
+	}
+
+	@Override
+	public void writeToBundle(Bundle bundle) {
+		bundle.putInt("distanceTo", distanceTo);
+		bundle.putInt("turnType", turnType);
+		bundle.putBoolean("isLeftSide", isLeftSide);
+	}
+}
\ No newline at end of file
diff --git a/app/src/main/java/net/osmand/aidlapi/navigation/ANavigationUpdateParams.java b/app/src/main/java/net/osmand/aidlapi/navigation/ANavigationUpdateParams.java
new file mode 100644
index 0000000..aad9958
--- /dev/null
+++ b/app/src/main/java/net/osmand/aidlapi/navigation/ANavigationUpdateParams.java
@@ -0,0 +1,60 @@
+package net.osmand.aidlapi.navigation;
+
+import android.os.Bundle;
+import android.os.Parcel;
+
+import net.osmand.aidlapi.AidlParams;
+
+public class ANavigationUpdateParams extends AidlParams {
+
+	private boolean subscribeToUpdates = true;
+	private long callbackId = -1L;
+
+	public ANavigationUpdateParams() {
+
+	}
+
+	protected ANavigationUpdateParams(Parcel in) {
+		readFromParcel(in);
+	}
+
+	public static final Creator<ANavigationUpdateParams> CREATOR = new Creator<ANavigationUpdateParams>() {
+		@Override
+		public ANavigationUpdateParams createFromParcel(Parcel in) {
+			return new ANavigationUpdateParams(in);
+		}
+
+		@Override
+		public ANavigationUpdateParams[] newArray(int size) {
+			return new ANavigationUpdateParams[size];
+		}
+	};
+
+	public long getCallbackId() {
+		return callbackId;
+	}
+
+	public void setCallbackId(long callbackId) {
+		this.callbackId = callbackId;
+	}
+
+	public void setSubscribeToUpdates(boolean subscribeToUpdates) {
+		this.subscribeToUpdates = subscribeToUpdates;
+	}
+
+	public boolean isSubscribeToUpdates() {
+		return subscribeToUpdates;
+	}
+
+	@Override
+	protected void readFromBundle(Bundle bundle) {
+		callbackId = bundle.getLong("callbackId");
+		subscribeToUpdates = bundle.getBoolean("subscribeToUpdates");
+	}
+
+	@Override
+	public void writeToBundle(Bundle bundle) {
+		bundle.putLong("callbackId", callbackId);
+		bundle.putBoolean("subscribeToUpdates", subscribeToUpdates);
+	}
+}
\ No newline at end of file
diff --git a/app/src/main/java/net/osmand/aidlapi/navigation/ANavigationVoiceRouterMessageParams.java b/app/src/main/java/net/osmand/aidlapi/navigation/ANavigationVoiceRouterMessageParams.java
new file mode 100644
index 0000000..4c4ce89
--- /dev/null
+++ b/app/src/main/java/net/osmand/aidlapi/navigation/ANavigationVoiceRouterMessageParams.java
@@ -0,0 +1,60 @@
+package net.osmand.aidlapi.navigation;
+
+import android.os.Bundle;
+import android.os.Parcel;
+
+import net.osmand.aidlapi.AidlParams;
+
+public class ANavigationVoiceRouterMessageParams extends AidlParams {
+
+	private long callbackId = -1L;
+	private boolean subscribeToUpdates = true;
+
+	public ANavigationVoiceRouterMessageParams() {
+
+	}
+
+	protected ANavigationVoiceRouterMessageParams(Parcel in) {
+		readFromParcel(in);
+	}
+
+	public static final Creator<ANavigationVoiceRouterMessageParams> CREATOR = new Creator<ANavigationVoiceRouterMessageParams>() {
+		@Override
+		public ANavigationVoiceRouterMessageParams createFromParcel(Parcel in) {
+			return new ANavigationVoiceRouterMessageParams(in);
+		}
+
+		@Override
+		public ANavigationVoiceRouterMessageParams[] newArray(int size) {
+			return new ANavigationVoiceRouterMessageParams[size];
+		}
+	};
+
+	public long getCallbackId() {
+		return callbackId;
+	}
+
+	public void setCallbackId(long callbackId) {
+		this.callbackId = callbackId;
+	}
+
+	public void setSubscribeToUpdates(boolean subscribeToUpdates) {
+		this.subscribeToUpdates = subscribeToUpdates;
+	}
+
+	public boolean isSubscribeToUpdates() {
+		return subscribeToUpdates;
+	}
+
+	@Override
+	protected void readFromBundle(Bundle bundle) {
+		callbackId = bundle.getLong("callbackId");
+		subscribeToUpdates = bundle.getBoolean("subscribeToUpdates");
+	}
+
+	@Override
+	public void writeToBundle(Bundle bundle) {
+		bundle.putLong("callbackId", callbackId);
+		bundle.putBoolean("subscribeToUpdates", subscribeToUpdates);
+	}
+}
\ No newline at end of file
diff --git a/app/src/main/java/net/osmand/aidlapi/navigation/OnVoiceNavigationParams.java b/app/src/main/java/net/osmand/aidlapi/navigation/OnVoiceNavigationParams.java
new file mode 100644
index 0000000..3c74568
--- /dev/null
+++ b/app/src/main/java/net/osmand/aidlapi/navigation/OnVoiceNavigationParams.java
@@ -0,0 +1,70 @@
+package net.osmand.aidlapi.navigation;
+
+import android.os.Bundle;
+import android.os.Parcel;
+
+import net.osmand.aidlapi.AidlParams;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class OnVoiceNavigationParams extends AidlParams {
+
+	private ArrayList<String> cmds = new ArrayList<>();
+	private ArrayList<String> played = new ArrayList<>();
+
+	public OnVoiceNavigationParams() {
+
+	}
+
+	public OnVoiceNavigationParams(List<String> cmds, List<String> played) {
+		if (cmds != null) {
+			this.cmds.addAll(cmds);
+		}
+		if (played != null) {
+			this.played.addAll(played);
+		}
+	}
+
+	public OnVoiceNavigationParams(Parcel in) {
+		readFromParcel(in);
+	}
+
+	public static final Creator<OnVoiceNavigationParams> CREATOR = new Creator<OnVoiceNavigationParams>() {
+		@Override
+		public OnVoiceNavigationParams createFromParcel(Parcel in) {
+			return new OnVoiceNavigationParams(in);
+		}
+
+		@Override
+		public OnVoiceNavigationParams[] newArray(int size) {
+			return new OnVoiceNavigationParams[size];
+		}
+	};
+
+	public List<String> getCommands() {
+		return cmds;
+	}
+
+	public List<String> getPlayed() {
+		return played;
+	}
+
+	@Override
+	public void writeToBundle(Bundle bundle) {
+		bundle.putStringArrayList("cmds", cmds);
+		bundle.putStringArrayList("played", played);
+	}
+
+	@Override
+	protected void readFromBundle(Bundle bundle) {
+		cmds = bundle.getStringArrayList("cmds");
+		if (cmds == null) {
+			cmds = new ArrayList<>();
+		}
+		played = bundle.getStringArrayList("played");
+		if (played == null) {
+			played = new ArrayList<>();
+		}
+	}
+}
\ No newline at end of file
diff --git a/app/src/main/java/net/osmand/aidlapi/search/SearchResult.java b/app/src/main/java/net/osmand/aidlapi/search/SearchResult.java
new file mode 100644
index 0000000..863b90b
--- /dev/null
+++ b/app/src/main/java/net/osmand/aidlapi/search/SearchResult.java
@@ -0,0 +1,94 @@
+package net.osmand.aidlapi.search;
+
+import android.os.Bundle;
+import android.os.Parcel;
+
+import net.osmand.aidlapi.AidlParams;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class SearchResult extends AidlParams {
+
+	private double latitude;
+	private double longitude;
+
+	private String localName;
+	private String localTypeName;
+
+	private String alternateName;
+	private ArrayList<String> otherNames = new ArrayList<>();
+
+
+	public SearchResult(double latitude, double longitude, String localName, String localTypeName,
+	                    String alternateName, List<String> otherNames) {
+		this.latitude = latitude;
+		this.longitude = longitude;
+		this.localName = localName;
+		this.localTypeName = localTypeName;
+		this.alternateName = alternateName;
+		if (otherNames != null) {
+			this.otherNames.addAll(otherNames);
+		}
+	}
+
+	public SearchResult(Parcel in) {
+		readFromParcel(in);
+	}
+
+	public static final Creator<SearchResult> CREATOR = new Creator<SearchResult>() {
+		@Override
+		public SearchResult createFromParcel(Parcel in) {
+			return new SearchResult(in);
+		}
+
+		@Override
+		public SearchResult[] newArray(int size) {
+			return new SearchResult[size];
+		}
+	};
+
+	public double getLatitude() {
+		return latitude;
+	}
+
+	public double getLongitude() {
+		return longitude;
+	}
+
+	public String getLocalName() {
+		return localName;
+	}
+
+	public String getLocalTypeName() {
+		return localTypeName;
+	}
+
+	public String getAlternateName() {
+		return alternateName;
+	}
+
+	public List<String> getOtherNames() {
+		return otherNames;
+	}
+
+	@Override
+	public void writeToBundle(Bundle bundle) {
+		bundle.putDouble("latitude", latitude);
+		bundle.putDouble("longitude", longitude);
+		bundle.putString("localName", localName);
+		bundle.putString("localTypeName", localTypeName);
+		bundle.putString("alternateName", alternateName);
+		bundle.putStringArrayList("otherNames", otherNames);
+	}
+
+	@Override
+	protected void readFromBundle(Bundle bundle) {
+		latitude = bundle.getDouble("latitude");
+		longitude = bundle.getDouble("longitude");
+		localName = bundle.getString("localName");
+		localTypeName = bundle.getString("localTypeName");
+		alternateName = bundle.getString("alternateName");
+		otherNames = bundle.getStringArrayList("otherName");
+	}
+}
\ No newline at end of file
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBApplication.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBApplication.java
new file mode 100644
index 0000000..d1c0789
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBApplication.java
@@ -0,0 +1,2096 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, Arjan Schrijver, Carsten
+    Pfeiffer, Damien Gaignon, Daniel Dakhno, Daniele Gobbetti, Davis Mosenkovs,
+    Dmitriy Bogdanov, Joel Beckmeyer, Jos茅 Rebelo, Korn茅l Schmidt, Ludovic
+    Jozeau, Martin, Martin.JM, mvn23, Normano64, odavo32nof, Pauli Salmenrinne,
+    Pavel Elagin, Petr Van臎k, Saul Nunez, Taavi Eom盲e, x29a
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge;
+
+import android.annotation.TargetApi;
+import android.app.AlarmManager;
+import android.app.Application;
+import android.app.NotificationManager;
+import android.app.NotificationManager.Policy;
+import android.app.PendingIntent;
+import android.bluetooth.BluetoothAdapter;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.SharedPreferences;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Build.VERSION;
+import android.preference.PreferenceManager;
+import android.provider.ContactsContract.PhoneLookup;
+import android.util.Log;
+import android.util.TypedValue;
+
+import androidx.core.app.NotificationCompat;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+import nodomain.freeyourgadget.gadgetbridge.activities.ControlCenterv2;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.database.DBOpenHelper;
+import nodomain.freeyourgadget.gadgetbridge.database.PeriodicExporter;
+import nodomain.freeyourgadget.gadgetbridge.devices.DeviceManager;
+import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoMaster;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.externalevents.BluetoothStateChangeReceiver;
+import nodomain.freeyourgadget.gadgetbridge.externalevents.opentracks.OpenTracksContentObserver;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceService;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.model.Weather;
+import nodomain.freeyourgadget.gadgetbridge.service.NotificationCollectorMonitorService;
+import nodomain.freeyourgadget.gadgetbridge.util.AndroidUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+import nodomain.freeyourgadget.gadgetbridge.util.GBPrefs;
+import nodomain.freeyourgadget.gadgetbridge.util.LimitedQueue;
+import nodomain.freeyourgadget.gadgetbridge.util.PendingIntentUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+import nodomain.freeyourgadget.gadgetbridge.util.preferences.DevicePrefs;
+
+import static nodomain.freeyourgadget.gadgetbridge.model.DeviceType.AMAZFITBIP;
+import static nodomain.freeyourgadget.gadgetbridge.model.DeviceType.AMAZFITCOR;
+import static nodomain.freeyourgadget.gadgetbridge.model.DeviceType.AMAZFITCOR2;
+import static nodomain.freeyourgadget.gadgetbridge.model.DeviceType.FITPRO;
+import static nodomain.freeyourgadget.gadgetbridge.model.DeviceType.GALAXY_BUDS;
+import static nodomain.freeyourgadget.gadgetbridge.model.DeviceType.LEFUN;
+import static nodomain.freeyourgadget.gadgetbridge.model.DeviceType.MIBAND;
+import static nodomain.freeyourgadget.gadgetbridge.model.DeviceType.MIBAND2;
+import static nodomain.freeyourgadget.gadgetbridge.model.DeviceType.MIBAND2_HRX;
+import static nodomain.freeyourgadget.gadgetbridge.model.DeviceType.MIBAND3;
+import static nodomain.freeyourgadget.gadgetbridge.model.DeviceType.PEBBLE;
+import static nodomain.freeyourgadget.gadgetbridge.model.DeviceType.TLW64;
+import static nodomain.freeyourgadget.gadgetbridge.model.DeviceType.WATCHXPLUS;
+import static nodomain.freeyourgadget.gadgetbridge.util.GB.NOTIFICATION_CHANNEL_HIGH_PRIORITY_ID;
+import static nodomain.freeyourgadget.gadgetbridge.util.GB.NOTIFICATION_ID_ERROR;
+
+import org.apache.commons.lang3.StringUtils;
+import org.json.JSONObject;
+
+/**
+ * Main Application class that initializes and provides access to certain things like
+ * logging and DB access.
+ */
+public class GBApplication extends Application {
+    // Since this class must not log to slf4j, we use plain android.util.Log
+    private static final String TAG = "GBApplication";
+    public static final String DATABASE_NAME = "Gadgetbridge";
+
+    private static GBApplication context;
+    private static final Lock dbLock = new ReentrantLock();
+    private static DeviceService deviceService;
+    private static SharedPreferences sharedPrefs;
+    private static final String PREFS_VERSION = "shared_preferences_version";
+    //if preferences have to be migrated, increment the following and add the migration logic in migratePrefs below; see http://stackoverflow.com/questions/16397848/how-can-i-migrate-android-preferences-with-a-new-version
+    private static final int CURRENT_PREFS_VERSION = 45;
+
+    private static final LimitedQueue<Integer, String> mIDSenderLookup = new LimitedQueue<>(16);
+    private static GBPrefs prefs;
+    private static LockHandler lockHandler;
+    /**
+     * Note: is null on Lollipop
+     */
+    private static NotificationManager notificationManager;
+
+    public static final String ACTION_QUIT
+            = "nodomain.freeyourgadget.gadgetbridge.gbapplication.action.quit";
+    public static final String ACTION_LANGUAGE_CHANGE = "nodomain.freeyourgadget.gadgetbridge.gbapplication.action.language_change";
+    public static final String ACTION_THEME_CHANGE = "nodomain.freeyourgadget.gadgetbridge.gbapplication.action.theme_change";
+    public static final String ACTION_NEW_DATA = "nodomain.freeyourgadget.gadgetbridge.action.new_data";
+
+    private static GBApplication app;
+
+    private static final Logging logging = new Logging() {
+        @Override
+        protected String createLogDirectory() throws IOException {
+            if (GBEnvironment.env().isLocalTest()) {
+                return System.getProperty(Logging.PROP_LOGFILES_DIR);
+            } else {
+                File dir = FileUtils.getExternalFilesDir();
+                return dir.getAbsolutePath();
+            }
+        }
+    };
+    private static Locale language;
+
+    private DeviceManager deviceManager;
+    private BluetoothStateChangeReceiver bluetoothStateChangeReceiver;
+
+    private OpenTracksContentObserver openTracksObserver;
+
+    private long lastAutoExportTimestamp = 0;
+    private long autoExportScheduledTimestamp = 0;
+
+    public static void quit() {
+        GB.log("Quitting Gadgetbridge...", GB.INFO, null);
+        Intent quitIntent = new Intent(GBApplication.ACTION_QUIT);
+        LocalBroadcastManager.getInstance(context).sendBroadcast(quitIntent);
+        GBApplication.deviceService().quit();
+        System.exit(0);
+    }
+
+    public static void restart() {
+        GB.log("Restarting Gadgetbridge...", GB.INFO, null);
+        final Intent quitIntent = new Intent(GBApplication.ACTION_QUIT);
+        LocalBroadcastManager.getInstance(context).sendBroadcast(quitIntent);
+        GBApplication.deviceService().quit();
+
+        final Intent startActivity = new Intent(context, ControlCenterv2.class);
+        final PendingIntent pendingIntent = PendingIntentUtils.getActivity(context, 1337, startActivity, PendingIntent.FLAG_CANCEL_CURRENT, false);
+        final AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+        alarmManager.set(AlarmManager.RTC, System.currentTimeMillis() + 1500, pendingIntent);
+
+        Runtime.getRuntime().exit(0);
+    }
+
+    public GBApplication() {
+        context = this;
+        // don't do anything here, add it to onCreate instead
+
+        //if (BuildConfig.DEBUG) {
+        //    // detect everything
+        //    //StrictMode.enableDefaults();
+        //    // detect closeable objects
+        //    //StrictMode.setVmPolicy(
+        //    //        new StrictMode.VmPolicy.Builder()
+        //    //                .detectLeakedClosableObjects()
+        //    //                .penaltyLog()
+        //    //                .build()
+        //    //);
+        //}
+    }
+
+    public static Logging getLogging() {
+        return logging;
+    }
+
+    protected DeviceService createDeviceService() {
+        return new GBDeviceService(this);
+    }
+
+    @Override
+    public void onCreate() {
+        app = this;
+        super.onCreate();
+
+        if (lockHandler != null) {
+            // guard against multiple invocations (robolectric)
+            return;
+        }
+
+        sharedPrefs = PreferenceManager.getDefaultSharedPreferences(context);
+        prefs = new GBPrefs(sharedPrefs);
+
+        if (!GBEnvironment.isEnvironmentSetup()) {
+            GBEnvironment.setupEnvironment(GBEnvironment.createDeviceEnvironment());
+            // setup db after the environment is set up, but don't do it in test mode
+            // in test mode, it's done individually, see TestBase
+            setupDatabase();
+        }
+
+        // don't do anything here before we set up logging, otherwise
+        // slf4j may be implicitly initialized before we properly configured it.
+        setupLogging(isFileLoggingEnabled());
+
+        if (getPrefsFileVersion() != CURRENT_PREFS_VERSION) {
+            migratePrefs(getPrefsFileVersion());
+        }
+
+        // Uncomment the line below to force a device key migration, after you updated
+        // the devicetype.json file
+        //migrateDeviceTypes();
+
+        setupExceptionHandler(prefs.getBoolean("crash_notification", isDebug()));
+
+        Weather.getInstance().setCacheFile(getCacheDir(), prefs.getBoolean("cache_weather", true));
+
+        deviceManager = new DeviceManager(this);
+        String language = prefs.getString("language", "default");
+        setLanguage(language);
+
+        deviceService = createDeviceService();
+        loadAppsNotifBlackList();
+        loadAppsPebbleBlackList();
+
+        PeriodicExporter.enablePeriodicExport(context);
+
+        if (isRunningMarshmallowOrLater()) {
+            notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+            if (isRunningOreoOrLater()) {
+                bluetoothStateChangeReceiver = new BluetoothStateChangeReceiver();
+                registerReceiver(bluetoothStateChangeReceiver, new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED));
+            }
+            try {
+                //the following will ensure the notification manager is kept alive
+                startService(new Intent(this, NotificationCollectorMonitorService.class));
+            } catch (IllegalStateException e) {
+                String message = e.toString();
+                final Intent instructionsIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://gadgetbridge.org/basics/topics/background-service/"));
+                final PendingIntent pi = PendingIntentUtils.getActivity(context, 0, instructionsIntent, PendingIntent.FLAG_ONE_SHOT, false);
+                GB.notify(NOTIFICATION_ID_ERROR,
+                        new NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_HIGH_PRIORITY_ID)
+                                .setSmallIcon(R.drawable.ic_notification)
+                                .setContentTitle(getString(R.string.error_background_service))
+                                .setContentText(getString(R.string.error_background_service_reason_truncated))
+                                .setContentIntent(pi)
+                                .setStyle(new NotificationCompat.BigTextStyle()
+                                        .bigText(getString(R.string.error_background_service_reason) + " \"" + message + "\""))
+                                .setPriority(NotificationCompat.PRIORITY_DEFAULT)
+                                .build(), context);
+            }
+        }
+    }
+
+    @Override
+    public void onTrimMemory(int level) {
+        super.onTrimMemory(level);
+        if (level >= TRIM_MEMORY_BACKGROUND) {
+            if (!hasBusyDevice()) {
+                DBHelper.clearSession();
+            }
+        }
+    }
+
+    /**
+     * Returns true if at least a single device is busy, e.g synchronizing activity data
+     * or something similar.
+     * Note: busy is not the same as connected or initialized!
+     */
+    private boolean hasBusyDevice() {
+        List<GBDevice> devices = getDeviceManager().getDevices();
+        for (GBDevice device : devices) {
+            if (device.isBusy()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public static void setupLogging(boolean enabled) {
+        logging.setupLogging(enabled);
+    }
+
+    public static String getLogPath() {
+        return logging.getLogPath();
+    }
+
+    private void setupExceptionHandler(final boolean notifyOnCrash) {
+        final GBExceptionHandler handler = new GBExceptionHandler(Thread.getDefaultUncaughtExceptionHandler(), notifyOnCrash);
+        Thread.setDefaultUncaughtExceptionHandler(handler);
+    }
+
+    public static boolean isFileLoggingEnabled() {
+        return prefs.getBoolean("log_to_file", false);
+    }
+
+    public static boolean minimizeNotification() {
+        return prefs.getBoolean("minimize_priority", false);
+    }
+
+    public void setupDatabase() {
+        DaoMaster.OpenHelper helper;
+        GBEnvironment env = GBEnvironment.env();
+        if (env.isTest()) {
+            helper = new DaoMaster.DevOpenHelper(this, null, null);
+        } else {
+            helper = new DBOpenHelper(this, DATABASE_NAME, null);
+        }
+        SQLiteDatabase db = helper.getWritableDatabase();
+        DaoMaster daoMaster = new DaoMaster(db);
+        if (lockHandler == null) {
+            lockHandler = new LockHandler();
+        }
+        lockHandler.init(daoMaster, helper);
+    }
+
+    public static Context getContext() {
+        return context;
+    }
+
+    /**
+     * Returns the facade for talking to devices. Devices are managed by
+     * an Android Service and this facade provides access to its functionality.
+     *
+     * @return the facade for talking to the service/devices.
+     */
+    public static DeviceService deviceService() {
+        return deviceService;
+    }
+
+    /**
+     * Returns the facade for talking to a specific device. Devices are managed by
+     * an Android Service and this facade provides access to its functionality.
+     *
+     * @return the facade for talking to the service/device.
+     */
+    public static DeviceService deviceService(GBDevice device) {
+        return deviceService.forDevice(device);
+    }
+
+    /**
+     * Returns the DBHandler instance for reading/writing or throws GBException
+     * when that was not successful
+     * If acquiring was successful, callers must call #releaseDB when they
+     * are done (from the same thread that acquired the lock!
+     * <p>
+     * Callers must not hold a reference to the returned instance because it
+     * will be invalidated at some point.
+     *
+     * @return the DBHandler
+     * @throws GBException
+     * @see #releaseDB()
+     */
+    public static DBHandler acquireDB() throws GBException {
+        try {
+            if (dbLock.tryLock(30, TimeUnit.SECONDS)) {
+                return lockHandler;
+            }
+        } catch (InterruptedException ex) {
+            Log.i(TAG, "Interrupted while waiting for DB lock");
+        }
+        throw new GBException("Unable to access the database.");
+    }
+
+    /**
+     * Releases the database lock.
+     *
+     * @throws IllegalMonitorStateException if the current thread is not owning the lock
+     * @see #acquireDB()
+     */
+    public static void releaseDB() {
+        dbLock.unlock();
+    }
+
+    public static boolean isRunningMarshmallowOrLater() {
+        return VERSION.SDK_INT >= Build.VERSION_CODES.M;
+    }
+
+    public static boolean isRunningNougatOrLater() {
+        return VERSION.SDK_INT >= Build.VERSION_CODES.N;
+    }
+
+    public static boolean isRunningOreoOrLater() {
+        return VERSION.SDK_INT >= Build.VERSION_CODES.O;
+    }
+
+    public static boolean isRunningTenOrLater() {
+        return VERSION.SDK_INT >= Build.VERSION_CODES.Q;
+    }
+
+    public static boolean isRunningTwelveOrLater() {
+        return VERSION.SDK_INT >= 31;  // Build.VERSION_CODES.S, but our target SDK is lower
+    }
+
+    public static boolean isRunningPieOrLater() {
+        return VERSION.SDK_INT >= Build.VERSION_CODES.P;
+    }
+
+    private static boolean isPrioritySender(int prioritySenders, String number) {
+        if (prioritySenders == Policy.PRIORITY_SENDERS_ANY) {
+            return true;
+        } else {
+            Uri uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number));
+            String[] projection = new String[]{PhoneLookup._ID, PhoneLookup.STARRED};
+            Cursor cursor = context.getContentResolver().query(uri, projection, null, null, null);
+            boolean exists = false;
+            int starred = 0;
+            try {
+                if (cursor != null && cursor.moveToFirst()) {
+                    exists = true;
+                    starred = cursor.getInt(cursor.getColumnIndexOrThrow(PhoneLookup.STARRED));
+                }
+            } finally {
+                if (cursor != null) {
+                    cursor.close();
+                }
+            }
+            if (prioritySenders == Policy.PRIORITY_SENDERS_CONTACTS && exists) {
+                return true;
+            } else if (prioritySenders == Policy.PRIORITY_SENDERS_STARRED && starred == 1) {
+                return true;
+            }
+            return false;
+        }
+    }
+
+    @TargetApi(Build.VERSION_CODES.M)
+    public static boolean isPriorityNumber(int priorityType, String number) {
+        NotificationManager.Policy notificationPolicy = notificationManager.getNotificationPolicy();
+        if (priorityType == Policy.PRIORITY_CATEGORY_MESSAGES) {
+            if ((notificationPolicy.priorityCategories & Policy.PRIORITY_CATEGORY_MESSAGES) == Policy.PRIORITY_CATEGORY_MESSAGES) {
+                return isPrioritySender(notificationPolicy.priorityMessageSenders, number);
+            }
+        } else if (priorityType == Policy.PRIORITY_CATEGORY_CALLS) {
+            if ((notificationPolicy.priorityCategories & Policy.PRIORITY_CATEGORY_CALLS) == Policy.PRIORITY_CATEGORY_CALLS) {
+                return isPrioritySender(notificationPolicy.priorityCallSenders, number);
+            }
+        }
+        return false;
+    }
+
+    @TargetApi(Build.VERSION_CODES.M)
+    public static int getGrantedInterruptionFilter() {
+        if (GBApplication.isRunningMarshmallowOrLater() && notificationManager.isNotificationPolicyAccessGranted()) {
+            return notificationManager.getCurrentInterruptionFilter();
+        }
+        return NotificationManager.INTERRUPTION_FILTER_ALL;
+    }
+
+    private static HashSet<String> apps_notification_blacklist = null;
+
+    public static boolean appIsNotifBlacklisted(String packageName) {
+        if (apps_notification_blacklist == null) {
+            GB.log("appIsNotifBlacklisted: apps_notification_blacklist is null!", GB.INFO, null);
+        }
+        return apps_notification_blacklist != null && apps_notification_blacklist.contains(packageName);
+    }
+
+    public static void setAppsNotifBlackList(Set<String> packageNames) {
+        setAppsNotifBlackList(packageNames, sharedPrefs.edit());
+    }
+
+    public static void setAppsNotifBlackList(Set<String> packageNames, SharedPreferences.Editor editor) {
+        if (packageNames == null) {
+            GB.log("Set null apps_notification_blacklist", GB.INFO, null);
+            apps_notification_blacklist = new HashSet<>();
+        } else {
+            apps_notification_blacklist = new HashSet<>(packageNames);
+        }
+        GB.log("New apps_notification_blacklist has " + apps_notification_blacklist.size() + " entries", GB.INFO, null);
+        saveAppsNotifBlackList(editor);
+    }
+
+    private static void loadAppsNotifBlackList() {
+        GB.log("Loading apps_notification_blacklist", GB.INFO, null);
+        apps_notification_blacklist = (HashSet<String>) sharedPrefs.getStringSet(GBPrefs.PACKAGE_BLACKLIST, null); // lgtm [java/abstract-to-concrete-cast]
+        if (apps_notification_blacklist == null) {
+            apps_notification_blacklist = new HashSet<>();
+        }
+        GB.log("Loaded apps_notification_blacklist has " + apps_notification_blacklist.size() + " entries", GB.INFO, null);
+    }
+
+    private static void saveAppsNotifBlackList() {
+        saveAppsNotifBlackList(sharedPrefs.edit());
+    }
+
+    private static void saveAppsNotifBlackList(SharedPreferences.Editor editor) {
+        GB.log("Saving apps_notification_blacklist with " + apps_notification_blacklist.size() + " entries", GB.INFO, null);
+        if (apps_notification_blacklist.isEmpty()) {
+            editor.putStringSet(GBPrefs.PACKAGE_BLACKLIST, null);
+        } else {
+            Prefs.putStringSet(editor, GBPrefs.PACKAGE_BLACKLIST, apps_notification_blacklist);
+        }
+        editor.apply();
+    }
+
+    public static void addAppToNotifBlacklist(String packageName) {
+        if (apps_notification_blacklist.add(packageName)) {
+            saveAppsNotifBlackList();
+        }
+    }
+
+    public static synchronized void removeFromAppsNotifBlacklist(String packageName) {
+        GB.log("Removing from apps_notification_blacklist: " + packageName, GB.INFO, null);
+        apps_notification_blacklist.remove(packageName);
+        saveAppsNotifBlackList();
+    }
+
+    private static HashSet<String> apps_pebblemsg_blacklist = null;
+
+    public static boolean appIsPebbleBlacklisted(String sender) {
+        if (apps_pebblemsg_blacklist == null) {
+            GB.log("appIsPebbleBlacklisted: apps_pebblemsg_blacklist is null!", GB.INFO, null);
+        }
+        return apps_pebblemsg_blacklist != null && apps_pebblemsg_blacklist.contains(sender);
+    }
+
+    public static void setAppsPebbleBlackList(Set<String> packageNames) {
+        setAppsPebbleBlackList(packageNames, sharedPrefs.edit());
+    }
+
+    public static void setAppsPebbleBlackList(Set<String> packageNames, SharedPreferences.Editor editor) {
+        if (packageNames == null) {
+            GB.log("Set null apps_pebblemsg_blacklist", GB.INFO, null);
+            apps_pebblemsg_blacklist = new HashSet<>();
+        } else {
+            apps_pebblemsg_blacklist = new HashSet<>(packageNames);
+        }
+        GB.log("New apps_pebblemsg_blacklist has " + apps_pebblemsg_blacklist.size() + " entries", GB.INFO, null);
+        saveAppsPebbleBlackList(editor);
+    }
+
+    private static void loadAppsPebbleBlackList() {
+        GB.log("Loading apps_pebblemsg_blacklist", GB.INFO, null);
+        apps_pebblemsg_blacklist = (HashSet<String>) sharedPrefs.getStringSet(GBPrefs.PACKAGE_PEBBLEMSG_BLACKLIST, null); // lgtm [java/abstract-to-concrete-cast]
+        if (apps_pebblemsg_blacklist == null) {
+            apps_pebblemsg_blacklist = new HashSet<>();
+        }
+        GB.log("Loaded apps_pebblemsg_blacklist has " + apps_pebblemsg_blacklist.size() + " entries", GB.INFO, null);
+    }
+
+    private static void saveAppsPebbleBlackList() {
+        saveAppsPebbleBlackList(sharedPrefs.edit());
+    }
+
+    private static void saveAppsPebbleBlackList(SharedPreferences.Editor editor) {
+        GB.log("Saving apps_pebblemsg_blacklist with " + apps_pebblemsg_blacklist.size() + " entries", GB.INFO, null);
+        if (apps_pebblemsg_blacklist.isEmpty()) {
+            editor.putStringSet(GBPrefs.PACKAGE_PEBBLEMSG_BLACKLIST, null);
+        } else {
+            Prefs.putStringSet(editor, GBPrefs.PACKAGE_PEBBLEMSG_BLACKLIST, apps_pebblemsg_blacklist);
+        }
+        editor.apply();
+    }
+
+    public static void addAppToPebbleBlacklist(String packageName) {
+        if (apps_pebblemsg_blacklist.add(packageNameToPebbleMsgSender(packageName))) {
+            saveAppsPebbleBlackList();
+        }
+    }
+
+    public static synchronized void removeFromAppsPebbleBlacklist(String packageName) {
+        GB.log("Removing from apps_pebblemsg_blacklist: " + packageName, GB.INFO, null);
+        apps_pebblemsg_blacklist.remove(packageNameToPebbleMsgSender(packageName));
+        saveAppsPebbleBlackList();
+    }
+
+    public static String packageNameToPebbleMsgSender(String packageName) {
+        if ("eu.siacs.conversations".equals(packageName)) {
+            return ("Conversations");
+        } else if ("net.osmand.plus".equals(packageName)) {
+            return ("OsmAnd");
+        }
+        return packageName;
+    }
+
+    /**
+     * Deletes both the old Activity database and the new one recreates it with empty tables.
+     *
+     * @return true on successful deletion
+     */
+    public static synchronized boolean deleteActivityDatabase(Context context) {
+        // TODO: flush, close, reopen db
+        if (lockHandler != null) {
+            lockHandler.closeDb();
+        }
+        boolean result = deleteOldActivityDatabase(context);
+        result &= getContext().deleteDatabase(DATABASE_NAME);
+        return result;
+    }
+
+    /**
+     * Deletes the legacy (pre 0.12) Activity database
+     *
+     * @return true on successful deletion
+     */
+    public static synchronized boolean deleteOldActivityDatabase(Context context) {
+        DBHelper dbHelper = new DBHelper(context);
+        boolean result = true;
+        if (dbHelper.existsDB("ActivityDatabase")) {
+            result = getContext().deleteDatabase("ActivityDatabase");
+        }
+        return result;
+    }
+
+    private int getPrefsFileVersion() {
+        try {
+            return Integer.parseInt(sharedPrefs.getString(PREFS_VERSION, "0")); //0 is legacy
+        } catch (Exception e) {
+            //in version 1 this was an int
+            return 1;
+        }
+    }
+
+    private void migrateStringPrefToPerDevicePref(String globalPref, String globalPrefDefault, String perDevicePref, ArrayList<DeviceType> deviceTypes) {
+        SharedPreferences.Editor editor = sharedPrefs.edit();
+        String globalPrefValue = prefs.getString(globalPref, globalPrefDefault);
+        try (DBHandler db = acquireDB()) {
+            DaoSession daoSession = db.getDaoSession();
+            List<Device> activeDevices = DBHelper.getActiveDevices(daoSession);
+            for (Device dbDevice : activeDevices) {
+                SharedPreferences deviceSpecificSharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(dbDevice.getIdentifier());
+                if (deviceSpecificSharedPrefs != null) {
+                    SharedPreferences.Editor deviceSharedPrefsEdit = deviceSpecificSharedPrefs.edit();
+                    DeviceType deviceType = DeviceType.fromName(dbDevice.getTypeName());
+
+                    if (deviceTypes.contains(deviceType)) {
+                        Log.i(TAG, "migrating global string preference " + globalPref + " for " + deviceType.name() + " " + dbDevice.getIdentifier());
+                        deviceSharedPrefsEdit.putString(perDevicePref, globalPrefValue);
+                    }
+                    deviceSharedPrefsEdit.apply();
+                }
+            }
+            editor.remove(globalPref);
+            editor.apply();
+        } catch (Exception e) {
+            Log.w(TAG, "error acquiring DB lock");
+        }
+    }
+
+    private void migrateBooleanPrefToPerDevicePref(String globalPref, Boolean globalPrefDefault, String perDevicePref, ArrayList<DeviceType> deviceTypes) {
+        SharedPreferences.Editor editor = sharedPrefs.edit();
+        boolean globalPrefValue = prefs.getBoolean(globalPref, globalPrefDefault);
+        try (DBHandler db = acquireDB()) {
+            DaoSession daoSession = db.getDaoSession();
+            List<Device> activeDevices = DBHelper.getActiveDevices(daoSession);
+            for (Device dbDevice : activeDevices) {
+                SharedPreferences deviceSpecificSharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(dbDevice.getIdentifier());
+                if (deviceSpecificSharedPrefs != null) {
+                    SharedPreferences.Editor deviceSharedPrefsEdit = deviceSpecificSharedPrefs.edit();
+                    DeviceType deviceType = DeviceType.fromName(dbDevice.getTypeName());
+
+                    if (deviceTypes.contains(deviceType)) {
+                        Log.i(TAG, "migrating global boolean preference " + globalPref + " for " + deviceType.name() + " " + dbDevice.getIdentifier());
+                        deviceSharedPrefsEdit.putBoolean(perDevicePref, globalPrefValue);
+                    }
+                    deviceSharedPrefsEdit.apply();
+                }
+            }
+            editor.remove(globalPref);
+            editor.apply();
+        } catch (Exception e) {
+            Log.e(TAG, "Failed to migrate " + globalPref, e);
+        }
+    }
+
+    private void migrateDeviceTypes() {
+        try (DBHandler db = acquireDB()) {
+            final InputStream inputStream = getAssets().open("migrations/devicetype.json");
+            final byte[] buffer = new byte[inputStream.available()];
+            inputStream.read(buffer);
+            inputStream.close();
+            final JSONObject deviceMapping = new JSONObject(new String(buffer));
+            final JSONObject deviceIdNameMapping = deviceMapping.getJSONObject("by-id");
+
+            final DaoSession daoSession = db.getDaoSession();
+            final List<Device> activeDevices = DBHelper.getActiveDevices(daoSession);
+
+            for (Device dbDevice : activeDevices) {
+                String deviceTypeName = dbDevice.getTypeName();
+                if (deviceTypeName.isEmpty() || deviceTypeName.equals("UNKNOWN")) {
+                    deviceTypeName = deviceIdNameMapping.optString(
+                            String.valueOf(dbDevice.getType()),
+                            "UNKNOWN"
+                    );
+                    dbDevice.setTypeName(deviceTypeName);
+                    daoSession.getDeviceDao().update(dbDevice);
+                }
+            }
+        } catch (Exception e) {
+            Log.e(TAG, "Failed to migrate device types", e);
+        }
+    }
+
+    private void migratePrefs(int oldVersion) {
+        SharedPreferences.Editor editor = sharedPrefs.edit();
+
+        // this comes before all other migrations since the new column DeviceTypeName was added as non-null
+        if (oldVersion < 25) {
+            migrateDeviceTypes();
+        }
+
+        if (oldVersion == 0) {
+            String legacyGender = sharedPrefs.getString("mi_user_gender", null);
+            String legacyHeight = sharedPrefs.getString("mi_user_height_cm", null);
+            String legacyWeight = sharedPrefs.getString("mi_user_weight_kg", null);
+            String legacyYOB = sharedPrefs.getString("mi_user_year_of_birth", null);
+            if (legacyGender != null) {
+                int gender = "male".equals(legacyGender) ? 1 : "female".equals(legacyGender) ? 0 : 2;
+                editor.putString(ActivityUser.PREF_USER_GENDER, Integer.toString(gender));
+                editor.remove("mi_user_gender");
+            }
+            if (legacyHeight != null) {
+                editor.putString(ActivityUser.PREF_USER_HEIGHT_CM, legacyHeight);
+                editor.remove("mi_user_height_cm");
+            }
+            if (legacyWeight != null) {
+                editor.putString(ActivityUser.PREF_USER_WEIGHT_KG, legacyWeight);
+                editor.remove("mi_user_weight_kg");
+            }
+            if (legacyYOB != null) {
+                editor.putString("activity_user_year_of_birth", legacyYOB);
+                editor.remove("mi_user_year_of_birth");
+            }
+        }
+        if (oldVersion < 2) {
+            //migrate the integer version of gender introduced in version 1 to a string value, needed for the way Android accesses the shared preferences
+            int legacyGender_1 = 2;
+            try {
+                legacyGender_1 = sharedPrefs.getInt(ActivityUser.PREF_USER_GENDER, 2);
+            } catch (Exception e) {
+                Log.e(TAG, "Could not access legacy activity gender", e);
+            }
+            editor.putString(ActivityUser.PREF_USER_GENDER, Integer.toString(legacyGender_1));
+        }
+        if (oldVersion < 3) {
+            try (DBHandler db = acquireDB()) {
+                DaoSession daoSession = db.getDaoSession();
+                List<Device> activeDevices = DBHelper.getActiveDevices(daoSession);
+                for (Device dbDevice : activeDevices) {
+                    SharedPreferences deviceSpecificSharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(dbDevice.getIdentifier());
+                    if (deviceSpecificSharedPrefs != null) {
+                        SharedPreferences.Editor deviceSharedPrefsEdit = deviceSpecificSharedPrefs.edit();
+                        String preferenceKey = dbDevice.getIdentifier() + "_lastSportsActivityTimeMillis";
+                        long lastSportsActivityTimeMillis = sharedPrefs.getLong(preferenceKey, 0);
+                        if (lastSportsActivityTimeMillis != 0) {
+                            deviceSharedPrefsEdit.putLong("lastSportsActivityTimeMillis", lastSportsActivityTimeMillis);
+                            editor.remove(preferenceKey);
+                        }
+                        preferenceKey = dbDevice.getIdentifier() + "_lastSyncTimeMillis";
+                        long lastSyncTimeMillis = sharedPrefs.getLong(preferenceKey, 0);
+                        if (lastSyncTimeMillis != 0) {
+                            deviceSharedPrefsEdit.putLong("lastSyncTimeMillis", lastSyncTimeMillis);
+                            editor.remove(preferenceKey);
+                        }
+
+                        String newLanguage = null;
+                        Set<String> displayItems = null;
+
+                        DeviceType deviceType = DeviceType.fromName(dbDevice.getTypeName());
+
+                        if (deviceType == AMAZFITBIP || deviceType == AMAZFITCOR || deviceType == AMAZFITCOR2) {
+                            int oldLanguage = prefs.getInt("amazfitbip_language", -1);
+                            newLanguage = "auto";
+                            String[] oldLanguageLookup = {"zh_CN", "zh_TW", "en_US", "es_ES", "ru_RU", "de_DE", "it_IT", "fr_FR", "tr_TR"};
+                            if (oldLanguage >= 0 && oldLanguage < oldLanguageLookup.length) {
+                                newLanguage = oldLanguageLookup[oldLanguage];
+                            }
+                        }
+
+                        if (deviceType == AMAZFITBIP || deviceType == AMAZFITCOR) {
+                            deviceSharedPrefsEdit.putString("disconnect_notification", prefs.getString("disconnect_notification", "off"));
+                            deviceSharedPrefsEdit.putString("disconnect_notification_start", prefs.getString("disconnect_notification_start", "8:00"));
+                            deviceSharedPrefsEdit.putString("disconnect_notification_end", prefs.getString("disconnect_notification_end", "22:00"));
+                        }
+                        if (deviceType == MIBAND2 || deviceType == MIBAND2_HRX || deviceType == MIBAND3) {
+                            deviceSharedPrefsEdit.putString("do_not_disturb", prefs.getString("mi2_do_not_disturb", "off"));
+                            deviceSharedPrefsEdit.putString("do_not_disturb_start", prefs.getString("mi2_do_not_disturb_start", "1:00"));
+                            deviceSharedPrefsEdit.putString("do_not_disturb_end", prefs.getString("mi2_do_not_disturb_end", "6:00"));
+                        }
+                        if (dbDevice.getManufacturer().equals("Huami")) {
+                            deviceSharedPrefsEdit.putString("activate_display_on_lift_wrist", prefs.getString("activate_display_on_lift_wrist", "off"));
+                            deviceSharedPrefsEdit.putString("display_on_lift_start", prefs.getString("display_on_lift_start", "0:00"));
+                            deviceSharedPrefsEdit.putString("display_on_lift_end", prefs.getString("display_on_lift_end", "0:00"));
+                        }
+                        switch (deviceType) {
+                            case MIBAND:
+                                deviceSharedPrefsEdit.putBoolean("low_latency_fw_update", prefs.getBoolean("mi_low_latency_fw_update", true));
+                                deviceSharedPrefsEdit.putString("device_time_offset_hours", String.valueOf(prefs.getInt("mi_device_time_offset_hours", 0)));
+                                break;
+                            case AMAZFITCOR:
+                                displayItems = prefs.getStringSet("cor_display_items", null);
+                                break;
+                            case AMAZFITBIP:
+                                displayItems = prefs.getStringSet("bip_display_items", null);
+                                break;
+                            case MIBAND2:
+                            case MIBAND2_HRX:
+                                displayItems = prefs.getStringSet("mi2_display_items", null);
+                                deviceSharedPrefsEdit.putBoolean("mi2_enable_text_notifications", prefs.getBoolean("mi2_enable_text_notifications", true));
+                                deviceSharedPrefsEdit.putString("mi2_dateformat", prefs.getString("mi2_dateformat", "dateformat_time"));
+                                deviceSharedPrefsEdit.putBoolean("rotate_wrist_to_cycle_info", prefs.getBoolean("mi2_rotate_wrist_to_switch_info", false));
+                                break;
+                            case MIBAND3:
+                                newLanguage = prefs.getString("miband3_language", "auto");
+                                displayItems = prefs.getStringSet("miband3_display_items", null);
+                                deviceSharedPrefsEdit.putBoolean("swipe_unlock", prefs.getBoolean("mi3_band_screen_unlock", false));
+                                deviceSharedPrefsEdit.putString("night_mode", prefs.getString("mi3_night_mode", "off"));
+                                deviceSharedPrefsEdit.putString("night_mode_start", prefs.getString("mi3_night_mode_start", "16:00"));
+                                deviceSharedPrefsEdit.putString("night_mode_end", prefs.getString("mi3_night_mode_end", "7:00"));
+
+                        }
+                        if (displayItems != null) {
+                            deviceSharedPrefsEdit.putStringSet("display_items", displayItems);
+                        }
+                        if (newLanguage != null) {
+                            deviceSharedPrefsEdit.putString("language", newLanguage);
+                        }
+                        deviceSharedPrefsEdit.apply();
+                    }
+                }
+                editor.remove("amazfitbip_language");
+                editor.remove("bip_display_items");
+                editor.remove("cor_display_items");
+                editor.remove("disconnect_notification");
+                editor.remove("disconnect_notification_start");
+                editor.remove("disconnect_notification_end");
+                editor.remove("activate_display_on_lift_wrist");
+                editor.remove("display_on_lift_start");
+                editor.remove("display_on_lift_end");
+
+                editor.remove("mi_low_latency_fw_update");
+                editor.remove("mi_device_time_offset_hours");
+                editor.remove("mi2_do_not_disturb");
+                editor.remove("mi2_do_not_disturb_start");
+                editor.remove("mi2_do_not_disturb_end");
+                editor.remove("mi2_dateformat");
+                editor.remove("mi2_display_items");
+                editor.remove("mi2_rotate_wrist_to_switch_info");
+                editor.remove("mi2_enable_text_notifications");
+                editor.remove("mi3_band_screen_unlock");
+                editor.remove("mi3_night_mode");
+                editor.remove("mi3_night_mode_start");
+                editor.remove("mi3_night_mode_end");
+                editor.remove("miband3_language");
+
+            } catch (Exception e) {
+                Log.e(TAG, "Failed to migrate prefs to version 3", e);
+            }
+        }
+        if (oldVersion < 4) {
+            try (DBHandler db = acquireDB()) {
+                DaoSession daoSession = db.getDaoSession();
+                List<Device> activeDevices = DBHelper.getActiveDevices(daoSession);
+                for (Device dbDevice : activeDevices) {
+                    SharedPreferences deviceSharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(dbDevice.getIdentifier());
+                    SharedPreferences.Editor deviceSharedPrefsEdit = deviceSharedPrefs.edit();
+                    DeviceType deviceType = DeviceType.fromName(dbDevice.getTypeName());
+
+                    if (deviceType == MIBAND) {
+                        int deviceTimeOffsetHours = deviceSharedPrefs.getInt("device_time_offset_hours", 0);
+                        deviceSharedPrefsEdit.putString("device_time_offset_hours", Integer.toString(deviceTimeOffsetHours));
+                    }
+
+                    deviceSharedPrefsEdit.apply();
+                }
+            } catch (Exception e) {
+                Log.e(TAG, "Failed to migrate prefs to version 4", e);
+            }
+        }
+        if (oldVersion < 5) {
+            try (DBHandler db = acquireDB()) {
+                DaoSession daoSession = db.getDaoSession();
+                List<Device> activeDevices = DBHelper.getActiveDevices(daoSession);
+                for (Device dbDevice : activeDevices) {
+                    SharedPreferences deviceSpecificSharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(dbDevice.getIdentifier());
+                    if (deviceSpecificSharedPrefs != null) {
+                        SharedPreferences.Editor deviceSharedPrefsEdit = deviceSpecificSharedPrefs.edit();
+                        DeviceType deviceType = DeviceType.fromName(dbDevice.getTypeName());
+
+                        String newWearside = null;
+                        String newOrientation = null;
+                        String newTimeformat = null;
+                        switch (deviceType) {
+                            case AMAZFITBIP:
+                            case AMAZFITCOR:
+                            case AMAZFITCOR2:
+                            case MIBAND:
+                            case MIBAND2:
+                            case MIBAND2_HRX:
+                            case MIBAND3:
+                            case MIBAND4:
+                                newWearside = prefs.getString("mi_wearside", "left");
+                                break;
+                            case MIBAND5:
+                                newWearside = prefs.getString("mi_wearside", "left");
+                                break;
+                            case HPLUS:
+                                newWearside = prefs.getString("hplus_wrist", "left");
+                                newTimeformat = prefs.getString("hplus_timeformat", "24h");
+                                break;
+                            case ID115:
+                                newWearside = prefs.getString("id115_wrist", "left");
+                                newOrientation = prefs.getString("id115_screen_orientation", "horizontal");
+                                break;
+                            case ZETIME:
+                                newWearside = prefs.getString("zetime_wrist", "left");
+                                newTimeformat = prefs.getInt("zetime_timeformat", 1) == 2 ? "am/pm" : "24h";
+                                break;
+                        }
+                        if (newWearside != null) {
+                            deviceSharedPrefsEdit.putString("wearlocation", newWearside);
+                        }
+                        if (newOrientation != null) {
+                            deviceSharedPrefsEdit.putString("screen_orientation", newOrientation);
+                        }
+                        if (newTimeformat != null) {
+                            deviceSharedPrefsEdit.putString("timeformat", newTimeformat);
+                        }
+                        deviceSharedPrefsEdit.apply();
+                    }
+                }
+                editor.remove("hplus_timeformat");
+                editor.remove("hplus_wrist");
+                editor.remove("id115_wrist");
+                editor.remove("id115_screen_orientation");
+                editor.remove("mi_wearside");
+                editor.remove("zetime_timeformat");
+                editor.remove("zetime_wrist");
+
+            } catch (Exception e) {
+                Log.e(TAG, "Failed to migrate prefs to version 5", e);
+            }
+        }
+        if (oldVersion < 6) {
+            migrateBooleanPrefToPerDevicePref("mi2_enable_button_action", false, "button_action_enable", new ArrayList<>(Collections.singletonList(MIBAND2)));
+            migrateBooleanPrefToPerDevicePref("mi2_button_action_vibrate", false, "button_action_vibrate", new ArrayList<>(Collections.singletonList(MIBAND2)));
+            migrateStringPrefToPerDevicePref("mi_button_press_count", "6", "button_action_press_count", new ArrayList<>(Collections.singletonList(MIBAND2)));
+            migrateStringPrefToPerDevicePref("mi_button_press_count_max_delay", "2000", "button_action_press_max_interval", new ArrayList<>(Collections.singletonList(MIBAND2)));
+            migrateStringPrefToPerDevicePref("mi_button_press_count_match_delay", "0", "button_action_broadcast_delay", new ArrayList<>(Collections.singletonList(MIBAND2)));
+            migrateStringPrefToPerDevicePref("mi_button_press_broadcast", "nodomain.freeyourgadget.gadgetbridge.ButtonPressed", "button_action_broadcast", new ArrayList<>(Collections.singletonList(MIBAND2)));
+        }
+        if (oldVersion < 7) {
+            migrateStringPrefToPerDevicePref("mi_reserve_alarm_calendar", "0", "reserve_alarms_calendar", new ArrayList<>(Arrays.asList(MIBAND, MIBAND2)));
+        }
+
+        if (oldVersion < 8) {
+            for (int i = 1; i <= 16; i++) {
+                String message = prefs.getString("canned_message_dismisscall_" + i, null);
+                if (message != null) {
+                    migrateStringPrefToPerDevicePref("canned_message_dismisscall_" + i, "", "canned_message_dismisscall_" + i, new ArrayList<>(Collections.singletonList(PEBBLE)));
+                }
+            }
+            for (int i = 1; i <= 16; i++) {
+                String message = prefs.getString("canned_reply_" + i, null);
+                if (message != null) {
+                    migrateStringPrefToPerDevicePref("canned_reply_" + i, "", "canned_reply_" + i, new ArrayList<>(Collections.singletonList(PEBBLE)));
+                }
+            }
+        }
+        if (oldVersion < 9) {
+            try (DBHandler db = acquireDB()) {
+                DaoSession daoSession = db.getDaoSession();
+                List<Device> activeDevices = DBHelper.getActiveDevices(daoSession);
+                migrateBooleanPrefToPerDevicePref("transliteration", false, "pref_transliteration_enabled", (ArrayList) activeDevices);
+                Log.w(TAG, "migrating transliteration settings");
+            } catch (Exception e) {
+                Log.e(TAG, "Failed to migrate prefs to version 9", e);
+            }
+        }
+        if (oldVersion < 10) {
+            //migrate the string version of pref_galaxy_buds_ambient_volume to int due to transition to SeekBarPreference
+            try (DBHandler db = acquireDB()) {
+                DaoSession daoSession = db.getDaoSession();
+                List<Device> activeDevices = DBHelper.getActiveDevices(daoSession);
+                for (Device dbDevice : activeDevices) {
+                    SharedPreferences deviceSharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(dbDevice.getIdentifier());
+                    SharedPreferences.Editor deviceSharedPrefsEdit = deviceSharedPrefs.edit();
+                    DeviceType deviceType = DeviceType.fromName(dbDevice.getTypeName());
+
+                    if (deviceType == GALAXY_BUDS) {
+                        GB.log("migrating Galaxy Buds volume", GB.INFO, null);
+                        String volume = deviceSharedPrefs.getString(DeviceSettingsPreferenceConst.PREF_GALAXY_BUDS_AMBIENT_VOLUME, "1");
+                        deviceSharedPrefsEdit.putInt(DeviceSettingsPreferenceConst.PREF_GALAXY_BUDS_AMBIENT_VOLUME, Integer.parseInt(volume));
+                    }
+                    deviceSharedPrefsEdit.apply();
+                }
+            } catch (Exception e) {
+                Log.e(TAG, "Failed to migrate prefs to version 10", e);
+            }
+        }
+        if (oldVersion < 11) {
+            try (DBHandler db = acquireDB()) {
+                DaoSession daoSession = db.getDaoSession();
+                List<Device> activeDevices = DBHelper.getActiveDevices(daoSession);
+                for (Device dbDevice : activeDevices) {
+                    SharedPreferences deviceSharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(dbDevice.getIdentifier());
+                    SharedPreferences.Editor deviceSharedPrefsEdit = deviceSharedPrefs.edit();
+                    DeviceType deviceType = DeviceType.fromName(dbDevice.getTypeName());
+                    if (deviceType == WATCHXPLUS || deviceType == FITPRO || deviceType == LEFUN) {
+                        deviceSharedPrefsEdit.putBoolean("inactivity_warnings_enable", deviceSharedPrefs.getBoolean("pref_longsit_switch", false));
+                        deviceSharedPrefsEdit.remove("pref_longsit_switch");
+                    }
+                    if (deviceType == WATCHXPLUS || deviceType == FITPRO) {
+                        deviceSharedPrefsEdit.putString("inactivity_warnings_start", deviceSharedPrefs.getString("pref_longsit_start", "06:00"));
+                        deviceSharedPrefsEdit.putString("inactivity_warnings_end", deviceSharedPrefs.getString("pref_longsit_end", "23:00"));
+                        deviceSharedPrefsEdit.remove("pref_longsit_start");
+                        deviceSharedPrefsEdit.remove("pref_longsit_end");
+                    }
+                    if (deviceType == WATCHXPLUS || deviceType == LEFUN) {
+                        deviceSharedPrefsEdit.putString("inactivity_warnings_threshold", deviceSharedPrefs.getString("pref_longsit_period", "60"));
+                        deviceSharedPrefsEdit.remove("pref_longsit_period");
+                    }
+                    if (deviceType == TLW64) {
+                        deviceSharedPrefsEdit.putBoolean("inactivity_warnings_enable_noshed", deviceSharedPrefs.getBoolean("screen_longsit_noshed", false));
+                        deviceSharedPrefsEdit.remove("screen_longsit_noshed");
+                    }
+                    if (dbDevice.getManufacturer().equals("Huami")) {
+                        editor.putBoolean("inactivity_warnings_dnd", prefs.getBoolean("mi2_inactivity_warnings_dnd", false));
+                        editor.putString("inactivity_warnings_dnd_start", prefs.getString("mi2_inactivity_warnings_dnd_start", "12:00"));
+                        editor.putString("inactivity_warnings_dnd_end", prefs.getString("mi2_inactivity_warnings_dnd_end", "14:00"));
+                        editor.putBoolean("inactivity_warnings_enable", prefs.getBoolean("mi2_inactivity_warnings", false));
+                        editor.putInt("inactivity_warnings_threshold", prefs.getInt("mi2_inactivity_warnings_threshold", 60));
+                        editor.putString("inactivity_warnings_start", prefs.getString("mi2_inactivity_warnings_start", "06:00"));
+                        editor.putString("inactivity_warnings_end", prefs.getString("mi2_inactivity_warnings_end", "22:00"));
+                    }
+                    switch (deviceType) {
+                        case LEFUN:
+                            deviceSharedPrefsEdit.putString("language", deviceSharedPrefs.getString("pref_lefun_interface_language", "0"));
+                            deviceSharedPrefsEdit.remove("pref_lefun_interface_language");
+                            break;
+                        case FITPRO:
+                            deviceSharedPrefsEdit.putString("inactivity_warnings_threshold", deviceSharedPrefs.getString("pref_longsit_period", "4"));
+                            deviceSharedPrefsEdit.remove("pref_longsit_period");
+                            break;
+                        case ZETIME:
+                            editor.putString("do_not_disturb", prefs.getString("zetime_do_not_disturb", "off"));
+                            editor.putString("do_not_disturb_start", prefs.getString("zetime_do_not_disturb_start", "22:00"));
+                            editor.putString("do_not_disturb_end", prefs.getString("zetime_do_not_disturb_end", "07:00"));
+                            editor.putBoolean("inactivity_warnings_enable", prefs.getBoolean("zetime_inactivity_warnings", false));
+                            editor.putString("inactivity_warnings_start", prefs.getString("zetime_inactivity_warnings_start", "06:00"));
+                            editor.putString("inactivity_warnings_end", prefs.getString("zetime_inactivity_warnings_end", "22:00"));
+                            editor.putInt("inactivity_warnings_threshold", prefs.getInt("zetime_inactivity_warnings_threshold", 60));
+                            editor.putBoolean("inactivity_warnings_mo", prefs.getBoolean("zetime_prefs_inactivity_repetitions_mo", false));
+                            editor.putBoolean("inactivity_warnings_tu", prefs.getBoolean("zetime_prefs_inactivity_repetitions_tu", false));
+                            editor.putBoolean("inactivity_warnings_we", prefs.getBoolean("zetime_prefs_inactivity_repetitions_we", false));
+                            editor.putBoolean("inactivity_warnings_th", prefs.getBoolean("zetime_prefs_inactivity_repetitions_th", false));
+                            editor.putBoolean("inactivity_warnings_fr", prefs.getBoolean("zetime_prefs_inactivity_repetitions_fr", false));
+                            editor.putBoolean("inactivity_warnings_sa", prefs.getBoolean("zetime_prefs_inactivity_repetitions_sa", false));
+                            editor.putBoolean("inactivity_warnings_su", prefs.getBoolean("zetime_prefs_inactivity_repetitions_su", false));
+                            break;
+                    }
+                    deviceSharedPrefsEdit.apply();
+                }
+                editor.putInt("fitness_goal", prefs.getInt("mi_fitness_goal", 8000));
+
+                editor.remove("zetime_do_not_disturb");
+                editor.remove("zetime_do_not_disturb_start");
+                editor.remove("zetime_do_not_disturb_end");
+                editor.remove("zetime_inactivity_warnings");
+                editor.remove("zetime_inactivity_warnings_start");
+                editor.remove("zetime_inactivity_warnings_end");
+                editor.remove("zetime_inactivity_warnings_threshold");
+                editor.remove("zetime_prefs_inactivity_repetitions_mo");
+                editor.remove("zetime_prefs_inactivity_repetitions_tu");
+                editor.remove("zetime_prefs_inactivity_repetitions_we");
+                editor.remove("zetime_prefs_inactivity_repetitions_th");
+                editor.remove("zetime_prefs_inactivity_repetitions_fr");
+                editor.remove("zetime_prefs_inactivity_repetitions_sa");
+                editor.remove("zetime_prefs_inactivity_repetitions_su");
+                editor.remove("mi2_inactivity_warnings_dnd");
+                editor.remove("mi2_inactivity_warnings_dnd_start");
+                editor.remove("mi2_inactivity_warnings_dnd_end");
+                editor.remove("mi2_inactivity_warnings");
+                editor.remove("mi2_inactivity_warnings_threshold");
+                editor.remove("mi2_inactivity_warnings_start");
+                editor.remove("mi2_inactivity_warnings_end");
+                editor.remove("mi_fitness_goal");
+            } catch (Exception e) {
+                Log.e(TAG, "Failed to migrate prefs to version 11", e);
+            }
+        }
+        if (oldVersion < 12) {
+            // Convert preferences that were wrongly migrated to int, since Android saves them as Strings internally
+            editor.putString("inactivity_warnings_threshold", String.valueOf(prefs.getInt("inactivity_warnings_threshold", 60)));
+            editor.putString("fitness_goal", String.valueOf(prefs.getInt("fitness_goal", 8000)));
+        }
+
+        if (oldVersion < 13) {
+            try (DBHandler db = acquireDB()) {
+                final DaoSession daoSession = db.getDaoSession();
+                final List<Device> activeDevices = DBHelper.getActiveDevices(daoSession);
+
+                for (Device dbDevice : activeDevices) {
+                    final SharedPreferences deviceSharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(dbDevice.getIdentifier());
+                    final SharedPreferences.Editor deviceSharedPrefsEdit = deviceSharedPrefs.edit();
+
+                    if (dbDevice.getManufacturer().equals("Huami")) {
+                        deviceSharedPrefsEdit.putBoolean("inactivity_warnings_enable", prefs.getBoolean("inactivity_warnings_enable", false));
+                        deviceSharedPrefsEdit.putString("inactivity_warnings_threshold", prefs.getString("inactivity_warnings_threshold", "60"));
+                        deviceSharedPrefsEdit.putString("inactivity_warnings_start", prefs.getString("inactivity_warnings_start", "06:00"));
+                        deviceSharedPrefsEdit.putString("inactivity_warnings_end", prefs.getString("inactivity_warnings_end", "22:00"));
+
+                        deviceSharedPrefsEdit.putBoolean("inactivity_warnings_dnd", prefs.getBoolean("inactivity_warnings_dnd", false));
+                        deviceSharedPrefsEdit.putString("inactivity_warnings_dnd_start", prefs.getString("inactivity_warnings_dnd_start", "12:00"));
+                        deviceSharedPrefsEdit.putString("inactivity_warnings_dnd_end", prefs.getString("inactivity_warnings_dnd_end", "14:00"));
+
+                        deviceSharedPrefsEdit.putBoolean("fitness_goal_notification", prefs.getBoolean("mi2_goal_notification", false));
+                    }
+
+                    // Not removing the first 4 preferences since they're still used by some devices (ZeTime)
+                    editor.remove("inactivity_warnings_dnd");
+                    editor.remove("inactivity_warnings_dnd_start");
+                    editor.remove("inactivity_warnings_dnd_end");
+                    editor.remove("mi2_goal_notification");
+
+                    deviceSharedPrefsEdit.apply();
+                }
+            } catch (Exception e) {
+                Log.e(TAG, "Failed to migrate prefs to version 13", e);
+            }
+        }
+
+        if (oldVersion < 14) {
+            try (DBHandler db = acquireDB()) {
+                final DaoSession daoSession = db.getDaoSession();
+                final List<Device> activeDevices = DBHelper.getActiveDevices(daoSession);
+
+                for (Device dbDevice : activeDevices) {
+                    final SharedPreferences deviceSharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(dbDevice.getIdentifier());
+                    final SharedPreferences.Editor deviceSharedPrefsEdit = deviceSharedPrefs.edit();
+
+                    if (DeviceType.MIBAND.equals(dbDevice.getType()) || dbDevice.getManufacturer().equals("Huami")) {
+                        deviceSharedPrefsEdit.putBoolean("heartrate_sleep_detection", prefs.getBoolean("mi_hr_sleep_detection", false));
+                        deviceSharedPrefsEdit.putString("heartrate_measurement_interval", prefs.getString("heartrate_measurement_interval", "0"));
+                    }
+
+                    // Not removing heartrate_measurement_interval since it's still used by some devices (ZeTime)
+                    editor.remove("mi_hr_sleep_detection");
+
+                    deviceSharedPrefsEdit.apply();
+                }
+            } catch (Exception e) {
+                Log.e(TAG, "Failed to migrate prefs to version 14", e);
+            }
+        }
+
+        if (oldVersion < 15) {
+            try (DBHandler db = acquireDB()) {
+                final DaoSession daoSession = db.getDaoSession();
+                final List<Device> activeDevices = DBHelper.getActiveDevices(daoSession);
+
+                for (Device dbDevice : activeDevices) {
+                    final SharedPreferences deviceSharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(dbDevice.getIdentifier());
+                    final SharedPreferences.Editor deviceSharedPrefsEdit = deviceSharedPrefs.edit();
+
+                    if (DeviceType.FITPRO.equals(dbDevice.getType())) {
+                        editor.remove("inactivity_warnings_threshold");
+                        deviceSharedPrefsEdit.apply();
+                    }
+                }
+            } catch (Exception e) {
+                Log.e(TAG, "Failed to migrate prefs to version 15", e);
+            }
+        }
+
+        if (oldVersion < 16) {
+            // If transliteration was enabled for a device, migrate it to the per-language setting
+            final String defaultLanguagesIfEnabled = "extended_ascii,common_symbols,scandinavian,german,russian,hebrew,greek,ukranian,arabic,persian,latvian,lithuanian,polish,estonian,icelandic,czech,turkish,bengali,korean,hungarian";
+            try (DBHandler db = acquireDB()) {
+                final DaoSession daoSession = db.getDaoSession();
+                final List<Device> activeDevices = DBHelper.getActiveDevices(daoSession);
+
+                for (Device dbDevice : activeDevices) {
+                    final SharedPreferences deviceSharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(dbDevice.getIdentifier());
+                    final SharedPreferences.Editor deviceSharedPrefsEdit = deviceSharedPrefs.edit();
+
+                    if (deviceSharedPrefs.getBoolean("pref_transliteration_enabled", false)) {
+                        deviceSharedPrefsEdit.putString("pref_transliteration_languages", defaultLanguagesIfEnabled);
+                    }
+
+                    deviceSharedPrefsEdit.remove("pref_transliteration_enabled");
+
+                    deviceSharedPrefsEdit.apply();
+                }
+            } catch (Exception e) {
+                Log.e(TAG, "Failed to migrate prefs to version 16", e);
+            }
+        }
+
+        if (oldVersion < 17) {
+            final HashSet<String> calendarBlacklist = (HashSet<String>) prefs.getStringSet(GBPrefs.CALENDAR_BLACKLIST, null);
+
+            try (DBHandler db = acquireDB()) {
+                final DaoSession daoSession = db.getDaoSession();
+                final List<Device> activeDevices = DBHelper.getActiveDevices(daoSession);
+
+                for (Device dbDevice : activeDevices) {
+                    final SharedPreferences deviceSharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(dbDevice.getIdentifier());
+                    final SharedPreferences.Editor deviceSharedPrefsEdit = deviceSharedPrefs.edit();
+
+                    deviceSharedPrefsEdit.putBoolean("sync_calendar", prefs.getBoolean("enable_calendar_sync", true));
+
+                    if (calendarBlacklist != null) {
+                        Prefs.putStringSet(deviceSharedPrefsEdit, GBPrefs.CALENDAR_BLACKLIST, calendarBlacklist);
+                    }
+
+                    deviceSharedPrefsEdit.apply();
+                }
+            } catch (Exception e) {
+                Log.e(TAG, "Failed to migrate prefs to version 17", e);
+            }
+
+            editor.remove(GBPrefs.CALENDAR_BLACKLIST);
+        }
+
+        if (oldVersion < 18) {
+            // Migrate the default value for Huami find band vibration pattern
+            try (DBHandler db = acquireDB()) {
+                final DaoSession daoSession = db.getDaoSession();
+                final List<Device> activeDevices = DBHelper.getActiveDevices(daoSession);
+
+                for (Device dbDevice : activeDevices) {
+                    if (!dbDevice.getManufacturer().equals("Huami")) {
+                        continue;
+                    }
+
+                    final SharedPreferences deviceSharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(dbDevice.getIdentifier());
+                    final SharedPreferences.Editor deviceSharedPrefsEdit = deviceSharedPrefs.edit();
+
+                    deviceSharedPrefsEdit.putString("huami_vibration_profile_find_band", "long");
+                    deviceSharedPrefsEdit.putString("huami_vibration_count_find_band", "1");
+
+                    deviceSharedPrefsEdit.apply();
+                }
+            } catch (Exception e) {
+                Log.e(TAG, "Failed to migrate prefs to version 18", e);
+            }
+        }
+
+        if (oldVersion < 19) {
+            //remove old ble scanning prefences, now unsupported
+            editor.remove("disable_new_ble_scanning");
+        }
+
+        if (oldVersion < 20) {
+            // Add the new stress tab to all devices
+            try (DBHandler db = acquireDB()) {
+                final DaoSession daoSession = db.getDaoSession();
+                final List<Device> activeDevices = DBHelper.getActiveDevices(daoSession);
+
+                for (final Device dbDevice : activeDevices) {
+                    final SharedPreferences deviceSharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(dbDevice.getIdentifier());
+
+                    final String chartsTabsValue = deviceSharedPrefs.getString("charts_tabs", null);
+                    if (chartsTabsValue == null) {
+                        continue;
+                    }
+
+                    final String newPrefValue;
+                    if (!StringUtils.isBlank(chartsTabsValue)) {
+                        newPrefValue = chartsTabsValue + ",stress";
+                    } else {
+                        newPrefValue = "stress";
+                    }
+
+                    final SharedPreferences.Editor deviceSharedPrefsEdit = deviceSharedPrefs.edit();
+                    deviceSharedPrefsEdit.putString("charts_tabs", newPrefValue);
+                    deviceSharedPrefsEdit.apply();
+                }
+            } catch (Exception e) {
+                Log.e(TAG, "Failed to migrate prefs to version 20", e);
+            }
+        }
+
+        if (oldVersion < 21) {
+            // Add the new PAI tab to all devices
+            try (DBHandler db = acquireDB()) {
+                final DaoSession daoSession = db.getDaoSession();
+                final List<Device> activeDevices = DBHelper.getActiveDevices(daoSession);
+
+                for (final Device dbDevice : activeDevices) {
+                    final SharedPreferences deviceSharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(dbDevice.getIdentifier());
+
+                    final String chartsTabsValue = deviceSharedPrefs.getString("charts_tabs", null);
+                    if (chartsTabsValue == null) {
+                        continue;
+                    }
+
+                    final String newPrefValue;
+                    if (!StringUtils.isBlank(chartsTabsValue)) {
+                        newPrefValue = chartsTabsValue + ",pai";
+                    } else {
+                        newPrefValue = "pai";
+                    }
+
+                    final SharedPreferences.Editor deviceSharedPrefsEdit = deviceSharedPrefs.edit();
+                    deviceSharedPrefsEdit.putString("charts_tabs", newPrefValue);
+                    deviceSharedPrefsEdit.apply();
+                }
+            } catch (Exception e) {
+                Log.e(TAG, "Failed to migrate prefs to version 21", e);
+            }
+        }
+
+        if (oldVersion < 22) {
+            try (DBHandler db = acquireDB()) {
+                final DaoSession daoSession = db.getDaoSession();
+                final List<Device> activeDevices = DBHelper.getActiveDevices(daoSession);
+
+                for (Device dbDevice : activeDevices) {
+                    final DeviceType deviceType = DeviceType.fromName(dbDevice.getTypeName());
+                    if (deviceType == MIBAND2) {
+                        final String name = dbDevice.getName();
+                        if ("Mi Band HRX".equalsIgnoreCase(name) || "Mi Band 2i".equalsIgnoreCase(name)) {
+                            dbDevice.setTypeName(DeviceType.MIBAND2_HRX.name());
+                            daoSession.getDeviceDao().update(dbDevice);
+                        }
+                    }
+                }
+            } catch (Exception e) {
+                Log.e(TAG, "Failed to migrate prefs to version 22", e);
+            }
+        }
+
+        if (oldVersion < 26) {
+            try (DBHandler db = acquireDB()) {
+                final DaoSession daoSession = db.getDaoSession();
+                final List<Device> activeDevices = DBHelper.getActiveDevices(daoSession);
+
+                for (final Device dbDevice : activeDevices) {
+                    final SharedPreferences deviceSharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(dbDevice.getIdentifier());
+
+                    final String chartsTabsValue = deviceSharedPrefs.getString("charts_tabs", null);
+                    if (chartsTabsValue == null) {
+                        continue;
+                    }
+
+                    final String newPrefValue;
+                    if (!StringUtils.isBlank(chartsTabsValue)) {
+                        newPrefValue = chartsTabsValue + ",spo2";
+                    } else {
+                        newPrefValue = "spo2";
+                    }
+
+                    final SharedPreferences.Editor deviceSharedPrefsEdit = deviceSharedPrefs.edit();
+                    deviceSharedPrefsEdit.putString("charts_tabs", newPrefValue);
+                    deviceSharedPrefsEdit.apply();
+                }
+            } catch (Exception e) {
+                Log.e(TAG, "Failed to migrate prefs to version 26", e);
+            }
+        }
+
+        if (oldVersion < 27) {
+            try (DBHandler db = acquireDB()) {
+                final DaoSession daoSession = db.getDaoSession();
+                final List<Device> activeDevices = DBHelper.getActiveDevices(daoSession);
+
+                for (final Device dbDevice : activeDevices) {
+                    final SharedPreferences deviceSharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(dbDevice.getIdentifier());
+                    final SharedPreferences.Editor deviceSharedPrefsEdit = deviceSharedPrefs.edit();
+
+                    for (final Map.Entry<String, ?> entry : deviceSharedPrefs.getAll().entrySet()) {
+                        final String key = entry.getKey();
+                        if (key.startsWith("huami_2021_known_config_")) {
+                            deviceSharedPrefsEdit.putString(
+                                    key.replace("huami_2021_known_config_", "") + "_is_known",
+                                    entry.getValue().toString()
+                            );
+                        } else if (key.endsWith("_huami_2021_possible_values")) {
+                            deviceSharedPrefsEdit.putString(
+                                    key.replace("_huami_2021_possible_values", "") + "_possible_values",
+                                    entry.getValue().toString()
+                            );
+                        }
+                    }
+
+                    deviceSharedPrefsEdit.apply();
+                }
+            } catch (Exception e) {
+                Log.e(TAG, "Failed to migrate prefs to version 27", e);
+            }
+        }
+
+        if (oldVersion < 28) {
+            try (DBHandler db = acquireDB()) {
+                final DaoSession daoSession = db.getDaoSession();
+                final List<Device> activeDevices = DBHelper.getActiveDevices(daoSession);
+
+                for (final Device dbDevice : activeDevices) {
+                    final SharedPreferences deviceSharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(dbDevice.getIdentifier());
+                    final SharedPreferences.Editor deviceSharedPrefsEdit = deviceSharedPrefs.edit();
+                    boolean shouldApply = false;
+
+                    if (!"UNKNOWN".equals(deviceSharedPrefs.getString("events_forwarding_fellsleep_action_selection", "UNKNOWN"))) {
+                        shouldApply = true;
+                        deviceSharedPrefsEdit.putStringSet(
+                                "events_forwarding_fellsleep_action_selections",
+                                Collections.singleton(deviceSharedPrefs.getString("events_forwarding_fellsleep_action_selection", "UNKNOWN"))
+                        );
+                    }
+                    if (!"UNKNOWN".equals(deviceSharedPrefs.getString("events_forwarding_wokeup_action_selection", "UNKNOWN"))) {
+                        shouldApply = true;
+                        deviceSharedPrefsEdit.putStringSet(
+                                "events_forwarding_wokeup_action_selections",
+                                Collections.singleton(deviceSharedPrefs.getString("events_forwarding_wokeup_action_selection", "UNKNOWN"))
+                        );
+                    }
+                    if (!"UNKNOWN".equals(deviceSharedPrefs.getString("events_forwarding_startnonwear_action_selection", "UNKNOWN"))) {
+                        shouldApply = true;
+                        deviceSharedPrefsEdit.putStringSet(
+                                "events_forwarding_startnonwear_action_selections",
+                                Collections.singleton(deviceSharedPrefs.getString("events_forwarding_startnonwear_action_selection", "UNKNOWN"))
+                        );
+                    }
+
+                    if (shouldApply) {
+                        deviceSharedPrefsEdit.apply();
+                    }
+                }
+            } catch (Exception e) {
+                Log.e(TAG, "Failed to migrate prefs to version 28", e);
+            }
+        }
+
+        if (oldVersion < 29) {
+            // Migrate HPlus preferences to device-specific
+            try (DBHandler db = acquireDB()) {
+                final DaoSession daoSession = db.getDaoSession();
+                final List<Device> activeDevices = DBHelper.getActiveDevices(daoSession);
+
+                for (Device dbDevice : activeDevices) {
+                    final DeviceType deviceType = DeviceType.fromName(dbDevice.getTypeName());
+                    if (deviceType == DeviceType.HPLUS) {
+                        final SharedPreferences deviceSharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(dbDevice.getIdentifier());
+                        final SharedPreferences.Editor deviceSharedPrefsEdit = deviceSharedPrefs.edit();
+
+                        deviceSharedPrefsEdit.putString("hplus_screentime", sharedPrefs.getString("hplus_screentime", "5"));
+                        deviceSharedPrefsEdit.putBoolean("hplus_alldayhr", sharedPrefs.getBoolean("hplus_alldayhr", true));
+                        deviceSharedPrefsEdit.apply();
+                    }
+                }
+            } catch (Exception e) {
+                Log.e(TAG, "Failed to migrate prefs to version 29", e);
+            }
+        }
+
+        if (oldVersion < 30) {
+            // Migrate QHybrid preferences to device-specific
+            try (DBHandler db = acquireDB()) {
+                final DaoSession daoSession = db.getDaoSession();
+                final List<Device> activeDevices = DBHelper.getActiveDevices(daoSession);
+
+                for (Device dbDevice : activeDevices) {
+                    final DeviceType deviceType = DeviceType.fromName(dbDevice.getTypeName());
+                    if (deviceType == DeviceType.FOSSILQHYBRID) {
+                        final SharedPreferences deviceSharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(dbDevice.getIdentifier());
+                        final SharedPreferences.Editor deviceSharedPrefsEdit = deviceSharedPrefs.edit();
+
+                        deviceSharedPrefsEdit.putInt("QHYBRID_TIME_OFFSET", sharedPrefs.getInt("QHYBRID_TIME_OFFSET", 0));
+                        deviceSharedPrefsEdit.putInt("QHYBRID_TIMEZONE_OFFSET", sharedPrefs.getInt("QHYBRID_TIMEZONE_OFFSET", 0));
+                        deviceSharedPrefsEdit.apply();
+                    }
+                }
+            } catch (Exception e) {
+                Log.e(TAG, "Failed to migrate prefs to version 30", e);
+            }
+        }
+
+        if (oldVersion < 31) {
+            // Add the new HRV Status tab to all devices
+            try (DBHandler db = acquireDB()) {
+                final DaoSession daoSession = db.getDaoSession();
+                final List<Device> activeDevices = DBHelper.getActiveDevices(daoSession);
+
+                for (final Device dbDevice : activeDevices) {
+                    final SharedPreferences deviceSharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(dbDevice.getIdentifier());
+
+                    final String chartsTabsValue = deviceSharedPrefs.getString("charts_tabs", null);
+                    if (chartsTabsValue == null) {
+                        continue;
+                    }
+
+                    final String newPrefValue;
+                    if (!StringUtils.isBlank(chartsTabsValue)) {
+                        newPrefValue = chartsTabsValue + ",hrvstatus";
+                    } else {
+                        newPrefValue = "hrvstatus";
+                    }
+
+                    final SharedPreferences.Editor deviceSharedPrefsEdit = deviceSharedPrefs.edit();
+                    deviceSharedPrefsEdit.putString("charts_tabs", newPrefValue);
+                    deviceSharedPrefsEdit.apply();
+                }
+            } catch (Exception e) {
+                Log.e(TAG, "Failed to migrate prefs to version 31", e);
+            }
+        }
+
+        if (oldVersion < 32) {
+            // Add the new body energy tab to all devices
+            try (DBHandler db = acquireDB()) {
+                final DaoSession daoSession = db.getDaoSession();
+                final List<Device> activeDevices = DBHelper.getActiveDevices(daoSession);
+
+                for (final Device dbDevice : activeDevices) {
+                    final SharedPreferences deviceSharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(dbDevice.getIdentifier());
+
+                    final String chartsTabsValue = deviceSharedPrefs.getString("charts_tabs", null);
+                    if (chartsTabsValue == null) {
+                        continue;
+                    }
+
+                    final String newPrefValue;
+                    if (!StringUtils.isBlank(chartsTabsValue)) {
+                        newPrefValue = chartsTabsValue + ",bodyenergy";
+                    } else {
+                        newPrefValue = "bodyenergy";
+                    }
+
+                    final SharedPreferences.Editor deviceSharedPrefsEdit = deviceSharedPrefs.edit();
+                    deviceSharedPrefsEdit.putString("charts_tabs", newPrefValue);
+                    deviceSharedPrefsEdit.apply();
+                }
+            } catch (Exception e) {
+                Log.e(TAG, "Failed to migrate prefs to version 32", e);
+            }
+        }
+
+        if (oldVersion < 33) {
+            // Remove sleep week tab from all devices, since it does not exist anymore
+            try (DBHandler db = acquireDB()) {
+                final DaoSession daoSession = db.getDaoSession();
+                final List<Device> activeDevices = DBHelper.getActiveDevices(daoSession);
+
+                for (final Device dbDevice : activeDevices) {
+                    final SharedPreferences deviceSharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(dbDevice.getIdentifier());
+
+                    final String chartsTabsValue = deviceSharedPrefs.getString("charts_tabs", null);
+                    if (chartsTabsValue == null) {
+                        continue;
+                    }
+
+                    final String newPrefValue;
+                    if (!StringUtils.isBlank(chartsTabsValue)) {
+                        newPrefValue = chartsTabsValue.replace(",sleepweek", "");
+                    } else {
+                        newPrefValue = chartsTabsValue;
+                    }
+
+                    final SharedPreferences.Editor deviceSharedPrefsEdit = deviceSharedPrefs.edit();
+                    deviceSharedPrefsEdit.putString("charts_tabs", newPrefValue);
+                    deviceSharedPrefsEdit.apply();
+                }
+            } catch (Exception e) {
+                Log.e(TAG, "Failed to migrate prefs to version 33", e);
+            }
+        }
+
+        if (oldVersion < 34) {
+            // Migrate Mi Band preferences to device-specific
+            try (DBHandler db = acquireDB()) {
+                final DaoSession daoSession = db.getDaoSession();
+                final List<Device> activeDevices = DBHelper.getActiveDevices(daoSession);
+
+                for (Device dbDevice : activeDevices) {
+                    final DeviceType deviceType = DeviceType.fromName(dbDevice.getTypeName());
+                    if (deviceType == MIBAND || deviceType == MIBAND2 || deviceType == MIBAND2_HRX) {
+                        final SharedPreferences deviceSharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(dbDevice.getIdentifier());
+                        final SharedPreferences.Editor deviceSharedPrefsEdit = deviceSharedPrefs.edit();
+
+                        deviceSharedPrefsEdit.putString("mi_vibration_profile_generic_sms", sharedPrefs.getString("mi_vibration_profile_generic_sms", "staccato"));
+                        deviceSharedPrefsEdit.putString("mi_vibration_count_generic_sms", sharedPrefs.getString("mi_vibration_count_generic_sms", "3"));
+                        deviceSharedPrefsEdit.putString("mi_vibration_profile_incoming_call", sharedPrefs.getString("mi_vibration_profile_incoming_call", "ring"));
+                        deviceSharedPrefsEdit.putString("mi_vibration_count_incoming_call", sharedPrefs.getString("mi_vibration_count_incoming_call", "60"));
+                        deviceSharedPrefsEdit.putString("mi_vibration_profile_generic_email", sharedPrefs.getString("mi_vibration_profile_generic_email", "medium"));
+                        deviceSharedPrefsEdit.putString("mi_vibration_count_generic_email", sharedPrefs.getString("mi_vibration_count_generic_email", "2"));
+                        deviceSharedPrefsEdit.putString("mi_vibration_profile_generic_chat", sharedPrefs.getString("mi_vibration_profile_generic_chat", "waterdrop"));
+                        deviceSharedPrefsEdit.putString("mi_vibration_count_generic_chat", sharedPrefs.getString("mi_vibration_count_generic_chat", "3"));
+                        deviceSharedPrefsEdit.putString("mi_vibration_profile_generic_social", sharedPrefs.getString("mi_vibration_profile_generic_social", "waterdrop"));
+                        deviceSharedPrefsEdit.putString("mi_vibration_count_generic_social", sharedPrefs.getString("mi_vibration_count_generic_social", "3"));
+                        deviceSharedPrefsEdit.putString("mi_vibration_profile_alarm_clock", sharedPrefs.getString("mi_vibration_profile_alarm_clock", "alarm_clock"));
+                        deviceSharedPrefsEdit.putString("mi_vibration_count_alarm_clock", sharedPrefs.getString("mi_vibration_count_alarm_clock", "3"));
+                        deviceSharedPrefsEdit.putString("mi_vibration_profile_generic_navigation", sharedPrefs.getString("mi_vibration_profile_generic_navigation", "waterdrop"));
+                        deviceSharedPrefsEdit.putString("mi_vibration_count_generic_navigation", sharedPrefs.getString("mi_vibration_count_generic_navigation", "3"));
+                        deviceSharedPrefsEdit.putString("mi_vibration_profile_generic", sharedPrefs.getString("mi_vibration_profile_generic", "waterdrop"));
+                        deviceSharedPrefsEdit.putString("mi_vibration_count_generic", sharedPrefs.getString("mi_vibration_count_generic", "3"));
+
+                        if (deviceType == MIBAND) {
+                            deviceSharedPrefsEdit.putBoolean("keep_activity_data_on_device", sharedPrefs.getBoolean("mi_dont_ack_transfer", false));
+                        }
+
+                        deviceSharedPrefsEdit.apply();
+                    }
+                }
+            } catch (Exception e) {
+                Log.e(TAG, "Failed to migrate prefs to version 34", e);
+            }
+        }
+
+        if (oldVersion < 35) {
+            // Migrate ZeTime preferences to device-specific
+            try (DBHandler db = acquireDB()) {
+                final DaoSession daoSession = db.getDaoSession();
+                final List<Device> activeDevices = DBHelper.getActiveDevices(daoSession);
+
+                for (Device dbDevice : activeDevices) {
+                    final DeviceType deviceType = DeviceType.fromName(dbDevice.getTypeName());
+                    if (deviceType == DeviceType.ZETIME) {
+                        final SharedPreferences deviceSharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(dbDevice.getIdentifier());
+                        final SharedPreferences.Editor deviceSharedPrefsEdit = deviceSharedPrefs.edit();
+
+                        // Vibration Profiles
+                        deviceSharedPrefsEdit.putString("zetime_vibration_profile_sms", sharedPrefs.getString("zetime_vibration_profile_sms", "2"));
+                        deviceSharedPrefsEdit.putString("zetime_vibration_profile_incoming_call", sharedPrefs.getString("zetime_vibration_profile_incoming_call", "13"));
+                        deviceSharedPrefsEdit.putString("zetime_vibration_profile_missed_call", sharedPrefs.getString("zetime_vibration_profile_missed_call", "12"));
+                        deviceSharedPrefsEdit.putString("zetime_vibration_profile_generic_email", sharedPrefs.getString("zetime_vibration_profile_generic_email", "12"));
+                        deviceSharedPrefsEdit.putString("zetime_vibration_profile_generic_social", sharedPrefs.getString("zetime_vibration_profile_generic_social", "12"));
+                        deviceSharedPrefsEdit.putString("zetime_alarm_signaling", sharedPrefs.getString("zetime_alarm_signaling", "11"));
+                        deviceSharedPrefsEdit.putString("zetime_vibration_profile_calendar", sharedPrefs.getString("zetime_vibration_profile_calendar", "12"));
+                        deviceSharedPrefsEdit.putString("zetime_vibration_profile_inactivity", sharedPrefs.getString("zetime_vibration_profile_inactivity", "12"));
+                        deviceSharedPrefsEdit.putString("zetime_vibration_profile_lowpower", sharedPrefs.getString("zetime_vibration_profile_lowpower", "4"));
+                        deviceSharedPrefsEdit.putString("zetime_vibration_profile_antiloss", sharedPrefs.getString("zetime_vibration_profile_antiloss", "13"));
+                        // DND
+                        deviceSharedPrefsEdit.putString("do_not_disturb_no_auto", sharedPrefs.getString("do_not_disturb", "off"));
+                        deviceSharedPrefsEdit.putString("do_not_disturb_no_auto_start", sharedPrefs.getString("do_not_disturb_start", "22:00"));
+                        deviceSharedPrefsEdit.putString("do_not_disturb_no_auto_end", sharedPrefs.getString("do_not_disturb_end", "07:00"));
+                        // HR
+                        deviceSharedPrefsEdit.putString("heartrate_measurement_interval", sharedPrefs.getString("heartrate_measurement_interval", "0"));
+                        deviceSharedPrefsEdit.putBoolean("zetime_heartrate_alarm_enable", sharedPrefs.getBoolean("zetime_heartrate_alarm_enable", false));
+                        deviceSharedPrefsEdit.putString("alarm_max_heart_rate", sharedPrefs.getString("alarm_max_heart_rate", "180"));
+                        deviceSharedPrefsEdit.putString("alarm_min_heart_rate", sharedPrefs.getString("alarm_min_heart_rate", "60"));
+                        // Inactivity warnings
+                        deviceSharedPrefsEdit.putBoolean("inactivity_warnings_enable", sharedPrefs.getBoolean("inactivity_warnings_enable", false));
+                        deviceSharedPrefsEdit.putString("inactivity_warnings_threshold", sharedPrefs.getString("inactivity_warnings_threshold", "60"));
+                        deviceSharedPrefsEdit.putString("inactivity_warnings_start", sharedPrefs.getString("inactivity_warnings_start", "06:00"));
+                        deviceSharedPrefsEdit.putString("inactivity_warnings_end", sharedPrefs.getString("inactivity_warnings_end", "22:00"));
+                        deviceSharedPrefsEdit.putBoolean("inactivity_warnings_mo", sharedPrefs.getBoolean("inactivity_warnings_mo", false));
+                        deviceSharedPrefsEdit.putBoolean("inactivity_warnings_tu", sharedPrefs.getBoolean("inactivity_warnings_tu", false));
+                        deviceSharedPrefsEdit.putBoolean("inactivity_warnings_we", sharedPrefs.getBoolean("inactivity_warnings_we", false));
+                        deviceSharedPrefsEdit.putBoolean("inactivity_warnings_th", sharedPrefs.getBoolean("inactivity_warnings_th", false));
+                        deviceSharedPrefsEdit.putBoolean("inactivity_warnings_fr", sharedPrefs.getBoolean("inactivity_warnings_fr", false));
+                        deviceSharedPrefsEdit.putBoolean("inactivity_warnings_sa", sharedPrefs.getBoolean("inactivity_warnings_sa", false));
+                        deviceSharedPrefsEdit.putBoolean("inactivity_warnings_su", sharedPrefs.getBoolean("inactivity_warnings_su", false));
+                        // Developer settings
+                        deviceSharedPrefsEdit.putBoolean("keep_activity_data_on_device", sharedPrefs.getBoolean("zetime_dont_del_actdata", false));
+                        // Activity info
+                        deviceSharedPrefsEdit.putBoolean("zetime_activity_tracking", sharedPrefs.getBoolean("zetime_activity_tracking", false));
+                        deviceSharedPrefsEdit.putString("zetime_calories_type", sharedPrefs.getString("zetime_calories_type", "0"));
+                        // Display
+                        deviceSharedPrefsEdit.putString("zetime_screentime", sharedPrefs.getString("zetime_screentime", "30"));
+                        deviceSharedPrefsEdit.putBoolean("zetime_handmove_display", sharedPrefs.getBoolean("zetime_handmove_display", false));
+                        deviceSharedPrefsEdit.putString("zetime_analog_mode", sharedPrefs.getString("zetime_analog_mode", "0"));
+                        // Date format
+                        deviceSharedPrefsEdit.putString("zetime_date_format", sharedPrefs.getString("zetime_date_format", "2"));
+                        // Unused, but migrate it anyway
+                        deviceSharedPrefsEdit.putString("zetime_shock_strength", sharedPrefs.getString("zetime_shock_strength", "255"));
+
+                        deviceSharedPrefsEdit.apply();
+                    }
+                }
+            } catch (Exception e) {
+                Log.e(TAG, "Failed to migrate prefs to version 35", e);
+            }
+        }
+        if (oldVersion < 36) {
+            // Migrate Pebble preferences to device-specific
+            try (DBHandler db = acquireDB()) {
+                final DaoSession daoSession = db.getDaoSession();
+                final List<Device> activeDevices = DBHelper.getActiveDevices(daoSession);
+
+                for (Device dbDevice : activeDevices) {
+                    final DeviceType deviceType = DeviceType.fromName(dbDevice.getTypeName());
+                    if (deviceType == PEBBLE) {
+                        final SharedPreferences deviceSharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(dbDevice.getIdentifier());
+                        final SharedPreferences.Editor deviceSharedPrefsEdit = deviceSharedPrefs.edit();
+
+                        deviceSharedPrefsEdit.putBoolean("pebble_enable_outgoing_call", sharedPrefs.getBoolean("pebble_enable_outgoing_call", true));
+                        deviceSharedPrefsEdit.putString("pebble_pref_privacy_mode", sharedPrefs.getString("pebble_pref_privacy_mode", getString(R.string.p_pebble_privacy_mode_off)));
+                        deviceSharedPrefsEdit.putBoolean("send_sunrise_sunset", sharedPrefs.getBoolean("send_sunrise_sunset", false));
+                        deviceSharedPrefsEdit.putString("pebble_activitytracker", sharedPrefs.getString("pebble_activitytracker", String.valueOf(SampleProvider.PROVIDER_PEBBLE_HEALTH)));
+                        deviceSharedPrefsEdit.putBoolean("pebble_sync_health", sharedPrefs.getBoolean("pebble_sync_health", true));
+                        deviceSharedPrefsEdit.putBoolean("pebble_health_store_raw", sharedPrefs.getBoolean("pebble_health_store_raw", true));
+                        deviceSharedPrefsEdit.putBoolean("pebble_sync_misfit", sharedPrefs.getBoolean("pebble_sync_misfit", true));
+                        deviceSharedPrefsEdit.putBoolean("pebble_sync_morpheuz", sharedPrefs.getBoolean("pebble_sync_morpheuz", true));
+                        deviceSharedPrefsEdit.putBoolean("pebble_force_protocol", sharedPrefs.getBoolean("pebble_force_protocol", false));
+                        deviceSharedPrefsEdit.putBoolean("pebble_force_untested", sharedPrefs.getBoolean("pebble_force_untested", false));
+                        deviceSharedPrefsEdit.putBoolean("pebble_force_le", sharedPrefs.getBoolean("pebble_force_le", false));
+                        deviceSharedPrefsEdit.putString("pebble_mtu_limit", sharedPrefs.getString("pebble_mtu_limit", "512"));
+                        deviceSharedPrefsEdit.putBoolean("pebble_gatt_clientonly", sharedPrefs.getBoolean("pebble_gatt_clientonly", false));
+                        deviceSharedPrefsEdit.putBoolean("pebble_enable_applogs", sharedPrefs.getBoolean("pebble_enable_applogs", false));
+                        deviceSharedPrefsEdit.putBoolean("third_party_apps_set_settings", sharedPrefs.getBoolean("pebble_enable_pebblekit", false));
+                        deviceSharedPrefsEdit.putBoolean("pebble_always_ack_pebblekit", sharedPrefs.getBoolean("pebble_always_ack_pebblekit", false));
+                        deviceSharedPrefsEdit.putBoolean("pebble_enable_background_javascript", sharedPrefs.getBoolean("pebble_enable_background_javascript", false));
+
+                        deviceSharedPrefsEdit.apply();
+                    }
+                }
+            } catch (Exception e) {
+                Log.e(TAG, "Failed to migrate prefs to version 36", e);
+            }
+        }
+
+        if (oldVersion < 37) {
+            // Add new dashboard widgets
+            final String dashboardWidgetsOrder = sharedPrefs.getString("pref_dashboard_widgets_order", null);
+            if (!StringUtils.isBlank(dashboardWidgetsOrder) && !dashboardWidgetsOrder.contains("bodyenergy")) {
+                editor.putString("pref_dashboard_widgets_order", dashboardWidgetsOrder + ",bodyenergy,stress_segmented,hrv");
+            }
+        }
+
+        if (oldVersion < 38) {
+            // Migrate year of birth to date of birth
+            try {
+                final String yearOfBirth = sharedPrefs.getString("activity_user_year_of_birth", null);
+                if (StringUtils.isNotBlank(yearOfBirth)) {
+                    final int yearOfBirthValue = Integer.parseInt(yearOfBirth);
+                    if (yearOfBirthValue > 1800 && yearOfBirthValue < 3000) {
+                        editor.putString("activity_user_date_of_birth", String.format(Locale.ROOT, "%s-01-01", yearOfBirth.trim()));
+                    } else {
+                        Log.e(TAG, "Year of birth out of range, not migrating - " + yearOfBirth);
+                    }
+                }
+            } catch (final Exception e) {
+                Log.e(TAG, "Failed to migrate year of birth to date of birth in version 38", e);
+            }
+        }
+
+        if (oldVersion < 39) {
+            // Add the new Heart Rate tab to all devices
+            try (DBHandler db = acquireDB()) {
+                final DaoSession daoSession = db.getDaoSession();
+                final List<Device> activeDevices = DBHelper.getActiveDevices(daoSession);
+
+                for (final Device dbDevice : activeDevices) {
+                    final SharedPreferences deviceSharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(dbDevice.getIdentifier());
+
+                    final String chartsTabsValue = deviceSharedPrefs.getString("charts_tabs", null);
+                    if (chartsTabsValue == null) {
+                        continue;
+                    }
+
+                    String newPrefValue = chartsTabsValue;
+                    if (!StringUtils.isBlank(chartsTabsValue)) {
+                        if (!chartsTabsValue.contains("heartrate")) {
+                            newPrefValue = newPrefValue + ",heartrate";
+                        }
+                    } else {
+                        newPrefValue = "heartrate";
+                    }
+
+                    final SharedPreferences.Editor deviceSharedPrefsEdit = deviceSharedPrefs.edit();
+                    deviceSharedPrefsEdit.putString("charts_tabs", newPrefValue);
+                    deviceSharedPrefsEdit.apply();
+                }
+            } catch (Exception e) {
+                Log.e(TAG, "Failed to migrate prefs to version 39", e);
+            }
+        }
+
+        if (oldVersion < 40) {
+            // Add the new VO2Max tab to all devices
+            try (DBHandler db = acquireDB()) {
+                final DaoSession daoSession = db.getDaoSession();
+                final List<Device> activeDevices = DBHelper.getActiveDevices(daoSession);
+
+                for (final Device dbDevice : activeDevices) {
+                    final SharedPreferences deviceSharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(dbDevice.getIdentifier());
+
+                    final String chartsTabsValue = deviceSharedPrefs.getString("charts_tabs", null);
+                    if (chartsTabsValue == null) {
+                        continue;
+                    }
+
+                    final String newPrefValue;
+                    if (!StringUtils.isBlank(chartsTabsValue)) {
+                        newPrefValue = chartsTabsValue + ",vo2max";
+                    } else {
+                        newPrefValue = "vo2max";
+                    }
+
+                    final SharedPreferences.Editor deviceSharedPrefsEdit = deviceSharedPrefs.edit();
+                    deviceSharedPrefsEdit.putString("charts_tabs", newPrefValue);
+                    deviceSharedPrefsEdit.apply();
+                }
+            } catch (Exception e) {
+                Log.e(TAG, "Failed to migrate prefs to version 40", e);
+            }
+        }
+
+        if (oldVersion < 41) {
+            // Add vo2max widget.
+            final String dashboardWidgetsOrder = sharedPrefs.getString("pref_dashboard_widgets_order", null);
+            if (!StringUtils.isBlank(dashboardWidgetsOrder) && !dashboardWidgetsOrder.contains("vo2max")) {
+                editor.putString("pref_dashboard_widgets_order", dashboardWidgetsOrder + ",vo2max");
+            }
+        }
+
+        if (oldVersion < 42) {
+            // Enable crash notification by default on debug builds
+            if (!prefs.contains("crash_notification")) {
+                editor.putBoolean("crash_notification", isDebug());
+            }
+        }
+
+        if (oldVersion < 43) {
+            // Add the new calories tab to all devices.
+            try (DBHandler db = acquireDB()) {
+                final DaoSession daoSession = db.getDaoSession();
+                final List<Device> activeDevices = DBHelper.getActiveDevices(daoSession);
+
+                for (final Device dbDevice : activeDevices) {
+                    final SharedPreferences deviceSharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(dbDevice.getIdentifier());
+
+                    final String chartsTabsValue = deviceSharedPrefs.getString("charts_tabs", null);
+                    if (chartsTabsValue == null) {
+                        continue;
+                    }
+
+                    final String newPrefValue;
+                    if (!StringUtils.isBlank(chartsTabsValue)) {
+                        if (!chartsTabsValue.contains("calories")) {
+                            newPrefValue = chartsTabsValue + ",calories";
+                        } else {
+                            newPrefValue = chartsTabsValue;
+                        }
+                    } else {
+                        newPrefValue = "calories";
+                    }
+
+                    final SharedPreferences.Editor deviceSharedPrefsEdit = deviceSharedPrefs.edit();
+                    deviceSharedPrefsEdit.putString("charts_tabs", newPrefValue);
+                    deviceSharedPrefsEdit.apply();
+                }
+            } catch (Exception e) {
+                Log.e(TAG, "Failed to migrate prefs to version 43", e);
+            }
+        }
+
+        if (oldVersion < 44) {
+            // Users upgrading to this version don't need to see the welcome screen
+            try (DBHandler db = acquireDB()) {
+                final DaoSession daoSession = db.getDaoSession();
+                final List<Device> activeDevices = DBHelper.getActiveDevices(daoSession);
+
+                if (!activeDevices.isEmpty()) {
+                    editor.putBoolean("first_run", false);
+                }
+            } catch (final Exception e) {
+                Log.e(TAG, "Failed to migrate prefs to version 44", e);
+            }
+        }
+
+        if (oldVersion < 45) {
+            // Add the new respiratory rate tab to all devices.
+            try (DBHandler db = acquireDB()) {
+                final DaoSession daoSession = db.getDaoSession();
+                final List<Device> activeDevices = DBHelper.getActiveDevices(daoSession);
+
+                for (final Device dbDevice : activeDevices) {
+                    final SharedPreferences deviceSharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(dbDevice.getIdentifier());
+
+                    final String chartsTabsValue = deviceSharedPrefs.getString("charts_tabs", null);
+                    if (chartsTabsValue == null) {
+                        continue;
+                    }
+
+                    final String newPrefValue;
+                    if (!StringUtils.isBlank(chartsTabsValue)) {
+                        if (!chartsTabsValue.contains("respiratoryrate")) {
+                            newPrefValue = chartsTabsValue + ",respiratoryrate";
+                        } else {
+                            newPrefValue = chartsTabsValue;
+                        }
+                    } else {
+                        newPrefValue = "respiratoryrate";
+                    }
+
+                    final SharedPreferences.Editor deviceSharedPrefsEdit = deviceSharedPrefs.edit();
+                    deviceSharedPrefsEdit.putString("charts_tabs", newPrefValue);
+                    deviceSharedPrefsEdit.apply();
+                }
+            } catch (Exception e) {
+                Log.e(TAG, "Failed to migrate prefs to version 45", e);
+            }
+        }
+
+        editor.putString(PREFS_VERSION, Integer.toString(CURRENT_PREFS_VERSION));
+        editor.apply();
+    }
+
+    public static SharedPreferences getDeviceSpecificSharedPrefs(String deviceIdentifier) {
+        if (deviceIdentifier == null || deviceIdentifier.isEmpty()) {
+            return null;
+        }
+        return context.getSharedPreferences("devicesettings_" + deviceIdentifier, Context.MODE_PRIVATE);
+    }
+
+    public static DevicePrefs getDevicePrefs(GBDevice gbDevice) {
+        return new DevicePrefs(getDeviceSpecificSharedPrefs(gbDevice.getAddress()), gbDevice);
+    }
+
+    public static void deleteDeviceSpecificSharedPrefs(String deviceIdentifier) {
+        if (deviceIdentifier == null || deviceIdentifier.isEmpty()) {
+            return;
+        }
+        context.getSharedPreferences("devicesettings_" + deviceIdentifier, Context.MODE_PRIVATE).edit().clear().apply();
+    }
+
+
+    public static void setLanguage(String lang) {
+        if (lang.equals("default")) {
+            language = Resources.getSystem().getConfiguration().locale;
+        } else if (lang.length() == 2) {
+            language = new Locale(lang);
+        } else {
+            final String[] split = lang.split("_");
+            if (split.length == 2) {
+                language = new Locale(split[0], split[1]);
+            } else {
+                // Unexpected format, fallback to system default
+                language = Resources.getSystem().getConfiguration().locale;
+            }
+        }
+        updateLanguage(language);
+    }
+
+    public static void updateLanguage(Locale locale) {
+        AndroidUtils.setLanguage(context, locale);
+
+        Intent intent = new Intent();
+        intent.setAction(ACTION_LANGUAGE_CHANGE);
+        LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
+    }
+
+    public static LimitedQueue<Integer, String> getIDSenderLookup() {
+        return mIDSenderLookup;
+    }
+
+    public static boolean isDarkThemeEnabled() {
+        String selectedTheme = prefs.getString("pref_key_theme", context.getString(R.string.pref_theme_value_system));
+        Resources resources = context.getResources();
+
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q &&
+                (selectedTheme.equals(context.getString(R.string.pref_theme_value_system)) || selectedTheme.equals(context.getString(R.string.pref_theme_value_dynamic)))) {
+            return (resources.getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES;
+        } else {
+            return selectedTheme.equals(context.getString(R.string.pref_theme_value_dark));
+        }
+    }
+
+    public static boolean isAmoledBlackEnabled() {
+        return prefs.getBoolean("pref_key_theme_amoled_black", false);
+    }
+
+    public static boolean areDynamicColorsEnabled() {
+        String selectedTheme = prefs.getString("pref_key_theme", context.getString(R.string.pref_theme_value_system));
+        return selectedTheme.equals(context.getString(R.string.pref_theme_value_dynamic));
+    }
+
+    public static int getTextColor(Context context) {
+        if (GBApplication.isDarkThemeEnabled()) {
+            return context.getResources().getColor(R.color.primarytext_dark);
+        } else {
+            return context.getResources().getColor(R.color.primarytext_light);
+        }
+    }
+
+    public static int getSecondaryTextColor(Context context) {
+        return context.getResources().getColor(R.color.secondarytext);
+    }
+
+    @Override
+    public void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        updateLanguage(getLanguage());
+    }
+
+    public static int getBackgroundColor(Context context) {
+        TypedValue typedValue = new TypedValue();
+        Resources.Theme theme = context.getTheme();
+        theme.resolveAttribute(android.R.attr.background, typedValue, true);
+        return typedValue.data;
+    }
+
+    public static int getWindowBackgroundColor(Context context) {
+        TypedValue typedValue = new TypedValue();
+        Resources.Theme theme = context.getTheme();
+        theme.resolveAttribute(android.R.attr.windowBackground, typedValue, true);
+        return typedValue.data;
+    }
+
+    public static GBPrefs getPrefs() {
+        return prefs;
+    }
+
+    public DeviceManager getDeviceManager() {
+        return deviceManager;
+    }
+
+    public static GBApplication app() {
+        return app;
+    }
+
+    public static Locale getLanguage() {
+        return language;
+    }
+
+    public static boolean isNightly() {
+        //noinspection ConstantValue - false positive
+        return BuildConfig.APPLICATION_ID.contains("nightly");
+    }
+
+    public static boolean isDebug() {
+        return BuildConfig.DEBUG;
+    }
+
+    public String getVersion() {
+        try {
+            return getPackageManager().getPackageInfo(getPackageName(), PackageManager.GET_META_DATA).versionName;
+        } catch (PackageManager.NameNotFoundException e) {
+            GB.log("Unable to determine Gadgetbridge's version", GB.WARN, e);
+            return "0.0.0";
+        }
+    }
+
+    public String getNameAndVersion() {
+        try {
+            ApplicationInfo appInfo = getPackageManager().getApplicationInfo(getContext().getPackageName(), PackageManager.GET_META_DATA);
+            PackageInfo packageInfo = getPackageManager().getPackageInfo(getPackageName(), PackageManager.GET_META_DATA);
+            return String.format("%s %s", appInfo.name, packageInfo.versionName);
+        } catch (PackageManager.NameNotFoundException e) {
+            GB.log("Unable to determine Gadgetbridge's name/version", GB.WARN, e);
+            return "Gadgetbridge";
+        }
+    }
+
+    public void setOpenTracksObserver(OpenTracksContentObserver openTracksObserver) {
+        this.openTracksObserver = openTracksObserver;
+    }
+
+    public OpenTracksContentObserver getOpenTracksObserver() {
+        return openTracksObserver;
+    }
+
+    public long getLastAutoExportTimestamp() {
+        return lastAutoExportTimestamp;
+    }
+
+    public void setLastAutoExportTimestamp(long lastAutoExportTimestamp) {
+        this.lastAutoExportTimestamp = lastAutoExportTimestamp;
+    }
+
+    public long getAutoExportScheduledTimestamp() {
+        return autoExportScheduledTimestamp;
+    }
+
+    public void setAutoExportScheduledTimestamp(long autoExportScheduledTimestamp) {
+        this.autoExportScheduledTimestamp = autoExportScheduledTimestamp;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBEnvironment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBEnvironment.java
new file mode 100644
index 0000000..721d3c6
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBEnvironment.java
@@ -0,0 +1,59 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, Carsten Pfeiffer
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge;
+
+/**
+ * Some more or less useful utility methods to aid local (non-device) testing.
+ */
+public class GBEnvironment {
+// DO NOT USE A LOGGER HERE. Will break LoggingTest!
+//    private static final Logger LOG = LoggerFactory.getLogger(GBEnvironment.class);
+
+    private static GBEnvironment environment;
+    private boolean localTest;
+    private boolean deviceTest;
+
+    public static GBEnvironment createLocalTestEnvironment() {
+        GBEnvironment env = new GBEnvironment();
+        env.localTest = true;
+        return env;
+    }
+
+    static GBEnvironment createDeviceEnvironment() {
+        return new GBEnvironment();
+    }
+
+    public final boolean isTest() {
+        return localTest || deviceTest;
+    }
+
+    public boolean isLocalTest() {
+        return localTest;
+    }
+
+    public static synchronized GBEnvironment env() {
+        return environment;
+    }
+
+    static synchronized boolean isEnvironmentSetup() {
+        return environment != null;
+    }
+
+    public synchronized static void setupEnvironment(GBEnvironment env) {
+        environment = env;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBException.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBException.java
new file mode 100644
index 0000000..f7b14ce
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBException.java
@@ -0,0 +1,35 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, Carsten Pfeiffer
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge;
+
+public class GBException extends Exception {
+    public GBException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public GBException(String message) {
+        super(message);
+    }
+
+    public GBException(Throwable cause) {
+        super(cause);
+    }
+
+    public GBException() {
+        super();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBExceptionHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBExceptionHandler.java
new file mode 100644
index 0000000..4f74e62
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBExceptionHandler.java
@@ -0,0 +1,103 @@
+/*  Copyright (C) 2015-2024 Carsten Pfeiffer, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge;
+
+
+import static nodomain.freeyourgadget.gadgetbridge.util.GB.NOTIFICATION_CHANNEL_HIGH_PRIORITY_ID;
+import static nodomain.freeyourgadget.gadgetbridge.util.GB.NOTIFICATION_ID_ERROR;
+
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.core.app.NotificationCompat;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Random;
+
+import ch.qos.logback.classic.LoggerContext;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+import nodomain.freeyourgadget.gadgetbridge.util.PendingIntentUtils;
+
+/**
+ * Catches otherwise uncaught exceptions, logs them and terminates the app.
+ */
+public class GBExceptionHandler implements Thread.UncaughtExceptionHandler {
+    private static final Logger LOG = LoggerFactory.getLogger(GBExceptionHandler.class);
+    private final Thread.UncaughtExceptionHandler mDelegate;
+    private final boolean mNotifyOnCrash;
+
+    public GBExceptionHandler(Thread.UncaughtExceptionHandler delegate, final boolean notifyOnCrash) {
+        mDelegate = delegate;
+        mNotifyOnCrash = notifyOnCrash;
+    }
+
+    @Override
+    public void uncaughtException(@NonNull Thread thread, @NonNull Throwable ex) {
+        LOG.error("Uncaught exception", ex);
+        // flush the log buffers and stop logging
+        LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
+        loggerContext.stop();
+
+        if (mNotifyOnCrash) {
+            showNotification(ex);
+        }
+
+        if (mDelegate != null) {
+            mDelegate.uncaughtException(thread, ex);
+        } else {
+            System.exit(1);
+        }
+    }
+
+    private void showNotification(final Throwable e) {
+        final Context context = GBApplication.getContext();
+
+        final Intent shareIntent = new Intent();
+        shareIntent.setAction(Intent.ACTION_SEND);
+        shareIntent.putExtra(Intent.EXTRA_TEXT, Log.getStackTraceString(e));
+        shareIntent.setType("text/plain");
+
+        final PendingIntent pendingShareIntent = PendingIntentUtils.getActivity(
+                context,
+                0,
+                Intent.createChooser(shareIntent, context.getString(R.string.app_crash_share_stacktrace)),
+                PendingIntent.FLAG_UPDATE_CURRENT,
+                false
+        );
+
+        final NotificationCompat.Action shareAction = new NotificationCompat.Action.Builder(android.R.drawable.ic_menu_share, context.getString(R.string.share), pendingShareIntent).build();
+
+        final Notification notification = new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_HIGH_PRIORITY_ID)
+                .setSmallIcon(R.drawable.ic_notification)
+                .setContentTitle(context.getString(
+                        R.string.app_crash_notification_title,
+                        context.getString(R.string.app_name)
+                ))
+                .setContentText(e.getLocalizedMessage())
+                .setPriority(NotificationCompat.PRIORITY_DEFAULT)
+                .addAction(shareAction)
+                .build();
+
+        GB.notify(NOTIFICATION_ID_ERROR, notification, context);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/LockHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/LockHandler.java
new file mode 100644
index 0000000..cd13e53
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/LockHandler.java
@@ -0,0 +1,116 @@
+/*  Copyright (C) 2016-2024 Andreas Shimokawa, Carsten Pfeiffer, Taavi Eom盲e
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge;
+
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoMaster;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+
+/**
+ * Provides low-level access to the database.
+ */
+public class LockHandler implements DBHandler {
+
+    private DaoMaster daoMaster = null;
+    private DaoSession session = null;
+    private SQLiteOpenHelper helper = null;
+
+    public LockHandler() {
+    }
+
+    public void init(DaoMaster daoMaster, DaoMaster.OpenHelper helper) {
+        if (isValid()) {
+            throw new IllegalStateException("DB must be closed before initializing it again");
+        }
+        if (daoMaster == null) {
+            throw new IllegalArgumentException("daoMaster must not be null");
+        }
+        if (helper == null) {
+            throw new IllegalArgumentException("helper must not be null");
+        }
+        this.daoMaster = daoMaster;
+        this.helper = helper;
+
+        session = daoMaster.newSession();
+        if (session == null) {
+            throw new RuntimeException("Unable to create database session");
+        }
+    }
+
+    @Override
+    public DaoMaster getDaoMaster() {
+        return daoMaster;
+    }
+
+    private boolean isValid() {
+        return daoMaster != null;
+    }
+
+    private void ensureValid() {
+        if (!isValid()) {
+            throw new IllegalStateException("LockHandler is not in a valid state");
+        }
+    }
+
+    @Override
+    public void close() {
+        ensureValid();
+        GBApplication.releaseDB();
+    }
+
+    @Override
+    public synchronized void openDb() {
+        if (session != null) {
+            throw new IllegalStateException("session must be null");
+        }
+        // this will create completely new db instances and in turn update this handler through #init()
+        GBApplication.app().setupDatabase();
+    }
+
+    @Override
+    public synchronized void closeDb() {
+        if (session == null) {
+            throw new IllegalStateException("session must not be null");
+        }
+        session.clear();
+        session.getDatabase().close();
+        session = null;
+        helper = null;
+        daoMaster = null;
+    }
+
+    @Override
+    public SQLiteOpenHelper getHelper() {
+        ensureValid();
+        return helper;
+    }
+
+    @Override
+    public DaoSession getDaoSession() {
+        ensureValid();
+        return session;
+    }
+
+    @Override
+    public SQLiteDatabase getDatabase() {
+        ensureValid();
+        return daoMaster.getDatabase();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/Logging.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/Logging.java
new file mode 100644
index 0000000..5ac336d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/Logging.java
@@ -0,0 +1,208 @@
+/*  Copyright (C) 2016-2024 Andreas Shimokawa, Carsten Pfeiffer, Daniele
+    Gobbetti, Jos茅 Rebelo, Pavel Elagin, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge;
+
+import android.util.Log;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+
+import ch.qos.logback.classic.LoggerContext;
+import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import ch.qos.logback.core.Appender;
+import ch.qos.logback.core.FileAppender;
+import ch.qos.logback.core.rolling.RollingFileAppender;
+import ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy;
+import ch.qos.logback.core.util.FileSize;
+import ch.qos.logback.core.util.StatusPrinter;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+import nodomain.freeyourgadget.gadgetbridge.BuildConfig;
+
+public abstract class Logging {
+    // Only used for tests
+    public static final String PROP_LOGFILES_DIR = "GB_LOGFILES_DIR";
+
+    private String logDirectory;
+    private FileAppender<ILoggingEvent> fileLogger;
+
+    public void setupLogging(boolean enable) {
+        try {
+            if (!isFileLoggerInitialized()) {
+                init();
+            }
+            if (enable) {
+                startFileLogger();
+            } else {
+                stopFileLogger();
+            }
+            getLogger().info("Gadgetbridge version: {}-{}", BuildConfig.VERSION_NAME, BuildConfig.GIT_HASH_SHORT);
+        } catch (Exception ex) {
+            Log.e("GBApplication", "External files dir not available, cannot log to file", ex);
+            stopFileLogger();
+        }
+    }
+
+    public String getLogPath() {
+        if (fileLogger != null)
+            return fileLogger.getFile();
+        else
+            return null;
+    }
+
+    public void setImmediateFlush(final boolean immediateFlush) {
+        if (fileLogger != null) {
+            fileLogger.setImmediateFlush(true);
+        }
+    }
+
+    public boolean isFileLoggerInitialized() {
+        return logDirectory != null;
+    }
+
+    public void debugLoggingConfiguration() {
+        // For debugging problems with the logback configuration
+        LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
+        // print logback's internal status
+        StatusPrinter.print(lc);
+//        Logger logger = LoggerFactory.getLogger(Logging.class);
+    }
+
+    protected abstract String createLogDirectory() throws IOException;
+
+    protected void init() throws IOException {
+        Log.i("GBApplication", "Initializing logging");
+        logDirectory = createLogDirectory();
+        if (logDirectory == null) {
+            throw new IllegalArgumentException("log directory must not be null");
+        }
+    }
+
+    private Logger getLogger() {
+        return LoggerFactory.getLogger(Logging.class);
+    }
+
+    private void startFileLogger() {
+        if (fileLogger != null) {
+            Log.w("GBApplication", "Logger already started");
+            return;
+        }
+
+        if (logDirectory == null) {
+            Log.e("GBApplication", "Can't start file logging without a log directory");
+            return;
+        }
+
+        final FileAppender fileAppender = createFileAppender(logDirectory);
+        fileAppender.start();
+        attachLogger(fileAppender);
+        fileLogger = fileAppender;
+    }
+
+    private void stopFileLogger() {
+        if (fileLogger == null) {
+            return;
+        }
+
+        if (fileLogger.isStarted()) {
+            fileLogger.stop();
+        }
+
+        detachLogger(fileLogger);
+
+        fileLogger = null;
+    }
+
+    private void attachLogger(Appender<ILoggingEvent> logger) {
+        try {
+            ch.qos.logback.classic.Logger root = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
+            if (!root.isAttached(logger)) {
+                root.addAppender(logger);
+            }
+        } catch (Throwable ex) {
+            Log.e("GBApplication", "Error attaching logger appender", ex);
+        }
+    }
+
+    private void detachLogger(Appender<ILoggingEvent> logger) {
+        try {
+            ch.qos.logback.classic.Logger root = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
+            if (logger != null && root.isAttached(logger)) {
+                root.detachAppender(logger);
+            }
+        } catch (Throwable ex) {
+            Log.e("GBApplication", "Error detaching logger appender", ex);
+        }
+    }
+
+    public FileAppender<ILoggingEvent> getFileLogger() {
+        return fileLogger;
+    }
+
+    public static String formatBytes(byte[] bytes) {
+        if (bytes == null) {
+            return "(null)";
+        }
+        StringBuilder builder = new StringBuilder(bytes.length * 5);
+        for (byte b : bytes) {
+            builder.append(String.format("0x%02x", b));
+            builder.append(" ");
+        }
+        return builder.toString().trim();
+    }
+
+    public static void logBytes(Logger logger, byte[] value) {
+        if (value != null) {
+            logger.warn("DATA: " + GB.hexdump(value, 0, value.length));
+        }
+    }
+
+    public static FileAppender createFileAppender(final String logDirectory) {
+        final LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
+
+        final PatternLayoutEncoder ple = new PatternLayoutEncoder();
+        //ple.setPattern("%date %level [%thread] %logger{10} [%file:%line] %msg%n");
+        ple.setPattern("%d{HH:mm:ss.SSS} [%thread] %-5level %logger{1} - %msg%n");
+        ple.setContext(lc);
+        ple.start();
+
+        final SizeAndTimeBasedRollingPolicy rollingPolicy = new SizeAndTimeBasedRollingPolicy();
+        final RollingFileAppender<ILoggingEvent> fileAppender = new RollingFileAppender<ILoggingEvent>();
+
+        rollingPolicy.setContext(lc);
+        rollingPolicy.setFileNamePattern(logDirectory + "/gadgetbridge-%d{yyyy-MM-dd}.%i.log.zip");
+        rollingPolicy.setParent(fileAppender);
+        rollingPolicy.setMaxFileSize(FileSize.valueOf("2MB"));
+        rollingPolicy.setMaxHistory(10);
+        rollingPolicy.setTotalSizeCap(FileSize.valueOf("100MB"));
+        rollingPolicy.start();
+
+        fileAppender.setContext(lc);
+        fileAppender.setName("FILE");
+        fileAppender.setLazy(true);
+        fileAppender.setFile(logDirectory + "/gadgetbridge.log");
+        fileAppender.setEncoder(ple);
+        // to debug crashes, set immediateFlush to true, otherwise keep it false to improve throughput
+        fileAppender.setImmediateFlush(false);
+        fileAppender.setRollingPolicy(rollingPolicy);
+
+        return fileAppender;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/SleepAlarmWidget.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/SleepAlarmWidget.java
new file mode 100644
index 0000000..85f0795
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/SleepAlarmWidget.java
@@ -0,0 +1,159 @@
+/*  Copyright (C) 2016-2024 0nse, Andreas Shimokawa, Carsten Pfeiffer,
+    Dmitry Markin, Ganblejs, Jos茅 Rebelo, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge;
+
+import android.annotation.TargetApi;
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProvider;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Build;
+import android.os.Bundle;
+import android.widget.RemoteViews;
+import android.widget.Toast;
+
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+
+import nodomain.freeyourgadget.gadgetbridge.activities.ConfigureAlarms;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
+import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
+import nodomain.freeyourgadget.gadgetbridge.util.AlarmUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+import nodomain.freeyourgadget.gadgetbridge.util.PendingIntentUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.WidgetPreferenceStorage;
+
+/**
+ * Implementation of SleepAlarmWidget functionality. When pressing the widget, an alarm will be set
+ * to trigger after a predefined number of hours. A toast will confirm the user about this. The
+ * value is retrieved using ActivityUser.().getSleepDuration().
+ */
+public class SleepAlarmWidget extends AppWidgetProvider {
+    /**
+     * This is our dedicated action to detect when the widget has been clicked.
+     */
+    public static final String ACTION_CLICK =
+            "nodomain.freeyourgadget.gadgetbridge.SLEEP_ALARM_WIDGET_CLICK";
+
+    static void updateAppWidget(Context context, AppWidgetManager appWidgetManager,
+                                int appWidgetId) {
+
+        // Construct the RemoteViews object
+        RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.sleep_alarm_widget);
+
+        // Add our own click intent
+        Intent intent = new Intent(context, SleepAlarmWidget.class);
+        intent.setPackage(BuildConfig.APPLICATION_ID);
+        intent.setAction(ACTION_CLICK);
+        intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
+        PendingIntent clickPI = PendingIntentUtils.getBroadcast(
+                context, appWidgetId, intent, PendingIntent.FLAG_UPDATE_CURRENT, false);
+        views.setOnClickPendingIntent(R.id.sleepalarmwidget_text, clickPI);
+
+        // Instruct the widget manager to update the widget
+        appWidgetManager.updateAppWidget(appWidgetId, views);
+    }
+
+    @Override
+    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
+        // There may be multiple widgets active, so update all of them
+        for (int appWidgetId : appWidgetIds) {
+            updateAppWidget(context, appWidgetManager, appWidgetId);
+        }
+    }
+
+    @Override
+    public void onEnabled(Context context) {
+        // Enter relevant functionality for when the first widget is created
+    }
+
+    @Override
+    public void onDisabled(Context context) {
+        // Enter relevant functionality for when the last widget is disabled
+    }
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        super.onReceive(context, intent);
+        Bundle extras = intent.getExtras();
+        int appWidgetId = -1;
+        if (extras != null) {
+            appWidgetId = extras.getInt(
+                    AppWidgetManager.EXTRA_APPWIDGET_ID,
+                    AppWidgetManager.INVALID_APPWIDGET_ID);
+        }
+
+        if (ACTION_CLICK.equals(intent.getAction())) {
+            int userSleepDuration = new ActivityUser().getSleepDurationGoal();
+            // current timestamp
+            GregorianCalendar calendar = new GregorianCalendar();
+            // add preferred sleep duration
+            if (userSleepDuration > 0) {
+                calendar.add(Calendar.HOUR_OF_DAY, userSleepDuration);
+            } else { // probably testing
+                calendar.add(Calendar.MINUTE, 1);
+            }
+
+            // overwrite the first alarm and activate it, without
+
+            GBDevice deviceForWidget = new WidgetPreferenceStorage().getDeviceForWidget(appWidgetId);
+            if (deviceForWidget == null || !deviceForWidget.isInitialized()) {
+                GB.toast(context, context.getString(R.string.appwidget_not_connected),
+                        Toast.LENGTH_SHORT, GB.WARN);
+                return;
+            }
+
+            int hours = calendar.get(Calendar.HOUR_OF_DAY);
+            int minutes = calendar.get(Calendar.MINUTE);
+
+            GB.toast(context,
+                    context.getString(R.string.appwidget_setting_alarm, hours, minutes),
+                    Toast.LENGTH_SHORT, GB.INFO);
+
+            Alarm alarm = AlarmUtils.createSingleShot(0, true, false, calendar);
+            ArrayList<Alarm> alarms = new ArrayList<>(1);
+            alarms.add(alarm);
+            GBApplication.deviceService(deviceForWidget).onSetAlarms(alarms);
+
+//          setAlarmViaAlarmManager(context, calendar.getTimeInMillis());
+        }
+    }
+
+    /**
+     * Use the Android alarm manager to create the alarm icon in the status bar.
+     *
+     * @param packageContext {@code Context}: A Context of the application package implementing this
+     *                       class.
+     * @param triggerTime    {@code long}: time at which the underlying alarm is triggered in wall time
+     *                       milliseconds since the epoch
+     */
+    private void setAlarmViaAlarmManager(Context packageContext, long triggerTime) {
+        AlarmManager am = (AlarmManager) packageContext.getSystemService(Context.ALARM_SERVICE);
+        // TODO: launch the alarm configuration activity when clicking the alarm in the status bar
+        Intent intent = new Intent(packageContext, ConfigureAlarms.class);
+        intent.setPackage(BuildConfig.APPLICATION_ID);
+        PendingIntent pi = PendingIntentUtils.getBroadcast(packageContext, 0, intent,
+                PendingIntent.FLAG_CANCEL_CURRENT, false);
+        am.setAlarmClock(new AlarmManager.AlarmClockInfo(triggerTime, pi), pi);
+    }
+}
+
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/Widget.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/Widget.java
new file mode 100644
index 0000000..56ab047
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/Widget.java
@@ -0,0 +1,258 @@
+/*  Copyright (C) 2019-2024 Andreas Shimokawa, Carsten Pfeiffer, Ganblejs,
+    Jos茅 Rebelo, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge;
+
+import android.app.PendingIntent;
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProvider;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.RemoteViews;
+import android.widget.Toast;
+
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.concurrent.TimeUnit;
+
+import nodomain.freeyourgadget.gadgetbridge.activities.ControlCenterv2;
+import nodomain.freeyourgadget.gadgetbridge.activities.WidgetAlarmsActivity;
+import nodomain.freeyourgadget.gadgetbridge.activities.charts.ActivityChartsActivity;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
+import nodomain.freeyourgadget.gadgetbridge.model.DailyTotals;
+import nodomain.freeyourgadget.gadgetbridge.model.RecordedDataTypes;
+import nodomain.freeyourgadget.gadgetbridge.util.AndroidUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.FormatUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+import nodomain.freeyourgadget.gadgetbridge.util.PendingIntentUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.WidgetPreferenceStorage;
+
+public class Widget extends AppWidgetProvider {
+    public static final String WIDGET_CLICK = "nodomain.freeyourgadget.gadgetbridge.WidgetClick";
+    public static final String APPWIDGET_DELETED = "android.appwidget.action.APPWIDGET_DELETED";
+
+    private static final Logger LOG = LoggerFactory.getLogger(Widget.class);
+    static BroadcastReceiver broadcastReceiver = null;
+
+
+    private DailyTotals getSteps(GBDevice gbDevice) {
+        Context context = GBApplication.getContext();
+        Calendar day = GregorianCalendar.getInstance();
+
+        if (!(context instanceof GBApplication)) {
+            return new DailyTotals();
+        }
+        return DailyTotals.getDailyTotalsForDevice(gbDevice, day);
+    }
+
+    private String getHM(long value) {
+        return DateTimeUtils.formatDurationHoursMinutes(value, TimeUnit.MINUTES);
+    }
+
+    private void updateAppWidget(Context context, AppWidgetManager appWidgetManager,
+                                 int appWidgetId) {
+
+        GBDevice deviceForWidget = new WidgetPreferenceStorage().getDeviceForWidget(appWidgetId);
+        if (deviceForWidget == null) {
+            LOG.debug("Widget: no device, bailing out");
+            return;
+        }
+
+        RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget);
+
+        //onclick refresh
+        Intent intent = new Intent(context, Widget.class);
+        intent.setPackage(BuildConfig.APPLICATION_ID);
+        intent.setAction(WIDGET_CLICK);
+        intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
+        PendingIntent refreshDataIntent = PendingIntentUtils.getBroadcast(
+                context, appWidgetId, intent, PendingIntent.FLAG_UPDATE_CURRENT, false);
+        views.setOnClickPendingIntent(R.id.todaywidget_header_container, refreshDataIntent);
+
+        //open GB main window
+        Intent startMainIntent = new Intent(context, ControlCenterv2.class);
+        startMainIntent.setPackage(BuildConfig.APPLICATION_ID);
+        PendingIntent startMainPIntent = PendingIntentUtils.getActivity(context, 0, startMainIntent, 0, false);
+        views.setOnClickPendingIntent(R.id.todaywidget_header_icon, startMainPIntent);
+
+        //alarms popup menu
+        Intent startAlarmListIntent = new Intent(context, WidgetAlarmsActivity.class);
+        startAlarmListIntent.setPackage(BuildConfig.APPLICATION_ID);
+        startAlarmListIntent.putExtra(GBDevice.EXTRA_DEVICE, deviceForWidget);
+        PendingIntent startAlarmListPIntent = PendingIntentUtils.getActivity(context, appWidgetId, startAlarmListIntent, PendingIntent.FLAG_UPDATE_CURRENT, false);
+        views.setOnClickPendingIntent(R.id.todaywidget_header_alarm_icon, startAlarmListPIntent);
+
+        //charts
+        Intent startChartsIntent = new Intent(context, ActivityChartsActivity.class);
+        startChartsIntent.setPackage(BuildConfig.APPLICATION_ID);
+        startChartsIntent.putExtra(GBDevice.EXTRA_DEVICE, deviceForWidget);
+        PendingIntent startChartsPIntent = PendingIntentUtils.getActivity(context, appWidgetId, startChartsIntent, PendingIntent.FLAG_CANCEL_CURRENT, false);
+        views.setOnClickPendingIntent(R.id.todaywidget_bottom_layout, startChartsPIntent);
+
+        DailyTotals dailyTotals = getSteps(deviceForWidget);
+        int steps = (int) dailyTotals.getSteps();
+        int sleep = (int) dailyTotals.getSleep();
+        int distanceCm = (int) dailyTotals.getDistance();
+        ActivityUser activityUser = new ActivityUser();
+        int stepGoal = activityUser.getStepsGoal();
+        int sleepGoal = activityUser.getSleepDurationGoal();
+        int sleepGoalMinutes = sleepGoal * 60;
+        int distanceGoal = activityUser.getDistanceGoalMeters() * 100;
+        int stepLength = activityUser.getStepLengthCm();
+        double distanceMeters = (distanceCm > 0 ? distanceCm : steps * stepLength) * 0.01;
+        String distanceFormatted = FormatUtils.getFormattedDistanceLabel(distanceMeters);
+
+        if (sleep < 1) {
+            views.setViewVisibility(R.id.todaywidget_sleep_layout, View.GONE);
+        } else {
+            views.setViewVisibility(R.id.todaywidget_sleep_layout, View.VISIBLE);
+        }
+
+        views.setTextViewText(R.id.todaywidget_steps, String.format("%1s", steps));
+        views.setTextViewText(R.id.todaywidget_sleep, String.format("%1s", getHM(sleep)));
+        views.setTextViewText(R.id.todaywidget_distance, distanceFormatted);
+        views.setProgressBar(R.id.todaywidget_steps_progress, stepGoal, steps, false);
+        views.setProgressBar(R.id.todaywidget_sleep_progress, sleepGoalMinutes, sleep, false);
+        views.setProgressBar(R.id.todaywidget_distance_progress, distanceGoal, steps * stepLength, false);
+        views.setViewVisibility(R.id.todaywidget_battery_icon, View.GONE);
+        if (deviceForWidget != null) {
+            String status = String.format("%1s", deviceForWidget.getStateString(context));
+            if (deviceForWidget.isConnected()) {
+                if (deviceForWidget.getBatteryLevel() > 1) {
+                    views.setViewVisibility(R.id.todaywidget_battery_icon, View.VISIBLE);
+
+                    status = String.format("%1s%%", deviceForWidget.getBatteryLevel());
+                }
+            }
+
+            String deviceName = deviceForWidget.getAlias() != null ? deviceForWidget.getAlias() : deviceForWidget.getName();
+            views.setTextViewText(R.id.todaywidget_device_status, status);
+            views.setTextViewText(R.id.todaywidget_device_name, deviceName);
+        }
+
+        // Instruct the widget manager to update the widget
+        appWidgetManager.updateAppWidget(appWidgetId, views);
+    }
+
+    public void refreshData(int appWidgetId) {
+        Context context = GBApplication.getContext();
+        GBDevice deviceForWidget = new WidgetPreferenceStorage().getDeviceForWidget(appWidgetId);
+
+        if (deviceForWidget == null || !deviceForWidget.isInitialized()) {
+            GB.toast(context,
+                    context.getString(R.string.device_not_connected),
+                    Toast.LENGTH_SHORT, GB.ERROR);
+            GBApplication.deviceService(deviceForWidget).connect();
+            GB.toast(context,
+                    context.getString(R.string.connecting),
+                    Toast.LENGTH_SHORT, GB.INFO);
+
+            return;
+        }
+        GB.toast(context,
+                context.getString(R.string.busy_task_fetch_activity_data),
+                Toast.LENGTH_SHORT, GB.INFO);
+
+        GBApplication.deviceService(deviceForWidget).onFetchRecordedData(RecordedDataTypes.TYPE_ACTIVITY);
+    }
+
+    public void updateWidget() {
+        Context context = GBApplication.getContext();
+        AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
+        ComponentName thisAppWidget = new ComponentName(context.getPackageName(), Widget.class.getName());
+        int[] appWidgetIds = appWidgetManager.getAppWidgetIds(thisAppWidget);
+        onUpdate(context, appWidgetManager, appWidgetIds);
+    }
+
+    public void removeWidget(Context context, int appWidgetId) {
+        WidgetPreferenceStorage widgetPreferenceStorage = new WidgetPreferenceStorage();
+        widgetPreferenceStorage.removeWidgetById(context, appWidgetId);
+    }
+
+    @Override
+    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
+        // There may be multiple widgets active, so update all of them
+        for (int appWidgetId : appWidgetIds) {
+            updateAppWidget(context, appWidgetManager, appWidgetId);
+        }
+    }
+
+    @Override
+    public void onEnabled(Context context) {
+        if (broadcastReceiver == null) {
+            LOG.debug("gbwidget BROADCAST receiver initialized.");
+            broadcastReceiver = new BroadcastReceiver() {
+                @Override
+                public void onReceive(Context context, Intent intent) {
+                    LOG.debug("gbwidget BROADCAST, action" + intent.getAction());
+                    updateWidget();
+                }
+            };
+            IntentFilter intentFilter = new IntentFilter();
+            intentFilter.addAction(GBApplication.ACTION_NEW_DATA);
+            intentFilter.addAction(GBDevice.ACTION_DEVICE_CHANGED);
+            LocalBroadcastManager.getInstance(context).registerReceiver(broadcastReceiver, intentFilter);
+        }
+    }
+
+    @Override
+    public void onDisabled(Context context) {
+        if (broadcastReceiver != null) {
+            AndroidUtils.safeUnregisterBroadcastReceiver(context, broadcastReceiver);
+            broadcastReceiver = null;
+        }
+    }
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        super.onReceive(context, intent);
+        LOG.debug("gbwidget LOCAL onReceive, action: " + intent.getAction() + intent);
+        Bundle extras = intent.getExtras();
+        int appWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID;
+        if (extras != null) {
+            appWidgetId = extras.getInt(
+                    AppWidgetManager.EXTRA_APPWIDGET_ID,
+                    AppWidgetManager.INVALID_APPWIDGET_ID);
+        }
+
+        //this handles widget re-connection after apk updates
+        if (WIDGET_CLICK.equals(intent.getAction())) {
+            if (broadcastReceiver == null) {
+                onEnabled(context);
+            }
+                refreshData(appWidgetId);
+            //updateWidget();
+        } else if (APPWIDGET_DELETED.equals(intent.getAction())) {
+            onDisabled(context);
+            removeWidget(context, appWidgetId);
+        }
+    }
+
+}
+
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AboutActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AboutActivity.java
new file mode 100644
index 0000000..5782b5c
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AboutActivity.java
@@ -0,0 +1,65 @@
+/*  Copyright (C) 2020-2024 Petr Van臎k, Taavi Eom盲e
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities;
+
+import android.content.ClipData;
+import android.content.ClipboardManager;
+import android.content.Context;
+import android.os.Bundle;
+import android.text.method.LinkMovementMethod;
+import android.view.View;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import nodomain.freeyourgadget.gadgetbridge.BuildConfig;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class AboutActivity extends AbstractGBActivity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_about);
+        TextView about_version = findViewById(R.id.about_version);
+        TextView about_hash = findViewById(R.id.about_hash);
+        String versionName = BuildConfig.VERSION_NAME;
+        String versionHASH = BuildConfig.GIT_HASH_SHORT;
+        about_version.setText(String.format(getString(R.string.about_version), versionName));
+        about_version.setOnClickListener(this::copyVersionToClipboard);
+        about_hash.setText(String.format(getString(R.string.about_hash), versionHASH));
+        about_hash.setOnClickListener(this::copyVersionToClipboard);
+
+        TextView link1 = findViewById(R.id.links1);
+        link1.setMovementMethod(LinkMovementMethod.getInstance());
+        TextView link2 = findViewById(R.id.links2);
+        link2.setMovementMethod(LinkMovementMethod.getInstance());
+        TextView link3 = findViewById(R.id.links3);
+        link3.setMovementMethod(LinkMovementMethod.getInstance());
+    }
+
+    private void copyVersionToClipboard(View view) {
+        String versions = "Version: " + BuildConfig.VERSION_NAME +
+                "\nCommit: " + BuildConfig.GIT_HASH_SHORT +
+                "\nFlavor: " + BuildConfig.FLAVOR;
+        ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
+        ClipData clip = ClipData.newPlainText("Build data", versions);
+        clipboard.setPrimaryClip(clip);
+        GB.toast(getString(R.string.about_build_details_copied_to_clipboard), Toast.LENGTH_LONG, GB.INFO);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AboutUserPreferencesActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AboutUserPreferencesActivity.java
new file mode 100644
index 0000000..ca6dea5
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AboutUserPreferencesActivity.java
@@ -0,0 +1,117 @@
+/*  Copyright (C) 2020-2024 Andreas Shimokawa, Jos茅 Rebelo, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities;
+
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivityUser.PREF_USER_ACTIVETIME_MINUTES;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivityUser.PREF_USER_CALORIES_BURNT;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivityUser.PREF_USER_DATE_OF_BIRTH;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivityUser.PREF_USER_DISTANCE_METERS;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivityUser.PREF_USER_GENDER;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivityUser.PREF_USER_GOAL_FAT_BURN_TIME_MINUTES;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivityUser.PREF_USER_GOAL_STANDING_TIME_HOURS;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivityUser.PREF_USER_GOAL_WEIGHT_KG;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivityUser.PREF_USER_HEIGHT_CM;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivityUser.PREF_USER_NAME;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivityUser.PREF_USER_SLEEP_DURATION;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivityUser.PREF_USER_STEPS_GOAL;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivityUser.PREF_USER_STEP_LENGTH_CM;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivityUser.PREF_USER_WEIGHT_KG;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.text.InputType;
+
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceFragmentCompat;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.DeviceManager;
+
+public class AboutUserPreferencesActivity extends AbstractSettingsActivityV2 {
+    @Override
+    protected String fragmentTag() {
+        return AboutUserPreferencesFragment.FRAGMENT_TAG;
+    }
+
+    @Override
+    protected PreferenceFragmentCompat newFragment() {
+        return new AboutUserPreferencesFragment();
+    }
+
+    public static class AboutUserPreferencesFragment extends AbstractPreferenceFragment {
+        static final String FRAGMENT_TAG = "ABOUT_USER_PREFERENCES_FRAGMENT";
+
+        @Override
+        public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) {
+            setPreferencesFromResource(R.xml.about_user, rootKey);
+
+            addPreferenceHandlerFor(PREF_USER_NAME, true, false);
+            addPreferenceHandlerFor(PREF_USER_DATE_OF_BIRTH, true, false);
+            addPreferenceHandlerFor(PREF_USER_HEIGHT_CM, true, true);
+            addPreferenceHandlerFor(PREF_USER_WEIGHT_KG, true, false);
+            addPreferenceHandlerFor(PREF_USER_GENDER, true, false);
+            addPreferenceHandlerFor(PREF_USER_STEPS_GOAL, true, true);
+            addPreferenceHandlerFor(PREF_USER_GOAL_WEIGHT_KG, true, true);
+            addPreferenceHandlerFor(PREF_USER_GOAL_STANDING_TIME_HOURS, true, true);
+            addPreferenceHandlerFor(PREF_USER_GOAL_FAT_BURN_TIME_MINUTES, true, true);
+            addPreferenceHandlerFor(PREF_USER_SLEEP_DURATION, false, true);
+            addPreferenceHandlerFor(PREF_USER_STEP_LENGTH_CM, false, true);
+            addPreferenceHandlerFor(PREF_USER_DISTANCE_METERS, false, true);
+
+            setInputTypeFor(PREF_USER_HEIGHT_CM, InputType.TYPE_CLASS_NUMBER);
+            setInputTypeFor(PREF_USER_WEIGHT_KG, InputType.TYPE_CLASS_NUMBER);
+            setInputTypeFor(PREF_USER_STEPS_GOAL, InputType.TYPE_CLASS_NUMBER);
+            setInputTypeFor(PREF_USER_GOAL_WEIGHT_KG, InputType.TYPE_CLASS_NUMBER);
+            setInputTypeFor(PREF_USER_GOAL_STANDING_TIME_HOURS, InputType.TYPE_CLASS_NUMBER);
+            setInputTypeFor(PREF_USER_GOAL_FAT_BURN_TIME_MINUTES, InputType.TYPE_CLASS_NUMBER);
+            setInputTypeFor(PREF_USER_SLEEP_DURATION, InputType.TYPE_CLASS_NUMBER);
+            setInputTypeFor(PREF_USER_CALORIES_BURNT, InputType.TYPE_CLASS_NUMBER);
+            setInputTypeFor(PREF_USER_ACTIVETIME_MINUTES, InputType.TYPE_CLASS_NUMBER);
+            setInputTypeFor(PREF_USER_STEP_LENGTH_CM, InputType.TYPE_CLASS_NUMBER);
+            setInputTypeFor(PREF_USER_DISTANCE_METERS, InputType.TYPE_CLASS_NUMBER);
+        }
+
+        /**
+         * @param prefKey           the pref key that chagned
+         * @param sendToDevice      notify all device support classes of the preference change
+         * @param refreshDeviceList Ensure that the Control center is re-rendered when user preferences change
+         */
+        private void addPreferenceHandlerFor(final String prefKey,
+                                             final boolean sendToDevice,
+                                             final boolean refreshDeviceList) {
+            final Preference pref = findPreference(prefKey);
+            if (pref == null) {
+                LOG.warn("Could not find preference {}", prefKey);
+                return;
+            }
+
+            pref.setOnPreferenceChangeListener((preference, newVal) -> {
+                if (sendToDevice) {
+                    GBApplication.deviceService().onSendConfiguration(prefKey);
+                }
+                if (refreshDeviceList) {
+                    final Intent refreshIntent = new Intent(DeviceManager.ACTION_REFRESH_DEVICELIST);
+                    LocalBroadcastManager.getInstance(requireActivity().getApplicationContext()).sendBroadcast(refreshIntent);
+                    return true;
+                }
+                return true;
+            });
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AbstractFragmentPagerAdapter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AbstractFragmentPagerAdapter.java
new file mode 100644
index 0000000..227b067
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AbstractFragmentPagerAdapter.java
@@ -0,0 +1,68 @@
+/*  Copyright (C) 2015-2024 Carsten Pfeiffer, Daniele Gobbetti
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities;
+
+import android.view.ViewGroup;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import androidx.fragment.app.FragmentManager;
+import androidx.fragment.app.FragmentStatePagerAdapter;
+
+public abstract class AbstractFragmentPagerAdapter extends FragmentStatePagerAdapter {
+    private final Set<AbstractGBFragment> fragments = new HashSet<>();
+    private Object primaryFragment;
+
+    public AbstractFragmentPagerAdapter(FragmentManager fm) {
+        super(fm);
+    }
+
+    @Override
+    public Object instantiateItem(ViewGroup container, int position) {
+        Object fragment = super.instantiateItem(container, position);
+        if (fragment instanceof AbstractGBFragment) {
+            fragments.add((AbstractGBFragment) fragment);
+        }
+        return fragment;
+    }
+
+    @Override
+    public void destroyItem(ViewGroup container, int position, Object object) {
+        super.destroyItem(container, position, object);
+        fragments.remove(object);
+    }
+
+    @Override
+    public void setPrimaryItem(ViewGroup container, int position, Object object) {
+        super.setPrimaryItem(container, position, object);
+        if (object != primaryFragment) {
+            primaryFragment = object;
+            setCurrentFragment(primaryFragment);
+        }
+    }
+
+    private void setCurrentFragment(Object newCurrentFragment) {
+        for (AbstractGBFragment frag : fragments) {
+            if (frag != newCurrentFragment) {
+                frag.onMadeInvisibleInActivity();
+            } else {
+                frag.onMadeVisibleInActivityInternal();
+            }
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AbstractGBActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AbstractGBActivity.java
new file mode 100644
index 0000000..670bfd9
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AbstractGBActivity.java
@@ -0,0 +1,144 @@
+/*  Copyright (C) 2017-2024 Andreas Shimokawa, Arjan Schrijver, Carsten
+    Pfeiffer, Daniele Gobbetti, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities;
+
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import java.util.Locale;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.util.AndroidUtils;
+
+
+public abstract class AbstractGBActivity extends AppCompatActivity implements GBActivity {
+    private boolean isLanguageInvalid = false;
+
+    public static final int NONE = 0;
+    public static final int NO_ACTIONBAR = 1;
+
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            if (action == null) {
+                return;
+            }
+            switch (action) {
+                case GBApplication.ACTION_LANGUAGE_CHANGE:
+                    setLanguage(GBApplication.getLanguage(), true);
+                    break;
+                case GBApplication.ACTION_THEME_CHANGE:
+                    recreate();
+                    break;
+                case GBApplication.ACTION_QUIT:
+                    finish();
+                    break;
+            }
+        }
+    };
+
+    public void setLanguage(Locale language, boolean invalidateLanguage) {
+        if (invalidateLanguage) {
+            isLanguageInvalid = true;
+        }
+        AndroidUtils.setLanguage(this, language);
+    }
+
+    public static void init(GBActivity activity) {
+        init(activity, NONE);
+    }
+
+    public static void init(GBActivity activity, int flags) {
+        if (GBApplication.areDynamicColorsEnabled()) {
+            if (GBApplication.isDarkThemeEnabled()) {
+                if ((flags & NO_ACTIONBAR) != 0) {
+                    if (GBApplication.isAmoledBlackEnabled())
+                        activity.setTheme(R.style.GadgetbridgeThemeDynamicDarkAmoled_NoActionBar);
+                    else
+                        activity.setTheme(R.style.GadgetbridgeThemeDynamicDark_NoActionBar);
+                } else {
+                    if (GBApplication.isAmoledBlackEnabled())
+                        activity.setTheme(R.style.GadgetbridgeThemeDynamicDarkAmoled);
+                    else
+                        activity.setTheme(R.style.GadgetbridgeThemeDynamicDark);
+                }
+            } else {
+                if ((flags & NO_ACTIONBAR) != 0) {
+                    activity.setTheme(R.style.GadgetbridgeThemeDynamicLight_NoActionBar);
+                } else {
+                    activity.setTheme(R.style.GadgetbridgeThemeDynamicLight);
+                }
+            }
+        } else if (GBApplication.isDarkThemeEnabled()) {
+            if ((flags & NO_ACTIONBAR) != 0) {
+                if (GBApplication.isAmoledBlackEnabled())
+                    activity.setTheme(R.style.GadgetbridgeThemeBlack_NoActionBar);
+                else
+                    activity.setTheme(R.style.GadgetbridgeThemeDark_NoActionBar);
+            } else {
+                if (GBApplication.isAmoledBlackEnabled())
+                    activity.setTheme(R.style.GadgetbridgeThemeBlack);
+                else
+                    activity.setTheme(R.style.GadgetbridgeThemeDark);
+            }
+        } else {
+            if ((flags & NO_ACTIONBAR) != 0) {
+                activity.setTheme(R.style.GadgetbridgeTheme_NoActionBar);
+            } else {
+                activity.setTheme(R.style.GadgetbridgeTheme);
+            }
+        }
+        activity.setLanguage(GBApplication.getLanguage(), false);
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        IntentFilter filterLocal = new IntentFilter();
+        filterLocal.addAction(GBApplication.ACTION_QUIT);
+        filterLocal.addAction(GBApplication.ACTION_LANGUAGE_CHANGE);
+        filterLocal.addAction(GBApplication.ACTION_THEME_CHANGE);
+        LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, filterLocal);
+
+        init(this);
+        super.onCreate(savedInstanceState);
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        if (isLanguageInvalid) {
+            isLanguageInvalid = false;
+            recreate();
+        }
+    }
+
+    @Override
+    protected void onDestroy() {
+        LocalBroadcastManager.getInstance(this).unregisterReceiver(mReceiver);
+        super.onDestroy();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AbstractGBFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AbstractGBFragment.java
new file mode 100644
index 0000000..a1d448a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AbstractGBFragment.java
@@ -0,0 +1,74 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, Carsten Pfeiffer, Daniele
+    Gobbetti, walkjivefly
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities;
+
+import androidx.annotation.Nullable;
+import androidx.fragment.app.Fragment;
+
+/**
+ * Abstract base class for fragments. Provides hooks that are called when
+ * the fragment is made visible and invisible in the activity. also allows
+ * the fragment to define the title to be shown in the activity.
+ *
+ * @see AbstractGBFragmentActivity
+ */
+public abstract class AbstractGBFragment extends Fragment {
+    private boolean mVisibleInActivity;
+
+    /**
+     * Called when this fragment has been fully scrolled into the activity.
+     *
+     * @see #isVisibleInActivity()
+     * @see #onMadeInvisibleInActivity()
+     */
+    protected void onMadeVisibleInActivity() {
+    }
+
+    /**
+     * Called when this fragment has been scrolled out of the activity.
+     *
+     * @see #isVisibleInActivity()
+     * @see #onMadeVisibleInActivity()
+     */
+    public void onMadeInvisibleInActivity() {
+        mVisibleInActivity = false;
+    }
+
+    /**
+     * Returns true if this fragment is currently visible in the hosting
+     * activity, not taking into account whether the screen is enabled at all.
+     */
+    public boolean isVisibleInActivity() {
+        return mVisibleInActivity;
+    }
+
+    @Nullable
+    protected abstract CharSequence getTitle();
+
+    /**
+     * Internal
+     *
+     * @hide
+     */
+    public void onMadeVisibleInActivityInternal() {
+        mVisibleInActivity = true;
+        if (isVisible()) {
+            onMadeVisibleInActivity();
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AbstractGBFragmentActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AbstractGBFragmentActivity.java
new file mode 100644
index 0000000..2da4bf9
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AbstractGBFragmentActivity.java
@@ -0,0 +1,72 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, Carsten Pfeiffer, Daniele
+    Gobbetti
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities;
+
+import android.os.Bundle;
+
+import androidx.fragment.app.FragmentManager;
+import androidx.fragment.app.FragmentPagerAdapter;
+import androidx.fragment.app.FragmentStatePagerAdapter;
+import androidx.viewpager.widget.PagerAdapter;
+
+/**
+ * A base activity that supports paging through fragments by swiping.
+ * Subclasses will have to add a ViewPager to their layout and add something
+ * like this to hook it to the fragments:
+ * <p/>
+ * <pre>
+ * // Set up the ViewPager with the sections adapter.
+ * ViewPager viewPager = (ViewPager) findViewById(R.id.pager);
+ * viewPager.setAdapter(getPagerAdapter());
+ * </pre>
+ *
+ * @see AbstractGBFragment
+ */
+public abstract class AbstractGBFragmentActivity extends AbstractGBActivity {
+    /**
+     * The {@link PagerAdapter} that will provide
+     * fragments for each of the sections. We use a
+     * {@link FragmentPagerAdapter} derivative, which will keep every
+     * loaded fragment in memory. If this becomes too memory intensive, it
+     * may be best to switch to a
+     * {@link FragmentStatePagerAdapter}.
+     */
+    private AbstractFragmentPagerAdapter mSectionsPagerAdapter;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        // Create the adapter that will return a fragment for each of the three
+        // primary sections of the activity.
+        mSectionsPagerAdapter = createFragmentPagerAdapter(getSupportFragmentManager());
+    }
+
+    public AbstractFragmentPagerAdapter getPagerAdapter() {
+        return mSectionsPagerAdapter;
+    }
+
+    /**
+     * Creates a PagerAdapter that will create the fragments to be used with this
+     * activity. The fragments should typically extend AbstractGBFragment
+     *
+     * @param fragmentManager
+     * @return
+     */
+    protected abstract AbstractFragmentPagerAdapter createFragmentPagerAdapter(FragmentManager fragmentManager);
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AbstractListActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AbstractListActivity.java
new file mode 100644
index 0000000..be17e2d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AbstractListActivity.java
@@ -0,0 +1,78 @@
+/*  Copyright (C) 2017-2024 Andreas Shimokawa, Carsten Pfeiffer, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities;
+
+import android.os.Bundle;
+
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.adapter.AbstractActivityListingAdapter;
+
+public abstract class AbstractListActivity<T> extends AbstractGBActivity {
+    private AbstractActivityListingAdapter<T> itemAdapter;
+    private RecyclerView itemListView;
+
+    public void setItemAdapter(AbstractActivityListingAdapter<T> itemAdapter) {
+        this.itemAdapter = itemAdapter;
+        itemListView.setAdapter(itemAdapter);
+    }
+
+    protected void refresh() {
+        this.itemAdapter.loadItems();
+    }
+
+    public void setActivityKindFilter(int activityKind) {
+        this.itemAdapter.setActivityKindFilter(activityKind);
+    }
+
+    public void setDateFromFilter(long date) {
+        this.itemAdapter.setDateFromFilter(date);
+    }
+
+    public void setDateToFilter(long date) {
+        this.itemAdapter.setDateToFilter(date);
+    }
+
+    public void setNameContainsFilter(String name) {
+        this.itemAdapter.setNameContainsFilter(name);
+    }
+
+    public void setItemsFilter(List<Long> items) {
+        this.itemAdapter.setItemsFilter(items);
+    }
+
+    public void setDeviceFilter(long device) {
+        this.itemAdapter.setDeviceFilter(device);
+    }
+
+    public AbstractActivityListingAdapter<T> getItemAdapter() {
+        return itemAdapter;
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.activity_list);
+        itemListView = findViewById(R.id.itemListView);
+        itemListView.setLayoutManager(new LinearLayoutManager(this));
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AbstractPreferenceFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AbstractPreferenceFragment.java
new file mode 100644
index 0000000..9824d13
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AbstractPreferenceFragment.java
@@ -0,0 +1,259 @@
+/*  Copyright (C) 2023-2024 Andreas Shimokawa, Arjan Schrijver, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities;
+
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.text.InputType;
+import android.text.TextUtils;
+
+import androidx.annotation.NonNull;
+import androidx.fragment.app.DialogFragment;
+import androidx.preference.EditTextPreference;
+import androidx.preference.ListPreference;
+import androidx.preference.MultiSelectListPreference;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceCategory;
+import androidx.preference.PreferenceFragmentCompat;
+import androidx.preference.PreferenceGroup;
+import androidx.preference.PreferenceScreen;
+import androidx.preference.SeekBarPreference;
+import androidx.preference.SwitchPreferenceCompat;
+
+import com.mobeta.android.dslv.DragSortListPreference;
+import com.mobeta.android.dslv.DragSortListPreferenceFragment;
+
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.util.XDatePreference;
+import nodomain.freeyourgadget.gadgetbridge.util.XDatePreferenceFragment;
+import nodomain.freeyourgadget.gadgetbridge.util.XTimePreference;
+import nodomain.freeyourgadget.gadgetbridge.util.XTimePreferenceFragment;
+import nodomain.freeyourgadget.gadgetbridge.util.dialogs.MaterialEditTextPreferenceDialogFragment;
+import nodomain.freeyourgadget.gadgetbridge.util.dialogs.MaterialListPreferenceDialogFragment;
+import nodomain.freeyourgadget.gadgetbridge.util.dialogs.MaterialMultiSelectListPreferenceDialogFragment;
+import nodomain.freeyourgadget.gadgetbridge.util.preferences.MinMaxTextWatcher;
+
+public abstract class AbstractPreferenceFragment extends PreferenceFragmentCompat {
+    protected static final Logger LOG = LoggerFactory.getLogger(AbstractPreferenceFragment.class);
+
+    private final SharedPreferencesChangeHandler sharedPreferencesChangeHandler = new SharedPreferencesChangeHandler();
+
+    @Override
+    public void onStart() {
+        super.onStart();
+
+        final SharedPreferences sharedPreferences = getPreferenceManager().getSharedPreferences();
+
+        reloadPreferences(sharedPreferences, getPreferenceScreen());
+
+        sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferencesChangeHandler);
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+
+        updateActionBarTitle();
+    }
+
+    private void updateActionBarTitle() {
+        try {
+            CharSequence title = getPreferenceScreen().getTitle();
+            if (StringUtils.isBlank(title)) {
+                title = requireActivity().getTitle();
+            }
+            ((AbstractSettingsActivityV2) requireActivity()).setActionBarTitle(title);
+        } catch (final Exception e) {
+            LOG.error("Failed to update action bar title", e);
+        }
+    }
+
+    @Override
+    public void onStop() {
+        getPreferenceManager().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(sharedPreferencesChangeHandler);
+
+        super.onStop();
+    }
+
+    @Override
+    public void onDisplayPreferenceDialog(@NonNull final Preference preference) {
+        DialogFragment dialogFragment;
+        if (preference instanceof XTimePreference) {
+            dialogFragment = new XTimePreferenceFragment();
+        } else if (preference instanceof XDatePreference) {
+            dialogFragment = new XDatePreferenceFragment();
+        } else if (preference instanceof DragSortListPreference) {
+            dialogFragment = new DragSortListPreferenceFragment();
+        } else if (preference instanceof EditTextPreference) {
+            dialogFragment = MaterialEditTextPreferenceDialogFragment.newInstance(preference.getKey());
+        } else if (preference instanceof ListPreference) {
+            dialogFragment = MaterialListPreferenceDialogFragment.newInstance(preference.getKey());
+        } else if (preference instanceof MultiSelectListPreference) {
+            dialogFragment = MaterialMultiSelectListPreferenceDialogFragment.newInstance(preference.getKey());
+        } else {
+            super.onDisplayPreferenceDialog(preference);
+            return;
+        }
+        final Bundle bundle = new Bundle(1);
+        bundle.putString("key", preference.getKey());
+        dialogFragment.setArguments(bundle);
+        dialogFragment.setTargetFragment(this, 0);
+        if (getFragmentManager() != null) {
+            dialogFragment.show(getFragmentManager(), "androidx.preference.PreferenceFragment.DIALOG");
+        }
+    }
+
+    /**
+     * Keys of preferences which should print its values as a summary below the preference name.
+     */
+    protected Set<String> getPreferenceKeysWithSummary() {
+        return Collections.emptySet();
+    }
+
+    protected void onSharedPreferenceChanged(final Preference preference) {
+        // Nothing to do
+    }
+
+    public void setInputTypeFor(final String preferenceKey, final int editTypeFlags) {
+        final EditTextPreference textPreference = findPreference(preferenceKey);
+        if (textPreference != null) {
+            textPreference.setOnBindEditTextListener(editText -> editText.setInputType(editTypeFlags));
+        }
+    }
+
+    public void setNumericInputTypeWithRangeFor(final String preferenceKey, int min, int max, boolean allowEmpty) {
+        final EditTextPreference textPreference = findPreference(preferenceKey);
+        if (textPreference != null) {
+            textPreference.setOnBindEditTextListener(editText -> {
+                editText.setInputType(InputType.TYPE_CLASS_NUMBER);
+                editText.addTextChangedListener(new MinMaxTextWatcher(editText, min, max, allowEmpty));
+                editText.setSelection(editText.getText().length());
+            });
+        }
+    }
+
+    /**
+     * Reload the preferences in the current screen. This is needed when the user enters or exists a PreferenceScreen,
+     * otherwise the settings won't be reloaded by the {@link SharedPreferencesChangeHandler}, as the preferences return
+     * null, since they're not visible.
+     *
+     * @param sharedPreferences the {@link SharedPreferences} instance
+     * @param preferenceGroup   the {@link PreferenceGroup} for which preferences will be reloaded
+     */
+    private void reloadPreferences(final SharedPreferences sharedPreferences, final PreferenceGroup preferenceGroup) {
+        if (preferenceGroup == null) {
+            return;
+        }
+
+        for (int i = 0; i < preferenceGroup.getPreferenceCount(); i++) {
+            final Preference preference = preferenceGroup.getPreference(i);
+
+            LOG.debug("Reloading {}", preference.getKey());
+
+            if (preference instanceof PreferenceCategory) {
+                reloadPreferences(sharedPreferences, (PreferenceCategory) preference);
+                continue;
+            }
+
+            sharedPreferencesChangeHandler.onSharedPreferenceChanged(sharedPreferences, preference.getKey());
+        }
+    }
+
+    /**
+     * Handler for preference changes, update UI accordingly (if device updates the preferences).
+     */
+    private class SharedPreferencesChangeHandler implements SharedPreferences.OnSharedPreferenceChangeListener {
+        @Override
+        public void onSharedPreferenceChanged(final SharedPreferences prefs, final String key) {
+            LOG.debug("Preference changed: {}", key);
+
+            if (key == null) {
+                LOG.warn("Preference null, ignoring");
+                return;
+            }
+
+            final Preference preference = findPreference(key);
+            if (preference == null) {
+                LOG.warn("Preference {} not found", key);
+
+                return;
+            }
+
+            if (preference instanceof SeekBarPreference) {
+                final SeekBarPreference seekBarPreference = (SeekBarPreference) preference;
+                seekBarPreference.setValue(prefs.getInt(key, seekBarPreference.getValue()));
+            } else if (preference instanceof SwitchPreferenceCompat) {
+                final SwitchPreferenceCompat switchPreference = (SwitchPreferenceCompat) preference;
+                switchPreference.setChecked(prefs.getBoolean(key, switchPreference.isChecked()));
+            } else if (preference instanceof ListPreference) {
+                final ListPreference listPreference = (ListPreference) preference;
+                listPreference.setValue(prefs.getString(key, listPreference.getValue()));
+            } else if (preference instanceof EditTextPreference) {
+                final EditTextPreference editTextPreference = (EditTextPreference) preference;
+                editTextPreference.setText(prefs.getString(key, editTextPreference.getText()));
+            } else if (preference instanceof PreferenceScreen) {
+                // Ignoring
+            } else {
+                LOG.warn("Unknown preference class {} for {}, ignoring", preference.getClass(), key);
+            }
+
+            if (getPreferenceKeysWithSummary().contains(key)) {
+                final String summary;
+
+                // For multi select preferences, let's set the summary to the values, comma-delimited
+                if (preference instanceof MultiSelectListPreference) {
+                    final Set<String> prefSetValue = prefs.getStringSet(key, Collections.emptySet());
+                    if (prefSetValue.isEmpty()) {
+                        summary = requireContext().getString(R.string.not_set);
+                    } else {
+                        final MultiSelectListPreference multiSelectListPreference = (MultiSelectListPreference) preference;
+                        final CharSequence[] entries = multiSelectListPreference.getEntries();
+                        final CharSequence[] entryValues = multiSelectListPreference.getEntryValues();
+                        final List<String> translatedEntries = new ArrayList<>();
+                        for (int i = 0; i < entryValues.length; i++) {
+                            if (prefSetValue.contains(entryValues[i].toString())) {
+                                translatedEntries.add(entries[i].toString());
+                            }
+                        }
+                        summary = TextUtils.join(", ", translatedEntries);
+                    }
+                } else {
+                    summary = prefs.getString(key, preference.getSummary() != null ? preference.getSummary().toString() : "");
+                }
+
+                if (preference.getSummaryProvider() != null) {
+                    try {
+                        preference.setSummary(summary);
+                    } catch (final IllegalStateException e) {
+                        LOG.error("Failed to set preference summary for {}", key, e);
+                    }
+                }
+            }
+
+            AbstractPreferenceFragment.this.onSharedPreferenceChanged(preference);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AbstractSettingsActivityV2.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AbstractSettingsActivityV2.java
new file mode 100644
index 0000000..5ea4b9d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AbstractSettingsActivityV2.java
@@ -0,0 +1,92 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities;
+
+import android.os.Bundle;
+import android.view.MenuItem;
+
+import androidx.annotation.NonNull;
+import androidx.appcompat.app.ActionBar;
+import androidx.fragment.app.Fragment;
+import androidx.preference.PreferenceFragmentCompat;
+import androidx.preference.PreferenceScreen;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+
+public abstract class AbstractSettingsActivityV2 extends AbstractGBActivity implements
+        PreferenceFragmentCompat.OnPreferenceStartScreenCallback {
+
+    protected abstract String fragmentTag();
+    protected abstract PreferenceFragmentCompat newFragment();
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_device_settings);
+        if (savedInstanceState == null) {
+            Fragment fragment = getSupportFragmentManager().findFragmentByTag(fragmentTag());
+            if (fragment == null) {
+                fragment = newFragment();
+            }
+            getSupportFragmentManager()
+                    .beginTransaction()
+                    .replace(R.id.settings_container, fragment, fragmentTag())
+                    .commit();
+        }
+    }
+
+    @Override
+    public boolean onPreferenceStartScreen(final PreferenceFragmentCompat caller, final PreferenceScreen preferenceScreen) {
+        final PreferenceFragmentCompat fragment = newFragment();
+        final Bundle args;
+        if (fragment.getArguments() != null) {
+            args = fragment.getArguments();
+        } else {
+            args = new Bundle();
+        }
+        args.putString(PreferenceFragmentCompat.ARG_PREFERENCE_ROOT, preferenceScreen.getKey());
+        fragment.setArguments(args);
+
+        getSupportFragmentManager()
+                .beginTransaction()
+                .replace(R.id.settings_container, fragment, preferenceScreen.getKey())
+                .addToBackStack(preferenceScreen.getKey())
+                .commit();
+
+        return true;
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(@NonNull final MenuItem item) {
+        final int itemId = item.getItemId();
+        if (itemId == android.R.id.home) {
+            // Simulate a back press, so that we don't actually exit the activity when
+            // in a nested PreferenceScreen
+            this.onBackPressed();
+            return true;
+        }
+
+        return super.onOptionsItemSelected(item);
+    }
+
+    public void setActionBarTitle(final CharSequence title) {
+        final ActionBar actionBar = getSupportActionBar();
+        if (actionBar != null) {
+            actionBar.setTitle(title);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ActivitySummariesActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ActivitySummariesActivity.java
new file mode 100644
index 0000000..4acc2a7
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ActivitySummariesActivity.java
@@ -0,0 +1,472 @@
+/*  Copyright (C) 2017-2024 Andreas Shimokawa, Arjan Schrijver, Carsten
+    Pfeiffer, Daniele Gobbetti, Jos茅 Rebelo, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities;
+
+import android.app.DatePickerDialog;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.SharedPreferences;
+import android.content.res.Resources;
+import android.net.Uri;
+import android.os.Bundle;
+import android.util.SparseBooleanArray;
+import android.util.TypedValue;
+import android.view.ActionMode;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.AbsListView;
+import android.widget.DatePicker;
+import android.widget.ListView;
+import android.widget.Toast;
+
+import androidx.core.content.FileProvider;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
+
+import com.google.android.material.dialog.MaterialAlertDialogBuilder;
+import com.google.android.material.floatingactionbutton.FloatingActionButton;
+
+import java.io.File;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.BitSet;
+import java.util.Calendar;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Objects;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.adapter.ActivitySummariesAdapter;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummary;
+import nodomain.freeyourgadget.gadgetbridge.model.RecordedDataTypes;
+import nodomain.freeyourgadget.gadgetbridge.util.ActivitySummaryUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+
+public class ActivitySummariesActivity extends AbstractListActivity<BaseActivitySummary> {
+    static final int ACTIVITY_FILTER = 1;
+    static final int ACTIVITY_DETAIL = 11;
+    HashMap<String, ActivityKind> activityKindMap = new HashMap<>(0);
+    int activityFilter = 0;
+    long dateFromFilter = 0;
+    long dateToFilter = 0;
+    long deviceFilter;
+    List<Long> itemsFilter;
+    String nameContainsFilter;
+    private GBDevice mGBDevice;
+    private BitSet selectedItems;
+    private SwipeRefreshLayout swipeLayout;
+    private ActionMode mActionMode;
+
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            switch (Objects.requireNonNull(action)) {
+                case GBDevice.ACTION_DEVICE_CHANGED:
+                    GBDevice device = intent.getParcelableExtra(GBDevice.EXTRA_DEVICE);
+                    mGBDevice = device;
+                    if (device.isBusy()) {
+                        swipeLayout.setRefreshing(true);
+                    } else {
+                        boolean wasBusy = swipeLayout.isRefreshing();
+                        swipeLayout.setRefreshing(false);
+                        if (wasBusy) {
+                            refresh();
+                        }
+                    }
+                    break;
+            }
+        }
+    };
+    private int subtrackDashboard = 0;
+
+    public static int getBackgroundColor(Context context) {
+        TypedValue typedValue = new TypedValue();
+        Resources.Theme theme = context.getTheme();
+        theme.resolveAttribute(R.attr.sports_activity_summary_background, typedValue, true);
+        return typedValue.data;
+    }
+
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        super.onCreateOptionsMenu(menu);
+
+        getMenuInflater().inflate(R.menu.activity_list_menu, menu);
+
+        return true;
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(final MenuItem item) {
+        final int itemId = item.getItemId();
+        if (itemId == android.R.id.home) {
+            // back button, close drawer if open, otherwise exit
+            finish();
+            return true;
+        } else if (itemId == R.id.activity_action_manage_timestamp) {
+            resetFetchTimestampToChosenDate();
+            return true;
+        } else if (itemId == R.id.activity_action_filter) {
+            runFilterActivity();
+            return true;
+        }
+
+        return false;
+    }
+
+    @Override
+    public void onActivityResult(int requestCode, int resultCode, Intent resultData) {
+        super.onActivityResult(requestCode, resultCode, resultData);
+        if (requestCode == ACTIVITY_FILTER && resultData != null) {
+            Bundle bundle = resultData.getExtras();
+            activityFilter = bundle.getInt("activityFilter", 0);
+            dateFromFilter = bundle.getLong("dateFromFilter", 0);
+            dateToFilter = bundle.getLong("dateToFilter", 0);
+            deviceFilter = bundle.getLong("deviceFilter", 0);
+            nameContainsFilter = bundle.getString("nameContainsFilter");
+            itemsFilter = (List<Long>) bundle.getSerializable("itemsFilter");
+            setActivityKindFilter(activityFilter);
+            setDateFromFilter(dateFromFilter);
+            setDateToFilter(dateToFilter);
+            setNameContainsFilter(nameContainsFilter);
+            setItemsFilter(itemsFilter);
+            setDeviceFilter(deviceFilter);
+            refresh();
+        }
+        if (requestCode == ACTIVITY_DETAIL) {
+            refresh();
+        }
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        Bundle extras = getIntent().getExtras();
+        if (extras != null) {
+            mGBDevice = extras.getParcelable(GBDevice.EXTRA_DEVICE);
+        } else {
+            throw new IllegalArgumentException("Must provide a device when invoking this activity");
+        }
+        deviceFilter = getDeviceId(mGBDevice);
+        IntentFilter filterLocal = new IntentFilter();
+        filterLocal.addAction(GBDevice.ACTION_DEVICE_CHANGED);
+        LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, filterLocal);
+
+        super.onCreate(savedInstanceState);
+
+        ActivitySummariesAdapter activitySummariesAdapter = new ActivitySummariesAdapter(this, mGBDevice, activityFilter, dateFromFilter, dateToFilter, nameContainsFilter, deviceFilter, itemsFilter);
+        selectedItems = activitySummariesAdapter.getSelectedItems();
+
+        activitySummariesAdapter.setOnItemClickListener(position -> {
+            if (!selectedItems.isEmpty()) {
+                selectedItems.set(position, !selectedItems.get(position));
+                activitySummariesAdapter.notifyItemChanged(position);
+                if (!selectedItems.isEmpty()) {
+                    startActionMode();
+                } else {
+                    stopActionMode();
+                }
+                return;
+            }
+            if (position == 0) return; // item 0 is empty for dashboard
+            ActivitySummary summary = activitySummariesAdapter.getItem(position);
+            if (summary != null) {
+                try {
+                    showActivityDetail(position);
+                } catch (Exception e) {
+                    GB.toast(getApplicationContext(), "Unable to display Activity Detail, maybe the activity is not available yet: " + e.getMessage(), Toast.LENGTH_LONG, GB.ERROR, e);
+                }
+            }
+        });
+        activitySummariesAdapter.setOnItemLongClickListener(position -> {
+            selectedItems.set(position, !selectedItems.get(position));
+            activitySummariesAdapter.notifyItemChanged(position);
+
+            if (!selectedItems.isEmpty()) {
+                startActionMode();
+            } else {
+                stopActionMode();
+            }
+        });
+        setItemAdapter(activitySummariesAdapter);
+
+        swipeLayout = findViewById(R.id.list_activity_swipe_layout);
+        swipeLayout.setOnRefreshListener(this::fetchTrackData);
+
+        FloatingActionButton fab = findViewById(R.id.fab);
+        fab.setOnClickListener(v -> fetchTrackData());
+
+        activityKindMap = fillKindMap();
+    }
+
+    private void stopActionMode() {
+        if (mActionMode != null) {
+            mActionMode.finish();
+            mActionMode = null;
+        }
+    }
+
+    private void startActionMode() {
+        int[] numSelected = new int[]{0};
+        for (int i = 0; i < selectedItems.length(); i++) {
+            if (selectedItems.get(i)) {
+                numSelected[0]++;
+            }
+        }
+        if (mActionMode != null) {
+            // already in action mode
+            mActionMode.setTitle(getString(R.string.number_selected_items, numSelected[0]));
+            return;
+        }
+
+        mActionMode = startActionMode(new ActionMode.Callback() {
+            @Override
+            public boolean onCreateActionMode(final ActionMode mode, final Menu menu) {
+                mode.setTitle(getString(R.string.number_selected_items, numSelected[0]));
+
+                getMenuInflater().inflate(R.menu.activity_list_context_menu, menu);
+                findViewById(R.id.fab).setVisibility(View.INVISIBLE);
+                return true;
+            }
+
+            @Override
+            public boolean onPrepareActionMode(final ActionMode mode, final Menu menu) {
+                return false;
+            }
+
+            @Override
+            public boolean onActionItemClicked(final ActionMode actionMode, final MenuItem menuItem) {
+                boolean processed = false;
+                final int itemId = menuItem.getItemId();
+                if (itemId == R.id.activity_action_delete) {
+                    final List<BaseActivitySummary> toDelete = new ArrayList<>();
+                    for (int i = 0; i < selectedItems.length(); i++) {
+                        if (selectedItems.get(i)) {
+                            toDelete.add(getItemAdapter().getItem(i));
+                        }
+                    }
+
+                    new MaterialAlertDialogBuilder(ActivitySummariesActivity.this)
+                            .setTitle(ActivitySummariesActivity.this.getString(R.string.sports_activity_confirm_delete_title, toDelete.size()))
+                            .setMessage(ActivitySummariesActivity.this.getString(R.string.sports_activity_confirm_delete_description, toDelete.size()))
+                            .setIcon(R.drawable.ic_delete_forever)
+                            .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
+                                public void onClick(final DialogInterface dialog, final int whichButton) {
+                                    deleteItems(toDelete);
+                                }
+                            })
+                            .setNegativeButton(android.R.string.no, null)
+                            .show();
+
+                    processed = true;
+                } else if (itemId == R.id.activity_action_export) {
+                    final List<String> paths = new ArrayList<>();
+
+                    for (int i = 0; i < selectedItems.length(); i++) {
+                        if (selectedItems.get(i)) {
+                            BaseActivitySummary summary = getItemAdapter().getItem(i);
+                            if (summary != null) {
+                                File gpxTrack = ActivitySummaryUtils.getGpxFile(summary);
+                                if (gpxTrack != null) {
+                                    paths.add(gpxTrack.getPath());
+                                }
+                            }
+                        }
+                    }
+                    shareMultiple(paths);
+                    processed = true;
+                } else if (itemId == R.id.activity_action_select_all) {
+                    for (int i = 1; i < getItemAdapter().getItemCount() - 1; i++) {
+                        if (!selectedItems.get(i)) {
+                            numSelected[0]++;
+                            selectedItems.set(i, true);
+                            getItemAdapter().notifyItemChanged(i);
+                        }
+                    }
+                    actionMode.setTitle(getString(R.string.number_selected_items, numSelected[0]));
+                    return true; //don't finish actionMode in this case!
+                } else if (itemId == R.id.activity_action_addto_filter) {
+                    final List<Long> toFilter = new ArrayList<>();
+                    for (int i = 0; i < selectedItems.length(); i++) {
+                        if (selectedItems.get(i)) {
+                            BaseActivitySummary summary = getItemAdapter().getItem(i);
+                            if (summary != null && summary.getId() != null) {
+                                Long id = summary.getId();
+                                toFilter.add(id);
+                            }
+                        }
+                    }
+                    itemsFilter = toFilter;
+                    setItemsFilter(itemsFilter);
+                    refresh();
+
+                    processed = true;
+                }
+                actionMode.finish();
+                mActionMode = null;
+                return processed;
+            }
+
+            @Override
+            public void onDestroyActionMode(final ActionMode mode) {
+                mActionMode = null;
+                for (int i = 0; i < selectedItems.length(); i++) {
+                    if (selectedItems.get(i)) {
+                        selectedItems.set(i, false);
+                        getItemAdapter().notifyItemChanged(i);
+                    }
+                }
+                findViewById(R.id.fab).setVisibility(View.VISIBLE);
+            }
+        });
+    }
+
+    private LinkedHashMap<String, ActivityKind> fillKindMap() {
+        LinkedHashMap<String, ActivityKind> newMap = new LinkedHashMap<>(0); //reset
+
+        newMap.put(getString(R.string.activity_summaries_all_activities), ActivityKind.UNKNOWN);
+        for (BaseActivitySummary item : getItemAdapter().getItems()) {
+            String activityName = ActivityKind.fromCode(item.getActivityKind()).getLabel(this);
+            if (!newMap.containsKey(activityName) && item.getActivityKind() != 0) {
+                newMap.put(activityName, ActivityKind.fromCode(item.getActivityKind()));
+            }
+        }
+        return newMap;
+    }
+
+    public void resetFetchTimestampToChosenDate() {
+        final Calendar currentDate = Calendar.getInstance();
+        new DatePickerDialog(this, new DatePickerDialog.OnDateSetListener() {
+            @Override
+            public void onDateSet(DatePicker view, int year, int monthOfYear, int dayOfMonth) {
+                Calendar date = Calendar.getInstance();
+                date.set(year, monthOfYear, dayOfMonth);
+
+                long timestamp = date.getTimeInMillis() - 1000;
+                SharedPreferences.Editor editor = GBApplication.getDeviceSpecificSharedPrefs(mGBDevice.getAddress()).edit();
+                editor.remove("lastSportsActivityTimeMillis"); //FIXME: key reconstruction is BAD
+                editor.putLong("lastSportsActivityTimeMillis", timestamp);
+                editor.apply();
+            }
+        }, currentDate.get(Calendar.YEAR), currentDate.get(Calendar.MONTH), currentDate.get(Calendar.DATE)).show();
+    }
+
+    @Override
+    protected void onDestroy() {
+        LocalBroadcastManager.getInstance(this).unregisterReceiver(mReceiver);
+        super.onDestroy();
+    }
+
+    private void deleteItems(List<BaseActivitySummary> items) {
+        for (BaseActivitySummary item : items) {
+            try {
+                item.delete();
+            } catch (Exception e) {
+                //pass delete error
+            }
+        }
+        // Adapter is fully reloaded after refresh
+        refresh();
+    }
+
+    private void fetchTrackData() {
+        if (mGBDevice.isInitialized() && !mGBDevice.isBusy()) {
+            swipeLayout.setRefreshing(true);
+            GBApplication.deviceService(mGBDevice).onFetchRecordedData(RecordedDataTypes.TYPE_GPS_TRACKS);
+        } else {
+            swipeLayout.setRefreshing(false);
+            if (!mGBDevice.isInitialized()) {
+                GB.toast(this, getString(R.string.device_not_connected), Toast.LENGTH_SHORT, GB.ERROR);
+            }
+        }
+    }
+
+    private void shareMultiple(List<String> paths) {
+
+        ArrayList<Uri> uris = new ArrayList<>();
+        for (String path : paths) {
+            File file = new File(path);
+            uris.add(FileProvider.getUriForFile(this, getApplicationContext().getPackageName() + ".screenshot_provider", file));
+        }
+
+        if (!uris.isEmpty()) {
+            final Intent intent = new Intent(Intent.ACTION_SEND_MULTIPLE);
+            intent.setType("application/gpx+xml");
+            intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris);
+            startActivity(Intent.createChooser(intent, "SHARE"));
+        } else {
+            GB.toast(this, "No selected activity contains a GPX track to share", Toast.LENGTH_SHORT, GB.ERROR);
+        }
+    }
+
+    private void showActivityDetail(int position) {
+        Intent ActivitySummaryDetailIntent = new Intent(this, ActivitySummaryDetail.class);
+        Bundle bundle = new Bundle();
+
+        bundle.putInt("position", position);
+        bundle.putSerializable("activityKindMap", activityKindMap);
+        bundle.putSerializable("itemsFilter", (Serializable) itemsFilter);
+        bundle.putInt("activityFilter", activityFilter);
+        bundle.putLong("dateFromFilter", dateFromFilter);
+        bundle.putLong("dateToFilter", dateToFilter);
+        bundle.putLong("deviceFilter", deviceFilter);
+        bundle.putLong("initial_deviceFilter", getDeviceId(mGBDevice));
+        bundle.putString("nameContainsFilter", nameContainsFilter);
+        ActivitySummaryDetailIntent.putExtras(bundle);
+
+        ActivitySummaryDetailIntent.putExtra(GBDevice.EXTRA_DEVICE, mGBDevice);
+        startActivityForResult(ActivitySummaryDetailIntent, ACTIVITY_DETAIL);
+    }
+
+    private void runFilterActivity() {
+        Intent filterIntent = new Intent(this, ActivitySummariesFilter.class);
+        Bundle bundle = new Bundle();
+        bundle.putSerializable("activityKindMap", activityKindMap);
+        bundle.putSerializable("itemsFilter", (Serializable) itemsFilter);
+        bundle.putInt("activityFilter", activityFilter);
+        bundle.putLong("dateFromFilter", dateFromFilter);
+        bundle.putLong("dateToFilter", dateToFilter);
+        bundle.putLong("deviceFilter", deviceFilter);
+        bundle.putLong("initial_deviceFilter", getDeviceId(mGBDevice));
+        bundle.putString("nameContainsFilter", nameContainsFilter);
+        filterIntent.putExtras(bundle);
+        startActivityForResult(filterIntent, ACTIVITY_FILTER);
+    }
+
+    private long getDeviceId(GBDevice device) {
+        try (DBHandler handler = GBApplication.acquireDB()) {
+            Device dbDevice = DBHelper.findDevice(device, handler.getDaoSession());
+            return dbDevice.getId();
+        } catch (Exception e) {
+        }
+        return 0;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ActivitySummariesChartFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ActivitySummariesChartFragment.java
new file mode 100644
index 0000000..256a499
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ActivitySummariesChartFragment.java
@@ -0,0 +1,313 @@
+/*  Copyright (C) 2020-2024 Jos茅 Rebelo, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.Nullable;
+
+import com.github.mikephil.charting.charts.Chart;
+import com.github.mikephil.charting.charts.LineChart;
+import com.github.mikephil.charting.components.Legend;
+import com.github.mikephil.charting.components.LegendEntry;
+import com.github.mikephil.charting.components.XAxis;
+import com.github.mikephil.charting.components.YAxis;
+import com.github.mikephil.charting.data.Entry;
+import com.github.mikephil.charting.data.LineData;
+import com.github.mikephil.charting.data.LineDataSet;
+import com.github.mikephil.charting.formatter.ValueFormatter;
+import com.github.mikephil.charting.interfaces.datasets.ILineDataSet;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.charts.AbstractActivityChartFragment;
+import nodomain.freeyourgadget.gadgetbridge.activities.charts.ChartsData;
+import nodomain.freeyourgadget.gadgetbridge.activities.charts.ChartsHost;
+import nodomain.freeyourgadget.gadgetbridge.activities.charts.DefaultChartsData;
+import nodomain.freeyourgadget.gadgetbridge.activities.charts.SampleXLabelFormatter;
+import nodomain.freeyourgadget.gadgetbridge.activities.charts.TimestampTranslation;
+import nodomain.freeyourgadget.gadgetbridge.database.DBAccess;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityPoint;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
+
+
+public class ActivitySummariesChartFragment extends AbstractActivityChartFragment<ChartsData> {
+    private static final Logger LOG = LoggerFactory.getLogger(ActivitySummariesChartFragment.class);
+
+    private LineChart mChart;
+    private View view;
+
+    // If a track file is being used (takes precedence over activity data)
+    private File trackFile;
+
+    // If activity data is being used
+    private GBDevice gbDevice;
+    private int startTime;
+    private int endTime;
+
+    public void setDateAndGetData(@Nullable File trackFile, GBDevice gbDevice, long startTime, long endTime) {
+        this.trackFile = trackFile;
+        this.startTime = (int) startTime;
+        this.endTime = (int) endTime;
+        this.gbDevice = gbDevice;
+        if (this.view != null) {
+            createLocalRefreshTask("getting hr and activity", getActivity()).execute();
+        }
+    }
+
+    protected RefreshTask createLocalRefreshTask(String task, Context context) {
+        return new RefreshTask(task, context);
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+                             Bundle savedInstanceState) {
+        View rootView = inflater.inflate(R.layout.fragment_charts, container, false);
+        mChart = rootView.findViewById(R.id.activitysleepchart);
+        return rootView;
+    }
+
+    @Override
+    public void onViewCreated(final View view, @Nullable Bundle savedInstanceState) {
+        super.onViewCreated(view, savedInstanceState);
+        init();
+        this.view = view;
+        if (this.trackFile != null || this.gbDevice != null) {
+            setupChart();
+            createLocalRefreshTask("getting hr and activity", getActivity()).execute();
+        }
+    }
+
+
+    @Override
+    public String getTitle() {
+        return "";
+    }
+
+    private void setupChart() {
+        mChart.setBackgroundColor(BACKGROUND_COLOR);
+        mChart.getDescription().setTextColor(DESCRIPTION_COLOR);
+        configureBarLineChartDefaults(mChart);
+
+        XAxis x = mChart.getXAxis();
+        x.setDrawLabels(true);
+        x.setDrawGridLines(false);
+        x.setEnabled(true);
+        x.setTextColor(CHART_TEXT_COLOR);
+        x.setDrawLimitLinesBehindData(true);
+
+        YAxis y = mChart.getAxisLeft();
+        y.setDrawGridLines(false);
+//        y.setDrawLabels(false);
+        // TODO: make fixed max value optional
+        y.setAxisMaximum(1f);
+        y.setAxisMinimum(0);
+        y.setDrawTopYLabelEntry(false);
+        y.setTextColor(CHART_TEXT_COLOR);
+
+//        y.setLabelCount(5);
+        y.setEnabled(true);
+
+        YAxis yAxisRight = mChart.getAxisRight();
+        yAxisRight.setDrawGridLines(false);
+        yAxisRight.setEnabled(supportsHeartrate(gbDevice));
+        yAxisRight.setDrawLabels(true);
+        yAxisRight.setDrawTopYLabelEntry(true);
+        yAxisRight.setTextColor(CHART_TEXT_COLOR);
+        yAxisRight.setAxisMaximum(HeartRateUtils.getInstance().getMaxHeartRate());
+        yAxisRight.setAxisMinimum(HeartRateUtils.getInstance().getMinHeartRate());
+
+    }
+
+    @Override
+    protected List<? extends ActivitySample> getSamples(DBHandler db, GBDevice device, int tsFrom, int tsTo) {
+        return getAllSamples(db, device, tsFrom, tsTo);
+    }
+
+    @Override
+    protected List<? extends ActivitySample> getSamplesHighRes(DBHandler db, GBDevice device, int tsFrom, int tsTo) {
+        return getAllSamplesHighRes(db, device, tsFrom, tsTo);
+    }
+
+    @Override
+    protected void setupLegend(Chart<?> chart) {
+        List<LegendEntry> legendEntries = new ArrayList<>(5);
+
+        LegendEntry activityEntry = new LegendEntry();
+        activityEntry.label = akActivity.label;
+        activityEntry.formColor = akActivity.color;
+        legendEntries.add(activityEntry);
+
+        if (supportsHeartrate(gbDevice)) {
+            LegendEntry hrEntry = new LegendEntry();
+            hrEntry.label = HEARTRATE_LABEL;
+            hrEntry.formColor = HEARTRATE_COLOR;
+            legendEntries.add(hrEntry);
+        }
+
+        chart.getLegend().setCustom(legendEntries);
+        chart.getLegend().setTextColor(LEGEND_TEXT_COLOR);
+        chart.getLegend().setWordWrapEnabled(true);
+        chart.getLegend().setHorizontalAlignment(Legend.LegendHorizontalAlignment.CENTER);
+    }
+
+    @Override
+    protected ChartsData refreshInBackground(ChartsHost chartsHost, DBHandler db, GBDevice device) {
+        return null;
+    }
+
+    @Override
+    protected void renderCharts() {
+    }
+
+    protected Entry createLineEntry(float value, int xValue) {
+        return new Entry(xValue, value);
+    }
+
+    @Override
+    protected void updateChartsnUIThread(ChartsData chartsData) {
+    }
+
+    public class RefreshTask extends DBAccess {
+
+        public RefreshTask(String task, Context context) {
+            super(task, context);
+        }
+
+        @Override
+        protected void doInBackground(DBHandler handler) {
+            final DefaultChartsData<?> dcd;
+            final DefaultChartsData<LineData> activitySamplesData = buildChartFromSamples(handler);
+
+            if (trackFile != null) {
+                final List<ActivityPoint> activityPoints = ActivitySummariesGpsFragment.getActivityPoints(trackFile)
+                        .stream()
+                        .filter(ap -> ap.getHeartRate() > 0)
+                        .collect(Collectors.toList());
+
+                if (!activityPoints.isEmpty()) {
+                    dcd = buildHeartRateChart(activityPoints, activitySamplesData);
+                } else {
+                    dcd = activitySamplesData;
+                }
+            } else {
+                dcd = activitySamplesData;
+            }
+
+            if (dcd != null) {
+                mChart.setData(null); // workaround for https://github.com/PhilJay/MPAndroidChart/issues/2317
+                mChart.getXAxis().setValueFormatter(dcd.getXValueFormatter());
+                mChart.setData((LineData) dcd.getData());
+            }
+        }
+
+        @Override
+        protected void onPostExecute(Object o) {
+            mChart.invalidate();
+        }
+
+        private DefaultChartsData<LineData> buildChartFromSamples(DBHandler handler) {
+            final List<? extends ActivitySample> samples = getAllSamples(handler, gbDevice, startTime, endTime);
+            final List<? extends ActivitySample> highResSamples = getAllSamplesHighRes(handler, gbDevice, startTime, endTime);
+
+            try {
+                if (highResSamples == null)
+                    return refresh(gbDevice, samples);
+                return refresh(gbDevice, samples, highResSamples);
+            } catch (Exception e) {
+                LOG.error("Unable to get charts data right now", e);
+            }
+
+            return null;
+        }
+
+        private DefaultChartsData<LineData> buildHeartRateChart(final List<ActivityPoint> activityPoints,
+                                                                final DefaultChartsData<LineData> activitySamplesData) {
+            // If we have data from activity samples, we need to use the same TimestampTranslation so
+            // that the HR chart is aligned
+            // This is not ideal...
+            final TimestampTranslation tsTranslation;
+            if (activitySamplesData != null) {
+                final ValueFormatter xValueFormatter = activitySamplesData.getXValueFormatter();
+                if (xValueFormatter instanceof SampleXLabelFormatter) {
+                    tsTranslation = ((SampleXLabelFormatter) xValueFormatter).getTsTranslation();
+                } else {
+                    LOG.error("Unable to get TimestampTranslation from x value formatter - class changed?");
+                    tsTranslation = new TimestampTranslation();
+                }
+            } else {
+                tsTranslation = new TimestampTranslation();
+            }
+
+            final List<Entry> heartRateEntries = new ArrayList<>(activityPoints.size());
+            final List<ILineDataSet> heartRateDataSets = new ArrayList<>();
+            int lastTsShorten = 0;
+            for (final ActivityPoint activityPoint : activityPoints) {
+                int tsShorten = tsTranslation.shorten((int) (activityPoint.getTime().getTime() / 1000));
+                if (lastTsShorten == 0 || (tsShorten - lastTsShorten) <= 60 * HeartRateUtils.MAX_HR_MEASUREMENTS_GAP_MINUTES) {
+                    heartRateEntries.add(new Entry(tsShorten, activityPoint.getHeartRate()));
+                } else {
+                    if (!heartRateEntries.isEmpty()) {
+                        List<Entry> clone = new ArrayList<>(heartRateEntries.size());
+                        clone.addAll(heartRateEntries);
+                        heartRateDataSets.add(createHeartrateSet(clone, "Heart Rate"));
+                        heartRateEntries.clear();
+                    }
+                }
+                lastTsShorten = tsShorten;
+                heartRateEntries.add(new Entry(tsShorten, activityPoint.getHeartRate()));
+            }
+            if (!heartRateEntries.isEmpty()) {
+                heartRateDataSets.add(createHeartrateSet(heartRateEntries, "Heart Rate"));
+            }
+
+            if (activitySamplesData != null) {
+                // if we have activity samples, replace the heart rate dataset
+                LineData data = activitySamplesData.getData();
+                List<ILineDataSet> dataSets = data.getDataSets();
+                for (final ILineDataSet dataSet : dataSets) {
+                    if ("Heart Rate".equals(dataSet.getLabel())) {
+                        dataSets.remove(dataSet);
+                        dataSets.addAll(heartRateDataSets);
+                        return activitySamplesData;
+                    }
+                }
+                // We failed to find a heart rate dataset. We can't append ours, or it will crash
+                //dataSets.add(heartRateSet);
+                return activitySamplesData;
+            } else {
+                final LineData lineData = new LineData(heartRateDataSets);
+                final ValueFormatter xValueFormatter = new SampleXLabelFormatter(tsTranslation, "HH:mm");
+                return new DefaultChartsData<>(lineData, xValueFormatter);
+            }
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ActivitySummariesFilter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ActivitySummariesFilter.java
new file mode 100644
index 0000000..17521a4
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ActivitySummariesFilter.java
@@ -0,0 +1,481 @@
+/*  Copyright (C) 2020-2024 Daniel Dakhno, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities;
+
+import android.app.DatePickerDialog;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Color;
+import android.graphics.LightingColorFilter;
+import android.os.Bundle;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.util.Pair;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.DatePicker;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+import android.widget.Spinner;
+import android.widget.TextView;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.adapter.SpinnerWithIconAdapter;
+import nodomain.freeyourgadget.gadgetbridge.adapter.SpinnerWithIconItem;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
+
+
+public class ActivitySummariesFilter extends AbstractGBActivity {
+    private static final Logger LOG = LoggerFactory.getLogger(ActivitySummariesFilter.class);
+    private static final String DATE_FILTER_FROM = "dateFromFilter";
+    private static final String DATE_FILTER_TO = "dateToFilter";
+    public static long ALL_DEVICES = 999;
+    int activityFilter = 0;
+    long dateFromFilter = 0;
+    long dateToFilter = 0;
+    String nameContainsFilter;
+    HashMap<String, ActivityKind> activityKindMap = new HashMap<>(1);
+    List<Long> itemsFilter;
+    long deviceFilter;
+    long initial_deviceFilter;
+    int BACKGROUND_COLOR;
+    LinkedHashMap<String, Pair<Long, Integer>> allDevices;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        Bundle bundle = this.getIntent().getExtras();
+
+        activityKindMap = (HashMap<String, ActivityKind>) bundle.getSerializable("activityKindMap");
+        itemsFilter = (List<Long>) bundle.getSerializable("itemsFilter");
+        activityFilter = bundle.getInt("activityFilter", 0);
+        dateFromFilter = bundle.getLong("dateFromFilter", 0);
+        dateToFilter = bundle.getLong("dateToFilter", 0);
+        initial_deviceFilter = bundle.getLong("initial_deviceFilter", 0);
+        deviceFilter = bundle.getLong("deviceFilter", 0);
+        nameContainsFilter = bundle.getString("nameContainsFilter");
+
+        Context appContext = this.getApplicationContext();
+        if (appContext instanceof GBApplication) {
+            setContentView(R.layout.sport_activity_filter);
+        }
+        BACKGROUND_COLOR = GBApplication.getBackgroundColor(appContext);
+
+        allDevices = getAllDevices(appContext);
+
+        //device filter spinner
+        final Spinner deviceFilterSpinner = findViewById(R.id.select_device);
+        ArrayList<SpinnerWithIconItem> filterDevicesArray = new ArrayList<>();
+        for (Map.Entry<String, Pair<Long, Integer>> item : allDevices.entrySet()) {
+            filterDevicesArray.add(new SpinnerWithIconItem(item.getKey(), item.getValue().first, item.getValue().second));
+        }
+        final SpinnerWithIconAdapter filterDevicesAdapter = new SpinnerWithIconAdapter(this,
+                R.layout.spinner_with_image_layout, R.id.spinner_item_text, filterDevicesArray);
+        deviceFilterSpinner.setAdapter(filterDevicesAdapter);
+        deviceFilterSpinner.setSelection(filterDevicesAdapter.getItemPositionForSelection(getDeviceById(deviceFilter)));
+        addListenerOnSpinnerDeviceSelection();
+
+        //Kind filter spinner - assign data, set selected item...
+        final Spinner filterKindSpinner = findViewById(R.id.select_kind);
+        ArrayList<SpinnerWithIconItem> kindArray = new ArrayList<>();
+
+        for (Map.Entry<String, ActivityKind> item : activityKindMap.entrySet()) {
+            if (item.getValue() == ActivityKind.UNKNOWN) continue; //do not put here All devices, but we do need them in the array
+            kindArray.add(new SpinnerWithIconItem(item.getKey(), (long) item.getValue().getCode(), item.getValue().getIcon()));
+        }
+
+        kindArray.sort((o1, o2) -> o1.getText().compareToIgnoreCase(o2.getText()));
+
+        //ensure that all items is always first in the list, this is an issue on old android
+        SpinnerWithIconItem allActivities = new SpinnerWithIconItem(getString(R.string.activity_summaries_all_activities), (long) ActivityKind.UNKNOWN.getCode(), ActivityKind.UNKNOWN.getIcon());
+        kindArray.add(0, allActivities);
+
+        SpinnerWithIconAdapter adapter = new SpinnerWithIconAdapter(this,
+                R.layout.spinner_with_image_layout, R.id.spinner_item_text, kindArray);
+
+        SpinnerWithIconItem selectedActivity = getKindByValue(activityFilter);
+        int selectedPosition = adapter.getItemPositionForSelection(selectedActivity);
+
+        filterKindSpinner.setAdapter(adapter);
+        filterKindSpinner.setSelection(selectedPosition);
+        addListenerOnSpinnerKindSelection();
+
+
+        //quick date filter selection
+        final Spinner quick_filter_period_select = findViewById(R.id.quick_filter_period_select);
+
+        ArrayList<String> activity_filter_quick_filter_period_items = new ArrayList<>(Arrays.asList(getResources().getStringArray(R.array.activity_filter_quick_filter_period_items)));
+        ArrayAdapter<String> filterDateAdapter = new ArrayAdapter<>(this,
+                R.layout.simple_spinner_item_themed, activity_filter_quick_filter_period_items);
+        quick_filter_period_select.setAdapter(filterDateAdapter);
+        addListenerOnQuickFilterSelection();
+
+        //set current values coming from parent
+        update_filter_fields();
+
+        final LinearLayout filterfrom = findViewById(R.id.filterfrom);
+        final LinearLayout filterto = findViewById(R.id.filterto);
+        final EditText nameContainsFilterdata = findViewById(R.id.textViewNameData);
+
+        final Button reset_filter_button = findViewById(R.id.reset_filter_button);
+        final Button apply_filter_button = findViewById(R.id.apply_filter_button);
+
+        nameContainsFilterdata.addTextChangedListener(new TextWatcher() {
+
+            public void afterTextChanged(Editable s) {
+
+            }
+
+            public void beforeTextChanged(CharSequence s, int start,
+                                          int count, int after) {
+            }
+
+            public void onTextChanged(CharSequence s, int start,
+                                      int before, int count) {
+                nameContainsFilter = s.toString();
+                update_filter_fields();
+            }
+        });
+
+
+        reset_filter_button.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                activityFilter = 0;
+                dateFromFilter = 0;
+                dateToFilter = 0;
+                nameContainsFilter = "";
+                filterKindSpinner.setSelection(0);
+                itemsFilter = null;
+                deviceFilterSpinner.setSelection(filterDevicesAdapter.getItemPositionForSelection(getDeviceById(initial_deviceFilter)));
+                quick_filter_period_select.setSelection(0);
+                update_filter_fields();
+            }
+        });
+
+        apply_filter_button.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                String text = nameContainsFilterdata.getText().toString();
+                if (!text.isEmpty()) {
+                    nameContainsFilter = text;
+                }
+                Intent intent = new Intent();
+                Bundle bundle = new Bundle();
+                bundle.putInt("activityFilter", activityFilter);
+                bundle.putSerializable("itemsFilter", (Serializable) itemsFilter);
+                bundle.putLong("dateFromFilter", dateFromFilter);
+                bundle.putLong("dateToFilter", dateToFilter);
+                bundle.putLong("deviceFilter", deviceFilter);
+                bundle.putString("nameContainsFilter", nameContainsFilter);
+                intent.putExtras(bundle);
+                setResult(1, intent);
+                finish();
+            }
+        });
+
+
+        filterfrom.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                getDate(DATE_FILTER_FROM, dateFromFilter);
+                quick_filter_period_select.setSelection(0);
+
+
+            }
+        });
+
+        filterto.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                getDate(DATE_FILTER_TO, dateToFilter);
+                quick_filter_period_select.setSelection(0);
+            }
+        });
+
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(final MenuItem item) {
+        final int itemId = item.getItemId();
+        if (itemId == android.R.id.home) {
+            // back button
+            finish();
+            return true;
+        }
+        return super.onOptionsItemSelected(item);
+    }
+
+    private void addListenerOnSpinnerDeviceSelection() {
+        Spinner spinner = findViewById(R.id.select_device);
+        spinner.setOnItemSelectedListener(new CustomOnDeviceSelectedListener());
+    }
+
+    public void addListenerOnSpinnerKindSelection() {
+        Spinner spinner = findViewById(R.id.select_kind);
+        spinner.setOnItemSelectedListener(new CustomOnKindSelectedListener());
+    }
+
+    public void addListenerOnQuickFilterSelection() {
+        Spinner spinner = findViewById(R.id.quick_filter_period_select);
+        spinner.setOnItemSelectedListener(new CustomQuickFilterSelectionListener());
+    }
+
+    public void update_filter_fields() {
+        TextView filterDateFromDataView = findViewById(R.id.textViewFromData);
+        TextView filterDateToDataView = findViewById(R.id.textViewToData);
+        Button reset_filter_button = findViewById(R.id.reset_filter_button);
+        TextView textViewItemsData = findViewById(R.id.textViewItemsData);
+
+        final EditText nameContainsFilterdata = findViewById(R.id.textViewNameData);
+
+        if (dateFromFilter > 0) {
+            filterDateFromDataView.setText(DateTimeUtils.formatDate(new Date(dateFromFilter)));
+        } else {
+            filterDateFromDataView.setText("");
+        }
+
+        if (dateToFilter > 0) {
+            filterDateToDataView.setText(DateTimeUtils.formatDate(new Date(dateToFilter)));
+        } else {
+            filterDateToDataView.setText("");
+        }
+
+        if (dateToFilter < dateFromFilter && dateToFilter > 0) {
+            filterDateFromDataView.setBackgroundColor(Color.RED);
+            filterDateToDataView.setBackgroundColor(Color.RED);
+        } else {
+            filterDateFromDataView.setBackgroundColor(BACKGROUND_COLOR);
+            filterDateToDataView.setBackgroundColor(BACKGROUND_COLOR);
+        }
+
+        if (itemsFilter != null) {
+            textViewItemsData.setText(String.format("%s", itemsFilter.size()));
+        } else {
+            textViewItemsData.setText("0");
+        }
+
+        if (nameContainsFilter != null && !nameContainsFilter.equals(nameContainsFilterdata.getText().toString())) {
+            nameContainsFilterdata.setText(nameContainsFilter);
+        }
+        if (dateToFilter != 0 || dateFromFilter != 0 || activityFilter != 0 || nameContainsFilterdata.length() > 0 || itemsFilter != null || deviceFilter != initial_deviceFilter) {
+            reset_filter_button.getBackground().clearColorFilter();
+
+        } else {
+            reset_filter_button.getBackground().setColorFilter(new LightingColorFilter(0x0, 0x00888888));
+        }
+    }
+
+    public void getDate(final String filter, long currentDatemillis) {
+        Calendar currentDate = Calendar.getInstance();
+        if (currentDatemillis > 0) {
+            currentDate = GregorianCalendar.getInstance();
+            currentDate.setTimeInMillis(currentDatemillis);
+        }
+
+        new DatePickerDialog(this, new DatePickerDialog.OnDateSetListener() {
+            @Override
+            public void onDateSet(DatePicker view, int year, int monthOfYear, int dayOfMonth) {
+                Calendar date = Calendar.getInstance();
+
+                if (filter.equals(DATE_FILTER_FROM)) {
+                    date.set(year, monthOfYear, dayOfMonth, 0, 0);
+                    dateFromFilter = date.getTimeInMillis();
+                } else {
+                    date.set(year, monthOfYear, dayOfMonth, 23, 59);
+                    dateToFilter = date.getTimeInMillis();
+                }
+                update_filter_fields();
+            }
+        }, currentDate.get(Calendar.YEAR), currentDate.get(Calendar.MONTH), currentDate.get(Calendar.DATE)).show();
+    }
+
+    private void setTimePeriodFilter(String selection) {
+        Calendar date = Calendar.getInstance();
+        date.set(Calendar.HOUR_OF_DAY, 0);
+        date.set(Calendar.MINUTE, 0);
+        date.set(Calendar.SECOND, 0);
+        long firstdate;
+        long lastdate;
+
+        switch (selection) {
+            case "thisweek":
+                date.set(Calendar.DAY_OF_WEEK, date.getFirstDayOfWeek());
+                firstdate = date.getTimeInMillis();
+                lastdate = Calendar.getInstance().getTimeInMillis();
+                break;
+            case "thismonth":
+                date.set(Calendar.DAY_OF_MONTH, 1);
+                firstdate = date.getTimeInMillis();
+                lastdate = Calendar.getInstance().getTimeInMillis();
+                break;
+            case "lastweek":
+                int i = date.get(Calendar.DAY_OF_WEEK) - date.getFirstDayOfWeek();
+                date.add(Calendar.DATE, -i - 7);
+                firstdate = date.getTimeInMillis();
+                date.add(Calendar.DATE, 6);
+                lastdate = date.getTimeInMillis();
+                break;
+            case "lastmonth":
+                date.set(Calendar.DATE, 1);
+                date.add(Calendar.DAY_OF_MONTH, -1);
+                lastdate = date.getTimeInMillis();
+                date.set(Calendar.DATE, 1);
+                firstdate = date.getTimeInMillis();
+                break;
+            case "7days":
+                date.add(Calendar.DATE, -7);
+                firstdate = date.getTimeInMillis();
+                lastdate = Calendar.getInstance().getTimeInMillis();
+                break;
+            case "30days":
+                date.add(Calendar.DATE, -30);
+                firstdate = date.getTimeInMillis();
+                lastdate = Calendar.getInstance().getTimeInMillis();
+                break;
+            default:
+                return;
+        }
+        dateFromFilter = firstdate;
+        dateToFilter = lastdate;
+        update_filter_fields();
+    }
+
+    public LinkedHashMap<String, Pair<Long, Integer>> getAllDevices(Context appContext) {
+        DaoSession daoSession;
+        GBApplication gbApp = (GBApplication) appContext;
+        LinkedHashMap<String, Pair<Long, Integer>> newMap = new LinkedHashMap<>(1);
+        List<? extends GBDevice> devices = gbApp.getDeviceManager().getDevices();
+        newMap.put(getString(R.string.activity_summaries_all_devices), new Pair<>(ALL_DEVICES, R.drawable.ic_device_default_disabled));
+
+        try (DBHandler handler = GBApplication.acquireDB()) {
+            daoSession = handler.getDaoSession();
+            for (GBDevice device : devices) {
+                DeviceCoordinator coordinator = device.getType().getDeviceCoordinator();
+                Device dbDevice = DBHelper.findDevice(device, daoSession);
+                int icon = device.getEnabledDisabledIconResource();
+                if (dbDevice != null && coordinator != null
+                        && coordinator.supportsActivityTracks()
+                        && !newMap.containsKey(device.getAliasOrName())) {
+                    newMap.put(device.getAliasOrName(), new Pair<>(dbDevice.getId(), icon));
+                }
+            }
+
+        } catch (Exception e) {
+            LOG.debug("Error getting list of all devices: " + e);
+        }
+        return newMap;
+    }
+
+    public SpinnerWithIconItem getKindByValue(int value) {
+        for (Map.Entry<String, ActivityKind> entry : activityKindMap.entrySet()) {
+            if (value == entry.getValue().getCode()) {
+                return new SpinnerWithIconItem(
+                        entry.getKey(),
+                        (long) entry.getValue().getCode(),
+                        entry.getValue().getIcon()
+                );
+            }
+        }
+        return null;
+    }
+
+    public SpinnerWithIconItem getDeviceById(long id) {
+        for (Map.Entry<String, Pair<Long, Integer>> device : allDevices.entrySet()) {
+            if (Objects.equals(id, device.getValue().first)) {
+                return new SpinnerWithIconItem(device.getKey(),
+                        device.getValue().first,
+                        device.getValue().second);
+            }
+        }
+        return null;
+    }
+
+    public class CustomOnKindSelectedListener implements AdapterView.OnItemSelectedListener {
+
+        public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
+            SpinnerWithIconItem selectedItem = (SpinnerWithIconItem) parent.getItemAtPosition(pos);
+            String activity = selectedItem.getText();
+            activityFilter = activityKindMap.get(activity).getCode();
+            update_filter_fields();
+        }
+
+        @Override
+        public void onNothingSelected(AdapterView<?> arg0) {
+            // TODO Auto-generated method stub
+        }
+
+    }
+
+    public class CustomOnDeviceSelectedListener implements AdapterView.OnItemSelectedListener {
+
+        public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
+            SpinnerWithIconItem selectedItem = (SpinnerWithIconItem) parent.getItemAtPosition(pos);
+            deviceFilter = selectedItem.getId();
+            update_filter_fields();
+        }
+
+        @Override
+        public void onNothingSelected(AdapterView<?> arg0) {
+            // TODO Auto-generated method stub
+        }
+
+    }
+
+    public class CustomQuickFilterSelectionListener implements AdapterView.OnItemSelectedListener {
+        ArrayList<String> activity_filter_quick_filter_period_values = new ArrayList<>(Arrays.asList(getResources().getStringArray(R.array.activity_filter_quick_filter_period_values)));
+        String selection;
+
+        public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
+            selection = activity_filter_quick_filter_period_values.get(pos);
+            setTimePeriodFilter(selection);
+        }
+
+        @Override
+        public void onNothingSelected(AdapterView<?> arg0) {
+            // TODO Auto-generated method stub
+        }
+
+    }
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ActivitySummariesGpsFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ActivitySummariesGpsFragment.java
new file mode 100644
index 0000000..24138af
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ActivitySummariesGpsFragment.java
@@ -0,0 +1,180 @@
+/*  Copyright (C) 2020-2024 Jos茅 Rebelo, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+
+import androidx.annotation.Nullable;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityPoint;
+import nodomain.freeyourgadget.gadgetbridge.model.GPSCoordinate;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.FitFile;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordData;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitRecord;
+import nodomain.freeyourgadget.gadgetbridge.util.gpx.GpxParseException;
+import nodomain.freeyourgadget.gadgetbridge.util.gpx.GpxParser;
+
+import static android.graphics.Bitmap.createBitmap;
+
+
+public class ActivitySummariesGpsFragment extends AbstractGBFragment {
+    private static final Logger LOG = LoggerFactory.getLogger(ActivitySummariesGpsFragment.class);
+    private ImageView gpsView;
+    private final int CANVAS_SIZE = 360;
+    private File inputFile;
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+                             Bundle savedInstanceState) {
+        View rootView = inflater.inflate(R.layout.fragment_gps, container, false);
+        gpsView = rootView.findViewById(R.id.activitygpsview);
+        if (inputFile != null) {
+            processInBackgroundThread();
+        }
+        return rootView;
+    }
+
+    public void set_data(File inputFile) {
+        this.inputFile = inputFile;
+        if (gpsView != null) { //first fragment inflate is AFTER this is called
+            processInBackgroundThread();
+        }
+    }
+
+    private void processInBackgroundThread() {
+        final Canvas canvas = createCanvas(gpsView);
+        new Thread(() -> {
+            final List<GPSCoordinate> points = getActivityPoints(inputFile)
+                    .stream()
+                    .map(ActivityPoint::getLocation)
+                    .filter(Objects::nonNull)
+                    .collect(Collectors.toList());
+
+            if (!points.isEmpty()) {
+                drawTrack(canvas, points);
+            }
+        }).start();
+    }
+
+    public static List<ActivityPoint> getActivityPoints(final File trackFile) {
+        final List<ActivityPoint> points = new ArrayList<>();
+        if (trackFile.getName().endsWith(".gpx")) {
+            try (FileInputStream inputStream = new FileInputStream(trackFile)) {
+                final GpxParser gpxParser = new GpxParser(inputStream);
+                points.addAll(gpxParser.getGpxFile().getActivityPoints());
+            } catch (final IOException e) {
+                LOG.error("Failed to open {}", trackFile, e);
+            } catch (final GpxParseException e) {
+                LOG.error("Failed to parse gpx file", e);
+            }
+        } else if (trackFile.getName().endsWith(".fit")) {
+            try {
+                FitFile fitFile = FitFile.parseIncoming(trackFile);
+                for (final RecordData record : fitFile.getRecords()) {
+                    if (record instanceof FitRecord) {
+                        points.add(((FitRecord) record).toActivityPoint());
+                    }
+                }
+            } catch (final IOException e) {
+                LOG.error("Failed to open {}", trackFile, e);
+            } catch (final Exception e) {
+                LOG.error("Failed to parse fit file", e);
+            }
+        } else {
+            LOG.warn("Unknown file type {}", trackFile.getName());
+        }
+
+        return points;
+    }
+
+    private void drawTrack(Canvas canvas, List<? extends GPSCoordinate> trackPoints) {
+        double maxLat = (Collections.max(trackPoints, new GPSCoordinate.compareLatitude())).getLatitude();
+        double minLat = (Collections.min(trackPoints, new GPSCoordinate.compareLatitude())).getLatitude();
+        double maxLon = (Collections.max(trackPoints, new GPSCoordinate.compareLongitude())).getLongitude();
+        double minLon = (Collections.min(trackPoints, new GPSCoordinate.compareLongitude())).getLongitude();
+        double maxAlt = (Collections.max(trackPoints, new GPSCoordinate.compareElevation())).getAltitude();
+        double minAlt = (Collections.min(trackPoints, new GPSCoordinate.compareElevation())).getAltitude();
+        float scale_factor_w = (float) ((maxLon - minLon) / (maxLat - minLat));
+        float scale_factor_h = (float) ((maxLat - minLat) / (maxLon - minLon));
+
+        if (scale_factor_h > scale_factor_w) { //scaling to draw proportionally
+            scale_factor_h = 1;
+        } else {
+            scale_factor_w = 1;
+        }
+
+
+        Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
+        paint.setStrokeWidth(1);
+        paint.setColor(getResources().getColor(R.color.chart_activity_light));
+
+        for (GPSCoordinate p : trackPoints) {
+            float lat = (float) ((p.getLatitude() - minLat) / (maxLat - minLat));
+            float lon = (float) ((p.getLongitude() - minLon) / (maxLon - minLon));
+            float alt = (float) ((p.getAltitude() - minAlt) / (maxAlt - minAlt));
+            paint.setStrokeWidth(1 + alt); //make thicker with higher altitude, we could do more here
+            canvas.drawPoint(CANVAS_SIZE * lon * scale_factor_w, CANVAS_SIZE * lat * scale_factor_h, paint);
+        }
+    }
+
+    private Canvas createCanvas(ImageView imageView) {
+        Bitmap bitmap = createBitmap(CANVAS_SIZE, CANVAS_SIZE, Bitmap.Config.ARGB_8888);
+        Canvas canvas = new Canvas(bitmap);
+        canvas.drawColor(GBApplication.getWindowBackgroundColor(requireActivity()));
+        //frame around, but it doesn't look so nice
+        /*
+        Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
+        paint.setStrokeWidth(0);
+        paint.setStyle(Paint.Style.STROKE);
+        paint.setColor(getResources().getColor(R.color.chart_activity_light));
+        canvas.drawRect(0,0,360,360,paint);
+         */
+        imageView.setImageBitmap(bitmap);
+        imageView.setScaleY(-1f); //flip the canvas
+
+        return canvas;
+    }
+
+    @Nullable
+    @Override
+    protected CharSequence getTitle() {
+        return null;
+    }
+
+}
+
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ActivitySummaryDetail.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ActivitySummaryDetail.java
new file mode 100644
index 0000000..ee700c5
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ActivitySummaryDetail.java
@@ -0,0 +1,729 @@
+/*  Copyright (C) 2020-2024 Andreas Shimokawa, Arjan Schrijver, Daniel Dakhno,
+    Jos茅 Rebelo, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities;
+
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.*;
+
+import android.annotation.SuppressLint;
+import android.content.ActivityNotFoundException;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Typeface;
+import android.net.Uri;
+import android.os.Bundle;
+import android.text.InputType;
+import android.text.format.DateUtils;
+import android.util.TypedValue;
+import android.view.Gravity;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.SubMenu;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
+import android.widget.ArrayAdapter;
+import android.widget.EditText;
+import android.widget.FrameLayout;
+
+import androidx.appcompat.app.ActionBar;
+import androidx.gridlayout.widget.GridLayout;
+
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.ScrollView;
+import android.widget.TableRow;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AlertDialog;
+import androidx.core.content.FileProvider;
+
+import com.google.android.material.dialog.MaterialAlertDialogBuilder;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.tuple.Pair;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.workouts.WorkoutValueFormatter;
+import nodomain.freeyourgadget.gadgetbridge.activities.workouts.entries.ActivitySummaryEntry;
+import nodomain.freeyourgadget.gadgetbridge.activities.workouts.entries.ActivitySummarySimpleEntry;
+import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryData;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryItems;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryJsonSummary;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryParser;
+import nodomain.freeyourgadget.gadgetbridge.util.ActivitySummaryUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.AndroidUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+import nodomain.freeyourgadget.gadgetbridge.util.SwipeEvents;
+
+public class ActivitySummaryDetail extends AbstractGBActivity {
+    private static final Logger LOG = LoggerFactory.getLogger(ActivitySummaryDetail.class);
+    BaseActivitySummary currentItem = null;
+    private GBDevice gbDevice;
+    private Menu mOptionsMenu;
+    List<String> filesGpxList = new ArrayList<>();
+    int selectedGpxIndex;
+    String selectedGpxFile;
+    File export_path = null;
+
+    private ActivitySummariesChartFragment activitySummariesChartFragment;
+    private ActivitySummariesGpsFragment activitySummariesGpsFragment;
+
+    private final WorkoutValueFormatter workoutValueFormatter = new WorkoutValueFormatter();
+
+    public static int getAlternateColor(Context context) {
+        TypedValue typedValue = new TypedValue();
+        Resources.Theme theme = context.getTheme();
+        theme.resolveAttribute(R.attr.alternate_row_background, typedValue, true);
+        return typedValue.data;
+    }
+
+    public static Bitmap getScreenShot(View view, int height, int width, Context context) {
+        Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+        Canvas canvas = new Canvas(bitmap);
+        canvas.drawColor(GBApplication.getWindowBackgroundColor(context));
+        view.draw(canvas);
+        return bitmap;
+    }
+
+    @SuppressLint("ClickableViewAccessibility")
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        final Context appContext = this.getApplicationContext();
+        if (appContext instanceof GBApplication) {
+            setContentView(R.layout.activity_summary_details);
+        }
+
+
+        Intent intent = getIntent();
+
+        Bundle bundle = intent.getExtras();
+        gbDevice = bundle.getParcelable(GBDevice.EXTRA_DEVICE);
+        final int position = bundle.getInt("position", 0);
+        final int activityFilter = bundle.getInt("activityFilter", 0);
+        final long dateFromFilter = bundle.getLong("dateFromFilter", 0);
+        final long dateToFilter = bundle.getLong("dateToFilter", 0);
+        final long deviceFilter = bundle.getLong("deviceFilter", 0);
+        final String nameContainsFilter = bundle.getString("nameContainsFilter");
+        final List<Long> itemsFilter = (List<Long>) bundle.getSerializable("itemsFilter");
+
+        final ActivitySummaryItems items = new ActivitySummaryItems(this, gbDevice, activityFilter, dateFromFilter, dateToFilter, nameContainsFilter, deviceFilter, itemsFilter);
+        final ScrollView layout = findViewById(R.id.activity_summary_detail_scroll_layout);
+        //final LinearLayout layout = findViewById(R.id.activity_summary_detail_relative_layout);
+
+        final Animation animFadeRight;
+        final Animation animFadeLeft;
+        final Animation animBounceLeft;
+        final Animation animBounceRight;
+
+        animFadeRight = AnimationUtils.loadAnimation(
+                this,
+                R.anim.flyright);
+        animFadeLeft = AnimationUtils.loadAnimation(
+                this,
+                R.anim.flyleft);
+        animBounceLeft = AnimationUtils.loadAnimation(
+                this,
+                R.anim.bounceleft);
+        animBounceRight = AnimationUtils.loadAnimation(
+                this,
+                R.anim.bounceright);
+
+        activitySummariesChartFragment = new ActivitySummariesChartFragment();
+        activitySummariesGpsFragment = new ActivitySummariesGpsFragment();
+
+        getSupportFragmentManager()
+                .beginTransaction()
+                .replace(R.id.chartsFragmentHolder, activitySummariesChartFragment)
+                .replace(R.id.gpsFragmentHolder, activitySummariesGpsFragment)
+                .commit();
+
+        layout.setOnTouchListener(new SwipeEvents(this) {
+            @Override
+            public void onSwipeRight() {
+                BaseActivitySummary newItem = items.getNextItem();
+                if (newItem != null) {
+                    currentItem = newItem;
+                    refreshFromCurrentItem();
+                    layout.startAnimation(animFadeRight);
+                } else {
+                    layout.startAnimation(animBounceRight);
+                }
+            }
+
+            @Override
+            public void onSwipeLeft() {
+                BaseActivitySummary newItem = items.getPrevItem();
+                if (newItem != null) {
+                    currentItem = newItem;
+                    refreshFromCurrentItem();
+                    layout.startAnimation(animFadeLeft);
+                } else {
+                    layout.startAnimation(animBounceLeft);
+                }
+            }
+        });
+
+        currentItem = items.getItem(position);
+        if (currentItem != null) {
+            refreshFromCurrentItem();
+        }
+
+        //allows long-press.switch of data being in raw form or recalculated
+        ImageView activity_icon = findViewById(R.id.item_image);
+        activity_icon.setOnLongClickListener(new View.OnLongClickListener() {
+            public boolean onLongClick(View v) {
+                workoutValueFormatter.toggleRawData();
+                if (currentItem != null) {
+                    makeSummaryHeader(currentItem);
+                    makeSummaryContent(currentItem);
+                }
+                return false;
+            }
+        });
+    }
+
+    private void makeSummaryHeader(BaseActivitySummary item) {
+        //make view of data from main part of item
+        String activitykindname = ActivityKind.fromCode(item.getActivityKind()).getLabel(getApplicationContext());
+        String activityname = item.getName();
+        Date starttime = item.getStartTime();
+        Date endtime = item.getEndTime();
+        String durationhms = DateTimeUtils.formatDurationHoursMinutes((endtime.getTime() - starttime.getTime()), TimeUnit.MILLISECONDS);
+
+        ImageView activity_icon = findViewById(R.id.item_image);
+        activity_icon.setImageResource(ActivityKind.fromCode(item.getActivityKind()).getIcon());
+
+        final ActionBar actionBar = getSupportActionBar();
+        if (actionBar != null) {
+            actionBar.setTitle(activitykindname);
+        }
+
+        TextView activity_name = findViewById(R.id.activityname);
+        activity_name.setText(activityname);
+        if (StringUtils.isBlank(activityname)) {
+            activity_name.setVisibility(View.GONE);
+        } else {
+            activity_name.setVisibility(View.VISIBLE);
+        }
+
+        TextView activityDateTextView = findViewById(R.id.activitydate);
+
+        final Context context = activityDateTextView.getContext();
+        final String timeString;
+        if (DateTimeUtils.isSameDay(starttime, endtime)) {
+            timeString = context.getString(
+                    R.string.date_placeholders__start_time__end_time,
+                    DateTimeUtils.formatDateTimeRelative(context, starttime),
+                    DateTimeUtils.formatTime(endtime.getHours(), endtime.getMinutes())
+            );
+        } else {
+            timeString = context.getString(
+                    R.string.date_placeholders__start_time__end_time,
+                    DateTimeUtils.formatDateTimeRelative(context, starttime),
+                    DateTimeUtils.formatDateTimeRelative(context, endtime)
+            );
+        }
+        activityDateTextView.setText(timeString);
+
+        TextView activityTimeTextView = findViewById(R.id.activityduration);
+        activityTimeTextView.setText(durationhms);
+    }
+
+    private void refreshFromCurrentItem() {
+        makeSummaryHeader(currentItem);
+        makeSummaryContent(currentItem);
+
+        activitySummariesChartFragment.setDateAndGetData(
+                getTrackFile(),
+                getGBDevice(currentItem.getDevice()),
+                currentItem.getStartTime().getTime() / 1000,
+                currentItem.getEndTime().getTime() / 1000
+        );
+
+        if (itemHasGps()) {
+            showGpsCanvas();
+            activitySummariesGpsFragment.set_data(getTrackFile());
+        } else {
+            hideGpsCanvas();
+        }
+
+        updateMenuItems();
+    }
+
+    private File get_path() {
+        File path = null;
+        try {
+            path = FileUtils.getExternalFilesDir();
+        } catch (IOException e) {
+            LOG.error("Error getting path", e);
+        }
+        return path;
+    }
+
+    private List<String> get_gpx_file_list() {
+        List<String> list = new ArrayList<>();
+
+        File[] fileListing = export_path.listFiles(new FileFilter() {
+            @Override
+            public boolean accept(File file) {
+                return file.getPath().toLowerCase().endsWith(".gpx");
+            }
+        });
+
+        if (fileListing != null && fileListing.length > 1) {
+            Arrays.sort(fileListing, new Comparator<File>() {
+                @Override
+                public int compare(File fileA, File fileB) {
+                    if (fileA.lastModified() < fileB.lastModified()) {
+                        return 1;
+                    }
+                    if (fileA.lastModified() > fileB.lastModified()) {
+                        return -1;
+                    }
+                    return 0;
+                }
+            });
+        }
+
+        list.add(getString(R.string.activity_summary_detail_clear_gpx_track));
+
+        for (File file : fileListing) {
+            list.add(file.getName());
+        }
+        return list;
+    }
+
+    private void makeSummaryContent(BaseActivitySummary item) {
+        final DeviceCoordinator coordinator = gbDevice.getDeviceCoordinator();
+        final ActivitySummaryParser summaryParser = coordinator.getActivitySummaryParser(gbDevice, this);
+
+        //make view of data from summaryData of item
+        LinearLayout fieldLayout = findViewById(R.id.summaryDetails);
+        fieldLayout.removeAllViews(); //remove old widgets
+        ActivitySummaryJsonSummary activitySummaryJsonSummary = new ActivitySummaryJsonSummary(summaryParser, item);
+        Map<String, List<Pair<String, ActivitySummaryEntry>>> data = activitySummaryJsonSummary.getSummaryGroupedList(); //get list, grouped by groups
+        if (data == null) return;
+
+        for (final Map.Entry<String, List<Pair<String, ActivitySummaryEntry>>> group : data.entrySet()) {
+            final String groupKey = group.getKey();
+            final List<Pair<String, ActivitySummaryEntry>> entries = group.getValue();
+
+            TableRow label_row = new TableRow(ActivitySummaryDetail.this);
+            TextView label_field = new TextView(ActivitySummaryDetail.this);
+            label_field.setId(View.generateViewId());
+            label_field.setTextSize(18);
+            label_field.setPaddingRelative(dpToPx(16), dpToPx(16), 0, dpToPx(16));
+            label_field.setTypeface(null, Typeface.BOLD);
+            label_field.setText(workoutValueFormatter.getStringResourceByName(groupKey));
+            label_row.addView(label_field);
+            fieldLayout.addView(label_row);
+
+            GridLayout gridLayout = new GridLayout(ActivitySummaryDetail.this);
+            gridLayout.setBackgroundColor(getResources().getColor(R.color.gauge_line_color));
+            gridLayout.setColumnCount(2);
+            gridLayout.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
+            int totalCells = entries.stream().mapToInt(e -> e.getRight().getColumnSpan()).sum();
+            totalCells += totalCells & 1; // round up to nearest even number, since we have 2 columns
+            int cellNumber = 0;
+            for (final Pair<String, ActivitySummaryEntry> entry : entries) {
+                final int columnSpan = entry.getRight().getColumnSpan();
+                if (columnSpan == 2 && cellNumber % 2 != 0) {
+                    // This entry needs 2 columns, so let's move to the next row
+                    cellNumber++;
+                }
+                LinearLayout linearLayout = generateLinearLayout(cellNumber, cellNumber + 2 >= totalCells, columnSpan);
+                entry.getRight().populate(entry.getLeft(), linearLayout, workoutValueFormatter);
+                gridLayout.addView(linearLayout);
+                cellNumber += columnSpan;
+            }
+            if (gridLayout.getChildCount() > 0) {
+                if (cellNumber % 2 != 0) {
+                    final LinearLayout emptyLayout = generateLinearLayout(cellNumber, true, 1);
+                    new ActivitySummarySimpleEntry(null, "", "string").populate("", emptyLayout, workoutValueFormatter);
+                    gridLayout.addView(emptyLayout);
+                }
+                fieldLayout.addView(gridLayout);
+            }
+        }
+    }
+
+    public LinearLayout generateLinearLayout(int i, boolean lastRow, int columnSize) {
+        LinearLayout linearLayout = new LinearLayout(ActivitySummaryDetail.this);
+        GridLayout.LayoutParams columnParams = new GridLayout.LayoutParams();
+        columnParams.columnSpec = GridLayout.spec(i % 2 == 0 ? 0 : 1, 1);
+        GridLayout.LayoutParams layoutParams = new GridLayout.LayoutParams(
+                GridLayout.spec(GridLayout.UNDEFINED, GridLayout.FILL, 1f),
+                GridLayout.spec(GridLayout.UNDEFINED, columnSize, GridLayout.FILL, 1f)
+        );
+        layoutParams.width = 0;
+        linearLayout.setLayoutParams(layoutParams);
+        linearLayout.setOrientation(LinearLayout.VERTICAL);
+        linearLayout.setGravity(Gravity.CENTER);
+        linearLayout.setPadding(dpToPx(15), dpToPx(15), dpToPx(15), dpToPx(15));
+        linearLayout.setBackgroundColor(GBApplication.getWindowBackgroundColor(ActivitySummaryDetail.this));
+        int marginLeft = 0;
+        int marginTop = 0;
+        int marginBottom = 0;
+        int marginRight = 0;
+        if (i % 2 == 0) {
+            marginTop = 2;
+            marginRight = 1;
+        }
+        if (i % 2 == 1) {
+            marginTop = 2;
+            marginLeft = 1;
+        }
+        if (lastRow) {
+            marginBottom = 2;
+        }
+        layoutParams.setMargins(dpToPx(marginLeft), dpToPx(marginTop), dpToPx(marginRight), dpToPx(marginBottom));
+
+        return linearLayout;
+    }
+
+    public int dpToPx(int dp) {
+        float density = getResources().getDisplayMetrics().density;
+        return Math.round(dp * density);
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(final MenuItem item) {
+        final int itemId = item.getItemId();
+        if (itemId == android.R.id.home) {
+            // back button
+            finish();
+            return true;
+        } else if (itemId == R.id.activity_action_take_screenshot) {
+            take_share_screenshot(ActivitySummaryDetail.this);
+            return true;
+        } else if (itemId == R.id.activity_action_show_gpx) {
+            viewGpxTrack(ActivitySummaryDetail.this);
+            return true;
+        } else if (itemId == R.id.activity_action_share_gpx) {
+            shareGpxTrack(ActivitySummaryDetail.this);
+            return true;
+        } else if (itemId == R.id.activity_action_dev_share_raw_summary) {
+            shareRawSummary(ActivitySummaryDetail.this, currentItem);
+            return true;
+        } else if (itemId == R.id.activity_action_dev_share_raw_details) {
+            shareRawDetails(ActivitySummaryDetail.this, currentItem);
+            return true;
+        } else if (itemId == R.id.activity_action_dev_share_json_details) {
+            shareJsonDetails(ActivitySummaryDetail.this, currentItem);
+            return true;
+        } else if (itemId == R.id.activity_summary_detail_action_edit_name) {
+            editLabel();
+            return true;
+        } else if (itemId == R.id.activity_summary_detail_action_edit_gps) {
+            editGps();
+            return true;
+        }
+        return super.onOptionsItemSelected(item);
+    }
+
+    private void editLabel() {
+        final EditText input = new EditText(ActivitySummaryDetail.this);
+        input.setInputType(InputType.TYPE_CLASS_TEXT);
+        String name = currentItem.getName();
+        input.setText((name != null) ? name : "");
+        FrameLayout container = new FrameLayout(ActivitySummaryDetail.this);
+        FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+        params.leftMargin = getResources().getDimensionPixelSize(R.dimen.dialog_margin);
+        params.rightMargin = getResources().getDimensionPixelSize(R.dimen.dialog_margin);
+        input.setLayoutParams(params);
+        container.addView(input);
+
+        new MaterialAlertDialogBuilder(ActivitySummaryDetail.this)
+                .setView(container)
+                .setCancelable(true)
+                .setTitle(ActivitySummaryDetail.this.getString(R.string.activity_summary_edit_name_title))
+                .setPositiveButton(R.string.ok, (dialog, which) -> {
+                    String name1 = input.getText().toString();
+                    if (name1.isEmpty()) name1 = null;
+                    currentItem.setName(name1);
+                    currentItem.update();
+                    makeSummaryHeader(currentItem);
+                    makeSummaryContent(currentItem);
+                })
+                .setNegativeButton(R.string.Cancel, (dialog, which) -> {
+                    // do nothing
+                })
+                .show();
+    }
+
+    private void editGps() {
+        export_path = get_path();
+        filesGpxList = get_gpx_file_list();
+
+        MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(ActivitySummaryDetail.this);
+        builder.setTitle(R.string.activity_summary_detail_select_gpx_track);
+        ArrayAdapter<String> directory_listing = new ArrayAdapter<>(ActivitySummaryDetail.this, android.R.layout.simple_list_item_1, filesGpxList);
+        builder.setSingleChoiceItems(directory_listing, 0, (dialog, which) -> {
+            selectedGpxIndex = which;
+            selectedGpxFile = export_path + "/" + filesGpxList.get(selectedGpxIndex);
+            String message = String.format("%s %s?", getString(R.string.set), filesGpxList.get(selectedGpxIndex));
+            if (selectedGpxIndex == 0) {
+                selectedGpxFile = null;
+                message = String.format("%s?", getString(R.string.activity_summary_detail_clear_gpx_track));
+            }
+
+            new MaterialAlertDialogBuilder(ActivitySummaryDetail.this)
+                    .setCancelable(true)
+                    .setIcon(R.drawable.ic_warning)
+                    .setTitle(R.string.activity_summary_detail_editing_gpx_track)
+                    .setMessage(message)
+                    .setPositiveButton(R.string.ok, (dialog1, which1) -> {
+                        currentItem.setGpxTrack(selectedGpxFile);
+                        currentItem.update();
+                        if (itemHasGps()) {
+                            showGpsCanvas();
+                            activitySummariesGpsFragment.set_data(getTrackFile());
+                        } else {
+                            hideGpsCanvas();
+                        }
+                    })
+                    .setNegativeButton(R.string.Cancel, (dialog2, which2) -> {
+                    })
+                    .show();
+            dialog.dismiss();
+        });
+        AlertDialog dialog = builder.create();
+        dialog.show();
+    }
+
+    private void take_share_screenshot(Context context) {
+        final ScrollView layout = findViewById(R.id.activity_summary_detail_scroll_layout);
+        final int width = layout.getChildAt(0).getWidth();
+        final int height = layout.getChildAt(0).getHeight();
+        final Bitmap screenShot = getScreenShot(layout, height, width, context);
+
+        final String fileName = FileUtils.makeValidFileName("Screenshot-" + ActivityKind.fromCode(currentItem.getActivityKind()).getLabel(context).toLowerCase() + "-" + DateTimeUtils.formatIso8601(currentItem.getStartTime()) + ".png");
+        try {
+            final File targetFile = new File(FileUtils.getExternalFilesDir(), fileName);
+            final FileOutputStream fOut = new FileOutputStream(targetFile);
+            screenShot.compress(Bitmap.CompressFormat.PNG, 85, fOut);
+            fOut.flush();
+            fOut.close();
+            shareScreenshot(targetFile, context);
+            GB.toast(getApplicationContext(), "Screenshot saved", Toast.LENGTH_LONG, GB.INFO);
+        } catch (IOException e) {
+            LOG.error("Error getting screenshot", e);
+        }
+    }
+
+    private void shareScreenshot(File targetFile, Context context) {
+        Uri contentUri = FileProvider.getUriForFile(context,
+                context.getApplicationContext().getPackageName() + ".screenshot_provider", targetFile);
+        Intent sharingIntent = new Intent(android.content.Intent.ACTION_SEND);
+        sharingIntent.setType("image/*");
+        String shareBody = "Sports Activity";
+        sharingIntent.putExtra(android.content.Intent.EXTRA_SUBJECT, "Sports Activity");
+        sharingIntent.putExtra(android.content.Intent.EXTRA_TEXT, shareBody);
+        sharingIntent.putExtra(Intent.EXTRA_STREAM, contentUri);
+
+        try {
+            startActivity(Intent.createChooser(sharingIntent, "Share via"));
+        } catch (ActivityNotFoundException e) {
+            Toast.makeText(context, R.string.activity_error_no_app_for_png, Toast.LENGTH_LONG).show();
+        }
+    }
+
+    private void viewGpxTrack(Context context) {
+        final File gpxFile = ActivitySummaryUtils.getGpxFile(currentItem);
+        if (gpxFile == null) {
+            GB.toast(getApplicationContext(), "No GPX track in this activity", Toast.LENGTH_LONG, GB.INFO);
+            return;
+        }
+
+        try {
+            AndroidUtils.viewFile(gpxFile.getPath(), "application/gpx+xml", context);
+        } catch (final Exception e) {
+            GB.toast(getApplicationContext(), "Unable to display GPX track: " + e.getMessage(), Toast.LENGTH_LONG, GB.ERROR, e);
+        }
+    }
+
+    private void shareGpxTrack(final Context context) {
+        final File gpxFile = ActivitySummaryUtils.getGpxFile(currentItem);
+        if (gpxFile == null) {
+            GB.toast(getApplicationContext(), "No GPX track in this activity", Toast.LENGTH_LONG, GB.INFO);
+            return;
+        }
+
+        try {
+            AndroidUtils.shareFile(context, gpxFile);
+        } catch (final Exception e) {
+            GB.toast(context, "Unable to share GPX track: " + e.getMessage(), Toast.LENGTH_LONG, GB.ERROR, e);
+        }
+    }
+
+    private static void shareRawSummary(final Context context, final BaseActivitySummary summary) {
+        if (summary.getRawSummaryData() == null) {
+            GB.toast(context, "No raw summary in this activity", Toast.LENGTH_LONG, GB.WARN);
+            return;
+        }
+
+        final String rawSummaryFilename = FileUtils.makeValidFileName(String.format("%s_summary.bin", DateTimeUtils.formatIso8601(summary.getStartTime())));
+
+        try {
+            AndroidUtils.shareBytesAsFile(context, rawSummaryFilename, summary.getRawSummaryData());
+        } catch (final Exception e) {
+            GB.toast(context, "Unable to share GPX track: " + e.getMessage(), Toast.LENGTH_LONG, GB.ERROR, e);
+        }
+    }
+
+    private static void shareRawDetails(final Context context, final BaseActivitySummary summary) {
+        if (summary.getRawDetailsPath() == null) {
+            GB.toast(context, "No raw details in this activity", Toast.LENGTH_LONG, GB.WARN);
+            return;
+        }
+
+        try {
+            AndroidUtils.shareFile(context, new File(summary.getRawDetailsPath()));
+        } catch (final Exception e) {
+            GB.toast(context, "Unable to share raw details: " + e.getMessage(), Toast.LENGTH_LONG, GB.ERROR, e);
+        }
+    }
+
+    private static void shareJsonDetails(final Context context, final BaseActivitySummary summary) {
+        if (summary.getSummaryData() == null) {
+            GB.toast(context, "No json details in this activity", Toast.LENGTH_LONG, GB.WARN);
+            return;
+        }
+
+        final String jsonSummaryFilename = FileUtils.makeValidFileName(String.format("%s.json", DateTimeUtils.formatIso8601(summary.getStartTime())));
+
+        try {
+            AndroidUtils.shareBytesAsFile(context, jsonSummaryFilename, summary.getSummaryData().getBytes(StandardCharsets.UTF_8));
+        } catch (final Exception e) {
+            GB.toast(context, "Unable to share json details: " + e.getMessage(), Toast.LENGTH_LONG, GB.ERROR, e);
+        }
+    }
+
+    private void updateMenuItems() {
+        boolean hasGpx = false;
+        boolean hasRawSummary = false;
+        boolean hasRawDetails = false;
+
+        if (currentItem != null) {
+            hasGpx = itemHasGps();
+            hasRawSummary = currentItem.getRawSummaryData() != null;
+
+            final String rawDetailsPath = currentItem.getRawDetailsPath();
+            if (rawDetailsPath != null) {
+                hasRawDetails = new File(rawDetailsPath).exists();
+            }
+        }
+
+        if (mOptionsMenu != null) {
+            final SubMenu overflowMenu = mOptionsMenu.findItem(R.id.activity_detail_overflowMenu).getSubMenu();
+            if (overflowMenu != null) {
+                overflowMenu.findItem(R.id.activity_action_show_gpx).setVisible(hasGpx);
+                overflowMenu.findItem(R.id.activity_action_share_gpx).setVisible(hasGpx);
+                overflowMenu.findItem(R.id.activity_action_dev_share_raw_summary).setVisible(hasRawSummary);
+                overflowMenu.findItem(R.id.activity_action_dev_share_raw_details).setVisible(hasRawDetails);
+                final MenuItem devToolsMenu = overflowMenu.findItem(R.id.activity_action_dev_tools);
+                final SubMenu devToolsSubMenu = devToolsMenu.getSubMenu();
+                devToolsMenu.setVisible(devToolsSubMenu != null && devToolsSubMenu.hasVisibleItems());
+            }
+        }
+    }
+
+    private void showGpsCanvas() {
+        final View gpsView = findViewById(R.id.gpsFragmentHolder);
+        final ViewGroup.LayoutParams params = gpsView.getLayoutParams();
+        params.height = (int) (300 * getApplicationContext().getResources().getDisplayMetrics().density);
+        gpsView.setLayoutParams(params);
+    }
+
+    private void hideGpsCanvas() {
+        final View gpsView = findViewById(R.id.gpsFragmentHolder);
+        final ViewGroup.LayoutParams params = gpsView.getLayoutParams();
+        params.height = 0;
+        gpsView.setLayoutParams(params);
+    }
+
+    private boolean itemHasGps() {
+        if (currentItem.getGpxTrack() != null) {
+            final File existing = FileUtils.tryFixPath(new File(currentItem.getGpxTrack()));
+            if (existing != null && existing.canRead()) {
+                return true;
+            }
+        }
+        final String summaryDataJson = currentItem.getSummaryData();
+        if (summaryDataJson != null && summaryDataJson.contains(INTERNAL_HAS_GPS)) {
+            final ActivitySummaryData summaryData = ActivitySummaryData.fromJson(summaryDataJson);
+            return summaryData != null && summaryData.getBoolean(INTERNAL_HAS_GPS, false);
+        }
+
+        return false;
+    }
+
+    @Nullable
+    private File getTrackFile() {
+        return ActivitySummaryUtils.getTrackFile(currentItem);
+    }
+
+    @Override
+    public boolean onCreateOptionsMenu(final Menu menu) {
+        super.onCreateOptionsMenu(menu);
+        mOptionsMenu = menu;
+        getMenuInflater().inflate(R.menu.activity_take_screenshot_menu, menu);
+        updateMenuItems();
+        return true;
+    }
+
+    @Nullable
+    private static GBDevice getGBDevice(final Device findDevice) {
+        return GBApplication.app().getDeviceManager().getDevices()
+                .stream()
+                .filter(d -> d.getAddress().equalsIgnoreCase(findDevice.getIdentifier()))
+                .findFirst()
+                .orElse(null);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AlarmDetails.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AlarmDetails.java
new file mode 100644
index 0000000..276c120
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AlarmDetails.java
@@ -0,0 +1,297 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, Carsten Pfeiffer, Daniel
+    Dakhno, Daniele Gobbetti, Dmitry Markin, Lem Dulfo, Taavi Eom盲e, Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities;
+
+import android.os.Bundle;
+import android.text.InputFilter;
+import android.text.Spanned;
+import android.text.format.DateFormat;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.CheckedTextView;
+import android.widget.EditText;
+import android.widget.TimePicker;
+
+import java.text.NumberFormat;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.entities.Alarm;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.util.AlarmUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
+
+public class AlarmDetails extends AbstractGBActivity {
+
+    private Alarm alarm;
+    private TimePicker timePicker;
+    private CheckedTextView cbSmartWakeup;
+    private CheckedTextView cbSnooze;
+    private CheckedTextView cbMonday;
+    private CheckedTextView cbTuesday;
+    private CheckedTextView cbWednesday;
+    private CheckedTextView cbThursday;
+    private CheckedTextView cbFriday;
+    private CheckedTextView cbSaturday;
+    private CheckedTextView cbSunday;
+    private EditText title;
+    private EditText description;
+    private EditText smartWakeupInterval;
+    private GBDevice device;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_alarm_details);
+
+        alarm = (Alarm) getIntent().getSerializableExtra(nodomain.freeyourgadget.gadgetbridge.model.Alarm.EXTRA_ALARM);
+        device = getIntent().getParcelableExtra(GBDevice.EXTRA_DEVICE);
+
+        title = findViewById(R.id.alarm_title);
+        description = findViewById(R.id.alarm_description);
+
+        timePicker = findViewById(R.id.alarm_time_picker);
+        cbSmartWakeup = findViewById(R.id.alarm_cb_smart_wakeup);
+        smartWakeupInterval = findViewById(R.id.alarm_cb_smart_wakeup_interval);
+        cbSnooze = findViewById(R.id.alarm_cb_snooze);
+        cbMonday = findViewById(R.id.alarm_cb_monday);
+        cbTuesday = findViewById(R.id.alarm_cb_tuesday);
+        cbWednesday = findViewById(R.id.alarm_cb_wednesday);
+        cbThursday = findViewById(R.id.alarm_cb_thursday);
+        cbFriday = findViewById(R.id.alarm_cb_friday);
+        cbSaturday = findViewById(R.id.alarm_cb_saturday);
+        cbSunday = findViewById(R.id.alarm_cb_sunday);
+
+
+        cbSmartWakeup.setOnClickListener(new View.OnClickListener() {
+            public void onClick(View v) {
+                ((CheckedTextView) v).toggle();
+                smartWakeupInterval.setEnabled(((CheckedTextView) v).isChecked());
+            }
+        });
+        cbSnooze.setOnClickListener(new View.OnClickListener() {
+            public void onClick(View v) {
+                ((CheckedTextView) v).toggle();
+            }
+        });
+        cbMonday.setOnClickListener(new View.OnClickListener() {
+            public void onClick(View v) {
+                ((CheckedTextView) v).toggle();
+            }
+        });
+        cbTuesday.setOnClickListener(new View.OnClickListener() {
+            public void onClick(View v) {
+                ((CheckedTextView) v).toggle();
+            }
+        });
+        cbWednesday.setOnClickListener(new View.OnClickListener() {
+            public void onClick(View v) {
+                ((CheckedTextView) v).toggle();
+            }
+        });
+        cbThursday.setOnClickListener(new View.OnClickListener() {
+            public void onClick(View v) {
+                ((CheckedTextView) v).toggle();
+            }
+        });
+        cbFriday.setOnClickListener(new View.OnClickListener() {
+            public void onClick(View v) {
+                ((CheckedTextView) v).toggle();
+            }
+        });
+        cbSaturday.setOnClickListener(new View.OnClickListener() {
+            public void onClick(View v) {
+                ((CheckedTextView) v).toggle();
+            }
+        });
+        cbSunday.setOnClickListener(new View.OnClickListener() {
+            public void onClick(View v) {
+                ((CheckedTextView) v).toggle();
+            }
+        });
+
+        timePicker.setIs24HourView(DateFormat.is24HourFormat(GBApplication.getContext()));
+        timePicker.setCurrentHour(alarm.getHour());
+        timePicker.setCurrentMinute(alarm.getMinute());
+
+        boolean smartAlarmSupported = supportsSmartWakeup(alarm.getPosition());
+        boolean smartAlarmForced = forcedSmartWakeup(alarm.getPosition());
+        boolean smartAlarmIntervalSupported = supportsSmartWakeupInterval(alarm.getPosition());
+
+        cbSmartWakeup.setChecked(alarm.getSmartWakeup() || smartAlarmForced);
+        cbSmartWakeup.setVisibility(smartAlarmSupported ? View.VISIBLE : View.GONE);
+        if (smartAlarmForced) {
+            cbSmartWakeup.setEnabled(false);
+            // Force the text to be visible for the "interval" part
+            // Enabled or not can still be seen in the checkmark
+            // TODO: I'd like feedback on this
+            if (GBApplication.isDarkThemeEnabled())
+                cbSmartWakeup.setTextColor(getResources().getColor(android.R.color.secondary_text_dark));
+            else
+                cbSmartWakeup.setTextColor(getResources().getColor(android.R.color.secondary_text_light));
+        }
+        if (smartAlarmIntervalSupported)
+            cbSmartWakeup.setText(R.string.alarm_smart_wakeup_interval);
+
+        smartWakeupInterval.setVisibility(smartAlarmSupported && smartAlarmIntervalSupported ? View.VISIBLE : View.GONE);
+        smartWakeupInterval.setEnabled(alarm.getSmartWakeup() || smartAlarmForced);
+        if (alarm.getSmartWakeupInterval() != null)
+            smartWakeupInterval.setText(NumberFormat.getInstance().format(alarm.getSmartWakeupInterval()));
+        smartWakeupInterval.setFilters(new InputFilter[] {
+                new InputFilter() {
+                    @Override
+                    public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
+                        if (dend >= 3) // Limit length
+                            return "";
+
+                        String strValue = dest.subSequence(0, dstart) + source.subSequence(start, end).toString() + dest.subSequence(dend, dest.length());
+                        try {
+                            int value = Integer.parseInt(strValue);
+                            if (value > 255) {
+                                smartWakeupInterval.setText("255");
+                                smartWakeupInterval.setSelection(3); // Move cursor to end
+                            }
+                        } catch (NumberFormatException e) {
+                            return "";
+                        }
+                        return null;
+                    }
+                }
+        });
+
+        cbSnooze.setChecked(alarm.getSnooze());
+        int snoozeVisibility = supportsSnoozing() ? View.VISIBLE : View.GONE;
+        cbSnooze.setVisibility(snoozeVisibility);
+
+        title.setVisibility(supportsTitle() ? View.VISIBLE : View.GONE);
+        title.setText(alarm.getTitle());
+
+        final int titleLimit = getAlarmTitleLimit();
+        if (titleLimit > 0) {
+            title.setFilters(new InputFilter[]{new InputFilter.LengthFilter(titleLimit)});
+        }
+
+        description.setVisibility(supportsDescription() ? View.VISIBLE : View.GONE);
+        description.setText(alarm.getDescription());
+
+        cbMonday.setChecked(alarm.getRepetition(Alarm.ALARM_MON));
+        cbTuesday.setChecked(alarm.getRepetition(Alarm.ALARM_TUE));
+        cbWednesday.setChecked(alarm.getRepetition(Alarm.ALARM_WED));
+        cbThursday.setChecked(alarm.getRepetition(Alarm.ALARM_THU));
+        cbFriday.setChecked(alarm.getRepetition(Alarm.ALARM_FRI));
+        cbSaturday.setChecked(alarm.getRepetition(Alarm.ALARM_SAT));
+        cbSunday.setChecked(alarm.getRepetition(Alarm.ALARM_SUN));
+    }
+
+    private boolean supportsSmartWakeup(int position) {
+        if (device != null) {
+            DeviceCoordinator coordinator = device.getDeviceCoordinator();
+            return coordinator.supportsSmartWakeup(device, position);
+        }
+        return false;
+    }
+
+    private boolean supportsSmartWakeupInterval(int position) {
+        if (device != null) {
+            DeviceCoordinator coordinator = device.getDeviceCoordinator();
+            return coordinator.supportsSmartWakeupInterval(device, position);
+        }
+        return false;
+    }
+
+    /**
+     * The alarm at this position *must* be a smart alarm
+     */
+    private boolean forcedSmartWakeup(int position) {
+        if (device != null) {
+            DeviceCoordinator coordinator = device.getDeviceCoordinator();
+            return coordinator.forcedSmartWakeup(device, position);
+        }
+        return false;
+    }
+
+    private boolean supportsTitle() {
+        if (device != null) {
+            DeviceCoordinator coordinator = device.getDeviceCoordinator();
+            return coordinator.supportsAlarmTitle(device);
+        }
+        return false;
+    }
+
+    private int getAlarmTitleLimit() {
+        if (device != null) {
+            DeviceCoordinator coordinator = device.getDeviceCoordinator();
+            return coordinator.getAlarmTitleLimit(device);
+        }
+        return -1;
+    }
+
+    private boolean supportsDescription() {
+        if (device != null) {
+            DeviceCoordinator coordinator = device.getDeviceCoordinator();
+            return coordinator.supportsAlarmDescription(device);
+        }
+        return false;
+    }
+
+    private boolean supportsSnoozing() {
+        if (device != null) {
+            DeviceCoordinator coordinator = device.getDeviceCoordinator();
+            return coordinator.supportsAlarmSnoozing();
+        }
+        return false;
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(final MenuItem item) {
+        final int itemId = item.getItemId();
+        if (itemId == android.R.id.home) {
+            // back button
+            finish();
+            return true;
+        }
+        return super.onOptionsItemSelected(item);
+    }
+
+    private void updateAlarm() {
+        // Set alarm as used and enabled if time has changed
+        if (alarm.getUnused() && alarm.getHour() != timePicker.getCurrentHour() || alarm.getMinute() != timePicker.getCurrentMinute()) {
+            alarm.setUnused(false);
+            alarm.setEnabled(true);
+        }
+        alarm.setSmartWakeup(supportsSmartWakeup(alarm.getPosition()) && cbSmartWakeup.isChecked());
+        String interval = smartWakeupInterval.getText().toString();
+        alarm.setSmartWakeupInterval(interval.equals("") ? null : Integer.parseInt(interval));
+        alarm.setSnooze(supportsSnoozing() && cbSnooze.isChecked());
+        int repetitionMask = AlarmUtils.createRepetitionMask(cbMonday.isChecked(), cbTuesday.isChecked(), cbWednesday.isChecked(), cbThursday.isChecked(), cbFriday.isChecked(), cbSaturday.isChecked(), cbSunday.isChecked());
+        alarm.setRepetition(repetitionMask);
+        alarm.setHour(timePicker.getCurrentHour());
+        alarm.setMinute(timePicker.getCurrentMinute());
+        alarm.setTitle(title.getText().toString());
+        alarm.setDescription(description.getText().toString());
+        DBHelper.store(alarm);
+    }
+
+    @Override
+    protected void onPause() {
+        updateAlarm();
+        super.onPause();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AndroidPairingActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AndroidPairingActivity.java
new file mode 100644
index 0000000..92452ca
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AndroidPairingActivity.java
@@ -0,0 +1,30 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, Carsten Pfeiffer, Lem Dulfo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities;
+
+import android.os.Bundle;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+
+public class AndroidPairingActivity extends AbstractGBActivity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_android_pairing);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AppBlacklistActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AppBlacklistActivity.java
new file mode 100644
index 0000000..a7ba373
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AppBlacklistActivity.java
@@ -0,0 +1,86 @@
+/*  Copyright (C) 2015-2024 abettenburg, Andreas Shimokawa, Carsten Pfeiffer,
+    Daniele Gobbetti, Lem Dulfo, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities;
+
+import android.os.Bundle;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+
+import androidx.appcompat.widget.SearchView;
+import androidx.core.app.NavUtils;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.adapter.AppBlacklistAdapter;
+
+
+public class AppBlacklistActivity extends AbstractGBActivity {
+    private AppBlacklistAdapter appBlacklistAdapter;
+
+    @Override
+    protected void onCreate(final Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_appblacklist);
+        final RecyclerView appListView = (RecyclerView) findViewById(R.id.appListView);
+        appListView.setLayoutManager(new LinearLayoutManager(this));
+
+        appBlacklistAdapter = new AppBlacklistAdapter(R.layout.item_app_blacklist, this);
+
+        appListView.setAdapter(appBlacklistAdapter);
+
+        final SearchView searchView = findViewById(R.id.appListViewSearch);
+        searchView.setIconifiedByDefault(false);
+        searchView.setIconified(false);
+        searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
+            @Override
+            public boolean onQueryTextSubmit(final String query) {
+                return false;
+            }
+
+            @Override
+            public boolean onQueryTextChange(final String newText) {
+                appBlacklistAdapter.getFilter().filter(newText);
+                return true;
+            }
+        });
+    }
+
+    @Override
+    public boolean onCreateOptionsMenu(final Menu menu) {
+        final MenuInflater inflater = getMenuInflater();
+        inflater.inflate(R.menu.app_blacklist_menu, menu);
+        return super.onCreateOptionsMenu(menu);
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(final MenuItem item) {
+        final int itemId = item.getItemId();
+        if (itemId == android.R.id.home) {
+            NavUtils.navigateUpFromSameTask(this);
+            return true;
+        } else if (itemId == R.id.check_all_applications) {
+            appBlacklistAdapter.checkAllApplications();
+            return true;
+        } else if (itemId == R.id.uncheck_all_applications) {
+            appBlacklistAdapter.uncheckAllApplications();
+            return true;
+        }
+        return super.onOptionsItemSelected(item);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AppCompatPreferenceActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AppCompatPreferenceActivity.java
new file mode 100644
index 0000000..89372fc
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AppCompatPreferenceActivity.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package nodomain.freeyourgadget.gadgetbridge.activities;
+
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.preference.PreferenceActivity;
+import android.view.MenuInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.LayoutRes;
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.ActionBar;
+import androidx.appcompat.app.AppCompatDelegate;
+import androidx.appcompat.widget.Toolbar;
+
+/**
+ * A {@link android.preference.PreferenceActivity} which implements and proxies the necessary calls
+ * to be used with AppCompat.
+ *
+ * This technique can be used with an {@link android.app.Activity} class, not just
+ * {@link android.preference.PreferenceActivity}.
+ */
+public abstract class AppCompatPreferenceActivity extends PreferenceActivity {
+
+    private AppCompatDelegate mDelegate;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        getDelegate().installViewFactory();
+        getDelegate().onCreate(savedInstanceState);
+        super.onCreate(savedInstanceState);
+    }
+
+    @Override
+    protected void onPostCreate(Bundle savedInstanceState) {
+        super.onPostCreate(savedInstanceState);
+        getDelegate().onPostCreate(savedInstanceState);
+    }
+
+    public ActionBar getSupportActionBar() {
+        return getDelegate().getSupportActionBar();
+    }
+
+    public void setSupportActionBar(@Nullable Toolbar toolbar) {
+        getDelegate().setSupportActionBar(toolbar);
+    }
+
+    @Override
+    public MenuInflater getMenuInflater() {
+        return getDelegate().getMenuInflater();
+    }
+
+    @Override
+    public void setContentView(@LayoutRes int layoutResID) {
+        getDelegate().setContentView(layoutResID);
+    }
+
+    @Override
+    public void setContentView(View view) {
+        getDelegate().setContentView(view);
+    }
+
+    @Override
+    public void setContentView(View view, ViewGroup.LayoutParams params) {
+        getDelegate().setContentView(view, params);
+    }
+
+    @Override
+    public void addContentView(View view, ViewGroup.LayoutParams params) {
+        getDelegate().addContentView(view, params);
+    }
+
+    @Override
+    protected void onPostResume() {
+        super.onPostResume();
+        getDelegate().onPostResume();
+    }
+
+    @Override
+    protected void onTitleChanged(CharSequence title, int color) {
+        super.onTitleChanged(title, color);
+        getDelegate().setTitle(title);
+    }
+
+    @Override
+    public void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        getDelegate().onConfigurationChanged(newConfig);
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+        getDelegate().onStop();
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        getDelegate().onDestroy();
+    }
+
+    @Override
+    public void invalidateOptionsMenu() {
+        getDelegate().invalidateOptionsMenu();
+    }
+
+    private AppCompatDelegate getDelegate() {
+        if (mDelegate == null) {
+            mDelegate = AppCompatDelegate.create(this, null);
+        }
+        return mDelegate;
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/BackupRestoreProgressActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/BackupRestoreProgressActivity.java
new file mode 100644
index 0000000..2d9f002
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/BackupRestoreProgressActivity.java
@@ -0,0 +1,241 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities;
+
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+
+import androidx.activity.OnBackPressedCallback;
+import androidx.annotation.Nullable;
+import androidx.documentfile.provider.DocumentFile;
+
+import com.google.android.material.dialog.MaterialAlertDialogBuilder;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.util.backup.AbstractZipBackupJob;
+import nodomain.freeyourgadget.gadgetbridge.util.backup.ZipBackupCallback;
+import nodomain.freeyourgadget.gadgetbridge.util.backup.ZipBackupExportJob;
+import nodomain.freeyourgadget.gadgetbridge.util.backup.ZipBackupImportJob;
+
+public class BackupRestoreProgressActivity extends AbstractGBActivity {
+    private static final Logger LOG = LoggerFactory.getLogger(BackupRestoreProgressActivity.class);
+
+    public static final String EXTRA_URI = "uri";
+    public static final String EXTRA_ACTION = "action"; // import/export
+
+    private boolean jobFinished = false;
+    private Uri uri;
+    private String action;
+    private Thread mThread;
+    private AbstractZipBackupJob mZipBackupJob;
+
+    @Override
+    protected void onCreate(final Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_backup_restore_progress);
+
+        final Bundle extras = getIntent().getExtras();
+        if (extras == null) {
+            LOG.error("No extras");
+            finish();
+            return;
+        }
+
+        uri = extras.getParcelable(EXTRA_URI);
+        if (uri == null) {
+            LOG.error("No uri");
+            finish();
+            return;
+        }
+
+        action = extras.getString(EXTRA_ACTION);
+        if (action == null) {
+            LOG.error("No action");
+            finish();
+            return;
+        }
+
+        final TextView backupRestoreHint = findViewById(R.id.backupRestoreHint);
+        final ProgressBar backupRestoreProgressBar = findViewById(R.id.backupRestoreProgressBar);
+        final TextView backupRestoreProgressText = findViewById(R.id.backupRestoreProgressText);
+        final TextView backupRestoreProgressPercentage = findViewById(R.id.backupRestoreProgressPercentage);
+
+        final ZipBackupCallback zipBackupCallback = new ZipBackupCallback() {
+            @Override
+            public void onProgress(final int progress, final String message) {
+                backupRestoreProgressBar.setIndeterminate(progress == 0);
+                backupRestoreProgressBar.setProgress(progress);
+                backupRestoreProgressText.setText(message);
+                backupRestoreProgressPercentage.setText(getString(R.string.battery_percentage_str, String.valueOf(progress)));
+            }
+
+            @Override
+            public void onSuccess(final String warnings) {
+                jobFinished = true;
+                backupRestoreHint.setVisibility(View.GONE);
+                backupRestoreProgressBar.setProgress(100);
+                backupRestoreProgressPercentage.setText(getString(R.string.battery_percentage_str, "100"));
+
+                switch (action) {
+                    case "import":
+                        backupRestoreProgressText.setText(R.string.backup_restore_import_complete);
+
+                        final StringBuilder message = new StringBuilder();
+
+                        message.append(getString(R.string.backup_restore_restart_summary, getString(R.string.app_name)));
+
+                        if (warnings != null) {
+                            message.append("\n\n").append(warnings);
+                        }
+
+                        new MaterialAlertDialogBuilder(BackupRestoreProgressActivity.this)
+                                .setCancelable(false)
+                                .setIcon(R.drawable.ic_sync)
+                                .setTitle(R.string.backup_restore_restart_title)
+                                .setMessage(message.toString())
+                                .setOnCancelListener((dialog -> {
+                                    finish();
+                                    GBApplication.restart();
+                                }))
+                                .setPositiveButton(R.string.ok, (dialog, which) -> {
+                                    finish();
+                                    GBApplication.restart();
+                                }).show();
+                        break;
+                    case "export":
+                        backupRestoreProgressText.setText(R.string.backup_restore_export_complete);
+                        break;
+                }
+            }
+
+            @Override
+            public void onFailure(@Nullable final String errorMessage) {
+                jobFinished = true;
+
+                switch (action) {
+                    case "import":
+                        backupRestoreHint.setText(R.string.backup_restore_error_import);
+                        break;
+                    case "export":
+                        backupRestoreHint.setText(R.string.backup_restore_error_export);
+                        break;
+                }
+
+                backupRestoreProgressText.setText(errorMessage);
+                backupRestoreProgressPercentage.setVisibility(View.GONE);
+
+                if ("export".equals(action)) {
+                    final DocumentFile documentFile = DocumentFile.fromSingleUri(BackupRestoreProgressActivity.this, uri);
+                    if (documentFile != null) {
+                        documentFile.delete();
+                    }
+                }
+            }
+        };
+
+        switch (action) {
+            case "import":
+                backupRestoreHint.setText(getString(R.string.backup_restore_do_not_exit, getString(R.string.backup_restore_importing)));
+                mZipBackupJob = new ZipBackupImportJob(GBApplication.getContext(), zipBackupCallback, uri);
+                break;
+            case "export":
+                backupRestoreHint.setText(getString(R.string.backup_restore_do_not_exit, getString(R.string.backup_restore_exporting)));
+                mZipBackupJob = new ZipBackupExportJob(GBApplication.getContext(), zipBackupCallback, uri);
+                break;
+            default:
+                LOG.error("Unknown action {}", action);
+                finish();
+                return;
+        }
+
+        mThread = new Thread(mZipBackupJob, "gb-backup-restore");
+        mThread.start();
+
+        getOnBackPressedDispatcher().addCallback(new OnBackPressedCallback(true) {
+            @Override
+            public void handleOnBackPressed() {
+                confirmExit();
+            }
+        });
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(final MenuItem item) {
+        final int itemId = item.getItemId();
+        if (itemId == android.R.id.home) {
+            // back button
+            confirmExit();
+            return true;
+        }
+        return super.onOptionsItemSelected(item);
+    }
+
+    private void confirmExit() {
+        if (jobFinished) {
+            finish();
+            return;
+        }
+
+        final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this)
+                .setCancelable(true)
+                .setIcon(R.drawable.ic_warning)
+                .setTitle(R.string.backup_restore_abort_title)
+                .setPositiveButton(R.string.backup_restore_abort_title, (dialog, which) -> {
+                    if (mZipBackupJob != null) {
+                        LOG.info("Aborting {}", action);
+                        final Handler handler = new Handler(getMainLooper());
+                        mZipBackupJob.abort();
+                        new Thread(() -> {
+                            try {
+                                mThread.join(60_000);
+                            } catch (final InterruptedException ignored) {
+                            }
+                            handler.post(() -> {
+                                LOG.info("Aborted {}", action);
+                                if ("export".equals(action)) {
+                                    // Delete the incomplete export file
+                                    final DocumentFile documentFile = DocumentFile.fromSingleUri(BackupRestoreProgressActivity.this, uri);
+                                    if (documentFile != null) {
+                                        documentFile.delete();
+                                    }
+                                }
+                                finish();
+                            });
+                        }).start();
+                    }
+                })
+                .setNegativeButton(R.string.Cancel, (dialog, which) -> {
+                });
+
+        if ("import".equals(action)) {
+            builder.setMessage(R.string.backup_restore_abort_import_confirmation);
+        } else {
+            builder.setMessage(R.string.backup_restore_abort_export_confirmation);
+        }
+
+        builder.show();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/BatteryInfoActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/BatteryInfoActivity.java
new file mode 100644
index 0000000..dac71e1
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/BatteryInfoActivity.java
@@ -0,0 +1,241 @@
+/*  Copyright (C) 2021-2024 Daniel Dakhno, Jos茅 Rebelo, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities;
+
+import android.app.DatePickerDialog;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.DatePicker;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.SeekBar;
+import android.widget.TextView;
+
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Calendar;
+import java.util.Date;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.BatteryConfig;
+import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
+
+public class BatteryInfoActivity extends AbstractGBActivity {
+    private static final Logger LOG = LoggerFactory.getLogger(BatteryInfoActivity.class);
+    GBDevice gbDevice;
+    private int timeFrom;
+    private int timeTo;
+    private int batteryIndex = 0;
+    TextView battery_status_battery_level_text;
+    TextView battery_status_battery_voltage;
+    LocalBroadcastManager localBroadcastManager;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+
+        super.onCreate(savedInstanceState);
+
+        final Context appContext = this.getApplicationContext();
+        if (appContext instanceof GBApplication) {
+            setContentView(R.layout.activity_battery_info);
+        }
+
+        Intent intent = getIntent();
+        Bundle bundle = intent.getExtras();
+        if (bundle != null) {
+            gbDevice = bundle.getParcelable(GBDevice.EXTRA_DEVICE);
+            batteryIndex = bundle.getInt(GBDevice.BATTERY_INDEX, 0);
+        } else {
+            throw new IllegalArgumentException("Must provide a device when invoking this activity");
+        }
+
+        localBroadcastManager = LocalBroadcastManager.getInstance(this);
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(GBDevice.ACTION_DEVICE_CHANGED);
+        localBroadcastManager.registerReceiver(commandReceiver, filter);
+
+        final BatteryInfoChartFragment batteryInfoChartFragment = new BatteryInfoChartFragment();
+
+        getSupportFragmentManager()
+                .beginTransaction()
+                .replace(R.id.batteryChartFragmentHolder, batteryInfoChartFragment)
+                .commit();
+
+        timeTo = (int) (System.currentTimeMillis() / 1000);
+
+        batteryInfoChartFragment.setDateAndGetData(gbDevice, batteryIndex, timeFrom, timeTo);
+
+        TextView battery_status_device_name_text = (TextView) findViewById(R.id.battery_status_device_name);
+        battery_status_battery_voltage = (TextView) findViewById(R.id.battery_status_battery_voltage);
+        TextView battery_status_extra_name = (TextView) findViewById(R.id.battery_status_extra_name);
+        final TextView battery_status_date_from_text = (TextView) findViewById(R.id.battery_status_date_from_text);
+        final TextView battery_status_date_to_text = (TextView) findViewById(R.id.battery_status_date_to_text);
+        final SeekBar battery_status_time_span_seekbar = (SeekBar) findViewById(R.id.battery_status_time_span_seekbar);
+        final TextView battery_status_time_span_text = (TextView) findViewById(R.id.battery_status_time_span_text);
+
+        LinearLayout battery_status_date_to_layout = (LinearLayout) findViewById(R.id.battery_status_date_to_layout);
+
+        battery_status_time_span_seekbar.setMax(5);
+        battery_status_time_span_seekbar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
+
+            @Override
+            public void onProgressChanged(SeekBar seekBar, int i, boolean b) {
+                String text;
+                switch (i) {
+                    case 0:
+                        text = getString(R.string.calendar_day);
+                        timeFrom = DateTimeUtils.shiftDays(timeTo, -1);
+                        break;
+                    case 1:
+                        text = getString(R.string.calendar_week);
+                        timeFrom = DateTimeUtils.shiftDays(timeTo, -7);
+                        break;
+                    case 2:
+                        text = getString(R.string.calendar_two_weeks);
+                        timeFrom = DateTimeUtils.shiftDays(timeTo, -14);
+                        break;
+                    case 3:
+                        text = getString(R.string.calendar_month);
+                        timeFrom = DateTimeUtils.shiftMonths(timeTo, -1);
+                        break;
+                    case 4:
+                        text = getString(R.string.calendar_six_months);
+                        timeFrom = DateTimeUtils.shiftMonths(timeTo, -6);
+                        break;
+                    case 5:
+                        text = getString(R.string.calendar_year);
+                        timeFrom = DateTimeUtils.shiftMonths(timeTo, -12);
+                        break;
+                    default:
+                        text = getString(R.string.calendar_two_weeks);
+                        timeFrom = DateTimeUtils.shiftDays(timeTo, -14);
+                }
+
+                battery_status_time_span_text.setText(text);
+                battery_status_date_from_text.setText(DateTimeUtils.formatDate(new Date(timeFrom * 1000L)));
+                battery_status_date_to_text.setText(DateTimeUtils.formatDate(new Date(timeTo * 1000L)));
+                batteryInfoChartFragment.setDateAndGetData(gbDevice, batteryIndex, timeFrom, timeTo);
+            }
+
+
+            @Override
+            public void onStartTrackingTouch(SeekBar seekBar) {
+
+            }
+
+            @Override
+            public void onStopTrackingTouch(SeekBar seekBar) {
+
+            }
+        });
+
+        //Button battery_status_calendar_button = findViewById(R.id.battery_status_calendar_button);
+        battery_status_date_to_layout.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+
+                final Calendar currentDate = Calendar.getInstance();
+                currentDate.setTimeInMillis(timeTo * 1000L);
+                Context context = getApplicationContext();
+
+                if (context instanceof GBApplication) {
+                    new DatePickerDialog(BatteryInfoActivity.this, new DatePickerDialog.OnDateSetListener() {
+                        @Override
+                        public void onDateSet(DatePicker view, int year, int monthOfYear, int dayOfMonth) {
+
+                            Calendar date = Calendar.getInstance();
+                            date.set(year, monthOfYear, dayOfMonth);
+                            timeTo = (int) (date.getTimeInMillis() / 1000);
+                            battery_status_date_to_text.setText(DateTimeUtils.formatDate(new Date(timeTo * 1000L)));
+                            battery_status_time_span_seekbar.setProgress(0);
+                            battery_status_time_span_seekbar.setProgress(1);
+
+                            batteryInfoChartFragment.setDateAndGetData(gbDevice, batteryIndex, timeFrom, timeTo);
+                        }
+                    }, currentDate.get(Calendar.YEAR), currentDate.get(Calendar.MONTH), currentDate.get(Calendar.DATE)).show();
+                }
+            }
+        });
+
+
+        battery_status_time_span_seekbar.setProgress(2);
+
+        DeviceCoordinator coordinator = gbDevice.getDeviceCoordinator();
+
+        ImageView battery_status_device_icon = findViewById(R.id.battery_status_device_icon);
+        battery_status_device_icon.setImageResource(gbDevice.getEnabledDisabledIconResource());
+        battery_status_battery_level_text = (TextView) findViewById(R.id.battery_status_battery_level);
+        battery_status_device_name_text.setText(gbDevice.getAliasOrName());
+
+        setBatteryLabels();
+        for (BatteryConfig batteryConfig : coordinator.getBatteryConfig(gbDevice)) {
+            if (batteryConfig.getBatteryIndex() == batteryIndex) {
+                if (batteryConfig.getBatteryLabel() != GBDevice.BATTERY_LABEL_DEFAULT) {
+                    battery_status_extra_name.setText(batteryConfig.getBatteryLabel());
+                }
+                if (batteryConfig.getBatteryIcon() != GBDevice.BATTERY_ICON_DEFAULT) {
+                    battery_status_device_icon.setImageResource(batteryConfig.getBatteryIcon());
+                    if (gbDevice.isInitialized()) {
+                        battery_status_device_icon.setColorFilter(this.getResources().getColor(R.color.accent));
+                    }
+                }
+            }
+        }
+    }
+
+    private void setBatteryLabels() {
+        String level = gbDevice.getBatteryLevel(batteryIndex) > 0 ? String.format("%1s%%", gbDevice.getBatteryLevel(batteryIndex)) : "";
+        String voltage = gbDevice.getBatteryVoltage(batteryIndex) > 0 ? String.format("%1sV", gbDevice.getBatteryVoltage(batteryIndex)) : "";
+        battery_status_battery_level_text.setText(level);
+        battery_status_battery_voltage.setText(voltage);
+    }
+
+    BroadcastReceiver commandReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            LOG.debug("device receiver received " + intent.getAction());
+            if (intent.getAction().equals(GBDevice.ACTION_DEVICE_CHANGED)) {
+                GBDevice newDevice = intent.getParcelableExtra(GBDevice.EXTRA_DEVICE);
+                if (newDevice.equals(gbDevice)) {
+                    gbDevice = newDevice;
+                    setBatteryLabels();
+                }
+
+            }
+        }
+    };
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        LocalBroadcastManager.getInstance(this).unregisterReceiver(commandReceiver);
+    }
+
+}
+
+
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/BatteryInfoChartFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/BatteryInfoChartFragment.java
new file mode 100644
index 0000000..b9ea582
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/BatteryInfoChartFragment.java
@@ -0,0 +1,292 @@
+/*  Copyright (C) 2021-2024 Jos茅 Rebelo, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import androidx.core.content.ContextCompat;
+
+import com.github.mikephil.charting.charts.LineChart;
+import com.github.mikephil.charting.components.Legend;
+import com.github.mikephil.charting.components.MarkerView;
+import com.github.mikephil.charting.components.XAxis;
+import com.github.mikephil.charting.components.YAxis;
+import com.github.mikephil.charting.data.ChartData;
+import com.github.mikephil.charting.data.Entry;
+import com.github.mikephil.charting.data.LineData;
+import com.github.mikephil.charting.data.LineDataSet;
+import com.github.mikephil.charting.formatter.ValueFormatter;
+import com.github.mikephil.charting.highlight.Highlight;
+import com.github.mikephil.charting.utils.MPPointF;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+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 de.greenrobot.dao.query.QueryBuilder;
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.charts.DefaultChartsData;
+import nodomain.freeyourgadget.gadgetbridge.activities.charts.TimestampTranslation;
+import nodomain.freeyourgadget.gadgetbridge.database.DBAccess;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.entities.BatteryLevel;
+import nodomain.freeyourgadget.gadgetbridge.entities.BatteryLevelDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+
+public class BatteryInfoChartFragment extends AbstractGBFragment {
+    private static final Logger LOG = LoggerFactory.getLogger(BatteryInfoChartFragment.class);
+    protected int BACKGROUND_COLOR;
+    protected int DESCRIPTION_COLOR;
+    protected int CHART_TEXT_COLOR;
+    protected int LEGEND_TEXT_COLOR;
+    protected String BATTERY_LABEL;
+
+    private LineChart mChart;
+    private int startTime;
+    private int endTime;
+    private GBDevice gbDevice;
+    private int batteryIndex;
+
+    public void setDateAndGetData(GBDevice gbDevice, int batteryIndex, long startTime, long endTime) {
+        this.startTime = (int) startTime;
+        this.endTime = (int) endTime;
+        this.gbDevice = gbDevice;
+        this.batteryIndex = batteryIndex;
+        try {
+            createRefreshTask("Visualizing data", getActivity()).execute();
+        } catch (Exception e) {
+            LOG.debug("Unable to fill charts data right now:", e);
+        }
+    }
+
+
+    protected RefreshTask createRefreshTask(String task, Context context) {
+        return new RefreshTask(task, context);
+    }
+
+    private DefaultBatteryChartsData fill_dcd(List<? extends BatteryLevel> samples) {
+        TimestampTranslation tsTranslation = new TimestampTranslation();
+        List<Entry> entries = new ArrayList<Entry>();
+        int firstTs = 0;
+
+        for (BatteryLevel sample : samples) {
+            entries.add(new Entry(tsTranslation.shorten(sample.getTimestamp()), sample.getLevel()));
+            if (firstTs == 0) {
+                firstTs = sample.getTimestamp();
+            }
+        }
+
+        LineDataSet dataSet = new LineDataSet(entries, BATTERY_LABEL);
+        dataSet.setLineWidth(2.2f);
+        dataSet.setMode(LineDataSet.Mode.HORIZONTAL_BEZIER);
+        dataSet.setCubicIntensity(0.1f);
+        dataSet.setDrawCircles(false);
+        dataSet.setCircleRadius(2f);
+        dataSet.setDrawValues(true);
+        dataSet.setValueTextColor(CHART_TEXT_COLOR);
+        dataSet.setHighlightEnabled(true);
+        dataSet.setHighlightEnabled(true);
+        LineData lineData = new LineData(dataSet);
+
+        return new DefaultBatteryChartsData(lineData, new customFormatter(tsTranslation), firstTs);
+
+    }
+
+    private void init() {
+        BACKGROUND_COLOR = GBApplication.getBackgroundColor(getContext());
+        LEGEND_TEXT_COLOR = DESCRIPTION_COLOR = GBApplication.getTextColor(getContext());
+        CHART_TEXT_COLOR = GBApplication.getSecondaryTextColor(getContext());
+        BATTERY_LABEL = getString(R.string.battery_level);
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+                             Bundle savedInstanceState) {
+        init();
+        View rootView = inflater.inflate(R.layout.fragment_charts, container, false);
+        mChart = rootView.findViewById(R.id.activitysleepchart);
+        if (this.gbDevice != null) {
+            setupChart();
+            createRefreshTask("Visualizing data", getActivity()).execute();
+        }
+        return rootView;
+    }
+
+    @Override
+    public String getTitle() {
+        return "";
+    }
+
+    private void setupChart() {
+        LEGEND_TEXT_COLOR = GBApplication.getTextColor(getContext());
+        mChart.getLegend().setTextColor(LEGEND_TEXT_COLOR);
+        mChart.setBackgroundColor(BACKGROUND_COLOR);
+        mChart.getDescription().setTextColor(DESCRIPTION_COLOR);
+        mChart.setTouchEnabled(true);
+        mChart.getLegend().setHorizontalAlignment(Legend.LegendHorizontalAlignment.CENTER);
+        mChart.getDescription().setEnabled(false);
+
+        XAxis x = mChart.getXAxis();
+        x.setDrawLabels(true);
+        x.setDrawGridLines(false);
+        x.setEnabled(true);
+        mChart.getXAxis().setSpaceMin(0.5f);
+        x.setLabelCount(3);
+        x.setTextColor(CHART_TEXT_COLOR);
+
+        YAxis yAxisLeft = mChart.getAxisLeft();
+        yAxisLeft.setAxisMaximum(100L);
+        yAxisLeft.setAxisMinimum(0);
+        yAxisLeft.setEnabled(true);
+        yAxisLeft.setTextColor(CHART_TEXT_COLOR);
+
+        YAxis yAxisRight = mChart.getAxisRight();
+        yAxisRight.setAxisMaximum(100L);
+        yAxisRight.setAxisMinimum(0);
+        yAxisRight.setEnabled(true);
+        yAxisRight.setTextColor(CHART_TEXT_COLOR);
+    }
+
+    private List<? extends BatteryLevel> getBatteryLevels(DBHandler db, GBDevice device, int batteryIndex, int tsFrom, int tsTo) {
+        BatteryLevelDao batteryLevelDao = db.getDaoSession().getBatteryLevelDao();
+        Device dbDevice = DBHelper.findDevice(device, db.getDaoSession());
+        QueryBuilder<BatteryLevel> qb = batteryLevelDao.queryBuilder();
+
+        qb.where(BatteryLevelDao.Properties.DeviceId.eq(dbDevice.getId())).orderAsc(BatteryLevelDao.Properties.Timestamp);
+        qb.where(BatteryLevelDao.Properties.BatteryIndex.eq(batteryIndex));
+        qb.where(BatteryLevelDao.Properties.Timestamp.gt(tsFrom));
+        qb.where(BatteryLevelDao.Properties.Timestamp.lt(tsTo));
+
+        List<BatteryLevel> levels = new ArrayList<>();
+        levels.addAll(qb.build().list());
+        return levels;
+    }
+
+    protected static class customFormatter extends ValueFormatter {
+        private final TimestampTranslation tsTranslation;
+        SimpleDateFormat annotationDateFormat = new SimpleDateFormat("dd.MM HH:mm");
+        Calendar cal = GregorianCalendar.getInstance();
+
+        public customFormatter(TimestampTranslation tsTranslation) {
+            this.tsTranslation = tsTranslation;
+        }
+
+        @Override
+        public String getFormattedValue(float value) {
+            cal.clear();
+            int ts = (int) value;
+            cal.setTimeInMillis(tsTranslation.toOriginalValue(ts) * 1000L);
+            Date date = cal.getTime();
+            return annotationDateFormat.format(date);
+        }
+    }
+
+    public class RefreshTask extends DBAccess {
+
+        public RefreshTask(String task, Context context) {
+            super(task, context);
+        }
+
+        @Override
+        protected void doInBackground(DBHandler handler) {
+            List<? extends BatteryLevel> samples = getBatteryLevels(handler, gbDevice, batteryIndex, startTime, endTime);
+            DefaultBatteryChartsData dcd = null;
+            try {
+                dcd = fill_dcd(samples);
+            } catch (Exception e) {
+                LOG.debug("Unable to get charts data right now:", e);
+            }
+            if (dcd != null && mChart != null) {
+                mChart.setTouchEnabled(true);
+                mChart.setMarker(new batteryValuesAndDateMarker(getContext(), R.layout.custom_chart_marker, dcd.firstTs));
+                mChart.getXAxis().setValueFormatter(dcd.getXValueFormatter());
+                mChart.setData((LineData) dcd.getData());
+            }
+        }
+
+        @Override
+        protected void onPostExecute(Object o) {
+            mChart.invalidate();
+        }
+    }
+
+    private class DefaultBatteryChartsData extends DefaultChartsData {
+        public int firstTs;
+
+        public DefaultBatteryChartsData(ChartData data, ValueFormatter xValueFormatter, int ts) {
+            super(data, xValueFormatter);
+            firstTs = ts;
+        }
+    }
+
+
+    public class batteryValuesAndDateMarker extends MarkerView {
+
+        customFormatter formatter;
+        private TextView top_text;
+        private TextView bottom_text;
+        private MPPointF mOffset;
+        private int firstTs;
+
+        public batteryValuesAndDateMarker(Context context, int layoutResource, int ts) {
+            super(context, layoutResource);
+            TimestampTranslation tsTranslation = new TimestampTranslation();
+            formatter = new customFormatter(tsTranslation);
+            top_text = (TextView) findViewById(R.id.chart_marker_item_top);
+            bottom_text = (TextView) findViewById(R.id.chart_marker_item_bottom);
+            firstTs = ts;
+        }
+
+        // callbacks everytime the MarkerView is redrawn, can be used to update the
+        // content (user-interface)
+        @Override
+        public void refreshContent(Entry e, Highlight highlight) {
+
+            top_text.setText(String.format("%1s%%", (int) e.getY()));
+            bottom_text.setText(formatter.getFormattedValue(e.getX() + firstTs));
+
+            // this will perform necessary layouting
+            super.refreshContent(e, highlight);
+        }
+
+        @Override
+        public MPPointF getOffset() {
+
+            if (mOffset == null) {
+                // center the marker horizontally and vertically
+                mOffset = new MPPointF(-(getWidth() / 2) + 20, -getHeight() - 10);
+            }
+            return mOffset;
+        }
+    }
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/CalBlacklistActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/CalBlacklistActivity.java
new file mode 100644
index 0000000..6c849b4
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/CalBlacklistActivity.java
@@ -0,0 +1,171 @@
+/*  Copyright (C) 2017-2024 Carsten Pfeiffer, Daniele Gobbetti, Jos茅 Rebelo,
+    Ludovic Jozeau
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities;
+
+import android.Manifest;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.database.Cursor;
+import android.graphics.Paint;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.CalendarContract;
+import android.view.LayoutInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.CheckBox;
+import android.widget.ListView;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.core.app.ActivityCompat;
+import androidx.core.app.NavUtils;
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+import nodomain.freeyourgadget.gadgetbridge.util.calendar.CalendarManager;
+
+
+public class CalBlacklistActivity extends AbstractGBActivity {
+
+    private final String[] EVENT_PROJECTION = new String[]{
+            CalendarContract.Calendars.CALENDAR_DISPLAY_NAME,
+            CalendarContract.Calendars.ACCOUNT_NAME,
+            CalendarContract.Calendars.CALENDAR_COLOR
+    };
+    private ArrayList<Calendar> calendarsArrayList;
+
+    private GBDevice gbDevice;
+    private CalendarManager calendarManager;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_calblacklist);
+        ListView calListView = (ListView) findViewById(R.id.calListView);
+
+        gbDevice = getIntent().getParcelableExtra(GBDevice.EXTRA_DEVICE);
+        calendarManager = new CalendarManager(this, gbDevice.getAddress());
+
+        final Uri uri = CalendarContract.Calendars.CONTENT_URI;
+        if (ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_CALENDAR) != PackageManager.PERMISSION_GRANTED) {
+            GB.toast(this, "Calendar permission not granted. Nothing to do.", Toast.LENGTH_SHORT, GB.WARN);
+            return;
+        }
+        try (Cursor cur = getContentResolver().query(uri, EVENT_PROJECTION, null, null, null)) {
+            calendarsArrayList = new ArrayList<>();
+            while (cur != null && cur.moveToNext()) {
+                calendarsArrayList.add(new Calendar(cur.getString(0), cur.getString(1), cur.getInt(2)));
+            }
+        }
+
+        ArrayAdapter<Calendar> calAdapter = new CalendarListAdapter(this, calendarsArrayList);
+        calListView.setAdapter(calAdapter);
+        calListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+            @Override
+            public void onItemClick(AdapterView<?> parent, View view, int i, long id) {
+                Calendar item = calendarsArrayList.get(i);
+                CheckBox selected = (CheckBox) view.findViewById(R.id.item_checkbox);
+                toggleEntry(view);
+                if (selected.isChecked()) {
+                    calendarManager.addCalendarToBlacklist(item.getUniqueString());
+                } else {
+                    calendarManager.removeFromCalendarBlacklist(item.getUniqueString());
+                }
+            }
+        });
+
+
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(final MenuItem item) {
+        final int itemId = item.getItemId();
+        if (itemId == android.R.id.home) {
+            NavUtils.navigateUpFromSameTask(this);
+            return true;
+        }
+        return super.onOptionsItemSelected(item);
+    }
+
+    private void toggleEntry(View view) {
+        TextView name = (TextView) view.findViewById(R.id.calendar_name);
+        CheckBox checked = (CheckBox) view.findViewById(R.id.item_checkbox);
+
+        name.setPaintFlags(name.getPaintFlags() ^ Paint.STRIKE_THRU_TEXT_FLAG);
+        checked.toggle();
+    }
+
+    class Calendar {
+        private final String displayName;
+        private final String accountName;
+        private final int color;
+
+        public Calendar(String displayName, String accountName, int color) {
+            this.displayName = displayName;
+            this.accountName = accountName;
+            this.color = color;
+        }
+
+        public String getUniqueString() {
+            return accountName + '/' + displayName;
+        }
+    }
+
+    private class CalendarListAdapter extends ArrayAdapter<Calendar> {
+
+        CalendarListAdapter(@NonNull Context context, @NonNull List<Calendar> calendars) {
+            super(context, 0, calendars);
+        }
+
+        @NonNull
+        @Override
+        public View getView(int position, @Nullable View view, @NonNull ViewGroup parent) {
+            Calendar item = getItem(position);
+
+            if (view == null) {
+                LayoutInflater inflater = (LayoutInflater) super.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+                view = inflater.inflate(R.layout.item_cal_blacklist, parent, false);
+            }
+
+            View color = view.findViewById(R.id.calendar_color);
+            TextView name = (TextView) view.findViewById(R.id.calendar_name);
+            TextView ownerAccount = (TextView) view.findViewById(R.id.calendar_owner_account);
+            CheckBox checked = (CheckBox) view.findViewById(R.id.item_checkbox);
+
+            if (calendarManager.calendarIsBlacklisted(item.getUniqueString()) && !checked.isChecked() ||
+                    !calendarManager.calendarIsBlacklisted(item.getUniqueString()) && checked.isChecked()) {
+                toggleEntry(view);
+            }
+            color.setBackgroundColor(item.color);
+            name.setText(item.displayName);
+            ownerAccount.setText(item.accountName);
+
+            return view;
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/CameraActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/CameraActivity.java
new file mode 100644
index 0000000..a481dc3
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/CameraActivity.java
@@ -0,0 +1,218 @@
+/*  Copyright (C) 2024 Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities;
+
+import android.Manifest;
+import android.content.ContentValues;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.provider.MediaStore;
+import android.widget.Toast;
+
+import androidx.activity.result.ActivityResultCallback;
+import androidx.activity.result.ActivityResultLauncher;
+import androidx.activity.result.contract.ActivityResultContracts;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.camera.core.CameraSelector;
+import androidx.camera.core.ImageCapture;
+import androidx.camera.core.ImageCaptureException;
+import androidx.camera.core.Preview;
+import androidx.camera.lifecycle.ProcessCameraProvider;
+import androidx.camera.view.PreviewView;
+import androidx.core.content.ContextCompat;
+
+import com.google.common.util.concurrent.ListenableFuture;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.concurrent.ExecutionException;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventCameraRemote;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class CameraActivity extends AppCompatActivity {
+    private static final Logger LOG = LoggerFactory.getLogger(CameraActivity.class);
+
+    public static final String intentExtraEvent = "EVENT";
+
+    private ListenableFuture<ProcessCameraProvider> cameraProviderListenableFuture;
+    private ImageCapture imageCapture;
+
+    private boolean reportClosing = true;
+
+    public static boolean supportsCamera() {
+        return GBApplication.getContext().getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY);
+    }
+
+    public static boolean hasCameraPermission() {
+        return GBApplication.getContext().checkCallingOrSelfPermission(Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED;
+    }
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_camera);
+
+        if (!supportsCamera()) {
+            LOG.error("No camera support");
+            GB.toast(getString(R.string.toast_camera_support_required), Toast.LENGTH_SHORT, GB.ERROR);
+            GBApplication.deviceService().onCameraStatusChange(GBDeviceEventCameraRemote.Event.EXCEPTION, null);
+            reportClosing = false;
+            finish();
+            return;
+        }
+
+        if (!hasCameraPermission()) {
+            LOG.info("Requesting camera permission");
+
+            ActivityResultLauncher<String> requestPermissionLauncher =
+                    registerForActivityResult(new ActivityResultContracts.RequestPermission(), new ActivityResultCallback<Boolean>() {
+                        @Override
+                        public void onActivityResult(Boolean isGranted) {
+                            if (isGranted) {
+                                initCamera();
+                            } else {
+                                LOG.error("Did not receive camera permission");
+                                GB.toast(getString(R.string.toast_camera_permission_required), Toast.LENGTH_SHORT, GB.ERROR);
+                                GBApplication.deviceService().onCameraStatusChange(GBDeviceEventCameraRemote.Event.EXCEPTION, null);
+                                reportClosing = false;
+                                finish();
+                            }
+                        }
+                    });
+            requestPermissionLauncher.launch(Manifest.permission.CAMERA);
+            return;
+        }
+
+        initCamera();
+    }
+
+    private void initCamera() {
+        cameraProviderListenableFuture = ProcessCameraProvider.getInstance(this);
+        cameraProviderListenableFuture.addListener(new Runnable() {
+            @Override
+            public void run() {
+                try {
+                    ProcessCameraProvider cameraProvider = cameraProviderListenableFuture.get();
+
+                    PreviewView previewView = findViewById(R.id.preview);
+
+                    Preview preview = new Preview.Builder().build();
+
+                    CameraSelector cameraSelector = new CameraSelector.Builder()
+                            .requireLensFacing(CameraSelector.LENS_FACING_BACK) // TODO: make setting
+                            .build();
+
+                    preview.setSurfaceProvider(previewView.getSurfaceProvider());
+
+                    imageCapture = new ImageCapture.Builder()
+                            .setTargetRotation(preview.getTargetRotation())
+                            .build();
+
+                    cameraProvider.bindToLifecycle(
+                            CameraActivity.this,
+                            cameraSelector,
+                            imageCapture,
+                            preview
+                    );
+                } catch (ExecutionException | InterruptedException e) {
+                    throw new RuntimeException(e);
+                }
+            }
+        }, ContextCompat.getMainExecutor(this));
+
+        handleIntent(getIntent());
+    }
+
+    @Override
+    protected void onNewIntent(Intent intent) {
+        super.onNewIntent(intent);
+        handleIntent(intent);
+    }
+
+    private void handleIntent(Intent intent) {
+        if (!intent.hasExtra(intentExtraEvent)) {
+            this.finish();
+            return;
+        }
+
+        GBDeviceEventCameraRemote.Event event = GBDeviceEventCameraRemote.intToEvent(intent.getIntExtra(intentExtraEvent, 0));
+
+        LOG.info("Camera received event: " + event.name());
+
+        // Nothing to do for unknown events
+
+        if (event == GBDeviceEventCameraRemote.Event.CLOSE_CAMERA) {
+            finish();
+        } else if (event == GBDeviceEventCameraRemote.Event.OPEN_CAMERA) {
+             GBApplication.deviceService().onCameraStatusChange(GBDeviceEventCameraRemote.Event.OPEN_CAMERA, null);
+        } else if (event == GBDeviceEventCameraRemote.Event.TAKE_PICTURE) {
+            ContentValues contentValues = new ContentValues();
+            contentValues.put(MediaStore.Images.Media.TITLE, "Gadgetbridge photo");
+            contentValues.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
+
+            ImageCapture.OutputFileOptions outputFileOptions = new ImageCapture.OutputFileOptions.Builder(
+                    getContentResolver(),
+                    MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
+                    contentValues
+            ).build();
+            imageCapture.takePicture(outputFileOptions, ContextCompat.getMainExecutor(this), new ImageCapture.OnImageSavedCallback() {
+                @Override
+                public void onImageSaved(@NonNull ImageCapture.OutputFileResults outputFileResults) {
+                    if (outputFileResults.getSavedUri() == null) {
+                        // Shouldn't ever happen
+                        GBApplication.deviceService().onCameraStatusChange(GBDeviceEventCameraRemote.Event.EXCEPTION, null);
+                        return;
+                    }
+
+                    // TODO: improve feedback that the photo has been taken
+                    GB.toast(
+                            String.format(getString(R.string.toast_camera_photo_taken),
+                                    outputFileResults.getSavedUri().getPath()),
+                            Toast.LENGTH_LONG,
+                            GB.INFO
+                    );
+
+                    GBApplication.deviceService().onCameraStatusChange(
+                            GBDeviceEventCameraRemote.Event.TAKE_PICTURE,
+                            outputFileResults.getSavedUri().getPath()
+                    );
+                }
+
+                @Override
+                public void onError(@NonNull ImageCaptureException exception) {
+                    LOG.error("Failed to save image", exception);
+                    GBApplication.deviceService().onCameraStatusChange(GBDeviceEventCameraRemote.Event.EXCEPTION, null);
+                }
+            });
+        }
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+
+        if (reportClosing)
+            GBApplication.deviceService().onCameraStatusChange(GBDeviceEventCameraRemote.Event.CLOSE_CAMERA, null);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ConfigureAlarms.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ConfigureAlarms.java
new file mode 100644
index 0000000..7b3f545
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ConfigureAlarms.java
@@ -0,0 +1,194 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, Carsten Pfeiffer, Damien
+    Gaignon, Daniel Dakhno, Daniele Gobbetti, Dmitry Markin, Jos茅 Rebelo,
+    Lem Dulfo, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.view.MenuItem;
+
+import androidx.annotation.NonNull;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.adapter.GBAlarmListAdapter;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.entities.Alarm;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.entities.User;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
+import nodomain.freeyourgadget.gadgetbridge.util.AlarmUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
+
+
+public class ConfigureAlarms extends AbstractGBActivity {
+    private static final Logger LOG = LoggerFactory.getLogger(ConfigureAlarms.class);
+
+    private static final int REQ_CONFIGURE_ALARM = 1;
+
+    private GBAlarmListAdapter mGBAlarmListAdapter;
+    private boolean avoidSendAlarmsToDevice;
+    private GBDevice gbDevice;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.activity_configure_alarms);
+
+        IntentFilter filterLocal = new IntentFilter();
+        filterLocal.addAction(DeviceService.ACTION_SAVE_ALARMS);
+        LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, filterLocal);
+
+        gbDevice = getIntent().getParcelableExtra(GBDevice.EXTRA_DEVICE);
+
+        mGBAlarmListAdapter = new GBAlarmListAdapter(this);
+
+        RecyclerView alarmsRecyclerView = findViewById(R.id.alarm_list);
+        alarmsRecyclerView.setHasFixedSize(true);
+        alarmsRecyclerView.setLayoutManager(new LinearLayoutManager(this));
+        alarmsRecyclerView.setAdapter(mGBAlarmListAdapter);
+        updateAlarmsFromDB();
+    }
+
+    @Override
+    protected void onPause() {
+        if (!avoidSendAlarmsToDevice && gbDevice.isInitialized()) {
+            sendAlarmsToDevice();
+        }
+        super.onPause();
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        super.onActivityResult(requestCode, resultCode, data);
+        if (requestCode == REQ_CONFIGURE_ALARM) {
+            avoidSendAlarmsToDevice = false;
+            updateAlarmsFromDB();
+        }
+    }
+
+    /**
+     * Reads the available alarms from the database and updates the view afterwards.
+     */
+    private void updateAlarmsFromDB() {
+        List<Alarm> alarms = DBHelper.getAlarms(getGbDevice());
+        if (alarms.isEmpty()) {
+            alarms = AlarmUtils.readAlarmsFromPrefs(getGbDevice());
+            storeMigratedAlarms(alarms);
+        }
+        addMissingAlarms(alarms);
+
+        mGBAlarmListAdapter.setAlarmList(alarms);
+        mGBAlarmListAdapter.notifyDataSetChanged();
+    }
+
+    private void storeMigratedAlarms(List<Alarm> alarms) {
+        for (Alarm alarm : alarms) {
+            DBHelper.store(alarm);
+        }
+    }
+
+    private void addMissingAlarms(List<Alarm> alarms) {
+        DeviceCoordinator coordinator = getGbDevice().getDeviceCoordinator();
+        int supportedNumAlarms = coordinator.getAlarmSlotCount(getGbDevice());
+        if (supportedNumAlarms > alarms.size()) {
+            try (DBHandler db = GBApplication.acquireDB()) {
+                DaoSession daoSession = db.getDaoSession();
+                for (int position = 0; position < supportedNumAlarms; position++) {
+                    boolean found = false;
+                    for (Alarm alarm : alarms) {
+                        if (alarm.getPosition() == position) {
+                            found = true;
+                            break;
+                        }
+                    }
+                    if (!found) {
+                        LOG.info("adding missing alarm at position " + position);
+                        alarms.add(position, AlarmUtils.createDefaultAlarm(daoSession, getGbDevice(), position));
+                    }
+                }
+            } catch (Exception e) {
+                LOG.error("Error accessing database", e);
+            }
+        }
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(final MenuItem item) {
+        final int itemId = item.getItemId();
+        if (itemId == android.R.id.home) {
+            // back button
+            finish();
+            return true;
+        }
+        return super.onOptionsItemSelected(item);
+    }
+
+    public void configureAlarm(Alarm alarm) {
+        avoidSendAlarmsToDevice = true;
+        Intent startIntent = new Intent(getApplicationContext(), AlarmDetails.class);
+        startIntent.putExtra(Alarm.EXTRA_ALARM, alarm);
+        startIntent.putExtra(GBDevice.EXTRA_DEVICE, getGbDevice());
+        startActivityForResult(startIntent, REQ_CONFIGURE_ALARM);
+    }
+
+    private GBDevice getGbDevice() {
+        return gbDevice;
+    }
+
+    private void sendAlarmsToDevice() {
+        GBApplication.deviceService(gbDevice).onSetAlarms(mGBAlarmListAdapter.getAlarmList());
+    }
+
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+
+            switch (action) {
+                case DeviceService.ACTION_SAVE_ALARMS: {
+                    updateAlarmsFromDB();
+                    break;
+                }
+            }
+        }
+    };
+
+    @Override
+    protected void onDestroy() {
+        LocalBroadcastManager.getInstance(this).unregisterReceiver(mReceiver);
+        super.onDestroy();
+    }
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ConfigureContacts.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ConfigureContacts.java
new file mode 100644
index 0000000..c083e6c
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ConfigureContacts.java
@@ -0,0 +1,173 @@
+/*  Copyright (C) 2023-2024 Arjan Schrijver, Daniel Dakhno, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.MenuItem;
+
+import androidx.activity.result.ActivityResult;
+import androidx.activity.result.ActivityResultCallback;
+import androidx.activity.result.ActivityResultLauncher;
+import androidx.activity.result.contract.ActivityResultContracts;
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.google.android.material.dialog.MaterialAlertDialogBuilder;
+import com.google.android.material.floatingactionbutton.FloatingActionButton;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+import java.util.Locale;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.adapter.GBContactListAdapter;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.entities.Contact;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.entities.User;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+
+public class ConfigureContacts extends AbstractGBActivity {
+    private static final Logger LOG = LoggerFactory.getLogger(ConfigureContacts.class);
+
+    private GBContactListAdapter mGBContactListAdapter;
+    private GBDevice gbDevice;
+
+    private ActivityResultLauncher<Intent> configureContactLauncher;
+    private final ActivityResultCallback<ActivityResult> configureContactCallback = result -> {
+        if (result.getResultCode() == Activity.RESULT_OK) {
+            updateContactsFromDB();
+            sendContactsToDevice();
+        }
+    };
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.activity_configure_contacts);
+
+        gbDevice = getIntent().getParcelableExtra(GBDevice.EXTRA_DEVICE);
+
+        configureContactLauncher = registerForActivityResult(
+                new ActivityResultContracts.StartActivityForResult(),
+                configureContactCallback
+        );
+
+        mGBContactListAdapter = new GBContactListAdapter(this);
+
+        final RecyclerView contactsRecyclerView = findViewById(R.id.contact_list);
+        contactsRecyclerView.setHasFixedSize(true);
+        contactsRecyclerView.setLayoutManager(new LinearLayoutManager(this));
+        contactsRecyclerView.setAdapter(mGBContactListAdapter);
+        updateContactsFromDB();
+
+        final FloatingActionButton fab = findViewById(R.id.fab);
+        fab.setOnClickListener(v -> {
+            final DeviceCoordinator coordinator = gbDevice.getDeviceCoordinator();
+
+            int deviceSlots = coordinator.getContactsSlotCount(gbDevice);
+
+            if (mGBContactListAdapter.getItemCount() >= deviceSlots) {
+                // No more free slots
+                new MaterialAlertDialogBuilder(v.getContext())
+                        .setTitle(R.string.reminder_no_free_slots_title)
+                        .setMessage(getBaseContext().getString(R.string.contact_no_free_slots_description, String.format(Locale.getDefault(), "%d", deviceSlots)))
+                        .setIcon(R.drawable.ic_warning)
+                        .setPositiveButton(android.R.string.ok, (dialog, whichButton) -> {
+                        })
+                        .show();
+                return;
+            }
+
+            final Contact contact;
+            try (DBHandler db = GBApplication.acquireDB()) {
+                final DaoSession daoSession = db.getDaoSession();
+                final Device device = DBHelper.getDevice(gbDevice, daoSession);
+                final User user = DBHelper.getUser(daoSession);
+                contact = createDefaultContact(device, user);
+            } catch (final Exception e) {
+                LOG.error("Error accessing database", e);
+                return;
+            }
+
+            configureContact(contact);
+        });
+    }
+
+    private Contact createDefaultContact(@NonNull Device device, @NonNull User user) {
+        final Contact contact = new Contact();
+        contact.setName("");
+        contact.setNumber("");
+        contact.setDeviceId(device.getId());
+        contact.setUserId(user.getId());
+        contact.setContactId(UUID.randomUUID().toString());
+
+        return contact;
+    }
+
+    /**
+     * Reads the available contacts from the database and updates the view afterwards.
+     */
+    private void updateContactsFromDB() {
+        final List<Contact> contacts = DBHelper.getContacts(gbDevice);
+
+        mGBContactListAdapter.setContactList(contacts);
+        mGBContactListAdapter.notifyDataSetChanged();
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(final MenuItem item) {
+        final int itemId = item.getItemId();
+        if (itemId == android.R.id.home) {
+            // back button
+            finish();
+            return true;
+        }
+        return super.onOptionsItemSelected(item);
+    }
+
+    public void configureContact(final Contact contact) {
+        final Intent startIntent = new Intent(getApplicationContext(), ContactDetails.class);
+        startIntent.putExtra(GBDevice.EXTRA_DEVICE, gbDevice);
+        startIntent.putExtra(Contact.EXTRA_CONTACT, contact);
+        configureContactLauncher.launch(startIntent);
+    }
+
+    public void deleteContact(final Contact contact) {
+        DBHelper.delete(contact);
+        updateContactsFromDB();
+        sendContactsToDevice();
+    }
+
+    private void sendContactsToDevice() {
+        if (gbDevice.isInitialized()) {
+            GBApplication.deviceService(gbDevice).onSetContacts(mGBContactListAdapter.getContactList());
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ConfigureReminders.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ConfigureReminders.java
new file mode 100644
index 0000000..88db995
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ConfigureReminders.java
@@ -0,0 +1,210 @@
+/*  Copyright (C) 2021-2024 Arjan Schrijver, Daniel Dakhno, Jos茅 Rebelo,
+    Johannes Krude
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.view.MenuItem;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.google.android.material.dialog.MaterialAlertDialogBuilder;
+import com.google.android.material.floatingactionbutton.FloatingActionButton;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Calendar;
+import java.util.TimeZone;
+import java.util.List;
+import java.util.Locale;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+import nodomain.freeyourgadget.gadgetbridge.adapter.GBReminderListAdapter;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.entities.Reminder;
+import nodomain.freeyourgadget.gadgetbridge.entities.User;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+
+public class ConfigureReminders extends AbstractGBActivity {
+    private static final Logger LOG = LoggerFactory.getLogger(ConfigureReminders.class);
+
+    private static final int REQ_CONFIGURE_REMINDER = 1;
+
+    private GBReminderListAdapter mGBReminderListAdapter;
+    private GBDevice gbDevice;
+
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(final Context context, final Intent intent) {
+            if (DeviceService.ACTION_SAVE_REMINDERS.equals(intent.getAction())) {
+                updateRemindersFromDB();
+            }
+        }
+    };
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.activity_configure_reminders);
+
+        IntentFilter filterLocal = new IntentFilter();
+        filterLocal.addAction(DeviceService.ACTION_SAVE_REMINDERS);
+        LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, filterLocal);
+
+        gbDevice = getIntent().getParcelableExtra(GBDevice.EXTRA_DEVICE);
+
+        mGBReminderListAdapter = new GBReminderListAdapter(this, gbDevice.getDeviceCoordinator().getRemindersHaveTime());
+
+        final RecyclerView remindersRecyclerView = findViewById(R.id.reminder_list);
+        remindersRecyclerView.setHasFixedSize(true);
+        remindersRecyclerView.setLayoutManager(new LinearLayoutManager(this));
+        remindersRecyclerView.setAdapter(mGBReminderListAdapter);
+        updateRemindersFromDB();
+
+        final FloatingActionButton fab = findViewById(R.id.fab);
+        fab.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                final DeviceCoordinator coordinator = gbDevice.getDeviceCoordinator();
+
+                int deviceSlots = coordinator.getReminderSlotCount(gbDevice) - GBApplication.getDevicePrefs(gbDevice).getReservedReminderCalendarSlots();
+
+                if (mGBReminderListAdapter.getItemCount() >= deviceSlots) {
+                    // No more free slots
+                    new MaterialAlertDialogBuilder(v.getContext())
+                            .setTitle(R.string.reminder_no_free_slots_title)
+                            .setMessage(getBaseContext().getString(R.string.reminder_no_free_slots_description, String.format(Locale.getDefault(), "%d", deviceSlots)))
+                            .setIcon(R.drawable.ic_warning)
+                            .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+                                public void onClick(final DialogInterface dialog, final int whichButton) {
+                                }
+                            })
+                            .show();
+                    return;
+                }
+
+                final Reminder reminder;
+                try (DBHandler db = GBApplication.acquireDB()) {
+                    final DaoSession daoSession = db.getDaoSession();
+                    final Device device = DBHelper.getDevice(gbDevice, daoSession);
+                    final User user = DBHelper.getUser(daoSession);
+                    reminder = createDefaultReminder(device, user);
+                } catch (final Exception e) {
+                    LOG.error("Error accessing database", e);
+                    return;
+                }
+
+                configureReminder(reminder);
+            }
+        });
+    }
+
+    @Override
+    protected void onDestroy() {
+        LocalBroadcastManager.getInstance(this).unregisterReceiver(mReceiver);
+        super.onDestroy();
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        super.onActivityResult(requestCode, resultCode, data);
+
+        if (requestCode == REQ_CONFIGURE_REMINDER && resultCode == 1) {
+            updateRemindersFromDB();
+            sendRemindersToDevice();
+        }
+    }
+
+    private Reminder createDefaultReminder(@NonNull Device device, @NonNull User user) {
+        final Reminder reminder = new Reminder();
+        reminder.setRepetition(Reminder.ONCE);
+        if (gbDevice.getDeviceCoordinator().getRemindersHaveTime()) {
+            reminder.setDate(Calendar.getInstance().getTime());
+        } else {
+            Calendar noonUTC = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
+            noonUTC.set(noonUTC.get(Calendar.YEAR), noonUTC.get(Calendar.MONTH), noonUTC.get(Calendar.DAY_OF_MONTH), 12, 0);
+            reminder.setDate(noonUTC.getTime());
+        }
+        reminder.setMessage("");
+        reminder.setDeviceId(device.getId());
+        reminder.setUserId(user.getId());
+        reminder.setReminderId(UUID.randomUUID().toString());
+
+        return reminder;
+    }
+
+    /**
+     * Reads the available reminders from the database and updates the view afterwards.
+     */
+    private void updateRemindersFromDB() {
+        final List<Reminder> reminders = DBHelper.getReminders(gbDevice);
+
+        mGBReminderListAdapter.setReminderList(reminders);
+        mGBReminderListAdapter.notifyDataSetChanged();
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(final MenuItem item) {
+        final int itemId = item.getItemId();
+        if (itemId == android.R.id.home) {
+            // back button
+            finish();
+            return true;
+        }
+        return super.onOptionsItemSelected(item);
+    }
+
+    public void configureReminder(final Reminder reminder) {
+        final Intent startIntent = new Intent(getApplicationContext(), ReminderDetails.class);
+        startIntent.putExtra(GBDevice.EXTRA_DEVICE, gbDevice);
+        startIntent.putExtra(Reminder.EXTRA_REMINDER, reminder);
+        startActivityForResult(startIntent, REQ_CONFIGURE_REMINDER);
+    }
+
+    public void deleteReminder(final Reminder reminder) {
+        DBHelper.delete(reminder);
+        updateRemindersFromDB();
+        sendRemindersToDevice();
+    }
+
+    private void sendRemindersToDevice() {
+        if (gbDevice.isInitialized()) {
+            GBApplication.deviceService(gbDevice).onSetReminders(mGBReminderListAdapter.getReminderList());
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ConfigureWorldClocks.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ConfigureWorldClocks.java
new file mode 100644
index 0000000..7a35ff1
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ConfigureWorldClocks.java
@@ -0,0 +1,220 @@
+/*  Copyright (C) 2022-2024 Arjan Schrijver, Daniel Dakhno, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.view.MenuItem;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.core.content.ContextCompat;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.google.android.material.dialog.MaterialAlertDialogBuilder;
+import com.google.android.material.floatingactionbutton.FloatingActionButton;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+import java.util.Locale;
+import java.util.TimeZone;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.adapter.GBWorldClockListAdapter;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.entities.User;
+import nodomain.freeyourgadget.gadgetbridge.entities.WorldClock;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
+
+
+public class ConfigureWorldClocks extends AbstractGBActivity {
+    private static final Logger LOG = LoggerFactory.getLogger(ConfigureWorldClocks.class);
+
+    private static final int REQ_CONFIGURE_WORLD_CLOCK = 1;
+
+    private GBWorldClockListAdapter mGBWorldClockListAdapter;
+    private GBDevice gbDevice;
+
+    private BroadcastReceiver timeTickBroadcastReceiver;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.activity_configure_world_clocks);
+
+        gbDevice = getIntent().getParcelableExtra(GBDevice.EXTRA_DEVICE);
+
+        mGBWorldClockListAdapter = new GBWorldClockListAdapter(this);
+
+        final RecyclerView worldClocksRecyclerView = findViewById(R.id.world_clock_list);
+        worldClocksRecyclerView.setHasFixedSize(true);
+        worldClocksRecyclerView.setLayoutManager(new LinearLayoutManager(this));
+        worldClocksRecyclerView.setAdapter(mGBWorldClockListAdapter);
+        updateWorldClocksFromDB();
+
+        final FloatingActionButton fab = findViewById(R.id.fab);
+        fab.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                final DeviceCoordinator coordinator = gbDevice.getDeviceCoordinator();
+
+                int deviceSlots = coordinator.getWorldClocksSlotCount();
+
+                if (mGBWorldClockListAdapter.getItemCount() >= deviceSlots) {
+                    // No more free slots
+                    new MaterialAlertDialogBuilder(v.getContext())
+                            .setTitle(R.string.world_clock_no_free_slots_title)
+                            .setMessage(getBaseContext().getString(R.string.world_clock_no_free_slots_description, String.format(Locale.getDefault(), "%d", deviceSlots)))
+                            .setIcon(R.drawable.ic_warning)
+                            .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+                                public void onClick(final DialogInterface dialog, final int whichButton) {
+                                }
+                            })
+                            .show();
+                    return;
+                }
+
+                final WorldClock worldClock;
+                try (DBHandler db = GBApplication.acquireDB()) {
+                    final DaoSession daoSession = db.getDaoSession();
+                    final Device device = DBHelper.getDevice(gbDevice, daoSession);
+                    final User user = DBHelper.getUser(daoSession);
+                    worldClock = createDefaultWorldClock(device, user);
+                } catch (final Exception e) {
+                    LOG.error("Error accessing database", e);
+                    return;
+                }
+
+                configureWorldClock(worldClock);
+            }
+        });
+    }
+
+    @Override
+    protected void onStart() {
+        super.onStart();
+
+        timeTickBroadcastReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(final Context context, Intent intent) {
+                if (Intent.ACTION_TIME_TICK.equals(intent.getAction())) {
+                    // Refresh the UI, to update the current time in each timezone
+                    mGBWorldClockListAdapter.notifyDataSetChanged();
+                }
+            }
+        };
+
+        ContextCompat.registerReceiver(this, timeTickBroadcastReceiver, new IntentFilter(Intent.ACTION_TIME_TICK), ContextCompat.RECEIVER_EXPORTED);
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+
+        if (timeTickBroadcastReceiver != null) {
+            unregisterReceiver(timeTickBroadcastReceiver);
+        }
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        // Refresh to update the current time on each clock
+        mGBWorldClockListAdapter.notifyDataSetChanged();
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        super.onActivityResult(requestCode, resultCode, data);
+
+        if (requestCode == REQ_CONFIGURE_WORLD_CLOCK && resultCode == 1) {
+            updateWorldClocksFromDB();
+            sendWorldClocksToDevice();
+        }
+    }
+
+    private WorldClock createDefaultWorldClock(@NonNull Device device, @NonNull User user) {
+        final WorldClock worldClock = new WorldClock();
+        final String timezone = TimeZone.getDefault().getID();
+        worldClock.setEnabled(true);
+        worldClock.setTimeZoneId(timezone);
+        final String[] timezoneParts = timezone.split("/");
+        worldClock.setLabel(timezoneParts[timezoneParts.length - 1]);
+        worldClock.setCode(StringUtils.truncate(timezoneParts[timezoneParts.length - 1], 3).toUpperCase(Locale.getDefault()));
+
+        worldClock.setDeviceId(device.getId());
+        worldClock.setUserId(user.getId());
+        worldClock.setWorldClockId(UUID.randomUUID().toString());
+
+        return worldClock;
+    }
+
+    /**
+     * Reads the available worldClocks from the database and updates the view afterwards.
+     */
+    private void updateWorldClocksFromDB() {
+        final List<WorldClock> worldClocks = DBHelper.getWorldClocks(gbDevice);
+
+        mGBWorldClockListAdapter.setWorldClockList(worldClocks);
+        mGBWorldClockListAdapter.notifyDataSetChanged();
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(final MenuItem item) {
+        final int itemId = item.getItemId();
+        if (itemId == android.R.id.home) {
+            // back button
+            finish();
+            return true;
+        }
+        return super.onOptionsItemSelected(item);
+    }
+
+    public void configureWorldClock(final WorldClock worldClock) {
+        final Intent startIntent = new Intent(getApplicationContext(), WorldClockDetails.class);
+        startIntent.putExtra(GBDevice.EXTRA_DEVICE, gbDevice);
+        startIntent.putExtra(WorldClock.EXTRA_WORLD_CLOCK, worldClock);
+        startActivityForResult(startIntent, REQ_CONFIGURE_WORLD_CLOCK);
+    }
+
+    public void deleteWorldClock(final WorldClock worldClock) {
+        DBHelper.delete(worldClock);
+        updateWorldClocksFromDB();
+        sendWorldClocksToDevice();
+    }
+
+    private void sendWorldClocksToDevice() {
+        if (gbDevice.isInitialized()) {
+            GBApplication.deviceService(gbDevice).onSetWorldClocks(mGBWorldClockListAdapter.getWorldClockList());
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ContactDetails.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ContactDetails.java
new file mode 100644
index 0000000..df13be6
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ContactDetails.java
@@ -0,0 +1,154 @@
+/*  Copyright (C) 2023-2024 Daniel Dakhno, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.view.MenuItem;
+import android.widget.EditText;
+import android.widget.Toast;
+
+import androidx.annotation.NonNull;
+
+import com.google.android.material.floatingactionbutton.FloatingActionButton;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.entities.Contact;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
+
+public class ContactDetails extends AbstractGBActivity {
+    private static final Logger LOG = LoggerFactory.getLogger(ContactDetails.class);
+
+    private Contact contact;
+    private GBDevice device;
+
+    EditText contactName;
+    EditText contactNumber;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_contact_details);
+
+        contact = (Contact) getIntent().getSerializableExtra(Contact.EXTRA_CONTACT);
+
+        if (contact == null) {
+            GB.toast("No contact provided to ContactDetails Activity", Toast.LENGTH_LONG, GB.ERROR);
+            finish();
+            return;
+        }
+
+        contactName = findViewById(R.id.contact_name);
+        contactNumber = findViewById(R.id.contact_number);
+
+        device = getIntent().getParcelableExtra(GBDevice.EXTRA_DEVICE);
+        final DeviceCoordinator coordinator = device.getDeviceCoordinator();
+
+        contactName.addTextChangedListener(new TextWatcher() {
+            @Override
+            public void beforeTextChanged(final CharSequence s, int start, int count, int after) {
+            }
+
+            @Override
+            public void onTextChanged(final CharSequence s, int start, int before, int count) {
+            }
+
+            @Override
+            public void afterTextChanged(final Editable s) {
+                contact.setName(s.toString());
+            }
+        });
+
+        contactNumber.addTextChangedListener(new TextWatcher() {
+            @Override
+            public void beforeTextChanged(final CharSequence s, int start, int count, int after) {
+            }
+
+            @Override
+            public void onTextChanged(final CharSequence s, int start, int before, int count) {
+            }
+
+            @Override
+            public void afterTextChanged(final Editable s) {
+                contact.setNumber(s.toString());
+            }
+        });
+
+        final FloatingActionButton fab = findViewById(R.id.fab_save);
+        fab.setOnClickListener(view -> {
+            if (StringUtils.isNullOrEmpty(contact.getName())) {
+                GB.toast(getBaseContext().getString(R.string.contact_missing_name), Toast.LENGTH_LONG, GB.WARN);
+                return;
+            }
+
+            if (StringUtils.isNullOrEmpty(contact.getNumber())) {
+                GB.toast(getBaseContext().getString(R.string.contact_missing_number), Toast.LENGTH_LONG, GB.WARN);
+                return;
+            }
+
+            updateContact();
+            ContactDetails.this.setResult(Activity.RESULT_OK);
+            finish();
+        });
+
+        updateUiFromContact();
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(final MenuItem item) {
+        final int itemId = item.getItemId();
+        if (itemId == android.R.id.home) {
+            // back button
+            // TODO confirm when exiting without saving
+            finish();
+            return true;
+        }
+        return super.onOptionsItemSelected(item);
+    }
+
+    private void updateContact() {
+        DBHelper.store(contact);
+    }
+
+    @Override
+    protected void onSaveInstanceState(@NonNull final Bundle state) {
+        super.onSaveInstanceState(state);
+        state.putSerializable("contact", contact);
+    }
+
+    @Override
+    protected void onRestoreInstanceState(@NonNull final Bundle savedInstanceState) {
+        super.onRestoreInstanceState(savedInstanceState);
+        contact = (Contact) savedInstanceState.getSerializable("contact");
+        updateUiFromContact();
+    }
+
+    public void updateUiFromContact() {
+        contactName.setText(contact.getName());
+        contactNumber.setText(contact.getNumber());
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ControlCenterv2.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ControlCenterv2.java
new file mode 100644
index 0000000..eea3a80
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ControlCenterv2.java
@@ -0,0 +1,524 @@
+/*  Copyright (C) 2016-2024 Andreas Shimokawa, Andrzej Surowiec, Arjan
+    Schrijver, Carsten Pfeiffer, Daniel Dakhno, Daniele Gobbetti, Ganblejs,
+    gfwilliams, Gordon Williams, Johannes Tysiak, Jos茅 Rebelo, marco.altomonte,
+    Petr Van臎k, Taavi Eom盲e
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities;
+
+import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_CONNECT;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.util.TypedValue;
+import android.view.MenuItem;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.Toast;
+
+import androidx.annotation.ColorInt;
+import androidx.annotation.NonNull;
+import androidx.activity.OnBackPressedCallback;
+import androidx.appcompat.app.ActionBarDrawerToggle;
+import androidx.appcompat.app.AlertDialog;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.appcompat.app.AppCompatDelegate;
+import androidx.core.view.GravityCompat;
+import androidx.core.view.MenuProvider;
+import androidx.drawerlayout.widget.DrawerLayout;
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentActivity;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
+import androidx.viewpager2.adapter.FragmentStateAdapter;
+import androidx.viewpager2.widget.ViewPager2;
+
+import com.google.android.material.appbar.MaterialToolbar;
+import com.google.android.material.bottomnavigation.BottomNavigationView;
+import com.google.android.material.navigation.NavigationView;
+import com.google.android.material.tabs.TabLayout;
+import com.google.android.material.tabs.TabLayoutMediator;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Objects;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.discovery.DiscoveryActivityV2;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
+import nodomain.freeyourgadget.gadgetbridge.model.RecordedDataTypes;
+import nodomain.freeyourgadget.gadgetbridge.util.AndroidUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+import nodomain.freeyourgadget.gadgetbridge.util.GBChangeLog;
+import nodomain.freeyourgadget.gadgetbridge.util.PermissionsUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+import nodomain.freeyourgadget.gadgetbridge.womp.WompKeepAliveService;
+import nodomain.freeyourgadget.gadgetbridge.womp.WompWorkbenchFragment;
+
+//TODO: extend AbstractGBActivity, but it requires actionbar that is not available
+public class ControlCenterv2 extends AppCompatActivity
+        implements NavigationView.OnNavigationItemSelectedListener, GBActivity {
+    private static final Logger LOG = LoggerFactory.getLogger(ControlCenterv2.class);
+    public static final int MENU_REFRESH_CODE = 1;
+    private boolean isLanguageInvalid = false;
+    private boolean isThemeInvalid = false;
+    private ViewPager2 viewPager;
+    private FragmentStateAdapter pagerAdapter;
+    private SwipeRefreshLayout swipeLayout;
+    private AlertDialog clDialog;
+    private TabLayout bandTabs;
+    private View workbenchContainer;
+    private BottomNavigationView navigationView;
+    private long lastBackPressedAt;
+
+    //needed for KK compatibility
+    static {
+        AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);
+    }
+
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            switch (Objects.requireNonNull(action)) {
+                case GBApplication.ACTION_LANGUAGE_CHANGE:
+                    setLanguage(GBApplication.getLanguage(), true);
+                    break;
+                case GBApplication.ACTION_THEME_CHANGE:
+                    isThemeInvalid = true;
+                    break;
+                case GBApplication.ACTION_QUIT:
+                    finish();
+                    break;
+                case DeviceService.ACTION_REALTIME_SAMPLES:
+                    final GBDevice device = intent.getParcelableExtra(GBDevice.EXTRA_DEVICE);
+                    handleRealtimeSample(device, intent.getSerializableExtra(DeviceService.EXTRA_REALTIME_SAMPLE));
+                    break;
+                case GBDevice.ACTION_DEVICE_CHANGED:
+                    GBDevice dev = intent.getParcelableExtra(GBDevice.EXTRA_DEVICE);
+                    if (dev != null && !dev.isBusy()) {
+                        swipeLayout.setRefreshing(false);
+                    }
+                    break;
+            }
+        }
+    };
+    private boolean pesterWithPermissions = true;
+    private final Map<GBDevice, ActivitySample> currentHRSample = new HashMap<>();
+
+    public ActivitySample getCurrentHRSample(final GBDevice device) {
+        return currentHRSample.get(device);
+    }
+
+    private void setCurrentHRSample(final GBDevice device, ActivitySample sample) {
+        if (HeartRateUtils.getInstance().isValidHeartRateValue(sample.getHeartRate())) {
+            currentHRSample.put(device, sample);
+        }
+    }
+
+    private void handleRealtimeSample(final GBDevice device, Serializable extra) {
+        if (extra instanceof ActivitySample) {
+            ActivitySample sample = (ActivitySample) extra;
+            setCurrentHRSample(device, sample);
+        }
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        AbstractGBActivity.init(this, AbstractGBActivity.NO_ACTIONBAR);
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.activity_main);
+        WompKeepAliveService.start(this);
+
+        Prefs prefs = GBApplication.getPrefs();
+
+        // Determine availability of device with activity tracking functionality
+        boolean activityTrackerAvailable = false;
+        List<GBDevice> devices = GBApplication.app().getDeviceManager().getDevices();
+        for (GBDevice dev : devices) {
+            if (dev.getDeviceCoordinator().supportsActivityTracking()) {
+                activityTrackerAvailable = true;
+                break;
+            }
+        }
+        if (savedInstanceState != null) {
+            if (savedInstanceState.getBoolean("cl")) {
+                GBChangeLog cl = GBChangeLog.createChangeLog(this);
+                try {
+                    if (cl.hasChanges(false)) {
+                        clDialog = cl.getMaterialLogDialog();
+                    } else {
+                        clDialog = cl.getMaterialFullLogDialog();
+                    }
+                    clDialog.show();
+                } catch (Exception ignored) {
+                    GB.toast(getBaseContext(), getString(R.string.error_showing_changelog), Toast.LENGTH_LONG, GB.ERROR);
+                }
+            }
+        }
+
+
+        // Initialize drawer
+        NavigationView drawerNavigationView = findViewById(R.id.nav_view);
+        drawerNavigationView.setNavigationItemSelectedListener(this);
+
+        // Initialize bottom navigation锛氭墜鐜� / 宸ヤ綔鍙颁袱涓《绾у叆鍙o紝浜掍笉渚濊禆鐧诲綍
+        navigationView = findViewById(R.id.bottom_nav_bar);
+        bandTabs = findViewById(R.id.band_tabs);
+        workbenchContainer = findViewById(R.id.workbench_container);
+        navigationView.setVisibility(View.VISIBLE);
+        navigationView.setOnItemSelectedListener(menuItem -> {
+            final int itemId = menuItem.getItemId();
+            if (itemId == R.id.bottom_nav_band) {
+                showBandContent();
+            } else if (itemId == R.id.bottom_nav_workbench) {
+                showWorkbenchContent();
+            }
+            return true;
+        });
+
+        if (savedInstanceState == null) {
+            getSupportFragmentManager().beginTransaction()
+                    .replace(R.id.workbench_container, new WompWorkbenchFragment(), "womp_workbench")
+                    .commit();
+        }
+
+        // Initialize actionbar
+        MaterialToolbar toolbar = findViewById(R.id.toolbar);
+        setSupportActionBar(toolbar);
+        DrawerLayout drawer = findViewById(R.id.drawer_layout);
+        ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(
+                this, drawer, toolbar, R.string.controlcenter_navigation_drawer_open, R.string.controlcenter_navigation_drawer_close);
+        drawer.setDrawerListener(toggle);
+        toggle.syncState();
+        if (GBApplication.areDynamicColorsEnabled()) {
+            TypedValue typedValue = new TypedValue();
+            Resources.Theme theme = getTheme();
+            theme.resolveAttribute(com.google.android.material.R.attr.colorSurface, typedValue, true);
+            @ColorInt int toolbarBackground = typedValue.data;
+            toolbar.setBackgroundColor(toolbarBackground);
+        } else {
+            toolbar.setBackgroundColor(getResources().getColor(R.color.primarydark_light));
+            toolbar.setTitleTextColor(getResources().getColor(android.R.color.white));
+        }
+
+        // Configure ViewPager2 with fragment adapter and default fragment
+        viewPager = findViewById(R.id.dashboard_viewpager);
+        pagerAdapter = new MainFragmentsPagerAdapter(this);
+        viewPager.setAdapter(pagerAdapter);
+        new TabLayoutMediator(bandTabs, viewPager, (tab, position) -> {
+            if (position == 0) {
+                tab.setText(R.string.bottom_nav_health);
+            } else {
+                tab.setText(R.string.bottom_nav_devices);
+            }
+        }).attach();
+        if (!prefs.getBoolean("dashboard_as_default_view", true) || !activityTrackerAvailable) {
+            viewPager.setCurrentItem(1, false);
+        }
+
+        // Sync ViewPager changes with fragment menus
+        viewPager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
+            private MenuProvider existingMenuProvider = null;
+
+            @Override
+            public void onPageSelected(int position) {
+                // Ensure the menu provider is set to the current fragment
+                if (existingMenuProvider != null) {
+                    ControlCenterv2.this.removeMenuProvider(existingMenuProvider);
+                }
+                final Fragment fragment = getSupportFragmentManager().findFragmentByTag("f" + position);
+                if (fragment instanceof MenuProvider) {
+                    existingMenuProvider = (MenuProvider) fragment;
+                    ControlCenterv2.this.addMenuProvider(existingMenuProvider);
+                }
+            }
+        });
+
+        // Make sure the SwipeRefreshLayout doesn't interfere with the ViewPager2
+        swipeLayout = findViewById(R.id.dashboard_swipe_layout);
+        if (viewPager.getChildAt(0) != null) {
+            viewPager.getChildAt(0).setOnTouchListener((v, event) -> {
+                if (event.getAction() == MotionEvent.ACTION_MOVE) {
+                    swipeLayout.setEnabled(false);
+                } else {
+                    swipeLayout.setEnabled(true);
+                }
+                return false;
+            });
+        }
+
+        // Set pull-down-to-refresh action
+        swipeLayout.setOnRefreshListener(() -> {
+            // Fetch activity for all connected devices
+            GBApplication.deviceService().onFetchRecordedData(RecordedDataTypes.TYPE_SYNC);
+            // Hide 'refreshing' animation immediately if no health devices are connected
+            List<GBDevice> devices1 = GBApplication.app().getDeviceManager().getDevices();
+            for (GBDevice dev : devices1) {
+                if (dev.getDeviceCoordinator().supportsActivityTracking() && dev.isInitialized()) {
+                    return;
+                }
+            }
+            swipeLayout.setRefreshing(false);
+            GB.toast(getString(R.string.info_no_devices_connected), Toast.LENGTH_LONG, GB.WARN);
+        });
+
+        // Set up local intent listener
+        IntentFilter filterLocal = new IntentFilter();
+        filterLocal.addAction(GBApplication.ACTION_LANGUAGE_CHANGE);
+        filterLocal.addAction(GBApplication.ACTION_THEME_CHANGE);
+        filterLocal.addAction(GBApplication.ACTION_QUIT);
+        filterLocal.addAction(DeviceService.ACTION_REALTIME_SAMPLES);
+        filterLocal.addAction(GBDevice.ACTION_DEVICE_CHANGED);
+        LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, filterLocal);
+
+        // 鍒嗘嫞鍛樼璺宠繃棣栨寮曞涓庢洿鏂版棩蹇�
+        boolean firstRun = prefs.getBoolean("first_run", true);
+        if (firstRun) {
+            prefs.getPreferences().edit().putBoolean("first_run", false).apply();
+        }
+        pesterWithPermissions = prefs.getBoolean("permission_pestering", true);
+        if (pesterWithPermissions && !PermissionsUtils.checkAllPermissions(this)) {
+            Intent permissionsIntent = new Intent(this, PermissionsActivity.class);
+            startActivity(permissionsIntent);
+        }
+
+        /*
+        GBChangeLog cl = GBChangeLog.createChangeLog(this);
+        boolean showChangelog = prefs.getBoolean("show_changelog", true);
+        if (showChangelog && cl.isFirstRun() && cl.hasChanges(cl.isFirstRunEver())) {
+            try {
+                cl.getMaterialLogDialog().show();
+            } catch (Exception ignored) {
+                GB.toast(this, getString(R.string.error_showing_changelog), Toast.LENGTH_LONG, GB.ERROR);
+            }
+        }
+        */
+
+        GBApplication.deviceService().requestDeviceInfo();
+
+        if (savedInstanceState != null && !savedInstanceState.getBoolean("womp_wb", true)) {
+            navigationView.setSelectedItemId(R.id.bottom_nav_band);
+            showBandContent();
+        } else {
+            navigationView.setSelectedItemId(R.id.bottom_nav_workbench);
+            showWorkbenchContent();
+        }
+
+        getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) {
+            @Override
+            public void handleOnBackPressed() {
+                handleMainBackPressed();
+            }
+        });
+    }
+
+    public void showBandTab() {
+        if (navigationView != null) {
+            navigationView.setSelectedItemId(R.id.bottom_nav_band);
+        } else {
+            showBandContent();
+        }
+    }
+
+    private void showBandContent() {
+        if (workbenchContainer != null) {
+            workbenchContainer.setVisibility(View.GONE);
+        }
+        if (swipeLayout != null) {
+            swipeLayout.setVisibility(View.VISIBLE);
+        }
+        if (bandTabs != null) {
+            bandTabs.setVisibility(View.VISIBLE);
+        }
+        if (getSupportActionBar() != null) {
+            getSupportActionBar().setTitle(R.string.title_activity_controlcenter);
+        }
+    }
+
+    private void showWorkbenchContent() {
+        if (swipeLayout != null) {
+            swipeLayout.setRefreshing(false);
+            swipeLayout.setVisibility(View.GONE);
+        }
+        if (bandTabs != null) {
+            bandTabs.setVisibility(View.GONE);
+        }
+        if (workbenchContainer != null) {
+            workbenchContainer.setVisibility(View.VISIBLE);
+        }
+        if (getSupportActionBar() != null) {
+            getSupportActionBar().setTitle(R.string.womp_workbench);
+        }
+    }
+
+    private void handleMainBackPressed() {
+        final DrawerLayout drawer = findViewById(R.id.drawer_layout);
+        if (drawer != null && drawer.isDrawerOpen(GravityCompat.START)) {
+            drawer.closeDrawer(GravityCompat.START);
+            return;
+        }
+        if (workbenchContainer != null && workbenchContainer.getVisibility() == View.VISIBLE) {
+            Fragment fragment = getSupportFragmentManager().findFragmentByTag("womp_workbench");
+            if (fragment instanceof WompWorkbenchFragment && ((WompWorkbenchFragment) fragment).goBack()) {
+                return;
+            }
+        }
+        long now = System.currentTimeMillis();
+        if (now - lastBackPressedAt < 2000) {
+            finishAffinity();
+            return;
+        }
+        lastBackPressedAt = now;
+        Toast.makeText(this, R.string.womp_press_again_to_exit, Toast.LENGTH_SHORT).show();
+    }
+
+    @Override
+    public void onSaveInstanceState(@NonNull Bundle outState) {
+       if (clDialog != null){
+           outState.putBoolean("cl", clDialog.isShowing());
+       }
+       if (workbenchContainer != null) {
+           outState.putBoolean("womp_wb", workbenchContainer.getVisibility() == View.VISIBLE);
+       }
+       super.onSaveInstanceState(outState);
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        handleShortcut(getIntent());
+        if (isLanguageInvalid || isThemeInvalid) {
+            isLanguageInvalid = false;
+            isThemeInvalid = false;
+            recreate();
+        }
+    }
+
+    @Override
+    protected void onDestroy() {
+        try {
+            LocalBroadcastManager.getInstance(this).unregisterReceiver(mReceiver);
+        } catch (Exception ignored) {
+        }
+        super.onDestroy();
+    }
+
+    @Override
+    public boolean onNavigationItemSelected(@NonNull final MenuItem item) {
+        final DrawerLayout drawer = findViewById(R.id.drawer_layout);
+        drawer.closeDrawer(GravityCompat.START);
+
+        final int itemId = item.getItemId();
+        if (itemId == R.id.action_womp_workbench) {
+            if (navigationView != null) {
+                navigationView.setSelectedItemId(R.id.bottom_nav_workbench);
+            } else {
+                showWorkbenchContent();
+            }
+            return false;
+        } else if (itemId == R.id.action_settings) {
+            final Intent settingsIntent = new Intent(this, SettingsActivity.class);
+            startActivityForResult(settingsIntent, MENU_REFRESH_CODE);
+            return false;
+        } else if (itemId == R.id.device_action_discover) {
+            launchDiscoveryActivity();
+            return false;
+        } else if (itemId == R.id.action_quit) {
+            GBApplication.quit();
+            return false;
+        } else if (itemId == R.id.about) {
+            final Intent aboutIntent = new Intent(this, AboutActivity.class);
+            startActivity(aboutIntent);
+            return false;
+        }
+        /*
+        } else if (itemId == R.id.action_debug) {
+            final Intent debugIntent = new Intent(this, DebugActivity.class);
+            startActivity(debugIntent);
+            return false;
+        } else if (itemId == R.id.action_data_management) {
+            final Intent dbIntent = new Intent(this, DataManagementActivity.class);
+            startActivity(dbIntent);
+            return false;
+        } else if (itemId == R.id.donation_link) {
+            ...
+        } else if (itemId == R.id.external_changelog) {
+            ...
+        }
+        */
+
+        return false;  // we do not want the drawer menu item to get selected
+    }
+
+    private void launchDiscoveryActivity() {
+        startActivity(new Intent(this, DiscoveryActivityV2.class));
+    }
+
+    private void handleShortcut(Intent intent) {
+        if(ACTION_CONNECT.equals(intent.getAction())) {
+            String btDeviceAddress = intent.getStringExtra("device");
+            if(btDeviceAddress!=null){
+                GBDevice candidate = DeviceHelper.getInstance().findAvailableDevice(btDeviceAddress, this);
+                if (candidate != null && !candidate.isConnected()) {
+                    GBApplication.deviceService(candidate).connect();
+                }
+            }
+        }
+    }
+
+    public void setLanguage(Locale language, boolean invalidateLanguage) {
+        if (invalidateLanguage) {
+            isLanguageInvalid = true;
+        }
+        AndroidUtils.setLanguage(this, language);
+    }
+
+    private class MainFragmentsPagerAdapter extends FragmentStateAdapter {
+        public MainFragmentsPagerAdapter(FragmentActivity fa) {
+            super(fa);
+        }
+
+        @Override
+        public Fragment createFragment(int position) {
+            if (position == 0) {
+                return new DashboardFragment();
+            } else {
+                return new DevicesFragment();
+            }
+        }
+
+        @Override
+        public int getItemCount() {
+            return 2;
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DashboardFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DashboardFragment.java
new file mode 100644
index 0000000..ba21a2b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DashboardFragment.java
@@ -0,0 +1,532 @@
+/*  Copyright (C) 2023-2024 Arjan Schrijver, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities;
+
+import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.util.DisplayMetrics;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import androidx.activity.result.ActivityResult;
+import androidx.activity.result.ActivityResultCallback;
+import androidx.activity.result.ActivityResultLauncher;
+import androidx.activity.result.contract.ActivityResultContracts;
+import androidx.annotation.NonNull;
+import androidx.core.view.MenuProvider;
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentContainerView;
+import androidx.gridlayout.widget.GridLayout;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import com.google.android.material.card.MaterialCardView;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.GregorianCalendar;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Supplier;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.dashboard.AbstractDashboardWidget;
+import nodomain.freeyourgadget.gadgetbridge.activities.dashboard.DashboardCaloriesActiveGoalWidget;
+import nodomain.freeyourgadget.gadgetbridge.activities.dashboard.DashboardActiveTimeWidget;
+import nodomain.freeyourgadget.gadgetbridge.activities.dashboard.DashboardBodyEnergyWidget;
+import nodomain.freeyourgadget.gadgetbridge.activities.dashboard.DashboardCalendarActivity;
+import nodomain.freeyourgadget.gadgetbridge.activities.dashboard.DashboardCaloriesTotalSegmentedWidget;
+import nodomain.freeyourgadget.gadgetbridge.activities.dashboard.DashboardDistanceWidget;
+import nodomain.freeyourgadget.gadgetbridge.activities.dashboard.DashboardGoalsWidget;
+import nodomain.freeyourgadget.gadgetbridge.activities.dashboard.DashboardHrvWidget;
+import nodomain.freeyourgadget.gadgetbridge.activities.dashboard.DashboardSleepScoreWidget;
+import nodomain.freeyourgadget.gadgetbridge.activities.dashboard.DashboardSleepWidget;
+import nodomain.freeyourgadget.gadgetbridge.activities.dashboard.DashboardStepsWidget;
+import nodomain.freeyourgadget.gadgetbridge.activities.dashboard.DashboardStressBreakdownWidget;
+import nodomain.freeyourgadget.gadgetbridge.activities.dashboard.DashboardStressSegmentedWidget;
+import nodomain.freeyourgadget.gadgetbridge.activities.dashboard.DashboardStressSimpleWidget;
+import nodomain.freeyourgadget.gadgetbridge.activities.dashboard.DashboardTodayWidget;
+import nodomain.freeyourgadget.gadgetbridge.activities.dashboard.DashboardVO2MaxCyclingWidget;
+import nodomain.freeyourgadget.gadgetbridge.activities.dashboard.DashboardVO2MaxAnyWidget;
+import nodomain.freeyourgadget.gadgetbridge.activities.dashboard.DashboardVO2MaxRunningWidget;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+import nodomain.freeyourgadget.gadgetbridge.util.DashboardUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+public class DashboardFragment extends Fragment implements MenuProvider {
+    private static final Logger LOG = LoggerFactory.getLogger(DashboardFragment.class);
+
+    private final Calendar day = GregorianCalendar.getInstance();
+    private TextView textViewDate;
+    private TextView arrowRight;
+    private GridLayout gridLayout;
+    private final Map<String, AbstractDashboardWidget> widgetMap = new HashMap<>();
+    private DashboardData dashboardData = new DashboardData();
+    private boolean isConfigChanged = false;
+
+    private ActivityResultLauncher<Intent> calendarLauncher;
+    private final ActivityResultCallback<ActivityResult> calendarCallback = result -> {
+        if (result.getResultCode() == Activity.RESULT_OK && result.getData() != null) {
+            long timeMillis = result.getData().getLongExtra(DashboardCalendarActivity.EXTRA_TIMESTAMP, 0);
+            if (timeMillis != 0) {
+                day.setTimeInMillis(timeMillis);
+                fullRefresh();
+            }
+        }
+    };
+
+    public static final String ACTION_CONFIG_CHANGE = "nodomain.freeyourgadget.gadgetbridge.activities.dashboardfragment.action.config_change";
+
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            if (action == null) return;
+            switch (action) {
+                case GBApplication.ACTION_NEW_DATA:
+                    final GBDevice dev = intent.getParcelableExtra(GBDevice.EXTRA_DEVICE);
+                    if (dev != null) {
+                        if (dashboardData.showAllDevices || dashboardData.showDeviceList.contains(dev.getAddress())) {
+                            refresh();
+                        }
+                    }
+                    break;
+                case ACTION_CONFIG_CHANGE:
+                    isConfigChanged = true;
+                    break;
+            }
+        }
+    };
+
+    @Override
+    public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+        super.onCreateView(inflater, container, savedInstanceState);
+        View dashboardView = inflater.inflate(R.layout.fragment_dashboard, container, false);
+        textViewDate = dashboardView.findViewById(R.id.dashboard_date);
+        gridLayout = dashboardView.findViewById(R.id.dashboard_gridlayout);
+
+        calendarLauncher = registerForActivityResult(
+                new ActivityResultContracts.StartActivityForResult(),
+                calendarCallback
+        );
+
+        // Increase column count on landscape, tablets and open foldables
+        DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
+        if (displayMetrics.widthPixels / displayMetrics.density >= 600) {
+            gridLayout.setColumnCount(4);
+        }
+
+        final TextView arrowLeft = dashboardView.findViewById(R.id.arrow_left);
+        arrowLeft.setOnClickListener(v -> {
+            day.add(Calendar.DAY_OF_MONTH, -1);
+            refresh();
+        });
+        arrowRight = dashboardView.findViewById(R.id.arrow_right);
+        arrowRight.setOnClickListener(v -> {
+            Calendar today = GregorianCalendar.getInstance();
+            if (!DateTimeUtils.isSameDay(today, day)) {
+                day.add(Calendar.DAY_OF_MONTH, 1);
+                refresh();
+            }
+        });
+
+        if (savedInstanceState != null && savedInstanceState.containsKey("dashboard_data") && dashboardData.isEmpty()) {
+            dashboardData = (DashboardData) savedInstanceState.getSerializable("dashboard_data");
+        } else if (dashboardData.isEmpty()) {
+            reloadPreferences();
+        }
+
+        IntentFilter filterLocal = new IntentFilter();
+        filterLocal.addAction(GBDevice.ACTION_DEVICE_CHANGED);
+        filterLocal.addAction(GBApplication.ACTION_NEW_DATA);
+        filterLocal.addAction(ACTION_CONFIG_CHANGE);
+        LocalBroadcastManager.getInstance(requireContext()).registerReceiver(mReceiver, filterLocal);
+
+        return dashboardView;
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        if (isConfigChanged) {
+            isConfigChanged = false;
+            fullRefresh();
+        } else if (dashboardData.isEmpty() || !widgetMap.containsKey("today")) {
+            refresh();
+        }
+    }
+
+    @Override
+    public void onDestroy() {
+        LocalBroadcastManager.getInstance(requireContext()).unregisterReceiver(mReceiver);
+        super.onDestroy();
+    }
+
+    @Override
+    public void onSaveInstanceState(@NonNull Bundle outState) {
+        super.onSaveInstanceState(outState);
+        outState.putSerializable("dashboard_data", dashboardData);
+    }
+
+    @Override
+    public void onCreateMenu(@NonNull final Menu menu, @NonNull final MenuInflater inflater) {
+        inflater.inflate(R.menu.dashboard_menu, menu);
+    }
+
+    @Override
+    public boolean onMenuItemSelected(@NonNull final MenuItem item) {
+        final int itemId = item.getItemId();
+        if (itemId == R.id.dashboard_show_calendar) {
+            final Intent intent = new Intent(requireActivity(), DashboardCalendarActivity.class);
+            intent.putExtra(DashboardCalendarActivity.EXTRA_TIMESTAMP, day.getTimeInMillis());
+            calendarLauncher.launch(intent);
+            return true;
+        } else if (itemId == R.id.dashboard_settings) {
+            final Intent intent = new Intent(requireActivity(), DashboardPreferencesActivity.class);
+            startActivity(intent);
+            return true;
+        }
+        return false;
+    }
+
+    private void fullRefresh() {
+        gridLayout.removeAllViews();
+        widgetMap.clear();
+        refresh();
+    }
+
+    private void refresh() {
+        day.set(Calendar.HOUR_OF_DAY, 23);
+        day.set(Calendar.MINUTE, 59);
+        day.set(Calendar.SECOND, 59);
+        dashboardData.clear();
+        reloadPreferences();
+        draw();
+    }
+
+    private void reloadPreferences() {
+        Prefs prefs = GBApplication.getPrefs();
+        dashboardData.showAllDevices = prefs.getBoolean("dashboard_devices_all", true);
+        dashboardData.showDeviceList = prefs.getStringSet("dashboard_devices_multiselect", new HashSet<>());
+        dashboardData.hrIntervalSecs = prefs.getInt("dashboard_widget_today_hr_interval", 1) * 60;
+        dashboardData.timeTo = (int) (day.getTimeInMillis() / 1000);
+        dashboardData.timeFrom = DateTimeUtils.shiftDays(dashboardData.timeTo, -1);
+    }
+
+    private void draw() {
+        Prefs prefs = GBApplication.getPrefs();
+        String defaultWidgetsOrder = String.join(",", getResources().getStringArray(R.array.pref_dashboard_widgets_order_default));
+        String widgetsOrderPref = prefs.getString("pref_dashboard_widgets_order", defaultWidgetsOrder);
+        String[] widgetsOrder = widgetsOrderPref.split(",");
+
+        Calendar today = GregorianCalendar.getInstance();
+        if (DateTimeUtils.isSameDay(today, day)) {
+            textViewDate.setText(requireContext().getString(R.string.activity_summary_today));
+            arrowRight.setAlpha(0.5f);
+        } else {
+            textViewDate.setText(DateTimeUtils.formatDate(day.getTime()));
+            arrowRight.setAlpha(1);
+        }
+
+        boolean cardsEnabled = prefs.getBoolean("dashboard_cards_enabled", true);
+
+        for (String widgetName : widgetsOrder) {
+            AbstractDashboardWidget widget = widgetMap.get(widgetName);
+            if (widget == null) {
+                int columnSpan = 1;
+                switch (widgetName) {
+                    case "today":
+                        widget = DashboardTodayWidget.newInstance(dashboardData);
+                        columnSpan = prefs.getBoolean("dashboard_widget_today_2columns", true) ? 2 : 1;
+                        break;
+                    case "goals":
+                        widget = DashboardGoalsWidget.newInstance(dashboardData);
+                        columnSpan = prefs.getBoolean("dashboard_widget_goals_2columns", true) ? 2 : 1;
+                        break;
+                    case "steps":
+                        widget = DashboardStepsWidget.newInstance(dashboardData);
+                        break;
+                    case "distance":
+                        widget = DashboardDistanceWidget.newInstance(dashboardData);
+                        break;
+                    case "activetime":
+                        widget = DashboardActiveTimeWidget.newInstance(dashboardData);
+                        break;
+                    case "sleep":
+                        widget = DashboardSleepWidget.newInstance(dashboardData);
+                        break;
+                    case "stress_simple":
+                        widget = DashboardStressSimpleWidget.newInstance(dashboardData);
+                        break;
+                    case "stress_segmented":
+                        widget = DashboardStressSegmentedWidget.newInstance(dashboardData);
+                        break;
+                    case "stress_breakdown":
+                        widget = DashboardStressBreakdownWidget.newInstance(dashboardData);
+                        break;
+                    case "bodyenergy":
+                        widget = DashboardBodyEnergyWidget.newInstance(dashboardData);
+                        break;
+                    case "hrv":
+                        widget = DashboardHrvWidget.newInstance(dashboardData);
+                        break;
+                    case "vo2max_running":
+                        widget = DashboardVO2MaxRunningWidget.newInstance(dashboardData);
+                        break;
+                    case "vo2max_cycling":
+                        widget = DashboardVO2MaxCyclingWidget.newInstance(dashboardData);
+                        break;
+                    case "vo2max":
+                        widget = DashboardVO2MaxAnyWidget.newInstance(dashboardData);
+                        break;
+                    case "calories_active":
+                        widget = DashboardCaloriesActiveGoalWidget.newInstance(dashboardData);
+                        break;
+                    case "calories_segmented":
+                        widget = DashboardCaloriesTotalSegmentedWidget.newInstance(dashboardData);
+                        break;
+                    case "sleepscore":
+                        widget = DashboardSleepScoreWidget.newInstance(dashboardData);
+                        break;
+                    default:
+                        LOG.error("Unknown dashboard widget {}", widgetName);
+                        continue;
+                }
+
+                createWidget(widget, cardsEnabled, columnSpan);
+
+                widgetMap.put(widgetName, widget);
+            } else {
+                widget.update();
+            }
+        }
+    }
+
+    private void createWidget(AbstractDashboardWidget widgetObj, boolean cardsEnabled, int columnSpan) {
+        final float scale = requireContext().getResources().getDisplayMetrics().density;
+        FragmentContainerView fragment = new FragmentContainerView(requireActivity());
+        int fragmentId = View.generateViewId();
+        fragment.setId(fragmentId);
+        getChildFragmentManager()
+                .beginTransaction()
+                .replace(fragmentId, widgetObj)
+                .commitAllowingStateLoss(); // FIXME: #4007
+
+        GridLayout.LayoutParams layoutParams = new GridLayout.LayoutParams(
+                GridLayout.spec(GridLayout.UNDEFINED, GridLayout.FILL, 1f),
+                GridLayout.spec(GridLayout.UNDEFINED, columnSpan, GridLayout.FILL, 1f)
+        );
+        layoutParams.width = 0;
+        int pixels_8dp = (int) (8 * scale + 0.5f);
+        layoutParams.setMargins(pixels_8dp, pixels_8dp, pixels_8dp, pixels_8dp);
+
+        if (cardsEnabled) {
+            MaterialCardView card = new MaterialCardView(requireActivity());
+            int pixels_4dp = (int) (4 * scale + 0.5f);
+            card.setRadius(pixels_4dp);
+            card.setCardElevation(pixels_4dp);
+            card.setContentPadding(pixels_4dp, pixels_4dp, pixels_4dp, pixels_4dp);
+            card.setLayoutParams(layoutParams);
+            card.addView(fragment);
+            gridLayout.addView(card);
+        } else {
+            fragment.setLayoutParams(layoutParams);
+            gridLayout.addView(fragment);
+        }
+    }
+
+    /**
+     * This class serves as a data collection object for all data points used by the various
+     * dashboard widgets. Since retrieving this data can be costly, this class makes sure it will
+     * only be done once. It will be passed to every widget, making sure they have the necessary
+     * data available.
+     */
+    public static class DashboardData implements Serializable {
+        public boolean showAllDevices;
+        public Set<String> showDeviceList;
+        public int hrIntervalSecs;
+        public int timeFrom;
+        public int timeTo;
+        public final List<GeneralizedActivity> generalizedActivities = Collections.synchronizedList(new ArrayList<>());
+        private int stepsTotal;
+        private float stepsGoalFactor;
+        private int restingCaloriesTotal;
+        private int activeCaloriesTotal;
+        private float activeCaloriesGoalFactor;
+        private int caloriesTotal;
+        private long sleepTotalMinutes;
+        private float sleepGoalFactor;
+        private float distanceTotalMeters;
+        private float distanceGoalFactor;
+        private long activeMinutesTotal;
+        private float activeMinutesGoalFactor;
+        private final Map<String, Serializable> genericData = new ConcurrentHashMap<>();
+
+        public void clear() {
+            restingCaloriesTotal = 0;
+            activeCaloriesTotal = 0;
+            activeCaloriesGoalFactor = 0;
+            caloriesTotal = 0;
+            stepsTotal = 0;
+            stepsGoalFactor = 0;
+            sleepTotalMinutes = 0;
+            sleepGoalFactor = 0;
+            distanceTotalMeters = 0;
+            distanceGoalFactor = 0;
+            activeMinutesTotal = 0;
+            activeMinutesGoalFactor = 0;
+            generalizedActivities.clear();
+            genericData.clear();
+        }
+
+        public boolean isEmpty() {
+            return (stepsTotal == 0 &&
+                    stepsGoalFactor == 0 &&
+                    restingCaloriesTotal == 0 &&
+                    activeCaloriesTotal == 0 &&
+                    activeCaloriesGoalFactor == 0 &&
+                    caloriesTotal == 0 &&
+                    sleepTotalMinutes == 0 &&
+                    sleepGoalFactor == 0 &&
+                    distanceTotalMeters == 0 &&
+                    distanceGoalFactor == 0 &&
+                    activeMinutesTotal == 0 &&
+                    activeMinutesGoalFactor == 0 &&
+                    genericData.isEmpty() &&
+                    generalizedActivities.isEmpty());
+        }
+
+        public synchronized int getStepsTotal() {
+            if (stepsTotal == 0)
+                stepsTotal = DashboardUtils.getStepsTotal(this);
+            return stepsTotal;
+        }
+
+        public synchronized float getStepsGoalFactor() {
+            if (stepsGoalFactor == 0)
+                stepsGoalFactor = DashboardUtils.getStepsGoalFactor(this);
+            return stepsGoalFactor;
+        }
+
+        public synchronized float getDistanceTotal() {
+            if (distanceTotalMeters == 0)
+                distanceTotalMeters = DashboardUtils.getDistanceTotal(this);
+            return distanceTotalMeters;
+        }
+
+        public synchronized float getDistanceGoalFactor() {
+            if (distanceGoalFactor == 0)
+                distanceGoalFactor = DashboardUtils.getDistanceGoalFactor(this);
+            return distanceGoalFactor;
+        }
+
+        public synchronized long getActiveMinutesTotal() {
+            if (activeMinutesTotal == 0)
+                activeMinutesTotal = DashboardUtils.getActiveMinutesTotal(this);
+            return activeMinutesTotal;
+        }
+
+        public synchronized float getActiveMinutesGoalFactor() {
+            if (activeMinutesGoalFactor == 0)
+                activeMinutesGoalFactor = DashboardUtils.getActiveMinutesGoalFactor(this);
+            return activeMinutesGoalFactor;
+        }
+
+        public synchronized long getSleepMinutesTotal() {
+            if (sleepTotalMinutes == 0)
+                sleepTotalMinutes = DashboardUtils.getSleepMinutesTotal(this);
+            return sleepTotalMinutes;
+        }
+
+        public synchronized float getSleepMinutesGoalFactor() {
+            if (sleepGoalFactor == 0)
+                sleepGoalFactor = DashboardUtils.getSleepMinutesGoalFactor(this);
+            return sleepGoalFactor;
+        }
+
+        public synchronized int getActiveCaloriesTotal() {
+            if (activeCaloriesTotal == 0)
+                activeCaloriesTotal = DashboardUtils.getActiveCaloriesTotal(this);
+            return activeCaloriesTotal;
+        }
+
+        public synchronized int getRestingCaloriesTotal() {
+            if (restingCaloriesTotal == 0)
+                restingCaloriesTotal = DashboardUtils.getRestingCaloriesTotal(this);
+            return restingCaloriesTotal;
+        }
+
+        public synchronized float getActiveCaloriesGoalFactor() {
+            if (activeCaloriesGoalFactor == 0)
+                activeCaloriesGoalFactor = DashboardUtils.getActiveCaloriesGoalFactor(this);
+            return activeCaloriesGoalFactor;
+        }
+
+        public void put(final String key, final Serializable value) {
+            genericData.put(key, value);
+        }
+
+        public Serializable get(final String key) {
+            return genericData.get(key);
+        }
+
+        /**
+         * @noinspection UnusedReturnValue
+         */
+        public Serializable computeIfAbsent(final String key, final Supplier<Serializable> supplier) {
+            return genericData.computeIfAbsent(key, absent -> supplier.get());
+        }
+
+        public static class GeneralizedActivity implements Serializable {
+            public ActivityKind activityKind;
+            public long timeFrom;
+            public long timeTo;
+
+            public GeneralizedActivity(ActivityKind activityKind, long timeFrom, long timeTo) {
+                this.activityKind = activityKind;
+                this.timeFrom = timeFrom;
+                this.timeTo = timeTo;
+            }
+
+            @NonNull
+            @Override
+            public String toString() {
+                return "Generalized activity: timeFrom=" + timeFrom + ", timeTo=" + timeTo + ", activityKind=" + activityKind + ", calculated duration: " + (timeTo - timeFrom) + " seconds";
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DashboardPreferencesActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DashboardPreferencesActivity.java
new file mode 100644
index 0000000..1e5e720
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DashboardPreferencesActivity.java
@@ -0,0 +1,104 @@
+/*  Copyright (C) 2024 Arjan Schrijver
+
+    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 <http://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.text.InputType;
+
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+import androidx.preference.MultiSelectListPreference;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceFragmentCompat;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class DashboardPreferencesActivity extends AbstractSettingsActivityV2 {
+    @Override
+    protected String fragmentTag() {
+        return DashboardPreferencesFragment.FRAGMENT_TAG;
+    }
+
+    @Override
+    protected PreferenceFragmentCompat newFragment() {
+        return new DashboardPreferencesFragment();
+    }
+
+    public static class DashboardPreferencesFragment extends AbstractPreferenceFragment {
+        static final String FRAGMENT_TAG = "DASHBOARD_PREFERENCES_FRAGMENT";
+
+        @Override
+        public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) {
+            setPreferencesFromResource(R.xml.dashboard_preferences, rootKey);
+
+            setInputTypeFor("dashboard_widget_today_hr_interval", InputType.TYPE_CLASS_NUMBER);
+
+            final MultiSelectListPreference dashboardDevices = findPreference("dashboard_devices_multiselect");
+            if (dashboardDevices != null) {
+                List<GBDevice> devices = GBApplication.app().getDeviceManager().getDevices();
+                List<String> deviceMACs = new ArrayList<>();
+                List<String> deviceNames = new ArrayList<>();
+                for (GBDevice dev : devices) {
+                    deviceMACs.add(dev.getAddress());
+                    deviceNames.add(dev.getAliasOrName());
+                }
+                dashboardDevices.setEntryValues(deviceMACs.toArray(new String[0]));
+                dashboardDevices.setEntries(deviceNames.toArray(new String[0]));
+            }
+            List<String> dashboardPrefs = Arrays.asList(
+                    "dashboard_cards_enabled",
+                    "pref_dashboard_widgets_order",
+                    "dashboard_widget_today_24h",
+                    "dashboard_widget_today_24h_upside_down",
+                    "dashboard_widget_today_show_yesterday",
+                    "dashboard_widget_today_time_indicator",
+                    "dashboard_widget_today_2columns",
+                    "dashboard_widget_today_legend",
+                    "dashboard_widget_today_hr_interval",
+                    "dashboard_widget_goals_2columns",
+                    "dashboard_widget_goals_legend",
+                    "dashboard_devices_all",
+                    "dashboard_devices_multiselect"
+            );
+            Preference pref;
+            for (String dashboardPref : dashboardPrefs) {
+                pref = findPreference(dashboardPref);
+                if (pref != null) {
+                    pref.setOnPreferenceChangeListener((preference, autoExportEnabled) -> {
+                        sendDashboardConfigChangedIntent();
+                        return true;
+                    });
+                }
+            }
+        }
+
+        /**
+         * Signal dashboard that its config has changed
+         */
+        private void sendDashboardConfigChangedIntent() {
+            Intent intent = new Intent();
+            intent.setAction(DashboardFragment.ACTION_CONFIG_CHANGE);
+            LocalBroadcastManager.getInstance(requireContext()).sendBroadcast(intent);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DataManagementActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DataManagementActivity.java
new file mode 100644
index 0000000..593b1dd
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DataManagementActivity.java
@@ -0,0 +1,518 @@
+/*  Copyright (C) 2021-2024 Arjan Schrijver, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities;
+
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.net.Uri;
+import android.os.Bundle;
+import android.preference.PreferenceManager;
+import android.provider.DocumentsContract;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.Button;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.activity.result.ActivityResultLauncher;
+import androidx.activity.result.contract.ActivityResultContracts;
+import androidx.core.app.NavUtils;
+
+import com.google.android.material.dialog.MaterialAlertDialogBuilder;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.files.FileManagerActivity;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.database.PeriodicExporter;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.util.AndroidUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+import nodomain.freeyourgadget.gadgetbridge.util.GBPrefs;
+import nodomain.freeyourgadget.gadgetbridge.util.ImportExportSharedPreferences;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+
+public class DataManagementActivity extends AbstractGBActivity {
+    private static final Logger LOG = LoggerFactory.getLogger(DataManagementActivity.class);
+    private static SharedPreferences sharedPrefs;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_data_management);
+
+        final ActivityResultLauncher<String> backupZipFileChooser = registerForActivityResult(
+                new ActivityResultContracts.CreateDocument("application/zip"),
+                uri -> {
+                    LOG.info("Got target backup file: {}", uri);
+                    if (uri != null) {
+                        final Intent startBackupIntent = new Intent(DataManagementActivity.this, BackupRestoreProgressActivity.class);
+                        startBackupIntent.putExtra(BackupRestoreProgressActivity.EXTRA_URI, uri);
+                        startBackupIntent.putExtra(BackupRestoreProgressActivity.EXTRA_ACTION, "export");
+                        startActivity(startBackupIntent);
+                    }
+                }
+        );
+
+        final Button backupToZipButton = findViewById(R.id.backupToZipButton);
+        backupToZipButton.setOnClickListener(v -> {
+            final SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss", Locale.getDefault());
+            final String defaultFilename = String.format(Locale.ROOT, "gadgetbridge_%s.zip", sdf.format(new Date()));
+            backupZipFileChooser.launch(defaultFilename);
+        });
+
+        final ActivityResultLauncher<String[]> restoreFileChooser = registerForActivityResult(
+                new ActivityResultContracts.OpenDocument(),
+                uri -> {
+                    LOG.info("Got restore file: {}", uri);
+
+                    if (uri == null) {
+                        return;
+                    }
+
+                    new MaterialAlertDialogBuilder(this)
+                            .setCancelable(true)
+                            .setIcon(R.drawable.ic_warning)
+                            .setTitle(R.string.dbmanagementactivity_import_data_title)
+                            .setMessage(R.string.dbmanagementactivity_overwrite_database_confirmation)
+                            .setPositiveButton(R.string.dbmanagementactivity_overwrite, (dialog, which) -> {
+                                // Disconnect from all devices right away
+                                GBApplication.deviceService().disconnect();
+
+                                final Intent startBackupIntent = new Intent(DataManagementActivity.this, BackupRestoreProgressActivity.class);
+                                startBackupIntent.putExtra(BackupRestoreProgressActivity.EXTRA_URI, uri);
+                                startBackupIntent.putExtra(BackupRestoreProgressActivity.EXTRA_ACTION, "import");
+                                startActivity(startBackupIntent);
+                            })
+                            .setNegativeButton(R.string.Cancel, (dialog, which) -> {
+                            })
+                            .show();
+                }
+        );
+
+        final Button restoreFromZipButton = findViewById(R.id.restoreFromZipButton);
+        restoreFromZipButton.setOnClickListener(v -> restoreFileChooser.launch(new String[]{"application/zip"}));
+
+        TextView dbPath = findViewById(R.id.activity_data_management_path);
+        dbPath.setText(getExternalPath());
+
+        Button exportDBButton = findViewById(R.id.exportDataButton);
+        exportDBButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                exportDB();
+            }
+        });
+        Button importDBButton = findViewById(R.id.importDataButton);
+        importDBButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                importDB();
+            }
+        });
+
+        Button showContentDataButton = findViewById(R.id.showContentDataButton);
+        showContentDataButton.setOnClickListener(v -> {
+            final Intent fileManagerIntent = new Intent(DataManagementActivity.this, FileManagerActivity.class);
+            startActivity(fileManagerIntent);
+        });
+
+        int oldDBVisibility = hasOldActivityDatabase() ? View.VISIBLE : View.GONE;
+
+        TextView deleteOldActivityTitle = findViewById(R.id.mergeOldActivityDataTitle);
+        deleteOldActivityTitle.setVisibility(oldDBVisibility);
+
+        Button deleteOldActivityDBButton = findViewById(R.id.deleteOldActivityDB);
+        deleteOldActivityDBButton.setVisibility(oldDBVisibility);
+        deleteOldActivityDBButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                deleteOldActivityDbFile();
+            }
+        });
+
+        Button deleteDBButton = findViewById(R.id.emptyDBButton);
+        deleteDBButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                deleteActivityDatabase();
+            }
+        });
+
+        TextView dbPath2 = findViewById(R.id.activity_data_management_path2);
+        dbPath2.setText(getExternalPath());
+
+        Button cleanExportDirectoryButton = findViewById(R.id.cleanExportDirectoryButton);
+        cleanExportDirectoryButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                cleanExportDirectory();
+            }
+        });
+        GBApplication gbApp = GBApplication.app();
+        Prefs prefs = GBApplication.getPrefs();
+        boolean autoExportEnabled = prefs.getBoolean(GBPrefs.AUTO_EXPORT_ENABLED, false);
+        int autoExportInterval = prefs.getInt(GBPrefs.AUTO_EXPORT_INTERVAL, 0);
+        //returns an ugly content://...
+        //String autoExportLocation = prefs.getString(GBPrefs.AUTO_EXPORT_LOCATION, "");
+
+        int testExportVisibility = (autoExportInterval > 0 && autoExportEnabled) ? View.VISIBLE : View.GONE;
+        boolean isExportEnabled = autoExportInterval > 0 && autoExportEnabled;
+        TextView autoExportLocation_label = findViewById(R.id.autoExportLocation_label);
+        autoExportLocation_label.setVisibility(testExportVisibility);
+
+        TextView autoExportLocation_path = findViewById(R.id.autoExportLocation_path);
+        autoExportLocation_path.setVisibility(testExportVisibility);
+        autoExportLocation_path.setText(getAutoExportLocationUserString() + " (" + getAutoExportLocationPreferenceString() + ")" );
+
+        TextView autoExportEnabled_label = findViewById(R.id.autoExportEnabled);
+        if (isExportEnabled) {
+            autoExportEnabled_label.setText(getString(R.string.activity_db_management_autoexport_enabled_yes));
+        } else {
+            autoExportEnabled_label.setText(getString(R.string.activity_db_management_autoexport_enabled_no));
+        }
+
+        TextView autoExportScheduled = findViewById(R.id.autoExportScheduled);
+        autoExportScheduled.setVisibility(testExportVisibility);
+        long setAutoExportScheduledTimestamp = gbApp.getAutoExportScheduledTimestamp();
+        if (setAutoExportScheduledTimestamp > 0) {
+            autoExportScheduled.setText(getString(R.string.activity_db_management_autoexport_scheduled_yes,
+                    DateTimeUtils.formatDateTime(new Date(setAutoExportScheduledTimestamp))));
+        } else {
+            autoExportScheduled.setText(getResources().getString(R.string.activity_db_management_autoexport_scheduled_no));
+        }
+
+        TextView autoExport_lastTime_label = findViewById(R.id.autoExport_lastTime_label);
+        long lastAutoExportTimestamp = gbApp.getLastAutoExportTimestamp();
+
+        autoExport_lastTime_label.setVisibility(View.GONE);
+        autoExport_lastTime_label.setText(getString(R.string.autoExport_lastTime_label,
+                DateTimeUtils.formatDateTime(new Date(lastAutoExportTimestamp))));
+
+        if (lastAutoExportTimestamp > 0) {
+            autoExport_lastTime_label.setVisibility(testExportVisibility);
+            autoExport_lastTime_label.setVisibility(testExportVisibility);
+        }
+
+        final Context context = getApplicationContext();
+        Button testExportDBButton = findViewById(R.id.testExportDBButton);
+        testExportDBButton.setVisibility(testExportVisibility);
+        testExportDBButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                sendBroadcast(new Intent(context, PeriodicExporter.class));
+                GB.toast(context,
+                        context.getString(R.string.activity_DB_test_export_message),
+                        Toast.LENGTH_SHORT, GB.INFO);
+            }
+        });
+
+        sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);
+    }
+
+    private String getAutoExportLocationPreferenceString() {
+        String autoExportLocation = GBApplication.getPrefs().getString(GBPrefs.AUTO_EXPORT_LOCATION, null);
+        if (autoExportLocation == null) {
+            return "";
+        }
+        return autoExportLocation;
+    }
+
+    private String getAutoExportLocationUri() {
+        String autoExportLocation = getAutoExportLocationPreferenceString();
+        if (autoExportLocation == null) {
+            return "";
+        }
+        Uri uri = Uri.parse(autoExportLocation);
+        try {
+
+            return AndroidUtils.getFilePath(getApplicationContext(), uri);
+        } catch (IllegalArgumentException e) {
+            LOG.error("getFilePath did not work, trying to resolve content provider path");
+            try {
+                Cursor cursor = getContentResolver().query(
+                        uri,
+                        new String[]{DocumentsContract.Document.COLUMN_DISPLAY_NAME},
+                        null, null, null, null
+                );
+                if (cursor != null && cursor.moveToFirst()) {
+                    return cursor.getString(cursor.getColumnIndex(DocumentsContract.Document.COLUMN_DISPLAY_NAME));
+                }
+            } catch (Exception exception) {
+                LOG.error("Error getting export path", exception);
+            }
+        }
+        return "";
+    }
+
+    private String getAutoExportLocationUserString() {
+        String location = getAutoExportLocationUri();
+        if (location == "") {
+            return getString(R.string.activity_db_management_autoexport_location);
+        }
+        return location;
+    }
+
+    private boolean hasOldActivityDatabase() {
+        return new DBHelper(this).existsDB("ActivityDatabase");
+    }
+
+    private String getExternalPath() {
+        try {
+            return FileUtils.getExternalFilesDir().getAbsolutePath();
+        } catch (Exception ex) {
+            LOG.warn("Unable to get external files dir", ex);
+        }
+        return getString(R.string.dbmanagementactivvity_cannot_access_export_path);
+    }
+
+    private void exportShared() {
+        try {
+            File myPath = FileUtils.getExternalFilesDir();
+            File myFile = new File(myPath, "Export_preference");
+            ImportExportSharedPreferences.exportToFile(sharedPrefs, myFile);
+        } catch (IOException ex) {
+            GB.toast(this, getString(R.string.dbmanagementactivity_error_exporting_shared, ex.getMessage()), Toast.LENGTH_LONG, GB.ERROR, ex);
+        }
+        try (DBHandler lockHandler = GBApplication.acquireDB()) {
+            List<Device> activeDevices = DBHelper.getActiveDevices(lockHandler.getDaoSession());
+            for (Device dbDevice : activeDevices) {
+                SharedPreferences deviceSharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(dbDevice.getIdentifier());
+                if (sharedPrefs != null) {
+                    File myPath = FileUtils.getExternalFilesDir();
+                    File myFile = new File(myPath, "Export_preference_" + FileUtils.makeValidFileName(dbDevice.getIdentifier()));
+                    try {
+                        ImportExportSharedPreferences.exportToFile(deviceSharedPrefs, myFile);
+                    } catch (Exception ignore) {
+                        // some devices no not have device specific preferences
+                    }
+                }
+            }
+        } catch (Exception e) {
+            GB.toast("Error exporting device specific preferences", Toast.LENGTH_SHORT, GB.ERROR, e);
+        }
+    }
+
+    private void importShared() {
+        try {
+            File myPath = FileUtils.getExternalFilesDir();
+            File myFile = new File(myPath, "Export_preference");
+            ImportExportSharedPreferences.importFromFile(sharedPrefs, myFile);
+        } catch (Exception ex) {
+            GB.toast(DataManagementActivity.this, getString(R.string.dbmanagementactivity_error_importing_db, ex.getMessage()), Toast.LENGTH_LONG, GB.ERROR, ex);
+        }
+
+        try (DBHandler lockHandler = GBApplication.acquireDB()) {
+            List<Device> activeDevices = DBHelper.getActiveDevices(lockHandler.getDaoSession());
+            for (Device dbDevice : activeDevices) {
+                SharedPreferences deviceSharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(dbDevice.getIdentifier());
+                if (sharedPrefs != null) {
+                    File myPath = FileUtils.getExternalFilesDir();
+                    File myFile = new File(myPath, "Export_preference_" + FileUtils.makeValidFileName(dbDevice.getIdentifier()));
+
+                    if (!myFile.exists()) { //first try to use file in new format de_ad_be_af, if doesn't exist use old format de:at:be:af
+                        myFile = new File(myPath, "Export_preference_" + dbDevice.getIdentifier());
+                        LOG.info("Trying to import with older filename");
+                    }else{
+                        LOG.info("Trying to import with new filename");
+                    }
+
+                    try {
+                        ImportExportSharedPreferences.importFromFile(deviceSharedPrefs, myFile);
+                    } catch (Exception ignore) {
+                        // some devices no not have device specific preferences
+                    }
+                }
+            }
+        } catch (Exception e) {
+            GB.toast("Error importing device specific preferences", Toast.LENGTH_SHORT, GB.ERROR);
+        }
+    }
+
+    private void exportDB() {
+        new MaterialAlertDialogBuilder(this)
+                .setCancelable(true)
+                .setIcon(R.drawable.ic_warning)
+                .setTitle(R.string.dbmanagementactivity_export_data_title)
+                .setMessage(R.string.dbmanagementactivity_export_confirmation)
+                .setPositiveButton(R.string.activity_DB_ExportButton, new DialogInterface.OnClickListener() {
+                    @Override
+                    public void onClick(DialogInterface dialog, int which) {
+                        try (DBHandler dbHandler = GBApplication.acquireDB()) {
+                            exportShared();
+                            DBHelper helper = new DBHelper(DataManagementActivity.this);
+                            File dir = FileUtils.getExternalFilesDir();
+                            File destFile = helper.exportDB(dbHandler, dir);
+                            GB.toast(DataManagementActivity.this, getString(R.string.dbmanagementactivity_exported_to, destFile.getAbsolutePath()), Toast.LENGTH_LONG, GB.INFO);
+                        } catch (Exception ex) {
+                            GB.toast(DataManagementActivity.this, getString(R.string.dbmanagementactivity_error_exporting_db, ex.getMessage()), Toast.LENGTH_LONG, GB.ERROR, ex);
+                        }
+                    }
+                })
+                .setNegativeButton(R.string.Cancel, new DialogInterface.OnClickListener() {
+                    @Override
+                    public void onClick(DialogInterface dialog, int which) {
+                    }
+                })
+                .show();
+    }
+
+    private void importDB() {
+        new MaterialAlertDialogBuilder(this)
+                .setCancelable(true)
+                .setIcon(R.drawable.ic_warning)
+                .setTitle(R.string.dbmanagementactivity_import_data_title)
+                .setMessage(R.string.dbmanagementactivity_overwrite_database_confirmation)
+                .setPositiveButton(R.string.dbmanagementactivity_overwrite, new DialogInterface.OnClickListener() {
+                    @Override
+                    public void onClick(DialogInterface dialog, int which) {
+                        try (DBHandler dbHandler = GBApplication.acquireDB()) {
+                            DBHelper helper = new DBHelper(DataManagementActivity.this);
+                            File dir = FileUtils.getExternalFilesDir();
+                            SQLiteOpenHelper sqLiteOpenHelper = dbHandler.getHelper();
+                            File sourceFile = new File(dir, sqLiteOpenHelper.getDatabaseName());
+                            helper.importDB(dbHandler, sourceFile);
+                            helper.validateDB(sqLiteOpenHelper);
+                            GB.toast(DataManagementActivity.this, getString(R.string.dbmanagementactivity_import_successful), Toast.LENGTH_LONG, GB.INFO);
+                        } catch (Exception ex) {
+                            GB.toast(DataManagementActivity.this, getString(R.string.dbmanagementactivity_error_importing_db, ex.getMessage()), Toast.LENGTH_LONG, GB.ERROR, ex);
+                        }
+                        importShared();
+                    }
+                })
+                .setNegativeButton(R.string.Cancel, new DialogInterface.OnClickListener() {
+                    @Override
+                    public void onClick(DialogInterface dialog, int which) {
+                    }
+                })
+                .show();
+    }
+
+    private void deleteActivityDatabase() {
+        new MaterialAlertDialogBuilder(this)
+                .setCancelable(true)
+                .setIcon(R.drawable.ic_warning)
+                .setTitle(R.string.dbmanagementactivity_delete_activity_data_title)
+                .setMessage(R.string.dbmanagementactivity_really_delete_entire_db)
+                .setPositiveButton(R.string.Delete, new DialogInterface.OnClickListener() {
+                    @Override
+                    public void onClick(DialogInterface dialog, int which) {
+                        if (GBApplication.deleteActivityDatabase(DataManagementActivity.this)) {
+                            GB.toast(DataManagementActivity.this, getString(R.string.dbmanagementactivity_database_successfully_deleted), Toast.LENGTH_SHORT, GB.INFO);
+                        } else {
+                            GB.toast(DataManagementActivity.this, getString(R.string.dbmanagementactivity_db_deletion_failed), Toast.LENGTH_SHORT, GB.INFO);
+                        }
+                    }
+                })
+                .setNegativeButton(R.string.Cancel, new DialogInterface.OnClickListener() {
+                    @Override
+                    public void onClick(DialogInterface dialog, int which) {
+                    }
+                })
+                .show();
+    }
+
+    private void deleteOldActivityDbFile() {
+        new MaterialAlertDialogBuilder(this)
+                .setCancelable(true)
+                .setTitle(R.string.dbmanagementactivity_delete_old_activity_db)
+                .setIcon(R.drawable.ic_warning)
+                .setMessage(R.string.dbmanagementactivity_delete_old_activitydb_confirmation)
+                .setPositiveButton(R.string.Delete, new DialogInterface.OnClickListener() {
+            @Override
+            public void onClick(DialogInterface dialog, int which) {
+                if (GBApplication.deleteOldActivityDatabase(DataManagementActivity.this)) {
+                    GB.toast(DataManagementActivity.this, getString(R.string.dbmanagementactivity_old_activity_db_successfully_deleted), Toast.LENGTH_SHORT, GB.INFO);
+                } else {
+                    GB.toast(DataManagementActivity.this, getString(R.string.dbmanagementactivity_old_activity_db_deletion_failed), Toast.LENGTH_SHORT, GB.INFO);
+                }
+            }
+        });
+        new MaterialAlertDialogBuilder(this).setNegativeButton(R.string.Cancel, new DialogInterface.OnClickListener() {
+            @Override
+            public void onClick(DialogInterface dialog, int which) {
+            }
+        });
+        new MaterialAlertDialogBuilder(this).show();
+    }
+
+    private void cleanExportDirectory() {
+        new MaterialAlertDialogBuilder(this)
+                .setCancelable(true)
+                .setIcon(R.drawable.ic_warning)
+                .setTitle(R.string.activity_DB_clean_export_directory_warning_title)
+                .setMessage(getString(R.string.activity_DB_clean_export_directory_warning_message))
+                .setPositiveButton(R.string.Delete, new DialogInterface.OnClickListener() {
+                    @Override
+                    public void onClick(DialogInterface dialog, int which) {
+                        try {
+                            File externalFilesDir = FileUtils.getExternalFilesDir();
+                            String autoexportFile = getAutoExportLocationUri();
+                            for (File file : externalFilesDir.listFiles()) {
+                                if (file.isFile() &&
+                                        (!FileUtils.getExtension(file.toString()).toLowerCase().equals("gpx")) && //keep GPX files
+                                        (!file.toString().equals(autoexportFile)) // do not remove autoexport
+                                ) {
+                                    LOG.debug("Deleting file: " + file);
+                                    try {
+                                        file.delete();
+                                    } catch (Exception exception) {
+                                        LOG.error("Error erasing file: " + exception);
+                                    }
+                                }
+                            }
+                            GB.toast(getString(R.string.dbmanagementactivity_export_finished), Toast.LENGTH_SHORT, GB.INFO);
+                        } catch (Exception ex) {
+                            GB.toast(DataManagementActivity.this, getString(R.string.dbmanagementactivity_error_cleaning_export_directory, ex.getMessage()), Toast.LENGTH_LONG, GB.ERROR, ex);
+                        }
+                    }
+                })
+                .setNegativeButton(R.string.Cancel, new DialogInterface.OnClickListener() {
+                    @Override
+                    public void onClick(DialogInterface dialog, int which) {
+                    }
+                })
+                .show();
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        if (item.getItemId() == android.R.id.home) {
+            NavUtils.navigateUpFromSameTask(this);
+            return true;
+        }
+        return super.onOptionsItemSelected(item);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DebugActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DebugActivity.java
new file mode 100644
index 0000000..92e0f0c
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DebugActivity.java
@@ -0,0 +1,1260 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, Arjan Schrijver, Carsten
+    Pfeiffer, Daniel Dakhno, Daniele Gobbetti, Dmitriy Bogdanov, Frank Slezak,
+    Ganblejs, ivanovlev, Jos茅 Rebelo, Kamalei Zestri, Kasha, Lem Dulfo, Pavel
+    Elagin, Petr Van臎k, Steffen Liebergeld, Tim
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities;
+
+import static android.content.Intent.EXTRA_SUBJECT;
+import static nodomain.freeyourgadget.gadgetbridge.util.GB.NOTIFICATION_CHANNEL_ID;
+
+import android.app.Activity;
+import android.app.DatePickerDialog;
+import android.app.PendingIntent;
+import android.appwidget.AppWidgetHost;
+import android.appwidget.AppWidgetManager;
+import android.bluetooth.BluetoothDevice;
+import android.companion.AssociationRequest;
+import android.companion.BluetoothDeviceFilter;
+import android.companion.CompanionDeviceManager;
+import android.content.BroadcastReceiver;
+import android.content.ClipData;
+import android.content.ClipboardManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.IntentSender;
+import android.content.SharedPreferences;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.preference.PreferenceManager;
+import android.text.Editable;
+import android.text.TextUtils;
+import android.text.TextWatcher;
+import android.util.Pair;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import android.widget.DatePicker;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+import android.widget.Spinner;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+import androidx.appcompat.app.AlertDialog;
+import androidx.core.app.ActivityCompat;
+import androidx.core.app.NavUtils;
+import androidx.core.app.NotificationCompat;
+import androidx.core.app.RemoteInput;
+import androidx.core.content.ContextCompat;
+import androidx.core.content.FileProvider;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import com.google.android.material.dialog.MaterialAlertDialogBuilder;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.Serializable;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.Date;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Random;
+import java.util.TreeMap;
+
+import nodomain.freeyourgadget.gadgetbridge.BuildConfig;
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.Widget;
+import nodomain.freeyourgadget.gadgetbridge.activities.welcome.WelcomeActivity;
+import nodomain.freeyourgadget.gadgetbridge.adapter.SpinnerWithIconAdapter;
+import nodomain.freeyourgadget.gadgetbridge.adapter.SpinnerWithIconItem;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventCameraRemote;
+import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.DeviceManager;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.externalevents.gps.GBLocationService;
+import nodomain.freeyourgadget.gadgetbridge.externalevents.opentracks.OpenTracksContentObserver;
+import nodomain.freeyourgadget.gadgetbridge.externalevents.opentracks.OpenTracksController;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.NotificationType;
+import nodomain.freeyourgadget.gadgetbridge.model.RecordedDataTypes;
+import nodomain.freeyourgadget.gadgetbridge.model.Weather;
+import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
+import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+import nodomain.freeyourgadget.gadgetbridge.util.PendingIntentUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.WidgetPreferenceStorage;
+
+public class DebugActivity extends AbstractGBActivity {
+    private static final Logger LOG = LoggerFactory.getLogger(DebugActivity.class);
+
+    private static Bundle dataLossSave;
+
+    private static final String EXTRA_REPLY = "reply";
+    private static final String ACTION_REPLY
+            = "nodomain.freeyourgadget.gadgetbridge.DebugActivity.action.reply";
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            switch (Objects.requireNonNull(intent.getAction())) {
+                case ACTION_REPLY: {
+                    Bundle remoteInput = RemoteInput.getResultsFromIntent(intent);
+                    CharSequence reply = remoteInput.getCharSequence(EXTRA_REPLY);
+                    LOG.info("got wearable reply: " + reply);
+                    GB.toast(context, "got wearable reply: " + reply, Toast.LENGTH_SHORT, GB.INFO);
+                    break;
+                }
+                case DeviceService.ACTION_REALTIME_SAMPLES:
+                    handleRealtimeSample(intent.getSerializableExtra(DeviceService.EXTRA_REALTIME_SAMPLE));
+                    break;
+                default:
+                    LOG.info("ignoring intent action " + intent.getAction());
+                    break;
+            }
+        }
+    };
+    private Spinner sendTypeSpinner;
+    private EditText editContent;
+    public static final long SELECT_DEVICE = -1;
+    private long selectedTestDeviceKey = SELECT_DEVICE;
+    private String selectedTestDeviceMAC;
+
+    private static final int SELECT_DEVICE_REQUEST_CODE = 1;
+
+    private void handleRealtimeSample(Serializable extra) {
+        if (extra instanceof ActivitySample) {
+            ActivitySample sample = (ActivitySample) extra;
+            GB.toast(this, "Heart Rate measured: " + sample.getHeartRate(), Toast.LENGTH_LONG, GB.INFO);
+        }
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_debug);
+
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(ACTION_REPLY);
+        filter.addAction(DeviceService.ACTION_REALTIME_SAMPLES);
+        LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, filter);
+        ContextCompat.registerReceiver(this, mReceiver, filter, ContextCompat.RECEIVER_EXPORTED); // for ACTION_REPLY
+
+        editContent = findViewById(R.id.editContent);
+
+        final ArrayList<String> spinnerArray = new ArrayList<>();
+        for (NotificationType notificationType : NotificationType.sortedValues()) {
+            spinnerArray.add(notificationType.name());
+        }
+        ArrayAdapter<String> spinnerArrayAdapter = new ArrayAdapter<>(this, android.R.layout.simple_spinner_dropdown_item, spinnerArray);
+        sendTypeSpinner = findViewById(R.id.sendTypeSpinner);
+        sendTypeSpinner.setAdapter(spinnerArrayAdapter);
+
+        Button sendButton = findViewById(R.id.sendButton);
+        sendButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                NotificationSpec notificationSpec = new NotificationSpec();
+                String testString = editContent.getText().toString();
+                notificationSpec.phoneNumber = testString;
+                notificationSpec.body = testString;
+                notificationSpec.sender = testString;
+                notificationSpec.subject = testString;
+                if (notificationSpec.type != NotificationType.GENERIC_SMS) {
+                    // SMS notifications don't have a source app ID when sent by the SMSReceiver,
+                    // so let's not set it here as well for consistency
+                    notificationSpec.sourceAppId = BuildConfig.APPLICATION_ID;
+                }
+                notificationSpec.sourceName = getApplicationContext().getApplicationInfo()
+                        .loadLabel(getApplicationContext().getPackageManager())
+                        .toString();
+                notificationSpec.type = NotificationType.sortedValues()[sendTypeSpinner.getSelectedItemPosition()];
+                notificationSpec.pebbleColor = notificationSpec.type.color;
+                notificationSpec.attachedActions = new ArrayList<>();
+
+                if (notificationSpec.type == NotificationType.GENERIC_SMS) {
+                    // REPLY action
+                    NotificationSpec.Action replyAction = new NotificationSpec.Action();
+                    replyAction.title = "Reply";
+                    replyAction.type = NotificationSpec.Action.TYPE_SYNTECTIC_REPLY_PHONENR;
+                    notificationSpec.attachedActions.add(replyAction);
+                } else if (notificationSpec.type == NotificationType.CONVERSATIONS) {
+                    // REPLY action
+                    NotificationSpec.Action replyAction = new NotificationSpec.Action();
+                    replyAction.title = "Reply";
+                    replyAction.type = NotificationSpec.Action.TYPE_WEARABLE_REPLY;
+                    notificationSpec.attachedActions.add(replyAction);
+                }
+
+                GBApplication.deviceService().onNotification(notificationSpec);
+            }
+        });
+
+        Button incomingCallButton = findViewById(R.id.incomingCallButton);
+        incomingCallButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                CallSpec callSpec = new CallSpec();
+                callSpec.command = CallSpec.CALL_INCOMING;
+                callSpec.number = editContent.getText().toString();
+                GBApplication.deviceService().onSetCallState(callSpec);
+            }
+        });
+        Button outgoingCallButton = findViewById(R.id.outgoingCallButton);
+        outgoingCallButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                CallSpec callSpec = new CallSpec();
+                callSpec.command = CallSpec.CALL_OUTGOING;
+                callSpec.number = editContent.getText().toString();
+                GBApplication.deviceService().onSetCallState(callSpec);
+            }
+        });
+
+        Button startCallButton = findViewById(R.id.startCallButton);
+        startCallButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                CallSpec callSpec = new CallSpec();
+                callSpec.command = CallSpec.CALL_START;
+                GBApplication.deviceService().onSetCallState(callSpec);
+            }
+        });
+        Button endCallButton = findViewById(R.id.endCallButton);
+        endCallButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                CallSpec callSpec = new CallSpec();
+                callSpec.command = CallSpec.CALL_END;
+                GBApplication.deviceService().onSetCallState(callSpec);
+            }
+        });
+
+        Button rebootButton = findViewById(R.id.rebootButton);
+        rebootButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                GBApplication.deviceService().onReset(GBDeviceProtocol.RESET_FLAGS_REBOOT);
+            }
+        });
+
+        Button factoryResetButton = findViewById(R.id.factoryResetButton);
+        factoryResetButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                new MaterialAlertDialogBuilder(DebugActivity.this)
+                        .setCancelable(true)
+                        .setTitle(R.string.debugactivity_really_factoryreset_title)
+                        .setMessage(R.string.debugactivity_really_factoryreset)
+                        .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
+                            @Override
+                            public void onClick(DialogInterface dialog, int which) {
+                                GBApplication.deviceService().onReset(GBDeviceProtocol.RESET_FLAGS_FACTORY_RESET);
+                            }
+                        })
+                        .setNegativeButton(R.string.Cancel, new DialogInterface.OnClickListener() {
+                            @Override
+                            public void onClick(DialogInterface dialog, int which) {
+                            }
+                        })
+                        .show();
+            }
+        });
+
+        Button heartRateButton = findViewById(R.id.HeartRateButton);
+        heartRateButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                GB.toast("Measuring heart rate, please wait...", Toast.LENGTH_LONG, GB.INFO);
+                GBApplication.deviceService().onHeartRateTest();
+            }
+        });
+
+        Button setFetchTimeButton = findViewById(R.id.SetFetchTimeButton);
+        setFetchTimeButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+
+                final Calendar currentDate = Calendar.getInstance();
+                Context context = getApplicationContext();
+
+                if (context instanceof GBApplication) {
+                    GBApplication gbApp = (GBApplication) context;
+                    final List<GBDevice> devices = gbApp.getDeviceManager().getSelectedDevices();
+                    if(devices.size() == 0){
+                        GB.toast("Device not selected/connected", Toast.LENGTH_LONG, GB.INFO);
+                        return;
+                    }
+                    new DatePickerDialog(DebugActivity.this, new DatePickerDialog.OnDateSetListener() {
+                        @Override
+                        public void onDateSet(DatePicker view, int year, int monthOfYear, int dayOfMonth) {
+                            Calendar date = Calendar.getInstance();
+                            date.set(year, monthOfYear, dayOfMonth);
+
+                            long timestamp = date.getTimeInMillis() - 1000;
+                            GB.toast("Setting lastSyncTimeMillis: " + timestamp, Toast.LENGTH_LONG, GB.INFO);
+
+                            for(GBDevice device : devices){
+                                SharedPreferences.Editor editor = GBApplication.getDeviceSpecificSharedPrefs(device.getAddress()).edit();
+                                editor.remove("lastSyncTimeMillis"); //FIXME: key reconstruction is BAD
+                                editor.putLong("lastSyncTimeMillis", timestamp);
+                                editor.apply();
+                            }
+                        }
+                    }, currentDate.get(Calendar.YEAR), currentDate.get(Calendar.MONTH), currentDate.get(Calendar.DATE)).show();
+                }
+
+
+            }
+        });
+
+        Button setWeatherButton = findViewById(R.id.setWeatherButton);
+        setWeatherButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                if (Weather.getInstance().getWeatherSpec() == null) {
+                    final WeatherSpec weatherSpec = new WeatherSpec();
+                    weatherSpec.forecasts = new ArrayList<>();
+
+                    weatherSpec.location = "Green Hill";
+                    weatherSpec.currentConditionCode = 601; // snow
+                    weatherSpec.currentCondition = Weather.getConditionString(weatherSpec.currentConditionCode);
+
+                    weatherSpec.currentTemp = 15 + 273;
+                    weatherSpec.currentHumidity = 30;
+
+                    weatherSpec.windSpeed = 10;
+                    weatherSpec.windDirection = 12;
+                    weatherSpec.timestamp = (int) (System.currentTimeMillis() / 1000);
+                    weatherSpec.todayMinTemp = 10 + 273;
+                    weatherSpec.todayMaxTemp = 25 + 273;
+
+                    for (int i = 0; i < 5; i++) {
+                        final WeatherSpec.Daily gbForecast = new WeatherSpec.Daily();
+                        gbForecast.minTemp = 10 + i + 273;
+                        gbForecast.maxTemp = 25 + i + 273;
+
+                        gbForecast.conditionCode = 800; // clear
+                        weatherSpec.forecasts.add(gbForecast);
+                    }
+
+                    Weather.getInstance().setWeatherSpec(new ArrayList<>(Collections.singletonList(weatherSpec)));
+                }
+
+                final ArrayList<WeatherSpec> specs = new ArrayList<>(Weather.getInstance().getWeatherSpecs());
+                GBApplication.deviceService().onSendWeather(specs);
+            }
+        });
+
+        Button showCachedWeatherButton = findViewById(R.id.showCachedWeatherButton);
+        showCachedWeatherButton.setOnClickListener(new View.OnClickListener(){
+            @Override
+            public void onClick(View v) {
+                final List<WeatherSpec> weatherSpecs = Weather.getInstance().getWeatherSpecs();
+
+                if (weatherSpecs == null || weatherSpecs.isEmpty()) {
+                    displayWeatherInfo(null);
+                    return;
+                } else if (weatherSpecs.size() == 1) {
+                    displayWeatherInfo(weatherSpecs.get(0));
+                    return;
+                }
+
+                final String[] weatherLocations = new String[weatherSpecs.size()];
+
+                for (int i = 0; i < weatherSpecs.size(); i++) {
+                    weatherLocations[i] = weatherSpecs.get(i).location;
+                }
+
+                new MaterialAlertDialogBuilder(DebugActivity.this)
+                        .setCancelable(true)
+                        .setTitle("Choose Location")
+                        .setItems(weatherLocations, (dialog, which) -> displayWeatherInfo(weatherSpecs.get(which)))
+                        .setNegativeButton("Cancel", (dialog, which) -> {
+                        })
+                        .show();
+            }
+        });
+
+        Button setMusicInfoButton = findViewById(R.id.setMusicInfoButton);
+        setMusicInfoButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                MusicSpec musicSpec = new MusicSpec();
+                String testString = editContent.getText().toString();
+                musicSpec.artist = testString + "(artist)";
+                musicSpec.album = testString + "(album)";
+                musicSpec.track = testString + "(track)";
+                musicSpec.duration = 10;
+                musicSpec.trackCount = 5;
+                musicSpec.trackNr = 2;
+
+                GBApplication.deviceService().onSetMusicInfo(musicSpec);
+
+                MusicStateSpec stateSpec = new MusicStateSpec();
+                stateSpec.position = 0;
+                stateSpec.state = 0x01; // playing
+                stateSpec.playRate = 100;
+                stateSpec.repeat = 1;
+                stateSpec.shuffle = 1;
+
+                GBApplication.deviceService().onSetMusicState(stateSpec);
+            }
+        });
+
+        Button setTimeButton = findViewById(R.id.setTimeButton);
+        setTimeButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                GBApplication.deviceService().onSetTime();
+            }
+        });
+
+        Button testNotificationButton = findViewById(R.id.testNotificationButton);
+        testNotificationButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                testNotification();
+            }
+        });
+
+        Button testPebbleKitNotificationButton = findViewById(R.id.testPebbleKitNotificationButton);
+        testPebbleKitNotificationButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                testPebbleKitNotification();
+            }
+        });
+
+        Button fetchDebugLogsButton = findViewById(R.id.fetchDebugLogsButton);
+        fetchDebugLogsButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                GBApplication.deviceService().onFetchRecordedData(RecordedDataTypes.TYPE_DEBUGLOGS);
+            }
+        });
+
+        Button testNewFunctionalityButton = findViewById(R.id.testNewFunctionality);
+        testNewFunctionalityButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                testNewFunctionality();
+            }
+        });
+
+        Button shareLogButton = findViewById(R.id.shareLog);
+        shareLogButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(DebugActivity.this);
+                Prefs prefs = new Prefs(sharedPrefs);
+                boolean logging_enabled = prefs.getBoolean("log_to_file", false);
+                if (logging_enabled) {
+                    showLogSharingWarning();
+                } else {
+                    showLogSharingNotEnabledAlert();
+                }
+            }
+        });
+
+        Button showWidgetsButton = findViewById(R.id.showWidgetsButton);
+        showWidgetsButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                showAllRegisteredAppWidgets();
+            }
+        });
+
+        Button unregisterWidgetsButton = findViewById(R.id.deleteWidgets);
+        unregisterWidgetsButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                unregisterAllRegisteredAppWidgets();
+            }
+        });
+
+        Button showWidgetsPrefsButton = findViewById(R.id.showWidgetsPrefs);
+        showWidgetsPrefsButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                showAppWidgetsPrefs();
+            }
+        });
+
+        Button deleteWidgetsPrefsButton = findViewById(R.id.deleteWidgetsPrefs);
+        deleteWidgetsPrefsButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                deleteWidgetsPrefs();
+            }
+        });
+
+        Button removeDevicePreferencesButton = findViewById(R.id.removeDevicePreferences);
+        removeDevicePreferencesButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                new MaterialAlertDialogBuilder(DebugActivity.this)
+                        .setCancelable(true)
+                        .setTitle(R.string.debugactivity_confirm_remove_device_preferences_title)
+                        .setMessage(R.string.debugactivity_confirm_remove_device_preferences)
+                        .setPositiveButton(R.string.ok, (dialog, which) -> {
+                            final GBApplication gbApp = (GBApplication) getApplicationContext();
+                            final List<GBDevice> devices = gbApp.getDeviceManager().getSelectedDevices();
+                            for(final GBDevice device : devices){
+                                GBApplication.deleteDeviceSpecificSharedPrefs(device.getAddress());
+                            }
+                        })
+                        .setNegativeButton(R.string.Cancel, (dialog, which) -> {})
+                        .show();
+            }
+        });
+
+        Button runDebugFunction = findViewById(R.id.runDebugFunction);
+        runDebugFunction.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                //SharedPreferences.Editor editor = GBApplication.getPrefs().getPreferences().edit();
+                //editor.remove("notification_list_is_blacklist").apply();
+            }
+        });
+
+        Button addDeviceButtonDebug = findViewById(R.id.addDeviceButtonDebug);
+        addDeviceButtonDebug.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                Map<String, Pair<Long, Integer>> allDevices = getAllSupportedDevices(getApplicationContext());
+
+                final LinearLayout linearLayout = new LinearLayout(DebugActivity.this);
+                linearLayout.setOrientation(LinearLayout.VERTICAL);
+
+                final LinearLayout macLayout = new LinearLayout(DebugActivity.this);
+                macLayout.setOrientation(LinearLayout.HORIZONTAL);
+                macLayout.setPadding(20, 0, 20, 0);
+
+                final TextView textView = new TextView(DebugActivity.this);
+                textView.setText("MAC Address: ");
+                final EditText editText = new EditText(DebugActivity.this);
+                selectedTestDeviceMAC = randomMac();
+                editText.setText(selectedTestDeviceMAC);
+                editText.addTextChangedListener(new TextWatcher() {
+                    @Override
+                    public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
+
+                    }
+
+                    @Override
+                    public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
+
+                    }
+
+                    @Override
+                    public void afterTextChanged(Editable editable) {
+                        selectedTestDeviceMAC = editable.toString();
+
+                    }
+                });
+
+                macLayout.addView(textView);
+                macLayout.addView(editText);
+
+                final Spinner deviceListSpinner = new Spinner(DebugActivity.this);
+                ArrayList<SpinnerWithIconItem> deviceListArray = new ArrayList<>();
+                for (Map.Entry<String, Pair<Long, Integer>> item : allDevices.entrySet()) {
+                    deviceListArray.add(new SpinnerWithIconItem(item.getKey(), item.getValue().first, item.getValue().second));
+                }
+                final SpinnerWithIconAdapter deviceListAdapter = new SpinnerWithIconAdapter(DebugActivity.this,
+                        R.layout.spinner_with_image_layout, R.id.spinner_item_text, deviceListArray);
+                deviceListSpinner.setAdapter(deviceListAdapter);
+                addListenerOnSpinnerDeviceSelection(deviceListSpinner);
+
+                linearLayout.addView(deviceListSpinner);
+                linearLayout.addView(macLayout);
+
+                new MaterialAlertDialogBuilder(DebugActivity.this)
+                        .setCancelable(true)
+                        .setTitle(R.string.add_test_device)
+                        .setView(linearLayout)
+                        .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
+                            @Override
+                            public void onClick(DialogInterface dialog, int which) {
+                                createTestDevice(DebugActivity.this, selectedTestDeviceKey, selectedTestDeviceMAC, null);
+                            }
+                        })
+                        .setNegativeButton(R.string.Cancel, new DialogInterface.OnClickListener() {
+                            @Override
+                            public void onClick(DialogInterface dialog, int which) {
+                            }
+                        })
+                        .show();
+            }
+        });
+
+        CheckBox activity_list_debug_extra_time_range = findViewById(R.id.activity_list_debug_extra_time_range);
+        activity_list_debug_extra_time_range.setAllCaps(true);
+        boolean activity_list_debug_extra_time_range_value = GBApplication.getPrefs().getPreferences().getBoolean("activity_list_debug_extra_time_range", false);
+        activity_list_debug_extra_time_range.setChecked(activity_list_debug_extra_time_range_value);
+
+        activity_list_debug_extra_time_range.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+            @Override
+            public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
+                GBApplication.getPrefs().getPreferences().getBoolean("activity_list_debug_extra_time_range", false);
+                SharedPreferences.Editor editor = GBApplication.getPrefs().getPreferences().edit();
+                editor.putBoolean("activity_list_debug_extra_time_range", b).apply();
+            }
+        });
+
+        Button startFitnessAppTracking = findViewById(R.id.startFitnessAppTracking);
+        startFitnessAppTracking.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                OpenTracksController.startRecording(DebugActivity.this);
+            }
+        });
+
+        Button stopFitnessAppTracking = findViewById(R.id.stopFitnessAppTracking);
+        stopFitnessAppTracking.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                OpenTracksController.stopRecording(DebugActivity.this);
+            }
+        });
+
+        Button stopPhoneGpsLocationListener = findViewById(R.id.stopPhoneGpsLocationListener);
+        stopPhoneGpsLocationListener.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                GBLocationService.stop(DebugActivity.this, null);
+            }
+        });
+
+        Button showCompanionDevices = findViewById(R.id.showCompanionDevices);
+        showCompanionDevices.setVisibility(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ? View.VISIBLE : View.GONE);
+        showCompanionDevices.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
+                    LOG.warn("Android version < O, companion devices not supported");
+                    return;
+                }
+
+                final CompanionDeviceManager manager = (CompanionDeviceManager) GBApplication.getContext().getSystemService(Context.COMPANION_DEVICE_SERVICE);
+                final List<String> associations = new ArrayList<>(manager.getAssociations());
+                Collections.sort(associations);
+                String companionDevicesList = String.format(Locale.ROOT, "%d companion devices", associations.size());
+                if (!associations.isEmpty()) {
+                    companionDevicesList += "\n\n" + StringUtils.join("\n", associations.toArray(new String[0]));
+                }
+
+                new MaterialAlertDialogBuilder(DebugActivity.this)
+                        .setCancelable(false)
+                        .setTitle("Companion Devices")
+                        .setMessage(companionDevicesList)
+                        .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
+                            @Override
+                            public void onClick(DialogInterface dialog, int which) {
+                            }
+                        })
+                        .show();
+            }
+        });
+
+        final Button pairAsCompanion = findViewById(R.id.pairAsCompanion);
+        pairAsCompanion.setVisibility(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ? View.VISIBLE : View.GONE);
+        pairAsCompanion.setOnClickListener(v -> {
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+                pairCurrentAsCompanion();
+            }
+        });
+
+        Button showStatusFitnessAppTracking = findViewById(R.id.showStatusFitnessAppTracking);
+        final int delay = 2 * 1000;
+
+        showStatusFitnessAppTracking.setOnClickListener(new View.OnClickListener() {
+            final Handler handler = new Handler();
+            Runnable runnable;
+
+            @Override
+            public void onClick(View v) {
+                final MaterialAlertDialogBuilder fitnesStatusBuilder = new MaterialAlertDialogBuilder(DebugActivity.this);
+                fitnesStatusBuilder
+                        .setCancelable(false)
+                        .setTitle("openTracksObserver Status")
+                        .setMessage("Starting openTracksObserver watcher, waiting for an update, refreshing every: " + delay + "ms")
+                        .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
+                            @Override
+                            public void onClick(DialogInterface dialog, int which) {
+                                handler.removeCallbacks(runnable);
+                            }
+                        });
+                final AlertDialog alert = fitnesStatusBuilder.show();
+
+
+                runnable = new Runnable() {
+                    @Override
+                    public void run() {
+                        LOG.debug("openTracksObserver debug watch dialog running");
+                        handler.postDelayed(this, delay); //schedule next execution
+
+                        OpenTracksContentObserver openTracksObserver = GBApplication.app().getOpenTracksObserver();
+                        if (openTracksObserver == null) {
+                            LOG.debug("openTracksObserver is null");
+                            alert.cancel();
+                            alert.setMessage("openTracksObserver not running");
+                            alert.show();
+                            return;
+                        }
+                        LOG.debug("openTracksObserver is not null, updating debug view");
+                        long timeSecs = openTracksObserver.getTimeMillisChange() / 1000;
+                        float distanceCM = openTracksObserver.getDistanceMeterChange() * 100;
+
+                        LOG.debug("Time: " + timeSecs + " distanceCM " + distanceCM);
+                        alert.cancel();
+                        alert.setMessage("TimeSec: " + timeSecs + " distanceCM " + distanceCM);
+                        alert.show();
+                    }
+                };
+                handler.postDelayed(runnable, delay);
+            }
+        });
+
+        Button cameraOpenButton = findViewById(R.id.cameraOpen);
+        cameraOpenButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+
+                Intent cameraIntent = new Intent(getApplicationContext(), CameraActivity.class);
+                cameraIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                cameraIntent.putExtra(CameraActivity.intentExtraEvent, GBDeviceEventCameraRemote.eventToInt(GBDeviceEventCameraRemote.Event.OPEN_CAMERA));
+                getApplicationContext().startActivity(cameraIntent);
+            }
+        });
+
+        Button startWelcomeActivity = findViewById(R.id.startWelcomeActivity);
+        startWelcomeActivity.setOnClickListener(view -> {
+            startActivity(new Intent(this, WelcomeActivity.class));
+        });
+
+        Button startPermissionsActivity = findViewById(R.id.startPermissionsActivity);
+        startPermissionsActivity.setOnClickListener(view -> {
+            startActivity(new Intent(this, PermissionsActivity.class));
+        });
+    }
+
+    @RequiresApi(Build.VERSION_CODES.O)
+    @Override
+    public void onActivityResult(int requestCode, int resultCode, Intent data) {
+        super.onActivityResult(requestCode, resultCode, data);
+
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O &&
+                requestCode == SELECT_DEVICE_REQUEST_CODE &&
+                resultCode == Activity.RESULT_OK) {
+
+            final BluetoothDevice deviceToPair = data.getParcelableExtra(CompanionDeviceManager.EXTRA_DEVICE);
+
+            if (deviceToPair != null) {
+                if (deviceToPair.getBondState() != BluetoothDevice.BOND_BONDED) {
+                    GB.toast("Creating bond...", Toast.LENGTH_SHORT, GB.INFO);
+                    deviceToPair.createBond();
+                } else {
+                    GB.toast("Bonding complete", Toast.LENGTH_LONG, GB.INFO);
+                }
+            } else {
+                GB.toast("No device to pair", Toast.LENGTH_LONG, GB.ERROR);
+            }
+        }
+    }
+
+    @RequiresApi(Build.VERSION_CODES.O)
+    private void pairCurrentAsCompanion() {
+        final GBApplication gbApp = (GBApplication) getApplicationContext();
+        final List<GBDevice> devices = gbApp.getDeviceManager().getSelectedDevices();
+        if (devices.size() != 1) {
+            GB.toast("Please connect to a single device that you want to pair as companion", Toast.LENGTH_LONG, GB.WARN);
+            return;
+        }
+
+        final GBDevice device = devices.get(0);
+
+        final CompanionDeviceManager manager = (CompanionDeviceManager) getSystemService(Context.COMPANION_DEVICE_SERVICE);
+
+        if (manager.getAssociations().contains(device.getAddress())) {
+            GB.toast(device.getAliasOrName() + " already paired as companion", Toast.LENGTH_LONG, GB.INFO);
+            return;
+        }
+
+        final BluetoothDeviceFilter deviceFilter = new BluetoothDeviceFilter.Builder()
+                .setAddress(device.getAddress())
+                .build();
+
+        final AssociationRequest pairingRequest = new AssociationRequest.Builder()
+                .addDeviceFilter(deviceFilter)
+                .setSingleDevice(true)
+                .build();
+
+        CompanionDeviceManager.Callback callback = new CompanionDeviceManager.Callback() {
+            @Override
+            public void onFailure(final CharSequence error) {
+                GB.toast("Companion pairing failed: " + error, Toast.LENGTH_LONG, GB.ERROR);
+            }
+
+            @Override
+            public void onDeviceFound(@NonNull final IntentSender chooserLauncher) {
+                GB.toast("Found device", Toast.LENGTH_SHORT, GB.INFO);
+
+                try {
+                    ActivityCompat.startIntentSenderForResult(
+                            DebugActivity.this,
+                            chooserLauncher,
+                            SELECT_DEVICE_REQUEST_CODE,
+                            null,
+                            0,
+                            0,
+                            0,
+                            null
+                    );
+                } catch (final IntentSender.SendIntentException e) {
+                    LOG.error("Failed to send intent", e);
+                }
+            }
+        };
+
+        manager.associate(pairingRequest, callback, null);
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+        if (dataLossSave != null ) {
+            dataLossSave.clear();
+            dataLossSave = null ;
+        }
+        dataLossSave = new Bundle();
+        dataLossSave.putString("editContent", editContent.getText().toString());
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        if (dataLossSave != null ) {
+            editContent.setText(dataLossSave.getString("editContent", ""));
+        }else{
+            editContent.setText("Test");
+        }
+    }
+
+    private void deleteWidgetsPrefs() {
+        WidgetPreferenceStorage widgetPreferenceStorage = new WidgetPreferenceStorage();
+        widgetPreferenceStorage.deleteWidgetsPrefs(DebugActivity.this);
+        widgetPreferenceStorage.showAppWidgetsPrefs(DebugActivity.this);
+    }
+
+    private void showAppWidgetsPrefs() {
+        WidgetPreferenceStorage widgetPreferenceStorage = new WidgetPreferenceStorage();
+        widgetPreferenceStorage.showAppWidgetsPrefs(DebugActivity.this);
+
+    }
+
+    private void showAllRegisteredAppWidgets() {
+        //https://stackoverflow.com/questions/17387191/check-if-a-widget-is-exists-on-homescreen-using-appwidgetid
+
+        AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(DebugActivity.this);
+        AppWidgetHost appWidgetHost = new AppWidgetHost(DebugActivity.this, 1); // for removing phantoms
+        int[] appWidgetIDs = appWidgetManager.getAppWidgetIds(new ComponentName(DebugActivity.this, Widget.class));
+        GB.toast("Number of registered app widgets: " + appWidgetIDs.length, Toast.LENGTH_SHORT, GB.INFO);
+        for (int appWidgetID : appWidgetIDs) {
+            GB.toast("Widget: " + appWidgetID, Toast.LENGTH_SHORT, GB.INFO);
+        }
+    }
+
+    private void unregisterAllRegisteredAppWidgets() {
+        //https://stackoverflow.com/questions/17387191/check-if-a-widget-is-exists-on-homescreen-using-appwidgetid
+
+        AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(DebugActivity.this);
+        AppWidgetHost appWidgetHost = new AppWidgetHost(DebugActivity.this, 1); // for removing phantoms
+        int[] appWidgetIDs = appWidgetManager.getAppWidgetIds(new ComponentName(DebugActivity.this, Widget.class));
+        GB.toast("Number of registered app widgets: " + appWidgetIDs.length, Toast.LENGTH_SHORT, GB.INFO);
+        for (int appWidgetID : appWidgetIDs) {
+            appWidgetHost.deleteAppWidgetId(appWidgetID);
+            GB.toast("Removing widget: " + appWidgetID, Toast.LENGTH_SHORT, GB.INFO);
+        }
+    }
+
+    private void showLogSharingNotEnabledAlert() {
+        new MaterialAlertDialogBuilder(this)
+                .setTitle(R.string.note)
+                .setPositiveButton(R.string.ok, null)
+                .setMessage(R.string.share_log_not_enabled_message)
+                .show();
+    }
+
+    private void showLogSharingWarning() {
+        new MaterialAlertDialogBuilder(this)
+                .setCancelable(true)
+                .setTitle(R.string.warning)
+                .setMessage(R.string.share_log_warning)
+                .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
+                    @Override
+                    public void onClick(DialogInterface dialog, int which) {
+                        shareLog();
+                    }
+                })
+                .setNegativeButton(R.string.Cancel, new DialogInterface.OnClickListener() {
+                    @Override
+                    public void onClick(DialogInterface dialog, int which) {
+                        // do nothing
+                    }
+                })
+                .show();
+    }
+
+    private void testNewFunctionality() {
+        GBApplication.deviceService().onTestNewFunction();
+    }
+
+    private void shareLog() {
+        String fileName = GBApplication.getLogPath();
+        if (fileName != null && fileName.length() > 0) {
+            // Flush the logs, so that we ensure latest lines are also there
+            GBApplication.getLogging().setImmediateFlush(true);
+            LOG.debug("Flushing logs before sharing");
+            GBApplication.getLogging().setImmediateFlush(false);
+
+            File logFile = new File(fileName);
+            if (!logFile.exists()) {
+                GB.toast("File does not exist", Toast.LENGTH_LONG, GB.INFO);
+                return;
+            }
+
+            final Uri providerUri = FileProvider.getUriForFile(
+                    this,
+                    getApplicationContext().getPackageName() + ".screenshot_provider",
+                    logFile
+            );
+
+            Intent emailIntent = new Intent(android.content.Intent.ACTION_SEND);
+            emailIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+            emailIntent.setType("*/*");
+            emailIntent.putExtra(EXTRA_SUBJECT, "Gadgetbridge log file");
+            emailIntent.putExtra(Intent.EXTRA_STREAM, providerUri);
+            startActivity(Intent.createChooser(emailIntent, "Share File"));
+        }
+    }
+
+    private void testNotification() {
+        Intent notificationIntent = new Intent(getApplicationContext(), DebugActivity.class);
+        notificationIntent.setPackage(BuildConfig.APPLICATION_ID);
+        notificationIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+                | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+        PendingIntent pendingIntent = PendingIntentUtils.getActivity(getApplicationContext(), 0,
+                notificationIntent, 0, false);
+
+        RemoteInput remoteInput = new RemoteInput.Builder(EXTRA_REPLY)
+                .build();
+
+        Intent replyIntent = new Intent(ACTION_REPLY);
+        replyIntent.setPackage(BuildConfig.APPLICATION_ID);
+
+        PendingIntent replyPendingIntent = PendingIntentUtils.getBroadcast(this, 0, replyIntent, 0, true);
+
+        NotificationCompat.Action action =
+                new NotificationCompat.Action.Builder(android.R.drawable.ic_input_add, "Reply", replyPendingIntent)
+                        .addRemoteInput(remoteInput)
+                        .build();
+
+        NotificationCompat.WearableExtender wearableExtender = new NotificationCompat.WearableExtender().addAction(action);
+
+        NotificationCompat.Builder ncomp = new NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID)
+                .setContentTitle(getString(R.string.test_notification))
+                .setContentText(getString(R.string.this_is_a_test_notification_from_gadgetbridge))
+                .setTicker(getString(R.string.this_is_a_test_notification_from_gadgetbridge))
+                .setSmallIcon(R.drawable.ic_notification)
+                .setAutoCancel(true)
+                .setContentIntent(pendingIntent)
+                .extend(wearableExtender);
+
+        GB.notify((int) System.currentTimeMillis(), ncomp.build(), this);
+    }
+
+    private void testPebbleKitNotification() {
+        Intent pebbleKitIntent = new Intent("com.getpebble.action.SEND_NOTIFICATION");
+        pebbleKitIntent.putExtra("messageType", "PEBBLE_ALERT");
+        pebbleKitIntent.putExtra("notificationData", "[{\"title\":\"PebbleKitTest\",\"body\":\"sent from Gadgetbridge\"}]");
+        getApplicationContext().sendBroadcast(pebbleKitIntent);
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(final MenuItem item) {
+        final int itemId = item.getItemId();
+        if (itemId == android.R.id.home) {
+            NavUtils.navigateUpFromSameTask(this);
+            return true;
+        }
+        return super.onOptionsItemSelected(item);
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        LocalBroadcastManager.getInstance(this).unregisterReceiver(mReceiver);
+        unregisterReceiver(mReceiver);
+    }
+
+    private void addListenerOnSpinnerDeviceSelection(Spinner spinner) {
+        spinner.setOnItemSelectedListener(new CustomOnDeviceSelectedListener());
+    }
+
+    public static void createTestDevice(Context context, long deviceKey, String deviceMac, String deviceName) {
+        if (deviceKey == SELECT_DEVICE) {
+            return;
+        }
+        DeviceType deviceType = DeviceType.values()[(int) deviceKey];
+        if(deviceName == null) {
+            int deviceNameResource = deviceType.getDeviceCoordinator().getDeviceNameResource();
+            if(deviceNameResource == 0){
+                deviceName = deviceType.name();
+            }else {
+                deviceName = context.getString(deviceNameResource);
+            }
+        };
+        try (
+            DBHandler db = GBApplication.acquireDB()) {
+            DaoSession daoSession = db.getDaoSession();
+            GBDevice gbDevice = new GBDevice(deviceMac, deviceName, "", null, deviceType);
+            gbDevice.setFirmwareVersion("N/A");
+            gbDevice.setFirmwareVersion2("N/A");
+
+            //this causes the attributes (fw version) to be stored as well. Not much useful, but still...
+            gbDevice.setState(GBDevice.State.INITIALIZED);
+
+            Device device = DBHelper.getDevice(gbDevice, daoSession); //the addition happens here
+            Intent refreshIntent = new Intent(DeviceManager.ACTION_REFRESH_DEVICELIST);
+            LocalBroadcastManager.getInstance(context).sendBroadcast(refreshIntent);
+            GB.toast(context, "Added test device: " + deviceName, Toast.LENGTH_SHORT, GB.INFO);
+
+        } catch (
+                Exception e) {
+            GB.log("Error accessing database", GB.ERROR, e);
+        }
+    }
+
+    private String randomMac() {
+        Random random = new Random();
+        String separator = ":";
+        String[] mac = {
+                String.format("%02x", random.nextInt(0xff)),
+                String.format("%02x", random.nextInt(0xff)),
+                String.format("%02x", random.nextInt(0xff)),
+                String.format("%02x", random.nextInt(0xff)),
+                String.format("%02x", random.nextInt(0xff)),
+                String.format("%02x", random.nextInt(0xff))
+        };
+        return TextUtils.join(separator, mac).toUpperCase(Locale.ROOT);
+    }
+
+    private void displayWeatherInfo(final WeatherSpec weatherSpec) {
+        final String weatherInfo = getWeatherInfo(weatherSpec);
+
+        new MaterialAlertDialogBuilder(DebugActivity.this)
+                .setCancelable(true)
+                .setTitle("Cached Weather Data")
+                .setMessage(weatherInfo)
+                .setPositiveButton(R.string.ok, (dialog, which) -> {
+                })
+                .setNeutralButton(android.R.string.copy, (dialog, which) -> {
+                    final ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
+                    ClipData clip = ClipData.newPlainText("Weather Info", weatherInfo);
+                    clipboard.setPrimaryClip(clip);
+                })
+                .show();
+    }
+
+    private String getWeatherInfo(final WeatherSpec weatherSpec) {
+        final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.ROOT);
+
+        final StringBuilder builder = new StringBuilder();
+
+        if (weatherSpec == null)
+            return "Weather cache is empty.";
+
+        builder.append("Location: ").append(weatherSpec.location).append("\n");
+        builder.append("Timestamp: ").append(weatherSpec.timestamp).append("\n");
+        builder.append("Current Temp: ").append(weatherSpec.currentTemp).append(" K\n");
+        builder.append("Max Temp: ").append(weatherSpec.todayMaxTemp).append(" K\n");
+        builder.append("Min Temp: ").append(weatherSpec.todayMinTemp).append(" K\n");
+        builder.append("Condition: ").append(weatherSpec.currentCondition).append("\n");
+        builder.append("Condition Code: ").append(weatherSpec.currentConditionCode).append("\n");
+        builder.append("Humidity: ").append(weatherSpec.currentHumidity).append("\n");
+        builder.append("Wind Speed: ").append(weatherSpec.windSpeed).append(" kmph\n");
+        builder.append("Wind Direction: ").append(weatherSpec.windDirection).append(" deg\n");
+        builder.append("UV Index: ").append(weatherSpec.uvIndex).append("\n");
+        builder.append("Precip Probability: ").append(weatherSpec.precipProbability).append(" %\n");
+        builder.append("Dew Point: ").append(weatherSpec.dewPoint).append(" K\n");
+        builder.append("Pressure: ").append(weatherSpec.pressure).append(" mb\n");
+        builder.append("Cloud Cover: ").append(weatherSpec.cloudCover).append(" %\n");
+        builder.append("Visibility: ").append(weatherSpec.visibility).append(" m\n");
+        builder.append("Sun Rise: ").append(sdf.format(new Date(weatherSpec.sunRise * 1000L))).append("\n");
+        builder.append("Sun Set: ").append(sdf.format(new Date(weatherSpec.sunSet * 1000L))).append("\n");
+        builder.append("Moon Rise: ").append(sdf.format(new Date(weatherSpec.moonRise * 1000L))).append("\n");
+        builder.append("Moon Set: ").append(sdf.format(new Date(weatherSpec.moonSet * 1000L))).append("\n");
+        builder.append("Moon Phase: ").append(weatherSpec.moonPhase).append(" deg\n");
+        builder.append("Latitude: ").append(weatherSpec.latitude).append("\n");
+        builder.append("Longitude: ").append(weatherSpec.longitude).append("\n");
+        builder.append("Feels Like Temp: ").append(weatherSpec.feelsLikeTemp).append(" K\n");
+        builder.append("Is Current Location: ").append(weatherSpec.isCurrentLocation).append("\n");
+
+        if (weatherSpec.airQuality != null) {
+            builder.append("Air Quality aqi: ").append(weatherSpec.airQuality.aqi).append("\n");
+            builder.append("Air Quality co: ").append(weatherSpec.airQuality.co).append("\n");
+            builder.append("Air Quality no2: ").append(weatherSpec.airQuality.no2).append("\n");
+            builder.append("Air Quality o3: ").append(weatherSpec.airQuality.o3).append("\n");
+            builder.append("Air Quality pm10: ").append(weatherSpec.airQuality.pm10).append("\n");
+            builder.append("Air Quality pm25: ").append(weatherSpec.airQuality.pm25).append("\n");
+            builder.append("Air Quality so2: ").append(weatherSpec.airQuality.so2).append("\n");
+            builder.append("Air Quality coAqi: ").append(weatherSpec.airQuality.coAqi).append("\n");
+            builder.append("Air Quality no2Aqi: ").append(weatherSpec.airQuality.no2Aqi).append("\n");
+            builder.append("Air Quality o3Aqi: ").append(weatherSpec.airQuality.o3Aqi).append("\n");
+            builder.append("Air Quality pm10Aqi: ").append(weatherSpec.airQuality.pm10Aqi).append("\n");
+            builder.append("Air Quality pm25Aqi: ").append(weatherSpec.airQuality.pm25Aqi).append("\n");
+            builder.append("Air Quality so2Aqi: ").append(weatherSpec.airQuality.so2Aqi).append("\n");
+        } else {
+            builder.append("Air Quality: null\n");
+        }
+
+        int i = 0;
+        for (final WeatherSpec.Daily daily : weatherSpec.forecasts) {
+            builder.append("-------------\n");
+            builder.append("-->Day ").append(i++).append("\n");
+            builder.append("Max Temp: ").append(daily.maxTemp).append(" K\n");
+            builder.append("Min Temp: ").append(daily.minTemp).append(" K\n");
+            builder.append("Condition Code: ").append(daily.conditionCode).append("\n");
+            builder.append("Humidity: ").append(daily.humidity).append("\n");
+            builder.append("Wind Speed: ").append(daily.windSpeed).append(" kmph\n");
+            builder.append("Wind Direction: ").append(daily.windDirection).append(" deg\n");
+            builder.append("UV Index: ").append(daily.uvIndex).append("\n");
+            builder.append("Precip Probability: ").append(daily.precipProbability).append(" %\n");
+            builder.append("Sun Rise: ").append(sdf.format(new Date(daily.sunRise * 1000L))).append("\n");
+            builder.append("Sun Set: ").append(sdf.format(new Date(daily.sunSet * 1000L))).append("\n");
+            builder.append("Moon Rise: ").append(sdf.format(new Date(daily.moonRise * 1000L))).append("\n");
+            builder.append("Moon Set: ").append(sdf.format(new Date(daily.moonSet * 1000L))).append("\n");
+            builder.append("Moon Phase: ").append(daily.moonPhase).append(" deg\n");
+
+            if (daily.airQuality != null) {
+                builder.append("Air Quality aqi: ").append(daily.airQuality.aqi).append("\n");
+                builder.append("Air Quality co: ").append(daily.airQuality.co).append("\n");
+                builder.append("Air Quality no2: ").append(daily.airQuality.no2).append("\n");
+                builder.append("Air Quality o3: ").append(daily.airQuality.o3).append("\n");
+                builder.append("Air Quality pm10: ").append(daily.airQuality.pm10).append("\n");
+                builder.append("Air Quality pm25: ").append(daily.airQuality.pm25).append("\n");
+                builder.append("Air Quality so2: ").append(daily.airQuality.so2).append("\n");
+                builder.append("Air Quality coAqi: ").append(daily.airQuality.coAqi).append("\n");
+                builder.append("Air Quality no2Aqi: ").append(daily.airQuality.no2Aqi).append("\n");
+                builder.append("Air Quality o3Aqi: ").append(daily.airQuality.o3Aqi).append("\n");
+                builder.append("Air Quality pm10Aqi: ").append(daily.airQuality.pm10Aqi).append("\n");
+                builder.append("Air Quality pm25Aqi: ").append(daily.airQuality.pm25Aqi).append("\n");
+                builder.append("Air Quality so2Aqi: ").append(daily.airQuality.so2Aqi).append("\n");
+            } else {
+                builder.append("Air Quality: null\n");
+            }
+        }
+
+        builder.append("=============\n");
+
+        for (final WeatherSpec.Hourly hourly : weatherSpec.hourly) {
+            builder.append("-------------\n");
+            builder.append("-->Hour: ").append(sdf.format(new Date(hourly.timestamp * 1000L))).append("\n");
+            builder.append("Max Temp: ").append(hourly.temp).append(" K\n");
+            builder.append("Condition Code: ").append(hourly.conditionCode).append("\n");
+            builder.append("Humidity: ").append(hourly.humidity).append("\n");
+            builder.append("Wind Speed: ").append(hourly.windSpeed).append(" kmph\n");
+            builder.append("Wind Direction: ").append(hourly.windDirection).append(" deg\n");
+            builder.append("UV Index: ").append(hourly.uvIndex).append("\n");
+            builder.append("Precip Probability: ").append(hourly.precipProbability).append(" %\n");
+        }
+
+        return builder.toString();
+    }
+
+    public static Map<String, Pair<Long, Integer>> getAllSupportedDevices(Context appContext) {
+        LinkedHashMap<String, Pair<Long, Integer>> newMap = new LinkedHashMap<>(1);
+        GBApplication app = (GBApplication) appContext;
+        for (DeviceType deviceType : DeviceType.values()) {
+            DeviceCoordinator coordinator = deviceType.getDeviceCoordinator();
+            int icon = coordinator.getDefaultIconResource();
+            String name = app.getString(coordinator.getDeviceNameResource());
+            if (!name.startsWith(coordinator.getManufacturer())) {
+                name += " (" + coordinator.getManufacturer() + ")";
+            }
+            long deviceId = deviceType.ordinal();
+            newMap.put(name, new Pair(deviceId, icon));
+        }
+        TreeMap <String, Pair<Long, Integer>> sortedMap = new TreeMap<>(newMap);
+        newMap = new LinkedHashMap<>(1);
+        newMap.put(app.getString(R.string.widget_settings_select_device_title), new Pair(SELECT_DEVICE, R.drawable.ic_device_unknown));
+        newMap.put(app.getString(R.string.devicetype_scannable), new Pair((long) DeviceType.SCANNABLE.ordinal(), R.drawable.ic_device_scannable));
+        newMap.put(app.getString(R.string.devicetype_ble_gatt_client), new Pair((long) DeviceType.BLE_GATT_CLIENT.ordinal(), R.drawable.ic_device_scannable));
+
+        newMap.putAll(sortedMap);
+
+        return newMap;
+    }
+
+    public class CustomOnDeviceSelectedListener implements AdapterView.OnItemSelectedListener {
+
+        public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
+            SpinnerWithIconItem selectedItem = (SpinnerWithIconItem) parent.getItemAtPosition(pos);
+            selectedTestDeviceKey = selectedItem.getId();
+        }
+
+        @Override
+        public void onNothingSelected(AdapterView<?> arg0) {
+            // TODO Auto-generated method stub
+        }
+
+    }
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DevicesFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DevicesFragment.java
new file mode 100644
index 0000000..726fea7
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DevicesFragment.java
@@ -0,0 +1,282 @@
+/*  Copyright (C) 2016-2024 Andreas Shimokawa, Andrzej Surowiec, Arjan
+    Schrijver, Carsten Pfeiffer, Daniel Dakhno, Daniele Gobbetti, Ganblejs,
+    gfwilliams, Gordon Williams, Johannes Tysiak, Jos茅 Rebelo, marco.altomonte,
+    Petr Van臎k, Taavi Eom盲e
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Build;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.Nullable;
+import androidx.fragment.app.Fragment;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.google.android.material.floatingactionbutton.FloatingActionButton;
+
+import java.io.Serializable;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Objects;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+import nodomain.freeyourgadget.gadgetbridge.activities.discovery.DiscoveryActivityV2;
+import nodomain.freeyourgadget.gadgetbridge.adapter.GBDeviceAdapterv2;
+import nodomain.freeyourgadget.gadgetbridge.database.DBAccess;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.DeviceManager;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.model.DailyTotals;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class DevicesFragment extends Fragment {
+
+    private DeviceManager deviceManager;
+    private GBDeviceAdapterv2 mGBDeviceAdapter;
+    private RecyclerView deviceListView;
+    private FloatingActionButton fab;
+    List<GBDevice> deviceList;
+    private  HashMap<String, DailyTotals> deviceActivityHashMap = new HashMap();
+
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            final GBDevice device = intent.getParcelableExtra(GBDevice.EXTRA_DEVICE);
+            switch (Objects.requireNonNull(action)) {
+                case DeviceManager.ACTION_DEVICES_CHANGED:
+                case GBApplication.ACTION_NEW_DATA:
+                    if (action.equals(GBApplication.ACTION_NEW_DATA)) {
+                        createRefreshTask("get activity data", requireContext(), device).execute();
+                    }
+                    if (device != null) {
+                        // Refresh only this device
+                        refreshSingleDevice(device);
+                    } else {
+                        refreshPairedDevices();
+                    }
+
+                    break;
+                case DeviceService.ACTION_REALTIME_SAMPLES:
+                    handleRealtimeSample(device, intent.getSerializableExtra(DeviceService.EXTRA_REALTIME_SAMPLE));
+                    break;
+            }
+        }
+    };
+
+    private void handleRealtimeSample(GBDevice device, Serializable extra) {
+        if (extra instanceof ActivitySample) {
+            ActivitySample sample = (ActivitySample) extra;
+            if (HeartRateUtils.getInstance().isValidHeartRateValue(sample.getHeartRate())) {
+                if (device != null) {
+                    refreshSingleDevice(device);
+                } else {
+                    refreshPairedDevices();
+                }
+            }
+        }
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
+        View currentView = inflater.inflate(R.layout.fragment_devices, container, false);
+
+        deviceManager = ((GBApplication) getActivity().getApplication()).getDeviceManager();
+
+        deviceListView = currentView.findViewById(R.id.deviceListView);
+        deviceListView.setHasFixedSize(true);
+        deviceListView.setLayoutManager(new LinearLayoutManager(currentView.getContext()));
+
+        deviceList = deviceManager.getDevices();
+        mGBDeviceAdapter = new GBDeviceAdapterv2(currentView.getContext(), deviceList, deviceActivityHashMap);
+        mGBDeviceAdapter.setHasStableIds(true);
+
+        deviceListView.setAdapter(this.mGBDeviceAdapter);
+
+        // get activity data asynchronously, this fills the deviceActivityHashMap
+        // and calls refreshPairedDevices() 鈫� notifyDataSetChanged
+        deviceListView.post(new Runnable() {
+            @Override
+            public void run() {
+                if (getContext() != null) {
+                    createRefreshTask("get activity data", getContext(), null).execute();
+                }
+            }
+        });
+
+        fab = currentView.findViewById(R.id.fab);
+        fab.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                launchDiscoveryActivity();
+            }
+        });
+
+        showFabIfNeccessary();
+
+        /* uncomment to enable fixed-swipe to reveal more actions
+
+        ItemTouchHelper swipeToDismissTouchHelper = new ItemTouchHelper(new ItemTouchHelper.SimpleCallback(
+                ItemTouchHelper.LEFT , ItemTouchHelper.RIGHT) {
+            @Override
+            public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
+                if(dX>50)
+                    dX = 50;
+                super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
+
+            }
+
+            @Override
+            public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
+                GB.toast(getBaseContext(), "onMove", Toast.LENGTH_LONG, GB.ERROR);
+
+                return false;
+            }
+
+            @Override
+            public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
+                GB.toast(getBaseContext(), "onSwiped", Toast.LENGTH_LONG, GB.ERROR);
+
+            }
+
+            @Override
+            public void onChildDrawOver(Canvas c, RecyclerView recyclerView,
+                                        RecyclerView.ViewHolder viewHolder, float dX, float dY,
+                                        int actionState, boolean isCurrentlyActive) {
+            }
+        });
+
+        swipeToDismissTouchHelper.attachToRecyclerView(deviceListView);
+        */
+
+        registerForContextMenu(deviceListView);
+
+        IntentFilter filterLocal = new IntentFilter();
+        filterLocal.addAction(GBApplication.ACTION_NEW_DATA);
+        filterLocal.addAction(DeviceManager.ACTION_DEVICES_CHANGED);
+        filterLocal.addAction(DeviceService.ACTION_REALTIME_SAMPLES);
+        LocalBroadcastManager.getInstance(requireContext()).registerReceiver(mReceiver, filterLocal);
+
+        refreshPairedDevices();
+
+        if (GB.isBluetoothEnabled() && deviceList.isEmpty() && Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
+            startActivity(new Intent(getActivity(), DiscoveryActivityV2.class));
+        }
+
+        return currentView;
+    }
+
+    private void launchDiscoveryActivity() {
+        startActivity(new Intent(getActivity(), DiscoveryActivityV2.class));
+    }
+
+    private void showFabIfNeccessary() {
+        if (GBApplication.getPrefs().getBoolean("display_add_device_fab", true)) {
+            fab.show();
+        } else {
+            if (deviceManager.getDevices().size() < 1) {
+                fab.show();
+            } else {
+                fab.hide();
+            }
+        }
+    }
+
+    @Override
+    public void onDestroy() {
+        if (deviceListView != null) unregisterForContextMenu(deviceListView);
+        LocalBroadcastManager.getInstance(requireContext()).unregisterReceiver(mReceiver);
+        super.onDestroy();
+    }
+
+    private DailyTotals getSteps(GBDevice device, DBHandler db) {
+        Calendar day = GregorianCalendar.getInstance();
+
+        return DailyTotals.getDailyTotalsForDevice(device, day, db);
+    }
+
+    public void refreshPairedDevices() {
+        if (mGBDeviceAdapter != null) {
+            mGBDeviceAdapter.rebuildFolders();
+            mGBDeviceAdapter.notifyDataSetChanged();
+        }
+    }
+
+    public void refreshSingleDevice(final GBDevice device) {
+        if (mGBDeviceAdapter != null) {
+            mGBDeviceAdapter.refreshSingleDevice(device);
+        }
+    }
+
+    public RefreshTask createRefreshTask(String task, Context context, GBDevice device) {
+        return new RefreshTask(task, context, device);
+    }
+
+    public class RefreshTask extends DBAccess {
+        private final GBDevice device;
+
+        public RefreshTask(final String task, final Context context, final GBDevice device) {
+            super(task, context);
+            this.device = device;
+        }
+
+        @Override
+        protected void doInBackground(final DBHandler db) {
+            if (device != null) {
+                updateDevice(db, device);
+            } else {
+                for (GBDevice gbDevice : deviceList) {
+                    updateDevice(db, gbDevice);
+                }
+            }
+        }
+
+        private void updateDevice(final DBHandler db, final GBDevice gbDevice) {
+            final DeviceCoordinator coordinator = gbDevice.getDeviceCoordinator();
+            final boolean showActivityCard = GBApplication.getDevicePrefs(gbDevice).getBoolean(DeviceSettingsPreferenceConst.PREFS_ACTIVITY_IN_DEVICE_CARD, true);
+            if (coordinator.supportsActivityTracking() && showActivityCard) {
+                final DailyTotals stepsAndSleepData = getSteps(gbDevice, db);
+                deviceActivityHashMap.put(gbDevice.getAddress(), stepsAndSleepData);
+            }
+        }
+
+        @Override
+        protected void onPostExecute(final Object o) {
+            if (device != null) {
+                refreshSingleDevice(device);
+            } else {
+                refreshPairedDevices();
+            }
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ExternalPebbleJSActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ExternalPebbleJSActivity.java
new file mode 100644
index 0000000..9f1c46a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ExternalPebbleJSActivity.java
@@ -0,0 +1,242 @@
+/*  Copyright (C) 2016-2024 Andreas Shimokawa, Carsten Pfeiffer, Daniele
+    Gobbetti, Davis Mosenkovs, Lem Dulfo, Taavi Eom盲e, Uwe Hermann
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities;
+
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.webkit.JavascriptInterface;
+import android.webkit.WebSettings;
+import android.webkit.WebView;
+import android.widget.FrameLayout;
+import android.widget.Toast;
+
+import androidx.annotation.NonNull;
+import androidx.core.app.NavUtils;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.DeviceManager;
+import nodomain.freeyourgadget.gadgetbridge.devices.pebble.PebbleCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceCommunicationService;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.webview.GBChromeClient;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.webview.GBWebClient;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.webview.JSInterface;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+import nodomain.freeyourgadget.gadgetbridge.util.GBPrefs;
+import nodomain.freeyourgadget.gadgetbridge.util.WebViewSingleton;
+
+import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_CONNECT;
+
+public class ExternalPebbleJSActivity extends AbstractGBActivity {
+
+    private static final Logger LOG = LoggerFactory.getLogger(ExternalPebbleJSActivity.class);
+
+    private Uri confUri;
+    /**
+     * When bgjs is enabled, this field refers to the WebViewSingleton,
+     * otherwise it refers to the legacy webview from the activity_legacy_external_pebble_js layout
+     */
+    private WebView myWebView;
+    public static final String START_BG_WEBVIEW = "start_webview";
+    public static final String SHOW_CONFIG = "configure";
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        Bundle extras = getIntent().getExtras();
+
+        boolean showConfig = false;
+
+        UUID currentUUID = null;
+        GBDevice currentDevice = null;
+
+        if (extras == null) {
+            confUri = getIntent().getData();
+            if(confUri.getScheme().equals("gadgetbridge")) {
+                try {
+                    currentUUID = UUID.fromString(confUri.getHost());
+                } catch (IllegalArgumentException e) {
+                    LOG.error("UUID in incoming configuration is not a valid UUID: " +confUri.toString());
+                }
+
+                //first check if we are still connected to a pebble
+                DeviceManager deviceManager = ((GBApplication) getApplication()).getDeviceManager();
+                List<GBDevice> deviceList = deviceManager.getDevices();
+                for (GBDevice device : deviceList) {
+                    if (device.getState() == GBDevice.State.INITIALIZED && device.getType().equals(DeviceType.PEBBLE)) {
+                        currentDevice = device;
+                        break;
+                    }
+                }
+                if (currentDevice == null) {
+                    //then try to reconnect to one of last connected Pebble devices
+                    Set<String> lastDeviceAddresses = GBApplication.getPrefs().getStringSet(GBPrefs.LAST_DEVICE_ADDRESSES, Collections.emptySet());
+                    for (GBDevice device : deviceList) {
+                        if (!device.isConnected() && device.getType() == DeviceType.PEBBLE && lastDeviceAddresses.contains(device.getAddress())) {
+                            Intent intent = new Intent(this, DeviceCommunicationService.class)
+                                    .setAction(ACTION_CONNECT)
+                                    .putExtra(GBDevice.EXTRA_DEVICE, device);
+                            this.startService(intent);
+                            currentDevice = device;
+                            break;
+                        }
+                    }
+                }
+
+                showConfig = true; //we are getting incoming configuration data
+            }
+        } else {
+            currentDevice = extras.getParcelable(GBDevice.EXTRA_DEVICE);
+            currentUUID = (UUID) extras.getSerializable(DeviceService.EXTRA_APP_UUID);
+
+            if (currentDevice != null && extras.getBoolean(START_BG_WEBVIEW, false) && ((PebbleCoordinator) currentDevice.getDeviceCoordinator()).isBackgroundJsEnabled(currentDevice)) {
+                startBackgroundWebViewAndFinish();
+                return;
+            }
+            showConfig = extras.getBoolean(SHOW_CONFIG, false);
+        }
+
+        if (currentDevice != null && ((PebbleCoordinator) currentDevice.getDeviceCoordinator()).isBackgroundJsEnabled(currentDevice)) {
+            if (showConfig) {
+                Objects.requireNonNull(currentDevice, "Must provide a device when invoking this activity");
+                Objects.requireNonNull(currentUUID, "Must provide a uuid when invoking this activity");
+                WebViewSingleton.getInstance().runJavascriptInterface(this, currentDevice, currentUUID);
+            }
+
+            // FIXME: is this really supposed to be outside the check for SHOW_CONFIG?
+            setupBGWebView();
+        } else {
+            Objects.requireNonNull(currentDevice, "Must provide a device when invoking this activity without bgjs");
+            Objects.requireNonNull(currentUUID, "Must provide a uuid when invoking this activity without bgjs");
+            setupLegacyWebView(currentDevice, currentUUID);
+        }
+    }
+
+    private void startBackgroundWebViewAndFinish() {
+        WebViewSingleton.ensureCreated(this);
+        finish();
+    }
+
+    private void setupBGWebView() {
+        setContentView(R.layout.activity_external_pebble_js);
+        myWebView = WebViewSingleton.getInstance().getWebView(this);
+        if (myWebView.getParent() != null) {
+            ((ViewGroup) myWebView.getParent()).removeView(myWebView);
+        }
+        myWebView.setWillNotDraw(false);
+        myWebView.removeJavascriptInterface("GBActivity");
+        myWebView.addJavascriptInterface(new ActivityJSInterface(), "GBActivity");
+        FrameLayout fl = (FrameLayout) findViewById(R.id.webview_placeholder);
+        fl.addView(myWebView);
+
+        myWebView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
+            @Override
+            public void onViewAttachedToWindow(View v) {
+                v.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+            }
+
+            @Override
+            public void onViewDetachedFromWindow(View v) {
+                v.removeOnAttachStateChangeListener(this);
+                FrameLayout fl = (FrameLayout) findViewById(R.id.webview_placeholder);
+                fl.removeAllViews();
+            }
+        });
+    }
+
+    private void setupLegacyWebView(@NonNull GBDevice device, @NonNull UUID uuid) {
+        setContentView(R.layout.activity_legacy_external_pebble_js);
+        myWebView = (WebView) findViewById(R.id.configureWebview);
+        myWebView.clearCache(true);
+        myWebView.setWebViewClient(new GBWebClient());
+        myWebView.setWebChromeClient(new GBChromeClient());
+        WebSettings webSettings = myWebView.getSettings();
+        //noinspection SetJavaScriptEnabled
+        webSettings.setJavaScriptEnabled(true);
+        //needed to access the DOM
+        webSettings.setDomStorageEnabled(true);
+        //needed for localstorage
+        webSettings.setDatabaseEnabled(true);
+        // #3373 #3424 - Fix configuration for pebble apps
+        // TODO: this should use a WebViewAssetLoader
+        webSettings.setAllowFileAccess(true);
+
+        JSInterface gbJSInterface = new JSInterface(device, uuid);
+        myWebView.addJavascriptInterface(gbJSInterface, "GBjs");
+        myWebView.addJavascriptInterface(new ActivityJSInterface(), "GBActivity");
+
+        myWebView.loadUrl("file:///android_asset/app_config/configure.html");
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        String queryString = "";
+        if (confUri != null) {
+            //getting back with configuration data
+            LOG.debug("WEBVIEW returned config: " + confUri.toString());
+            try {
+                queryString = confUri.getEncodedQuery();
+            } catch (IllegalArgumentException e) {
+                GB.toast("returned uri: " + confUri.toString(), Toast.LENGTH_LONG, GB.ERROR);
+            }
+            myWebView.stopLoading();
+            myWebView.loadUrl("file:///android_asset/app_config/configure.html?" + queryString);
+        }
+    }
+    @Override
+    protected void onNewIntent(Intent incoming) {
+        incoming.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+        super.onNewIntent(incoming);
+        confUri = incoming.getData();
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(final MenuItem item) {
+        final int itemId = item.getItemId();
+        if (itemId == android.R.id.home) {
+            NavUtils.navigateUpFromSameTask(this);
+            return true;
+        }
+        return super.onOptionsItemSelected(item);
+    }
+
+    private class ActivityJSInterface {
+
+        @JavascriptInterface
+        public void closeActivity() {
+            NavUtils.navigateUpFromSameTask(ExternalPebbleJSActivity.this);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/FindPhoneActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/FindPhoneActivity.java
new file mode 100644
index 0000000..4516c0a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/FindPhoneActivity.java
@@ -0,0 +1,218 @@
+/*  Copyright (C) 2018-2024 Andreas Shimokawa, Anemograph, Carsten Pfeiffer,
+    Cre3per, Daniele Gobbetti, Dmitriy Bogdanov, Jos茅 Rebelo, Pauli Salmenrinne
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.AssetFileDescriptor;
+import android.media.AudioManager;
+import android.media.MediaPlayer;
+import android.media.RingtoneManager;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.VibrationEffect;
+import android.os.Vibrator;
+import android.view.View;
+import android.widget.Button;
+
+import androidx.core.content.ContextCompat;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+import nodomain.freeyourgadget.gadgetbridge.util.GBPrefs;
+
+
+public class FindPhoneActivity extends AbstractGBActivity {
+    private static final Logger LOG = LoggerFactory.getLogger(FindPhoneActivity.class);
+
+    public static final String ACTION_FOUND = "nodomain.freeyourgadget.gadgetbridge.findphone.action.reply";
+    public static final String ACTION_VIBRATE = "nodomain.freeyourgadget.gadgetbridge.findphone.action.vibrate";
+    public static final String ACTION_RING = "nodomain.freeyourgadget.gadgetbridge.findphone.action.ring";
+
+    public static final String EXTRA_RING = "extra_ring";
+
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            if (action != null) {
+                LOG.info("Got action: {}", action);
+
+                switch (action) {
+                    case ACTION_FOUND:
+                        finish();
+                        break;
+                    case ACTION_VIBRATE:
+                        stopSound();
+                        break;
+                    case ACTION_RING:
+                        playRingtone();
+                        break;
+                }
+            }
+        }
+    };
+
+    Vibrator mVibrator;
+    AudioManager mAudioManager;
+    int userVolume;
+    MediaPlayer mp;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_find_phone);
+
+        final boolean ring = getIntent().getBooleanExtra(EXTRA_RING, true);
+
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(ACTION_FOUND);
+        filter.addAction(ACTION_VIBRATE);
+        filter.addAction(ACTION_RING);
+        LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, filter);
+        ContextCompat.registerReceiver(this, mReceiver, filter, ContextCompat.RECEIVER_NOT_EXPORTED); // for ACTION_FOUND
+
+        Button foundButton = findViewById(R.id.foundbutton);
+        foundButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                finish();
+            }
+        });
+
+        GB.removeNotification(GB.NOTIFICATION_ID_PHONE_FIND, this);
+
+        vibrate();
+        if (ring) {
+            playRingtone();
+        }
+        GBApplication.deviceService().onFindPhone(true);
+    }
+
+    private void vibrate() {
+        mVibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
+
+        long[] vibrationPattern = new long[]{1000, 1000};
+
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+            VibrationEffect vibrationEffect = VibrationEffect.createWaveform(vibrationPattern, 0);
+
+            mVibrator.vibrate(vibrationEffect);
+        } else {
+            mVibrator.vibrate(vibrationPattern, 0);
+        }
+    }
+
+    private void playRingtone() {
+        mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
+        if (mAudioManager != null) {
+            userVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_ALARM);
+        }
+        if (mp != null && mp.isPlaying()) {
+            LOG.warn("Already playing");
+            return;
+        } else if (mp == null) {
+            mp = new MediaPlayer();
+        }
+
+        if (!playConfiguredRingtone()) {
+            playFallbackRingtone();
+        }
+
+        if (mAudioManager != null) {
+            userVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_ALARM);
+            mAudioManager.setStreamVolume(AudioManager.STREAM_ALARM, mAudioManager.getStreamMaxVolume(AudioManager.STREAM_ALARM), AudioManager.FLAG_PLAY_SOUND);
+        }
+    }
+
+    private void stopVibration() {
+        mVibrator.cancel();
+    }
+
+    /**
+     * Attempt to play the configured ringtone. This fails to get the default ringtone on some ROMs
+     * (<a href="https://codeberg.org/Freeyourgadget/Gadgetbridge/pulls/2697">#2697</a>)
+     *
+     * @return whether playing the configured ringtone was successful or not.
+     */
+    private boolean playConfiguredRingtone() {
+        try {
+            Uri ringtoneUri = Uri.parse(GBApplication.getPrefs().getString(GBPrefs.PING_TONE, RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE).toString()));
+            mp.setDataSource(this, ringtoneUri);
+            mp.setAudioStreamType(AudioManager.STREAM_ALARM);
+            mp.setLooping(true);
+            mp.prepare();
+            mp.start();
+        } catch (final Exception e) {
+            LOG.warn("Failed to play configured ringtone", e);
+            return false;
+        }
+
+        return true;
+    }
+
+    private void playFallbackRingtone() {
+        try {
+            final AssetFileDescriptor afd = getBaseContext().getResources().openRawResourceFd(R.raw.ping_tone);
+            if (afd == null) {
+                LOG.error("Failed to load fallback ringtone");
+                return;
+            }
+
+            mp.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
+            mp.setAudioStreamType(AudioManager.STREAM_ALARM);
+            mp.setLooping(true);
+            mp.prepare();
+            mp.start();
+        } catch (final Exception e) {
+            LOG.warn("Failed to play fallback ringtone", e);
+        }
+    }
+
+    private void stopSound() {
+        if (mAudioManager != null) {
+            mAudioManager.setStreamVolume(AudioManager.STREAM_ALARM, userVolume, AudioManager.FLAG_PLAY_SOUND);
+        }
+        if (mp != null) {
+            mp.stop();
+            mp.reset();
+            mp.release();
+            mp = null;
+        }
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+
+        stopVibration();
+        stopSound();
+        GBApplication.deviceService().onFindPhone(false);
+
+        LocalBroadcastManager.getInstance(this).unregisterReceiver(mReceiver);
+        unregisterReceiver(mReceiver);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/FwAppInstallerActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/FwAppInstallerActivity.java
new file mode 100644
index 0000000..2efedd5
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/FwAppInstallerActivity.java
@@ -0,0 +1,351 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, Carsten Pfeiffer, Daniel
+    Dakhno, Daniele Gobbetti, Jos茅 Rebelo, Lem Dulfo, Petr Van臎k, Taavi Eom盲e
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.graphics.Bitmap;
+import android.net.Uri;
+import android.os.Bundle;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.annotation.Nullable;
+import androidx.core.app.NavUtils;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.adapter.ItemWithDetailsAdapter;
+import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.DeviceManager;
+import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.model.GenericItem;
+import nodomain.freeyourgadget.gadgetbridge.model.ItemWithDetails;
+import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+
+public class FwAppInstallerActivity extends AbstractGBActivity implements InstallActivity {
+
+    private static final Logger LOG = LoggerFactory.getLogger(FwAppInstallerActivity.class);
+    private static final String ITEM_DETAILS = "details";
+
+    private TextView fwAppInstallTextView;
+    private ImageView previewImage;
+    private Button installButton;
+    private Uri uri;
+    private GBDevice device;
+    private InstallHandler installHandler;
+    private boolean mayConnect;
+
+    private ProgressBar progressBar;
+    private TextView progressText;
+    private ListView itemListView;
+    private final List<ItemWithDetails> items = new ArrayList<>();
+    private ItemWithDetailsAdapter itemAdapter;
+
+    private ListView detailsListView;
+    private ItemWithDetailsAdapter detailsAdapter;
+    private ArrayList<ItemWithDetails> details = new ArrayList<>();
+
+    private final BroadcastReceiver receiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            if (GBDevice.ACTION_DEVICE_CHANGED.equals(action)) {
+                final GBDevice changedDevice = intent.getParcelableExtra(GBDevice.EXTRA_DEVICE);
+                if (changedDevice != null && changedDevice.equals(device)) {
+                    refreshBusyState(device);
+                    if (!device.isInitialized()) {
+                        setInstallEnabled(false);
+                        if (mayConnect) {
+                            GB.toast(FwAppInstallerActivity.this, getString(R.string.connecting), Toast.LENGTH_SHORT, GB.INFO);
+                            connect();
+                        } else {
+                            setInfoText(getString(R.string.fwappinstaller_connection_state, device.getStateString(context)));
+                        }
+                    } else {
+                        validateInstallation();
+                    }
+                }
+            } else if (GB.ACTION_SET_PROGRESS_BAR.equals(action)) {
+                if (intent.hasExtra(GB.PROGRESS_BAR_INDETERMINATE)) {
+                    setProgressIndeterminate(intent.getBooleanExtra(GB.PROGRESS_BAR_INDETERMINATE, false));
+                }
+
+                if (intent.hasExtra(GB.PROGRESS_BAR_PROGRESS)) {
+                    setProgressIndeterminate(false);
+                    setProgressBar(intent.getIntExtra(GB.PROGRESS_BAR_PROGRESS, 0));
+                }
+            } else if (GB.ACTION_SET_PROGRESS_TEXT.equals(action)) {
+                if (intent.hasExtra(GB.DISPLAY_MESSAGE_MESSAGE)) {
+                    setProgressText(intent.getStringExtra(GB.DISPLAY_MESSAGE_MESSAGE));
+                }
+            } else if (GB.ACTION_SET_INFO_TEXT.equals(action)) {
+                if (intent.hasExtra(GB.DISPLAY_MESSAGE_MESSAGE)) {
+                    setInfoText(intent.getStringExtra(GB.DISPLAY_MESSAGE_MESSAGE));
+                }
+            } else if (GB.ACTION_DISPLAY_MESSAGE.equals(action)) {
+                String message = intent.getStringExtra(GB.DISPLAY_MESSAGE_MESSAGE);
+                int severity = intent.getIntExtra(GB.DISPLAY_MESSAGE_SEVERITY, GB.INFO);
+                addMessage(message, severity);
+            }
+        }
+    };
+
+    private void refreshBusyState(GBDevice dev) {
+        if (dev.isConnecting() || dev.isBusy()) {
+            progressBar.setVisibility(View.VISIBLE);
+        } else {
+            boolean wasBusy = progressBar.getVisibility() != View.GONE;
+            if (wasBusy) {
+                progressBar.setVisibility(View.GONE);
+                // done!
+            }
+        }
+    }
+
+    public void setProgressIndeterminate(boolean indeterminate) {
+        progressBar.setVisibility(View.VISIBLE);
+        progressBar.setIndeterminate(indeterminate);
+    }
+
+    public void setProgressBar(int progress) {
+        progressBar.setProgress(progress);
+    }
+
+    public void setProgressText(String text) {
+        progressText.setVisibility(View.VISIBLE);
+        progressText.setText(text);
+    }
+
+    private void connect() {
+        mayConnect = false; // only do that once per #onCreate
+        GBApplication.deviceService(device).connect();
+    }
+
+    private void validateInstallation() {
+        if (installHandler != null) {
+            installHandler.validateInstallation(this, device);
+        }
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_appinstaller);
+
+        GBDevice dev = getIntent().getParcelableExtra(GBDevice.EXTRA_DEVICE);
+        if (dev != null) {
+            device = dev;
+        }
+        if (savedInstanceState != null) {
+            details = savedInstanceState.getParcelableArrayList(ITEM_DETAILS);
+            if (details == null) {
+                details = new ArrayList<>();
+            }
+        }
+
+        mayConnect = true;
+        itemListView = findViewById(R.id.itemListView);
+        itemAdapter = new ItemWithDetailsAdapter(this, items);
+        itemListView.setAdapter(itemAdapter);
+        fwAppInstallTextView = findViewById(R.id.infoTextView);
+        previewImage = findViewById(R.id.previewImage);
+        installButton = findViewById(R.id.installButton);
+        progressBar = findViewById(R.id.installProgressBar);
+        progressText = findViewById(R.id.installProgressText);
+        detailsListView = findViewById(R.id.detailsListView);
+        detailsAdapter = new ItemWithDetailsAdapter(this, details);
+        detailsAdapter.setSize(ItemWithDetailsAdapter.SIZE_SMALL);
+        detailsListView.setAdapter(detailsAdapter);
+
+        setInstallEnabled(false);
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(GBDevice.ACTION_DEVICE_CHANGED);
+        filter.addAction(GB.ACTION_DISPLAY_MESSAGE);
+        filter.addAction(GB.ACTION_SET_PROGRESS_BAR);
+        filter.addAction(GB.ACTION_SET_PROGRESS_TEXT);
+        filter.addAction(GB.ACTION_SET_INFO_TEXT);
+        LocalBroadcastManager.getInstance(this).registerReceiver(receiver, filter);
+
+        installButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                setInstallEnabled(false);
+                installHandler.onStartInstall(device);
+                GBApplication.deviceService(device).onInstallApp(uri);
+            }
+        });
+
+        uri = getIntent().getData();
+        if (uri == null) { // For "share" intent
+            uri = getIntent().getParcelableExtra(Intent.EXTRA_STREAM);
+        }
+        installHandler = findInstallHandlerFor(uri);
+        if (installHandler == null) {
+            setInfoText(getString(R.string.installer_activity_unable_to_find_handler));
+        } else {
+            setInfoText(getString(R.string.installer_activity_wait_while_determining_status));
+
+            List<GBDevice> selectedDevices = GBApplication.app().getDeviceManager().getSelectedDevices();
+            if(selectedDevices.size() == 0){
+                GB.toast(getString(R.string.open_fw_installer_connect_minimum_one_device), Toast.LENGTH_LONG, GB.ERROR);
+                finish();
+                return;
+            }
+            if(selectedDevices.size() != 1){
+                GB.toast(getString(R.string.open_fw_installer_connect_maximum_one_device), Toast.LENGTH_LONG, GB.ERROR);
+                finish();
+                return;
+            }
+            device = selectedDevices.get(0);
+
+            // needed to get the device
+            if (device == null || !device.isConnected()) {
+                connect();
+            } else {
+                GBApplication.deviceService(device).requestDeviceInfo();
+            }
+        }
+    }
+
+    @Override
+    protected void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        outState.putParcelableArrayList(ITEM_DETAILS, details);
+    }
+
+    private InstallHandler findInstallHandlerFor(Uri uri) {
+        for (DeviceCoordinator coordinator : getAllCoordinatorsConnectedFirst()) {
+            InstallHandler handler = coordinator.findInstallHandler(uri, this);
+            if (handler != null) {
+                LOG.info("Found install handler {} from {}", handler.getClass(), coordinator.getClass());
+                return handler;
+            }
+        }
+        return null;
+    }
+
+    private List<DeviceCoordinator> getAllCoordinatorsConnectedFirst() {
+        DeviceManager deviceManager = ((GBApplication) getApplicationContext()).getDeviceManager();
+        List<DeviceCoordinator> connectedCoordinators = new ArrayList<>();
+        List<DeviceCoordinator> allCoordinators = new ArrayList<>(DeviceType.values().length);
+        for(DeviceType type : DeviceType.values()){
+            allCoordinators.add(type.getDeviceCoordinator());
+        }
+        List<DeviceCoordinator> sortedCoordinators = new ArrayList<>(allCoordinators.size());
+
+        List<GBDevice> devices = deviceManager.getSelectedDevices();
+        for(GBDevice connectedDevice : devices){
+            if (connectedDevice.isConnected()) {
+                DeviceCoordinator coordinator = connectedDevice.getDeviceCoordinator();
+                if (coordinator != null) {
+                    connectedCoordinators.add(coordinator);
+                }
+            }
+        }
+
+
+        sortedCoordinators.addAll(connectedCoordinators);
+        for (DeviceCoordinator coordinator : allCoordinators) {
+            if (!connectedCoordinators.contains(coordinator)) {
+                sortedCoordinators.add(coordinator);
+            }
+        }
+        return sortedCoordinators;
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(final MenuItem item) {
+        final int itemId = item.getItemId();
+        if (itemId == android.R.id.home) {
+            NavUtils.navigateUpFromSameTask(this);
+            return true;
+        }
+        return super.onOptionsItemSelected(item);
+    }
+
+    @Override
+    protected void onDestroy() {
+        LocalBroadcastManager.getInstance(this).unregisterReceiver(receiver);
+        super.onDestroy();
+    }
+
+    @Override
+    public void setInfoText(String text) {
+        fwAppInstallTextView.setText(text);
+    }
+
+    @Override
+    public void setPreview(@Nullable final Bitmap bitmap) {
+        previewImage.setImageBitmap(bitmap);
+        if (previewImage == null) {
+            previewImage.setVisibility(View.GONE);
+        } else {
+            previewImage.setVisibility(View.VISIBLE);
+        }
+    }
+
+    @Override
+    public CharSequence getInfoText() {
+        return fwAppInstallTextView.getText();
+    }
+
+    @Override
+    public void setInstallEnabled(boolean enable) {
+        boolean enabled = device != null && device.isConnected() && enable;
+        installButton.setEnabled(enabled);
+        installButton.setVisibility(enabled ? View.VISIBLE : View.GONE);
+    }
+
+    @Override
+    public void clearInstallItems() {
+        items.clear();
+        itemAdapter.notifyDataSetChanged();
+    }
+
+    @Override
+    public void setInstallItem(ItemWithDetails item) {
+        items.clear();
+        items.add(item);
+        itemAdapter.notifyDataSetChanged();
+    }
+
+    private void addMessage(String message, int severity) {
+        details.add(new GenericItem(message));
+        detailsAdapter.notifyDataSetChanged();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/GBActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/GBActivity.java
new file mode 100644
index 0000000..94b9b02
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/GBActivity.java
@@ -0,0 +1,25 @@
+/*  Copyright (C) 2016-2024 Andreas Shimokawa, Carsten Pfeiffer
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities;
+
+import java.util.Locale;
+
+public interface GBActivity {
+    void setLanguage(Locale language, boolean invalidateLanguage);
+    void setTheme(int themeId);
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/GpxReceiverActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/GpxReceiverActivity.java
new file mode 100644
index 0000000..3a6bc03
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/GpxReceiverActivity.java
@@ -0,0 +1,162 @@
+/*  Copyright (C) 2021-2024 Gordon Williams, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities;
+
+import android.content.ClipData;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.Button;
+import android.widget.TextView;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
+
+public class GpxReceiverActivity extends AbstractGBActivity {
+    private static final Logger LOG = LoggerFactory.getLogger(ActivitySummaryDetail.class);
+    boolean toOverwrite = false;
+    ArrayList<FileToProcess> fileList = new ArrayList<>();
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_gpx_receiver);
+        Button gpx_receiver_ok = findViewById(R.id.gpx_receiver_ok);
+        Button gpx_receiver_cancel = findViewById(R.id.gpx_receiver_cancel);
+        View gpx_receiver_overwrite_label = findViewById(R.id.gpx_receiver_overwrite_label);
+        TextView gpx_receiver_files_listing = findViewById(R.id.gpx_receiver_files_listing);
+        TextView gpx_receiver_received_label = findViewById(R.id.gpx_receiver_received_label);
+
+        gpx_receiver_cancel.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                finish();
+            }
+        });
+
+        gpx_receiver_ok.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                for (FileToProcess fileToProcess : fileList) {
+                    save_file(fileToProcess.source, fileToProcess.destination);
+                }
+                finish();
+            }
+        });
+
+        final Intent intent = getIntent();
+        final ClipData intentClipData = intent.getClipData();
+        StringBuilder fileListingText = new StringBuilder();
+        ArrayList<Uri> documentUris = new ArrayList<>();
+
+        if (savedInstanceState == null) {
+            if (intent.getData() != null) {
+                documentUris.add(intent.getData());
+            } else {
+                if (intentClipData != null && intentClipData.getItemCount() > 0) {
+                    for (int i = 0; i < intentClipData.getItemCount(); i++) {
+                        documentUris.add(intentClipData.getItemAt(i).getUri());
+                    }
+                }
+            }
+        }
+
+        if (documentUris != null) {
+            for (Uri uri : documentUris) {
+                if (uri!=null && uri.getPath().toLowerCase().endsWith(".gpx")) {
+                    FileToProcess file = new FileToProcess(uri);
+                    fileList.add(file);
+                    fileListingText.append(String.format("%s %s\n\n", file.name, file.exists ? getString(R.string.dbmanagementactivity_overwrite) : ""));
+                }
+            }
+        }
+
+        if (toOverwrite) {
+            gpx_receiver_overwrite_label.setVisibility(View.VISIBLE);
+        } else {
+            gpx_receiver_overwrite_label.setVisibility(View.GONE);
+        }
+
+        gpx_receiver_received_label.setText(String.format("%s %s", getString(R.string.gpx_receiver_files_received), fileList.toArray().length));
+        gpx_receiver_files_listing.setText(fileListingText.toString());
+    }
+
+    private String get_file_name(Uri source) {
+        int cut = source.getPath().lastIndexOf("/");
+        String fileName = null;
+        if (cut != -1) {
+            fileName = source.getPath().substring(cut + 1);
+        }
+        return fileName;
+    }
+
+    private File create_file_from_uri(Uri source) {
+        File destination = null;
+        try {
+            File external = FileUtils.getExternalFilesDir();
+            String fileName = get_file_name(source);
+
+            if (fileName != null) {
+                destination = new File(external + "/" + fileName);
+            }
+        } catch (IOException exception) {
+            LOG.error("Error creating file", exception);
+        }
+        return destination;
+    }
+
+    private void save_file(Uri source, File destination) {
+        try {
+            String fileName = get_file_name(source);
+            if (fileName != null) {
+                FileUtils.copyURItoFile(GpxReceiverActivity.this, source, destination);
+            }
+
+        } catch (IOException exception) {
+            LOG.error("Error copying file", exception);
+        }
+    }
+
+    private class FileToProcess {
+        String name;
+        Uri source;
+        File destination;
+        boolean exists;
+
+        FileToProcess(Uri file) {
+            this.name = get_file_name(file);
+            this.source = file;
+            this.destination = create_file_from_uri(file);
+            this.exists = this.destination.exists();
+
+            if (this.exists) {
+                toOverwrite = true;
+            }
+        }
+    }
+}
+
+
+
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/HeartRateDialog.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/HeartRateDialog.java
new file mode 100644
index 0000000..56380df
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/HeartRateDialog.java
@@ -0,0 +1,153 @@
+/*  Copyright (C) 2021-2024 Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities;
+
+import android.app.Dialog;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.Serializable;
+import java.util.Objects;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
+import nodomain.freeyourgadget.gadgetbridge.model.HeartRateSample;
+
+public class HeartRateDialog extends Dialog {
+    private final GBDevice device;
+
+    protected static final Logger LOG = LoggerFactory.getLogger(HeartRateDialog.class);
+    LinearLayout heart_rate_dialog_results_layout;
+    RelativeLayout heart_rate_dialog_loading_layout;
+
+    TextView heart_rate_widget_hr_value;
+    TextView heart_rate_widget_spo2_value;
+    TextView heart_rate_widget_pressure_value;
+
+    LinearLayout heart_rate_hr;
+    LinearLayout heart_rate_spo2;
+    LinearLayout heart_rate_pressure;
+
+    TextView heart_rate_dialog_label;
+
+    final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            switch (Objects.requireNonNull(intent.getAction())) {
+                case DeviceService.ACTION_REALTIME_SAMPLES:
+                    final GBDevice incomingDevice = intent.getParcelableExtra(GBDevice.EXTRA_DEVICE);
+                    if (device.equals(incomingDevice)) {
+                        setMeasurementResults(intent.getSerializableExtra(DeviceService.EXTRA_REALTIME_SAMPLE));
+                    }
+                    break;
+                default:
+                    LOG.info("ignoring intent action {}", intent.getAction());
+                    break;
+            }
+        }
+    };
+
+    public HeartRateDialog(final GBDevice device, @NonNull Context context) {
+        super(context);
+        this.device = device;
+    }
+
+    private void setMeasurementResults(Serializable result) {
+        heart_rate_dialog_results_layout.setVisibility(View.VISIBLE);
+        heart_rate_dialog_loading_layout.setVisibility(View.GONE);
+        heart_rate_dialog_label.setText(getContext().getString(R.string.heart_rate_result));
+
+        int heartRate = 0;
+        if (result instanceof ActivitySample) {
+            ActivitySample sample = (ActivitySample) result;
+            heartRate = sample.getHeartRate();
+        }
+        if (result instanceof HeartRateSample) {
+            HeartRateSample sample = (HeartRateSample) result;
+            heartRate = sample.getHeartRate();
+        }
+        if (HeartRateUtils.getInstance().isValidHeartRateValue(heartRate)) {
+            heart_rate_hr.setVisibility(View.VISIBLE);
+            heart_rate_widget_hr_value.setText(String.valueOf(heartRate));
+        }
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(DeviceService.ACTION_REALTIME_SAMPLES);
+        LocalBroadcastManager.getInstance(getContext()).registerReceiver(mReceiver, filter);
+
+        setContentView(R.layout.heart_rate_dialog);
+        heart_rate_dialog_results_layout = findViewById(R.id.heart_rate_dialog_results_layout);
+        heart_rate_dialog_loading_layout = findViewById(R.id.heart_rate_dialog_loading_layout);
+
+        heart_rate_hr = findViewById(R.id.heart_rate_measurements1);
+        heart_rate_spo2 = findViewById(R.id.heart_rate_measurements2);
+        heart_rate_pressure = findViewById(R.id.heart_rate_measurements3);
+
+        TextView heart_rate_widget_hr_title = heart_rate_hr.findViewById(R.id.generic_widget_title);
+        TextView heart_rate_widget_spo2_title = heart_rate_spo2.findViewById(R.id.generic_widget_title);
+        TextView heart_rate_widget_pressure_title = heart_rate_pressure.findViewById(R.id.generic_widget_title);
+
+        heart_rate_widget_hr_value = heart_rate_hr.findViewById(R.id.generic_widget_value);
+        heart_rate_widget_spo2_value = heart_rate_spo2.findViewById(R.id.generic_widget_value);
+        heart_rate_widget_pressure_value = heart_rate_pressure.findViewById(R.id.generic_widget_value);
+
+        ImageView heart_rate_widget_hr_icon = heart_rate_hr.findViewById(R.id.generic_widget_icon);
+        ImageView heart_rate_widget_spo2_icon = heart_rate_spo2.findViewById(R.id.generic_widget_icon);
+        ImageView heart_rate_widget_pressure_icon = heart_rate_pressure.findViewById(R.id.generic_widget_icon);
+
+        heart_rate_widget_hr_icon.setImageResource(R.drawable.ic_heart);
+        heart_rate_widget_spo2_icon.setImageResource(R.drawable.ic_circle);
+        heart_rate_widget_pressure_icon.setImageResource(R.drawable.ic_heartrate);
+
+        heart_rate_hr.setVisibility(View.VISIBLE);
+        heart_rate_spo2.setVisibility(View.GONE);
+        heart_rate_pressure.setVisibility(View.GONE);
+
+        heart_rate_widget_hr_title.setText(R.string.heart_rate);
+        heart_rate_widget_spo2_title.setText(R.string.menuitem_spo2);
+        heart_rate_widget_pressure_title.setText(R.string.blood_pressure);
+
+        heart_rate_dialog_label = findViewById(R.id.heart_rate_dialog_title);
+        heart_rate_dialog_results_layout.setVisibility(View.GONE);
+        heart_rate_dialog_loading_layout.setVisibility(View.VISIBLE);
+
+        setOnCancelListener(dialogInterface -> {
+            LocalBroadcastManager.getInstance(getContext()).unregisterReceiver(mReceiver);
+        });
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/HeartRateUtils.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/HeartRateUtils.java
new file mode 100644
index 0000000..fec5418
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/HeartRateUtils.java
@@ -0,0 +1,69 @@
+/*  Copyright (C) 2016-2024 Andreas Shimokawa, Carsten Pfeiffer, Dikay900
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.util.GBPrefs;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+public class HeartRateUtils {
+    public static final int MAX_HEART_RATE_VALUE = 250;
+    public static final int MIN_HEART_RATE_VALUE = 10;
+
+    /**
+     * The maxiumum gap between two hr measurements in which
+     * we interpolate between the measurements. Otherwise, two
+     * distinct measurements will be shown.
+     *
+     * Value is in minutes
+     */
+    public static final int MAX_HR_MEASUREMENTS_GAP_MINUTES = 10;
+
+    private int maxHeartRateValue;
+    private int minHeartRateValue;
+
+    private static final HeartRateUtils instance = new HeartRateUtils();
+
+    public static HeartRateUtils getInstance() {
+        return instance;
+    }
+
+    /**
+     * Singleton - to access this class use the static #getInstance()
+     */
+    private HeartRateUtils() {
+        updateCachedHeartRatePreferences();
+    }
+
+    public void updateCachedHeartRatePreferences(){
+        Prefs prefs = GBApplication.getPrefs();
+        maxHeartRateValue = prefs.getInt(GBPrefs.CHART_MAX_HEART_RATE, MAX_HEART_RATE_VALUE);
+        minHeartRateValue = prefs.getInt(GBPrefs.CHART_MIN_HEART_RATE, MIN_HEART_RATE_VALUE);
+    }
+
+    public int getMaxHeartRate(){
+        return maxHeartRateValue;
+    }
+
+    public int getMinHeartRate(){
+        return minHeartRateValue;
+    }
+
+    public boolean isValidHeartRateValue(int value) {
+        return value > 0 && value >= getMinHeartRate() && value <= getMaxHeartRate();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/InstallActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/InstallActivity.java
new file mode 100644
index 0000000..162b064
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/InstallActivity.java
@@ -0,0 +1,38 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, Carsten Pfeiffer, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities;
+
+import android.graphics.Bitmap;
+
+import androidx.annotation.Nullable;
+
+import nodomain.freeyourgadget.gadgetbridge.model.ItemWithDetails;
+
+public interface InstallActivity {
+    CharSequence getInfoText();
+
+    void setInfoText(String text);
+
+    void setPreview(@Nullable Bitmap bitmap);
+
+    void setInstallEnabled(boolean enable);
+
+    void clearInstallItems();
+
+    void setInstallItem(ItemWithDetails item);
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/NotificationFilterActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/NotificationFilterActivity.java
new file mode 100644
index 0000000..ab6e3ff
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/NotificationFilterActivity.java
@@ -0,0 +1,233 @@
+/*  Copyright (C) 2018-2024 abettenburg, AndrewBedscastle, Carsten Pfeiffer,
+    Daniele Gobbetti
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities;
+
+import android.os.Bundle;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.Spinner;
+import android.widget.Toast;
+
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import de.greenrobot.dao.query.Query;
+import nodomain.freeyourgadget.gadgetbridge.BuildConfig;
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.adapter.AppBlacklistAdapter;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.entities.NotificationFilter;
+import nodomain.freeyourgadget.gadgetbridge.entities.NotificationFilterDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.NotificationFilterEntry;
+import nodomain.freeyourgadget.gadgetbridge.entities.NotificationFilterEntryDao;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class NotificationFilterActivity extends AbstractGBActivity {
+
+    public static final int NOTIFICATION_FILTER_MODE_NONE = 0;
+    public static final int NOTIFICATION_FILTER_MODE_WHITELIST = 1;
+    public static final int NOTIFICATION_FILTER_MODE_BLACKLIST = 2;
+    public static final int NOTIFICATION_FILTER_SUBMODE_ANY = 0;
+    public static final int NOTIFICATION_FILTER_SUBMODE_ALL = 1;
+
+    private Button mButtonSave;
+    private Spinner mSpinnerFilterMode;
+    private Spinner mSpinnerFilterSubMode;
+    private NotificationFilter mNotificationFilter;
+    private EditText mEditTextWords;
+    private List<String> mWordsList = new ArrayList<>();
+    private List<Long> mFilterEntryIds = new ArrayList<>();
+
+    private static final Logger LOG = LoggerFactory.getLogger(NotificationFilterActivity.class);
+
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_notification_filter);
+
+        String packageName = getIntent().getStringExtra(AppBlacklistAdapter.STRING_EXTRA_PACKAGE_NAME);
+
+        if (StringUtils.isBlank(packageName)) {
+            this.finish();
+        }
+
+        packageName = packageName.toLowerCase();
+
+        try (DBHandler db = GBApplication.acquireDB()) {
+
+            NotificationFilterDao notificationFilterDao = db.getDaoSession().getNotificationFilterDao();
+            NotificationFilterEntryDao notificationFilterEntryDao = db.getDaoSession().getNotificationFilterEntryDao();
+
+            Query<NotificationFilter> query = notificationFilterDao.queryBuilder().where(NotificationFilterDao.Properties.AppIdentifier.eq(packageName)).build();
+            mNotificationFilter = query.unique();
+
+            if (mNotificationFilter == null) {
+                mNotificationFilter = new NotificationFilter();
+                mNotificationFilter.setAppIdentifier(packageName);
+                LOG.debug("New Notification Filter");
+            } else {
+                LOG.debug("Loaded existing notification filter");
+                Query<NotificationFilterEntry> queryEntries = notificationFilterEntryDao.queryBuilder().where(NotificationFilterEntryDao.Properties.NotificationFilterId.eq(mNotificationFilter.getId())).build();
+                List<NotificationFilterEntry> filterEntries = queryEntries.list();
+                for (NotificationFilterEntry temp : filterEntries) {
+                    mWordsList.add(temp.getNotificationFilterContent());
+                    mFilterEntryIds.add(temp.getId());
+                    LOG.debug("Loaded filter word: " + temp.getNotificationFilterContent());
+                }
+            }
+
+            setupView(db);
+
+        } catch (Exception e) {
+            GB.toast(this, "Error accessing the database: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
+            this.finish();
+        }
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+    }
+
+    private void setupView(DBHandler db) {
+
+        mSpinnerFilterMode = findViewById(R.id.spinnerFilterMode);
+        mSpinnerFilterMode.setSelection(mNotificationFilter.getNotificationFilterMode());
+
+        mSpinnerFilterSubMode = findViewById(R.id.spinnerSubMode);
+        mSpinnerFilterMode.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
+            @Override
+            public void onItemSelected(AdapterView<?> adapterView, View view, int pos, long id) {
+                switch (pos) {
+                    case NOTIFICATION_FILTER_MODE_NONE:
+                        mEditTextWords.setEnabled(false);
+                        mSpinnerFilterSubMode.setEnabled(false);
+                        break;
+                    case NOTIFICATION_FILTER_MODE_BLACKLIST:
+                    case NOTIFICATION_FILTER_MODE_WHITELIST:
+                        mEditTextWords.setEnabled(true);
+                        mSpinnerFilterSubMode.setEnabled(true);
+                        break;
+
+                }
+            }
+
+            @Override
+            public void onNothingSelected(AdapterView<?> adapterView) {
+
+            }
+        });
+
+        mSpinnerFilterSubMode.setSelection(mNotificationFilter.getNotificationFilterSubMode());
+
+        mEditTextWords = findViewById(R.id.editTextWords);
+
+        if (!mWordsList.isEmpty()) {
+            StringBuilder builder = new StringBuilder();
+            for (String temp : mWordsList) {
+                builder.append(temp);
+                builder.append("\n");
+            }
+            mEditTextWords.setText(builder.toString());
+        }
+
+        mEditTextWords.setEnabled(mSpinnerFilterMode.getSelectedItemPosition() == NOTIFICATION_FILTER_MODE_NONE);
+
+        mButtonSave = findViewById(R.id.buttonSaveFilter);
+
+        mButtonSave.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                saveFilter();
+            }
+        });
+    }
+
+    private void saveFilter() {
+        // TODO: check for modifications, only save if something changed
+        String words = mEditTextWords.getText().toString();
+
+        if (StringUtils.isBlank(words) && mSpinnerFilterMode.getSelectedItemPosition() != NOTIFICATION_FILTER_MODE_NONE) {
+            Toast.makeText(NotificationFilterActivity.this, R.string.toast_notification_filter_words_empty_hint, Toast.LENGTH_SHORT).show();
+            return;
+        }
+
+        try (DBHandler db = GBApplication.acquireDB()) {
+            NotificationFilterDao notificationFilterDao = db.getDaoSession().getNotificationFilterDao();
+            NotificationFilterEntryDao notificationFilterEntryDao = db.getDaoSession().getNotificationFilterEntryDao();
+
+            debugOutput(notificationFilterDao);
+
+            mNotificationFilter.setNotificationFilterMode(mSpinnerFilterMode.getSelectedItemPosition());
+            mNotificationFilter.setNotificationFilterSubMode(mSpinnerFilterSubMode.getSelectedItemPosition());
+
+            notificationFilterEntryDao.deleteByKeyInTx(mFilterEntryIds);
+
+           long filterId = notificationFilterDao.insertOrReplace(mNotificationFilter);
+
+            // only save words if filter mode != none
+            if (mNotificationFilter.getNotificationFilterMode() != NOTIFICATION_FILTER_MODE_NONE) {
+                String[] wordsSplitted = words.split("\n");
+                for (String temp : wordsSplitted) {
+
+                    if (StringUtils.isBlank(temp)) {
+                        continue;
+                    }
+
+                    temp = temp.trim();
+                    NotificationFilterEntry notificationFilterEntry = new NotificationFilterEntry();
+                    notificationFilterEntry.setNotificationFilterContent(temp);
+                    notificationFilterEntry.setNotificationFilterId(filterId);
+                    notificationFilterEntryDao.insert(notificationFilterEntry);
+                }
+            }
+
+            Toast.makeText(NotificationFilterActivity.this, R.string.toast_notification_filter_saved_successfully, Toast.LENGTH_SHORT).show();
+            NotificationFilterActivity.this.finish();
+
+        } catch (Exception e) {
+            GB.toast(NotificationFilterActivity.this, "Error accessing the database: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
+        }
+    }
+
+    /**
+     * Only used for debugging purposes
+     *
+     * @param notificationFilterDao {@link NotificationFilterDao}
+     */
+    private void debugOutput(NotificationFilterDao notificationFilterDao) {
+        if (BuildConfig.DEBUG) {
+
+            List<NotificationFilter> filters = notificationFilterDao.loadAll();
+
+            LOG.info("Saved filters");
+
+            for (NotificationFilter temp : filters) {
+                LOG.info("Filter: " + temp.getId() + " " + temp.getAppIdentifier());
+            }
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/NotificationManagementActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/NotificationManagementActivity.java
new file mode 100644
index 0000000..fd04ebd
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/NotificationManagementActivity.java
@@ -0,0 +1,149 @@
+/*  Copyright (C) 2021-2024 Jos茅 Rebelo, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities;
+
+import android.content.Intent;
+import android.media.RingtoneManager;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.Settings;
+
+import androidx.preference.Preference;
+import androidx.preference.PreferenceCategory;
+import androidx.preference.PreferenceFragmentCompat;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.util.GBPrefs;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+public class NotificationManagementActivity extends AbstractSettingsActivityV2 {
+    private static final int RINGTONE_REQUEST_CODE = 4712;
+    private static final String DEFAULT_RINGTONE_URI = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE).toString();
+
+    @Override
+    protected String fragmentTag() {
+        return NotificationPreferencesFragment.FRAGMENT_TAG;
+    }
+
+    @Override
+    protected PreferenceFragmentCompat newFragment() {
+        return new NotificationPreferencesFragment();
+    }
+
+    public static class NotificationPreferencesFragment extends AbstractPreferenceFragment {
+        private static final Logger LOG = LoggerFactory.getLogger(NotificationPreferencesFragment.class);
+
+        static final String FRAGMENT_TAG = "NOTIFICATION_PREFERENCES_FRAGMENT";
+
+        @Override
+        protected void onSharedPreferenceChanged(final Preference preference) {
+            if (GBPrefs.PING_TONE.equals(preference.getKey())) {
+                try {
+                    final Prefs prefs = GBApplication.getPrefs();
+                    // This fails on some ROMs. The actual implementation falls-back to an internal ping tone
+                    preference.setSummary(RingtoneManager.getRingtone(requireContext(), Uri.parse(prefs.getString(GBPrefs.PING_TONE, DEFAULT_RINGTONE_URI))).getTitle(requireContext()));
+                } catch (final Exception e) {
+                    LOG.error("Failed to find the configured ping ringtone", e);
+                    preference.setSummary("-");
+                }
+            }
+        }
+
+        @Override
+        public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) {
+            setPreferencesFromResource(R.xml.notifications_preferences, rootKey);
+
+            Preference pref = findPreference("notifications_generic");
+            pref.setOnPreferenceClickListener(preference -> {
+                final Intent enableIntent = new Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS");
+                startActivity(enableIntent);
+                return true;
+            });
+
+            pref = findPreference(GBPrefs.PING_TONE);
+            pref.setOnPreferenceClickListener(preference -> {
+                final Intent intent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER);
+                intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TITLE, "Set Ping tone");
+                intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, false);
+                intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true);
+                intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_ALL);
+                startActivityForResult(intent, RINGTONE_REQUEST_CODE);
+                return true;
+            });
+
+            pref = findPreference("pref_key_blacklist");
+            pref.setOnPreferenceClickListener(preference -> {
+                final Intent enableIntent = new Intent(requireContext(), AppBlacklistActivity.class);
+                startActivity(enableIntent);
+                return true;
+            });
+
+            pref = findPreference("notifications_settings");
+            pref.setOnPreferenceClickListener(preference -> {
+                if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.O) {
+                    LOG.warn("This preference should not be displayed in Android < O");
+                    return true;
+                }
+
+                final Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS);
+                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                intent.putExtra(Settings.EXTRA_APP_PACKAGE, requireActivity().getPackageName());
+                //This could open notification channel settings, if needed...:
+                //Intent intent = new Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS);
+                //intent.putExtra(Settings.EXTRA_CHANNEL_ID, GB.NOTIFICATION_CHANNEL_ID_TRANSFER);
+                startActivity(intent);
+                return true;
+            });
+
+            final PreferenceCategory notificationsCategory = findPreference("pref_key_notifications");
+
+            if (!GBApplication.isRunningMarshmallowOrLater()) {
+                pref = findPreference("notification_filter");
+                notificationsCategory.removePreference(pref);
+            }
+
+            if (GBApplication.isRunningTenOrLater()) {
+                pref = findPreference("minimize_priority");
+                notificationsCategory.removePreference(pref);
+            }
+
+            if (!GBApplication.isRunningOreoOrLater()) {
+                pref = findPreference("notifications_settings");
+                notificationsCategory.removePreference(pref);
+            }
+        }
+
+        // TODO: Migrate this to ActivityResultContract
+        @Override
+        public void onActivityResult(final int requestCode, final int resultCode, final Intent intent) {
+            if (requestCode == RINGTONE_REQUEST_CODE && intent != null) {
+                if (intent.getExtras().getParcelable(RingtoneManager.EXTRA_RINGTONE_PICKED_URI) != null) {
+                    final Uri uri = intent.getExtras().getParcelable(RingtoneManager.EXTRA_RINGTONE_PICKED_URI);
+                    GBApplication.getPrefs()
+                            .getPreferences()
+                            .edit()
+                            .putString(GBPrefs.PING_TONE, uri.toString())
+                            .apply();
+                }
+            }
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/OpenFwAppInstallerActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/OpenFwAppInstallerActivity.java
new file mode 100644
index 0000000..6dda0ca
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/OpenFwAppInstallerActivity.java
@@ -0,0 +1,93 @@
+/*  Copyright (C) 2022-2024 Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.Button;
+import android.widget.TextView;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.appmanager.AppManagerActivity;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class OpenFwAppInstallerActivity extends AbstractGBActivity {
+    private static final Logger LOG = LoggerFactory.getLogger(AppManagerActivity.class);
+    private int READ_REQUEST_CODE = 42;
+    private GBDevice device;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        Bundle extras = getIntent().getExtras();
+        if (extras != null) {
+            device = extras.getParcelable(GBDevice.EXTRA_DEVICE);
+        } else {
+            throw new IllegalArgumentException("Must provide a device when invoking this activity");
+        }
+
+        setContentView(R.layout.activity_open_fw_app_installer);
+        Button pickFileButton = findViewById(R.id.open_fw_installer_pick_button);
+        TextView label = findViewById(R.id.open_fw_installer_no_device);
+        label.setText(String.format(getString(R.string.open_fw_installer_select_file), device.getAliasOrName()));
+
+        final List<GBDevice> devices = ((GBApplication) getApplicationContext()).getDeviceManager().getSelectedDevices();
+        switch (devices.size()) {
+            case 0:
+                label.setText(R.string.open_fw_installer_connect_minimum_one_device);
+                pickFileButton.setEnabled(false);
+                break;
+            case 1:
+                label.setText(String.format(getString(R.string.open_fw_installer_select_file), device.getAliasOrName()));
+                pickFileButton.setEnabled(true);
+                break;
+            default:
+                label.setText(R.string.open_fw_installer_connect_maximum_one_device);
+                pickFileButton.setEnabled(false);
+        }
+
+        pickFileButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
+                intent.addCategory(Intent.CATEGORY_OPENABLE);
+                intent.setType("*/*");
+                startActivityForResult(intent, READ_REQUEST_CODE);
+            }
+        });
+    }
+
+    @Override
+    public void onActivityResult(int requestCode, int resultCode, Intent resultData) {
+        super.onActivityResult(requestCode, resultCode, resultData);
+        if (requestCode == READ_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
+            Intent startIntent = new Intent(OpenFwAppInstallerActivity.this, FwAppInstallerActivity.class);
+            startIntent.putExtra(GBDevice.EXTRA_DEVICE, device);
+            startIntent.setAction(Intent.ACTION_VIEW);
+            startIntent.setDataAndType(resultData.getData(), null);
+            startActivity(startIntent);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/PermissionsActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/PermissionsActivity.java
new file mode 100644
index 0000000..22713c3
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/PermissionsActivity.java
@@ -0,0 +1,38 @@
+/*  Copyright (C) 2024 Arjan Schrijver
+
+    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 <http://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities;
+
+import android.os.Bundle;
+
+import androidx.fragment.app.FragmentManager;
+import androidx.fragment.app.FragmentTransaction;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.welcome.WelcomeFragmentPermissions;
+
+public class PermissionsActivity extends AbstractGBActivity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_permissions);
+
+        WelcomeFragmentPermissions permissionsFragment = new WelcomeFragmentPermissions();
+        FragmentManager fragmentManager = getSupportFragmentManager();
+        FragmentTransaction transaction = fragmentManager.beginTransaction();
+        transaction.replace(R.id.fragment_container, permissionsFragment).commit();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ReminderDetails.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ReminderDetails.java
new file mode 100644
index 0000000..aec7cea
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ReminderDetails.java
@@ -0,0 +1,232 @@
+/*  Copyright (C) 2021-2024 Arjan Schrijver, Daniel Dakhno, Jos茅 Rebelo,
+    Johannes Krude
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities;
+
+import android.app.DatePickerDialog;
+import android.app.TimePickerDialog;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.text.Editable;
+import android.text.InputFilter;
+import android.text.TextWatcher;
+import android.text.format.DateFormat;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.ArrayAdapter;
+import android.widget.DatePicker;
+import android.widget.EditText;
+import android.widget.TextView;
+import android.widget.TimePicker;
+import android.widget.Toast;
+
+import androidx.annotation.NonNull;
+
+import com.google.android.material.dialog.MaterialAlertDialogBuilder;
+import com.google.android.material.floatingactionbutton.FloatingActionButton;
+
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.TimeZone;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.Locale;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.entities.Reminder;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class ReminderDetails extends AbstractGBActivity implements TimePickerDialog.OnTimeSetListener, DatePickerDialog.OnDateSetListener {
+    private Reminder reminder;
+    private GBDevice device;
+
+    ArrayAdapter<String> repeatAdapter;
+
+    SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault());
+    SimpleDateFormat timeFormat = new SimpleDateFormat("HH:mm", Locale.getDefault());
+
+    TextView reminderRepeat;
+    TextView reminderDate;
+    TextView reminderTime;
+    EditText reminderText;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_reminder_details);
+
+        reminder = (Reminder) getIntent().getSerializableExtra(Reminder.EXTRA_REMINDER);
+
+        if (reminder == null) {
+            GB.toast("No reminder provided to ReminderDetails Activity", Toast.LENGTH_LONG, GB.ERROR);
+            finish();
+            return;
+        }
+
+        reminderRepeat = findViewById(R.id.reminder_repeat);
+        reminderDate = findViewById(R.id.reminder_date);
+        reminderTime = findViewById(R.id.reminder_time);
+        reminderText = findViewById(R.id.reminder_message);
+
+        device = getIntent().getParcelableExtra(GBDevice.EXTRA_DEVICE);
+        final DeviceCoordinator coordinator = device.getDeviceCoordinator();
+
+        final String[] repeatStrings = getResources().getStringArray(R.array.reminder_repeat);
+        repeatAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, repeatStrings);
+
+        final View cardRepeat = findViewById(R.id.card_repeat);
+        cardRepeat.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                new MaterialAlertDialogBuilder(ReminderDetails.this).setAdapter(repeatAdapter, new DialogInterface.OnClickListener() {
+                    @Override
+                    public void onClick(DialogInterface dialogInterface, int i) {
+                        reminder.setRepetition(i);
+                        updateUiFromReminder();
+                    }
+                }).create().show();
+            }
+        });
+
+        final View cardDate = findViewById(R.id.card_date);
+        cardDate.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                final Calendar date = new GregorianCalendar();
+                date.setTime(reminder.getDate());
+                new DatePickerDialog(
+                        ReminderDetails.this,
+                        ReminderDetails.this,
+                        date.get(Calendar.YEAR),
+                        date.get(Calendar.MONTH),
+                        date.get(Calendar.DAY_OF_MONTH)
+                ).show();
+            }
+        });
+
+        final View cardTime = findViewById(R.id.card_time);
+        if (coordinator.getRemindersHaveTime()) {
+            cardTime.setOnClickListener(new View.OnClickListener() {
+                @Override
+                public void onClick(View view) {
+                    new TimePickerDialog(
+                            ReminderDetails.this,
+                            ReminderDetails.this,
+                            reminder.getDate().getHours(),
+                            reminder.getDate().getMinutes(),
+                            DateFormat.is24HourFormat(GBApplication.getContext())
+                    ).show();
+                }
+            });
+        } else {
+            cardTime.setVisibility(View.GONE);
+        }
+
+        reminderText.setFilters(new InputFilter[]{new InputFilter.LengthFilter(coordinator.getMaximumReminderMessageLength())});
+        reminderText.addTextChangedListener(new TextWatcher() {
+            @Override
+            public void beforeTextChanged(final CharSequence s, int start, int count, int after) {
+            }
+
+            @Override
+            public void onTextChanged(final CharSequence s, int start, int before, int count) {
+            }
+
+            @Override
+            public void afterTextChanged(final Editable s) {
+                reminder.setMessage(s.toString());
+            }
+        });
+
+        final FloatingActionButton fab = findViewById(R.id.fab_save);
+        fab.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                updateReminder();
+                ReminderDetails.this.setResult(1);
+                finish();
+            }
+        });
+
+        updateUiFromReminder();
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(final MenuItem item) {
+        final int itemId = item.getItemId();
+        if (itemId == android.R.id.home) {
+            // back button
+            // TODO confirm when exiting without saving
+            finish();
+            return true;
+        }
+        return super.onOptionsItemSelected(item);
+    }
+
+    private void updateReminder() {
+        DBHelper.store(reminder);
+    }
+
+    @Override
+    protected void onSaveInstanceState(@NonNull final Bundle state) {
+        super.onSaveInstanceState(state);
+        state.putSerializable("reminder", reminder);
+    }
+
+    @Override
+    protected void onRestoreInstanceState(@NonNull final Bundle savedInstanceState) {
+        super.onRestoreInstanceState(savedInstanceState);
+        reminder = (Reminder) savedInstanceState.getSerializable("reminder");
+        updateUiFromReminder();
+    }
+
+    @Override
+    public void onTimeSet(TimePicker timePicker, int hour, int minute) {
+        reminder.getDate().setHours(hour);
+        reminder.getDate().setMinutes(minute);
+        updateUiFromReminder();
+    }
+
+    @Override
+    public void onDateSet(DatePicker datePicker, int year, int month, int dayOfMonth) {
+        final DeviceCoordinator coordinator = device.getDeviceCoordinator();
+
+        if (coordinator.getRemindersHaveTime()) {
+            final Calendar date = new GregorianCalendar(year, month, dayOfMonth);
+            reminder.setDate(new Date(date.getTimeInMillis()));
+        } else {
+            Calendar noonUTC = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
+            noonUTC.set(year, month, dayOfMonth, 12, 0);
+            reminder.setDate(noonUTC.getTime());
+        }
+        updateUiFromReminder();
+    }
+
+    public void updateUiFromReminder() {
+        reminderRepeat.setText(repeatAdapter.getItem(reminder.getRepetition()));
+        reminderText.setText(reminder.getMessage());
+
+        if (reminder.getDate() != null) {
+            reminderDate.setText(dateFormat.format(reminder.getDate()));
+            reminderTime.setText(timeFormat.format(reminder.getDate()));
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/SettingsActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/SettingsActivity.java
new file mode 100644
index 0000000..4483dc1
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/SettingsActivity.java
@@ -0,0 +1,588 @@
+/*  Copyright (C) 2015-2024 0nse, Andreas Shimokawa, Anemograph, Arjan
+    Schrijver, Carsten Pfeiffer, Daniel Dakhno, Daniele Gobbetti, Felix Konstantin
+    Maurer, Jos茅 Rebelo, Martin, Normano64, Pavel Elagin, Petr Van臎k, Sebastian
+    Kranz, Taavi Eom盲e
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities;
+
+import android.Manifest;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.database.Cursor;
+import android.location.Criteria;
+import android.location.Location;
+import android.location.LocationListener;
+import android.location.LocationManager;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.DocumentsContract;
+import android.text.InputType;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+import android.widget.Spinner;
+import android.widget.Toast;
+
+import androidx.core.app.ActivityCompat;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+import androidx.preference.ListPreference;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceFragmentCompat;
+
+import com.google.android.material.color.DynamicColors;
+import com.google.android.material.dialog.MaterialAlertDialogBuilder;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Set;
+
+import nodomain.freeyourgadget.gadgetbridge.BuildConfig;
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.charts.ChartsPreferencesActivity;
+import nodomain.freeyourgadget.gadgetbridge.activities.discovery.DiscoveryPairingPreferenceActivity;
+import nodomain.freeyourgadget.gadgetbridge.database.PeriodicExporter;
+import nodomain.freeyourgadget.gadgetbridge.externalevents.TimeChangeReceiver;
+import nodomain.freeyourgadget.gadgetbridge.model.Weather;
+import nodomain.freeyourgadget.gadgetbridge.util.AndroidUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+import nodomain.freeyourgadget.gadgetbridge.util.GBPrefs;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+public class SettingsActivity extends AbstractSettingsActivityV2 {
+    public static final String PREF_MEASUREMENT_SYSTEM = "measurement_system";
+
+    @Override
+    protected String fragmentTag() {
+        return SettingsFragment.FRAGMENT_TAG;
+    }
+
+    @Override
+    protected PreferenceFragmentCompat newFragment() {
+        return new SettingsFragment();
+    }
+
+    public static class SettingsFragment extends AbstractPreferenceFragment {
+        private static final Logger LOG = LoggerFactory.getLogger(SettingsActivity.class);
+
+        static final String FRAGMENT_TAG = "SETTINGS_FRAGMENT";
+
+        private static final int EXPORT_LOCATION_FILE_REQUEST_CODE = 4711;
+        private EditText fitnessAppEditText = null;
+        private int fitnessAppSelectionListSpinnerFirstRun = 0;
+
+        @Override
+        public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) {
+            setPreferencesFromResource(R.xml.preferences, rootKey);
+
+            setInputTypeFor("rtl_max_line_length", InputType.TYPE_CLASS_NUMBER);
+            setInputTypeFor("location_latitude", InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_SIGNED);
+            setInputTypeFor("location_longitude", InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_SIGNED);
+            setInputTypeFor("auto_export_interval", InputType.TYPE_CLASS_NUMBER);
+            setInputTypeFor("auto_fetch_interval_limit", InputType.TYPE_CLASS_NUMBER);
+
+            Prefs prefs = GBApplication.getPrefs();
+            Preference pref = findPreference("pref_category_activity_personal");
+            if (pref != null) {
+                pref.setOnPreferenceClickListener(preference -> {
+                    Intent enableIntent = new Intent(requireContext(), AboutUserPreferencesActivity.class);
+                    startActivity(enableIntent);
+                    return true;
+                });
+            }
+
+            pref = findPreference("pref_charts");
+            if (pref != null) {
+                pref.setOnPreferenceClickListener(preference -> {
+                    Intent enableIntent = new Intent(requireContext(), ChartsPreferencesActivity.class);
+                    startActivity(enableIntent);
+                    return true;
+                });
+            }
+
+            pref = findPreference("datetime_synconconnect");
+            if (pref != null) {
+                pref.setOnPreferenceChangeListener((preference, newVal) -> {
+                    if (Boolean.TRUE.equals(newVal)) {
+                        TimeChangeReceiver.scheduleNextDstChangeOrPeriodicSync(requireContext());
+                        GBApplication.deviceService().onSetTime();
+                    }
+                    return true;
+                });
+            }
+
+            pref = findPreference("log_to_file");
+            if (pref != null) {
+                pref.setOnPreferenceChangeListener((preference, newVal) -> {
+                    boolean doEnable = Boolean.TRUE.equals(newVal);
+                    try {
+                        if (doEnable) {
+                            FileUtils.getExternalFilesDir(); // ensures that it is created
+                        }
+                        GBApplication.setupLogging(doEnable);
+                    } catch (IOException ex) {
+                        GB.toast(requireContext().getApplicationContext(),
+                                getString(R.string.error_creating_directory_for_logfiles, ex.getLocalizedMessage()),
+                                Toast.LENGTH_LONG,
+                                GB.ERROR,
+                                ex);
+                    }
+                    return true;
+                });
+
+                // If we didn't manage to initialize file logging, disable the preference
+                if (!GBApplication.getLogging().isFileLoggerInitialized()) {
+                    pref.setEnabled(false);
+                    pref.setSummary(R.string.pref_write_logfiles_not_available);
+                }
+            }
+
+            pref = findPreference("cache_weather");
+            if (pref != null) {
+                pref.setOnPreferenceChangeListener((preference, newVal) -> {
+                    boolean doEnable = Boolean.TRUE.equals(newVal);
+
+                    Weather.getInstance().setCacheFile(requireContext().getCacheDir(), doEnable);
+
+                    return true;
+                });
+            }
+
+            pref = findPreference("language");
+            if (pref != null) {
+                pref.setOnPreferenceChangeListener((preference, newVal) -> {
+                    String newLang = newVal.toString();
+                    try {
+                        GBApplication.setLanguage(newLang);
+                        requireActivity().recreate();
+                    } catch (Exception ex) {
+                        GB.toast(requireContext().getApplicationContext(),
+                                "Error setting language: " + ex.getLocalizedMessage(),
+                                Toast.LENGTH_LONG,
+                                GB.ERROR,
+                                ex);
+                    }
+                    return true;
+                });
+            }
+
+            pref = findPreference("display_add_device_fab");
+            if (pref != null) {
+                pref.setOnPreferenceChangeListener((preference, newValue) -> {
+                    sendThemeChangeIntent();
+                    return true;
+                });
+            }
+            pref = findPreference("display_bottom_navigation_bar");
+            if (pref != null) {
+                pref.setOnPreferenceChangeListener((preference, newVal) -> {
+                    sendThemeChangeIntent();
+                    return true;
+                });
+            }
+
+            final Preference unit = findPreference(PREF_MEASUREMENT_SYSTEM);
+            if (unit != null) {
+                unit.setOnPreferenceChangeListener((preference, newVal) -> {
+                    invokeLater(() -> GBApplication.deviceService().onSendConfiguration(PREF_MEASUREMENT_SYSTEM));
+                    return true;
+                });
+            }
+
+            pref = findPreference("location_aquire");
+            if (pref != null) {
+                pref.setOnPreferenceClickListener(preference -> {
+                    if (ActivityCompat.checkSelfPermission(requireContext().getApplicationContext(), Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
+                        ActivityCompat.requestPermissions(requireActivity(), new String[]{Manifest.permission.ACCESS_COARSE_LOCATION}, 0);
+                    }
+
+                    LocationManager locationManager = (LocationManager) requireContext().getSystemService(Context.LOCATION_SERVICE);
+                    Criteria criteria = new Criteria();
+                    String provider = locationManager.getBestProvider(criteria, false);
+                    if (provider != null) {
+                        Location location = locationManager.getLastKnownLocation(provider);
+                        if (location != null) {
+                            setLocationPreferences(location);
+                        } else {
+                            locationManager.requestSingleUpdate(provider, new LocationListener() {
+                                @Override
+                                public void onLocationChanged(Location location) {
+                                    setLocationPreferences(location);
+                                }
+
+                                @Override
+                                public void onStatusChanged(String provider, int status, Bundle extras) {
+                                    LOG.info("provider status changed to " + status + " (" + provider + ")");
+                                }
+
+                                @Override
+                                public void onProviderEnabled(String provider) {
+                                    LOG.info("provider enabled (" + provider + ")");
+                                }
+
+                                @Override
+                                public void onProviderDisabled(String provider) {
+                                    LOG.info("provider disabled (" + provider + ")");
+                                    GB.toast(requireContext(), getString(R.string.toast_enable_networklocationprovider), 3000, 0);
+                                }
+                            }, null);
+                        }
+                    } else {
+                        LOG.warn("No location provider found, did you deny location permission?");
+                    }
+                    return true;
+                });
+            }
+
+            pref = findPreference("weather_city");
+            if (pref != null) {
+                pref.setOnPreferenceChangeListener((preference, newVal) -> {
+                    // reset city id and force a new lookup
+                    GBApplication.getPrefs().getPreferences().edit().putString("weather_cityid", null).apply();
+                    Intent intent = new Intent("GB_UPDATE_WEATHER");
+                    intent.setPackage(BuildConfig.APPLICATION_ID);
+                    requireContext().sendBroadcast(intent);
+                    return true;
+                });
+            }
+
+
+            pref = findPreference(GBPrefs.AUTO_EXPORT_LOCATION);
+            if (pref != null) {
+                pref.setOnPreferenceClickListener(preference -> {
+                    Intent i = new Intent(Intent.ACTION_CREATE_DOCUMENT);
+                    i.setType("application/x-sqlite3");
+                    i.addCategory(Intent.CATEGORY_OPENABLE);
+                    i.putExtra(Intent.EXTRA_TITLE, "Gadgetbridge.db");
+                    i.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+                    String title = requireContext().getApplicationContext().getString(R.string.choose_auto_export_location);
+                    startActivityForResult(Intent.createChooser(i, title), EXPORT_LOCATION_FILE_REQUEST_CODE);
+                    return true;
+                });
+                pref.setSummary(getAutoExportLocationSummary());
+            }
+
+            pref = findPreference(GBPrefs.AUTO_EXPORT_INTERVAL);
+            if (pref != null) {
+                pref.setOnPreferenceChangeListener((preference, autoExportInterval) -> {
+                    String summary = String.format(
+                            requireContext().getApplicationContext().getString(R.string.pref_summary_auto_export_interval),
+                            Integer.valueOf((String) autoExportInterval));
+                    preference.setSummary(summary);
+                    boolean auto_export_enabled = GBApplication.getPrefs().getBoolean(GBPrefs.AUTO_EXPORT_ENABLED, false);
+                    PeriodicExporter.scheduleAlarm(requireContext().getApplicationContext(), Integer.valueOf((String) autoExportInterval), auto_export_enabled);
+                    return true;
+                });
+                int autoExportInterval = GBApplication.getPrefs().getInt(GBPrefs.AUTO_EXPORT_INTERVAL, 0);
+                String summary = String.format(
+                        requireContext().getApplicationContext().getString(R.string.pref_summary_auto_export_interval),
+                        autoExportInterval);
+                pref.setSummary(summary);
+            }
+
+            pref = findPreference(GBPrefs.AUTO_EXPORT_ENABLED);
+            if (pref != null) {
+                pref.setOnPreferenceChangeListener((preference, autoExportEnabled) -> {
+                    int autoExportInterval = GBApplication.getPrefs().getInt(GBPrefs.AUTO_EXPORT_INTERVAL, 0);
+                    PeriodicExporter.scheduleAlarm(requireContext().getApplicationContext(), autoExportInterval, (boolean) autoExportEnabled);
+                    return true;
+                });
+            }
+
+            pref = findPreference("auto_fetch_interval_limit");
+            if (pref != null) {
+                pref.setOnPreferenceChangeListener((preference, autoFetchInterval) -> {
+                    String summary = String.format(
+                            requireContext().getApplicationContext().getString(R.string.pref_auto_fetch_limit_fetches_summary),
+                            Integer.valueOf((String) autoFetchInterval));
+                    preference.setSummary(summary);
+                    return true;
+                });
+
+                int autoFetchInterval = GBApplication.getPrefs().getInt("auto_fetch_interval_limit", 0);
+                String summary = String.format(
+                        requireContext().getApplicationContext().getString(R.string.pref_auto_fetch_limit_fetches_summary),
+                        autoFetchInterval);
+                pref.setSummary(summary);
+            }
+
+            final ListPreference audioPlayer = findPreference("audio_player");
+            if (audioPlayer != null) {
+                // Get all receivers of Media Buttons
+                Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
+
+                PackageManager pm = requireContext().getPackageManager();
+                List<ResolveInfo> mediaReceivers = pm.queryBroadcastReceivers(mediaButtonIntent,
+                        PackageManager.GET_INTENT_FILTERS | PackageManager.GET_RESOLVED_FILTER);
+
+                CharSequence[] newEntries = new CharSequence[mediaReceivers.size() + 1];
+                CharSequence[] newValues = new CharSequence[mediaReceivers.size() + 1];
+                newEntries[0] = getString(R.string.pref_default);
+                newValues[0] = "default";
+
+                int i = 1;
+                Set<String> existingNames = new HashSet<>();
+                for (ResolveInfo resolveInfo : mediaReceivers) {
+                    newEntries[i] = resolveInfo.activityInfo.loadLabel(pm) + " (" + resolveInfo.activityInfo.packageName + ")";
+                    if (existingNames.contains(newEntries[i].toString().trim())) {
+                        newEntries[i] = resolveInfo.activityInfo.loadLabel(pm) + " (" + resolveInfo.activityInfo.name + ")";
+                    } else {
+                        existingNames.add(newEntries[i].toString().trim());
+                    }
+                    newValues[i] = resolveInfo.activityInfo.packageName;
+                    i++;
+                }
+
+                audioPlayer.setEntries(newEntries);
+                audioPlayer.setEntryValues(newValues);
+                audioPlayer.setDefaultValue(newValues[0]);
+            }
+
+            pref = findPreference("pref_category_dashboard");
+            if (pref != null) {
+                pref.setOnPreferenceClickListener(preference -> {
+                    Intent enableIntent = new Intent(requireContext(), DashboardPreferencesActivity.class);
+                    startActivity(enableIntent);
+                    return true;
+                });
+            }
+
+            pref = findPreference("pref_category_sleepasandroid");
+            if (pref != null) {
+                pref.setOnPreferenceClickListener(preference -> {
+                    Intent enableIntent = new Intent(requireContext(), SleepAsAndroidPreferencesActivity.class);
+                    startActivity(enableIntent);
+                    return true;
+                });
+            }
+
+            pref = findPreference("pref_category_notifications");
+            if (pref != null) {
+                pref.setOnPreferenceClickListener(preference -> {
+                    Intent enableIntent = new Intent(requireContext(), NotificationManagementActivity.class);
+                    startActivity(enableIntent);
+                    return true;
+                });
+            }
+
+            final Preference theme = findPreference("pref_key_theme");
+            final Preference amoled_black = findPreference("pref_key_theme_amoled_black");
+
+            if (amoled_black != null) {
+                String selectedTheme = prefs.getString("pref_key_theme", requireContext().getString(R.string.pref_theme_value_system));
+                if (selectedTheme.equals("light"))
+                    amoled_black.setEnabled(false);
+                else
+                    amoled_black.setEnabled(true);
+                amoled_black.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
+                    @Override
+                    public boolean onPreferenceChange(Preference preference, Object newVal) {
+                        sendThemeChangeIntent();
+                        return true;
+                    }
+                });
+            }
+
+            if (theme != null) {
+                theme.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
+                    @Override
+                    public boolean onPreferenceChange(Preference preference, Object newVal) {
+                        final String val = newVal.toString();
+                        if (amoled_black != null) {
+                            if (val.equals("light"))
+                                amoled_black.setEnabled(false);
+                            else
+                                amoled_black.setEnabled(true);
+                        }
+                        // Warn user if dynamic colors are not available
+                        if (val.equals(requireContext().getString(R.string.pref_theme_value_dynamic)) && !DynamicColors.isDynamicColorAvailable()) {
+                            new MaterialAlertDialogBuilder(requireContext())
+                                    .setTitle(R.string.warning)
+                                    .setMessage(R.string.pref_theme_dynamic_colors_not_available_warning)
+                                    .setIcon(R.drawable.ic_warning)
+                                    .setPositiveButton(R.string.ok, (dialog, whichButton) -> {
+                                        sendThemeChangeIntent();
+                                    })
+                                    .show();
+                        } else {
+                            sendThemeChangeIntent();
+                        }
+                        return true;
+                    }
+                });
+            }
+
+            pref = findPreference("pref_discovery_pairing");
+            if (pref != null) {
+                pref.setOnPreferenceClickListener(preference -> {
+                    Intent enableIntent = new Intent(requireContext(), DiscoveryPairingPreferenceActivity.class);
+                    startActivity(enableIntent);
+                    return true;
+                });
+            }
+
+            //fitness app (OpenTracks) package name selection for OpenTracks observer
+            pref = findPreference("pref_key_opentracks_packagename");
+            if (pref != null) {
+                pref.setOnPreferenceClickListener(preference -> {
+                    final LinearLayout outerLayout = new LinearLayout(requireContext());
+                    outerLayout.setOrientation(LinearLayout.VERTICAL);
+                    final LinearLayout innerLayout = new LinearLayout(requireContext());
+                    innerLayout.setOrientation(LinearLayout.HORIZONTAL);
+                    innerLayout.setPadding(20, 0, 20, 0);
+                    final Spinner selectionListSpinner = new Spinner(requireContext());
+                    String[] appListArray = getResources().getStringArray(R.array.fitness_tracking_apps_package_names);
+                    ArrayAdapter<String> spinnerArrayAdapter = new ArrayAdapter<String>(requireContext(),
+                            android.R.layout.simple_spinner_dropdown_item, appListArray);
+                    selectionListSpinner.setAdapter(spinnerArrayAdapter);
+                    fitnessAppSelectionListSpinnerFirstRun = 0;
+                    addListenerOnSpinnerDeviceSelection(selectionListSpinner);
+                    Prefs prefs1 = GBApplication.getPrefs();
+                    String packageName = prefs1.getString("opentracks_packagename", "de.dennisguse.opentracks");
+                    // Set the spinner to the selected package name by default
+                    for (int i = 0; i < appListArray.length; i++) {
+                        if (appListArray[i].equals(packageName)) {
+                            selectionListSpinner.setSelection(i);
+                            break;
+                        }
+                    }
+                    fitnessAppEditText = new EditText(requireContext());
+                    fitnessAppEditText.setText(packageName);
+                    innerLayout.addView(fitnessAppEditText);
+                    outerLayout.addView(selectionListSpinner);
+                    outerLayout.addView(innerLayout);
+
+                    new MaterialAlertDialogBuilder(requireContext())
+                            .setCancelable(true)
+                            .setTitle(R.string.pref_title_opentracks_packagename)
+                            .setView(outerLayout)
+                            .setPositiveButton(R.string.ok, (dialog, which) -> {
+                                SharedPreferences.Editor editor = GBApplication.getPrefs().getPreferences().edit();
+                                editor.putString("opentracks_packagename", fitnessAppEditText.getText().toString());
+                                editor.apply();
+                            })
+                            .setNegativeButton(R.string.Cancel, (dialog, which) -> {})
+                            .show();
+                    return false;
+                });
+            }
+        }
+
+        private void addListenerOnSpinnerDeviceSelection(Spinner spinner) {
+            spinner.setOnItemSelectedListener(new CustomOnDeviceSelectedListener());
+        }
+
+        public class CustomOnDeviceSelectedListener implements AdapterView.OnItemSelectedListener {
+            public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
+                if (++fitnessAppSelectionListSpinnerFirstRun > 1) { //this prevents the setText to be set when spinner just is being initialized
+                    fitnessAppEditText.setText(parent.getItemAtPosition(pos).toString());
+                }
+            }
+
+            @Override
+            public void onNothingSelected(AdapterView<?> arg0) {
+                // TODO Auto-generated method stub
+            }
+        }
+
+        @Override
+        public void onActivityResult(int requestCode, int resultCode, Intent intent) {
+            if (requestCode == EXPORT_LOCATION_FILE_REQUEST_CODE && intent != null) {
+                Uri uri = intent.getData();
+                requireContext().getContentResolver().takePersistableUriPermission(uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+                GBApplication.getPrefs().getPreferences()
+                        .edit()
+                        .putString(GBPrefs.AUTO_EXPORT_LOCATION, uri.toString())
+                        .apply();
+                String summary = getAutoExportLocationSummary();
+                findPreference(GBPrefs.AUTO_EXPORT_LOCATION).setSummary(summary);
+                boolean autoExportEnabled = GBApplication
+                        .getPrefs().getBoolean(GBPrefs.AUTO_EXPORT_ENABLED, false);
+                int autoExportPeriod = GBApplication
+                        .getPrefs().getInt(GBPrefs.AUTO_EXPORT_INTERVAL, 0);
+                PeriodicExporter.scheduleAlarm(requireContext().getApplicationContext(), autoExportPeriod, autoExportEnabled);
+            }
+        }
+
+        /*
+        Either returns the file path of the selected document, or the display name, or an empty string
+         */
+        public String getAutoExportLocationSummary() {
+            String autoExportLocation = GBApplication.getPrefs().getString(GBPrefs.AUTO_EXPORT_LOCATION, null);
+            if (autoExportLocation == null) {
+                return "";
+            }
+            Uri uri = Uri.parse(autoExportLocation);
+            try {
+                return AndroidUtils.getFilePath(requireContext().getApplicationContext(), uri);
+            } catch (IllegalArgumentException e) {
+                try {
+                    Cursor cursor = requireContext().getContentResolver().query(
+                            uri,
+                            new String[]{DocumentsContract.Document.COLUMN_DISPLAY_NAME},
+                            null, null, null, null
+                    );
+                    if (cursor != null && cursor.moveToFirst()) {
+                        return cursor.getString(cursor.getColumnIndex(DocumentsContract.Document.COLUMN_DISPLAY_NAME));
+                    }
+                } catch (Exception fdfsdfds) {
+                    LOG.warn("fuck");
+                }
+            }
+            return "";
+        }
+
+        /*
+         * delayed execution so that the preferences are applied first
+         */
+        private void invokeLater(Runnable runnable) {
+            getListView().post(runnable);
+        }
+
+        private void setLocationPreferences(Location location) {
+            String latitude = String.format(Locale.US, "%.6g", location.getLatitude());
+            String longitude = String.format(Locale.US, "%.6g", location.getLongitude());
+            LOG.info("got location. Lat: {} Lng: {}", latitude, longitude);
+            GB.toast(requireContext(), getString(R.string.toast_aqurired_networklocation), 2000, 0);
+            GBApplication.getPrefs().getPreferences()
+                    .edit()
+                    .putString("location_latitude", latitude)
+                    .putString("location_longitude", longitude)
+                    .apply();
+        }
+
+        /**
+         * Signal running activities that the theme has changed
+         */
+        private void sendThemeChangeIntent() {
+            Intent intent = new Intent();
+            intent.setAction(GBApplication.ACTION_THEME_CHANGE);
+            LocalBroadcastManager.getInstance(requireContext()).sendBroadcast(intent);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/SleepAlarmWidgetConfigurationActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/SleepAlarmWidgetConfigurationActivity.java
new file mode 100644
index 0000000..e7cc18e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/SleepAlarmWidgetConfigurationActivity.java
@@ -0,0 +1,154 @@
+/*  Copyright (C) 2022-2024 Arjan Schrijver, Damien Gaignon, Daniel Dakhno,
+    Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities;
+
+import android.app.Activity;
+import android.appwidget.AppWidgetManager;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Pair;
+import android.widget.ListView;
+
+import androidx.appcompat.app.AlertDialog;
+
+import com.google.android.material.dialog.MaterialAlertDialogBuilder;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.util.AndroidUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
+import nodomain.freeyourgadget.gadgetbridge.util.WidgetPreferenceStorage;
+
+public class SleepAlarmWidgetConfigurationActivity extends Activity implements GBActivity {
+
+    // modified copy of WidgetConfigurationActivity
+    // if we knew which widget is calling this config activity, we could only use a single configuration
+    // activity and customize the filter in getAllDevices based on the caller.
+
+    private static final Logger LOG = LoggerFactory.getLogger(SleepAlarmWidgetConfigurationActivity.class);
+    int mAppWidgetId;
+
+    LinkedHashMap<String, Pair<String, Integer>> allDevices;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        AbstractGBActivity.init(this, AbstractGBActivity.NO_ACTIONBAR);
+
+        super.onCreate(savedInstanceState);
+
+        setResult(RESULT_CANCELED);
+
+        Intent intent = getIntent();
+        Bundle extras = intent.getExtras();
+
+        if (extras != null) {
+            mAppWidgetId = extras.getInt(
+                    AppWidgetManager.EXTRA_APPWIDGET_ID,
+                    AppWidgetManager.INVALID_APPWIDGET_ID);
+        }
+        // make the result intent and set the result to canceled
+        Intent resultValue;
+        resultValue = new Intent();
+        resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId);
+        setResult(RESULT_CANCELED, resultValue);
+
+        if (mAppWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID) {
+            finish();
+        }
+
+        MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(SleepAlarmWidgetConfigurationActivity.this);
+        builder.setTitle(R.string.widget_settings_select_device_title);
+
+        allDevices = getAllDevices(getApplicationContext());
+
+        List<String> list = new ArrayList<>();
+        for (Map.Entry<String, Pair<String, Integer>> item : allDevices.entrySet()) {
+            list.add(item.getKey());
+        }
+        String[] allDevicesString = list.toArray(new String[0]);
+
+        builder.setSingleChoiceItems(allDevicesString, 0, new DialogInterface.OnClickListener() {
+            @Override
+            public void onClick(DialogInterface dialog, int which) {
+                ListView lw = ((AlertDialog) dialog).getListView();
+                int selectedItemPosition = lw.getCheckedItemPosition();
+
+                if (selectedItemPosition > -1) {
+                    Map.Entry<String, Pair<String, Integer>> selectedItem =
+                            (Map.Entry<String, Pair<String, Integer>>) allDevices.entrySet().toArray()[selectedItemPosition];
+                    WidgetPreferenceStorage widgetPreferenceStorage = new WidgetPreferenceStorage();
+                    widgetPreferenceStorage.saveWidgetPrefs(getApplicationContext(), String.valueOf(mAppWidgetId), selectedItem.getValue().first);
+                }
+                Intent resultValue = new Intent();
+                resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId);
+                setResult(RESULT_OK, resultValue);
+                finish();
+            }
+        });
+        builder.setCancelable(false);
+
+        AlertDialog dialog = builder.create();
+        dialog.show();
+    }
+
+    public LinkedHashMap getAllDevices(Context appContext) {
+        DaoSession daoSession;
+        GBApplication gbApp = (GBApplication) appContext;
+        LinkedHashMap<String, Pair<String, Integer>> newMap = new LinkedHashMap<>(1);
+        List<? extends GBDevice> devices = gbApp.getDeviceManager().getDevices();
+
+        try (DBHandler handler = GBApplication.acquireDB()) {
+            daoSession = handler.getDaoSession();
+            for (GBDevice device : devices) {
+                DeviceCoordinator coordinator = device.getDeviceCoordinator();
+                Device dbDevice = DBHelper.findDevice(device, daoSession);
+                int icon = device.getEnabledDisabledIconResource();
+                if (dbDevice != null && coordinator != null
+                        && (coordinator.getAlarmSlotCount(device) > 0)
+                        && !newMap.containsKey(device.getAliasOrName())) {
+                    newMap.put(device.getAliasOrName(), new Pair(device.getAddress(), icon));
+                }
+            }
+        } catch (Exception e) {
+            LOG.error("Error getting list of all devices: " + e);
+        }
+        return newMap;
+    }
+
+    @Override
+    public void setLanguage(Locale language, boolean invalidateLanguage) {
+        AndroidUtils.setLanguage(this, language);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/SleepAsAndroidPreferencesActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/SleepAsAndroidPreferencesActivity.java
new file mode 100644
index 0000000..438f918
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/SleepAsAndroidPreferencesActivity.java
@@ -0,0 +1,138 @@
+package nodomain.freeyourgadget.gadgetbridge.activities;
+
+import android.os.Bundle;
+import android.util.Log;
+import android.widget.Toast;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.preference.ListPreference;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceFragmentCompat;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+import nodomain.freeyourgadget.gadgetbridge.devices.SleepAsAndroidFeature;
+
+public class SleepAsAndroidPreferencesActivity extends AbstractSettingsActivityV2 {
+    @Override
+    protected String fragmentTag() {
+        return SleepAsAndroidPreferencesFragment.FRAGMENT_TAG;
+    }
+
+    @Override
+    protected PreferenceFragmentCompat newFragment() {
+        return new SleepAsAndroidPreferencesFragment();
+    }
+
+    public static class SleepAsAndroidPreferencesFragment extends AbstractPreferenceFragment {
+        static final String FRAGMENT_TAG = "SLEEPASANDROID_PREFERENCES_FRAGMENT";
+
+        @Override
+        public void onCreatePreferences(@Nullable Bundle savedInstanceState, @Nullable String rootKey) {
+            setPreferencesFromResource(R.xml.sleepasandroid_preferences, rootKey);
+
+            final ListPreference sleepAsAndroidSlots = findPreference("sleepasandroid_alarm_slot");
+            if (sleepAsAndroidSlots != null)
+            {
+                loadAlarmSlots(sleepAsAndroidSlots);
+            }
+
+            final ListPreference sleepAsAndroidDevices = findPreference("sleepasandroid_device");
+            if (sleepAsAndroidDevices != null) {
+                loadDevicesList(sleepAsAndroidDevices);
+                sleepAsAndroidDevices.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
+                    @Override
+                    public boolean onPreferenceChange(@NonNull Preference preference, Object newValue) {
+                        GBDevice device = GBApplication.app().getDeviceManager().getDeviceByAddress(newValue.toString());
+                        if (device != null) {
+
+                            GBApplication.getPrefs().getPreferences().edit().putString("sleepasandroid_device", device.getAddress()).apply();
+
+                            Set<SleepAsAndroidFeature> supportedFeatures = device.getDeviceCoordinator().getSleepAsAndroidFeatures();
+                            findPreference("sleepasandroid_alarm_slot").setEnabled(supportedFeatures.contains(SleepAsAndroidFeature.ALARMS));
+                            findPreference("pref_key_sleepasandroid_feat_alarms").setEnabled(supportedFeatures.contains(SleepAsAndroidFeature.ALARMS));
+                            findPreference("pref_key_sleepasandroid_feat_notifications").setEnabled(supportedFeatures.contains(SleepAsAndroidFeature.NOTIFICATIONS));
+                            findPreference("pref_key_sleepasandroid_feat_movement").setEnabled(supportedFeatures.contains(SleepAsAndroidFeature.ACCELEROMETER));
+                            findPreference("pref_key_sleepasandroid_feat_hr").setEnabled(supportedFeatures.contains(SleepAsAndroidFeature.HEART_RATE));
+                            findPreference("pref_key_sleepasandroid_feat_oximetry").setEnabled(supportedFeatures.contains(SleepAsAndroidFeature.OXIMETRY));
+                            findPreference("pref_key_sleepasandroid_feat_spo2").setEnabled(supportedFeatures.contains(SleepAsAndroidFeature.SPO2));
+
+                            ListPreference alarmSlots = findPreference("sleepasandroid_alarm_slot");
+                            if (alarmSlots != null)
+                            {
+                                loadAlarmSlots(alarmSlots);
+                                if (alarmSlots.getEntries().length > 0)
+                                {
+                                    alarmSlots.setValueIndex(0);
+                                    GB.toast(getString(R.string.alarm_slot_reset), Toast.LENGTH_SHORT, GB.WARN);
+                                }
+                            }
+                        }
+                        return false;
+                    }
+                });
+
+            }
+
+            String defaultDeviceAddr = GBApplication.getPrefs().getString("sleepasandroid_device", "");
+            if (!defaultDeviceAddr.isEmpty()) {
+                GBDevice device = GBApplication.app().getDeviceManager().getDeviceByAddress(defaultDeviceAddr);
+                if (device != null) {
+
+                    Set<SleepAsAndroidFeature> supportedFeatures = device.getDeviceCoordinator().getSleepAsAndroidFeatures();
+                    findPreference("sleepasandroid_alarm_slot").setEnabled(supportedFeatures.contains(SleepAsAndroidFeature.ALARMS));
+                    findPreference("pref_key_sleepasandroid_feat_alarms").setEnabled(supportedFeatures.contains(SleepAsAndroidFeature.ALARMS));
+                    findPreference("pref_key_sleepasandroid_feat_notifications").setEnabled(supportedFeatures.contains(SleepAsAndroidFeature.NOTIFICATIONS));
+                    findPreference("pref_key_sleepasandroid_feat_movement").setEnabled(supportedFeatures.contains(SleepAsAndroidFeature.ACCELEROMETER));
+                    findPreference("pref_key_sleepasandroid_feat_hr").setEnabled(supportedFeatures.contains(SleepAsAndroidFeature.HEART_RATE));
+                    findPreference("pref_key_sleepasandroid_feat_oximetry").setEnabled(supportedFeatures.contains(SleepAsAndroidFeature.OXIMETRY));
+                    findPreference("pref_key_sleepasandroid_feat_spo2").setEnabled(supportedFeatures.contains(SleepAsAndroidFeature.SPO2));
+                }
+            }
+        }
+    }
+
+    private static void loadAlarmSlots(ListPreference sleepAsAndroidSlots) {
+        if (sleepAsAndroidSlots != null) {
+            String defaultDeviceAddr = GBApplication.getPrefs().getString("sleepasandroid_device", "");
+            if (!defaultDeviceAddr.isEmpty()) {
+                GBDevice device = GBApplication.app().getDeviceManager().getDeviceByAddress(defaultDeviceAddr);
+                if (device != null) {
+                    int maxAlarmSlots = device.getDeviceCoordinator().getAlarmSlotCount(device);
+                    if (maxAlarmSlots > 0) {
+                        List<String> alarmSlots = new ArrayList<>();
+                        int reservedAlarmSlots = GBApplication.getPrefs().getInt(DeviceSettingsPreferenceConst.PREF_RESERVER_ALARMS_CALENDAR, 0);
+                        for (int i = reservedAlarmSlots + 1;i < maxAlarmSlots; i++) {
+                            alarmSlots.add(String.valueOf(i));
+                        }
+                        sleepAsAndroidSlots.setEntryValues(alarmSlots.toArray(new String[0]));
+                        sleepAsAndroidSlots.setEntries(alarmSlots.toArray(new String[0]));
+                    }
+                }
+            }
+        }
+    }
+
+    private static void loadDevicesList(ListPreference sleepAsAndroidDevices) {
+        List<GBDevice> devices = GBApplication.app().getDeviceManager().getDevices();
+        List<String> deviceMACs = new ArrayList<>();
+        List<String> deviceNames = new ArrayList<>();
+        for (GBDevice dev : devices) {
+            if (dev.getDeviceCoordinator().supportsSleepAsAndroid()) {
+                deviceMACs.add(dev.getAddress());
+                deviceNames.add(dev.getAliasOrName());
+            }
+        }
+
+        sleepAsAndroidDevices.setEntryValues(deviceMACs.toArray(new String[0]));
+        sleepAsAndroidDevices.setEntries(deviceNames.toArray(new String[0]));
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/TextReceiverActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/TextReceiverActivity.java
new file mode 100644
index 0000000..5d38694
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/TextReceiverActivity.java
@@ -0,0 +1,80 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities;
+
+import android.content.Intent;
+import android.os.Bundle;
+
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import nodomain.freeyourgadget.gadgetbridge.BuildConfig;
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.NotificationType;
+
+/**
+ * Receive any shared plaintext and forward it directly to the devices as a notification.
+ */
+public class TextReceiverActivity extends AbstractGBActivity {
+    private static final Logger LOG = LoggerFactory.getLogger(TextReceiverActivity.class);
+
+    @Override
+    protected void onCreate(final Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        final Intent intent = getIntent();
+
+        final String action = intent.getAction();
+        if (!Intent.ACTION_SEND.equals(action)) {
+            LOG.warn("Unknown action '{}'", action);
+            finish();
+            return;
+        }
+
+        final String type = intent.getType();
+        if (!"text/plain".equals(type)) {
+            LOG.warn("Unknown type '{}'", type);
+            finish();
+            return;
+        }
+
+        final String text = intent.getStringExtra(Intent.EXTRA_TEXT);
+        if (StringUtils.isBlank(text)) {
+            LOG.warn("Text is null or empty");
+            finish();
+            return;
+        }
+
+        LOG.info("Sending '{}' to all devices", text);
+
+        final NotificationSpec notificationSpec = new NotificationSpec();
+        final String appName = getApplicationContext().getApplicationInfo()
+                .loadLabel(getApplicationContext().getPackageManager())
+                .toString();
+        notificationSpec.title = appName;
+        notificationSpec.body = text;
+        notificationSpec.sourceAppId = BuildConfig.APPLICATION_ID;
+        notificationSpec.sourceName = appName;
+        notificationSpec.type = NotificationType.UNKNOWN;
+        notificationSpec.pebbleColor = notificationSpec.type.color;
+
+        GBApplication.deviceService().onNotification(notificationSpec);
+        finish();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/VibrationActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/VibrationActivity.java
new file mode 100644
index 0000000..5805cdc
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/VibrationActivity.java
@@ -0,0 +1,59 @@
+/*  Copyright (C) 2016-2024 Andreas Shimokawa, Carsten Pfeiffer
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities;
+
+import android.os.Bundle;
+import android.widget.SeekBar;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+
+
+public class VibrationActivity extends AbstractGBActivity {
+    private static final Logger LOG = LoggerFactory.getLogger(VibrationActivity.class);
+    private SeekBar seekBar;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_vibration);
+
+        seekBar = (SeekBar) findViewById(R.id.vibration_seekbar);
+        seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
+            @Override
+            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+                if (progress > 0) { // 1-16
+                    progress = progress * 16 - 1; // max 255
+                }
+                GBApplication.deviceService().onSetConstantVibration(progress);
+            }
+
+            @Override
+            public void onStartTrackingTouch(SeekBar seekBar) {
+
+            }
+
+            @Override
+            public void onStopTrackingTouch(SeekBar seekBar) {
+
+            }
+        });
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/WakeActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/WakeActivity.java
new file mode 100644
index 0000000..fbf9693
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/WakeActivity.java
@@ -0,0 +1,66 @@
+/*  Copyright (C) 2022-2024 Ganblejs
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities;
+
+import android.app.Activity;
+import android.app.KeyguardManager;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Build;
+import android.os.Bundle;
+import android.view.WindowManager;
+
+import androidx.annotation.Nullable;
+
+public class WakeActivity extends Activity {
+    /*
+    By starting this activity via an intent sent e.g. by a Bangle.js the keyguard can be
+    dismissed automatically if the android device is in a trusted state (e.g. using a GB Device (Bangle.js) as a
+    trusted device via smart lock settings)
+
+    First try to start the activity you want to start with an intent and then start this activity with a second intent, both initiated on the Bangle.js, or other device.
+     */
+
+    public static void start(final Context context) {
+        Intent intent = new Intent(context, WakeActivity.class);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
+        intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
+        context.startActivity(intent);
+    }
+
+    private void dismissKeyguard() {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
+            setTurnScreenOn(true);
+            KeyguardManager keyguardManager = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE);
+            keyguardManager.requestDismissKeyguard(this, null);
+        } else {
+            this.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON|WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
+        }
+    }
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(null);
+        // Unlock the android device if it's in a trusted state, otherwise request user action to unlock
+        dismissKeyguard();
+        // Go back to last activity, which can have been waiting to start under
+        // the lock screen, e.g. it was previously initiated via intent message from Bangle.js
+        this.onBackPressed();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/WidgetAlarmsActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/WidgetAlarmsActivity.java
new file mode 100644
index 0000000..8d56e9e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/WidgetAlarmsActivity.java
@@ -0,0 +1,144 @@
+/*  Copyright (C) 2019-2024 Dmitry Markin, Jos茅 Rebelo, Petr Van臎k,
+    Taavi Eom盲e
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.os.Handler;
+import android.view.View;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
+import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
+import nodomain.freeyourgadget.gadgetbridge.util.AlarmUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class WidgetAlarmsActivity extends Activity implements View.OnClickListener {
+
+    TextView textView;
+    GBDevice deviceForWidget;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        Context appContext = this.getApplicationContext();
+
+        Bundle extras = getIntent().getExtras();
+        if (extras != null) {
+            deviceForWidget = extras.getParcelable(GBDevice.EXTRA_DEVICE);
+        } else {
+            GB.toast(this,
+                    "Error no device",
+                    Toast.LENGTH_LONG, GB.ERROR);
+            return;
+        }
+
+        if (appContext instanceof GBApplication) {
+
+            if (deviceForWidget == null || !deviceForWidget.isInitialized()) {
+                GB.toast(this,
+                        this.getString(R.string.not_connected),
+                        Toast.LENGTH_LONG, GB.INFO);
+
+            } else {
+                setContentView(R.layout.widget_alarms_activity_list);
+                int userSleepDuration = new ActivityUser().getSleepDurationGoal();
+                textView = findViewById(R.id.alarm5);
+                if (userSleepDuration > 0) {
+                    Resources res = getResources();
+                    textView.setText(res.getQuantityString(R.plurals.widget_alarm_target_hours, userSleepDuration, userSleepDuration));
+                } else {
+                    textView.setVisibility(View.GONE);
+                }
+            }
+        }
+    }
+
+    @Override
+    public void onClick(final View v) {
+        final int viewId = v.getId();
+        if (viewId == R.id.alarm1) {
+            setAlarm(5);
+        } else if (viewId == R.id.alarm2) {
+            setAlarm(10);
+        } else if (viewId == R.id.alarm3) {
+            setAlarm(20);
+        } else if (viewId == R.id.alarm4) {
+            setAlarm(60);
+        } else if (viewId == R.id.alarm5) {
+            setAlarm(0);
+        }
+
+        //this is to prevent screen flashing during closing
+        new Handler().postDelayed(new Runnable() {
+            @Override
+            public void run() {
+                finish();
+            }
+        }, 150);
+    }
+
+    public void setAlarm(int duration) {
+        // current timestamp
+        GregorianCalendar calendar = new GregorianCalendar();
+        if (duration > 0) {
+            calendar.add(Calendar.MINUTE, duration);
+        } else {
+            int userSleepDuration = new ActivityUser().getSleepDurationGoal();
+            // add preferred sleep duration
+            if (userSleepDuration > 0) {
+                calendar.add(Calendar.HOUR_OF_DAY, userSleepDuration);
+            } else { // probably testing
+                calendar.add(Calendar.MINUTE, 1);
+            }
+        }
+
+        // overwrite the first alarm and activate it, without
+
+        if (deviceForWidget == null || !deviceForWidget.isInitialized()) {
+            GB.toast(this,
+                    this.getString(R.string.appwidget_not_connected),
+                    Toast.LENGTH_LONG, GB.WARN);
+            return;
+        }
+
+        int hours = calendar.get(Calendar.HOUR_OF_DAY);
+        int minutes = calendar.get(Calendar.MINUTE);
+
+        GB.toast(this,
+                this.getString(R.string.appwidget_setting_alarm, hours, minutes),
+                Toast.LENGTH_SHORT, GB.INFO);
+
+        Alarm alarm = AlarmUtils.createSingleShot(0, true, true, calendar);
+        ArrayList<Alarm> alarms = new ArrayList<>(1);
+        alarms.add(alarm);
+        GBApplication.deviceService(deviceForWidget).onSetAlarms(alarms);
+
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/WidgetConfigurationActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/WidgetConfigurationActivity.java
new file mode 100644
index 0000000..056411c
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/WidgetConfigurationActivity.java
@@ -0,0 +1,147 @@
+/*  Copyright (C) 2020-2024 Arjan Schrijver, Daniel Dakhno, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities;
+
+import android.app.Activity;
+import android.appwidget.AppWidgetManager;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Pair;
+import android.widget.ListView;
+
+import androidx.appcompat.app.AlertDialog;
+
+import com.google.android.material.dialog.MaterialAlertDialogBuilder;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.util.AndroidUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
+import nodomain.freeyourgadget.gadgetbridge.util.WidgetPreferenceStorage;
+
+public class WidgetConfigurationActivity extends Activity implements GBActivity {
+    private static final Logger LOG = LoggerFactory.getLogger(WidgetConfigurationActivity.class);
+    int mAppWidgetId;
+
+    LinkedHashMap<String, Pair<String, Integer>> allDevices;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        AbstractGBActivity.init(this, AbstractGBActivity.NO_ACTIONBAR);
+
+        super.onCreate(savedInstanceState);
+
+        setResult(RESULT_CANCELED);
+
+        Intent intent = getIntent();
+        Bundle extras = intent.getExtras();
+
+        if (extras != null) {
+            mAppWidgetId = extras.getInt(
+                    AppWidgetManager.EXTRA_APPWIDGET_ID,
+                    AppWidgetManager.INVALID_APPWIDGET_ID);
+        }
+        // make the result intent and set the result to canceled
+        Intent resultValue; resultValue = new Intent();
+        resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId);
+        setResult(RESULT_CANCELED, resultValue);
+
+        if (mAppWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID) {
+            finish();
+        }
+
+        MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(WidgetConfigurationActivity.this);
+        builder.setTitle(R.string.widget_settings_select_device_title);
+
+        allDevices = getAllDevices(getApplicationContext());
+
+        List<String> list = new ArrayList<>();
+        for (Map.Entry<String, Pair<String, Integer>> item : allDevices.entrySet()) {
+            list.add(item.getKey());
+        }
+        String[] allDevicesString = list.toArray(new String[0]);
+
+        builder.setSingleChoiceItems(allDevicesString, 0, new DialogInterface.OnClickListener() {
+            @Override
+            public void onClick(DialogInterface dialog, int which) {
+                ListView lw = ((AlertDialog) dialog).getListView();
+                int selectedItemPosition = lw.getCheckedItemPosition();
+
+                if (selectedItemPosition > -1) {
+                    Map.Entry<String, Pair<String, Integer>> selectedItem =
+                            (Map.Entry<String, Pair<String, Integer>>) allDevices.entrySet().toArray()[selectedItemPosition];
+                    WidgetPreferenceStorage widgetPreferenceStorage = new WidgetPreferenceStorage();
+                    widgetPreferenceStorage.saveWidgetPrefs(getApplicationContext(), String.valueOf(mAppWidgetId), selectedItem.getValue().first);
+                }
+                Intent resultValue = new Intent();
+                resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId);
+                setResult(RESULT_OK, resultValue);
+                finish();
+            }
+        });
+        builder.setCancelable(false);
+
+        AlertDialog dialog = builder.create();
+        dialog.show();
+    }
+
+    public LinkedHashMap getAllDevices(Context appContext) {
+        DaoSession daoSession;
+        GBApplication gbApp = (GBApplication) appContext;
+        LinkedHashMap<String, Pair<String, Integer>> newMap = new LinkedHashMap<>(1);
+        List<? extends GBDevice> devices = gbApp.getDeviceManager().getDevices();
+
+        try (DBHandler handler = GBApplication.acquireDB()) {
+            daoSession = handler.getDaoSession();
+            for (GBDevice device : devices) {
+                DeviceCoordinator coordinator = device.getDeviceCoordinator();
+                Device dbDevice = DBHelper.findDevice(device, daoSession);
+                int icon = device.getEnabledDisabledIconResource();
+                if (dbDevice != null && coordinator != null
+                        && (coordinator.supportsActivityDataFetching() || coordinator.supportsActivityTracking())
+                        && !newMap.containsKey(device.getAliasOrName())) {
+                    newMap.put(device.getAliasOrName(), new Pair(device.getAddress(), icon));
+                }
+            }
+        } catch (Exception e) {
+            LOG.error("Error getting list of all devices: " + e);
+        }
+        return newMap;
+    }
+
+    @Override
+    public void setLanguage(Locale language, boolean invalidateLanguage) {
+        AndroidUtils.setLanguage(this, language);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/WorldClockDetails.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/WorldClockDetails.java
new file mode 100644
index 0000000..9d48ac3
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/WorldClockDetails.java
@@ -0,0 +1,220 @@
+/*  Copyright (C) 2022-2024 Arjan Schrijver, Daniel Dakhno, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities;
+
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.text.Editable;
+import android.text.InputFilter;
+import android.text.TextWatcher;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.ArrayAdapter;
+import android.widget.CheckBox;
+import android.widget.EditText;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.annotation.NonNull;
+
+import com.google.android.material.dialog.MaterialAlertDialogBuilder;
+import com.google.android.material.floatingactionbutton.FloatingActionButton;
+
+import java.util.TimeZone;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.entities.WorldClock;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
+
+public class WorldClockDetails extends AbstractGBActivity {
+    private WorldClock worldClock;
+    private GBDevice device;
+
+    ArrayAdapter<String> timezoneAdapter;
+
+    TextView worldClockTimezone;
+    EditText worldClockLabel;
+    EditText worldClockCode;
+    View worldClockEnabledCard;
+    CheckBox worldClockEnabled;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_world_clock_details);
+
+        worldClock = (WorldClock) getIntent().getSerializableExtra(WorldClock.EXTRA_WORLD_CLOCK);
+
+        if (worldClock == null) {
+            GB.toast("No worldClock provided to WorldClockDetails Activity", Toast.LENGTH_LONG, GB.ERROR);
+            finish();
+            return;
+        }
+
+        worldClockEnabledCard = findViewById(R.id.card_enabled);
+        worldClockEnabled = findViewById(R.id.world_clock_enabled);
+        worldClockTimezone = findViewById(R.id.world_clock_timezone);
+        worldClockLabel = findViewById(R.id.world_clock_label);
+        worldClockCode = findViewById(R.id.world_clock_code);
+
+        device = getIntent().getParcelableExtra(GBDevice.EXTRA_DEVICE);
+        if (device == null) {
+            GB.toast("No device provided to WorldClockDetails Activity", Toast.LENGTH_LONG, GB.ERROR);
+            finish();
+            return;
+        }
+
+        final DeviceCoordinator coordinator = device.getDeviceCoordinator();
+
+        final String[] timezoneIDs = TimeZone.getAvailableIDs();
+        timezoneAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, timezoneIDs);
+
+        final View cardTimezone = findViewById(R.id.card_timezone);
+        cardTimezone.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                new MaterialAlertDialogBuilder(WorldClockDetails.this).setAdapter(timezoneAdapter, new DialogInterface.OnClickListener() {
+                    @Override
+                    public void onClick(DialogInterface dialogInterface, int i) {
+                        worldClock.setTimeZoneId(timezoneIDs[i]);
+                        updateUiFromWorldClock();
+                    }
+                }).create().show();
+            }
+        });
+
+        if (coordinator.supportsDisabledWorldClocks()) {
+            worldClockEnabled.setOnCheckedChangeListener((buttonView, isChecked) -> {
+                worldClock.setEnabled(isChecked);
+            });
+        } else {
+            worldClockEnabledCard.setVisibility(View.GONE);
+        }
+
+        worldClockLabel.setFilters(new InputFilter[]{new InputFilter.LengthFilter(coordinator.getWorldClocksLabelLength())});
+        worldClockLabel.addTextChangedListener(new TextWatcher() {
+            @Override
+            public void beforeTextChanged(final CharSequence s, int start, int count, int after) {
+            }
+
+            @Override
+            public void onTextChanged(final CharSequence s, int start, int before, int count) {
+            }
+
+            @Override
+            public void afterTextChanged(final Editable s) {
+                worldClock.setLabel(s.toString());
+            }
+        });
+
+        worldClockCode.setFilters(new InputFilter[]{new InputFilter.LengthFilter(3)});
+        worldClockCode.addTextChangedListener(new TextWatcher() {
+            @Override
+            public void beforeTextChanged(final CharSequence s, int start, int count, int after) {
+            }
+
+            @Override
+            public void onTextChanged(final CharSequence s, int start, int before, int count) {
+            }
+
+            @Override
+            public void afterTextChanged(final Editable s) {
+                worldClock.setCode(s.toString());
+            }
+        });
+
+        final FloatingActionButton fab = findViewById(R.id.fab_save);
+        fab.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                updateWorldClock();
+                WorldClockDetails.this.setResult(1);
+                finish();
+            }
+        });
+
+        updateUiFromWorldClock();
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(final MenuItem item) {
+        final int itemId = item.getItemId();
+        if (itemId == android.R.id.home) {
+            // back button
+            // TODO confirm when exiting without saving
+            finish();
+            return true;
+        }
+        return super.onOptionsItemSelected(item);
+    }
+
+    private void updateWorldClock() {
+        DBHelper.store(worldClock);
+    }
+
+    @Override
+    protected void onSaveInstanceState(@NonNull final Bundle state) {
+        super.onSaveInstanceState(state);
+        state.putSerializable("worldClock", worldClock);
+    }
+
+    @Override
+    protected void onRestoreInstanceState(@NonNull final Bundle savedInstanceState) {
+        super.onRestoreInstanceState(savedInstanceState);
+        worldClock = (WorldClock) savedInstanceState.getSerializable("worldClock");
+        updateUiFromWorldClock();
+    }
+
+    public void updateUiFromWorldClock() {
+        final DeviceCoordinator coordinator = device.getDeviceCoordinator();
+        final int maxLabelLength = coordinator.getWorldClocksLabelLength();
+
+        worldClockEnabled.setChecked(worldClock.getEnabled() == null || worldClock.getEnabled());
+
+        final String oldTimezone = worldClockTimezone.getText().toString();
+
+        worldClockTimezone.setText(worldClock.getTimeZoneId());
+
+        // Check if the label was still the default (the timezone city name)
+        // If so, and if the user changed the timezone, update the label to match the new city name
+        if (!StringUtils.isNullOrEmpty(oldTimezone) && !oldTimezone.equals(worldClock.getTimeZoneId())) {
+            final String[] oldTimezoneParts = oldTimezone.split("/");
+            final String[] newTimezoneParts = worldClock.getTimeZoneId().split("/");
+            final String newLabel = StringUtils.truncate(newTimezoneParts[newTimezoneParts.length - 1], maxLabelLength);
+            final String oldLabel = StringUtils.truncate(oldTimezoneParts[oldTimezoneParts.length - 1], maxLabelLength);
+            final String userLabel = worldClockLabel.getText().toString();
+            if (StringUtils.isNullOrEmpty(userLabel) || userLabel.equals(oldLabel)) {
+                // The label was still the original, so let's override it with the new city
+                worldClock.setLabel(newLabel);
+            }
+            final String newCode = StringUtils.truncate(newLabel, 3).toUpperCase();
+            final String oldCode = StringUtils.truncate(oldLabel, 3).toUpperCase();
+            final String userCode = worldClockCode.getText().toString();
+            if (StringUtils.isNullOrEmpty(userCode) || userCode.equals(oldCode)) {
+                // The code was still the original, so let's override it with the new one
+                worldClock.setCode(newCode);
+            }
+        }
+
+        worldClockLabel.setText(worldClock.getLabel());
+        worldClockCode.setText(worldClock.getCode());
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/app_specific_notifications/AppSpecificNotificationSettingsActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/app_specific_notifications/AppSpecificNotificationSettingsActivity.java
new file mode 100644
index 0000000..5a172b7
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/app_specific_notifications/AppSpecificNotificationSettingsActivity.java
@@ -0,0 +1,76 @@
+/*  Copyright (C) 2023-2024 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 <https://www.gnu.org/licenses/>. */
+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 0000000..27af770
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/app_specific_notifications/AppSpecificNotificationSettingsDetailActivity.java
@@ -0,0 +1,184 @@
+/*  Copyright (C) 2023-2024 akasaka / Genjitsu Labs, Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.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<String> mLedPatternValues = new ArrayList<>();
+    private List<String> mVibrationPatternValues = new ArrayList<>();
+    private List<String> 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 = mDevice.getDeviceCoordinator();
+
+        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<String> createAdapterFromArrayAddingDefault(AbstractNotificationPattern[] array) {
+        List<String> allOptions = new ArrayList<>();
+        allOptions.add(getString(R.string.pref_default));
+        for(AbstractNotificationPattern s: array) allOptions.add(s.getUserReadableName(getApplicationContext()));
+
+        return new ArrayAdapter<String>(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/appmanager/AbstractAppManagerFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/appmanager/AbstractAppManagerFragment.java
new file mode 100644
index 0000000..3ddd57e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/appmanager/AbstractAppManagerFragment.java
@@ -0,0 +1,787 @@
+/*  Copyright (C) 2016-2024 Andreas Shimokawa, Arjan Schrijver, Carsten
+    Pfeiffer, Daniel Dakhno, Daniele Gobbetti, Jos茅 Rebelo, Konrad Iturbe,
+    TylerWilliamson
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities.appmanager;
+
+import static nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.QHybridSupport.QHYBRID_ACTION_DOWNLOADED_FILE;
+
+import android.app.Activity;
+import android.content.ActivityNotFoundException;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.PopupMenu;
+import android.widget.Toast;
+
+import androidx.core.content.FileProvider;
+import androidx.fragment.app.Fragment;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+import androidx.recyclerview.widget.ItemTouchHelper;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.google.android.material.dialog.MaterialAlertDialogBuilder;
+import com.google.android.material.floatingactionbutton.FloatingActionButton;
+
+import org.apache.commons.lang3.StringUtils;
+import org.json.JSONObject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.ExternalPebbleJSActivity;
+import nodomain.freeyourgadget.gadgetbridge.adapter.GBDeviceAppAdapter;
+import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.qhybrid.FossilFileReader;
+import nodomain.freeyourgadget.gadgetbridge.devices.qhybrid.FossilHRInstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.qhybrid.QHybridConstants;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.PebbleProtocol;
+import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+import nodomain.freeyourgadget.gadgetbridge.util.GridAutoFitLayoutManager;
+import nodomain.freeyourgadget.gadgetbridge.util.PebbleUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.Version;
+
+
+public abstract class AbstractAppManagerFragment extends Fragment {
+    public static final String ACTION_REFRESH_APPLIST
+            = "nodomain.freeyourgadget.gadgetbridge.appmanager.action.refresh_applist";
+    private static final Logger LOG = LoggerFactory.getLogger(AbstractAppManagerFragment.class);
+    private static final int CHILD_ACTIVITY_WATCHFACE_EDITOR = 0;
+
+    private ItemTouchHelper appManagementTouchHelper;
+
+    protected final List<GBDeviceApp> appList = new ArrayList<>();
+    private GBDeviceAppAdapter mGBDeviceAppAdapter;
+    protected GBDevice mGBDevice = null;
+    protected DeviceCoordinator mCoordinator = null;
+    private Class<? extends Activity> watchfaceDesignerActivity;
+
+    protected abstract List<GBDeviceApp> getSystemAppsInCategory();
+
+    protected abstract String getSortFilename();
+
+    protected abstract boolean isCacheManager();
+
+    protected abstract boolean filterApp(GBDeviceApp gbDeviceApp);
+
+    public void startDragging(RecyclerView.ViewHolder viewHolder) {
+        appManagementTouchHelper.startDrag(viewHolder);
+    }
+
+    protected void onChangedAppOrder() {
+        List<UUID> uuidList = new ArrayList<>();
+        for (GBDeviceApp gbDeviceApp : mGBDeviceAppAdapter.getAppList()) {
+            uuidList.add(gbDeviceApp.getUUID());
+        }
+        AppManagerActivity.rewriteAppOrderFile(getSortFilename(), uuidList);
+    }
+
+    protected void refreshList() {
+        appList.clear();
+        ArrayList<UUID> uuids = AppManagerActivity.getUuidsFromFile(getSortFilename());
+        List<GBDeviceApp> systemApps = getSystemAppsInCategory();
+        boolean needsRewrite = false;
+        for (GBDeviceApp systemApp : systemApps) {
+            if (!uuids.contains(systemApp.getUUID())) {
+                uuids.add(systemApp.getUUID());
+                needsRewrite = true;
+            }
+        }
+        if (needsRewrite) {
+            AppManagerActivity.rewriteAppOrderFile(getSortFilename(), uuids);
+        }
+        appList.addAll(getCachedApps(uuids));
+    }
+
+    private void refreshListFromDevice(Intent intent) {
+        final Map<UUID, GBDeviceApp> cachedAppsMap = getCachedAppsMap(null);
+
+        appList.clear();
+        int appCount = intent.getIntExtra("app_count", 0);
+        for (int i = 0; i < appCount; i++) {
+            String appName = intent.getStringExtra("app_name" + i);
+            String appCreator = intent.getStringExtra("app_creator" + i);
+            String appVersion = intent.getStringExtra("app_version" + i);
+            UUID uuid = UUID.fromString(intent.getStringExtra("app_uuid" + i));
+            GBDeviceApp.Type appType = GBDeviceApp.Type.values()[intent.getIntExtra("app_type" + i, 0)];
+            Bitmap previewImage = getAppPreviewImage(uuid.toString());
+
+            // Fill out information from the cached app if missing
+            final GBDeviceApp cachedApp = cachedAppsMap.get(uuid);
+            if (cachedApp != null) {
+                if (StringUtils.isBlank(appName)) {
+                    appName = cachedApp.getName();
+                }
+                if (StringUtils.isBlank(appCreator)) {
+                    appCreator = cachedApp.getCreator();
+                }
+            } else {
+                if (StringUtils.isBlank(appName)) {
+                    // If the app does not have a name, fallback to uuid
+                    appName = uuid.toString();
+                }
+            }
+
+            GBDeviceApp app = new GBDeviceApp(uuid, appName, appCreator, appVersion, appType, previewImage);
+            app.setOnDevice(true);
+            if (mGBDevice.getType() == DeviceType.FOSSILQHYBRID) {
+                if ((app.getType() == GBDeviceApp.Type.WATCHFACE) && (!QHybridConstants.HYBRIDHR_WATCHFACE_VERSION.equals(appVersion))) {
+                    app.setUpToDate(false);
+                }
+                try {
+                    if ((app.getType() == GBDeviceApp.Type.APP_GENERIC) && ((new Version(app.getVersion())).smallerThan(new Version(QHybridConstants.KNOWN_WAPP_VERSIONS.get(app.getName()))))) {
+                        app.setUpToDate(false);
+                    }
+                } catch (IllegalArgumentException e) {
+                    LOG.warn("App JSON: " + app.getJSON().toString());
+                    LOG.warn("Couldn't read app version", e);
+                }
+            }
+
+            if (filterApp(app)) {
+                appList.add(app);
+            }
+        }
+        if (mGBDevice.getType() == DeviceType.FOSSILQHYBRID) {
+            List<GBDeviceApp> systemApps = getSystemAppsInCategory();
+            for (GBDeviceApp systemApp : systemApps) {
+                appList.add(systemApp);
+            }
+        }
+    }
+
+    private Bitmap getAppPreviewImage(String name) {
+        Bitmap previewImage = null;
+        try {
+            File cacheDir = mCoordinator.getAppCacheDir();
+            File previewImgFile = new File(cacheDir, name + "_preview.png");
+            if (previewImgFile.exists()) {
+                previewImage = BitmapFactory.decodeFile(previewImgFile.getAbsolutePath());
+            }
+        } catch (IOException e) {
+            LOG.warn("Couldn't load watch app preview image", e);
+        }
+        return previewImage;
+    }
+
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            switch (action) {
+                case ACTION_REFRESH_APPLIST: {
+                    if (intent.hasExtra("app_count")) {
+                        LOG.info("got app info from device");
+                        if (!isCacheManager()) {
+                            LOG.info("will refresh list based on data from device");
+                            refreshListFromDevice(intent);
+                        }
+                    } else if (mCoordinator.supportsAppListFetching()) {
+                        refreshList();
+                    } else if (isCacheManager()) {
+                        refreshList();
+                    }
+                    mGBDeviceAppAdapter.notifyDataSetChanged();
+                    break;
+                }
+                case QHYBRID_ACTION_DOWNLOADED_FILE: {
+                    if (!intent.getBooleanExtra("EXTRA_SUCCESS", false)) {
+                        LOG.warn("wapp download was not successful");
+                        GB.toast(context.getString(R.string.appmanager_download_app_error), Toast.LENGTH_LONG, GB.ERROR);
+                        break;
+                    }
+                    if (!intent.getBooleanExtra("EXTRA_TOCACHE", false)) {
+                        break;
+                    }
+                    String path = intent.getStringExtra("EXTRA_PATH");
+                    String name = intent.getStringExtra("EXTRA_NAME");
+                    LOG.info("Attempting to add downloaded app " + name + " to cache");
+                    FossilFileReader fileReader;
+                    try {
+                        fileReader = new FossilFileReader(Uri.fromFile(new File(path)), context);
+                    } catch (IOException e) {
+                        LOG.warn("Could not find downloaded wapp", e);
+                        break;
+                    }
+                    if (FossilHRInstallHandler.saveAppInCache(fileReader, fileReader.getBackground(), fileReader.getPreview(), mCoordinator, context)) {
+                        LOG.info("Successfully moved downloaded app " + name + " to cache");
+                        GB.toast(String.format(context.getString(R.string.appmanager_downloaded_to_cache), name), Toast.LENGTH_LONG, GB.INFO);
+                        if (isCacheManager()) {
+                            refreshList();
+                            mGBDeviceAppAdapter.notifyDataSetChanged();
+                        }
+                        (new File(path)).delete();
+                    } else {
+                        LOG.warn("Parsing downloaded wapp was not successful");
+                        GB.toast(context.getString(R.string.appmanager_download_app_error), Toast.LENGTH_LONG, GB.ERROR);
+                    }
+                    break;
+                }
+            }
+        }
+    };
+
+    protected Map<UUID, GBDeviceApp> getCachedAppsMap(final List<UUID> uuids) {
+        final List<GBDeviceApp> cachedApps = getCachedApps(uuids);
+        final Map<UUID, GBDeviceApp> cachedAppsMap = new HashMap<>();
+        for (GBDeviceApp cachedApp : cachedApps) {
+            cachedAppsMap.put(cachedApp.getUUID(), cachedApp);
+        }
+        return cachedAppsMap;
+    }
+
+    protected List<GBDeviceApp> getCachedApps(List<UUID> uuids) {
+        List<GBDeviceApp> cachedAppList = new ArrayList<>();
+        File cachePath;
+        try {
+            cachePath = mCoordinator.getAppCacheDir();
+        } catch (IOException e) {
+            LOG.warn("could not get external dir while reading app cache.");
+            return cachedAppList;
+        }
+
+        if (cachePath == null) {
+            LOG.warn("Cached apps path is null");
+            return Collections.emptyList();
+        }
+
+        File[] files;
+        if (uuids == null) {
+            files = cachePath.listFiles();
+        } else {
+            files = new File[uuids.size()];
+            int index = 0;
+            for (UUID uuid : uuids) {
+                files[index++] = new File(uuid.toString() + mCoordinator.getAppFileExtension());
+            }
+        }
+        if (files != null) {
+            for (File file : files) {
+                if (file.getName().endsWith(mCoordinator.getAppFileExtension())) {
+                    String baseName = file.getName().substring(0, file.getName().length() - mCoordinator.getAppFileExtension().length());
+                    //metadata
+                    File jsonFile = new File(cachePath, baseName + ".json");
+                    //configuration
+                    File configFile = new File(cachePath, baseName + "_config.js");
+                    try {
+                        String jsonstring = FileUtils.getStringFromFile(jsonFile);
+                        JSONObject json = new JSONObject(jsonstring);
+                        GBDeviceApp app = new GBDeviceApp(json, configFile.exists(), getAppPreviewImage(baseName));
+                        if (mGBDevice.getType() == DeviceType.FOSSILQHYBRID) {
+                            if ((app.getType() == GBDeviceApp.Type.WATCHFACE) && (!QHybridConstants.HYBRIDHR_WATCHFACE_VERSION.equals(app.getVersion()))) {
+                                app.setUpToDate(false);
+                            }
+                            try {
+                                if ((app.getType() == GBDeviceApp.Type.APP_GENERIC) && ((new Version(app.getVersion())).smallerThan(new Version(QHybridConstants.KNOWN_WAPP_VERSIONS.get(app.getName()))))) {
+                                    app.setUpToDate(false);
+                                }
+                            } catch (IllegalArgumentException e) {
+                                LOG.warn("Couldn't read app version", e);
+                            }
+                        }
+                        cachedAppList.add(app);
+                    } catch (Exception e) {
+                        LOG.info("could not read json file for " + baseName);
+                        if (mGBDevice.getType() == DeviceType.PEBBLE) {
+                            //FIXME: this is really ugly, if we do not find system uuids in pbw cache add them manually. Also duplicated code
+                            switch (baseName) {
+                                case "8f3c8686-31a1-4f5f-91f5-01600c9bdc59":
+                                    cachedAppList.add(new GBDeviceApp(UUID.fromString(baseName), "Tic Toc (System)", "Pebble Inc.", "", GBDeviceApp.Type.WATCHFACE_SYSTEM));
+                                    break;
+                                case "1f03293d-47af-4f28-b960-f2b02a6dd757":
+                                    cachedAppList.add(new GBDeviceApp(UUID.fromString(baseName), "Music (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
+                                    break;
+                                case "b2cae818-10f8-46df-ad2b-98ad2254a3c1":
+                                    cachedAppList.add(new GBDeviceApp(UUID.fromString(baseName), "Notifications (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
+                                    break;
+                                case "67a32d95-ef69-46d4-a0b9-854cc62f97f9":
+                                    cachedAppList.add(new GBDeviceApp(UUID.fromString(baseName), "Alarms (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
+                                    break;
+                                case "18e443ce-38fd-47c8-84d5-6d0c775fbe55":
+                                    cachedAppList.add(new GBDeviceApp(UUID.fromString(baseName), "Watchfaces (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
+                                    break;
+                                case "0863fc6a-66c5-4f62-ab8a-82ed00a98b5d":
+                                    cachedAppList.add(new GBDeviceApp(UUID.fromString(baseName), "Send Text (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
+                                    break;
+                            }
+                            /*
+                            else if (baseName.equals("4dab81a6-d2fc-458a-992c-7a1f3b96a970")) {
+                                cachedAppList.add(new GBDeviceApp(UUID.fromString("4dab81a6-d2fc-458a-992c-7a1f3b96a970"), "Sports (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
+                            } else if (baseName.equals("cf1e816a-9db0-4511-bbb8-f60c48ca8fac")) {
+                                cachedAppList.add(new GBDeviceApp(UUID.fromString("cf1e816a-9db0-4511-bbb8-f60c48ca8fac"), "Golf (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
+                            }
+                            */
+                            if (mGBDevice != null) {
+                                if (PebbleUtils.hasHealth(mGBDevice.getModel())) {
+                                    if (baseName.equals(PebbleProtocol.UUID_PEBBLE_HEALTH.toString())) {
+                                        cachedAppList.add(new GBDeviceApp(PebbleProtocol.UUID_PEBBLE_HEALTH, "Health (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
+                                        continue;
+                                    }
+                                }
+                                if (PebbleUtils.hasHRM(mGBDevice.getModel())) {
+                                    if (baseName.equals(PebbleProtocol.UUID_WORKOUT.toString())) {
+                                        cachedAppList.add(new GBDeviceApp(PebbleProtocol.UUID_WORKOUT, "Workout (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
+                                        continue;
+                                    }
+                                }
+                                if (PebbleUtils.getFwMajor(mGBDevice.getFirmwareVersion()) >= 4) {
+                                    if (baseName.equals("3af858c3-16cb-4561-91e7-f1ad2df8725f")) {
+                                        cachedAppList.add(new GBDeviceApp(UUID.fromString(baseName), "Kickstart (System)", "Pebble Inc.", "", GBDeviceApp.Type.WATCHFACE_SYSTEM));
+                                    }
+                                    if (baseName.equals(PebbleProtocol.UUID_WEATHER.toString())) {
+                                        cachedAppList.add(new GBDeviceApp(PebbleProtocol.UUID_WEATHER, "Weather (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
+                                    }
+                                }
+                            }
+                            if (uuids == null) {
+                                cachedAppList.add(new GBDeviceApp(UUID.fromString(baseName), baseName, "N/A", "", GBDeviceApp.Type.UNKNOWN));
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        return cachedAppList;
+    }
+
+    @Override
+    public void onActivityCreated(Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(ACTION_REFRESH_APPLIST);
+        filter.addAction(QHYBRID_ACTION_DOWNLOADED_FILE);
+
+        LocalBroadcastManager.getInstance(getContext()).registerReceiver(mReceiver, filter);
+
+        if (mCoordinator.supportsAppListFetching()) {
+            GBApplication.deviceService(mGBDevice).onAppInfoReq();
+            if (isCacheManager()) {
+                refreshList();
+            }
+        } else {
+            refreshList();
+        }
+
+        try {
+            File appCacheDir = mCoordinator.getAppCacheDir();
+            File appTempDir = new File(appCacheDir, "temp_sharing");
+            if (appTempDir.isDirectory()) {
+                for (File child : appTempDir.listFiles())
+                    child.delete();
+                appTempDir.delete();
+            }
+        } catch (IOException e) {
+            LOG.warn("Could not delete temporary app cache directory", e);
+        }
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+        mGBDevice = ((AppManagerActivity) getActivity()).getGBDevice();
+        mCoordinator = mGBDevice.getDeviceCoordinator();
+
+        final FloatingActionButton appListFab = ((FloatingActionButton) getActivity().findViewById(R.id.fab));
+        final FloatingActionButton appListFabNew = ((FloatingActionButton) getActivity().findViewById(R.id.fab_new));
+        watchfaceDesignerActivity = mCoordinator.getWatchfaceDesignerActivity();
+        View rootView = inflater.inflate(R.layout.activity_appmanager, container, false);
+
+        RecyclerView appListView = (RecyclerView) (rootView.findViewById(R.id.appListView));
+
+        appListView.addOnScrollListener(new RecyclerView.OnScrollListener() {
+            @Override
+            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
+                if (dy > 0) {
+                    appListFab.hide();
+                    appListFabNew.hide();
+                } else if (dy < 0) {
+                    appListFab.show();
+                    if (watchfaceDesignerActivity != null) {
+                        appListFabNew.show();
+                    }
+                }
+            }
+        });
+        appListView.setLayoutManager(new GridAutoFitLayoutManager(getActivity(), 300));
+        mGBDeviceAppAdapter = new GBDeviceAppAdapter(
+                appList,
+                R.layout.item_appmanager_watchapp,
+                this,
+                mCoordinator.supportsAppReordering() || isCacheManager()
+        );
+        appListView.setAdapter(mGBDeviceAppAdapter);
+
+        ItemTouchHelper.Callback appItemTouchHelperCallback = new AppItemTouchHelperCallback(mGBDeviceAppAdapter);
+        appManagementTouchHelper = new ItemTouchHelper(appItemTouchHelperCallback);
+
+        appManagementTouchHelper.attachToRecyclerView(appListView);
+
+        if ((watchfaceDesignerActivity != null) && (appListFabNew != null)) {
+            appListFabNew.setOnClickListener(new View.OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    Intent startIntent = new Intent(getContext(), watchfaceDesignerActivity);
+                    startIntent.putExtra(GBDevice.EXTRA_DEVICE, mGBDevice);
+                    getContext().startActivity(startIntent);
+                }
+            });
+            appListFabNew.show();
+        }
+
+        return rootView;
+    }
+
+    @Override
+    public void onActivityResult(int requestCode, int resultCode, Intent resultData) {
+        if (requestCode == CHILD_ACTIVITY_WATCHFACE_EDITOR) {
+            refreshList();
+        }
+    }
+
+    protected void sendOrderToDevice(String concatFilename) {
+        ArrayList<UUID> uuids = new ArrayList<>();
+        for (GBDeviceApp gbDeviceApp : mGBDeviceAppAdapter.getAppList()) {
+            uuids.add(gbDeviceApp.getUUID());
+        }
+        if (concatFilename != null) {
+            ArrayList<UUID> concatUuids = AppManagerActivity.getUuidsFromFile(concatFilename);
+            uuids.addAll(concatUuids);
+        }
+        GBApplication.deviceService(mGBDevice).onAppReorder(uuids.toArray(new UUID[uuids.size()]));
+    }
+
+    public void onItemClick(View view, GBDeviceApp deviceApp) {
+        openPopupMenu(view, deviceApp);
+    }
+
+    public boolean openPopupMenu(View view, GBDeviceApp deviceApp) {
+        PopupMenu popupMenu = new PopupMenu(getContext(), view);
+        popupMenu.getMenuInflater().inflate(R.menu.appmanager_context, popupMenu.getMenu());
+        Menu menu = popupMenu.getMenu();
+        final GBDeviceApp selectedApp = deviceApp;
+
+        if (!selectedApp.isOnDevice() || selectedApp.getType() != GBDeviceApp.Type.WATCHFACE) {
+            menu.removeItem(R.id.appmanager_watchface_activate);
+        }
+        if (!selectedApp.isOnDevice() || selectedApp.getType() != GBDeviceApp.Type.APP_GENERIC) {
+            menu.removeItem(R.id.appmanager_app_start);
+        }
+        if (!selectedApp.isInCache()) {
+            menu.removeItem(R.id.appmanager_app_edit);
+            menu.removeItem(R.id.appmanager_app_reinstall);
+            menu.removeItem(R.id.appmanager_app_share);
+            menu.removeItem(R.id.appmanager_app_delete_cache);
+        }
+        if (!PebbleProtocol.UUID_PEBBLE_HEALTH.equals(selectedApp.getUUID())) {
+            menu.removeItem(R.id.appmanager_health_activate);
+            menu.removeItem(R.id.appmanager_health_deactivate);
+        }
+        if (!PebbleProtocol.UUID_WORKOUT.equals(selectedApp.getUUID())) {
+            menu.removeItem(R.id.appmanager_hrm_activate);
+            menu.removeItem(R.id.appmanager_hrm_deactivate);
+        }
+        if (!PebbleProtocol.UUID_WEATHER.equals(selectedApp.getUUID())) {
+            menu.removeItem(R.id.appmanager_weather_activate);
+            menu.removeItem(R.id.appmanager_weather_deactivate);
+            menu.removeItem(R.id.appmanager_weather_install_provider);
+        }
+        if (selectedApp.getType() == GBDeviceApp.Type.APP_SYSTEM || selectedApp.getType() == GBDeviceApp.Type.WATCHFACE_SYSTEM) {
+            menu.removeItem(R.id.appmanager_app_delete);
+        }
+        if (!selectedApp.isConfigurable()) {
+            menu.removeItem(R.id.appmanager_app_configure);
+        }
+
+        if (PebbleProtocol.UUID_WEATHER.equals(selectedApp.getUUID())) {
+            PackageManager pm = getActivity().getPackageManager();
+            try {
+                pm.getPackageInfo("ru.gelin.android.weather.notification", PackageManager.GET_ACTIVITIES);
+                menu.removeItem(R.id.appmanager_weather_install_provider);
+            } catch (PackageManager.NameNotFoundException e) {
+                //menu.removeItem(R.id.appmanager_weather_activate);
+                //menu.removeItem(R.id.appmanager_weather_deactivate);
+            }
+        }
+
+        if ((mGBDevice.getType() != DeviceType.FOSSILQHYBRID) || (selectedApp.getType() != GBDeviceApp.Type.WATCHFACE)) {
+            menu.removeItem(R.id.appmanager_app_edit);
+        }
+        if ((mGBDevice.getType() != DeviceType.FOSSILQHYBRID) || (!selectedApp.isOnDevice()) || ((selectedApp.getType() != GBDeviceApp.Type.WATCHFACE) && (selectedApp.getType() != GBDeviceApp.Type.APP_GENERIC))) {
+            menu.removeItem(R.id.appmanager_app_download);
+        }
+        if (mGBDevice.getType() == DeviceType.FOSSILQHYBRID && selectedApp.getName().equals("workoutApp")) {
+            menu.removeItem(R.id.appmanager_app_delete);
+        }
+
+        if (mGBDevice.getType() == DeviceType.PEBBLE) {
+            switch (selectedApp.getType()) {
+                case WATCHFACE:
+                case APP_GENERIC:
+                case APP_ACTIVITYTRACKER:
+                    break;
+                default:
+                    menu.removeItem(R.id.appmanager_app_openinstore);
+            }
+        } else {
+            menu.removeItem(R.id.appmanager_app_openinstore);
+        }
+        //menu.setHeaderTitle(selectedApp.getName());
+        popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
+                                                 public boolean onMenuItemClick(MenuItem item) {
+                                                     return onContextItemSelected(item, selectedApp);
+                                                 }
+                                             }
+        );
+
+        popupMenu.show();
+        return true;
+    }
+
+    private boolean onContextItemSelected(final MenuItem item, final GBDeviceApp selectedApp) {
+        final File appCacheDir;
+        try {
+            appCacheDir = mCoordinator.getAppCacheDir();
+        } catch (final IOException e) {
+            LOG.warn("could not get external dir while trying to access app cache.");
+            return true;
+        }
+        final int itemId = item.getItemId();
+        if (itemId == R.id.appmanager_app_delete_cache) {
+            deleteAppConfirm(selectedApp, true);
+            return true;
+        } else if (itemId == R.id.appmanager_app_delete) {
+            deleteAppConfirm(selectedApp, false);
+            return true;
+        } else if (itemId == R.id.appmanager_app_start || itemId == R.id.appmanager_watchface_activate) {
+            GBApplication.deviceService(mGBDevice).onAppStart(selectedApp.getUUID(), true);
+            return true;
+        } else if (itemId == R.id.appmanager_app_download) {
+            GBApplication.deviceService(mGBDevice).onAppDownload(selectedApp.getUUID());
+            GB.toast(requireContext().getString(R.string.appmanager_download_started), Toast.LENGTH_LONG, GB.INFO);
+            return true;
+        } else if (itemId == R.id.appmanager_app_reinstall) {
+            final File cachePath = new File(appCacheDir, selectedApp.getUUID() + mCoordinator.getAppFileExtension());
+            GBApplication.deviceService(mGBDevice).onInstallApp(Uri.fromFile(cachePath));
+            return true;
+        } else if (itemId == R.id.appmanager_app_share) {
+            final File origFilePath = new File(appCacheDir, selectedApp.getUUID() + mCoordinator.getAppFileExtension());
+            final File appTempDir = new File(appCacheDir, "temp_sharing");
+            final File sharedAppFile = new File(appTempDir, selectedApp.getName() + mCoordinator.getAppFileExtension());
+            try {
+                appTempDir.mkdirs();
+                FileUtils.copyFile(origFilePath, sharedAppFile);
+            } catch (final IOException e) {
+                return true;
+            }
+            final Uri contentUri = FileProvider.getUriForFile(requireContext(), requireContext().getApplicationContext().getPackageName() + ".screenshot_provider", sharedAppFile);
+            final Intent shareIntent = new Intent(Intent.ACTION_SEND);
+            shareIntent.putExtra(Intent.EXTRA_STREAM, contentUri);
+            shareIntent.setType("*/*");
+            try {
+                startActivity(Intent.createChooser(shareIntent, null));
+            } catch (ActivityNotFoundException e) {
+                LOG.warn("Sharing watchface failed", e);
+            }
+            return true;
+        } else if (itemId == R.id.appmanager_health_activate) {
+            GBApplication.deviceService(mGBDevice).onInstallApp(Uri.parse("fake://health"));
+            return true;
+        } else if (itemId == R.id.appmanager_hrm_activate) {
+            GBApplication.deviceService(mGBDevice).onInstallApp(Uri.parse("fake://hrm"));
+            return true;
+        } else if (itemId == R.id.appmanager_weather_activate) {
+            GBApplication.deviceService(mGBDevice).onInstallApp(Uri.parse("fake://weather"));
+            return true;
+        } else if (itemId == R.id.appmanager_health_deactivate || itemId == R.id.appmanager_hrm_deactivate || itemId == R.id.appmanager_weather_deactivate) {
+            GBApplication.deviceService(mGBDevice).onAppDelete(selectedApp.getUUID());
+            return true;
+        } else if (itemId == R.id.appmanager_weather_install_provider) {
+            startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("https://f-droid.org/app/ru.gelin.android.weather.notification")));
+            return true;
+        } else if (itemId == R.id.appmanager_app_configure) {
+            GBApplication.deviceService(mGBDevice).onAppStart(selectedApp.getUUID(), true);
+
+            final Intent startIntent = new Intent(getContext().getApplicationContext(), ExternalPebbleJSActivity.class);
+            startIntent.putExtra(DeviceService.EXTRA_APP_UUID, selectedApp.getUUID());
+            startIntent.putExtra(GBDevice.EXTRA_DEVICE, mGBDevice);
+            startIntent.putExtra(ExternalPebbleJSActivity.SHOW_CONFIG, true);
+            startActivity(startIntent);
+            return true;
+        } else if (itemId == R.id.appmanager_app_openinstore) {
+            final String url = "https://apps.rebble.io/en_US/search/" + ((selectedApp.getType() == GBDeviceApp.Type.WATCHFACE) ? "watchfaces" : "watchapps") + "/1/?native=true&?query=" +  Uri.encode(selectedApp.getName());
+            final Intent intent = new Intent(Intent.ACTION_VIEW);
+            intent.setData(Uri.parse(url));
+            startActivity(intent);
+            return true;
+        } else if (itemId == R.id.appmanager_app_edit) {
+            final Intent editWatchfaceIntent = new Intent(getContext(), watchfaceDesignerActivity);
+            editWatchfaceIntent.putExtra(GBDevice.EXTRA_DEVICE, mGBDevice);
+            editWatchfaceIntent.putExtra(GBDevice.EXTRA_UUID, selectedApp.getUUID().toString());
+            startActivityForResult(editWatchfaceIntent, CHILD_ACTIVITY_WATCHFACE_EDITOR);
+            return true;
+        }
+
+        return super.onContextItemSelected(item);
+    }
+
+    private void deleteAppConfirm(final GBDeviceApp selectedApp, final boolean deleteFromCache) {
+        new MaterialAlertDialogBuilder(getContext())
+                .setTitle(R.string.Delete)
+                .setMessage(requireContext().getString(R.string.contact_delete_confirm_description, selectedApp.getName()))
+                .setIcon(R.drawable.ic_warning)
+                .setPositiveButton(android.R.string.yes, (dialog, whichButton) -> {
+                    if (deleteFromCache) {
+                        deleteAppFromCache(selectedApp);
+                    }
+                    deleteAppFromDevice(selectedApp);
+                })
+                .setNegativeButton(android.R.string.no, null)
+                .show();
+    }
+
+    private void deleteAppFromCache(final GBDeviceApp selectedApp) {
+        final File appCacheDir;
+        try {
+            appCacheDir = mCoordinator.getAppCacheDir();
+        } catch (final IOException e) {
+            LOG.warn("Could not get external dir while trying to access app cache", e);
+            return;
+        }
+
+        String baseName = selectedApp.getUUID().toString();
+        String[] suffixToDelete = new String[]{mCoordinator.getAppFileExtension(), ".json", "_config.js", "_preset.json", ".png", "_preview.png", "_bg.png"};
+        for (String suffix : suffixToDelete) {
+            File fileToDelete = new File(appCacheDir, baseName + suffix);
+            if (!fileToDelete.delete()) {
+                LOG.warn("Could not delete file from app cache: {}", fileToDelete);
+            } else {
+                LOG.debug("Deleted from app cache: {}", fileToDelete);
+            }
+        }
+        AppManagerActivity.deleteFromAppOrderFile(getSortFilename(), selectedApp.getUUID()); // FIXME: only if successful
+        Intent refreshIntent = new Intent(AbstractAppManagerFragment.ACTION_REFRESH_APPLIST);
+        LocalBroadcastManager.getInstance(requireContext()).sendBroadcast(refreshIntent);
+    }
+
+    private void deleteAppFromDevice(final GBDeviceApp selectedApp) {
+        if (mCoordinator.supportsAppReordering()) {
+            AppManagerActivity.deleteFromAppOrderFile(mGBDevice.getAddress() + ".watchapps", selectedApp.getUUID()); // FIXME: only if successful
+            AppManagerActivity.deleteFromAppOrderFile(mGBDevice.getAddress() + ".watchfaces", selectedApp.getUUID()); // FIXME: only if successful
+            Intent refreshIntent = new Intent(AbstractAppManagerFragment.ACTION_REFRESH_APPLIST);
+            LocalBroadcastManager.getInstance(requireContext()).sendBroadcast(refreshIntent);
+        }
+
+        GBApplication.deviceService(mGBDevice).onAppDelete(selectedApp.getUUID());
+    }
+
+    /**
+     * Sort an app list by the UUIDs in the sort filename.
+     *
+     * @param appList the app list to sort in-place
+     */
+    protected void sortAppList(final List<GBDeviceApp> appList) {
+        final ArrayList<UUID> uuids = AppManagerActivity.getUuidsFromFile(getSortFilename());
+        final Map<UUID, Integer> uuidPosMap = new HashMap<>();
+        for (int i = 0; i < uuids.size(); i++) {
+            uuidPosMap.put(uuids.get(i), i);
+        }
+
+        Collections.sort(appList, (a1, a2) -> {
+            final Integer pos1 = uuidPosMap.get(a1.getUUID());
+            final Integer pos2 = uuidPosMap.get(a2.getUUID());
+
+            if (pos1 != null && pos2 != null) return Integer.compare(pos1, pos2);
+            if (pos1 == null && pos2 == null) return a1.getName().compareToIgnoreCase(a2.getName());
+            if (pos1 != null) return -1;
+
+            return 1;
+        });
+    }
+
+    @Override
+    public void onDestroy() {
+        LocalBroadcastManager.getInstance(getContext()).unregisterReceiver(mReceiver);
+        super.onDestroy();
+    }
+
+    public class AppItemTouchHelperCallback extends ItemTouchHelper.Callback {
+
+        private final GBDeviceAppAdapter gbDeviceAppAdapter;
+
+        public AppItemTouchHelperCallback(GBDeviceAppAdapter gbDeviceAppAdapter) {
+            this.gbDeviceAppAdapter = gbDeviceAppAdapter;
+        }
+
+        @Override
+        public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
+            if (!mCoordinator.supportsAppReordering() && !isCacheManager()) {
+                return 0;
+            }
+            //we only support up and down movement and only for moving, not for swiping apps away
+            return makeMovementFlags(ItemTouchHelper.UP | ItemTouchHelper.DOWN, 0);
+        }
+
+        @Override
+        public boolean isLongPressDragEnabled() {
+            return false;
+        }
+
+        @Override
+        public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder source, RecyclerView.ViewHolder target) {
+            gbDeviceAppAdapter.onItemMove(source.getAdapterPosition(), target.getAdapterPosition());
+            return true;
+        }
+
+        @Override
+        public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
+            //nothing to do
+        }
+
+        @Override
+        public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
+            super.clearView(recyclerView, viewHolder);
+            onChangedAppOrder();
+        }
+
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/appmanager/AppManagerActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/appmanager/AppManagerActivity.java
new file mode 100644
index 0000000..2398e91
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/appmanager/AppManagerActivity.java
@@ -0,0 +1,217 @@
+/*  Copyright (C) 2016-2024 Andreas Shimokawa, Arjan Schrijver, Carsten
+    Pfeiffer, Daniele Gobbetti, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities.appmanager;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.MenuItem;
+import android.view.View;
+
+import com.google.android.material.floatingactionbutton.FloatingActionButton;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+import androidx.core.app.NavUtils;
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentManager;
+import androidx.viewpager.widget.ViewPager;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.AbstractFragmentPagerAdapter;
+import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBFragmentActivity;
+import nodomain.freeyourgadget.gadgetbridge.activities.FwAppInstallerActivity;
+import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
+
+
+public class AppManagerActivity extends AbstractGBFragmentActivity {
+
+    private static final Logger LOG = LoggerFactory.getLogger(AppManagerActivity.class);
+    private int READ_REQUEST_CODE = 42;
+
+    private GBDevice mGBDevice = null;
+
+    private List<String> enabledTabsList;
+
+    public GBDevice getGBDevice() {
+        return mGBDevice;
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.activity_fragmentappmanager);
+
+        Bundle extras = getIntent().getExtras();
+        if (extras != null) {
+            mGBDevice = extras.getParcelable(GBDevice.EXTRA_DEVICE);
+        }
+        if (mGBDevice == null) {
+            throw new IllegalArgumentException("Must provide a device when invoking this activity");
+        }
+
+        final DeviceCoordinator coordinator = mGBDevice.getDeviceCoordinator();
+
+        enabledTabsList = new ArrayList<>();
+
+        if (coordinator.supportsCachedAppManagement(mGBDevice)) {
+            enabledTabsList.add("cache");
+        }
+        if (coordinator.supportsInstalledAppManagement(mGBDevice)) {
+            enabledTabsList.add("apps");
+        }
+        if (coordinator.supportsWatchfaceManagement(mGBDevice)) {
+            enabledTabsList.add("watchfaces");
+        }
+
+        FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
+        assert fab != null;
+        fab.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
+                intent.addCategory(Intent.CATEGORY_OPENABLE);
+                intent.setType("*/*");
+                startActivityForResult(intent, READ_REQUEST_CODE);
+            }
+        });
+
+        // Set up the ViewPager with the sections adapter.
+        ViewPager viewPager = (ViewPager) findViewById(R.id.appmanager_pager);
+        if (viewPager != null) {
+            viewPager.setAdapter(getPagerAdapter());
+        }
+    }
+
+    @Override
+    protected AbstractFragmentPagerAdapter createFragmentPagerAdapter(FragmentManager fragmentManager) {
+        return new SectionsPagerAdapter(fragmentManager);
+    }
+
+    public static synchronized void deleteFromAppOrderFile(String filename, UUID uuid) {
+        ArrayList<UUID> uuids = getUuidsFromFile(filename);
+        uuids.remove(uuid);
+        rewriteAppOrderFile(filename, uuids);
+    }
+
+    public class SectionsPagerAdapter extends AbstractFragmentPagerAdapter {
+
+        SectionsPagerAdapter(FragmentManager fm) {
+            super(fm);
+        }
+
+        @Override
+        public Fragment getItem(int position) {
+            // getItem is called to instantiate the fragment for the given page.
+            switch (enabledTabsList.get(position)) {
+                case "cache":
+                    return new AppManagerFragmentCache();
+                case "apps":
+                    return new AppManagerFragmentInstalledApps();
+                case "watchfaces":
+                    return new AppManagerFragmentInstalledWatchfaces();
+            }
+            return null;
+        }
+
+        @Override
+        public int getCount() {
+            return enabledTabsList.toArray().length;
+        }
+
+        @Override
+        public CharSequence getPageTitle(int position) {
+            switch (enabledTabsList.get(position)) {
+                case "cache":
+                    return getString(R.string.appmanager_cached_watchapps_watchfaces);
+                case "apps":
+                    return getString(R.string.appmanager_installed_watchapps);
+                case "watchfaces":
+                    return getString(R.string.appmanager_installed_watchfaces);
+            }
+            return super.getPageTitle(position);
+        }
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(final MenuItem item) {
+        final int itemId = item.getItemId();
+        if (itemId == android.R.id.home) {
+            NavUtils.navigateUpFromSameTask(this);
+            return true;
+        }
+        return super.onOptionsItemSelected(item);
+    }
+
+
+    static synchronized void rewriteAppOrderFile(String filename, List<UUID> uuids) {
+        try (BufferedWriter out = new BufferedWriter(new FileWriter(FileUtils.getExternalFilesDir() + "/" + filename))) {
+            for (UUID uuid : uuids) {
+                out.write(uuid.toString());
+                out.newLine();
+            }
+        } catch (IOException e) {
+            LOG.warn("can't write app order to file!");
+        }
+    }
+
+    synchronized public static void addToAppOrderFile(String filename, UUID uuid) {
+        ArrayList<UUID> uuids = getUuidsFromFile(filename);
+        if (!uuids.contains(uuid)) {
+            uuids.add(uuid);
+            rewriteAppOrderFile(filename, uuids);
+        }
+    }
+
+    static synchronized ArrayList<UUID> getUuidsFromFile(String filename) {
+        ArrayList<UUID> uuids = new ArrayList<>();
+        try (BufferedReader in = new BufferedReader(new FileReader(FileUtils.getExternalFilesDir() + "/" + filename))) {
+            String line;
+            while ((line = in.readLine()) != null) {
+                uuids.add(UUID.fromString(line));
+            }
+        } catch (IOException e) {
+            LOG.warn("could not read sort file");
+        }
+        return uuids;
+    }
+
+    @Override
+    public void onActivityResult(int requestCode, int resultCode, Intent resultData) {
+        super.onActivityResult(requestCode, resultCode, resultData);
+        if (requestCode == READ_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
+            Intent startIntent = new Intent(AppManagerActivity.this, FwAppInstallerActivity.class);
+            startIntent.setAction(Intent.ACTION_VIEW);
+            startIntent.setDataAndType(resultData.getData(), null);
+            startActivity(startIntent);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/appmanager/AppManagerFragmentCache.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/appmanager/AppManagerFragmentCache.java
new file mode 100644
index 0000000..c603dfb
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/appmanager/AppManagerFragmentCache.java
@@ -0,0 +1,51 @@
+/*  Copyright (C) 2016-2024 Andreas Shimokawa, Arjan Schrijver, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities.appmanager;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp;
+
+public class AppManagerFragmentCache extends AbstractAppManagerFragment {
+    @Override
+    public void refreshList() {
+        appList.clear();
+        final List<GBDeviceApp> cachedApps = getCachedApps(null);
+        sortAppList(cachedApps);
+        appList.addAll(cachedApps);
+    }
+
+    @Override
+    protected boolean isCacheManager() {
+        return true;
+    }
+
+    @Override
+    protected List<GBDeviceApp> getSystemAppsInCategory() {
+        return null;
+    }
+
+    @Override
+    public String getSortFilename() {
+        return mCoordinator.getAppCacheSortFilename();
+    }
+
+    @Override
+    protected boolean filterApp(GBDeviceApp gbDeviceApp) {
+        return true;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/appmanager/AppManagerFragmentInstalledApps.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/appmanager/AppManagerFragmentInstalledApps.java
new file mode 100644
index 0000000..2f07bf9
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/appmanager/AppManagerFragmentInstalledApps.java
@@ -0,0 +1,83 @@
+/*  Copyright (C) 2016-2024 Andreas Shimokawa, Arjan Schrijver, Daniele
+    Gobbetti
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities.appmanager;
+
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.PebbleProtocol;
+import nodomain.freeyourgadget.gadgetbridge.util.PebbleUtils;
+
+public class AppManagerFragmentInstalledApps extends AbstractAppManagerFragment {
+
+    @Override
+    protected List<GBDeviceApp> getSystemAppsInCategory() {
+        List<GBDeviceApp> systemApps = new ArrayList<>();
+
+        if (mGBDevice.getType() == DeviceType.PEBBLE) {
+            //systemApps.add(new GBDeviceApp(UUID.fromString("4dab81a6-d2fc-458a-992c-7a1f3b96a970"), "Sports (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
+            //systemApps.add(new GBDeviceApp(UUID.fromString("cf1e816a-9db0-4511-bbb8-f60c48ca8fac"), "Golf (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
+            systemApps.add(new GBDeviceApp(UUID.fromString("1f03293d-47af-4f28-b960-f2b02a6dd757"), "Music (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
+            systemApps.add(new GBDeviceApp(PebbleProtocol.UUID_NOTIFICATIONS, "Notifications (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
+            systemApps.add(new GBDeviceApp(UUID.fromString("67a32d95-ef69-46d4-a0b9-854cc62f97f9"), "Alarms (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
+            systemApps.add(new GBDeviceApp(UUID.fromString("18e443ce-38fd-47c8-84d5-6d0c775fbe55"), "Watchfaces (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
+
+            if (mGBDevice != null) {
+                if (PebbleUtils.hasHealth(mGBDevice.getModel())) {
+                    systemApps.add(new GBDeviceApp(UUID.fromString("0863fc6a-66c5-4f62-ab8a-82ed00a98b5d"), "Send Text (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
+                    systemApps.add(new GBDeviceApp(PebbleProtocol.UUID_PEBBLE_HEALTH, "Health (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
+                }
+                if (PebbleUtils.hasHRM(mGBDevice.getModel())) {
+                    systemApps.add(new GBDeviceApp(PebbleProtocol.UUID_WORKOUT, "Workout (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
+                }
+                if (PebbleUtils.getFwMajor(mGBDevice.getFirmwareVersion()) >= 4) {
+                    systemApps.add(new GBDeviceApp(PebbleProtocol.UUID_WEATHER, "Weather (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
+                }
+            }
+        } else if (mGBDevice.getType() == DeviceType.FOSSILQHYBRID) {
+            systemApps.add(new GBDeviceApp(UUID.nameUUIDFromBytes("workoutApp".getBytes(StandardCharsets.UTF_8)), "workoutApp", "", "", GBDeviceApp.Type.APP_ACTIVITYTRACKER));
+        }
+
+        return systemApps;
+    }
+
+    @Override
+    protected boolean isCacheManager() {
+        return false;
+    }
+
+    @Override
+    protected String getSortFilename() {
+        return mGBDevice.getAddress() + ".watchapps";
+    }
+
+    @Override
+    protected void onChangedAppOrder() {
+        super.onChangedAppOrder();
+        sendOrderToDevice(mGBDevice.getAddress() + ".watchfaces");
+    }
+
+    @Override
+    protected boolean filterApp(GBDeviceApp gbDeviceApp) {
+        return gbDeviceApp.getType() == GBDeviceApp.Type.APP_ACTIVITYTRACKER || gbDeviceApp.getType() == GBDeviceApp.Type.APP_GENERIC;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/appmanager/AppManagerFragmentInstalledWatchfaces.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/appmanager/AppManagerFragmentInstalledWatchfaces.java
new file mode 100644
index 0000000..20faeb8
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/appmanager/AppManagerFragmentInstalledWatchfaces.java
@@ -0,0 +1,62 @@
+/*  Copyright (C) 2016-2024 Andreas Shimokawa, Arjan Schrijver
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities.appmanager;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+
+public class AppManagerFragmentInstalledWatchfaces extends AbstractAppManagerFragment {
+
+    @Override
+    protected List<GBDeviceApp> getSystemAppsInCategory() {
+        List<GBDeviceApp> systemWatchfaces = new ArrayList<>();
+        if (mGBDevice.getType() != DeviceType.PEBBLE) {
+            return systemWatchfaces;
+        }
+        systemWatchfaces.add(new GBDeviceApp(UUID.fromString("8f3c8686-31a1-4f5f-91f5-01600c9bdc59"), "Tic Toc (System)", "Pebble Inc.", "", GBDeviceApp.Type.WATCHFACE_SYSTEM));
+        systemWatchfaces.add(new GBDeviceApp(UUID.fromString("3af858c3-16cb-4561-91e7-f1ad2df8725f"), "Kickstart (System)", "Pebble Inc.", "", GBDeviceApp.Type.WATCHFACE_SYSTEM));
+        return systemWatchfaces;
+    }
+
+    @Override
+    protected boolean isCacheManager() {
+        return false;
+    }
+
+    @Override
+    protected String getSortFilename() {
+        return mGBDevice.getAddress() + ".watchfaces";
+    }
+
+    @Override
+    protected void onChangedAppOrder() {
+        super.onChangedAppOrder();
+        sendOrderToDevice(mGBDevice.getAddress() + ".watchapps");
+    }
+
+    @Override
+    protected boolean filterApp(GBDeviceApp gbDeviceApp) {
+        if (gbDeviceApp.getType() == GBDeviceApp.Type.WATCHFACE) {
+            return true;
+        }
+        return false;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/AbstractActivityChartFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/AbstractActivityChartFragment.java
new file mode 100644
index 0000000..1fdccd5
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/AbstractActivityChartFragment.java
@@ -0,0 +1,462 @@
+/*  Copyright (C) 2023-2024 Daniel Dakhno, Jos茅 Rebelo, a0z
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities.charts;
+
+import android.util.TypedValue;
+
+import androidx.core.content.ContextCompat;
+
+import com.github.mikephil.charting.components.YAxis;
+import com.github.mikephil.charting.data.Entry;
+import com.github.mikephil.charting.data.LineData;
+import com.github.mikephil.charting.data.LineDataSet;
+import com.github.mikephil.charting.formatter.ValueFormatter;
+import com.github.mikephil.charting.interfaces.datasets.ILineDataSet;
+
+import org.apache.commons.lang3.NotImplementedException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.HeartRateUtils;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.AbstractActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+public abstract class AbstractActivityChartFragment<D extends ChartsData> extends AbstractChartFragment<D>  {
+    private static final Logger LOG = LoggerFactory.getLogger(AbstractActivityChartFragment.class);
+
+    public static final float Y_VALUE_DEEP_SLEEP = 0.01f;
+
+    public boolean supportsHeartrate(GBDevice device) {
+        DeviceCoordinator coordinator = device.getDeviceCoordinator();
+        return coordinator.supportsHeartRateMeasurement(device);
+    }
+
+    public boolean supportsRemSleep(GBDevice device) {
+        DeviceCoordinator coordinator = device.getDeviceCoordinator();
+        return coordinator.supportsRemSleep();
+    }
+
+    public boolean supportsAwakeSleep(GBDevice device) {
+        DeviceCoordinator coordinator = device.getDeviceCoordinator();
+        return coordinator.supportsAwakeSleep();
+    }
+
+    protected static final class ActivityConfig {
+        public final ActivityKind type;
+        public final String label;
+        public final Integer color;
+
+        public ActivityConfig(ActivityKind kind, String label, Integer color) {
+            this.type = kind;
+            this.label = label;
+            this.color = color;
+        }
+    }
+
+    protected ActivityConfig akActivity;
+    protected ActivityConfig akLightSleep;
+    protected ActivityConfig akDeepSleep;
+    protected ActivityConfig akRemSleep;
+    protected ActivityConfig akAwakeSleep;
+    protected ActivityConfig akNotWorn;
+
+    protected int BACKGROUND_COLOR;
+    protected int DESCRIPTION_COLOR;
+    protected int CHART_TEXT_COLOR;
+    protected int LEGEND_TEXT_COLOR;
+    protected int HEARTRATE_COLOR;
+    protected int HEARTRATE_FILL_COLOR;
+    protected int AK_ACTIVITY_COLOR;
+    protected int AK_DEEP_SLEEP_COLOR;
+    protected int AK_REM_SLEEP_COLOR;
+    protected int AK_AWAKE_SLEEP_COLOR;
+    protected int AK_LIGHT_SLEEP_COLOR;
+    protected int AK_NOT_WORN_COLOR;
+
+    protected String HEARTRATE_LABEL;
+    protected String HEARTRATE_AVERAGE_LABEL;
+
+    @Override
+    protected void init() {
+        Prefs prefs = GBApplication.getPrefs();
+        TypedValue runningColor = new TypedValue();
+        BACKGROUND_COLOR = GBApplication.getBackgroundColor(getContext());
+        LEGEND_TEXT_COLOR = DESCRIPTION_COLOR = GBApplication.getTextColor(getContext());
+        CHART_TEXT_COLOR = GBApplication.getSecondaryTextColor(getContext());
+        if (prefs.getBoolean("chart_heartrate_color", false)) {
+            HEARTRATE_COLOR = ContextCompat.getColor(getContext(), R.color.chart_heartrate_alternative);
+        }else{
+            HEARTRATE_COLOR = ContextCompat.getColor(getContext(), R.color.chart_heartrate);
+        }
+        HEARTRATE_FILL_COLOR = ContextCompat.getColor(getContext(), R.color.chart_heartrate_fill);
+
+        getContext().getTheme().resolveAttribute(R.attr.chart_activity, runningColor, true);
+        AK_ACTIVITY_COLOR = runningColor.data;
+        getContext().getTheme().resolveAttribute(R.attr.chart_deep_sleep, runningColor, true);
+        AK_DEEP_SLEEP_COLOR = runningColor.data;
+        getContext().getTheme().resolveAttribute(R.attr.chart_light_sleep, runningColor, true);
+        AK_LIGHT_SLEEP_COLOR = runningColor.data;
+        getContext().getTheme().resolveAttribute(R.attr.chart_rem_sleep, runningColor, true);
+        AK_REM_SLEEP_COLOR = runningColor.data;
+        getContext().getTheme().resolveAttribute(R.attr.chart_awake_sleep, runningColor, true);
+        AK_AWAKE_SLEEP_COLOR = runningColor.data;
+        getContext().getTheme().resolveAttribute(R.attr.chart_not_worn, runningColor, true);
+        AK_NOT_WORN_COLOR = runningColor.data;
+
+        HEARTRATE_LABEL = getContext().getString(R.string.charts_legend_heartrate);
+        HEARTRATE_AVERAGE_LABEL = getContext().getString(R.string.charts_legend_heartrate_average);
+
+        akActivity = new ActivityConfig(ActivityKind.ACTIVITY, getString(R.string.abstract_chart_fragment_kind_activity), AK_ACTIVITY_COLOR);
+        akLightSleep = new ActivityConfig(ActivityKind.LIGHT_SLEEP, getString(R.string.abstract_chart_fragment_kind_light_sleep), AK_LIGHT_SLEEP_COLOR);
+        akDeepSleep = new ActivityConfig(ActivityKind.DEEP_SLEEP, getString(R.string.abstract_chart_fragment_kind_deep_sleep), AK_DEEP_SLEEP_COLOR);
+        akRemSleep = new ActivityConfig(ActivityKind.REM_SLEEP, getString(R.string.abstract_chart_fragment_kind_rem_sleep), AK_REM_SLEEP_COLOR);
+        akAwakeSleep = new ActivityConfig(ActivityKind.REM_SLEEP, getString(R.string.abstract_chart_fragment_kind_awake_sleep), AK_AWAKE_SLEEP_COLOR);
+        akNotWorn = new ActivityConfig(ActivityKind.NOT_WORN, getString(R.string.abstract_chart_fragment_kind_not_worn), AK_NOT_WORN_COLOR);
+    }
+
+    @Override
+    protected boolean isSingleDay() {
+        return false;
+    }
+
+    protected Integer getColorFor(ActivityKind activityKind) {
+        switch (activityKind) {
+            case DEEP_SLEEP:
+                return akDeepSleep.color;
+            case LIGHT_SLEEP:
+                return akLightSleep.color;
+            case REM_SLEEP:
+                return akRemSleep.color;
+            case AWAKE_SLEEP:
+                return akAwakeSleep.color;
+            case ACTIVITY:
+                return akActivity.color;
+        }
+        return akActivity.color;
+    }
+
+    protected SampleProvider<? extends AbstractActivitySample> getProvider(DBHandler db, GBDevice device) {
+        DeviceCoordinator coordinator = device.getDeviceCoordinator();
+        return coordinator.getSampleProvider(device, db.getDaoSession());
+    }
+
+    /**
+     * Returns all kinds of samples for the given device.
+     * To be called from a background thread.
+     *
+     * @param device
+     * @param tsFrom
+     * @param tsTo
+     */
+    protected List<? extends ActivitySample> getAllSamples(DBHandler db, GBDevice device, int tsFrom, int tsTo) {
+        SampleProvider<? extends ActivitySample> provider = getProvider(db, device);
+        return provider.getAllActivitySamples(tsFrom, tsTo);
+    }
+
+    protected List<? extends ActivitySample> getAllSamplesHighRes(DBHandler db, GBDevice device, int tsFrom, int tsTo) {
+        SampleProvider<? extends ActivitySample> provider = getProvider(db, device);
+        // Only retrieve if the provider signals it has high res data, otherwise it is useless
+        if (provider.hasHighResData())
+            return provider.getAllActivitySamplesHighRes(tsFrom, tsTo);
+        return null;
+    }
+
+    protected List<? extends AbstractActivitySample> getActivitySamples(DBHandler db, GBDevice device, int tsFrom, int tsTo) {
+        SampleProvider<? extends AbstractActivitySample> provider = getProvider(db, device);
+        return provider.getActivitySamples(tsFrom, tsTo);
+    }
+
+    public DefaultChartsData<LineData> refresh(GBDevice gbDevice, List<? extends ActivitySample> samples) {
+        // If there is no high res samples, all the samples are high res samples
+        return refresh(gbDevice, samples, samples);
+    }
+
+    public DefaultChartsData<LineData> refresh(GBDevice gbDevice, List<? extends ActivitySample> samples, List<? extends ActivitySample> highResSamples) {
+        TimestampTranslation tsTranslation = new TimestampTranslation();
+        LOG.info("{}: number of samples: {}", getTitle(), samples.size());
+        LOG.info("{}: number of high res samples: {}", getTitle(), highResSamples.size());
+        LineData lineData;
+
+        if (samples.isEmpty()) {
+            lineData = new LineData();
+            ValueFormatter xValueFormatter = new SampleXLabelFormatter(tsTranslation, "HH:mm");
+            return new DefaultChartsData<>(lineData, xValueFormatter);
+        }
+
+        ActivityKind last_type = ActivityKind.UNKNOWN;
+        float last_value = 0;
+
+        int numEntries = samples.size();
+        List<List<Entry>> entries = new ArrayList<>();
+        for (int i = 0; i < 6; i++) {
+            entries.add(new ArrayList<>());
+        }
+
+        for (int i = 0; i < numEntries; i++) {
+            ActivitySample sample = samples.get(i);
+            ActivityKind type = sample.getKind();
+            int ts = tsTranslation.shorten(sample.getTimestamp());
+            final float value;
+            if (type != ActivityKind.NOT_WORN) {
+                if (ActivityKind.isSleep(type) && sample.getIntensity() < 0) {
+                    switch (type) {
+                        case SLEEP_ANY:
+                        case AWAKE_SLEEP:
+                            value = 0.25f;
+                            break;
+                        case DEEP_SLEEP:
+                            value = 0.10f;
+                            break;
+                        case LIGHT_SLEEP:
+                            value = 0.15f;
+                            break;
+                        case REM_SLEEP:
+                            value = 0.20f;
+                            break;
+                        default:
+                            value = Y_VALUE_DEEP_SLEEP;
+                            break;
+                    }
+                } else {
+                    value = sample.getIntensity();
+                }
+            } else {
+                value = Y_VALUE_DEEP_SLEEP;
+            }
+
+            // do not interpolate NOT_WORN on any side
+            boolean interpolate = !(last_type == ActivityKind.NOT_WORN || type == ActivityKind.NOT_WORN);
+            float interpolation_value = interpolate ? value : last_value;
+
+            // filled charts
+            int index = getIndexOfActivity(type);
+            int last_index = getIndexOfActivity(last_type);
+            if (last_type != type) {
+                entries.get(index).add(createLineEntry(0, ts));
+                entries.get(last_index).add(createLineEntry(interpolation_value, ts));
+                entries.get(last_index).add(createLineEntry(0, ts));
+            }
+            entries.get(index).add(createLineEntry(value, ts));
+
+            last_type = type;
+            last_value = value;
+        }
+
+        boolean hr = supportsHeartrate(gbDevice);
+        final List<Entry> heartRateLineEntries = new ArrayList<>();
+        final List<ILineDataSet> heartRateDataSets = new ArrayList<>();
+        int lastTsShorten = 0;
+        HeartRateUtils heartRateUtilsInstance = HeartRateUtils.getInstance();
+
+        // Currently only for HR
+        if (hr) {
+            for (ActivitySample sample : highResSamples) {
+                if (sample.getKind() != ActivityKind.NOT_WORN && heartRateUtilsInstance.isValidHeartRateValue(sample.getHeartRate())) {
+                    int tsShorten = tsTranslation.shorten(sample.getTimestamp());
+                    if (lastTsShorten == 0 || (tsShorten - lastTsShorten) <= 60 * HeartRateUtils.MAX_HR_MEASUREMENTS_GAP_MINUTES) {
+                        heartRateLineEntries.add(new Entry(tsShorten, sample.getHeartRate()));
+                    } else {
+                        if (!heartRateLineEntries.isEmpty()) {
+                            List<Entry> clone = new ArrayList<>(heartRateLineEntries.size());
+                            clone.addAll(heartRateLineEntries);
+                            heartRateDataSets.add(createHeartrateSet(clone, "Heart Rate"));
+                            heartRateLineEntries.clear();
+                        }
+                    }
+                    lastTsShorten = tsShorten;
+                    heartRateLineEntries.add(new Entry(tsShorten, sample.getHeartRate()));
+                }
+            }
+        }
+        if (!heartRateLineEntries.isEmpty()) {
+            heartRateDataSets.add(createHeartrateSet(heartRateLineEntries, "Heart Rate"));
+        }
+
+        // convert Entry Lists to Datasets
+        List<ILineDataSet> lineDataSets = new ArrayList<>();
+
+        lineDataSets.add(createDataSet(
+            entries.get(getIndexOfActivity(ActivityKind.ACTIVITY)), akActivity.color, "Activity"
+        ));
+        lineDataSets.add(createDataSet(
+            entries.get(getIndexOfActivity(ActivityKind.DEEP_SLEEP)), akDeepSleep.color, "Deep Sleep"
+        ));
+        lineDataSets.add(createDataSet(
+            entries.get(getIndexOfActivity(ActivityKind.LIGHT_SLEEP)), akLightSleep.color, "Light Sleep"
+        ));
+        lineDataSets.add(createDataSet(
+            entries.get(getIndexOfActivity(ActivityKind.NOT_WORN)), akNotWorn.color, "Not worn"
+        ));
+
+        if (supportsRemSleep(gbDevice)) {
+            lineDataSets.add(createDataSet(
+                entries.get(getIndexOfActivity(ActivityKind.REM_SLEEP)), akRemSleep.color, "REM Sleep"
+            ));
+        }
+        if (supportsAwakeSleep(gbDevice)) {
+            lineDataSets.add(createDataSet(
+                entries.get(getIndexOfActivity(ActivityKind.AWAKE_SLEEP)), akAwakeSleep.color, "Awake Sleep"
+            ));
+        }
+        if (hr && !heartRateDataSets.isEmpty()) {
+            lineDataSets.addAll(heartRateDataSets);
+        }
+
+        lineData = new LineData(lineDataSets);
+
+        ValueFormatter xValueFormatter = new SampleXLabelFormatter(tsTranslation, "HH:mm");
+        return new DefaultChartsData<>(lineData, xValueFormatter);
+    }
+
+    protected int getIndexOfActivity(ActivityKind kind) {
+        switch (kind) {
+            case DEEP_SLEEP: return 0;
+            case LIGHT_SLEEP: return 1;
+            case REM_SLEEP: return 2;
+            case AWAKE_SLEEP: return 3;
+            case NOT_WORN: return 4;
+            default: return 5; // treated as ActivityKind.ACTIVITY
+        }
+    }
+
+    protected Entry createLineEntry(float value, int xValue) {
+        return new Entry(xValue, value);
+    }
+
+    protected LineDataSet createDataSet(List<Entry> values, Integer color, String label) {
+        LineDataSet set1 = new LineDataSet(values, label);
+        set1.setColor(color);
+        set1.setDrawFilled(true);
+        set1.setDrawCircles(false);
+        set1.setFillColor(color);
+        set1.setFillAlpha(255);
+        set1.setDrawValues(false);
+        set1.setValueTextColor(CHART_TEXT_COLOR);
+        set1.setAxisDependency(YAxis.AxisDependency.LEFT);
+        return set1;
+    }
+
+    protected LineDataSet createHeartrateSet(List<Entry> values, String label) {
+        LineDataSet set1 = new LineDataSet(values, label);
+        set1.setLineWidth(2.2f);
+        set1.setColor(HEARTRATE_COLOR);
+        set1.setMode(LineDataSet.Mode.HORIZONTAL_BEZIER);
+        set1.setCubicIntensity(0.1f);
+        set1.setDrawCircles(false);
+        set1.setDrawValues(true);
+        set1.setValueTextColor(CHART_TEXT_COLOR);
+        set1.setAxisDependency(YAxis.AxisDependency.RIGHT);
+        return set1;
+    }
+
+    /**
+     * Implement this to supply the samples to be displayed.
+     */
+    protected abstract List<? extends ActivitySample> getSamples(DBHandler db, GBDevice device, int tsFrom, int tsTo);
+
+    /**
+     * Implement this to supply high resolution data
+     */
+    protected List<? extends ActivitySample> getSamplesHighRes(DBHandler db, GBDevice device, int tsFrom, int tsTo) {
+        throw new NotImplementedException("High resolution samples have not been implemented for this chart.");
+    }
+
+    protected List<? extends ActivitySample> getSamples(DBHandler db, GBDevice device) {
+        int tsStart = getTSStart();
+        int tsEnd = getTSEnd();
+        List<ActivitySample> samples = (List<ActivitySample>) getSamples(db, device, tsStart, tsEnd);
+        ensureStartAndEndSamples(samples, tsStart, tsEnd);
+//        List<ActivitySample> samples2 = new ArrayList<>();
+//        int min = Math.min(samples.size(), 10);
+//        int min = Math.min(samples.size(), 10);
+//        for (int i = 0; i < min; i++) {
+//            samples2.add(samples.get(i));
+//        }
+//        return samples2;
+        return samples;
+    }
+
+    protected List<? extends ActivitySample> getSamplesHighRes(DBHandler db, GBDevice device) {
+        int tsStart = getTSStart();
+        int tsEnd = getTSEnd();
+        return getSamplesHighRes(db, device, tsStart, tsEnd);
+    }
+
+    protected List<? extends ActivitySample> getSamplesofSleep(DBHandler db, GBDevice device) {
+        int SLEEP_HOUR_LIMIT = 12;
+
+        int tsStart = getTSStart();
+        Calendar day = GregorianCalendar.getInstance();
+        day.setTimeInMillis(tsStart * 1000L);
+        day.set(Calendar.HOUR_OF_DAY, SLEEP_HOUR_LIMIT);
+        day.set(Calendar.MINUTE, 0);
+        day.set(Calendar.SECOND, 0);
+        tsStart = toTimestamp(day.getTime());
+
+        int tsEnd = getTSEnd();
+        day.setTimeInMillis(tsEnd* 1000L);
+        day.set(Calendar.HOUR_OF_DAY, SLEEP_HOUR_LIMIT);
+        day.set(Calendar.MINUTE, 0);
+        day.set(Calendar.SECOND, 0);
+        tsEnd = toTimestamp(day.getTime());
+
+        List<ActivitySample> samples = (List<ActivitySample>) getSamples(db, device, tsStart, tsEnd);
+        ensureStartAndEndSamples(samples, tsStart, tsEnd);
+        return samples;
+    }
+
+    protected void ensureStartAndEndSamples(List<ActivitySample> samples, int tsStart, int tsEnd) {
+        if (samples == null || samples.isEmpty()) {
+            return;
+        }
+        ActivitySample lastSample = samples.get(samples.size() - 1);
+        if (lastSample.getTimestamp() < tsEnd) {
+            samples.add(createTrailingActivitySample(lastSample, tsEnd));
+        }
+
+        ActivitySample firstSample = samples.get(0);
+        if (firstSample.getTimestamp() > tsStart) {
+            samples.add(createTrailingActivitySample(firstSample, tsStart));
+        }
+    }
+
+    private ActivitySample createTrailingActivitySample(ActivitySample referenceSample, int timestamp) {
+        TrailingActivitySample sample = new TrailingActivitySample();
+        if (referenceSample instanceof AbstractActivitySample) {
+            AbstractActivitySample reference = (AbstractActivitySample) referenceSample;
+            sample.setUserId(reference.getUserId());
+            sample.setDeviceId(reference.getDeviceId());
+            sample.setProvider(reference.getProvider());
+        }
+        sample.setTimestamp(timestamp);
+        return sample;
+    }
+}
+
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/AbstractChartFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/AbstractChartFragment.java
new file mode 100644
index 0000000..6f6c581
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/AbstractChartFragment.java
@@ -0,0 +1,403 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, Carsten Pfeiffer, Daniele
+    Gobbetti, Dikay900, Jos茅 Rebelo, Pavel Elagin, Petr Van臎k, walkjivefly
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities.charts;
+
+import android.annotation.SuppressLint;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.text.format.DateUtils;
+import android.view.View;
+
+import androidx.fragment.app.FragmentActivity;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import com.github.mikephil.charting.charts.BarChart;
+import com.github.mikephil.charting.charts.BarLineChartBase;
+import com.github.mikephil.charting.charts.Chart;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Set;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBFragment;
+import nodomain.freeyourgadget.gadgetbridge.database.DBAccess;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
+
+/**
+ * A base class fragment to be used with ChartsActivity. The fragment can supply
+ * a title to be displayed in the activity by returning non-null in #getTitle()
+ * Broadcast events can be received by overriding #onReceive(Context,Intent).
+ * The chart can be refreshed by calling #refresh()
+ * Implement refreshInBackground(DBHandler, GBDevice) to fetch the samples from the DB,
+ * and add the samples to the chart. The actual rendering, which must be performed in the UI
+ * thread, must be done in #renderCharts().
+ * Access functionality of the hosting activity with #getHost()
+ * <p/>
+ * The hosting ChartsHost activity provides a section for displaying a date or date range
+ * being the basis for the chart, as well as two buttons for moving backwards and forward
+ * in time. The date is held by the activity, so that it can be shared by multiple chart
+ * fragments. It is still the responsibility of the (currently visible) chart fragment
+ * to set the desired date in the ChartsActivity via #setDateRange(Date,Date).
+ * The default implementations #handleDatePrev(Date,Date) and #handleDateNext(Date,Date)
+ * shift the date by one day.
+ */
+public abstract class AbstractChartFragment<D extends ChartsData> extends AbstractGBFragment {
+    protected final int ANIM_TIME = 250;
+
+    private static final Logger LOG = LoggerFactory.getLogger(AbstractChartFragment.class);
+    @SuppressLint("SimpleDateFormat")
+    protected final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
+
+    private final Set<String> mIntentFilterActions;
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            AbstractChartFragment.this.onReceive(context, intent);
+        }
+    };
+
+    private boolean mChartDirty = true;
+    private AsyncTask refreshTask;
+
+    protected AbstractChartFragment(String... intentFilterActions) {
+        mIntentFilterActions = new HashSet<>();
+        if (intentFilterActions != null) {
+            mIntentFilterActions.addAll(Arrays.asList(intentFilterActions));
+        }
+        mIntentFilterActions.add(ChartsHost.DATE_NEXT_DAY);
+        mIntentFilterActions.add(ChartsHost.DATE_PREV_DAY);
+        mIntentFilterActions.add(ChartsHost.DATE_NEXT_WEEK);
+        mIntentFilterActions.add(ChartsHost.DATE_PREV_WEEK);
+        mIntentFilterActions.add(ChartsHost.DATE_NEXT_MONTH);
+        mIntentFilterActions.add(ChartsHost.DATE_PREV_MONTH);
+        mIntentFilterActions.add(ChartsHost.REFRESH);
+    }
+
+    @Override
+    public abstract String getTitle();
+
+    /**
+     * Called in the fragment's onCreate, initializes this fragment.
+     */
+    protected abstract void init();
+
+    /**
+     * This method reads the data from the database, analyzes and prepares it for
+     * the charts. This will be called from a background task, so there must not be
+     * any UI access. #updateChartsInUIThread and #renderCharts will be automatically called after this method.
+     */
+    protected abstract D refreshInBackground(ChartsHost chartsHost, DBHandler db, GBDevice device);
+
+    /**
+     * Triggers the actual (re-) rendering of the chart.
+     * Always called from the UI thread.
+     */
+    protected abstract void renderCharts();
+
+    protected abstract void setupLegend(Chart<?> chart);
+
+    protected abstract void updateChartsnUIThread(D chartsData);
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        init();
+
+        final IntentFilter filter = new IntentFilter();
+        for (String action : mIntentFilterActions) {
+            filter.addAction(action);
+        }
+        LocalBroadcastManager.getInstance(requireActivity()).registerReceiver(mReceiver, filter);
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        LocalBroadcastManager.getInstance(requireActivity()).unregisterReceiver(mReceiver);
+    }
+
+    /**
+     * Called when this fragment has been fully scrolled into the activity.
+     *
+     * @see #isVisibleInActivity()
+     * @see #onMadeInvisibleInActivity()
+     */
+    @Override
+    protected void onMadeVisibleInActivity() {
+        super.onMadeVisibleInActivity();
+        showDateBar(true);
+        updateDateInfo(getStartDate(), getEndDate());
+        if (mChartDirty) {
+            refresh();
+        }
+    }
+
+    protected ChartsHost getChartsHost() {
+        return (ChartsHost) requireActivity();
+    }
+
+    private void setStartDate(Date date) {
+        getChartsHost().setStartDate(date);
+    }
+
+    private void setEndDate(Date date) {
+        getChartsHost().setEndDate(date);
+    }
+
+    public Date getStartDate() {
+        return getChartsHost().getStartDate();
+    }
+
+    public Date getEndDate() {
+        return getChartsHost().getEndDate();
+    }
+
+    protected int getTSEnd() {
+        return toTimestamp(getEndDate());
+    }
+
+    protected int getTSStart() {
+        return toTimestamp(getStartDate());
+    }
+
+    /**
+     * Whether this chart shows data only for a single day. The day is expected to match
+     * the end timestamp.
+     */
+    protected boolean isSingleDay() {
+        return true;
+    }
+
+    protected int toTimestamp(Date date) {
+        return (int) ((date.getTime() / 1000));
+    }
+
+    protected void showDateBar(boolean show) {
+        getChartsHost().getDateBar().setVisibility(show ? View.VISIBLE : View.GONE);
+    }
+
+    protected void onReceive(Context context, Intent intent) {
+        String action = intent.getAction();
+        if (ChartsHost.REFRESH.equals(action)) {
+            updateDateInfo(getStartDate(), getEndDate());
+            refresh();
+        } else if (ChartsHost.DATE_NEXT_DAY.equals(action)) {
+            handleDate(getStartDate(), getEndDate(), +1);
+        } else if (ChartsHost.DATE_PREV_DAY.equals(action)) {
+            handleDate(getStartDate(), getEndDate(), -1);
+        } else if (ChartsHost.DATE_NEXT_WEEK.equals(action)) {
+            handleDate(getStartDate(), getEndDate(), +7);
+        } else if (ChartsHost.DATE_PREV_WEEK.equals(action)) {
+            handleDate(getStartDate(), getEndDate(), -7);
+        } else if (ChartsHost.DATE_NEXT_MONTH.equals(action)) {
+            //calculate dates to jump by month but keep subsequent logic working
+            int time1 = DateTimeUtils.shiftMonths((int) (getStartDate().getTime() / 1000), 1);
+            int time2 = DateTimeUtils.shiftMonths((int) (getEndDate().getTime() / 1000), 1);
+            Date date1 = DateTimeUtils.shiftByDays(new Date(time1 * 1000L), 30);
+            Date date2 = DateTimeUtils.shiftByDays(new Date(time2 * 1000L), 30);
+            handleDate(date1, date2, -30);
+        } else if (ChartsHost.DATE_PREV_MONTH.equals(action)) {
+            int time1 = DateTimeUtils.shiftMonths((int) (getStartDate().getTime() / 1000), -1);
+            int time2 = DateTimeUtils.shiftMonths((int) (getEndDate().getTime() / 1000), -1);
+            Date date1 = DateTimeUtils.shiftByDays(new Date(time1 * 1000L), -30);
+            Date date2 = DateTimeUtils.shiftByDays(new Date(time2 * 1000L), -30);
+            handleDate(date1, date2, 30);
+        }
+    }
+
+    /**
+     * Default implementation shifts the dates by one day, if visible
+     * and calls #refreshIfVisible().
+     *
+     * @param startDate the start date
+     * @param endDate   the end date
+     * @param offset    the offset, in days
+     */
+    private void handleDate(Date startDate, Date endDate, Integer offset) {
+        if (isVisibleInActivity()) {
+            if (!shiftDates(startDate, endDate, offset)) {
+                return;
+            }
+
+            updateDateInfo(getStartDate(), getEndDate());
+        }
+        refreshIfVisible();
+    }
+
+    private void refreshIfVisible() {
+        if (isVisibleInActivity()) {
+            refresh();
+        } else {
+            mChartDirty = true;
+        }
+    }
+
+    /**
+     * Shifts the given dates by offset days. offset may be positive or negative.
+     *
+     * @param startDate the start date
+     * @param endDate   the end date
+     * @param offset    a positive or negative number of days to shift the dates
+     * @return true if the shift was successful and false otherwise
+     */
+    private boolean shiftDates(Date startDate, Date endDate, int offset) {
+        Date newStart = DateTimeUtils.shiftByDays(startDate, offset);
+        Date newEnd = DateTimeUtils.shiftByDays(endDate, offset);
+        Date now = new Date();
+        if (newEnd.after(now)) { //allow to jump to the end (now) if week/month reach after now
+            newEnd = now;
+            newStart = DateTimeUtils.shiftByDays(now, -1);
+        }
+        return setDateRange(newStart, newEnd);
+    }
+
+    protected void configureChartDefaults(Chart<?> chart) {
+        chart.getXAxis().setValueFormatter(new TimestampValueFormatter());
+        chart.getDescription().setText("");
+
+        // if enabled, the chart will always start at zero on the y-axis
+        chart.setNoDataText(getString(R.string.chart_no_data_synchronize));
+
+        // disable value highlighting
+        chart.setHighlightPerTapEnabled(false);
+
+        // enable touch gestures
+        chart.setTouchEnabled(true);
+
+// commented out: this has weird bugs/side-effects at least on WeekStepsCharts
+// where only the first Day-label is drawn, because AxisRenderer.computeAxisValues(float,float)
+// appears to have an overflow when calculating 'n' (number of entries)
+//        chart.getXAxis().setGranularity(60*5);
+
+        setupLegend(chart);
+    }
+
+    protected void configureBarLineChartDefaults(BarLineChartBase<?> chart) {
+        configureChartDefaults(chart);
+        if (chart instanceof BarChart) {
+            ((BarChart) chart).setFitBars(true);
+        }
+
+        // enable scaling and dragging
+        chart.setDragEnabled(true);
+        chart.setScaleEnabled(true);
+
+        // if disabled, scaling can be done on x- and y-axis separately
+//        chart.setPinchZoom(true);
+
+        chart.setDrawGridBackground(false);
+    }
+
+    /**
+     * This method will invoke a background task to read the data from the
+     * database, analyze it, prepare it for the charts and eventually call
+     * #renderCharts
+     */
+    protected void refresh() {
+        LOG.info("Refreshing data for {} from {} to {}", getTitle(), sdf.format(getStartDate()), sdf.format(getEndDate()));
+
+        ChartsHost chartsHost = getChartsHost();
+        if (chartsHost != null) {
+            if (chartsHost.getDevice() != null) {
+                mChartDirty = false;
+                if (refreshTask != null && refreshTask.getStatus() != AsyncTask.Status.FINISHED) {
+                    refreshTask.cancel(true);
+                }
+                refreshTask = createRefreshTask("Visualizing data", getActivity()).execute();
+            }
+        }
+    }
+
+    private RefreshTask createRefreshTask(final String task, final Context context) {
+        return new RefreshTask(task, context);
+    }
+
+    @SuppressLint("StaticFieldLeak")
+    private final class RefreshTask extends DBAccess {
+        private D chartsData;
+
+        public RefreshTask(final String task, final Context context) {
+            super(task, context);
+        }
+
+        @Override
+        protected void doInBackground(final DBHandler db) {
+            final ChartsHost chartsHost = getChartsHost();
+            if (chartsHost != null) {
+                chartsData = refreshInBackground(chartsHost, db, chartsHost.getDevice());
+            } else {
+                cancel(true);
+            }
+        }
+
+        @Override
+        protected void onPostExecute(final Object o) {
+            super.onPostExecute(o);
+            final FragmentActivity activity = getActivity();
+            if (activity != null && !activity.isFinishing() && !activity.isDestroyed()) {
+                updateChartsnUIThread(chartsData);
+                renderCharts();
+            } else {
+                LOG.info("Not rendering charts because activity is not available anymore");
+            }
+        }
+    }
+
+    /**
+     * Returns true if the date was successfully shifted, and false if the shift
+     * was ignored, e.g. when the to-value is in the future.
+     *
+     * @param from the start date
+     * @param to   the end date
+     */
+    private boolean setDateRange(final Date from, final Date to) {
+        if (from.compareTo(to) > 0) {
+            throw new IllegalArgumentException("Bad date range: " + from + ".." + to);
+        }
+        final Date now = new Date();
+        if (to.after(now) || //do not refresh chart if we reached now
+                to.getTime() / 10000 == (getEndDate().getTime() / 10000)) {
+            return false;
+        }
+        setStartDate(from);
+        setEndDate(to);
+        return true;
+    }
+
+    private void updateDateInfo(final Date from, final Date to) {
+        int dateFlags = DateUtils.FORMAT_SHOW_WEEKDAY;
+        if (isSingleDay() || from.equals(to)) {
+            getChartsHost().setDateInfo(DateTimeUtils.formatDate(to, dateFlags));
+        } else {
+            getChartsHost().setDateInfo(DateTimeUtils.formatDateRange(from, to, dateFlags));
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/AbstractChartsActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/AbstractChartsActivity.java
new file mode 100644
index 0000000..d979092
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/AbstractChartsActivity.java
@@ -0,0 +1,361 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities.charts;
+
+import android.app.Activity;
+import android.app.DatePickerDialog;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.activity.result.ActivityResult;
+import androidx.activity.result.ActivityResultCallback;
+import androidx.activity.result.ActivityResultLauncher;
+import androidx.activity.result.contract.ActivityResultContracts;
+import androidx.annotation.NonNull;
+import androidx.appcompat.app.ActionBar;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
+import androidx.viewpager.widget.ViewPager;
+
+import com.google.android.material.tabs.TabLayout;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+import java.util.Objects;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBFragmentActivity;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public abstract class AbstractChartsActivity extends AbstractGBFragmentActivity implements ChartsHost {
+    private static final Logger LOG = LoggerFactory.getLogger(AbstractChartsActivity.class);
+
+    public static final String STATE_START_DATE = "stateStartDate";
+    public static final String STATE_END_DATE = "stateEndDate";
+
+    public static final String EXTRA_FRAGMENT_ID = "fragmentId";
+    public static final String EXTRA_SINGLE_FRAGMENT_NAME = "singleFragmentName";
+    public static final String EXTRA_ACTIONBAR_TITLE = "actionbarTitle";
+    public static final String EXTRA_TIMESTAMP = "timestamp";
+    public static final String EXTRA_MODE = "mode";
+
+    private TextView mDateControl;
+
+    private Date mStartDate;
+    private Date mEndDate;
+    private SwipeRefreshLayout swipeLayout;
+
+    List<String> enabledTabsList;
+
+    private GBDevice mGBDevice;
+    private ViewGroup dateBar;
+
+    private ActivityResultLauncher<Intent> chartsPreferencesLauncher;
+    private final ActivityResultCallback<ActivityResult> chartsPreferencesCallback = result -> {
+        recreate();
+    };
+
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(final Context context, final Intent intent) {
+            final String action = intent.getAction();
+            //noinspection SwitchStatementWithTooFewBranches
+            switch (Objects.requireNonNull(action)) {
+                case GBDevice.ACTION_DEVICE_CHANGED:
+                    final GBDevice dev = intent.getParcelableExtra(GBDevice.EXTRA_DEVICE);
+                    if (dev != null) {
+                        refreshBusyState(dev);
+                    }
+                    break;
+            }
+        }
+    };
+
+    private void refreshBusyState(final GBDevice dev) {
+        if (dev.isBusy()) {
+            swipeLayout.setRefreshing(true);
+        } else {
+            final boolean wasBusy = swipeLayout.isRefreshing();
+            swipeLayout.setRefreshing(false);
+            if (wasBusy) {
+                LocalBroadcastManager.getInstance(this).sendBroadcast(new Intent(REFRESH));
+            }
+        }
+        enableSwipeRefresh(true);
+    }
+
+    @Override
+    protected void onCreate(final Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_charts);
+
+        final Bundle extras = getIntent().getExtras();
+        if (extras == null) {
+            throw new IllegalArgumentException("Must provide a device when invoking this activity");
+        }
+
+        mGBDevice = extras.getParcelable(GBDevice.EXTRA_DEVICE);
+
+        chartsPreferencesLauncher = registerForActivityResult(
+                new ActivityResultContracts.StartActivityForResult(),
+                chartsPreferencesCallback
+        );
+
+        // Set start and end date
+        if (savedInstanceState != null) {
+            setEndDate(new Date(savedInstanceState.getLong(STATE_END_DATE, System.currentTimeMillis())));
+        } else if (extras.containsKey(EXTRA_TIMESTAMP)) {
+            final int endTimestamp = extras.getInt(EXTRA_TIMESTAMP, 0);
+            setEndDate(new Date(endTimestamp * 1000L));
+        } else {
+            setEndDate(new Date());
+        }
+        setStartDate(DateTimeUtils.shiftByDays(getEndDate(), -1));
+
+        final IntentFilter filterLocal = new IntentFilter();
+        filterLocal.addAction(GBDevice.ACTION_DEVICE_CHANGED);
+        LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, filterLocal);
+
+        // Open the specified fragment, if any, and setup single page view if specified
+        final int tabFragmentIdToOpen = extras.getInt(EXTRA_FRAGMENT_ID, -1);
+        final String singleFragmentName = extras.getString(EXTRA_SINGLE_FRAGMENT_NAME, null);
+        final int actionbarTitle = extras.getInt(EXTRA_ACTIONBAR_TITLE, 0);
+
+        if (tabFragmentIdToOpen >= 0 && singleFragmentName != null) {
+            throw new IllegalArgumentException("Must specify either fragment ID or single fragment name, not both");
+        }
+
+        if (singleFragmentName != null) {
+            enabledTabsList = Collections.singletonList(singleFragmentName);
+        } else {
+            enabledTabsList = fillChartsTabsList();
+        }
+
+        swipeLayout = findViewById(R.id.activity_swipe_layout);
+        swipeLayout.setOnRefreshListener(this::fetchRecordedData);
+        enableSwipeRefresh(true);
+
+        // Set up the ViewPager with the sections adapter.
+        final NonSwipeableViewPager viewPager = findViewById(R.id.charts_pager);
+        viewPager.setAdapter(getPagerAdapter());
+        if (tabFragmentIdToOpen > -1) {
+            viewPager.setCurrentItem(tabFragmentIdToOpen);  // open the tab as specified in the intent
+        }
+
+        viewPager.setAllowSwipe(singleFragmentName == null && GBApplication.getPrefs().getBoolean("charts_allow_swipe", true));
+
+        if (singleFragmentName != null) {
+            final TabLayout tabLayout = findViewById(R.id.charts_pagerTabStrip);
+            tabLayout.setVisibility(TextView.GONE);
+        }
+
+        if (actionbarTitle != 0) {
+            final ActionBar actionBar = getSupportActionBar();
+
+            if (actionBar != null) {
+                actionBar.setTitle(actionbarTitle);
+            }
+        }
+
+        viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
+            @Override
+            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
+            }
+
+            @Override
+            public void onPageSelected(int position) {
+            }
+
+            @Override
+            public void onPageScrollStateChanged(int state) {
+                enableSwipeRefresh(state == ViewPager.SCROLL_STATE_IDLE);
+            }
+        });
+
+        dateBar = findViewById(R.id.charts_date_bar);
+        mDateControl = findViewById(R.id.charts_text_date);
+        mDateControl.setOnClickListener(v -> {
+            String detailedDuration = formatDetailedDuration();
+            new ShowDurationDialog(detailedDuration, AbstractChartsActivity.this).show();
+        });
+
+        final Button mPrevButton = findViewById(R.id.charts_previous_day);
+        mPrevButton.setOnClickListener(v -> handleButtonClicked(DATE_PREV_DAY));
+        final Button mNextButton = findViewById(R.id.charts_next_day);
+        mNextButton.setOnClickListener(v -> handleButtonClicked(DATE_NEXT_DAY));
+
+        final Button mPrevWeekButton = findViewById(R.id.charts_previous_week);
+        mPrevWeekButton.setOnClickListener(v -> handleButtonClicked(DATE_PREV_WEEK));
+        final Button mNextWeekButton = findViewById(R.id.charts_next_week);
+        mNextWeekButton.setOnClickListener(v -> handleButtonClicked(DATE_NEXT_WEEK));
+
+        final Button mPrevMonthButton = findViewById(R.id.charts_previous_month);
+        mPrevMonthButton.setOnClickListener(v -> handleButtonClicked(DATE_PREV_MONTH));
+        final Button mNextMonthButton = findViewById(R.id.charts_next_month);
+        mNextMonthButton.setOnClickListener(v -> handleButtonClicked(DATE_NEXT_MONTH));
+    }
+
+    @Override
+    protected void onSaveInstanceState(@NonNull final Bundle outState) {
+        super.onSaveInstanceState(outState);
+
+        outState.putLong(STATE_END_DATE, getEndDate().getTime());
+        outState.putLong(STATE_START_DATE, getStartDate().getTime());
+    }
+
+    @Override
+    protected void onRestoreInstanceState(@NonNull final Bundle savedInstanceState) {
+        super.onRestoreInstanceState(savedInstanceState);
+
+        setEndDate(new Date(savedInstanceState.getLong(STATE_END_DATE, System.currentTimeMillis())));
+        setStartDate(new Date(savedInstanceState.getLong(STATE_START_DATE, DateTimeUtils.shiftByDays(getEndDate(), -1).getTime())));
+    }
+
+    protected abstract List<String> fillChartsTabsList();
+
+    private String formatDetailedDuration() {
+        final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.getDefault());
+        final String dateStringFrom = dateFormat.format(getStartDate());
+        final String dateStringTo = dateFormat.format(getEndDate());
+
+        return getString(R.string.sleep_activity_date_range, dateStringFrom, dateStringTo);
+    }
+
+    protected void initDates() {
+        setEndDate(new Date());
+        setStartDate(DateTimeUtils.shiftByDays(getEndDate(), -1));
+    }
+
+    @Override
+    public GBDevice getDevice() {
+        return mGBDevice;
+    }
+
+    @Override
+    public void setStartDate(Date startDate) {
+        mStartDate = startDate;
+    }
+
+    @Override
+    public void setEndDate(Date endDate) {
+        mEndDate = endDate;
+    }
+
+    @Override
+    public Date getStartDate() {
+        return mStartDate;
+    }
+
+    @Override
+    public Date getEndDate() {
+        return mEndDate;
+    }
+
+    @Override
+    public void setDateInfo(final String dateInfo) {
+        mDateControl.setText(dateInfo);
+    }
+
+    @Override
+    public ViewGroup getDateBar() {
+        return dateBar;
+    }
+
+    private void handleButtonClicked(final String action) {
+        LocalBroadcastManager.getInstance(this).sendBroadcast(new Intent(action));
+    }
+
+    @Override
+    protected void onDestroy() {
+        LocalBroadcastManager.getInstance(this).unregisterReceiver(mReceiver);
+        super.onDestroy();
+    }
+
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        super.onCreateOptionsMenu(menu);
+        getMenuInflater().inflate(R.menu.menu_charts, menu);
+
+        if (!mGBDevice.isConnected() || !supportsRefresh()) {
+            menu.removeItem(R.id.charts_fetch_activity_data);
+        }
+        return true;
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(final MenuItem item) {
+        final int itemId = item.getItemId();
+        if (itemId == R.id.charts_fetch_activity_data) {
+            fetchRecordedData();
+            return true;
+        } else if (itemId == R.id.charts_set_date) {
+            final Calendar currentDate = Calendar.getInstance();
+            currentDate.setTime(getEndDate());
+            new DatePickerDialog(this, (view, year, monthOfYear, dayOfMonth) -> {
+                currentDate.set(year, monthOfYear, dayOfMonth);
+                setEndDate(currentDate.getTime());
+                setStartDate(DateTimeUtils.shiftByDays(getEndDate(), -1));
+                LocalBroadcastManager.getInstance(this).sendBroadcast(new Intent(REFRESH));
+            }, currentDate.get(Calendar.YEAR), currentDate.get(Calendar.MONTH), currentDate.get(Calendar.DATE)).show();
+        } else if (itemId == R.id.prefs_charts_menu) {
+            final Intent settingsIntent = new Intent(this, ChartsPreferencesActivity.class);
+            chartsPreferencesLauncher.launch(settingsIntent);
+            return true;
+        }
+
+        return super.onOptionsItemSelected(item);
+    }
+
+    @Override
+    public void enableSwipeRefresh(final boolean enable) {
+        swipeLayout.setEnabled(enable && allowRefresh());
+    }
+
+    protected abstract boolean supportsRefresh();
+
+    protected abstract boolean allowRefresh();
+
+    protected abstract int getRecordedDataType();
+
+    private void fetchRecordedData() {
+        if (getDevice().isInitialized()) {
+            GBApplication.deviceService(getDevice()).onFetchRecordedData(getRecordedDataType());
+        } else {
+            swipeLayout.setRefreshing(false);
+            GB.toast(this, getString(R.string.device_not_connected), Toast.LENGTH_SHORT, GB.ERROR);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/AbstractCollectionFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/AbstractCollectionFragment.java
new file mode 100644
index 0000000..aa61fe7
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/AbstractCollectionFragment.java
@@ -0,0 +1,120 @@
+/*  Copyright (C) 2024 a0z, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities.charts;
+
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.fragment.app.FragmentManager;
+import androidx.viewpager2.widget.ViewPager2;
+
+import com.google.android.material.tabs.TabLayout;
+import com.google.android.material.tabs.TabLayoutMediator;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBFragment;
+import nodomain.freeyourgadget.gadgetbridge.adapter.NestedFragmentAdapter;
+
+public abstract class AbstractCollectionFragment extends AbstractGBFragment {
+    protected static final String ARG_ALLOW_SWIPE = "allow_swipe";
+
+    protected NestedFragmentAdapter nestedFragmentsAdapter;
+    protected ViewPager2 viewPager;
+    private int last_position = 0;
+    private boolean allowSwipe;
+
+    public abstract NestedFragmentAdapter getNestedFragmentAdapter(final AbstractGBFragment fragment,
+                                                                   final FragmentManager childFragmentManager);
+
+    @Override
+    public void onCreate(final Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        if (getArguments() != null) {
+            allowSwipe = getArguments().getBoolean(ARG_ALLOW_SWIPE, false);
+        }
+    }
+
+    @Override
+    protected void onMadeVisibleInActivity() {
+        super.onMadeVisibleInActivity();
+        nestedFragmentsAdapter.updateFragments(last_position);
+    }
+
+    @Override
+    public void onMadeInvisibleInActivity() {
+        if (nestedFragmentsAdapter != null) {
+            nestedFragmentsAdapter.updateFragments(-1);
+        }
+    }
+
+    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
+                             @Nullable Bundle savedInstanceState) {
+        View rootView = inflater.inflate(R.layout.fragment_nested_tabs, container, false);
+        nestedFragmentsAdapter = getNestedFragmentAdapter(this, getChildFragmentManager());
+        viewPager = rootView.findViewById(R.id.pager);
+        viewPager.setAdapter(nestedFragmentsAdapter);
+        if (!allowSwipe) {
+            viewPager.setOrientation(ViewPager2.ORIENTATION_VERTICAL);
+            viewPager.setUserInputEnabled(false);
+        }
+        viewPager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
+            @Override
+            public void onPageSelected(int position) {
+                super.onPageSelected(position);
+                last_position = position;
+                viewPager.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        if (isVisibleInActivity()) {
+                            nestedFragmentsAdapter.updateFragments(position);
+                        }
+                    }
+                });
+            }
+        });
+
+        return rootView;
+    }
+
+    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
+        TabLayout tabLayout = view.findViewById(R.id.tab_layout);
+        new TabLayoutMediator(tabLayout, viewPager, (tab, position) -> {
+            switch (position) {
+                case 0:
+                    tab.setText(getString(R.string.calendar_day));
+                    break;
+                case 1:
+                    tab.setText(getString(R.string.calendar_week));
+                    break;
+                case 2:
+                    tab.setText(getString(R.string.calendar_month));
+                    break;
+            }
+        }).attach();
+    }
+
+    @Nullable
+    @Override
+    protected CharSequence getTitle() {
+        return null;
+    }
+}
+
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/AbstractWeekChartFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/AbstractWeekChartFragment.java
new file mode 100644
index 0000000..96f3eaf
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/AbstractWeekChartFragment.java
@@ -0,0 +1,392 @@
+/*  Copyright (C) 2017-2024 Alberto, Andreas Shimokawa, Carsten Pfeiffer,
+    Daniele Gobbetti, Jos茅 Rebelo, Pavel Elagin, Petr Van臎k, a0z
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities.charts;
+
+import android.app.Activity;
+import android.graphics.Color;
+import android.os.Build;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import com.github.mikephil.charting.charts.BarChart;
+import com.github.mikephil.charting.components.LimitLine;
+import com.github.mikephil.charting.components.XAxis;
+import com.github.mikephil.charting.components.YAxis;
+import com.github.mikephil.charting.data.BarData;
+import com.github.mikephil.charting.data.BarDataSet;
+import com.github.mikephil.charting.data.BarEntry;
+import com.github.mikephil.charting.data.ChartData;
+import com.github.mikephil.charting.data.Entry;
+import com.github.mikephil.charting.data.LineData;
+import com.github.mikephil.charting.data.LineDataSet;
+import com.github.mikephil.charting.formatter.ValueFormatter;
+import com.github.mikephil.charting.interfaces.datasets.ILineDataSet;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.List;
+import java.util.Locale;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.TimeSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityAmounts;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.model.SleepScoreSample;
+import nodomain.freeyourgadget.gadgetbridge.util.LimitedQueue;
+
+
+public abstract class AbstractWeekChartFragment extends AbstractActivityChartFragment<AbstractWeekChartFragment.MyChartsData> {
+    protected static final Logger LOG = LoggerFactory.getLogger(AbstractWeekChartFragment.class);
+    protected int TOTAL_DAYS = getRangeDays();
+    protected int TOTAL_DAYS_FOR_AVERAGE = 0;
+
+    protected Locale mLocale;
+    protected int mTargetValue = 0;
+
+    protected BarChart mWeekChart;
+    protected TextView mBalanceView;
+
+    private int mOffsetHours = getOffsetHours();
+
+    protected String getWeeksChartsLabel(Calendar day){
+        if (TOTAL_DAYS > 7) {
+            //month, show day date
+            return String.valueOf(day.get(Calendar.DAY_OF_MONTH));
+        }
+        else{
+            //week, show short day name
+            return day.getDisplayName(Calendar.DAY_OF_WEEK, Calendar.SHORT, mLocale);
+        }
+    }
+    protected WeekChartsData<BarData> refreshWeekBeforeData(DBHandler db, BarChart barChart, Calendar day, GBDevice device) {
+        day = (Calendar) day.clone(); // do not modify the caller's argument
+        day.add(Calendar.DATE, -TOTAL_DAYS + 1);
+        List<BarEntry> entries = new ArrayList<>();
+        ArrayList<String> labels = new ArrayList<String>();
+
+        long balance = 0;
+        long daily_balance = 0;
+        TOTAL_DAYS_FOR_AVERAGE=0;
+        List<Entry> sleepScoreEntities = new ArrayList<>();
+        final List<ILineDataSet> sleepScoreDataSets = new ArrayList<>();
+        for (int counter = 0; counter < TOTAL_DAYS; counter++) {
+            // Sleep stages
+            ActivityAmounts amounts = getActivityAmountsForDay(db, day, device);
+            daily_balance=calculateBalance(amounts);
+            if (daily_balance > 0) {
+                TOTAL_DAYS_FOR_AVERAGE++;
+            }
+            balance += daily_balance;
+            entries.add(new BarEntry(counter, getTotalsForActivityAmounts(amounts)));
+            labels.add(getWeeksChartsLabel(day));
+            // Sleep score
+            if (supportsSleepScore()) {
+                List<? extends SleepScoreSample> sleepScoreSamples = getSleepScoreSamples(db, device, day);
+                if (!sleepScoreSamples.isEmpty() && sleepScoreSamples.get(0).getSleepScore() > 0) {
+                    sleepScoreEntities.add(new Entry(counter, sleepScoreSamples.get(0).getSleepScore()));
+                } else {
+                    if (!sleepScoreEntities.isEmpty()) {
+                        List<Entry> clone = new ArrayList<>(sleepScoreEntities.size());
+                        clone.addAll(sleepScoreEntities);
+                        sleepScoreDataSets.add(createSleepScoreDataSet(clone));
+                        sleepScoreEntities.clear();
+                    }
+                }
+            }
+            day.add(Calendar.DATE, 1);
+        }
+        if (!sleepScoreEntities.isEmpty()) {
+            sleepScoreDataSets.add(createSleepScoreDataSet(sleepScoreEntities));
+        }
+        final LineData sleepScoreLineData = new LineData(sleepScoreDataSets);
+        sleepScoreLineData.setHighlightEnabled(false);
+
+        BarDataSet set = new BarDataSet(entries, "");
+        set.setColors(getColors());
+        set.setValueFormatter(getBarValueFormatter());
+
+        BarData barData = new BarData(set);
+        barData.setValueTextColor(Color.GRAY); //prevent tearing other graph elements with the black text. Another approach would be to hide the values cmpletely with data.setDrawValues(false);
+        barData.setValueTextSize(10f);
+
+        barChart.getAxisLeft().setAxisMaximum(Math.max(set.getYMax(), mTargetValue) + 60);
+
+        LimitLine target = new LimitLine(mTargetValue);
+        target.setLineWidth(1.5f);
+        target.enableDashedLine(15f, 10f, 0f);
+        target.setLineColor(getResources().getColor(R.color.chart_deep_sleep_dark));
+        barChart.getAxisLeft().removeAllLimitLines();
+        barChart.getAxisLeft().addLimitLine(target);
+
+        float average = 0;
+        if (TOTAL_DAYS_FOR_AVERAGE > 0) {
+            average = Math.abs(balance / TOTAL_DAYS_FOR_AVERAGE);
+        }
+        LimitLine average_line = new LimitLine(average);
+        average_line.setLineWidth(1.5f);
+        average_line.enableDashedLine(15f, 10f, 0f);
+        average_line.setLabel(getString(R.string.average, getAverage(average)));
+
+        if (average > (mTargetValue)) {
+            average_line.setLineColor(Color.GREEN);
+            average_line.setTextColor(Color.GREEN);
+        }
+        else {
+            average_line.setLineColor(Color.RED);
+            average_line.setTextColor(Color.RED);
+        }
+        if (average > 0) {
+            if (GBApplication.getPrefs().getBoolean("charts_show_average", true)) {
+                barChart.getAxisLeft().addLimitLine(average_line);
+            }
+        }
+
+        if (supportsSleepScore()) {
+            return new WeekChartsData(barData, new PreformattedXIndexLabelFormatter(labels), getBalanceMessage(balance, mTargetValue), sleepScoreLineData);
+        }
+        return new WeekChartsData(barData, new PreformattedXIndexLabelFormatter(labels), getBalanceMessage(balance, mTargetValue));
+    }
+
+    protected List<SleepScoreSample> getSleepScoreSamples(DBHandler db, GBDevice device, Calendar day) {
+        int startTs;
+        int endTs;
+
+        day = (Calendar) day.clone(); // do not modify the caller's argument
+        day.set(Calendar.HOUR_OF_DAY, 0);
+        day.set(Calendar.MINUTE, 0);
+        day.set(Calendar.SECOND, 0);
+        day.add(Calendar.HOUR, 0);
+        startTs = (int) (day.getTimeInMillis() / 1000);
+        endTs = startTs + 24 * 60 * 60 - 1;
+
+        TimeSampleProvider<? extends SleepScoreSample> provider = device.getDeviceCoordinator().getSleepScoreProvider(device, db.getDaoSession());
+        return (List<SleepScoreSample>) provider.getAllSamples(startTs * 1000L, endTs * 1000L);
+    }
+
+    protected LineDataSet createSleepScoreDataSet(final List<Entry> values) {
+        final LineDataSet lineDataSet = new LineDataSet(values, getString(R.string.sleep_score));
+        lineDataSet.setColor(getResources().getColor(R.color.chart_light_sleep_light));
+        lineDataSet.setDrawCircles(false);
+        lineDataSet.setLineWidth(2f);
+        lineDataSet.setFillAlpha(255);
+        lineDataSet.setCircleRadius(5f);
+        lineDataSet.setDrawCircles(true);
+        lineDataSet.setDrawCircleHole(true);
+        lineDataSet.setCircleColor(getResources().getColor(R.color.chart_light_sleep_light));
+        lineDataSet.setAxisDependency(YAxis.AxisDependency.LEFT);
+        lineDataSet.setDrawValues(true);
+        lineDataSet.setValueTextSize(10f);
+        lineDataSet.setValueTextColor(CHART_TEXT_COLOR);
+        lineDataSet.setValueFormatter(new ValueFormatter() {
+            @Override
+            public String getFormattedValue(float value) {
+                return String.format(Locale.ROOT, "%d", (int) value);
+            }
+        });
+        return lineDataSet;
+    };
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+                             Bundle savedInstanceState) {
+        mLocale = getResources().getConfiguration().locale;
+
+        View rootView = inflater.inflate(R.layout.fragment_weeksteps_chart, container, false);
+
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+            rootView.setOnScrollChangeListener((v, scrollX, scrollY, oldScrollX, oldScrollY) -> {
+                getChartsHost().enableSwipeRefresh(scrollY == 0);
+            });
+        }
+
+        final int goal = getGoal();
+        if (goal >= 0) {
+            mTargetValue = goal;
+        }
+
+        mWeekChart = rootView.findViewById(R.id.weekstepschart);
+        mBalanceView = rootView.findViewById(R.id.balance);
+
+        setupWeekChart();
+
+        // refresh immediately instead of use refreshIfVisible(), for perceived performance
+        refresh();
+
+        return rootView;
+    }
+
+    protected void setupWeekChart() {
+        mWeekChart.setBackgroundColor(BACKGROUND_COLOR);
+        mWeekChart.getDescription().setTextColor(DESCRIPTION_COLOR);
+        mWeekChart.getDescription().setText("");
+        mWeekChart.setFitBars(true);
+
+        configureBarLineChartDefaults(mWeekChart);
+
+        XAxis x = mWeekChart.getXAxis();
+        x.setDrawLabels(true);
+        x.setDrawGridLines(false);
+        x.setEnabled(true);
+        x.setTextColor(CHART_TEXT_COLOR);
+        x.setDrawLimitLinesBehindData(true);
+        x.setPosition(XAxis.XAxisPosition.BOTTOM);
+
+        YAxis y = mWeekChart.getAxisLeft();
+        y.setDrawGridLines(false);
+        y.setDrawTopYLabelEntry(false);
+        y.setTextColor(CHART_TEXT_COLOR);
+        y.setDrawZeroLine(true);
+        y.setSpaceBottom(0);
+        y.setAxisMinimum(0);
+        y.setValueFormatter(getYAxisFormatter());
+        y.setEnabled(true);
+
+        YAxis yAxisRight = mWeekChart.getAxisRight();
+        yAxisRight.setDrawGridLines(false);
+        yAxisRight.setEnabled(false);
+        yAxisRight.setDrawLabels(false);
+        yAxisRight.setDrawTopYLabelEntry(false);
+        yAxisRight.setTextColor(CHART_TEXT_COLOR);
+    }
+
+    private List<? extends ActivitySample> getSamplesOfDay(DBHandler db, Calendar day, int offsetHours, GBDevice device) {
+        int startTs;
+        int endTs;
+
+        day = (Calendar) day.clone(); // do not modify the caller's argument
+        day.set(Calendar.HOUR_OF_DAY, 0);
+        day.set(Calendar.MINUTE, 0);
+        day.set(Calendar.SECOND, 0);
+        day.add(Calendar.HOUR, offsetHours);
+
+        startTs = (int) (day.getTimeInMillis() / 1000);
+        endTs = startTs + 24 * 60 * 60 - 1;
+
+        return getSamples(db, device, startTs, endTs);
+    }
+
+    @Override
+    protected List<? extends ActivitySample> getSamples(DBHandler db, GBDevice device, int tsFrom, int tsTo) {
+        return super.getAllSamples(db, device, tsFrom, tsTo);
+    }
+
+    protected static class MyChartsData extends ChartsData {
+        private final WeekChartsData<BarData> weekBeforeData;
+
+        MyChartsData(WeekChartsData<BarData> weekBeforeData) {
+            this.weekBeforeData = weekBeforeData;
+        }
+
+        WeekChartsData<BarData> getWeekBeforeData() {
+            return weekBeforeData;
+        }
+    }
+
+    protected ActivityAmounts getActivityAmountsForDay(DBHandler db, Calendar day, GBDevice device) {
+
+        LimitedQueue<Integer, ActivityAmounts> activityAmountCache = null;
+        ActivityAmounts amounts = null;
+
+        Activity activity = getActivity();
+        int key = (int) (day.getTimeInMillis() / 1000) + (mOffsetHours * 3600);
+        if (activity != null) {
+            activityAmountCache = ((ActivityChartsActivity) activity).mActivityAmountCache;
+            amounts = activityAmountCache.lookup(key);
+        }
+
+        if (amounts == null) {
+            ActivityAnalysis analysis = new ActivityAnalysis();
+            amounts = analysis.calculateActivityAmounts(getSamplesOfDay(db, day, mOffsetHours, device));
+            if (activityAmountCache != null) {
+                activityAmountCache.add(key, amounts);
+            }
+        }
+
+        return amounts;
+    }
+
+    private int getRangeDays(){
+        if (GBApplication.getPrefs().getBoolean("charts_range", true)) {
+            return 30;}
+        else{
+            return 7;
+        }
+    }
+
+    public boolean supportsSleepScore() {
+        final GBDevice device = getChartsHost().getDevice();
+        return device.getDeviceCoordinator().supportsSleepScore();
+    }
+
+    abstract String getAverage(float value);
+
+    abstract int getGoal();
+
+    abstract int getOffsetHours();
+
+    abstract float[] getTotalsForActivityAmounts(ActivityAmounts activityAmounts);
+
+    abstract String formatPieValue(long value);
+
+    abstract String[] getPieLabels();
+
+    abstract ValueFormatter getPieValueFormatter();
+
+    abstract ValueFormatter getBarValueFormatter();
+
+    abstract ValueFormatter getYAxisFormatter();
+
+    abstract int[] getColors();
+
+    abstract String getPieDescription(int targetValue);
+
+    protected abstract long calculateBalance(ActivityAmounts amounts);
+
+    protected abstract String getBalanceMessage(long balance, int targetValue);
+
+    protected class WeekChartsData<T extends ChartData<?>> extends DefaultChartsData<T> {
+        private final String balanceMessage;
+        private LineData sleepScoresLineData;
+
+        public WeekChartsData(T data, PreformattedXIndexLabelFormatter xIndexLabelFormatter, String balanceMessage) {
+            super(data, xIndexLabelFormatter);
+            this.balanceMessage = balanceMessage;
+        }
+
+        public WeekChartsData(T data, PreformattedXIndexLabelFormatter xIndexLabelFormatter, String balanceMessage, LineData sleepScores) {
+            super(data, xIndexLabelFormatter);
+            this.balanceMessage = balanceMessage;
+            this.sleepScoresLineData = sleepScores;
+        }
+
+        public String getBalanceMessage() {
+            return balanceMessage;
+        }
+
+        public LineData getSleepScoreData() { return sleepScoresLineData; }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ActivityAnalysis.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ActivityAnalysis.java
new file mode 100644
index 0000000..a8798f1
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ActivityAnalysis.java
@@ -0,0 +1,157 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, Carsten Pfeiffer, Daniele
+    Gobbetti, Jos茅 Rebelo, Pavel Elagin, Petr Van臎k, Vebryn
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities.charts;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.HashMap;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityAmount;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityAmounts;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
+
+public class ActivityAnalysis {
+    public static final Logger LOG = LoggerFactory.getLogger(ActivityAnalysis.class);
+
+    // store raw steps and duration
+    protected HashMap<Integer, Long> stats = new HashMap<Integer, Long>();
+    // max speed determined from samples
+    private int maxSpeed = 0;
+
+    public ActivityAmounts calculateActivityAmounts(List<? extends ActivitySample> samples) {
+        ActivityAmount deepSleep = new ActivityAmount(ActivityKind.DEEP_SLEEP);
+        ActivityAmount lightSleep = new ActivityAmount(ActivityKind.LIGHT_SLEEP);
+        ActivityAmount remSleep = new ActivityAmount(ActivityKind.REM_SLEEP);
+        ActivityAmount awakeSleep = new ActivityAmount(ActivityKind.AWAKE_SLEEP);
+        ActivityAmount notWorn = new ActivityAmount(ActivityKind.NOT_WORN);
+        ActivityAmount activity = new ActivityAmount(ActivityKind.ACTIVITY);
+
+        ActivityAmount previousAmount = null;
+        ActivitySample previousSample = null;
+        for (ActivitySample sample : samples) {
+            ActivityAmount amount;
+            switch (sample.getKind()) {
+                case DEEP_SLEEP:
+                    amount = deepSleep;
+                    break;
+                case LIGHT_SLEEP:
+                    amount = lightSleep;
+                    break;
+                case REM_SLEEP:
+                    amount = remSleep;
+                    break;
+                case AWAKE_SLEEP:
+                    amount = awakeSleep;
+                    break;
+                case NOT_WORN:
+                    amount = notWorn;
+                    break;
+                case ACTIVITY:
+                default:
+                    amount = activity;
+                    break;
+            }
+
+            int steps = sample.getSteps();
+            if (steps > 0) {
+                amount.addSteps(steps);
+            }
+
+            final int distance = sample.getDistanceCm();
+            if (distance >= 0) {
+                amount.addDistance(distance);
+            }
+
+            final int activeCalories = sample.getActiveCalories();
+            if (activeCalories > 0) {
+                amount.addActiveCalories(activeCalories);
+            }
+
+            if (previousSample != null) {
+                long timeDifference = sample.getTimestamp() - previousSample.getTimestamp();
+                if (previousSample.getRawKind() == sample.getRawKind()) {
+                    amount.addSeconds(timeDifference);
+                } else {
+                    long sharedTimeDifference = (long) (timeDifference / 2.0f);
+                    previousAmount.addSeconds(sharedTimeDifference);
+                    amount.addSeconds(sharedTimeDifference);
+                }
+
+                // add time
+                if (steps > 0 && sample.getKind() == ActivityKind.ACTIVITY) {
+                    if (steps > maxSpeed) {
+                        maxSpeed = steps;
+                    }
+
+                    if (!stats.containsKey(steps)) {
+//                        LOG.debug("Adding: " + steps);
+                        stats.put(steps, timeDifference);
+                    } else {
+                        long time = stats.get(steps);
+//                        LOG.debug("Updating: " + steps + " " + timeDifference + time);
+                        stats.put(steps, timeDifference + time);
+                    }
+                }
+            }
+
+            amount.setStartDate(sample.getTimestamp());
+            amount.setEndDate(sample.getTimestamp());
+
+            previousAmount = amount;
+            previousSample = sample;
+        }
+
+        ActivityAmounts result = new ActivityAmounts();
+        if (deepSleep.getTotalSeconds() > 0) {
+            result.addAmount(deepSleep);
+        }
+        if (lightSleep.getTotalSeconds() > 0) {
+            result.addAmount(lightSleep);
+        }
+        if (remSleep.getTotalSeconds() > 0) {
+            result.addAmount(remSleep);
+        }
+        if (awakeSleep.getTotalSeconds() > 0) {
+            result.addAmount(awakeSleep);
+        }
+        if (activity.getTotalSeconds() > 0) {
+            result.addAmount(activity);
+        }
+        if (notWorn.getTotalSeconds() > 0) {
+            result.addAmount(notWorn);
+        }
+
+        result.calculatePercentages();
+
+        return result;
+    }
+
+    int calculateTotalSteps(List<? extends ActivitySample> samples) {
+        int totalSteps = 0;
+        for (ActivitySample sample : samples) {
+            int steps = sample.getSteps();
+            if (steps > 0) {
+                totalSteps += steps;
+            }
+        }
+        return totalSteps;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ActivityChartsActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ActivityChartsActivity.java
new file mode 100644
index 0000000..743ae91
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ActivityChartsActivity.java
@@ -0,0 +1,277 @@
+/*  Copyright (C) 2023-2024 Daniel Dakhno, Jos茅 Rebelo, Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities.charts;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentManager;
+import androidx.fragment.app.FragmentStatePagerAdapter;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.AbstractFragmentPagerAdapter;
+import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBFragment;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityAmounts;
+import nodomain.freeyourgadget.gadgetbridge.model.RecordedDataTypes;
+import nodomain.freeyourgadget.gadgetbridge.util.LimitedQueue;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+public class ActivityChartsActivity extends AbstractChartsActivity {
+    LimitedQueue<Integer, ActivityAmounts> mActivityAmountCache = new LimitedQueue<>(60);
+
+    @Override
+    protected AbstractFragmentPagerAdapter createFragmentPagerAdapter(final FragmentManager fragmentManager) {
+        return new SectionsPagerAdapter(fragmentManager);
+    }
+
+    @Override
+    protected int getRecordedDataType() {
+        return RecordedDataTypes.TYPE_ACTIVITY | RecordedDataTypes.TYPE_STRESS;
+    }
+
+    @Override
+    protected boolean supportsRefresh() {
+        final DeviceCoordinator coordinator = getDevice().getDeviceCoordinator();
+        return coordinator.supportsActivityDataFetching();
+    }
+
+    @Override
+    protected boolean allowRefresh() {
+        final DeviceCoordinator coordinator = getDevice().getDeviceCoordinator();
+        return coordinator.allowFetchActivityData(getDevice()) && supportsRefresh();
+    }
+
+    @Override
+    protected List<String> fillChartsTabsList() {
+        return fillChartsTabsList(getDevice(), this);
+    }
+
+    private static List<String> fillChartsTabsList(final GBDevice device, final Context context) {
+        final List<String> tabList;
+        final Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(device.getAddress()));
+        final String myTabs = prefs.getString(DeviceSettingsPreferenceConst.PREFS_DEVICE_CHARTS_TABS, null);
+
+        if (myTabs == null) {
+            //make list mutable to be able to remove items later
+            tabList = new ArrayList<>(Arrays.asList(context.getResources().getStringArray(R.array.pref_charts_tabs_items_default)));
+        } else {
+            tabList = new ArrayList<>(Arrays.asList(myTabs.split(",")));
+        }
+        final DeviceCoordinator coordinator = device.getDeviceCoordinator();
+        if (!coordinator.supportsActivityTabs()) {
+            tabList.remove("activity");
+            tabList.remove("activitylist");
+        }
+        if (!coordinator.supportsSleepMeasurement()) {
+            tabList.remove("sleep");
+        }
+        if (!coordinator.supportsStressMeasurement()) {
+            tabList.remove("stress");
+        }
+        if (!coordinator.supportsPai()) {
+            tabList.remove("pai");
+        }
+        if (!coordinator.supportsSpo2(device)) {
+            tabList.remove("spo2");
+        }
+        if (!coordinator.supportsStepCounter()) {
+            tabList.remove("stepsweek");
+        }
+        if (!coordinator.supportsSpeedzones()) {
+            tabList.remove("speedzones");
+        }
+        if (!coordinator.supportsRealtimeData()) {
+            tabList.remove("livestats");
+        }
+        if (!coordinator.supportsTemperatureMeasurement()) {
+            tabList.remove("temperature");
+        }
+        if (!coordinator.supportsCyclingData()) {
+            tabList.remove("cycling");
+        }
+        if (!coordinator.supportsWeightMeasurement()) {
+            tabList.remove("weight");
+        }
+        if (!coordinator.supportsHrvMeasurement()) {
+            tabList.remove("hrvstatus");
+        }
+        if (!coordinator.supportsHeartRateMeasurement(device)) {
+            tabList.remove("heartrate");
+        }
+        if (!coordinator.supportsBodyEnergy()) {
+            tabList.remove("bodyenergy");
+        }
+        if (!coordinator.supportsVO2Max()) {
+            tabList.remove("vo2max");
+        }
+        if (!coordinator.supportsActiveCalories()) {
+            tabList.remove("calories");
+        }
+        if (!coordinator.supportsRespiratoryRate()) {
+            tabList.remove("respiratoryrate");
+        }
+        return tabList;
+    }
+
+    public static int getChartsTabIndex(final String tab, final GBDevice device, final Context context) {
+        final List<String> enabledTabsList = fillChartsTabsList(device, context);
+        return enabledTabsList.indexOf(tab);
+    }
+
+    /**
+     * A {@link FragmentStatePagerAdapter} that returns a fragment corresponding to
+     * one of the sections/tabs/pages.
+     */
+    private class SectionsPagerAdapter extends AbstractFragmentPagerAdapter {
+        SectionsPagerAdapter(FragmentManager fm) {
+            super(fm);
+        }
+
+        @NonNull
+        @Override
+        public Fragment getItem(int position) {
+            final DeviceCoordinator coordinator = getDevice().getDeviceCoordinator();
+            // getItem is called to instantiate the fragment for the given page.
+            switch (enabledTabsList.get(position)) {
+                case "activity":
+                    return new ActivitySleepChartFragment();
+                case "activitylist":
+                    return new ActivityListingChartFragment();
+                case "sleep":
+                    return SleepCollectionFragment.newInstance(enabledTabsList.size() == 1);
+                case "heartrate":
+                    return new HeartRateDailyFragment();
+                case "hrvstatus":
+                    return new HRVStatusFragment();
+                case "bodyenergy":
+                    return new BodyEnergyFragment();
+                case "vo2max":
+                    return new VO2MaxFragment();
+                case "stress":
+                    return new StressChartFragment();
+                case "pai":
+                    return new PaiChartFragment();
+                case "stepsweek":
+                    return StepsCollectionFragment.newInstance(enabledTabsList.size() == 1);
+                case "speedzones":
+                    return new SpeedZonesFragment();
+                case "livestats":
+                    return new LiveActivityFragment();
+                case "spo2":
+                    return new Spo2ChartFragment();
+                case "temperature":
+                    return coordinator.supportsContinuousTemperature()? new TemperatureDailyFragment(): new TemperatureChartFragment();
+                case "cycling":
+                    return new CyclingChartFragment();
+                case "weight":
+                    return new WeightChartFragment();
+                case "calories":
+                    Intent intent = getIntent();
+                    String mode = intent.getStringExtra(ActivityChartsActivity.EXTRA_MODE);
+                    return CaloriesDailyFragment.newInstance(mode);
+                case "respiratoryrate":
+                    return RespiratoryRateCollectionFragment.newInstance(enabledTabsList.size() == 1);
+            }
+
+            return new UnknownFragment();
+        }
+
+        @Override
+        public int getCount() {
+            return enabledTabsList.toArray().length;
+        }
+
+        @Override
+        public CharSequence getPageTitle(int position) {
+            switch (enabledTabsList.get(position)) {
+                case "activity":
+                    return getString(R.string.activity_sleepchart_activity_and_sleep);
+                case "activitylist":
+                    return getString(R.string.charts_activity_list);
+                case "sleep":
+                    return getString(R.string.sleepchart_your_sleep);
+                case "heartrate":
+                    return getString(R.string.menuitem_hr);
+                case "hrvstatus":
+                    return getString(R.string.pref_header_hrv_status);
+                case "bodyenergy":
+                    return getString(R.string.body_energy);
+                case "vo2max":
+                    return getString(R.string.menuitem_vo2_max);
+                case "stress":
+                    return getString(R.string.menuitem_stress);
+                case "pai":
+                    return getString(getDevice().getDeviceCoordinator().getPaiName());
+                case "stepsweek":
+                    return getString(R.string.steps);
+                case "speedzones":
+                    return getString(R.string.stats_title);
+                case "livestats":
+                    return getString(R.string.liveactivity_live_activity);
+                case "spo2":
+                    return getString(R.string.pref_header_spo2);
+                case "temperature":
+                    return getString(R.string.menuitem_temperature);
+                case "cycling":
+                    return getString(R.string.title_cycling);
+                case "weight":
+                    return getString(R.string.menuitem_weight);
+                case "calories":
+                    return getString(R.string.calories);
+                case "respiratoryrate":
+                    return getString(R.string.respiratoryrate);
+            }
+
+            return String.format(Locale.getDefault(), "Unknown %d", position);
+        }
+    }
+
+    /**
+     * A dummy fragment to avoid a crash when we get a unknown tab position (eg. broken
+     * preference migration).
+     */
+    public static class UnknownFragment extends AbstractGBFragment {
+        @Override
+        public View onCreateView(@NonNull final LayoutInflater inflater,
+                                 final ViewGroup container,
+                                 final Bundle savedInstanceState) {
+            return null;
+        }
+
+        @Nullable
+        @Override
+        protected CharSequence getTitle() {
+            return "Unknown";
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ActivityListingAdapter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ActivityListingAdapter.java
new file mode 100644
index 0000000..a83753d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ActivityListingAdapter.java
@@ -0,0 +1,286 @@
+/*  Copyright (C) 2020-2024 Jos茅 Rebelo, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities.charts;
+
+import android.content.Context;
+import android.graphics.Color;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.appcompat.content.res.AppCompatResources;
+
+import com.github.mikephil.charting.animation.Easing;
+import com.github.mikephil.charting.charts.PieChart;
+import com.github.mikephil.charting.data.PieData;
+import com.github.mikephil.charting.data.PieDataSet;
+import com.github.mikephil.charting.data.PieEntry;
+import com.github.mikephil.charting.utils.MPPointF;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.text.DecimalFormat;
+import java.util.ArrayList;
+import java.util.concurrent.TimeUnit;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.adapter.AbstractActivityListingAdapter;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityListItem;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySession;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
+import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.FormatUtils;
+
+public class ActivityListingAdapter extends AbstractActivityListingAdapter<ActivitySession> {
+    public static final String CHART_COLOR_START = "#e74c3c";
+    public static final String CHART_COLOR_END = "#2ecc71";
+    protected static final Logger LOG = LoggerFactory.getLogger(ActivityListingAdapter.class);
+    protected final int ANIM_TIME = 250;
+    ActivityUser activityUser = new ActivityUser();
+    int stepsGoal = activityUser.getStepsGoal();
+    int distanceGoalMeters = activityUser.getDistanceGoalMeters();
+    long activeTimeGoalTimeMillis = activityUser.getActiveTimeGoalMinutes() * 60 * 1000L;
+
+    public ActivityListingAdapter(Context context) {
+        super(context);
+    }
+
+    @NonNull
+    @Override
+    public AbstractActivityListingViewHolder<ActivitySession> onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) {
+        switch (viewType) {
+            case 0: // dashboard
+                return new DashboardViewHolder(LayoutInflater.from(getContext()).inflate(R.layout.activity_list_dashboard_item, parent, false));
+            case 2: // item
+                return new ActivityItemViewHolder(LayoutInflater.from(getContext()).inflate(R.layout.activity_list_item, parent, false));
+        }
+
+        return super.onCreateViewHolder(parent, viewType);
+    }
+
+    private void setChartsData(PieChart pieChart, float value, float target, String label, Context context) {
+        ArrayList<PieEntry> entries = new ArrayList<>();
+        entries.add(new PieEntry(value, AppCompatResources.getDrawable(context, R.drawable.ic_star_gold)));
+
+        Easing.EasingFunction animationEffect = Easing.EaseInOutSine;
+
+        if (value < target) {
+            entries.add(new PieEntry(target - value));
+        }
+
+        pieChart.setCenterText(String.format("%d%%\n%s", (int) (value * 100 / target), label));
+        float colorValue = Math.max(0, Math.min(1, value / target));
+        int chartColor = interpolateColor(Color.parseColor(CHART_COLOR_START), Color.parseColor(CHART_COLOR_END), colorValue);
+
+        PieDataSet dataSet = new PieDataSet(entries, "");
+        dataSet.setDrawIcons(false);
+        dataSet.setIconsOffset(new MPPointF(0, -66));
+
+        if (colorValue == 1) {
+            dataSet.setDrawIcons(true);
+        }
+        dataSet.setSliceSpace(0f);
+        dataSet.setSelectionShift(5f);
+        dataSet.setColors(chartColor, Color.LTGRAY);
+
+        PieData data = new PieData(dataSet);
+        data.setValueTextSize(0f);
+        data.setValueTextColor(Color.WHITE);
+
+        pieChart.setData(data);
+        pieChart.invalidate();
+        pieChart.animateY(ANIM_TIME, animationEffect);
+    }
+
+    private float interpolate(float a, float b, float proportion) {
+        return (a + ((b - a) * proportion));
+    }
+
+    private int interpolateColor(int a, int b, float proportion) {
+        float[] hsva = new float[3];
+        float[] hsvb = new float[3];
+        Color.colorToHSV(a, hsva);
+        Color.colorToHSV(b, hsvb);
+        for (int i = 0; i < 3; i++) {
+            hsvb[i] = interpolate(hsva[i], hsvb[i], proportion);
+        }
+        return Color.HSVToColor(hsvb);
+    }
+
+    public static class ActivityItemViewHolder extends AbstractActivityListingViewHolder<ActivitySession> {
+        final View rootView;
+        final ActivityListItem activityListItem;
+
+        public ActivityItemViewHolder(@NonNull final View itemView) {
+            super(itemView);
+            this.rootView = itemView;
+            this.activityListItem = new ActivityListItem(itemView);
+        }
+
+        @Override
+        public void fill(final int position, final ActivitySession session, final boolean selected) {
+            this.activityListItem.update(
+                    session.getStartTime(),
+                    session.getEndTime(),
+                    session.getActivityKind(),
+                    null,
+                    session.getActiveSteps(),
+                    session.getDistance(),
+                    session.getHeartRateAverage(),
+                    session.getIntensity(),
+                    session.getEndTime().getTime() - session.getStartTime().getTime(),
+                    false,
+                    null,
+                    position % 2 == 1,
+                    selected
+            );
+        }
+    }
+
+    public class DashboardViewHolder extends AbstractActivityListingViewHolder<ActivitySession> {
+        final PieChart ActiveStepsChart;
+        final PieChart DistanceChart;
+        final PieChart ActiveTimeChart;
+        final TextView stepLabel;
+        final TextView stepTotalLabel;
+        final TextView distanceLabel;
+        final TextView hrLabel;
+        final TextView intensityLabel;
+        final TextView intensity2Label;
+        final TextView durationLabel;
+        final TextView sessionCountLabel;
+        final LinearLayout durationLayout;
+        final LinearLayout countLayout;
+        final View hrLayout;
+        final LinearLayout stepsLayout;
+        final LinearLayout stepsTotalLayout;
+        final LinearLayout distanceLayout;
+        final View intensityLayout;
+        final View intensity2Layout;
+
+        public DashboardViewHolder(@NonNull final View itemView) {
+            super(itemView);
+
+            ActiveStepsChart = itemView.findViewById(R.id.activity_dashboard_piechart1);
+            DistanceChart = itemView.findViewById(R.id.activity_dashboard_piechart2);
+            ActiveTimeChart = itemView.findViewById(R.id.activity_dashboard_piechart3);
+            stepLabel = itemView.findViewById(R.id.line_layout_step_label);
+            stepTotalLabel = itemView.findViewById(R.id.line_layout_total_step_label);
+            distanceLabel = itemView.findViewById(R.id.line_layout_distance_label);
+            hrLabel = itemView.findViewById(R.id.heartrate_widget_label);
+            intensityLabel = itemView.findViewById(R.id.intensity_widget_label);
+            intensity2Label = itemView.findViewById(R.id.line_layout_intensity2_label);
+            durationLabel = itemView.findViewById(R.id.line_layout_duration_label);
+            sessionCountLabel = itemView.findViewById(R.id.line_layout_count_label);
+            durationLayout = itemView.findViewById(R.id.line_layout_duration);
+            countLayout = itemView.findViewById(R.id.line_layout_count);
+            hrLayout = itemView.findViewById(R.id.heartrate_widget_icon);
+            stepsLayout = itemView.findViewById(R.id.line_layout_step);
+            stepsTotalLayout = itemView.findViewById(R.id.line_layout_total_step);
+            distanceLayout = itemView.findViewById(R.id.line_layout_distance);
+            intensityLayout = itemView.findViewById(R.id.intensity_widget_icon);
+            intensity2Layout = itemView.findViewById(R.id.line_layout_intensity2);
+        }
+
+        @Override
+        public void fill(final int position, final ActivitySession session, final boolean selected) {
+            setUpChart(ActiveStepsChart);
+            int steps = session.getActiveSteps();
+            setChartsData(ActiveStepsChart, steps, stepsGoal, getContext().getString(R.string.activity_list_summary_active_steps), getContext());
+
+            setUpChart(DistanceChart);
+            float distance = session.getDistance();
+            setChartsData(DistanceChart, distance, distanceGoalMeters, getContext().getString(R.string.distance), getContext());
+
+            setUpChart(ActiveTimeChart);
+            long duration = session.getEndTime().getTime() - session.getStartTime().getTime();
+            setChartsData(ActiveTimeChart, duration, activeTimeGoalTimeMillis, getContext().getString(R.string.activity_list_summary_active_time), getContext());
+
+            durationLabel.setText(DateTimeUtils.formatDurationHoursMinutes(duration, TimeUnit.MILLISECONDS));
+            sessionCountLabel.setText(String.valueOf(session.getSessionCount()));
+
+            if (session.getHeartRateAverage() > 0) {
+                hrLabel.setText(String.valueOf(session.getHeartRateAverage()));
+                hrLayout.setVisibility(View.VISIBLE);
+                hrLabel.setVisibility(View.VISIBLE);
+            } else {
+                hrLayout.setVisibility(View.GONE);
+                hrLabel.setVisibility(View.GONE);
+            }
+
+            if (session.getIntensity() > 0) {
+                final DecimalFormat df = new DecimalFormat("###");
+                intensityLabel.setText(df.format(session.getIntensity()));
+                intensity2Label.setText(df.format(session.getIntensity()));
+                intensityLayout.setVisibility(View.VISIBLE);
+                intensity2Layout.setVisibility(View.VISIBLE);
+                intensityLabel.setVisibility(View.VISIBLE);
+                intensity2Label.setVisibility(View.VISIBLE);
+            } else {
+                intensityLayout.setVisibility(View.GONE);
+                intensity2Layout.setVisibility(View.GONE);
+                intensityLabel.setVisibility(View.GONE);
+                intensity2Label.setVisibility(View.GONE);
+            }
+
+            if (session.getDistance() > 0) {
+                distanceLabel.setText(FormatUtils.getFormattedDistanceLabel(session.getDistance()));
+                distanceLayout.setVisibility(View.VISIBLE);
+            } else {
+                distanceLayout.setVisibility(View.GONE);
+            }
+
+            if (session.getActiveSteps() > 0) {
+                stepLabel.setText(String.valueOf(session.getActiveSteps()));
+                stepsLayout.setVisibility(View.VISIBLE);
+            } else {
+                stepsLayout.setVisibility(View.GONE);
+            }
+
+            if (session.getTotalDaySteps() > 0) {
+                stepTotalLabel.setText(String.valueOf(session.getTotalDaySteps()));
+                stepsTotalLayout.setVisibility(View.VISIBLE);
+                countLayout.setVisibility(View.VISIBLE);
+                durationLayout.setVisibility(View.VISIBLE);
+            } else {
+                stepsTotalLayout.setVisibility(View.GONE);
+                countLayout.setVisibility(View.GONE);
+                durationLayout.setVisibility(View.GONE);
+            }
+        }
+
+        private void setUpChart(PieChart DashboardChart) {
+            DashboardChart.setNoDataText("");
+            DashboardChart.getLegend().setEnabled(false);
+            DashboardChart.setDrawHoleEnabled(true);
+            DashboardChart.setHoleColor(Color.WHITE);
+            DashboardChart.getDescription().setText("");
+            DashboardChart.setTransparentCircleColor(Color.WHITE);
+            DashboardChart.setTransparentCircleAlpha(110);
+            DashboardChart.setHoleRadius(70f);
+            DashboardChart.setTransparentCircleRadius(75f);
+            DashboardChart.setDrawCenterText(true);
+            DashboardChart.setRotationEnabled(true);
+            DashboardChart.setHighlightPerTapEnabled(true);
+            DashboardChart.setCenterTextOffset(0, 0);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ActivityListingChartFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ActivityListingChartFragment.java
new file mode 100644
index 0000000..a3a228a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ActivityListingChartFragment.java
@@ -0,0 +1,244 @@
+/*  Copyright (C) 2020-2024 Jos茅 Rebelo, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities.charts;
+
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Color;
+import android.os.Bundle;
+import android.text.format.DateUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import androidx.fragment.app.FragmentManager;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.github.mikephil.charting.charts.Chart;
+import com.google.android.material.floatingactionbutton.FloatingActionButton;
+import com.google.android.material.snackbar.Snackbar;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySession;
+import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.FormatUtils;
+
+public class ActivityListingChartFragment extends AbstractActivityChartFragment<ActivityListingChartFragment.MyChartsData> {
+    protected static final Logger LOG = LoggerFactory.getLogger(ActivityListingChartFragment.class);
+    int tsDateTo;
+
+    private View rootView;
+    private ActivityListingAdapter stepListAdapter;
+    private TextView stepsDateView;
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+                             Bundle savedInstanceState) {
+        rootView = inflater.inflate(R.layout.fragment_steps_list, container, false);
+        getChartsHost().enableSwipeRefresh(false);
+        RecyclerView stepsList = rootView.findViewById(R.id.itemListView);
+        stepListAdapter = new ActivityListingAdapter(getContext());
+        stepsList.setAdapter(stepListAdapter);
+        stepsList.setLayoutManager(new LinearLayoutManager(requireContext()));
+
+        stepListAdapter.setOnItemClickListener(position -> {
+            ActivitySession item = stepListAdapter.getItem(position);
+            if (item.getSessionType() != ActivitySession.SESSION_SUMMARY) {
+                int tsFrom = (int) (item.getStartTime().getTime() / 1000);
+                int tsTo = (int) (item.getEndTime().getTime() / 1000);
+                showDetail(tsFrom, tsTo, item, getChartsHost().getDevice());
+            }
+        });
+
+        stepsDateView = rootView.findViewById(R.id.stepsDateView);
+        FloatingActionButton fab;
+        fab = rootView.findViewById(R.id.fab);
+        fab.setVisibility(View.VISIBLE);
+        fab.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                showDashboard(tsDateTo, getChartsHost().getDevice());
+            }
+        });
+
+        refresh();
+        return rootView;
+    }
+
+
+    @Override
+    public String getTitle() {
+        return "Steps list";
+    }
+
+    @Override
+    protected boolean isSingleDay() {
+        return true;
+    }
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        String action = intent.getAction();
+        if (ChartsHost.REFRESH.equals(action)) {
+            // TODO: use LimitLines to visualize smart alarms?
+            refresh();
+        } else {
+            super.onReceive(context, intent);
+        }
+    }
+
+    @Override
+    protected MyChartsData refreshInBackground(ChartsHost chartsHost, DBHandler db, GBDevice device) {
+        List<? extends ActivitySample> activitySamples;
+        activitySamples = getSamples(db, device);
+        List<ActivitySession> stepSessions = null;
+        ActivitySession ongoingSession = null;
+        StepAnalysis stepAnalysis = new StepAnalysis();
+        boolean isEmptySummary = false;
+
+        if (activitySamples != null) {
+            stepSessions = stepAnalysis.calculateStepSessions(activitySamples);
+            if (stepSessions.toArray().length == 0) {
+                isEmptySummary = true;
+            }
+            ActivitySession stepSessionsSummary = stepAnalysis.calculateSummary(stepSessions, isEmptySummary);
+            stepSessions.add(0, stepSessionsSummary);
+            ActivitySession emptySession = new ActivitySession();
+            emptySession.setSessionType(ActivitySession.SESSION_EMPTY);
+            stepSessions.add(emptySession); //this is to have an empty item at the end to be able to use FAB without it blocking anything
+            ongoingSession = stepAnalysis.getOngoingSessions(stepSessions);
+        }
+        return new MyChartsData(stepSessions, ongoingSession);
+    }
+
+    @Override
+    protected void updateChartsnUIThread(MyChartsData mcd) {
+        if (mcd == null) {
+            return;
+        }
+        if (mcd.getStepSessions() == null) {
+            return;
+        }
+
+        //noinspection RedundantIfStatement
+        if (mcd.getStepSessions().toArray().length == 0) {
+            getChartsHost().enableSwipeRefresh(true); //enable pull to refresh, might be needed
+        } else {
+            getChartsHost().enableSwipeRefresh(false); //disable pull to refresh as it collides with swipable view
+        }
+
+        Date activityDate = new Date(tsDateTo * 1000L);
+        stepsDateView.setText(DateTimeUtils.formatDate(activityDate, DateUtils.FORMAT_SHOW_WEEKDAY));
+
+        if (GBApplication.getPrefs().getBoolean("charts_show_ongoing_activity", true)) {
+            if (mcd.getOngoingSession() != null && DateUtils.isToday(activityDate.getTime())) {
+                showOngoingActivitySnackbar(mcd.getOngoingSession());
+            }
+        }
+        stepListAdapter.setItems(mcd.getStepSessions(), true);
+    }
+
+    @Override
+    protected void renderCharts() {
+    }
+
+    @Override
+    protected void setupLegend(Chart<?> chart) {
+    }
+
+    @Override
+    protected List<? extends ActivitySample> getSamples(DBHandler db, GBDevice device, int tsFrom, int tsTo) {
+        Calendar day = Calendar.getInstance();
+        day.setTimeInMillis(tsTo * 1000L); //we need today initially, which is the end of the time range
+        day.set(Calendar.HOUR_OF_DAY, 0); //and we set time for the start and end of the same day
+        day.set(Calendar.MINUTE, 0);
+        day.set(Calendar.SECOND, 0);
+        tsFrom = (int) (day.getTimeInMillis() / 1000);
+        tsTo = tsFrom + 24 * 60 * 60 - 1;
+        tsDateTo = tsTo;
+        return getAllSamples(db, device, tsFrom, tsTo);
+    }
+
+    private void showOngoingActivitySnackbar(ActivitySession ongoingSession) {
+
+        String distanceLabel = FormatUtils.getFormattedDistanceLabel(ongoingSession.getDistance());
+        String stepLabel = String.valueOf(ongoingSession.getActiveSteps());
+        String durationLabel = DateTimeUtils.formatDurationHoursMinutes(ongoingSession.getEndTime().getTime() - ongoingSession.getStartTime().getTime(), TimeUnit.MILLISECONDS);
+        String hrLabel = String.valueOf(ongoingSession.getHeartRateAverage());
+        String activityName = ongoingSession.getActivityKind().getLabel(requireContext());
+        int icon = ongoingSession.getActivityKind().getIcon();
+
+        String text = String.format("%s:\u00A0%s, %s:\u00A0%s, %s:\u00A0%s, %s:\u00A0%s", activityName, durationLabel, getString(R.string.heart_rate), hrLabel, getString(R.string.steps), stepLabel, getString(R.string.distance), distanceLabel);
+        final Snackbar snackbar = Snackbar.make(rootView, text, 1000 * 8);
+
+        View snackbarView = snackbar.getView();
+        snackbarView.setBackgroundColor(requireContext().getResources().getColor(R.color.accent));
+        snackbar.setActionTextColor(Color.WHITE);
+        snackbar.setAction(getString(R.string.dialog_hide).toUpperCase(), new View.OnClickListener() {
+                    @Override
+                    public void onClick(View view) {
+                        snackbar.dismiss();
+                    }
+                }
+        );
+        snackbar.show();
+    }
+
+    private void showDashboard(int date, GBDevice device) {
+        FragmentManager fm = requireActivity().getSupportFragmentManager();
+        ActivityListingDashboard listingDashboardFragment = ActivityListingDashboard.newInstance(date, device);
+        listingDashboardFragment.show(fm, "activity_list_total_dashboard");
+    }
+
+    private void showDetail(int tsFrom, int tsTo, ActivitySession item, GBDevice device) {
+        FragmentManager fm = requireActivity().getSupportFragmentManager();
+        ActivityListingDetail listingDetailFragment = ActivityListingDetail.newInstance(tsFrom, tsTo, item, device);
+        listingDetailFragment.show(fm, "activity_list_detail");
+    }
+
+    protected static final class MyChartsData extends ChartsData {
+        private final List<ActivitySession> stepSessions;
+        private final ActivitySession ongoingSession;
+
+        MyChartsData(List<ActivitySession> stepSessions, ActivitySession ongoingSession) {
+            this.stepSessions = stepSessions;
+            this.ongoingSession = ongoingSession;
+        }
+
+        public List<ActivitySession> getStepSessions() {
+            return stepSessions;
+        }
+
+        public ActivitySession getOngoingSession() {
+            return this.ongoingSession;
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ActivityListingDashboard.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ActivityListingDashboard.java
new file mode 100644
index 0000000..7c4d6ac
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ActivityListingDashboard.java
@@ -0,0 +1,329 @@
+/*  Copyright (C) 2021-2024 Arjan Schrijver, Daniel Dakhno, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities.charts;
+
+import android.app.DatePickerDialog;
+import android.content.Context;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.DatePicker;
+import android.widget.LinearLayout;
+import android.widget.RelativeLayout;
+import android.widget.SeekBar;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.fragment.app.FragmentActivity;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.database.DBAccess;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.AbstractActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySession;
+import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.FormatUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.dialogs.MaterialDialogFragment;
+
+public class ActivityListingDashboard extends MaterialDialogFragment {
+    protected static final Logger LOG = LoggerFactory.getLogger(ActivityListingDashboard.class);
+    GBDevice gbDevice;
+    ActivityListingAdapter stepListAdapter;
+    ActivitySession stepSessionsSummary;
+    private int timeFrom;
+    private int timeTo;
+
+    public ActivityListingDashboard() {
+
+    }
+
+    public static ActivityListingDashboard newInstance(int timestamp, GBDevice device) {
+
+        ActivityListingDashboard frag = new ActivityListingDashboard();
+
+        Bundle args = new Bundle();
+        args.putInt("time", timestamp);
+        args.putParcelable(GBDevice.EXTRA_DEVICE, device);
+        frag.setArguments(args);
+        return frag;
+
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+
+                             Bundle savedInstanceState) {
+
+        return inflater.inflate(R.layout.activity_list_total_dashboard, container);
+    }
+
+    @Override
+
+    public void onViewCreated(@NonNull final View view, @Nullable Bundle savedInstanceState) {
+        super.onViewCreated(view, savedInstanceState);
+
+        int time = getArguments().getInt("time", 1);
+        Calendar day = Calendar.getInstance();
+        day.setTimeInMillis(time * 1000L);
+        day.set(Calendar.HOUR_OF_DAY, 23);
+        day.set(Calendar.MINUTE, 59);
+        day.set(Calendar.SECOND, 59);
+        timeTo = (int) (day.getTimeInMillis() / 1000);
+
+
+        gbDevice = getArguments().getParcelable(GBDevice.EXTRA_DEVICE);
+        if (gbDevice == null) {
+            throw new IllegalArgumentException("Must provide a device when invoking this activity");
+        }
+        stepListAdapter = new ActivityListingAdapter(getContext());
+
+        final TextView battery_status_date_from_text = getView().findViewById(R.id.battery_status_date_from_text);
+        final TextView battery_status_date_to_text = getView().findViewById(R.id.battery_status_date_to_text);
+        LinearLayout battery_status_date_to_layout = getView().findViewById(R.id.battery_status_date_to_layout);
+        final SeekBar battery_status_time_span_seekbar = getView().findViewById(R.id.battery_status_time_span_seekbar);
+
+        boolean activity_list_debug_extra_time_range_value = GBApplication.getPrefs().getPreferences().getBoolean("activity_list_debug_extra_time_range", false);
+
+        if (!activity_list_debug_extra_time_range_value) {
+            battery_status_time_span_seekbar.setMax(3);
+        }
+        final TextView battery_status_time_span_text = (TextView) getView().findViewById(R.id.battery_status_time_span_text);
+
+        battery_status_time_span_seekbar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
+
+            @Override
+            public void onProgressChanged(SeekBar seekBar, int i, boolean b) {
+                String text;
+                switch (i) {
+                    case 0:
+                        text = getString(R.string.calendar_day);
+                        timeFrom = DateTimeUtils.shiftDays(timeTo, -1);
+                        break;
+                    case 1:
+                        text = getString(R.string.calendar_week);
+                        timeFrom = DateTimeUtils.shiftDays(timeTo, -7);
+                        break;
+                    case 2:
+                        text = getString(R.string.calendar_two_weeks);
+                        timeFrom = DateTimeUtils.shiftDays(timeTo, -14);
+                        break;
+                    case 3:
+                        text = getString(R.string.calendar_month);
+                        timeFrom = DateTimeUtils.shiftMonths(timeTo, -1);
+                        break;
+                    case 4:
+                        text = getString(R.string.calendar_three_months);
+                        timeFrom = DateTimeUtils.shiftMonths(timeTo, -3);
+                        break;
+                    case 5:
+                        text = getString(R.string.calendar_six_months);
+                        timeFrom = DateTimeUtils.shiftMonths(timeTo, -6);
+                        break;
+                    case 6:
+                        text = getString(R.string.calendar_year);
+                        timeFrom = DateTimeUtils.shiftMonths(timeTo, -12);
+                        break;
+                    default:
+                        text = getString(R.string.calendar_two_weeks);
+                        timeFrom = DateTimeUtils.shiftDays(timeTo, -14);
+
+                }
+
+                battery_status_time_span_text.setText(text);
+                battery_status_date_from_text.setText(DateTimeUtils.formatDate(new Date(timeFrom * 1000L)));
+                battery_status_date_to_text.setText(DateTimeUtils.formatDate(new Date(timeTo * 1000L)));
+                createRefreshTask("Visualizing step sessions", getActivity()).execute();
+            }
+
+            @Override
+            public void onStartTrackingTouch(SeekBar seekBar) {
+
+            }
+
+            @Override
+            public void onStopTrackingTouch(SeekBar seekBar) {
+
+            }
+        });
+        battery_status_date_to_layout.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                final Calendar currentDate = Calendar.getInstance();
+                currentDate.setTimeInMillis(timeTo * 1000L);
+
+                new DatePickerDialog(getContext(), new DatePickerDialog.OnDateSetListener() {
+                    @Override
+                    public void onDateSet(DatePicker view, int year, int monthOfYear, int dayOfMonth) {
+
+                        Calendar date = Calendar.getInstance();
+                        date.set(year, monthOfYear, dayOfMonth);
+                        int time = (int) (date.getTimeInMillis() / 1000);
+                        Calendar day = Calendar.getInstance();
+                        day.setTimeInMillis(time * 1000L);
+                        day.set(Calendar.HOUR_OF_DAY, 23);
+                        day.set(Calendar.MINUTE, 59);
+                        day.set(Calendar.SECOND, 59);
+                        timeTo = (int) (day.getTimeInMillis() / 1000);
+
+                        battery_status_date_to_text.setText(DateTimeUtils.formatDate(new Date(timeTo * 1000L)));
+                        battery_status_time_span_seekbar.setProgress(0);
+                        battery_status_time_span_seekbar.setProgress(1);
+                    }
+                }, currentDate.get(Calendar.YEAR), currentDate.get(Calendar.MONTH), currentDate.get(Calendar.DATE)).show();
+            }
+        });
+        battery_status_time_span_seekbar.setProgress(2);
+    }
+
+    protected RefreshTask createRefreshTask(String task, Context context) {
+        return new RefreshTask(task, context);
+    }
+
+    private ActivitySession get_data(GBDevice gbDevice, DBHandler db, int timeFrom, int timeTo) {
+
+        List<ActivitySession> stepSessions;
+        List<? extends ActivitySample> activitySamples = getAllSamples(db, gbDevice, timeFrom, timeTo);
+        StepAnalysis stepAnalysis = new StepAnalysis();
+
+        boolean isEmptySummary = false;
+        if (activitySamples != null) {
+            stepSessions = stepAnalysis.calculateStepSessions(activitySamples);
+            if (stepSessions.toArray().length == 0) {
+                isEmptySummary = true;
+            }
+            stepSessionsSummary = stepAnalysis.calculateSummary(stepSessions, isEmptySummary);
+        }
+        return stepSessionsSummary;
+    }
+
+    SampleProvider<? extends AbstractActivitySample> getProvider(DBHandler db, GBDevice device) {
+        DeviceCoordinator coordinator = device.getDeviceCoordinator();
+        return coordinator.getSampleProvider(device, db.getDaoSession());
+    }
+
+    protected List<? extends ActivitySample> getAllSamples(DBHandler db, GBDevice device, int tsFrom, int tsTo) {
+        SampleProvider<? extends ActivitySample> provider = getProvider(db, device);
+        return provider.getAllActivitySamples(tsFrom, tsTo);
+    }
+
+    void indicate_progress(boolean inProgress) {
+        View view = getView();
+        if (view == null) {
+            return;
+        }
+        LinearLayout activity_list_dashboard_results_layout = view.findViewById(R.id.activity_list_dashboard_results_layout);
+        RelativeLayout activity_list_dashboard_loading_layout = view.findViewById(R.id.activity_list_dashboard_loading_layout);
+        if (inProgress) {
+            activity_list_dashboard_results_layout.setVisibility(View.GONE);
+            activity_list_dashboard_loading_layout.setVisibility(View.VISIBLE);
+        } else {
+            activity_list_dashboard_results_layout.setVisibility(View.VISIBLE);
+            activity_list_dashboard_loading_layout.setVisibility(View.GONE);
+        }
+    }
+
+    void populateData(ActivitySession item) {
+        View view = getView();
+        if (view == null) {
+            return;
+        }
+
+        TextView stepLabel = view.findViewById(R.id.line_layout_step_label);
+        TextView stepTotalLabel = view.findViewById(R.id.line_layout_total_step_label);
+        TextView distanceLabel = view.findViewById(R.id.line_layout_distance_label);
+        TextView durationLabel = view.findViewById(R.id.line_layout_duration_label);
+        TextView sessionCountLabel = view.findViewById(R.id.line_layout_count_label);
+        LinearLayout durationLayout = view.findViewById(R.id.line_layout_duration);
+        LinearLayout countLayout = view.findViewById(R.id.line_layout_count);
+        LinearLayout stepsLayout = view.findViewById(R.id.line_layout_step);
+        LinearLayout stepsTotalLayout = view.findViewById(R.id.line_layout_total_step);
+        LinearLayout distanceLayout = view.findViewById(R.id.line_layout_distance);
+
+        final long duration = item.getEndTime().getTime() - item.getStartTime().getTime();
+        durationLabel.setText(DateTimeUtils.formatDurationHoursMinutes(duration, TimeUnit.MILLISECONDS));
+        sessionCountLabel.setText(String.valueOf(item.getSessionCount()));
+
+        if (item.getDistance() > 0) {
+            distanceLabel.setText(FormatUtils.getFormattedDistanceLabel(item.getDistance()));
+            distanceLayout.setVisibility(View.VISIBLE);
+        } else {
+            distanceLayout.setVisibility(View.GONE);
+        }
+
+        if (item.getActiveSteps() > 0) {
+            stepLabel.setText(String.valueOf(item.getActiveSteps()));
+            stepsLayout.setVisibility(View.VISIBLE);
+        } else {
+            stepsLayout.setVisibility(View.GONE);
+        }
+
+        if (item.getTotalDaySteps() > 0) {
+            stepTotalLabel.setText(String.valueOf(item.getTotalDaySteps()));
+            stepsTotalLayout.setVisibility(View.VISIBLE);
+            countLayout.setVisibility(View.VISIBLE);
+            durationLayout.setVisibility(View.VISIBLE);
+        } else {
+            stepsTotalLayout.setVisibility(View.GONE);
+            countLayout.setVisibility(View.GONE);
+            durationLayout.setVisibility(View.GONE);
+        }
+    }
+
+    public class RefreshTask extends DBAccess {
+
+        public RefreshTask(String task, Context context) {
+            super(task, context);
+        }
+
+        @Override
+        protected void doInBackground(DBHandler db) {
+            stepSessionsSummary = get_data(gbDevice, db, timeFrom, timeTo);
+        }
+
+        @Override
+        protected void onPreExecute() {
+            indicate_progress(true);
+        }
+
+        @Override
+        protected void onPostExecute(Object o) {
+            super.onPostExecute(o);
+            FragmentActivity activity = getActivity();
+            if (activity != null && !activity.isFinishing() && !activity.isDestroyed()) {
+                populateData(stepSessionsSummary);
+                indicate_progress(false);
+            } else {
+                LOG.info("Not filling data because activity is not available anymore");
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ActivityListingDetail.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ActivityListingDetail.java
new file mode 100644
index 0000000..3eaaae0
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ActivityListingDetail.java
@@ -0,0 +1,106 @@
+/*  Copyright (C) 2021-2024 Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities.charts;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+
+import androidx.annotation.Nullable;
+import androidx.fragment.app.DialogFragment;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.ActivitySummariesChartFragment;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityListItem;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySession;
+
+public class ActivityListingDetail extends DialogFragment {
+    protected static final Logger LOG = LoggerFactory.getLogger(ActivityListingDetail.class);
+
+    public ActivityListingDetail() {
+
+    }
+
+    public static ActivityListingDetail newInstance(int tsFrom, int tsTo, ActivitySession item, GBDevice device) {
+        ActivityListingDetail frag = new ActivityListingDetail();
+        Bundle args = new Bundle();
+        args.putInt("tsFrom", tsFrom);
+        args.putInt("tsTo", tsTo);
+        args.putSerializable("item", item);
+        args.putParcelable(GBDevice.EXTRA_DEVICE, device);
+        frag.setArguments(args);
+        return frag;
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+                             Bundle savedInstanceState) {
+        return inflater.inflate(R.layout.activity_list_detail, container);
+    }
+
+    @Override
+    public void onViewCreated(final View view, @Nullable Bundle savedInstanceState) {
+
+        super.onViewCreated(view, savedInstanceState);
+
+        int tsFrom = getArguments().getInt("tsFrom");
+        int tsTo = getArguments().getInt("tsTo");
+        ActivitySession item = (ActivitySession) getArguments().getSerializable("item");
+        GBDevice gbDevice;
+        gbDevice = getArguments().getParcelable(GBDevice.EXTRA_DEVICE);
+        if (gbDevice == null) {
+            throw new IllegalArgumentException("Must provide a device when invoking this activity");
+        }
+
+        final ActivitySummariesChartFragment activitySummariesChartFragment = new ActivitySummariesChartFragment();
+        getChildFragmentManager()
+                .beginTransaction()
+                .replace(R.id.chartsFragmentHolder, activitySummariesChartFragment)
+                .commit();
+        activitySummariesChartFragment.setDateAndGetData(null, gbDevice, tsFrom, tsTo);
+
+        ActivityListingAdapter stepListAdapter = new ActivityListingAdapter(getContext());
+        View activityItem = view.findViewById(R.id.activityItemHolder);
+        ActivityListItem activityListItem = new ActivityListItem(activityItem);
+        activityListItem.update(
+                item.getStartTime(),
+                item.getEndTime(),
+                item.getActivityKind(),
+                null,
+                item.getActiveSteps(),
+                item.getDistance(),
+                item.getHeartRateAverage(),
+                item.getIntensity(),
+                item.getEndTime().getTime() - item.getStartTime().getTime(),
+                false,
+                null,
+                false,
+                false
+        );
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ActivitySleepChartFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ActivitySleepChartFragment.java
new file mode 100644
index 0000000..eb99928
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ActivitySleepChartFragment.java
@@ -0,0 +1,203 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, Carsten Pfeiffer, Daniele
+    Gobbetti, Dikay900, Jos茅 Rebelo, Pavel Elagin
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities.charts;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.github.mikephil.charting.animation.Easing;
+import com.github.mikephil.charting.charts.Chart;
+import com.github.mikephil.charting.charts.LineChart;
+import com.github.mikephil.charting.components.Legend;
+import com.github.mikephil.charting.components.LegendEntry;
+import com.github.mikephil.charting.components.XAxis;
+import com.github.mikephil.charting.components.YAxis;
+import com.github.mikephil.charting.data.LineData;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.HeartRateUtils;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
+
+
+public class ActivitySleepChartFragment extends AbstractActivityChartFragment<DefaultChartsData<LineData>> {
+    protected static final Logger LOG = LoggerFactory.getLogger(ActivitySleepChartFragment.class);
+
+    private LineChart mChart;
+
+    private int mSmartAlarmFrom = -1;
+    private int mSmartAlarmTo = -1;
+    private int mTimestampFrom = -1;
+    private int mSmartAlarmGoneOff = -1;
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+                             Bundle savedInstanceState) {
+        View rootView = inflater.inflate(R.layout.fragment_charts, container, false);
+
+        mChart = (LineChart) rootView.findViewById(R.id.activitysleepchart);
+
+        setupChart();
+
+        return rootView;
+    }
+
+    @Override
+    public String getTitle() {
+        return getString(R.string.activity_sleepchart_activity_and_sleep);
+    }
+
+    private void setupChart() {
+        mChart.setBackgroundColor(BACKGROUND_COLOR);
+        mChart.getDescription().setTextColor(DESCRIPTION_COLOR);
+        configureBarLineChartDefaults(mChart);
+
+
+        XAxis x = mChart.getXAxis();
+        x.setDrawLabels(true);
+        x.setDrawGridLines(false);
+        x.setEnabled(true);
+        x.setTextColor(CHART_TEXT_COLOR);
+        x.setDrawLimitLinesBehindData(true);
+
+        YAxis y = mChart.getAxisLeft();
+        y.setDrawGridLines(false);
+//        y.setDrawLabels(false);
+        // TODO: make fixed max value optional
+        y.setAxisMaximum(1f);
+        y.setAxisMinimum(0);
+        y.setDrawTopYLabelEntry(false);
+        y.setTextColor(CHART_TEXT_COLOR);
+
+//        y.setLabelCount(5);
+        y.setEnabled(true);
+
+        YAxis yAxisRight = mChart.getAxisRight();
+        yAxisRight.setDrawGridLines(false);
+        yAxisRight.setEnabled(supportsHeartrate(getChartsHost().getDevice()));
+        yAxisRight.setDrawLabels(true);
+        yAxisRight.setDrawTopYLabelEntry(true);
+        yAxisRight.setTextColor(CHART_TEXT_COLOR);
+        yAxisRight.setAxisMaximum(HeartRateUtils.getInstance().getMaxHeartRate());
+        yAxisRight.setAxisMinimum(HeartRateUtils.getInstance().getMinHeartRate());
+
+        // refresh immediately instead of use refreshIfVisible(), for perceived performance
+        refresh();
+    }
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        String action = intent.getAction();
+        if (action.equals(ChartsHost.REFRESH)) {
+            // TODO: use LimitLines to visualize smart alarms?
+            mSmartAlarmFrom = intent.getIntExtra("smartalarm_from", -1);
+            mSmartAlarmTo = intent.getIntExtra("smartalarm_to", -1);
+            mTimestampFrom = intent.getIntExtra("recording_base_timestamp", -1);
+            mSmartAlarmGoneOff = intent.getIntExtra("alarm_gone_off", -1);
+            refresh();
+        } else {
+            super.onReceive(context, intent);
+        }
+    }
+
+    @Override
+    protected DefaultChartsData<LineData> refreshInBackground(ChartsHost chartsHost, DBHandler db, GBDevice device) {
+        List<? extends ActivitySample> samples = getSamples(db, device);
+        List<? extends ActivitySample> highResSamples = getSamplesHighRes(db, device);
+        if (highResSamples == null)
+            return refresh(device, samples);
+        return refresh(device, samples, highResSamples);
+    }
+
+    @Override
+    protected void updateChartsnUIThread(DefaultChartsData<LineData> dcd) {
+        mChart.getLegend().setTextColor(LEGEND_TEXT_COLOR);
+        mChart.setData(null); // workaround for https://github.com/PhilJay/MPAndroidChart/issues/2317
+        mChart.getXAxis().setValueFormatter(dcd.getXValueFormatter());
+        mChart.setData((LineData) dcd.getData());
+    }
+
+    @Override
+    protected void renderCharts() {
+        mChart.animateX(ANIM_TIME, Easing.EaseInOutQuart);
+//        mChart.invalidate();
+    }
+
+    @Override
+    protected void setupLegend(Chart<?> chart) {
+        List<LegendEntry> legendEntries = new ArrayList<>(5);
+
+        LegendEntry activityEntry = new LegendEntry();
+        activityEntry.label = akActivity.label;
+        activityEntry.formColor = akActivity.color;
+        legendEntries.add(activityEntry);
+
+        LegendEntry lightSleepEntry = new LegendEntry();
+        lightSleepEntry.label = akLightSleep.label;
+        lightSleepEntry.formColor = akLightSleep.color;
+        legendEntries.add(lightSleepEntry);
+
+        LegendEntry deepSleepEntry = new LegendEntry();
+        deepSleepEntry.label = akDeepSleep.label;
+        deepSleepEntry.formColor = akDeepSleep.color;
+        legendEntries.add(deepSleepEntry);
+
+        if (supportsRemSleep(getChartsHost().getDevice())) {
+            LegendEntry remSleepEntry = new LegendEntry();
+            remSleepEntry.label = akRemSleep.label;
+            remSleepEntry.formColor = akRemSleep.color;
+            legendEntries.add(remSleepEntry);
+        }
+
+        LegendEntry notWornEntry = new LegendEntry();
+        notWornEntry.label = akNotWorn.label;
+        notWornEntry.formColor = akNotWorn.color;
+        legendEntries.add(notWornEntry);
+
+        if (supportsHeartrate(getChartsHost().getDevice())) {
+            LegendEntry hrEntry = new LegendEntry();
+            hrEntry.label = HEARTRATE_LABEL;
+            hrEntry.formColor = HEARTRATE_COLOR;
+            legendEntries.add(hrEntry);
+        }
+        chart.getLegend().setCustom(legendEntries);
+        chart.getLegend().setWordWrapEnabled(true);
+        chart.getLegend().setHorizontalAlignment(Legend.LegendHorizontalAlignment.CENTER);
+    }
+
+    @Override
+    protected List<? extends ActivitySample> getSamples(DBHandler db, GBDevice device, int tsFrom, int tsTo) {
+        return getAllSamples(db, device, tsFrom, tsTo);
+    }
+
+    @Override
+    protected List<? extends ActivitySample> getSamplesHighRes(DBHandler db, GBDevice device, int tsFrom, int tsTo) {
+        return getAllSamplesHighRes(db, device, tsFrom, tsTo);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/AngledLabelsChartRenderer.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/AngledLabelsChartRenderer.java
new file mode 100644
index 0000000..6d034b3
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/AngledLabelsChartRenderer.java
@@ -0,0 +1,42 @@
+/*  Copyright (C) 2019-2024 Andreas Shimokawa, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities.charts;
+
+import android.graphics.Canvas;
+
+import com.github.mikephil.charting.animation.ChartAnimator;
+import com.github.mikephil.charting.interfaces.dataprovider.BarDataProvider;
+import com.github.mikephil.charting.renderer.BarChartRenderer;
+import com.github.mikephil.charting.utils.ViewPortHandler;
+
+public class AngledLabelsChartRenderer extends BarChartRenderer {
+    AngledLabelsChartRenderer(BarDataProvider chart, ChartAnimator animator, ViewPortHandler viewPortHandler) {
+        super(chart, animator, viewPortHandler);
+    }
+
+    @Override
+    public void drawValue(Canvas canvas, String valueText, float x, float y, int color) {
+        mValuePaint.setColor(color);
+        //move position to the center of bar
+        x = x + 8;
+        y = y - 32;
+        canvas.save();
+        canvas.rotate(-90, x, y);
+        canvas.drawText(valueText, x, y, mValuePaint);
+        canvas.restore();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/BarChartStackedTimeValueFormatter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/BarChartStackedTimeValueFormatter.java
new file mode 100644
index 0000000..3392ba8
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/BarChartStackedTimeValueFormatter.java
@@ -0,0 +1,55 @@
+package nodomain.freeyourgadget.gadgetbridge.activities.charts;
+
+import com.github.mikephil.charting.data.BarEntry;
+import com.github.mikephil.charting.formatter.StackedValueFormatter;
+
+import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
+
+public class BarChartStackedTimeValueFormatter extends StackedValueFormatter {
+    private float[] processedValues;
+    private BarEntry lastEntry;
+    private int ignoreLast;
+    private int lastNonZeroIndex;
+    private int index = 0;
+
+    public BarChartStackedTimeValueFormatter(boolean drawWholeStack, String suffix, int decimals, int ignoreLast) {
+        super(drawWholeStack, suffix, decimals);
+        this.ignoreLast = ignoreLast;
+    }
+
+    private int getLastNonZeroIndex(float[] array) {
+        int last = 0;
+        int i = 0;
+        for(float v: array) {
+            last = v == 0 ? last : i;
+            i++;
+        }
+        return last;
+    }
+
+    @Override
+    public String getBarStackedLabel(float value, BarEntry entry) {
+        if (lastEntry != entry) {
+            processedValues = entry.getYVals();
+            lastEntry = entry;
+            lastNonZeroIndex = getLastNonZeroIndex(processedValues);
+            index = 0;
+        }
+
+        if (index == lastNonZeroIndex) {
+            return getFormattedValue(processedValues);
+        }
+
+        index++;
+        return "";
+    }
+
+    String getFormattedValue(float[] values) {
+        float sum = 0;
+        for (int i = 0; i < values.length - ignoreLast; i++) {
+            sum += values[i];
+        }
+        return DateTimeUtils.minutesToHHMM((int) sum);
+    }
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/BodyEnergyFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/BodyEnergyFragment.java
new file mode 100644
index 0000000..796ab77
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/BodyEnergyFragment.java
@@ -0,0 +1,305 @@
+package nodomain.freeyourgadget.gadgetbridge.activities.charts;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.os.Build;
+import android.os.Bundle;
+import android.util.TypedValue;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import androidx.annotation.ColorInt;
+
+import com.github.mikephil.charting.charts.Chart;
+import com.github.mikephil.charting.charts.LineChart;
+import com.github.mikephil.charting.components.LegendEntry;
+import com.github.mikephil.charting.components.XAxis;
+import com.github.mikephil.charting.components.YAxis;
+import com.github.mikephil.charting.data.Entry;
+import com.github.mikephil.charting.data.LineData;
+import com.github.mikephil.charting.data.LineDataSet;
+import com.github.mikephil.charting.formatter.ValueFormatter;
+import com.github.mikephil.charting.interfaces.datasets.ILineDataSet;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+import java.util.TimeZone;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.TimeSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.BodyEnergySample;
+
+
+public class BodyEnergyFragment extends AbstractChartFragment<BodyEnergyFragment.BodyEnergyData> {
+    protected static final Logger LOG = LoggerFactory.getLogger(BodyEnergyFragment.class);
+
+    private TextView mDateView;
+    private ImageView bodyEnergyGauge;
+    private TextView bodyEnergyGained;
+    private TextView bodyEnergyLost;
+    private LineChart bodyEnergyChart;
+
+    protected int CHART_TEXT_COLOR;
+    protected int LEGEND_TEXT_COLOR;
+    protected int TEXT_COLOR;
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+        View rootView = inflater.inflate(R.layout.fragment_body_energy, container, false);
+
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+            rootView.setOnScrollChangeListener((v, scrollX, scrollY, oldScrollX, oldScrollY) -> {
+                getChartsHost().enableSwipeRefresh(scrollY == 0);
+            });
+        }
+
+        mDateView = rootView.findViewById(R.id.body_energy_date_view);
+        bodyEnergyGauge = rootView.findViewById(R.id.body_energy_gauge);
+        bodyEnergyGained = rootView.findViewById(R.id.body_energy_gained);
+        bodyEnergyLost = rootView.findViewById(R.id.body_energy_lost);
+        bodyEnergyChart = rootView.findViewById(R.id.body_energy_chart);
+        setupBodyEnergyLevelChart();
+        refresh();
+
+
+        return rootView;
+    }
+
+
+    @Override
+    public String getTitle() {
+        return getString(R.string.body_energy);
+    }
+
+    @Override
+    protected void init() {
+        TEXT_COLOR = GBApplication.getTextColor(requireContext());
+        LEGEND_TEXT_COLOR = GBApplication.getTextColor(requireContext());
+        CHART_TEXT_COLOR = GBApplication.getSecondaryTextColor(requireContext());
+    }
+
+    @Override
+    protected BodyEnergyData refreshInBackground(ChartsHost chartsHost, DBHandler db, GBDevice device) {
+        List<? extends BodyEnergySample> samples = getBodyEnergySamples(db, device, getTSStart(), getTSEnd());
+        return new BodyEnergyData(samples);
+    }
+
+    @Override
+    protected void updateChartsnUIThread(BodyEnergyData bodyEnergyData) {
+        String formattedDate = new SimpleDateFormat("E, MMM dd").format(getEndDate());
+        mDateView.setText(formattedDate);
+
+        List<Entry> lineEntries = new ArrayList<>();
+        final List<ILineDataSet> lineDataSets = new ArrayList<>();
+        final AtomicInteger gainedValue = new AtomicInteger(0);
+        final AtomicInteger drainedValue = new AtomicInteger(0);
+        int newestValue = 0;
+        long referencedTimestamp;
+        if (!bodyEnergyData.samples.isEmpty()) {
+            newestValue = bodyEnergyData.samples.get(bodyEnergyData.samples.size() - 1).getEnergy();
+            referencedTimestamp = bodyEnergyData.samples.get(0).getTimestamp();
+            final AtomicInteger[] lastValue = {new AtomicInteger(0)};
+            bodyEnergyData.samples.forEach((sample) -> {
+                if (sample.getEnergy() < lastValue[0].intValue()) {
+                    drainedValue.set(drainedValue.get() + lastValue[0].intValue() - sample.getEnergy());
+                } else if (lastValue[0].intValue() > 0 && sample.getEnergy() > lastValue[0].intValue()) {
+                    gainedValue.set(gainedValue.get() + sample.getEnergy() - lastValue[0].intValue());
+                }
+                lastValue[0].set(sample.getEnergy());
+                float x = (float) sample.getTimestamp() / 1000 - (float) referencedTimestamp / 1000;
+                lineEntries.add(new Entry(x, sample.getEnergy()));
+            });
+        }
+
+        final LineDataSet lineDataSet = new LineDataSet(lineEntries, getString(R.string.body_energy_legend_level));
+        lineDataSet.setColor(getResources().getColor(R.color.body_energy_level_color));
+        lineDataSet.setDrawCircles(false);
+        lineDataSet.setLineWidth(2f);
+        lineDataSet.setFillAlpha(255);
+        lineDataSet.setDrawCircles(false);
+        lineDataSet.setCircleColor(getResources().getColor(R.color.body_energy_level_color));
+        lineDataSet.setAxisDependency(YAxis.AxisDependency.LEFT);
+        lineDataSet.setDrawValues(false);
+        lineDataSet.setMode(LineDataSet.Mode.CUBIC_BEZIER);
+        lineDataSet.setDrawFilled(true);
+        lineDataSet.setFillAlpha(60);
+        lineDataSet.setFillColor(getResources().getColor(R.color.body_energy_level_color ));
+
+        List<LegendEntry> legendEntries = new ArrayList<>(1);
+        LegendEntry activityEntry = new LegendEntry();
+        activityEntry.label = getString(R.string.body_energy_legend_level);
+        activityEntry.formColor = getResources().getColor(R.color.body_energy_level_color);
+        legendEntries.add(activityEntry);
+        bodyEnergyChart.getLegend().setTextColor(LEGEND_TEXT_COLOR);
+        bodyEnergyChart.getLegend().setCustom(legendEntries);
+
+        lineDataSets.add(lineDataSet);
+        final LineData lineData = new LineData(lineDataSets);
+        bodyEnergyChart.setData(lineData);
+
+        final int width = (int) TypedValue.applyDimension(
+                TypedValue.COMPLEX_UNIT_DIP,
+                300,
+                GBApplication.getContext().getResources().getDisplayMetrics()
+        );
+
+        bodyEnergyGauge.setImageBitmap(drawGauge(
+                width,
+                width / 15,
+                getResources().getColor(R.color.body_energy_level_color),
+                newestValue,
+                100
+        ));
+        bodyEnergyGained.setText(String.format("+ %s", gainedValue.intValue()));
+        bodyEnergyLost.setText(String.format("- %s", drainedValue));
+    }
+
+    @Override
+    protected void renderCharts() {
+        bodyEnergyChart.invalidate();
+    }
+
+
+    public List<? extends BodyEnergySample> getBodyEnergySamples(final DBHandler db, final GBDevice device, int tsFrom, int tsTo) {
+        Calendar day = Calendar.getInstance();
+        day.setTimeInMillis(tsTo * 1000L); //we need today initially, which is the end of the time range
+        day.set(Calendar.HOUR_OF_DAY, 0); //and we set time for the start and end of the same day
+        day.set(Calendar.MINUTE, 0);
+        day.set(Calendar.SECOND, 0);
+        tsFrom = (int) (day.getTimeInMillis() / 1000);
+        tsTo = tsFrom + 24 * 60 * 60 - 1;
+
+        final DeviceCoordinator coordinator = device.getDeviceCoordinator();
+        final TimeSampleProvider<? extends BodyEnergySample> sampleProvider = coordinator.getBodyEnergySampleProvider(device, db.getDaoSession());
+        return sampleProvider.getAllSamples(tsFrom * 1000L, tsTo * 1000L);
+    }
+
+    protected void setupLegend(Chart<?> chart) {}
+
+    Bitmap drawGauge(int width, int barWidth, @ColorInt int filledColor, int value, int maxValue) {
+        int height = width;
+        int barMargin = (int) Math.ceil(barWidth / 2f);
+        float filledFactor = (float) value / maxValue;
+
+        Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+        Canvas canvas = new Canvas(bitmap);
+        Paint paint = new Paint();
+        paint.setAntiAlias(true);
+        paint.setStyle(Paint.Style.STROKE);
+        paint.setStrokeCap(Paint.Cap.ROUND);
+        paint.setStrokeWidth(barWidth);
+        paint.setColor(getResources().getColor(R.color.gauge_line_color));
+        canvas.drawArc(
+                barMargin,
+                barMargin,
+                width - barMargin,
+                width - barMargin,
+                120,
+                300,
+                false,
+                paint);
+        paint.setStrokeWidth(barWidth);
+        paint.setColor(filledColor);
+        canvas.drawArc(
+                barMargin,
+                barMargin,
+                width - barMargin,
+                height - barMargin,
+                120,
+                300 * filledFactor,
+                false,
+                paint
+        );
+
+        Paint textPaint = new Paint();
+        textPaint.setColor(TEXT_COLOR);
+        float textPixels = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, width * 0.06f, requireContext().getResources().getDisplayMetrics());
+        textPaint.setTextSize(textPixels);
+        textPaint.setTextAlign(Paint.Align.CENTER);
+        int yPos = (int) ((float) height / 2 - ((textPaint.descent() + textPaint.ascent()) / 2)) ;
+        canvas.drawText(String.valueOf(value), width / 2f, yPos, textPaint);
+        Paint textLowerPaint = new Paint();
+        textLowerPaint.setColor(TEXT_COLOR);
+        textLowerPaint.setTextAlign(Paint.Align.CENTER);
+        float textLowerPixels = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, width * 0.025f, requireContext().getResources().getDisplayMetrics());
+        textLowerPaint.setTextSize(textLowerPixels);
+        int yPosLowerText = (int) ((float) height / 2 - textPaint.ascent()) ;
+        canvas.drawText(String.valueOf(maxValue), width / 2f, yPosLowerText, textLowerPaint);
+
+        return bitmap;
+    }
+
+    private void setupBodyEnergyLevelChart() {
+        bodyEnergyChart.getDescription().setEnabled(false);
+        bodyEnergyChart.setTouchEnabled(false);
+        bodyEnergyChart.setPinchZoom(false);
+        bodyEnergyChart.setDoubleTapToZoomEnabled(false);
+
+
+        final XAxis xAxisBottom = bodyEnergyChart.getXAxis();
+        xAxisBottom.setPosition(XAxis.XAxisPosition.BOTTOM);
+        xAxisBottom.setDrawLabels(true);
+        xAxisBottom.setDrawGridLines(false);
+        xAxisBottom.setEnabled(true);
+        xAxisBottom.setDrawLimitLinesBehindData(true);
+        xAxisBottom.setTextColor(CHART_TEXT_COLOR);
+        xAxisBottom.setAxisMinimum(0f);
+        xAxisBottom.setAxisMaximum(86400f);
+        xAxisBottom.setLabelCount(7, true);
+        xAxisBottom.setValueFormatter(getBodyEnergyChartXValueFormatter());
+
+        final YAxis yAxisLeft = bodyEnergyChart.getAxisLeft();
+        yAxisLeft.setDrawGridLines(true);
+        yAxisLeft.setAxisMaximum(100);
+        yAxisLeft.setAxisMinimum(0);
+        yAxisLeft.setDrawTopYLabelEntry(true);
+        yAxisLeft.setEnabled(true);
+        yAxisLeft.setTextColor(CHART_TEXT_COLOR);
+
+        final YAxis yAxisRight = bodyEnergyChart.getAxisRight();
+        yAxisRight.setEnabled(true);
+        yAxisRight.setDrawLabels(false);
+        yAxisRight.setDrawGridLines(false);
+        yAxisRight.setDrawAxisLine(true);
+
+    }
+
+    ValueFormatter getBodyEnergyChartXValueFormatter() {
+        return new ValueFormatter() {
+            @Override
+            public String getFormattedValue(float value) {
+                long timestamp = (long) (value * 1000);
+                Date date = new Date ();
+                date.setTime(timestamp);
+                SimpleDateFormat df = new SimpleDateFormat("HH:mm", Locale.getDefault());
+                df.setTimeZone(TimeZone.getTimeZone("UTC"));
+                return df.format(date);
+            }
+        };
+    }
+
+    protected static class BodyEnergyData extends ChartsData {
+        private final List<? extends BodyEnergySample> samples;
+
+        protected BodyEnergyData(List<? extends BodyEnergySample> samples) {
+            this.samples = samples;
+        }
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/CaloriesDailyFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/CaloriesDailyFragment.java
new file mode 100644
index 0000000..3184d40
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/CaloriesDailyFragment.java
@@ -0,0 +1,253 @@
+package nodomain.freeyourgadget.gadgetbridge.activities.charts;
+
+import android.content.Intent;
+import android.os.Build;
+import android.os.Bundle;
+import android.util.TypedValue;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import androidx.core.content.ContextCompat;
+
+import com.github.mikephil.charting.charts.Chart;
+
+import org.apache.commons.lang3.EnumUtils;
+
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.dashboard.GaugeDrawer;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.DefaultRestingMetabolicRateProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.TimeSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.AbstractActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
+import nodomain.freeyourgadget.gadgetbridge.model.RestingMetabolicRateSample;
+
+public class CaloriesDailyFragment extends AbstractChartFragment<CaloriesDailyFragment.CaloriesData> {
+
+    private ImageView caloriesGauge;
+    private TextView dateView;
+    private TextView caloriesResting;
+    private TextView caloriesActive;
+    private LinearLayout caloriesActiveWrapper;
+    private TextView caloriesActiveGoal;
+    private LinearLayout caloriesActiveGoalWrapper;
+    protected int CALORIES_GOAL;
+    protected int ACTIVE_CALORIES_GOAL;
+    public enum GaugeViewMode {
+        ACTIVE_CALORIES_GOAL,
+        TOTAL_CALORIES_SEGMENT
+    }
+    private GaugeViewMode gaugeViewMode;
+
+    @Override
+    public void onCreate(final Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        if (getArguments() != null) {
+            String mode = getArguments().getString(ActivityChartsActivity.EXTRA_MODE, "");
+            if (EnumUtils.isValidEnum(GaugeViewMode.class, mode)) {
+                gaugeViewMode = GaugeViewMode.valueOf(mode);
+            }
+        }
+    }
+
+    public static CaloriesDailyFragment newInstance(final String mode) {
+        final CaloriesDailyFragment fragment = new CaloriesDailyFragment();
+        final Bundle args = new Bundle();
+        args.putString(ActivityChartsActivity.EXTRA_MODE, mode);
+        fragment.setArguments(args);
+        return fragment;
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+        View rootView = inflater.inflate(R.layout.fragment_calories, container, false);
+
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+            rootView.setOnScrollChangeListener((v, scrollX, scrollY, oldScrollX, oldScrollY) -> {
+                getChartsHost().enableSwipeRefresh(scrollY == 0);
+            });
+        }
+
+        caloriesGauge = rootView.findViewById(R.id.calories_gauge);
+        dateView = rootView.findViewById(R.id.date_view);
+        caloriesResting = rootView.findViewById(R.id.calories_resting);
+        caloriesActive = rootView.findViewById(R.id.calories_active);
+        caloriesActiveWrapper = rootView.findViewById(R.id.calories_active_wrapper);
+        caloriesActiveGoal = rootView.findViewById(R.id.calories_active_goal);
+        caloriesActiveGoalWrapper = rootView.findViewById(R.id.calories_active_goal_wrapper);
+        ActivityUser activityUser = new ActivityUser();
+        ACTIVE_CALORIES_GOAL = activityUser.getCaloriesBurntGoal();
+
+        refresh();
+        if (!supportsActiveCalories()) {
+            caloriesActiveWrapper.setVisibility(View.GONE);
+            caloriesActiveGoalWrapper.setVisibility(View.GONE);
+        }
+
+        if (gaugeViewMode == null) {
+            gaugeViewMode = GaugeViewMode.TOTAL_CALORIES_SEGMENT;
+        }
+
+        if (gaugeViewMode.equals(GaugeViewMode.ACTIVE_CALORIES_GOAL)) {
+            CALORIES_GOAL = ACTIVE_CALORIES_GOAL;
+        }
+
+        return rootView;
+    }
+
+    public boolean supportsActiveCalories() {
+        final GBDevice device = getChartsHost().getDevice();
+        return device.getDeviceCoordinator().supportsActiveCalories();
+    }
+
+    protected RestingMetabolicRateSample getRestingMetabolicRate(DBHandler db, GBDevice device) {
+        TimeSampleProvider<? extends RestingMetabolicRateSample> provider = device.getDeviceCoordinator().getRestingMetabolicRateProvider(device, db.getDaoSession());
+        RestingMetabolicRateSample latestSample = provider.getLatestSample();
+        if (latestSample != null) {
+            return latestSample;
+        }
+        DefaultRestingMetabolicRateProvider defaultProvider = new DefaultRestingMetabolicRateProvider(device, db.getDaoSession());
+        return defaultProvider.getLatestSample();
+    }
+
+    protected List<? extends AbstractActivitySample> getActivitySamples(DBHandler db, GBDevice device, int tsFrom, int tsTo) {
+        SampleProvider<? extends ActivitySample> provider = device.getDeviceCoordinator().getSampleProvider(device, db.getDaoSession());
+        return provider.getAllActivitySamples(tsFrom, tsTo);
+    }
+
+    @Override
+    public String getTitle() {
+        return getString(R.string.calories);
+    }
+
+    @Override
+    protected void init() {}
+
+    @Override
+    protected CaloriesDailyFragment.CaloriesData refreshInBackground(ChartsHost chartsHost, DBHandler db, GBDevice device) {
+        Calendar calendar = Calendar.getInstance();
+        Calendar day = Calendar.getInstance();
+        day.setTime(chartsHost.getEndDate());
+        day.add(Calendar.DATE, 0);
+        day.set(Calendar.HOUR_OF_DAY, 0);
+        day.set(Calendar.MINUTE, 0);
+        day.set(Calendar.SECOND, 0);
+        day.add(Calendar.HOUR, 0);
+        int startTs = (int) (day.getTimeInMillis() / 1000);
+        int endTs = startTs + 24 * 60 * 60 - 1;
+        Date date = new Date((long) endTs * 1000);
+        String formattedDate = new SimpleDateFormat("E, MMM dd").format(date);
+        dateView.setText(formattedDate);
+        List<? extends ActivitySample> samples = getActivitySamples(db, device, startTs, endTs);
+        RestingMetabolicRateSample metabolicRate = getRestingMetabolicRate(db, device);
+        if (metabolicRate == null) {
+            return new CaloriesData(0, 0, 0);
+        }
+        int totalBurnt;
+        int activeBurnt = 0;
+        boolean sameDay = calendar.get(Calendar.DAY_OF_YEAR) == day.get(Calendar.DAY_OF_YEAR) &&
+                calendar.get(Calendar.YEAR) == day.get(Calendar.YEAR);
+        double passedDayProportion = 1;
+        if (sameDay) {
+            passedDayProportion = (double) (calendar.getTimeInMillis() - day.getTimeInMillis()) / (24L * 60 * 60 * 1000);
+        }
+        int restingBurnt = (int) (metabolicRate.getRestingMetabolicRate() * passedDayProportion);
+
+        for (int i = 0; i <= samples.size() - 1; i++) {
+            ActivitySample sample = samples.get(i);
+            if (sample.getActiveCalories() > 0) {
+                activeBurnt += sample.getActiveCalories();
+            }
+        }
+        // Convert calories to kcal
+        activeBurnt = activeBurnt / 1000;
+        totalBurnt = restingBurnt + activeBurnt;
+
+        return new CaloriesData(totalBurnt, activeBurnt, restingBurnt);
+    }
+
+    @Override
+    protected void updateChartsnUIThread(CaloriesDailyFragment.CaloriesData data) {
+        int restingCalories = data.restingBurnt;
+        int activeCalories = data.activeBurnt;
+        int totalCalories = activeCalories + restingCalories;
+        caloriesActive.setText(String.valueOf(activeCalories));
+        caloriesResting.setText(String.valueOf(restingCalories));
+        caloriesActiveGoal.setText(String.valueOf(ACTIVE_CALORIES_GOAL));
+
+        if (gaugeViewMode.equals(GaugeViewMode.TOTAL_CALORIES_SEGMENT)) {
+            int[] colors = new int[] {
+                    ContextCompat.getColor(GBApplication.getContext(), R.color.calories_resting_color),
+                    ContextCompat.getColor(GBApplication.getContext(), R.color.calories_color)
+            };
+            float[] segments = new float[] {
+                    restingCalories > 0 ? (float) restingCalories / totalCalories : 0,
+                    activeCalories > 0 ? (float) activeCalories / totalCalories : 0
+            };
+            final int width = (int) TypedValue.applyDimension(
+                    TypedValue.COMPLEX_UNIT_DIP,
+                    300,
+                    GBApplication.getContext().getResources().getDisplayMetrics()
+            );
+            caloriesGauge.setImageBitmap(GaugeDrawer.drawCircleGaugeSegmented(
+                    width,
+                    width / 15,
+                    colors,
+                    segments,
+                    true,
+                    String.valueOf(totalCalories),
+                    getContext().getString(R.string.total_calories_burnt),
+                    getContext()
+            ));
+        } else {
+            int value = 0;
+            if (gaugeViewMode.equals(GaugeViewMode.ACTIVE_CALORIES_GOAL)) {
+                value = activeCalories;
+            }
+            final int width = (int) TypedValue.applyDimension(
+                    TypedValue.COMPLEX_UNIT_DIP,
+                    300,
+                    GBApplication.getContext().getResources().getDisplayMetrics()
+            );
+            caloriesGauge.setImageBitmap(GaugeDrawer.drawCircleGauge(
+                    width,
+                    width / 15,
+                    getResources().getColor(R.color.calories_color),
+                    value,
+                    CALORIES_GOAL,
+                    getContext()
+            ));
+        }
+    }
+
+    @Override
+    protected void renderCharts() {}
+
+    @Override
+    protected void setupLegend(Chart<?> chart) {}
+
+    protected static class CaloriesData extends ChartsData {
+        public int activeBurnt;
+        public int restingBurnt;
+        public int totalBurnt;
+
+        protected CaloriesData(int totalBurnt, int activeBurnt, int restingBurnt) {
+            this.totalBurnt = totalBurnt;
+            this.activeBurnt = activeBurnt;
+            this.restingBurnt = restingBurnt;
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ChartsData.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ChartsData.java
new file mode 100644
index 0000000..f9bf5f6
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ChartsData.java
@@ -0,0 +1,20 @@
+/*  Copyright (C) 2016-2024 Carsten Pfeiffer
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities.charts;
+
+public abstract class ChartsData {
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ChartsHost.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ChartsHost.java
new file mode 100644
index 0000000..790d38d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ChartsHost.java
@@ -0,0 +1,51 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, Carsten Pfeiffer, Jos茅 Rebelo,
+    Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities.charts;
+
+import android.view.ViewGroup;
+
+import java.util.Date;
+
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public interface ChartsHost {
+    String DATE_PREV_DAY = ChartsHost.class.getName().concat(".date_prev_day");
+    String DATE_NEXT_DAY = ChartsHost.class.getName().concat(".date_next_day");
+    String DATE_PREV_WEEK = ChartsHost.class.getName().concat(".date_prev_week");
+    String DATE_NEXT_WEEK = ChartsHost.class.getName().concat(".date_next_week");
+    String DATE_PREV_MONTH = ChartsHost.class.getName().concat(".date_prev_month");
+    String DATE_NEXT_MONTH = ChartsHost.class.getName().concat(".date_next_month");
+
+    String REFRESH = ChartsHost.class.getName().concat(".refresh");
+
+    GBDevice getDevice();
+
+    void setStartDate(Date startDate);
+
+    void setEndDate(Date endDate);
+
+    Date getStartDate();
+
+    Date getEndDate();
+
+    void setDateInfo(String dateInfo);
+
+    ViewGroup getDateBar();
+
+    void enableSwipeRefresh(boolean enable);
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ChartsPreferencesActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ChartsPreferencesActivity.java
new file mode 100644
index 0000000..9addbe1
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ChartsPreferencesActivity.java
@@ -0,0 +1,131 @@
+/*  Copyright (C) 2019-2024 Jos茅 Rebelo, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities.charts;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.text.InputType;
+
+import androidx.annotation.NonNull;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceFragmentCompat;
+
+import com.mobeta.android.dslv.DragSortListPreference;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.AboutUserPreferencesActivity;
+import nodomain.freeyourgadget.gadgetbridge.activities.AbstractPreferenceFragment;
+import nodomain.freeyourgadget.gadgetbridge.activities.AbstractSettingsActivityV2;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.util.GBPrefs;
+import nodomain.freeyourgadget.gadgetbridge.util.preferences.DevicePrefs;
+
+public class ChartsPreferencesActivity extends AbstractSettingsActivityV2 {
+    private GBDevice device;
+
+    @Override
+    protected String fragmentTag() {
+        return ChartsPreferencesFragment.FRAGMENT_TAG;
+    }
+
+    @Override
+    protected PreferenceFragmentCompat newFragment() {
+        return ChartsPreferencesFragment.newInstance(device);
+    }
+
+    @Override
+    protected void onCreate(final Bundle savedInstanceState) {
+        device = getIntent().getParcelableExtra(GBDevice.EXTRA_DEVICE);
+
+        super.onCreate(savedInstanceState);
+    }
+
+    public static class ChartsPreferencesFragment extends AbstractPreferenceFragment {
+        static final String FRAGMENT_TAG = "CHARTS_PREFERENCES_FRAGMENT";
+
+        private GBDevice device;
+
+        static ChartsPreferencesFragment newInstance(final GBDevice device) {
+            final ChartsPreferencesFragment fragment = new ChartsPreferencesFragment();
+            fragment.setDevice(device);
+            return fragment;
+        }
+
+        private void setDevice(final GBDevice device) {
+            final Bundle args = getArguments() != null ? getArguments() : new Bundle();
+            args.putParcelable("device", device);
+            setArguments(args);
+        }
+
+        @Override
+        public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) {
+            final Bundle arguments = getArguments();
+            if (arguments != null) {
+                this.device = arguments.getParcelable(GBDevice.EXTRA_DEVICE);
+            }
+
+            setPreferencesFromResource(R.xml.charts_preferences, rootKey);
+
+            // If a device was provided, show the charts tabs preference, since that's the only one
+            // that is device-specific for now. We also sync changes to the device-specific preference.
+            //final DragSortListPreference prefChartsTabs = findPreference(DeviceSettingsPreferenceConst.PREFS_DEVICE_CHARTS_TABS);
+            //if (prefChartsTabs != null) {
+            //    if (device != null) {
+            //        final DevicePrefs devicePrefs = GBApplication.getDevicePrefs(device.getAddress());
+            //        final String myTabs = devicePrefs.getString(DeviceSettingsPreferenceConst.PREFS_DEVICE_CHARTS_TABS, null);
+            //        if (myTabs != null) {
+            //            prefChartsTabs.setValue(myTabs);
+            //        }
+            //        prefChartsTabs.setOnPreferenceChangeListener((preference, newValue) -> {
+            //            devicePrefs.getPreferences().edit()
+            //                    .putString(DeviceSettingsPreferenceConst.PREFS_DEVICE_CHARTS_TABS, String.valueOf(newValue))
+            //                    .apply();
+            //            return true;
+            //        });
+            //    } else {
+            //        prefChartsTabs.setVisible(false);
+            //    }
+            //}
+
+            setInputTypeFor(GBPrefs.CHART_MAX_HEART_RATE, InputType.TYPE_CLASS_NUMBER);
+            setInputTypeFor(GBPrefs.CHART_MIN_HEART_RATE, InputType.TYPE_CLASS_NUMBER);
+            setInputTypeFor("chart_sleep_lines_limit", InputType.TYPE_CLASS_NUMBER);
+            setInputTypeFor("chart_list_min_session_length", InputType.TYPE_CLASS_NUMBER);
+            setInputTypeFor("chart_list_max_idle_phase_length", InputType.TYPE_CLASS_NUMBER);
+            setInputTypeFor("chart_list_min_steps_per_minute", InputType.TYPE_CLASS_NUMBER);
+            setInputTypeFor("chart_list_min_steps_per_minute_for_run", InputType.TYPE_CLASS_NUMBER);
+
+            final Preference aboutUserPref = findPreference("pref_category_activity_personal");
+            if (aboutUserPref != null) {
+                if (device != null) {
+                    aboutUserPref.setOnPreferenceClickListener(preference -> {
+                        final Intent enableIntent = new Intent(getActivity(), AboutUserPreferencesActivity.class);
+                        startActivity(enableIntent);
+                        return true;
+                    });
+                } else {
+                    final Preference aboutUserHeader = findPreference("pref_category_activity_personal_title");
+                    if (aboutUserHeader != null) {
+                        aboutUserHeader.setVisible(false);
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/CustomBarChart.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/CustomBarChart.java
new file mode 100644
index 0000000..1eb3580
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/CustomBarChart.java
@@ -0,0 +1,76 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, Carsten Pfeiffer
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities.charts;
+
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.util.AttributeSet;
+
+import com.github.mikephil.charting.charts.BarChart;
+import com.github.mikephil.charting.data.Entry;
+import com.github.mikephil.charting.renderer.BarChartRenderer;
+
+/**
+ * A BarChart with some specific customization, like
+ * <li>allowing to animate a single entry's values without going over 0</li>
+ */
+public class CustomBarChart extends BarChart {
+
+    private Entry entry = null;
+    private SingleEntryValueAnimator singleEntryAnimator;
+
+    public CustomBarChart(Context context) {
+        super(context);
+    }
+
+    public CustomBarChart(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public CustomBarChart(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    public void setSinglAnimationEntry(Entry entry) {
+        this.entry = entry;
+
+        if (entry != null) {
+            // single entry animation mode
+            singleEntryAnimator = new SingleEntryValueAnimator(entry, new ValueAnimator.AnimatorUpdateListener() {
+                @Override
+                public void onAnimationUpdate(ValueAnimator animation) {
+                    // ViewCompat.postInvalidateOnAnimation(Chart.this);
+                    postInvalidate();
+                }
+            });
+            mAnimator = singleEntryAnimator;
+            mRenderer = new BarChartRenderer(this, singleEntryAnimator, getViewPortHandler());
+        }
+    }
+
+    /**
+     * Call this to set the next value for the Entry to be animated.
+     * Call animateY() when ready to do that.
+     *
+     * @param nextValue
+     */
+    public void setSingleEntryYValue(float nextValue) {
+        if (singleEntryAnimator != null) {
+            singleEntryAnimator.setEntryYValue(nextValue);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/CyclingChartFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/CyclingChartFragment.java
new file mode 100644
index 0000000..556f247
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/CyclingChartFragment.java
@@ -0,0 +1,295 @@
+package nodomain.freeyourgadget.gadgetbridge.activities.charts;
+
+import android.graphics.Color;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.core.content.ContextCompat;
+
+import com.github.mikephil.charting.animation.Easing;
+import com.github.mikephil.charting.charts.Chart;
+import com.github.mikephil.charting.charts.LineChart;
+import com.github.mikephil.charting.components.LegendEntry;
+import com.github.mikephil.charting.components.LimitLine;
+import com.github.mikephil.charting.components.XAxis;
+import com.github.mikephil.charting.components.YAxis;
+import com.github.mikephil.charting.data.Entry;
+import com.github.mikephil.charting.data.LineData;
+import com.github.mikephil.charting.data.LineDataSet;
+import com.github.mikephil.charting.formatter.ValueFormatter;
+import com.github.mikephil.charting.interfaces.datasets.ILineDataSet;
+
+import java.text.DateFormat;
+import java.text.DecimalFormat;
+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 nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.TimeSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.CyclingSample;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+public class CyclingChartFragment extends AbstractChartFragment<CyclingChartFragment.CyclingChartsData>{
+    private LineChart cyclingHistoryChart;
+
+    private int BACKGROUND_COLOR;
+    private int DESCRIPTION_COLOR;
+    private int CHART_TEXT_COLOR;
+    private int LEGEND_TEXT_COLOR;
+    private int CHART_LINE_COLOR_DISTANCE;
+    private int CHART_LINE_COLOR_SPEED;
+    private final Prefs prefs = GBApplication.getPrefs();
+
+    protected static class CyclingChartsData extends DefaultChartsData<LineData> {
+        public CyclingChartsData(LineData lineData) {
+            super(lineData, new TimeFormatter());
+        }
+    }
+
+    @Override
+    public String getTitle() {
+        return "Cycling data";
+    }
+
+    @Override
+    protected void init() {
+        BACKGROUND_COLOR = GBApplication.getBackgroundColor(requireContext());
+        LEGEND_TEXT_COLOR = DESCRIPTION_COLOR = GBApplication.getTextColor(requireContext());
+        CHART_TEXT_COLOR = GBApplication.getSecondaryTextColor(requireContext());
+
+        if (prefs.getBoolean("chart_heartrate_color", false)) {
+            CHART_LINE_COLOR_DISTANCE = ContextCompat.getColor(getContext(), R.color.chart_activity_dark);
+            CHART_LINE_COLOR_SPEED  = ContextCompat.getColor(getContext(), R.color.chart_heartrate);
+        } else {
+            CHART_LINE_COLOR_DISTANCE = ContextCompat.getColor(getContext(), R.color.chart_activity_light);
+            CHART_LINE_COLOR_SPEED = ContextCompat.getColor(getContext(), R.color.chart_heartrate_alternative);
+        }
+    }
+
+    @Override
+    protected CyclingChartsData refreshInBackground(ChartsHost chartsHost, DBHandler db, GBDevice device) {
+        List<CyclingSample> samples = getSamples(db, device);
+
+        return new CyclingChartsDataBuilder(samples).build();
+    }
+
+    protected class CyclingChartsDataBuilder {
+        private final List<CyclingSample> samples;
+
+        private final List<Entry> lineEntries = new ArrayList<>();
+
+        long averageSum;
+        long averageNumSamples;
+
+        public CyclingChartsDataBuilder(final List<CyclingSample> samples) {
+            this.samples = samples;
+        }
+
+        private void reset() {
+            lineEntries.clear();
+
+            averageSum = 0;
+            averageNumSamples = 0;
+        }
+
+        public CyclingChartsData build() {
+            List<Entry> distanceEntries = new ArrayList<>();
+            List<Entry> speedEntries = new ArrayList<>();
+
+            Float dayStart = 0f;
+
+            if(!samples.isEmpty()){
+                dayStart = samples.get(0).getDistance() / 1000f;
+            }
+
+            int nextIndex = 0;
+            CyclingSample oldSample = null;
+            for (CyclingSample sample : samples) {
+                // add distance in Km
+                distanceEntries.add(new Entry(sample.getTimestamp(), (sample.getDistance() / 1000f) - dayStart));
+
+                if(oldSample != null) {
+                    float deltaMeters = sample.getDistance() - oldSample.getDistance();
+                    float deltaMillis = sample.getTimestamp() - oldSample.getTimestamp();
+
+                    float metersPerMillisecond = deltaMeters / deltaMillis;
+                    float kmh = metersPerMillisecond * 3600;
+
+                    // Float speed = sample.getSpeed();
+                    if(kmh < 6.0) {
+                        // speed to slow, cutting down to 0
+                        speedEntries.add(new Entry(oldSample.getTimestamp() + 30_000, 0));
+                    }
+                    speedEntries.add(new Entry(sample.getTimestamp(), kmh));
+                }
+
+                nextIndex++;
+                oldSample = sample;
+            }
+
+            LineDataSet distanceSet = new LineDataSet(distanceEntries, "Cycling");
+            distanceSet.setLineWidth(2.2f);
+            distanceSet.setColor(CHART_LINE_COLOR_DISTANCE);
+            distanceSet.setDrawCircles(false);
+            distanceSet.setDrawCircleHole(false);
+            distanceSet.setDrawValues(true);
+            distanceSet.setValueTextSize(10f);
+            distanceSet.setValueTextColor(CHART_TEXT_COLOR);
+            distanceSet.setHighlightEnabled(false);
+            distanceSet.setValueFormatter(new CyclingDistanceFormatter(CyclingChartFragment.this, dayStart));
+            distanceSet.setAxisDependency(cyclingHistoryChart.getAxisLeft().getAxisDependency());
+            LineData lineData = new LineData(distanceSet);
+
+            LineDataSet speedSet = new LineDataSet(speedEntries, "Speed");
+            speedSet.setLineWidth(2.2f);
+            speedSet.setColor(CHART_LINE_COLOR_SPEED);
+            speedSet.setDrawCircles(false);
+            speedSet.setDrawCircleHole(false);
+            speedSet.setDrawValues(true);
+            speedSet.setValueTextSize(10f);
+            speedSet.setValueTextColor(CHART_TEXT_COLOR);
+            speedSet.setHighlightEnabled(true);
+            speedSet.setValueFormatter(new CyclingSpeedFormatter(CyclingChartFragment.this));
+            speedSet.setAxisDependency(cyclingHistoryChart.getAxisRight().getAxisDependency());
+
+            lineData.addDataSet(speedSet);
+
+            return new CyclingChartsData(lineData);
+        }
+    }
+
+    @Override
+    protected void renderCharts() {
+        cyclingHistoryChart.animateX(ANIM_TIME, Easing.EaseInOutQuart);
+    }
+
+    @Override
+    protected void setupLegend(final Chart<?> chart) {
+        final List<LegendEntry> legendEntries = new ArrayList<>(2);
+
+        final LegendEntry distanceEntry = new LegendEntry();
+        distanceEntry.label = getString(R.string.activity_list_summary_distance);
+        distanceEntry.formColor = CHART_LINE_COLOR_DISTANCE;
+        legendEntries.add(distanceEntry);
+
+        final LegendEntry speedEntry = new LegendEntry();
+        speedEntry.label = getString(R.string.Speed);
+        speedEntry.formColor = CHART_LINE_COLOR_SPEED;
+        legendEntries.add(speedEntry);
+
+        chart.getLegend().setCustom(legendEntries);
+        chart.getLegend().setTextColor(LEGEND_TEXT_COLOR);
+    }
+
+    @Override
+    protected void updateChartsnUIThread(CyclingChartsData cyclingData) {
+        cyclingHistoryChart.setData(null); // workaround for https://github.com/PhilJay/MPAndroidChart/issues/2317
+        cyclingHistoryChart.getXAxis().setValueFormatter(cyclingData.getXValueFormatter());
+        cyclingHistoryChart.getXAxis().setAvoidFirstLastClipping(true);
+
+        cyclingHistoryChart.setData(cyclingData.getData());
+    }
+
+    @Nullable
+    @Override
+    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
+        View rootView = inflater.inflate(R.layout.fragment_cycling, container, false);
+
+        cyclingHistoryChart = rootView.findViewById(R.id.chart_cycling_history);
+
+        cyclingHistoryChart.setBackgroundColor(BACKGROUND_COLOR);
+        cyclingHistoryChart.getDescription().setTextColor(DESCRIPTION_COLOR);
+        configureBarLineChartDefaults(cyclingHistoryChart);
+
+        final XAxis x = cyclingHistoryChart.getXAxis();
+        x.setDrawLabels(true);
+        x.setDrawGridLines(true);
+        x.setEnabled(true);
+        x.setTextColor(CHART_TEXT_COLOR);
+        x.setDrawLimitLinesBehindData(true);
+
+        final YAxis yAxisLeft = cyclingHistoryChart.getAxisLeft();
+        yAxisLeft.setDrawGridLines(true);
+        yAxisLeft.setTextColor(CHART_LINE_COLOR_DISTANCE);
+        yAxisLeft.setEnabled(true);
+        yAxisLeft.setGridColor(CHART_LINE_COLOR_DISTANCE);
+
+        final YAxis yAxisRight = cyclingHistoryChart.getAxisRight();
+        yAxisRight.setDrawGridLines(true);
+        yAxisRight.setTextColor(CHART_LINE_COLOR_SPEED);
+        yAxisRight.setEnabled(true);
+        yAxisRight.setGridColor(CHART_LINE_COLOR_SPEED);
+
+        refresh();
+
+        return rootView;
+    }
+
+    private List<CyclingSample> getSamples(final DBHandler db, final GBDevice device) {
+        final int tsStart = getTSStart();
+        final int tsEnd = getTSEnd();
+        final DeviceCoordinator coordinator = device.getDeviceCoordinator();
+        final TimeSampleProvider<CyclingSample> sampleProvider = coordinator.getCyclingSampleProvider(device, db.getDaoSession());
+        return sampleProvider.getAllSamples(tsStart * 1000L, tsEnd * 1000L);
+    }
+
+
+
+    protected static class CyclingDistanceFormatter extends ValueFormatter {
+        // private final DecimalFormat formatter = new DecimalFormat("0.00 km");
+        Float dayStartDistance;
+        CyclingChartFragment fragment;
+
+        public CyclingDistanceFormatter(CyclingChartFragment fragment, Float dayStartDistance) {
+            this.dayStartDistance = dayStartDistance;
+            this.fragment = fragment;
+        }
+
+        @Override
+        public String getPointLabel(Entry entry) {
+            return fragment.getString(R.string.chart_cycling_point_label_distance, entry.getY(), entry.getY() + dayStartDistance);
+        }
+    }
+
+    protected static class CyclingSpeedFormatter extends ValueFormatter {
+        CyclingChartFragment fragment;
+
+        public CyclingSpeedFormatter(CyclingChartFragment fragment) {
+            this.fragment = fragment;
+        }
+
+        @Override
+        public String getPointLabel(Entry entry) {
+            return fragment.getString(R.string.chart_cycling_point_label_speed, entry.getY());
+        }
+    }
+
+    protected static class TimeFormatter extends ValueFormatter {
+        DateFormat annotationDateFormat = SimpleDateFormat.getTimeInstance(DateFormat.SHORT);
+        Calendar cal = GregorianCalendar.getInstance();
+
+        public TimeFormatter() {
+        }
+
+        @Override
+        public String getFormattedValue(float value) {
+            cal.clear();
+            cal.setTimeInMillis((long)(value));
+            Date date = cal.getTime();
+            return annotationDateFormat.format(date);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/DaySleepChartFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/DaySleepChartFragment.java
new file mode 100644
index 0000000..2d3a7a1
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/DaySleepChartFragment.java
@@ -0,0 +1,616 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, Carsten Pfeiffer, Daniele
+    Gobbetti, Dikay900, Jos茅 Rebelo, ozkanpakdil, Pavel Elagin, Petr Van臎k, Q-er, a0z
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities.charts;
+
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Color;
+import android.os.Build;
+import android.os.Bundle;
+import android.text.method.ScrollingMovementMethod;
+import android.util.TypedValue;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import androidx.core.content.ContextCompat;
+
+import com.github.mikephil.charting.animation.Easing;
+import com.github.mikephil.charting.charts.Chart;
+import com.github.mikephil.charting.charts.LineChart;
+import com.github.mikephil.charting.components.*;
+import com.github.mikephil.charting.data.LineData;
+
+import org.apache.commons.lang3.tuple.Triple;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.text.DecimalFormat;
+import java.text.SimpleDateFormat;
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.HeartRateUtils;
+import nodomain.freeyourgadget.gadgetbridge.activities.charts.SleepAnalysis.SleepSession;
+import nodomain.freeyourgadget.gadgetbridge.activities.dashboard.GaugeDrawer;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.TimeSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.model.SleepScoreSample;
+import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+
+public class DaySleepChartFragment extends AbstractActivityChartFragment<DaySleepChartFragment.MyChartsData> {
+    protected static final Logger LOG = LoggerFactory.getLogger(ActivitySleepChartFragment.class);
+
+    private LineChart mActivityChart;
+    private ImageView sleepStagesGauge;
+    private TextView mSleepchartInfo;
+    private TextView remSleepTimeText;
+    private LinearLayout remSleepTimeTextWrapper;
+    private TextView awakeSleepTimeText;
+    private LinearLayout awakeSleepTimeTextWrapper;
+    private TextView deepSleepTimeText;
+    private TextView lightSleepTimeText;
+    private TextView lowestHrText;
+    private TextView highestHrText;
+    private TextView movementIntensityText;
+    private LinearLayout movementIntensityTextWrapper;
+    private LinearLayout dummyTile;
+    private TextView sleepDateText;
+    private int heartRateMin = 0;
+    private int heartRateMax = 0;
+    private float intensityTotal = 0;
+
+
+
+    private int mSmartAlarmFrom = -1;
+    private int mSmartAlarmTo = -1;
+    private int mTimestampFrom = -1;
+    private int mSmartAlarmGoneOff = -1;
+    Prefs prefs = GBApplication.getPrefs();
+    private boolean CHARTS_SLEEP_RANGE_24H = prefs.getBoolean("chart_sleep_range_24h", false);
+    private boolean SHOW_CHARTS_AVERAGE = prefs.getBoolean("charts_show_average", true);
+    private int sleepLinesLimit = prefs.getInt("chart_sleep_lines_limit", 6);
+
+    @Override
+    protected boolean isSingleDay() {
+        return true;
+    }
+
+    @Override
+    protected MyChartsData refreshInBackground(ChartsHost chartsHost, DBHandler db, GBDevice device) {
+        List<? extends ActivitySample> samples;
+        if (CHARTS_SLEEP_RANGE_24H) {
+            samples = getSamples(db, device);
+        } else {
+            samples = getSamplesofSleep(db, device);
+        }
+        List<? extends SleepScoreSample> sleepScoreSamples = new ArrayList<>();
+        if (supportsSleepScore()) {
+            sleepScoreSamples = getSleepScoreSamples(db, device, getTSStart(), getTSEnd());
+        }
+        MySleepChartsData mySleepChartsData = refreshSleepAmounts(device, samples, sleepScoreSamples);
+
+        if (!CHARTS_SLEEP_RANGE_24H) {
+            if (mySleepChartsData.sleepSessions.size() > 0) {
+                long tstart = mySleepChartsData.sleepSessions.get(0).getSleepStart().getTime() / 1000;
+                long tend = mySleepChartsData.sleepSessions.get(mySleepChartsData.sleepSessions.size() - 1).getSleepEnd().getTime() / 1000;
+
+                for (Iterator<ActivitySample> iterator = (Iterator<ActivitySample>) samples.iterator(); iterator.hasNext(); ) {
+                    ActivitySample sample = iterator.next();
+                    if (sample.getTimestamp() < tstart || sample.getTimestamp() > tend) {
+                        iterator.remove();
+                    }
+                }
+            }
+        }
+        DefaultChartsData<LineData> chartsData = refresh(device, samples);
+        Triple<Float, Integer, Integer> hrData = calculateHrData(samples);
+        Triple<Float, Float, Float> intensityData = calculateIntensityData(samples);
+        return new MyChartsData(mySleepChartsData, chartsData, hrData.getLeft(), hrData.getMiddle(), hrData.getRight(), intensityData.getLeft(), intensityData.getMiddle(), intensityData.getRight());
+    }
+
+
+
+    private MySleepChartsData refreshSleepAmounts(GBDevice mGBDevice, List<? extends ActivitySample> samples, List<? extends SleepScoreSample> sleepScoreSamples) {
+        SleepAnalysis sleepAnalysis = new SleepAnalysis();
+        List<SleepSession> sleepSessions = sleepAnalysis.calculateSleepSessions(samples);
+
+        final long lightSleepDuration = calculateLightSleepDuration(sleepSessions);
+        final long deepSleepDuration = calculateDeepSleepDuration(sleepSessions);
+        final long remSleepDuration = calculateRemSleepDuration(sleepSessions);
+        final long awakeSleepDuration = calculateAwakeSleepDuration(sleepSessions);
+        final long totalSeconds = lightSleepDuration + deepSleepDuration + remSleepDuration;
+
+        int sleepScore = 0;
+        if (!sleepScoreSamples.isEmpty()) {
+            sleepScore = sleepScoreSamples.get(sleepScoreSamples.size() - 1).getSleepScore();
+        }
+
+        return new MySleepChartsData(sleepSessions, totalSeconds, awakeSleepDuration, remSleepDuration, deepSleepDuration, lightSleepDuration, sleepScore);
+    }
+
+    private long calculateLightSleepDuration(List<SleepSession> sleepSessions) {
+        long result = 0;
+        for (SleepSession sleepSession : sleepSessions) {
+            result += sleepSession.getLightSleepDuration();
+        }
+        return result;
+    }
+
+    private long calculateDeepSleepDuration(List<SleepSession> sleepSessions) {
+        long result = 0;
+        for (SleepSession sleepSession : sleepSessions) {
+            result += sleepSession.getDeepSleepDuration();
+        }
+        return result;
+    }
+
+    private long calculateRemSleepDuration(List<SleepSession> sleepSessions) {
+        long result = 0;
+        for (SleepSession sleepSession : sleepSessions) {
+            result += sleepSession.getRemSleepDuration();
+        }
+        return result;
+    }
+
+    private long calculateAwakeSleepDuration(List<SleepSession> sleepSessions) {
+        long result = 0;
+        for (SleepSession sleepSession : sleepSessions) {
+            result += sleepSession.getAwakeSleepDuration();
+        }
+        return result;
+    }
+
+    protected void sleepStagesGaugeUpdate(MySleepChartsData pieData) {
+        int[] colors = new int[] {
+                ContextCompat.getColor(GBApplication.getContext(), R.color.chart_light_sleep_light),
+                ContextCompat.getColor(GBApplication.getContext(), R.color.chart_deep_sleep_light),
+                ContextCompat.getColor(GBApplication.getContext(), R.color.chart_rem_sleep_light),
+                ContextCompat.getColor(GBApplication.getContext(), R.color.chart_awake_sleep_light),
+        };
+        long total = pieData.getTotalSleep() + pieData.getTotalAwake();
+        float[] segments = new float[] {
+                pieData.getTotalLight() > 0 ? (float) pieData.getTotalLight() / total : 0,
+                pieData.getTotalDeep() > 0 ? (float) pieData.getTotalDeep() / total : 0,
+                pieData.getTotalRem() > 0 ? (float) pieData.getTotalRem() / total : 0,
+                pieData.getTotalAwake() > 0 ? (float) pieData.getTotalAwake() / total : 0,
+        };
+        final int width = (int) TypedValue.applyDimension(
+                TypedValue.COMPLEX_UNIT_DIP,
+                300,
+                GBApplication.getContext().getResources().getDisplayMetrics()
+        );
+        String lowerText = "";
+        if (supportsSleepScore()) {
+            lowerText = GBApplication.getContext().getString(R.string.sleep_score_value, pieData.getSleepScore());
+        }
+        sleepStagesGauge.setImageBitmap(GaugeDrawer.drawCircleGaugeSegmented(
+                width,
+                width / 15,
+                colors,
+                segments,
+                true,
+                String.valueOf(timeStringFormat(pieData.getTotalSleep())),
+                lowerText,
+                getContext()
+        ));
+    }
+
+    private String timeStringFormat(long seconds) {
+        return DateTimeUtils.formatDurationHoursMinutes(seconds, TimeUnit.SECONDS);
+    }
+
+    @Override
+    protected void updateChartsnUIThread(MyChartsData mcd) {
+        MySleepChartsData pieData = mcd.getPieData();
+
+        Date date = new Date((long) this.getTSEnd() * 1000);
+        String formattedDate = new SimpleDateFormat("E, MMM dd").format(date);
+        sleepDateText.setText(formattedDate);
+
+        sleepStagesGaugeUpdate(pieData);
+
+        if (!pieData.sleepSessions.isEmpty()) {
+            awakeSleepTimeText.setText(timeStringFormat(pieData.getTotalAwake()));
+            remSleepTimeText.setText(timeStringFormat(pieData.getTotalRem()));
+            deepSleepTimeText.setText(timeStringFormat(pieData.getTotalDeep()));
+            lightSleepTimeText.setText(timeStringFormat(pieData.getTotalLight()));
+        } else {
+            awakeSleepTimeText.setText("-");
+            remSleepTimeText.setText("-");
+            deepSleepTimeText.setText("-");
+            lightSleepTimeText.setText("-");
+        }
+        if (!supportsRemSleep(getChartsHost().getDevice())) {
+            remSleepTimeTextWrapper.setVisibility(View.GONE);
+        }
+        if (!supportsAwakeSleep(getChartsHost().getDevice())) {
+            awakeSleepTimeTextWrapper.setVisibility(View.GONE);
+        }
+        mSleepchartInfo.setText(buildYouSleptText(pieData));
+        mSleepchartInfo.setMovementMethod(new ScrollingMovementMethod());
+        mActivityChart.setData(null); // workaround for https://github.com/PhilJay/MPAndroidChart/issues/2317
+        mActivityChart.getXAxis().setValueFormatter(mcd.getChartsData().getXValueFormatter());
+        mActivityChart.getAxisLeft().setDrawLabels(false);
+
+        mActivityChart.setData(mcd.getChartsData().getData());
+        heartRateMin = mcd.getHeartRateAxisMin();
+        heartRateMax = mcd.getHeartRateAxisMax();
+        intensityTotal = mcd.getIntensityTotal();
+        lowestHrText.setText(String.valueOf(heartRateMin != 0 ? heartRateMin : "-"));
+        highestHrText.setText(String.valueOf(heartRateMax != 0 ? heartRateMax : "-"));
+        movementIntensityText.setText(intensityTotal > 0 ? new DecimalFormat("###.#").format(intensityTotal) : "-");
+        movementIntensityTextWrapper.setVisibility(intensityTotal > 0 ? View.VISIBLE : View.GONE);
+        dummyTile.setVisibility(intensityTotal > 0 ? View.VISIBLE : View.GONE);
+
+        if (!CHARTS_SLEEP_RANGE_24H
+                && supportsHeartrate(getChartsHost().getDevice())
+                && SHOW_CHARTS_AVERAGE) {
+            if (mcd.getHeartRateAxisMax() != 0 || mcd.getHeartRateAxisMin() != 0) {
+                mActivityChart.getAxisRight().setAxisMaximum(mcd.getHeartRateAxisMax() + (mcd.getHeartRateAxisMin() / 2f));
+                mActivityChart.getAxisRight().setAxisMinimum(mcd.getHeartRateAxisMin() / 2f);
+            }
+            LimitLine hrAverage_line = new LimitLine(mcd.getHeartRateAverage());
+            hrAverage_line.setLineColor(Color.RED);
+            hrAverage_line.setLineWidth(1.5f);
+            hrAverage_line.enableDashedLine(15f, 10f, 0f);
+            mActivityChart.getAxisRight().removeAllLimitLines();
+            mActivityChart.getAxisRight().addLimitLine(hrAverage_line);
+        }
+    }
+
+    private Triple<Float, Integer, Integer> calculateHrData(List<? extends ActivitySample> samples) {
+        if (samples.toArray().length < 1) {
+            return Triple.of(0f, 0, 0);
+        }
+
+        List<Integer> heartRateValues = new ArrayList<>();
+        HeartRateUtils heartRateUtilsInstance = HeartRateUtils.getInstance();
+        for (ActivitySample sample : samples) {
+            if (sample.getKind() == ActivityKind.LIGHT_SLEEP || sample.getKind() == ActivityKind.DEEP_SLEEP) {
+                int heartRate = sample.getHeartRate();
+                if (heartRateUtilsInstance.isValidHeartRateValue(heartRate)) {
+                    heartRateValues.add(heartRate);
+                }
+            }
+        }
+        if (heartRateValues.toArray().length < 1) {
+            return Triple.of(0f, 0, 0);
+        }
+
+        int min = Collections.min(heartRateValues);
+        int max = Collections.max(heartRateValues);
+        int count = heartRateValues.toArray().length;
+        float sum = calculateSumOfInts(heartRateValues);
+        float average = sum / count;
+        return Triple.of(average, min, max);
+    }
+
+    private float calculateIntensitySum(List<Float> samples) {
+        float result = 0;
+        for (Float sample : samples) {
+            result += sample;
+        }
+        return result;
+    }
+
+    private float calculateSumOfInts(List<Integer> samples) {
+        float result = 0;
+        for (Integer sample : samples) {
+            result += sample;
+        }
+        return result;
+    }
+
+    private Triple<Float, Float, Float> calculateIntensityData(List<? extends ActivitySample> samples) {
+        if (samples.toArray().length < 1) {
+            return Triple.of(0f, 0f, 0f);
+        }
+
+        List<Float> allIntensities = new ArrayList<>();
+
+        for (ActivitySample s : samples) {
+            if (s.getKind() == ActivityKind.LIGHT_SLEEP || s.getKind() == ActivityKind.DEEP_SLEEP) {
+                float intensity = s.getIntensity();
+                allIntensities.add(intensity);
+            }
+        }
+        if (allIntensities.toArray().length < 1) {
+            return Triple.of(0f, 0f, 0f);
+        }
+
+        Float min = Collections.min(allIntensities);
+        Float max = Collections.max(allIntensities);
+        Float sum = calculateIntensitySum(allIntensities);
+
+        return Triple.of(sum, min, max);
+    }
+
+    private String buildYouSleptText(MySleepChartsData pieData) {
+        final StringBuilder result = new StringBuilder();
+        if (!pieData.getSleepSessions().isEmpty()) {
+            for (SleepSession sleepSession : pieData.getSleepSessions()) {
+                if (result.length() > 0) {
+                    result.append("  |  ");
+                }
+                String from = DateTimeUtils.timeToString(sleepSession.getSleepStart());
+                String to = DateTimeUtils.timeToString(sleepSession.getSleepEnd());
+                result.append(String.format("%s - %s", from, to));
+            }
+        }
+        return result.toString();
+    }
+
+    public boolean supportsSleepScore() {
+        final GBDevice device = getChartsHost().getDevice();
+        return device.getDeviceCoordinator().supportsSleepScore();
+    }
+
+    @Override
+    public String getTitle() {
+        return getString(R.string.sleepchart_your_sleep);
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+                             Bundle savedInstanceState) {
+        View rootView = inflater.inflate(R.layout.fragment_sleepchart, container, false);
+
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+            rootView.setOnScrollChangeListener((v, scrollX, scrollY, oldScrollX, oldScrollY) -> {
+            getChartsHost().enableSwipeRefresh(scrollY == 0);
+            });
+        }
+
+        mActivityChart = rootView.findViewById(R.id.sleepchart);
+        sleepStagesGauge = rootView.findViewById(R.id.sleep_stages_gauge);
+        mSleepchartInfo = rootView.findViewById(R.id.sleepchart_info);
+        remSleepTimeText = rootView.findViewById(R.id.sleep_chart_legend_rem_time);
+        remSleepTimeTextWrapper = rootView.findViewById(R.id.sleep_chart_legend_rem_time_wrapper);
+        awakeSleepTimeText = rootView.findViewById(R.id.sleep_chart_legend_awake_time);
+        awakeSleepTimeTextWrapper = rootView.findViewById(R.id.sleep_chart_legend_awake_time_wrapper);
+        deepSleepTimeText = rootView.findViewById(R.id.sleep_chart_legend_deep_time);
+        lightSleepTimeText = rootView.findViewById(R.id.sleep_chart_legend_light_time);
+        lowestHrText = rootView.findViewById(R.id.sleep_hr_lowest);
+        highestHrText = rootView.findViewById(R.id.sleep_hr_highest);
+        movementIntensityText = rootView.findViewById(R.id.sleep_movement_intensity);
+        movementIntensityTextWrapper = rootView.findViewById(R.id.sleep_chart_legend_movement_intensity_wrapper);
+        dummyTile = rootView.findViewById(R.id.sleep_chart_legend_dummy_tile);
+        sleepDateText = rootView.findViewById(R.id.sleep_date);
+
+        mSleepchartInfo.setMaxLines(sleepLinesLimit);
+
+        setupActivityChart();
+
+        // refresh immediately instead of use refreshIfVisible(), for perceived performance
+        refresh();
+
+        return rootView;
+    }
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        String action = intent.getAction();
+        if (action.equals(ChartsHost.REFRESH)) {
+            // TODO: use LimitLines to visualize smart alarms?
+            mSmartAlarmFrom = intent.getIntExtra("smartalarm_from", -1);
+            mSmartAlarmTo = intent.getIntExtra("smartalarm_to", -1);
+            mTimestampFrom = intent.getIntExtra("recording_base_timestamp", -1);
+            mSmartAlarmGoneOff = intent.getIntExtra("alarm_gone_off", -1);
+            refresh();
+        } else {
+            super.onReceive(context, intent);
+        }
+    }
+
+    private void setupActivityChart() {
+        mActivityChart.setBackgroundColor(BACKGROUND_COLOR);
+        mActivityChart.getDescription().setTextColor(DESCRIPTION_COLOR);
+        configureBarLineChartDefaults(mActivityChart);
+
+        XAxis x = mActivityChart.getXAxis();
+        x.setDrawLabels(true);
+        x.setDrawGridLines(false);
+        x.setEnabled(true);
+        x.setTextColor(CHART_TEXT_COLOR);
+        x.setDrawLimitLinesBehindData(true);
+
+        YAxis y = mActivityChart.getAxisLeft();
+        y.setDrawGridLines(false);
+        y.setAxisMaximum(1f);
+        y.setAxisMinimum(0);
+        y.setDrawTopYLabelEntry(false);
+        y.setTextColor(CHART_TEXT_COLOR);
+        y.setEnabled(true);
+
+        YAxis yAxisRight = mActivityChart.getAxisRight();
+        yAxisRight.setDrawGridLines(false);
+        yAxisRight.setEnabled(supportsHeartrate(getChartsHost().getDevice()));
+        yAxisRight.setDrawLabels(true);
+        yAxisRight.setDrawTopYLabelEntry(true);
+        yAxisRight.setTextColor(CHART_TEXT_COLOR);
+        yAxisRight.setAxisMaximum(HeartRateUtils.getInstance().getMaxHeartRate());
+        yAxisRight.setAxisMinimum(HeartRateUtils.getInstance().getMinHeartRate());
+    }
+
+    @Override
+    protected void setupLegend(Chart<?> chart) {
+        List<LegendEntry> legendEntries = new ArrayList<>(4);
+        LegendEntry lightSleepEntry = new LegendEntry();
+        lightSleepEntry.label = getActivity().getString(R.string.sleep_colored_stats_light);
+        lightSleepEntry.formColor = akLightSleep.color;
+        legendEntries.add(lightSleepEntry);
+
+        LegendEntry deepSleepEntry = new LegendEntry();
+        deepSleepEntry.label = getActivity().getString(R.string.sleep_colored_stats_deep);
+        deepSleepEntry.formColor = akDeepSleep.color;
+        legendEntries.add(deepSleepEntry);
+
+        if (supportsRemSleep(getChartsHost().getDevice())) {
+            LegendEntry remSleepEntry = new LegendEntry();
+            remSleepEntry.label = getActivity().getString(R.string.sleep_colored_stats_rem);
+            remSleepEntry.formColor = akRemSleep.color;
+            legendEntries.add(remSleepEntry);
+        }
+
+        if (supportsAwakeSleep(getChartsHost().getDevice())) {
+            LegendEntry awakeSleepEntry = new LegendEntry();
+            awakeSleepEntry.label = getActivity().getString(R.string.abstract_chart_fragment_kind_awake_sleep);
+            awakeSleepEntry.formColor = akAwakeSleep.color;
+            legendEntries.add(awakeSleepEntry);
+        }
+
+        if (supportsHeartrate(getChartsHost().getDevice())) {
+            LegendEntry hrEntry = new LegendEntry();
+            hrEntry.label = HEARTRATE_LABEL;
+            hrEntry.formColor = HEARTRATE_COLOR;
+            legendEntries.add(hrEntry);
+            if (!CHARTS_SLEEP_RANGE_24H && SHOW_CHARTS_AVERAGE) {
+                LegendEntry hrAverageEntry = new LegendEntry();
+                hrAverageEntry.label = HEARTRATE_AVERAGE_LABEL;
+                hrAverageEntry.formColor = Color.RED;
+                legendEntries.add(hrAverageEntry);
+            }
+        }
+        chart.getLegend().setCustom(legendEntries);
+        chart.getLegend().setTextColor(LEGEND_TEXT_COLOR);
+    }
+
+    @Override
+    protected List<? extends ActivitySample> getSamples(DBHandler db, GBDevice device, int tsFrom, int tsTo) {
+        // Temporary fix for totally wrong sleep amounts.
+        return super.getAllSamples(db, device, tsFrom, tsTo);
+    }
+
+    protected List<SleepScoreSample> getSleepScoreSamples(DBHandler db, GBDevice device, int tsFrom, int tsTo) {
+        TimeSampleProvider<? extends SleepScoreSample> provider = device.getDeviceCoordinator().getSleepScoreProvider(device, db.getDaoSession());
+        return (List<SleepScoreSample>) provider.getAllSamples(tsFrom * 1000L, tsTo * 1000L);
+    }
+
+    @Override
+    protected void renderCharts() {
+        mActivityChart.animateX(ANIM_TIME, Easing.EaseInOutQuart);
+    }
+
+    private static class MySleepChartsData extends ChartsData {
+        private long totalSleep;
+        private long totalAwake;
+        private long totalRem;
+        private long totalDeep;
+        private long totalLight;
+        private int sleepScore;
+        private final List<SleepSession> sleepSessions;
+
+        public MySleepChartsData(List<SleepSession> sleepSessions, long totalSleep, long totalAwake, long totalRem, long totalDeep, long totalLight, int sleepScore) {
+            this.sleepSessions = sleepSessions;
+            this.totalAwake = totalAwake;
+            this.totalSleep = totalSleep;
+            this.totalRem = totalRem;
+            this.totalDeep = totalDeep;
+            this.totalLight = totalLight;
+            this.sleepScore = sleepScore;
+        }
+
+        public long getTotalSleep() {
+            return totalSleep;
+        }
+
+        public long getTotalAwake() {
+            return totalAwake;
+        }
+
+        public long getTotalRem() {
+            return totalRem;
+        }
+
+        public long getTotalDeep() {
+            return totalDeep;
+        }
+
+        public long getTotalLight() {
+            return totalLight;
+        }
+
+        public int getSleepScore() {return sleepScore;}
+
+        public List<SleepSession> getSleepSessions() {
+            return sleepSessions;
+        }
+    }
+
+    protected static class MyChartsData extends ChartsData {
+        private final DefaultChartsData<LineData> chartsData;
+        private final MySleepChartsData pieData;
+        private final float heartRateAverage;
+        private int heartRateAxisMax;
+        private int heartRateAxisMin;
+        private float intensityAxisMax;
+        private float intensityAxisMin;
+        private float intensityTotal;
+
+        public MyChartsData(MySleepChartsData pieData, DefaultChartsData<LineData> chartsData, float heartRateAverage, int heartRateAxisMin, int heartRateAxisMax, float intensityTotal, float intensityAxisMin, float intensityAxisMax) {
+            this.pieData = pieData;
+            this.chartsData = chartsData;
+            this.heartRateAverage = heartRateAverage;
+            this.heartRateAxisMax = heartRateAxisMax;
+            this.heartRateAxisMin = heartRateAxisMin;
+            this.intensityTotal = intensityTotal;
+            this.intensityAxisMin = intensityAxisMin;
+            this.intensityAxisMax = intensityAxisMax;
+        }
+
+        public MySleepChartsData getPieData() {
+            return pieData;
+        }
+
+        public DefaultChartsData<LineData> getChartsData() {
+            return chartsData;
+        }
+
+        public float getHeartRateAverage() {
+            return heartRateAverage;
+        }
+
+        public int getHeartRateAxisMax() {
+            return heartRateAxisMax;
+        }
+
+        public int getHeartRateAxisMin() { return heartRateAxisMin; }
+
+        public float getIntensityAxisMax() {
+            return intensityAxisMax;
+        }
+
+        public float getIntensityAxisMin() {
+            return intensityAxisMin;
+        }
+
+        public float getIntensityTotal() { return intensityTotal;}
+
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/DefaultChartsData.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/DefaultChartsData.java
new file mode 100644
index 0000000..e09601a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/DefaultChartsData.java
@@ -0,0 +1,38 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities.charts;
+
+import com.github.mikephil.charting.data.ChartData;
+import com.github.mikephil.charting.formatter.ValueFormatter;
+
+public class DefaultChartsData<T extends ChartData<?>> extends ChartsData {
+    private final T data;
+    private final ValueFormatter xValueFormatter;
+
+    public DefaultChartsData(final T data, final ValueFormatter xValueFormatter) {
+        this.xValueFormatter = xValueFormatter;
+        this.data = data;
+    }
+
+    public ValueFormatter getXValueFormatter() {
+        return xValueFormatter;
+    }
+
+    public T getData() {
+        return data;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/HRVStatusFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/HRVStatusFragment.java
new file mode 100644
index 0000000..d1b2cd8
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/HRVStatusFragment.java
@@ -0,0 +1,415 @@
+/*  Copyright (C) 2017-2024 Andreas Shimokawa, Daniele Gobbetti, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities.charts;
+
+import static java.util.stream.Collectors.toCollection;
+
+import android.os.Build;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.github.mikephil.charting.charts.Chart;
+import com.github.mikephil.charting.charts.LineChart;
+import com.github.mikephil.charting.components.LegendEntry;
+import com.github.mikephil.charting.components.XAxis;
+import com.github.mikephil.charting.components.YAxis;
+import com.github.mikephil.charting.data.Entry;
+import com.github.mikephil.charting.data.LineData;
+import com.github.mikephil.charting.data.LineDataSet;
+import com.github.mikephil.charting.formatter.ValueFormatter;
+import com.github.mikephil.charting.interfaces.datasets.ILineDataSet;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+import java.util.Optional;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.dashboard.DashboardHrvWidget;
+import nodomain.freeyourgadget.gadgetbridge.activities.dashboard.GaugeDrawer;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.TimeSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.HrvSummarySample;
+import nodomain.freeyourgadget.gadgetbridge.model.HrvValueSample;
+import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
+
+
+public class HRVStatusFragment extends AbstractChartFragment<HRVStatusFragment.HRVStatusWeeklyData> {
+    protected static final Logger LOG = LoggerFactory.getLogger(HRVStatusFragment.class);
+    protected final int TOTAL_DAYS = 7;
+
+    protected GaugeDrawer gaugeDrawer;
+    private ImageView mHRVStatusGauge;
+    private LineChart mWeeklyHRVStatusChart;
+    private TextView mHRVStatusSevenDaysAvg;
+    private TextView mHRVStatusSevenDaysAvgStatus; // Balanced, Unbalanced, Low
+    private TextView mHRVStatusLastNight;
+    private TextView mHRVStatusLastNight5MinHighest;
+    private TextView mHRVStatusDayAvg;
+    private TextView mHRVStatusBaseline;
+    private TextView mDateView;
+    private TextView mHRVGaugeValue;
+    private TextView mHRVGaugeStatus;
+    protected int CHART_TEXT_COLOR;
+    protected int LEGEND_TEXT_COLOR;
+    protected int TEXT_COLOR;
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+        View rootView = inflater.inflate(R.layout.fragment_hrv_status, container, false);
+
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+            rootView.setOnScrollChangeListener((v, scrollX, scrollY, oldScrollX, oldScrollY) -> {
+                getChartsHost().enableSwipeRefresh(scrollY == 0);
+            });
+        }
+
+        mWeeklyHRVStatusChart = rootView.findViewById(R.id.hrv_weekly_line_chart);
+        mHRVStatusLastNight = rootView.findViewById(R.id.hrv_status_last_night);
+        mHRVStatusSevenDaysAvg = rootView.findViewById(R.id.hrv_status_seven_days_avg);
+        mHRVStatusSevenDaysAvgStatus = rootView.findViewById(R.id.hrv_status_seven_days_avg_rate);
+        mHRVStatusLastNight5MinHighest = rootView.findViewById(R.id.hrv_status_last_night_highest_5);
+        mHRVStatusDayAvg = rootView.findViewById(R.id.hrv_status_day_avg);
+        mHRVStatusBaseline = rootView.findViewById(R.id.hrv_status_baseline);
+        mDateView = rootView.findViewById(R.id.hrv_status_date_view);
+        mHRVStatusGauge = rootView.findViewById(R.id.hrv_status_gauge_bar);
+        mHRVGaugeValue = rootView.findViewById(R.id.hrv_gauge_value);
+        mHRVGaugeStatus = rootView.findViewById(R.id.hrv_gauge_status);
+
+        gaugeDrawer = new GaugeDrawer();
+        setupLineChart();
+        refresh();
+
+        return rootView;
+    }
+
+    @Override
+    public String getTitle() {
+        return getString(R.string.pref_header_hrv_status);
+    }
+
+    @Override
+    protected void init() {
+        TEXT_COLOR = GBApplication.getTextColor(requireContext());
+        LEGEND_TEXT_COLOR = GBApplication.getTextColor(requireContext());
+        CHART_TEXT_COLOR = GBApplication.getSecondaryTextColor(requireContext());
+    }
+
+    @Override
+    protected HRVStatusWeeklyData refreshInBackground(ChartsHost chartsHost, DBHandler db, GBDevice device) {
+        Calendar day = Calendar.getInstance();
+        day.setTime(getEndDate());
+
+        List<HRVStatusDayData> weeklyData = getWeeklyData(db, day, device);
+        return new HRVStatusWeeklyData(weeklyData);
+    }
+
+    @Override
+    protected void renderCharts() {
+        mWeeklyHRVStatusChart.invalidate();
+    }
+
+    protected LineDataSet createDataSet(final List<Entry> values) {
+        final LineDataSet lineDataSet = new LineDataSet(values, getString(R.string.hrv_status_day_avg));
+        lineDataSet.setColor(getResources().getColor(R.color.hrv_status_char_line_color));
+        lineDataSet.setDrawCircles(false);
+        lineDataSet.setLineWidth(2f);
+        lineDataSet.setFillAlpha(255);
+        lineDataSet.setCircleRadius(5f);
+        lineDataSet.setDrawCircles(true);
+        lineDataSet.setDrawCircleHole(true);
+        lineDataSet.setCircleColor(getResources().getColor(R.color.hrv_status_char_line_color));
+        lineDataSet.setAxisDependency(YAxis.AxisDependency.LEFT);
+        lineDataSet.setDrawValues(true);
+        lineDataSet.setValueTextSize(10f);
+        lineDataSet.setValueTextColor(CHART_TEXT_COLOR);
+        lineDataSet.setValueFormatter(new ValueFormatter() {
+            @Override
+            public String getFormattedValue(float value) {
+                return String.format(Locale.ROOT, "%d", (int) value);
+            }
+        });
+        return lineDataSet;
+    }
+
+    @Override
+    protected void updateChartsnUIThread(HRVStatusWeeklyData weeklyData) {
+        String formattedDate = new SimpleDateFormat("E, MMM dd").format(getEndDate());
+        mDateView.setText(formattedDate);
+
+        mWeeklyHRVStatusChart.setData(null); // workaround for https://github.com/PhilJay/MPAndroidChart/issues/2317
+        List<Entry> lineEntries = new ArrayList<>();
+        final List<ILineDataSet> lineDataSets = new ArrayList<>();
+        weeklyData.getDaysData().forEach((HRVStatusDayData day) -> {
+            if (day.status.getNum() > 0) {
+                lineEntries.add(new Entry(day.i, day.dayAvg));
+            } else {
+                if (!lineEntries.isEmpty()) {
+                    List<Entry> clone = new ArrayList<>(lineEntries.size());
+                    clone.addAll(lineEntries);
+                    lineDataSets.add(createDataSet(clone));
+                    lineEntries.clear();
+                }
+            }
+        });
+        if (!lineEntries.isEmpty()) {
+            lineDataSets.add(createDataSet(lineEntries));
+        }
+
+        List<LegendEntry> legendEntries = new ArrayList<>(1);
+        LegendEntry activityEntry = new LegendEntry();
+        activityEntry.label = getString(R.string.hrv_status_day_avg_legend);
+        activityEntry.formColor = getResources().getColor(R.color.hrv_status_char_line_color);
+        legendEntries.add(activityEntry);
+        mWeeklyHRVStatusChart.getLegend().setTextColor(LEGEND_TEXT_COLOR);
+        mWeeklyHRVStatusChart.getLegend().setCustom(legendEntries);
+
+        final LineData lineData = new LineData(lineDataSets);
+        mWeeklyHRVStatusChart.setData(lineData);
+
+        final XAxis x = mWeeklyHRVStatusChart.getXAxis();
+        x.setValueFormatter(getHRVStatusChartDayValueFormatter(weeklyData));
+
+        HRVStatusDayData today = weeklyData.getCurrentDay();
+        mHRVStatusSevenDaysAvg.setText(today.weeklyAvg > 0 ? getString(R.string.hrv_status_unit, today.weeklyAvg) : "-");
+        mHRVStatusLastNight.setText(today.lastNight > 0 ? getString(R.string.hrv_status_unit, today.lastNight) : "-");
+        mHRVStatusLastNight5MinHighest.setText(today.lastNight5MinHigh > 0 ? getString(R.string.hrv_status_unit, today.lastNight5MinHigh) : "-");
+        mHRVStatusDayAvg.setText(today.dayAvg > 0 ? getString(R.string.hrv_status_unit, today.dayAvg) : "-");
+        mHRVStatusBaseline.setText(today.baseLineBalancedLower > 0 && today.baseLineBalancedUpper > 0 ? getString(R.string.hrv_status_baseline, today.baseLineBalancedLower, today.baseLineBalancedUpper) : "-");
+        switch (today.status) {
+            case NONE:
+                mHRVStatusSevenDaysAvgStatus.setText("-");
+                mHRVStatusSevenDaysAvgStatus.setTextColor(TEXT_COLOR);
+                mHRVGaugeStatus.setText("");
+                mHRVGaugeStatus.setTextColor(TEXT_COLOR);
+                break;
+            case POOR:
+                mHRVStatusSevenDaysAvgStatus.setText(getString(R.string.hrv_status_poor));
+                mHRVStatusSevenDaysAvgStatus.setTextColor(getResources().getColor(R.color.hrv_status_poor));
+                mHRVGaugeStatus.setText(getString(R.string.hrv_status_poor));
+                mHRVGaugeStatus.setTextColor(getResources().getColor(R.color.hrv_status_poor));
+                break;
+            case LOW:
+                mHRVStatusSevenDaysAvgStatus.setText(getString(R.string.hrv_status_low));
+                mHRVStatusSevenDaysAvgStatus.setTextColor(getResources().getColor(R.color.hrv_status_low));
+                mHRVGaugeStatus.setText(getString(R.string.hrv_status_low));
+                mHRVGaugeStatus.setTextColor(getResources().getColor(R.color.hrv_status_low));
+                break;
+            case UNBALANCED:
+                mHRVStatusSevenDaysAvgStatus.setText(getString(R.string.hrv_status_unbalanced));
+                mHRVStatusSevenDaysAvgStatus.setTextColor(getResources().getColor(R.color.hrv_status_unbalanced));
+                mHRVGaugeStatus.setText(getString(R.string.hrv_status_unbalanced));
+                mHRVGaugeStatus.setTextColor(getResources().getColor(R.color.hrv_status_unbalanced));
+                break;
+            case BALANCED:
+                mHRVStatusSevenDaysAvgStatus.setText(getString(R.string.hrv_status_balanced));
+                mHRVStatusSevenDaysAvgStatus.setTextColor(getResources().getColor(R.color.hrv_status_balanced));
+                mHRVGaugeStatus.setText(getString(R.string.hrv_status_balanced));
+                mHRVGaugeStatus.setTextColor(getResources().getColor(R.color.hrv_status_balanced));
+                break;
+        }
+        float value = DashboardHrvWidget.calculateGaugeValue(today.weeklyAvg, today.baseLineLowUpper, today.baseLineBalancedLower, today.baseLineBalancedUpper);
+        final String valueText = value > 0 ? getString(R.string.hrv_status_unit, today.weeklyAvg) : getString(R.string.stats_empty_value);
+        mHRVGaugeValue.setText(valueText);
+        gaugeDrawer.drawSegmentedGauge(mHRVStatusGauge, DashboardHrvWidget.getColors(), DashboardHrvWidget.getSegments(), value, false, true);
+    }
+
+    private List<HRVStatusDayData> getWeeklyData(DBHandler db, Calendar day, GBDevice device) {
+        day = DateTimeUtils.dayStart(day);
+        day.add(Calendar.DATE, -TOTAL_DAYS + 1);
+
+        List<HRVStatusDayData> weeklyData = new ArrayList<>();
+        for (int counter = 0; counter < TOTAL_DAYS; counter++) {
+            int startTs = (int) (day.getTimeInMillis() / 1000);
+            int endTs = startTs + 24 * 60 * 60 - 1;
+            Optional<? extends HrvSummarySample> latestSummarySample = getSamples(db, device, startTs, endTs)
+                    .stream()
+                    .max(Comparator.comparingLong(HrvSummarySample::getTimestamp));
+            List<? extends HrvValueSample> valueSamples = getHrvValueSamples(db, device, startTs, endTs);
+
+            int avgHRV = (int) valueSamples.stream().mapToInt(HrvValueSample::getValue).average().orElse(0);
+            if (latestSummarySample.isPresent()) {
+                final HrvSummarySample sample = latestSummarySample.get();
+                Calendar finalDay = (Calendar) day.clone();
+                weeklyData.add(new HRVStatusDayData(
+                        finalDay,
+                        counter,
+                        sample.getTimestamp(),
+                        avgHRV,
+                        sample.getWeeklyAverage() != null ? sample.getWeeklyAverage() : 0,
+                        sample.getLastNightAverage() != null ? sample.getLastNightAverage() : 0,
+                        sample.getLastNight5MinHigh() != null ? sample.getLastNight5MinHigh() : 0,
+                        sample.getBaselineLowUpper() != null ? sample.getBaselineLowUpper() : 0,
+                        sample.getBaselineBalancedLower() != null ? sample.getBaselineBalancedLower() : 0,
+                        sample.getBaselineBalancedUpper() != null ? sample.getBaselineBalancedUpper() : 0,
+                        sample.getStatus() != null ? sample.getStatus() : HrvSummarySample.Status.NONE
+                ));
+            } else {
+                HRVStatusDayData d = new HRVStatusDayData(
+                        (Calendar) day.clone(),
+                        counter,
+                        0,
+                        avgHRV,
+                        0,
+                        0,
+                        0,
+                        0,
+                        0,
+                        0,
+                        HrvSummarySample.Status.NONE
+                );
+                weeklyData.add(d);
+            }
+
+            day.add(Calendar.DATE, 1);
+        }
+        return weeklyData;
+    }
+
+    private List<? extends HrvSummarySample> getSamples(final DBHandler db, final GBDevice device, int tsFrom, int tsTo) {
+        final DeviceCoordinator coordinator = device.getDeviceCoordinator();
+        final TimeSampleProvider<? extends HrvSummarySample> sampleProvider = coordinator.getHrvSummarySampleProvider(device, db.getDaoSession());
+        return sampleProvider.getAllSamples(tsFrom * 1000L, tsTo * 1000L);
+    }
+
+    public List<? extends HrvValueSample> getHrvValueSamples(final DBHandler db, final GBDevice device, int tsFrom, int tsTo) {
+        final DeviceCoordinator coordinator = device.getDeviceCoordinator();
+        final TimeSampleProvider<? extends HrvValueSample> sampleProvider = coordinator.getHrvValueSampleProvider(device, db.getDaoSession());
+        return sampleProvider.getAllSamples(tsFrom * 1000L, tsTo * 1000L);
+    }
+
+    private void setupLineChart() {
+        mWeeklyHRVStatusChart.getDescription().setEnabled(false);
+        mWeeklyHRVStatusChart.setTouchEnabled(false);
+        mWeeklyHRVStatusChart.setPinchZoom(false);
+        mWeeklyHRVStatusChart.setDoubleTapToZoomEnabled(false);
+
+
+        final XAxis xAxisBottom = mWeeklyHRVStatusChart.getXAxis();
+        xAxisBottom.setPosition(XAxis.XAxisPosition.BOTTOM);
+        xAxisBottom.setDrawLabels(true);
+        xAxisBottom.setDrawGridLines(false);
+        xAxisBottom.setEnabled(true);
+        xAxisBottom.setDrawLimitLinesBehindData(true);
+        xAxisBottom.setAxisMaximum(6 + 0.5f);
+        xAxisBottom.setAxisMinimum(0 - 0.5f);
+        xAxisBottom.setTextColor(CHART_TEXT_COLOR);
+
+        final YAxis yAxisLeft = mWeeklyHRVStatusChart.getAxisLeft();
+        yAxisLeft.setDrawGridLines(true);
+        yAxisLeft.setAxisMaximum(120);
+        yAxisLeft.setAxisMinimum(0);
+        yAxisLeft.setDrawTopYLabelEntry(false);
+        yAxisLeft.setEnabled(true);
+        yAxisLeft.setTextColor(CHART_TEXT_COLOR);
+
+        final YAxis yAxisRight = mWeeklyHRVStatusChart.getAxisRight();
+        yAxisRight.setEnabled(true);
+        yAxisRight.setDrawLabels(false);
+        yAxisRight.setDrawGridLines(false);
+        yAxisRight.setDrawAxisLine(true);
+    }
+
+    ValueFormatter getHRVStatusChartDayValueFormatter(HRVStatusWeeklyData weeklyData) {
+        return new ValueFormatter() {
+            @Override
+            public String getFormattedValue(float value) {
+                return formatHRVStatusChartValue((long) value, weeklyData);
+            }
+        };
+    }
+
+    protected String formatHRVStatusChartValue(long value, HRVStatusWeeklyData weeklyData) {
+        HRVStatusDayData day = weeklyData.getDay((int) value);
+
+        SimpleDateFormat formatLetterDay = new SimpleDateFormat("EEE", Locale.getDefault());
+        return formatLetterDay.format(new Date(day.day.getTimeInMillis()));
+    }
+
+    protected void setupLegend(Chart<?> chart) {}
+
+    protected static class HRVStatusWeeklyData extends ChartsData {
+        private final List<HRVStatusDayData> data;
+
+        public HRVStatusWeeklyData(final List<HRVStatusDayData> chartsData) {
+            this.data = chartsData;
+        }
+
+        public HRVStatusDayData getDay(int i) {
+            return this.data.get(i);
+        }
+
+        public HRVStatusDayData getCurrentDay() {
+            return this.data.get(this.data.size() - 1);
+        }
+
+        public List<HRVStatusDayData> getDaysData() {
+            return data;
+        }
+    }
+
+    protected static class HRVStatusDayData {
+        public Integer i;
+        public long timestamp;
+        public Integer weeklyAvg;
+        public Integer lastNight;
+        public Integer lastNight5MinHigh;
+        public Integer dayAvg;
+        public Integer baseLineBalancedLower;
+        public Integer baseLineBalancedUpper;
+        public Integer baseLineLowUpper;
+        public HrvSummarySample.Status status;
+        public Calendar day;
+
+        public HRVStatusDayData(Calendar day,
+                                int i, long timestamp,
+                                Integer dayAvg,
+                                Integer weeklyAvg,
+                                Integer lastNight,
+                                Integer lastNight5MinHigh,
+                                Integer baseLineLowUpper,
+                                Integer baseLineBalancedLower,
+                                Integer baseLineBalancedUpper,
+                                HrvSummarySample.Status status) {
+            this.lastNight = lastNight;
+            this.weeklyAvg = weeklyAvg;
+            this.lastNight5MinHigh = lastNight5MinHigh;
+            this.i = i;
+            this.timestamp = timestamp;
+            this.status = status;
+            this.day = day;
+            this.dayAvg = dayAvg;
+            this.baseLineLowUpper = baseLineLowUpper;
+            this.baseLineBalancedLower = baseLineBalancedLower;
+            this.baseLineBalancedUpper = baseLineBalancedUpper;
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/HeartRateDailyFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/HeartRateDailyFragment.java
new file mode 100644
index 0000000..10e19c9
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/HeartRateDailyFragment.java
@@ -0,0 +1,322 @@
+package nodomain.freeyourgadget.gadgetbridge.activities.charts;
+
+import android.graphics.Color;
+import android.os.Build;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import androidx.core.content.ContextCompat;
+
+import com.github.mikephil.charting.charts.Chart;
+import com.github.mikephil.charting.charts.LineChart;
+import com.github.mikephil.charting.components.LegendEntry;
+import com.github.mikephil.charting.components.LimitLine;
+import com.github.mikephil.charting.components.XAxis;
+import com.github.mikephil.charting.components.YAxis;
+import com.github.mikephil.charting.data.Entry;
+import com.github.mikephil.charting.data.LineData;
+import com.github.mikephil.charting.data.LineDataSet;
+import com.github.mikephil.charting.interfaces.datasets.ILineDataSet;
+
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.HeartRateUtils;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.AbstractActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.model.HeartRateSample;
+import nodomain.freeyourgadget.gadgetbridge.util.Accumulator;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+public class HeartRateDailyFragment extends AbstractChartFragment<HeartRateDailyFragment.HeartRateData> {
+
+    protected int HEARTRATE_COLOR;
+    protected int CHART_TEXT_COLOR;
+    protected int BACKGROUND_COLOR;
+    protected int DESCRIPTION_COLOR;
+    protected int LEGEND_TEXT_COLOR;
+
+    private TextView mDateView;
+    private TextView hrResting;
+    private TextView hrAverage;
+    private TextView hrMinimum;
+    private TextView hrMaximum;
+    private LineChart hrLineChart;
+
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+        View rootView = inflater.inflate(R.layout.fragment_heart_rate, container, false);
+
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+            rootView.setOnScrollChangeListener((v, scrollX, scrollY, oldScrollX, oldScrollY) -> {
+                getChartsHost().enableSwipeRefresh(scrollY == 0);
+            });
+        }
+
+        mDateView = rootView.findViewById(R.id.hr_date_view);
+        hrLineChart = rootView.findViewById(R.id.heart_rate_line_chart);
+        hrResting = rootView.findViewById(R.id.hr_resting);
+        hrAverage = rootView.findViewById(R.id.hr_average);
+        hrMinimum = rootView.findViewById(R.id.hr_minimum);
+        hrMaximum = rootView.findViewById(R.id.hr_maximum);
+        final LinearLayout heartRateRestingWrapper = rootView.findViewById(R.id.hr_resting_wrapper);
+
+        setupChart();
+        refresh();
+        setupLegend(hrLineChart);
+
+        if (!supportsHeartRateRestingMeasurement()) {
+            heartRateRestingWrapper.setVisibility(View.GONE);
+        }
+
+        return rootView;
+    }
+
+    public boolean supportsHeartRateRestingMeasurement() {
+        final GBDevice device = getChartsHost().getDevice();
+        return device.getDeviceCoordinator().supportsHeartRateRestingMeasurement(device);
+    }
+
+    protected List<? extends AbstractActivitySample> getActivitySamples(DBHandler db, GBDevice device, int tsFrom, int tsTo) {
+        SampleProvider<? extends ActivitySample> provider = device.getDeviceCoordinator().getSampleProvider(device, db.getDaoSession());
+        return provider.getAllActivitySamplesHighRes(tsFrom, tsTo);
+    }
+
+    @Override
+    public String getTitle() {
+        return getString(R.string.heart_rate);
+    }
+
+    @Override
+    protected void init() {
+        Prefs prefs = GBApplication.getPrefs();
+        CHART_TEXT_COLOR = GBApplication.getSecondaryTextColor(getContext());
+        DESCRIPTION_COLOR = LEGEND_TEXT_COLOR = GBApplication.getTextColor(getContext());
+        if (prefs.getBoolean("chart_heartrate_color", false)) {
+            HEARTRATE_COLOR = ContextCompat.getColor(getContext(), R.color.chart_heartrate_alternative);
+        }else{
+            HEARTRATE_COLOR = ContextCompat.getColor(getContext(), R.color.chart_heartrate);
+        }
+    }
+
+    @Override
+    protected HeartRateDailyFragment.HeartRateData refreshInBackground(ChartsHost chartsHost, DBHandler db, GBDevice device) {
+        Calendar day = Calendar.getInstance();
+        day.setTime(chartsHost.getEndDate());
+        day.add(Calendar.DATE, 0);
+        day.set(Calendar.HOUR_OF_DAY, 0);
+        day.set(Calendar.MINUTE, 0);
+        day.set(Calendar.SECOND, 0);
+        day.add(Calendar.HOUR, 0);
+        int startTs = (int) (day.getTimeInMillis() / 1000);
+        int endTs = startTs + 24 * 60 * 60 - 1;
+
+        List<? extends ActivitySample> samples = getActivitySamples(db, device, startTs, endTs);
+
+        int restingHeartRate = -1;
+        if (supportsHeartRateRestingMeasurement()) {
+            restingHeartRate = device.getDeviceCoordinator()
+                    .getHeartRateRestingSampleProvider(device, db.getDaoSession())
+                    .getAllSamples(startTs * 1000L, endTs * 1000L)
+                    .stream()
+                    .max(Comparator.comparingLong(HeartRateSample::getTimestamp))
+                    .map(HeartRateSample::getHeartRate)
+                    .orElse(-1);
+        }
+
+        return new HeartRateData(samples, restingHeartRate);
+    }
+
+    @Override
+    protected void renderCharts() {
+        hrLineChart.invalidate();
+    }
+
+    private void setupChart() {
+        hrLineChart.setBackgroundColor(BACKGROUND_COLOR);
+        hrLineChart.getDescription().setTextColor(DESCRIPTION_COLOR);
+        hrLineChart.getDescription().setEnabled(false);
+
+
+        XAxis x = hrLineChart.getXAxis();
+        x.setDrawLabels(true);
+        x.setDrawGridLines(false);
+        x.setEnabled(true);
+        x.setTextColor(CHART_TEXT_COLOR);
+        x.setDrawLimitLinesBehindData(true);
+        x.setPosition(XAxis.XAxisPosition.BOTTOM);
+        x.setAxisMinimum(0f);
+        x.setAxisMaximum(86400f);
+
+        YAxis y = hrLineChart.getAxisLeft();
+        y.setDrawGridLines(false);
+        y.setDrawTopYLabelEntry(true);
+        y.setTextColor(CHART_TEXT_COLOR);
+        y.setEnabled(true);
+        y.setAxisMaximum(HeartRateUtils.getInstance().getMaxHeartRate());
+        y.setAxisMinimum(HeartRateUtils.getInstance().getMinHeartRate());
+
+        YAxis yAxisRight = hrLineChart.getAxisRight();
+        yAxisRight.setDrawGridLines(false);
+        yAxisRight.setDrawLabels(true);
+        yAxisRight.setDrawTopYLabelEntry(true);
+        yAxisRight.setTextColor(CHART_TEXT_COLOR);
+        yAxisRight.setAxisMaximum(HeartRateUtils.getInstance().getMaxHeartRate());
+        yAxisRight.setAxisMinimum(HeartRateUtils.getInstance().getMinHeartRate());
+
+        refresh();
+    }
+
+    @Override
+    protected void setupLegend(Chart<?> chart) {
+        List<LegendEntry> legendEntries = new ArrayList<>(1);
+        LegendEntry hrEntry = new LegendEntry();
+        hrEntry.label = getTitle();
+        hrEntry.formColor = HEARTRATE_COLOR;
+        legendEntries.add(hrEntry);
+
+        if (GBApplication.getPrefs().getBoolean("charts_show_average", true)) {
+            LegendEntry hrAverageEntry = new LegendEntry();
+            hrAverageEntry.label = getString(R.string.hr_average);
+            hrAverageEntry.formColor = Color.RED;
+            legendEntries.add(hrAverageEntry);
+        }
+
+        //if (supportsHeartRateRestingMeasurement()) {
+        //    LegendEntry hrRestingEntry = new LegendEntry();
+        //    hrRestingEntry.label = getString(R.string.hr_resting);
+        //    hrRestingEntry.formColor = Color.GRAY;
+        //    legendEntries.add(hrRestingEntry);
+        //}
+
+        chart.getLegend().setCustom(legendEntries);
+        chart.getLegend().setTextColor(LEGEND_TEXT_COLOR);
+        chart.getLegend().setWordWrapEnabled(true);
+    }
+
+    protected LineDataSet createHeartRateDataSet(final List<Entry> values) {
+        LineDataSet dataSet = new LineDataSet(values, "Heart Rate");
+        dataSet.setLineWidth(1.5f);
+        dataSet.setMode(LineDataSet.Mode.HORIZONTAL_BEZIER);
+        dataSet.setCubicIntensity(0.1f);
+        dataSet.setDrawCircles(false);
+        dataSet.setDrawValues(true);
+        dataSet.setAxisDependency(YAxis.AxisDependency.RIGHT);
+        dataSet.setColor(HEARTRATE_COLOR);
+        dataSet.setValueTextColor(CHART_TEXT_COLOR);
+        return dataSet;
+    }
+
+    @Override
+    protected void updateChartsnUIThread(HeartRateDailyFragment.HeartRateData data) {
+        Calendar day = Calendar.getInstance();
+        day.setTime(getEndDate());
+        day.add(Calendar.DATE, 0);
+        day.set(Calendar.HOUR_OF_DAY, 0);
+        day.set(Calendar.MINUTE, 0);
+        day.set(Calendar.SECOND, 0);
+        day.add(Calendar.HOUR, 0);
+        int startTs = (int) (day.getTimeInMillis() / 1000);
+        int endTs = startTs + 24 * 60 * 60 - 1;
+        Date date = new Date((long) endTs * 1000);
+        String formattedDate = new SimpleDateFormat("E, MMM dd").format(date);
+        mDateView.setText(formattedDate);
+
+        HeartRateUtils heartRateUtilsInstance = HeartRateUtils.getInstance();
+        final TimestampTranslation tsTranslation = new TimestampTranslation();
+        final List<Entry> lineEntries = new ArrayList<>();
+        List<? extends ActivitySample> samples = data.samples;
+        final Accumulator accumulator = new Accumulator();
+
+        final List<ILineDataSet> lineDataSets = new ArrayList<>();
+        int lastTsShorten = 0;
+        for (int i =0; i < samples.size(); i++) {
+            final ActivitySample sample = samples.get(i);
+            final int tsShorten = tsTranslation.shorten(sample.getTimestamp());
+            if (!heartRateUtilsInstance.isValidHeartRateValue(sample.getHeartRate())) {
+                continue;
+            }
+            if (lastTsShorten == 0 || (tsShorten - lastTsShorten) <= 60 * HeartRateUtils.MAX_HR_MEASUREMENTS_GAP_MINUTES) {
+                lineEntries.add(new Entry(tsShorten, sample.getHeartRate()));
+            } else {
+                if (!lineEntries.isEmpty()) {
+                    List<Entry> clone = new ArrayList<>(lineEntries.size());
+                    clone.addAll(lineEntries);
+                    lineDataSets.add(createHeartRateDataSet(clone));
+                    lineEntries.clear();
+                }
+            }
+            lastTsShorten = tsShorten;
+            lineEntries.add(new Entry(tsShorten, sample.getHeartRate()));
+            accumulator.add(sample.getHeartRate());
+        }
+
+        if (!lineEntries.isEmpty()) {
+            lineDataSets.add(createHeartRateDataSet(lineEntries));
+        }
+
+        final int average = accumulator.getCount() > 0 ? (int) Math.round(accumulator.getAverage()) : -1;
+        final int minimum = accumulator.getCount() > 0 ? (int) Math.round(accumulator.getMin()) : -1;
+        final int maximum = accumulator.getCount() > 0 ? (int) Math.round(accumulator.getMax()) : -1;
+
+        hrAverage.setText(average > 0 ? getString(R.string.bpm_value_unit, average) : "-");
+        hrMinimum.setText(minimum > 0 ? getString(R.string.bpm_value_unit, minimum) : "-");
+        hrMaximum.setText(maximum > 0 ? getString(R.string.bpm_value_unit, maximum) : "-");
+        hrResting.setText(data.restingHeartRate > 0 ? getString(R.string.bpm_value_unit, data.restingHeartRate) : "-");
+
+        if (minimum > 0) {
+            hrLineChart.getAxisLeft().setAxisMinimum(Math.max(minimum - 30, 0));
+            hrLineChart.getAxisRight().setAxisMinimum(Math.max(minimum - 30, 0));
+        }
+        if (maximum > 0) {
+            hrLineChart.getAxisLeft().setAxisMaximum(maximum + 30);
+            hrLineChart.getAxisRight().setAxisMaximum(maximum + 30);
+        }
+
+        hrLineChart.getXAxis().setValueFormatter(new SampleXLabelFormatter(tsTranslation, "HH:mm"));
+        hrLineChart.setData(new LineData(lineDataSets));
+
+        hrLineChart.getAxisLeft().removeAllLimitLines();
+
+        if (average > 0 && GBApplication.getPrefs().getBoolean("charts_show_average", true)) {
+            final LimitLine averageLine = new LimitLine(average);
+            averageLine.setLineWidth(1.5f);
+            averageLine.enableDashedLine(15f, 10f, 0f);
+            averageLine.setLineColor(Color.RED);
+            hrLineChart.getAxisLeft().addLimitLine(averageLine);
+        }
+
+        //if (data.restingHeartRate > 0) {
+        //    final LimitLine restingLine = new LimitLine(data.restingHeartRate);
+        //    restingLine.setLineWidth(1.5f);
+        //    restingLine.enableDashedLine(15f, 10f, 0f);
+        //    restingLine.setLineColor(Color.GRAY);
+        //    hrLineChart.getAxisLeft().addLimitLine(restingLine);
+        //}
+    }
+
+    protected static class HeartRateData extends ChartsData {
+        public List<? extends ActivitySample> samples;
+        public int restingHeartRate;
+
+        protected HeartRateData(List<? extends ActivitySample> samples, int restingHeartRate) {
+            this.samples = samples;
+            this.restingHeartRate = restingHeartRate;
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/LiveActivityFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/LiveActivityFragment.java
new file mode 100644
index 0000000..a807249
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/LiveActivityFragment.java
@@ -0,0 +1,547 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, Carsten Pfeiffer, Cre3per,
+    Daniele Gobbetti, Dikay900, Jos茅 Rebelo, Pavel Elagin
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities.charts;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.graphics.Paint;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.github.mikephil.charting.charts.BarLineChartBase;
+import com.github.mikephil.charting.charts.Chart;
+import com.github.mikephil.charting.components.LimitLine;
+import com.github.mikephil.charting.components.XAxis;
+import com.github.mikephil.charting.components.YAxis;
+import com.github.mikephil.charting.data.BarData;
+import com.github.mikephil.charting.data.BarDataSet;
+import com.github.mikephil.charting.data.BarEntry;
+import com.github.mikephil.charting.data.ChartData;
+import com.github.mikephil.charting.data.Entry;
+import com.github.mikephil.charting.data.LineData;
+import com.github.mikephil.charting.data.LineDataSet;
+import com.github.mikephil.charting.utils.Utils;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+import androidx.annotation.Nullable;
+import androidx.fragment.app.FragmentActivity;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.HeartRateUtils;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class LiveActivityFragment extends AbstractActivityChartFragment<ChartsData> {
+    private static final Logger LOG = LoggerFactory.getLogger(LiveActivityFragment.class);
+    private static final int MAX_STEPS_PER_MINUTE = 300;
+    private static final int MIN_STEPS_PER_MINUTE = 60;
+    private static final int RESET_COUNT = 10; // reset the max steps per minute value every 10s
+
+    private BarEntry totalStepsEntry;
+    private BarEntry stepsPerMinuteEntry;
+    private BarDataSet mStepsPerMinuteData;
+    private BarDataSet mTotalStepsData;
+    private LineDataSet mHistorySet;
+    private BarLineChartBase mStepsPerMinuteHistoryChart;
+    private CustomBarChart mStepsPerMinuteCurrentChart;
+    private CustomBarChart mTotalStepsChart;
+    private TextView mMaxHeartRateView;
+
+    private final Steps mSteps = new Steps();
+    private ScheduledExecutorService pulseScheduler;
+    private int maxStepsResetCounter;
+    private LineDataSet mHeartRateSet;
+    private int mHeartRate;
+    private int mMaxHeartRate = 0;
+    private TimestampTranslation tsTranslation;
+
+    private class Steps {
+        private int steps;
+        private int lastTimestamp;
+        private int currentStepsPerMinute;
+        private int maxStepsPerMinute;
+        private int lastStepsPerMinute;
+
+        public int getStepsPerMinute(boolean reset) {
+            lastStepsPerMinute = currentStepsPerMinute;
+            int result = currentStepsPerMinute;
+            if (reset) {
+                currentStepsPerMinute = 0;
+            }
+            return result;
+        }
+
+        public int getTotalSteps() {
+            return steps;
+        }
+
+        public int getMaxStepsPerMinute() {
+            return maxStepsPerMinute;
+        }
+
+        public void updateCurrentSteps(int stepsDelta, int timestamp) {
+            try {
+                if (steps == 0) {
+                    steps += stepsDelta;
+                    lastTimestamp = timestamp;
+                    return;
+                }
+
+                int timeDelta = timestamp - lastTimestamp;
+                currentStepsPerMinute = calculateStepsPerMinute(stepsDelta, timeDelta);
+                if (currentStepsPerMinute > maxStepsPerMinute) {
+                    maxStepsPerMinute = currentStepsPerMinute;
+                    maxStepsResetCounter = 0;
+                }
+                steps += stepsDelta;
+                lastTimestamp = timestamp;
+            } catch (Exception ex) {
+                GB.toast(LiveActivityFragment.this.getContext(), ex.getMessage(), Toast.LENGTH_SHORT, GB.ERROR, ex);
+            }
+        }
+
+        private int calculateStepsPerMinute(int stepsDelta, int seconds) {
+            if (stepsDelta == 0) {
+                return 0; // not walking or not enough data per mills?
+            }
+            if (seconds <= 0) {
+                throw new IllegalArgumentException("delta in seconds is <= 0 -- time change?");
+            }
+
+            int oneMinute = 60;
+            float factor = oneMinute / seconds;
+            int result = (int) (stepsDelta * factor);
+            if (result > MAX_STEPS_PER_MINUTE) {
+                // ignore, return previous value instead
+                result = lastStepsPerMinute;
+            }
+            return result;
+        }
+    }
+
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            switch (action) {
+                case DeviceService.ACTION_REALTIME_SAMPLES: {
+                    ActivitySample sample = (ActivitySample) intent.getSerializableExtra(DeviceService.EXTRA_REALTIME_SAMPLE);
+                    addSample(sample);
+                    break;
+                }
+            }
+        }
+    };
+
+    private void addSample(ActivitySample sample) {
+        int heartRate = sample.getHeartRate();
+        int timestamp = tsTranslation.shorten(sample.getTimestamp());
+        if (HeartRateUtils.getInstance().isValidHeartRateValue(heartRate)) {
+            setCurrentHeartRate(heartRate, timestamp);
+        }
+        int steps = sample.getSteps();
+        if (steps > 0) {
+            addEntries(steps, timestamp);
+        }
+    }
+
+    private int translateTimestampFrom(Intent intent) {
+        return translateTimestamp(intent.getLongExtra(DeviceService.EXTRA_TIMESTAMP, System.currentTimeMillis()));
+    }
+
+    private int translateTimestamp(long tsMillis) {
+        int timestamp = (int) (tsMillis / 1000); // translate to seconds
+        return tsTranslation.shorten(timestamp); // and shorten
+    }
+
+    private void setCurrentHeartRate(int heartRate, int timestamp) {
+        addHistoryDataSet(true);
+        mHeartRate = heartRate;
+        if (mMaxHeartRate < mHeartRate) {
+            mMaxHeartRate = mHeartRate;
+        }
+        mMaxHeartRateView.setText(getContext().getString(R.string.live_activity_max_heart_rate, heartRate, mMaxHeartRate));
+    }
+
+    private int getCurrentHeartRate() {
+        int result = mHeartRate;
+        mHeartRate = -1;
+        return result;
+    }
+
+    private void addEntries(int steps, int timestamp) {
+        mSteps.updateCurrentSteps(steps, timestamp);
+        if (++maxStepsResetCounter > RESET_COUNT) {
+            maxStepsResetCounter = 0;
+            mSteps.maxStepsPerMinute = 0;
+        }
+        // Or: count down the steps until goal reached? And then flash GOAL REACHED -> Set stretch goal
+        LOG.info("Steps: " + steps + ", total: " + mSteps.getTotalSteps() + ", current: " + mSteps.getStepsPerMinute(false));
+
+//        addEntries();
+    }
+
+    private void addEntries(int timestamp) {
+        mTotalStepsChart.setSingleEntryYValue(mSteps.getTotalSteps());
+        YAxis stepsPerMinuteCurrentYAxis = mStepsPerMinuteCurrentChart.getAxisLeft();
+        int maxStepsPerMinute = mSteps.getMaxStepsPerMinute();
+//        int extraRoom = maxStepsPerMinute/5;
+//        buggy in MPAndroidChart? Disable.
+//        stepsPerMinuteCurrentYAxis.setAxisMaxValue(Math.max(MIN_STEPS_PER_MINUTE, maxStepsPerMinute + extraRoom));
+        LimitLine target = new LimitLine(maxStepsPerMinute);
+        stepsPerMinuteCurrentYAxis.removeAllLimitLines();
+        stepsPerMinuteCurrentYAxis.addLimitLine(target);
+
+        int stepsPerMinute = mSteps.getStepsPerMinute(true);
+        mStepsPerMinuteCurrentChart.setSingleEntryYValue(stepsPerMinute);
+
+        if (!addHistoryDataSet(false)) {
+            return;
+        }
+
+        ChartData data = mStepsPerMinuteHistoryChart.getData();
+        if (stepsPerMinute < 0) {
+            stepsPerMinute = 0;
+        }
+        mHistorySet.addEntry(new Entry(timestamp, stepsPerMinute));
+        int hr = getCurrentHeartRate();
+        if (hr > HeartRateUtils.getInstance().getMinHeartRate()) {
+            mHeartRateSet.addEntry(new Entry(timestamp, hr));
+        }
+    }
+
+    private boolean addHistoryDataSet(boolean force) {
+        if (mStepsPerMinuteHistoryChart.getData() == null) {
+            // ignore the first default value to keep the "no-data-description" visible
+            if (force || mSteps.getTotalSteps() > 0) {
+                LineData data = new LineData();
+                data.addDataSet(mHistorySet);
+                data.addDataSet(mHeartRateSet);
+                mStepsPerMinuteHistoryChart.setData(data);
+                return true;
+            }
+            return false;
+        }
+        return true;
+    }
+
+    @Nullable
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+        IntentFilter filterLocal = new IntentFilter();
+        filterLocal.addAction(DeviceService.ACTION_REALTIME_SAMPLES);
+        tsTranslation = new TimestampTranslation();
+
+        View rootView = inflater.inflate(R.layout.fragment_live_activity, container, false);
+
+        mStepsPerMinuteCurrentChart = rootView.findViewById(R.id.livechart_steps_per_minute_current);
+        mTotalStepsChart = rootView.findViewById(R.id.livechart_steps_total);
+        mStepsPerMinuteHistoryChart = rootView.findViewById(R.id.livechart_steps_per_minute_history);
+
+        totalStepsEntry = new BarEntry(1, 0);
+        stepsPerMinuteEntry = new BarEntry(1, 0);
+
+        mStepsPerMinuteData = setupCurrentChart(mStepsPerMinuteCurrentChart, stepsPerMinuteEntry, getString(R.string.live_activity_current_steps_per_minute));
+        mStepsPerMinuteData.setDrawValues(true);
+        mStepsPerMinuteData.setValueTextColor(DESCRIPTION_COLOR);
+        mTotalStepsData = setupTotalStepsChart(mTotalStepsChart, totalStepsEntry, getString(R.string.live_activity_total_steps));
+        mTotalStepsData.setDrawValues(true);
+        mTotalStepsData.setValueTextColor(DESCRIPTION_COLOR);
+        setupHistoryChart(mStepsPerMinuteHistoryChart);
+        mMaxHeartRateView = rootView.findViewById(R.id.livechart_max_heart_rate);
+
+        LocalBroadcastManager.getInstance(getActivity()).registerReceiver(mReceiver, filterLocal);
+
+        return rootView;
+    }
+
+    @Override
+    public void onPause() {
+        enableRealtimeTracking(false);
+        super.onPause();
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        enableRealtimeTracking(true);
+    }
+
+    private ScheduledExecutorService startActivityPulse() {
+        ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor();
+        service.scheduleAtFixedRate(new Runnable() {
+            @Override
+            public void run() {
+                FragmentActivity activity = LiveActivityFragment.this.getActivity();
+                if (activity != null && !activity.isFinishing() && !activity.isDestroyed()) {
+                    activity.runOnUiThread(new Runnable() {
+                        @Override
+                        public void run() {
+                            pulse();
+                        }
+                    });
+                }
+            }
+        }, 0, getPulseIntervalMillis(), TimeUnit.MILLISECONDS);
+        return service;
+    }
+
+    private void stopActivityPulse() {
+        if (pulseScheduler != null) {
+            pulseScheduler.shutdownNow();
+            pulseScheduler = null;
+        }
+    }
+
+    /**
+     * Called in the UI thread.
+     */
+    private void pulse() {
+        addEntries(translateTimestamp(System.currentTimeMillis()));
+
+        LineData historyData = (LineData) mStepsPerMinuteHistoryChart.getData();
+        if (historyData == null) {
+            return;
+        }
+
+        historyData.notifyDataChanged();
+        mTotalStepsData.notifyDataSetChanged();
+        mStepsPerMinuteData.notifyDataSetChanged();
+        mStepsPerMinuteHistoryChart.notifyDataSetChanged();
+
+        renderCharts();
+
+        // have to enable it again and again to keep it measuring
+        GBApplication.deviceService(getChartsHost().getDevice()).onEnableRealtimeHeartRateMeasurement(true);
+    }
+
+    private int getPulseIntervalMillis() {
+        return 1000;
+    }
+
+    @Override
+    protected void onMadeVisibleInActivity() {
+        super.onMadeVisibleInActivity();
+        enableRealtimeTracking(true);
+    }
+
+    private void enableRealtimeTracking(boolean enable) {
+        if (enable && pulseScheduler != null) {
+            // already running
+            return;
+        }
+
+        try {
+            GBApplication.deviceService(getChartsHost().getDevice()).onEnableRealtimeSteps(enable);
+            GBApplication.deviceService(getChartsHost().getDevice()).onEnableRealtimeHeartRateMeasurement(enable);
+        } catch (IllegalStateException e) {
+            LOG.error("IllegalStateException catched, setting realtime tracking globally to {}", enable);
+            GBApplication.deviceService().onEnableRealtimeSteps(enable);
+            GBApplication.deviceService().onEnableRealtimeHeartRateMeasurement(enable);
+        }
+
+        if (enable) {
+            if (getActivity() != null) {
+                getActivity().getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+            }
+            pulseScheduler = startActivityPulse();
+        } else {
+            stopActivityPulse();
+            if (getActivity() != null) {
+                getActivity().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+            }
+        }
+    }
+
+    @Override
+    public void onMadeInvisibleInActivity() {
+        enableRealtimeTracking(false);
+        super.onMadeInvisibleInActivity();
+    }
+
+    @Override
+    public void onDestroyView() {
+        onMadeInvisibleInActivity();
+        LocalBroadcastManager.getInstance(getActivity()).unregisterReceiver(mReceiver);
+        super.onDestroyView();
+    }
+
+    private BarDataSet setupCurrentChart(CustomBarChart chart, BarEntry entry, String title) {
+        mStepsPerMinuteCurrentChart.getAxisLeft().setAxisMaximum(MAX_STEPS_PER_MINUTE);
+        return setupCommonChart(chart, entry, title);
+    }
+
+    private BarDataSet setupCommonChart(CustomBarChart chart, BarEntry entry, String title) {
+        chart.setSinglAnimationEntry(entry);
+
+//        chart.getXAxis().setPosition(XAxis.XAxisPosition.TOP);
+        chart.getXAxis().setDrawLabels(false);
+        chart.getXAxis().setEnabled(false);
+        chart.getXAxis().setTextColor(CHART_TEXT_COLOR);
+        chart.getAxisLeft().setTextColor(CHART_TEXT_COLOR);
+
+        chart.setBackgroundColor(BACKGROUND_COLOR);
+        chart.getDescription().setTextColor(DESCRIPTION_COLOR);
+        chart.getDescription().setText(title);
+//        chart.setNoDataTextDescription("");
+        chart.setNoDataText("");
+        chart.getAxisRight().setEnabled(false);
+
+        List<BarEntry> entries = new ArrayList<>();
+        List<Integer> colors = new ArrayList<>();
+
+        entries.add(entry);
+        colors.add(akActivity.color);
+        colors.add(akActivity.color);
+        colors.add(akActivity.color);
+//        //we don't want labels
+//        xLabels.add("");
+//        xLabels.add("");
+//        xLabels.add("");
+
+        BarDataSet set = new BarDataSet(entries, "");
+        set.setDrawValues(false);
+        set.setColors(colors);
+        BarData data = new BarData(set);
+//        data.setGroupSpace(0);
+        chart.setData(data);
+
+        chart.getLegend().setEnabled(false);
+
+        return set;
+    }
+
+    private BarDataSet setupTotalStepsChart(CustomBarChart chart, BarEntry entry, String label) {
+        mTotalStepsChart.getAxisLeft().addLimitLine(new LimitLine(GBApplication.getPrefs().getInt(ActivityUser.PREF_USER_STEPS_GOAL, ActivityUser.defaultUserStepsGoal), "ss")); // TODO: use daily goal - already reached steps
+        mTotalStepsChart.getAxisLeft().setAxisMinimum(0);
+        mTotalStepsChart.setAutoScaleMinMaxEnabled(true);
+        return setupCommonChart(chart, entry, label); // at the moment, these look the same
+    }
+
+    private void setupHistoryChart(BarLineChartBase chart) {
+        configureBarLineChartDefaults(chart);
+
+        chart.setTouchEnabled(false); // no zooming or anything, because it's updated all the time
+        chart.setBackgroundColor(BACKGROUND_COLOR);
+        chart.getDescription().setTextColor(DESCRIPTION_COLOR);
+        chart.getDescription().setText(getString(R.string.live_activity_steps_per_minute_history));
+        chart.setNoDataText(getString(R.string.live_activity_start_your_activity));
+        chart.getLegend().setEnabled(false);
+        Paint infoPaint = chart.getPaint(Chart.PAINT_INFO);
+        infoPaint.setTextSize(Utils.convertDpToPixel(20f));
+        infoPaint.setFakeBoldText(true);
+        chart.setPaint(infoPaint, Chart.PAINT_INFO);
+
+        XAxis x = chart.getXAxis();
+        x.setDrawLabels(true);
+        x.setDrawGridLines(false);
+        x.setEnabled(true);
+        x.setTextColor(CHART_TEXT_COLOR);
+        x.setValueFormatter(new SampleXLabelFormatter(tsTranslation, "HH:mm"));
+        x.setDrawLimitLinesBehindData(true);
+
+        YAxis y = chart.getAxisLeft();
+        y.setDrawGridLines(false);
+        y.setDrawTopYLabelEntry(false);
+        y.setTextColor(CHART_TEXT_COLOR);
+        y.setEnabled(true);
+        y.setAxisMinimum(0);
+
+        YAxis yAxisRight = chart.getAxisRight();
+        yAxisRight.setDrawGridLines(false);
+        yAxisRight.setEnabled(true);
+        yAxisRight.setDrawLabels(true);
+        yAxisRight.setDrawTopYLabelEntry(false);
+        yAxisRight.setTextColor(CHART_TEXT_COLOR);
+
+        mHistorySet = new LineDataSet(new ArrayList<Entry>(), getString(R.string.live_activity_steps_history));
+        mHistorySet.setAxisDependency(YAxis.AxisDependency.LEFT);
+        mHistorySet.setColor(akActivity.color);
+        mHistorySet.setDrawCircles(false);
+        mHistorySet.setMode(LineDataSet.Mode.CUBIC_BEZIER);
+        mHistorySet.setDrawFilled(true);
+        mHistorySet.setDrawValues(false);
+
+        mHeartRateSet = createHeartrateSet(new ArrayList<Entry>(), getString(R.string.live_activity_heart_rate));
+        mHeartRateSet.setDrawValues(false);
+    }
+
+    @Override
+    public String getTitle() {
+        return getContext().getString(R.string.liveactivity_live_activity);
+    }
+
+    @Override
+    protected void showDateBar(boolean show) {
+        // never show the data bar
+        super.showDateBar(false);
+    }
+
+    @Override
+    protected void refresh() {
+        // do nothing, we don't have any db interaction
+    }
+
+    @Override
+    protected ChartsData refreshInBackground(ChartsHost chartsHost, DBHandler db, GBDevice device) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    protected void updateChartsnUIThread(ChartsData chartsData) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    protected void renderCharts() {
+        mStepsPerMinuteCurrentChart.animateY(150);
+        mTotalStepsChart.animateY(150);
+        mStepsPerMinuteHistoryChart.invalidate();
+    }
+
+    @Override
+    protected List<ActivitySample> getSamples(DBHandler db, GBDevice device, int tsFrom, int tsTo) {
+        throw new UnsupportedOperationException("no db access supported for live activity");
+    }
+
+    @Override
+    protected void setupLegend(Chart<?> chart) {
+        // no legend
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/NonSwipeableViewPager.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/NonSwipeableViewPager.java
new file mode 100644
index 0000000..6aebcbc
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/NonSwipeableViewPager.java
@@ -0,0 +1,53 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities.charts;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+
+import androidx.viewpager.widget.ViewPager;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+
+public class NonSwipeableViewPager extends ViewPager {
+    private boolean allowSwipe = true;
+
+    public NonSwipeableViewPager(final Context context, final AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public void setAllowSwipe(final boolean allowSwipe) {
+        this.allowSwipe = allowSwipe;
+    }
+
+    @Override
+    public boolean onInterceptTouchEvent(final MotionEvent ev) {
+        if (allowSwipe) {
+            return super.onInterceptTouchEvent(ev);
+        }
+        return false;
+    }
+
+    @Override
+    public boolean onTouchEvent(final MotionEvent ev) {
+        if (allowSwipe) {
+            return super.onTouchEvent(ev);
+        }
+        return false;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/PaiChartFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/PaiChartFragment.java
new file mode 100644
index 0000000..b1a75e4
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/PaiChartFragment.java
@@ -0,0 +1,477 @@
+/*  Copyright (C) 2023-2024 Daniel Dakhno, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities.charts;
+
+import android.graphics.Color;
+import android.os.Build;
+import android.os.Bundle;
+import android.util.TypedValue;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import androidx.core.content.ContextCompat;
+
+import com.github.mikephil.charting.charts.BarChart;
+import com.github.mikephil.charting.charts.Chart;
+import com.github.mikephil.charting.charts.PieChart;
+import com.github.mikephil.charting.components.Legend;
+import com.github.mikephil.charting.components.LegendEntry;
+import com.github.mikephil.charting.components.XAxis;
+import com.github.mikephil.charting.components.YAxis;
+import com.github.mikephil.charting.data.BarData;
+import com.github.mikephil.charting.data.BarDataSet;
+import com.github.mikephil.charting.data.BarEntry;
+import com.github.mikephil.charting.data.ChartData;
+import com.github.mikephil.charting.formatter.ValueFormatter;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+import java.util.Optional;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.dashboard.GaugeDrawer;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.TimeSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.PaiSample;
+import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
+
+public class PaiChartFragment extends AbstractChartFragment<PaiChartFragment.PaiChartsData> {
+    protected static final Logger LOG = LoggerFactory.getLogger(PaiChartFragment.class);
+
+    protected final int TOTAL_DAYS = getRangeDays();
+
+    protected Locale mLocale;
+
+    protected ImageView mGoalMinutesGauge;
+    protected BarChart mWeekChart;
+    protected TextView mDateView;
+    protected TextView mLineToday;
+    protected TextView mLineTotal;
+    protected TextView mLineLowInc;
+    protected TextView mLineLowTime;
+    protected TextView mLineModerateInc;
+    protected TextView mLineModerateTime;
+    protected TextView mLineHighInc;
+    protected TextView mLineHighTime;
+    protected LinearLayout mTileLow;
+    protected LinearLayout mTileModerate;
+    protected LinearLayout mTileHigh;
+
+    protected int BACKGROUND_COLOR;
+    protected int DESCRIPTION_COLOR;
+    protected int CHART_TEXT_COLOR;
+    protected int LEGEND_TEXT_COLOR;
+
+    protected int PAI_TOTAL_COLOR;
+    protected int PAI_DAY_COLOR;
+
+    @Override
+    protected void init() {
+        BACKGROUND_COLOR = GBApplication.getBackgroundColor(requireContext());
+        LEGEND_TEXT_COLOR = DESCRIPTION_COLOR = GBApplication.getTextColor(requireContext());
+        CHART_TEXT_COLOR = ContextCompat.getColor(requireContext(), R.color.secondarytext);
+
+        PAI_TOTAL_COLOR = ContextCompat.getColor(requireContext(), R.color.chart_pai_weekly);
+        PAI_DAY_COLOR = ContextCompat.getColor(requireContext(), R.color.chart_pai_today);
+    }
+
+    @Override
+    public View onCreateView(final LayoutInflater inflater,
+                             final ViewGroup container,
+                             final Bundle savedInstanceState) {
+        mLocale = getResources().getConfiguration().locale;
+
+        final View rootView = inflater.inflate(R.layout.fragment_pai_chart, container, false);
+
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+            rootView.setOnScrollChangeListener((v, scrollX, scrollY, oldScrollX, oldScrollY) -> {
+                getChartsHost().enableSwipeRefresh(scrollY == 0);
+            });
+        }
+
+        mGoalMinutesGauge = rootView.findViewById(R.id.goal_minutes_gauge);
+        mWeekChart = rootView.findViewById(R.id.pai_chart_week);
+        mDateView = rootView.findViewById(R.id.pai_date_view);
+        mLineToday = rootView.findViewById(R.id.pai_line_today);
+        mLineTotal = rootView.findViewById(R.id.pai_line_total);
+        mLineLowInc = rootView.findViewById(R.id.pai_line_low_inc);
+        mLineLowTime = rootView.findViewById(R.id.pai_line_low_time);
+        mLineModerateInc = rootView.findViewById(R.id.pai_line_moderate_inc);
+        mLineModerateTime = rootView.findViewById(R.id.pai_line_moderate_time);
+        mLineHighInc = rootView.findViewById(R.id.pai_line_high_inc);
+        mLineHighTime = rootView.findViewById(R.id.pai_line_high_time);
+        mTileLow = rootView.findViewById(R.id.pai_tile_low);
+        mTileModerate = rootView.findViewById(R.id.pai_tile_moderate);
+        mTileHigh = rootView.findViewById(R.id.pai_tile_high);
+
+        if (!getChartsHost().getDevice().getDeviceCoordinator().supportsPaiTime()) {
+            mLineLowTime.setVisibility(View.GONE);
+            mLineModerateTime.setVisibility(View.GONE);
+            mLineHighTime.setVisibility(View.GONE);
+        }
+
+        if (!getChartsHost().getDevice().getDeviceCoordinator().supportsPaiLow()) {
+            mTileLow.setVisibility(View.GONE);
+        }
+
+        setupWeekChart();
+
+        // refresh immediately instead of use refreshIfVisible(), for perceived performance
+        refresh();
+
+        return rootView;
+    }
+
+    private void setupWeekChart() {
+        mWeekChart.setBackgroundColor(BACKGROUND_COLOR);
+        mWeekChart.getDescription().setTextColor(DESCRIPTION_COLOR);
+        mWeekChart.getDescription().setText("");
+        mWeekChart.setFitBars(true);
+
+        configureBarLineChartDefaults(mWeekChart);
+
+        final XAxis x = mWeekChart.getXAxis();
+        x.setDrawLabels(true);
+        x.setDrawGridLines(false);
+        x.setEnabled(true);
+        x.setTextColor(CHART_TEXT_COLOR);
+        x.setDrawLimitLinesBehindData(true);
+        x.setPosition(XAxis.XAxisPosition.BOTTOM);
+
+        final YAxis y = mWeekChart.getAxisLeft();
+        y.setDrawGridLines(true);
+        y.setDrawTopYLabelEntry(true);
+        y.setTextColor(CHART_TEXT_COLOR);
+        y.setDrawZeroLine(true);
+        y.setSpaceBottom(0);
+        y.setAxisMinimum(0);
+        y.setAxisMaximum(getPaiTarget());
+        y.setValueFormatter(getRoundFormatter());
+        y.setEnabled(true);
+
+        final YAxis yAxisRight = mWeekChart.getAxisRight();
+        yAxisRight.setDrawGridLines(false);
+        yAxisRight.setEnabled(false);
+        yAxisRight.setDrawLabels(false);
+        yAxisRight.setDrawTopYLabelEntry(false);
+        yAxisRight.setTextColor(CHART_TEXT_COLOR);
+    }
+
+    private int getRangeDays() {
+        if (GBApplication.getPrefs().getBoolean("charts_range", true)) {
+            return 30;
+        } else {
+            return 7;
+        }
+    }
+
+    private int getPaiTarget() {
+        return getChartsHost().getDevice().getDeviceCoordinator().getPaiTarget();
+    }
+
+    @Override
+    public String getTitle() {
+        if (GBApplication.getPrefs().getBoolean("charts_range", true)) {
+            return getString(R.string.pai_chart_per_month);
+        } else {
+            return getString(R.string.pai_chart_per_week);
+        }
+    }
+
+    @Override
+    protected PaiChartsData refreshInBackground(final ChartsHost chartsHost, final DBHandler db, final GBDevice device) {
+        final Calendar day = Calendar.getInstance();
+        day.setTime(chartsHost.getEndDate());
+        //NB: we could have omitted the day, but this way we can move things to the past easily
+        final DayData dayData = refreshDayData(db, day, device);
+        final WeekChartsData<BarData> weekBeforeData = refreshWeekBeforeData(db, mWeekChart, day, device);
+
+        return new PaiChartsData(dayData, weekBeforeData);
+    }
+
+    @Override
+    protected void updateChartsnUIThread(final PaiChartsData pcd) {
+        setupLegend(mWeekChart);
+        int[] colors = new int[] {
+                ContextCompat.getColor(GBApplication.getContext(), R.color.chart_pai_weekly),
+                ContextCompat.getColor(GBApplication.getContext(), R.color.chart_pai_today)
+        };
+        final int width = (int) TypedValue.applyDimension(
+                TypedValue.COMPLEX_UNIT_DIP,
+                300,
+                GBApplication.getContext().getResources().getDisplayMetrics()
+        );
+        mGoalMinutesGauge.setImageBitmap(GaugeDrawer.drawCircleGaugeSegmented(
+                width,
+                width / 15,
+                colors,
+                pcd.getDayData().getGaugeSegments(),
+                false,
+                String.valueOf(pcd.getDayData().getTotal()),
+                String.valueOf(getPaiTarget()),
+                getContext()
+        ));
+
+        // set custom renderer for 30days bar charts
+        if (GBApplication.getPrefs().getBoolean("charts_range", true)) {
+            mWeekChart.setRenderer(new AngledLabelsChartRenderer(mWeekChart, mWeekChart.getAnimator(), mWeekChart.getViewPortHandler()));
+        }
+
+        mWeekChart.setData(null); // workaround for https://github.com/PhilJay/MPAndroidChart/issues/2317
+        mWeekChart.setData(pcd.getWeekBeforeData().getData());
+        mWeekChart.getXAxis().setValueFormatter(pcd.getWeekBeforeData().getXValueFormatter());
+
+        mDateView.setText(DateTimeUtils.formatDate(pcd.getDayData().day.getTime()));
+        mLineToday.setText(requireContext().getString(R.string.pai_plus_num, pcd.getDayData().today));
+        mLineTotal.setText(String.valueOf(pcd.getDayData().total));
+        mLineLowInc.setText(String.valueOf(pcd.getDayData().paiLow));
+        mLineLowTime.setText(requireContext().getString(R.string.num_min, pcd.getDayData().minutesLow));
+        mLineModerateInc.setText(String.valueOf(pcd.getDayData().paiModerate));
+        mLineModerateTime.setText(requireContext().getString(R.string.num_min, pcd.getDayData().minutesModerate));
+        mLineHighInc.setText(String.valueOf(pcd.getDayData().paiHigh));
+        mLineHighTime.setText(requireContext().getString(R.string.num_min, pcd.getDayData().minutesHigh));
+    }
+
+    @Override
+    protected void renderCharts() {
+        mWeekChart.invalidate();
+    }
+
+    protected String getWeeksChartsLabel(final Calendar day) {
+        if (GBApplication.getPrefs().getBoolean("charts_range", true)) {
+            // month, show day date
+            return String.valueOf(day.get(Calendar.DAY_OF_MONTH));
+        } else {
+            // week, show short day name
+            return day.getDisplayName(Calendar.DAY_OF_WEEK, Calendar.SHORT, mLocale);
+        }
+    }
+
+    protected WeekChartsData<BarData> refreshWeekBeforeData(final DBHandler db,
+                                                            final BarChart barChart,
+                                                            Calendar day,
+                                                            final GBDevice device) {
+        day = (Calendar) day.clone(); // do not modify the caller's argument
+        day.add(Calendar.DATE, -TOTAL_DAYS + 1);
+
+        List<BarEntry> entries = new ArrayList<>();
+        final ArrayList<String> labels = new ArrayList<>();
+
+        int maxPai = -1;
+
+        for (int counter = 0; counter < TOTAL_DAYS; counter++) {
+            final Optional<? extends PaiSample> sampleOpt = getSamplePaiForDay(db, device, day);
+
+            if (sampleOpt.isPresent()) {
+                final PaiSample sample = sampleOpt.get();
+                final int paiToday = Math.round(sample.getPaiToday());
+                final int paiTotal = Math.round(sample.getPaiTotal());
+
+                if (paiTotal > maxPai) {
+                    maxPai = paiTotal;
+                }
+
+                final float[] paiBar = new float[]{
+                        paiTotal - paiToday,
+                        paiToday
+                };
+
+                entries.add(new BarEntry(counter, paiBar));
+            } else {
+                entries.add(new BarEntry(counter, new float[]{0.0f, 0.0f}));
+            }
+            labels.add(getWeeksChartsLabel(day));
+            day.add(Calendar.DATE, 1);
+        }
+
+        BarDataSet set = new BarDataSet(entries, "");
+        set.setColors(PAI_TOTAL_COLOR, PAI_DAY_COLOR);
+        set.setValueFormatter(getRoundFormatter());
+
+        BarData barData = new BarData(set);
+        barData.setValueTextColor(Color.GRAY); //prevent tearing other graph elements with the black text. Another approach would be to hide the values cmpletely with data.setDrawValues(false);
+        barData.setValueTextSize(10f);
+
+        barChart.getAxisLeft().setAxisMaximum(Math.max(maxPai, getPaiTarget()));
+
+        return new WeekChartsData(barData, new PreformattedXIndexLabelFormatter(labels));
+    }
+
+    protected DayData refreshDayData(final DBHandler db,
+                                     final Calendar day,
+                                     final GBDevice device) {
+        final Optional<? extends PaiSample> sampleOpt = getSamplePaiForDay(db, device, day);
+
+        final int today;
+        final int total;
+        final int paiLow;
+        final int paiModerate;
+        final int paiHigh;
+        final int minutesLow;
+        final int minutesModerate;
+        final int minutesHigh;
+
+        if (sampleOpt.isPresent()) {
+            final PaiSample sample = sampleOpt.get();
+            today = Math.round(sample.getPaiToday());
+            total = Math.round(sample.getPaiTotal());
+            paiLow = Math.round(sample.getPaiLow());
+            paiModerate = Math.round(sample.getPaiModerate());
+            paiHigh = Math.round(sample.getPaiHigh());
+            minutesLow = sample.getTimeLow();
+            minutesModerate = sample.getTimeModerate();
+            minutesHigh = sample.getTimeHigh();
+        } else {
+            today = 0;
+            total = 0;
+            paiLow = 0;
+            paiModerate = 0;
+            paiHigh = 0;
+            minutesLow = 0;
+            minutesModerate = 0;
+            minutesHigh = 0;
+        }
+
+        final int maxPai = Math.max(getPaiTarget(), total);
+
+        float[] segments = new float[] {
+                (float) (total - today) / maxPai,
+                (float) today / maxPai
+        };
+
+        return new DayData(day, segments, today, total, paiLow, paiModerate, paiHigh, minutesLow, minutesModerate, minutesHigh);
+    }
+
+    protected ValueFormatter getRoundFormatter() {
+        return new ValueFormatter() {
+            @Override
+            public String getFormattedValue(final float value) {
+                return String.valueOf(Math.round(value));
+            }
+        };
+    }
+
+    @Override
+    protected void setupLegend(Chart<?> chart) {
+        List<LegendEntry> legendEntries = new ArrayList<>(2);
+
+        LegendEntry lightSleepEntry = new LegendEntry();
+        lightSleepEntry.label = requireContext().getString(R.string.pai_total);
+        lightSleepEntry.formColor = PAI_TOTAL_COLOR;
+        legendEntries.add(lightSleepEntry);
+
+        LegendEntry deepSleepEntry = new LegendEntry();
+        deepSleepEntry.label = requireContext().getString(R.string.pai_day);
+        deepSleepEntry.formColor = PAI_DAY_COLOR;
+        legendEntries.add(deepSleepEntry);
+
+        chart.getLegend().setCustom(legendEntries);
+        chart.getLegend().setTextColor(LEGEND_TEXT_COLOR);
+        chart.getLegend().setWordWrapEnabled(true);
+        chart.getLegend().setHorizontalAlignment(Legend.LegendHorizontalAlignment.CENTER);
+    }
+
+    private Optional<? extends PaiSample> getSamplePaiForDay(final DBHandler db, final GBDevice device, final Calendar day) {
+        final Date dayStart = DateTimeUtils.dayStart(day.getTime());
+        final Date dayEnd = DateTimeUtils.dayEnd(day.getTime());
+        final DeviceCoordinator coordinator = device.getDeviceCoordinator();
+        final TimeSampleProvider<? extends PaiSample> sampleProvider = coordinator.getPaiSampleProvider(device, db.getDaoSession());
+        final List<? extends PaiSample> daySamples = sampleProvider.getAllSamples(dayStart.getTime(), dayEnd.getTime());
+        return Optional.ofNullable(daySamples.isEmpty() ? null : daySamples.get(daySamples.size() - 1));
+    }
+
+    protected static class DayData {
+        private final Calendar day;
+        private final float[] gaugeSegments;
+        private final int today;
+        private final int total;
+        private final int paiLow;
+        private final int paiModerate;
+        private final int paiHigh;
+        private final int minutesLow;
+        private final int minutesModerate;
+        private final int minutesHigh;
+
+        DayData(final Calendar day,
+                float[] gaugeSegments,
+                final int today,
+                final int total,
+                final int paiLow,
+                final int paiModerate,
+                final int paiHigh,
+                final int minutesLow,
+                final int minutesModerate,
+                final int minutesHigh) {
+            this.day = day;
+            this.gaugeSegments = gaugeSegments;
+            this.today = today;
+            this.total = total;
+            this.paiLow = paiLow;
+            this.paiModerate = paiModerate;
+            this.paiHigh = paiHigh;
+            this.minutesLow = minutesLow;
+            this.minutesModerate = minutesModerate;
+            this.minutesHigh = minutesHigh;
+        }
+
+        public int getTotal() {
+            return total;
+        }
+
+        public float[] getGaugeSegments() {
+            return gaugeSegments;
+        }
+    }
+
+    protected static class WeekChartsData<T extends ChartData<?>> extends DefaultChartsData<T> {
+        public WeekChartsData(final T data,
+                              final PreformattedXIndexLabelFormatter xIndexLabelFormatter) {
+            super(data, xIndexLabelFormatter);
+        }
+    }
+
+    protected static class PaiChartsData extends ChartsData {
+        private final WeekChartsData<BarData> weekBeforeData;
+        private final DayData dayData;
+
+        PaiChartsData(final DayData dayData, final WeekChartsData<BarData> weekBeforeData) {
+            this.dayData = dayData;
+            this.weekBeforeData = weekBeforeData;
+        }
+
+        public DayData getDayData() {
+            return dayData;
+        }
+
+        public WeekChartsData<BarData> getWeekBeforeData() {
+            return weekBeforeData;
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/PreformattedXIndexLabelFormatter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/PreformattedXIndexLabelFormatter.java
new file mode 100644
index 0000000..2cde827
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/PreformattedXIndexLabelFormatter.java
@@ -0,0 +1,38 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities.charts;
+
+import com.github.mikephil.charting.formatter.ValueFormatter;
+
+import java.util.ArrayList;
+
+class PreformattedXIndexLabelFormatter extends ValueFormatter {
+    private final ArrayList<String> xLabels;
+
+    public PreformattedXIndexLabelFormatter(ArrayList<String> xLabels) {
+        this.xLabels = xLabels;
+    }
+
+    @Override
+    public String getFormattedValue(float value) {
+        int index = (int) value;
+        if (xLabels == null || index >= xLabels.size()) {
+            return String.valueOf(value);
+        }
+        return xLabels.get(index);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/RespiratoryRateCollectionFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/RespiratoryRateCollectionFragment.java
new file mode 100644
index 0000000..1097a3c
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/RespiratoryRateCollectionFragment.java
@@ -0,0 +1,29 @@
+package nodomain.freeyourgadget.gadgetbridge.activities.charts;
+
+import android.os.Bundle;
+
+import androidx.fragment.app.FragmentManager;
+
+import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBFragment;
+import nodomain.freeyourgadget.gadgetbridge.adapter.NestedFragmentAdapter;
+import nodomain.freeyourgadget.gadgetbridge.adapter.RespiratoryRateFragmentAdapter;
+
+public class RespiratoryRateCollectionFragment extends AbstractCollectionFragment {
+    public RespiratoryRateCollectionFragment() {
+
+    }
+
+    public static RespiratoryRateCollectionFragment newInstance(final boolean allowSwipe) {
+        final RespiratoryRateCollectionFragment fragment = new RespiratoryRateCollectionFragment();
+        final Bundle args = new Bundle();
+        args.putBoolean(ARG_ALLOW_SWIPE, allowSwipe);
+        fragment.setArguments(args);
+        return fragment;
+    }
+
+    @Override
+    public NestedFragmentAdapter getNestedFragmentAdapter(AbstractGBFragment fragment, FragmentManager childFragmentManager) {
+        return new RespiratoryRateFragmentAdapter(this, getChildFragmentManager());
+    }
+}
+
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/RespiratoryRateDailyFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/RespiratoryRateDailyFragment.java
new file mode 100644
index 0000000..db7600a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/RespiratoryRateDailyFragment.java
@@ -0,0 +1,208 @@
+package nodomain.freeyourgadget.gadgetbridge.activities.charts;
+
+import android.os.Build;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import com.github.mikephil.charting.charts.Chart;
+import com.github.mikephil.charting.charts.LineChart;
+import com.github.mikephil.charting.components.LegendEntry;
+import com.github.mikephil.charting.components.XAxis;
+import com.github.mikephil.charting.components.YAxis;
+import com.github.mikephil.charting.data.Entry;
+import com.github.mikephil.charting.data.LineData;
+import com.github.mikephil.charting.data.LineDataSet;
+import com.github.mikephil.charting.interfaces.datasets.ILineDataSet;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.entities.AbstractRespiratoryRateSample;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class RespiratoryRateDailyFragment extends RespiratoryRateFragment<RespiratoryRateFragment.RespiratoryRateDay> {
+    protected static final Logger LOG = LoggerFactory.getLogger(BodyEnergyFragment.class);
+
+    private TextView mDateView;
+    private TextView sleepAvg;
+    private TextView awakeAvg;
+    private TextView lowest;
+    private TextView highest;
+    private LineChart respiratoryRateChart;
+
+    @Override
+    public void onResume() {
+        super.onResume();
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+        View rootView = inflater.inflate(R.layout.fragment_respiratory_rate, container, false);
+
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+            rootView.setOnScrollChangeListener((v, scrollX, scrollY, oldScrollX, oldScrollY) -> {
+                getChartsHost().enableSwipeRefresh(scrollY == 0);
+            });
+        }
+
+        mDateView = rootView.findViewById(R.id.rr_date_view);
+        awakeAvg = rootView.findViewById(R.id.awake_avg);
+        sleepAvg = rootView.findViewById(R.id.sleep_avg);
+        lowest = rootView.findViewById(R.id.day_lowest);
+        highest = rootView.findViewById(R.id.day_highest);
+        respiratoryRateChart = rootView.findViewById(R.id.respiratory_rate_line_chart);
+        setupRespiratoryRateChart();
+        refresh();
+
+        return rootView;
+    }
+
+        @Override
+    public String getTitle() {
+        return getString(R.string.respiratoryrate);
+    }
+
+    @Override
+    protected RespiratoryRateDay refreshInBackground(ChartsHost chartsHost, DBHandler db, GBDevice device) {
+        Calendar day = Calendar.getInstance();
+        day.setTime(chartsHost.getEndDate());
+        String formattedDate = new SimpleDateFormat("E, MMM dd").format(chartsHost.getEndDate());
+        mDateView.setText(formattedDate);
+        List<RespiratoryRateDay> stepsDayList = getMyRespiratoryRateDaysData(db, day, device);
+        final RespiratoryRateDay RespiratoryRateDay;
+        if (stepsDayList.isEmpty()) {
+            LOG.error("Failed to get RespiratoryRateDay for {}", day);
+            List<? extends AbstractRespiratoryRateSample> s = new ArrayList<>();
+            RespiratoryRateDay = new RespiratoryRateDay(day, new ArrayList<>(), new ArrayList<>(), true);
+        } else {
+            RespiratoryRateDay = stepsDayList.get(0);
+        }
+        return RespiratoryRateDay;
+    }
+
+    @Override
+    protected void updateChartsnUIThread(RespiratoryRateFragment.RespiratoryRateDay respiratoryRateDay) {
+        final String emptyValue = requireContext().getString(R.string.stats_empty_value);
+        awakeAvg.setText(respiratoryRateDay.awakeRateAvg > 0 ? String.valueOf(respiratoryRateDay.awakeRateAvg) : emptyValue);
+        sleepAvg.setText(respiratoryRateDay.sleepRateAvg > 0 ? String.valueOf(respiratoryRateDay.sleepRateAvg) : emptyValue);
+        lowest.setText(respiratoryRateDay.rateLowest > 0 ? String.valueOf(respiratoryRateDay.rateLowest) : emptyValue);
+        highest.setText(respiratoryRateDay.rateHighest > 0 ? String.valueOf(respiratoryRateDay.rateHighest) : emptyValue);
+
+        // Chart
+        final List<LegendEntry> legendEntries = new ArrayList<>(1);
+        final LegendEntry respiratoryRateEntry = new LegendEntry();
+        respiratoryRateEntry.label = getString(R.string.respiratoryrate);
+        respiratoryRateEntry.formColor = getResources().getColor(R.color.respiratory_rate_color);
+        legendEntries.add(respiratoryRateEntry);
+        respiratoryRateChart.getLegend().setTextColor(TEXT_COLOR);
+        respiratoryRateChart.getLegend().setCustom(legendEntries);
+
+        final List<ILineDataSet> lineDataSets = new ArrayList<>();
+        List<Entry> lineEntries = new ArrayList<>();
+        final TimestampTranslation tsTranslation = new TimestampTranslation();
+        int lastTsShorten = 0;
+        for (final AbstractRespiratoryRateSample sample : respiratoryRateDay.respiratoryRateSamples) {
+            int ts = (int) (sample.getTimestamp() / 1000L);
+            int tsShorten = tsTranslation.shorten(ts);
+            if (lastTsShorten == 0 || (tsShorten - lastTsShorten) <= 300) {
+                lineEntries.add(new Entry(tsShorten, (int) sample.getRespiratoryRate()));
+            } else {
+                if (!lineEntries.isEmpty()) {
+                    List<Entry> clone = new ArrayList<>(lineEntries.size());
+                    clone.addAll(lineEntries);
+                    lineDataSets.add(createDataSet(clone));
+                    lineEntries.clear();
+                }
+            }
+            lastTsShorten = tsShorten;
+            lineEntries.add(new Entry(tsShorten, (int) sample.getRespiratoryRate()));
+        }
+
+        if (!lineEntries.isEmpty()) {
+            lineDataSets.add(createDataSet(lineEntries));
+        }
+
+        respiratoryRateChart.getXAxis().setValueFormatter(new SampleXLabelFormatter(tsTranslation, "HH:mm"));
+        if (respiratoryRateDay.rateLowest > 0 && respiratoryRateDay.rateHighest > 0) {
+            final YAxis yAxisLeft = respiratoryRateChart.getAxisLeft();
+            yAxisLeft.setAxisMaximum(Math.max(respiratoryRateDay.rateHighest + 3, 20));
+        }
+
+        final LineDataSet lineDataSet = new LineDataSet(lineEntries, getString(R.string.respiratoryrate));
+        lineDataSet.setColor(getResources().getColor(R.color.respiratory_rate_color));
+        lineDataSet.setDrawCircles(false);
+        lineDataSet.setLineWidth(2f);
+        lineDataSet.setFillAlpha(255);
+        lineDataSet.setDrawCircles(false);
+        lineDataSet.setCircleColor(getResources().getColor(R.color.respiratory_rate_color));
+        lineDataSet.setAxisDependency(YAxis.AxisDependency.LEFT);
+        lineDataSet.setDrawValues(false);
+        lineDataSet.setMode(LineDataSet.Mode.CUBIC_BEZIER);
+
+        lineDataSets.add(lineDataSet);
+        final LineData lineData = new LineData(lineDataSets);
+        respiratoryRateChart.setData(lineData);
+    }
+
+    protected LineDataSet createDataSet(final List<Entry> values) {
+        final LineDataSet lineDataSet = new LineDataSet(values, getString(R.string.respiratoryrate));
+        lineDataSet.setColor(getResources().getColor(R.color.respiratory_rate_color));
+        lineDataSet.setDrawCircles(false);
+        lineDataSet.setLineWidth(2f);
+        lineDataSet.setFillAlpha(255);
+        lineDataSet.setDrawCircles(false);
+        lineDataSet.setCircleColor(getResources().getColor(R.color.respiratory_rate_color));
+        lineDataSet.setAxisDependency(YAxis.AxisDependency.LEFT);
+        lineDataSet.setDrawValues(false);
+        lineDataSet.setMode(LineDataSet.Mode.CUBIC_BEZIER);
+
+        return lineDataSet;
+    }
+
+    @Override
+    protected void renderCharts() {
+        respiratoryRateChart.invalidate();
+    }
+
+    protected void setupLegend(Chart<?> chart) {}
+
+    private void setupRespiratoryRateChart() {
+        respiratoryRateChart.getDescription().setEnabled(false);
+        respiratoryRateChart.setDoubleTapToZoomEnabled(false);
+
+        final XAxis xAxisBottom = respiratoryRateChart.getXAxis();
+        xAxisBottom.setPosition(XAxis.XAxisPosition.BOTTOM);
+        xAxisBottom.setDrawLabels(true);
+        xAxisBottom.setDrawGridLines(false);
+        xAxisBottom.setEnabled(true);
+        xAxisBottom.setDrawLimitLinesBehindData(true);
+        xAxisBottom.setTextColor(CHART_TEXT_COLOR);
+        xAxisBottom.setAxisMinimum(0f);
+        xAxisBottom.setAxisMaximum(86400f);
+        xAxisBottom.setLabelCount(7, true);
+
+        final YAxis yAxisLeft = respiratoryRateChart.getAxisLeft();
+        yAxisLeft.setDrawGridLines(true);
+        yAxisLeft.setAxisMinimum(0);
+        yAxisLeft.setAxisMaximum(20);
+        yAxisLeft.setDrawTopYLabelEntry(true);
+        yAxisLeft.setEnabled(true);
+        yAxisLeft.setTextColor(CHART_TEXT_COLOR);
+
+        final YAxis yAxisRight = respiratoryRateChart.getAxisRight();
+        yAxisRight.setEnabled(true);
+        yAxisRight.setDrawLabels(false);
+        yAxisRight.setDrawGridLines(false);
+        yAxisRight.setDrawAxisLine(true);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/RespiratoryRateFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/RespiratoryRateFragment.java
new file mode 100644
index 0000000..fb7fc55
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/RespiratoryRateFragment.java
@@ -0,0 +1,155 @@
+package nodomain.freeyourgadget.gadgetbridge.activities.charts;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.TimeSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.AbstractRespiratoryRateSample;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.model.RespiratoryRateSample;
+
+abstract class RespiratoryRateFragment<T extends ChartsData> extends AbstractChartFragment<T> {
+    protected static final Logger LOG = LoggerFactory.getLogger(RespiratoryRateFragment.class);
+
+    protected int CHART_TEXT_COLOR;
+    protected int TEXT_COLOR;
+    protected int LEGEND_TEXT_COLOR;
+
+    protected int BACKGROUND_COLOR;
+    protected int DESCRIPTION_COLOR;
+    protected int TOTAL_DAYS = 1;
+
+    @Override
+    public String getTitle() {
+        return getString(R.string.respiratoryrate);
+    }
+
+    @Override
+    protected void init() {
+        TEXT_COLOR = GBApplication.getTextColor(requireContext());
+        CHART_TEXT_COLOR = GBApplication.getSecondaryTextColor(requireContext());
+        BACKGROUND_COLOR = GBApplication.getBackgroundColor(getContext());
+        LEGEND_TEXT_COLOR = DESCRIPTION_COLOR = GBApplication.getTextColor(getContext());
+        CHART_TEXT_COLOR = GBApplication.getSecondaryTextColor(getContext());
+    }
+
+    protected List<RespiratoryRateFragment.RespiratoryRateDay> getMyRespiratoryRateDaysData(DBHandler db, Calendar day, GBDevice device) {
+        day = (Calendar) day.clone(); // do not modify the caller's argument
+        day.add(Calendar.DATE, -TOTAL_DAYS + 1);
+
+        final boolean supportsDayRespiratoryRate = device.getDeviceCoordinator().supportsDayRespiratoryRate();
+
+        List<RespiratoryRateDay> daysData = new ArrayList<>();;
+        for (int counter = 0; counter < TOTAL_DAYS; counter++) {
+            int startTs;
+            int endTs;
+            day = (Calendar) day.clone(); // do not modify the caller's argument
+            day.set(Calendar.HOUR_OF_DAY, 0);
+            day.set(Calendar.MINUTE, 0);
+            day.set(Calendar.SECOND, 0);
+            day.add(Calendar.HOUR, 0);
+            startTs = (int) (day.getTimeInMillis() / 1000);
+            endTs = startTs + 24 * 60 * 60 - 1;
+            List<? extends ActivitySample> activitySamples = getAllActivitySamples(db, device, startTs, endTs);
+            SleepAnalysis sleepAnalysis = new SleepAnalysis();
+            List<SleepAnalysis.SleepSession> sleepSessions = sleepAnalysis.calculateSleepSessions(activitySamples);
+            List<? extends AbstractRespiratoryRateSample> samples = getRespiratoryRateSamples(db, device, startTs, endTs);
+            Calendar d = (Calendar) day.clone();
+            daysData.add(new RespiratoryRateDay(d, samples, sleepSessions, supportsDayRespiratoryRate));
+            day.add(Calendar.DATE, 1);
+        }
+        return daysData;
+    }
+
+    protected List<? extends AbstractRespiratoryRateSample> getSamplesOfDay(DBHandler db, GBDevice device, int startTs, int endTs) {
+        return getRespiratoryRateSamples(db, device, startTs, endTs);
+    }
+
+    protected List<AbstractRespiratoryRateSample> getRespiratoryRateSamples(DBHandler db, GBDevice device, int tsFrom, int tsTo) {
+        TimeSampleProvider<? extends RespiratoryRateSample> provider = device.getDeviceCoordinator().getRespiratoryRateSampleProvider(device, db.getDaoSession());
+        return (List<AbstractRespiratoryRateSample>) provider.getAllSamples(tsFrom * 1000L, tsTo * 1000L);
+    }
+
+    protected List<? extends ActivitySample> getAllActivitySamples(DBHandler db, GBDevice device, int startTs, int endTs) {
+        SampleProvider<? extends ActivitySample> provider = device.getDeviceCoordinator().getSampleProvider(device, db.getDaoSession());
+        return provider.getAllActivitySamples(startTs, endTs);
+    }
+
+    protected static class RespiratoryRateDay extends ChartsData {
+        public int awakeRateAvg;
+        public int sleepRateAvg;
+        public int rateLowest;
+        public int rateHighest;
+        public Calendar day;
+        List<? extends AbstractRespiratoryRateSample> respiratoryRateSamples;
+        List<SleepAnalysis.SleepSession> sleepSessions;
+
+        protected RespiratoryRateDay(Calendar day,
+                                     List<? extends AbstractRespiratoryRateSample> respiratoryRateSamples,
+                                     List<SleepAnalysis.SleepSession> sleepSessions,
+                                     boolean supportsDayRespiratoryRate) {
+            this.day = day;
+            this.respiratoryRateSamples = respiratoryRateSamples;
+            this.sleepSessions = sleepSessions;
+            float awakeRateTotal = 0;
+            int awakeCounter = 0;
+            float sleepRateTotal = 0;
+            int sleepCounter = 0;
+            float lowest = 0;
+            float highest = 0;
+            if (!this.respiratoryRateSamples.isEmpty()) {
+                for (AbstractRespiratoryRateSample sample : this.respiratoryRateSamples) {
+                    if (isSleepSample(sample, supportsDayRespiratoryRate)) {
+                        sleepRateTotal += sample.getRespiratoryRate();
+                        sleepCounter++;
+                    } else {
+                        awakeRateTotal += sample.getRespiratoryRate();
+                        awakeCounter++;
+                    }
+                    if (sample.getRespiratoryRate() > highest) {
+                        highest = sample.getRespiratoryRate();
+                    }
+                    if (sample.getRespiratoryRate() < lowest || lowest == 0) {
+                        lowest = sample.getRespiratoryRate();
+                    }
+                }
+            }
+            if (awakeRateTotal > 0) {
+                this.awakeRateAvg = Math.round(awakeRateTotal / awakeCounter);
+            }
+            if (sleepRateTotal > 0) {
+                this.sleepRateAvg = Math.round(sleepRateTotal / sleepCounter);
+            }
+            this.rateLowest = (int) lowest;
+            this.rateHighest = (int) highest;
+        }
+
+        private boolean isSleepSample(AbstractRespiratoryRateSample sample, boolean supportsDayRespiratoryRate) {
+            if (!supportsDayRespiratoryRate) {
+                return true;
+            }
+
+            if (this.sleepSessions.isEmpty()) {
+                return true;
+            }
+
+            for (SleepAnalysis.SleepSession session : this.sleepSessions) {
+                if (sample.getTimestamp() >= session.getSleepStart().getTime() && sample.getTimestamp() <= session.getSleepEnd().getTime()) {
+                    return true;
+                }
+            }
+            return false;
+        }
+    }
+
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/RespiratoryRatePeriodFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/RespiratoryRatePeriodFragment.java
new file mode 100644
index 0000000..80b9cf3
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/RespiratoryRatePeriodFragment.java
@@ -0,0 +1,239 @@
+package nodomain.freeyourgadget.gadgetbridge.activities.charts;
+
+import android.graphics.Color;
+import android.os.Build;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import com.github.mikephil.charting.charts.Chart;
+import com.github.mikephil.charting.charts.LineChart;
+import com.github.mikephil.charting.components.LegendEntry;
+import com.github.mikephil.charting.components.XAxis;
+import com.github.mikephil.charting.components.YAxis;
+import com.github.mikephil.charting.data.Entry;
+import com.github.mikephil.charting.data.LineData;
+import com.github.mikephil.charting.data.LineDataSet;
+import com.github.mikephil.charting.formatter.ValueFormatter;
+import com.github.mikephil.charting.interfaces.datasets.ILineDataSet;
+
+import org.apache.commons.lang3.time.DateUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class RespiratoryRatePeriodFragment extends RespiratoryRateFragment<RespiratoryRatePeriodFragment.RespiratoryRateData> {
+    protected static final Logger LOG = LoggerFactory.getLogger(BodyEnergyFragment.class);
+
+    private TextView mDateView;
+    private TextView sleepAvg;
+    private TextView awakeAvg;
+    private LineChart respiratoryRateChart;
+
+    public static RespiratoryRatePeriodFragment newInstance (int totalDays) {
+        RespiratoryRatePeriodFragment fragmentFirst = new RespiratoryRatePeriodFragment();
+        Bundle args = new Bundle();
+        args.putInt("totalDays", totalDays);
+        fragmentFirst.setArguments(args);
+        return fragmentFirst;
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        TOTAL_DAYS = getArguments() != null ? getArguments().getInt("totalDays") : 0;
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+        View rootView = inflater.inflate(R.layout.fragment_respiratory_rate_period, container, false);
+
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+            rootView.setOnScrollChangeListener((v, scrollX, scrollY, oldScrollX, oldScrollY) -> {
+                getChartsHost().enableSwipeRefresh(scrollY == 0);
+            });
+        }
+
+        mDateView = rootView.findViewById(R.id.rr_date_view);
+        sleepAvg = rootView.findViewById(R.id.sleep_avg);
+        awakeAvg = rootView.findViewById(R.id.awake_avg);
+        respiratoryRateChart = rootView.findViewById(R.id.respiratory_rate_line_chart);
+        setupRespiratoryRateChart();
+        refresh();
+
+        return rootView;
+    }
+
+    protected void setupRespiratoryRateChart() {
+        respiratoryRateChart.getDescription().setEnabled(false);
+        if (TOTAL_DAYS <= 7) {
+            respiratoryRateChart.setTouchEnabled(false);
+            respiratoryRateChart.setPinchZoom(false);
+        }
+
+        respiratoryRateChart.getDescription().setEnabled(false);
+        respiratoryRateChart.setDoubleTapToZoomEnabled(false);
+
+        final XAxis xAxisBottom = respiratoryRateChart.getXAxis();
+        xAxisBottom.setPosition(XAxis.XAxisPosition.BOTTOM);
+        xAxisBottom.setDrawLabels(true);
+        xAxisBottom.setDrawGridLines(false);
+        xAxisBottom.setEnabled(true);
+        xAxisBottom.setDrawLimitLinesBehindData(true);
+        xAxisBottom.setTextColor(CHART_TEXT_COLOR);
+        xAxisBottom.setLabelCount(7, true);
+        xAxisBottom.setAxisMinimum(0);
+        xAxisBottom.setAxisMaximum(TOTAL_DAYS - 1);
+
+        final YAxis yAxisLeft = respiratoryRateChart.getAxisLeft();
+        yAxisLeft.setDrawGridLines(true);
+        yAxisLeft.setAxisMinimum(0);
+        yAxisLeft.setAxisMaximum(20);
+        yAxisLeft.setDrawTopYLabelEntry(true);
+        yAxisLeft.setEnabled(true);
+        yAxisLeft.setTextColor(CHART_TEXT_COLOR);
+
+        final YAxis yAxisRight = respiratoryRateChart.getAxisRight();
+        yAxisRight.setEnabled(true);
+        yAxisRight.setDrawLabels(false);
+        yAxisRight.setDrawGridLines(false);
+        yAxisRight.setDrawAxisLine(true);
+    }
+
+        @Override
+    public String getTitle() {
+        return getString(R.string.respiratoryrate);
+    }
+
+    @Override
+    protected RespiratoryRateData refreshInBackground(ChartsHost chartsHost, DBHandler db, GBDevice device) {
+        Calendar day = Calendar.getInstance();
+        Date to = new Date((long) this.getTSEnd() * 1000);
+        Date from = DateUtils.addDays(to,-(TOTAL_DAYS - 1));
+        String toFormattedDate = new SimpleDateFormat("E, MMM dd").format(to);
+        String fromFormattedDate = new SimpleDateFormat("E, MMM dd").format(from);
+        mDateView.setText(fromFormattedDate + " - " + toFormattedDate);
+        day.setTime(to);
+        List<RespiratoryRateDay> respiratoryRateDaysData = getMyRespiratoryRateDaysData(db, day, device);
+        return new RespiratoryRateData(respiratoryRateDaysData);
+    }
+
+    protected LineDataSet createDataSet(final List<Entry> values, String label, int color) {
+        final LineDataSet lineDataSet = new LineDataSet(values, label);
+        lineDataSet.setColor(getResources().getColor(color));
+        lineDataSet.setDrawCircles(false);
+        lineDataSet.setLineWidth(2f);
+        lineDataSet.setFillAlpha(255);
+        lineDataSet.setCircleRadius(5f);
+        lineDataSet.setDrawCircles(true);
+        lineDataSet.setDrawCircleHole(true);
+        lineDataSet.setCircleColor(getResources().getColor(color));
+        lineDataSet.setAxisDependency(YAxis.AxisDependency.LEFT);
+        lineDataSet.setDrawValues(false);
+        return lineDataSet;
+    }
+
+    @Override
+    protected void updateChartsnUIThread(RespiratoryRateData respiratoryRateData) {
+        respiratoryRateChart.setData(null);
+        final String emptyValue = requireContext().getString(R.string.stats_empty_value);
+        sleepAvg.setText(respiratoryRateData.sleepRateAvg > 0 ? String.valueOf(respiratoryRateData.sleepRateAvg) : emptyValue);
+        awakeAvg.setText(respiratoryRateData.awakeRateAvg > 0 ? String.valueOf(respiratoryRateData.awakeRateAvg) : emptyValue);
+
+        List<Entry> lineAwakeRateAvgEntries = new ArrayList<>();
+        List<Entry> lineSleepRateEntries = new ArrayList<>();
+        for (int i = 0; i < TOTAL_DAYS; i++) {
+            RespiratoryRateDay day = respiratoryRateData.days.get(i);
+            if (day.awakeRateAvg > 0) {
+                lineAwakeRateAvgEntries.add(new Entry(i, day.awakeRateAvg));
+            }
+            if (day.sleepRateAvg > 0) {
+                lineSleepRateEntries.add(new Entry(i, day.sleepRateAvg));
+            }
+        }
+
+        LineDataSet awakeDataSet = createDataSet(lineAwakeRateAvgEntries, getString(R.string.sleep_colored_stats_awake_avg), R.color.respiratory_rate_color);
+        LineDataSet sleepDataSet = createDataSet(lineSleepRateEntries, getString(R.string.sleep_avg), R.color.chart_light_sleep_light);
+
+        final List<ILineDataSet> lineDataSets = new ArrayList<>();
+        lineDataSets.add(awakeDataSet);
+        lineDataSets.add(sleepDataSet);
+
+        List<LegendEntry> legendEntries = new ArrayList<>(1);
+        LegendEntry awakeEntry = new LegendEntry();
+        awakeEntry.label = getString(R.string.sleep_colored_stats_awake_avg);
+        awakeEntry.formColor = getResources().getColor(R.color.respiratory_rate_color);
+        LegendEntry sleepEntry = new LegendEntry();
+        sleepEntry.label = getString(R.string.sleep_avg);
+        sleepEntry.formColor = getResources().getColor(R.color.chart_light_sleep_light);
+        legendEntries.add(awakeEntry);
+        legendEntries.add(sleepEntry);
+        respiratoryRateChart.getLegend().setTextColor(LEGEND_TEXT_COLOR);
+        respiratoryRateChart.getLegend().setCustom(legendEntries);
+        final LineData lineData = new LineData(lineDataSets);
+        respiratoryRateChart.setData(lineData);
+        final XAxis x = respiratoryRateChart.getXAxis();
+        x.setValueFormatter(getRespiratoryRateChartDayValueFormatter(respiratoryRateData));
+    }
+
+    ValueFormatter getRespiratoryRateChartDayValueFormatter(RespiratoryRateData RespiratoryRateData) {
+        return new ValueFormatter() {
+            @Override
+            public String getFormattedValue(float value) {
+                RespiratoryRateFragment.RespiratoryRateDay day = RespiratoryRateData.days.get((int) value);
+                String pattern = TOTAL_DAYS > 7 ? "dd" : "EEE";
+                SimpleDateFormat formatLetterDay = new SimpleDateFormat(pattern, Locale.getDefault());
+                return formatLetterDay.format(new Date(day.day.getTimeInMillis()));
+            }
+        };
+    }
+
+    @Override
+    protected void renderCharts() {
+        respiratoryRateChart.invalidate();
+    }
+
+    protected void setupLegend(Chart<?> chart) {}
+
+    protected static class RespiratoryRateData extends ChartsData {
+        List<RespiratoryRateDay> days;
+        int awakeRateAvg;
+        int sleepRateAvg;
+
+        protected RespiratoryRateData(List<RespiratoryRateDay> days) {
+            this.days = days;
+            int awakeTotal = 0;
+            int sleepTotal = 0;
+            int awakeCounter = 0;
+            int sleepCounter = 0;
+            for(RespiratoryRateDay day: days) {
+                if (day.awakeRateAvg > 0) {
+                    awakeTotal += day.awakeRateAvg;
+                    awakeCounter++;
+                }
+                if (day.sleepRateAvg > 0) {
+                    sleepTotal += day.sleepRateAvg;
+                    sleepCounter++;
+                }
+            }
+            if (awakeTotal > 0) {
+                this.awakeRateAvg = Math.round((float) awakeTotal / awakeCounter);
+            }
+            if (sleepTotal > 0) {
+                this.sleepRateAvg = Math.round((float) sleepTotal / sleepCounter);
+            }
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/SampleXLabelFormatter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/SampleXLabelFormatter.java
new file mode 100644
index 0000000..bb5fdf3
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/SampleXLabelFormatter.java
@@ -0,0 +1,53 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities.charts;
+
+import android.annotation.SuppressLint;
+
+import com.github.mikephil.charting.formatter.ValueFormatter;
+
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+
+public class SampleXLabelFormatter extends ValueFormatter {
+    private final TimestampTranslation tsTranslation;
+    @SuppressLint("SimpleDateFormat")
+    private final SimpleDateFormat annotationDateFormat;
+    //        SimpleDateFormat dateFormat = new SimpleDateFormat("dd.MM.yyyy HH:mm");
+    private final Calendar cal = GregorianCalendar.getInstance();
+
+    public SampleXLabelFormatter(final TimestampTranslation tsTranslation, String simpleDateFormatPattern) {
+        this.tsTranslation = tsTranslation;
+        this.annotationDateFormat = new SimpleDateFormat(simpleDateFormatPattern);
+    }
+
+    // TODO: this does not work. Cannot use precomputed labels
+    @Override
+    public String getFormattedValue(final float value) {
+        cal.clear();
+        final int ts = (int) value;
+        cal.setTimeInMillis(tsTranslation.toOriginalValue(ts) * 1000L);
+        final Date date = cal.getTime();
+        return annotationDateFormat.format(date);
+    }
+
+    public TimestampTranslation getTsTranslation() {
+        return tsTranslation;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ShowDurationDialog.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ShowDurationDialog.java
new file mode 100644
index 0000000..4614388
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ShowDurationDialog.java
@@ -0,0 +1,51 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities.charts;
+
+import android.app.Dialog;
+import android.content.Context;
+import android.os.Bundle;
+import android.widget.TextView;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+
+public class ShowDurationDialog extends Dialog {
+    private final String mDuration;
+    private TextView durationLabel;
+
+    ShowDurationDialog(final String duration, final Context context) {
+        super(context);
+        mDuration = duration;
+    }
+
+    @Override
+    protected void onCreate(final Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_charts_durationdialog);
+
+        durationLabel = findViewById(R.id.charts_duration_label);
+        setDuration(mDuration);
+    }
+
+    public void setDuration(final String duration) {
+        if (mDuration != null) {
+            durationLabel.setText(duration);
+        } else {
+            durationLabel.setText("");
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/SingleEntryValueAnimator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/SingleEntryValueAnimator.java
new file mode 100644
index 0000000..e099e2d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/SingleEntryValueAnimator.java
@@ -0,0 +1,70 @@
+/*  Copyright (C) 2015-2024 Carsten Pfeiffer
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities.charts;
+
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+
+import com.github.mikephil.charting.animation.ChartAnimator;
+import com.github.mikephil.charting.data.Entry;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class SingleEntryValueAnimator extends ChartAnimator {
+    private static final Logger LOG = LoggerFactory.getLogger(SingleEntryValueAnimator.class);
+
+    private final Entry entry;
+    private final ValueAnimator.AnimatorUpdateListener listener;
+    private float previousValue;
+
+    public SingleEntryValueAnimator(Entry singleEntry, ValueAnimator.AnimatorUpdateListener listener) {
+        super(listener);
+        this.listener = listener;
+        entry = singleEntry;
+    }
+
+    public void setEntryYValue(float value) {
+        this.previousValue = entry.getY();
+        entry.setY(value);
+    }
+
+    @Override
+    public void animateY(int durationMillis) {
+        // we start with the previous value and animate the change to the
+        // next value.
+        // as our animation values are not used as absolute values, but as factors,
+        // we have to calculate the proper factors in advance. The entry already has
+        // the new value, so we create a factor to calculate the old value from the
+        // new value.
+
+        float startAnim;
+        float endAnim = 1f;
+        if (entry.getY() == 0f) {
+            startAnim = 0f;
+        } else {
+            startAnim = previousValue / entry.getY();
+        }
+
+//        LOG.debug("anim factors: " + startAnim + ", " + endAnim);
+
+        ObjectAnimator animatorY = ObjectAnimator.ofFloat(this, "phaseY", startAnim, endAnim);
+        animatorY.setDuration(durationMillis);
+        animatorY.addUpdateListener(listener);
+        animatorY.start();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/SleepAnalysis.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/SleepAnalysis.java
new file mode 100644
index 0000000..0c53344
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/SleepAnalysis.java
@@ -0,0 +1,153 @@
+/*  Copyright (C) 2019-2024 Jos茅 Rebelo, Petr Van臎k, Q-er
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities.charts;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
+
+public class SleepAnalysis {
+
+    public static final long MIN_SESSION_LENGTH = 5 * 60;
+    public static final long MAX_WAKE_PHASE_LENGTH = 2 * 60 * 60;
+
+    public List<SleepSession> calculateSleepSessions(List<? extends ActivitySample> samples) {
+        List<SleepSession> result = new ArrayList<>();
+
+        ActivitySample previousSample = null;
+        Date sleepStart = null;
+        Date sleepEnd = null;
+        long lightSleepDuration = 0;
+        long deepSleepDuration = 0;
+        long remSleepDuration = 0;
+        long awakeSleepDuration = 0;
+        long durationSinceLastSleep = 0;
+
+        for (ActivitySample sample : samples) {
+            if (isSleep(sample)) {
+                if (sleepStart == null)
+                    sleepStart = getDateFromSample(sample);
+                sleepEnd = getDateFromSample(sample);
+
+                durationSinceLastSleep = 0;
+            } else {
+                //exclude "not worn" times from sleep sessions as this makes a discrepancy with the charts
+                if (lightSleepDuration + deepSleepDuration + remSleepDuration + awakeSleepDuration > MIN_SESSION_LENGTH)
+                    result.add(new SleepSession(sleepStart, sleepEnd, lightSleepDuration, deepSleepDuration, remSleepDuration, awakeSleepDuration));
+                sleepStart = null;
+                sleepEnd = null;
+                lightSleepDuration = 0;
+                deepSleepDuration = 0;
+                remSleepDuration = 0;
+                awakeSleepDuration = 0;
+            }
+
+            if (previousSample != null) {
+                long durationSinceLastSample = sample.getTimestamp() - previousSample.getTimestamp();
+                if (sample.getKind() == ActivityKind.LIGHT_SLEEP) {
+                    lightSleepDuration += durationSinceLastSample;
+                } else if (sample.getKind() == ActivityKind.DEEP_SLEEP) {
+                    deepSleepDuration += durationSinceLastSample;
+                } else if (sample.getKind() == ActivityKind.REM_SLEEP) {
+                    remSleepDuration += durationSinceLastSample;
+                } else if (sample.getKind() == ActivityKind.AWAKE_SLEEP) {
+                    awakeSleepDuration += durationSinceLastSample;
+                } else {
+                    durationSinceLastSleep += durationSinceLastSample;
+                    if (sleepStart != null && durationSinceLastSleep > MAX_WAKE_PHASE_LENGTH) {
+                        if (lightSleepDuration + deepSleepDuration + remSleepDuration + awakeSleepDuration > MIN_SESSION_LENGTH)
+                            result.add(new SleepSession(sleepStart, sleepEnd, lightSleepDuration, deepSleepDuration, remSleepDuration, awakeSleepDuration));
+                        sleepStart = null;
+                        sleepEnd = null;
+                        lightSleepDuration = 0;
+                        deepSleepDuration = 0;
+                        remSleepDuration = 0;
+                        awakeSleepDuration = 0;
+                    }
+                }
+            }
+
+            previousSample = sample;
+        }
+        if (lightSleepDuration + deepSleepDuration + remSleepDuration + awakeSleepDuration > MIN_SESSION_LENGTH) {
+            result.add(new SleepSession(sleepStart, sleepEnd, lightSleepDuration, deepSleepDuration, remSleepDuration, awakeSleepDuration));
+        }
+        return result;
+    }
+
+    private boolean isSleep(ActivitySample sample) {
+        return sample.getKind() == ActivityKind.DEEP_SLEEP ||
+                sample.getKind() == ActivityKind.LIGHT_SLEEP ||
+                sample.getKind() == ActivityKind.REM_SLEEP ||
+                sample.getKind() == ActivityKind.AWAKE_SLEEP;
+    }
+
+    private Date getDateFromSample(ActivitySample sample) {
+        return new Date(sample.getTimestamp() * 1000L);
+    }
+
+
+    public static class SleepSession {
+        private final Date sleepStart;
+        private final Date sleepEnd;
+        private final long lightSleepDuration;
+        private final long deepSleepDuration;
+        private final long remSleepDuration;
+        private final long awakeSleepDuration;
+
+        private SleepSession(Date sleepStart,
+                             Date sleepEnd,
+                             long lightSleepDuration,
+                             long deepSleepDuration,
+                             long remSleepDuration,
+                             long awakeSleepDuration) {
+            this.sleepStart = sleepStart;
+            this.sleepEnd = sleepEnd;
+            this.lightSleepDuration = lightSleepDuration;
+            this.deepSleepDuration = deepSleepDuration;
+            this.remSleepDuration = remSleepDuration;
+            this.awakeSleepDuration = awakeSleepDuration;
+        }
+
+        public Date getSleepStart() {
+            return sleepStart;
+        }
+
+        public Date getSleepEnd() {
+            return sleepEnd;
+        }
+
+        public long getLightSleepDuration() {
+            return lightSleepDuration;
+        }
+
+        public long getDeepSleepDuration() {
+            return deepSleepDuration;
+        }
+
+        public long getRemSleepDuration() {
+            return remSleepDuration;
+        }
+
+        public long getAwakeSleepDuration() {
+            return awakeSleepDuration;
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/SleepCollectionFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/SleepCollectionFragment.java
new file mode 100644
index 0000000..5377f5d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/SleepCollectionFragment.java
@@ -0,0 +1,44 @@
+/*  Copyright (C) 2024 a0z, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities.charts;
+
+import android.os.Bundle;
+
+import androidx.fragment.app.FragmentManager;
+
+import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBFragment;
+import nodomain.freeyourgadget.gadgetbridge.adapter.NestedFragmentAdapter;
+import nodomain.freeyourgadget.gadgetbridge.adapter.SleepFragmentAdapter;
+
+public class SleepCollectionFragment extends AbstractCollectionFragment {
+    public SleepCollectionFragment() {
+
+    }
+
+    public static SleepCollectionFragment newInstance(final boolean allowSwipe) {
+        final SleepCollectionFragment fragment = new SleepCollectionFragment();
+        final Bundle args = new Bundle();
+        args.putBoolean(ARG_ALLOW_SWIPE, allowSwipe);
+        fragment.setArguments(args);
+        return fragment;
+    }
+
+    @Override
+    public NestedFragmentAdapter getNestedFragmentAdapter(AbstractGBFragment fragment, FragmentManager childFragmentManager) {
+        return new SleepFragmentAdapter(this, getChildFragmentManager());
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/SpeedZonesFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/SpeedZonesFragment.java
new file mode 100644
index 0000000..74986cb
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/SpeedZonesFragment.java
@@ -0,0 +1,174 @@
+/*  Copyright (C) 2017-2024 Andreas Shimokawa, Daniele Gobbetti, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities.charts;
+
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.github.mikephil.charting.charts.Chart;
+import com.github.mikephil.charting.charts.HorizontalBarChart;
+import com.github.mikephil.charting.components.XAxis;
+import com.github.mikephil.charting.components.YAxis;
+import com.github.mikephil.charting.data.BarData;
+import com.github.mikephil.charting.data.BarDataSet;
+import com.github.mikephil.charting.data.BarEntry;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
+
+
+public class SpeedZonesFragment extends AbstractActivityChartFragment<ChartsData> {
+    protected static final Logger LOG = LoggerFactory.getLogger(SpeedZonesFragment.class);
+
+    private HorizontalBarChart mStatsChart;
+
+    @Override
+    protected ChartsData refreshInBackground(ChartsHost chartsHost, DBHandler db, GBDevice device) {
+        List<? extends ActivitySample> samples = getSamples(db, device);
+
+        MySpeedZonesData mySpeedZonesData = refreshStats(samples);
+
+        return new MyChartsData(mySpeedZonesData);
+    }
+
+    private MySpeedZonesData refreshStats(List<? extends ActivitySample> samples) {
+        ActivityAnalysis analysis = new ActivityAnalysis();
+        analysis.calculateActivityAmounts(samples);
+        BarData data = new BarData();
+        data.setValueTextColor(CHART_TEXT_COLOR);
+        List<BarEntry> entries = new ArrayList<>();
+
+        ActivityUser user = new ActivityUser();
+        /*double distanceFactorCm;
+        if (user.getGender() == user.GENDER_MALE){
+            distanceFactorCm = user.getHeightCm() * user.GENDER_MALE_DISTANCE_FACTOR / 1000;
+        } else {
+            distanceFactorCm = user.getHeightCm() * user.GENDER_FEMALE_DISTANCE_FACTOR / 1000;
+        }*/
+
+        for (Map.Entry<Integer, Long> entry : analysis.stats.entrySet()) {
+            entries.add(new BarEntry(entry.getKey(), entry.getValue() / 60));
+        }
+
+        BarDataSet set = new BarDataSet(entries, "");
+        set.setValueTextColor(CHART_TEXT_COLOR);
+        set.setColors(getColorFor(ActivityKind.ACTIVITY));
+        //set.setDrawValues(false);
+        //data.setBarWidth(0.1f);
+        data.addDataSet(set);
+
+        return new MySpeedZonesData(data);
+    }
+
+    @Override
+    protected void updateChartsnUIThread(ChartsData chartsData) {
+        MyChartsData mcd = (MyChartsData) chartsData;
+        mStatsChart.setData(mcd.getChartsData().getBarData());
+    }
+
+    @Override
+    public String getTitle() {
+        return getString(R.string.stats_title);
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+                             Bundle savedInstanceState) {
+        View rootView = inflater.inflate(R.layout.fragment_statschart, container, false);
+
+        mStatsChart = (HorizontalBarChart) rootView.findViewById(R.id.statschart);
+        setupStatsChart();
+
+        // refresh immediately instead of use refreshIfVisible(), for perceived performance
+        refresh();
+
+        return rootView;
+    }
+
+    private void setupStatsChart() {
+        mStatsChart.setBackgroundColor(BACKGROUND_COLOR);
+        mStatsChart.getDescription().setTextColor(DESCRIPTION_COLOR);
+        mStatsChart.setNoDataText("");
+        mStatsChart.getLegend().setEnabled(false);
+        mStatsChart.setTouchEnabled(false);
+        mStatsChart.getDescription().setText("");
+
+        XAxis right = mStatsChart.getXAxis(); //believe it or not, the X axis is vertical for HorizontalBarChart
+        right.setTextColor(CHART_TEXT_COLOR);
+
+        YAxis bottom = mStatsChart.getAxisRight();
+        bottom.setTextColor(CHART_TEXT_COLOR);
+        bottom.setGranularity(1f);
+
+        YAxis top = mStatsChart.getAxisLeft();
+        top.setTextColor(CHART_TEXT_COLOR);
+        top.setGranularity(1f);
+    }
+
+    @Override
+    protected List<? extends ActivitySample> getSamples(DBHandler db, GBDevice device, int tsFrom, int tsTo) {
+        return super.getAllSamples(db, device, tsFrom, tsTo);
+    }
+
+    @Override
+    protected void setupLegend(Chart<?> chart) {
+        // no legend here, it is all about the steps here
+        chart.getLegend().setEnabled(false);
+    }
+
+    @Override
+    protected void renderCharts() {
+        mStatsChart.invalidate();
+    }
+
+    private static class MySpeedZonesData extends ChartsData {
+        private final BarData barData;
+
+        MySpeedZonesData(BarData barData) {
+            this.barData = barData;
+        }
+
+        BarData getBarData() {
+            return barData;
+        }
+    }
+
+    private static class MyChartsData extends ChartsData {
+        private final MySpeedZonesData chartsData;
+
+        MyChartsData(MySpeedZonesData chartsData) {
+            this.chartsData = chartsData;
+        }
+
+        MySpeedZonesData getChartsData() {
+            return chartsData;
+        }
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/Spo2ChartFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/Spo2ChartFragment.java
new file mode 100644
index 0000000..f235cab
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/Spo2ChartFragment.java
@@ -0,0 +1,287 @@
+/*  Copyright (C) 2023-2024 Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities.charts;
+
+import android.graphics.Color;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.core.content.ContextCompat;
+
+import com.github.mikephil.charting.animation.Easing;
+import com.github.mikephil.charting.charts.Chart;
+import com.github.mikephil.charting.charts.LineChart;
+import com.github.mikephil.charting.components.LegendEntry;
+import com.github.mikephil.charting.components.LimitLine;
+import com.github.mikephil.charting.components.XAxis;
+import com.github.mikephil.charting.components.YAxis;
+import com.github.mikephil.charting.data.Entry;
+import com.github.mikephil.charting.data.LineData;
+import com.github.mikephil.charting.data.LineDataSet;
+import com.github.mikephil.charting.formatter.ValueFormatter;
+import com.github.mikephil.charting.interfaces.datasets.ILineDataSet;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.TimeSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.Spo2Sample;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+// Based on StressChartFragment
+
+public class Spo2ChartFragment extends AbstractChartFragment<Spo2ChartFragment.Spo2ChartsData> {
+    protected static final Logger LOG = LoggerFactory.getLogger(Spo2ChartFragment.class);
+
+    private LineChart mSpo2Chart;
+
+    private int BACKGROUND_COLOR;
+    private int DESCRIPTION_COLOR;
+    private int CHART_TEXT_COLOR;
+    private int LEGEND_TEXT_COLOR;
+    private int CHART_LINE_COLOR;
+
+    private String SPO2_AVERAGE_LABEL;
+
+    private final Prefs prefs = GBApplication.getPrefs();
+
+    private final boolean CHARTS_SLEEP_RANGE_24H = prefs.getBoolean("chart_sleep_range_24h", false);
+    private final boolean SHOW_CHARTS_AVERAGE = prefs.getBoolean("charts_show_average", true);
+
+    @Override
+    protected void init() {
+        BACKGROUND_COLOR = GBApplication.getBackgroundColor(requireContext());
+        LEGEND_TEXT_COLOR = DESCRIPTION_COLOR = GBApplication.getTextColor(requireContext());
+        CHART_TEXT_COLOR = GBApplication.getSecondaryTextColor(requireContext());
+
+        if (prefs.getBoolean("chart_heartrate_color", false)) {
+            CHART_LINE_COLOR = ContextCompat.getColor(requireContext(), R.color.chart_heartrate_alternative);
+        } else {
+            CHART_LINE_COLOR = ContextCompat.getColor(requireContext(), R.color.chart_heartrate);
+        }
+
+        SPO2_AVERAGE_LABEL = requireContext().getString(R.string.charts_legend_spo2_average);
+    }
+
+    @Override
+    protected Spo2ChartsData refreshInBackground(final ChartsHost chartsHost, final DBHandler db, final GBDevice device) {
+        final List<? extends Spo2Sample> samples = getSamples(db, device);
+
+        LOG.info("Got {} SpO2 samples", samples.size());
+
+        return new Spo2ChartsDataBuilder(samples).build();
+    }
+
+    protected LineDataSet createDataSet(final List<Entry> values) {
+        final LineDataSet lineDataSet = new LineDataSet(values, "SpO2");
+        lineDataSet.setColor(CHART_LINE_COLOR);
+        lineDataSet.setDrawCircles(false);
+        lineDataSet.setLineWidth(2.2f);
+        lineDataSet.setFillAlpha(255);
+        lineDataSet.setValueTextColor(CHART_TEXT_COLOR);
+        lineDataSet.setAxisDependency(YAxis.AxisDependency.LEFT);
+        lineDataSet.setValueFormatter(new ValueFormatter() {
+            @Override
+            public String getFormattedValue(float value) {
+                return String.format(Locale.ROOT, "%d", (int) value);
+            }
+        });
+        return lineDataSet;
+    }
+
+    @Override
+    protected void updateChartsnUIThread(final Spo2ChartsData spo2Data) {
+        final DefaultChartsData<LineData> chartsData = spo2Data.getChartsData();
+        mSpo2Chart.setData(null); // workaround for https://github.com/PhilJay/MPAndroidChart/issues/2317
+        mSpo2Chart.getXAxis().setValueFormatter(chartsData.getXValueFormatter());
+        mSpo2Chart.setData(chartsData.getData());
+        mSpo2Chart.getAxisLeft().removeAllLimitLines();
+
+        LOG.debug("SpO2 average: {}", spo2Data.getAverage());
+
+        if (spo2Data.getAverage() > 0 && SHOW_CHARTS_AVERAGE) {
+            final LimitLine averageLine = new LimitLine(spo2Data.getAverage());
+            averageLine.setLineColor(Color.RED);
+            averageLine.setLineWidth(1.5f);
+            averageLine.enableDashedLine(15f, 10f, 0f);
+            mSpo2Chart.getAxisLeft().addLimitLine(averageLine);
+        }
+
+        mSpo2Chart.getAxisRight().setEnabled(false);
+    }
+
+    @Override
+    public String getTitle() {
+        return requireContext().getString(R.string.pref_header_spo2);
+    }
+
+    @Override
+    public boolean isSingleDay() {
+        return false;
+    }
+
+    @Override
+    public View onCreateView(final LayoutInflater inflater,
+                             final ViewGroup container,
+                             final Bundle savedInstanceState) {
+        final View rootView = inflater.inflate(R.layout.fragment_charts, container, false);
+
+        mSpo2Chart = rootView.findViewById(R.id.activitysleepchart);
+
+        setupLineChart();
+
+        // refresh immediately instead of use refreshIfVisible(), for perceived performance
+        refresh();
+
+        return rootView;
+    }
+
+    private void setupLineChart() {
+        mSpo2Chart.setBackgroundColor(BACKGROUND_COLOR);
+        mSpo2Chart.getDescription().setTextColor(DESCRIPTION_COLOR);
+        configureBarLineChartDefaults(mSpo2Chart);
+
+        final XAxis x = mSpo2Chart.getXAxis();
+        x.setDrawLabels(true);
+        x.setDrawGridLines(false);
+        x.setEnabled(true);
+        x.setTextColor(CHART_TEXT_COLOR);
+        x.setDrawLimitLinesBehindData(true);
+
+        final YAxis yAxisLeft = mSpo2Chart.getAxisLeft();
+        yAxisLeft.setDrawGridLines(true);
+        yAxisLeft.setAxisMaximum(100f);
+        yAxisLeft.setAxisMinimum(75f);
+        yAxisLeft.setDrawTopYLabelEntry(false);
+        yAxisLeft.setTextColor(CHART_TEXT_COLOR);
+        yAxisLeft.setEnabled(true);
+    }
+
+    @Override
+    protected void setupLegend(final Chart<?> chart) {
+        final List<LegendEntry> legendEntries = new ArrayList<>(2);
+
+        final LegendEntry entry = new LegendEntry();
+        entry.label = requireContext().getString(R.string.pref_header_spo2);
+        entry.formColor = CHART_LINE_COLOR;
+        legendEntries.add(entry);
+
+        if (SHOW_CHARTS_AVERAGE) {
+            final LegendEntry averageEntry = new LegendEntry();
+            averageEntry.label = SPO2_AVERAGE_LABEL;
+            averageEntry.formColor = Color.RED;
+            legendEntries.add(averageEntry);
+        }
+
+        chart.getLegend().setCustom(legendEntries);
+        chart.getLegend().setTextColor(LEGEND_TEXT_COLOR);
+    }
+
+    @Override
+    protected void renderCharts() {
+        mSpo2Chart.animateX(ANIM_TIME, Easing.EaseInOutQuart);
+    }
+
+    private List<? extends Spo2Sample> getSamples(final DBHandler db, final GBDevice device) {
+        final int tsStart = getTSStart();
+        final int tsEnd = getTSEnd();
+        final DeviceCoordinator coordinator = device.getDeviceCoordinator();
+        final TimeSampleProvider<? extends Spo2Sample> sampleProvider = coordinator.getSpo2SampleProvider(device, db.getDaoSession());
+        return sampleProvider.getAllSamples(tsStart * 1000L, tsEnd * 1000L);
+    }
+
+    protected class Spo2ChartsDataBuilder {
+        private final List<? extends Spo2Sample> samples;
+
+        private final TimestampTranslation tsTranslation = new TimestampTranslation();
+
+        private final List<Entry> lineEntries = new ArrayList<>();
+
+        long averageSum;
+        long averageNumSamples;
+
+        public Spo2ChartsDataBuilder(final List<? extends Spo2Sample> samples) {
+            this.samples = samples;
+        }
+
+        private void reset() {
+            tsTranslation.reset();
+            lineEntries.clear();
+
+            averageSum = 0;
+            averageNumSamples = 0;
+        }
+
+        private void processSamples() {
+            reset();
+
+            for (final Spo2Sample sample : samples) {
+                processSample(sample);
+            }
+        }
+
+        private void processSample(final Spo2Sample sample) {
+            final int ts = tsTranslation.shorten((int) (sample.getTimestamp() / 1000L));
+            lineEntries.add(new Entry(ts, sample.getSpo2()));
+
+            averageSum += sample.getSpo2();
+            averageNumSamples += 1;
+        }
+
+        public Spo2ChartsData build() {
+            processSamples();
+
+            final List<ILineDataSet> lineDataSets = new ArrayList<>();
+
+            lineDataSets.add(createDataSet(lineEntries));
+
+            final LineData lineData = new LineData(lineDataSets);
+            final ValueFormatter xValueFormatter = new SampleXLabelFormatter(tsTranslation, "HH:mm");
+            final DefaultChartsData<LineData> chartsData = new DefaultChartsData<>(lineData, xValueFormatter);
+            return new Spo2ChartsData(chartsData, Math.round((float) averageSum / averageNumSamples));
+        }
+    }
+
+    protected static class Spo2ChartsData extends ChartsData {
+        private final DefaultChartsData<LineData> chartsData;
+        private final int average;
+
+        public Spo2ChartsData(final DefaultChartsData<LineData> chartsData, final int average) {
+            this.chartsData = chartsData;
+            this.average = average;
+        }
+
+        public DefaultChartsData<LineData> getChartsData() {
+            return chartsData;
+        }
+
+        public int getAverage() {
+            return average;
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/StepAnalysis.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/StepAnalysis.java
new file mode 100644
index 0000000..c35e8c9
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/StepAnalysis.java
@@ -0,0 +1,260 @@
+/*  Copyright (C) 2020-2024 Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities.charts;
+
+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.activities.HeartRateUtils;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySession;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
+
+public class StepAnalysis {
+    protected static final Logger LOG = LoggerFactory.getLogger(StepAnalysis.class);
+    private int totalDailySteps = 0;
+
+    public List<ActivitySession> calculateStepSessions(List<? extends ActivitySample> samples) {
+        LOG.debug("get all samples activity sessions: {}", samples.size());
+        List<ActivitySession> result = new ArrayList<>();
+        ActivityUser activityUser = new ActivityUser();
+        final int MIN_SESSION_LENGTH = 60 * GBApplication.getPrefs().getInt("chart_list_min_session_length", 5);
+        final int MAX_IDLE_PHASE_LENGTH = 60 * GBApplication.getPrefs().getInt("chart_list_max_idle_phase_length", 5);
+        final int MIN_STEPS_PER_MINUTE = GBApplication.getPrefs().getInt("chart_list_min_steps_per_minute", 40);
+        int stepLengthCm = activityUser.getStepLengthCm();
+        final double STEP_LENGTH_M = stepLengthCm * 0.01;
+        final double MIN_SESSION_INTENSITY = Math.max(0, Math.min(1, MIN_STEPS_PER_MINUTE * 0.01));
+        totalDailySteps = 0;
+
+        ActivitySample previousSample = null;
+        Date sessionStart = null;
+        Date sessionEnd;
+        int activeSteps = 0; //steps that we count
+        int activeDistanceCm = 0;
+        int stepsBetweenActivePeriods = 0; //steps during time when we maybe take a rest but then restart
+        int distanceBetweenActivePeriods = 0;
+        int durationSinceLastActiveStep = 0;
+        ActivityKind activityKind;
+
+        List<Integer> heartRateSum = new ArrayList<>();
+        List<Integer> heartRateBetweenActivePeriodsSum = new ArrayList<>();
+
+        float activeIntensity = 0;
+        float intensityBetweenActivePeriods = 0;
+        HeartRateUtils heartRateUtilsInstance = HeartRateUtils.getInstance();
+
+        for (ActivitySample sample : samples) {
+            int steps = sample.getSteps();
+            if (steps > 0) {
+                totalDailySteps += steps;
+            }
+
+            /*
+             * FIXME This should only consider non-sleep samples. However, this always had the wrong
+             *  check for that, so it processed everything. In #3977, that was corrected, which
+             *  introduces a regression for some devices such as the Amazfit Bip. Processing everything
+             *  seems to work, but this logic needs to be reviewed.
+             */
+            if (!(sample instanceof TrailingActivitySample)) { //trailing samples have wrong date and make trailing activity have 0 duration
+
+                if (sessionStart == null) {
+                    sessionStart = getDateFromSample(sample);
+                    if (sample.getSteps() >= 0) {
+                        activeSteps = sample.getSteps();
+                    } else {
+                        activeSteps = 0;
+                    }
+                    if (sample.getDistanceCm() >= 0) {
+                        activeDistanceCm = sample.getDistanceCm();
+                    } else if (activeSteps > 0) {
+                        activeDistanceCm = activeSteps * stepLengthCm;
+                    } else {
+                        activeDistanceCm = 0;
+                    }
+                    activeIntensity = sample.getIntensity();
+                    heartRateSum = new ArrayList<>();
+                    if (heartRateUtilsInstance.isValidHeartRateValue(sample.getHeartRate())) {
+                        heartRateSum.add(sample.getHeartRate());
+                    }
+                    durationSinceLastActiveStep = 0;
+                    stepsBetweenActivePeriods = 0;
+                    distanceBetweenActivePeriods = 0;
+                    heartRateBetweenActivePeriodsSum = new ArrayList<>();
+                    previousSample = null;
+                }
+                if (previousSample != null) {
+                    int durationSinceLastSample = sample.getTimestamp() - previousSample.getTimestamp();
+
+                    if (sample.getSteps() > MIN_STEPS_PER_MINUTE || //either some steps
+                            (sample.getIntensity() > MIN_SESSION_INTENSITY && sample.getSteps() > 0)) { //or some intensity plus at least one step
+                        activeSteps += sample.getSteps() + stepsBetweenActivePeriods;
+                        if (sample.getDistanceCm() >= 0) {
+                            activeDistanceCm += sample.getDistanceCm() + distanceBetweenActivePeriods;
+                        } else {
+                            activeDistanceCm += sample.getSteps() * stepLengthCm + distanceBetweenActivePeriods;
+                        }
+                        activeIntensity += sample.getIntensity() + intensityBetweenActivePeriods;
+                        if (heartRateUtilsInstance.isValidHeartRateValue(sample.getHeartRate())) {
+                            heartRateSum.add(sample.getHeartRate());
+                        }
+                        heartRateSum.addAll(heartRateBetweenActivePeriodsSum);
+                        heartRateBetweenActivePeriodsSum = new ArrayList<>();
+                        stepsBetweenActivePeriods = 0;
+                        distanceBetweenActivePeriods = 0;
+                        intensityBetweenActivePeriods = 0;
+                        durationSinceLastActiveStep = 0;
+
+                    } else { //short break data to remember, we will add it to the rest later, if break not too long
+                        if (sample.getSteps() >= 0) {
+                            stepsBetweenActivePeriods += sample.getSteps();
+                        }
+                        if (sample.getDistanceCm() >= 0) {
+                            distanceBetweenActivePeriods += sample.getDistanceCm();
+                        } else if (sample.getSteps() > 0) {
+                            distanceBetweenActivePeriods += sample.getSteps() * stepLengthCm;
+                        }
+                        if (heartRateUtilsInstance.isValidHeartRateValue(sample.getHeartRate())) {
+                            heartRateBetweenActivePeriodsSum.add(sample.getHeartRate());
+                        }
+                        durationSinceLastActiveStep += durationSinceLastSample;
+                        intensityBetweenActivePeriods += sample.getIntensity();
+                    }
+                    if (durationSinceLastActiveStep >= MAX_IDLE_PHASE_LENGTH) { //break too long, we split here
+
+                        int current = sample.getTimestamp();
+                        int starting = (int) (sessionStart.getTime() / 1000);
+                        int session_length = current - starting - durationSinceLastActiveStep;
+
+                        if (session_length >= MIN_SESSION_LENGTH) { //valid activity session
+                            int heartRateAverage = heartRateSum.toArray().length > 0 ? calculateSumOfInts(heartRateSum) / heartRateSum.toArray().length : 0;
+                            float distance = activeDistanceCm * 0.01f;
+                            sessionEnd = new Date((sample.getTimestamp() - durationSinceLastActiveStep) * 1000L);
+                            activityKind = detect_activity_kind(session_length, activeSteps, heartRateAverage, activeIntensity);
+                            ActivitySession activitySession = new ActivitySession(sessionStart, sessionEnd, activeSteps, heartRateAverage, activeIntensity, distance, activityKind);
+                            //activitySession.setSessionType(ActivitySession.SESSION_ONGOING);
+                            result.add(activitySession);
+                        }
+                        sessionStart = null;
+                    }
+                }
+                previousSample = sample;
+            }
+        }
+        //trailing activity: make sure we show the last portion of the data as well in case no further activity is recorded yet
+
+        if (sessionStart != null) {
+            int current = previousSample.getTimestamp();
+            int starting = (int) (sessionStart.getTime() / 1000);
+            int session_length = current - starting - durationSinceLastActiveStep;
+
+            if (session_length >= MIN_SESSION_LENGTH) {
+                int heartRateAverage = heartRateSum.toArray().length > 0 ? calculateSumOfInts(heartRateSum) / heartRateSum.toArray().length : 0;
+                float distance = activeDistanceCm * 0.01f;
+                sessionEnd = getDateFromSample(previousSample);
+                activityKind = detect_activity_kind(session_length, activeSteps, heartRateAverage, activeIntensity);
+                ActivitySession ongoingActivity = new ActivitySession(sessionStart, sessionEnd, activeSteps, heartRateAverage, activeIntensity, distance, activityKind);
+                ongoingActivity.setSessionType(ActivitySession.SESSION_ONGOING);
+                result.add(ongoingActivity);
+            }
+        }
+        return result;
+    }
+
+    public ActivitySession calculateSummary(List<ActivitySession> sessions, boolean empty) {
+
+        Date startTime = null;
+        Date endTime = null;
+        int stepsSum = 0;
+        int heartRateAverage = 0;
+        List<Integer> heartRateSum = new ArrayList<>();
+        int distanceSum = 0;
+        float intensitySum = 0;
+        int sessionCount;
+        long durationSum = 0;
+
+        for (ActivitySession session : sessions) {
+            startTime = session.getStartTime();
+            endTime = session.getEndTime();
+            durationSum += endTime.getTime() - startTime.getTime();
+            stepsSum += session.getActiveSteps();
+            distanceSum += session.getDistance();
+            heartRateSum.add(session.getHeartRateAverage());
+            intensitySum += session.getIntensity();
+        }
+
+        sessionCount = sessions.toArray().length;
+        if (heartRateSum.toArray().length > 0) {
+            heartRateAverage = calculateSumOfInts(heartRateSum) / heartRateSum.toArray().length;
+        }
+        startTime = new Date(0);
+        endTime = new Date(durationSum);
+
+        ActivitySession stepSessionSummary = new ActivitySession(startTime, endTime,
+                stepsSum, heartRateAverage, intensitySum, distanceSum, ActivityKind.UNKNOWN);
+
+        stepSessionSummary.setSessionCount(sessionCount);
+        stepSessionSummary.setSessionType(ActivitySession.SESSION_SUMMARY);
+        stepSessionSummary.setEmptySummary(empty);
+
+
+        stepSessionSummary.setTotalDaySteps(totalDailySteps);
+        return stepSessionSummary;
+    }
+
+    public ActivitySession getOngoingSessions(List<ActivitySession> sessions) {
+
+        for (ActivitySession session : sessions) {
+            if (session.getSessionType() == ActivitySession.SESSION_ONGOING) {
+                return session;
+            }
+        }
+        return null;
+    }
+
+    private int calculateSumOfInts(List<Integer> samples) {
+        int result = 0;
+        for (Integer sample : samples) {
+            result += sample;
+        }
+        return result;
+    }
+
+    private ActivityKind detect_activity_kind(int session_length, int activeSteps, int heartRateAverage, float intensity) {
+        final int MIN_STEPS_PER_MINUTE_FOR_RUN = GBApplication.getPrefs().getInt("chart_list_min_steps_per_minute_for_run", 120);
+        int spm = (int) (activeSteps / (session_length / 60));
+        if (spm > MIN_STEPS_PER_MINUTE_FOR_RUN) {
+            return ActivityKind.RUNNING;
+        }
+        if (activeSteps > 200) {
+            return ActivityKind.WALKING;
+        }
+        if (heartRateAverage > 90 && intensity > 15) { //needs tuning
+            return ActivityKind.EXERCISE;
+        }
+        return ActivityKind.ACTIVITY;
+    }
+
+    private Date getDateFromSample(ActivitySample sample) {
+        return new Date(sample.getTimestamp() * 1000L);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/StepStreaksDashboard.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/StepStreaksDashboard.java
new file mode 100644
index 0000000..39f138e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/StepStreaksDashboard.java
@@ -0,0 +1,496 @@
+/*  Copyright (C) 2022-2024 Arjan Schrijver, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities.charts;
+
+import android.content.ActivityNotFoundException;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.format.DateUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageButton;
+import android.widget.LinearLayout;
+import android.widget.ProgressBar;
+import android.widget.ScrollView;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.annotation.Nullable;
+import androidx.core.content.FileProvider;
+import androidx.fragment.app.FragmentActivity;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Calendar;
+import java.util.Date;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.database.DBAccess;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.model.DailyTotals;
+import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+import nodomain.freeyourgadget.gadgetbridge.util.dialogs.MaterialDialogFragment;
+
+public class StepStreaksDashboard extends MaterialDialogFragment {
+    protected static final Logger LOG = LoggerFactory.getLogger(StepStreaksDashboard.class);
+    GBDevice gbDevice;
+    int stepsGoal;
+    boolean cancelTasks = false;
+    boolean backgroundTaskFinished = false;
+    private StepsStreaks stepsStreaks = new StepsStreaks();
+    private static final String GOAL = "goal";
+    private static final String STREAKS = "streaks";
+    private static final String PERIOD_CURRENT = "current";
+    private static final String PERIOD_TOTALS = "totals";
+    private static final int MIN_YEAR = 2015; //we go back in time, this is minimal year boundary
+
+    public StepStreaksDashboard() {
+
+    }
+
+    //Calculates some stats for longest streak (daily steps goal being reached for subsequent days
+    //without interruption (day with steps less then goal)
+    //Possible improvements/nice to haves:
+    //- cache values until new activity fetch is performed
+    //- read the goals from the USER_ATTRIBUTES table. But, this would also require to be able
+    //to edit/add values there...
+
+    public static StepStreaksDashboard newInstance(int goal, GBDevice device) {
+
+        StepStreaksDashboard fragment = new StepStreaksDashboard();
+        Bundle args = new Bundle();
+        args.putInt(GOAL, goal);
+        args.putParcelable(GBDevice.EXTRA_DEVICE, device);
+        fragment.setArguments(args);
+        return fragment;
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+        return inflater.inflate(R.layout.steps_streaks_dashboard, container);
+    }
+
+    @Override
+    public void onStop() {
+        super.onStop();
+        cancelTasks = true;
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        cancelTasks = true;
+    }
+
+
+    @Override
+    public void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        if (backgroundTaskFinished) {
+            outState.putParcelable(STREAKS, stepsStreaks);
+        }
+    }
+
+    @Override
+    public void onViewCreated(final View view, @Nullable Bundle savedInstanceState) {
+        super.onViewCreated(view, savedInstanceState);
+
+        stepsGoal = getArguments().getInt(GOAL, 0);
+        gbDevice = getArguments().getParcelable(GBDevice.EXTRA_DEVICE);
+        if (gbDevice == null) {
+            throw new IllegalArgumentException("Must provide a device when invoking this activity");
+        }
+
+        if (savedInstanceState != null) {
+            StepsStreaks streaks = (StepsStreaks) savedInstanceState.getParcelable(STREAKS);
+            if (streaks != null) {
+                stepsStreaks = streaks;
+                backgroundTaskFinished = true;
+                cancelTasks = true;
+                indicate_progress(false);
+                populateData();
+            }
+        }
+        createTaskCalculateLatestStepsStreak("Visualizing data current", getActivity(), PERIOD_CURRENT).execute();
+        createTaskCalculateLatestStepsStreak("Visualizing data maximum", getActivity(), PERIOD_TOTALS).execute();
+    }
+
+    void indicate_progress(boolean inProgress) {
+        ProgressBar step_streak_dashboard_loading_circle = getView().findViewById(R.id.step_streak_dashboard_loading_circle);
+        if (inProgress) {
+            step_streak_dashboard_loading_circle.setAlpha(0.4f); //make it a bit softer
+        } else {
+            step_streak_dashboard_loading_circle.setAlpha(0);
+        }
+    }
+
+    void populateData() {
+
+        LinearLayout current = getView().findViewById(R.id.step_streak_current_layout);
+        TextView days_current = current.findViewById(R.id.step_streak_days_value);
+        TextView average_current = current.findViewById(R.id.step_streak_average_value);
+        TextView total_current = current.findViewById(R.id.step_streak_total_value);
+        TextView date_current_value = current.findViewById(R.id.step_streak_current_date_value);
+
+        LinearLayout maximum = getView().findViewById(R.id.step_streak_maximum_layout);
+        TextView days_maximum = maximum.findViewById(R.id.step_streak_days_value);
+        TextView average_maximum = maximum.findViewById(R.id.step_streak_average_value);
+        TextView total_maximum = maximum.findViewById(R.id.step_streak_total_value);
+        TextView date_maximum_value = maximum.findViewById(R.id.step_streak_maximum_date_value);
+
+        LinearLayout total = getView().findViewById(R.id.step_streak_total_layout);
+        TextView days_total = total.findViewById(R.id.step_streak_days_value);
+        TextView days_total_label = total.findViewById(R.id.step_streak_days_label);
+        TextView total_total = total.findViewById(R.id.step_streak_total_value);
+        TextView date_total_value = total.findViewById(R.id.step_streak_total_date_value);
+        TextView date_total_label = total.findViewById(R.id.step_streak_total_label);
+
+        ImageButton step_streak_share_button = getView().findViewById(R.id.step_streak_share_button);
+        step_streak_share_button.setVisibility(View.GONE);
+        step_streak_share_button.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                take_share_screenshot(getActivity());
+            }
+        });
+
+        if (stepsStreaks.current.days > 0) {
+            current.setVisibility(View.VISIBLE);
+            days_current.setText(Integer.toString(stepsStreaks.current.days));
+            average_current.setText(Integer.toString(stepsStreaks.current.steps / stepsStreaks.current.days));
+            total_current.setText(Integer.toString(stepsStreaks.current.steps));
+
+            Date startDate = new Date(stepsStreaks.current.timestamp * 1000L);
+            Date endDate = DateTimeUtils.shiftByDays(startDate, stepsStreaks.current.days - 1); //first day is 1 not 0
+            date_current_value.setText(DateTimeUtils.formatDateRange(startDate, endDate));
+        }
+
+        if (stepsStreaks.maximum.days > 0) {
+            maximum.setVisibility(View.VISIBLE);
+            days_maximum.setText(Integer.toString(stepsStreaks.maximum.days));
+            average_maximum.setText(Integer.toString(stepsStreaks.maximum.steps / stepsStreaks.maximum.days));
+            total_maximum.setText(Integer.toString(stepsStreaks.maximum.steps));
+
+            Date startDate = new Date(stepsStreaks.maximum.timestamp * 1000L);
+            Date endDate = DateTimeUtils.shiftByDays(startDate, stepsStreaks.maximum.days - 1); //first day is 1 not 0
+            date_maximum_value.setText(DateTimeUtils.formatDateRange(startDate, endDate));
+        }
+        if (stepsStreaks.total.steps > 0 || backgroundTaskFinished) {
+            step_streak_share_button.setVisibility(View.VISIBLE);
+            total.setVisibility(View.VISIBLE);
+            days_total_label.setText(R.string.steps_streaks_achievement_rate);
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+                //labels here have diferent meaning, so we must also add proper hint
+                days_total_label.setTooltipText(getString(R.string.steps_streaks_total_days_hint_totals));
+                days_total.setTooltipText(getString(R.string.steps_streaks_total_days_hint_totals));
+                date_total_label.setTooltipText(getString(R.string.steps_streaks_total_steps_hint_totals));
+            }
+
+            days_total.setText(String.format("%.1f%%", 0.0));
+            if (stepsStreaks.total.total_days > 0) {
+                days_total.setText(String.format("%.1f%%", (float) stepsStreaks.total.days / stepsStreaks.total.total_days * 100));
+                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+                    total_total.setTooltipText(String.format(getString(R.string.steps_streaks_total_steps_average_hint), stepsStreaks.total.steps / stepsStreaks.total.total_days));
+                }
+            }
+            if (stepsStreaks.total.timestamp > 0) {
+                date_total_value.setVisibility(View.VISIBLE);
+                date_total_value.setText(String.format(getString(R.string.steps_streaks_since_date), DateTimeUtils.formatDate(new Date(stepsStreaks.total.timestamp * 1000L))));
+            } else {
+                date_total_value.setVisibility(View.GONE);
+            }
+            total_total.setText(Integer.toString(stepsStreaks.total.steps));
+        }
+    }
+
+    protected TaskCalculateLatestStepsStreak createTaskCalculateLatestStepsStreak(String taskName, Context context, String period) {
+        return new TaskCalculateLatestStepsStreak(taskName, context, period);
+    }
+
+    public class TaskCalculateLatestStepsStreak extends DBAccess {
+        String period;
+
+        public TaskCalculateLatestStepsStreak(String taskName, Context context, String period) {
+            super(taskName, context);
+            this.period = period;
+        }
+
+        @Override
+        protected void doInBackground(DBHandler db) {
+            switch (period) {
+                case PERIOD_CURRENT:
+                    calculateStreakData(db, PERIOD_CURRENT, gbDevice, stepsGoal);
+
+                    break;
+                case PERIOD_TOTALS:
+                    calculateStreakData(db, PERIOD_TOTALS, gbDevice, stepsGoal);
+                    break;
+            }
+        }
+
+        @Override
+        protected void onPreExecute() {
+            indicate_progress(true);
+        }
+
+        @Override
+        protected void onPostExecute(Object o) {
+            super.onPostExecute(o);
+            FragmentActivity activity = getActivity();
+            if (activity != null && !activity.isFinishing() && !activity.isDestroyed()) {
+                if (period.equals(PERIOD_TOTALS)) {
+                    backgroundTaskFinished = true;
+                    indicate_progress(false);
+                }
+                populateData();
+            } else {
+                LOG.info("Not filling data because activity is not available anymore");
+            }
+        }
+    }
+
+    private void calculateStreakData(DBHandler db, String period, GBDevice device, int goal) {
+        Calendar day = Calendar.getInstance();
+        int streak_steps = 0;
+        int streak_days = 0;
+        int timestamp = 0;
+
+        int all_step_days = 0;
+        int all_streak_days = 0;
+        int all_steps = 0;
+        int firstDataTimestamp = 0;
+
+        ActivitySample firstSample = DailyTotals.getFirstSample(db, device);
+        if (firstSample == null) { //no data at all
+            return;
+        }
+        Calendar firstDate = Calendar.getInstance();
+        firstDate.setTime(DateTimeUtils.shiftByDays(new Date(firstSample.getTimestamp() * 1000L), -1));
+        //go one day back, to ensure we are before the first day, to calculate first day data as well
+
+        while (true) {
+            if (cancelTasks) {
+                GB.toast("Cancelling background jobs", Toast.LENGTH_SHORT, GB.INFO);
+                break;
+            }
+
+            DailyTotals daily_data = DailyTotals.getDailyTotalsForDevice(device, day, db);
+            int steps_this_day = (int) daily_data.getSteps();
+
+            if (steps_this_day > 0) {
+                all_step_days++;
+                all_steps += steps_this_day;
+                firstDataTimestamp = (int) (day.getTimeInMillis() / 1000);
+            }
+
+            if (steps_this_day >= goal) {
+                streak_steps += steps_this_day;
+                streak_days++;
+                all_streak_days++;
+                timestamp = (int) (day.getTimeInMillis() / 1000);
+                Date newDate = DateTimeUtils.shiftByDays(new Date(day.getTimeInMillis()), -1);
+                day.setTime(newDate);
+            } else if (DateUtils.isToday(day.getTimeInMillis())) {
+                //if goal is not reached today, we might still get our steps later
+                // so do not count this day but do not interrupt
+                Date newDate = DateTimeUtils.shiftByDays(new Date(day.getTimeInMillis()), -1);
+                day.setTime(newDate);
+            } else {
+                if (period.equals(PERIOD_CURRENT)) {
+                    stepsStreaks.current.days = streak_days;
+                    stepsStreaks.current.steps = streak_steps;
+                    stepsStreaks.current.timestamp = timestamp;
+                    return;
+                } else if (period.equals(PERIOD_TOTALS)) {
+                    //reset max
+                    if (streak_days > stepsStreaks.maximum.days) {
+                        stepsStreaks.maximum.steps = streak_steps;
+                        stepsStreaks.maximum.days = streak_days;
+                        stepsStreaks.maximum.timestamp = timestamp;
+                    }
+
+                    streak_days = 0;
+                    streak_steps = 0;
+                    Date newDate = DateTimeUtils.shiftByDays(new Date(day.getTimeInMillis()), -1);
+                    day.setTime(newDate);
+                    if (day.before(firstDate) || day.get(Calendar.YEAR) < MIN_YEAR) {
+                        //avoid rolling back too far, if the data has a timestamp too far into future
+                        //we could make this date configurable if needed for people who imported old data
+                        stepsStreaks.total.steps = all_steps;
+                        stepsStreaks.total.days = all_streak_days;
+                        stepsStreaks.total.total_days = all_step_days;
+                        stepsStreaks.total.timestamp = firstDataTimestamp;
+                        return;
+                    }
+                }
+            }
+        }
+    }
+
+    private void take_share_screenshot(Context context) {
+        final ScrollView layout = getView().findViewById(R.id.streaks_dashboard);
+        final LinearLayout sharingLayout = getView().findViewById(R.id.streaks_dashboard_inner);
+        int width = layout.getChildAt(0).getHeight();
+        int height = layout.getChildAt(0).getWidth();
+        Bitmap screenShot = getScreenShot(sharingLayout, width, height, context);
+        String fileName = FileUtils.makeValidFileName("Screenshot-" + "StepsStreak-" + DateTimeUtils.formatIso8601(new Date(Calendar.getInstance().getTimeInMillis())) + ".png");
+
+        try {
+            File targetFile = new File(FileUtils.getExternalFilesDir(), fileName);
+            FileOutputStream fOut = new FileOutputStream(targetFile);
+            screenShot.compress(Bitmap.CompressFormat.PNG, 85, fOut);
+            fOut.flush();
+            fOut.close();
+            shareScreenshot(targetFile, context);
+            GB.toast(getActivity(), "Screenshot saved", Toast.LENGTH_LONG, GB.INFO);
+        } catch (IOException e) {
+            LOG.error("Error getting screenshot", e);
+        }
+    }
+
+    private void shareScreenshot(File targetFile, Context context) {
+        Uri contentUri = FileProvider.getUriForFile(context,
+                context.getApplicationContext().getPackageName() + ".screenshot_provider", targetFile);
+        getActivity().grantUriPermission(
+                context.getApplicationContext().getPackageName(),
+                contentUri,
+                Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION
+        );
+        Intent sharingIntent = new Intent(android.content.Intent.ACTION_SEND);
+        sharingIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+        sharingIntent.setType("image/*");
+        String shareBody = getString(R.string.step_streaks_achievements_sharing_message);
+        sharingIntent.putExtra(android.content.Intent.EXTRA_SUBJECT, getString(R.string.step_streaks_achievements_sharing_title));
+        sharingIntent.putExtra(android.content.Intent.EXTRA_TEXT, shareBody);
+        sharingIntent.putExtra(Intent.EXTRA_STREAM, contentUri);
+
+        try {
+            startActivity(Intent.createChooser(sharingIntent, "Share via"));
+        } catch (ActivityNotFoundException e) {
+            Toast.makeText(context, R.string.activity_error_no_app_for_png, Toast.LENGTH_LONG).show();
+        }
+    }
+
+    public static Bitmap getScreenShot(View view, int height, int width, Context context) {
+        Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+        Canvas canvas = new Canvas(bitmap);
+        canvas.drawColor(GBApplication.getWindowBackgroundColor(context));
+        view.draw(canvas);
+        return bitmap;
+    }
+
+    private static class StepsStreak implements Parcelable {
+        private int days = 0;
+        private int steps = 0;
+        private int timestamp;
+        private int total_days = 0;
+
+        private StepsStreak() {
+
+        }
+
+        protected StepsStreak(Parcel in) {
+            days = in.readInt();
+            steps = in.readInt();
+            timestamp = in.readInt();
+            total_days = in.readInt();
+        }
+
+        public static final Creator<StepsStreak> CREATOR = new Creator<StepsStreak>() {
+            @Override
+            public StepsStreak createFromParcel(Parcel in) {
+                return new StepsStreak(in);
+            }
+
+            @Override
+            public StepsStreak[] newArray(int size) {
+                return new StepsStreak[size];
+            }
+        };
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeInt(days);
+            dest.writeInt(steps);
+            dest.writeInt(timestamp);
+            dest.writeInt(total_days);
+        }
+    }
+
+    private class StepsStreaks implements Parcelable {
+        private StepsStreak current = new StepsStreak();
+        private StepsStreak maximum = new StepsStreak();
+        private StepsStreak total = new StepsStreak();
+
+        private StepsStreaks() {
+
+        }
+
+        protected StepsStreaks(Parcel in) {
+            current = in.readParcelable(StepsStreak.class.getClassLoader());
+            maximum = in.readParcelable(StepsStreak.class.getClassLoader());
+            total = in.readParcelable(StepsStreak.class.getClassLoader());
+        }
+
+        public final Creator<StepsStreaks> CREATOR = new Creator<StepsStreaks>() {
+            @Override
+            public StepsStreaks createFromParcel(Parcel in) {
+                return new StepsStreaks(in);
+            }
+
+            @Override
+            public StepsStreaks[] newArray(int size) {
+                return new StepsStreaks[size];
+            }
+        };
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeParcelable(current, flags);
+            dest.writeParcelable(maximum, flags);
+            dest.writeParcelable(total, flags);
+        }
+    }
+}
+
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/StepsCollectionFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/StepsCollectionFragment.java
new file mode 100644
index 0000000..b900426
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/StepsCollectionFragment.java
@@ -0,0 +1,45 @@
+/*  Copyright (C) 2024 a0z, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities.charts;
+
+import android.os.Bundle;
+
+import androidx.fragment.app.FragmentManager;
+
+import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBFragment;
+import nodomain.freeyourgadget.gadgetbridge.adapter.NestedFragmentAdapter;
+import nodomain.freeyourgadget.gadgetbridge.adapter.StepsFragmentAdapter;
+
+public class StepsCollectionFragment extends AbstractCollectionFragment {
+    public StepsCollectionFragment() {
+
+    }
+
+    public static StepsCollectionFragment newInstance(final boolean allowSwipe) {
+        final StepsCollectionFragment fragment = new StepsCollectionFragment();
+        final Bundle args = new Bundle();
+        args.putBoolean(ARG_ALLOW_SWIPE, allowSwipe);
+        fragment.setArguments(args);
+        return fragment;
+    }
+
+    @Override
+    public NestedFragmentAdapter getNestedFragmentAdapter(AbstractGBFragment fragment, FragmentManager childFragmentManager) {
+        return new StepsFragmentAdapter(this, getChildFragmentManager());
+    }
+}
+
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/StepsDailyFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/StepsDailyFragment.java
new file mode 100644
index 0000000..f20b048
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/StepsDailyFragment.java
@@ -0,0 +1,243 @@
+package nodomain.freeyourgadget.gadgetbridge.activities.charts;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.os.Build;
+import android.os.Bundle;
+import android.util.TypedValue;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import androidx.annotation.ColorInt;
+import androidx.fragment.app.FragmentManager;
+
+import com.github.mikephil.charting.charts.Chart;
+import com.github.mikephil.charting.charts.LineChart;
+import com.github.mikephil.charting.components.LegendEntry;
+import com.github.mikephil.charting.components.LimitLine;
+import com.github.mikephil.charting.components.XAxis;
+import com.github.mikephil.charting.components.YAxis;
+import com.github.mikephil.charting.data.Entry;
+import com.github.mikephil.charting.data.LineData;
+import com.github.mikephil.charting.data.LineDataSet;
+import com.github.mikephil.charting.interfaces.datasets.ILineDataSet;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.dashboard.GaugeDrawer;
+import nodomain.freeyourgadget.gadgetbridge.activities.workouts.WorkoutValueFormatter;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
+
+public class StepsDailyFragment extends StepsFragment<StepsDailyFragment.StepsData> {
+    protected static final Logger LOG = LoggerFactory.getLogger(BodyEnergyFragment.class);
+
+    private TextView mDateView;
+    private ImageView stepsGauge;
+    private TextView steps;
+    private TextView distance;
+    ImageView stepsStreaksButton;
+    private LineChart stepsChart;
+
+    protected int STEPS_GOAL;
+
+    @Override
+    public void onResume() {
+        super.onResume();
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+        View rootView = inflater.inflate(R.layout.fragment_steps, container, false);
+
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+            rootView.setOnScrollChangeListener((v, scrollX, scrollY, oldScrollX, oldScrollY) -> {
+                getChartsHost().enableSwipeRefresh(scrollY == 0);
+            });
+        }
+
+        mDateView = rootView.findViewById(R.id.steps_date_view);
+        stepsGauge = rootView.findViewById(R.id.steps_gauge);
+        steps = rootView.findViewById(R.id.steps_count);
+        distance = rootView.findViewById(R.id.steps_distance);
+        stepsChart = rootView.findViewById(R.id.steps_daily_chart);
+        setupStepsChart();
+
+        STEPS_GOAL = GBApplication.getPrefs().getInt(ActivityUser.PREF_USER_STEPS_GOAL, ActivityUser.defaultUserStepsGoal);
+        refresh();
+
+        stepsStreaksButton = rootView.findViewById(R.id.steps_streaks_button);
+        stepsStreaksButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                FragmentManager fm = getActivity().getSupportFragmentManager();
+                StepStreaksDashboard stepStreaksDashboard = StepStreaksDashboard.newInstance(STEPS_GOAL, getChartsHost().getDevice());
+                stepStreaksDashboard.show(fm, "steps_streaks_dashboard");
+            }
+        });
+
+        return rootView;
+    }
+
+        @Override
+    public String getTitle() {
+        return getString(R.string.steps);
+    }
+
+    @Override
+    protected StepsDailyFragment.StepsData refreshInBackground(ChartsHost chartsHost, DBHandler db, GBDevice device) {
+        Calendar day = Calendar.getInstance();
+        day.setTime(chartsHost.getEndDate());
+        List<StepsDay> stepsDayList = getMyStepsDaysData(db, day, device);
+        final StepsDay stepsDay;
+        if (stepsDayList.isEmpty()) {
+            LOG.error("Failed to get StepsDay for {}", day);
+            stepsDay = new StepsDay(day, 0, 0);
+        } else {
+            stepsDay = stepsDayList.get(0);
+        }
+        List<? extends ActivitySample> samplesOfDay = getSamplesOfDay(db, day, 0, device);
+        return new StepsDailyFragment.StepsData(stepsDay, samplesOfDay);
+    }
+
+    @Override
+    protected void updateChartsnUIThread(StepsDailyFragment.StepsData stepsData) {
+        String formattedDate = new SimpleDateFormat("E, MMM dd").format(getEndDate());
+        mDateView.setText(formattedDate);
+
+        final int width = (int) TypedValue.applyDimension(
+                TypedValue.COMPLEX_UNIT_DIP,
+                300,
+                GBApplication.getContext().getResources().getDisplayMetrics()
+        );
+
+        stepsGauge.setImageBitmap(GaugeDrawer.drawCircleGauge(
+                width,
+                width / 15,
+                getResources().getColor(R.color.steps_color),
+                (int) stepsData.todayStepsDay.steps,
+                STEPS_GOAL,
+                getContext()
+        ));
+
+        steps.setText(String.format(String.valueOf(stepsData.todayStepsDay.steps)));
+
+        final WorkoutValueFormatter valueFormatter = new WorkoutValueFormatter();
+        distance.setText(valueFormatter.formatValue(stepsData.todayStepsDay.distance, "km"));
+
+        // Chart
+        final List<LegendEntry> legendEntries = new ArrayList<>(1);
+        final LegendEntry stepsEntry = new LegendEntry();
+        stepsEntry.label = getString(R.string.steps);
+        stepsEntry.formColor = getResources().getColor(R.color.steps_color);
+        legendEntries.add(stepsEntry);
+        stepsChart.getLegend().setTextColor(TEXT_COLOR);
+        stepsChart.getLegend().setCustom(legendEntries);
+
+        final List<Entry> lineEntries = new ArrayList<>();
+        final TimestampTranslation tsTranslation = new TimestampTranslation();
+        int sum = 0;
+        for (final ActivitySample sample : stepsData.samples) {
+            if (sample.getSteps() > 0) {
+                sum += sample.getSteps();
+            }
+            lineEntries.add(new Entry(tsTranslation.shorten(sample.getTimestamp()), sum));
+        }
+
+        stepsChart.getXAxis().setValueFormatter(new SampleXLabelFormatter(tsTranslation, "HH:mm"));
+
+        if (sum < STEPS_GOAL) {
+            stepsChart.getAxisLeft().setAxisMaximum(STEPS_GOAL);
+        } else {
+            stepsChart.getAxisLeft().resetAxisMaximum();
+        }
+
+        final LineDataSet lineDataSet = new LineDataSet(lineEntries, getString(R.string.steps));
+        lineDataSet.setColor(getResources().getColor(R.color.steps_color));
+        lineDataSet.setDrawCircles(false);
+        lineDataSet.setLineWidth(2f);
+        lineDataSet.setFillAlpha(255);
+        lineDataSet.setDrawCircles(false);
+        lineDataSet.setCircleColor(getResources().getColor(R.color.steps_color));
+        lineDataSet.setAxisDependency(YAxis.AxisDependency.LEFT);
+        lineDataSet.setDrawValues(false);
+        lineDataSet.setMode(LineDataSet.Mode.CUBIC_BEZIER);
+        lineDataSet.setDrawFilled(true);
+        lineDataSet.setFillAlpha(60);
+        lineDataSet.setFillColor(getResources().getColor(R.color.steps_color ));
+
+        final LimitLine goalLine = new LimitLine(STEPS_GOAL);
+        goalLine.setLineColor(getResources().getColor(R.color.steps_color));
+        goalLine.setLineWidth(1.5f);
+        goalLine.enableDashedLine(15f, 10f, 0f);
+        stepsChart.getAxisLeft().removeAllLimitLines();
+        stepsChart.getAxisLeft().addLimitLine(goalLine);
+        stepsChart.getAxisLeft().setAxisMaximum(Math.max(lineDataSet.getYMax(), STEPS_GOAL) + 2000);
+
+        final List<ILineDataSet> lineDataSets = new ArrayList<>();
+        lineDataSets.add(lineDataSet);
+        final LineData lineData = new LineData(lineDataSets);
+        stepsChart.setData(lineData);
+    }
+
+    @Override
+    protected void renderCharts() {
+        stepsChart.invalidate();
+    }
+
+    protected void setupLegend(Chart<?> chart) {}
+
+    private void setupStepsChart() {
+        stepsChart.getDescription().setEnabled(false);
+        stepsChart.setDoubleTapToZoomEnabled(false);
+
+        final XAxis xAxisBottom = stepsChart.getXAxis();
+        xAxisBottom.setPosition(XAxis.XAxisPosition.BOTTOM);
+        xAxisBottom.setDrawLabels(true);
+        xAxisBottom.setDrawGridLines(false);
+        xAxisBottom.setEnabled(true);
+        xAxisBottom.setDrawLimitLinesBehindData(true);
+        xAxisBottom.setTextColor(CHART_TEXT_COLOR);
+        xAxisBottom.setAxisMinimum(0f);
+        xAxisBottom.setAxisMaximum(86400f);
+        //xAxisBottom.setLabelCount(7, true);
+
+        final YAxis yAxisLeft = stepsChart.getAxisLeft();
+        yAxisLeft.setDrawGridLines(true);
+        yAxisLeft.setAxisMinimum(0);
+        yAxisLeft.setDrawTopYLabelEntry(true);
+        yAxisLeft.setEnabled(true);
+        yAxisLeft.setTextColor(CHART_TEXT_COLOR);
+
+        final YAxis yAxisRight = stepsChart.getAxisRight();
+        yAxisRight.setEnabled(true);
+        yAxisRight.setDrawLabels(false);
+        yAxisRight.setDrawGridLines(false);
+        yAxisRight.setDrawAxisLine(true);
+    }
+
+    protected static class StepsData extends ChartsData {
+        StepsDay todayStepsDay;
+        List<? extends ActivitySample> samples;
+
+        public StepsData(final StepsDay todayStepsDay, final List<? extends ActivitySample> samplesOfDay) {
+            this.todayStepsDay = todayStepsDay;
+            this.samples = samplesOfDay;
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/StepsFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/StepsFragment.java
new file mode 100644
index 0000000..c3dfba4
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/StepsFragment.java
@@ -0,0 +1,134 @@
+package nodomain.freeyourgadget.gadgetbridge.activities.charts;
+
+import android.app.Activity;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityAmount;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityAmounts;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
+import nodomain.freeyourgadget.gadgetbridge.util.LimitedQueue;
+
+abstract class StepsFragment<T extends ChartsData> extends AbstractChartFragment<T> {
+    protected static final Logger LOG = LoggerFactory.getLogger(StepsDailyFragment.class);
+
+    protected int CHART_TEXT_COLOR;
+    protected int TEXT_COLOR;
+
+    protected int BACKGROUND_COLOR;
+    protected int DESCRIPTION_COLOR;
+    protected int TOTAL_DAYS = 1;
+
+    @Override
+    public String getTitle() {
+        return getString(R.string.steps);
+    }
+
+    @Override
+    protected void init() {
+        TEXT_COLOR = GBApplication.getTextColor(requireContext());
+        CHART_TEXT_COLOR = GBApplication.getSecondaryTextColor(requireContext());
+        BACKGROUND_COLOR = GBApplication.getBackgroundColor(getContext());
+        DESCRIPTION_COLOR = GBApplication.getTextColor(getContext());
+        CHART_TEXT_COLOR = GBApplication.getSecondaryTextColor(getContext());
+    }
+
+    protected List<StepsFragment.StepsDay> getMyStepsDaysData(DBHandler db, Calendar day, GBDevice device) {
+        day = (Calendar) day.clone(); // do not modify the caller's argument
+        day.add(Calendar.DATE, -TOTAL_DAYS + 1);
+
+        List<StepsDay> daysData = new ArrayList<>();;
+        for (int counter = 0; counter < TOTAL_DAYS; counter++) {
+            long totalSteps = 0;
+            long totalDistance = 0;
+            ActivityAmounts amounts = getActivityAmountsForDay(db, day, device);
+            for (ActivityAmount amount : amounts.getAmounts()) {
+                if (amount.getTotalSteps() > 0) {
+                    totalSteps += amount.getTotalSteps();
+                }
+                if (amount.getTotalDistance() > 0) {
+                    totalDistance += amount.getTotalDistance();
+                }
+            }
+            double distance = totalDistance;
+            if (totalDistance == 0 && totalSteps > 0) {
+                // For gadgets that do not report distance, compute it from the steps
+                ActivityUser activityUser = new ActivityUser();
+                int stepLength = activityUser.getStepLengthCm();
+                distance = stepLength * totalSteps;
+            }
+            Calendar d = (Calendar) day.clone();
+            daysData.add(new StepsDay(d, totalSteps, distance / 100_000));
+            day.add(Calendar.DATE, 1);
+        }
+        return daysData;
+    }
+
+    protected ActivityAmounts getActivityAmountsForDay(DBHandler db, Calendar day, GBDevice device) {
+        LimitedQueue<Integer, ActivityAmounts> activityAmountCache = null;
+        ActivityAmounts amounts = null;
+
+        Activity activity = getActivity();
+        int key = (int) (day.getTimeInMillis() / 1000);
+        if (activity != null) {
+            activityAmountCache = ((ActivityChartsActivity) activity).mActivityAmountCache;
+            amounts = activityAmountCache.lookup(key);
+        }
+
+        if (amounts == null) {
+            ActivityAnalysis analysis = new ActivityAnalysis();
+            amounts = analysis.calculateActivityAmounts(getSamplesOfDay(db, day, 0, device));
+            if (activityAmountCache != null) {
+                activityAmountCache.add(key, amounts);
+            }
+        }
+
+        return amounts;
+    }
+
+    protected List<? extends ActivitySample> getSamplesOfDay(DBHandler db, Calendar day, int offsetHours, GBDevice device) {
+        int startTs;
+        int endTs;
+
+        day = (Calendar) day.clone(); // do not modify the caller's argument
+        day.set(Calendar.HOUR_OF_DAY, 0);
+        day.set(Calendar.MINUTE, 0);
+        day.set(Calendar.SECOND, 0);
+        day.add(Calendar.HOUR, offsetHours);
+
+        startTs = (int) (day.getTimeInMillis() / 1000);
+        endTs = startTs + 24 * 60 * 60 - 1;
+
+        return getSamples(db, device, startTs, endTs);
+    }
+
+    protected List<? extends ActivitySample> getSamples(DBHandler db, GBDevice device, int tsFrom, int tsTo) {
+        SampleProvider<? extends ActivitySample> provider = device.getDeviceCoordinator().getSampleProvider(device, db.getDaoSession());
+        return provider.getAllActivitySamples(tsFrom, tsTo);
+    }
+
+    protected static class StepsDay {
+        public long steps;
+        public double distance;
+        public Calendar day;
+
+        protected StepsDay(Calendar day, long steps, double distance) {
+            this.steps = steps;
+            this.distance = distance;
+            this.day = day;
+        }
+    }
+
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/StepsPeriodFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/StepsPeriodFragment.java
new file mode 100644
index 0000000..8e377db
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/StepsPeriodFragment.java
@@ -0,0 +1,231 @@
+package nodomain.freeyourgadget.gadgetbridge.activities.charts;
+
+import android.graphics.Color;
+import android.os.Build;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import com.github.mikephil.charting.charts.BarChart;
+import com.github.mikephil.charting.charts.Chart;
+import com.github.mikephil.charting.components.LimitLine;
+import com.github.mikephil.charting.components.XAxis;
+import com.github.mikephil.charting.components.YAxis;
+import com.github.mikephil.charting.data.BarData;
+import com.github.mikephil.charting.data.BarDataSet;
+import com.github.mikephil.charting.data.BarEntry;
+import com.github.mikephil.charting.formatter.ValueFormatter;
+
+import org.apache.commons.lang3.time.DateUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.workouts.WorkoutValueFormatter;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
+
+public class StepsPeriodFragment extends StepsFragment<StepsPeriodFragment.StepsData> {
+    protected static final Logger LOG = LoggerFactory.getLogger(BodyEnergyFragment.class);
+
+    private TextView mDateView;
+    private TextView stepsAvg;
+    private TextView stepsTotal;
+    private TextView distanceAvg;
+    private TextView distanceTotal;
+    private BarChart stepsChart;
+
+    protected int CHART_TEXT_COLOR;
+    protected int TEXT_COLOR;
+    protected int STEPS_GOAL;
+
+    protected int BACKGROUND_COLOR;
+    protected int DESCRIPTION_COLOR;
+
+    public static StepsPeriodFragment newInstance ( int totalDays ) {
+        StepsPeriodFragment fragmentFirst = new StepsPeriodFragment();
+        Bundle args = new Bundle();
+        args.putInt("totalDays", totalDays);
+        fragmentFirst.setArguments(args);
+        return fragmentFirst;
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        TOTAL_DAYS = getArguments() != null ? getArguments().getInt("totalDays") : 0;
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+        View rootView = inflater.inflate(R.layout.fragment_steps_period, container, false);
+
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+            rootView.setOnScrollChangeListener((v, scrollX, scrollY, oldScrollX, oldScrollY) -> {
+                getChartsHost().enableSwipeRefresh(scrollY == 0);
+            });
+        }
+
+        mDateView = rootView.findViewById(R.id.steps_date_view);
+        stepsChart = rootView.findViewById(R.id.steps_chart);
+        stepsAvg = rootView.findViewById(R.id.steps_avg);
+        distanceAvg = rootView.findViewById(R.id.distance_avg);
+        stepsTotal = rootView.findViewById(R.id.steps_total);
+        distanceTotal = rootView.findViewById(R.id.distance_total);
+        STEPS_GOAL = GBApplication.getPrefs().getInt(ActivityUser.PREF_USER_STEPS_GOAL, ActivityUser.defaultUserStepsGoal);
+        setupStepsChart();
+        refresh();
+
+        return rootView;
+    }
+
+    protected void setupStepsChart() {
+        stepsChart.getDescription().setEnabled(false);
+        if (TOTAL_DAYS <= 7) {
+            stepsChart.setTouchEnabled(false);
+            stepsChart.setPinchZoom(false);
+        }
+        stepsChart.setDoubleTapToZoomEnabled(false);
+        stepsChart.getLegend().setEnabled(false);
+
+        final XAxis xAxisBottom = stepsChart.getXAxis();
+        xAxisBottom.setPosition(XAxis.XAxisPosition.BOTTOM);
+        xAxisBottom.setDrawLabels(true);
+        xAxisBottom.setDrawGridLines(false);
+        xAxisBottom.setEnabled(true);
+        xAxisBottom.setDrawLimitLinesBehindData(true);
+        xAxisBottom.setTextColor(CHART_TEXT_COLOR);
+
+        final YAxis yAxisLeft = stepsChart.getAxisLeft();
+        yAxisLeft.setDrawGridLines(true);
+        yAxisLeft.setDrawTopYLabelEntry(true);
+        yAxisLeft.setEnabled(true);
+        yAxisLeft.setTextColor(CHART_TEXT_COLOR);
+        yAxisLeft.setAxisMinimum(0f);
+        final LimitLine goalLine = new LimitLine(STEPS_GOAL);
+        goalLine.setLineColor(getResources().getColor(R.color.steps_color));
+        goalLine.setLineWidth(1.5f);
+        goalLine.enableDashedLine(15f, 10f, 0f);
+        yAxisLeft.addLimitLine(goalLine);
+
+        final YAxis yAxisRight = stepsChart.getAxisRight();
+        yAxisRight.setEnabled(true);
+        yAxisRight.setDrawLabels(false);
+        yAxisRight.setDrawGridLines(false);
+        yAxisRight.setDrawAxisLine(true);
+    }
+
+        @Override
+    public String getTitle() {
+        return getString(R.string.steps);
+    }
+
+    @Override
+    protected void init() {
+        TEXT_COLOR = GBApplication.getTextColor(requireContext());
+        CHART_TEXT_COLOR = GBApplication.getSecondaryTextColor(requireContext());
+        BACKGROUND_COLOR = GBApplication.getBackgroundColor(getContext());
+        DESCRIPTION_COLOR = GBApplication.getTextColor(getContext());
+        CHART_TEXT_COLOR = GBApplication.getSecondaryTextColor(getContext());
+    }
+
+    @Override
+    protected StepsData refreshInBackground(ChartsHost chartsHost, DBHandler db, GBDevice device) {
+        Calendar day = Calendar.getInstance();
+        day.setTime(getEndDate());
+        List<StepsDay> stepsDaysData = getMyStepsDaysData(db, day, device);
+        return new StepsData(stepsDaysData);
+    }
+
+    @Override
+    protected void updateChartsnUIThread(StepsData stepsData) {
+        Date to = new Date((long) getTSEnd() * 1000);
+        Date from = DateUtils.addDays(to,-(TOTAL_DAYS - 1));
+        String toFormattedDate = new SimpleDateFormat("E, MMM dd").format(to);
+        String fromFormattedDate = new SimpleDateFormat("E, MMM dd").format(from);
+        mDateView.setText(fromFormattedDate + " - " + toFormattedDate);
+
+        stepsChart.setData(null);
+
+        List<BarEntry> entries = new ArrayList<>();
+        int counter = 0;
+        for(StepsDay day : stepsData.days) {
+            entries.add(new BarEntry(counter, day.steps));
+            counter++;
+        }
+        BarDataSet set = new BarDataSet(entries, "Steps");
+        set.setDrawValues(true);
+        set.setColors(getResources().getColor(R.color.steps_color));
+        final XAxis x = stepsChart.getXAxis();
+        x.setValueFormatter(getStepsChartDayValueFormatter(stepsData));
+        stepsChart.getAxisLeft().setAxisMaximum(Math.max(set.getYMax(), STEPS_GOAL) + 2000);
+
+        BarData barData = new BarData(set);
+        barData.setValueTextColor(Color.GRAY); //prevent tearing other graph elements with the black text. Another approach would be to hide the values cmpletely with data.setDrawValues(false);
+        barData.setValueTextSize(10f);
+        if (TOTAL_DAYS > 7) {
+            stepsChart.setRenderer(new AngledLabelsChartRenderer(stepsChart, stepsChart.getAnimator(), stepsChart.getViewPortHandler()));
+        }
+        stepsChart.setData(barData);
+        stepsAvg.setText(String.format(String.valueOf(stepsData.stepsDailyAvg)));
+        final WorkoutValueFormatter valueFormatter = new WorkoutValueFormatter();
+        distanceAvg.setText(valueFormatter.formatValue(stepsData.distanceDailyAvg, "km"));
+        stepsTotal.setText(String.format(String.valueOf(stepsData.totalSteps)));
+        distanceTotal.setText(valueFormatter.formatValue(stepsData.totalDistance, "km"));
+    }
+
+    ValueFormatter getStepsChartDayValueFormatter(StepsPeriodFragment.StepsData stepsData) {
+        return new ValueFormatter() {
+            @Override
+            public String getFormattedValue(float value) {
+                StepsPeriodFragment.StepsDay day = stepsData.days.get((int) value);
+                String pattern = TOTAL_DAYS > 7 ? "dd" : "EEE";
+                SimpleDateFormat formatLetterDay = new SimpleDateFormat(pattern, Locale.getDefault());
+                return formatLetterDay.format(new Date(day.day.getTimeInMillis()));
+            }
+        };
+    }
+
+    @Override
+    protected void renderCharts() {
+        stepsChart.invalidate();
+    }
+
+    protected void setupLegend(Chart<?> chart) {}
+
+    protected static class StepsData extends ChartsData {
+        List<StepsDay> days;
+        long stepsDailyAvg = 0;
+        double distanceDailyAvg = 0;
+        long totalSteps = 0;
+        double totalDistance = 0;
+        StepsDay todayStepsDay;
+        protected StepsData(List<StepsDay> days) {
+            this.days = days;
+            int daysCounter = 0;
+            for(StepsDay day : days) {
+                this.totalSteps += day.steps;
+                this.totalDistance += day.distance;
+                if (day.steps > 0) {
+                    daysCounter++;
+                }
+            }
+            if (daysCounter > 0) {
+                this.stepsDailyAvg = this.totalSteps / daysCounter;
+                this.distanceDailyAvg = this.totalDistance / daysCounter;
+            }
+            this.todayStepsDay = days.get(days.size() - 1);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/StressChartFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/StressChartFragment.java
new file mode 100644
index 0000000..1c095f9
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/StressChartFragment.java
@@ -0,0 +1,571 @@
+/*  Copyright (C) 2023-2024 Daniel Dakhno, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities.charts;
+
+import android.content.Context;
+import android.graphics.Color;
+import android.os.Build;
+import android.os.Bundle;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.Spanned;
+import android.text.style.RelativeSizeSpan;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import androidx.core.content.ContextCompat;
+
+import com.github.mikephil.charting.animation.Easing;
+import com.github.mikephil.charting.charts.Chart;
+import com.github.mikephil.charting.charts.LineChart;
+import com.github.mikephil.charting.charts.PieChart;
+import com.github.mikephil.charting.components.LegendEntry;
+import com.github.mikephil.charting.components.LimitLine;
+import com.github.mikephil.charting.components.XAxis;
+import com.github.mikephil.charting.components.YAxis;
+import com.github.mikephil.charting.data.Entry;
+import com.github.mikephil.charting.data.LineData;
+import com.github.mikephil.charting.data.LineDataSet;
+import com.github.mikephil.charting.data.PieData;
+import com.github.mikephil.charting.data.PieDataSet;
+import com.github.mikephil.charting.data.PieEntry;
+import com.github.mikephil.charting.formatter.ValueFormatter;
+import com.github.mikephil.charting.interfaces.datasets.ILineDataSet;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.TimeSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.StressSample;
+import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+public class StressChartFragment extends AbstractChartFragment<StressChartFragment.StressChartsData> {
+    protected static final Logger LOG = LoggerFactory.getLogger(ActivitySleepChartFragment.class);
+
+    private LineChart mStressChart;
+    private PieChart mStressLevelsPieChart;
+    private TextView stressChartRelaxedTime;
+    private TextView stressChartMildTime;
+    private TextView stressChartModerateTime;
+    private TextView stressChartHighTime;
+    private TextView stressDate;
+
+    private int BACKGROUND_COLOR;
+    private int DESCRIPTION_COLOR;
+    private int CHART_TEXT_COLOR;
+    private int LEGEND_TEXT_COLOR;
+
+    private String STRESS_AVERAGE_LABEL;
+
+    private final Prefs prefs = GBApplication.getPrefs();
+
+    private final boolean CHARTS_SLEEP_RANGE_24H = prefs.getBoolean("chart_sleep_range_24h", false);
+    private final boolean SHOW_CHARTS_AVERAGE = prefs.getBoolean("charts_show_average", true);
+
+    @Override
+    protected void init() {
+        BACKGROUND_COLOR = GBApplication.getBackgroundColor(requireContext());
+        LEGEND_TEXT_COLOR = DESCRIPTION_COLOR = GBApplication.getTextColor(requireContext());
+        CHART_TEXT_COLOR = GBApplication.getSecondaryTextColor(requireContext());
+
+        STRESS_AVERAGE_LABEL = requireContext().getString(R.string.charts_legend_stress_average);
+    }
+
+    @Override
+    protected StressChartsData refreshInBackground(final ChartsHost chartsHost, final DBHandler db, final GBDevice device) {
+        int tsEnd = getTSEnd();
+        Calendar day = Calendar.getInstance();
+        day.setTimeInMillis(tsEnd * 1000L);
+        day.set(Calendar.HOUR_OF_DAY, 0);
+        day.set(Calendar.MINUTE, 0);
+        day.set(Calendar.SECOND, 0);
+        final int tsStart = (int) (day.getTimeInMillis() / 1000);
+        tsEnd = tsStart + 24 * 60 * 60 - 1;
+        final List<? extends StressSample> samples = getSamples(db, device, tsStart, tsEnd);
+
+        LOG.info("Got {} stress samples", samples.size());
+
+        ensureStartAndEndSamples((List<StressSample>) samples, tsStart, tsEnd);
+
+        return new StressChartsDataBuilder(samples, device.getDeviceCoordinator().getStressRanges()).build();
+    }
+
+    protected LineDataSet createDataSet(final StressType stressType, final List<Entry> values) {
+        final LineDataSet lineDataSet = new LineDataSet(values, stressType.getLabel(requireContext()));
+        lineDataSet.setColor(stressType.getColor(requireContext()));
+        lineDataSet.setDrawFilled(true);
+        lineDataSet.setDrawCircles(false);
+        lineDataSet.setFillColor(stressType.getColor(requireContext()));
+        lineDataSet.setFillAlpha(255);
+        lineDataSet.setDrawValues(false);
+        lineDataSet.setValueTextColor(CHART_TEXT_COLOR);
+        lineDataSet.setAxisDependency(YAxis.AxisDependency.LEFT);
+        return lineDataSet;
+    }
+
+    @Override
+    protected void updateChartsnUIThread(final StressChartsData stressData) {
+        final PieData pieData = stressData.getPieData();
+
+        Date date = new Date((long) this.getTSEnd() * 1000);
+        String formattedDate = new SimpleDateFormat("E, MMM dd").format(date);
+        stressDate.setText(formattedDate);
+
+        Map<StressType, Integer> stressZoneTimes = stressData.getStressZoneTimes();
+        Integer relaxedTime = stressZoneTimes.get(StressType.RELAXED);
+        if (0 < relaxedTime) {
+            stressChartRelaxedTime.setText(DateTimeUtils.formatDurationHoursMinutes(relaxedTime, TimeUnit.SECONDS));
+        } else {
+            stressChartRelaxedTime.setText(R.string.stats_empty_value);
+        }
+        Integer mildTime = stressZoneTimes.get(StressType.MILD);
+        if (mildTime > 0) {
+            stressChartMildTime.setText(DateTimeUtils.formatDurationHoursMinutes(mildTime, TimeUnit.SECONDS));
+        } else {
+            stressChartMildTime.setText(R.string.stats_empty_value);
+        }
+        Integer moderateTime = stressZoneTimes.get(StressType.MODERATE);
+        if (moderateTime > 0) {
+            stressChartModerateTime.setText(DateTimeUtils.formatDurationHoursMinutes(moderateTime, TimeUnit.SECONDS));
+        } else {
+            stressChartModerateTime.setText(R.string.stats_empty_value);
+        }
+        Integer highTime = stressZoneTimes.get(StressType.HIGH);
+        if (highTime > 0) {
+            stressChartHighTime.setText(DateTimeUtils.formatDurationHoursMinutes(highTime, TimeUnit.SECONDS));
+        } else {
+            stressChartHighTime.setText(R.string.stats_empty_value);
+        }
+
+        if (stressData.getAverage() > 0) {
+            int noc = String.valueOf(stressData.getAverage()).length();
+            SpannableString pieChartCenterText = new SpannableString(stressData.getAverage() + "\n" + requireContext().getString(R.string.stress_average));
+            pieChartCenterText.setSpan(new RelativeSizeSpan(1.75f), 0, noc, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+            pieChartCenterText.setSpan(new RelativeSizeSpan(0.72f), noc, pieChartCenterText.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+            mStressLevelsPieChart.setCenterText(pieChartCenterText);
+        } else {
+            SpannableString pieChartCenterText = new SpannableString("-\n" + requireContext().getString(R.string.stress_average));
+            pieChartCenterText.setSpan(new RelativeSizeSpan(1.25f), 0, 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+            pieChartCenterText.setSpan(new RelativeSizeSpan(0.72f), 2, pieChartCenterText.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+            mStressLevelsPieChart.setCenterText(pieChartCenterText);
+        }
+        mStressLevelsPieChart.setData(pieData);
+
+        final DefaultChartsData<LineData> chartsData = stressData.getChartsData();
+        mStressChart.setData(null); // workaround for https://github.com/PhilJay/MPAndroidChart/issues/2317
+        mStressChart.getXAxis().setValueFormatter(chartsData.getXValueFormatter());
+        mStressChart.setData(chartsData.getData());
+        mStressChart.getAxisRight().removeAllLimitLines();
+
+        if (stressData.getAverage() > 0) {
+            final LimitLine averageLine = new LimitLine(stressData.getAverage());
+            averageLine.setLineColor(Color.GRAY);
+            averageLine.setLineWidth(1.5f);
+            averageLine.enableDashedLine(15f, 10f, 0f);
+            mStressChart.getAxisRight().addLimitLine(averageLine);
+        }
+    }
+
+    @Override
+    public String getTitle() {
+        return getString(R.string.menuitem_stress);
+    }
+
+    @Override
+    public View onCreateView(final LayoutInflater inflater,
+                             final ViewGroup container,
+                             final Bundle savedInstanceState) {
+        final View rootView = inflater.inflate(R.layout.fragment_stresschart, container, false);
+
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+            rootView.setOnScrollChangeListener((v, scrollX, scrollY, oldScrollX, oldScrollY) -> {
+                getChartsHost().enableSwipeRefresh(scrollY == 0);
+            });
+        }
+
+        mStressChart = rootView.findViewById(R.id.stress_line_chart);
+        mStressLevelsPieChart = rootView.findViewById(R.id.stress_pie_chart);
+        stressChartRelaxedTime = rootView.findViewById(R.id.stress_chart_relaxed_time);
+        stressChartMildTime = rootView.findViewById(R.id.stress_chart_mild_time);
+        stressChartModerateTime = rootView.findViewById(R.id.stress_chart_moderate_time);
+        stressChartHighTime = rootView.findViewById(R.id.stress_chart_high_time);
+        stressDate = rootView.findViewById(R.id.stress_date);
+
+        setupLineChart();
+        setupPieChart();
+
+        // refresh immediately instead of use refreshIfVisible(), for perceived performance
+        refresh();
+
+        return rootView;
+    }
+
+    private void setupPieChart() {
+        mStressLevelsPieChart.setBackgroundColor(BACKGROUND_COLOR);
+        mStressLevelsPieChart.getDescription().setTextColor(DESCRIPTION_COLOR);
+        mStressLevelsPieChart.setEntryLabelColor(DESCRIPTION_COLOR);
+        mStressLevelsPieChart.getDescription().setText("");
+        mStressLevelsPieChart.setNoDataText("");
+        mStressLevelsPieChart.setTouchEnabled(false);
+        mStressLevelsPieChart.setCenterTextColor(GBApplication.getTextColor(getContext()));
+        mStressLevelsPieChart.setCenterTextSize(18f);
+        mStressLevelsPieChart.setHoleColor(getContext().getResources().getColor(R.color.transparent));
+        mStressLevelsPieChart.setHoleRadius(85);
+        mStressLevelsPieChart.setDrawEntryLabels(false);
+        mStressLevelsPieChart.getLegend().setEnabled(false);
+    }
+
+    private void setupLineChart() {
+        mStressChart.setBackgroundColor(BACKGROUND_COLOR);
+        mStressChart.getDescription().setTextColor(DESCRIPTION_COLOR);
+        configureBarLineChartDefaults(mStressChart);
+
+        final XAxis x = mStressChart.getXAxis();
+        x.setDrawLabels(true);
+        x.setDrawGridLines(false);
+        x.setEnabled(true);
+        x.setTextColor(CHART_TEXT_COLOR);
+        x.setDrawLimitLinesBehindData(true);
+        x.setAxisMinimum(0f);
+        x.setAxisMaximum(86400f);
+
+        final YAxis yAxisLeft = mStressChart.getAxisLeft();
+        yAxisLeft.setDrawGridLines(true);
+        yAxisLeft.setAxisMaximum(100f);
+        yAxisLeft.setAxisMinimum(0);
+        yAxisLeft.setDrawTopYLabelEntry(false);
+        yAxisLeft.setTextColor(CHART_TEXT_COLOR);
+        yAxisLeft.setEnabled(true);
+
+        final YAxis yAxisRight = mStressChart.getAxisRight();
+        yAxisRight.setDrawGridLines(false);
+        yAxisRight.setEnabled(true);
+        yAxisRight.setDrawLabels(false);
+        yAxisRight.setDrawTopYLabelEntry(true);
+        yAxisRight.setTextColor(CHART_TEXT_COLOR);
+        yAxisRight.setAxisMaximum(100f);
+        yAxisRight.setAxisMinimum(0);
+    }
+
+    @Override
+    protected void setupLegend(final Chart<?> chart) {
+        final List<LegendEntry> legendEntries = new ArrayList<>(StressType.values().length + 1);
+
+        for (final StressType stressType : StressType.values()) {
+            final LegendEntry entry = new LegendEntry();
+            entry.label = stressType.getLabel(requireContext());
+            entry.formColor = stressType.getColor(requireContext());
+            legendEntries.add(entry);
+        }
+
+        if (!CHARTS_SLEEP_RANGE_24H && SHOW_CHARTS_AVERAGE) {
+            final LegendEntry averageEntry = new LegendEntry();
+            averageEntry.label = STRESS_AVERAGE_LABEL;
+            averageEntry.formColor = Color.GRAY;
+            legendEntries.add(averageEntry);
+        }
+
+        chart.getLegend().setCustom(legendEntries);
+        chart.getLegend().setTextColor(LEGEND_TEXT_COLOR);
+    }
+
+    @Override
+    protected void renderCharts() {
+        mStressChart.animateX(ANIM_TIME, Easing.EaseInOutQuart);
+        mStressLevelsPieChart.invalidate();
+    }
+
+    private List<? extends StressSample> getSamples(final DBHandler db, final GBDevice device, int tsStart, int tsEnd) {
+        final DeviceCoordinator coordinator = device.getDeviceCoordinator();
+        final TimeSampleProvider<? extends StressSample> sampleProvider = coordinator.getStressSampleProvider(device, db.getDaoSession());
+        return sampleProvider.getAllSamples(tsStart * 1000L, tsEnd * 1000L);
+    }
+
+    protected void ensureStartAndEndSamples(final List<StressSample> samples, int tsStart, int tsEnd) {
+        if (samples == null || samples.isEmpty()) {
+            return;
+        }
+
+        final long tsEndMillis = tsEnd * 1000L;
+        final long tsStartMillis = tsStart * 1000L;
+
+        final StressSample lastSample = samples.get(samples.size() - 1);
+        if (lastSample.getTimestamp() < tsEndMillis) {
+            samples.add(new EmptyStressSample(tsEndMillis));
+        }
+
+        final StressSample firstSample = samples.get(0);
+        if (firstSample.getTimestamp() > tsStartMillis) {
+            samples.add(0, new EmptyStressSample(tsStartMillis));
+        }
+    }
+
+    protected static final class EmptyStressSample implements StressSample {
+        private final long ts;
+
+        public EmptyStressSample(final long ts) {
+            this.ts = ts;
+        }
+
+        @Override
+        public Type getType() {
+            return Type.AUTOMATIC;
+        }
+
+        @Override
+        public int getStress() {
+            return -1;
+        }
+
+        @Override
+        public long getTimestamp() {
+            return ts;
+        }
+    }
+
+    protected class StressChartsDataBuilder {
+        private static final int UNKNOWN_VAL = 2;
+
+        private final List<? extends StressSample> samples;
+        private final int[] stressRanges;
+
+        private final TimestampTranslation tsTranslation = new TimestampTranslation();
+
+        private final Map<StressType, List<Entry>> lineEntriesPerLevel = new HashMap<>();
+        private final Map<StressType, Integer> accumulator = new HashMap<>();
+
+        int previousTs;
+        int currentTypeStartTs;
+        StressType previousStressType;
+        long averageSum;
+        long averageNumSamples;
+
+        public StressChartsDataBuilder(final List<? extends StressSample> samples, final int[] stressRanges) {
+            this.samples = samples;
+            this.stressRanges = stressRanges;
+        }
+
+        private void reset() {
+            tsTranslation.reset();
+            lineEntriesPerLevel.clear();
+            accumulator.clear();
+            for (final StressType stressType : StressType.values()) {
+                lineEntriesPerLevel.put(stressType, new ArrayList<>());
+                accumulator.put(stressType, 0);
+            }
+            previousTs = 0;
+            currentTypeStartTs = 0;
+            previousStressType = StressType.UNKNOWN;
+        }
+
+        private void processSamples() {
+            reset();
+
+            for (final StressSample sample : samples) {
+                processSample(sample);
+            }
+
+            // Add the last block, if any
+            if (currentTypeStartTs != previousTs) {
+                set(previousTs, previousStressType, samples.get(samples.size() - 1).getStress());
+            }
+        }
+
+        private void processSample(final StressSample sample) {
+            //LOG.debug("Processing sample {} {}", sdf.format(new Date(sample.getTimestamp())), sample.getStress());
+
+            final StressType stressType = StressType.fromStress(sample.getStress(), stressRanges);
+            final int ts = tsTranslation.shorten((int) (sample.getTimestamp() / 1000L));
+
+            if (ts == 0) {
+                // First sample
+                previousTs = ts;
+                currentTypeStartTs = ts;
+                previousStressType = stressType;
+                set(ts, stressType, sample.getStress());
+                return;
+            }
+
+            if (ts - previousTs > 60 * 10) {
+                // More than 15 minutes since last sample
+                // Set to unknown right after the last sample we got until the current time
+                int lastEndTs = Math.min(previousTs + 60 * 5, ts - 1);
+                set(lastEndTs, StressType.UNKNOWN, UNKNOWN_VAL);
+                set(ts - 1, StressType.UNKNOWN, UNKNOWN_VAL);
+            }
+
+            if (!stressType.equals(previousStressType)) {
+                currentTypeStartTs = ts;
+            }
+
+            set(ts, stressType, sample.getStress());
+
+            accumulator.put(stressType, accumulator.get(stressType) + 60);
+
+            if (stressType != StressType.UNKNOWN) {
+                averageSum += sample.getStress();
+                averageNumSamples++;
+            }
+
+            previousStressType = stressType;
+            previousTs = ts;
+        }
+
+        private void set(final int ts, final StressType stressType, final int stress) {
+            for (final Map.Entry<StressType, List<Entry>> stressTypeListEntry : lineEntriesPerLevel.entrySet()) {
+                if (stressTypeListEntry.getKey() == stressType) {
+                    stressTypeListEntry.getValue().add(new Entry(ts, stress));
+                } else {
+                    stressTypeListEntry.getValue().add(new Entry(ts, 0));
+                }
+            }
+        }
+
+        public StressChartsData build() {
+            processSamples();
+
+            final List<ILineDataSet> lineDataSets = new ArrayList<>();
+            final List<PieEntry> pieEntries = new ArrayList<>();
+            final List<Integer> pieColors = new ArrayList<>();
+            final Map<StressType, Integer> stressZoneTimes = new HashMap<>();
+
+            for (final StressType stressType : StressType.values()) {
+                final List<Entry> stressEntries = lineEntriesPerLevel.get(stressType);
+                lineDataSets.add(createDataSet(stressType, stressEntries));
+
+                final Integer stressTime = accumulator.get(stressType);
+                stressZoneTimes.put(stressType, stressTime);
+                if (stressType != StressType.UNKNOWN && stressTime != null && stressTime != 0) {
+                    pieEntries.add(new PieEntry(stressTime, stressType.getLabel(requireContext())));
+                    pieColors.add(stressType.getColor(requireContext()));
+                }
+            }
+
+            if (pieEntries.isEmpty()) {
+                pieEntries.add(new PieEntry(1));
+                pieColors.add(getResources().getColor(R.color.gauge_line_color));
+            }
+
+            final PieDataSet pieDataSet = new PieDataSet(pieEntries, "");
+            pieDataSet.setValueFormatter(new ValueFormatter() {
+                @Override
+                public String getFormattedValue(float value) {
+                    return DateTimeUtils.formatDurationHoursMinutes((long) value, TimeUnit.SECONDS);
+                }
+            });
+            pieDataSet.setColors(pieColors);
+            pieDataSet.setValueTextColor(DESCRIPTION_COLOR);
+            pieDataSet.setValueTextSize(13f);
+            pieDataSet.setXValuePosition(PieDataSet.ValuePosition.OUTSIDE_SLICE);
+            pieDataSet.setYValuePosition(PieDataSet.ValuePosition.OUTSIDE_SLICE);
+            pieDataSet.setDrawValues(false);
+            pieDataSet.setSliceSpace(2f);
+            final PieData pieData = new PieData(pieDataSet);
+
+            final LineData lineData = new LineData(lineDataSets);
+            final ValueFormatter xValueFormatter = new SampleXLabelFormatter(tsTranslation, "HH:mm");
+            final DefaultChartsData<LineData> chartsData = new DefaultChartsData<>(lineData, xValueFormatter);
+            return new StressChartsData(pieData, chartsData, Math.round((float) averageSum / averageNumSamples), stressZoneTimes);
+        }
+    }
+
+    protected static class StressChartsData extends ChartsData {
+        private final PieData pieData;
+        private final DefaultChartsData<LineData> chartsData;
+        private final int average;
+        private Map<StressType, Integer> stressZoneTimes;
+
+        public StressChartsData(final PieData pieData, final DefaultChartsData<LineData> chartsData, final int average, Map<StressType, Integer> stressZoneTimes) {
+            this.pieData = pieData;
+            this.chartsData = chartsData;
+            this.average = average;
+            this.stressZoneTimes = stressZoneTimes;
+        }
+
+        public Map<StressType, Integer> getStressZoneTimes() {
+            return stressZoneTimes;
+        }
+
+        public PieData getPieData() {
+            return pieData;
+        }
+
+        public DefaultChartsData<LineData> getChartsData() {
+            return chartsData;
+        }
+
+        public int getAverage() {
+            return average;
+        }
+    }
+
+    public enum StressType {
+        UNKNOWN(R.string.unknown, R.color.chart_stress_unknown),
+        RELAXED(R.string.stress_relaxed, R.color.chart_stress_relaxed),
+        MILD(R.string.stress_mild, R.color.chart_stress_mild),
+        MODERATE(R.string.stress_moderate, R.color.chart_stress_moderate),
+        HIGH(R.string.stress_high, R.color.chart_stress_high),
+        ;
+
+        private final int labelId;
+        private final int colorId;
+
+        StressType(final int labelId, final int colorId) {
+            this.labelId = labelId;
+            this.colorId = colorId;
+        }
+
+        public String getLabel(final Context context) {
+            return context.getString(labelId);
+        }
+
+        public int getColor(final Context context) {
+            return ContextCompat.getColor(context, colorId);
+        }
+
+        public static StressType fromStress(final int stress, final int[] stressRanges) {
+            if (stress < stressRanges[0]) {
+                return StressType.UNKNOWN;
+            } else if (stress < stressRanges[1]) {
+                return StressType.RELAXED;
+            } else if (stress < stressRanges[2]) {
+                return StressType.MILD;
+            } else if (stress < stressRanges[3]) {
+                return StressType.MODERATE;
+            } else {
+                return StressType.HIGH;
+            }
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/TemperatureChartFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/TemperatureChartFragment.java
new file mode 100644
index 0000000..7cea483
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/TemperatureChartFragment.java
@@ -0,0 +1,245 @@
+/*  Copyright (C) 2023 Alicia Hormann
+
+    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 <http://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities.charts;
+
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.github.mikephil.charting.animation.Easing;
+import com.github.mikephil.charting.charts.Chart;
+import com.github.mikephil.charting.charts.LineChart;
+import com.github.mikephil.charting.components.XAxis;
+import com.github.mikephil.charting.components.YAxis;
+import com.github.mikephil.charting.data.Entry;
+import com.github.mikephil.charting.data.LineData;
+import com.github.mikephil.charting.data.LineDataSet;
+import com.github.mikephil.charting.formatter.ValueFormatter;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.text.DecimalFormat;
+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 nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.TimeSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.TemperatureSample;
+
+public class TemperatureChartFragment extends AbstractChartFragment<TemperatureChartFragment.TemperatureChartsData> {
+    protected static final Logger LOG = LoggerFactory.getLogger(TemperatureChartFragment.class);
+
+    private LineChart mTemperatureChart;
+    private int BACKGROUND_COLOR;
+    private int DESCRIPTION_COLOR;
+    private int CHART_TEXT_COLOR;
+
+    protected final int TOTAL_DAYS = getRangeDays();
+
+
+    @Override
+    protected void init() {
+        BACKGROUND_COLOR = GBApplication.getBackgroundColor(requireContext());
+        DESCRIPTION_COLOR = GBApplication.getTextColor(requireContext());
+        CHART_TEXT_COLOR = GBApplication.getSecondaryTextColor(requireContext());
+
+    }
+    private int getRangeDays() {
+        if (GBApplication.getPrefs().getBoolean("charts_range", true)) {
+            return 30;
+        } else {
+            return 7;
+        }
+    }
+
+    @Override
+    protected TemperatureChartsData refreshInBackground(final ChartsHost chartsHost, final DBHandler db, final GBDevice device) {
+        final List<? extends TemperatureSample> samples = getSamples(db, device);
+
+        LOG.info("Got {} temperature samples", samples.size());
+        return new TemperatureChartsDataBuilder(samples).build();
+    }
+
+
+    @Override
+    protected void updateChartsnUIThread(final TemperatureChartsData temperatureData) {
+        mTemperatureChart.setData(null); // workaround for https://github.com/PhilJay/MPAndroidChart/issues/2317
+        mTemperatureChart.getXAxis().setValueFormatter(temperatureData.getXValueFormatter());
+        mTemperatureChart.getXAxis().setAvoidFirstLastClipping(true);
+
+        // Using approximately the range of survivable body-temperatures (in celsius), rounded to multiples of 5
+        mTemperatureChart.getAxisLeft().setAxisMinimum(30f);
+        mTemperatureChart.getAxisLeft().setAxisMaximum(45f);
+
+        mTemperatureChart.setData(temperatureData.getData());
+    }
+
+    @Override
+    public String getTitle() {
+        return getString(R.string.menuitem_temperature);
+    }
+
+    @Override
+    public View onCreateView(final LayoutInflater inflater,
+                             final ViewGroup container,
+                             final Bundle savedInstanceState) {
+        final View rootView = inflater.inflate(R.layout.fragment_temperaturechart, container, false);
+
+        mTemperatureChart = rootView.findViewById(R.id.temperature_line_chart);
+
+        setupLineChart();
+
+        // refresh immediately instead of use refreshIfVisible(), for perceived performance
+        refresh();
+
+        return rootView;
+    }
+
+    private void setupLineChart() {
+        mTemperatureChart.setBackgroundColor(BACKGROUND_COLOR);
+        mTemperatureChart.getDescription().setTextColor(DESCRIPTION_COLOR);
+        configureBarLineChartDefaults(mTemperatureChart);
+
+        final XAxis x = mTemperatureChart.getXAxis();
+        x.setDrawLabels(true);
+        x.setDrawGridLines(false);
+        x.setEnabled(true);
+        x.setTextColor(CHART_TEXT_COLOR);
+        x.setDrawLimitLinesBehindData(true);
+
+        final YAxis yAxisLeft = mTemperatureChart.getAxisLeft();
+        yAxisLeft.setDrawGridLines(true);
+        yAxisLeft.setDrawTopYLabelEntry(false);
+        yAxisLeft.setTextColor(CHART_TEXT_COLOR);
+        yAxisLeft.setEnabled(true);
+
+        final YAxis yAxisRight = mTemperatureChart.getAxisRight();
+        yAxisRight.setDrawGridLines(false);
+        yAxisRight.setDrawLabels(false);
+    }
+
+    @Override
+    protected void setupLegend(final Chart<?> chart) {
+    }
+
+    @Override
+    protected void renderCharts() {
+        mTemperatureChart.animateX(ANIM_TIME, Easing.EaseInOutQuart);
+    }
+
+    @Override
+    protected boolean isSingleDay() {
+        return false;
+    }
+
+    @Override
+    protected int getTSStart() {
+        return getTSEnd() - TOTAL_DAYS*24*60*60;
+    }
+
+    private List<? extends TemperatureSample> getSamples(final DBHandler db, final GBDevice device) {
+        final int tsStart = getTSStart();
+        final int tsEnd = getTSEnd();
+        final DeviceCoordinator coordinator = device.getDeviceCoordinator();
+        final TimeSampleProvider<? extends TemperatureSample> sampleProvider = coordinator.getTemperatureSampleProvider(device, db.getDaoSession());
+        return sampleProvider.getAllSamples(tsStart * 1000L, tsEnd * 1000L);
+    }
+
+    protected class TemperatureChartsDataBuilder {
+        private final List<? extends TemperatureSample> samples;
+
+        public TemperatureChartsDataBuilder(final List<? extends TemperatureSample> samples) {
+            this.samples = samples;
+        }
+
+        public TemperatureChartsData build() {
+            TimestampTranslation tsTranslation = new TimestampTranslation();
+            List<Entry> entries = new ArrayList<Entry>();
+            long firstTs = 0;
+
+            for (TemperatureSample sample : samples) {
+                int timestamp_in_seconds = (int) (sample.getTimestamp() / 1000L);
+                entries.add(new Entry(tsTranslation.shorten(timestamp_in_seconds), sample.getTemperature()));
+                if (firstTs == 0) {
+                    firstTs = sample.getTimestamp();
+                }
+            }
+
+            LineDataSet dataSet = new LineDataSet(entries, getString(R.string.menuitem_temperature));
+            dataSet.setLineWidth(2.2f);
+            dataSet.setMode(LineDataSet.Mode.HORIZONTAL_BEZIER);
+            dataSet.setCubicIntensity(0.1f);
+            dataSet.setDrawCircles(true);
+            dataSet.setCircleRadius(5f);
+            dataSet.setDrawCircleHole(false);
+            dataSet.setDrawValues(true);
+            dataSet.setValueTextSize(10f);
+            dataSet.setValueTextColor(CHART_TEXT_COLOR);
+            dataSet.setHighlightEnabled(true);
+            dataSet.setValueFormatter(new MyValueFormatter());
+            LineData lineData = new LineData(dataSet);
+
+            return new TemperatureChartsData(lineData, tsTranslation);
+        }
+    }
+
+    protected static class TemperatureChartsData extends DefaultChartsData<LineData> {
+        public TemperatureChartsData(LineData lineData, TimestampTranslation tsTranslation) {
+            super(lineData, new dateFormatter(tsTranslation));
+        }
+    }
+
+
+    protected static class dateFormatter extends ValueFormatter {
+        private final TimestampTranslation tsTranslation;
+        SimpleDateFormat annotationDateFormat = new SimpleDateFormat("dd.MM.");
+        Calendar cal = GregorianCalendar.getInstance();
+
+        public dateFormatter(TimestampTranslation tsTranslation) {
+            this.tsTranslation = tsTranslation;
+        }
+
+        @Override
+        public String getFormattedValue(float value) {
+            cal.clear();
+            int ts = (int) value;
+            cal.setTimeInMillis(tsTranslation.toOriginalValue(ts) * 1000L);
+            Date date = cal.getTime();
+            return annotationDateFormat.format(date);
+        }
+    }
+
+    protected static class MyValueFormatter extends ValueFormatter {
+        private final DecimalFormat formatter = new DecimalFormat("0.00");
+
+        @Override
+        public String getPointLabel(Entry entry) {
+            return formatter.format(entry.getY());
+        }
+    }
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/TemperatureDailyFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/TemperatureDailyFragment.java
new file mode 100644
index 0000000..3cded0d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/TemperatureDailyFragment.java
@@ -0,0 +1,234 @@
+package nodomain.freeyourgadget.gadgetbridge.activities.charts;
+
+import android.graphics.Color;
+import android.os.Build;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import com.github.mikephil.charting.charts.Chart;
+import com.github.mikephil.charting.charts.LineChart;
+import com.github.mikephil.charting.components.LegendEntry;
+import com.github.mikephil.charting.components.LimitLine;
+import com.github.mikephil.charting.components.XAxis;
+import com.github.mikephil.charting.components.YAxis;
+import com.github.mikephil.charting.data.Entry;
+import com.github.mikephil.charting.data.LineData;
+import com.github.mikephil.charting.data.LineDataSet;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.HeartRateUtils;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.TimeSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.TemperatureSample;
+import nodomain.freeyourgadget.gadgetbridge.util.Accumulator;
+
+public class TemperatureDailyFragment extends AbstractChartFragment<TemperatureDailyFragment.TemperatureChartData> {
+
+    protected static final Logger LOG = LoggerFactory.getLogger(TemperatureDailyFragment.class);
+
+    protected int TEMPERATURE_COLOR;
+    protected int CHART_TEXT_COLOR;
+    protected int BACKGROUND_COLOR;
+    protected int DESCRIPTION_COLOR;
+    protected int LEGEND_TEXT_COLOR;
+
+    private TextView dateView;
+    private TextView tempAverage;
+    private TextView tempMinimum;
+    private TextView tempMaximum;
+    private LineChart tempLineChart;
+
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+        View rootView = inflater.inflate(R.layout.fragment_temperature, container, false);
+
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+            rootView.setOnScrollChangeListener((v, scrollX, scrollY, oldScrollX, oldScrollY) -> {
+                getChartsHost().enableSwipeRefresh(scrollY == 0);
+            });
+        }
+
+        dateView = rootView.findViewById(R.id.temp_date_view);
+        tempLineChart = rootView.findViewById(R.id.temp_line_chart);
+        tempAverage = rootView.findViewById(R.id.temp_average);
+        tempMinimum = rootView.findViewById(R.id.temp_minimum);
+        tempMaximum = rootView.findViewById(R.id.temp_maximum);
+
+        setupChart();
+        refresh();
+        setupLegend(tempLineChart);
+
+        return rootView;
+    }
+
+    @Override
+    public String getTitle() {
+        return getString(R.string.menuitem_temperature);
+    }
+
+    @Override
+    protected void init() {
+        CHART_TEXT_COLOR = GBApplication.getSecondaryTextColor(requireContext());
+        DESCRIPTION_COLOR = LEGEND_TEXT_COLOR = GBApplication.getTextColor(requireContext());
+        TEMPERATURE_COLOR = GBApplication.getSecondaryTextColor(requireContext());
+    }
+
+    @Override
+    protected TemperatureChartData refreshInBackground(ChartsHost chartsHost, DBHandler db, GBDevice device) {
+        int startTs = getTSStart();
+        int endTs = getTSEnd();
+
+        final DeviceCoordinator coordinator = device.getDeviceCoordinator();
+        final TimeSampleProvider<? extends TemperatureSample> sampleProvider = coordinator.getTemperatureSampleProvider(device, db.getDaoSession());
+
+        final List<? extends TemperatureSample> samples = sampleProvider.getAllSamples(startTs  * 1000L, endTs  * 1000L);
+        LOG.info("Got {} temperature samples", samples.size());
+
+        return new TemperatureChartData(samples);
+    }
+
+    @Override
+    protected void renderCharts() {
+        tempLineChart.invalidate();
+    }
+
+    private void setupChart() {
+        tempLineChart.setBackgroundColor(BACKGROUND_COLOR);
+        tempLineChart.getDescription().setTextColor(DESCRIPTION_COLOR);
+        tempLineChart.getDescription().setEnabled(false);
+
+
+        XAxis x = tempLineChart.getXAxis();
+        x.setDrawLabels(true);
+        x.setDrawGridLines(false);
+        x.setEnabled(true);
+        x.setTextColor(CHART_TEXT_COLOR);
+        x.setDrawLimitLinesBehindData(true);
+        x.setPosition(XAxis.XAxisPosition.BOTTOM);
+        x.setAxisMinimum(0f);
+        x.setAxisMaximum(86400f);
+
+        YAxis y = tempLineChart.getAxisLeft();
+        y.setDrawGridLines(false);
+        y.setDrawTopYLabelEntry(true);
+        y.setTextColor(CHART_TEXT_COLOR);
+        y.setEnabled(true);
+        y.setAxisMaximum(HeartRateUtils.getInstance().getMaxHeartRate());
+        y.setAxisMinimum(HeartRateUtils.getInstance().getMinHeartRate());
+
+        YAxis yAxisRight = tempLineChart.getAxisRight();
+        yAxisRight.setDrawGridLines(false);
+        yAxisRight.setDrawLabels(true);
+        yAxisRight.setDrawTopYLabelEntry(true);
+        yAxisRight.setTextColor(CHART_TEXT_COLOR);
+        yAxisRight.setAxisMaximum(HeartRateUtils.getInstance().getMaxHeartRate());
+        yAxisRight.setAxisMinimum(HeartRateUtils.getInstance().getMinHeartRate());
+
+        refresh();
+    }
+
+    @Override
+    protected void setupLegend(Chart<?> chart) {
+        List<LegendEntry> legendEntries = new ArrayList<>(1);
+        LegendEntry dataEntry = new LegendEntry();
+        dataEntry.label = getTitle();
+        dataEntry.formColor = TEMPERATURE_COLOR;
+        legendEntries.add(dataEntry);
+
+        if (GBApplication.getPrefs().getBoolean("charts_show_average", true)) {
+            LegendEntry dataAverageEntry = new LegendEntry();
+            dataAverageEntry.label = getString(R.string.hr_average);
+            dataAverageEntry.formColor = Color.CYAN;
+            legendEntries.add(dataAverageEntry);
+        }
+
+        chart.getLegend().setCustom(legendEntries);
+        chart.getLegend().setTextColor(LEGEND_TEXT_COLOR);
+        chart.getLegend().setWordWrapEnabled(true);
+    }
+
+    @Override
+    protected void updateChartsnUIThread(TemperatureDailyFragment.TemperatureChartData data) {
+        Date date = new Date(getTSEnd() * 1000L);
+        String formattedDate = new SimpleDateFormat("E, MMM dd").format(date);
+        dateView.setText(formattedDate);
+
+        final TimestampTranslation tsTranslation = new TimestampTranslation();
+        final List<Entry> lineEntries = new ArrayList<>();
+        List<? extends TemperatureSample> samples = data.samples;
+        final Accumulator accumulator = new Accumulator();
+
+        for (int i =0; i < samples.size(); i++) {
+            TemperatureSample sample = samples.get(i);
+            int timestamp_in_seconds = (int) (sample.getTimestamp() / 1000L);
+            lineEntries.add(new Entry(tsTranslation.shorten(timestamp_in_seconds), sample.getTemperature()));
+            accumulator.add(sample.getTemperature());
+        }
+
+        LineDataSet dataSet = new LineDataSet(lineEntries, "Heart Rate");
+        dataSet.setLineWidth(1.5f);
+        dataSet.setMode(LineDataSet.Mode.HORIZONTAL_BEZIER);
+        dataSet.setCubicIntensity(0.1f);
+        dataSet.setDrawCircles(false);
+        dataSet.setDrawValues(true);
+        dataSet.setAxisDependency(YAxis.AxisDependency.RIGHT);
+        dataSet.setColor(TEMPERATURE_COLOR);
+        dataSet.setValueTextColor(CHART_TEXT_COLOR);
+
+        final double average = accumulator.getCount() > 0 ? accumulator.getAverage() : -1;
+        final double minimum = accumulator.getCount() > 0 ? accumulator.getMin() : -1;
+        final double maximum = accumulator.getCount() > 0 ? accumulator.getMax() : -1;
+
+        tempAverage.setText(average > 0 ? String.format(Locale.ROOT, "%.1f", average) : "-");
+        tempMinimum.setText(minimum > 0 ? String.format(Locale.ROOT, "%.1f", minimum) : "-");
+        tempMaximum.setText(maximum > 0 ? String.format(Locale.ROOT, "%.1f", maximum) : "-");
+
+        if (minimum > 0) {
+            long axisMin = Math.max(Math.round(minimum) - 3, 0);
+            tempLineChart.getAxisLeft().setAxisMinimum(axisMin);
+            tempLineChart.getAxisRight().setAxisMinimum(axisMin);
+        }
+        if (maximum > 0) {
+            tempLineChart.getAxisLeft().setAxisMaximum(Math.round(maximum) + 3);
+            tempLineChart.getAxisRight().setAxisMaximum(Math.round(maximum) + 3);
+        }
+
+        tempLineChart.getXAxis().setValueFormatter(new SampleXLabelFormatter(tsTranslation, "HH:mm"));
+        tempLineChart.setData(new LineData(dataSet));
+
+        tempLineChart.getAxisLeft().removeAllLimitLines();
+
+        if (average > 0 && GBApplication.getPrefs().getBoolean("charts_show_average", true)) {
+            final LimitLine averageLine = new LimitLine((float) average);
+            averageLine.setLineWidth(1.5f);
+            averageLine.enableDashedLine(15f, 10f, 0f);
+            averageLine.setLineColor(Color.CYAN);
+            tempLineChart.getAxisLeft().addLimitLine(averageLine);
+        }
+
+    }
+
+    protected static class TemperatureChartData extends ChartsData {
+        public List<? extends TemperatureSample> samples;
+
+        protected TemperatureChartData(List<? extends TemperatureSample> samples) {
+            this.samples = samples;
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/TimestampTranslation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/TimestampTranslation.java
new file mode 100644
index 0000000..8cc36aa
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/TimestampTranslation.java
@@ -0,0 +1,48 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities.charts;
+
+/**
+ * Awkward class that helps in translating long timestamp
+ * values to float (sic!) values. It basically rebases all
+ * timestamps to a base (the very first) timestamp value.
+ * <p>
+ * It does this so that the large timestamp values can be used
+ * floating point values, where the mantissa is just 24 bits.
+ */
+public class TimestampTranslation {
+    private int tsOffset = -1;
+
+    public int shorten(int timestamp) {
+        if (tsOffset == -1) {
+            tsOffset = timestamp;
+            return 0;
+        }
+        return timestamp - tsOffset;
+    }
+
+    public int toOriginalValue(int timestamp) {
+        if (tsOffset == -1) {
+            return timestamp;
+        }
+        return timestamp + tsOffset;
+    }
+
+    public void reset() {
+        tsOffset = -1;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/TimestampValueFormatter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/TimestampValueFormatter.java
new file mode 100644
index 0000000..6d82079
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/TimestampValueFormatter.java
@@ -0,0 +1,50 @@
+/*  Copyright (C) 2016-2024 Andreas Shimokawa, Carsten Pfeiffer
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities.charts;
+
+import com.github.mikephil.charting.formatter.ValueFormatter;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+
+public class TimestampValueFormatter extends ValueFormatter {
+    private final Calendar cal;
+    //    private DateFormat dateFormat = new SimpleDateFormat("dd.MM.yyyy HH:mm");
+    private DateFormat dateFormat;
+
+    public TimestampValueFormatter() {
+        this(new SimpleDateFormat("HH:mm"));
+
+    }
+
+    public TimestampValueFormatter(DateFormat dateFormat) {
+        this.dateFormat = dateFormat;
+        cal = GregorianCalendar.getInstance();
+        cal.clear();
+    }
+
+    @Override
+    public String getFormattedValue(float value) {
+        cal.setTimeInMillis((int) value * 1000L);
+        Date date = cal.getTime();
+        String dateString = dateFormat.format(date);
+        return dateString;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/TrailingActivitySample.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/TrailingActivitySample.java
new file mode 100644
index 0000000..8bec737
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/TrailingActivitySample.java
@@ -0,0 +1,55 @@
+/*  Copyright (C) 2016-2024 Carsten Pfeiffer
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities.charts;
+
+import nodomain.freeyourgadget.gadgetbridge.entities.AbstractActivitySample;
+
+public class TrailingActivitySample extends AbstractActivitySample {
+    private int timestamp;
+    private long userId;
+    private long deviceId;
+
+    @Override
+    public void setTimestamp(int timestamp) {
+        this.timestamp = timestamp;
+    }
+
+    @Override
+    public void setUserId(long userId) {
+        this.userId = userId;
+    }
+
+    @Override
+    public void setDeviceId(long deviceId) {
+        this.deviceId = deviceId;
+    }
+
+    @Override
+    public long getDeviceId() {
+        return deviceId;
+    }
+
+    @Override
+    public long getUserId() {
+        return userId;
+    }
+
+    @Override
+    public int getTimestamp() {
+        return timestamp;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/VO2MaxFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/VO2MaxFragment.java
new file mode 100644
index 0000000..c1d0a84
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/VO2MaxFragment.java
@@ -0,0 +1,312 @@
+package nodomain.freeyourgadget.gadgetbridge.activities.charts;
+
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.GridLayout;
+import android.widget.ImageView;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+
+import androidx.annotation.Nullable;
+import androidx.core.content.ContextCompat;
+
+import com.github.mikephil.charting.charts.Chart;
+import com.github.mikephil.charting.charts.LineChart;
+import com.github.mikephil.charting.components.XAxis;
+import com.github.mikephil.charting.components.YAxis;
+import com.github.mikephil.charting.data.Entry;
+import com.github.mikephil.charting.data.LineData;
+import com.github.mikephil.charting.data.LineDataSet;
+import com.github.mikephil.charting.formatter.ValueFormatter;
+import com.github.mikephil.charting.interfaces.datasets.ILineDataSet;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.dashboard.AbstractDashboardVO2MaxWidget;
+import nodomain.freeyourgadget.gadgetbridge.activities.dashboard.GaugeDrawer;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.TimeSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.Vo2MaxSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.Vo2MaxSample;
+
+
+public class VO2MaxFragment extends AbstractChartFragment<VO2MaxFragment.VO2MaxData> {
+    protected static final Logger LOG = LoggerFactory.getLogger(VO2MaxFragment.class);
+
+    private TextView mDateView;
+    private TextView vo2MaxRunningValue;
+    private TextView vo2MaxCyclingValue;
+    private ImageView vo2MaxRunningGauge;
+    private ImageView vo2MaxCyclingGauge;
+    protected GaugeDrawer gaugeDrawer = new GaugeDrawer();
+    private LineChart vo2MaxChart;
+    private RelativeLayout vo2maxCyclingWrapper;
+    private RelativeLayout vo2maxRunningWrapper;
+    private GridLayout tilesGridWrapper;
+    private int tsFrom;
+    GBDevice device;
+
+    protected int CHART_TEXT_COLOR;
+    protected int LEGEND_TEXT_COLOR;
+    protected int TEXT_COLOR;
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+        View rootView = inflater.inflate(R.layout.fragment_vo2max, container, false);
+
+        mDateView = rootView.findViewById(R.id.vo2max_date_view);
+        vo2MaxRunningValue = rootView.findViewById(R.id.vo2max_running_gauge_value);
+        vo2MaxCyclingValue = rootView.findViewById(R.id.vo2max_cycling_gauge_value);
+        vo2MaxRunningGauge = rootView.findViewById(R.id.vo2max_running_gauge);
+        vo2MaxCyclingGauge = rootView.findViewById(R.id.vo2max_cycling_gauge);
+        vo2MaxChart = rootView.findViewById(R.id.vo2max_chart);
+        vo2maxCyclingWrapper = rootView.findViewById(R.id.vo2max_cycling_card_layout);
+        vo2maxRunningWrapper = rootView.findViewById(R.id.vo2max_running_card_layout);
+        tilesGridWrapper = rootView.findViewById(R.id.tiles_grid_wrapper);
+        device = getChartsHost().getDevice();
+        if (!supportsVO2MaxCycling(device)) {
+            tilesGridWrapper.removeView(vo2maxCyclingWrapper);
+        }
+        if (!supportsVO2MaxRunning(device)) {
+            tilesGridWrapper.removeView(vo2maxRunningWrapper);
+        }
+        setupVO2MaxChart();
+        refresh();
+        setupLegend(vo2MaxChart);
+
+
+        return rootView;
+    }
+
+    public boolean supportsVO2MaxCycling(GBDevice device) {
+        DeviceCoordinator coordinator = device.getDeviceCoordinator();
+        return coordinator != null && coordinator.supportsVO2MaxCycling();
+    }
+
+    public boolean supportsVO2MaxRunning(GBDevice device) {
+        DeviceCoordinator coordinator = device.getDeviceCoordinator();
+        return coordinator != null && coordinator.supportsVO2MaxRunning();
+    }
+
+    @Override
+    public String getTitle() {
+        return getString(R.string.menuitem_vo2_max);
+    }
+
+    @Override
+    protected void init() {
+        TEXT_COLOR = GBApplication.getTextColor(requireContext());
+        LEGEND_TEXT_COLOR = GBApplication.getTextColor(requireContext());
+        CHART_TEXT_COLOR = GBApplication.getSecondaryTextColor(requireContext());
+    }
+
+    @Override
+    protected VO2MaxData refreshInBackground(ChartsHost chartsHost, DBHandler db, GBDevice device) {
+        List<VO2MaxRecord> records = new ArrayList<>();
+        int tsEnd = getTSEnd();
+        Calendar day = Calendar.getInstance();
+        day.setTimeInMillis(tsEnd * 1000L); //we need today initially, which is the end of the time range
+        day.set(Calendar.HOUR_OF_DAY, 0); //and we set time for the start and end of the same day
+        day.set(Calendar.MINUTE, 0);
+        day.set(Calendar.SECOND, 0);
+        day.add(Calendar.DAY_OF_YEAR,  -30);
+        tsFrom = (int) (day.getTimeInMillis() / 1000);
+        List<? extends Vo2MaxSample> samples = getAllSamples(db, device, tsFrom, tsEnd);
+        for (Vo2MaxSample sample : samples) {
+            records.add(new VO2MaxRecord(sample.getTimestamp() / 1000, sample.getValue(), sample.getType()));
+        }
+        Map<Vo2MaxSample.Type, VO2MaxRecord> latestValues = new HashMap<>();
+        for (Vo2MaxSample.Type type : Vo2MaxSample.Type.values()) {
+            Vo2MaxSample sample = getLatestVo2MaxSample(db, device, type);
+            if (sample != null) {
+                latestValues.put(type, new VO2MaxRecord(sample.getTimestamp() / 1000, sample.getValue(), type));
+            }
+        }
+        return new VO2MaxData(records, latestValues);
+    }
+
+    @Override
+    protected void updateChartsnUIThread(VO2MaxData vo2MaxData) {
+        String formattedDate = new SimpleDateFormat("E, MMM dd").format(getEndDate());
+        mDateView.setText(formattedDate);
+
+        List<Entry> runningEntries = new ArrayList<>();
+        List<Entry> cyclingEntries = new ArrayList<>();
+        vo2MaxData.records.forEach((record) -> {
+            float nd = (float) (record.timestamp - this.tsFrom) / (60 * 60 * 24);
+            switch (record.type) {
+                case RUNNING:
+                    runningEntries.add(new Entry(nd, record.value));
+                    break;
+                case CYCLING:
+                    cyclingEntries.add(new Entry(nd, record.value));
+                    break;
+            }
+        });
+        final int[] colors = AbstractDashboardVO2MaxWidget.getColors();
+        final float[] segments = AbstractDashboardVO2MaxWidget.getSegments();
+        float[] vo2MaxRanges = AbstractDashboardVO2MaxWidget.getVO2MaxRanges();
+        final List<ILineDataSet> lineDataSets = new ArrayList<>();
+        if (supportsVO2MaxRunning(device)) {
+            VO2MaxRecord latestRunningRecord = vo2MaxData.getLatestValue(Vo2MaxSample.Type.RUNNING);
+            float runningVO2MaxValue = calculateVO2maxGaugeValue(vo2MaxRanges, latestRunningRecord != null ? latestRunningRecord.value : 0);
+            vo2MaxRunningValue.setText(String.valueOf(latestRunningRecord != null ? Math.round(latestRunningRecord.value) : "-"));
+            gaugeDrawer.drawSegmentedGauge(vo2MaxRunningGauge, colors, segments, runningVO2MaxValue, false, true);
+            lineDataSets.add(createDataSet(runningEntries, getResources().getColor(R.color.vo2max_running_char_line_color), getString(R.string.vo2max_running)));
+        }
+        if (supportsVO2MaxCycling(device)) {
+            VO2MaxRecord latestCyclingRecord = vo2MaxData.getLatestValue(Vo2MaxSample.Type.CYCLING);
+            float cyclingVO2MaxValue = calculateVO2maxGaugeValue(vo2MaxRanges, latestCyclingRecord != null ? latestCyclingRecord.value : 0);
+            gaugeDrawer.drawSegmentedGauge(vo2MaxCyclingGauge, colors, segments, cyclingVO2MaxValue, false, true);
+            vo2MaxCyclingValue.setText(String.valueOf(latestCyclingRecord != null ? Math.round(latestCyclingRecord.value) : "-"));
+            lineDataSets.add(createDataSet(cyclingEntries, getResources().getColor(R.color.vo2max_cycling_char_line_color), getString(R.string.vo2max_cycling)));
+        }
+        final LineData lineData = new LineData(lineDataSets);
+        vo2MaxChart.getXAxis().setValueFormatter(getVO2MaxLineChartValueFormatter());
+        vo2MaxChart.setData(lineData);
+    }
+
+    ValueFormatter getVO2MaxLineChartValueFormatter() {
+        return new ValueFormatter() {
+            @Override
+            public String getFormattedValue(float value) {
+                Calendar day = Calendar.getInstance();
+                day.setTimeInMillis(tsFrom * 1000L);
+                day.add(Calendar.DAY_OF_YEAR, (int) value);
+                return new SimpleDateFormat("dd/MM").format(day.getTime());
+            }
+        };
+    }
+
+    private float calculateVO2maxGaugeValue(float[] vo2MaxRanges, float vo2MaxValue) {
+        float value = -1;
+        for (int i = 0; i < vo2MaxRanges.length; i++) {
+            if (vo2MaxValue - vo2MaxRanges[i] > 0) {
+                float rangeValue = i - 1 >= 0 ? vo2MaxRanges[i-1] : 60F;
+                float rangeDiff = rangeValue - vo2MaxRanges[i];
+                float valueDiff = vo2MaxValue - vo2MaxRanges[i];
+                float multiplayer = valueDiff / rangeDiff;
+                value = (4 - i) * 0.2F + 0.2F * (multiplayer > 1 ? 1 : multiplayer) ;
+                break;
+            }
+        }
+        return value;
+    }
+
+    protected LineDataSet createDataSet(final List<Entry> values, int color, String label) {
+        final LineDataSet lineDataSet = new LineDataSet(values, label);
+        lineDataSet.setColor(color);
+        lineDataSet.setDrawCircles(false);
+        lineDataSet.setLineWidth(2f);
+        lineDataSet.setFillAlpha(255);
+        lineDataSet.setCircleRadius(5f);
+        lineDataSet.setDrawCircles(true);
+        lineDataSet.setDrawCircleHole(true);
+        lineDataSet.setCircleColor(color);
+        lineDataSet.setAxisDependency(YAxis.AxisDependency.LEFT);
+        lineDataSet.setDrawValues(true);
+        lineDataSet.setValueTextSize(10f);
+        lineDataSet.setValueTextColor(CHART_TEXT_COLOR);
+        lineDataSet.setValueFormatter(new ValueFormatter() {
+            @Override
+            public String getFormattedValue(float value) {
+                return String.format(Locale.ROOT, "%d", Math.round(value));
+            }
+        });
+        return lineDataSet;
+    }
+
+    @Override
+    protected void renderCharts() {
+        vo2MaxChart.invalidate();
+    }
+
+
+    public List<? extends Vo2MaxSample> getAllSamples(final DBHandler db, final GBDevice device, int tsFrom, int tsTo) {
+        final DeviceCoordinator coordinator = device.getDeviceCoordinator();
+        final TimeSampleProvider<? extends Vo2MaxSample> sampleProvider = coordinator.getVo2MaxSampleProvider(device, db.getDaoSession());
+        return sampleProvider.getAllSamples(tsFrom * 1000L, tsTo * 1000L);
+    }
+
+    public Vo2MaxSample getLatestVo2MaxSample(final DBHandler db, final GBDevice device, Vo2MaxSample.Type type) {
+        final DeviceCoordinator coordinator = device.getDeviceCoordinator();
+        final Vo2MaxSampleProvider sampleProvider = (Vo2MaxSampleProvider) coordinator.getVo2MaxSampleProvider(device, db.getDaoSession());
+        return  sampleProvider.getLatestSample(type, getTSEnd() * 1000L);
+    }
+
+    private void setupVO2MaxChart() {
+        final XAxis xAxisBottom = vo2MaxChart.getXAxis();
+        xAxisBottom.setPosition(XAxis.XAxisPosition.BOTTOM);
+        xAxisBottom.setDrawLabels(true);
+        xAxisBottom.setDrawGridLines(false);
+        xAxisBottom.setEnabled(true);
+        xAxisBottom.setDrawLimitLinesBehindData(true);
+        xAxisBottom.setTextColor(CHART_TEXT_COLOR);
+        xAxisBottom.setAxisMinimum(0f);
+        xAxisBottom.setAxisMaximum(31f);
+        xAxisBottom.setGranularity(1f);
+        xAxisBottom.setGranularityEnabled(true);
+
+        final YAxis yAxisLeft = vo2MaxChart.getAxisLeft();
+        yAxisLeft.setDrawGridLines(true);
+        yAxisLeft.setAxisMaximum(100);
+        yAxisLeft.setAxisMinimum(0);
+        yAxisLeft.setDrawTopYLabelEntry(true);
+        yAxisLeft.setEnabled(true);
+        yAxisLeft.setTextColor(CHART_TEXT_COLOR);
+
+        final YAxis yAxisRight = vo2MaxChart.getAxisRight();
+        yAxisRight.setEnabled(true);
+        yAxisRight.setDrawLabels(false);
+        yAxisRight.setDrawGridLines(false);
+        yAxisRight.setDrawAxisLine(true);
+    }
+
+    protected void setupLegend(Chart<?> chart) {
+        chart.getLegend().setTextColor(LEGEND_TEXT_COLOR);
+        chart.getLegend().setWordWrapEnabled(true);
+    }
+
+    protected static class VO2MaxRecord {
+        float value;
+        long timestamp;
+        Vo2MaxSample.Type type;
+
+        protected VO2MaxRecord(long timestamp, float value, Vo2MaxSample.Type type) {
+            this.timestamp = timestamp;
+            this.value = value;
+            this.type = type;
+        }
+    }
+
+    protected static class VO2MaxData extends ChartsData {
+        private final List<? extends VO2MaxRecord> records;
+        private final Map<Vo2MaxSample.Type, VO2MaxRecord> latestValues;
+
+        protected VO2MaxData(List<? extends VO2MaxRecord> records, Map<Vo2MaxSample.Type, VO2MaxRecord> latestValues) {
+            this.records = records;
+            this.latestValues = latestValues;
+        }
+
+        @Nullable
+        public VO2MaxRecord getLatestValue(Vo2MaxSample.Type type) {
+            return this.latestValues.get(type);
+        }
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/WeekSleepChartFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/WeekSleepChartFragment.java
new file mode 100644
index 0000000..bfef323
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/WeekSleepChartFragment.java
@@ -0,0 +1,499 @@
+/*  Copyright (C) 2017-2024 Andreas Shimokawa, Carsten Pfeiffer, Jos茅 Rebelo,
+    Pavel Elagin, Petr Van臎k, a0z
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities.charts;
+
+import android.os.Build;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.github.mikephil.charting.charts.Chart;
+import com.github.mikephil.charting.charts.LineChart;
+import com.github.mikephil.charting.components.Legend;
+import com.github.mikephil.charting.components.LegendEntry;
+import com.github.mikephil.charting.components.XAxis;
+import com.github.mikephil.charting.components.YAxis;
+import com.github.mikephil.charting.data.BarData;
+import com.github.mikephil.charting.formatter.ValueFormatter;
+
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.time.DateUtils;
+
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityAmount;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityAmounts;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
+import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
+
+public class WeekSleepChartFragment extends AbstractWeekChartFragment {
+
+    private TextView awakeSleepTimeText;
+    private LinearLayout awakeSleepTimeTextWrapper;
+    private TextView remSleepTimeText;
+    private LinearLayout remSleepTimeTextWrapper;
+    private TextView deepSleepTimeText;
+    private TextView lightSleepTimeText;
+    private TextView sleepDatesText;
+    private MySleepWeeklyData mySleepWeeklyData;
+    private LinearLayout sleepScoreWrapper;
+    private LineChart sleepScoreChart;
+
+    public static WeekSleepChartFragment newInstance ( int totalDays ) {
+        WeekSleepChartFragment fragmentFirst = new WeekSleepChartFragment();
+        Bundle args = new Bundle();
+        args.putInt("totalDays", totalDays);
+        fragmentFirst.setArguments(args);
+        return fragmentFirst;
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        TOTAL_DAYS = getArguments() != null ? getArguments().getInt("totalDays") : 0;
+    }
+
+    private MySleepWeeklyData getMySleepWeeklyData(DBHandler db, Calendar day, GBDevice device) {
+        day = (Calendar) day.clone(); // do not modify the caller's argument
+        day.add(Calendar.DATE, -TOTAL_DAYS + 1);
+        TOTAL_DAYS_FOR_AVERAGE=0;
+        long awakeWeeklyTotal = 0;
+        long remWeeklyTotal = 0;
+        long deepWeeklyTotal = 0;
+        long lightWeeklyTotal = 0;
+
+        for (int counter = 0; counter < TOTAL_DAYS; counter++) {
+            ActivityAmounts amounts = getActivityAmountsForDay(db, day, device);
+            if (calculateBalance(amounts) > 0) {
+                TOTAL_DAYS_FOR_AVERAGE++;
+            }
+
+            float[] totalAmounts = getTotalsForActivityAmounts(amounts);
+            int i = 0;
+            deepWeeklyTotal += (long) totalAmounts[i++];
+            lightWeeklyTotal += (long) totalAmounts[i++];
+            if (supportsRemSleep(getChartsHost().getDevice())) {
+                remWeeklyTotal += (long) totalAmounts[i++];
+            }
+            if (supportsAwakeSleep(getChartsHost().getDevice())) {
+                awakeWeeklyTotal += (long) totalAmounts[i++];
+            }
+
+            day.add(Calendar.DATE, 1);
+        }
+
+        return new MySleepWeeklyData(awakeWeeklyTotal, remWeeklyTotal, deepWeeklyTotal, lightWeeklyTotal);
+    }
+
+        @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+                             Bundle savedInstanceState) {
+        mLocale = getResources().getConfiguration().locale;
+        View rootView = inflater.inflate(R.layout.fragment_weeksleep_chart, container, false);
+
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+            rootView.setOnScrollChangeListener((v, scrollX, scrollY, oldScrollX, oldScrollY) -> {
+                getChartsHost().enableSwipeRefresh(scrollY == 0);
+            });
+        }
+
+        final int goal = getGoal();
+        if (goal >= 0) {
+            mTargetValue = goal;
+        }
+
+        mWeekChart = rootView.findViewById(R.id.weekstepschart);
+        sleepScoreWrapper = rootView.findViewById(R.id.sleep_score_wrapper);
+        sleepScoreChart = rootView.findViewById(R.id.sleep_score_chart);
+        remSleepTimeText = rootView.findViewById(R.id.sleep_chart_legend_rem_time);
+        remSleepTimeTextWrapper = rootView.findViewById(R.id.sleep_chart_legend_rem_time_wrapper);
+        awakeSleepTimeText = rootView.findViewById(R.id.sleep_chart_legend_awake_time);
+        awakeSleepTimeTextWrapper = rootView.findViewById(R.id.sleep_chart_legend_awake_time_wrapper);
+        deepSleepTimeText = rootView.findViewById(R.id.sleep_chart_legend_deep_time);
+        lightSleepTimeText = rootView.findViewById(R.id.sleep_chart_legend_light_time);
+        sleepDatesText = rootView.findViewById(R.id.sleep_dates);
+
+        mBalanceView = rootView.findViewById(R.id.balance);
+
+        if (!supportsSleepScore()) {
+            sleepScoreWrapper.setVisibility(View.GONE);
+        } else {
+            setupSleepScoreChart();
+        }
+
+        setupWeekChart();
+        // refresh immediately instead of use refreshIfVisible(), for perceived performance
+        refresh();
+
+        return rootView;
+    }
+
+    protected void setupWeekChart() {
+        super.setupWeekChart();
+        if (TOTAL_DAYS > 7) {
+            mWeekChart.setRenderer(new AngledLabelsChartRenderer(mWeekChart, mWeekChart.getAnimator(), mWeekChart.getViewPortHandler()));
+        } else {
+            mWeekChart.setScaleEnabled(false);
+            mWeekChart.setTouchEnabled(false);
+        }
+    }
+
+    @Override
+    protected void updateChartsnUIThread(MyChartsData mcd) {
+        setupLegend(mWeekChart);
+
+        mWeekChart.setData(null); // workaround for https://github.com/PhilJay/MPAndroidChart/issues/2317
+        mWeekChart.setData(mcd.getWeekBeforeData().getData());
+        mWeekChart.getXAxis().setValueFormatter(mcd.getWeekBeforeData().getXValueFormatter());
+        mWeekChart.getBarData().setValueTextSize(10f);
+
+        if (supportsSleepScore()) {
+            sleepScoreChart.setData(null);
+            sleepScoreChart.getXAxis().setValueFormatter(mcd.getWeekBeforeData().getXValueFormatter());
+            sleepScoreChart.getLegend().setTextColor(LEGEND_TEXT_COLOR);
+            sleepScoreChart.setData(mcd.getWeekBeforeData().getSleepScoreData());
+        }
+
+        // The last value is for awake time, which we do not want to include in the "total sleep time"
+        final int barIgnoreLast = supportsAwakeSleep(getChartsHost().getDevice()) ? 1 : 0;
+        mWeekChart.getBarData().setValueFormatter(new BarChartStackedTimeValueFormatter(false, "", 0, barIgnoreLast));
+
+        if (TOTAL_DAYS_FOR_AVERAGE > 0) {
+            float avgDeep = Math.abs(this.mySleepWeeklyData.getTotalDeep() / TOTAL_DAYS_FOR_AVERAGE);
+            deepSleepTimeText.setText(DateTimeUtils.formatDurationHoursMinutes((int) avgDeep, TimeUnit.MINUTES));
+            float avgLight = Math.abs(this.mySleepWeeklyData.getTotalLight() / TOTAL_DAYS_FOR_AVERAGE);
+            lightSleepTimeText.setText(DateTimeUtils.formatDurationHoursMinutes((int) avgLight, TimeUnit.MINUTES));
+            float avgRem = Math.abs(this.mySleepWeeklyData.getTotalRem() / TOTAL_DAYS_FOR_AVERAGE);
+            remSleepTimeText.setText(DateTimeUtils.formatDurationHoursMinutes((int) avgRem, TimeUnit.MINUTES));
+            float avgAwake = Math.abs(this.mySleepWeeklyData.getTotalAwake() / TOTAL_DAYS_FOR_AVERAGE);
+            awakeSleepTimeText.setText(DateTimeUtils.formatDurationHoursMinutes((int) avgAwake, TimeUnit.MINUTES));
+        } else {
+            deepSleepTimeText.setText("-");
+            lightSleepTimeText.setText("-");
+            remSleepTimeText.setText("-");
+            awakeSleepTimeText.setText("-");
+        }
+
+        if (!supportsRemSleep(getChartsHost().getDevice())) {
+            remSleepTimeTextWrapper.setVisibility(View.GONE);
+        }
+
+        if (!supportsAwakeSleep(getChartsHost().getDevice())) {
+            awakeSleepTimeTextWrapper.setVisibility(View.GONE);
+        }
+
+        Date to = new Date((long) this.getTSEnd() * 1000);
+        Date from = DateUtils.addDays(to,-(TOTAL_DAYS - 1));
+        String toFormattedDate = new SimpleDateFormat("E, MMM dd").format(to);
+        String fromFormattedDate = new SimpleDateFormat("E, MMM dd").format(from);
+        sleepDatesText.setText(fromFormattedDate + " - " + toFormattedDate);
+
+        mBalanceView.setText(mcd.getWeekBeforeData().getBalanceMessage());
+    }
+
+    @Override
+    protected MyChartsData refreshInBackground(ChartsHost chartsHost, DBHandler db, GBDevice device) {
+        Calendar day = Calendar.getInstance();
+        day.setTime(chartsHost.getEndDate());
+        //NB: we could have omitted the day, but this way we can move things to the past easily
+        WeekChartsData<BarData> weekBeforeData = refreshWeekBeforeData(db, mWeekChart, day, device);
+        mySleepWeeklyData = getMySleepWeeklyData(db, day, device);
+
+        return new MyChartsData(weekBeforeData);
+    }
+
+    private void setupSleepScoreChart() {
+        final XAxis xAxisBottom = sleepScoreChart.getXAxis();
+        xAxisBottom.setPosition(XAxis.XAxisPosition.BOTTOM);
+        xAxisBottom.setDrawLabels(true);
+        xAxisBottom.setDrawGridLines(false);
+        xAxisBottom.setEnabled(true);
+        xAxisBottom.setDrawLimitLinesBehindData(true);
+        xAxisBottom.setTextColor(CHART_TEXT_COLOR);
+        xAxisBottom.setAxisMinimum(0f);
+        xAxisBottom.setAxisMaximum(TOTAL_DAYS-1);
+        xAxisBottom.setGranularity(1f);
+        xAxisBottom.setGranularityEnabled(true);
+
+        final YAxis yAxisLeft = sleepScoreChart.getAxisLeft();
+        yAxisLeft.setDrawGridLines(true);
+        yAxisLeft.setAxisMaximum(100);
+        yAxisLeft.setAxisMinimum(0);
+        yAxisLeft.setDrawTopYLabelEntry(true);
+        yAxisLeft.setEnabled(true);
+        yAxisLeft.setTextColor(CHART_TEXT_COLOR);
+
+        final YAxis yAxisRight = sleepScoreChart.getAxisRight();
+        yAxisRight.setEnabled(true);
+        yAxisRight.setDrawLabels(false);
+        yAxisRight.setDrawGridLines(false);
+        yAxisRight.setDrawAxisLine(true);
+
+        sleepScoreChart.setDoubleTapToZoomEnabled(false);
+        sleepScoreChart.getDescription().setEnabled(false);
+        if (TOTAL_DAYS <= 7) {
+            sleepScoreChart.setScaleEnabled(false);
+            sleepScoreChart.setTouchEnabled(false);
+        }
+    }
+
+    @Override
+    protected void renderCharts() {
+        mWeekChart.invalidate();
+        sleepScoreChart.invalidate();
+    }
+
+    @Override
+    public String getTitle() {
+        if (GBApplication.getPrefs().getBoolean("charts_range", true)) {
+            return getString(R.string.weeksleepchart_sleep_a_month);
+        }
+        else{
+            return getString(R.string.weeksleepchart_sleep_a_week);
+        }
+    }
+
+    @Override
+    String getPieDescription(int targetValue) {
+        return getString(R.string.weeksleepchart_today_sleep_description, DateTimeUtils.formatDurationHoursMinutes(targetValue, TimeUnit.MINUTES));
+    }
+
+    @Override
+    int getGoal() {
+        return GBApplication.getPrefs().getInt(ActivityUser.PREF_USER_SLEEP_DURATION, 8) * 60;
+    }
+
+    @Override
+    int getOffsetHours() {
+        return -12;
+    }
+
+
+    @Override
+    protected long calculateBalance(ActivityAmounts activityAmounts) {
+        long balance = 0;
+
+        for (ActivityAmount amount : activityAmounts.getAmounts()) {
+            if (amount.getActivityKind() == ActivityKind.DEEP_SLEEP ||
+                    amount.getActivityKind() == ActivityKind.LIGHT_SLEEP ||
+                    amount.getActivityKind() == ActivityKind.REM_SLEEP) {
+                balance += amount.getTotalSeconds();
+            }
+        }
+        return (int) (balance / 60);
+    }
+
+    @Override
+    protected String getBalanceMessage(long balance, int targetValue) {
+        if (balance > 0) {
+            final long totalBalance = balance - ((long)targetValue * TOTAL_DAYS_FOR_AVERAGE);
+            if (totalBalance > 0)
+                return getString(R.string.overslept, getHM(totalBalance));
+            else
+                return getString(R.string.lack_of_sleep, getHM(Math.abs(totalBalance)));
+        } else
+            return getString(R.string.no_data);
+    }
+
+    @Override
+    float[] getTotalsForActivityAmounts(ActivityAmounts activityAmounts) {
+        long totalSecondsDeepSleep = 0;
+        long totalSecondsLightSleep = 0;
+        long totalSecondsRemSleep = 0;
+        long totalSecondsAwakeSleep = 0;
+        for (ActivityAmount amount : activityAmounts.getAmounts()) {
+            if (amount.getActivityKind() == ActivityKind.DEEP_SLEEP) {
+                totalSecondsDeepSleep += amount.getTotalSeconds();
+            } else if (amount.getActivityKind() == ActivityKind.LIGHT_SLEEP) {
+                totalSecondsLightSleep += amount.getTotalSeconds();
+            } else if (amount.getActivityKind() == ActivityKind.REM_SLEEP) {
+                totalSecondsRemSleep += amount.getTotalSeconds();
+            } else if (amount.getActivityKind() == ActivityKind.AWAKE_SLEEP) {
+                totalSecondsAwakeSleep += amount.getTotalSeconds();
+            }
+        }
+        int totalMinutesDeepSleep = (int) (totalSecondsDeepSleep / 60);
+        int totalMinutesLightSleep = (int) (totalSecondsLightSleep / 60);
+        int totalMinutesRemSleep = (int) (totalSecondsRemSleep / 60);
+        int totalMinutesAwakeSleep = (int) (totalSecondsAwakeSleep / 60);
+
+        float[] activityAmountsTotals =  {totalMinutesDeepSleep, totalMinutesLightSleep};
+        if (supportsRemSleep(getChartsHost().getDevice())) {
+            activityAmountsTotals = ArrayUtils.add(activityAmountsTotals, totalMinutesRemSleep);
+        }
+        if (supportsAwakeSleep(getChartsHost().getDevice())) {
+            activityAmountsTotals = ArrayUtils.add(activityAmountsTotals, totalMinutesAwakeSleep);
+        }
+
+        return activityAmountsTotals;
+    }
+
+    @Override
+    protected String formatPieValue(long value) {
+        return DateTimeUtils.formatDurationHoursMinutes(value, TimeUnit.MINUTES);
+    }
+
+    @Override
+    String[] getPieLabels() {
+        String[] labels = {
+                getString(R.string.abstract_chart_fragment_kind_deep_sleep),
+                getString(R.string.abstract_chart_fragment_kind_light_sleep)
+        };
+        if (supportsRemSleep(getChartsHost().getDevice())) {
+            labels = ArrayUtils.add(labels,  getString(R.string.abstract_chart_fragment_kind_rem_sleep));
+        }
+        if (supportsAwakeSleep(getChartsHost().getDevice())) {
+            labels = ArrayUtils.add(labels,  getString(R.string.abstract_chart_fragment_kind_awake_sleep));
+        }
+        return labels;
+    }
+
+    @Override
+    ValueFormatter getPieValueFormatter() {
+        return new ValueFormatter() {
+            @Override
+            public String getFormattedValue(float value) {
+                return formatPieValue((long) value);
+            }
+        };
+    }
+
+    @Override
+    ValueFormatter getBarValueFormatter() {
+        return new ValueFormatter() {
+            @Override
+            public String getFormattedValue(float value) {
+                return DateTimeUtils.minutesToHHMM((int) value);
+            }
+        };
+    }
+
+    @Override
+    ValueFormatter getYAxisFormatter() {
+        return new ValueFormatter() {
+            @Override
+            public String getFormattedValue(float value) {
+                return DateTimeUtils.minutesToHHMM((int) value);
+            }
+        };
+    }
+
+    @Override
+    int[] getColors() {
+        int[] colors = {akDeepSleep.color, akLightSleep.color};
+        if (supportsRemSleep(getChartsHost().getDevice())) {
+            colors = ArrayUtils.add(colors, akRemSleep.color);
+        }
+        if (supportsAwakeSleep(getChartsHost().getDevice())) {
+            colors = ArrayUtils.add(colors, akAwakeSleep.color);
+        }
+        return colors;
+    }
+
+    @Override
+    protected void setupLegend(Chart<?> chart) {
+        List<LegendEntry> legendEntries = new ArrayList<>(2);
+
+        LegendEntry lightSleepEntry = new LegendEntry();
+        lightSleepEntry.label = getActivity().getString(R.string.sleep_colored_stats_light);
+        lightSleepEntry.formColor = akLightSleep.color;
+        legendEntries.add(lightSleepEntry);
+
+        LegendEntry deepSleepEntry = new LegendEntry();
+        deepSleepEntry.label = getActivity().getString(R.string.sleep_colored_stats_deep);
+        deepSleepEntry.formColor = akDeepSleep.color;
+        legendEntries.add(deepSleepEntry);
+
+        if (supportsRemSleep(getChartsHost().getDevice())) {
+            LegendEntry remSleepEntry = new LegendEntry();
+            remSleepEntry.label = getActivity().getString(R.string.sleep_colored_stats_rem);
+            remSleepEntry.formColor = akRemSleep.color;
+            legendEntries.add(remSleepEntry);
+        }
+
+        if (supportsAwakeSleep(getChartsHost().getDevice())) {
+            LegendEntry awakeSleepEntry = new LegendEntry();
+            awakeSleepEntry.label = getActivity().getString(R.string.abstract_chart_fragment_kind_awake_sleep);
+            awakeSleepEntry.formColor = akAwakeSleep.color;
+            legendEntries.add(awakeSleepEntry);
+        }
+
+        chart.getLegend().setCustom(legendEntries);
+        chart.getLegend().setTextColor(LEGEND_TEXT_COLOR);
+        chart.getLegend().setWordWrapEnabled(true);
+        chart.getLegend().setHorizontalAlignment(Legend.LegendHorizontalAlignment.CENTER);
+    }
+
+    private String getHM(long value) {
+        return DateTimeUtils.formatDurationHoursMinutes(value, TimeUnit.MINUTES);
+    }
+
+    @Override
+    String getAverage(float value) {
+        return getHM((long)value);
+    }
+
+    private static class MySleepWeeklyData {
+        private long totalAwake;
+        private long totalRem;
+        private long totalDeep;
+        private long totalLight;
+        private int totalDaysForAverage;
+
+        public MySleepWeeklyData(long totalAwake, long totalRem, long totalDeep, long totalLight) {
+            this.totalDeep = totalDeep;
+            this.totalRem = totalRem;
+            this.totalAwake = totalAwake;
+            this.totalLight = totalLight;
+            this.totalDaysForAverage = 0;
+        }
+
+        public long getTotalAwake() {
+            return this.totalAwake;
+        }
+
+        public long getTotalRem() {
+            return this.totalRem;
+        }
+
+        public long getTotalDeep() {
+            return this.totalDeep;
+        }
+
+        public long getTotalLight() {
+            return this.totalLight;
+        }
+
+        public int getTotalDaysForAverage() {
+            return this.totalDaysForAverage;
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/WeightChartFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/WeightChartFragment.java
new file mode 100644
index 0000000..2653181
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/WeightChartFragment.java
@@ -0,0 +1,257 @@
+/*  Copyright (C) 2024 Severin von Wnuck-Lipinski
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities.charts;
+
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import androidx.fragment.app.Fragment;
+
+import com.github.mikephil.charting.animation.Easing;
+import com.github.mikephil.charting.charts.Chart;
+import com.github.mikephil.charting.charts.LineChart;
+import com.github.mikephil.charting.components.LimitLine;
+import com.github.mikephil.charting.components.XAxis;
+import com.github.mikephil.charting.components.YAxis;
+import com.github.mikephil.charting.data.Entry;
+import com.github.mikephil.charting.data.LineData;
+import com.github.mikephil.charting.data.LineDataSet;
+import com.github.mikephil.charting.formatter.ValueFormatter;
+
+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 nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.SettingsActivity;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.TimeSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
+import nodomain.freeyourgadget.gadgetbridge.model.WeightSample;
+import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.GBPrefs;
+
+public class WeightChartFragment extends AbstractChartFragment<WeightChartFragment.WeightChartsData> {
+    private int colorBackground;
+    private int colorSecondaryText;
+
+    private int totalDays;
+    private boolean imperialUnits;
+    private int weightTargetKg;
+
+    private LineChart chart;
+    private TextView textTimeSpan;
+    private TextView textWeightLatest;
+    private TextView textWeightTarget;
+
+    @Override
+    public String getTitle() {
+        return getString(R.string.menuitem_weight);
+    }
+
+    @Override
+    protected void init() {
+        GBPrefs prefs = GBApplication.getPrefs();
+
+        colorBackground = GBApplication.getBackgroundColor(requireContext());
+        colorSecondaryText = GBApplication.getSecondaryTextColor(requireContext());
+
+        if (prefs.getBoolean("charts_range", true))
+            totalDays = 30;
+        else
+            totalDays = 7;
+
+        String unitSystem = prefs.getString(SettingsActivity.PREF_MEASUREMENT_SYSTEM, getString(R.string.p_unit_metric));
+
+        if (unitSystem.equals(getString(R.string.p_unit_imperial)))
+            imperialUnits = true;
+        else
+            imperialUnits = false;
+
+        weightTargetKg = prefs.getInt(ActivityUser.PREF_USER_GOAL_WEIGHT_KG, ActivityUser.defaultUserGoalWeightKg);
+    }
+
+    @Override
+    protected boolean isSingleDay() {
+        return false;
+    }
+
+    @Override
+    protected WeightChartsData refreshInBackground(ChartsHost chartsHost, DBHandler db, GBDevice device) {
+        long tsStart = getTSStart() * 1000L;
+        long tsEnd = getTSEnd() * 1000L;
+
+        DeviceCoordinator coordinator = device.getDeviceCoordinator();
+        TimeSampleProvider<? extends WeightSample> provider = coordinator.getWeightSampleProvider(device, db.getDaoSession());
+        List<? extends WeightSample> samples = provider.getAllSamples(tsStart, tsEnd);
+        WeightSample latestSample = provider.getLatestSample();
+
+        return createChartsData(samples, latestSample);
+    }
+
+    @Override
+    protected void renderCharts() {
+        chart.animateX(ANIM_TIME, Easing.EaseInOutQuart);
+    }
+
+    @Override
+    protected void setupLegend(Chart<?> chart) {}
+
+    @Override
+    protected void updateChartsnUIThread(WeightChartsData chartsData) {
+        chart.setData(null); // workaround for https://github.com/PhilJay/MPAndroidChart/issues/2317
+        chart.getXAxis().setValueFormatter(chartsData.getXValueFormatter());
+        chart.getXAxis().setAvoidFirstLastClipping(true);
+        chart.setData(chartsData.getData());
+
+        Date dateStart = DateTimeUtils.parseTimeStamp(getTSStart());
+        Date dateEnd = DateTimeUtils.parseTimeStamp(getTSEnd());
+        SimpleDateFormat format = new SimpleDateFormat("E, MMM dd");
+        WeightSample latestSample = chartsData.getLatestSample();
+
+        textTimeSpan.setText(format.format(dateStart) + " - " + format.format(dateEnd));
+
+        if (latestSample != null)
+            textWeightLatest.setText(formatWeight(weightFromKg(latestSample.getWeightKg())));
+
+        textWeightTarget.setText(formatWeight(weightFromKg(weightTargetKg)));
+    }
+
+    @Override
+    protected int getTSStart() {
+        return DateTimeUtils.shiftDays(getTSEnd(), -totalDays + 1);
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+        View rootView = inflater.inflate(R.layout.fragment_weightchart, container, false);
+
+        chart = rootView.findViewById(R.id.weight_chart);
+        textTimeSpan = rootView.findViewById(R.id.weight_time_span_text);
+        textWeightLatest = rootView.findViewById(R.id.weight_latest_text);
+        textWeightTarget = rootView.findViewById(R.id.weight_target_text);
+
+        configureBarLineChartDefaults(chart);
+        chart.setBackgroundColor(colorBackground);
+        chart.getDescription().setEnabled(false);
+        chart.getLegend().setEnabled(false);
+        chart.getAxisRight().setEnabled(false);
+        chart.setDoubleTapToZoomEnabled(false);
+
+        LimitLine targetLine = new LimitLine(weightFromKg(weightTargetKg));
+        targetLine.setTextColor(colorSecondaryText);
+
+        XAxis xAxis = chart.getXAxis();
+        xAxis.setTextColor(colorSecondaryText);
+        xAxis.setDrawLabels(true);
+        xAxis.setDrawLimitLinesBehindData(true);
+
+        YAxis yAxis = chart.getAxisLeft();
+        yAxis.setTextColor(colorSecondaryText);
+        yAxis.addLimitLine(targetLine);
+        yAxis.setDrawGridLines(true);
+
+        refresh();
+
+        return rootView;
+    }
+
+    private WeightChartsData createChartsData(List<? extends WeightSample> samples, WeightSample latestSample) {
+        List<Entry> entries = new ArrayList<>();
+        TimestampTranslation tsTranslation = new TimestampTranslation();
+
+        for (WeightSample sample : samples) {
+            int tsSeconds = (int)(sample.getTimestamp() / 1000L);
+            float weight = weightFromKg(sample.getWeightKg());
+
+            entries.add(new Entry(tsTranslation.shorten(tsSeconds), weight));
+        }
+
+        LineDataSet dataSet = new LineDataSet(entries, getString(R.string.menuitem_weight));
+        dataSet.setLineWidth(2.2f);
+        dataSet.setMode(LineDataSet.Mode.HORIZONTAL_BEZIER);
+        dataSet.setCubicIntensity(0.1f);
+        dataSet.setCircleRadius(5);
+        dataSet.setDrawCircleHole(false);
+        dataSet.setDrawValues(true);
+        dataSet.setValueTextSize(10);
+        dataSet.setValueTextColor(colorSecondaryText);
+        dataSet.setValueFormatter(new ValueFormatter() {
+            @Override
+            public String getPointLabel(Entry entry) {
+                return formatWeight(entry.getY());
+            }
+        });
+
+        return new WeightChartsData(new LineData(dataSet), tsTranslation, latestSample);
+    }
+
+    private float weightFromKg(float weight) {
+        // Convert to lbs
+        if (imperialUnits)
+            weight *= 2.2046226f;
+
+        return weight;
+    }
+
+    private String formatWeight(float weight) {
+        int weightString = imperialUnits ? R.string.weight_lbs : R.string.weight_kg;
+
+        return getString(weightString, weight);
+    }
+
+    protected static class WeightChartsData extends DefaultChartsData<LineData> {
+        private final WeightSample latestSample;
+
+        public WeightChartsData(LineData lineData, TimestampTranslation tsTranslation, WeightSample latestSample) {
+            super(lineData, new DateFormatter(tsTranslation));
+
+            this.latestSample = latestSample;
+        }
+
+        private WeightSample getLatestSample() {
+            return latestSample;
+        }
+    }
+
+    private static class DateFormatter extends ValueFormatter {
+        private TimestampTranslation translation;
+        private SimpleDateFormat format = new SimpleDateFormat("dd.MM.");
+        private Calendar calendar = GregorianCalendar.getInstance();
+
+        public DateFormatter(TimestampTranslation translation) {
+            this.translation = translation;
+        }
+
+        @Override
+        public String getFormattedValue(float value) {
+            calendar.clear();
+            calendar.setTimeInMillis(translation.toOriginalValue((int)value) * 1000L);
+
+            return format.format(calendar.getTime());
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/AbstractDashboardVO2MaxWidget.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/AbstractDashboardVO2MaxWidget.java
new file mode 100644
index 0000000..d8b6ea7
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/AbstractDashboardVO2MaxWidget.java
@@ -0,0 +1,124 @@
+package nodomain.freeyourgadget.gadgetbridge.activities.dashboard;
+
+import androidx.annotation.Nullable;
+import androidx.core.content.ContextCompat;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.Serializable;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.DashboardFragment;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.Vo2MaxSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.Vo2MaxSample;
+
+public abstract class AbstractDashboardVO2MaxWidget extends AbstractGaugeWidget implements DashboardVO2MaxWidgetInterface {
+    protected static final Logger LOG = LoggerFactory.getLogger(AbstractDashboardVO2MaxWidget.class);
+
+    public AbstractDashboardVO2MaxWidget(int label, @Nullable String targetActivityTab) {
+        super(label, targetActivityTab);
+    }
+
+    @Override
+    protected void populateData(final DashboardFragment.DashboardData dashboardData) {
+        final List<GBDevice> devices = getSupportedDevices(dashboardData);
+        final VO2MaxData data = new VO2MaxData();
+
+        // Latest vo2max sample.
+        Vo2MaxSample sample = null;
+        try (DBHandler dbHandler = GBApplication.acquireDB()) {
+            for (GBDevice dev : devices) {
+                final Vo2MaxSampleProvider sampleProvider = (Vo2MaxSampleProvider) dev.getDeviceCoordinator().getVo2MaxSampleProvider(dev, dbHandler.getDaoSession());
+                final Vo2MaxSample latestSample = sampleProvider.getLatestSample(getVO2MaxType(), dashboardData.timeTo * 1000L);
+                if (latestSample != null && (sample == null || latestSample.getTimestamp() > sample.getTimestamp())) {
+                    sample = latestSample;
+                }
+            }
+
+            if (sample != null) {
+                data.value = sample.getValue();
+            }
+
+        } catch (final Exception e) {
+            LOG.error("Could not get vo2max for today", e);
+        }
+
+        dashboardData.put(getWidgetKey(), data);
+    }
+
+    public static int[] getColors() {
+        return new int[]{
+                ContextCompat.getColor(GBApplication.getContext(), R.color.vo2max_value_poor_color),
+                ContextCompat.getColor(GBApplication.getContext(), R.color.vo2max_value_fair_color),
+                ContextCompat.getColor(GBApplication.getContext(), R.color.vo2max_value_good_color),
+                ContextCompat.getColor(GBApplication.getContext(), R.color.vo2max_value_excellent_color),
+                ContextCompat.getColor(GBApplication.getContext(), R.color.vo2max_value_superior_color),
+        };
+    }
+
+    public static float[] getSegments() {
+        return new float[] {
+                0.20F,
+                0.20F,
+                0.20F,
+                0.20F,
+                0.20F,
+        };
+    }
+
+    public static float[] getVO2MaxRanges() {
+        return new float[] {
+                55.4F,
+                51.1F,
+                45.4F,
+                41.7F,
+                0.0F,
+        };
+    }
+
+    @Override
+    protected void draw(final DashboardFragment.DashboardData dashboardData) {
+        final VO2MaxData vo2MaxData = (VO2MaxData) dashboardData.get(getWidgetKey());
+        if (vo2MaxData == null) {
+            drawSimpleGauge(0, -1);
+            return;
+        }
+
+        final int[] colors = getColors();
+        final float[] segments = getSegments();
+        final float[] vo2MaxRanges = getVO2MaxRanges();
+        float vo2MaxValue = calculateVO2maxGaugeValue(vo2MaxRanges, vo2MaxData.value != -1 ? vo2MaxData.value : 0);
+        setText(String.valueOf(vo2MaxData.value != -1 ? Math.round(vo2MaxData.value) : "-"));
+        drawSegmentedGauge(
+                colors,
+                segments,
+                vo2MaxValue,
+                false,
+                true
+        );
+    }
+
+    private float calculateVO2maxGaugeValue(float[] vo2MaxRanges, float vo2MaxValue) {
+        float value = -1;
+        for (int i = 0; i < vo2MaxRanges.length; i++) {
+            if (vo2MaxValue - vo2MaxRanges[i] > 0) {
+                float rangeValue = i - 1 >= 0 ? vo2MaxRanges[i-1] : 60F;
+                float rangeDiff = rangeValue - vo2MaxRanges[i];
+                float valueDiff = vo2MaxValue - vo2MaxRanges[i];
+                float multiplayer = valueDiff / rangeDiff;
+                value = (4 - i) * 0.2F + 0.2F * (multiplayer > 1 ? 1 : multiplayer) ;
+                break;
+            }
+        }
+        return value;
+    }
+
+    private static class VO2MaxData implements Serializable {
+        private float value = -1;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/AbstractDashboardWidget.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/AbstractDashboardWidget.java
new file mode 100644
index 0000000..eebbfe0
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/AbstractDashboardWidget.java
@@ -0,0 +1,136 @@
+/*  Copyright (C) 2023-2024 Arjan Schrijver, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities.dashboard;
+
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Color;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.Toast;
+
+import androidx.annotation.ColorInt;
+import androidx.fragment.app.Fragment;
+
+import com.google.android.material.dialog.MaterialAlertDialogBuilder;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.DashboardFragment;
+import nodomain.freeyourgadget.gadgetbridge.activities.charts.ActivityChartsActivity;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public abstract class AbstractDashboardWidget extends Fragment {
+    private static final Logger LOG = LoggerFactory.getLogger(AbstractDashboardWidget.class);
+
+    protected static String ARG_DASHBOARD_DATA = "dashboard_widget_argument_data";
+
+    protected DashboardFragment.DashboardData dashboardData;
+
+    protected @ColorInt int color_unknown = Color.argb(25, 128, 128, 128);
+    protected @ColorInt int color_not_worn = Color.BLACK;
+    protected @ColorInt int color_worn = Color.rgb(128, 128, 128);
+    protected @ColorInt int color_activity = Color.GREEN;
+    protected @ColorInt int color_exercise = Color.rgb(255, 128, 0);
+    protected @ColorInt int color_deep_sleep = Color.rgb(0, 84, 163);
+    protected @ColorInt int color_light_sleep = Color.rgb(7, 158, 243);
+    protected @ColorInt int color_rem_sleep = Color.rgb(228, 39, 199);
+    protected @ColorInt int color_awake_sleep = Color.rgb(0xff, 0x86, 0x6e);
+    protected @ColorInt int color_distance = Color.BLUE;
+    protected @ColorInt int color_active_time = Color.rgb(170, 0, 255);
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        if (getArguments() != null) {
+            dashboardData = (DashboardFragment.DashboardData) getArguments().getSerializable(ARG_DASHBOARD_DATA);
+        }
+    }
+
+    public void update() {
+        fillData();
+    }
+
+    protected abstract void fillData();
+
+    protected boolean isSupportedBy(final GBDevice device) {
+        return device.getDeviceCoordinator().supportsActivityTracking();
+    }
+
+    protected List<GBDevice> getSupportedDevices(final DashboardFragment.DashboardData dashboardData) {
+        return GBApplication.app().getDeviceManager().getDevices()
+                .stream()
+                .filter(dev -> dashboardData.showAllDevices || dashboardData.showDeviceList.contains(dev.getAddress()))
+                .filter(this::isSupportedBy)
+                .collect(Collectors.toList());
+    }
+
+    protected void onClickOpenChart(final View view, final String chart, final int label, final String mode) {
+        view.setOnClickListener(v -> {
+            chooseDevice(dashboardData, device -> {
+                final Intent startIntent;
+                startIntent = new Intent(requireContext(), ActivityChartsActivity.class);
+                startIntent.putExtra(GBDevice.EXTRA_DEVICE, device);
+                startIntent.putExtra(ActivityChartsActivity.EXTRA_SINGLE_FRAGMENT_NAME, chart);
+                startIntent.putExtra(ActivityChartsActivity.EXTRA_ACTIONBAR_TITLE, label);
+                startIntent.putExtra(ActivityChartsActivity.EXTRA_TIMESTAMP, dashboardData.timeTo);
+                startIntent.putExtra(ActivityChartsActivity.EXTRA_MODE, mode);
+                requireContext().startActivity(startIntent);
+            });
+        });
+    }
+
+    protected void chooseDevice(final DashboardFragment.DashboardData dashboardData,
+                                final Consumer<GBDevice> consumer) {
+        final List<GBDevice> devices = getSupportedDevices(dashboardData);
+
+        if (devices.size() == 1) {
+            consumer.accept(devices.get(0));
+            return;
+        }
+
+        if (devices.isEmpty()) {
+            GB.toast(GBApplication.getContext(), R.string.no_supported_devices_found, Toast.LENGTH_LONG, GB.WARN);
+            return;
+        }
+
+        final String[] deviceNames = devices.stream()
+                .map(GBDevice::getAliasOrName)
+                .toArray(String[]::new);
+
+        final Context activity = getActivity();
+        if (activity == null) {
+            return;
+        }
+
+        new MaterialAlertDialogBuilder(activity)
+                .setCancelable(true)
+                .setTitle(R.string.choose_device)
+                .setItems(deviceNames, (dialog, which) -> consumer.accept(devices.get(which)))
+                .setNegativeButton(android.R.string.cancel, (dialog, which) -> {
+                })
+                .show();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/AbstractGaugeWidget.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/AbstractGaugeWidget.java
new file mode 100644
index 0000000..4fd321d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/AbstractGaugeWidget.java
@@ -0,0 +1,164 @@
+/*  Copyright (C) 2023-2024 Arjan Schrijver, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities.dashboard;
+
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.StringRes;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.DashboardFragment;
+
+public abstract class AbstractGaugeWidget extends AbstractDashboardWidget {
+    private static final Logger LOG = LoggerFactory.getLogger(AbstractGaugeWidget.class);
+
+    private TextView gaugeValue;
+    private ImageView gaugeBar;
+    protected GaugeDrawer gaugeDrawer;
+
+    private final int label;
+    private final String targetActivityTab;
+    private String mode = "";
+
+    public AbstractGaugeWidget(@StringRes final int label, @Nullable final String targetActivityTab) {
+        this.label = label;
+        this.targetActivityTab = targetActivityTab;
+    }
+
+    public AbstractGaugeWidget(@StringRes final int label, @Nullable final String targetActivityTab, final String mode) {
+        this(label, targetActivityTab);
+        this.mode = mode;
+    }
+
+    @Override
+    public View onCreateView(final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) {
+        final View fragmentView = inflater.inflate(R.layout.dashboard_widget_generic_gauge, container, false);
+
+        if (targetActivityTab != null) {
+            onClickOpenChart(fragmentView, targetActivityTab, label, mode);
+        }
+
+        gaugeValue = fragmentView.findViewById(R.id.gauge_value);
+        gaugeBar = fragmentView.findViewById(R.id.gauge_bar);
+        gaugeDrawer = new GaugeDrawer();
+        final TextView gaugeLabel = fragmentView.findViewById(R.id.gauge_label);
+        gaugeLabel.setText(label);
+
+        fillData();
+
+        return fragmentView;
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        if (gaugeValue != null && gaugeBar != null) fillData();
+    }
+
+    @Override
+    protected void fillData() {
+        if (gaugeBar == null) return;
+        gaugeBar.post(() -> {
+            final FillDataAsyncTask myAsyncTask = new FillDataAsyncTask();
+            myAsyncTask.execute();
+        });
+    }
+
+    /**
+     * This is called from the async task, outside of the UI thread. It's expected that
+     * {@link nodomain.freeyourgadget.gadgetbridge.activities.DashboardFragment.DashboardData} be
+     * populated with the necessary data for display.
+     *
+     * @param dashboardData the DashboardData to populate
+     */
+    protected abstract void populateData(DashboardFragment.DashboardData dashboardData);
+
+    /**
+     * This is called from the UI thread.
+     *
+     * @param dashboardData populated DashboardData
+     */
+    protected abstract void draw(DashboardFragment.DashboardData dashboardData);
+
+    private class FillDataAsyncTask extends AsyncTask<Void, Void, Void> {
+        @Override
+        protected Void doInBackground(final Void... params) {
+            final long nanoStart = System.nanoTime();
+            try {
+                populateData(dashboardData);
+            } catch (final Exception e) {
+                LOG.error("fillData for {} failed", AbstractGaugeWidget.this.getClass().getSimpleName(), e);
+            }
+            final long nanoEnd = System.nanoTime();
+            final long executionTime = (nanoEnd - nanoStart) / 1000000;
+            LOG.debug("fillData for {} took {}ms", AbstractGaugeWidget.this.getClass().getSimpleName(), executionTime);
+            return null;
+        }
+
+        @Override
+        protected void onPostExecute(final Void unused) {
+            super.onPostExecute(unused);
+            try {
+                draw(dashboardData);
+            } catch (final Exception e) {
+                LOG.error("draw for {} failed", AbstractGaugeWidget.this.getClass().getSimpleName(), e);
+            }
+        }
+    }
+
+    protected void setText(final CharSequence text) {
+        gaugeValue.setText(text);
+    }
+
+    /**
+     * Draw a simple gauge.
+     *
+     * @param color     the gauge color
+     * @param value     the gauge value. Range: [0, 1]
+     */
+    protected void drawSimpleGauge(final int color,
+                                   final float value) {
+        gaugeDrawer.drawSimpleGauge(gaugeBar, color, value);
+    }
+
+    /**
+     * Draws a segmented gauge.
+     *
+     * @param colors             the colors of each segment
+     * @param segments           the size of each segment. The sum of all segments should be 1
+     * @param value              the gauge value, in range [0, 1], or -1 for no value and only segments
+     * @param fadeOutsideDot     whether to fade out colors outside the dot value
+     * @param gapBetweenSegments whether to introduce a small gap between the segments
+     */
+    protected void drawSegmentedGauge(final int[] colors,
+                                      final float[] segments,
+                                      final float value,
+                                      final boolean fadeOutsideDot,
+                                      final boolean gapBetweenSegments) {
+        gaugeDrawer.drawSegmentedGauge(gaugeBar, colors, segments, value, fadeOutsideDot, gapBetweenSegments);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/DashboardActiveTimeWidget.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/DashboardActiveTimeWidget.java
new file mode 100644
index 0000000..ebeaac5
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/DashboardActiveTimeWidget.java
@@ -0,0 +1,74 @@
+/*  Copyright (C) 2023-2024 Arjan Schrijver, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities.dashboard;
+
+import android.os.Bundle;
+
+import java.util.Locale;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.DashboardFragment;
+
+/**
+ * A simple {@link AbstractDashboardWidget} subclass.
+ * Use the {@link DashboardActiveTimeWidget#newInstance} factory method to
+ * create an instance of this fragment.
+ */
+public class DashboardActiveTimeWidget extends AbstractGaugeWidget {
+    public DashboardActiveTimeWidget() {
+        super(R.string.activity_list_summary_active_time, "activity");
+    }
+
+    /**
+     * Use this factory method to create a new instance of
+     * this fragment using the provided parameters.
+     *
+     * @param dashboardData An instance of DashboardFragment.DashboardData.
+     * @return A new instance of fragment DashboardActiveTimeWidget.
+     */
+    public static DashboardActiveTimeWidget newInstance(final DashboardFragment.DashboardData dashboardData) {
+        final DashboardActiveTimeWidget fragment = new DashboardActiveTimeWidget();
+        final Bundle args = new Bundle();
+        args.putSerializable(ARG_DASHBOARD_DATA, dashboardData);
+        fragment.setArguments(args);
+        return fragment;
+    }
+
+    @Override
+    protected void populateData(final DashboardFragment.DashboardData dashboardData) {
+        dashboardData.getActiveMinutesTotal();
+        dashboardData.getActiveMinutesGoalFactor();
+    }
+
+    @Override
+    protected void draw(final DashboardFragment.DashboardData dashboardData) {
+        final long totalActiveMinutes = dashboardData.getActiveMinutesTotal();
+        final String valueText = String.format(
+                Locale.ROOT,
+                "%d:%02d",
+                (int) Math.floor(totalActiveMinutes / 60f),
+                (int) (totalActiveMinutes % 60f)
+        );
+
+        setText(valueText);
+
+        drawSimpleGauge(
+                color_active_time,
+                dashboardData.getActiveMinutesGoalFactor()
+        );
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/DashboardBodyEnergyWidget.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/DashboardBodyEnergyWidget.java
new file mode 100644
index 0000000..820486a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/DashboardBodyEnergyWidget.java
@@ -0,0 +1,191 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities.dashboard;
+
+import android.os.Bundle;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.TextUtils;
+import android.text.format.DateUtils;
+import android.text.style.RelativeSizeSpan;
+
+import androidx.core.content.ContextCompat;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.Serializable;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.DashboardFragment;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.BodyEnergySample;
+
+public class DashboardBodyEnergyWidget extends AbstractGaugeWidget {
+    private static final Logger LOG = LoggerFactory.getLogger(DashboardBodyEnergyWidget.class);
+
+    public DashboardBodyEnergyWidget() {
+        super(R.string.body_energy, "bodyenergy");
+    }
+
+    public static DashboardBodyEnergyWidget newInstance(final DashboardFragment.DashboardData dashboardData) {
+        final DashboardBodyEnergyWidget fragment = new DashboardBodyEnergyWidget();
+        final Bundle args = new Bundle();
+        args.putSerializable(ARG_DASHBOARD_DATA, dashboardData);
+        fragment.setArguments(args);
+        return fragment;
+    }
+
+    @Override
+    protected boolean isSupportedBy(final GBDevice device) {
+        return device.getDeviceCoordinator().supportsBodyEnergy();
+    }
+
+    @Override
+    protected void populateData(final DashboardFragment.DashboardData dashboardData) {
+        final List<GBDevice> devices = getSupportedDevices(dashboardData);
+
+        final boolean isToday = DateUtils.isToday(dashboardData.timeTo * 1000L);
+
+        final BodyEnergyData data = new BodyEnergyData();
+        data.isToday = isToday;
+
+        if (isToday) {
+            // Latest stress sample for today
+            BodyEnergySample sample = null;
+
+            try (DBHandler dbHandler = GBApplication.acquireDB()) {
+                for (GBDevice dev : devices) {
+                    final BodyEnergySample latestSample = dev.getDeviceCoordinator().getBodyEnergySampleProvider(dev, dbHandler.getDaoSession())
+                            .getLatestSample();
+
+                    if (latestSample != null && (sample == null || latestSample.getTimestamp() > sample.getTimestamp())) {
+                        sample = latestSample;
+                    }
+                }
+
+                if (sample != null) {
+                    data.value = sample.getEnergy();
+                }
+
+            } catch (final Exception e) {
+                LOG.error("Could not get body energy for today", e);
+            }
+        } else {
+            // Gain / loss for the period
+            try (DBHandler dbHandler = GBApplication.acquireDB()) {
+                for (GBDevice dev : devices) {
+                    if ((dashboardData.showAllDevices || dashboardData.showDeviceList.contains(dev.getAddress())) && dev.getDeviceCoordinator().supportsBodyEnergy()) {
+                        final List<? extends BodyEnergySample> samples = dev.getDeviceCoordinator()
+                                .getBodyEnergySampleProvider(dev, dbHandler.getDaoSession())
+                                .getAllSamples(dashboardData.timeFrom * 1000L, dashboardData.timeTo * 1000L);
+
+                        if (samples.size() > 1) {
+                            int gained = 0;
+                            int lost = 0;
+                            for (int i = 1; i < samples.size(); i++) {
+                                final BodyEnergySample s1 = samples.get(i - 1);
+                                final BodyEnergySample s2 = samples.get(i);
+                                if (s2.getEnergy() > s1.getEnergy()) {
+                                    gained += s2.getEnergy() - s1.getEnergy();
+                                } else {
+                                    lost += s1.getEnergy() - s2.getEnergy();
+                                }
+                            }
+
+                            data.gained = gained;
+                            data.lost = lost;
+                        }
+                    }
+                }
+            } catch (final Exception e) {
+                LOG.error("Could not calculate average stress", e);
+            }
+        }
+
+        dashboardData.put("bodyenergy", data);
+    }
+
+    @Override
+    protected void draw(final DashboardFragment.DashboardData dashboardData) {
+        final BodyEnergyData bodyEnergyData = (BodyEnergyData) dashboardData.get("bodyenergy");
+        if (bodyEnergyData == null) {
+            drawSimpleGauge(0, -1);
+            return;
+        }
+
+        final int colorEnergy = ContextCompat.getColor(GBApplication.getContext(), R.color.body_energy_level_color);
+
+        if (bodyEnergyData.isToday) {
+            if (bodyEnergyData.value < 0) {
+                drawSimpleGauge(0, -1);
+                return;
+            }
+
+            setText(String.valueOf(bodyEnergyData.value));
+            drawSimpleGauge(
+                    colorEnergy,
+                    bodyEnergyData.value / 100f
+            );
+        } else {
+            if (bodyEnergyData.gained < 0 || bodyEnergyData.lost < 0) {
+                drawSimpleGauge(0, -1);
+                return;
+            }
+
+            final int diff = bodyEnergyData.gained - bodyEnergyData.lost;
+
+            final SpannableString spanGain = new SpannableString("鈫�" + bodyEnergyData.gained);
+            final SpannableString spanLost = new SpannableString("鈫�" + bodyEnergyData.lost);
+            spanGain.setSpan(new RelativeSizeSpan(0.65f), 0, 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+            spanLost.setSpan(new RelativeSizeSpan(0.65f), 0, 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+
+            setText(TextUtils.concat(spanGain, " ", spanLost));
+            drawSimpleGauge(
+                    colorEnergy,
+                    Math.abs(diff) / 100f
+            );
+
+            final int[] colors = {
+                    colorEnergy,
+                    ContextCompat.getColor(GBApplication.getContext(), R.color.body_energy_lost_color)
+            };
+            final float[] segments = {
+                    bodyEnergyData.gained / (float) (bodyEnergyData.gained + bodyEnergyData.lost),
+                    bodyEnergyData.lost / (float) (bodyEnergyData.gained + bodyEnergyData.lost),
+            };
+
+            drawSegmentedGauge(
+                    colors,
+                    segments,
+                    -1,
+                    false,
+                    true
+            );
+        }
+    }
+
+    private static class BodyEnergyData implements Serializable {
+        private int value = -1;
+        private int gained = -1;
+        private int lost = -1;
+        private boolean isToday;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/DashboardCalendarActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/DashboardCalendarActivity.java
new file mode 100644
index 0000000..01d754b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/DashboardCalendarActivity.java
@@ -0,0 +1,374 @@
+/*  Copyright (C) 2023-2024 Arjan Schrijver
+
+    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 <http://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities.dashboard;
+
+import android.content.Intent;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.GradientDrawable;
+import android.graphics.drawable.LayerDrawable;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.SpannableStringBuilder;
+import android.text.style.ForegroundColorSpan;
+import android.util.DisplayMetrics;
+import android.view.Gravity;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import androidx.annotation.ColorInt;
+import androidx.gridlayout.widget.GridLayout;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBActivity;
+import nodomain.freeyourgadget.gadgetbridge.activities.DashboardFragment;
+import nodomain.freeyourgadget.gadgetbridge.util.DashboardUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+public class DashboardCalendarActivity extends AbstractGBActivity {
+    private static final Logger LOG = LoggerFactory.getLogger(DashboardCalendarActivity.class);
+    public static String EXTRA_TIMESTAMP = "dashboard_calendar_chosen_day";
+    private final ConcurrentHashMap<Calendar, TextView> dayCells = new ConcurrentHashMap<>();
+    private final ConcurrentHashMap<Integer, Integer> dayColors = new ConcurrentHashMap<>();
+
+    @ColorInt private int color_unknown = Color.argb(50, 128, 128, 128);
+    @ColorInt private int color_0_25 = Color.argb(128, 255, 0, 0); // Red
+    @ColorInt private int color_25_50 = Color.argb(128, 255, 128, 0); // Orange
+    @ColorInt private int color_50_75 = Color.argb(128, 255, 255, 0); // Yellow
+    @ColorInt private int color_75_100 = Color.argb(128, 0, 128, 0); // Dark green
+    @ColorInt private int color_100 = Color.argb(128, 0, 255, 0); // Green
+
+    private boolean showAllDevices;
+    private Set<String> showDeviceList;
+
+    TextView monthTextView;
+    TextView arrowLeft;
+    TextView arrowRight;
+    GridLayout calendarGrid;
+    Calendar currentDay;
+    Calendar cal;
+    ImageView monthGoalsChart;
+    TextView monthGoalsText;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_dashboard_calendar);
+        monthTextView = findViewById(R.id.calendar_month);
+        calendarGrid = findViewById(R.id.dashboard_calendar_grid);
+        monthGoalsChart = findViewById(R.id.dashboard_calendar_month_goals_chart);
+        monthGoalsText = findViewById(R.id.dashboard_calendar_month_goals_text);
+        currentDay = Calendar.getInstance();
+        cal = Calendar.getInstance();
+        long receivedTimestamp = getIntent().getLongExtra(EXTRA_TIMESTAMP, 0);
+        if (receivedTimestamp != 0) {
+            currentDay.setTimeInMillis(receivedTimestamp);
+            cal.setTimeInMillis(receivedTimestamp);
+        }
+
+        Prefs prefs = GBApplication.getPrefs();
+        showAllDevices = prefs.getBoolean("dashboard_devices_all", true);
+        showDeviceList = prefs.getStringSet("dashboard_devices_multiselect", new HashSet<>());
+
+        arrowLeft = findViewById(R.id.arrow_left);
+        arrowLeft.setOnClickListener(v -> {
+            cal.add(Calendar.MONTH, -1);
+            draw();
+        });
+        arrowRight = findViewById(R.id.arrow_right);
+        arrowRight.setOnClickListener(v -> {
+            Calendar today = GregorianCalendar.getInstance();
+            if (!DateTimeUtils.isSameMonth(today, cal)) {
+                cal.add(Calendar.MONTH, 1);
+                draw();
+            }
+        });
+
+        draw();
+    }
+
+    private void displayColorsAsync() {
+        calendarGrid.post(new Runnable() {
+            @Override
+            public void run() {
+                FillDataAsyncTask myAsyncTask = new FillDataAsyncTask();
+                myAsyncTask.execute();
+            }
+        });
+    }
+
+    private void draw() {
+        // Remove previous calendar days
+        dayCells.clear();
+        dayColors.clear();
+        calendarGrid.removeAllViews();
+        // Update month display
+        SimpleDateFormat monthFormat = new SimpleDateFormat("LLLL yyyy", Locale.getDefault());
+        monthTextView.setText(monthFormat.format(cal.getTime()));
+        Calendar today = GregorianCalendar.getInstance();
+        today.set(Calendar.HOUR, 23);
+        today.set(Calendar.MINUTE, 59);
+        today.set(Calendar.SECOND, 59);
+        if (DateTimeUtils.isSameMonth(today, cal)) {
+            arrowRight.setAlpha(0.5f);
+        } else {
+            arrowRight.setAlpha(1);
+        }
+        // Calculate grid cell size for dates
+        DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
+        int screenWidth = displayMetrics.widthPixels;
+        int cellSize = screenWidth / 7;
+        // Determine first day that should be displayed
+        Calendar drawCal = (Calendar) cal.clone();
+        drawCal.set(Calendar.DAY_OF_MONTH, 1);
+        int displayMonth = drawCal.get(Calendar.MONTH);
+        int firstDayOfWeek = cal.getFirstDayOfWeek();
+        int daysToFirstDay = (drawCal.get(Calendar.DAY_OF_WEEK) - firstDayOfWeek + 7) % 7;
+        drawCal.add(Calendar.DAY_OF_MONTH, -daysToFirstDay);
+        // Determine last day that should be displayed
+        Calendar lastDay = (Calendar) cal.clone();
+        lastDay.set(Calendar.DAY_OF_MONTH, cal.getActualMaximum(Calendar.DAY_OF_MONTH));
+        int daysAfterMonth = (firstDayOfWeek + 7 - lastDay.get(Calendar.DAY_OF_WEEK)) % 7;
+        if (daysAfterMonth == 0 && lastDay.get(Calendar.DAY_OF_WEEK) == firstDayOfWeek) {
+            daysAfterMonth = 7;
+        }
+        lastDay.add(Calendar.DAY_OF_MONTH, daysAfterMonth);
+        // Add day names header
+        SimpleDateFormat dayFormat = new SimpleDateFormat("E", Locale.getDefault());
+        Calendar weekdays = Calendar.getInstance();
+        for (int i=0; i<7; i++) {
+            int currentDayOfWeek = (firstDayOfWeek + i - 1) % 7 + 1;
+            weekdays.set(Calendar.DAY_OF_WEEK, currentDayOfWeek);
+            createWeekdayCell(dayFormat.format(weekdays.getTime()), cellSize);
+        }
+        // Loop through month days and create grid cells for them
+        while (!DateTimeUtils.isSameDay(drawCal, lastDay)) {
+            boolean clickable = drawCal.get(Calendar.MONTH) == displayMonth;
+            if (drawCal.after(today)) clickable = false;
+            createDateCell(drawCal, cellSize, clickable);
+            drawCal.add(Calendar.DAY_OF_MONTH, 1);
+        }
+        // Asynchronously determine and display goal colors
+        displayColorsAsync();
+    }
+
+    private TextView prepareGridElement(int cellSize) {
+        GridLayout.LayoutParams layoutParams = new GridLayout.LayoutParams(
+                GridLayout.spec(GridLayout.UNDEFINED, GridLayout.FILL,1f),
+                GridLayout.spec(GridLayout.UNDEFINED, 1, GridLayout.FILL,1f)
+        );
+        int margin = cellSize / 10;
+        layoutParams.width = 0;
+        layoutParams.height = cellSize - 2 * margin;
+        layoutParams.setMargins(margin, margin, margin, margin);
+        TextView text = new TextView(this);
+        text.setLayoutParams(layoutParams);
+        text.setGravity(Gravity.CENTER);
+        return text;
+    }
+
+    private void createWeekdayCell(String day, int cellSize) {
+        TextView text = prepareGridElement(cellSize);
+        text.setText(day);
+        calendarGrid.addView(text);
+    }
+
+    private void createDateCell(Calendar day, int cellSize, boolean clickable) {
+        TextView text = prepareGridElement(cellSize);
+        text.setText(String.valueOf(day.get(Calendar.DAY_OF_MONTH)));
+        if (clickable) {
+            // Save textview for later coloring
+            dayCells.put((Calendar) day.clone(), text);
+        }
+        calendarGrid.addView(text);
+    }
+
+    private class FillDataAsyncTask extends AsyncTask<Void, Void, Void> {
+        int amount_0_25 = 0;
+        int amount_25_50 = 0;
+        int amount_50_75 = 0;
+        int amount_75_100 = 0;
+        int amount_100 = 0;
+
+        @Override
+        protected Void doInBackground(Void... params) {
+            for (Calendar day : dayCells.keySet()) {
+                // Determine day color by the amount of the steps goal reached
+                DashboardFragment.DashboardData dashboardData = new DashboardFragment.DashboardData();
+                dashboardData.showAllDevices = showAllDevices;
+                dashboardData.showDeviceList = showDeviceList;
+                dashboardData.timeTo = (int) (day.getTimeInMillis() / 1000);
+                dashboardData.timeFrom = DateTimeUtils.shiftDays(dashboardData.timeTo, -1);
+                float goalFactor = DashboardUtils.getStepsGoalFactor(dashboardData);
+                @ColorInt int dayColor;
+                if (goalFactor >= 1) {
+                    dayColor = color_100;
+                    amount_100++;
+                } else if (goalFactor >= 0.75) {
+                    dayColor = color_75_100;
+                    amount_75_100++;
+                } else if (goalFactor >= 0.5) {
+                    dayColor = color_50_75;
+                    amount_50_75++;
+                } else if (goalFactor >= 0.25) {
+                    dayColor = color_25_50;
+                    amount_25_50++;
+                } else if (goalFactor > 0) {
+                    dayColor = color_0_25;
+                    amount_0_25++;
+                } else {
+                    dayColor = color_unknown;
+                }
+                dayColors.put(day.get(Calendar.DAY_OF_MONTH), dayColor);
+            }
+            return null;
+        }
+
+        @Override
+        protected void onPostExecute(Void unused) {
+            super.onPostExecute(unused);
+            for (Map.Entry<Calendar, TextView> entry : dayCells.entrySet()) {
+                Calendar day = entry.getKey();
+                TextView text = entry.getValue();
+                @ColorInt int dayColor;
+                try {
+                    dayColor = dayColors.get(day.get(Calendar.DAY_OF_MONTH));
+                } catch (NullPointerException e) {
+                    continue;
+                }
+                final long timestamp = day.getTimeInMillis();
+                // Draw colored circle
+                GradientDrawable backgroundDrawable = new GradientDrawable();
+                backgroundDrawable.setShape(GradientDrawable.OVAL);
+                backgroundDrawable.setColor(dayColor);
+                if (DateTimeUtils.isSameDay(day, currentDay)) {
+                    GradientDrawable borderDrawable = new GradientDrawable();
+                    borderDrawable.setShape(GradientDrawable.OVAL);
+                    borderDrawable.setColor(Color.TRANSPARENT);
+                    borderDrawable.setStroke(5, GBApplication.getTextColor(getApplicationContext()));
+                    LayerDrawable layerDrawable = new LayerDrawable(new Drawable[]{backgroundDrawable, borderDrawable});
+                    text.setBackground(layerDrawable);
+                } else {
+                    text.setBackground(backgroundDrawable);
+                }
+                text.setOnClickListener(v -> {
+                    Intent resultIntent = new Intent();
+                    resultIntent.putExtra(EXTRA_TIMESTAMP, timestamp);
+                    setResult(RESULT_OK, resultIntent);
+                    finish();
+                });
+            }
+
+            // Draw visual representation of this month's day goals
+            drawMonthGoalsLine();
+
+            // Fill legend
+            Resources res = getResources();
+            SpannableString line_100 = new SpannableString("鈻� 100%: " + res.getQuantityString(R.plurals.amount_of_days, amount_100, amount_100));
+            line_100.setSpan(new ForegroundColorSpan(color_100), 0, 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+            SpannableString line_75_100 = new SpannableString("鈻� 75-100%: " + res.getQuantityString(R.plurals.amount_of_days, amount_75_100, amount_75_100));
+            line_75_100.setSpan(new ForegroundColorSpan(color_75_100), 0, 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+            SpannableString line_50_75 = new SpannableString("鈻� 50-75%: " + res.getQuantityString(R.plurals.amount_of_days, amount_50_75, amount_50_75));
+            line_50_75.setSpan(new ForegroundColorSpan(color_50_75), 0, 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+            SpannableString line_25_50 = new SpannableString("鈻� 25-50%: " + res.getQuantityString(R.plurals.amount_of_days, amount_25_50, amount_25_50));
+            line_25_50.setSpan(new ForegroundColorSpan(color_25_50), 0, 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+            SpannableString line_0_25 = new SpannableString("鈻� 0-25%: " + res.getQuantityString(R.plurals.amount_of_days, amount_0_25, amount_0_25));
+            line_0_25.setSpan(new ForegroundColorSpan(color_0_25), 0, 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+            SpannableStringBuilder builder = new SpannableStringBuilder();
+            monthGoalsText.setText(builder.append(line_100).append("\n").append(line_75_100).append("\n").append(line_50_75).append("\n").append(line_25_50).append("\n").append(line_0_25));
+        }
+
+        private void drawMonthGoalsLine() {
+            int monthMaxDays = cal.getActualMaximum(Calendar.DAY_OF_MONTH);
+            int amountOfDays = amount_0_25 + amount_25_50 + amount_50_75 + amount_75_100 + amount_100;
+            int width = 700;
+            int height = 40;
+            int totalDrawWidth = width - height / 2;
+            float drawWidth = totalDrawWidth * ((float) amountOfDays / monthMaxDays);
+
+            Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+            Canvas canvas = new Canvas(bitmap);
+            Paint paint = new Paint();
+            paint.setAntiAlias(true);
+            paint.setStyle(Paint.Style.STROKE);
+            paint.setStrokeCap(Paint.Cap.ROUND);
+            paint.setStrokeWidth(height);
+            paint.setColor(color_unknown);
+            canvas.drawLine(height / 2, height / 2, totalDrawWidth, height / 2, paint);
+
+            // 0-25%
+            if (amount_0_25 > 0) {
+                paint.setColor(color_0_25);
+                canvas.drawLine(height / 2, height / 2, drawWidth, height / 2, paint);
+            }
+
+            // 25-50%
+            if (amount_25_50 > 0) {
+                paint.setColor(color_25_50);
+                float barDays = amount_25_50 + amount_50_75 + amount_75_100 + amount_100;
+                float barFraction = barDays / amountOfDays;
+                canvas.drawLine(height / 2, height / 2, drawWidth * barFraction, height / 2, paint);
+            }
+
+            // 50-75%
+            if (amount_50_75 > 0) {
+                paint.setColor(color_50_75);
+                float barDays = amount_50_75 + amount_75_100 + amount_100;
+                float barFraction = barDays / amountOfDays;
+                canvas.drawLine(height / 2, height / 2, drawWidth * barFraction, height / 2, paint);
+            }
+
+            // 75-100%
+            if (amount_75_100 > 0) {
+                paint.setColor(color_75_100);
+                float barDays = amount_75_100 + amount_100;
+                float barFraction = barDays / amountOfDays;
+                canvas.drawLine(height / 2, height / 2, drawWidth * barFraction, height / 2, paint);
+            }
+
+            // 100%
+            if (amount_100 > 0) {
+                paint.setColor(color_100);
+                float barDays = amount_100;
+                float barFraction = barDays / amountOfDays;
+                canvas.drawLine(height / 2, height / 2, drawWidth * barFraction, height / 2, paint);
+            }
+
+            monthGoalsChart.setImageBitmap(bitmap);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/DashboardCaloriesActiveGoalWidget.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/DashboardCaloriesActiveGoalWidget.java
new file mode 100644
index 0000000..3adce8e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/DashboardCaloriesActiveGoalWidget.java
@@ -0,0 +1,58 @@
+package nodomain.freeyourgadget.gadgetbridge.activities.dashboard;
+
+import android.os.Bundle;
+
+import androidx.core.content.ContextCompat;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.DashboardFragment;
+import nodomain.freeyourgadget.gadgetbridge.activities.charts.CaloriesDailyFragment;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+/**
+ * A simple {@link AbstractDashboardWidget} subclass.
+ * Use the {@link DashboardCaloriesActiveGoalWidget#newInstance} factory method to
+ * create an instance of this fragment.
+ */
+public class DashboardCaloriesActiveGoalWidget extends AbstractGaugeWidget {
+    public DashboardCaloriesActiveGoalWidget() {
+        super(R.string.active_calories, "calories", CaloriesDailyFragment.GaugeViewMode.ACTIVE_CALORIES_GOAL.toString());
+    }
+
+    /**
+     * Use this factory method to create a new instance of
+     * this fragment using the provided parameters.
+     *
+     * @param dashboardData An instance of DashboardFragment.DashboardData.
+     * @return A new instance of fragment DashboardStepsWidget.
+     */
+    public static DashboardCaloriesActiveGoalWidget newInstance(final DashboardFragment.DashboardData dashboardData) {
+        final DashboardCaloriesActiveGoalWidget fragment = new DashboardCaloriesActiveGoalWidget();
+        final Bundle args = new Bundle();
+        args.putSerializable(ARG_DASHBOARD_DATA, dashboardData);
+        fragment.setArguments(args);
+        return fragment;
+    }
+
+    @Override
+    protected boolean isSupportedBy(final GBDevice device) {
+        return device.getDeviceCoordinator().supportsActiveCalories();
+    }
+
+    @Override
+    protected void populateData(final DashboardFragment.DashboardData dashboardData) {
+        dashboardData.getActiveCaloriesTotal();
+        dashboardData.getActiveCaloriesGoalFactor();
+    }
+
+    @Override
+    protected void draw(final DashboardFragment.DashboardData dashboardData) {
+        setText(String.valueOf(dashboardData.getActiveCaloriesTotal()));
+        final int colorCalories = ContextCompat.getColor(GBApplication.getContext(), R.color.calories_color);
+        drawSimpleGauge(
+                colorCalories,
+                dashboardData.getActiveCaloriesGoalFactor()
+        );
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/DashboardCaloriesTotalSegmentedWidget.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/DashboardCaloriesTotalSegmentedWidget.java
new file mode 100644
index 0000000..5df4159
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/DashboardCaloriesTotalSegmentedWidget.java
@@ -0,0 +1,83 @@
+package nodomain.freeyourgadget.gadgetbridge.activities.dashboard;
+
+import android.graphics.Color;
+import android.os.Bundle;
+
+import androidx.core.content.ContextCompat;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.DashboardFragment;
+import nodomain.freeyourgadget.gadgetbridge.activities.charts.CaloriesDailyFragment;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+/**
+ * A simple {@link AbstractDashboardWidget} subclass.
+ * Use the {@link DashboardCaloriesTotalSegmentedWidget#newInstance} factory method to
+ * create an instance of this fragment.
+ */
+public class DashboardCaloriesTotalSegmentedWidget extends AbstractGaugeWidget {
+    public DashboardCaloriesTotalSegmentedWidget() {
+        super(R.string.calories, "calories", CaloriesDailyFragment.GaugeViewMode.TOTAL_CALORIES_SEGMENT.toString());
+    }
+
+    /**
+     * Use this factory method to create a new instance of
+     * this fragment using the provided parameters.
+     *
+     * @param dashboardData An instance of DashboardFragment.DashboardData.
+     * @return A new instance of fragment DashboardStepsWidget.
+     */
+    public static DashboardCaloriesTotalSegmentedWidget newInstance(final DashboardFragment.DashboardData dashboardData) {
+        final DashboardCaloriesTotalSegmentedWidget fragment = new DashboardCaloriesTotalSegmentedWidget();
+        final Bundle args = new Bundle();
+        args.putSerializable(ARG_DASHBOARD_DATA, dashboardData);
+        fragment.setArguments(args);
+        return fragment;
+    }
+
+    @Override
+    protected boolean isSupportedBy(final GBDevice device) {
+        return device.getDeviceCoordinator().supportsActiveCalories();
+    }
+
+    @Override
+    protected void populateData(final DashboardFragment.DashboardData dashboardData) {
+        dashboardData.getActiveCaloriesTotal();
+        dashboardData.getRestingCaloriesTotal();
+    }
+
+    @Override
+    protected void draw(final DashboardFragment.DashboardData dashboardData) {
+        int activeCalories = dashboardData.getActiveCaloriesTotal();
+        int restingCalories = dashboardData.getRestingCaloriesTotal();
+        int totalCalories = activeCalories + restingCalories;
+        setText(String.valueOf(totalCalories));
+        final int[] colors;
+        final float[] segments;
+        if (totalCalories != 0) {
+            colors = new int[] {
+                    ContextCompat.getColor(GBApplication.getContext(), R.color.calories_resting_color),
+                    ContextCompat.getColor(GBApplication.getContext(), R.color.calories_color)
+            };
+            segments = new float[] {
+                    restingCalories > 0 ? (float) restingCalories / totalCalories : 0,
+                    activeCalories > 0 ? (float) activeCalories / totalCalories : 0
+            };
+        } else {
+            colors = new int[]{
+                    Color.argb(25, 128, 128, 128)
+            };
+            segments = new float[] {
+                    1f
+            };
+        }
+        drawSegmentedGauge(
+                colors,
+                segments,
+                -1,
+                false,
+                false
+        );
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/DashboardDistanceWidget.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/DashboardDistanceWidget.java
new file mode 100644
index 0000000..0a32cbb
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/DashboardDistanceWidget.java
@@ -0,0 +1,64 @@
+/*  Copyright (C) 2023-2024 Arjan Schrijver, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities.dashboard;
+
+import android.os.Bundle;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.DashboardFragment;
+import nodomain.freeyourgadget.gadgetbridge.util.FormatUtils;
+
+/**
+ * A simple {@link AbstractDashboardWidget} subclass.
+ * Use the {@link DashboardDistanceWidget#newInstance} factory method to
+ * create an instance of this fragment.
+ */
+public class DashboardDistanceWidget extends AbstractGaugeWidget {
+    public DashboardDistanceWidget() {
+        super(R.string.distance, "stepsweek");
+    }
+
+    /**
+     * Use this factory method to create a new instance of
+     * this fragment using the provided parameters.
+     *
+     * @param dashboardData An instance of DashboardFragment.DashboardData.
+     * @return A new instance of fragment DashboardDistanceWidget.
+     */
+    public static DashboardDistanceWidget newInstance(final DashboardFragment.DashboardData dashboardData) {
+        final DashboardDistanceWidget fragment = new DashboardDistanceWidget();
+        final Bundle args = new Bundle();
+        args.putSerializable(ARG_DASHBOARD_DATA, dashboardData);
+        fragment.setArguments(args);
+        return fragment;
+    }
+
+    @Override
+    protected void populateData(final DashboardFragment.DashboardData dashboardData) {
+        dashboardData.getDistanceTotal();
+        dashboardData.getDistanceGoalFactor();
+    }
+
+    @Override
+    protected void draw(final DashboardFragment.DashboardData dashboardData) {
+        setText(FormatUtils.getFormattedDistanceLabel(dashboardData.getDistanceTotal()));
+        drawSimpleGauge(
+                color_distance,
+                dashboardData.getDistanceGoalFactor()
+        );
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/DashboardGoalsWidget.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/DashboardGoalsWidget.java
new file mode 100644
index 0000000..1b857e2
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/DashboardGoalsWidget.java
@@ -0,0 +1,177 @@
+/*  Copyright (C) 2023-2024 Arjan Schrijver, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities.dashboard;
+
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.SpannableStringBuilder;
+import android.text.style.ForegroundColorSpan;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.DashboardFragment;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+/**
+ * A simple {@link AbstractDashboardWidget} subclass.
+ * Use the {@link DashboardGoalsWidget#newInstance} factory method to
+ * create an instance of this fragment.
+ */
+public class DashboardGoalsWidget extends AbstractDashboardWidget {
+    private static final Logger LOG = LoggerFactory.getLogger(DashboardGoalsWidget.class);
+    private View goalsView;
+    private ImageView goalsChart;
+
+    public DashboardGoalsWidget() {
+        // Required empty public constructor
+    }
+
+    /**
+     * Use this factory method to create a new instance of
+     * this fragment using the provided parameters.
+     *
+     * @param dashboardData An instance of DashboardFragment.DashboardData.
+     * @return A new instance of fragment DashboardGoalsWidget.
+     */
+    public static DashboardGoalsWidget newInstance(DashboardFragment.DashboardData dashboardData) {
+        DashboardGoalsWidget fragment = new DashboardGoalsWidget();
+        Bundle args = new Bundle();
+        args.putSerializable(ARG_DASHBOARD_DATA, dashboardData);
+        fragment.setArguments(args);
+        return fragment;
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+        goalsView = inflater.inflate(R.layout.dashboard_widget_goals, container, false);
+        goalsChart = goalsView.findViewById(R.id.dashboard_goals_chart);
+
+        // Initialize legend
+        TextView legend = goalsView.findViewById(R.id.dashboard_goals_legend);
+        SpannableString l_steps = new SpannableString("鈻� " + getString(R.string.steps));
+        l_steps.setSpan(new ForegroundColorSpan(color_activity), 0, 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+        SpannableString l_distance = new SpannableString("鈻� " + getString(R.string.distance));
+        l_distance.setSpan(new ForegroundColorSpan(color_distance), 0, 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+        SpannableString l_active_time = new SpannableString("鈻� " + getString(R.string.activity_list_summary_active_time));
+        l_active_time.setSpan(new ForegroundColorSpan(color_active_time), 0, 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+        SpannableString l_sleep = new SpannableString("鈻� " + getString(R.string.menuitem_sleep));
+        l_sleep.setSpan(new ForegroundColorSpan(color_light_sleep), 0, 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+        SpannableStringBuilder legendBuilder = new SpannableStringBuilder();
+        legend.setText(legendBuilder.append(l_steps).append(" ").append(l_distance).append("\n").append(l_active_time).append(" ").append(l_sleep));
+
+        Prefs prefs = GBApplication.getPrefs();
+        legend.setVisibility(prefs.getBoolean("dashboard_widget_goals_legend", true) ? View.VISIBLE : View.GONE);
+
+        return goalsView;
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        if (goalsChart != null) fillData();
+    }
+
+    @Override
+    protected void fillData() {
+        if (goalsView == null) return;
+        goalsView.post(new Runnable() {
+            @Override
+            public void run() {
+                FillDataAsyncTask myAsyncTask = new FillDataAsyncTask();
+                myAsyncTask.execute();
+            }
+        });
+    }
+
+    private class FillDataAsyncTask extends AsyncTask<Void, Void, Void> {
+        private Bitmap goalsBitmap;
+
+        @Override
+        protected Void doInBackground(Void... params) {
+            final long nanoStart = System.nanoTime();
+
+            int width = Resources.getSystem().getDisplayMetrics().widthPixels;
+            int height = width;
+            int barWidth = Math.round(height * 0.04f);
+            int barMargin = (int) Math.ceil(barWidth / 2f);
+
+            goalsBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+            Canvas canvas = new Canvas(goalsBitmap);
+            Paint paint = new Paint();
+            paint.setAntiAlias(true);
+            paint.setStyle(Paint.Style.STROKE);
+            paint.setStrokeCap(Paint.Cap.ROUND);
+
+            paint.setStrokeWidth(barWidth * 0.75f);
+            paint.setColor(color_unknown);
+            canvas.drawArc(barMargin, barMargin, width - barMargin, height - barMargin, 270, 360, false, paint);
+            paint.setStrokeWidth(barWidth);
+            paint.setColor(color_activity);
+            canvas.drawArc(barMargin, barMargin, width - barMargin, height - barMargin, 270, 360 * dashboardData.getStepsGoalFactor(), false, paint);
+
+            barMargin += barWidth * 1.5;
+            paint.setStrokeWidth(barWidth * 0.75f);
+            paint.setColor(color_unknown);
+            canvas.drawArc(barMargin, barMargin, width - barMargin, height - barMargin, 270, 360, false, paint);
+            paint.setStrokeWidth(barWidth);
+            paint.setColor(color_distance);
+            canvas.drawArc(barMargin, barMargin, width - barMargin, height - barMargin, 270, 360 * dashboardData.getDistanceGoalFactor(), false, paint);
+
+            barMargin += barWidth * 1.5;
+            paint.setStrokeWidth(barWidth * 0.75f);
+            paint.setColor(color_unknown);
+            canvas.drawArc(barMargin, barMargin, width - barMargin, height - barMargin, 270, 360, false, paint);
+            paint.setStrokeWidth(barWidth);
+            paint.setColor(color_active_time);
+            canvas.drawArc(barMargin, barMargin, width - barMargin, height - barMargin, 270, 360 * dashboardData.getActiveMinutesGoalFactor(), false, paint);
+
+            barMargin += barWidth * 1.5;
+            paint.setStrokeWidth(barWidth * 0.75f);
+            paint.setColor(color_unknown);
+            canvas.drawArc(barMargin, barMargin, width - barMargin, height - barMargin, 270, 360, false, paint);
+            paint.setStrokeWidth(barWidth);
+            paint.setColor(color_light_sleep);
+            canvas.drawArc(barMargin, barMargin, width - barMargin, height - barMargin, 270, 360 * dashboardData.getSleepMinutesGoalFactor(), false, paint);
+
+            final long nanoEnd = System.nanoTime();
+            final long executionTime = (nanoEnd - nanoStart) / 1000000;
+            LOG.debug("fillData for {} took {}ms", DashboardGoalsWidget.this.getClass().getSimpleName(), executionTime);
+
+            return null;
+        }
+
+        @Override
+        protected void onPostExecute(Void unused) {
+            super.onPostExecute(unused);
+            goalsChart.setImageBitmap(goalsBitmap);
+        }
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/DashboardHrvWidget.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/DashboardHrvWidget.java
new file mode 100644
index 0000000..7252a0d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/DashboardHrvWidget.java
@@ -0,0 +1,157 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities.dashboard;
+
+import android.os.Bundle;
+
+import androidx.core.content.ContextCompat;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.Serializable;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.DashboardFragment;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.HrvSummarySample;
+
+public class DashboardHrvWidget extends AbstractGaugeWidget {
+    private static final Logger LOG = LoggerFactory.getLogger(DashboardHrvWidget.class);
+
+    public DashboardHrvWidget() {
+        super(R.string.hrv, "hrvstatus");
+    }
+
+    public static DashboardHrvWidget newInstance(final DashboardFragment.DashboardData dashboardData) {
+        final DashboardHrvWidget fragment = new DashboardHrvWidget();
+        final Bundle args = new Bundle();
+        args.putSerializable(ARG_DASHBOARD_DATA, dashboardData);
+        fragment.setArguments(args);
+        return fragment;
+    }
+
+    @Override
+    protected boolean isSupportedBy(final GBDevice device) {
+        return device.getDeviceCoordinator().supportsHrvMeasurement();
+    }
+
+    @Override
+    protected void populateData(final DashboardFragment.DashboardData dashboardData) {
+        final List<GBDevice> devices = getSupportedDevices(dashboardData);
+
+        HrvSummarySample latestSummary = null;
+
+        try (DBHandler dbHandler = GBApplication.acquireDB()) {
+            for (GBDevice dev : devices) {
+                final List<? extends HrvSummarySample> deviceLatestSummaries = dev.getDeviceCoordinator().getHrvSummarySampleProvider(dev, dbHandler.getDaoSession())
+                        .getAllSamples(dashboardData.timeFrom * 1000L, dashboardData.timeTo * 1000L);
+
+                if (!deviceLatestSummaries.isEmpty() && (latestSummary == null || latestSummary.getTimestamp() < deviceLatestSummaries.get(deviceLatestSummaries.size() - 1).getTimestamp())) {
+                    latestSummary = deviceLatestSummaries.get(deviceLatestSummaries.size() - 1);
+                }
+            }
+
+            final HrvData hrvData = new HrvData();
+
+            if (latestSummary != null) {
+                hrvData.weeklyAverage = latestSummary.getWeeklyAverage() != null ? latestSummary.getWeeklyAverage() : 0;
+                hrvData.lastNightAverage = latestSummary.getLastNightAverage() != null ? latestSummary.getLastNightAverage() : 0;
+                hrvData.lastNight5MinHigh = latestSummary.getLastNight5MinHigh() != null ? latestSummary.getLastNight5MinHigh() : 0;
+                hrvData.baselineLowUpper = latestSummary.getBaselineLowUpper() != null ? latestSummary.getBaselineLowUpper() : 0;
+                hrvData.baselineBalancedLower = latestSummary.getBaselineBalancedLower() != null ? latestSummary.getBaselineBalancedLower() : 0;
+                hrvData.baselineBalancedUpper = latestSummary.getBaselineBalancedUpper() != null ? latestSummary.getBaselineBalancedUpper() : 0;
+
+                dashboardData.put("hrv", hrvData);
+            }
+
+        } catch (final Exception e) {
+            LOG.error("Could not get hrv sample", e);
+        }
+    }
+
+    @Override
+    protected void draw(final DashboardFragment.DashboardData dashboardData) {
+        final int[] colors = getColors();
+        final float[] segments = getSegments();
+        final HrvData hrvData = (HrvData) dashboardData.get("hrv");
+        final float value = hrvData != null ? calculateGaugeValue(hrvData.weeklyAverage, hrvData.baselineLowUpper, hrvData.baselineBalancedLower, hrvData.baselineBalancedUpper) : -1;
+        final String valueText;
+        valueText = value > 0 ? getString(R.string.hrv_status_unit, hrvData.weeklyAverage) : getString(R.string.stats_empty_value);
+        setText(valueText);
+        drawSegmentedGauge(
+                colors,
+                segments,
+                value,
+                false,
+                true
+        );
+    }
+
+    public static int[] getColors() {
+        return new int[]{
+                ContextCompat.getColor(GBApplication.getContext(), R.color.hrv_status_low),
+                ContextCompat.getColor(GBApplication.getContext(), R.color.hrv_status_unbalanced),
+                ContextCompat.getColor(GBApplication.getContext(), R.color.hrv_status_balanced),
+                ContextCompat.getColor(GBApplication.getContext(), R.color.hrv_status_unbalanced),
+        };
+    }
+
+    public static float[] getSegments() {
+        return new float[]{
+                0.125f, // low
+                0.125f, // unbalanced
+                0.5f, // normal
+                0.25f, // unbalanced
+        };
+    }
+
+    public static float calculateGaugeValue(int weeklyAverage, int baselineLowUpper, int baselineBalancedLower, int baselineBalancedUpper) {
+        final float value;
+        if (weeklyAverage != 0 && baselineLowUpper != 0 && baselineBalancedLower != 0 && baselineBalancedUpper != 0) {
+            if (weeklyAverage <= baselineLowUpper) {
+                value = (float) GaugeDrawer.normalize(weeklyAverage, 0f, baselineLowUpper, 0, 0.124f);
+            } else if (weeklyAverage < baselineBalancedLower) {
+                value = (float) GaugeDrawer.normalize((float) weeklyAverage, baselineLowUpper + 1, baselineBalancedLower - 1, 0.126f, 0.249f);
+            } else if (weeklyAverage <= baselineBalancedUpper) {
+                value = (float) GaugeDrawer.normalize((float) weeklyAverage, baselineBalancedLower, baselineBalancedUpper, 0.251f, 0.749f);
+            } else {
+                value = (float) GaugeDrawer.normalize((float) weeklyAverage, baselineBalancedUpper, 2 * baselineBalancedUpper, 0.751f, 1);
+            }
+        } else {
+            value = -1;
+        }
+        return value;
+    }
+
+    private static class HrvData implements Serializable {
+        private int weeklyAverage;
+        private int lastNightAverage;
+        private int lastNight5MinHigh;
+        private int baselineLowUpper;
+        private int baselineBalancedLower;
+        private int baselineBalancedUpper;
+        private int statusNum;
+
+        public boolean hasBaselines() {
+            return baselineLowUpper != 0 && baselineBalancedLower != 0 && baselineBalancedUpper != 0;
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/DashboardSleepScoreWidget.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/DashboardSleepScoreWidget.java
new file mode 100644
index 0000000..b542224
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/DashboardSleepScoreWidget.java
@@ -0,0 +1,105 @@
+/*  Copyright (C) 2023-2024 a0z
+
+    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 <http://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities.dashboard;
+
+import android.os.Bundle;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.Serializable;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.DashboardFragment;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.TimeSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.SleepScoreSample;
+
+/**
+ * A simple {@link AbstractDashboardWidget} subclass.
+ * Use the {@link DashboardSleepScoreWidget#newInstance} factory method to
+ * create an instance of this fragment.
+ */
+public class DashboardSleepScoreWidget extends AbstractGaugeWidget {
+    protected static final Logger LOG = LoggerFactory.getLogger(AbstractDashboardVO2MaxWidget.class);
+    public DashboardSleepScoreWidget() {
+        super(R.string.sleep_score, "sleep");
+    }
+
+    /**
+     * Use this factory method to create a new instance of
+     * this fragment using the provided parameters.
+     *
+     * @param dashboardData An instance of DashboardFragment.DashboardData.
+     * @return A new instance of fragment DashboardSleepScoreWidget.
+     */
+    public static DashboardSleepScoreWidget newInstance(final DashboardFragment.DashboardData dashboardData) {
+        final DashboardSleepScoreWidget fragment = new DashboardSleepScoreWidget();
+        final Bundle args = new Bundle();
+        args.putSerializable(ARG_DASHBOARD_DATA, dashboardData);
+        fragment.setArguments(args);
+        return fragment;
+    }
+
+    @Override
+    protected void populateData(final DashboardFragment.DashboardData dashboardData) {
+        final List<GBDevice> devices = getSupportedDevices(dashboardData);
+        final SleepScoreData data = new SleepScoreData();
+
+        SleepScoreSample sample = null;
+        try (DBHandler dbHandler = GBApplication.acquireDB()) {
+            for (GBDevice dev : devices) {
+                TimeSampleProvider<? extends SleepScoreSample> provider = dev.getDeviceCoordinator().getSleepScoreProvider(dev, dbHandler.getDaoSession());
+                final SleepScoreSample latestSample = provider.getLatestSample(dashboardData.timeTo * 1000L);
+                if (latestSample != null && (sample == null || latestSample.getTimestamp() > sample.getTimestamp())) {
+                    sample = latestSample;
+                }
+            }
+
+            if (sample != null) {
+                data.value = sample.getSleepScore();
+            }
+
+        } catch (final Exception e) {
+            LOG.error("Could not get vo2max for today", e);
+        }
+
+        dashboardData.put("sleepscore", data);
+    }
+
+    @Override
+    protected void draw(final DashboardFragment.DashboardData dashboardData) {
+        final SleepScoreData data = (SleepScoreData) dashboardData.get("sleepscore");
+        setText(String.valueOf(data.value));
+        drawSimpleGauge(
+                color_light_sleep,
+                (float) data.value / 100
+        );
+    }
+
+    @Override
+    protected boolean isSupportedBy(final GBDevice device) {
+        return device.getDeviceCoordinator().supportsSleepScore();
+    }
+
+    private static class SleepScoreData implements Serializable {
+        public int value = -1;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/DashboardSleepWidget.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/DashboardSleepWidget.java
new file mode 100644
index 0000000..41818bd
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/DashboardSleepWidget.java
@@ -0,0 +1,79 @@
+/*  Copyright (C) 2023-2024 Arjan Schrijver, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities.dashboard;
+
+import android.os.Bundle;
+
+import java.util.Locale;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.DashboardFragment;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+/**
+ * A simple {@link AbstractDashboardWidget} subclass.
+ * Use the {@link DashboardSleepWidget#newInstance} factory method to
+ * create an instance of this fragment.
+ */
+public class DashboardSleepWidget extends AbstractGaugeWidget {
+    public DashboardSleepWidget() {
+        super(R.string.menuitem_sleep, "sleep");
+    }
+
+    /**
+     * Use this factory method to create a new instance of
+     * this fragment using the provided parameters.
+     *
+     * @param dashboardData An instance of DashboardFragment.DashboardData.
+     * @return A new instance of fragment DashboardSleepWidget.
+     */
+    public static DashboardSleepWidget newInstance(final DashboardFragment.DashboardData dashboardData) {
+        final DashboardSleepWidget fragment = new DashboardSleepWidget();
+        final Bundle args = new Bundle();
+        args.putSerializable(ARG_DASHBOARD_DATA, dashboardData);
+        fragment.setArguments(args);
+        return fragment;
+    }
+
+    @Override
+    protected boolean isSupportedBy(final GBDevice device) {
+        return device.getDeviceCoordinator().supportsSleepMeasurement();
+    }
+
+    @Override
+    protected void populateData(final DashboardFragment.DashboardData dashboardData) {
+        dashboardData.getSleepMinutesTotal();
+        dashboardData.getSleepMinutesGoalFactor();
+    }
+
+    @Override
+    protected void draw(final DashboardFragment.DashboardData dashboardData) {
+        final long totalSleepMinutes = dashboardData.getSleepMinutesTotal();
+        final String valueText = String.format(
+                Locale.ROOT,
+                "%d:%02d",
+                (int) Math.floor(totalSleepMinutes / 60f),
+                (int) (totalSleepMinutes % 60f)
+        );
+
+        setText(valueText);
+        drawSimpleGauge(
+                color_light_sleep,
+                dashboardData.getSleepMinutesGoalFactor()
+        );
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/DashboardStepsWidget.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/DashboardStepsWidget.java
new file mode 100644
index 0000000..d86e1e3
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/DashboardStepsWidget.java
@@ -0,0 +1,63 @@
+/*  Copyright (C) 2023-2024 Arjan Schrijver, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities.dashboard;
+
+import android.os.Bundle;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.DashboardFragment;
+
+/**
+ * A simple {@link AbstractDashboardWidget} subclass.
+ * Use the {@link DashboardStepsWidget#newInstance} factory method to
+ * create an instance of this fragment.
+ */
+public class DashboardStepsWidget extends AbstractGaugeWidget {
+    public DashboardStepsWidget() {
+        super(R.string.steps, "stepsweek");
+    }
+
+    /**
+     * Use this factory method to create a new instance of
+     * this fragment using the provided parameters.
+     *
+     * @param dashboardData An instance of DashboardFragment.DashboardData.
+     * @return A new instance of fragment DashboardStepsWidget.
+     */
+    public static DashboardStepsWidget newInstance(final DashboardFragment.DashboardData dashboardData) {
+        final DashboardStepsWidget fragment = new DashboardStepsWidget();
+        final Bundle args = new Bundle();
+        args.putSerializable(ARG_DASHBOARD_DATA, dashboardData);
+        fragment.setArguments(args);
+        return fragment;
+    }
+
+    @Override
+    protected void populateData(final DashboardFragment.DashboardData dashboardData) {
+        dashboardData.getStepsTotal();
+        dashboardData.getStepsGoalFactor();
+    }
+
+    @Override
+    protected void draw(final DashboardFragment.DashboardData dashboardData) {
+        setText(String.valueOf(dashboardData.getStepsTotal()));
+        drawSimpleGauge(
+                color_activity,
+                dashboardData.getStepsGoalFactor()
+        );
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/DashboardStressBreakdownWidget.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/DashboardStressBreakdownWidget.java
new file mode 100644
index 0000000..af98222
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/DashboardStressBreakdownWidget.java
@@ -0,0 +1,89 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities.dashboard;
+
+import android.os.Bundle;
+
+import androidx.core.content.ContextCompat;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.DashboardFragment;
+import nodomain.freeyourgadget.gadgetbridge.activities.dashboard.data.DashboardStressData;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class DashboardStressBreakdownWidget extends AbstractGaugeWidget {
+    public DashboardStressBreakdownWidget() {
+        super(R.string.menuitem_stress, "stress");
+    }
+
+    public static DashboardStressBreakdownWidget newInstance(final DashboardFragment.DashboardData dashboardData) {
+        final DashboardStressBreakdownWidget fragment = new DashboardStressBreakdownWidget();
+        final Bundle args = new Bundle();
+        args.putSerializable(ARG_DASHBOARD_DATA, dashboardData);
+        fragment.setArguments(args);
+        return fragment;
+    }
+
+    @Override
+    protected boolean isSupportedBy(final GBDevice device) {
+        return device.getDeviceCoordinator().supportsStressMeasurement();
+    }
+
+    @Override
+    protected void populateData(final DashboardFragment.DashboardData dashboardData) {
+        dashboardData.computeIfAbsent("stress", () -> DashboardStressData.compute(dashboardData));
+    }
+
+    @Override
+    protected void draw(final DashboardFragment.DashboardData dashboardData) {
+        final DashboardStressData stressData = (DashboardStressData) dashboardData.get("stress");
+        if (stressData == null) {
+            drawSimpleGauge(0, -1);
+            return;
+        }
+
+        final int[] colors = new int[]{
+                ContextCompat.getColor(GBApplication.getContext(), R.color.chart_stress_relaxed),
+                ContextCompat.getColor(GBApplication.getContext(), R.color.chart_stress_mild),
+                ContextCompat.getColor(GBApplication.getContext(), R.color.chart_stress_moderate),
+                ContextCompat.getColor(GBApplication.getContext(), R.color.chart_stress_high),
+        };
+
+        final float[] segments = new float[4];
+
+        int sum = 0;
+        for (final int stressTime : stressData.totalTime) {
+            sum += stressTime;
+        }
+        if (sum != 0) {
+            for (int i = 0; i < 4; i++) {
+                segments[i] = stressData.totalTime[i] / (float) sum;
+            }
+        }
+
+        setText(String.valueOf(stressData.value));
+
+        drawSegmentedGauge(
+                colors,
+                segments,
+                -1,
+                false,
+                true
+        );
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/DashboardStressSegmentedWidget.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/DashboardStressSegmentedWidget.java
new file mode 100644
index 0000000..7f39697
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/DashboardStressSegmentedWidget.java
@@ -0,0 +1,96 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities.dashboard;
+
+import android.os.Bundle;
+
+import androidx.core.content.ContextCompat;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.DashboardFragment;
+import nodomain.freeyourgadget.gadgetbridge.activities.dashboard.data.DashboardStressData;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class DashboardStressSegmentedWidget extends AbstractGaugeWidget {
+    public DashboardStressSegmentedWidget() {
+        super(R.string.menuitem_stress, "stress");
+    }
+
+    public static DashboardStressSegmentedWidget newInstance(final DashboardFragment.DashboardData dashboardData) {
+        final DashboardStressSegmentedWidget fragment = new DashboardStressSegmentedWidget();
+        final Bundle args = new Bundle();
+        args.putSerializable(ARG_DASHBOARD_DATA, dashboardData);
+        fragment.setArguments(args);
+        return fragment;
+    }
+
+    @Override
+    protected boolean isSupportedBy(final GBDevice device) {
+        return device.getDeviceCoordinator().supportsStressMeasurement();
+    }
+
+    @Override
+    protected void populateData(final DashboardFragment.DashboardData dashboardData) {
+        dashboardData.computeIfAbsent("stress", () -> DashboardStressData.compute(dashboardData));
+    }
+
+    @Override
+    protected void draw(final DashboardFragment.DashboardData dashboardData) {
+        final int[] colors = new int[]{
+                ContextCompat.getColor(GBApplication.getContext(), R.color.chart_stress_relaxed),
+                ContextCompat.getColor(GBApplication.getContext(), R.color.chart_stress_mild),
+                ContextCompat.getColor(GBApplication.getContext(), R.color.chart_stress_moderate),
+                ContextCompat.getColor(GBApplication.getContext(), R.color.chart_stress_high),
+        };
+
+        final float[] segments;
+        final float value;
+        final String valueText;
+
+        final DashboardStressData stressData = (DashboardStressData) dashboardData.get("stress");
+
+        if (stressData != null) {
+            segments = new float[]{
+                    (stressData.ranges[1] - stressData.ranges[0]) / 100f,
+                    (stressData.ranges[2] - stressData.ranges[1]) / 100f,
+                    (stressData.ranges[3] - stressData.ranges[2]) / 100f,
+                    1 - stressData.ranges[2] / 100f,
+            };
+            value = stressData.value / 100f;
+            valueText = String.valueOf(stressData.value);
+        } else {
+            segments = new float[]{
+                    40 / 100f,
+                    20 / 100f,
+                    20 / 100f,
+                    20 / 100f,
+            };
+            value = -1;
+            valueText = GBApplication.getContext().getString(R.string.stats_empty_value);
+        }
+
+        setText(valueText);
+        drawSegmentedGauge(
+                colors,
+                segments,
+                value,
+                false,
+                true
+        );
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/DashboardStressSimpleWidget.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/DashboardStressSimpleWidget.java
new file mode 100644
index 0000000..d1f1149
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/DashboardStressSimpleWidget.java
@@ -0,0 +1,70 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities.dashboard;
+
+import android.os.Bundle;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.DashboardFragment;
+import nodomain.freeyourgadget.gadgetbridge.activities.charts.StressChartFragment;
+import nodomain.freeyourgadget.gadgetbridge.activities.dashboard.data.DashboardStressData;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class DashboardStressSimpleWidget extends AbstractGaugeWidget {
+    public DashboardStressSimpleWidget() {
+        super(R.string.menuitem_stress, "stress");
+    }
+
+    public static DashboardStressSimpleWidget newInstance(final DashboardFragment.DashboardData dashboardData) {
+        final DashboardStressSimpleWidget fragment = new DashboardStressSimpleWidget();
+        final Bundle args = new Bundle();
+        args.putSerializable(ARG_DASHBOARD_DATA, dashboardData);
+        fragment.setArguments(args);
+        return fragment;
+    }
+
+    @Override
+    protected boolean isSupportedBy(final GBDevice device) {
+        return device.getDeviceCoordinator().supportsStressMeasurement();
+    }
+
+    @Override
+    protected void populateData(final DashboardFragment.DashboardData dashboardData) {
+        dashboardData.computeIfAbsent("stress", () -> DashboardStressData.compute(dashboardData));
+    }
+
+    @Override
+    protected void draw(final DashboardFragment.DashboardData dashboardData) {
+        final DashboardStressData stressData = (DashboardStressData) dashboardData.get("stress");
+        if (stressData == null) {
+            drawSimpleGauge(0, -1);
+            return;
+        }
+
+        final int color = StressChartFragment.StressType.fromStress(
+                stressData.value,
+                stressData.ranges
+        ).getColor(GBApplication.getContext());
+
+        final float value = stressData.value / 100f;
+        final String valueText = String.valueOf(stressData.value);
+
+        setText(valueText);
+        drawSimpleGauge(color, value);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/DashboardTodayWidget.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/DashboardTodayWidget.java
new file mode 100644
index 0000000..7e1f58b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/DashboardTodayWidget.java
@@ -0,0 +1,600 @@
+/*  Copyright (C) 2023-2024 Arjan Schrijver, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities.dashboard;
+
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.SpannableStringBuilder;
+import android.text.format.DateFormat;
+import android.text.style.ForegroundColorSpan;
+import android.util.TypedValue;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.stream.Collectors;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.DashboardFragment;
+import nodomain.freeyourgadget.gadgetbridge.activities.HeartRateUtils;
+import nodomain.freeyourgadget.gadgetbridge.activities.charts.StepAnalysis;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySession;
+import nodomain.freeyourgadget.gadgetbridge.util.DashboardUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+/**
+ * A simple {@link AbstractDashboardWidget} subclass.
+ * Use the {@link DashboardTodayWidget#newInstance} factory method to
+ * create an instance of this fragment.
+ */
+public class DashboardTodayWidget extends AbstractDashboardWidget {
+    private static final Logger LOG = LoggerFactory.getLogger(DashboardTodayWidget.class);
+
+    private View todayView;
+    private ImageView todayChart;
+
+    private boolean mode_24h;
+
+    public DashboardTodayWidget() {
+        // Required empty public constructor
+    }
+
+    /**
+     * Use this factory method to create a new instance of
+     * this fragment using the provided parameters.
+     *
+     * @param dashboardData An instance of DashboardFragment.DashboardData.
+     * @return A new instance of fragment DashboardTodayWidget.
+     */
+    public static DashboardTodayWidget newInstance(DashboardFragment.DashboardData dashboardData) {
+        DashboardTodayWidget fragment = new DashboardTodayWidget();
+        Bundle args = new Bundle();
+        args.putSerializable(ARG_DASHBOARD_DATA, dashboardData);
+        fragment.setArguments(args);
+        return fragment;
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+        todayView = inflater.inflate(R.layout.dashboard_widget_today, container, false);
+        todayChart = todayView.findViewById(R.id.dashboard_today_chart);
+
+        // Determine whether to draw a single or a double chart. In case 24h mode is selected,
+        // use just the outer chart (chart_12_24) for all data.
+        Prefs prefs = GBApplication.getPrefs();
+        mode_24h = prefs.getBoolean("dashboard_widget_today_24h", false);
+
+        // Initialize legend
+        TextView legend = todayView.findViewById(R.id.dashboard_piechart_legend);
+        SpannableString l_not_worn = new SpannableString("鈻� " + getString(R.string.abstract_chart_fragment_kind_not_worn));
+        l_not_worn.setSpan(new ForegroundColorSpan(color_not_worn), 0, 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+        SpannableString l_worn = new SpannableString("鈻� " + getString(R.string.activity_type_worn));
+        l_worn.setSpan(new ForegroundColorSpan(color_worn), 0, 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+        SpannableString l_activity = new SpannableString("鈻� " + getString(R.string.activity_type_activity));
+        l_activity.setSpan(new ForegroundColorSpan(color_activity), 0, 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+        SpannableString l_exercise = new SpannableString("鈻� " + getString(R.string.activity_type_exercise));
+        l_exercise.setSpan(new ForegroundColorSpan(color_exercise), 0, 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+        SpannableString l_deep_sleep = new SpannableString("鈻� " + getString(R.string.activity_type_deep_sleep));
+        l_deep_sleep.setSpan(new ForegroundColorSpan(color_deep_sleep), 0, 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+        SpannableString l_light_sleep = new SpannableString("鈻� " + getString(R.string.activity_type_light_sleep));
+        l_light_sleep.setSpan(new ForegroundColorSpan(color_light_sleep), 0, 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+        SpannableString l_rem_sleep = new SpannableString("鈻� " + getString(R.string.activity_type_rem_sleep));
+        l_rem_sleep.setSpan(new ForegroundColorSpan(color_rem_sleep), 0, 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+        SpannableStringBuilder legendBuilder = new SpannableStringBuilder();
+        legend.setText(legendBuilder.append(l_not_worn).append(" ").append(l_worn).append("\n").append(l_activity).append(" ").append(l_exercise).append("\n").append(l_light_sleep).append(" ").append(l_deep_sleep).append(" ").append(l_rem_sleep));
+
+        legend.setVisibility(prefs.getBoolean("dashboard_widget_today_legend", true) ? View.VISIBLE : View.GONE);
+
+        if (!dashboardData.generalizedActivities.isEmpty()) {
+            draw();
+        }
+
+        return todayView;
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        if (todayChart != null) fillData();
+    }
+
+    private void draw() {
+        Prefs prefs = GBApplication.getPrefs();
+        boolean upsideDown24h = prefs.getBoolean("dashboard_widget_today_24h_upside_down", false);
+        boolean showYesterday = prefs.getBoolean("dashboard_widget_today_show_yesterday", false);
+
+        // Prepare circular chart
+        long currentDayStart = dashboardData.timeTo - 86400;
+        long midDaySecond = currentDayStart + (12 * 60 * 60);
+        int width = Resources.getSystem().getDisplayMetrics().widthPixels;
+        int height = width;
+        int barWidth = Math.round(width * 0.08f);
+        int hourTextSp = Math.round(width * 0.024f);
+        float hourTextPixels = TypedValue.applyDimension(
+                TypedValue.COMPLEX_UNIT_SP,
+                hourTextSp,
+                GBApplication.getContext().getResources().getDisplayMetrics()
+        );
+        float outerCircleMargin = mode_24h ? barWidth / 2f : barWidth / 2f + hourTextPixels * 1.3f;
+        float innerCircleMargin = outerCircleMargin + barWidth * 1.3f;
+        float degreeFactor = mode_24h ? 240 : 120;
+        Bitmap todayBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+        Canvas canvas = new Canvas(todayBitmap);
+        Paint paint = new Paint();
+        paint.setAntiAlias(true);
+        paint.setStyle(Paint.Style.STROKE);
+
+        // Draw clock stripes
+        float clockMargin = outerCircleMargin + (mode_24h ? barWidth : barWidth * 2.3f);
+        int clockStripesInterval = mode_24h ? 15 : 30;
+        float clockStripesWidth = barWidth / 3f;
+        paint.setStrokeWidth(clockStripesWidth);
+        paint.setColor(color_worn);
+        for (int i = 0; i < 360; i += clockStripesInterval) {
+            canvas.drawArc(clockMargin, clockMargin, width - clockMargin, height - clockMargin, i, 1, false, paint);
+        }
+
+        // Draw hours
+        boolean normalClock = DateFormat.is24HourFormat(GBApplication.getContext());
+        Map<Integer, String> hours = new HashMap<Integer, String>() {
+            {
+                put(0, normalClock ? (mode_24h ? "0" : "12") : "12pm");
+                put(3, "3");
+                put(6, normalClock ? "6" : "6am");
+                put(9, "9");
+                put(12, normalClock ? (mode_24h ? "12" : "0") : "12am");
+                put(15, normalClock ? "15" : "3");
+                put(18, normalClock ? "18" : "6pm");
+                put(21, normalClock ? "21" : "9");
+            }
+        };
+        Paint textPaint = new Paint();
+        textPaint.setAntiAlias(true);
+        textPaint.setColor(color_worn);
+        textPaint.setTextSize(hourTextPixels);
+        textPaint.setTextAlign(Paint.Align.CENTER);
+        Rect textBounds = new Rect();
+        if (mode_24h && upsideDown24h) {
+            textPaint.getTextBounds(hours.get(0), 0, hours.get(0).length(), textBounds);
+            canvas.drawText(hours.get(0), width / 2f, height - (clockMargin + clockStripesWidth), textPaint);
+            textPaint.getTextBounds(hours.get(6), 0, hours.get(6).length(), textBounds);
+            canvas.drawText(hours.get(6), clockMargin + clockStripesWidth + textBounds.width() / 2f, height / 2f + textBounds.height() / 2f, textPaint);
+            textPaint.getTextBounds(hours.get(12), 0, hours.get(12).length(), textBounds);
+            canvas.drawText(hours.get(12), width / 2f, clockMargin + clockStripesWidth + textBounds.height(), textPaint);
+            textPaint.getTextBounds(hours.get(18), 0, hours.get(18).length(), textBounds);
+            canvas.drawText(hours.get(18), width - (clockMargin + clockStripesWidth + textBounds.width()), height / 2f + textBounds.height() / 2f, textPaint);
+        } else if (mode_24h) {
+            textPaint.getTextBounds(hours.get(0), 0, hours.get(0).length(), textBounds);
+            canvas.drawText(hours.get(0), width / 2f, clockMargin + clockStripesWidth + textBounds.height(), textPaint);
+            textPaint.getTextBounds(hours.get(6), 0, hours.get(6).length(), textBounds);
+            canvas.drawText(hours.get(6), width - (clockMargin + clockStripesWidth + textBounds.width()), height / 2f + textBounds.height() / 2f, textPaint);
+            textPaint.getTextBounds(hours.get(12), 0, hours.get(12).length(), textBounds);
+            canvas.drawText(hours.get(12), width / 2f, height - (clockMargin + clockStripesWidth), textPaint);
+            textPaint.getTextBounds(hours.get(18), 0, hours.get(18).length(), textBounds);
+            canvas.drawText(hours.get(18), clockMargin + clockStripesWidth + textBounds.width() / 2f, height / 2f + textBounds.height() / 2f, textPaint);
+        } else {
+            textPaint.getTextBounds(hours.get(0), 0, hours.get(0).length(), textBounds);
+            canvas.drawText(hours.get(0), width / 2f, textBounds.height(), textPaint);
+            textPaint.getTextBounds(hours.get(3), 0, hours.get(3).length(), textBounds);
+            canvas.drawText(hours.get(3), width - (clockMargin + clockStripesWidth + textBounds.width()), height / 2f + textBounds.height() / 2f, textPaint);
+            textPaint.getTextBounds(hours.get(6), 0, hours.get(6).length(), textBounds);
+            canvas.drawText(hours.get(6), width / 2f, height - (clockMargin + clockStripesWidth), textPaint);
+            textPaint.getTextBounds(hours.get(9), 0, hours.get(9).length(), textBounds);
+            canvas.drawText(hours.get(9), clockMargin + clockStripesWidth + textBounds.width() / 2f, height / 2f + textBounds.height() / 2f, textPaint);
+            textPaint.getTextBounds(hours.get(12), 0, hours.get(12).length(), textBounds);
+            canvas.drawText(hours.get(12), width / 2f, clockMargin + clockStripesWidth + textBounds.height(), textPaint);
+            textPaint.getTextBounds(hours.get(15), 0, hours.get(15).length(), textBounds);
+            canvas.drawText(hours.get(15), (float) (width - Math.ceil(textBounds.width() / 2f)), height / 2f + textBounds.height() / 2f, textPaint);
+            textPaint.getTextBounds(hours.get(18), 0, hours.get(18).length(), textBounds);
+            canvas.drawText(hours.get(18), width / 2f, height - textBounds.height() / 2f, textPaint);
+            textPaint.setTextAlign(Paint.Align.LEFT);
+            textPaint.getTextBounds(hours.get(21), 0, hours.get(21).length(), textBounds);
+            canvas.drawText(hours.get(21), 1, height / 2f + textBounds.height() / 2f, textPaint);
+        }
+
+        // Draw generalized activities on circular chart
+        long secondIndex = dashboardData.timeFrom;
+        long currentTime = Calendar.getInstance().getTimeInMillis() / 1000;
+        boolean dayIsToday = !(dashboardData.timeTo < currentTime);
+        int startAngle = mode_24h && upsideDown24h ? 90 : 270;
+        synchronized (dashboardData.generalizedActivities) {
+            for (DashboardFragment.DashboardData.GeneralizedActivity activity : dashboardData.generalizedActivities) {
+                // Determine margin
+                float margin = innerCircleMargin;
+                if (mode_24h || activity.timeFrom >= midDaySecond) {
+                    margin = outerCircleMargin;
+                }
+                if (!mode_24h && showYesterday && dayIsToday) {
+                    if (activity.timeFrom < currentDayStart && activity.timeFrom > midDaySecond - 86400) {
+                        margin = outerCircleMargin;
+                    }
+                }
+                // Skip activities from before 24h ago (to prevent double drawing the same position)
+                if (showYesterday && dayIsToday && (activity.timeTo < currentTime - 86400)) {
+                    continue;
+                }
+                // Draw inactive slices
+                if (!mode_24h && secondIndex < midDaySecond && activity.timeFrom >= midDaySecond) {
+                    paint.setStrokeWidth(barWidth / 3f);
+                    paint.setColor(color_unknown);
+                    canvas.drawArc(innerCircleMargin, innerCircleMargin, width - innerCircleMargin, height - innerCircleMargin, startAngle + (secondIndex - dashboardData.timeFrom) / degreeFactor, (midDaySecond - secondIndex) / degreeFactor, false, paint);
+                    secondIndex = midDaySecond;
+                }
+                if (activity.timeFrom > secondIndex) {
+                    paint.setStrokeWidth(barWidth / 3f);
+                    paint.setColor(color_unknown);
+                    canvas.drawArc(margin, margin, width - margin, height - margin, startAngle + (secondIndex - dashboardData.timeFrom) / degreeFactor, (activity.timeFrom - secondIndex) / degreeFactor, false, paint);
+                }
+                float start_angle = startAngle + (activity.timeFrom - dashboardData.timeFrom) / degreeFactor;
+                float sweep_angle = (activity.timeTo - activity.timeFrom) / degreeFactor;
+                if (activity.activityKind == ActivityKind.NOT_MEASURED) {
+                    paint.setStrokeWidth(barWidth / 3f);
+                    paint.setColor(color_worn);
+                    if (showYesterday && dayIsToday && activity.timeFrom < currentDayStart) {
+                        paint.setAlpha(64);
+                    }
+                    canvas.drawArc(margin, margin, width - margin, height - margin, start_angle, sweep_angle, false, paint);
+                } else if (activity.activityKind == ActivityKind.NOT_WORN) {
+                    paint.setStrokeWidth(barWidth / 3f);
+                    paint.setColor(color_not_worn);
+                    if (showYesterday && dayIsToday && activity.timeFrom < currentDayStart) {
+                        paint.setAlpha(64);
+                    }
+                    canvas.drawArc(margin, margin, width - margin, height - margin, start_angle, sweep_angle, false, paint);
+                } else if (activity.activityKind == ActivityKind.LIGHT_SLEEP || activity.activityKind == ActivityKind.SLEEP_ANY) {
+                    paint.setStrokeWidth(barWidth);
+                    paint.setColor(color_light_sleep);
+                    if (showYesterday && dayIsToday && activity.timeFrom < currentDayStart) {
+                        paint.setAlpha(64);
+                    }
+                    canvas.drawArc(margin, margin, width - margin, height - margin, start_angle, sweep_angle, false, paint);
+                } else if (activity.activityKind == ActivityKind.REM_SLEEP) {
+                    paint.setStrokeWidth(barWidth);
+                    paint.setColor(color_rem_sleep);
+                    if (showYesterday && dayIsToday && activity.timeFrom < currentDayStart) {
+                        paint.setAlpha(64);
+                    }
+                    canvas.drawArc(margin, margin, width - margin, height - margin, start_angle, sweep_angle, false, paint);
+                } else if (activity.activityKind == ActivityKind.DEEP_SLEEP) {
+                    paint.setStrokeWidth(barWidth);
+                    paint.setColor(color_deep_sleep);
+                    if (showYesterday && dayIsToday && activity.timeFrom < currentDayStart) {
+                        paint.setAlpha(64);
+                    }
+                    canvas.drawArc(margin, margin, width - margin, height - margin, start_angle, sweep_angle, false, paint);
+                } else if (activity.activityKind == ActivityKind.AWAKE_SLEEP) {
+                    paint.setStrokeWidth(barWidth);
+                    paint.setColor(color_awake_sleep);
+                    if (showYesterday && dayIsToday && activity.timeFrom < currentDayStart) {
+                        paint.setAlpha(64);
+                    }
+                    canvas.drawArc(margin, margin, width - margin, height - margin, start_angle, sweep_angle, false, paint);
+                } else if (activity.activityKind == ActivityKind.EXERCISE) {
+                    paint.setStrokeWidth(barWidth);
+                    paint.setColor(color_exercise);
+                    if (showYesterday && dayIsToday && activity.timeFrom < currentDayStart) {
+                        paint.setAlpha(64);
+                    }
+                    canvas.drawArc(margin, margin, width - margin, height - margin, start_angle, sweep_angle, false, paint);
+                } else {
+                    paint.setStrokeWidth(barWidth);
+                    paint.setColor(color_activity);
+                    if (showYesterday && dayIsToday && activity.timeFrom < currentDayStart) {
+                        paint.setAlpha(64);
+                    }
+                    canvas.drawArc(margin, margin, width - margin, height - margin, start_angle, sweep_angle, false, paint);
+                }
+                secondIndex = activity.timeTo;
+            }
+        }
+        // Draw indicator for current time
+        if (prefs.getBoolean("dashboard_widget_today_time_indicator", false) && currentTime < dashboardData.timeTo) {
+            float margin = (mode_24h || currentTime >= midDaySecond) ? outerCircleMargin : innerCircleMargin;
+            paint.setStrokeWidth(barWidth);
+            paint.setColor(GBApplication.getTextColor(requireContext()));
+            canvas.drawArc(margin, margin, width - margin, height - margin, startAngle + (currentTime - dashboardData.timeFrom) / degreeFactor, 300 / degreeFactor, false, paint);
+        }
+        // Fill remaining time until current time in 12h mode before midday
+        if (!mode_24h && currentTime < midDaySecond) {
+            // Fill inner bar up until current time
+            paint.setStrokeWidth(barWidth / 3f);
+            paint.setColor(color_unknown);
+            canvas.drawArc(innerCircleMargin, innerCircleMargin, width - innerCircleMargin, height - innerCircleMargin, startAngle + (secondIndex - dashboardData.timeFrom) / degreeFactor, (currentTime - secondIndex) / degreeFactor, false, paint);
+            // Fill inner bar up until midday
+            paint.setStrokeWidth(barWidth / 3f);
+            paint.setColor(color_unknown);
+            canvas.drawArc(innerCircleMargin, innerCircleMargin, width - innerCircleMargin, height - innerCircleMargin, startAngle + (currentTime - dashboardData.timeFrom) / degreeFactor, (midDaySecond - currentTime) / degreeFactor, false, paint);
+            // Fill outer bar up until midnight
+            paint.setStrokeWidth(barWidth / 3f);
+            paint.setColor(color_unknown);
+            canvas.drawArc(outerCircleMargin, outerCircleMargin, width - outerCircleMargin, height - outerCircleMargin, 0, 360, false, paint);
+        }
+        // Fill remaining time until current time in 24h mode or in 12h mode after midday
+        if ((mode_24h || currentTime >= midDaySecond) && currentTime < dashboardData.timeTo) {
+            // Fill inner bar up until midday
+            if (!mode_24h && secondIndex < midDaySecond) {
+                paint.setStrokeWidth(barWidth / 3f);
+                paint.setColor(color_unknown);
+                canvas.drawArc(innerCircleMargin, innerCircleMargin, width - innerCircleMargin, height - innerCircleMargin, startAngle + (secondIndex - dashboardData.timeFrom) / degreeFactor, (midDaySecond - secondIndex) / degreeFactor, false, paint);
+                secondIndex = midDaySecond;
+            }
+            // Fill outer bar up until current time
+            paint.setStrokeWidth(barWidth / 3f);
+            paint.setColor(color_unknown);
+            canvas.drawArc(outerCircleMargin, outerCircleMargin, width - outerCircleMargin, height - outerCircleMargin, startAngle + (secondIndex - dashboardData.timeFrom) / degreeFactor, (currentTime - secondIndex) / degreeFactor, false, paint);
+            // Fill outer bar up until midnight
+            paint.setStrokeWidth(barWidth / 3f);
+            paint.setColor(color_unknown);
+            canvas.drawArc(outerCircleMargin, outerCircleMargin, width - outerCircleMargin, height - outerCircleMargin, startAngle + (currentTime - dashboardData.timeFrom) / degreeFactor, (dashboardData.timeTo - currentTime) / degreeFactor, false, paint);
+        }
+        // Only when displaying a past day
+        if (secondIndex < dashboardData.timeTo && currentTime > dashboardData.timeTo) {
+            // Fill outer bar up until midnight
+            paint.setStrokeWidth(barWidth / 3f);
+            paint.setColor(color_unknown);
+            canvas.drawArc(outerCircleMargin, outerCircleMargin, width - outerCircleMargin, height - outerCircleMargin, startAngle + (secondIndex - dashboardData.timeFrom) / degreeFactor, (dashboardData.timeTo - secondIndex) / degreeFactor, false, paint);
+        }
+
+        todayChart.setImageBitmap(todayBitmap);
+    }
+
+    protected void fillData() {
+        if (todayView == null) return;
+
+        Prefs prefs = GBApplication.getPrefs();
+        if (prefs.getBoolean("dashboard_widget_today_show_yesterday", false)) {
+            Calendar today = Calendar.getInstance();
+            Calendar dashboardDate = Calendar.getInstance();
+            dashboardDate.setTimeInMillis((dashboardData.timeFrom + 1) * 1000L);
+            if (DateTimeUtils.isSameDay(today, dashboardDate)) {
+                dashboardData.timeFrom -= 86400;
+            }
+        }
+
+        todayView.post(new Runnable() {
+            @Override
+            public void run() {
+                FillDataAsyncTask myAsyncTask = new FillDataAsyncTask();
+                myAsyncTask.execute();
+            }
+        });
+    }
+
+    private class FillDataAsyncTask extends AsyncTask<Void, Void, Void> {
+        private final HashMap<Long, ActivityKind> activityTimestamps = new HashMap<>();
+
+        /**
+         * Add per-second activities to `activityTimestamps`
+         */
+        private void addActivity(long timeFrom, long timeTo, ActivityKind activityKind) {
+            for (long i = timeFrom; i <= timeTo; i++) {
+                // If the current timestamp isn't saved yet, do so immediately
+                if (activityTimestamps.get(i) == null) {
+                    activityTimestamps.put(i, activityKind);
+                    continue;
+                }
+                // If the current timestamp is already saved, compare the activity kinds and
+                // keep the most 'important' one
+                switch (activityTimestamps.get(i)) {
+                    case EXERCISE:
+                        break;
+                    case ACTIVITY:
+                        if (activityKind == ActivityKind.EXERCISE)
+                            activityTimestamps.put(i, activityKind);
+                        break;
+                    case DEEP_SLEEP:
+                        if (activityKind == ActivityKind.EXERCISE ||
+                                activityKind == ActivityKind.ACTIVITY)
+                            activityTimestamps.put(i, activityKind);
+                        break;
+                    case LIGHT_SLEEP:
+                        if (activityKind == ActivityKind.EXERCISE ||
+                                activityKind == ActivityKind.ACTIVITY ||
+                                activityKind == ActivityKind.DEEP_SLEEP)
+                            activityTimestamps.put(i, activityKind);
+                        break;
+                    case REM_SLEEP:
+                        if (activityKind == ActivityKind.EXERCISE ||
+                                activityKind == ActivityKind.ACTIVITY ||
+                                activityKind == ActivityKind.DEEP_SLEEP ||
+                                activityKind == ActivityKind.LIGHT_SLEEP)
+                            activityTimestamps.put(i, activityKind);
+                        break;
+                    case AWAKE_SLEEP:
+                        if (activityKind == ActivityKind.EXERCISE ||
+                                activityKind == ActivityKind.ACTIVITY ||
+                                activityKind == ActivityKind.DEEP_SLEEP ||
+                                activityKind == ActivityKind.LIGHT_SLEEP ||
+                                activityKind == ActivityKind.REM_SLEEP)
+                            activityTimestamps.put(i, activityKind);
+                        break;
+                    case SLEEP_ANY:
+                    case NOT_MEASURED:
+                        if (activityKind == ActivityKind.EXERCISE ||
+                                activityKind == ActivityKind.ACTIVITY ||
+                                activityKind == ActivityKind.DEEP_SLEEP ||
+                                activityKind == ActivityKind.LIGHT_SLEEP ||
+                                activityKind == ActivityKind.REM_SLEEP ||
+                                activityKind == ActivityKind.AWAKE_SLEEP)
+                            activityTimestamps.put(i, activityKind);
+                        break;
+                    default:
+                        activityTimestamps.put(i, activityKind);
+                        break;
+                }
+            }
+        }
+
+        /**
+         * Add NOT_MEASURED (worn) activities for every successful heart rate measurement
+         */
+        private void calculateWornSessions(List<ActivitySample> samples) {
+            int firstTimestamp = 0;
+            int lastTimestamp = 0;
+
+            for (ActivitySample sample : samples) {
+                if (sample.getHeartRate() < 10 && firstTimestamp == 0) continue;
+                if (firstTimestamp == 0) firstTimestamp = sample.getTimestamp();
+                if (lastTimestamp == 0) lastTimestamp = sample.getTimestamp();
+                if (HeartRateUtils.getInstance().isValidHeartRateValue(sample.getHeartRate())
+                        && sample.getTimestamp() > lastTimestamp + dashboardData.hrIntervalSecs
+                        && firstTimestamp != lastTimestamp) {
+                    LOG.debug("Registered worn session from {} to {}", firstTimestamp, lastTimestamp);
+                    addActivity(firstTimestamp, lastTimestamp, ActivityKind.NOT_MEASURED);
+                    if (sample.getHeartRate() < 10) {
+                        firstTimestamp = 0;
+                        lastTimestamp = 0;
+                    } else {
+                        firstTimestamp = sample.getTimestamp();
+                        lastTimestamp = sample.getTimestamp();
+                    }
+                    continue;
+                }
+                if (HeartRateUtils.getInstance().isValidHeartRateValue(sample.getHeartRate())) {
+                    lastTimestamp = sample.getTimestamp();
+                }
+            }
+            if (firstTimestamp != lastTimestamp) {
+                LOG.debug("Registered worn session from {} to {}", firstTimestamp, lastTimestamp);
+                addActivity(firstTimestamp, lastTimestamp, ActivityKind.NOT_MEASURED);
+            }
+        }
+
+        /**
+         * Merge per-second activities from `activityTimestamps` into generalized activity ranges
+         * with minute-based resolution
+         */
+        private void createGeneralizedActivities() {
+            long currentTime = Calendar.getInstance().getTimeInMillis() / 1000;
+            long midDaySecond = dashboardData.timeTo - (12 * 60 * 60);
+            DashboardFragment.DashboardData.GeneralizedActivity previous = null;
+            List<Map.Entry<Long, ActivityKind>> sortedActivityTimestamps = activityTimestamps.entrySet()
+                    .stream()
+                    .sorted(Map.Entry.comparingByKey())
+                    .collect(Collectors.toList());
+            for (Map.Entry<Long, ActivityKind> activity : sortedActivityTimestamps) {
+                long timestamp = activity.getKey();
+                ActivityKind activityKind = activity.getValue();
+                // Start a new merged activity on certain conditions
+                if (previous == null ||
+                        previous.activityKind != activityKind ||
+                        (!mode_24h && timestamp == midDaySecond) ||
+                        (!mode_24h && timestamp == midDaySecond - 86400) ||
+                        timestamp == dashboardData.timeTo - 86400 ||
+                        timestamp == currentTime - 86400 ||
+                        previous.timeTo < timestamp - 60) {
+                    previous = new DashboardFragment.DashboardData.GeneralizedActivity(activityKind, timestamp, timestamp);
+                    dashboardData.generalizedActivities.add(previous);
+                } else {
+                    previous.timeTo = timestamp;
+                }
+            }
+        }
+
+        @Override
+        protected Void doInBackground(Void... params) {
+            final long nanoStart = System.nanoTime();
+
+            // Retrieve activity data
+            dashboardData.generalizedActivities.clear();
+            List<GBDevice> devices = GBApplication.app().getDeviceManager().getDevices();
+            List<ActivitySample> allActivitySamples = new ArrayList<>();
+            List<ActivitySession> stepSessions = new ArrayList<>();
+            List<BaseActivitySummary> activitySummaries = null;
+            try (DBHandler dbHandler = GBApplication.acquireDB()) {
+                for (GBDevice dev : devices) {
+                    if ((dashboardData.showAllDevices || dashboardData.showDeviceList.contains(dev.getAddress())) && dev.getDeviceCoordinator().supportsActivityTracking()) {
+                        List<? extends ActivitySample> activitySamples = DashboardUtils.getAllSamples(dbHandler, dev, dashboardData);
+                        allActivitySamples.addAll(activitySamples);
+                        StepAnalysis stepAnalysis = new StepAnalysis();
+                        stepSessions.addAll(stepAnalysis.calculateStepSessions(activitySamples));
+                    }
+                }
+                activitySummaries = DashboardUtils.getWorkoutSamples(dbHandler, dashboardData);
+            } catch (Exception e) {
+                LOG.warn("Could not retrieve activity amounts: ", e);
+            }
+            Collections.sort(allActivitySamples, (lhs, rhs) -> Integer.valueOf(lhs.getTimestamp()).compareTo(rhs.getTimestamp()));
+
+            // Determine worn sessions from heart rate samples
+            calculateWornSessions(allActivitySamples);
+
+            // Integrate various data from multiple devices
+            for (ActivitySample sample : allActivitySamples) {
+                // Handle only TYPE_NOT_WORN and TYPE_SLEEP (including variants) here
+                if (sample.getKind() != ActivityKind.NOT_WORN && (sample.getKind() == ActivityKind.NOT_MEASURED || !ActivityKind.isSleep(sample.getKind())))
+                    continue;
+                // Add to day results
+                addActivity(sample.getTimestamp(), sample.getTimestamp() + 60, sample.getKind());
+            }
+            if (activitySummaries != null) {
+                for (BaseActivitySummary baseActivitySummary : activitySummaries) {
+                    addActivity(baseActivitySummary.getStartTime().getTime() / 1000, baseActivitySummary.getEndTime().getTime() / 1000, ActivityKind.EXERCISE);
+                }
+            }
+            for (ActivitySession session : stepSessions) {
+                addActivity(session.getStartTime().getTime() / 1000, session.getEndTime().getTime() / 1000, ActivityKind.ACTIVITY);
+            }
+
+            // Merge per-second activities
+            createGeneralizedActivities();
+
+            final long nanoEnd = System.nanoTime();
+            final long executionTime = (nanoEnd - nanoStart) / 1000000;
+            LOG.debug("fillData for {} took {}ms", DashboardTodayWidget.this.getClass().getSimpleName(), executionTime);
+
+            return null;
+        }
+
+        @Override
+        protected void onPostExecute(final Void unused) {
+            super.onPostExecute(unused);
+            try {
+                draw();
+            } catch (final Exception e) {
+                LOG.error("calling draw() failed", e);
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/DashboardVO2MaxAnyWidget.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/DashboardVO2MaxAnyWidget.java
new file mode 100644
index 0000000..2f1c19a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/DashboardVO2MaxAnyWidget.java
@@ -0,0 +1,36 @@
+package nodomain.freeyourgadget.gadgetbridge.activities.dashboard;
+
+import android.os.Bundle;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.DashboardFragment;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.Vo2MaxSample;
+
+public class DashboardVO2MaxAnyWidget extends AbstractDashboardVO2MaxWidget {
+
+    public DashboardVO2MaxAnyWidget() {
+        super(R.string.menuitem_vo2_max, "vo2max");
+    }
+
+    public static DashboardVO2MaxAnyWidget newInstance(final DashboardFragment.DashboardData dashboardData) {
+        final DashboardVO2MaxAnyWidget fragment = new DashboardVO2MaxAnyWidget();
+        final Bundle args = new Bundle();
+        args.putSerializable(ARG_DASHBOARD_DATA, dashboardData);
+        fragment.setArguments(args);
+        return fragment;
+    }
+
+    public Vo2MaxSample.Type getVO2MaxType() {
+        return Vo2MaxSample.Type.ANY;
+    }
+
+    public String getWidgetKey() {
+        return "vo2max";
+    }
+
+    @Override
+    protected boolean isSupportedBy(final GBDevice device) {
+        return device.getDeviceCoordinator().supportsVO2Max();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/DashboardVO2MaxCyclingWidget.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/DashboardVO2MaxCyclingWidget.java
new file mode 100644
index 0000000..94d45f4
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/DashboardVO2MaxCyclingWidget.java
@@ -0,0 +1,36 @@
+package nodomain.freeyourgadget.gadgetbridge.activities.dashboard;
+
+import android.os.Bundle;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.DashboardFragment;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.Vo2MaxSample;
+
+public class DashboardVO2MaxCyclingWidget extends AbstractDashboardVO2MaxWidget {
+
+    public DashboardVO2MaxCyclingWidget() {
+        super(R.string.vo2max_cycling, "vo2max");
+    }
+
+    public static DashboardVO2MaxCyclingWidget newInstance(final DashboardFragment.DashboardData dashboardData) {
+        final DashboardVO2MaxCyclingWidget fragment = new DashboardVO2MaxCyclingWidget();
+        final Bundle args = new Bundle();
+        args.putSerializable(ARG_DASHBOARD_DATA, dashboardData);
+        fragment.setArguments(args);
+        return fragment;
+    }
+
+    public Vo2MaxSample.Type getVO2MaxType() {
+        return Vo2MaxSample.Type.CYCLING;
+    }
+
+    public String getWidgetKey() {
+        return "vo2max_cycling";
+    }
+
+    @Override
+    protected boolean isSupportedBy(final GBDevice device) {
+        return device.getDeviceCoordinator().supportsVO2MaxCycling();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/DashboardVO2MaxRunningWidget.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/DashboardVO2MaxRunningWidget.java
new file mode 100644
index 0000000..544c0cf
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/DashboardVO2MaxRunningWidget.java
@@ -0,0 +1,36 @@
+package nodomain.freeyourgadget.gadgetbridge.activities.dashboard;
+
+import android.os.Bundle;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.DashboardFragment;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.Vo2MaxSample;
+
+public class DashboardVO2MaxRunningWidget extends AbstractDashboardVO2MaxWidget {
+
+    public DashboardVO2MaxRunningWidget() {
+        super(R.string.vo2max_running, "vo2max");
+    }
+
+    public static DashboardVO2MaxRunningWidget newInstance(final DashboardFragment.DashboardData dashboardData) {
+        final DashboardVO2MaxRunningWidget fragment = new DashboardVO2MaxRunningWidget();
+        final Bundle args = new Bundle();
+        args.putSerializable(ARG_DASHBOARD_DATA, dashboardData);
+        fragment.setArguments(args);
+        return fragment;
+    }
+
+    public Vo2MaxSample.Type getVO2MaxType() {
+        return Vo2MaxSample.Type.RUNNING;
+    }
+
+    public String getWidgetKey() {
+        return "vo2max_running";
+    }
+
+    @Override
+    protected boolean isSupportedBy(final GBDevice device) {
+        return device.getDeviceCoordinator().supportsVO2MaxRunning();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/DashboardVO2MaxWidgetInterface.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/DashboardVO2MaxWidgetInterface.java
new file mode 100644
index 0000000..10ccc1c
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/DashboardVO2MaxWidgetInterface.java
@@ -0,0 +1,8 @@
+package nodomain.freeyourgadget.gadgetbridge.activities.dashboard;
+
+import nodomain.freeyourgadget.gadgetbridge.model.Vo2MaxSample;
+
+public interface DashboardVO2MaxWidgetInterface {
+    Vo2MaxSample.Type getVO2MaxType();
+    String getWidgetKey();
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/GaugeDrawer.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/GaugeDrawer.java
new file mode 100644
index 0000000..b35db2f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/GaugeDrawer.java
@@ -0,0 +1,356 @@
+package nodomain.freeyourgadget.gadgetbridge.activities.dashboard;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
+import android.util.TypedValue;
+import android.widget.ImageView;
+
+import androidx.annotation.ColorInt;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+
+public class GaugeDrawer {
+    private static final Logger LOG = LoggerFactory.getLogger(GaugeDrawer.class);
+    protected @ColorInt int color_unknown = Color.argb(25, 128, 128, 128);
+
+    /**
+     * Draw a simple gauge.
+     *
+     * @param color     the gauge color
+     * @param value     the gauge value. Range: [0, 1]
+     */
+    public void drawSimpleGauge(ImageView gaugeBar, final int color,
+                                   final float value) {
+
+        final int width = (int) TypedValue.applyDimension(
+                TypedValue.COMPLEX_UNIT_DIP,
+                150,
+                GBApplication.getContext().getResources().getDisplayMetrics()
+        );
+
+        // Draw gauge
+        gaugeBar.setImageBitmap(drawSimpleGaugeInternal(
+                width,
+                Math.round(width * 0.075f),
+                color,
+                value
+        ));
+    }
+
+    /**
+     * @param width        Bitmap width in pixels
+     * @param barWidth     Gauge bar width in pixels
+     * @param filledColor  Color of the filled part of the gauge
+     * @param filledFactor Factor between 0 and 1 that determines the amount of the gauge that should be filled
+     * @return Bitmap containing the gauge
+     */
+    private Bitmap drawSimpleGaugeInternal(final int width, final int barWidth, @ColorInt final int filledColor, final float filledFactor) {
+        final int height = width / 2;
+        final int barMargin = (int) Math.ceil(barWidth / 2f);
+
+        final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+        final Canvas canvas = new Canvas(bitmap);
+        final Paint paint = new Paint();
+        paint.setAntiAlias(true);
+        paint.setStyle(Paint.Style.STROKE);
+        paint.setStrokeCap(Paint.Cap.ROUND);
+        paint.setStrokeWidth(barWidth * 0.75f);
+        paint.setColor(color_unknown);
+        canvas.drawArc(barMargin, barMargin, width - barMargin, width - barMargin, 180 + 180 * filledFactor, 180 - 180 * filledFactor, false, paint);
+
+        if (filledFactor >= 0) {
+            paint.setStrokeWidth(barWidth);
+            paint.setColor(filledColor);
+            canvas.drawArc(barMargin, barMargin, width - barMargin, width - barMargin, 180, 180 * filledFactor, false, paint);
+        }
+
+        return bitmap;
+    }
+
+    /**
+     * Draws a segmented gauge.
+     *
+     * @param colors             the colors of each segment
+     * @param segments           the size of each segment. The sum of all segments should be 1
+     * @param value              the gauge value, in range [0, 1], or -1 for no value and only segments
+     * @param fadeOutsideDot     whether to fade out colors outside the dot value
+     * @param gapBetweenSegments whether to introduce a small gap between the segments
+     */
+    public void drawSegmentedGauge(ImageView gaugeBar,
+                                      final int[] colors,
+                                      final float[] segments,
+                                      final float value,
+                                      final boolean fadeOutsideDot,
+                                      final boolean gapBetweenSegments) {
+        if (colors.length != segments.length) {
+            LOG.error("Colors length {} differs from segments length {}", colors.length, segments.length);
+            return;
+        }
+
+        final int width = (int) TypedValue.applyDimension(
+                TypedValue.COMPLEX_UNIT_DIP,
+                150,
+                GBApplication.getContext().getResources().getDisplayMetrics()
+        );
+
+        final int barWidth = Math.round(width * 0.075f);
+
+        final int height = width / 2;
+        final int barMargin = (int) Math.ceil(barWidth / 2f);
+
+        final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+        final Canvas canvas = new Canvas(bitmap);
+        final Paint paint = new Paint();
+        paint.setAntiAlias(true);
+        paint.setStyle(Paint.Style.STROKE);
+        paint.setStrokeCap(Paint.Cap.BUTT);
+        paint.setStrokeWidth(barWidth);
+
+        final double cornersGapRadians = Math.asin((width * 0.055f) / (double) height);
+        final double cornersGapFactor = cornersGapRadians / Math.PI;
+
+        int dotColor = 0;
+        float angleSum = 0;
+        for (int i = 0; i < segments.length; i++) {
+            if (segments[i] == 0) {
+                continue;
+            }
+
+            paint.setColor(colors[i]);
+            paint.setStrokeWidth(barWidth);
+
+            if (value < 0 || (value >= angleSum && value <= angleSum + segments[i])) {
+                dotColor = colors[i];
+            } else {
+                if (fadeOutsideDot) {
+                    paint.setColor(colors[i] - 0xB0000000);
+                } else {
+                    paint.setStrokeWidth(barWidth * 0.75f);
+                }
+            }
+
+            float startAngleDegrees = 180 + angleSum * 180;
+            float sweepAngleDegrees = segments[i] * 180;
+
+            if (value >= 0) {
+                // Do not draw to the end if it will be overlapped by the dot
+                if (i == 0 && value <= cornersGapFactor) {
+                    startAngleDegrees += (float) Math.toDegrees(cornersGapRadians);
+                    sweepAngleDegrees -= (float) Math.toDegrees(cornersGapRadians);
+                } else if (i == segments.length - 1 && value >= 1 - cornersGapFactor) {
+                    sweepAngleDegrees -= (float) Math.toDegrees(cornersGapRadians);
+                }
+            }
+
+            if (gapBetweenSegments) {
+                if (i + 1 < segments.length) {
+                    sweepAngleDegrees -= 2;
+                }
+            }
+
+            canvas.drawArc(
+                    barMargin,
+                    barMargin,
+                    width - barMargin,
+                    width - barMargin,
+                    startAngleDegrees,
+                    sweepAngleDegrees,
+                    false,
+                    paint
+            );
+            angleSum += segments[i];
+        }
+
+        if (value >= 0) {
+            // Prevent the dot from going outside the widget in the extremities
+            final float angleRadians = (float) normalize(value, 0, 1, cornersGapRadians, Math.toRadians(180) - cornersGapRadians);
+
+            paint.setColor(Color.TRANSPARENT);
+            paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
+
+            // In the corners the circle is slightly offset, so adjust it slightly
+            final float widthAdjustment = width * 0.04f * (float) normalize(Math.abs(value - 0.5d), 0, 0.5d);
+
+            final float x = ((width - (barWidth / 2f) - widthAdjustment) / 2f) * (float) Math.cos(angleRadians);
+            final float y = (height - (barWidth / 2f)) * (float) Math.sin(angleRadians);
+
+            // Draw hole
+            paint.setStyle(Paint.Style.FILL);
+            canvas.drawCircle((width / 2f) - x, height - y, barMargin * 1.6f, paint);
+
+            // Draw dot
+            paint.setColor(dotColor);
+            paint.setXfermode(null);
+            canvas.drawCircle((width / 2f) - x, height - y, barMargin, paint);
+        }
+
+        gaugeBar.setImageBitmap(bitmap);
+    }
+
+    public static Bitmap drawCircleGaugeSegmented(int width,
+                                                  int barWidth,
+                                                  final int[] colors,
+                                                  final float[] segments,
+                                                  final boolean gapBetweenSegments,
+                                                  String text,
+                                                  String lowerText,
+                                                  Context context) {
+        int TEXT_COLOR = GBApplication.getTextColor(context);
+        int height = width;
+        int barMargin = (int) Math.ceil(barWidth / 2f);
+
+        Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+        Canvas canvas = new Canvas(bitmap);
+        Paint paint = new Paint();
+        paint.setAntiAlias(true);
+        paint.setStyle(Paint.Style.STROKE);
+        paint.setStrokeCap(Paint.Cap.BUTT);
+        paint.setStrokeWidth(barWidth);
+        paint.setColor(context.getResources().getColor(R.color.gauge_line_color));
+        canvas.drawArc(
+                barMargin,
+                barMargin,
+                width - barMargin,
+                width - barMargin,
+                90,
+                360,
+                false,
+                paint);
+        paint.setStrokeWidth(barWidth);
+
+        float remainingAngle = 360;
+        float gapDegree = 1f;
+        if (gapBetweenSegments) {
+            int validSegments = segments.length;
+            for (int i = 0; i < segments.length; i++) {
+                if (segments[i] == 0) {
+                    validSegments--;
+                }
+            }
+
+            remainingAngle = 360 - (validSegments * gapDegree);
+        }
+
+        float angleSum = 0;
+        for (int i = 0; i < segments.length; i++) {
+            if (segments[i] == 0) {
+                continue;
+            }
+
+            paint.setColor(colors[i]);
+            paint.setStrokeWidth(barWidth);
+
+            float startAngleDegrees = 270 + (angleSum * remainingAngle);
+            float sweepAngleDegrees = segments[i] * remainingAngle;
+
+            canvas.drawArc(
+                    barMargin,
+                    barMargin,
+                    width - barMargin,
+                    height - barMargin,
+                    startAngleDegrees,
+                    sweepAngleDegrees,
+                    false,
+                    paint
+            );
+            angleSum += segments[i];
+            if (gapBetweenSegments) {
+                angleSum += (gapDegree / 360f);
+            }
+        }
+
+        Paint textPaint = new Paint();
+        textPaint.setColor(TEXT_COLOR);
+        float textPixels = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, width * 0.06f, context.getResources().getDisplayMetrics());
+        textPaint.setTextSize(textPixels);
+        textPaint.setTextAlign(Paint.Align.CENTER);
+        int yPos = (int) ((float) height / 2 - ((textPaint.descent() + textPaint.ascent()) / 2)) ;
+        canvas.drawText(String.valueOf(text), width / 2f, yPos, textPaint);
+        Paint textLowerPaint = new Paint();
+        textLowerPaint.setColor(TEXT_COLOR);
+        textLowerPaint.setTextAlign(Paint.Align.CENTER);
+        float textLowerPixels = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, width * 0.025f, context.getResources().getDisplayMetrics());
+        textLowerPaint.setTextSize(textLowerPixels);
+        int yPosLowerText = (int) ((float) height / 2 - textPaint.ascent()) ;
+        canvas.drawText(String.valueOf(lowerText), width / 2f, yPosLowerText, textLowerPaint);
+
+        return bitmap;
+    }
+
+    public static Bitmap drawCircleGauge(int width,
+                                         int barWidth,
+                                         @ColorInt int filledColor,
+                                         int value,
+                                         int maxValue,
+                                         Context context) {
+        int TEXT_COLOR = GBApplication.getTextColor(context);
+        int height = width;
+        int barMargin = (int) Math.ceil(barWidth / 2f);
+        float filledFactor = (float) value / maxValue;
+
+        Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+        Canvas canvas = new Canvas(bitmap);
+        Paint paint = new Paint();
+        paint.setAntiAlias(true);
+        paint.setStyle(Paint.Style.STROKE);
+        paint.setStrokeCap(Paint.Cap.ROUND);
+        paint.setStrokeWidth(barWidth);
+        paint.setColor(context.getResources().getColor(R.color.gauge_line_color));
+        canvas.drawArc(
+                barMargin,
+                barMargin,
+                width - barMargin,
+                width - barMargin,
+                90,
+                360,
+                false,
+                paint);
+        paint.setStrokeWidth(barWidth);
+        paint.setColor(filledColor);
+        canvas.drawArc(
+                barMargin,
+                barMargin,
+                width - barMargin,
+                height - barMargin,
+                270,
+                360 * filledFactor,
+                false,
+                paint
+        );
+
+        Paint textPaint = new Paint();
+        textPaint.setColor(TEXT_COLOR);
+        float textPixels = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, width * 0.06f, context.getResources().getDisplayMetrics());
+        textPaint.setTextSize(textPixels);
+        textPaint.setTextAlign(Paint.Align.CENTER);
+        int yPos = (int) ((float) height / 2 - ((textPaint.descent() + textPaint.ascent()) / 2)) ;
+        canvas.drawText(String.valueOf(value), width / 2f, yPos, textPaint);
+        Paint textLowerPaint = new Paint();
+        textLowerPaint.setColor(TEXT_COLOR);
+        textLowerPaint.setTextAlign(Paint.Align.CENTER);
+        float textLowerPixels = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, width * 0.025f, context.getResources().getDisplayMetrics());
+        textLowerPaint.setTextSize(textLowerPixels);
+        int yPosLowerText = (int) ((float) height / 2 - textPaint.ascent()) ;
+        canvas.drawText(String.valueOf(maxValue), width / 2f, yPosLowerText, textLowerPaint);
+
+        return bitmap;
+    }
+
+    public static double normalize(final double value, final double min, final double max) {
+        return normalize(value, min, max, 0, 1);
+    }
+
+    public static double normalize(final double value, final double minSource, final double maxSource, final double minTarget, final double maxTarget) {
+        return ((value - minSource) * (maxTarget - minTarget)) / (maxSource - minSource) + minTarget;
+    }
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/data/DashboardStressData.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/data/DashboardStressData.java
new file mode 100644
index 0000000..3106ae6
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/data/DashboardStressData.java
@@ -0,0 +1,85 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities.dashboard.data;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.Serializable;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.activities.DashboardFragment;
+import nodomain.freeyourgadget.gadgetbridge.activities.charts.StressChartFragment;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.StressSample;
+
+public class DashboardStressData implements Serializable {
+    private static final Logger LOG = LoggerFactory.getLogger(DashboardStressData.class);
+
+    public int value;
+    public int[] ranges;
+    public int[] totalTime;
+
+    public static DashboardStressData compute(final DashboardFragment.DashboardData dashboardData) {
+        final List<GBDevice> devices = GBApplication.app().getDeviceManager().getDevices();
+
+        GBDevice stressDevice = null;
+        double averageStress = -1;
+
+        final int[] totalTime = new int[StressChartFragment.StressType.values().length];
+
+        try (DBHandler dbHandler = GBApplication.acquireDB()) {
+            for (GBDevice dev : devices) {
+                if ((dashboardData.showAllDevices || dashboardData.showDeviceList.contains(dev.getAddress())) && dev.getDeviceCoordinator().supportsStressMeasurement()) {
+                    final List<? extends StressSample> samples = dev.getDeviceCoordinator()
+                            .getStressSampleProvider(dev, dbHandler.getDaoSession())
+                            .getAllSamples(dashboardData.timeFrom * 1000L, dashboardData.timeTo * 1000L);
+
+                    if (!samples.isEmpty()) {
+                        stressDevice = dev;
+                        final int[] stressRanges = dev.getDeviceCoordinator().getStressRanges();
+                        averageStress = samples.stream()
+                                .mapToInt(StressSample::getStress)
+                                .peek(stress -> {
+                                    final StressChartFragment.StressType stressType = StressChartFragment.StressType.fromStress(stress, stressRanges);
+                                    if (stressType != StressChartFragment.StressType.UNKNOWN) {
+                                        totalTime[stressType.ordinal() - 1] += 60;
+                                    }
+                                })
+                                .average()
+                                .orElse(0);
+                    }
+                }
+            }
+        } catch (final Exception e) {
+            LOG.error("Could not compute stress", e);
+        }
+
+        if (stressDevice != null) {
+            final DashboardStressData stressData = new DashboardStressData();
+            stressData.value = (int) Math.round(averageStress);
+            stressData.ranges = stressDevice.getDeviceCoordinator().getStressRanges();
+            stressData.totalTime = totalTime;
+
+            return stressData;
+        }
+
+        return null;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSettingsActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSettingsActivity.java
new file mode 100644
index 0000000..49fe217
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSettingsActivity.java
@@ -0,0 +1,45 @@
+/*  Copyright (C) 2019-2024 Andreas Shimokawa, Jos茅 Rebelo, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities.devicesettings;
+
+import androidx.preference.PreferenceFragmentCompat;
+
+import nodomain.freeyourgadget.gadgetbridge.activities.AbstractSettingsActivityV2;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class DeviceSettingsActivity extends AbstractSettingsActivityV2 {
+    public static final String MENU_ENTRY_POINT = "MENU_ENTRY_POINT";
+
+    public enum MENU_ENTRY_POINTS {
+        DEVICE_SETTINGS,
+        AUTH_SETTINGS,
+        APPLICATION_SETTINGS
+    }
+
+    @Override
+    protected String fragmentTag() {
+        return DeviceSpecificSettingsFragment.FRAGMENT_TAG;
+    }
+
+    @Override
+    protected PreferenceFragmentCompat newFragment() {
+        final GBDevice device = getIntent().getParcelableExtra(GBDevice.EXTRA_DEVICE);
+        final MENU_ENTRY_POINTS menu_entry = (MENU_ENTRY_POINTS) getIntent().getSerializableExtra(MENU_ENTRY_POINT);
+
+        return DeviceSpecificSettingsFragment.newInstance(device, menu_entry);
+    }
+}
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
new file mode 100644
index 0000000..79596e5
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSettingsPreferenceConst.java
@@ -0,0 +1,602 @@
+/*  Copyright (C) 2019-2024 115ek, akasaka / Genjitsu Labs, Alicia Hormann,
+    Andreas B枚hler, Andreas Shimokawa, Arjan Schrijver, Damien Gaignon, Daniel
+    Dakhno, Daniele Gobbetti, Davis Mosenkovs, foxstidious, Gordon Williams,
+    Jos茅 Rebelo, Lukas, LukasEdl, mamucho, narektor, NekoBox, opavlov, Petr
+    Van臎k, Yoran Vulker, Yukai Li
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities.devicesettings;
+
+public class DeviceSettingsPreferenceConst {
+    public static final String PREF_HEADER_APPS = "pref_header_apps";
+    public static final String PREF_HEADER_TIME = "pref_header_time";
+    public static final String PREF_HEADER_DISPLAY = "pref_header_display";
+    public static final String PREF_HEADER_HEALTH = "pref_header_health";
+    public static final String PREF_HEADER_WORKOUT = "pref_header_workout";
+    public static final String PREF_HEADER_WORKOUT_DETECTION = "pref_header_workout_detection";
+    public static final String PREF_HEADER_GPS = "pref_header_gps";
+    public static final String PREF_HEADER_AGPS = "pref_header_agps";
+    public static final String PREF_HEADER_WIFI_HOTSPOT_CONFIGURATION = "pref_header_wifi_hotspot_configuration";
+    public static final String PREF_HEADER_WIFI_HOTSPOT_STATUS = "pref_header_wifi_hotspot_status";
+    public static final String PREF_HEADER_FTP_SERVER_STATUS = "pref_header_ftp_server_status";
+    public static final String PREF_HEADER_FTP_SERVER_CONFIGURATION = "pref_header_ftp_server_configuration";
+
+    public static final String PREF_BATTERY_SHOW_IN_NOTIFICATION = "battery_show_in_notification_";
+    public static final String PREF_BATTERY_NOTIFY_LOW_ENABLED = "battery_notify_low_enabled_";
+    public static final String PREF_BATTERY_NOTIFY_LOW_THRESHOLD = "battery_notify_low_threshold_";
+    public static final String PREF_BATTERY_NOTIFY_FULL_ENABLED = "battery_notify_full_enabled_";
+    public static final String PREF_BATTERY_NOTIFY_FULL_THRESHOLD = "battery_notify_full_threshold_";
+
+    public static final String PREF_BATTERY_POLLING_ENABLE = "pref_battery_polling_enabled";
+    public static final String PREF_BATTERY_POLLING_INTERVAL = "pref_battery_polling_interval";
+
+    public static final String PREF_SCREEN_NIGHT_MODE = "pref_screen_night_mode";
+    public static final String PREF_SCREEN_SLEEP_MODE = "pref_screen_sleep_mode";
+    public static final String PREF_SCREEN_LIFT_WRIST = "pref_screen_lift_wrist";
+    public static final String PREF_SCREEN_PASSWORD = "pref_screen_password";
+    public static final String PREF_SCREEN_ALWAYS_ON_DISPLAY = "pref_screen_always_on_display";
+    public static final String PREF_SCREEN_HEARTRATE_MONITORING = "pref_screen_heartrate_monitoring";
+    public static final String PREF_SCREEN_INACTIVITY_EXTENDED = "pref_screen_inactivity_extended";
+    public static final String PREF_SCREEN_SOUND_AND_VIBRATION = "pref_screen_sound_and_vibration";
+    public static final String PREF_SCREEN_DO_NOT_DISTURB = "pref_screen_do_not_disturb";
+    public static final String PREF_SCREEN_OFFLINE_VOICE = "pref_screen_offline_voice";
+    public static final String PREF_SCREEN_WIFI_HOTSPOT = "pref_screen_wifi_hotspot";
+    public static final String PREF_SCREEN_FTP_SERVER = "pref_screen_ftp_server";
+    public static final String PREF_SCREEN_MORNING_UPDATES = "pref_morning_updates";
+
+    public static final String PREF_LANGUAGE = "language";
+    public static final String PREF_LANGUAGE_AUTO = "auto";
+    public static final String PREF_DEVICE_REGION = "device_region";
+    public static final String PREF_DEVICE_NAME = "pref_device_name";
+    public static final String PREF_DATEFORMAT = "dateformat";
+    public static final String PREF_DATEFORMAT_AUTO = "auto";
+    public static final String PREF_DATEFORMAT_DAY_MONTH = "day_month";
+    public static final String PREF_DATEFORMAT_MONTH_DAY = "month_day";
+    public static final String PREF_TIMEFORMAT = "timeformat";
+    public static final String PREF_TIMEFORMAT_24H = "24h";
+    public static final String PREF_TIMEFORMAT_12H = "am/pm";
+    public static final String PREF_TIMEFORMAT_AUTO = "auto";
+    public static final String PREF_WEARLOCATION = "wearlocation";
+    public static final String PREF_WEARDIRECTION = "weardirection";
+    public static final String PREF_WEARMODE = "wearmode";
+    public static final String PREF_VIBRATION_ENABLE = "vibration_enable";
+    public static final String PREF_NOTIFICATION_ENABLE = "notification_enable";
+    public static final String PREF_SCREEN_BRIGHTNESS = "screen_brightness";
+    public static final String PREF_SCREEN_AUTO_BRIGHTNESS = "screen_auto_brightness";
+    public static final String PREF_SCREEN_ORIENTATION = "screen_orientation";
+    public static final String PREF_SCREEN_TIMEOUT = "screen_timeout";
+    public static final String PREF_RESERVER_ALARMS_CALENDAR = "reserve_alarms_calendar";
+    public static final String PREF_RESERVE_REMINDERS_CALENDAR = "reserve_reminders_calendar";
+    public static final String PREF_ALLOW_HIGH_MTU = "allow_high_mtu";
+    public static final String PREF_SYNC_CALENDAR = "sync_calendar";
+    public static final String PREF_CALENDAR_LOOKAHEAD_DAYS = "calendar_lookahead_days";
+    public static final String PREF_TIME_SYNC = "time_sync";
+    public static final String PREF_USE_CUSTOM_DEVICEICON = "use_custom_deviceicon";
+    public static final String PREF_BUTTON_1_FUNCTION_SHORT = "button_1_function_short";
+    public static final String PREF_BUTTON_2_FUNCTION_SHORT = "button_2_function_short";
+    public static final String PREF_BUTTON_3_FUNCTION_SHORT = "button_3_function_short";
+    public static final String PREF_BUTTON_1_FUNCTION_LONG = "button_1_function_long";
+    public static final String PREF_BUTTON_2_FUNCTION_LONG = "button_2_function_long";
+    public static final String PREF_BUTTON_3_FUNCTION_LONG = "button_3_function_long";
+    public static final String PREF_BUTTON_1_FUNCTION_DOUBLE = "button_1_function_double";
+    public static final String PREF_BUTTON_2_FUNCTION_DOUBLE = "button_2_function_double";
+    public static final String PREF_BUTTON_3_FUNCTION_DOUBLE = "button_3_function_double";
+    public static final String PREF_UPPER_BUTTON_LONG_PRESS = "pref_button_action_upper_long";
+    public static final String PREF_LOWER_BUTTON_SHORT_PRESS = "pref_button_action_lower_short";
+    public static final String PREF_VIBRATION_STRENGH_PERCENTAGE = "vibration_strength";
+    public static final String PREF_RELAX_FIRMWARE_CHECKS = "relax_firmware_checks";
+
+    public static final String PREF_DEVICE_GPS_UPDATE = "banglejs_gps_update";
+    public static final String PREF_DEVICE_GPS_UPDATE_INTERVAL = "banglejs_gps_update_interval";
+    public static final String PREF_DEVICE_GPS_USE_NETWORK_ONLY = "banglejs_gps_use_network_only";
+
+    public static final String PREF_DEVICE_INTERNET_ACCESS = "device_internet_access";
+    public static final String PREF_DEVICE_INTENTS = "device_intents";
+
+    public static final String PREF_ACTIVE_NOISE_CANCELLING_TOGGLE = "active_noise_cancelling_toggle";
+    public static final String PREF_WEAR_SENSOR_TOGGLE = "wear_sensor_toggle";
+    public static final String PREF_BANDW_PSERIES_VPT_ENABLED = "bandw_pseries_vpt_enabled";
+    public static final String PREF_BANDW_PSERIES_VPT_LEVEL = "bandw_pseries_vpt_level";
+    public static final String PREF_BANDW_PSERIES_GUI_VPT_LEVEL = "bandw_pseries_gui_vpt_level";
+
+    public static final String PREF_BANGLEJS_TEXT_BITMAP = "banglejs_text_bitmap";
+    public static final String PREF_BANGLEJS_TEXT_BITMAP_SIZE = "banglejs_txt_bitmap_size";
+    public static final String PREF_BANGLEJS_WEBVIEW_URL = "banglejs_webview_url";
+
+    public static final String PREF_DISCONNECT_NOTIFICATION = "disconnect_notification";
+    public static final String PREF_DISCONNECT_NOTIFICATION_START = "disconnect_notification_start";
+    public static final String PREF_DISCONNECT_NOTIFICATION_END = "disconnect_notification_end";
+
+    public static final String PREF_HYBRID_HR_FORCE_WHITE_COLOR = "force_white_color_scheme";
+    public static final String PREF_HYBRID_HR_DRAW_WIDGET_CIRCLES = "widget_draw_circles";
+    public static final String PREF_HYBRID_HR_SAVE_RAW_ACTIVITY_FILES = "save_raw_activity_files";
+    public static final String PREF_HYBRID_HR_DANGEROUS_EXTERNAL_INTENTS = "dangerous_external_intents";
+    public static final String PREF_HYBRID_HR_ACTIVITY_RECOGNITION_RUNNING_ENABLED = "activity_recognize_running_enabled";
+    public static final String PREF_HYBRID_HR_ACTIVITY_RECOGNITION_RUNNING_ASK_FIRST = "activity_recognize_running_ask_first";
+    public static final String PREF_HYBRID_HR_ACTIVITY_RECOGNITION_RUNNING_MINUTES = "activity_recognize_running_minutes";
+    public static final String PREF_HYBRID_HR_ACTIVITY_RECOGNITION_BIKING_ENABLED = "activity_recognize_biking_enabled";
+    public static final String PREF_HYBRID_HR_ACTIVITY_RECOGNITION_BIKING_ASK_FIRST = "activity_recognize_biking_ask_first";
+    public static final String PREF_HYBRID_HR_ACTIVITY_RECOGNITION_BIKING_MINUTES = "activity_recognize_biking_minutes";
+    public static final String PREF_HYBRID_HR_ACTIVITY_RECOGNITION_WALKING_ENABLED = "activity_recognize_walking_enabled";
+    public static final String PREF_HYBRID_HR_ACTIVITY_RECOGNITION_WALKING_ASK_FIRST = "activity_recognize_walking_ask_first";
+    public static final String PREF_HYBRID_HR_ACTIVITY_RECOGNITION_WALKING_MINUTES = "activity_recognize_walking_minutes";
+    public static final String PREF_HYBRID_HR_ACTIVITY_RECOGNITION_ROWING_ENABLED = "activity_recognize_rowing_enabled";
+    public static final String PREF_HYBRID_HR_ACTIVITY_RECOGNITION_ROWING_ASK_FIRST = "activity_recognize_rowing_ask_first";
+    public static final String PREF_HYBRID_HR_ACTIVITY_RECOGNITION_ROWING_MINUTES = "activity_recognize_rowing_minutes";
+
+    public static final String PREF_ACTIVATE_DISPLAY_ON_LIFT = "activate_display_on_lift_wrist";
+    public static final String PREF_DISPLAY_ON_LIFT_START = "display_on_lift_start";
+    public static final String PREF_DISPLAY_ON_LIFT_END = "display_on_lift_end";
+    public static final String PREF_DISPLAY_ON_LIFT_SENSITIVITY = "display_on_lift_sensitivity";
+
+    public static final String PREF_ALWAYS_ON_DISPLAY_MODE = "always_on_display_mode";
+    public static final String PREF_ALWAYS_ON_DISPLAY_START = "always_on_display_start";
+    public static final String PREF_ALWAYS_ON_DISPLAY_END = "always_on_display_end";
+    public static final String PREF_ALWAYS_ON_DISPLAY_OFF = "off";
+    public static final String PREF_ALWAYS_ON_DISPLAY_AUTOMATIC = "automatic";
+    public static final String PREF_ALWAYS_ON_DISPLAY_ALWAYS = "always";
+    public static final String PREF_ALWAYS_ON_DISPLAY_SCHEDULED = "scheduled";
+    public static final String PREF_ALWAYS_ON_DISPLAY_FOLLOW_WATCHFACE = "always_on_display_follow_watchface";
+    public static final String PREF_ALWAYS_ON_DISPLAY_STYLE = "always_on_display_style";
+
+    public static final String PREF_VOLUME = "volume";
+    public static final String PREF_CROWN_VIBRATION = "crown_vibration";
+    public static final String PREF_ALERT_TONE = "alert_tone";
+    public static final String PREF_COVER_TO_MUTE = "cover_to_mute";
+    public static final String PREF_VIBRATE_FOR_ALERT = "vibrate_for_alert";
+    public static final String PREF_TEXT_TO_SPEECH = "text_to_speech";
+
+    public static final String PREF_OFFLINE_VOICE_RESPOND_TURN_WRIST = "offline_voice_respond_turn_wrist";
+    public static final String PREF_OFFLINE_VOICE_RESPOND_SCREEN_ON = "offline_voice_respond_screen_on";
+    public static final String PREF_OFFLINE_VOICE_RESPONSE_DURING_SCREEN_LIGHTING = "offline_voice_response_during_screen_lighting";
+    public static final String PREF_OFFLINE_VOICE_LANGUAGE = "offline_voice_language";
+
+    public static final String PREF_SLEEP_TIME = "prefs_enable_sleep_time";
+    public static final String PREF_SLEEP_TIME_START = "prefs_sleep_time_start";
+    public static final String PREF_SLEEP_TIME_END = "prefs_sleep_time_end";
+    public static final String PREF_SLEEP_MODE_SCHEDULE_ENABLED = "sleep_mode_schedule_enabled";
+    public static final String PREF_SLEEP_MODE_SCHEDULE_START = "sleep_mode_schedule_start";
+    public static final String PREF_SLEEP_MODE_SCHEDULE_END = "sleep_mode_schedule_end";
+    public static final String PREF_SLEEP_MODE_SLEEP_SCREEN = "pref_sleep_mode_sleep_screen";
+    public static final String PREF_SLEEP_MODE_SMART_ENABLE = "pref_sleep_mode_smart_enable";
+
+    public static final String PREF_LIFTWRIST_NOSHED = "activate_display_on_lift_wrist_noshed";
+    public static final String PREF_DISCONNECTNOTIF_NOSHED = "disconnect_notification_noshed";
+    public static final String PREF_INACTIVITY_KEY =  "inactivity_warnings_key";
+    public static final String PREF_INACTIVITY_ENABLE = "inactivity_warnings_enable";
+    public static final String PREF_INACTIVITY_ENABLE_NOSHED = "inactivity_warnings_enable_noshed";
+    public static final String PREF_INACTIVITY_START = "inactivity_warnings_start";
+    public static final String PREF_INACTIVITY_END = "inactivity_warnings_end";
+    public static final String PREF_INACTIVITY_THRESHOLD = "inactivity_warnings_threshold";
+    public static final String PREF_INACTIVITY_THRESHOLD_EXTENDED = "inactivity_warnings_threshold_extended";
+    public static final String PREF_INACTIVITY_MO = "inactivity_warnings_mo";
+    public static final String PREF_INACTIVITY_TU = "inactivity_warnings_tu";
+    public static final String PREF_INACTIVITY_WE = "inactivity_warnings_we";
+    public static final String PREF_INACTIVITY_TH = "inactivity_warnings_th";
+    public static final String PREF_INACTIVITY_FR = "inactivity_warnings_fr";
+    public static final String PREF_INACTIVITY_SA = "inactivity_warnings_sa";
+    public static final String PREF_INACTIVITY_SU = "inactivity_warnings_su";
+    public static final String PREF_INACTIVITY_DND = "inactivity_warnings_dnd";
+    public static final String PREF_INACTIVITY_DND_START = "inactivity_warnings_dnd_start";
+    public static final String PREF_INACTIVITY_DND_END = "inactivity_warnings_dnd_end";
+
+    public static final String PREF_HEARTRATE_USE_FOR_SLEEP_DETECTION = "heartrate_sleep_detection";
+    public static final String PREF_HEARTRATE_MEASUREMENT_INTERVAL = "heartrate_measurement_interval";
+    public static final String PREF_HEARTRATE_ACTIVITY_MONITORING = "heartrate_activity_monitoring";
+    public static final String PREF_HEARTRATE_ALERT_ENABLED = "heartrate_alert_enabled";
+    public static final String PREF_HEARTRATE_ALERT_ACTIVE_HIGH_THRESHOLD = "heartrate_alert_active_high_threshold";
+    public static final String PREF_HEARTRATE_ALERT_HIGH_THRESHOLD = "heartrate_alert_threshold";
+    public static final String PREF_HEARTRATE_ALERT_LOW_THRESHOLD = "heartrate_alert_low_threshold";
+    public static final String PREF_HEARTRATE_STRESS_MONITORING = "heartrate_stress_monitoring";
+    public static final String PREF_HEARTRATE_STRESS_RELAXATION_REMINDER = "heartrate_stress_relaxation_reminder";
+    public static final String PREF_HEARTRATE_SLEEP_BREATHING_QUALITY_MONITORING = "heartrate_sleep_breathing_quality_monitoring";
+    public static final String PREF_SPO2_ALL_DAY_MONITORING = "spo2_all_day_monitoring_enabled";
+    public static final String PREF_SPO2_LOW_ALERT_THRESHOLD = "spo2_low_alert_threshold";
+    public static final String PREF_HRV_ALL_DAY_MONITORING = "hrv_all_day_monitoring_enabled";
+
+    public static final String PREF_AUTOHEARTRATE_SWITCH = "pref_autoheartrate_switch";
+    public static final String PREF_AUTOHEARTRATE_SLEEP = "pref_autoheartrate_sleep";
+    public static final String PREF_AUTOHEARTRATE_INTERVAL = "pref_autoheartrate_interval";
+    public static final String PREF_AUTOHEARTRATE_START = "pref_autoheartrate_start";
+    public static final String PREF_AUTOHEARTRATE_END = "pref_autoheartrate_end";
+
+    public static final String PREF_POWER_MODE = "power_mode";
+    public static final String PREF_CONNECTION_DURATION = "connection_duration";
+    public static final String PREF_BUTTON_BP_CALIBRATE = "prefs_sensors_button_bp_calibration";
+    public static final String PREF_ALTITUDE_CALIBRATE = "pref_sensors_altitude";
+    public static final String PREF_DO_NOT_DISTURB_NOAUTO = "do_not_disturb_no_auto";
+    public static final String PREF_DO_NOT_DISTURB_NOAUTO_START = "do_not_disturb_no_auto_start";
+    public static final String PREF_DO_NOT_DISTURB_NOAUTO_END = "do_not_disturb_no_auto_end";
+    public static final String PREF_DO_NOT_DISTURB = "do_not_disturb";
+    public static final String PREF_DO_NOT_DISTURB_START = "do_not_disturb_start";
+    public static final String PREF_DO_NOT_DISTURB_END = "do_not_disturb_end";
+    public static final String PREF_DO_NOT_DISTURB_LIFT_WRIST = "do_not_disturb_lift_wrist";
+    public static final String PREF_DO_NOT_DISTURB_NOT_WEAR = "do_not_disturb_not_wear";
+    public static final String PREF_DO_NOT_DISTURB_OFF = "off";
+    public static final String PREF_DO_NOT_DISTURB_AUTOMATIC = "automatic";
+    public static final String PREF_DO_NOT_DISTURB_ALWAYS = "always";
+    public static final String PREF_DO_NOT_DISTURB_SCHEDULED = "scheduled";
+    public static final String PREF_DO_NOT_DISTURB_MO = "pref_do_not_disturb_mo";
+    public static final String PREF_DO_NOT_DISTURB_TU = "pref_do_not_disturb_tu";
+    public static final String PREF_DO_NOT_DISTURB_WE = "pref_do_not_disturb_we";
+    public static final String PREF_DO_NOT_DISTURB_TH = "pref_do_not_disturb_th";
+    public static final String PREF_DO_NOT_DISTURB_FR = "pref_do_not_disturb_fr";
+    public static final String PREF_DO_NOT_DISTURB_SA = "pref_do_not_disturb_sa";
+    public static final String PREF_DO_NOT_DISTURB_SU = "pref_do_not_disturb_su";
+
+    public static final String PREF_CAMERA_REMOTE = "camera_remote";
+
+    public static final String PREF_WORKOUT_START_ON_PHONE = "workout_start_on_phone";
+    public static final String PREF_WORKOUT_SEND_GPS_TO_BAND = "workout_send_gps_to_band";
+    public static final String PREF_WORKOUT_DETECTION_CATEGORIES = "workout_detection_categories";
+    public static final String PREF_WORKOUT_DETECTION_ALERT = "workout_detection_alert";
+    public static final String PREF_WORKOUT_DETECTION_SENSITIVITY = "workout_detection_sensitivity";
+
+    public static final String PREF_GPS_MODE_PRESET = "pref_gps_mode_preset";
+    public static final String PREF_GPS_BAND = "pref_gps_band";
+    public static final String PREF_GPS_COMBINATION = "pref_gps_combination";
+    public static final String PREF_GPS_SATELLITE_SEARCH = "pref_gps_satellite_search";
+    public static final String PREF_AGPS_EXPIRY_REMINDER_ENABLED = "pref_agps_expiry_reminder_enabled";
+    public static final String PREF_AGPS_EXPIRY_REMINDER_TIME = "pref_agps_expiry_reminder_time";
+    public static final String PREF_AGPS_UPDATE_TIME = "pref_agps_update_time";
+    public static final String PREF_AGPS_EXPIRE_TIME = "pref_agps_expire_time";
+    public static final String PREF_AGPS_STATUS = "pref_agps_status";
+
+    public static final String PREF_FIND_PHONE = "prefs_find_phone";
+    public static final String PREF_FIND_PHONE_DURATION = "prefs_find_phone_duration";
+    public static final String PREF_AUTOLIGHT = "autolight";
+
+    public static final String PREF_PREVIEW_MESSAGE_IN_TITLE = "preview_message_in_title";
+
+    public static final String PREF_CASIO_ALERT_CALENDAR = "casio_alert_calendar";
+
+    public static final String PREF_CASIO_ALERT_CALL = "casio_alert_call";
+
+    public static final String PREF_CASIO_ALERT_EMAIL = "casio_alert_email";
+
+    public static final String PREF_CASIO_ALERT_OTHER = "casio_alert_other";
+
+    public static final String PREF_CASIO_ALERT_SMS = "casio_alert_sms";
+    public static final String PREF_LIGHT_DURATION_LONGER = "light_duration_longer";
+    public static final String PREF_AUTOREMOVE_MESSAGE = "autoremove_message";
+    public static final String PREF_SEND_APP_NOTIFICATIONS = "send_app_notifications";
+    public static final String PREF_NOTIFICATION_WAKE_ON_OPEN = "notification_wake_on_open";
+    public static final String PREF_AUTOREMOVE_NOTIFICATIONS = "autoremove_notifications";
+    public static final String PREF_SCREEN_ON_ON_NOTIFICATIONS = "screen_on_on_notifications";
+    public static final String PREF_WORKOUT_KEEP_SCREEN_ON = "workout_keep_screen_on";
+    public static final String PREF_OPERATING_SOUNDS = "operating_sounds";
+    public static final String PREF_KEY_VIBRATION = "key_vibration";
+    public static final String PREF_FAKE_RING_DURATION = "fake_ring_duration";
+
+    public static final String PREF_WORLD_CLOCKS = "pref_world_clocks";
+    public static final String PREF_CONTACTS = "pref_contacts";
+    public static final String PREF_WIDGETS = "pref_widgets";
+
+    public static final String PREF_MUSIC_MANAGEMENT = "pref_music_management";
+
+    public static final String PREF_ANTILOST_ENABLED = "pref_antilost_enabled";
+    public static final String PREF_HYDRATION_SWITCH = "pref_hydration_switch";
+    public static final String PREF_HYDRATION_PERIOD = "pref_hydration_period";
+    public static final String PREF_HYDRATION_DND = "pref_hydration_dnd";
+    public static final String PREF_HYDRATION_DND_START = "pref_hydration_dnd_start";
+    public static final String PREF_HYDRATION_DND_END = "pref_hydration_dnd_end";
+    public static final String PREF_AMPM_ENABLED = "pref_ampm_enabled";
+
+    public static final String PREF_SONYSWR12_LOW_VIBRATION = "vibration_preference";
+    public static final String PREF_SONYSWR12_STAMINA = "stamina_preference";
+    public static final String PREF_SONYSWR12_SMART_INTERVAL = "smart_alarm_interval_preference";
+
+    public static final String PREF_BT_CONNECTED_ADVERTISEMENT = "bt_connected_advertisement";
+    public static final String PREF_TRANSLITERATION_LANGUAGES = "pref_transliteration_languages";
+
+    public static final String PREF_BLUETOOTH_CALLS_PAIR = "bluetooth_calls_pair";
+    public static final String PREF_BLUETOOTH_CALLS_ENABLED = "bluetooth_calls_enabled";
+    public static final String PREF_DISPLAY_CALLER = "display_caller";
+    public static final String PREF_NOTIFICATION_DELAY_CALLS = "notification_delay_calls";
+    public static final String PREF_CALL_REJECT_METHOD = "call_reject_method";
+
+    public static final String WIFI_HOTSPOT_SSID = "wifi_hotspot_ssid";
+    public static final String WIFI_HOTSPOT_PASSWORD = "wifi_hotspot_password";
+    public static final String WIFI_HOTSPOT_START = "wifi_hotspot_start";
+    public static final String WIFI_HOTSPOT_STOP = "wifi_hotspot_stop";
+    public static final String WIFI_HOTSPOT_STATUS = "wifi_hotspot_status";
+
+    public static final String PREF_APP_LOGS_START = "pref_app_logs_start";
+    public static final String PREF_APP_LOGS_STOP = "pref_app_logs_stop";
+
+    public static final String MORNING_UPDATES_ENABLED = "morning_updates_enabled";
+    public static final String MORNING_UPDATES_CATEGORIES_SORTABLE = "morning_updates_categories";
+
+    public static final String SHORTCUT_CARDS_SORTABLE = "shortcut_cards_sortable";
+
+    public static final String PREF_WATCHFACE = "watchface";
+
+    public static final String FTP_SERVER_ROOT_DIR = "ftp_server_root_dir";
+    public static final String FTP_SERVER_ADDRESS = "ftp_server_address";
+    public static final String FTP_SERVER_USERNAME = "ftp_server_username";
+    public static final String FTP_SERVER_START = "ftp_server_start";
+    public static final String FTP_SERVER_STOP = "ftp_server_stop";
+    public static final String FTP_SERVER_STATUS = "ftp_server_status";
+
+    public static final String PREF_NOTHING_EAR1_INEAR = "pref_nothing_inear_detection";
+    public static final String PREF_NOTHING_EAR1_AUDIOMODE = "pref_nothing_audiomode";
+
+
+    public static final String PREF_HUAWEI_FREEBUDS_INEAR = "pref_freebuds_inear_detection";
+    public static final String PREF_HUAWEI_FREEBUDS_AUDIOMODE = "pref_freebuds_audiomode";
+
+    public static final String PREF_GALAXY_BUDS_AMBIENT_MODE = "pref_galaxy_buds_ambient_mode";
+    public static final String PREF_GALAXY_BUDS_AMBIENT_VOICE_FOCUS = "pref_galaxy_buds_ambient_voice_focus";
+    public static final String PREF_GALAXY_BUDS_AMBIENT_VOLUME = "pref_galaxy_buds_ambient_volume";
+    public static final String PREF_GALAXY_BUDS_LOCK_TOUCH = "pref_galaxy_buds_lock_touch";
+    public static final String PREF_GALAXY_BUDS_GAME_MODE = "pref_galaxy_buds_game_mode";
+    public static final String PREF_GALAXY_BUDS_EQUALIZER = "pref_galaxy_buds_equalizer";
+    public static final String PREF_GALAXY_BUDS_EQUALIZER_DOLBY = "pref_galaxy_buds_equalizer_dolby";
+    public static final String PREF_GALAXY_BUDS_EQUALIZER_MODE = "pref_galaxy_buds_equalizer_mode";
+    public static final String PREF_GALAXY_BUDS_TOUCH_LEFT = "pref_galaxy_buds_touch_left";
+    public static final String PREF_GALAXY_BUDS_TOUCH_RIGHT = "pref_galaxy_buds_touch_right";
+    public static final String PREF_GALAXY_BUDS_TOUCH_RIGHT_SWITCH = "pref_galaxy_buds_touch_right_switch";
+    public static final String PREF_GALAXY_BUDS_TOUCH_LEFT_SWITCH = "pref_galaxy_buds_touch_left_switch";
+    public static final String PREF_GALAXY_BUDS_LIVE_ANC = "pref_galaxy_buds_live_anc";
+    public static final String PREF_GALAXY_BUDS_PRESSURE_RELIEF = "pref_galaxy_buds_live_pressure_relief";
+    public static final String PREF_GALAXY_BUDS_AMBIENT_SOUND = "pref_galaxy_buds_ambient_sound";
+    public static final String PREF_GALAXY_BUDS_PRO_NOISE_CONTROL="pref_galaxy_buds_pro_noise_control";
+    public static final String PREF_GALAXY_BUDS_2_NOISE_CONTROL="pref_galaxy_buds_2_noise_control";
+    public static final String PREF_GALAXY_PRO_DOUBLE_TAP_EDGE ="pref_galaxy_pro_double_tap_edge";
+    public static final String PREF_GALAXY_BUDS_PRO_IN_EAR_DETECTION ="pref_galaxy_buds_pro_in_ear_detection";
+    public static final String PREF_GALAXY_BUDS_PRO_VOICE_DETECT ="pref_galaxy_buds_pro_voice_detect";
+    public static final String PREF_GALAXY_BUDS_PRO_VOICE_DETECT_DURATION ="pref_galaxy_buds_pro_voice_detect_duration";
+    public static final String PREF_GALAXY_BUDS_PRO_BALANCE="pref_galaxy_buds_pro_balance";
+    public static final String PREF_GALAXY_BUDS_PRO_READ_NOTIFICATIONS_OUTLOUD ="pref_galaxy_buds_pro_read_notifications_outloud";
+    public static final String PREF_GALAXY_BUDS_AMBIENT_MODE_DURING_CALL ="pref_galaxy_buds_ambient_mode_during_call";
+    public static final String PREF_GALAXY_BUDS_PRO_AMBIENT_VOLUME_RIGHT ="pref_galaxy_buds_pro_ambient_volume_right";
+    public static final String PREF_GALAXY_BUDS_PRO_AMBIENT_VOLUME_LEFT="pref_galaxy_buds_pro_ambient_volume_left";
+    public static final String PREF_GALAXY_BUDS_PRO_AMBIENT_SOUND_TONE ="pref_galaxy_buds_pro_ambient_sound_tone";
+    public static final String PREFS_NOISE_CONTROL_WITH_ONE_EARBUD ="pref_galaxy_buds_noise_controls_with_one_earbud";
+    public static final String PREF_GALAXY_BUDS_PRO_ANC_LEVEL="pref_galaxy_buds_pro_anc_level";
+    public static final String PREFS_GALAXY_BUDS_SEAMLESS_CONNECTION="prefs_galaxy_buds_seamless_connection";
+
+    public static final String PREF_REDMI_BUDS_5_PRO_AMBIENT_SOUND_CONTROL="pref_redmi_buds_5_pro_ambient_sound_control";
+    public static final String PREF_REDMI_BUDS_5_PRO_NOISE_CANCELLING_STRENGTH="pref_redmi_buds_5_pro_noise_cancelling_strength";
+    public static final String PREF_REDMI_BUDS_5_PRO_TRANSPARENCY_STRENGTH="pref_redmi_buds_5_pro_transparency_strength";
+    public static final String PREF_REDMI_BUDS_5_PRO_ADAPTIVE_NOISE_CANCELLING="pref_redmi_buds_5_pro_adaptive_noise_cancelling";
+//    public static final String PREF_REDMI_BUDS_5_PRO_PERSONALIZED_NOISE_CANCELLING="pref_redmi_buds_5_pro_personalized_noise_cancelling";
+    public static final String PREF_REDMI_BUDS_5_PRO_CONTROL_SINGLE_TAP_LEFT="pref_redmi_buds_5_pro_control_single_tap_left";
+    public static final String PREF_REDMI_BUDS_5_PRO_CONTROL_SINGLE_TAP_RIGHT="pref_redmi_buds_5_pro_control_single_tap_right";
+    public static final String PREF_REDMI_BUDS_5_PRO_CONTROL_DOUBLE_TAP_LEFT="pref_redmi_buds_5_pro_control_double_tap_left";
+    public static final String PREF_REDMI_BUDS_5_PRO_CONTROL_DOUBLE_TAP_RIGHT="pref_redmi_buds_5_pro_control_double_tap_right";
+    public static final String PREF_REDMI_BUDS_5_PRO_CONTROL_TRIPLE_TAP_LEFT="pref_redmi_buds_5_pro_control_triple_tap_left";
+    public static final String PREF_REDMI_BUDS_5_PRO_CONTROL_TRIPLE_TAP_RIGHT="pref_redmi_buds_5_pro_control_triple_tap_right";
+    public static final String PREF_REDMI_BUDS_5_PRO_CONTROL_LONG_TAP_MODE_LEFT ="pref_redmi_buds_5_pro_control_long_tap_mode_left";
+    public static final String PREF_REDMI_BUDS_5_PRO_CONTROL_LONG_TAP_MODE_RIGHT ="pref_redmi_buds_5_pro_control_long_tap_mode_right";
+    public static final String PREF_REDMI_BUDS_5_PRO_CONTROL_LONG_TAP_SETTINGS_LEFT ="pref_redmi_buds_5_pro_control_long_tap_settings_left";
+    public static final String PREF_REDMI_BUDS_5_PRO_CONTROL_LONG_TAP_SETTINGS_RIGHT ="pref_redmi_buds_5_pro_control_long_tap_settings_right";
+    public static final String PREF_REDMI_BUDS_5_PRO_WEARING_DETECTION="pref_redmi_buds_5_pro_wearing_detection";
+    public static final String PREF_REDMI_BUDS_5_PRO_AUTO_REPLY_PHONECALL="pref_redmi_buds_5_pro_auto_reply_phonecall";
+    public static final String PREF_REDMI_BUDS_5_PRO_DOUBLE_CONNECTION="pref_redmi_buds_5_pro_double_connection";
+//    public static final String PREF_REDMI_BUDS_5_PRO_SURROUND_SOUND="pref_redmi_buds_5_pro_surround_sound";
+//    public static final String PREF_REDMI_BUDS_5_PRO_SURROUND_SOUND_MODE="pref_redmi_buds_5_pro_surround_sound_mode";
+    public static final String PREF_REDMI_BUDS_5_PRO_EQUALIZER_PRESET="pref_redmi_buds_5_pro_equalizer_preset";
+    public static final String PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_62="pref_redmi_buds_5_pro_equalizer_band_62";
+    public static final String PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_125="pref_redmi_buds_5_pro_equalizer_band_125";
+    public static final String PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_250="pref_redmi_buds_5_pro_equalizer_band_250";
+    public static final String PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_500="pref_redmi_buds_5_pro_equalizer_band_500";
+    public static final String PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_1k="pref_redmi_buds_5_pro_equalizer_band_1k";
+    public static final String PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_2k="pref_redmi_buds_5_pro_equalizer_band_2k";
+    public static final String PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_4k="pref_redmi_buds_5_pro_equalizer_band_4k";
+    public static final String PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_8k="pref_redmi_buds_5_pro_equalizer_band_8k";
+    public static final String PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_12k="pref_redmi_buds_5_pro_equalizer_band_12k";
+    public static final String PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_16k="pref_redmi_buds_5_pro_equalizer_band_16k";
+    public static final String PREF_REDMI_BUDS_5_PRO_ADAPTIVE_SOUND="pref_redmi_buds_5_pro_adaptive_sound";
+
+    public static final String PREF_SONY_AUDIO_CODEC = "pref_sony_audio_codec";
+    public static final String PREF_SONY_PROTOCOL_VERSION = "pref_protocol_version";
+    public static final String PREF_SONY_ACTUAL_PROTOCOL_VERSION = "pref_actual_protocol_version";
+    public static final String PREF_SONY_AMBIENT_SOUND_CONTROL = "pref_sony_ambient_sound_control";
+    public static final String PREF_SOUNDCORE_AMBIENT_SOUND_CONTROL = "pref_soundcore_ambient_sound_control";
+    public static final String PREF_SOUNDCORE_ADAPTIVE_NOISE_CANCELLING = "pref_adaptive_noise_cancelling";
+    public static final String PREF_SOUNDCORE_WIND_NOISE_REDUCTION= "pref_soundcore_wind_noise_reduction";
+    public static final String PREF_SOUNDCORE_TRANSPARENCY_VOCAL_MODE = "pref_soundcore_transparency_vocal_mode";
+    public static final String PREF_SOUNDCORE_WEARING_DETECTION = "pref_soundcore_wearing_detection";
+    public static final String PREF_SOUNDCORE_WEARING_TONE = "pref_soundcore_wearing_tone";
+    public static final String PREF_SOUNDCORE_TOUCH_TONE = "pref_soundcore_touch_tone";
+    public static final String PREF_SOUNDCORE_CONTROL_SINGLE_TAP_DISABLED = "pref_soundcore_control_single_tap_disabled";
+    public static final String PREF_SOUNDCORE_CONTROL_DOUBLE_TAP_DISABLED = "pref_soundcore_control_double_tap_disabled";
+    public static final String PREF_SOUNDCORE_CONTROL_TRIPLE_TAP_DISABLED = "pref_soundcore_control_triple_tap_disabled";
+    public static final String PREF_SOUNDCORE_CONTROL_LONG_PRESS_DISABLED = "pref_soundcore_control_long_press_disabled";
+    public static final String PREF_SOUNDCORE_CONTROL_SINGLE_TAP_ACTION_LEFT = "pref_soundcore_control_single_tap_action_left";
+    public static final String PREF_SOUNDCORE_CONTROL_DOUBLE_TAP_ACTION_LEFT = "pref_soundcore_control_double_tap_action_left";
+    public static final String PREF_SOUNDCORE_CONTROL_TRIPLE_TAP_ACTION_LEFT = "pref_soundcore_control_triple_tap_action_left";
+    public static final String PREF_SOUNDCORE_CONTROL_LONG_PRESS_ACTION_LEFT = "pref_soundcore_control_long_press_action_left";
+    public static final String PREF_SOUNDCORE_CONTROL_SINGLE_TAP_ACTION_RIGHT = "pref_soundcore_control_single_tap_action_right";
+    public static final String PREF_SOUNDCORE_CONTROL_DOUBLE_TAP_ACTION_RIGHT = "pref_soundcore_control_double_tap_action_right";
+    public static final String PREF_SOUNDCORE_CONTROL_TRIPLE_TAP_ACTION_RIGHT = "pref_soundcore_control_triple_tap_action_right";
+    public static final String PREF_SOUNDCORE_CONTROL_LONG_PRESS_ACTION_RIGHT = "pref_soundcore_control_long_press_action_right";
+    public static final String PREF_SOUNDCORE_VOICE_PROMPTS = "pref_soundcore_voice_prompts";
+    public static final String PREF_SOUNDCORE_BUTTON_BRIGHTNESS = "pref_soundcore_button_brightness";
+    public static final String PREF_SOUNDCORE_AUTO_POWER_OFF = "pref_soundcore_auto_power_off";
+    public static final String PREF_SOUNDCORE_LDAC_MODE = "pref_soundcore_ldac_mode";
+    public static final String PREF_SOUNDCORE_ADAPTIVE_DIRECTION = "pref_soundcore_adaptive_direction";
+    public static final String PREF_SOUNDCORE_EQUALIZER_PRESET = "pref_soundcore_equalizer_preset";
+    public static final String PREF_SOUNDCORE_EQUALIZER_CUSTOM = "pref_soundcore_equalizer_custom";
+    public static final String PREF_SOUNDCORE_EQUALIZER_DIRECTION = "pref_soundcore_equalizer_direction";
+    public static final String PREF_SOUNDCORE_EQUALIZER_RESET = "pref_soundcore_equalizer_reset";
+    public static final String PREF_SOUNDCORE_EQUALIZER_BAND1_FREQ = "pref_soundcore_equalizer_band1_freq";
+    public static final String PREF_SOUNDCORE_EQUALIZER_BAND1_VALUE = "pref_soundcore_equalizer_band1_value";
+    public static final String PREF_SOUNDCORE_EQUALIZER_BAND2_FREQ = "pref_soundcore_equalizer_band2_freq";
+    public static final String PREF_SOUNDCORE_EQUALIZER_BAND2_VALUE = "pref_soundcore_equalizer_band2_value";
+    public static final String PREF_SOUNDCORE_EQUALIZER_BAND3_FREQ = "pref_soundcore_equalizer_band3_freq";
+    public static final String PREF_SOUNDCORE_EQUALIZER_BAND3_VALUE = "pref_soundcore_equalizer_band3_value";
+    public static final String PREF_SOUNDCORE_EQUALIZER_BAND4_FREQ = "pref_soundcore_equalizer_band4_freq";
+    public static final String PREF_SOUNDCORE_EQUALIZER_BAND4_VALUE = "pref_soundcore_equalizer_band4_value";
+    public static final String PREF_SOUNDCORE_EQUALIZER_BAND5_FREQ = "pref_soundcore_equalizer_band5_freq";
+    public static final String PREF_SOUNDCORE_EQUALIZER_BAND5_VALUE = "pref_soundcore_equalizer_band5_value";
+    public static final String PREF_SOUNDCORE_EQUALIZER_BAND6_FREQ = "pref_soundcore_equalizer_band6_freq";
+    public static final String PREF_SOUNDCORE_EQUALIZER_BAND6_VALUE = "pref_soundcore_equalizer_band6_value";
+    public static final String PREF_SOUNDCORE_EQUALIZER_BAND7_FREQ = "pref_soundcore_equalizer_band7_freq";
+    public static final String PREF_SOUNDCORE_EQUALIZER_BAND7_VALUE = "pref_soundcore_equalizer_band7_value";
+    public static final String PREF_SOUNDCORE_EQUALIZER_BAND8_FREQ = "pref_soundcore_equalizer_band8_freq";
+    public static final String PREF_SOUNDCORE_EQUALIZER_BAND8_VALUE = "pref_soundcore_equalizer_band8_value";
+    public static final String PREF_SOUNDCORE_EQUALIZER_BAND9_FREQ = "pref_soundcore_equalizer_band9_freq";
+    public static final String PREF_SOUNDCORE_EQUALIZER_BAND9_VALUE = "pref_soundcore_equalizer_band9_value";
+    public static final String PREF_SONY_AMBIENT_SOUND_CONTROL_BUTTON_MODE = "pref_sony_ambient_sound_control_button_mode";
+    public static final String PREF_SONY_FOCUS_VOICE = "pref_sony_focus_voice";
+    public static final String PREF_SONY_AMBIENT_SOUND_LEVEL = "pref_sony_ambient_sound_level";
+    public static final String PREF_SONY_NOISE_OPTIMIZER_START = "pref_sony_noise_optimizer_start";
+    public static final String PREF_SONY_NOISE_OPTIMIZER_CANCEL = "pref_sony_noise_optimizer_cancel";
+    public static final String PREF_SONY_NOISE_OPTIMIZER_STATUS = "pref_sony_noise_optimizer_status";
+    public static final String PREF_SONY_NOISE_OPTIMIZER_STATE_PRESSURE = "pref_sony_noise_optimizer_state_pressure";
+    public static final String PREF_SONY_SOUND_POSITION = "pref_sony_sound_position";
+    public static final String PREF_SONY_SURROUND_MODE = "pref_sony_surround_mode";
+    public static final String PREF_SONY_EQUALIZER = "pref_sony_equalizer";
+    public static final String PREF_SONY_EQUALIZER_MODE = "pref_sony_equalizer_mode";
+    public static final String PREF_SONY_AUDIO_UPSAMPLING = "pref_sony_audio_upsampling";
+    public static final String PREF_SONY_EQUALIZER_BAND_400 = "pref_sony_equalizer_band_400";
+    public static final String PREF_SONY_EQUALIZER_BAND_1000 = "pref_sony_equalizer_band_1000";
+    public static final String PREF_SONY_EQUALIZER_BAND_2500 = "pref_sony_equalizer_band_2500";
+    public static final String PREF_SONY_EQUALIZER_BAND_6300 = "pref_sony_equalizer_band_6300";
+    public static final String PREF_SONY_EQUALIZER_BAND_16000 = "pref_sony_equalizer_band_16000";
+    public static final String PREF_SONY_EQUALIZER_BASS = "pref_sony_equalizer_bass";
+    public static final String PREF_SONY_TOUCH_SENSOR = "pref_sony_touch_sensor";
+    public static final String PREF_SONY_PAUSE_WHEN_TAKEN_OFF = "pref_sony_pause_when_taken_off";
+    public static final String PREF_SONY_BUTTON_MODE_LEFT = "pref_sony_button_mode_left";
+    public static final String PREF_SONY_BUTTON_MODE_RIGHT = "pref_sony_button_mode_right";
+    public static final String PREF_SONY_QUICK_ACCESS_DOUBLE_TAP = "pref_sony_quick_access_double_tap";
+    public static final String PREF_SONY_QUICK_ACCESS_TRIPLE_TAP = "pref_sony_quick_access_triple_tap";
+    public static final String PREF_SONY_AUTOMATIC_POWER_OFF = "pref_sony_automatic_power_off";
+    public static final String PREF_SONY_NOTIFICATION_VOICE_GUIDE = "pref_sony_notification_voice_guide";
+    public static final String PREF_SONY_SPEAK_TO_CHAT = "pref_sony_speak_to_chat";
+    public static final String PREF_SONY_SPEAK_TO_CHAT_SENSITIVITY = "pref_sony_speak_to_chat_sensitivity";
+    public static final String PREF_SONY_SPEAK_TO_CHAT_FOCUS_ON_VOICE = "pref_sony_speak_to_chat_focus_on_voice";
+    public static final String PREF_SONY_SPEAK_TO_CHAT_TIMEOUT = "pref_sony_speak_to_chat_timeout";
+    public static final String PREF_SONY_CONNECT_TWO_DEVICES = "pref_sony_connect_two_devices";
+    public static final String PREF_SONY_ADAPTIVE_VOLUME_CONTROL = "pref_adaptive_volume_control";
+    public static final String PREF_SONY_WIDE_AREA_TAP = "pref_wide_area_tap";
+
+    public static final String PREF_MOONDROP_EQUALIZER_PRESET = "pref_moondrop_equalizer_preset";
+    public static final String PREF_MOONDROP_TOUCH_PLAY_PAUSE_EARBUD = "pref_moondrop_touch_play_pause_earbud";
+    public static final String PREF_MOONDROP_TOUCH_PLAY_PAUSE_TRIGGER = "pref_moondrop_touch_play_pause_trigger";
+    public static final String PREF_MOONDROP_TOUCH_MEDIA_PREV_EARBUD = "pref_moondrop_touch_media_prev_earbud";
+    public static final String PREF_MOONDROP_TOUCH_MEDIA_PREV_TRIGGER = "pref_moondrop_touch_media_prev_trigger";
+    public static final String PREF_MOONDROP_TOUCH_MEDIA_NEXT_EARBUD = "pref_moondrop_touch_media_next_earbud";
+    public static final String PREF_MOONDROP_TOUCH_MEDIA_NEXT_TRIGGER = "pref_moondrop_touch_media_next_trigger";
+    public static final String PREF_MOONDROP_TOUCH_CALL_PICK_HANG_EARBUD = "pref_moondrop_touch_call_pick_hang_earbud";
+    public static final String PREF_MOONDROP_TOUCH_CALL_PICK_HANG_TRIGGER = "pref_moondrop_touch_call_pick_hang_trigger";
+    public static final String PREF_MOONDROP_TOUCH_CALL_START_EARBUD = "pref_moondrop_touch_call_start_earbud";
+    public static final String PREF_MOONDROP_TOUCH_CALL_START_TRIGGER = "pref_moondrop_touch_call_start_trigger";
+    public static final String PREF_MOONDROP_TOUCH_ASSISTANT_EARBUD = "pref_moondrop_touch_assistant_earbud";
+    public static final String PREF_MOONDROP_TOUCH_ASSISTANT_TRIGGER = "pref_moondrop_touch_assistant_trigger";
+    public static final String PREF_MOONDROP_TOUCH_ANC_MODE_EARBUD = "pref_moondrop_touch_anc_mode_earbud";
+    public static final String PREF_MOONDROP_TOUCH_ANC_MODE_TRIGGER = "pref_moondrop_touch_anc_mode_trigger";
+
+    public static final String PREF_MISCALE_WEIGHT_UNIT = "pref_miscale_weight_unit";
+    public static final String PREF_MISCALE_SMALL_OBJECTS = "pref_miscale_small_objects";
+
+    public static final String PREF_MIJIA_LYWSD_COMFORT_CHARACTERISTIC_LENGTH = "pref_mijia_lywsd_comfort_characteristic_length";
+    public static final String PREF_MIJIA_LYWSD_COMFORT_LEVEL = "pref_mijia_lywsd_comfort_level";
+    public static final String PREF_MIJIA_LYWSD_COMFORT_TEMPERATURE_LOWER = "pref_mijia_lywsd_comfort_temperature_lower";
+    public static final String PREF_MIJIA_LYWSD_COMFORT_TEMPERATURE_UPPER = "pref_mijia_lywsd_comfort_temperature_upper";
+    public static final String PREF_MIJIA_LYWSD_COMFORT_HUMIDITY_LOWER = "pref_mijia_lywsd_comfort_humidity_lower";
+    public static final String PREF_MIJIA_LYWSD_COMFORT_HUMIDITY_UPPER = "pref_mijia_lywsd_comfort_humidity_upper";
+
+    public static final String PREF_QC35_NOISE_CANCELLING_LEVEL = "qc35_noise_cancelling_level";
+
+    public static final String PREFS_ACTIVITY_IN_DEVICE_CARD = "prefs_activity_in_device_card";
+    public static final String PREFS_ACTIVITY_IN_DEVICE_CARD_STEPS = "prefs_activity_in_device_card_steps";
+    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";
+
+    public static final String PREF_VESC_MINIMUM_VOLTAGE = "vesc_minimum_battery_voltage";
+    public static final String PREF_VESC_MAXIMUM_VOLTAGE = "vesc_maximum_battery_voltage";
+
+    public static final String PREF_IDASEN_SIT_HEIGHT = "idasen_sit_height";
+    public static final String PREF_IDASEN_MID_HEIGHT = "idasen_mid_height";
+    public static final String PREF_IDASEN_STAND_HEIGHT = "idasen_stand_height";
+
+    public static final String PREF_SOUNDS = "sounds";
+    public static final String PREF_AUTH_KEY = "authkey";
+    public static final String PREF_USER_FITNESS_GOAL = "fitness_goal";
+    public static final String PREF_USER_FITNESS_GOAL_NOTIFICATION = "fitness_goal_notification";
+    public static final String PREF_USER_FITNESS_GOAL_SECONDARY = "fitness_goal_secondary";
+    public static final String PREF_VITALITY_SCORE_7_DAY = "pref_vitality_score_7_day";
+    public static final String PREF_VITALITY_SCORE_DAILY = "pref_vitality_score_daily";
+
+    public static final String PREF_PHONE_SILENT_MODE = "phone_silent_mode";
+
+    public static final String PREF_HOURLY_CHIME_ENABLE = "hourly_chime_enable";
+    public static final String PREF_HOURLY_CHIME_START = "hourly_chime_start";
+    public static final String PREF_HOURLY_CHIME_END = "hourly_chime_end";
+
+    public static final String PREF_VOICE_SERVICE_LANGUAGE = "voice_service_language";
+
+    public static final String PREF_TEMPERATURE_SCALE_CF = "temperature_scale_cf";
+
+    public static final String PREF_FAKE_ANDROID_ID = "fake_android_id";
+
+    public static final String PREF_HEARTRATE_AUTOMATIC_ENABLE = "heartrate_automatic_enable";
+    public static final String PREF_SPO_AUTOMATIC_ENABLE = "spo_automatic_enable";
+
+    public static final String PREF_FORCE_OPTIONS = "pref_force_options";
+    public static final String PREF_FORCE_ENABLE_SMART_ALARM = "pref_force_enable_smart_alarm";
+    public static final String PREF_FORCE_ENABLE_WEAR_LOCATION = "pref_force_enable_wear_location";
+    public static final String PREF_FORCE_DND_SUPPORT = "pref_force_dnd_support";
+    public static final String PREF_FORCE_ENABLE_HEARTRATE_SUPPORT = "pref_force_enable_heartrate_support";
+    public static final String PREF_FORCE_ENABLE_SPO2_SUPPORT = "pref_force_enable_spo2_support";
+    public static final String PREF_IGNORE_WAKEUP_STATUS_START = "pref_force_ignore_wakeup_status_start";
+    public static final String PREF_IGNORE_WAKEUP_STATUS_END = "pref_force_ignore_wakeup_status_end";
+
+    public static final String PREF_FEMOMETER_MEASUREMENT_MODE = "femometer_measurement_mode";
+
+    public static final String PREF_PREFIX_NOTIFICATION_WITH_APP = "pref_prefix_notification_with_app";
+    public static final String PREF_DEVICE_ACTION_SELECTION_BROADCAST = "BROADCAST";
+    public static final String PREF_DEVICE_ACTION_FELL_SLEEP_SELECTIONS = "events_forwarding_fellsleep_action_selections";
+    public static final String PREF_DEVICE_ACTION_FELL_SLEEP_BROADCAST = "prefs_events_forwarding_fellsleep_broadcast";
+    public static final String PREF_DEVICE_ACTION_WOKE_UP_SELECTIONS = "events_forwarding_wokeup_action_selections";
+    public static final String PREF_DEVICE_ACTION_WOKE_UP_BROADCAST = "prefs_events_forwarding_wokeup_broadcast";
+    public static final String PREF_DEVICE_ACTION_START_NON_WEAR_SELECTIONS = "events_forwarding_startnonwear_action_selections";
+    public static final String PREF_DEVICE_ACTION_START_NON_WEAR_BROADCAST = "prefs_events_forwarding_startnonwear_broadcast";
+    public static final String PREF_CLAP_HANDS_TO_WAKEUP_DEVICE = "pref_key_clap_hands_to_wakeup_device";
+    public static final String PREF_POWER_SAVING = "pref_key_power_saving";
+    public static final String PREF_FORCE_CONNECTION_TYPE = "pref_force_connection_type";
+
+    public static final String PREF_AUTO_REPLY_INCOMING_CALL = "pref_auto_reply_phonecall";
+    public static final String PREF_AUTO_REPLY_INCOMING_CALL_DELAY = "pref_auto_reply_phonecall_delay";
+    public static final String PREF_SPEAK_NOTIFICATIONS_ALOUD = "pref_speak_notifications_aloud";
+    public static final String PREF_SPEAK_NOTIFICATIONS_FOCUS_EXCLUSIVE = "pref_speak_notifications_focus_exclusive";
+
+    public static final String PREF_CYCLING_SENSOR_PERSISTENCE_INTERVAL = "pref_cycling_persistence_interval";
+    public static final String PREF_CYCLING_SENSOR_WHEEL_DIAMETER = "pref_cycling_wheel_diameter";
+
+    public static final String PREFS_KEY_DEVICE_BLE_API_DEVICE_STATE = "prefs_device_ble_api_state";
+    public static final String PREFS_KEY_DEVICE_BLE_API_DEVICE_READ_WRITE = "prefs_device_ble_api_characteristic_read_write";
+    public static final String PREFS_KEY_DEVICE_BLE_API_DEVICE_NOTIFY = "prefs_device_ble_api_characteristic_notify";
+    public static final String PREFS_KEY_DEVICE_BLE_API_PACKAGE = "prefs_device_ble_api_package";
+
+    public static final String PREF_BATTERY_DISCHARGE_INTERVAL1_WATT = "battery_discharge_interval1_watt";
+    public static final String PREF_BATTERY_DISCHARGE_INTERVAL2_WATT = "battery_discharge_interval2_watt";
+    public static final String PREF_BATTERY_DISCHARGE_INTERVAL3_WATT = "battery_discharge_interval3_watt";
+    public static final String PREF_BATTERY_DISCHARGE_INTERVAL4_WATT = "battery_discharge_interval4_watt";
+    public static final String PREF_BATTERY_DISCHARGE_INTERVAL5_WATT = "battery_discharge_interval5_watt";
+    public static final String PREF_BATTERY_DISCHARGE_INTERVALS_SET = "battery_discharge_intervals_set";
+    public static final String PREF_BATTERY_DISCHARGE_MANAUAL = "battery_discharge_manual";
+    public static final String PREF_BATTERY_MINIMUM_CHARGE = "battery_minimum_charge";
+    public static final String PREF_BATTERY_ALLOW_PASS_THOUGH = "battery_allow_pass_though";
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSettingsUtils.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSettingsUtils.java
new file mode 100644
index 0000000..88dc40e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSettingsUtils.java
@@ -0,0 +1,212 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities.devicesettings;
+
+import android.text.InputFilter;
+import android.text.InputType;
+import android.text.Spanned;
+
+import androidx.preference.EditTextPreference;
+import androidx.preference.ListPreference;
+import androidx.preference.MultiSelectListPreference;
+import androidx.preference.Preference;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+public final class DeviceSettingsUtils {
+    private static final Logger LOG = LoggerFactory.getLogger(DeviceSettingsUtils.class);
+
+    private DeviceSettingsUtils() {
+        // utility class
+    }
+
+    /**
+     * Returns the preference key where to save the list of possible value for a preference, comma-separated.
+     */
+    public static String getPrefPossibleValuesKey(final String key) {
+        return String.format(Locale.ROOT, "%s_possible_values", key);
+    }
+
+    /**
+     * Returns the preference key where to save the list of entry labels for a preference, comma-separated.
+     */
+    public static String getPrefPossibleValueLabelsKey(final String key) {
+        return String.format(Locale.ROOT, "%s_possible_value_labels", key);
+    }
+
+    /**
+     * Returns the preference key where to that a config was reported as supported (boolean).
+     */
+    public static String getPrefKnownConfig(final String key) {
+        return String.format(Locale.ROOT, "%s_is_known", key);
+    }
+
+    /**
+     * Populates a list preference, or hides it if no known supported values are known.
+     */
+    public static void populateOrHideListPreference(final String prefKey,
+                                                    final DeviceSpecificSettingsHandler handler,
+                                                    final Prefs prefs) {
+        final Preference pref = handler.findPreference(prefKey);
+        if (pref == null) {
+            return;
+        }
+
+        // Get the list of possible values for this preference, as reported by the band
+        final List<String> possibleValues = prefs.getList(getPrefPossibleValuesKey(prefKey), null);
+        if (possibleValues == null || possibleValues.isEmpty()) {
+            // The band hasn't reported this setting, so we don't know the possible values.
+            // Hide it
+            pref.setVisible(false);
+
+            return;
+        }
+
+        final CharSequence[] originalEntries;
+        final CharSequence[] originalValues;
+
+        if (pref instanceof ListPreference) {
+            originalEntries = ((ListPreference) pref).getEntries();
+            originalValues = ((ListPreference) pref).getEntryValues();
+        } else if (pref instanceof MultiSelectListPreference) {
+            originalEntries = ((MultiSelectListPreference) pref).getEntries();
+            originalValues = ((MultiSelectListPreference) pref).getEntryValues();
+        } else {
+            LOG.error("Unknown list pref class {}", pref.getClass().getName());
+            return;
+        }
+
+        final Map<CharSequence, CharSequence> entryNames = new HashMap<>();
+        final List<String> knownLabels = prefs.getList(getPrefPossibleValueLabelsKey(prefKey), null);
+        if (knownLabels != null) {
+            // We got some known labels from the watch
+            if (knownLabels.size() != possibleValues.size()) {
+                LOG.warn(
+                        "Number of possible values ({}) and labels ({}) for {} differs - this should never happen",
+                        possibleValues.size(),
+                        knownLabels.size(),
+                        prefKey
+                );
+
+                // Abort and hide preference - we can't safely recover from this
+                pref.setVisible(false);
+                return;
+            }
+
+            for (int i = 0; i < knownLabels.size(); i++) {
+                entryNames.put(possibleValues.get(i), knownLabels.get(i));
+            }
+        } else {
+            for (int i = 0; i < originalEntries.length; i++) {
+                entryNames.put(originalValues[i], originalEntries[i]);
+            }
+        }
+
+        final CharSequence[] entries = new CharSequence[possibleValues.size()];
+        final CharSequence[] values = new CharSequence[possibleValues.size()];
+        for (int i = 0; i < possibleValues.size(); i++) {
+            final String possibleValue = possibleValues.get(i);
+            final CharSequence knownLabel = entryNames.get(possibleValue);
+
+            if (knownLabel != null) {
+                entries[i] = knownLabel;
+            } else {
+                entries[i] = handler.getContext().getString(R.string.menuitem_unknown_app, possibleValue);
+            }
+            values[i] = possibleValue;
+        }
+
+        if (pref instanceof ListPreference) {
+            ((ListPreference) pref).setEntries(entries);
+            ((ListPreference) pref).setEntryValues(values);
+        } else if (pref instanceof MultiSelectListPreference) {
+            ((MultiSelectListPreference) pref).setEntries(entries);
+            ((MultiSelectListPreference) pref).setEntryValues(values);
+        }
+    }
+
+    /**
+     * Hides the the prefToHide preference if none of the preferences in the preferences list are
+     * visible.
+     */
+    public static void hidePrefIfNoneVisible(final DeviceSpecificSettingsHandler handler,
+                                             final String prefToHide,
+                                             final List<String> subPrefs) {
+        final Preference pref = handler.findPreference(prefToHide);
+        if (pref == null) {
+            return;
+        }
+
+        for (final String subPrefKey : subPrefs) {
+            final Preference subPref = handler.findPreference(subPrefKey);
+            if (subPref == null) {
+                continue;
+            }
+            if (subPref.isVisible()) {
+                // At least one preference is visible
+                return;
+            }
+        }
+
+        // No preference was visible, hide
+        pref.setVisible(false);
+    }
+
+    public static void enforceMinMax(final EditTextPreference pref, final int minValue, final int maxValue) {
+        if (minValue >= maxValue) {
+            LOG.warn("Invalid min/max values for {}: {}/{}", pref.getKey(), minValue, maxValue);
+            return;
+        }
+
+        pref.setOnBindEditTextListener(p -> {
+            p.setInputType(InputType.TYPE_CLASS_NUMBER);
+            p.setFilters(new InputFilter[]{new MinMaxInputFilter(minValue, maxValue)});
+            p.setSelection(p.getText().length());
+        });
+    }
+
+    public static final class MinMaxInputFilter implements InputFilter {
+        private final int min;
+        private final int max;
+
+        public MinMaxInputFilter(final int min, final int max) {
+            this.min = min;
+            this.max = max;
+        }
+
+        @Override
+        public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
+            try {
+                final int input = Integer.parseInt(dest.toString() + source.toString());
+                if (input >= min && input <= max) {
+                    return null;
+                }
+            } catch (final NumberFormatException ignored) {
+            }
+            return "";
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSpecificSettings.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSpecificSettings.java
new file mode 100644
index 0000000..5cd514f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSpecificSettings.java
@@ -0,0 +1,193 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities.devicesettings;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.XmlRes;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * A class that contains the device-specific settings screens for a device. All the integers in this
+ * class correspond to xml resources for preferences.
+ * <p>
+ * This class contains 2 types of screens:
+ * - Root screens - the ones that are displayed in the first page of the device settings activity. These can be
+ * normal preference screens / preferences, or dummy root screens (see {@link DeviceSpecificSettingsScreen}.
+ * - Sub-screens - a screen that is opened when one of the {@link DeviceSpecificSettingsScreen} is clicked.
+ * <p>
+ * There can be an arbitrary number of nested sub-screens, as long as they are all mapped by key in the
+ * subScreens map.
+ * <p>
+ * See the XiaomiCoordinator and ZeppOsCoordinator for example usages.
+ */
+public class DeviceSpecificSettings implements Parcelable {
+    private final List<Integer> rootScreens = new ArrayList<>();
+    private final Map<String, List<Integer>> subScreens = new LinkedHashMap<>();
+
+    public DeviceSpecificSettings() {
+    }
+
+    public DeviceSpecificSettings(final int[] rootScreens) {
+        for (final int setting : rootScreens) {
+            this.rootScreens.add(setting);
+        }
+    }
+
+    public void addRootScreen(@XmlRes final int screen) {
+        if (!rootScreens.contains(screen)) {
+            rootScreens.add(screen);
+        }
+    }
+
+    public List<Integer> addRootScreen(final DeviceSpecificSettingsScreen screen) {
+        if (!rootScreens.contains(screen.getXml())) {
+            rootScreens.add(screen.getXml());
+        }
+
+        return addSubScreen(screen, screen.getXml());
+    }
+
+    public List<Integer> addRootScreen(final DeviceSpecificSettingsScreen screen, final int... subScreens) {
+        final List<Integer> subScreenScreens = addRootScreen(screen);
+        for (final int subScreen : subScreens) {
+            subScreenScreens.add(subScreen);
+        }
+        return subScreenScreens;
+    }
+
+    public void addRootScreen(final int index, @XmlRes final int screen) {
+        rootScreens.add(index, screen);
+    }
+
+    public List<Integer> addSubScreen(final DeviceSpecificSettingsScreen rootScreen, final int... screens) {
+        return addSubScreen(rootScreen.getKey(), screens);
+    }
+
+    private List<Integer> addSubScreen(final String key, final int... screens) {
+        if (!subScreens.containsKey(key)) {
+            subScreens.put(key, new ArrayList<>());
+        }
+
+        final List<Integer> subscreenPages = Objects.requireNonNull(subScreens.get(key));
+
+        for (final int screen : screens) {
+            if (!subscreenPages.contains(screen)) {
+                subscreenPages.add(screen);
+            }
+        }
+
+        return subscreenPages;
+    }
+
+    public void mergeFrom(final DeviceSpecificSettings deviceSpecificSettings) {
+        for (final Integer rootScreen : deviceSpecificSettings.rootScreens) {
+            addRootScreen(rootScreen);
+        }
+        for (final Map.Entry<String, List<Integer>> e : deviceSpecificSettings.subScreens.entrySet()) {
+            if (!subScreens.containsKey(e.getKey())) {
+                subScreens.put(e.getKey(), new ArrayList<>());
+            }
+
+            for (final int screen : e.getValue()) {
+                Objects.requireNonNull(subScreens.get(e.getKey())).add(screen);
+            }
+        }
+    }
+
+    public List<Integer> getRootScreens() {
+        return rootScreens;
+    }
+
+    @Nullable
+    public List<Integer> getScreen(@NonNull final String key) {
+        return subScreens.get(key);
+    }
+
+    public List<Integer> getAllNonRootScreens() {
+        final List<Integer> allScreens = new ArrayList<>();
+        for (final List<Integer> screens : subScreens.values()) {
+            allScreens.addAll(screens);
+        }
+        allScreens.removeAll(rootScreens);
+        return allScreens;
+    }
+
+    public List<Integer> getAllScreens() {
+        final List<Integer> allScreens = new ArrayList<>(rootScreens);
+        for (final List<Integer> screens : subScreens.values()) {
+            allScreens.addAll(screens);
+        }
+        return allScreens;
+    }
+
+    public static final Creator<DeviceSpecificSettings> CREATOR = new Creator<DeviceSpecificSettings>() {
+        @Override
+        public DeviceSpecificSettings createFromParcel(final Parcel in) {
+            final DeviceSpecificSettings deviceSpecificSettings = new DeviceSpecificSettings();
+            final int numRootScreens = in.readInt();
+            for (int i = 0; i < numRootScreens; i++) {
+                deviceSpecificSettings.addRootScreen(in.readInt());
+            }
+            final int numSubScreens = in.readInt();
+            for (int i = 0; i < numSubScreens; i++) {
+                final String key = in.readString();
+                final int numScreens = in.readInt();
+                final int[] screens = new int[numScreens];
+                for (int j = 0; j < numScreens; j++) {
+                    screens[j] = in.readInt();
+                }
+                deviceSpecificSettings.addSubScreen(key, screens);
+            }
+            return deviceSpecificSettings;
+        }
+
+        @Override
+        public DeviceSpecificSettings[] newArray(final int size) {
+            return new DeviceSpecificSettings[size];
+        }
+    };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull final Parcel dest, final int flags) {
+        dest.writeInt(rootScreens.size());
+        for (final Integer rootScreen : rootScreens) {
+            dest.writeInt(rootScreen);
+        }
+        dest.writeInt(subScreens.size());
+        for (final Map.Entry<String, List<Integer>> e : subScreens.entrySet()) {
+            dest.writeString(e.getKey());
+            dest.writeInt(e.getValue().size());
+            for (final Integer s : e.getValue()) {
+                dest.writeInt(s);
+            }
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSpecificSettingsCustomizer.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSpecificSettingsCustomizer.java
new file mode 100644
index 0000000..a64abb0
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSpecificSettingsCustomizer.java
@@ -0,0 +1,52 @@
+/*  Copyright (C) 2021-2024 Jos茅 Rebelo, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities.devicesettings;
+
+import android.os.Parcelable;
+
+import androidx.preference.Preference;
+
+import java.util.Set;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+/**
+ * A device-specific preference handler, that allows for concrete implementations to customize the preferences in
+ * the {@link DeviceSpecificSettingsFragment}.
+ */
+public interface DeviceSpecificSettingsCustomizer extends Parcelable {
+    /**
+     * Called when a {@link Preference} changes, not caused by user input (so the preference change listener is not called).
+     *
+     * @param preference the {@link Preference} preference that changed
+     * @param handler    the {@link DeviceSpecificSettingsHandler}
+     */
+    void onPreferenceChange(final Preference preference, final DeviceSpecificSettingsHandler handler);
+
+    /**
+     * Customize the settings on the {@link DeviceSpecificSettingsFragment}.
+     *
+     * @param handler the {@link DeviceSpecificSettingsHandler}
+     * @param prefs   the {@link android.content.SharedPreferences}
+     * @param rootKey the root key of the current screen
+     */
+    void customizeSettings(final DeviceSpecificSettingsHandler handler, Prefs prefs, final String rootKey);
+
+    /**
+     * Keys of preferences which should print its values as a summary below the preference name.
+     */
+    Set<String> getPreferenceKeysWithSummary();
+}
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
new file mode 100644
index 0000000..591d1e5
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSpecificSettingsFragment.java
@@ -0,0 +1,1472 @@
+/*  Copyright (C) 2019-2024 akasaka / Genjitsu Labs, Alicia Hormann, Andreas
+    B枚hler, Andreas Shimokawa, Arjan Schrijver, Cre3per, Damien Gaignon, Daniel
+    Dakhno, Daniele Gobbetti, Davis Mosenkovs, foxstidious, Jos茅 Rebelo, mamucho,
+    NekoBox, opavlov, Petr Van臎k, Yoran Vulker, Yukai Li, Zhong Jianxin
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities.devicesettings;
+
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.*;
+import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_CONTROL_CENTER_SORTABLE;
+import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_DISPLAY_ITEMS;
+import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_DISPLAY_ITEMS_SORTABLE;
+import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_EXPOSE_HR_THIRDPARTY;
+import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_SHORTCUTS;
+import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_SHORTCUTS_SORTABLE;
+import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_WORKOUT_ACTIVITY_TYPES_SORTABLE;
+import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MI2_DATEFORMAT;
+import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MI2_ROTATE_WRIST_TO_SWITCH_INFO;
+import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_NIGHT_MODE;
+import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_NIGHT_MODE_END;
+import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_NIGHT_MODE_OFF;
+import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_NIGHT_MODE_SCHEDULED;
+import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_NIGHT_MODE_START;
+import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_SWIPE_UNLOCK;
+
+import android.content.Context;
+import android.content.Intent;
+import android.media.AudioManager;
+import android.os.Bundle;
+import android.text.InputType;
+
+import androidx.annotation.NonNull;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+import androidx.preference.EditTextPreference;
+import androidx.preference.ListPreference;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceCategory;
+import androidx.preference.PreferenceScreen;
+import androidx.preference.SwitchPreferenceCompat;
+
+import org.apache.commons.lang3.ArrayUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.AbstractPreferenceFragment;
+import nodomain.freeyourgadget.gadgetbridge.activities.CalBlacklistActivity;
+import nodomain.freeyourgadget.gadgetbridge.activities.ConfigureContacts;
+import nodomain.freeyourgadget.gadgetbridge.activities.ConfigureWorldClocks;
+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.activities.musicmanager.MusicManagerActivity;
+import nodomain.freeyourgadget.gadgetbridge.activities.widgets.WidgetScreensListActivity;
+import nodomain.freeyourgadget.gadgetbridge.capabilities.HeartRateCapability;
+import nodomain.freeyourgadget.gadgetbridge.capabilities.password.PasswordCapabilityImpl;
+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.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.BatteryConfig;
+import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+import nodomain.freeyourgadget.gadgetbridge.util.preferences.GBSimpleSummaryProvider;
+import nodomain.freeyourgadget.gadgetbridge.util.preferences.MinMaxTextWatcher;
+import nodomain.freeyourgadget.gadgetbridge.util.preferences.PreferenceCategoryMultiline;
+
+public class DeviceSpecificSettingsFragment extends AbstractPreferenceFragment implements DeviceSpecificSettingsHandler {
+
+    private static final Logger LOG = LoggerFactory.getLogger(DeviceSpecificSettingsFragment.class);
+
+    static final String FRAGMENT_TAG = "DEVICE_SPECIFIC_SETTINGS_FRAGMENT";
+
+    private DeviceSpecificSettingsCustomizer deviceSpecificSettingsCustomizer;
+
+    private GBDevice device;
+
+    private void setSettingsFileSuffix(String settingsFileSuffix) {
+        Bundle args = new Bundle();
+        args.putString("settingsFileSuffix", settingsFileSuffix);
+        setArguments(args);
+    }
+
+    private void setDeviceSpecificSettings(final DeviceSpecificSettings deviceSpecificSettings) {
+        final Bundle args = getArguments() != null ? getArguments() : new Bundle();
+        args.putParcelable("deviceSpecificSettings", deviceSpecificSettings);
+        setArguments(args);
+    }
+
+    private void setDeviceSpecificSettingsCustomizer(final DeviceSpecificSettingsCustomizer customizer) {
+        final Bundle args = getArguments() != null ? getArguments() : new Bundle();
+        args.putParcelable("deviceSpecificSettingsCustomizer", customizer);
+        setArguments(args);
+    }
+
+    private void setDevice(final GBDevice device) {
+        final Bundle args = getArguments() != null ? getArguments() : new Bundle();
+        args.putParcelable("device", device);
+        setArguments(args);
+    }
+
+    @Override
+    public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
+        Bundle arguments = getArguments();
+        if (arguments == null) {
+            return;
+        }
+        String settingsFileSuffix = arguments.getString("settingsFileSuffix", null);
+        DeviceSpecificSettings deviceSpecificSettings = arguments.getParcelable("deviceSpecificSettings");
+        this.deviceSpecificSettingsCustomizer = arguments.getParcelable("deviceSpecificSettingsCustomizer");
+        this.device = arguments.getParcelable("device");
+
+        if (settingsFileSuffix == null || deviceSpecificSettings == null) {
+            return;
+        }
+
+        getPreferenceManager().setSharedPreferencesName("devicesettings_" + settingsFileSuffix);
+
+        LOG.debug("onCreatePreferences: {}", rootKey);
+
+        if (rootKey == null) {
+            // we are the main preference screen
+            boolean first = true;
+            for (int setting : deviceSpecificSettings.getRootScreens()) {
+                if (first) {
+                    setPreferencesFromResource(setting, null);
+                    first = false;
+                } else {
+                    addPreferencesFromResource(setting);
+                }
+            }
+        } else {
+            // First attempt to find a known screen for this key
+            final List<Integer> screenSettings = deviceSpecificSettings.getScreen(rootKey);
+            if (screenSettings != null) {
+                boolean first = true;
+                for (int setting : screenSettings) {
+                    if (first) {
+                        // Use the root key here to set the root screen, so that the actionbar title gets updated
+                        setPreferencesFromResource(setting, rootKey);
+                        first = false;
+                        addDynamicSettings(rootKey);
+                    } else {
+                        addPreferencesFromResource(setting);
+                    }
+                }
+            } else {
+                // Now, this is ugly: search all the xml files for the rootKey
+                // This means that this device is using the deprecated getSupportedDeviceSpecificSettings,
+                // or that we're on a sub-screen
+                final List<Integer> allScreens = deviceSpecificSettings.getAllScreens();
+                for (int setting : allScreens) {
+                    try {
+                        setPreferencesFromResource(setting, rootKey);
+                    } catch (Exception ignore) {
+                        continue;
+                    }
+                    break;
+                }
+            }
+        }
+
+        // Since all root preference screens are empty, clicking them will not do anything
+        // add on-click listeners
+        for (final DeviceSpecificSettingsScreen value : DeviceSpecificSettingsScreen.values()) {
+            final PreferenceScreen prefScreen = findPreference(value.getKey());
+            if (prefScreen != null) {
+                prefScreen.setOnPreferenceClickListener(p -> {
+                    onNavigateToScreen(prefScreen);
+                    return true;
+                });
+            }
+        }
+
+        setChangeListener(rootKey);
+    }
+
+    private void addDynamicSettings(final String rootKey) {
+        if (rootKey.equals(DeviceSpecificSettingsScreen.BATTERY.getKey())) {
+            addBatterySettings();
+        }
+    }
+
+    private void addBatterySettings() {
+        final DeviceCoordinator coordinator = device.getDeviceCoordinator();
+        final PreferenceScreen batteryScreen = getPreferenceScreen();
+        if (batteryScreen == null) {
+            return;
+        }
+        final BatteryConfig[] batteryConfigs = coordinator.getBatteryConfig(device);
+        for (final BatteryConfig batteryConfig : batteryConfigs) {
+            if (batteryConfigs.length > 1 || coordinator.addBatteryPollingSettings()) {
+                final Preference prefHeader = new PreferenceCategory(requireContext());
+                prefHeader.setKey("pref_battery_header_" + batteryConfig.getBatteryIndex());
+                prefHeader.setIconSpaceReserved(false);
+                if (batteryConfig.getBatteryLabel() != GBDevice.BATTERY_LABEL_DEFAULT) {
+                    prefHeader.setTitle(batteryConfig.getBatteryLabel());
+                } else {
+                    prefHeader.setTitle(requireContext().getString(R.string.battery_i, batteryConfig.getBatteryIndex()));
+                }
+                batteryScreen.addPreference(prefHeader);
+            }
+
+            final SwitchPreferenceCompat showInNotification = new SwitchPreferenceCompat(requireContext());
+            showInNotification.setLayoutResource(R.layout.preference_checkbox);
+            showInNotification.setKey(PREF_BATTERY_SHOW_IN_NOTIFICATION + batteryConfig.getBatteryIndex());
+            showInNotification.setTitle(R.string.show_in_notification);
+            showInNotification.setIconSpaceReserved(false);
+            showInNotification.setDefaultValue(true);
+            batteryScreen.addPreference(showInNotification);
+
+            final SwitchPreferenceCompat notifyLowEnabled = new SwitchPreferenceCompat(requireContext());
+            notifyLowEnabled.setLayoutResource(R.layout.preference_checkbox);
+            notifyLowEnabled.setKey(PREF_BATTERY_NOTIFY_LOW_ENABLED + batteryConfig.getBatteryIndex());
+            notifyLowEnabled.setTitle(R.string.battery_low_notify_enabled);
+            notifyLowEnabled.setDefaultValue(true);
+            notifyLowEnabled.setIconSpaceReserved(false);
+            batteryScreen.addPreference(notifyLowEnabled);
+
+            final EditTextPreference notifyLowThreshold = new EditTextPreference(requireContext());
+            notifyLowThreshold.setKey(PREF_BATTERY_NOTIFY_LOW_THRESHOLD + batteryConfig.getBatteryIndex());
+            notifyLowThreshold.setTitle(R.string.battery_low_threshold);
+            notifyLowThreshold.setDialogTitle(R.string.battery_low_threshold);
+            notifyLowThreshold.setIconSpaceReserved(false);
+            notifyLowThreshold.setOnBindEditTextListener(editText -> {
+                editText.setInputType(InputType.TYPE_CLASS_NUMBER);
+                editText.addTextChangedListener(new MinMaxTextWatcher(editText, 0, 100, true));
+                editText.setSelection(editText.getText().length());
+            });
+            notifyLowThreshold.setSummaryProvider(new GBSimpleSummaryProvider(
+                    requireContext().getString(R.string.default_percentage, batteryConfig.getDefaultLowThreshold()),
+                    R.string.battery_percentage_str
+            ));
+
+            batteryScreen.addPreference(notifyLowThreshold);
+
+            final SwitchPreferenceCompat notifyFullEnabled = new SwitchPreferenceCompat(requireContext());
+            notifyFullEnabled.setLayoutResource(R.layout.preference_checkbox);
+            notifyFullEnabled.setKey(PREF_BATTERY_NOTIFY_FULL_ENABLED + batteryConfig.getBatteryIndex());
+            notifyFullEnabled.setTitle(R.string.battery_full_notify_enabled);
+            notifyFullEnabled.setDefaultValue(true);
+            notifyFullEnabled.setIconSpaceReserved(false);
+            batteryScreen.addPreference(notifyFullEnabled);
+
+            final EditTextPreference notifyFullThreshold = new EditTextPreference(requireContext());
+            notifyFullThreshold.setKey(PREF_BATTERY_NOTIFY_FULL_THRESHOLD + batteryConfig.getBatteryIndex());
+            notifyFullThreshold.setTitle(R.string.battery_full_threshold);
+            notifyFullThreshold.setDialogTitle(R.string.battery_full_threshold);
+            notifyFullThreshold.setIconSpaceReserved(false);
+            notifyFullThreshold.setOnBindEditTextListener(editText -> {
+                editText.setInputType(InputType.TYPE_CLASS_NUMBER);
+                editText.addTextChangedListener(new MinMaxTextWatcher(editText, 0, 100, true));
+                editText.setSelection(editText.getText().length());
+            });
+            notifyFullThreshold.setSummaryProvider(new GBSimpleSummaryProvider(
+                    requireContext().getString(R.string.default_percentage, batteryConfig.getDefaultFullThreshold()),
+                    R.string.battery_percentage_str
+            ));
+            batteryScreen.addPreference(notifyFullThreshold);
+        }
+
+        if (coordinator.addBatteryPollingSettings()) {
+            final Preference prefHeader = new PreferenceCategoryMultiline(requireContext());
+            prefHeader.setKey("pref_battery_polling_header");
+            prefHeader.setIconSpaceReserved(false);
+            prefHeader.setTitle(R.string.pref_battery_polling_configuration);
+            prefHeader.setSummary(R.string.pref_battery_polling_summary);
+            batteryScreen.addPreference(prefHeader);
+
+            final SwitchPreferenceCompat pollingToggle = new SwitchPreferenceCompat(requireContext());
+            pollingToggle.setLayoutResource(R.layout.preference_checkbox);
+            pollingToggle.setKey(PREF_BATTERY_POLLING_ENABLE);
+            pollingToggle.setTitle(R.string.pref_battery_polling_enable);
+            pollingToggle.setDefaultValue(true);
+            pollingToggle.setIconSpaceReserved(false);
+            batteryScreen.addPreference(pollingToggle);
+
+            final EditTextPreference pollingInterval = new EditTextPreference(requireContext());
+            pollingInterval.setKey(PREF_BATTERY_POLLING_INTERVAL);
+            pollingInterval.setTitle(R.string.pref_battery_polling_interval);
+            pollingInterval.setDialogTitle(R.string.pref_battery_polling_interval);
+            pollingInterval.setIconSpaceReserved(false);
+            pollingInterval.setOnBindEditTextListener(editText -> {
+                editText.setInputType(InputType.TYPE_CLASS_NUMBER);
+                // Max is set to 8 days, which should be more than enough
+                editText.addTextChangedListener(new MinMaxTextWatcher(editText, 0, 11520, true));
+                editText.setSelection(editText.getText().length());
+            });
+            pollingInterval.setSummaryProvider(new GBSimpleSummaryProvider(
+                    getString(R.string.interval_fifteen_minutes),
+                    R.string.pref_battery_polling_interval_format
+            ));
+            batteryScreen.addPreference(pollingInterval);
+        }
+    }
+
+    /*
+     * delayed execution so that the preferences are applied first
+     */
+    private void invokeLater(Runnable runnable) {
+        getListView().post(runnable);
+    }
+
+    /*
+     * delayed execution so that the preferences are applied first
+     */
+    @Override
+    public void notifyPreferenceChanged(final String preferenceKey) {
+        invokeLater(new Runnable() {
+            @Override
+            public void run() {
+                GBApplication.deviceService(device).onSendConfiguration(preferenceKey);
+            }
+        });
+    }
+
+    private void setChangeListener(final String rootKey) {
+        final DeviceCoordinator coordinator = device.getDeviceCoordinator();
+
+        final Prefs prefs = new Prefs(getPreferenceManager().getSharedPreferences());
+
+        final ListPreference languageListPreference = findPreference("language");
+        if (languageListPreference != null) {
+            final String[] supportedLanguages = coordinator.getSupportedLanguageSettings(device);
+            CharSequence[] entries = languageListPreference.getEntries();
+            CharSequence[] values = languageListPreference.getEntryValues();
+            for (int i = entries.length - 1; i >= 0; i--) {
+                if (!ArrayUtils.contains(supportedLanguages, values[i])) {
+                    entries = ArrayUtils.remove(entries, i);
+                    values = ArrayUtils.remove(values, i);
+                }
+            }
+            languageListPreference.setEntries(entries);
+            languageListPreference.setEntryValues(values);
+        }
+
+        String disconnectNotificationState = prefs.getString(PREF_DISCONNECT_NOTIFICATION, PREF_DO_NOT_DISTURB_OFF);
+        boolean disconnectNotificationScheduled = disconnectNotificationState.equals(PREF_DO_NOT_DISTURB_SCHEDULED);
+
+        final Preference disconnectNotificationStart = findPreference(PREF_DISCONNECT_NOTIFICATION_START);
+        if (disconnectNotificationStart != null) {
+            disconnectNotificationStart.setEnabled(disconnectNotificationScheduled);
+            disconnectNotificationStart.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
+                @Override
+                public boolean onPreferenceChange(Preference preference, Object newVal) {
+                    notifyPreferenceChanged(PREF_DISCONNECT_NOTIFICATION_START);
+                    return true;
+                }
+            });
+        }
+
+        final Preference disconnectNotificationEnd = findPreference(PREF_DISCONNECT_NOTIFICATION_END);
+        if (disconnectNotificationEnd != null) {
+            disconnectNotificationEnd.setEnabled(disconnectNotificationScheduled);
+            disconnectNotificationEnd.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
+                @Override
+                public boolean onPreferenceChange(Preference preference, Object newVal) {
+                    notifyPreferenceChanged(PREF_DISCONNECT_NOTIFICATION_END);
+                    return true;
+                }
+            });
+        }
+
+        final Preference disconnectNotification = findPreference(PREF_DISCONNECT_NOTIFICATION);
+        if (disconnectNotification != null) {
+            disconnectNotification.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
+                @Override
+                public boolean onPreferenceChange(Preference preference, Object newVal) {
+                    final boolean scheduled = PREF_DO_NOT_DISTURB_SCHEDULED.equals(newVal.toString());
+
+                    Objects.requireNonNull(disconnectNotificationStart).setEnabled(scheduled);
+                    Objects.requireNonNull(disconnectNotificationEnd).setEnabled(scheduled);
+                    notifyPreferenceChanged(PREF_DISCONNECT_NOTIFICATION);
+                    return true;
+                }
+            });
+
+        }
+
+        String nightModeState = prefs.getString(MiBandConst.PREF_NIGHT_MODE, PREF_NIGHT_MODE_OFF);
+        boolean nightModeScheduled = nightModeState.equals(PREF_NIGHT_MODE_SCHEDULED);
+
+        final Preference nightModeStart = findPreference(PREF_NIGHT_MODE_START);
+        if (nightModeStart != null) {
+            nightModeStart.setEnabled(nightModeScheduled);
+            nightModeStart.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
+                @Override
+                public boolean onPreferenceChange(Preference preference, Object newVal) {
+                    notifyPreferenceChanged(PREF_NIGHT_MODE_START);
+                    return true;
+                }
+            });
+        }
+
+        final Preference nightModeEnd = findPreference(PREF_NIGHT_MODE_END);
+        if (nightModeEnd != null) {
+            nightModeEnd.setEnabled(nightModeScheduled);
+            nightModeEnd.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
+                @Override
+                public boolean onPreferenceChange(Preference preference, Object newVal) {
+                    notifyPreferenceChanged(PREF_NIGHT_MODE_END);
+                    return true;
+                }
+            });
+        }
+
+        final Preference nightMode = findPreference(PREF_NIGHT_MODE);
+        if (nightMode != null) {
+
+            nightMode.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
+                @Override
+                public boolean onPreferenceChange(Preference preference, Object newVal) {
+                    final boolean scheduled = PREF_NIGHT_MODE_SCHEDULED.equals(newVal.toString());
+
+                    Objects.requireNonNull(nightModeStart).setEnabled(scheduled);
+                    Objects.requireNonNull(nightModeEnd).setEnabled(scheduled);
+
+                    notifyPreferenceChanged(PREF_NIGHT_MODE);
+                    return true;
+                }
+            });
+        }
+
+
+        String doNotDisturbState = prefs.getString(PREF_DO_NOT_DISTURB, PREF_DO_NOT_DISTURB_OFF);
+        boolean doNotDisturbScheduled = doNotDisturbState.equals(PREF_DO_NOT_DISTURB_SCHEDULED);
+
+        final Preference doNotDisturbStart = findPreference(PREF_DO_NOT_DISTURB_START);
+        if (doNotDisturbStart != null) {
+            doNotDisturbStart.setEnabled(doNotDisturbScheduled);
+            doNotDisturbStart.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
+                @Override
+                public boolean onPreferenceChange(Preference preference, Object newVal) {
+                    notifyPreferenceChanged(PREF_DO_NOT_DISTURB_START);
+                    return true;
+                }
+            });
+        }
+
+        final Preference doNotDisturbEnd = findPreference(PREF_DO_NOT_DISTURB_END);
+        if (doNotDisturbEnd != null) {
+            doNotDisturbEnd.setEnabled(doNotDisturbScheduled);
+            doNotDisturbEnd.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
+                @Override
+                public boolean onPreferenceChange(Preference preference, Object newVal) {
+                    notifyPreferenceChanged(PREF_DO_NOT_DISTURB_END);
+                    return true;
+                }
+            });
+        }
+
+        final Preference doNotDisturb = findPreference(PREF_DO_NOT_DISTURB);
+        if (doNotDisturb != null) {
+            doNotDisturb.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
+                @Override
+                public boolean onPreferenceChange(Preference preference, Object newVal) {
+                    final boolean scheduled = PREF_DO_NOT_DISTURB_SCHEDULED.equals(newVal.toString());
+
+                    Objects.requireNonNull(doNotDisturbStart).setEnabled(scheduled);
+                    Objects.requireNonNull(doNotDisturbEnd).setEnabled(scheduled);
+
+                    notifyPreferenceChanged(PREF_DO_NOT_DISTURB);
+                    return true;
+                }
+            });
+        }
+
+        addPreferenceHandlerFor(PREF_SEND_APP_NOTIFICATIONS);
+        addPreferenceHandlerFor(PREF_SWIPE_UNLOCK);
+        addPreferenceHandlerFor(PREF_MI2_DATEFORMAT);
+        addPreferenceHandlerFor(PREF_DATEFORMAT);
+        addPreferenceHandlerFor(PREF_DISPLAY_ITEMS);
+        addPreferenceHandlerFor(PREF_DISPLAY_ITEMS_SORTABLE);
+        addPreferenceHandlerFor(PREF_WORKOUT_ACTIVITY_TYPES_SORTABLE);
+        addPreferenceHandlerFor(PREF_SHORTCUTS);
+        addPreferenceHandlerFor(PREF_SHORTCUTS_SORTABLE);
+        addPreferenceHandlerFor(PREF_CONTROL_CENTER_SORTABLE);
+        addPreferenceHandlerFor(PREF_LANGUAGE);
+        addPreferenceHandlerFor(PREF_EXPOSE_HR_THIRDPARTY);
+        addPreferenceHandlerFor(PREF_BT_CONNECTED_ADVERTISEMENT);
+        addPreferenceHandlerFor(PREF_WEARLOCATION);
+        addPreferenceHandlerFor(PREF_VIBRATION_ENABLE);
+        addPreferenceHandlerFor(PREF_NOTIFICATION_ENABLE);
+        addPreferenceHandlerFor(PREF_DEVICE_NAME);
+        addPreferenceHandlerFor(PREF_SCREEN_BRIGHTNESS);
+        addPreferenceHandlerFor(PREF_SCREEN_AUTO_BRIGHTNESS);
+        addPreferenceHandlerFor(PREF_SCREEN_ORIENTATION);
+        addPreferenceHandlerFor(PREF_SCREEN_TIMEOUT);
+        addPreferenceHandlerFor(PREF_TIMEFORMAT);
+        addPreferenceHandlerFor(PREF_UPPER_BUTTON_LONG_PRESS);
+        addPreferenceHandlerFor(PREF_LOWER_BUTTON_SHORT_PRESS);
+        addPreferenceHandlerFor(PREF_BUTTON_1_FUNCTION_SHORT);
+        addPreferenceHandlerFor(PREF_BUTTON_2_FUNCTION_SHORT);
+        addPreferenceHandlerFor(PREF_BUTTON_3_FUNCTION_SHORT);
+        addPreferenceHandlerFor(PREF_BUTTON_1_FUNCTION_LONG);
+        addPreferenceHandlerFor(PREF_BUTTON_2_FUNCTION_LONG);
+        addPreferenceHandlerFor(PREF_BUTTON_3_FUNCTION_LONG);
+        addPreferenceHandlerFor(PREF_BUTTON_1_FUNCTION_DOUBLE);
+        addPreferenceHandlerFor(PREF_BUTTON_2_FUNCTION_DOUBLE);
+        addPreferenceHandlerFor(PREF_BUTTON_3_FUNCTION_DOUBLE);
+        addPreferenceHandlerFor(PREF_VIBRATION_STRENGH_PERCENTAGE);
+        addPreferenceHandlerFor(PREF_POWER_MODE);
+        addPreferenceHandlerFor(PREF_CONNECTION_DURATION);
+        addPreferenceHandlerFor(PREF_LIFTWRIST_NOSHED);
+        addPreferenceHandlerFor(PREF_DISCONNECTNOTIF_NOSHED);
+        addPreferenceHandlerFor(PREF_BUTTON_BP_CALIBRATE);
+        addPreferenceHandlerFor(PREF_ALTITUDE_CALIBRATE);
+        addPreferenceHandlerFor(PREF_INACTIVITY_ENABLE);
+        addPreferenceHandlerFor(PREF_INACTIVITY_START);
+        addPreferenceHandlerFor(PREF_INACTIVITY_END);
+        addPreferenceHandlerFor(PREF_INACTIVITY_THRESHOLD);
+        addPreferenceHandlerFor(PREF_INACTIVITY_THRESHOLD_EXTENDED);
+        addPreferenceHandlerFor(PREF_INACTIVITY_MO);
+        addPreferenceHandlerFor(PREF_INACTIVITY_TU);
+        addPreferenceHandlerFor(PREF_INACTIVITY_WE);
+        addPreferenceHandlerFor(PREF_INACTIVITY_TH);
+        addPreferenceHandlerFor(PREF_INACTIVITY_FR);
+        addPreferenceHandlerFor(PREF_INACTIVITY_SA);
+        addPreferenceHandlerFor(PREF_INACTIVITY_SU);
+        addPreferenceHandlerFor(PREF_INACTIVITY_DND);
+        addPreferenceHandlerFor(PREF_INACTIVITY_DND_START);
+        addPreferenceHandlerFor(PREF_INACTIVITY_DND_END);
+        addPreferenceHandlerFor(PREF_AUTOHEARTRATE_SWITCH);
+        addPreferenceHandlerFor(PREF_AUTOHEARTRATE_SLEEP);
+        addPreferenceHandlerFor(PREF_AUTOHEARTRATE_INTERVAL);
+        addPreferenceHandlerFor(PREF_AUTOHEARTRATE_START);
+        addPreferenceHandlerFor(PREF_AUTOHEARTRATE_END);
+        addPreferenceHandlerFor(PREF_HEARTRATE_ACTIVITY_MONITORING);
+        addPreferenceHandlerFor(PREF_HEARTRATE_ALERT_ENABLED);
+        addPreferenceHandlerFor(PREF_HEARTRATE_STRESS_MONITORING);
+        addPreferenceHandlerFor(PREF_HEARTRATE_STRESS_RELAXATION_REMINDER);
+        addPreferenceHandlerFor(PREF_HEARTRATE_SLEEP_BREATHING_QUALITY_MONITORING);
+        addPreferenceHandlerFor(PREF_SPO2_ALL_DAY_MONITORING);
+        addPreferenceHandlerFor(PREF_SPO2_LOW_ALERT_THRESHOLD);
+        addPreferenceHandlerFor(PREF_HRV_ALL_DAY_MONITORING);
+        addPreferenceHandlerFor(PREF_DO_NOT_DISTURB_NOAUTO);
+        addPreferenceHandlerFor(PREF_DO_NOT_DISTURB_NOAUTO_START);
+        addPreferenceHandlerFor(PREF_DO_NOT_DISTURB_NOAUTO_END);
+        addPreferenceHandlerFor(PREF_DO_NOT_DISTURB);
+        addPreferenceHandlerFor(PREF_DO_NOT_DISTURB_START);
+        addPreferenceHandlerFor(PREF_DO_NOT_DISTURB_END);
+        addPreferenceHandlerFor(PREF_DO_NOT_DISTURB_MO);
+        addPreferenceHandlerFor(PREF_DO_NOT_DISTURB_TU);
+        addPreferenceHandlerFor(PREF_DO_NOT_DISTURB_WE);
+        addPreferenceHandlerFor(PREF_DO_NOT_DISTURB_TH);
+        addPreferenceHandlerFor(PREF_DO_NOT_DISTURB_FR);
+        addPreferenceHandlerFor(PREF_DO_NOT_DISTURB_SA);
+        addPreferenceHandlerFor(PREF_DO_NOT_DISTURB_SU);
+        addPreferenceHandlerFor(PREF_DO_NOT_DISTURB_LIFT_WRIST);
+        addPreferenceHandlerFor(PREF_DO_NOT_DISTURB_NOT_WEAR);
+        addPreferenceHandlerFor(PREF_FIND_PHONE);
+        addPreferenceHandlerFor(PREF_FIND_PHONE_DURATION);
+        addPreferenceHandlerFor(PREF_AUTOLIGHT);
+        addPreferenceHandlerFor(PREF_LIGHT_DURATION_LONGER);
+        addPreferenceHandlerFor(PREF_AUTOREMOVE_MESSAGE);
+        addPreferenceHandlerFor(PREF_AUTOREMOVE_NOTIFICATIONS);
+        addPreferenceHandlerFor(PREF_PREVIEW_MESSAGE_IN_TITLE);
+        addPreferenceHandlerFor(PREF_CASIO_ALERT_EMAIL);
+        addPreferenceHandlerFor(PREF_CASIO_ALERT_SMS);
+        addPreferenceHandlerFor(PREF_CASIO_ALERT_CALL);
+        addPreferenceHandlerFor(PREF_CASIO_ALERT_CALENDAR);
+        addPreferenceHandlerFor(PREF_CASIO_ALERT_OTHER);
+        addPreferenceHandlerFor(PREF_SCREEN_ON_ON_NOTIFICATIONS);
+        addPreferenceHandlerFor(PREF_WORKOUT_KEEP_SCREEN_ON);
+        addPreferenceHandlerFor(PREF_KEY_VIBRATION);
+        addPreferenceHandlerFor(PREF_OPERATING_SOUNDS);
+        addPreferenceHandlerFor(PREF_FAKE_RING_DURATION);
+        addPreferenceHandlerFor(PREF_ANTILOST_ENABLED);
+        addPreferenceHandlerFor(PREF_HYDRATION_SWITCH);
+        addPreferenceHandlerFor(PREF_HYDRATION_PERIOD);
+        addPreferenceHandlerFor(PREF_HYDRATION_DND);
+        addPreferenceHandlerFor(PREF_HYDRATION_DND_START);
+        addPreferenceHandlerFor(PREF_HYDRATION_DND_END);
+        addPreferenceHandlerFor(PREF_AMPM_ENABLED);
+        addPreferenceHandlerFor(PREF_SOUNDS);
+        addPreferenceHandlerFor(PREF_CAMERA_REMOTE);
+        addPreferenceHandlerFor(PREF_SCREEN_LIFT_WRIST);
+        addPreferenceHandlerFor(PREF_SYNC_CALENDAR);
+        addPreferenceHandlerFor(PREF_CALENDAR_LOOKAHEAD_DAYS);
+
+        addPreferenceHandlerFor(PREF_BATTERY_POLLING_ENABLE);
+        addPreferenceHandlerFor(PREF_BATTERY_POLLING_INTERVAL);
+        addPreferenceHandlerFor(PREF_TIME_SYNC);
+
+        addPreferenceHandlerFor(PREF_BLUETOOTH_CALLS_ENABLED);
+        addPreferenceHandlerFor(PREF_DISPLAY_CALLER);
+        addPreferenceHandlerFor(PREF_NOTIFICATION_DELAY_CALLS);
+        addPreferenceHandlerFor(PREF_CALL_REJECT_METHOD);
+        addPreferenceHandlerFor(PREF_AUTO_REPLY_INCOMING_CALL);
+
+        addPreferenceHandlerFor(PREF_SLEEP_MODE_SLEEP_SCREEN);
+        addPreferenceHandlerFor(PREF_SLEEP_MODE_SMART_ENABLE);
+
+        addPreferenceHandlerFor(PREF_ACTIVE_NOISE_CANCELLING_TOGGLE);
+        addPreferenceHandlerFor(PREF_WEAR_SENSOR_TOGGLE);
+        addPreferenceHandlerFor(PREF_BANDW_PSERIES_GUI_VPT_LEVEL);
+
+        addPreferenceHandlerFor(PREF_HYBRID_HR_DRAW_WIDGET_CIRCLES);
+        addPreferenceHandlerFor(PREF_HYBRID_HR_FORCE_WHITE_COLOR);
+        addPreferenceHandlerFor(PREF_HYBRID_HR_SAVE_RAW_ACTIVITY_FILES);
+        addPreferenceHandlerFor(PREF_HYBRID_HR_DANGEROUS_EXTERNAL_INTENTS);
+        addPreferenceHandlerFor(PREF_HYBRID_HR_ACTIVITY_RECOGNITION_RUNNING_ENABLED);
+        addPreferenceHandlerFor(PREF_HYBRID_HR_ACTIVITY_RECOGNITION_RUNNING_ASK_FIRST);
+        addPreferenceHandlerFor(PREF_HYBRID_HR_ACTIVITY_RECOGNITION_RUNNING_MINUTES);
+        addPreferenceHandlerFor(PREF_HYBRID_HR_ACTIVITY_RECOGNITION_BIKING_ENABLED);
+        addPreferenceHandlerFor(PREF_HYBRID_HR_ACTIVITY_RECOGNITION_BIKING_ASK_FIRST);
+        addPreferenceHandlerFor(PREF_HYBRID_HR_ACTIVITY_RECOGNITION_BIKING_MINUTES);
+        addPreferenceHandlerFor(PREF_HYBRID_HR_ACTIVITY_RECOGNITION_WALKING_ENABLED);
+        addPreferenceHandlerFor(PREF_HYBRID_HR_ACTIVITY_RECOGNITION_WALKING_ASK_FIRST);
+        addPreferenceHandlerFor(PREF_HYBRID_HR_ACTIVITY_RECOGNITION_WALKING_MINUTES);
+        addPreferenceHandlerFor(PREF_HYBRID_HR_ACTIVITY_RECOGNITION_ROWING_ENABLED);
+        addPreferenceHandlerFor(PREF_HYBRID_HR_ACTIVITY_RECOGNITION_ROWING_ASK_FIRST);
+        addPreferenceHandlerFor(PREF_HYBRID_HR_ACTIVITY_RECOGNITION_ROWING_MINUTES);
+
+        addPreferenceHandlerFor(PREF_SONYSWR12_STAMINA);
+        addPreferenceHandlerFor(PREF_SONYSWR12_LOW_VIBRATION);
+        addPreferenceHandlerFor(PREF_SONYSWR12_SMART_INTERVAL);
+
+        addPreferenceHandlerFor(PREF_NOTHING_EAR1_INEAR);
+        addPreferenceHandlerFor(PREF_NOTHING_EAR1_AUDIOMODE);
+
+        addPreferenceHandlerFor(PREF_HUAWEI_FREEBUDS_INEAR);
+        addPreferenceHandlerFor(PREF_HUAWEI_FREEBUDS_AUDIOMODE);
+
+        addPreferenceHandlerFor(PREF_GALAXY_BUDS_AMBIENT_VOICE_FOCUS);
+        addPreferenceHandlerFor(PREF_GALAXY_BUDS_AMBIENT_VOLUME);
+        addPreferenceHandlerFor(PREF_GALAXY_BUDS_LOCK_TOUCH);
+        addPreferenceHandlerFor(PREF_GALAXY_BUDS_GAME_MODE);
+        addPreferenceHandlerFor(PREF_GALAXY_BUDS_EQUALIZER);
+        addPreferenceHandlerFor(PREF_GALAXY_BUDS_EQUALIZER_DOLBY);
+        addPreferenceHandlerFor(PREF_GALAXY_BUDS_EQUALIZER_MODE);
+        addPreferenceHandlerFor(PREF_GALAXY_BUDS_TOUCH_LEFT);
+        addPreferenceHandlerFor(PREF_GALAXY_BUDS_TOUCH_RIGHT);
+        addPreferenceHandlerFor(PREF_GALAXY_BUDS_LIVE_ANC);
+        addPreferenceHandlerFor(PREF_GALAXY_BUDS_PRESSURE_RELIEF);
+        addPreferenceHandlerFor(PREF_GALAXY_BUDS_PRO_ANC_LEVEL);
+        addPreferenceHandlerFor(PREF_GALAXY_BUDS_AMBIENT_SOUND);
+        addPreferenceHandlerFor(PREF_GALAXY_PRO_DOUBLE_TAP_EDGE);
+        addPreferenceHandlerFor(PREF_GALAXY_BUDS_PRO_IN_EAR_DETECTION);
+        addPreferenceHandlerFor(PREF_GALAXY_BUDS_PRO_VOICE_DETECT);
+        addPreferenceHandlerFor(PREF_GALAXY_BUDS_PRO_VOICE_DETECT_DURATION);
+        addPreferenceHandlerFor(PREF_GALAXY_BUDS_PRO_BALANCE);
+        addPreferenceHandlerFor(PREF_GALAXY_BUDS_PRO_READ_NOTIFICATIONS_OUTLOUD);
+        addPreferenceHandlerFor(PREF_GALAXY_BUDS_AMBIENT_MODE_DURING_CALL);
+        addPreferenceHandlerFor(PREF_GALAXY_BUDS_PRO_AMBIENT_VOLUME_RIGHT);
+        addPreferenceHandlerFor(PREF_GALAXY_BUDS_PRO_AMBIENT_VOLUME_LEFT);
+        addPreferenceHandlerFor(PREF_GALAXY_BUDS_PRO_AMBIENT_SOUND_TONE);
+        addPreferenceHandlerFor(PREFS_NOISE_CONTROL_WITH_ONE_EARBUD);
+        addPreferenceHandlerFor(PREF_GALAXY_BUDS_AMBIENT_MODE);
+        addPreferenceHandlerFor(PREFS_GALAXY_BUDS_SEAMLESS_CONNECTION);
+        addPreferenceHandlerFor(PREF_GALAXY_BUDS_TOUCH_LEFT_SWITCH);
+        addPreferenceHandlerFor(PREF_GALAXY_BUDS_TOUCH_RIGHT_SWITCH);
+
+        addPreferenceHandlerFor(MORNING_UPDATES_ENABLED);
+        addPreferenceHandlerFor(MORNING_UPDATES_CATEGORIES_SORTABLE);
+
+        addPreferenceHandlerFor(SHORTCUT_CARDS_SORTABLE);
+
+        addPreferenceHandlerFor(PREF_WATCHFACE);
+
+        addPreferenceHandlerFor(PREF_REDMI_BUDS_5_PRO_AMBIENT_SOUND_CONTROL);
+        addPreferenceHandlerFor(PREF_REDMI_BUDS_5_PRO_NOISE_CANCELLING_STRENGTH);
+        addPreferenceHandlerFor(PREF_REDMI_BUDS_5_PRO_TRANSPARENCY_STRENGTH);
+        addPreferenceHandlerFor(PREF_REDMI_BUDS_5_PRO_ADAPTIVE_NOISE_CANCELLING);
+//        addPreferenceHandlerFor(PREF_REDMI_BUDS_5_PRO_PERSONALIZED_NOISE_CANCELLING);
+        addPreferenceHandlerFor(PREF_REDMI_BUDS_5_PRO_CONTROL_SINGLE_TAP_LEFT);
+        addPreferenceHandlerFor(PREF_REDMI_BUDS_5_PRO_CONTROL_SINGLE_TAP_RIGHT);
+        addPreferenceHandlerFor(PREF_REDMI_BUDS_5_PRO_CONTROL_DOUBLE_TAP_LEFT);
+        addPreferenceHandlerFor(PREF_REDMI_BUDS_5_PRO_CONTROL_DOUBLE_TAP_RIGHT);
+        addPreferenceHandlerFor(PREF_REDMI_BUDS_5_PRO_CONTROL_TRIPLE_TAP_LEFT);
+        addPreferenceHandlerFor(PREF_REDMI_BUDS_5_PRO_CONTROL_TRIPLE_TAP_RIGHT);
+        addPreferenceHandlerFor(PREF_REDMI_BUDS_5_PRO_CONTROL_LONG_TAP_MODE_LEFT);
+        addPreferenceHandlerFor(PREF_REDMI_BUDS_5_PRO_CONTROL_LONG_TAP_MODE_RIGHT);
+        addPreferenceHandlerFor(PREF_REDMI_BUDS_5_PRO_CONTROL_LONG_TAP_SETTINGS_LEFT);
+        addPreferenceHandlerFor(PREF_REDMI_BUDS_5_PRO_CONTROL_LONG_TAP_SETTINGS_RIGHT);
+        addPreferenceHandlerFor(PREF_REDMI_BUDS_5_PRO_WEARING_DETECTION);
+        addPreferenceHandlerFor(PREF_REDMI_BUDS_5_PRO_AUTO_REPLY_PHONECALL);
+        addPreferenceHandlerFor(PREF_REDMI_BUDS_5_PRO_DOUBLE_CONNECTION);
+//        addPreferenceHandlerFor(PREF_REDMI_BUDS_5_PRO_SURROUND_SOUND);
+        addPreferenceHandlerFor(PREF_REDMI_BUDS_5_PRO_ADAPTIVE_SOUND);
+//        addPreferenceHandlerFor(PREF_REDMI_BUDS_5_PRO_SURROUND_SOUND_MODE);
+        addPreferenceHandlerFor(PREF_REDMI_BUDS_5_PRO_EQUALIZER_PRESET);
+        addPreferenceHandlerFor(PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_62);
+        addPreferenceHandlerFor(PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_125);
+        addPreferenceHandlerFor(PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_250);
+        addPreferenceHandlerFor(PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_500);
+        addPreferenceHandlerFor(PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_1k);
+        addPreferenceHandlerFor(PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_2k);
+        addPreferenceHandlerFor(PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_4k);
+        addPreferenceHandlerFor(PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_8k);
+        addPreferenceHandlerFor(PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_12k);
+        addPreferenceHandlerFor(PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_16k);
+
+        addPreferenceHandlerFor(PREF_SONY_AMBIENT_SOUND_CONTROL);
+        addPreferenceHandlerFor(PREF_SONY_AMBIENT_SOUND_CONTROL_BUTTON_MODE);
+        addPreferenceHandlerFor(PREF_SONY_FOCUS_VOICE);
+        addPreferenceHandlerFor(PREF_SONY_AMBIENT_SOUND_LEVEL);
+        addPreferenceHandlerFor(PREF_SONY_SOUND_POSITION);
+        addPreferenceHandlerFor(PREF_SONY_SURROUND_MODE);
+        addPreferenceHandlerFor(PREF_SONY_EQUALIZER_MODE);
+        addPreferenceHandlerFor(PREF_SONY_EQUALIZER_BAND_400);
+        addPreferenceHandlerFor(PREF_SONY_EQUALIZER_BAND_1000);
+        addPreferenceHandlerFor(PREF_SONY_EQUALIZER_BAND_2500);
+        addPreferenceHandlerFor(PREF_SONY_EQUALIZER_BAND_6300);
+        addPreferenceHandlerFor(PREF_SONY_EQUALIZER_BAND_16000);
+        addPreferenceHandlerFor(PREF_SONY_EQUALIZER_BASS);
+        addPreferenceHandlerFor(PREF_SONY_AUDIO_UPSAMPLING);
+        addPreferenceHandlerFor(PREF_SONY_TOUCH_SENSOR);
+        addPreferenceHandlerFor(PREF_SONY_PAUSE_WHEN_TAKEN_OFF);
+        addPreferenceHandlerFor(PREF_SONY_BUTTON_MODE_LEFT);
+        addPreferenceHandlerFor(PREF_SONY_BUTTON_MODE_RIGHT);
+        addPreferenceHandlerFor(PREF_SONY_QUICK_ACCESS_DOUBLE_TAP);
+        addPreferenceHandlerFor(PREF_SONY_QUICK_ACCESS_TRIPLE_TAP);
+        addPreferenceHandlerFor(PREF_SONY_AUTOMATIC_POWER_OFF);
+        addPreferenceHandlerFor(PREF_SONY_NOTIFICATION_VOICE_GUIDE);
+        addPreferenceHandlerFor(PREF_SONY_SPEAK_TO_CHAT);
+        addPreferenceHandlerFor(PREF_SONY_SPEAK_TO_CHAT_SENSITIVITY);
+        addPreferenceHandlerFor(PREF_SONY_SPEAK_TO_CHAT_FOCUS_ON_VOICE);
+        addPreferenceHandlerFor(PREF_SONY_SPEAK_TO_CHAT_TIMEOUT);
+        addPreferenceHandlerFor(PREF_SONY_CONNECT_TWO_DEVICES);
+        addPreferenceHandlerFor(PREF_SONY_ADAPTIVE_VOLUME_CONTROL);
+        addPreferenceHandlerFor(PREF_SONY_WIDE_AREA_TAP);
+
+        addPreferenceHandlerFor(PREF_SOUNDCORE_AMBIENT_SOUND_CONTROL);
+        addPreferenceHandlerFor(PREF_SOUNDCORE_WIND_NOISE_REDUCTION);
+        addPreferenceHandlerFor(PREF_SOUNDCORE_TRANSPARENCY_VOCAL_MODE);
+        addPreferenceHandlerFor(PREF_SOUNDCORE_ADAPTIVE_NOISE_CANCELLING);
+        addPreferenceHandlerFor(PREF_SOUNDCORE_TOUCH_TONE);
+        addPreferenceHandlerFor(PREF_SOUNDCORE_WEARING_TONE);
+        addPreferenceHandlerFor(PREF_SOUNDCORE_WEARING_DETECTION);
+        addPreferenceHandlerFor(PREF_SOUNDCORE_CONTROL_SINGLE_TAP_DISABLED);
+        addPreferenceHandlerFor(PREF_SOUNDCORE_CONTROL_DOUBLE_TAP_DISABLED);
+        addPreferenceHandlerFor(PREF_SOUNDCORE_CONTROL_TRIPLE_TAP_DISABLED);
+        addPreferenceHandlerFor(PREF_SOUNDCORE_CONTROL_LONG_PRESS_DISABLED);
+        addPreferenceHandlerFor(PREF_SOUNDCORE_CONTROL_SINGLE_TAP_ACTION_LEFT);
+        addPreferenceHandlerFor(PREF_SOUNDCORE_CONTROL_SINGLE_TAP_ACTION_RIGHT);
+        addPreferenceHandlerFor(PREF_SOUNDCORE_CONTROL_DOUBLE_TAP_ACTION_LEFT);
+        addPreferenceHandlerFor(PREF_SOUNDCORE_CONTROL_DOUBLE_TAP_ACTION_RIGHT);
+        addPreferenceHandlerFor(PREF_SOUNDCORE_CONTROL_TRIPLE_TAP_ACTION_LEFT);
+        addPreferenceHandlerFor(PREF_SOUNDCORE_CONTROL_TRIPLE_TAP_ACTION_RIGHT);
+        addPreferenceHandlerFor(PREF_SOUNDCORE_CONTROL_LONG_PRESS_ACTION_LEFT);
+        addPreferenceHandlerFor(PREF_SOUNDCORE_CONTROL_LONG_PRESS_ACTION_RIGHT);
+        addPreferenceHandlerFor(PREF_SOUNDCORE_VOICE_PROMPTS);
+        addPreferenceHandlerFor(PREF_SOUNDCORE_BUTTON_BRIGHTNESS);
+        addPreferenceHandlerFor(PREF_SOUNDCORE_AUTO_POWER_OFF);
+        addPreferenceHandlerFor(PREF_SOUNDCORE_LDAC_MODE);
+        addPreferenceHandlerFor(PREF_SOUNDCORE_ADAPTIVE_DIRECTION);
+        addPreferenceHandlerFor(PREF_SOUNDCORE_EQUALIZER_PRESET);
+        addPreferenceHandlerFor(PREF_SOUNDCORE_EQUALIZER_DIRECTION);
+        addPreferenceHandlerFor(PREF_SOUNDCORE_EQUALIZER_BAND1_FREQ);
+        addPreferenceHandlerFor(PREF_SOUNDCORE_EQUALIZER_BAND1_VALUE);
+        addPreferenceHandlerFor(PREF_SOUNDCORE_EQUALIZER_BAND2_FREQ);
+        addPreferenceHandlerFor(PREF_SOUNDCORE_EQUALIZER_BAND2_VALUE);
+        addPreferenceHandlerFor(PREF_SOUNDCORE_EQUALIZER_BAND3_FREQ);
+        addPreferenceHandlerFor(PREF_SOUNDCORE_EQUALIZER_BAND3_VALUE);
+        addPreferenceHandlerFor(PREF_SOUNDCORE_EQUALIZER_BAND4_FREQ);
+        addPreferenceHandlerFor(PREF_SOUNDCORE_EQUALIZER_BAND4_VALUE);
+        addPreferenceHandlerFor(PREF_SOUNDCORE_EQUALIZER_BAND5_FREQ);
+        addPreferenceHandlerFor(PREF_SOUNDCORE_EQUALIZER_BAND5_VALUE);
+        addPreferenceHandlerFor(PREF_SOUNDCORE_EQUALIZER_BAND6_FREQ);
+        addPreferenceHandlerFor(PREF_SOUNDCORE_EQUALIZER_BAND6_VALUE);
+        addPreferenceHandlerFor(PREF_SOUNDCORE_EQUALIZER_BAND7_FREQ);
+        addPreferenceHandlerFor(PREF_SOUNDCORE_EQUALIZER_BAND7_VALUE);
+        addPreferenceHandlerFor(PREF_SOUNDCORE_EQUALIZER_BAND8_FREQ);
+        addPreferenceHandlerFor(PREF_SOUNDCORE_EQUALIZER_BAND8_VALUE);
+        addPreferenceHandlerFor(PREF_SOUNDCORE_EQUALIZER_BAND9_FREQ);
+        addPreferenceHandlerFor(PREF_SOUNDCORE_EQUALIZER_BAND9_VALUE);
+
+        addPreferenceHandlerFor(PREF_MOONDROP_EQUALIZER_PRESET);
+        addPreferenceHandlerFor(PREF_MOONDROP_TOUCH_PLAY_PAUSE_EARBUD);
+        addPreferenceHandlerFor(PREF_MOONDROP_TOUCH_PLAY_PAUSE_TRIGGER);
+        addPreferenceHandlerFor(PREF_MOONDROP_TOUCH_MEDIA_PREV_EARBUD);
+        addPreferenceHandlerFor(PREF_MOONDROP_TOUCH_MEDIA_PREV_TRIGGER);
+        addPreferenceHandlerFor(PREF_MOONDROP_TOUCH_MEDIA_NEXT_EARBUD);
+        addPreferenceHandlerFor(PREF_MOONDROP_TOUCH_MEDIA_NEXT_TRIGGER);
+        addPreferenceHandlerFor(PREF_MOONDROP_TOUCH_CALL_PICK_HANG_EARBUD);
+        addPreferenceHandlerFor(PREF_MOONDROP_TOUCH_CALL_PICK_HANG_TRIGGER);
+        addPreferenceHandlerFor(PREF_MOONDROP_TOUCH_CALL_START_EARBUD);
+        addPreferenceHandlerFor(PREF_MOONDROP_TOUCH_CALL_START_TRIGGER);
+        addPreferenceHandlerFor(PREF_MOONDROP_TOUCH_ASSISTANT_EARBUD);
+        addPreferenceHandlerFor(PREF_MOONDROP_TOUCH_ASSISTANT_TRIGGER);
+        addPreferenceHandlerFor(PREF_MOONDROP_TOUCH_ANC_MODE_EARBUD);
+        addPreferenceHandlerFor(PREF_MOONDROP_TOUCH_ANC_MODE_TRIGGER);
+
+        addPreferenceHandlerFor(PREF_MISCALE_WEIGHT_UNIT);
+        addPreferenceHandlerFor(PREF_MISCALE_SMALL_OBJECTS);
+
+        addPreferenceHandlerFor(PREF_MIJIA_LYWSD_COMFORT_TEMPERATURE_LOWER);
+        addPreferenceHandlerFor(PREF_MIJIA_LYWSD_COMFORT_TEMPERATURE_UPPER);
+        addPreferenceHandlerFor(PREF_MIJIA_LYWSD_COMFORT_HUMIDITY_LOWER);
+        addPreferenceHandlerFor(PREF_MIJIA_LYWSD_COMFORT_HUMIDITY_UPPER);
+
+        addPreferenceHandlerFor(PREF_FEMOMETER_MEASUREMENT_MODE);
+
+        addPreferenceHandlerFor(PREF_QC35_NOISE_CANCELLING_LEVEL);
+        addPreferenceHandlerFor(PREF_USER_FITNESS_GOAL);
+        addPreferenceHandlerFor(PREF_USER_FITNESS_GOAL_NOTIFICATION);
+        addPreferenceHandlerFor(PREF_USER_FITNESS_GOAL_SECONDARY);
+
+        addPreferenceHandlerFor(PREF_VITALITY_SCORE_7_DAY);
+        addPreferenceHandlerFor(PREF_VITALITY_SCORE_DAILY);
+
+        addPreferenceHandlerFor(PREF_UM25_SHOW_THRESHOLD_NOTIFICATION);
+        addPreferenceHandlerFor(PREF_UM25_SHOW_THRESHOLD);
+        addPreferenceHandlerFor(PREF_HOURLY_CHIME_ENABLE);
+        addPreferenceHandlerFor(PREF_HOURLY_CHIME_START);
+        addPreferenceHandlerFor(PREF_HOURLY_CHIME_END);
+
+        addPreferenceHandlerFor(PREF_WORKOUT_DETECTION_CATEGORIES);
+        addPreferenceHandlerFor(PREF_WORKOUT_DETECTION_ALERT);
+        addPreferenceHandlerFor(PREF_WORKOUT_DETECTION_SENSITIVITY);
+
+        addPreferenceHandlerFor(PREF_GPS_MODE_PRESET);
+        addPreferenceHandlerFor(PREF_GPS_BAND);
+        addPreferenceHandlerFor(PREF_GPS_COMBINATION);
+        addPreferenceHandlerFor(PREF_GPS_SATELLITE_SEARCH);
+        addPreferenceHandlerFor(PREF_AGPS_EXPIRY_REMINDER_ENABLED);
+        addPreferenceHandlerFor(PREF_AGPS_EXPIRY_REMINDER_TIME);
+        addPreferenceHandlerFor(PREF_ALWAYS_ON_DISPLAY_FOLLOW_WATCHFACE);
+        addPreferenceHandlerFor(PREF_ALWAYS_ON_DISPLAY_STYLE);
+        addPreferenceHandlerFor(PREF_WEARDIRECTION);
+        addPreferenceHandlerFor(PREF_WEARMODE);
+
+        addPreferenceHandlerFor(PREF_VOLUME);
+        addPreferenceHandlerFor(PREF_CROWN_VIBRATION);
+        addPreferenceHandlerFor(PREF_ALERT_TONE);
+        addPreferenceHandlerFor(PREF_COVER_TO_MUTE);
+        addPreferenceHandlerFor(PREF_VIBRATE_FOR_ALERT);
+        addPreferenceHandlerFor(PREF_TEXT_TO_SPEECH);
+
+        addPreferenceHandlerFor(PREF_OFFLINE_VOICE_RESPOND_TURN_WRIST);
+        addPreferenceHandlerFor(PREF_OFFLINE_VOICE_RESPOND_SCREEN_ON);
+        addPreferenceHandlerFor(PREF_OFFLINE_VOICE_RESPONSE_DURING_SCREEN_LIGHTING);
+        addPreferenceHandlerFor(PREF_OFFLINE_VOICE_LANGUAGE);
+
+        addPreferenceHandlerFor(PREF_VOICE_SERVICE_LANGUAGE);
+
+        addPreferenceHandlerFor(PREF_TEMPERATURE_SCALE_CF);
+
+        addPreferenceHandlerFor(PREF_PREFIX_NOTIFICATION_WITH_APP);
+
+        addPreferenceHandlerFor(PREF_SLEEP_MODE_SCHEDULE_ENABLED);
+        addPreferenceHandlerFor(PREF_SLEEP_MODE_SCHEDULE_START);
+        addPreferenceHandlerFor(PREF_SLEEP_MODE_SCHEDULE_END);
+
+        addPreferenceHandlerFor(PREF_CLAP_HANDS_TO_WAKEUP_DEVICE);
+        addPreferenceHandlerFor(PREF_POWER_SAVING);
+
+        addPreferenceHandlerFor(PREF_HEARTRATE_AUTOMATIC_ENABLE);
+        addPreferenceHandlerFor(PREF_SPO_AUTOMATIC_ENABLE);
+
+        addPreferenceHandlerFor(PREF_CYCLING_SENSOR_PERSISTENCE_INTERVAL);
+        addPreferenceHandlerFor(PREF_CYCLING_SENSOR_WHEEL_DIAMETER);
+
+        addPreferenceHandlerFor(PREF_SPEAK_NOTIFICATIONS_FOCUS_EXCLUSIVE);
+
+        addPreferenceHandlerFor(PREFS_KEY_DEVICE_BLE_API_DEVICE_STATE);
+        addPreferenceHandlerFor(PREFS_KEY_DEVICE_BLE_API_DEVICE_READ_WRITE);
+        addPreferenceHandlerFor(PREFS_KEY_DEVICE_BLE_API_DEVICE_NOTIFY);
+        addPreferenceHandlerFor(PREFS_KEY_DEVICE_BLE_API_PACKAGE);
+
+        addPreferenceHandlerFor("lock");
+
+        addPreferenceHandlerFor(PREF_BATTERY_MINIMUM_CHARGE);
+        addPreferenceHandlerFor(PREF_BATTERY_ALLOW_PASS_THOUGH);
+
+        final Preference dischargeIntervalsSet = findPreference(PREF_BATTERY_DISCHARGE_INTERVALS_SET);
+        if (dischargeIntervalsSet != null) {
+            dischargeIntervalsSet.setOnPreferenceClickListener(preference -> {
+                notifyPreferenceChanged(PREF_BATTERY_DISCHARGE_INTERVALS_SET);
+                return true;
+            });
+        }
+
+        String sleepTimeState = prefs.getString(PREF_SLEEP_TIME, PREF_DO_NOT_DISTURB_OFF);
+        boolean sleepTimeScheduled = sleepTimeState.equals(PREF_DO_NOT_DISTURB_SCHEDULED);
+
+        final Preference sleepTimeInfo = findPreference(PREF_SLEEP_TIME);
+        if (sleepTimeInfo != null) {
+            //sleepTimeInfo.setEnabled(!PREF_DO_NOT_DISTURB_OFF.equals(sleepTimeInfo));
+            sleepTimeInfo.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
+                @Override
+                public boolean onPreferenceChange(Preference preference, Object newVal) {
+                    notifyPreferenceChanged(PREF_SLEEP_TIME);
+                    return true;
+                }
+            });
+        }
+
+        final Preference sleepTimeStart = findPreference(PREF_SLEEP_TIME_START);
+        if (sleepTimeStart != null) {
+            sleepTimeStart.setEnabled(sleepTimeScheduled);
+            sleepTimeStart.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
+                @Override
+                public boolean onPreferenceChange(Preference preference, Object newVal) {
+                    notifyPreferenceChanged(PREF_SLEEP_TIME_START);
+                    return true;
+                }
+            });
+        }
+
+        final Preference sleepTimeEnd = findPreference(PREF_SLEEP_TIME_END);
+        if (sleepTimeEnd != null) {
+            sleepTimeEnd.setEnabled(sleepTimeScheduled);
+            sleepTimeEnd.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
+                @Override
+                public boolean onPreferenceChange(Preference preference, Object newVal) {
+                    notifyPreferenceChanged(PREF_SLEEP_TIME_END);
+                    return true;
+                }
+            });
+        }
+
+        final Preference sleepTime = findPreference(PREF_SLEEP_TIME);
+        if (sleepTime != null) {
+            sleepTime.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
+                @Override
+                public boolean onPreferenceChange(Preference preference, Object newVal) {
+                    final boolean scheduled = PREF_DO_NOT_DISTURB_SCHEDULED.equals(newVal.toString());
+                    Objects.requireNonNull(sleepTimeStart).setEnabled(scheduled);
+                    Objects.requireNonNull(sleepTimeEnd).setEnabled(scheduled);
+                    if (sleepTimeInfo != null) {
+                        //sleepTimeInfo.setEnabled(!PREF_DO_NOT_DISTURB_OFF.equals(newVal.toString()));
+                    }
+                    notifyPreferenceChanged(PREF_SLEEP_TIME);
+                    return true;
+                }
+            });
+        }
+        String displayOnLiftState = prefs.getString(PREF_ACTIVATE_DISPLAY_ON_LIFT, PREF_DO_NOT_DISTURB_OFF);
+        boolean displayOnLiftScheduled = displayOnLiftState.equals(PREF_DO_NOT_DISTURB_SCHEDULED);
+        boolean displayOnLiftOff = displayOnLiftState.equals(PREF_DO_NOT_DISTURB_OFF);
+
+        final Preference rotateWristCycleInfo = findPreference(PREF_MI2_ROTATE_WRIST_TO_SWITCH_INFO);
+        if (rotateWristCycleInfo != null) {
+            rotateWristCycleInfo.setEnabled(!PREF_DO_NOT_DISTURB_OFF.equals(displayOnLiftState));
+            rotateWristCycleInfo.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
+                @Override
+                public boolean onPreferenceChange(Preference preference, Object newVal) {
+                    notifyPreferenceChanged(PREF_MI2_ROTATE_WRIST_TO_SWITCH_INFO);
+                    return true;
+                }
+            });
+        }
+
+        final Preference phoneSilentMode = findPreference(PREF_PHONE_SILENT_MODE);
+        if (phoneSilentMode != null) {
+            phoneSilentMode.setOnPreferenceChangeListener((preference, newVal) -> {
+                final AudioManager audioManager = (AudioManager) requireContext().getSystemService(Context.AUDIO_SERVICE);
+                GBApplication.deviceService(device).onChangePhoneSilentMode(audioManager.getRingerMode());
+                return true;
+            });
+        }
+
+        final String alwaysOnDisplayState = prefs.getString(PREF_ALWAYS_ON_DISPLAY_MODE, PREF_ALWAYS_ON_DISPLAY_OFF);
+        boolean alwaysOnDisplayScheduled = alwaysOnDisplayState.equals(PREF_ALWAYS_ON_DISPLAY_SCHEDULED);
+        boolean alwaysOnDisplayOff = alwaysOnDisplayState.equals(PREF_ALWAYS_ON_DISPLAY_OFF);
+
+        final Preference alwaysOnDisplayStart = findPreference(PREF_ALWAYS_ON_DISPLAY_START);
+        if (alwaysOnDisplayStart != null) {
+            alwaysOnDisplayStart.setEnabled(alwaysOnDisplayScheduled);
+            alwaysOnDisplayStart.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
+                @Override
+                public boolean onPreferenceChange(Preference preference, Object newVal) {
+                    notifyPreferenceChanged(PREF_ALWAYS_ON_DISPLAY_START);
+                    return true;
+                }
+            });
+        }
+
+        final Preference alwaysOnDisplayEnd = findPreference(PREF_ALWAYS_ON_DISPLAY_END);
+        if (alwaysOnDisplayEnd != null) {
+            alwaysOnDisplayEnd.setEnabled(alwaysOnDisplayScheduled);
+            alwaysOnDisplayEnd.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
+                @Override
+                public boolean onPreferenceChange(Preference preference, Object newVal) {
+                    notifyPreferenceChanged(PREF_ALWAYS_ON_DISPLAY_END);
+                    return true;
+                }
+            });
+        }
+
+        final Preference alwaysOnDisplayMode = findPreference(PREF_ALWAYS_ON_DISPLAY_MODE);
+        if (alwaysOnDisplayMode != null) {
+            alwaysOnDisplayMode.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
+                @Override
+                public boolean onPreferenceChange(Preference preference, Object newVal) {
+                    final boolean scheduled = PREF_ALWAYS_ON_DISPLAY_SCHEDULED.equals(newVal.toString());
+                    final boolean off = PREF_ALWAYS_ON_DISPLAY_OFF.equals(newVal.toString());
+                    alwaysOnDisplayStart.setEnabled(scheduled);
+                    alwaysOnDisplayEnd.setEnabled(scheduled);
+                    notifyPreferenceChanged(PREF_ALWAYS_ON_DISPLAY_MODE);
+                    return true;
+                }
+            });
+        }
+
+        final Preference displayOnLiftStart = findPreference(PREF_DISPLAY_ON_LIFT_START);
+        if (displayOnLiftStart != null) {
+            displayOnLiftStart.setEnabled(displayOnLiftScheduled);
+            displayOnLiftStart.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
+                @Override
+                public boolean onPreferenceChange(Preference preference, Object newVal) {
+                    notifyPreferenceChanged(PREF_DISPLAY_ON_LIFT_START);
+                    return true;
+                }
+            });
+        }
+
+        final Preference displayOnLiftEnd = findPreference(PREF_DISPLAY_ON_LIFT_END);
+        if (displayOnLiftEnd != null) {
+            displayOnLiftEnd.setEnabled(displayOnLiftScheduled);
+            displayOnLiftEnd.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
+                @Override
+                public boolean onPreferenceChange(Preference preference, Object newVal) {
+                    notifyPreferenceChanged(PREF_DISPLAY_ON_LIFT_END);
+                    return true;
+                }
+            });
+        }
+
+        final Preference displayOnLiftSensitivity = findPreference(PREF_DISPLAY_ON_LIFT_SENSITIVITY);
+        if (displayOnLiftSensitivity != null) {
+            displayOnLiftSensitivity.setEnabled(!displayOnLiftOff);
+            displayOnLiftSensitivity.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
+                @Override
+                public boolean onPreferenceChange(Preference preference, Object newVal) {
+                    notifyPreferenceChanged(PREF_DISPLAY_ON_LIFT_SENSITIVITY);
+                    return true;
+                }
+            });
+        }
+
+        final Preference displayOnLift = findPreference(PREF_ACTIVATE_DISPLAY_ON_LIFT);
+        if (displayOnLift != null) {
+            displayOnLift.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
+                @Override
+                public boolean onPreferenceChange(Preference preference, Object newVal) {
+                    final boolean scheduled = PREF_DO_NOT_DISTURB_SCHEDULED.equals(newVal.toString());
+                    final boolean off = PREF_DO_NOT_DISTURB_OFF.equals(newVal.toString());
+                    Objects.requireNonNull(displayOnLiftStart).setEnabled(scheduled);
+                    Objects.requireNonNull(displayOnLiftEnd).setEnabled(scheduled);
+                    if (rotateWristCycleInfo != null) {
+                        rotateWristCycleInfo.setEnabled(!off);
+                    }
+                    if (displayOnLiftSensitivity != null) {
+                        displayOnLiftSensitivity.setEnabled(!off);
+                    }
+                    notifyPreferenceChanged(PREF_ACTIVATE_DISPLAY_ON_LIFT);
+                    return true;
+                }
+            });
+        }
+
+        final Preference worldClocks = findPreference(PREF_WORLD_CLOCKS);
+        if (worldClocks != null) {
+            worldClocks.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
+                @Override
+                public boolean onPreferenceClick(Preference preference) {
+                    final Intent intent = new Intent(getContext(), ConfigureWorldClocks.class);
+                    intent.putExtra(GBDevice.EXTRA_DEVICE, device);
+                    startActivity(intent);
+                    return true;
+                }
+            });
+        }
+
+        final Preference contacts = findPreference(PREF_CONTACTS);
+        if (contacts != null) {
+            contacts.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
+                @Override
+                public boolean onPreferenceClick(Preference preference) {
+                    final Intent intent = new Intent(getContext(), ConfigureContacts.class);
+                    intent.putExtra(GBDevice.EXTRA_DEVICE, device);
+                    startActivity(intent);
+                    return true;
+                }
+            });
+        }
+
+        final Preference music_management = findPreference(PREF_MUSIC_MANAGEMENT);
+        if (music_management != null) {
+            music_management.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
+                @Override
+                public boolean onPreferenceClick(Preference preference) {
+                    final Intent intent = new Intent(getContext(), MusicManagerActivity.class);
+                    intent.putExtra(GBDevice.EXTRA_DEVICE, device);
+                    startActivity(intent);
+                    return true;
+                }
+            });
+        }
+
+        final Preference widgets = findPreference(PREF_WIDGETS);
+        if (widgets != null) {
+            widgets.setOnPreferenceClickListener(preference -> {
+                final Intent intent = new Intent(getContext(), WidgetScreensListActivity.class);
+                intent.putExtra(GBDevice.EXTRA_DEVICE, device);
+                startActivity(intent);
+                return true;
+            });
+        }
+
+        final Preference calendarBlacklist = findPreference("blacklist_calendars");
+        if (calendarBlacklist != null) {
+            calendarBlacklist.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
+                public boolean onPreferenceClick(Preference preference) {
+                    Intent intent = new Intent(getContext(), CalBlacklistActivity.class);
+                    intent.putExtra(GBDevice.EXTRA_DEVICE, device);
+                    startActivity(intent);
+                    return true;
+                }
+            });
+        }
+
+        final int cannedRepliesSlotCount = coordinator.getCannedRepliesSlotCount(device);
+
+        final Preference cannedMessagesDismissCall = findPreference("canned_messages_dismisscall_send");
+        if (cannedMessagesDismissCall != null) {
+            cannedMessagesDismissCall.setOnPreferenceClickListener(new androidx.preference.Preference.OnPreferenceClickListener() {
+                public boolean onPreferenceClick(androidx.preference.Preference preference) {
+                    ArrayList<String> messages = new ArrayList<>();
+                    for (int i = 1; i <= cannedRepliesSlotCount; i++) {
+                        String message = prefs.getString("canned_message_dismisscall_" + i, null);
+                        if (message != null && !message.equals("")) {
+                            messages.add(message);
+                        }
+                    }
+                    CannedMessagesSpec cannedMessagesSpec = new CannedMessagesSpec();
+                    cannedMessagesSpec.type = CannedMessagesSpec.TYPE_REJECTEDCALLS;
+                    cannedMessagesSpec.cannedMessages = messages.toArray(new String[0]);
+                    GBApplication.deviceService(device).onSetCannedMessages(cannedMessagesSpec);
+                    return true;
+                }
+            });
+
+            // TODO we could use this to auto-create preferences for watches with > 16 slots
+            for (int i = cannedRepliesSlotCount + 1; i <= 16; i++) {
+                final Preference cannedReplyPref = findPreference("canned_message_dismisscall_" + i);
+                if (cannedReplyPref != null) {
+                    cannedReplyPref.setVisible(false);
+                }
+            }
+        }
+
+        final Preference cannedMessagesGeneric = findPreference("canned_messages_generic_send");
+        if (cannedMessagesGeneric != null) {
+
+            cannedMessagesGeneric.setOnPreferenceClickListener(new androidx.preference.Preference.OnPreferenceClickListener() {
+                public boolean onPreferenceClick(androidx.preference.Preference preference) {
+                    final ArrayList<String> messages = new ArrayList<>();
+                    for (int i = 1; i <= cannedRepliesSlotCount; i++) {
+                        String message = prefs.getString("canned_reply_" + i, null);
+                        if (message != null && !message.equals("")) {
+                            messages.add(message);
+                        }
+                    }
+                    final CannedMessagesSpec cannedMessagesSpec = new CannedMessagesSpec();
+                    cannedMessagesSpec.type = CannedMessagesSpec.TYPE_GENERIC;
+                    cannedMessagesSpec.cannedMessages = messages.toArray(new String[0]);
+                    GBApplication.deviceService().onSetCannedMessages(cannedMessagesSpec);
+                    return true;
+                }
+            });
+
+            // TODO we could use this to auto-create preferences for watches with > 16 slots
+            for (int i = cannedRepliesSlotCount + 1; i <= 16; i++) {
+                final Preference cannedReplyPref = findPreference("canned_reply_" + i);
+                if (cannedReplyPref != null) {
+                    cannedReplyPref.setVisible(false);
+                }
+            }
+        }
+
+        final EditTextPreference calendarLookahead = findPreference(DeviceSettingsPreferenceConst.PREF_CALENDAR_LOOKAHEAD_DAYS);
+        if (calendarLookahead != null) {
+            setInputTypeFor(DeviceSettingsPreferenceConst.PREF_CALENDAR_LOOKAHEAD_DAYS, InputType.TYPE_CLASS_NUMBER);
+            calendarLookahead.setSummaryProvider(new GBSimpleSummaryProvider(
+                    requireContext().getString(R.string.pref_summary_calendar_lookahead, "7"),
+                    R.string.pref_summary_calendar_lookahead
+            ));
+        }
+
+        setInputTypeFor(HuamiConst.PREF_BUTTON_ACTION_BROADCAST_DELAY, InputType.TYPE_CLASS_NUMBER);
+        setInputTypeFor(HuamiConst.PREF_BUTTON_ACTION_PRESS_MAX_INTERVAL, InputType.TYPE_CLASS_NUMBER);
+        setInputTypeFor(HuamiConst.PREF_BUTTON_ACTION_PRESS_COUNT, InputType.TYPE_CLASS_NUMBER);
+        setInputTypeFor(MiBandConst.PREF_MIBAND_DEVICE_TIME_OFFSET_HOURS, InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_SIGNED);
+        setInputTypeFor(DeviceSettingsPreferenceConst.PREF_FIND_PHONE_DURATION, InputType.TYPE_CLASS_NUMBER);
+        setInputTypeFor(DeviceSettingsPreferenceConst.PREF_RESERVER_ALARMS_CALENDAR, InputType.TYPE_CLASS_NUMBER);
+        setInputTypeFor(DeviceSettingsPreferenceConst.PREF_RESERVE_REMINDERS_CALENDAR, InputType.TYPE_CLASS_NUMBER);
+        setInputTypeFor(DeviceSettingsPreferenceConst.PREF_INACTIVITY_THRESHOLD, InputType.TYPE_CLASS_NUMBER);
+        setInputTypeFor(DeviceSettingsPreferenceConst.PREF_DEVICE_GPS_UPDATE_INTERVAL, InputType.TYPE_CLASS_NUMBER);
+        setInputTypeFor(DeviceSettingsPreferenceConst.PREF_BANGLEJS_TEXT_BITMAP_SIZE, InputType.TYPE_CLASS_NUMBER);
+        setInputTypeFor(DeviceSettingsPreferenceConst.PREF_AUTO_REPLY_INCOMING_CALL_DELAY, InputType.TYPE_CLASS_NUMBER);
+        setInputTypeFor("hplus_screentime", InputType.TYPE_CLASS_NUMBER);
+        setNumericInputTypeWithRangeFor(DeviceSettingsPreferenceConst.PREF_BATTERY_DISCHARGE_INTERVAL1_WATT, 80, 800, false);
+        setNumericInputTypeWithRangeFor(DeviceSettingsPreferenceConst.PREF_BATTERY_DISCHARGE_INTERVAL2_WATT, 80, 800, false);
+        setNumericInputTypeWithRangeFor(DeviceSettingsPreferenceConst.PREF_BATTERY_DISCHARGE_INTERVAL3_WATT, 80, 800, false);
+        setNumericInputTypeWithRangeFor(DeviceSettingsPreferenceConst.PREF_BATTERY_DISCHARGE_INTERVAL4_WATT, 80, 800, false);
+        setNumericInputTypeWithRangeFor(DeviceSettingsPreferenceConst.PREF_BATTERY_DISCHARGE_INTERVAL5_WATT, 80, 800, false);
+        setNumericInputTypeWithRangeFor(PREF_BATTERY_MINIMUM_CHARGE, 0, 100, false);
+
+        new PasswordCapabilityImpl().registerPreferences(getContext(), coordinator.getPasswordCapability(), this);
+        new HeartRateCapability().registerPreferences(getContext(), coordinator.getHeartRateMeasurementIntervals(), this);
+
+        Set<String> deviceActionsFellSleepSelection = prefs.getStringSet(PREF_DEVICE_ACTION_FELL_SLEEP_SELECTIONS, Collections.emptySet());
+        final Preference deviceActionsFellSleep = findPreference(PREF_DEVICE_ACTION_FELL_SLEEP_SELECTIONS);
+        final Preference deviceActionsFellSleepBroadcast = findPreference(PREF_DEVICE_ACTION_FELL_SLEEP_BROADCAST);
+        boolean deviceActionsFellSleepSelectionBroadcast = deviceActionsFellSleepSelection.contains(PREF_DEVICE_ACTION_SELECTION_BROADCAST);
+        if (deviceActionsFellSleep != null) {
+            deviceActionsFellSleep.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
+                @Override
+                public boolean onPreferenceChange(Preference preference, Object newVal) {
+                    final Set<String> newValSet = (Set<String>) newVal;
+                    final boolean broadcast = newValSet.contains(PREF_DEVICE_ACTION_SELECTION_BROADCAST);
+                    Objects.requireNonNull(deviceActionsFellSleepBroadcast).setEnabled(broadcast);
+                    return true;
+                }
+            });
+        }
+        if (deviceActionsFellSleepBroadcast != null) {
+            deviceActionsFellSleepBroadcast.setEnabled(deviceActionsFellSleepSelectionBroadcast);
+        }
+
+        Set<String> deviceActionsWokeUpSelection = prefs.getStringSet(PREF_DEVICE_ACTION_WOKE_UP_SELECTIONS, Collections.emptySet());
+        final Preference deviceActionsWokeUp = findPreference(PREF_DEVICE_ACTION_WOKE_UP_SELECTIONS);
+        final Preference deviceActionsWokeUpBroadcast = findPreference(PREF_DEVICE_ACTION_WOKE_UP_BROADCAST);
+        boolean deviceActionsWokeUpSelectionBroadcast = deviceActionsWokeUpSelection.contains(PREF_DEVICE_ACTION_SELECTION_BROADCAST);
+        if (deviceActionsWokeUp != null) {
+            deviceActionsWokeUp.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
+                @Override
+                public boolean onPreferenceChange(Preference preference, Object newVal) {
+                    final Set<String> newValSet = (Set<String>) newVal;
+                    final boolean broadcast = newValSet.contains(PREF_DEVICE_ACTION_SELECTION_BROADCAST);
+                    Objects.requireNonNull(deviceActionsWokeUpBroadcast).setEnabled(broadcast);
+                    return true;
+                }
+            });
+        }
+        if (deviceActionsWokeUpBroadcast != null) {
+            deviceActionsWokeUpBroadcast.setEnabled(deviceActionsWokeUpSelectionBroadcast);
+        }
+
+        Set<String> deviceActionsStartNonWearSelection = prefs.getStringSet(PREF_DEVICE_ACTION_START_NON_WEAR_SELECTIONS, Collections.emptySet());
+        final Preference deviceActionsStartNonWear = findPreference(PREF_DEVICE_ACTION_START_NON_WEAR_SELECTIONS);
+        final Preference deviceActionsStartNonWearBroadcast = findPreference(PREF_DEVICE_ACTION_START_NON_WEAR_BROADCAST);
+        boolean deviceActionsStartNonWearSelectionBroadcast = deviceActionsStartNonWearSelection.contains(PREF_DEVICE_ACTION_SELECTION_BROADCAST);
+        if (deviceActionsStartNonWear != null) {
+            deviceActionsStartNonWear.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
+                @Override
+                public boolean onPreferenceChange(Preference preference, Object newVal) {
+                    final Set<String> newValSet = (Set<String>) newVal;
+                    final boolean broadcast = newValSet.contains(PREF_DEVICE_ACTION_SELECTION_BROADCAST);
+                    Objects.requireNonNull(deviceActionsStartNonWearBroadcast).setEnabled(broadcast);
+                    return true;
+                }
+            });
+        }
+        if (deviceActionsStartNonWearBroadcast != null) {
+            deviceActionsStartNonWearBroadcast.setEnabled(deviceActionsStartNonWearSelectionBroadcast);
+        }
+
+        // this is to ensure that Control Center device cards are refreshed on preference changes
+        final Preference activityInDeviceCard = findPreference(PREFS_ACTIVITY_IN_DEVICE_CARD);
+        final Preference activityInDeviceSteps = findPreference(PREFS_ACTIVITY_IN_DEVICE_CARD_STEPS);
+        final Preference activityInDeviceSleep = findPreference(PREFS_ACTIVITY_IN_DEVICE_CARD_SLEEP);
+        final Preference activityInDeviceDistance = findPreference(PREFS_ACTIVITY_IN_DEVICE_CARD_DISTANCE);
+        final Preference chartsTabsOrderSelection = findPreference(PREFS_DEVICE_CHARTS_TABS);
+
+        Preference.OnPreferenceClickListener sendIntentRefreshDeviceListListener = new Preference.OnPreferenceClickListener() {
+            @Override
+            public boolean onPreferenceClick(Preference arg0) {
+                Intent refreshIntent = new Intent(DeviceManager.ACTION_REFRESH_DEVICELIST);
+                LocalBroadcastManager.getInstance(getContext()).sendBroadcast(refreshIntent);
+                return true;
+            }
+        };
+
+        Preference[] preferencesInControlCenter = {
+                activityInDeviceCard,
+                activityInDeviceSteps,
+                activityInDeviceSleep,
+                activityInDeviceDistance,
+                chartsTabsOrderSelection,
+        };
+
+        for (Preference preferenceInControlCenter : preferencesInControlCenter) {
+            if (preferenceInControlCenter != null) {
+                preferenceInControlCenter.setOnPreferenceClickListener(sendIntentRefreshDeviceListListener);
+            }
+        }
+
+        // Replace the PAI with the device-specific name
+        if (chartsTabsOrderSelection != null) {
+            final ListPreference chartsTabsListPref = (ListPreference) chartsTabsOrderSelection;
+            final CharSequence[] entries = chartsTabsListPref.getEntries();
+            final CharSequence[] entryValues = chartsTabsListPref.getEntryValues();
+            for (int i = 0; i < entries.length; i++) {
+                if ("pai".equals(entryValues[i].toString())) {
+                    entries[i] = getString(coordinator.getPaiName());
+                    break;
+                }
+            }
+            chartsTabsListPref.setEntries(entries);
+        }
+
+        final Preference loyaltyCards = findPreference(LoyaltyCardsSettingsConst.PREF_KEY_LOYALTY_CARDS);
+        if (loyaltyCards != null) {
+            loyaltyCards.setOnPreferenceClickListener(preference -> {
+                final Intent intent = new Intent(getContext(), LoyaltyCardsSettingsActivity.class);
+                intent.putExtra(GBDevice.EXTRA_DEVICE, getDevice());
+                startActivity(intent);
+                return true;
+            });
+        }
+
+        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, rootKey);
+        }
+    }
+
+    static DeviceSpecificSettingsFragment newInstance(GBDevice device, DeviceSettingsActivity.MENU_ENTRY_POINTS applicationSpecificSettings) {
+        final DeviceCoordinator coordinator = device.getDeviceCoordinator();
+
+        final DeviceSpecificSettings deviceSpecificSettings = new DeviceSpecificSettings();
+
+        if (applicationSpecificSettings.equals(DeviceSettingsActivity.MENU_ENTRY_POINTS.AUTH_SETTINGS)) { //auth settings screen
+            deviceSpecificSettings.addRootScreen(R.xml.devicesettings_pairingkey_explanation);
+            for (final int s : coordinator.getSupportedDeviceSpecificAuthenticationSettings()) {
+                deviceSpecificSettings.addRootScreen(s);
+            }
+        } else { //device/application settings
+            if (coordinator.getSupportedLanguageSettings(device) != null) {
+                deviceSpecificSettings.addRootScreen(R.xml.devicesettings_language_generic);
+            }
+            DeviceSpecificSettings coordinatorDeviceSettings = coordinator.getDeviceSpecificSettings(device);
+            if (coordinatorDeviceSettings != null) {
+                deviceSpecificSettings.mergeFrom(coordinatorDeviceSettings);
+            }
+            final int[] supportedAuthSettings = coordinator.getSupportedDeviceSpecificAuthenticationSettings();
+            if (supportedAuthSettings != null && supportedAuthSettings.length > 0) {
+                deviceSpecificSettings.addRootScreen(
+                        DeviceSpecificSettingsScreen.AUTHENTICATION,
+                        supportedAuthSettings
+                );
+            }
+
+            deviceSpecificSettings.addRootScreen(
+                    DeviceSpecificSettingsScreen.CONNECTION,
+                    coordinator.getSupportedDeviceSpecificConnectionSettings()
+            );
+
+            if (coordinator.getBatteryCount() > 0) {
+                deviceSpecificSettings.addRootScreen(
+                        DeviceSpecificSettingsScreen.BATTERY
+                );
+            }
+
+            if (coordinator.supportsActivityTracking()) {
+                deviceSpecificSettings.addRootScreen(
+                        DeviceSpecificSettingsScreen.ACTIVITY_INFO,
+                        R.xml.devicesettings_chartstabs,
+                        R.xml.devicesettings_device_card_activity_card_preferences
+                );
+            }
+
+            deviceSpecificSettings.addRootScreen(
+                    DeviceSpecificSettingsScreen.DEVELOPER,
+                    R.xml.devicesettings_settings_third_party_apps
+            );
+            if (coordinator.getConnectionType().usesBluetoothLE()) {
+                deviceSpecificSettings.addRootScreen(
+                        DeviceSpecificSettingsScreen.DEVELOPER,
+                        R.xml.devicesettings_ble_api
+                );
+            }
+        }
+
+        final DeviceSpecificSettingsCustomizer deviceSpecificSettingsCustomizer = coordinator.getDeviceSpecificSettingsCustomizer(device);
+        final String settingsFileSuffix = device.getAddress();
+        final DeviceSpecificSettingsFragment fragment = new DeviceSpecificSettingsFragment();
+        fragment.setSettingsFileSuffix(settingsFileSuffix);
+        fragment.setDeviceSpecificSettings(deviceSpecificSettings);
+        fragment.setDeviceSpecificSettingsCustomizer(deviceSpecificSettingsCustomizer);
+        fragment.setDevice(device);
+
+        return fragment;
+    }
+
+    @Override
+    public void addPreferenceHandlerFor(final String preferenceKey) {
+        addPreferenceHandlerFor(preferenceKey, null);
+    }
+
+    @Override
+    public void addPreferenceHandlerFor(final String preferenceKey, final Preference.OnPreferenceChangeListener extraListener) {
+        Preference pref = findPreference(preferenceKey);
+        if (pref != null) {
+            pref.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
+                public boolean onPreferenceChange(Preference preference, Object newVal) {
+                    notifyPreferenceChanged(preferenceKey);
+
+                    if (extraListener != null) {
+                        return extraListener.onPreferenceChange(preference, newVal);
+                    }
+
+                    return true;
+                }
+            });
+        }
+    }
+
+    @Override
+    public GBDevice getDevice() {
+        return device;
+    }
+
+    @Override
+    protected Set<String> getPreferenceKeysWithSummary() {
+        final Set<String> keysWithSummary = new HashSet<>();
+
+        keysWithSummary.add(PREF_INACTIVITY_THRESHOLD);
+        keysWithSummary.add(PREF_DEVICE_ACTION_FELL_SLEEP_SELECTIONS);
+        keysWithSummary.add(PREF_DEVICE_ACTION_START_NON_WEAR_SELECTIONS);
+        keysWithSummary.add(PREF_DEVICE_ACTION_WOKE_UP_SELECTIONS);
+
+        if (deviceSpecificSettingsCustomizer != null) {
+            keysWithSummary.addAll(deviceSpecificSettingsCustomizer.getPreferenceKeysWithSummary());
+        }
+
+        return keysWithSummary;
+    }
+
+    @Override
+    protected void onSharedPreferenceChanged(Preference preference) {
+        if (deviceSpecificSettingsCustomizer != null) {
+            deviceSpecificSettingsCustomizer.onPreferenceChange(preference, DeviceSpecificSettingsFragment.this);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSpecificSettingsHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSpecificSettingsHandler.java
new file mode 100644
index 0000000..ae6d31a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSpecificSettingsHandler.java
@@ -0,0 +1,83 @@
+/*  Copyright (C) 2021-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities.devicesettings;
+
+import android.content.Context;
+
+import androidx.activity.result.ActivityResultCaller;
+import androidx.annotation.NonNull;
+import androidx.preference.Preference;
+
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+/**
+ * A device-specific preference handler, that allows for {@link nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator}s to register
+ * their own preferences dynamically.
+ */
+public interface DeviceSpecificSettingsHandler extends ActivityResultCaller {
+    /**
+     * Finds a preference with the given key. Returns null if the preference is not found.
+     *
+     * @param preferenceKey the preference key.
+     * @return the preference, if found.
+     */
+    <T extends Preference> T findPreference(@NonNull CharSequence preferenceKey);
+
+    /**
+     * Adds a preference handler for a preference key. This handler sends the preference to the device on change.
+     *
+     * @param preferenceKey the preference key.
+     */
+    void addPreferenceHandlerFor(final String preferenceKey);
+
+    /**
+     * Notify the device that a preference changed.
+     *
+     * @param preferenceKey the preference key.
+     */
+    void notifyPreferenceChanged(final String preferenceKey);
+
+    /**
+     * Adds a preference handler for a preference key. On change, this handler calls the provided extra listener, and then sends the preference to the device.
+     *
+     * @param preferenceKey the preference key.
+     * @param extraListener the extra listener.
+     */
+    void addPreferenceHandlerFor(final String preferenceKey, Preference.OnPreferenceChangeListener extraListener);
+
+    /**
+     * Sets the input type flags for an EditText preference.
+     *
+     * @param preferenceKey the preference key.
+     * @param editTypeFlags the edit type {@link android.text.InputType} flags.
+     */
+    void setInputTypeFor(final String preferenceKey, final int editTypeFlags);
+
+    /**
+     * Get the device associated with this {@link DeviceSpecificSettingsHandler}.
+     *
+     * @return the {@link GBDevice}.
+     */
+    GBDevice getDevice();
+
+    /**
+     * Get the current {@link Context}.
+     *
+     * @return the {@link Context}.
+     */
+    Context getContext();
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSpecificSettingsScreen.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSpecificSettingsScreen.java
new file mode 100644
index 0000000..bd041bf
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSpecificSettingsScreen.java
@@ -0,0 +1,59 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities.devicesettings;
+
+import androidx.annotation.XmlRes;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+
+public enum DeviceSpecificSettingsScreen {
+    ACTIVITY_INFO("pref_screen_activity_info", R.xml.devicesettings_root_activity_info),
+    AUDIO("pref_screen_audio", R.xml.devicesettings_root_audio),
+    AUTHENTICATION("pref_screen_authentication", R.xml.devicesettings_root_authentication),
+    BATTERY("pref_screen_battery", R.xml.devicesettings_root_battery),
+    CALENDAR("pref_screen_calendar", R.xml.devicesettings_root_calendar),
+    CALLS_AND_NOTIFICATIONS("pref_screen_calls_and_notifications", R.xml.devicesettings_root_calls_and_notifications),
+    CONNECTION("pref_screen_connection", R.xml.devicesettings_root_connection),
+    DEVELOPER("pref_screen_developer", R.xml.devicesettings_root_developer),
+    DISPLAY("pref_screen_display", R.xml.devicesettings_root_display),
+    GENERIC("pref_screen_generic", R.xml.devicesettings_root_generic),
+    LOCATION("pref_screen_location", R.xml.devicesettings_root_location),
+    NOTIFICATIONS("pref_screen_notifications", R.xml.devicesettings_root_notifications),
+    DATE_TIME("pref_screen_date_time", R.xml.devicesettings_root_date_time),
+    WORKOUT("pref_screen_workout", R.xml.devicesettings_root_workout),
+    HEALTH("pref_screen_health", R.xml.devicesettings_root_health),
+    TOUCH_OPTIONS("pref_screen_touch_options", R.xml.devicesettings_root_touch_options),
+    SOUND("pref_screen_sound", R.xml.devicesettings_root_touch_options),
+    ;
+
+    private final String key;
+    @XmlRes
+    private final int xml;
+
+    DeviceSpecificSettingsScreen(final String key, final int xml) {
+        this.key = key;
+        this.xml = xml;
+    }
+
+    public String getKey() {
+        return key;
+    }
+
+    public int getXml() {
+        return xml;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/discovery/DiscoveryActivityV2.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/discovery/DiscoveryActivityV2.java
new file mode 100644
index 0000000..fc44df6
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/discovery/DiscoveryActivityV2.java
@@ -0,0 +1,932 @@
+/*  Copyright (C) 2023-2024 Andreas B枚hler, Daniel Dakhno, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities.discovery;
+
+import static nodomain.freeyourgadget.gadgetbridge.util.GB.toast;
+
+import android.Manifest;
+import android.app.Activity;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothManager;
+import android.bluetooth.le.BluetoothLeScanner;
+import android.bluetooth.le.ScanCallback;
+import android.bluetooth.le.ScanFilter;
+import android.bluetooth.le.ScanRecord;
+import android.bluetooth.le.ScanResult;
+import android.bluetooth.le.ScanSettings;
+import android.content.BroadcastReceiver;
+import android.content.ClipData;
+import android.content.ClipboardManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
+import android.location.LocationManager;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.os.ParcelUuid;
+import android.os.Parcelable;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.Pair;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.Button;
+import android.widget.LinearLayout;
+import android.widget.ListView;
+import android.widget.ProgressBar;
+import android.widget.Spinner;
+import android.widget.Toast;
+
+import androidx.activity.result.ActivityResultLauncher;
+import androidx.activity.result.contract.ActivityResultContracts;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
+import androidx.annotation.RequiresPermission;
+import androidx.annotation.StringRes;
+import androidx.core.app.ActivityCompat;
+import androidx.core.content.ContextCompat;
+import androidx.core.view.MenuProvider;
+
+import com.google.android.material.dialog.MaterialAlertDialogBuilder;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBActivity;
+import nodomain.freeyourgadget.gadgetbridge.activities.DebugActivity;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsActivity;
+import nodomain.freeyourgadget.gadgetbridge.adapter.DeviceCandidateAdapter;
+import nodomain.freeyourgadget.gadgetbridge.adapter.SpinnerWithIconAdapter;
+import nodomain.freeyourgadget.gadgetbridge.adapter.SpinnerWithIconItem;
+import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.util.AndroidUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.BondingInterface;
+import nodomain.freeyourgadget.gadgetbridge.util.BondingUtil;
+import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+
+public class DiscoveryActivityV2 extends AbstractGBActivity implements AdapterView.OnItemClickListener,
+        AdapterView.OnItemLongClickListener,
+        BondingInterface,
+        GBScanEventProcessor.Callback,
+        MenuProvider {
+    private static final Logger LOG = LoggerFactory.getLogger(DiscoveryActivityV2.class);
+
+    private final Handler handler = new Handler();
+
+    private static final long SCAN_DURATION = 30000; // 30s
+    private static final long LIST_REFRESH_THRESHOLD_MS = 1000L;
+    private long lastListRefresh = System.currentTimeMillis();
+
+    private final ScanCallback bleScanCallback = new BleScanCallback();
+
+    private ProgressBar bluetoothProgress;
+
+    private DeviceCandidateAdapter deviceCandidateAdapter;
+    private GBDeviceCandidate deviceTarget;
+    private BluetoothAdapter adapter;
+
+    private Button startButton;
+    private boolean scanning;
+
+    private long selectedUnsupportedDeviceKey = DebugActivity.SELECT_DEVICE;
+
+    private final Runnable stopRunnable = () -> {
+        stopDiscovery();
+        LOG.info("Discovery stopped by thread timeout.");
+    };
+
+    private final BroadcastReceiver bluetoothReceiver = new BluetoothReceiver();
+
+    private final GBScanEventProcessor deviceFoundProcessor = new GBScanEventProcessor(this);
+
+    // Array to back the adapter for the UI
+    private final ArrayList<GBDeviceCandidate> deviceCandidates = new ArrayList<>();
+
+    @RequiresApi(Build.VERSION_CODES.O)
+    @Override
+    public void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
+        super.onActivityResult(requestCode, resultCode, data);
+        BondingUtil.handleActivityResult(this, requestCode, resultCode, data);
+    }
+
+    @Nullable
+    private GBDeviceCandidate getCandidateFromMAC(final BluetoothDevice device) {
+        for (final GBDeviceCandidate candidate : deviceCandidates) {
+            if (candidate.getMacAddress().equals(device.getAddress())) {
+                return candidate;
+            }
+        }
+        LOG.warn("This shouldn't happen unless the list somehow emptied itself, device MAC: {}", device.getAddress());
+        return null;
+    }
+
+    @Override
+    protected void onCreate(final Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        loadSettings();
+
+        setContentView(R.layout.activity_discovery);
+
+        addMenuProvider(this);
+
+        startButton = findViewById(R.id.discovery_start);
+        startButton.setOnClickListener(v -> toggleDiscovery());
+
+        bluetoothProgress = findViewById(R.id.discovery_progressbar);
+        bluetoothProgress.setProgress(0);
+        bluetoothProgress.setIndeterminate(true);
+        bluetoothProgress.setVisibility(View.GONE);
+
+        deviceCandidateAdapter = new DeviceCandidateAdapter(this, deviceCandidates);
+
+        final ListView deviceCandidatesView = findViewById(R.id.discovery_device_candidates_list);
+        deviceCandidatesView.setAdapter(deviceCandidateAdapter);
+        deviceCandidatesView.setOnItemClickListener(this);
+        deviceCandidatesView.setOnItemLongClickListener(this);
+
+        registerBroadcastReceivers();
+
+        checkAndRequestLocationPermission();
+
+        if (!startDiscovery()) {
+            /* if we couldn't start scanning, go back to the main page.
+            A toast will have been shown explaining what's wrong */
+            finish();
+        }
+    }
+
+    @Override
+    protected void onSaveInstanceState(@NonNull final Bundle outState) {
+        super.onSaveInstanceState(outState);
+        outState.putParcelableArrayList("deviceCandidates", deviceCandidates);
+    }
+
+    @Override
+    protected void onRestoreInstanceState(@NonNull final Bundle savedInstanceState) {
+        super.onRestoreInstanceState(savedInstanceState);
+        final List<Parcelable> restoredCandidates = savedInstanceState.getParcelableArrayList("deviceCandidates");
+        if (restoredCandidates != null) {
+            deviceCandidates.clear();
+            for (final Parcelable p : restoredCandidates) {
+                final GBDeviceCandidate candidate = (GBDeviceCandidate) p;
+                deviceCandidates.add(candidate);
+            }
+        }
+    }
+
+    @Override
+    protected void onDestroy() {
+        unregisterBroadcastReceivers();
+        stopDiscovery();
+        super.onDestroy();
+    }
+
+    @Override
+    protected void onStop() {
+        unregisterBroadcastReceivers();
+        stopDiscovery();
+        super.onStop();
+    }
+
+    @Override
+    protected void onPause() {
+        unregisterBroadcastReceivers();
+        stopDiscovery();
+        super.onPause();
+    }
+
+    @Override
+    protected void onResume() {
+        loadSettings();
+        registerBroadcastReceivers();
+        super.onResume();
+    }
+
+    private void refreshDeviceList(final boolean throttle) {
+        handler.post(() -> {
+            if (throttle && System.currentTimeMillis() - lastListRefresh < LIST_REFRESH_THRESHOLD_MS) {
+                return;
+            }
+
+            LOG.debug("Refreshing device list");
+
+            // Clear and re-populate the list. deviceFoundProcessor keeps insertion order, so newer devices
+            // will still be at the end
+            deviceCandidates.clear();
+            deviceCandidates.addAll(deviceFoundProcessor.getDevices());
+
+            deviceCandidateAdapter.notifyDataSetChanged();
+
+            lastListRefresh = System.currentTimeMillis();
+        });
+    }
+
+    private void toggleDiscovery() {
+        if (scanning) {
+            stopDiscovery();
+        } else {
+            startDiscovery();
+        }
+    }
+
+    private boolean startDiscovery() {
+        if (scanning) {
+            LOG.warn("Not starting discovery, because already scanning.");
+            return false;
+        }
+
+        LOG.info("Starting discovery");
+        startButton.setText(getString(R.string.discovery_stop_scanning));
+
+        deviceFoundProcessor.clear();
+        deviceFoundProcessor.start();
+
+        refreshDeviceList(false);
+
+        // Pre-add currently connected devices, as those will not trigger discovery events
+        // Paired devices that are not connected do not need to be added, as those will be discovered
+        try {
+            final Set<BluetoothDevice> pairedDevices = BluetoothAdapter.getDefaultAdapter().getBondedDevices();
+            for (final BluetoothDevice device : pairedDevices) {
+                try {
+                    final Method isConnectedMethod = device.getClass().getMethod("isConnected");
+                    final Boolean isConnected = (Boolean) isConnectedMethod.invoke(device);
+                    if (isConnected!= null && isConnected) {
+                        LOG.debug("Pre-adding already bonded device {}", device.getAddress());
+                        deviceFoundProcessor.scheduleProcessing(new GBScanEvent(device, (short) -1, null));
+                    }
+                } catch (final Exception e) {
+                    LOG.error("Failed to check whether {} is connected", device.getAddress());
+                }
+            }
+        } catch (final SecurityException e) {
+            LOG.error("Failed to pre-add paired devices", e);
+        }
+
+        try {
+            if (!ensureBluetoothReady()) {
+                toast(DiscoveryActivityV2.this, getString(R.string.discovery_enable_bluetooth), Toast.LENGTH_SHORT, GB.ERROR);
+                return false;
+            }
+
+            if (GB.supportsBluetoothLE()) {
+                startBTLEDiscovery();
+            }
+            startBTDiscovery();
+
+            bluetoothProgress.setVisibility(View.VISIBLE);
+        } catch (final SecurityException e) {
+            LOG.error("SecurityException on startDiscovery");
+            deviceFoundProcessor.stop();
+            return false;
+        }
+
+        setScanning(true);
+
+        return true;
+    }
+
+    private void stopDiscovery() {
+        LOG.info("Stopping discovery");
+        try {
+            stopBTDiscovery();
+            stopBLEDiscovery();
+        } catch (final SecurityException e) {
+            LOG.error("SecurityException on stopDiscovery");
+        }
+        setScanning(false);
+        deviceFoundProcessor.stop();
+        handler.removeMessages(0, stopRunnable);
+
+        // Refresh the device list one last time when finishing
+        refreshDeviceList(false);
+    }
+
+    public void setScanning(final boolean scanning) {
+        this.scanning = scanning;
+        if (scanning) {
+            startButton.setText(getString(R.string.discovery_stop_scanning));
+        } else {
+            startButton.setText(getString(R.string.discovery_start_scanning));
+            bluetoothProgress.setVisibility(View.GONE);
+        }
+    }
+
+    @RequiresPermission("android.permission.BLUETOOTH_SCAN")
+    private void startBTLEDiscovery() {
+        LOG.info("Starting BLE discovery");
+
+        handler.removeMessages(0, stopRunnable);
+        handler.sendMessageDelayed(getPostMessage(stopRunnable), SCAN_DURATION);
+
+        // Filters being non-null would be a very good idea with background scan, but in this case,
+        // not really required.
+        // TODO getScanFilters maybe
+        adapter.getBluetoothLeScanner().startScan(null, getScanSettings(), bleScanCallback);
+
+        LOG.debug("Bluetooth LE discovery started successfully");
+    }
+
+    @RequiresPermission("android.permission.BLUETOOTH_SCAN")
+    private void stopBLEDiscovery() {
+        if (adapter == null) {
+            return;
+        }
+
+        final BluetoothLeScanner bluetoothLeScanner = adapter.getBluetoothLeScanner();
+        if (bluetoothLeScanner == null) {
+            LOG.warn("Could not get BluetoothLeScanner()!");
+            return;
+        }
+
+        if (bleScanCallback == null) {
+            LOG.warn("newLeScanCallback == null!");
+            return;
+        }
+
+        try {
+            bluetoothLeScanner.stopScan(bleScanCallback);
+        } catch (final NullPointerException e) {
+            LOG.warn("Internal NullPointerException when stopping the scan!");
+            return;
+        }
+
+        LOG.debug("Stopped BLE discovery");
+    }
+
+    /**
+     * Starts a regular Bluetooth scan
+     */
+    @RequiresPermission("android.permission.BLUETOOTH_SCAN")
+    private void startBTDiscovery() {
+        LOG.info("Starting BT discovery");
+        try {
+            // LineageOS quirk, can't stop scan properly,
+            // if scan has been started by something else
+            stopBTDiscovery();
+        } catch (final Exception ignored) {
+        }
+        handler.removeMessages(0, stopRunnable);
+        handler.sendMessageDelayed(getPostMessage(stopRunnable), SCAN_DURATION);
+
+        if (adapter.startDiscovery()) {
+            LOG.debug("Discovery started successfully");
+        } else {
+            LOG.error("Discovery starting failed");
+        }
+    }
+
+    @RequiresPermission("android.permission.BLUETOOTH_SCAN")
+    private void stopBTDiscovery() {
+        if (adapter == null) return;
+        adapter.cancelDiscovery();
+        LOG.info("Stopped BT discovery");
+    }
+
+    private void bluetoothStateChanged(final int newState) {
+        if (newState == BluetoothAdapter.STATE_ON) {
+            this.adapter = BluetoothAdapter.getDefaultAdapter();
+            startButton.setEnabled(true);
+        } else {
+            this.adapter = null;
+            startButton.setEnabled(false);
+            bluetoothProgress.setVisibility(View.GONE);
+        }
+    }
+
+    private boolean checkBluetoothAvailable() {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+            if (ActivityCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.BLUETOOTH_SCAN) != PackageManager.PERMISSION_GRANTED) {
+                LOG.warn("No BLUETOOTH_SCAN permission");
+                this.adapter = null;
+                return false;
+            }
+            if (ActivityCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) {
+                LOG.warn("No BLUETOOTH_CONNECT permission");
+                this.adapter = null;
+                return false;
+            }
+        }
+
+        final BluetoothManager bluetoothService = (BluetoothManager) getSystemService(BLUETOOTH_SERVICE);
+        if (bluetoothService == null) {
+            LOG.warn("No bluetooth service available");
+            this.adapter = null;
+            return false;
+        }
+
+        final BluetoothAdapter adapter = bluetoothService.getAdapter();
+        if (adapter == null) {
+            LOG.warn("No bluetooth adapter available");
+            this.adapter = null;
+            return false;
+        }
+
+        if (!adapter.isEnabled()) {
+            LOG.warn("Bluetooth not enabled");
+            final Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
+            startActivity(enableBtIntent);
+            this.adapter = null;
+            return false;
+        }
+
+        this.adapter = adapter;
+        return true;
+    }
+
+    @RequiresPermission("android.permission.BLUETOOTH_SCAN")
+    private boolean ensureBluetoothReady() {
+        final boolean available = checkBluetoothAvailable();
+        startButton.setEnabled(available);
+
+        if (available) {
+            adapter.cancelDiscovery();
+            // must not return the result of cancelDiscovery()
+            // appears to return false when currently not scanning
+            return true;
+        }
+        return false;
+    }
+
+    private static ScanSettings getScanSettings() {
+        final ScanSettings.Builder builder = new ScanSettings.Builder()
+                .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY);
+
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+            builder.setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES);
+            builder.setMatchMode(ScanSettings.MATCH_MODE_AGGRESSIVE);
+            builder.setNumOfMatches(ScanSettings.MATCH_NUM_ONE_ADVERTISEMENT);
+        }
+
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+            builder.setPhy(ScanSettings.PHY_LE_ALL_SUPPORTED);
+        }
+
+        return builder.build();
+    }
+
+    private List<ScanFilter> getScanFilters() {
+        final List<ScanFilter> allFilters = new ArrayList<>();
+        for (DeviceType deviceType : DeviceType.values()) {
+            allFilters.addAll(deviceType.getDeviceCoordinator().createBLEScanFilters());
+        }
+        return allFilters;
+    }
+
+    private Message getPostMessage(final Runnable runnable) {
+        final Message message = Message.obtain(handler, runnable);
+        message.obj = runnable;
+        return message;
+    }
+
+    private void showWarnDialog(@StringRes final int message) {
+        new MaterialAlertDialogBuilder(getContext())
+                .setMessage(message)
+                .setPositiveButton(R.string.ok, (dialog, whichButton) -> {})
+                .show();
+    }
+
+    private void checkAndRequestLocationPermission() {
+        /* This is more or less a copy of what's in ControlCenterv2, but
+        we do this in case the permissions weren't requested since there
+        is no way we can scan without this stuff */
+        List<String> wantedPermissions = new ArrayList<>();
+        if (ActivityCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
+            LOG.error("No permission to access coarse location!");
+            wantedPermissions.add(Manifest.permission.ACCESS_COARSE_LOCATION);
+        }
+        if (ActivityCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
+            LOG.error("No permission to access fine location!");
+            wantedPermissions.add(Manifest.permission.ACCESS_FINE_LOCATION);
+        }
+        // if we need location permissions, request both together to avoid a bunch of dialogs
+        if (wantedPermissions.size() > 0) {
+            toast(DiscoveryActivityV2.this, getString(R.string.error_no_location_access), Toast.LENGTH_SHORT, GB.ERROR);
+            ActivityCompat.requestPermissions(this, wantedPermissions.toArray(new String[0]), 0);
+            wantedPermissions.clear();
+        }
+        // Now we have to request background location separately!
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+            if (ActivityCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.ACCESS_BACKGROUND_LOCATION) != PackageManager.PERMISSION_GRANTED) {
+                LOG.error("No permission to access background location!");
+                toast(DiscoveryActivityV2.this, getString(R.string.error_no_location_access), Toast.LENGTH_SHORT, GB.ERROR);
+                ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_BACKGROUND_LOCATION}, 0);
+            }
+        }
+        // Now, we can request Bluetooth permissions....
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+            if (ActivityCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.BLUETOOTH_SCAN) != PackageManager.PERMISSION_GRANTED) {
+                LOG.error("No permission to access Bluetooth scanning!");
+                toast(DiscoveryActivityV2.this, getString(R.string.error_no_bluetooth_scan), Toast.LENGTH_SHORT, GB.ERROR);
+                wantedPermissions.add(Manifest.permission.BLUETOOTH_SCAN);
+            }
+            if (ActivityCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) {
+                LOG.error("No permission to access Bluetooth connection!");
+                toast(DiscoveryActivityV2.this, getString(R.string.error_no_bluetooth_connect), Toast.LENGTH_SHORT, GB.ERROR);
+                wantedPermissions.add(Manifest.permission.BLUETOOTH_CONNECT);
+            }
+        }
+        if (wantedPermissions.size() > 0) {
+            GB.toast(this, getString(R.string.permission_granting_mandatory), Toast.LENGTH_LONG, GB.ERROR);
+            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
+                ActivityCompat.requestPermissions(this, wantedPermissions.toArray(new String[0]), 0);
+            } else {
+                ActivityResultLauncher<String[]> requestMultiplePermissionsLauncher =
+                        registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(), isGranted -> {
+                            if (!isGranted.containsValue(false)) {
+                                // Permission is granted. Continue the action or workflow in your app.
+                                // should we do startDiscovery here??
+                            } else {
+                                // Explain to the user that the feature is unavailable because the feature requires a permission that the user has denied.
+                                GB.toast(this, getString(R.string.permission_granting_mandatory), Toast.LENGTH_LONG, GB.ERROR);
+                            }
+                        });
+                requestMultiplePermissionsLauncher.launch(wantedPermissions.toArray(new String[0]));
+            }
+        }
+
+        LocationManager locationManager = (LocationManager) DiscoveryActivityV2.this.getSystemService(Context.LOCATION_SERVICE);
+        try {
+            if (locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER) || locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)) {
+                // Do nothing
+                LOG.debug("Some location provider is enabled, assuming location is enabled");
+            } else {
+                toast(DiscoveryActivityV2.this, getString(R.string.require_location_provider), Toast.LENGTH_LONG, GB.ERROR);
+                DiscoveryActivityV2.this.startActivity(new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS));
+                // We can't be sure location was enabled, cancel scan start and wait for new user action
+                toast(DiscoveryActivityV2.this, getString(R.string.error_location_enabled_mandatory), Toast.LENGTH_SHORT, GB.ERROR);
+                return;
+            }
+        } catch (final Exception ex) {
+            LOG.error("Exception when checking location status", ex);
+        }
+        LOG.info("Permissions seems to be fine for scanning");
+    }
+
+    @Override
+    public void onItemClick(final AdapterView<?> parent, final View view, final int position, final long id) {
+        final GBDeviceCandidate deviceCandidate = deviceCandidates.get(position);
+        if (deviceCandidate == null) {
+            LOG.error("Device candidate clicked, but item not found");
+            return;
+        }
+
+        DeviceType deviceType = DeviceHelper.getInstance().resolveDeviceType(deviceCandidate);
+
+        if (!deviceType.isSupported()) {
+            LOG.warn("Unsupported device candidate {}", deviceCandidate);
+            copyDetailsToClipboard(deviceCandidate);
+            return;
+        }
+
+        stopDiscovery();
+
+        final DeviceCoordinator coordinator = deviceType.getDeviceCoordinator();
+        LOG.info("Using device candidate {} with coordinator {}", deviceCandidate, coordinator.getClass());
+
+        if (coordinator.getBondingStyle() == DeviceCoordinator.BONDING_STYLE_REQUIRE_KEY) {
+            final SharedPreferences sharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(deviceCandidate.getMacAddress());
+
+            final String authKey = sharedPrefs.getString("authkey", null);
+            if (authKey == null || authKey.isEmpty()) {
+                showWarnDialog(R.string.discovery_need_to_enter_authkey);
+                return;
+            } else if (!coordinator.validateAuthKey(authKey)) {
+                showWarnDialog(R.string.discovery_entered_invalid_authkey);
+                return;
+            }
+        }
+
+        if (coordinator.suggestUnbindBeforePair() && deviceCandidate.isBonded()) {
+            new MaterialAlertDialogBuilder(getContext())
+                    .setTitle(R.string.unbind_before_pair_title)
+                    .setMessage(R.string.unbind_before_pair_message)
+                    .setIcon(R.drawable.ic_warning_gray)
+                    .setPositiveButton(R.string.ok, (dialog, whichButton) -> {
+                        startPair(deviceCandidate, coordinator);
+                    })
+                    .setNegativeButton(android.R.string.cancel, null)
+                    .show();
+        } else {
+            startPair(deviceCandidate, coordinator);
+        }
+    }
+
+    private void startPair(final GBDeviceCandidate deviceCandidate, final DeviceCoordinator coordinator) {
+        final Class<? extends Activity> pairingActivity = coordinator.getPairingActivity();
+        if (pairingActivity != null) {
+            final Intent intent = new Intent(this, pairingActivity);
+            intent.putExtra(DeviceCoordinator.EXTRA_DEVICE_CANDIDATE, deviceCandidate);
+            startActivity(intent);
+        } else {
+            if (coordinator.getBondingStyle() == DeviceCoordinator.BONDING_STYLE_NONE ||
+                    coordinator.getBondingStyle() == DeviceCoordinator.BONDING_STYLE_LAZY) {
+                LOG.info("No bonding needed, according to coordinator, so connecting right away");
+                BondingUtil.connectThenComplete(this, deviceCandidate);
+                return;
+            }
+
+            try {
+                this.deviceTarget = deviceCandidate;
+                BondingUtil.initiateCorrectBonding(this, deviceCandidate, coordinator);
+            } catch (final Exception e) {
+                LOG.error("Error pairing device {}", deviceCandidate.getMacAddress(), e);
+            }
+        }
+    }
+
+    private void copyDetailsToClipboard(final GBDeviceCandidate deviceCandidate) {
+        final List<String> deviceDetails = new ArrayList<>();
+        deviceDetails.add(deviceCandidate.getName());
+        deviceDetails.add(deviceCandidate.getMacAddress());
+        try {
+            for (final ParcelUuid uuid : deviceCandidate.getServiceUuids()) {
+                deviceDetails.add(uuid.getUuid().toString());
+            }
+        } catch (final Exception e) {
+            LOG.error("Error collecting device uuids", e);
+        }
+        final String clipboardData = TextUtils.join(", ", deviceDetails);
+        final ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
+        final ClipData clip = ClipData.newPlainText(deviceCandidate.getName(), clipboardData);
+        clipboard.setPrimaryClip(clip);
+        toast(this, "Device details copied to clipboard", Toast.LENGTH_SHORT, GB.INFO);
+    }
+
+    @Override
+    public boolean onItemLongClick(final AdapterView<?> adapterView, final View view, final int position, final long id) {
+        stopDiscovery();
+
+        final GBDeviceCandidate deviceCandidate = deviceCandidates.get(position);
+        if (deviceCandidate == null) {
+            LOG.error("Device candidate clicked, but item not found");
+            return true;
+        }
+
+        DeviceType deviceType = DeviceHelper.getInstance().resolveDeviceType(deviceCandidate);
+
+        if (!deviceType.isSupported()) {
+            showUnsupportedDeviceDialog(deviceCandidate);
+            return true;
+        }
+
+        final DeviceCoordinator coordinator = deviceType.getDeviceCoordinator();
+        final GBDevice device = DeviceHelper.getInstance().toSupportedDevice(deviceCandidate);
+        if (coordinator.getSupportedDeviceSpecificSettings(device) == null) {
+            return true;
+        }
+
+        final Intent startIntent;
+        startIntent = new Intent(this, DeviceSettingsActivity.class);
+        startIntent.putExtra(GBDevice.EXTRA_DEVICE, device);
+        if (coordinator.getBondingStyle() == DeviceCoordinator.BONDING_STYLE_REQUIRE_KEY) {
+            startIntent.putExtra(DeviceSettingsActivity.MENU_ENTRY_POINT, DeviceSettingsActivity.MENU_ENTRY_POINTS.AUTH_SETTINGS);
+        } else {
+            startIntent.putExtra(DeviceSettingsActivity.MENU_ENTRY_POINT, DeviceSettingsActivity.MENU_ENTRY_POINTS.DEVICE_SETTINGS);
+        }
+        startActivity(startIntent);
+        return true;
+    }
+
+    private void showUnsupportedDeviceDialog(final GBDeviceCandidate deviceCandidate) {
+        LOG.info("Unsupported device candidate selected: {}", deviceCandidate);
+
+        final Map<String, Pair<Long, Integer>> allDevices = DebugActivity.getAllSupportedDevices(getApplicationContext());
+
+        final LinearLayout linearLayout = new LinearLayout(DiscoveryActivityV2.this);
+        linearLayout.setOrientation(LinearLayout.VERTICAL);
+
+        final ArrayList<SpinnerWithIconItem> deviceListArray = new ArrayList<>();
+        for (Map.Entry<String, Pair<Long, Integer>> item : allDevices.entrySet()) {
+            deviceListArray.add(new SpinnerWithIconItem(item.getKey(), item.getValue().first, item.getValue().second));
+        }
+        final SpinnerWithIconAdapter deviceListAdapter = new SpinnerWithIconAdapter(
+                DiscoveryActivityV2.this,
+                R.layout.spinner_with_image_layout,
+                R.id.spinner_item_text,
+                deviceListArray
+        );
+
+        final Spinner deviceListSpinner = new Spinner(DiscoveryActivityV2.this);
+        deviceListSpinner.setAdapter(deviceListAdapter);
+        deviceListSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
+            @Override
+            public void onItemSelected(final AdapterView<?> parent, final View view, final int pos, final long id) {
+                final SpinnerWithIconItem selectedItem = (SpinnerWithIconItem) parent.getItemAtPosition(pos);
+                selectedUnsupportedDeviceKey = selectedItem.getId();
+            }
+
+            @Override
+            public void onNothingSelected(final AdapterView<?> arg0) {
+            }
+        });
+        linearLayout.addView(deviceListSpinner);
+
+        final LinearLayout macLayout = new LinearLayout(DiscoveryActivityV2.this);
+        macLayout.setOrientation(LinearLayout.HORIZONTAL);
+        macLayout.setPadding(20, 0, 20, 0);
+        linearLayout.addView(macLayout);
+
+        new MaterialAlertDialogBuilder(DiscoveryActivityV2.this)
+                .setCancelable(true)
+                .setTitle(R.string.add_test_device)
+                .setView(linearLayout)
+                .setPositiveButton(R.string.ok, (dialog, which) -> {
+                    if (selectedUnsupportedDeviceKey != DebugActivity.SELECT_DEVICE) {
+                        DebugActivity.createTestDevice(
+                                DiscoveryActivityV2.this,
+                                selectedUnsupportedDeviceKey,
+                                deviceCandidate.getMacAddress(),
+                                deviceCandidate.getName()
+                        );
+                        finish();
+                    }
+                })
+                .setNegativeButton(R.string.Cancel, (dialog, which) -> {
+                })
+                .show();
+    }
+
+    @Override
+    public void onBondingComplete(final boolean success) {
+        finish();
+    }
+
+    @Override
+    public GBDeviceCandidate getCurrentTarget() {
+        return this.deviceTarget;
+    }
+
+    @Override
+    public String getMacAddress() {
+        return deviceTarget.getDevice().getAddress();
+    }
+
+    @Override
+    public boolean getAttemptToConnect() {
+        return true;
+    }
+
+    @Override
+    public void registerBroadcastReceivers() {
+        final IntentFilter bluetoothIntents = new IntentFilter();
+        bluetoothIntents.addAction(BluetoothDevice.ACTION_FOUND);
+        bluetoothIntents.addAction(BluetoothDevice.ACTION_UUID);
+        bluetoothIntents.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
+        bluetoothIntents.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED);
+        bluetoothIntents.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
+
+        ContextCompat.registerReceiver(this, bluetoothReceiver, bluetoothIntents, ContextCompat.RECEIVER_EXPORTED);
+    }
+
+    @Override
+    public void unregisterBroadcastReceivers() {
+        AndroidUtils.safeUnregisterBroadcastReceiver(this, bluetoothReceiver);
+    }
+
+    @Override
+    public Context getContext() {
+        return this;
+    }
+
+    private void loadSettings() {
+        final Prefs prefs = GBApplication.getPrefs();
+        deviceFoundProcessor.setDiscoverUnsupported(prefs.getBoolean("discover_unsupported_devices", false));
+    }
+
+    @Override
+    public void onDeviceChanged() {
+        refreshDeviceList(true);
+    }
+
+    @Override
+    public void onCreateMenu(@NonNull final Menu menu, @NonNull final MenuInflater menuInflater) {
+        menuInflater.inflate(R.menu.menu_discovery, menu);
+    }
+
+    @Override
+    public boolean onMenuItemSelected(@NonNull final MenuItem menuItem) {
+        final int itemId = menuItem.getItemId();
+        if (itemId == R.id.prefs_discovery_pairing) {
+            final Intent intent = new Intent(DiscoveryActivityV2.this, DiscoveryPairingPreferenceActivity.class);
+            startActivity(intent);
+            return true;
+        }
+        return false;
+    }
+
+    private final class BluetoothReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(final Context context, final Intent intent) {
+            switch (Objects.requireNonNull(intent.getAction())) {
+                case BluetoothAdapter.ACTION_DISCOVERY_STARTED: {
+                    LOG.debug("ACTION_DISCOVERY_STARTED");
+                    break;
+                }
+                case BluetoothAdapter.ACTION_STATE_CHANGED: {
+                    LOG.debug("ACTION_STATE_CHANGED ");
+                    bluetoothStateChanged(intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.STATE_OFF));
+                    break;
+                }
+                case BluetoothDevice.ACTION_FOUND: {
+                    final BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+                    if (device == null) {
+                        LOG.warn("ACTION_FOUND with null device");
+                        return;
+                    }
+                    LOG.debug("ACTION_FOUND {}", device.getAddress());
+                    final short rssi = intent.getShortExtra(BluetoothDevice.EXTRA_RSSI, GBDevice.RSSI_UNKNOWN);
+                    deviceFoundProcessor.scheduleProcessing(new GBScanEvent(device, rssi, null));
+                    break;
+                }
+                case BluetoothDevice.ACTION_UUID: {
+                    final BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+                    if (device == null) {
+                        LOG.warn("ACTION_UUID with null device");
+                        return;
+                    }
+                    LOG.debug("ACTION_UUID {}", device.getAddress());
+                    final short rssi = intent.getShortExtra(BluetoothDevice.EXTRA_RSSI, GBDevice.RSSI_UNKNOWN);
+                    final Parcelable[] uuids = intent.getParcelableArrayExtra(BluetoothDevice.EXTRA_UUID);
+                    final ParcelUuid[] uuids2 = AndroidUtils.toParcelUuids(uuids);
+                    deviceFoundProcessor.scheduleProcessing(new GBScanEvent(device, rssi, uuids2));
+                    break;
+                }
+                case BluetoothDevice.ACTION_BOND_STATE_CHANGED: {
+                    LOG.debug("ACTION_BOND_STATE_CHANGED");
+                    final BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+                    if (device != null) {
+                        final int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.BOND_NONE);
+                        LOG.debug("Bond state: {}", bondState);
+
+                        if (bondState == BluetoothDevice.BOND_BONDED) {
+                            BondingUtil.handleDeviceBonded((BondingInterface) context, getCandidateFromMAC(device));
+                        }
+                    }
+                    break;
+                }
+            }
+        }
+    }
+
+    private final class BleScanCallback extends ScanCallback {
+        @Override
+        public void onScanResult(final int callbackType, final ScanResult result) {
+            super.onScanResult(callbackType, result);
+            try {
+                final ScanRecord scanRecord = result.getScanRecord();
+                ParcelUuid[] uuids = null;
+                if (scanRecord != null) {
+                    final List<ParcelUuid> serviceUuids = scanRecord.getServiceUuids();
+                    if (serviceUuids != null) {
+                        uuids = serviceUuids.toArray(new ParcelUuid[0]);
+                    }
+                }
+                final BluetoothDevice device = result.getDevice();
+                final short rssi = (short) result.getRssi();
+                LOG.debug("BLE result: {}, {}, {}", device.getAddress(), ((scanRecord != null) ? scanRecord.getBytes().length : -1), rssi);
+                deviceFoundProcessor.scheduleProcessing(new GBScanEvent(device, rssi, uuids));
+            } catch (final Exception e) {
+                LOG.warn("Error handling BLE scan result", e);
+            }
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/discovery/DiscoveryPairingPreferenceActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/discovery/DiscoveryPairingPreferenceActivity.java
new file mode 100644
index 0000000..11c933f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/discovery/DiscoveryPairingPreferenceActivity.java
@@ -0,0 +1,54 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities.discovery;
+
+import android.os.Bundle;
+import android.widget.Toast;
+
+import androidx.preference.PreferenceFragmentCompat;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.AbstractPreferenceFragment;
+import nodomain.freeyourgadget.gadgetbridge.activities.AbstractSettingsActivityV2;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class DiscoveryPairingPreferenceActivity extends AbstractSettingsActivityV2 {
+    @Override
+    protected String fragmentTag() {
+        return DiscoveryPairingPreferenceFragment.FRAGMENT_TAG;
+    }
+
+    @Override
+    protected PreferenceFragmentCompat newFragment() {
+        return new DiscoveryPairingPreferenceFragment();
+    }
+
+    public static class DiscoveryPairingPreferenceFragment extends AbstractPreferenceFragment {
+        static final String FRAGMENT_TAG = "DISCOVERY_PAIRING_PREFERENCES_FRAGMENT";
+
+        @Override
+        public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) {
+            setPreferencesFromResource(R.xml.discovery_pairing_preferences, rootKey);
+
+            findPreference("prefs_general_key_auto_reconnect_scan").setOnPreferenceChangeListener((preference, newValue) -> {
+                GB.toast(GBApplication.getContext().getString(R.string.prompt_restart_gadgetbridge), Toast.LENGTH_LONG, GB.INFO);
+                return true;
+            });
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/discovery/GBScanEvent.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/discovery/GBScanEvent.java
new file mode 100644
index 0000000..c77e55e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/discovery/GBScanEvent.java
@@ -0,0 +1,53 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities.discovery;
+
+import android.bluetooth.BluetoothDevice;
+import android.os.ParcelUuid;
+
+import androidx.annotation.Nullable;
+
+/**
+ * A scan event originating from either BT or BLE scan. References the BluetoothDevice, rssi,
+ * and service UUIDs, if any.
+ */
+public class GBScanEvent {
+    private final BluetoothDevice device;
+    private final short rssi;
+
+    @Nullable
+    private final ParcelUuid[] serviceUuids;
+
+    public GBScanEvent(final BluetoothDevice device, final short rssi, @Nullable final ParcelUuid[] serviceUuids) {
+        this.device = device;
+        this.rssi = rssi;
+        this.serviceUuids = serviceUuids;
+    }
+
+    public BluetoothDevice getDevice() {
+        return device;
+    }
+
+    public short getRssi() {
+        return rssi;
+    }
+
+    @Nullable
+    public ParcelUuid[] getServiceUuids() {
+        return serviceUuids;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/discovery/GBScanEventProcessor.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/discovery/GBScanEventProcessor.java
new file mode 100644
index 0000000..220c03b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/discovery/GBScanEventProcessor.java
@@ -0,0 +1,262 @@
+/*  Copyright (C) 2023-2024 Daniel Dakhno, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities.discovery;
+
+import android.os.ParcelUuid;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
+
+/**
+ * A dedicated thread to process {@link GBScanEvent}s. This class keeps a map from mac address to
+ * GBDeviceCandidate, with the current known state of each candidate.
+ * <p>
+ * Processing works as follows:
+ * - The processor consumes mac addresses from the eventsToProcessQueue
+ * - Mac addresses are placed on the queue when there are one or more new GBScanEvents to process in
+ *   the eventsToProcessMap map
+ * - The eventsToProcessMap contains a list of events per device, so that they can be processed in batch
+ * - The GBDeviceEvent for the corresponding mac address in candidatesByAddress gets updated with the new
+ *   information, and matched against the coordinators.
+ */
+public final class GBScanEventProcessor implements Runnable {
+    private static final Logger LOG = LoggerFactory.getLogger(GBScanEventProcessor.class);
+
+    private static final ParcelUuid ZERO_UUID = ParcelUuid.fromString("00000000-0000-0000-0000-000000000000");
+
+    private final Map<String, GBDeviceCandidate> candidatesByAddress = new LinkedHashMap<>();
+
+    private final BlockingQueue<String> eventsToProcessQueue = new LinkedBlockingQueue<>();
+    private final Map<String, List<GBScanEvent>> eventsToProcessMap = new HashMap<>();
+
+    private boolean discoverUnsupported = false;
+
+    private volatile boolean running = false;
+    private Thread thread = null;
+
+    private final Callback callback;
+
+    public GBScanEventProcessor(final Callback callback) {
+        this.callback = callback;
+    }
+
+    @Override
+    public void run() {
+        LOG.info("Device Found Processor Thread started.");
+
+        while (running) {
+            try {
+                LOG.debug("Polling found devices queue, current size = {}", eventsToProcessQueue.size());
+                final String candidateAddress = eventsToProcessQueue.take();
+                if (candidateAddress != null) {
+                    if (processAllScanEvents(candidateAddress)) {
+                        callback.onDeviceChanged();
+                    }
+                }
+            } catch (final InterruptedException e) {
+                LOG.warn("Processing thread interrupted");
+                Thread.currentThread().interrupt();
+                break;
+            }
+        }
+    }
+
+    public void start() {
+        if (running) {
+            LOG.warn("Already running!");
+            return;
+        }
+
+        running = true;
+        thread = new Thread("Gadgetbridge Device Found Processor Thread") {
+            @Override
+            public void run() {
+                GBScanEventProcessor.this.run();
+            }
+        };
+        thread.start();
+    }
+
+    public void stop() {
+        running = false;
+
+        if (thread != null) {
+            thread.interrupt();
+            thread = null;
+        }
+    }
+
+    public void clear() {
+        candidatesByAddress.clear();
+        eventsToProcessMap.clear();
+        eventsToProcessQueue.clear();
+    }
+
+    public void setDiscoverUnsupported(boolean discoverUnsupported) {
+        this.discoverUnsupported = discoverUnsupported;
+    }
+
+    /**
+     * Returns the current list of GBDeviceCandidates. The candidates are cloned, since they can be
+     * modified concurrently by the processor.
+     */
+    public List<GBDeviceCandidate> getDevices() {
+        final List<GBDeviceCandidate> ret = new ArrayList<>();
+        // candidatesByAddress keeps insertion order, so newer devices will be at the end
+        synchronized (candidatesByAddress) {
+            for (final Map.Entry<String, GBDeviceCandidate> entry : candidatesByAddress.entrySet()) {
+                ret.add(entry.getValue().clone());
+            }
+        }
+        return ret;
+    }
+
+    /**
+     * Schedule a {@link GBScanEvent} to be processed asynchronously.
+     */
+    public void scheduleProcessing(final GBScanEvent event) {
+        LOG.debug("Scheduling {} for processing ({})", event.getDevice().getAddress(), event.getServiceUuids());
+
+        final String address = event.getDevice().getAddress();
+        synchronized (eventsToProcessMap) {
+            if (!eventsToProcessMap.containsKey(address)) {
+                eventsToProcessMap.put(address, new LinkedList<>());
+            }
+            Objects.requireNonNull(eventsToProcessMap.get(address)).add(event);
+        }
+
+        try {
+            eventsToProcessQueue.put(address);
+        } catch (final InterruptedException e) {
+            LOG.error("Failed to put device on processing queue", e);
+        }
+    }
+
+    private boolean processCandidate(final GBDeviceCandidate candidate) {
+        LOG.debug("found device: {}, {}", candidate.getName(), candidate.getMacAddress());
+        if (LOG.isDebugEnabled()) {
+            final ParcelUuid[] uuids = candidate.getServiceUuids();
+            if (uuids != null && uuids.length > 0) {
+                for (ParcelUuid uuid : uuids) {
+                    LOG.debug("  supports uuid: " + uuid.toString());
+                }
+            }
+        }
+
+        final DeviceType deviceType = DeviceHelper.getInstance().resolveDeviceType(candidate, false);
+
+        if (deviceType.isSupported() || discoverUnsupported) {
+            synchronized (candidatesByAddress) {
+                candidatesByAddress.put(candidate.getMacAddress(), candidate);
+            }
+        }
+
+        return deviceType.isSupported();
+    }
+
+    private boolean processAllScanEvents(final String address) {
+        final List<GBScanEvent> events;
+        synchronized (eventsToProcessMap) {
+            events = eventsToProcessMap.remove(address);
+        }
+        if (events == null || events.isEmpty()) {
+            LOG.warn("Attempted to process {}, but found no events", address);
+            return false;
+        }
+
+        LOG.debug("Processing {} events for {}", events.size(), address);
+
+        GBDeviceCandidate candidate = candidatesByAddress.get(address);
+
+        String previousName = null;
+        ParcelUuid[] previousUuids = null;
+        boolean firstTime = false;
+
+        if (candidate == null) {
+            // First time we see this device
+            LOG.debug("Found {} for the first time", address);
+            firstTime = true;
+            final GBScanEvent firstEvent = events.get(0);
+            events.remove(0);
+            candidate = new GBDeviceCandidate(firstEvent.getDevice(), firstEvent.getRssi(), firstEvent.getServiceUuids());
+        } else {
+            previousName = candidate.getName();
+            previousUuids = candidate.getServiceUuids();
+        }
+
+        // Update the device with the remaining events
+        for (final GBScanEvent event : events) {
+            candidate.setRssi(event.getRssi());
+            candidate.addUuids(event.getServiceUuids());
+        }
+
+        candidate.refreshNameIfUnknown();
+        try {
+            candidate.addUuids(candidate.getDevice().getUuids());
+        } catch (final SecurityException e) {
+            LOG.error("SecurityException on candidate.getDevice().getUuids()");
+        }
+
+        if (!firstTime) {
+            if (Objects.equals(candidate.getName(), previousName) && Arrays.equals(candidate.getServiceUuids(), previousUuids)) {
+                // Neither name nor uuids changed, do not reprocess
+                LOG.trace("Not reprocessing {} due to no changes", address);
+                return false;
+            }
+        }
+
+        if (processCandidate(candidate)) {
+            LOG.info(
+                    "Device {} ({}) is supported as '{}' without scanning services",
+                    candidate.getDevice(),
+                    candidate.getName(),
+                    DeviceHelper.getInstance().resolveDeviceType(candidate, false)
+            );
+            return true;
+        }
+
+        if (candidate.getServiceUuids().length == 0 || (candidate.getServiceUuids().length == 1 && candidate.getServiceUuids()[0].equals(ZERO_UUID))) {
+            LOG.debug("Fetching uuids for {} with sdp", candidate.getDevice().getAddress());
+            try {
+                candidate.getDevice().fetchUuidsWithSdp();
+            } catch (final SecurityException e) {
+                LOG.error("SecurityException on candidate.getDevice().fetchUuidsWithSdp()");
+            }
+        }
+
+        return true;
+    }
+
+    public interface Callback {
+        void onDeviceChanged();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/files/FileManagerActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/files/FileManagerActivity.java
new file mode 100644
index 0000000..517dfd3
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/files/FileManagerActivity.java
@@ -0,0 +1,135 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities.files;
+
+import android.os.Bundle;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.Toast;
+
+import androidx.annotation.NonNull;
+import androidx.appcompat.app.ActionBar;
+import androidx.appcompat.widget.SearchView;
+import androidx.core.view.MenuProvider;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBActivity;
+import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class FileManagerActivity extends AbstractGBActivity implements MenuProvider {
+    private static final Logger LOG = LoggerFactory.getLogger(FileManagerActivity.class);
+
+    public static final String EXTRA_PATH = "path";
+
+    private SearchView searchView;
+
+    @Override
+    protected void onCreate(final Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_file_manager);
+        addMenuProvider(this);
+
+        final RecyclerView fileListView = findViewById(R.id.fileListView);
+        fileListView.setLayoutManager(new LinearLayoutManager(this));
+
+        final File directory;
+        if (getIntent().hasExtra(EXTRA_PATH)) {
+            directory = new File(getIntent().getStringExtra(EXTRA_PATH));
+        } else {
+            try {
+                directory = FileUtils.getExternalFilesDir();
+            } catch (final IOException e) {
+                GB.toast("Failed to list external files dir", Toast.LENGTH_LONG, GB.ERROR);
+                LOG.error("Failed to list external files dir", e);
+                finish();
+                return;
+            }
+        }
+
+        if (!directory.isDirectory()) {
+            GB.toast("Not a directory", Toast.LENGTH_LONG, GB.ERROR);
+            finish();
+            return;
+        }
+
+        final ActionBar actionBar = getSupportActionBar();
+        if (actionBar != null) {
+            actionBar.setTitle(directory.getName());
+        }
+
+        final FileManagerAdapter appListAdapter = new FileManagerAdapter(this, directory);
+
+        fileListView.setAdapter(appListAdapter);
+
+        searchView = findViewById(R.id.fileListSearchView);
+        searchView.setIconifiedByDefault(false);
+        searchView.setVisibility(View.GONE);
+        searchView.setIconified(false);
+        searchView.setQuery("", false);
+        searchView.clearFocus();
+        searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
+            @Override
+            public boolean onQueryTextSubmit(final String query) {
+                return false;
+            }
+
+            @Override
+            public boolean onQueryTextChange(final String newText) {
+                appListAdapter.getFilter().filter(newText);
+                return true;
+            }
+        });
+    }
+
+    @Override
+    public void onCreateMenu(@NonNull final Menu menu, @NonNull final MenuInflater menuInflater) {
+        menuInflater.inflate(R.menu.menu_file_manager, menu);
+    }
+
+    @Override
+    public boolean onMenuItemSelected(@NonNull final MenuItem menuItem) {
+        final int itemId = menuItem.getItemId();
+        if (itemId == R.id.file_manager_search) {
+            searchView.setVisibility(View.VISIBLE);
+            searchView.requestFocus();
+            searchView.setIconified(true);
+            searchView.setIconified(false);
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(final 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/files/FileManagerAdapter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/files/FileManagerAdapter.java
new file mode 100644
index 0000000..1f78997
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/files/FileManagerAdapter.java
@@ -0,0 +1,206 @@
+/*  Copyright (C) 2023-2024 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 <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities.files;
+
+import android.content.Context;
+import android.content.Intent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Filter;
+import android.widget.ImageView;
+import android.widget.PopupMenu;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.annotation.NonNull;
+import androidx.appcompat.content.res.AppCompatResources;
+import androidx.recyclerview.widget.RecyclerView;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.IOException;
+import java.text.DecimalFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.util.AndroidUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class FileManagerAdapter extends RecyclerView.Adapter<FileManagerAdapter.FileManagerViewHolder> {
+    protected static final Logger LOG = LoggerFactory.getLogger(FileManagerAdapter.class);
+
+    private final List<File> fileList;
+    private final Context mContext;
+
+    private final FileFilter fileFilter;
+
+    public FileManagerAdapter(final Context context, final File directory) {
+        mContext = context;
+
+        // FIXME: This can be slow, make it async
+        fileList = new ArrayList<>(Arrays.asList(directory.listFiles()));
+        fileList.sort((f1, f2) -> {
+            if (f1.isDirectory() && f2.isFile())
+                return -1;
+            if (f1.isFile() && f2.isDirectory())
+                return 1;
+
+            return String.CASE_INSENSITIVE_ORDER.compare(f1.getName(), f2.getName());
+        });
+
+        fileFilter = new FileFilter(this, fileList);
+    }
+
+    @NonNull
+    @Override
+    public FileManagerViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) {
+        final View view = LayoutInflater.from(mContext).inflate(R.layout.item_file_manager, parent, false);
+        return new FileManagerViewHolder(view);
+    }
+
+    @Override
+    public void onBindViewHolder(final FileManagerViewHolder holder, int position) {
+        final File file = fileList.get(position);
+
+        holder.name.setText(file.getName());
+        if (file.isDirectory()) {
+            holder.icon.setImageDrawable(AppCompatResources.getDrawable(mContext, R.drawable.ic_folder));
+            holder.description.setVisibility(View.GONE);
+            holder.menu.setVisibility(View.GONE);
+        } else {
+            holder.icon.setImageDrawable(AppCompatResources.getDrawable(mContext, R.drawable.ic_file_open));
+            holder.description.setText(formatFileSize(file.length()));
+            holder.description.setVisibility(View.VISIBLE);
+            holder.menu.setVisibility(View.VISIBLE);
+            holder.menu.setOnClickListener(view -> {
+                final PopupMenu menu = new PopupMenu(mContext, holder.menu);
+                menu.inflate(R.menu.file_manager_file);
+                menu.setOnMenuItemClickListener(item -> {
+                    final int itemId = item.getItemId();
+                    if (itemId == R.id.file_manager_file_menu_share) {
+                        try {
+                            AndroidUtils.shareFile(mContext, file, "*/*");
+                        } catch (final IOException e) {
+                            GB.toast("Failed to share file", Toast.LENGTH_LONG, GB.ERROR, e);
+                        }
+                        return true;
+                    }
+
+                    return false;
+                });
+                menu.show();
+            });
+        }
+
+        holder.itemView.setOnClickListener(v -> {
+            if (file.isDirectory()) {
+                final Intent fileManagerIntent = new Intent(mContext, FileManagerActivity.class);
+                fileManagerIntent.putExtra(FileManagerActivity.EXTRA_PATH, file.getPath());
+                mContext.startActivity(fileManagerIntent);
+            } else {
+                try {
+                    AndroidUtils.viewFile(file.getAbsolutePath(), "*/*", mContext);
+                } catch (final IOException e) {
+                    GB.toast("Failed to open file", Toast.LENGTH_LONG, GB.ERROR, e);
+                }
+            }
+        });
+    }
+
+    @Override
+    public int getItemCount() {
+        return fileList.size();
+    }
+
+    public Filter getFilter() {
+        return fileFilter;
+    }
+
+    public static class FileManagerViewHolder extends RecyclerView.ViewHolder {
+        final ImageView icon;
+        final TextView name;
+        final TextView description;
+        final ImageView menu;
+
+        FileManagerViewHolder(View itemView) {
+            super(itemView);
+
+            icon = itemView.findViewById(R.id.file_icon);
+            name = itemView.findViewById(R.id.file_name);
+            description = itemView.findViewById(R.id.file_description);
+            menu = itemView.findViewById(R.id.file_menu);
+        }
+    }
+
+    private static final DecimalFormat SIZE_FORMAT = new DecimalFormat("#,##0.#");
+
+    public static String formatFileSize(final long size) {
+        if (size <= 0) return "0";
+        final String[] units = new String[]{"B", "kB", "MB", "GB", "TB", "PB", "EB"};
+        int digitGroups = (int) (Math.log10(size) / Math.log10(1024));
+        return SIZE_FORMAT.format(size / Math.pow(1024, digitGroups)) + " " + units[digitGroups];
+    }
+
+    private class FileFilter extends Filter {
+        private final FileManagerAdapter adapter;
+        private final List<File> originalList;
+        private final List<File> filteredList;
+
+        private FileFilter(final FileManagerAdapter adapter, final List<File> originalList) {
+            super();
+            this.originalList = new ArrayList<>(originalList);
+            this.filteredList = new ArrayList<>();
+            this.adapter = adapter;
+        }
+
+
+        @Override
+        protected FilterResults performFiltering(final 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 (File f : originalList) {
+                    final CharSequence name = f.getName();
+                    if (name.toString().toLowerCase().contains(filterPattern)) {
+                        filteredList.add(f);
+                    }
+                }
+            }
+
+            results.values = filteredList;
+            results.count = filteredList.size();
+            return results;
+        }
+
+        @Override
+        protected void publishResults(final CharSequence constraint, final FilterResults filterResults) {
+            adapter.fileList.clear();
+            adapter.fileList.addAll((List<File>) filterResults.values);
+            adapter.notifyDataSetChanged();
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/loyaltycards/LoyaltyCardsSettingsActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/loyaltycards/LoyaltyCardsSettingsActivity.java
new file mode 100644
index 0000000..4b84a45
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/loyaltycards/LoyaltyCardsSettingsActivity.java
@@ -0,0 +1,87 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities.loyaltycards;
+
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.view.MenuItem;
+
+import androidx.annotation.NonNull;
+import androidx.core.app.ActivityCompat;
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentManager;
+import androidx.preference.PreferenceFragmentCompat;
+import androidx.preference.PreferenceScreen;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBActivity;
+import nodomain.freeyourgadget.gadgetbridge.activities.AbstractSettingsActivityV2;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+
+public class LoyaltyCardsSettingsActivity extends AbstractSettingsActivityV2 implements
+        PreferenceFragmentCompat.OnPreferenceStartScreenCallback,
+        ActivityCompat.OnRequestPermissionsResultCallback {
+
+    public static final int PERMISSION_REQUEST_CODE = 0;
+
+    private GBDevice device;
+
+    @Override
+    protected String fragmentTag() {
+        return LoyaltyCardsSettingsFragment.FRAGMENT_TAG;
+    }
+
+    @Override
+    protected PreferenceFragmentCompat newFragment() {
+        return LoyaltyCardsSettingsFragment.newInstance(device);
+    }
+
+    @Override
+    protected void onCreate(final Bundle savedInstanceState) {
+        device = getIntent().getParcelableExtra(GBDevice.EXTRA_DEVICE);
+
+        super.onCreate(savedInstanceState);
+    }
+
+    @Override
+    public void onRequestPermissionsResult(final int requestCode,
+                                           @NonNull final String[] permissions,
+                                           @NonNull final int[] grantResults) {
+        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
+
+        if (requestCode != PERMISSION_REQUEST_CODE) {
+            return;
+        }
+
+        if (grantResults.length == 0) {
+            return;
+        }
+
+        if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+            final FragmentManager fragmentManager = getSupportFragmentManager();
+            final Fragment fragment = fragmentManager.findFragmentByTag(LoyaltyCardsSettingsFragment.FRAGMENT_TAG);
+            if (fragment == null) {
+                return;
+            }
+
+            if (fragment instanceof LoyaltyCardsSettingsFragment) {
+                ((LoyaltyCardsSettingsFragment) fragment).reloadPreferences(null);
+            }
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/loyaltycards/LoyaltyCardsSettingsConst.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/loyaltycards/LoyaltyCardsSettingsConst.java
new file mode 100644
index 0000000..42bd0e2
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/loyaltycards/LoyaltyCardsSettingsConst.java
@@ -0,0 +1,37 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities.loyaltycards;
+
+public final class LoyaltyCardsSettingsConst {
+    public static final String PREF_KEY_LOYALTY_CARDS = "pref_key_loyalty_cards";
+
+    public static final String PREF_KEY_HEADER_LOYALTY_CARDS_CATIMA = "pref_key_header_loyalty_cards_catima";
+    public static final String PREF_KEY_HEADER_LOYALTY_CARDS_SYNC = "pref_key_header_loyalty_cards_sync";
+    public static final String PREF_KEY_HEADER_LOYALTY_CARDS_SYNC_OPTIONS = "pref_key_header_loyalty_cards_sync_options";
+
+    public static final String LOYALTY_CARDS_CATIMA_PACKAGE = "loyalty_cards_catima_package";
+    public static final String LOYALTY_CARDS_OPEN_CATIMA = "loyalty_cards_open_catima";
+    public static final String LOYALTY_CARDS_CATIMA_NOT_INSTALLED = "loyalty_cards_catima_not_installed";
+    public static final String LOYALTY_CARDS_CATIMA_NOT_COMPATIBLE = "loyalty_cards_catima_not_compatible";
+    public static final String LOYALTY_CARDS_INSTALL_CATIMA = "loyalty_cards_install_catima";
+    public static final String LOYALTY_CARDS_CATIMA_PERMISSIONS = "loyalty_cards_catima_permissions";
+    public static final String LOYALTY_CARDS_SYNC = "loyalty_cards_sync";
+    public static final String LOYALTY_CARDS_SYNC_GROUPS_ONLY = "loyalty_cards_sync_groups_only";
+    public static final String LOYALTY_CARDS_SYNC_GROUPS = "loyalty_cards_sync_groups";
+    public static final String LOYALTY_CARDS_SYNC_ARCHIVED = "loyalty_cards_sync_archived";
+    public static final String LOYALTY_CARDS_SYNC_STARRED = "loyalty_cards_sync_starred";
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/loyaltycards/LoyaltyCardsSettingsFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/loyaltycards/LoyaltyCardsSettingsFragment.java
new file mode 100644
index 0000000..87659ae
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/loyaltycards/LoyaltyCardsSettingsFragment.java
@@ -0,0 +1,260 @@
+/*  Copyright (C) 2023-2024 Andreas Shimokawa, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities.loyaltycards;
+
+import static nodomain.freeyourgadget.gadgetbridge.activities.loyaltycards.LoyaltyCardsSettingsConst.LOYALTY_CARDS_CATIMA_NOT_COMPATIBLE;
+import static nodomain.freeyourgadget.gadgetbridge.activities.loyaltycards.LoyaltyCardsSettingsConst.LOYALTY_CARDS_CATIMA_NOT_INSTALLED;
+import static nodomain.freeyourgadget.gadgetbridge.activities.loyaltycards.LoyaltyCardsSettingsConst.LOYALTY_CARDS_CATIMA_PACKAGE;
+import static nodomain.freeyourgadget.gadgetbridge.activities.loyaltycards.LoyaltyCardsSettingsConst.LOYALTY_CARDS_CATIMA_PERMISSIONS;
+import static nodomain.freeyourgadget.gadgetbridge.activities.loyaltycards.LoyaltyCardsSettingsConst.LOYALTY_CARDS_INSTALL_CATIMA;
+import static nodomain.freeyourgadget.gadgetbridge.activities.loyaltycards.LoyaltyCardsSettingsConst.LOYALTY_CARDS_OPEN_CATIMA;
+import static nodomain.freeyourgadget.gadgetbridge.activities.loyaltycards.LoyaltyCardsSettingsConst.LOYALTY_CARDS_SYNC;
+import static nodomain.freeyourgadget.gadgetbridge.activities.loyaltycards.LoyaltyCardsSettingsConst.LOYALTY_CARDS_SYNC_GROUPS;
+import static nodomain.freeyourgadget.gadgetbridge.activities.loyaltycards.LoyaltyCardsSettingsConst.PREF_KEY_HEADER_LOYALTY_CARDS_CATIMA;
+import static nodomain.freeyourgadget.gadgetbridge.activities.loyaltycards.LoyaltyCardsSettingsConst.PREF_KEY_HEADER_LOYALTY_CARDS_SYNC;
+import static nodomain.freeyourgadget.gadgetbridge.activities.loyaltycards.LoyaltyCardsSettingsConst.PREF_KEY_HEADER_LOYALTY_CARDS_SYNC_OPTIONS;
+
+import android.content.ActivityNotFoundException;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.widget.Toast;
+
+import androidx.core.app.ActivityCompat;
+import androidx.core.content.ContextCompat;
+import androidx.preference.ListPreference;
+import androidx.preference.MultiSelectListPreference;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceCategory;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.AbstractPreferenceFragment;
+import nodomain.freeyourgadget.gadgetbridge.capabilities.loyaltycards.CatimaContentProvider;
+import nodomain.freeyourgadget.gadgetbridge.capabilities.loyaltycards.CatimaManager;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
+
+public class LoyaltyCardsSettingsFragment extends AbstractPreferenceFragment {
+    private static final Logger LOG = LoggerFactory.getLogger(LoyaltyCardsSettingsFragment.class);
+
+    static final String FRAGMENT_TAG = "LOYALTY_CARDS_SETTINGS_FRAGMENT";
+
+    private GBDevice device;
+
+    private void setSettingsFileSuffix(final String settingsFileSuffix) {
+        final Bundle args = new Bundle();
+        args.putString("settingsFileSuffix", settingsFileSuffix);
+        setArguments(args);
+    }
+
+    private void setDevice(final GBDevice device) {
+        final Bundle args = getArguments() != null ? getArguments() : new Bundle();
+        args.putParcelable("device", device);
+        setArguments(args);
+    }
+
+    @Override
+    public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) {
+        final Bundle arguments = getArguments();
+        if (arguments == null) {
+            return;
+        }
+        final String settingsFileSuffix = arguments.getString("settingsFileSuffix", null);
+        this.device = arguments.getParcelable("device");
+
+        if (settingsFileSuffix == null) {
+            return;
+        }
+
+        getPreferenceManager().setSharedPreferencesName("devicesettings_" + settingsFileSuffix);
+        setPreferencesFromResource(R.xml.loyalty_cards, rootKey);
+
+        reloadPreferences(null);
+    }
+
+    static LoyaltyCardsSettingsFragment newInstance(GBDevice device) {
+        final String settingsFileSuffix = device.getAddress();
+        final LoyaltyCardsSettingsFragment fragment = new LoyaltyCardsSettingsFragment();
+        fragment.setSettingsFileSuffix(settingsFileSuffix);
+        fragment.setDevice(device);
+
+        return fragment;
+    }
+
+    protected void reloadPreferences(final String catimaPackageName) {
+        final CatimaManager catimaManager = new CatimaManager(requireContext());
+
+        final List<CharSequence> installedCatimaPackages = catimaManager.findInstalledCatimaPackages();
+        final boolean catimaInstalled = !installedCatimaPackages.isEmpty();
+
+        final ListPreference catimaPackagePreference = findPreference(LOYALTY_CARDS_CATIMA_PACKAGE);
+
+        if (catimaPackagePreference != null) {
+            catimaPackagePreference.setEntries(installedCatimaPackages.toArray(new CharSequence[0]));
+            catimaPackagePreference.setEntryValues(installedCatimaPackages.toArray(new CharSequence[0]));
+            catimaPackagePreference.setVisible(installedCatimaPackages.size() > 1);
+            catimaPackagePreference.setOnPreferenceChangeListener((preference, newValue) -> {
+                LOG.info("Catima package preference changed to {}", newValue);
+                reloadPreferences((String) newValue);
+                return true;
+            });
+
+            if (catimaInstalled) {
+                // Ensure the currently selected value is actually an installed Catima package
+                if (StringUtils.isNullOrEmpty(catimaPackagePreference.getValue()) || !installedCatimaPackages.contains(catimaPackagePreference.getValue())) {
+                    catimaPackagePreference.setValue(installedCatimaPackages.get(0).toString());
+                }
+            }
+        }
+
+        final String finalCatimaPackageName;
+
+        if (catimaPackageName != null) {
+            finalCatimaPackageName = catimaPackageName;
+        } else if (catimaPackagePreference != null) {
+            finalCatimaPackageName = catimaPackagePreference.getValue();
+        } else {
+            LOG.warn("This should never happen - catima package not found");
+            finalCatimaPackageName = "this.should.never.happen";
+        }
+
+        final CatimaContentProvider catima = new CatimaContentProvider(requireContext(), finalCatimaPackageName);
+
+        final Preference openCatimaPreference = findPreference(LOYALTY_CARDS_OPEN_CATIMA);
+        if (openCatimaPreference != null) {
+            openCatimaPreference.setVisible(catimaInstalled);
+            openCatimaPreference.setOnPreferenceClickListener(preference -> {
+                if (catimaPackagePreference != null) {
+                    final PackageManager packageManager = requireContext().getPackageManager();
+                    final Intent launchIntent = packageManager.getLaunchIntentForPackage(finalCatimaPackageName);
+                    if (launchIntent != null) {
+                        startActivity(launchIntent);
+                    }
+                }
+                return true;
+            });
+        }
+
+        final Preference catimaNotInstalledPreference = findPreference(LOYALTY_CARDS_CATIMA_NOT_INSTALLED);
+        if (catimaNotInstalledPreference != null) {
+            catimaNotInstalledPreference.setVisible(!catimaInstalled);
+        }
+
+        final Preference installCatimaPreference = findPreference(LOYALTY_CARDS_INSTALL_CATIMA);
+        if (installCatimaPreference != null) {
+            installCatimaPreference.setVisible(!catimaInstalled);
+            installCatimaPreference.setOnPreferenceClickListener(preference -> {
+                installCatima();
+                return true;
+            });
+        }
+
+        final boolean permissionGranted = ContextCompat.checkSelfPermission(requireContext(), catima.getReadPermission()) == PackageManager.PERMISSION_GRANTED;
+        final Preference catimaPermissionsPreference = findPreference(LOYALTY_CARDS_CATIMA_PERMISSIONS);
+        if (catimaPermissionsPreference != null) {
+            catimaPermissionsPreference.setVisible(catimaInstalled && !permissionGranted);
+            catimaPermissionsPreference.setOnPreferenceClickListener(preference -> {
+                ActivityCompat.requestPermissions(
+                        requireActivity(),
+                        new String[]{catima.getReadPermission()},
+                        LoyaltyCardsSettingsActivity.PERMISSION_REQUEST_CODE
+                );
+                return true;
+            });
+        }
+
+        final boolean catimaCompatible = catima.isCatimaCompatible();
+        final Preference catimaNotCompatiblePreference = findPreference(LOYALTY_CARDS_CATIMA_NOT_COMPATIBLE);
+        if (catimaNotCompatiblePreference != null) {
+            catimaNotCompatiblePreference.setVisible(catimaInstalled && permissionGranted && !catimaCompatible);
+        }
+
+        final Preference syncPreference = findPreference(LOYALTY_CARDS_SYNC);
+        if (syncPreference != null) {
+            syncPreference.setEnabled(catimaInstalled && catimaCompatible && permissionGranted);
+            syncPreference.setOnPreferenceClickListener(preference -> {
+                catimaManager.sync(device);
+                return true;
+            });
+        }
+
+        final PreferenceCategory headerCatima = findPreference(PREF_KEY_HEADER_LOYALTY_CARDS_CATIMA);
+        if (headerCatima != null) {
+            boolean allHidden = true;
+            for (int i = 0; i < headerCatima.getPreferenceCount(); i++) {
+                if (headerCatima.getPreference(i).isVisible()) {
+                    allHidden = false;
+                    break;
+                }
+            }
+            headerCatima.setVisible(!allHidden);
+        }
+
+        if (catimaInstalled && catimaCompatible && permissionGranted) {
+            final MultiSelectListPreference syncGroups = findPreference(LOYALTY_CARDS_SYNC_GROUPS);
+            if (syncGroups != null) {
+                final List<String> groups = catima.getGroups();
+                final CharSequence[] entries = groups.toArray(new CharSequence[0]);
+                syncGroups.setEntries(entries);
+                syncGroups.setEntryValues(entries);
+
+                // Remove groups that do not exist anymore from the summary
+                final Set<String> values = new HashSet<>(syncGroups.getValues());
+                final Set<String> toRemove = new HashSet<>();
+                for (final String group : values) {
+                    if (!groups.contains(group)) {
+                        toRemove.add(group);
+                    }
+                }
+                values.removeAll(toRemove);
+                syncGroups.setSummary(TextUtils.join(", ", values));
+            }
+        }
+
+        final boolean allowSync = catimaInstalled && permissionGranted && catimaCompatible;
+        final PreferenceCategory syncCategory = findPreference(PREF_KEY_HEADER_LOYALTY_CARDS_SYNC);
+        if (syncCategory != null) {
+            for (int i = 0; i < syncCategory.getPreferenceCount(); i++) {
+                syncCategory.getPreference(i).setEnabled(allowSync);
+            }
+        }
+        final PreferenceCategory syncOptionsCategory = findPreference(PREF_KEY_HEADER_LOYALTY_CARDS_SYNC_OPTIONS);
+        if (syncOptionsCategory != null) {
+            for (int i = 0; i < syncOptionsCategory.getPreferenceCount(); i++) {
+                syncOptionsCategory.getPreference(i).setEnabled(allowSync);
+            }
+        }
+    }
+
+    private void installCatima() {
+        try {
+            startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=me.hackerchick.catima")));
+        } catch (final ActivityNotFoundException e) {
+            GB.toast(requireContext(), requireContext().getString(R.string.loyalty_cards_install_catima_fail), Toast.LENGTH_LONG, GB.WARN);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/musicmanager/MusicManagerActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/musicmanager/MusicManagerActivity.java
new file mode 100644
index 0000000..b6999f9
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/musicmanager/MusicManagerActivity.java
@@ -0,0 +1,720 @@
+package nodomain.freeyourgadget.gadgetbridge.activities.musicmanager;
+
+import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.os.Handler;
+import android.text.InputType;
+import android.text.TextUtils;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.EditText;
+import android.widget.FrameLayout;
+import android.widget.ImageButton;
+import android.widget.PopupMenu;
+import android.widget.Spinner;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.activity.result.ActivityResult;
+import androidx.activity.result.ActivityResultCallback;
+import androidx.activity.result.ActivityResultLauncher;
+import androidx.activity.result.contract.ActivityResultContracts;
+import androidx.annotation.NonNull;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.google.android.material.appbar.MaterialToolbar;
+import com.google.android.material.dialog.MaterialAlertDialogBuilder;
+import com.google.android.material.floatingactionbutton.FloatingActionButton;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBActivity;
+import nodomain.freeyourgadget.gadgetbridge.activities.FwAppInstallerActivity;
+import nodomain.freeyourgadget.gadgetbridge.adapter.MusicListAdapter;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceMusic;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceMusicPlaylist;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+import nodomain.freeyourgadget.gadgetbridge.util.GridAutoFitLayoutManager;
+
+public class MusicManagerActivity extends AbstractGBActivity {
+    private static final Logger LOG = LoggerFactory.getLogger(MusicManagerActivity.class);
+
+    public static final String ACTION_MUSIC_DATA
+            = "nodomain.freeyourgadget.gadgetbridge.musicmanager.action.music_data";
+    public static final String ACTION_MUSIC_UPDATE
+            = "nodomain.freeyourgadget.gadgetbridge.musicmanager.action.music_update";
+
+    protected GBDevice mGBDevice = null;
+
+    private View loadingView = null;
+    private TextView musicDeviceInfo = null;
+
+    private final List<GBDeviceMusic> allMusic = new ArrayList<>();
+
+    private final List<GBDeviceMusic> musicList = new ArrayList<>();
+    private MusicListAdapter musicAdapter;
+
+    private final List<GBDeviceMusicPlaylist> playlists = new ArrayList<>();
+    private ArrayAdapter<GBDeviceMusicPlaylist> playlistAdapter;
+
+    private View playlistSpinnerLayout;
+    private Spinner playlistsSpinner;
+
+    private FloatingActionButton fabMusicUpload;
+    private FloatingActionButton fabMusicPlaylistAdd;
+
+
+    private MaterialToolbar bottomToolbar;
+    private TextView selectionInfo;
+    private ImageButton selectionAddPlaylist;
+    private ImageButton selectionDelete;
+
+    private int maxMusicCount = 0;
+    private int maxPlaylistCount = 0;
+    
+    public GBDevice getGBDevice() {
+        return mGBDevice;
+    }
+
+    Handler loadingTimeout = new Handler();
+    Runnable loadingRunnable = new Runnable() {
+        @Override
+        public void run() {
+            GB.toast(getString(R.string.music_error), Toast.LENGTH_SHORT, GB.ERROR);
+            stopLoading();
+        }
+    };
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.activity_musicmanager);
+
+        Bundle extras = getIntent().getExtras();
+        if (extras != null) {
+            mGBDevice = extras.getParcelable(GBDevice.EXTRA_DEVICE);
+        }
+        if (mGBDevice == null) {
+            throw new IllegalArgumentException("Must provide a device when invoking this activity");
+        }
+
+        fabMusicUpload = findViewById(R.id.fab_music_upload);
+        assert fabMusicUpload != null;
+        fabMusicUpload.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
+                intent.addCategory(Intent.CATEGORY_OPENABLE);
+                intent.setType("audio/*");
+                openAudioActivityResultLauncher.launch(intent);
+            }
+        });
+
+        fabMusicPlaylistAdd = findViewById(R.id.fab_music_playlist_add);
+        assert fabMusicPlaylistAdd != null;
+        fabMusicPlaylistAdd.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                addMusicPlaylist();
+            }
+        });
+
+        hideActionButtons();
+
+        bottomToolbar = findViewById(R.id.bottom_toolbar);
+
+        ImageButton cancelSelection = findViewById(R.id.music_cancel_selection);
+        selectionInfo = findViewById(R.id.music_selection_info);
+        selectionAddPlaylist = findViewById(R.id.music_multiselect_add_to_playlist);
+        selectionDelete = findViewById(R.id.music_multiselect_delete);
+
+
+        RecyclerView musicListView = findViewById(R.id.music_songs_list);
+        loadingView = findViewById(R.id.music_loading);
+
+        musicDeviceInfo = findViewById(R.id.music_device_info);
+
+        musicListView.addOnScrollListener(new RecyclerView.OnScrollListener() {
+            @Override
+            public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
+                if (dy > 0) {
+                    hideActionButtons();
+                } else if (dy < 0) {
+                    showActionButtons();
+                }
+            }
+        });
+        musicListView.setLayoutManager(new GridAutoFitLayoutManager(this, 300));
+
+        musicAdapter = new MusicListAdapter(
+                musicList,
+                new MusicListAdapter.onDataAction() {
+                    @Override
+                    public void onItemClick(View view, GBDeviceMusic music) {
+                        openPopupMenu(view, music);
+                    }
+
+                    @Override
+                    public void onMultiSelect(int count, boolean limit) {
+                        multiSelect(count, limit);
+                    }
+
+                }
+        );
+        musicListView.setAdapter(musicAdapter);
+
+        cancelSelection.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                musicAdapter.clearSelectedItems();
+            }
+        });
+
+        selectionAddPlaylist.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                List<GBDeviceMusic> musics = musicAdapter.getSelectedItems();
+                if(!musics.isEmpty()) {
+                    ArrayList<Integer> list = new ArrayList<>();
+                    for(GBDeviceMusic music: musics) {
+                        list.add(music.getId());
+                    }
+                    addMusicSongToPlaylist(list);
+                }
+            }
+        });
+
+        selectionDelete.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                List<GBDeviceMusic> musics = musicAdapter.getSelectedItems();
+                if(!musics.isEmpty()) {
+                    ArrayList<Integer> list = new ArrayList<>();
+                    for(GBDeviceMusic music: musics) {
+                        list.add(music.getId());
+                    }
+                    deleteMusicMultipleConfirm(list);
+                }
+            }
+        });
+
+        playlistSpinnerLayout = findViewById(R.id.music_playlists_layout);
+
+        playlistsSpinner = findViewById(R.id.music_playlists);
+
+        ImageButton renamePlaylist = findViewById(R.id.music_playlist_rename);
+        assert renamePlaylist != null;
+        renamePlaylist.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                renameMusicPlaylist((GBDeviceMusicPlaylist) playlistsSpinner.getSelectedItem());
+            }
+        });
+
+        ImageButton deletePlaylist = findViewById(R.id.music_playlist_delete);
+        assert deletePlaylist != null;
+        deletePlaylist.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                deleteMusicPlaylist((GBDeviceMusicPlaylist) playlistsSpinner.getSelectedItem());
+            }
+        });
+
+
+        playlistsSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
+            @Override
+            public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
+                GBDeviceMusicPlaylist item = (GBDeviceMusicPlaylist) adapterView.getItemAtPosition(i);
+                musicAdapter.clearSelectedItems();
+                if (item.getId() == 0) {
+                    deletePlaylist.setVisibility(View.GONE);
+                    renamePlaylist.setVisibility(View.GONE);
+
+                } else {
+                    deletePlaylist.setVisibility(View.VISIBLE);
+                    renamePlaylist.setVisibility(View.VISIBLE);
+                }
+                updateCurrentMusicList();
+            }
+
+            @Override
+            public void onNothingSelected(AdapterView<?> adapterView) {
+
+            }
+        });
+
+        playlistAdapter = new ArrayAdapter<>(this, android.R.layout.simple_spinner_item, playlists);
+        initPlaylists();
+
+        playlistAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+        playlistsSpinner.setAdapter(playlistAdapter);
+    }
+
+    private void multiSelect(int count, boolean limit) {
+        if(limit) {
+            GB.toast(this, R.string.music_multiselect_limit, Toast.LENGTH_LONG, GB.WARN);
+        }
+        if(count > 0) {
+            hideActionButtons();
+            bottomToolbar.setVisibility(View.VISIBLE);
+            selectionInfo.setText("" + count);
+            if(playlists.size() <= 1) {
+                selectionAddPlaylist.setVisibility(View.GONE);
+            } else {
+                selectionAddPlaylist.setVisibility(View.VISIBLE);
+            }
+        } else {
+            bottomToolbar.setVisibility(View.GONE);
+            showActionButtons();
+        }
+    }
+
+    private void hideActionButtons() {
+        fabMusicUpload.hide();
+        fabMusicPlaylistAdd.hide();
+
+    }
+
+    private void showActionButtons() {
+        fabMusicUpload.show();
+        if(maxPlaylistCount > 0) {
+            fabMusicPlaylistAdd.show();
+        }
+    }
+
+
+    private void startLoading(long timeout) {
+        hideActionButtons();
+        loadingView.setVisibility(View.VISIBLE);
+        if(timeout > 0) {
+            loadingTimeout.postDelayed(loadingRunnable, timeout);
+        }
+    }
+    private void startLoading() {
+        startLoading(4000);
+    }
+
+    private void stopLoading() {
+        loadingTimeout.removeCallbacks(loadingRunnable);
+        loadingView.setVisibility(View.GONE);
+        musicAdapter.clearSelectedItems();
+        showActionButtons();
+    }
+
+    private void updateCurrentMusicList() {
+        GBDeviceMusicPlaylist current = (GBDeviceMusicPlaylist) playlistsSpinner.getSelectedItem();
+        musicList.clear();
+        if (current.getId() == 0) {
+            musicList.addAll(allMusic);
+        } else {
+            List<GBDeviceMusic> filtered = allMusic.stream().filter(m -> current.getMusicIds().contains(m.getId())).collect(Collectors.toList());
+            musicList.addAll(filtered);
+        }
+        musicAdapter.notifyDataSetChanged();
+    }
+
+    private void initPlaylists() {
+        playlists.clear();
+        playlists.add(new GBDeviceMusicPlaylist(0,this.getString(R.string.music_all_songs),null));
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(ACTION_MUSIC_DATA);
+        filter.addAction(ACTION_MUSIC_UPDATE);
+
+        LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, filter);
+
+        // Load music data without timeout
+        startLoading(0);
+        GBApplication.deviceService(mGBDevice).onMusicListReq();
+    }
+
+    @Override
+    public void onStop() {
+        LocalBroadcastManager.getInstance(this).unregisterReceiver(mReceiver);
+        super.onStop();
+    }
+
+    ActivityResultLauncher<Intent> openAudioActivityResultLauncher = registerForActivityResult(
+            new ActivityResultContracts.StartActivityForResult(),
+            new ActivityResultCallback<ActivityResult>() {
+                @Override
+                public void onActivityResult(ActivityResult result) {
+                    if (result.getResultCode() == Activity.RESULT_OK && result.getData() != null) {
+                        Intent startIntent = new Intent(MusicManagerActivity.this, FwAppInstallerActivity.class);
+                        startIntent.setAction(Intent.ACTION_VIEW);
+                        startIntent.setDataAndType(result.getData().getData(), null);
+                        startActivity(startIntent);
+                    }
+                }
+            });
+
+
+    public boolean openPopupMenu(View view, GBDeviceMusic music) {
+        PopupMenu popupMenu = new PopupMenu(this, view);
+        popupMenu.getMenuInflater().inflate(R.menu.musicmanager_context, popupMenu.getMenu());
+        Menu menu = popupMenu.getMenu();
+
+        if (playlists.size() <= 1) {
+            menu.removeItem(R.id.musicmanager_add_to_playlist);
+        }
+
+        GBDeviceMusicPlaylist current = (GBDeviceMusicPlaylist) playlistsSpinner.getSelectedItem();
+        if (current.getId() == 0) {
+            menu.removeItem(R.id.musicmanager_delete_from_playlist);
+        } else {
+            menu.removeItem(R.id.musicmanager_delete);
+        }
+
+        popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
+                                                 public boolean onMenuItemClick(MenuItem item) {
+                                                     return onPopupItemSelected(item, music);
+                                                 }
+                                             }
+        );
+
+        popupMenu.show();
+        return true;
+    }
+
+    private boolean onPopupItemSelected(final MenuItem item, final GBDeviceMusic music) {
+        final int itemId = item.getItemId();
+        if (itemId == R.id.musicmanager_delete || itemId == R.id.musicmanager_delete_from_playlist) {
+            deleteMusicConfirm(music);
+            return true;
+        } else if (itemId == R.id.musicmanager_add_to_playlist) {
+            ArrayList<Integer> list = new ArrayList<>();
+            list.add(music.getId());
+            addMusicSongToPlaylist(list);
+            return true;
+        }
+        return false;
+    }
+
+    private void deleteMusicConfirm(final GBDeviceMusic music) {
+        ArrayList<Integer> list = new ArrayList<>();
+        list.add(music.getId());
+
+        new MaterialAlertDialogBuilder(this)
+                .setTitle(R.string.Delete)
+                .setMessage(this.getString(R.string.music_delete_confirm_description, music.getTitle()))
+                .setIcon(R.drawable.ic_warning)
+                .setPositiveButton(android.R.string.yes, (dialog, whichButton) -> {
+                    deleteMusicFromDevice((GBDeviceMusicPlaylist) playlistsSpinner.getSelectedItem(), list);
+                })
+                .setNegativeButton(android.R.string.no, null)
+                .show();
+    }
+
+    private void deleteMusicMultipleConfirm(final ArrayList<Integer> list) {
+        new MaterialAlertDialogBuilder(this)
+                .setTitle(R.string.Delete)
+                .setMessage(this.getString(R.string.music_delete_multiple_confirm_description, list.size()))
+                .setIcon(R.drawable.ic_warning)
+                .setPositiveButton(android.R.string.yes, (dialog, whichButton) -> {
+                    deleteMusicFromDevice((GBDeviceMusicPlaylist) playlistsSpinner.getSelectedItem(), list);
+                })
+                .setNegativeButton(android.R.string.no, null)
+                .show();
+    }
+
+    private void addPlaylistToDevice(final String playlistName) {
+        startLoading();
+        GBApplication.deviceService(mGBDevice).onMusicOperation(0, -1, playlistName, null);
+    }
+
+    private void deletePlaylistFromDevice(final GBDeviceMusicPlaylist playlist) {
+        startLoading();
+        GBApplication.deviceService(mGBDevice).onMusicOperation(1, playlist.getId(), null, null);
+    }
+
+    private void renamePlaylistOnDevice(final GBDeviceMusicPlaylist playlist, String newPlaylistName) {
+        startLoading();
+        GBApplication.deviceService(mGBDevice).onMusicOperation(2, playlist.getId(), newPlaylistName, null);
+    }
+
+    private void addMusicToDevicePlaylist(GBDeviceMusicPlaylist playlist, final ArrayList<Integer> list) {
+        startLoading();
+        GBApplication.deviceService(mGBDevice).onMusicOperation(3, playlist.getId(), null, list);
+    }
+
+    private void deleteMusicFromDevice(GBDeviceMusicPlaylist playlist, final ArrayList<Integer> list) {
+        startLoading();
+        GBApplication.deviceService(mGBDevice).onMusicOperation(4, playlist.getId(), null, list);
+    }
+
+    private void addMusicPlaylist() {
+        final EditText input = new EditText(this);
+        input.setInputType(InputType.TYPE_CLASS_TEXT);
+
+        FrameLayout container = new FrameLayout(this);
+        FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+        params.leftMargin = getResources().getDimensionPixelSize(R.dimen.dialog_margin);
+        params.rightMargin = getResources().getDimensionPixelSize(R.dimen.dialog_margin);
+        input.setLayoutParams(params);
+        container.addView(input);
+
+        new MaterialAlertDialogBuilder(this)
+                .setTitle(R.string.music_new_playlist)
+                .setView(container)
+                .setPositiveButton(android.R.string.ok, (dialog, whichButton) -> {
+                    String playlistName = input.getText().toString();
+                    if(!playlistName.isEmpty()) {
+                        addPlaylistToDevice(playlistName);
+                    }
+                })
+                .setNegativeButton(android.R.string.cancel, null)
+                .show();
+    }
+
+    private void renameMusicPlaylist(GBDeviceMusicPlaylist playlist) {
+        if(playlist.getId() == 0)
+            return;
+        final EditText input = new EditText(this);
+        input.setInputType(InputType.TYPE_CLASS_TEXT);
+        input.setText(playlist.getName());
+
+        FrameLayout container = new FrameLayout(this);
+        FrameLayout.LayoutParams params = new  FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+        params.leftMargin = getResources().getDimensionPixelSize(R.dimen.dialog_margin);
+        params.rightMargin = getResources().getDimensionPixelSize(R.dimen.dialog_margin);
+        input.setLayoutParams(params);
+        container.addView(input);
+
+        new MaterialAlertDialogBuilder(this)
+                .setTitle(R.string.music_rename_playlist)
+                .setView(container)
+                .setPositiveButton(android.R.string.ok, (dialog, whichButton) -> {
+                    String playlistName = input.getText().toString();
+                    if(!playlistName.isEmpty()) {
+                        renamePlaylistOnDevice(playlist, playlistName);
+                    }
+                })
+                .setNegativeButton(android.R.string.cancel, null)
+                .show();
+    }
+
+    private void deleteMusicPlaylist(GBDeviceMusicPlaylist playlist) {
+        if(playlist.getId() == 0)
+            return;
+        new MaterialAlertDialogBuilder(this)
+                .setTitle(R.string.Delete)
+                .setMessage(this.getString(R.string.music_delete_confirm_description, playlist.getName()))
+                .setIcon(R.drawable.ic_warning)
+                .setPositiveButton(android.R.string.yes, (dialog, whichButton) -> {
+                    deletePlaylistFromDevice(playlist);
+                })
+                .setNegativeButton(android.R.string.no, null)
+                .show();
+    }
+
+    private void addMusicSongToPlaylist(final ArrayList<Integer> list) {
+        final Spinner dPlaylists = new Spinner(this);
+
+        List<GBDeviceMusicPlaylist> dialogPlaylists = new ArrayList<>();
+        for (GBDeviceMusicPlaylist playlist : playlists) {
+            if (playlist.getId() != 0) {
+                dialogPlaylists.add(playlist);
+            }
+        }
+
+        ArrayAdapter<GBDeviceMusicPlaylist> dialogPlaylistAdapter = new ArrayAdapter<>(this, android.R.layout.simple_spinner_item, dialogPlaylists);
+        dialogPlaylistAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+        dPlaylists.setAdapter(dialogPlaylistAdapter);
+
+        FrameLayout container = new FrameLayout(this);
+        FrameLayout.LayoutParams params = new  FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+        params.leftMargin = getResources().getDimensionPixelSize(R.dimen.dialog_margin);
+        params.rightMargin = getResources().getDimensionPixelSize(R.dimen.dialog_margin);
+        dPlaylists.setLayoutParams(params);
+        container.addView(dPlaylists);
+
+        new MaterialAlertDialogBuilder(this)
+                .setTitle(R.string.music_add_to_playlist)
+                .setView(container)
+                .setPositiveButton(android.R.string.ok, (dialog, whichButton) -> {
+                    GBDeviceMusicPlaylist playlist = (GBDeviceMusicPlaylist) dPlaylists.getSelectedItem();
+                    addMusicToDevicePlaylist(playlist, list);
+                })
+                .setNegativeButton(android.R.string.cancel, null)
+                .show();
+    }
+
+    private void startSyncFromDevice(Intent intent) {
+        String info = intent.getStringExtra("deviceInfo");
+        if (!TextUtils.isEmpty(info)) {
+            musicDeviceInfo.setText(info);
+        } else {
+            musicDeviceInfo.setVisibility(View.GONE);
+        }
+
+        maxMusicCount = intent.getIntExtra("maxMusicCount", 0);
+        maxPlaylistCount = intent.getIntExtra("maxPlaylistCount", 0);
+
+        // Hide playlist if device does not support it.
+        playlistSpinnerLayout.setVisibility(maxPlaylistCount>0?View.VISIBLE:View.GONE);
+
+        allMusic.clear();
+        musicList.clear();
+        initPlaylists();
+    }
+
+    private void musicListFromDevice(Intent intent) {
+        ArrayList<GBDeviceMusic> list = (ArrayList<GBDeviceMusic>) intent.getSerializableExtra("musicList");
+        if (list != null && !list.isEmpty()) {
+            allMusic.addAll(list);
+        }
+
+        ArrayList<GBDeviceMusicPlaylist> devicePlaylist = (ArrayList<GBDeviceMusicPlaylist>) intent.getSerializableExtra("musicPlaylist");
+        if (devicePlaylist != null && !devicePlaylist.isEmpty()) {
+            playlists.addAll(devicePlaylist);
+            playlistAdapter.notifyDataSetChanged();
+        }
+    }
+
+    private void musicOperationResponse(Intent intent) {
+        int operation = intent.getIntExtra("operation", -1);
+        if (operation == 0) {
+            int playlistIndex = intent.getIntExtra("playlistIndex", -1);
+            String playlistName = intent.getStringExtra("playlistName");
+
+            if (playlistIndex != -1 && playlistName != null) {
+                playlists.add(new GBDeviceMusicPlaylist(playlistIndex, playlistName, new ArrayList<>()));
+                playlistAdapter.notifyDataSetChanged();
+            }
+        } else if (operation == 1) {
+            int playlistIndex = intent.getIntExtra("playlistIndex", -1);
+            if (playlistIndex != -1) {
+                for (Iterator<GBDeviceMusicPlaylist> iterator = playlists.iterator(); iterator.hasNext(); ) {
+                    GBDeviceMusicPlaylist playlist = iterator.next();
+                    if (playlist.getId() == playlistIndex) {
+                        iterator.remove();
+                    }
+                }
+                playlistAdapter.notifyDataSetChanged();
+            }
+        } else if (operation == 2) {
+            int playlistIndex = intent.getIntExtra("playlistIndex", -1);
+            String playlistName = intent.getStringExtra("playlistName");
+            if (playlistIndex != -1 && playlistName != null) {
+                for (GBDeviceMusicPlaylist playlist : playlists) {
+                    if (playlist.getId() == playlistIndex) {
+                        playlist.setName(playlistName);
+                        break;
+                    }
+                }
+                playlistAdapter.notifyDataSetChanged();
+            }
+        } else if (operation == 3) {
+            int playlistIndex = intent.getIntExtra("playlistIndex", -1);
+            ArrayList<Integer> ids = (ArrayList<Integer>) intent.getSerializableExtra("musicIds");
+            if (playlistIndex != -1 && ids != null && !ids.isEmpty()) {
+                for (GBDeviceMusicPlaylist playlist : playlists) {
+                    if (playlist.getId() == playlistIndex) {
+                        ArrayList<Integer> currentList = playlist.getMusicIds();
+                        for (Integer id : ids) {
+                            if (!currentList.contains(id))
+                                currentList.add(id);
+                        }
+                        playlist.setMusicIds(currentList);
+                        break;
+                    }
+                }
+                playlistAdapter.notifyDataSetChanged();
+                updateCurrentMusicList();
+            }
+
+        } else if (operation == 4) {
+            ArrayList<Integer> ids = (ArrayList<Integer>) intent.getSerializableExtra("musicIds");
+            int playlistIndex = intent.getIntExtra("playlistIndex", 0);
+            if (ids != null && !ids.isEmpty()) {
+                if (playlistIndex == 0) {
+                    for (Iterator<GBDeviceMusic> iterator = musicList.iterator(); iterator.hasNext(); ) {
+                        GBDeviceMusic music = iterator.next();
+                        if (ids.contains(music.getId())) {
+                            iterator.remove();
+                        }
+                    }
+                    for (Iterator<GBDeviceMusic> iterator = allMusic.iterator(); iterator.hasNext(); ) {
+                        GBDeviceMusic music = iterator.next();
+                        if (ids.contains(music.getId())) {
+                            iterator.remove();
+                        }
+                    }
+                } else {
+                    for (GBDeviceMusicPlaylist playlist : playlists) {
+                        if (playlist.getId() == playlistIndex) {
+                            ArrayList<Integer> currentList = playlist.getMusicIds();
+                            for (Integer id : ids) {
+                                currentList.remove(id);
+                            }
+                            playlist.setMusicIds(currentList);
+                            break;
+                        }
+                    }
+                }
+                playlistAdapter.notifyDataSetChanged();
+                updateCurrentMusicList();
+            }
+        }
+    }
+
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            switch (action) {
+                case ACTION_MUSIC_DATA: {
+                    if (!intent.hasExtra("type"))
+                        break;
+                    int type = intent.getIntExtra("type", -1);
+
+                    LOG.info("UPDATE type: {}", type);
+                    if (type == 1) {
+                        startSyncFromDevice(intent);
+                    } else if (type == 2) {
+                        LOG.info("got music list or playlist from device");
+                        musicListFromDevice(intent);
+                    } else if (type == 10) {
+                        updateCurrentMusicList();
+                        stopLoading();
+                    }
+                    break;
+                }
+                case ACTION_MUSIC_UPDATE: {
+                    boolean success = intent.getBooleanExtra("success", false);
+                    if (intent.hasExtra("operation") && success) {
+                        musicOperationResponse(intent);
+                    }
+                    stopLoading();
+                    break;
+                }
+            }
+        }
+    };
+
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/welcome/WelcomeActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/welcome/WelcomeActivity.java
new file mode 100644
index 0000000..b44f8ba
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/welcome/WelcomeActivity.java
@@ -0,0 +1,83 @@
+/*  Copyright (C) 2024 Arjan Schrijver
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities.welcome;
+
+import android.os.Bundle;
+
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentActivity;
+import androidx.viewpager2.adapter.FragmentStateAdapter;
+import androidx.viewpager2.widget.ViewPager2;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBActivity;
+
+public class WelcomeActivity extends AbstractGBActivity {
+    private static final Logger LOG = LoggerFactory.getLogger(WelcomeActivity.class);
+
+    private ViewPager2 viewPager;
+    private WelcomeFragmentsPagerAdapter pagerAdapter;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        AbstractGBActivity.init(this, AbstractGBActivity.NO_ACTIONBAR);
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_welcome);
+        if (getSupportActionBar() != null) {
+            getSupportActionBar().hide();
+        }
+
+        // Configure ViewPager2 with fragment adapter and default fragment
+        viewPager = findViewById(R.id.welcome_viewpager);
+        pagerAdapter = new WelcomeFragmentsPagerAdapter(this);
+        viewPager.setAdapter(pagerAdapter);
+
+        // Set up welcome page indicator
+        WelcomePageIndicator pageIndicator = findViewById(R.id.welcome_page_indicator);
+        pageIndicator.setViewPager(viewPager);
+    }
+
+    private class WelcomeFragmentsPagerAdapter extends FragmentStateAdapter {
+        public WelcomeFragmentsPagerAdapter(FragmentActivity fa) {
+            super(fa);
+        }
+
+        @Override
+        public Fragment createFragment(int position) {
+            switch (position) {
+                case 0:
+                    return new WelcomeFragmentIntro();
+                case 1:
+                    return new WelcomeFragmentOverview();
+                case 2:
+                    return new WelcomeFragmentDocsSource();
+                case 3:
+                    return new WelcomeFragmentPermissions();
+                default:
+                    return new WelcomeFragmentGetStarted();
+            }
+        }
+
+        @Override
+        public int getItemCount() {
+            return 5;
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/welcome/WelcomeFragmentDocsSource.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/welcome/WelcomeFragmentDocsSource.java
new file mode 100644
index 0000000..ca5130c
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/welcome/WelcomeFragmentDocsSource.java
@@ -0,0 +1,42 @@
+/*  Copyright (C) 2024 Arjan Schrijver
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities.welcome;
+
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.fragment.app.Fragment;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+
+public class WelcomeFragmentDocsSource extends Fragment {
+    private static final Logger LOG = LoggerFactory.getLogger(WelcomeFragmentDocsSource.class);
+
+    @Nullable
+    @Override
+    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
+        super.onCreateView(inflater, container, savedInstanceState);
+        return inflater.inflate(R.layout.fragment_welcome_docs_source, container, false);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/welcome/WelcomeFragmentGetStarted.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/welcome/WelcomeFragmentGetStarted.java
new file mode 100644
index 0000000..2de4198
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/welcome/WelcomeFragmentGetStarted.java
@@ -0,0 +1,61 @@
+/*  Copyright (C) 2024 Arjan Schrijver
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities.welcome;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.fragment.app.Fragment;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.DataManagementActivity;
+import nodomain.freeyourgadget.gadgetbridge.activities.discovery.DiscoveryActivityV2;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+public class WelcomeFragmentGetStarted extends Fragment {
+    private static final Logger LOG = LoggerFactory.getLogger(WelcomeFragmentGetStarted.class);
+
+    @Nullable
+    @Override
+    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
+        super.onCreateView(inflater, container, savedInstanceState);
+        View view = inflater.inflate(R.layout.fragment_welcome_get_started, container, false);
+
+        Button firstDevice = view.findViewById(R.id.welcome_button_add_device);
+        firstDevice.setOnClickListener(firstDeviceButton -> startActivity(new Intent(requireActivity(), DiscoveryActivityV2.class)));
+        Button restore = view.findViewById(R.id.welcome_button_restore);
+        restore.setOnClickListener(restoreButton -> startActivity(new Intent(requireActivity(), DataManagementActivity.class)));
+        Button toApp = view.findViewById(R.id.welcome_button_to_app);
+        toApp.setOnClickListener(toAppButton -> {
+            Prefs prefs = GBApplication.getPrefs();
+            prefs.getPreferences().edit().putBoolean("first_run", false).apply();
+            requireActivity().finish();
+        });
+
+        return view;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/welcome/WelcomeFragmentIntro.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/welcome/WelcomeFragmentIntro.java
new file mode 100644
index 0000000..988b183
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/welcome/WelcomeFragmentIntro.java
@@ -0,0 +1,74 @@
+/*  Copyright (C) 2024 Arjan Schrijver
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities.welcome;
+
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.os.Handler;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.fragment.app.Fragment;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import com.google.android.material.textfield.MaterialAutoCompleteTextView;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Arrays;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+public class WelcomeFragmentIntro extends Fragment {
+    private static final Logger LOG = LoggerFactory.getLogger(WelcomeFragmentIntro.class);
+
+    @Nullable
+    @Override
+    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
+        super.onCreateView(inflater, container, savedInstanceState);
+
+        final View view = inflater.inflate(R.layout.fragment_welcome_intro, container, false);
+        final String[] themes = getResources().getStringArray(R.array.pref_theme_values);
+        final Prefs prefs = GBApplication.getPrefs();
+        final String currentTheme = prefs.getString("pref_key_theme", getString(R.string.pref_theme_value_system));
+        final int currentThemeIndex = Arrays.asList(themes).indexOf(currentTheme);
+
+        final MaterialAutoCompleteTextView themeMenu = view.findViewById(R.id.app_theme_dropdown_menu);
+        themeMenu.setSaveEnabled(false);  // https://github.com/material-components/material-components-android/issues/1464#issuecomment-1258051448
+        themeMenu.setText(getResources().getStringArray(R.array.pref_theme_options)[currentThemeIndex], false);
+        themeMenu.setOnItemClickListener((adapterView, view1, i, l) -> {
+            final SharedPreferences.Editor editor = prefs.getPreferences().edit();
+            editor.putString("pref_key_theme", themes[i]).apply();
+            final Handler handler = new Handler();
+            handler.postDelayed(() -> {
+                // Delay recreation of the Activity to give the dropdown some time to settle.
+                // If we recreate it immediately, the theme popup will reopen, which is not what the user expects.
+                Intent intent = new Intent();
+                intent.setAction(GBApplication.ACTION_THEME_CHANGE);
+                LocalBroadcastManager.getInstance(requireContext()).sendBroadcast(intent);
+            }, 500);
+        });
+        return view;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/welcome/WelcomeFragmentOverview.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/welcome/WelcomeFragmentOverview.java
new file mode 100644
index 0000000..3e68bae
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/welcome/WelcomeFragmentOverview.java
@@ -0,0 +1,42 @@
+/*  Copyright (C) 2024 Arjan Schrijver
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities.welcome;
+
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.fragment.app.Fragment;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+
+public class WelcomeFragmentOverview extends Fragment {
+    private static final Logger LOG = LoggerFactory.getLogger(WelcomeFragmentOverview.class);
+
+    @Nullable
+    @Override
+    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
+        super.onCreateView(inflater, container, savedInstanceState);
+        return inflater.inflate(R.layout.fragment_welcome_overview, container, false);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/welcome/WelcomeFragmentPermissions.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/welcome/WelcomeFragmentPermissions.java
new file mode 100644
index 0000000..c95b8cd
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/welcome/WelcomeFragmentPermissions.java
@@ -0,0 +1,173 @@
+/*  Copyright (C) 2024 Arjan Schrijver
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities.welcome;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.core.app.ActivityCompat;
+import androidx.fragment.app.Fragment;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.util.PermissionsUtils;
+
+public class WelcomeFragmentPermissions extends Fragment {
+    private static final Logger LOG = LoggerFactory.getLogger(WelcomeFragmentPermissions.class);
+
+    private RecyclerView permissionsListView;
+    private PermissionAdapter permissionAdapter;
+    private Button requestAllButton;
+    private List<String> requestingPermissions = new ArrayList<>();
+
+    @Nullable
+    @Override
+    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
+        super.onCreateView(inflater, container, savedInstanceState);
+        View view = inflater.inflate(R.layout.fragment_welcome_permissions, container, false);
+
+        requestAllButton = view.findViewById(R.id.button_request_all);
+        requestAllButton.setOnClickListener(v -> {
+            List<PermissionsUtils.PermissionDetails> wantedPermissions = PermissionsUtils.getRequiredPermissionsList(requireActivity());
+            requestingPermissions = new ArrayList<>();
+            for (PermissionsUtils.PermissionDetails wantedPermission : wantedPermissions) {
+                requestingPermissions.add(wantedPermission.getPermission());
+            }
+            requestAllPermissions();
+        });
+
+        if (((AppCompatActivity)getActivity()).getSupportActionBar().isShowing()) {
+            // Hide title when the Action Bar is visible (i.e. when not in the first run flow)
+            view.findViewById(R.id.permissions_title).setVisibility(View.GONE);
+        }
+
+        // Initialize RecyclerView and data
+        permissionsListView = view.findViewById(R.id.permissions_list);
+
+        // Set up RecyclerView
+        permissionAdapter = new PermissionAdapter(PermissionsUtils.getRequiredPermissionsList(requireActivity()), requireContext());
+        permissionsListView.setLayoutManager(new LinearLayoutManager(requireContext()));
+        permissionsListView.setAdapter(permissionAdapter);
+
+        return view;
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        permissionAdapter.notifyDataSetChanged();
+        if (PermissionsUtils.checkAllPermissions(requireActivity())) {
+            requestAllButton.setEnabled(false);
+        }
+        if (!requestingPermissions.isEmpty()) {
+            requestAllPermissions();
+        }
+    }
+
+    public void requestAllPermissions() {
+        if (!requestingPermissions.isEmpty()) {
+            Iterator<String> it = requestingPermissions.iterator();
+            while (it.hasNext()) {
+                String currentPermission = it.next();
+                if (PermissionsUtils.specialPermissions.contains(currentPermission)) {
+                    it.remove();
+                    if (!PermissionsUtils.checkPermission(requireActivity(), currentPermission)) {
+                        PermissionsUtils.requestPermission(requireActivity(), currentPermission);
+                        return;
+                    }
+                }
+            }
+            String[] combinedPermissions = requestingPermissions.toArray(new String[0]);
+            requestingPermissions.clear();
+            ActivityCompat.requestPermissions(requireActivity(), combinedPermissions, 0);
+        }
+    }
+
+    private class PermissionHolder extends RecyclerView.ViewHolder {
+        TextView titleTextView;
+        TextView summaryTextView;
+        ImageView checkmarkImageView;
+        Button requestButton;
+
+        public PermissionHolder(View itemView) {
+            super(itemView);
+            titleTextView = itemView.findViewById(R.id.permission_title);
+            summaryTextView = itemView.findViewById(R.id.permission_summary);
+            checkmarkImageView = itemView.findViewById(R.id.permission_check);
+            requestButton = itemView.findViewById(R.id.permission_request);
+        }
+    }
+
+    private class PermissionAdapter extends RecyclerView.Adapter<PermissionHolder> {
+        private List<PermissionsUtils.PermissionDetails> permissionList;
+        private Context context;
+
+        public PermissionAdapter(List<PermissionsUtils.PermissionDetails> permissionList, Context context) {
+            this.permissionList = permissionList;
+            this.context = context;
+        }
+
+        @NonNull
+        @Override
+        public PermissionHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+            View itemView = LayoutInflater.from(parent.getContext())
+                    .inflate(R.layout.fragment_welcome_permission_row, parent, false);
+            return new PermissionHolder(itemView);
+        }
+
+        @Override
+        public void onBindViewHolder(@NonNull PermissionHolder holder, int position) {
+            PermissionsUtils.PermissionDetails permissionData = permissionList.get(position);
+            holder.titleTextView.setText(permissionData.getTitle());
+            holder.summaryTextView.setText(permissionData.getSummary());
+            if (PermissionsUtils.checkPermission(requireContext(), permissionData.getPermission())) {
+                holder.requestButton.setVisibility(View.INVISIBLE);
+                holder.requestButton.setEnabled(false);
+                holder.checkmarkImageView.setVisibility(View.VISIBLE);
+            } else {
+                holder.requestButton.setVisibility(View.VISIBLE);
+                holder.requestButton.setEnabled(true);
+                holder.checkmarkImageView.setVisibility(View.GONE);
+                holder.requestButton.setOnClickListener(view -> {
+                    PermissionsUtils.requestPermission(requireActivity(), permissionData.getPermission());
+                });
+            }
+        }
+
+        @Override
+        public int getItemCount() {
+            return permissionList.size();
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/welcome/WelcomePageIndicator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/welcome/WelcomePageIndicator.java
new file mode 100644
index 0000000..be06b8b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/welcome/WelcomePageIndicator.java
@@ -0,0 +1,135 @@
+/*  Copyright (C) 2024 Arjan Schrijver
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities.welcome;
+
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.util.AttributeSet;
+import android.view.View;
+
+import androidx.annotation.Nullable;
+import androidx.viewpager2.widget.ViewPager2;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+
+public class WelcomePageIndicator extends View {
+    private ViewPager2 viewPager;
+    private int pageCount;
+    private int dotRadius = 15;
+    private int color;
+
+    private Paint outlinePaint;
+    private Paint filledPaint;
+    private float currentX = 0.0f;
+
+    private ValueAnimator dotAnimator;
+
+    public WelcomePageIndicator(Context context) {
+        super(context);
+        init();
+    }
+
+    public WelcomePageIndicator(Context context, @Nullable AttributeSet attrs) {
+        super(context, attrs);
+        determineColor(context, attrs);
+        init();
+    }
+
+    public WelcomePageIndicator(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        determineColor(context, attrs);
+        init();
+    }
+
+    private void determineColor(Context context, @Nullable AttributeSet attrs) {
+        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.WelcomePageIndicator);
+        color = a.getColor(R.styleable.WelcomePageIndicator_page_indicator_color, Color.BLACK);
+        a.recycle();
+    }
+
+    private void init() {
+        outlinePaint = new Paint();
+        outlinePaint.setColor(color);
+        outlinePaint.setStyle(Paint.Style.STROKE);
+        outlinePaint.setStrokeWidth(4);
+        outlinePaint.setAntiAlias(true);
+        filledPaint = new Paint();
+        filledPaint.setColor(color);
+        filledPaint.setStyle(Paint.Style.FILL);
+        outlinePaint.setAntiAlias(true);
+    }
+
+    public void setViewPager(ViewPager2 viewPager) {
+        this.viewPager = viewPager;
+        this.pageCount = viewPager.getAdapter().getItemCount();
+        viewPager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
+            @Override
+            public void onPageSelected(int position) {
+                animateIndicator(position);
+            }
+        });
+        invalidate();
+    }
+
+    private int getHorizontalMargin() {
+        int dotDiameter = dotRadius * 2;
+        int dotSpaces = pageCount * 2 - 1;
+        return (getWidth() - dotSpaces * dotDiameter) / 2 + dotRadius;
+    }
+
+    private void animateIndicator(int position) {
+        float horizontalMargin = getHorizontalMargin();
+        if (horizontalMargin <= 0.0f) {
+            // Not animating because the drawable is not ready yet
+            return;
+        }
+        float targetX = horizontalMargin + 4 * dotRadius * position;
+        if (dotAnimator != null && dotAnimator.isRunning()) {
+            dotAnimator.cancel();
+        }
+        if (currentX == 0.0f) currentX = horizontalMargin;
+        dotAnimator = ValueAnimator.ofFloat(currentX, targetX);
+        dotAnimator.addUpdateListener(animation -> {
+            currentX = (float) animation.getAnimatedValue();
+            invalidate();
+        });
+        dotAnimator.setDuration(300);
+        dotAnimator.start();
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+
+        if (viewPager == null || pageCount == 0) {
+            return;
+        }
+
+        float horizontalMargin = getHorizontalMargin();
+        if (currentX == 0.0f && horizontalMargin != 0.0f) currentX = horizontalMargin;
+        float circleY = getHeight() / 2f;
+        for (int i = 0; i < pageCount; i++) {
+            float circleX = horizontalMargin + 4 * dotRadius * i;
+            canvas.drawCircle(circleX, circleY, dotRadius, outlinePaint);
+        }
+        canvas.drawCircle(currentX, circleY, dotRadius, filledPaint);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/widgets/WidgetScreenDetailsActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/widgets/WidgetScreenDetailsActivity.java
new file mode 100644
index 0000000..f3783a7
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/widgets/WidgetScreenDetailsActivity.java
@@ -0,0 +1,280 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities.widgets;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.ArrayAdapter;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.annotation.NonNull;
+
+import com.google.android.material.dialog.MaterialAlertDialogBuilder;
+import com.google.android.material.floatingactionbutton.FloatingActionButton;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBActivity;
+import nodomain.freeyourgadget.gadgetbridge.capabilities.widgets.WidgetLayout;
+import nodomain.freeyourgadget.gadgetbridge.capabilities.widgets.WidgetManager;
+import nodomain.freeyourgadget.gadgetbridge.capabilities.widgets.WidgetPart;
+import nodomain.freeyourgadget.gadgetbridge.capabilities.widgets.WidgetPartSubtype;
+import nodomain.freeyourgadget.gadgetbridge.capabilities.widgets.WidgetScreen;
+import nodomain.freeyourgadget.gadgetbridge.capabilities.widgets.WidgetType;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class WidgetScreenDetailsActivity extends AbstractGBActivity {
+    private static final Logger LOG = LoggerFactory.getLogger(WidgetScreenDetailsActivity.class);
+
+    private WidgetScreen widgetScreen;
+    private WidgetManager widgetManager;
+
+    private View cardWidgetTopLeft;
+    private View cardWidgetTopRight;
+    private View cardWidgetCenter;
+    private View cardWidgetBotLeft;
+    private View cardWidgetBotRight;
+
+    private TextView labelWidgetScreenLayout;
+    private TextView labelWidgetTopLeft;
+    private TextView labelWidgetTopRight;
+    private TextView labelWidgetCenter;
+    private TextView labelWidgetBotLeft;
+    private TextView labelWidgetBotRight;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_widget_screen_details);
+
+        final GBDevice device = getIntent().getParcelableExtra(GBDevice.EXTRA_DEVICE);
+        if (device == null) {
+            LOG.error("device must not be null");
+            finish();
+            return;
+        }
+
+        widgetScreen = (WidgetScreen) getIntent().getSerializableExtra(WidgetScreen.EXTRA_WIDGET_SCREEN);
+        if (widgetScreen == null) {
+            GB.toast("No widget screen provided to WidgetScreenDetailsActivity", Toast.LENGTH_LONG, GB.ERROR);
+            finish();
+            return;
+        }
+
+        widgetManager = device.getDeviceCoordinator().getWidgetManager(device);
+
+        // Save button
+        final FloatingActionButton fab = findViewById(R.id.fab_save);
+        fab.setOnClickListener(view -> {
+            for (final WidgetPart part : widgetScreen.getParts()) {
+                if (part.getId() == null) {
+                    GB.toast(getBaseContext().getString(R.string.widget_missing_parts), Toast.LENGTH_LONG, GB.WARN);
+                    return;
+                }
+            }
+
+            updateWidgetScreen();
+            WidgetScreenDetailsActivity.this.setResult(Activity.RESULT_OK);
+            finish();
+        });
+
+        // Layouts
+        final List<WidgetLayout> supportedLayouts = widgetManager.getSupportedWidgetLayouts();
+        final String[] layoutStrings = new String[supportedLayouts.size()];
+        for (int i = 0; i < supportedLayouts.size(); i++) {
+            layoutStrings[i] = getBaseContext().getString(supportedLayouts.get(i).getName());
+        }
+        final ArrayAdapter<String> layoutAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, layoutStrings);
+
+        final View cardLayout = findViewById(R.id.card_layout);
+        cardLayout.setOnClickListener(view -> {
+            new MaterialAlertDialogBuilder(WidgetScreenDetailsActivity.this).setAdapter(layoutAdapter, (dialogInterface, i) -> {
+                if (widgetScreen.getLayout() != supportedLayouts.get(i)) {
+                    final ArrayList<WidgetPart> defaultParts = new ArrayList<>();
+                    for (final WidgetType widgetType : supportedLayouts.get(i).getWidgetTypes()) {
+                        defaultParts.add(new WidgetPart(null, "", widgetType));
+                    }
+                    widgetScreen.setParts(defaultParts);
+                }
+                widgetScreen.setLayout(supportedLayouts.get(i));
+                updateUiFromWidget();
+            }).setTitle(R.string.widget_layout).create().show();
+        });
+
+        cardWidgetTopLeft = findViewById(R.id.card_widget_top_left);
+        cardWidgetTopRight = findViewById(R.id.card_widget_top_right);
+        cardWidgetCenter = findViewById(R.id.card_widget_center);
+        cardWidgetBotLeft = findViewById(R.id.card_widget_bottom_left);
+        cardWidgetBotRight = findViewById(R.id.card_widget_bottom_right);
+
+        labelWidgetScreenLayout = findViewById(R.id.widget_screen_layout);
+        labelWidgetTopLeft = findViewById(R.id.widget_top_left);
+        labelWidgetTopRight = findViewById(R.id.widget_top_right);
+        labelWidgetCenter = findViewById(R.id.widget_center);
+        labelWidgetBotLeft = findViewById(R.id.widget_bottom_left);
+        labelWidgetBotRight = findViewById(R.id.widget_bottom_right);
+
+        updateUiFromWidget();
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(final MenuItem item) {
+        final int itemId = item.getItemId();
+        if (itemId == android.R.id.home) {
+            // back button
+            // TODO confirm when exiting without saving
+            finish();
+            return true;
+        }
+        return super.onOptionsItemSelected(item);
+    }
+
+    private void updateWidgetScreen() {
+        widgetManager.saveScreen(widgetScreen);
+    }
+
+    @Override
+    protected void onSaveInstanceState(@NonNull final Bundle state) {
+        super.onSaveInstanceState(state);
+        state.putSerializable("widgetScreen", widgetScreen);
+    }
+
+    @Override
+    protected void onRestoreInstanceState(@NonNull final Bundle savedInstanceState) {
+        super.onRestoreInstanceState(savedInstanceState);
+        widgetScreen = (WidgetScreen) savedInstanceState.getSerializable("widgetScreen");
+        updateUiFromWidget();
+    }
+
+    private void updateUiFromWidget() {
+        labelWidgetScreenLayout.setText(widgetScreen.getLayout().getName());
+
+        switch (widgetScreen.getLayout()) {
+            case TOP_1_BOT_2:
+            case TOP_2X2_BOT_2:
+                updateWidget(cardWidgetTopLeft, labelWidgetTopLeft, -1);
+                updateWidget(cardWidgetTopRight, labelWidgetTopRight, -1);
+                updateWidget(cardWidgetCenter, labelWidgetCenter, 0);
+                updateWidget(cardWidgetBotLeft, labelWidgetBotLeft, 1);
+                updateWidget(cardWidgetBotRight, labelWidgetBotRight, 2);
+                break;
+            case TOP_2_BOT_1:
+            case TOP_2_BOT_2X2:
+                updateWidget(cardWidgetTopLeft, labelWidgetTopLeft, 0);
+                updateWidget(cardWidgetTopRight, labelWidgetTopRight, 1);
+                updateWidget(cardWidgetCenter, labelWidgetCenter, 2);
+                updateWidget(cardWidgetBotLeft, labelWidgetBotLeft, -1);
+                updateWidget(cardWidgetBotRight, labelWidgetBotRight, -1);
+                break;
+            case TOP_2_BOT_2:
+                updateWidget(cardWidgetTopLeft, labelWidgetTopLeft, 0);
+                updateWidget(cardWidgetTopRight, labelWidgetTopRight, 1);
+                updateWidget(cardWidgetCenter, labelWidgetCenter, -1);
+                updateWidget(cardWidgetBotLeft, labelWidgetBotLeft, 2);
+                updateWidget(cardWidgetBotRight, labelWidgetBotRight, 3);
+                break;
+            case ONE_BY_TWO_SINGLE:
+            case TWO_BY_TWO_SINGLE:
+            case TWO_BY_THREE_SINGLE:
+                updateWidget(cardWidgetTopLeft, labelWidgetTopLeft, -1);
+                updateWidget(cardWidgetTopRight, labelWidgetTopRight, -1);
+                updateWidget(cardWidgetCenter, labelWidgetCenter, 0);
+                updateWidget(cardWidgetBotLeft, labelWidgetBotLeft, -1);
+                updateWidget(cardWidgetBotRight, labelWidgetBotRight, -1);
+                break;
+            case TWO:
+            case TOP_1_BOT_2X2:
+            case TOP_2X2_BOT_1:
+                updateWidget(cardWidgetTopLeft, labelWidgetTopLeft, -1);
+                updateWidget(cardWidgetTopRight, labelWidgetTopRight, 0);
+                updateWidget(cardWidgetCenter, labelWidgetCenter, 1);
+                updateWidget(cardWidgetBotLeft, labelWidgetBotLeft, -1);
+                updateWidget(cardWidgetBotRight, labelWidgetBotRight, -1);
+                break;
+            default:
+                throw new IllegalStateException("Unknown layout " + widgetScreen.getLayout());
+        }
+    }
+
+    private void updateWidget(final View card, final TextView label, final int partIdx) {
+        final boolean validPart = partIdx >= 0 && partIdx < widgetScreen.getParts().size();
+
+        card.setVisibility(validPart ? View.VISIBLE : View.GONE);
+
+        if (!validPart) {
+            card.setOnClickListener(null);
+            label.setText(R.string.not_set);
+            return;
+        }
+
+        final WidgetPart widgetPart = widgetScreen.getParts().get(partIdx);
+
+        if (widgetPart.getId() == null) {
+            label.setText(R.string.not_set);
+        } else {
+            label.setText(widgetPart.getFullName());
+        }
+
+        // Select widget part
+
+        final List<WidgetPart> supportedParts = widgetManager.getSupportedWidgetParts(widgetPart.getType());
+        final String[] layoutStrings = new String[supportedParts.size()];
+        for (int i = 0; i < supportedParts.size(); i++) {
+            layoutStrings[i] = supportedParts.get(i).getName();
+        }
+        final ArrayAdapter<String> partAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, layoutStrings);
+
+        card.setOnClickListener(view -> {
+            new MaterialAlertDialogBuilder(WidgetScreenDetailsActivity.this).setAdapter(partAdapter, (dialogInterface, i) -> {
+                final WidgetPart selectedPart = supportedParts.get(i);
+
+                final List<WidgetPartSubtype> supportedSubtypes = selectedPart.getSupportedSubtypes();
+                if (supportedSubtypes.isEmpty()) {
+                    // No subtypes selected
+
+                    widgetScreen.getParts().set(partIdx, selectedPart);
+                    updateUiFromWidget();
+                    return;
+                }
+
+                // If the selected part supports subtypes, the user must select a subtype
+
+                final String[] subtypeStrings = new String[supportedSubtypes.size()];
+                for (int j = 0; j < supportedSubtypes.size(); j++) {
+                    subtypeStrings[j] = supportedSubtypes.get(j).getName();
+                }
+                final ArrayAdapter<String> subtypesAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, subtypeStrings);
+
+                new MaterialAlertDialogBuilder(WidgetScreenDetailsActivity.this).setAdapter(subtypesAdapter, (dialogInterface1, j) -> {
+                    final WidgetPartSubtype selectedSubtype = supportedSubtypes.get(j);
+                    selectedPart.setSubtype(selectedSubtype);
+                    widgetScreen.getParts().set(partIdx, selectedPart);
+                    updateUiFromWidget();
+                }).setTitle(R.string.widget_subtype).create().show();
+            }).setTitle(R.string.widget).create().show();
+        });
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/widgets/WidgetScreenListAdapter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/widgets/WidgetScreenListAdapter.java
new file mode 100644
index 0000000..2c4e019
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/widgets/WidgetScreenListAdapter.java
@@ -0,0 +1,124 @@
+/*  Copyright (C) 2023-2024 Andreas Shimokawa, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities.widgets;
+
+import android.content.Context;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.google.android.material.card.MaterialCardView;
+import com.google.android.material.dialog.MaterialAlertDialogBuilder;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.capabilities.widgets.WidgetPart;
+import nodomain.freeyourgadget.gadgetbridge.capabilities.widgets.WidgetScreen;
+
+public class WidgetScreenListAdapter extends RecyclerView.Adapter<WidgetScreenListAdapter.ViewHolder> {
+
+    private final Context mContext;
+    private ArrayList<WidgetScreen> widgetScreenList;
+
+    public WidgetScreenListAdapter(Context context) {
+        this.mContext = context;
+    }
+
+    public void setWidgetScreenList(List<WidgetScreen> widgetScreens) {
+        this.widgetScreenList = new ArrayList<>(widgetScreens);
+    }
+
+    public ArrayList<WidgetScreen> getWidgetScreenList() {
+        return widgetScreenList;
+    }
+
+    @NonNull
+    @Override
+    public WidgetScreenListAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+        final View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_widget_screen, parent, false);
+        return new ViewHolder(view);
+    }
+
+    @Override
+    public void onBindViewHolder(@NonNull ViewHolder holder, final int position) {
+        final WidgetScreen widgetScreen = widgetScreenList.get(position);
+
+        holder.container.setOnClickListener(v -> ((WidgetScreensListActivity) mContext).configureWidgetScreen(widgetScreen));
+
+        holder.container.setOnLongClickListener(v -> {
+            // TODO move up
+            // TODO move down
+            new MaterialAlertDialogBuilder(v.getContext())
+                    .setTitle(R.string.widget_screen_delete_confirm_title)
+                    .setMessage(mContext.getString(
+                            R.string.widget_screen_delete_confirm_description,
+                            mContext.getString(R.string.widget_screen_x, widgetScreen.getId())
+                    ))
+                    .setIcon(R.drawable.ic_warning)
+                    .setPositiveButton(android.R.string.yes, (dialog, whichButton) -> {
+                        ((WidgetScreensListActivity) mContext).deleteWidgetScreen(widgetScreen);
+                    })
+                    .setNegativeButton(android.R.string.no, null)
+                    .show();
+
+            return true;
+        });
+
+        holder.widgetScreenName.setText(mContext.getString(R.string.widget_screen_x, widgetScreen.getId()));
+        final List<String> widgetNames = new ArrayList<>();
+
+        for (final WidgetPart part : widgetScreen.getParts()) {
+            if (part.getId() != null) {
+                widgetNames.add(part.getFullName());
+            }
+        }
+
+        if (!widgetNames.isEmpty()) {
+            holder.widgetScreenDescription.setText(TextUtils.join(", ", widgetNames));
+        } else {
+            holder.widgetScreenDescription.setText(R.string.unknown);
+        }
+    }
+
+    @Override
+    public int getItemCount() {
+        return widgetScreenList.size();
+    }
+
+    static class ViewHolder extends RecyclerView.ViewHolder {
+        final MaterialCardView container;
+
+        final TextView widgetScreenName;
+        final TextView widgetScreenDescription;
+
+        ViewHolder(View view) {
+            super(view);
+
+            container = view.findViewById(R.id.card_widget_screen);
+
+            widgetScreenName = view.findViewById(R.id.widget_screen_name);
+            widgetScreenDescription = view.findViewById(R.id.widget_screen_description);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/widgets/WidgetScreensListActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/widgets/WidgetScreensListActivity.java
new file mode 100644
index 0000000..eb4f3eb
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/widgets/WidgetScreensListActivity.java
@@ -0,0 +1,183 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.activities.widgets;
+
+import android.annotation.SuppressLint;
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.MenuItem;
+
+import androidx.activity.result.ActivityResult;
+import androidx.activity.result.ActivityResultCallback;
+import androidx.activity.result.ActivityResultLauncher;
+import androidx.activity.result.contract.ActivityResultContracts;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.google.android.material.dialog.MaterialAlertDialogBuilder;
+import com.google.android.material.floatingactionbutton.FloatingActionButton;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBActivity;
+import nodomain.freeyourgadget.gadgetbridge.capabilities.widgets.WidgetLayout;
+import nodomain.freeyourgadget.gadgetbridge.capabilities.widgets.WidgetManager;
+import nodomain.freeyourgadget.gadgetbridge.capabilities.widgets.WidgetPart;
+import nodomain.freeyourgadget.gadgetbridge.capabilities.widgets.WidgetScreen;
+import nodomain.freeyourgadget.gadgetbridge.capabilities.widgets.WidgetType;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class WidgetScreensListActivity extends AbstractGBActivity {
+    private static final Logger LOG = LoggerFactory.getLogger(WidgetScreensListActivity.class);
+
+    private WidgetScreenListAdapter mGBWidgetScreenListAdapter;
+    private GBDevice gbDevice;
+    private WidgetManager widgetManager;
+
+    private ActivityResultLauncher<Intent> configureWidgetScreenLauncher;
+    private final ActivityResultCallback<ActivityResult> configureWidgetCallback = result -> {
+        if (result.getResultCode() == Activity.RESULT_OK) {
+            updateWidgetScreensFromManager();
+            sendWidgetsToDevice();
+        }
+    };
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.activity_widget_screens_list);
+
+        gbDevice = getIntent().getParcelableExtra(GBDevice.EXTRA_DEVICE);
+        if (gbDevice == null) {
+            LOG.error("gbDevice must not be null");
+            finish();
+            return;
+        }
+
+        widgetManager = gbDevice.getDeviceCoordinator().getWidgetManager(gbDevice);
+        if (widgetManager == null) {
+            LOG.error("widgetManager must not be null");
+            finish();
+            return;
+        }
+
+        configureWidgetScreenLauncher = registerForActivityResult(
+                new ActivityResultContracts.StartActivityForResult(),
+                configureWidgetCallback
+        );
+
+        mGBWidgetScreenListAdapter = new WidgetScreenListAdapter(this);
+
+        final RecyclerView widgetsRecyclerView = findViewById(R.id.widget_screens_list);
+        widgetsRecyclerView.setHasFixedSize(true);
+        widgetsRecyclerView.setLayoutManager(new LinearLayoutManager(this));
+        widgetsRecyclerView.setAdapter(mGBWidgetScreenListAdapter);
+        updateWidgetScreensFromManager();
+
+        final FloatingActionButton fab = findViewById(R.id.fab);
+        fab.setOnClickListener(v -> {
+            int deviceSlots = widgetManager.getMaxScreens();
+
+            if (mGBWidgetScreenListAdapter.getItemCount() >= deviceSlots) {
+                // No more free slots
+                new MaterialAlertDialogBuilder(v.getContext())
+                        .setTitle(R.string.reminder_no_free_slots_title)
+                        .setMessage(getBaseContext().getString(R.string.widget_screen_no_free_slots_description, String.format(Locale.getDefault(), "%d", deviceSlots)))
+                        .setIcon(R.drawable.ic_warning)
+                        .setPositiveButton(android.R.string.ok, (dialog, whichButton) -> {
+                        })
+                        .show();
+                return;
+            }
+
+            final WidgetLayout defaultLayout = widgetManager.getSupportedWidgetLayouts().get(0);
+            final ArrayList<WidgetPart> defaultParts = new ArrayList<>();
+            for (final WidgetType widgetType : defaultLayout.getWidgetTypes()) {
+                defaultParts.add(new WidgetPart(null, "", widgetType));
+            }
+            final WidgetScreen widgetScreen = new WidgetScreen(
+                    null,
+                    defaultLayout,
+                    defaultParts
+            );
+
+            configureWidgetScreen(widgetScreen);
+        });
+    }
+
+    /**
+     * Reads the available widgets from the database and updates the view afterwards.
+     */
+    @SuppressLint("NotifyDataSetChanged")
+    private void updateWidgetScreensFromManager() {
+        final List<WidgetScreen> widgetScreens = widgetManager.getWidgetScreens();
+
+        mGBWidgetScreenListAdapter.setWidgetScreenList(widgetScreens);
+        mGBWidgetScreenListAdapter.notifyDataSetChanged();
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(final MenuItem item) {
+        final int itemId = item.getItemId();
+        if (itemId == android.R.id.home) {
+            // back button
+            finish();
+            return true;
+        }
+        return super.onOptionsItemSelected(item);
+    }
+
+    public void configureWidgetScreen(final WidgetScreen widgetScreen) {
+        final Intent startIntent = new Intent(getApplicationContext(), WidgetScreenDetailsActivity.class);
+        startIntent.putExtra(GBDevice.EXTRA_DEVICE, gbDevice);
+        startIntent.putExtra(WidgetScreen.EXTRA_WIDGET_SCREEN, widgetScreen);
+        configureWidgetScreenLauncher.launch(startIntent);
+    }
+
+    public void deleteWidgetScreen(final WidgetScreen widgetScreen) {
+        if (mGBWidgetScreenListAdapter.getItemCount() - 1 < widgetManager.getMinScreens()) {
+            // Under minimum slots
+            new MaterialAlertDialogBuilder(this)
+                    .setTitle(R.string.widget_screen_delete_confirm_title)
+                    .setMessage(this.getString(R.string.widget_screen_min_screens, String.format(Locale.getDefault(), "%d", widgetManager.getMinScreens())))
+                    .setIcon(R.drawable.ic_warning)
+                    .setPositiveButton(android.R.string.ok, (dialog, whichButton) -> {
+                    })
+                    .show();
+            return;
+        }
+
+        widgetManager.deleteScreen(widgetScreen);
+
+        updateWidgetScreensFromManager();
+        sendWidgetsToDevice();
+    }
+
+    private void sendWidgetsToDevice() {
+        if (gbDevice.isInitialized()) {
+            widgetManager.sendToDevice();
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/workouts/WorkoutValueFormatter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/workouts/WorkoutValueFormatter.java
new file mode 100644
index 0000000..42df33a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/workouts/WorkoutValueFormatter.java
@@ -0,0 +1,159 @@
+package nodomain.freeyourgadget.gadgetbridge.activities.workouts;
+
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.UNIT_CM;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.UNIT_KG;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.UNIT_KILOMETERS;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.UNIT_METERS;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.UNIT_METERS_PER_SECOND;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.UNIT_SECONDS_PER_KM;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.UNIT_SECONDS_PER_M;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.text.DecimalFormat;
+import java.util.Locale;
+import java.util.concurrent.TimeUnit;
+
+import nodomain.freeyourgadget.gadgetbridge.BuildConfig;
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.SettingsActivity;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries;
+import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
+
+public class WorkoutValueFormatter {
+    private static final Logger LOG = LoggerFactory.getLogger(WorkoutValueFormatter.class);
+
+    private boolean show_raw_data = false;
+
+    private final String units;
+    private final String UNIT_IMPERIAL;
+    private final DecimalFormat df = new DecimalFormat("#.##");
+
+    public WorkoutValueFormatter() {
+        this.units = GBApplication.getPrefs().getString(SettingsActivity.PREF_MEASUREMENT_SYSTEM, GBApplication.getContext().getString(R.string.p_unit_metric));
+        this.UNIT_IMPERIAL = GBApplication.getContext().getString(R.string.p_unit_imperial);
+    }
+
+    public void toggleRawData() {
+        this.show_raw_data = !show_raw_data;
+    }
+
+    public String formatValue(final Object rawValue, String unit) {
+        if (rawValue == null) {
+            return GBApplication.getContext().getString(R.string.stats_empty_value);
+        }
+
+        if (ActivitySummaryEntries.UNIT_RAW_STRING.equals(unit)) {
+            return String.valueOf(rawValue);
+        }
+
+        if (rawValue instanceof CharSequence || ActivitySummaryEntries.UNIT_STRING.equals(unit)) {
+            // we could optimize here a bit and only do this for particular activities (swim at the moment...)
+            try {
+                return getStringResourceByName(String.valueOf(rawValue));
+            } catch (final Exception e) {
+                LOG.error("Failed to get string resource by name for {}", rawValue);
+                return String.valueOf(rawValue);
+            }
+        }
+
+        if (!(rawValue instanceof Number)) {
+            return String.valueOf(rawValue);
+        }
+
+        double value = ((Number) rawValue).doubleValue();
+
+        if (!show_raw_data) {
+            //special casing here + imperial units handling
+            switch (unit) {
+                case UNIT_KG:
+                    if (units.equals(UNIT_IMPERIAL)) {
+                        value = value * 2.2046226f;
+                        unit = "lb";
+                    }
+                    break;
+                case UNIT_CM:
+                    if (units.equals(UNIT_IMPERIAL)) {
+                        value = value * 0.0328084;
+                        unit = "ft";
+                    }
+                    break;
+                case UNIT_METERS_PER_SECOND:
+                    if (units.equals(UNIT_IMPERIAL)) {
+                        value = value * 2.236936D;
+                        unit = "mi_h";
+                    } else { //metric
+                        value = value * 3.6;
+                        unit = "km_h";
+                    }
+                    break;
+                case UNIT_SECONDS_PER_M:
+                    if (units.equals(UNIT_IMPERIAL)) {
+                        value = value * (1609.344 / 60D);
+                        unit = "minutes_mi";
+                    } else { //metric
+                        value = value * (1000 / 60D);
+                        unit = "minutes_km";
+                    }
+                    break;
+                case UNIT_SECONDS_PER_KM:
+                    if (units.equals(UNIT_IMPERIAL)) {
+                        value = value / 60D * 1.609344;
+                        unit = "minutes_mi";
+                    } else { //metric
+                        value = value / 60D;
+                        unit = "minutes_km";
+                    }
+                    break;
+                case UNIT_KILOMETERS:
+                    if (units.equals(UNIT_IMPERIAL)) {
+                        value = value * 0.621371D;
+                        unit = "mi";
+                    }
+                    break;
+                case UNIT_METERS:
+                    if (units.equals(UNIT_IMPERIAL)) {
+                        value = value * 3.28084D;
+                        unit = "ft";
+                        if (value > 6000) {
+                            value = value * 0.0001893939D;
+                            unit = "mi";
+                        }
+                    } else { //metric
+                        if (value > 2000) {
+                            value = value / 1000;
+                            unit = "km";
+                        }
+                    }
+                    break;
+            }
+        }
+
+        if (unit.equals("seconds") && !show_raw_data) { //rather then plain seconds, show formatted duration
+            return DateTimeUtils.formatDurationHoursMinutes((long) value, TimeUnit.SECONDS);
+        } else if (unit.equals("minutes_km") || unit.equals("minutes_mi")) {
+            // Format pace
+            return String.format(
+                    Locale.getDefault(),
+                    "%d:%02d %s",
+                    (int) Math.floor(value), (int) Math.round(60 * (value - (int) Math.floor(value))),
+                    getStringResourceByName(unit)
+            );
+        } else {
+            return String.format("%s %s", df.format(value), getStringResourceByName(unit));
+        }
+    }
+
+    public String getStringResourceByName(String aString) {
+        String packageName = BuildConfig.APPLICATION_ID;
+        int resId = GBApplication.getContext().getResources().getIdentifier(aString, "string", packageName);
+        if (resId == 0) {
+            //LOG.warn("SportsActivity " + "Missing string in strings:" + aString);
+            return aString;
+        } else {
+            return GBApplication.getContext().getString(resId);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/workouts/entries/ActivitySummaryEntry.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/workouts/entries/ActivitySummaryEntry.java
new file mode 100644
index 0000000..1c64c84
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/workouts/entries/ActivitySummaryEntry.java
@@ -0,0 +1,23 @@
+package nodomain.freeyourgadget.gadgetbridge.activities.workouts.entries;
+
+import android.widget.LinearLayout;
+
+import nodomain.freeyourgadget.gadgetbridge.activities.workouts.WorkoutValueFormatter;
+
+public abstract class ActivitySummaryEntry {
+    private final String group;
+
+    public ActivitySummaryEntry(final String group) {
+        this.group = group;
+    }
+
+    public String getGroup() {
+        return group;
+    }
+
+    public abstract int getColumnSpan();
+
+    public abstract void populate(final String key,
+                                  final LinearLayout linearLayout,
+                                  final WorkoutValueFormatter workoutValueFormatter);
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/workouts/entries/ActivitySummaryProgressEntry.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/workouts/entries/ActivitySummaryProgressEntry.java
new file mode 100644
index 0000000..b1fbcb8
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/workouts/entries/ActivitySummaryProgressEntry.java
@@ -0,0 +1,78 @@
+package nodomain.freeyourgadget.gadgetbridge.activities.workouts.entries;
+
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.view.Gravity;
+import android.view.View;
+import android.widget.LinearLayout;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+
+import nodomain.freeyourgadget.gadgetbridge.activities.workouts.WorkoutValueFormatter;
+
+public class ActivitySummaryProgressEntry extends ActivitySummarySimpleEntry {
+    private final int progress;
+    private int color;
+
+    public ActivitySummaryProgressEntry(final Object value, final String unit, final int progress) {
+        this(null, value, unit, progress);
+    }
+
+    public ActivitySummaryProgressEntry(final String group, final Object value, final String unit, final int progress) {
+        super(group, value, unit);
+        this.progress = progress;
+    }
+
+    public ActivitySummaryProgressEntry(final Object value, final String unit, final int progress, final int color) {
+        this(null, value, unit, progress);
+        this.color = color;
+    }
+
+    public int getProgress() {
+        return progress;
+    }
+
+    @Override
+    public int getColumnSpan() {
+        return 2;
+    }
+
+    @Override
+    public void populate(final String key, final LinearLayout linearLayout, final WorkoutValueFormatter workoutValueFormatter) {
+        final Context context = linearLayout.getContext();
+
+        // Label
+        final TextView labelTextView = new TextView(context);
+        labelTextView.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT));
+        labelTextView.setTextSize(12);
+        labelTextView.setText(workoutValueFormatter.getStringResourceByName(key));
+
+        // Value
+        final TextView valueTextView = new TextView(context);
+        valueTextView.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT));
+        valueTextView.setText(String.format("%s", "-"));
+        valueTextView.setTextSize(12);
+        valueTextView.setGravity(Gravity.END);
+        valueTextView.setText(workoutValueFormatter.formatValue(getValue(), getUnit()));
+
+        // Layout for the labels, so the value is at the right
+        final LinearLayout labelsLinearLayout = new LinearLayout(context);
+        labelsLinearLayout.setOrientation(LinearLayout.HORIZONTAL);
+        labelsLinearLayout.addView(labelTextView);
+        labelsLinearLayout.addView(valueTextView);
+
+        final LinearLayout progressLayout = new LinearLayout(context);
+        final ProgressBar progressBar = new ProgressBar(context, null, android.R.attr.progressBarStyleHorizontal);
+        progressBar.setIndeterminate(false);
+        progressBar.setProgress(progress);
+        progressBar.setVisibility(View.VISIBLE);
+        if (color != 0) {
+            progressBar.setProgressTintList(ColorStateList.valueOf(color));
+        }
+        LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
+        progressLayout.addView(progressBar, params);
+
+        linearLayout.addView(labelsLinearLayout);
+        linearLayout.addView(progressLayout);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/workouts/entries/ActivitySummarySimpleEntry.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/workouts/entries/ActivitySummarySimpleEntry.java
new file mode 100644
index 0000000..4dcc441
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/workouts/entries/ActivitySummarySimpleEntry.java
@@ -0,0 +1,57 @@
+package nodomain.freeyourgadget.gadgetbridge.activities.workouts.entries;
+
+import android.content.Context;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.workouts.WorkoutValueFormatter;
+
+public class ActivitySummarySimpleEntry extends ActivitySummaryEntry {
+    private final Object value;
+    private final String unit;
+
+    public ActivitySummarySimpleEntry(final Object value, final String unit) {
+        this(null, value, unit);
+    }
+
+    public ActivitySummarySimpleEntry(final String group, final Object value, final String unit) {
+        super(group);
+        this.value = value;
+        this.unit = unit;
+    }
+
+    public Object getValue() {
+        return value;
+    }
+
+    public String getUnit() {
+        return unit;
+    }
+
+    @Override
+    public int getColumnSpan() {
+        return 1;
+    }
+
+    @Override
+    public void populate(final String key, final LinearLayout linearLayout, final WorkoutValueFormatter workoutValueFormatter) {
+        final Context context = linearLayout.getContext();
+
+        // Value
+        final TextView valueTextView = new TextView(context);
+        valueTextView.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT));
+        valueTextView.setText(context.getString(R.string.stats_empty_value));
+        valueTextView.setTextSize(20);
+        valueTextView.setText(workoutValueFormatter.formatValue(value, unit));
+
+        // Label
+        final TextView labelTextView = new TextView(context);
+        labelTextView.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT));
+        labelTextView.setTextSize(12);
+        labelTextView.setText(workoutValueFormatter.getStringResourceByName(key));
+
+        linearLayout.addView(valueTextView);
+        linearLayout.addView(labelTextView);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/workouts/entries/ActivitySummaryTableRowEntry.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/workouts/entries/ActivitySummaryTableRowEntry.java
new file mode 100644
index 0000000..14a4f8e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/workouts/entries/ActivitySummaryTableRowEntry.java
@@ -0,0 +1,72 @@
+package nodomain.freeyourgadget.gadgetbridge.activities.workouts.entries;
+
+import android.graphics.Typeface;
+import android.view.Gravity;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import androidx.gridlayout.widget.GridLayout;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.activities.workouts.WorkoutValueFormatter;
+
+public class ActivitySummaryTableRowEntry extends ActivitySummaryEntry {
+    private final List<ActivitySummaryValue> columns;
+    private final boolean isHeader;
+    private final boolean boldFirstColumn;
+
+    public ActivitySummaryTableRowEntry(final List<ActivitySummaryValue> columns) {
+        this(null, columns, false, false);
+    }
+
+    public ActivitySummaryTableRowEntry(final String group,
+                                        final List<ActivitySummaryValue> columns,
+                                        final boolean isHeader,
+                                        final boolean boldFirstColumn) {
+        super(group);
+        this.columns = columns;
+        this.isHeader = isHeader;
+        this.boldFirstColumn = boldFirstColumn;
+    }
+
+    @Override
+    public int getColumnSpan() {
+        return 2;
+    }
+
+    @Override
+    public void populate(final String key, final LinearLayout linearLayout, final WorkoutValueFormatter workoutValueFormatter) {
+        final GridLayout rowLayout = new GridLayout(linearLayout.getContext());
+        rowLayout.setColumnCount(columns.size());
+        rowLayout.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
+
+        for (int i = 0; i < columns.size(); i++) {
+            final LinearLayout cellLayout = new LinearLayout(linearLayout.getContext());
+            final GridLayout.LayoutParams columnParams = new GridLayout.LayoutParams();
+            columnParams.columnSpec = GridLayout.spec(i, columns.size());
+            final GridLayout.LayoutParams layoutParams = new GridLayout.LayoutParams(
+                    GridLayout.spec(GridLayout.UNDEFINED, GridLayout.FILL, 1f),
+                    GridLayout.spec(GridLayout.UNDEFINED, 1, GridLayout.FILL, 1f)
+            );
+            layoutParams.width = 0;
+            cellLayout.setLayoutParams(layoutParams);
+            cellLayout.setOrientation(LinearLayout.VERTICAL);
+            cellLayout.setGravity(Gravity.CENTER);
+
+            final TextView columnTextView = new TextView(linearLayout.getContext());
+            columnTextView.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT));
+            columnTextView.setText(columns.get(i).format(workoutValueFormatter));
+            columnTextView.setTextSize(12);
+            if (isHeader || (i == 0 && boldFirstColumn)) {
+                columnTextView.setTypeface(null, Typeface.BOLD);
+            }
+
+            cellLayout.addView(columnTextView);
+            rowLayout.addView(cellLayout);
+        }
+
+        linearLayout.addView(rowLayout);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/workouts/entries/ActivitySummaryValue.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/workouts/entries/ActivitySummaryValue.java
new file mode 100644
index 0000000..520f2d8
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/workouts/entries/ActivitySummaryValue.java
@@ -0,0 +1,22 @@
+package nodomain.freeyourgadget.gadgetbridge.activities.workouts.entries;
+
+import nodomain.freeyourgadget.gadgetbridge.activities.workouts.WorkoutValueFormatter;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries;
+
+public class ActivitySummaryValue {
+    private final Object value;
+    private final String unit;
+
+    public ActivitySummaryValue(final Object value, final String unit) {
+        this.value = value;
+        this.unit = unit;
+    }
+
+    public ActivitySummaryValue(final String value) {
+        this(value, ActivitySummaryEntries.UNIT_STRING);
+    }
+
+    public String format(final WorkoutValueFormatter formatter) {
+        return formatter.formatValue(value, unit);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/AbstractActivityListingAdapter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/AbstractActivityListingAdapter.java
new file mode 100644
index 0000000..8591893
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/AbstractActivityListingAdapter.java
@@ -0,0 +1,181 @@
+/*  Copyright (C) 2020-2024 Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.adapter;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+
+import java.util.ArrayList;
+import java.util.BitSet;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+
+/**
+ * Adapter for displaying generic ItemWithDetails instances.
+ */
+public abstract class AbstractActivityListingAdapter<T> extends RecyclerView.Adapter<AbstractActivityListingAdapter.AbstractActivityListingViewHolder<T>> {
+    private final Context context;
+    private final List<T> items;
+    private final BitSet selectedItems = new BitSet();
+
+    private OnItemClickListener onItemSingleClickListener;
+    private OnItemClickListener onItemLongClickListener;
+
+    public AbstractActivityListingAdapter(Context context) {
+        this.context = context;
+        this.items = new ArrayList<T>();
+    }
+
+    public void setOnItemClickListener(final OnItemClickListener onItemSingleClickListener) {
+        this.onItemSingleClickListener = onItemSingleClickListener;
+    }
+
+    public void setOnItemLongClickListener(final OnItemClickListener onItemLongClickListener) {
+        this.onItemLongClickListener = onItemLongClickListener;
+    }
+
+    public Context getContext() {
+        return context;
+    }
+
+    public BitSet getSelectedItems() {
+        return selectedItems;
+    }
+
+    @Override
+    public int getItemCount() {
+        return items.size();
+    }
+
+    public T getItem(final int position) {
+        return items.get(position);
+    }
+
+    public int getPosition(final T item) {
+        return items.indexOf(item);
+    }
+
+    public boolean isSelected(final int position) {
+        return selectedItems.get(position);
+    }
+
+    @Override
+    public int getItemViewType(final int position) {
+        if (position == 0) {
+            // First item is always the dashboard
+            return 0;
+        } else if (position == getItemCount() - 1) {
+            // Last item is always an empty session (prevent overlap with fab)
+            return 1;
+        }
+
+        return 2;
+    }
+
+    @NonNull
+    @Override
+    public AbstractActivityListingViewHolder<T> onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) {
+        switch (viewType) {
+            case 1: // empty
+                return new EmptyViewHolder(LayoutInflater.from(getContext()).inflate(R.layout.activity_list_item, parent, false));
+        }
+
+        throw new IllegalArgumentException("Unknown view type " + viewType);
+    }
+
+    @Override
+    public void onBindViewHolder(@NonNull final AbstractActivityListingViewHolder<T> holder, int position) {
+        holder.fill(position, getItem(position), isSelected(position));
+        if (position > 0 && position < getItemCount() - 1) {
+            if (onItemSingleClickListener != null) {
+                holder.itemView.setOnClickListener(v -> onItemSingleClickListener.onClick(position));
+            }
+            if (onItemLongClickListener != null) {
+                holder.itemView.setOnLongClickListener(v -> {
+                    onItemLongClickListener.onClick(position);
+                    return true;
+                });
+            }
+        }
+    }
+
+    public List<T> getItems() {
+        return items;
+    }
+
+    public void loadItems() {
+    }
+
+    public void setItems(List<T> items, boolean notify) {
+        this.items.clear();
+        this.items.addAll(items);
+        this.selectedItems.clear();
+        if (notify) {
+            notifyDataSetChanged();
+        }
+    }
+
+    public void setActivityKindFilter(int activityKind) {
+    }
+
+    public void setDateFromFilter(long date) {
+    }
+
+    public void setDateToFilter(long date) {
+    }
+
+    public void setNameContainsFilter(String name) {
+    }
+
+    public void setItemsFilter(List<Long> items) {
+    }
+
+    public void setDeviceFilter(long device) {
+    }
+
+    public abstract static class AbstractActivityListingViewHolder<T> extends RecyclerView.ViewHolder {
+        public AbstractActivityListingViewHolder(@NonNull final View itemView) {
+            super(itemView);
+        }
+
+        public abstract void fill(int position, T item, final boolean selected);
+    }
+
+    public interface OnItemClickListener {
+        void onClick(int position);
+    }
+
+    public class EmptyViewHolder extends AbstractActivityListingViewHolder<T> {
+        final View rootView;
+
+        public EmptyViewHolder(@NonNull final View itemView) {
+            super(itemView);
+            this.rootView = itemView;
+        }
+
+        @Override
+        public void fill(final int position, final T item, final boolean selected) {
+            rootView.setVisibility(View.GONE);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/AbstractItemAdapter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/AbstractItemAdapter.java
new file mode 100644
index 0000000..dbbbeb3
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/AbstractItemAdapter.java
@@ -0,0 +1,135 @@
+/*  Copyright (C) 2017-2024 Arjan Schrijver, Carsten Pfeiffer, Daniele
+    Gobbetti, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.adapter;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import androidx.annotation.DrawableRes;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+
+/**
+ * Adapter for displaying generic ItemWithDetails instances.
+ */
+public abstract class AbstractItemAdapter<T> extends ArrayAdapter<T> {
+
+    public static final int SIZE_SMALL = 1;
+    public static final int SIZE_MEDIUM = 2;
+    public static final int SIZE_LARGE = 3;
+    private final Context context;
+    private final List<T> items;
+    private boolean horizontalAlignment;
+    private int size = SIZE_MEDIUM;
+    private int backgroundColor=0;
+
+    public AbstractItemAdapter(Context context) {
+        this (context, new ArrayList<T>());
+    }
+
+    public AbstractItemAdapter(Context context, List<T> items) {
+        super(context, 0, items);
+
+        this.context = context;
+        this.items = items;
+    }
+
+    public void setHorizontalAlignment(boolean horizontalAlignment) {
+        this.horizontalAlignment = horizontalAlignment;
+    }
+
+
+
+    @Override
+    public View getView(int position, View view, ViewGroup parent) {
+        T item = getItem(position);
+
+        if (view == null) {
+            LayoutInflater inflater = (LayoutInflater) context
+                    .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+
+            if (horizontalAlignment) {
+                view = inflater.inflate(R.layout.item_with_details_horizontal, parent, false);
+            } else {
+                switch (size) {
+                    case SIZE_SMALL:
+                        view = inflater.inflate(R.layout.item_with_details_small, parent, false);
+                        break;
+                    default:
+                        view = inflater.inflate(R.layout.item_with_details, parent, false);
+                        break;
+                }
+            }
+        }
+        ImageView iconView = (ImageView) view.findViewById(R.id.item_image);
+        TextView nameView = (TextView) view.findViewById(R.id.item_name);
+        TextView detailsView = (TextView) view.findViewById(R.id.item_details);
+        ImageView previewView = (ImageView) view.findViewById(R.id.item_preview);
+
+        nameView.setText(getName(item));
+        detailsView.setText(getDetails(item));
+        iconView.setImageResource(getIcon(item));
+        iconView.setBackgroundColor(backgroundColor);
+        previewView.setImageBitmap(getPreview(item));
+
+        return view;
+    }
+
+    protected abstract String getName(T item);
+
+    protected abstract String getDetails(T item);
+
+    @DrawableRes
+    protected abstract int getIcon(T item);
+
+    protected abstract Bitmap getPreview(T item);
+
+    public void setSize(int size) {
+        this.size = size;
+    }
+
+    public void setBackgroundColor(int backgroundColor) {
+        this.backgroundColor = backgroundColor;
+    }
+    public int getSize() {
+        return size;
+    }
+    public List<T> getItems() {
+        return items;
+    }
+
+    public void loadItems() {
+    }
+
+    public void setItems(List<T> items, boolean notify) {
+        this.items.clear();
+        this.items.addAll(items);
+        if (notify) {
+            notifyDataSetChanged();
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/ActivitySummariesAdapter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/ActivitySummariesAdapter.java
new file mode 100644
index 0000000..bef1e03
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/ActivitySummariesAdapter.java
@@ -0,0 +1,394 @@
+/*  Copyright (C) 2017-2024 Carsten Pfeiffer, Daniel Dakhno, Daniele Gobbetti,
+    Jos茅 Rebelo, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.adapter;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.text.DecimalFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import de.greenrobot.dao.query.QueryBuilder;
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary;
+import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummaryDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityListItem;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryData;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryJsonSummary;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryParser;
+import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.FormatUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+import static nodomain.freeyourgadget.gadgetbridge.activities.ActivitySummariesFilter.ALL_DEVICES;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.INTERNAL_HAS_GPS;
+
+import androidx.annotation.NonNull;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+
+public class ActivitySummariesAdapter extends AbstractActivityListingAdapter<BaseActivitySummary> {
+    protected static final Logger LOG = LoggerFactory.getLogger(ActivitySummariesAdapter.class);
+    private final GBDevice device;
+    long dateFromFilter = 0;
+    long dateToFilter = 0;
+    long deviceFilter;
+    String nameContainsFilter;
+    List<Long> itemsFilter;
+    private int activityKindFilter;
+
+    public ActivitySummariesAdapter(Context context, GBDevice device, int activityKindFilter, long dateFromFilter, long dateToFilter, String nameContainsFilter, long deviceFilter, List itemsFilter) {
+        super(context);
+        this.device = device;
+        this.activityKindFilter = activityKindFilter;
+        this.dateFromFilter = dateFromFilter;
+        this.dateToFilter = dateToFilter;
+        this.nameContainsFilter = nameContainsFilter;
+        this.deviceFilter = deviceFilter;
+        this.itemsFilter = itemsFilter;
+        loadItems();
+    }
+
+    @Override
+    public void loadItems() {
+        try (DBHandler handler = GBApplication.acquireDB()) {
+            BaseActivitySummaryDao summaryDao = handler.getDaoSession().getBaseActivitySummaryDao();
+            Device dbDevice = DBHelper.findDevice(device, handler.getDaoSession());
+
+            QueryBuilder<BaseActivitySummary> qb = summaryDao.queryBuilder();
+
+            if (deviceFilter == ALL_DEVICES) {
+                qb.orderDesc(BaseActivitySummaryDao.Properties.StartTime);
+            } else if (deviceFilter != 0) {
+                qb.where(
+                        BaseActivitySummaryDao.Properties.DeviceId.eq(
+                                deviceFilter)).orderDesc(BaseActivitySummaryDao.Properties.StartTime);
+            } else {
+                qb.where(
+                        BaseActivitySummaryDao.Properties.DeviceId.eq(
+                                dbDevice.getId())).orderDesc(BaseActivitySummaryDao.Properties.StartTime);
+            }
+
+            if (activityKindFilter != 0) {
+                qb.where(
+                        BaseActivitySummaryDao.Properties.ActivityKind.eq(activityKindFilter));
+            }
+
+            if (dateFromFilter != 0) {
+                qb.where(
+                        BaseActivitySummaryDao.Properties.StartTime.gt(new Date(dateFromFilter)));
+            }
+            if (dateToFilter != 0) {
+                qb.where(
+                        BaseActivitySummaryDao.Properties.EndTime.lt(new Date(dateToFilter)));
+            }
+            if (nameContainsFilter != null && !nameContainsFilter.isEmpty()) {
+                qb.where(
+                        BaseActivitySummaryDao.Properties.Name.like("%" + nameContainsFilter + "%"));
+            }
+            if (itemsFilter != null) {
+                qb.where(
+                        BaseActivitySummaryDao.Properties.Id.in(itemsFilter));
+            }
+
+
+            List<BaseActivitySummary> allSummaries = new ArrayList<>();
+            // HACK: Populate json in a dummy summary, so stats load faster
+            BaseActivitySummary dashboardSummary = new BaseActivitySummary();
+            final List<BaseActivitySummary> summaries = qb.build().list();
+            dashboardSummary.setSummaryData(StatsContainer.from(
+                    device.getDeviceCoordinator().getActivitySummaryParser(device, getContext()),
+                    summaries
+            ).toJson());
+            allSummaries.add(dashboardSummary); // dashboard
+            allSummaries.addAll(summaries);
+            allSummaries.add(new BaseActivitySummary()); // empty
+            setItems(allSummaries, true);
+        } catch (Exception e) {
+            GB.toast("Error loading activity summaries.", Toast.LENGTH_SHORT, GB.ERROR, e);
+        }
+    }
+
+    @NonNull
+    @Override
+    public AbstractActivityListingViewHolder<BaseActivitySummary> onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) {
+        switch (viewType) {
+            case 0: // dashboard
+                return new DashboardViewHolder(LayoutInflater.from(getContext()).inflate(R.layout.activity_summary_dashboard_item, parent, false));
+            case 2: // item
+                return new ActivityItemViewHolder(LayoutInflater.from(getContext()).inflate(R.layout.activity_list_item, parent, false));
+        }
+
+        return super.onCreateViewHolder(parent, viewType);
+    }
+
+    public void setActivityKindFilter(int filter) {
+        this.activityKindFilter = filter;
+    }
+
+    public void setDateFromFilter(long date) {
+        this.dateFromFilter = date;
+    }
+
+    public void setDateToFilter(long date) {
+        this.dateToFilter = date;
+    }
+
+    public void setNameContainsFilter(String name) {
+        this.nameContainsFilter = name;
+    }
+
+    public void setItemsFilter(List<Long> items) {
+        this.itemsFilter = items;
+    }
+
+    public void setDeviceFilter(long device) {
+        this.deviceFilter = device;
+    }
+
+    public int gettActivityKindFilter() {
+        return this.activityKindFilter;
+    }
+
+    public static class ActivityItemViewHolder extends AbstractActivityListingViewHolder<BaseActivitySummary> {
+        final View rootView;
+        final ActivityListItem activityListItem;
+
+        public ActivityItemViewHolder(@NonNull final View itemView) {
+            super(itemView);
+            this.rootView = itemView;
+            this.activityListItem = new ActivityListItem(itemView);
+        }
+
+        @Override
+        public void fill(final int position, final BaseActivitySummary summary, final boolean selected) {
+            final boolean hasGps;
+
+            if (summary.getGpxTrack() != null) {
+                hasGps = true;
+            } else if (summary.getSummaryData() != null && summary.getSummaryData().contains(ActivitySummaryEntries.INTERNAL_HAS_GPS)) {
+                final ActivitySummaryData summaryData = ActivitySummaryData.fromJson(summary.getSummaryData());
+                hasGps = summaryData != null && summaryData.getBoolean(INTERNAL_HAS_GPS, false);
+            } else {
+                hasGps = false;
+            }
+
+            this.activityListItem.update(
+                    null,
+                    null,
+                    ActivityKind.fromCode(summary.getActivityKind()),
+                    summary.getName(),
+                    -1,
+                    -1,
+                    -1,
+                    -1,
+                    summary.getEndTime().getTime() - summary.getStartTime().getTime(),
+                    hasGps,
+                    summary.getStartTime(),
+                    position % 2 == 1,
+                    selected
+            );
+        }
+    }
+
+    public class DashboardViewHolder extends AbstractActivityListingViewHolder<BaseActivitySummary> {
+        final TextView durationSumView;
+        final TextView caloriesBurntSumView;
+        final TextView distanceSumView;
+        final TextView activeSecondsSumView;
+        final TextView timeStartView;
+        final TextView timeEndView;
+        final TextView activitiesCountView;
+        final TextView activityKindView;
+        final ImageView activityIconView;
+        final ImageView activityIconBigView;
+
+        public DashboardViewHolder(@NonNull final View itemView) {
+            super(itemView);
+
+            durationSumView = itemView.findViewById(R.id.summary_dashboard_layout_duration_label);
+            caloriesBurntSumView = itemView.findViewById(R.id.summary_dashboard_layout_calories_label);
+            distanceSumView = itemView.findViewById(R.id.summary_dashboard_layout_distance_label);
+            activeSecondsSumView = itemView.findViewById(R.id.summary_dashboard_layout_active_duration_label);
+            timeStartView = itemView.findViewById(R.id.summary_dashboard_layout_from_label);
+            timeEndView = itemView.findViewById(R.id.summary_dashboard_layout_to_label);
+            activitiesCountView = itemView.findViewById(R.id.summary_dashboard_layout_count_label);
+            activityKindView = itemView.findViewById(R.id.summary_dashboard_layout_activity_label);
+            activityIconView = itemView.findViewById(R.id.summary_dashboard_layout_activity_icon);
+            activityIconBigView = itemView.findViewById(R.id.summary_dashboard_layout_big_activity_icon);
+        }
+
+        @Override
+        public void fill(final int position, final BaseActivitySummary summary, final boolean selected) {
+            int activitiesCount = getItemCount() - 2; // remove dashboard and end spacer
+
+            final DeviceCoordinator coordinator = device.getDeviceCoordinator();
+
+            String summaryData = summary.getSummaryData();
+            final StatsContainer stats;
+            if (StringUtils.isNotBlank(summaryData)) {
+                stats = StatsContainer.fromJson(summaryData);
+            } else {
+                stats = StatsContainer.from(
+                        coordinator.getActivitySummaryParser(device, getContext()),
+                        getItems()
+                );
+            }
+
+            DecimalFormat df = new DecimalFormat("#.##");
+            durationSumView.setText(String.format("%s", DateTimeUtils.formatDurationHoursMinutes((long) stats.durationSum, TimeUnit.MILLISECONDS)));
+            caloriesBurntSumView.setText(String.format("%s %s", (long) stats.caloriesBurntSum, getContext().getString(R.string.calories_unit)));
+            distanceSumView.setText(String.format("%s %s", df.format(stats.distanceSum / 1000), getContext().getString(R.string.km)));
+            distanceSumView.setText(FormatUtils.getFormattedDistanceLabel(stats.distanceSum));
+
+            activeSecondsSumView.setText(String.format("%s", DateTimeUtils.formatDurationHoursMinutes((long) stats.activeSecondsSum, TimeUnit.SECONDS)));
+            activitiesCountView.setText(String.valueOf(activitiesCount));
+            String activityName = getContext().getString(R.string.activity_summaries_all_activities);
+            if (gettActivityKindFilter() != 0) {
+                ActivityKind activityKind = ActivityKind.fromCode(gettActivityKindFilter());
+                activityName = activityKind.getLabel(getContext());
+                activityIconView.setImageResource(activityKind.getIcon());
+                activityIconBigView.setImageResource(activityKind.getIcon());
+            } else if (stats.activityIcon != 0) {
+                ActivityKind activityKind = ActivityKind.fromCode(stats.activityIcon);
+                activityIconView.setImageResource(activityKind.getIcon());
+                activityIconBigView.setImageResource(activityKind.getIcon());
+            } else {
+                activityIconView.setImageResource(R.drawable.ic_activity_unknown_small);
+                activityIconBigView.setImageResource(R.drawable.ic_activity_unknown_small);
+            }
+
+            activityKindView.setText(activityName);
+
+            //start and end are inverted when filer not applied, because items are sorted the other way
+            timeStartView.setText((dateFromFilter != 0) ? DateTimeUtils.formatDate(new Date(dateFromFilter)) : DateTimeUtils.formatDate(new Date((long) stats.lastItemDate)));
+            timeEndView.setText((dateToFilter != 0) ? DateTimeUtils.formatDate(new Date(dateToFilter)) : DateTimeUtils.formatDate(new Date((long) stats.firstItemDate)));
+        }
+    }
+
+    private static class StatsContainer {
+        private static final Gson GSON = new GsonBuilder().create();
+
+        private final double durationSum;
+        private final double caloriesBurntSum;
+        private final double distanceSum;
+        private final double activeSecondsSum;
+        private final double firstItemDate;
+        private final double lastItemDate;
+        private final int activityIcon;
+
+        public StatsContainer(final double durationSum,
+                              final double caloriesBurntSum,
+                              final double distanceSum,
+                              final double activeSecondsSum,
+                              final double firstItemDate,
+                              final double lastItemDate,
+                              final int activityIcon) {
+            this.durationSum = durationSum;
+            this.caloriesBurntSum = caloriesBurntSum;
+            this.distanceSum = distanceSum;
+            this.activeSecondsSum = activeSecondsSum;
+            this.firstItemDate = firstItemDate;
+            this.lastItemDate = lastItemDate;
+            this.activityIcon = activityIcon;
+        }
+
+        private static StatsContainer from(final ActivitySummaryParser summaryParser,
+                                           final List<BaseActivitySummary> activities) {
+            double durationSum = 0;
+            double caloriesBurntSum = 0;
+            double distanceSum = 0;
+            double activeSecondsSum = 0;
+            double firstItemDate = 0;
+            double lastItemDate = 0;
+            int activityIcon = 0;
+            boolean activitySame = true;
+
+            for (BaseActivitySummary summary : activities) {
+                if (summary.getStartTime() == null) continue; //first item is empty, for dashboard
+
+                if (firstItemDate == 0) firstItemDate = summary.getStartTime().getTime();
+                lastItemDate = summary.getEndTime().getTime();
+                durationSum += summary.getEndTime().getTime() - summary.getStartTime().getTime();
+
+                if (activityIcon == 0) {
+                    activityIcon = summary.getActivityKind();
+                } else {
+                    if (activityIcon != summary.getActivityKind()) {
+                        activitySame = false;
+                    }
+                }
+
+                final ActivitySummaryJsonSummary activitySummaryJsonSummary = new ActivitySummaryJsonSummary(summaryParser, summary);
+                ActivitySummaryData summarySubdata = activitySummaryJsonSummary.getSummaryData(false);
+
+                if (summarySubdata != null) {
+                    if (summarySubdata.has("caloriesBurnt")) {
+                        caloriesBurntSum += summarySubdata.getNumber("caloriesBurnt", 0).doubleValue();
+                    }
+                    if (summarySubdata.has("distanceMeters")) {
+                        distanceSum += summarySubdata.getNumber("distanceMeters", 0).doubleValue();
+                    }
+                    if (summarySubdata.has("activeSeconds")) {
+                        activeSecondsSum += summarySubdata.getNumber("activeSeconds", 0).doubleValue();
+                    }
+                }
+            }
+
+            return new StatsContainer(
+                    durationSum,
+                    caloriesBurntSum,
+                    distanceSum,
+                    activeSecondsSum,
+                    firstItemDate,
+                    lastItemDate,
+                    activitySame ? activityIcon : 0
+            );
+        }
+
+        public static StatsContainer fromJson(final String json) {
+            return GSON.fromJson(json, StatsContainer.class);
+        }
+
+        public String toJson() {
+            return GSON.toJson(this);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/AppBlacklistAdapter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/AppBlacklistAdapter.java
new file mode 100644
index 0000000..6507a3e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/AppBlacklistAdapter.java
@@ -0,0 +1,307 @@
+/*  Copyright (C) 2017-2024 abettenburg, AndrewBedscastle, Carsten Pfeiffer,
+    Daniele Gobbetti, Jos茅 Rebelo, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.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.CheckedTextView;
+import android.widget.Filter;
+import android.widget.Filterable;
+import android.widget.ImageView;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+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.NotificationFilterActivity;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+import static nodomain.freeyourgadget.gadgetbridge.GBApplication.packageNameToPebbleMsgSender;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class AppBlacklistAdapter extends RecyclerView.Adapter<AppBlacklistAdapter.AppBLViewHolder> implements Filterable {
+    protected static final Logger LOG = LoggerFactory.getLogger(AppBlacklistAdapter.class);
+
+    public static final String STRING_EXTRA_PACKAGE_NAME = "packageName";
+
+    private final List<ApplicationInfo> applicationInfoList;
+    private final int mLayoutId;
+    private final Context mContext;
+    private final PackageManager mPm;
+    private final IdentityHashMap<ApplicationInfo, String> mNameMap;
+
+    private ApplicationFilter applicationFilter;
+
+    public AppBlacklistAdapter(int layoutId, Context context) {
+        mLayoutId = layoutId;
+        mContext = context;
+        mPm = context.getPackageManager();
+
+        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;
+            }
+            if (GBApplication.appIsNotifBlacklisted(ai.packageName) || GBApplication.appIsPebbleBlacklisted(packageNameToPebbleMsgSender(ai.packageName))) {
+                // sort blacklisted first by prefixing with a '!'
+                name = "!" + name;
+            }
+            mNameMap.put(ai, name.toString());
+        }
+
+        Collections.sort(applicationInfoList, new Comparator<ApplicationInfo>() {
+            @Override
+            public int compare(ApplicationInfo ai1, ApplicationInfo ai2) {
+                final String s1 = mNameMap.get(ai1);
+                final String s2 = mNameMap.get(ai2);
+                return s1.compareToIgnoreCase(s2);
+            }
+        });
+
+    }
+
+    @Override
+    public AppBlacklistAdapter.AppBLViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+        View view = LayoutInflater.from(mContext).inflate(mLayoutId, parent, false);
+        return new AppBLViewHolder(view);
+    }
+
+    @Override
+    public void onBindViewHolder(final AppBlacklistAdapter.AppBLViewHolder 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.blacklist_checkbox.setChecked(GBApplication.appIsNotifBlacklisted(appInfo.packageName));
+        holder.blacklist_pebble_checkbox.setChecked(GBApplication.appIsPebbleBlacklisted(packageNameToPebbleMsgSender(appInfo.packageName)));
+
+        holder.blacklist_pebble_checkbox.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                ((CheckedTextView) view).toggle();
+                if (((CheckedTextView) view).isChecked()) {
+                    GBApplication.addAppToPebbleBlacklist(appInfo.packageName);
+                } else {
+                    GBApplication.removeFromAppsPebbleBlacklist(appInfo.packageName);
+                }
+
+            }
+        });
+        holder.itemView.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                CheckedTextView checkBox = (v.findViewById(R.id.item_checkbox));
+                checkBox.toggle();
+                if (checkBox.isChecked()) {
+                    GBApplication.addAppToNotifBlacklist(appInfo.packageName);
+                } else {
+                    GBApplication.removeFromAppsNotifBlacklist(appInfo.packageName);
+                }
+            }
+        });
+
+        holder.btnConfigureApp.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+
+                if (GBApplication.getPrefs().getString("notification_list_is_blacklist", "true").equals("true")) {
+                    if (holder.blacklist_checkbox.isChecked()) {
+                        GB.toast(mContext, mContext.getString(R.string.toast_app_must_not_be_selected), Toast.LENGTH_SHORT, GB.INFO);
+                    } else {
+                        Intent intentStartNotificationFilterActivity = new Intent(mContext, NotificationFilterActivity.class);
+                        intentStartNotificationFilterActivity.putExtra(STRING_EXTRA_PACKAGE_NAME, appInfo.packageName);
+                        mContext.startActivity(intentStartNotificationFilterActivity);
+                    }
+                } else {
+                    if (holder.blacklist_checkbox.isChecked()) {
+                        Intent intentStartNotificationFilterActivity = new Intent(mContext, NotificationFilterActivity.class);
+                        intentStartNotificationFilterActivity.putExtra(STRING_EXTRA_PACKAGE_NAME, appInfo.packageName);
+                        mContext.startActivity(intentStartNotificationFilterActivity);
+                    } else {
+                        GB.toast(mContext, mContext.getString(R.string.toast_app_must_be_selected), Toast.LENGTH_SHORT, GB.INFO);
+                    }
+                }
+            }
+        });
+    }
+
+    /**
+     * Returns all applications on the device, including applications in work profiles.
+     */
+    public List<ApplicationInfo> getAllApplications() {
+        final Set<String> allPackageNames = new HashSet<>();
+        final List<ApplicationInfo> ret = new LinkedList<>();
+
+        // Get apps for the current user
+        final List<ApplicationInfo> currentUserApps = mPm.getInstalledApplications(PackageManager.GET_META_DATA);
+        for (final ApplicationInfo app : currentUserApps) {
+            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<UserHandle> userProfiles = um.getUserProfiles();
+            for (final UserHandle userProfile : userProfiles) {
+                if (userProfile.equals(currentUser)) {
+                    continue;
+                }
+
+                final List<LauncherActivityInfo> userActivityList = launcher.getActivityList(null, userProfile);
+
+                for (final LauncherActivityInfo app : userActivityList) {
+                    if (!allPackageNames.contains(app.getApplicationInfo().packageName)) {
+                        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;
+    }
+
+    public void checkAllApplications() {
+        Set<String> apps_blacklist = new HashSet<>();
+        List<ApplicationInfo> allApps = getAllApplications();
+        for (ApplicationInfo ai : allApps) {
+            apps_blacklist.add(ai.packageName);
+        }
+        GBApplication.setAppsNotifBlackList(apps_blacklist);
+        notifyDataSetChanged();
+    }
+
+    public void uncheckAllApplications() {
+        Set<String> apps_blacklist = new HashSet<>();
+        GBApplication.setAppsNotifBlackList(apps_blacklist);
+        notifyDataSetChanged();
+    }
+
+    @Override
+    public int getItemCount() {
+        return applicationInfoList.size();
+    }
+
+    @Override
+    public Filter getFilter() {
+        if (applicationFilter == null)
+            applicationFilter = new ApplicationFilter(this, applicationInfoList);
+        return applicationFilter;
+    }
+
+    class AppBLViewHolder extends RecyclerView.ViewHolder {
+
+        final CheckedTextView blacklist_checkbox;
+        final CheckedTextView blacklist_pebble_checkbox;
+        final ImageView deviceImageView;
+        final TextView deviceAppVersionAuthorLabel;
+        final TextView deviceAppNameLabel;
+        final ImageView btnConfigureApp;
+
+        AppBLViewHolder(View itemView) {
+            super(itemView);
+
+            blacklist_checkbox = itemView.findViewById(R.id.item_checkbox);
+            blacklist_pebble_checkbox = itemView.findViewById(R.id.item_pebble_checkbox);
+            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 AppBlacklistAdapter adapter;
+        private final List<ApplicationInfo> originalList;
+        private final List<ApplicationInfo> filteredList;
+
+        private ApplicationFilter(AppBlacklistAdapter adapter, List<ApplicationInfo> 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<ApplicationInfo>) filterResults.values);
+            adapter.notifyDataSetChanged();
+        }
+    }
+
+}
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 0000000..966a13b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/AppSpecificNotificationSettingsAppListAdapter.java
@@ -0,0 +1,252 @@
+/*  Copyright (C) 2023-2024 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 <https://www.gnu.org/licenses/>. */
+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<AppSpecificNotificationSettingsAppListAdapter.AppNotificationSettingsViewHolder> 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<ApplicationInfo> applicationInfoList;
+    private final int mLayoutId;
+    private final Context mContext;
+    private final PackageManager mPm;
+    private GBDevice mDevice;
+    private final IdentityHashMap<ApplicationInfo, String> 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<ApplicationInfo> getAllApplications() {
+        final Set<String> allPackageNames = new HashSet<>();
+        final List<ApplicationInfo> ret = new LinkedList<>();
+        boolean filterInverted = !GBApplication.getPrefs().getString("notification_list_is_blacklist", "true").equals("true");
+
+        // Get apps for the current user
+        final List<ApplicationInfo> 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<UserHandle> userProfiles = um.getUserProfiles();
+            for (final UserHandle userProfile : userProfiles) {
+                if (userProfile.equals(currentUser)) {
+                    continue;
+                }
+
+                final List<LauncherActivityInfo> 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<ApplicationInfo> originalList;
+        private final List<ApplicationInfo> filteredList;
+
+        private ApplicationFilter(AppSpecificNotificationSettingsAppListAdapter adapter, List<ApplicationInfo> 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<ApplicationInfo>) filterResults.values);
+            adapter.notifyDataSetChanged();
+        }
+    }
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/DeviceCandidateAdapter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/DeviceCandidateAdapter.java
new file mode 100644
index 0000000..40126b3
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/DeviceCandidateAdapter.java
@@ -0,0 +1,100 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, Carsten Pfeiffer, Daniel
+    Dakhno, Jos茅 Rebelo, Petr Van臎k, Taavi Eom盲e
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.adapter;
+
+import android.content.Context;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+/**
+ * Adapter for displaying GBDeviceCandate instances.
+ */
+public class DeviceCandidateAdapter extends ArrayAdapter<GBDeviceCandidate> {
+
+    private final Context context;
+
+    public DeviceCandidateAdapter(Context context, List<GBDeviceCandidate> deviceCandidates) {
+        super(context, 0, deviceCandidates);
+
+        this.context = context;
+    }
+
+    @Override
+    public View getView(int position, View view, ViewGroup parent) {
+        GBDeviceCandidate device = getItem(position);
+        if (view == null) {
+            LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+            view = inflater.inflate(R.layout.item_with_details, parent, false);
+        }
+        ImageView deviceImageView = view.findViewById(R.id.item_image);
+        TextView deviceNameLabel = view.findViewById(R.id.item_name);
+        TextView deviceAddressLabel = view.findViewById(R.id.item_details);
+        TextView deviceStatus = view.findViewById(R.id.item_status);
+
+        DeviceType deviceType = DeviceHelper.getInstance().resolveDeviceType(device);
+
+        DeviceCoordinator coordinator = deviceType.getDeviceCoordinator();
+
+        String name = formatDeviceCandidate(device);
+        deviceNameLabel.setText(name);
+        deviceAddressLabel.setText(device.getMacAddress());
+        deviceImageView.setImageResource(coordinator.getDefaultIconResource());
+
+        final List<String> statusLines = new ArrayList<>();
+        if (device.isBonded()) {
+            statusLines.add(getContext().getString(R.string.device_is_currently_bonded));
+        }
+
+        if (!deviceType.isSupported()) {
+            statusLines.add(getContext().getString(R.string.device_unsupported));
+        }
+
+        if (coordinator.isExperimental()) {
+            statusLines.add(getContext().getString(R.string.device_experimental));
+        }
+        if (coordinator.getBondingStyle() == DeviceCoordinator.BONDING_STYLE_REQUIRE_KEY) {
+            statusLines.add(getContext().getString(R.string.device_requires_key));
+        }
+
+        deviceStatus.setText(TextUtils.join("\n", statusLines));
+        return view;
+    }
+
+    private String formatDeviceCandidate(GBDeviceCandidate device) {
+        if (device.getRssi() > GBDevice.RSSI_UNKNOWN) {
+            return context.getString(R.string.device_with_rssi, device.getName(), GB.formatRssi(device.getRssi()));
+        }
+        return device.getName();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/GBAlarmListAdapter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/GBAlarmListAdapter.java
new file mode 100644
index 0000000..66ace18
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/GBAlarmListAdapter.java
@@ -0,0 +1,166 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, Arjan Schrijver, Carsten
+    Pfeiffer, Daniele Gobbetti
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.adapter;
+
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.CheckedTextView;
+import android.widget.CompoundButton;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.google.android.material.card.MaterialCardView;
+import com.google.android.material.materialswitch.MaterialSwitch;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.ConfigureAlarms;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.entities.Alarm;
+import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
+
+/**
+ * Adapter for displaying GBAlarm instances.
+ */
+public class GBAlarmListAdapter extends RecyclerView.Adapter<GBAlarmListAdapter.ViewHolder> {
+
+    private final Context mContext;
+    private ArrayList<Alarm> alarmList;
+
+    public GBAlarmListAdapter(Context context) {
+        this.mContext = context;
+    }
+
+    public void setAlarmList(List<Alarm> alarms) {
+        this.alarmList = new ArrayList<>(alarms);
+    }
+
+    public ArrayList<Alarm> getAlarmList() {
+        return alarmList;
+    }
+
+    private void updateInDB(Alarm alarm) {
+        DBHelper.store(alarm);
+    }
+
+    @NonNull
+    @Override
+    public GBAlarmListAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_alarm, parent, false);
+        return new ViewHolder(view);
+    }
+
+    @Override
+    public void onBindViewHolder(@NonNull final ViewHolder holder, final int position) {
+
+        final Alarm alarm = alarmList.get(position);
+
+        holder.alarmDayMonday.setChecked(alarm.getRepetition(Alarm.ALARM_MON));
+        holder.alarmDayTuesday.setChecked(alarm.getRepetition(Alarm.ALARM_TUE));
+        holder.alarmDayWednesday.setChecked(alarm.getRepetition(Alarm.ALARM_WED));
+        holder.alarmDayThursday.setChecked(alarm.getRepetition(Alarm.ALARM_THU));
+        holder.alarmDayFriday.setChecked(alarm.getRepetition(Alarm.ALARM_FRI));
+        holder.alarmDaySaturday.setChecked(alarm.getRepetition(Alarm.ALARM_SAT));
+        holder.alarmDaySunday.setChecked(alarm.getRepetition(Alarm.ALARM_SUN));
+        holder.container.setAlpha(alarm.getUnused() ? 0.5f : 1.0f);
+        holder.isEnabled.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+            @Override
+            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+                if (isChecked) {
+                    alarm.setUnused(false);
+                    holder.container.setAlpha(1.0f);
+                }
+                alarm.setEnabled(isChecked);
+                updateInDB(alarm);
+            }
+        });
+
+        holder.container.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                ((ConfigureAlarms) mContext).configureAlarm(alarm);
+            }
+        });
+        holder.container.setOnLongClickListener(new View.OnLongClickListener() {
+            @Override
+            public boolean onLongClick(View v) {
+                alarm.setUnused(!alarm.getUnused());
+                holder.container.setAlpha(alarm.getUnused() ? 0.5f : 1.0f);
+                holder.isEnabled.setChecked(false); // This falls through to the onCheckChanged function
+                updateInDB(alarm);
+                return true;
+            }
+        });
+
+        holder.alarmTime.setText(DateTimeUtils.formatTime(alarm.getHour(), alarm.getMinute()));
+        holder.isEnabled.setChecked(alarm.getEnabled());
+        if (alarm.getSmartWakeup()) {
+            holder.isSmartWakeup.setVisibility(TextView.VISIBLE);
+        } else {
+            holder.isSmartWakeup.setVisibility(TextView.GONE);
+        }
+    }
+
+    @Override
+    public int getItemCount() {
+        return alarmList.size();
+    }
+
+    static class ViewHolder extends RecyclerView.ViewHolder {
+
+        MaterialCardView container;
+
+        TextView alarmTime;
+        MaterialSwitch isEnabled;
+        TextView isSmartWakeup;
+
+        CheckedTextView alarmDayMonday;
+        CheckedTextView alarmDayTuesday;
+        CheckedTextView alarmDayWednesday;
+        CheckedTextView alarmDayThursday;
+        CheckedTextView alarmDayFriday;
+        CheckedTextView alarmDaySaturday;
+        CheckedTextView alarmDaySunday;
+
+        ViewHolder(View view) {
+            super(view);
+
+            container = view.findViewById(R.id.card_view);
+
+            alarmTime = view.findViewById(R.id.alarm_item_time);
+            isEnabled = view.findViewById(R.id.alarm_item_toggle);
+            isSmartWakeup = view.findViewById(R.id.alarm_smart_wakeup);
+
+            alarmDayMonday = view.findViewById(R.id.alarm_item_monday);
+            alarmDayTuesday = view.findViewById(R.id.alarm_item_tuesday);
+            alarmDayWednesday = view.findViewById(R.id.alarm_item_wednesday);
+            alarmDayThursday = view.findViewById(R.id.alarm_item_thursday);
+            alarmDayFriday = view.findViewById(R.id.alarm_item_friday);
+            alarmDaySaturday = view.findViewById(R.id.alarm_item_saturday);
+            alarmDaySunday = view.findViewById(R.id.alarm_item_sunday);
+        }
+    }
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/GBContactListAdapter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/GBContactListAdapter.java
new file mode 100644
index 0000000..7c98310
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/GBContactListAdapter.java
@@ -0,0 +1,110 @@
+/*  Copyright (C) 2023-2024 Arjan Schrijver, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.adapter;
+
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.google.android.material.card.MaterialCardView;
+import com.google.android.material.dialog.MaterialAlertDialogBuilder;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.ConfigureContacts;
+import nodomain.freeyourgadget.gadgetbridge.entities.Contact;
+
+/**
+ * Adapter for displaying Contact instances.
+ */
+public class GBContactListAdapter extends RecyclerView.Adapter<GBContactListAdapter.ViewHolder> {
+
+    private final Context mContext;
+    private ArrayList<Contact> contactList;
+
+    public GBContactListAdapter(Context context) {
+        this.mContext = context;
+    }
+
+    public void setContactList(List<Contact> contacts) {
+        this.contactList = new ArrayList<>(contacts);
+    }
+
+    public ArrayList<Contact> getContactList() {
+        return contactList;
+    }
+
+    @NonNull
+    @Override
+    public GBContactListAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+        final View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_contact, parent, false);
+        return new ViewHolder(view);
+    }
+
+    @Override
+    public void onBindViewHolder(@NonNull ViewHolder holder, final int position) {
+        final Contact contact = contactList.get(position);
+
+        holder.container.setOnClickListener(v -> ((ConfigureContacts) mContext).configureContact(contact));
+
+        holder.container.setOnLongClickListener(v -> {
+            new MaterialAlertDialogBuilder(v.getContext())
+                    .setTitle(R.string.contact_delete_confirm_title)
+                    .setMessage(mContext.getString(R.string.contact_delete_confirm_description, contact.getName()))
+                    .setIcon(R.drawable.ic_warning)
+                    .setPositiveButton(android.R.string.yes, (dialog, whichButton) -> {
+                        ((ConfigureContacts) mContext).deleteContact(contact);
+                    })
+                    .setNegativeButton(android.R.string.no, null)
+                    .show();
+
+            return true;
+        });
+
+        holder.contactName.setText(contact.getName());
+        holder.contactNumber.setText(contact.getNumber());
+    }
+
+    @Override
+    public int getItemCount() {
+        return contactList.size();
+    }
+
+    static class ViewHolder extends RecyclerView.ViewHolder {
+        final MaterialCardView container;
+
+        final TextView contactName;
+        final TextView contactNumber;
+
+        ViewHolder(View view) {
+            super(view);
+
+            container = view.findViewById(R.id.card_contact);
+
+            contactName = view.findViewById(R.id.contact_item_name);
+            contactNumber = view.findViewById(R.id.contact_item_phone_number);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/GBDeviceAdapterv2.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/GBDeviceAdapterv2.java
new file mode 100644
index 0000000..aab204d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/GBDeviceAdapterv2.java
@@ -0,0 +1,1520 @@
+/*  Copyright (C) 2016-2024 Andreas Shimokawa, Arjan Schrijver, Carsten
+    Pfeiffer, Damien Gaignon, Daniel Dakhno, Daniele Gobbetti, Davis Mosenkovs,
+    fparri, Jos茅 Rebelo, mamucho, maxirnilian, mkusnierz, Petr Van臎k, Taavi
+    Eom盲e
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.adapter;
+
+import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_CONNECT;
+
+import android.annotation.SuppressLint;
+import android.app.Activity;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.pm.ShortcutInfo;
+import android.content.pm.ShortcutManager;
+import android.content.res.Resources;
+import android.graphics.Color;
+import android.graphics.drawable.GradientDrawable;
+import android.graphics.drawable.Icon;
+import android.os.Build;
+import android.text.Editable;
+import android.text.InputType;
+import android.text.TextUtils;
+import android.text.TextWatcher;
+import android.util.Pair;
+import android.util.TypedValue;
+import android.view.LayoutInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.ListView;
+import android.widget.NumberPicker;
+import android.widget.PopupMenu;
+import android.widget.ProgressBar;
+import android.widget.RelativeLayout;
+import android.widget.Spinner;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+import androidx.appcompat.app.AlertDialog;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.coordinatorlayout.widget.CoordinatorLayout;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+import androidx.recyclerview.widget.DiffUtil;
+import androidx.recyclerview.widget.ListAdapter;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.github.mikephil.charting.charts.PieChart;
+import com.github.mikephil.charting.data.PieData;
+import com.github.mikephil.charting.data.PieDataSet;
+import com.github.mikephil.charting.data.PieEntry;
+import com.github.mikephil.charting.utils.MPPointF;
+import com.google.android.flexbox.FlexboxLayout;
+import com.google.android.material.card.MaterialCardView;
+import com.google.android.material.dialog.MaterialAlertDialogBuilder;
+import com.google.android.material.snackbar.Snackbar;
+import com.jaredrummler.android.colorpicker.ColorPickerDialog;
+import com.jaredrummler.android.colorpicker.ColorPickerDialogListener;
+
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.ActivitySummariesActivity;
+import nodomain.freeyourgadget.gadgetbridge.activities.BatteryInfoActivity;
+import nodomain.freeyourgadget.gadgetbridge.activities.ConfigureAlarms;
+import nodomain.freeyourgadget.gadgetbridge.activities.ConfigureReminders;
+import nodomain.freeyourgadget.gadgetbridge.activities.ControlCenterv2;
+import nodomain.freeyourgadget.gadgetbridge.activities.HeartRateDialog;
+import nodomain.freeyourgadget.gadgetbridge.activities.OpenFwAppInstallerActivity;
+import nodomain.freeyourgadget.gadgetbridge.activities.VibrationActivity;
+import nodomain.freeyourgadget.gadgetbridge.activities.charts.ActivityChartsActivity;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsActivity;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.DeviceManager;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceFolder;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
+import nodomain.freeyourgadget.gadgetbridge.model.BatteryState;
+import nodomain.freeyourgadget.gadgetbridge.model.DailyTotals;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.model.RecordedDataTypes;
+import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
+import nodomain.freeyourgadget.gadgetbridge.util.FormatUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+import nodomain.freeyourgadget.gadgetbridge.util.GBPrefs;
+import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
+
+/**
+ * Adapter for displaying GBDevice instances.
+ */
+public class GBDeviceAdapterv2 extends ListAdapter<GBDevice, GBDeviceAdapterv2.ViewHolder> {
+    private static final Logger LOG = LoggerFactory.getLogger(GBDeviceAdapterv2.class);
+
+    private final Context context;
+    private List<GBDevice> deviceList;
+    private List<GBDevice> devicesListWithFolders;
+    private String expandedDeviceAddress = "";
+    private String expandedFolderName = "";
+    private ViewGroup parent;
+    private HashMap<String, DailyTotals> deviceActivityMap = new HashMap<>();
+    private final StableIdGenerator idGenerator = new StableIdGenerator();
+
+    public GBDeviceAdapterv2(Context context, List<GBDevice> deviceList, HashMap<String, DailyTotals> deviceMap) {
+        super(new GBDeviceDiffUtil());
+        this.context = context;
+        this.deviceList = deviceList;
+        rebuildFolders();
+        this.deviceActivityMap = deviceMap;
+    }
+
+    public void rebuildFolders() {
+        this.devicesListWithFolders = enrichDeviceListWithFolder(deviceList);
+    }
+
+    @SuppressLint("NotifyDataSetChanged")
+    public final void refreshSingleDevice(final GBDevice device) {
+        final int i = devicesListWithFolders.indexOf(device);
+        if (i > 0) {
+            notifyItemChanged(i);
+        } else {
+            // Somehow the device was not on the list - rebuild everything
+            rebuildFolders();
+            notifyDataSetChanged();
+        }
+    }
+
+    private List<GBDevice> enrichDeviceListWithFolder(List<GBDevice> deviceList) {
+        final Map<String, List<GBDevice>> devicesPerFolder = new LinkedHashMap<>();
+        final List<GBDevice> enrichedList = new ArrayList<>();
+
+        for (GBDevice device : deviceList) {
+            String folder = device.getParentFolder();
+            if (StringUtils.isNullOrEmpty(folder)){
+                enrichedList.add(device);
+                continue;
+            }
+            if (!devicesPerFolder.containsKey(folder)) {
+                devicesPerFolder.put(folder, new ArrayList<>());
+            }
+            devicesPerFolder.get(folder).add(device);
+        }
+
+        for (final Map.Entry<String, List<GBDevice>> folder : devicesPerFolder.entrySet()) {
+            enrichedList.add(new GBDeviceFolder(folder.getKey()));
+            if (folder.getKey().equals(expandedFolderName)) {
+                enrichedList.addAll(folder.getValue());
+            }
+        }
+
+        return enrichedList;
+    }
+
+    @NonNull
+    @Override
+    public GBDeviceAdapterv2.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+        this.parent = parent;
+        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.device_itemv2, parent, false);
+        return new ViewHolder(view);
+    }
+
+    private int countDevicesInFolder(String folderName, boolean needsToBeConnected){
+        int count = 0;
+        for(GBDevice device : deviceList){
+            if(folderName.equals(device.getParentFolder()) && ((!needsToBeConnected) || device.isConnected())){
+                count++;
+            }
+        }
+        return count;
+    }
+
+    private void showDeviceFolder(ViewHolder holder, final GBDeviceFolder folder){
+        holder.container.setVisibility(View.VISIBLE);
+        holder.deviceNameLabel.setText(folder.getName());
+        holder.infoIcons.setVisibility(View.GONE);
+        holder.deviceInfoBox.setVisibility(View.GONE);
+        holder.cardViewActivityCardLayout.setVisibility(View.GONE);
+        if(countDevicesInFolder(folder.getName(), true) == 0){
+            holder.deviceImageView.setImageResource(R.drawable.ic_device_folder_disabled);
+        }else{
+
+            holder.deviceImageView.setImageResource(R.drawable.ic_device_folder);
+        }
+        holder.deviceInfoView.setVisibility(View.GONE);
+        int countInFolder = countDevicesInFolder(folder.getName(), false);
+        int connectedInFolder = countDevicesInFolder(folder.getName(), true);
+        holder.deviceStatusLabel.setText(context.getString(R.string.controlcenter_connected_fraction, connectedInFolder, countInFolder));
+
+        holder.container.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                if (expandedFolderName.equals(folder.getName())){
+                    // collapse open folder
+                    expandedFolderName = "";
+                } else {
+                    expandedFolderName = folder.getName();
+                }
+                rebuildFolders();
+                notifyDataSetChanged();
+            }
+        });
+        holder.container.setOnLongClickListener(null);
+    }
+
+    private void setItemMargin(ViewHolder holder, GBDevice device){
+        Resources r = context.getResources();
+        int widthDp = 8;
+        if(!StringUtils.isNullOrEmpty(device.getParentFolder())){
+            widthDp = 16;
+        }
+        float px = TypedValue.applyDimension(
+                TypedValue.COMPLEX_UNIT_DIP,
+                widthDp,
+                r.getDisplayMetrics()
+        );
+        CoordinatorLayout.LayoutParams layoutParams = (CoordinatorLayout.LayoutParams) holder.container.getLayoutParams();
+        layoutParams.setMarginStart((int) px);
+        holder.container.setLayoutParams(layoutParams);
+
+        int alpha = 0;
+        if(device instanceof GBDeviceFolder && device.getName().equals(expandedFolderName)){
+            alpha = 50;
+        }else if(!StringUtils.isNullOrEmpty(device.getParentFolder()) && expandedFolderName.equals(device.getParentFolder())){
+            alpha = 50;
+        }
+
+        holder.root.setBackgroundColor(Color.argb(alpha, 0, 0, 0));
+    }
+
+    void handleDeviceConnect(GBDevice device){
+        if(!device.getDeviceCoordinator().isConnectable()){
+            device.setState(GBDevice.State.WAITING_FOR_SCAN);
+            device.sendDeviceUpdateIntent(GBApplication.getContext(), GBDevice.DeviceUpdateSubject.CONNECTION_STATE);
+            return;
+        }
+
+        GBApplication.deviceService(device).connect();
+    }
+
+    @Override
+    public void onBindViewHolder(@NonNull ViewHolder holder, final int position) {
+        final GBDevice device = devicesListWithFolders.get(position);
+
+        setItemMargin(holder, device);
+
+        if(device instanceof GBDeviceFolder){
+            showDeviceFolder(holder, (GBDeviceFolder) device);
+            return;
+        }
+
+        String parentFolder = device.getParentFolder();
+        if(!StringUtils.isNullOrEmpty(parentFolder)){
+            if(parentFolder.equals(expandedFolderName)){
+                holder.container.setVisibility(View.VISIBLE);
+            }else{
+                holder.container.setVisibility(View.GONE);
+            }
+        }else{
+            holder.container.setVisibility(View.VISIBLE);
+        }
+
+        DailyTotals dailyTotals = new DailyTotals();
+        if (deviceActivityMap.containsKey(device.getAddress())) {
+            dailyTotals = deviceActivityMap.get(device.getAddress());
+        }
+
+        final DeviceCoordinator coordinator = device.getDeviceCoordinator();
+        holder.container.setOnClickListener(new View.OnClickListener() {
+
+            @Override
+            public void onClick(View v) {
+
+                if (device.isInitialized() || device.isConnected()) {
+                    showTransientSnackbar(R.string.controlcenter_snackbar_need_longpress);
+                } else {
+                    showTransientSnackbar(R.string.controlcenter_snackbar_connecting);
+                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+                        createDynamicShortcut(device);
+                    }
+                    handleDeviceConnect(device);
+                }
+            }
+        });
+
+        holder.container.setOnLongClickListener(new View.OnLongClickListener() {
+            @Override
+            public boolean onLongClick(View v) {
+                showDeviceSubmenu(v, device);
+                return true;
+            }
+        });
+
+        holder.deviceImageView.setImageResource(device.getEnabledDisabledIconResource());
+
+        holder.deviceNameLabel.setText(getUniqueDeviceName(device));
+
+        if (device.isBusy()) {
+            holder.deviceStatusLabel.setText(device.getBusyTask());
+            holder.busyIndicator.setVisibility(View.VISIBLE);
+        } else {
+            holder.deviceStatusLabel.setText(device.getStateString(context));
+            holder.busyIndicator.setVisibility(View.INVISIBLE);
+        }
+
+        //begin of action row
+        //battery
+        // multiple battery support: at this point we support up to three batteries
+        // to support more batteries, the battery UI would need to be extended
+
+        holder.batteryStatusBox0.setVisibility(coordinator.getBatteryCount() > 0 ? View.VISIBLE : View.GONE);
+        holder.batteryStatusBox1.setVisibility(coordinator.getBatteryCount() > 1 ? View.VISIBLE : View.GONE);
+        holder.batteryStatusBox2.setVisibility(coordinator.getBatteryCount() > 2 ? View.VISIBLE : View.GONE);
+
+        LinearLayout[] batteryStatusBoxes = {holder.batteryStatusBox0, holder.batteryStatusBox1, holder.batteryStatusBox2};
+        TextView[] batteryStatusLabels = {holder.batteryStatusLabel0, holder.batteryStatusLabel1, holder.batteryStatusLabel2};
+        ImageView[] batteryIcons = {holder.batteryIcon0, holder.batteryIcon1, holder.batteryIcon2};
+
+        for (int batteryIndex = 0; batteryIndex < coordinator.getBatteryCount(); batteryIndex++) {
+
+            int batteryLevel = device.getBatteryLevel(batteryIndex);
+            float batteryVoltage = device.getBatteryVoltage(batteryIndex);
+            BatteryState batteryState = device.getBatteryState(batteryIndex);
+            int batteryIcon = device.getBatteryIcon(batteryIndex);
+            int batteryLabel = device.getBatteryLabel(batteryIndex); //unused for now
+            batteryIcons[batteryIndex].setImageResource(R.drawable.level_list_battery);
+
+            if (batteryIcon != GBDevice.BATTERY_ICON_DEFAULT){
+                batteryIcons[batteryIndex].setImageResource(batteryIcon);
+            }
+
+            if (batteryLevel != GBDevice.BATTERY_UNKNOWN) {
+                batteryStatusLabels[batteryIndex].setText(device.getBatteryLevel(batteryIndex) + "%");
+                if (BatteryState.BATTERY_CHARGING.equals(batteryState) ||
+                        BatteryState.BATTERY_CHARGING_FULL.equals(batteryState)) {
+                    batteryIcons[batteryIndex].setImageLevel(device.getBatteryLevel(batteryIndex) + 100);
+                } else {
+                    batteryIcons[batteryIndex].setImageLevel(device.getBatteryLevel(batteryIndex));
+                }
+            } else if (BatteryState.NO_BATTERY.equals(batteryState) && batteryVoltage != GBDevice.BATTERY_UNKNOWN) {
+                batteryStatusLabels[batteryIndex].setText(String.format(Locale.getDefault(), "%.2f", batteryVoltage));
+                batteryIcons[batteryIndex].setImageLevel(200);
+            } else {
+                //should be the "default" status, shown when the device is not connected
+                batteryStatusLabels[batteryIndex].setText("");
+                batteryIcons[batteryIndex].setImageLevel(300);
+            }
+            final int finalBatteryIndex = batteryIndex;
+            batteryStatusBoxes[batteryIndex].setOnClickListener(new View.OnClickListener() {
+                                                               @Override
+                                                               public void onClick(View v) {
+                                                                   Intent startIntent;
+                                                                   startIntent = new Intent(context, BatteryInfoActivity.class);
+                                                                   startIntent.putExtra(GBDevice.EXTRA_DEVICE, device);
+                                                                   startIntent.putExtra(GBDevice.BATTERY_INDEX, finalBatteryIndex);
+                                                                   context.startActivity(startIntent);
+                                                               }
+                                                           }
+            );
+
+            // Hide the battery status level, if it has no text
+            if (TextUtils.isEmpty(batteryStatusLabels[batteryIndex].getText())) {
+                batteryStatusLabels[batteryIndex].setVisibility(View.GONE);
+            } else {
+                batteryStatusLabels[batteryIndex].setVisibility(View.VISIBLE);
+            }
+        }
+        holder.heartRateStatusBox.setVisibility((device.isInitialized() && coordinator.supportsRealtimeData() && coordinator.supportsManualHeartRateMeasurement(device)) ? View.VISIBLE : View.GONE);
+        if (parent.getContext() instanceof ControlCenterv2) {
+            ActivitySample sample = ((ControlCenterv2) parent.getContext()).getCurrentHRSample(device);
+            if (sample != null) {
+                holder.heartRateStatusLabel.setText(String.valueOf(sample.getHeartRate()));
+            } else {
+                holder.heartRateStatusLabel.setText("");
+            }
+
+            // Hide the level, if it has no text
+            if (TextUtils.isEmpty(holder.heartRateStatusLabel.getText())) {
+                holder.heartRateStatusLabel.setVisibility(View.GONE);
+            } else {
+                holder.heartRateStatusLabel.setVisibility(View.VISIBLE);
+            }
+        }
+
+        holder.heartRateStatusBox.setOnClickListener(new View.OnClickListener() {
+                                                         @Override
+                                                         public void onClick(View v) {
+                                                             GBApplication.deviceService(device).onHeartRateTest();
+                                                             HeartRateDialog dialog = new HeartRateDialog(device, context);
+                                                             dialog.show();
+                                                         }
+                                                     }
+        );
+
+        //device specific settings
+        holder.deviceSpecificSettingsView.setVisibility(coordinator.getSupportedDeviceSpecificSettings(device)  != null ? View.VISIBLE : View.GONE);
+        holder.deviceSpecificSettingsView.setOnClickListener(new View.OnClickListener()
+
+                                                {
+                                                    @Override
+                                                    public void onClick(View v) {
+                                                        Intent startIntent;
+                                                        startIntent = new Intent(context, DeviceSettingsActivity.class);
+                                                        startIntent.putExtra(GBDevice.EXTRA_DEVICE, device);
+                                                        startIntent.putExtra(DeviceSettingsActivity.MENU_ENTRY_POINT, DeviceSettingsActivity.MENU_ENTRY_POINTS.DEVICE_SETTINGS);
+                                                        context.startActivity(startIntent);
+                                                    }
+                                                }
+        );
+
+        //fetch activity data
+        holder.fetchActivityDataBox.setVisibility((device.isInitialized() && coordinator.supportsActivityDataFetching()) ? View.VISIBLE : View.GONE);
+        holder.fetchActivityData.setOnClickListener(new View.OnClickListener()
+
+                                                    {
+                                                        @Override
+                                                        public void onClick(View v) {
+                                                            showTransientSnackbar(R.string.busy_task_fetch_activity_data);
+                                                            GBApplication.deviceService(device).onFetchRecordedData(RecordedDataTypes.TYPE_SYNC);
+                                                        }
+                                                    }
+        );
+
+
+        //take screenshot
+        holder.takeScreenshotView.setVisibility((device.isInitialized() && coordinator.supportsScreenshots(device)) ? View.VISIBLE : View.GONE);
+        holder.takeScreenshotView.setOnClickListener(new View.OnClickListener()
+
+                                                     {
+                                                         @Override
+                                                         public void onClick(View v) {
+                                                             showTransientSnackbar(R.string.controlcenter_snackbar_requested_screenshot);
+                                                             GBApplication.deviceService(device).onScreenshotReq();
+                                                         }
+                                                     }
+        );
+
+        //manage apps
+        holder.manageAppsView.setVisibility((device.isInitialized() && coordinator.supportsAppsManagement(device)) ? View.VISIBLE : View.GONE);
+        holder.manageAppsView.setOnClickListener(new View.OnClickListener()
+
+                                                 {
+                                                     @Override
+                                                     public void onClick(View v) {
+                                                         DeviceCoordinator coordinator = device.getDeviceCoordinator();
+                                                         Class<? extends Activity> appsManagementActivity = coordinator.getAppsManagementActivity();
+                                                         if (appsManagementActivity != null) {
+                                                             Intent startIntent = new Intent(context, appsManagementActivity);
+                                                             startIntent.putExtra(GBDevice.EXTRA_DEVICE, device);
+                                                             context.startActivity(startIntent);
+                                                         }
+                                                     }
+                                                 }
+        );
+
+        //set alarms
+        holder.setAlarmsView.setVisibility(coordinator.getAlarmSlotCount(device) > 0 ? View.VISIBLE : View.GONE);
+        holder.setAlarmsView.setOnClickListener(new View.OnClickListener()
+
+                                                {
+                                                    @Override
+                                                    public void onClick(View v) {
+                                                        Intent startIntent;
+                                                        startIntent = new Intent(context, ConfigureAlarms.class);
+                                                        startIntent.putExtra(GBDevice.EXTRA_DEVICE, device);
+                                                        context.startActivity(startIntent);
+                                                    }
+                                                }
+        );
+
+        //set reminders
+        holder.setRemindersView.setVisibility(coordinator.getReminderSlotCount(device) > 0 ? View.VISIBLE : View.GONE);
+        holder.setRemindersView.setOnClickListener(new View.OnClickListener()
+
+                                                {
+                                                    @Override
+                                                    public void onClick(View v) {
+                                                        Intent startIntent;
+                                                        startIntent = new Intent(context, ConfigureReminders.class);
+                                                        startIntent.putExtra(GBDevice.EXTRA_DEVICE, device);
+                                                        context.startActivity(startIntent);
+                                                    }
+                                                }
+        );
+
+        //show graphs
+        holder.showActivityGraphs.setVisibility(coordinator.supportsActivityTracking() ? View.VISIBLE : View.GONE);
+        holder.showActivityGraphs.setOnClickListener(new View.OnClickListener()
+
+                                                     {
+                                                         @Override
+                                                         public void onClick(View v) {
+                                                             Intent startIntent;
+                                                             startIntent = new Intent(context, ActivityChartsActivity.class);
+                                                             startIntent.putExtra(GBDevice.EXTRA_DEVICE, device);
+                                                             context.startActivity(startIntent);
+                                                         }
+                                                     }
+        );
+
+        //show activity tracks
+        holder.showActivityTracks.setVisibility(coordinator.supportsActivityTracks() ? View.VISIBLE : View.GONE);
+        holder.showActivityTracks.setOnClickListener(new View.OnClickListener()
+                                                     {
+                                                         @Override
+                                                         public void onClick(View v) {
+                                                             Intent startIntent;
+                                                             startIntent = new Intent(context, ActivitySummariesActivity.class);
+                                                             startIntent.putExtra(GBDevice.EXTRA_DEVICE, device);
+                                                             context.startActivity(startIntent);
+                                                         }
+                                                     }
+        );
+
+        ItemWithDetailsAdapter infoAdapter = new ItemWithDetailsAdapter(context, device.getDeviceInfos());
+        infoAdapter.setHorizontalAlignment(true);
+        holder.deviceInfoList.setAdapter(infoAdapter);
+        justifyListViewHeightBasedOnChildren(holder.deviceInfoList);
+        holder.deviceInfoList.setFocusable(false);
+
+        holder.infoIcons.setVisibility(View.VISIBLE);
+
+        final boolean detailsShown = expandedDeviceAddress.equals(device.getAddress());
+        boolean showInfoIcon = device.hasDeviceInfos() && !device.isBusy();
+        holder.deviceInfoBox.setActivated(detailsShown);
+        holder.deviceInfoBox.setVisibility(detailsShown ? View.VISIBLE : View.GONE);
+        holder.deviceInfoView.setVisibility(View.VISIBLE);
+        holder.deviceInfoView.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                showDeviceSubmenu(v, device);
+            }
+        });
+
+        holder.findDevice.setVisibility(device.isInitialized() && coordinator.supportsFindDevice() ? View.VISIBLE : View.GONE);
+        holder.findDevice.setOnClickListener(new View.OnClickListener() {
+                                                 @Override
+                                                 public void onClick(View v) {
+                                                     new MaterialAlertDialogBuilder(context)
+                                                             .setCancelable(true)
+                                                             .setTitle(context.getString(R.string.controlcenter_find_device))
+                                                             .setMessage(context.getString(R.string.find_lost_device_message, device.getName()))
+                                                             .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
+                                                                 @Override
+                                                                 public void onClick(DialogInterface dialog, int which) {
+                                                                     if (device.getType() == DeviceType.VIBRATISSIMO) {
+                                                                         Intent startIntent;
+                                                                         startIntent = new Intent(context, VibrationActivity.class);
+                                                                         startIntent.putExtra(GBDevice.EXTRA_DEVICE, device);
+                                                                         context.startActivity(startIntent);
+                                                                         return;
+                                                                     }
+                                                                     GBApplication.deviceService(device).onFindDevice(true);
+                                                                     Snackbar.make(parent, R.string.control_center_find_lost_device, Snackbar.LENGTH_INDEFINITE).setAction(R.string.find_lost_device_you_found_it, new View.OnClickListener() {
+                                                                         @Override
+                                                                         public void onClick(View v) {
+                                                                             GBApplication.deviceService(device).onFindDevice(false);
+                                                                         }
+                                                                     }).setCallback(new Snackbar.Callback() {
+                                                                         @Override
+                                                                         public void onDismissed(Snackbar snackbar, int event) {
+                                                                             GBApplication.deviceService(device).onFindDevice(false);
+                                                                             super.onDismissed(snackbar, event);
+                                                                         }
+                                                                     }).show();
+
+                                                                 }
+                                                             })
+                                                             .setNegativeButton(R.string.Cancel, new DialogInterface.OnClickListener() {
+                                                                 @Override
+                                                                 public void onClick(DialogInterface dialog, int which) {
+                                                                     // do nothing
+                                                                 }
+                                                             })
+                                                             .show();
+//                                                             ProgressDialog.show(
+//                                                             context,
+//                                                             context.getString(R.string.control_center_find_lost_device),
+//                                                             context.getString(R.string.control_center_cancel_to_stop_vibration),
+//                                                             true, true,
+//                                                             new DialogInterface.OnCancelListener() {
+//                                                                 @Override
+//                                                                 public void onCancel(DialogInterface dialog) {
+//                                                                     GBApplication.deviceService().onFindDevice(false);
+//                                                                 }
+//                                                             });
+                                                 }
+                                             }
+        );
+
+        holder.calibrateDevice.setVisibility(device.isInitialized() && (coordinator.getCalibrationActivity() != null) ? View.VISIBLE : View.GONE);
+        holder.calibrateDevice.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                Intent startIntent = new Intent(context, coordinator.getCalibrationActivity());
+                startIntent.putExtra(GBDevice.EXTRA_DEVICE, device);
+                context.startActivity(startIntent);
+            }
+        });
+
+        holder.fmFrequencyBox.setVisibility(View.GONE);
+        if (device.isInitialized() && device.getExtraInfo("fm_frequency") != null) {
+            holder.fmFrequencyBox.setVisibility(View.VISIBLE);
+            holder.fmFrequencyLabel.setText(String.format(Locale.getDefault(), "%.1f", (float) device.getExtraInfo("fm_frequency")));
+        }
+        final TextView fmFrequencyLabel = holder.fmFrequencyLabel;
+        final float FREQ_MIN = 87.5F;
+        final float FREQ_MAX = 108.0F;
+        final int FREQ_MIN_INT = (int) Math.floor(FREQ_MIN);
+        final int FREQ_MAX_INT = (int) Math.round(FREQ_MAX);
+        final AlertDialog alert[] = new AlertDialog[1];
+
+        holder.fmFrequencyBox.setOnClickListener(new View.OnClickListener() {
+
+            @Override
+            public void onClick(View view) {
+                final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(context);
+                final LayoutInflater inflater = LayoutInflater.from(context);
+                final View frequency_picker_view = inflater.inflate(R.layout.dialog_frequency_picker, null);
+                builder.setTitle(R.string.preferences_fm_frequency);
+                final float[] fm_presets = new float[3];
+
+                fm_presets[0] = GBApplication.getDeviceSpecificSharedPrefs(device.getAddress()).getFloat("fm_preset0", 99);
+                fm_presets[1] = GBApplication.getDeviceSpecificSharedPrefs(device.getAddress()).getFloat("fm_preset1", 100);
+                fm_presets[2] = GBApplication.getDeviceSpecificSharedPrefs(device.getAddress()).getFloat("fm_preset2", 101);
+
+                final NumberPicker frequency_decimal_picker = frequency_picker_view.findViewById(R.id.frequency_dec);
+                frequency_decimal_picker.setMinValue(FREQ_MIN_INT);
+                frequency_decimal_picker.setMaxValue(FREQ_MAX_INT);
+
+                final NumberPicker frequency_fraction_picker = frequency_picker_view.findViewById(R.id.frequency_fraction);
+                frequency_fraction_picker.setMinValue(0);
+                frequency_fraction_picker.setMaxValue(9);
+
+                final NumberPicker.OnValueChangeListener picker_listener = new NumberPicker.OnValueChangeListener() {
+                    @Override
+                    public void onValueChange(NumberPicker numberPicker, int oldVal, int newVal) {
+
+                        int decimal_value = numberPicker.getValue();
+                        if (decimal_value == FREQ_MIN_INT) {
+                            frequency_fraction_picker.setMinValue(5);
+                            frequency_fraction_picker.setMaxValue(9);
+                        } else if (decimal_value == FREQ_MAX_INT) {
+                            frequency_fraction_picker.setMinValue(0);
+                            frequency_fraction_picker.setMaxValue(0);
+                        } else {
+                            frequency_fraction_picker.setMinValue(0);
+                            frequency_fraction_picker.setMaxValue(9);
+                        }
+                    }
+                };
+
+                frequency_decimal_picker.setOnValueChangedListener(picker_listener);
+
+                final Button[] button_presets = new Button[]{
+                        frequency_picker_view.findViewById(R.id.frequency_preset1),
+                        frequency_picker_view.findViewById(R.id.frequency_preset2),
+                        frequency_picker_view.findViewById(R.id.frequency_preset3)
+                };
+
+                for (int i = 0; i < button_presets.length; i++) {
+                    final int index = i;
+                    button_presets[index].setText(String.valueOf(fm_presets[index]));
+                    button_presets[index].setOnClickListener(new View.OnClickListener() {
+                        @Override
+                        public void onClick(View view) {
+                            final float frequency = fm_presets[index];
+                            device.setExtraInfo("fm_frequency", fm_presets[index]);
+                            fmFrequencyLabel.setText(String.format(Locale.getDefault(), "%.1f", (float) frequency));
+                            GBApplication.deviceService(device).onSetFmFrequency(frequency);
+                            alert[0].dismiss();
+                        }
+                    });
+                    button_presets[index].setOnLongClickListener(new View.OnLongClickListener() {
+                        @Override
+                        public boolean onLongClick(View view) {
+                            final float frequency = (float) (frequency_decimal_picker.getValue() + (0.1 * frequency_fraction_picker.getValue()));
+                            fm_presets[index] = frequency;
+                            button_presets[index].setText(String.valueOf(frequency));
+                            SharedPreferences.Editor editor = GBApplication.getDeviceSpecificSharedPrefs(device.getAddress()).edit();
+                            editor.putFloat((String.format("fm_preset%s", index)), frequency);
+                            editor.apply();
+                            return true;
+                        }
+                    });
+
+                }
+
+                final float frequency = (float) device.getExtraInfo("fm_frequency");
+                final int decimal = (int) frequency;
+                final int fraction = Math.round((frequency - decimal) * 10);
+                frequency_decimal_picker.setValue(decimal);
+                picker_listener.onValueChange(frequency_decimal_picker, frequency_decimal_picker.getValue(), decimal);
+                frequency_fraction_picker.setValue(fraction);
+
+                builder.setView(frequency_picker_view);
+
+                builder.setPositiveButton(context.getResources().getString(android.R.string.ok),
+                        new DialogInterface.OnClickListener() {
+                            @Override
+                            public void onClick(DialogInterface dialog, int which) {
+                                float frequency = (float) (frequency_decimal_picker.getValue() + (0.1 * frequency_fraction_picker.getValue()));
+                                if (frequency < FREQ_MIN || frequency > FREQ_MAX) {
+                                    new MaterialAlertDialogBuilder(context)
+                                            .setTitle(R.string.pref_invalid_frequency_title)
+                                            .setMessage(R.string.pref_invalid_frequency_message)
+                                            .setNeutralButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+                                                public void onClick(DialogInterface dialog, int which) {
+                                                }
+                                            })
+                                            .show();
+                                } else {
+                                    device.setExtraInfo("fm_frequency", frequency);
+                                    fmFrequencyLabel.setText(String.format(Locale.getDefault(), "%.1f", frequency));
+                                    GBApplication.deviceService(device).onSetFmFrequency(frequency);
+                                }
+                            }
+                        });
+                builder.setNegativeButton(context.getResources().getString(R.string.Cancel), new DialogInterface.OnClickListener() {
+                    @Override
+                    public void onClick(DialogInterface dialog, int which) {
+                        dialog.cancel();
+                    }
+                });
+
+                alert[0] = builder.create();
+                alert[0].show();
+            }
+        });
+
+        holder.ledColor.setVisibility(View.GONE);
+        if (device.isInitialized() && device.getExtraInfo("led_color") != null && coordinator.supportsLedColor()) {
+            holder.ledColor.setVisibility(View.VISIBLE);
+            final GradientDrawable ledColor = (GradientDrawable) holder.ledColor.getDrawable().mutate();
+            ledColor.setColor((int) device.getExtraInfo("led_color"));
+            holder.ledColor.setOnClickListener(new View.OnClickListener() {
+                @Override
+                public void onClick(View view) {
+                    ColorPickerDialog.Builder builder = ColorPickerDialog.newBuilder();
+                    builder.setDialogTitle(R.string.preferences_led_color);
+
+                    int[] presets = coordinator.getColorPresets();
+
+                    builder.setColor((int) device.getExtraInfo("led_color"));
+                    builder.setShowAlphaSlider(false);
+                    builder.setShowColorShades(false);
+                    if (coordinator.supportsRgbLedColor()) {
+                        builder.setAllowCustom(true);
+                        if (presets.length == 0) {
+                            builder.setDialogType(ColorPickerDialog.TYPE_CUSTOM);
+                        }
+                    } else {
+                        builder.setAllowCustom(false);
+                    }
+
+                    if (presets.length > 0) {
+                        builder.setAllowPresets(true);
+                        builder.setPresets(presets);
+                    }
+
+                    ColorPickerDialog dialog = builder.create();
+                    dialog.setColorPickerDialogListener(new ColorPickerDialogListener() {
+                        @Override
+                        public void onColorSelected(int dialogId, int color) {
+                            ledColor.setColor(color);
+                            device.setExtraInfo("led_color", color);
+                            GBApplication.deviceService(device).onSetLedColor(color);
+                        }
+
+                        @Override
+                        public void onDialogDismissed(int dialogId) {
+                            // Nothing to do
+                        }
+                    });
+                    dialog.show(((AppCompatActivity) context).getSupportFragmentManager(), "color-picker-dialog");
+                }
+            });
+        }
+
+        holder.powerOff.setVisibility(View.GONE);
+        if (device.isInitialized() && coordinator.supportsPowerOff()) {
+            holder.powerOff.setVisibility(View.VISIBLE);
+            holder.powerOff.setOnClickListener(new View.OnClickListener() {
+                @Override
+                public void onClick(View view) {
+                    new MaterialAlertDialogBuilder(context)
+                            .setTitle(R.string.controlcenter_power_off_confirm_title)
+                            .setMessage(R.string.controlcenter_power_off_confirm_description)
+                            .setIcon(R.drawable.ic_power_settings_new)
+                            .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
+                                public void onClick(final DialogInterface dialog, final int whichButton) {
+                                    GBApplication.deviceService(device).onPowerOff();
+                                }
+                            })
+                            .setNegativeButton(android.R.string.no, null)
+                            .show();
+                }
+            });
+        }
+
+        holder.cardViewActivityCardLayout.setVisibility(coordinator.supportsActivityTracking() ? View.VISIBLE : View.GONE);
+        holder.cardViewActivityCardLayout.setMinimumWidth(coordinator.supportsActivityTracking() ? View.VISIBLE : View.GONE);
+
+        if (coordinator.supportsActivityTracking()) {
+            setActivityCard(holder, device, dailyTotals);
+        }
+    }
+
+    private boolean showInstallerItem(GBDevice device) {
+        final DeviceCoordinator coordinator = device.getDeviceCoordinator();
+        return coordinator.supportsAppsManagement(device) || coordinator.supportsFlashing();
+    }
+
+    private void showDeviceSubmenu(final View v, final GBDevice device) {
+        boolean deviceConnected = device.getState() != GBDevice.State.NOT_CONNECTED;
+        PopupMenu menu = new PopupMenu(v.getContext(), v);
+        menu.inflate(R.menu.fragment_devices_device_submenu);
+
+        final boolean detailsShown = expandedDeviceAddress.equals(device.getAddress());
+        boolean showInfoIcon = device.hasDeviceInfos() && !device.isBusy();
+
+        menu.getMenu().findItem(R.id.controlcenter_device_submenu_connect).setVisible(!deviceConnected);
+        menu.getMenu().findItem(R.id.controlcenter_device_submenu_disconnect).setVisible(deviceConnected);
+        menu.getMenu().findItem(R.id.controlcenter_device_submenu_show_details).setEnabled(showInfoIcon);
+        menu.getMenu().findItem(R.id.controlcenter_device_submenu_installer).setEnabled(deviceConnected);
+        menu.getMenu().findItem(R.id.controlcenter_device_submenu_installer).setVisible(showInstallerItem(device));
+
+        menu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
+            @Override
+            public boolean onMenuItemClick(final MenuItem item) {
+                final int itemId = item.getItemId();
+                if (itemId == R.id.controlcenter_device_submenu_connect) {
+                    if (device.getState() != GBDevice.State.CONNECTED) {
+                        showTransientSnackbar(R.string.controlcenter_snackbar_connecting);
+                        handleDeviceConnect(device);
+                    }
+                    return true;
+                } else if (itemId == R.id.controlcenter_device_submenu_disconnect) {
+                    if (device.getState() != GBDevice.State.NOT_CONNECTED) {
+                        showTransientSnackbar(R.string.controlcenter_snackbar_disconnecting);
+                        GBApplication.deviceService(device).disconnect();
+                    }
+                    removeFromLastDeviceAddressesPref(device);
+                    return true;
+                } else if (itemId == R.id.controlcenter_device_submenu_set_alias) {
+                    showSetAliasDialog(device);
+                    return true;
+                } else if (itemId == R.id.controlcenter_device_submenu_remove) {
+                    showRemoveDeviceDialog(device);
+                    return true;
+                } else if (itemId == R.id.controlcenter_device_submenu_show_details) {
+                    final String previouslyExpandedDeviceAddress = expandedDeviceAddress;
+                    expandedDeviceAddress = detailsShown ? "" : device.getAddress();
+
+                    if (!previouslyExpandedDeviceAddress.isEmpty()) {
+                        // Notify the previously expanded device for a change (collapsing it)
+                        for (int i = 0; i < devicesListWithFolders.size(); i++) {
+                            final GBDevice gbDevice = devicesListWithFolders.get(i);
+                            if (gbDevice.getAddress().equals(previouslyExpandedDeviceAddress)) {
+                                notifyItemChanged(devicesListWithFolders.indexOf(gbDevice));
+                                break;
+                            }
+                        }
+                    }
+
+                    // Update the current one
+                    notifyItemChanged(devicesListWithFolders.indexOf(device));
+                    return true;
+                } else if (itemId == R.id.controlcenter_device_submenu_set_parent_folder) {
+                    showSetParentFolderDialog(device);
+                    return true;
+                } else if (itemId == R.id.controlcenter_device_submenu_installer) {
+                    Intent openFwIntent = new Intent(context, OpenFwAppInstallerActivity.class);
+                    openFwIntent.putExtra(GBDevice.EXTRA_DEVICE, device);
+                    context.startActivity(openFwIntent);
+                    return false;
+                }
+
+                return false;
+            }
+        });
+        menu.show();
+    }
+
+    private void showRemoveDeviceDialog(final GBDevice device) {
+        new MaterialAlertDialogBuilder(context)
+                .setCancelable(true)
+                .setTitle(context.getString(R.string.controlcenter_delete_device_name, device.getName()))
+                .setMessage(R.string.controlcenter_delete_device_dialogmessage)
+                .setPositiveButton(R.string.Delete, new DialogInterface.OnClickListener() {
+                    @Override
+                    public void onClick(DialogInterface dialog, int which) {
+                        try {
+                            DeviceCoordinator coordinator = device.getDeviceCoordinator();
+                            coordinator.deleteDevice(device);
+                            DeviceHelper.getInstance().removeBond(device);
+                            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+                                removeDynamicShortcut(device);
+                            }
+                        } catch (Exception ex) {
+                            GB.toast(context, context.getString(R.string.error_deleting_device, ex.getMessage()), Toast.LENGTH_LONG, GB.ERROR, ex);
+                        } finally {
+                            Intent refreshIntent = new Intent(DeviceManager.ACTION_REFRESH_DEVICELIST);
+                            LocalBroadcastManager.getInstance(context).sendBroadcast(refreshIntent);
+                        }
+                    }
+                })
+                .setNegativeButton(R.string.Cancel, new DialogInterface.OnClickListener() {
+                    @Override
+                    public void onClick(DialogInterface dialog, int which) {
+                        // do nothing
+                    }
+                })
+                .show();
+    }
+
+    private void showSetParentFolderDialog(final GBDevice device) {
+        final String[] selectedFolder = new String[1];
+
+        final LinearLayout linearLayout = new LinearLayout(context);
+        linearLayout.setOrientation(LinearLayout.VERTICAL);
+
+        final LinearLayout newFolderLayout = new LinearLayout(context);
+        newFolderLayout.setOrientation(LinearLayout.HORIZONTAL);
+        newFolderLayout.setPadding(context.getResources().getDimensionPixelSize(R.dimen.dialog_margin),
+                0, context.getResources().getDimensionPixelSize(R.dimen.dialog_margin), 0);
+
+        final TextView newFolderLabel = new TextView(context);
+        newFolderLabel.setText(R.string.controlcenter_folder_name);
+        final EditText newFolderInput = new EditText(context);
+        newFolderInput.setInputType(InputType.TYPE_CLASS_TEXT);
+
+        FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
+                ViewGroup.LayoutParams.WRAP_CONTENT);
+        newFolderInput.setLayoutParams(params);
+
+        newFolderInput.addTextChangedListener(new TextWatcher() {
+            @Override
+            public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
+
+            }
+
+            @Override
+            public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
+
+            }
+
+            @Override
+            public void afterTextChanged(Editable editable) {
+                selectedFolder[0] = editable.toString();
+            }
+        });
+
+        newFolderLayout.addView(newFolderLabel);
+        newFolderLayout.addView(newFolderInput);
+
+        final Spinner deviceListSpinner = new Spinner(context);
+        ArrayList<SpinnerWithIconItem> foldersList = new ArrayList<>();
+        for (GBDevice oneDevice : deviceList) {
+            String folder = oneDevice.getParentFolder();
+            if (StringUtils.isNullOrEmpty(folder)) {
+                continue;
+            }
+            if (folderListContainsName(foldersList, folder)) {
+                continue;
+            }
+            foldersList.add(new SpinnerWithIconItem(folder, 2L, R.drawable.ic_folder));
+        }
+
+        foldersList.add(new SpinnerWithIconItem(context.getString(R.string.controlcenter_add_new_folder), 0L, R.drawable.ic_create_new_folder));
+        if (foldersList.toArray().length > 1) {
+            foldersList.add(new SpinnerWithIconItem(context.getString(R.string.controlcenter_unset_folder), 1L, R.drawable.ic_folder_delete));
+        }
+
+        final SpinnerWithIconAdapter deviceListAdapter = new SpinnerWithIconAdapter((Activity) context,
+                R.layout.spinner_with_image_layout, R.id.spinner_item_text, foldersList);
+        deviceListSpinner.setAdapter(deviceListAdapter);
+
+        deviceListSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
+            public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
+                SpinnerWithIconItem selectedItem = (SpinnerWithIconItem) parent.getItemAtPosition(pos);
+                int folderId = selectedItem.getId().intValue();
+                switch (folderId) {
+                    case 0: //Add new folder from test input
+                        newFolderLayout.setVisibility(View.VISIBLE);
+                        selectedFolder[0] = newFolderInput.getText().toString();
+                        break;
+                    case 1: //Unset folder
+                        newFolderLayout.setVisibility(View.GONE);
+                        selectedFolder[0] = "";
+                        break;
+                    default: //Set folder from selection
+                        newFolderLayout.setVisibility(View.GONE);
+                        selectedFolder[0] = selectedItem.getText();
+                        break;
+                }
+            }
+
+            @Override
+            public void onNothingSelected(AdapterView<?> arg0) {
+                // TODO Auto-generated method stub
+            }
+        });
+
+        linearLayout.addView(deviceListSpinner);
+        linearLayout.addView(newFolderLayout);
+
+        new MaterialAlertDialogBuilder(context)
+                .setCancelable(true)
+                .setTitle(R.string.controlcenter_set_folder_title)
+                .setView(linearLayout)
+                .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
+                    @Override
+                    public void onClick(DialogInterface dialog, int which) {
+
+                        try (DBHandler dbHandler = GBApplication.acquireDB()) {
+                            DaoSession session = dbHandler.getDaoSession();
+                            Device dbDevice = DBHelper.getDevice(device, session);
+                            String parentFolder = selectedFolder[0];
+                            dbDevice.setParentFolder(parentFolder);
+                            dbDevice.update();
+                            device.setParentFolder(parentFolder);
+                            expandedFolderName = parentFolder;
+                        } catch (Exception ex) {
+                            GB.toast(context, context.getString(R.string.error_setting_parent_folder, ex.getMessage()), Toast.LENGTH_LONG, GB.ERROR, ex);
+                        } finally {
+                            Intent refreshIntent = new Intent(DeviceManager.ACTION_REFRESH_DEVICELIST);
+                            LocalBroadcastManager.getInstance(context).sendBroadcast(refreshIntent);
+                        }
+                    }
+                })
+                .setNegativeButton(R.string.Cancel, new DialogInterface.OnClickListener() {
+                    @Override
+                    public void onClick(DialogInterface dialog, int which) {
+                    }
+                })
+                .show();
+    }
+
+    private boolean folderListContainsName(ArrayList<SpinnerWithIconItem> list, String name){
+        for (SpinnerWithIconItem item: list) {
+            if (item.getText().equals(name)){
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private void removeFromLastDeviceAddressesPref(GBDevice device) {
+        Set<String> lastDeviceAddresses = GBApplication.getPrefs().getStringSet(GBPrefs.LAST_DEVICE_ADDRESSES, Collections.emptySet());
+        if (lastDeviceAddresses.contains(device.getAddress())) {
+            lastDeviceAddresses = new HashSet<String>(lastDeviceAddresses);
+            lastDeviceAddresses.remove(device.getAddress());
+            GBApplication.getPrefs().getPreferences().edit().putStringSet(GBPrefs.LAST_DEVICE_ADDRESSES, lastDeviceAddresses).apply();
+        }
+    }
+
+    private void setAppPreferences(GBDevice device) {
+        Intent startIntent;
+        startIntent = new Intent(context, DeviceSettingsActivity.class);
+        startIntent.putExtra(GBDevice.EXTRA_DEVICE, device);
+        startIntent.putExtra(DeviceSettingsActivity.MENU_ENTRY_POINT, DeviceSettingsActivity.MENU_ENTRY_POINTS.APPLICATION_SETTINGS);
+        context.startActivity(startIntent);
+    }
+    private void showSetAliasDialog(final GBDevice device) {
+        final EditText input = new EditText(context);
+        input.setInputType(InputType.TYPE_CLASS_TEXT);
+        input.setText(device.getAlias());
+        FrameLayout container = new FrameLayout(context);
+        FrameLayout.LayoutParams params = new  FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+        params.leftMargin = context.getResources().getDimensionPixelSize(R.dimen.dialog_margin);
+        params.rightMargin = context.getResources().getDimensionPixelSize(R.dimen.dialog_margin);
+        input.setLayoutParams(params);
+        container.addView(input);
+        // Specify the type of input expected; this, for example, sets the input as a password, and will mask the text
+
+        new MaterialAlertDialogBuilder(context)
+                .setView(container)
+                .setCancelable(true)
+                .setTitle(context.getString(R.string.controlcenter_set_alias))
+                .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
+                    @Override
+                    public void onClick(DialogInterface dialog, int which) {
+                        try (DBHandler dbHandler = GBApplication.acquireDB()) {
+                            DaoSession session = dbHandler.getDaoSession();
+                            Device dbDevice = DBHelper.getDevice(device, session);
+                            String alias = input.getText().toString();
+                            dbDevice.setAlias(alias);
+                            dbDevice.update();
+                            device.setAlias(alias);
+                        } catch (Exception ex) {
+                            GB.toast(context, context.getString(R.string.error_setting_alias) + ex.getMessage(), Toast.LENGTH_LONG, GB.ERROR, ex);
+                        } finally {
+                            Intent refreshIntent = new Intent(DeviceManager.ACTION_REFRESH_DEVICELIST);
+                            LocalBroadcastManager.getInstance(context).sendBroadcast(refreshIntent);
+                        }
+                    }
+                })
+                .setNegativeButton(R.string.Cancel, new DialogInterface.OnClickListener() {
+                    @Override
+                    public void onClick(DialogInterface dialog, int which) {
+                        // do nothing
+                    }
+                })
+                .show();
+    }
+
+    @Override
+    public int getItemCount() {
+        return devicesListWithFolders.size();
+    }
+
+    @Override
+    public long getItemId(int position) {
+        return idGenerator.getId(devicesListWithFolders.get(position));
+    }
+
+    static class ViewHolder extends RecyclerView.ViewHolder {
+
+        View root;
+        MaterialCardView container;
+
+        ImageView deviceImageView;
+        TextView deviceNameLabel;
+        TextView deviceStatusLabel;
+
+        //actions
+        LinearLayout batteryStatusBox0;
+        TextView batteryStatusLabel0;
+        ImageView batteryIcon0;
+        LinearLayout batteryStatusBox1;
+        TextView batteryStatusLabel1;
+        ImageView batteryIcon1;
+        LinearLayout batteryStatusBox2;
+        TextView batteryStatusLabel2;
+        ImageView batteryIcon2;
+        ImageView deviceSpecificSettingsView;
+        LinearLayout fetchActivityDataBox;
+        ImageView fetchActivityData;
+        ProgressBar busyIndicator;
+        ImageView takeScreenshotView;
+        ImageView manageAppsView;
+        ImageView setAlarmsView;
+        ImageView setRemindersView;
+        ImageView showActivityGraphs;
+        ImageView showActivityTracks;
+        ImageView calibrateDevice;
+        LinearLayout heartRateStatusBox;
+        ImageView heartRateIcon;
+        TextView heartRateStatusLabel;
+        FlexboxLayout infoIcons;
+
+
+        ImageView deviceInfoView;
+        //overflow
+        final RelativeLayout deviceInfoBox;
+        ListView deviceInfoList;
+        ImageView findDevice;
+        LinearLayout fmFrequencyBox;
+        TextView fmFrequencyLabel;
+        ImageView ledColor;
+        ImageView powerOff;
+
+        //activity card
+        LinearLayout cardViewActivityCardLayout;
+        PieChart TotalStepsChart;
+        PieChart TotalDistanceChart;
+        PieChart SleepTimeChart;
+
+        ViewHolder(View view) {
+            super(view);
+
+            root = view;
+
+            container = view.findViewById(R.id.card_view);
+
+            deviceImageView = view.findViewById(R.id.device_image);
+            deviceNameLabel = view.findViewById(R.id.device_name);
+            deviceStatusLabel = view.findViewById(R.id.device_status);
+
+            //actions
+            batteryStatusBox0 = view.findViewById(R.id.device_battery_status_box);
+            batteryStatusLabel0 = view.findViewById(R.id.battery_status);
+            batteryIcon0 = view.findViewById(R.id.device_battery_status);
+            batteryStatusBox1 = view.findViewById(R.id.device_battery_status_box1);
+            batteryStatusLabel1 = view.findViewById(R.id.battery_status1);
+            batteryIcon1 = view.findViewById(R.id.device_battery_status1);
+            batteryStatusBox2 = view.findViewById(R.id.device_battery_status_box2);
+            batteryStatusLabel2 = view.findViewById(R.id.battery_status2);
+            batteryIcon2 = view.findViewById(R.id.device_battery_status2);
+
+
+
+            deviceSpecificSettingsView = view.findViewById(R.id.device_specific_settings);
+            fetchActivityDataBox = view.findViewById(R.id.device_action_fetch_activity_box);
+            fetchActivityData = view.findViewById(R.id.device_action_fetch_activity);
+            busyIndicator = view.findViewById(R.id.device_busy_indicator);
+            takeScreenshotView = view.findViewById(R.id.device_action_take_screenshot);
+            manageAppsView = view.findViewById(R.id.device_action_manage_apps);
+            setAlarmsView = view.findViewById(R.id.device_action_set_alarms);
+            setRemindersView = view.findViewById(R.id.device_action_set_reminders);
+            showActivityGraphs = view.findViewById(R.id.device_action_show_activity_graphs);
+            showActivityTracks = view.findViewById(R.id.device_action_show_activity_tracks);
+            deviceInfoView = view.findViewById(R.id.device_info_image);
+            calibrateDevice = view.findViewById(R.id.device_action_calibrate);
+
+            deviceInfoBox = view.findViewById(R.id.device_item_infos_box);
+            //overflow
+            deviceInfoList = view.findViewById(R.id.device_item_infos);
+            findDevice = view.findViewById(R.id.device_action_find);
+            fmFrequencyBox = view.findViewById(R.id.device_fm_frequency_box);
+            fmFrequencyLabel = view.findViewById(R.id.fm_frequency);
+            ledColor = view.findViewById(R.id.device_led_color);
+            powerOff = view.findViewById(R.id.device_action_power_off);
+            heartRateStatusBox = view.findViewById(R.id.device_heart_rate_status_box);
+            heartRateStatusLabel = view.findViewById(R.id.heart_rate_status);
+            heartRateIcon = view.findViewById(R.id.device_heart_rate_status);
+            infoIcons = view.findViewById(R.id.device_info_icons);
+
+            cardViewActivityCardLayout = view.findViewById(R.id.card_view_activity_card_layout);
+
+            TotalStepsChart = view.findViewById(R.id.activity_dashboard_piechart1);
+            TotalDistanceChart = view.findViewById(R.id.activity_dashboard_piechart2);
+            SleepTimeChart = view.findViewById(R.id.activity_dashboard_piechart3);
+        }
+
+    }
+
+    private void justifyListViewHeightBasedOnChildren(ListView listView) {
+        ArrayAdapter adapter = (ArrayAdapter) listView.getAdapter();
+
+        if (adapter == null) {
+            return;
+        }
+        int totalHeight = 0;
+        for (int i = 0; i < adapter.getCount(); i++) {
+            View listItem = adapter.getView(i, null, listView);
+            listItem.measure(0, 0);
+            totalHeight += listItem.getMeasuredHeight();
+        }
+
+        ViewGroup.LayoutParams par = listView.getLayoutParams();
+        par.height = totalHeight + (listView.getDividerHeight() * (adapter.getCount() - 1));
+        listView.setLayoutParams(par);
+        listView.requestLayout();
+    }
+
+    private String getUniqueDeviceName(GBDevice device) {
+        String deviceName = device.getAliasOrName();
+
+        if (!isUniqueDeviceName(device, deviceName)) {
+            if (device.getModel() != null) {
+                deviceName = deviceName + " " + device.getModel();
+                if (!isUniqueDeviceName(device, deviceName)) {
+                    deviceName = deviceName + " " + device.getShortAddress();
+                }
+            } else {
+                deviceName = deviceName + " " + device.getShortAddress();
+            }
+        }
+        return deviceName;
+    }
+
+    private boolean isUniqueDeviceName(GBDevice device, String deviceName) {
+        for (int i = 0; i < deviceList.size(); i++) {
+            GBDevice item = deviceList.get(i);
+            if (item == device) {
+                continue;
+            }
+            if (deviceName.equals(item.getName())) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private void showTransientSnackbar(int resource) {
+        Snackbar snackbar = Snackbar.make(parent, resource, Snackbar.LENGTH_SHORT);
+
+        //View snackbarView = snackbar.getView();
+
+        // change snackbar text color
+        //int snackbarTextId = android.support.design.R.id.snackbar_text;
+        //TextView textView = snackbarView.findViewById(snackbarTextId);
+        //textView.setTextColor();
+        //snackbarView.setBackgroundColor(Color.MAGENTA);
+        snackbar.show();
+    }
+
+    private void setActivityCard(ViewHolder holder, final GBDevice device, DailyTotals dailyTotals) {
+        boolean showActivityCard = GBApplication.getDeviceSpecificSharedPrefs(device.getAddress()).getBoolean(DeviceSettingsPreferenceConst.PREFS_ACTIVITY_IN_DEVICE_CARD, true);
+        holder.cardViewActivityCardLayout.setVisibility(showActivityCard ? View.VISIBLE : View.GONE);
+
+        if (!showActivityCard) {
+            return;
+        }
+
+        int steps = (int) dailyTotals.getSteps();
+        int sleep = (int) dailyTotals.getSleep();
+        int distanceCm = (int) dailyTotals.getDistance();
+        ActivityUser activityUser = new ActivityUser();
+        int stepGoal = activityUser.getStepsGoal();
+        int sleepGoal = activityUser.getSleepDurationGoal();
+        int sleepGoalMinutes = sleepGoal * 60;
+        int distanceGoal = activityUser.getDistanceGoalMeters() * 100;
+        int stepLength = activityUser.getStepLengthCm();
+        double distanceMeters = (distanceCm > 0 ? distanceCm : steps * stepLength) * 0.01;
+        String distanceFormatted = FormatUtils.getFormattedDistanceLabel(distanceMeters);
+
+        setUpChart(holder.TotalStepsChart);
+        setChartsData(holder.TotalStepsChart, steps, stepGoal, context.getString(R.string.steps), String.valueOf(steps), context);
+
+        setUpChart(holder.TotalDistanceChart);
+        setChartsData(holder.TotalDistanceChart, steps * stepLength, distanceGoal, context.getString(R.string.distance), distanceFormatted, context);
+
+        setUpChart(holder.SleepTimeChart);
+        setChartsData(holder.SleepTimeChart, sleep, sleepGoalMinutes, context.getString(R.string.prefs_activity_in_device_card_sleep_title), String.format("%1s", getHM(sleep)), context);
+
+
+        boolean showActivitySteps = GBApplication.getDeviceSpecificSharedPrefs(device.getAddress()).getBoolean(DeviceSettingsPreferenceConst.PREFS_ACTIVITY_IN_DEVICE_CARD_STEPS, true);
+        boolean showActivitySleep = GBApplication.getDeviceSpecificSharedPrefs(device.getAddress()).getBoolean(DeviceSettingsPreferenceConst.PREFS_ACTIVITY_IN_DEVICE_CARD_SLEEP, true);
+        boolean showActivityDistance = GBApplication.getDeviceSpecificSharedPrefs(device.getAddress()).getBoolean(DeviceSettingsPreferenceConst.PREFS_ACTIVITY_IN_DEVICE_CARD_DISTANCE, true);
+
+        //do the multiple mini-charts for activities in a loop
+        Hashtable<PieChart, Pair<Boolean, Integer>> activitiesStatusMiniCharts = new Hashtable<>();
+        activitiesStatusMiniCharts.put(holder.TotalStepsChart, new Pair<>(showActivitySteps && steps > 0, ActivityChartsActivity.getChartsTabIndex("stepsweek", device, context)));
+        activitiesStatusMiniCharts.put(holder.SleepTimeChart, new Pair<>(showActivitySleep && sleep > 0, ActivityChartsActivity.getChartsTabIndex("sleep", device, context)));
+        activitiesStatusMiniCharts.put(holder.TotalDistanceChart, new Pair<>(showActivityDistance && steps > 0, ActivityChartsActivity.getChartsTabIndex("activity", device, context)));
+
+        for (Map.Entry<PieChart, Pair<Boolean, Integer>> miniCharts : activitiesStatusMiniCharts.entrySet()) {
+            PieChart miniChart = miniCharts.getKey();
+            final Pair<Boolean, Integer> parameters = miniCharts.getValue();
+            miniChart.setVisibility(parameters.first ? View.VISIBLE : View.GONE);
+            miniChart.setOnClickListener(new View.OnClickListener() {
+                                             @Override
+                                             public void onClick(View v) {
+                                                 Intent startIntent;
+                                                 startIntent = new Intent(context, ActivityChartsActivity.class);
+                                                 startIntent.putExtra(GBDevice.EXTRA_DEVICE, device);
+                                                 startIntent.putExtra(ActivityChartsActivity.EXTRA_FRAGMENT_ID, parameters.second);
+                                                 context.startActivity(startIntent);
+                                             }
+                                         }
+            );
+        }
+    }
+
+    private String getHM(long value) {
+        return DateTimeUtils.formatDurationHoursMinutes(value, TimeUnit.MINUTES);
+    }
+    private void setUpChart(PieChart DashboardChart) {
+        DashboardChart.setTouchEnabled(false);
+        DashboardChart.setNoDataText("");
+        DashboardChart.getLegend().setEnabled(false);
+        DashboardChart.setDrawHoleEnabled(true);
+        DashboardChart.setHoleColor(Color.WHITE);
+        DashboardChart.getDescription().setText("");
+        DashboardChart.setTransparentCircleColor(Color.WHITE);
+        DashboardChart.setTransparentCircleAlpha(110);
+        DashboardChart.setHoleRadius(70f);
+        DashboardChart.setTransparentCircleRadius(75f);
+        DashboardChart.setDrawCenterText(true);
+        DashboardChart.setRotationEnabled(true);
+        DashboardChart.setHighlightPerTapEnabled(true);
+        DashboardChart.setCenterTextOffset(0, 0);
+    }
+    private void setChartsData(PieChart pieChart, float value, float target, String label, String stringValue, Context context) {
+        final String CHART_COLOR_START = "#e74c3c";
+        final String CHART_COLOR_END = "#2ecc71";
+
+        ArrayList<PieEntry> entries = new ArrayList<>();
+        entries.add(new PieEntry((float) value, context.getResources().getDrawable(R.drawable.ic_star_gold)));
+
+        if (value < target) {
+            entries.add(new PieEntry((float) (target - value)));
+        }
+
+        pieChart.setCenterText(String.format("%s\n%s", stringValue, label));
+        float colorValue = Math.max(0, Math.min(1, value / target));
+        int chartColor = interpolateColor(Color.parseColor(CHART_COLOR_START), Color.parseColor(CHART_COLOR_END), colorValue);
+
+        PieDataSet dataSet = new PieDataSet(entries, "");
+        dataSet.setDrawIcons(false);
+        dataSet.setIconsOffset(new MPPointF(0, -66));
+
+        if (colorValue == 1) {
+            dataSet.setDrawIcons(true);
+        }
+        dataSet.setSliceSpace(0f);
+        dataSet.setSelectionShift(5f);
+        dataSet.setColors(chartColor, Color.LTGRAY);
+
+        PieData data = new PieData(dataSet);
+        data.setValueTextSize(0f);
+        data.setValueTextColor(Color.WHITE);
+
+        pieChart.setData(data);
+        pieChart.invalidate();
+    }
+    private float interpolate(float a, float b, float proportion) {
+        return (a + ((b - a) * proportion));
+    }
+
+    private int interpolateColor(int a, int b, float proportion) {
+        float[] hsva = new float[3];
+        float[] hsvb = new float[3];
+        Color.colorToHSV(a, hsva);
+        Color.colorToHSV(b, hsvb);
+        for (int i = 0; i < 3; i++) {
+            hsvb[i] = interpolate(hsva[i], hsvb[i], proportion);
+        }
+        return Color.HSVToColor(hsvb);
+    }
+
+    @RequiresApi(api = Build.VERSION_CODES.R)
+    void createDynamicShortcut(GBDevice device) {
+        Intent intent = new Intent(context, ControlCenterv2.class)
+                .setAction(ACTION_CONNECT)
+                .setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
+                .putExtra("device", device.getAddress());
+
+        ShortcutManager shortcutManager = (ShortcutManager) context.getApplicationContext().getSystemService(Context.SHORTCUT_SERVICE);
+
+        DeviceCoordinator coordinator = device.getDeviceCoordinator();
+
+        shortcutManager.pushDynamicShortcut(new ShortcutInfo.Builder(context, device.getAddress())
+                .setLongLived(false)
+                .setShortLabel(device.getAliasOrName())
+                .setIntent(intent)
+                .setIcon(Icon.createWithResource(context, coordinator.getDefaultIconResource()))
+                .build()
+        );
+    }
+
+    @RequiresApi(api = Build.VERSION_CODES.R)
+    void removeDynamicShortcut(GBDevice device) {
+        final ShortcutManager shortcutManager = (ShortcutManager) context.getApplicationContext().getSystemService(Context.SHORTCUT_SERVICE);
+
+        shortcutManager.removeDynamicShortcuts(Collections.singletonList(device.getAddress()));
+    }
+
+    private static class GBDeviceDiffUtil extends DiffUtil.ItemCallback<GBDevice> {
+        @Override
+        public boolean areItemsTheSame(@NonNull GBDevice oldItem, @NonNull GBDevice newItem) {
+            return new EqualsBuilder()
+                    .append(oldItem.getAddress(), newItem.getAddress())
+                    .append(oldItem.getName(), newItem.getName())
+                    .isEquals();
+        }
+
+        @Override
+        public boolean areContentsTheSame(@NonNull GBDevice oldItem, @NonNull GBDevice newItem) {
+            return EqualsBuilder.reflectionEquals(oldItem, newItem);
+        }
+    }
+
+    /**
+     * A generator of stable IDs, given a string, since hashCode can easily have collisions.
+     */
+    private static class StableIdGenerator {
+        private final Map<String, Long> idMapping = new HashMap<String, Long>();
+
+        private long nextId = 0;
+
+        public long getId(final GBDevice device) {
+            final String str = String.format("%s_%s", device.getAddress(), device.getName());
+
+            if (!idMapping.containsKey(str)) {
+                idMapping.put(str, nextId++);
+            }
+
+            return idMapping.get(str);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/GBDeviceAppAdapter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/GBDeviceAppAdapter.java
new file mode 100644
index 0000000..5cd82eb
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/GBDeviceAppAdapter.java
@@ -0,0 +1,175 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, Arjan Schrijver, Carsten
+    Pfeiffer, Daniele Gobbetti, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.adapter;
+
+import android.graphics.Bitmap;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import java.util.Collections;
+import java.util.List;
+
+import androidx.recyclerview.widget.RecyclerView;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.appmanager.AbstractAppManagerFragment;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp;
+
+/**
+ * Adapter for displaying GBDeviceApp instances.
+ */
+
+public class GBDeviceAppAdapter extends RecyclerView.Adapter<GBDeviceAppAdapter.AppViewHolder> {
+    private final Logger LOG = LoggerFactory.getLogger(GBDeviceAppAdapter.class);
+
+    private final int mLayoutId;
+    private final List<GBDeviceApp> appList;
+    private final AbstractAppManagerFragment mParentFragment;
+    private final boolean allowReorder;
+
+    public List<GBDeviceApp> getAppList() {
+        return appList;
+    }
+
+    public GBDeviceAppAdapter(List<GBDeviceApp> list, int layoutId, AbstractAppManagerFragment parentFragment, boolean reorder) {
+        mLayoutId = layoutId;
+        appList = list;
+        mParentFragment = parentFragment;
+        allowReorder = reorder;
+    }
+
+    @Override
+    public long getItemId(int position) {
+        return appList.get(position).getUUID().getLeastSignificantBits();
+    }
+
+    @Override
+    public int getItemCount() {
+        return appList.size();
+    }
+
+    @Override
+    public GBDeviceAppAdapter.AppViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+        View view = LayoutInflater.from(parent.getContext()).inflate(mLayoutId, parent, false);
+        return new AppViewHolder(view);
+    }
+
+    @Override
+    public void onBindViewHolder(final AppViewHolder holder, int position) {
+        final GBDeviceApp deviceApp = appList.get(position);
+
+        String appVersionAuthor;
+        if ((deviceApp.getCreator().equals("")) || (deviceApp.getCreator().equals("(unknown)"))) {
+            appVersionAuthor = deviceApp.getVersion();
+        } else {
+            appVersionAuthor = GBApplication.getContext().getString(R.string.appversion_by_creator, deviceApp.getVersion(), deviceApp.getCreator());
+        }
+        holder.mDeviceAppVersionAuthorLabel.setText(appVersionAuthor);
+        if (deviceApp.isUpToDate()) {
+            holder.mDeviceAppOutdated.setVisibility(View.GONE);
+        } else {
+            holder.mDeviceAppOutdated.setVisibility(View.VISIBLE);
+        }
+        // FIXME: replace with small icons
+        String appNameLabelText = deviceApp.getName();
+        holder.mDeviceAppNameLabel.setText(appNameLabelText);
+
+        Bitmap previewImage = deviceApp.getPreviewImage();
+        holder.mPreviewImage.setImageBitmap(previewImage);
+        if (previewImage == null) {
+            holder.mPreviewImage.setVisibility(View.GONE);
+        } else {
+            holder.mPreviewImage.setVisibility(View.VISIBLE);
+        }
+
+        switch (deviceApp.getType()) {
+            case APP_GENERIC:
+                holder.mDeviceImageView.setImageResource(R.drawable.ic_watchapp);
+                break;
+            case APP_ACTIVITYTRACKER:
+                holder.mDeviceImageView.setImageResource(R.drawable.ic_activitytracker);
+                break;
+            case APP_SYSTEM:
+                holder.mDeviceImageView.setImageResource(R.drawable.ic_systemapp);
+                break;
+            case WATCHFACE:
+                holder.mDeviceImageView.setImageResource(R.drawable.ic_watchface);
+                break;
+            default:
+                holder.mDeviceImageView.setImageResource(R.drawable.ic_watchapp);
+        }
+
+        holder.itemView.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                mParentFragment.onItemClick(view, deviceApp);
+            }
+        });
+
+        holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
+            @Override
+            public boolean onLongClick(View view) {
+                return mParentFragment.openPopupMenu(view, deviceApp);
+            }
+        });
+
+        holder.mDragHandle.setVisibility(allowReorder ? View.VISIBLE : View.GONE);
+        holder.mDragHandle.setOnTouchListener(new View.OnTouchListener() {
+            @Override
+            public boolean onTouch(View view, MotionEvent motionEvent) {
+                mParentFragment.startDragging(holder);
+                return true;
+            }
+        });
+
+    }
+
+    public void onItemMove(int from, int to) {
+        Collections.swap(appList, from, to);
+        notifyItemMoved(from, to);
+    }
+
+    public class AppViewHolder extends RecyclerView.ViewHolder {
+        final TextView mDeviceAppVersionAuthorLabel;
+        final TextView mDeviceAppNameLabel;
+        final TextView mDeviceAppOutdated;
+        final ImageView mDeviceImageView;
+        final ImageView mDragHandle;
+        final ImageView mPreviewImage;
+
+        AppViewHolder(View itemView) {
+            super(itemView);
+            mDeviceAppVersionAuthorLabel = (TextView) itemView.findViewById(R.id.item_details);
+            mDeviceAppNameLabel = (TextView) itemView.findViewById(R.id.item_name);
+            mDeviceAppOutdated = (TextView) itemView.findViewById(R.id.item_watchapp_outdated);
+            mDeviceImageView = (ImageView) itemView.findViewById(R.id.item_image);
+            mDragHandle = (ImageView) itemView.findViewById(R.id.drag_handle);
+            mPreviewImage = (ImageView) itemView.findViewById(R.id.item_preview_image);
+        }
+
+    }
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/GBReminderListAdapter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/GBReminderListAdapter.java
new file mode 100644
index 0000000..7163bdf
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/GBReminderListAdapter.java
@@ -0,0 +1,156 @@
+/*  Copyright (C) 2021-2024 Arjan Schrijver, Jos茅 Rebelo, Johannes Krude
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.adapter;
+
+
+import android.content.Context;
+import android.content.DialogInterface;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.google.android.material.card.MaterialCardView;
+import com.google.android.material.dialog.MaterialAlertDialogBuilder;
+
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.ConfigureReminders;
+import nodomain.freeyourgadget.gadgetbridge.entities.Reminder;
+
+/**
+ * Adapter for displaying Reminder instances.
+ */
+public class GBReminderListAdapter extends RecyclerView.Adapter<GBReminderListAdapter.ViewHolder> {
+
+    private final Context mContext;
+    private ArrayList<Reminder> reminderList;
+    private boolean remindersHaveTime;
+
+    public GBReminderListAdapter(Context context, boolean remindersHaveTime) {
+        this.mContext = context;
+        this.remindersHaveTime = remindersHaveTime;
+    }
+
+    public void setReminderList(List<Reminder> reminders) {
+        this.reminderList = new ArrayList<>(reminders);
+    }
+
+    public ArrayList<Reminder> getReminderList() {
+        return reminderList;
+    }
+
+    @NonNull
+    @Override
+    public GBReminderListAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+        final View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_reminder, parent, false);
+        return new ViewHolder(view);
+    }
+
+    @Override
+    public void onBindViewHolder(@NonNull ViewHolder holder, final int position) {
+        final Reminder reminder = reminderList.get(position);
+
+        holder.container.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                ((ConfigureReminders) mContext).configureReminder(reminder);
+            }
+        });
+
+        holder.container.setOnLongClickListener(new View.OnLongClickListener() {
+            @Override
+            public boolean onLongClick(View v) {
+                new MaterialAlertDialogBuilder(v.getContext())
+                        .setTitle(R.string.reminder_delete_confirm_title)
+                        .setMessage(R.string.reminder_delete_confirm_description)
+                        .setIcon(R.drawable.ic_warning)
+                        .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
+                            public void onClick(final DialogInterface dialog, final int whichButton) {
+                                ((ConfigureReminders) mContext).deleteReminder(reminder);
+                            }
+                        })
+                        .setNegativeButton(android.R.string.no, null)
+                        .show();
+
+                return true;
+            }
+        });
+
+        holder.reminderMessage.setText(reminder.getMessage());
+
+        final Date time = reminder.getDate();
+        SimpleDateFormat format;
+        if (remindersHaveTime) {
+            format = new SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.getDefault());
+        } else {
+            format = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault());
+        }
+
+        int stringResId = 0;
+
+        switch (reminder.getRepetition()) {
+            case Reminder.ONCE:
+                stringResId = R.string.reminder_time_once;
+                break;
+            case Reminder.EVERY_DAY:
+                stringResId = R.string.reminder_time_every_day;
+                break;
+            case Reminder.EVERY_WEEK:
+                stringResId = R.string.reminder_time_every_week;
+                break;
+            case Reminder.EVERY_MONTH:
+                stringResId = R.string.reminder_time_every_month;
+                break;
+            case Reminder.EVERY_YEAR:
+                stringResId = R.string.reminder_time_every_year;
+                break;
+        }
+
+        final String reminderTimeText = mContext.getString(stringResId, format.format(time));
+        holder.reminderTime.setText(reminderTimeText);
+    }
+
+    @Override
+    public int getItemCount() {
+        return reminderList.size();
+    }
+
+    static class ViewHolder extends RecyclerView.ViewHolder {
+        final MaterialCardView container;
+
+        final TextView reminderTime;
+        final TextView reminderMessage;
+
+        ViewHolder(View view) {
+            super(view);
+
+            container = view.findViewById(R.id.card_view);
+
+            reminderTime = view.findViewById(R.id.reminder_item_time);
+            reminderMessage = view.findViewById(R.id.reminder_item_message);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/GBWorldClockListAdapter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/GBWorldClockListAdapter.java
new file mode 100644
index 0000000..b687be8
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/GBWorldClockListAdapter.java
@@ -0,0 +1,132 @@
+/*  Copyright (C) 2022-2024 Arjan Schrijver, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.adapter;
+
+
+import android.content.Context;
+import android.content.DialogInterface;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.google.android.material.card.MaterialCardView;
+import com.google.android.material.dialog.MaterialAlertDialogBuilder;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.TimeZone;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.ConfigureWorldClocks;
+import nodomain.freeyourgadget.gadgetbridge.entities.WorldClock;
+
+/**
+ * Adapter for displaying WorldClock instances.
+ */
+public class GBWorldClockListAdapter extends RecyclerView.Adapter<GBWorldClockListAdapter.ViewHolder> {
+
+    private final Context mContext;
+    private ArrayList<WorldClock> worldClockList;
+
+    public GBWorldClockListAdapter(final Context context) {
+        this.mContext = context;
+    }
+
+    public void setWorldClockList(final List<WorldClock> worldClocks) {
+        this.worldClockList = new ArrayList<>(worldClocks);
+    }
+
+    public ArrayList<WorldClock> getWorldClockList() {
+        return worldClockList;
+    }
+
+    @NonNull
+    @Override
+    public GBWorldClockListAdapter.ViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) {
+        final View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_world_clock, parent, false);
+        return new ViewHolder(view);
+    }
+
+    @Override
+    public void onBindViewHolder(@NonNull ViewHolder holder, final int position) {
+        final WorldClock worldClock = worldClockList.get(position);
+
+        holder.container.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                ((ConfigureWorldClocks) mContext).configureWorldClock(worldClock);
+            }
+        });
+
+        holder.container.setOnLongClickListener(new View.OnLongClickListener() {
+            @Override
+            public boolean onLongClick(View v) {
+                new MaterialAlertDialogBuilder(v.getContext())
+                        .setTitle(v.getContext().getString(R.string.world_clock_delete_confirm_title, worldClock.getLabel()))
+                        .setMessage(R.string.world_clock_delete_confirm_description)
+                        .setIcon(R.drawable.ic_warning)
+                        .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
+                            public void onClick(final DialogInterface dialog, final int whichButton) {
+                                ((ConfigureWorldClocks) mContext).deleteWorldClock(worldClock);
+                            }
+                        })
+                        .setNegativeButton(android.R.string.no, null)
+                        .show();
+
+                return true;
+            }
+        });
+
+        holder.worldClockLabel.setText(worldClock.getLabel());
+        holder.worldClockTimezone.setText(worldClock.getTimeZoneId());
+
+        final DateFormat df = new SimpleDateFormat("HH:mm", GBApplication.getLanguage());
+        df.setTimeZone(TimeZone.getTimeZone(worldClock.getTimeZoneId()));
+        holder.worldClockCurrentTime.setText(df.format(new Date()));
+    }
+
+    @Override
+    public int getItemCount() {
+        return worldClockList.size();
+    }
+
+    static class ViewHolder extends RecyclerView.ViewHolder {
+        final MaterialCardView container;
+
+        final TextView worldClockTimezone;
+        final TextView worldClockLabel;
+        final TextView worldClockCurrentTime;
+
+        ViewHolder(View view) {
+            super(view);
+
+            container = view.findViewById(R.id.card_view);
+
+            worldClockTimezone = view.findViewById(R.id.world_clock_item_timezone);
+            worldClockLabel = view.findViewById(R.id.world_clock_item_label);
+            worldClockCurrentTime = view.findViewById(R.id.world_clock_current_time);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/ItemWithDetailsAdapter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/ItemWithDetailsAdapter.java
new file mode 100644
index 0000000..3bb2e07
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/ItemWithDetailsAdapter.java
@@ -0,0 +1,55 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, Arjan Schrijver, Carsten
+    Pfeiffer
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.adapter;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.model.ItemWithDetails;
+
+/**
+ * Adapter for displaying generic ItemWithDetails instances.
+ */
+public class ItemWithDetailsAdapter extends AbstractItemAdapter<ItemWithDetails> {
+
+    public ItemWithDetailsAdapter(Context context, List<ItemWithDetails> items) {
+        super(context, items);
+    }
+
+    @Override
+    protected String getName(ItemWithDetails item) {
+        return item.getName();
+    }
+
+    @Override
+    protected String getDetails(ItemWithDetails item) {
+        return item.getDetails();
+    }
+
+    @Override
+    protected int getIcon(ItemWithDetails item) {
+        return item.getIcon();
+    }
+
+    @Override
+    protected Bitmap getPreview(ItemWithDetails item) {
+        return item.getPreview();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/MusicListAdapter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/MusicListAdapter.java
new file mode 100644
index 0000000..6b9580d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/MusicListAdapter.java
@@ -0,0 +1,118 @@
+package nodomain.freeyourgadget.gadgetbridge.adapter;
+
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import androidx.recyclerview.widget.RecyclerView;
+import java.util.ArrayList;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceMusic;
+
+public class MusicListAdapter extends RecyclerView.Adapter<MusicListAdapter.MusicViewHolder> {
+
+    public static int MAX_MULTISELECT_COUNT = 50;
+    public interface onDataAction {
+        void onItemClick(View view, GBDeviceMusic music);
+        void onMultiSelect(int count, boolean limit);
+    }
+
+    private final List<GBDeviceMusic> musicList;
+    private final onDataAction callback;
+
+    private final List<GBDeviceMusic> selectedItems = new ArrayList<>();
+
+    public MusicListAdapter(List<GBDeviceMusic> list, onDataAction callback) {
+        this.musicList = list;
+        this.callback = callback;
+    }
+
+    @Override
+    public long getItemId(int position) {
+        return musicList.get(position).getId();
+    }
+
+    @Override
+    public int getItemCount() {
+        return musicList.size();
+    }
+
+    @Override
+    public MusicListAdapter.MusicViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_musicmanager_song, parent, false);
+        return new MusicViewHolder(view);
+    }
+
+    private void toggleSelection(GBDeviceMusic music) {
+        if(selectedItems.size() >= MAX_MULTISELECT_COUNT) {
+            callback.onMultiSelect(selectedItems.size(), true);
+            return;
+        }
+        if(selectedItems.contains(music)) {
+            selectedItems.remove(music);
+        } else {
+            selectedItems.add(music);
+        }
+        notifyItemChanged(musicList.indexOf(music));
+        callback.onMultiSelect(selectedItems.size(), false);
+    }
+
+    @Override
+    public void onBindViewHolder(final MusicListAdapter.MusicViewHolder holder, int position) {
+        final GBDeviceMusic music = musicList.get(position);
+
+        holder.musicTitle.setText(music.getTitle());
+        holder.musicArtist.setText(music.getArtist());
+
+        holder.icon.setSelected(selectedItems.contains(music));
+
+        if(callback != null) {
+            holder.itemView.setOnClickListener(new View.OnClickListener() {
+                @Override
+                public void onClick(View view) {
+                    if(!selectedItems.isEmpty()) {
+                        toggleSelection(music);
+                    } else {
+                        callback.onItemClick(view, music);
+                    }
+                }
+            });
+
+            holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
+                @Override
+                public boolean onLongClick(View view) {
+                    toggleSelection(music);
+                    return true;
+                }
+            });
+        }
+    }
+
+    public void clearSelectedItems() {
+        selectedItems.clear();
+        notifyDataSetChanged();
+        callback.onMultiSelect(selectedItems.size(), false);
+    }
+
+    public List<GBDeviceMusic> getSelectedItems() {
+        return selectedItems;
+    }
+
+    public static class MusicViewHolder extends RecyclerView.ViewHolder {
+        final TextView musicArtist;
+        final TextView musicTitle;
+        final ImageView icon;
+
+        MusicViewHolder(View itemView) {
+            super(itemView);
+            musicArtist = itemView.findViewById(R.id.item_details);
+            musicTitle = itemView.findViewById(R.id.item_name);
+            icon = itemView.findViewById(R.id.item_image);
+        }
+
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/NestedFragmentAdapter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/NestedFragmentAdapter.java
new file mode 100644
index 0000000..48f40e5
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/NestedFragmentAdapter.java
@@ -0,0 +1,52 @@
+/*  Copyright (C) 2024 a0z, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.adapter;
+
+import androidx.fragment.app.FragmentManager;
+import androidx.viewpager2.adapter.FragmentStateAdapter;
+
+import java.util.List;
+import java.util.stream.Collectors;
+import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBFragment;
+
+public abstract class NestedFragmentAdapter extends FragmentStateAdapter {
+    protected FragmentManager fragmentManager;
+
+    public NestedFragmentAdapter(AbstractGBFragment fragment, FragmentManager childFragmentManager) {
+        super(fragment);
+        fragmentManager = childFragmentManager;
+    }
+
+    @Override
+    public int getItemCount() {
+        return 3;
+    }
+
+    public void updateFragments(int position) {
+        List<AbstractGBFragment> fragments = fragmentManager.getFragments()
+                .stream()
+                .map(e -> (AbstractGBFragment) e)
+                .collect(Collectors.toList());
+        for (AbstractGBFragment fragment : fragments) {
+            if (position < 0 || fragment != fragmentManager.findFragmentByTag("f" + position)) {
+                fragment.onMadeInvisibleInActivity();
+            } else {
+                fragment.onMadeVisibleInActivityInternal();
+            }
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/RespiratoryRateFragmentAdapter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/RespiratoryRateFragmentAdapter.java
new file mode 100644
index 0000000..5394197
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/RespiratoryRateFragmentAdapter.java
@@ -0,0 +1,32 @@
+package nodomain.freeyourgadget.gadgetbridge.adapter;
+
+import androidx.annotation.NonNull;
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentManager;
+
+import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBFragment;
+import nodomain.freeyourgadget.gadgetbridge.activities.charts.RespiratoryRateDailyFragment;
+import nodomain.freeyourgadget.gadgetbridge.activities.charts.RespiratoryRatePeriodFragment;
+
+public class RespiratoryRateFragmentAdapter extends NestedFragmentAdapter {
+    protected FragmentManager fragmentManager;
+
+    public RespiratoryRateFragmentAdapter(AbstractGBFragment fragment, FragmentManager childFragmentManager) {
+        super(fragment, childFragmentManager);
+        fragmentManager = childFragmentManager;
+    }
+
+    @NonNull
+    @Override
+    public Fragment createFragment(int position) {
+        switch (position) {
+            case 0:
+                return new RespiratoryRateDailyFragment();
+            case 1:
+                return RespiratoryRatePeriodFragment.newInstance(7);
+            case 2:
+                return RespiratoryRatePeriodFragment.newInstance(30);
+        }
+        return new RespiratoryRateDailyFragment();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/SleepFragmentAdapter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/SleepFragmentAdapter.java
new file mode 100644
index 0000000..7ea34a8
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/SleepFragmentAdapter.java
@@ -0,0 +1,30 @@
+package nodomain.freeyourgadget.gadgetbridge.adapter;
+
+import androidx.annotation.NonNull;
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentManager;
+
+
+import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBFragment;
+import nodomain.freeyourgadget.gadgetbridge.activities.charts.DaySleepChartFragment;
+import nodomain.freeyourgadget.gadgetbridge.activities.charts.WeekSleepChartFragment;
+
+public class SleepFragmentAdapter extends NestedFragmentAdapter {
+    public SleepFragmentAdapter(AbstractGBFragment fragment, FragmentManager childFragmentManager) {
+        super(fragment, childFragmentManager);
+    }
+
+    @NonNull
+    @Override
+    public Fragment createFragment(int position) {
+        switch (position) {
+            case 0:
+                return new DaySleepChartFragment();
+            case 1:
+                return WeekSleepChartFragment.newInstance(7);
+            case 2:
+                return WeekSleepChartFragment.newInstance(30);
+        }
+        return new DaySleepChartFragment();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/SpinnerWithIconAdapter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/SpinnerWithIconAdapter.java
new file mode 100644
index 0000000..5732cdb
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/SpinnerWithIconAdapter.java
@@ -0,0 +1,78 @@
+/*  Copyright (C) 2020-2024 Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.adapter;
+
+import android.app.Activity;
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+
+public class SpinnerWithIconAdapter extends ArrayAdapter<SpinnerWithIconItem> {
+    private static final Logger LOG = LoggerFactory.getLogger(SpinnerWithIconAdapter.class);
+    int groupid;
+    Activity context;
+    ArrayList<SpinnerWithIconItem> list;
+    LayoutInflater inflater;
+
+    public SpinnerWithIconAdapter(Activity context, int groupid, int id, ArrayList<SpinnerWithIconItem>
+            list) {
+        super(context, id, list);
+        this.list = list;
+        inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+        this.groupid = groupid;
+    }
+
+    public View getView(int position, View convertView, ViewGroup parent) {
+        View itemView = inflater.inflate(groupid, parent, false);
+        ImageView imageView = (ImageView) itemView.findViewById(R.id.spinner_item_icon);
+        imageView.setImageResource(list.get(position).getImageId());
+        TextView textView = (TextView) itemView.findViewById(R.id.spinner_item_text);
+        textView.setText(list.get(position).getText());
+
+        return itemView;
+    }
+
+    public View getDropDownView(int position, View convertView, ViewGroup
+            parent) {
+        return getView(position, convertView, parent);
+
+    }
+
+    public int getItemPositionForSelection(SpinnerWithIconItem item) {
+        if (item == null) return -1;
+        int index = 0;
+        for (SpinnerWithIconItem listItem : list) {
+            if (listItem.id.equals(item.id)) {
+                return index;
+            }
+            index++;
+        }
+        return -1;
+    }
+}
+
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/SpinnerWithIconItem.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/SpinnerWithIconItem.java
new file mode 100644
index 0000000..dd1292b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/SpinnerWithIconItem.java
@@ -0,0 +1,48 @@
+/*  Copyright (C) 2020-2024 Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.adapter;
+
+public class SpinnerWithIconItem {
+
+    String text;
+    Long id;
+    int imageId;
+
+    public SpinnerWithIconItem(String text, Long id, int imageId) {
+        this.text = text;
+        this.id = id;
+        this.imageId = imageId;
+    }
+
+    public String getText() {
+        return text;
+    }
+
+    public int getImageId() {
+        return imageId;
+    }
+
+    public Long getId() {
+        return id;
+    }
+
+    @Override
+    public String toString() {
+        return text + " " + id + " " + imageId;
+    }
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/StepsFragmentAdapter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/StepsFragmentAdapter.java
new file mode 100644
index 0000000..295aa65
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/StepsFragmentAdapter.java
@@ -0,0 +1,32 @@
+package nodomain.freeyourgadget.gadgetbridge.adapter;
+
+import androidx.annotation.NonNull;
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentManager;
+
+import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBFragment;
+import nodomain.freeyourgadget.gadgetbridge.activities.charts.StepsDailyFragment;
+import nodomain.freeyourgadget.gadgetbridge.activities.charts.StepsPeriodFragment;
+
+public class StepsFragmentAdapter extends NestedFragmentAdapter {
+    protected FragmentManager fragmentManager;
+
+    public StepsFragmentAdapter(AbstractGBFragment fragment, FragmentManager childFragmentManager) {
+        super(fragment, childFragmentManager);
+        fragmentManager = childFragmentManager;
+    }
+
+    @NonNull
+    @Override
+    public Fragment createFragment(int position) {
+        switch (position) {
+            case 0:
+                return new StepsDailyFragment();
+            case 1:
+                return StepsPeriodFragment.newInstance(7);
+            case 2:
+                return StepsPeriodFragment.newInstance(30);
+        }
+        return new StepsDailyFragment();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/capabilities/GpsCapability.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/capabilities/GpsCapability.java
new file mode 100644
index 0000000..b718fbf
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/capabilities/GpsCapability.java
@@ -0,0 +1,45 @@
+/*  Copyright (C) 2022-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.capabilities;
+
+public class GpsCapability {
+    public enum Preset {
+        ACCURACY,
+        BALANCED,
+        POWER_SAVING,
+        CUSTOM
+    }
+
+    public enum Band {
+        SINGLE_BAND,
+        DUAL_BAND
+    }
+
+    public enum Combination {
+        LOW_POWER_GPS,
+        GPS,
+        GPS_BDS,
+        GPS_GLONASS,
+        GPS_GALILEO,
+        ALL_SATELLITES
+    }
+
+    public enum SatelliteSearch {
+        SPEED_FIRST,
+        ACCURACY_FIRST
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/capabilities/HeartRateCapability.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/capabilities/HeartRateCapability.java
new file mode 100644
index 0000000..c6029fb
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/capabilities/HeartRateCapability.java
@@ -0,0 +1,163 @@
+/*  Copyright (C) 2022-2024 Arjan Schrijver, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.capabilities;
+
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_HEARTRATE_ACTIVITY_MONITORING;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_HEARTRATE_ALERT_ACTIVE_HIGH_THRESHOLD;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_HEARTRATE_ALERT_ENABLED;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_HEARTRATE_ALERT_HIGH_THRESHOLD;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_HEARTRATE_ALERT_LOW_THRESHOLD;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_HEARTRATE_MEASUREMENT_INTERVAL;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_HEARTRATE_STRESS_MONITORING;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_HEARTRATE_USE_FOR_SLEEP_DETECTION;
+
+import android.content.Context;
+
+import androidx.preference.ListPreference;
+import androidx.preference.Preference;
+import androidx.preference.SwitchPreferenceCompat;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsHandler;
+
+public class HeartRateCapability {
+    public enum MeasurementInterval {
+        OFF(0, R.string.off),
+        SMART(-1, R.string.smart),
+        MINUTES_1(60, R.string.interval_one_minute),
+        MINUTES_5(300, R.string.interval_five_minutes),
+        MINUTES_10(600, R.string.interval_ten_minutes),
+        MINUTES_15(900, R.string.interval_fifteen_minutes),
+        MINUTES_30(1800, R.string.interval_thirty_minutes),
+        MINUTES_45(2700, R.string.interval_forty_five_minutes),
+        HOUR_1(3600, R.string.interval_one_hour),
+        ;
+
+        private final int intervalSeconds;
+        private final int label;
+
+        MeasurementInterval(final int intervalSeconds, final int label) {
+            this.intervalSeconds = intervalSeconds;
+            this.label = label;
+        }
+
+        public int getIntervalSeconds() {
+            return intervalSeconds;
+        }
+
+        public int getLabel() {
+            return label;
+        }
+
+        public String getLabel(final Context context) {
+            return context.getString(label);
+        }
+    }
+
+    public void registerPreferences(final Context context, final List<MeasurementInterval> intervals, final DeviceSpecificSettingsHandler handler) {
+        final Preference enableHeartrateSleepSupport = handler.findPreference(PREF_HEARTRATE_USE_FOR_SLEEP_DETECTION);
+        if (enableHeartrateSleepSupport != null) {
+            enableHeartrateSleepSupport.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
+                @Override
+                public boolean onPreferenceChange(Preference preference, Object newVal) {
+                    GBApplication.deviceService(handler.getDevice()).onEnableHeartRateSleepSupport(Boolean.TRUE.equals(newVal));
+                    return true;
+                }
+            });
+        }
+
+        handler.addPreferenceHandlerFor(PREF_HEARTRATE_ALERT_ACTIVE_HIGH_THRESHOLD);
+        handler.addPreferenceHandlerFor(PREF_HEARTRATE_ALERT_HIGH_THRESHOLD);
+        handler.addPreferenceHandlerFor(PREF_HEARTRATE_ALERT_LOW_THRESHOLD);
+
+        final ListPreference heartrateMeasurementInterval = handler.findPreference(PREF_HEARTRATE_MEASUREMENT_INTERVAL);
+        final ListPreference heartrateAlertActiveHigh = handler.findPreference(PREF_HEARTRATE_ALERT_ACTIVE_HIGH_THRESHOLD);
+        final ListPreference heartrateAlertHigh = handler.findPreference(PREF_HEARTRATE_ALERT_HIGH_THRESHOLD);
+        final ListPreference heartrateAlertLow = handler.findPreference(PREF_HEARTRATE_ALERT_LOW_THRESHOLD);
+        // Newer devices that have low alert threshold can only use it if measurement interval is smart (-1) or 1 minute
+        final boolean hrAlertsNeedSmartOrOne = heartrateAlertActiveHigh != null && heartrateAlertHigh != null && heartrateAlertLow != null && heartrateMeasurementInterval != null;
+        if (hrAlertsNeedSmartOrOne) {
+            final boolean hrMonitoringIsSmartOrOne = heartrateMeasurementInterval.getValue().equals("60") ||
+                    heartrateMeasurementInterval.getValue().equals("-1");
+
+            heartrateAlertHigh.setEnabled(hrMonitoringIsSmartOrOne);
+            heartrateAlertLow.setEnabled(hrMonitoringIsSmartOrOne);
+            heartrateAlertActiveHigh.setEnabled(hrMonitoringIsSmartOrOne);
+        }
+
+        if (heartrateMeasurementInterval != null) {
+            // Set the measurement intervals dynamically, as per the device's capability
+            final CharSequence[] entries = new CharSequence[intervals.size()];
+            final CharSequence[] values = new CharSequence[intervals.size()];
+            for (int i = 0; i < intervals.size(); i++) {
+                entries[i] = intervals.get(i).getLabel(context);
+                values[i] = String.valueOf(intervals.get(i).getIntervalSeconds());
+            }
+            heartrateMeasurementInterval.setEntries(entries);
+            heartrateMeasurementInterval.setEntryValues(values);
+
+            final SwitchPreferenceCompat activityMonitoring = handler.findPreference(PREF_HEARTRATE_ACTIVITY_MONITORING);
+            final SwitchPreferenceCompat heartrateAlertEnabled = handler.findPreference(PREF_HEARTRATE_ALERT_ENABLED);
+            final SwitchPreferenceCompat stressMonitoring = handler.findPreference(PREF_HEARTRATE_STRESS_MONITORING);
+
+            heartrateMeasurementInterval.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
+                public boolean onPreferenceChange(final Preference preference, final Object newVal) {
+                    GBApplication.deviceService(handler.getDevice()).onSetHeartRateMeasurementInterval(Integer.parseInt((String) newVal));
+
+                    final boolean isMeasurementIntervalEnabled = !newVal.equals("0");
+
+                    if (activityMonitoring != null) {
+                        activityMonitoring.setEnabled(isMeasurementIntervalEnabled);
+                    }
+                    if (heartrateAlertEnabled != null) {
+                        heartrateAlertEnabled.setEnabled(isMeasurementIntervalEnabled);
+                    }
+                    if (hrAlertsNeedSmartOrOne) {
+                        // Same as above, check if smart or 1 minute
+                        final boolean hrMonitoringIsSmartOrOne = newVal.equals("60") || newVal.equals("-1");
+
+                        heartrateAlertActiveHigh.setEnabled(hrMonitoringIsSmartOrOne);
+                        heartrateAlertHigh.setEnabled(hrMonitoringIsSmartOrOne);
+                        heartrateAlertLow.setEnabled(hrMonitoringIsSmartOrOne);
+                    }
+                    if (stressMonitoring != null && !hrAlertsNeedSmartOrOne) {
+                        // Newer devices (that have hrAlertsNeedSmartOrOne) also don't need HR monitoring for stress monitoring
+                        stressMonitoring.setEnabled(isMeasurementIntervalEnabled);
+                    }
+
+                    return true;
+                }
+            });
+
+            final boolean isMeasurementIntervalEnabled = !heartrateMeasurementInterval.getValue().equals("0");
+
+            if (activityMonitoring != null) {
+                activityMonitoring.setEnabled(isMeasurementIntervalEnabled);
+            }
+            if (heartrateAlertEnabled != null) {
+                heartrateAlertEnabled.setEnabled(isMeasurementIntervalEnabled);
+            }
+            if (stressMonitoring != null && !hrAlertsNeedSmartOrOne) {
+                // Newer devices (that have hrAlertsNeedSmartOrOne) also don't need HR monitoring for stress monitoring
+                stressMonitoring.setEnabled(isMeasurementIntervalEnabled);
+            }
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/capabilities/WorkoutDetectionCapability.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/capabilities/WorkoutDetectionCapability.java
new file mode 100644
index 0000000..12da8ab
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/capabilities/WorkoutDetectionCapability.java
@@ -0,0 +1,36 @@
+/*  Copyright (C) 2022-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.capabilities;
+
+public class WorkoutDetectionCapability {
+    public enum Category {
+        WALKING,
+        INDOOR_WALKING,
+        OUTDOOR_RUNNING,
+        TREADMILL,
+        OUTDOOR_CYCLING,
+        POOL_SWIMMING,
+        ELLIPTICAL,
+        ROWING_MACHINE
+    }
+
+    public enum Sensitivity {
+        HIGH,
+        STANDARD,
+        LOW
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/capabilities/loyaltycards/BarcodeFormat.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/capabilities/loyaltycards/BarcodeFormat.java
new file mode 100644
index 0000000..3f6311d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/capabilities/loyaltycards/BarcodeFormat.java
@@ -0,0 +1,36 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.capabilities.loyaltycards;
+
+/**
+ * Matching com.google.zxing.BarcodeFormat
+ */
+public enum BarcodeFormat {
+    AZTEC,
+    CODE_39,
+    CODE_93,
+    CODE_128,
+    CODABAR,
+    DATA_MATRIX,
+    EAN_8,
+    EAN_13,
+    ITF,
+    PDF_417,
+    QR_CODE,
+    UPC_A,
+    UPC_E,
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/capabilities/loyaltycards/CatimaContentProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/capabilities/loyaltycards/CatimaContentProvider.java
new file mode 100644
index 0000000..a6b6da7
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/capabilities/loyaltycards/CatimaContentProvider.java
@@ -0,0 +1,264 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.capabilities.loyaltycards;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Currency;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+public class CatimaContentProvider {
+    private static final Logger LOG = LoggerFactory.getLogger(CatimaContentProvider.class);
+
+    public static final List<String> KNOWN_PACKAGES = new ArrayList<String>() {{
+        add("me.hackerchick.catima");
+        add("me.hackerchick.catima.debug");
+    }};
+
+    private final Context mContext;
+    private final Uri versionUri;
+    private final Uri cardsUri;
+    private final Uri groupsUri;
+    private final Uri cardGroupsUri;
+    private final String readPermission;
+
+    public CatimaContentProvider(final Context context, final String catimaPackageName) {
+        this.mContext = context;
+        final String catimaAuthority = catimaPackageName + ".contentprovider.cards";
+        this.versionUri = Uri.parse(String.format(Locale.ROOT, "content://%s/version", catimaAuthority));
+        this.cardsUri = Uri.parse(String.format(Locale.ROOT, "content://%s/cards", catimaAuthority));
+        this.groupsUri = Uri.parse(String.format(Locale.ROOT, "content://%s/groups", catimaAuthority));
+        this.cardGroupsUri = Uri.parse(String.format(Locale.ROOT, "content://%s/card_groups", catimaAuthority));
+        this.readPermission = String.format(Locale.ROOT, "%s.READ_CARDS", catimaPackageName);
+    }
+
+    public String getReadPermission() {
+        return this.readPermission;
+    }
+
+    public boolean isCatimaCompatible() {
+        final ContentResolver contentResolver = mContext.getContentResolver();
+        try (Cursor cursor = contentResolver.query(versionUri, null, null, null, null)) {
+            if (cursor == null || cursor.getCount() == 0) {
+                LOG.warn("Catima content provider version not found");
+                return false;
+            }
+
+            cursor.moveToNext();
+            final int major = cursor.getInt(cursor.getColumnIndexOrThrow("major"));
+            final int minor = cursor.getInt(cursor.getColumnIndexOrThrow("minor"));
+
+            LOG.info("Got catima content provider version: {}.{}", major, minor);
+
+            // We only support version 1.x for now
+            return major == 1;
+        } catch (final Exception e) {
+            LOG.error("Failed to get content provider version from Catima", e);
+        }
+
+        return false;
+    }
+
+    public List<LoyaltyCard> getCards() {
+        final List<LoyaltyCard> cards = new ArrayList<>();
+        final ContentResolver contentResolver = mContext.getContentResolver();
+        try (Cursor cursor = contentResolver.query(cardsUri, null, null, null, null)) {
+            if (cursor == null || cursor.getCount() == 0) {
+                LOG.debug("No cards found");
+                return cards;
+            }
+
+            while (cursor.moveToNext()) {
+                final LoyaltyCard loyaltyCard = toLoyaltyCard(cursor);
+                cards.add(loyaltyCard);
+            }
+        } catch (final Exception e) {
+            LOG.error("Failed to list cards from Catima", e);
+            return cards;
+        }
+
+        return cards;
+    }
+
+    public List<String> getGroups() {
+        final List<String> groups = new ArrayList<>();
+        final ContentResolver contentResolver = mContext.getContentResolver();
+        try (Cursor cursor = contentResolver.query(groupsUri, null, null, null, null)) {
+            if (cursor == null || cursor.getCount() == 0) {
+                LOG.debug("No groups found");
+                return groups;
+            }
+
+            while (cursor.moveToNext()) {
+                final String groupId = cursor.getString(cursor.getColumnIndexOrThrow(LoyaltyCardDbGroups.ID));
+                groups.add(groupId);
+            }
+        } catch (final Exception e) {
+            LOG.error("Failed to list groups from Catima", e);
+            return groups;
+        }
+
+        return groups;
+    }
+
+    /**
+     * Gets the mapping of group to list of card IDs.
+     *
+     * @return the mapping of group to list of card IDS.
+     */
+    public Map<String, List<Integer>> getGroupCards() {
+        final Map<String, List<Integer>> groupCards = new HashMap<>();
+        final ContentResolver contentResolver = mContext.getContentResolver();
+        try (Cursor cursor = contentResolver.query(cardGroupsUri, null, null, null, null)) {
+            if (cursor == null || cursor.getCount() == 0) {
+                LOG.debug("No card groups found");
+                return groupCards;
+            }
+
+            while (cursor.moveToNext()) {
+                final int cardId = cursor.getInt(cursor.getColumnIndexOrThrow(LoyaltyCardDbIdsGroups.cardID));
+                final String groupId = cursor.getString(cursor.getColumnIndexOrThrow(LoyaltyCardDbIdsGroups.groupID));
+                final List<Integer> group;
+                if (groupCards.containsKey(groupId)) {
+                    group = groupCards.get(groupId);
+                } else {
+                    group = new ArrayList<>();
+                    groupCards.put(groupId, group);
+                }
+                group.add(cardId);
+            }
+        } catch (final Exception e) {
+            LOG.error("Failed to get group cards from Catima", e);
+            return groupCards;
+        }
+
+        return groupCards;
+    }
+
+    public static LoyaltyCard toLoyaltyCard(final Cursor cursor) {
+        final int id = cursor.getInt(cursor.getColumnIndexOrThrow(LoyaltyCardDbIds.ID));
+        final String name = cursor.getString(cursor.getColumnIndexOrThrow(LoyaltyCardDbIds.STORE));
+        final String note = cursor.getString(cursor.getColumnIndexOrThrow(LoyaltyCardDbIds.NOTE));
+        final long expiryLong = cursor.getLong(cursor.getColumnIndexOrThrow(LoyaltyCardDbIds.EXPIRY));
+        final BigDecimal balance = new BigDecimal(cursor.getString(cursor.getColumnIndexOrThrow(LoyaltyCardDbIds.BALANCE)));
+        final String cardId = cursor.getString(cursor.getColumnIndexOrThrow(LoyaltyCardDbIds.CARD_ID));
+        final String barcodeId = cursor.getString(cursor.getColumnIndexOrThrow(LoyaltyCardDbIds.BARCODE_ID));
+        final int starred = cursor.getInt(cursor.getColumnIndexOrThrow(LoyaltyCardDbIds.STAR_STATUS));
+        final long lastUsed = cursor.getLong(cursor.getColumnIndexOrThrow(LoyaltyCardDbIds.LAST_USED));
+        final int archiveStatus = cursor.getInt(cursor.getColumnIndexOrThrow(LoyaltyCardDbIds.ARCHIVE_STATUS));
+
+        int barcodeTypeColumn = cursor.getColumnIndexOrThrow(LoyaltyCardDbIds.BARCODE_TYPE);
+        int balanceTypeColumn = cursor.getColumnIndexOrThrow(LoyaltyCardDbIds.BALANCE_TYPE);
+        int headerColorColumn = cursor.getColumnIndexOrThrow(LoyaltyCardDbIds.HEADER_COLOR);
+
+        BarcodeFormat barcodeFormat = null;
+        Currency balanceType = null;
+        Date expiry = null;
+        Integer headerColor = null;
+
+        if (!cursor.isNull(barcodeTypeColumn)) {
+            try {
+                barcodeFormat = BarcodeFormat.valueOf(cursor.getString(barcodeTypeColumn));
+            } catch (final IllegalArgumentException e) {
+                LOG.error("Unknown barcode format {}", barcodeTypeColumn);
+            }
+        }
+
+        if (!cursor.isNull(balanceTypeColumn)) {
+            balanceType = Currency.getInstance(cursor.getString(balanceTypeColumn));
+        }
+
+        if (expiryLong > 0) {
+            expiry = new Date(expiryLong);
+        }
+
+        if (!cursor.isNull(headerColorColumn)) {
+            headerColor = cursor.getInt(headerColorColumn);
+        }
+
+        return new LoyaltyCard(
+                id,
+                name,
+                note,
+                expiry,
+                balance,
+                balanceType,
+                cardId,
+                barcodeId,
+                barcodeFormat,
+                headerColor,
+                starred != 0,
+                archiveStatus != 0,
+                lastUsed
+        );
+    }
+
+    /**
+     * Copied from Catima, protect.card_locker.DBHelper.LoyaltyCardDbGroups.
+     * Commit: 8607e1c2
+     */
+    public static class LoyaltyCardDbGroups {
+        public static final String TABLE = "groups";
+        public static final String ID = "_id";
+        public static final String ORDER = "orderId";
+    }
+
+    /**
+     * Copied from Catima, protect.card_locker.DBHelper.LoyaltyCardDbIds.
+     * Commit: 8607e1c2
+     */
+    public static class LoyaltyCardDbIds {
+        public static final String TABLE = "cards";
+        public static final String ID = "_id";
+        public static final String STORE = "store";
+        public static final String EXPIRY = "expiry";
+        public static final String BALANCE = "balance";
+        public static final String BALANCE_TYPE = "balancetype";
+        public static final String NOTE = "note";
+        public static final String HEADER_COLOR = "headercolor";
+        public static final String HEADER_TEXT_COLOR = "headertextcolor";
+        public static final String CARD_ID = "cardid";
+        public static final String BARCODE_ID = "barcodeid";
+        public static final String BARCODE_TYPE = "barcodetype";
+        public static final String STAR_STATUS = "starstatus";
+        public static final String LAST_USED = "lastused";
+        public static final String ZOOM_LEVEL = "zoomlevel";
+        public static final String ARCHIVE_STATUS = "archive";
+    }
+
+    /**
+     * Copied from Catima, protect.card_locker.DBHelper.LoyaltyCardDbIdsGroups.
+     * Commit: 8607e1c2
+     */
+    public static class LoyaltyCardDbIdsGroups {
+        public static final String TABLE = "cardsGroups";
+        public static final String cardID = "cardId";
+        public static final String groupID = "groupId";
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/capabilities/loyaltycards/CatimaManager.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/capabilities/loyaltycards/CatimaManager.java
new file mode 100644
index 0000000..88715f4
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/capabilities/loyaltycards/CatimaManager.java
@@ -0,0 +1,128 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.capabilities.loyaltycards;
+
+import static nodomain.freeyourgadget.gadgetbridge.activities.loyaltycards.LoyaltyCardsSettingsConst.LOYALTY_CARDS_CATIMA_PACKAGE;
+import static nodomain.freeyourgadget.gadgetbridge.activities.loyaltycards.LoyaltyCardsSettingsConst.LOYALTY_CARDS_SYNC_ARCHIVED;
+import static nodomain.freeyourgadget.gadgetbridge.activities.loyaltycards.LoyaltyCardsSettingsConst.LOYALTY_CARDS_SYNC_GROUPS;
+import static nodomain.freeyourgadget.gadgetbridge.activities.loyaltycards.LoyaltyCardsSettingsConst.LOYALTY_CARDS_SYNC_GROUPS_ONLY;
+import static nodomain.freeyourgadget.gadgetbridge.activities.loyaltycards.LoyaltyCardsSettingsConst.LOYALTY_CARDS_SYNC_STARRED;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.widget.Toast;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+public class CatimaManager {
+    private static final Logger LOG = LoggerFactory.getLogger(CatimaManager.class);
+
+    private final Context context;
+
+    public CatimaManager(final Context context) {
+        this.context = context;
+    }
+
+    public void sync(final GBDevice gbDevice) {
+        final List<CharSequence> installedCatimaPackages = findInstalledCatimaPackages();
+        if (installedCatimaPackages.isEmpty()) {
+            LOG.warn("Catima is not installed");
+            return;
+        }
+
+        final Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()));
+
+        final boolean syncGroupsOnly = prefs.getBoolean(LOYALTY_CARDS_SYNC_GROUPS_ONLY, false);
+        final Set<String> syncGroups = prefs.getStringSet(LOYALTY_CARDS_SYNC_GROUPS, Collections.emptySet());
+        final boolean syncArchived = prefs.getBoolean(LOYALTY_CARDS_SYNC_ARCHIVED, false);
+        final boolean syncStarred = prefs.getBoolean(LOYALTY_CARDS_SYNC_STARRED, false);
+
+        final String catimaPackage = prefs.getString(LOYALTY_CARDS_CATIMA_PACKAGE, installedCatimaPackages.get(0).toString());
+        final CatimaContentProvider catima = new CatimaContentProvider(context, catimaPackage);
+
+        if (!catima.isCatimaCompatible()) {
+            LOG.warn("Catima is not compatible");
+            return;
+        }
+
+        final List<LoyaltyCard> cards = catima.getCards();
+        final Map<String, List<Integer>> groupCards = catima.getGroupCards();
+
+        final Set<Integer> cardsInGroupsToSync = new HashSet<>();
+        if (syncGroupsOnly) {
+            for (final Map.Entry<String, List<Integer>> groupCardsEntry : groupCards.entrySet()) {
+                if (syncGroups.contains(groupCardsEntry.getKey())) {
+                    cardsInGroupsToSync.addAll(groupCardsEntry.getValue());
+                }
+            }
+        }
+
+        final ArrayList<LoyaltyCard> cardsToSync = new ArrayList<>();
+        for (final LoyaltyCard card : cards) {
+            if (syncGroupsOnly && !cardsInGroupsToSync.contains(card.getId())) {
+                continue;
+            }
+            if (!syncArchived && card.isArchived()) {
+                continue;
+            }
+            if (syncStarred && !card.isStarred()) {
+                continue;
+            }
+            cardsToSync.add(card);
+        }
+
+        Collections.sort(cardsToSync);
+
+        LOG.debug("Will sync cards: {}", cardsToSync);
+
+        GB.toast(context, context.getString(R.string.loyalty_cards_syncing, cardsToSync.size()), Toast.LENGTH_LONG, GB.INFO);
+
+        GBApplication.deviceService(gbDevice).onSetLoyaltyCards(cardsToSync);
+    }
+
+    public List<CharSequence> findInstalledCatimaPackages() {
+        final List<CharSequence> installedCatimaPackages = new ArrayList<>();
+        for (final String knownPackage : CatimaContentProvider.KNOWN_PACKAGES) {
+            if (isPackageInstalled(knownPackage)) {
+                installedCatimaPackages.add(knownPackage);
+            }
+        }
+        return installedCatimaPackages;
+    }
+
+    private boolean isPackageInstalled(final String packageName) {
+        try {
+            return context.getPackageManager().getApplicationInfo(packageName, 0).enabled;
+        } catch (final PackageManager.NameNotFoundException e) {
+            return false;
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/capabilities/loyaltycards/LoyaltyCard.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/capabilities/loyaltycards/LoyaltyCard.java
new file mode 100644
index 0000000..9b3d131
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/capabilities/loyaltycards/LoyaltyCard.java
@@ -0,0 +1,153 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.capabilities.loyaltycards;
+
+import androidx.annotation.Nullable;
+
+import org.apache.commons.lang3.builder.CompareToBuilder;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.util.Currency;
+import java.util.Date;
+import java.util.Locale;
+
+public class LoyaltyCard implements Serializable, Comparable<LoyaltyCard> {
+
+    private final int id;
+    private final String name;
+    private final String note;
+    private final Date expiry;
+    private final BigDecimal balance;
+    private final Currency balanceType;
+    private final String cardId;
+
+    @Nullable
+    private final String barcodeId;
+
+    @Nullable
+    private final BarcodeFormat barcodeFormat;
+
+    @Nullable
+    private final Integer color;
+
+    private final boolean starred;
+    private final boolean archived;
+    private final long lastUsed;
+
+    public LoyaltyCard(final int id,
+                       final String name,
+                       final String note,
+                       final Date expiry,
+                       final BigDecimal balance,
+                       final Currency balanceType,
+                       final String cardId,
+                       @Nullable final String barcodeId,
+                       @Nullable final BarcodeFormat barcodeFormat,
+                       @Nullable final Integer color,
+                       final boolean starred,
+                       final boolean archived,
+                       final long lastUsed) {
+        this.id = id;
+        this.name = name;
+        this.note = note;
+        this.expiry = expiry;
+        this.balance = balance;
+        this.balanceType = balanceType;
+        this.cardId = cardId;
+        this.barcodeId = barcodeId;
+        this.barcodeFormat = barcodeFormat;
+        this.color = color;
+        this.starred = starred;
+        this.archived = archived;
+        this.lastUsed = lastUsed;
+    }
+
+    public int getId() {
+        return id;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public String getNote() {
+        return note;
+    }
+
+    public Date getExpiry() {
+        return expiry;
+    }
+
+    public BigDecimal getBalance() {
+        return balance;
+    }
+
+    public Currency getBalanceType() {
+        return balanceType;
+    }
+
+    public String getCardId() {
+        return cardId;
+    }
+
+    @Nullable
+    public String getBarcodeId() {
+        return barcodeId;
+    }
+
+    @Nullable
+    public BarcodeFormat getBarcodeFormat() {
+        return barcodeFormat;
+    }
+
+    @Nullable
+    public Integer getColor() {
+        return color;
+    }
+
+    public boolean isStarred() {
+        return starred;
+    }
+
+    public boolean isArchived() {
+        return archived;
+    }
+
+    public long getLastUsed() {
+        return lastUsed;
+    }
+
+    @Override
+    public String toString() {
+        return String.format(
+                Locale.ROOT,
+                "LoyaltyCard{id=%s, name=%s, cardId=%s}",
+                id, name, cardId
+        );
+    }
+
+    @Override
+    public int compareTo(final LoyaltyCard o) {
+        return new CompareToBuilder()
+                .append(isStarred(), o.isStarred())
+                .append(isArchived(), o.isArchived())
+                .append(getName(), o.getName())
+                .append(getCardId(), o.getCardId())
+                .build();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/capabilities/password/PasswordCapabilityImpl.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/capabilities/password/PasswordCapabilityImpl.java
new file mode 100644
index 0000000..9e4aab9
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/capabilities/password/PasswordCapabilityImpl.java
@@ -0,0 +1,153 @@
+/*  Copyright (C) 2022-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.capabilities.password;
+
+import android.content.Context;
+import android.text.Editable;
+import android.text.InputFilter;
+import android.text.InputType;
+import android.text.Spanned;
+import android.text.TextWatcher;
+import android.widget.EditText;
+
+import androidx.annotation.NonNull;
+import androidx.preference.EditTextPreference;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsHandler;
+
+public class PasswordCapabilityImpl {
+    public static final String PREF_PASSWORD = "pref_password";
+    public static final String PREF_PASSWORD_ENABLED = "pref_password_enabled";
+
+    public enum Mode {
+        NONE,
+        NUMBERS_4_DIGITS_0_TO_9,
+        NUMBERS_4_DIGITS_1_TO_4,
+        NUMBERS_6,
+    }
+
+    public void registerPreferences(final Context context, final Mode mode, final DeviceSpecificSettingsHandler handler) {
+        if (mode == Mode.NONE) {
+            return;
+        }
+
+        final EditTextPreference password = handler.findPreference(PREF_PASSWORD);
+        if (password == null) {
+            return;
+        }
+
+        handler.addPreferenceHandlerFor(PREF_PASSWORD);
+        handler.addPreferenceHandlerFor(PREF_PASSWORD_ENABLED);
+
+        switch (mode) {
+            case NUMBERS_6:
+                password.setSummary(R.string.prefs_password_6_digits_0_to_9_summary);
+                break;
+            case NUMBERS_4_DIGITS_0_TO_9:
+                password.setSummary(R.string.prefs_password_4_digits_0_to_9_summary);
+                break;
+            case NUMBERS_4_DIGITS_1_TO_4:
+                password.setSummary(R.string.prefs_password_4_digits_1_to_4_summary);
+                break;
+            default:
+                break;
+        }
+
+        password.setOnBindEditTextListener(new EditTextPreference.OnBindEditTextListener() {
+            @Override
+            public void onBindEditText(@NonNull final EditText editText) {
+                final int expectedLength;
+                final List<InputFilter> inputFilters = new ArrayList<>();
+
+                switch (mode) {
+                    case NUMBERS_6:
+                        password.setSummary(R.string.prefs_password_6_digits_0_to_9_summary);
+                        editText.setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_PASSWORD);
+                        expectedLength = 6;
+                        break;
+                    case NUMBERS_4_DIGITS_0_TO_9:
+                        password.setSummary(R.string.prefs_password_4_digits_0_to_9_summary);
+                        editText.setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_PASSWORD);
+                        expectedLength = 4;
+                        break;
+                    case NUMBERS_4_DIGITS_1_TO_4:
+                        password.setSummary(R.string.prefs_password_4_digits_1_to_4_summary);
+                        editText.setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_PASSWORD);
+                        expectedLength = 4;
+                        inputFilters.add(new InputFilter_Digits_1to4());
+                        break;
+                    default:
+                        editText.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
+                        expectedLength = -1;
+                        break;
+                }
+
+                if (expectedLength != -1) {
+                    inputFilters.add(new InputFilter.LengthFilter(expectedLength));
+                }
+
+                editText.setSelection(editText.getText().length());
+                editText.setFilters(inputFilters.toArray(new InputFilter[0]));
+                editText.addTextChangedListener(new ExpectedLengthTextWatcher(editText, expectedLength));
+            }
+        });
+    }
+
+    private static class InputFilter_Digits_1to4 implements InputFilter {
+        @Override
+        public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
+            for (int i = start; i < end; i++) {
+                if (!Character.isDigit(source.charAt(i))) {
+                    return "";
+                }
+
+                if (source.charAt(i) < '1' || source.charAt(i) > '4') {
+                    return "";
+                }
+            }
+            return null;
+        }
+    }
+
+    private static class ExpectedLengthTextWatcher implements TextWatcher {
+        private final EditText editText;
+        private final int expectedLength;
+
+        private ExpectedLengthTextWatcher(final EditText editText, final int expectedLength) {
+            this.editText = editText;
+            this.expectedLength = expectedLength;
+        }
+
+        @Override
+        public void beforeTextChanged(final CharSequence s, final int start, final int count, final int after) {
+        }
+
+        @Override
+        public void onTextChanged(final CharSequence s, final int start, final int before, final int count) {
+        }
+
+        @Override
+        public void afterTextChanged(final Editable editable) {
+            editText.getRootView().findViewById(android.R.id.button1)
+                    .setEnabled(expectedLength == -1 || editable.length() == expectedLength);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/capabilities/widgets/WidgetLayout.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/capabilities/widgets/WidgetLayout.java
new file mode 100644
index 0000000..3df35e4
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/capabilities/widgets/WidgetLayout.java
@@ -0,0 +1,59 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.capabilities.widgets;
+
+import androidx.annotation.StringRes;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+
+public enum WidgetLayout {
+    // Square screen layouts, 2x2
+    TOP_1_BOT_2(R.string.widget_layout_top_1_bot_2, WidgetType.WIDE, WidgetType.SMALL, WidgetType.SMALL),
+    TOP_2_BOT_1(R.string.widget_layout_top_2_bot_1, WidgetType.SMALL, WidgetType.SMALL, WidgetType.SMALL),
+    TOP_2_BOT_2(R.string.widget_layout_top_2_bot_2, WidgetType.SMALL, WidgetType.SMALL, WidgetType.SMALL, WidgetType.SMALL),
+    TWO_BY_TWO_SINGLE(R.string.widget_layout_single, WidgetType.LARGE),
+
+    // Narrow screen layouts, 2x1
+    ONE_BY_TWO_SINGLE(R.string.widget_layout_single, WidgetType.TALL),
+    TWO(R.string.widget_layout_two, WidgetType.SMALL, WidgetType.SMALL),
+
+    // Portrait 2x3 screen layouts
+    TOP_2_BOT_2X2(R.string.widget_layout_top_2_bot_1, WidgetType.SMALL, WidgetType.SMALL, WidgetType.LARGE),
+    TOP_2X2_BOT_2(R.string.widget_layout_top_1_bot_2, WidgetType.LARGE, WidgetType.SMALL, WidgetType.SMALL),
+    TOP_1_BOT_2X2(R.string.widget_layout_top_wide_bot_large, WidgetType.WIDE, WidgetType.LARGE),
+    TOP_2X2_BOT_1(R.string.widget_layout_top_large_bot_wide, WidgetType.LARGE, WidgetType.WIDE),
+    TWO_BY_THREE_SINGLE(R.string.widget_layout_single, WidgetType.PORTRAIT_LARGE),
+    ;
+
+    @StringRes
+    private final int name;
+    private final WidgetType[] widgetTypes;
+
+    WidgetLayout(final int name, final WidgetType... widgetTypes) {
+        this.name = name;
+        this.widgetTypes = widgetTypes;
+    }
+
+    @StringRes
+    public int getName() {
+        return name;
+    }
+
+    public WidgetType[] getWidgetTypes() {
+        return widgetTypes;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/capabilities/widgets/WidgetManager.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/capabilities/widgets/WidgetManager.java
new file mode 100644
index 0000000..0d4c9c4
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/capabilities/widgets/WidgetManager.java
@@ -0,0 +1,70 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.capabilities.widgets;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+/**
+ * Provide an interface to manage a device's widgets in a device-independent manner.
+ */
+public interface WidgetManager {
+    /**
+     * The widget layouts supported by this device.
+     */
+    List<WidgetLayout> getSupportedWidgetLayouts();
+
+    /**
+     * The widget parts that can be used to build a widget screen, for a specific widget type.x
+     */
+    List<WidgetPart> getSupportedWidgetParts(WidgetType targetWidgetType);
+
+    /**
+     * The currently configured widget screens on this device.
+     */
+    List<WidgetScreen> getWidgetScreens();
+
+    GBDevice getDevice();
+
+    /**
+     * The minimum number of screens that must be present - deleting screens won't be allowed
+     * past this number.
+     */
+    int getMinScreens();
+
+    /**
+     * The maximum number of screens that can be created.
+     */
+    int getMaxScreens();
+
+    /**
+     * Saves a screen. If the screen is new, the ID will be null, otherwise it matches the
+     * screen that is being updated.
+     */
+    void saveScreen(WidgetScreen widgetScreen);
+
+    /**
+     * Deletes a screen.
+     */
+    void deleteScreen(WidgetScreen widgetScreen);
+
+    /**
+     * Send the currently configured screens to the device.
+     */
+    void sendToDevice();
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/capabilities/widgets/WidgetPart.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/capabilities/widgets/WidgetPart.java
new file mode 100644
index 0000000..c224672
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/capabilities/widgets/WidgetPart.java
@@ -0,0 +1,97 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.capabilities.widgets;
+
+import androidx.annotation.Nullable;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * A widget part is a single widget in a widget screen.
+ */
+public class WidgetPart implements Serializable {
+    // Null when not selected
+    @Nullable
+    private String id;
+
+    // The human-readable part name
+    private String name;
+
+    private WidgetType type;
+
+    // Null if it has no specific subtype
+    @Nullable
+    private WidgetPartSubtype subtype;
+
+    // The list of subtypes supported by this part, if any
+    private final List<WidgetPartSubtype> supportedSubtypes = new ArrayList<>();
+
+    public WidgetPart(@Nullable final String id, final String name, final WidgetType type) {
+        this.id = id;
+        this.name = name;
+        this.type = type;
+    }
+
+    @Nullable
+    public String getId() {
+        return id;
+    }
+
+    public void setId(@Nullable final String id) {
+        this.id = id;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public String getFullName() {
+        if (subtype != null) {
+            return String.format(Locale.ROOT, "%s (%s)", name, subtype.getName());
+        }
+
+        return name;
+    }
+
+    public void setName(final String name) {
+        this.name = name;
+    }
+
+    public WidgetType getType() {
+        return type;
+    }
+
+    public void setType(final WidgetType type) {
+        this.type = type;
+    }
+
+    @Nullable
+    public WidgetPartSubtype getSubtype() {
+        return subtype;
+    }
+
+    public void setSubtype(@Nullable final WidgetPartSubtype subtype) {
+        this.subtype = subtype;
+    }
+
+    public List<WidgetPartSubtype> getSupportedSubtypes() {
+        return supportedSubtypes;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/capabilities/widgets/WidgetPartSubtype.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/capabilities/widgets/WidgetPartSubtype.java
new file mode 100644
index 0000000..b4be3ff
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/capabilities/widgets/WidgetPartSubtype.java
@@ -0,0 +1,48 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.capabilities.widgets;
+
+import java.io.Serializable;
+
+/**
+ * A widget part can have an optional subtype (eg. a workout type).
+ */
+public class WidgetPartSubtype implements Serializable {
+    private String id;
+    private String name;
+
+    public WidgetPartSubtype(final String id, final String name) {
+        this.id = id;
+        this.name = name;
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(final String id) {
+        this.id = id;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(final String name) {
+        this.name = name;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/capabilities/widgets/WidgetScreen.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/capabilities/widgets/WidgetScreen.java
new file mode 100644
index 0000000..25cd567
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/capabilities/widgets/WidgetScreen.java
@@ -0,0 +1,65 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.capabilities.widgets;
+
+import androidx.annotation.Nullable;
+
+import java.io.Serializable;
+import java.util.List;
+
+public class WidgetScreen implements Serializable {
+    public static final String EXTRA_WIDGET_SCREEN = "widget_screen";
+
+    // Null when creating a new screen
+    @Nullable
+    private String id;
+    private WidgetLayout layout;
+
+    // The list of parts must match what the WidgetLayout expects
+    private List<WidgetPart> parts;
+
+    public WidgetScreen(@Nullable final String id, final WidgetLayout layout, final List<WidgetPart> parts) {
+        this.id = id;
+        this.layout = layout;
+        this.parts = parts;
+    }
+
+    @Nullable
+    public String getId() {
+        return id;
+    }
+
+    public void setId(@Nullable final String id) {
+        this.id = id;
+    }
+
+    public WidgetLayout getLayout() {
+        return layout;
+    }
+
+    public void setLayout(final WidgetLayout layout) {
+        this.layout = layout;
+    }
+
+    public List<WidgetPart> getParts() {
+        return parts;
+    }
+
+    public void setParts(final List<WidgetPart> parts) {
+        this.parts = parts;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/capabilities/widgets/WidgetType.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/capabilities/widgets/WidgetType.java
new file mode 100644
index 0000000..01e3e95
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/capabilities/widgets/WidgetType.java
@@ -0,0 +1,26 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.capabilities.widgets;
+
+public enum WidgetType {
+    SMALL, // 1x1
+    TALL,  // 1x2
+    WIDE,  // 2x1
+    LARGE, // 2x2
+    PORTRAIT_LARGE, // 2x3
+    ;
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/contentprovider/PebbleContentProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/contentprovider/PebbleContentProvider.java
new file mode 100644
index 0000000..4e97565
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/contentprovider/PebbleContentProvider.java
@@ -0,0 +1,117 @@
+/*  Copyright (C) 2016-2024 Andreas Shimokawa, Carsten Pfeiffer, Daniele
+    Gobbetti
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.contentprovider;
+
+import android.content.BroadcastReceiver;
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.net.Uri;
+
+import androidx.annotation.NonNull;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.util.preferences.DevicePrefs;
+
+public class PebbleContentProvider extends ContentProvider {
+
+    public static final int COLUMN_CONNECTED = 0;
+    public static final int COLUMN_APPMSG_SUPPORT = 1;
+    public static final int COLUMN_DATALOGGING_SUPPORT = 2;
+    public static final int COLUMN_VERSION_MAJOR = 3;
+    public static final int COLUMN_VERSION_MINOR = 4;
+    public static final int COLUMN_VERSION_POINT = 5;
+    public static final int COLUMN_VERSION_TAG = 6;
+
+    // this is only needed for the MatrixCursor constructor
+    public static final String[] columnNames = new String[]{"0", "1", "2", "3", "4", "5", "6"};
+
+    static final String PROVIDER_NAME = "com.getpebble.android.provider";
+    static final String URL = "content://" + PROVIDER_NAME + "/state";
+    static final Uri CONTENT_URI = Uri.parse(URL);
+
+    private GBDevice mGBDevice = null;
+
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            if (action.equals(GBDevice.ACTION_DEVICE_CHANGED)) {
+                mGBDevice = intent.getParcelableExtra(GBDevice.EXTRA_DEVICE);
+            }
+        }
+    };
+
+    @Override
+    public boolean onCreate() {
+
+        LocalBroadcastManager.getInstance(this.getContext()).registerReceiver(mReceiver, new IntentFilter(GBDevice.ACTION_DEVICE_CHANGED));
+
+        return true;
+    }
+
+    @Override
+    public Cursor query(@NonNull Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
+        if (uri.equals(CONTENT_URI)) {
+            MatrixCursor mc = new MatrixCursor(columnNames);
+            int connected = 0;
+            int pebbleKit = 0;
+            String fwString = "unknown";
+            if (mGBDevice != null && mGBDevice.getType() == DeviceType.PEBBLE && mGBDevice.isInitialized()) {
+                final DevicePrefs deviceSpecificSharedPrefsrefs = GBApplication.getDevicePrefs(mGBDevice);
+                if (deviceSpecificSharedPrefsrefs.getBoolean("third_party_apps_set_settings", false)) {
+                    pebbleKit = 1;
+                }
+                connected = 1;
+                fwString = mGBDevice.getFirmwareVersion();
+            }
+            mc.addRow(new Object[]{connected, pebbleKit, pebbleKit, 3, 8, 2, fwString});
+
+            return mc;
+        } else {
+            return null;
+        }
+    }
+
+    @Override
+    public String getType(@NonNull Uri uri) {
+        return null;
+    }
+
+    @Override
+    public Uri insert(@NonNull Uri uri, ContentValues values) {
+        return null;
+    }
+
+    @Override
+    public int delete(@NonNull Uri uri, String selection, String[] selectionArgs) {
+        return 0;
+    }
+
+    @Override
+    public int update(@NonNull Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+        return 0;
+    }
+}
\ No newline at end of file
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 0000000..f9afb1c
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/AppSpecificNotificationSettingsRepository.java
@@ -0,0 +1,85 @@
+/*  Copyright (C) 2023-2024 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 <https://www.gnu.org/licenses/>. */
+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<AppSpecificNotificationSetting> 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<AppSpecificNotificationSetting> 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/database/DBAccess.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/DBAccess.java
new file mode 100644
index 0000000..aa87711
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/DBAccess.java
@@ -0,0 +1,71 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, Carsten Pfeiffer, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.database;
+
+import android.content.Context;
+import android.os.AsyncTask;
+import android.widget.Toast;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public abstract class DBAccess extends AsyncTask {
+    private static final Logger LOG = LoggerFactory.getLogger(DBAccess.class);
+
+    private final String mTask;
+    private final Context mContext;
+    private Exception mError;
+
+    public DBAccess(String task, Context context) {
+        mTask = task;
+        mContext = context;
+    }
+
+    public Context getContext() {
+        return mContext;
+    }
+
+    protected abstract void doInBackground(DBHandler handler);
+
+    @Override
+    protected Object doInBackground(Object[] params) {
+        try (DBHandler db = GBApplication.acquireDB()) {
+            doInBackground(db);
+        } catch (Exception e) {
+            LOG.error("Error during DBAccess for {}", mTask, e);
+            mError = e;
+        }
+        return null;
+    }
+
+    @Override
+    protected void onPostExecute(Object o) {
+        if (mError != null) {
+            displayError(mError);
+        }
+    }
+
+
+
+    protected void displayError(Throwable error) {
+        GB.toast(getContext(), getContext().getString(R.string.dbaccess_error_executing, error.getMessage()), Toast.LENGTH_LONG, GB.ERROR, error);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/DBHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/DBHandler.java
new file mode 100644
index 0000000..7e18d1f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/DBHandler.java
@@ -0,0 +1,53 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, Carsten Pfeiffer, Daniele
+    Gobbetti, JohnnySun
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.database;
+
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoMaster;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+
+/**
+ * Provides low-level access to the database.
+ */
+public interface DBHandler extends AutoCloseable {
+    /**
+     * Closes the database.
+     */
+    void closeDb();
+
+    /**
+     * Opens the database. Note that this is only possible after an explicit
+     * #closeDb(). Initially the db is implicitly open.
+     */
+    void openDb();
+
+    SQLiteOpenHelper getHelper();
+
+    /**
+     * Releases the DB handler. No DB access will be possible before
+     * #openDb() will be called.
+     */
+    void close() throws Exception;
+
+    SQLiteDatabase getDatabase();
+
+    DaoMaster getDaoMaster();
+    DaoSession getDaoSession();
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/DBHelper.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/DBHelper.java
new file mode 100644
index 0000000..8333456
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/DBHelper.java
@@ -0,0 +1,777 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, Carsten Pfeiffer, Damien
+    Gaignon, Daniel Dakhno, Daniele Gobbetti, Felix Konstantin Maurer, JohnnySun,
+    Jos茅 Rebelo, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.database;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+import java.util.Objects;
+
+import de.greenrobot.dao.Property;
+import de.greenrobot.dao.query.Query;
+import de.greenrobot.dao.query.QueryBuilder;
+import de.greenrobot.dao.query.WhereCondition;
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.entities.ActivityDescription;
+import nodomain.freeyourgadget.gadgetbridge.entities.ActivityDescriptionDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.Alarm;
+import nodomain.freeyourgadget.gadgetbridge.entities.AlarmDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.Contact;
+import nodomain.freeyourgadget.gadgetbridge.entities.ContactDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.entities.DeviceAttributes;
+import nodomain.freeyourgadget.gadgetbridge.entities.DeviceAttributesDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.DeviceDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.Reminder;
+import nodomain.freeyourgadget.gadgetbridge.entities.ReminderDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.Tag;
+import nodomain.freeyourgadget.gadgetbridge.entities.TagDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.User;
+import nodomain.freeyourgadget.gadgetbridge.entities.UserAttributes;
+import nodomain.freeyourgadget.gadgetbridge.entities.UserDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.WorldClock;
+import nodomain.freeyourgadget.gadgetbridge.entities.WorldClockDao;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
+import nodomain.freeyourgadget.gadgetbridge.model.ValidByDate;
+import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
+import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+import nodomain.freeyourgadget.gadgetbridge.util.GBPrefs;
+
+
+/**
+ * Provides utility access to some common entities, so you won't need to use
+ * their DAO classes.
+ * <p/>
+ * Maybe this code should actually be in the DAO classes themselves, but then
+ * these should be under revision control instead of 100% generated at build time.
+ */
+public class DBHelper {
+    private static final Logger LOG = LoggerFactory.getLogger(DBHelper.class);
+
+    private final Context context;
+
+    public DBHelper(Context context) {
+        this.context = context;
+    }
+
+    /**
+     * Closes the database and returns its name.
+     * Important: after calling this, you have to DBHandler#openDb() it again
+     * to get it back to work.
+     *
+     * @param dbHandler
+     * @return
+     * @throws IllegalStateException
+     */
+    private String getClosedDBPath(DBHandler dbHandler) throws IllegalStateException {
+        SQLiteDatabase db = dbHandler.getDatabase();
+        String path = db.getPath();
+        dbHandler.closeDb();
+        if (db.isOpen()) { // reference counted, so may still be open
+            throw new IllegalStateException("Database must be closed");
+        }
+        return path;
+    }
+
+    public File exportDB(DBHandler dbHandler, File toDir) throws IllegalStateException, IOException {
+        String dbPath = getClosedDBPath(dbHandler);
+        try {
+            File sourceFile = new File(dbPath);
+            File destFile = new File(toDir, sourceFile.getName());
+            if (destFile.exists()) {
+                File backup = new File(toDir, destFile.getName() + "_" + getDate());
+                destFile.renameTo(backup);
+            } else if (!toDir.exists()) {
+                if (!toDir.mkdirs()) {
+                    throw new IOException("Unable to create directory: " + toDir.getAbsolutePath());
+                }
+            }
+
+            FileUtils.copyFile(sourceFile, destFile);
+            return destFile;
+        } finally {
+            dbHandler.openDb();
+        }
+    }
+
+    public void exportDB(DBHandler dbHandler, OutputStream dest) throws IOException {
+        String dbPath = getClosedDBPath(dbHandler);
+        try {
+            File source = new File(dbPath);
+            FileUtils.copyFileToStream(source, dest);
+        } finally {
+            dbHandler.openDb();
+        }
+    }
+
+    private String getDate() {
+        return new SimpleDateFormat("yyyyMMdd-HHmmss", Locale.US).format(new Date());
+    }
+
+    public void importDB(DBHandler dbHandler, File fromFile) throws IllegalStateException, IOException {
+        importDB(dbHandler, new FileInputStream(fromFile));
+    }
+
+    public void importDB(DBHandler dbHandler, InputStream inputStream) throws IllegalStateException, IOException {
+        String dbPath = getClosedDBPath(dbHandler);
+        try {
+            File toFile = new File(dbPath);
+            FileUtils.copyStreamToFile(inputStream, toFile);
+        } finally {
+            dbHandler.openDb();
+        }
+    }
+
+    public void validateDB(SQLiteOpenHelper dbHandler) throws IOException {
+        try (SQLiteDatabase db = dbHandler.getReadableDatabase()) {
+            if (!db.isDatabaseIntegrityOk()) {
+                throw new IOException("Database integrity is not OK");
+            }
+        }
+    }
+
+    public static void dropTable(String tableName, SQLiteDatabase db) {
+        String statement = "DROP TABLE IF EXISTS '" + tableName + "'";
+        db.execSQL(statement);
+    }
+
+    public boolean existsDB(String dbName) {
+        File path = context.getDatabasePath(dbName);
+        return path != null && path.exists();
+    }
+
+    public static boolean existsColumn(String tableName, String columnName, SQLiteDatabase db) {
+        try (Cursor res = db.rawQuery("PRAGMA table_info('" + tableName + "')", null)) {
+            int index = res.getColumnIndex("name");
+            if (index < 1) {
+                return false; // something's really wrong
+            }
+            while (res.moveToNext()) {
+                String cn = res.getString(index);
+                if (columnName.equals(cn)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Looks up the user entity in the database. If a user exists already, it will
+     * be updated with the current preferences values. If no user exists yet, it will
+     * be created in the database.
+     *
+     * Note: so far there is only ever a single user; there is no multi-user support yet
+     * @param session
+     * @return the User entity
+     */
+    @NonNull
+    public static User getUser(DaoSession session) {
+        ActivityUser prefsUser = new ActivityUser();
+        UserDao userDao = session.getUserDao();
+        User user;
+        List<User> users = userDao.loadAll();
+        if (users.isEmpty()) {
+            user = createUser(prefsUser, session);
+        } else {
+            user = users.get(0); // TODO: multiple users support?
+            ensureUserUpToDate(user, prefsUser, session);
+        }
+        ensureUserAttributes(user, prefsUser, session);
+
+        return user;
+    }
+
+    @NonNull
+    public static UserAttributes getUserAttributes(User user) {
+        List<UserAttributes> list = user.getUserAttributesList();
+        if (list.isEmpty()) {
+            throw new IllegalStateException("user has no attributes");
+        }
+        return list.get(0);
+    }
+
+    @NonNull
+    private static User createUser(ActivityUser prefsUser, DaoSession session) {
+        User user = new User();
+        ensureUserUpToDate(user, prefsUser, session);
+
+        return user;
+    }
+
+    private static void ensureUserUpToDate(User user, ActivityUser prefsUser, DaoSession session) {
+        if (!isUserUpToDate(user, prefsUser)) {
+            user.setName(prefsUser.getName());
+            user.setBirthday(prefsUser.getUserBirthday());
+            user.setGender(prefsUser.getGender());
+
+            if (user.getId() == null) {
+                session.getUserDao().insert(user);
+            } else {
+                session.getUserDao().update(user);
+            }
+        }
+    }
+
+    public static boolean isUserUpToDate(User user, ActivityUser prefsUser) {
+        if (!Objects.equals(user.getName(), prefsUser.getName())) {
+            return false;
+        }
+        if (!Objects.equals(user.getBirthday(), prefsUser.getUserBirthday())) {
+            return false;
+        }
+        if (user.getGender() != prefsUser.getGender()) {
+            return false;
+        }
+
+        return true;
+    }
+
+    private static void ensureUserAttributes(User user, ActivityUser prefsUser, DaoSession session) {
+        List<UserAttributes> userAttributes = user.getUserAttributesList();
+        UserAttributes[] previousUserAttributes = new UserAttributes[1];
+        if (hasUpToDateUserAttributes(userAttributes, prefsUser, previousUserAttributes)) {
+            return;
+        }
+
+        Calendar now = DateTimeUtils.getCalendarUTC();
+        invalidateUserAttributes(previousUserAttributes[0], now, session);
+
+        UserAttributes attributes = new UserAttributes();
+        attributes.setValidFromUTC(now.getTime());
+        attributes.setHeightCM(prefsUser.getHeightCm());
+        attributes.setWeightKG(prefsUser.getWeightKg());
+        attributes.setSleepGoalHPD(prefsUser.getSleepDurationGoal());
+        attributes.setStepsGoalSPD(prefsUser.getStepsGoal());
+        attributes.setUserId(user.getId());
+        session.getUserAttributesDao().insert(attributes);
+
+// sort order is important, so we re-fetch from the db
+//        userAttributes.add(attributes);
+        user.resetUserAttributesList();
+    }
+
+    private static void invalidateUserAttributes(UserAttributes userAttributes, Calendar now, DaoSession session) {
+        if (userAttributes != null) {
+            Calendar invalid = (Calendar) now.clone();
+            invalid.add(Calendar.MINUTE, -1);
+            userAttributes.setValidToUTC(invalid.getTime());
+            session.getUserAttributesDao().update(userAttributes);
+        }
+    }
+
+    private static boolean hasUpToDateUserAttributes(List<UserAttributes> userAttributes, ActivityUser prefsUser, UserAttributes[] outPreviousUserAttributes) {
+        for (UserAttributes attr : userAttributes) {
+            if (!isValidNow(attr)) {
+                continue;
+            }
+            if (isEqual(attr, prefsUser)) {
+                return true;
+            } else {
+                outPreviousUserAttributes[0] = attr;
+            }
+        }
+        return false;
+    }
+
+    // TODO: move this into db queries?
+    private static boolean isValidNow(ValidByDate element) {
+        Calendar cal = DateTimeUtils.getCalendarUTC();
+        Date nowUTC = cal.getTime();
+        return isValid(element, nowUTC);
+    }
+
+    private static boolean isValid(ValidByDate element, Date nowUTC) {
+        Date validFromUTC = element.getValidFromUTC();
+        Date validToUTC = element.getValidToUTC();
+        if (nowUTC.before(validFromUTC)) {
+            return false;
+        }
+        if (validToUTC != null && nowUTC.after(validToUTC)) {
+            return false;
+        }
+        return true;
+    }
+
+    private static boolean isEqual(UserAttributes attr, ActivityUser prefsUser) {
+        if (prefsUser.getHeightCm() != attr.getHeightCM()) {
+            LOG.info("user height changed to " + prefsUser.getHeightCm() + " from " + attr.getHeightCM());
+            return false;
+        }
+        if (prefsUser.getWeightKg() != attr.getWeightKG()) {
+            LOG.info("user changed to " + prefsUser.getWeightKg() + " from " + attr.getWeightKG());
+            return false;
+        }
+        if (!Integer.valueOf(prefsUser.getSleepDurationGoal()).equals(attr.getSleepGoalHPD())) {
+            LOG.info("user sleep goal changed to " + prefsUser.getSleepDurationGoal() + " from " + attr.getSleepGoalHPD());
+            return false;
+        }
+        if (!Integer.valueOf(prefsUser.getStepsGoal()).equals(attr.getStepsGoalSPD())) {
+            LOG.info("user steps goal changed to " + prefsUser.getStepsGoal() + " from " + attr.getStepsGoalSPD());
+            return false;
+        }
+        return true;
+    }
+
+    private static boolean isEqual(DeviceAttributes attr, GBDevice gbDevice) {
+        if (!Objects.equals(attr.getFirmwareVersion1(), gbDevice.getFirmwareVersion())) {
+            return false;
+        }
+        if (!Objects.equals(attr.getFirmwareVersion2(), gbDevice.getFirmwareVersion2())) {
+            return false;
+        }
+        if (!Objects.equals(attr.getVolatileIdentifier(), gbDevice.getVolatileAddress())) {
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Finds the corresponding Device entity for the given GBDevice.
+     * @param gbDevice
+     * @param session
+     * @return the corresponding Device entity, or null if none
+     */
+    @Nullable
+    public static Device findDevice(GBDevice gbDevice, DaoSession session) {
+        DeviceDao deviceDao = session.getDeviceDao();
+        Query<Device> query = deviceDao.queryBuilder().where(DeviceDao.Properties.Identifier.eq(gbDevice.getAddress())).build();
+        List<Device> devices = query.list();
+        if (devices.size() > 0) {
+            return devices.get(0);
+        }
+        return null;
+    }
+
+    public static void updateDeviceMacAddress(final DaoSession session, final String oldAddress, final String newAddress) {
+        final DeviceDao deviceDao = session.getDeviceDao();
+        final Query<Device> query = deviceDao.queryBuilder().where(DeviceDao.Properties.Identifier.eq(oldAddress)).build();
+        final List<Device> devices = query.list();
+        if (devices.isEmpty()) {
+            LOG.warn("Failed to find device with address {}", oldAddress);
+            return;
+        }
+
+        final Device device = devices.get(0);
+        device.setIdentifier(newAddress);
+        session.getDeviceDao().update(device);
+    }
+
+    /**
+     * Returns all active (that is, not old, archived ones) from the database.
+     * (currently the active handling is not available)
+     * @param daoSession
+     */
+    public static List<Device> getActiveDevices(DaoSession daoSession) {
+        return daoSession.getDeviceDao().loadAll();
+    }
+
+    /**
+     * Looks up in the database the Device entity corresponding to the GBDevice. If a device
+     * exists already, it will be updated with the current preferences values. If no device exists
+     * yet, it will be created in the database.
+     *
+     * @param session
+     * @return the device entity corresponding to the given GBDevice
+     */
+    public static Device getDevice(GBDevice gbDevice, DaoSession session) {
+        Device device = findDevice(gbDevice, session);
+        if (device == null) {
+            device = createDevice(gbDevice, session);
+        } else {
+            ensureDeviceUpToDate(device, gbDevice, session);
+        }
+        if (gbDevice.isInitialized()) {
+            ensureDeviceAttributes(device, gbDevice, session);
+        }
+
+        return device;
+    }
+
+    @NonNull
+    public static DeviceAttributes getDeviceAttributes(Device device) {
+        List<DeviceAttributes> list = device.getDeviceAttributesList();
+        if (list.isEmpty()) {
+            throw new IllegalStateException("device has no attributes");
+        }
+        return list.get(0);
+    }
+
+    private static void ensureDeviceUpToDate(Device device, GBDevice gbDevice, DaoSession session) {
+        if (!isDeviceUpToDate(device, gbDevice)) {
+            device.setIdentifier(gbDevice.getAddress());
+            device.setName(gbDevice.getName());
+            device.setAlias(gbDevice.getAlias());
+            DeviceCoordinator coordinator = gbDevice.getDeviceCoordinator();
+            device.setManufacturer(coordinator.getManufacturer());
+            device.setTypeName(gbDevice.getType().name());
+            device.setModel(gbDevice.getModel());
+
+            if (device.getId() == null) {
+                session.getDeviceDao().insert(device);
+            } else {
+                session.getDeviceDao().update(device);
+            }
+        }
+    }
+
+    private static boolean isDeviceUpToDate(Device device, GBDevice gbDevice) {
+        if (!Objects.equals(device.getIdentifier(), gbDevice.getAddress())) {
+            return false;
+        }
+        if (!Objects.equals(device.getName(), gbDevice.getName())) {
+            return false;
+        }
+        if (!Objects.equals(device.getAlias(), gbDevice.getAlias())) {
+            return false;
+        }
+        DeviceCoordinator coordinator = gbDevice.getDeviceCoordinator();
+        if (!Objects.equals(device.getManufacturer(), coordinator.getManufacturer())) {
+            return false;
+        }
+        if(!gbDevice.getType().name().equals(device.getTypeName())){
+            return false;
+        }
+        if (!Objects.equals(device.getModel(), gbDevice.getModel())) {
+            return false;
+        }
+        return true;
+    }
+
+    private static Device createDevice(GBDevice gbDevice, DaoSession session) {
+        Device device = new Device();
+        ensureDeviceUpToDate(device, gbDevice, session);
+
+        return device;
+    }
+
+    private static void ensureDeviceAttributes(Device device, GBDevice gbDevice, DaoSession session) {
+        List<DeviceAttributes> deviceAttributes = device.getDeviceAttributesList();
+        DeviceAttributes[] previousDeviceAttributes = new DeviceAttributes[1];
+        if (hasUpToDateDeviceAttributes(deviceAttributes, gbDevice, previousDeviceAttributes)) {
+            return;
+        }
+
+        Calendar now = DateTimeUtils.getCalendarUTC();
+        invalidateDeviceAttributes(previousDeviceAttributes[0], now, session);
+
+        DeviceAttributes attributes = new DeviceAttributes();
+        attributes.setDeviceId(device.getId());
+        attributes.setValidFromUTC(now.getTime());
+        attributes.setFirmwareVersion1(gbDevice.getFirmwareVersion());
+        attributes.setFirmwareVersion2(gbDevice.getFirmwareVersion2());
+        attributes.setVolatileIdentifier(gbDevice.getVolatileAddress());
+        DeviceAttributesDao attributesDao = session.getDeviceAttributesDao();
+        attributesDao.insert(attributes);
+
+// sort order is important, so we re-fetch from the db
+//        deviceAttributes.add(attributes);
+        device.resetDeviceAttributesList();
+    }
+
+    private static void invalidateDeviceAttributes(DeviceAttributes deviceAttributes, Calendar now, DaoSession session) {
+        if (deviceAttributes != null) {
+            Calendar invalid = (Calendar) now.clone();
+            invalid.add(Calendar.MINUTE, -1);
+            deviceAttributes.setValidToUTC(invalid.getTime());
+            session.getDeviceAttributesDao().update(deviceAttributes);
+        }
+    }
+
+    private static boolean hasUpToDateDeviceAttributes(List<DeviceAttributes> deviceAttributes, GBDevice gbDevice, DeviceAttributes[] outPreviousAttributes) {
+        for (DeviceAttributes attr : deviceAttributes) {
+            if (!isValidNow(attr)) {
+                continue;
+            }
+            if (isEqual(attr, gbDevice)) {
+                return true;
+            } else {
+                outPreviousAttributes[0] = attr;
+            }
+        }
+        return false;
+    }
+
+    @NonNull
+    public static List<ActivityDescription> findActivityDecriptions(@NonNull User user, int tsFrom, int tsTo, @NonNull DaoSession session) {
+        Property tsFromProperty = ActivityDescriptionDao.Properties.TimestampFrom;
+        Property tsToProperty = ActivityDescriptionDao.Properties.TimestampTo;
+        Property userIdProperty = ActivityDescriptionDao.Properties.UserId;
+        QueryBuilder<ActivityDescription> qb = session.getActivityDescriptionDao().queryBuilder();
+        qb.where(userIdProperty.eq(user.getId()), isAtLeastPartiallyInRange(qb, tsFromProperty, tsToProperty, tsFrom, tsTo));
+        List<ActivityDescription> descriptions = qb.build().list();
+        return descriptions;
+    }
+
+    /**
+     * Returns a condition that matches when the range of the entity (tsFromProperty..tsToProperty)
+     * is completely or partially inside the range tsFrom..tsTo.
+     * @param qb the query builder to use
+     * @param tsFromProperty the property indicating the start of the entity's range
+     * @param tsToProperty the property indicating the end of the entity's range
+     * @param tsFrom the timestamp indicating the start of the range to match
+     * @param tsTo the timestamp indicating the end of the range to match
+     * @param <T> the query builder's type parameter
+     * @return the range WhereCondition
+     */
+    private static <T> WhereCondition isAtLeastPartiallyInRange(QueryBuilder<T> qb, Property tsFromProperty, Property tsToProperty, int tsFrom, int tsTo) {
+        return qb.and(tsFromProperty.lt(tsTo), tsToProperty.gt(tsFrom));
+    }
+
+    @NonNull
+    public static ActivityDescription createActivityDescription(@NonNull User user, int tsFrom, int tsTo, @NonNull DaoSession session) {
+        ActivityDescription desc = new ActivityDescription();
+        desc.setUser(user);
+        desc.setTimestampFrom(tsFrom);
+        desc.setTimestampTo(tsTo);
+        session.getActivityDescriptionDao().insertOrReplace(desc);
+
+        return desc;
+    }
+
+    @NonNull
+    public static Tag getTag(@NonNull User user, @NonNull String name, @NonNull DaoSession session) {
+        TagDao tagDao = session.getTagDao();
+        QueryBuilder<Tag> qb = tagDao.queryBuilder();
+        Query<Tag> query = qb.where(TagDao.Properties.UserId.eq(user.getId()), TagDao.Properties.Name.eq(name)).build();
+        List<Tag> tags = query.list();
+        if (tags.size() > 0) {
+            return tags.get(0);
+        }
+        return createTag(user, name, null, session);
+    }
+
+    static Tag createTag(@NonNull User user, @NonNull String name, @Nullable String description, @NonNull DaoSession session) {
+        Tag tag = new Tag();
+        tag.setUserId(user.getId());
+        tag.setName(name);
+        tag.setDescription(description);
+        session.getTagDao().insertOrReplace(tag);
+        return tag;
+    }
+
+    /**
+     * Returns all user-configurable alarms for the given user and device. The list is sorted by
+     * {@link Alarm#position}. Calendar events that may also be modeled as alarms are not stored
+     * in the database and hence not returned by this method.
+     * @param gbDevice the device for which the alarms shall be loaded
+     * @return the list of alarms for the given device
+     */
+    @NonNull
+    public static List<Alarm> getAlarms(@NonNull GBDevice gbDevice) {
+        DeviceCoordinator coordinator = gbDevice.getDeviceCoordinator();
+        GBPrefs prefs = new GBPrefs(new Prefs(GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress())));
+
+        int reservedSlots = prefs.getInt(DeviceSettingsPreferenceConst.PREF_RESERVER_ALARMS_CALENDAR, 0);
+        int alarmSlots = coordinator.getAlarmSlotCount(gbDevice);
+
+        try (DBHandler db = GBApplication.acquireDB()) {
+            DaoSession daoSession = db.getDaoSession();
+            User user = getUser(daoSession);
+            Device dbDevice = DBHelper.findDevice(gbDevice, daoSession);
+            if (dbDevice != null) {
+                AlarmDao alarmDao = daoSession.getAlarmDao();
+                Long deviceId = dbDevice.getId();
+                QueryBuilder<Alarm> qb = alarmDao.queryBuilder();
+                qb.where(
+                        AlarmDao.Properties.UserId.eq(user.getId()),
+                        AlarmDao.Properties.DeviceId.eq(deviceId)).orderAsc(AlarmDao.Properties.Position).limit(alarmSlots - reservedSlots);
+                return qb.build().list();
+            }
+        } catch (Exception e) {
+            LOG.warn("Error reading alarms from db", e);
+        }
+        return Collections.emptyList();
+    }
+
+    public static void store(Alarm alarm) {
+        try (DBHandler db = GBApplication.acquireDB()) {
+            DaoSession daoSession = db.getDaoSession();
+            daoSession.insertOrReplace(alarm);
+        } catch (Exception e) {
+             LOG.error("Error acquiring database", e);
+        }
+    }
+
+    /**
+     * Returns all user-configurable reminders for the given user and device. The list is sorted by
+     * {@link Reminder#getDate}. Calendar events that may also be modeled as reminders are not stored
+     * in the database and hence not returned by this method.
+     * @param gbDevice the device for which the alarms shall be loaded
+     * @return the list of reminders for the given device
+     */
+    @NonNull
+    public static List<Reminder> getReminders(@NonNull GBDevice gbDevice) {
+        final DeviceCoordinator coordinator = gbDevice.getDeviceCoordinator();
+
+        final int reservedSlots = GBApplication.getDevicePrefs(gbDevice).getReservedReminderCalendarSlots();
+        final int reminderSlots = coordinator.getReminderSlotCount(gbDevice);
+
+        try (DBHandler db = GBApplication.acquireDB()) {
+            final DaoSession daoSession = db.getDaoSession();
+            final User user = getUser(daoSession);
+            final Device dbDevice = DBHelper.findDevice(gbDevice, daoSession);
+            if (dbDevice != null) {
+                final ReminderDao reminderDao = daoSession.getReminderDao();
+                final Long deviceId = dbDevice.getId();
+                final QueryBuilder<Reminder> qb = reminderDao.queryBuilder();
+                qb.where(
+                        ReminderDao.Properties.UserId.eq(user.getId()),
+                        ReminderDao.Properties.DeviceId.eq(deviceId)).orderAsc(ReminderDao.Properties.Date).limit(reminderSlots - reservedSlots);
+                return qb.build().list();
+            }
+        } catch (final Exception e) {
+            LOG.error("Error reading reminders from db", e);
+        }
+
+        return Collections.emptyList();
+    }
+
+    @NonNull
+    public static List<WorldClock> getWorldClocks(@NonNull GBDevice gbDevice) {
+        try (DBHandler db = GBApplication.acquireDB()) {
+            final DaoSession daoSession = db.getDaoSession();
+            final User user = getUser(daoSession);
+            final Device dbDevice = DBHelper.findDevice(gbDevice, daoSession);
+            if (dbDevice != null) {
+                final WorldClockDao worldClockDao = daoSession.getWorldClockDao();
+                final Long deviceId = dbDevice.getId();
+                final QueryBuilder<WorldClock> qb = worldClockDao.queryBuilder();
+                qb.where(
+                        WorldClockDao.Properties.UserId.eq(user.getId()),
+                        WorldClockDao.Properties.DeviceId.eq(deviceId)).orderAsc(WorldClockDao.Properties.WorldClockId);
+                return qb.build().list();
+            }
+        } catch (final Exception e) {
+            LOG.error("Error reading world clocks from db", e);
+        }
+
+        return Collections.emptyList();
+    }
+
+    @NonNull
+    public static List<Contact> getContacts(@NonNull GBDevice gbDevice) {
+        try (DBHandler db = GBApplication.acquireDB()) {
+            final DaoSession daoSession = db.getDaoSession();
+            final User user = getUser(daoSession);
+            final Device dbDevice = DBHelper.findDevice(gbDevice, daoSession);
+            if (dbDevice != null) {
+                final ContactDao contactDao = daoSession.getContactDao();
+                final Long deviceId = dbDevice.getId();
+                final QueryBuilder<Contact> qb = contactDao.queryBuilder();
+                qb.where(
+                        ContactDao.Properties.UserId.eq(user.getId()),
+                        ContactDao.Properties.DeviceId.eq(deviceId)).orderAsc(ContactDao.Properties.Name);
+                return qb.build().list();
+            }
+        } catch (final Exception e) {
+            LOG.error("Error reading contacts from db", e);
+        }
+
+        return Collections.emptyList();
+    }
+
+    public static void store(final Reminder reminder) {
+        try (DBHandler db = GBApplication.acquireDB()) {
+            final DaoSession daoSession = db.getDaoSession();
+            daoSession.insertOrReplace(reminder);
+        } catch (final Exception e) {
+            LOG.error("Error acquiring database", e);
+        }
+    }
+
+    public static void store(final WorldClock worldClock) {
+        try (DBHandler db = GBApplication.acquireDB()) {
+            final DaoSession daoSession = db.getDaoSession();
+            daoSession.insertOrReplace(worldClock);
+        } catch (final Exception e) {
+            LOG.error("Error acquiring database", e);
+        }
+    }
+
+    public static void store(final Contact contact) {
+        try (DBHandler db = GBApplication.acquireDB()) {
+            final DaoSession daoSession = db.getDaoSession();
+            daoSession.insertOrReplace(contact);
+        } catch (final Exception e) {
+            LOG.error("Error acquiring database", e);
+        }
+    }
+
+    public static void delete(final Reminder reminder) {
+        try (DBHandler db = GBApplication.acquireDB()) {
+            final DaoSession daoSession = db.getDaoSession();
+            daoSession.delete(reminder);
+        } catch (final Exception e) {
+            LOG.error("Error acquiring database", e);
+        }
+    }
+
+    public static void delete(final WorldClock worldClock) {
+        try (DBHandler db = GBApplication.acquireDB()) {
+            final DaoSession daoSession = db.getDaoSession();
+            daoSession.delete(worldClock);
+        } catch (final Exception e) {
+            LOG.error("Error acquiring database", e);
+        }
+    }
+
+    public static void delete(final Contact contact) {
+        try (DBHandler db = GBApplication.acquireDB()) {
+            final DaoSession daoSession = db.getDaoSession();
+            daoSession.delete(contact);
+        } catch (final Exception e) {
+            LOG.error("Error acquiring database", e);
+        }
+    }
+
+    public static void clearSession() {
+        try (DBHandler dbHandler = GBApplication.acquireDB()) {
+            DaoSession session = dbHandler.getDaoSession();
+            session.clear();
+        } catch (Exception e) {
+            LOG.warn("Unable to acquire database to clear the session", e);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/DBOpenHelper.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/DBOpenHelper.java
new file mode 100644
index 0000000..a958e00
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/DBOpenHelper.java
@@ -0,0 +1,50 @@
+/*  Copyright (C) 2016-2024 Andreas Shimokawa, Carsten Pfeiffer
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.database;
+
+import android.content.Context;
+import android.database.sqlite.SQLiteDatabase;
+
+import nodomain.freeyourgadget.gadgetbridge.database.schema.SchemaMigration;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoMaster;
+
+public class DBOpenHelper extends DaoMaster.OpenHelper {
+    private final String updaterClassNamePrefix;
+    private final Context context;
+
+    public DBOpenHelper(Context context, String dbName, SQLiteDatabase.CursorFactory factory) {
+        super(context, dbName, factory);
+        updaterClassNamePrefix = dbName + "Update_";
+        this.context = context;
+    }
+
+    @Override
+    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+        DaoMaster.createAllTables(db, true);
+        new SchemaMigration(updaterClassNamePrefix).onUpgrade(db, oldVersion, newVersion);
+    }
+
+    @Override
+    public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+        DaoMaster.createAllTables(db, true);
+        new SchemaMigration(updaterClassNamePrefix).onDowngrade(db, oldVersion, newVersion);
+    }
+
+    public Context getContext() {
+        return context;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/DBUpdateScript.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/DBUpdateScript.java
new file mode 100644
index 0000000..3dc3940
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/DBUpdateScript.java
@@ -0,0 +1,33 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, Carsten Pfeiffer
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.database;
+
+import android.database.sqlite.SQLiteDatabase;
+
+/**
+ * Interface for updating a database schema.
+ * Implementors provide the update from the prior schema
+ * version to this version, and the downgrade from this schema
+ * version to the next lower version.
+ * <p/>
+ * Implementations must have a public, no-arg constructor.
+ */
+public interface DBUpdateScript {
+    void upgradeSchema(SQLiteDatabase database);
+
+    void downgradeSchema(SQLiteDatabase database);
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/PeriodicExporter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/PeriodicExporter.java
new file mode 100644
index 0000000..31eb33c
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/PeriodicExporter.java
@@ -0,0 +1,149 @@
+/*  Copyright (C) 2018-2024 Carsten Pfeiffer, Felix Konstantin Maurer,
+    Ganblejs, Jos茅 Rebelo, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.database;
+
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Build;
+import android.os.SystemClock;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.OutputStream;
+
+import nodomain.freeyourgadget.gadgetbridge.BuildConfig;
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+import nodomain.freeyourgadget.gadgetbridge.util.GBPrefs;
+import nodomain.freeyourgadget.gadgetbridge.util.PendingIntentUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+/**
+ * Created by maufl on 1/4/18.
+ */
+
+public class PeriodicExporter extends BroadcastReceiver {
+    private static final Logger LOG = LoggerFactory.getLogger(PeriodicExporter.class);
+
+    public static final String ACTION_DATABASE_EXPORT_SUCCESS = "nodomain.freeyourgadget.gadgetbridge.action.DATABASE_EXPORT_SUCCESS";
+    public static final String ACTION_DATABASE_EXPORT_FAIL = "nodomain.freeyourgadget.gadgetbridge.action.DATABASE_EXPORT_FAIL";
+
+    public static void enablePeriodicExport(Context context) {
+        Prefs prefs = GBApplication.getPrefs();
+        GBApplication gbApp = GBApplication.app();
+        long autoExportScheduled = gbApp.getAutoExportScheduledTimestamp();
+        boolean autoExportEnabled = prefs.getBoolean(GBPrefs.AUTO_EXPORT_ENABLED, false);
+        Integer autoExportInterval = prefs.getInt(GBPrefs.AUTO_EXPORT_INTERVAL, 0);
+        scheduleAlarm(context, autoExportInterval, autoExportEnabled && autoExportScheduled == 0);
+    }
+
+    public static void scheduleAlarm(Context context, Integer autoExportInterval, boolean autoExportEnabled) {
+        Intent i = new Intent(context, PeriodicExporter.class);
+        i.setPackage(BuildConfig.APPLICATION_ID);
+        PendingIntent pi = PendingIntentUtils.getBroadcast(context, 0, i, 0, false);
+        AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+        am.cancel(pi);
+        if (!autoExportEnabled) {
+            LOG.info("Not scheduling periodic export, either already scheduled or not enabled");
+            return;
+        }
+        int exportPeriod = autoExportInterval * 60 * 60 * 1000;
+        if (exportPeriod == 0) {
+            LOG.info("Not scheduling periodic export, interval set to 0");
+            return;
+        }
+        LOG.info("Scheduling periodic export");
+        GBApplication gbApp = GBApplication.app();
+        gbApp.setAutoExportScheduledTimestamp(System.currentTimeMillis() + exportPeriod);
+        am.setInexactRepeating(
+                AlarmManager.ELAPSED_REALTIME,
+                SystemClock.elapsedRealtime() + exportPeriod,
+                exportPeriod,
+                pi
+        );
+    }
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        LOG.info("Received command to export DB");
+        createRefreshTask("Export database", context).execute();
+    }
+
+    protected RefreshTask createRefreshTask(String task, Context context) {
+        return new RefreshTask(task, context);
+    }
+
+    public class RefreshTask extends DBAccess {
+        Context localContext;
+
+        public RefreshTask(String task, Context context) {
+            super(task, context);
+            localContext = context;
+        }
+
+        @Override
+        protected void doInBackground(DBHandler handler) {
+            LOG.info("Exporting DB in a background thread");
+            try (DBHandler dbHandler = GBApplication.acquireDB()) {
+                DBHelper helper = new DBHelper(localContext);
+                String dst = GBApplication.getPrefs().getString(GBPrefs.AUTO_EXPORT_LOCATION, null);
+                if (dst == null) {
+                    LOG.warn("Unable to export DB, export location not set");
+                    broadcastSuccess(false);
+                    return;
+                }
+                Uri dstUri = Uri.parse(dst);
+                try (OutputStream out = localContext.getContentResolver().openOutputStream(dstUri)) {
+                    helper.exportDB(dbHandler, out);
+                    GBApplication gbApp = GBApplication.app();
+                    gbApp.setLastAutoExportTimestamp(System.currentTimeMillis());
+                }
+
+                broadcastSuccess(true);
+
+                LOG.info("DB export completed");
+            } catch (Exception ex) {
+                GB.updateExportFailedNotification(localContext.getString(R.string.notif_export_failed_title), localContext);
+                LOG.info("Exception while exporting DB: ", ex);
+                broadcastSuccess(false);
+            }
+        }
+
+        private void broadcastSuccess(final boolean success) {
+            if (!GBApplication.getPrefs().getBoolean("intent_api_broadcast_export", false)) {
+                return;
+            }
+
+            LOG.info("Broadcasting database export success={}", success);
+
+            final String action = success ? ACTION_DATABASE_EXPORT_SUCCESS : ACTION_DATABASE_EXPORT_FAIL;
+            final Intent exportedNotifyIntent = new Intent(action);
+            localContext.sendBroadcast(exportedNotifyIntent);
+        }
+
+        @Override
+        protected void onPostExecute(Object o) {
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_14.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_14.java
new file mode 100644
index 0000000..6eef4ea
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_14.java
@@ -0,0 +1,42 @@
+/*  Copyright (C) 2016-2024 Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.database.schema;
+
+import android.database.sqlite.SQLiteDatabase;
+
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.database.DBUpdateScript;
+import nodomain.freeyourgadget.gadgetbridge.entities.PebbleHealthActivitySampleDao;
+
+/*
+ * adds heart rate column to health table
+ */
+
+public class GadgetbridgeUpdate_14 implements DBUpdateScript {
+    @Override
+    public void upgradeSchema(SQLiteDatabase db) {
+        if (!DBHelper.existsColumn(PebbleHealthActivitySampleDao.TABLENAME, PebbleHealthActivitySampleDao.Properties.HeartRate.columnName, db)) {
+            String ADD_COLUMN_HEART_RATE = "ALTER TABLE " + PebbleHealthActivitySampleDao.TABLENAME + " ADD COLUMN "
+                    + PebbleHealthActivitySampleDao.Properties.HeartRate.columnName + " INTEGER NOT NULL DEFAULT 0;";
+            db.execSQL(ADD_COLUMN_HEART_RATE);
+        }
+    }
+
+    @Override
+    public void downgradeSchema(SQLiteDatabase db) {
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_15.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_15.java
new file mode 100644
index 0000000..5a50ec9
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_15.java
@@ -0,0 +1,42 @@
+/*  Copyright (C) 2016-2024 Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.database.schema;
+
+import android.database.sqlite.SQLiteDatabase;
+
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.database.DBUpdateScript;
+import nodomain.freeyourgadget.gadgetbridge.entities.DeviceAttributesDao;
+
+/*
+ * adds heart rate column to health table
+ */
+
+public class GadgetbridgeUpdate_15 implements DBUpdateScript {
+    @Override
+    public void upgradeSchema(SQLiteDatabase db) {
+        if (!DBHelper.existsColumn(DeviceAttributesDao.TABLENAME, DeviceAttributesDao.Properties.VolatileIdentifier.columnName, db)) {
+            String ADD_COLUMN_VOLATILE_IDENTIFIER = "ALTER TABLE " + DeviceAttributesDao.TABLENAME + " ADD COLUMN "
+                    + DeviceAttributesDao.Properties.VolatileIdentifier.columnName + " TEXT;";
+            db.execSQL(ADD_COLUMN_VOLATILE_IDENTIFIER);
+        }
+    }
+
+    @Override
+    public void downgradeSchema(SQLiteDatabase db) {
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_17.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_17.java
new file mode 100644
index 0000000..9e50ead
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_17.java
@@ -0,0 +1,38 @@
+/*  Copyright (C) 2017-2024 protomors
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.database.schema;
+
+import android.database.sqlite.SQLiteDatabase;
+
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.database.DBUpdateScript;
+import nodomain.freeyourgadget.gadgetbridge.entities.No1F1ActivitySampleDao;
+
+public class GadgetbridgeUpdate_17 implements DBUpdateScript {
+    @Override
+    public void upgradeSchema(SQLiteDatabase db) {
+        if (!DBHelper.existsColumn(No1F1ActivitySampleDao.TABLENAME, No1F1ActivitySampleDao.Properties.HeartRate.columnName, db)) {
+            String ADD_COLUMN_HEART_RATE = "ALTER TABLE " + No1F1ActivitySampleDao.TABLENAME + " ADD COLUMN "
+                    + No1F1ActivitySampleDao.Properties.HeartRate.columnName + " INTEGER NOT NULL DEFAULT 0;";
+            db.execSQL(ADD_COLUMN_HEART_RATE);
+        }
+    }
+
+    @Override
+    public void downgradeSchema(SQLiteDatabase db) {
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_22.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_22.java
new file mode 100644
index 0000000..c740c4f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_22.java
@@ -0,0 +1,39 @@
+/*  Copyright (C) 2019-2024 Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.database.schema;
+
+import android.database.sqlite.SQLiteDatabase;
+
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.database.DBUpdateScript;
+import nodomain.freeyourgadget.gadgetbridge.entities.AlarmDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.No1F1ActivitySampleDao;
+
+public class GadgetbridgeUpdate_22 implements DBUpdateScript {
+    @Override
+    public void upgradeSchema(SQLiteDatabase db) {
+        if (!DBHelper.existsColumn(AlarmDao.TABLENAME, AlarmDao.Properties.Unused.columnName, db)) {
+            String ADD_COLUMN_UNUSED = "ALTER TABLE " + AlarmDao.TABLENAME + " ADD COLUMN "
+                    + AlarmDao.Properties.Unused.columnName + " INTEGER NOT NULL DEFAULT 0;";
+            db.execSQL(ADD_COLUMN_UNUSED);
+        }
+    }
+
+    @Override
+    public void downgradeSchema(SQLiteDatabase db) {
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_23.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_23.java
new file mode 100644
index 0000000..79b88ac
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_23.java
@@ -0,0 +1,40 @@
+/*  Copyright (C) 2020-2024 Dmitry Markin
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.database.schema;
+
+import android.database.sqlite.SQLiteDatabase;
+
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.database.DBUpdateScript;
+import nodomain.freeyourgadget.gadgetbridge.entities.AlarmDao;
+
+public class GadgetbridgeUpdate_23 implements DBUpdateScript {
+    @Override
+    public void upgradeSchema(SQLiteDatabase db) {
+        if (!DBHelper.existsColumn(AlarmDao.TABLENAME, AlarmDao.Properties.Snooze.columnName, db)) {
+            // Setting default value of SNOOZE column to 1 (true), so that existing MiBand2 alarms
+            // behave as before
+            String ADD_COLUMN_SNOOZE = "ALTER TABLE " + AlarmDao.TABLENAME + " ADD COLUMN "
+                    + AlarmDao.Properties.Snooze.columnName + " INTEGER NOT NULL DEFAULT 1;";
+            db.execSQL(ADD_COLUMN_SNOOZE);
+        }
+    }
+
+    @Override
+    public void downgradeSchema(SQLiteDatabase db) {
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_24.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_24.java
new file mode 100644
index 0000000..420cd2c
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_24.java
@@ -0,0 +1,35 @@
+/*  Copyright (C) 2020-2024 Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.database.schema;
+
+import android.database.sqlite.SQLiteDatabase;
+
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.database.DBUpdateScript;
+import nodomain.freeyourgadget.gadgetbridge.entities.AlarmDao;
+
+public class GadgetbridgeUpdate_24 implements DBUpdateScript {
+    @Override
+    public void upgradeSchema(SQLiteDatabase db) {
+        // Probably it isbetter to hardcode then using constants since constants could change after refactoring and potentially break migration
+        db.execSQL("UPDATE DEVICE SET Type=170 where (Type=80 and Manufacturer like 'Fossil%')");
+    }
+
+    @Override
+    public void downgradeSchema(SQLiteDatabase db) {
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_26.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_26.java
new file mode 100644
index 0000000..0ef3682
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_26.java
@@ -0,0 +1,43 @@
+/*  Copyright (C) 2020-2024 Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.database.schema;
+
+import android.database.sqlite.SQLiteDatabase;
+
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.database.DBUpdateScript;
+import nodomain.freeyourgadget.gadgetbridge.entities.AlarmDao;
+
+public class GadgetbridgeUpdate_26 implements DBUpdateScript {
+    @Override
+    public void upgradeSchema(SQLiteDatabase db) {
+        if (!DBHelper.existsColumn(AlarmDao.TABLENAME, AlarmDao.Properties.Title.columnName, db)) {
+            String ADD_COLUMN_TITLE = "ALTER TABLE " + AlarmDao.TABLENAME + " ADD COLUMN "
+                    + AlarmDao.Properties.Title.columnName + " TEXT";
+            db.execSQL(ADD_COLUMN_TITLE);
+        }
+        if (!DBHelper.existsColumn(AlarmDao.TABLENAME, AlarmDao.Properties.Description.columnName, db)) {
+            String ADD_COLUMN_TITLE = "ALTER TABLE " + AlarmDao.TABLENAME + " ADD COLUMN "
+                    + AlarmDao.Properties.Description.columnName + " TEXT";
+            db.execSQL(ADD_COLUMN_TITLE);
+        }
+    }
+
+    @Override
+    public void downgradeSchema(SQLiteDatabase db) {
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_27.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_27.java
new file mode 100644
index 0000000..00cf6e4
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_27.java
@@ -0,0 +1,38 @@
+/*  Copyright (C) 2020-2024 Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.database.schema;
+
+import android.database.sqlite.SQLiteDatabase;
+
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.database.DBUpdateScript;
+import nodomain.freeyourgadget.gadgetbridge.entities.DeviceDao;
+
+public class GadgetbridgeUpdate_27 implements DBUpdateScript {
+    @Override
+    public void upgradeSchema(SQLiteDatabase db) {
+        if (!DBHelper.existsColumn(DeviceDao.TABLENAME, DeviceDao.Properties.Alias.columnName, db)) {
+            String ADD_COLUMN_ALIAS = "ALTER TABLE " + DeviceDao.TABLENAME + " ADD COLUMN "
+                    + DeviceDao.Properties.Alias.columnName + " TEXT";
+            db.execSQL(ADD_COLUMN_ALIAS);
+        }
+    }
+
+    @Override
+    public void downgradeSchema(SQLiteDatabase db) {
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_29.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_29.java
new file mode 100644
index 0000000..711dea4
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_29.java
@@ -0,0 +1,38 @@
+/*  Copyright (C) 2020-2024 Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.database.schema;
+
+import android.database.sqlite.SQLiteDatabase;
+
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.database.DBUpdateScript;
+import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummaryDao;
+
+public class GadgetbridgeUpdate_29 implements DBUpdateScript {
+    @Override
+    public void upgradeSchema(SQLiteDatabase db) {
+        if (!DBHelper.existsColumn(BaseActivitySummaryDao.TABLENAME, BaseActivitySummaryDao.Properties.SummaryData.columnName, db)) {
+            String ADD_COLUMN_SUMMARY_DATA = "ALTER TABLE " + BaseActivitySummaryDao.TABLENAME + " ADD COLUMN "
+                    + BaseActivitySummaryDao.Properties.SummaryData.columnName + " TEXT";
+            db.execSQL(ADD_COLUMN_SUMMARY_DATA);
+        }
+    }
+
+    @Override
+    public void downgradeSchema(SQLiteDatabase db) {
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_30.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_30.java
new file mode 100644
index 0000000..195ae3a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_30.java
@@ -0,0 +1,38 @@
+/*  Copyright (C) 2020-2024 Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.database.schema;
+
+import android.database.sqlite.SQLiteDatabase;
+
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.database.DBUpdateScript;
+import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummaryDao;
+
+public class GadgetbridgeUpdate_30 implements DBUpdateScript {
+    @Override
+    public void upgradeSchema(SQLiteDatabase db) {
+        if (!DBHelper.existsColumn(BaseActivitySummaryDao.TABLENAME, BaseActivitySummaryDao.Properties.RawSummaryData.columnName, db)) {
+            String ADD_COLUMN_RAW_SUMMARY_DATA = "ALTER TABLE " + BaseActivitySummaryDao.TABLENAME + " ADD COLUMN "
+                    + BaseActivitySummaryDao.Properties.RawSummaryData.columnName + " BLOB";
+            db.execSQL(ADD_COLUMN_RAW_SUMMARY_DATA);
+        }
+    }
+
+    @Override
+    public void downgradeSchema(SQLiteDatabase db) {
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_35.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_35.java
new file mode 100644
index 0000000..5253d90
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_35.java
@@ -0,0 +1,54 @@
+/*  Copyright (C) 2021-2024 Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.database.schema;
+
+import android.database.sqlite.SQLiteDatabase;
+
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.database.DBUpdateScript;
+import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummaryDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.BatteryLevelDao;
+
+public class GadgetbridgeUpdate_35 implements DBUpdateScript {
+    @Override
+    public void upgradeSchema(SQLiteDatabase db) {
+        if (!DBHelper.existsColumn(BatteryLevelDao.TABLENAME, BatteryLevelDao.Properties.BatteryIndex.columnName, db)) {
+            String MOVE_DATA_TO_TEMP_TABLE = "ALTER TABLE battery_level RENAME TO battery_levels_temp;";
+            db.execSQL(MOVE_DATA_TO_TEMP_TABLE);
+
+            String CREATE_TABLE = "CREATE TABLE IF NOT EXISTS \"BATTERY_LEVEL\" (\"TIMESTAMP\" INTEGER  NOT NULL ," +
+                    "\"DEVICE_ID\" INTEGER  NOT NULL ,\"LEVEL\" INTEGER NOT NULL ,\"BATTERY_INDEX\" INTEGER  NOT NULL ," +
+                    "PRIMARY KEY (\"TIMESTAMP\" ,\"DEVICE_ID\" ,\"BATTERY_INDEX\" ) ON CONFLICT REPLACE) WITHOUT ROWID;";
+            db.execSQL(CREATE_TABLE);
+
+            String MIGATE_DATA = "insert into " + BatteryLevelDao.TABLENAME
+                    + " (" + BatteryLevelDao.Properties.Timestamp.columnName + ","
+                    + BatteryLevelDao.Properties.DeviceId.columnName + ","
+                    + BatteryLevelDao.Properties.BatteryIndex.columnName + ","
+                    + BatteryLevelDao.Properties.Level.columnName + ") "
+                    + " select Timestamp, Device_ID, 0, Level from battery_levels_temp;";
+            db.execSQL(MIGATE_DATA);
+
+            String DROP_TEMP_TABLE = "drop table if exists battery_levels_temp";
+            db.execSQL(DROP_TEMP_TABLE);
+        }
+    }
+
+    @Override
+    public void downgradeSchema(SQLiteDatabase db) {
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_42.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_42.java
new file mode 100644
index 0000000..29dfe43
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_42.java
@@ -0,0 +1,38 @@
+/*  Copyright (C) 2022-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.database.schema;
+
+import android.database.sqlite.SQLiteDatabase;
+
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.database.DBUpdateScript;
+import nodomain.freeyourgadget.gadgetbridge.entities.DeviceDao;
+
+public class GadgetbridgeUpdate_42 implements DBUpdateScript {
+    @Override
+    public void upgradeSchema(SQLiteDatabase db) {
+        if (!DBHelper.existsColumn(DeviceDao.TABLENAME, DeviceDao.Properties.ParentFolder.columnName, db)) {
+            String ADD_COLUMN_CPONTAINED_FOLDER = "ALTER TABLE " + DeviceDao.TABLENAME + " ADD COLUMN "
+                    + DeviceDao.Properties.ParentFolder.columnName + " TEXT";
+            db.execSQL(ADD_COLUMN_CPONTAINED_FOLDER);
+        }
+    }
+
+    @Override
+    public void downgradeSchema(SQLiteDatabase db) {
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_44.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_44.java
new file mode 100644
index 0000000..76c11df
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_44.java
@@ -0,0 +1,38 @@
+/*  Copyright (C) 2022-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.database.schema;
+
+import android.database.sqlite.SQLiteDatabase;
+
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.database.DBUpdateScript;
+import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummaryDao;
+
+public class GadgetbridgeUpdate_44 implements DBUpdateScript {
+    @Override
+    public void upgradeSchema(final SQLiteDatabase db) {
+        if (!DBHelper.existsColumn(BaseActivitySummaryDao.TABLENAME, BaseActivitySummaryDao.Properties.RawDetailsPath.columnName, db)) {
+            final String statement = "ALTER TABLE " + BaseActivitySummaryDao.TABLENAME + " ADD COLUMN "
+                    + BaseActivitySummaryDao.Properties.RawDetailsPath.columnName + " TEXT";
+            db.execSQL(statement);
+        }
+    }
+
+    @Override
+    public void downgradeSchema(final SQLiteDatabase db) {
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_45.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_45.java
new file mode 100644
index 0000000..465ef36
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_45.java
@@ -0,0 +1,44 @@
+/*  Copyright (C) 2022-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.database.schema;
+
+import android.database.sqlite.SQLiteDatabase;
+
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.database.DBUpdateScript;
+import nodomain.freeyourgadget.gadgetbridge.entities.WorldClockDao;
+
+public class GadgetbridgeUpdate_45 implements DBUpdateScript {
+    @Override
+    public void upgradeSchema(final SQLiteDatabase db) {
+        if (!DBHelper.existsColumn(WorldClockDao.TABLENAME, WorldClockDao.Properties.Code.columnName, db)) {
+            final String statement = "ALTER TABLE " + WorldClockDao.TABLENAME + " ADD COLUMN "
+                    + WorldClockDao.Properties.Code.columnName + " TEXT";
+            db.execSQL(statement);
+        }
+
+        if (!DBHelper.existsColumn(WorldClockDao.TABLENAME, WorldClockDao.Properties.Enabled.columnName, db)) {
+            final String statement = "ALTER TABLE " + WorldClockDao.TABLENAME + " ADD COLUMN "
+                    + WorldClockDao.Properties.Enabled.columnName + " BOOLEAN DEFAULT TRUE";
+            db.execSQL(statement);
+        }
+    }
+
+    @Override
+    public void downgradeSchema(final SQLiteDatabase db) {
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_51.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_51.java
new file mode 100644
index 0000000..557e1f1
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_51.java
@@ -0,0 +1,38 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.database.schema;
+
+import android.database.sqlite.SQLiteDatabase;
+
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.database.DBUpdateScript;
+import nodomain.freeyourgadget.gadgetbridge.entities.BangleJSActivitySampleDao;
+
+public class GadgetbridgeUpdate_51 implements DBUpdateScript {
+    @Override
+    public void upgradeSchema(final SQLiteDatabase db) {
+        if (!DBHelper.existsColumn(BangleJSActivitySampleDao.TABLENAME, BangleJSActivitySampleDao.Properties.RawIntensity.columnName, db)) {
+            final String sqlAddColumnRawIntensity = "ALTER TABLE " + BangleJSActivitySampleDao.TABLENAME + " ADD COLUMN "
+                    + BangleJSActivitySampleDao.Properties.RawIntensity.columnName + " INTEGER DEFAULT -1 NOT NULL";
+            db.execSQL(sqlAddColumnRawIntensity);
+        }
+    }
+
+    @Override
+    public void downgradeSchema(final SQLiteDatabase db) {
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_62.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_62.java
new file mode 100644
index 0000000..9ec2d78
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_62.java
@@ -0,0 +1,38 @@
+/*  Copyright (C) 2023-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.database.schema;
+
+import android.database.sqlite.SQLiteDatabase;
+
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.database.DBUpdateScript;
+import nodomain.freeyourgadget.gadgetbridge.entities.DeviceDao;
+
+public class GadgetbridgeUpdate_62 implements DBUpdateScript {
+    @Override
+    public void upgradeSchema(SQLiteDatabase db) {
+        if (!DBHelper.existsColumn(DeviceDao.TABLENAME, DeviceDao.Properties.TypeName.columnName, db)) {
+            String ADD_COLUMN_CPONTAINED_FOLDER = "ALTER TABLE " + DeviceDao.TABLENAME + " ADD COLUMN "
+                    + DeviceDao.Properties.TypeName.columnName + " TEXT NOT NULL DEFAULT \"\"";
+            db.execSQL(ADD_COLUMN_CPONTAINED_FOLDER);
+        }
+    }
+
+    @Override
+    public void downgradeSchema(SQLiteDatabase db) {
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_66.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_66.java
new file mode 100644
index 0000000..7aa15a0
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_66.java
@@ -0,0 +1,56 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.database.schema;
+
+import android.database.sqlite.SQLiteDatabase;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
+
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.database.DBUpdateScript;
+import nodomain.freeyourgadget.gadgetbridge.entities.XiaomiSleepTimeSampleDao;
+
+public class GadgetbridgeUpdate_66 implements DBUpdateScript {
+    @Override
+    public void upgradeSchema(final SQLiteDatabase db) {
+        final List<String> newColumns = Arrays.asList(
+                XiaomiSleepTimeSampleDao.Properties.TotalDuration.columnName,
+                XiaomiSleepTimeSampleDao.Properties.DeepSleepDuration.columnName,
+                XiaomiSleepTimeSampleDao.Properties.LightSleepDuration.columnName,
+                XiaomiSleepTimeSampleDao.Properties.RemSleepDuration.columnName,
+                XiaomiSleepTimeSampleDao.Properties.AwakeDuration.columnName
+        );
+
+        for (final String newColumn : newColumns) {
+            if (!DBHelper.existsColumn(XiaomiSleepTimeSampleDao.TABLENAME, newColumn, db)) {
+                final String SQL_ALTER_TABLE = String.format(
+                        Locale.ROOT,
+                        "ALTER TABLE %s ADD COLUMN %s INTEGER",
+                        XiaomiSleepTimeSampleDao.TABLENAME,
+                        newColumn
+                );
+                db.execSQL(SQL_ALTER_TABLE);
+            }
+        }
+    }
+
+    @Override
+    public void downgradeSchema(SQLiteDatabase db) {
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_71.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_71.java
new file mode 100644
index 0000000..384242b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_71.java
@@ -0,0 +1,38 @@
+/*  Copyright (C) 2024 Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.database.schema;
+
+import android.database.sqlite.SQLiteDatabase;
+
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.database.DBUpdateScript;
+import nodomain.freeyourgadget.gadgetbridge.entities.AlarmDao;
+
+public class GadgetbridgeUpdate_71 implements DBUpdateScript {
+    @Override
+    public void upgradeSchema(final SQLiteDatabase db) {
+        if (!DBHelper.existsColumn(AlarmDao.TABLENAME, AlarmDao.Properties.SmartWakeupInterval.columnName, db)) {
+            final String statement = "ALTER TABLE " + AlarmDao.TABLENAME + " ADD COLUMN \""
+                    + AlarmDao.Properties.SmartWakeupInterval.columnName + "\" INTEGER";
+            db.execSQL(statement);
+        }
+    }
+
+    @Override
+    public void downgradeSchema(final SQLiteDatabase db) {
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_72.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_72.java
new file mode 100644
index 0000000..21bc21d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_72.java
@@ -0,0 +1,43 @@
+/*  Copyright (C) 2024 Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.database.schema;
+
+import android.database.sqlite.SQLiteDatabase;
+
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.database.DBUpdateScript;
+import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutDataSampleDao;
+
+public class GadgetbridgeUpdate_72 implements DBUpdateScript {
+    @Override
+    public void upgradeSchema(final SQLiteDatabase db) {
+        if (!DBHelper.existsColumn(HuaweiWorkoutDataSampleDao.TABLENAME, HuaweiWorkoutDataSampleDao.Properties.Calories.columnName, db)) {
+            final String statement = "ALTER TABLE " + HuaweiWorkoutDataSampleDao.TABLENAME + " ADD COLUMN \""
+                    + HuaweiWorkoutDataSampleDao.Properties.Calories.columnName + "\" INTEGER NOT NULL DEFAULT -1";
+            db.execSQL(statement);
+        }
+        if (!DBHelper.existsColumn(HuaweiWorkoutDataSampleDao.TABLENAME, HuaweiWorkoutDataSampleDao.Properties.CyclingPower.columnName, db)) {
+            final String statement = "ALTER TABLE " + HuaweiWorkoutDataSampleDao.TABLENAME + " ADD COLUMN \""
+                    + HuaweiWorkoutDataSampleDao.Properties.CyclingPower.columnName + "\" INTEGER NOT NULL DEFAULT -1";
+            db.execSQL(statement);
+        }
+    }
+
+    @Override
+    public void downgradeSchema(final SQLiteDatabase db) {
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_73.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_73.java
new file mode 100644
index 0000000..fd486c1
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_73.java
@@ -0,0 +1,43 @@
+/*  Copyright (C) 2024 Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.database.schema;
+
+import android.database.sqlite.SQLiteDatabase;
+
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.database.DBUpdateScript;
+import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutDataSampleDao;
+
+public class GadgetbridgeUpdate_73 implements DBUpdateScript {
+    @Override
+    public void upgradeSchema(final SQLiteDatabase db) {
+        if (!DBHelper.existsColumn(HuaweiWorkoutDataSampleDao.TABLENAME, HuaweiWorkoutDataSampleDao.Properties.Frequency.columnName, db)) {
+            final String statement = "ALTER TABLE " + HuaweiWorkoutDataSampleDao.TABLENAME + " ADD COLUMN \""
+                    + HuaweiWorkoutDataSampleDao.Properties.Frequency.columnName + "\" INTEGER NOT NULL DEFAULT -1";
+            db.execSQL(statement);
+        }
+        if (!DBHelper.existsColumn(HuaweiWorkoutDataSampleDao.TABLENAME, HuaweiWorkoutDataSampleDao.Properties.Altitude.columnName, db)) {
+            final String statement = "ALTER TABLE " + HuaweiWorkoutDataSampleDao.TABLENAME + " ADD COLUMN \""
+                    + HuaweiWorkoutDataSampleDao.Properties.Altitude.columnName + "\" INTEGER DEFAULT NULL";
+            db.execSQL(statement);
+        }
+    }
+
+    @Override
+    public void downgradeSchema(final SQLiteDatabase db) {
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_79.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_79.java
new file mode 100644
index 0000000..4663b61
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_79.java
@@ -0,0 +1,38 @@
+/*  Copyright (C) 2024 Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.database.schema;
+
+import android.database.sqlite.SQLiteDatabase;
+
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.database.DBUpdateScript;
+import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutSummarySampleDao;
+
+public class GadgetbridgeUpdate_79 implements DBUpdateScript {
+    @Override
+    public void upgradeSchema(final SQLiteDatabase db) {
+        if (!DBHelper.existsColumn(HuaweiWorkoutSummarySampleDao.TABLENAME, HuaweiWorkoutSummarySampleDao.Properties.GpxFileLocation.columnName, db)) {
+            final String statement = "ALTER TABLE " + HuaweiWorkoutSummarySampleDao.TABLENAME + " ADD COLUMN \""
+                    + HuaweiWorkoutSummarySampleDao.Properties.GpxFileLocation.columnName + "\" TEXT";
+            db.execSQL(statement);
+        }
+    }
+
+    @Override
+    public void downgradeSchema(final SQLiteDatabase db) {
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_80.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_80.java
new file mode 100644
index 0000000..077dcd2
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_80.java
@@ -0,0 +1,44 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.database.schema;
+
+import android.database.sqlite.SQLiteDatabase;
+
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.database.DBUpdateScript;
+import nodomain.freeyourgadget.gadgetbridge.entities.GarminActivitySampleDao;
+
+public class GadgetbridgeUpdate_80 implements DBUpdateScript {
+    @Override
+    public void upgradeSchema(final SQLiteDatabase db) {
+        if (!DBHelper.existsColumn(GarminActivitySampleDao.TABLENAME, GarminActivitySampleDao.Properties.DistanceCm.columnName, db)) {
+            final String statement = "ALTER TABLE " + GarminActivitySampleDao.TABLENAME + " ADD COLUMN \""
+                    + GarminActivitySampleDao.Properties.DistanceCm.columnName + "\" INTEGER NOT NULL DEFAULT -1";
+            db.execSQL(statement);
+        }
+
+        if (!DBHelper.existsColumn(GarminActivitySampleDao.TABLENAME, GarminActivitySampleDao.Properties.ActiveCalories.columnName, db)) {
+            final String statement = "ALTER TABLE " + GarminActivitySampleDao.TABLENAME + " ADD COLUMN \""
+                    + GarminActivitySampleDao.Properties.ActiveCalories.columnName + "\" INTEGER NOT NULL DEFAULT -1";
+            db.execSQL(statement);
+        }
+    }
+
+    @Override
+    public void downgradeSchema(final SQLiteDatabase db) {
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_83.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_83.java
new file mode 100644
index 0000000..17c995b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_83.java
@@ -0,0 +1,47 @@
+package nodomain.freeyourgadget.gadgetbridge.database.schema;
+
+import android.database.sqlite.SQLiteDatabase;
+
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.database.DBUpdateScript;
+import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutPaceSampleDao;
+
+public class GadgetbridgeUpdate_83 implements DBUpdateScript {
+    @Override
+    public void upgradeSchema(final SQLiteDatabase db) {
+        if (!DBHelper.existsColumn(HuaweiWorkoutPaceSampleDao.TABLENAME, HuaweiWorkoutPaceSampleDao.Properties.PaceIndex.columnName, db) && !DBHelper.existsColumn(HuaweiWorkoutPaceSampleDao.TABLENAME, HuaweiWorkoutPaceSampleDao.Properties.PointIndex.columnName, db)) {
+            String MOVE_DATA_TO_TEMP_TABLE = "ALTER TABLE " + HuaweiWorkoutPaceSampleDao.TABLENAME + " RENAME TO " +HuaweiWorkoutPaceSampleDao.TABLENAME + "_temp;";
+            db.execSQL(MOVE_DATA_TO_TEMP_TABLE);
+
+            String CREATE_TABLE = "CREATE TABLE IF NOT EXISTS \"" + HuaweiWorkoutPaceSampleDao.TABLENAME +
+                    "\" (\""+HuaweiWorkoutPaceSampleDao.Properties.WorkoutId.columnName+"\" INTEGER  NOT NULL ," +
+                    "\""+ HuaweiWorkoutPaceSampleDao.Properties.PaceIndex.columnName +"\" INTEGER  NOT NULL ," +
+                    "\""+ HuaweiWorkoutPaceSampleDao.Properties.Distance.columnName +"\" INTEGER  NOT NULL ," +
+                    "\""+ HuaweiWorkoutPaceSampleDao.Properties.Type.columnName +"\" INTEGER  NOT NULL ," +
+                    "\""+ HuaweiWorkoutPaceSampleDao.Properties.Pace.columnName +"\" INTEGER  NOT NULL ," +
+                    "\""+ HuaweiWorkoutPaceSampleDao.Properties.PointIndex.columnName +"\" INTEGER  NOT NULL ," +
+                    "\""+ HuaweiWorkoutPaceSampleDao.Properties.Correction.columnName +"\" INTEGER ," +
+                    "PRIMARY KEY (\""+HuaweiWorkoutPaceSampleDao.Properties.WorkoutId.columnName+"\" ,\""+ HuaweiWorkoutPaceSampleDao.Properties.PaceIndex.columnName +"\" ,\""+ HuaweiWorkoutPaceSampleDao.Properties.Distance.columnName +"\", \""+ HuaweiWorkoutPaceSampleDao.Properties.Type.columnName +"\") ON CONFLICT REPLACE) WITHOUT ROWID;";
+            db.execSQL(CREATE_TABLE);
+
+            String MIGATE_DATA = "INSERT INTO " + HuaweiWorkoutPaceSampleDao.TABLENAME
+                    + " (" +HuaweiWorkoutPaceSampleDao.Properties.WorkoutId.columnName+ ","
+                    + HuaweiWorkoutPaceSampleDao.Properties.PaceIndex.columnName + ","
+                    + HuaweiWorkoutPaceSampleDao.Properties.Distance.columnName+ ","
+                    + HuaweiWorkoutPaceSampleDao.Properties.Type.columnName + ","
+                    + HuaweiWorkoutPaceSampleDao.Properties.Pace.columnName + ","
+                    + HuaweiWorkoutPaceSampleDao.Properties.PointIndex.columnName + ","
+                    + HuaweiWorkoutPaceSampleDao.Properties.Correction.columnName + ") "
+                    + " SELECT WORKOUT_ID, -1, DISTANCE, TYPE, PACE, 0, CORRECTION  FROM " +HuaweiWorkoutPaceSampleDao.TABLENAME + "_temp;";
+
+            db.execSQL(MIGATE_DATA);
+
+            String DROP_TEMP_TABLE = "drop table if exists " +HuaweiWorkoutPaceSampleDao.TABLENAME + "_temp;";
+            db.execSQL(DROP_TEMP_TABLE);
+        }
+    }
+
+    @Override
+    public void downgradeSchema(final SQLiteDatabase db) {
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_84.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_84.java
new file mode 100644
index 0000000..22ffd9f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_84.java
@@ -0,0 +1,79 @@
+package nodomain.freeyourgadget.gadgetbridge.database.schema;
+
+import android.database.sqlite.SQLiteDatabase;
+
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.database.DBUpdateScript;
+import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutSummarySampleDao;
+
+public class GadgetbridgeUpdate_84 implements DBUpdateScript {
+    @Override
+    public void upgradeSchema(final SQLiteDatabase db) {
+        if (!DBHelper.existsColumn(HuaweiWorkoutSummarySampleDao.TABLENAME, HuaweiWorkoutSummarySampleDao.Properties.MaxAltitude.columnName, db)) {
+            final String statement = "ALTER TABLE " + HuaweiWorkoutSummarySampleDao.TABLENAME + " ADD COLUMN \""
+                    + HuaweiWorkoutSummarySampleDao.Properties.MaxAltitude.columnName + "\" INTEGER";
+            db.execSQL(statement);
+        }
+
+        if (!DBHelper.existsColumn(HuaweiWorkoutSummarySampleDao.TABLENAME, HuaweiWorkoutSummarySampleDao.Properties.MinAltitude.columnName, db)) {
+            final String statement = "ALTER TABLE " + HuaweiWorkoutSummarySampleDao.TABLENAME + " ADD COLUMN \""
+                    + HuaweiWorkoutSummarySampleDao.Properties.MinAltitude.columnName + "\" INTEGER";
+            db.execSQL(statement);
+        }
+
+        if (!DBHelper.existsColumn(HuaweiWorkoutSummarySampleDao.TABLENAME, HuaweiWorkoutSummarySampleDao.Properties.ElevationGain.columnName, db)) {
+            final String statement = "ALTER TABLE " + HuaweiWorkoutSummarySampleDao.TABLENAME + " ADD COLUMN \""
+                    + HuaweiWorkoutSummarySampleDao.Properties.ElevationGain.columnName + "\" INTEGER";
+            db.execSQL(statement);
+        }
+
+        if (!DBHelper.existsColumn(HuaweiWorkoutSummarySampleDao.TABLENAME, HuaweiWorkoutSummarySampleDao.Properties.ElevationLoss.columnName, db)) {
+            final String statement = "ALTER TABLE " + HuaweiWorkoutSummarySampleDao.TABLENAME + " ADD COLUMN \""
+                    + HuaweiWorkoutSummarySampleDao.Properties.ElevationLoss.columnName + "\" INTEGER";
+            db.execSQL(statement);
+        }
+
+        if (!DBHelper.existsColumn(HuaweiWorkoutSummarySampleDao.TABLENAME, HuaweiWorkoutSummarySampleDao.Properties.WorkoutLoad.columnName, db)) {
+            final String statement = "ALTER TABLE " + HuaweiWorkoutSummarySampleDao.TABLENAME + " ADD COLUMN \""
+                    + HuaweiWorkoutSummarySampleDao.Properties.WorkoutLoad.columnName + "\" INTEGER NOT NULL DEFAULT 0;";
+            db.execSQL(statement);
+        }
+        if (!DBHelper.existsColumn(HuaweiWorkoutSummarySampleDao.TABLENAME, HuaweiWorkoutSummarySampleDao.Properties.WorkoutAerobicEffect.columnName, db)) {
+            final String statement = "ALTER TABLE " + HuaweiWorkoutSummarySampleDao.TABLENAME + " ADD COLUMN \""
+                    + HuaweiWorkoutSummarySampleDao.Properties.WorkoutAerobicEffect.columnName + "\" INTEGER NOT NULL DEFAULT 0;";
+            db.execSQL(statement);
+        }
+        if (!DBHelper.existsColumn(HuaweiWorkoutSummarySampleDao.TABLENAME, HuaweiWorkoutSummarySampleDao.Properties.WorkoutAnaerobicEffect.columnName, db)) {
+            final String statement = "ALTER TABLE " + HuaweiWorkoutSummarySampleDao.TABLENAME + " ADD COLUMN \""
+                    + HuaweiWorkoutSummarySampleDao.Properties.WorkoutAnaerobicEffect.columnName + "\" INTEGER NOT NULL DEFAULT -1;";
+            db.execSQL(statement);
+        }
+        if (!DBHelper.existsColumn(HuaweiWorkoutSummarySampleDao.TABLENAME, HuaweiWorkoutSummarySampleDao.Properties.RecoveryTime.columnName, db)) {
+            final String statement = "ALTER TABLE " + HuaweiWorkoutSummarySampleDao.TABLENAME + " ADD COLUMN \""
+                    + HuaweiWorkoutSummarySampleDao.Properties.RecoveryTime.columnName + "\" INTEGER NOT NULL DEFAULT 0;";
+            db.execSQL(statement);
+        }
+
+        if (!DBHelper.existsColumn(HuaweiWorkoutSummarySampleDao.TABLENAME, HuaweiWorkoutSummarySampleDao.Properties.MinHeartRatePeak.columnName, db)) {
+            final String statement = "ALTER TABLE " + HuaweiWorkoutSummarySampleDao.TABLENAME + " ADD COLUMN \""
+                    + HuaweiWorkoutSummarySampleDao.Properties.MinHeartRatePeak.columnName + "\" INTEGER NOT NULL DEFAULT 0;";
+            db.execSQL(statement);
+        }
+
+        if (!DBHelper.existsColumn(HuaweiWorkoutSummarySampleDao.TABLENAME, HuaweiWorkoutSummarySampleDao.Properties.MaxHeartRatePeak.columnName, db)) {
+            final String statement = "ALTER TABLE " + HuaweiWorkoutSummarySampleDao.TABLENAME + " ADD COLUMN \""
+                    + HuaweiWorkoutSummarySampleDao.Properties.MaxHeartRatePeak.columnName + "\" INTEGER NOT NULL DEFAULT 0;";
+            db.execSQL(statement);
+        }
+
+        if (!DBHelper.existsColumn(HuaweiWorkoutSummarySampleDao.TABLENAME, HuaweiWorkoutSummarySampleDao.Properties.RecoveryHeartRates.columnName, db)) {
+            final String statement = "ALTER TABLE " + HuaweiWorkoutSummarySampleDao.TABLENAME + " ADD COLUMN \""
+                    + HuaweiWorkoutSummarySampleDao.Properties.RecoveryHeartRates.columnName + "\" BLOB";
+            db.execSQL(statement);
+        }
+    }
+
+    @Override
+    public void downgradeSchema(final SQLiteDatabase db) {
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_85.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_85.java
new file mode 100644
index 0000000..7579729
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_85.java
@@ -0,0 +1,22 @@
+package nodomain.freeyourgadget.gadgetbridge.database.schema;
+
+import android.database.sqlite.SQLiteDatabase;
+
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.database.DBUpdateScript;
+import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutSummarySampleDao;
+
+public class GadgetbridgeUpdate_85 implements DBUpdateScript {
+    @Override
+    public void upgradeSchema(final SQLiteDatabase db) {
+        if (!DBHelper.existsColumn(HuaweiWorkoutSummarySampleDao.TABLENAME, HuaweiWorkoutSummarySampleDao.Properties.SwimType.columnName, db)) {
+            final String statement = "ALTER TABLE " + HuaweiWorkoutSummarySampleDao.TABLENAME + " ADD COLUMN \""
+                    + HuaweiWorkoutSummarySampleDao.Properties.SwimType.columnName + "\" INTEGER NOT NULL DEFAULT -1;";
+            db.execSQL(statement);
+        }
+    }
+
+    @Override
+    public void downgradeSchema(final SQLiteDatabase db) {
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_89.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_89.java
new file mode 100644
index 0000000..384e0a3
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_89.java
@@ -0,0 +1,93 @@
+package nodomain.freeyourgadget.gadgetbridge.database.schema;
+
+import android.database.sqlite.SQLiteDatabase;
+
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.database.DBUpdateScript;
+import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutSummarySampleDao;
+
+public class GadgetbridgeUpdate_89 implements DBUpdateScript {
+    @Override
+    public void upgradeSchema(final SQLiteDatabase db) {
+
+        if (!DBHelper.existsColumn(HuaweiWorkoutSummarySampleDao.TABLENAME, HuaweiWorkoutSummarySampleDao.Properties.MaxMET.columnName, db)) {
+            final String statement = "ALTER TABLE " + HuaweiWorkoutSummarySampleDao.TABLENAME + " ADD COLUMN \""
+                    + HuaweiWorkoutSummarySampleDao.Properties.MaxMET.columnName + "\" INTEGER NOT NULL DEFAULT 0;";
+            db.execSQL(statement);
+        }
+        if (!DBHelper.existsColumn(HuaweiWorkoutSummarySampleDao.TABLENAME, HuaweiWorkoutSummarySampleDao.Properties.HrZoneType.columnName, db)) {
+            final String statement = "ALTER TABLE " + HuaweiWorkoutSummarySampleDao.TABLENAME + " ADD COLUMN \""
+                    + HuaweiWorkoutSummarySampleDao.Properties.HrZoneType.columnName + "\" INTEGER NOT NULL DEFAULT -1;";
+            db.execSQL(statement);
+        }
+        if (!DBHelper.existsColumn(HuaweiWorkoutSummarySampleDao.TABLENAME, HuaweiWorkoutSummarySampleDao.Properties.RunPaceZone1Min.columnName, db)) {
+            final String statement = "ALTER TABLE " + HuaweiWorkoutSummarySampleDao.TABLENAME + " ADD COLUMN \""
+                    + HuaweiWorkoutSummarySampleDao.Properties.RunPaceZone1Min.columnName + "\" INTEGER NOT NULL DEFAULT -1;";
+            db.execSQL(statement);
+        }
+        if (!DBHelper.existsColumn(HuaweiWorkoutSummarySampleDao.TABLENAME, HuaweiWorkoutSummarySampleDao.Properties.RunPaceZone2Min.columnName, db)) {
+            final String statement = "ALTER TABLE " + HuaweiWorkoutSummarySampleDao.TABLENAME + " ADD COLUMN \""
+                    + HuaweiWorkoutSummarySampleDao.Properties.RunPaceZone2Min.columnName + "\" INTEGER NOT NULL DEFAULT -1;";
+            db.execSQL(statement);
+        }
+        if (!DBHelper.existsColumn(HuaweiWorkoutSummarySampleDao.TABLENAME, HuaweiWorkoutSummarySampleDao.Properties.RunPaceZone3Min.columnName, db)) {
+            final String statement = "ALTER TABLE " + HuaweiWorkoutSummarySampleDao.TABLENAME + " ADD COLUMN \""
+                    + HuaweiWorkoutSummarySampleDao.Properties.RunPaceZone3Min.columnName + "\" INTEGER NOT NULL DEFAULT -1;";
+            db.execSQL(statement);
+        }
+        if (!DBHelper.existsColumn(HuaweiWorkoutSummarySampleDao.TABLENAME, HuaweiWorkoutSummarySampleDao.Properties.RunPaceZone4Min.columnName, db)) {
+            final String statement = "ALTER TABLE " + HuaweiWorkoutSummarySampleDao.TABLENAME + " ADD COLUMN \""
+                    + HuaweiWorkoutSummarySampleDao.Properties.RunPaceZone4Min.columnName + "\" INTEGER NOT NULL DEFAULT -1;";
+            db.execSQL(statement);
+        }
+        if (!DBHelper.existsColumn(HuaweiWorkoutSummarySampleDao.TABLENAME, HuaweiWorkoutSummarySampleDao.Properties.RunPaceZone5Min.columnName, db)) {
+            final String statement = "ALTER TABLE " + HuaweiWorkoutSummarySampleDao.TABLENAME + " ADD COLUMN \""
+                    + HuaweiWorkoutSummarySampleDao.Properties.RunPaceZone5Min.columnName + "\" INTEGER NOT NULL DEFAULT -1;";
+            db.execSQL(statement);
+        }
+        if (!DBHelper.existsColumn(HuaweiWorkoutSummarySampleDao.TABLENAME, HuaweiWorkoutSummarySampleDao.Properties.RunPaceZone5Max.columnName, db)) {
+            final String statement = "ALTER TABLE " + HuaweiWorkoutSummarySampleDao.TABLENAME + " ADD COLUMN \""
+                    + HuaweiWorkoutSummarySampleDao.Properties.RunPaceZone5Max.columnName + "\" INTEGER NOT NULL DEFAULT -1;";
+            db.execSQL(statement);
+        }
+        if (!DBHelper.existsColumn(HuaweiWorkoutSummarySampleDao.TABLENAME, HuaweiWorkoutSummarySampleDao.Properties.RunPaceZone1Time.columnName, db)) {
+            final String statement = "ALTER TABLE " + HuaweiWorkoutSummarySampleDao.TABLENAME + " ADD COLUMN \""
+                    + HuaweiWorkoutSummarySampleDao.Properties.RunPaceZone1Time.columnName + "\" INTEGER NOT NULL DEFAULT -1;";
+            db.execSQL(statement);
+        }
+        if (!DBHelper.existsColumn(HuaweiWorkoutSummarySampleDao.TABLENAME, HuaweiWorkoutSummarySampleDao.Properties.RunPaceZone2Time.columnName, db)) {
+            final String statement = "ALTER TABLE " + HuaweiWorkoutSummarySampleDao.TABLENAME + " ADD COLUMN \""
+                    + HuaweiWorkoutSummarySampleDao.Properties.RunPaceZone2Time.columnName + "\" INTEGER NOT NULL DEFAULT -1;";
+            db.execSQL(statement);
+        }
+        if (!DBHelper.existsColumn(HuaweiWorkoutSummarySampleDao.TABLENAME, HuaweiWorkoutSummarySampleDao.Properties.RunPaceZone3Time.columnName, db)) {
+            final String statement = "ALTER TABLE " + HuaweiWorkoutSummarySampleDao.TABLENAME + " ADD COLUMN \""
+                    + HuaweiWorkoutSummarySampleDao.Properties.RunPaceZone3Time.columnName + "\" INTEGER NOT NULL DEFAULT -1;";
+            db.execSQL(statement);
+        }
+        if (!DBHelper.existsColumn(HuaweiWorkoutSummarySampleDao.TABLENAME, HuaweiWorkoutSummarySampleDao.Properties.RunPaceZone4Time.columnName, db)) {
+            final String statement = "ALTER TABLE " + HuaweiWorkoutSummarySampleDao.TABLENAME + " ADD COLUMN \""
+                    + HuaweiWorkoutSummarySampleDao.Properties.RunPaceZone4Time.columnName + "\" INTEGER NOT NULL DEFAULT -1;";
+            db.execSQL(statement);
+        }
+        if (!DBHelper.existsColumn(HuaweiWorkoutSummarySampleDao.TABLENAME, HuaweiWorkoutSummarySampleDao.Properties.RunPaceZone5Time.columnName, db)) {
+            final String statement = "ALTER TABLE " + HuaweiWorkoutSummarySampleDao.TABLENAME + " ADD COLUMN \""
+                    + HuaweiWorkoutSummarySampleDao.Properties.RunPaceZone5Time.columnName + "\" INTEGER NOT NULL DEFAULT -1;";
+            db.execSQL(statement);
+        }
+        if (!DBHelper.existsColumn(HuaweiWorkoutSummarySampleDao.TABLENAME, HuaweiWorkoutSummarySampleDao.Properties.AlgType.columnName, db)) {
+            final String statement = "ALTER TABLE " + HuaweiWorkoutSummarySampleDao.TABLENAME + " ADD COLUMN \""
+                    + HuaweiWorkoutSummarySampleDao.Properties.AlgType.columnName + "\" INTEGER NOT NULL DEFAULT 0;";
+            db.execSQL(statement);
+        }
+        if (!DBHelper.existsColumn(HuaweiWorkoutSummarySampleDao.TABLENAME, HuaweiWorkoutSummarySampleDao.Properties.TrainingPoints.columnName, db)) {
+            final String statement = "ALTER TABLE " + HuaweiWorkoutSummarySampleDao.TABLENAME + " ADD COLUMN \""
+                    + HuaweiWorkoutSummarySampleDao.Properties.TrainingPoints.columnName + "\" INTEGER NOT NULL DEFAULT 0;";
+            db.execSQL(statement);
+        }
+    }
+
+    @Override
+    public void downgradeSchema(final SQLiteDatabase db) {
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/SchemaMigration.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/SchemaMigration.java
new file mode 100644
index 0000000..5eda0d6
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/SchemaMigration.java
@@ -0,0 +1,80 @@
+/*  Copyright (C) 2016-2024 Andreas Shimokawa, Carsten Pfeiffer
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.database.schema;
+
+import android.database.sqlite.SQLiteDatabase;
+import android.widget.Toast;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import nodomain.freeyourgadget.gadgetbridge.database.DBUpdateScript;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class SchemaMigration {
+    private static final Logger LOG = LoggerFactory.getLogger(SchemaMigration.class);
+    private final String classNamePrefix;
+
+    public SchemaMigration(String updaterClassNamePrefix) {
+        classNamePrefix = updaterClassNamePrefix;
+    }
+
+    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+        LOG.info("ActivityDatabase: schema upgrade requested from " + oldVersion + " to " + newVersion);
+        try {
+            for (int i = oldVersion + 1; i <= newVersion; i++) {
+                DBUpdateScript updater = getUpdateScript(db, i);
+                if (updater != null) {
+                    LOG.info("upgrading activity database to version " + i);
+                    updater.upgradeSchema(db);
+                }
+            }
+            LOG.info("activity database is now at version " + newVersion);
+        } catch (RuntimeException ex) {
+            GB.toast("Error upgrading database.", Toast.LENGTH_SHORT, GB.ERROR, ex);
+            throw ex; // reject upgrade
+        }
+    }
+
+    public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+        LOG.info("ActivityDatabase: schema downgrade requested from " + oldVersion + " to " + newVersion);
+        try {
+            for (int i = oldVersion; i >= newVersion; i--) {
+                DBUpdateScript updater = getUpdateScript(db, i);
+                if (updater != null) {
+                    LOG.info("downgrading activity database to version " + (i - 1));
+                    updater.downgradeSchema(db);
+                }
+            }
+            LOG.info("activity database is now at version " + newVersion);
+        } catch (RuntimeException ex) {
+            GB.toast("Error downgrading database.", Toast.LENGTH_SHORT, GB.ERROR, ex);
+            throw ex; // reject downgrade
+        }
+    }
+
+    private DBUpdateScript getUpdateScript(SQLiteDatabase db, int version) {
+        try {
+            Class<?> updateClass = getClass().getClassLoader().loadClass(getClass().getPackage().getName() + "." + classNamePrefix + version);
+            return (DBUpdateScript) updateClass.newInstance();
+        } catch (ClassNotFoundException e) {
+            return null;
+        } catch (InstantiationException | IllegalAccessException e) {
+            throw new RuntimeException("Error instantiating DBUpdate class for version " + version, e);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEvent.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEvent.java
new file mode 100644
index 0000000..2c77fde
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEvent.java
@@ -0,0 +1,27 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, Carsten Pfeiffer, Daniele
+    Gobbetti
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.deviceevents;
+
+
+public abstract class GBDeviceEvent {
+    @Override
+    public String toString() {
+        return getClass().getSimpleName() + ": ";
+    }
+}
+
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventAppInfo.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventAppInfo.java
new file mode 100644
index 0000000..afc4259
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventAppInfo.java
@@ -0,0 +1,24 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, Carsten Pfeiffer
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.deviceevents;
+
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp;
+
+public class GBDeviceEventAppInfo extends GBDeviceEvent {
+    public GBDeviceApp apps[];
+    public byte freeSlot = -1;
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventAppManagement.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventAppManagement.java
new file mode 100644
index 0000000..190edfa
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventAppManagement.java
@@ -0,0 +1,42 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.deviceevents;
+
+import java.util.UUID;
+
+public class GBDeviceEventAppManagement extends GBDeviceEvent {
+    public Event event = Event.UNKNOWN;
+    public EventType type = EventType.UNKNOWN;
+    public int token = -1;
+    public UUID uuid = null;
+
+    public enum EventType {
+        UNKNOWN,
+        INSTALL,
+        DELETE,
+        START,
+        STOP,
+    }
+
+    public enum Event {
+        UNKNOWN,
+        SUCCESS,
+        ACKNOWLEDGE,
+        FAILURE,
+        REQUEST,
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventAppMessage.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventAppMessage.java
new file mode 100644
index 0000000..0bab08d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventAppMessage.java
@@ -0,0 +1,40 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, Carsten Pfeiffer
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.deviceevents;
+
+import java.util.UUID;
+
+public class GBDeviceEventAppMessage extends GBDeviceEvent {
+    public static int TYPE_APPMESSAGE = 0;
+    public static int TYPE_ACK = 1;
+    public static int TYPE_NACK = 2;
+
+    public int type;
+    public UUID appUUID;
+    public int id;
+    public String message;
+
+    @Override
+    public String toString() {
+        return "GBDeviceEventAppMessage{" +
+                "type=" + type +
+                ", appUUID=" + appUUID +
+                ", message='" + message + '\'' +
+                ", id=" + id +
+                '}';
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventBatteryInfo.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventBatteryInfo.java
new file mode 100644
index 0000000..88376ee
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventBatteryInfo.java
@@ -0,0 +1,39 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, Daniele Gobbetti, Jos茅 Rebelo,
+    Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.deviceevents;
+
+
+import java.util.GregorianCalendar;
+
+import nodomain.freeyourgadget.gadgetbridge.model.BatteryState;
+
+public class GBDeviceEventBatteryInfo extends GBDeviceEvent {
+    public GregorianCalendar lastChargeTime = null;
+    public BatteryState state = BatteryState.UNKNOWN;
+    public int batteryIndex = 0;
+    public int level = 50;
+    public int numCharges = -1;
+    public float voltage = -1f;
+
+    public boolean extendedInfoAvailable() {
+        if (numCharges != -1 && lastChargeTime != null) {
+            return true;
+        }
+        return false;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventCallControl.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventCallControl.java
new file mode 100644
index 0000000..596e1dd
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventCallControl.java
@@ -0,0 +1,40 @@
+/*  Copyright (C) 2015-2024 Andreas B枚hler, Andreas Shimokawa, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.deviceevents;
+
+
+public class GBDeviceEventCallControl extends GBDeviceEvent {
+    public Event event = Event.UNKNOWN;
+
+    public GBDeviceEventCallControl() {
+    }
+
+    public GBDeviceEventCallControl(final Event event) {
+        this.event = event;
+    }
+
+    public enum Event {
+        UNKNOWN,
+        ACCEPT,
+        END,
+        INCOMING,
+        OUTGOING,
+        REJECT,
+        START,
+        IGNORE,
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventCameraRemote.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventCameraRemote.java
new file mode 100644
index 0000000..cf522dd
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventCameraRemote.java
@@ -0,0 +1,57 @@
+/*  Copyright (C) 2024 Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.deviceevents;
+
+public class GBDeviceEventCameraRemote extends GBDeviceEvent {
+    public Event event = Event.UNKNOWN;
+
+    public enum Event {
+        UNKNOWN,
+        OPEN_CAMERA,
+        TAKE_PICTURE,
+        CLOSE_CAMERA,
+        EXCEPTION
+    }
+
+    static public int eventToInt(Event event) {
+        switch (event) {
+            case UNKNOWN:
+                return 0;
+            case OPEN_CAMERA:
+                return 1;
+            case TAKE_PICTURE:
+                return 2;
+            case CLOSE_CAMERA:
+                return 3;
+        }
+        return -1;
+    }
+
+    static public Event intToEvent(int event) {
+        switch (event) {
+            case 0:
+                return Event.UNKNOWN;
+            case 1:
+                return Event.OPEN_CAMERA;
+            case 2:
+                return Event.TAKE_PICTURE;
+            case 3:
+                return Event.CLOSE_CAMERA;
+        }
+        return Event.EXCEPTION;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventDisplayMessage.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventDisplayMessage.java
new file mode 100644
index 0000000..1fbc87f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventDisplayMessage.java
@@ -0,0 +1,38 @@
+/*  Copyright (C) 2016-2024 Andreas Shimokawa, Carsten Pfeiffer
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.deviceevents;
+
+public class GBDeviceEventDisplayMessage {
+    public String message;
+    public int duration;
+    public int severity;
+
+    /**
+     * An event for displaying a message to the user. How the message is displayed
+     * is a detail of the current activity, which needs to listen to the Intent
+     * GB.ACTION_DISPLAY_MESSAGE.
+     *
+     * @param message
+     * @param duration
+     * @param severity
+     */
+    public GBDeviceEventDisplayMessage(String message, int duration, int severity) {
+        this.message = message;
+        this.duration = duration;
+        this.severity = severity;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventFindPhone.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventFindPhone.java
new file mode 100644
index 0000000..92aeac6
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventFindPhone.java
@@ -0,0 +1,31 @@
+/*  Copyright (C) 2018-2024 Andreas Shimokawa, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+
+package nodomain.freeyourgadget.gadgetbridge.deviceevents;
+
+public class GBDeviceEventFindPhone extends GBDeviceEvent {
+    public Event event = Event.UNKNOWN;
+
+    public enum Event {
+        UNKNOWN,
+        START,
+        START_VIBRATE,
+        STOP,
+        VIBRATE,
+        RING,
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventFmFrequency.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventFmFrequency.java
new file mode 100644
index 0000000..7c02a46
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventFmFrequency.java
@@ -0,0 +1,25 @@
+/*  Copyright (C) 2018-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.deviceevents;
+
+public class GBDeviceEventFmFrequency extends GBDeviceEvent {
+    public final float frequency;
+
+    public GBDeviceEventFmFrequency(final float frequency) {
+        this.frequency = frequency;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventLEDColor.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventLEDColor.java
new file mode 100644
index 0000000..e476759
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventLEDColor.java
@@ -0,0 +1,25 @@
+/*  Copyright (C) 2018-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.deviceevents;
+
+public class GBDeviceEventLEDColor extends GBDeviceEvent {
+    public final int color;
+
+    public GBDeviceEventLEDColor(final int color) {
+        this.color = color;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventMusicControl.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventMusicControl.java
new file mode 100644
index 0000000..df9d118
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventMusicControl.java
@@ -0,0 +1,35 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.deviceevents;
+
+
+public class GBDeviceEventMusicControl extends GBDeviceEvent {
+    public Event event = Event.UNKNOWN;
+
+    public enum Event {
+        UNKNOWN,
+        PLAY,
+        PAUSE,
+        PLAYPAUSE,
+        NEXT,
+        PREVIOUS,
+        VOLUMEUP,
+        VOLUMEDOWN,
+        FORWARD,
+        REWIND
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventNotificationControl.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventNotificationControl.java
new file mode 100644
index 0000000..be05245
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventNotificationControl.java
@@ -0,0 +1,34 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, Daniele Gobbetti
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.deviceevents;
+
+public class GBDeviceEventNotificationControl extends GBDeviceEvent {
+    public long handle;
+    public String phoneNumber;
+    public String reply;
+    public String title;
+    public Event event = Event.UNKNOWN;
+
+    public enum Event {
+        UNKNOWN,
+        DISMISS,
+        DISMISS_ALL,
+        OPEN,
+        MUTE,
+        REPLY,
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventScreenshot.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventScreenshot.java
new file mode 100644
index 0000000..b67b9a5
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventScreenshot.java
@@ -0,0 +1,29 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.deviceevents;
+
+public class GBDeviceEventScreenshot extends GBDeviceEvent {
+    private final byte[] data;
+
+    public GBDeviceEventScreenshot(final byte[] data) {
+        this.data = data;
+    }
+
+    public byte[] getData() {
+        return data;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventSendBytes.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventSendBytes.java
new file mode 100644
index 0000000..8a4243a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventSendBytes.java
@@ -0,0 +1,28 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.deviceevents;
+
+public class GBDeviceEventSendBytes extends GBDeviceEvent {
+    public byte[] encodedBytes;
+
+    public GBDeviceEventSendBytes() {
+    }
+
+    public GBDeviceEventSendBytes(final byte[] encodedBytes) {
+        this.encodedBytes = encodedBytes;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventSilentMode.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventSilentMode.java
new file mode 100644
index 0000000..e057843
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventSilentMode.java
@@ -0,0 +1,29 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.deviceevents;
+
+public class GBDeviceEventSilentMode extends GBDeviceEvent {
+    private final boolean enabled;
+
+    public GBDeviceEventSilentMode(final boolean enabled) {
+        this.enabled = enabled;
+    }
+
+    public boolean isEnabled() {
+        return enabled;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventSleepStateDetection.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventSleepStateDetection.java
new file mode 100644
index 0000000..b4fcdca
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventSleepStateDetection.java
@@ -0,0 +1,30 @@
+/*  Copyright (C) 2023-2024 Yoran Vulker
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.deviceevents;
+
+import java.util.Locale;
+
+import nodomain.freeyourgadget.gadgetbridge.model.SleepState;
+
+public class GBDeviceEventSleepStateDetection extends GBDeviceEvent {
+    public SleepState sleepState;
+
+    @Override
+    public String toString() {
+        return super.toString() + String.format(Locale.ROOT, "sleepState=%s", sleepState);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventUpdateDeviceInfo.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventUpdateDeviceInfo.java
new file mode 100644
index 0000000..d1951c1
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventUpdateDeviceInfo.java
@@ -0,0 +1,32 @@
+/*  Copyright (C) 2021-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.deviceevents;
+
+import nodomain.freeyourgadget.gadgetbridge.model.GenericItem;
+import nodomain.freeyourgadget.gadgetbridge.model.ItemWithDetails;
+
+public class GBDeviceEventUpdateDeviceInfo extends GBDeviceEvent {
+    public ItemWithDetails item;
+
+    public GBDeviceEventUpdateDeviceInfo(final ItemWithDetails item) {
+        this.item = item;
+    }
+
+    public GBDeviceEventUpdateDeviceInfo(final String name, final String details) {
+        this(new GenericItem(name, details));
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventUpdateDeviceState.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventUpdateDeviceState.java
new file mode 100644
index 0000000..572d556
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventUpdateDeviceState.java
@@ -0,0 +1,27 @@
+/*  Copyright (C) 2021-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.deviceevents;
+
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class GBDeviceEventUpdateDeviceState extends GBDeviceEvent {
+    public GBDevice.State state;
+
+    public GBDeviceEventUpdateDeviceState(final GBDevice.State state) {
+        this.state = state;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventUpdatePreferences.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventUpdatePreferences.java
new file mode 100644
index 0000000..8691c30
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventUpdatePreferences.java
@@ -0,0 +1,94 @@
+/*  Copyright (C) 2021-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.deviceevents;
+
+import android.content.SharedPreferences;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+public class GBDeviceEventUpdatePreferences extends GBDeviceEvent {
+    private static final Logger LOG = LoggerFactory.getLogger(GBDeviceEventUpdatePreferences.class);
+
+    public final Map<String, Object> preferences;
+
+    public GBDeviceEventUpdatePreferences() {
+        this.preferences = new HashMap<>();
+    }
+
+    public GBDeviceEventUpdatePreferences(final Map<String, Object> preferences) {
+        this.preferences = preferences;
+    }
+
+    public GBDeviceEventUpdatePreferences(final String key, final Object value) {
+        this.preferences = new HashMap<>();
+        this.preferences.put(key, value);
+    }
+
+    public GBDeviceEventUpdatePreferences withPreference(final String key, final Object value) {
+        this.preferences.put(key, value);
+
+        return this;
+    }
+
+    public GBDeviceEventUpdatePreferences withPreferences(final Map<String, Object> preferences) {
+        this.preferences.putAll(preferences);
+
+        return this;
+    }
+
+    /**
+     * Update a {@link SharedPreferences} instance with the preferences in the event.
+     *
+     * @param prefs the SharedPreferences object to update.
+     */
+    public void update(final SharedPreferences prefs) {
+        final SharedPreferences.Editor editor = prefs.edit();
+
+        for (String key : preferences.keySet()) {
+            final Object value = preferences.get(key);
+
+            LOG.debug("Updating {} = {}", key, value);
+
+            if (value == null) {
+                editor.remove(key);
+            } else if (value instanceof Short) {
+                editor.putInt(key, (Short) value);
+            } else if (value instanceof Integer) {
+                editor.putInt(key, (Integer) value);
+            } else if (value instanceof Boolean) {
+                editor.putBoolean(key, (Boolean) value);
+            } else if (value instanceof String) {
+                editor.putString(key, (String) value);
+            } else if (value instanceof Float) {
+                editor.putFloat(key, (Float) value);
+            } else if (value instanceof Long) {
+                editor.putLong(key, (Long) value);
+            } else if (value instanceof Set) {
+                editor.putStringSet(key, (Set) value);
+            } else {
+                LOG.warn("Unknown preference value type {} for {}", value.getClass(), key);
+            }
+        }
+
+        editor.apply();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventVersionInfo.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventVersionInfo.java
new file mode 100644
index 0000000..0f60765
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventVersionInfo.java
@@ -0,0 +1,39 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, Carsten Pfeiffer, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.deviceevents;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+
+public class GBDeviceEventVersionInfo extends GBDeviceEvent {
+    public String fwVersion = "N/A";
+    public String fwVersion2 = null;
+    public String hwVersion = "N/A";
+
+    public GBDeviceEventVersionInfo() {
+        if (GBApplication.getContext() != null) {
+            // Only get from context if there is one (eg. not in unit tests)
+            this.fwVersion = GBApplication.getContext().getString(R.string.n_a);
+            this.hwVersion = GBApplication.getContext().getString(R.string.n_a);
+        }
+    }
+
+    @Override
+    public String toString() {
+        return super.toString() + "fwVersion: " + fwVersion + "; fwVersion2: " + fwVersion2 + "; hwVersion: " + hwVersion;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventWearState.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventWearState.java
new file mode 100644
index 0000000..2c2e992
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventWearState.java
@@ -0,0 +1,30 @@
+/*  Copyright (C) 2023-2024 Yoran Vulker
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.deviceevents;
+
+import java.util.Locale;
+
+import nodomain.freeyourgadget.gadgetbridge.model.WearingState;
+
+public class GBDeviceEventWearState extends GBDeviceEvent {
+    public WearingState wearingState = WearingState.UNKNOWN;
+
+    @Override
+    public String toString() {
+        return super.toString() + String.format(Locale.ROOT, "wearingState=%s", wearingState);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceMusicData.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceMusicData.java
new file mode 100644
index 0000000..9f7d268
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceMusicData.java
@@ -0,0 +1,15 @@
+package nodomain.freeyourgadget.gadgetbridge.deviceevents;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceMusic;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceMusicPlaylist;
+
+public class GBDeviceMusicData extends GBDeviceEvent {
+    public int type = 0; // 1 - sync start, 2 - music list, 10 - end sync
+    public List<GBDeviceMusic> list = null;
+    public List<GBDeviceMusicPlaylist> playlists = null;
+    public String deviceInfo = null;
+    public int maxMusicCount = 0;
+    public int maxPlaylistCount = 0;
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceMusicUpdate.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceMusicUpdate.java
new file mode 100644
index 0000000..000b92a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceMusicUpdate.java
@@ -0,0 +1,11 @@
+package nodomain.freeyourgadget.gadgetbridge.deviceevents;
+
+import java.util.ArrayList;
+
+public class GBDeviceMusicUpdate extends GBDeviceEvent {
+    public boolean success = false;
+    public int operation = -1;
+    public int playlistIndex = -1;
+    public String playlistName;
+    public ArrayList<Integer> musicIds = null;
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/pebble/GBDeviceEventDataLogging.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/pebble/GBDeviceEventDataLogging.java
new file mode 100644
index 0000000..0889150
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/pebble/GBDeviceEventDataLogging.java
@@ -0,0 +1,33 @@
+/*  Copyright (C) 2017-2024 Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.deviceevents.pebble;
+
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
+
+public class GBDeviceEventDataLogging extends GBDeviceEvent {
+    public static final int COMMAND_RECEIVE_DATA = 1;
+    public static final int COMMAND_FINISH_SESSION = 2;
+
+    public int command;
+    public UUID appUUID;
+    public long timestamp;
+    public long tag;
+    public byte pebbleDataType;
+    public Object[] data;
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractBLClassicDeviceCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractBLClassicDeviceCoordinator.java
new file mode 100644
index 0000000..64cda98
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractBLClassicDeviceCoordinator.java
@@ -0,0 +1,24 @@
+/*  Copyright (C) 2022-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices;
+
+public abstract class AbstractBLClassicDeviceCoordinator extends AbstractDeviceCoordinator {
+    @Override
+    public ConnectionType getConnectionType() {
+        return ConnectionType.BT_CLASSIC;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractBLEDeviceCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractBLEDeviceCoordinator.java
new file mode 100644
index 0000000..565837c
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractBLEDeviceCoordinator.java
@@ -0,0 +1,24 @@
+/*  Copyright (C) 2022-2024 Daniel Dakhno, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices;
+
+public abstract class AbstractBLEDeviceCoordinator extends AbstractDeviceCoordinator {
+    @Override
+    public ConnectionType getConnectionType() {
+        return ConnectionType.BLE;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractDeviceCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractDeviceCoordinator.java
new file mode 100644
index 0000000..eff63dc
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractDeviceCoordinator.java
@@ -0,0 +1,912 @@
+/*  Copyright (C) 2015-2024 akasaka / Genjitsu Labs, Alicia Hormann, Andreas
+    Shimokawa, Arjan Schrijver, Carsten Pfeiffer, Daniel Dakhno, Daniele Gobbetti,
+    Davis Mosenkovs, Dmitry Markin, Jos茅 Rebelo, Matthieu Baerts, Nephiel,
+    Petr Van臎k, Taavi Eom盲e, Johannes Krude
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices;
+
+import static nodomain.freeyourgadget.gadgetbridge.GBApplication.getPrefs;
+
+import android.app.Activity;
+import android.bluetooth.BluetoothClass;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.le.ScanFilter;
+import android.content.Context;
+import android.net.Uri;
+
+import androidx.annotation.DrawableRes;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import org.apache.commons.lang3.ArrayUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+import de.greenrobot.dao.query.QueryBuilder;
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.GBException;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettings;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer;
+import nodomain.freeyourgadget.gadgetbridge.capabilities.HeartRateCapability;
+import nodomain.freeyourgadget.gadgetbridge.capabilities.password.PasswordCapabilityImpl;
+import nodomain.freeyourgadget.gadgetbridge.capabilities.widgets.WidgetManager;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.entities.AlarmDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.BatteryLevelDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.CyclingSample;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+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.ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryParser;
+import nodomain.freeyourgadget.gadgetbridge.model.BatteryConfig;
+import nodomain.freeyourgadget.gadgetbridge.model.BodyEnergySample;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.model.HeartRateSample;
+import nodomain.freeyourgadget.gadgetbridge.model.HrvSummarySample;
+import nodomain.freeyourgadget.gadgetbridge.model.HrvValueSample;
+import nodomain.freeyourgadget.gadgetbridge.model.PaiSample;
+import nodomain.freeyourgadget.gadgetbridge.model.RespiratoryRateSample;
+import nodomain.freeyourgadget.gadgetbridge.model.RestingMetabolicRateSample;
+import nodomain.freeyourgadget.gadgetbridge.model.SleepScoreSample;
+import nodomain.freeyourgadget.gadgetbridge.model.Spo2Sample;
+import nodomain.freeyourgadget.gadgetbridge.model.StressSample;
+import nodomain.freeyourgadget.gadgetbridge.model.TemperatureSample;
+import nodomain.freeyourgadget.gadgetbridge.model.Vo2MaxSample;
+import nodomain.freeyourgadget.gadgetbridge.model.WeightSample;
+import nodomain.freeyourgadget.gadgetbridge.service.ServiceDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.GBPrefs;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+public abstract class AbstractDeviceCoordinator implements DeviceCoordinator {
+    private static final Logger LOG = LoggerFactory.getLogger(AbstractDeviceCoordinator.class);
+
+    private Pattern supportedDeviceName = null;
+
+    /**
+     * This method should return a Regexp pattern that will matched against a found device
+     * to check whether this coordinator supports that device.
+     * If more sophisticated logic is needed to determine device support, the supports(GBDeviceCandidate)
+     * should be overridden.
+     *
+     * @return Pattern
+     */
+    protected Pattern getSupportedDeviceName() {
+        return null;
+    }
+
+    @Override
+    public boolean supports(GBDeviceCandidate candidate) {
+        if (supportedDeviceName == null) {
+            supportedDeviceName = getSupportedDeviceName();
+        }
+        if (supportedDeviceName == null) {
+            LOG.error("{} should either override getSupportedDeviceName or supports(GBDeviceCandidate)", getClass());
+            return false;
+        }
+
+        return supportedDeviceName.matcher(candidate.getName()).matches();
+    }
+
+    @Override
+    public ConnectionType getConnectionType() {
+        return ConnectionType.BOTH;
+    }
+
+    @Override
+    public boolean isConnectable(){
+        return true;
+    }
+
+    @NonNull
+    @Override
+    public Collection<? extends ScanFilter> createBLEScanFilters() {
+        return Collections.emptyList();
+    }
+
+    @Override
+    public GBDevice createDevice(GBDeviceCandidate candidate, DeviceType deviceType) {
+        GBDevice gbDevice = new GBDevice(candidate.getDevice().getAddress(), candidate.getName(), null, null, deviceType);
+        for (BatteryConfig batteryConfig : getBatteryConfig(gbDevice)) {
+            gbDevice.setBatteryIcon(batteryConfig.getBatteryIcon(), batteryConfig.getBatteryIndex());
+            gbDevice.setBatteryLabel(batteryConfig.getBatteryLabel(), batteryConfig.getBatteryIndex());
+        }
+        return gbDevice;
+    }
+
+    @Override
+    public final void deleteDevice(final GBDevice gbDevice) throws GBException {
+        LOG.info("will try to delete device: {}", gbDevice.getName());
+        if (gbDevice.isConnected() || gbDevice.isConnecting()) {
+            GBApplication.deviceService(gbDevice).disconnect();
+        }
+        Prefs prefs = getPrefs();
+
+        Set<String> lastDeviceAddresses = prefs.getStringSet(GBPrefs.LAST_DEVICE_ADDRESSES, Collections.emptySet());
+        if (lastDeviceAddresses.contains(gbDevice.getAddress())) {
+            LOG.debug("#1605 removing last device (one of last devices)");
+            lastDeviceAddresses = new HashSet<String>(lastDeviceAddresses);
+            lastDeviceAddresses.remove(gbDevice.getAddress());
+            prefs.getPreferences().edit().putStringSet(GBPrefs.LAST_DEVICE_ADDRESSES, lastDeviceAddresses).apply();
+        }
+
+        GBApplication.deleteDeviceSpecificSharedPrefs(gbDevice.getAddress());
+
+        try (DBHandler dbHandler = GBApplication.acquireDB()) {
+            DaoSession session = dbHandler.getDaoSession();
+            Device device = DBHelper.findDevice(gbDevice, session);
+            if (device != null) {
+                deleteDevice(gbDevice, device, session);
+                QueryBuilder<?> qb = session.getDeviceAttributesDao().queryBuilder();
+                qb.where(DeviceAttributesDao.Properties.DeviceId.eq(device.getId())).buildDelete().executeDeleteWithoutDetachingEntities();
+                QueryBuilder<?> batteryLevelQueryBuilder = session.getBatteryLevelDao().queryBuilder();
+                batteryLevelQueryBuilder.where(BatteryLevelDao.Properties.DeviceId.eq(device.getId())).buildDelete().executeDeleteWithoutDetachingEntities();
+                QueryBuilder<?> alarmDeviceQueryBuilder = session.getAlarmDao().queryBuilder();
+                alarmDeviceQueryBuilder.where(AlarmDao.Properties.DeviceId.eq(device.getId())).buildDelete().executeDeleteWithoutDetachingEntities();
+                session.getDeviceDao().delete(device);
+            } else {
+                LOG.info("device to delete not found in db: {}", gbDevice);
+            }
+        } catch (Exception e) {
+            throw new GBException("Error deleting device: " + e.getMessage(), e);
+        }
+    }
+
+    /**
+     * Hook for subclasses to perform device-specific deletion logic, e.g. db cleanup.
+     *
+     * @param gbDevice the GBDevice
+     * @param device   the corresponding database Device
+     * @param session  the session to use
+     * @throws GBException if there was an error deleting device-specific resources
+     */
+    protected abstract void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException;
+
+    @Override
+    public boolean allowFetchActivityData(GBDevice device) {
+        return device.isInitialized() && !device.isBusy() && supportsActivityDataFetching();
+    }
+
+    @Override
+    public SampleProvider<? extends ActivitySample> getSampleProvider(final GBDevice device, final DaoSession session) {
+        return null;
+    }
+
+    @Override
+    public TimeSampleProvider<? extends StressSample> getStressSampleProvider(GBDevice device, DaoSession session) {
+        return null;
+    }
+
+    @Override
+    public TimeSampleProvider<? extends BodyEnergySample> getBodyEnergySampleProvider(final GBDevice device, final DaoSession session) {
+        return null;
+    }
+
+    @Override
+    public TimeSampleProvider<? extends HrvSummarySample> getHrvSummarySampleProvider(GBDevice device, DaoSession session) {
+        return null;
+    }
+
+    @Override
+    public TimeSampleProvider<? extends HrvValueSample> getHrvValueSampleProvider(GBDevice device, DaoSession session) {
+        return null;
+    }
+
+    @Override
+    public Vo2MaxSampleProvider<? extends Vo2MaxSample> getVo2MaxSampleProvider(GBDevice device, DaoSession session) {
+        return null;
+    }
+
+    @Override
+    public int[] getStressRanges() {
+        // 0-39 = relaxed
+        // 40-59 = mild
+        // 60-79 = moderate
+        // 80-100 = high
+        return new int[]{0, 40, 60, 80};
+    }
+
+    @Override
+    public TimeSampleProvider<? extends TemperatureSample> getTemperatureSampleProvider(GBDevice device, DaoSession session) {
+        return null;
+    }
+
+    @Override
+    public TimeSampleProvider<? extends Spo2Sample> getSpo2SampleProvider(GBDevice device, DaoSession session) {
+        return null;
+    }
+
+    @Override
+    public TimeSampleProvider<CyclingSample> getCyclingSampleProvider(GBDevice device, DaoSession session) {
+        return null;
+    }
+
+    @Override
+    public TimeSampleProvider<? extends HeartRateSample> getHeartRateMaxSampleProvider(GBDevice device, DaoSession session) {
+        return null;
+    }
+
+    @Override
+    public TimeSampleProvider<? extends HeartRateSample> getHeartRateRestingSampleProvider(GBDevice device, DaoSession session) {
+        return null;
+    }
+
+    @Override
+    public TimeSampleProvider<? extends HeartRateSample> getHeartRateManualSampleProvider(GBDevice device, DaoSession session) {
+        return null;
+    }
+
+    @Override
+    public TimeSampleProvider<? extends PaiSample> getPaiSampleProvider(GBDevice device, DaoSession session) {
+        return null;
+    }
+
+    @Override
+    public TimeSampleProvider<? extends RespiratoryRateSample> getRespiratoryRateSampleProvider(GBDevice device, DaoSession session) {
+        return null;
+    }
+
+    @Override
+    public TimeSampleProvider<? extends WeightSample> getWeightSampleProvider(GBDevice device, DaoSession session) {
+        return null;
+    }
+
+    @Override
+    public TimeSampleProvider<? extends RestingMetabolicRateSample> getRestingMetabolicRateProvider(final GBDevice device, final DaoSession session) {
+        return new DefaultRestingMetabolicRateProvider(device, session);
+    }
+
+    @Override
+    public TimeSampleProvider<? extends SleepScoreSample> getSleepScoreProvider(final GBDevice device, final DaoSession session) {
+        return null;
+    }
+
+    @Override
+    @Nullable
+    public ActivitySummaryParser getActivitySummaryParser(final GBDevice device, final Context context) {
+        return null;
+    }
+
+    public boolean isHealthWearable(BluetoothDevice device) {
+        BluetoothClass bluetoothClass;
+        try {
+            bluetoothClass = device.getBluetoothClass();
+        } catch (SecurityException se) {
+            LOG.warn("missing bluetooth permission: ", se);
+            return false;
+        }
+        if (bluetoothClass == null) {
+            LOG.warn("unable to determine bluetooth device class of {}", device);
+            return false;
+        }
+        if (bluetoothClass.getMajorDeviceClass() == BluetoothClass.Device.Major.WEARABLE
+                || bluetoothClass.getMajorDeviceClass() == BluetoothClass.Device.Major.UNCATEGORIZED) {
+            int deviceClasses =
+                    BluetoothClass.Device.HEALTH_BLOOD_PRESSURE
+                            | BluetoothClass.Device.HEALTH_DATA_DISPLAY
+                            | BluetoothClass.Device.HEALTH_PULSE_RATE
+                            | BluetoothClass.Device.HEALTH_WEIGHING
+                            | BluetoothClass.Device.HEALTH_UNCATEGORIZED
+                            | BluetoothClass.Device.HEALTH_PULSE_OXIMETER
+                            | BluetoothClass.Device.HEALTH_GLUCOSE;
+
+            return (bluetoothClass.getDeviceClass() & deviceClasses) != 0;
+        }
+        return false;
+    }
+
+    @Override
+    public File getAppCacheDir() throws IOException {
+        return null;
+    }
+
+    @Override
+    public File getWritableExportDirectory(final GBDevice device) throws IOException {
+        File dir;
+        dir = new File(FileUtils.getExternalFilesDir() + File.separator + device.getAddress());
+        if (!dir.isDirectory()) {
+            if (!dir.mkdir()) {
+                throw new IOException("Cannot create device specific directory for " + device.getName());
+            }
+        }
+        return dir;
+    }
+
+    @Override
+    public String getAppCacheSortFilename() {
+        return null;
+    }
+
+    @Override
+    public String getAppFileExtension() {
+        return null;
+    }
+
+    @Override
+    public boolean supportsAppListFetching() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsFlashing() {
+        return false;
+    }
+
+    @Nullable
+    @Override
+    public InstallHandler findInstallHandler(final Uri uri, final Context context) {
+        return null;
+    }
+
+    @Override
+    public boolean supportsScreenshots(final GBDevice device) {
+        return false;
+    }
+
+    @Override
+    public int getAlarmSlotCount(final GBDevice device) {
+        return 0;
+    }
+
+    @Override
+    public boolean supportsSmartWakeup(GBDevice device, int alarmPosition) {
+        return false;
+    }
+
+    @Override
+    public boolean supportsSmartWakeupInterval(GBDevice device, int alarmPosition) {
+        return false;
+    }
+
+    @Override
+    public boolean forcedSmartWakeup(GBDevice device, int alarmPosition) {
+        return false;
+    }
+
+    @Override
+    public boolean supportsAppReordering() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsAppsManagement(final GBDevice device) {
+        return false;
+    }
+
+    @Override
+    public boolean supportsCachedAppManagement(final GBDevice device) {
+        try {
+            return supportsAppsManagement(device) && getAppCacheDir() != null;
+        } catch (final Exception e) {
+            // we failed, but still tried, so it's supported..
+            LOG.error("Failed to get app cache dir", e);
+            return true;
+        }
+    }
+
+    @Override
+    public boolean supportsInstalledAppManagement(final GBDevice device) {
+        return supportsAppsManagement(device);
+    }
+
+    @Override
+    public boolean supportsWatchfaceManagement(final GBDevice device) {
+        return supportsAppsManagement(device);
+    }
+
+    @Nullable
+    @Override
+    public Class<? extends Activity> getAppsManagementActivity() {
+        return null;
+    }
+
+    @Nullable
+    @Override
+    public Class<? extends Activity> getWatchfaceDesignerActivity() {
+        return null;
+    }
+
+    @Override
+    public int getBondingStyle() {
+        return BONDING_STYLE_ASK;
+    }
+
+    @Override
+    public boolean suggestUnbindBeforePair() {
+        return true;
+    }
+
+    @Override
+    public boolean isExperimental() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsCalendarEvents() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsActivityDataFetching() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsActivityTracking() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsActivityTracks() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsStressMeasurement() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsBodyEnergy() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsHrvMeasurement() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsVO2Max() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsVO2MaxCycling() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsVO2MaxRunning() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsActiveCalories() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsActivityTabs() {
+        return supportsActivityTracking();
+    }
+    @Override
+    public boolean supportsSleepMeasurement() {
+        return supportsActivityTracking();
+    }
+    @Override
+    public boolean supportsStepCounter() {
+        return supportsActivityTracking();
+    }
+    @Override
+    public boolean supportsSpeedzones() {
+        return supportsActivityTracking();
+    }
+
+    @Override
+    public boolean supportsTemperatureMeasurement() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsContinuousTemperature() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsSpo2(final GBDevice device) {
+        return false;
+    }
+
+    @Override
+    public boolean supportsHeartRateStats() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsPai() {
+        return false;
+    }
+
+    @Override
+    public int getPaiName() {
+        return R.string.menuitem_pai;
+    }
+
+    @Override
+    public boolean supportsPaiTime() {
+        return supportsPai();
+    }
+
+    @Override
+    public boolean supportsPaiLow() {
+        return supportsPai();
+    }
+
+    @Override
+    public int getPaiTarget() {
+        return 100;
+    }
+
+    @Override
+    public boolean supportsRespiratoryRate() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsDayRespiratoryRate() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsSleepRespiratoryRate() {
+        return supportsRespiratoryRate();
+    }
+
+    @Override
+    public boolean supportsWeightMeasurement() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsAlarmSnoozing() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsAlarmTitle(GBDevice device) {
+        return false;
+    }
+
+    @Override
+    public int getAlarmTitleLimit(GBDevice device) {
+        return -1;
+    }
+
+    @Override
+    public boolean supportsAlarmDescription(GBDevice device) {
+        return false;
+    }
+
+    @Override
+    public boolean supportsMusicInfo() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsLedColor() {
+        return false;
+    }
+
+    @Override
+    public int getMaximumReminderMessageLength() {
+        return 0;
+    }
+
+    @Override
+    public int getReminderSlotCount(final GBDevice device) {
+        return 0;
+    }
+
+    @Override
+    public boolean getRemindersHaveTime() {
+        return true;
+    }
+
+    @Override
+    public boolean getReserveReminderSlotsForCalendar() {
+        return false;
+    }
+
+    @Override
+    public int getCannedRepliesSlotCount(final GBDevice device) {
+        return 0;
+    }
+
+    @Override
+    public int getWorldClocksSlotCount() {
+        return 0;
+    }
+
+    @Override
+    public int getWorldClocksLabelLength() {
+        return 10;
+    }
+
+    @Override
+    public boolean supportsDisabledWorldClocks() {
+        return false;
+    }
+
+    @Override
+    public int getContactsSlotCount(final GBDevice device) {
+        return 0;
+    }
+
+    @Override
+    public boolean supportsRgbLedColor() {
+        return false;
+    }
+
+    @NonNull
+    @Override
+    public int[] getColorPresets() {
+        return new int[0];
+    }
+
+    @Override
+    public boolean supportsHeartRateMeasurement(final GBDevice device) {
+        return false;
+    }
+
+    @Override
+    public boolean supportsHeartRateRestingMeasurement(final GBDevice device) {
+        return false;
+    }
+
+    @Override
+    public boolean supportsManualHeartRateMeasurement(final GBDevice device) {
+        return supportsHeartRateMeasurement(device);
+    }
+
+    @Override
+    public boolean supportsRealtimeData() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsCyclingData() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsRemSleep() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsAwakeSleep() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsSleepScore() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsWeather() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsFindDevice() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsUnicodeEmojis() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsSleepAsAndroid() {
+        return false;
+    }
+
+    @Override
+    public Set<SleepAsAndroidFeature> getSleepAsAndroidFeatures() {
+        return Collections.emptySet();
+    }
+
+    @Override
+    public int[] getSupportedDeviceSpecificConnectionSettings() {
+        int[] settings = new int[0];
+        ConnectionType connectionType = getConnectionType();
+
+        if (connectionType.usesBluetoothLE()) {
+            settings = ArrayUtils.insert(0, settings, R.xml.devicesettings_reconnect_ble);
+        }
+        if (connectionType.usesBluetoothClassic()) {
+            settings = ArrayUtils.insert(0, settings, R.xml.devicesettings_reconnect_bl_classic);
+        }
+
+        return settings;
+    }
+
+    @Override
+    public int[] getSupportedDeviceSpecificSettings(GBDevice device) {
+        return new int[0];
+    }
+
+    @Override
+    public DeviceSpecificSettings getDeviceSpecificSettings(GBDevice device) {
+        final int[] settings = getSupportedDeviceSpecificSettings(device);
+        if (settings == null || settings.length == 0) {
+            return null;
+        }
+
+        return new DeviceSpecificSettings(settings);
+    }
+
+    @Override
+    public int[] getSupportedDeviceSpecificAuthenticationSettings() {
+        return new int[0];
+    }
+
+    @Override
+    public DeviceSpecificSettingsCustomizer getDeviceSpecificSettingsCustomizer(GBDevice device) {
+        return null;
+    }
+
+    @Override
+    public String[] getSupportedLanguageSettings(GBDevice device) {
+        return null;
+    }
+
+    @Nullable
+    @Override
+    public Class<? extends Activity> getPairingActivity() {
+        return null;
+    }
+
+    @Nullable
+    @Override
+    public Class<? extends Activity> getCalibrationActivity() {
+        return null;
+    }
+
+    @Override
+    public int getBatteryCount() {
+        return 1;
+    } //multiple battery support, default is 1, maximum is 3, 0 will disable the battery in UI
+
+    @Override
+    public BatteryConfig[] getBatteryConfig(final GBDevice device) {
+        final BatteryConfig[] batteryConfigs = new BatteryConfig[getBatteryCount()];
+        for (int i = 0; i < getBatteryCount(); i++) {
+            batteryConfigs[i] = new BatteryConfig(i);
+        }
+        return batteryConfigs;
+    }
+
+    @Override
+    public boolean addBatteryPollingSettings() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsPowerOff() {
+        return false;
+    }
+
+    @Override
+    public PasswordCapabilityImpl.Mode getPasswordCapability() {
+        return PasswordCapabilityImpl.Mode.NONE;
+    }
+
+    @Override
+    public List<HeartRateCapability.MeasurementInterval> getHeartRateMeasurementIntervals() {
+        return Arrays.asList(
+                HeartRateCapability.MeasurementInterval.OFF,
+                HeartRateCapability.MeasurementInterval.MINUTES_1,
+                HeartRateCapability.MeasurementInterval.MINUTES_5,
+                HeartRateCapability.MeasurementInterval.MINUTES_10,
+                HeartRateCapability.MeasurementInterval.MINUTES_30,
+                HeartRateCapability.MeasurementInterval.HOUR_1
+        );
+    }
+
+    @Override
+    public boolean supportsWidgets(final GBDevice device) {
+        return false;
+    }
+
+    @Nullable
+    @Override
+    public WidgetManager getWidgetManager(final GBDevice device) {
+        return null;
+    }
+
+    public boolean supportsNavigation() {
+        return false;
+    }
+
+    @Override
+    public int getOrderPriority() {
+        return 0;
+    }
+
+    @Override
+    public EnumSet<ServiceDeviceSupport.Flags> getInitialFlags() {
+        return EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING);
+    }
+
+    @Override
+    @DrawableRes
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_default;
+    }
+
+    @Override
+    @DrawableRes
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_default_disabled;
+    }
+
+    @Override
+    public boolean supportsNotificationVibrationPatterns() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsNotificationVibrationRepetitionPatterns() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsNotificationLedPatterns() {
+        return false;
+    }
+
+    @Override
+    public AbstractNotificationPattern[] getNotificationVibrationPatterns() {
+        return new AbstractNotificationPattern[0];
+    }
+
+    @Override
+    public AbstractNotificationPattern[] getNotificationVibrationRepetitionPatterns() {
+        return new AbstractNotificationPattern[0];
+    }
+
+    @Override
+    public AbstractNotificationPattern[] getNotificationLedPatterns() {
+        return new AbstractNotificationPattern[0];
+    }
+
+    @Override
+    public boolean validateAuthKey(final String authKey) {
+        return !(authKey.getBytes().length < 34 || !authKey.startsWith("0x"));
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractSampleProvider.java
new file mode 100644
index 0000000..b226fa9
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractSampleProvider.java
@@ -0,0 +1,399 @@
+/*  Copyright (C) 2016-2024 Andreas Shimokawa, Carsten Pfeiffer, Daniel
+    Dakhno, Daniele Gobbetti, Jos茅 Rebelo, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.time.Instant;
+import java.time.LocalDate;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Set;
+
+import de.greenrobot.dao.AbstractDao;
+import de.greenrobot.dao.Property;
+import de.greenrobot.dao.query.QueryBuilder;
+import de.greenrobot.dao.query.WhereCondition;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.entities.AbstractActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
+
+/**
+ * Base class for all sample providers. A Sample provider is device specific and provides
+ * access to the device specific samples. There are both read and write operations.
+ * @param <T> the sample type
+ */
+public abstract class AbstractSampleProvider<T extends AbstractActivitySample> implements SampleProvider<T> {
+    private static final Logger LOG = LoggerFactory.getLogger(AbstractSampleProvider.class);
+
+    private static final WhereCondition[] NO_CONDITIONS = new WhereCondition[0];
+    private final DaoSession mSession;
+    private final GBDevice mDevice;
+
+    protected AbstractSampleProvider(GBDevice device, DaoSession session) {
+        mDevice = device;
+        mSession = session;
+    }
+
+    public GBDevice getDevice() {
+        return mDevice;
+    }
+
+    public DaoSession getSession() {
+        return mSession;
+    }
+
+    @NonNull
+    @Override
+    public List<T> getAllActivitySamples(int timestamp_from, int timestamp_to) {
+        return getGBActivitySamples(timestamp_from, timestamp_to);
+    }
+
+    @NonNull
+    @Override
+    public List<T> getAllActivitySamplesHighRes(int timestamp_from, int timestamp_to) {
+        return getGBActivitySamplesHighRes(timestamp_from, timestamp_to);
+    }
+
+    @Override
+    public boolean hasHighResData() {
+        return false;
+    }
+
+    @NonNull
+    @Override
+    @Deprecated // use getAllActivitySamples
+    public List<T> getActivitySamples(int timestamp_from, int timestamp_to) {
+        if (getRawKindSampleProperty() != null) {
+            return getGBActivitySamples(timestamp_from, timestamp_to);
+        } else {
+            return getActivitySamplesByActivityFilter(timestamp_from, timestamp_to, Collections.singleton(ActivityKind.ACTIVITY));
+        }
+    }
+
+    @Override
+    public void addGBActivitySample(T activitySample) {
+        getSampleDao().insertOrReplace(activitySample);
+    }
+
+    @Override
+    public void addGBActivitySamples(T[] activitySamples) {
+        getSampleDao().insertOrReplaceInTx(activitySamples);
+    }
+
+    @Nullable
+    @Override
+    public T getLatestActivitySample() {
+        QueryBuilder<T> qb = getSampleDao().queryBuilder();
+        Device dbDevice = DBHelper.findDevice(getDevice(), getSession());
+        if (dbDevice == null) {
+            // no device, no sample
+            return null;
+        }
+        Property deviceProperty = getDeviceIdentifierSampleProperty();
+        qb.where(deviceProperty.eq(dbDevice.getId())).orderDesc(getTimestampSampleProperty()).limit(1);
+        List<T> samples = qb.build().list();
+        if (samples.isEmpty()) {
+            return null;
+        }
+        T sample = samples.get(0);
+        sample.setProvider(this);
+        return sample;
+    }
+
+    @Nullable
+    @Override
+    public T getLatestActivitySample(final int until) {
+        QueryBuilder<T> qb = getSampleDao().queryBuilder();
+        Device dbDevice = DBHelper.findDevice(getDevice(), getSession());
+        if (dbDevice == null) {
+            // no device, no sample
+            return null;
+        }
+        Property deviceProperty = getDeviceIdentifierSampleProperty();
+        Property timestampProperty = getTimestampSampleProperty();
+        qb.where(timestampProperty.le(until))
+                .where(deviceProperty.eq(dbDevice.getId()))
+                .orderDesc(timestampProperty).limit(1);
+        List<T> samples = qb.build().list();
+        if (samples.isEmpty()) {
+            return null;
+        }
+        T sample = samples.get(0);
+        sample.setProvider(this);
+        return sample;
+    }
+
+    @Nullable
+    @Override
+    public T getFirstActivitySample() {
+        QueryBuilder<T> qb = getSampleDao().queryBuilder();
+        Device dbDevice = DBHelper.findDevice(getDevice(), getSession());
+        if (dbDevice == null) {
+            // no device, no sample
+            return null;
+        }
+        Property deviceProperty = getDeviceIdentifierSampleProperty();
+        qb.where(deviceProperty.eq(dbDevice.getId())).orderAsc(getTimestampSampleProperty()).limit(1);
+        List<T> samples = qb.build().list();
+        if (samples.isEmpty()) {
+            return null;
+        }
+        T sample = samples.get(0);
+        sample.setProvider(this);
+        return sample;
+    }
+
+    /**
+     * Get the activity samples between two timestamps (inclusive). Exactly one every minute.
+     * @param timestamp_from Start timestamp
+     * @param timestamp_to End timestamp
+     * @return Exactly one sample for every minute
+     */
+    protected List<T> getGBActivitySamples(int timestamp_from, int timestamp_to) {
+        QueryBuilder<T> qb = getSampleDao().queryBuilder();
+        Property timestampProperty = getTimestampSampleProperty();
+        Device dbDevice = DBHelper.findDevice(getDevice(), getSession());
+        if (dbDevice == null) {
+            // no device, no samples
+            return Collections.emptyList();
+        }
+        Property deviceProperty = getDeviceIdentifierSampleProperty();
+        qb.where(deviceProperty.eq(dbDevice.getId()), timestampProperty.ge(timestamp_from))
+            .where(timestampProperty.le(timestamp_to));
+        List<T> samples = qb.build().list();
+        for (T sample : samples) {
+            sample.setProvider(this);
+        }
+        detachFromSession();
+        return samples;
+    }
+
+    /**
+     * Get the activity samples between two timestamps (inclusive).
+     * Differs from {@link #getGBActivitySamples(int, int)} in that it supplies as many samples as
+     * available.
+     * It assumes {@link #getGBActivitySamples(int, int)} returns the highest resolution data unless
+     * this is overwritten.
+     * @param timestamp_from Start timestamp
+     * @param timestamp_to End timestamp
+     * @return All the samples between start and end timestamp (inclusive)
+     */
+    protected List<T> getGBActivitySamplesHighRes(int timestamp_from, int timestamp_to) {
+        return getGBActivitySamples(timestamp_from, timestamp_to);
+    }
+
+    /**
+     * Detaches all samples of this type from the session. Changes to them may not be
+     * written back to the database.
+     * <p>
+     * Subclasses should call this method after performing custom queries.
+     */
+    protected void detachFromSession() {
+        getSampleDao().detachAll();
+    }
+
+    private List<T> getActivitySamplesByActivityFilter(int timestamp_from, int timestamp_to, Set<ActivityKind> activityFilter) {
+        List<T> samples = getAllActivitySamples(timestamp_from, timestamp_to);
+        List<T> filteredSamples = new ArrayList<>();
+
+        for (T sample : samples) {
+            if (activityFilter.contains(sample.getKind())) {
+                filteredSamples.add(sample);
+            }
+        }
+        return filteredSamples;
+    }
+
+    public abstract AbstractDao<T,?> getSampleDao();
+
+    @Nullable
+    protected abstract Property getRawKindSampleProperty();
+
+    @NonNull
+    protected abstract Property getTimestampSampleProperty();
+
+    @NonNull
+    protected abstract Property getDeviceIdentifierSampleProperty();
+
+    public void convertCumulativeSteps(final List<T> samples, final Property stepsSampleProperty) {
+        // Fix over-counting at the turn of day
+        final T lastSample = getLastSampleWithStepsBefore(samples.get(0).getTimestamp(), stepsSampleProperty);
+        if (lastSample != null && sameDay(lastSample, samples.get(0))) {
+            if (samples.get(0).getSteps() > 0) {
+                samples.get(0).setSteps(samples.get(0).getSteps() - lastSample.getSteps());
+            }
+
+            if (samples.get(0).getDistanceCm() > 0) {
+                samples.get(0).setDistanceCm(samples.get(0).getDistanceCm() - lastSample.getDistanceCm());
+            }
+
+            if (samples.get(0).getActiveCalories() > 0) {
+                samples.get(0).setActiveCalories(samples.get(0).getActiveCalories() - lastSample.getActiveCalories());
+            }
+        }
+
+        // This slightly breaks activity recognition, because we don't have per-minute granularity...
+        int prevSteps = samples.get(0).getSteps();
+        int prevDistance = samples.get(0).getDistanceCm();
+        int prevActiveCalories = samples.get(0).getActiveCalories();
+        samples.get(0).setTimestamp((samples.get(0).getTimestamp() / 60) * 60);
+        int bak;
+
+        for (int i = 1; i < samples.size(); i++) {
+            final T s1 = samples.get(i - 1);
+            final T s2 = samples.get(i);
+            s2.setTimestamp((s2.getTimestamp() / 60) * 60);
+
+            if (!sameDay(s1, s2)) {
+                // went past midnight - reset steps
+                prevSteps = s2.getSteps() > 0 ? s2.getSteps() : 0;
+                prevDistance = s2.getDistanceCm() > 0 ? s2.getDistanceCm() : 0;
+                prevActiveCalories = s2.getActiveCalories() > 0 ? s2.getActiveCalories() : 0;
+            } else {
+                // New value for the current day - subtract the previous seen sample
+
+                if (s2.getSteps() > 0) {
+                    bak = s2.getSteps();
+                    s2.setSteps(s2.getSteps() - prevSteps);
+                    prevSteps = bak;
+                }
+                if (s2.getDistanceCm() > 0) {
+                    bak = s2.getDistanceCm();
+                    s2.setDistanceCm(s2.getDistanceCm() - prevDistance);
+                    prevDistance = bak;
+                }
+                if (s2.getActiveCalories() > 0) {
+                    bak = s2.getActiveCalories();
+                    s2.setActiveCalories(s2.getActiveCalories() - prevActiveCalories);
+                    prevActiveCalories = bak;
+                }
+            }
+        }
+    }
+
+    @Nullable
+    public T getLastSampleWithStepsBefore(final int timestampTo, final Property stepsSampleProperty) {
+        final Device dbDevice = DBHelper.findDevice(getDevice(), getSession());
+        if (dbDevice == null) {
+            // no device, no sample
+            return null;
+        }
+
+        final List<T> samples = getSampleDao().queryBuilder()
+                .where(
+                        getDeviceIdentifierSampleProperty().eq(dbDevice.getId()),
+                        getTimestampSampleProperty().le(timestampTo),
+                        stepsSampleProperty.gt(-1)
+                ).orderDesc(getTimestampSampleProperty())
+                .limit(1)
+                .list();
+
+        return !samples.isEmpty() ? samples.get(0) : null;
+    }
+
+    public boolean sameDay(final T s1, final T s2) {
+        final Calendar cal = Calendar.getInstance();
+
+        cal.setTimeInMillis(s1.getTimestamp() * 1000L - 1000L);
+        final LocalDate d1 = LocalDate.of(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH) + 1, cal.get(Calendar.DAY_OF_MONTH));
+
+        cal.setTimeInMillis(s2.getTimestamp() * 1000L - 1000L);
+        final LocalDate d2 = LocalDate.of(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH) + 1, cal.get(Calendar.DAY_OF_MONTH));
+
+        return d1.equals(d2);
+    }
+
+    protected List<T> fillGaps(final List<T> samples, final int timestamp_from, final int timestamp_to) {
+        if (samples.isEmpty()) {
+            return samples;
+        }
+
+        final long nanoStart = System.nanoTime();
+
+        final List<T> ret = new LinkedList<>(samples);
+
+        //ret.sort(Comparator.comparingLong(T::getTimestamp));
+
+        final int firstTimestamp = ret.get(0).getTimestamp();
+        if (firstTimestamp - timestamp_from > 60) {
+            // Gap at the start
+            for (int ts = timestamp_from; ts <= firstTimestamp + 60; ts += 60) {
+                ret.add(0, createDummySample(ts));
+            }
+        }
+
+        final int lastTimestamp = ret.get(ret.size() - 1).getTimestamp();
+        // Do not generate fake samples into the future
+        final long minTo = Math.min(timestamp_to, System.currentTimeMillis() / 1000L);
+        if (minTo - lastTimestamp > 60) {
+            // Gap at the end
+            for (int ts = lastTimestamp + 60; ts <= minTo; ts += 60) {
+                ret.add(createDummySample(ts));
+            }
+        }
+
+        final ListIterator<T> it = ret.listIterator();
+        T previousSample = it.next();
+
+        while (it.hasNext()) {
+            final T sample = it.next();
+            if (sample.getTimestamp() - previousSample.getTimestamp() > 60) {
+                LOG.trace("Filling gap between {} and {}", Instant.ofEpochSecond(previousSample.getTimestamp() + 60), Instant.ofEpochSecond(sample.getTimestamp()));
+                for (int ts = previousSample.getTimestamp() + 60; ts < sample.getTimestamp(); ts += 60) {
+                    it.add(createDummySample(ts));
+                }
+            }
+            previousSample = sample;
+        }
+
+        final long nanoEnd = System.nanoTime();
+
+        final long executionTime = (nanoEnd - nanoStart) / 1000000;
+
+        final int dummyCount = ret.size() - samples.size();
+        LOG.trace("Filled gaps with {} samples in {}ms", dummyCount, executionTime);
+
+        return ret;
+    }
+
+    private T createDummySample(final int ts) {
+        final T dummySample = createActivitySample();
+        dummySample.setTimestamp(ts);
+        dummySample.setRawKind(ActivityKind.UNKNOWN.getCode());
+        dummySample.setRawIntensity(ActivitySample.NOT_MEASURED);
+        dummySample.setSteps(ActivitySample.NOT_MEASURED);
+        dummySample.setHeartRate(ActivitySample.NOT_MEASURED);
+        dummySample.setDistanceCm(ActivitySample.NOT_MEASURED);
+        dummySample.setActiveCalories(ActivitySample.NOT_MEASURED);
+        dummySample.setProvider(this);
+        return dummySample;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractSampleToTimeSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractSampleToTimeSampleProvider.java
new file mode 100644
index 0000000..005d880
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractSampleToTimeSampleProvider.java
@@ -0,0 +1,104 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.entities.AbstractActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.TimeSample;
+
+/**
+ * Wraps a {@link SampleProvider} into a {@link TimeSampleProvider}.
+ */
+public abstract class AbstractSampleToTimeSampleProvider<T extends TimeSample, S extends AbstractActivitySample> implements TimeSampleProvider<T> {
+    private final SampleProvider<S> mSampleProvider;
+    private final DaoSession mSession;
+    private final GBDevice mDevice;
+
+    protected AbstractSampleToTimeSampleProvider(final SampleProvider<S> sampleProvider, final GBDevice device, final DaoSession session) {
+        mSampleProvider = sampleProvider;
+        mDevice = device;
+        mSession = session;
+    }
+
+    @Nullable
+    protected abstract T convertSample(final S sample);
+
+    public GBDevice getDevice() {
+        return mDevice;
+    }
+
+    public DaoSession getSession() {
+        return mSession;
+    }
+
+    @NonNull
+    @Override
+    public List<T> getAllSamples(final long timestampFrom, final long timestampTo) {
+        final List<S> upstreamSamples = mSampleProvider.getAllActivitySamples((int) (timestampFrom / 1000L), (int) (timestampTo / 1000L));
+        final List<T> ret = new ArrayList<>();
+        for (final S sample : upstreamSamples) {
+            final T converted = convertSample(sample);
+            if (converted != null) {
+                ret.add(converted);
+            }
+        }
+        return ret;
+    }
+
+    @Override
+    public void addSample(final T timeSample) {
+        throw new UnsupportedOperationException("This sample provider is read-only!");
+    }
+
+    @Override
+    public void addSamples(final List<T> timeSamples) {
+        throw new UnsupportedOperationException("This sample provider is read-only!");
+    }
+
+    @Override
+    public T createSample() {
+        throw new UnsupportedOperationException("This sample provider is read-only!");
+    }
+
+    @Nullable
+    @Override
+    public T getLatestSample() {
+        final S latestSample = mSampleProvider.getLatestActivitySample();
+        return convertSample(latestSample);
+    }
+
+    @Nullable
+    @Override
+    public T getLatestSample(final long until) {
+        final S latestSample = mSampleProvider.getLatestActivitySample((int) (until / 1000L));
+        return convertSample(latestSample);
+    }
+
+    @Nullable
+    @Override
+    public T getFirstSample() {
+        final S firstSample = mSampleProvider.getFirstActivitySample();
+        return convertSample(firstSample);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractTimeSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractTimeSampleProvider.java
new file mode 100644
index 0000000..c4ea621
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractTimeSampleProvider.java
@@ -0,0 +1,199 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.Collections;
+import java.util.List;
+
+import de.greenrobot.dao.AbstractDao;
+import de.greenrobot.dao.Property;
+import de.greenrobot.dao.query.QueryBuilder;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.entities.AbstractTimeSample;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+/**
+ * Base class for all time sample providers. A Sample provider is device specific and provides
+ * access to the device specific samples. There are both read and write operations.
+ *
+ * @param <T> the sample type
+ */
+public abstract class AbstractTimeSampleProvider<T extends AbstractTimeSample> implements TimeSampleProvider<T> {
+    private final DaoSession mSession;
+    private final GBDevice mDevice;
+
+    protected AbstractTimeSampleProvider(final GBDevice device, final DaoSession session) {
+        mDevice = device;
+        mSession = session;
+    }
+
+    public GBDevice getDevice() {
+        return mDevice;
+    }
+
+    public DaoSession getSession() {
+        return mSession;
+    }
+
+    @NonNull
+    @Override
+    public List<T> getAllSamples(final long timestampFrom, final long timestampTo) {
+        final QueryBuilder<T> qb = getSampleDao().queryBuilder();
+        final Property timestampProperty = getTimestampSampleProperty();
+        final Device dbDevice = DBHelper.findDevice(getDevice(), getSession());
+        if (dbDevice == null) {
+            // no device, no samples
+            return Collections.emptyList();
+        }
+        final Property deviceProperty = getDeviceIdentifierSampleProperty();
+        qb.where(deviceProperty.eq(dbDevice.getId()), timestampProperty.ge(timestampFrom))
+                .where(timestampProperty.le(timestampTo));
+        final List<T> samples = qb.build().list();
+        detachFromSession();
+        return samples;
+    }
+
+    @Override
+    public void addSample(final T activitySample) {
+        getSampleDao().insertOrReplace(activitySample);
+    }
+
+    @Override
+    public void addSamples(final List<T> activitySamples) {
+        getSampleDao().insertOrReplaceInTx(activitySamples);
+    }
+
+    @Nullable
+    @Override
+    public T getLatestSample() {
+        final QueryBuilder<T> qb = getSampleDao().queryBuilder();
+        final Device dbDevice = DBHelper.findDevice(getDevice(), getSession());
+        if (dbDevice == null) {
+            // no device, no sample
+            return null;
+        }
+        final Property deviceProperty = getDeviceIdentifierSampleProperty();
+        qb.where(deviceProperty.eq(dbDevice.getId())).orderDesc(getTimestampSampleProperty()).limit(1);
+        final List<T> samples = qb.build().list();
+        if (samples.isEmpty()) {
+            return null;
+        }
+        return samples.get(0);
+    }
+
+    @Nullable
+    @Override
+    public T getLatestSample(final long until) {
+        final QueryBuilder<T> qb = getSampleDao().queryBuilder();
+        final Device dbDevice = DBHelper.findDevice(getDevice(), getSession());
+        if (dbDevice == null) {
+            // no device, no sample
+            return null;
+        }
+        final Property deviceProperty = getDeviceIdentifierSampleProperty();
+        qb.where(getTimestampSampleProperty().le(until))
+                .where(deviceProperty.eq(dbDevice.getId()))
+                .orderDesc(getTimestampSampleProperty()).limit(1);
+        final List<T> samples = qb.build().list();
+        if (samples.isEmpty()) {
+            return null;
+        }
+        return samples.get(0);
+    }
+
+    @Nullable
+    public T getLastSampleBefore(final long timestampTo) {
+        final Device dbDevice = DBHelper.findDevice(getDevice(), getSession());
+        if (dbDevice == null) {
+            // no device, no sample
+            return null;
+        }
+
+        final Property deviceIdSampleProp = getDeviceIdentifierSampleProperty();
+        final Property timestampSampleProp = getTimestampSampleProperty();
+        final List<T> samples = getSampleDao().queryBuilder()
+                .where(deviceIdSampleProp.eq(dbDevice.getId()),
+                        timestampSampleProp.le(timestampTo))
+                .orderDesc(getTimestampSampleProperty())
+                .limit(1)
+                .list();
+
+        return !samples.isEmpty() ? samples.get(0) : null;
+    }
+
+    @Nullable
+    public T getNextSampleAfter(final long timestampFrom) {
+        final Device dbDevice = DBHelper.findDevice(getDevice(), getSession());
+        if (dbDevice == null) {
+            // no device, no sample
+            return null;
+        }
+
+        final Property deviceIdSampleProp = getDeviceIdentifierSampleProperty();
+        final Property timestampSampleProp = getTimestampSampleProperty();
+        final List<T> samples = getSampleDao().queryBuilder()
+                .where(deviceIdSampleProp.eq(dbDevice.getId()),
+                        timestampSampleProp.ge(timestampFrom))
+                .orderAsc(getTimestampSampleProperty())
+                .limit(1)
+                .list();
+
+        return !samples.isEmpty() ? samples.get(0) : null;
+    }
+
+    @Nullable
+    @Override
+    public T getFirstSample() {
+        final QueryBuilder<T> qb = getSampleDao().queryBuilder();
+        final Device dbDevice = DBHelper.findDevice(getDevice(), getSession());
+        if (dbDevice == null) {
+            // no device, no sample
+            return null;
+        }
+        final Property deviceProperty = getDeviceIdentifierSampleProperty();
+        qb.where(deviceProperty.eq(dbDevice.getId())).orderAsc(getTimestampSampleProperty()).limit(1);
+        final List<T> samples = qb.build().list();
+        if (samples.isEmpty()) {
+            return null;
+        }
+        return samples.get(0);
+    }
+
+    /**
+     * Detaches all samples of this type from the session. Changes to them may not be
+     * written back to the database.
+     * <p>
+     * Subclasses should call this method after performing custom queries.
+     */
+    protected void detachFromSession() {
+        getSampleDao().detachAll();
+    }
+
+    @NonNull
+    public abstract AbstractDao<T, ?> getSampleDao();
+
+    @NonNull
+    protected abstract Property getTimestampSampleProperty();
+
+    @NonNull
+    protected abstract Property getDeviceIdentifierSampleProperty();
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/DefaultRestingMetabolicRateProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/DefaultRestingMetabolicRateProvider.java
new file mode 100644
index 0000000..756d786
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/DefaultRestingMetabolicRateProvider.java
@@ -0,0 +1,190 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.Collections;
+import java.util.List;
+
+import de.greenrobot.dao.AbstractDao;
+import de.greenrobot.dao.Property;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.entities.User;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
+import nodomain.freeyourgadget.gadgetbridge.model.RestingMetabolicRateSample;
+
+/**
+ * Provides a default resting metabolic rate, for devices that do not provide it. Currently it uses the
+ * Mifflin St Jeor equation.
+ * TODO: use the user data at that timestamp, and make the algorithm configurable.
+ * TODO: maybe let the user also configure their own static value
+ */
+public class DefaultRestingMetabolicRateProvider extends AbstractTimeSampleProvider<RestingMetabolicRateSample> {
+    private final DaoSession mSession;
+    private final GBDevice mDevice;
+
+    public DefaultRestingMetabolicRateProvider(final GBDevice device, final DaoSession session) {
+        super(device, session);
+        mDevice = device;
+        mSession = session;
+    }
+
+    public GBDevice getDevice() {
+        return mDevice;
+    }
+
+    public DaoSession getSession() {
+        return mSession;
+    }
+
+    @NonNull
+    @Override
+    public List<RestingMetabolicRateSample> getAllSamples(final long timestampFrom, final long timestampTo) {
+        return Collections.singletonList(getLatestSample());
+    }
+
+    @Override
+    public void addSample(final RestingMetabolicRateSample timeSample) {
+        throw new UnsupportedOperationException("addSample not supported");
+    }
+
+    @Override
+    public void addSamples(final List<RestingMetabolicRateSample> timeSamples) {
+        throw new UnsupportedOperationException("addSamples not supported");
+    }
+
+    @Override
+    public RestingMetabolicRateSample createSample() {
+        return new DefaultRestingMetabolicRateSample(System.currentTimeMillis());
+    }
+
+    @NonNull
+    @Override
+    public AbstractDao<RestingMetabolicRateSample, ?> getSampleDao() {
+        throw new UnsupportedOperationException("getSampleDao not supported");
+    }
+
+    @NonNull
+    @Override
+    protected Property getTimestampSampleProperty() {
+        throw new UnsupportedOperationException("getTimestampSampleProperty not supported");
+    }
+
+    @NonNull
+    @Override
+    protected Property getDeviceIdentifierSampleProperty() {
+        throw new UnsupportedOperationException("getDeviceIdentifierSampleProperty not supported");
+    }
+
+    @Nullable
+    @Override
+    public RestingMetabolicRateSample getLatestSample() {
+        return new DefaultRestingMetabolicRateSample(System.currentTimeMillis());
+    }
+
+    @Nullable
+    public RestingMetabolicRateSample getLastSampleBefore(final long timestampTo) {
+        return new DefaultRestingMetabolicRateSample(timestampTo);
+    }
+
+    @Nullable
+    public RestingMetabolicRateSample getNextSampleAfter(final long timestampFrom) {
+        return new DefaultRestingMetabolicRateSample(timestampFrom);
+    }
+
+    @Nullable
+    @Override
+    public RestingMetabolicRateSample getFirstSample() {
+        return getLatestSample();
+    }
+
+    public static class DefaultRestingMetabolicRateSample extends RestingMetabolicRateSample {
+        private long timestamp;
+        private final int rate;
+
+        public DefaultRestingMetabolicRateSample(final long timestamp) {
+            this.timestamp = timestamp;
+            ActivityUser activityUser = new ActivityUser();
+            final int weightKg = activityUser.getWeightKg();
+            final int heightCm = activityUser.getHeightCm();
+            final int age = activityUser.getAge();
+            final int s;
+            switch (activityUser.getGender()) {
+                case ActivityUser.GENDER_FEMALE:
+                    s = -161;
+                    break;
+                case ActivityUser.GENDER_MALE:
+                    s = 5;
+                    break;
+                default:
+                    s = (5 - 161) / 2; // average it?
+                    break;
+            }
+            // Mifflin St Jeor equation
+            this.rate = (int) Math.round(10.0d * weightKg + 6.25 * heightCm - 5d * age + s);
+        }
+
+        @Override
+        public int getRestingMetabolicRate() {
+            return rate;
+        }
+
+        @Override
+        public void setTimestamp(final long timestamp) {
+            this.timestamp = timestamp;
+        }
+
+        @Override
+        public long getUserId() {
+            return 0;
+        }
+
+        @Override
+        public void setUserId(final long userId) {
+
+        }
+
+        @Override
+        public long getDeviceId() {
+            return 0;
+        }
+
+        @Override
+        public void setDeviceId(final long deviceId) {
+
+        }
+
+        @Override
+        public void setDevice(final Device device) {
+
+        }
+
+        @Override
+        public void setUser(final User user) {
+
+        }
+
+        @Override
+        public long getTimestamp() {
+            return timestamp;
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/DeviceCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/DeviceCoordinator.java
new file mode 100644
index 0000000..5b1d7fe
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/DeviceCoordinator.java
@@ -0,0 +1,818 @@
+/*  Copyright (C) 2015-2024 akasaka / Genjitsu Labs, Alicia Hormann, Andreas
+    B枚hler, Andreas Shimokawa, Arjan Schrijver, Carsten Pfeiffer, Damien Gaignon,
+    Daniel Dakhno, Daniele Gobbetti, Dmitry Markin, JohnnySun, Jos茅 Rebelo,
+    Matthieu Baerts, Nephiel, Petr Van臎k, Uwe Hermann, Johannes Krude
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices;
+
+import android.app.Activity;
+import android.bluetooth.le.ScanFilter;
+import android.content.Context;
+import android.net.Uri;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Set;
+
+import androidx.annotation.DrawableRes;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.StringRes;
+
+import nodomain.freeyourgadget.gadgetbridge.GBException;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettings;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer;
+import nodomain.freeyourgadget.gadgetbridge.capabilities.HeartRateCapability;
+import nodomain.freeyourgadget.gadgetbridge.capabilities.password.PasswordCapabilityImpl;
+import nodomain.freeyourgadget.gadgetbridge.capabilities.widgets.WidgetManager;
+import nodomain.freeyourgadget.gadgetbridge.entities.CyclingSample;
+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;
+import nodomain.freeyourgadget.gadgetbridge.model.BodyEnergySample;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.model.HeartRateSample;
+import nodomain.freeyourgadget.gadgetbridge.model.HrvSummarySample;
+import nodomain.freeyourgadget.gadgetbridge.model.HrvValueSample;
+import nodomain.freeyourgadget.gadgetbridge.model.PaiSample;
+import nodomain.freeyourgadget.gadgetbridge.model.RespiratoryRateSample;
+import nodomain.freeyourgadget.gadgetbridge.model.RestingMetabolicRateSample;
+import nodomain.freeyourgadget.gadgetbridge.model.SleepScoreSample;
+import nodomain.freeyourgadget.gadgetbridge.model.Spo2Sample;
+import nodomain.freeyourgadget.gadgetbridge.model.StressSample;
+import nodomain.freeyourgadget.gadgetbridge.model.TemperatureSample;
+import nodomain.freeyourgadget.gadgetbridge.model.Vo2MaxSample;
+import nodomain.freeyourgadget.gadgetbridge.model.WeightSample;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.ServiceDeviceSupport;
+
+/**
+ * This interface is implemented at least once for every supported gadget device.
+ * It allows Gadgetbridge to generically deal with different kinds of devices
+ * without actually knowing the details of any device.
+ * <p/>
+ * Instances will be created as needed and asked whether they support a given
+ * device. If a coordinator answers true, it will be used to assist in handling
+ * the given device.
+ */
+public interface DeviceCoordinator {
+    String EXTRA_DEVICE_CANDIDATE = "nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate.EXTRA_DEVICE_CANDIDATE";
+    /**
+     * Do not attempt to bond after discovery.
+     */
+    int BONDING_STYLE_NONE = 0;
+    /**
+     * Bond after discovery.
+     * This is not recommended, as there are mobile devices on which bonding does not work.
+     * Prefer to use #BONDING_STYLE_ASK instead.
+     */
+    int BONDING_STYLE_BOND = 1;
+    /**
+     * Let the user decide whether to bond or not after discovery.
+     * Prefer this over #BONDING_STYLE_BOND
+     */
+    int BONDING_STYLE_ASK = 2;
+
+    /**
+     * A secret key has to be entered before connecting
+     */
+    int BONDING_STYLE_REQUIRE_KEY = 3;
+
+    /**
+     * Lazy pairing, i.e. device initiated pairing is requested
+     */
+    int BONDING_STYLE_LAZY = 4;
+
+    enum ConnectionType{
+        BLE(false, true),
+        BT_CLASSIC(true, false),
+        BOTH(true, true)
+        ;
+        boolean usesBluetoothClassic, usesBluetoothLE;
+
+        ConnectionType(boolean usesBluetoothClassic, boolean usesBluetoothLE) {
+            this.usesBluetoothClassic = usesBluetoothClassic;
+            this.usesBluetoothLE = usesBluetoothLE;
+        }
+
+        public boolean usesBluetoothLE(){
+            return usesBluetoothLE;
+        }
+
+        public boolean usesBluetoothClassic(){
+            return usesBluetoothClassic;
+        }
+    }
+
+    /**
+     * Returns the type of connection, Classic of BLE
+     *
+     * @return ConnectionType
+     */
+    ConnectionType getConnectionType();
+
+    /**
+     * Returns false is the Device is not connectable,
+     * only scannable, like beacons
+     *
+     * @return boolean
+     */
+    boolean isConnectable();
+
+    /**
+     * Checks whether this coordinator handles the given candidate.
+     *
+     * @param candidate
+     * @return true if this coordinator handles the given candidate.
+     */
+    boolean supports(GBDeviceCandidate candidate);
+
+    /**
+     * Returns a list of scan filters that shall be used to discover devices supported
+     * by this coordinator.
+     * @return the list of scan filters, may be empty
+     */
+    @NonNull
+    Collection<? extends ScanFilter> createBLEScanFilters();
+
+    GBDevice createDevice(GBDeviceCandidate candidate, DeviceType type);
+
+    /**
+     * Deletes all information, including all related database content about the
+     * given device.
+     * @throws GBException
+     */
+    void deleteDevice(GBDevice device) throws GBException;
+
+    /**
+     * Returns the Activity class to be started in order to perform a pairing of a
+     * given device after its discovery.
+     *
+     * @return the activity class for pairing/initial authentication, or null if none
+     */
+    @Nullable
+    Class<? extends Activity> getPairingActivity();
+
+    @Nullable
+    Class<? extends Activity> getCalibrationActivity();
+
+    /**
+     * Returns true if activity data fetching is supported by the device
+     * (with this coordinator).
+     * This enables the sync button in control center and the device can thus be asked to send the data
+     * (as opposed the device pushing the data to us by itself)
+     *
+     * @return
+     */
+    boolean supportsActivityDataFetching();
+
+    /**
+     * Returns true if activity tracking is supported by the device
+     * (with this coordinator).
+     * This enables the ActivityChartsActivity.
+     *
+     * @return
+     */
+    boolean supportsActivityTracking();
+
+    /**
+     * Returns true if cycling data is supported by the device
+     * (with this coordinator).
+     * This enables the ChartsActivity.
+     *
+     * @return
+     */
+    boolean supportsCyclingData();
+
+    /**
+     * Indicates whether the device supports recording dedicated activity tracks, like
+     * walking, hiking, running, swimming, etc. and retrieving the recorded
+     * data. This is different from the constant activity tracking since the tracks are
+     * usually recorded with additional features, like e.g. GPS.
+     */
+    boolean supportsActivityTracks();
+
+    /**
+     * Returns true if stress measurement and fetching is supported by the device
+     * (with this coordinator).
+     */
+    boolean supportsStressMeasurement();
+
+    boolean supportsBodyEnergy();
+    boolean supportsHrvMeasurement();
+    boolean supportsVO2Max();
+    boolean supportsVO2MaxCycling();
+    boolean supportsVO2MaxRunning();
+    boolean supportsSleepMeasurement();
+    boolean supportsStepCounter();
+    boolean supportsSpeedzones();
+    boolean supportsActivityTabs();
+    boolean supportsActiveCalories();
+
+    /**
+     * Returns true if measurement and fetching of body temperature is supported by the device
+     * (with this coordinator).
+     */
+    boolean supportsTemperatureMeasurement();
+
+    /**
+     * Returns true if continuous temperature measurement used in device
+     * (with this coordinator).
+     */
+    boolean supportsContinuousTemperature();
+
+    /**
+     * Returns true if SpO2 measurement and fetching is supported by the device
+     * (with this coordinator).
+     */
+    boolean supportsSpo2(GBDevice device);
+
+    /**
+     * Returns true if heart rate stats (max, resting, manual) measurement and fetching is supported
+     * by the device (with this coordinator).
+     */
+    boolean supportsHeartRateStats();
+
+    /**
+     * Returns true if PAI (Personal Activity Intelligence) measurement and fetching is supported by
+     * the device (with this coordinator).
+     */
+    boolean supportsPai();
+
+    /**
+     * Returns the device-specific name for PAI (eg. Vitality Score).
+     */
+    @StringRes
+    int getPaiName();
+
+    /**
+     * Returns true if the device is capable of providing the time contribution for each PAI type
+     * (light, moderate, high).
+     */
+    boolean supportsPaiTime();
+
+    /**
+     * Returns true if the device is capable of providing the time contribution for light PAI type.
+     */
+    boolean supportsPaiLow();
+
+    /**
+     * Returns the PAI target - usually 100.
+     */
+    int getPaiTarget();
+
+    /**
+     * Indicates whether the device supports respiratory rate tracking.
+     */
+    boolean supportsRespiratoryRate();
+
+    /**
+     * Indicates whether the device tracks respiratory rate during the day, will be false
+     * if only during the night.
+     */
+    boolean supportsDayRespiratoryRate();
+
+    /**
+     * Returns true if sleep respiratory rate measurement and fetching is supported by
+     * the device (with this coordinator).
+     */
+    boolean supportsSleepRespiratoryRate();
+
+    /**
+     * Returns true if measurement and fetching of body weight is supported by the device
+     * (with this coordinator).
+     */
+    boolean supportsWeightMeasurement();
+
+    /**
+     * Returns true if activity data fetching is supported AND possible at this
+     * very moment. This will consider the device state (being connected/disconnected/busy...)
+     * etc.
+     *
+     * @param device
+     * @return
+     */
+    boolean allowFetchActivityData(GBDevice device);
+
+    /**
+     * Returns the sample provider for the device being supported.
+     *
+     * @return
+     */
+    SampleProvider<? extends ActivitySample> getSampleProvider(GBDevice device, DaoSession session);
+
+    /**
+     * Returns the sample provider for stress data, for the device being supported.
+     */
+    TimeSampleProvider<? extends StressSample> getStressSampleProvider(GBDevice device, DaoSession session);
+
+    /**
+     * Returns the sample provider for body energy data, for the device being supported.
+     */
+    TimeSampleProvider<? extends BodyEnergySample> getBodyEnergySampleProvider(GBDevice device, DaoSession session);
+
+    /**
+     * Returns the sample provider for HRV summary, for the device being supported.
+     */
+    TimeSampleProvider<? extends HrvSummarySample> getHrvSummarySampleProvider(GBDevice device, DaoSession session);
+
+    /**
+     * Returns the sample provider for HRV values, for the device being supported.
+     */
+    TimeSampleProvider<? extends HrvValueSample> getHrvValueSampleProvider(GBDevice device, DaoSession session);
+
+    /**
+     * Returns the sample provider for VO2 max values, for the device being supported.
+     */
+    TimeSampleProvider<? extends Vo2MaxSample> getVo2MaxSampleProvider(GBDevice device, DaoSession session);
+
+    /**
+     * Returns the stress ranges (relaxed, mild, moderate, high), so that stress can be categorized.
+     */
+    int[] getStressRanges();
+
+    /**
+     * Returns the sample provider for temperature data, for the device being supported.
+     */
+    TimeSampleProvider<? extends TemperatureSample> getTemperatureSampleProvider(GBDevice device, DaoSession session);
+
+    /**
+     * Returns the sample provider for SpO2 data, for the device being supported.
+     */
+    TimeSampleProvider<? extends Spo2Sample> getSpo2SampleProvider(GBDevice device, DaoSession session);
+
+    /**
+     * Returns the sample provider for Cycling data, for the device being supported.
+     */
+    TimeSampleProvider<CyclingSample> getCyclingSampleProvider(GBDevice device, DaoSession session);
+
+    /**
+     * Returns the sample provider for max HR data, for the device being supported.
+     */
+    TimeSampleProvider<? extends HeartRateSample> getHeartRateMaxSampleProvider(GBDevice device, DaoSession session);
+
+    /**
+     * Returns the sample provider for resting HR data, for the device being supported.
+     */
+    TimeSampleProvider<? extends HeartRateSample> getHeartRateRestingSampleProvider(GBDevice device, DaoSession session);
+
+    /**
+     * Returns the sample provider for manual HR data, for the device being supported.
+     */
+    TimeSampleProvider<? extends HeartRateSample> getHeartRateManualSampleProvider(GBDevice device, DaoSession session);
+
+    /**
+     * Returns the sample provider for PAI data, for the device being supported.
+     */
+    TimeSampleProvider<? extends PaiSample> getPaiSampleProvider(GBDevice device, DaoSession session);
+
+    /**
+     * Returns the sample provider for sleep respiratory rate data, for the device being supported.
+     */
+    TimeSampleProvider<? extends RespiratoryRateSample> getRespiratoryRateSampleProvider(GBDevice device, DaoSession session);
+
+    /**
+     * Returns the sample provider for weight data, for the device being supported.
+     */
+    TimeSampleProvider<? extends WeightSample> getWeightSampleProvider(GBDevice device, DaoSession session);
+
+    TimeSampleProvider<? extends RestingMetabolicRateSample> getRestingMetabolicRateProvider(GBDevice device, DaoSession session);
+
+    TimeSampleProvider<? extends SleepScoreSample> getSleepScoreProvider(GBDevice device, DaoSession session);
+
+    /**
+     * Returns the {@link ActivitySummaryParser} for the device being supported.
+     *
+     * @return
+     */
+    ActivitySummaryParser getActivitySummaryParser(final GBDevice device, final Context context);
+
+    /**
+     * Returns true if this device/coordinator supports installing files like firmware,
+     * watchfaces, gps, resources, fonts...
+     *
+     * @return
+     */
+    boolean supportsFlashing();
+
+    /**
+     * Finds an install handler for the given uri that can install the given
+     * uri on the device being managed.
+     *
+     * @param uri
+     * @param context
+     * @return the install handler or null if that uri cannot be installed on the device
+     */
+    InstallHandler findInstallHandler(Uri uri, Context context);
+
+    /**
+     * Returns true if this device/coordinator supports taking screenshots.
+     *
+     * @return
+     */
+    boolean supportsScreenshots(GBDevice device);
+
+    /**
+     * Returns the number of alarms this device/coordinator supports
+     * Shall return 0 also if it is not possible to set alarms via
+     * protocol, but only on the smart device itself.
+     *
+     * @return
+     */
+    int getAlarmSlotCount(GBDevice device);
+
+    /**
+     * Returns true if this device/coordinator supports an alarm with smart wakeup for the current position
+     * @param alarmPosition Position of the alarm
+     */
+    boolean supportsSmartWakeup(GBDevice device, int alarmPosition);
+
+    /**
+     * Returns true if the smart alarm at the specified position supports setting an interval for this device/coordinator
+     * @param alarmPosition Position of the alarm
+     */
+    boolean supportsSmartWakeupInterval(GBDevice device, int alarmPosition);
+
+    /**
+     * Returns true if the alarm at the specified position *must* be a smart alarm for this device/coordinator
+     * @param alarmPosition Position of the alarm
+     * @return True if it must be a smart alarm, false otherwise
+     */
+    boolean forcedSmartWakeup(GBDevice device, int alarmPosition);
+
+    /**
+     * Returns true if this device/coordinator supports alarm snoozing
+     * @return
+     */
+    boolean supportsAlarmSnoozing();
+
+    /**
+     * Returns true if this device/coordinator supports alarm titles
+     * @return
+     */
+    boolean supportsAlarmTitle(GBDevice device);
+
+    /**
+     * Returns the character limit for the alarm title, negative if no limit.
+     * @return
+     */
+    int getAlarmTitleLimit(GBDevice device);
+
+    /**
+     * Returns true if this device/coordinator supports alarm descriptions
+     * @return
+     */
+    boolean supportsAlarmDescription(GBDevice device);
+
+    /**
+     * Returns true if the given device supports heart rate measurements.
+     * @return
+     */
+    boolean supportsHeartRateMeasurement(GBDevice device);
+
+    /**
+     * Returns true if the given device supports resting heart rate measurements.
+     */
+    boolean supportsHeartRateRestingMeasurement(GBDevice device);
+
+    /**
+     * Returns true if the device supports triggering manual one-shot heart rate measurements.
+     */
+    boolean supportsManualHeartRateMeasurement(GBDevice device);
+
+    /**
+     * Returns the readable name of the manufacturer.
+     */
+    String getManufacturer();
+
+    /**
+     * Returns true if this device/coordinator supports managing device apps.
+     *
+     * @return
+     */
+    boolean supportsAppsManagement(GBDevice device);
+
+    boolean supportsCachedAppManagement(GBDevice device);
+    boolean supportsInstalledAppManagement(GBDevice device);
+    boolean supportsWatchfaceManagement(GBDevice device);
+
+    /**
+     * Returns the Activity class that will be used to manage device apps.
+     *
+     * @return
+     */
+    Class<? extends Activity> getAppsManagementActivity();
+
+    /**
+     * Returns the Activity class that will be used to design watchfaces.
+     *
+     * @return
+     */
+    Class<? extends Activity> getWatchfaceDesignerActivity();
+
+    /**
+     * Returns the device app cache directory.
+     */
+    File getAppCacheDir() throws IOException;
+
+    /**
+     * Returns the dedicated writable export directory for this device.
+     */
+    File getWritableExportDirectory(GBDevice device) throws IOException;
+
+    /**
+     * Returns a String containing the device app sort order filename.
+     */
+    String getAppCacheSortFilename();
+
+    /**
+     * Returns a String containing the file extension for watch apps.
+     */
+    String getAppFileExtension();
+
+    /**
+     * Indicated whether the device supports fetching a list of its apps.
+     */
+    boolean supportsAppListFetching();
+
+    /**
+     * Indicates whether the device supports reordering of apps.
+     */
+    boolean supportsAppReordering();
+
+    /**
+     * Returns how/if the given device should be bonded before connecting to it.
+     */
+    int getBondingStyle();
+
+    /**
+     * Whether it is recommended to unbind the device before pairing due to compatibility issues. Returns false
+     * if the device is known to pair without issues even when already bound in Android bluetooth settings.
+     */
+    boolean suggestUnbindBeforePair();
+
+    /**
+     * Returns true if this device is in an experimental state / not tested.
+     */
+    boolean isExperimental();
+
+    /**
+     * Indicates whether the device has some kind of calender we can sync to.
+     * Also used for generated sunrise/sunset events
+     */
+    boolean supportsCalendarEvents();
+
+    /**
+     * Indicates whether the device supports getting a stream of live data.
+     * This can be live HR, steps etc.
+     */
+    boolean supportsRealtimeData();
+
+    /**
+     * Indicates whether the device supports REM sleep tracking.
+     */
+    boolean supportsRemSleep();
+
+    /**
+     * Indicates whether the device supports Awake sleep tracking.
+     */
+    boolean supportsAwakeSleep();
+
+    /**
+     * Indicates whether the device supports determining a sleep score in a 0-100 range.
+     */
+    boolean supportsSleepScore();
+
+    /**
+     * Indicates whether the device supports current weather and/or weather
+     * forecast display.
+     */
+    boolean supportsWeather();
+
+    /**
+     * Indicates whether the device supports being found by vibrating, 
+     * making some sound or lighting up
+     */
+    boolean supportsFindDevice();
+
+    /**
+     * Indicates whether the device supports displaying music information
+     * like artist, title, album, play state etc.
+     */
+    boolean supportsMusicInfo();
+
+    /**
+     * Indicates whether the device supports features required by Sleep As Android
+     */
+    boolean supportsSleepAsAndroid();
+
+    /**
+     * Indicates the maximum reminder message length.
+     */
+    int getMaximumReminderMessageLength();
+
+    /**
+     * Indicates the maximum number of reminder slots available in the device.
+     */
+    int getReminderSlotCount(GBDevice device);
+
+    /**
+     * Indicates whether reminders have a time of day.
+     */
+    boolean getRemindersHaveTime();
+
+    /**
+     * Indicates whether some reminder slots are used for calendar events.
+     */
+    boolean getReserveReminderSlotsForCalendar();
+
+    /**
+     * Indicates the maximum number of canned replies available in the device.
+     */
+    int getCannedRepliesSlotCount(GBDevice device);
+
+    /**
+     * Indicates the maximum number of slots available for world clocks in the device.
+     */
+    int getWorldClocksSlotCount();
+
+    /**
+     * Indicates the maximum label length for a world clock in the device.
+     */
+    int getWorldClocksLabelLength();
+
+    /**
+     * Indicates whether the device supports disabled world clocks that can be enabled through
+     * a menu on the device.
+     */
+    boolean supportsDisabledWorldClocks();
+
+    /**
+     * Indicates the maximum number of slots available for contacts in the device.
+     */
+    int getContactsSlotCount(GBDevice device);
+
+    /**
+     * Indicates whether the device has an led which supports custom colors
+     */
+    boolean supportsLedColor();
+
+    /**
+     * Indicates whether the device's led supports any RGB color,
+     * or only preset colors
+     */
+    boolean supportsRgbLedColor();
+
+    /**
+     * Returns the preset colors supported by the device, if any, in ARGB, with alpha = 255
+     */
+    @NonNull
+    int[] getColorPresets();
+
+    /**
+     * Indicates whether the device supports unicode emojis.
+     */
+    boolean supportsUnicodeEmojis();
+
+    /**
+     * Returns the set of supported sleep as Android features
+      * @return Set
+     */
+    Set<SleepAsAndroidFeature> getSleepAsAndroidFeatures();
+
+    /**
+     * Returns device specific settings related to connection
+     *
+     * @return int[]
+     */
+    int[] getSupportedDeviceSpecificConnectionSettings();
+
+    /**
+     * Returns device specific settings related to the Auth key
+     * @return int[]
+     */
+    int[] getSupportedDeviceSpecificAuthenticationSettings();
+
+    /**
+     * Indicates which device specific settings the device supports (not per device type or family, but unique per device).
+     *
+     * @deprecated use getDeviceSpecificSettings
+     */
+    @Deprecated
+    int[] getSupportedDeviceSpecificSettings(GBDevice device);
+
+    /**
+     * Returns the device-specific settings supported by this specific device. See
+     * {@link DeviceSpecificSettings} for more information
+     */
+    @Nullable
+    DeviceSpecificSettings getDeviceSpecificSettings(GBDevice device);
+
+    /**
+     * Returns the {@link DeviceSpecificSettingsCustomizer}, allowing for the customization of the devices specific settings screen.
+     */
+    DeviceSpecificSettingsCustomizer getDeviceSpecificSettingsCustomizer(GBDevice device);
+
+    /**
+     * Indicates which device specific language the device supports
+     */
+    String[] getSupportedLanguageSettings(GBDevice device);
+
+    /**
+     *
+     * Multiple battery support: Indicates how many batteries the device has.
+     * 1 is default, 3 is maximum at the moment (as per UI layout)
+     * 0 will disable the battery from the UI
+     */
+    int getBatteryCount();
+
+    BatteryConfig[] getBatteryConfig(GBDevice device);
+
+    boolean addBatteryPollingSettings();
+
+    boolean supportsPowerOff();
+
+    PasswordCapabilityImpl.Mode getPasswordCapability();
+
+    List<HeartRateCapability.MeasurementInterval> getHeartRateMeasurementIntervals();
+
+    /**
+     * Whether the device supports screens with configurable widgets.
+     */
+    boolean supportsWidgets(GBDevice device);
+
+    /**
+     * Gets the {@link WidgetManager} for this device. Must not be null if supportsWidgets is true.
+     */
+    @Nullable
+    WidgetManager getWidgetManager(GBDevice device);
+
+    boolean supportsNavigation();
+
+    int getOrderPriority();
+
+    @NonNull
+    Class<? extends DeviceSupport> getDeviceSupportClass();
+
+    EnumSet<ServiceDeviceSupport.Flags> getInitialFlags();
+
+    @StringRes
+    int getDeviceNameResource();
+
+    @DrawableRes
+    int getDefaultIconResource();
+
+    @DrawableRes
+    int getDisabledIconResource();
+
+    /**
+     * 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();
+
+    boolean validateAuthKey(String authKey);
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/DeviceManager.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/DeviceManager.java
new file mode 100644
index 0000000..43b3450
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/DeviceManager.java
@@ -0,0 +1,196 @@
+/*  Copyright (C) 2016-2024 Andreas Shimokawa, Carsten Pfeiffer, Daniel
+    Dakhno, Daniele Gobbetti, Gordon Williams, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices;
+
+import android.bluetooth.BluetoothDevice;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+
+import androidx.core.content.ContextCompat;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.text.Collator;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Set;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+/**
+ * Provides access to the list of devices managed by Gadgetbridge.
+ * Changes to the devices (e.g. connection state) or the list of devices
+ * are broadcasted via #ACTION_DEVICE_CHANGED
+ */
+public class DeviceManager {
+    private static final Logger LOG = LoggerFactory.getLogger(DeviceManager.class);
+
+    public static final String BLUETOOTH_DEVICE_ACTION_ALIAS_CHANGED = "android.bluetooth.device.action.ALIAS_CHANGED";
+    /**
+     * Intent action to notify that the list of devices has changed.
+     */
+    public static final String ACTION_DEVICES_CHANGED
+            = "nodomain.freeyourgadget.gadgetbridge.devices.devicemanager.action.devices_changed";
+    /**
+     * Intent action to notify this class that the list of devices shall be refreshed.
+     */
+    public static final String ACTION_REFRESH_DEVICELIST
+            = "nodomain.freeyourgadget.gadgetbridge.devices.devicemanager.action.set_version";
+    private final Context context;
+    /**
+     * This list is final, it will never be recreated. Only its contents change.
+     * This allows direct access to the list from ListAdapters.
+     */
+    private final List<GBDevice> deviceList = new ArrayList<>();
+    private List<GBDevice> selectedDevices = new ArrayList<>();
+
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            switch (action) {
+                case ACTION_REFRESH_DEVICELIST: // fall through
+                case BluetoothDevice.ACTION_BOND_STATE_CHANGED:
+                    refreshPairedDevices();
+                    break;
+                case BluetoothDevice.ACTION_NAME_CHANGED:
+                case BLUETOOTH_DEVICE_ACTION_ALIAS_CHANGED:
+                    BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+                    String newName = intent.getStringExtra(BluetoothDevice.EXTRA_NAME);
+                    if (device!=null)
+                      updateDeviceName(device, newName);
+                    break;
+                case GBDevice.ACTION_DEVICE_CHANGED:
+                    GBDevice dev = intent.getParcelableExtra(GBDevice.EXTRA_DEVICE);
+                    if (dev.getAddress() != null) {
+                        int index = deviceList.indexOf(dev); // search by address
+                        if (index >= 0) {
+                            deviceList.get(index).copyFromDevice(dev);
+                        } else {
+                            deviceList.add(dev);
+                        }
+                        if (dev.isInitialized()) {
+                            try (DBHandler dbHandler = GBApplication.acquireDB()) {
+                                DBHelper.getDevice(dev, dbHandler.getDaoSession()); // implicitly creates the device in database if not present, and updates device attributes
+                            } catch (Exception ignore) {
+                            }
+                        }
+                    }
+                    updateSelectedDevice(dev);
+                    refreshPairedDevices();
+                    break;
+            }
+        }
+    };
+
+    public DeviceManager(Context context) {
+        this.context = context;
+        IntentFilter filterLocal = new IntentFilter();
+        filterLocal.addAction(DeviceManager.ACTION_REFRESH_DEVICELIST);
+        filterLocal.addAction(GBDevice.ACTION_DEVICE_CHANGED);
+        filterLocal.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
+        LocalBroadcastManager.getInstance(context).registerReceiver(mReceiver, filterLocal);
+
+        IntentFilter filterGlobal = new IntentFilter();
+        filterGlobal.addAction(BluetoothDevice.ACTION_NAME_CHANGED);
+        filterGlobal.addAction(BLUETOOTH_DEVICE_ACTION_ALIAS_CHANGED);
+        filterGlobal.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
+        ContextCompat.registerReceiver(context, mReceiver, filterGlobal, ContextCompat.RECEIVER_EXPORTED);
+
+        refreshPairedDevices();
+    }
+
+    private void updateDeviceName(BluetoothDevice device, String newName) {
+        for (GBDevice dev : deviceList) {
+            if (device.getAddress().equals(dev.getAddress())) {
+                if (!dev.getName().equals(newName)) {
+                    dev.setName(newName);
+                    notifyDevicesChanged();
+                    return;
+                }
+            }
+        }
+    }
+
+    private void updateSelectedDevice(GBDevice dev) {
+        selectedDevices.clear();
+        for(GBDevice device : deviceList){
+            if(device.isInitialized()){
+                selectedDevices.add(device);
+            }
+        }
+        GB.updateNotification(selectedDevices, context);
+    }
+
+    private void refreshPairedDevices() {
+        Set<GBDevice> availableDevices = DeviceHelper.getInstance().getAvailableDevices(context);
+        deviceList.retainAll(availableDevices);
+        for (GBDevice availableDevice : availableDevices) {
+            if (!deviceList.contains(availableDevice)) {
+                deviceList.add(availableDevice);
+            }
+        }
+
+        Collections.sort(deviceList, new Comparator<GBDevice>() {
+            @Override
+            public int compare(GBDevice lhs, GBDevice rhs) {
+                if (rhs.getStateOrdinal() - lhs.getStateOrdinal() == 0) {
+                    return Collator.getInstance().compare(lhs.getAliasOrName(), rhs.getAliasOrName());
+                }
+                return (rhs.getStateOrdinal() - lhs.getStateOrdinal());
+            }
+        });
+        notifyDevicesChanged();
+    }
+
+    /**
+     * The returned list is final, it will never be recreated. Only its contents change.
+     * This allows direct access to the list from ListAdapters.
+     */
+    public List<GBDevice> getDevices() {
+        return Collections.unmodifiableList(deviceList);
+    }
+
+    public GBDevice getDeviceByAddress(String address){
+        for(GBDevice device : deviceList){
+            if(device.getAddress().compareToIgnoreCase(address) == 0){
+                return device;
+            }
+        }
+        return null;
+    }
+
+    public List<GBDevice> getSelectedDevices() {
+        return selectedDevices;
+    }
+
+    private void notifyDevicesChanged() {
+        LocalBroadcastManager.getInstance(context).sendBroadcast(new Intent(ACTION_DEVICES_CHANGED));
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/EventHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/EventHandler.java
new file mode 100644
index 0000000..a9be0fb
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/EventHandler.java
@@ -0,0 +1,162 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, Arjan Schrijver, Carsten
+    Pfeiffer, Jos茅 Rebelo, Julien Pivotto, Kasha, Sebastian Kranz, Steffen
+    Liebergeld, Uwe Hermann
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices;
+
+import android.location.Location;
+import android.net.Uri;
+import android.os.Bundle;
+
+import java.util.ArrayList;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.capabilities.loyaltycards.LoyaltyCard;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventCameraRemote;
+import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
+import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.Contact;
+import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.NavigationInfoSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.Reminder;
+import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.WorldClock;
+
+/**
+ * Specifies all events that Gadgetbridge intends to send to the gadget device.
+ * Implementations can decide to ignore events that they do not support.
+ * Implementations need to send/encode event to the connected device.
+ */
+public interface EventHandler {
+    void onNotification(NotificationSpec notificationSpec);
+
+    void onDeleteNotification(int id);
+
+    void onSetTime();
+
+    void onSetAlarms(ArrayList<? extends Alarm> alarms);
+
+    void onSetReminders(ArrayList<? extends Reminder> reminders);
+
+    void onSetLoyaltyCards(ArrayList<LoyaltyCard> cards);
+
+    void onSetWorldClocks(ArrayList<? extends WorldClock> clocks);
+
+    void onSetContacts(ArrayList<? extends Contact> contacts);
+
+    void onSetCallState(CallSpec callSpec);
+
+    void onSetCannedMessages(CannedMessagesSpec cannedMessagesSpec);
+
+    void onSetMusicState(MusicStateSpec stateSpec);
+
+    void onSetMusicInfo(MusicSpec musicSpec);
+
+    /**
+     * Sets the current phone media volume.
+     *
+     * @param volume the volume percentage (0 to 100).
+     */
+    void onSetPhoneVolume(final float volume);
+
+    void onChangePhoneSilentMode(int ringerMode);
+
+    void onSetNavigationInfo(NavigationInfoSpec navigationInfoSpec);
+
+    void onEnableRealtimeSteps(boolean enable);
+
+    void onInstallApp(Uri uri);
+
+    void onAppInfoReq();
+
+    void onAppStart(UUID uuid, boolean start);
+
+    void onAppDownload(UUID uuid);
+
+    void onAppDelete(UUID uuid);
+
+    void onAppConfiguration(UUID appUuid, String config, Integer id);
+
+    void onAppReorder(UUID[] uuids);
+
+    void onFetchRecordedData(int dataTypes);
+
+    void onReset(int flags);
+
+    void onHeartRateTest();
+
+    void onEnableRealtimeHeartRateMeasurement(boolean enable);
+
+    void onFindDevice(boolean start);
+
+    void onFindPhone(boolean start);
+
+    void onSetConstantVibration(int integer);
+
+    void onScreenshotReq();
+
+    void onEnableHeartRateSleepSupport(boolean enable);
+
+    void onSetHeartRateMeasurementInterval(int seconds);
+
+    void onAddCalendarEvent(CalendarEventSpec calendarEventSpec);
+
+    void onDeleteCalendarEvent(byte type, long id);
+
+    /**
+     * Sets the given option in the device, typically with values from the preferences.
+     * The config name is device specific.
+     * @param config the device specific option to set on the device
+     */
+    void onSendConfiguration(String config);
+
+    /**
+     * Gets the given option from the device, sets the values in the preferences.
+     * The config name is device specific.
+     * @param config the device specific option to get from the device
+     */
+    void onReadConfiguration(String config);
+
+    void onTestNewFunction();
+
+    void onSendWeather(ArrayList<WeatherSpec> weatherSpecs);
+
+    void onSetFmFrequency(float frequency);
+
+    /**
+     * Set the device's led color.
+     * @param color the new color, in ARGB, with alpha = 255
+     */
+    void onSetLedColor(int color);
+
+    void onPowerOff();
+
+    void onSetGpsLocation(Location location);
+
+    void onSleepAsAndroidAction(String action, Bundle extras);
+
+    void onCameraStatusChange(GBDeviceEventCameraRemote.Event event, String filename);
+
+
+    void onMusicListReq();
+
+    void onMusicOperation(int operation, int playlistIndex, String playlistName, ArrayList<Integer> musicIds);
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/HeartPulseSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/HeartPulseSampleProvider.java
new file mode 100644
index 0000000..33b27c2
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/HeartPulseSampleProvider.java
@@ -0,0 +1,55 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices;
+
+import androidx.annotation.NonNull;
+
+import de.greenrobot.dao.AbstractDao;
+import de.greenrobot.dao.Property;
+import nodomain.freeyourgadget.gadgetbridge.entities.HeartPulseSample;
+import nodomain.freeyourgadget.gadgetbridge.entities.HeartPulseSampleDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class HeartPulseSampleProvider extends AbstractTimeSampleProvider<HeartPulseSample> {
+    public HeartPulseSampleProvider(final GBDevice device, final DaoSession session) {
+        super(device, session);
+    }
+
+    @NonNull
+    @Override
+    public AbstractDao<HeartPulseSample, ?> getSampleDao() {
+        return getSession().getHeartPulseSampleDao();
+    }
+
+    @NonNull
+    @Override
+    protected Property getTimestampSampleProperty() {
+        return HeartPulseSampleDao.Properties.Timestamp;
+    }
+
+    @NonNull
+    @Override
+    protected Property getDeviceIdentifierSampleProperty() {
+        return HeartPulseSampleDao.Properties.DeviceId;
+    }
+
+    @Override
+    public HeartPulseSample createSample() {
+        return new HeartPulseSample();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/InstallHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/InstallHandler.java
new file mode 100644
index 0000000..87556b8
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/InstallHandler.java
@@ -0,0 +1,50 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, Carsten Pfeiffer
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices;
+
+import nodomain.freeyourgadget.gadgetbridge.activities.InstallActivity;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+/**
+ * Interface for the UI side of certain kinds of installation of things on the
+ * gadget device. The actual element to install will be passed in the constructor.
+ */
+public interface InstallHandler {
+
+    /**
+     * Returns true if this handler is able to install the element.
+     * #validateInstallation may only be called if this method returned true.
+     */
+    boolean isValid();
+
+    /**
+     * Checks whether the installation of the 'element' on the device is possible
+     * and configures the InstallActivity accordingly (sets helpful texts,
+     * enables/disables the "Install" button, etc.
+     * <p/>
+     * Note: may only be called if #isValid previously returned true.
+     *
+     * @param installActivity the activity to interact with
+     * @param device          the device to which the element shall be installed
+     */
+    void validateInstallation(InstallActivity installActivity, GBDevice device);
+
+    /**
+     * Allows device specific code to be executed just before the installation starts
+     */
+    void onStartInstall(GBDevice device);
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/PendingFileProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/PendingFileProvider.java
new file mode 100644
index 0000000..729911e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/PendingFileProvider.java
@@ -0,0 +1,100 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.UUID;
+
+import de.greenrobot.dao.Property;
+import de.greenrobot.dao.query.QueryBuilder;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.entities.PendingFile;
+import nodomain.freeyourgadget.gadgetbridge.entities.PendingFileDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.User;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public final class PendingFileProvider {
+    private final DaoSession mSession;
+    private final GBDevice mDevice;
+
+    public PendingFileProvider(final GBDevice device, final DaoSession session) {
+        mDevice = device;
+        mSession = session;
+    }
+
+    @NonNull
+    public List<PendingFile> getAllPendingFiles() {
+        final QueryBuilder<PendingFile> qb = mSession.getPendingFileDao().queryBuilder();
+        final Device dbDevice = DBHelper.findDevice(mDevice, mSession);
+        if (dbDevice == null) {
+            // no device, no pending files
+            return Collections.emptyList();
+        }
+        final Property deviceProperty = PendingFileDao.Properties.DeviceId;
+        qb.where(deviceProperty.eq(dbDevice.getId()));
+        final List<PendingFile> ret = qb.build().list();
+        mSession.getPendingFileDao().detachAll();
+        return ret;
+    }
+
+    public void removePendingFile(final String path) {
+        final PendingFile pendingFile = findByPath(path);
+        if (pendingFile != null) {
+            pendingFile.delete();
+        }
+    }
+
+    public void addPendingFile(final String path) {
+        final PendingFile existingFile = findByPath(path);
+        if (existingFile != null) {
+            return;
+        }
+
+        final Device device = DBHelper.getDevice(mDevice, mSession);
+
+        final PendingFile pendingFile = new PendingFile();
+        pendingFile.setPath(path);
+        pendingFile.setDevice(device);
+
+        addPendingFile(pendingFile);
+    }
+
+    public void addPendingFile(final PendingFile pendingFile) {
+        mSession.getPendingFileDao().insertOrReplace(pendingFile);
+    }
+
+    @Nullable
+    private PendingFile findByPath(final String path) {
+        final Device device = DBHelper.getDevice(mDevice, mSession);
+
+        final PendingFileDao pendingFileDao = mSession.getPendingFileDao();
+        final QueryBuilder<PendingFile> qb = pendingFileDao.queryBuilder();
+        qb.where(PendingFileDao.Properties.DeviceId.eq(device.getId()));
+        qb.where(PendingFileDao.Properties.Path.eq(path));
+        final List<PendingFile> pendingFiles = qb.build().list();
+        if (!pendingFiles.isEmpty()) {
+            return pendingFiles.get(0);
+        }
+        return null;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/SampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/SampleProvider.java
new file mode 100644
index 0000000..e35e6cd
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/SampleProvider.java
@@ -0,0 +1,122 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, Carsten Pfeiffer, Daniele
+    Gobbetti, Joa虄o Paulo Barraca, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices;
+
+import java.util.List;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import nodomain.freeyourgadget.gadgetbridge.entities.AbstractActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+
+/**
+ * Interface to retrieve samples from the database, and also create and add samples to the database.
+ * There are multiple device specific implementations, this interface defines the generic access.
+ *
+ * Note that the provided samples must typically be considered read-only, because they are immediately
+ * removed from the session before they are returned.
+ *
+ * @param <T> the device/provider specific sample type (must extend AbstractActivitySample)
+ */
+public interface SampleProvider<T extends AbstractActivitySample> {
+    // These are only used for SharedPreferences
+    int PROVIDER_PEBBLE_MORPHEUZ = 1;
+    int PROVIDER_PEBBLE_MISFIT = 3;
+    int PROVIDER_PEBBLE_HEALTH = 4;
+
+    ActivityKind normalizeType(int rawType);
+
+    int toRawActivityKind(ActivityKind activityKind);
+
+    float normalizeIntensity(int rawIntensity);
+
+    /**
+     * Returns the list of all samples, of any type, within the given time span.
+     * This returns exactly one sample every minute.
+     * @param timestamp_from the start timestamp
+     * @param timestamp_to the end timestamp
+     * @return the list of samples of any type
+     */
+    @NonNull
+    List<T> getAllActivitySamples(int timestamp_from, int timestamp_to);
+
+    /**
+     * Same as {@link #getAllActivitySamples(int, int)}}, but returns as many samples as possible.
+     * Explicitly does not make a guarantee about how many samples there are per timeframe, which
+     * can also change over time.
+     */
+    List<T> getAllActivitySamplesHighRes(int timestamp_from, int timestamp_to);
+
+    /**
+     * Specifies that the sample provider has higher resolution data. Set to true if the sample
+     * provider can provide more than one sample a minute.
+     */
+    boolean hasHighResData();
+
+    /**
+     * Returns the list of all samples that represent user "activity", within
+     * the given time span. This excludes samples of type sleep, for example.
+     * @param timestamp_from the start timestamp
+     * @param timestamp_to the end timestamp
+     * @return the list of samples of type user activity, e.g. non-sleep
+     */
+    @NonNull
+    List<T> getActivitySamples(int timestamp_from, int timestamp_to);
+
+    /**
+     * Adds the given sample to the database. An existing sample with the same
+     * timestamp will be overwritten.
+     * @param activitySample the sample to add
+     */
+    void addGBActivitySample(T activitySample);
+
+    /**
+     * Adds the given samples to the database. Existing samples with the same
+     * timestamp will be overwritten.
+     * @param activitySamples the samples to add
+     */
+    void addGBActivitySamples(T[] activitySamples);
+
+    /**
+     * Factory method to creates an empty sample of the correct type for this sample provider
+     * @return the newly created "empty" sample
+     */
+    T createActivitySample();
+
+    /**
+     * Returns the activity sample with the highest timestamp. or null if none
+     * @return the latest sample or null
+     */
+    @Nullable
+    T getLatestActivitySample();
+
+    /**
+     * Returns the activity sample with the highest timestamp, until a limit (inclusive). or null if none
+     * @return the latest sample or null
+     */
+    @Nullable
+    T getLatestActivitySample(int until);
+
+    /**
+     * Returns the activity sample with the oldest timestamp or null if none
+     * @return the oldest sample or null
+     */
+    @Nullable
+    T getFirstActivitySample();
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/SleepAsAndroidFeature.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/SleepAsAndroidFeature.java
new file mode 100644
index 0000000..8092350
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/SleepAsAndroidFeature.java
@@ -0,0 +1,11 @@
+package nodomain.freeyourgadget.gadgetbridge.devices;
+
+
+public enum SleepAsAndroidFeature {
+    HEART_RATE,
+    ALARMS,
+    NOTIFICATIONS,
+    ACCELEROMETER,
+    OXIMETRY,
+    SPO2
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/TimeSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/TimeSampleProvider.java
new file mode 100644
index 0000000..475c646
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/TimeSampleProvider.java
@@ -0,0 +1,99 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.model.TimeSample;
+
+/**
+ * Interface to retrieve samples from the database, and also create and add samples to the database.
+ * There are multiple device specific implementations, this interface defines the generic access.
+ * <p>
+ * Note that the provided samples must typically be considered read-only, because they are immediately
+ * removed from the session before they are returned.
+ * <p>
+ * This differs from SampleProvider by assuming milliseconds for timestamps instead of seconds, as well as
+ * not enforcing an all-in-once ActivitySample interface for data that might be completely unrelated with
+ * activity data.
+ *
+ * @param <T> the device/provider specific sample type (must extend TimeSample).
+ */
+public interface TimeSampleProvider<T extends TimeSample> {
+    /**
+     * Returns the list of all samples, of any type, within the given time span.
+     *
+     * @param timestampFrom the start timestamp, in milliseconds
+     * @param timestampTo   the end timestamp, in milliseconds
+     * @return the list of samples of any type
+     */
+    @NonNull
+    List<T> getAllSamples(long timestampFrom, long timestampTo);
+
+    /**
+     * Adds the given sample to the database. An existing sample with the same
+     * timestamp will be overwritten.
+     *
+     * @param timeSample the sample to add
+     */
+    void addSample(T timeSample);
+
+    /**
+     * Adds the given samples to the database. Existing samples with the same
+     * timestamp (and a potential combination of other attributes, depending on the sample implementation)
+     * will be overwritten.
+     *
+     * @param timeSamples the samples to add
+     */
+    void addSamples(List<T> timeSamples);
+
+    /**
+     * Factory method to creates an empty sample of the correct type for this sample provider.
+     *
+     * @return the newly created "empty" sample
+     */
+    T createSample();
+
+    /**
+     * Returns the sample with the highest timestamp, or null if none.
+     *
+     * @return the latest sample, or null if none is found.
+     */
+    @Nullable
+    T getLatestSample();
+
+    /**
+     * Returns the sample with the highest timestamp until a limit, or null if none.
+     *
+     * @param until maximum timestamp of the sample, inclusive
+     *
+     * @return the latest sample, or null if none is found.
+     */
+    @Nullable
+    T getLatestSample(long until);
+
+    /**
+     * Returns the sample with the oldest timestamp, or null if none.
+     *
+     * @return the oldest sample, or null if none is found
+     */
+    @Nullable
+    T getFirstSample();
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/UnknownDeviceCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/UnknownDeviceCoordinator.java
new file mode 100644
index 0000000..d56035c
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/UnknownDeviceCoordinator.java
@@ -0,0 +1,177 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, Carsten Pfeiffer, Damien
+    Gaignon, Daniel Dakhno, Daniele Gobbetti, Jos茅 Rebelo, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices;
+
+import android.app.Activity;
+import android.content.Context;
+import android.net.Uri;
+
+import java.util.Collections;
+import java.util.List;
+
+import androidx.annotation.DrawableRes;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.StringRes;
+
+import nodomain.freeyourgadget.gadgetbridge.GBException;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.ControlCenterv2;
+import nodomain.freeyourgadget.gadgetbridge.entities.AbstractActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.unknown.UnknownDeviceSupport;
+
+public class UnknownDeviceCoordinator extends AbstractDeviceCoordinator {
+    private final UnknownSampleProvider sampleProvider;
+
+    private static final class UnknownSampleProvider implements SampleProvider<AbstractActivitySample> {
+        @Override
+        public ActivityKind normalizeType(int rawType) {
+            return ActivityKind.UNKNOWN;
+        }
+
+        @Override
+        public int toRawActivityKind(ActivityKind activityKind) {
+            return 0;
+        }
+
+        @Override
+        public float normalizeIntensity(int rawIntensity) {
+            return 0;
+        }
+
+        @Override
+        public List<AbstractActivitySample> getAllActivitySamples(int timestamp_from, int timestamp_to) {
+            return null;
+        }
+
+        @Override
+        public List<AbstractActivitySample> getAllActivitySamplesHighRes(int timestamp_from, int timestamp_to) {
+            return null;
+        }
+
+        @Override
+        public boolean hasHighResData() {
+            return false;
+        }
+
+        @Override
+        public List<AbstractActivitySample> getActivitySamples(int timestamp_from, int timestamp_to) {
+            return null;
+        }
+
+        @Override
+        public void addGBActivitySample(AbstractActivitySample activitySample) {
+        }
+
+        @Override
+        public void addGBActivitySamples(AbstractActivitySample[] activitySamples) {
+        }
+
+        @Override
+        public AbstractActivitySample createActivitySample() {
+            return null;
+        }
+
+        @Nullable
+        @Override
+        public AbstractActivitySample getLatestActivitySample() {
+            return null;
+        }
+
+        @Nullable
+        @Override
+        public AbstractActivitySample getLatestActivitySample(final int until) {
+            return null;
+        }
+
+        @Nullable
+        @Override
+        public AbstractActivitySample getFirstActivitySample() {
+            return null;
+        }
+
+    }
+
+    public UnknownDeviceCoordinator() {
+        sampleProvider = new UnknownSampleProvider();
+    }
+
+    @Override
+    public boolean supports(GBDeviceCandidate candidate) {
+        return false;
+    }
+
+    @Override
+    protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
+    }
+
+    @Override
+    public Class<? extends Activity> getPairingActivity() {
+        return ControlCenterv2.class;
+    }
+
+    @Override
+    public SampleProvider<?> getSampleProvider(GBDevice device, DaoSession session) {
+        return new UnknownSampleProvider();
+    }
+
+    @Override
+    public InstallHandler findInstallHandler(Uri uri, Context context) {
+        return null;
+    }
+
+    @Override
+    public String getManufacturer() {
+        return "Generic";
+    }
+
+    @Override
+    public Class<? extends Activity> getAppsManagementActivity() {
+        return null;
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return UnknownDeviceSupport.class;
+    }
+
+    @Override
+    @StringRes
+    public int getDeviceNameResource() {
+        return R.string.devicetype_unknown;
+    }
+
+    @Override
+    @DrawableRes
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_unknown;
+    }
+
+    @Override
+    @DrawableRes
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_unknown_disabled;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/Vo2MaxSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/Vo2MaxSampleProvider.java
new file mode 100644
index 0000000..bddd6be
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/Vo2MaxSampleProvider.java
@@ -0,0 +1,15 @@
+package nodomain.freeyourgadget.gadgetbridge.devices;
+
+import androidx.annotation.Nullable;
+
+import nodomain.freeyourgadget.gadgetbridge.model.Vo2MaxSample;
+
+public interface Vo2MaxSampleProvider<T extends Vo2MaxSample> extends TimeSampleProvider<T> {
+    @Nullable
+    T getLatestSample(Vo2MaxSample.Type type, long until);
+
+    @Nullable
+    default T getLatestSample(long until) {
+        return getLatestSample(Vo2MaxSample.Type.ANY, until);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/WorkoutVo2MaxSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/WorkoutVo2MaxSampleProvider.java
new file mode 100644
index 0000000..dd5c2d2
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/WorkoutVo2MaxSampleProvider.java
@@ -0,0 +1,273 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+import de.greenrobot.dao.query.QueryBuilder;
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary;
+import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummaryDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryData;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryParser;
+import nodomain.freeyourgadget.gadgetbridge.model.Vo2MaxSample;
+
+/**
+ * An implementation of {@link Vo2MaxSampleProvider} that extracts VO2 Max values from
+ * workouts, for devices that provide them as part of the workout data.
+ */
+public class WorkoutVo2MaxSampleProvider implements Vo2MaxSampleProvider<Vo2MaxSample> {
+    private static final Logger LOG = LoggerFactory.getLogger(WorkoutVo2MaxSampleProvider.class);
+
+    private final GBDevice device;
+    private final DaoSession session;
+
+    public WorkoutVo2MaxSampleProvider(final GBDevice device, final DaoSession session) {
+        this.device = device;
+        this.session = session;
+    }
+
+    @NonNull
+    @Override
+    public List<Vo2MaxSample> getAllSamples(final long timestampFrom, final long timestampTo) {
+        final BaseActivitySummaryDao summaryDao = session.getBaseActivitySummaryDao();
+        final Device dbDevice = DBHelper.findDevice(device, session);
+        if (dbDevice == null) {
+            // no device, no samples
+            return Collections.emptyList();
+        }
+
+        final DeviceCoordinator coordinator = device.getDeviceCoordinator();
+
+        final QueryBuilder<BaseActivitySummary> qb = summaryDao.queryBuilder();
+        qb.where(BaseActivitySummaryDao.Properties.DeviceId.eq(dbDevice.getId()))
+                .where(BaseActivitySummaryDao.Properties.StartTime.gt(new Date(timestampFrom)))
+                .where(BaseActivitySummaryDao.Properties.StartTime.lt(new Date(timestampTo)))
+                .orderAsc(BaseActivitySummaryDao.Properties.StartTime);
+
+        final List<BaseActivitySummary> samples = qb.build().list();
+        summaryDao.detachAll();
+        fillSummaryData(coordinator, samples);
+
+        return samples.stream()
+                .map(GarminVo2maxSample::fromActivitySummary)
+                .filter(Objects::nonNull)
+                .collect(Collectors.toList());
+    }
+
+    @Override
+    public void addSample(final Vo2MaxSample timeSample) {
+        throw new UnsupportedOperationException("Read-only sample provider");
+    }
+
+    @Override
+    public void addSamples(final List<Vo2MaxSample> timeSamples) {
+        throw new UnsupportedOperationException("Read-only sample provider");
+    }
+
+    @Override
+    public Vo2MaxSample createSample() {
+        throw new UnsupportedOperationException("Read-only sample provider");
+    }
+
+    @Nullable
+    @Override
+    public Vo2MaxSample getLatestSample(final Vo2MaxSample.Type type, final long until) {
+        final BaseActivitySummaryDao summaryDao = session.getBaseActivitySummaryDao();
+        final Device dbDevice = DBHelper.findDevice(device, session);
+        if (dbDevice == null) {
+            // no device, no samples
+            return null;
+        }
+
+        final DeviceCoordinator coordinator = device.getDeviceCoordinator();
+        final QueryBuilder<BaseActivitySummary> qb = summaryDao.queryBuilder();
+
+        addWhereFilter(coordinator, qb, type);
+
+        if (until != 0) {
+            qb.where(BaseActivitySummaryDao.Properties.StartTime.le(new Date(until)));
+        }
+
+        qb.where(BaseActivitySummaryDao.Properties.DeviceId.eq(dbDevice.getId()))
+                .orderDesc(BaseActivitySummaryDao.Properties.StartTime)
+                .limit(1);
+
+        final List<BaseActivitySummary> samples = qb.build().list();
+        summaryDao.detachAll();
+        fillSummaryData(coordinator, samples);
+
+        return !samples.isEmpty() ? GarminVo2maxSample.fromActivitySummary(samples.get(0)) : null;
+    }
+
+    private static void addWhereFilter(final DeviceCoordinator coordinator,
+                                       final QueryBuilder<BaseActivitySummary> qb,
+                                       final Vo2MaxSample.Type type) {
+        final List<Integer> codes = new ArrayList<>();
+
+        if (coordinator.supportsVO2MaxRunning()) {
+            if (type == Vo2MaxSample.Type.ANY || type == Vo2MaxSample.Type.RUNNING) {
+                codes.addAll(Arrays.asList(
+                        ActivityKind.INDOOR_RUNNING.getCode(),
+                        ActivityKind.OUTDOOR_RUNNING.getCode(),
+                        ActivityKind.CROSS_COUNTRY_RUNNING.getCode(),
+                        ActivityKind.RUNNING.getCode()
+                ));
+            }
+        }
+        if (coordinator.supportsVO2MaxCycling()) {
+            if (type == Vo2MaxSample.Type.ANY || type == Vo2MaxSample.Type.CYCLING) {
+                codes.addAll(Arrays.asList(
+                        ActivityKind.CYCLING.getCode(),
+                        ActivityKind.INDOOR_CYCLING.getCode(),
+                        ActivityKind.HANDCYCLING.getCode(),
+                        ActivityKind.HANDCYCLING_INDOOR.getCode(),
+                        ActivityKind.OUTDOOR_CYCLING.getCode()
+                ));
+            }
+        }
+
+        qb.where(BaseActivitySummaryDao.Properties.ActivityKind.in(codes));
+    }
+
+    private void fillSummaryData(final DeviceCoordinator coordinator,
+                                 final Collection<BaseActivitySummary> summaries) {
+        ActivitySummaryParser activitySummaryParser = coordinator.getActivitySummaryParser(device, GBApplication.getContext());
+        for (final BaseActivitySummary summary : summaries) {
+            if (summary.getSummaryData() == null) {
+                activitySummaryParser.parseBinaryData(summary, true);
+            }
+        }
+    }
+
+    @Nullable
+    @Override
+    public Vo2MaxSample getLatestSample() {
+        return getLatestSample(Vo2MaxSample.Type.ANY, 0);
+    }
+
+    @Nullable
+    @Override
+    public Vo2MaxSample getFirstSample() {
+        final BaseActivitySummaryDao summaryDao = session.getBaseActivitySummaryDao();
+        final Device dbDevice = DBHelper.findDevice(device, session);
+        if (dbDevice == null) {
+            // no device, no samples
+            return null;
+        }
+
+        final QueryBuilder<BaseActivitySummary> qb = summaryDao.queryBuilder();
+        qb.where(BaseActivitySummaryDao.Properties.DeviceId.eq(dbDevice.getId()))
+                .orderAsc(BaseActivitySummaryDao.Properties.StartTime)
+                .limit(1);
+
+        final List<BaseActivitySummary> samples = qb.build().list();
+        summaryDao.detachAll();
+
+        return !samples.isEmpty() ? GarminVo2maxSample.fromActivitySummary(samples.get(0)) : null;
+    }
+
+
+    public static class GarminVo2maxSample implements Vo2MaxSample {
+        private final long timestamp;
+        private final Type type;
+        private final float value;
+
+        public GarminVo2maxSample(final long timestamp, final Type type, final float value) {
+            this.timestamp = timestamp;
+            this.type = type;
+            this.value = value;
+        }
+
+        @Override
+        public long getTimestamp() {
+            return timestamp;
+        }
+
+        @Override
+        public Type getType() {
+            return type;
+        }
+
+        @Override
+        public float getValue() {
+            return value;
+        }
+
+        @Nullable
+        public static GarminVo2maxSample fromActivitySummary(final BaseActivitySummary summary) {
+            final String summaryDataJson = summary.getSummaryData();
+            if (summaryDataJson == null) {
+                return null;
+            }
+
+            if (!summaryDataJson.contains(ActivitySummaryEntries.MAXIMUM_OXYGEN_UPTAKE)) {
+                return null;
+            }
+
+            final ActivitySummaryData summaryData = ActivitySummaryData.fromJson(summaryDataJson);
+            if (summaryData == null) {
+                return null;
+            }
+
+            final double value = summaryData.getNumber(ActivitySummaryEntries.MAXIMUM_OXYGEN_UPTAKE, 0).doubleValue();
+            if (value == 0) {
+                return null;
+            }
+
+            final Vo2MaxSample.Type type;
+            switch (ActivityKind.fromCode(summary.getActivityKind())) {
+                case INDOOR_RUNNING:
+                case OUTDOOR_RUNNING:
+                case CROSS_COUNTRY_RUNNING:
+                case RUNNING:
+                    type = Vo2MaxSample.Type.RUNNING;
+                    break;
+                case CYCLING:
+                case INDOOR_CYCLING:
+                case HANDCYCLING:
+                case HANDCYCLING_INDOOR:
+                case MOTORCYCLING:
+                case OUTDOOR_CYCLING:
+                    type = Vo2MaxSample.Type.CYCLING;
+                    break;
+                default:
+                    type = Vo2MaxSample.Type.ANY;
+            }
+            return new GarminVo2maxSample(summary.getStartTime().getTime(), type, (float) value);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/amazfitbip/BipActivitySummary.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/amazfitbip/BipActivitySummary.java
new file mode 100644
index 0000000..b520592
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/amazfitbip/BipActivitySummary.java
@@ -0,0 +1,219 @@
+/*  Copyright (C) 2017-2024 Carsten Pfeiffer, Daniele Gobbetti
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.amazfitbip;
+
+import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary;
+
+public class BipActivitySummary extends BaseActivitySummary {
+    private int version;
+    private float distanceMeters;
+    private float ascentMeters;
+    private float descentMeters;
+    private float minAltitude;
+    private float maxAltitude;
+    private int minLatitude;
+    private int maxLatitude;
+    private int minLongitude;
+    private int maxLongitude;
+    private long steps;
+    private long activeTimeSeconds;
+    private float caloriesBurnt;
+    private float maxSpeed;
+    private float minPace;
+    private float maxPace;
+    private float totalStride;
+    private long timeAscent;
+    private long timeDescent;
+    private long timeFlat;
+    private int averageHR;
+    private int averagePace;
+    private int averageStride;
+
+//    @Override
+//    public long getSteps() {
+//        return steps;
+//    }
+//
+//    @Override
+//    public float getDistanceMeters() {
+//        return distanceMeters;
+//    }
+//
+//    @Override
+//    public float getAscentMeters() {
+//        return ascentMeters;
+//    }
+//
+//    @Override
+//    public float getDescentMeters() {
+//        return descentMeters;
+//    }
+//
+//    @Override
+//    public float getMinAltitude() {
+//        return minAltitude;
+//    }
+//
+//    @Override
+//    public float getMaxAltitude() {
+//        return maxAltitude;
+//    }
+//
+//    @Override
+//    public float getCalories() {
+//        return caloriesBurnt;
+//    }
+//
+//    @Override
+//    public float getMaxSpeed() {
+//        return maxSpeed;
+//    }
+//
+//    @Override
+//    public float getMinSpeed() {
+//        return minPace;
+//    }
+//
+//    @Override
+//    public float getAverageSpeed() {
+//        return averagePace;
+//    }
+
+    public void setVersion(int version) {
+        this.version = version;
+    }
+
+    public int getVersion() {
+        return version;
+    }
+
+    public void setDistanceMeters(float distanceMeters) {
+        this.distanceMeters = distanceMeters;
+    }
+
+    public void setAscentMeters(float ascentMeters) {
+        this.ascentMeters = ascentMeters;
+    }
+
+    public void setDescentMeters(float descentMeters) {
+        this.descentMeters = descentMeters;
+    }
+
+    public void setMinAltitude(float minAltitude) {
+        this.minAltitude = minAltitude;
+    }
+
+    public void setMaxAltitude(float maxAltitude) {
+        this.maxAltitude = maxAltitude;
+    }
+
+    public void setMinLatitude(int minLatitude) {
+        this.minLatitude = minLatitude;
+    }
+
+    public void setMaxLatitude(int maxLatitude) {
+        this.maxLatitude = maxLatitude;
+    }
+
+    public void setMinLongitude(int minLongitude) {
+        this.minLongitude = minLongitude;
+    }
+
+    public void setMaxLongitude(int maxLongitude) {
+        this.maxLongitude = maxLongitude;
+    }
+
+    public void setSteps(long steps) {
+        this.steps = steps;
+    }
+
+    public void setActiveTimeSeconds(long activeTimeSeconds) {
+        this.activeTimeSeconds = activeTimeSeconds;
+    }
+
+    public void setCaloriesBurnt(float caloriesBurnt) {
+        this.caloriesBurnt = caloriesBurnt;
+    }
+
+    public void setMaxSpeed(float maxSpeed) {
+        this.maxSpeed = maxSpeed;
+    }
+
+    public void setMinPace(float minPace) {
+        this.minPace = minPace;
+    }
+
+    public void setMaxPace(float maxPace) {
+        this.maxPace = maxPace;
+    }
+
+    public void setTotalStride(float totalStride) {
+        this.totalStride = totalStride;
+    }
+
+    public float getTotalStride() {
+        return totalStride;
+    }
+
+    public void setTimeAscent(long timeAscent) {
+        this.timeAscent = timeAscent;
+    }
+
+    public long getTimeAscent() {
+        return timeAscent;
+    }
+
+    public void setTimeDescent(long timeDescent) {
+        this.timeDescent = timeDescent;
+    }
+
+    public long getTimeDescent() {
+        return timeDescent;
+    }
+
+    public void setTimeFlat(long timeFlat) {
+        this.timeFlat = timeFlat;
+    }
+
+    public long getTimeFlat() {
+        return timeFlat;
+    }
+
+    public void setAverageHR(int averageHR) {
+        this.averageHR = averageHR;
+    }
+
+    public int getAverageHR() {
+        return averageHR;
+    }
+
+    public void setAveragePace(int averagePace) {
+        this.averagePace = averagePace;
+    }
+
+    public int getAveragePace() {
+        return averagePace;
+    }
+
+    public void setAverageStride(int averageStride) {
+        this.averageStride = averageStride;
+    }
+
+    public int getAverageStride() {
+        return averageStride;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/asteroidos/AsteroidOSConstants.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/asteroidos/AsteroidOSConstants.java
new file mode 100644
index 0000000..dade95d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/asteroidos/AsteroidOSConstants.java
@@ -0,0 +1,137 @@
+/*  Copyright (C) 2022-2024 Noodlez
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.asteroidos;
+
+import java.util.UUID;
+
+/**
+ * A class to hold all the constants needed by the AsteroidOS devices
+ */
+public class AsteroidOSConstants {
+    /**
+     * A list of all the known supported codenames
+     */
+    public static final String[] KNOWN_DEVICE_CODENAMES = {
+        "bass", "sturgeon", "catfish", "catfish_ext",
+        "catshark", "lenok", "smelt", "carp",
+        "sparrow", "wren", "anthias", "beluga",
+        "dory", "firefish", "harmony", "inharmony",
+        "narwhal", "ray", "sawfish", "sawshark",
+        "skipjack", "tunny", "mooneye", "swift",
+        "minnow", "sprat", "tetra", "pike", "hoki",
+        "koi", "ayu"
+    };
+
+    /**
+     * AsteroidOS Service Watch Filter UUID
+     */
+    public static final UUID SERVICE_UUID               = UUID.fromString("00000000-0000-0000-0000-00A57E401D05");
+
+    /**
+     * Battery level service
+     */
+    public static final UUID BATTERY_SERVICE_UUID       = UUID.fromString("0000180F-0000-1000-8000-00805F9B34FB");
+    /**
+     * Battery level characteristic
+     */
+    public static final UUID BATTERY_UUID               = UUID.fromString("00002A19-0000-1000-8000-00805F9B34FB");
+
+    /**
+     * Time service
+     */
+    public static final UUID TIME_SERVICE_UUID          = UUID.fromString("00005071-0000-0000-0000-00A57E401D05");
+    /**
+     * Time characteristic
+     */
+    public static final UUID TIME_SET_CHAR              = UUID.fromString("00005001-0000-0000-0000-00A57E401D05");
+
+    /**
+     * Screenshot service
+     */
+    public static final UUID SCREENSHOT_SERVICE_UUID    = UUID.fromString("00006071-0000-0000-0000-00A57E401D05");
+    /**
+     * Screenshot request characteristic
+     */
+    public static final UUID SCREENSHOT_REQUEST_CHAR     = UUID.fromString("00006001-0000-0000-0000-00A57E401D05");
+    /**
+     * Screenshot content characteristic
+     */
+    public static final UUID SCREENSHOT_CONTENT_CHAR         = UUID.fromString("00006002-0000-0000-0000-00A57E401D05");
+
+    /**
+     * Media service
+     */
+    public static final UUID MEDIA_SERVICE_UUID         = UUID.fromString("00007071-0000-0000-0000-00A57E401D05");
+    /**
+     * Media title characteristic
+     */
+    public static final UUID MEDIA_TITLE_CHAR           = UUID.fromString("00007001-0000-0000-0000-00A57E401D05");
+    /**
+     * Media album characteristic
+     */
+    public static final UUID MEDIA_ALBUM_CHAR           = UUID.fromString("00007002-0000-0000-0000-00A57E401D05");
+    /**
+     * Media artist characteristic
+     */
+    public static final UUID MEDIA_ARTIST_CHAR          = UUID.fromString("00007003-0000-0000-0000-00A57E401D05");
+    /**
+     * Media playing status characteristic
+     */
+    public static final UUID MEDIA_PLAYING_CHAR         = UUID.fromString("00007004-0000-0000-0000-00A57E401D05");
+    /**
+     * Media command characteristic
+     */
+    public static final UUID MEDIA_COMMANDS_CHAR        = UUID.fromString("00007005-0000-0000-0000-00A57E401D05");
+    /**
+     * Media volume characteristic
+     */
+    public static final UUID MEDIA_VOLUME_CHAR          = UUID.fromString("00007006-0000-0000-0000-00A57E401D05");
+
+    /**
+     * Weather service
+     */
+    public static final UUID WEATHER_SERVICE_UUID       = UUID.fromString("00008071-0000-0000-0000-00A57E401D05");
+    /**
+     * Weather city name characteristic
+     */
+    public static final UUID WEATHER_CITY_CHAR          = UUID.fromString("00008001-0000-0000-0000-00A57E401D05");
+    /**
+     * Weather condition codes characteristic
+     */
+    public static final UUID WEATHER_IDS_CHAR           = UUID.fromString("00008002-0000-0000-0000-00A57E401D05");
+    /**
+     * Weather minimum temps characteristic
+     */
+    public static final UUID WEATHER_MIN_TEMPS_CHAR     = UUID.fromString("00008003-0000-0000-0000-00A57E401D05");
+    /**
+     * Weather maximum temps characteristic
+     */
+    public static final UUID WEATHER_MAX_TEMPS_CHAR     = UUID.fromString("00008004-0000-0000-0000-00A57E401D05");
+
+    /**
+     * Notification service
+     */
+    public static final UUID NOTIFICATION_SERVICE_UUID  = UUID.fromString("00009071-0000-0000-0000-00A57E401D05");
+    /**
+     * Notification update characteristic
+     */
+    public static final UUID NOTIFICATION_UPDATE_CHAR   = UUID.fromString("00009001-0000-0000-0000-00A57E401D05");
+    /**
+     * Notification feedback characteristic
+     */
+    public static final UUID NOTIFICATION_FEEDBACK_CHAR = UUID.fromString("00009002-0000-0000-0000-00A57E401D05");
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/asteroidos/AsteroidOSDeviceCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/asteroidos/AsteroidOSDeviceCoordinator.java
new file mode 100644
index 0000000..951991d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/asteroidos/AsteroidOSDeviceCoordinator.java
@@ -0,0 +1,98 @@
+/*  Copyright (C) 2022-2024 Damien Gaignon, Daniel Dakhno, Jos茅 Rebelo,
+    Noodlez
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.asteroidos;
+
+import android.bluetooth.le.ScanFilter;
+import android.os.ParcelUuid;
+
+import androidx.annotation.NonNull;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.GBException;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.AbstractDeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.asteroidos.AsteroidOSDeviceSupport;
+
+public class AsteroidOSDeviceCoordinator extends AbstractDeviceCoordinator {
+
+    @Override
+    public String getManufacturer() {
+        return "AsteroidOS";
+    }
+
+    @NonNull
+    @Override
+    public Collection<? extends ScanFilter> createBLEScanFilters() {
+        ParcelUuid asteroidUUID = ParcelUuid.fromString(AsteroidOSConstants.SERVICE_UUID.toString());
+        ScanFilter serviceFilter = new ScanFilter.Builder().setServiceUuid(asteroidUUID).build();
+
+        List<ScanFilter> filters = new ArrayList<>();
+        filters.add(serviceFilter);
+
+        return filters;
+    }
+
+    @Override
+    protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
+    }
+
+    @Override
+    public boolean supports(GBDeviceCandidate candidate) {
+        if (candidate.supportsService(AsteroidOSConstants.SERVICE_UUID))
+            return true;
+        // Some devices still can't see the service
+        for (String codename : AsteroidOSConstants.KNOWN_DEVICE_CODENAMES)
+            if (candidate.getName().equals(codename))
+                return true;
+        return false;
+    }
+
+    @Override
+    public boolean supportsWeather() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsFindDevice() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsMusicInfo() {
+        return true;
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return AsteroidOSDeviceSupport.class;
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_asteroidos;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/asteroidos/AsteroidOSMediaCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/asteroidos/AsteroidOSMediaCommand.java
new file mode 100644
index 0000000..316efd2
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/asteroidos/AsteroidOSMediaCommand.java
@@ -0,0 +1,61 @@
+/*  Copyright (C) 2022-2024 Noodlez
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.asteroidos;
+
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventMusicControl;
+
+/**
+ * An adapter class for the media commands sent by AsteroidOS
+ */
+public class AsteroidOSMediaCommand {
+    public static final byte COMMAND_PREVIOUS = 0x0;
+    public static final byte COMMAND_NEXT = 0x1;
+    public static final byte COMMAND_PLAY = 0x2;
+    public static final byte COMMAND_PAUSE = 0x3;
+    public static final byte COMMAND_VOLUME = 0x4;
+
+    public byte command;
+    public AsteroidOSMediaCommand(byte value) {
+        command = value;
+    }
+
+    /**
+     * Convert the MediaCommand to a music control event
+     * @return the matching music control event
+     */
+    public GBDeviceEventMusicControl toMusicControlEvent() {
+        GBDeviceEventMusicControl event = new GBDeviceEventMusicControl();
+        switch (command) {
+            case COMMAND_PREVIOUS:
+                event.event = GBDeviceEventMusicControl.Event.PREVIOUS;
+                break;
+            case COMMAND_NEXT:
+                event.event = GBDeviceEventMusicControl.Event.NEXT;
+                break;
+            case COMMAND_PLAY:
+                event.event = GBDeviceEventMusicControl.Event.PLAY;
+                break;
+            case COMMAND_PAUSE:
+                event.event = GBDeviceEventMusicControl.Event.PAUSE;
+                break;
+            case COMMAND_VOLUME:
+            default:
+                event.event = GBDeviceEventMusicControl.Event.UNKNOWN;
+        }
+        return event;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/asteroidos/AsteroidOSNotification.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/asteroidos/AsteroidOSNotification.java
new file mode 100644
index 0000000..67a3ad2
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/asteroidos/AsteroidOSNotification.java
@@ -0,0 +1,176 @@
+/*  Copyright (C) 2022-2024 Noodlez
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.asteroidos;
+
+import androidx.annotation.NonNull;
+
+import java.util.Locale;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.NotificationType;
+
+/**
+ * An adapter for notifications on AsteroidOS
+ */
+public class AsteroidOSNotification {
+    private String packageName = null;
+    private Integer id;
+    private String applicationName = null;
+    private String body = null;
+    private String summary = null;
+    private String icon = null;
+    private Boolean remove = false;
+
+    /**
+     * The vibration strength of a notification
+     */
+    public enum VibrationStrength {
+        STRONG,
+        NORMAL,
+        RINGTONE,
+        NONE;
+
+        @NonNull
+        @Override
+        public String toString() {
+            return name().toLowerCase(Locale.ROOT);
+        }
+    }
+    private VibrationStrength vibrationStrength = VibrationStrength.NORMAL;
+
+    /**
+     * Creates a notification
+     * @param spec The NotificationSpec to create the notification from
+     */
+    public AsteroidOSNotification(NotificationSpec spec) {
+        this.body = spec.body;
+        this.applicationName = spec.sourceName;
+        this.summary = spec.subject;
+        this.id = spec.getId();
+        this.packageName = spec.sourceAppId;
+        this.icon = this.gbNotificationTypeToIcon(spec.type);
+    }
+
+    /**
+     * Creates a call notification
+     * @param callSpec The callSpec given by the device support
+     */
+    public AsteroidOSNotification(CallSpec callSpec) {
+        switch (callSpec.command) {
+            case CallSpec.CALL_INCOMING:
+                this.applicationName = GBApplication.getContext().getString(R.string.pref_screen_notification_profile_incoming_call);
+                this.summary = callSpec.name;
+                this.body = callSpec.number;
+                this.vibrationStrength = VibrationStrength.RINGTONE;
+                this.id = (callSpec.name + callSpec.number).hashCode();
+                this.icon = "ios-call-outline";
+                break;
+            case CallSpec.CALL_OUTGOING:
+                break;
+            case CallSpec.CALL_REJECT:
+            case CallSpec.CALL_ACCEPT:
+            case CallSpec.CALL_END:
+            case CallSpec.CALL_START:
+            case CallSpec.CALL_UNDEFINED:
+            default:
+                this.id = (callSpec.name + callSpec.number).hashCode();
+                this.remove = true;
+        }
+    }
+
+    /**
+     * Creates a "remove" notification
+     * @param id Notification ID to remove
+     */
+    public AsteroidOSNotification(int id) {
+        this.id = id;
+        this.remove = true;
+    }
+
+    /**
+     * Converts the notification to a string to be sent to the device
+     */
+    @NonNull
+    @Override
+    public String toString() {
+        if (remove) {
+            return "<removed><id>" + this.id + "</id></removed>";
+        }
+        String retString = "";
+        retString += "<insert>";
+        if (id != null)
+            retString += "<id>" + id + "</id>";
+        retString += "<vb>" + vibrationStrength.toString() + "</vb>";
+        if (packageName != null)
+            retString += "<pn>" + packageName + "</pn>";
+        if (applicationName != null)
+            retString += "<an>" + applicationName + "</an>";
+        if (icon != null)
+            retString += "<ai>" + icon + "</ai>";
+        if (summary != null)
+            retString += "<su>" + summary + "</su>";
+        if (body != null)
+            retString += "<bo>" + body + "</bo>";
+        retString += "</insert>";
+        return retString;
+    }
+
+    public String gbNotificationTypeToIcon(NotificationType type) {
+        switch (type) {
+            // Logos
+            case FACEBOOK:
+                return "logo-facebook";
+            case INSTAGRAM:
+                return "logo-instagram";
+            case LINKEDIN:
+                return "logo-linkedin";
+            case SIGNAL:
+                return "logo-signal";
+            case SKYPE:
+                return "logo-skype";
+            case SNAPCHAT:
+                return "logo-snapchat";
+            case TWITTER:
+                return "logo-twitter";
+            case WHATSAPP:
+                return "logo-whatsapp";
+            case YAHOO_MAIL:
+                return "logo-yahoo";
+            // Generic
+            default:
+                switch (type.getGenericType()) {
+                    case "generic_email":
+                        return "ios-mail-outline";
+                    case "generic_navigation":
+                        return "ios-navigate-outline";
+                    case "generic_sms":
+                        return "ios-chatboxes-outline";
+                    case "generic_alarm_clock":
+                        return "ios-alarm-outline";
+                    case "generic_social":
+                        return "ios-people-outline";
+                    case "generic_chat":
+                        return "ios-chatbubbles-outline";
+                    default:
+                        return "ios-notifications-outline";
+                }
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/asteroidos/AsteroidOSWeather.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/asteroidos/AsteroidOSWeather.java
new file mode 100644
index 0000000..655b0d8
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/asteroidos/AsteroidOSWeather.java
@@ -0,0 +1,136 @@
+/*  Copyright (C) 2022-2024 Jos茅 Rebelo, Noodlez
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.asteroidos;
+
+import java.io.ByteArrayOutputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+
+import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
+
+
+/**
+ * An adapter class for weather
+ */
+public class AsteroidOSWeather {
+    /**
+     * Provides a day's worth of weather
+     */
+    public static class Day {
+        /**
+         * The minimum temp of the day
+         */
+        public int minTemp;
+        /**
+         * The maximum temp of the day
+         */
+        public int maxTemp;
+        /**
+         * The current OWM weather condition code
+         */
+        public int condition;
+
+        /**
+         * Creates a Day from the forecast given
+         * @param forecast A day in the weather forecast
+         */
+        public Day(WeatherSpec.Daily forecast) {
+            minTemp = forecast.minTemp;
+            maxTemp = forecast.maxTemp;
+            condition = forecast.conditionCode;
+        }
+
+        /**
+         * Creates a Day from the WeatherSpec given
+         * @param spec The weather spec itself
+         */
+        public Day(WeatherSpec spec) {
+            minTemp = spec.todayMinTemp;
+            maxTemp = spec.todayMaxTemp;
+            condition = spec.currentConditionCode;
+        }
+    }
+
+    /**
+     * The days of the weather
+     */
+    public ArrayList<Day> days = new ArrayList<>();
+    /**
+     * The city name of the weather
+     */
+    public String cityName;
+
+
+    /**
+     * Creates an AsteroidOSWeather from the WeatherSpec given
+     * @param spec The WeatherSpec given to the device support class
+     */
+    public AsteroidOSWeather(WeatherSpec spec) {
+        cityName = spec.location;
+        days.add(new Day(spec));
+        for (int i = 1; i < spec.forecasts.size(); i++) {
+            days.add(new Day(spec.forecasts.get(i - 1)));
+        }
+    }
+
+    /**
+     * Returns a byte array of the city name
+     * @return a byte array of the city name
+     */
+    public byte[] getCityName() {
+        return cityName.getBytes(StandardCharsets.UTF_8);
+    }
+
+    /**
+     * Parses the days' weather conditions and returns them in a format AsteroidOS can handle
+     * @return a byte array to be sent to the device
+     */
+    public byte[] getWeatherConditions() {
+        ByteArrayOutputStream stream = new ByteArrayOutputStream();
+        for (Day day : days) {
+            stream.write((byte) (day.condition >> 8));
+            stream.write((byte) (day.condition));
+        }
+        return stream.toByteArray();
+    }
+
+    /**
+     * Parses the days' min temps and returns them in a format AsteroidOS can handle
+     * @return a byte array to be sent to the device
+     */
+    public byte[] getMinTemps() {
+        ByteArrayOutputStream stream = new ByteArrayOutputStream();
+        for (Day day : days) {
+            stream.write((byte) (day.minTemp >> 8));
+            stream.write((byte) (day.minTemp));
+        }
+        return stream.toByteArray();
+    }
+
+    /**
+     * Parses the days' max temps and returns them in a format AsteroidOS can handle
+     * @return a byte array to be sent to the device
+     */
+    public byte[] getMaxTemps() {
+        ByteArrayOutputStream stream = new ByteArrayOutputStream();
+        for (Day day : days) {
+            stream.write((byte) (day.maxTemp >> 8));
+            stream.write((byte) (day.maxTemp));
+        }
+        return stream.toByteArray();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/bandwpseries/BandWPSeriesDeviceCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/bandwpseries/BandWPSeriesDeviceCoordinator.java
new file mode 100644
index 0000000..ea816ba
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/bandwpseries/BandWPSeriesDeviceCoordinator.java
@@ -0,0 +1,63 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.bandwpseries;
+
+import androidx.annotation.NonNull;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLEDeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.BatteryConfig;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.bandwpseries.BandWPSeriesDeviceSupport;
+
+public class BandWPSeriesDeviceCoordinator extends AbstractBLEDeviceCoordinator {
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_bandw_pseries;
+    }
+
+    @Override
+    public String getManufacturer() {
+        return "Bowers and Wilkins";
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return BandWPSeriesDeviceSupport.class;
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("LE_BWHP");
+    }
+
+    @Override
+    protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) {
+    }
+
+    @Override
+    public int getBatteryCount() {
+        return 3;
+    }
+
+    public BatteryConfig[] getBatteryConfig(final GBDevice device) {
+        BatteryConfig battery0 = new BatteryConfig(0, R.drawable.ic_earbuds_battery, R.string.left_earbud);
+        BatteryConfig battery1 = new BatteryConfig(1, R.drawable.ic_earbuds_battery, R.string.right_earbud);
+        BatteryConfig battery2 = new BatteryConfig(2, R.drawable.ic_tws_case, R.string.battery_case);
+        return new BatteryConfig[]{battery0, battery1, battery2};
+    }
+
+    @Override
+    public int[] getSupportedDeviceSpecificSettings(GBDevice device) {
+        return new int[] {
+                R.xml.devicesettings_active_noise_cancelling_toggle,
+                R.xml.devicesettings_bandw_pseries,
+                R.xml.devicesettings_wear_sensor_toggle
+        };
+    }
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/banglejs/AppsManagementActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/banglejs/AppsManagementActivity.java
new file mode 100644
index 0000000..9137fc6
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/banglejs/AppsManagementActivity.java
@@ -0,0 +1,346 @@
+/*  Copyright (C) 2022-2024 Andreas Shimokawa, Daniel Dakhno, Gordon Williams
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.banglejs;
+
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_DEVICE_INTERNET_ACCESS;
+
+import android.app.Activity;
+import android.app.DownloadManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Environment;
+import android.util.Base64;
+import android.view.LayoutInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.webkit.DownloadListener;
+import android.webkit.JavascriptInterface;
+import android.webkit.PermissionRequest;
+import android.webkit.ValueCallback;
+import android.webkit.WebChromeClient;
+import android.webkit.WebResourceRequest;
+import android.webkit.WebSettings;
+import android.webkit.WebView;
+import android.webkit.WebViewClient;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.ListView;
+import android.widget.PopupMenu;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import org.json.JSONArray;
+import org.json.JSONObject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBActivity;
+import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.banglejs.BangleJSDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.QHybridSupport;
+import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_BANGLEJS_WEBVIEW_URL;
+
+public class AppsManagementActivity extends AbstractGBActivity {
+    private static final Logger LOG = LoggerFactory.getLogger(AppsManagementActivity.class);
+
+    private WebView webView;
+    private GBDevice mGBDevice;
+    private DeviceCoordinator mCoordinator;
+    /// It seems we can get duplicate broadcasts sometimes - so this helps to avoid that
+    private int deviceRxSeq = -1;
+    /// When a file chooser has been opened in the WebView, this is what should get called
+    private ValueCallback<Uri[]> fileChooserCallback;
+    /// used by showSaveFileDialog for file data
+    private byte[] fileSaveData;
+
+    // Request code for creating a PDF document.
+    private static final int CHOOSE_FILE = 0;
+    private static final int CREATE_FILE = 1;
+
+    public AppsManagementActivity() {
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_banglejs_apps_management);
+
+        Intent intent = getIntent();
+        Bundle bundle = intent.getExtras();
+        if (bundle != null) {
+            mGBDevice = bundle.getParcelable(GBDevice.EXTRA_DEVICE);
+        } else {
+            throw new IllegalArgumentException("Must provide a device when invoking this activity");
+        }
+        mCoordinator = mGBDevice.getDeviceCoordinator();
+    }
+
+    private void toast(String data) {
+        GB.toast(data, Toast.LENGTH_LONG, GB.INFO);
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        if (webView!=null) return; // already set up
+        IntentFilter commandFilter = new IntentFilter();
+        commandFilter.addAction(GBDevice.ACTION_DEVICE_CHANGED);
+        commandFilter.addAction(BangleJSDeviceSupport.BANGLEJS_COMMAND_RX);
+        LocalBroadcastManager.getInstance(this).registerReceiver(deviceUpdateReceiver, commandFilter);
+        initViews();
+    }
+
+    @Override
+    protected void onDestroy() {
+        webView.destroy();
+        webView = null;
+        LocalBroadcastManager.getInstance(this).unregisterReceiver(deviceUpdateReceiver);
+        super.onDestroy();
+        finish();
+    }
+
+    BroadcastReceiver deviceUpdateReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            switch (intent.getAction()) {
+                case BangleJSDeviceSupport.BANGLEJS_COMMAND_RX: {
+                    String data = String.valueOf(intent.getExtras().get("DATA"));
+                    int seq = intent.getIntExtra("SEQ",0);
+                    LOG.info("WebView TX: " + data + "("+seq+")");
+                    if (seq==deviceRxSeq) {
+                        LOG.info("WebView TX DUPLICATE AND IGNORED");
+                    } else {
+                        deviceRxSeq = seq;
+                        bangleRxData(data);
+                    }
+                    break;
+                }
+            }
+        }
+    };
+
+    public class WebViewInterface {
+        Context mContext;
+
+        WebViewInterface(Context c) {
+            mContext = c;
+        }
+
+        /// Called from the WebView when data needs to be sent to the Bangle
+        @JavascriptInterface
+        public void bangleTx(String data) {
+            LOG.info("WebView RX: " + data);
+            bangleTxData(data);
+        }
+
+        /// Called from the WebView to download a file
+        @JavascriptInterface
+        public void saveFile(String filename, String mimetype, String b64) {
+            LOG.info("WebView save file: {}", filename);
+            showSaveFileDialog(filename, mimetype, Base64.decode(b64, Base64.DEFAULT));
+        }
+     }
+
+    // Called when data received from Bangle.js - push data to the WebView
+    public void bangleRxData(String data) {
+        JSONArray s = new JSONArray();
+        s.put(data);
+        String ss = s.toString();
+        final String js = "bangleRx("+ss.substring(1, ss.length()-1)+");";
+        LOG.info("WebView TX cmd: " + js);
+        if (webView!=null) webView.post(new Runnable() {
+            @Override
+            public void run() {
+                if (webView==null) return; // webView may have gone by the time we get called!
+                webView.evaluateJavascript(js, null);
+            }
+        });
+    }
+
+    // Called to send data to Bangle.js
+    public void bangleTxData(String data) {
+        Intent intent = new Intent(BangleJSDeviceSupport.BANGLEJS_COMMAND_TX);
+        intent.putExtra("DATA", data);
+        LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
+    }
+
+    public void showSaveFileDialog(String fileName, String mimeType, byte[] data) {
+        Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
+        intent.addCategory(Intent.CATEGORY_OPENABLE);
+        if (mimeType!=null) intent.setType(mimeType);
+        if (fileName!=null) intent.putExtra(Intent.EXTRA_TITLE, fileName);
+        fileSaveData = data;
+        startActivityForResult(intent, CREATE_FILE);
+        // see onActivityResult
+    }
+
+    private void initViews() {
+        //https://stackoverflow.com/questions/4325639/android-calling-javascript-functions-in-webview
+        webView = findViewById(R.id.webview);
+        webView.setWebViewClient(new WebViewClient());
+        WebSettings settings = webView.getSettings();
+        settings.setJavaScriptEnabled(true);
+        settings.setDatabaseEnabled(true);
+        settings.setDomStorageEnabled(true);
+        settings.setUseWideViewPort(true);
+        settings.setLoadWithOverviewMode(true);
+        String databasePath = this.getApplicationContext().getDir("database", Context.MODE_PRIVATE).getPath();
+        settings.setDatabasePath(databasePath);
+        webView.addJavascriptInterface(new WebViewInterface(this), "Android");
+        webView.setWebContentsDebuggingEnabled(true); // FIXME
+
+        Prefs devicePrefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(mGBDevice.getAddress()));
+        String url = devicePrefs.getString(PREF_BANGLEJS_WEBVIEW_URL, "").trim();
+        if (url.isEmpty()) url = "https://banglejs.com/apps/android.html";
+        webView.loadUrl(url);
+
+        webView.setWebViewClient(new WebViewClient(){
+            public void onPageFinished(WebView view, String weburl){
+                //webView.loadUrl("javascript:showToast('WebView in Espruino')");
+            }
+            @Override
+            public boolean shouldOverrideUrlLoading(WebView vw, WebResourceRequest request) {
+                Intent intent = new Intent(Intent.ACTION_VIEW, request.getUrl());
+                vw.getContext().startActivity(intent);
+                return true;
+            }
+
+            @Override
+            public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
+                Toast.makeText(AppsManagementActivity.this, "Error:" + description, Toast.LENGTH_SHORT).show();
+                view.loadUrl("about:blank");
+            }
+        });
+        webView.setWebChromeClient(new WebChromeClient() {
+            @Override
+            public void onPermissionRequest(final PermissionRequest request) {
+                request.grant(request.getResources());
+            }
+
+            @Override
+            public boolean onShowFileChooser(WebView vw, ValueCallback<Uri[]> filePathCallback,
+                                             FileChooserParams fileChooserParams) {
+                if (fileChooserCallback != null) {
+                    fileChooserCallback.onReceiveValue(null);
+                }
+                fileChooserCallback = filePathCallback;
+
+                Intent selectionIntent = new Intent(Intent.ACTION_GET_CONTENT);
+                selectionIntent.addCategory(Intent.CATEGORY_OPENABLE);
+                String[] acceptedTypes = fileChooserParams.getAcceptTypes();
+                if ((acceptedTypes!=null) && acceptedTypes.length>0 && acceptedTypes[0].contains("/"))
+                    selectionIntent.setType(acceptedTypes[0]);
+                else
+                    selectionIntent.setType("*/*");
+
+                Intent chooserIntent = new Intent(Intent.ACTION_CHOOSER);
+                chooserIntent.putExtra(Intent.EXTRA_INTENT, selectionIntent);
+                startActivityForResult(chooserIntent, CHOOSE_FILE);
+                // see onActivityResult
+                return true;
+            }
+
+
+        });
+        webView.setDownloadListener(new DownloadListener() {
+            public void onDownloadStart(String url, String userAgent,
+                                        String contentDisposition, String mimetype,
+                                        long contentLength) {
+                if (url == null) return;
+                // FIXME: When we get to this point we have no idea what the filename is
+                // maybe we should just add 'Android.downloadFile' and call that from the App Loader
+                if(url.startsWith("blob:")) {
+                    LOG.error("WebView blob: download requested, but not supported");
+                    // This should be possible with https://stackoverflow.com/a/48954970/1215872
+                    // but having tried the XMLHttpRequest can't find the blob - I think because the blob ends
+                    // up being created inside an iframe, and the data isn't accessible from the
+                    // external one
+                } else if(url.startsWith("data:") && url.contains(";base64,")) {
+                    int commaIdx = url.indexOf(";base64,");
+                    if (commaIdx>=0) {
+                        String b64 = url.substring(commaIdx + 8);
+                        byte data[] = Base64.decode(b64, Base64.DEFAULT);
+                        showSaveFileDialog(null, mimetype, data);
+                    } else LOG.error("WebView data: download requested, but not base64");
+                } else { // just view in the web browser
+                    Intent i = new Intent(Intent.ACTION_VIEW);
+                    i.setData(Uri.parse(url));
+                    startActivity(i);
+                }
+            }
+        });
+    }
+
+    @Override // for file chooser results
+    protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
+        super.onActivityResult(requestCode, resultCode, intent);
+        if (requestCode == CHOOSE_FILE) { // onShowFileChooser
+            if (fileChooserCallback == null) return;
+            Uri[] results = null;
+            if (resultCode == Activity.RESULT_OK)
+                results = new Uri[]{Uri.parse(intent.getDataString())};
+            fileChooserCallback.onReceiveValue(results);
+            fileChooserCallback = null;
+        }
+        if (requestCode == CREATE_FILE) { // showSaveFileDialog
+            if (resultCode == Activity.RESULT_CANCELED || intent == null || intent.getData() == null) {
+                return;
+            }
+
+            OutputStream os = null;
+            try {
+                os = getContentResolver().openOutputStream(intent.getData());
+                os.write(fileSaveData);
+                os.close();
+            } catch (IOException e) {
+                e.printStackTrace();
+            } finally {
+                try {
+                    os.close();
+                } catch (IOException e) {
+                }
+            }
+            fileSaveData = null;
+        }
+    }
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/banglejs/BangleJSActivityPoint.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/banglejs/BangleJSActivityPoint.java
new file mode 100644
index 0000000..e073821
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/banglejs/BangleJSActivityPoint.java
@@ -0,0 +1,241 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.banglejs;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.LinkedList;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityPoint;
+import nodomain.freeyourgadget.gadgetbridge.model.GPSCoordinate;
+import nodomain.freeyourgadget.gadgetbridge.util.GBToStringBuilder;
+
+public class BangleJSActivityPoint {
+    private static final Logger LOG = LoggerFactory.getLogger(BangleJSActivityPoint.class);
+
+    private final GPSCoordinate location;
+    private final long time;
+    private final int heartRate;
+    private final int hrConfidence;
+    private final String hrSource;
+    private final int steps;
+    private final int batteryPercentage;
+    private final double batteryVoltage;
+    private final boolean charging;
+    private final double barometerTemperature;
+    private final double barometerPressure;
+    private final double barometerAltitude;
+
+    public BangleJSActivityPoint(final long time,
+                                 final GPSCoordinate location,
+                                 final int heartRate,
+                                 final int hrConfidence,
+                                 final String hrSource,
+                                 final int steps,
+                                 final int batteryPercentage,
+                                 final double batteryVoltage,
+                                 final boolean charging,
+                                 final double barometerTemperature,
+                                 final double barometerPressure,
+                                 final double barometerAltitude) {
+        this.time = time;
+        this.location = location;
+        this.heartRate = heartRate;
+        this.hrConfidence = hrConfidence;
+        this.hrSource = hrSource;
+        this.steps = steps;
+        this.batteryPercentage = batteryPercentage;
+        this.batteryVoltage = batteryVoltage;
+        this.charging = charging;
+        this.barometerTemperature = barometerTemperature;
+        this.barometerPressure = barometerPressure;
+        this.barometerAltitude = barometerAltitude;
+    }
+
+    public long getTime() {
+        return time;
+    }
+
+    @Nullable
+    public GPSCoordinate getLocation() {
+        return location;
+    }
+
+    public int getHeartRate() {
+        return heartRate;
+    }
+
+    public int getHrConfidence() {
+        return hrConfidence;
+    }
+
+    public String getHrSource() {
+        return hrSource;
+    }
+
+    public int getSteps() {
+        return steps;
+    }
+
+    public int getBatteryPercentage() {
+        return batteryPercentage;
+    }
+
+    public double getBatteryVoltage() {
+        return batteryVoltage;
+    }
+
+    public boolean isCharging() {
+        return charging;
+    }
+
+    public double getBarometerTemperature() {
+        return barometerTemperature;
+    }
+
+    public double getBarometerPressure() {
+        return barometerPressure;
+    }
+
+    public double getBarometerAltitude() {
+        return barometerAltitude;
+    }
+
+    public ActivityPoint toActivityPoint() {
+        final ActivityPoint activityPoint = new ActivityPoint();
+        activityPoint.setTime(new Date(time));
+        if (heartRate > 0) {
+            activityPoint.setHeartRate(heartRate);
+        }
+        if (location != null) {
+            activityPoint.setLocation(location);
+        }
+        return activityPoint;
+    }
+
+    @NonNull
+    @Override
+    public String toString() {
+        final GBToStringBuilder tsb = new GBToStringBuilder(this);
+        tsb.append("location", location);
+        tsb.append("time", time);
+        tsb.append("heartRate", heartRate);
+        tsb.append("hrConfidence", hrConfidence);
+        tsb.append("hrSource", hrSource);
+        tsb.append("steps", steps);
+        tsb.append("batteryPercentage", batteryPercentage);
+        tsb.append("batteryVoltage", batteryVoltage);
+        tsb.append("charging", charging);
+        tsb.append("barometerTemperature", barometerTemperature);
+        tsb.append("barometerPressure", barometerPressure);
+        tsb.append("barometerAltitude", barometerAltitude);
+        return tsb.toString();
+    }
+
+    public static List<BangleJSActivityPoint> fromCsv(final File inputFile) {
+        final List<BangleJSActivityPoint> points = new LinkedList<>();
+        try (BufferedReader reader = new BufferedReader(new FileReader(inputFile))) {
+            final List<String> header = Arrays.asList(reader.readLine().split(","));
+            String line;
+            while ((line = reader.readLine()) != null) {
+                points.add(BangleJSActivityPoint.fromCsvLine(header, line));
+            }
+        } catch (final IOException e) {
+            LOG.error("Failed to read {}", inputFile);
+            return null;
+        }
+
+        return points;
+    }
+
+    /**
+     * This parses all the standard fields from the <a href="https://github.com/espruino/BangleApps/blob/master/apps/recorder/widget.js">recorder</a>.
+     * Some apps such as bthrm add extra fields or modify others. We attempt to gracefully handle other formats (eg. source being "int" or "bthrm").
+     */
+    @Nullable
+    @VisibleForTesting
+    public static BangleJSActivityPoint fromCsvLine(final List<String> header, final String csvLine) {
+        final String[] split = csvLine.trim().replace(",", ", ").split(",");
+        if (split.length != header.size()) {
+            LOG.error("csv line {} length {} differs from header {} length {}", csvLine, split.length, header, header.size());
+            return null;
+        }
+        for (int i = 0; i < split.length; i++) {
+            split[i] = split[i].strip();
+        }
+
+        final int idxTime = header.indexOf("Time");
+        final int idxLatitude = header.indexOf("Latitude");
+        final int idxLongitude = header.indexOf("Longitude");
+        final int idxAltitude = header.indexOf("Altitude");
+        final int idxHeartrate = header.indexOf("Heartrate");
+        final int idxConfidence = header.indexOf("Confidence");
+        final int idxSource = header.indexOf("Source");
+        final int idxSteps = header.indexOf("Steps");
+        final int idxBatteryPercentage = header.indexOf("Battery Percentage");
+        final int idxBatteryVoltage = header.indexOf("Battery Voltage");
+        final int idxCharging = header.indexOf("Charging");
+        final int idxBarometerTemperature = header.indexOf("Barometer Temperature");
+        final int idxBarometerPressure = header.indexOf("Barometer Pressure");
+        final int idxBarometerAltitude = header.indexOf("Barometer Altitude");
+
+        final long time = idxTime >= 0 && StringUtils.isNotBlank(split[idxTime]) ? ((long) (Double.parseDouble(split[idxTime]) * 1000L)) : 0L;
+
+        try {
+            final GPSCoordinate location;
+            if (idxLatitude >= 0 && StringUtils.isNotBlank(split[idxLatitude]) && idxLongitude >= 0 && StringUtils.isNotBlank(split[idxLongitude])) {
+                final double latitude = Double.parseDouble(split[idxLatitude]);
+                final double longitude = Double.parseDouble(split[idxLongitude]);
+                final double altitude;
+                if (idxAltitude >= 0 && StringUtils.isNotBlank(split[idxAltitude])) {
+                    altitude = Double.parseDouble(split[idxAltitude]);
+                } else {
+                    altitude = GPSCoordinate.UNKNOWN_ALTITUDE;
+                }
+                location = new GPSCoordinate(longitude, latitude, altitude);
+            } else {
+                location = null;
+            }
+            final int heartRate = idxHeartrate >= 0 && StringUtils.isNotBlank(split[idxHeartrate]) ? (int) Math.round(Double.parseDouble(split[idxHeartrate])) : 0;
+            final int confidence = idxConfidence >= 0 && StringUtils.isNotBlank(split[idxConfidence]) ? Integer.parseInt(split[idxConfidence]) : 0;
+            final String source = idxSource >= 0 && StringUtils.isNotBlank(split[idxSource]) ? split[idxSource] : "";
+            final int steps = idxSteps >= 0 && StringUtils.isNotBlank(split[idxSteps]) ? Integer.parseInt(split[idxSteps]) : 0;
+            final int batteryPercentage = idxBatteryPercentage >= 0 && StringUtils.isNotBlank(split[idxBatteryPercentage]) ? Integer.parseInt(split[idxBatteryPercentage]) : -1;
+            final double batteryVoltage = idxBatteryVoltage >= 0 && StringUtils.isNotBlank(split[idxBatteryVoltage]) ? Double.parseDouble(split[idxBatteryVoltage]) : -1;
+            final boolean charging = idxCharging >= 0 && StringUtils.isNotBlank(split[idxCharging]) && Boolean.parseBoolean(split[idxCharging]);
+            final double barometerTemperature = idxBarometerTemperature >= 0 && StringUtils.isNotBlank(split[idxBarometerTemperature]) ? Double.parseDouble(split[idxBarometerTemperature]) : 0;
+            final double barometerPressure = idxBarometerPressure >= 0 && StringUtils.isNotBlank(split[idxBarometerPressure]) ? Double.parseDouble(split[idxBarometerPressure]) : 0;
+            final double barometerAltitude = idxBarometerAltitude >= 0 && StringUtils.isNotBlank(split[idxBarometerAltitude]) ? Double.parseDouble(split[idxBarometerAltitude]) : GPSCoordinate.UNKNOWN_ALTITUDE;
+
+            return new BangleJSActivityPoint(
+                    time,
+                    location,
+                    heartRate,
+                    confidence,
+                    source,
+                    steps,
+                    batteryPercentage,
+                    batteryVoltage,
+                    charging,
+                    barometerTemperature,
+                    barometerPressure,
+                    barometerAltitude
+            );
+        } catch (final Exception e) {
+            LOG.error("failed to parse '{}'", csvLine, e);
+            // Salvage the time at least
+            return new BangleJSActivityPoint(time, null, 0, 0, "", 0, -1, -1, false, 0, 0, 0);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/banglejs/BangleJSConstants.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/banglejs/BangleJSConstants.java
new file mode 100644
index 0000000..6f1858f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/banglejs/BangleJSConstants.java
@@ -0,0 +1,32 @@
+/*  Copyright (C) 2019-2024 Gordon Williams, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.banglejs;
+
+import java.util.UUID;
+
+public final class BangleJSConstants {
+
+
+    public static final UUID UUID_SERVICE_NORDIC_UART = UUID.fromString("6e400001-b5a3-f393-e0a9-e50e24dcca9e");
+    public static final UUID UUID_CHARACTERISTIC_NORDIC_UART_TX = UUID.fromString("6e400002-b5a3-f393-e0a9-e50e24dcca9e");
+    public static final UUID UUID_CHARACTERISTIC_NORDIC_UART_RX = UUID.fromString("6e400003-b5a3-f393-e0a9-e50e24dcca9e");
+
+    public static final String PREF_BANGLEJS_ACTIVITY_FULL_SYNC_TRIGGER = "pref_banglejs_activity_full_sync_trigger";
+    public static final String PREF_BANGLEJS_ACTIVITY_FULL_SYNC_STATUS = "pref_banglejs_activity_full_sync_status";
+    public static final String PREF_BANGLEJS_ACTIVITY_FULL_SYNC_START = "pref_banglejs_activity_full_sync_start";
+    public static final String PREF_BANGLEJS_ACTIVITY_FULL_SYNC_STOP = "pref_banglejs_activity_full_sync_stop";
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/banglejs/BangleJSCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/banglejs/BangleJSCoordinator.java
new file mode 100644
index 0000000..6e8b26e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/banglejs/BangleJSCoordinator.java
@@ -0,0 +1,287 @@
+/*  Copyright (C) 2019-2024 Andreas Shimokawa, Damien Gaignon, Daniel Dakhno,
+    Gabriele Monaco, Ganblejs, glemco, Gordon Williams, Jos茅 Rebelo, LukasEdl,
+    Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.banglejs;
+
+import android.app.Activity;
+import android.bluetooth.le.ScanFilter;
+import android.content.Context;
+import android.net.Uri;
+import android.os.ParcelUuid;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import org.apache.commons.lang3.ArrayUtils;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.BuildConfig;
+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.SleepAsAndroidFeature;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryParser;
+import nodomain.freeyourgadget.gadgetbridge.model.BatteryConfig;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.banglejs.BangleJSDeviceSupport;
+
+public class BangleJSCoordinator extends AbstractBLEDeviceCoordinator {
+
+    @Override
+    public String getManufacturer() {
+        return "Espruino";
+    }
+
+    @NonNull
+    @Override
+    public Collection<? extends ScanFilter> createBLEScanFilters() {
+        // TODO: filter on name beginning Bangle.js? Doesn't appear to be built-in :(
+        // https://developer.android.com/reference/android/bluetooth/le/ScanFilter.Builder.html#setDeviceName(java.lang.String)
+        ParcelUuid hpService = new ParcelUuid(BangleJSConstants.UUID_SERVICE_NORDIC_UART);
+        ScanFilter filter = new ScanFilter.Builder().setServiceUuid(hpService).build();
+        return Collections.singletonList(filter);
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("Bangle\\.js.*|Pixl\\.js.*|Puck\\.js.*|MDBT42Q.*|Espruino.*");
+    }
+
+    @Override
+    public int getBondingStyle(){
+        // Let the user decide whether to bond or not after discovery.
+        return BONDING_STYLE_ASK;
+    }
+
+    @Override
+    public boolean supportsCalendarEvents() {
+        return true;
+    }
+
+    @Override
+    public int getCannedRepliesSlotCount(final GBDevice device) {
+        return 16;
+    }
+
+    @Override
+    public boolean supportsSleepAsAndroid() {
+        return true;
+    }
+
+    @Override
+    public Set<SleepAsAndroidFeature> getSleepAsAndroidFeatures() {
+        return EnumSet.of(SleepAsAndroidFeature.ACCELEROMETER, SleepAsAndroidFeature.HEART_RATE, SleepAsAndroidFeature.NOTIFICATIONS, SleepAsAndroidFeature.ALARMS);
+    }
+
+    @Nullable
+    @Override
+    public ActivitySummaryParser getActivitySummaryParser(final GBDevice device, final Context context) {
+        return new BangleJSWorkoutParser(context);
+    }
+
+    @Override
+    public boolean supportsRealtimeData()  {
+        return true;
+    }
+
+    @Override
+    public boolean supportsWeather() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsFindDevice() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsActivityDataFetching() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsActivityTracking() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsActivityTracks() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsScreenshots(final GBDevice device) {
+        return device.getModel() != null && device.getModel().equals("2");
+    }
+
+    @Override
+    public boolean supportsHeartRateMeasurement(GBDevice device) {
+        return true;
+    }
+
+    @Override
+    public boolean supportsManualHeartRateMeasurement(GBDevice device) {
+        /* we could do this, but the current code for onHeartRateTest
+        looks completely broken. There's no way to stop heart rate measurements
+        and it doesn't even appear to care what device it's getting the current
+        heart rate measurements from. Fixing it is too much work so disabling
+        for now.
+         */
+        return false;
+    }
+
+    @Override
+    public int getAlarmSlotCount(GBDevice device) {
+        return 10;
+    }
+
+    @Override
+    public boolean supportsAppsManagement(final GBDevice device) {
+        return BuildConfig.INTERNET_ACCESS;
+    }
+
+    @Override
+    public Class<? extends Activity> getAppsManagementActivity() {
+        return BuildConfig.INTERNET_ACCESS ? AppsManagementActivity.class : null;
+    }
+
+    @Override
+    public boolean suggestUnbindBeforePair() {
+        return false;
+    }
+
+    @Override
+    protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) {
+    }
+
+    @Override
+    public Class<? extends Activity> getPairingActivity() {
+        return null;
+    }
+
+    @Override
+    public SampleProvider<? extends ActivitySample> getSampleProvider(GBDevice device, DaoSession session) {
+        return new BangleJSSampleProvider(device, session);
+    }
+
+    @Override
+    public InstallHandler findInstallHandler(Uri uri, Context context) {
+        return null;
+    }
+
+    @Override
+    public boolean supportsUnicodeEmojis() {
+        /* we say yes here (because we can't get a handle to our device's prefs to check)
+        and then in 'renderUnicodeAsImage' we call EmojiConverter.convertUnicodeEmojiToAscii
+        just like DeviceCommunicationService.sanitizeNotifText would have done if we'd
+        reported false *if* conversion is disabled */
+        return true;
+    }
+
+    @Override
+    public BatteryConfig[] getBatteryConfig(final GBDevice device) {
+        return new BatteryConfig[]{
+                new BatteryConfig(
+                        0,
+                        GBDevice.BATTERY_ICON_DEFAULT,
+                        GBDevice.BATTERY_LABEL_DEFAULT,
+                        15,
+                        100
+                )
+        };
+    }
+
+    @Override
+    public int[] getSupportedDeviceSpecificSettings(final GBDevice device) {
+        final List<Integer> settings = new ArrayList<>();
+
+        settings.add(R.xml.devicesettings_banglejs_location);
+
+        settings.add(R.xml.devicesettings_header_notifications);
+        settings.add(R.xml.devicesettings_send_app_notifications);
+        settings.add(R.xml.devicesettings_notification_wake_on_open);
+        settings.add(R.xml.devicesettings_text_bitmaps);
+        settings.add(R.xml.devicesettings_transliteration);
+        settings.add(R.xml.devicesettings_canned_reply_16);
+
+        settings.add(R.xml.devicesettings_header_calendar);
+        settings.add(R.xml.devicesettings_sync_calendar);
+
+        settings.add(R.xml.devicesettings_header_connection);
+        settings.add(R.xml.devicesettings_high_mtu);
+        if (BuildConfig.INTERNET_ACCESS)
+            settings.add(R.xml.devicesettings_device_internet_access);
+
+        settings.add(R.xml.devicesettings_banglejs_activity);
+
+        settings.add(R.xml.devicesettings_header_apps);
+        settings.add(R.xml.devicesettings_loyalty_cards);
+
+        settings.add(R.xml.devicesettings_header_developer);
+        settings.add(R.xml.devicesettings_banglejs_apploader);
+        settings.add(R.xml.devicesettings_device_intents);
+
+        return ArrayUtils.toPrimitive(settings.toArray(new Integer[0]));
+    }
+
+    @Override
+    public DeviceSpecificSettingsCustomizer getDeviceSpecificSettingsCustomizer(final GBDevice device) {
+        return new BangleJSSettingsCustomizer(device);
+    }
+
+    @Override
+    public boolean supportsNavigation() {
+        return true;
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return BangleJSDeviceSupport.class;
+    }
+
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_banglejs;
+    }
+
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_banglejs;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_banglejs_disabled;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/banglejs/BangleJSSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/banglejs/BangleJSSampleProvider.java
new file mode 100644
index 0000000..304e590
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/banglejs/BangleJSSampleProvider.java
@@ -0,0 +1,127 @@
+/*  Copyright (C) 2020-2024 Gordon Williams, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.banglejs;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+import de.greenrobot.dao.AbstractDao;
+import de.greenrobot.dao.Property;
+import nodomain.freeyourgadget.gadgetbridge.devices.AbstractSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.BangleJSActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.entities.BangleJSActivitySampleDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+
+public class BangleJSSampleProvider extends AbstractSampleProvider<BangleJSActivitySample> {
+    private static final Logger LOG = LoggerFactory.getLogger(BangleJSSampleProvider.class);
+
+    public BangleJSSampleProvider(GBDevice device, DaoSession session) {
+        super(device, session);
+    }
+
+    @Override
+    public AbstractDao<BangleJSActivitySample, ?> getSampleDao() {
+        return getSession().getBangleJSActivitySampleDao();
+    }
+
+    @Nullable
+    @Override
+    protected Property getRawKindSampleProperty() {
+        return BangleJSActivitySampleDao.Properties.RawKind;
+    }
+
+    @NonNull
+    @Override
+    protected Property getTimestampSampleProperty() {
+        return BangleJSActivitySampleDao.Properties.Timestamp;
+    }
+
+    @NonNull
+    @Override
+    protected Property getDeviceIdentifierSampleProperty() {
+        return BangleJSActivitySampleDao.Properties.DeviceId;
+    }
+
+    @Override
+    public ActivityKind normalizeType(int rawType) {
+        return ActivityKind.fromCode(rawType);
+    }
+
+    @Override
+    public int toRawActivityKind(ActivityKind activityKind) {
+        return activityKind.getCode();
+    }
+
+    @Override
+    public float normalizeIntensity(int rawIntensity) {
+        return rawIntensity / 2048.0f;
+    }
+
+    @Override
+    public BangleJSActivitySample createActivitySample() {
+        return new BangleJSActivitySample();
+    }
+
+    /**
+     * Upserts a sample in the database, avoiding duplicated samples if a sample already exists in a
+     * close timestamp (within 2 minutes);
+     */
+    public void upsertSample(final BangleJSActivitySample sample) {
+        final List<BangleJSActivitySample> nearSamples = getGBActivitySamples(
+                sample.getTimestamp() - 60 * 2,
+                sample.getTimestamp() + 60 * 2
+        );
+
+        if (nearSamples.isEmpty()) {
+            // No nearest sample, just insert
+            LOG.debug("No duplicate found at {}, inserting", sample.getTimestamp());
+            addGBActivitySample(sample);
+            return;
+        }
+
+        BangleJSActivitySample nearestSample = nearSamples.get(0);
+
+        for (final BangleJSActivitySample s : nearSamples) {
+            final int curDist = Math.abs(nearestSample.getTimestamp() - s.getTimestamp());
+            final int newDist = Math.abs(sample.getTimestamp() - s.getTimestamp());
+            if (newDist < curDist) {
+                nearestSample = s;
+            }
+        }
+
+        LOG.debug("Found {} duplicates for {}, updating nearest sample at {}", nearSamples.size(), sample.getTimestamp(), nearestSample.getTimestamp());
+
+        if (sample.getHeartRate() != 0) {
+            nearestSample.setHeartRate(sample.getHeartRate());
+        }
+        if (sample.getSteps() != 0) {
+            nearestSample.setSteps(sample.getSteps());
+        }
+        if (sample.getRawIntensity() != 0) {
+            nearestSample.setRawIntensity(sample.getRawIntensity());
+        }
+
+        addGBActivitySample(nearestSample);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/banglejs/BangleJSSettingsCustomizer.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/banglejs/BangleJSSettingsCustomizer.java
new file mode 100644
index 0000000..8937e70
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/banglejs/BangleJSSettingsCustomizer.java
@@ -0,0 +1,141 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.banglejs;
+
+import static nodomain.freeyourgadget.gadgetbridge.devices.banglejs.BangleJSConstants.PREF_BANGLEJS_ACTIVITY_FULL_SYNC_START;
+import static nodomain.freeyourgadget.gadgetbridge.devices.banglejs.BangleJSConstants.PREF_BANGLEJS_ACTIVITY_FULL_SYNC_STATUS;
+import static nodomain.freeyourgadget.gadgetbridge.devices.banglejs.BangleJSConstants.PREF_BANGLEJS_ACTIVITY_FULL_SYNC_STOP;
+import static nodomain.freeyourgadget.gadgetbridge.devices.banglejs.BangleJSConstants.PREF_BANGLEJS_ACTIVITY_FULL_SYNC_TRIGGER;
+
+import android.app.AlertDialog;
+import android.app.ProgressDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.os.Parcel;
+
+import androidx.preference.EditTextPreference;
+import androidx.preference.Preference;
+
+import java.util.Collections;
+import java.util.Set;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsHandler;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+public class BangleJSSettingsCustomizer implements DeviceSpecificSettingsCustomizer {
+    private ProgressDialog activityFullSyncDialog;
+
+    final GBDevice device;
+
+    public BangleJSSettingsCustomizer(final GBDevice device) {
+        this.device = device;
+    }
+
+    @Override
+    public void onPreferenceChange(final Preference preference, final DeviceSpecificSettingsHandler handler) {
+        // Handle full sync status
+        if (preference.getKey().equals(PREF_BANGLEJS_ACTIVITY_FULL_SYNC_STATUS)) {
+            final EditTextPreference fullSyncStatusPreference = (EditTextPreference) preference;
+            final String statusValue = fullSyncStatusPreference.getText();
+
+            if (activityFullSyncDialog != null) {
+                switch (statusValue) {
+                    case "start":
+                        activityFullSyncDialog.setMessage(handler.getContext().getString(R.string.busy_task_fetch_activity_data));
+                        break;
+                    case "end":
+                        activityFullSyncDialog.dismiss();
+                        activityFullSyncDialog = null;
+                        break;
+                    default:
+                        activityFullSyncDialog.setMessage(statusValue);
+                }
+            }
+        }
+    }
+
+    @Override
+    public void customizeSettings(final DeviceSpecificSettingsHandler handler, final Prefs prefs, final String rootKey) {
+        final Preference fullSyncPref = handler.findPreference(PREF_BANGLEJS_ACTIVITY_FULL_SYNC_TRIGGER);
+        if (fullSyncPref != null) {
+            fullSyncPref.setOnPreferenceClickListener(preference -> {
+                if (activityFullSyncDialog != null) {
+                    // Already syncing
+                    return true;
+                }
+
+                final Context context = preference.getContext();
+
+                new AlertDialog.Builder(context)
+                        .setTitle(R.string.pref_activity_full_sync_trigger_title)
+                        .setMessage(R.string.pref_activity_full_sync_trigger_warning)
+                        .setIcon(R.drawable.ic_refresh)
+                        .setPositiveButton(R.string.start, (dialog, whichButton) -> {
+                            handler.notifyPreferenceChanged(PREF_BANGLEJS_ACTIVITY_FULL_SYNC_START);
+
+                            activityFullSyncDialog = new ProgressDialog(context);
+                            activityFullSyncDialog.setCancelable(false);
+                            activityFullSyncDialog.setMessage(context.getString(R.string.sony_anc_optimizer_status_starting));
+                            activityFullSyncDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
+                            activityFullSyncDialog.setProgress(0);
+                            activityFullSyncDialog.setButton(DialogInterface.BUTTON_NEGATIVE, context.getString(R.string.Cancel), (dialog1, which) -> {
+                                dialog1.dismiss();
+                                activityFullSyncDialog = null;
+                                handler.notifyPreferenceChanged(PREF_BANGLEJS_ACTIVITY_FULL_SYNC_STOP);
+                            });
+
+                            activityFullSyncDialog.show();
+                        })
+                        .setNegativeButton(android.R.string.cancel, null)
+                        .show();
+
+                return true;
+            });
+        }
+    }
+
+    @Override
+    public Set<String> getPreferenceKeysWithSummary() {
+        return Collections.emptySet();
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(final Parcel dest, final int flags) {
+        dest.writeParcelable(device, 0);
+    }
+
+    public static final Creator<BangleJSSettingsCustomizer> CREATOR = new Creator<BangleJSSettingsCustomizer>() {
+        @Override
+        public BangleJSSettingsCustomizer createFromParcel(final Parcel in) {
+            final GBDevice device = in.readParcelable(BangleJSSettingsCustomizer.class.getClassLoader());
+            return new BangleJSSettingsCustomizer(device);
+        }
+
+        @Override
+        public BangleJSSettingsCustomizer[] newArray(final int size) {
+            return new BangleJSSettingsCustomizer[size];
+        }
+    };
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/banglejs/BangleJSWorkoutParser.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/banglejs/BangleJSWorkoutParser.java
new file mode 100644
index 0000000..b02157e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/banglejs/BangleJSWorkoutParser.java
@@ -0,0 +1,170 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.banglejs;
+
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.ACTIVE_SECONDS;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.ALTITUDE_AVG;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.ALTITUDE_MAX;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.ALTITUDE_MIN;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.CADENCE_AVG;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.DISTANCE_METERS;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.HR_AVG;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.HR_MAX;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.HR_MIN;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.INTERNAL_HAS_GPS;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.SPEED_AVG;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.SPEED_MAX;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.SPEED_MIN;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.STEPS;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.STRIDE_AVG;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.STRIDE_MAX;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.STRIDE_MIN;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.UNIT_BPM;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.UNIT_METERS;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.UNIT_METERS_PER_SECOND;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.UNIT_SECONDS;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.UNIT_SPM;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.UNIT_STEPS;
+
+import android.content.Context;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryData;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryParser;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
+import nodomain.freeyourgadget.gadgetbridge.util.Accumulator;
+import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
+
+public class BangleJSWorkoutParser implements ActivitySummaryParser {
+    private static final Logger LOG = LoggerFactory.getLogger(BangleJSWorkoutParser.class);
+
+    private final Context mContext;
+
+    public BangleJSWorkoutParser(final Context context) {
+        this.mContext = context;
+    }
+
+    @Override
+    public BaseActivitySummary parseBinaryData(final BaseActivitySummary summary, final boolean forDetails) {
+        if (!forDetails) {
+            // Re-parsing the csv is too slow for summary
+            return summary;
+        }
+
+        if (summary.getRawDetailsPath() == null) {
+            return summary;
+        }
+
+        final File inputFile = FileUtils.tryFixPath(new File(summary.getRawDetailsPath()));
+        if (inputFile == null) {
+            return summary;
+        }
+
+        final List<BangleJSActivityPoint> points = BangleJSActivityPoint.fromCsv(inputFile);
+        if (points == null) {
+            return summary;
+        }
+
+        summary.setSummaryData(dataFromPoints(points).toString());
+        return summary;
+    }
+
+    public static ActivitySummaryData dataFromPoints(final List<BangleJSActivityPoint> points) {
+        final Accumulator accHeartRate = new Accumulator();
+        final Accumulator accSpeed = new Accumulator();
+        final Accumulator accAltitude = new Accumulator();
+        final Accumulator accStride = new Accumulator();
+        double totalDistance = 0;
+        int totalSteps = 0;
+        long totalTime = 0;
+        long totalActiveTime = 0;
+        boolean hasGps = false;
+
+        final ActivityUser activityUser = new ActivityUser();
+
+        BangleJSActivityPoint previousPoint = null;
+        for (final BangleJSActivityPoint p : points) {
+            if (p.getHeartRate() > 0) {
+                accHeartRate.add(p.getHeartRate());
+            }
+            final long timeDiff = previousPoint != null ? p.getTime() - previousPoint.getTime() : 0;
+            double distanceDiff;
+            // FIXME: GPS data can be missing for some entries which is handled here.
+            //  Should use more complex logic to be more accurate. Use interpolation.
+            //  Should distances be done via the GPX file we generate instead?
+            if (previousPoint != null && previousPoint.getLocation() != null && p.getLocation() != null) {
+                distanceDiff = p.getLocation().getDistance(previousPoint.getLocation());
+                hasGps = true;
+            } else {
+                distanceDiff = p.getSteps() * activityUser.getStepLengthCm() * 0.01d;
+            }
+            if (p.getSteps() > 0) {
+                accStride.add(distanceDiff / p.getSteps());
+            }
+            totalTime += timeDiff;
+            totalDistance += distanceDiff;
+            if (distanceDiff != 0) {
+                totalActiveTime += timeDiff;
+            }
+            if (timeDiff > 0) {
+                accSpeed.add(distanceDiff / (timeDiff / 1000d));
+            }
+
+            totalSteps += p.getSteps();
+
+            previousPoint = p;
+        }
+
+        final ActivitySummaryData summaryData = new ActivitySummaryData();
+        if (totalDistance != 0) {
+            summaryData.add(DISTANCE_METERS, (float) totalDistance, UNIT_METERS);
+        }
+        if (totalActiveTime > 0) {
+            summaryData.add(ACTIVE_SECONDS, Math.round(totalActiveTime / 1000d), UNIT_SECONDS);
+        }
+
+        if (totalSteps != 0) {
+            summaryData.add(STEPS, totalSteps, UNIT_STEPS);
+        }
+        if (accHeartRate.getCount() > 0) {
+            summaryData.add(HR_AVG, accHeartRate.getAverage(), UNIT_BPM);
+            summaryData.add(HR_MAX, (int) accHeartRate.getMax(), UNIT_BPM);
+            summaryData.add(HR_MIN, (int) accHeartRate.getMin(), UNIT_BPM);
+        }
+        if (accStride.getCount() > 0) {
+            summaryData.add(STRIDE_AVG, accStride.getAverage(), UNIT_METERS);
+            summaryData.add(STRIDE_MAX, accStride.getMax(), UNIT_METERS);
+            summaryData.add(STRIDE_MIN, accStride.getMin(), UNIT_METERS);
+        }
+        if (accSpeed.getCount() > 0) {
+            summaryData.add(SPEED_AVG, accSpeed.getAverage(), UNIT_METERS_PER_SECOND);
+            summaryData.add(SPEED_MAX, accSpeed.getMax(), UNIT_METERS_PER_SECOND);
+            summaryData.add(SPEED_MIN, accSpeed.getMin(), UNIT_METERS_PER_SECOND);
+        }
+        if (accAltitude.getCount() != 0) {
+            summaryData.add(ALTITUDE_MAX, accAltitude.getMax(), UNIT_METERS);
+            summaryData.add(ALTITUDE_MIN, accAltitude.getMin(), UNIT_METERS);
+            summaryData.add(ALTITUDE_AVG, accAltitude.getAverage(), UNIT_METERS);
+        }
+
+        if (totalTime > 0) {
+            // FIXME: Should cadence be steps/min or half that? https://www.polar.com/blog/what-is-running-cadence/
+            //  The Bangle.js App Loader has Cadence = (steps/min)/2,  https://github.com/espruino/BangleApps/blob/master/apps/recorder/interface.html#L103,
+            //  as discussed here: https://github.com/espruino/BangleApps/pull/3068#issuecomment-1790293879 .
+            summaryData.add(CADENCE_AVG, 0.5 * 60 * totalSteps / (totalTime / 1000d), UNIT_SPM);
+        }
+
+        // TODO: Implement hrZones by doing calculations on Gadgetbridge side or make Bangle.js report
+        //  this (Karvonen method implemented to a degree in watch app "Run+")?
+
+        // TODO: Does Bangle.js report laps in recorder logs?
+
+        summaryData.add(INTERNAL_HAS_GPS, String.valueOf(hasGps));
+
+        return summaryData;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/binary_sensor/activity/DataActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/binary_sensor/activity/DataActivity.java
new file mode 100644
index 0000000..6b8f76b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/binary_sensor/activity/DataActivity.java
@@ -0,0 +1,86 @@
+/*  Copyright (C) 2022-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.binary_sensor.activity;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.widget.TextView;
+
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBActivity;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.binary_sensor.BinarySensorSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.binary_sensor.protocol.constants.SensorState;
+
+public class DataActivity extends AbstractGBActivity {
+    TextView stateView, countView;
+
+    BroadcastReceiver stateReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (stateView == null) {
+                return;
+            }
+            if (countView == null) {
+                return;
+            }
+            boolean is_closed = intent.getBooleanExtra("EXTRA_SENSOR_CLOSED", false);
+            int count = intent.getIntExtra("EXTRA_SENSOR_COUNT", -1);
+
+            stateView.setText(is_closed ? "CLOSED" : "OPEN");
+            stateView.setBackgroundResource(is_closed ? android.R.color.holo_green_light : android.R.color.holo_red_light);
+            countView.setText("Count: " + count);
+        }
+    };
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_binary_sensor_data);
+
+        stateView = findViewById(R.id.text_sensor_state);
+        countView = findViewById(R.id.text_sensor_count);
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(BinarySensorSupport.ACTION_SENSOR_STATE_CHANGED);
+        LocalBroadcastManager.getInstance(getApplicationContext())
+                .registerReceiver(
+                        stateReceiver,
+                        filter
+                );
+
+        LocalBroadcastManager.getInstance(getApplicationContext())
+                .sendBroadcast(new Intent(BinarySensorSupport.ACTION_SENSOR_STATE_REQUEST));
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+        LocalBroadcastManager.getInstance(getApplicationContext())
+                .unregisterReceiver(
+                        stateReceiver
+                );
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/binary_sensor/coordinator/BinarySensorCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/binary_sensor/coordinator/BinarySensorCoordinator.java
new file mode 100644
index 0000000..ab3767e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/binary_sensor/coordinator/BinarySensorCoordinator.java
@@ -0,0 +1,188 @@
+/*  Copyright (C) 2022-2024 Arjan Schrijver, Damien Gaignon, Daniel Dakhno,
+    Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.binary_sensor.coordinator;
+
+import android.app.Activity;
+import android.bluetooth.le.ScanFilter;
+import android.content.Context;
+import android.net.Uri;
+import android.os.ParcelUuid;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.Collection;
+import java.util.Collections;
+
+import nodomain.freeyourgadget.gadgetbridge.GBException;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLEDeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.binary_sensor.activity.DataActivity;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.binary_sensor.BinarySensorSupport;
+
+public class BinarySensorCoordinator extends AbstractBLEDeviceCoordinator {
+    @Override
+    protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
+
+    }
+
+    @NonNull
+    @Override
+    public Collection<? extends ScanFilter> createBLEScanFilters() {
+        return Collections.singletonList(
+                new ScanFilter.Builder()
+                        .setServiceUuid(ParcelUuid.fromString(BinarySensorSupport.BINARY_SENSOR_SERVICE_UUID))
+                        .build()
+        );
+    }
+
+    @NonNull
+    @Override
+    public boolean supports(GBDeviceCandidate candidate) {
+        for(ParcelUuid service : candidate.getServiceUuids()){
+            if(service.getUuid().toString().equals(BinarySensorSupport.BINARY_SENSOR_SERVICE_UUID)){
+                return true;
+            };
+        }
+        return false;
+    }
+
+    @Override
+    public int[] getSupportedDeviceSpecificSettings(GBDevice device) {
+        return new int[0];
+    }
+
+    @Nullable
+    @Override
+    public Class<? extends Activity> getPairingActivity() {
+        return null;
+    }
+
+    @Override
+    public boolean supportsActivityDataFetching() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsActivityTracking() {
+        return false;
+    }
+
+    @Override
+    public SampleProvider<? extends ActivitySample> getSampleProvider(GBDevice device, DaoSession session) {
+        return null;
+    }
+
+    @Override
+    public boolean supportsFindDevice() {
+        return false;
+    }
+
+    @Override
+    public InstallHandler findInstallHandler(Uri uri, Context context) {
+        return null;
+    }
+
+    @Override
+    public boolean supportsScreenshots(final GBDevice device) {
+        return false;
+    }
+
+    @Override
+    public int getAlarmSlotCount(GBDevice device) {
+        return 0;
+    }
+
+    @Override
+    public boolean supportsHeartRateMeasurement(GBDevice device) {
+        return false;
+    }
+
+    @Override
+    public String getManufacturer() {
+        return "Generic";
+    }
+
+    @Override
+    public boolean supportsAppsManagement(final GBDevice device) {
+        return true;
+    }
+
+    @Override
+    public Class<? extends Activity> getAppsManagementActivity() {
+        return DataActivity.class;
+    }
+
+    @Override
+    public boolean supportsCalendarEvents() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsRealtimeData() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsWeather() {
+        return false;
+    }
+
+    @Override
+    public int getBatteryCount() {
+        return 0;
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return BinarySensorSupport.class;
+    }
+
+    @Override
+    public int getBondingStyle() {
+        return BONDING_STYLE_NONE;
+    }
+
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_binary_sensor;
+    }
+
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_unknown;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_unknown_disabled;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/casio/Casio2C2DDeviceCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/casio/Casio2C2DDeviceCoordinator.java
new file mode 100644
index 0000000..b4407b5
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/casio/Casio2C2DDeviceCoordinator.java
@@ -0,0 +1,28 @@
+/*  Copyright (C) 2024 Johannes Krude
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+/*  Based on code from BlueWatcher, https://github.com/masterjc/bluewatcher */
+package nodomain.freeyourgadget.gadgetbridge.devices.casio;
+
+
+public abstract class Casio2C2DDeviceCoordinator extends CasioDeviceCoordinator {
+
+    @Override
+    public boolean getRemindersHaveTime() {
+        return false;
+    }
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/casio/CasioConstants.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/casio/CasioConstants.java
new file mode 100644
index 0000000..8c6ad01
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/casio/CasioConstants.java
@@ -0,0 +1,154 @@
+/*  Copyright (C) 2020-2024 Andreas B枚hler, Johannes Krude
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.casio;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+public final class CasioConstants {
+    public static final UUID CASIO_VIRTUAL_SERVER_SERVICE = UUID.fromString("26eb0007-b012-49a8-b1f8-394fb2032b0f");
+
+    public static final UUID CASIO_VIRTUAL_SERVER_FEATURES = UUID.fromString("26eb0008-b012-49a8-b1f8-394fb2032b0f");
+
+    public static final UUID CASIO_A_NOT_W_REQ_NOT = UUID.fromString( "26eb0009-b012-49a8-b1f8-394fb2032b0f");
+    public static final UUID CASIO_A_NOT_COM_SET_NOT = UUID.fromString( "26eb000a-b012-49a8-b1f8-394fb2032b0f");
+
+    public static final UUID CCC_DESCRIPTOR_UUID = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");
+
+    // Immediate Alert
+
+    public static final UUID IMMEDIATE_ALERT_SERVICE_UUID = UUID.fromString("00001802-0000-1000-8000-00805f9b34fb");
+
+    // Alert
+
+    public static final UUID ALERT_SERVICE_UUID = UUID.fromString("26eb0000-b012-49a8-b1f8-394fb2032b0f");
+    public static final UUID ALERT_CHARACTERISTIC_UUID = UUID.fromString("00002a46-0000-1000-8000-00805f9b34fb");
+    public static final UUID ALERT_NOTIFICATION_CONTROL_POINT = UUID.fromString("00002a44-0000-1000-8000-00805f9b34fb");
+
+    // More Alert
+
+    public static final UUID MORE_ALERT_SERVICE_UUID = UUID.fromString("26eb001a-b012-49a8-b1f8-394fb2032b0f");
+    public static final UUID MORE_ALERT_UUID = UUID.fromString("26eb001b-b012-49a8-b1f8-394fb2032b0f");
+    public static final UUID MORE_ALERT_FOR_LONG_UUID = UUID.fromString("26eb001c-b012-49a8-b1f8-394fb2032b0f");
+
+    // Phone Alert
+    public static final UUID CASIO_PHONE_ALERT_STATUS_SERVICE = UUID.fromString("26eb0001-b012-49a8-b1f8-394fb2032b0f");
+    public static final UUID RINGER_CONTROL_POINT = UUID.fromString("00002a40-0000-1000-8000-00805f9b34fb");
+
+    // Phone Finder
+
+    public static final UUID CASIO_IMMEDIATE_ALERT_SERVICE_UUID = UUID.fromString("26eb0005-b012-49a8-b1f8-394fb2032b0f");
+    public static final UUID ALERT_LEVEL_CHARACTERISTIC_UUID = UUID.fromString("00002a06-0000-1000-8000-00805f9b34fb");
+
+    // Current Time
+
+    public static final UUID CURRENT_TIME_SERVICE_UUID = UUID.fromString("26eb0002-b012-49a8-b1f8-394fb2032b0f");
+    public static final UUID CURRENT_TIME_CHARACTERISTIC_UUID = UUID.fromString("00002a2b-0000-1000-8000-00805f9b34fb");
+    public static final UUID LOCAL_TIME_CHARACTERISTIC_UUID = UUID.fromString("00002a0f-0000-1000-8000-00805f9b34fb");
+
+    // Control Mode
+    public static final UUID WATCH_FEATURES_SERVICE_UUID = UUID.fromString("26eb000d-b012-49a8-b1f8-394fb2032b0f");
+    public static final UUID WATCH_CTRL_SERVICE_UUID = UUID.fromString("26eb0018-b012-49a8-b1f8-394fb2032b0f");
+    public static final UUID KEY_CONTAINER_CHARACTERISTIC_UUID = UUID.fromString("26eb0019-b012-49a8-b1f8-394fb2032b0f");
+    public static final UUID NAME_OF_APP_CHARACTERISTIC_UUID = UUID.fromString("26eb001d-b012-49a8-b1f8-394fb2032b0f");
+    public static final UUID FUNCTION_SWITCH_CHARACTERISTIC = UUID.fromString("26eb001e-b012-49a8-b1f8-394fb2032b0f");
+    public static final String MUSIC_MESSAGE = "Music";
+
+    // Modern Watches - All Features
+    public static final UUID CASIO_READ_REQUEST_FOR_ALL_FEATURES_CHARACTERISTIC_UUID = UUID.fromString("26eb002c-b012-49a8-b1f8-394fb2032b0f");
+    public static final UUID CASIO_ALL_FEATURES_CHARACTERISTIC_UUID = UUID.fromString("26eb002d-b012-49a8-b1f8-394fb2032b0f");
+    public static final UUID CASIO_DATA_REQUEST_SP_CHARACTERISTIC_UUID = UUID.fromString("26eb0023-b012-49a8-b1f8-394fb2032b0f");
+    public static final UUID CASIO_CONVOY_CHARACTERISTIC_UUID = UUID.fromString("26eb0024-b012-49a8-b1f8-394fb2032b0f");
+    public static final UUID CASIO_NOTIFICATION_CHARACTERISTIC_UUID = UUID.fromString("26eb0030-b012-49a8-b1f8-394fb2032b0f");
+
+    // Link Loss
+
+    public static final UUID LINK_LOSS_SERVICE = UUID.fromString("00001803-0000-1000-8000-00805f9b34fb");
+
+    // TxPower
+
+    public static final UUID TX_POWER_SERVICE_UUID = UUID.fromString("00001804-0000-1000-8000-00805f9b34fb");
+    public static final UUID TX_POWER_LEVEL_CHARACTERISTIC_UUID = UUID.fromString("00002a07-0000-1000-8000-00805f9b34fb");
+    // Settings
+    public static final UUID CASIO_SETTING_FOR_BLE_CHARACTERISTIC_UUID = UUID.fromString("26eb000f-b012-49a8-b1f8-394fb2032b0f");
+    public static final UUID CASIO_SETTING_FOR_ALM_CHARACTERISTIC_UUID = UUID.fromString("26eb0013-b012-49a8-b1f8-394fb2032b0f");
+
+    // Notification Types - GB6900
+
+    public static final byte CALL_NOTIFICATION_ID = 3;
+    public static final byte MAIL_NOTIFICATION_ID = 1;
+    public static final byte CALENDAR_NOTIFICATION_ID = 7;
+    public static final byte SNS_NOTIFICATION_ID = 13;
+    public static final byte SMS_NOTIFICATION_ID = 5;
+
+    // Notification Types - GBX100
+    public static final byte CATEGORY_ADVERTISEMENT = 13;
+    public static final byte CATEGORY_BUSINESS = 9;
+    public static final byte CATEGORY_CONDITION = 12;
+    public static final byte CATEGORY_EMAIL = 6;
+    public static final byte CATEGORY_ENTERTAINMENT = 11;
+    public static final byte CATEGORY_HEALTH_AND_FITNESS = 8;
+    public static final byte CATEGORY_INCOMING_CALL = 1;
+    public static final byte CATEGORY_LOCATION = 10;
+    public static final byte CATEGORY_MISSED_CALL = 2;
+    public static final byte CATEGORY_NEWS = 7;
+    public static final byte CATEGORY_OTHER = 0;
+    public static final byte CATEGORY_SCHEDULE_AND_ALARM = 5;
+    public static final byte CATEGORY_SNS = 4;
+    public static final byte CATEGORY_VOICEMAIL = 3;
+
+    // Connection Reason - GW-B5600
+    public static final byte CONNECT_CNCT = 1;
+    public static final byte CONNECT_FIND = 2;
+    public static final byte CONNECT_AUTO = 3;
+    public static final byte CONNECT_TIME = 4;
+
+    public enum Model {
+        MODEL_CASIO_GENERIC,
+        MODEL_CASIO_6900B,
+        MODEL_CASIO_5600B,
+        MODEL_CASIO_GBX100,
+        MODEL_CASIO_STB1000,
+        MODEL_CASIO_GWB5600,
+    }
+
+    public enum ConfigurationOption {
+        OPTION_GENDER,
+        OPTION_WEIGHT,
+        OPTION_HEIGHT,
+        OPTION_WRIST,
+        OPTION_BIRTHDAY,
+        OPTION_STEP_GOAL,
+        OPTION_DISTANCE_GOAL,
+        OPTION_ACTIVITY_GOAL,
+        OPTION_AUTOLIGHT,
+        OPTION_TIMEFORMAT,
+        OPTION_KEY_VIBRATION,
+        OPTION_OPERATING_SOUNDS,
+        OPTION_ALL
+    }
+
+    public static final int CASIO_CONVOY_DATATYPE_STEPS = 0x04;
+    public static final int CASIO_CONVOY_DATATYPE_CALORIES = 0x05;
+
+    public static final int CASIO_FAKE_RING_SLEEP_DURATION = 3000;
+    public static final int CASIO_FAKE_RING_RETRIES = 10;
+
+    public static final int CASIO_AUTOREMOVE_MESSAGE_DELAY = 10000;
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/casio/CasioDeviceCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/casio/CasioDeviceCoordinator.java
new file mode 100644
index 0000000..133c50f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/casio/CasioDeviceCoordinator.java
@@ -0,0 +1,45 @@
+/*  Copyright (C) 2023-2024 Johannes Krude
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+/*  Based on code from BlueWatcher, https://github.com/masterjc/bluewatcher */
+package nodomain.freeyourgadget.gadgetbridge.devices.casio;
+
+import java.util.Collection;
+import java.util.Collections;
+
+import android.bluetooth.le.ScanFilter;
+import android.os.ParcelUuid;
+
+import androidx.annotation.NonNull;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLEDeviceCoordinator;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.casio.CasioConstants;
+
+public abstract class CasioDeviceCoordinator extends AbstractBLEDeviceCoordinator {
+    @NonNull
+    @Override
+    public Collection<? extends ScanFilter> createBLEScanFilters() {
+        ParcelUuid casioService = new ParcelUuid(CasioConstants.WATCH_FEATURES_SERVICE_UUID);
+        ScanFilter filter = new ScanFilter.Builder().setServiceUuid(casioService).build();
+        return Collections.singletonList(filter);
+    }
+
+    @Override
+    public String getManufacturer() {
+        return "Casio";
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/casio/ecbs100/CasioECBS100DeviceCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/casio/ecbs100/CasioECBS100DeviceCoordinator.java
new file mode 100644
index 0000000..fc4fcf1
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/casio/ecbs100/CasioECBS100DeviceCoordinator.java
@@ -0,0 +1,67 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.casio.ecbs100;
+
+import androidx.annotation.NonNull;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.GBException;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.casio.Casio2C2DDeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.casio.ecbs100.CasioECBS100DeviceSupport;
+
+public class CasioECBS100DeviceCoordinator extends Casio2C2DDeviceCoordinator {
+    @Override
+    protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
+    }
+
+    @Override
+    public int getBondingStyle(){
+        return BONDING_STYLE_BOND;
+    }
+
+    @Override
+    public int[] getSupportedDeviceSpecificSettings(GBDevice device) {
+        return new int[]{
+                R.xml.devicesettings_timeformat,
+                R.xml.devicesettings_dateformat_day_month_order,
+                R.xml.devicesettings_operating_sounds,
+                R.xml.devicesettings_hourly_chime_enable,
+                R.xml.devicesettings_autolight,
+                R.xml.devicesettings_light_duration_longer,
+                R.xml.devicesettings_power_saving,
+                R.xml.devicesettings_casio_connection_duration,
+                R.xml.devicesettings_time_sync,
+
+                // timer
+                // reminder
+                // world time
+        };
+    }
+
+
+    @Override
+    public int getAlarmSlotCount(GBDevice device) {
+        return 5;
+    }
+
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return CasioECBS100DeviceSupport.class;
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("CASIO ECB-S100");
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_casioecbs100;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/casio/gb6900/CasioGB6900DeviceCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/casio/gb6900/CasioGB6900DeviceCoordinator.java
new file mode 100644
index 0000000..47034df
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/casio/gb6900/CasioGB6900DeviceCoordinator.java
@@ -0,0 +1,152 @@
+/*  Copyright (C) 2020-2024 Andreas B枚hler, Damien Gaignon, Daniel Dakhno,
+    Johannes Krude, Jos茅 Rebelo, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+/*  Based on code from BlueWatcher, https://github.com/masterjc/bluewatcher */
+package nodomain.freeyourgadget.gadgetbridge.devices.casio.gb6900;
+
+import android.app.Activity;
+import android.content.Context;
+import android.net.Uri;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import androidx.annotation.NonNull;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.GBException;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.casio.CasioDeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.casio.gb6900.CasioGB6900DeviceSupport;
+
+public class CasioGB6900DeviceCoordinator extends CasioDeviceCoordinator {
+    protected static final Logger LOG = LoggerFactory.getLogger(CasioGB6900DeviceCoordinator.class);
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("CASIO.*(6900B|5600B|STB-1000).*");
+    }
+
+    @Override
+    public int getBondingStyle(){
+        return BONDING_STYLE_LAZY;
+    }
+
+    @Override
+    public boolean supportsCalendarEvents() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsRealtimeData() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsWeather() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsFindDevice() {
+        return true;
+    }
+
+    @Override
+    public Class<? extends Activity> getPairingActivity() {
+        return null;
+    }
+
+    @Override
+    public InstallHandler findInstallHandler(Uri uri, Context context) {
+        return null;
+    }
+
+    @Override
+    public boolean supportsActivityDataFetching() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsActivityTracking() {
+        return false;
+    }
+
+    @Override
+    public SampleProvider<? extends ActivitySample> getSampleProvider(GBDevice device, DaoSession session) {
+        return null;
+    }
+
+    @Override
+    public boolean supportsScreenshots(final GBDevice device) {
+        return false;
+    }
+
+    @Override
+    public int getAlarmSlotCount(GBDevice device) {
+        return 5; // 4 regular and one snooze
+    }
+
+    @Override
+    public boolean supportsHeartRateMeasurement(GBDevice device) {
+        return false;
+    }
+
+    @Override
+    public boolean supportsAppsManagement(final GBDevice device) {
+        return false;
+    }
+
+    @Override
+    public Class<? extends Activity> getAppsManagementActivity() {
+        return null;
+    }
+
+    @Override
+    protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
+
+    }
+
+    @Override
+    public int[] getSupportedDeviceSpecificSettings(GBDevice device) {
+        return new int[] {
+                R.xml.devicesettings_disconnectnotification_noshed,
+                R.xml.devicesettings_transliteration
+        };
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return CasioGB6900DeviceSupport.class;
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_casiogb6900;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/casio/gbx100/CasioGBX100DeviceCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/casio/gbx100/CasioGBX100DeviceCoordinator.java
new file mode 100644
index 0000000..fb4cc14
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/casio/gbx100/CasioGBX100DeviceCoordinator.java
@@ -0,0 +1,159 @@
+/*  Copyright (C) 2020-2024 Andreas B枚hler, Damien Gaignon, Daniel Dakhno,
+    foxstidious, Johannes Krude, Jos茅 Rebelo, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+/*  Based on code from BlueWatcher, https://github.com/masterjc/bluewatcher */
+package nodomain.freeyourgadget.gadgetbridge.devices.casio.gbx100;
+
+import android.app.Activity;
+import android.content.Context;
+import android.net.Uri;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import androidx.annotation.NonNull;
+
+import java.util.regex.Pattern;
+
+import de.greenrobot.dao.query.QueryBuilder;
+import nodomain.freeyourgadget.gadgetbridge.GBException;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.casio.Casio2C2DDeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.entities.CasioGBX100ActivitySampleDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.casio.gbx100.CasioGBX100DeviceSupport;
+
+public class CasioGBX100DeviceCoordinator extends Casio2C2DDeviceCoordinator {
+    /** CASIO brand identifier in GB Device name */
+    public static final String CASIO_IDENTIFIER = "CASIO";
+
+    /** Sub-model string for GBX-100 in GB Device name */
+    public static final String GBX_100_SUB_MODEL = "GBX-100";
+    /** Sub-model string for GBD-200 in GB Device name */
+    public static final String GBD_200_SUB_MODEL = "GBD-200";
+    /** Sub-model string for GBD-100 in GB Device name */
+    public static final String GBD_100_SUB_MODEL = "GBD-100";
+    /** Sub-model string for GBD-H1000 in GB Device name */
+    public static final String GBD_H1000_SUB_MODEL = "GBD-H1000";
+
+    public static final String[] VARIANTS = {
+            GBX_100_SUB_MODEL,
+            GBD_200_SUB_MODEL,
+            GBD_100_SUB_MODEL,
+            GBD_H1000_SUB_MODEL};
+
+    protected static final Logger LOG = LoggerFactory.getLogger(CasioGBX100DeviceCoordinator.class);
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        String pattern = CASIO_IDENTIFIER + ".*(";
+        for (int i = 0; i < VARIANTS.length; i++) {
+            pattern += VARIANTS[i];
+            if (i < VARIANTS.length - 1) {
+                pattern += "|";
+            }
+        }
+        pattern += ")";
+        return Pattern.compile(pattern);
+    }
+
+    @Override
+    public int getBondingStyle(){
+        return BONDING_STYLE_LAZY;
+    }
+
+    @Override
+    public boolean supportsAlarmSnoozing() {
+        return true;
+    }
+
+    @Override
+    public Class<? extends Activity> getPairingActivity() {
+        return null;
+    }
+
+    @Override
+    public InstallHandler findInstallHandler(Uri uri, Context context) {
+        return null;
+    }
+
+    @Override
+    public boolean supportsActivityDataFetching() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsActivityTracking() {
+        return true;
+    }
+
+    @Override
+    public SampleProvider<? extends ActivitySample> getSampleProvider(GBDevice device, DaoSession session) {
+        return new CasioGBX100SampleProvider(device, session);
+    }
+
+    @Override
+    public int getAlarmSlotCount(GBDevice device) {
+        return 4;
+    }
+
+    @Override
+    public Class<? extends Activity> getAppsManagementActivity() {
+        return null;
+    }
+
+    @Override
+    protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
+        Long deviceId = device.getId();
+        QueryBuilder<?> qb = session.getCasioGBX100ActivitySampleDao().queryBuilder();
+        qb.where(CasioGBX100ActivitySampleDao.Properties.DeviceId.eq(deviceId)).buildDelete().executeDeleteWithoutDetachingEntities();
+    }
+
+    @Override
+    public int[] getSupportedDeviceSpecificSettings(GBDevice device) {
+        return new int[]{
+                R.xml.devicesettings_find_phone,
+                R.xml.devicesettings_wearlocation,
+                R.xml.devicesettings_timeformat,
+                R.xml.devicesettings_autolight,
+                R.xml.devicesettings_key_vibration,
+                R.xml.devicesettings_operating_sounds,
+                R.xml.devicesettings_fake_ring_duration,
+                R.xml.devicesettings_autoremove_message,
+                R.xml.devicesettings_transliteration,
+                R.xml.devicesettings_preview_message_in_title,
+                R.xml.devicesettings_casio_alert
+        };
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return CasioGBX100DeviceSupport.class;
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_casiogbx100;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/casio/gbx100/CasioGBX100SampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/casio/gbx100/CasioGBX100SampleProvider.java
new file mode 100644
index 0000000..50d0f0a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/casio/gbx100/CasioGBX100SampleProvider.java
@@ -0,0 +1,80 @@
+/*  Copyright (C) 2023-2024 Johannes Krude
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.casio.gbx100;
+
+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.CasioGBX100ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.entities.CasioGBX100ActivitySampleDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+
+public class CasioGBX100SampleProvider extends AbstractSampleProvider<CasioGBX100ActivitySample> {
+    public CasioGBX100SampleProvider(GBDevice device, DaoSession session) {
+        super(device, session);
+    }
+
+    @Override
+    public ActivityKind normalizeType(int rawType) {
+        return ActivityKind.fromCode(rawType);
+    }
+
+    @Override
+    public int toRawActivityKind(ActivityKind activityKind) {
+        return activityKind.getCode();
+    }
+
+    @Override
+    public float normalizeIntensity(int rawIntensity) {
+        // The magic number 1500 is based on
+        // https://www.livestrong.com/article/474836-what-sport-burns-the-most-calories-per-hour/
+        return (rawIntensity / 1500f);
+    }
+
+    @Override
+    public CasioGBX100ActivitySample createActivitySample() {
+        return new CasioGBX100ActivitySample();
+    }
+
+    @Override
+    public AbstractDao<CasioGBX100ActivitySample, ?> getSampleDao() {
+        return getSession().getCasioGBX100ActivitySampleDao();
+    }
+
+    @Nullable
+    @Override
+    protected Property getRawKindSampleProperty() {
+        return CasioGBX100ActivitySampleDao.Properties.RawKind;
+    }
+
+    @NonNull
+    @Override
+    protected Property getTimestampSampleProperty() {
+        return CasioGBX100ActivitySampleDao.Properties.Timestamp;
+    }
+
+    @NonNull
+    @Override
+    protected Property getDeviceIdentifierSampleProperty() {
+        return CasioGBX100ActivitySampleDao.Properties.DeviceId;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/casio/gwb5600/CasioGMWB5000DeviceCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/casio/gwb5600/CasioGMWB5000DeviceCoordinator.java
new file mode 100644
index 0000000..f6a1263
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/casio/gwb5600/CasioGMWB5000DeviceCoordinator.java
@@ -0,0 +1,38 @@
+/*  Copyright (C) 2023-2024 Daniel Dakhno, Johannes Krude, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+/*  Based on code from BlueWatcher, https://github.com/masterjc/bluewatcher */
+package nodomain.freeyourgadget.gadgetbridge.devices.casio.gwb5600;
+
+import androidx.annotation.NonNull;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
+
+public class CasioGMWB5000DeviceCoordinator extends CasioGWB5600DeviceCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("CASIO GMW-B5000");
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_casiogmwb5000;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/casio/gwb5600/CasioGWB5600DeviceCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/casio/gwb5600/CasioGWB5600DeviceCoordinator.java
new file mode 100644
index 0000000..39cc91d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/casio/gwb5600/CasioGWB5600DeviceCoordinator.java
@@ -0,0 +1,116 @@
+/*  Copyright (C) 2023-2024 Daniel Dakhno, Johannes Krude, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+/*  Based on code from BlueWatcher, https://github.com/masterjc/bluewatcher */
+package nodomain.freeyourgadget.gadgetbridge.devices.casio.gwb5600;
+
+import android.app.Activity;
+import android.content.Context;
+import android.net.Uri;
+
+import androidx.annotation.NonNull;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLEDeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.casio.Casio2C2DDeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.casio.gwb5600.CasioGWB5600DeviceSupport;
+
+public class CasioGWB5600DeviceCoordinator extends Casio2C2DDeviceCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("CASIO GW-B5600");
+    }
+
+    @Override
+    public int getBondingStyle(){
+        return BONDING_STYLE_BOND;
+    }
+
+
+    @Override
+    public int[] getSupportedDeviceSpecificSettings(GBDevice device) {
+        return new int[]{
+                R.xml.devicesettings_timeformat,
+                R.xml.devicesettings_dateformat_day_month_order,
+                R.xml.devicesettings_operating_sounds,
+                R.xml.devicesettings_hourly_chime_enable,
+                R.xml.devicesettings_autolight,
+                R.xml.devicesettings_light_duration_longer,
+                R.xml.devicesettings_power_saving,
+                R.xml.devicesettings_casio_connection_duration,
+                R.xml.devicesettings_time_sync,
+
+                // timer
+                // reminder
+                // world time
+        };
+    }
+
+    @Override
+    public String[] getSupportedLanguageSettings(GBDevice device) {
+        return new String[]{
+                "auto",
+                "en_US",
+                "es_ES",
+                "fr_FR",
+                "de_DE",
+                "it_IT",
+                "ru_RU",
+        };
+    }
+
+    @Override
+    protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) {
+    }
+
+    @Override
+    public int getAlarmSlotCount(GBDevice device) {
+        return 5;
+    }
+
+    @Override
+    public int getMaximumReminderMessageLength() {
+        return 18;
+    }
+
+    @Override
+    public int getReminderSlotCount(final GBDevice device) {
+        return 5;
+    }
+
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return CasioGWB5600DeviceSupport.class;
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_casiogwb5600;
+    }
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/cmfwatchpro/CmfWatchPro2Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/cmfwatchpro/CmfWatchPro2Coordinator.java
new file mode 100644
index 0000000..a8123c5
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/cmfwatchpro/CmfWatchPro2Coordinator.java
@@ -0,0 +1,54 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.cmfwatchpro;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+
+public class CmfWatchPro2Coordinator extends CmfWatchProCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("^CMF Watch Pro 2(-[A-Z0-9]{4})$");
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_nothing_cmf_watch_pro_2;
+    }
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_watchxplus;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_watchxplus_disabled;
+    }
+
+    @Override
+    public int getBondingStyle() {
+        // We can negotiate auth key - #3982
+        return BONDING_STYLE_BOND;
+    }
+
+    @Override
+    public boolean supportsSunriseSunset() {
+        return true;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/cmfwatchpro/CmfWatchProCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/cmfwatchpro/CmfWatchProCoordinator.java
new file mode 100644
index 0000000..f6d3bdd
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/cmfwatchpro/CmfWatchProCoordinator.java
@@ -0,0 +1,377 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.cmfwatchpro;
+
+import android.app.Activity;
+import android.bluetooth.le.ScanFilter;
+import android.content.Context;
+import android.net.Uri;
+import android.os.ParcelUuid;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import org.apache.commons.lang3.ArrayUtils;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.GBException;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.appmanager.AppManagerActivity;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer;
+import nodomain.freeyourgadget.gadgetbridge.capabilities.HeartRateCapability;
+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.devices.cmfwatchpro.samples.CmfActivitySampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.cmfwatchpro.samples.CmfSpo2SampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.cmfwatchpro.samples.CmfStressSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.cmfwatchpro.workout.CmfWorkoutSummaryParser;
+import nodomain.freeyourgadget.gadgetbridge.entities.CmfActivitySampleDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.CmfHeartRateSampleDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.CmfSleepSessionSampleDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.CmfSleepStageSampleDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.CmfSpo2SampleDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.CmfStressSampleDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryParser;
+import nodomain.freeyourgadget.gadgetbridge.model.Spo2Sample;
+import nodomain.freeyourgadget.gadgetbridge.model.StressSample;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.cmfwatchpro.CmfInstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.cmfwatchpro.CmfWatchProSupport;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+public class CmfWatchProCoordinator extends AbstractBLEDeviceCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("^Watch Pro$");
+    }
+
+    @NonNull
+    @Override
+    public Collection<? extends ScanFilter> createBLEScanFilters() {
+        final ParcelUuid casioService = new ParcelUuid(CmfWatchProSupport.UUID_SERVICE_CMF_CMD);
+        final ScanFilter filter = new ScanFilter.Builder().setServiceUuid(casioService).build();
+        return Collections.singletonList(filter);
+    }
+
+    @Nullable
+    @Override
+    public InstallHandler findInstallHandler(final Uri uri, final Context context) {
+        final CmfInstallHandler handler = new CmfInstallHandler(uri, context);
+        return handler.isValid() ? handler : null;
+    }
+
+    @Override
+    public boolean suggestUnbindBeforePair() {
+        return false;
+    }
+
+    @Override
+    protected void deleteDevice(@NonNull final GBDevice gbDevice,
+                                @NonNull final Device device,
+                                @NonNull final DaoSession session) throws GBException {
+        final Long deviceId = device.getId();
+
+        session.getCmfActivitySampleDao().queryBuilder()
+                .where(CmfActivitySampleDao.Properties.DeviceId.eq(deviceId))
+                .buildDelete().executeDeleteWithoutDetachingEntities();
+
+        session.getCmfStressSampleDao().queryBuilder()
+                .where(CmfStressSampleDao.Properties.DeviceId.eq(deviceId))
+                .buildDelete().executeDeleteWithoutDetachingEntities();
+
+        session.getCmfHeartRateSampleDao().queryBuilder()
+                .where(CmfHeartRateSampleDao.Properties.DeviceId.eq(deviceId))
+                .buildDelete().executeDeleteWithoutDetachingEntities();
+
+        session.getCmfSleepSessionSampleDao().queryBuilder()
+                .where(CmfSleepSessionSampleDao.Properties.DeviceId.eq(deviceId))
+                .buildDelete().executeDeleteWithoutDetachingEntities();
+
+        session.getCmfSleepStageSampleDao().queryBuilder()
+                .where(CmfSleepStageSampleDao.Properties.DeviceId.eq(deviceId))
+                .buildDelete().executeDeleteWithoutDetachingEntities();
+
+        session.getCmfSpo2SampleDao().queryBuilder()
+                .where(CmfSpo2SampleDao.Properties.DeviceId.eq(deviceId))
+                .buildDelete().executeDeleteWithoutDetachingEntities();
+    }
+
+    @Override
+    public String getManufacturer() {
+        return "Nothing";
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_nothing_cmf_watch_pro;
+    }
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_amazfit_bip;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_amazfit_bip_disabled;
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return CmfWatchProSupport.class;
+    }
+
+    @Override
+    public int getBondingStyle() {
+        return BONDING_STYLE_REQUIRE_KEY;
+    }
+
+    @Override
+    public boolean validateAuthKey(final String authKey) {
+        final byte[] authKeyBytes = authKey.trim().getBytes();
+        return authKeyBytes.length == 32 || (authKey.startsWith("0x") && authKeyBytes.length == 34);
+    }
+
+    @Override
+    public int[] getSupportedDeviceSpecificAuthenticationSettings() {
+        return new int[]{R.xml.devicesettings_pairingkey};
+    }
+
+    @Override
+    public SampleProvider<? extends ActivitySample> getSampleProvider(final GBDevice device, DaoSession session) {
+        return new CmfActivitySampleProvider(device, session);
+    }
+
+    @Override
+    public TimeSampleProvider<? extends StressSample> getStressSampleProvider(final GBDevice device, final DaoSession session) {
+        return new CmfStressSampleProvider(device, session);
+    }
+
+    @Override
+    public int[] getStressRanges() {
+        // 1-29 = relaxed
+        // 30-59 = normal
+        // 60-79 = medium
+        // 80-99 = high
+        return new int[]{1, 30, 60, 80};
+    }
+
+    @Override
+    public TimeSampleProvider<? extends Spo2Sample> getSpo2SampleProvider(final GBDevice device, final DaoSession session) {
+        return new CmfSpo2SampleProvider(device, session);
+    }
+
+    @Nullable
+    @Override
+    public ActivitySummaryParser getActivitySummaryParser(final GBDevice device, final Context context) {
+        return new CmfWorkoutSummaryParser(device);
+    }
+
+    @Override
+    public boolean supportsFlashing() {
+        return true;
+    }
+
+    @Override
+    public int getAlarmSlotCount(final GBDevice device) {
+        return 5;
+    }
+
+    @Override
+    public boolean supportsAlarmTitle(final GBDevice device) {
+        return true;
+    }
+
+    @Override
+    public int getAlarmTitleLimit(final GBDevice device) {
+        return 8;
+    }
+
+    @Override
+    public boolean supportsAppsManagement(final GBDevice device) {
+        return false; // TODO for watchface management
+    }
+
+    @Override
+    public boolean supportsCachedAppManagement(GBDevice device) {
+        return false;
+    }
+
+    @Override
+    public boolean supportsInstalledAppManagement(GBDevice device) {
+        return false;
+    }
+
+    @Override
+    public boolean supportsWatchfaceManagement(GBDevice device) {
+        return supportsAppsManagement(device);
+    }
+
+    @Override
+    public Class<? extends Activity> getAppsManagementActivity() {
+        return AppManagerActivity.class;
+    }
+
+    @Override
+    public boolean supportsAppListFetching() {
+        return false; // TODO it does not, but we can fake it for watchfaces
+    }
+
+    @Override
+    public boolean supportsActivityDataFetching() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsActivityTracking() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsActivityTracks() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsStressMeasurement() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsSpo2(GBDevice device) {
+        return true;
+    }
+
+    @Override
+    public boolean supportsMusicInfo() {
+        return true;
+    }
+
+    @Override
+    public int getContactsSlotCount(final GBDevice device) {
+        return 20;
+    }
+
+    @Override
+    public boolean supportsHeartRateMeasurement(final GBDevice device) {
+        return true;
+    }
+
+    @Override
+    public boolean supportsManualHeartRateMeasurement(final GBDevice device) {
+        return false;
+    }
+
+    @Override
+    public boolean supportsRemSleep() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsWeather() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsFindDevice() {
+        return true;
+    }
+
+    @Override
+    public int[] getSupportedDeviceSpecificSettings(final GBDevice device) {
+        final List<Integer> settings = new ArrayList<>();
+
+        settings.add(R.xml.devicesettings_header_time);
+        settings.add(R.xml.devicesettings_timeformat);
+
+        settings.add(R.xml.devicesettings_header_display);
+        settings.add(R.xml.devicesettings_workout_activity_types);
+        settings.add(R.xml.devicesettings_liftwrist_display_noshed);
+
+        settings.add(R.xml.devicesettings_header_health);
+        settings.add(R.xml.devicesettings_heartrate_sleep_alert_activity_stress_spo2);
+        settings.add(R.xml.devicesettings_inactivity_dnd);
+        settings.add(R.xml.devicesettings_hydration_reminder_dnd);
+
+        settings.add(R.xml.devicesettings_header_notifications);
+        settings.add(R.xml.devicesettings_send_app_notifications);
+        settings.add(R.xml.devicesettings_transliteration);
+
+        settings.add(R.xml.devicesettings_header_other);
+        if (getContactsSlotCount(device) > 0) {
+            settings.add(R.xml.devicesettings_contacts);
+        }
+
+        return ArrayUtils.toPrimitive(settings.toArray(new Integer[0]));
+    }
+
+    @Override
+    public DeviceSpecificSettingsCustomizer getDeviceSpecificSettingsCustomizer(final GBDevice device) {
+        return new CmfWatchProSettingsCustomizer();
+    }
+
+    @Override
+    public String[] getSupportedLanguageSettings(final GBDevice device) {
+        return null;
+        // FIXME language setting does not seem to work from phone
+        //return new String[]{
+        //        "auto",
+        //        "ar_SA",
+        //        "de_DE",
+        //        "en_US",
+        //        "es_ES",
+        //        "fr_FR",
+        //        "hi_IN",
+        //        "id_ID",
+        //        "it_IT",
+        //        "ja_JP",
+        //        "ko_KO",
+        //        "zh_CN",
+        //        "zh_HK",
+        //};
+    }
+
+    @Override
+    public List<HeartRateCapability.MeasurementInterval> getHeartRateMeasurementIntervals() {
+        return Arrays.asList(
+                HeartRateCapability.MeasurementInterval.OFF,
+                HeartRateCapability.MeasurementInterval.SMART
+        );
+    }
+
+    protected static Prefs getPrefs(final GBDevice device) {
+        return new Prefs(GBApplication.getDeviceSpecificSharedPrefs(device.getAddress()));
+    }
+
+    public boolean supportsSunriseSunset() {
+        return false;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/cmfwatchpro/CmfWatchProSettingsCustomizer.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/cmfwatchpro/CmfWatchProSettingsCustomizer.java
new file mode 100644
index 0000000..a6b9b0e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/cmfwatchpro/CmfWatchProSettingsCustomizer.java
@@ -0,0 +1,82 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.cmfwatchpro;
+
+import android.os.Parcel;
+
+import androidx.annotation.NonNull;
+import androidx.preference.Preference;
+
+import java.util.Collections;
+import java.util.Set;
+
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsHandler;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+public class CmfWatchProSettingsCustomizer implements DeviceSpecificSettingsCustomizer {
+    @Override
+    public void onPreferenceChange(final Preference preference, final DeviceSpecificSettingsHandler handler) {
+    }
+
+    @Override
+    public void customizeSettings(final DeviceSpecificSettingsHandler handler, final Prefs prefs, final String rootKey) {
+        final String[] prefsToHide = new String[]{
+                "pref_key_header_heartrate_sleep",
+                DeviceSettingsPreferenceConst.PREF_HEARTRATE_USE_FOR_SLEEP_DETECTION,
+                DeviceSettingsPreferenceConst.PREF_HEARTRATE_SLEEP_BREATHING_QUALITY_MONITORING,
+                DeviceSettingsPreferenceConst.PREF_HEARTRATE_ACTIVITY_MONITORING,
+                DeviceSettingsPreferenceConst.PREF_HEARTRATE_STRESS_RELAXATION_REMINDER,
+                DeviceSettingsPreferenceConst.PREF_INACTIVITY_START,
+                DeviceSettingsPreferenceConst.PREF_INACTIVITY_END,
+        };
+
+        for (final String prefKey : prefsToHide) {
+            final Preference pref = handler.findPreference(prefKey);
+            if (pref != null) {
+                pref.setVisible(false);
+            }
+        }
+    }
+
+    @Override
+    public Set<String> getPreferenceKeysWithSummary() {
+        return Collections.emptySet();
+    }
+
+    public static final Creator<CmfWatchProSettingsCustomizer> CREATOR = new Creator<CmfWatchProSettingsCustomizer>() {
+        @Override
+        public CmfWatchProSettingsCustomizer createFromParcel(final Parcel in) {
+            return new CmfWatchProSettingsCustomizer();
+        }
+
+        @Override
+        public CmfWatchProSettingsCustomizer[] newArray(final int size) {
+            return new CmfWatchProSettingsCustomizer[size];
+        }
+    };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull final Parcel dest, final int flags) {
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/cmfwatchpro/samples/CmfActivitySampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/cmfwatchpro/samples/CmfActivitySampleProvider.java
new file mode 100644
index 0000000..d1c42e5
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/cmfwatchpro/samples/CmfActivitySampleProvider.java
@@ -0,0 +1,187 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.cmfwatchpro.samples;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.time.LocalDate;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import de.greenrobot.dao.AbstractDao;
+import de.greenrobot.dao.Property;
+import nodomain.freeyourgadget.gadgetbridge.devices.AbstractSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.CmfActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.entities.CmfActivitySampleDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.CmfHeartRateSample;
+import nodomain.freeyourgadget.gadgetbridge.entities.CmfSleepStageSample;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
+
+public class CmfActivitySampleProvider extends AbstractSampleProvider<CmfActivitySample> {
+    private static final Logger LOG = LoggerFactory.getLogger(CmfActivitySampleProvider.class);
+
+    public CmfActivitySampleProvider(final GBDevice device, final DaoSession session) {
+        super(device, session);
+    }
+
+    @Override
+    public AbstractDao<CmfActivitySample, ?> getSampleDao() {
+        return getSession().getCmfActivitySampleDao();
+    }
+
+    @Nullable
+    @Override
+    protected Property getRawKindSampleProperty() {
+        return CmfActivitySampleDao.Properties.RawKind;
+    }
+
+    @NonNull
+    @Override
+    protected Property getTimestampSampleProperty() {
+        return CmfActivitySampleDao.Properties.Timestamp;
+    }
+
+    @NonNull
+    @Override
+    protected Property getDeviceIdentifierSampleProperty() {
+        return CmfActivitySampleDao.Properties.DeviceId;
+    }
+
+    @Override
+    public ActivityKind normalizeType(final int rawType) {
+        return ActivityKind.fromCode(rawType);
+    }
+
+    @Override
+    public int toRawActivityKind(final ActivityKind activityKind) {
+        return activityKind.getCode();
+    }
+
+    @Override
+    public float normalizeIntensity(final int rawIntensity) {
+        return rawIntensity / 100f;
+    }
+
+    @Override
+    public CmfActivitySample createActivitySample() {
+        return new CmfActivitySample();
+    }
+
+    @Override
+    protected List<CmfActivitySample> getGBActivitySamples(final int timestamp_from, final int timestamp_to) {
+        LOG.trace(
+                "Getting cmf activity samples between {} and {}",
+                timestamp_from,
+                timestamp_to
+        );
+
+        final long nanoStart = System.nanoTime();
+
+        final List<CmfActivitySample> samples = super.getGBActivitySamples(timestamp_from, timestamp_to);
+
+        if (!samples.isEmpty()) {
+            convertCumulativeSteps(samples, CmfActivitySampleDao.Properties.Steps);
+        }
+
+        final Map<Integer, CmfActivitySample> sampleByTs = new HashMap<>();
+        for (final CmfActivitySample sample : samples) {
+            sampleByTs.put(sample.getTimestamp(), sample);
+        }
+
+        overlayHeartRate(sampleByTs, timestamp_from, timestamp_to);
+        overlaySleep(sampleByTs, timestamp_from, timestamp_to);
+
+        final List<CmfActivitySample> finalSamples = new ArrayList<>(sampleByTs.values());
+        Collections.sort(finalSamples, (a, b) -> Integer.compare(a.getTimestamp(), b.getTimestamp()));
+
+        final long nanoEnd = System.nanoTime();
+
+        final long executionTime = (nanoEnd - nanoStart) / 1000000;
+
+        LOG.trace("Getting cmf samples took {}ms", executionTime);
+
+        return finalSamples;
+    }
+
+    private void overlayHeartRate(final Map<Integer, CmfActivitySample> sampleByTs, final int timestamp_from, final int timestamp_to) {
+        final CmfHeartRateSampleProvider heartRateSampleProvider = new CmfHeartRateSampleProvider(getDevice(), getSession());
+        final List<CmfHeartRateSample> hrSamples = heartRateSampleProvider.getAllSamples(timestamp_from * 1000L, timestamp_to * 1000L);
+
+        for (final CmfHeartRateSample hrSample : hrSamples) {
+            // round to the nearest minute, we don't need per-second granularity
+            final int tsSeconds = (int) ((hrSample.getTimestamp() / 1000) / 60) * 60;
+            CmfActivitySample sample = sampleByTs.get(tsSeconds);
+            if (sample == null) {
+                //LOG.debug("Adding dummy sample at {} for hr", tsSeconds);
+                sample = new CmfActivitySample();
+                sample.setTimestamp(tsSeconds);
+                sample.setProvider(this);
+                sampleByTs.put(tsSeconds, sample);
+            }
+
+            sample.setHeartRate(hrSample.getHeartRate());
+        }
+    }
+
+    private void overlaySleep(final Map<Integer, CmfActivitySample> sampleByTs, final int timestamp_from, final int timestamp_to) {
+        final CmfSleepStageSampleProvider sleepStageSampleProvider = new CmfSleepStageSampleProvider(getDevice(), getSession());
+        final List<CmfSleepStageSample> sleepStageSamples = sleepStageSampleProvider.getAllSamples(timestamp_from * 1000L, timestamp_to * 1000L);
+
+        for (final CmfSleepStageSample sleepStageSample : sleepStageSamples) {
+            // round to the nearest minute, we don't need per-second granularity
+            final int tsSeconds = (int) ((sleepStageSample.getTimestamp() / 1000) / 60) * 60;
+            for (int i = tsSeconds; i < tsSeconds + sleepStageSample.getDuration(); i += 60) {
+                CmfActivitySample sample = sampleByTs.get(i);
+                if (sample == null) {
+                    //LOG.debug("Adding dummy sample at {} for sleep", i);
+                    sample = new CmfActivitySample();
+                    sample.setTimestamp(i);
+                    sample.setProvider(this);
+                    sampleByTs.put(i, sample);
+                }
+
+                final ActivityKind sleepRawKind = sleepStageToActivityKind(sleepStageSample.getStage());
+                sample.setRawKind(sleepRawKind.getCode());
+                sample.setRawIntensity(ActivitySample.NOT_MEASURED);
+            }
+        }
+    }
+
+    final ActivityKind sleepStageToActivityKind(final int sleepStage) {
+        switch (sleepStage) {
+            case 1:
+                return ActivityKind.DEEP_SLEEP;
+            case 2:
+                return ActivityKind.LIGHT_SLEEP;
+            case 3:
+                return ActivityKind.REM_SLEEP;
+            default:
+                return ActivityKind.UNKNOWN;
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/cmfwatchpro/samples/CmfHeartRateSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/cmfwatchpro/samples/CmfHeartRateSampleProvider.java
new file mode 100644
index 0000000..268e44d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/cmfwatchpro/samples/CmfHeartRateSampleProvider.java
@@ -0,0 +1,56 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.cmfwatchpro.samples;
+
+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.CmfHeartRateSample;
+import nodomain.freeyourgadget.gadgetbridge.entities.CmfHeartRateSampleDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class CmfHeartRateSampleProvider extends AbstractTimeSampleProvider<CmfHeartRateSample> {
+    public CmfHeartRateSampleProvider(final GBDevice device, final DaoSession session) {
+        super(device, session);
+    }
+
+    @NonNull
+    @Override
+    public AbstractDao<CmfHeartRateSample, ?> getSampleDao() {
+        return getSession().getCmfHeartRateSampleDao();
+    }
+
+    @NonNull
+    @Override
+    protected Property getTimestampSampleProperty() {
+        return CmfHeartRateSampleDao.Properties.Timestamp;
+    }
+
+    @NonNull
+    @Override
+    protected Property getDeviceIdentifierSampleProperty() {
+        return CmfHeartRateSampleDao.Properties.DeviceId;
+    }
+
+    @Override
+    public CmfHeartRateSample createSample() {
+        return new CmfHeartRateSample();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/cmfwatchpro/samples/CmfSleepSessionSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/cmfwatchpro/samples/CmfSleepSessionSampleProvider.java
new file mode 100644
index 0000000..b907137
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/cmfwatchpro/samples/CmfSleepSessionSampleProvider.java
@@ -0,0 +1,56 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.cmfwatchpro.samples;
+
+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.CmfSleepSessionSample;
+import nodomain.freeyourgadget.gadgetbridge.entities.CmfSleepSessionSampleDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class CmfSleepSessionSampleProvider extends AbstractTimeSampleProvider<CmfSleepSessionSample> {
+    public CmfSleepSessionSampleProvider(final GBDevice device, final DaoSession session) {
+        super(device, session);
+    }
+
+    @NonNull
+    @Override
+    public AbstractDao<CmfSleepSessionSample, ?> getSampleDao() {
+        return getSession().getCmfSleepSessionSampleDao();
+    }
+
+    @NonNull
+    @Override
+    protected Property getTimestampSampleProperty() {
+        return CmfSleepSessionSampleDao.Properties.Timestamp;
+    }
+
+    @NonNull
+    @Override
+    protected Property getDeviceIdentifierSampleProperty() {
+        return CmfSleepSessionSampleDao.Properties.DeviceId;
+    }
+
+    @Override
+    public CmfSleepSessionSample createSample() {
+        return new CmfSleepSessionSample();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/cmfwatchpro/samples/CmfSleepStageSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/cmfwatchpro/samples/CmfSleepStageSampleProvider.java
new file mode 100644
index 0000000..c78a1b5
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/cmfwatchpro/samples/CmfSleepStageSampleProvider.java
@@ -0,0 +1,56 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.cmfwatchpro.samples;
+
+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.CmfSleepStageSample;
+import nodomain.freeyourgadget.gadgetbridge.entities.CmfSleepStageSampleDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class CmfSleepStageSampleProvider extends AbstractTimeSampleProvider<CmfSleepStageSample> {
+    public CmfSleepStageSampleProvider(final GBDevice device, final DaoSession session) {
+        super(device, session);
+    }
+
+    @NonNull
+    @Override
+    public AbstractDao<CmfSleepStageSample, ?> getSampleDao() {
+        return getSession().getCmfSleepStageSampleDao();
+    }
+
+    @NonNull
+    @Override
+    protected Property getTimestampSampleProperty() {
+        return CmfSleepStageSampleDao.Properties.Timestamp;
+    }
+
+    @NonNull
+    @Override
+    protected Property getDeviceIdentifierSampleProperty() {
+        return CmfSleepStageSampleDao.Properties.DeviceId;
+    }
+
+    @Override
+    public CmfSleepStageSample createSample() {
+        return new CmfSleepStageSample();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/cmfwatchpro/samples/CmfSpo2SampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/cmfwatchpro/samples/CmfSpo2SampleProvider.java
new file mode 100644
index 0000000..860c50e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/cmfwatchpro/samples/CmfSpo2SampleProvider.java
@@ -0,0 +1,56 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.cmfwatchpro.samples;
+
+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.CmfSpo2Sample;
+import nodomain.freeyourgadget.gadgetbridge.entities.CmfSpo2SampleDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class CmfSpo2SampleProvider extends AbstractTimeSampleProvider<CmfSpo2Sample> {
+    public CmfSpo2SampleProvider(final GBDevice device, final DaoSession session) {
+        super(device, session);
+    }
+
+    @NonNull
+    @Override
+    public AbstractDao<CmfSpo2Sample, ?> getSampleDao() {
+        return getSession().getCmfSpo2SampleDao();
+    }
+
+    @NonNull
+    @Override
+    protected Property getTimestampSampleProperty() {
+        return CmfSpo2SampleDao.Properties.Timestamp;
+    }
+
+    @NonNull
+    @Override
+    protected Property getDeviceIdentifierSampleProperty() {
+        return CmfSpo2SampleDao.Properties.DeviceId;
+    }
+
+    @Override
+    public CmfSpo2Sample createSample() {
+        return new CmfSpo2Sample();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/cmfwatchpro/samples/CmfStressSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/cmfwatchpro/samples/CmfStressSampleProvider.java
new file mode 100644
index 0000000..3e1a94a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/cmfwatchpro/samples/CmfStressSampleProvider.java
@@ -0,0 +1,56 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.cmfwatchpro.samples;
+
+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.CmfStressSample;
+import nodomain.freeyourgadget.gadgetbridge.entities.CmfStressSampleDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class CmfStressSampleProvider extends AbstractTimeSampleProvider<CmfStressSample> {
+    public CmfStressSampleProvider(final GBDevice device, final DaoSession session) {
+        super(device, session);
+    }
+
+    @NonNull
+    @Override
+    public AbstractDao<CmfStressSample, ?> getSampleDao() {
+        return getSession().getCmfStressSampleDao();
+    }
+
+    @NonNull
+    @Override
+    protected Property getTimestampSampleProperty() {
+        return CmfStressSampleDao.Properties.Timestamp;
+    }
+
+    @NonNull
+    @Override
+    protected Property getDeviceIdentifierSampleProperty() {
+        return CmfStressSampleDao.Properties.DeviceId;
+    }
+
+    @Override
+    public CmfStressSample createSample() {
+        return new CmfStressSample();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/cmfwatchpro/samples/CmfWorkoutGpsSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/cmfwatchpro/samples/CmfWorkoutGpsSampleProvider.java
new file mode 100644
index 0000000..680906e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/cmfwatchpro/samples/CmfWorkoutGpsSampleProvider.java
@@ -0,0 +1,56 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.cmfwatchpro.samples;
+
+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.CmfWorkoutGpsSample;
+import nodomain.freeyourgadget.gadgetbridge.entities.CmfWorkoutGpsSampleDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class CmfWorkoutGpsSampleProvider extends AbstractTimeSampleProvider<CmfWorkoutGpsSample> {
+    public CmfWorkoutGpsSampleProvider(final GBDevice device, final DaoSession session) {
+        super(device, session);
+    }
+
+    @NonNull
+    @Override
+    public AbstractDao<CmfWorkoutGpsSample, ?> getSampleDao() {
+        return getSession().getCmfWorkoutGpsSampleDao();
+    }
+
+    @NonNull
+    @Override
+    protected Property getTimestampSampleProperty() {
+        return CmfWorkoutGpsSampleDao.Properties.Timestamp;
+    }
+
+    @NonNull
+    @Override
+    protected Property getDeviceIdentifierSampleProperty() {
+        return CmfWorkoutGpsSampleDao.Properties.DeviceId;
+    }
+
+    @Override
+    public CmfWorkoutGpsSample createSample() {
+        return new CmfWorkoutGpsSample();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/cmfwatchpro/workout/CmfWorkoutSummaryParser.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/cmfwatchpro/workout/CmfWorkoutSummaryParser.java
new file mode 100644
index 0000000..f66faf6
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/cmfwatchpro/workout/CmfWorkoutSummaryParser.java
@@ -0,0 +1,74 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.cmfwatchpro.workout;
+
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.ACTIVE_SECONDS;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.UNIT_SECONDS;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Date;
+
+import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryData;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryParser;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.cmfwatchpro.CmfActivityType;
+
+public class CmfWorkoutSummaryParser implements ActivitySummaryParser {
+    private final GBDevice gbDevice;
+
+    public CmfWorkoutSummaryParser(final GBDevice device) {
+        this.gbDevice = device;
+    }
+
+    @Override
+    public BaseActivitySummary parseBinaryData(final BaseActivitySummary summary, final boolean forDetails) {
+        final byte[] rawSummaryData = summary.getRawSummaryData();
+        if (rawSummaryData == null) {
+            return summary;
+        }
+
+        final ByteBuffer buf = ByteBuffer.wrap(rawSummaryData).order(ByteOrder.LITTLE_ENDIAN);
+
+        final ActivitySummaryData summaryData = new ActivitySummaryData();
+
+        final int startTime = buf.getInt();
+        final int duration = buf.getShort();
+        final byte workoutType = buf.get();
+
+        buf.get(new byte[19]); // ?
+        final int endTime = buf.getInt();
+        final boolean gps = buf.get() == 1;
+        buf.get(); // ?
+
+        summary.setStartTime(new Date(startTime * 1000L));
+        summary.setEndTime(new Date(endTime * 1000L));
+
+        final CmfActivityType cmfActivityType = CmfActivityType.fromCode(workoutType);
+        if (cmfActivityType != null) {
+            summary.setActivityKind(cmfActivityType.getActivityKind().getCode());
+        } else {
+            summary.setActivityKind(ActivityKind.UNKNOWN.getCode());
+        }
+
+        summaryData.add(ACTIVE_SECONDS, duration, UNIT_SECONDS);
+
+        return summary;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/colmi/AbstractColmiR0xCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/colmi/AbstractColmiR0xCoordinator.java
new file mode 100644
index 0000000..1a6136c
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/colmi/AbstractColmiR0xCoordinator.java
@@ -0,0 +1,233 @@
+/*  Copyright (C) 2024 Arjan Schrijver
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.colmi;
+
+import androidx.annotation.NonNull;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import de.greenrobot.dao.AbstractDao;
+import de.greenrobot.dao.Property;
+import nodomain.freeyourgadget.gadgetbridge.GBException;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettings;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsScreen;
+import nodomain.freeyourgadget.gadgetbridge.capabilities.HeartRateCapability;
+import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLEDeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.TimeSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.colmi.samples.ColmiActivitySampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.colmi.samples.ColmiHrvSummarySampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.colmi.samples.ColmiHrvValueSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.colmi.samples.ColmiSpo2SampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.colmi.samples.ColmiStressSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.ColmiActivitySampleDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.ColmiHeartRateSampleDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.ColmiHrvSummarySampleDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.ColmiHrvValueSampleDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.ColmiSleepSessionSampleDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.ColmiSleepStageSampleDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.ColmiSpo2SampleDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.ColmiStressSampleDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.model.HrvSummarySample;
+import nodomain.freeyourgadget.gadgetbridge.model.HrvValueSample;
+import nodomain.freeyourgadget.gadgetbridge.model.Spo2Sample;
+import nodomain.freeyourgadget.gadgetbridge.model.StressSample;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.colmi.ColmiR0xDeviceSupport;
+
+public abstract class AbstractColmiR0xCoordinator extends AbstractBLEDeviceCoordinator {
+    private static final Logger LOG = LoggerFactory.getLogger(AbstractColmiR0xCoordinator.class);
+
+    @Override
+    protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
+        final Long deviceId = device.getId();
+
+        final Map<AbstractDao<?, ?>, Property> daoMap = new HashMap<AbstractDao<?, ?>, Property>() {{
+            put(session.getColmiActivitySampleDao(), ColmiActivitySampleDao.Properties.DeviceId);
+            put(session.getColmiHeartRateSampleDao(), ColmiHeartRateSampleDao.Properties.DeviceId);
+            put(session.getColmiSpo2SampleDao(), ColmiSpo2SampleDao.Properties.DeviceId);
+            put(session.getColmiStressSampleDao(), ColmiStressSampleDao.Properties.DeviceId);
+            put(session.getColmiSleepSessionSampleDao(), ColmiSleepSessionSampleDao.Properties.DeviceId);
+            put(session.getColmiSleepStageSampleDao(), ColmiSleepStageSampleDao.Properties.DeviceId);
+            put(session.getColmiHrvSummarySampleDao(), ColmiHrvSummarySampleDao.Properties.DeviceId);
+            put(session.getColmiHrvValueSampleDao(), ColmiHrvValueSampleDao.Properties.DeviceId);
+        }};
+
+        for (final Map.Entry<AbstractDao<?, ?>, Property> e : daoMap.entrySet()) {
+            e.getKey().queryBuilder()
+                    .where(e.getValue().eq(deviceId))
+                    .buildDelete().executeDeleteWithoutDetachingEntities();
+        }
+    }
+
+    @Override
+    public String getManufacturer() {
+        return "Colmi";
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return ColmiR0xDeviceSupport.class;
+    }
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_smartring;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_smartring_disabled;
+    }
+
+    @Override
+    public int getBondingStyle() {
+        return BONDING_STYLE_NONE;
+    }
+
+    @Override
+    public boolean supportsPowerOff() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsFindDevice() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsActivityTracking() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsActivityDataFetching() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsRealtimeData() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsStressMeasurement() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsSpo2(GBDevice device) {
+        return true;
+    }
+
+    @Override
+    public boolean supportsHeartRateStats() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsHeartRateMeasurement(GBDevice device) {
+        return true;
+    }
+
+    @Override
+    public boolean supportsManualHeartRateMeasurement(GBDevice device) {
+        return true;
+    }
+
+    @Override
+    public boolean supportsRemSleep() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsAwakeSleep() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsHrvMeasurement() {
+        return true;
+    }
+
+    @Override
+    public SampleProvider<? extends ActivitySample> getSampleProvider(GBDevice device, DaoSession session) {
+        return new ColmiActivitySampleProvider(device, session);
+    }
+
+    @Override
+    public TimeSampleProvider<? extends Spo2Sample> getSpo2SampleProvider(GBDevice device, DaoSession session) {
+        return new ColmiSpo2SampleProvider(device, session);
+    }
+
+    @Override
+    public TimeSampleProvider<? extends StressSample> getStressSampleProvider(GBDevice device, DaoSession session) {
+        return new ColmiStressSampleProvider(device, session);
+    }
+
+    @Override
+    public TimeSampleProvider<? extends HrvSummarySample> getHrvSummarySampleProvider(GBDevice device, DaoSession session) {
+        return new ColmiHrvSummarySampleProvider(device, session);
+    }
+
+    @Override
+    public TimeSampleProvider<? extends HrvValueSample> getHrvValueSampleProvider(final GBDevice device, final DaoSession session) {
+        return new ColmiHrvValueSampleProvider(device, session);
+    }
+
+    @Override
+    public List<HeartRateCapability.MeasurementInterval> getHeartRateMeasurementIntervals() {
+        return Arrays.asList(
+                HeartRateCapability.MeasurementInterval.OFF,
+                HeartRateCapability.MeasurementInterval.MINUTES_5,
+                HeartRateCapability.MeasurementInterval.MINUTES_10,
+                HeartRateCapability.MeasurementInterval.MINUTES_15,
+                HeartRateCapability.MeasurementInterval.MINUTES_30,
+                HeartRateCapability.MeasurementInterval.MINUTES_45,
+                HeartRateCapability.MeasurementInterval.HOUR_1
+        );
+    }
+
+    @Override
+    public int[] getStressRanges() {
+        // 1-29 = relaxed
+        // 30-59 = normal
+        // 60-79 = medium
+        // 80-99 = high
+        return new int[]{1, 30, 60, 80};
+    }
+
+    @Override
+    public DeviceSpecificSettings getDeviceSpecificSettings(final GBDevice device) {
+        final DeviceSpecificSettings deviceSpecificSettings = new DeviceSpecificSettings();
+        final List<Integer> health = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.HEALTH);
+        health.add(R.xml.devicesettings_colmi_r0x);
+        return deviceSpecificSettings;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/colmi/ColmiR02Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/colmi/ColmiR02Coordinator.java
new file mode 100644
index 0000000..e56f2ff
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/colmi/ColmiR02Coordinator.java
@@ -0,0 +1,38 @@
+/*  Copyright (C) 2024 Arjan Schrijver
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.colmi;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+
+public class ColmiR02Coordinator extends AbstractColmiR0xCoordinator {
+    private static final Logger LOG = LoggerFactory.getLogger(ColmiR02Coordinator.class);
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("R02_.*");
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_colmi_r02;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/colmi/ColmiR03Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/colmi/ColmiR03Coordinator.java
new file mode 100644
index 0000000..0ab8a74
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/colmi/ColmiR03Coordinator.java
@@ -0,0 +1,38 @@
+/*  Copyright (C) 2024 Arjan Schrijver
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.colmi;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+
+public class ColmiR03Coordinator extends AbstractColmiR0xCoordinator {
+    private static final Logger LOG = LoggerFactory.getLogger(ColmiR03Coordinator.class);
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("R03_.*");
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_colmi_r03;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/colmi/ColmiR06Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/colmi/ColmiR06Coordinator.java
new file mode 100644
index 0000000..46ea27b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/colmi/ColmiR06Coordinator.java
@@ -0,0 +1,38 @@
+/*  Copyright (C) 2024 Arjan Schrijver
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.colmi;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+
+public class ColmiR06Coordinator extends AbstractColmiR0xCoordinator {
+    private static final Logger LOG = LoggerFactory.getLogger(ColmiR06Coordinator.class);
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("R06_.*");
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_colmi_r06;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/colmi/ColmiR09Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/colmi/ColmiR09Coordinator.java
new file mode 100644
index 0000000..8a27e0d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/colmi/ColmiR09Coordinator.java
@@ -0,0 +1,38 @@
+/*  Copyright (C) 2024 Arjan Schrijver
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.colmi;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+
+public class ColmiR09Coordinator extends AbstractColmiR0xCoordinator {
+    private static final Logger LOG = LoggerFactory.getLogger(ColmiR09Coordinator.class);
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("R09_.*");
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_colmi_r09;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/colmi/ColmiR0xConstants.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/colmi/ColmiR0xConstants.java
new file mode 100644
index 0000000..fd80e8c
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/colmi/ColmiR0xConstants.java
@@ -0,0 +1,67 @@
+/*  Copyright (C) 2024 Arjan Schrijver
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.colmi;
+
+import java.util.UUID;
+
+public class ColmiR0xConstants {
+    public static final UUID CHARACTERISTIC_SERVICE_V1 = UUID.fromString("6e40fff0-b5a3-f393-e0a9-e50e24dcca9e");
+    public static final UUID CHARACTERISTIC_SERVICE_V2 = UUID.fromString("de5bf728-d711-4e47-af26-65e3012a5dc7");
+    public static final UUID CHARACTERISTIC_WRITE = UUID.fromString("6e400002-b5a3-f393-e0a9-e50e24dcca9e");
+    public static final UUID CHARACTERISTIC_COMMAND = UUID.fromString("de5bf72a-d711-4e47-af26-65e3012a5dc7");
+    public static final UUID CHARACTERISTIC_NOTIFY_V1 = UUID.fromString("6e400003-b5a3-f393-e0a9-e50e24dcca9e");
+    public static final UUID CHARACTERISTIC_NOTIFY_V2 = UUID.fromString("de5bf729-d711-4e47-af26-65e3012a5dc7");
+
+    public static final byte CMD_SET_DATE_TIME = 0x01;
+    public static final byte CMD_BATTERY = 0x03;
+    public static final byte CMD_PHONE_NAME = 0x04;
+    public static final byte CMD_POWER_OFF = 0x08;
+    public static final byte CMD_PREFERENCES = 0x0a;
+    public static final byte CMD_SYNC_HEART_RATE = 0x15;
+    public static final byte CMD_AUTO_HR_PREF = 0x16;
+    public static final byte CMD_GOALS = 0x21;
+    public static final byte CMD_AUTO_SPO2_PREF = 0x2c;
+    public static final byte CMD_PACKET_SIZE = 0x2f;
+    public static final byte CMD_AUTO_STRESS_PREF = 0x36;
+    public static final byte CMD_SYNC_STRESS = 0x37;
+    public static final byte CMD_AUTO_HRV_PREF = 0x38;
+    public static final byte CMD_SYNC_HRV = 0x39;
+    public static final byte CMD_SYNC_ACTIVITY = 0x43;
+    public static final byte CMD_FIND_DEVICE = 0x50;
+    public static final byte CMD_MANUAL_HEART_RATE = 0x69;
+    public static final byte CMD_NOTIFICATION = 0x73;
+    public static final byte CMD_BIG_DATA_V2 = (byte) 0xbc;
+    public static final byte CMD_FACTORY_RESET = (byte) 0xff;
+
+    public static final byte PREF_READ = 0x01;
+    public static final byte PREF_WRITE = 0x02;
+    public static final byte PREF_DELETE = 0x03;
+
+    public static final byte NOTIFICATION_NEW_HR_DATA = 0x01;
+    public static final byte NOTIFICATION_NEW_SPO2_DATA = 0x03;
+    public static final byte NOTIFICATION_NEW_STEPS_DATA = 0x04;
+    public static final byte NOTIFICATION_BATTERY_LEVEL = 0x0c;
+    public static final byte NOTIFICATION_LIVE_ACTIVITY = 0x12;
+
+    public static final byte BIG_DATA_TYPE_SLEEP = 0x27;
+    public static final byte BIG_DATA_TYPE_SPO2 = 0x2a;
+
+    public static final byte SLEEP_TYPE_LIGHT = 0x02;
+    public static final byte SLEEP_TYPE_DEEP = 0x03;
+    public static final byte SLEEP_TYPE_REM = 0x04;
+    public static final byte SLEEP_TYPE_AWAKE = 0x05;
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/colmi/ColmiR0xPacketHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/colmi/ColmiR0xPacketHandler.java
new file mode 100644
index 0000000..d7d06ef
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/colmi/ColmiR0xPacketHandler.java
@@ -0,0 +1,491 @@
+/*  Copyright (C) 2024 Arjan Schrijver
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.colmi;
+
+import android.content.Context;
+import android.content.Intent;
+import android.widget.Toast;
+
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.List;
+
+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.GBDeviceEventUpdatePreferences;
+import nodomain.freeyourgadget.gadgetbridge.devices.colmi.samples.ColmiActivitySampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.colmi.samples.ColmiHeartRateSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.colmi.samples.ColmiHrvValueSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.colmi.samples.ColmiSleepSessionSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.colmi.samples.ColmiSleepStageSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.colmi.samples.ColmiSpo2SampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.colmi.samples.ColmiStressSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.ColmiActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.entities.ColmiHeartRateSample;
+import nodomain.freeyourgadget.gadgetbridge.entities.ColmiHrvValueSample;
+import nodomain.freeyourgadget.gadgetbridge.entities.ColmiSleepSessionSample;
+import nodomain.freeyourgadget.gadgetbridge.entities.ColmiSleepStageSample;
+import nodomain.freeyourgadget.gadgetbridge.entities.ColmiSpo2Sample;
+import nodomain.freeyourgadget.gadgetbridge.entities.ColmiStressSample;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.entities.User;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.colmi.ColmiR0xDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
+
+public class ColmiR0xPacketHandler {
+    private static final Logger LOG = LoggerFactory.getLogger(ColmiR0xPacketHandler.class);
+
+    public static void hrIntervalSettings(ColmiR0xDeviceSupport support, byte[] value) {
+        if (value[1] == ColmiR0xConstants.PREF_WRITE) return;  // ignore empty response when writing setting
+        boolean enabled = value[2] == 0x01;
+        int minutes = value[3];
+        LOG.info("Received HR interval preference: {} minutes, enabled={}", minutes, enabled);
+        GBDeviceEventUpdatePreferences eventUpdatePreferences = new GBDeviceEventUpdatePreferences();
+        eventUpdatePreferences.withPreference(
+                DeviceSettingsPreferenceConst.PREF_HEARTRATE_MEASUREMENT_INTERVAL,
+                String.valueOf(minutes * 60)
+        );
+        support.evaluateGBDeviceEvent(eventUpdatePreferences);
+    }
+
+    public static void spo2Settings(ColmiR0xDeviceSupport support, byte[] value) {
+        boolean enabled = value[2] == 0x01;
+        LOG.info("Received SpO2 preference: {}", enabled ? "enabled" : "disabled");
+        GBDeviceEventUpdatePreferences eventUpdatePreferences = new GBDeviceEventUpdatePreferences();
+        eventUpdatePreferences.withPreference(
+                DeviceSettingsPreferenceConst.PREF_SPO2_ALL_DAY_MONITORING,
+                enabled
+        );
+        support.evaluateGBDeviceEvent(eventUpdatePreferences);
+    }
+
+    public static void stressSettings(ColmiR0xDeviceSupport support, byte[] value) {
+        boolean enabled = value[2] == 0x01;
+        LOG.info("Received stress preference: {}", enabled ? "enabled" : "disabled");
+        GBDeviceEventUpdatePreferences eventUpdatePreferences = new GBDeviceEventUpdatePreferences();
+        eventUpdatePreferences.withPreference(
+                DeviceSettingsPreferenceConst.PREF_HEARTRATE_STRESS_MONITORING,
+                enabled
+        );
+        support.evaluateGBDeviceEvent(eventUpdatePreferences);
+    }
+
+    public static void hrvSettings(ColmiR0xDeviceSupport support, byte[] value) {
+        boolean enabled = value[2] == 0x01;
+        LOG.info("Received HRV preference: {}", enabled ? "enabled" : "disabled");
+        GBDeviceEventUpdatePreferences eventUpdatePreferences = new GBDeviceEventUpdatePreferences();
+        eventUpdatePreferences.withPreference(
+                DeviceSettingsPreferenceConst.PREF_HRV_ALL_DAY_MONITORING,
+                enabled
+        );
+        support.evaluateGBDeviceEvent(eventUpdatePreferences);
+    }
+
+    public static void goalsSettings(byte[] value) {
+        int steps = BLETypeConversions.toUint32(value[2], value[3], value[4], (byte) 0);
+        int calories = BLETypeConversions.toUint32(value[5], value[6], value[7], (byte) 0);
+        int distance = BLETypeConversions.toUint32(value[8], value[9], value[10], (byte) 0);
+        int sport = BLETypeConversions.toUint16(value[11], value[12]);
+        int sleep = BLETypeConversions.toUint16(value[13], value[14]);
+        LOG.info("Received goals preferences: {} steps, {} calories, {}m distance, {}min sport, {}min sleep", steps, calories, distance, sport, sleep);
+    }
+
+    public static void liveHeartRate(GBDevice device, Context context, byte[] value) {
+        int errorCode = value[2];
+        int hrResponse = value[3] & 0xff;
+        switch (errorCode) {
+            case 0:
+                LOG.info("Received live heart rate response: {} bpm", hrResponse);
+                break;
+            case 1:
+                GB.toast(context.getString(R.string.smart_ring_measurement_error_worn_incorrectly), Toast.LENGTH_LONG, GB.ERROR);
+                LOG.warn("Live HR error code {} received from ring", errorCode);
+                return;
+            case 2:
+                LOG.warn("Live HR error 2 (temporary error / missing data) received");
+                return;
+            default:
+                GB.toast(String.format(context.getString(R.string.smart_ring_measurement_error_unknown), errorCode), Toast.LENGTH_LONG, GB.ERROR);
+                LOG.warn("Live HR error code {} received from ring", errorCode);
+                return;
+        }
+        if (hrResponse > 0) {
+            try (DBHandler db = GBApplication.acquireDB()) {
+                // Build sample object and save in database
+                ColmiHeartRateSampleProvider sampleProvider = new ColmiHeartRateSampleProvider(device, db.getDaoSession());
+                Long userId = DBHelper.getUser(db.getDaoSession()).getId();
+                Long deviceId = DBHelper.getDevice(device, db.getDaoSession()).getId();
+                ColmiHeartRateSample gbSample = new ColmiHeartRateSample();
+                gbSample.setDeviceId(deviceId);
+                gbSample.setUserId(userId);
+                gbSample.setTimestamp(Calendar.getInstance().getTimeInMillis());
+                gbSample.setHeartRate(hrResponse);
+                sampleProvider.addSample(gbSample);
+                // Send local intent with sample for listeners like the heart rate dialog
+                Intent liveIntent = new Intent(DeviceService.ACTION_REALTIME_SAMPLES);
+                liveIntent.putExtra(GBDevice.EXTRA_DEVICE, device);
+                liveIntent.putExtra(DeviceService.EXTRA_REALTIME_SAMPLE, gbSample);
+                LocalBroadcastManager.getInstance(context)
+                        .sendBroadcast(liveIntent);
+            } catch (Exception e) {
+                LOG.error("Error acquiring database for recording heart rate samples", e);
+            }
+        }
+    }
+
+    public static void liveActivity(byte[] value) {
+        int steps = BLETypeConversions.toUint32(value[4], value[3], value[2], (byte) 0);
+        int calories = BLETypeConversions.toUint32(value[7], value[6], value[5], (byte) 0) / 10;
+        int distance = BLETypeConversions.toUint32(value[10], value[9], value[8], (byte) 0);
+        LOG.info("Received live activity notification: {} steps, {} calories, {}m distance", steps, calories, distance);
+    }
+
+    public static void historicalActivity(GBDevice device, Context context, byte[] value) {
+        if ((value[1] & 0xff) == 0xff) {
+            device.unsetBusyTask();
+            device.sendDeviceUpdateIntent(context);
+            LOG.info("Empty activity history, sync aborted");
+        } else if ((value[1] & 0xff) == 0xf0) {
+            // initial packet, doesn't contain anything interesting
+        } else {
+            // Unpack timestamp and data
+            Calendar sampleCal = Calendar.getInstance();
+            // The code below converts the raw hex value to a date. That seems wrong, but is correct,
+            // because this date is for some reason transmitted as ints used as literal bytes:
+            // A date like 2024-08-18 would be transmitted as 0x24 0x08 0x18.
+            sampleCal.set(Calendar.YEAR, 2000 + Integer.valueOf(String.format("%02x", value[1])));
+            sampleCal.set(Calendar.MONTH, Integer.valueOf(String.format("%02x", value[2])) - 1);
+            sampleCal.set(Calendar.DAY_OF_MONTH, Integer.valueOf(String.format("%02x", value[3])));
+            sampleCal.set(Calendar.HOUR_OF_DAY, value[4] / 4);  // And the hour is transmitted as nth quarter of the day...
+            sampleCal.set(Calendar.MINUTE, 0);
+            sampleCal.set(Calendar.SECOND, 0);
+            sampleCal.set(Calendar.MILLISECOND, 0);
+            int calories = BLETypeConversions.toUint16(value[7], value[8]);
+            int steps = BLETypeConversions.toUint16(value[9], value[10]);
+            int distance = BLETypeConversions.toUint16(value[11], value[12]);
+            LOG.info("Received activity sample: {} - {} calories, {} steps, {} distance", sampleCal.getTime(), calories, steps, distance);
+            // Build sample object and save in database
+            try (DBHandler db = GBApplication.acquireDB()) {
+                ColmiActivitySampleProvider sampleProvider = new ColmiActivitySampleProvider(device, db.getDaoSession());
+                Long userId = DBHelper.getUser(db.getDaoSession()).getId();
+                Long deviceId = DBHelper.getDevice(device, db.getDaoSession()).getId();
+                ColmiActivitySample gbSample = sampleProvider.createActivitySample();
+                gbSample.setProvider(sampleProvider);
+                gbSample.setDeviceId(deviceId);
+                gbSample.setUserId(userId);
+                gbSample.setRawKind(ActivityKind.ACTIVITY.getCode());
+                gbSample.setTimestamp((int) (sampleCal.getTimeInMillis() / 1000));
+                gbSample.setCalories(calories);
+                gbSample.setSteps(steps);
+                gbSample.setDistance(distance);
+                sampleProvider.addGBActivitySample(gbSample);
+            } catch (Exception e) {
+                LOG.error("Error acquiring database for recording activity samples", e);
+            }
+            // Determine if this sync is done
+            int currentActivityPacket = value[5];
+            int totalActivityPackets = value[6];
+            if (currentActivityPacket == totalActivityPackets - 1) {
+                device.unsetBusyTask();
+                device.sendDeviceUpdateIntent(context);
+            }
+        }
+    }
+
+    public static void historicalStress(GBDevice device, Context context, byte[] value) {
+        ArrayList<ColmiStressSample> stressSamples = new ArrayList<>();
+        int stressPacketNr = value[1] & 0xff;
+        if (stressPacketNr == 0xff) {
+            device.unsetBusyTask();
+            device.sendDeviceUpdateIntent(context);
+            LOG.info("Empty stress history, sync aborted");
+        } else if (stressPacketNr == 0) {
+            LOG.info("Received initial stress history response");
+        } else {
+            Calendar sampleCal = Calendar.getInstance();
+            sampleCal.set(Calendar.SECOND, 0);
+            sampleCal.set(Calendar.MILLISECOND, 0);
+            int startValue = stressPacketNr == 1 ? 3 : 2;  // packet 1 data starts at byte 3, others at byte 2
+            int minutesInPreviousPackets = 0;
+            if (stressPacketNr > 1) {
+                // 30 is the interval in minutes between values/measurements
+                minutesInPreviousPackets = 12 * 30;  // 12 values in packet 1
+                minutesInPreviousPackets += (stressPacketNr - 2) * 13 * 30;  // 13 values per packet
+            }
+            for (int i = startValue; i < value.length - 1; i++) {
+                if (value[i] != 0x00) {
+                    // Determine time of day
+                    int minuteOfDay = minutesInPreviousPackets + (i - startValue) * 30;
+                    sampleCal.set(Calendar.HOUR_OF_DAY, minuteOfDay / 60);
+                    sampleCal.set(Calendar.MINUTE, minuteOfDay % 60);
+                    LOG.info("Stress level is {} at {}", value[i] & 0xff, sampleCal.getTime());
+                    // Build sample object and save in database
+                    ColmiStressSample gbSample = new ColmiStressSample();
+                    gbSample.setTimestamp(sampleCal.getTimeInMillis());
+                    gbSample.setStress(value[i] & 0xff);
+                    stressSamples.add(gbSample);
+                }
+            }
+            if (!stressSamples.isEmpty()) {
+                try (DBHandler db = GBApplication.acquireDB()) {
+                    ColmiStressSampleProvider sampleProvider = new ColmiStressSampleProvider(device, db.getDaoSession());
+                    Long userId = DBHelper.getUser(db.getDaoSession()).getId();
+                    Long deviceId = DBHelper.getDevice(device, db.getDaoSession()).getId();
+                    for (final ColmiStressSample sample : stressSamples) {
+                        sample.setDeviceId(deviceId);
+                        sample.setUserId(userId);
+                    }
+                    LOG.info("Will persist {} stress samples", stressSamples.size());
+                    sampleProvider.addSamples(stressSamples);
+                } catch (Exception e) {
+                    LOG.error("Error acquiring database for recording stress samples", e);
+                }
+            }
+            if (stressPacketNr == 4) {
+                device.unsetBusyTask();
+                device.sendDeviceUpdateIntent(context);
+            }
+        }
+    }
+
+    public static void historicalSpo2(GBDevice device, byte[] value) {
+        ArrayList<ColmiSpo2Sample> spo2Samples = new ArrayList<>();
+        int length = BLETypeConversions.toUint16(value[2], value[3]);
+        int index = 6; // start of data (day nr, followed by values)
+        int spo2_days_ago = -1;
+        while (spo2_days_ago != 0 && index - 6 < length) {
+            spo2_days_ago = value[index];
+            Calendar syncingDay = Calendar.getInstance();
+            syncingDay.add(Calendar.DAY_OF_MONTH, 0 - spo2_days_ago);
+            syncingDay.set(Calendar.MINUTE, 0);
+            syncingDay.set(Calendar.SECOND, 0);
+            syncingDay.set(Calendar.MILLISECOND, 0);
+            index++;
+            for (int hour=0; hour<=23; hour++) {
+                syncingDay.set(Calendar.HOUR_OF_DAY, hour);
+                float spo2_min = value[index];
+                index++;
+                float spo2_max = value[index];
+                index++;
+                if (spo2_min > 0 && spo2_max > 0) {
+                    LOG.info("Received SpO2 data from {} days ago at {}:00: min={}, max={}", spo2_days_ago, hour, spo2_min, spo2_max);
+                    ColmiSpo2Sample spo2Sample = new ColmiSpo2Sample();
+                    spo2Sample.setTimestamp(syncingDay.getTimeInMillis());
+                    spo2Sample.setSpo2(Math.round((spo2_min + spo2_max) / 2.0f));
+                    spo2Samples.add(spo2Sample);
+                }
+                if (index - 6 >= length) {
+                    break;
+                }
+            }
+        }
+        if (!spo2Samples.isEmpty()) {
+            try (DBHandler db = GBApplication.acquireDB()) {
+                ColmiSpo2SampleProvider sampleProvider = new ColmiSpo2SampleProvider(device, db.getDaoSession());
+                Long userId = DBHelper.getUser(db.getDaoSession()).getId();
+                Long deviceId = DBHelper.getDevice(device, db.getDaoSession()).getId();
+                for (final ColmiSpo2Sample sample : spo2Samples) {
+                    sample.setDeviceId(deviceId);
+                    sample.setUserId(userId);
+                }
+                LOG.info("Will persist {} SpO2 samples", spo2Samples.size());
+                sampleProvider.addSamples(spo2Samples);
+            } catch (Exception e) {
+                LOG.error("Error acquiring database for recording SpO2 samples", e);
+            }
+        }
+    }
+
+    public static void historicalSleep(GBDevice gbDevice, Context context, byte[] value) {
+        int packetLength = BLETypeConversions.toUint16(value[2], value[3]);
+        if (packetLength < 2) {
+            LOG.info("Received empty sleep data packet: {}", StringUtils.bytesToHex(value));
+        } else {
+            int daysInPacket = value[6];
+            LOG.debug("Received sleep data packet for {} days: {}", daysInPacket, StringUtils.bytesToHex(value));
+            int index = 7;
+            for (int i = 1; i <= daysInPacket; i++) {
+                // Parse sleep session
+                int daysAgo = value[index];
+                index++;
+                int dayBytes = value[index];
+                index++;
+                // sleepStart is received as "minutes after midnight"
+                int sleepStart = BLETypeConversions.toUint16(value[index], value[index + 1]);
+                index += 2;
+                // sleepEnd is received as "minutes after midnight"
+                int sleepEnd = BLETypeConversions.toUint16(value[index], value[index + 1]);
+                index += 2;
+                // Calculate sleep start timestamp
+                LOG.info("Sleep session daysAgo={}, dayBytes={}, sleepStart={}, sleepEnd={}", daysAgo, dayBytes, sleepStart, sleepEnd);
+                Calendar sessionStart = Calendar.getInstance();
+                sessionStart.add(Calendar.DAY_OF_MONTH, 0 - daysAgo);
+                sessionStart.set(Calendar.HOUR_OF_DAY, 0);
+                sessionStart.set(Calendar.MINUTE, 0);
+                sessionStart.set(Calendar.SECOND, 0);
+                sessionStart.set(Calendar.MILLISECOND, 0);
+                if (sleepStart > sleepEnd) {
+                    // Sleep started a day earlier, so before midnight
+                    sessionStart.add(Calendar.MINUTE, sleepStart - 1440);
+                } else {
+                    // Sleep started this day, so after midnight
+                    sessionStart.add(Calendar.MINUTE, sleepStart);
+                }
+                // Calculate sleep end timestamp
+                Calendar sessionEnd = Calendar.getInstance();
+                sessionEnd.add(Calendar.DAY_OF_MONTH, 0 - daysAgo);
+                sessionEnd.set(Calendar.HOUR_OF_DAY, 0);
+                sessionEnd.set(Calendar.MINUTE, sleepEnd);
+                sessionEnd.set(Calendar.SECOND, 0);
+                sessionEnd.set(Calendar.MILLISECOND, 0);
+                LOG.info("Sleep session starts at {} and ends at {}", sessionStart.getTime(), sessionEnd.getTime());
+                // Build sample object to persist
+                final ColmiSleepSessionSample sessionSample = new ColmiSleepSessionSample();
+                sessionSample.setTimestamp(sessionStart.getTimeInMillis());
+                sessionSample.setWakeupTime(sessionEnd.getTimeInMillis());
+                // Handle sleep stages
+                final List<ColmiSleepStageSample> stageSamples = new ArrayList<>();
+                Calendar sleepStage = (Calendar) sessionStart.clone();
+                for (int j = 4; j < dayBytes; j += 2) {
+                    int sleepMinutes = value[index + 1];
+                    final ColmiSleepStageSample sample = new ColmiSleepStageSample();
+                    sample.setTimestamp(sleepStage.getTimeInMillis());
+                    sample.setDuration(value[index + 1]);
+                    sample.setStage(value[index]);
+                    if (sleepMinutes > 0) {
+                        LOG.info("Sleep stage type={} starts at {} and lasts for {} minutes", value[index], sleepStage.getTime(), sleepMinutes);
+                        if (sleepStage.getTimeInMillis() + sleepMinutes * 60 * 1000 > sessionEnd.getTimeInMillis()) {
+                            LOG.warn("Warning: sleep stage exceeds end of sleep session, received data may be corrupt");
+                        }
+                        stageSamples.add(sample);
+                        sleepStage.add(Calendar.MINUTE, sleepMinutes);
+                    } else {
+                        LOG.info("Ignoring sleep stage type={} starts at {} and lasts for {} minutes", value[index], sleepStage.getTime(), sleepMinutes);
+                    }
+                    // Prepare for next sample
+                    index += 2;
+                }
+                // Persist sleep session
+                try (DBHandler handler = GBApplication.acquireDB()) {
+                    final DaoSession session = handler.getDaoSession();
+
+                    final Device device = DBHelper.getDevice(gbDevice, session);
+                    final User user = DBHelper.getUser(session);
+
+                    final ColmiSleepSessionSampleProvider sampleProvider = new ColmiSleepSessionSampleProvider(gbDevice, session);
+
+                    sessionSample.setDevice(device);
+                    sessionSample.setUser(user);
+
+                    LOG.debug("Will persist 1 sleep session sample from {} to {}", sessionSample.getTimestamp(), sessionSample.getWakeupTime());
+                    sampleProvider.addSample(sessionSample);
+                } catch (final Exception e) {
+                    GB.toast(context, "Error saving sleep session sample", Toast.LENGTH_LONG, GB.ERROR, e);
+                }
+                // Persist sleep stages
+                try (DBHandler handler = GBApplication.acquireDB()) {
+                    final DaoSession session = handler.getDaoSession();
+
+                    final Device device = DBHelper.getDevice(gbDevice, session);
+                    final User user = DBHelper.getUser(session);
+
+                    final ColmiSleepStageSampleProvider sampleProvider = new ColmiSleepStageSampleProvider(gbDevice, session);
+
+                    for (final ColmiSleepStageSample sample : stageSamples) {
+                        sample.setDevice(device);
+                        sample.setUser(user);
+                    }
+
+                    LOG.debug("Will persist {} sleep stage samples", stageSamples.size());
+                    sampleProvider.addSamples(stageSamples);
+                } catch (final Exception e) {
+                    GB.toast(context, "Error saving sleep stage samples", Toast.LENGTH_LONG, GB.ERROR, e);
+                }
+            }
+        }
+    }
+
+    public static void historicalHRV(GBDevice device, Context context, byte[] value, int daysAgo) {
+        LOG.info("Received HRV history sync packet: {}", StringUtils.bytesToHex(value));
+        int hrvPacketNr = value[1] & 0xff;
+        if (hrvPacketNr == 0xff) {
+            LOG.info("Empty HRV history, sync aborted");
+            device.unsetBusyTask();
+            device.sendDeviceUpdateIntent(context);
+        } else if (hrvPacketNr == 0) {
+            int packetsTotalNr = value[2];
+            LOG.info("HRV history packet {} out of total {}", hrvPacketNr, packetsTotalNr);
+        } else {
+            LOG.info("HRV history packet {}", hrvPacketNr);
+            Calendar sampleCal = Calendar.getInstance();
+            if (daysAgo != 0) {
+                sampleCal.add(Calendar.DAY_OF_MONTH, 0 - daysAgo);
+                sampleCal.set(Calendar.HOUR_OF_DAY, 0);
+                sampleCal.set(Calendar.MINUTE, 0);
+            }
+            sampleCal.set(Calendar.SECOND, 0);
+            sampleCal.set(Calendar.MILLISECOND, 0);
+            int startValue = hrvPacketNr == 1 ? 3 : 2;  // packet 1 contains something in byte 2
+            int minutesInPreviousPackets = 0;
+            if (hrvPacketNr > 1) {
+                minutesInPreviousPackets = 12 * 30;  // packet 1
+                minutesInPreviousPackets += (hrvPacketNr - 2) * 13 * 30;
+            }
+            for (int i = startValue; i < value.length - 1; i++) {
+                if (value[i] != 0x00) {
+                    // Determine time of day
+                    int minuteOfDay = minutesInPreviousPackets + (i - startValue) * 30;
+                    sampleCal.set(Calendar.HOUR_OF_DAY, minuteOfDay / 60);
+                    sampleCal.set(Calendar.MINUTE, minuteOfDay % 60);
+                    LOG.info("Value {} is {} ms, time of day is {}", i, value[i] & 0xff, sampleCal.getTime());
+                    // Build sample object and save in database
+                    try (DBHandler db = GBApplication.acquireDB()) {
+                        ColmiHrvValueSampleProvider sampleProvider = new ColmiHrvValueSampleProvider(device, db.getDaoSession());
+                        Long userId = DBHelper.getUser(db.getDaoSession()).getId();
+                        Long deviceId = DBHelper.getDevice(device, db.getDaoSession()).getId();
+                        ColmiHrvValueSample gbSample = new ColmiHrvValueSample();
+                        gbSample.setDeviceId(deviceId);
+                        gbSample.setUserId(userId);
+                        gbSample.setTimestamp(sampleCal.getTimeInMillis());
+                        gbSample.setValue(value[i] & 0xff);
+                        sampleProvider.addSample(gbSample);
+                    } catch (Exception e) {
+                        LOG.error("Error acquiring database for recording HRV samples", e);
+                    }
+                }
+            }
+            if (hrvPacketNr == 4) {
+                device.unsetBusyTask();
+                device.sendDeviceUpdateIntent(context);
+            }
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/colmi/ColmiR10Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/colmi/ColmiR10Coordinator.java
new file mode 100644
index 0000000..4f6972d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/colmi/ColmiR10Coordinator.java
@@ -0,0 +1,38 @@
+/*  Copyright (C) 2024 Arjan Schrijver
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.colmi;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+
+public class ColmiR10Coordinator extends AbstractColmiR0xCoordinator {
+    private static final Logger LOG = LoggerFactory.getLogger(ColmiR10Coordinator.class);
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("^COLMI R10_.*");
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_colmi_r10;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/colmi/samples/ColmiActivitySampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/colmi/samples/ColmiActivitySampleProvider.java
new file mode 100644
index 0000000..a027fa1
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/colmi/samples/ColmiActivitySampleProvider.java
@@ -0,0 +1,191 @@
+/*  Copyright (C) 2024 Arjan Schrijver
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.colmi.samples;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import de.greenrobot.dao.AbstractDao;
+import de.greenrobot.dao.Property;
+import nodomain.freeyourgadget.gadgetbridge.devices.AbstractSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.colmi.ColmiR0xConstants;
+import nodomain.freeyourgadget.gadgetbridge.entities.ColmiActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.entities.ColmiActivitySampleDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.ColmiHeartRateSample;
+import nodomain.freeyourgadget.gadgetbridge.entities.ColmiSleepStageSample;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
+
+public class ColmiActivitySampleProvider extends AbstractSampleProvider<ColmiActivitySample> {
+    private static final Logger LOG = LoggerFactory.getLogger(ColmiActivitySampleProvider.class);
+
+    public ColmiActivitySampleProvider(final GBDevice device, final DaoSession session) {
+        super(device, session);
+    }
+
+    @Override
+    public AbstractDao<ColmiActivitySample, ?> getSampleDao() {
+        return getSession().getColmiActivitySampleDao();
+    }
+
+    @Nullable
+    @Override
+    protected Property getRawKindSampleProperty() {
+        return null;
+    }
+
+    @NonNull
+    @Override
+    protected Property getTimestampSampleProperty() {
+        return ColmiActivitySampleDao.Properties.Timestamp;
+    }
+
+    @NonNull
+    @Override
+    protected Property getDeviceIdentifierSampleProperty() {
+        return ColmiActivitySampleDao.Properties.DeviceId;
+    }
+
+    @Override
+    public ActivityKind normalizeType(int rawType) {
+        return ActivityKind.fromCode(rawType);
+    }
+
+    @Override
+    public int toRawActivityKind(ActivityKind activityKind) {
+        return activityKind.getCode();
+    }
+
+    @Override
+    public float normalizeIntensity(int rawIntensity) {
+        return Math.min(rawIntensity / 7000f, 1f);
+    }
+
+    @Override
+    public ColmiActivitySample createActivitySample() {
+        return new ColmiActivitySample();
+    }
+
+    @Override
+    protected List<ColmiActivitySample> getGBActivitySamples(final int timestamp_from, final int timestamp_to) {
+        LOG.trace(
+                "Getting Colmi activity samples between {} and {}",
+                timestamp_from,
+                timestamp_to
+        );
+        final long nanoStart = System.nanoTime();
+
+        final List<ColmiActivitySample> samples = fillGaps(
+                super.getGBActivitySamples(timestamp_from, timestamp_to),
+                timestamp_from,
+                timestamp_to
+        );
+
+        final Map<Integer, ColmiActivitySample> sampleByTs = new HashMap<>();
+        for (final ColmiActivitySample sample : samples) {
+            sampleByTs.put(sample.getTimestamp(), sample);
+        }
+
+        overlayHeartRate(sampleByTs, timestamp_from, timestamp_to);
+        overlaySleep(sampleByTs, timestamp_from, timestamp_to);
+
+        final List<ColmiActivitySample> finalSamples = new ArrayList<>(sampleByTs.values());
+        Collections.sort(finalSamples, (a, b) -> Integer.compare(a.getTimestamp(), b.getTimestamp()));
+
+        final long nanoEnd = System.nanoTime();
+        final long executionTime = (nanoEnd - nanoStart) / 1000000;
+        LOG.trace("Getting Colmi samples took {}ms", executionTime);
+
+        return finalSamples;
+    }
+
+    private void overlayHeartRate(final Map<Integer, ColmiActivitySample> sampleByTs, final int timestamp_from, final int timestamp_to) {
+        final ColmiHeartRateSampleProvider heartRateSampleProvider = new ColmiHeartRateSampleProvider(getDevice(), getSession());
+        final List<ColmiHeartRateSample> hrSamples = heartRateSampleProvider.getAllSamples(timestamp_from * 1000L, timestamp_to * 1000L);
+
+        for (final ColmiHeartRateSample hrSample : hrSamples) {
+            // round to the nearest minute, we don't need per-second granularity
+            final int tsSeconds = (int) ((hrSample.getTimestamp() / 1000) / 60) * 60;
+            ColmiActivitySample sample = sampleByTs.get(tsSeconds);
+            if (sample == null) {
+                sample = new ColmiActivitySample();
+                sample.setTimestamp(tsSeconds);
+                sample.setProvider(this);
+                sampleByTs.put(tsSeconds, sample);
+            }
+
+            sample.setHeartRate(hrSample.getHeartRate());
+        }
+    }
+
+    private void overlaySleep(final Map<Integer, ColmiActivitySample> sampleByTs, final int timestamp_from, final int timestamp_to) {
+        final ColmiSleepStageSampleProvider sleepStageSampleProvider = new ColmiSleepStageSampleProvider(getDevice(), getSession());
+        final List<ColmiSleepStageSample> sleepStageSamples = sleepStageSampleProvider.getAllSamples(timestamp_from * 1000L, timestamp_to * 1000L);
+
+        // Retrieve the last stage before this time range, as the user could have been asleep during
+        // the range transition
+        final ColmiSleepStageSample lastSleepStageBeforeRange = sleepStageSampleProvider.getLastSampleBefore(timestamp_from * 1000L);
+        if (lastSleepStageBeforeRange != null && (lastSleepStageBeforeRange.getTimestamp() + lastSleepStageBeforeRange.getDuration() * 1000L > timestamp_from)) {
+            LOG.debug("Last sleep stage before range: ts={}, stage={}", lastSleepStageBeforeRange.getTimestamp(), lastSleepStageBeforeRange.getStage());
+            sleepStageSamples.add(0, lastSleepStageBeforeRange);
+        }
+
+        for (final ColmiSleepStageSample sleepStageSample : sleepStageSamples) {
+            final ActivityKind sleepRawKind = sleepStageToActivityKind(sleepStageSample.getStage());
+            // round to the nearest minute, we don't need per-second granularity
+            final int tsSeconds = (int) ((sleepStageSample.getTimestamp() / 1000) / 60) * 60;
+            for (int i = tsSeconds; i < tsSeconds + sleepStageSample.getDuration() * 60; i += 60) {
+                if (i < timestamp_from) continue;
+                ColmiActivitySample sample = sampleByTs.get(i);
+                if (sample == null) {
+                    sample = new ColmiActivitySample();
+                    sample.setTimestamp(i);
+                    sample.setProvider(this);
+                    sampleByTs.put(i, sample);
+                }
+                sample.setRawKind(sleepRawKind.getCode());
+                sample.setRawIntensity(ActivitySample.NOT_MEASURED);
+            }
+        }
+    }
+
+    final ActivityKind sleepStageToActivityKind(final int sleepStage) {
+        switch (sleepStage) {
+            case ColmiR0xConstants.SLEEP_TYPE_LIGHT:
+                return ActivityKind.LIGHT_SLEEP;
+            case ColmiR0xConstants.SLEEP_TYPE_DEEP:
+                return ActivityKind.DEEP_SLEEP;
+            case ColmiR0xConstants.SLEEP_TYPE_REM:
+                return ActivityKind.REM_SLEEP;
+            case ColmiR0xConstants.SLEEP_TYPE_AWAKE:
+                return ActivityKind.AWAKE_SLEEP;
+            default:
+                return ActivityKind.UNKNOWN;
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/colmi/samples/ColmiHeartRateSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/colmi/samples/ColmiHeartRateSampleProvider.java
new file mode 100644
index 0000000..b9b2660
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/colmi/samples/ColmiHeartRateSampleProvider.java
@@ -0,0 +1,56 @@
+/*  Copyright (C) 2024 Arjan Schrijver
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.colmi.samples;
+
+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.ColmiHeartRateSample;
+import nodomain.freeyourgadget.gadgetbridge.entities.ColmiHeartRateSampleDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class ColmiHeartRateSampleProvider extends AbstractTimeSampleProvider<ColmiHeartRateSample> {
+    public ColmiHeartRateSampleProvider(final GBDevice device, final DaoSession session) {
+        super(device, session);
+    }
+
+    @NonNull
+    @Override
+    public AbstractDao<ColmiHeartRateSample, ?> getSampleDao() {
+        return getSession().getColmiHeartRateSampleDao();
+    }
+
+    @NonNull
+    @Override
+    protected Property getTimestampSampleProperty() {
+        return ColmiHeartRateSampleDao.Properties.Timestamp;
+    }
+
+    @NonNull
+    @Override
+    protected Property getDeviceIdentifierSampleProperty() {
+        return ColmiHeartRateSampleDao.Properties.DeviceId;
+    }
+
+    @Override
+    public ColmiHeartRateSample createSample() {
+        return new ColmiHeartRateSample();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/colmi/samples/ColmiHrvSummarySampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/colmi/samples/ColmiHrvSummarySampleProvider.java
new file mode 100644
index 0000000..99214f8
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/colmi/samples/ColmiHrvSummarySampleProvider.java
@@ -0,0 +1,56 @@
+/*  Copyright (C) 2024 Arjan Schrijver
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.colmi.samples;
+
+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.ColmiHrvSummarySample;
+import nodomain.freeyourgadget.gadgetbridge.entities.ColmiHrvSummarySampleDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class ColmiHrvSummarySampleProvider extends AbstractTimeSampleProvider<ColmiHrvSummarySample> {
+    public ColmiHrvSummarySampleProvider(final GBDevice device, final DaoSession session) {
+        super(device, session);
+    }
+
+    @NonNull
+    @Override
+    public AbstractDao<ColmiHrvSummarySample, ?> getSampleDao() {
+        return getSession().getColmiHrvSummarySampleDao();
+    }
+
+    @NonNull
+    @Override
+    protected Property getTimestampSampleProperty() {
+        return ColmiHrvSummarySampleDao.Properties.Timestamp;
+    }
+
+    @NonNull
+    @Override
+    protected Property getDeviceIdentifierSampleProperty() {
+        return ColmiHrvSummarySampleDao.Properties.DeviceId;
+    }
+
+    @Override
+    public ColmiHrvSummarySample createSample() {
+        return new ColmiHrvSummarySample();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/colmi/samples/ColmiHrvValueSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/colmi/samples/ColmiHrvValueSampleProvider.java
new file mode 100644
index 0000000..6bb4e1c
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/colmi/samples/ColmiHrvValueSampleProvider.java
@@ -0,0 +1,56 @@
+/*  Copyright (C) 2024 Arjan Schrijver
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.colmi.samples;
+
+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.ColmiHrvValueSample;
+import nodomain.freeyourgadget.gadgetbridge.entities.ColmiHrvValueSampleDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class ColmiHrvValueSampleProvider extends AbstractTimeSampleProvider<ColmiHrvValueSample> {
+    public ColmiHrvValueSampleProvider(final GBDevice device, final DaoSession session) {
+        super(device, session);
+    }
+
+    @NonNull
+    @Override
+    public AbstractDao<ColmiHrvValueSample, ?> getSampleDao() {
+        return getSession().getColmiHrvValueSampleDao();
+    }
+
+    @NonNull
+    @Override
+    protected Property getTimestampSampleProperty() {
+        return ColmiHrvValueSampleDao.Properties.Timestamp;
+    }
+
+    @NonNull
+    @Override
+    protected Property getDeviceIdentifierSampleProperty() {
+        return ColmiHrvValueSampleDao.Properties.DeviceId;
+    }
+
+    @Override
+    public ColmiHrvValueSample createSample() {
+        return new ColmiHrvValueSample();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/colmi/samples/ColmiSleepSessionSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/colmi/samples/ColmiSleepSessionSampleProvider.java
new file mode 100644
index 0000000..2d237cd
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/colmi/samples/ColmiSleepSessionSampleProvider.java
@@ -0,0 +1,56 @@
+/*  Copyright (C) 2024 Arjan Schrijver
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.colmi.samples;
+
+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.ColmiSleepSessionSample;
+import nodomain.freeyourgadget.gadgetbridge.entities.ColmiSleepSessionSampleDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class ColmiSleepSessionSampleProvider extends AbstractTimeSampleProvider<ColmiSleepSessionSample> {
+    public ColmiSleepSessionSampleProvider(final GBDevice device, final DaoSession session) {
+        super(device, session);
+    }
+
+    @NonNull
+    @Override
+    public AbstractDao<ColmiSleepSessionSample, ?> getSampleDao() {
+        return getSession().getColmiSleepSessionSampleDao();
+    }
+
+    @NonNull
+    @Override
+    protected Property getTimestampSampleProperty() {
+        return ColmiSleepSessionSampleDao.Properties.Timestamp;
+    }
+
+    @NonNull
+    @Override
+    protected Property getDeviceIdentifierSampleProperty() {
+        return ColmiSleepSessionSampleDao.Properties.DeviceId;
+    }
+
+    @Override
+    public ColmiSleepSessionSample createSample() {
+        return new ColmiSleepSessionSample();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/colmi/samples/ColmiSleepStageSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/colmi/samples/ColmiSleepStageSampleProvider.java
new file mode 100644
index 0000000..06db0e9
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/colmi/samples/ColmiSleepStageSampleProvider.java
@@ -0,0 +1,56 @@
+/*  Copyright (C) 2024 Arjan Schrijver
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.colmi.samples;
+
+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.ColmiSleepStageSample;
+import nodomain.freeyourgadget.gadgetbridge.entities.ColmiSleepStageSampleDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class ColmiSleepStageSampleProvider extends AbstractTimeSampleProvider<ColmiSleepStageSample> {
+    public ColmiSleepStageSampleProvider(final GBDevice device, final DaoSession session) {
+        super(device, session);
+    }
+
+    @NonNull
+    @Override
+    public AbstractDao<ColmiSleepStageSample, ?> getSampleDao() {
+        return getSession().getColmiSleepStageSampleDao();
+    }
+
+    @NonNull
+    @Override
+    protected Property getTimestampSampleProperty() {
+        return ColmiSleepStageSampleDao.Properties.Timestamp;
+    }
+
+    @NonNull
+    @Override
+    protected Property getDeviceIdentifierSampleProperty() {
+        return ColmiSleepStageSampleDao.Properties.DeviceId;
+    }
+
+    @Override
+    public ColmiSleepStageSample createSample() {
+        return new ColmiSleepStageSample();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/colmi/samples/ColmiSpo2SampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/colmi/samples/ColmiSpo2SampleProvider.java
new file mode 100644
index 0000000..10e9eb1
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/colmi/samples/ColmiSpo2SampleProvider.java
@@ -0,0 +1,56 @@
+/*  Copyright (C) 2024 Arjan Schrijver
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.colmi.samples;
+
+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.ColmiSpo2Sample;
+import nodomain.freeyourgadget.gadgetbridge.entities.ColmiSpo2SampleDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class ColmiSpo2SampleProvider extends AbstractTimeSampleProvider<ColmiSpo2Sample> {
+    public ColmiSpo2SampleProvider(final GBDevice device, final DaoSession session) {
+        super(device, session);
+    }
+
+    @NonNull
+    @Override
+    public AbstractDao<ColmiSpo2Sample, ?> getSampleDao() {
+        return getSession().getColmiSpo2SampleDao();
+    }
+
+    @NonNull
+    @Override
+    protected Property getTimestampSampleProperty() {
+        return ColmiSpo2SampleDao.Properties.Timestamp;
+    }
+
+    @NonNull
+    @Override
+    protected Property getDeviceIdentifierSampleProperty() {
+        return ColmiSpo2SampleDao.Properties.DeviceId;
+    }
+
+    @Override
+    public ColmiSpo2Sample createSample() {
+        return new ColmiSpo2Sample();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/colmi/samples/ColmiStressSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/colmi/samples/ColmiStressSampleProvider.java
new file mode 100644
index 0000000..4b6966f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/colmi/samples/ColmiStressSampleProvider.java
@@ -0,0 +1,56 @@
+/*  Copyright (C) 2024 Arjan Schrijver
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.colmi.samples;
+
+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.ColmiStressSample;
+import nodomain.freeyourgadget.gadgetbridge.entities.ColmiStressSampleDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class ColmiStressSampleProvider extends AbstractTimeSampleProvider<ColmiStressSample> {
+    public ColmiStressSampleProvider(final GBDevice device, final DaoSession session) {
+        super(device, session);
+    }
+
+    @NonNull
+    @Override
+    public AbstractDao<ColmiStressSample, ?> getSampleDao() {
+        return getSession().getColmiStressSampleDao();
+    }
+
+    @NonNull
+    @Override
+    protected Property getTimestampSampleProperty() {
+        return ColmiStressSampleDao.Properties.Timestamp;
+    }
+
+    @NonNull
+    @Override
+    protected Property getDeviceIdentifierSampleProperty() {
+        return ColmiStressSampleDao.Properties.DeviceId;
+    }
+
+    @Override
+    public ColmiStressSample createSample() {
+        return new ColmiStressSample();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/cycling_sensor/activity/CyclingLiveDataActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/cycling_sensor/activity/CyclingLiveDataActivity.java
new file mode 100644
index 0000000..98a4153
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/cycling_sensor/activity/CyclingLiveDataActivity.java
@@ -0,0 +1,170 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.cycling_sensor.activity;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBActivity;
+import nodomain.freeyourgadget.gadgetbridge.activities.SettingsActivity;
+import nodomain.freeyourgadget.gadgetbridge.entities.CyclingSample;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class CyclingLiveDataActivity extends AbstractGBActivity {
+    private TextView speedView, tripDistanceView, totalDistanceView;
+    private GBDevice selectedDevice;
+    private float tripStartDistance = 0, tripCurrentDistance = 0;
+    private float toUnitFactor = 1;
+    private int
+        speedStringResource = R.string.km_h,
+        tripStringResource = R.string.label_distance_trip,
+        totalStringResource = R.string.label_distance_total;
+
+    private static final String PREFS_KEY_TRIP_START = "CYCLING_TRIP_START";
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        if(getIntent() == null) {
+            selectedDevice = getStoredCyclingSensor();
+        }else if(getIntent().getExtras() == null) {
+            selectedDevice = getStoredCyclingSensor();
+        }else if((selectedDevice =
+                getIntent().getExtras().getParcelable("device")) == null) {
+            selectedDevice = getStoredCyclingSensor();
+        }
+
+        if (selectedDevice == null) {
+            GB.toast(getString(R.string.error_no_cycling_sensor_found), Toast.LENGTH_SHORT, GB.ERROR);
+            finish();
+            return;
+        }
+
+        setContentView(R.layout.activity_cycling_live_data);
+
+        speedView = findViewById(R.id.cycling_data_speed);
+        tripDistanceView = findViewById(R.id.cycling_data_trip_distance);
+        totalDistanceView = findViewById(R.id.cycling_data_total_distance);
+
+        tripStartDistance = GBApplication
+                .getDevicePrefs(selectedDevice)
+                .getFloat(PREFS_KEY_TRIP_START, 0);
+
+        tripDistanceView.setOnLongClickListener(new View.OnLongClickListener() {
+            @Override
+            public boolean onLongClick(View view) {
+                if(tripCurrentDistance == 0) {
+                    return true;
+                }
+
+                tripStartDistance = tripCurrentDistance;
+
+                GBApplication
+                        .getDeviceSpecificSharedPrefs(selectedDevice.getAddress())
+                        .edit()
+                        .putFloat(PREFS_KEY_TRIP_START, tripStartDistance)
+                        .apply();
+
+                tripDistanceView.setText(getString(tripStringResource, 0f));
+
+                return true;
+            }
+        });
+
+        String measurementSystem = GBApplication.getPrefs().getString(SettingsActivity.PREF_MEASUREMENT_SYSTEM, "metric");
+
+        if(!measurementSystem.equals("metric")) {
+            toUnitFactor = 0.621371f;
+
+            speedStringResource = R.string.mi_h;
+            tripStringResource = R.string.label_distance_trip_mph;
+            totalStringResource = R.string.label_distance_total_mph;
+        }
+    }
+
+    private GBDevice getStoredCyclingSensor(){
+        List<GBDevice> devices = GBApplication
+                .app()
+                .getDeviceManager()
+                .getDevices();
+
+        for(GBDevice device: devices) {
+            if (device.getState() != GBDevice.State.INITIALIZED) continue;
+            if (device.getType() != DeviceType.CYCLING_SENSOR) continue;
+
+            return device;
+        }
+
+        return null;
+    }
+
+    private BroadcastReceiver cyclingDataReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String deviceAddress = intent.getStringExtra("EXTRA_DEVICE_ADDRESS");
+
+            if(deviceAddress == null) {
+                return;
+            }
+
+            if(!deviceAddress.equals(selectedDevice.getAddress())) {
+                return;
+            }
+
+            CyclingSample sample = (CyclingSample) intent.getSerializableExtra(DeviceService.EXTRA_REALTIME_SAMPLE);
+
+            if(sample == null) {
+                return;
+            }
+
+            Float metersPerSecond = sample.getSpeed();
+            if(metersPerSecond != null) {
+                float kmh = metersPerSecond * 3.6f * toUnitFactor;
+                speedView.setText(String.format("%.1f %s", kmh, getString(speedStringResource)));
+            }else{
+                speedView.setText(String.format("%.1f %s", 0f, getString(speedStringResource)));
+            }
+
+            tripCurrentDistance = sample.getDistance();
+
+            tripDistanceView.setText(getString(tripStringResource, ((tripCurrentDistance - tripStartDistance) * toUnitFactor) / 1000));
+            totalDistanceView.setText(getString(totalStringResource, (tripCurrentDistance * toUnitFactor) / 1000));
+        }
+    };
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+
+        LocalBroadcastManager
+                .getInstance(this)
+                .registerReceiver(
+                        cyclingDataReceiver,
+                        new IntentFilter(DeviceService.ACTION_REALTIME_SAMPLES)
+                );
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+
+        LocalBroadcastManager
+                .getInstance(this)
+                .unregisterReceiver(cyclingDataReceiver);
+    }
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/cycling_sensor/coordinator/CyclingSensorCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/cycling_sensor/coordinator/CyclingSensorCoordinator.java
new file mode 100644
index 0000000..ed74e0b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/cycling_sensor/coordinator/CyclingSensorCoordinator.java
@@ -0,0 +1,121 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.cycling_sensor.coordinator;
+
+import android.app.Activity;
+import android.content.Context;
+import android.net.Uri;
+
+import androidx.annotation.NonNull;
+
+import nodomain.freeyourgadget.gadgetbridge.GBException;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLEDeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.TimeSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.cycling_sensor.activity.CyclingLiveDataActivity;
+import nodomain.freeyourgadget.gadgetbridge.devices.cycling_sensor.db.CyclingSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.CyclingSample;
+import nodomain.freeyourgadget.gadgetbridge.entities.CyclingSampleDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.cycling_sensor.support.CyclingSensorSupport;
+
+public class CyclingSensorCoordinator extends AbstractBLEDeviceCoordinator {
+    @Override
+    protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
+        final Long deviceId = device.getId();
+
+        session.getCyclingSampleDao().queryBuilder()
+                .where(CyclingSampleDao.Properties.DeviceId.eq(deviceId))
+                .buildDelete().executeDeleteWithoutDetachingEntities();
+    }
+
+    @Override
+    public boolean supports(GBDeviceCandidate candidate) {
+        return candidate.supportsService(CyclingSensorSupport.UUID_CYCLING_SENSOR_SERVICE);
+    }
+
+    @Override
+    public boolean supportsCyclingData() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsActivityTracking() {
+        return true;
+    }
+
+    @Override
+    public TimeSampleProvider<CyclingSample> getCyclingSampleProvider(GBDevice device, DaoSession session) {
+        return new CyclingSampleProvider(device, session);
+    }
+
+    @Override
+    public boolean supportsSleepMeasurement() {
+        return false;
+    }
+    @Override
+    public boolean supportsStepCounter() {
+        return false;
+    }
+    @Override
+    public boolean supportsSpeedzones() {
+        return false;
+    }
+    @Override
+    public boolean supportsActivityTabs() {
+        return false;
+    }
+
+    @Override
+    public InstallHandler findInstallHandler(Uri uri, Context context) {
+        return null;
+    }
+
+    @Override
+    public String getManufacturer() {
+        return "Generic";
+    }
+
+    @Override
+    public Class<? extends Activity> getAppsManagementActivity() {
+        return CyclingLiveDataActivity.class;
+    }
+
+    @Override
+    public boolean supportsRealtimeData() {
+        return false;
+    }
+
+    @Override
+    public int getBondingStyle() {
+        return BONDING_STYLE_NONE;
+    }
+
+    @Override
+    public int[] getSupportedDeviceSpecificSettings(GBDevice device) {
+        return new int[]{
+                R.xml.devicesettings_cycling_sensor
+        };
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return CyclingSensorSupport.class;
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_cycling_sensor;
+    }
+
+    @Override
+    public boolean supportsAppsManagement(GBDevice device) {
+        return true;
+    }
+
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/cycling_sensor/db/CyclingSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/cycling_sensor/db/CyclingSampleProvider.java
new file mode 100644
index 0000000..6e521b6
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/cycling_sensor/db/CyclingSampleProvider.java
@@ -0,0 +1,41 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.cycling_sensor.db;
+
+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.devices.AbstractTimeSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.CyclingSample;
+import nodomain.freeyourgadget.gadgetbridge.entities.CyclingSampleDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class CyclingSampleProvider extends AbstractTimeSampleProvider<CyclingSample> {
+    public CyclingSampleProvider(GBDevice device, DaoSession session) {
+        super(device, session);
+    }
+
+    @Override
+    public AbstractDao<CyclingSample, ?> getSampleDao() {
+        return getSession().getCyclingSampleDao();
+    }
+
+    @NonNull
+    @Override
+    protected Property getTimestampSampleProperty() {
+        return CyclingSampleDao.Properties.Timestamp;
+    }
+
+    @NonNull
+    @Override
+    protected Property getDeviceIdentifierSampleProperty() {
+        return CyclingSampleDao.Properties.DeviceId;
+    }
+
+    @Override
+    public CyclingSample createSample() {
+        return new CyclingSample();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/divoom/PixooCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/divoom/PixooCoordinator.java
new file mode 100644
index 0000000..9407254
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/divoom/PixooCoordinator.java
@@ -0,0 +1,109 @@
+/*  Copyright (C) 2023-2024 Andreas Shimokawa, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.divoom;
+
+import android.content.Context;
+import android.net.Uri;
+
+import androidx.annotation.NonNull;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.GBException;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLEDeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.divoom.PixooSupport;
+
+public class PixooCoordinator extends AbstractBLEDeviceCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("Pixoo(-.+)?");
+    }
+
+    @Override
+    protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
+
+    }
+
+    @Override
+    public String getManufacturer() {
+        return "Divoom";
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return PixooSupport.class;
+    }
+
+    @Override
+    public InstallHandler findInstallHandler(final Uri uri, final Context context) {
+        PixooInstallHandler installHandler = new PixooInstallHandler(uri, context);
+        return installHandler.isValid() ? installHandler : null;
+    }
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_pixoo;
+    }
+
+    @Override
+    public boolean supportsFlashing() {
+        // To install bitmaps
+        return true;
+    }
+
+    @Override
+    public boolean supportsWeather() {
+        return true;
+    }
+
+    @Override
+    public int getAlarmSlotCount(GBDevice device) {
+        return 10;
+    }
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_lovetoy;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_lovetoy_disabled;
+    }
+
+    @Override
+    public int[] getSupportedDeviceSpecificSettings(GBDevice device) {
+        return new int[]{
+                R.xml.devicesettings_header_display,
+                R.xml.devicesettings_screen_brightness,
+                R.xml.devicesettings_header_time,
+                R.xml.devicesettings_timeformat,
+                R.xml.devicesettings_header_notifications,
+                R.xml.devicesettings_send_app_notifications,
+                R.xml.devicesettings_header_other,
+                R.xml.devicesettings_pixoo,
+                R.xml.devicesettings_header_connection,
+                R.xml.devicesettings_device_name,
+        };
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/divoom/PixooInstallHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/divoom/PixooInstallHandler.java
new file mode 100644
index 0000000..beee43e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/divoom/PixooInstallHandler.java
@@ -0,0 +1,50 @@
+/*  Copyright (C) 2023-2024 Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+
+package nodomain.freeyourgadget.gadgetbridge.devices.divoom;
+
+import android.content.Context;
+import android.net.Uri;
+
+import nodomain.freeyourgadget.gadgetbridge.activities.InstallActivity;
+import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class PixooInstallHandler implements InstallHandler {
+    private final Context mContext;
+    private final Uri mUri;
+
+    public PixooInstallHandler(Uri uri, Context context) {
+        mContext = context;
+        mUri = uri;
+    }
+
+    @Override
+    public boolean isValid() {
+        return true;
+    }
+
+    @Override
+    public void validateInstallation(InstallActivity installActivity, GBDevice device) {
+        installActivity.setInstallEnabled(true);
+    }
+
+    @Override
+    public void onStartInstall(GBDevice device) {
+
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/domyos/DomyosT540Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/domyos/DomyosT540Coordinator.java
new file mode 100644
index 0000000..8fd1c53
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/domyos/DomyosT540Coordinator.java
@@ -0,0 +1,155 @@
+/*  Copyright (C) 2023-2024 Damien Gaignon, Daniel Dakhno, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.domyos;
+
+import android.app.Activity;
+import android.content.Context;
+import android.net.Uri;
+
+import androidx.annotation.NonNull;
+
+import java.util.EnumSet;
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLEDeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.ServiceDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.domyos.DomyosT540Support;
+
+public class DomyosT540Coordinator extends AbstractBLEDeviceCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("Domyos-TC-9610.*");
+    }
+
+    @Override
+    public Class<? extends Activity> getPairingActivity() {
+        return null;
+    }
+
+    @Override
+    public InstallHandler findInstallHandler(Uri uri, Context context) {
+        return null;
+    }
+
+    @Override
+    public boolean supportsActivityDataFetching() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsActivityTracking() {
+        return false;
+    }
+
+    @Override
+    public SampleProvider<? extends ActivitySample> getSampleProvider(GBDevice device, DaoSession session) {
+        return null;
+    }
+
+    @Override
+    public boolean supportsScreenshots(final GBDevice device) {
+        return false;
+    }
+
+    @Override
+    public int getAlarmSlotCount(GBDevice device) {
+        return 0;
+    }
+
+    @Override
+    public boolean supportsHeartRateMeasurement(GBDevice device) {
+        return false;
+    }
+
+    @Override
+    public String getManufacturer() {
+        return "Domyos";
+    }
+
+    @Override
+    public boolean supportsAppsManagement(final GBDevice device) {
+        return false;
+    }
+
+    @Override
+    public Class<? extends Activity> getAppsManagementActivity() {
+        return null;
+    }
+
+    @Override
+    public boolean supportsCalendarEvents() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsRealtimeData() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsWeather() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsFindDevice() {
+        return true;
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return DomyosT540Support.class;
+    }
+
+    @Override
+    public EnumSet<ServiceDeviceSupport.Flags> getInitialFlags() {
+        return EnumSet.of(ServiceDeviceSupport.Flags.THROTTLING, ServiceDeviceSupport.Flags.BUSY_CHECKING);
+    }
+
+    @Override
+    protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) {
+        // nothing to delete, yet
+    }
+
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_domyos_t540;
+    }
+
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_lovetoy;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_lovetoy_disabled;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/femometer/FemometerVinca2DeviceCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/femometer/FemometerVinca2DeviceCoordinator.java
new file mode 100644
index 0000000..3a51cc9
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/femometer/FemometerVinca2DeviceCoordinator.java
@@ -0,0 +1,140 @@
+/*  Copyright (C) 2023-2024 Alicia Hormann
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.femometer;
+
+import android.app.Activity;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.regex.Pattern;
+
+import de.greenrobot.dao.query.QueryBuilder;
+import nodomain.freeyourgadget.gadgetbridge.GBException;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.AbstractDeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.TimeSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.entities.FemometerVinca2TemperatureSample;
+import nodomain.freeyourgadget.gadgetbridge.entities.FemometerVinca2TemperatureSampleDao;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.femometer.FemometerVinca2DeviceSupport;
+
+public class FemometerVinca2DeviceCoordinator extends AbstractDeviceCoordinator {
+    @Override
+    public String getManufacturer() {
+        // Actual manufacturer is Joytech Healthcare
+        return "Femometer";
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return FemometerVinca2DeviceSupport.class;
+    }
+
+    @Override
+    public TimeSampleProvider<FemometerVinca2TemperatureSample> getTemperatureSampleProvider(GBDevice device, DaoSession session) {
+        return new FemometerVinca2SampleProvider(device, session);
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("BM-Vinca2");
+    }
+
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_femometer_vinca2;
+    }
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_thermometer;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_thermometer_disabled;
+    }
+
+
+    @Override
+    protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
+        Long deviceId = device.getId();
+        QueryBuilder<?> qb = session.getFemometerVinca2TemperatureSampleDao().queryBuilder();
+        qb.where(FemometerVinca2TemperatureSampleDao.Properties.DeviceId.eq(deviceId)).buildDelete().executeDeleteWithoutDetachingEntities();
+    }
+
+
+    @Override
+    public int getBondingStyle(){
+        return BONDING_STYLE_NONE;
+    }
+
+    @Nullable
+    @Override
+    public Class<? extends Activity> getPairingActivity() {
+        return null;
+    }
+
+    @Override
+    public int getAlarmSlotCount(final GBDevice device) {
+        return 1;
+    }
+
+    @Override
+    public boolean supportsTemperatureMeasurement() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsActivityTracking() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsSleepMeasurement() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsStepCounter() {
+        return false;
+    }
+    @Override
+    public boolean supportsSpeedzones() {
+        return false;
+    }
+    @Override
+    public boolean supportsActivityTabs() {
+        return false;
+    }
+
+    @Override
+    public int[] getSupportedDeviceSpecificSettings(GBDevice device) {
+        return new int[]{
+                R.xml.devicesettings_volume,
+                R.xml.devicesettings_femometer,
+                R.xml.devicesettings_temperature_scale_cf,
+        };
+    }
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/femometer/FemometerVinca2SampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/femometer/FemometerVinca2SampleProvider.java
new file mode 100644
index 0000000..40a2ba8
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/femometer/FemometerVinca2SampleProvider.java
@@ -0,0 +1,55 @@
+/*  Copyright (C) 2023-2024 Alicia Hormann
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.femometer;
+
+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.FemometerVinca2TemperatureSample;
+import nodomain.freeyourgadget.gadgetbridge.entities.FemometerVinca2TemperatureSampleDao;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class FemometerVinca2SampleProvider extends AbstractTimeSampleProvider<FemometerVinca2TemperatureSample> {
+
+    public FemometerVinca2SampleProvider(GBDevice device, DaoSession session) {
+        super(device, session);
+    }
+
+    @Override
+    @NonNull
+    public AbstractDao<FemometerVinca2TemperatureSample, ?> getSampleDao() {
+        return getSession().getFemometerVinca2TemperatureSampleDao();
+    }
+
+    @NonNull
+    protected Property getTimestampSampleProperty() {
+        return FemometerVinca2TemperatureSampleDao.Properties.Timestamp;
+    }
+
+    @NonNull
+    protected Property getDeviceIdentifierSampleProperty() {
+        return FemometerVinca2TemperatureSampleDao.Properties.DeviceId;
+    }
+
+    @Override
+    public FemometerVinca2TemperatureSample createSample() {
+        return new FemometerVinca2TemperatureSample();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/fitpro/FitProConstants.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/fitpro/FitProConstants.java
new file mode 100644
index 0000000..5f07131
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/fitpro/FitProConstants.java
@@ -0,0 +1,250 @@
+/*  Copyright (C) 2021-2024 Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+
+package nodomain.freeyourgadget.gadgetbridge.devices.fitpro;
+
+import java.util.UUID;
+
+public class FitProConstants {
+
+    // cd 00 2a 05 01 0c 00 0c 0000012d000000bb000018
+    // |  |  |  |  |  |  |  |  |-- payload ->
+    // |  |  |  |  |  |  |  |----- payload length low
+    // |  |  |  |  |  |  |-------- payload length high
+    // |  |  |  |  |  |----------- command
+    // |  |  |  |  |-------------- delimiter/version
+    // |  |  |  |----------------- command group
+    // |  |  |-------------------- full length low
+    // |  |----------------------- full length high
+    // |-------------------------- header
+
+    public static final byte DATA_HEADER = (byte) 0xCD;
+    public static final byte DATA_HEADER_ACK = (byte) 0xDC;
+
+    public static final byte[] DATA_TEMPLATE = {
+            (byte) DATA_HEADER, // header
+            (byte) 0x00, // data len hi
+            (byte) 0,    // data len low
+            (byte) 0,    // command 1
+            (byte) 0x1,  // delimiter?
+            (byte) 0,    // command 2
+            (byte) 0x0,  // data len hi
+            (byte) 0,    // data len low
+            // data payload
+    };
+
+    public static final UUID UUID_CHARACTERISTIC_UART = UUID.fromString("6e400001-b5a3-f393-e0a9-e50e24dcca9d");
+    public static final UUID UUID_CHARACTERISTIC_TX = UUID.fromString("6e400002-b5a3-f393-e0a9-e50e24dcca9d");
+    public static final UUID UUID_CHARACTERISTIC_RX = UUID.fromString("6e400003-b5a3-f393-e0a9-e50e24dcca9d");
+    //public static final UUID LT716_OTA = UUID.fromString("00010203-0405-0607-0809-0a0b0c0d1912"); //when in OTA mode
+
+    public static final byte CMD_GROUP_GENERAL = (byte) 0x12;
+    public static final byte CMD_GROUP_BAND_INFO = (byte) 0x20;
+    public static final byte CMD_GROUP_RECEIVE_BUTTON_DATA = 0x1c;
+    public static final byte CMD_GROUP_RECEIVE_SPORTS_DATA = 0x15;
+    public static final byte CMD_GROUP_HEARTRATE_SETTINGS = 0x16;
+    public static final byte CMD_GROUP_REQUEST_DATA = 0x1a;
+    public static final byte CMD_GROUP_BIND = 0x14;
+    public static final byte CMD_GROUP_RESET = 0x1d;
+
+    //group general 0x12
+    public static final byte CMD_FIND_BAND = (byte) 0x0b;
+    public static final byte CMD_SET_DATE_TIME = (byte) 0x1;
+    public static final byte CMD_SET_LANGUAGE = (byte) 0x15;
+    public static final byte CMD_NOTIFICATION_MESSAGE = (byte) 0x12;
+    public static final byte CMD_NOTIFICATION_CALL = (byte) 0x11;
+    public static final byte CMD_WEATHER = (byte) 0x20;
+    public static final byte CMD_CAMERA = (byte) 0xc;
+    public static final byte CMD_HEART_RATE_MEASUREMENT = 0x18; //on/off
+    public static final byte CMD_DND = (byte) 0x14;
+    public static final byte CMD_INIT1 = 0xa;
+    public static final byte CMD_INIT2 = 0xc;
+    public static final byte CMD_INIT3 = (byte) 0xff;
+
+    public static final byte CMD_SET_SLEEP_TIMES = (byte) 0xF;
+    public static final byte CMD_ALARM = (byte) 0x2;
+    public static final byte CMD_SET_ARM = (byte) 0x6;
+    public static final byte CMD_GET_HR = (byte) 0xd; //0/1
+    public static final byte CMD_GET_PRESS = (byte) 0xe; //0/1
+
+    public static final byte CMD_NOTIFICATIONS_ENABLE = 0x7;
+    public static final byte[] VALUE_SET_NOTIFICATIONS_ENABLE_ON = new byte[]{0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1};
+    public static final byte[] VALUE_SET_NOTIFICATIONS_ENABLE_OFF = new byte[]{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0};
+
+    public static final byte CMD_SET_LONG_SIT_REMINDER = (byte) 0x5;
+    public static final byte[] VALUE_SET_LONG_SIT_REMINDER_ON = new byte[]{0x0, 0x1, 0x0, (byte) 0x96};
+    public static final byte[] VALUE_SET_LONG_SIT_REMINDER_OFF = new byte[]{0x0, 0x0, 0x0, (byte) 0x96, 0x4, 0x8, 0x16, 0x7f};
+
+    public static final byte CMD_SET_DISPLAY_ON_LIFT = (byte) 0x9;
+    public static final byte CMD_SET_STEP_GOAL = (byte) 0x3;
+    public static final byte CMD_SET_USER_DATA = (byte) 0x4;
+    public static final byte CMD_SET_DEVICE_VIBRATIONS = (byte) 0x8;
+
+    //group receive sports data 0x15
+    public static final byte CMD_REQUEST_DAY_STEPS_SUMMARY = 0x6;
+    //request steps/hr, as per
+    // https://github.com/vanous/MFitX/blob/AntiGoogle/app/src/main/java/anonymouls/dev/mgcex/app/backend/LM517CommandInterpreter.kt#L242
+    public static final byte CMD_REQUEST_FETCH_DAY_STEPS_DATA = 0xd;
+    public static final byte CMD_REQUEST_STEPS_DATA1 = 0x6;
+    public static final byte CMD_REQUEST_STEPS_DATA0x7 = 0x7;
+    public static final byte CMD_REQUEST_STEPS_DATA0x8 = 0x8;
+    public static final byte CMD_REQUEST_STEPS_DATA0x10 = 0x10;
+
+    //also sleep data as per mfitx
+    //https://github.com/vanous/MFitX/blob/AntiGoogle/app/src/main/java/anonymouls/dev/mgcex/app/backend/LM517CommandInterpreter.kt#L235
+
+    public static final byte RX_HEART_RATE_DATA = 0x0e;
+    public static final byte RX_SPORTS_MEASUREMENT = 0x18; //0/1
+    public static final byte SPORTS_RECEIVE_KEY = 0x1; //0/1
+
+    public static final byte RX_SLEEP_DATA = 0x3;
+    public static final byte RX_STEP_DATA = 0x2;
+    // https://github.com/vanous/MFitX/blob/AntiGoogle/app/src/main/java/anonymouls/dev/mgcex/app/backend/LM517CommandInterpreter.kt#L162
+
+    public static final byte RX_SPORTS_DAY_DATA = 0xc; //0/1
+
+    //group get band info 0x20
+    public static final byte CMD_RX_BAND_INFO = (byte) 0x2;
+
+    //group request data 0x1a
+    public static final byte CMD_GET_STEPS_TARGET = 0x2;
+    public static final byte CMD_GET_HW_INFO = 0x10;
+    public static final byte CMD_GET_AUTO_HR = 0x8;
+    public static final byte CMD_GET_CONTACTS = 0xd;
+
+    //group 0x14
+    public static final byte CMD_UNBIND = (byte) 0x0;
+
+    //group 0x1d
+    public static final byte CMD_RESET = (byte) 0x1;
+
+    // group receive data
+    public static final byte RX_FIND_PHONE = (byte) 0x01;
+    public static final byte RX_CAMERA1 = (byte) 0x02;
+    public static final byte RX_CAMERA2 = (byte) 0x03;
+    public static final byte RX_CAMERA3 = (byte) 0x04;
+    public static final byte RX_MEDIA_PLAY_PAUSE = (byte) 0x0b;
+    public static final byte RX_MEDIA_FORW = (byte) 0x0c;
+    public static final byte RX_MEDIA_BACK = (byte) 0x0a;
+
+
+    //values
+    public static final byte VALUE_ON = (byte) 0x1;
+    public static final byte VALUE_OFF = (byte) 0x0;
+
+    public static final byte UNIT_METRIC = (byte) 0x1;
+    public static final byte UNIT_IMPERIAL = (byte) 0x2;
+
+    public static final byte GENDER_MALE = (byte) 0x1;
+    public static final byte GENDER_FEMALE = (byte) 0x0;
+
+    public static final byte VALUE_SET_ARM_LEFT = (byte) 0x0; //guessing
+    public static final byte VALUE_SET_ARM_RIGHT = (byte) 0x1;
+
+
+    public static final byte[] VALUE_SET_DEVICE_VIBRATIONS_ENABLE = new byte[]{0x1, 0x1, 0x1, 0x1};
+    public static final byte[] VALUE_SET_DEVICE_VIBRATIONS_DISABLE = new byte[]{0, 0, 0, 0};
+
+    public static final byte NOTIFICATION_ICON_FACEBOOK = (byte) 0x4;
+    public static final byte NOTIFICATION_ICON_TWITTER = (byte) 0x5;
+    public static final byte NOTIFICATION_ICON_WHATSAPP = (byte) 0x8;
+    public static final byte NOTIFICATION_ICON_LINE = (byte) 0x7;
+    public static final byte NOTIFICATION_ICON_SMS = (byte) 0x1;
+    public static final byte NOTIFICATION_ICON_WECHAT = (byte) 0x3;
+    public static final byte NOTIFICATION_ICON_QQ = (byte) 0x2;
+    public static final byte NOTIFICATION_ICON_INSTAGRAM = (byte) 0x10;
+
+    public static final int LANG_CHINESE = 0x0;
+    public static final int LANG_PORTUGUESE = 0xc;
+    public static final int LANG_GERMAN = 0x5;
+    public static final int LANG_SPANISH = 0x6;
+    public static final int LANG_FRENCH = 0x7;
+    public static final int LANG_NETHERLANDS = 0xa;
+    public static final int LANG_POLISH = 0xb;
+    public static final int LANG_RUSSIAN = 0xd;
+    public static final int LANG_TURKISH = 0x10;
+    public static final int LANG_ENGLISH = 0x1;
+    public static final int LANG_CZECH = 0x4;
+    public static final int LANG_ITALIAN = 0x12;
+
+    //00       01         00      96            03            08      16    7f
+    //^^ zeros ^^ on/off  ^^ sep  ^^unknown     ^^minutes     ^^from  ^^ to ^^ unknown
+    //minutes are array of minutes by 15, in 45,60, 75...鈫� 3,4,5...
+    //the byte 4 鈫� 96 could be experimented with to set different values..., byte 3 is probably Hi, byte 4 Low
+    //public static final byte[] CMD_SET_LONG_SIT_REMINDER = new byte[]{(byte) 0x12, (byte) 0x5};
+    //maybe could be usign the ON/OFF?
+    //public static final byte[] VALUE_SET_LONG_SIT_REMINDER_ON = new byte[]{0x0, 0x1, 0x0, (byte) 0x96};
+    //public static final byte[] VALUE_SET_LONG_SIT_REMINDER_OFF = new byte[]{0x0, 0x0, 0x0, (byte) 0x96, 0x4, 0x8, 0x16, 0x7f};
+
+    //Value: cd 00 0a 12 01 09 00 05 01 01 e0 05 28 ON
+    //Value: cd 00 0a 12 01 09 00 05 00 01 e0 05 28 OFF
+    //       00 1  2  3  4  5  6  7  8  9  10 11 12
+    //byte 8 on/off
+    // 9,10,11,12 鈫� time from/to
+    //public static final byte[] CMD_SET_DISPLAY_ON_LIFT = new byte[]{(byte) 0x12, (byte) 0x9};
+    //maybe could be usign the ON/OFF?
+    //public static final byte VALUE_SET_DISPLAY_ON_LIFT_ON = 0x1;
+    //public static final byte[] VALUE_SET_DISPLAY_ON_LIFT_OFF = new byte[]{0x0, 0x0, 0x0, 0x0, 0x0};
+
+    //time
+    //0xCD 0x00 0x09 0x12 0x01 0x01 0x00 0x04 0xA5 0x83 0x73 0xDB
+    //find watch
+    //new byte[]{(byte) 0xcd, (byte) 0x00, (byte) 0x06,
+    // (byte) 0x12, (byte) 0x01, (byte) 0x0b, (byte) 0x00, (byte) 0x01, (byte) 0x01};
+
+    /*init procedure:
+    get pair cd 00 06 12 01 0a 00 01 02 鈫� 18, 10
+    cd 00 09 12 01 01 00 04 55 f8 36 90
+    cd 00 05 1a 01 0a 00 00
+    cd 00 05 1a 01 0c 00 00
+    cd 00 06 12 01 15 00 01 01
+    cd 00 06 12 01 ff 00 01 01
+    cd 00 05 1a 01 01 00 00
+    dc 00 05 1a 01 00 0c 01
+    cd 00 05 1a 01 0f 00 00
+    cd 00 05 1a 01 10 00 00
+    dc 00 05 1a 01 00 1c 01
+    something cd 00 05 20 01 02 00 00 鈫� 32, 2
+    real time step cd 00 06 15 01 06 00 01 01 鈫� 21, 6
+     */
+
+    //received heartrate
+    //0xcd 0x00 0x11 0x15 0x01 0x0e 0x00 0x0c 0x2a 0xf8 0x00 0x01 0x00 0x00 0x31 0x2c 0x62 0x75 0x54 0x47
+    //0xcd 0x00 0x11 0x15 0x01 0x0e 0x00 0x0c 0x2b 0x04 0x00 0x01 0x00 0x01 0x43 0x94 0x62 0x5c 0x48 0x53
+    //cd001115010e000c2b04000100014394625c4853
+
+    //find band
+    // cd 00 06 12 01 0b 00 01 01
+
+    //set celsius/fahrenheit?
+    //cd0006120121000100
+    //cd0006120121000101
+
+    //find phone
+    //0xcd 0x00 0x05 0x1c 0x01 0x01 0x00 0x00
+    //camera:
+    //0xcd 0x00 0x05 0x1c 0x01 0x03 0x00 0x00
+    //0xcd 0x00 0x05 0x1c 0x01 0x02 0x00 0x00
+    //0xcd 0x00 0x05 0x1c 0x01 0x04 0x00 0x00
+
+    // <<   0xCD 0x00 0x05 0x1C 0x01 0x0A 0x00 0x00
+    //pause 0xCD 0x00 0x05 0x1C 0x01 0x0B 0x00 0x00
+    // >>   0xCD 0x00 0x05 0x1C 0x01 0x0C 0x00 0x00
+
+    //0xCD 0x00 0x05 0x1C 0x01 0x03 0x00 0x00
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/fitpro/FitProDeviceCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/fitpro/FitProDeviceCoordinator.java
new file mode 100644
index 0000000..2598faf
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/fitpro/FitProDeviceCoordinator.java
@@ -0,0 +1,180 @@
+/*  Copyright (C) 2021-2024 a b, Damien Gaignon, Daniel Dakhno, Jos茅 Rebelo,
+    Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+
+package nodomain.freeyourgadget.gadgetbridge.devices.fitpro;
+
+import android.app.Activity;
+import android.content.Context;
+import android.net.Uri;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.EnumSet;
+import java.util.regex.Pattern;
+
+import de.greenrobot.dao.query.QueryBuilder;
+import nodomain.freeyourgadget.gadgetbridge.GBException;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLEDeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.entities.FitProActivitySampleDao;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.ServiceDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.fitpro.FitProDeviceSupport;
+
+public class FitProDeviceCoordinator extends AbstractBLEDeviceCoordinator {
+    private static final Logger LOG = LoggerFactory.getLogger(FitProDeviceCoordinator.class);
+
+
+    @Override
+    protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
+        Long deviceId = device.getId();
+        QueryBuilder<?> qb = session.getFitProActivitySampleDao().queryBuilder();
+        qb.where(FitProActivitySampleDao.Properties.DeviceId.eq(deviceId)).buildDelete().executeDeleteWithoutDetachingEntities();
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("M6.*|M4.*|LH716|Sunset 6|Watch7|Fit1900");
+    }
+
+    @Override
+    public int getBondingStyle() {
+        // different devices seem to work differently.
+        // user will unfortunately need to decide
+        return BONDING_STYLE_ASK;
+    }
+
+    @Nullable
+    @Override
+    public Class<? extends Activity> getPairingActivity() {
+        return null;
+    }
+
+    @Override
+    public boolean supportsActivityDataFetching() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsActivityTracking() {
+        return true;
+    }
+
+    @Override
+    public SampleProvider<? extends ActivitySample> getSampleProvider(GBDevice device, DaoSession session) {
+        return new FitProSampleProvider(device, session);
+    }
+
+    @Override
+    public InstallHandler findInstallHandler(Uri uri, Context context) {
+        return null;
+    }
+
+    @Override
+    public boolean supportsScreenshots(final GBDevice device) {
+        return false;
+    }
+
+    @Override
+    public int getAlarmSlotCount(GBDevice device) {
+        return 8;
+    }
+
+    @Override
+    public boolean supportsHeartRateMeasurement(GBDevice device) {
+        return true;
+    }
+
+    @Override
+    public String getManufacturer() {
+        return "FitPro";
+    }
+
+    @Override
+    public boolean supportsAppsManagement(final GBDevice device) {
+        return false;
+    }
+
+    @Override
+    public Class<? extends Activity> getAppsManagementActivity() {
+        return null;
+
+    }
+
+    @Override
+    public boolean supportsCalendarEvents() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsRealtimeData() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsWeather() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsFindDevice() {
+        return true;
+    }
+
+    @Override
+    public int[] getSupportedDeviceSpecificSettings(GBDevice device) {
+        return new int[]{
+                R.xml.devicesettings_liftwrist_display_no_on,
+                R.xml.devicesettings_inactivity_extended,
+                R.xml.devicesettings_donotdisturb_no_auto,
+                R.xml.devicesettings_sleep_time,
+                R.xml.devicesettings_wearlocation,
+                //R.xml.devicesettings_autoheartrate, //removed, this is most likely not supported by these bands
+                R.xml.devicesettings_vibrations_enable,
+                R.xml.devicesettings_notifications_enable,
+                R.xml.devicesettings_fitpro,
+                R.xml.devicesettings_transliteration
+        };
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return FitProDeviceSupport.class;
+    }
+
+    @Override
+    public EnumSet<ServiceDeviceSupport.Flags> getInitialFlags() {
+        return EnumSet.of(ServiceDeviceSupport.Flags.THROTTLING, ServiceDeviceSupport.Flags.BUSY_CHECKING);
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_fitpro;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/fitpro/FitProSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/fitpro/FitProSampleProvider.java
new file mode 100644
index 0000000..37303a9
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/fitpro/FitProSampleProvider.java
@@ -0,0 +1,99 @@
+/*  Copyright (C) 2021-2024 Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+
+package nodomain.freeyourgadget.gadgetbridge.devices.fitpro;
+
+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.FitProActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.entities.FitProActivitySampleDao;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+
+public class FitProSampleProvider extends AbstractSampleProvider<FitProActivitySample> {
+    public FitProSampleProvider(GBDevice device, DaoSession session) {
+        super(device, session);
+    }
+
+    @Override
+    public AbstractDao<FitProActivitySample, ?> getSampleDao() {
+        return getSession().getFitProActivitySampleDao();
+    }
+
+
+    // as per FitProDeviceSupport.rawActivityKindToUniqueKind
+
+    @Override
+    public ActivityKind normalizeType(int rawType) {
+        switch (rawType) {
+            case 1:
+                return ActivityKind.ACTIVITY;
+            case 11:
+                return ActivityKind.DEEP_SLEEP;
+            case 12:
+                return ActivityKind.LIGHT_SLEEP;
+            default:
+                return ActivityKind.UNKNOWN;
+        }
+    }
+
+    @Override
+    public int toRawActivityKind(ActivityKind activityKind) {
+        switch (activityKind) {
+            case DEEP_SLEEP:
+                return 11;
+            case LIGHT_SLEEP:
+                return 12;
+            case ACTIVITY:
+            default:
+                return 1;
+        }
+    }
+
+    @Override
+    public float normalizeIntensity(int rawIntensity) {
+        return rawIntensity / 2000f; //samples are per 5 minutes, so this should be sufficient
+    }
+
+    @Override
+    public FitProActivitySample createActivitySample() {
+        return new FitProActivitySample();
+    }
+
+    @Nullable
+    @Override
+    protected Property getRawKindSampleProperty() {
+        return FitProActivitySampleDao.Properties.RawKind;
+    }
+
+    @NonNull
+    @Override
+    protected Property getTimestampSampleProperty() {
+        return FitProActivitySampleDao.Properties.Timestamp;
+    }
+
+    @NonNull
+    @Override
+    protected Property getDeviceIdentifierSampleProperty() {
+        return FitProActivitySampleDao.Properties.DeviceId;
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/fitpro/colacao/ColaCao21Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/fitpro/colacao/ColaCao21Coordinator.java
new file mode 100644
index 0000000..e630ca4
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/fitpro/colacao/ColaCao21Coordinator.java
@@ -0,0 +1,56 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+
+package nodomain.freeyourgadget.gadgetbridge.devices.fitpro.colacao;
+
+import androidx.annotation.DrawableRes;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProDeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class ColaCao21Coordinator extends FitProDeviceCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("ColaCao21");
+    }
+
+    @Override
+    public boolean supportsHeartRateMeasurement(final GBDevice device) {
+        // HR requests crash the device
+        return false;
+    }
+
+    @Override
+    @DrawableRes
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_amazfit_bip;
+    }
+
+    @Override
+    @DrawableRes
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_amazfit_bip_disabled;
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_colacao21;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/fitpro/colacao/ColaCao23Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/fitpro/colacao/ColaCao23Coordinator.java
new file mode 100644
index 0000000..ec502a6
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/fitpro/colacao/ColaCao23Coordinator.java
@@ -0,0 +1,56 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+
+package nodomain.freeyourgadget.gadgetbridge.devices.fitpro.colacao;
+
+import androidx.annotation.DrawableRes;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProDeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class ColaCao23Coordinator extends FitProDeviceCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("ColaCao23");
+    }
+
+    @Override
+    public boolean supportsHeartRateMeasurement(final GBDevice device) {
+        // HR requests crash the device
+        return false;
+    }
+
+    @Override
+    @DrawableRes
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_amazfit_bip;
+    }
+
+    @Override
+    @DrawableRes
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_amazfit_bip_disabled;
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_colacao23;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/flipper/zero/FlipperZeroCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/flipper/zero/FlipperZeroCoordinator.java
new file mode 100644
index 0000000..f8727ab
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/flipper/zero/FlipperZeroCoordinator.java
@@ -0,0 +1,150 @@
+/*  Copyright (C) 2022-2024 Damien Gaignon, Daniel Dakhno, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.flipper.zero;
+
+import android.app.Activity;
+import android.content.Context;
+import android.net.Uri;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.GBException;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLEDeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.flipper.zero.support.FlipperZeroSupport;
+
+public class FlipperZeroCoordinator extends AbstractBLEDeviceCoordinator {
+    @Override
+    protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
+
+    }
+
+    @NonNull
+    @Override
+    public boolean supports(GBDeviceCandidate candidate) {
+        if(candidate.supportsService(UUID.fromString("00003082-0000-1000-8000-00805f9b34fb"))){
+            return true; // need to filter for flipper here
+        }
+        return false;
+    }
+
+    @Nullable
+    @Override
+    public Class<? extends Activity> getPairingActivity() {
+        return null;
+    }
+
+    @Override
+    public boolean supportsActivityDataFetching() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsActivityTracking() {
+        return false;
+    }
+
+    @Override
+    public SampleProvider<? extends ActivitySample> getSampleProvider(GBDevice device, DaoSession session) {
+        return null;
+    }
+
+    @Override
+    public InstallHandler findInstallHandler(Uri uri, Context context) {
+        return null;
+    }
+
+    @Override
+    public boolean supportsScreenshots(final GBDevice device) {
+        return false;
+    }
+
+    @Override
+    public int getAlarmSlotCount(GBDevice device) {
+        return 0;
+    }
+
+    @Override
+    public String getManufacturer() {
+        return "Flipper Devices";
+    }
+
+    @Override
+    public boolean supportsAppsManagement(final GBDevice device) {
+        return false;
+    }
+
+    @Override
+    public Class<? extends Activity> getAppsManagementActivity() {
+        return null;
+    }
+
+    @Override
+    public boolean supportsCalendarEvents() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsRealtimeData() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsFindDevice() {
+        return false;
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return FlipperZeroSupport.class;
+    }
+
+    @Override
+    public int getBondingStyle() {
+        return BONDING_STYLE_NONE;
+    }
+
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_flipper_zero;
+    }
+
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_flipper;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_flipper_disabled;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/galaxy_buds/GalaxyBuds2DeviceCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/galaxy_buds/GalaxyBuds2DeviceCoordinator.java
new file mode 100644
index 0000000..c510b1c
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/galaxy_buds/GalaxyBuds2DeviceCoordinator.java
@@ -0,0 +1,76 @@
+/*  Copyright (C) 2022-2024 Daniel Dakhno, narektor
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.galaxy_buds;
+
+import androidx.annotation.NonNull;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.BatteryConfig;
+
+public class GalaxyBuds2DeviceCoordinator extends GalaxyBudsGenericCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        // Some devices are just called "Buds2", others "Galaxy Buds2 (..."
+        return Pattern.compile("(Galaxy )?Buds2( \\(.*)?");
+    }
+
+    @Override
+    public DeviceSpecificSettingsCustomizer getDeviceSpecificSettingsCustomizer(final GBDevice device) {
+        return new GalaxyBudsSettingsCustomizer(device);
+    }
+
+    @Override
+    public int getBatteryCount() {
+        return 3;
+    }
+
+    @Override
+    public BatteryConfig[] getBatteryConfig(final GBDevice device) {
+        BatteryConfig battery1 = new BatteryConfig(0, R.drawable.ic_buds_pro_case, R.string.battery_case);
+        BatteryConfig battery2 = new BatteryConfig(1, R.drawable.ic_buds_pro_left, R.string.left_earbud);
+        BatteryConfig battery3 = new BatteryConfig(2, R.drawable.ic_buds_pro_right, R.string.right_earbud);
+        return new BatteryConfig[]{battery1, battery2, battery3};
+    }
+
+    @Override
+    public int[] getSupportedDeviceSpecificSettings(GBDevice device) {
+        return new int[]{
+                R.xml.devicesettings_galaxy_buds_2,
+        };
+    }
+
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_galaxybuds_2;
+    }
+
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_galaxy_buds_pro;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_galaxy_buds_pro_disabled;
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/galaxy_buds/GalaxyBuds2ProDeviceCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/galaxy_buds/GalaxyBuds2ProDeviceCoordinator.java
new file mode 100644
index 0000000..cd7410b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/galaxy_buds/GalaxyBuds2ProDeviceCoordinator.java
@@ -0,0 +1,77 @@
+/*  Copyright (C) 2023-2024 Daniel Dakhno, narektor
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.galaxy_buds;
+
+import androidx.annotation.NonNull;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
+import nodomain.freeyourgadget.gadgetbridge.model.BatteryConfig;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+
+public class GalaxyBuds2ProDeviceCoordinator extends GalaxyBudsGenericCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("^(Galaxy )?Buds2 Pro.*");
+    }
+
+    @Override
+    public DeviceSpecificSettingsCustomizer getDeviceSpecificSettingsCustomizer(final GBDevice device) {
+        return new GalaxyBudsSettingsCustomizer(device);
+    }
+
+    @Override
+    public int getBatteryCount() {
+        return 3;
+    }
+
+    @Override
+    public BatteryConfig[] getBatteryConfig(final GBDevice device) {
+        BatteryConfig battery1 = new BatteryConfig(0, R.drawable.ic_buds_pro_case, R.string.battery_case);
+        BatteryConfig battery2 = new BatteryConfig(1, R.drawable.ic_buds_pro_left, R.string.left_earbud);
+        BatteryConfig battery3 = new BatteryConfig(2, R.drawable.ic_buds_pro_right, R.string.right_earbud);
+        return new BatteryConfig[]{battery1, battery2, battery3};
+    }
+
+    @Override
+    public int[] getSupportedDeviceSpecificSettings(GBDevice device) {
+        return new int[]{
+                R.xml.devicesettings_galaxy_buds_2_pro,
+        };
+    }
+
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_galaxybuds_2_pro;
+    }
+
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_galaxy_buds_pro;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_galaxy_buds_pro_disabled;
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/galaxy_buds/GalaxyBudsDeviceCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/galaxy_buds/GalaxyBudsDeviceCoordinator.java
new file mode 100644
index 0000000..a031bd2
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/galaxy_buds/GalaxyBudsDeviceCoordinator.java
@@ -0,0 +1,58 @@
+/*  Copyright (C) 2021-2024 Daniel Dakhno, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.galaxy_buds;
+
+import androidx.annotation.NonNull;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
+import nodomain.freeyourgadget.gadgetbridge.model.BatteryConfig;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+
+public class GalaxyBudsDeviceCoordinator extends GalaxyBudsGenericCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("Galaxy Buds \\(.*");
+    }
+
+    @Override
+    public int getBatteryCount() {
+        return 2;
+    }
+
+    @Override
+    public BatteryConfig[] getBatteryConfig(final GBDevice device) {
+        BatteryConfig battery1 = new BatteryConfig(0, R.drawable.ic_galaxy_buds_l, R.string.left_earbud);
+        BatteryConfig battery2 = new BatteryConfig(1, R.drawable.ic_galaxy_buds_r, R.string.right_earbud);
+        return new BatteryConfig[]{battery1, battery2};
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_galaxybuds;
+    }
+
+    @Override
+    public int[] getSupportedDeviceSpecificSettings(GBDevice device) {
+        return new int[]{
+                R.xml.devicesettings_galaxy_buds,
+        };
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/galaxy_buds/GalaxyBudsGenericCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/galaxy_buds/GalaxyBudsGenericCoordinator.java
new file mode 100644
index 0000000..f57d481
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/galaxy_buds/GalaxyBudsGenericCoordinator.java
@@ -0,0 +1,144 @@
+/*  Copyright (C) 2022-2024 Damien Gaignon, Daniel Dakhno, Jos茅 Rebelo,
+    Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.galaxy_buds;
+
+import android.app.Activity;
+import android.content.Context;
+import android.net.Uri;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import nodomain.freeyourgadget.gadgetbridge.GBException;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLClassicDeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.galaxy_buds.GalaxyBudsDeviceSupport;
+
+public abstract class GalaxyBudsGenericCoordinator extends AbstractBLClassicDeviceCoordinator {
+
+    @Override
+    public int getBondingStyle() {
+        return BONDING_STYLE_BOND;
+    }
+
+    @Nullable
+    @Override
+    public Class<? extends Activity> getPairingActivity() {
+        return null;
+    }
+
+    @Override
+    public boolean supportsActivityDataFetching() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsActivityTracking() {
+        return false;
+    }
+
+    @Override
+    public SampleProvider<? extends ActivitySample> getSampleProvider(GBDevice
+                                                                              device, DaoSession session) {
+        return null;
+    }
+
+    @Override
+    public InstallHandler findInstallHandler(Uri uri, Context context) {
+        return null;
+    }
+
+    @Override
+    public boolean supportsScreenshots(final GBDevice device) {
+        return false;
+    }
+
+    @Override
+    public int getAlarmSlotCount(GBDevice device) {
+        return 0;
+    }
+
+    @Override
+    public boolean supportsHeartRateMeasurement(GBDevice device) {
+        return false;
+    }
+
+    @Override
+    public String getManufacturer() {
+        return "Samsung";
+    }
+
+    @Override
+    public boolean supportsAppsManagement(final GBDevice device) {
+        return false;
+    }
+
+    @Override
+    public Class<? extends Activity> getAppsManagementActivity() {
+        return null;
+    }
+
+    @Override
+    public boolean supportsCalendarEvents() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsRealtimeData() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsWeather() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsFindDevice() {
+        return true;
+    }
+
+    @Override
+    protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device
+            device, @NonNull DaoSession session) throws GBException {
+
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return GalaxyBudsDeviceSupport.class;
+    }
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_galaxy_buds;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_galaxy_buds_disabled;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/galaxy_buds/GalaxyBudsLiveDeviceCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/galaxy_buds/GalaxyBudsLiveDeviceCoordinator.java
new file mode 100644
index 0000000..f5f1c11
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/galaxy_buds/GalaxyBudsLiveDeviceCoordinator.java
@@ -0,0 +1,72 @@
+/*  Copyright (C) 2021-2024 Daniel Dakhno, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.galaxy_buds;
+
+import java.util.List;
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettings;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsScreen;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.BatteryConfig;
+
+public class GalaxyBudsLiveDeviceCoordinator extends GalaxyBudsGenericCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("Galaxy Buds Live \\(.*");
+    }
+
+    @Override
+    public int getBatteryCount() {
+        return 3;
+    }
+
+    @Override
+    public BatteryConfig[] getBatteryConfig(final GBDevice device) {
+        BatteryConfig battery1 = new BatteryConfig(0, R.drawable.ic_tws_case, R.string.battery_case);
+        BatteryConfig battery2 = new BatteryConfig(1, R.drawable.ic_galaxy_buds_live_l, R.string.left_earbud);
+        BatteryConfig battery3 = new BatteryConfig(2, R.drawable.ic_galaxy_buds_live_r, R.string.right_earbud);
+        return new BatteryConfig[]{battery1, battery2, battery3};
+    }
+
+    @Override
+    public DeviceSpecificSettings getDeviceSpecificSettings(final GBDevice device) {
+        final DeviceSpecificSettings deviceSpecificSettings = new DeviceSpecificSettings();
+        final List<Integer> audio = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.AUDIO);
+        audio.add(R.xml.devicesettings_galaxy_buds_live);
+        final List<Integer> callsAndNotif = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.CALLS_AND_NOTIFICATIONS);
+        callsAndNotif.add(R.xml.devicesettings_headphones);
+        return deviceSpecificSettings;
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_galaxybuds_live;
+    }
+
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_galaxy_buds_live;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_galaxy_buds_live_disabled;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/galaxy_buds/GalaxyBudsProDeviceCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/galaxy_buds/GalaxyBudsProDeviceCoordinator.java
new file mode 100644
index 0000000..72bea04
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/galaxy_buds/GalaxyBudsProDeviceCoordinator.java
@@ -0,0 +1,77 @@
+/*  Copyright (C) 2022-2024 Daniel Dakhno, Jos茅 Rebelo, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.galaxy_buds;
+
+import androidx.annotation.NonNull;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
+import nodomain.freeyourgadget.gadgetbridge.model.BatteryConfig;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+
+public class GalaxyBudsProDeviceCoordinator extends GalaxyBudsGenericCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("Galaxy Buds Pro \\(.*");
+    }
+
+    @Override
+    public DeviceSpecificSettingsCustomizer getDeviceSpecificSettingsCustomizer(final GBDevice device) {
+        return new GalaxyBudsSettingsCustomizer(device);
+    }
+
+    @Override
+    public int getBatteryCount() {
+        return 3;
+    }
+
+    @Override
+    public BatteryConfig[] getBatteryConfig(final GBDevice device) {
+        BatteryConfig battery1 = new BatteryConfig(0, R.drawable.ic_buds_pro_case, R.string.battery_case);
+        BatteryConfig battery2 = new BatteryConfig(1, R.drawable.ic_buds_pro_left, R.string.left_earbud);
+        BatteryConfig battery3 = new BatteryConfig(2, R.drawable.ic_buds_pro_right, R.string.right_earbud);
+        return new BatteryConfig[]{battery1, battery2, battery3};
+    }
+
+    @Override
+    public int[] getSupportedDeviceSpecificSettings(GBDevice device) {
+        return new int[]{
+                R.xml.devicesettings_galaxy_buds_pro,
+        };
+    }
+
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_galaxybuds_pro;
+    }
+
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_galaxy_buds_pro;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_galaxy_buds_pro_disabled;
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/galaxy_buds/GalaxyBudsSettingsCustomizer.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/galaxy_buds/GalaxyBudsSettingsCustomizer.java
new file mode 100644
index 0000000..82d90c7
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/galaxy_buds/GalaxyBudsSettingsCustomizer.java
@@ -0,0 +1,282 @@
+/*  Copyright (C) 2022-2024 narektor, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.galaxy_buds;
+
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_GALAXY_BUDS_2_NOISE_CONTROL;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_GALAXY_BUDS_AMBIENT_VOLUME;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_GALAXY_BUDS_PRO_ANC_LEVEL;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_GALAXY_BUDS_PRO_BALANCE;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_GALAXY_BUDS_PRO_NOISE_CONTROL;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_GALAXY_BUDS_TOUCH_LEFT;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_GALAXY_BUDS_TOUCH_LEFT_SWITCH;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_GALAXY_BUDS_TOUCH_RIGHT;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_GALAXY_BUDS_TOUCH_RIGHT_SWITCH;
+
+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.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+public class GalaxyBudsSettingsCustomizer implements DeviceSpecificSettingsCustomizer {
+
+    final GBDevice device;
+
+    public GalaxyBudsSettingsCustomizer(final GBDevice device) {
+        this.device = device;
+    }
+
+    @Override
+    public void onPreferenceChange(final Preference preference, final DeviceSpecificSettingsHandler handler) {
+    }
+
+    @Override
+    public void customizeSettings(final DeviceSpecificSettingsHandler handler, Prefs prefs, final String rootKey) {
+
+        final Preference pref_galaxy_buds_pro_balance = handler.findPreference(PREF_GALAXY_BUDS_PRO_BALANCE);
+        if (pref_galaxy_buds_pro_balance != null) {
+            pref_galaxy_buds_pro_balance.setSummary(String.valueOf((prefs.getInt(PREF_GALAXY_BUDS_PRO_BALANCE, 16) - 16)));
+
+            pref_galaxy_buds_pro_balance.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
+                @Override
+                public boolean onPreferenceChange(Preference preference, Object newVal) {
+                    String newValue = String.valueOf((int) newVal - 16);
+                    pref_galaxy_buds_pro_balance.setSummary(newValue);
+                    handler.notifyPreferenceChanged(PREF_GALAXY_BUDS_PRO_BALANCE);
+                    return true;
+                }
+            });
+        }
+
+        final Preference pref_galaxy_buds_pro_noise_control = handler.findPreference(PREF_GALAXY_BUDS_PRO_NOISE_CONTROL);
+        String pref_galaxy_buds_pro_noise_control_value = prefs.getString(PREF_GALAXY_BUDS_PRO_NOISE_CONTROL, "0");
+        final Preference pref_galaxy_buds_2_noise_control = handler.findPreference(PREF_GALAXY_BUDS_2_NOISE_CONTROL);
+        String pref_galaxy_buds_2_noise_control_value = prefs.getString(PREF_GALAXY_BUDS_2_NOISE_CONTROL, "0");
+        final Preference pref_galaxy_buds_pro_anc_level = handler.findPreference(PREF_GALAXY_BUDS_PRO_ANC_LEVEL);
+        final Preference pref_galaxy_buds_ambient_volume = handler.findPreference(PREF_GALAXY_BUDS_AMBIENT_VOLUME);
+
+        if (pref_galaxy_buds_pro_noise_control != null) {
+
+            if (pref_galaxy_buds_pro_anc_level != null && pref_galaxy_buds_ambient_volume != null) {
+                switch (pref_galaxy_buds_pro_noise_control_value) {
+                    case "0":
+                        pref_galaxy_buds_pro_anc_level.setEnabled(false);
+                        pref_galaxy_buds_ambient_volume.setEnabled(false);
+                        break;
+                    case "1":
+                        pref_galaxy_buds_pro_anc_level.setEnabled(true);
+                        pref_galaxy_buds_ambient_volume.setEnabled(false);
+                        break;
+                    case "2":
+                        pref_galaxy_buds_pro_anc_level.setEnabled(false);
+                        pref_galaxy_buds_ambient_volume.setEnabled(true);
+                        break;
+                }
+            }
+
+            pref_galaxy_buds_pro_noise_control.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
+                @Override
+                public boolean onPreferenceChange(Preference preference, Object newVal) {
+                    handler.notifyPreferenceChanged(PREF_GALAXY_BUDS_PRO_NOISE_CONTROL);
+                    switch (newVal.toString()) {
+                        case "0":
+                            if (pref_galaxy_buds_pro_anc_level != null)
+                                pref_galaxy_buds_pro_anc_level.setEnabled(false);
+                            if (pref_galaxy_buds_ambient_volume != null)
+                                pref_galaxy_buds_ambient_volume.setEnabled(false);
+                            break;
+                        case "1":
+                            if (pref_galaxy_buds_pro_anc_level != null)
+                                pref_galaxy_buds_pro_anc_level.setEnabled(true);
+                            if (pref_galaxy_buds_ambient_volume != null)
+                                pref_galaxy_buds_ambient_volume.setEnabled(false);
+                            break;
+                        case "2":
+                            if (pref_galaxy_buds_pro_anc_level != null)
+                                pref_galaxy_buds_pro_anc_level.setEnabled(false);
+                            if (pref_galaxy_buds_ambient_volume != null)
+                                pref_galaxy_buds_ambient_volume.setEnabled(true);
+                            break;
+                    }
+
+                    return true;
+                }
+            });
+        }
+
+        if (pref_galaxy_buds_2_noise_control != null) {
+
+            switch (pref_galaxy_buds_2_noise_control_value) {
+                case "0":
+                case "1":
+                    pref_galaxy_buds_ambient_volume.setEnabled(false);
+                    break;
+                case "2":
+                    pref_galaxy_buds_ambient_volume.setEnabled(true);
+                    break;
+            }
+
+            pref_galaxy_buds_2_noise_control.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
+                @Override
+                public boolean onPreferenceChange(Preference preference, Object newVal) {
+                    handler.notifyPreferenceChanged(PREF_GALAXY_BUDS_2_NOISE_CONTROL);
+                    switch (newVal.toString()) {
+                        case "0":
+                        case "1":
+                            pref_galaxy_buds_ambient_volume.setEnabled(false);
+                            break;
+                        case "2":
+                            pref_galaxy_buds_ambient_volume.setEnabled(true);
+                            break;
+                    }
+
+                    return true;
+                }
+            });
+        }
+
+        final Preference pref_galaxy_buds_touch_right = handler.findPreference(PREF_GALAXY_BUDS_TOUCH_RIGHT);
+        String pref_galaxy_buds_touch_right_value = prefs.getString(PREF_GALAXY_BUDS_TOUCH_RIGHT, "1");
+        final Preference pref_galaxy_buds_touch_right_switch = handler.findPreference(PREF_GALAXY_BUDS_TOUCH_RIGHT_SWITCH);
+
+        if (pref_galaxy_buds_touch_right != null) {
+
+            switch (pref_galaxy_buds_touch_right_value) {
+                case "2":
+                    pref_galaxy_buds_touch_right_switch.setEnabled(true);
+                    break;
+                default:
+                    pref_galaxy_buds_touch_right_switch.setEnabled(false);
+            }
+
+            pref_galaxy_buds_touch_right.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
+                @Override
+                public boolean onPreferenceChange(Preference preference, Object newVal) {
+                    handler.notifyPreferenceChanged(PREF_GALAXY_BUDS_TOUCH_RIGHT);
+                    switch (newVal.toString()) {
+                        case "2":
+                            pref_galaxy_buds_touch_right_switch.setEnabled(true);
+                            break;
+                        default:
+                            pref_galaxy_buds_touch_right_switch.setEnabled(false);
+
+                    }
+
+                    return true;
+                }
+            });
+        }
+
+        final Preference pref_galaxy_buds_touch_left = handler.findPreference(PREF_GALAXY_BUDS_TOUCH_LEFT);
+        String pref_galaxy_buds_touch_left_value = prefs.getString(PREF_GALAXY_BUDS_TOUCH_LEFT, "1");
+        final Preference pref_galaxy_buds_touch_left_switch = handler.findPreference(PREF_GALAXY_BUDS_TOUCH_LEFT_SWITCH);
+
+        if (pref_galaxy_buds_touch_left != null) {
+
+            switch (pref_galaxy_buds_touch_left_value) {
+                case "2":
+                    pref_galaxy_buds_touch_left_switch.setEnabled(true);
+                    break;
+                default:
+                    pref_galaxy_buds_touch_left_switch.setEnabled(false);
+            }
+
+            pref_galaxy_buds_touch_left.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
+                @Override
+                public boolean onPreferenceChange(Preference preference, Object newVal) {
+                    handler.notifyPreferenceChanged(PREF_GALAXY_BUDS_TOUCH_LEFT);
+                    switch (newVal.toString()) {
+                        case "2":
+                            pref_galaxy_buds_touch_left_switch.setEnabled(true);
+                            break;
+                        default:
+                            pref_galaxy_buds_touch_left_switch.setEnabled(false);
+
+                    }
+
+                    return true;
+                }
+            });
+        }
+
+
+/*
+        final Preference pref_galaxy_buds_ambient_mode = handler.findPreference(PREF_GALAXY_BUDS_AMBIENT_SOUND);
+        boolean is_pref_galaxy_buds_ambient_mode_enabled = prefs.getBoolean(PREF_GALAXY_BUDS_AMBIENT_SOUND, false);
+        final Preference pref_galaxy_buds_ambient_voice_focus_preference = handler.findPreference(PREF_GALAXY_BUDS_AMBIENT_VOICE_FOCUS_PREFERENCE);
+
+        if (pref_galaxy_buds_ambient_mode != null) {
+            if (is_pref_galaxy_buds_ambient_mode_enabled) {
+                pref_galaxy_buds_ambient_voice_focus_preference.setEnabled(true);
+            } else {
+                pref_galaxy_buds_ambient_voice_focus_preference.setEnabled(false);
+            }
+
+
+            pref_galaxy_buds_ambient_mode.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
+                @Override
+                public boolean onPreferenceChange(Preference preference, Object newVal) {
+                    handler.notifyPreferenceChanged(PREF_GALAXY_BUDS_AMBIENT_SOUND);
+                    if ((boolean) newVal) {
+                        pref_galaxy_buds_ambient_voice_focus_preference.setEnabled(true);
+                    } else {
+                        pref_galaxy_buds_ambient_voice_focus_preference.setEnabled(false);
+                    }
+
+                    return true;
+                }
+            });
+        }
+
+ */
+    }
+
+    @Override
+    public Set<String> getPreferenceKeysWithSummary() {
+        return Collections.emptySet();
+    }
+
+
+    public static final Creator<GalaxyBudsSettingsCustomizer> CREATOR = new Creator<GalaxyBudsSettingsCustomizer>() {
+        @Override
+        public GalaxyBudsSettingsCustomizer createFromParcel(final Parcel in) {
+            final GBDevice device = in.readParcelable(GalaxyBudsSettingsCustomizer.class.getClassLoader());
+            return new GalaxyBudsSettingsCustomizer(device);
+        }
+
+        @Override
+        public GalaxyBudsSettingsCustomizer[] newArray(final int size) {
+            return new GalaxyBudsSettingsCustomizer[size];
+        }
+    };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(final Parcel dest, final int flags) {
+        dest.writeParcelable(device, 0);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminActivitySampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminActivitySampleProvider.java
new file mode 100644
index 0000000..606680c
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminActivitySampleProvider.java
@@ -0,0 +1,217 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.garmin;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+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.GarminActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.entities.GarminActivitySampleDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.GarminEventSample;
+import nodomain.freeyourgadget.gadgetbridge.entities.GarminSleepStageSample;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionSleepStage;
+import nodomain.freeyourgadget.gadgetbridge.util.RangeMap;
+
+public class GarminActivitySampleProvider extends AbstractSampleProvider<GarminActivitySample> {
+    private static final Logger LOG = LoggerFactory.getLogger(GarminActivitySampleProvider.class);
+
+    public GarminActivitySampleProvider(final GBDevice device, final DaoSession session) {
+        super(device, session);
+    }
+
+    @Override
+    public AbstractDao<GarminActivitySample, ?> getSampleDao() {
+        return getSession().getGarminActivitySampleDao();
+    }
+
+    @Nullable
+    @Override
+    protected Property getRawKindSampleProperty() {
+        return GarminActivitySampleDao.Properties.RawKind;
+    }
+
+    @NonNull
+    @Override
+    protected Property getTimestampSampleProperty() {
+        return GarminActivitySampleDao.Properties.Timestamp;
+    }
+
+    @NonNull
+    @Override
+    protected Property getDeviceIdentifierSampleProperty() {
+        return GarminActivitySampleDao.Properties.DeviceId;
+    }
+
+    @Override
+    public ActivityKind normalizeType(final int rawType) {
+        return ActivityKind.fromCode(rawType);
+    }
+
+    @Override
+    public int toRawActivityKind(final ActivityKind activityKind) {
+        return activityKind.getCode();
+    }
+
+    @Override
+    public float normalizeIntensity(final int rawIntensity) {
+        return rawIntensity / 100f;
+    }
+
+    @Override
+    public GarminActivitySample createActivitySample() {
+        return new GarminActivitySample();
+    }
+
+    @Override
+    protected List<GarminActivitySample> getGBActivitySamples(final int timestamp_from, final int timestamp_to) {
+        LOG.trace(
+                "Getting garmin activity samples between {} and {}",
+                timestamp_from,
+                timestamp_to
+        );
+
+        final long nanoStart = System.nanoTime();
+
+        final List<GarminActivitySample> samples = fillGaps(
+                super.getGBActivitySamples(timestamp_from, timestamp_to),
+                timestamp_from,
+                timestamp_to
+        );
+
+        if (!samples.isEmpty()) {
+            convertCumulativeSteps(samples, GarminActivitySampleDao.Properties.Steps);
+        }
+
+        convertCalories(samples);
+        overlaySleep(samples, timestamp_from, timestamp_to);
+
+        final long nanoEnd = System.nanoTime();
+
+        final long executionTime = (nanoEnd - nanoStart) / 1000000;
+
+        LOG.trace("Getting Garmin samples took {}ms", executionTime);
+
+        return samples;
+    }
+
+    /**
+     * Converts the calories from kcal to cal
+     */
+    private void convertCalories(List<GarminActivitySample> samples) {
+        for (GarminActivitySample sample : samples) {
+            sample.setActiveCalories(sample.getActiveCalories() * 1000);
+        }
+    }
+
+    public void overlaySleep(final List<GarminActivitySample> samples, final int timestamp_from, final int timestamp_to) {
+        // The samples provided by Garmin are upper-bound timestamps of the sleep stage
+        final RangeMap<Long, ActivityKind> stagesMap = new RangeMap<>(RangeMap.Mode.UPPER_BOUND);
+
+        final GarminEventSampleProvider eventSampleProvider = new GarminEventSampleProvider(getDevice(), getSession());
+        final List<GarminEventSample> sleepEventSamples = eventSampleProvider.getSleepEvents(
+                timestamp_from * 1000L,
+                timestamp_to * 1000L
+        );
+        if (!sleepEventSamples.isEmpty()) {
+            LOG.debug("Found {} sleep event samples between {} and {}", sleepEventSamples.size(), timestamp_from, timestamp_to);
+            for (final GarminEventSample event : sleepEventSamples) {
+                switch (event.getEventType()) {
+                    case 0: // start
+                        // We only need the start event as an upper-bound timestamp (anything before it is unknown)
+                        stagesMap.put(event.getTimestamp(), ActivityKind.UNKNOWN);
+                        break;
+                    case 1: // stop
+                        // See FitImporter#processRawSleepSamples / #4048
+                        if (event.getData() != null && event.getData() == -1) {
+                            stagesMap.put(event.getTimestamp(), ActivityKind.LIGHT_SLEEP);
+                        }
+                    default:
+                }
+            }
+        }
+
+        final GarminSleepStageSampleProvider sleepStagesSampleProvider = new GarminSleepStageSampleProvider(getDevice(), getSession());
+
+        // Retrieve the next stage after this time range
+        final GarminEventSample nextSleepStageAfterRange = eventSampleProvider.getNextSleepEventAfter(timestamp_to * 1000L);
+        if (nextSleepStageAfterRange != null && nextSleepStageAfterRange.getEventType() == 1) {
+            // Sleep session actually ends outside of this range, we need to fetch the next sleep stage
+            final GarminSleepStageSample nextStage = sleepStagesSampleProvider.getNextSampleAfter(timestamp_to * 1000L);
+            if (nextStage != null) {
+                stagesMap.put(nextStage.getTimestamp(), toActivityKind(nextStage));
+            }
+        }
+
+        final List<GarminSleepStageSample> stageSamples = sleepStagesSampleProvider.getAllSamples(
+                timestamp_from * 1000L,
+                timestamp_to * 1000L
+        );
+
+        if (!stageSamples.isEmpty()) {
+            // We got actual sleep stages
+            LOG.debug("Found {} sleep stage samples between {} and {}", stageSamples.size(), timestamp_from, timestamp_to);
+
+            for (final GarminSleepStageSample stageSample : stageSamples) {
+                stagesMap.put(stageSample.getTimestamp(), toActivityKind(stageSample));
+            }
+        }
+
+        if (!stagesMap.isEmpty()) {
+            for (final GarminActivitySample sample : samples) {
+                final long ts = sample.getTimestamp() * 1000L;
+                final ActivityKind sleepType = stagesMap.get(ts);
+                if (sleepType != null && !sleepType.equals(ActivityKind.UNKNOWN)) {
+                    sample.setRawKind(sleepType.getCode());
+                    sample.setRawIntensity(ActivitySample.NOT_MEASURED);
+                }
+            }
+        }
+    }
+
+    private ActivityKind toActivityKind(final GarminSleepStageSample stageSample) {
+        final FieldDefinitionSleepStage.SleepStage sleepStage = FieldDefinitionSleepStage.SleepStage.fromId(stageSample.getStage());
+        if (sleepStage == null) {
+            LOG.error("Unknown sleep stage for {}", stageSample.getStage());
+            return ActivityKind.UNKNOWN;
+        }
+
+        switch (sleepStage) {
+            case AWAKE:
+                return ActivityKind.AWAKE_SLEEP;
+            case LIGHT:
+                return ActivityKind.LIGHT_SLEEP;
+            case DEEP:
+                return ActivityKind.DEEP_SLEEP;
+            case REM:
+                return ActivityKind.REM_SLEEP;
+        }
+
+        return ActivityKind.UNKNOWN;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminBodyEnergySampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminBodyEnergySampleProvider.java
new file mode 100644
index 0000000..1fe89f0
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminBodyEnergySampleProvider.java
@@ -0,0 +1,56 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.garmin;
+
+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.GarminBodyEnergySample;
+import nodomain.freeyourgadget.gadgetbridge.entities.GarminBodyEnergySampleDao;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class GarminBodyEnergySampleProvider extends AbstractTimeSampleProvider<GarminBodyEnergySample> {
+    public GarminBodyEnergySampleProvider(final GBDevice device, final DaoSession session) {
+        super(device, session);
+    }
+
+    @NonNull
+    @Override
+    public AbstractDao<GarminBodyEnergySample, ?> getSampleDao() {
+        return getSession().getGarminBodyEnergySampleDao();
+    }
+
+    @NonNull
+    @Override
+    protected Property getTimestampSampleProperty() {
+        return GarminBodyEnergySampleDao.Properties.Timestamp;
+    }
+
+    @NonNull
+    @Override
+    protected Property getDeviceIdentifierSampleProperty() {
+        return GarminBodyEnergySampleDao.Properties.DeviceId;
+    }
+
+    @Override
+    public GarminBodyEnergySample createSample() {
+        return new GarminBodyEnergySample();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminCoordinator.java
new file mode 100644
index 0000000..572f1d5
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminCoordinator.java
@@ -0,0 +1,414 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.garmin;
+
+import android.content.Context;
+import android.net.Uri;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import de.greenrobot.dao.AbstractDao;
+import de.greenrobot.dao.Property;
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.GBException;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettings;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsScreen;
+import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLEDeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.WorkoutVo2MaxSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.TimeSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.Vo2MaxSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.vivomovehr.GarminCapability;
+import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummaryDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.entities.GarminActivitySampleDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.GarminBodyEnergySampleDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.GarminEventSampleDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.GarminHrvSummarySampleDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.GarminHrvValueSampleDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.GarminSleepStageSampleDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.GarminSpo2SampleDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.GarminStressSampleDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.PendingFileDao;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryParser;
+import nodomain.freeyourgadget.gadgetbridge.model.BodyEnergySample;
+import nodomain.freeyourgadget.gadgetbridge.model.HrvSummarySample;
+import nodomain.freeyourgadget.gadgetbridge.model.HrvValueSample;
+import nodomain.freeyourgadget.gadgetbridge.model.PaiSample;
+import nodomain.freeyourgadget.gadgetbridge.model.RespiratoryRateSample;
+import nodomain.freeyourgadget.gadgetbridge.model.RestingMetabolicRateSample;
+import nodomain.freeyourgadget.gadgetbridge.model.SleepScoreSample;
+import nodomain.freeyourgadget.gadgetbridge.model.Spo2Sample;
+import nodomain.freeyourgadget.gadgetbridge.model.StressSample;
+import nodomain.freeyourgadget.gadgetbridge.model.Vo2MaxSample;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.GarminSupport;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+public abstract class GarminCoordinator extends AbstractBLEDeviceCoordinator {
+    @Override
+    public boolean suggestUnbindBeforePair() {
+        return false;
+    }
+
+    @Override
+    protected void deleteDevice(@NonNull final GBDevice gbDevice, @NonNull final Device device, @NonNull final DaoSession session) throws GBException {
+        deleteAllActivityData(device, session);
+    }
+
+    public void deleteAllActivityData(@NonNull final Device device, @NonNull final DaoSession session) throws GBException {
+        final Long deviceId = device.getId();
+
+        final Map<AbstractDao<?, ?>, Property> daoMap = new HashMap<AbstractDao<?, ?>, Property>() {{
+            put(session.getGarminActivitySampleDao(), GarminActivitySampleDao.Properties.DeviceId);
+            put(session.getGarminStressSampleDao(), GarminStressSampleDao.Properties.DeviceId);
+            put(session.getGarminBodyEnergySampleDao(), GarminBodyEnergySampleDao.Properties.DeviceId);
+            put(session.getGarminSpo2SampleDao(), GarminSpo2SampleDao.Properties.DeviceId);
+            put(session.getGarminSleepStageSampleDao(), GarminSleepStageSampleDao.Properties.DeviceId);
+            put(session.getGarminEventSampleDao(), GarminEventSampleDao.Properties.DeviceId);
+            put(session.getGarminHrvSummarySampleDao(), GarminHrvSummarySampleDao.Properties.DeviceId);
+            put(session.getGarminHrvValueSampleDao(), GarminHrvValueSampleDao.Properties.DeviceId);
+            put(session.getBaseActivitySummaryDao(), BaseActivitySummaryDao.Properties.DeviceId);
+            put(session.getPendingFileDao(), PendingFileDao.Properties.DeviceId);
+        }};
+
+        for (final Map.Entry<AbstractDao<?, ?>, Property> e : daoMap.entrySet()) {
+            e.getKey().queryBuilder()
+                    .where(e.getValue().eq(deviceId))
+                    .buildDelete().executeDeleteWithoutDetachingEntities();
+        }
+    }
+
+    @Override
+    public String getManufacturer() {
+        return "Garmin";
+    }
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_zetime;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_zetime_disabled;
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return GarminSupport.class;
+    }
+
+    @Nullable
+    @Override
+    public ActivitySummaryParser getActivitySummaryParser(final GBDevice device, final Context context) {
+        return new GarminWorkoutParser(context);
+    }
+
+    @Override
+    public SampleProvider<? extends ActivitySample> getSampleProvider(final GBDevice device, DaoSession session) {
+        return new GarminActivitySampleProvider(device, session);
+    }
+
+    @Override
+    public TimeSampleProvider<? extends StressSample> getStressSampleProvider(final GBDevice device, final DaoSession session) {
+        return new GarminStressSampleProvider(device, session);
+    }
+
+    @Override
+    public TimeSampleProvider<? extends BodyEnergySample> getBodyEnergySampleProvider(final GBDevice device, final DaoSession session) {
+        return new GarminBodyEnergySampleProvider(device, session);
+    }
+
+    @Override
+    public TimeSampleProvider<? extends HrvSummarySample> getHrvSummarySampleProvider(final GBDevice device, final DaoSession session) {
+        return new GarminHrvSummarySampleProvider(device, session);
+    }
+
+    @Override
+    public TimeSampleProvider<? extends HrvValueSample> getHrvValueSampleProvider(final GBDevice device, final DaoSession session) {
+        return new GarminHrvValueSampleProvider(device, session);
+    }
+
+    @Override
+    public Vo2MaxSampleProvider<? extends Vo2MaxSample> getVo2MaxSampleProvider(final GBDevice device, final DaoSession session) {
+        return new WorkoutVo2MaxSampleProvider(device, session);
+    }
+
+    @Override
+    public TimeSampleProvider<? extends Spo2Sample> getSpo2SampleProvider(final GBDevice device, final DaoSession session) {
+        return new GarminSpo2SampleProvider(device, session);
+    }
+
+    @Override
+    public TimeSampleProvider<? extends PaiSample> getPaiSampleProvider(final GBDevice device, final DaoSession session) {
+        return new GarminPaiSampleProvider(device, session);
+    }
+
+    @Override
+    public TimeSampleProvider<? extends RespiratoryRateSample> getRespiratoryRateSampleProvider(final GBDevice device, final DaoSession session) {
+        return new GarminRespiratoryRateSampleProvider(device, session);
+    }
+
+    @Override
+    public TimeSampleProvider<? extends RestingMetabolicRateSample> getRestingMetabolicRateProvider(final GBDevice device, final DaoSession session) {
+        return new GarminRestingMetabolicRateSampleProvider(device, session);
+    }
+
+    @Override
+    public TimeSampleProvider<? extends SleepScoreSample> getSleepScoreProvider(final GBDevice device, final DaoSession session) {
+        return new GarminSleepStatsSampleProvider(device, session);
+    }
+
+    @Override
+    public GarminHeartRateRestingSampleProvider getHeartRateRestingSampleProvider(final GBDevice device, final DaoSession session) {
+        return new GarminHeartRateRestingSampleProvider(device, session);
+    }
+
+    @Override
+    public DeviceSpecificSettings getDeviceSpecificSettings(final GBDevice device) {
+        final DeviceSpecificSettings deviceSpecificSettings = new DeviceSpecificSettings();
+
+        if (supports(device, GarminCapability.REALTIME_SETTINGS)) {
+            deviceSpecificSettings.addRootScreen(R.xml.devicesettings_garmin_realtime_settings);
+        }
+
+        final List<Integer> notifications = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.CALLS_AND_NOTIFICATIONS);
+
+        notifications.add(R.xml.devicesettings_send_app_notifications);
+
+        if (getCannedRepliesSlotCount(device) > 0) {
+            notifications.add(R.xml.devicesettings_canned_reply_16);
+            notifications.add(R.xml.devicesettings_canned_dismisscall_16);
+        }
+        if (getContactsSlotCount(device) > 0) {
+            notifications.add(R.xml.devicesettings_contacts);
+        }
+
+        final List<Integer> location = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.LOCATION);
+        location.add(R.xml.devicesettings_workout_send_gps_to_band);
+        if (supportsAgpsUpdates(device)) {
+            location.add(R.xml.devicesettings_garmin_agps);
+        }
+
+        final List<Integer> connection = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.CONNECTION);
+        connection.add(R.xml.devicesettings_high_mtu);
+
+        final List<Integer> developer = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DEVELOPER);
+        developer.add(R.xml.devicesettings_keep_activity_data_on_device);
+        developer.add(R.xml.devicesettings_fetch_unknown_files);
+
+        return deviceSpecificSettings;
+    }
+
+    @Override
+    public DeviceSpecificSettingsCustomizer getDeviceSpecificSettingsCustomizer(GBDevice device) {
+        return new GarminSettingsCustomizer();
+    }
+
+    @Override
+    public boolean supportsActivityDataFetching() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsActivityTracking() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsActivityTracks() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsStressMeasurement() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsBodyEnergy() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsHrvMeasurement() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsVO2Max() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsVO2MaxCycling() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsVO2MaxRunning() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsActiveCalories() {
+        return true;
+    }
+
+    @Override
+    public int[] getStressRanges() {
+        // 1-25 = relaxed
+        // 26-50 = low
+        // 51-80 = moderate
+        // 76-100 = high
+        return new int[]{1, 26, 51, 76};
+    }
+
+    @Override
+    public boolean supportsHeartRateMeasurement(final GBDevice device) {
+        return true;
+    }
+
+    @Override
+    public boolean supportsHeartRateRestingMeasurement(final GBDevice device) {
+        return true;
+    }
+
+    @Override
+    public boolean supportsRealtimeData() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsSpo2(GBDevice device) {
+        return true;
+    }
+
+    @Override
+    public boolean supportsRemSleep() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsAwakeSleep() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsSleepScore() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsRespiratoryRate() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsDayRespiratoryRate() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsPai() {
+        // Intensity Minutes
+        return true;
+    }
+
+    @Override
+    public int getPaiName() {
+        return R.string.garmin_intensity_minutes;
+    }
+
+    @Override
+    public boolean supportsPaiTime() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsPaiLow() {
+        return false;
+    }
+
+    @Override
+    public int getPaiTarget() {
+        return 150;
+    }
+
+    @Override
+    public boolean supportsFindDevice() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsWeather() {
+        return true;
+    }
+
+    @Override
+    public int getCannedRepliesSlotCount(final GBDevice device) {
+        if (getPrefs(device).getBoolean(GarminPreferences.PREF_FEAT_CANNED_MESSAGES, false)) {
+            return 16;
+        }
+
+        return 0;
+    }
+
+    @Override
+    public int getContactsSlotCount(final GBDevice device) {
+        if (getPrefs(device).getBoolean(GarminPreferences.PREF_FEAT_CONTACTS, false)) {
+            return 50;
+        }
+
+        return 0;
+    }
+
+    protected static Prefs getPrefs(final GBDevice device) {
+        return new Prefs(GBApplication.getDeviceSpecificSharedPrefs(device.getAddress()));
+    }
+
+    @Override
+    public boolean supportsUnicodeEmojis() {
+        return true;
+    }
+
+    public boolean supportsAgpsUpdates(final GBDevice device) {
+        return !getPrefs(device).getString(GarminPreferences.PREF_AGPS_KNOWN_URLS, "").isEmpty();
+    }
+
+    public boolean supports(final GBDevice device, final GarminCapability capability) {
+        return getPrefs(device).getStringSet(GarminPreferences.PREF_GARMIN_CAPABILITIES, Collections.emptySet())
+                .contains(capability.name());
+    }
+
+    @Override
+    public boolean supportsFlashing() {
+        return true;
+    }
+
+    @Nullable
+    @Override
+    public InstallHandler findInstallHandler(Uri uri, Context context) {
+        final GarminFitFileInstallHandler fitFileInstallHandler = new GarminFitFileInstallHandler(uri, context);
+        if (fitFileInstallHandler.isValid())
+            return fitFileInstallHandler;
+
+        final GarminGpxRouteInstallHandler garminGpxRouteInstallHandler = new GarminGpxRouteInstallHandler(uri, context);
+        if (garminGpxRouteInstallHandler.isValid())
+            return garminGpxRouteInstallHandler;
+
+        return null;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminEventSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminEventSampleProvider.java
new file mode 100644
index 0000000..1bdf211
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminEventSampleProvider.java
@@ -0,0 +1,101 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.garmin;
+
+import androidx.annotation.NonNull;
+
+import java.util.Collections;
+import java.util.List;
+
+import de.greenrobot.dao.AbstractDao;
+import de.greenrobot.dao.Property;
+import de.greenrobot.dao.query.QueryBuilder;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.AbstractTimeSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.entities.GarminEventSample;
+import nodomain.freeyourgadget.gadgetbridge.entities.GarminEventSampleDao;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class GarminEventSampleProvider extends AbstractTimeSampleProvider<GarminEventSample> {
+    public GarminEventSampleProvider(final GBDevice device, final DaoSession session) {
+        super(device, session);
+    }
+
+    @NonNull
+    @Override
+    public AbstractDao<GarminEventSample, ?> getSampleDao() {
+        return getSession().getGarminEventSampleDao();
+    }
+
+    @NonNull
+    @Override
+    protected Property getTimestampSampleProperty() {
+        return GarminEventSampleDao.Properties.Timestamp;
+    }
+
+    @NonNull
+    @Override
+    protected Property getDeviceIdentifierSampleProperty() {
+        return GarminEventSampleDao.Properties.DeviceId;
+    }
+
+    @Override
+    public GarminEventSample createSample() {
+        return new GarminEventSample();
+    }
+
+    public List<GarminEventSample> getSleepEvents(final long timestampFrom, final long timestampTo) {
+        final QueryBuilder<GarminEventSample> qb = getSampleDao().queryBuilder();
+        final Property timestampProperty = getTimestampSampleProperty();
+        final Device dbDevice = DBHelper.findDevice(getDevice(), getSession());
+        if (dbDevice == null) {
+            // no device, no samples
+            return Collections.emptyList();
+        }
+        final Property deviceProperty = getDeviceIdentifierSampleProperty();
+        qb.where(deviceProperty.eq(dbDevice.getId()), timestampProperty.ge(timestampFrom))
+                .where(timestampProperty.le(timestampTo))
+                .where(GarminEventSampleDao.Properties.Event.eq(74));
+
+        final List<GarminEventSample> samples = qb.build().list();
+        detachFromSession();
+        return samples;
+    }
+
+    public GarminEventSample getNextSleepEventAfter(final long timestampFrom) {
+        final Device dbDevice = DBHelper.findDevice(getDevice(), getSession());
+        if (dbDevice == null) {
+            // no device, no sample
+            return null;
+        }
+
+        final Property deviceIdSampleProp = getDeviceIdentifierSampleProperty();
+        final Property timestampSampleProp = getTimestampSampleProperty();
+        final List<GarminEventSample> samples = getSampleDao().queryBuilder()
+                .where(
+                        deviceIdSampleProp.eq(dbDevice.getId()),
+                        timestampSampleProp.ge(timestampFrom),
+                        GarminEventSampleDao.Properties.Event.eq(74)
+                ).orderAsc(getTimestampSampleProperty())
+                .limit(1)
+                .list();
+
+        return !samples.isEmpty() ? samples.get(0) : null;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminFitFileInstallHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminFitFileInstallHandler.java
new file mode 100644
index 0000000..c7e4ba5
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminFitFileInstallHandler.java
@@ -0,0 +1,231 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.garmin;
+
+import android.content.Context;
+import android.net.Uri;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Optional;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.InstallActivity;
+import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.vivomovehr.GarminCapability;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.GenericItem;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.FileType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.FitFile;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitCourse;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitFileId;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitWorkout;
+import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.UriHelper;
+
+public class GarminFitFileInstallHandler implements InstallHandler {
+    private static final Logger LOG = LoggerFactory.getLogger(GarminFitFileInstallHandler.class);
+
+    protected final Context mContext;
+    private byte[] rawBytes;
+    private FitFile fitFile;
+    private FileType.FILETYPE fileType;
+
+    public GarminFitFileInstallHandler(final Uri uri, final Context context) {
+        this.mContext = context;
+
+        final UriHelper uriHelper;
+        try {
+            uriHelper = UriHelper.get(uri, context);
+        } catch (final IOException e) {
+            LOG.error("Failed to get uri", e);
+            return;
+        }
+
+        // Quickly check whether it's a valid fit file without reading the entire thing
+        try (InputStream in = new BufferedInputStream(uriHelper.openInputStream())) {
+            final byte[] header = new byte[12];
+            final int read = in.read(header);
+            if (read != header.length) {
+                throw new IOException("Not enough bytes for fit header");
+            }
+            if (BLETypeConversions.toUint32(header, 8) != FitFile.Header.MAGIC) {
+                // not fit file
+                return;
+            }
+        } catch (final Exception e) {
+            LOG.error("Failed to validate fit file", e);
+            return;
+        }
+
+        try (InputStream in = new BufferedInputStream(uriHelper.openInputStream())) {
+            rawBytes = FileUtils.readAll(in, 10 * 1024 * 1024); // 10MB
+            fitFile = FitFile.parseIncoming(rawBytes);
+
+            final Optional<FitFileId> fitFileIdOpt = fitFile.getRecords().stream()
+                    .filter(r -> r instanceof FitFileId)
+                    .map(r -> (FitFileId) r)
+                    .findFirst();
+
+            if (!fitFileIdOpt.isPresent()) {
+                LOG.error("Fit file has no ID");
+                return;
+            }
+
+            final FitFileId fitFileId = fitFileIdOpt.get();
+            if (fitFileId.getType() == null) {
+                LOG.error("Fit file ID has null type");
+                return;
+            }
+
+            fileType = fitFileId.getType();
+        } catch (final Exception e) {
+            LOG.error("Failed to read fit file", e);
+        }
+    }
+
+    @Override
+    public boolean isValid() {
+        return fitFile != null && fileType != null;
+    }
+
+    @Override
+    public void validateInstallation(final InstallActivity installActivity, final GBDevice device) {
+        if (fitFile == null || fileType == null) {
+            return;
+        }
+
+        if (device.isBusy()) {
+            installActivity.setInfoText(device.getBusyTask());
+            installActivity.setInstallEnabled(false);
+            return;
+        }
+
+        final DeviceCoordinator coordinator = device.getDeviceCoordinator();
+        if (!(coordinator instanceof GarminCoordinator)) {
+            LOG.warn("Coordinator is not a GarminCoordinator: {}", coordinator.getClass());
+            installActivity.setInfoText(mContext.getString(R.string.fwapp_install_device_not_supported));
+            installActivity.setInstallEnabled(false);
+            return;
+        }
+        final GarminCoordinator garminCoordinator = (GarminCoordinator) coordinator;
+        final boolean fileSupported = parseFitFile(installActivity, garminCoordinator, device);
+
+        if (!fileSupported) {
+            installActivity.setInfoText(mContext.getString(R.string.fwapp_install_device_not_supported));
+            installActivity.setInstallEnabled(false);
+            return;
+        }
+
+        if (!device.isInitialized()) {
+            installActivity.setInfoText(mContext.getString(R.string.fwapp_install_device_not_ready));
+            installActivity.setInstallEnabled(false);
+            return;
+        }
+
+        installActivity.setInstallEnabled(true);
+    }
+
+    @Override
+    public void onStartInstall(final GBDevice device) {
+    }
+
+    public byte[] getRawBytes() {
+        return rawBytes;
+    }
+
+    public FitFile getFitFile() {
+        return fitFile;
+    }
+
+    public FileType.FILETYPE getFileType() {
+        return fileType;
+}
+
+    private boolean parseFitFile(final InstallActivity installActivity, final GarminCoordinator coordinator, final GBDevice device) {
+        final String name;
+        final int kindName;
+
+        switch (fileType) {
+            case COURSES:
+                if (!coordinator.supports(device, GarminCapability.COURSE_DOWNLOAD)) {
+                    LOG.warn("Device does not support course download");
+                    return false;
+                }
+                final Optional<FitCourse> fitCourseOpt = fitFile.getRecords().stream()
+                        .filter(r -> r instanceof FitCourse)
+                        .map(r -> (FitCourse) r)
+                        .findFirst();
+
+                if (!fitCourseOpt.isPresent()) {
+                    LOG.error("Fit file has no course record");
+                    return false;
+                }
+                final FitCourse fitCourse = fitCourseOpt.get();
+
+                name = String.valueOf(fitCourse.getName());
+                kindName = R.string.kind_gpx_route;
+
+                break;
+            case WORKOUTS:
+                if (!coordinator.supports(device, GarminCapability.WORKOUT_DOWNLOAD)) {
+                    LOG.warn("Device does not support workout download");
+                    return false;
+                }
+                final Optional<FitWorkout> fitWorkoutOpt = fitFile.getRecords().stream()
+                        .filter(r -> r instanceof FitWorkout)
+                        .map(r -> (FitWorkout) r)
+                        .findFirst();
+
+                if (!fitWorkoutOpt.isPresent()) {
+                    LOG.error("Fit file has no workout record");
+                    return false;
+                }
+                final FitWorkout fitWorkout = fitWorkoutOpt.get();
+
+                name = String.valueOf(fitWorkout.getName());
+                kindName = R.string.menuitem_workout;
+
+                break;
+            default:
+                LOG.warn("Unsupported fit file type: {}", fileType);
+                return false;
+        }
+
+        final GenericItem fwItem = new GenericItem(mContext.getString(
+                R.string.installhandler_firmware_name,
+                mContext.getString(coordinator.getDeviceNameResource()),
+                mContext.getString(kindName),
+                name
+        ));
+        fwItem.setIcon(coordinator.getDefaultIconResource());
+
+        final StringBuilder builder = new StringBuilder();
+        final String kindNameString = mContext.getString(kindName);
+        builder.append(mContext.getString(R.string.fw_upgrade_notice, kindNameString));
+        installActivity.setInfoText(builder.toString());
+        installActivity.setInstallItem(fwItem);
+
+        return true;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminGpxRouteInstallHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminGpxRouteInstallHandler.java
new file mode 100644
index 0000000..5afb29e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminGpxRouteInstallHandler.java
@@ -0,0 +1,139 @@
+/*  Copyright (C) 2023-2024 Daniel Dakhno, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.garmin;
+
+import android.content.Context;
+import android.net.Uri;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.InstallActivity;
+import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.GenericItem;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.GpxRouteFileConverter;
+import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.UriHelper;
+
+import static nodomain.freeyourgadget.gadgetbridge.devices.vivomovehr.GarminCapability.COURSE_DOWNLOAD;
+
+public class GarminGpxRouteInstallHandler implements InstallHandler {
+    private static final Logger LOG = LoggerFactory.getLogger(GarminGpxRouteInstallHandler.class);
+
+    protected final Context mContext;
+    public byte[] rawBytes;
+    private GpxRouteFileConverter gpxRouteFileConverter;
+
+    public GarminGpxRouteInstallHandler(final Uri uri, final Context context) {
+        this.mContext = context;
+
+        final UriHelper uriHelper;
+        try {
+            uriHelper = UriHelper.get(uri, context);
+        } catch (final IOException e) {
+            LOG.error("Failed to get uri", e);
+            return;
+        }
+        try (InputStream in = new BufferedInputStream(uriHelper.openInputStream())) {
+            rawBytes = FileUtils.readAll(in, 1024 * 1024); // 1MB
+
+            final GpxRouteFileConverter gpxRouteFileConverter1 = new GpxRouteFileConverter(rawBytes);
+            if (gpxRouteFileConverter1.isValid()) {
+                this.gpxRouteFileConverter = gpxRouteFileConverter1;
+            }
+        } catch (final Exception e) {
+            LOG.error("Failed to read file", e);
+        }
+    }
+
+    @Override
+    public boolean isValid() {
+        return gpxRouteFileConverter != null;
+    }
+
+    @Override
+    public void validateInstallation(final InstallActivity installActivity, final GBDevice device) {
+        if (device.isBusy()) {
+            installActivity.setInfoText(device.getBusyTask());
+            installActivity.setInstallEnabled(false);
+            return;
+        }
+
+        final DeviceCoordinator coordinator = device.getDeviceCoordinator();
+        if (!(coordinator instanceof GarminCoordinator)) {
+            LOG.warn("Coordinator is not a GarminCoordinator: {}", coordinator.getClass());
+            installActivity.setInfoText(mContext.getString(R.string.fwapp_install_device_not_supported));
+            installActivity.setInstallEnabled(false);
+            return;
+        }
+        final GarminCoordinator garminCoordinator = (GarminCoordinator) coordinator;
+        if (!garminCoordinator.supports(device, COURSE_DOWNLOAD)) {
+            installActivity.setInfoText(mContext.getString(R.string.fwapp_install_device_not_supported));
+            installActivity.setInstallEnabled(false);
+            return;
+        }
+
+        if (!device.isInitialized()) {
+            installActivity.setInfoText(mContext.getString(R.string.fwapp_install_device_not_ready));
+            installActivity.setInstallEnabled(false);
+            return;
+        }
+
+        final GenericItem fwItem = createInstallItem(device);
+        fwItem.setIcon(coordinator.getDefaultIconResource());
+
+        if (gpxRouteFileConverter == null) {
+            fwItem.setDetails(mContext.getString(R.string.miband_fwinstaller_incompatible_version));
+            installActivity.setInfoText(mContext.getString(R.string.fwinstaller_firmware_not_compatible_to_device));
+            installActivity.setInstallEnabled(false);
+            return;
+        }
+
+        final StringBuilder builder = new StringBuilder();
+        final String gpxRoute = mContext.getString(R.string.kind_gpx_route);
+        builder.append(mContext.getString(R.string.fw_upgrade_notice, gpxRoute));
+        installActivity.setInfoText(builder.toString());
+        installActivity.setInstallItem(fwItem);
+        installActivity.setInstallEnabled(true);
+    }
+
+    @Override
+    public void onStartInstall(final GBDevice device) {
+    }
+
+    public GpxRouteFileConverter getGpxRouteFileConverter() {
+        return gpxRouteFileConverter;
+    }
+
+    private GenericItem createInstallItem(final GBDevice device) {
+        DeviceCoordinator coordinator = device.getDeviceCoordinator();
+        final String firmwareName = mContext.getString(
+                R.string.installhandler_firmware_name,
+                mContext.getString(coordinator.getDeviceNameResource()),
+                mContext.getString(R.string.kind_gpx_route),
+                gpxRouteFileConverter.getName()
+        );
+        return new GenericItem(firmwareName);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminHeartRateRestingSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminHeartRateRestingSampleProvider.java
new file mode 100644
index 0000000..15a3779
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminHeartRateRestingSampleProvider.java
@@ -0,0 +1,56 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.garmin;
+
+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.GarminHeartRateRestingSample;
+import nodomain.freeyourgadget.gadgetbridge.entities.GarminHeartRateRestingSampleDao;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class GarminHeartRateRestingSampleProvider extends AbstractTimeSampleProvider<GarminHeartRateRestingSample> {
+    public GarminHeartRateRestingSampleProvider(final GBDevice device, final DaoSession session) {
+        super(device, session);
+    }
+
+    @NonNull
+    @Override
+    public AbstractDao<GarminHeartRateRestingSample, ?> getSampleDao() {
+        return getSession().getGarminHeartRateRestingSampleDao();
+    }
+
+    @NonNull
+    @Override
+    protected Property getTimestampSampleProperty() {
+        return GarminHeartRateRestingSampleDao.Properties.Timestamp;
+    }
+
+    @NonNull
+    @Override
+    protected Property getDeviceIdentifierSampleProperty() {
+        return GarminHeartRateRestingSampleDao.Properties.DeviceId;
+    }
+
+    @Override
+    public GarminHeartRateRestingSample createSample() {
+        return new GarminHeartRateRestingSample();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminHrvSummarySampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminHrvSummarySampleProvider.java
new file mode 100644
index 0000000..9053747
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminHrvSummarySampleProvider.java
@@ -0,0 +1,56 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.garmin;
+
+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.GarminHrvSummarySample;
+import nodomain.freeyourgadget.gadgetbridge.entities.GarminHrvSummarySampleDao;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class GarminHrvSummarySampleProvider extends AbstractTimeSampleProvider<GarminHrvSummarySample> {
+    public GarminHrvSummarySampleProvider(final GBDevice device, final DaoSession session) {
+        super(device, session);
+    }
+
+    @NonNull
+    @Override
+    public AbstractDao<GarminHrvSummarySample, ?> getSampleDao() {
+        return getSession().getGarminHrvSummarySampleDao();
+    }
+
+    @NonNull
+    @Override
+    protected Property getTimestampSampleProperty() {
+        return GarminHrvSummarySampleDao.Properties.Timestamp;
+    }
+
+    @NonNull
+    @Override
+    protected Property getDeviceIdentifierSampleProperty() {
+        return GarminHrvSummarySampleDao.Properties.DeviceId;
+    }
+
+    @Override
+    public GarminHrvSummarySample createSample() {
+        return new GarminHrvSummarySample();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminHrvValueSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminHrvValueSampleProvider.java
new file mode 100644
index 0000000..de6d88b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminHrvValueSampleProvider.java
@@ -0,0 +1,56 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.garmin;
+
+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.GarminHrvValueSample;
+import nodomain.freeyourgadget.gadgetbridge.entities.GarminHrvValueSampleDao;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class GarminHrvValueSampleProvider extends AbstractTimeSampleProvider<GarminHrvValueSample> {
+    public GarminHrvValueSampleProvider(final GBDevice device, final DaoSession session) {
+        super(device, session);
+    }
+
+    @NonNull
+    @Override
+    public AbstractDao<GarminHrvValueSample, ?> getSampleDao() {
+        return getSession().getGarminHrvValueSampleDao();
+    }
+
+    @NonNull
+    @Override
+    protected Property getTimestampSampleProperty() {
+        return GarminHrvValueSampleDao.Properties.Timestamp;
+    }
+
+    @NonNull
+    @Override
+    protected Property getDeviceIdentifierSampleProperty() {
+        return GarminHrvValueSampleDao.Properties.DeviceId;
+    }
+
+    @Override
+    public GarminHrvValueSample createSample() {
+        return new GarminHrvValueSample();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminIntensityMinutesSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminIntensityMinutesSampleProvider.java
new file mode 100644
index 0000000..8205ce4
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminIntensityMinutesSampleProvider.java
@@ -0,0 +1,56 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.garmin;
+
+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.GarminIntensityMinutesSample;
+import nodomain.freeyourgadget.gadgetbridge.entities.GarminIntensityMinutesSampleDao;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class GarminIntensityMinutesSampleProvider extends AbstractTimeSampleProvider<GarminIntensityMinutesSample> {
+    public GarminIntensityMinutesSampleProvider(final GBDevice device, final DaoSession session) {
+        super(device, session);
+    }
+
+    @NonNull
+    @Override
+    public AbstractDao<GarminIntensityMinutesSample, ?> getSampleDao() {
+        return getSession().getGarminIntensityMinutesSampleDao();
+    }
+
+    @NonNull
+    @Override
+    protected Property getTimestampSampleProperty() {
+        return GarminIntensityMinutesSampleDao.Properties.Timestamp;
+    }
+
+    @NonNull
+    @Override
+    protected Property getDeviceIdentifierSampleProperty() {
+        return GarminIntensityMinutesSampleDao.Properties.DeviceId;
+    }
+
+    @Override
+    public GarminIntensityMinutesSample createSample() {
+        return new GarminIntensityMinutesSample();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminPaiSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminPaiSampleProvider.java
new file mode 100644
index 0000000..aa7d48b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminPaiSampleProvider.java
@@ -0,0 +1,241 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.garmin;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.time.DayOfWeek;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.time.temporal.ChronoUnit;
+import java.time.temporal.TemporalAdjusters;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.TimeSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.GarminIntensityMinutesSample;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.PaiSample;
+
+public class GarminPaiSampleProvider implements TimeSampleProvider<PaiSample> {
+    private final GarminIntensityMinutesSampleProvider intensityMinutesSampleProvider;
+
+    public GarminPaiSampleProvider(final GBDevice device, final DaoSession session) {
+        this.intensityMinutesSampleProvider = new GarminIntensityMinutesSampleProvider(device, session);
+    }
+
+    @NonNull
+    @Override
+    public List<PaiSample> getAllSamples(final long timestampFrom, final long timestampTo) {
+        // Intensity minutes reset every monday, so we need to go to the previous monday if we
+        // are not there yet
+        final ZoneId tz = ZoneId.systemDefault();
+        ZonedDateTime zonedDateTime = ZonedDateTime.ofInstant(Instant.ofEpochMilli(timestampFrom), tz);
+        final DayOfWeek dayOfWeek = zonedDateTime.getDayOfWeek();
+        if (dayOfWeek != DayOfWeek.MONDAY) {
+            zonedDateTime = zonedDateTime.with(TemporalAdjusters.previous(DayOfWeek.MONDAY));
+        }
+        zonedDateTime = zonedDateTime.truncatedTo(ChronoUnit.DAYS);
+        final List<GarminIntensityMinutesSample> allSamples = intensityMinutesSampleProvider.getAllSamples(
+                zonedDateTime.toEpochSecond() * 1000L,
+                timestampTo
+        );
+        if (allSamples.isEmpty()) {
+            return Collections.emptyList();
+        }
+
+        int totalModerate = 0;
+        int totalVigorous = 0;
+        int currentDay = 0;
+
+        LocalDate lastSampleDate = LocalDate.ofInstant(Instant.ofEpochMilli(allSamples.get(0).getTimestamp()), tz);
+        final List<PaiSample> ret = new ArrayList<>(allSamples.size());
+        for (final GarminIntensityMinutesSample sample : allSamples) {
+            final LocalDate sampleDate = LocalDate.ofInstant(Instant.ofEpochMilli(sample.getTimestamp()), tz);
+
+            // Since we only persist minute samples for days where there was activity, we need to fill any gaps
+            for (LocalDate d = lastSampleDate.plusDays(1); d.isBefore(sampleDate); d = d.plusDays(1)) {
+                if (lastSampleDate.getDayOfWeek() != d.getDayOfWeek()) {
+                    currentDay = 0;
+                    if (d.getDayOfWeek() == DayOfWeek.MONDAY) {
+                        totalModerate = 0;
+                        totalVigorous = 0;
+                    }
+                }
+
+                ret.add(new GarminPaiSample(
+                        d.atStartOfDay(tz).toInstant().toEpochMilli(),
+                        totalModerate,
+                        totalVigorous,
+                        currentDay
+                ));
+
+                lastSampleDate = d;
+            }
+
+            if (sampleDate.getDayOfWeek() != lastSampleDate.getDayOfWeek()) {
+                currentDay = 0;
+                if (sampleDate.getDayOfWeek() == DayOfWeek.MONDAY) {
+                    totalModerate = 0;
+                    totalVigorous = 0;
+                }
+            }
+
+            totalModerate += sample.getModerate();
+            totalVigorous += sample.getVigorous();
+            currentDay += sample.getModerate() + sample.getVigorous() * 2;
+
+            if (sample.getTimestamp() >= timestampFrom && sample.getTimestamp() <= timestampTo) {
+                ret.add(new GarminPaiSample(
+                        sample.getTimestamp(),
+                        totalModerate,
+                        totalVigorous,
+                        currentDay
+                ));
+            }
+
+            lastSampleDate = sampleDate;
+        }
+
+        // Finally, fill out from the last sample to the end of the timestampTo
+        final LocalDate endDate = LocalDate.ofInstant(Instant.ofEpochMilli(timestampTo), tz);
+        for (LocalDate d = lastSampleDate.plusDays(1); !d.isAfter(endDate); d = d.plusDays(1)) {
+            if (lastSampleDate.getDayOfWeek() != d.getDayOfWeek()) {
+                currentDay = 0;
+                if (d.getDayOfWeek() == DayOfWeek.MONDAY) {
+                    totalModerate = 0;
+                    totalVigorous = 0;
+                }
+            }
+
+            ret.add(new GarminPaiSample(
+                    d.atStartOfDay(tz).toInstant().toEpochMilli(),
+                    totalModerate,
+                    totalVigorous,
+                    currentDay
+            ));
+
+            lastSampleDate = d;
+        }
+
+        return ret;
+    }
+
+    @Override
+    public void addSample(final PaiSample timeSample) {
+        throw new UnsupportedOperationException("This sample provider is read-only!");
+    }
+
+    @Override
+    public void addSamples(final List<PaiSample> timeSamples) {
+        throw new UnsupportedOperationException("This sample provider is read-only!");
+    }
+
+    @Override
+    public PaiSample createSample() {
+        throw new UnsupportedOperationException("This sample provider is read-only!");
+    }
+
+    @Nullable
+    @Override
+    public PaiSample getLatestSample() {
+        // TODO
+        return null;
+    }
+
+    @Nullable
+    @Override
+    public PaiSample getLatestSample(long until) {
+        // TODO
+        return null;
+    }
+
+    @Nullable
+    @Override
+    public PaiSample getFirstSample() {
+        // TODO
+        return null;
+    }
+
+
+    public static class GarminPaiSample implements PaiSample {
+        private final long timestamp;
+        private final int minutesModerate;
+        private final int minutesVigorous;
+        private final int today;
+
+        public GarminPaiSample(final long timestamp,
+                               final int moderate,
+                               final int vigorous,
+                               final int today) {
+            this.timestamp = timestamp;
+            this.minutesModerate = moderate;
+            this.minutesVigorous = vigorous;
+            this.today = today;
+        }
+
+        @Override
+        public long getTimestamp() {
+            return timestamp;
+        }
+
+        @Override
+        public float getPaiLow() {
+            return 0;
+        }
+
+        @Override
+        public float getPaiModerate() {
+            return minutesModerate;
+        }
+
+        @Override
+        public float getPaiHigh() {
+            return minutesVigorous * 2;
+        }
+
+        @Override
+        public int getTimeLow() {
+            return 0; // not supported
+        }
+
+        @Override
+        public int getTimeModerate() {
+            return minutesModerate;
+        }
+
+        @Override
+        public int getTimeHigh() {
+            return minutesVigorous;
+        }
+
+        @Override
+        public float getPaiToday() {
+            return today;
+        }
+
+        @Override
+        public float getPaiTotal() {
+            return minutesModerate + 2 * minutesVigorous;
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminPreferences.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminPreferences.java
new file mode 100644
index 0000000..1e5a885
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminPreferences.java
@@ -0,0 +1,27 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.garmin;
+
+import nodomain.freeyourgadget.gadgetbridge.util.CheckSums;
+
+public class GarminPreferences {
+    public static final String PREF_GARMIN_CAPABILITIES = "garmin_capabilities";
+    public static final String PREF_FEAT_CANNED_MESSAGES = "feat_canned_messages";
+    public static final String PREF_FEAT_CONTACTS = "feat_contacts";
+    public static final String PREF_AGPS_KNOWN_URLS = "garmin_agps_known_urls";
+    public static final String PREF_GARMIN_AGPS_STATUS = "garmin_agps_status_%s";
+    public static final String PREF_GARMIN_AGPS_UPDATE_TIME = "garmin_agps_update_time_%s";
+    public static final String PREF_GARMIN_AGPS_FOLDER = "garmin_agps_folder";
+    public static final String PREF_GARMIN_AGPS_FILENAME = "garmin_agps_filename_%s";
+    public static final String PREF_GARMIN_REALTIME_SETTINGS = "garmin_realtime_settings";
+
+    public static String agpsStatus(final String url) {
+        return String.format(GarminPreferences.PREF_GARMIN_AGPS_STATUS, CheckSums.md5(url));
+    }
+
+    public static String agpsUpdateTime(final String url) {
+        return String.format(GarminPreferences.PREF_GARMIN_AGPS_UPDATE_TIME, CheckSums.md5(url));
+    }
+
+    public static String agpsFilename(final String url) {
+        return String.format(GarminPreferences.PREF_GARMIN_AGPS_FILENAME, CheckSums.md5(url));
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminRealtimeSettingsActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminRealtimeSettingsActivity.java
new file mode 100644
index 0000000..eb8c041
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminRealtimeSettingsActivity.java
@@ -0,0 +1,94 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.garmin;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.widget.Toast;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentManager;
+import androidx.preference.PreferenceFragmentCompat;
+
+import java.util.Optional;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.AbstractSettingsActivityV2;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class GarminRealtimeSettingsActivity extends AbstractSettingsActivityV2 {
+    private GBDevice device;
+    private int screenId;
+
+    public static final String EXTRA_SCREEN_ID = "screenId";
+
+    @Override
+    protected String fragmentTag() {
+        return GarminRealtimeSettingsFragment.FRAGMENT_TAG;
+    }
+
+    @Override
+    protected PreferenceFragmentCompat newFragment() {
+        return GarminRealtimeSettingsFragment.newInstance(device, screenId);
+    }
+
+    @Override
+    protected void onCreate(final Bundle savedInstanceState) {
+        device = getIntent().getParcelableExtra(GBDevice.EXTRA_DEVICE);
+        screenId = getIntent().getIntExtra(EXTRA_SCREEN_ID, GarminRealtimeSettingsFragment.ROOT_SCREEN_ID);
+
+        super.onCreate(savedInstanceState);
+
+        if (device == null || !device.isInitialized()) {
+            GB.toast(getString(R.string.watch_not_connected), Toast.LENGTH_SHORT, GB.INFO);
+            finish();
+        }
+    }
+
+    @Override
+    public boolean onCreateOptionsMenu(final Menu menu) {
+        final MenuInflater inflater = getMenuInflater();
+        inflater.inflate(R.menu.menu_garmin_realtime_settings, menu);
+        return true;
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(@NonNull final MenuItem item) {
+        final int itemId = item.getItemId();
+
+        if (itemId == R.id.garmin_rt_debug_toggle) {
+            getFragment().ifPresent(GarminRealtimeSettingsFragment::toggleDebug);
+            return true;
+        } else if (itemId == R.id.garmin_rt_debug_share) {
+            getFragment().ifPresent(GarminRealtimeSettingsFragment::shareDebug);
+            return true;
+        }
+
+        return super.onOptionsItemSelected(item);
+    }
+
+    @Override
+    protected void onActivityResult(final int requestCode, final int resultCode, final @Nullable Intent data) {
+        super.onActivityResult(requestCode, resultCode, data);
+
+        getFragment().ifPresent(GarminRealtimeSettingsFragment::refreshFromDevice);
+    }
+
+    private Optional<GarminRealtimeSettingsFragment> getFragment() {
+        final FragmentManager fragmentManager = getSupportFragmentManager();
+        final Fragment fragment = fragmentManager.findFragmentByTag(GarminRealtimeSettingsFragment.FRAGMENT_TAG);
+        if (fragment == null) {
+            return Optional.empty();
+        }
+
+        if (fragment instanceof GarminRealtimeSettingsFragment) {
+            return Optional.of((GarminRealtimeSettingsFragment) fragment);
+        }
+
+        return Optional.empty();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminRealtimeSettingsFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminRealtimeSettingsFragment.java
new file mode 100644
index 0000000..b21d6c2
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminRealtimeSettingsFragment.java
@@ -0,0 +1,897 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.garmin;
+
+import android.content.ActivityNotFoundException;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.text.InputFilter;
+import android.text.InputType;
+import android.widget.ArrayAdapter;
+import android.widget.Toast;
+
+import androidx.annotation.DrawableRes;
+import androidx.fragment.app.FragmentActivity;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+import androidx.preference.DialogPreference;
+import androidx.preference.EditTextPreference;
+import androidx.preference.ListPreference;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceCategory;
+import androidx.preference.PreferenceScreen;
+import androidx.preference.SwitchPreferenceCompat;
+
+import com.google.android.material.dialog.MaterialAlertDialogBuilder;
+import com.google.protobuf.InvalidProtocolBufferException;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+import nodomain.freeyourgadget.gadgetbridge.BuildConfig;
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.AbstractPreferenceFragment;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.proto.garmin.GdiSettingsService;
+import nodomain.freeyourgadget.gadgetbridge.proto.garmin.GdiSmartProto;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.XDatePreference;
+import nodomain.freeyourgadget.gadgetbridge.util.XTimePreference;
+import nodomain.freeyourgadget.gadgetbridge.util.preferences.MinMaxTextWatcher;
+
+public class GarminRealtimeSettingsFragment extends AbstractPreferenceFragment {
+    private static final Logger LOG = LoggerFactory.getLogger(GarminRealtimeSettingsFragment.class);
+
+    public static final String EXTRA_SCREEN_ID = "screenId";
+    public static final String PREF_DEBUG = "garmin_rt_debug_mode";
+
+    public static final int ROOT_SCREEN_ID = 36352;
+
+    static final String FRAGMENT_TAG = "GARMIN_REALTIME_SETTINGS_FRAGMENT";
+
+    private GBDevice device;
+    private int screenId = ROOT_SCREEN_ID;
+
+    private GdiSettingsService.ScreenDefinition screenDefinition;
+    private GdiSettingsService.ScreenState screenState;
+
+    public static final String EXTRA_PROTOBUF = "protobuf";
+
+    public static final String ACTION_SCREEN_DEFINITION = "nodomain.freeyourgadget.gadgetbridge.garmin.realtime_settings.screen_definition";
+    public static final String ACTION_SCREEN_STATE = "nodomain.freeyourgadget.gadgetbridge.garmin.realtime_settings.screen_state";
+    public static final String ACTION_CHANGE = "nodomain.freeyourgadget.gadgetbridge.garmin.realtime_settings.change";
+
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(final Context context, final Intent intent) {
+            final String action = intent.getAction();
+            if (action == null) {
+                LOG.error("Got null action");
+                return;
+            }
+
+            switch (action) {
+                case ACTION_SCREEN_DEFINITION:
+                    final GdiSettingsService.ScreenDefinition incomingScreen;
+                    try {
+                        incomingScreen = GdiSettingsService.ScreenDefinition.parseFrom(intent.getByteArrayExtra(EXTRA_PROTOBUF));
+                    } catch (final InvalidProtocolBufferException e) {
+                        // should never happen
+                        LOG.error("Failed to parse protobuf for screen definition on {}", screenId, e);
+                        return;
+                    }
+                    if (incomingScreen.getScreenId() != screenId) {
+                        return;
+                    }
+                    LOG.debug("Got screen definition for screenId={}", screenId);
+                    screenDefinition = incomingScreen;
+                    break;
+                case ACTION_SCREEN_STATE:
+                    final GdiSettingsService.ScreenState incomingState;
+                    try {
+                        incomingState = GdiSettingsService.ScreenState.parseFrom(intent.getByteArrayExtra(EXTRA_PROTOBUF));
+                    } catch (final InvalidProtocolBufferException e) {
+                        // should never happen
+                        LOG.error("Failed to parse protobuf for screen state on {}", screenId, e);
+                        return;
+                    }
+                    if (incomingState.getScreenId() != screenId) {
+                        return;
+                    }
+                    LOG.debug("Got screen state for screenId={}", screenId);
+                    screenState = incomingState;
+                    break;
+                case ACTION_CHANGE:
+                    final GdiSettingsService.ChangeResponse incomingChange;
+                    try {
+                        incomingChange = GdiSettingsService.ChangeResponse.parseFrom(intent.getByteArrayExtra(EXTRA_PROTOBUF));
+                    } catch (final InvalidProtocolBufferException e) {
+                        // should never happen
+                        LOG.error("Failed to parse protobuf for change", e);
+                        return;
+                    }
+                    if (incomingChange.getState().getScreenId() != screenId) {
+                        return;
+                    }
+
+                    if (incomingChange.getShouldReturn()) {
+                        LOG.debug("Returning from {}", screenId);
+                        requireActivity().finish();
+                        return;
+                    }
+
+                    LOG.debug("Got screen change for screenId={}", screenId);
+
+                    GBApplication.deviceService(device).onReadConfiguration("screenId:" + screenId);
+                    return;
+                default:
+                    LOG.error("Unknown action {}", action);
+                    return;
+            }
+
+            reload();
+        }
+    };
+
+    private void setDevice(final GBDevice device) {
+        final Bundle args = getArguments() != null ? getArguments() : new Bundle();
+        args.putParcelable("device", device);
+        setArguments(args);
+    }
+
+    private void setScreenId(final int screenId) {
+        final Bundle args = getArguments() != null ? getArguments() : new Bundle();
+        args.putInt("screenId", screenId);
+        setArguments(args);
+    }
+
+    @Override
+    public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) {
+        final Bundle arguments = getArguments();
+        if (arguments == null) {
+            return;
+        }
+        this.device = arguments.getParcelable(GBDevice.EXTRA_DEVICE);
+        if (device == null) {
+            return;
+        }
+        this.screenId = arguments.getInt(EXTRA_SCREEN_ID, ROOT_SCREEN_ID);
+        if (screenId == 0) {
+            return;
+        }
+
+        LOG.info("Opened realtime preferences screen for {}", screenId);
+
+        getPreferenceManager().setSharedPreferencesName("garmin_rt_" + device.getAddress());
+        setPreferencesFromResource(R.xml.garmin_realtime_settings, rootKey);
+
+        final IntentFilter filter = new IntentFilter();
+        filter.addAction(ACTION_SCREEN_DEFINITION);
+        filter.addAction(ACTION_SCREEN_STATE);
+        filter.addAction(ACTION_CHANGE);
+        LocalBroadcastManager.getInstance(requireContext()).registerReceiver(mReceiver, filter);
+
+        GBApplication.deviceService(device).onReadConfiguration("screenId:" + screenId);
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        reload();
+    }
+
+    @Override
+    public void onDestroyView() {
+        LocalBroadcastManager.getInstance(requireContext()).unregisterReceiver(mReceiver);
+        super.onDestroyView();
+    }
+
+    static GarminRealtimeSettingsFragment newInstance(final GBDevice device, final int screenId) {
+        final GarminRealtimeSettingsFragment fragment = new GarminRealtimeSettingsFragment();
+        fragment.setDevice(device);
+        fragment.setScreenId(screenId);
+
+        return fragment;
+    }
+
+    void refreshFromDevice() {
+        screenDefinition = null;
+        screenState = null;
+        reload();
+        GBApplication.deviceService(device).onReadConfiguration("screenId:" + screenId);
+    }
+
+    void reload() {
+        final boolean debug = GBApplication.getDevicePrefs(device).getBoolean(PREF_DEBUG, BuildConfig.DEBUG);
+
+        final FragmentActivity activity = getActivity();
+        if (activity == null) {
+            LOG.error("Activity is null");
+            return;
+        }
+
+        final PreferenceScreen prefScreen = findPreference(GarminPreferences.PREF_GARMIN_REALTIME_SETTINGS);
+        if (prefScreen == null) {
+            LOG.error("Preference screen for {} is null", GarminPreferences.PREF_GARMIN_REALTIME_SETTINGS);
+            activity.finish();
+            return;
+        }
+
+        if (screenDefinition == null || screenState == null) {
+            ((GarminRealtimeSettingsActivity) activity).setActionBarTitle(activity.getString(R.string.loading));
+
+            // Disable all existing preferences while loading
+            for (int i = 0; i < prefScreen.getPreferenceCount(); i++) {
+                prefScreen.getPreference(i).setEnabled(false);
+            }
+
+            return;
+        }
+
+        prefScreen.removeAll();
+
+        if (debug) {
+            final Preference pref = new PreferenceCategory(activity);
+            pref.setIconSpaceReserved(false);
+            pref.setTitle("Screen ID: " + screenId);
+            pref.setPersistent(false);
+            pref.setKey("rt_pref_header_" + screenId);
+            prefScreen.addPreference(pref);
+        }
+
+        // Update the screen title, if any
+        if (screenDefinition.hasTitle()) {
+            final String title = screenDefinition.getTitle().getText();
+
+            ((GarminRealtimeSettingsActivity) activity).setActionBarTitle(title);
+        }
+
+        final Map<Integer, GdiSettingsService.EntryState> stateById = new HashMap<>();
+        for (final GdiSettingsService.EntryState state : screenState.getStateList()) {
+            stateById.put(state.getId(), state);
+        }
+
+        for (final GdiSettingsService.ScreenEntry entry : screenDefinition.getEntryList()) {
+            final GdiSettingsService.EntryState state = stateById.get(entry.getId());
+
+            final Preference pref;
+            boolean supported = true;
+
+            if (entry.hasTarget()) {
+                switch (entry.getTarget().getType()) {
+                    case 0: // subscreen
+                    case 9: // subscreen with options for a specific preference
+                        pref = new Preference(activity);
+                        pref.setOnPreferenceClickListener(preference -> {
+                            final Intent newIntent = new Intent(requireContext(), GarminRealtimeSettingsActivity.class);
+                            newIntent.putExtra(GBDevice.EXTRA_DEVICE, device);
+                            newIntent.putExtra(GarminRealtimeSettingsActivity.EXTRA_SCREEN_ID, entry.getTarget().getSubscreen());
+                            activity.startActivityForResult(newIntent, 0);
+                            return true;
+                        });
+                        break;
+                    case 1: // list preference
+                        pref = new ListPreference(activity);
+                        final CharSequence[] entries = new String[entry.getTarget().getOptions().getOptionList().size()];
+                        int optionIndex = 0;
+                        for (final GdiSettingsService.TargetOptionEntry option : entry.getTarget().getOptions().getOptionList()) {
+                            entries[optionIndex++] = option.getTitle().getText();
+                        }
+                        final ListPreference listPreference = (ListPreference) pref;
+                        listPreference.setEntries(entries);
+                        listPreference.setEntryValues(entries);
+                        listPreference.setValue(entries[Objects.requireNonNull(state).getSummary().getValueList().getIndex()].toString());
+                        listPreference.setOnPreferenceChangeListener((preference, newValue) -> {
+                            int newValueIdx = -1;
+                            for (int i = 0; i < entries.length; i++) {
+                                if (entries[i].equals(newValue.toString())) {
+                                    newValueIdx = i;
+                                    break;
+                                }
+                            }
+                            if (newValueIdx < 0) {
+                                LOG.error("Failed to find index for {}", newValue);
+                                return false;
+                            }
+
+                            pref.setEnabled(false);
+                            sendChangeRequest(
+                                    GdiSettingsService.ChangeRequest.newBuilder()
+                                            .setScreenId(screenId)
+                                            .setEntryId(entry.getId())
+                                            .setOption(GdiSettingsService.ChangeRequest.Option.newBuilder()
+                                                    .setIndex(newValueIdx)
+                                            )
+                            );
+                            return true;
+                        });
+                        break;
+
+                    case 3: // time
+                        pref = new XTimePreference(activity, null);
+                        ((XTimePreference) pref).setValue(
+                                Objects.requireNonNull(state).getSummary().getValueTime().getSeconds() / 3600,
+                                (Objects.requireNonNull(state).getSummary().getValueTime().getSeconds() % 3600) / 60
+                        );
+                        if (state.getSummary().getValueTime().hasTimeFormat()) {
+                            final int timeFormat = state.getSummary().getValueTime().getTimeFormat();
+                            switch (timeFormat) {
+                                case 0: // 12h
+                                    ((XTimePreference) pref).setFormat(XTimePreference.Format.FORMAT_12H);
+                                    break;
+                                case 1: // 24h
+                                    ((XTimePreference) pref).setFormat(XTimePreference.Format.FORMAT_24H);
+                                    break;
+                            }
+                        }
+                        pref.setSummary(state.getSummary().getValueDate().getSubtitle().getText());
+                        pref.setOnPreferenceChangeListener((preference, newValue) -> {
+                            final String[] pieces = newValue.toString().split(":");
+
+                            final int hour = Integer.parseInt(pieces[0]);
+                            final int minute = Integer.parseInt(pieces[1]);
+
+                            pref.setEnabled(false);
+                            sendChangeRequest(
+                                    GdiSettingsService.ChangeRequest.newBuilder()
+                                            .setScreenId(screenId)
+                                            .setEntryId(entry.getId())
+                                            .setTime(GdiSettingsService.ChangeRequest.Time.newBuilder()
+                                                    .setSeconds(hour * 3600 + minute * 60)
+                                            )
+                            );
+                            return true;
+                        });
+                        break;
+                    case 5: // number picker
+                        pref = new EditTextPreference(activity);
+                        ((EditTextPreference) pref).setText(String.valueOf(state.getSummary().getValueNumber().getValue()));
+                        ((EditTextPreference) pref).setSummary(state.getSummary().getValueNumber().getSubtitle().getText());
+
+                        ((EditTextPreference) pref).setOnBindEditTextListener(p -> {
+                            p.setInputType(InputType.TYPE_CLASS_NUMBER);
+                            int minValue = Integer.MIN_VALUE;
+                            int maxValue = Integer.MAX_VALUE;
+                            if (entry.getTarget().getNumberPicker().hasMin()) {
+                                minValue = entry.getTarget().getNumberPicker().getMin();
+                            }
+                            if (entry.getTarget().getNumberPicker().hasMax()) {
+                                maxValue = entry.getTarget().getNumberPicker().getMax();
+                            }
+                            p.addTextChangedListener(new MinMaxTextWatcher(p, minValue, maxValue));
+                            p.setSelection(p.getText().length());
+                        });
+                        ((EditTextPreference) pref).setOnPreferenceChangeListener((preference, newValue) -> {
+                            final int newValueInt = Integer.parseInt(newValue.toString());
+
+                            pref.setEnabled(false);
+                            sendChangeRequest(
+                                    GdiSettingsService.ChangeRequest.newBuilder()
+                                            .setScreenId(screenId)
+                                            .setEntryId(entry.getId())
+                                            .setNumber(GdiSettingsService.ChangeRequest.Number.newBuilder()
+                                                    .setValue(newValueInt)
+                                            )
+                            );
+                            return true;
+                        });
+                        break;
+                    case 6: // activity
+                        switch (entry.getTarget().getActivity()) {
+                            case 2: // garmin pay
+                            case 7: // text responses
+                            case 8: // music providers
+                            case 17: // Solar Intensity
+                            case 29: // Set Up ECG App
+                            case 30: // ECG
+                                pref = new Preference(activity);
+                                pref.setVisible(debug);
+                                pref.setEnabled(false);
+                                break;
+                            default:
+                                supported = false;
+                                pref = new Preference(activity);
+                                break;
+                        }
+
+                        break;
+                    case 7: // hidden?
+                        pref = new Preference(activity);
+                        pref.setVisible(debug);
+                        pref.setEnabled(false);
+                        break;
+                    case 10: // date picker
+                        pref = new XDatePreference(activity, null);
+                        ((XDatePreference) pref).setValue(
+                                Objects.requireNonNull(state).getSummary().getValueDate().getCurrentDate().getYear(),
+                                Objects.requireNonNull(state).getSummary().getValueDate().getCurrentDate().getMonth(),
+                                Objects.requireNonNull(state).getSummary().getValueDate().getCurrentDate().getDay()
+                        );
+                        if (state.getSummary().getValueDate().hasMinDate()) {
+                            final Calendar calendar = GregorianCalendar.getInstance();
+                            calendar.set(
+                                    state.getSummary().getValueDate().getMinDate().getYear(),
+                                    state.getSummary().getValueDate().getMinDate().getMonth() - 1,
+                                    state.getSummary().getValueDate().getMinDate().getDay()
+                            );
+                            ((XDatePreference) pref).setMinDate(calendar.getTimeInMillis());
+                        }
+                        if (state.getSummary().getValueDate().hasMaxDate()) {
+                            final Calendar calendar = GregorianCalendar.getInstance();
+                            calendar.set(
+                                    state.getSummary().getValueDate().getMaxDate().getYear(),
+                                    state.getSummary().getValueDate().getMaxDate().getMonth() - 1,
+                                    state.getSummary().getValueDate().getMaxDate().getDay()
+                            );
+                            ((XDatePreference) pref).setMaxDate(calendar.getTimeInMillis());
+                        }
+                        pref.setSummary(state.getSummary().getValueDate().getSubtitle().getText());
+                        pref.setOnPreferenceChangeListener((preference, newValue) -> {
+                            final String[] pieces = newValue.toString().split("-");
+
+                            final int year = Integer.parseInt(pieces[0]);
+                            final int month = Integer.parseInt(pieces[1]);
+                            final int day = Integer.parseInt(pieces[2]);
+
+                            pref.setEnabled(false);
+                            sendChangeRequest(
+                                    GdiSettingsService.ChangeRequest.newBuilder()
+                                            .setScreenId(screenId)
+                                            .setEntryId(entry.getId())
+                                            .setNewDate(GdiSettingsService.ChangeRequest.NewDate.newBuilder()
+                                                    .setValue(GdiSettingsService.Date.newBuilder()
+                                                            .setYear(year).setMonth(month).setDay(day)
+                                                    )
+                                            )
+                            );
+
+                            return true;
+                        });
+                        break;
+                    case 12: // Connect IQ Store
+                        pref = new Preference(activity);
+                        pref.setVisible(debug);
+                        pref.setEnabled(false);
+                        break;
+                    case 13: // height
+                        pref = new EditTextPreference(activity);
+                        ((EditTextPreference) pref).setText(String.valueOf(state.getSummary().getValueHeight().getValue()));
+                        ((EditTextPreference) pref).setSummary(state.getSummary().getValueHeight().getSubtitle().getText());
+                        if (state.getSummary().getValueHeight().getUnit() == 0) {
+                            ((EditTextPreference) pref).setDialogTitle(R.string.activity_prefs_height_cm);
+                            ((EditTextPreference) pref).setTitle(R.string.activity_prefs_height_cm);
+                        } else {
+                            ((EditTextPreference) pref).setDialogTitle(R.string.activity_prefs_height_inches);
+                            ((EditTextPreference) pref).setTitle(R.string.activity_prefs_height_inches);
+                        }
+                        ((EditTextPreference) pref).setOnBindEditTextListener(p -> {
+                            p.setInputType(InputType.TYPE_CLASS_NUMBER);
+                            p.addTextChangedListener(new MinMaxTextWatcher(p, 0, 300));
+                            p.setSelection(p.getText().length());
+                        });
+                        ((EditTextPreference) pref).setOnPreferenceChangeListener((preference, newValue) -> {
+                            final int newValueInt = Integer.parseInt(newValue.toString());
+
+                            pref.setEnabled(false);
+                            sendChangeRequest(
+                                    GdiSettingsService.ChangeRequest.newBuilder()
+                                            .setScreenId(screenId)
+                                            .setEntryId(entry.getId())
+                                            .setHeight(GdiSettingsService.ChangeRequest.Height.newBuilder()
+                                                    .setValue(newValueInt)
+                                                    .setUnit(state.getSummary().getValueHeight().getUnit())
+                                            )
+                            );
+                            return true;
+                        });
+                        break;
+                    default:
+                        supported = false;
+                        pref = new Preference(activity);
+                }
+            } else { // No target
+                switch (entry.getType()) {
+                    case 0: // notice
+                        pref = new Preference(activity);
+                        pref.setSummary(entry.getTitle().getText());
+                        break;
+                    case 1: // category
+                        pref = new PreferenceCategory(activity);
+                        break;
+                    case 2: // space
+                        pref = new PreferenceCategory(activity);
+                        pref.setTitle("");
+                        break;
+                    case 3: // switch
+                        pref = new SwitchPreferenceCompat(activity);
+                        pref.setLayoutResource(R.layout.preference_checkbox);
+                        ((SwitchPreferenceCompat) pref).setChecked(Objects.requireNonNull(state).getSwitch().getEnabled());
+                        ((SwitchPreferenceCompat) pref).setSummary(Objects.requireNonNull(state).getSwitch().getTitle().getText());
+                        pref.setOnPreferenceChangeListener((preference, newValue) -> {
+                            pref.setEnabled(false);
+                            sendChangeRequest(
+                                    GdiSettingsService.ChangeRequest.newBuilder()
+                                            .setScreenId(screenId)
+                                            .setEntryId(entry.getId())
+                                            .setSwitch(GdiSettingsService.ChangeRequest.Switch.newBuilder()
+                                                    .setValue((Boolean) newValue)
+                                            )
+                            );
+                            return true;
+                        });
+
+                        break;
+                    case 4: // single line + optional icon
+                    case 5: // double line
+                        pref = new Preference(activity);
+                        break;
+                    case 18: // single line with action (eg. glances)
+                    case 7: // single line, normally in list for selection?
+                        pref = new Preference(activity);
+                        pref.setOnPreferenceClickListener(preference -> {
+                            pref.setEnabled(false);
+                            sendChangeRequest(
+                                    GdiSettingsService.ChangeRequest.newBuilder()
+                                            .setScreenId(screenId)
+                                            .setEntryId(entry.getId())
+                            );
+                            return true;
+                        });
+                        break;
+                    case 8: // device + status?
+                    case 9: // finish setup
+                    case 10: // find my device
+                    case 11: // preferred activity tracker
+                    case 13: // help & info
+                    case 24: // available accessories?
+                        pref = new Preference(activity);
+                        pref.setVisible(debug);
+                        pref.setEnabled(false);
+                        break;
+                    case 15: // sortable + delete
+                        // Add all sortable items and then continue
+                        final String moveUpStr = activity.getString(R.string.widget_move_up);
+                        final String moveDownStr = activity.getString(R.string.widget_move_down);
+                        final String deleteStr = activity.getString(R.string.appmananger_app_delete);
+
+                        for (int i = 0; i < entry.getSortOptions().getEntriesCount(); i++) {
+                            final GdiSettingsService.SortEntry sortEntry = entry.getSortOptions().getEntries(i);
+                            final List<String> sortableOptions = new ArrayList<>(3);
+                            if (i > 0) {
+                                sortableOptions.add(moveUpStr);
+                            }
+                            if (i < entry.getSortOptions().getEntriesCount() - 1) {
+                                sortableOptions.add(moveDownStr);
+                            }
+                            sortableOptions.add(deleteStr);
+                            final ArrayAdapter<String> sortOptionsAdapter = new ArrayAdapter<>(activity, android.R.layout.simple_list_item_1, sortableOptions);
+                            final int iFinal = i;
+                            final Preference sortPref = new Preference(activity);
+                            sortPref.setTitle(sortEntry.getTitle().getText());
+                            sortPref.setPersistent(false);
+                            sortPref.setIconSpaceReserved(false);
+                            sortPref.setKey("rt_pref_" + screenId + "_" + entry.getId() + "__" + sortEntry.getId());
+                            sortPref.setOnPreferenceClickListener(preference -> {
+                                new MaterialAlertDialogBuilder(activity)
+                                        .setTitle(sortPref.getTitle())
+                                        .setAdapter(sortOptionsAdapter, (dialogInterface, j) -> {
+                                            final String option = sortableOptions.get(j);
+                                            int moveOffset = 0;
+                                            if (option.equals(moveUpStr)) {
+                                                moveOffset = -1;
+                                            } else if (option.equals(moveDownStr)) {
+                                                moveOffset = 1;
+                                            }
+
+                                            if (moveOffset != 0) {
+                                                sortPref.setEnabled(false);
+                                                sendChangeRequest(
+                                                        GdiSettingsService.ChangeRequest.newBuilder()
+                                                                .setScreenId(screenId)
+                                                                .setEntryId(sortEntry.getId())
+                                                                .setPosition(GdiSettingsService.ChangeRequest.Position.newBuilder()
+                                                                        .setIndex(iFinal + moveOffset)
+                                                                )
+                                                );
+                                                return;
+                                            }
+
+                                            if (option.equals(deleteStr)) {
+                                                sortPref.setEnabled(false);
+                                                sendChangeRequest(
+                                                        GdiSettingsService.ChangeRequest.newBuilder()
+                                                                .setScreenId(screenId)
+                                                                .setEntryId(sortEntry.getId())
+                                                                .setPosition(GdiSettingsService.ChangeRequest.Position.newBuilder()
+                                                                        .setDelete(true)
+                                                                )
+                                                );
+                                            }
+                                        }).setNegativeButton(android.R.string.cancel, null)
+                                        .create().show();
+                                return true;
+                            });
+                            prefScreen.addPreference(sortPref);
+                        }
+
+                        continue; // We already added all options above, continue
+                    case 16: // text
+                        pref = new EditTextPreference(activity);
+
+                        ((EditTextPreference) pref).setOnBindEditTextListener(p -> {
+                            int maxValue = Integer.MAX_VALUE;
+                            if (entry.getTextOption().hasLimits() && entry.getTextOption().getLimits().hasMaxLength()) {
+                                p.setFilters(new InputFilter[]{new InputFilter.LengthFilter(entry.getTextOption().getLimits().getMaxLength())});
+                            }
+                            p.setSelection(p.getText().length());
+                        });
+                        ((EditTextPreference) pref).setOnPreferenceChangeListener((preference, newValue) -> {
+                            if (StringUtils.isNullOrEmpty(newValue.toString())) {
+                                return true;
+                            }
+                            pref.setEnabled(false);
+                            sendChangeRequest(
+                                    GdiSettingsService.ChangeRequest.newBuilder()
+                                            .setScreenId(screenId)
+                                            .setEntryId(entry.getId())
+                                            .setText(GdiSettingsService.ChangeRequest.Text.newBuilder()
+                                                    .setValue(newValue.toString())
+                                            )
+                            );
+                            return true;
+                        });
+                        break;
+                    default:
+                        supported = false;
+                        pref = new Preference(activity);
+                }
+            }
+
+            if (StringUtils.isNullOrEmpty(pref.getTitle()) && entry.getType() != 0 && entry.getType() != 2) {
+                pref.setTitle(!StringUtils.isEmpty(entry.getTitle().getText()) ? entry.getTitle().getText() : activity.getString(R.string.unknown));
+
+                if (pref instanceof DialogPreference) {
+                    ((DialogPreference) pref).setDialogTitle(pref.getTitle());
+                }
+            }
+
+            final int icon = getIcon(entry);
+            if (icon != 0) {
+                pref.setIcon(icon);
+            } else {
+                pref.setIconSpaceReserved(false);
+            }
+
+            if (state != null && !StringUtils.isEmpty(state.getSummary().getTitle().getText())) {
+                pref.setSummary(state.getSummary().getTitle().getText());
+            }
+
+            if (state != null && state.hasState()) {
+                switch (state.getState()) {
+                    case 1:
+                        pref.setVisible(false);
+                        break;
+                    case 2:
+                        pref.setEnabled(false);
+                        break;
+                    default:
+                        LOG.warn("Unknown state value {}", state.getState());
+                }
+            }
+
+            if (!supported) {
+                pref.setEnabled(false);
+
+                if (StringUtils.isNullOrEmpty(pref.getSummary())) {
+                    pref.setSummary(R.string.unsupported);
+                } else {
+                    pref.setSummary(activity.getString(R.string.menuitem_unsupported, pref.getSummary()));
+                }
+            }
+
+            if (debug) {
+                final StringBuilder sb = new StringBuilder();
+
+                if (pref.getSummary() != null && pref.getSummary().length() != 0) {
+                    sb.append(pref.getSummary()).append("\n");
+                }
+
+                sb.append("id=").append(entry.getId());
+                sb.append(", type=").append(entry.getType());
+
+                if (icon == 0 && entry.hasIcon()) {
+                    sb.append(", icon=").append(entry.getIcon());
+                }
+
+                if (entry.hasTarget()) {
+                    sb.append(", targetType=").append(entry.getTarget().getType());
+                    if (entry.getTarget().hasActivity()) {
+                        sb.append(", targetActivity=").append(entry.getTarget().getActivity());
+                    }
+                }
+
+                pref.setSummary(sb.toString());
+            }
+
+            pref.setPersistent(false);
+            pref.setKey("rt_pref_" + screenId + "_" + entry.getId());
+            prefScreen.addPreference(pref);
+        }
+
+        // If no preferences after the last visible preference category are visible, hide it
+        boolean previousWasVisible = false;
+        PreferenceCategory lastSeenCategory = null;
+        for (int i = prefScreen.getPreferenceCount() - 1; i >= 0; i--) {
+            final Preference pref = prefScreen.getPreference(i);
+            if (pref instanceof PreferenceCategory) {
+                lastSeenCategory = (PreferenceCategory) pref;
+
+                if (!previousWasVisible) {
+                    lastSeenCategory.setVisible(false);
+                }
+
+                previousWasVisible = false;
+            } else {
+                previousWasVisible |= pref.isVisible();
+            }
+        }
+
+        if (!previousWasVisible && lastSeenCategory != null) {
+            lastSeenCategory.setVisible(false);
+        }
+    }
+
+    @DrawableRes
+    private int getIcon(final GdiSettingsService.ScreenEntry entry) {
+        if (entry.hasIcon()) {
+            switch (entry.getIcon()) {
+                //
+                // Main menu
+                case 20: // Garmin Pay
+                    return 0;
+                case 21: // Text Responses
+                    return R.drawable.ic_reply;
+                case 4: // Clocks
+                    return R.drawable.ic_access_time;
+                case 2: // Glances
+                    return R.drawable.ic_widgets;
+                case 3: // Controls
+                    return R.drawable.ic_menu;
+                case 1: // Activities / Apps, have the same icon
+                    return R.drawable.ic_activity_unknown_small;
+                case 39: // Shortcut
+                    return R.drawable.ic_shortcut;
+                case 27: // Notifications & Alerts
+                    return R.drawable.ic_notifications;
+                case 30: // Wrist heart rate frequency
+                    return R.drawable.ic_heartrate;
+                case 38: // Alarms
+                    return R.drawable.ic_access_alarms;
+                case 5: // Sensors & accessories
+                case 46: // Watch Sensors
+                    return R.drawable.ic_sensor_calibration;
+                case 47: // Accessories
+                    return R.drawable.ic_bluetooth_searching;
+                case 6: // Map
+                    return R.drawable.ic_map;
+                case 7: // Music
+                    return R.drawable.ic_music_note;
+                case 8: // Phone
+                    return R.drawable.ic_phone;
+                case 11: // Connectivity
+                    return R.drawable.ic_bluetooth_searching;
+                case 13: // Audio Prompts
+                case 60: // Sound & Vibe
+                    return R.drawable.ic_volume_up;
+                case 61: // Display & Brightness
+                    return R.drawable.ic_wb_sunny;
+                case 62: // Focus Modes
+                    return R.drawable.ic_focus;
+                case 14: // User Profile
+                    return R.drawable.ic_person;
+                case 15: // Safety & Tracking
+                    return R.drawable.ic_emergency;
+                case 16: // Activity Tracking
+                    return R.drawable.ic_activity_unknown_small;
+                case 17: // Navigation
+                    return R.drawable.ic_navigation;
+                case 18: // Power manager
+                    return R.drawable.ic_battery;
+                case 19: // System
+                    return R.drawable.ic_settings;
+                case 26: // Appearance
+                    return R.drawable.ic_paint;
+                case 44: // Health & wellness
+                    return R.drawable.ic_health;
+
+                //
+                // Sortable screens (glances, apps, etc)
+                case 33:
+                    return R.drawable.ic_add_gray;
+            }
+        }
+
+        return 0;
+    }
+
+    void toggleDebug() {
+        final Prefs prefs = GBApplication.getDevicePrefs(device);
+        prefs.getPreferences().edit()
+                .putBoolean(PREF_DEBUG, !prefs.getBoolean(PREF_DEBUG, BuildConfig.DEBUG))
+                .apply();
+
+        reload();
+    }
+
+    void shareDebug() {
+        final Intent intent = new Intent(android.content.Intent.ACTION_SEND);
+        intent.setType("text/plain");
+
+        final StringBuilder sb = new StringBuilder();
+
+        sb.append("screenId: ").append(screenId);
+        sb.append("\n");
+
+        sb.append("settingsScreen: ");
+        if (screenDefinition != null) {
+            sb.append(GB.hexdump(screenDefinition.toByteArray()));
+        } else {
+            sb.append("null");
+        }
+        sb.append("\n");
+
+        sb.append("settingsState: ");
+        if (screenState != null) {
+            sb.append(GB.hexdump(screenState.toByteArray()));
+        } else {
+            sb.append("null");
+        }
+        sb.append("\n");
+
+        intent.putExtra(android.content.Intent.EXTRA_SUBJECT, "Garmin Settings Screen " + screenId);
+        intent.putExtra(android.content.Intent.EXTRA_TEXT, sb.toString());
+
+        try {
+            startActivity(Intent.createChooser(intent, "Share debug info"));
+        } catch (final ActivityNotFoundException e) {
+            Toast.makeText(requireContext(), "Failed to share text", Toast.LENGTH_LONG).show();
+        }
+    }
+
+    private void sendChangeRequest(final GdiSettingsService.ChangeRequest.Builder changeRequest) {
+        screenDefinition = null;
+        screenState = null;
+        final GdiSmartProto.Smart smart = GdiSmartProto.Smart.newBuilder()
+                .setSettingsService(GdiSettingsService.SettingsService.newBuilder()
+                        .setChangeRequest(changeRequest)
+                ).build();
+        GBApplication.deviceService(device).onSendConfiguration("protobuf:" + GB.hexdump(smart.toByteArray()));
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminRespiratoryRateSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminRespiratoryRateSampleProvider.java
new file mode 100644
index 0000000..eca393f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminRespiratoryRateSampleProvider.java
@@ -0,0 +1,56 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.garmin;
+
+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.GarminRespiratoryRateSample;
+import nodomain.freeyourgadget.gadgetbridge.entities.GarminRespiratoryRateSampleDao;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class GarminRespiratoryRateSampleProvider extends AbstractTimeSampleProvider<GarminRespiratoryRateSample> {
+    public GarminRespiratoryRateSampleProvider(final GBDevice device, final DaoSession session) {
+        super(device, session);
+    }
+
+    @NonNull
+    @Override
+    public AbstractDao<GarminRespiratoryRateSample, ?> getSampleDao() {
+        return getSession().getGarminRespiratoryRateSampleDao();
+    }
+
+    @NonNull
+    @Override
+    protected Property getTimestampSampleProperty() {
+        return GarminRespiratoryRateSampleDao.Properties.Timestamp;
+    }
+
+    @NonNull
+    @Override
+    protected Property getDeviceIdentifierSampleProperty() {
+        return GarminRespiratoryRateSampleDao.Properties.DeviceId;
+    }
+
+    @Override
+    public GarminRespiratoryRateSample createSample() {
+        return new GarminRespiratoryRateSample();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminRestingMetabolicRateSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminRestingMetabolicRateSampleProvider.java
new file mode 100644
index 0000000..969f6e4
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminRestingMetabolicRateSampleProvider.java
@@ -0,0 +1,56 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.garmin;
+
+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.GarminRestingMetabolicRateSample;
+import nodomain.freeyourgadget.gadgetbridge.entities.GarminRestingMetabolicRateSampleDao;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class GarminRestingMetabolicRateSampleProvider extends AbstractTimeSampleProvider<GarminRestingMetabolicRateSample> {
+    public GarminRestingMetabolicRateSampleProvider(final GBDevice device, final DaoSession session) {
+        super(device, session);
+    }
+
+    @NonNull
+    @Override
+    public AbstractDao<GarminRestingMetabolicRateSample, ?> getSampleDao() {
+        return getSession().getGarminRestingMetabolicRateSampleDao();
+    }
+
+    @NonNull
+    @Override
+    protected Property getTimestampSampleProperty() {
+        return GarminRestingMetabolicRateSampleDao.Properties.Timestamp;
+    }
+
+    @NonNull
+    @Override
+    protected Property getDeviceIdentifierSampleProperty() {
+        return GarminRestingMetabolicRateSampleDao.Properties.DeviceId;
+    }
+
+    @Override
+    public GarminRestingMetabolicRateSample createSample() {
+        return new GarminRestingMetabolicRateSample();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminSettingsCustomizer.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminSettingsCustomizer.java
new file mode 100644
index 0000000..71a1eab
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminSettingsCustomizer.java
@@ -0,0 +1,262 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.garmin;
+
+import static nodomain.freeyourgadget.gadgetbridge.util.GB.toast;
+
+import android.content.ClipData;
+import android.content.ClipboardManager;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Parcel;
+import android.widget.Toast;
+
+import androidx.activity.result.ActivityResultLauncher;
+import androidx.activity.result.contract.ActivityResultContracts;
+import androidx.annotation.NonNull;
+import androidx.documentfile.provider.DocumentFile;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceCategory;
+
+import com.google.android.material.dialog.MaterialAlertDialogBuilder;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.text.SimpleDateFormat;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsHandler;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.agps.GarminAgpsStatus;
+import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+public class GarminSettingsCustomizer implements DeviceSpecificSettingsCustomizer {
+    private static final Logger LOG = LoggerFactory.getLogger(GarminSettingsCustomizer.class);
+
+    private final SimpleDateFormat SDF = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault());
+
+    @Override
+    public void onPreferenceChange(final Preference preference, final DeviceSpecificSettingsHandler handler) {
+    }
+
+    @Override
+    public void customizeSettings(final DeviceSpecificSettingsHandler handler, final Prefs prefs, final String rootKey) {
+        final Preference realtimeSettings = handler.findPreference(GarminPreferences.PREF_GARMIN_REALTIME_SETTINGS);
+        if (realtimeSettings != null) {
+            realtimeSettings.setOnPreferenceClickListener(preference -> {
+                final Intent intent = new Intent(handler.getContext(), GarminRealtimeSettingsActivity.class);
+                intent.putExtra(GBDevice.EXTRA_DEVICE, handler.getDevice());
+                handler.getContext().startActivity(intent);
+                return true;
+            });
+        }
+
+        final PreferenceCategory prefAgpsHeader = handler.findPreference(DeviceSettingsPreferenceConst.PREF_HEADER_AGPS);
+        if (prefAgpsHeader != null) {
+            final List<String> urls = prefs.getList(GarminPreferences.PREF_AGPS_KNOWN_URLS, Collections.emptyList(), "\n");
+            if (urls.isEmpty()) {
+                return;
+            }
+
+            final String currentFolder = prefs.getString(GarminPreferences.PREF_GARMIN_AGPS_FOLDER, "");
+
+            final Preference prefFolder = handler.findPreference(GarminPreferences.PREF_GARMIN_AGPS_FOLDER);
+            final ActivityResultLauncher<Uri> agpsFolderChooser = handler.registerForActivityResult(
+                    new ActivityResultContracts.OpenDocumentTree(),
+                    localUri -> {
+                        LOG.info("Garmin agps folder: {}", localUri);
+                        if (localUri != null) {
+                            handler.getContext().getContentResolver().takePersistableUriPermission(localUri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
+                            prefs.getPreferences().edit()
+                                    .putString(GarminPreferences.PREF_GARMIN_AGPS_FOLDER, localUri.toString())
+                                    .apply();
+                            prefFolder.setSummary(localUri.toString());
+
+                            for (final String url : urls) {
+                                updateAgpsStatus(handler, prefs, url);
+                            }
+                        }
+                    }
+            );
+            prefFolder.setOnPreferenceClickListener(preference -> {
+                agpsFolderChooser.launch(null);
+                return true;
+            });
+            prefFolder.setSummary(currentFolder);
+            prefAgpsHeader.addPreference(prefFolder);
+
+            int i = 0;
+            for (final String url : urls) {
+                i++;
+
+                final Preference prefHeader = new PreferenceCategory(handler.getContext());
+                prefHeader.setKey("pref_agps_url_header_" + i);
+                prefHeader.setIconSpaceReserved(false);
+                prefHeader.setTitle(handler.getContext().getString(R.string.garmin_agps_url_i, i));
+                prefAgpsHeader.addPreference(prefHeader);
+
+                final Preference prefUrl = new Preference(handler.getContext());
+                prefUrl.setOnPreferenceClickListener(preference -> {
+                    final ClipboardManager clipboard = (ClipboardManager) handler.getContext().getSystemService(Context.CLIPBOARD_SERVICE);
+                    final ClipData clip = ClipData.newPlainText(handler.getContext().getString(R.string.url), url);
+                    clipboard.setPrimaryClip(clip);
+                    toast(handler.getContext(), handler.getContext().getString(R.string.copied_to_clipboard), Toast.LENGTH_SHORT, GB.INFO);
+                    return true;
+                });
+                prefUrl.setKey("pref_garmin_agps_url_" + i);
+                prefUrl.setIcon(R.drawable.ic_link);
+                prefUrl.setTitle(R.string.url);
+                prefUrl.setSummary(url);
+                prefAgpsHeader.addPreference(prefUrl);
+
+                final Preference prefLocalFile = new Preference(handler.getContext());
+                prefLocalFile.setOnPreferenceClickListener(preference -> {
+                    selectAgpsFile(handler, prefs, url, prefLocalFile);
+                    return true;
+                });
+                prefLocalFile.setKey(GarminPreferences.agpsFilename(url));
+                prefLocalFile.setIcon(R.drawable.ic_file_open);
+                prefLocalFile.setTitle(R.string.garmin_agps_local_file);
+                prefLocalFile.setSummary(prefs.getString(GarminPreferences.agpsFilename(url), ""));
+                prefAgpsHeader.addPreference(prefLocalFile);
+
+                final Preference prefStatus = new Preference(handler.getContext());
+                prefStatus.setKey(GarminPreferences.agpsStatus(url));
+                prefStatus.setIcon(R.drawable.ic_health);
+                prefStatus.setTitle(R.string.status);
+                prefAgpsHeader.addPreference(prefStatus);
+                updateAgpsStatus(handler, prefs, url);
+
+                final Preference prefUpdateTime = new Preference(handler.getContext());
+                prefUpdateTime.setKey(GarminPreferences.agpsUpdateTime(url));
+                prefUpdateTime.setIcon(R.drawable.ic_calendar_today);
+                prefUpdateTime.setTitle(R.string.pref_agps_update_time);
+                final long ts = prefs.getLong(GarminPreferences.agpsUpdateTime(url), 0L);
+                if (ts > 0) {
+                    prefUpdateTime.setSummary(String.format("%s (%s)",
+                            SDF.format(new Date(ts)),
+                            DateTimeUtils.formatDurationHoursMinutes(System.currentTimeMillis() - ts, TimeUnit.MILLISECONDS)
+                    ));
+                } else {
+                    prefUpdateTime.setSummary(handler.getContext().getString(R.string.unknown));
+                }
+                prefAgpsHeader.addPreference(prefUpdateTime);
+            }
+        }
+    }
+
+    private void selectAgpsFile(final DeviceSpecificSettingsHandler handler, final Prefs prefs, final String url, final Preference prefLocalFile) {
+        final String currentFolder = prefs.getString(GarminPreferences.PREF_GARMIN_AGPS_FOLDER, "");
+
+        final String folderUri = prefs.getString(GarminPreferences.PREF_GARMIN_AGPS_FOLDER, "");
+        if (folderUri.isEmpty()) {
+            GB.toast(handler.getContext().getString(R.string.no_folder_selected), Toast.LENGTH_SHORT, GB.INFO);
+            return;
+        }
+
+        final DocumentFile folder = DocumentFile.fromTreeUri(handler.getContext(), Uri.parse(currentFolder));
+        if (folder == null || folder.listFiles().length == 0) {
+            GB.toast(handler.getContext().getString(R.string.folder_is_empty), Toast.LENGTH_SHORT, GB.INFO);
+            return;
+        }
+
+        final DocumentFile[] documentFiles = folder.listFiles();
+        final String[] files = new String[documentFiles.length + 1];
+        files[0] = handler.getContext().getString(R.string.none);
+        final String selectedFile = prefs.getString(GarminPreferences.agpsFilename(url), "");
+        int checkedItem = 0;
+        for (int j = 0; j < documentFiles.length; j++) {
+            files[j + 1] = documentFiles[j].getName();
+            if (selectedFile.equals(files[j + 1])) {
+                checkedItem = j + 1;
+            }
+        }
+
+        final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(handler.getContext());
+        builder.setTitle(R.string.garmin_agps_local_file);
+
+        final AtomicInteger selectedIdx = new AtomicInteger(0);
+        builder.setSingleChoiceItems(files, checkedItem, (dialog, which) -> {
+            selectedIdx.set(which);
+        });
+        builder.setPositiveButton(android.R.string.ok, (dialog, which) -> {
+            final String selectedFilename = selectedIdx.get() > 0 ? files[selectedIdx.get()] : null;
+            prefs.getPreferences().edit()
+                    .putString(GarminPreferences.agpsFilename(url), selectedFilename)
+                    .apply();
+            prefLocalFile.setSummary(selectedFilename);
+            updateAgpsStatus(handler, prefs, url);
+        });
+        builder.setNegativeButton(android.R.string.cancel, null);
+        builder.create().show();
+    }
+
+    private void updateAgpsStatus(final DeviceSpecificSettingsHandler handler, final Prefs prefs, final String url) {
+        final Preference prefStatus = handler.findPreference(GarminPreferences.agpsStatus(url));
+
+        final String filename = prefs.getString(GarminPreferences.agpsFilename(url), "");
+        if (filename.isEmpty()) {
+            prefStatus.setSummary("");
+            return;
+        }
+        final String folderUri = prefs.getString(GarminPreferences.PREF_GARMIN_AGPS_FOLDER, "");
+        if (folderUri.isEmpty()) {
+            prefStatus.setSummary("");
+            return;
+        }
+        final DocumentFile folder = DocumentFile.fromTreeUri(handler.getContext(), Uri.parse(folderUri));
+        if (folder == null) {
+            prefStatus.setSummary("");
+            return;
+        }
+        final GarminAgpsStatus agpsStatus;
+        final DocumentFile localFile = folder.findFile(filename);
+        if (localFile != null && localFile.isFile() && localFile.canRead()) {
+            if (localFile.lastModified() < prefs.getLong(GarminPreferences.agpsUpdateTime(url), 0L)) {
+                agpsStatus = GarminAgpsStatus.CURRENT;
+            } else {
+                agpsStatus = GarminAgpsStatus.PENDING;
+            }
+        } else {
+            agpsStatus = GarminAgpsStatus.MISSING;
+        }
+        prefStatus.setSummary(handler.getContext().getString(agpsStatus.getText()));
+    }
+
+    @Override
+    public Set<String> getPreferenceKeysWithSummary() {
+        return Collections.emptySet();
+    }
+
+    public static final Creator<GarminSettingsCustomizer> CREATOR = new Creator<GarminSettingsCustomizer>() {
+        @Override
+        public GarminSettingsCustomizer createFromParcel(final Parcel in) {
+            return new GarminSettingsCustomizer();
+        }
+
+        @Override
+        public GarminSettingsCustomizer[] newArray(final int size) {
+            return new GarminSettingsCustomizer[size];
+        }
+    };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminSleepStageSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminSleepStageSampleProvider.java
new file mode 100644
index 0000000..bf34a50
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminSleepStageSampleProvider.java
@@ -0,0 +1,56 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.garmin;
+
+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.GarminSleepStageSample;
+import nodomain.freeyourgadget.gadgetbridge.entities.GarminSleepStageSampleDao;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class GarminSleepStageSampleProvider extends AbstractTimeSampleProvider<GarminSleepStageSample> {
+    public GarminSleepStageSampleProvider(final GBDevice device, final DaoSession session) {
+        super(device, session);
+    }
+
+    @NonNull
+    @Override
+    public AbstractDao<GarminSleepStageSample, ?> getSampleDao() {
+        return getSession().getGarminSleepStageSampleDao();
+    }
+
+    @NonNull
+    @Override
+    protected Property getTimestampSampleProperty() {
+        return GarminSleepStageSampleDao.Properties.Timestamp;
+    }
+
+    @NonNull
+    @Override
+    protected Property getDeviceIdentifierSampleProperty() {
+        return GarminSleepStageSampleDao.Properties.DeviceId;
+    }
+
+    @Override
+    public GarminSleepStageSample createSample() {
+        return new GarminSleepStageSample();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminSleepStatsSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminSleepStatsSampleProvider.java
new file mode 100644
index 0000000..607a0d7
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminSleepStatsSampleProvider.java
@@ -0,0 +1,56 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.garmin;
+
+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.GarminSleepStatsSample;
+import nodomain.freeyourgadget.gadgetbridge.entities.GarminSleepStatsSampleDao;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class GarminSleepStatsSampleProvider extends AbstractTimeSampleProvider<GarminSleepStatsSample> {
+    public GarminSleepStatsSampleProvider(final GBDevice device, final DaoSession session) {
+        super(device, session);
+    }
+
+    @NonNull
+    @Override
+    public AbstractDao<GarminSleepStatsSample, ?> getSampleDao() {
+        return getSession().getGarminSleepStatsSampleDao();
+    }
+
+    @NonNull
+    @Override
+    protected Property getTimestampSampleProperty() {
+        return GarminSleepStatsSampleDao.Properties.Timestamp;
+    }
+
+    @NonNull
+    @Override
+    protected Property getDeviceIdentifierSampleProperty() {
+        return GarminSleepStatsSampleDao.Properties.DeviceId;
+    }
+
+    @Override
+    public GarminSleepStatsSample createSample() {
+        return new GarminSleepStatsSample();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminSpo2SampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminSpo2SampleProvider.java
new file mode 100644
index 0000000..0705aa1
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminSpo2SampleProvider.java
@@ -0,0 +1,56 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.garmin;
+
+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.GarminSpo2Sample;
+import nodomain.freeyourgadget.gadgetbridge.entities.GarminSpo2SampleDao;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class GarminSpo2SampleProvider extends AbstractTimeSampleProvider<GarminSpo2Sample> {
+    public GarminSpo2SampleProvider(final GBDevice device, final DaoSession session) {
+        super(device, session);
+    }
+
+    @NonNull
+    @Override
+    public AbstractDao<GarminSpo2Sample, ?> getSampleDao() {
+        return getSession().getGarminSpo2SampleDao();
+    }
+
+    @NonNull
+    @Override
+    protected Property getTimestampSampleProperty() {
+        return GarminSpo2SampleDao.Properties.Timestamp;
+    }
+
+    @NonNull
+    @Override
+    protected Property getDeviceIdentifierSampleProperty() {
+        return GarminSpo2SampleDao.Properties.DeviceId;
+    }
+
+    @Override
+    public GarminSpo2Sample createSample() {
+        return new GarminSpo2Sample();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminStressSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminStressSampleProvider.java
new file mode 100644
index 0000000..163b1d9
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminStressSampleProvider.java
@@ -0,0 +1,56 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.garmin;
+
+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.GarminStressSample;
+import nodomain.freeyourgadget.gadgetbridge.entities.GarminStressSampleDao;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class GarminStressSampleProvider extends AbstractTimeSampleProvider<GarminStressSample> {
+    public GarminStressSampleProvider(final GBDevice device, final DaoSession session) {
+        super(device, session);
+    }
+
+    @NonNull
+    @Override
+    public AbstractDao<GarminStressSample, ?> getSampleDao() {
+        return getSession().getGarminStressSampleDao();
+    }
+
+    @NonNull
+    @Override
+    protected Property getTimestampSampleProperty() {
+        return GarminStressSampleDao.Properties.Timestamp;
+    }
+
+    @NonNull
+    @Override
+    protected Property getDeviceIdentifierSampleProperty() {
+        return GarminStressSampleDao.Properties.DeviceId;
+    }
+
+    @Override
+    public GarminStressSample createSample() {
+        return new GarminStressSample();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminWorkoutParser.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminWorkoutParser.java
new file mode 100644
index 0000000..ae95af1
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminWorkoutParser.java
@@ -0,0 +1,369 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.garmin;
+
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.*;
+
+import android.content.Context;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Optional;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.workouts.entries.ActivitySummaryProgressEntry;
+import nodomain.freeyourgadget.gadgetbridge.activities.workouts.entries.ActivitySummaryTableRowEntry;
+import nodomain.freeyourgadget.gadgetbridge.activities.workouts.entries.ActivitySummaryValue;
+import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityPoint;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryData;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryParser;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.FitFile;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordData;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.enums.GarminSport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitPhysiologicalMetrics;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitRecord;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitSession;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitSet;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitSport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitTimeInZone;
+import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
+
+public class GarminWorkoutParser implements ActivitySummaryParser {
+    private static final Logger LOG = LoggerFactory.getLogger(GarminWorkoutParser.class);
+
+    private final Context context;
+
+    private final List<FitTimeInZone> timesInZone = new ArrayList<>();
+    private final List<ActivityPoint> activityPoints = new ArrayList<>();
+    private FitSession session = null;
+    private FitSport sport = null;
+    private FitPhysiologicalMetrics physiologicalMetrics = null;
+    private final List<FitSet> sets = new ArrayList<>();
+
+    public GarminWorkoutParser(final Context context) {
+        this.context = context;
+    }
+
+    @Override
+    public BaseActivitySummary parseBinaryData(final BaseActivitySummary summary, final boolean forDetails) {
+        if (!forDetails) {
+            // Our parsing is too slow, especially without a RecyclerView
+            return summary;
+        }
+
+        final long nanoStart = System.nanoTime();
+
+        reset();
+
+        final String rawDetailsPath = summary.getRawDetailsPath();
+        if (rawDetailsPath == null) {
+            LOG.warn("No rawDetailsPath");
+            return summary;
+        }
+        final File file = FileUtils.tryFixPath(new File(rawDetailsPath));
+        if (file == null || !file.isFile() || !file.canRead()) {
+            LOG.warn("Unable to read {}", rawDetailsPath);
+            return summary;
+        }
+
+        final FitFile fitFile;
+        try {
+            fitFile = FitFile.parseIncoming(file);
+        } catch (final IOException e) {
+            LOG.error("Failed to parse fit file", e);
+            return summary;
+        }
+
+        for (final RecordData record : fitFile.getRecords()) {
+            handleRecord(record);
+        }
+
+        updateSummary(summary);
+
+        final long nanoEnd = System.nanoTime();
+        final long executionTime = (nanoEnd - nanoStart) / 1000000;
+        LOG.trace("Updating summary took {}ms", executionTime);
+
+        return summary;
+    }
+
+    public void reset() {
+        timesInZone.clear();
+        activityPoints.clear();
+        session = null;
+        sport = null;
+        physiologicalMetrics = null;
+        sets.clear();
+    }
+
+    public boolean handleRecord(final RecordData record) {
+        if (record instanceof FitRecord) {
+            activityPoints.add(((FitRecord) record).toActivityPoint());
+        } else if (record instanceof FitSession) {
+            LOG.debug("Session: {}", record);
+            if (session != null) {
+                LOG.warn("Got multiple sessions - NOT SUPPORTED: {}", record);
+            } else {
+                // We only support 1 session
+                session = (FitSession) record;
+            }
+        } else if (record instanceof FitPhysiologicalMetrics) {
+            LOG.debug("Physiological Metrics: {}", record);
+            physiologicalMetrics = (FitPhysiologicalMetrics) record;
+        } else if (record instanceof FitSport) {
+            LOG.debug("Sport: {}", record);
+            if (sport != null) {
+                LOG.warn("Got multiple sports - NOT SUPPORTED: {}", record);
+            } else {
+                // We only support 1 sport
+                sport = (FitSport) record;
+            }
+        } else if (record instanceof FitTimeInZone) {
+            LOG.trace("Time in zone: {}", record);
+            timesInZone.add((FitTimeInZone) record);
+        } else if (record instanceof FitSet) {
+            LOG.trace("Set: {}", record);
+            sets.add((FitSet) record);
+        } else {
+            return false;
+        }
+
+        return true;
+    }
+
+    public void updateSummary(final BaseActivitySummary summary) {
+        if (session == null) {
+            LOG.error("Got workout, but no session");
+            return;
+        }
+
+        final ActivitySummaryData summaryData = new ActivitySummaryData();
+
+        final ActivityKind activityKind;
+        if (sport != null) {
+            summary.setName(sport.getName());
+            activityKind = getActivityKind(sport.getSport(), sport.getSubSport());
+        } else {
+            activityKind = getActivityKind(session.getSport(), session.getSubSport());
+        }
+        summary.setActivityKind(activityKind.getCode());
+
+        if (session.getTotalElapsedTime() != null) {
+            summary.setEndTime(new Date(summary.getStartTime().getTime() + session.getTotalElapsedTime().intValue()));
+        }
+
+        if (session.getTotalTimerTime() != null) {
+            summaryData.add(ACTIVE_SECONDS, session.getTotalTimerTime() / 1000f, UNIT_SECONDS);
+        }
+        if (session.getTotalDistance() != null) {
+            summaryData.add(DISTANCE_METERS, session.getTotalDistance() / 100f, UNIT_METERS);
+        }
+        if (session.getTotalSteps() != null) {
+            summaryData.add(STEPS, session.getTotalSteps(), UNIT_STEPS);
+        }
+        if (session.getTotalCalories() != null) {
+            summaryData.add(CALORIES_BURNT, session.getTotalCalories(), UNIT_KCAL);
+        }
+        if (session.getEstimatedSweatLoss() != null) {
+            summaryData.add(ESTIMATED_SWEAT_LOSS, session.getEstimatedSweatLoss(), UNIT_ML);
+        }
+        if (session.getAverageHeartRate() != null) {
+            summaryData.add(HR_AVG, session.getAverageHeartRate(), UNIT_BPM);
+        }
+        if (session.getMaxHeartRate() != null) {
+            summaryData.add(HR_MAX, session.getMaxHeartRate(), UNIT_BPM);
+        }
+        if (session.getHrvSdrr() != null) {
+            summaryData.add(HRV_SDRR, session.getHrvSdrr(), UNIT_MILLISECONDS);
+        }
+        if (session.getHrvRmssd() != null) {
+            summaryData.add(HRV_RMSSD, session.getHrvRmssd(), UNIT_MILLISECONDS);
+        }
+        if (session.getAvgSpo2() != null) {
+            summaryData.add(SPO2_AVG, session.getAvgSpo2(), UNIT_PERCENTAGE);
+        }
+        if (session.getEnhancedAvgRespirationRate() != null) {
+            summaryData.add(RESPIRATION_AVG, session.getEnhancedAvgRespirationRate(), UNIT_BREATHS_PER_MIN);
+        }
+        if (session.getEnhancedMaxRespirationRate() != null) {
+            summaryData.add(RESPIRATION_MAX, session.getEnhancedMaxRespirationRate(), UNIT_BREATHS_PER_MIN);
+        }
+        if (session.getEnhancedMinRespirationRate() != null) {
+            summaryData.add(RESPIRATION_MIN, session.getEnhancedMinRespirationRate(), UNIT_BREATHS_PER_MIN);
+        }
+        if (session.getAvgStress() != null) {
+            summaryData.add(STRESS_AVG, session.getAvgStress(), UNIT_NONE);
+        }
+        if (session.getAverageCadence() != null) {
+            summaryData.add(CADENCE_AVG, session.getAverageCadence(), UNIT_SPM);
+        }
+        if (session.getMaxCadence() != null) {
+            summaryData.add(CADENCE_MAX, session.getMaxCadence(), UNIT_SPM);
+        }
+        if (session.getTotalAscent() != null) {
+            summaryData.add(ASCENT_DISTANCE, session.getTotalAscent(), UNIT_METERS);
+        }
+        if (session.getTotalDescent() != null) {
+            summaryData.add(DESCENT_DISTANCE, session.getTotalDescent(), UNIT_METERS);
+        }
+
+        if (session.getEnhancedAvgSpeed() != null) {
+            if (ActivityKind.isPaceActivity(activityKind)) {
+                summaryData.add(PACE_AVG_SECONDS_KM, Math.round((60 / (session.getEnhancedAvgSpeed() * 3.6)) * 60), UNIT_SECONDS);
+            } else {
+                summaryData.add(SPEED_AVG, Math.round((session.getEnhancedAvgSpeed() * 3600 / 1000) * 100.0) / 100.0, UNIT_KMPH);
+            }
+        }
+
+        if (session.getEnhancedMaxSpeed() != null) {
+            if (ActivityKind.isPaceActivity(activityKind)) {
+                summaryData.add(PACE_MAX, Math.round((60 / (session.getEnhancedMaxSpeed() * 3.6)) * 60), UNIT_SECONDS);
+            } else {
+                summaryData.add(SPEED_MAX, Math.round((session.getEnhancedMaxSpeed() * 3600 / 1000) * 100.0) / 100.0, UNIT_KMPH);
+            }
+        }
+
+        for (final FitTimeInZone fitTimeInZone : timesInZone) {
+            // Find the first time in zone for the session (assumes single-session)
+            if (fitTimeInZone.getReferenceMessage() != null && fitTimeInZone.getReferenceMessage() == 18) {
+                final Double[] timeInZones = fitTimeInZone.getTimeInZone();
+                if (timeInZones == null) {
+                    continue;
+                }
+                final double totalTime = Arrays.stream(timeInZones).mapToDouble(Number::doubleValue).sum();
+                if (totalTime == 0) {
+                    continue;
+                }
+                if (timeInZones[0] != null && timeInZones[0] == totalTime) {
+                    // The total time is N/A, so do not add the section
+                    continue;
+                }
+                final List<String> zoneOrder = Arrays.asList(HR_ZONE_NA, HR_ZONE_WARM_UP, HR_ZONE_EASY, HR_ZONE_AEROBIC, HR_ZONE_THRESHOLD, HR_ZONE_MAXIMUM);
+                final int[] zoneColors = new int[]{
+                        0,
+                        context.getResources().getColor(R.color.hr_zone_warm_up_color),
+                        context.getResources().getColor(R.color.hr_zone_easy_color),
+                        context.getResources().getColor(R.color.hr_zone_aerobic_color),
+                        context.getResources().getColor(R.color.hr_zone_threshold_color),
+                        context.getResources().getColor(R.color.hr_zone_maximum_color),
+                };
+                for (int i = 0; i < zoneOrder.size(); i++) {
+                    double timeInZone = timeInZones[i] != null ? Math.rint(timeInZones[i]) : 0;
+                    summaryData.add(
+                            zoneOrder.get(i),
+                            new ActivitySummaryProgressEntry(
+                                    timeInZone,
+                                    UNIT_SECONDS,
+                                    (int) (100 * timeInZone / totalTime),
+                                    zoneColors[i]
+                            )
+                    );
+                }
+                break;
+            }
+        }
+
+        if (physiologicalMetrics != null) {
+            if (physiologicalMetrics.getAerobicEffect() != null) {
+                summaryData.add(TRAINING_EFFECT_AEROBIC, physiologicalMetrics.getAerobicEffect(), UNIT_NONE, true);
+            }
+            if (physiologicalMetrics.getAnaerobicEffect() != null) {
+                summaryData.add(TRAINING_EFFECT_ANAEROBIC, physiologicalMetrics.getAnaerobicEffect(), UNIT_NONE, true);
+            }
+            if (physiologicalMetrics.getMetMax() != null) {
+                summaryData.add(MAXIMUM_OXYGEN_UPTAKE, physiologicalMetrics.getMetMax().floatValue() * 3.5f, UNIT_ML_KG_MIN);
+            }
+            if (physiologicalMetrics.getRecoveryTime() != null) {
+                summaryData.add(RECOVERY_TIME, physiologicalMetrics.getRecoveryTime() * 60, UNIT_SECONDS);
+            }
+            if (physiologicalMetrics.getLactateThresholdHeartRate() != null) {
+                summaryData.add(LACTATE_THRESHOLD_HR, physiologicalMetrics.getLactateThresholdHeartRate(), UNIT_BPM);
+            }
+        }
+
+        if (!sets.isEmpty()) {
+            final boolean anyReps = sets.stream().anyMatch(s -> s.getRepetitions() != null);
+            final boolean anyWeight = sets.stream().anyMatch(s -> s.getWeight() != null);
+
+            final List<ActivitySummaryValue> header = new LinkedList<>();
+            header.add(new ActivitySummaryValue("set"));
+            header.add(new ActivitySummaryValue("workout_set_reps"));
+            header.add(new ActivitySummaryValue("menuitem_weight"));
+            header.add(new ActivitySummaryValue("activity_detail_duration_label"));
+
+            summaryData.add(
+                    "sets_header",
+                    new ActivitySummaryTableRowEntry(
+                            SETS,
+                            header,
+                            true,
+                            true
+                    )
+            );
+
+            int i = 1;
+            for (final FitSet set : sets) {
+                if (set.getSetType() != null && set.getDuration() != null && set.getSetType() == 1) {
+                    final List<ActivitySummaryValue> columns = new LinkedList<>();
+                    columns.add(new ActivitySummaryValue(i, UNIT_NONE));
+
+                    if (set.getRepetitions() != null) {
+                        columns.add(new ActivitySummaryValue(String.valueOf(set.getRepetitions())));
+                    } else {
+                        columns.add(new ActivitySummaryValue("stats_empty_value"));
+                    }
+
+                    if (set.getWeight() != null) {
+                        columns.add(new ActivitySummaryValue(set.getWeight(), UNIT_KG));
+                    } else {
+                        columns.add(new ActivitySummaryValue("stats_empty_value"));
+                    }
+
+                    columns.add(new ActivitySummaryValue(set.getDuration().longValue(), UNIT_SECONDS));
+
+                    summaryData.add(
+                            "set_" + i,
+                            new ActivitySummaryTableRowEntry(
+                                    SETS,
+                                    columns,
+                                    false,
+                                    true
+                            )
+                    );
+                    i++;
+                }
+            }
+        }
+
+        summaryData.add(
+                INTERNAL_HAS_GPS,
+                String.valueOf(activityPoints.stream().anyMatch(p -> p.getLocation() != null))
+        );
+
+        summary.setSummaryData(summaryData.toString());
+    }
+
+    private static ActivityKind getActivityKind(final Integer sport, final Integer subsport) {
+        final Optional<GarminSport> garminSport = GarminSport.fromCodes(sport, subsport);
+        if (garminSport.isPresent()) {
+            return garminSport.get().getActivityKind();
+        } else {
+            LOG.warn("Unknown garmin sport {}/{}", sport, subsport);
+
+            final Optional<GarminSport> optGarminSportFallback = GarminSport.fromCodes(sport, 0);
+            if (!optGarminSportFallback.isEmpty()) {
+                return optGarminSportFallback.get().getActivityKind();
+            }
+        }
+
+        return ActivityKind.UNKNOWN;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/enduro/GarminEnduro3Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/enduro/GarminEnduro3Coordinator.java
new file mode 100644
index 0000000..fee46b4
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/enduro/GarminEnduro3Coordinator.java
@@ -0,0 +1,18 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.enduro;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminCoordinator;
+
+public class GarminEnduro3Coordinator extends GarminCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("^Enduro 3$");
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_garmin_enduro_3;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/epix/GarminEpixProCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/epix/GarminEpixProCoordinator.java
new file mode 100644
index 0000000..bfa5b45
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/epix/GarminEpixProCoordinator.java
@@ -0,0 +1,18 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.epix;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminCoordinator;
+
+public class GarminEpixProCoordinator extends GarminCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("^EPIX PRO - \\d+mm$");
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_garmin_epix_pro;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/fenix/GarminFenix5Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/fenix/GarminFenix5Coordinator.java
new file mode 100644
index 0000000..8d8c2ed
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/fenix/GarminFenix5Coordinator.java
@@ -0,0 +1,18 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.fenix;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminCoordinator;
+
+public class GarminFenix5Coordinator extends GarminCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("^fenix 5$");
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_garmin_fenix_5;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/fenix/GarminFenix5PlusCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/fenix/GarminFenix5PlusCoordinator.java
new file mode 100644
index 0000000..d1e50d0
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/fenix/GarminFenix5PlusCoordinator.java
@@ -0,0 +1,24 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.fenix;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminCoordinator;
+
+public class GarminFenix5PlusCoordinator extends GarminCoordinator {
+    @Override
+    public boolean isExperimental() {
+        // https://codeberg.org/Freeyourgadget/Gadgetbridge/issues/3963
+        return true;
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("^fenix 5 Plus$");
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_garmin_fenix_5_plus;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/fenix/GarminFenix5XPlusCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/fenix/GarminFenix5XPlusCoordinator.java
new file mode 100644
index 0000000..537f98b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/fenix/GarminFenix5XPlusCoordinator.java
@@ -0,0 +1,24 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.fenix;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminCoordinator;
+
+public class GarminFenix5XPlusCoordinator extends GarminCoordinator {
+    @Override
+    public boolean isExperimental() {
+        // https://codeberg.org/Freeyourgadget/Gadgetbridge/issues/3963
+        return true;
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("^fenix 5X Plus$");
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_garmin_fenix_5x_plus;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/fenix/GarminFenix6Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/fenix/GarminFenix6Coordinator.java
new file mode 100644
index 0000000..5fcdb5c
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/fenix/GarminFenix6Coordinator.java
@@ -0,0 +1,18 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.fenix;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminCoordinator;
+
+public class GarminFenix6Coordinator extends GarminCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("^fenix 6$");
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_garmin_fenix_6;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/fenix/GarminFenix6SProCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/fenix/GarminFenix6SProCoordinator.java
new file mode 100644
index 0000000..f6cb87c
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/fenix/GarminFenix6SProCoordinator.java
@@ -0,0 +1,18 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.fenix;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminCoordinator;
+
+public class GarminFenix6SProCoordinator extends GarminCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("^fenix 6S Pro$");
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_garmin_fenix_6s_pro;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/fenix/GarminFenix6SSapphireCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/fenix/GarminFenix6SSapphireCoordinator.java
new file mode 100644
index 0000000..4d59246
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/fenix/GarminFenix6SSapphireCoordinator.java
@@ -0,0 +1,18 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.fenix;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminCoordinator;
+
+public class GarminFenix6SSapphireCoordinator extends GarminCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("^fenix 6S Sapphire$");
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_garmin_fenix_6s_sapphire;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/fenix/GarminFenix6SapphireCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/fenix/GarminFenix6SapphireCoordinator.java
new file mode 100644
index 0000000..106d1c9
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/fenix/GarminFenix6SapphireCoordinator.java
@@ -0,0 +1,18 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.fenix;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminCoordinator;
+
+public class GarminFenix6SapphireCoordinator extends GarminCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("^fenix 6 Sapphire$");
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_garmin_fenix_6_sapphire;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/fenix/GarminFenix7Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/fenix/GarminFenix7Coordinator.java
new file mode 100644
index 0000000..f5cde59
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/fenix/GarminFenix7Coordinator.java
@@ -0,0 +1,18 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.fenix;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminCoordinator;
+
+public class GarminFenix7Coordinator extends GarminCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("^fenix 7$");
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_garmin_fenix_7;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/fenix/GarminFenix7ProCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/fenix/GarminFenix7ProCoordinator.java
new file mode 100644
index 0000000..92f07b8
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/fenix/GarminFenix7ProCoordinator.java
@@ -0,0 +1,18 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.fenix;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminCoordinator;
+
+public class GarminFenix7ProCoordinator extends GarminCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("^fenix 7 Pro$");
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_garmin_fenix_7_pro;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/fenix/GarminFenix7SCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/fenix/GarminFenix7SCoordinator.java
new file mode 100644
index 0000000..3d89769
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/fenix/GarminFenix7SCoordinator.java
@@ -0,0 +1,18 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.fenix;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminCoordinator;
+
+public class GarminFenix7SCoordinator extends GarminCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("^fenix 7S$");
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_garmin_fenix_7s;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/fenix/GarminFenix7XCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/fenix/GarminFenix7XCoordinator.java
new file mode 100644
index 0000000..9b43ab2
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/fenix/GarminFenix7XCoordinator.java
@@ -0,0 +1,18 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.fenix;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminCoordinator;
+
+public class GarminFenix7XCoordinator extends GarminCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("^fenix 7X$");
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_garmin_fenix_7x;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/fenix/GarminFenix8Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/fenix/GarminFenix8Coordinator.java
new file mode 100644
index 0000000..f971ba5
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/fenix/GarminFenix8Coordinator.java
@@ -0,0 +1,18 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.fenix;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminCoordinator;
+
+public class GarminFenix8Coordinator extends GarminCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("^fenix 8( - [\\d+]+mm)?$");
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_garmin_fenix_8;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/forerunner/GarminForerunner165Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/forerunner/GarminForerunner165Coordinator.java
new file mode 100644
index 0000000..f685a9f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/forerunner/GarminForerunner165Coordinator.java
@@ -0,0 +1,18 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.forerunner;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminCoordinator;
+
+public class GarminForerunner165Coordinator extends GarminCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("^Forerunner 165$");
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_garmin_forerunner_165;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/forerunner/GarminForerunner235Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/forerunner/GarminForerunner235Coordinator.java
new file mode 100644
index 0000000..8085671
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/forerunner/GarminForerunner235Coordinator.java
@@ -0,0 +1,18 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.forerunner;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminCoordinator;
+
+public class GarminForerunner235Coordinator extends GarminCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("^Forerunner 235$");
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_garmin_forerunner_235;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/forerunner/GarminForerunner245Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/forerunner/GarminForerunner245Coordinator.java
new file mode 100644
index 0000000..5da1f73
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/forerunner/GarminForerunner245Coordinator.java
@@ -0,0 +1,18 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.forerunner;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminCoordinator;
+
+public class GarminForerunner245Coordinator extends GarminCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("^Forerunner 245$");
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_garmin_forerunner_245;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/forerunner/GarminForerunner245MusicCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/forerunner/GarminForerunner245MusicCoordinator.java
new file mode 100644
index 0000000..1a8b8e9
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/forerunner/GarminForerunner245MusicCoordinator.java
@@ -0,0 +1,24 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.forerunner;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminCoordinator;
+
+public class GarminForerunner245MusicCoordinator extends GarminCoordinator {
+    @Override
+    public boolean isExperimental() {
+        // https://codeberg.org/Freeyourgadget/Gadgetbridge/issues/3986
+        return true;
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("^Forerunner 245 Music$");
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_garmin_forerunner_245_music;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/forerunner/GarminForerunner255Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/forerunner/GarminForerunner255Coordinator.java
new file mode 100644
index 0000000..c9e72cb
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/forerunner/GarminForerunner255Coordinator.java
@@ -0,0 +1,18 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.forerunner;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminCoordinator;
+
+public class GarminForerunner255Coordinator extends GarminCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("^Forerunner 255$");
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_garmin_forerunner_255;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/forerunner/GarminForerunner255MusicCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/forerunner/GarminForerunner255MusicCoordinator.java
new file mode 100644
index 0000000..bb477f5
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/forerunner/GarminForerunner255MusicCoordinator.java
@@ -0,0 +1,18 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.forerunner;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminCoordinator;
+
+public class GarminForerunner255MusicCoordinator extends GarminCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("^Forerunner 255 Music$");
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_garmin_forerunner_255_music;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/forerunner/GarminForerunner255SCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/forerunner/GarminForerunner255SCoordinator.java
new file mode 100644
index 0000000..06979ed
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/forerunner/GarminForerunner255SCoordinator.java
@@ -0,0 +1,18 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.forerunner;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminCoordinator;
+
+public class GarminForerunner255SCoordinator extends GarminCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("^Forerunner 255S$");
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_garmin_forerunner_255s;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/forerunner/GarminForerunner255SMusicCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/forerunner/GarminForerunner255SMusicCoordinator.java
new file mode 100644
index 0000000..02e7e7f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/forerunner/GarminForerunner255SMusicCoordinator.java
@@ -0,0 +1,18 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.forerunner;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminCoordinator;
+
+public class GarminForerunner255SMusicCoordinator extends GarminCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("^Forerunner 255S Music$");
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_garmin_forerunner_255s_music;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/forerunner/GarminForerunner265Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/forerunner/GarminForerunner265Coordinator.java
new file mode 100644
index 0000000..8677319
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/forerunner/GarminForerunner265Coordinator.java
@@ -0,0 +1,18 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.forerunner;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminCoordinator;
+
+public class GarminForerunner265Coordinator extends GarminCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("^Forerunner 265$");
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_garmin_forerunner_265;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/forerunner/GarminForerunner265SCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/forerunner/GarminForerunner265SCoordinator.java
new file mode 100644
index 0000000..a07a400
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/forerunner/GarminForerunner265SCoordinator.java
@@ -0,0 +1,19 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.forerunner;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminCoordinator;
+
+public class GarminForerunner265SCoordinator extends GarminCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        // #4131 - including variants of the name just in case
+        return Pattern.compile("^(Garmin )?Forerunner 265[sS]$");
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_garmin_forerunner_265s;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/forerunner/GarminForerunner55Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/forerunner/GarminForerunner55Coordinator.java
new file mode 100644
index 0000000..090c9ae
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/forerunner/GarminForerunner55Coordinator.java
@@ -0,0 +1,34 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.forerunner;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminCoordinator;
+
+public class GarminForerunner55Coordinator extends GarminCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("^Forerunner 55$");
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_garmin_forerunner_55;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/forerunner/GarminForerunner620Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/forerunner/GarminForerunner620Coordinator.java
new file mode 100644
index 0000000..c679bb5
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/forerunner/GarminForerunner620Coordinator.java
@@ -0,0 +1,34 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.forerunner;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminCoordinator;
+
+public class GarminForerunner620Coordinator extends GarminCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("^Forerunner 620$");
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_garmin_forerunner_620;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/forerunner/GarminForerunner955Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/forerunner/GarminForerunner955Coordinator.java
new file mode 100644
index 0000000..d80f722
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/forerunner/GarminForerunner955Coordinator.java
@@ -0,0 +1,18 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.forerunner;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminCoordinator;
+
+public class GarminForerunner955Coordinator extends GarminCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("^Forerunner 955$");
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_garmin_forerunner_955;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/forerunner/GarminForerunner965Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/forerunner/GarminForerunner965Coordinator.java
new file mode 100644
index 0000000..d25a9b1
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/forerunner/GarminForerunner965Coordinator.java
@@ -0,0 +1,18 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.forerunner;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminCoordinator;
+
+public class GarminForerunner965Coordinator extends GarminCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("^Forerunner 965|UAT-4315$");
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_garmin_forerunner_965;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/instinct/GarminInstinct2Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/instinct/GarminInstinct2Coordinator.java
new file mode 100644
index 0000000..56ab03a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/instinct/GarminInstinct2Coordinator.java
@@ -0,0 +1,18 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.instinct;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminCoordinator;
+
+public class GarminInstinct2Coordinator extends GarminCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("^Instinct 2$");
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_garmin_instinct_2;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/instinct/GarminInstinct2SCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/instinct/GarminInstinct2SCoordinator.java
new file mode 100644
index 0000000..020ac99
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/instinct/GarminInstinct2SCoordinator.java
@@ -0,0 +1,18 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.instinct;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminCoordinator;
+
+public class GarminInstinct2SCoordinator extends GarminCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("^Instinct 2S$");
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_garmin_instinct_2s;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/instinct/GarminInstinct2SSolarCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/instinct/GarminInstinct2SSolarCoordinator.java
new file mode 100644
index 0000000..3911a1d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/instinct/GarminInstinct2SSolarCoordinator.java
@@ -0,0 +1,18 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.instinct;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminCoordinator;
+
+public class GarminInstinct2SSolarCoordinator extends GarminCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("^Instinct 2S Solar$");
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_garmin_instinct_2s_solar;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/instinct/GarminInstinct2SolTacCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/instinct/GarminInstinct2SolTacCoordinator.java
new file mode 100644
index 0000000..a6c02bf
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/instinct/GarminInstinct2SolTacCoordinator.java
@@ -0,0 +1,18 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.instinct;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminCoordinator;
+
+public class GarminInstinct2SolTacCoordinator extends GarminCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("^Instinct 2 SolTac$");
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_garmin_instinct_2_soltac;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/instinct/GarminInstinct2SolarCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/instinct/GarminInstinct2SolarCoordinator.java
new file mode 100644
index 0000000..81e5334
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/instinct/GarminInstinct2SolarCoordinator.java
@@ -0,0 +1,19 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.instinct;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+import java.util.regex.Pattern;
+
+public class GarminInstinct2SolarCoordinator extends GarminCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("^Instinct 2 Solar$");
+    }
+    
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_garmin_instinct_2_solar;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/instinct/GarminInstinct2XSolarCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/instinct/GarminInstinct2XSolarCoordinator.java
new file mode 100644
index 0000000..268c821
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/instinct/GarminInstinct2XSolarCoordinator.java
@@ -0,0 +1,19 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.instinct;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminCoordinator;
+
+public class GarminInstinct2XSolarCoordinator extends GarminCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        // Allow ending both with "Sol" (#3063) and "Solar" (reported on Matrix).
+        return Pattern.compile("^Instinct 2X Sol(ar)?$");
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_garmin_instinct_2x_solar;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/instinct/GarminInstinctCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/instinct/GarminInstinctCoordinator.java
new file mode 100644
index 0000000..c756664
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/instinct/GarminInstinctCoordinator.java
@@ -0,0 +1,18 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.instinct;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminCoordinator;
+
+public class GarminInstinctCoordinator extends GarminCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("^Instinct$");
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_garmin_instinct;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/instinct/GarminInstinctCrossoverCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/instinct/GarminInstinctCrossoverCoordinator.java
new file mode 100644
index 0000000..38e1225
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/instinct/GarminInstinctCrossoverCoordinator.java
@@ -0,0 +1,18 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.instinct;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminCoordinator;
+
+import java.util.regex.Pattern;
+
+public class GarminInstinctCrossoverCoordinator extends GarminCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("^Instinct Crossover$");
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_garmin_instinct_crossover;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/instinct/GarminInstinctSolarCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/instinct/GarminInstinctSolarCoordinator.java
new file mode 100644
index 0000000..83b3d28
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/instinct/GarminInstinctSolarCoordinator.java
@@ -0,0 +1,24 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.instinct;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class GarminInstinctSolarCoordinator extends GarminCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("^Instinct (Solar|Dual Power)$");
+    }
+
+    @Override
+    public int getCannedRepliesSlotCount(final GBDevice device) {
+        return 16;
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_garmin_instinct_solar;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/swim/GarminSwim2Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/swim/GarminSwim2Coordinator.java
new file mode 100644
index 0000000..54f7ff3
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/swim/GarminSwim2Coordinator.java
@@ -0,0 +1,18 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.swim;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminCoordinator;
+
+public class GarminSwim2Coordinator extends GarminCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("^Swim 2$");
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_garmin_swim_2;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/venu/GarminVenu2Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/venu/GarminVenu2Coordinator.java
new file mode 100644
index 0000000..8aaa8bb
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/venu/GarminVenu2Coordinator.java
@@ -0,0 +1,18 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.venu;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminCoordinator;
+
+public class GarminVenu2Coordinator extends GarminCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("^Venu 2$");
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_garmin_venu_2;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/venu/GarminVenu2PlusCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/venu/GarminVenu2PlusCoordinator.java
new file mode 100644
index 0000000..97d151f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/venu/GarminVenu2PlusCoordinator.java
@@ -0,0 +1,18 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.venu;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminCoordinator;
+
+public class GarminVenu2PlusCoordinator extends GarminCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("^Venu 2 Plus$");
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_garmin_venu_2_plus;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/venu/GarminVenu2SCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/venu/GarminVenu2SCoordinator.java
new file mode 100644
index 0000000..805f4ed
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/venu/GarminVenu2SCoordinator.java
@@ -0,0 +1,18 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.venu;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminCoordinator;
+
+public class GarminVenu2SCoordinator extends GarminCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("^Venu 2S$");
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_garmin_venu_2s;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/venu/GarminVenu3Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/venu/GarminVenu3Coordinator.java
new file mode 100644
index 0000000..66edd79
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/venu/GarminVenu3Coordinator.java
@@ -0,0 +1,18 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.venu;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminCoordinator;
+
+public class GarminVenu3Coordinator extends GarminCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("^Venu 3$");
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_garmin_venu_3;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/venu/GarminVenu3SCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/venu/GarminVenu3SCoordinator.java
new file mode 100644
index 0000000..c103f09
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/venu/GarminVenu3SCoordinator.java
@@ -0,0 +1,18 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.venu;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminCoordinator;
+
+public class GarminVenu3SCoordinator extends GarminCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("^Venu 3S|UAT-4261$");
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_garmin_venu_3s;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/venu/GarminVenuCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/venu/GarminVenuCoordinator.java
new file mode 100644
index 0000000..ae6cd5d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/venu/GarminVenuCoordinator.java
@@ -0,0 +1,18 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.venu;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminCoordinator;
+
+public class GarminVenuCoordinator extends GarminCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("^Venu$");
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_garmin_venu;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/venu/GarminVenuSq2Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/venu/GarminVenuSq2Coordinator.java
new file mode 100644
index 0000000..b76e854
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/venu/GarminVenuSq2Coordinator.java
@@ -0,0 +1,18 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.venu;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminCoordinator;
+
+public class GarminVenuSq2Coordinator extends GarminCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("^Venu Sq 2$");
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_garmin_venu_sq_2;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/venu/GarminVenuSqCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/venu/GarminVenuSqCoordinator.java
new file mode 100644
index 0000000..c4a2967
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/venu/GarminVenuSqCoordinator.java
@@ -0,0 +1,18 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.venu;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminCoordinator;
+
+public class GarminVenuSqCoordinator extends GarminCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("^Venu Sq$");
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_garmin_venu_sq;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/vivoactive/GarminVivoActive3Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/vivoactive/GarminVivoActive3Coordinator.java
new file mode 100644
index 0000000..5179bed
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/vivoactive/GarminVivoActive3Coordinator.java
@@ -0,0 +1,20 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.vivoactive;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminCoordinator;
+
+public class GarminVivoActive3Coordinator extends GarminCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        // The report on matrix did not include a space, but let's make it
+        // optional just in case
+        return Pattern.compile("^v铆voactive *3$");
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_garmin_vivoactive_3;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/vivoactive/GarminVivoActive4Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/vivoactive/GarminVivoActive4Coordinator.java
new file mode 100644
index 0000000..fd37ad9
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/vivoactive/GarminVivoActive4Coordinator.java
@@ -0,0 +1,18 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.vivoactive;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminCoordinator;
+
+public class GarminVivoActive4Coordinator extends GarminCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("^v铆voactive 4$");
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_garmin_vivoactive_4;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/vivoactive/GarminVivoActive4SCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/vivoactive/GarminVivoActive4SCoordinator.java
new file mode 100644
index 0000000..afbb437
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/vivoactive/GarminVivoActive4SCoordinator.java
@@ -0,0 +1,18 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.vivoactive;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminCoordinator;
+
+public class GarminVivoActive4SCoordinator extends GarminCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("^v铆voactive 4S$");
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_garmin_vivoactive_4s;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/vivoactive/GarminVivoActive5Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/vivoactive/GarminVivoActive5Coordinator.java
new file mode 100644
index 0000000..51fb392
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/vivoactive/GarminVivoActive5Coordinator.java
@@ -0,0 +1,18 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.vivoactive;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminCoordinator;
+
+public class GarminVivoActive5Coordinator extends GarminCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("^v铆voactive 5$");
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_garmin_vivoactive_5;
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/vivomove/GarminVivomoveHrCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/vivomove/GarminVivomoveHrCoordinator.java
new file mode 100644
index 0000000..49b12e3
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/vivomove/GarminVivomoveHrCoordinator.java
@@ -0,0 +1,18 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.vivomove;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminCoordinator;
+
+public class GarminVivomoveHrCoordinator extends GarminCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("^v铆vomove HR$");
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_garmin_vivomove_hr;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/vivomove/GarminVivomoveStyleCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/vivomove/GarminVivomoveStyleCoordinator.java
new file mode 100644
index 0000000..1dcb41b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/vivomove/GarminVivomoveStyleCoordinator.java
@@ -0,0 +1,18 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.vivomove;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminCoordinator;
+
+public class GarminVivomoveStyleCoordinator extends GarminCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("^v铆vomove Style$");
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_garmin_vivomove_style;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/vivomove/GarminVivomoveTrendCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/vivomove/GarminVivomoveTrendCoordinator.java
new file mode 100644
index 0000000..4325598
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/vivomove/GarminVivomoveTrendCoordinator.java
@@ -0,0 +1,18 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.vivomove;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminCoordinator;
+
+public class GarminVivomoveTrendCoordinator extends GarminCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("^v铆vomove Trend$");
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_garmin_vivomove_trend;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/vivosmart/GarminVivosmart5Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/vivosmart/GarminVivosmart5Coordinator.java
new file mode 100644
index 0000000..1d41a55
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/vivosmart/GarminVivosmart5Coordinator.java
@@ -0,0 +1,18 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.vivosmart;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminCoordinator;
+
+public class GarminVivosmart5Coordinator extends GarminCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("^v铆vosmart 5$");
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_garmin_vivosmart_5;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/vivosport/GarminVivosportCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/vivosport/GarminVivosportCoordinator.java
new file mode 100644
index 0000000..695fc84
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/vivosport/GarminVivosportCoordinator.java
@@ -0,0 +1,18 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.vivosport;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminCoordinator;
+
+public class GarminVivosportCoordinator extends GarminCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("^v铆vosport$");
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_garmin_vivosport;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/hama/fit6900/HamaFit6900Constants.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/hama/fit6900/HamaFit6900Constants.java
new file mode 100644
index 0000000..bfbdd07
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/hama/fit6900/HamaFit6900Constants.java
@@ -0,0 +1,28 @@
+/*
+Copyright (C) 2024 enoint
+
+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 <http://www.gnu.org/licenses/>.
+*/
+
+package nodomain.freeyourgadget.gadgetbridge.devices.hama.fit6900;
+
+import java.util.UUID;
+
+public class HamaFit6900Constants {
+    public static final UUID UUID_SERVICE_RXTX = UUID.fromString("c3e6fea0-e966-1000-8000-be99c223df6a");
+    public static final UUID UUID_CHARACTERISTIC_TX = UUID.fromString("c3e6fea1-e966-1000-8000-be99c223df6a");
+    public static final UUID UUID_CHARACTERISTIC_RX = UUID.fromString("c3e6fea2-e966-1000-8000-be99c223df6a");
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/hama/fit6900/HamaFit6900DeviceCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/hama/fit6900/HamaFit6900DeviceCoordinator.java
new file mode 100644
index 0000000..43aa75c
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/hama/fit6900/HamaFit6900DeviceCoordinator.java
@@ -0,0 +1,105 @@
+/*
+Copyright (C) 2024 enoint
+
+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 <http://www.gnu.org/licenses/>.
+*/
+
+package nodomain.freeyourgadget.gadgetbridge.devices.hama.fit6900;
+
+import androidx.annotation.NonNull;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.GBException;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLEDeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.hama.fit6900.HamaFit6900DeviceSupport;
+
+public final class HamaFit6900DeviceCoordinator extends AbstractBLEDeviceCoordinator {
+    @Override
+    protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
+
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("^Fit6900$");
+    }
+
+    @Override
+    public int getBondingStyle() {
+        return BONDING_STYLE_NONE;
+    }
+
+    @Override
+    public int getAlarmSlotCount(GBDevice device) {
+        return 5;
+    }
+
+    @Override
+    public boolean supportsFindDevice() {
+        return true;
+    }
+
+    @Override
+    public int[] getSupportedDeviceSpecificSettings(GBDevice device) {
+        return new int[]{
+                R.xml.devicesettings_allow_accept_reject_calls, // reject only
+                R.xml.devicesettings_camera_remote,
+                R.xml.devicesettings_find_phone,
+                R.xml.devicesettings_liftwrist_display_no_on,
+                R.xml.devicesettings_notifications_enable,
+                R.xml.devicesettings_timeformat,
+                R.xml.devicesettings_transliteration,
+                R.xml.devicesettings_donotdisturb_no_auto,
+                R.xml.devicesettings_autoheartrate,
+                R.xml.devicesettings_hydration_reminder
+        };
+    }
+
+    @Override
+    public String[] getSupportedLanguageSettings(GBDevice device) {
+        return new String[]{
+                "auto",
+                "en_US",
+                "es_ES",
+                "de_DE",
+                "it_IT",
+                "fr_FR",
+                "sv_SE"
+        };
+    }
+
+    @Override
+    public String getManufacturer() {
+        return "Hama";
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return HamaFit6900DeviceSupport.class;
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_hama_fit6900;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/hama/fit6900/Message.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/hama/fit6900/Message.java
new file mode 100644
index 0000000..49cbfba
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/hama/fit6900/Message.java
@@ -0,0 +1,440 @@
+/*
+Copyright (C) 2024 enoint
+
+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 <http://www.gnu.org/licenses/>.
+*/
+
+package nodomain.freeyourgadget.gadgetbridge.devices.hama.fit6900;
+
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.HashMap;
+import java.util.Map;
+
+import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
+import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
+
+public final class Message {
+    private static byte encodeBoolean(boolean value) {
+        return (byte) ((value) ? 1 : 0);
+    }
+
+    private static void encodeInt16(byte[] data, int offset, int value) {
+        data[offset + 0] = (byte) (value & 0xFF);
+        data[offset + 1] = (byte) ((value >> 8) & 0xFF);
+    }
+
+    private static void encodeInt32(byte[] data, int offset, int value) {
+        data[offset + 0] = (byte) (value & 0xFF);
+        data[offset + 1] = (byte) ((value >> 8) & 0xFF);
+        data[offset + 2] = (byte) ((value >> 16) & 0xFF);
+        data[offset + 3] = (byte) ((value >> 24) & 0xFF);
+    }
+
+    private static void encodeInt32Goal(byte[] data, int offset, int value) {
+        encodeInt32(data, offset, value);
+        data[offset + 3] = 0;
+    }
+
+    private static byte encodeTimeFormat(TimeFormat tf) {
+        return (byte) ((tf == TimeFormat.Format12H) ? 1 : 0);
+    }
+
+    public static int decodeInt32(byte[] data, int offset) {
+        return ((data[offset] & 0xFF) << 24) | ((data[offset + 1] & 0xFF) << 16) | ((data[offset + 2] & 0xFF) << 8) | (data[offset + 3] & 0xFF);
+    }
+
+    private static byte[] byteArrayAdd(byte[] b1, byte[] b2) {
+        if (b2 == null || b2.length == 0)
+            return b1;
+
+        byte[] result = new byte[b1.length + b2.length];
+        System.arraycopy(b1, 0, result, 0, b1.length);
+        System.arraycopy(b2, 0, result, b1.length, b2.length);
+
+        return result;
+    }
+
+    public static int calculateCrc(byte[] data) {
+        int crc = 0xFF;
+        for (byte b : data) {
+            crc ^= b & 0xFF;
+            for (int k = 0; k < 8; k++) {
+                if ((crc & 1) != 0) {
+                    crc = (crc >> 1) ^ 184;
+                } else {
+                    crc = (crc >> 1);
+                }
+            }
+        }
+        return crc;
+    }
+
+    private static final byte HEADER_MAGIC_NUMBER = (byte) 186;
+    private static final byte PROTOCOL_VERSION_0 = (byte) 0;
+
+    private static byte[] encodeMessage(byte[] commandData) {
+        // msgType | 1: single message or end of multi-part message, 3: one part of a multi-part message
+        byte msgType = 1;
+        byte unknown1 = 0; // 1 bit
+        byte unknown2 = 0; // 4 bits
+        int length = commandData.length;
+        int crc = calculateCrc(commandData);
+        int msgCounter = 0; // returned with the response
+
+        byte[] header = new byte[8];
+        header[0] = HEADER_MAGIC_NUMBER;
+        header[1] = (byte) ((msgType << 5) | (unknown1 << 4) | unknown2);
+        header[2] = (byte) ((length >> 8) & 0xFF);
+        header[3] = (byte) (length & 0xFF);
+        header[4] = (byte) ((crc >> 8) & 0xFF);
+        header[5] = (byte) (crc & 0xFF);
+        header[6] = (byte) ((msgCounter >> 8) & 0xFF);
+        header[7] = (byte) (msgCounter & 0xFF);
+
+        return byteArrayAdd(header, commandData);
+    }
+
+
+    private static byte[] encodeCommand(byte cmd, byte cmdKey, byte[] argsData) {
+        int length = (argsData != null) ? argsData.length : 0;
+
+        byte[] header = new byte[5];
+        header[0] = cmd;
+        header[1] = PROTOCOL_VERSION_0;
+        header[2] = cmdKey;
+        header[3] = (byte) ((length >> 8) & 1);
+        header[4] = (byte) (length & 0xFF);
+
+        return byteArrayAdd(header, argsData);
+    }
+
+    public static byte[] encodeCommandMessage(int cmd, int cmdKey, byte[] cmdArgsData) {
+        assert (cmd > 0);
+        assert (cmd <= 0xFF);
+        assert (cmdKey > 0);
+        assert (cmdKey <= 0xFF);
+        return encodeMessage(encodeCommand((byte) cmd, (byte) cmdKey, cmdArgsData));
+    }
+
+    public static class CommandMessage { // decoded message
+        public byte msgType;
+
+        public int cmd;
+        public int key;
+        public byte[] cmdArgs;
+    }
+
+
+    public static CommandMessage decodeCommandMessage(byte[] data) {
+        final int MESSAGE_HEADER_SIZE = 8;
+        final int COMMAND_HEADER_SIZE = 5;
+
+        if (data[0] != HEADER_MAGIC_NUMBER) {
+            return null;
+        }
+
+        byte messageType = (byte) (data[1] >> 5);
+        int messageLength = ((data[2] & 0xFF) << 8) | (data[3] & 0xFF);
+        int crcReceived = ((data[4] & 0xFF) << 8) | (data[5] & 0xFF);
+        //int msgCounter = ((data[6] & 0xFF) << 8) | (data[7] & 0xFF);
+
+        if (data.length != MESSAGE_HEADER_SIZE + messageLength) {
+            return null;
+        }
+
+        {
+            byte[] commandData = new byte[messageLength];
+            System.arraycopy(data, MESSAGE_HEADER_SIZE, commandData, 0, messageLength);
+
+            int crc = calculateCrc(commandData);
+            if (crc != crcReceived) {
+                return null;
+            }
+        }
+
+        int cmd = data[MESSAGE_HEADER_SIZE + 0] & 0xFF;
+        int version = data[MESSAGE_HEADER_SIZE + 1] & 0xFF;
+        int key = data[MESSAGE_HEADER_SIZE + 2] & 0xFF;
+        int commandLength = ((data[MESSAGE_HEADER_SIZE + 3] & 1) << 8) | (data[MESSAGE_HEADER_SIZE + 4] & 0xFF);
+
+        if (version != PROTOCOL_VERSION_0) {
+            return null;
+        }
+
+        if (data.length != MESSAGE_HEADER_SIZE + COMMAND_HEADER_SIZE + commandLength) {
+            return null;
+        }
+
+        byte[] commandArgs = new byte[commandLength];
+        System.arraycopy(data, MESSAGE_HEADER_SIZE + COMMAND_HEADER_SIZE, commandArgs, 0, commandLength);
+
+        CommandMessage result = new CommandMessage();
+        result.msgType = messageType;
+        result.cmd = cmd;
+        result.key = key;
+        result.cmdArgs = commandArgs;
+
+        return result;
+    }
+
+    public static byte[] encodeGetFirmwareVersion() {
+        return encodeCommandMessage(1, 18, null);
+    }
+
+    public static byte[] encodeGetBatteryStatus() {
+        return encodeCommandMessage(4, 64, null);
+    }
+
+    private static void encodeDateTime(byte[] data, int dataOffset, int year, int month, int day, int hour, int minute, int second) {
+        data[dataOffset + 0] = (byte) ((year % 100) & 0xFF);
+        data[dataOffset + 1] = (byte) (month & 0xFF);
+        data[dataOffset + 2] = (byte) (day & 0xFF);
+        data[dataOffset + 3] = (byte) (hour & 0xFF);
+        data[dataOffset + 4] = (byte) (minute & 0xFF);
+        data[dataOffset + 5] = (byte) (second & 0xFF);
+    }
+
+    public enum TimeFormat {
+        Format12H,
+        Format24H
+    }
+
+    public static byte[] encodeSetDateTime(Calendar dt, TimeFormat timeFormat) {
+        byte[] args = new byte[7];
+        encodeDateTime(args, 0, dt.get(Calendar.YEAR), dt.get(Calendar.MONTH) + 1,
+                dt.get(Calendar.DAY_OF_MONTH), dt.get(Calendar.HOUR_OF_DAY), dt.get(Calendar.MINUTE), dt.get(Calendar.SECOND));
+        // time format value is optional. shorter message is also accepted
+        args[6] = encodeTimeFormat(timeFormat);
+        return encodeCommandMessage(2, 32, args);
+    }
+
+    public enum Gender {
+        FEMALE,
+        MALE
+    }
+
+    public static byte[] encodeSetUserInfo(Gender gender, int age, int heightCm, int weightKg, int stepsGoal) {
+        byte[] args = new byte[8];
+        args[0] = (byte) ((gender == Gender.MALE) ? 1 : 0);
+        args[1] = (byte) (age & 0xFF);
+        args[2] = (byte) (heightCm & 0xFF);
+        args[3] = (byte) (weightKg & 0xFF);
+        encodeInt32Goal(args, 4, stepsGoal);
+
+        return encodeCommandMessage(2, 35, args);
+    }
+
+    public static byte[] encodeFindDevice() {
+        return encodeCommandMessage(5, 80, null);
+    }
+
+    public enum NotificationType {
+        INCOMING_CALL(0), // shows popup with hang up button
+
+        SMS(1),
+        MQQ(2),
+        WEIXIN(3),
+        FACEBOOK(4),
+        TWITTER(6),
+        WHATSAPP(7),
+        INSTAGRAM(8),
+        LINKEDIN(9),
+
+        CALL_REJECT(15), // closes INCOMING_CALL notification
+        CALL_ACCEPT(16), // closes INCOMING call notification
+
+        UNKNOWN(255);
+
+        private final byte value;
+
+        NotificationType(final int value) {
+            this.value = (byte) value;
+        }
+
+        public byte getValue() {
+            return this.value;
+        }
+    }
+
+    public static byte[] encodeShowNotification(NotificationType type, String text) {
+        final int TEXT_LENGTH_MAX = 64;
+
+        text = text.trim();
+        if (text.length() > TEXT_LENGTH_MAX) {
+            text = StringUtils.truncate(text, TEXT_LENGTH_MAX);
+            text = text.trim(); // trim again so text is centered on screen
+        }
+
+        byte[] args = byteArrayAdd(new byte[]{type.getValue()}, text.getBytes(StandardCharsets.UTF_16LE));
+        return encodeCommandMessage(6, 96, args);
+    }
+
+    public static byte[] encodeSetAlarms(ArrayList<? extends Alarm> alarms) {
+        final int ENTRY_COUNT = 5;
+        final int ENTRY_SIZE = 5;
+
+        byte[] args = new byte[ENTRY_COUNT * ENTRY_SIZE];
+        Arrays.fill(args, (byte) 0);
+
+        int offset = 0;
+        for (Alarm alarm : alarms) {
+            // When all properties of an alarm are 0, it will not be listed in the watch UI.
+            // Disabled alarms with non-0 properties will be shown in disabled state.
+            // Show only enabled:
+            if (alarm.getEnabled() && !alarm.getUnused()) {
+                args[offset + 0] = (byte) alarm.getHour();
+                args[offset + 1] = (byte) alarm.getMinute();
+                args[offset + 2] = (byte) alarm.getRepetition();
+                args[offset + 3] = (byte) 1;
+                args[offset + 4] = encodeBoolean(alarm.getEnabled());
+
+                offset += ENTRY_SIZE;
+            }
+        }
+
+        return encodeCommandMessage(2, 33, args);
+    }
+
+    private static final Map<String, Integer> LANGUAGES = new HashMap<String, Integer>() {{
+        put("en", 1);
+        put("es", 3);
+        put("de", 4);
+        put("it", 5);
+        put("fr", 6);
+        put("sv", 18);
+
+        put("ru", 2);
+        put("pt", 7);
+        put("pl", 8);
+        put("nl", 9);
+        put("el", 10);
+        put("tr", 11);
+        put("ro", 12);
+        put("ja", 13);
+        put("he", 15);
+        put("da", 16);
+        put("sr", 17);
+        put("cs", 19);
+        put("sk", 20);
+        put("hu", 21);
+        put("ar", 22);
+        put("bg", 23);
+        put("th", 24);
+        put("uk", 25);
+        put("fi", 26);
+        put("nb", 27);
+        put("ko", 28);
+        put("id", 29);
+        put("lv", 30);
+        put("lt", 31);
+        put("et", 32);
+        put("my", 33);
+        put("vi", 34);
+        put("hr", 35);
+    }};
+
+    private static int resolveLanguageId(String language_, String country_) {
+        String language = language_.toLowerCase();
+        String country = country_.toLowerCase();
+
+        if (language.equals("zh")) {
+            switch (country) {
+                case "cn":
+                    return 0;
+                case "tw":
+                case "hk":
+                case "mo":
+                    return 14;
+            }
+        } else if (language.equals("pt") && country.equals("br")) {
+            return 36;
+        } else {
+            Integer languageId = LANGUAGES.get(language);
+            if (languageId != null)
+                return languageId;
+        }
+
+        return LANGUAGES.get("en");
+    }
+
+    public static byte[] encodeSetSystemData(String lang, String country, TimeFormat timeFormat) {
+        byte[] args = new byte[4];
+        args[0] = (byte) resolveLanguageId(lang, country);
+        args[1] = encodeTimeFormat(timeFormat);
+        args[2] = (byte) 60; // screen. unclear
+        args[3] = (byte) 0; // pair. 1 has no effect
+
+        return encodeCommandMessage(2, 39, args);
+    }
+
+    public static byte[] encodeSetDoNotDisturb(boolean enable, int startHour, int startMinute, int endHour, int endMinute) {
+        byte[] args = new byte[5];
+        args[0] = encodeBoolean(enable);
+        args[1] = (byte) startHour;
+        args[2] = (byte) startMinute;
+        args[3] = (byte) endHour;
+        args[4] = (byte) endMinute;
+
+        return encodeCommandMessage(6, 100, args);
+    }
+
+    public static byte[] encodeSetUnit(boolean isMetric) {
+        byte[] args = new byte[2];
+        args[0] = args[1] = (byte) ((isMetric) ? 0 : 1); // 0: metric, 1: imperial
+        return encodeCommandMessage(2, 1, args);
+    }
+
+    public static byte[] encodeSetLiftWristDisplayOn(boolean enable) {
+        byte[] args = new byte[3];
+        args[0] = (byte) 1; // hand
+        args[1] = encodeBoolean(enable); // raise
+        args[2] = encodeBoolean(enable); // wrist
+
+        return encodeCommandMessage(4, 74, args);
+    }
+
+    public static byte[] encodeSetAutoHeartRate(boolean enable, int startHour, int startMinute, int endHour, int endMinute, int intervalMinutes) {
+        byte[] args = new byte[7];
+        args[0] = encodeBoolean(enable);
+        args[1] = (byte) startHour;
+        args[2] = (byte) startMinute;
+        args[3] = (byte) endHour;
+        args[4] = (byte) endMinute;
+        encodeInt16(args, 5, intervalMinutes);
+
+        return encodeCommandMessage(9, 146, args);
+    }
+
+    public static byte[] encodeSetHydrationReminder(boolean enable, int startHour, int startMinute, int endHour, int endMinute, int intervalMinutes) {
+        byte[] args = new byte[8];
+        args[0] = encodeBoolean(enable);
+        args[1] = (byte) startHour;
+        args[2] = (byte) startMinute;
+        args[3] = (byte) endHour;
+        args[4] = (byte) endMinute;
+        args[5] = (byte) 0x7F; // repeat: bitmask for days of week, bit 0=Monday
+        encodeInt16(args, 6, intervalMinutes);
+
+        return encodeCommandMessage(2, 40, args);
+    }
+
+    public static byte[] encodeFactoryReset() {
+        return encodeCommandMessage(2, 199, null);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/hplus/EXRIZUK8Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/hplus/EXRIZUK8Coordinator.java
new file mode 100644
index 0000000..ce7784f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/hplus/EXRIZUK8Coordinator.java
@@ -0,0 +1,47 @@
+/*  Copyright (C) 2017-2024 Daniel Dakhno, Daniele Gobbetti, Jos茅 Rebelo,
+    Quallenauge
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.hplus;
+
+/*
+* @author Quallenauge &lt;Hamsi2k@freenet.de&gt;
+*/
+
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+
+/**
+ * Pseudo Coordinator for the EXRIZU K8, a sub type of the HPLUS devices
+ */
+public class EXRIZUK8Coordinator extends HPlusCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("iRun .*");
+    }
+
+    @Override
+    public String getManufacturer() {
+        return "Exrizu";
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_exrizu_k8;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/hplus/HPlusConstants.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/hplus/HPlusConstants.java
new file mode 100644
index 0000000..bf94879
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/hplus/HPlusConstants.java
@@ -0,0 +1,191 @@
+/*  Copyright (C) 2016-2024 Andreas Shimokawa, Carsten Pfeiffer, Joa虄o
+    Paulo Barraca, Lesur Frederic
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.hplus;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+public final class HPlusConstants {
+
+    public static final UUID UUID_CHARACTERISTIC_CONTROL = UUID.fromString("14702856-620a-3973-7c78-9cfff0876abd");
+    public static final UUID UUID_CHARACTERISTIC_MEASURE = UUID.fromString("14702853-620a-3973-7c78-9cfff0876abd");
+    public static final UUID UUID_SERVICE_HP = UUID.fromString("14701820-620a-3973-7c78-9cfff0876abd");
+
+    public static final byte ARG_WRIST_LEFT = 0; //Guess...
+    public static final byte ARG_WRIST_RIGHT = 1; //Guess...
+
+    public static final byte ARG_LANGUAGE_CN = 1;
+    public static final byte ARG_LANGUAGE_EN = 2;
+
+    public static final byte ARG_TIMEMODE_24H = 1;
+    public static final byte ARG_TIMEMODE_12H = 0;
+
+    public static final byte ARG_UNIT_METRIC = 0;
+    public static final byte ARG_UNIT_IMPERIAL = 1;
+
+    public static final byte ARG_GENDER_MALE = 0;
+    public static final byte ARG_GENDER_FEMALE = 1;
+
+    public static final byte ARG_HEARTRATE_MEASURE_ON = 11;
+    public static final byte ARG_HEARTRATE_MEASURE_OFF = 22;
+
+    public static final byte ARG_HEARTRATE_ALLDAY_ON = 0x0A;
+    public static final byte ARG_HEARTRATE_ALLDAY_OFF = (byte) 0xff;
+
+    public static final byte INCOMING_CALL_STATE_DISABLED_THRESHOLD = 0x7B;
+    public static final byte INCOMING_CALL_STATE_ENABLED = (byte) 0xAA;
+
+    public static final byte ARG_ALARM_DISABLE = (byte) -1;
+
+    public static final byte[] CMD_SET_PREF_START = new byte[]{0x4f, 0x5a};
+    public static final byte[] CMD_SET_PREF_START1 = new byte[]{0x4d};
+    //public static final byte CMD_SET_ALARM = 0x4c; Unknown
+    public static final byte CMD_SET_ALARM = 0x0c;
+    public static final byte CMD_SET_LANGUAGE = 0x22;
+    public static final byte CMD_SET_TIMEMODE = 0x47;
+    public static final byte CMD_SET_UNITS = 0x48;
+    public static final byte CMD_SET_GENDER = 0x2d;
+    public static final byte CMD_SET_DATE = 0x08;
+    public static final byte CMD_SET_TIME = 0x09;
+    public static final byte CMD_SET_WEEK = 0x2a;
+    public static final byte CMD_SET_PREF_SIT = 0x1e;
+    public static final byte CMD_SET_WEIGHT = 0x05;
+    public static final byte CMD_HEIGHT = 0x04;
+    public static final byte CMD_SET_AGE = 0x2c;
+    public static final byte CMD_SET_GOAL = 0x26;
+    public static final byte CMD_SET_SCREENTIME = 0x0b;
+    public static final byte CMD_SET_BLOOD = 0x4e; //??
+
+    public static final byte CMD_SET_FINDME = 0x0a;
+    public static final byte ARG_FINDME_ON = 0x01;
+    public static final byte ARG_FINDME_OFF = 0x02;
+
+    public static final byte CMD_SET_END = 0x4f;
+    public static final byte CMD_SET_INCOMING_CALL_NUMBER = 0x23;
+    public static final byte CMD_SET_ALLDAY_HRM = 0x35;
+    public static final byte CMD_ACTION_INCOMING_CALL = 0x41;
+    public static final byte CMD_SET_CONF_END = 0x4f;
+    public static final byte CMD_SET_PREFS = 0x50;
+    public static final byte CMD_SET_SIT_INTERVAL = 0x51;
+    public static final byte CMD_SET_HEARTRATE_STATE = 0x32;
+    public static final byte CMD_SET_WEATHER_STATE = 0x5f;
+
+    //GET messages
+    public static final byte CMD_GET_VERSION = 0x17;
+    public static final byte CMD_GET_ACTIVE_DAY = 0x27;
+    public static final byte CMD_GET_DAY_DATA = 0x15;
+    public static final byte CMD_GET_SLEEP = 0x19;
+    public static final byte CMD_GET_CURR_DATA = 0x16;
+    public static final byte CMD_GET_DEVICE_ID = 0x24;
+
+    public static final byte CMD_ACTION_INCOMING_SOCIAL = 0x31;
+    //public static final byte COMMAND_ACTION_INCOMING_SMS = 0x40; //Unknown
+    public static final byte CMD_ACTION_DISPLAY_TEXT = 0x43;
+
+    public static final byte CMD_ACTION_DISPLAY_TEXT_NAME = 0x3F;
+    public static final byte CMD_ACTION_DISPLAY_TEXT_NAME_CN = 0x3E; //Text in GB2312?
+    public static final byte[] CMD_ACTION_HELLO = new byte[]{0x01, 0x00};
+    public static final byte CMD_SHUTDOWN = 0x5B;
+    public static final byte ARG_SHUTDOWN_EN = 0x5A;
+
+    public static final byte CMD_FACTORY_RESET = -74;
+    public static final byte ARG_FACTORY_RESET_EN = 0x5A;
+
+    public static final byte CMD_SET_INCOMING_MESSAGE = 0x07;
+    public static final byte CMD_SET_INCOMING_CALL = 0x06;
+    public static final byte ARG_INCOMING_CALL = (byte) -86;
+    public static final byte ARG_INCOMING_MESSAGE = (byte) -86;
+
+    //Incoming Messages
+    public static final byte DATA_STATS = 0x33;
+    public static final byte DATA_STEPS = 0x36;
+    public static final byte DATA_DAY_SUMMARY = 0x38;
+    public static final byte DATA_DAY_SUMMARY_ALT = 0x39;
+    public static final byte DATA_SLEEP = 0x1A;
+    public static final byte DATA_VERSION = 0x18;
+    public static final byte DATA_VERSION1 = 0x2E;
+    public static final byte DATA_DAY_UNKNOWN = 0x52; //To be defined
+    public static final byte DATA_UNKNOWN = 0x4d;
+
+    public static final String PREF_HPLUS_SCREENTIME = "hplus_screentime";
+    public static final String PREF_HPLUS_ALLDAYHR = "hplus_alldayhr";
+    public static final String PREF_HPLUS_SIT_START_TIME = "hplus_sit_start_time";
+    public static final String PREF_HPLUS_SIT_END_TIME = "hplus_sit_end_time";
+    public static final String PREF_HPLUS_UNICODE = "hplus_unicode";
+    public static final String PREF_HPLUS_DISPLAY_NOTIFICATION_ICON = "hplus_display_notification_icon";
+    public static final String PREF_HPLUS_NOTIFICATION_LINES = "hplus_notification_lines";
+
+    public static final Map<Character, byte[]> transliterateMap = new HashMap<Character, byte[]>() {
+        {
+            //These are missing
+            put('贸', new byte[]{(byte) 111});
+            put('脫', new byte[]{(byte) 79});
+            put('铆', new byte[]{(byte) 105});
+            put('脥', new byte[]{(byte) 73});
+            put('煤', new byte[]{(byte) 117});
+            put('脷', new byte[]{(byte) 85});
+
+            //These mostly belong to the extended ASCII table
+            put('脟', new byte[]{(byte) 128});
+            put('眉', new byte[]{(byte) 129});
+            put('茅', new byte[]{(byte) 130});
+            put('芒', new byte[]{(byte) 131});
+            put('盲', new byte[]{(byte) 132});
+            put('脿', new byte[]{(byte) 133});
+            put('茫', new byte[]{(byte) 134});
+            put('莽', new byte[]{(byte) 135});
+            put('锚', new byte[]{(byte) 136});
+            put('毛', new byte[]{(byte) 137});
+            put('脧', new byte[]{(byte) 139});
+            put('猫', new byte[]{(byte) 138});
+            put('脦', new byte[]{(byte) 140});
+            put('脤', new byte[]{(byte) 141});
+            put('脙', new byte[]{(byte) 142});
+            put('脛', new byte[]{(byte) 143});
+            put('脡', new byte[]{(byte) 144});
+            put('忙', new byte[]{(byte) 145});
+            put('脝', new byte[]{(byte) 146});
+            put('么', new byte[]{(byte) 147});
+            put('枚', new byte[]{(byte) 148});
+            put('貌', new byte[]{(byte) 149});
+            put('没', new byte[]{(byte) 150});
+            put('霉', new byte[]{(byte) 151});
+            put('每', new byte[]{(byte) 152});
+            put('脰', new byte[]{(byte) 153});
+            put('脺', new byte[]{(byte) 154});
+            put('垄', new byte[]{(byte) 155});
+            put('拢', new byte[]{(byte) 156});
+            put('楼', new byte[]{(byte) 157});
+            put('茠', new byte[]{(byte) 159});
+            put('谩', new byte[]{(byte) 160});
+            put('帽', new byte[]{(byte) 164});
+            put('脩', new byte[]{(byte) 165});
+            put('陋', new byte[]{(byte) 166});
+            put('潞', new byte[]{(byte) 167});
+            put('驴', new byte[]{(byte) 168});
+            put('卢', new byte[]{(byte) 170});
+            put('陆', new byte[]{(byte) 171});
+            put('录', new byte[]{(byte) 172});
+            put('隆', new byte[]{(byte) 173});
+            put('芦', new byte[]{(byte) 174});
+            put('禄', new byte[]{(byte) 175});
+            put('掳', new byte[]{(byte) 0xa1, (byte) 0xe3});
+        }
+    };
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/hplus/HPlusCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/hplus/HPlusCoordinator.java
new file mode 100644
index 0000000..5e06514
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/hplus/HPlusCoordinator.java
@@ -0,0 +1,273 @@
+/*  Copyright (C) 2016-2024 Andreas Shimokawa, Carsten Pfeiffer, Damien
+    Gaignon, Daniel Dakhno, Daniele Gobbetti, Joa虄o Paulo Barraca, Jos茅 Rebelo,
+    Lesur Frederic, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.hplus;
+
+import android.bluetooth.le.ScanFilter;
+import android.content.SharedPreferences;
+import android.os.ParcelUuid;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Locale;
+import java.util.regex.Pattern;
+
+import androidx.annotation.NonNull;
+
+import de.greenrobot.dao.query.QueryBuilder;
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.GBException;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.SettingsActivity;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLEDeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.entities.HPlusHealthActivitySampleDao;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.hplus.HPlusSupport;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+import static nodomain.freeyourgadget.gadgetbridge.GBApplication.getContext;
+
+public class HPlusCoordinator extends AbstractBLEDeviceCoordinator {
+    protected static final Logger LOG = LoggerFactory.getLogger(HPlusCoordinator.class);
+    protected static Prefs prefs = GBApplication.getPrefs();
+
+    @NonNull
+    @Override
+    public Collection<? extends ScanFilter> createBLEScanFilters() {
+        ParcelUuid hpService = new ParcelUuid(HPlusConstants.UUID_SERVICE_HP);
+        ScanFilter filter = new ScanFilter.Builder().setServiceUuid(hpService).build();
+        return Collections.singletonList(filter);
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("HPLUS.*");
+    }
+
+    @Override
+    public int getBondingStyle() {
+        return BONDING_STYLE_NONE;
+    }
+
+    @Override
+    public boolean supportsRealtimeData() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsFindDevice() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsActivityDataFetching() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsActivityTracking() {
+        return true;
+    }
+
+    @Override
+    public SampleProvider<? extends ActivitySample> getSampleProvider(GBDevice device, DaoSession session) {
+        return new HPlusHealthSampleProvider(device, session);
+    }
+
+    @Override
+    public int getAlarmSlotCount(GBDevice device) {
+        return 3; // FIXME - check the real value
+    }
+
+    @Override
+    public boolean supportsHeartRateMeasurement(GBDevice device) {
+        return true;
+    }
+
+    @Override
+    public String getManufacturer() {
+        return "Zeblaze";
+    }
+
+    @Override
+    protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
+        Long deviceId = device.getId();
+        QueryBuilder<?> qb = session.getHPlusHealthActivitySampleDao().queryBuilder();
+        qb.where(HPlusHealthActivitySampleDao.Properties.DeviceId.eq(deviceId)).buildDelete().executeDeleteWithoutDetachingEntities();
+    }
+
+    public static byte getLanguage(String address) {
+        String language = prefs.getString("language", "default");
+        Locale locale;
+
+        if (language.equals("default")) {
+            locale = Locale.getDefault();
+        } else {
+            locale = new Locale(language);
+        }
+
+        if (locale.getLanguage().equals(new Locale("cn").getLanguage())) {
+            return HPlusConstants.ARG_LANGUAGE_CN;
+        } else {
+            return HPlusConstants.ARG_LANGUAGE_EN;
+        }
+    }
+
+    public static byte getUnit(String address) {
+        String units = prefs.getString(SettingsActivity.PREF_MEASUREMENT_SYSTEM, getContext().getString(R.string.p_unit_metric));
+
+        if (units.equals(getContext().getString(R.string.p_unit_metric))) {
+            return HPlusConstants.ARG_UNIT_METRIC;
+        } else {
+            return HPlusConstants.ARG_UNIT_IMPERIAL;
+        }
+    }
+
+    public static byte getUserWeight() {
+        ActivityUser activityUser = new ActivityUser();
+
+        return (byte) (activityUser.getWeightKg() & 0xFF);
+    }
+
+    public static byte getUserHeight() {
+        ActivityUser activityUser = new ActivityUser();
+
+        return (byte) (activityUser.getHeightCm() & 0xFF);
+    }
+
+    public static byte getUserAge() {
+        ActivityUser activityUser = new ActivityUser();
+
+        return (byte) (activityUser.getAge() & 0xFF);
+    }
+
+    public static byte getUserGender() {
+        ActivityUser activityUser = new ActivityUser();
+
+        if (activityUser.getGender() == ActivityUser.GENDER_MALE)
+            return HPlusConstants.ARG_GENDER_MALE;
+
+        return HPlusConstants.ARG_GENDER_FEMALE;
+    }
+
+    public static int getGoal() {
+        ActivityUser activityUser = new ActivityUser();
+
+        return activityUser.getStepsGoal();
+    }
+
+    public static byte getSocial(String address) {
+        //TODO: Figure what this is. Returning the default value
+
+        return (byte) 255;
+    }
+
+    //FIXME: unused
+    public static byte getUserWrist(String deviceAddress) {
+        SharedPreferences sharedPreferences = GBApplication.getDeviceSpecificSharedPrefs(deviceAddress);
+        String value = sharedPreferences.getString(DeviceSettingsPreferenceConst.PREF_WEARLOCATION, "left");
+
+        if ("left".equals(value)) {
+            return HPlusConstants.ARG_WRIST_LEFT;
+        } else {
+            return HPlusConstants.ARG_WRIST_RIGHT;
+        }
+    }
+
+    public static int getSITStartTime(String address) {
+        return prefs.getInt(HPlusConstants.PREF_HPLUS_SIT_START_TIME, 0);
+    }
+
+    public static int getSITEndTime(String address) {
+        return prefs.getInt(HPlusConstants.PREF_HPLUS_SIT_END_TIME, 0);
+    }
+
+    public static void setDisplayIncomingMessageIcon(String address, boolean state) {
+        SharedPreferences.Editor editor = prefs.getPreferences().edit();
+        editor.putBoolean(HPlusConstants.PREF_HPLUS_DISPLAY_NOTIFICATION_ICON + "_" + address, state);
+        editor.apply();
+    }
+
+    public static boolean getDisplayIncomingMessageIcon(String address) {
+        return (prefs.getBoolean(HPlusConstants.PREF_HPLUS_DISPLAY_NOTIFICATION_ICON + "_" + address, false));
+    }
+
+    public static void setUnicodeSupport(String address, boolean state) {
+        SharedPreferences.Editor editor = prefs.getPreferences().edit();
+        editor.putBoolean(HPlusConstants.PREF_HPLUS_UNICODE + "_" + address, state);
+        editor.apply();
+    }
+
+    public static boolean getUnicodeSupport(String address) {
+        return (prefs.getBoolean(HPlusConstants.PREF_HPLUS_UNICODE + "_" + address, false));
+    }
+
+    public static void setNotificationLinesNumber(String address, int lineNumber) {
+        SharedPreferences.Editor editor = prefs.getPreferences().edit();
+        editor.putInt(HPlusConstants.PREF_HPLUS_NOTIFICATION_LINES + "_" + address, lineNumber);
+        editor.apply();
+    }
+
+    public static int getNotificationLinesNumber(String address) {
+        return (prefs.getInt(HPlusConstants.PREF_HPLUS_NOTIFICATION_LINES + "_" + address, 5));
+    }
+
+    @Override
+    public int[] getSupportedDeviceSpecificSettings(GBDevice device) {
+        return new int[]{
+                //R.xml.devicesettings_wearlocation, // disabled, since it is never used in code
+                R.xml.devicesettings_timeformat,
+                R.xml.devicesettings_hplus,
+                R.xml.devicesettings_transliteration
+        };
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return HPlusSupport.class;
+    }
+
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_hplus;
+    }
+
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_hplus;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_hplus_disabled;
+    }
+}
+    
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/hplus/HPlusHealthSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/hplus/HPlusHealthSampleProvider.java
new file mode 100644
index 0000000..17d8ef8
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/hplus/HPlusHealthSampleProvider.java
@@ -0,0 +1,240 @@
+/*  Copyright (C) 2017-2024 Andreas Shimokawa, Daniele Gobbetti, Joa虄o
+    Paulo Barraca
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.hplus;
+
+/*
+ * @author Jo茫o Paulo Barraca &lt;jpbarraca@gmail.com&gt;
+ */
+
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.GregorianCalendar;
+import java.util.List;
+
+import androidx.annotation.NonNull;
+
+import de.greenrobot.dao.AbstractDao;
+import de.greenrobot.dao.Property;
+import de.greenrobot.dao.query.QueryBuilder;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.AbstractSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.entities.HPlusHealthActivityOverlay;
+import nodomain.freeyourgadget.gadgetbridge.entities.HPlusHealthActivityOverlayDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.HPlusHealthActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.entities.HPlusHealthActivitySampleDao;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.hplus.HPlusDataRecord;
+
+public class HPlusHealthSampleProvider extends AbstractSampleProvider<HPlusHealthActivitySample> {
+    public HPlusHealthSampleProvider(GBDevice device, DaoSession session) {
+        super(device, session);
+    }
+
+    public ActivityKind normalizeType(int rawType) {
+        switch (rawType) {
+            case HPlusDataRecord.TYPE_DAY_SLOT:
+            case HPlusDataRecord.TYPE_DAY_SUMMARY:
+            case HPlusDataRecord.TYPE_REALTIME:
+            case HPlusDataRecord.TYPE_SLEEP:
+            case HPlusDataRecord.TYPE_UNKNOWN:
+                return ActivityKind.UNKNOWN;
+            default:
+                return ActivityKind.fromCode(rawType);
+        }
+    }
+
+    public int toRawActivityKind(ActivityKind activityKind) {
+        switch (activityKind) {
+            case DEEP_SLEEP:
+            case LIGHT_SLEEP:
+                return activityKind.getCode();
+            default:
+                return HPlusDataRecord.TYPE_DAY_SLOT;
+        }
+
+    }
+
+    @NonNull
+    @Override
+    protected Property getTimestampSampleProperty() {
+        return HPlusHealthActivitySampleDao.Properties.Timestamp;
+    }
+
+    @Override
+    public HPlusHealthActivitySample createActivitySample() {
+        return new HPlusHealthActivitySample();
+    }
+
+    @Override
+    protected Property getRawKindSampleProperty() {
+        return null; // HPlusHealthActivitySampleDao.Properties.RawKind;
+    }
+
+    @Override
+    public float normalizeIntensity(int rawIntensity) {
+        return rawIntensity / (float) 100.0;
+    }
+
+    @NonNull
+    @Override
+    protected Property getDeviceIdentifierSampleProperty() {
+        return HPlusHealthActivitySampleDao.Properties.DeviceId;
+    }
+
+    @Override
+    public AbstractDao<HPlusHealthActivitySample, ?> getSampleDao() {
+        return getSession().getHPlusHealthActivitySampleDao();
+    }
+
+    @NonNull
+    @Override
+    public List<HPlusHealthActivitySample> getAllActivitySamples(int timestamp_from, int timestamp_to) {
+        List<HPlusHealthActivitySample> samples = super.getGBActivitySamples(timestamp_from, timestamp_to);
+
+        Device dbDevice = DBHelper.findDevice(getDevice(), getSession());
+        if (dbDevice == null) {
+            return Collections.emptyList();
+        }
+
+        QueryBuilder<HPlusHealthActivityOverlay> qb = getSession().getHPlusHealthActivityOverlayDao().queryBuilder();
+
+        qb.where(HPlusHealthActivityOverlayDao.Properties.DeviceId.eq(dbDevice.getId()),
+                HPlusHealthActivityOverlayDao.Properties.TimestampFrom.ge(timestamp_from - 3600 * 24),
+                HPlusHealthActivityOverlayDao.Properties.TimestampTo.le(timestamp_to),
+                HPlusHealthActivityOverlayDao.Properties.TimestampTo.ge(timestamp_from));
+
+        List<HPlusHealthActivityOverlay> overlayRecords = qb.build().list();
+
+
+        //Apply Overlays
+        for (HPlusHealthActivityOverlay overlay : overlayRecords) {
+
+            //Create fake events to improve activity counters if there are no events around the overlay
+            //timestamp boundaries
+            //Insert one before, one at the beginning, one at the end, and one 1s after.
+            insertVirtualItem(samples, Math.max(overlay.getTimestampFrom() - 1, timestamp_from), overlay.getDeviceId(), overlay.getUserId());
+            insertVirtualItem(samples, Math.max(overlay.getTimestampFrom(), timestamp_from), overlay.getDeviceId(), overlay.getUserId());
+            insertVirtualItem(samples, Math.min(overlay.getTimestampTo() - 1, timestamp_to - 1), overlay.getDeviceId(), overlay.getUserId());
+            insertVirtualItem(samples, Math.min(overlay.getTimestampTo(), timestamp_to), overlay.getDeviceId(), overlay.getUserId());
+        }
+
+        Collections.sort(samples, new Comparator<HPlusHealthActivitySample>() {
+            public int compare(HPlusHealthActivitySample one, HPlusHealthActivitySample other) {
+                return one.getTimestamp() - other.getTimestamp();
+            }
+        });
+
+        //Apply Overlays
+        for (HPlusHealthActivityOverlay overlay : overlayRecords) {
+
+            long nonSleepTimeEnd = 0;
+            for (HPlusHealthActivitySample sample : samples) {
+                if (sample.getRawKind() == ActivityKind.NOT_WORN.getCode())
+                    continue;
+
+                if (sample.getTimestamp() >= overlay.getTimestampFrom() && sample.getTimestamp() < overlay.getTimestampTo()) {
+                    if (overlay.getRawKind() == ActivityKind.NOT_WORN.getCode() || overlay.getRawKind() == ActivityKind.LIGHT_SLEEP.getCode() || overlay.getRawKind() == ActivityKind.DEEP_SLEEP.getCode()) {
+                        if (sample.getRawKind() == HPlusDataRecord.TYPE_DAY_SLOT && sample.getSteps() > 0) {
+                            nonSleepTimeEnd = sample.getTimestamp() + 10 * 60; // 10 minutes
+                            continue;
+                        } else if (sample.getRawKind() == HPlusDataRecord.TYPE_REALTIME && sample.getTimestamp() <= nonSleepTimeEnd) {
+                            continue;
+                        }
+
+                        if (overlay.getRawKind() == ActivityKind.NOT_WORN.getCode())
+                            sample.setHeartRate(0);
+
+                        if (sample.getRawKind() != ActivityKind.NOT_WORN.getCode())
+                            sample.setRawKind(overlay.getRawKind());
+
+                        sample.setRawIntensity(10);
+                    }
+                }
+            }
+        }
+
+
+        //Fix Step counters
+        //Todays sample steps will come from the Day Slots messages
+        //Historical steps will be provided by Day Summaries messages
+        //This will allow both week and current day results to be consistent
+        Calendar today = GregorianCalendar.getInstance();
+        today.set(Calendar.HOUR_OF_DAY, 0);
+        today.set(Calendar.MINUTE, 0);
+        today.set(Calendar.SECOND, 0);
+        today.set(Calendar.MILLISECOND, 0);
+
+        int stepsTodayMax = 0;
+        int stepsTodayCount = 0;
+        HPlusHealthActivitySample lastSample = null;
+
+        for (HPlusHealthActivitySample sample : samples) {
+            if (sample.getTimestamp() >= today.getTimeInMillis() / 1000) {
+
+                /* Strategy is:
+                 * Calculate max steps from realtime messages
+                 * Calculate sum of steps from day 10 minute slot summaries
+                 */
+
+                if (sample.getRawKind() == HPlusDataRecord.TYPE_REALTIME) {
+                    stepsTodayMax = Math.max(stepsTodayMax, sample.getSteps());
+                } else if (sample.getRawKind() == HPlusDataRecord.TYPE_DAY_SLOT) {
+                    stepsTodayCount += sample.getSteps();
+                }
+
+                sample.setSteps(ActivitySample.NOT_MEASURED);
+                lastSample = sample;
+            } else {
+                if (sample.getRawKind() != HPlusDataRecord.TYPE_DAY_SUMMARY) {
+                    sample.setSteps(ActivitySample.NOT_MEASURED);
+                }
+            }
+        }
+
+        if (lastSample != null)
+            lastSample.setSteps(Math.max(stepsTodayCount, stepsTodayMax));
+
+        detachFromSession();
+
+        return samples;
+    }
+
+    private void insertVirtualItem(List<HPlusHealthActivitySample> samples, int timestamp, long deviceId, long userId) {
+        HPlusHealthActivitySample sample = new HPlusHealthActivitySample(
+                timestamp,            // ts
+                deviceId,
+                userId,          // User id
+                null,                         // Raw Data
+                ActivityKind.UNKNOWN.getCode(),
+                1, // Intensity
+                ActivitySample.NOT_MEASURED, // Steps
+                ActivitySample.NOT_MEASURED, // HR
+                ActivitySample.NOT_MEASURED, // Distance
+                ActivitySample.NOT_MEASURED  // Calories
+        );
+
+        sample.setProvider(this);
+        samples.add(sample);
+    }
+}
+
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/hplus/HPlusWeatherCode.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/hplus/HPlusWeatherCode.java
new file mode 100644
index 0000000..e9216f3
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/hplus/HPlusWeatherCode.java
@@ -0,0 +1,197 @@
+/*  Copyright (C) 2020-2024 Lesur Frederic
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.hplus;
+
+public class HPlusWeatherCode {
+    // Weather code from https://github.com/heweather/WeatherIcon
+    public static final int SUNNY = 100;
+    public static final int CLOUDY = 101;
+    public static final int FEW_CLOUDS = 102;
+    public static final int PARTLY_CLOUDY = 103;
+    public static final int OVERCAST = 104;
+    public static final int CLEAR = 150;
+    public static final int PARTLY_CLOUDY_NIGHT = 153;
+    public static final int OVERCAST_NIGHT = 154;
+    public static final int SHOWER_RAIN = 300;
+    public static final int HEAVY_SHOWER_RAIN = 301;
+    public static final int THUNDERSHOWER = 302;
+    public static final int HEAVY_THUNDERSTORM = 303;
+    public static final int THUNDERSHOWER_WITH_HAIL = 304;
+    public static final int LIGHT_RAIN = 305;
+    public static final int MODERATE_RAIN = 306;
+    public static final int HEAVY_RAIN = 307;
+    public static final int EXTREME_RAIN = 308;
+    public static final int DRIZZLE_RAIN = 309;
+    public static final int STORM = 310;
+    public static final int HEAVY_STORM = 311;
+    public static final int SEVERE_STORM = 312;
+    public static final int FREEZING_RAIN = 313;
+    public static final int LIGHT_TO_MODERATE_RAIN = 314;
+    public static final int MODERATE_TO_HEAVY_RAIN = 315;
+    public static final int HEAVY_RAIN_TO_STORM = 316;
+    public static final int STORM_TO_HEAVY_STORM = 317;
+    public static final int HEAVY_TO_SEVERE_STORM = 318;
+    public static final int RAIN = 399;
+    public static final int SHOWER_RAIN_NIGHT = 350;
+    public static final int HEAVY_SHOWER_RAIN_NIGHT = 351;
+    public static final int LIGHT_SNOW = 400;
+    public static final int MODERATE_SNOW = 401;
+    public static final int HEAVY_SNOW = 402;
+    public static final int SNOWSTORM = 403;
+    public static final int SLEET = 404;
+    public static final int RAIN_AND_SNOW = 405;
+    public static final int SHOWER_SNOW = 406;
+    public static final int SNOW_FLURRY = 407;
+    public static final int LIGHT_TO_MODERATE_SNOW = 408;
+    public static final int MODERATE_TO_HEAVY_SNOW = 409;
+    public static final int HEAVY_SNOW_TO_SNOWSTORM = 410;
+    public static final int SNOW = 499;
+    public static final int SHOWER_SNOW_NIGHT = 456;
+    public static final int SNOW_FLURRY_NIGHT = 457;
+    public static final int MIST = 500;
+    public static final int FOGGY = 501;
+    public static final int HAZE = 502;
+    public static final int SAND = 503;
+    public static final int DUST = 504;
+    public static final int DUSTSTORM = 507;
+    public static final int SANDSTORM = 508;
+    public static final int DENSE_FOG = 509;
+    public static final int STRONG_FOG = 510;
+    public static final int MODERATE_HAZE = 511;
+    public static final int HEAVY_HAZE = 512;
+    public static final int SEVERE_HAZE = 513;
+    public static final int HEAVY_FOG = 514;
+    public static final int EXTRA_HEAVY_FOG = 515;
+    public static final int HOT = 900;
+    public static final int COLD = 901;
+    public static final int UNKNOWN = 999;
+
+    public static final int mapOpenWeatherConditionToHPlusCondition(int openWeatherMapCondition) {
+        switch (openWeatherMapCondition) {
+            //Group 2xx: Thunderstorm
+            case 200:  //thunderstorm with light rain:  //11d
+                return HPlusWeatherCode.STORM;
+            case 201:  //thunderstorm with rain:  //11d
+            case 202:  //thunderstorm with heavy rain:  //11d
+                return HPlusWeatherCode.HEAVY_RAIN_TO_STORM;
+            case 210:  //light thunderstorm::  //11d
+            case 211:  //thunderstorm:  //11d
+            case 230:  //thunderstorm with light drizzle:  //11d
+            case 231:  //thunderstorm with drizzle:  //11d
+            case 232:  //thunderstorm with heavy drizzle:  //11d
+            case 212:  //heavy thunderstorm:  //11d
+            case 221:  //ragged thunderstorm:  //11d
+                return HPlusWeatherCode.HEAVY_THUNDERSTORM;
+            //Group 3xx: Drizzle
+            case 300:  //light intensity drizzle:  //09d
+            case 301:  //drizzle:  //09d
+            case 302:  //heavy intensity drizzle:  //09d
+            case 310:  //light intensity drizzle rain:  //09d
+            case 311:  //drizzle rain:  //09d
+            case 312:  //heavy intensity drizzle rain:  //09d
+            case 313:  //shower rain and drizzle:  //09d
+            case 314:  //heavy shower rain and drizzle:  //09d
+            case 321:  //shower drizzle:  //09d
+                return HPlusWeatherCode.DRIZZLE_RAIN;
+            case 500:  //light rain:  //10d
+                return HPlusWeatherCode.LIGHT_RAIN;
+            case 501:  //moderate rain:  //10d
+                return HPlusWeatherCode.MODERATE_RAIN;
+            //Group 5xx: Rain
+            case 502:  //heavy intensity rain:  //10d
+                return HPlusWeatherCode.HEAVY_RAIN;
+            case 503:  //very heavy rain:  //10d
+                return HPlusWeatherCode.HEAVY_RAIN_TO_STORM;
+            case 504:  //extreme rain:  //10d
+                return HPlusWeatherCode.EXTREME_RAIN;
+            case 511:  //freezing rain:  //13d
+                return HPlusWeatherCode.FREEZING_RAIN;
+            case 520:  //light intensity shower rain:  //09d
+            case 521:  //shower rain:  //09d
+                return HPlusWeatherCode.SHOWER_RAIN;
+            case 522:  //heavy intensity shower rain:  //09d
+            case 531:  //ragged shower rain:  //09d
+                return HPlusWeatherCode.HEAVY_SHOWER_RAIN;
+            //Group 6xx: Snow
+            case 600:  //light snow:  //[[file:13d.png]]
+                return HPlusWeatherCode.LIGHT_SNOW;
+            case 601:  //snow:  //[[file:13d.png]]
+                return HPlusWeatherCode.SNOW;
+            case 620:  //light shower snow:  //[[file:13d.png]]
+                return HPlusWeatherCode.LIGHT_TO_MODERATE_SNOW;
+            case 602:  //heavy snow:  //[[file:13d.png]]
+                return HPlusWeatherCode.MODERATE_TO_HEAVY_SNOW;
+            case 611:  //sleet:  //[[file:13d.png]]
+            case 612:  //shower sleet:  //[[file:13d.png]]
+                return HPlusWeatherCode.SNOW_FLURRY;
+            case 621:  //shower snow:  //[[file:13d.png]]
+            case 622:  //heavy shower snow:  //[[file:13d.png]]
+                return HPlusWeatherCode.SHOWER_SNOW;
+            case 615:  //light rain and snow:  //[[file:13d.png]]
+            case 616:  //rain and snow:  //[[file:13d.png]]
+                return HPlusWeatherCode.RAIN_AND_SNOW;
+            //Group 7xx: Atmosphere
+            case 701:  //mist:  //[[file:50d.png]]
+            case 711:  //smoke:  //[[file:50d.png]]
+            case 721:  //haze:  //[[file:50d.png]]
+            case 731:  //sandcase  dust whirls:  //[[file:50d.png]]
+            case 741:  //fog:  //[[file:50d.png]]
+            case 751:  //sand:  //[[file:50d.png]]
+            case 761:  //dust:  //[[file:50d.png]]
+            case 762:  //volcanic ash:  //[[file:50d.png]]
+            case 771:  //squalls:  //[[file:50d.png]]
+            case 781:  //tornado:  //[[file:50d.png]]
+            case 900:  //tornado
+                return HPlusWeatherCode.SANDSTORM;
+            //Group 800: Clear
+            case 800:  //clear sky:  //[[file:01d.png]] [[file:01n.png]]
+                return HPlusWeatherCode.CLEAR;
+            //Group 80x: Clouds
+            case 801:  //few clouds:  //[[file:02d.png]] [[file:02n.png]]
+                return HPlusWeatherCode.FEW_CLOUDS;
+            case 802:  //scattered clouds:  //[[file:03d.png]] [[file:03d.png]]
+            case 803:  //broken clouds:  //[[file:04d.png]] [[file:03d.png]]
+                return HPlusWeatherCode.PARTLY_CLOUDY;
+            case 804:  //overcast clouds:  //[[file:04d.png]] [[file:04d.png]]
+                return HPlusWeatherCode.CLOUDY;
+
+            //Group 90x: Extreme
+            case 901:  //tropical storm
+            case 903:  //cold
+            case 904:  //hot
+            case 905:  //windy
+            case 906:  //hail
+                //Group 9xx: Additional
+            case 951:  //calm
+            case 952:  //light breeze
+            case 953:  //gentle breeze
+            case 954:  //moderate breeze
+            case 955:  //fresh breeze
+            case 956:  //strong breeze
+            case 957:  //high windcase  near gale
+            case 958:  //gale
+            case 959:  //severe gale
+            case 960:  //storm
+            case 961:  //violent storm
+            case 902:  //hurricane
+            case 962:  //hurricane
+                return HPlusWeatherCode.SEVERE_HAZE;
+            default:
+                return HPlusWeatherCode.UNKNOWN;
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/hplus/MakibesF68Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/hplus/MakibesF68Coordinator.java
new file mode 100644
index 0000000..e005a14
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/hplus/MakibesF68Coordinator.java
@@ -0,0 +1,56 @@
+/*  Copyright (C) 2017-2024 Daniel Dakhno, Daniele Gobbetti, Joa虄o Paulo
+    Barraca, Jos茅 Rebelo, Stan Gomin
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.hplus;
+
+/*
+* @author Jo茫o Paulo Barraca &lt;jpbarraca@gmail.com&gt;
+*/
+
+
+import androidx.annotation.NonNull;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+
+/**
+ * Pseudo Coordinator for the Makibes F68, a sub type of the HPLUS devices
+ */
+public class MakibesF68Coordinator extends HPlusCoordinator {
+
+    @NonNull
+    @Override
+    public boolean supports(GBDeviceCandidate candidate) {
+        String name = candidate.getName();
+        if(name != null && name.startsWith("SPORT") && !name.startsWith("SPORTAGE")){
+            return true;
+        }
+
+        return false;
+    }
+
+    @Override
+    public String getManufacturer() {
+        return "Makibes";
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_makibes_f68;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/hplus/Q8Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/hplus/Q8Coordinator.java
new file mode 100644
index 0000000..3ed48db
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/hplus/Q8Coordinator.java
@@ -0,0 +1,51 @@
+/*  Copyright (C) 2018-2024 Daniel Dakhno, Daniele Gobbetti, Jos茅 Rebelo,
+    tiparega
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.hplus;
+
+/*
+* @author Alejandro Ladera Chamorro &lt;11555126+tiparega@users.noreply.github.com&gt;
+*/
+
+
+import androidx.annotation.NonNull;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+
+/**
+ * Pseudo Coordinator for the Q8, a sub type of the HPLUS devices
+ */
+public class Q8Coordinator extends HPlusCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("Q8.*");
+    }
+
+    @Override
+    public String getManufacturer() {
+        return "Makibes";
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_q8;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/hplus/SG2Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/hplus/SG2Coordinator.java
new file mode 100644
index 0000000..09e9615
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/hplus/SG2Coordinator.java
@@ -0,0 +1,69 @@
+/*  Copyright (C) 2020-2024 Daniel Dakhno, Jos茅 Rebelo, Lesur Frederic
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.hplus;
+
+import androidx.annotation.NonNull;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+
+/**
+ * Pseudo Coordinator for the  Lemfo SG2, a sub type of the HPLUS devices
+ */
+public class SG2Coordinator extends HPlusCoordinator {
+
+    @NonNull
+    @Override
+    public boolean supports(GBDeviceCandidate candidate) {
+        String name = candidate.getName();
+        if (name != null && name.startsWith("SG2")) {
+            HPlusCoordinator.setNotificationLinesNumber(candidate.getDevice().getAddress(), 9);
+            HPlusCoordinator.setUnicodeSupport(candidate.getDevice().getAddress(), true);
+            HPlusCoordinator.setDisplayIncomingMessageIcon(candidate.getDevice().getAddress(), false);
+            return true;
+        }
+
+        return false;
+    }
+
+    @Override
+    public String getManufacturer() {
+        return "Lemfo";
+    }
+
+    @Override
+    public boolean supportsWeather() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsSmartWakeup(GBDevice device, int position) {
+        return true;
+    }
+
+    @Override
+    public int getBondingStyle() {
+        return BONDING_STYLE_ASK;
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_sg2;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/ActivateDisplayOnLift.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/ActivateDisplayOnLift.java
new file mode 100644
index 0000000..776b895
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/ActivateDisplayOnLift.java
@@ -0,0 +1,24 @@
+/*  Copyright (C) 2018-2024 Andreas Shimokawa, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami;
+
+public enum ActivateDisplayOnLift {
+    ON,
+    OFF,
+    SCHEDULED,
+    SMART
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/ActivateDisplayOnLiftSensitivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/ActivateDisplayOnLiftSensitivity.java
new file mode 100644
index 0000000..dc05df7
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/ActivateDisplayOnLiftSensitivity.java
@@ -0,0 +1,22 @@
+/*  Copyright (C) 2022-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami;
+
+public enum ActivateDisplayOnLiftSensitivity {
+    NORMAL,
+    SENSITIVE
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/AlwaysOnDisplay.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/AlwaysOnDisplay.java
new file mode 100644
index 0000000..d32c800
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/AlwaysOnDisplay.java
@@ -0,0 +1,24 @@
+/*  Copyright (C) 2022-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami;
+
+public enum AlwaysOnDisplay {
+    OFF,
+    AUTOMATIC,
+    SCHEDULED,
+    ALWAYS
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/DisconnectNotificationSetting.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/DisconnectNotificationSetting.java
new file mode 100644
index 0000000..13680c2
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/DisconnectNotificationSetting.java
@@ -0,0 +1,23 @@
+/*  Copyright (C) 2019-2024 Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami;
+
+public enum DisconnectNotificationSetting {
+    ON,
+    OFF,
+    SCHEDULED
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/Huami2021Service.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/Huami2021Service.java
new file mode 100644
index 0000000..4a7ecd6
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/Huami2021Service.java
@@ -0,0 +1,136 @@
+/*  Copyright (C) 2022-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami;
+
+public class Huami2021Service {
+    /**
+     * Endpoints for 2021 chunked protocol
+     */
+    public static final short CHUNKED2021_ENDPOINT_WEATHER = 0x000e;
+    public static final short CHUNKED2021_ENDPOINT_CONNECTION = 0x0015;
+    public static final short CHUNKED2021_ENDPOINT_USER_INFO = 0x0017;
+    public static final short CHUNKED2021_ENDPOINT_STEPS = 0x0016;
+    public static final short CHUNKED2021_ENDPOINT_VIBRATION_PATTERNS = 0x0018;
+    public static final short CHUNKED2021_ENDPOINT_WORKOUT = 0x0019;
+    public static final short CHUNKED2021_ENDPOINT_FIND_DEVICE = 0x001a;
+    public static final short CHUNKED2021_ENDPOINT_HEARTRATE = 0x001d;
+    public static final short CHUNKED2021_ENDPOINT_BATTERY = 0x0029;
+    public static final short CHUNKED2021_ENDPOINT_SILENT_MODE = 0x003b;
+    public static final short CHUNKED2021_ENDPOINT_AUTH = 0x0082;
+    public static final short CHUNKED2021_ENDPOINT_COMPAT = 0x0090;
+
+    /**
+     * Find Device, for {@link Huami2021Service#CHUNKED2021_ENDPOINT_FIND_DEVICE}.
+     */
+    public static final byte FIND_BAND_START = 0x03;
+    public static final byte FIND_BAND_ACK = 0x04;
+    public static final byte FIND_BAND_STOP_FROM_PHONE = 0x06;
+    public static final byte FIND_BAND_STOP_FROM_BAND = 0x07;
+    public static final byte FIND_PHONE_START = 0x11;
+    public static final byte FIND_PHONE_ACK = 0x12;
+    public static final byte FIND_PHONE_STOP_FROM_BAND = 0x13;
+    public static final byte FIND_PHONE_STOP_FROM_PHONE = 0x14;
+    public static final byte FIND_PHONE_MODE = 0x15;
+
+    /**
+     * Steps, for {@link Huami2021Service#CHUNKED2021_ENDPOINT_STEPS}.
+     */
+    public static final byte STEPS_CMD_GET = 0x03;
+    public static final byte STEPS_CMD_REPLY = 0x04;
+    public static final byte STEPS_CMD_ENABLE_REALTIME = 0x05;
+    public static final byte STEPS_CMD_ENABLE_REALTIME_ACK = 0x06;
+    public static final byte STEPS_CMD_REALTIME_NOTIFICATION = 0x07;
+
+    /**
+     * Vibration Patterns, for {@link Huami2021Service#CHUNKED2021_ENDPOINT_VIBRATION_PATTERNS}.
+     */
+    public static final byte VIBRATION_PATTERN_SET = 0x03;
+    public static final byte VIBRATION_PATTERN_ACK = 0x04;
+
+    /**
+     * Battery, for {@link Huami2021Service#CHUNKED2021_ENDPOINT_BATTERY}.
+     */
+    public static final byte BATTERY_REQUEST = 0x03;
+    public static final byte BATTERY_REPLY = 0x04;
+
+    /**
+     * Silent Mode, for {@link Huami2021Service#CHUNKED2021_ENDPOINT_SILENT_MODE}.
+     */
+    public static final byte SILENT_MODE_CMD_CAPABILITIES_REQUEST = 0x01;
+    public static final byte SILENT_MODE_CMD_CAPABILITIES_RESPONSE = 0x02;
+    // Notify silent mode, from phone
+    public static final byte SILENT_MODE_CMD_NOTIFY_BAND = 0x03;
+    public static final byte SILENT_MODE_CMD_NOTIFY_BAND_ACK = 0x04;
+    // Query silent mode on phone, from band
+    public static final byte SILENT_MODE_CMD_QUERY = 0x05;
+    public static final byte SILENT_MODE_CMD_REPLY = 0x06;
+    // Set silent mode on phone, from band
+    // After this, phone sends ACK + NOTIFY
+    public static final byte SILENT_MODE_CMD_SET = 0x07;
+    public static final byte SILENT_MODE_CMD_ACK = 0x08;
+
+    /**
+     * Connection, for {@link Huami2021Service#CHUNKED2021_ENDPOINT_CONNECTION}.
+     */
+    public static final byte CONNECTION_CMD_MTU_REQUEST = 0x01;
+    public static final byte CONNECTION_CMD_MTU_RESPONSE = 0x02;
+    public static final byte CONNECTION_CMD_UNKNOWN_3 = 0x03;
+    public static final byte CONNECTION_CMD_UNKNOWN_4 = 0x04;
+
+    /**
+     * Notifications, for {@link Huami2021Service#CHUNKED2021_ENDPOINT_HEARTRATE}.
+     */
+    public static final byte HEART_RATE_CMD_REALTIME_SET = 0x04;
+    public static final byte HEART_RATE_CMD_REALTIME_ACK = 0x05;
+    public static final byte HEART_RATE_CMD_SLEEP = 0x06;
+    public static final byte HEART_RATE_FALL_ASLEEP = 0x01;
+    public static final byte HEART_RATE_WAKE_UP = 0x00;
+    public static final byte HEART_RATE_REALTIME_MODE_STOP = 0x00;
+    public static final byte HEART_RATE_REALTIME_MODE_START = 0x01;
+    public static final byte HEART_RATE_REALTIME_MODE_CONTINUE = 0x02;
+
+    /**
+     * Workout, for {@link Huami2021Service#CHUNKED2021_ENDPOINT_WORKOUT}.
+     */
+    public static final byte WORKOUT_CMD_GPS_LOCATION = 0x04;
+    public static final byte WORKOUT_CMD_APP_OPEN = 0x20;
+    public static final byte WORKOUT_CMD_STATUS = 0x11;
+    public static final int WORKOUT_GPS_FLAG_STATUS = 0x1;
+    public static final int WORKOUT_GPS_FLAG_POSITION = 0x40000;
+    public static final byte WORKOUT_STATUS_START = 0x01;
+    public static final byte WORKOUT_STATUS_END = 0x04;
+
+    /**
+     * Weather, for {@link Huami2021Service#CHUNKED2021_ENDPOINT_WEATHER}.
+     */
+    public static final byte WEATHER_CMD_SET_DEFAULT_LOCATION = 0x09;
+    public static final byte WEATHER_CMD_DEFAULT_LOCATION_ACK = 0x0a;
+
+    /**
+     * User Info, for {@link Huami2021Service#CHUNKED2021_ENDPOINT_USER_INFO}.
+     */
+    public static final byte USER_INFO_CMD_SET = 0x01;
+    public static final byte USER_INFO_CMD_SET_ACK = 0x02;
+
+    /**
+     * Raw sensor control.
+     */
+    public static final byte[] CMD_RAW_SENSOR_START_1 = new byte[]{0x01, 0x03, 0x19}; // band replies 10:01:03:05
+    public static final byte[] CMD_RAW_SENSOR_START_2 = new byte[]{0x01, 0x03, 0x00, 0x00, 0x00, 0x19}; // band replies 10:01:01:05
+    public static final byte[] CMD_RAW_SENSOR_START_3 = new byte[]{0x02}; // band replies 10:02:01
+    public static final byte[] CMD_RAW_SENSOR_STOP = new byte[]{0x03}; // band replies 10:03:01
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiActivitySummaryParser.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiActivitySummaryParser.java
new file mode 100644
index 0000000..1ae3737
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiActivitySummaryParser.java
@@ -0,0 +1,391 @@
+/*  Copyright (C) 2020-2024 Andreas Shimokawa, Jos茅 Rebelo, Sebastian Krey,
+    Your Name
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami;
+
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.*;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Date;
+
+import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryData;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryParser;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.AbstractHuamiActivityDetailsParser;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiActivityDetailsParser;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSportsActivityType;
+
+public class HuamiActivitySummaryParser implements ActivitySummaryParser {
+
+    private static final Logger LOG = LoggerFactory.getLogger(HuamiActivityDetailsParser.class);
+    protected ActivitySummaryData summaryData = new ActivitySummaryData();
+
+    public BaseActivitySummary parseBinaryData(BaseActivitySummary summary, final boolean forDetails) {
+        Date startTime = summary.getStartTime();
+        if (startTime == null) {
+            LOG.error("Due to a bug, we can only parse the summary when startTime is already set");
+            return null;
+        }
+        summaryData = new ActivitySummaryData();
+        parseBinaryData(summary, startTime);
+        summary.setSummaryData(summaryData.toString());
+        return summary;
+    }
+
+    public AbstractHuamiActivityDetailsParser getDetailsParser(final BaseActivitySummary summary) {
+        return new HuamiActivityDetailsParser(summary);
+    }
+
+    protected void parseBinaryData(BaseActivitySummary summary, Date startTime) {
+        final byte[] rawSummaryData = summary.getRawSummaryData();
+        if (rawSummaryData == null) {
+            return;
+        }
+        final ByteBuffer buffer = ByteBuffer.wrap(rawSummaryData).order(ByteOrder.LITTLE_ENDIAN);
+
+        short version = buffer.getShort(); // version
+        LOG.debug("Got sport summary version " + version + " total bytes=" + buffer.capacity());
+        ActivityKind activityKind = ActivityKind.UNKNOWN;
+        int rawKind = BLETypeConversions.toUnsigned(buffer.getShort());
+        try {
+            HuamiSportsActivityType activityType = HuamiSportsActivityType.fromCode(rawKind);
+            activityKind = activityType.toActivityKind();
+        } catch (Exception ex) {
+            LOG.error("Error mapping activity kind: " + ex.getMessage(), ex);
+            summaryData.add("Raw Activity Kind", rawKind, UNIT_NONE);
+        }
+        summary.setActivityKind(activityKind.getCode());
+
+        // FIXME: should honor timezone we were in at that time etc
+        long timestamp_start = BLETypeConversions.toUnsigned(buffer.getInt()) * 1000;
+        long timestamp_end = BLETypeConversions.toUnsigned(buffer.getInt()) * 1000;
+
+
+        // FIXME: should be done like this but seems to return crap when in DST
+        //summary.setStartTime(new Date(timestamp_start));
+        //summary.setEndTime(new Date(timestamp_end));
+
+        // FIXME ... so do it like this
+        long duration = timestamp_end - timestamp_start;
+        summary.setEndTime(new Date(startTime.getTime() + duration));
+
+        int baseLongitude = buffer.getInt();
+        int baseLatitude = buffer.getInt();
+        int baseAltitude = buffer.getInt();
+        summary.setBaseLongitude(baseLongitude);
+        summary.setBaseLatitude(baseLatitude);
+        summary.setBaseAltitude(baseAltitude);
+
+        int steps;
+        int activeSeconds;
+        int maxLatitude;
+        int minLatitude;
+        int maxLongitude;
+        int minLongitude;
+        float caloriesBurnt;
+        float distanceMeters;
+        float distanceMeters2 = 0;
+        float ascentDistance = 0;
+        float descentDistance = 0;
+        float flatDistance = 0;
+        float ascentMeters = 0;
+        float descentMeters = 0;
+        float maxAltitude = 0;
+        float minAltitude = 0;
+        float averageAltitude = 0;
+        float maxSpeed = 0;
+        float minSpeed = 0;
+        float averageSpeed = 0;
+        float minPace = 0;
+        float maxPace = 0;
+        float averagePace = 0;
+        int maxCadence = 0;
+        int minCadence = 0;
+        int averageCadence = 0;
+        int maxStride = 0;
+        int minStride = 0;
+        int averageStride2 = 0;
+        float totalStride = 0;
+        float averageStride;
+        short averageHR;
+        short maxHR = 0;
+        short minHR = 0;
+        short averageKMPaceSeconds;
+        int ascentSeconds = 0;
+        int descentSeconds = 0;
+        int flatSeconds = 0;
+
+        // Swimming
+        float averageStrokeDistance = 0;
+        float averageStrokesPerSecond = 0;
+        float averageLapPace = 0;
+        short strokes = 0;
+        short swolfIndex = 0; // this is called SWOLF score on bip s, SWOLF index on mi band 4
+        byte swimStyle = 0;
+        byte laps = 0;
+
+        // Just assuming, Bip has 259 which seems like 256+x
+        // Bip S now has 518 so assuming 512+x, might be wrong
+
+        if (version >= 512) {
+            if (version == 519) {
+                buffer.get(); // skip one byte
+                minHR  = buffer.getShort();
+                // hack that skips data on yet unknown summary version 519 data
+                buffer.position(0x8c);
+            } else if (version == 516) {
+                // hack that skips data on yet unknown summary version 516 data
+                buffer.position(buffer.position() + 4);
+            }
+            steps = buffer.getInt();
+            activeSeconds = buffer.getInt();
+
+            maxLatitude = buffer.getInt();
+            minLatitude = buffer.getInt();
+            maxLongitude = buffer.getInt();
+            minLongitude = buffer.getInt();
+
+            caloriesBurnt = buffer.getFloat();
+            distanceMeters = buffer.getFloat();
+
+            ascentMeters = buffer.getFloat();
+            descentMeters = buffer.getFloat();
+            maxAltitude = buffer.getFloat();
+            minAltitude = buffer.getFloat();
+            averageAltitude = buffer.getFloat();
+
+            maxSpeed = buffer.getFloat(); // in meter/second
+            minSpeed = buffer.getFloat();
+            averageSpeed = buffer.getFloat();
+            minPace = buffer.getFloat(); // in seconds/meter
+            maxPace = buffer.getFloat();
+            averagePace = buffer.getFloat();
+
+            maxCadence = Math.round(buffer.getFloat() * 60);
+            minCadence = Math.round(buffer.getFloat() * 60);
+            averageCadence = Math.round(buffer.getFloat() * 60);
+
+            maxStride = Math.round(buffer.getFloat() * 100);
+            minStride = Math.round(buffer.getFloat() * 100);
+            averageStride2 = Math.round(buffer.getFloat() * 100);
+
+            distanceMeters2 = buffer.getFloat(); // this distance is 87-97% of distanceMeters, so probably length of the GPS track (difference is larger, when GPS took longer to get a precise position)
+            buffer.getInt();
+            averageHR = buffer.getShort();
+            averageKMPaceSeconds = buffer.getShort();
+            averageStride = buffer.getShort();
+            maxHR = buffer.getShort();
+
+            if (activityKind == ActivityKind.CYCLING || activityKind == ActivityKind.RUNNING || activityKind == ActivityKind.HIKING || activityKind == ActivityKind.CLIMBING) {
+                // this had nonsense data with treadmill on bip s, need to test it with running
+                // for cycling it seems to work... hmm...
+                // 28 bytes
+                buffer.getInt(); // unknown
+                ascentDistance = buffer.getFloat();
+                ascentSeconds = buffer.getInt() / 1000; //ms?
+                descentDistance = buffer.getFloat();
+                descentSeconds = buffer.getInt() / 1000; //ms?
+                flatDistance = buffer.getFloat();
+                flatSeconds = buffer.getInt() / 1000; // ms?
+            } else if (activityKind == ActivityKind.SWIMMING || activityKind == ActivityKind.SWIMMING_OPENWATER) {
+                // offset 0x8c
+                /*
+                    data on the bip s display (example)
+                    main style backstroke
+                    SWOLF score 92
+                    total laps 1
+                    avg. pace 2,09/100
+                    strokes 36
+                    avg stroke rate 26 spm
+                    single stroke distance 1,79m
+                    max stroke rate 39
+                 */
+
+                averageStrokeDistance = buffer.getFloat();
+                buffer.getInt(); // unknown
+                buffer.getInt(); // unknown
+                buffer.getInt(); // unknown
+                buffer.getInt(); // unknown
+                buffer.getInt(); // unknown
+                averageStrokesPerSecond = buffer.getFloat();
+                averageLapPace = buffer.getFloat();
+                buffer.getInt(); // unknown
+                strokes = buffer.getShort();
+                swolfIndex = buffer.getShort();
+                swimStyle = buffer.get();
+                laps = buffer.get();
+                buffer.getInt(); // unknown
+                buffer.getInt(); // unknown
+            }
+        } else {
+            distanceMeters = buffer.getFloat();
+            ascentMeters = buffer.getFloat();
+            descentMeters = buffer.getFloat();
+            minAltitude = buffer.getFloat();
+            maxAltitude = buffer.getFloat();
+            maxLatitude = buffer.getInt(); // format?
+            minLatitude = buffer.getInt(); // format?
+            maxLongitude = buffer.getInt(); // format?
+            minLongitude = buffer.getInt(); // format?
+            steps = buffer.getInt();
+            activeSeconds = buffer.getInt();
+            caloriesBurnt = buffer.getFloat();
+            maxSpeed = buffer.getFloat();
+            maxPace = buffer.getFloat();
+            minPace = buffer.getFloat();
+            totalStride = buffer.getFloat();
+
+            buffer.getInt(); // unknown
+            if (activityKind == ActivityKind.SWIMMING) {
+                // 28 bytes
+                averageStrokeDistance = buffer.getFloat();
+                averageStrokesPerSecond = buffer.getFloat();
+                averageLapPace = buffer.getFloat();
+                strokes = buffer.getShort();
+                swolfIndex = buffer.getShort();
+                swimStyle = buffer.get();
+                laps = buffer.get();
+                buffer.getInt(); // unknown
+                buffer.getInt(); // unknown
+                buffer.getShort(); // unknown
+            } else {
+                // 28 bytes
+                buffer.getInt(); // unknown
+                buffer.getInt(); // unknown probably ascentDistance = buffer.getFloat();
+                ascentSeconds = buffer.getInt() / 1000; //ms?
+                buffer.getInt(); // unknown probably descentDistance = buffer.getFloat();
+                descentSeconds = buffer.getInt() / 1000; //ms?
+                buffer.getInt(); // unknown probably flatDistance = buffer.getFloat();
+                flatSeconds = buffer.getInt() / 1000; // ms?
+
+                summaryData.add(ASCENT_SECONDS, ascentSeconds, UNIT_SECONDS);
+                summaryData.add(DESCENT_SECONDS, descentSeconds, UNIT_SECONDS);
+                summaryData.add(FLAT_SECONDS, flatSeconds, UNIT_SECONDS);
+            }
+
+            averageHR = buffer.getShort();
+
+            averageKMPaceSeconds = buffer.getShort();
+            averageStride = buffer.getShort();
+        }
+
+//        summary.setBaseCoordinate(new GPSCoordinate(baseLatitude, baseLongitude, baseAltitude));
+//        summary.setDistanceMeters(distanceMeters);
+//        summary.setAscentMeters(ascentMeters);
+//        summary.setDescentMeters(descentMeters);
+//        summary.setMinAltitude(maxAltitude);
+//        summary.setMaxAltitude(maxAltitude);
+//        summary.setMinLatitude(minLatitude);
+//        summary.setMaxLatitude(maxLatitude);
+//        summary.setMinLongitude(minLatitude);
+//        summary.setMaxLongitude(maxLatitude);
+//        summary.setSteps(steps);
+//        summary.setActiveTimeSeconds(secondsActive);
+//        summary.setCaloriesBurnt(caloriesBurnt);
+//        summary.setMaxSpeed(maxSpeed);
+//        summary.setMinPace(minPace);
+//        summary.setMaxPace(maxPace);
+//        summary.setTotalStride(totalStride);
+//        summary.setTimeAscent(BLETypeConversions.toUnsigned(ascentSeconds);
+//        summary.setTimeDescent(BLETypeConversions.toUnsigned(descentSeconds);
+//        summary.setTimeFlat(BLETypeConversions.toUnsigned(flatSeconds);
+//        summary.setAverageHR(BLETypeConversions.toUnsigned(averageHR);
+//        summary.setAveragePace(BLETypeConversions.toUnsigned(averagePace);
+//        summary.setAverageStride(BLETypeConversions.toUnsigned(averageStride);
+
+        summaryData.add(ASCENT_SECONDS, ascentSeconds, UNIT_SECONDS);
+        summaryData.add(DESCENT_SECONDS, descentSeconds, UNIT_SECONDS);
+        summaryData.add(FLAT_SECONDS, flatSeconds, UNIT_SECONDS);
+        summaryData.add(ASCENT_DISTANCE, ascentDistance, UNIT_METERS);
+        summaryData.add(DESCENT_DISTANCE, descentDistance, UNIT_METERS);
+        summaryData.add(FLAT_DISTANCE, flatDistance, UNIT_METERS);
+
+        summaryData.add(DISTANCE_METERS, distanceMeters, UNIT_METERS);
+        // summaryData.add("distanceMeters2", distanceMeters2, UNIT_METERS);
+        summaryData.add(ASCENT_METERS, ascentMeters, UNIT_METERS);
+        summaryData.add(DESCENT_METERS, descentMeters, UNIT_METERS);
+        if (maxAltitude != -100000) {
+            summaryData.add(ALTITUDE_MAX, maxAltitude, UNIT_METERS);
+        }
+        if (minAltitude != 100000) {
+            summaryData.add(ALTITUDE_MIN, minAltitude, UNIT_METERS);
+        }
+        if (minAltitude != 100000) {
+            summaryData.add(ALTITUDE_AVG, averageAltitude, UNIT_METERS);
+        }
+        summaryData.add(STEPS, steps, UNIT_STEPS);
+        summaryData.add(ACTIVE_SECONDS, activeSeconds, UNIT_SECONDS);
+        summaryData.add(CALORIES_BURNT, caloriesBurnt, UNIT_KCAL);
+        summaryData.add(SPEED_MAX, maxSpeed, UNIT_METERS_PER_SECOND);
+        summaryData.add(SPEED_MIN, minSpeed, UNIT_METERS_PER_SECOND);
+        summaryData.add(SPEED_AVG, averageSpeed, UNIT_METERS_PER_SECOND);
+        summaryData.add(CADENCE_MAX, maxCadence, UNIT_SPM);
+        summaryData.add(CADENCE_MIN, minCadence, UNIT_SPM);
+        summaryData.add(CADENCE_AVG, averageCadence, UNIT_SPM);
+
+        if (!(activityKind == ActivityKind.ELLIPTICAL_TRAINER ||
+                activityKind == ActivityKind.JUMP_ROPING ||
+                activityKind == ActivityKind.EXERCISE ||
+                activityKind == ActivityKind.YOGA ||
+                activityKind == ActivityKind.INDOOR_CYCLING)) {
+            summaryData.add(PACE_MIN, minPace, UNIT_SECONDS_PER_M);
+            summaryData.add(PACE_MAX, maxPace, UNIT_SECONDS_PER_M);
+            // summaryData.add("averagePace", averagePace, UNIT_SECONDS_PER_M);
+        }
+
+        summaryData.add(STRIDE_TOTAL, totalStride, UNIT_METERS);
+        summaryData.add(HR_AVG, averageHR, UNIT_BPM);
+        summaryData.add(HR_MAX, maxHR, UNIT_BPM);
+        summaryData.add(HR_MIN, minHR, UNIT_BPM);
+        summaryData.add(PACE_AVG_SECONDS_KM, averageKMPaceSeconds, UNIT_SECONDS_PER_KM);
+        summaryData.add(STRIDE_AVG, averageStride, UNIT_CM);
+        summaryData.add(STRIDE_MAX, maxStride, UNIT_CM);
+        summaryData.add(STRIDE_MIN, minStride, UNIT_CM);
+        // summaryData.add("averageStride2", averageStride2, UNIT_CM);
+
+        if (activityKind == ActivityKind.SWIMMING || activityKind == ActivityKind.SWIMMING_OPENWATER) {
+            summaryData.add(STROKE_DISTANCE_AVG, averageStrokeDistance, UNIT_METERS);
+            summaryData.add(STROKE_AVG_PER_SECOND, averageStrokesPerSecond, UNIT_STROKES_PER_SECOND);
+            summaryData.add(LAP_PACE_AVERAGE, averageLapPace, "second");
+            summaryData.add(STROKES, strokes, "strokes");
+            summaryData.add(SWOLF_INDEX, swolfIndex, "swolf_index");
+            String swimStyleName = "unknown"; // TODO: translate here or keep as string identifier here?
+            switch (swimStyle) {
+                case 1:
+                    swimStyleName = "breaststroke";
+                    break;
+                case 2:
+                    swimStyleName = "freestyle";
+                    break;
+                case 3:
+                    swimStyleName = "backstroke";
+                    break;
+                case 4:
+                    swimStyleName = "medley";
+                    break;
+            }
+            summaryData.add(SWIM_STYLE, swimStyleName);
+            summaryData.add(LAPS, laps, "laps");
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiConst.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiConst.java
new file mode 100644
index 0000000..c5c3a4d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiConst.java
@@ -0,0 +1,184 @@
+/*  Copyright (C) 2018-2024 Andreas Shimokawa, Damien Gaignon, jhey, Jos茅
+    Rebelo, Maxime Reyrolle, Nephiel, odavo32nof, Petr Van臎k, Raghd Hamzeh,
+    sedy89, Stefan Bora, thermatk, xaos, Yoran Vulker, Zhong Jianxin
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami;
+
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+
+public class HuamiConst {
+    // observed the following values so far:
+    // 00 01 02 09 0a 0b 0c 10 11
+
+    // 0 = same activity kind as before
+    // 1 = light activity walking?
+    // 3 = definitely non-wear
+    // 9 = probably light sleep, definitely some kind of sleep
+    // 10 = ignore, except for hr (if valid)
+    // 11 = probably deep sleep
+    // 12 = definitely wake up
+    // 17 = definitely not sleep related
+
+    public static final int TYPE_UNSET = -1;
+    public static final int TYPE_NO_CHANGE = 0;
+    public static final int TYPE_ACTIVITY = 1;
+    public static final int TYPE_RUNNING = 2;
+    public static final int TYPE_NONWEAR = 3;
+    public static final int TYPE_RIDE_BIKE = 4;
+    public static final int TYPE_CHARGING = 6;
+    public static final int TYPE_LIGHT_SLEEP = 9;
+    public static final int TYPE_IGNORE = 10;
+    public static final int TYPE_DEEP_SLEEP = 11;
+    public static final int TYPE_WAKE_UP = 12;
+
+
+    public static final String MI_BAND2_NAME = "MI Band 2";
+    public static final String MI_BAND2_NAME_HRX = "Mi Band HRX";
+    public static final String MI_BAND3_NAME = "Mi Band 3";
+    public static final String MI_BAND3_NAME_2 = "Xiaomi Band 3";
+    public static final String MI_BAND4_NAME = "Mi Smart Band 4";
+    public static final String MI_BAND5_NAME = "Mi Smart Band 5";
+    public static final String MI_BAND6_NAME = "Mi Smart Band 6";
+    public static final String AMAZFIT_ACTIVE_NAME = "Amazfit Active";
+    public static final String AMAZFIT_ACTIVE_EDGE_NAME = "Amazfit Active Edge";
+    public static final String AMAZFIT_BALANCE_NAME = "Amazfit Balance";
+    public static final String AMAZFIT_BAND5_NAME = "Amazfit Band 5";
+    public static final String AMAZFIT_BAND7_NAME = "Amazfit Band 7";
+    public static final String AMAZFIT_NEO_NAME = "Amazfit Neo";
+    public static final String AMAZFIT_X = "Amazfit X";
+    public static final String AMAZFIT_BIP5_NAME = "Amazfit Bip 5";
+    public static final String AMAZFIT_BIP5_UNITY_NAME = "Amazfit Bip 5 Unity";
+    public static final String AMAZFIT_GTS3_NAME = "Amazfit GTS 3";
+    public static final String AMAZFIT_GTS4_NAME = "Amazfit GTS 4";
+    public static final String AMAZFIT_GTS4_MINI_NAME = "Amazfit GTS 4 Mini";
+    public static final String AMAZFIT_GTR3_NAME = "Amazfit GTR 3";
+    public static final String AMAZFIT_GTR3_PRO_NAME = "Amazfit GTR 3 Pro";
+    public static final String AMAZFIT_GTR4_NAME = "Amazfit GTR 4";
+    public static final String AMAZFIT_GTR_MINI_NAME = "Amazfit GTR Mini";
+    public static final String AMAZFIT_TREX_2_NAME = "Amazfit T-Rex 2";
+    public static final String AMAZFIT_TREX_3_NAME = "Amazfit T-Rex 3";
+    public static final String AMAZFIT_TREX_ULTRA = "Amazfit T-Rex Ultra";
+    public static final String AMAZFIT_CHEETAH_PRO_NAME = "Amazfit Cheetah Pro";
+    public static final String AMAZFIT_CHEETAH_SQUARE_NAME = "Amazfit Cheetah S";
+    public static final String AMAZFIT_CHEETAH_ROUND_NAME = "Amazfit Cheetah R";
+    public static final String AMAZFIT_FALCON_NAME = "Amazfit Falcon";
+
+    public static final String XIAOMI_SMART_BAND7_NAME = "Xiaomi Smart Band 7";
+    public static final String XIAOMI_SMART_BAND7_PRO_NAME = "Xiaomi Smart Band 7 Pro";
+
+    public static final String PREF_DISPLAY_ITEMS = "display_items";
+    public static final String PREF_DISPLAY_ITEMS_SORTABLE = "display_items_sortable";
+    public static final String PREF_WORKOUT_ACTIVITY_TYPES_SORTABLE = "workout_activity_types_sortable";
+    public static final String PREF_SHORTCUTS = "shortcuts";
+    public static final String PREF_SHORTCUTS_SORTABLE = "shortcuts_sortable";
+    public static final String PREF_CONTROL_CENTER_SORTABLE = "control_center_sortable";
+    public static final String PREF_EXPOSE_HR_THIRDPARTY = "expose_hr_thirdparty";
+    public static final String PREF_USE_CUSTOM_FONT = "use_custom_font";
+
+    public static final String PREF_BUTTON_ACTION_ENABLE = "button_action_enable";
+    public static final String PREF_BUTTON_ACTION_VIBRATE = "button_action_vibrate";
+    public static final String PREF_BUTTON_ACTION_PRESS_COUNT = "button_action_press_count";
+    public static final String PREF_BUTTON_ACTION_PRESS_MAX_INTERVAL = "button_action_press_max_interval";
+    public static final String PREF_BUTTON_ACTION_BROADCAST_DELAY = "button_action_broadcast_delay";
+    public static final String PREF_BUTTON_ACTION_BROADCAST = "button_action_broadcast";
+    public static final String PREF_BUTTON_ACTION_SELECTION_OFF = "UNKNOWN";
+    public static final String PREF_BUTTON_ACTION_SELECTION_BROADCAST = "BROADCAST";
+    public static final String PREF_BUTTON_ACTION_SELECTION_FITNESS_APP_START = "FITNESS_CONTROL_START";
+    public static final String PREF_BUTTON_ACTION_SELECTION_FITNESS_APP_STOP = "FITNESS_CONTROL_STOP";
+    public static final String PREF_BUTTON_ACTION_SELECTION_FITNESS_APP_TOGGLE = "FITNESS_CONTROL_TOGGLE";
+
+    /**
+     * The suffixes match the enum {@link nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiVibrationPatternNotificationType}.
+     */
+    public static final String PREF_HUAMI_VIBRATION_PROFILE_KEY_PREFIX = "vibration_profile_key_";
+    public static final String PREF_HUAMI_DEFAULT_VIBRATION_PROFILE = "default";
+    // profile
+    public static final String PREF_HUAMI_VIBRATION_PROFILE_PREFIX = "huami_vibration_profile_";
+    public static final String PREF_HUAMI_VIBRATION_PROFILE_APP_ALERTS = PREF_HUAMI_VIBRATION_PROFILE_PREFIX + "app_alerts";
+    public static final String PREF_HUAMI_VIBRATION_PROFILE_INCOMING_CALL = PREF_HUAMI_VIBRATION_PROFILE_PREFIX + "incoming_call";
+    public static final String PREF_HUAMI_VIBRATION_PROFILE_INCOMING_SMS = PREF_HUAMI_VIBRATION_PROFILE_PREFIX + "incoming_sms";
+    public static final String PREF_HUAMI_VIBRATION_PROFILE_GOAL_NOTIFICATION = PREF_HUAMI_VIBRATION_PROFILE_PREFIX + "goal_notification";
+    public static final String PREF_HUAMI_VIBRATION_PROFILE_ALARM = PREF_HUAMI_VIBRATION_PROFILE_PREFIX + "alarm";
+    public static final String PREF_HUAMI_VIBRATION_PROFILE_IDLE_ALERTS = PREF_HUAMI_VIBRATION_PROFILE_PREFIX + "idle_alerts";
+    public static final String PREF_HUAMI_VIBRATION_PROFILE_EVENT_REMINDER = PREF_HUAMI_VIBRATION_PROFILE_PREFIX + "event_reminder";
+    public static final String PREF_HUAMI_VIBRATION_PROFILE_FIND_BAND = PREF_HUAMI_VIBRATION_PROFILE_PREFIX + "find_band";
+    public static final String PREF_HUAMI_VIBRATION_PROFILE_TODO_LIST = PREF_HUAMI_VIBRATION_PROFILE_PREFIX + "todo_list";
+    public static final String PREF_HUAMI_VIBRATION_PROFILE_SCHEDULE = PREF_HUAMI_VIBRATION_PROFILE_PREFIX + "schedule";
+    // count
+    public static final String PREF_HUAMI_VIBRATION_COUNT_PREFIX = "huami_vibration_count_";
+    public static final String PREF_HUAMI_VIBRATION_COUNT_APP_ALERTS = PREF_HUAMI_VIBRATION_COUNT_PREFIX + "app_alerts";
+    public static final String PREF_HUAMI_VIBRATION_COUNT_INCOMING_CALL = PREF_HUAMI_VIBRATION_COUNT_PREFIX + "incoming_call";
+    public static final String PREF_HUAMI_VIBRATION_COUNT_INCOMING_SMS = PREF_HUAMI_VIBRATION_COUNT_PREFIX + "incoming_sms";
+    public static final String PREF_HUAMI_VIBRATION_COUNT_GOAL_NOTIFICATION = PREF_HUAMI_VIBRATION_COUNT_PREFIX + "goal_notification";
+    public static final String PREF_HUAMI_VIBRATION_COUNT_ALARM = PREF_HUAMI_VIBRATION_COUNT_PREFIX + "alarm";
+    public static final String PREF_HUAMI_VIBRATION_COUNT_IDLE_ALERTS = PREF_HUAMI_VIBRATION_COUNT_PREFIX + "idle_alerts";
+    public static final String PREF_HUAMI_VIBRATION_COUNT_EVENT_REMINDER = PREF_HUAMI_VIBRATION_COUNT_PREFIX + "event_reminder";
+    public static final String PREF_HUAMI_VIBRATION_COUNT_FIND_BAND = PREF_HUAMI_VIBRATION_COUNT_PREFIX + "find_band";
+    public static final String PREF_HUAMI_VIBRATION_COUNT_TODO_LIST = PREF_HUAMI_VIBRATION_COUNT_PREFIX + "todo_list";
+    public static final String PREF_HUAMI_VIBRATION_COUNT_SCHEDULE = PREF_HUAMI_VIBRATION_COUNT_PREFIX + "schedule";
+    // try
+    public static final String PREF_HUAMI_VIBRATION_TRY_PREFIX = "huami_vibration_try_";
+    public static final String PREF_HUAMI_VIBRATION_TRY_APP_ALERTS = PREF_HUAMI_VIBRATION_TRY_PREFIX + "app_alerts";
+    public static final String PREF_HUAMI_VIBRATION_TRY_INCOMING_CALL = PREF_HUAMI_VIBRATION_TRY_PREFIX + "incoming_call";
+    public static final String PREF_HUAMI_VIBRATION_TRY_INCOMING_SMS = PREF_HUAMI_VIBRATION_TRY_PREFIX + "incoming_sms";
+    public static final String PREF_HUAMI_VIBRATION_TRY_GOAL_NOTIFICATION = PREF_HUAMI_VIBRATION_TRY_PREFIX + "goal_notification";
+    public static final String PREF_HUAMI_VIBRATION_TRY_ALARM = PREF_HUAMI_VIBRATION_TRY_PREFIX + "alarm";
+    public static final String PREF_HUAMI_VIBRATION_TRY_IDLE_ALERTS = PREF_HUAMI_VIBRATION_TRY_PREFIX + "idle_alerts";
+    public static final String PREF_HUAMI_VIBRATION_TRY_EVENT_REMINDER = PREF_HUAMI_VIBRATION_TRY_PREFIX + "event_reminder";
+    public static final String PREF_HUAMI_VIBRATION_TRY_FIND_BAND = PREF_HUAMI_VIBRATION_TRY_PREFIX + "find_band";
+    public static final String PREF_HUAMI_VIBRATION_TRY_TODO_LIST = PREF_HUAMI_VIBRATION_TRY_PREFIX + "todo_list";
+    public static final String PREF_HUAMI_VIBRATION_TRY_SCHEDULE = PREF_HUAMI_VIBRATION_TRY_PREFIX + "schedule";
+
+    public static ActivityKind toActivityKind(int rawType) {
+        switch (rawType) {
+            case TYPE_DEEP_SLEEP:
+                return ActivityKind.DEEP_SLEEP;
+            case TYPE_LIGHT_SLEEP:
+                return ActivityKind.LIGHT_SLEEP;
+            case TYPE_ACTIVITY:
+            case TYPE_RUNNING:
+            case TYPE_WAKE_UP:
+                return ActivityKind.ACTIVITY;
+            case TYPE_NONWEAR:
+                return ActivityKind.NOT_WORN;
+            case TYPE_CHARGING:
+                return ActivityKind.NOT_WORN; //I believe it's a safe assumption
+            case TYPE_RIDE_BIKE:
+                return ActivityKind.CYCLING;
+            default:
+            case TYPE_UNSET: // fall through
+                return ActivityKind.UNKNOWN;
+        }
+    }
+
+    public static int toRawActivityType(ActivityKind activityKind) {
+        switch (activityKind) {
+            case ACTIVITY:
+                return TYPE_ACTIVITY;
+            case DEEP_SLEEP:
+                return TYPE_DEEP_SLEEP;
+            case LIGHT_SLEEP:
+                return TYPE_LIGHT_SLEEP;
+            case NOT_WORN:
+                return TYPE_NONWEAR;
+            case UNKNOWN: // fall through
+            default:
+                return TYPE_UNSET;
+        }
+    }
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiCoordinator.java
new file mode 100644
index 0000000..33ac998
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiCoordinator.java
@@ -0,0 +1,587 @@
+/*  Copyright (C) 2017-2024 Andreas Shimokawa, Damien Gaignon, Daniel Dakhno,
+    Daniele Gobbetti, Jos茅 Rebelo, NekoBox, Nephiel, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami;
+
+import android.app.Activity;
+import android.bluetooth.le.ScanFilter;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.ParcelUuid;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+
+import de.greenrobot.dao.query.QueryBuilder;
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.GBException;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.SettingsActivity;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer;
+import nodomain.freeyourgadget.gadgetbridge.capabilities.password.PasswordCapabilityImpl;
+import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLEDeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.DateTimeDisplay;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.DoNotDisturb;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBand2SampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandPairingActivity;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandService;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.VibrationProfile;
+import nodomain.freeyourgadget.gadgetbridge.entities.AbstractActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.entities.MiBandActivitySampleDao;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryParser;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiVibrationPatternNotificationType;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+public abstract class HuamiCoordinator extends AbstractBLEDeviceCoordinator {
+    @Override
+    public Class<? extends Activity> getPairingActivity() {
+        return MiBandPairingActivity.class;
+    }
+
+    @NonNull
+    @Override
+    public Collection<? extends ScanFilter> createBLEScanFilters() {
+        ParcelUuid mi2Service = new ParcelUuid(MiBandService.UUID_SERVICE_MIBAND2_SERVICE);
+        ScanFilter filter = new ScanFilter.Builder().setServiceUuid(mi2Service).build();
+        return Collections.singletonList(filter);
+    }
+
+    @Override
+    public boolean suggestUnbindBeforePair() {
+        return false;
+    }
+
+    @Override
+    protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
+        Long deviceId = device.getId();
+        QueryBuilder<?> qb = session.getMiBandActivitySampleDao().queryBuilder();
+        qb.where(MiBandActivitySampleDao.Properties.DeviceId.eq(deviceId)).buildDelete().executeDeleteWithoutDetachingEntities();
+    }
+
+    @Override
+    public String getManufacturer() {
+        return "Huami";
+    }
+
+    @Override
+    public boolean supportsFlashing() {
+        return true;
+    }
+
+    @Override
+    public Class<? extends Activity> getAppsManagementActivity() {
+        return null;
+    }
+
+    @Override
+    public boolean supportsRealtimeData() {
+        return true;
+    }
+
+    @Override
+    public int getAlarmSlotCount(GBDevice device) {
+        return 10;
+    }
+
+    @Override
+    public boolean supportsActivityDataFetching() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsActivityTracking() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsHeartRateRestingMeasurement(final GBDevice device) {
+        return true;
+    }
+
+    @Override
+    public int[] getSupportedDeviceSpecificAuthenticationSettings() {
+        return new int[]{R.xml.devicesettings_pairingkey};
+    }
+
+    @Override
+    public SampleProvider<? extends AbstractActivitySample> getSampleProvider(GBDevice device, DaoSession session) {
+        return new MiBand2SampleProvider(device, session);
+    }
+
+    @Override
+    public HuamiStressSampleProvider getStressSampleProvider(final GBDevice device, final  DaoSession session) {
+        return new HuamiStressSampleProvider(device, session);
+    }
+
+    @Override
+    public HuamiSpo2SampleProvider getSpo2SampleProvider(final GBDevice device, final  DaoSession session) {
+        return new HuamiSpo2SampleProvider(device, session);
+    }
+
+    @Override
+    public HuamiHeartRateMaxSampleProvider getHeartRateMaxSampleProvider(final GBDevice device, final DaoSession session) {
+        return new HuamiHeartRateMaxSampleProvider(device, session);
+    }
+
+    @Override
+    public HuamiHeartRateRestingSampleProvider getHeartRateRestingSampleProvider(final GBDevice device, final DaoSession session) {
+        return new HuamiHeartRateRestingSampleProvider(device, session);
+    }
+
+    @Override
+    public HuamiHeartRateManualSampleProvider getHeartRateManualSampleProvider(final GBDevice device, final DaoSession session) {
+        return new HuamiHeartRateManualSampleProvider(device, session);
+    }
+
+    @Override
+    public HuamiPaiSampleProvider getPaiSampleProvider(GBDevice device, DaoSession session) {
+        return new HuamiPaiSampleProvider(device, session);
+    }
+
+    @Override
+    public HuamiSleepRespiratoryRateSampleProvider getRespiratoryRateSampleProvider(GBDevice device, DaoSession session) {
+        return new HuamiSleepRespiratoryRateSampleProvider(device, session);
+    }
+
+    @Override
+    public ActivitySummaryParser getActivitySummaryParser(final GBDevice device, final Context context) {
+        return new HuamiActivitySummaryParser();
+    }
+
+    protected static Prefs getPrefs(final GBDevice device) {
+        return new Prefs(GBApplication.getDeviceSpecificSharedPrefs(device.getAddress()));
+    }
+
+    public static DateTimeDisplay getDateDisplay(Context context, String deviceAddress) throws IllegalArgumentException {
+        SharedPreferences sharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(deviceAddress);
+        String dateFormatTime = context.getString(R.string.p_dateformat_time);
+        if (dateFormatTime.equals(sharedPrefs.getString(MiBandConst.PREF_MI2_DATEFORMAT, dateFormatTime))) {
+            return DateTimeDisplay.TIME;
+        }
+        return DateTimeDisplay.DATE_TIME;
+    }
+
+    public static AlwaysOnDisplay getAlwaysOnDisplay(final String deviceAddress) {
+        final SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(deviceAddress);
+        final String pref = prefs.getString(DeviceSettingsPreferenceConst.PREF_ALWAYS_ON_DISPLAY_MODE, DeviceSettingsPreferenceConst.PREF_ALWAYS_ON_DISPLAY_OFF);
+        return AlwaysOnDisplay.valueOf(pref.toUpperCase(Locale.ROOT));
+    }
+
+    public static Date getAlwaysOnDisplayStart(final String deviceAddress) {
+        return getTimePreference(DeviceSettingsPreferenceConst.PREF_ALWAYS_ON_DISPLAY_START, "00:00", deviceAddress);
+    }
+
+    public static Date getAlwaysOnDisplayEnd(final String deviceAddress) {
+        return getTimePreference(DeviceSettingsPreferenceConst.PREF_ALWAYS_ON_DISPLAY_END, "00:00", deviceAddress);
+    }
+
+    public static ActivateDisplayOnLift getActivateDisplayOnLiftWrist(Context context, String deviceAddress) {
+        SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(deviceAddress);
+        String liftOff = context.getString(R.string.p_off);
+        String pref = prefs.getString(DeviceSettingsPreferenceConst.PREF_ACTIVATE_DISPLAY_ON_LIFT, liftOff);
+
+        return ActivateDisplayOnLift.valueOf(pref.toUpperCase(Locale.ROOT));
+    }
+
+    public static Date getDisplayOnLiftStart(String deviceAddress) {
+        return getTimePreference(DeviceSettingsPreferenceConst.PREF_DISPLAY_ON_LIFT_START, "00:00", deviceAddress);
+    }
+
+    public static Date getDisplayOnLiftEnd(String deviceAddress) {
+        return getTimePreference(DeviceSettingsPreferenceConst.PREF_DISPLAY_ON_LIFT_END, "00:00", deviceAddress);
+    }
+
+    public static ActivateDisplayOnLiftSensitivity getDisplayOnLiftSensitivity(String deviceAddress) {
+        final SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(deviceAddress);
+
+        final String pref = prefs.getString(DeviceSettingsPreferenceConst.PREF_DISPLAY_ON_LIFT_SENSITIVITY, "sensitive");
+
+        return ActivateDisplayOnLiftSensitivity.valueOf(pref.toUpperCase(Locale.ROOT));
+    }
+
+    public static DisconnectNotificationSetting getDisconnectNotificationSetting(Context context, String deviceAddress) {
+        Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(deviceAddress));
+        String liftOff = context.getString(R.string.p_off);
+        String pref = prefs.getString(DeviceSettingsPreferenceConst.PREF_DISCONNECT_NOTIFICATION, liftOff);
+
+        return DisconnectNotificationSetting.valueOf(pref.toUpperCase(Locale.ROOT));
+    }
+
+    public static Date getDisconnectNotificationStart(String deviceAddress) {
+        return getTimePreference(DeviceSettingsPreferenceConst.PREF_DISCONNECT_NOTIFICATION_START, "00:00", deviceAddress);
+    }
+
+    public static Date getDisconnectNotificationEnd(String deviceAddress) {
+        return getTimePreference(DeviceSettingsPreferenceConst.PREF_DISCONNECT_NOTIFICATION_END, "00:00", deviceAddress);
+    }
+
+    public static boolean getUseCustomFont(String deviceAddress) {
+        SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(deviceAddress);
+        return prefs.getBoolean(HuamiConst.PREF_USE_CUSTOM_FONT, false);
+    }
+
+    public static boolean getGoalNotification(String deviceAddress) {
+        SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(deviceAddress);
+        return prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_USER_FITNESS_GOAL_NOTIFICATION, false);
+    }
+
+    public static boolean getRotateWristToSwitchInfo(String deviceAddress) {
+        SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(deviceAddress);
+        return prefs.getBoolean(MiBandConst.PREF_MI2_ROTATE_WRIST_TO_SWITCH_INFO, false);
+    }
+
+    public static boolean getInactivityWarnings(String deviceAddress) {
+        SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(deviceAddress);
+        return prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_INACTIVITY_ENABLE, false);
+    }
+
+    public static int getInactivityWarningsThreshold(String deviceAddress) {
+        Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(deviceAddress));
+        return prefs.getInt(DeviceSettingsPreferenceConst.PREF_INACTIVITY_THRESHOLD, 60);
+    }
+
+    public static boolean getInactivityWarningsDnd(String deviceAddress) {
+        SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(deviceAddress);
+        return prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_INACTIVITY_DND, false);
+    }
+
+    public static Date getInactivityWarningsStart(String deviceAddress) {
+        return getTimePreference(DeviceSettingsPreferenceConst.PREF_INACTIVITY_START, "06:00", deviceAddress);
+    }
+
+    public static Date getInactivityWarningsEnd(String deviceAddress) {
+        return getTimePreference(DeviceSettingsPreferenceConst.PREF_INACTIVITY_END, "22:00", deviceAddress);
+    }
+
+    public static Date getInactivityWarningsDndStart(String deviceAddress) {
+        return getTimePreference(DeviceSettingsPreferenceConst.PREF_INACTIVITY_DND_START, "12:00", deviceAddress);
+    }
+
+    public static Date getInactivityWarningsDndEnd(String deviceAddress) {
+        return getTimePreference(DeviceSettingsPreferenceConst.PREF_INACTIVITY_DND_END, "14:00", deviceAddress);
+    }
+
+    public static Date getDoNotDisturbStart(String deviceAddress) {
+        return getTimePreference(DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_START, "01:00", deviceAddress);
+    }
+
+    public static Date getDoNotDisturbEnd(String deviceAddress) {
+        return getTimePreference(DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_END, "06:00", deviceAddress);
+    }
+
+    public static boolean getBandScreenUnlock(String deviceAddress) {
+        Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(deviceAddress));
+        return prefs.getBoolean(MiBandConst.PREF_SWIPE_UNLOCK, false);
+    }
+
+    public static boolean getScreenOnOnNotification(String deviceAddress) {
+        Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(deviceAddress));
+        return prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_SCREEN_ON_ON_NOTIFICATIONS, false);
+    }
+
+    public static int getScreenBrightness(String deviceAddress) throws IllegalArgumentException {
+        Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(deviceAddress));
+        return prefs.getInt(DeviceSettingsPreferenceConst.PREF_SCREEN_BRIGHTNESS, 50);
+    }
+
+    public static int getScreenTimeout(String deviceAddress) throws IllegalArgumentException {
+        Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(deviceAddress));
+        return prefs.getInt(DeviceSettingsPreferenceConst.PREF_SCREEN_TIMEOUT, 5);
+    }
+
+    public static boolean getExposeHRThirdParty(String deviceAddress) {
+        Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(deviceAddress));
+        return prefs.getBoolean(HuamiConst.PREF_EXPOSE_HR_THIRDPARTY, false);
+    }
+
+    public static int getHeartRateMeasurementInterval(String deviceAddress) {
+        Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(deviceAddress));
+        return prefs.getInt(DeviceSettingsPreferenceConst.PREF_HEARTRATE_MEASUREMENT_INTERVAL, 0) / 60;
+    }
+
+    public static boolean getHeartrateActivityMonitoring(String deviceAddress) throws IllegalArgumentException {
+        Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(deviceAddress));
+        return prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_HEARTRATE_ACTIVITY_MONITORING, false);
+    }
+
+    public static boolean getPasswordEnabled(String deviceAddress) throws IllegalArgumentException {
+        Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(deviceAddress));
+        return prefs.getBoolean(PasswordCapabilityImpl.PREF_PASSWORD_ENABLED, false);
+    }
+
+    public static String getPassword(String deviceAddress) throws IllegalArgumentException {
+        Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(deviceAddress));
+        return prefs.getString(PasswordCapabilityImpl.PREF_PASSWORD, null);
+    }
+
+    public static boolean getHeartrateAlert(String deviceAddress) throws IllegalArgumentException {
+        Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(deviceAddress));
+        return prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_HEARTRATE_ALERT_ENABLED, false);
+    }
+
+    public static int getHeartrateAlertHighThreshold(String deviceAddress) throws IllegalArgumentException {
+        Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(deviceAddress));
+        return prefs.getInt(DeviceSettingsPreferenceConst.PREF_HEARTRATE_ALERT_HIGH_THRESHOLD, 150);
+    }
+
+    public static int getHeartrateAlertLowThreshold(String deviceAddress) throws IllegalArgumentException {
+        Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(deviceAddress));
+        return prefs.getInt(DeviceSettingsPreferenceConst.PREF_HEARTRATE_ALERT_LOW_THRESHOLD, 45);
+    }
+
+    public static boolean getHeartrateSleepBreathingQualityMonitoring(String deviceAddress) throws IllegalArgumentException {
+        Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(deviceAddress));
+        return prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_HEARTRATE_SLEEP_BREATHING_QUALITY_MONITORING, false);
+    }
+
+    public static boolean getSPO2AllDayMonitoring(String deviceAddress) throws IllegalArgumentException {
+        Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(deviceAddress));
+        return prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_SPO2_ALL_DAY_MONITORING, false);
+    }
+
+    public static int getSPO2AlertThreshold(String deviceAddress) throws IllegalArgumentException {
+        Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(deviceAddress));
+        return prefs.getInt(DeviceSettingsPreferenceConst.PREF_SPO2_LOW_ALERT_THRESHOLD, 0);
+    }
+
+    public static boolean getHeartrateStressMonitoring(String deviceAddress) throws IllegalArgumentException {
+        Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(deviceAddress));
+        return prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_HEARTRATE_STRESS_MONITORING, false);
+    }
+
+    public static boolean getHeartrateStressRelaxationReminder(String deviceAddress) throws IllegalArgumentException {
+        Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(deviceAddress));
+        return prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_HEARTRATE_STRESS_RELAXATION_REMINDER, false);
+    }
+
+    public static boolean getBtConnectedAdvertising(String deviceAddress) {
+        Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(deviceAddress));
+        return prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_BT_CONNECTED_ADVERTISEMENT, false);
+    }
+
+    public static boolean getOverwriteSettingsOnConnection(String deviceAddress) {
+        Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(deviceAddress));
+        return prefs.getBoolean("overwrite_settings_on_connection", true);
+    }
+
+    public static boolean getKeepActivityDataOnDevice(String deviceAddress) {
+        Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(deviceAddress));
+        return prefs.getBoolean("keep_activity_data_on_device", false);
+    }
+
+    @Nullable
+    public static VibrationProfile getVibrationProfile(String deviceAddress, HuamiVibrationPatternNotificationType notificationType, boolean nullOnDeviceDefault) {
+        final String defaultVibrationProfileId;
+        final int defaultVibrationCount;
+
+        switch (notificationType) {
+            case APP_ALERTS:
+                defaultVibrationProfileId = VibrationProfile.ID_SHORT;
+                defaultVibrationCount = 2;
+                break;
+            case INCOMING_CALL:
+                defaultVibrationProfileId = VibrationProfile.ID_RING;
+                defaultVibrationCount = 1;
+                break;
+            case INCOMING_SMS:
+                defaultVibrationProfileId = VibrationProfile.ID_STACCATO;
+                defaultVibrationCount = 2;
+                break;
+            case GOAL_NOTIFICATION:
+                defaultVibrationProfileId = VibrationProfile.ID_LONG;
+                defaultVibrationCount = 1;
+                break;
+            case ALARM:
+                defaultVibrationProfileId = VibrationProfile.ID_LONG;
+                defaultVibrationCount = 7;
+                break;
+            case IDLE_ALERTS:
+                defaultVibrationProfileId = VibrationProfile.ID_MEDIUM;
+                defaultVibrationCount = 2;
+                break;
+            case EVENT_REMINDER:
+                defaultVibrationProfileId = VibrationProfile.ID_LONG;
+                defaultVibrationCount = 1;
+                break;
+            case FIND_BAND:
+                defaultVibrationProfileId = VibrationProfile.ID_RING;
+                defaultVibrationCount = 3;
+                break;
+            case TODO_LIST:
+                defaultVibrationProfileId = VibrationProfile.ID_SHORT;
+                defaultVibrationCount = 2;
+                break;
+            case SCHEDULE:
+                defaultVibrationProfileId = VibrationProfile.ID_SHORT;
+                defaultVibrationCount = 2;
+                break;
+            default:
+                defaultVibrationProfileId = VibrationProfile.ID_MEDIUM;
+                defaultVibrationCount = 2;
+        }
+
+        Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(deviceAddress));
+        final String vibrationProfileId = prefs.getString(
+                HuamiConst.PREF_HUAMI_VIBRATION_PROFILE_PREFIX + notificationType.name().toLowerCase(Locale.ROOT),
+                HuamiConst.PREF_HUAMI_DEFAULT_VIBRATION_PROFILE
+        );
+
+        if (HuamiConst.PREF_HUAMI_DEFAULT_VIBRATION_PROFILE.equals(vibrationProfileId)) {
+            if (nullOnDeviceDefault) {
+                // Return null, so the device default is used
+                return null;
+            }
+
+            return VibrationProfile.getProfile(defaultVibrationProfileId, (short) defaultVibrationCount);
+        }
+
+        final int vibrationProfileCount = prefs.getInt(HuamiConst.PREF_HUAMI_VIBRATION_COUNT_PREFIX + notificationType.name().toLowerCase(Locale.ROOT), defaultVibrationCount);
+
+        return VibrationProfile.getProfile(vibrationProfileId, (short) vibrationProfileCount);
+    }
+
+    protected static Date getTimePreference(String key, String defaultValue, String deviceAddress) {
+        Prefs prefs;
+
+        if (deviceAddress == null) {
+            prefs = GBApplication.getPrefs();
+        } else {
+            prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(deviceAddress));
+        }
+
+        return prefs.getTimePreference(key, defaultValue);
+    }
+
+    public static MiBandConst.DistanceUnit getDistanceUnit() {
+        Prefs prefs = GBApplication.getPrefs();
+        String unit = prefs.getString(SettingsActivity.PREF_MEASUREMENT_SYSTEM, GBApplication.getContext().getString(R.string.p_unit_metric));
+        if (unit.equals(GBApplication.getContext().getString(R.string.p_unit_metric))) {
+            return MiBandConst.DistanceUnit.METRIC;
+        } else {
+            return MiBandConst.DistanceUnit.IMPERIAL;
+        }
+    }
+
+    public static DoNotDisturb getDoNotDisturb(String deviceAddress) {
+        SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(deviceAddress);
+
+        String pref = prefs.getString(DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB, DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_OFF);
+
+        return DoNotDisturb.valueOf(pref.toUpperCase(Locale.ROOT));
+    }
+
+    public static boolean getDoNotDisturbLiftWrist(String deviceAddress) {
+        SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(deviceAddress);
+
+        return prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_LIFT_WRIST, false);
+    }
+
+    public static boolean getWorkoutStartOnPhone(String deviceAddress) {
+        SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(deviceAddress);
+
+        return prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_WORKOUT_START_ON_PHONE, false);
+    }
+
+    public static boolean getWorkoutSendGpsToBand(String deviceAddress) {
+        SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(deviceAddress);
+
+        return prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_WORKOUT_SEND_GPS_TO_BAND, false);
+    }
+
+    @Override
+    public boolean supportsFindDevice() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsAlarmSnoozing() {
+        return true;
+    }
+
+    @Override
+    public int getMaximumReminderMessageLength() {
+        return 16;
+    }
+
+    @Override
+    public int getReminderSlotCount(final GBDevice device) {
+        return 22; // At least, Mi Fit still allows more
+    }
+
+    @Override
+    public boolean getReserveReminderSlotsForCalendar() {
+        return !supportsCalendarEvents();
+    }
+
+    public boolean supportsDebugLogs() {
+        return true;
+    }
+
+    public List<HuamiVibrationPatternNotificationType> getVibrationPatternNotificationTypes(final GBDevice device) {
+        return Arrays.asList(
+                HuamiVibrationPatternNotificationType.APP_ALERTS,
+                HuamiVibrationPatternNotificationType.INCOMING_CALL,
+                HuamiVibrationPatternNotificationType.INCOMING_SMS,
+                HuamiVibrationPatternNotificationType.GOAL_NOTIFICATION,
+                HuamiVibrationPatternNotificationType.ALARM,
+                HuamiVibrationPatternNotificationType.IDLE_ALERTS,
+                HuamiVibrationPatternNotificationType.EVENT_REMINDER,
+                HuamiVibrationPatternNotificationType.FIND_BAND
+        );
+    }
+
+    @Override
+    public DeviceSpecificSettingsCustomizer getDeviceSpecificSettingsCustomizer(final GBDevice device) {
+        return new HuamiSettingsCustomizer(device, getVibrationPatternNotificationTypes(device));
+    }
+
+    public static boolean getHourlyChime(String deviceAddress) {
+        SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(deviceAddress);
+        return prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_HOURLY_CHIME_ENABLE, false);
+    }
+
+    public static Date getHourlyChimeStart(String deviceAddress) {
+        return getTimePreference(DeviceSettingsPreferenceConst.PREF_HOURLY_CHIME_START, "09:00", deviceAddress);
+    }
+
+    public static Date getHourlyChimeEnd(String deviceAddress) {
+        return getTimePreference(DeviceSettingsPreferenceConst.PREF_HOURLY_CHIME_END, "22:00", deviceAddress);
+    }
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_zetime;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_zetime_disabled;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiExtendedSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiExtendedSampleProvider.java
new file mode 100644
index 0000000..bd1c317
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiExtendedSampleProvider.java
@@ -0,0 +1,144 @@
+/*  Copyright (C) 2022-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami;
+
+import androidx.annotation.NonNull;
+
+import java.util.List;
+
+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.HuamiExtendedActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.entities.HuamiExtendedActivitySampleDao;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+
+public class HuamiExtendedSampleProvider extends AbstractSampleProvider<HuamiExtendedActivitySample> {
+    public static final int TYPE_CUSTOM_UNSET = -1;
+    public static final int TYPE_OUTDOOR_RUNNING = 64;
+    public static final int TYPE_NOT_WORN = 115;
+    public static final int TYPE_CHARGING = 118;
+    public static final int TYPE_SLEEP = 120;
+    public static final int TYPE_CUSTOM_DEEP_SLEEP = TYPE_SLEEP + 1;
+    public static final int TYPE_CUSTOM_REM_SLEEP = TYPE_SLEEP + 2;
+
+    public HuamiExtendedSampleProvider(final GBDevice device, final DaoSession session) {
+        super(device, session);
+    }
+
+    @Override
+    public float normalizeIntensity(int rawIntensity) {
+        return rawIntensity / 256.0f;
+    }
+
+    @Override
+    public AbstractDao<HuamiExtendedActivitySample, ?> getSampleDao() {
+        return getSession().getHuamiExtendedActivitySampleDao();
+    }
+
+    @NonNull
+    @Override
+    protected Property getTimestampSampleProperty() {
+        return HuamiExtendedActivitySampleDao.Properties.Timestamp;
+    }
+
+    @NonNull
+    @Override
+    protected Property getDeviceIdentifierSampleProperty() {
+        return HuamiExtendedActivitySampleDao.Properties.DeviceId;
+    }
+
+    @Override
+    protected Property getRawKindSampleProperty() {
+        return HuamiExtendedActivitySampleDao.Properties.RawKind;
+    }
+
+    @Override
+    public HuamiExtendedActivitySample createActivitySample() {
+        return new HuamiExtendedActivitySample();
+    }
+
+    @Override
+    protected List<HuamiExtendedActivitySample> getGBActivitySamples(final int timestamp_from, final int timestamp_to) {
+        final List<HuamiExtendedActivitySample> samples = super.getGBActivitySamples(timestamp_from, timestamp_to);
+        postProcess(samples);
+        return samples;
+    }
+
+    private void postProcess(final List<HuamiExtendedActivitySample> samples) {
+        if (samples.isEmpty()) {
+            return;
+        }
+
+        for (final HuamiExtendedActivitySample sample : samples) {
+            if (sample.getRawKind() == TYPE_SLEEP) {
+                // Band reports type sleep regardless of sleep type, so we map it to custom raw types
+                // These thresholds are arbitrary, but seem to somewhat match the data that's displayed on the band
+
+                sample.setDeepSleep(sample.getDeepSleep() & 127);
+                sample.setRemSleep(sample.getRemSleep() & 127);
+
+                if (sample.getRemSleep() > 55) {
+                    sample.setRawKind(TYPE_CUSTOM_REM_SLEEP);
+                    sample.setRawIntensity(sample.getRemSleep());
+                } else if (sample.getDeepSleep() > 42) {
+                    sample.setRawKind(TYPE_CUSTOM_DEEP_SLEEP);
+                    sample.setRawIntensity(sample.getDeepSleep());
+                } else {
+                    sample.setRawIntensity(sample.getSleep());
+                }
+            }
+        }
+    }
+
+    @Override
+    public ActivityKind normalizeType(final int rawType) {
+        switch (rawType) {
+            case TYPE_OUTDOOR_RUNNING:
+                return ActivityKind.RUNNING;
+            case TYPE_NOT_WORN:
+            case TYPE_CHARGING:
+                return ActivityKind.NOT_WORN;
+            case TYPE_SLEEP:
+                return ActivityKind.LIGHT_SLEEP;
+            case TYPE_CUSTOM_DEEP_SLEEP:
+                return ActivityKind.DEEP_SLEEP;
+            case TYPE_CUSTOM_REM_SLEEP:
+                return ActivityKind.REM_SLEEP;
+        }
+
+        return ActivityKind.UNKNOWN;
+    }
+
+    @Override
+    public int toRawActivityKind(final ActivityKind activityKind) {
+        switch (activityKind) {
+            case RUNNING:
+                return TYPE_OUTDOOR_RUNNING;
+            case NOT_WORN:
+                return TYPE_NOT_WORN;
+            case LIGHT_SLEEP:
+            case DEEP_SLEEP:
+            case REM_SLEEP:
+                return TYPE_SLEEP;
+        }
+
+        return TYPE_CUSTOM_UNSET;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiFWHelper.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiFWHelper.java
new file mode 100644
index 0000000..6d7248c
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiFWHelper.java
@@ -0,0 +1,109 @@
+/*  Copyright (C) 2017-2024 Andreas Shimokawa, Carsten Pfeiffer, Daniele
+    Gobbetti, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.net.Uri;
+
+import java.io.IOException;
+
+import androidx.annotation.NonNull;
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.AbstractMiBandFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.AbstractHuamiFirmwareInfo;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareType;
+
+public abstract class HuamiFWHelper extends AbstractMiBandFWHelper {
+    protected AbstractHuamiFirmwareInfo firmwareInfo;
+
+    public HuamiFWHelper(final Uri uri, final Context context) throws IOException {
+        super(uri, context);
+    }
+
+    @Override
+    public String format(int version) {
+        return firmwareInfo.toVersion(version);
+    }
+
+    @NonNull
+    @Override
+    public String getFirmwareKind() {
+        return GBApplication.getContext().getString(getFirmwareInfo().getFirmwareType().getNameResId());
+    }
+
+    @Override
+    public int getFirmwareVersion() {
+        return firmwareInfo.getFirmwareVersion();
+    }
+
+    @Override
+    public int getFirmware2Version() {
+        return 0;
+    }
+
+    @Override
+    public String getHumanFirmwareVersion2() {
+        return "";
+    }
+
+    @Override
+    protected int[] getWhitelistedFirmwareVersions() {
+        return firmwareInfo.getWhitelistedVersions();
+    }
+
+    @Override
+    public boolean isFirmwareGenerallyCompatibleWith(GBDevice device) {
+        return firmwareInfo.isGenerallyCompatibleWith(device);
+    }
+
+    @Override
+    public boolean isSingleFirmware() {
+        return true;
+    }
+
+    @Override
+    public void checkValid() throws IllegalArgumentException {
+        firmwareInfo.checkValid();
+    }
+
+    @Override
+    public HuamiFirmwareType getFirmwareType() {
+        return firmwareInfo.getFirmwareType();
+    }
+
+    @Override
+    public void unsetFwBytes() {
+        super.unsetFwBytes();
+        firmwareInfo.unsetFwBytes();
+    }
+
+    public AbstractHuamiFirmwareInfo getFirmwareInfo() {
+        return firmwareInfo;
+    }
+
+    @Override
+    public Bitmap getPreview() {
+        if (firmwareInfo != null) {
+            return firmwareInfo.getPreview();
+        }
+
+        return null;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiHeartRateManualSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiHeartRateManualSampleProvider.java
new file mode 100644
index 0000000..8d066d2
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiHeartRateManualSampleProvider.java
@@ -0,0 +1,56 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami;
+
+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.HuamiHeartRateManualSample;
+import nodomain.freeyourgadget.gadgetbridge.entities.HuamiHeartRateManualSampleDao;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class HuamiHeartRateManualSampleProvider extends AbstractTimeSampleProvider<HuamiHeartRateManualSample> {
+    public HuamiHeartRateManualSampleProvider(final GBDevice device, final DaoSession session) {
+        super(device, session);
+    }
+
+    @NonNull
+    @Override
+    public AbstractDao<HuamiHeartRateManualSample, ?> getSampleDao() {
+        return getSession().getHuamiHeartRateManualSampleDao();
+    }
+
+    @NonNull
+    @Override
+    protected Property getTimestampSampleProperty() {
+        return HuamiHeartRateManualSampleDao.Properties.Timestamp;
+    }
+
+    @NonNull
+    @Override
+    protected Property getDeviceIdentifierSampleProperty() {
+        return HuamiHeartRateManualSampleDao.Properties.DeviceId;
+    }
+
+    @Override
+    public HuamiHeartRateManualSample createSample() {
+        return new HuamiHeartRateManualSample();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiHeartRateMaxSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiHeartRateMaxSampleProvider.java
new file mode 100644
index 0000000..0f902ef
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiHeartRateMaxSampleProvider.java
@@ -0,0 +1,56 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami;
+
+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.HuamiHeartRateMaxSample;
+import nodomain.freeyourgadget.gadgetbridge.entities.HuamiHeartRateMaxSampleDao;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class HuamiHeartRateMaxSampleProvider extends AbstractTimeSampleProvider<HuamiHeartRateMaxSample> {
+    public HuamiHeartRateMaxSampleProvider(final GBDevice device, final DaoSession session) {
+        super(device, session);
+    }
+
+    @NonNull
+    @Override
+    public AbstractDao<HuamiHeartRateMaxSample, ?> getSampleDao() {
+        return getSession().getHuamiHeartRateMaxSampleDao();
+    }
+
+    @NonNull
+    @Override
+    protected Property getTimestampSampleProperty() {
+        return HuamiHeartRateMaxSampleDao.Properties.Timestamp;
+    }
+
+    @NonNull
+    @Override
+    protected Property getDeviceIdentifierSampleProperty() {
+        return HuamiHeartRateMaxSampleDao.Properties.DeviceId;
+    }
+
+    @Override
+    public HuamiHeartRateMaxSample createSample() {
+        return new HuamiHeartRateMaxSample();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiHeartRateRestingSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiHeartRateRestingSampleProvider.java
new file mode 100644
index 0000000..46ff142
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiHeartRateRestingSampleProvider.java
@@ -0,0 +1,56 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami;
+
+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.HuamiHeartRateRestingSample;
+import nodomain.freeyourgadget.gadgetbridge.entities.HuamiHeartRateRestingSampleDao;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class HuamiHeartRateRestingSampleProvider extends AbstractTimeSampleProvider<HuamiHeartRateRestingSample> {
+    public HuamiHeartRateRestingSampleProvider(final GBDevice device, final DaoSession session) {
+        super(device, session);
+    }
+
+    @NonNull
+    @Override
+    public AbstractDao<HuamiHeartRateRestingSample, ?> getSampleDao() {
+        return getSession().getHuamiHeartRateRestingSampleDao();
+    }
+
+    @NonNull
+    @Override
+    protected Property getTimestampSampleProperty() {
+        return HuamiHeartRateRestingSampleDao.Properties.Timestamp;
+    }
+
+    @NonNull
+    @Override
+    protected Property getDeviceIdentifierSampleProperty() {
+        return HuamiHeartRateRestingSampleDao.Properties.DeviceId;
+    }
+
+    @Override
+    public HuamiHeartRateRestingSample createSample() {
+        return new HuamiHeartRateRestingSample();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiPaiSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiPaiSampleProvider.java
new file mode 100644
index 0000000..39d5738
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiPaiSampleProvider.java
@@ -0,0 +1,56 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami;
+
+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.HuamiPaiSample;
+import nodomain.freeyourgadget.gadgetbridge.entities.HuamiPaiSampleDao;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class HuamiPaiSampleProvider extends AbstractTimeSampleProvider<HuamiPaiSample> {
+    public HuamiPaiSampleProvider(final GBDevice device, final DaoSession session) {
+        super(device, session);
+    }
+
+    @NonNull
+    @Override
+    public AbstractDao<HuamiPaiSample, ?> getSampleDao() {
+        return getSession().getHuamiPaiSampleDao();
+    }
+
+    @NonNull
+    @Override
+    protected Property getTimestampSampleProperty() {
+        return HuamiPaiSampleDao.Properties.Timestamp;
+    }
+
+    @NonNull
+    @Override
+    protected Property getDeviceIdentifierSampleProperty() {
+        return HuamiPaiSampleDao.Properties.DeviceId;
+    }
+
+    @Override
+    public HuamiPaiSample createSample() {
+        return new HuamiPaiSample();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiService.java
new file mode 100644
index 0000000..2686c06
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiService.java
@@ -0,0 +1,260 @@
+/*  Copyright (C) 2018-2024 Andreas Shimokawa, gsbhat, Jos茅 Rebelo, NekoBox
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+import static nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport.BASE_UUID;
+
+public class HuamiService {
+
+
+    public static final UUID UUID_SERVICE_MIBAND_SERVICE = UUID.fromString(String.format(BASE_UUID, "FEE0"));
+    public static final UUID UUID_SERVICE_MIBAND2_SERVICE = UUID.fromString(String.format(BASE_UUID, "FEE1"));
+    public static final UUID UUID_SERVICE_HEART_RATE = UUID.fromString(String.format(BASE_UUID, "180D"));
+    public static final UUID UUID_SERVICE_FIRMWARE_SERVICE = UUID.fromString("00001530-0000-3512-2118-0009af100700");
+
+    public static final UUID UUID_CHARACTERISTIC_FIRMWARE = UUID.fromString("00001531-0000-3512-2118-0009af100700");
+    public static final UUID UUID_CHARACTERISTIC_FIRMWARE_DATA = UUID.fromString("00001532-0000-3512-2118-0009af100700");
+
+    public static final UUID UUID_UNKNOWN_CHARACTERISTIC0 = UUID.fromString("00000000-0000-3512-2118-0009af100700");
+    public static final UUID UUID_CHARACTERISTIC_RAW_SENSOR_CONTROL = UUID.fromString("00000001-0000-3512-2118-0009af100700");
+    public static final UUID UUID_CHARACTERISTIC_RAW_SENSOR_DATA = UUID.fromString("00000002-0000-3512-2118-0009af100700");
+
+    /**
+     * Alarms, Display and other configuration.
+     */
+    public static final UUID UUID_CHARACTERISTIC_3_CONFIGURATION = UUID.fromString("00000003-0000-3512-2118-0009af100700");
+    public static final UUID UUID_UNKNOWN_CHARACTERISTIC4 = UUID.fromString("00000004-0000-3512-2118-0009af100700");
+    public static final UUID UUID_CHARACTERISTIC_5_ACTIVITY_DATA = UUID.fromString("00000005-0000-3512-2118-0009af100700");
+    public static final UUID UUID_CHARACTERISTIC_6_BATTERY_INFO = UUID.fromString("00000006-0000-3512-2118-0009af100700");
+    public static final UUID UUID_CHARACTERISTIC_7_REALTIME_STEPS = UUID.fromString("00000007-0000-3512-2118-0009af100700");
+    public static final UUID UUID_CHARACTERISTIC_8_USER_SETTINGS = UUID.fromString("00000008-0000-3512-2118-0009af100700");
+    // service uuid fee1
+    public static final UUID UUID_CHARACTERISTIC_AUTH = UUID.fromString("00000009-0000-3512-2118-0009af100700");
+    public static final UUID UUID_CHARACTERISTIC_WORKOUT = UUID.fromString("0000000f-0000-3512-2118-0009af100700");
+    public static final UUID UUID_CHARACTERISTIC_DEVICEEVENT = UUID.fromString("00000010-0000-3512-2118-0009af100700");
+    public static final UUID UUID_CHARACTERISTIC_AUDIO = UUID.fromString("00000012-0000-3512-2118-0009af100700");
+    public static final UUID UUID_CHARACTERISTIC_AUDIODATA = UUID.fromString("00000013-0000-3512-2118-0009af100700");
+    public static final UUID UUID_UNKNOWN_CHARACTERISTIC5 = UUID.fromString("00000014-0000-3512-2118-0009af100700");
+
+    public static final UUID UUID_CHARACTERISTIC_CHUNKEDTRANSFER_2021_WRITE = UUID.fromString("00000016-0000-3512-2118-0009af100700");
+    public static final UUID UUID_CHARACTERISTIC_CHUNKEDTRANSFER_2021_READ = UUID.fromString("00000017-0000-3512-2118-0009af100700");
+    public static final UUID UUID_CHARACTERISTIC_CHUNKEDTRANSFER = UUID.fromString("00000020-0000-3512-2118-0009af100700");
+
+    public static final UUID UUID_CHARACTERISTIC_ZEPP_OS_FILE_TRANSFER_V3 = UUID.fromString("00000023-0000-3512-2118-0009af100700");
+
+    public static final int ALERT_LEVEL_NONE = 0;
+    public static final int ALERT_LEVEL_MESSAGE = 1;
+    public static final int ALERT_LEVEL_PHONE_CALL = 2;
+    public static final int ALERT_LEVEL_VIBRATE_ONLY = 3;
+
+
+    // set metric distance
+    // set 12 hour time mode
+
+
+
+    private static final Map<UUID, String> MIBAND_DEBUG;
+
+    /**
+     * Mi Band 2 authentication has three steps.
+     * This is step 1: sending a "secret" key to the band.
+     * This is byte 0, followed by {@link #AUTH_BYTE} and then the key.
+     * In the response, it is byte 1 in the byte[] value.
+     */
+    public static final byte AUTH_SEND_KEY = 0x01;
+    /**
+     * Mi Band 2 authentication has three steps.
+     * This is step 2: requesting a random authentication key from the band.
+     * This is byte 0, followed by {@link #AUTH_BYTE}.
+     * In the response, it is byte 1 in the byte[] value.
+     */
+    public static final byte AUTH_REQUEST_RANDOM_AUTH_NUMBER = 0x02;
+    /**
+     * Mi Band 2 authentication has three steps.
+     * This is step 3: sending the encrypted random authentication key to the band.
+     * This is byte 0, followed by {@link #AUTH_BYTE} and then the encrypted random authentication key.
+     * In the response, it is byte 1 in the byte[] value.
+     */
+    public static final byte AUTH_SEND_ENCRYPTED_AUTH_NUMBER = 0x03;
+
+    /**
+     * Received in response to any authentication requests (byte 0 in the byte[] value.
+     */
+    public static final byte AUTH_RESPONSE = 0x10;
+    /**
+     * Received in response to any authentication requests (byte 2 in the byte[] value.
+     * 0x01 means success.
+     */
+    public static final byte AUTH_SUCCESS = 0x01;
+    /**
+     * Received in response to any authentication requests (byte 2 in the byte[] value.
+     * 0x04 means failure.
+     */
+    public static final byte AUTH_FAIL = 0x04;
+    /**
+     * In some logs it's 0x0...
+     */
+    public static final byte AUTH_BYTE = 0x08;
+
+    // maybe not really activity data, but steps?
+    public static final byte COMMAND_FETCH_DATA = 0x02;
+    // delete/drop activity data
+    // on Huami it's just the single 03 byte
+    // on Huami 2021 it's followed by 09 to keep, 01 to drop from device
+    public static final byte COMMAND_ACK_ACTIVITY_DATA = 0x03;
+
+    public static final byte[] COMMAND_SET_FITNESS_GOAL_START = new byte[] { 0x10, 0x0, 0x0 };
+    public static final byte[] COMMAND_SET_FITNESS_GOAL_END = new byte[] { 0, 0 };
+
+    public static final byte COMMAND_SET_USERINFO = 0x4f;
+
+    public static final byte ICON_HIGH_PRIORITY = 0x7;
+
+    public static byte ENDPOINT_DISPLAY_ITEMS = 0x0a;
+
+    public static byte DISPLAY_ITEM_BIT_CLOCK = 0x01;
+    public static byte DISPLAY_ITEM_BIT_STEPS = 0x02;
+    public static byte DISPLAY_ITEM_BIT_DISTANCE = 0x04;
+    public static byte DISPLAY_ITEM_BIT_CALORIES= 0x08;
+    public static byte DISPLAY_ITEM_BIT_HEART_RATE = 0x10;
+    public static byte DISPLAY_ITEM_BIT_BATTERY = 0x20;
+
+    // Second byte must be a bitwise OR combination of the above
+    // The clock can't be disabled
+    public static int SCREEN_CHANGE_BYTE = 1;
+    public static final byte[] COMMAND_CHANGE_SCREENS = new byte[]{ENDPOINT_DISPLAY_ITEMS, DISPLAY_ITEM_BIT_CLOCK, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05};
+
+    public static byte ENDPOINT_DISPLAY = 0x06;
+
+    public static final byte[] DATEFORMAT_DATE_TIME = new byte[] {ENDPOINT_DISPLAY, 0x0a, 0x0, 0x03 };
+    public static final byte[] DATEFORMAT_TIME = new byte[] {ENDPOINT_DISPLAY, 0x0a, 0x0, 0x0 };
+    public static final byte[] DATEFORMAT_TIME_12_HOURS = new byte[] {ENDPOINT_DISPLAY, 0x02, 0x0, 0x0 };
+    public static final byte[] DATEFORMAT_TIME_24_HOURS = new byte[] {ENDPOINT_DISPLAY, 0x02, 0x0, 0x1 };
+    public static final byte[] DATEFORMAT_DATE_MM_DD_YYYY = new byte[]{ENDPOINT_DISPLAY, 30, 0x00, 'M', 'M', '/', 'd', 'd', '/', 'y', 'y', 'y', 'y'};
+    public static final byte[] COMMAND_ENBALE_HR_CONNECTION = new byte[]{ENDPOINT_DISPLAY, 0x1f, 0x00, 0x01};
+    public static final byte[] COMMAND_DISABLE_HR_CONNECTION = new byte[]{ENDPOINT_DISPLAY, 0x1f, 0x00, 0x00};
+    public static final byte[] COMMAND_ENABLE_BT_CONNECTED_ADVERTISEMENT = new byte[]{ENDPOINT_DISPLAY, 0x01, 0x00, 0x01};
+    public static final byte[] COMMAND_DISABLE_BT_CONNECTED_ADVERTISEMENT = new byte[]{ENDPOINT_DISPLAY, 0x01, 0x00, 0x00};
+    public static final byte[] COMMAND_ENABLE_DISPLAY_ON_LIFT_WRIST = new byte[]{ENDPOINT_DISPLAY, 0x05, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00};
+    public static final byte[] COMMAND_DISABLE_DISPLAY_ON_LIFT_WRIST = new byte[]{ENDPOINT_DISPLAY, 0x05, 0x00, 0x00};
+    public static final byte[] COMMAND_SCHEDULE_DISPLAY_ON_LIFT_WRIST = new byte[]{ENDPOINT_DISPLAY, 0x05, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00};
+    public static final byte[] COMMAND_DISPLAY_ON_LIFT_WRIST_SPEED_NORMAL = new byte[]{ENDPOINT_DISPLAY, 0x23, 0x00, 0x00};
+    public static final byte[] COMMAND_DISPLAY_ON_LIFT_WRIST_SPEED_SENSITIVE = new byte[]{ENDPOINT_DISPLAY, 0x23, 0x00, 0x01};
+    public static final byte[] COMMAND_ENABLE_GOAL_NOTIFICATION = new byte[]{ENDPOINT_DISPLAY, 0x06, 0x00, 0x01};
+    public static final byte[] COMMAND_DISABLE_GOAL_NOTIFICATION = new byte[]{ENDPOINT_DISPLAY, 0x06, 0x00, 0x00};
+    public static final byte[] COMMAND_ENABLE_ROTATE_WRIST_TO_SWITCH_INFO = new byte[]{ENDPOINT_DISPLAY, 0x0d, 0x00, 0x01};
+    public static final byte[] COMMAND_DISABLE_ROTATE_WRIST_TO_SWITCH_INFO = new byte[]{ENDPOINT_DISPLAY, 0x0d, 0x00, 0x00};
+    public static final byte[] COMMAND_ENABLE_DISPLAY_CALLER = new byte[]{ENDPOINT_DISPLAY, 0x10, 0x00, 0x00, 0x01};
+    public static final byte[] COMMAND_DISABLE_DISPLAY_CALLER = new byte[]{ENDPOINT_DISPLAY, 0x10, 0x00, 0x00, 0x00};
+    public static final byte[] DISPLAY_YYY = new byte[] {ENDPOINT_DISPLAY, 0x10, 0x0, 0x1, 0x1 };
+    public static final byte[] COMMAND_DISTANCE_UNIT_METRIC = new byte[] { ENDPOINT_DISPLAY, 0x03, 0x00, 0x00 };
+    public static final byte[] COMMAND_DISTANCE_UNIT_IMPERIAL = new byte[] { ENDPOINT_DISPLAY, 0x03, 0x00, 0x01 };
+    public static final byte[] COMMAND_SET_LANGUAGE_NEW_TEMPLATE = new byte[]{ENDPOINT_DISPLAY, 0x17, 0x00, 0, 0, 0, 0, 0};
+    public static final byte[] COMMAND_FACTORY_RESET = new byte[]{ENDPOINT_DISPLAY, 0x0b, 0x00, 0x01};
+    public static final byte[] COMMAND_ENABLE_DISCONNECT_NOTIFCATION = new byte[]{ENDPOINT_DISPLAY, 0x0c, 0x00, 0x01, 0, 0, 0, 0};
+    public static final byte[] COMMAND_DISABLE_DISCONNECT_NOTIFCATION = new byte[]{ENDPOINT_DISPLAY, 0x0c, 0x00, 0x00, 0, 0, 0, 0};
+
+    public static final byte COMMAND_ALARMS = 0x0d;
+    public static final byte COMMAND_ALARMS_WITH_TIMES = 0x01;
+    public static final byte COMMAND_WORKOUT_ACTIVITY_TYPES = 0x11;
+    public static final byte COMMAND_GPS_VERSION = 0x0e;
+
+    public static final byte[] COMMAND_REQUEST_ALARMS = new byte[]{COMMAND_ALARMS};
+    public static final byte[] COMMAND_REQUEST_ALARMS_WITH_TIMES = new byte[]{(byte) 0xff, COMMAND_ALARMS_WITH_TIMES, 0x00, 0x00, 0x00};
+    public static final byte[] COMMAND_REQUEST_WORKOUT_ACTIVITY_TYPES = new byte[]{(byte) 0xff, COMMAND_WORKOUT_ACTIVITY_TYPES, 0x00, 0x00, 0x00};
+
+    public static final byte[] COMMAND_REQUEST_GPS_VERSION = new byte[]{COMMAND_GPS_VERSION};
+
+    // The third byte controls the threshold, in minutes
+    // The last 8 bytes represent 2 separate time intervals for the inactivity warnings
+    // If there is no do not disturb interval, the last 4 bytes (the second interval) are 0
+    // and only the first interval of the command is used
+    public static int INACTIVITY_WARNINGS_THRESHOLD = 2;
+    public static int INACTIVITY_WARNINGS_INTERVAL_1_START_HOURS = 4;
+    public static int INACTIVITY_WARNINGS_INTERVAL_1_START_MINUTES = 5;
+    public static int INACTIVITY_WARNINGS_INTERVAL_1_END_HOURS = 6;
+    public static int INACTIVITY_WARNINGS_INTERVAL_1_END_MINUTES = 7;
+    public static int INACTIVITY_WARNINGS_INTERVAL_2_START_HOURS = 8;
+    public static int INACTIVITY_WARNINGS_INTERVAL_2_START_MINUTES = 9;
+    public static int INACTIVITY_WARNINGS_INTERVAL_2_END_HOURS = 10;
+    public static int INACTIVITY_WARNINGS_INTERVAL_2_END_MINUTES = 11;
+    public static final byte[] COMMAND_ENABLE_INACTIVITY_WARNINGS = new byte[] { 0x08, 0x01, 0x3c, 0x00, 0x04, 0x00, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00 };
+    public static final byte[] COMMAND_DISABLE_INACTIVITY_WARNINGS = new byte[] { 0x08, 0x00, 0x3c, 0x00, 0x04, 0x00, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00 };
+
+    public static byte ENDPOINT_DND = 0x09;
+
+    public static final byte[] COMMAND_DO_NOT_DISTURB_AUTOMATIC = new byte[] { ENDPOINT_DND, (byte) 0x83 };
+    public static final byte[] COMMAND_DO_NOT_DISTURB_OFF = new byte[] { ENDPOINT_DND, (byte) 0x82 };
+    public static final byte[] COMMAND_DO_NOT_DISTURB_SCHEDULED = new byte[] { ENDPOINT_DND, (byte) 0x81, 0x01, 0x00, 0x06, 0x00 };
+    // The 4 last bytes set the start and end time in 24h format
+    public static byte DND_BYTE_START_HOURS = 2;
+    public static byte DND_BYTE_START_MINUTES = 3;
+    public static byte DND_BYTE_END_HOURS = 4;
+    public static byte DND_BYTE_END_MINUTES = 5;
+
+    public static final byte MUSIC_FLAG_STATE = 0x01;
+    public static final byte MUSIC_FLAG_ARTIST = 0x02;
+    public static final byte MUSIC_FLAG_ALBUM = 0x04;
+    public static final byte MUSIC_FLAG_TRACK = 0x08;
+    public static final byte MUSIC_FLAG_DURATION = 0x10;
+    public static final byte MUSIC_FLAG_NOTHING_PLAYING = 0x20;
+    public static final byte MUSIC_FLAG_VOLUME = 0x40;
+
+    public static final byte RESPONSE = 0x10;
+
+    public static final byte SUCCESS = 0x01;
+    public static final byte COMMAND_ACTIVITY_DATA_START_DATE = 0x01;
+    public static final byte COMMAND_ACTIVITY_DATA_TYPE_ACTIVTY = 0x01;
+    public static final byte COMMAND_ACTIVITY_DATA_XXX_DATE = 0x02; // issued on first connect, followd by COMMAND_XXXX_ACTIVITY_DATA instead of COMMAND_FETCH_DATA
+
+    public static final byte COMMAND_FIRMWARE_INIT = 0x01; // to UUID_CHARACTERISTIC_FIRMWARE, followed by fw file size in bytes
+    public static final byte COMMAND_FIRMWARE_START_DATA = 0x03; // to UUID_CHARACTERISTIC_FIRMWARE
+    public static final byte COMMAND_FIRMWARE_UPDATE_SYNC = 0x00; // to UUID_CHARACTERISTIC_FIRMWARE
+    public static final byte COMMAND_FIRMWARE_CHECKSUM = 0x04; // to UUID_CHARACTERISTIC_FIRMWARE
+    public static final byte COMMAND_FIRMWARE_REBOOT = 0x05; // to UUID_CHARACTERISTIC_FIRMWARE
+
+    public static final byte[] WEAR_LOCATION_LEFT_WRIST = new byte[] { 0x20, 0x00, 0x00, 0x02 };
+    public static final byte[] WEAR_LOCATION_RIGHT_WRIST = new byte[] { 0x20, 0x00, 0x00, (byte) 0x82};
+
+    public static final byte[] COMMAND_ENABLE_HR_SLEEP_MEASUREMENT = new byte[]{0x15, 0x00, 0x01};
+    public static final byte[] COMMAND_DISABLE_HR_SLEEP_MEASUREMENT = new byte[]{0x15, 0x00, 0x00};
+
+    public static final byte COMMAND_SET_PERIODIC_HR_MEASUREMENT_INTERVAL = 0x14;
+
+    public static final byte[] COMMAND_TEXT_NOTIFICATION = new byte[] {0x05, 0x01};
+
+    public static final byte[] COMMAND_ENABLE_HOURLY_CHIME = new byte[] { (byte) 0xfe, 0x0b, 0x00, 0x01, 0x0a, 0x00, 0x16, 0x00 };
+    public static final byte[] COMMAND_DISABLE_HOURLY_CHIME = new byte[] { (byte) 0xfe, 0x0b, 0x00, 0x00 };
+
+    static {
+        MIBAND_DEBUG = new HashMap<>();
+        MIBAND_DEBUG.put(UUID_SERVICE_MIBAND_SERVICE, "MiBand Service");
+        MIBAND_DEBUG.put(UUID_SERVICE_HEART_RATE, "MiBand HR Service");
+    }
+
+    public static String lookup(UUID uuid, String fallback) {
+        String name = MIBAND_DEBUG.get(uuid);
+        if (name == null) {
+            name = fallback;
+        }
+        return name;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiSettingsCustomizer.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiSettingsCustomizer.java
new file mode 100644
index 0000000..dd8dfe5
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiSettingsCustomizer.java
@@ -0,0 +1,135 @@
+/*  Copyright (C) 2022-2024 Jos茅 Rebelo, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami;
+
+import android.os.Parcel;
+import android.text.InputType;
+
+import androidx.preference.Preference;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Set;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsHandler;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiVibrationPatternNotificationType;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+public class HuamiSettingsCustomizer implements DeviceSpecificSettingsCustomizer {
+    final GBDevice device;
+    final List<HuamiVibrationPatternNotificationType> vibrationPatternNotificationTypes;
+
+    public HuamiSettingsCustomizer(final GBDevice device,
+                                   final List<HuamiVibrationPatternNotificationType> vibrationPatternNotificationTypes) {
+        this.device = device;
+        this.vibrationPatternNotificationTypes = vibrationPatternNotificationTypes;
+    }
+
+    @Override
+    public void onPreferenceChange(final Preference preference, final DeviceSpecificSettingsHandler handler) {
+        // Nothing to do here
+    }
+
+    @Override
+    public void customizeSettings(final DeviceSpecificSettingsHandler handler, final Prefs prefs, final String rootKey) {
+        final Preference hrAlertActivePref = handler.findPreference(DeviceSettingsPreferenceConst.PREF_HEARTRATE_ALERT_ACTIVE_HIGH_THRESHOLD);
+        if (hrAlertActivePref != null) {
+            hrAlertActivePref.setVisible(false);
+        }
+
+        // Setup the vibration patterns for all supported notification types
+        for (HuamiVibrationPatternNotificationType notificationType : HuamiVibrationPatternNotificationType.values()) {
+            final String typeKey = notificationType.name().toLowerCase(Locale.ROOT);
+
+            // Hide unsupported notification types
+            if (!vibrationPatternNotificationTypes.contains(notificationType)) {
+                final String screenKey = HuamiConst.PREF_HUAMI_VIBRATION_PROFILE_KEY_PREFIX + typeKey;
+                final Preference pref = handler.findPreference(screenKey);
+                if (pref != null) {
+                    pref.setVisible(false);
+                }
+                continue;
+            }
+
+            handler.addPreferenceHandlerFor(HuamiConst.PREF_HUAMI_VIBRATION_PROFILE_PREFIX + typeKey);
+            handler.addPreferenceHandlerFor(HuamiConst.PREF_HUAMI_VIBRATION_COUNT_PREFIX + typeKey);
+            handler.setInputTypeFor(HuamiConst.PREF_HUAMI_VIBRATION_COUNT_PREFIX + typeKey, InputType.TYPE_CLASS_NUMBER);
+
+            // Setup the try pref to vibrate the device
+            final String tryPrefKey = HuamiConst.PREF_HUAMI_VIBRATION_TRY_PREFIX + typeKey;
+            final Preference tryPref = handler.findPreference(tryPrefKey);
+            if (tryPref != null) {
+                tryPref.setOnPreferenceClickListener(preference -> {
+                    GBApplication.deviceService(device).onSendConfiguration(tryPrefKey);
+                    return true;
+                });
+            }
+
+            // Setup the default preference - disable count if default preference is selected
+            final String profilePrefKey = HuamiConst.PREF_HUAMI_VIBRATION_PROFILE_PREFIX + typeKey;
+            final String countPrefKey = HuamiConst.PREF_HUAMI_VIBRATION_COUNT_PREFIX + typeKey;
+            final Preference countPref = handler.findPreference(countPrefKey);
+
+            final Preference.OnPreferenceChangeListener profilePrefListener = (preference, newValue) -> {
+                if (countPref != null) {
+                    countPref.setEnabled(!HuamiConst.PREF_HUAMI_DEFAULT_VIBRATION_PROFILE.equals(newValue));
+                }
+                return true;
+            };
+
+            profilePrefListener.onPreferenceChange(null, prefs.getString(profilePrefKey, HuamiConst.PREF_HUAMI_DEFAULT_VIBRATION_PROFILE));
+            handler.addPreferenceHandlerFor(profilePrefKey, profilePrefListener);
+        }
+    }
+
+    @Override
+    public Set<String> getPreferenceKeysWithSummary() {
+        return new HashSet<>();
+    }
+
+    public static final Creator<HuamiSettingsCustomizer> CREATOR = new Creator<HuamiSettingsCustomizer>() {
+        @Override
+        public HuamiSettingsCustomizer createFromParcel(final Parcel in) {
+            final GBDevice device = in.readParcelable(HuamiSettingsCustomizer.class.getClassLoader());
+            final List<HuamiVibrationPatternNotificationType> vibrationPatternNotificationTypes = new ArrayList<>();
+            in.readList(vibrationPatternNotificationTypes, HuamiVibrationPatternNotificationType.class.getClassLoader());
+            return new HuamiSettingsCustomizer(device, vibrationPatternNotificationTypes);
+        }
+
+        @Override
+        public HuamiSettingsCustomizer[] newArray(final int size) {
+            return new HuamiSettingsCustomizer[size];
+        }
+    };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(final Parcel dest, final int flags) {
+        dest.writeParcelable(device, 0);
+        dest.writeList(vibrationPatternNotificationTypes);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiSleepRespiratoryRateSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiSleepRespiratoryRateSampleProvider.java
new file mode 100644
index 0000000..891a665
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiSleepRespiratoryRateSampleProvider.java
@@ -0,0 +1,56 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami;
+
+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.HuamiSleepRespiratoryRateSample;
+import nodomain.freeyourgadget.gadgetbridge.entities.HuamiSleepRespiratoryRateSampleDao;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class HuamiSleepRespiratoryRateSampleProvider extends AbstractTimeSampleProvider<HuamiSleepRespiratoryRateSample> {
+    public HuamiSleepRespiratoryRateSampleProvider(final GBDevice device, final DaoSession session) {
+        super(device, session);
+    }
+
+    @NonNull
+    @Override
+    public AbstractDao<HuamiSleepRespiratoryRateSample, ?> getSampleDao() {
+        return getSession().getHuamiSleepRespiratoryRateSampleDao();
+    }
+
+    @NonNull
+    @Override
+    protected Property getTimestampSampleProperty() {
+        return HuamiSleepRespiratoryRateSampleDao.Properties.Timestamp;
+    }
+
+    @NonNull
+    @Override
+    protected Property getDeviceIdentifierSampleProperty() {
+        return HuamiSleepRespiratoryRateSampleDao.Properties.DeviceId;
+    }
+
+    @Override
+    public HuamiSleepRespiratoryRateSample createSample() {
+        return new HuamiSleepRespiratoryRateSample();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiSpo2SampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiSpo2SampleProvider.java
new file mode 100644
index 0000000..af36d0f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiSpo2SampleProvider.java
@@ -0,0 +1,56 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami;
+
+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.HuamiSpo2Sample;
+import nodomain.freeyourgadget.gadgetbridge.entities.HuamiSpo2SampleDao;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class HuamiSpo2SampleProvider extends AbstractTimeSampleProvider<HuamiSpo2Sample> {
+    public HuamiSpo2SampleProvider(final GBDevice device, final DaoSession session) {
+        super(device, session);
+    }
+
+    @NonNull
+    @Override
+    public AbstractDao<HuamiSpo2Sample, ?> getSampleDao() {
+        return getSession().getHuamiSpo2SampleDao();
+    }
+
+    @NonNull
+    @Override
+    protected Property getTimestampSampleProperty() {
+        return HuamiSpo2SampleDao.Properties.Timestamp;
+    }
+
+    @NonNull
+    @Override
+    protected Property getDeviceIdentifierSampleProperty() {
+        return HuamiSpo2SampleDao.Properties.DeviceId;
+    }
+
+    @Override
+    public HuamiSpo2Sample createSample() {
+        return new HuamiSpo2Sample();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiStressSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiStressSampleProvider.java
new file mode 100644
index 0000000..dae4512
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiStressSampleProvider.java
@@ -0,0 +1,56 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami;
+
+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.HuamiStressSample;
+import nodomain.freeyourgadget.gadgetbridge.entities.HuamiStressSampleDao;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class HuamiStressSampleProvider extends AbstractTimeSampleProvider<HuamiStressSample> {
+    public HuamiStressSampleProvider(final GBDevice device, final DaoSession session) {
+        super(device, session);
+    }
+
+    @NonNull
+    @Override
+    public AbstractDao<HuamiStressSample, ?> getSampleDao() {
+        return getSession().getHuamiStressSampleDao();
+    }
+
+    @NonNull
+    @Override
+    protected Property getTimestampSampleProperty() {
+        return HuamiStressSampleDao.Properties.Timestamp;
+    }
+
+    @NonNull
+    @Override
+    protected Property getDeviceIdentifierSampleProperty() {
+        return HuamiStressSampleDao.Properties.DeviceId;
+    }
+
+    @Override
+    public HuamiStressSample createSample() {
+        return new HuamiStressSample();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiWeatherConditions.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiWeatherConditions.java
new file mode 100644
index 0000000..cea1963
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiWeatherConditions.java
@@ -0,0 +1,160 @@
+/*  Copyright (C) 2017-2024 Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+
+package nodomain.freeyourgadget.gadgetbridge.devices.huami;
+
+
+public class HuamiWeatherConditions {
+    public static final byte CLEAR_SKY = 0;
+    public static final byte SCATTERED_CLOUDS = 1;
+    public static final byte CLOUDY = 2;
+    public static final byte RAIN_WITH_SUN = 3;
+    public static final byte THUNDERSTORM = 4;
+    public static final byte HAIL = 5;
+    public static final byte RAIN_AND_SNOW = 6;
+    public static final byte LIGHT_RAIN = 7;
+    public static final byte MEDIUM_RAIN = 8;
+    public static final byte HEAVY_RAIN = 9;
+    public static final byte EXTREME_RAIN = 10;
+    public static final byte SUPER_EXTREME_RAIN = 11;
+    public static final byte TORRENTIAL_RAIN = 12;
+    public static final byte SNOW_AND_SUN = 13;
+    public static final byte LIGHT_SNOW = 14;
+    public static final byte MEDIUM_SNOW = 15;
+    public static final byte HEAVY_SNOW = 16;
+    public static final byte EXTREME_SNOW = 17;
+    public static final byte MIST = 18;
+    public static final byte DRIZZLE = 19;
+    public static final byte WIND_AND_RAIN = 20;
+    // 21- various types of rain
+
+    public static byte mapToAmazfitBipWeatherCode(int openWeatherMapCondition) {
+        // openweathermap.org conditions:
+        // http://openweathermap.org/weather-conditions
+        switch (openWeatherMapCondition) {
+//Group 2xx: Thunderstorm
+            case 200:  //thunderstorm with light rain:  //11d
+            case 201:  //thunderstorm with rain:  //11d
+            case 202:  //thunderstorm with heavy rain:  //11d
+            case 210:  //light thunderstorm::  //11d
+            case 211:  //thunderstorm:  //11d
+            case 230:  //thunderstorm with light drizzle:  //11d
+            case 231:  //thunderstorm with drizzle:  //11d
+            case 232:  //thunderstorm with heavy drizzle:  //11d
+            case 212:  //heavy thunderstorm:  //11d
+            case 221:  //ragged thunderstorm:  //11d
+                return THUNDERSTORM;
+//Group 3xx: Drizzle
+            case 300:  //light intensity drizzle:  //09d
+            case 301:  //drizzle:  //09d
+            case 302:  //heavy intensity drizzle:  //09d
+            case 310:  //light intensity drizzle rain:  //09d
+            case 311:  //drizzle rain:  //09d
+            case 312:  //heavy intensity drizzle rain:  //09d
+            case 313:  //shower rain and drizzle:  //09d
+            case 314:  //heavy shower rain and drizzle:  //09d
+            case 321:  //shower drizzle:  //09d
+                return DRIZZLE;
+//Group 5xx: Rain
+            case 500:  //light rain:  //10d
+                return LIGHT_RAIN;
+            case 501:  //moderate rain:  //10d
+                return MEDIUM_RAIN;
+            case 502:  //heavy intensity rain:  //10d
+                return HEAVY_RAIN;
+            case 503:  //very heavy rain:  //10d
+                return EXTREME_RAIN;
+            case 504:  //extreme rain:  //10d
+                return TORRENTIAL_RAIN;
+            case 511:  //freezing rain:  //13d
+                return MEDIUM_RAIN;
+            case 520:  //light intensity shower rain:  //09d
+                return LIGHT_RAIN;
+            case 521:  //shower rain:  //09d
+                return MEDIUM_RAIN;
+            case 522:  //heavy intensity shower rain:  //09d
+                return HEAVY_RAIN;
+            case 531:  //ragged shower rain:  //09d
+                return MEDIUM_RAIN;
+//Group 6xx: Snow
+            case 600:  //light snow:  //[[file:13d.png]]
+                return LIGHT_SNOW;
+            case 601:  //snow:  //[[file:13d.png]]
+                return MEDIUM_SNOW;
+            case 602:  //heavy snow:  //[[file:13d.png]]
+                return HEAVY_SNOW;
+            case 611:  //sleet:  //[[file:13d.png]]
+            case 612:  //shower sleet:  //[[file:13d.png]]
+            case 615:  //light rain and snow:  //[[file:13d.png]]
+            case 616:  //rain and snow:  //[[file:13d.png]]
+            case 620:  //light shower snow:  //[[file:13d.png]]
+            case 621:  //shower snow:  //[[file:13d.png]]
+            case 622:  //heavy shower snow:  //[[file:13d.png]]
+                return RAIN_AND_SNOW;
+
+//Group 7xx: Atmosphere
+            case 701:  //mist:  //[[file:50d.png]]
+            case 711:  //smoke:  //[[file:50d.png]]
+            case 721:  //haze:  //[[file:50d.png]]
+            case 731:  //sandcase  dust whirls:  //[[file:50d.png]]
+            case 741:  //fog:  //[[file:50d.png]]
+            case 751:  //sand:  //[[file:50d.png]]
+            case 761:  //dust:  //[[file:50d.png]]
+            case 762:  //volcanic ash:  //[[file:50d.png]]
+            case 771:  //squalls:  //[[file:50d.png]]
+                return MIST;
+            case 781:  //tornado:  //[[file:50d.png]]
+            case 900:  //tornado
+                return WIND_AND_RAIN;
+//Group 800: Clear
+            case 800:  //clear sky:  //[[file:01d.png]] [[file:01n.png]]
+                return CLEAR_SKY;
+//Group 80x: Clouds
+            case 801:  //few clouds:  //[[file:02d.png]] [[file:02n.png]]
+            case 802:  //scattered clouds:  //[[file:03d.png]] [[file:03d.png]]
+            case 803:  //broken clouds:  //[[file:04d.png]] [[file:03d.png]]
+                return SCATTERED_CLOUDS;
+            case 804:  //overcast clouds:  //[[file:04d.png]] [[file:04d.png]]
+                return CLOUDY;
+//Group 90x: Extreme
+            case 901:  //tropical storm
+                return WIND_AND_RAIN;
+            case 903:  //cold
+            case 904:  //hot
+            case 905:  //windy
+                return 0;
+            case 906:  //hail
+                return HAIL;
+//Group 9xx: Additional
+            case 951:  //calm
+            case 952:  //light breeze
+            case 953:  //gentle breeze
+            case 954:  //moderate breeze
+            case 955:  //fresh breeze
+            case 956:  //strong breeze
+            case 957:  //high windcase  near gale
+            case 958:  //gale
+            case 959:  //severe gale
+            case 960:  //storm
+            case 961:  //violent storm
+            case 902:  //hurricane
+            case 962:  //hurricane
+            default:
+                return 0;
+        }
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitactive/AmazfitActiveCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitactive/AmazfitActiveCoordinator.java
new file mode 100644
index 0000000..0b76f98
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitactive/AmazfitActiveCoordinator.java
@@ -0,0 +1,86 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitactive;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.zeppos.ZeppOsCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class AmazfitActiveCoordinator extends ZeppOsCoordinator {
+    @Override
+    public String getDeviceBluetoothName() {
+        return HuamiConst.AMAZFIT_ACTIVE_NAME;
+    }
+
+    @Override
+    public Set<Integer> getDeviceSources() {
+        return new HashSet<>(Arrays.asList(
+                8323328, // chinese mainland version
+                8323329
+        ));
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_amazfit_active;
+    }
+
+    @Override
+    public boolean supportsContinuousFindDevice() {
+        return true;
+    }
+
+    @Override
+    public boolean mainMenuHasMoreSection() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsGpxUploads() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsControlCenter() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsToDoList() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsWifiHotspot(final GBDevice device) {
+        return true;
+    }
+
+    @Override
+    public boolean supportsFtpServer(final GBDevice device) {
+        return true;
+    }
+
+    public boolean supportsBluetoothPhoneCalls(final GBDevice device) {
+        return true;
+    }
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitactiveedge/AmazfitActiveEdgeCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitactiveedge/AmazfitActiveEdgeCoordinator.java
new file mode 100644
index 0000000..f82d935
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitactiveedge/AmazfitActiveEdgeCoordinator.java
@@ -0,0 +1,90 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitactiveedge;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.zeppos.ZeppOsCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class AmazfitActiveEdgeCoordinator extends ZeppOsCoordinator {
+    @Override
+    public boolean isExperimental() {
+        return true;
+    }
+
+    @Override
+    public String getDeviceBluetoothName() {
+        return HuamiConst.AMAZFIT_ACTIVE_EDGE_NAME;
+    }
+
+    @Override
+    public Set<Integer> getDeviceSources() {
+        return new HashSet<>(Arrays.asList(
+                8388864, // chinese mainland version
+                8388865
+        ));
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_amazfit_active_edge;
+    }
+
+    @Override
+    public boolean supportsContinuousFindDevice() {
+        return true;
+    }
+
+    @Override
+    public boolean mainMenuHasMoreSection() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsGpxUploads() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsControlCenter() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsToDoList() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsWifiHotspot(final GBDevice device) {
+        return true;
+    }
+
+    @Override
+    public boolean supportsFtpServer(final GBDevice device) {
+        return true;
+    }
+
+    public boolean supportsBluetoothPhoneCalls(final GBDevice device) {
+        return true;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbalance/AmazfitBalanceCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbalance/AmazfitBalanceCoordinator.java
new file mode 100644
index 0000000..676cac8
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbalance/AmazfitBalanceCoordinator.java
@@ -0,0 +1,86 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo, Maxime Reyrolle
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitbalance;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.zeppos.ZeppOsCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class AmazfitBalanceCoordinator extends ZeppOsCoordinator {
+    @Override
+    public String getDeviceBluetoothName() {
+        return HuamiConst.AMAZFIT_BALANCE_NAME;
+    }
+
+    @Override
+    public Set<Integer> getDeviceSources() {
+        return new HashSet<>(Arrays.asList(
+                8519936, // chinese mainland version
+                8519937,
+                8519939
+        ));
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_amazfit_balance;
+    }
+
+    @Override
+    public boolean supportsContinuousFindDevice() {
+        return true;
+    }
+
+    @Override
+    public boolean mainMenuHasMoreSection() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsGpxUploads() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsControlCenter() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsToDoList() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsWifiHotspot(final GBDevice device) {
+        return true;
+    }
+
+    @Override
+    public boolean supportsFtpServer(final GBDevice device) {
+        return true;
+    }
+
+    public boolean supportsBluetoothPhoneCalls(final GBDevice device) {
+        return true;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitband5/AmazfitBand5Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitband5/AmazfitBand5Coordinator.java
new file mode 100644
index 0000000..87761de
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitband5/AmazfitBand5Coordinator.java
@@ -0,0 +1,188 @@
+/*  Copyright (C) 2020-2024 Andreas Shimokawa, Arjan Schrijver, Daniel Dakhno,
+    Jos茅 Rebelo, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitband5;
+
+import android.content.Context;
+import android.net.Uri;
+
+import androidx.annotation.NonNull;
+
+import java.util.List;
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettings;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsScreen;
+import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitband5.AmazfitBand5Support;
+
+public class AmazfitBand5Coordinator extends HuamiCoordinator {
+    @Override
+    public String getManufacturer() {
+        // Actual manufacturer is Huami
+        return "Amazfit";
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile(HuamiConst.AMAZFIT_BAND5_NAME, Pattern.CASE_INSENSITIVE);
+    }
+
+    @Override
+    public InstallHandler findInstallHandler(final Uri uri, final Context context) {
+        final AmazfitBand5FWInstallHandler handler = new AmazfitBand5FWInstallHandler(uri, context);
+        return handler.isValid() ? handler : null;
+    }
+
+    @Override
+    public boolean supportsHeartRateMeasurement(final GBDevice device) {
+        return true;
+    }
+
+    @Override
+    public boolean supportsWeather() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsActivityTracks() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsStressMeasurement() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsMusicInfo() {
+        return true;
+    }
+
+    @Override
+    public int getWorldClocksSlotCount() {
+        return 20; // as enforced by Mi Fit
+    }
+
+    @Override
+    public int getWorldClocksLabelLength() {
+        return 30; // at least
+    }
+
+    @Override
+    public DeviceSpecificSettings getDeviceSpecificSettings(final GBDevice device) {
+        final DeviceSpecificSettings deviceSpecificSettings = new DeviceSpecificSettings();
+
+        final List<Integer> generic = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.GENERIC);
+        generic.add(R.xml.devicesettings_wearlocation);
+        generic.add(R.xml.devicesettings_device_actions);
+        final List<Integer> dateTime = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DATE_TIME);
+        dateTime.add(R.xml.devicesettings_timeformat);
+        dateTime.add(R.xml.devicesettings_dateformat);
+        dateTime.add(R.xml.devicesettings_world_clocks);
+        final List<Integer> display = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DISPLAY);
+        display.add(R.xml.devicesettings_amazfitband5);
+        display.add(R.xml.devicesettings_nightmode);
+        display.add(R.xml.devicesettings_liftwrist_display_sensitivity);
+        display.add(R.xml.devicesettings_swipeunlock);
+        final List<Integer> health = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.HEALTH);
+        health.add(R.xml.devicesettings_heartrate_sleep_alert_activity_stress);
+        health.add(R.xml.devicesettings_inactivity_dnd);
+        health.add(R.xml.devicesettings_goal_notification);
+        final List<Integer> workout = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.WORKOUT);
+        workout.add(R.xml.devicesettings_workout_start_on_phone);
+        workout.add(R.xml.devicesettings_workout_send_gps_to_band);
+        final List<Integer> notifications = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.NOTIFICATIONS);
+        notifications.add(R.xml.devicesettings_custom_emoji_font);
+        notifications.add(R.xml.devicesettings_vibrationpatterns);
+        notifications.add(R.xml.devicesettings_phone_silent_mode);
+        notifications.add(R.xml.devicesettings_transliteration);
+        final List<Integer> calendar = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.CALENDAR);
+        calendar.add(R.xml.devicesettings_sync_calendar);
+        calendar.add(R.xml.devicesettings_reserve_reminders_calendar);
+        final List<Integer> connection = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.CONNECTION);
+        connection.add(R.xml.devicesettings_expose_hr_thirdparty);
+        connection.add(R.xml.devicesettings_bt_connected_advertisement);
+        connection.add(R.xml.devicesettings_high_mtu);
+        connection.add(R.xml.devicesettings_overwrite_settings_on_connection);
+        final List<Integer> developer = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DEVELOPER);
+        developer.add(R.xml.devicesettings_huami2021_fetch_operation_time_unit);
+
+        return deviceSpecificSettings;
+    }
+
+    @Override
+    public String[] getSupportedLanguageSettings(final GBDevice device) {
+        return new String[]{
+                "auto",
+                "ar_SA",
+                "cs_CZ",
+                "de_DE",
+                "el_GR",
+                "en_US",
+                "es_ES",
+                "fr_FR",
+                "he_IL",
+                "id_ID",
+                "it_IT",
+                "ja_JP",
+                "ko_KO",
+                "nl_NL",
+                "pt_BR",
+                "pl_PL",
+                "ro_RO",
+                "ru_RU",
+                "th_TH",
+                "tr_TR",
+                "uk_UA",
+                "vi_VN",
+                "zh_CN",
+                "zh_TW",
+        };
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return AmazfitBand5Support.class;
+    }
+
+    @Override
+    public int getBondingStyle() {
+        return BONDING_STYLE_REQUIRE_KEY;
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_amazfit_band5;
+    }
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_miband2;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_miband2_disabled;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitband5/AmazfitBand5FWHelper.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitband5/AmazfitBand5FWHelper.java
new file mode 100644
index 0000000..67ca1b8
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitband5/AmazfitBand5FWHelper.java
@@ -0,0 +1,40 @@
+/*  Copyright (C) 2020-2024 Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitband5;
+
+import android.content.Context;
+import android.net.Uri;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitband5.AmazfitBand5FirmwareInfo;
+
+public class AmazfitBand5FWHelper extends HuamiFWHelper {
+
+    public AmazfitBand5FWHelper(final Uri uri, final Context context) throws IOException {
+        super(uri, context);
+    }
+
+    @Override
+    protected void determineFirmwareInfo(byte[] wholeFirmwareBytes) {
+        firmwareInfo = new AmazfitBand5FirmwareInfo(wholeFirmwareBytes);
+        if (!firmwareInfo.isHeaderValid()) {
+            throw new IllegalArgumentException("Not a Amazfit Band 5 firmware");
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitband5/AmazfitBand5FWInstallHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitband5/AmazfitBand5FWInstallHandler.java
new file mode 100644
index 0000000..225040b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitband5/AmazfitBand5FWInstallHandler.java
@@ -0,0 +1,49 @@
+/*  Copyright (C) 2020-2024 Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitband5;
+
+import android.content.Context;
+import android.net.Uri;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.AbstractMiBandFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.AbstractMiBandFWInstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+
+class AmazfitBand5FWInstallHandler extends AbstractMiBandFWInstallHandler {
+    AmazfitBand5FWInstallHandler(final Uri uri, final Context context) {
+        super(uri, context);
+    }
+
+    @Override
+    protected String getFwUpgradeNotice() {
+        return mContext.getString(R.string.fw_upgrade_notice_miband5, helper.getHumanFirmwareVersion());
+    }
+
+    @Override
+    protected AbstractMiBandFWHelper createHelper(final Uri uri, final Context context) throws IOException {
+        return new AmazfitBand5FWHelper(uri, context);
+    }
+
+    @Override
+    protected boolean isSupportedDeviceType(final GBDevice device) {
+        return device.getType() == DeviceType.AMAZFITBAND5;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitband7/AmazfitBand7Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitband7/AmazfitBand7Coordinator.java
new file mode 100644
index 0000000..599c812
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitband7/AmazfitBand7Coordinator.java
@@ -0,0 +1,68 @@
+/*  Copyright (C) 2022-2024 Arjan Schrijver, Daniel Dakhno, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitband7;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.zeppos.ZeppOsCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class AmazfitBand7Coordinator extends ZeppOsCoordinator {
+    @Override
+    public String getDeviceBluetoothName() {
+        return HuamiConst.AMAZFIT_BAND7_NAME;
+    }
+
+    @Override
+    public Set<Integer> getDeviceSources() {
+        return new HashSet<>(Arrays.asList(
+                252,
+                253,
+                254
+        ));
+    }
+
+    @Override
+    public boolean supportsAgpsUpdates() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsBluetoothPhoneCalls(final GBDevice device) {
+        return false;
+    }
+
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_amazfit_band7;
+    }
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_default;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_default_disabled;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbip/AmazfitBipCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbip/AmazfitBipCoordinator.java
new file mode 100644
index 0000000..d17c282
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbip/AmazfitBipCoordinator.java
@@ -0,0 +1,142 @@
+/*  Copyright (C) 2017-2024 Andreas Shimokawa, Carsten Pfeiffer, Daniel
+    Dakhno, Daniele Gobbetti, Jos茅 Rebelo, Nephiel, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitbip;
+
+import android.content.Context;
+import android.net.Uri;
+
+import androidx.annotation.NonNull;
+
+import java.util.List;
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettings;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsScreen;
+import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitbip.AmazfitBipSupport;
+
+public class AmazfitBipCoordinator extends HuamiCoordinator {
+    @Override
+    public String getManufacturer() {
+        // Actual manufacturer is Huami
+        return "Amazfit";
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("Amazfit Bip Watch", Pattern.CASE_INSENSITIVE);
+    }
+
+    @Override
+    public InstallHandler findInstallHandler(final Uri uri, final Context context) {
+        final AmazfitBipFWInstallHandler handler = new AmazfitBipFWInstallHandler(uri, context);
+        return handler.isValid() ? handler : null;
+    }
+
+    @Override
+    public boolean supportsHeartRateMeasurement(final GBDevice device) {
+        return true;
+    }
+
+    @Override
+    public boolean supportsActivityTracks() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsWeather() {
+        return true;
+    }
+
+    @Override
+    public DeviceSpecificSettings getDeviceSpecificSettings(final GBDevice device) {
+        final DeviceSpecificSettings deviceSpecificSettings = new DeviceSpecificSettings();
+
+        final List<Integer> generic = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.GENERIC);
+        generic.add(R.xml.devicesettings_wearlocation);
+        generic.add(R.xml.devicesettings_buttonactions_with_longpress);
+        generic.add(R.xml.devicesettings_device_actions);
+        final List<Integer> dateTime = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DATE_TIME);
+        dateTime.add(R.xml.devicesettings_timeformat);
+        final List<Integer> display = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DISPLAY);
+        display.add(R.xml.devicesettings_amazfitbip);
+        display.add(R.xml.devicesettings_liftwrist_display);
+        final List<Integer> health = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.HEALTH);
+        health.add(R.xml.devicesettings_heartrate_sleep);
+        health.add(R.xml.devicesettings_inactivity_dnd);
+        health.add(R.xml.devicesettings_goal_notification);
+        final List<Integer> notifications = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.NOTIFICATIONS);
+        notifications.add(R.xml.devicesettings_custom_emoji_font);
+        notifications.add(R.xml.devicesettings_phone_silent_mode);
+        notifications.add(R.xml.devicesettings_transliteration);
+        final List<Integer> calendar = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.CALENDAR);
+        calendar.add(R.xml.devicesettings_sync_calendar);
+        calendar.add(R.xml.devicesettings_reserve_reminders_calendar);
+        final List<Integer> connection = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.CONNECTION);
+        connection.add(R.xml.devicesettings_disconnectnotification);
+        connection.add(R.xml.devicesettings_bt_connected_advertisement);
+        connection.add(R.xml.devicesettings_overwrite_settings_on_connection);
+        final List<Integer> developer = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DEVELOPER);
+        developer.add(R.xml.devicesettings_huami2021_fetch_operation_time_unit);
+
+        return deviceSpecificSettings;
+    }
+
+    @Override
+    public String[] getSupportedLanguageSettings(final GBDevice device) {
+        return new String[]{
+                "auto",
+                "zh_CN",
+                "zh_TW",
+                "en_US",
+                "es_ES",
+                "de_DE",
+                "it_IT",
+                "fr_FR",
+                "pt_BR",
+                "tr_TR",
+                "cs_CZ",
+                "ru_RU",
+        };
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return AmazfitBipSupport.class;
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_amazfit_bip;
+    }
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_amazfit_bip;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_amazfit_bip_disabled;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbip/AmazfitBipFWHelper.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbip/AmazfitBipFWHelper.java
new file mode 100644
index 0000000..9dccb3c
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbip/AmazfitBipFWHelper.java
@@ -0,0 +1,43 @@
+/*  Copyright (C) 2017-2024 Andreas Shimokawa, Daniele Gobbetti
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitbip;
+
+import android.content.Context;
+import android.net.Uri;
+
+import androidx.annotation.NonNull;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitbip.AmazfitBipFirmwareInfo;
+
+public class AmazfitBipFWHelper extends HuamiFWHelper {
+
+    public AmazfitBipFWHelper(final Uri uri, final Context context) throws IOException {
+        super(uri, context);
+    }
+
+    @NonNull
+    @Override
+    protected void determineFirmwareInfo(byte[] wholeFirmwareBytes) {
+        firmwareInfo = new AmazfitBipFirmwareInfo(wholeFirmwareBytes);
+        if (!firmwareInfo.isHeaderValid()) {
+            throw new IllegalArgumentException("Not a an Amazfit Bip firmware");
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbip/AmazfitBipFWInstallHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbip/AmazfitBipFWInstallHandler.java
new file mode 100644
index 0000000..80b9e42
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbip/AmazfitBipFWInstallHandler.java
@@ -0,0 +1,49 @@
+/*  Copyright (C) 2017-2024 Andreas Shimokawa, Carsten Pfeiffer
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitbip;
+
+import android.content.Context;
+import android.net.Uri;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.AbstractMiBandFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.AbstractMiBandFWInstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+
+class AmazfitBipFWInstallHandler extends AbstractMiBandFWInstallHandler {
+    AmazfitBipFWInstallHandler(final Uri uri, final Context context) {
+        super(uri, context);
+    }
+
+    @Override
+    protected String getFwUpgradeNotice() {
+        return mContext.getString(R.string.fw_upgrade_notice_amazfitbip, helper.getHumanFirmwareVersion());
+    }
+
+    @Override
+    protected AbstractMiBandFWHelper createHelper(final Uri uri, final Context context) throws IOException {
+        return new AmazfitBipFWHelper(uri, context);
+    }
+
+    @Override
+    protected boolean isSupportedDeviceType(final GBDevice device) {
+        return device.getType() == DeviceType.AMAZFITBIP;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbip/AmazfitBipLiteCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbip/AmazfitBipLiteCoordinator.java
new file mode 100644
index 0000000..5eae922
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbip/AmazfitBipLiteCoordinator.java
@@ -0,0 +1,98 @@
+/*  Copyright (C) 2019-2024 Andreas Shimokawa, Daniel Dakhno, Jos茅 Rebelo,
+    Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitbip;
+
+import android.content.Context;
+import android.net.Uri;
+
+import androidx.annotation.NonNull;
+
+import java.util.List;
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettings;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsScreen;
+import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitbip.AmazfitBipLiteSupport;
+
+public class AmazfitBipLiteCoordinator extends AmazfitBipCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("Amazfit Bip Lite", Pattern.CASE_INSENSITIVE);
+    }
+
+    @Override
+    public InstallHandler findInstallHandler(final Uri uri, final Context context) {
+        final AmazfitBipLiteFWInstallHandler handler = new AmazfitBipLiteFWInstallHandler(uri, context);
+        return handler.isValid() ? handler : null;
+    }
+
+    @Override
+    public int getBondingStyle() {
+        return BONDING_STYLE_REQUIRE_KEY;
+    }
+
+    @Override
+    public DeviceSpecificSettings getDeviceSpecificSettings(final GBDevice device) {
+        final DeviceSpecificSettings deviceSpecificSettings = new DeviceSpecificSettings();
+
+        final List<Integer> generic = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.GENERIC);
+        generic.add(R.xml.devicesettings_wearlocation);
+        generic.add(R.xml.devicesettings_buttonactions_with_longpress);
+        generic.add(R.xml.devicesettings_device_actions);
+        final List<Integer> dateTime = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DATE_TIME);
+        dateTime.add(R.xml.devicesettings_timeformat);
+        final List<Integer> display = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DISPLAY);
+        display.add(R.xml.devicesettings_amazfitbip);
+        display.add(R.xml.devicesettings_liftwrist_display);
+        final List<Integer> health = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.HEALTH);
+        health.add(R.xml.devicesettings_heartrate_sleep);
+        health.add(R.xml.devicesettings_inactivity_dnd);
+        health.add(R.xml.devicesettings_goal_notification);
+        final List<Integer> notifications = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.NOTIFICATIONS);
+        notifications.add(R.xml.devicesettings_custom_emoji_font);
+        notifications.add(R.xml.devicesettings_vibrationpatterns);
+        notifications.add(R.xml.devicesettings_transliteration);
+        final List<Integer> calendar = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.CALENDAR);
+        calendar.add(R.xml.devicesettings_sync_calendar);
+        //calendar.add(R.xml.devicesettings_reserve_reminders_calendar);
+        final List<Integer> connection = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.CONNECTION);
+        connection.add(R.xml.devicesettings_disconnectnotification);
+        connection.add(R.xml.devicesettings_bt_connected_advertisement);
+        connection.add(R.xml.devicesettings_overwrite_settings_on_connection);
+        final List<Integer> developer = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DEVELOPER);
+        developer.add(R.xml.devicesettings_relax_firmware_checks);
+        developer.add(R.xml.devicesettings_huami2021_fetch_operation_time_unit);
+
+        return deviceSpecificSettings;
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_amazfit_bip_lite;
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return AmazfitBipLiteSupport.class;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbip/AmazfitBipLiteFWHelper.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbip/AmazfitBipLiteFWHelper.java
new file mode 100644
index 0000000..a567b57
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbip/AmazfitBipLiteFWHelper.java
@@ -0,0 +1,43 @@
+/*  Copyright (C) 2019-2024 Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitbip;
+
+import android.content.Context;
+import android.net.Uri;
+
+import androidx.annotation.NonNull;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitbip.AmazfitBipLiteFirmwareInfo;
+
+public class AmazfitBipLiteFWHelper extends HuamiFWHelper {
+
+    public AmazfitBipLiteFWHelper(final Uri uri, final Context context) throws IOException {
+        super(uri, context);
+    }
+
+    @NonNull
+    @Override
+    protected void determineFirmwareInfo(byte[] wholeFirmwareBytes) {
+        firmwareInfo = new AmazfitBipLiteFirmwareInfo(wholeFirmwareBytes);
+        if (!firmwareInfo.isHeaderValid()) {
+            throw new IllegalArgumentException("Not a an Amazfit Bip Lite firmware");
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbip/AmazfitBipLiteFWInstallHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbip/AmazfitBipLiteFWInstallHandler.java
new file mode 100644
index 0000000..7dfbf9c
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbip/AmazfitBipLiteFWInstallHandler.java
@@ -0,0 +1,49 @@
+/*  Copyright (C) 2019-2024 Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitbip;
+
+import android.content.Context;
+import android.net.Uri;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.AbstractMiBandFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.AbstractMiBandFWInstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+
+class AmazfitBipLiteFWInstallHandler extends AbstractMiBandFWInstallHandler {
+    AmazfitBipLiteFWInstallHandler(final Uri uri, final Context context) {
+        super(uri, context);
+    }
+
+    @Override
+    protected String getFwUpgradeNotice() {
+        return mContext.getString(R.string.fw_upgrade_notice_amazfitbip_lite, helper.getHumanFirmwareVersion());
+    }
+
+    @Override
+    protected AbstractMiBandFWHelper createHelper(final Uri uri, final Context context) throws IOException {
+        return new AmazfitBipLiteFWHelper(uri, context);
+    }
+
+    @Override
+    protected boolean isSupportedDeviceType(final GBDevice device) {
+        return device.getType() == DeviceType.AMAZFITBIP_LITE;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbip/AmazfitBipService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbip/AmazfitBipService.java
new file mode 100644
index 0000000..d59a10c
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbip/AmazfitBipService.java
@@ -0,0 +1,33 @@
+/*  Copyright (C) 2017-2024 Andreas Shimokawa, Carsten Pfeiffer
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitbip;
+
+import java.util.UUID;
+
+import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService.ENDPOINT_DISPLAY;
+
+public class AmazfitBipService {
+    public static final UUID UUID_CHARACTERISTIC_WEATHER = UUID.fromString("0000000e-0000-3512-2118-0009af100700");
+
+    public static final byte[] COMMAND_SET_LANGUAGE_SIMPLIFIED_CHINESE = new byte[]{ENDPOINT_DISPLAY, 0x13, 0x00, 0x00};
+    public static final byte[] COMMAND_SET_LANGUAGE_TRADITIONAL_CHINESE = new byte[]{ENDPOINT_DISPLAY, 0x13, 0x00, 0x01};
+    public static final byte[] COMMAND_SET_LANGUAGE_ENGLISH = new byte[]{ENDPOINT_DISPLAY, 0x13, 0x00, 0x02};
+    public static final byte[] COMMAND_SET_LANGUAGE_SPANISH = new byte[]{ENDPOINT_DISPLAY, 0x13, 0x00, 0x03};
+
+    public static final byte[] COMMAND_ACK_FIND_PHONE_IN_PROGRESS = new byte[]{ENDPOINT_DISPLAY, 0x14, 0x00, 0x00};
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbip3/AmazfitBip3Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbip3/AmazfitBip3Coordinator.java
new file mode 100644
index 0000000..5b97416
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbip3/AmazfitBip3Coordinator.java
@@ -0,0 +1,191 @@
+/*  Copyright (C) 2023-2024 Daniel Dakhno, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitbip3;
+
+import android.content.Context;
+import android.net.Uri;
+
+import androidx.annotation.NonNull;
+
+import java.util.List;
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettings;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsScreen;
+import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitbip3.AmazfitBip3Support;
+
+public class AmazfitBip3Coordinator extends HuamiCoordinator {
+    @Override
+    public String getManufacturer() {
+        // Actual manufacturer is Huami
+        return "Amazfit";
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("^Amazfit Bip 3$", Pattern.CASE_INSENSITIVE);
+    }
+
+    @Override
+    public InstallHandler findInstallHandler(final Uri uri, final Context context) {
+        final AmazfitBip3FWInstallHandler handler = new AmazfitBip3FWInstallHandler(uri, context);
+        return handler.isValid() ? handler : null;
+    }
+
+    @Override
+    public int getBondingStyle() {
+        return BONDING_STYLE_REQUIRE_KEY;
+    }
+
+    @Override
+    public boolean supportsHeartRateMeasurement(final GBDevice device) {
+        return true;
+    }
+
+    @Override
+    public boolean supportsActivityTracks() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsWeather() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsMusicInfo() {
+        return true;
+    }
+
+    @Override
+    public int getWorldClocksSlotCount() {
+        return 20; // as enforced by Zepp
+    }
+
+    @Override
+    public int getWorldClocksLabelLength() {
+        return 30; // at least
+    }
+
+    @Override
+    public boolean supportsUnicodeEmojis() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsStressMeasurement() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsSpo2(final GBDevice device) {
+        return true;
+    }
+
+    @Override
+    public boolean supportsPai() {
+        return true;
+    }
+
+    @Override
+    public DeviceSpecificSettings getDeviceSpecificSettings(final GBDevice device) {
+        final DeviceSpecificSettings deviceSpecificSettings = new DeviceSpecificSettings();
+
+        final List<Integer> generic = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.GENERIC);
+        generic.add(R.xml.devicesettings_wearlocation);
+        generic.add(R.xml.devicesettings_device_actions);
+        final List<Integer> dateTime = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DATE_TIME);
+        dateTime.add(R.xml.devicesettings_timeformat);
+        dateTime.add(R.xml.devicesettings_dateformat);
+        dateTime.add(R.xml.devicesettings_world_clocks);
+        final List<Integer> display = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DISPLAY);
+        display.add(R.xml.devicesettings_amazfitbip3pro);
+        display.add(R.xml.devicesettings_liftwrist_display_sensitivity);
+        final List<Integer> health = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.HEALTH);
+        health.add(R.xml.devicesettings_heartrate_sleep_alert_activity_stress);
+        health.add(R.xml.devicesettings_inactivity_dnd);
+        health.add(R.xml.devicesettings_goal_notification);
+        final List<Integer> notifications = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.NOTIFICATIONS);
+        notifications.add(R.xml.devicesettings_vibrationpatterns);
+        notifications.add(R.xml.devicesettings_transliteration);
+        final List<Integer> calendar = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.CALENDAR);
+        calendar.add(R.xml.devicesettings_sync_calendar);
+        calendar.add(R.xml.devicesettings_reserve_reminders_calendar);
+        final List<Integer> connection = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.CONNECTION);
+        connection.add(R.xml.devicesettings_expose_hr_thirdparty);
+        connection.add(R.xml.devicesettings_bt_connected_advertisement);
+        connection.add(R.xml.devicesettings_high_mtu);
+        connection.add(R.xml.devicesettings_overwrite_settings_on_connection);
+        final List<Integer> developer = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DEVELOPER);
+        developer.add(R.xml.devicesettings_huami2021_fetch_operation_time_unit);
+
+        return deviceSpecificSettings;
+    }
+
+    @Override
+    public String[] getSupportedLanguageSettings(final GBDevice device) {
+        return new String[]{
+                "auto",
+                "cs_CZ",
+                "de_DE",
+                "el_GR",
+                "en_US",
+                "es_ES",
+                "fr_FR",
+                "id_ID",
+                "it_IT",
+                "ja_JP",
+                "ko_KO",
+                "nl_NL",
+                "pl_PL",
+                "pt_BR",
+                "ru_RU",
+                "th_TH",
+                "tr_TR",
+                "uk_UA",
+                "vi_VN",
+                "zh_CH",
+                "zh_TW",
+        };
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return AmazfitBip3Support.class;
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_amazfit_bip3;
+    }
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_amazfit_bip;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_amazfit_bip_disabled;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbip3/AmazfitBip3FWHelper.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbip3/AmazfitBip3FWHelper.java
new file mode 100644
index 0000000..4a5e908
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbip3/AmazfitBip3FWHelper.java
@@ -0,0 +1,39 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitbip3;
+
+import android.content.Context;
+import android.net.Uri;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitbip3.AmazfitBip3FirmwareInfo;
+
+public class AmazfitBip3FWHelper extends HuamiFWHelper {
+    public AmazfitBip3FWHelper(final Uri uri, final Context context) throws IOException {
+        super(uri, context);
+    }
+
+    @Override
+    protected void determineFirmwareInfo(final byte[] wholeFirmwareBytes) {
+        firmwareInfo = new AmazfitBip3FirmwareInfo(wholeFirmwareBytes);
+        if (!firmwareInfo.isHeaderValid()) {
+            throw new IllegalArgumentException("Not an Amazfit Bip 3 firmware");
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbip3/AmazfitBip3FWInstallHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbip3/AmazfitBip3FWInstallHandler.java
new file mode 100644
index 0000000..20d5323
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbip3/AmazfitBip3FWInstallHandler.java
@@ -0,0 +1,49 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitbip3;
+
+import android.content.Context;
+import android.net.Uri;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.AbstractMiBandFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.AbstractMiBandFWInstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+
+class AmazfitBip3FWInstallHandler extends AbstractMiBandFWInstallHandler {
+    AmazfitBip3FWInstallHandler(final Uri uri, final Context context) {
+        super(uri, context);
+    }
+
+    @Override
+    protected String getFwUpgradeNotice() {
+        return mContext.getString(R.string.fw_upgrade_notice_amazfitbip3, helper.getHumanFirmwareVersion());
+    }
+
+    @Override
+    protected AbstractMiBandFWHelper createHelper(final Uri uri, final Context context) throws IOException {
+        return new AmazfitBip3FWHelper(uri, context);
+    }
+
+    @Override
+    protected boolean isSupportedDeviceType(final GBDevice device) {
+        return device.getType() == DeviceType.AMAZFITBIP3;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbip3pro/AmazfitBip3ProCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbip3pro/AmazfitBip3ProCoordinator.java
new file mode 100644
index 0000000..882afb2
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbip3pro/AmazfitBip3ProCoordinator.java
@@ -0,0 +1,191 @@
+/*  Copyright (C) 2023-2024 Daniel Dakhno, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitbip3pro;
+
+import android.content.Context;
+import android.net.Uri;
+
+import androidx.annotation.NonNull;
+
+import java.util.List;
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettings;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsScreen;
+import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitbip3pro.AmazfitBip3ProSupport;
+
+public class AmazfitBip3ProCoordinator extends HuamiCoordinator {
+    @Override
+    public String getManufacturer() {
+        // Actual manufacturer is Huami
+        return "Amazfit";
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("Amazfit Bip 3 Pro", Pattern.CASE_INSENSITIVE);
+    }
+
+    @Override
+    public InstallHandler findInstallHandler(final Uri uri, final Context context) {
+        final AmazfitBip3ProFWInstallHandler handler = new AmazfitBip3ProFWInstallHandler(uri, context);
+        return handler.isValid() ? handler : null;
+    }
+
+    @Override
+    public int getBondingStyle() {
+        return BONDING_STYLE_REQUIRE_KEY;
+    }
+
+    @Override
+    public boolean supportsHeartRateMeasurement(final GBDevice device) {
+        return true;
+    }
+
+    @Override
+    public boolean supportsActivityTracks() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsWeather() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsMusicInfo() {
+        return true;
+    }
+
+    @Override
+    public int getWorldClocksSlotCount() {
+        return 20; // as enforced by Zepp
+    }
+
+    @Override
+    public int getWorldClocksLabelLength() {
+        return 30; // at least
+    }
+
+    @Override
+    public boolean supportsUnicodeEmojis() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsStressMeasurement() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsSpo2(final GBDevice device) {
+        return true;
+    }
+
+    @Override
+    public boolean supportsPai() {
+        return true;
+    }
+
+    @Override
+    public DeviceSpecificSettings getDeviceSpecificSettings(final GBDevice device) {
+        final DeviceSpecificSettings deviceSpecificSettings = new DeviceSpecificSettings();
+
+        final List<Integer> generic = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.GENERIC);
+        generic.add(R.xml.devicesettings_wearlocation);
+        generic.add(R.xml.devicesettings_device_actions);
+        final List<Integer> dateTime = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DATE_TIME);
+        dateTime.add(R.xml.devicesettings_timeformat);
+        dateTime.add(R.xml.devicesettings_dateformat);
+        dateTime.add(R.xml.devicesettings_world_clocks);
+        final List<Integer> display = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DISPLAY);
+        display.add(R.xml.devicesettings_amazfitbip3pro);
+        display.add(R.xml.devicesettings_liftwrist_display_sensitivity);
+        final List<Integer> health = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.HEALTH);
+        health.add(R.xml.devicesettings_heartrate_sleep_alert_activity_stress);
+        health.add(R.xml.devicesettings_inactivity_dnd);
+        health.add(R.xml.devicesettings_goal_notification);
+        final List<Integer> notifications = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.NOTIFICATIONS);
+        notifications.add(R.xml.devicesettings_vibrationpatterns);
+        notifications.add(R.xml.devicesettings_transliteration);
+        final List<Integer> calendar = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.CALENDAR);
+        calendar.add(R.xml.devicesettings_sync_calendar);
+        calendar.add(R.xml.devicesettings_reserve_reminders_calendar);
+        final List<Integer> connection = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.CONNECTION);
+        connection.add(R.xml.devicesettings_expose_hr_thirdparty);
+        connection.add(R.xml.devicesettings_bt_connected_advertisement);
+        connection.add(R.xml.devicesettings_high_mtu);
+        connection.add(R.xml.devicesettings_overwrite_settings_on_connection);
+        final List<Integer> developer = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DEVELOPER);
+        developer.add(R.xml.devicesettings_huami2021_fetch_operation_time_unit);
+
+        return deviceSpecificSettings;
+    }
+
+    @Override
+    public String[] getSupportedLanguageSettings(final GBDevice device) {
+        return new String[]{
+                "auto",
+                "cs_CZ",
+                "de_DE",
+                "el_GR",
+                "en_US",
+                "es_ES",
+                "fr_FR",
+                "id_ID",
+                "it_IT",
+                "ja_JP",
+                "ko_KO",
+                "nl_NL",
+                "pl_PL",
+                "pt_BR",
+                "ru_RU",
+                "th_TH",
+                "tr_TR",
+                "uk_UA",
+                "vi_VN",
+                "zh_CH",
+                "zh_TW",
+        };
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return AmazfitBip3ProSupport.class;
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_amazfit_bip3_pro;
+    }
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_amazfit_bip;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_amazfit_bip_disabled;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbip3pro/AmazfitBip3ProFWHelper.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbip3pro/AmazfitBip3ProFWHelper.java
new file mode 100644
index 0000000..e3e75c9
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbip3pro/AmazfitBip3ProFWHelper.java
@@ -0,0 +1,39 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitbip3pro;
+
+import android.content.Context;
+import android.net.Uri;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitbip3pro.AmazfitBip3ProFirmwareInfo;
+
+public class AmazfitBip3ProFWHelper extends HuamiFWHelper {
+    public AmazfitBip3ProFWHelper(final Uri uri, final Context context) throws IOException {
+        super(uri, context);
+    }
+
+    @Override
+    protected void determineFirmwareInfo(final byte[] wholeFirmwareBytes) {
+        firmwareInfo = new AmazfitBip3ProFirmwareInfo(wholeFirmwareBytes);
+        if (!firmwareInfo.isHeaderValid()) {
+            throw new IllegalArgumentException("Not an Amazfit Bip 3 Pro firmware");
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbip3pro/AmazfitBip3ProFWInstallHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbip3pro/AmazfitBip3ProFWInstallHandler.java
new file mode 100644
index 0000000..176c003
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbip3pro/AmazfitBip3ProFWInstallHandler.java
@@ -0,0 +1,49 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitbip3pro;
+
+import android.content.Context;
+import android.net.Uri;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.AbstractMiBandFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.AbstractMiBandFWInstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+
+class AmazfitBip3ProFWInstallHandler extends AbstractMiBandFWInstallHandler {
+    AmazfitBip3ProFWInstallHandler(final Uri uri, final Context context) {
+        super(uri, context);
+    }
+
+    @Override
+    protected String getFwUpgradeNotice() {
+        return mContext.getString(R.string.fw_upgrade_notice_amazfitbip3pro, helper.getHumanFirmwareVersion());
+    }
+
+    @Override
+    protected AbstractMiBandFWHelper createHelper(final Uri uri, final Context context) throws IOException {
+        return new AmazfitBip3ProFWHelper(uri, context);
+    }
+
+    @Override
+    protected boolean isSupportedDeviceType(final GBDevice device) {
+        return device.getType() == DeviceType.AMAZFITBIP3PRO;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbip5/AmazfitBip5Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbip5/AmazfitBip5Coordinator.java
new file mode 100644
index 0000000..c596258
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbip5/AmazfitBip5Coordinator.java
@@ -0,0 +1,81 @@
+/*  Copyright (C) 2023-2024 Daniel Dakhno, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitbip5;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.zeppos.ZeppOsCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class AmazfitBip5Coordinator extends ZeppOsCoordinator {
+    @Override
+    public String getDeviceBluetoothName() {
+        return HuamiConst.AMAZFIT_BIP5_NAME;
+    }
+
+    @Override
+    public Set<Integer> getDeviceSources() {
+        return new HashSet<>(Arrays.asList(
+                8454400, // chinese mainland version
+                8454401
+        ));
+    }
+
+    @Override
+    public boolean supportsContinuousFindDevice() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsGpxUploads() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsControlCenter() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsToDoList() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsBluetoothPhoneCalls(final GBDevice device) {
+        return true;
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_amazfit_bip5;
+    }
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_amazfit_bip;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_amazfit_bip_disabled;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbip5unity/AmazfitBip5UnityCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbip5unity/AmazfitBip5UnityCoordinator.java
new file mode 100644
index 0000000..796eff0
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbip5unity/AmazfitBip5UnityCoordinator.java
@@ -0,0 +1,83 @@
+/*  Copyright (C) 2023-2024 Daniel Dakhno, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitbip5unity;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.zeppos.ZeppOsCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class AmazfitBip5UnityCoordinator extends ZeppOsCoordinator {
+    @Override
+    public boolean isExperimental() {
+        // untested device
+        return true;
+    }
+
+    @Override
+    public String getDeviceBluetoothName() {
+        return HuamiConst.AMAZFIT_BIP5_UNITY_NAME;
+    }
+
+    @Override
+    public Set<Integer> getDeviceSources() {
+        return new HashSet<>(Arrays.asList(
+                8782081,
+                8782088,
+                8782089
+        ));
+    }
+
+    @Override
+    public boolean supportsContinuousFindDevice() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsControlCenter() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsToDoList() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsBluetoothPhoneCalls(final GBDevice device) {
+        return true;
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_amazfit_bip5_unity;
+    }
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_amazfit_bip;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_amazfit_bip_disabled;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbips/AmazfitBipSCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbips/AmazfitBipSCoordinator.java
new file mode 100644
index 0000000..3e31774
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbips/AmazfitBipSCoordinator.java
@@ -0,0 +1,163 @@
+/*  Copyright (C) 2020-2024 Andreas Shimokawa, Daniel Dakhno, Jos茅 Rebelo,
+    Petr Van臎k, Zhong Jianxin
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitbips;
+
+import android.content.Context;
+import android.net.Uri;
+
+import androidx.annotation.NonNull;
+
+import java.util.List;
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettings;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsScreen;
+import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitbips.AmazfitBipSSupport;
+
+public class AmazfitBipSCoordinator extends HuamiCoordinator {
+    @Override
+    public String getManufacturer() {
+        // Actual manufacturer is Huami
+        return "Amazfit";
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("Amazfit Bip S", Pattern.CASE_INSENSITIVE);
+    }
+
+    @Override
+    public InstallHandler findInstallHandler(final Uri uri, final Context context) {
+        final AmazfitBipSFWInstallHandler handler = new AmazfitBipSFWInstallHandler(uri, context);
+        return handler.isValid() ? handler : null;
+    }
+
+    @Override
+    public boolean supportsHeartRateMeasurement(final GBDevice device) {
+        return true;
+    }
+
+    @Override
+    public boolean supportsActivityTracks() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsWeather() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsMusicInfo() {
+        return true;
+    }
+
+    @Override
+    public int getWorldClocksSlotCount() {
+        return 20; // as enforced by Mi Fit
+    }
+
+    @Override
+    public int getWorldClocksLabelLength() {
+        return 30; // at least
+    }
+
+    @Override
+    public DeviceSpecificSettings getDeviceSpecificSettings(final GBDevice device) {
+        final DeviceSpecificSettings deviceSpecificSettings = new DeviceSpecificSettings();
+
+        final List<Integer> generic = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.GENERIC);
+        generic.add(R.xml.devicesettings_wearlocation);
+        generic.add(R.xml.devicesettings_device_actions);
+        final List<Integer> dateTime = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DATE_TIME);
+        dateTime.add(R.xml.devicesettings_timeformat);
+        dateTime.add(R.xml.devicesettings_world_clocks);
+        final List<Integer> display = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DISPLAY);
+        display.add(R.xml.devicesettings_amazfitbips);
+        display.add(R.xml.devicesettings_liftwrist_display);
+        final List<Integer> health = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.HEALTH);
+        health.add(R.xml.devicesettings_heartrate_sleep);
+        health.add(R.xml.devicesettings_inactivity_dnd);
+        health.add(R.xml.devicesettings_goal_notification);
+        final List<Integer> notifications = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.NOTIFICATIONS);
+        notifications.add(R.xml.devicesettings_custom_emoji_font);
+        notifications.add(R.xml.devicesettings_phone_silent_mode);
+        notifications.add(R.xml.devicesettings_transliteration);
+        final List<Integer> calendar = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.CALENDAR);
+        calendar.add(R.xml.devicesettings_sync_calendar);
+        calendar.add(R.xml.devicesettings_reserve_reminders_calendar);
+        final List<Integer> connection = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.CONNECTION);
+        connection.add(R.xml.devicesettings_expose_hr_thirdparty);
+        connection.add(R.xml.devicesettings_bt_connected_advertisement);
+        connection.add(R.xml.devicesettings_high_mtu);
+        connection.add(R.xml.devicesettings_overwrite_settings_on_connection);
+        final List<Integer> developer = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DEVELOPER);
+        developer.add(R.xml.devicesettings_huami2021_fetch_operation_time_unit);
+
+        return deviceSpecificSettings;
+    }
+
+    @Override
+    public String[] getSupportedLanguageSettings(final GBDevice device) {
+        return new String[]{
+                "auto",
+                "zh_CN",
+                "zh_TW",
+                "en_US",
+                "es_ES",
+                "de_DE",
+                "it_IT",
+                "fr_FR",
+                "pt_BR",
+                "tr_TR",
+                "cs_CZ",
+                "ru_RU",
+        };
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return AmazfitBipSSupport.class;
+    }
+
+    @Override
+    public int getBondingStyle() {
+        return BONDING_STYLE_REQUIRE_KEY;
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_amazfit_bips;
+    }
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_amazfit_bip;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_amazfit_bip_disabled;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbips/AmazfitBipSFWHelper.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbips/AmazfitBipSFWHelper.java
new file mode 100644
index 0000000..1ecf5dd
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbips/AmazfitBipSFWHelper.java
@@ -0,0 +1,40 @@
+/*  Copyright (C) 2020-2024 Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitbips;
+
+import android.content.Context;
+import android.net.Uri;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitbips.AmazfitBipSFirmwareInfo;
+
+public class AmazfitBipSFWHelper extends HuamiFWHelper {
+
+    public AmazfitBipSFWHelper(final Uri uri, final Context context) throws IOException {
+        super(uri, context);
+    }
+
+    @Override
+    protected void determineFirmwareInfo(byte[] wholeFirmwareBytes) {
+        firmwareInfo = new AmazfitBipSFirmwareInfo(wholeFirmwareBytes);
+        if (!firmwareInfo.isHeaderValid()) {
+            throw new IllegalArgumentException("Not a an Amazfit Bip S firmware");
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbips/AmazfitBipSFWInstallHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbips/AmazfitBipSFWInstallHandler.java
new file mode 100644
index 0000000..1413d55
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbips/AmazfitBipSFWInstallHandler.java
@@ -0,0 +1,49 @@
+/*  Copyright (C) 2020-2024 Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitbips;
+
+import android.content.Context;
+import android.net.Uri;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.AbstractMiBandFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.AbstractMiBandFWInstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+
+class AmazfitBipSFWInstallHandler extends AbstractMiBandFWInstallHandler {
+    AmazfitBipSFWInstallHandler(final Uri uri, final Context context) {
+        super(uri, context);
+    }
+
+    @Override
+    protected String getFwUpgradeNotice() {
+        return mContext.getString(R.string.fw_upgrade_notice_amazfitbip, helper.getHumanFirmwareVersion());
+    }
+
+    @Override
+    protected AbstractMiBandFWHelper createHelper(final Uri uri, final Context context) throws IOException {
+        return new AmazfitBipSFWHelper(uri, context);
+    }
+
+    @Override
+    protected boolean isSupportedDeviceType(final GBDevice device) {
+        return device.getType() == DeviceType.AMAZFITBIPS;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbips/AmazfitBipSLiteCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbips/AmazfitBipSLiteCoordinator.java
new file mode 100644
index 0000000..b77f479
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbips/AmazfitBipSLiteCoordinator.java
@@ -0,0 +1,53 @@
+/*  Copyright (C) 2020-2024 Andreas Shimokawa, Daniel Dakhno, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitbips;
+
+import android.content.Context;
+import android.net.Uri;
+
+import androidx.annotation.NonNull;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitbips.AmazfitBipSLiteSupport;
+
+public class AmazfitBipSLiteCoordinator extends AmazfitBipSCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("Bip S Lite", Pattern.CASE_INSENSITIVE);
+    }
+
+    @Override
+    public InstallHandler findInstallHandler(final Uri uri, final Context context) {
+        final AmazfitBipSLiteFWInstallHandler handler = new AmazfitBipSLiteFWInstallHandler(uri, context);
+        return handler.isValid() ? handler : null;
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_amazfit_bips_lite;
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return AmazfitBipSLiteSupport.class;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbips/AmazfitBipSLiteFWHelper.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbips/AmazfitBipSLiteFWHelper.java
new file mode 100644
index 0000000..23114f3
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbips/AmazfitBipSLiteFWHelper.java
@@ -0,0 +1,40 @@
+/*  Copyright (C) 2020-2024 Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitbips;
+
+import android.content.Context;
+import android.net.Uri;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitbips.AmazfitBipSLiteFirmwareInfo;
+
+public class AmazfitBipSLiteFWHelper extends HuamiFWHelper {
+
+    public AmazfitBipSLiteFWHelper(final Uri uri, final Context context) throws IOException {
+        super(uri, context);
+    }
+
+    @Override
+    protected void determineFirmwareInfo(byte[] wholeFirmwareBytes) {
+        firmwareInfo = new AmazfitBipSLiteFirmwareInfo(wholeFirmwareBytes);
+        if (!firmwareInfo.isHeaderValid()) {
+            throw new IllegalArgumentException("Not a an Amazfit Bip S Lite firmware");
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbips/AmazfitBipSLiteFWInstallHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbips/AmazfitBipSLiteFWInstallHandler.java
new file mode 100644
index 0000000..4149f34
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbips/AmazfitBipSLiteFWInstallHandler.java
@@ -0,0 +1,49 @@
+/*  Copyright (C) 2020-2024 Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitbips;
+
+import android.content.Context;
+import android.net.Uri;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.AbstractMiBandFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.AbstractMiBandFWInstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+
+class AmazfitBipSLiteFWInstallHandler extends AbstractMiBandFWInstallHandler {
+    AmazfitBipSLiteFWInstallHandler(final Uri uri, final Context context) {
+        super(uri, context);
+    }
+
+    @Override
+    protected String getFwUpgradeNotice() {
+        return mContext.getString(R.string.fw_upgrade_notice_amazfitbip, helper.getHumanFirmwareVersion());
+    }
+
+    @Override
+    protected AbstractMiBandFWHelper createHelper(final Uri uri, final Context context) throws IOException {
+        return new AmazfitBipSLiteFWHelper(uri, context);
+    }
+
+    @Override
+    protected boolean isSupportedDeviceType(final GBDevice device) {
+        return device.getType() == DeviceType.AMAZFITBIPS_LITE;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbipu/AmazfitBipUCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbipu/AmazfitBipUCoordinator.java
new file mode 100644
index 0000000..d9e8e30
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbipu/AmazfitBipUCoordinator.java
@@ -0,0 +1,192 @@
+/*  Copyright (C) 2020-2024 Andreas Shimokawa, Daniel Dakhno, Joel Beckmeyer,
+    Jos茅 Rebelo, Petr Van臎k, TinfoilSubmarine
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitbipu;
+
+import android.content.Context;
+import android.net.Uri;
+
+import androidx.annotation.NonNull;
+
+import java.util.List;
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettings;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsScreen;
+import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitbipu.AmazfitBipUSupport;
+
+public class AmazfitBipUCoordinator extends HuamiCoordinator {
+    @Override
+    public String getManufacturer() {
+        // Actual manufacturer is Huami
+        return "Amazfit";
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("Amazfit Bip U", Pattern.CASE_INSENSITIVE);
+    }
+
+    @Override
+    public InstallHandler findInstallHandler(final Uri uri, final Context context) {
+        final AmazfitBipUFWInstallHandler handler = new AmazfitBipUFWInstallHandler(uri, context);
+        return handler.isValid() ? handler : null;
+    }
+
+    @Override
+    public boolean supportsHeartRateMeasurement(final GBDevice device) {
+        return true;
+    }
+
+    @Override
+    public boolean supportsActivityTracks() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsWeather() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsMusicInfo() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsUnicodeEmojis() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsAlarmSnoozing() {
+        // All alarms snooze by default, there doesn't seem to be a flag that disables it
+        return false;
+    }
+
+    @Override
+    public int getReminderSlotCount(final GBDevice device) {
+        return 0;
+    }
+
+    @Override
+    public int getWorldClocksSlotCount() {
+        return 20; // as enforced by Mi Fit
+    }
+
+    @Override
+    public int getWorldClocksLabelLength() {
+        return 30; // at least
+    }
+
+    @Override
+    public DeviceSpecificSettings getDeviceSpecificSettings(final GBDevice device) {
+        final DeviceSpecificSettings deviceSpecificSettings = new DeviceSpecificSettings();
+
+        final List<Integer> generic = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.GENERIC);
+        generic.add(R.xml.devicesettings_wearlocation);
+        generic.add(R.xml.devicesettings_device_actions);
+        final List<Integer> dateTime = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DATE_TIME);
+        dateTime.add(R.xml.devicesettings_timeformat);
+        dateTime.add(R.xml.devicesettings_world_clocks);
+        final List<Integer> display = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DISPLAY);
+        display.add(R.xml.devicesettings_amazfitbipu);
+        display.add(R.xml.devicesettings_liftwrist_display_sensitivity);
+        final List<Integer> health = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.HEALTH);
+        health.add(R.xml.devicesettings_heartrate_sleep_alert_activity_stress);
+        health.add(R.xml.devicesettings_inactivity_dnd);
+        health.add(R.xml.devicesettings_goal_notification);
+        final List<Integer> workout = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.WORKOUT);
+        workout.add(R.xml.devicesettings_workout_start_on_phone);
+        workout.add(R.xml.devicesettings_workout_send_gps_to_band);
+        final List<Integer> notifications = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.NOTIFICATIONS);
+        notifications.add(R.xml.devicesettings_custom_emoji_font);
+        notifications.add(R.xml.devicesettings_vibrationpatterns);
+        notifications.add(R.xml.devicesettings_phone_silent_mode);
+        notifications.add(R.xml.devicesettings_transliteration);
+        final List<Integer> calendar = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.CALENDAR);
+        calendar.add(R.xml.devicesettings_sync_calendar);
+        calendar.add(R.xml.devicesettings_reserve_reminders_calendar);
+        final List<Integer> connection = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.CONNECTION);
+        connection.add(R.xml.devicesettings_expose_hr_thirdparty);
+        connection.add(R.xml.devicesettings_bt_connected_advertisement);
+        connection.add(R.xml.devicesettings_high_mtu);
+        connection.add(R.xml.devicesettings_overwrite_settings_on_connection);
+        final List<Integer> developer = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DEVELOPER);
+        developer.add(R.xml.devicesettings_huami2021_fetch_operation_time_unit);
+
+        return deviceSpecificSettings;
+    }
+
+    @Override
+    public String[] getSupportedLanguageSettings(final GBDevice device) {
+        return new String[]{
+                "auto",
+                "cs_CZ",
+                "de_DE",
+                "el_GR",
+                "en_US",
+                "es_ES",
+                "fr_FR",
+                "id_ID",
+                "it_IT",
+                "ja_JP",
+                "ko_KO",
+                "nl_NL",
+                "pl_PL",
+                "pt_BR",
+                "ru_RU",
+                "th_TH",
+                "tr_TR",
+                "uk_UA",
+                "vi_VN",
+                "zh_CH",
+                "zh_TW",
+        };
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return AmazfitBipUSupport.class;
+    }
+
+    @Override
+    public int getBondingStyle() {
+        return BONDING_STYLE_REQUIRE_KEY;
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_amazfit_bipu;
+    }
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_amazfit_bip;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_amazfit_bip_disabled;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbipu/AmazfitBipUFWHelper.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbipu/AmazfitBipUFWHelper.java
new file mode 100644
index 0000000..313308f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbipu/AmazfitBipUFWHelper.java
@@ -0,0 +1,40 @@
+/*  Copyright (C) 2020-2024 Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitbipu;
+
+import android.content.Context;
+import android.net.Uri;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitbipu.AmazfitBipUFirmwareInfo;
+
+public class AmazfitBipUFWHelper extends HuamiFWHelper {
+
+    public AmazfitBipUFWHelper(final Uri uri, final Context context) throws IOException {
+        super(uri, context);
+    }
+
+    @Override
+    protected void determineFirmwareInfo(byte[] wholeFirmwareBytes) {
+        firmwareInfo = new AmazfitBipUFirmwareInfo(wholeFirmwareBytes);
+        if (!firmwareInfo.isHeaderValid()) {
+            throw new IllegalArgumentException("Not a an Amazfit Bip U firmware");
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbipu/AmazfitBipUFWInstallHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbipu/AmazfitBipUFWInstallHandler.java
new file mode 100644
index 0000000..b7a9477
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbipu/AmazfitBipUFWInstallHandler.java
@@ -0,0 +1,49 @@
+/*  Copyright (C) 2020-2024 Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitbipu;
+
+import android.content.Context;
+import android.net.Uri;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.AbstractMiBandFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.AbstractMiBandFWInstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+
+class AmazfitBipUFWInstallHandler extends AbstractMiBandFWInstallHandler {
+    AmazfitBipUFWInstallHandler(final Uri uri, final Context context) {
+        super(uri, context);
+    }
+
+    @Override
+    protected String getFwUpgradeNotice() {
+        return mContext.getString(R.string.fw_upgrade_notice_amazfitbip, helper.getHumanFirmwareVersion());
+    }
+
+    @Override
+    protected AbstractMiBandFWHelper createHelper(final Uri uri, final Context context) throws IOException {
+        return new AmazfitBipUFWHelper(uri, context);
+    }
+
+    @Override
+    protected boolean isSupportedDeviceType(final GBDevice device) {
+        return device.getType() == DeviceType.AMAZFITBIPU;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbipupro/AmazfitBipUProCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbipupro/AmazfitBipUProCoordinator.java
new file mode 100644
index 0000000..5162529
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbipupro/AmazfitBipUProCoordinator.java
@@ -0,0 +1,183 @@
+/*  Copyright (C) 2021-2024 Andreas Shimokawa, DanialHanif, Daniel Dakhno,
+    Jos茅 Rebelo, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitbipupro;
+
+import android.content.Context;
+import android.net.Uri;
+
+import androidx.annotation.NonNull;
+
+import java.util.List;
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettings;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsScreen;
+import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitbipupro.AmazfitBipUProSupport;
+
+public class AmazfitBipUProCoordinator extends HuamiCoordinator {
+    @Override
+    public String getManufacturer() {
+        // Actual manufacturer is Huami
+        return "Amazfit";
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("Amazfit Bip U Pro", Pattern.CASE_INSENSITIVE);
+    }
+
+    @Override
+    public InstallHandler findInstallHandler(final Uri uri, final Context context) {
+        final AmazfitBipUProFWInstallHandler handler = new AmazfitBipUProFWInstallHandler(uri, context);
+        return handler.isValid() ? handler : null;
+    }
+
+    @Override
+    public boolean supportsHeartRateMeasurement(final GBDevice device) {
+        return true;
+    }
+
+    @Override
+    public boolean supportsActivityTracks() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsWeather() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsMusicInfo() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsUnicodeEmojis() {
+        return true;
+    }
+
+    @Override
+    public int getReminderSlotCount(final GBDevice device) {
+        return 0;
+    }
+
+    @Override
+    public int getWorldClocksSlotCount() {
+        return 20; // as enforced by Mi Fit
+    }
+
+    @Override
+    public int getWorldClocksLabelLength() {
+        return 30; // at least
+    }
+
+    @Override
+    public DeviceSpecificSettings getDeviceSpecificSettings(final GBDevice device) {
+        final DeviceSpecificSettings deviceSpecificSettings = new DeviceSpecificSettings();
+
+        final List<Integer> generic = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.GENERIC);
+        generic.add(R.xml.devicesettings_wearlocation);
+        generic.add(R.xml.devicesettings_device_actions);
+        final List<Integer> dateTime = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DATE_TIME);
+        dateTime.add(R.xml.devicesettings_timeformat);
+        dateTime.add(R.xml.devicesettings_world_clocks);
+        final List<Integer> display = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DISPLAY);
+        display.add(R.xml.devicesettings_amazfitbipupro);
+        display.add(R.xml.devicesettings_liftwrist_display_sensitivity);
+        final List<Integer> health = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.HEALTH);
+        health.add(R.xml.devicesettings_heartrate_sleep_alert_activity_stress);
+        health.add(R.xml.devicesettings_inactivity_dnd);
+        health.add(R.xml.devicesettings_goal_notification);
+        final List<Integer> notifications = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.NOTIFICATIONS);
+        notifications.add(R.xml.devicesettings_custom_emoji_font);
+        notifications.add(R.xml.devicesettings_vibrationpatterns);
+        notifications.add(R.xml.devicesettings_phone_silent_mode);
+        notifications.add(R.xml.devicesettings_transliteration);
+        final List<Integer> calendar = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.CALENDAR);
+        calendar.add(R.xml.devicesettings_sync_calendar);
+        calendar.add(R.xml.devicesettings_reserve_reminders_calendar);
+        final List<Integer> connection = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.CONNECTION);
+        connection.add(R.xml.devicesettings_expose_hr_thirdparty);
+        connection.add(R.xml.devicesettings_bt_connected_advertisement);
+        connection.add(R.xml.devicesettings_high_mtu);
+        connection.add(R.xml.devicesettings_overwrite_settings_on_connection);
+        final List<Integer> developer = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DEVELOPER);
+        developer.add(R.xml.devicesettings_huami2021_fetch_operation_time_unit);
+
+        return deviceSpecificSettings;
+    }
+
+    @Override
+    public String[] getSupportedLanguageSettings(final GBDevice device) {
+        return new String[]{
+                "auto",
+                "cs_CZ",
+                "de_DE",
+                "el_GR",
+                "en_US",
+                "es_ES",
+                "fr_FR",
+                "id_ID",
+                "it_IT",
+                "ja_JP",
+                "ko_KO",
+                "nl_NL",
+                "pl_PL",
+                "pt_BR",
+                "ru_RU",
+                "th_TH",
+                "tr_TR",
+                "uk_UA",
+                "vi_VN",
+                "zh_CH",
+                "zh_TW",
+        };
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return AmazfitBipUProSupport.class;
+    }
+
+    @Override
+    public int getBondingStyle() {
+        return BONDING_STYLE_REQUIRE_KEY;
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_amazfit_bipupro;
+    }
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_amazfit_bip;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_amazfit_bip_disabled;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbipupro/AmazfitBipUProFWHelper.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbipupro/AmazfitBipUProFWHelper.java
new file mode 100644
index 0000000..0eea939
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbipupro/AmazfitBipUProFWHelper.java
@@ -0,0 +1,40 @@
+/*  Copyright (C) 2021-2024 DanialHanif
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitbipupro;
+
+import android.content.Context;
+import android.net.Uri;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitbipupro.AmazfitBipUProFirmwareInfo;
+
+public class AmazfitBipUProFWHelper extends HuamiFWHelper {
+
+    public AmazfitBipUProFWHelper(final Uri uri, final Context context) throws IOException {
+        super(uri, context);
+    }
+
+    @Override
+    protected void determineFirmwareInfo(byte[] wholeFirmwareBytes) {
+        firmwareInfo = new AmazfitBipUProFirmwareInfo(wholeFirmwareBytes);
+        if (!firmwareInfo.isHeaderValid()) {
+            throw new IllegalArgumentException("Not a an Amazfit Bip U Pro firmware");
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbipupro/AmazfitBipUProFWInstallHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbipupro/AmazfitBipUProFWInstallHandler.java
new file mode 100644
index 0000000..971ccf4
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbipupro/AmazfitBipUProFWInstallHandler.java
@@ -0,0 +1,49 @@
+/*  Copyright (C) 2021-2024 DanialHanif
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitbipupro;
+
+import android.content.Context;
+import android.net.Uri;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.AbstractMiBandFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.AbstractMiBandFWInstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+
+class AmazfitBipUProFWInstallHandler extends AbstractMiBandFWInstallHandler {
+    AmazfitBipUProFWInstallHandler(final Uri uri, final Context context) {
+        super(uri, context);
+    }
+
+    @Override
+    protected String getFwUpgradeNotice() {
+        return mContext.getString(R.string.fw_upgrade_notice_amazfitbip, helper.getHumanFirmwareVersion());
+    }
+
+    @Override
+    protected AbstractMiBandFWHelper createHelper(final Uri uri, final Context context) throws IOException {
+        return new AmazfitBipUProFWHelper(uri, context);
+    }
+
+    @Override
+    protected boolean isSupportedDeviceType(final GBDevice device) {
+        return device.getType() == DeviceType.AMAZFITBIPUPRO;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitcheetahpro/AmazfitCheetahProCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitcheetahpro/AmazfitCheetahProCoordinator.java
new file mode 100644
index 0000000..dc1e706
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitcheetahpro/AmazfitCheetahProCoordinator.java
@@ -0,0 +1,85 @@
+/*  Copyright (C) 2023-2024 Daniel Dakhno, Jos茅 Rebelo, Raghd Hamzeh
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitcheetahpro;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.zeppos.ZeppOsCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class AmazfitCheetahProCoordinator extends ZeppOsCoordinator {
+    @Override
+    public String getDeviceBluetoothName() {
+        return HuamiConst.AMAZFIT_CHEETAH_PRO_NAME;
+    }
+
+    @Override
+    public Set<Integer> getDeviceSources() {
+        return new HashSet<>(Arrays.asList(
+                8126720, // chinese mainland version
+                8126721
+        ));
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_amazfit_cheetah_pro;
+    }
+
+    @Override
+    public boolean supportsContinuousFindDevice() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsControlCenter() {
+        return true;
+    }
+
+    @Override
+    public boolean mainMenuHasMoreSection() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsGpxUploads() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsToDoList() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsWifiHotspot(final GBDevice device) {
+        return true;
+    }
+
+    @Override
+    public boolean supportsFtpServer(final GBDevice device) {
+        return true;
+    }
+
+    public boolean supportsBluetoothPhoneCalls(final GBDevice device) {
+        return true;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitcheetahround/AmazfitCheetahRoundCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitcheetahround/AmazfitCheetahRoundCoordinator.java
new file mode 100644
index 0000000..34a8e86
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitcheetahround/AmazfitCheetahRoundCoordinator.java
@@ -0,0 +1,86 @@
+/*  Copyright (C) 2023-2024 Daniel Dakhno, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitcheetahround;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.zeppos.ZeppOsCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class AmazfitCheetahRoundCoordinator extends ZeppOsCoordinator {
+    @Override
+    public String getDeviceBluetoothName() {
+        return HuamiConst.AMAZFIT_CHEETAH_ROUND_NAME;
+    }
+
+    @Override
+    public Set<Integer> getDeviceSources() {
+        return new HashSet<>(Arrays.asList(
+                8192256, // chinese mainland version
+                8192257
+        ));
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_amazfit_cheetah_round;
+    }
+
+    @Override
+    public boolean supportsContinuousFindDevice() {
+        return true;
+    }
+
+    @Override
+    public boolean mainMenuHasMoreSection() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsGpxUploads() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsControlCenter() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsToDoList() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsWifiHotspot(final GBDevice device) {
+        return true;
+    }
+
+    @Override
+    public boolean supportsFtpServer(final GBDevice device) {
+        return true;
+    }
+
+    @Override
+    public boolean supportsBluetoothPhoneCalls(final GBDevice device) {
+        return true;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitcheetahsquare/AmazfitCheetahSquareCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitcheetahsquare/AmazfitCheetahSquareCoordinator.java
new file mode 100644
index 0000000..c5c676f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitcheetahsquare/AmazfitCheetahSquareCoordinator.java
@@ -0,0 +1,95 @@
+/*  Copyright (C) 2023-2024 Daniel Dakhno, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitcheetahsquare;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.zeppos.ZeppOsCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class AmazfitCheetahSquareCoordinator extends ZeppOsCoordinator {
+    @Override
+    public String getDeviceBluetoothName() {
+        return HuamiConst.AMAZFIT_CHEETAH_SQUARE_NAME;
+    }
+
+    @Override
+    public Set<Integer> getDeviceSources() {
+        return new HashSet<>(Collections.singletonList(
+                8257793
+        ));
+    }
+
+    @Override
+    public boolean supportsContinuousFindDevice() {
+        return true;
+    }
+
+    @Override
+    public boolean mainMenuHasMoreSection() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsGpxUploads() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsControlCenter() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsToDoList() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsWifiHotspot(final GBDevice device) {
+        return true;
+    }
+
+    @Override
+    public boolean supportsFtpServer(final GBDevice device) {
+        return true;
+    }
+
+    @Override
+    public boolean supportsBluetoothPhoneCalls(final GBDevice device) {
+        return true;
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_amazfit_cheetah_square;
+    }
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_amazfit_bip;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_amazfit_bip_disabled;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitcor/AmazfitCorCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitcor/AmazfitCorCoordinator.java
new file mode 100644
index 0000000..70ca827
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitcor/AmazfitCorCoordinator.java
@@ -0,0 +1,152 @@
+/*  Copyright (C) 2017-2024 Andreas Shimokawa, Daniel Dakhno, Daniele Gobbetti,
+    Jos茅 Rebelo, Matthieu Baerts, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitcor;
+
+import android.content.Context;
+import android.net.Uri;
+
+import androidx.annotation.NonNull;
+
+import java.util.List;
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettings;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsScreen;
+import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitcor.AmazfitCorSupport;
+
+public class AmazfitCorCoordinator extends HuamiCoordinator {
+    @Override
+    public String getManufacturer() {
+        // Actual manufacturer is Huami
+        return "Amazfit";
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("Amazfit Band|Amazfit Cor", Pattern.CASE_INSENSITIVE);
+    }
+
+    @Override
+    public InstallHandler findInstallHandler(final Uri uri, final Context context) {
+        final AmazfitCorFWInstallHandler handler = new AmazfitCorFWInstallHandler(uri, context);
+        return handler.isValid() ? handler : null;
+    }
+
+    @Override
+    public boolean supportsHeartRateMeasurement(final GBDevice device) {
+        return true;
+    }
+
+    @Override
+    public boolean supportsActivityTracks() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsWeather() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsMusicInfo() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsUnicodeEmojis() {
+        return true;
+    }
+
+    @Override
+    public DeviceSpecificSettings getDeviceSpecificSettings(final GBDevice device) {
+        final DeviceSpecificSettings deviceSpecificSettings = new DeviceSpecificSettings();
+
+        final List<Integer> generic = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.GENERIC);
+        generic.add(R.xml.devicesettings_wearlocation);
+        generic.add(R.xml.devicesettings_device_actions);
+        final List<Integer> dateTime = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DATE_TIME);
+        dateTime.add(R.xml.devicesettings_timeformat);
+        final List<Integer> display = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DISPLAY);
+        display.add(R.xml.devicesettings_amazfitcor);
+        display.add(R.xml.devicesettings_liftwrist_display);
+        final List<Integer> health = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.HEALTH);
+        health.add(R.xml.devicesettings_heartrate_sleep);
+        health.add(R.xml.devicesettings_inactivity_dnd);
+        health.add(R.xml.devicesettings_goal_notification);
+        final List<Integer> notifications = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.NOTIFICATIONS);
+        notifications.add(R.xml.devicesettings_custom_emoji_font);
+        notifications.add(R.xml.devicesettings_vibrationpatterns);
+        notifications.add(R.xml.devicesettings_phone_silent_mode);
+        notifications.add(R.xml.devicesettings_transliteration);
+        final List<Integer> calendar = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.CALENDAR);
+        calendar.add(R.xml.devicesettings_sync_calendar);
+        calendar.add(R.xml.devicesettings_reserve_reminders_calendar);
+        final List<Integer> connection = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.CONNECTION);
+        connection.add(R.xml.devicesettings_disconnectnotification);
+        connection.add(R.xml.devicesettings_bt_connected_advertisement);
+        connection.add(R.xml.devicesettings_overwrite_settings_on_connection);
+        final List<Integer> developer = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DEVELOPER);
+        developer.add(R.xml.devicesettings_huami2021_fetch_operation_time_unit);
+
+        return deviceSpecificSettings;
+    }
+
+    @Override
+    public String[] getSupportedLanguageSettings(final GBDevice device) {
+        return new String[]{
+                "auto",
+                "zh_CN",
+                "zh_TW",
+                "en_US",
+                "es_ES",
+                "de_DE",
+                "it_IT",
+                "fr_FR",
+                "pt_BR",
+                "tr_TR",
+                "cs_CZ",
+                "ru_RU",
+        };
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return AmazfitCorSupport.class;
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_amazfit_cor;
+    }
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_default;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_default_disabled;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitcor/AmazfitCorFWHelper.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitcor/AmazfitCorFWHelper.java
new file mode 100644
index 0000000..0f001ec
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitcor/AmazfitCorFWHelper.java
@@ -0,0 +1,40 @@
+/*  Copyright (C) 2017-2024 Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitcor;
+
+import android.content.Context;
+import android.net.Uri;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitcor.AmazfitCorFirmwareInfo;
+
+public class AmazfitCorFWHelper extends HuamiFWHelper {
+
+    public AmazfitCorFWHelper(final Uri uri, final Context context) throws IOException {
+        super(uri, context);
+    }
+
+    @Override
+    protected void determineFirmwareInfo(byte[] wholeFirmwareBytes) {
+        firmwareInfo = new AmazfitCorFirmwareInfo(wholeFirmwareBytes);
+        if (!firmwareInfo.isHeaderValid()) {
+            throw new IllegalArgumentException("Not a an Amazfit Cor firmware");
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitcor/AmazfitCorFWInstallHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitcor/AmazfitCorFWInstallHandler.java
new file mode 100644
index 0000000..1607117
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitcor/AmazfitCorFWInstallHandler.java
@@ -0,0 +1,49 @@
+/*  Copyright (C) 2017-2024 Andreas Shimokawa, Carsten Pfeiffer
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitcor;
+
+import android.content.Context;
+import android.net.Uri;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.AbstractMiBandFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.AbstractMiBandFWInstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+
+class AmazfitCorFWInstallHandler extends AbstractMiBandFWInstallHandler {
+    AmazfitCorFWInstallHandler(final Uri uri, final Context context) {
+        super(uri, context);
+    }
+
+    @Override
+    protected String getFwUpgradeNotice() {
+        return mContext.getString(R.string.fw_upgrade_notice_amazfitcor, helper.getHumanFirmwareVersion());
+    }
+
+    @Override
+    protected AbstractMiBandFWHelper createHelper(final Uri uri, final Context context) throws IOException {
+        return new AmazfitCorFWHelper(uri, context);
+    }
+
+    @Override
+    protected boolean isSupportedDeviceType(final GBDevice device) {
+        return device.getType() == DeviceType.AMAZFITCOR;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitcor2/AmazfitCor2Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitcor2/AmazfitCor2Coordinator.java
new file mode 100644
index 0000000..1e85bcc
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitcor2/AmazfitCor2Coordinator.java
@@ -0,0 +1,152 @@
+/*  Copyright (C) 2019-2024 Andreas Shimokawa, Daniel Dakhno, Jos茅 Rebelo,
+    Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitcor2;
+
+import android.content.Context;
+import android.net.Uri;
+
+import androidx.annotation.NonNull;
+
+import java.util.List;
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettings;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsScreen;
+import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitcor2.AmazfitCor2Support;
+
+public class AmazfitCor2Coordinator extends HuamiCoordinator {
+    @Override
+    public String getManufacturer() {
+        // Actual manufacturer is Huami
+        return "Amazfit";
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("Amazfit Band 2|Amazfit Cor 2", Pattern.CASE_INSENSITIVE);
+    }
+
+    @Override
+    public InstallHandler findInstallHandler(final Uri uri, final Context context) {
+        final AmazfitCor2FWInstallHandler handler = new AmazfitCor2FWInstallHandler(uri, context);
+        return handler.isValid() ? handler : null;
+    }
+
+    @Override
+    public boolean supportsHeartRateMeasurement(final GBDevice device) {
+        return true;
+    }
+
+    @Override
+    public boolean supportsActivityTracks() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsWeather() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsMusicInfo() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsUnicodeEmojis() {
+        return true;
+    }
+
+    @Override
+    public DeviceSpecificSettings getDeviceSpecificSettings(final GBDevice device) {
+        final DeviceSpecificSettings deviceSpecificSettings = new DeviceSpecificSettings();
+
+        final List<Integer> generic = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.GENERIC);
+        generic.add(R.xml.devicesettings_wearlocation);
+        generic.add(R.xml.devicesettings_device_actions);
+        final List<Integer> dateTime = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DATE_TIME);
+        dateTime.add(R.xml.devicesettings_timeformat);
+        final List<Integer> display = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DISPLAY);
+        display.add(R.xml.devicesettings_amazfitcor);
+        display.add(R.xml.devicesettings_liftwrist_display);
+        final List<Integer> health = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.HEALTH);
+        health.add(R.xml.devicesettings_heartrate_sleep);
+        health.add(R.xml.devicesettings_inactivity_dnd);
+        health.add(R.xml.devicesettings_goal_notification);
+        final List<Integer> notifications = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.NOTIFICATIONS);
+        notifications.add(R.xml.devicesettings_custom_emoji_font);
+        notifications.add(R.xml.devicesettings_vibrationpatterns);
+        notifications.add(R.xml.devicesettings_phone_silent_mode);
+        notifications.add(R.xml.devicesettings_transliteration);
+        final List<Integer> calendar = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.CALENDAR);
+        calendar.add(R.xml.devicesettings_sync_calendar);
+        calendar.add(R.xml.devicesettings_reserve_reminders_calendar);
+        final List<Integer> connection = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.CONNECTION);
+        connection.add(R.xml.devicesettings_disconnectnotification);
+        connection.add(R.xml.devicesettings_bt_connected_advertisement);
+        connection.add(R.xml.devicesettings_overwrite_settings_on_connection);
+        final List<Integer> developer = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DEVELOPER);
+        developer.add(R.xml.devicesettings_huami2021_fetch_operation_time_unit);
+
+        return deviceSpecificSettings;
+    }
+
+    @Override
+    public String[] getSupportedLanguageSettings(final GBDevice device) {
+        return new String[]{
+                "auto",
+                "zh_CN",
+                "zh_TW",
+                "en_US",
+                "es_ES",
+                "de_DE",
+                "it_IT",
+                "fr_FR",
+                "pt_BR",
+                "tr_TR",
+                "cs_CZ",
+                "ru_RU",
+        };
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return AmazfitCor2Support.class;
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_amazfit_cor2;
+    }
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_default;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_default_disabled;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitcor2/AmazfitCor2FWHelper.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitcor2/AmazfitCor2FWHelper.java
new file mode 100644
index 0000000..61c3d2b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitcor2/AmazfitCor2FWHelper.java
@@ -0,0 +1,40 @@
+/*  Copyright (C) 2019-2024 Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitcor2;
+
+import android.content.Context;
+import android.net.Uri;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitcor2.AmazfitCor2FirmwareInfo;
+
+public class AmazfitCor2FWHelper extends HuamiFWHelper {
+
+    public AmazfitCor2FWHelper(final Uri uri, final Context context) throws IOException {
+        super(uri, context);
+    }
+
+    @Override
+    protected void determineFirmwareInfo(byte[] wholeFirmwareBytes) {
+        firmwareInfo = new AmazfitCor2FirmwareInfo(wholeFirmwareBytes);
+        if (!firmwareInfo.isHeaderValid()) {
+            throw new IllegalArgumentException("Not a an Amazfit Cor 2 firmware");
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitcor2/AmazfitCor2FWInstallHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitcor2/AmazfitCor2FWInstallHandler.java
new file mode 100644
index 0000000..dce08ea
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitcor2/AmazfitCor2FWInstallHandler.java
@@ -0,0 +1,49 @@
+/*  Copyright (C) 2019-2024 Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitcor2;
+
+import android.content.Context;
+import android.net.Uri;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.AbstractMiBandFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.AbstractMiBandFWInstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+
+class AmazfitCor2FWInstallHandler extends AbstractMiBandFWInstallHandler {
+    AmazfitCor2FWInstallHandler(final Uri uri, final Context context) {
+        super(uri, context);
+    }
+
+    @Override
+    protected String getFwUpgradeNotice() {
+        return mContext.getString(R.string.fw_upgrade_notice_amazfitcor2, helper.getHumanFirmwareVersion());
+    }
+
+    @Override
+    protected AbstractMiBandFWHelper createHelper(final Uri uri, final Context context) throws IOException {
+        return new AmazfitCor2FWHelper(uri, context);
+    }
+
+    @Override
+    protected boolean isSupportedDeviceType(final GBDevice device) {
+        return device.getType() == DeviceType.AMAZFITCOR2;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitfalcon/AmazfitFalconCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitfalcon/AmazfitFalconCoordinator.java
new file mode 100644
index 0000000..09f2ec5
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitfalcon/AmazfitFalconCoordinator.java
@@ -0,0 +1,71 @@
+/*  Copyright (C) 2023-2024 Daniel Dakhno, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitfalcon;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.zeppos.ZeppOsCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class AmazfitFalconCoordinator extends ZeppOsCoordinator {
+    @Override
+    public boolean isExperimental() {
+        return true;
+    }
+
+    @Override
+    public String getDeviceBluetoothName() {
+        return HuamiConst.AMAZFIT_FALCON_NAME;
+    }
+
+    @Override
+    public Set<Integer> getDeviceSources() {
+        return new HashSet<>(Arrays.asList(
+                414, // chinese mainland version
+                415
+        ));
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_amazfit_falcon;
+    }
+
+    @Override
+    public boolean sendAgpsAsFileTransfer() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsWifiHotspot(final GBDevice device) {
+        return true;
+    }
+
+    @Override
+    public boolean supportsFtpServer(final GBDevice device) {
+        return true;
+    }
+
+    @Override
+    public boolean supportsBluetoothPhoneCalls(final GBDevice device) {
+        return false;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgtr/AmazfitGTRCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgtr/AmazfitGTRCoordinator.java
new file mode 100644
index 0000000..0c8dcb7
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgtr/AmazfitGTRCoordinator.java
@@ -0,0 +1,143 @@
+/*  Copyright (C) 2019-2024 Andreas Shimokawa, Daniel Dakhno, Jos茅 Rebelo,
+    Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitgtr;
+
+import android.content.Context;
+import android.net.Uri;
+
+import androidx.annotation.NonNull;
+
+import java.util.List;
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettings;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsScreen;
+import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitgtr.AmazfitGTRSupport;
+
+public class AmazfitGTRCoordinator extends HuamiCoordinator {
+    @Override
+    public String getManufacturer() {
+        // Actual manufacturer is Huami
+        return "Amazfit";
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("Amazfit GTR", Pattern.CASE_INSENSITIVE);
+    }
+    @Override
+    public InstallHandler findInstallHandler(final Uri uri, final Context context) {
+        final AmazfitGTRFWInstallHandler handler = new AmazfitGTRFWInstallHandler(uri, context);
+        return handler.isValid() ? handler : null;
+    }
+
+    @Override
+    public int getBondingStyle() {
+        return BONDING_STYLE_REQUIRE_KEY;
+    }
+
+    @Override
+    public boolean supportsHeartRateMeasurement(final GBDevice device) {
+        return true;
+    }
+
+    @Override
+    public boolean supportsActivityTracks() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsWeather() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsMusicInfo() {
+        return true;
+    }
+
+    @Override
+    public DeviceSpecificSettings getDeviceSpecificSettings(final GBDevice device) {
+        final DeviceSpecificSettings deviceSpecificSettings = new DeviceSpecificSettings();
+
+        final List<Integer> generic = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.GENERIC);
+        generic.add(R.xml.devicesettings_wearlocation);
+        generic.add(R.xml.devicesettings_buttonactions_with_longpress);
+        generic.add(R.xml.devicesettings_device_actions);
+        final List<Integer> dateTime = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DATE_TIME);
+        dateTime.add(R.xml.devicesettings_timeformat);
+        final List<Integer> display = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DISPLAY);
+        display.add(R.xml.devicesettings_amazfitgtsgtr);
+        display.add(R.xml.devicesettings_liftwrist_display);
+        final List<Integer> health = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.HEALTH);
+        health.add(R.xml.devicesettings_heartrate_sleep);
+        health.add(R.xml.devicesettings_inactivity_dnd);
+        health.add(R.xml.devicesettings_goal_notification);
+        final List<Integer> notifications = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.NOTIFICATIONS);
+        notifications.add(R.xml.devicesettings_vibrationpatterns);
+        notifications.add(R.xml.devicesettings_phone_silent_mode);
+        notifications.add(R.xml.devicesettings_transliteration);
+        final List<Integer> calendar = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.CALENDAR);
+        calendar.add(R.xml.devicesettings_sync_calendar);
+        calendar.add(R.xml.devicesettings_reserve_reminders_calendar);
+        final List<Integer> connection = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.CONNECTION);
+        connection.add(R.xml.devicesettings_disconnectnotification);
+        connection.add(R.xml.devicesettings_expose_hr_thirdparty);
+        connection.add(R.xml.devicesettings_bt_connected_advertisement);
+        connection.add(R.xml.devicesettings_high_mtu);
+        connection.add(R.xml.devicesettings_overwrite_settings_on_connection);
+        final List<Integer> developer = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DEVELOPER);
+        developer.add(R.xml.devicesettings_huami2021_fetch_operation_time_unit);
+
+        return deviceSpecificSettings;
+    }
+
+    @Override
+    public String[] getSupportedLanguageSettings(final GBDevice device) {
+        return new String[]{
+                "auto",
+                "zh_CN",
+                "zh_TW",
+                "en_US",
+                "es_ES",
+                "de_DE",
+                "it_IT",
+                "fr_FR",
+                "pt_BR",
+                "tr_TR",
+                "cs_CZ",
+                "ru_RU",
+        };
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return AmazfitGTRSupport.class;
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_amazfit_gtr;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgtr/AmazfitGTRFWHelper.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgtr/AmazfitGTRFWHelper.java
new file mode 100644
index 0000000..45be2c9
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgtr/AmazfitGTRFWHelper.java
@@ -0,0 +1,40 @@
+/*  Copyright (C) 2019-2024 Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitgtr;
+
+import android.content.Context;
+import android.net.Uri;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitgtr.AmazfitGTRFirmwareInfo;
+
+public class AmazfitGTRFWHelper extends HuamiFWHelper {
+
+    public AmazfitGTRFWHelper(final Uri uri, final Context context) throws IOException {
+        super(uri, context);
+    }
+
+    @Override
+    protected void determineFirmwareInfo(byte[] wholeFirmwareBytes) {
+        firmwareInfo = new AmazfitGTRFirmwareInfo(wholeFirmwareBytes);
+        if (!firmwareInfo.isHeaderValid()) {
+            throw new IllegalArgumentException("Not a an Amazfit GTR firmware");
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgtr/AmazfitGTRFWInstallHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgtr/AmazfitGTRFWInstallHandler.java
new file mode 100644
index 0000000..e3086aa
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgtr/AmazfitGTRFWInstallHandler.java
@@ -0,0 +1,49 @@
+/*  Copyright (C) 2019-2024 Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitgtr;
+
+import android.content.Context;
+import android.net.Uri;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.AbstractMiBandFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.AbstractMiBandFWInstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+
+class AmazfitGTRFWInstallHandler extends AbstractMiBandFWInstallHandler {
+    AmazfitGTRFWInstallHandler(final Uri uri, final Context context) {
+        super(uri, context);
+    }
+
+    @Override
+    protected String getFwUpgradeNotice() {
+        return mContext.getString(R.string.fw_upgrade_notice_amazfitgtr, helper.getHumanFirmwareVersion());
+    }
+
+    @Override
+    protected AbstractMiBandFWHelper createHelper(final Uri uri, final Context context) throws IOException {
+        return new AmazfitGTRFWHelper(uri, context);
+    }
+
+    @Override
+    protected boolean isSupportedDeviceType(final GBDevice device) {
+        return device.getType() == DeviceType.AMAZFITGTR;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgtr/AmazfitGTRLiteCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgtr/AmazfitGTRLiteCoordinator.java
new file mode 100644
index 0000000..5f698ef
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgtr/AmazfitGTRLiteCoordinator.java
@@ -0,0 +1,136 @@
+/*  Copyright (C) 2020-2024 Andreas Shimokawa, Daniel Dakhno, Jos茅 Rebelo,
+    Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitgtr;
+
+import android.content.Context;
+import android.net.Uri;
+
+import androidx.annotation.NonNull;
+
+import java.util.List;
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettings;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsScreen;
+import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitgtr.AmazfitGTRLiteSupport;
+
+public class AmazfitGTRLiteCoordinator extends HuamiCoordinator {
+    @Override
+    public String getManufacturer() {
+        // Actual manufacturer is Huami
+        return "Amazfit";
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("Amazfit GTR L", Pattern.CASE_INSENSITIVE);
+    }
+
+    @Override
+    public InstallHandler findInstallHandler(final Uri uri, final Context context) {
+        final AmazfitGTRLiteFWInstallHandler handler = new AmazfitGTRLiteFWInstallHandler(uri, context);
+        return handler.isValid() ? handler : null;
+    }
+
+    @Override
+    public int getBondingStyle() {
+        return BONDING_STYLE_REQUIRE_KEY;
+    }
+
+    @Override
+    public boolean supportsHeartRateMeasurement(final GBDevice device) {
+        return true;
+    }
+
+    @Override
+    public boolean supportsWeather() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsMusicInfo() {
+        return true;
+    }
+
+    @Override
+    public DeviceSpecificSettings getDeviceSpecificSettings(final GBDevice device) {
+        final DeviceSpecificSettings deviceSpecificSettings = new DeviceSpecificSettings();
+
+        final List<Integer> generic = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.GENERIC);
+        generic.add(R.xml.devicesettings_wearlocation);
+        final List<Integer> dateTime = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DATE_TIME);
+        dateTime.add(R.xml.devicesettings_timeformat);
+        final List<Integer> display = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DISPLAY);
+        display.add(R.xml.devicesettings_amazfitgtsgtr);
+        display.add(R.xml.devicesettings_liftwrist_display);
+        final List<Integer> health = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.HEALTH);
+        health.add(R.xml.devicesettings_heartrate_sleep);
+        health.add(R.xml.devicesettings_inactivity_dnd);
+        health.add(R.xml.devicesettings_goal_notification);
+        final List<Integer> notifications = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.NOTIFICATIONS);
+        notifications.add(R.xml.devicesettings_vibrationpatterns);
+        notifications.add(R.xml.devicesettings_phone_silent_mode);
+        notifications.add(R.xml.devicesettings_transliteration);
+        final List<Integer> calendar = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.CALENDAR);
+        calendar.add(R.xml.devicesettings_sync_calendar);
+        calendar.add(R.xml.devicesettings_reserve_reminders_calendar);
+        final List<Integer> connection = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.CONNECTION);
+        connection.add(R.xml.devicesettings_disconnectnotification);
+        connection.add(R.xml.devicesettings_expose_hr_thirdparty);
+        connection.add(R.xml.devicesettings_bt_connected_advertisement);
+        connection.add(R.xml.devicesettings_overwrite_settings_on_connection);
+        final List<Integer> developer = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DEVELOPER);
+        developer.add(R.xml.devicesettings_huami2021_fetch_operation_time_unit);
+
+        return deviceSpecificSettings;
+    }
+
+    @Override
+    public String[] getSupportedLanguageSettings(final GBDevice device) {
+        return new String[]{
+                "auto",
+                "zh_CN",
+                "zh_TW",
+                "en_US",
+                "es_ES",
+                "de_DE",
+                "it_IT",
+                "fr_FR",
+                "pt_BR",
+                "tr_TR",
+                "cs_CZ",
+                "ru_RU",
+        };
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return AmazfitGTRLiteSupport.class;
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_amazfit_gtr_lite;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgtr/AmazfitGTRLiteFWHelper.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgtr/AmazfitGTRLiteFWHelper.java
new file mode 100644
index 0000000..ecb63af
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgtr/AmazfitGTRLiteFWHelper.java
@@ -0,0 +1,40 @@
+/*  Copyright (C) 2020-2024 Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitgtr;
+
+import android.content.Context;
+import android.net.Uri;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitgtr.AmazfitGTRLiteFirmwareInfo;
+
+public class AmazfitGTRLiteFWHelper extends HuamiFWHelper {
+
+    public AmazfitGTRLiteFWHelper(final Uri uri, final Context context) throws IOException {
+        super(uri, context);
+    }
+
+    @Override
+    protected void determineFirmwareInfo(byte[] wholeFirmwareBytes) {
+        firmwareInfo = new AmazfitGTRLiteFirmwareInfo(wholeFirmwareBytes);
+        if (!firmwareInfo.isHeaderValid()) {
+            throw new IllegalArgumentException("Not a an Amazfit GTR Lite Firmware");
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgtr/AmazfitGTRLiteFWInstallHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgtr/AmazfitGTRLiteFWInstallHandler.java
new file mode 100644
index 0000000..46a136c
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgtr/AmazfitGTRLiteFWInstallHandler.java
@@ -0,0 +1,49 @@
+/*  Copyright (C) 2020-2024 Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitgtr;
+
+import android.content.Context;
+import android.net.Uri;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.AbstractMiBandFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.AbstractMiBandFWInstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+
+class AmazfitGTRLiteFWInstallHandler extends AbstractMiBandFWInstallHandler {
+    AmazfitGTRLiteFWInstallHandler(final Uri uri, final Context context) {
+        super(uri, context);
+    }
+
+    @Override
+    protected String getFwUpgradeNotice() {
+        return mContext.getString(R.string.fw_upgrade_notice_amazfitgtr, helper.getHumanFirmwareVersion());
+    }
+
+    @Override
+    protected AbstractMiBandFWHelper createHelper(final Uri uri, final Context context) throws IOException {
+        return new AmazfitGTRLiteFWHelper(uri, context);
+    }
+
+    @Override
+    protected boolean isSupportedDeviceType(final GBDevice device) {
+        return device.getType() == DeviceType.AMAZFITGTR_LITE;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgtr2/AmazfitGTR2Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgtr2/AmazfitGTR2Coordinator.java
new file mode 100644
index 0000000..00c1399
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgtr2/AmazfitGTR2Coordinator.java
@@ -0,0 +1,153 @@
+/*  Copyright (C) 2020-2024 Andreas Shimokawa, Daniel Dakhno, Jos茅 Rebelo,
+    pangwalla, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitgtr2;
+
+import android.content.Context;
+import android.net.Uri;
+
+import androidx.annotation.NonNull;
+
+import java.util.List;
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettings;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsScreen;
+import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitgtr2.AmazfitGTR2Support;
+
+public class AmazfitGTR2Coordinator extends HuamiCoordinator {
+    @Override
+    public String getManufacturer() {
+        // Actual manufacturer is Huami
+        return "Amazfit";
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("Amazfit GTR 2", Pattern.CASE_INSENSITIVE);
+    }
+
+    @Override
+    public InstallHandler findInstallHandler(final Uri uri, final Context context) {
+        final AmazfitGTR2FWInstallHandler handler = new AmazfitGTR2FWInstallHandler(uri, context);
+        return handler.isValid() ? handler : null;
+    }
+
+    @Override
+    public int getBondingStyle() {
+        return BONDING_STYLE_REQUIRE_KEY;
+    }
+
+    @Override
+    public boolean supportsHeartRateMeasurement(final GBDevice device) {
+        return true;
+    }
+
+    @Override
+    public boolean supportsActivityTracks() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsWeather() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsMusicInfo() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsUnicodeEmojis() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsPai() {
+        return true;
+    }
+
+    @Override
+    public DeviceSpecificSettings getDeviceSpecificSettings(final GBDevice device) {
+        final DeviceSpecificSettings deviceSpecificSettings = new DeviceSpecificSettings();
+
+        final List<Integer> generic = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.GENERIC);
+        generic.add(R.xml.devicesettings_wearlocation);
+        generic.add(R.xml.devicesettings_device_actions);
+        final List<Integer> dateTime = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DATE_TIME);
+        dateTime.add(R.xml.devicesettings_timeformat);
+        final List<Integer> display = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DISPLAY);
+        display.add(R.xml.devicesettings_amazfitgtsgtr2);
+        display.add(R.xml.devicesettings_liftwrist_display);
+        final List<Integer> health = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.HEALTH);
+        health.add(R.xml.devicesettings_heartrate_sleep);
+        health.add(R.xml.devicesettings_inactivity_dnd);
+        health.add(R.xml.devicesettings_goal_notification);
+        final List<Integer> notifications = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.NOTIFICATIONS);
+        notifications.add(R.xml.devicesettings_vibrationpatterns);
+        notifications.add(R.xml.devicesettings_phone_silent_mode);
+        notifications.add(R.xml.devicesettings_transliteration);
+        final List<Integer> calendar = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.CALENDAR);
+        calendar.add(R.xml.devicesettings_sync_calendar);
+        calendar.add(R.xml.devicesettings_reserve_reminders_calendar);
+        final List<Integer> connection = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.CONNECTION);
+        connection.add(R.xml.devicesettings_disconnectnotification);
+        connection.add(R.xml.devicesettings_expose_hr_thirdparty);
+        connection.add(R.xml.devicesettings_bt_connected_advertisement);
+        connection.add(R.xml.devicesettings_high_mtu);
+        connection.add(R.xml.devicesettings_overwrite_settings_on_connection);
+        final List<Integer> developer = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DEVELOPER);
+        developer.add(R.xml.devicesettings_huami2021_fetch_operation_time_unit);
+
+        return deviceSpecificSettings;
+    }
+
+    @Override
+    public String[] getSupportedLanguageSettings(final GBDevice device) {
+        return new String[]{
+                "auto",
+                "zh_CN",
+                "zh_TW",
+                "en_US",
+                "es_ES",
+                "de_DE",
+                "it_IT",
+                "fr_FR",
+                "pt_BR",
+                "tr_TR",
+                "cs_CZ",
+                "ru_RU",
+        };
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return AmazfitGTR2Support.class;
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_amazfit_gtr2;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgtr2/AmazfitGTR2FWHelper.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgtr2/AmazfitGTR2FWHelper.java
new file mode 100644
index 0000000..1f7c255
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgtr2/AmazfitGTR2FWHelper.java
@@ -0,0 +1,41 @@
+/*  Copyright (C) 2020-2024 Andreas Shimokawa, pangwalla
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitgtr2;
+
+import android.content.Context;
+import android.net.Uri;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitgtr.AmazfitGTRFirmwareInfo;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitgtr2.AmazfitGTR2FirmwareInfo;
+
+public class AmazfitGTR2FWHelper extends HuamiFWHelper {
+
+    public AmazfitGTR2FWHelper(final Uri uri, final Context context) throws IOException {
+        super(uri, context);
+    }
+
+    @Override
+    protected void determineFirmwareInfo(byte[] wholeFirmwareBytes) {
+        firmwareInfo = new AmazfitGTR2FirmwareInfo(wholeFirmwareBytes);
+        if (!firmwareInfo.isHeaderValid()) {
+            throw new IllegalArgumentException("Not an Amazfit GTR 2 firmware");
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgtr2/AmazfitGTR2FWInstallHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgtr2/AmazfitGTR2FWInstallHandler.java
new file mode 100644
index 0000000..09c9825
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgtr2/AmazfitGTR2FWInstallHandler.java
@@ -0,0 +1,49 @@
+/*  Copyright (C) 2020-2024 pangwalla
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitgtr2;
+
+import android.content.Context;
+import android.net.Uri;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.AbstractMiBandFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.AbstractMiBandFWInstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+
+class AmazfitGTR2FWInstallHandler extends AbstractMiBandFWInstallHandler {
+    AmazfitGTR2FWInstallHandler(final Uri uri, final Context context) {
+        super(uri, context);
+    }
+
+    @Override
+    protected String getFwUpgradeNotice() {
+        return mContext.getString(R.string.fw_upgrade_notice_amazfitgtr, helper.getHumanFirmwareVersion());
+    }
+
+    @Override
+    protected AbstractMiBandFWHelper createHelper(final Uri uri, final Context context) throws IOException {
+        return new AmazfitGTR2FWHelper(uri, context);
+    }
+
+    @Override
+    protected boolean isSupportedDeviceType(final GBDevice device) {
+        return device.getType() == DeviceType.AMAZFITGTR2;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgtr2/AmazfitGTR2eCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgtr2/AmazfitGTR2eCoordinator.java
new file mode 100644
index 0000000..a573093
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgtr2/AmazfitGTR2eCoordinator.java
@@ -0,0 +1,148 @@
+/*  Copyright (C) 2021-2024 Andreas Shimokawa, Daniel Dakhno, Jos茅 Rebelo,
+    Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitgtr2;
+
+import android.content.Context;
+import android.net.Uri;
+
+import androidx.annotation.NonNull;
+
+import java.util.List;
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettings;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsScreen;
+import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitgtr2.AmazfitGTR2eSupport;
+
+public class AmazfitGTR2eCoordinator extends HuamiCoordinator {
+    @Override
+    public String getManufacturer() {
+        // Actual manufacturer is Huami
+        return "Amazfit";
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("Amazfit GTR 2e", Pattern.CASE_INSENSITIVE);
+    }
+
+    @Override
+    public InstallHandler findInstallHandler(final Uri uri, final Context context) {
+        final AmazfitGTR2eFWInstallHandler handler = new AmazfitGTR2eFWInstallHandler(uri, context);
+        return handler.isValid() ? handler : null;
+    }
+
+    @Override
+    public int getBondingStyle() {
+        return BONDING_STYLE_REQUIRE_KEY;
+    }
+
+    @Override
+    public boolean supportsHeartRateMeasurement(final GBDevice device) {
+        return true;
+    }
+
+    @Override
+    public boolean supportsActivityTracks() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsWeather() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsMusicInfo() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsUnicodeEmojis() {
+        return true;
+    }
+
+    @Override
+    public DeviceSpecificSettings getDeviceSpecificSettings(final GBDevice device) {
+        final DeviceSpecificSettings deviceSpecificSettings = new DeviceSpecificSettings();
+
+        final List<Integer> generic = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.GENERIC);
+        generic.add(R.xml.devicesettings_wearlocation);
+        generic.add(R.xml.devicesettings_device_actions);
+        final List<Integer> dateTime = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DATE_TIME);
+        dateTime.add(R.xml.devicesettings_timeformat);
+        final List<Integer> display = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DISPLAY);
+        display.add(R.xml.devicesettings_amazfitgtsgtr2);
+        display.add(R.xml.devicesettings_liftwrist_display);
+        final List<Integer> health = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.HEALTH);
+        health.add(R.xml.devicesettings_heartrate_sleep);
+        health.add(R.xml.devicesettings_inactivity_dnd);
+        health.add(R.xml.devicesettings_goal_notification);
+        final List<Integer> notifications = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.NOTIFICATIONS);
+        notifications.add(R.xml.devicesettings_vibrationpatterns);
+        notifications.add(R.xml.devicesettings_phone_silent_mode);
+        notifications.add(R.xml.devicesettings_transliteration);
+        final List<Integer> calendar = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.CALENDAR);
+        calendar.add(R.xml.devicesettings_sync_calendar);
+        calendar.add(R.xml.devicesettings_reserve_reminders_calendar);
+        final List<Integer> connection = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.CONNECTION);
+        connection.add(R.xml.devicesettings_disconnectnotification);
+        connection.add(R.xml.devicesettings_expose_hr_thirdparty);
+        connection.add(R.xml.devicesettings_bt_connected_advertisement);
+        connection.add(R.xml.devicesettings_high_mtu);
+        connection.add(R.xml.devicesettings_overwrite_settings_on_connection);
+        final List<Integer> developer = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DEVELOPER);
+        developer.add(R.xml.devicesettings_huami2021_fetch_operation_time_unit);
+
+        return deviceSpecificSettings;
+    }
+
+    @Override
+    public String[] getSupportedLanguageSettings(final GBDevice device) {
+        return new String[]{
+                "auto",
+                "zh_CN",
+                "zh_TW",
+                "en_US",
+                "es_ES",
+                "de_DE",
+                "it_IT",
+                "fr_FR",
+                "pt_BR",
+                "tr_TR",
+                "cs_CZ",
+                "ru_RU",
+        };
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return AmazfitGTR2eSupport.class;
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_amazfit_gtr2e;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgtr2/AmazfitGTR2eFWHelper.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgtr2/AmazfitGTR2eFWHelper.java
new file mode 100644
index 0000000..92700fe
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgtr2/AmazfitGTR2eFWHelper.java
@@ -0,0 +1,40 @@
+/*  Copyright (C) 2021-2024 Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitgtr2;
+
+import android.content.Context;
+import android.net.Uri;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitgtr2.AmazfitGTR2eFirmwareInfo;
+
+public class AmazfitGTR2eFWHelper extends HuamiFWHelper {
+
+    public AmazfitGTR2eFWHelper(final Uri uri, final Context context) throws IOException {
+        super(uri, context);
+    }
+
+    @Override
+    protected void determineFirmwareInfo(byte[] wholeFirmwareBytes) {
+        firmwareInfo = new AmazfitGTR2eFirmwareInfo(wholeFirmwareBytes);
+        if (!firmwareInfo.isHeaderValid()) {
+            throw new IllegalArgumentException("Not an Amazfit GTR 2e firmware");
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgtr2/AmazfitGTR2eFWInstallHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgtr2/AmazfitGTR2eFWInstallHandler.java
new file mode 100644
index 0000000..31e9d64
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgtr2/AmazfitGTR2eFWInstallHandler.java
@@ -0,0 +1,49 @@
+/*  Copyright (C) 2021-2024 Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitgtr2;
+
+import android.content.Context;
+import android.net.Uri;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.AbstractMiBandFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.AbstractMiBandFWInstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+
+class AmazfitGTR2eFWInstallHandler extends AbstractMiBandFWInstallHandler {
+    AmazfitGTR2eFWInstallHandler(final Uri uri, final Context context) {
+        super(uri, context);
+    }
+
+    @Override
+    protected String getFwUpgradeNotice() {
+        return mContext.getString(R.string.fw_upgrade_notice_amazfitgtr, helper.getHumanFirmwareVersion());
+    }
+
+    @Override
+    protected AbstractMiBandFWHelper createHelper(final Uri uri, final Context context) throws IOException {
+        return new AmazfitGTR2eFWHelper(uri, context);
+    }
+
+    @Override
+    protected boolean isSupportedDeviceType(final GBDevice device) {
+        return device.getType() == DeviceType.AMAZFITGTR2E;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgtr3/AmazfitGTR3Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgtr3/AmazfitGTR3Coordinator.java
new file mode 100644
index 0000000..ae9a23c
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgtr3/AmazfitGTR3Coordinator.java
@@ -0,0 +1,56 @@
+/*  Copyright (C) 2022-2024 Daniel Dakhno, Jos茅 Rebelo, thermatk
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitgtr3;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.zeppos.ZeppOsCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class AmazfitGTR3Coordinator extends ZeppOsCoordinator {
+    @Override
+    public String getDeviceBluetoothName() {
+        return HuamiConst.AMAZFIT_GTR3_NAME;
+    }
+
+    @Override
+    public Set<Integer> getDeviceSources() {
+        return new HashSet<>(Arrays.asList(
+                226, // chinese mainland version
+                227
+        ));
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_amazfit_gtr3;
+    }
+
+    @Override
+    public boolean sendAgpsAsFileTransfer() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsBluetoothPhoneCalls(final GBDevice device) {
+        return false;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgtr3pro/AmazfitGTR3ProCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgtr3pro/AmazfitGTR3ProCoordinator.java
new file mode 100644
index 0000000..c49432b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgtr3pro/AmazfitGTR3ProCoordinator.java
@@ -0,0 +1,58 @@
+/*  Copyright (C) 2023-2024 Daniel Dakhno, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitgtr3pro;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.zeppos.ZeppOsCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class AmazfitGTR3ProCoordinator extends ZeppOsCoordinator {
+    @Override
+    public String getDeviceBluetoothName() {
+        return HuamiConst.AMAZFIT_GTR3_PRO_NAME;
+    }
+
+    @Override
+    public Set<Integer> getDeviceSources() {
+        return new HashSet<>(Arrays.asList(
+                229, // chinese mainland version
+                230,
+                242, // chinese mainland version
+                6095106 // chinese mainland version
+        ));
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_amazfit_gtr3_pro;
+    }
+
+    @Override
+    public boolean sendAgpsAsFileTransfer() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsBluetoothPhoneCalls(final GBDevice device) {
+        return true;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgtr4/AmazfitGTR4Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgtr4/AmazfitGTR4Coordinator.java
new file mode 100644
index 0000000..09a13e7
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgtr4/AmazfitGTR4Coordinator.java
@@ -0,0 +1,94 @@
+/*  Copyright (C) 2022-2024 Daniel Dakhno, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitgtr4;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.zeppos.ZeppOsCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class AmazfitGTR4Coordinator extends ZeppOsCoordinator {
+    @Override
+    public String getDeviceBluetoothName() {
+        return HuamiConst.AMAZFIT_GTR4_NAME;
+    }
+
+    @Override
+    public Set<Integer> getDeviceSources() {
+        return new HashSet<>(Arrays.asList(
+                7930112, // chinese mainland version
+                7930113,
+                7864577
+        ));
+    }
+
+    @Override
+    protected Map<Integer, String> getCrcMap() {
+        return new HashMap<Integer, String>() {{
+            // firmware
+            put(1699, "3.17.0.2");
+            put(20712, "3.18.1.1 (diff from 3.17.0.2)");
+            put(49685, "3.23.3.1 (diff from 3.21.0.1)");
+        }};
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_amazfit_gtr4;
+    }
+
+    @Override
+    public boolean supportsContinuousFindDevice() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsGpxUploads() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsControlCenter() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsToDoList() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsWifiHotspot(final GBDevice device) {
+        return true;
+    }
+
+    @Override
+    public boolean supportsFtpServer(final GBDevice device) {
+        return true;
+    }
+
+    @Override
+    public boolean supportsBluetoothPhoneCalls(final GBDevice device) {
+        return true;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgtrmini/AmazfitGTRMiniCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgtrmini/AmazfitGTRMiniCoordinator.java
new file mode 100644
index 0000000..fc439bf
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgtrmini/AmazfitGTRMiniCoordinator.java
@@ -0,0 +1,87 @@
+/*  Copyright (C) 2023-2024 Daniel Dakhno, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitgtrmini;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.zeppos.ZeppOsCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class AmazfitGTRMiniCoordinator extends ZeppOsCoordinator {
+    @Override
+    public String getDeviceBluetoothName() {
+        return HuamiConst.AMAZFIT_GTR_MINI_NAME;
+    }
+
+    @Override
+    public Set<Integer> getDeviceSources() {
+        return new HashSet<>(Arrays.asList(
+                250,
+                251
+        ));
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_amazfit_gtr_mini;
+    }
+
+    @Override
+    public boolean sendAgpsAsFileTransfer() {
+        // Even though it's a Zepp OS 2.0 device, it doesn't seem to support the AGPS service
+        return false;
+    }
+
+    @Override
+    public boolean supportsContinuousFindDevice() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsGpxUploads() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsControlCenter() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsToDoList() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsWifiHotspot(final GBDevice device) {
+        return false;
+    }
+
+    @Override
+    public boolean supportsFtpServer(final GBDevice device) {
+        return false;
+    }
+
+    @Override
+    public boolean supportsBluetoothPhoneCalls(final GBDevice device) {
+        return false;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgts/AmazfitGTSCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgts/AmazfitGTSCoordinator.java
new file mode 100644
index 0000000..165b7ff
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgts/AmazfitGTSCoordinator.java
@@ -0,0 +1,153 @@
+/*  Copyright (C) 2019-2024 Andreas Shimokawa, Daniel Dakhno, Jos茅 Rebelo,
+    Manuel Ru脽, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitgts;
+
+import android.content.Context;
+import android.net.Uri;
+
+import androidx.annotation.NonNull;
+
+import java.util.List;
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettings;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsScreen;
+import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitgts.AmazfitGTSSupport;
+
+public class AmazfitGTSCoordinator extends HuamiCoordinator {
+    @Override
+    public String getManufacturer() {
+        // Actual manufacturer is Huami
+        return "Amazfit";
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("Amazfit GTS", Pattern.CASE_INSENSITIVE);
+    }
+
+    @Override
+    public InstallHandler findInstallHandler(final Uri uri, final Context context) {
+        final AmazfitGTSFWInstallHandler handler = new AmazfitGTSFWInstallHandler(uri, context);
+        return handler.isValid() ? handler : null;
+    }
+
+    @Override
+    public int getBondingStyle() {
+        return BONDING_STYLE_REQUIRE_KEY;
+    }
+
+    @Override
+    public boolean supportsHeartRateMeasurement(final GBDevice device) {
+        return true;
+    }
+
+    @Override
+    public boolean supportsWeather() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsActivityTracks() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsMusicInfo() {
+        return true;
+    }
+
+    @Override
+    public DeviceSpecificSettings getDeviceSpecificSettings(final GBDevice device) {
+        final DeviceSpecificSettings deviceSpecificSettings = new DeviceSpecificSettings();
+
+        final List<Integer> generic = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.GENERIC);
+        generic.add(R.xml.devicesettings_wearlocation);
+        generic.add(R.xml.devicesettings_device_actions);
+        final List<Integer> dateTime = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DATE_TIME);
+        dateTime.add(R.xml.devicesettings_timeformat);
+        final List<Integer> display = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DISPLAY);
+        display.add(R.xml.devicesettings_amazfitgtsgtr);
+        display.add(R.xml.devicesettings_liftwrist_display);
+        final List<Integer> health = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.HEALTH);
+        health.add(R.xml.devicesettings_heartrate_sleep);
+        health.add(R.xml.devicesettings_inactivity_dnd);
+        health.add(R.xml.devicesettings_goal_notification);
+        final List<Integer> notifications = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.NOTIFICATIONS);
+        notifications.add(R.xml.devicesettings_vibrationpatterns);
+        notifications.add(R.xml.devicesettings_phone_silent_mode);
+        notifications.add(R.xml.devicesettings_transliteration);
+        final List<Integer> calendar = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.CALENDAR);
+        calendar.add(R.xml.devicesettings_sync_calendar);
+        calendar.add(R.xml.devicesettings_reserve_reminders_calendar);
+        final List<Integer> connection = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.CONNECTION);
+        connection.add(R.xml.devicesettings_disconnectnotification);
+        connection.add(R.xml.devicesettings_expose_hr_thirdparty);
+        connection.add(R.xml.devicesettings_bt_connected_advertisement);
+        connection.add(R.xml.devicesettings_high_mtu);
+        connection.add(R.xml.devicesettings_overwrite_settings_on_connection);
+        final List<Integer> developer = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DEVELOPER);
+        developer.add(R.xml.devicesettings_huami2021_fetch_operation_time_unit);
+
+        return deviceSpecificSettings;
+    }
+
+    @Override
+    public String[] getSupportedLanguageSettings(final GBDevice device) {
+        return new String[]{
+                "auto",
+                "zh_CN",
+                "zh_TW",
+                "en_US",
+                "es_ES",
+                "de_DE",
+                "it_IT",
+                "fr_FR",
+                "pt_BR",
+                "tr_TR",
+                "cs_CZ",
+                "ru_RU",
+        };
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return AmazfitGTSSupport.class;
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_amazfit_gts;
+    }
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_amazfit_bip;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_amazfit_bip_disabled;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgts/AmazfitGTSFWHelper.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgts/AmazfitGTSFWHelper.java
new file mode 100644
index 0000000..ad96628
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgts/AmazfitGTSFWHelper.java
@@ -0,0 +1,40 @@
+/*  Copyright (C) 2019-2024 Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitgts;
+
+import android.content.Context;
+import android.net.Uri;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitgts.AmazfitGTSFirmwareInfo;
+
+public class AmazfitGTSFWHelper extends HuamiFWHelper {
+
+    public AmazfitGTSFWHelper(final Uri uri, final Context context) throws IOException {
+        super(uri, context);
+    }
+
+    @Override
+    protected void determineFirmwareInfo(byte[] wholeFirmwareBytes) {
+        firmwareInfo = new AmazfitGTSFirmwareInfo(wholeFirmwareBytes);
+        if (!firmwareInfo.isHeaderValid()) {
+            throw new IllegalArgumentException("Not a an Amazfit GTS firmware");
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgts/AmazfitGTSFWInstallHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgts/AmazfitGTSFWInstallHandler.java
new file mode 100644
index 0000000..da7eb72
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgts/AmazfitGTSFWInstallHandler.java
@@ -0,0 +1,49 @@
+/*  Copyright (C) 2019-2024 Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitgts;
+
+import android.content.Context;
+import android.net.Uri;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.AbstractMiBandFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.AbstractMiBandFWInstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+
+class AmazfitGTSFWInstallHandler extends AbstractMiBandFWInstallHandler {
+    AmazfitGTSFWInstallHandler(final Uri uri, final Context context) {
+        super(uri, context);
+    }
+
+    @Override
+    protected String getFwUpgradeNotice() {
+        return mContext.getString(R.string.fw_upgrade_notice_amazfitgts, helper.getHumanFirmwareVersion());
+    }
+
+    @Override
+    protected AbstractMiBandFWHelper createHelper(final Uri uri, final Context context) throws IOException {
+        return new AmazfitGTSFWHelper(uri, context);
+    }
+
+    @Override
+    protected boolean isSupportedDeviceType(final GBDevice device) {
+        return device.getType() == DeviceType.AMAZFITGTS;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgts2/AmazfitGTS2Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgts2/AmazfitGTS2Coordinator.java
new file mode 100644
index 0000000..255dec9
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgts2/AmazfitGTS2Coordinator.java
@@ -0,0 +1,158 @@
+/*  Copyright (C) 2020-2024 Andreas Shimokawa, Daniel Dakhno, Jos茅 Rebelo,
+    Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitgts2;
+
+import android.content.Context;
+import android.net.Uri;
+
+import androidx.annotation.NonNull;
+
+import java.util.List;
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettings;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsScreen;
+import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitgts2.AmazfitGTS2Support;
+
+public class AmazfitGTS2Coordinator extends HuamiCoordinator {
+    @Override
+    public String getManufacturer() {
+        // Actual manufacturer is Huami
+        return "Amazfit";
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("Amazfit GTS 2", Pattern.CASE_INSENSITIVE);
+    }
+
+    @Override
+    public InstallHandler findInstallHandler(final Uri uri, final Context context) {
+        final AmazfitGTS2FWInstallHandler handler = new AmazfitGTS2FWInstallHandler(uri, context);
+        return handler.isValid() ? handler : null;
+    }
+
+    @Override
+    public int getBondingStyle() {
+        return BONDING_STYLE_REQUIRE_KEY;
+    }
+
+    @Override
+    public boolean supportsHeartRateMeasurement(final GBDevice device) {
+        return true;
+    }
+
+    @Override
+    public boolean supportsActivityTracks() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsWeather() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsMusicInfo() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsUnicodeEmojis() {
+        return true;
+    }
+
+    @Override
+    public DeviceSpecificSettings getDeviceSpecificSettings(final GBDevice device) {
+        final DeviceSpecificSettings deviceSpecificSettings = new DeviceSpecificSettings();
+
+        final List<Integer> generic = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.GENERIC);
+        generic.add(R.xml.devicesettings_wearlocation);
+        generic.add(R.xml.devicesettings_device_actions);
+        final List<Integer> dateTime = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DATE_TIME);
+        dateTime.add(R.xml.devicesettings_timeformat);
+        final List<Integer> display = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DISPLAY);
+        display.add(R.xml.devicesettings_amazfitgtsgtr2);
+        display.add(R.xml.devicesettings_liftwrist_display);
+        final List<Integer> health = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.HEALTH);
+        health.add(R.xml.devicesettings_heartrate_sleep);
+        health.add(R.xml.devicesettings_inactivity_dnd);
+        health.add(R.xml.devicesettings_goal_notification);
+        final List<Integer> notifications = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.NOTIFICATIONS);
+        notifications.add(R.xml.devicesettings_vibrationpatterns);
+        notifications.add(R.xml.devicesettings_phone_silent_mode);
+        notifications.add(R.xml.devicesettings_transliteration);
+        final List<Integer> calendar = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.CALENDAR);
+        calendar.add(R.xml.devicesettings_sync_calendar);
+        calendar.add(R.xml.devicesettings_reserve_reminders_calendar);
+        final List<Integer> connection = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.CONNECTION);
+        connection.add(R.xml.devicesettings_disconnectnotification);
+        connection.add(R.xml.devicesettings_expose_hr_thirdparty);
+        connection.add(R.xml.devicesettings_bt_connected_advertisement);
+        connection.add(R.xml.devicesettings_high_mtu);
+        connection.add(R.xml.devicesettings_overwrite_settings_on_connection);
+        final List<Integer> developer = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DEVELOPER);
+        developer.add(R.xml.devicesettings_huami2021_fetch_operation_time_unit);
+
+        return deviceSpecificSettings;
+    }
+
+    @Override
+    public String[] getSupportedLanguageSettings(final GBDevice device) {
+        return new String[]{
+                "auto",
+                "zh_CN",
+                "zh_TW",
+                "en_US",
+                "es_ES",
+                "de_DE",
+                "it_IT",
+                "fr_FR",
+                "pt_BR",
+                "tr_TR",
+                "cs_CZ",
+                "ru_RU",
+        };
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return AmazfitGTS2Support.class;
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_amazfit_gts2;
+    }
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_amazfit_bip;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_amazfit_bip_disabled;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgts2/AmazfitGTS2FWHelper.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgts2/AmazfitGTS2FWHelper.java
new file mode 100644
index 0000000..3841494
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgts2/AmazfitGTS2FWHelper.java
@@ -0,0 +1,40 @@
+/*  Copyright (C) 2020-2024 Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitgts2;
+
+import android.content.Context;
+import android.net.Uri;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitgts2.AmazfitGTS2FirmwareInfo;
+
+public class AmazfitGTS2FWHelper extends HuamiFWHelper {
+
+    public AmazfitGTS2FWHelper(final Uri uri, final Context context) throws IOException {
+        super(uri, context);
+    }
+
+    @Override
+    protected void determineFirmwareInfo(byte[] wholeFirmwareBytes) {
+        firmwareInfo = new AmazfitGTS2FirmwareInfo(wholeFirmwareBytes);
+        if (!firmwareInfo.isHeaderValid()) {
+            throw new IllegalArgumentException("Not an Amazfit GTS 2 firmware");
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgts2/AmazfitGTS2FWInstallHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgts2/AmazfitGTS2FWInstallHandler.java
new file mode 100644
index 0000000..2256051
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgts2/AmazfitGTS2FWInstallHandler.java
@@ -0,0 +1,49 @@
+/*  Copyright (C) 2020-2024 Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitgts2;
+
+import android.content.Context;
+import android.net.Uri;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.AbstractMiBandFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.AbstractMiBandFWInstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+
+class AmazfitGTS2FWInstallHandler extends AbstractMiBandFWInstallHandler {
+    AmazfitGTS2FWInstallHandler(final Uri uri, final Context context) {
+        super(uri, context);
+    }
+
+    @Override
+    protected String getFwUpgradeNotice() {
+        return mContext.getString(R.string.fw_upgrade_notice_amazfitgtr, helper.getHumanFirmwareVersion());
+    }
+
+    @Override
+    protected AbstractMiBandFWHelper createHelper(final Uri uri, final Context context) throws IOException {
+        return new AmazfitGTS2FWHelper(uri, context);
+    }
+
+    @Override
+    protected boolean isSupportedDeviceType(final GBDevice device) {
+        return device.getType() == DeviceType.AMAZFITGTS2;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgts2/AmazfitGTS2MiniCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgts2/AmazfitGTS2MiniCoordinator.java
new file mode 100644
index 0000000..a7aef13
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgts2/AmazfitGTS2MiniCoordinator.java
@@ -0,0 +1,93 @@
+/*  Copyright (C) 2021-2024 Andreas Shimokawa, Daniel Dakhno, Jos茅 Rebelo,
+    Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitgts2;
+
+import android.content.Context;
+import android.net.Uri;
+
+import androidx.annotation.NonNull;
+
+import java.util.List;
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettings;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsScreen;
+import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitgts2.AmazfitGTS2MiniSupport;
+
+public class AmazfitGTS2MiniCoordinator extends AmazfitGTS2Coordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("Amazfit GTS2 mini", Pattern.CASE_INSENSITIVE);
+    }
+
+    @Override
+    public InstallHandler findInstallHandler(final Uri uri, final Context context) {
+        final AmazfitGTS2MiniFWInstallHandler handler = new AmazfitGTS2MiniFWInstallHandler(uri, context);
+        return handler.isValid() ? handler : null;
+    }
+
+    @Override
+    public DeviceSpecificSettings getDeviceSpecificSettings(final GBDevice device) {
+        final DeviceSpecificSettings deviceSpecificSettings = new DeviceSpecificSettings();
+
+        final List<Integer> generic = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.GENERIC);
+        generic.add(R.xml.devicesettings_wearlocation);
+        generic.add(R.xml.devicesettings_device_actions);
+        final List<Integer> dateTime = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DATE_TIME);
+        dateTime.add(R.xml.devicesettings_timeformat);
+        final List<Integer> display = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DISPLAY);
+        display.add(R.xml.devicesettings_amazfitgts2mini);
+        display.add(R.xml.devicesettings_liftwrist_display);
+        final List<Integer> health = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.HEALTH);
+        health.add(R.xml.devicesettings_heartrate_sleep);
+        health.add(R.xml.devicesettings_inactivity_dnd);
+        health.add(R.xml.devicesettings_goal_notification);
+        final List<Integer> notifications = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.NOTIFICATIONS);
+        notifications.add(R.xml.devicesettings_vibrationpatterns);
+        notifications.add(R.xml.devicesettings_phone_silent_mode);
+        notifications.add(R.xml.devicesettings_transliteration);
+        final List<Integer> calendar = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.CALENDAR);
+        calendar.add(R.xml.devicesettings_sync_calendar);
+        calendar.add(R.xml.devicesettings_reserve_reminders_calendar);
+        final List<Integer> connection = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.CONNECTION);
+        connection.add(R.xml.devicesettings_disconnectnotification);
+        connection.add(R.xml.devicesettings_expose_hr_thirdparty);
+        connection.add(R.xml.devicesettings_bt_connected_advertisement);
+        connection.add(R.xml.devicesettings_high_mtu);
+        connection.add(R.xml.devicesettings_overwrite_settings_on_connection);
+        final List<Integer> developer = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DEVELOPER);
+        developer.add(R.xml.devicesettings_huami2021_fetch_operation_time_unit);
+
+        return deviceSpecificSettings;
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_amazfit_gts2_mini;
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return AmazfitGTS2MiniSupport.class;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgts2/AmazfitGTS2MiniFWHelper.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgts2/AmazfitGTS2MiniFWHelper.java
new file mode 100644
index 0000000..c39b39e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgts2/AmazfitGTS2MiniFWHelper.java
@@ -0,0 +1,40 @@
+/*  Copyright (C) 2021-2024 Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitgts2;
+
+import android.content.Context;
+import android.net.Uri;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitgts2.AmazfitGTS2MiniFirmwareInfo;
+
+public class AmazfitGTS2MiniFWHelper extends HuamiFWHelper {
+
+    public AmazfitGTS2MiniFWHelper(final Uri uri, final Context context) throws IOException {
+        super(uri, context);
+    }
+
+    @Override
+    protected void determineFirmwareInfo(byte[] wholeFirmwareBytes) {
+        firmwareInfo = new AmazfitGTS2MiniFirmwareInfo(wholeFirmwareBytes);
+        if (!firmwareInfo.isHeaderValid()) {
+            throw new IllegalArgumentException("Not an Amazfit GTS 2 Mini firmware");
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgts2/AmazfitGTS2MiniFWInstallHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgts2/AmazfitGTS2MiniFWInstallHandler.java
new file mode 100644
index 0000000..25d33dd
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgts2/AmazfitGTS2MiniFWInstallHandler.java
@@ -0,0 +1,49 @@
+/*  Copyright (C) 2021-2024 Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitgts2;
+
+import android.content.Context;
+import android.net.Uri;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.AbstractMiBandFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.AbstractMiBandFWInstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+
+class AmazfitGTS2MiniFWInstallHandler extends AbstractMiBandFWInstallHandler {
+    AmazfitGTS2MiniFWInstallHandler(final Uri uri, final Context context) {
+        super(uri, context);
+    }
+
+    @Override
+    protected String getFwUpgradeNotice() {
+        return mContext.getString(R.string.fw_upgrade_notice_amazfitgtr, helper.getHumanFirmwareVersion());
+    }
+
+    @Override
+    protected AbstractMiBandFWHelper createHelper(final Uri uri, final Context context) throws IOException {
+        return new AmazfitGTS2MiniFWHelper(uri, context);
+    }
+
+    @Override
+    protected boolean isSupportedDeviceType(final GBDevice device) {
+        return device.getType() == DeviceType.AMAZFITGTS2_MINI;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgts2/AmazfitGTS2eCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgts2/AmazfitGTS2eCoordinator.java
new file mode 100644
index 0000000..4016de5
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgts2/AmazfitGTS2eCoordinator.java
@@ -0,0 +1,158 @@
+/*  Copyright (C) 2021-2024 Andreas Shimokawa, Daniel Dakhno, Jos茅 Rebelo,
+    Petr Van臎k, pommes
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitgts2;
+
+import android.content.Context;
+import android.net.Uri;
+
+import androidx.annotation.NonNull;
+
+import java.util.List;
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettings;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsScreen;
+import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitgts2.AmazfitGTS2eSupport;
+
+public class AmazfitGTS2eCoordinator extends HuamiCoordinator {
+    @Override
+    public String getManufacturer() {
+        // Actual manufacturer is Huami
+        return "Amazfit";
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("Amazfit GTS 2e", Pattern.CASE_INSENSITIVE);
+    }
+
+    @Override
+    public InstallHandler findInstallHandler(final Uri uri, final Context context) {
+        final AmazfitGTS2eFWInstallHandler handler = new AmazfitGTS2eFWInstallHandler(uri, context);
+        return handler.isValid() ? handler : null;
+    }
+
+    @Override
+    public int getBondingStyle() {
+        return BONDING_STYLE_REQUIRE_KEY;
+    }
+
+    @Override
+    public boolean supportsHeartRateMeasurement(final GBDevice device) {
+        return true;
+    }
+
+    @Override
+    public boolean supportsActivityTracks() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsWeather() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsMusicInfo() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsUnicodeEmojis() {
+        return true;
+    }
+
+    @Override
+    public DeviceSpecificSettings getDeviceSpecificSettings(final GBDevice device) {
+        final DeviceSpecificSettings deviceSpecificSettings = new DeviceSpecificSettings();
+
+        final List<Integer> generic = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.GENERIC);
+        generic.add(R.xml.devicesettings_wearlocation);
+        generic.add(R.xml.devicesettings_device_actions);
+        final List<Integer> dateTime = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DATE_TIME);
+        dateTime.add(R.xml.devicesettings_timeformat);
+        final List<Integer> display = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DISPLAY);
+        display.add(R.xml.devicesettings_amazfitgtsgtr2);
+        display.add(R.xml.devicesettings_liftwrist_display_sensitivity);
+        final List<Integer> health = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.HEALTH);
+        health.add(R.xml.devicesettings_heartrate_sleep);
+        health.add(R.xml.devicesettings_inactivity_dnd);
+        health.add(R.xml.devicesettings_goal_notification);
+        final List<Integer> notifications = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.NOTIFICATIONS);
+        notifications.add(R.xml.devicesettings_vibrationpatterns);
+        notifications.add(R.xml.devicesettings_phone_silent_mode);
+        notifications.add(R.xml.devicesettings_transliteration);
+        final List<Integer> calendar = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.CALENDAR);
+        calendar.add(R.xml.devicesettings_sync_calendar);
+        calendar.add(R.xml.devicesettings_reserve_reminders_calendar);
+        final List<Integer> connection = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.CONNECTION);
+        connection.add(R.xml.devicesettings_disconnectnotification);
+        connection.add(R.xml.devicesettings_expose_hr_thirdparty);
+        connection.add(R.xml.devicesettings_bt_connected_advertisement);
+        connection.add(R.xml.devicesettings_high_mtu);
+        connection.add(R.xml.devicesettings_overwrite_settings_on_connection);
+        final List<Integer> developer = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DEVELOPER);
+        developer.add(R.xml.devicesettings_huami2021_fetch_operation_time_unit);
+
+        return deviceSpecificSettings;
+    }
+
+    @Override
+    public String[] getSupportedLanguageSettings(final GBDevice device) {
+        return new String[]{
+                "auto",
+                "zh_CN",
+                "zh_TW",
+                "en_US",
+                "es_ES",
+                "de_DE",
+                "it_IT",
+                "fr_FR",
+                "pt_BR",
+                "tr_TR",
+                "cs_CZ",
+                "ru_RU",
+        };
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return AmazfitGTS2eSupport.class;
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_amazfit_gts2e;
+    }
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_amazfit_bip;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_amazfit_bip_disabled;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgts2/AmazfitGTS2eFWHelper.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgts2/AmazfitGTS2eFWHelper.java
new file mode 100644
index 0000000..685ddc0
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgts2/AmazfitGTS2eFWHelper.java
@@ -0,0 +1,40 @@
+/*  Copyright (C) 2021-2024 Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitgts2;
+
+import android.content.Context;
+import android.net.Uri;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitgts2.AmazfitGTS2eFirmwareInfo;
+
+public class AmazfitGTS2eFWHelper extends HuamiFWHelper {
+
+    public AmazfitGTS2eFWHelper(final Uri uri, final Context context) throws IOException {
+        super(uri, context);
+    }
+
+    @Override
+    protected void determineFirmwareInfo(byte[] wholeFirmwareBytes) {
+        firmwareInfo = new AmazfitGTS2eFirmwareInfo(wholeFirmwareBytes);
+        if (!firmwareInfo.isHeaderValid()) {
+            throw new IllegalArgumentException("Not an Amazfit GTR 2e firmware");
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgts2/AmazfitGTS2eFWInstallHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgts2/AmazfitGTS2eFWInstallHandler.java
new file mode 100644
index 0000000..79a8e45
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgts2/AmazfitGTS2eFWInstallHandler.java
@@ -0,0 +1,49 @@
+/*  Copyright (C) 2021-2024 Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitgts2;
+
+import android.content.Context;
+import android.net.Uri;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.AbstractMiBandFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.AbstractMiBandFWInstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+
+class AmazfitGTS2eFWInstallHandler extends AbstractMiBandFWInstallHandler {
+    AmazfitGTS2eFWInstallHandler(final Uri uri, final Context context) {
+        super(uri, context);
+    }
+
+    @Override
+    protected String getFwUpgradeNotice() {
+        return mContext.getString(R.string.fw_upgrade_notice_amazfitgts, helper.getHumanFirmwareVersion());
+    }
+
+    @Override
+    protected AbstractMiBandFWHelper createHelper(final Uri uri, final Context context) throws IOException {
+        return new AmazfitGTS2eFWHelper(uri, context);
+    }
+
+    @Override
+    protected boolean isSupportedDeviceType(final GBDevice device) {
+        return device.getType() == DeviceType.AMAZFITGTS2E;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgts3/AmazfitGTS3Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgts3/AmazfitGTS3Coordinator.java
new file mode 100644
index 0000000..96196ea
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgts3/AmazfitGTS3Coordinator.java
@@ -0,0 +1,66 @@
+/*  Copyright (C) 2022-2024 Daniel Dakhno, Jos茅 Rebelo, sedy89
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitgts3;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.zeppos.ZeppOsCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class AmazfitGTS3Coordinator extends ZeppOsCoordinator {
+    @Override
+    public String getDeviceBluetoothName() {
+        return HuamiConst.AMAZFIT_GTS3_NAME;
+    }
+
+    @Override
+    public Set<Integer> getDeviceSources() {
+        return new HashSet<>(Arrays.asList(
+                224, // chinese mainland version
+                225
+        ));
+    }
+
+    @Override
+    public boolean sendAgpsAsFileTransfer() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsBluetoothPhoneCalls(final GBDevice device) {
+        return false;
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_amazfit_gts3;
+    }
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_amazfit_bip;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_amazfit_bip_disabled;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgts4/AmazfitGTS4Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgts4/AmazfitGTS4Coordinator.java
new file mode 100644
index 0000000..b7ab450
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgts4/AmazfitGTS4Coordinator.java
@@ -0,0 +1,91 @@
+/*  Copyright (C) 2022-2024 Daniel Dakhno, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitgts4;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.zeppos.ZeppOsCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class AmazfitGTS4Coordinator extends ZeppOsCoordinator {
+    @Override
+    public String getDeviceBluetoothName() {
+        return HuamiConst.AMAZFIT_GTS4_NAME;
+    }
+
+    @Override
+    public Set<Integer> getDeviceSources() {
+        return new HashSet<>(Arrays.asList(
+                7995648, // chinese mainland version
+                7995649
+        ));
+    }
+
+    @Override
+    public boolean supportsContinuousFindDevice() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsGpxUploads() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsControlCenter() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsToDoList() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsWifiHotspot(final GBDevice device) {
+        return true;
+    }
+
+    @Override
+    public boolean supportsFtpServer(final GBDevice device) {
+        return true;
+    }
+
+    @Override
+    public boolean supportsBluetoothPhoneCalls(final GBDevice device) {
+        return true;
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_amazfit_gts4;
+    }
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_amazfit_bip;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_amazfit_bip_disabled;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgts4mini/AmazfitGTS4MiniCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgts4mini/AmazfitGTS4MiniCoordinator.java
new file mode 100644
index 0000000..7a821ec
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgts4mini/AmazfitGTS4MiniCoordinator.java
@@ -0,0 +1,66 @@
+/*  Copyright (C) 2022-2024 Daniel Dakhno, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitgts4mini;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.zeppos.ZeppOsCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class AmazfitGTS4MiniCoordinator extends ZeppOsCoordinator {
+    @Override
+    public String getDeviceBluetoothName() {
+        return HuamiConst.AMAZFIT_GTS4_MINI_NAME;
+    }
+
+    @Override
+    public Set<Integer> getDeviceSources() {
+        return new HashSet<>(Arrays.asList(
+                246, // chinese mainland version
+                247
+        ));
+    }
+
+    @Override
+    public boolean sendAgpsAsFileTransfer() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsBluetoothPhoneCalls(final GBDevice device) {
+        return false;
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_amazfit_gts4_mini;
+    }
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_amazfit_bip;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_amazfit_bip_disabled;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitneo/AmazfitNeoCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitneo/AmazfitNeoCoordinator.java
new file mode 100644
index 0000000..671f68c
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitneo/AmazfitNeoCoordinator.java
@@ -0,0 +1,153 @@
+/*  Copyright (C) 2021-2024 Andreas Shimokawa, Daniel Dakhno, Jos茅 Rebelo,
+    NekoBox, Petr Van臎k, xaos
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitneo;
+
+import android.content.Context;
+import android.net.Uri;
+
+import androidx.annotation.NonNull;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettings;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsScreen;
+import nodomain.freeyourgadget.gadgetbridge.capabilities.HeartRateCapability;
+import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitneo.AmazfitNeoSupport;
+
+public class AmazfitNeoCoordinator extends HuamiCoordinator {
+    @Override
+    public String getManufacturer() {
+        // Actual manufacturer is Huami
+        return "Amazfit";
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile(HuamiConst.AMAZFIT_NEO_NAME, Pattern.CASE_INSENSITIVE);
+    }
+
+    @Override
+    public InstallHandler findInstallHandler(final Uri uri, final Context context) {
+        final AmazfitNeoFWInstallHandler handler = new AmazfitNeoFWInstallHandler(uri, context);
+        return handler.isValid() ? handler : null;
+    }
+
+    @Override
+    public boolean supportsHeartRateMeasurement(final GBDevice device) {
+        return true;
+    }
+
+    @Override
+    public boolean supportsPai(){
+        return true;
+    }
+
+    @Override
+    public boolean supportsWeather() {
+        return true;
+    }
+
+    @Override
+    public int getWorldClocksSlotCount() {
+        return 20; // max in Zepp app
+    }
+
+    @Override
+    public int getReminderSlotCount(final GBDevice device) {
+        return 0; // Neo does not support reminders
+    }
+
+    @Override
+    public DeviceSpecificSettings getDeviceSpecificSettings(final GBDevice device) {
+        final DeviceSpecificSettings deviceSpecificSettings = new DeviceSpecificSettings();
+
+        final List<Integer> generic = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.GENERIC);
+        generic.add(R.xml.devicesettings_wearlocation);
+        generic.add(R.xml.devicesettings_device_actions);
+        final List<Integer> dateTime = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DATE_TIME);
+        dateTime.add(R.xml.devicesettings_timeformat);
+        dateTime.add(R.xml.devicesettings_world_clocks);
+        final List<Integer> display = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DISPLAY);
+        display.add(R.xml.devicesettings_amazfitneo_display);
+        display.add(R.xml.devicesettings_liftwrist_display);
+        final List<Integer> health = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.HEALTH);
+        health.add(R.xml.devicesettings_heartrate_sleep_activity);
+        health.add(R.xml.devicesettings_inactivity_dnd);
+        health.add(R.xml.devicesettings_goal_notification);
+        final List<Integer> notifications = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.NOTIFICATIONS);
+        notifications.add(R.xml.devicesettings_phone_silent_mode);
+        notifications.add(R.xml.devicesettings_transliteration);
+        final List<Integer> sound = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.SOUND);
+        sound.add(R.xml.devicesettings_amazfitneo_sound);
+        sound.add(R.xml.devicesettings_hourly_chime);
+        final List<Integer> connection = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.CONNECTION);
+        connection.add(R.xml.devicesettings_expose_hr_thirdparty);
+        connection.add(R.xml.devicesettings_bt_connected_advertisement);
+        connection.add(R.xml.devicesettings_high_mtu);
+        connection.add(R.xml.devicesettings_overwrite_settings_on_connection);
+        final List<Integer> developer = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DEVELOPER);
+        developer.add(R.xml.devicesettings_huami2021_fetch_operation_time_unit);
+
+        return deviceSpecificSettings;
+    }
+
+    @Override
+    public int getBondingStyle() {
+        return BONDING_STYLE_REQUIRE_KEY;
+    }
+
+    @Override
+    public List<HeartRateCapability.MeasurementInterval> getHeartRateMeasurementIntervals() {
+        return Arrays.asList(
+                HeartRateCapability.MeasurementInterval.OFF,
+                HeartRateCapability.MeasurementInterval.MINUTES_1,
+                HeartRateCapability.MeasurementInterval.MINUTES_5,
+                HeartRateCapability.MeasurementInterval.MINUTES_10,
+                HeartRateCapability.MeasurementInterval.MINUTES_30
+        );
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return AmazfitNeoSupport.class;
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_amazfit_neo;
+    }
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_amazfit_bip;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_amazfit_bip_disabled;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitneo/AmazfitNeoFWHelper.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitneo/AmazfitNeoFWHelper.java
new file mode 100644
index 0000000..42648f8
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitneo/AmazfitNeoFWHelper.java
@@ -0,0 +1,41 @@
+/*  Copyright (C) 2021-2024 Andreas Shimokawa, xaos
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitneo;
+
+import android.content.Context;
+import android.net.Uri;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitneo.AmazfitNeoFirmwareInfo;
+
+public class AmazfitNeoFWHelper extends HuamiFWHelper {
+
+    public AmazfitNeoFWHelper(final Uri uri, final Context context) throws IOException {
+        super(uri, context);
+    }
+
+    @Override
+    protected void determineFirmwareInfo(byte[] wholeFirmwareBytes) {
+        firmwareInfo = new AmazfitNeoFirmwareInfo(wholeFirmwareBytes);
+        if (!firmwareInfo.isHeaderValid()) {
+            throw new IllegalArgumentException("Not an Amazfit Neo firmware");
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitneo/AmazfitNeoFWInstallHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitneo/AmazfitNeoFWInstallHandler.java
new file mode 100644
index 0000000..26bf607
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitneo/AmazfitNeoFWInstallHandler.java
@@ -0,0 +1,50 @@
+/*  Copyright (C) 2021-2024 Andreas Shimokawa, xaos
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitneo;
+
+import android.content.Context;
+import android.net.Uri;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.AbstractMiBandFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.AbstractMiBandFWInstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+
+class AmazfitNeoFWInstallHandler extends AbstractMiBandFWInstallHandler {
+    AmazfitNeoFWInstallHandler(final Uri uri, final Context context) {
+        super(uri, context);
+    }
+
+    @Override
+    protected String getFwUpgradeNotice() {
+        return mContext.getString(R.string.fw_upgrade_notice_amazfitneo, helper.getHumanFirmwareVersion());
+    }
+
+    @Override
+    protected AbstractMiBandFWHelper createHelper(final Uri uri, final Context context) throws IOException {
+        return new AmazfitNeoFWHelper(uri, context);
+    }
+
+    @Override
+    protected boolean isSupportedDeviceType(final GBDevice device) {
+        return device.getType() == DeviceType.AMAZFITNEO;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitpop/AmazfitPopCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitpop/AmazfitPopCoordinator.java
new file mode 100644
index 0000000..3929b4d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitpop/AmazfitPopCoordinator.java
@@ -0,0 +1,64 @@
+/*  Copyright (C) 2022-2024 Andreas Shimokawa, Daniel Dakhno, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitpop;
+
+import android.content.Context;
+import android.net.Uri;
+
+import androidx.annotation.NonNull;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitbipu.AmazfitBipUCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitpop.AmazfitPopSupport;
+
+public class AmazfitPopCoordinator extends AmazfitBipUCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("Amazfit Pop", Pattern.CASE_INSENSITIVE);
+    }
+
+    @Override
+    public InstallHandler findInstallHandler(final Uri uri, final Context context) {
+        final AmazfitPopFWInstallHandler handler = new AmazfitPopFWInstallHandler(uri, context);
+        return handler.isValid() ? handler : null;
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_amazfit_pop;
+    }
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_amazfit_bip;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_amazfit_bip_disabled;
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return AmazfitPopSupport.class;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitpop/AmazfitPopFWHelper.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitpop/AmazfitPopFWHelper.java
new file mode 100644
index 0000000..75a4603
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitpop/AmazfitPopFWHelper.java
@@ -0,0 +1,40 @@
+/*  Copyright (C) 2022-2024 Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitpop;
+
+import android.content.Context;
+import android.net.Uri;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitpoppro.AmazfitPopProFirmwareInfo;
+
+public class AmazfitPopFWHelper extends HuamiFWHelper {
+
+    public AmazfitPopFWHelper(final Uri uri, final Context context) throws IOException {
+        super(uri, context);
+    }
+
+    @Override
+    protected void determineFirmwareInfo(byte[] wholeFirmwareBytes) {
+        firmwareInfo = new AmazfitPopProFirmwareInfo(wholeFirmwareBytes);
+        if (!firmwareInfo.isHeaderValid()) {
+            throw new IllegalArgumentException("Not an Amazfit Pop Pro firmware");
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitpop/AmazfitPopFWInstallHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitpop/AmazfitPopFWInstallHandler.java
new file mode 100644
index 0000000..5602867
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitpop/AmazfitPopFWInstallHandler.java
@@ -0,0 +1,49 @@
+/*  Copyright (C) 2022-2024 Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitpop;
+
+import android.content.Context;
+import android.net.Uri;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.AbstractMiBandFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.AbstractMiBandFWInstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+
+class AmazfitPopFWInstallHandler extends AbstractMiBandFWInstallHandler {
+    AmazfitPopFWInstallHandler(final Uri uri, final Context context) {
+        super(uri, context);
+    }
+
+    @Override
+    protected String getFwUpgradeNotice() {
+        return mContext.getString(R.string.fw_upgrade_notice_amazfitbip, helper.getHumanFirmwareVersion());
+    }
+
+    @Override
+    protected AbstractMiBandFWHelper createHelper(final Uri uri, final Context context) throws IOException {
+        return new AmazfitPopFWHelper(uri, context);
+    }
+
+    @Override
+    protected boolean isSupportedDeviceType(final GBDevice device) {
+        return device.getType() == DeviceType.AMAZFITPOP;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitpoppro/AmazfitPopProCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitpoppro/AmazfitPopProCoordinator.java
new file mode 100644
index 0000000..eb82116
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitpoppro/AmazfitPopProCoordinator.java
@@ -0,0 +1,54 @@
+/*  Copyright (C) 2022-2024 Andreas Shimokawa, Daniel Dakhno, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitpoppro;
+
+import android.content.Context;
+import android.net.Uri;
+
+import androidx.annotation.NonNull;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitbipupro.AmazfitBipUProCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitpoppro.AmazfitPopProSupport;
+
+public class AmazfitPopProCoordinator extends AmazfitBipUProCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("Amazfit Pop Pro", Pattern.CASE_INSENSITIVE);
+    }
+
+    @Override
+    public InstallHandler findInstallHandler(final Uri uri, final Context context) {
+        final AmazfitPopProFWInstallHandler handler = new AmazfitPopProFWInstallHandler(uri, context);
+        return handler.isValid() ? handler : null;
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_amazfit_pop_pro;
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return AmazfitPopProSupport.class;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitpoppro/AmazfitPopProFWHelper.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitpoppro/AmazfitPopProFWHelper.java
new file mode 100644
index 0000000..553de24
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitpoppro/AmazfitPopProFWHelper.java
@@ -0,0 +1,40 @@
+/*  Copyright (C) 2022-2024 Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitpoppro;
+
+import android.content.Context;
+import android.net.Uri;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitpoppro.AmazfitPopProFirmwareInfo;
+
+public class AmazfitPopProFWHelper extends HuamiFWHelper {
+
+    public AmazfitPopProFWHelper(final Uri uri, final Context context) throws IOException {
+        super(uri, context);
+    }
+
+    @Override
+    protected void determineFirmwareInfo(byte[] wholeFirmwareBytes) {
+        firmwareInfo = new AmazfitPopProFirmwareInfo(wholeFirmwareBytes);
+        if (!firmwareInfo.isHeaderValid()) {
+            throw new IllegalArgumentException("Not an Amazfit Pop Pro firmware");
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitpoppro/AmazfitPopProFWInstallHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitpoppro/AmazfitPopProFWInstallHandler.java
new file mode 100644
index 0000000..b552e35
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitpoppro/AmazfitPopProFWInstallHandler.java
@@ -0,0 +1,49 @@
+/*  Copyright (C) 2022-2024 Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitpoppro;
+
+import android.content.Context;
+import android.net.Uri;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.AbstractMiBandFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.AbstractMiBandFWInstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+
+class AmazfitPopProFWInstallHandler extends AbstractMiBandFWInstallHandler {
+    AmazfitPopProFWInstallHandler(final Uri uri, final Context context) {
+        super(uri, context);
+    }
+
+    @Override
+    protected String getFwUpgradeNotice() {
+        return mContext.getString(R.string.fw_upgrade_notice_amazfitbip, helper.getHumanFirmwareVersion());
+    }
+
+    @Override
+    protected AbstractMiBandFWHelper createHelper(final Uri uri, final Context context) throws IOException {
+        return new AmazfitPopProFWHelper(uri, context);
+    }
+
+    @Override
+    protected boolean isSupportedDeviceType(final GBDevice device) {
+        return device.getType() == DeviceType.AMAZFITPOPPRO;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfittrex/AmazfitTRexCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfittrex/AmazfitTRexCoordinator.java
new file mode 100644
index 0000000..153d73f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfittrex/AmazfitTRexCoordinator.java
@@ -0,0 +1,143 @@
+/*  Copyright (C) 2020-2024 Andreas Shimokawa, Daniel Dakhno, Dmytro Bielik,
+    Jos茅 Rebelo, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfittrex;
+
+import android.content.Context;
+import android.net.Uri;
+
+import androidx.annotation.NonNull;
+
+import java.util.List;
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettings;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsScreen;
+import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfittrex.AmazfitTRexSupport;
+
+public class AmazfitTRexCoordinator extends HuamiCoordinator {
+    @Override
+    public String getManufacturer() {
+        // Actual manufacturer is Huami
+        return "Amazfit";
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("Amazfit T-Rex", Pattern.CASE_INSENSITIVE);
+    }
+
+    @Override
+    public InstallHandler findInstallHandler(final Uri uri, final Context context) {
+        final AmazfitTRexFWInstallHandler handler = new AmazfitTRexFWInstallHandler(uri, context);
+        return handler.isValid() ? handler : null;
+    }
+
+    @Override
+    public int getBondingStyle() {
+        return BONDING_STYLE_REQUIRE_KEY;
+    }
+
+    @Override
+    public boolean supportsHeartRateMeasurement(final GBDevice device) {
+        return true;
+    }
+
+    @Override
+    public boolean supportsActivityTracks() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsWeather() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsMusicInfo() {
+        return true;
+    }
+
+    @Override
+    public DeviceSpecificSettings getDeviceSpecificSettings(final GBDevice device) {
+        final DeviceSpecificSettings deviceSpecificSettings = new DeviceSpecificSettings();
+
+        final List<Integer> generic = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.GENERIC);
+        generic.add(R.xml.devicesettings_wearlocation);
+        generic.add(R.xml.devicesettings_device_actions);
+        final List<Integer> dateTime = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DATE_TIME);
+        dateTime.add(R.xml.devicesettings_timeformat);
+        final List<Integer> display = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DISPLAY);
+        display.add(R.xml.devicesettings_amazfittrex);
+        display.add(R.xml.devicesettings_liftwrist_display);
+        final List<Integer> health = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.HEALTH);
+        health.add(R.xml.devicesettings_heartrate_sleep);
+        health.add(R.xml.devicesettings_inactivity_dnd);
+        health.add(R.xml.devicesettings_goal_notification);
+        final List<Integer> notifications = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.NOTIFICATIONS);
+        notifications.add(R.xml.devicesettings_vibrationpatterns);
+        notifications.add(R.xml.devicesettings_phone_silent_mode);
+        notifications.add(R.xml.devicesettings_transliteration);
+        final List<Integer> calendar = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.CALENDAR);
+        calendar.add(R.xml.devicesettings_sync_calendar);
+        calendar.add(R.xml.devicesettings_reserve_reminders_calendar);
+        final List<Integer> connection = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.CONNECTION);
+        connection.add(R.xml.devicesettings_disconnectnotification);
+        connection.add(R.xml.devicesettings_expose_hr_thirdparty);
+        connection.add(R.xml.devicesettings_bt_connected_advertisement);
+        connection.add(R.xml.devicesettings_high_mtu);
+        connection.add(R.xml.devicesettings_overwrite_settings_on_connection);
+        final List<Integer> developer = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DEVELOPER);
+        developer.add(R.xml.devicesettings_huami2021_fetch_operation_time_unit);
+
+        return deviceSpecificSettings;
+    }
+
+    @Override
+    public String[] getSupportedLanguageSettings(final GBDevice device) {
+        return new String[]{
+                "auto",
+                "zh_CN",
+                "zh_TW",
+                "en_US",
+                "es_ES",
+                "de_DE",
+                "it_IT",
+                "fr_FR",
+                "pt_BR",
+                "tr_TR",
+                "cs_CZ",
+                "ru_RU",
+        };
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return AmazfitTRexSupport.class;
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_amazfit_trex;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfittrex/AmazfitTRexFWHelper.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfittrex/AmazfitTRexFWHelper.java
new file mode 100644
index 0000000..2581480
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfittrex/AmazfitTRexFWHelper.java
@@ -0,0 +1,40 @@
+/*  Copyright (C) 2020-2024 Dmytro Bielik
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfittrex;
+
+import android.content.Context;
+import android.net.Uri;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfittrex.AmazfitTRexFirmwareInfo;
+
+public class AmazfitTRexFWHelper extends HuamiFWHelper {
+
+    public AmazfitTRexFWHelper(final Uri uri, final Context context) throws IOException {
+        super(uri, context);
+    }
+
+    @Override
+    protected void determineFirmwareInfo(byte[] wholeFirmwareBytes) {
+        firmwareInfo = new AmazfitTRexFirmwareInfo(wholeFirmwareBytes);
+        if (!firmwareInfo.isHeaderValid()) {
+            throw new IllegalArgumentException("Not a an Amazfit T-Rex firmware");
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfittrex/AmazfitTRexFWInstallHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfittrex/AmazfitTRexFWInstallHandler.java
new file mode 100644
index 0000000..d5ca396
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfittrex/AmazfitTRexFWInstallHandler.java
@@ -0,0 +1,49 @@
+/*  Copyright (C) 2020-2024 Dmytro Bielik
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfittrex;
+
+import android.content.Context;
+import android.net.Uri;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.AbstractMiBandFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.AbstractMiBandFWInstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+
+class AmazfitTRexFWInstallHandler extends AbstractMiBandFWInstallHandler {
+    AmazfitTRexFWInstallHandler(final Uri uri, final Context context) {
+        super(uri, context);
+    }
+
+    @Override
+    protected String getFwUpgradeNotice() {
+        return mContext.getString(R.string.fw_upgrade_notice_amazfit_trex, helper.getHumanFirmwareVersion());
+    }
+
+    @Override
+    protected AbstractMiBandFWHelper createHelper(final Uri uri, final Context context) throws IOException {
+        return new AmazfitTRexFWHelper(uri, context);
+    }
+
+    @Override
+    protected boolean isSupportedDeviceType(final GBDevice device) {
+        return device.getType() == DeviceType.AMAZFITTREX;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfittrex2/AmazfitTRex2Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfittrex2/AmazfitTRex2Coordinator.java
new file mode 100644
index 0000000..d4826c7
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfittrex2/AmazfitTRex2Coordinator.java
@@ -0,0 +1,66 @@
+/*  Copyright (C) 2023-2024 Daniel Dakhno, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfittrex2;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.zeppos.ZeppOsCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class AmazfitTRex2Coordinator extends ZeppOsCoordinator {
+    @Override
+    public String getDeviceBluetoothName() {
+        return HuamiConst.AMAZFIT_TREX_2_NAME;
+    }
+
+    @Override
+    public Set<Integer> getDeviceSources() {
+        return new HashSet<>(Arrays.asList(
+                418, // chinese mainland version
+                419
+        ));
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_amazfit_trex_2;
+    }
+
+    @Override
+    public boolean supportsControlCenter() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsToDoList() {
+        return true;
+    }
+
+    @Override
+    public boolean sendAgpsAsFileTransfer() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsBluetoothPhoneCalls(final GBDevice device) {
+        return false;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfittrex3/AmazfitTRex3Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfittrex3/AmazfitTRex3Coordinator.java
new file mode 100644
index 0000000..38138ec
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfittrex3/AmazfitTRex3Coordinator.java
@@ -0,0 +1,92 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfittrex3;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.zeppos.ZeppOsCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class AmazfitTRex3Coordinator extends ZeppOsCoordinator {
+    @Override
+    public boolean isExperimental() {
+        // Untested device
+        return true;
+    }
+
+    @Override
+    public String getDeviceBluetoothName() {
+        return HuamiConst.AMAZFIT_TREX_3_NAME;
+    }
+
+    @Override
+    public Set<Integer> getDeviceSources() {
+        return new HashSet<>(Arrays.asList(
+                8716544,
+                8716545, // chinese mainland version
+                8716547 // chinese mainland version
+        ));
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_amazfit_trex_3;
+    }
+
+    @Override
+    public boolean supportsContinuousFindDevice() {
+        return true;
+    }
+
+    @Override
+    public boolean mainMenuHasMoreSection() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsGpxUploads() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsControlCenter() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsToDoList() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsWifiHotspot(final GBDevice device) {
+        return true;
+    }
+
+    @Override
+    public boolean supportsFtpServer(final GBDevice device) {
+        return true;
+    }
+
+    public boolean supportsBluetoothPhoneCalls(final GBDevice device) {
+        return true;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfittrexpro/AmazfitTRexProCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfittrexpro/AmazfitTRexProCoordinator.java
new file mode 100644
index 0000000..438e74a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfittrexpro/AmazfitTRexProCoordinator.java
@@ -0,0 +1,143 @@
+/*  Copyright (C) 2021-2024 Daniel Dakhno, GeekosaurusR3x, Jos茅 Rebelo,
+    Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfittrexpro;
+
+import android.content.Context;
+import android.net.Uri;
+
+import androidx.annotation.NonNull;
+
+import java.util.List;
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettings;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsScreen;
+import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfittrexpro.AmazfitTRexProSupport;
+
+public class AmazfitTRexProCoordinator extends HuamiCoordinator {
+    @Override
+    public String getManufacturer() {
+        // Actual manufacturer is Huami
+        return "Amazfit";
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("Amazfit T-Rex Pro", Pattern.CASE_INSENSITIVE);
+    }
+
+    @Override
+    public InstallHandler findInstallHandler(final Uri uri, final Context context) {
+        final AmazfitTRexProFWInstallHandler handler = new AmazfitTRexProFWInstallHandler(uri, context);
+        return handler.isValid() ? handler : null;
+    }
+
+    @Override
+    public int getBondingStyle() {
+        return BONDING_STYLE_REQUIRE_KEY;
+    }
+
+    @Override
+    public boolean supportsHeartRateMeasurement(final GBDevice device) {
+        return true;
+    }
+
+    @Override
+    public boolean supportsActivityTracks() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsWeather() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsMusicInfo() {
+        return true;
+    }
+
+    @Override
+    public DeviceSpecificSettings getDeviceSpecificSettings(final GBDevice device) {
+        final DeviceSpecificSettings deviceSpecificSettings = new DeviceSpecificSettings();
+
+        final List<Integer> generic = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.GENERIC);
+        generic.add(R.xml.devicesettings_wearlocation);
+        generic.add(R.xml.devicesettings_device_actions);
+        final List<Integer> dateTime = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DATE_TIME);
+        dateTime.add(R.xml.devicesettings_timeformat);
+        final List<Integer> display = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DISPLAY);
+        display.add(R.xml.devicesettings_amazfittrexpro);
+        display.add(R.xml.devicesettings_liftwrist_display_sensitivity);
+        final List<Integer> health = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.HEALTH);
+        health.add(R.xml.devicesettings_heartrate_sleep);
+        health.add(R.xml.devicesettings_inactivity_dnd);
+        health.add(R.xml.devicesettings_goal_notification);
+        final List<Integer> notifications = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.NOTIFICATIONS);
+        notifications.add(R.xml.devicesettings_vibrationpatterns);
+        notifications.add(R.xml.devicesettings_phone_silent_mode);
+        notifications.add(R.xml.devicesettings_transliteration);
+        final List<Integer> calendar = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.CALENDAR);
+        calendar.add(R.xml.devicesettings_sync_calendar);
+        calendar.add(R.xml.devicesettings_reserve_reminders_calendar);
+        final List<Integer> connection = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.CONNECTION);
+        connection.add(R.xml.devicesettings_disconnectnotification);
+        connection.add(R.xml.devicesettings_expose_hr_thirdparty);
+        connection.add(R.xml.devicesettings_bt_connected_advertisement);
+        connection.add(R.xml.devicesettings_high_mtu);
+        connection.add(R.xml.devicesettings_overwrite_settings_on_connection);
+        final List<Integer> developer = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DEVELOPER);
+        developer.add(R.xml.devicesettings_huami2021_fetch_operation_time_unit);
+
+        return deviceSpecificSettings;
+    }
+
+    @Override
+    public String[] getSupportedLanguageSettings(final GBDevice device) {
+        return new String[]{
+                "auto",
+                "zh_CN",
+                "zh_TW",
+                "en_US",
+                "es_ES",
+                "de_DE",
+                "it_IT",
+                "fr_FR",
+                "pt_BR",
+                "tr_TR",
+                "cs_CZ",
+                "ru_RU",
+        };
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return AmazfitTRexProSupport.class;
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_amazfit_trex_pro;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfittrexpro/AmazfitTRexProFWHelper.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfittrexpro/AmazfitTRexProFWHelper.java
new file mode 100644
index 0000000..5a861b9
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfittrexpro/AmazfitTRexProFWHelper.java
@@ -0,0 +1,40 @@
+/*  Copyright (C) 2021-2024 GeekosaurusR3x
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfittrexpro;
+
+import android.content.Context;
+import android.net.Uri;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfittrexpro.AmazfitTRexProFirmwareInfo;
+
+public class AmazfitTRexProFWHelper extends HuamiFWHelper {
+
+    public AmazfitTRexProFWHelper(final Uri uri, final Context context) throws IOException {
+        super(uri, context);
+    }
+
+    @Override
+    protected void determineFirmwareInfo(byte[] wholeFirmwareBytes) {
+        firmwareInfo = new AmazfitTRexProFirmwareInfo(wholeFirmwareBytes);
+        if (!firmwareInfo.isHeaderValid()) {
+            throw new IllegalArgumentException("Not a an Amazfit T-Rex Pro firmware");
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfittrexpro/AmazfitTRexProFWInstallHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfittrexpro/AmazfitTRexProFWInstallHandler.java
new file mode 100644
index 0000000..1ec2147
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfittrexpro/AmazfitTRexProFWInstallHandler.java
@@ -0,0 +1,49 @@
+/*  Copyright (C) 2021-2024 GeekosaurusR3x
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfittrexpro;
+
+import android.content.Context;
+import android.net.Uri;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.AbstractMiBandFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.AbstractMiBandFWInstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+
+class AmazfitTRexProFWInstallHandler extends AbstractMiBandFWInstallHandler {
+    AmazfitTRexProFWInstallHandler(final Uri uri, final Context context) {
+        super(uri, context);
+    }
+
+    @Override
+    protected String getFwUpgradeNotice() {
+        return mContext.getString(R.string.fw_upgrade_notice_amazfit_trex, helper.getHumanFirmwareVersion());
+    }
+
+    @Override
+    protected AbstractMiBandFWHelper createHelper(final Uri uri, final Context context) throws IOException {
+        return new AmazfitTRexProFWHelper(uri, context);
+    }
+
+    @Override
+    protected boolean isSupportedDeviceType(final GBDevice device) {
+        return device.getType() == DeviceType.AMAZFITTREXPRO;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfittrexultra/AmazfitTRexUltraCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfittrexultra/AmazfitTRexUltraCoordinator.java
new file mode 100644
index 0000000..8b61c4e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfittrexultra/AmazfitTRexUltraCoordinator.java
@@ -0,0 +1,81 @@
+/*  Copyright (C) 2023-2024 Daniel Dakhno, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfittrexultra;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.zeppos.ZeppOsCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class AmazfitTRexUltraCoordinator extends ZeppOsCoordinator {
+    @Override
+    public String getDeviceBluetoothName() {
+        return HuamiConst.AMAZFIT_TREX_ULTRA;
+    }
+
+    @Override
+    public Set<Integer> getDeviceSources() {
+        return new HashSet<>(Arrays.asList(
+                6553856, // chinese mainland version
+                6553857
+        ));
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_amazfit_trex_ultra;
+    }
+
+    @Override
+    public boolean supportsContinuousFindDevice() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsGpxUploads() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsControlCenter() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsToDoList() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsWifiHotspot(final GBDevice device) {
+        return true;
+    }
+
+    @Override
+    public boolean supportsFtpServer(final GBDevice device) {
+        return true;
+    }
+
+    @Override
+    public boolean supportsBluetoothPhoneCalls(final GBDevice device) {
+        return false;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitvergel/AmazfitVergeLCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitvergel/AmazfitVergeLCoordinator.java
new file mode 100644
index 0000000..94497c4
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitvergel/AmazfitVergeLCoordinator.java
@@ -0,0 +1,134 @@
+/*  Copyright (C) 2020-2024 angelpup, Daniel Dakhno, Jos茅 Rebelo, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitvergel;
+
+import android.content.Context;
+import android.net.Uri;
+
+import androidx.annotation.NonNull;
+
+import java.util.List;
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettings;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsScreen;
+import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitvergel.AmazfitVergeLSupport;
+
+public class AmazfitVergeLCoordinator extends HuamiCoordinator {
+    @Override
+    public String getManufacturer() {
+        // Actual manufacturer is Huami
+        return "Amazfit";
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("Amazfit Verge L", Pattern.CASE_INSENSITIVE);
+    }
+
+    @Override
+    public InstallHandler findInstallHandler(final Uri uri, final Context context) {
+        final AmazfitVergeLFWInstallHandler handler = new AmazfitVergeLFWInstallHandler(uri, context);
+        return handler.isValid() ? handler : null;
+    }
+
+    @Override
+    public int getBondingStyle() {
+        return BONDING_STYLE_REQUIRE_KEY;
+    }
+
+    @Override
+    public boolean supportsHeartRateMeasurement(final GBDevice device) {
+        return true;
+    }
+
+    @Override
+    public boolean supportsWeather() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsActivityTracks() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsMusicInfo() {
+        return true;
+    }
+
+
+    @Override
+    public DeviceSpecificSettings getDeviceSpecificSettings(final GBDevice device) {
+        final DeviceSpecificSettings deviceSpecificSettings = new DeviceSpecificSettings();
+
+        final List<Integer> generic = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.GENERIC);
+        generic.add(R.xml.devicesettings_wearlocation);
+        generic.add(R.xml.devicesettings_buttonactions_with_longpress);
+        final List<Integer> dateTime = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DATE_TIME);
+        dateTime.add(R.xml.devicesettings_timeformat);
+        final List<Integer> display = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DISPLAY);
+        display.add(R.xml.devicesettings_amazfitvergel);
+        display.add(R.xml.devicesettings_liftwrist_display);
+        final List<Integer> health = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.HEALTH);
+        health.add(R.xml.devicesettings_heartrate_sleep);
+        health.add(R.xml.devicesettings_inactivity_dnd);
+        health.add(R.xml.devicesettings_goal_notification);
+        final List<Integer> notifications = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.NOTIFICATIONS);
+        notifications.add(R.xml.devicesettings_vibrationpatterns);
+        notifications.add(R.xml.devicesettings_phone_silent_mode);
+        notifications.add(R.xml.devicesettings_transliteration);
+        final List<Integer> calendar = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.CALENDAR);
+        calendar.add(R.xml.devicesettings_sync_calendar);
+        calendar.add(R.xml.devicesettings_reserve_reminders_calendar);
+        final List<Integer> connection = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.CONNECTION);
+        connection.add(R.xml.devicesettings_disconnectnotification);
+        connection.add(R.xml.devicesettings_expose_hr_thirdparty);
+        connection.add(R.xml.devicesettings_bt_connected_advertisement);
+        connection.add(R.xml.devicesettings_overwrite_settings_on_connection);
+        final List<Integer> developer = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DEVELOPER);
+        developer.add(R.xml.devicesettings_huami2021_fetch_operation_time_unit);
+
+        return deviceSpecificSettings;
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return AmazfitVergeLSupport.class;
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_amazfit_vergel;
+    }
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_amazfit_bip;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_amazfit_bip_disabled;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitvergel/AmazfitVergeLFWHelper.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitvergel/AmazfitVergeLFWHelper.java
new file mode 100644
index 0000000..5635ae8
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitvergel/AmazfitVergeLFWHelper.java
@@ -0,0 +1,40 @@
+/*  Copyright (C) 2020-2024 angelpup
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitvergel;
+
+import android.content.Context;
+import android.net.Uri;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitvergel.AmazfitVergeLFirmwareInfo;
+
+public class AmazfitVergeLFWHelper extends HuamiFWHelper {
+
+    public AmazfitVergeLFWHelper(final Uri uri, final Context context) throws IOException {
+        super(uri, context);
+    }
+
+    @Override
+    protected void determineFirmwareInfo(byte[] wholeFirmwareBytes) {
+        firmwareInfo = new AmazfitVergeLFirmwareInfo(wholeFirmwareBytes);
+        if (!firmwareInfo.isHeaderValid()) {
+            throw new IllegalArgumentException("Not a an Amazfit Verge Lite firmware");
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitvergel/AmazfitVergeLFWInstallHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitvergel/AmazfitVergeLFWInstallHandler.java
new file mode 100644
index 0000000..e45177a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitvergel/AmazfitVergeLFWInstallHandler.java
@@ -0,0 +1,49 @@
+/*  Copyright (C) 2020-2024 angelpup
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitvergel;
+
+import android.content.Context;
+import android.net.Uri;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.AbstractMiBandFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.AbstractMiBandFWInstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+
+class AmazfitVergeLFWInstallHandler extends AbstractMiBandFWInstallHandler {
+    AmazfitVergeLFWInstallHandler(final Uri uri, final Context context) {
+        super(uri, context);
+    }
+
+    @Override
+    protected String getFwUpgradeNotice() {
+        return mContext.getString(R.string.fw_upgrade_notice_amazfitvergel, helper.getHumanFirmwareVersion());
+    }
+
+    @Override
+    protected AbstractMiBandFWHelper createHelper(final Uri uri, final Context context) throws IOException {
+        return new AmazfitVergeLFWHelper(uri, context);
+    }
+
+    @Override
+    protected boolean isSupportedDeviceType(final GBDevice device) {
+        return device.getType() == DeviceType.AMAZFITVERGEL;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitx/AmazfitXCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitx/AmazfitXCoordinator.java
new file mode 100644
index 0000000..7d20ffc
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitx/AmazfitXCoordinator.java
@@ -0,0 +1,151 @@
+/*  Copyright (C) 2021-2024 Andreas Shimokawa, Daniel Dakhno, Jos茅 Rebelo,
+    Petr Van臎k, Stefan Bora
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitx;
+
+import android.content.Context;
+import android.net.Uri;
+
+import androidx.annotation.NonNull;
+
+import java.util.List;
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettings;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsScreen;
+import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitx.AmazfitXSupport;
+
+public class AmazfitXCoordinator extends HuamiCoordinator {
+    @Override
+    public String getManufacturer() {
+        // Actual manufacturer is Huami
+        return "Amazfit";
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile(HuamiConst.AMAZFIT_X, Pattern.CASE_INSENSITIVE);
+    }
+
+    @Override
+    public InstallHandler findInstallHandler(final Uri uri, final Context context) {
+        // TOOD Need to be checked first
+        final AmazfitXFWInstallHandler handler = new AmazfitXFWInstallHandler(uri, context);
+        return handler.isValid() ? handler : null;
+//        return null;
+    }
+
+    @Override
+    public boolean supportsHeartRateMeasurement(final GBDevice device) {
+        return true;
+    }
+
+    @Override
+    public boolean supportsWeather() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsActivityTracks() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsMusicInfo() {
+        return true;
+    }
+
+    @Override
+    public DeviceSpecificSettings getDeviceSpecificSettings(final GBDevice device) {
+        final DeviceSpecificSettings deviceSpecificSettings = new DeviceSpecificSettings();
+
+        final List<Integer> generic = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.GENERIC);
+        generic.add(R.xml.devicesettings_wearlocation);
+        generic.add(R.xml.devicesettings_device_actions);
+        final List<Integer> dateTime = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DATE_TIME);
+        dateTime.add(R.xml.devicesettings_timeformat);
+        dateTime.add(R.xml.devicesettings_dateformat);
+        final List<Integer> display = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DISPLAY);
+        display.add(R.xml.devicesettings_amazfitx);
+        display.add(R.xml.devicesettings_nightmode);
+        display.add(R.xml.devicesettings_liftwrist_display);
+        display.add(R.xml.devicesettings_swipeunlock);
+        final List<Integer> health = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.HEALTH);
+        health.add(R.xml.devicesettings_heartrate_sleep);
+        health.add(R.xml.devicesettings_inactivity_dnd);
+        health.add(R.xml.devicesettings_goal_notification);
+        final List<Integer> notifications = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.NOTIFICATIONS);
+        notifications.add(R.xml.devicesettings_custom_emoji_font);
+        notifications.add(R.xml.devicesettings_vibrationpatterns);
+        notifications.add(R.xml.devicesettings_phone_silent_mode);
+        notifications.add(R.xml.devicesettings_transliteration);
+        final List<Integer> calendar = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.CALENDAR);
+        calendar.add(R.xml.devicesettings_sync_calendar);
+        calendar.add(R.xml.devicesettings_reserve_reminders_calendar);
+        final List<Integer> connection = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.CONNECTION);
+        connection.add(R.xml.devicesettings_expose_hr_thirdparty);
+        connection.add(R.xml.devicesettings_bt_connected_advertisement);
+        connection.add(R.xml.devicesettings_high_mtu);
+        connection.add(R.xml.devicesettings_overwrite_settings_on_connection);
+        final List<Integer> developer = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DEVELOPER);
+        developer.add(R.xml.devicesettings_huami2021_fetch_operation_time_unit);
+
+        return deviceSpecificSettings;
+    }
+
+    @Override
+    public String[] getSupportedLanguageSettings(final GBDevice device) {
+        return new String[]{
+                "auto",
+                "en_US",
+                "zh_CN",
+                "zh_TW",
+        };
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return AmazfitXSupport.class;
+    }
+
+    @Override
+    public int getBondingStyle() {
+        return BONDING_STYLE_REQUIRE_KEY;
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_amazfit_x;
+    }
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_miband2;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_miband2_disabled;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitx/AmazfitXFWHelper.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitx/AmazfitXFWHelper.java
new file mode 100644
index 0000000..d331bc1
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitx/AmazfitXFWHelper.java
@@ -0,0 +1,40 @@
+/*  Copyright (C) 2021-2024 Stefan Bora
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitx;
+
+import android.content.Context;
+import android.net.Uri;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitx.AmazfitXFirmwareInfo;
+
+public class AmazfitXFWHelper extends HuamiFWHelper {
+
+    public AmazfitXFWHelper(final Uri uri, final Context context) throws IOException {
+        super(uri, context);
+    }
+
+    @Override
+    protected void determineFirmwareInfo(byte[] wholeFirmwareBytes) {
+        firmwareInfo = new AmazfitXFirmwareInfo(wholeFirmwareBytes);
+        if (!firmwareInfo.isHeaderValid()) {
+            throw new IllegalArgumentException("Not an Amazfit X firmware");
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitx/AmazfitXFWInstallHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitx/AmazfitXFWInstallHandler.java
new file mode 100644
index 0000000..4b15063
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitx/AmazfitXFWInstallHandler.java
@@ -0,0 +1,50 @@
+/*  Copyright (C) 2021-2024 Stefan Bora
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitx;
+
+import android.content.Context;
+import android.net.Uri;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitx.AmazfitXFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.AbstractMiBandFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.AbstractMiBandFWInstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+
+class AmazfitXFWInstallHandler extends AbstractMiBandFWInstallHandler {
+    AmazfitXFWInstallHandler(final Uri uri, final Context context) {
+        super(uri, context);
+    }
+
+    @Override
+    protected String getFwUpgradeNotice() {
+        return mContext.getString(R.string.fw_upgrade_notice_amazfitx, helper.getHumanFirmwareVersion());
+    }
+
+    @Override
+    protected AbstractMiBandFWHelper createHelper(final Uri uri, final Context context) throws IOException {
+        return new AmazfitXFWHelper(uri, context);
+    }
+
+    @Override
+    protected boolean isSupportedDeviceType(final GBDevice device) {
+        return device.getType() == DeviceType.AMAZFITX;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband2/MiBand2Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband2/MiBand2Coordinator.java
new file mode 100644
index 0000000..1767c9f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband2/MiBand2Coordinator.java
@@ -0,0 +1,141 @@
+/*  Copyright (C) 2017-2024 Andreas Shimokawa, Daniel Dakhno, Daniele Gobbetti,
+    Dmitry Markin, Jos茅 Rebelo, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.miband2;
+
+import android.content.Context;
+import android.net.Uri;
+
+import androidx.annotation.NonNull;
+
+import java.util.EnumSet;
+import java.util.List;
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettings;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsScreen;
+import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandSettingsCustomizer;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.ServiceDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.miband2.MiBand2Support;
+
+public class MiBand2Coordinator extends HuamiCoordinator {
+    @Override
+    public String getManufacturer() {
+        // Actual manufacturer is Huami
+        return "Xiaomi";
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile(HuamiConst.MI_BAND2_NAME, Pattern.CASE_INSENSITIVE);
+    }
+
+    @Override
+    public InstallHandler findInstallHandler(final Uri uri, final Context context) {
+        final MiBand2FWInstallHandler handler = new MiBand2FWInstallHandler(uri, context);
+        return handler.isValid() ? handler : null;
+    }
+
+    @Override
+    public boolean supportsHeartRateMeasurement(final GBDevice device) {
+        return true;
+    }
+
+    @Override
+    public int getReminderSlotCount(final GBDevice device) {
+        return 0;
+    }
+
+    @Override
+    public boolean supportsDebugLogs() {
+        return false;
+    }
+
+    @Override
+    public DeviceSpecificSettings getDeviceSpecificSettings(final GBDevice device) {
+        final DeviceSpecificSettings deviceSpecificSettings = new DeviceSpecificSettings();
+
+        final List<Integer> generic = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.GENERIC);
+        generic.add(R.xml.devicesettings_wearlocation);
+        generic.add(R.xml.devicesettings_buttonactions);
+        final List<Integer> dateTime = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DATE_TIME);
+        dateTime.add(R.xml.devicesettings_timeformat);
+        dateTime.add(R.xml.devicesettings_miband2_dateformat);
+        final List<Integer> display = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DISPLAY);
+        display.add(R.xml.devicesettings_miband2_display);
+        display.add(R.xml.devicesettings_liftwrist_display);
+        display.add(R.xml.devicesettings_rotatewrist_cycleinfo);
+        final List<Integer> health = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.HEALTH);
+        health.add(R.xml.devicesettings_heartrate_sleep);
+        health.add(R.xml.devicesettings_inactivity_dnd);
+        health.add(R.xml.devicesettings_goal_notification);
+        final List<Integer> notifications = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.NOTIFICATIONS);
+        notifications.add(R.xml.devicesettings_donotdisturb_withauto);
+        notifications.add(R.xml.devicesettings_miband_vibrationpatterns);
+        notifications.add(R.xml.devicesettings_transliteration);
+        final List<Integer> calendar = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.CALENDAR);
+        //calendar.add(R.xml.devicesettings_sync_calendar);
+        calendar.add(R.xml.devicesettings_reserve_alarms_calendar);
+        final List<Integer> connection = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.CONNECTION);
+        connection.add(R.xml.devicesettings_bt_connected_advertisement);
+        connection.add(R.xml.devicesettings_overwrite_settings_on_connection);
+        final List<Integer> developer = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DEVELOPER);
+        developer.add(R.xml.devicesettings_huami2021_fetch_operation_time_unit);
+
+        return deviceSpecificSettings;
+    }
+
+    @Override
+    public DeviceSpecificSettingsCustomizer getDeviceSpecificSettingsCustomizer(final GBDevice device) {
+        // For the vibration patterns, which are still legacy mi_ ones
+        return new MiBandSettingsCustomizer(device);
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return MiBand2Support.class;
+    }
+
+    @Override
+    public EnumSet<ServiceDeviceSupport.Flags> getInitialFlags() {
+        return EnumSet.of(ServiceDeviceSupport.Flags.THROTTLING, ServiceDeviceSupport.Flags.BUSY_CHECKING);
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_miband2;
+    }
+
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_miband2;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_miband2_disabled;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband2/MiBand2FWHelper.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband2/MiBand2FWHelper.java
new file mode 100644
index 0000000..9744697
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband2/MiBand2FWHelper.java
@@ -0,0 +1,42 @@
+/*  Copyright (C) 2017-2024 Andreas Shimokawa, Daniele Gobbetti
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.miband2;
+
+import android.content.Context;
+import android.net.Uri;
+
+import java.io.IOException;
+
+import androidx.annotation.NonNull;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.miband2.Mi2FirmwareInfo;
+
+public class MiBand2FWHelper extends HuamiFWHelper {
+
+    public MiBand2FWHelper(final Uri uri, final Context context) throws IOException {
+        super(uri, context);
+    }
+
+    @NonNull
+    @Override
+    protected void determineFirmwareInfo(byte[] wholeFirmwareBytes) {
+        firmwareInfo = new Mi2FirmwareInfo(wholeFirmwareBytes);
+        if (!firmwareInfo.isHeaderValid()) {
+            throw new IllegalArgumentException("Not a Mi Band 2 firmware");
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband2/MiBand2FWInstallHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband2/MiBand2FWInstallHandler.java
new file mode 100644
index 0000000..b442c9e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband2/MiBand2FWInstallHandler.java
@@ -0,0 +1,122 @@
+/*  Copyright (C) 2017-2024 Andreas Shimokawa, Daniel Dakhno, Taavi Eom盲e
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.miband2;
+
+import android.content.Context;
+import android.net.Uri;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.InstallActivity;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.AbstractMiBandFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.AbstractMiBandFWInstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareType;
+import nodomain.freeyourgadget.gadgetbridge.util.Version;
+
+public class MiBand2FWInstallHandler extends AbstractMiBandFWInstallHandler {
+    private static final Logger LOG = LoggerFactory.getLogger(MiBand2FWInstallHandler.class);
+
+    MiBand2FWInstallHandler(final Uri uri, final Context context) {
+        super(uri, context);
+    }
+
+    @Override
+    public void validateInstallation(InstallActivity installActivity, GBDevice device) {
+        super.validateInstallation(installActivity, device);
+        maybeAddFw53Hint(installActivity, device);
+        maybeAddFontHint(installActivity);
+    }
+
+    private void maybeAddFontHint(InstallActivity installActivity) {
+        HuamiFirmwareType type = getFirmwareType();
+        if (type == HuamiFirmwareType.FIRMWARE) {
+            String newInfoText = installActivity.getInfoText() + "\n\n" + "Note: you may install Mili_pro.ft or Mili_pro.ft.en to enable text notifications.";
+            installActivity.setInfoText(newInfoText);
+        }
+    }
+
+    private void maybeAddFw53Hint(InstallActivity installActivity, GBDevice device) {
+        HuamiFirmwareType type = getFirmwareType();
+        if (type != HuamiFirmwareType.FIRMWARE) {
+            return;
+        }
+
+        Version deviceVersion = getFirmwareVersionOf(device);
+        if (deviceVersion != null) {
+            Version v53 = MiBandConst.MI2_FW_VERSION_INTERMEDIATE_UPGRADE_53;
+            if (deviceVersion.compareTo(v53) < 0) {
+                String vInstall = getHelper().format(getHelper().getFirmwareVersion());
+                try {
+                    if (vInstall == null || new Version(vInstall).compareTo(v53) > 0) {
+                        String newInfoText = getContext().getString(R.string.mi2_fw_installhandler_fw53_hint, v53.get()) +
+                                "\n\n" +
+                                installActivity.getInfoText();
+                        installActivity.setInfoText(newInfoText);
+                    }
+                } catch (IllegalArgumentException e) {
+                    String newInfoText = getContext().getString(R.string.mi2_fw_installhandler_fw53_hint, v53.get()) +
+                            "\n\n" +
+                            installActivity.getInfoText() +
+                            "\n\n" +
+                            getContext().getString(R.string.error_version_check_extreme_caution, vInstall);
+                    installActivity.setInfoText(newInfoText);
+                }
+            }
+        }
+    }
+
+    private Version getFirmwareVersionOf(final GBDevice device) {
+        String version = device.getFirmwareVersion();
+        if (version == null || version.length() == 0) {
+            return null;
+        }
+        if (version.charAt(0) == 'V') {
+            version = version.substring(1);
+        }
+        try {
+            return new Version(version);
+        } catch (Exception ex) {
+            LOG.error("Unable to parse version: " + version);
+            return null;
+        }
+    }
+
+    private HuamiFirmwareType getFirmwareType() {
+        AbstractMiBandFWHelper helper = getHelper();
+        if (helper instanceof MiBand2FWHelper) {
+            return ((MiBand2FWHelper) helper).getFirmwareInfo().getFirmwareType();
+        }
+        return HuamiFirmwareType.INVALID;
+    }
+
+    @Override
+    protected AbstractMiBandFWHelper createHelper(final Uri uri, final Context context) throws IOException {
+        return new MiBand2FWHelper(uri, context);
+    }
+
+    @Override
+    protected boolean isSupportedDeviceType(final GBDevice device) {
+        return device.getType() == DeviceType.MIBAND2 || device.getType() == DeviceType.MIBAND2_HRX;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband2/MiBand2HRXCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband2/MiBand2HRXCoordinator.java
new file mode 100644
index 0000000..dad5952
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband2/MiBand2HRXCoordinator.java
@@ -0,0 +1,131 @@
+/*  Copyright (C) 2017-2024 Andreas Shimokawa, Carsten Pfeiffer, Daniel
+    Dakhno, Daniele Gobbetti, Dmitry Markin, Jos茅 Rebelo, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.miband2;
+
+import android.content.Context;
+import android.net.Uri;
+
+import androidx.annotation.NonNull;
+
+import java.util.EnumSet;
+import java.util.List;
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettings;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsScreen;
+import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandSettingsCustomizer;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.ServiceDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.miband2.MiBand2Support;
+
+public class MiBand2HRXCoordinator extends HuamiCoordinator {
+    @Override
+    public String getManufacturer() {
+        // Actual manufacturer is Huami
+        return "Xiaomi";
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile(
+                HuamiConst.MI_BAND2_NAME_HRX + "|Mi Band 2i",
+                Pattern.CASE_INSENSITIVE
+        );
+    }
+
+    @Override
+    public InstallHandler findInstallHandler(final Uri uri, final Context context) {
+        return null;
+    }
+
+    @Override
+    public boolean supportsFlashing() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsDebugLogs() {
+        return false;
+    }
+
+    @Override
+    public DeviceSpecificSettings getDeviceSpecificSettings(final GBDevice device) {
+        final DeviceSpecificSettings deviceSpecificSettings = new DeviceSpecificSettings();
+
+        final List<Integer> generic = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.GENERIC);
+        generic.add(R.xml.devicesettings_wearlocation);
+        final List<Integer> dateTime = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DATE_TIME);
+        dateTime.add(R.xml.devicesettings_miband2_dateformat);
+        final List<Integer> display = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DISPLAY);
+        display.add(R.xml.devicesettings_miband2_display);
+        display.add(R.xml.devicesettings_liftwrist_display);
+        display.add(R.xml.devicesettings_rotatewrist_cycleinfo);
+        final List<Integer> health = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.HEALTH);
+        health.add(R.xml.devicesettings_heartrate_sleep);
+        health.add(R.xml.devicesettings_inactivity_dnd);
+        health.add(R.xml.devicesettings_goal_notification);
+        final List<Integer> notifications = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.NOTIFICATIONS);
+        notifications.add(R.xml.devicesettings_donotdisturb_withauto);
+        notifications.add(R.xml.devicesettings_miband_vibrationpatterns);
+        notifications.add(R.xml.devicesettings_transliteration);
+        final List<Integer> connection = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.CONNECTION);
+        connection.add(R.xml.devicesettings_overwrite_settings_on_connection);
+        final List<Integer> developer = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DEVELOPER);
+        developer.add(R.xml.devicesettings_huami2021_fetch_operation_time_unit);
+
+        return deviceSpecificSettings;
+    }
+
+    @Override
+    public DeviceSpecificSettingsCustomizer getDeviceSpecificSettingsCustomizer(final GBDevice device) {
+        // For the vibration patterns, which are still legacy mi_ ones
+        return new MiBandSettingsCustomizer(device);
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return MiBand2Support.class;
+    }
+
+    @Override
+    public EnumSet<ServiceDeviceSupport.Flags> getInitialFlags() {
+        return EnumSet.of(ServiceDeviceSupport.Flags.THROTTLING, ServiceDeviceSupport.Flags.BUSY_CHECKING);
+    }
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_miband2;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_miband2_disabled;
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_miband2_hrx;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband3/MiBand3Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband3/MiBand3Coordinator.java
new file mode 100644
index 0000000..d13b367
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband3/MiBand3Coordinator.java
@@ -0,0 +1,175 @@
+/*  Copyright (C) 2018-2024 Andreas Shimokawa, Daniel Dakhno, Daniele Gobbetti,
+    Jos茅 Rebelo, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.miband3;
+
+import android.content.Context;
+import android.net.Uri;
+
+import androidx.annotation.NonNull;
+
+import java.util.Date;
+import java.util.List;
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettings;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsScreen;
+import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.miband3.MiBand3Support;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+public class MiBand3Coordinator extends HuamiCoordinator {
+    @Override
+    public String getManufacturer() {
+        // Actual manufacturer is Huami
+        return "Xiaomi";
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile(
+                HuamiConst.MI_BAND3_NAME + "|" + HuamiConst.MI_BAND3_NAME_2,
+                Pattern.CASE_INSENSITIVE
+        );
+    }
+
+    @Override
+    public InstallHandler findInstallHandler(final Uri uri, final Context context) {
+        final MiBand3FWInstallHandler handler = new MiBand3FWInstallHandler(uri, context);
+        return handler.isValid() ? handler : null;
+    }
+
+    @Override
+    public boolean supportsHeartRateMeasurement(final GBDevice device) {
+        return true;
+    }
+
+    @Override
+    public boolean supportsWeather() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsActivityTracks() {
+        return true;
+    }
+
+    public static String getNightMode(final String deviceAddress) {
+        Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(deviceAddress));
+
+        return prefs.getString(MiBandConst.PREF_NIGHT_MODE, MiBandConst.PREF_NIGHT_MODE_OFF);
+    }
+
+    public static Date getNightModeStart(final String deviceAddress) {
+        return getTimePreference(MiBandConst.PREF_NIGHT_MODE_START, "16:00", deviceAddress);
+    }
+
+    public static Date getNightModeEnd(final String deviceAddress) {
+        return getTimePreference(MiBandConst.PREF_NIGHT_MODE_END, "07:00", deviceAddress);
+    }
+
+    @Override
+    public DeviceSpecificSettings getDeviceSpecificSettings(final GBDevice device) {
+        final DeviceSpecificSettings deviceSpecificSettings = new DeviceSpecificSettings();
+
+        final List<Integer> generic = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.GENERIC);
+        generic.add(R.xml.devicesettings_wearlocation);
+        generic.add(R.xml.devicesettings_device_actions);
+        final List<Integer> dateTime = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DATE_TIME);
+        dateTime.add(R.xml.devicesettings_timeformat);
+        dateTime.add(R.xml.devicesettings_dateformat);
+        final List<Integer> display = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DISPLAY);
+        display.add(R.xml.devicesettings_miband3);
+        display.add(R.xml.devicesettings_nightmode);
+        display.add(R.xml.devicesettings_liftwrist_display);
+        display.add(R.xml.devicesettings_donotdisturb_lift_wrist);
+        display.add(R.xml.devicesettings_swipeunlock);
+        final List<Integer> health = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.HEALTH);
+        health.add(R.xml.devicesettings_heartrate_sleep);
+        health.add(R.xml.devicesettings_inactivity_dnd);
+        health.add(R.xml.devicesettings_goal_notification);
+        final List<Integer> notifications = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.NOTIFICATIONS);
+        notifications.add(R.xml.devicesettings_donotdisturb_withauto);
+        notifications.add(R.xml.devicesettings_phone_silent_mode);
+        notifications.add(R.xml.devicesettings_transliteration);
+        final List<Integer> calendar = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.CALENDAR);
+        calendar.add(R.xml.devicesettings_sync_calendar);
+        calendar.add(R.xml.devicesettings_reserve_reminders_calendar);
+        final List<Integer> connection = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.CONNECTION);
+        connection.add(R.xml.devicesettings_expose_hr_thirdparty);
+        connection.add(R.xml.devicesettings_bt_connected_advertisement);
+        connection.add(R.xml.devicesettings_overwrite_settings_on_connection);
+        final List<Integer> developer = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DEVELOPER);
+        developer.add(R.xml.devicesettings_huami2021_fetch_operation_time_unit);
+
+        return deviceSpecificSettings;
+    }
+
+    @Override
+    public String[] getSupportedLanguageSettings(final GBDevice device) {
+        return new String[]{
+                "auto",
+                "ar_SA",
+                "de_DE",
+                "en_US",
+                "es_ES",
+                "fr_FR",
+                "id_ID",
+                "it_IT",
+                "ja_JP",
+                "ko_KO",
+                "pt_PT",
+                "nl_NL",
+                "pl_PL",
+                "ru_RU",
+                "th_TH",
+                "tr_TR",
+                "uk_UA",
+                "vi_VN",
+                "zh_CN",
+                "zh_TW",
+        };
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return MiBand3Support.class;
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_miband3;
+    }
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_miband2;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_miband2_disabled;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband3/MiBand3FWHelper.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband3/MiBand3FWHelper.java
new file mode 100644
index 0000000..7f3ff29
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband3/MiBand3FWHelper.java
@@ -0,0 +1,40 @@
+/*  Copyright (C) 2018-2024 Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.miband3;
+
+import android.content.Context;
+import android.net.Uri;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.miband3.MiBand3FirmwareInfo;
+
+public class MiBand3FWHelper extends HuamiFWHelper {
+
+    public MiBand3FWHelper(final Uri uri, final Context context) throws IOException {
+        super(uri, context);
+    }
+
+    @Override
+    protected void determineFirmwareInfo(byte[] wholeFirmwareBytes) {
+        firmwareInfo = new MiBand3FirmwareInfo(wholeFirmwareBytes);
+        if (!firmwareInfo.isHeaderValid()) {
+            throw new IllegalArgumentException("Not a Mi Band 3 firmware");
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband3/MiBand3FWInstallHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband3/MiBand3FWInstallHandler.java
new file mode 100644
index 0000000..e9f52b5
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband3/MiBand3FWInstallHandler.java
@@ -0,0 +1,49 @@
+/*  Copyright (C) 2018-2024 Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.miband3;
+
+import android.content.Context;
+import android.net.Uri;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.AbstractMiBandFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.AbstractMiBandFWInstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+
+class MiBand3FWInstallHandler extends AbstractMiBandFWInstallHandler {
+    MiBand3FWInstallHandler(final Uri uri, final Context context) {
+        super(uri, context);
+    }
+
+    @Override
+    protected String getFwUpgradeNotice() {
+        return mContext.getString(R.string.fw_upgrade_notice_miband3, helper.getHumanFirmwareVersion());
+    }
+
+    @Override
+    protected AbstractMiBandFWHelper createHelper(final Uri uri, final Context context) throws IOException {
+        return new MiBand3FWHelper(uri, context);
+    }
+
+    @Override
+    protected boolean isSupportedDeviceType(final GBDevice device) {
+        return device.getType() == DeviceType.MIBAND3;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband3/MiBand3Service.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband3/MiBand3Service.java
new file mode 100644
index 0000000..b83cec8
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband3/MiBand3Service.java
@@ -0,0 +1,28 @@
+/*  Copyright (C) 2018-2024 Andreas Shimokawa, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.miband3;
+
+import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService.ENDPOINT_DISPLAY;
+
+public class MiBand3Service {
+    public static final byte[] COMMAND_ENABLE_BAND_SCREEN_UNLOCK = new byte[]{ENDPOINT_DISPLAY, 0x16, 0x00, 0x01};
+    public static final byte[] COMMAND_DISABLE_BAND_SCREEN_UNLOCK = new byte[]{ENDPOINT_DISPLAY, 0x16, 0x00, 0x00};
+    public static final byte[] COMMAND_NIGHT_MODE_OFF = new byte[]{0x1a, 0x00};
+    public static final byte[] COMMAND_NIGHT_MODE_SUNSET = new byte[]{0x1a, 0x02};
+    public static final byte[] COMMAND_NIGHT_MODE_SCHEDULED = new byte[]{0x1a, 0x01, 0x10, 0x00, 0x07, 0x00};
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband4/MiBand4Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband4/MiBand4Coordinator.java
new file mode 100644
index 0000000..9baeae8
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband4/MiBand4Coordinator.java
@@ -0,0 +1,185 @@
+/*  Copyright (C) 2019-2024 Adam B眉chner, Andreas Shimokawa, Daniel Dakhno,
+    HardLight, Jos茅 Rebelo, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.miband4;
+
+import android.content.Context;
+import android.net.Uri;
+
+import androidx.annotation.NonNull;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettings;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsScreen;
+import nodomain.freeyourgadget.gadgetbridge.capabilities.HeartRateCapability;
+import nodomain.freeyourgadget.gadgetbridge.capabilities.password.PasswordCapabilityImpl;
+import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.miband4.MiBand4Support;
+
+public class MiBand4Coordinator extends HuamiCoordinator {
+    @Override
+    public String getManufacturer() {
+        // Actual manufacturer is Huami
+        return "Xiaomi";
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile(HuamiConst.MI_BAND4_NAME, Pattern.CASE_INSENSITIVE);
+    }
+
+    @Override
+    public InstallHandler findInstallHandler(final Uri uri, final Context context) {
+        final MiBand4FWInstallHandler handler = new MiBand4FWInstallHandler(uri, context);
+        return handler.isValid() ? handler : null;
+    }
+
+    @Override
+    public boolean supportsHeartRateMeasurement(final GBDevice device) {
+        return true;
+    }
+
+    @Override
+    public boolean supportsWeather() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsActivityTracks() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsMusicInfo() {
+        return true;
+    }
+
+    @Override
+    public DeviceSpecificSettings getDeviceSpecificSettings(final GBDevice device) {
+        final DeviceSpecificSettings deviceSpecificSettings = new DeviceSpecificSettings();
+
+        final List<Integer> generic = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.GENERIC);
+        generic.add(R.xml.devicesettings_wearlocation);
+        generic.add(R.xml.devicesettings_device_actions);
+        final List<Integer> dateTime = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DATE_TIME);
+        dateTime.add(R.xml.devicesettings_timeformat);
+        dateTime.add(R.xml.devicesettings_dateformat);
+        final List<Integer> display = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DISPLAY);
+        display.add(R.xml.devicesettings_miband3);
+        display.add(R.xml.devicesettings_nightmode);
+        display.add(R.xml.devicesettings_liftwrist_display_sensitivity);
+        display.add(R.xml.devicesettings_swipeunlock);
+        display.add(R.xml.devicesettings_password);
+        final List<Integer> health = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.HEALTH);
+        health.add(R.xml.devicesettings_heartrate_sleep_activity);
+        health.add(R.xml.devicesettings_inactivity_dnd);
+        health.add(R.xml.devicesettings_goal_notification);
+        final List<Integer> workout = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.WORKOUT);
+        workout.add(R.xml.devicesettings_workout_start_on_phone);
+        workout.add(R.xml.devicesettings_workout_send_gps_to_band);
+        final List<Integer> notifications = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.NOTIFICATIONS);
+        notifications.add(R.xml.devicesettings_custom_emoji_font);
+        notifications.add(R.xml.devicesettings_vibrationpatterns);
+        notifications.add(R.xml.devicesettings_transliteration);
+        final List<Integer> calendar = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.CALENDAR);
+        calendar.add(R.xml.devicesettings_sync_calendar);
+        calendar.add(R.xml.devicesettings_reserve_reminders_calendar);
+        final List<Integer> connection = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.CONNECTION);
+        connection.add(R.xml.devicesettings_expose_hr_thirdparty);
+        connection.add(R.xml.devicesettings_bt_connected_advertisement);
+        connection.add(R.xml.devicesettings_high_mtu);
+        connection.add(R.xml.devicesettings_overwrite_settings_on_connection);
+        final List<Integer> developer = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DEVELOPER);
+        developer.add(R.xml.devicesettings_huami2021_fetch_operation_time_unit);
+
+        return deviceSpecificSettings;
+    }
+
+    @Override
+    public String[] getSupportedLanguageSettings(final GBDevice device) {
+        return new String[]{
+                "auto",
+                "ar_SA",
+                "de_DE",
+                "el_GR",
+                "en_US",
+                "es_ES",
+                "fr_FR",
+                "id_ID",
+                "it_IT",
+                "pt_PT",
+                "pl_PL",
+                "ru_RU",
+                "th_TH",
+                "tr_TR",
+                "uk_UA",
+                "vi_VN",
+                "zh_CN",
+                "zh_TW",
+        };
+    }
+
+    @Override
+    public int getBondingStyle() {
+        return BONDING_STYLE_REQUIRE_KEY;
+    }
+
+    @Override
+    public PasswordCapabilityImpl.Mode getPasswordCapability() {
+        return PasswordCapabilityImpl.Mode.NUMBERS_4_DIGITS_1_TO_4;
+    }
+
+    @Override
+    public List<HeartRateCapability.MeasurementInterval> getHeartRateMeasurementIntervals() {
+        return Arrays.asList(
+                HeartRateCapability.MeasurementInterval.OFF,
+                HeartRateCapability.MeasurementInterval.MINUTES_1,
+                HeartRateCapability.MeasurementInterval.MINUTES_5,
+                HeartRateCapability.MeasurementInterval.MINUTES_10,
+                HeartRateCapability.MeasurementInterval.MINUTES_30
+        );
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return MiBand4Support.class;
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_miband4;
+    }
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_miband2;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_miband2_disabled;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband4/MiBand4FWHelper.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband4/MiBand4FWHelper.java
new file mode 100644
index 0000000..6717540
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband4/MiBand4FWHelper.java
@@ -0,0 +1,40 @@
+/*  Copyright (C) 2019-2024 Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.miband4;
+
+import android.content.Context;
+import android.net.Uri;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.miband4.MiBand4FirmwareInfo;
+
+public class MiBand4FWHelper extends HuamiFWHelper {
+
+    public MiBand4FWHelper(final Uri uri, final Context context) throws IOException {
+        super(uri, context);
+    }
+
+    @Override
+    protected void determineFirmwareInfo(byte[] wholeFirmwareBytes) {
+        firmwareInfo = new MiBand4FirmwareInfo(wholeFirmwareBytes);
+        if (!firmwareInfo.isHeaderValid()) {
+            throw new IllegalArgumentException("Not a Mi Band 4 firmware");
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband4/MiBand4FWInstallHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband4/MiBand4FWInstallHandler.java
new file mode 100644
index 0000000..3581eea
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband4/MiBand4FWInstallHandler.java
@@ -0,0 +1,50 @@
+/*  Copyright (C) 2019-2024 Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.miband4;
+
+import android.content.Context;
+import android.net.Uri;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.miband3.MiBand3FWHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.AbstractMiBandFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.AbstractMiBandFWInstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+
+class MiBand4FWInstallHandler extends AbstractMiBandFWInstallHandler {
+    MiBand4FWInstallHandler(final Uri uri, final Context context) {
+        super(uri, context);
+    }
+
+    @Override
+    protected String getFwUpgradeNotice() {
+        return mContext.getString(R.string.fw_upgrade_notice_miband4, helper.getHumanFirmwareVersion());
+    }
+
+    @Override
+    protected AbstractMiBandFWHelper createHelper(final Uri uri, final Context context) throws IOException {
+        return new MiBand4FWHelper(uri, context);
+    }
+
+    @Override
+    protected boolean isSupportedDeviceType(final GBDevice device) {
+        return device.getType() == DeviceType.MIBAND4;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband5/MiBand5Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband5/MiBand5Coordinator.java
new file mode 100644
index 0000000..db7f4d6
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband5/MiBand5Coordinator.java
@@ -0,0 +1,204 @@
+/*  Copyright (C) 2020-2024 Andreas Shimokawa, Daniel Dakhno, Jos茅 Rebelo,
+    odavo32nof, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.miband5;
+
+import android.content.Context;
+import android.net.Uri;
+
+import androidx.annotation.NonNull;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettings;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsScreen;
+import nodomain.freeyourgadget.gadgetbridge.capabilities.HeartRateCapability;
+import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.miband5.MiBand5Support;
+
+public class MiBand5Coordinator extends HuamiCoordinator {
+    @Override
+    public String getManufacturer() {
+        // Actual manufacturer is Huami
+        return "Xiaomi";
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile(HuamiConst.MI_BAND5_NAME, Pattern.CASE_INSENSITIVE);
+    }
+
+    @Override
+    public InstallHandler findInstallHandler(final Uri uri, final Context context) {
+        final MiBand5FWInstallHandler handler = new MiBand5FWInstallHandler(uri, context);
+        return handler.isValid() ? handler : null;
+    }
+
+    @Override
+    public boolean supportsHeartRateMeasurement(final GBDevice device) {
+        return true;
+    }
+
+    @Override
+    public boolean supportsWeather() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsActivityTracks() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsStressMeasurement() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsMusicInfo() {
+        return true;
+    }
+
+    @Override
+    public int getReminderSlotCount(final GBDevice device) {
+        return 50; // as enforced by Zepp Life
+    }
+
+    @Override
+    public int getWorldClocksSlotCount() {
+        return 20; // as enforced by Mi Fit
+    }
+
+    @Override
+    public int getWorldClocksLabelLength() {
+        return 30; // at least
+    }
+
+    @Override
+    public DeviceSpecificSettings getDeviceSpecificSettings(final GBDevice device) {
+        final DeviceSpecificSettings deviceSpecificSettings = new DeviceSpecificSettings();
+
+        final List<Integer> generic = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.GENERIC);
+        generic.add(R.xml.devicesettings_wearlocation);
+        generic.add(R.xml.devicesettings_device_actions);
+        final List<Integer> dateTime = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DATE_TIME);
+        dateTime.add(R.xml.devicesettings_timeformat);
+        dateTime.add(R.xml.devicesettings_dateformat);
+        dateTime.add(R.xml.devicesettings_world_clocks);
+        final List<Integer> display = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DISPLAY);
+        display.add(R.xml.devicesettings_miband5);
+        display.add(R.xml.devicesettings_nightmode);
+        display.add(R.xml.devicesettings_liftwrist_display_sensitivity);
+        display.add(R.xml.devicesettings_swipeunlock);
+        final List<Integer> health = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.HEALTH);
+        health.add(R.xml.devicesettings_heartrate_sleep_alert_activity_stress);
+        health.add(R.xml.devicesettings_inactivity_dnd);
+        health.add(R.xml.devicesettings_goal_notification);
+        final List<Integer> workout = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.WORKOUT);
+        workout.add(R.xml.devicesettings_workout_start_on_phone);
+        workout.add(R.xml.devicesettings_workout_send_gps_to_band);
+        final List<Integer> notifications = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.NOTIFICATIONS);
+        notifications.add(R.xml.devicesettings_custom_emoji_font);
+        notifications.add(R.xml.devicesettings_vibrationpatterns);
+        notifications.add(R.xml.devicesettings_phone_silent_mode);
+        notifications.add(R.xml.devicesettings_transliteration);
+        final List<Integer> calendar = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.CALENDAR);
+        calendar.add(R.xml.devicesettings_sync_calendar);
+        calendar.add(R.xml.devicesettings_reserve_reminders_calendar);
+        final List<Integer> connection = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.CONNECTION);
+        connection.add(R.xml.devicesettings_expose_hr_thirdparty);
+        connection.add(R.xml.devicesettings_bt_connected_advertisement);
+        connection.add(R.xml.devicesettings_high_mtu);
+        connection.add(R.xml.devicesettings_overwrite_settings_on_connection);
+        final List<Integer> developer = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DEVELOPER);
+        developer.add(R.xml.devicesettings_huami2021_fetch_operation_time_unit);
+
+        return deviceSpecificSettings;
+    }
+
+    @Override
+    public String[] getSupportedLanguageSettings(final GBDevice device) {
+        return new String[]{
+                "auto",
+                "ar_SA",
+                "cs_CZ",
+                "de_DE",
+                "el_GR",
+                "en_US",
+                "es_ES",
+                "fr_FR",
+                "he_IL",
+                "id_ID",
+                "it_IT",
+                "nl_NL",
+                "pt_BR",
+                "pl_PL",
+                "ro_RO",
+                "ru_RU",
+                "th_TH",
+                "tr_TR",
+                "uk_UA",
+                "vi_VN",
+                "zh_CN",
+                "zh_TW",
+        };
+    }
+
+    @Override
+    public int getBondingStyle() {
+        return BONDING_STYLE_REQUIRE_KEY;
+    }
+
+    @Override
+    public List<HeartRateCapability.MeasurementInterval> getHeartRateMeasurementIntervals() {
+        return Arrays.asList(
+                HeartRateCapability.MeasurementInterval.OFF,
+                HeartRateCapability.MeasurementInterval.MINUTES_1,
+                HeartRateCapability.MeasurementInterval.MINUTES_5,
+                HeartRateCapability.MeasurementInterval.MINUTES_10,
+                HeartRateCapability.MeasurementInterval.MINUTES_30
+        );
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return MiBand5Support.class;
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_miband5;
+    }
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_miband2;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_miband2_disabled;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband5/MiBand5FWHelper.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband5/MiBand5FWHelper.java
new file mode 100644
index 0000000..3dd3ed1
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband5/MiBand5FWHelper.java
@@ -0,0 +1,40 @@
+/*  Copyright (C) 2020-2024 odavo32nof
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.miband5;
+
+import android.content.Context;
+import android.net.Uri;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.miband5.MiBand5FirmwareInfo;
+
+public class MiBand5FWHelper extends HuamiFWHelper {
+
+    public MiBand5FWHelper(final Uri uri, final Context context) throws IOException {
+        super(uri, context);
+    }
+
+    @Override
+    protected void determineFirmwareInfo(byte[] wholeFirmwareBytes) {
+        firmwareInfo = new MiBand5FirmwareInfo(wholeFirmwareBytes);
+        if (!firmwareInfo.isHeaderValid()) {
+            throw new IllegalArgumentException("Not a Mi Band 5 firmware");
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband5/MiBand5FWInstallHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband5/MiBand5FWInstallHandler.java
new file mode 100644
index 0000000..db96139
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband5/MiBand5FWInstallHandler.java
@@ -0,0 +1,50 @@
+/*  Copyright (C) 2020-2024 odavo32nof
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.miband5;
+
+import android.content.Context;
+import android.net.Uri;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.miband5.MiBand5FWHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.AbstractMiBandFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.AbstractMiBandFWInstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+
+class MiBand5FWInstallHandler extends AbstractMiBandFWInstallHandler {
+    MiBand5FWInstallHandler(final Uri uri, final Context context) {
+        super(uri, context);
+    }
+
+    @Override
+    protected String getFwUpgradeNotice() {
+        return mContext.getString(R.string.fw_upgrade_notice_miband5, helper.getHumanFirmwareVersion());
+    }
+
+    @Override
+    protected AbstractMiBandFWHelper createHelper(final Uri uri, final Context context) throws IOException {
+        return new MiBand5FWHelper(uri, context);
+    }
+
+    @Override
+    protected boolean isSupportedDeviceType(final GBDevice device) {
+        return device.getType() == DeviceType.MIBAND5;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband6/MiBand6Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband6/MiBand6Coordinator.java
new file mode 100644
index 0000000..d158c68
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband6/MiBand6Coordinator.java
@@ -0,0 +1,226 @@
+/*  Copyright (C) 2021-2024 Aidan Crane, Andreas Shimokawa, Daniel Dakhno,
+    Daniele Gobbetti, jhey, Jos茅 Rebelo, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.miband6;
+
+import android.content.Context;
+import android.net.Uri;
+
+import androidx.annotation.NonNull;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettings;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsScreen;
+import nodomain.freeyourgadget.gadgetbridge.capabilities.HeartRateCapability;
+import nodomain.freeyourgadget.gadgetbridge.capabilities.password.PasswordCapabilityImpl;
+import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.miband6.MiBand6Support;
+
+public class MiBand6Coordinator extends HuamiCoordinator {
+    @Override
+    public String getManufacturer() {
+        // Actual manufacturer is Huami
+        return "Xiaomi";
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile(HuamiConst.MI_BAND6_NAME, Pattern.CASE_INSENSITIVE);
+    }
+
+    @Override
+    public InstallHandler findInstallHandler(final Uri uri, final Context context) {
+        final MiBand6FWInstallHandler handler = new MiBand6FWInstallHandler(uri, context);
+        return handler.isValid() ? handler : null;
+    }
+
+    @Override
+    public boolean supportsHeartRateMeasurement(final GBDevice device) {
+        return true;
+    }
+
+    @Override
+    public boolean supportsWeather() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsUnicodeEmojis() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsActivityTracks() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsMusicInfo() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsPai() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsStressMeasurement() {
+        return true;
+    }
+
+    @Override
+    public int getWorldClocksSlotCount() {
+        return 20; // as enforced by Mi Fit
+    }
+
+    @Override
+    public int getWorldClocksLabelLength() {
+        return 30; // at least
+    }
+
+    @Override
+    public DeviceSpecificSettings getDeviceSpecificSettings(final GBDevice device) {
+        final DeviceSpecificSettings deviceSpecificSettings = new DeviceSpecificSettings();
+
+        final List<Integer> generic = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.GENERIC);
+        generic.add(R.xml.devicesettings_wearlocation);
+        final List<Integer> dateTime = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DATE_TIME);
+        dateTime.add(R.xml.devicesettings_timeformat);
+        dateTime.add(R.xml.devicesettings_dateformat);
+        dateTime.add(R.xml.devicesettings_world_clocks);
+        final List<Integer> display = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DISPLAY);
+        display.add(R.xml.devicesettings_miband6);
+        display.add(R.xml.devicesettings_nightmode);
+        display.add(R.xml.devicesettings_liftwrist_display_sensitivity);
+        display.add(R.xml.devicesettings_swipeunlock);
+        display.add(R.xml.devicesettings_password);
+        final List<Integer> health = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.HEALTH);
+        health.add(R.xml.devicesettings_heartrate_sleep_alert_activity_stress);
+        health.add(R.xml.devicesettings_inactivity_dnd);
+        health.add(R.xml.devicesettings_goal_notification);
+        final List<Integer> workout = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.WORKOUT);
+        workout.add(R.xml.devicesettings_workout_start_on_phone);
+        workout.add(R.xml.devicesettings_workout_send_gps_to_band);
+        final List<Integer> notifications = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.NOTIFICATIONS);
+        notifications.add(R.xml.devicesettings_custom_emoji_font);
+        notifications.add(R.xml.devicesettings_vibrationpatterns);
+        notifications.add(R.xml.devicesettings_phone_silent_mode);
+        notifications.add(R.xml.devicesettings_transliteration);
+        final List<Integer> calendar = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.CALENDAR);
+        calendar.add(R.xml.devicesettings_sync_calendar);
+        calendar.add(R.xml.devicesettings_reserve_reminders_calendar);
+        final List<Integer> connection = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.CONNECTION);
+        connection.add(R.xml.devicesettings_expose_hr_thirdparty);
+        connection.add(R.xml.devicesettings_bt_connected_advertisement);
+        connection.add(R.xml.devicesettings_high_mtu);
+        connection.add(R.xml.devicesettings_overwrite_settings_on_connection);
+        final List<Integer> developer = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DEVELOPER);
+        developer.add(R.xml.devicesettings_huami2021_fetch_operation_time_unit);
+
+        return deviceSpecificSettings;
+    }
+
+    @Override
+    public int[] getSupportedDeviceSpecificAuthenticationSettings() {
+        return new int[]{
+                R.xml.devicesettings_pairingkey,
+                R.xml.devicesettings_miband6_new_protocol,
+                R.xml.devicesettings_miband6_new_auth_protocol_explanation,
+        };
+    }
+
+    @Override
+    public String[] getSupportedLanguageSettings(final GBDevice device) {
+        return new String[]{
+                "auto",
+                "ar_SA",
+                "cs_CZ",
+                "de_DE",
+                "el_GR",
+                "en_US",
+                "es_ES",
+                "fr_FR",
+                "he_IL",
+                "id_ID",
+                "it_IT",
+                "nl_NL",
+                "pt_PT",
+                "pl_PL",
+                "ro_RO",
+                "ru_RU",
+                "th_TH",
+                "tr_TR",
+                "uk_UA",
+                "vi_VN",
+                "zh_CN",
+                "zh_TW",
+        };
+    }
+
+    @Override
+    public int getBondingStyle() {
+        return BONDING_STYLE_REQUIRE_KEY;
+    }
+
+    @Override
+    public PasswordCapabilityImpl.Mode getPasswordCapability() {
+        return PasswordCapabilityImpl.Mode.NUMBERS_6;
+    }
+
+    @Override
+    public List<HeartRateCapability.MeasurementInterval> getHeartRateMeasurementIntervals() {
+        return Arrays.asList(
+                HeartRateCapability.MeasurementInterval.OFF,
+                HeartRateCapability.MeasurementInterval.MINUTES_1,
+                HeartRateCapability.MeasurementInterval.MINUTES_5,
+                HeartRateCapability.MeasurementInterval.MINUTES_10,
+                HeartRateCapability.MeasurementInterval.MINUTES_30
+        );
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return MiBand6Support.class;
+    }
+
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_miband6;
+    }
+
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_miband6;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_miband6_disabled;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband6/MiBand6FWHelper.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband6/MiBand6FWHelper.java
new file mode 100644
index 0000000..8764614
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband6/MiBand6FWHelper.java
@@ -0,0 +1,40 @@
+/*  Copyright (C) 2021-2024 Andreas Shimokawa, jhey
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.miband6;
+
+import android.content.Context;
+import android.net.Uri;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.miband6.MiBand6FirmwareInfo;
+
+public class MiBand6FWHelper extends HuamiFWHelper {
+
+    public MiBand6FWHelper(final Uri uri, final Context context) throws IOException {
+        super(uri, context);
+    }
+
+    @Override
+    protected void determineFirmwareInfo(byte[] wholeFirmwareBytes) {
+        firmwareInfo = new MiBand6FirmwareInfo(wholeFirmwareBytes);
+        if (!firmwareInfo.isHeaderValid()) {
+            throw new IllegalArgumentException("Not a Mi Band 6 firmware");
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband6/MiBand6FWInstallHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband6/MiBand6FWInstallHandler.java
new file mode 100644
index 0000000..6bf497a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband6/MiBand6FWInstallHandler.java
@@ -0,0 +1,50 @@
+/*  Copyright (C) 2021-2024 jhey
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.miband6;
+
+import android.content.Context;
+import android.net.Uri;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.miband5.MiBand5FWHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.AbstractMiBandFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.AbstractMiBandFWInstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+
+class MiBand6FWInstallHandler extends AbstractMiBandFWInstallHandler {
+    MiBand6FWInstallHandler(final Uri uri, final Context context) {
+        super(uri, context);
+    }
+
+    @Override
+    protected String getFwUpgradeNotice() {
+        return mContext.getString(R.string.fw_upgrade_notice_miband6, helper.getHumanFirmwareVersion());
+    }
+
+    @Override
+    protected AbstractMiBandFWHelper createHelper(final Uri uri, final Context context) throws IOException {
+        return new MiBand6FWHelper(uri, context);
+    }
+
+    @Override
+    protected boolean isSupportedDeviceType(final GBDevice device) {
+        return device.getType() == DeviceType.MIBAND6;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband7/MiBand7Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband7/MiBand7Coordinator.java
new file mode 100644
index 0000000..3dc1446
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband7/MiBand7Coordinator.java
@@ -0,0 +1,87 @@
+/*  Copyright (C) 2022-2024 Daniel Dakhno, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.miband7;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.zeppos.ZeppOsCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class MiBand7Coordinator extends ZeppOsCoordinator {
+    @Override
+    public String getManufacturer() {
+        // Actual manufacturer is Huami
+        return "Xiaomi";
+    }
+
+    @Override
+    public String getDeviceBluetoothName() {
+        return HuamiConst.XIAOMI_SMART_BAND7_NAME;
+    }
+
+    @Override
+    public Set<Integer> getDeviceSources() {
+        return new HashSet<>(Arrays.asList(260, 262, 263, 264, 265));
+    }
+
+    @Override
+    protected Map<Integer, String> getCrcMap() {
+        return new HashMap<Integer, String>() {{
+            // firmware
+            put(26036, "1.20.3.1");
+            put(55449, "1.27.0.4");
+            put(14502, "2.0.0.2");
+            put(25658, "2.1.0.1");
+        }};
+    }
+
+    @Override
+    public boolean supportsAgpsUpdates() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsScreenshots(final GBDevice device) {
+        return false;
+    }
+
+    @Override
+    public boolean supportsBluetoothPhoneCalls(final GBDevice device) {
+        return false;
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_miband7;
+    }
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_miband6;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_miband6_disabled;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/zeppe/ZeppECoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/zeppe/ZeppECoordinator.java
new file mode 100644
index 0000000..4a2f6a3
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/zeppe/ZeppECoordinator.java
@@ -0,0 +1,143 @@
+/*  Copyright (C) 2021-2024 Andrew Watkins, Daniel Dakhno, Jos茅 Rebelo,
+    Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.zeppe;
+
+import android.content.Context;
+import android.net.Uri;
+
+import androidx.annotation.NonNull;
+
+import java.util.List;
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettings;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsScreen;
+import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppe.ZeppESupport;
+
+public class ZeppECoordinator extends HuamiCoordinator {
+    @Override
+    public String getManufacturer() {
+        // Actual manufacturer is Huami
+        return "Amazfit";
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("Zepp E", Pattern.CASE_INSENSITIVE);
+    }
+
+    @Override
+    public InstallHandler findInstallHandler(final Uri uri, final Context context) {
+        final ZeppEFWInstallHandler handler = new ZeppEFWInstallHandler(uri, context);
+        return handler.isValid() ? handler : null;
+    }
+
+    @Override
+    public int getBondingStyle() {
+        return BONDING_STYLE_REQUIRE_KEY;
+    }
+
+    @Override
+    public boolean supportsHeartRateMeasurement(final GBDevice device) {
+        return true;
+    }
+
+    @Override
+    public boolean supportsActivityTracks() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsWeather() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsMusicInfo() {
+        return true;
+    }
+
+    @Override
+    public DeviceSpecificSettings getDeviceSpecificSettings(final GBDevice device) {
+        final DeviceSpecificSettings deviceSpecificSettings = new DeviceSpecificSettings();
+
+        final List<Integer> generic = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.GENERIC);
+        generic.add(R.xml.devicesettings_wearlocation);
+        generic.add(R.xml.devicesettings_device_actions);
+        final List<Integer> dateTime = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DATE_TIME);
+        dateTime.add(R.xml.devicesettings_timeformat);
+        final List<Integer> display = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DISPLAY);
+        display.add(R.xml.devicesettings_amazfitgtsgtr);
+        display.add(R.xml.devicesettings_liftwrist_display);
+        final List<Integer> health = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.HEALTH);
+        health.add(R.xml.devicesettings_heartrate_sleep);
+        health.add(R.xml.devicesettings_inactivity_dnd);
+        health.add(R.xml.devicesettings_goal_notification);
+        final List<Integer> notifications = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.NOTIFICATIONS);
+        notifications.add(R.xml.devicesettings_vibrationpatterns);
+        notifications.add(R.xml.devicesettings_phone_silent_mode);
+        notifications.add(R.xml.devicesettings_transliteration);
+        final List<Integer> calendar = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.CALENDAR);
+        calendar.add(R.xml.devicesettings_sync_calendar);
+        //calendar.add(R.xml.devicesettings_reserve_reminders_calendar);
+        final List<Integer> connection = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.CONNECTION);
+        connection.add(R.xml.devicesettings_disconnectnotification);
+        connection.add(R.xml.devicesettings_expose_hr_thirdparty);
+        connection.add(R.xml.devicesettings_bt_connected_advertisement);
+        connection.add(R.xml.devicesettings_high_mtu);
+        connection.add(R.xml.devicesettings_overwrite_settings_on_connection);
+        final List<Integer> developer = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DEVELOPER);
+        developer.add(R.xml.devicesettings_huami2021_fetch_operation_time_unit);
+
+        return deviceSpecificSettings;
+    }
+
+    @Override
+    public String[] getSupportedLanguageSettings(final GBDevice device) {
+        return new String[]{
+                "auto",
+                "zh_CN",
+                "zh_TW",
+                "en_US",
+                "es_ES",
+                "de_DE",
+                "it_IT",
+                "fr_FR",
+                "pt_BR",
+                "tr_TR",
+                "cs_CZ",
+                "ru_RU",
+        };
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return ZeppESupport.class;
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_zepp_e;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/zeppe/ZeppEFWHelper.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/zeppe/ZeppEFWHelper.java
new file mode 100644
index 0000000..2c36c14
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/zeppe/ZeppEFWHelper.java
@@ -0,0 +1,40 @@
+/*  Copyright (C) 2021-2024 Andreas Shimokawa, Andrew Watkins
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.zeppe;
+
+import android.content.Context;
+import android.net.Uri;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppe.ZeppEFirmwareInfo;
+
+public class ZeppEFWHelper extends HuamiFWHelper {
+
+    public ZeppEFWHelper(final Uri uri, final Context context) throws IOException {
+        super(uri, context);
+    }
+
+    @Override
+    protected void determineFirmwareInfo(byte[] wholeFirmwareBytes) {
+        firmwareInfo = new ZeppEFirmwareInfo(wholeFirmwareBytes);
+        if (!firmwareInfo.isHeaderValid()) {
+            throw new IllegalArgumentException("Not a Zepp E firmware");
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/zeppe/ZeppEFWInstallHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/zeppe/ZeppEFWInstallHandler.java
new file mode 100644
index 0000000..78a1d5c
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/zeppe/ZeppEFWInstallHandler.java
@@ -0,0 +1,49 @@
+/*  Copyright (C) 2021-2024 Andrew Watkins
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.zeppe;
+
+import android.content.Context;
+import android.net.Uri;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.AbstractMiBandFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.AbstractMiBandFWInstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+
+class ZeppEFWInstallHandler extends AbstractMiBandFWInstallHandler {
+    ZeppEFWInstallHandler(final Uri uri, final Context context) {
+        super(uri, context);
+    }
+
+    @Override
+    protected String getFwUpgradeNotice() {
+        return mContext.getString(R.string.fw_upgrade_notice_amazfitgtr, helper.getHumanFirmwareVersion());
+    }
+
+    @Override
+    protected AbstractMiBandFWHelper createHelper(final Uri uri, final Context context) throws IOException {
+        return new ZeppEFWHelper(uri, context);
+    }
+
+    @Override
+    protected boolean isSupportedDeviceType(final GBDevice device) {
+        return device.getType() == DeviceType.ZEPP_E;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/zeppos/ZeppOsActivitySummaryParser.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/zeppos/ZeppOsActivitySummaryParser.java
new file mode 100644
index 0000000..fc6244e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/zeppos/ZeppOsActivitySummaryParser.java
@@ -0,0 +1,212 @@
+/*  Copyright (C) 2022-2024 Jos茅 Rebelo, Reiner Herrmann
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.zeppos;
+
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.*;
+
+import android.content.Context;
+
+import com.google.protobuf.InvalidProtocolBufferException;
+
+import org.apache.commons.lang3.ArrayUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.workouts.entries.ActivitySummaryProgressEntry;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiActivitySummaryParser;
+import nodomain.freeyourgadget.gadgetbridge.proto.HuamiProtos;
+import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.AbstractHuamiActivityDetailsParser;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsActivityDetailsParser;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsActivityType;
+
+public class ZeppOsActivitySummaryParser extends HuamiActivitySummaryParser {
+    private static final Logger LOG = LoggerFactory.getLogger(ZeppOsActivitySummaryParser.class);
+    private final Context context;
+
+    @Override
+    public AbstractHuamiActivityDetailsParser getDetailsParser(final BaseActivitySummary summary) {
+        return new ZeppOsActivityDetailsParser(summary);
+    }
+
+    public ZeppOsActivitySummaryParser(final Context context) {
+        this.context = context;
+    }
+
+    @Override
+    protected void parseBinaryData(final BaseActivitySummary summary, final Date startTime) {
+        final byte[] rawData = summary.getRawSummaryData();
+        if (rawData == null) {
+            return;
+        }
+
+        final int version = (rawData[0] & 0xff) | ((rawData[1] & 0xff) << 8);
+        if (version != 0x8000) {
+            LOG.warn("Unexpected binary data version {}, parsing might fail", version);
+        }
+
+        final byte[] protobufData = ArrayUtils.subarray(rawData, 2, rawData.length);
+        final HuamiProtos.WorkoutSummary summaryProto;
+        try {
+            summaryProto = HuamiProtos.WorkoutSummary.parseFrom(protobufData);
+        } catch (final InvalidProtocolBufferException e) {
+            LOG.error("Failed to parse summary protobuf data", e);
+            return;
+        }
+
+        if (summaryProto.hasType()) {
+            final ZeppOsActivityType activityType = ZeppOsActivityType
+                    .fromCode((byte) summaryProto.getType().getType());
+
+            final ActivityKind activityKind;
+            if (activityType != null) {
+                activityKind = activityType.toActivityKind();
+            } else {
+                LOG.warn("Unknown workout activity type code {}", String.format("0x%X", summaryProto.getType().getType()));
+                activityKind = ActivityKind.UNKNOWN;
+            }
+            summary.setActivityKind(activityKind.getCode());
+        }
+
+        if (summaryProto.hasTime()) {
+            int totalDuration = summaryProto.getTime().getTotalDuration();
+            summary.setEndTime(new Date(startTime.getTime() + totalDuration * 1000L));
+            summaryData.add(ACTIVE_SECONDS, summaryProto.getTime().getWorkoutDuration(), UNIT_SECONDS);
+            // TODO pause durations
+        }
+
+        if (summaryProto.hasLocation()) {
+            summary.setBaseLongitude(summaryProto.getLocation().getBaseLongitude());
+            summary.setBaseLatitude(summaryProto.getLocation().getBaseLatitude());
+            summary.setBaseAltitude(summaryProto.getLocation().getBaseAltitude() / 2);
+            // TODO: Min/Max Latitude/Longitude
+            summaryData.add(ALTITUDE_BASE, summaryProto.getLocation().getBaseAltitude() / 2f, UNIT_METERS);
+        }
+
+        if (summaryProto.hasHeartRate()) {
+            summaryData.add(HR_AVG, summaryProto.getHeartRate().getAvg(), UNIT_BPM);
+            summaryData.add(HR_MAX, summaryProto.getHeartRate().getMax(), UNIT_BPM);
+            summaryData.add(HR_MIN, summaryProto.getHeartRate().getMin(), UNIT_BPM);
+        }
+
+        if (summaryProto.hasSteps()) {
+            summaryData.add(CADENCE_MAX, summaryProto.getSteps().getMaxCadence() * 60, UNIT_SPM);
+            summaryData.add(CADENCE_AVG, summaryProto.getSteps().getAvgCadence() * 60, UNIT_SPM);
+            summaryData.add(STRIDE_AVG, summaryProto.getSteps().getAvgStride(), UNIT_CM);
+            summaryData.add(STEPS, summaryProto.getSteps().getSteps(), UNIT_STEPS);
+        }
+
+        if (summaryProto.hasDistance()) {
+            summaryData.add(DISTANCE_METERS, summaryProto.getDistance().getDistance(), UNIT_METERS);
+        }
+
+        if (summaryProto.hasPace()) {
+            summaryData.add(PACE_MAX, summaryProto.getPace().getBest(), UNIT_SECONDS_PER_M);
+            summaryData.add(PACE_AVG_SECONDS_KM, summaryProto.getPace().getAvg() * 1000, UNIT_SECONDS_PER_KM);
+        }
+
+        if (summaryProto.hasCalories()) {
+            summaryData.add(CALORIES_BURNT, summaryProto.getCalories().getCalories(), UNIT_KCAL);
+        }
+
+        if (summaryProto.hasHeartRateZones()) {
+            // TODO hr zones bpm?
+            if (summaryProto.getHeartRateZones().getZoneTimeCount() == 6) {
+                final double totalTime = summaryProto.getHeartRateZones().getZoneTimeList()
+                        .stream()
+                        .mapToInt(v -> v)
+                        .sum();
+
+                final List<String> zoneOrder = Arrays.asList(HR_ZONE_NA, HR_ZONE_WARM_UP, HR_ZONE_FAT_BURN, HR_ZONE_AEROBIC, HR_ZONE_ANAEROBIC, HR_ZONE_EXTREME);
+                final int[] zoneColors = new int[]{
+                        0,
+                        context.getResources().getColor(R.color.hr_zone_warm_up_color),
+                        context.getResources().getColor(R.color.hr_zone_easy_color),
+                        context.getResources().getColor(R.color.hr_zone_aerobic_color),
+                        context.getResources().getColor(R.color.hr_zone_threshold_color),
+                        context.getResources().getColor(R.color.hr_zone_maximum_color),
+                };
+                for (int i = 0; i < zoneOrder.size(); i++) {
+                    summaryData.add(
+                            zoneOrder.get(i),
+                            new ActivitySummaryProgressEntry(
+                                    summaryProto.getHeartRateZones().getZoneTime(i),
+                                    UNIT_SECONDS,
+                                    (int) ((100 * summaryProto.getHeartRateZones().getZoneTime(i)) / totalTime),
+                                    zoneColors[i]
+                            )
+                    );
+                }
+            } else {
+                LOG.warn("Unexpected number of HR zones {}", summaryProto.getHeartRateZones().getZoneTimeCount());
+            }
+        }
+
+        if (summaryProto.hasTrainingEffect()) {
+            summaryData.add(TRAINING_EFFECT_AEROBIC, summaryProto.getTrainingEffect().getAerobicTrainingEffect(), UNIT_NONE);
+            summaryData.add(TRAINING_EFFECT_ANAEROBIC, summaryProto.getTrainingEffect().getAnaerobicTrainingEffect(), UNIT_NONE);
+            summaryData.add(WORKOUT_LOAD, summaryProto.getTrainingEffect().getCurrentWorkoutLoad(), UNIT_NONE);
+            summaryData.add(MAXIMUM_OXYGEN_UPTAKE, summaryProto.getTrainingEffect().getMaximumOxygenUptake(), UNIT_ML_KG_MIN);
+        }
+
+        if (summaryProto.hasAltitude()) {
+            summaryData.add(ALTITUDE_MAX, summaryProto.getAltitude().getMaxAltitude() / 200f, UNIT_METERS);
+            summaryData.add(ALTITUDE_MIN, summaryProto.getAltitude().getMinAltitude() / 200f, UNIT_METERS);
+            summaryData.add(ALTITUDE_AVG, summaryProto.getAltitude().getAvgAltitude() / 200f, UNIT_METERS);
+            // TODO totalClimbing
+            summaryData.add(ELEVATION_GAIN, summaryProto.getAltitude().getElevationGain() / 100f, UNIT_METERS);
+            summaryData.add(ELEVATION_LOSS, summaryProto.getAltitude().getElevationLoss() / 100f, UNIT_METERS);
+        }
+
+        if (summaryProto.hasElevation()) {
+            summaryData.add(ASCENT_SECONDS, summaryProto.getElevation().getUphillTime(), UNIT_SECONDS);
+            summaryData.add(DESCENT_SECONDS, summaryProto.getElevation().getDownhillTime(), UNIT_SECONDS);
+        }
+
+        if (summaryProto.hasSwimmingData()) {
+            summaryData.add(LAPS, summaryProto.getSwimmingData().getLaps(), UNIT_LAPS);
+            switch (summaryProto.getSwimmingData().getLaneLengthUnit()) {
+                case 0:
+                    summaryData.add(LANE_LENGTH, summaryProto.getSwimmingData().getLaneLength(), UNIT_METERS);
+                    break;
+                case 1:
+                    summaryData.add(LANE_LENGTH, summaryProto.getSwimmingData().getLaneLength(), UNIT_YARD);
+                    break;
+            }
+            switch (summaryProto.getSwimmingData().getStyle()) {
+                // TODO i18n these
+                case 1:
+                    summaryData.add(SWIM_STYLE, "breaststroke");
+                    break;
+                case 2:
+                    summaryData.add(SWIM_STYLE, "freestyle");
+                    break;
+            }
+            summaryData.add(STROKES, summaryProto.getSwimmingData().getStrokes(), UNIT_STROKES);
+            summaryData.add(STROKE_RATE_AVG, summaryProto.getSwimmingData().getAvgStrokeRate(), UNIT_STROKES_PER_MINUTE);
+            summaryData.add(STROKE_RATE_MAX, summaryProto.getSwimmingData().getMaxStrokeRate(), UNIT_STROKES_PER_MINUTE);
+            summaryData.add(STROKE_DISTANCE_AVG, summaryProto.getSwimmingData().getAvgDps(), UNIT_CM);
+            summaryData.add(SWOLF_INDEX, summaryProto.getSwimmingData().getSwolf(), UNIT_NONE);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/zeppos/ZeppOsAgpsInstallHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/zeppos/ZeppOsAgpsInstallHandler.java
new file mode 100644
index 0000000..6349f6c
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/zeppos/ZeppOsAgpsInstallHandler.java
@@ -0,0 +1,138 @@
+/*  Copyright (C) 2023-2024 Daniel Dakhno, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.zeppos;
+
+import android.content.Context;
+import android.net.Uri;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.InstallActivity;
+import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.GenericItem;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.operations.ZeppOsAgpsFile;
+import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.UriHelper;
+
+public class ZeppOsAgpsInstallHandler implements InstallHandler {
+    private static final Logger LOG = LoggerFactory.getLogger(ZeppOsAgpsInstallHandler.class);
+
+    protected final Context mContext;
+    private ZeppOsAgpsFile file;
+
+    public ZeppOsAgpsInstallHandler(final Uri uri, final Context context) {
+        this.mContext = context;
+
+        final UriHelper uriHelper;
+        try {
+            uriHelper = UriHelper.get(uri, context);
+        } catch (final IOException e) {
+            LOG.error("Failed to get uri", e);
+            return;
+        }
+
+        try (InputStream in = new BufferedInputStream(uriHelper.openInputStream())) {
+            final byte[] rawBytes = FileUtils.readAll(in, 1024 * 1024); // 1MB, they're usually ~128KB
+            final ZeppOsAgpsFile agpsFile = new ZeppOsAgpsFile(rawBytes);
+            if (agpsFile.isValid()) {
+                this.file = agpsFile;
+            }
+        } catch (final Exception e) {
+            LOG.error("Failed to read file", e);
+        }
+    }
+
+    @Override
+    public boolean isValid() {
+        return file != null;
+    }
+
+    @Override
+    public void validateInstallation(final InstallActivity installActivity, final GBDevice device) {
+        if (device.isBusy()) {
+            installActivity.setInfoText(device.getBusyTask());
+            installActivity.setInstallEnabled(false);
+            return;
+        }
+
+        final DeviceCoordinator coordinator = device.getDeviceCoordinator();
+        if (!(coordinator instanceof ZeppOsCoordinator)) {
+            LOG.warn("Coordinator is not a ZeppOsCoordinator: {}", coordinator.getClass());
+            installActivity.setInfoText(mContext.getString(R.string.fwapp_install_device_not_supported));
+            installActivity.setInstallEnabled(false);
+            return;
+        }
+        final ZeppOsCoordinator zeppOsCoordinator = (ZeppOsCoordinator) coordinator;
+        if (!zeppOsCoordinator.supportsAgpsUpdates()) {
+            installActivity.setInfoText(mContext.getString(R.string.fwapp_install_device_not_supported));
+            installActivity.setInstallEnabled(false);
+            return;
+        }
+
+        if (!device.isInitialized()) {
+            installActivity.setInfoText(mContext.getString(R.string.fwapp_install_device_not_ready));
+            installActivity.setInstallEnabled(false);
+            return;
+        }
+
+        final GenericItem fwItem = createInstallItem(device);
+        fwItem.setIcon(coordinator.getDefaultIconResource());
+
+        if (file == null) {
+            fwItem.setDetails(mContext.getString(R.string.miband_fwinstaller_incompatible_version));
+            installActivity.setInfoText(mContext.getString(R.string.fwinstaller_firmware_not_compatible_to_device));
+            installActivity.setInstallEnabled(false);
+            return;
+        }
+
+        final StringBuilder builder = new StringBuilder();
+        final String agpsBundle = mContext.getString(R.string.kind_agps_bundle);
+        builder.append(mContext.getString(R.string.fw_upgrade_notice, agpsBundle));
+        builder.append("\n\n").append(mContext.getString(R.string.miband_firmware_unknown_warning));
+        fwItem.setDetails(mContext.getString(R.string.miband_fwinstaller_untested_version));
+        installActivity.setInfoText(builder.toString());
+        installActivity.setInstallItem(fwItem);
+        installActivity.setInstallEnabled(true);
+    }
+
+    @Override
+    public void onStartInstall(final GBDevice device) {
+    }
+
+    public ZeppOsAgpsFile getFile() {
+        return file;
+    }
+
+    private GenericItem createInstallItem(final GBDevice device) {
+        DeviceCoordinator coordinator = device.getDeviceCoordinator();
+        final String firmwareName = mContext.getString(
+                R.string.installhandler_firmware_name,
+                mContext.getString(coordinator.getDeviceNameResource()),
+                mContext.getString(R.string.kind_agps_bundle),
+                ""
+        );
+        return new GenericItem(firmwareName);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/zeppos/ZeppOsCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/zeppos/ZeppOsCoordinator.java
new file mode 100644
index 0000000..5fa92ea
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/zeppos/ZeppOsCoordinator.java
@@ -0,0 +1,629 @@
+/*  Copyright (C) 2022-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.zeppos;
+
+import android.app.Activity;
+import android.content.Context;
+import android.net.Uri;
+
+import androidx.annotation.NonNull;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.appmanager.AppManagerActivity;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsScreen;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettings;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer;
+import nodomain.freeyourgadget.gadgetbridge.capabilities.HeartRateCapability;
+import nodomain.freeyourgadget.gadgetbridge.capabilities.password.PasswordCapabilityImpl;
+import nodomain.freeyourgadget.gadgetbridge.GBException;
+import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.Vo2MaxSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.WorkoutVo2MaxSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiExtendedSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.AbstractActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.entities.HuamiExtendedActivitySampleDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.HuamiHeartRateManualSampleDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.HuamiHeartRateMaxSampleDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.HuamiHeartRateRestingSampleDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.HuamiPaiSampleDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.HuamiSleepRespiratoryRateSampleDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.HuamiSpo2SampleDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.HuamiStressSampleDao;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryParser;
+import nodomain.freeyourgadget.gadgetbridge.model.Vo2MaxSample;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsFwInstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsAlexaService;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsContactsService;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsLogsService;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsLoyaltyCardService;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsRemindersService;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsShortcutCardsService;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsConfigService;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiLanguageType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiVibrationPatternNotificationType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsPhoneService;
+import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
+import nodomain.freeyourgadget.gadgetbridge.devices.SleepAsAndroidFeature;
+
+public abstract class ZeppOsCoordinator extends HuamiCoordinator {
+    public abstract String getDeviceBluetoothName();
+
+    public abstract Set<Integer> getDeviceSources();
+
+    protected Map<Integer, String> getCrcMap() {
+        // A map from CRC16 to human-readable version for flashable files
+        return Collections.emptyMap();
+    }
+
+    @Override
+    public String getManufacturer() {
+        // Actual manufacturer is Huami
+        return "Amazfit";
+    }
+
+    @Override
+    protected final Pattern getSupportedDeviceName() {
+        // Most devices use the exact bluetooth name
+        // Some devices have a " XXXX" suffix with the last 4 digits of mac address (eg. Mi Band 7)
+        // *However*, some devices broadcast a 2nd bluetooth device with "-XXXX" suffix, which I believe
+        // is only used for calls, and Gadgetbridge can't use for pairing, but I was not yet able to
+        // fully confirm this, so we still recognize them.
+        return Pattern.compile("^" + getDeviceBluetoothName() + "([- ][A-Z0-9]{4})?$");
+    }
+
+    @NonNull
+    @Override
+    public final Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return ZeppOsSupport.class;
+    }
+
+    @Override
+    public InstallHandler findInstallHandler(final Uri uri, final Context context) {
+        if (supportsAgpsUpdates()) {
+            final ZeppOsAgpsInstallHandler agpsInstallHandler = new ZeppOsAgpsInstallHandler(uri, context);
+            if (agpsInstallHandler.isValid()) {
+                return agpsInstallHandler;
+            }
+        }
+
+        if (supportsGpxUploads()) {
+            final ZeppOsGpxRouteInstallHandler gpxRouteInstallHandler = new ZeppOsGpxRouteInstallHandler(uri, context);
+            if (gpxRouteInstallHandler.isValid()) {
+                return gpxRouteInstallHandler;
+            }
+        }
+
+        final ZeppOsFwInstallHandler handler = new ZeppOsFwInstallHandler(
+                uri,
+                context,
+                getDeviceBluetoothName(),
+                getDeviceSources()
+        );
+        return handler.isValid() ? handler : null;
+    }
+
+    @Override
+    public boolean supportsScreenshots(final GBDevice device) {
+        return true;
+    }
+
+    @Override
+    public boolean supportsHeartRateMeasurement(final GBDevice device) {
+        return true;
+    }
+
+    @Override
+    public boolean supportsManualHeartRateMeasurement(final GBDevice device) {
+        // TODO: It should be supported, but not yet properly implemented
+        return false;
+    }
+
+    @Override
+    public boolean supportsWeather() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsUnicodeEmojis() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsRemSleep() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsActivityTracks() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsStressMeasurement() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsSpo2(final GBDevice device) {
+        return true;
+    }
+
+    @Override
+    public boolean supportsVO2Max() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsVO2MaxRunning() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsHeartRateStats() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsPai() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsSleepRespiratoryRate() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsMusicInfo() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsSleepAsAndroid() {
+        return true;
+    }
+
+    @Override
+    public Set<SleepAsAndroidFeature> getSleepAsAndroidFeatures() {
+        return EnumSet.of(SleepAsAndroidFeature.ACCELEROMETER, SleepAsAndroidFeature.HEART_RATE, SleepAsAndroidFeature.ALARMS, SleepAsAndroidFeature.NOTIFICATIONS);
+    }
+
+    @Override
+    public int getWorldClocksSlotCount() {
+        return 20; // as enforced by Zepp
+    }
+
+    @Override
+    public int getWorldClocksLabelLength() {
+        return 30; // at least
+    }
+
+    @Override
+    public boolean supportsDisabledWorldClocks() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsAppsManagement(final GBDevice device) {
+        return experimentalFeatures(device);
+    }
+
+    @Override
+    public Class<? extends Activity> getAppsManagementActivity() {
+        return AppManagerActivity.class;
+    }
+
+    @Override
+    public File getAppCacheDir() throws IOException {
+        return new File(FileUtils.getExternalFilesDir(), "zepp-os-app-cache");
+    }
+
+    @Override
+    public String getAppCacheSortFilename() {
+        return "zepp-os-app-cache-order.txt";
+    }
+
+    @Override
+    public String getAppFileExtension() {
+        return ".zip";
+    }
+
+    @Override
+    public boolean supportsAppListFetching() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsCalendarEvents() {
+        return true;
+    }
+
+    @Override
+    protected void deleteDevice(@NonNull final GBDevice gbDevice,
+                                @NonNull final Device device,
+                                @NonNull final DaoSession session) throws GBException {
+        final Long deviceId = device.getId();
+
+        session.getHuamiExtendedActivitySampleDao().queryBuilder()
+                .where(HuamiExtendedActivitySampleDao.Properties.DeviceId.eq(deviceId))
+                .buildDelete().executeDeleteWithoutDetachingEntities();
+
+        session.getHuamiStressSampleDao().queryBuilder()
+                .where(HuamiStressSampleDao.Properties.DeviceId.eq(deviceId))
+                .buildDelete().executeDeleteWithoutDetachingEntities();
+
+        session.getHuamiSpo2SampleDao().queryBuilder()
+                .where(HuamiSpo2SampleDao.Properties.DeviceId.eq(deviceId))
+                .buildDelete().executeDeleteWithoutDetachingEntities();
+
+        session.getHuamiHeartRateManualSampleDao().queryBuilder()
+                .where(HuamiHeartRateManualSampleDao.Properties.DeviceId.eq(deviceId))
+                .buildDelete().executeDeleteWithoutDetachingEntities();
+
+        session.getHuamiHeartRateMaxSampleDao().queryBuilder()
+                .where(HuamiHeartRateMaxSampleDao.Properties.DeviceId.eq(deviceId))
+                .buildDelete().executeDeleteWithoutDetachingEntities();
+
+        session.getHuamiHeartRateRestingSampleDao().queryBuilder()
+                .where(HuamiHeartRateRestingSampleDao.Properties.DeviceId.eq(deviceId))
+                .buildDelete().executeDeleteWithoutDetachingEntities();
+
+        session.getHuamiPaiSampleDao().queryBuilder()
+                .where(HuamiPaiSampleDao.Properties.DeviceId.eq(deviceId))
+                .buildDelete().executeDeleteWithoutDetachingEntities();
+
+        session.getHuamiSleepRespiratoryRateSampleDao().queryBuilder()
+                .where(HuamiSleepRespiratoryRateSampleDao.Properties.DeviceId.eq(deviceId))
+                .buildDelete().executeDeleteWithoutDetachingEntities();
+    }
+
+    @Override
+    public SampleProvider<? extends AbstractActivitySample> getSampleProvider(final GBDevice device, final DaoSession session) {
+        return new HuamiExtendedSampleProvider(device, session);
+    }
+
+    @Override
+    public Vo2MaxSampleProvider<? extends Vo2MaxSample> getVo2MaxSampleProvider(final GBDevice device, final DaoSession session) {
+        return new WorkoutVo2MaxSampleProvider(device, session);
+    }
+
+    @Override
+    public ActivitySummaryParser getActivitySummaryParser(final GBDevice device, final Context context) {
+        return new ZeppOsActivitySummaryParser(context);
+    }
+
+    @Override
+    public boolean supportsAlarmSnoozing() {
+        // All alarms snooze by default, there doesn't seem to be a flag that disables it
+        return false;
+    }
+
+    @Override
+    public boolean supportsSmartWakeup(final GBDevice device, int position) {
+        return true;
+    }
+
+    @Override
+    public int getReminderSlotCount(final GBDevice device) {
+        return ZeppOsRemindersService.getSlotCount(getPrefs(device));
+    }
+
+    @Override
+    public int getCannedRepliesSlotCount(final GBDevice device) {
+        return 16;
+    }
+
+    @Override
+    public int getContactsSlotCount(final GBDevice device) {
+        return getPrefs(device).getInt(ZeppOsContactsService.PREF_CONTACTS_SLOT_COUNT, 0);
+    }
+
+    @Override
+    public String[] getSupportedLanguageSettings(final GBDevice device) {
+        // Return all known languages by default. Unsupported languages will be removed by Huami2021SettingsCustomizer
+        final List<String> allLanguages = new ArrayList<>(HuamiLanguageType.idLookup.keySet());
+        allLanguages.add(0, "auto");
+        return allLanguages.toArray(new String[0]);
+    }
+
+    @Override
+    public PasswordCapabilityImpl.Mode getPasswordCapability() {
+        return PasswordCapabilityImpl.Mode.NUMBERS_6;
+    }
+
+    @Override
+    public List<HeartRateCapability.MeasurementInterval> getHeartRateMeasurementIntervals() {
+        // Return all known by default. Unsupported will be removed by Huami2021SettingsCustomizer
+        return Arrays.asList(HeartRateCapability.MeasurementInterval.values());
+    }
+
+    /**
+     * Returns a superset of all settings supported by Zepp OS Devices. Unsupported settings are removed
+     * by {@link ZeppOsSettingsCustomizer}.
+     */
+    @Override
+    public DeviceSpecificSettings getDeviceSpecificSettings(final GBDevice device) {
+        final DeviceSpecificSettings deviceSpecificSettings = new DeviceSpecificSettings();
+
+        //
+        // Apps
+        // TODO: These should go somewhere else
+        //
+        if (ZeppOsLoyaltyCardService.isSupported(getPrefs(device))) {
+            deviceSpecificSettings.addRootScreen(R.xml.devicesettings_loyalty_cards);
+        }
+
+        //
+        // Time
+        //
+        final List<Integer> dateTime = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DATE_TIME);
+        //dateTime.add(R.xml.devicesettings_timeformat);
+        dateTime.add(R.xml.devicesettings_dateformat_2);
+        if (getWorldClocksSlotCount() > 0) {
+            dateTime.add(R.xml.devicesettings_world_clocks);
+        }
+
+        //
+        // Display
+        //
+        final List<Integer> display = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DISPLAY);
+        display.add(R.xml.devicesettings_huami2021_displayitems);
+        display.add(R.xml.devicesettings_huami2021_shortcuts);
+        if (supportsControlCenter()) {
+            display.add(R.xml.devicesettings_huami2021_control_center);
+        }
+        if (supportsShortcutCards(device)) {
+            display.add(R.xml.devicesettings_huami2021_shortcut_cards);
+        }
+        display.add(R.xml.devicesettings_nightmode);
+        display.add(R.xml.devicesettings_sleep_mode);
+        display.add(R.xml.devicesettings_liftwrist_display_sensitivity_with_smart);
+        display.add(R.xml.devicesettings_password);
+        display.add(R.xml.devicesettings_huami2021_watchface);
+        display.add(R.xml.devicesettings_always_on_display);
+        display.add(R.xml.devicesettings_screen_timeout);
+        if (supportsAutoBrightness(device)) {
+            display.add(R.xml.devicesettings_screen_brightness_withauto);
+        } else {
+            display.add(R.xml.devicesettings_screen_brightness);
+        }
+
+        //
+        // Health
+        //
+        deviceSpecificSettings.addRootScreen(R.xml.devicesettings_heartrate_sleep_alert_activity_stress_spo2);
+        deviceSpecificSettings.addRootScreen(R.xml.devicesettings_inactivity_dnd_no_threshold);
+        deviceSpecificSettings.addRootScreen(R.xml.devicesettings_goal_notification);
+
+        //
+        // Workout
+        //
+        final List<Integer> workout = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.WORKOUT);
+        if (hasGps(device)) {
+            workout.add(R.xml.devicesettings_gps_agps);
+        } else {
+            // If the device has GPS, it doesn't report workout start/end to the phone
+            workout.add(R.xml.devicesettings_workout_start_on_phone);
+            workout.add(R.xml.devicesettings_workout_send_gps_to_band);
+        }
+        workout.add(R.xml.devicesettings_workout_keep_screen_on);
+        workout.add(R.xml.devicesettings_workout_detection);
+
+        //
+        // Notifications
+        //
+        final List<Integer> notifications = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.CALLS_AND_NOTIFICATIONS);
+        if (supportsBluetoothPhoneCalls(device)) {
+            notifications.add(R.xml.devicesettings_phone_calls_watch_pair);
+        } else {
+            notifications.add(R.xml.devicesettings_display_caller);
+        }
+        notifications.add(R.xml.devicesettings_sound_and_vibration);
+        notifications.add(R.xml.devicesettings_vibrationpatterns);
+        notifications.add(R.xml.devicesettings_donotdisturb_withauto_and_always);
+        notifications.add(R.xml.devicesettings_send_app_notifications);
+        notifications.add(R.xml.devicesettings_screen_on_on_notifications);
+        notifications.add(R.xml.devicesettings_autoremove_notifications);
+        notifications.add(R.xml.devicesettings_canned_reply_16);
+        notifications.add(R.xml.devicesettings_transliteration);
+
+        //
+        // Calendar
+        //
+        deviceSpecificSettings.addRootScreen(
+                DeviceSpecificSettingsScreen.CALENDAR,
+                R.xml.devicesettings_sync_calendar
+        );
+
+        //
+        // Other
+        //
+        if (getContactsSlotCount(device) > 0) {
+            deviceSpecificSettings.addRootScreen(R.xml.devicesettings_contacts);
+        }
+        deviceSpecificSettings.addRootScreen(R.xml.devicesettings_offline_voice);
+        deviceSpecificSettings.addRootScreen(R.xml.devicesettings_device_actions_without_not_wear);
+        deviceSpecificSettings.addRootScreen(R.xml.devicesettings_phone_silent_mode);
+        deviceSpecificSettings.addRootScreen(R.xml.devicesettings_buttonactions_upper_long);
+        deviceSpecificSettings.addRootScreen(R.xml.devicesettings_buttonactions_lower_short);
+        deviceSpecificSettings.addRootScreen(R.xml.devicesettings_weardirection);
+        deviceSpecificSettings.addRootScreen(R.xml.devicesettings_camera_remote);
+        deviceSpecificSettings.addRootScreen(R.xml.devicesettings_morning_updates);
+
+        //
+        // Connection
+        //
+        final List<Integer> connection = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.CONNECTION);
+        connection.add(R.xml.devicesettings_expose_hr_thirdparty);
+        connection.add(R.xml.devicesettings_bt_connected_advertisement);
+        connection.add(R.xml.devicesettings_high_mtu);
+
+        //
+        // Developer
+        //
+        final List<Integer> developer = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DEVELOPER);
+        if (ZeppOsLogsService.isSupported(getPrefs(device))) {
+            developer.add(R.xml.devicesettings_app_logs_start_stop);
+        }
+        if (supportsAlexa(device)) {
+            developer.add(R.xml.devicesettings_huami2021_alexa);
+        }
+        if (supportsWifiHotspot(device)) {
+            developer.add(R.xml.devicesettings_wifi_hotspot);
+        }
+        if (supportsFtpServer(device)) {
+            developer.add(R.xml.devicesettings_ftp_server);
+        }
+        developer.add(R.xml.devicesettings_keep_activity_data_on_device);
+        developer.add(R.xml.devicesettings_huami2021_fetch_operation_time_unit);
+
+        return deviceSpecificSettings;
+    }
+
+    @Override
+    public List<HuamiVibrationPatternNotificationType> getVibrationPatternNotificationTypes(final GBDevice device) {
+        final List<HuamiVibrationPatternNotificationType> notificationTypes = new ArrayList<>(Arrays.asList(
+                HuamiVibrationPatternNotificationType.APP_ALERTS,
+                HuamiVibrationPatternNotificationType.INCOMING_CALL,
+                HuamiVibrationPatternNotificationType.INCOMING_SMS,
+                HuamiVibrationPatternNotificationType.GOAL_NOTIFICATION,
+                HuamiVibrationPatternNotificationType.ALARM,
+                HuamiVibrationPatternNotificationType.IDLE_ALERTS
+        ));
+
+        if (getReminderSlotCount(device) > 0) {
+            notificationTypes.add(HuamiVibrationPatternNotificationType.EVENT_REMINDER);
+        }
+
+        if (!supportsContinuousFindDevice()) {
+            notificationTypes.add(HuamiVibrationPatternNotificationType.FIND_BAND);
+        }
+
+        if (supportsToDoList()) {
+            notificationTypes.add(HuamiVibrationPatternNotificationType.SCHEDULE);
+            notificationTypes.add(HuamiVibrationPatternNotificationType.TODO_LIST);
+        }
+
+        return notificationTypes;
+    }
+
+    @Override
+    public DeviceSpecificSettingsCustomizer getDeviceSpecificSettingsCustomizer(final GBDevice device) {
+        return new ZeppOsSettingsCustomizer(device, getVibrationPatternNotificationTypes(device));
+    }
+
+    @Override
+    public int getBondingStyle() {
+        return BONDING_STYLE_REQUIRE_KEY;
+    }
+
+    public boolean supportsContinuousFindDevice() {
+        // TODO: Auto-detect continuous find device?
+        return false;
+    }
+
+    public boolean supportsAgpsUpdates() {
+        return true;
+    }
+
+    /**
+     * true for Zepp OS 2.0+, false for Zepp OS 1
+     */
+    public boolean sendAgpsAsFileTransfer() {
+        return true;
+    }
+
+    public boolean supportsGpxUploads() {
+        return false;
+    }
+
+    public boolean supportsControlCenter() {
+        // TODO: Auto-detect control center?
+        return false;
+    }
+
+    public boolean supportsToDoList() {
+        // TODO: Not yet implemented
+        // TODO: When implemented, query the capability like reminders
+        return false;
+    }
+
+    public boolean mainMenuHasMoreSection() {
+        // Devices that have a control center don't seem to have a "more" section in the main menu
+        return !supportsControlCenter();
+    }
+
+    public boolean supportsWifiHotspot(final GBDevice device) {
+        return false;
+    }
+
+    public boolean supportsFtpServer(final GBDevice device) {
+        return false;
+    }
+
+    public boolean hasGps(final GBDevice device) {
+        return supportsConfig(device, ZeppOsConfigService.ConfigArg.WORKOUT_GPS_PRESET);
+    }
+
+    public boolean supportsAutoBrightness(final GBDevice device) {
+        return supportsConfig(device, ZeppOsConfigService.ConfigArg.SCREEN_AUTO_BRIGHTNESS);
+    }
+
+    public boolean supportsBluetoothPhoneCalls(final GBDevice device) {
+        return ZeppOsPhoneService.isSupported(getPrefs(device));
+    }
+
+    public boolean supportsShortcutCards(final GBDevice device) {
+        return ZeppOsShortcutCardsService.isSupported(getPrefs(device));
+    }
+
+    public boolean supportsAlexa(final GBDevice device) {
+        return experimentalFeatures(device) && ZeppOsAlexaService.isSupported(getPrefs(device));
+    }
+
+    private boolean supportsConfig(final GBDevice device, final ZeppOsConfigService.ConfigArg config) {
+        return ZeppOsConfigService.deviceHasConfig(getPrefs(device), config);
+    }
+
+    public static boolean experimentalFeatures(final GBDevice device) {
+        return getPrefs(device).getBoolean("zepp_os_experimental_features", false);
+    }
+
+    @Override
+    public boolean validateAuthKey(final String authKey) {
+        final byte[] authKeyBytes = authKey.trim().getBytes();
+        return authKeyBytes.length == 32 || (authKey.trim().startsWith("0x") && authKeyBytes.length == 34);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/zeppos/ZeppOsFwHelper.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/zeppos/ZeppOsFwHelper.java
new file mode 100644
index 0000000..803d6a0
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/zeppos/ZeppOsFwHelper.java
@@ -0,0 +1,538 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.zeppos;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+
+import androidx.annotation.Nullable;
+
+import org.json.JSONArray;
+import org.json.JSONObject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.UUID;
+import java.util.zip.CRC32;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipException;
+import java.util.zip.ZipFile;
+
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.UIHHContainer;
+import nodomain.freeyourgadget.gadgetbridge.util.BitmapUtil;
+import nodomain.freeyourgadget.gadgetbridge.util.CheckSums;
+import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.GBZipFile;
+import nodomain.freeyourgadget.gadgetbridge.util.UriHelper;
+import nodomain.freeyourgadget.gadgetbridge.util.ZipFileException;
+
+public class ZeppOsFwHelper {
+    private static final Logger LOG = LoggerFactory.getLogger(ZeppOsFwHelper.class);
+
+    private final Uri uri;
+    private final Context context;
+    private final String deviceName;
+    private final Set<Integer> deviceSources;
+
+    private HuamiFirmwareType firmwareType = HuamiFirmwareType.INVALID;
+    private File file = null;
+    private int crc32;
+    private String version = "Unknown";
+    private GBDeviceApp gbDeviceApp = null;
+
+    public ZeppOsFwHelper(final Uri uri, final Context context, final String deviceName, final Set<Integer> deviceSources) {
+        this.uri = uri;
+        this.context = context;
+        this.deviceName = deviceName;
+        this.deviceSources = deviceSources;
+
+        processUri();
+    }
+
+    public HuamiFirmwareType getFirmwareType() {
+        return firmwareType;
+    }
+
+    public String getFirmwareVersion() {
+        return version;
+    }
+
+    public File getFile() {
+        if (file == null) {
+            throw new IllegalStateException("file is null");
+        }
+
+        return file;
+    }
+
+    public int getSize() {
+        if (file == null) {
+            throw new IllegalStateException("file is null");
+        }
+
+        return (int) file.length();
+    }
+
+    public int getCrc32() {
+        return crc32;
+    }
+
+    private void processUri() {
+        // Copy file to cache first
+        final File cacheDir = context.getCacheDir();
+        final File zpkCacheDir = new File(cacheDir, "zeppos");
+        zpkCacheDir.mkdir();
+
+        try {
+            file = File.createTempFile("fwhelper", "bin", context.getCacheDir());
+            file.deleteOnExit();
+        } catch (final IOException e) {
+            LOG.error("Failed to create temp file for zpk", e);
+            return;
+        }
+
+        final UriHelper uriHelper;
+        try {
+            uriHelper = UriHelper.get(uri, context);
+        } catch (final IOException e) {
+            LOG.error("Failed to get uri helper", e);
+            return;
+        }
+
+        final CRC32 crc = new CRC32();
+        try (FileOutputStream outputStream = new FileOutputStream(file);
+             InputStream inputStream = uriHelper.openInputStream()) {
+            final byte[] buffer = new byte[64 * 1024];
+            int len;
+            while ((len = inputStream.read(buffer)) != -1) {
+                outputStream.write(buffer, 0, len);
+                crc.update(buffer, 0, len);
+            }
+            crc32 = (int) crc.getValue();
+        } catch (final IOException e) {
+            LOG.error("Failed to write bytes to temporary file", e);
+            return;
+        }
+
+        final byte[] header = getHeader(file, 4);
+        if (header == null) {
+            return;
+        }
+
+        if (Arrays.equals(header, GBZipFile.ZIP_HEADER)) {
+            try (ZipFile zipFile = new ZipFile(file, java.util.zip.ZipFile.OPEN_READ)) {
+                processZipFile(zipFile);
+            } catch (final ZipException e) {
+                LOG.warn("{} is not a valid zip file", uri, e);
+            } catch (final IOException e) {
+                LOG.warn("Error while processing {}", uri, e);
+            }
+        } else if (Arrays.equals(header, UIHHContainer.UIHH_HEADER)) {
+            // FIXME: This should be refactored to avoid pulling the entire file to memory
+            // However, it's currently only used for agps updates, which are usually just ~140KB
+            try (InputStream in = new BufferedInputStream(uriHelper.openInputStream())) {
+                final byte[] fullFile = FileUtils.readAll(in, 32 * 1024 * 1024); // 32MB
+                processAsUihh(fullFile);
+            } catch (final IOException e) {
+                LOG.error("Failed to read full uihh from file", e);
+            }
+        }
+    }
+
+    private void processAsUihh(byte[] bytes) {
+        final UIHHContainer uihh = UIHHContainer.fromRawBytes(bytes);
+        if (uihh == null) {
+            LOG.warn("Invalid UIHH file");
+            return;
+        }
+
+        final Set<UIHHContainer.FileType> agpsEpoTypes = new HashSet<>();
+        UIHHContainer.FileEntry uihhFirmwareZipFile = null;
+        boolean hasChangelog = false;
+        for (final UIHHContainer.FileEntry file : uihh.getFiles()) {
+            switch (file.getType()) {
+                case FIRMWARE_ZIP:
+                    uihhFirmwareZipFile = file;
+                    continue;
+                case FIRMWARE_CHANGELOG:
+                    hasChangelog = true;
+                    continue;
+                case AGPS_EPO_GR_3:
+                case AGPS_EPO_GAL_7:
+                case AGPS_EPO_BDS_3:
+                    agpsEpoTypes.add(file.getType());
+                    continue;
+                default:
+                    LOG.warn("Unexpected file for {}", file.getType());
+            }
+        }
+
+        if (uihhFirmwareZipFile != null && hasChangelog) {
+            // UIHH firmware update
+            final GBZipFile zipFile = new GBZipFile(uihhFirmwareZipFile.getContent());
+            final byte[] firmwareBin;
+            try {
+                firmwareBin = zipFile.getFileFromZip("META/firmware.bin");
+            } catch (final ZipFileException e) {
+                LOG.error("Failed to read zip from UIHH", e);
+                return;
+            }
+
+            if (isCompatibleFirmwareBin(firmwareBin)) {
+                firmwareType = HuamiFirmwareType.FIRMWARE_UIHH_2021_ZIP_WITH_CHANGELOG;
+            }
+        } else if (agpsEpoTypes.size() == 3) {
+            // AGPS EPO update
+            firmwareType = HuamiFirmwareType.AGPS_UIHH;
+        }
+    }
+
+    private void processZipFile(final ZipFile zipFile) {
+        // Attempt to handle as a firmware
+        final byte[] firmwareBin = getFileFromZip(zipFile, "META/firmware.bin");
+        if (firmwareBin != null) {
+            if (isCompatibleFirmwareBin(firmwareBin)) {
+                firmwareType = HuamiFirmwareType.FIRMWARE;
+                final JSONObject fwInfoRoot = getJson(zipFile, "META/fw_info");
+                if (fwInfoRoot != null) {
+                    final JSONArray fwInfoArr = fwInfoRoot.optJSONArray("fw_info");
+                    if (fwInfoArr != null) {
+                        for (int i = 0; i < fwInfoArr.length(); i++) {
+                            final JSONObject fwInfo = fwInfoArr.optJSONObject(i);
+                            if (fwInfo == null) {
+                                continue;
+                            }
+
+                            if ("firmware".equals(fwInfo.optString("name"))) {
+                                version = fwInfo.optString("version");
+                                break;
+                            }
+                        }
+                    }
+                } else {
+                    version = getFirmwareVersion(firmwareBin);
+                }
+            } else {
+                firmwareType = HuamiFirmwareType.INVALID;
+            }
+
+            return;
+        }
+
+        // Attempt to handle as an app / watchface
+        final JSONObject appJson = getJson(zipFile, "app.json");
+        if (appJson != null) {
+            final int appId;
+            final String appName;
+            final String appVersion;
+            final String appType;
+            final String appCreator;
+            final String appIconPath;
+            final JSONObject appJsonApp;
+            try {
+                appJsonApp = appJson.getJSONObject("app");
+                appId = appJsonApp.getInt("appId");
+                appName = appJsonApp.getString("appName");
+                appVersion = appJsonApp.getJSONObject("version").getString("name");
+                appType = appJsonApp.getString("appType");
+                appCreator = appJsonApp.getString("vender");
+                appIconPath = appJsonApp.getString("icon");
+            } catch (final Exception e) {
+                LOG.error("Failed to get appType from app.json", e);
+                firmwareType = HuamiFirmwareType.INVALID;
+                return;
+            }
+
+            final GBDeviceApp.Type gbDeviceAppType;
+            switch (appType) {
+                case "watchface":
+                    firmwareType = HuamiFirmwareType.WATCHFACE;
+                    gbDeviceAppType = GBDeviceApp.Type.WATCHFACE;
+                    version = String.format("%s (watchface)", appName);
+                    break;
+                case "app":
+                    firmwareType = HuamiFirmwareType.APP;
+                    gbDeviceAppType = GBDeviceApp.Type.APP_GENERIC;
+                    version = String.format("%s (app)", appName);
+                    break;
+                default:
+                    LOG.warn("Unknown app type {}", appType);
+                    firmwareType = HuamiFirmwareType.INVALID;
+                    return;
+            }
+
+            Bitmap icon = null;
+            final byte[] iconBytes = getFileFromZip(zipFile, "assets/" + appIconPath);
+            if (iconBytes != null) {
+                if (BitmapUtil.isPng(iconBytes)) {
+                    icon = BitmapFactory.decodeByteArray(iconBytes, 0, iconBytes.length);
+                } else {
+                    icon = BitmapUtil.decodeTga(iconBytes);
+                }
+            }
+
+            gbDeviceApp = new GBDeviceApp(
+                    UUID.fromString(String.format("%08x-0000-0000-0000-000000000000", appId)),
+                    appName,
+                    appCreator,
+                    appVersion,
+                    gbDeviceAppType,
+                    icon
+            );
+
+            return;
+        }
+
+        // Attempt to handle as a zab file
+        final byte[] zpkBytes = handleZabPackage(zipFile);
+        if (zpkBytes != null) {
+            final File cacheDir = context.getCacheDir();
+            final File zpkCacheDir = new File(cacheDir, "zpk");
+            zpkCacheDir.mkdir();
+
+            final File zpkFile;
+            try {
+                zpkFile = File.createTempFile("zpk", "zip", context.getCacheDir());
+                zpkFile.deleteOnExit();
+            } catch (final IOException e) {
+                LOG.error("Failed to create temp file for zpk", e);
+                return;
+            }
+
+            try (FileOutputStream outputStream = new FileOutputStream(zpkFile)) {
+                outputStream.write(zpkBytes);
+            } catch (final IOException e) {
+                LOG.error("Failed to write zpk bytes to temporary file", e);
+                return;
+            }
+
+            try (ZipFile zpkZpkFile = new ZipFile(zpkFile, java.util.zip.ZipFile.OPEN_READ)) {
+                processZipFile(zpkZpkFile);
+            } catch (final ZipException e) {
+                LOG.warn("{} is not a valid zip file", uri, e);
+            } catch (final IOException e) {
+                LOG.warn("Error while processing {}", uri, e);
+            }
+
+            if (firmwareType != HuamiFirmwareType.INVALID) {
+                file = zpkFile;
+                crc32 = CheckSums.getCRC32(zpkBytes);
+            }
+        }
+    }
+
+    /**
+     * A zab package is a zip file with:
+     * - manifest.json
+     * - .sc (source code)
+     * - One or more zpk files
+     * <p>
+     * Right now, we only handle the first compatible zpk file that is supported by the connected device.
+     */
+    private byte[] handleZabPackage(final ZipFile zipFile) {
+        final JSONObject manifest = getJson(zipFile, "manifest.json");
+        if (manifest == null) {
+            return null;
+        }
+
+        final JSONArray zpks;
+        try {
+            zpks = manifest.getJSONArray("zpks");
+        } catch (final Exception e) {
+            LOG.error("Failed to get zpks from manifest.json", e);
+            return null;
+        }
+
+        // Iterate all zpks until a compatible one is found
+        for (int i = 0; i < zpks.length(); i++) {
+            try {
+                final JSONObject zpkEntry = zpks.getJSONObject(i);
+                final JSONArray platforms = zpkEntry.getJSONArray("platforms");
+
+                // Check if this zpk is compatible with the current device
+                for (int j = 0; j < platforms.length(); j++) {
+                    final JSONObject platform = platforms.getJSONObject(j);
+
+                    if (deviceSources.contains(platform.getInt("deviceSource"))) {
+                        // It's compatible with the device, fetch device.zip
+                        final String name = zpkEntry.getString("name");
+                        final byte[] zpkBytes = getFileFromZip(zipFile, name);
+                        if (!GBZipFile.isZipFile(zpkBytes)) {
+                            LOG.warn("bytes for {} not a zip file", name);
+                            continue;
+                        }
+                        final GBZipFile zpkFile = new GBZipFile(zpkBytes);
+                        final byte[] deviceZip = zpkFile.getFileFromZip("device.zip");
+                        if (!GBZipFile.isZipFile(zpkBytes)) {
+                            LOG.warn("bytes for device.zip of zpk {} not a zip file", name);
+                            continue;
+                        }
+
+                        return deviceZip;
+                    }
+                }
+            } catch (final Exception e) {
+                LOG.warn("Failed to parse zpk", e);
+            }
+        }
+
+        LOG.warn("No compatible zpk found in zab file");
+
+        return null;
+    }
+
+    @Nullable
+    public GBDeviceApp getAppInfo() {
+        return gbDeviceApp;
+    }
+
+    public boolean isValid() {
+        return firmwareType != HuamiFirmwareType.INVALID;
+    }
+
+    @Nullable
+    public Bitmap getPreview() {
+        if (gbDeviceApp != null) {
+            return gbDeviceApp.getPreviewImage();
+        }
+
+        return null;
+    }
+
+    private boolean isCompatibleFirmwareBin(final byte[] firmwareBin) {
+        if (firmwareBin == null) {
+            LOG.error("firmware bin is null");
+            return false;
+        }
+
+        if (!searchString(firmwareBin, deviceName)) {
+            LOG.warn("Failed to find {} in fwBytes", deviceName);
+            return false;
+        }
+
+        return true;
+    }
+
+    public static String getFirmwareVersion(final byte[] firmwareBin) {
+        int startIdx = 10;
+        int endIdx = -1;
+
+        for (int i = startIdx; i < startIdx + 20; i++) {
+            byte c = firmwareBin[i];
+
+            if (c == 0) {
+                endIdx = i;
+                break;
+            }
+
+            if (c != '.' && (c < '0' || c > '9')) {
+                // not a valid version character
+                break;
+            }
+        }
+
+        if (endIdx == -1) {
+            LOG.warn("Failed to find firmware version in expected offset");
+            return null;
+        }
+
+        return new String(Arrays.copyOfRange(firmwareBin, startIdx, endIdx));
+    }
+
+    @Nullable
+    private static JSONObject getJson(final ZipFile zipFile, final String path) {
+        final byte[] appJsonBin = getFileFromZip(zipFile, path);
+        if (appJsonBin == null) {
+            return null;
+        }
+
+        try {
+            final String appJsonString = new String(appJsonBin, StandardCharsets.UTF_8)
+                    // Remove UTF-8 BOM if present
+                    .replace("\uFEFF", "");
+            return new JSONObject(appJsonString);
+        } catch (final Exception e) {
+            LOG.error("Failed to parse " + path, e);
+        }
+
+        return null;
+    }
+
+    @Nullable
+    private static byte[] getFileFromZip(final ZipFile zipFile, final String path) {
+        try {
+            final ZipEntry entry = zipFile.getEntry(path);
+            if (entry == null) {
+                return null;
+            }
+            return GBZipFile.readAllBytes(zipFile.getInputStream(entry));
+        } catch (final IOException e) {
+            LOG.error("Failed to read " + path, e);
+            return null;
+        }
+    }
+
+    @Nullable
+    public static byte[] getHeader(final File file, final int bytes) {
+        final byte[] header = new byte[bytes];
+
+        try (InputStream is = new FileInputStream(file)) {
+            if (is.read(header) != header.length) {
+                LOG.warn("Read unexpected number of header bytes");
+                return null;
+            }
+        } catch (final IOException e) {
+            LOG.error("Error while reading header bytes", e);
+            return null;
+        }
+
+        return header;
+    }
+
+    public static boolean searchString(final byte[] fwBytes, final String str) {
+        final byte[] strBytes = (str + "\0").getBytes(StandardCharsets.UTF_8);
+
+        for (int i = 0; i < fwBytes.length - strBytes.length + 1; i++) {
+            boolean found = true;
+            for (int j = 0; j < strBytes.length; j++) {
+                if (fwBytes[i + j] != strBytes[j]) {
+                    found = false;
+                    break;
+                }
+            }
+            if (found) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/zeppos/ZeppOsGpxRouteInstallHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/zeppos/ZeppOsGpxRouteInstallHandler.java
new file mode 100644
index 0000000..a172156
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/zeppos/ZeppOsGpxRouteInstallHandler.java
@@ -0,0 +1,135 @@
+/*  Copyright (C) 2023-2024 Daniel Dakhno, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.zeppos;
+
+import android.content.Context;
+import android.net.Uri;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.InstallActivity;
+import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.GenericItem;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.operations.ZeppOsGpxRouteFile;
+import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.UriHelper;
+
+public class ZeppOsGpxRouteInstallHandler implements InstallHandler {
+    private static final Logger LOG = LoggerFactory.getLogger(ZeppOsGpxRouteInstallHandler.class);
+
+    protected final Context mContext;
+    private ZeppOsGpxRouteFile file;
+
+    public ZeppOsGpxRouteInstallHandler(final Uri uri, final Context context) {
+        this.mContext = context;
+
+        final UriHelper uriHelper;
+        try {
+            uriHelper = UriHelper.get(uri, context);
+        } catch (final IOException e) {
+            LOG.error("Failed to get uri", e);
+            return;
+        }
+        try (InputStream in = new BufferedInputStream(uriHelper.openInputStream())) {
+            final byte[] rawBytes = FileUtils.readAll(in, 1024 * 1024); // 1MB
+            final ZeppOsGpxRouteFile gpxFile = new ZeppOsGpxRouteFile(rawBytes);
+            if (gpxFile.isValid()) {
+                this.file = gpxFile;
+            }
+        } catch (final Exception e) {
+            LOG.error("Failed to read file", e);
+        }
+    }
+
+    @Override
+    public boolean isValid() {
+        return file != null;
+    }
+
+    @Override
+    public void validateInstallation(final InstallActivity installActivity, final GBDevice device) {
+        if (device.isBusy()) {
+            installActivity.setInfoText(device.getBusyTask());
+            installActivity.setInstallEnabled(false);
+            return;
+        }
+
+        final DeviceCoordinator coordinator = device.getDeviceCoordinator();
+        if (!(coordinator instanceof ZeppOsCoordinator)) {
+            LOG.warn("Coordinator is not a ZeppOsCoordinator: {}", coordinator.getClass());
+            installActivity.setInfoText(mContext.getString(R.string.fwapp_install_device_not_supported));
+            installActivity.setInstallEnabled(false);
+            return;
+        }
+        final ZeppOsCoordinator zeppOsCoordinator = (ZeppOsCoordinator) coordinator;
+        if (!zeppOsCoordinator.supportsGpxUploads()) {
+            installActivity.setInfoText(mContext.getString(R.string.fwapp_install_device_not_supported));
+            installActivity.setInstallEnabled(false);
+            return;
+        }
+
+        if (!device.isInitialized()) {
+            installActivity.setInfoText(mContext.getString(R.string.fwapp_install_device_not_ready));
+            installActivity.setInstallEnabled(false);
+            return;
+        }
+
+        final GenericItem fwItem = createInstallItem(device);
+        fwItem.setIcon(coordinator.getDefaultIconResource());
+
+        if (file == null) {
+            fwItem.setDetails(mContext.getString(R.string.miband_fwinstaller_incompatible_version));
+            installActivity.setInfoText(mContext.getString(R.string.fwinstaller_firmware_not_compatible_to_device));
+            installActivity.setInstallEnabled(false);
+            return;
+        }
+
+        final StringBuilder builder = new StringBuilder();
+        final String gpxRoute = mContext.getString(R.string.kind_gpx_route);
+        builder.append(mContext.getString(R.string.fw_upgrade_notice, gpxRoute));
+        installActivity.setInfoText(builder.toString());
+        installActivity.setInstallItem(fwItem);
+        installActivity.setInstallEnabled(true);
+    }
+
+    @Override
+    public void onStartInstall(final GBDevice device) {
+    }
+
+    public ZeppOsGpxRouteFile getFile() {
+        return file;
+    }
+
+    private GenericItem createInstallItem(final GBDevice device) {
+        DeviceCoordinator coordinator = device.getDeviceCoordinator();
+        final String firmwareName = mContext.getString(
+                R.string.installhandler_firmware_name,
+                mContext.getString(coordinator.getDeviceNameResource()),
+                mContext.getString(R.string.kind_gpx_route),
+                file.getName()
+        );
+        return new GenericItem(firmwareName);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/zeppos/ZeppOsSettingsCustomizer.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/zeppos/ZeppOsSettingsCustomizer.java
new file mode 100644
index 0000000..df62ab7
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/zeppos/ZeppOsSettingsCustomizer.java
@@ -0,0 +1,457 @@
+/*  Copyright (C) 2022-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huami.zeppos;
+
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsUtils.hidePrefIfNoneVisible;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsUtils.populateOrHideListPreference;
+
+import android.os.Parcel;
+
+import androidx.preference.EditTextPreference;
+import androidx.preference.ListPreference;
+import androidx.preference.Preference;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsUtils;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsHandler;
+import nodomain.freeyourgadget.gadgetbridge.activities.loyaltycards.LoyaltyCardsSettingsConst;
+import nodomain.freeyourgadget.gadgetbridge.capabilities.GpsCapability;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiSettingsCustomizer;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiVibrationPatternNotificationType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsConfigService;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+public class ZeppOsSettingsCustomizer extends HuamiSettingsCustomizer {
+    private static final Logger LOG = LoggerFactory.getLogger(ZeppOsSettingsCustomizer.class);
+
+    public ZeppOsSettingsCustomizer(final GBDevice device, final List<HuamiVibrationPatternNotificationType> vibrationPatternNotificationTypes) {
+        super(device, vibrationPatternNotificationTypes);
+    }
+
+    @Override
+    public void customizeSettings(final DeviceSpecificSettingsHandler handler, final Prefs prefs, final String rootKey) {
+        super.customizeSettings(handler, prefs, rootKey);
+
+        // These are not reported by the normal configs
+        populateOrHideListPreference(HuamiConst.PREF_DISPLAY_ITEMS_SORTABLE, handler, prefs);
+        populateOrHideListPreference(HuamiConst.PREF_SHORTCUTS_SORTABLE, handler, prefs);
+        populateOrHideListPreference(HuamiConst.PREF_CONTROL_CENTER_SORTABLE, handler, prefs);
+        populateOrHideListPreference(DeviceSettingsPreferenceConst.SHORTCUT_CARDS_SORTABLE, handler, prefs);
+        populateOrHideListPreference(DeviceSettingsPreferenceConst.PREF_WATCHFACE, handler, prefs);
+        populateOrHideListPreference(DeviceSettingsPreferenceConst.MORNING_UPDATES_CATEGORIES_SORTABLE, handler, prefs);
+        populateOrHideListPreference(DeviceSettingsPreferenceConst.PREF_VOICE_SERVICE_LANGUAGE, handler, prefs);
+
+        for (final ZeppOsConfigService.ConfigArg config : ZeppOsConfigService.ConfigArg.values()) {
+            if (config.getPrefKey() == null) {
+                continue;
+            }
+            final ZeppOsConfigService.ConfigType configType = config.getConfigType(null);
+            if (configType == null) {
+                // Should never happen
+                LOG.error("configType is null - this should never happen");
+                return;
+            }
+            switch (configType) {
+                case BYTE:
+                case BYTE_LIST:
+                case STRING_LIST:
+                    // For list preferences, remove the unsupported items
+                    populateOrHideListPreference(config.getPrefKey(), handler, prefs);
+                    break;
+                case SHORT:
+                case INT:
+                    hidePrefIfNoConfigSupported(handler, prefs, config.getPrefKey(), config.name());
+                    enforceMinMax(handler, prefs, config);
+                    break;
+                case BOOL:
+                case DATETIME_HH_MM:
+                case TIMESTAMP_MILLIS:
+                default:
+                    // For other preferences, just hide them if they were not reported as supported by the device
+                    hidePrefIfNoConfigSupported(handler, prefs, config.getPrefKey(), config.name());
+                    break;
+            }
+        }
+
+        // Hide all config groups that may not be mapped directly to a preference
+        final Map<String, List<String>> configScreens = new HashMap<String, List<String>>() {{
+            put(DeviceSettingsPreferenceConst.PREF_SCREEN_NIGHT_MODE, Arrays.asList(
+                    ZeppOsConfigService.ConfigArg.NIGHT_MODE_MODE.name(),
+                    ZeppOsConfigService.ConfigArg.NIGHT_MODE_SCHEDULED_START.name(),
+                    ZeppOsConfigService.ConfigArg.NIGHT_MODE_SCHEDULED_END.name()
+            ));
+            put(DeviceSettingsPreferenceConst.PREF_SCREEN_SLEEP_MODE, Arrays.asList(
+                    ZeppOsConfigService.ConfigArg.SLEEP_MODE_SLEEP_SCREEN.name(),
+                    ZeppOsConfigService.ConfigArg.SLEEP_MODE_SMART_ENABLE.name()
+            ));
+            put(DeviceSettingsPreferenceConst.PREF_SCREEN_LIFT_WRIST, Arrays.asList(
+                    ZeppOsConfigService.ConfigArg.LIFT_WRIST_MODE.name(),
+                    ZeppOsConfigService.ConfigArg.LIFT_WRIST_SCHEDULED_START.name(),
+                    ZeppOsConfigService.ConfigArg.LIFT_WRIST_SCHEDULED_END.name(),
+                    ZeppOsConfigService.ConfigArg.LIFT_WRIST_RESPONSE_SENSITIVITY.name()
+            ));
+            put(DeviceSettingsPreferenceConst.PREF_SCREEN_PASSWORD, Arrays.asList(
+                    ZeppOsConfigService.ConfigArg.PASSWORD_ENABLED.name(),
+                    ZeppOsConfigService.ConfigArg.PASSWORD_TEXT.name()
+            ));
+            put(DeviceSettingsPreferenceConst.PREF_SCREEN_ALWAYS_ON_DISPLAY, Arrays.asList(
+                    ZeppOsConfigService.ConfigArg.ALWAYS_ON_DISPLAY_MODE.name(),
+                    ZeppOsConfigService.ConfigArg.ALWAYS_ON_DISPLAY_SCHEDULED_START.name(),
+                    ZeppOsConfigService.ConfigArg.ALWAYS_ON_DISPLAY_SCHEDULED_END.name(),
+                    ZeppOsConfigService.ConfigArg.ALWAYS_ON_DISPLAY_FOLLOW_WATCHFACE.name(),
+                    ZeppOsConfigService.ConfigArg.ALWAYS_ON_DISPLAY_STYLE.name()
+            ));
+            put(DeviceSettingsPreferenceConst.PREF_SCREEN_AUTO_BRIGHTNESS, Arrays.asList(
+                    ZeppOsConfigService.ConfigArg.SCREEN_AUTO_BRIGHTNESS.name()
+            ));
+            put(DeviceSettingsPreferenceConst.PREF_SCREEN_HEARTRATE_MONITORING, Arrays.asList(
+                    ZeppOsConfigService.ConfigArg.HEART_RATE_ALL_DAY_MONITORING.name(),
+                    ZeppOsConfigService.ConfigArg.HEART_RATE_HIGH_ALERTS.name(),
+                    ZeppOsConfigService.ConfigArg.HEART_RATE_LOW_ALERTS.name(),
+                    ZeppOsConfigService.ConfigArg.HEART_RATE_ACTIVITY_MONITORING.name(),
+                    ZeppOsConfigService.ConfigArg.SLEEP_HIGH_ACCURACY_MONITORING.name(),
+                    ZeppOsConfigService.ConfigArg.SLEEP_BREATHING_QUALITY_MONITORING.name(),
+                    ZeppOsConfigService.ConfigArg.STRESS_MONITORING.name(),
+                    ZeppOsConfigService.ConfigArg.STRESS_RELAXATION_REMINDER.name(),
+                    ZeppOsConfigService.ConfigArg.SPO2_ALL_DAY_MONITORING.name(),
+                    ZeppOsConfigService.ConfigArg.SPO2_LOW_ALERT.name()
+            ));
+            put(DeviceSettingsPreferenceConst.PREF_SCREEN_INACTIVITY_EXTENDED, Arrays.asList(
+                    ZeppOsConfigService.ConfigArg.INACTIVITY_WARNINGS_ENABLED.name(),
+                    ZeppOsConfigService.ConfigArg.INACTIVITY_WARNINGS_SCHEDULED_START.name(),
+                    ZeppOsConfigService.ConfigArg.INACTIVITY_WARNINGS_SCHEDULED_END.name(),
+                    ZeppOsConfigService.ConfigArg.INACTIVITY_WARNINGS_DND_ENABLED.name(),
+                    ZeppOsConfigService.ConfigArg.INACTIVITY_WARNINGS_DND_SCHEDULED_START.name(),
+                    ZeppOsConfigService.ConfigArg.INACTIVITY_WARNINGS_DND_SCHEDULED_END.name()
+            ));
+            put(DeviceSettingsPreferenceConst.PREF_HEADER_GPS, Arrays.asList(
+                    ZeppOsConfigService.ConfigArg.WORKOUT_GPS_PRESET.name(),
+                    ZeppOsConfigService.ConfigArg.WORKOUT_GPS_BAND.name(),
+                    ZeppOsConfigService.ConfigArg.WORKOUT_GPS_COMBINATION.name(),
+                    ZeppOsConfigService.ConfigArg.WORKOUT_GPS_SATELLITE_SEARCH.name(),
+                    ZeppOsConfigService.ConfigArg.WORKOUT_AGPS_EXPIRY_REMINDER_ENABLED.name(),
+                    ZeppOsConfigService.ConfigArg.WORKOUT_AGPS_EXPIRY_REMINDER_TIME.name()
+            ));
+            put(DeviceSettingsPreferenceConst.PREF_SCREEN_SOUND_AND_VIBRATION, Arrays.asList(
+                    ZeppOsConfigService.ConfigArg.VOLUME.name(),
+                    ZeppOsConfigService.ConfigArg.CROWN_VIBRATION.name(),
+                    ZeppOsConfigService.ConfigArg.ALERT_TONE.name(),
+                    ZeppOsConfigService.ConfigArg.COVER_TO_MUTE.name(),
+                    ZeppOsConfigService.ConfigArg.VIBRATE_FOR_ALERT.name(),
+                    ZeppOsConfigService.ConfigArg.TEXT_TO_SPEECH.name()
+            ));
+            put(DeviceSettingsPreferenceConst.PREF_SCREEN_DO_NOT_DISTURB, Arrays.asList(
+                    ZeppOsConfigService.ConfigArg.DND_MODE.name(),
+                    ZeppOsConfigService.ConfigArg.DND_SCHEDULED_START.name(),
+                    ZeppOsConfigService.ConfigArg.DND_SCHEDULED_END.name()
+            ));
+            put(DeviceSettingsPreferenceConst.PREF_HEADER_WORKOUT_DETECTION, Arrays.asList(
+                    ZeppOsConfigService.ConfigArg.WORKOUT_DETECTION_CATEGORY.name(),
+                    ZeppOsConfigService.ConfigArg.WORKOUT_DETECTION_ALERT.name(),
+                    ZeppOsConfigService.ConfigArg.WORKOUT_DETECTION_SENSITIVITY.name()
+            ));
+            put(DeviceSettingsPreferenceConst.PREF_SCREEN_OFFLINE_VOICE, Arrays.asList(
+                    ZeppOsConfigService.ConfigArg.OFFLINE_VOICE_RESPOND_TURN_WRIST.name(),
+                    ZeppOsConfigService.ConfigArg.OFFLINE_VOICE_RESPOND_SCREEN_ON.name(),
+                    ZeppOsConfigService.ConfigArg.OFFLINE_VOICE_RESPONSE_DURING_SCREEN_LIGHTING.name(),
+                    ZeppOsConfigService.ConfigArg.OFFLINE_VOICE_LANGUAGE.name()
+            ));
+            put(DeviceSettingsPreferenceConst.PREF_SCREEN_MORNING_UPDATES, Arrays.asList(
+                    DeviceSettingsPreferenceConst.MORNING_UPDATES_ENABLED,
+                    DeviceSettingsPreferenceConst.MORNING_UPDATES_CATEGORIES_SORTABLE
+            ));
+        }};
+
+        for (final Map.Entry<String, List<String>> configScreen : configScreens.entrySet()) {
+            hidePrefIfNoConfigSupported(
+                    handler,
+                    prefs,
+                    configScreen.getKey(),
+                    configScreen.getValue().toArray(new String[0])
+            );
+        }
+
+        // Hides the headers if none of the preferences under them are available
+        hidePrefIfNoneVisible(handler, DeviceSettingsPreferenceConst.PREF_HEADER_APPS, Arrays.asList(
+                LoyaltyCardsSettingsConst.PREF_KEY_LOYALTY_CARDS
+        ));
+        hidePrefIfNoneVisible(handler, DeviceSettingsPreferenceConst.PREF_HEADER_TIME, Arrays.asList(
+                DeviceSettingsPreferenceConst.PREF_TIMEFORMAT,
+                DeviceSettingsPreferenceConst.PREF_DATEFORMAT,
+                DeviceSettingsPreferenceConst.PREF_WORLD_CLOCKS
+        ));
+        hidePrefIfNoneVisible(handler, DeviceSettingsPreferenceConst.PREF_HEADER_DISPLAY, Arrays.asList(
+                HuamiConst.PREF_DISPLAY_ITEMS_SORTABLE,
+                HuamiConst.PREF_SHORTCUTS_SORTABLE,
+                HuamiConst.PREF_CONTROL_CENTER_SORTABLE,
+                DeviceSettingsPreferenceConst.PREF_SCREEN_NIGHT_MODE,
+                DeviceSettingsPreferenceConst.PREF_SCREEN_SLEEP_MODE,
+                DeviceSettingsPreferenceConst.PREF_SCREEN_LIFT_WRIST,
+                DeviceSettingsPreferenceConst.PREF_SCREEN_PASSWORD,
+                DeviceSettingsPreferenceConst.PREF_SCREEN_ALWAYS_ON_DISPLAY,
+                DeviceSettingsPreferenceConst.PREF_SCREEN_TIMEOUT,
+                DeviceSettingsPreferenceConst.PREF_SCREEN_AUTO_BRIGHTNESS,
+                DeviceSettingsPreferenceConst.PREF_SCREEN_BRIGHTNESS
+        ));
+        hidePrefIfNoneVisible(handler, DeviceSettingsPreferenceConst.PREF_HEADER_HEALTH, Arrays.asList(
+                DeviceSettingsPreferenceConst.PREF_SCREEN_HEARTRATE_MONITORING,
+                DeviceSettingsPreferenceConst.PREF_SCREEN_INACTIVITY_EXTENDED,
+                DeviceSettingsPreferenceConst.PREF_USER_FITNESS_GOAL_NOTIFICATION
+        ));
+        hidePrefIfNoneVisible(handler, DeviceSettingsPreferenceConst.PREF_HEADER_WORKOUT, Arrays.asList(
+                DeviceSettingsPreferenceConst.PREF_HEADER_GPS,
+                DeviceSettingsPreferenceConst.PREF_WORKOUT_START_ON_PHONE,
+                DeviceSettingsPreferenceConst.PREF_WORKOUT_SEND_GPS_TO_BAND,
+                DeviceSettingsPreferenceConst.PREF_HEADER_WORKOUT_DETECTION,
+                DeviceSettingsPreferenceConst.PREF_WORKOUT_KEEP_SCREEN_ON
+        ));
+        hidePrefIfNoneVisible(handler, DeviceSettingsPreferenceConst.PREF_HEADER_AGPS, Arrays.asList(
+                DeviceSettingsPreferenceConst.PREF_AGPS_EXPIRY_REMINDER_ENABLED,
+                DeviceSettingsPreferenceConst.PREF_AGPS_EXPIRY_REMINDER_TIME,
+                DeviceSettingsPreferenceConst.PREF_AGPS_UPDATE_TIME,
+                DeviceSettingsPreferenceConst.PREF_AGPS_EXPIRE_TIME
+        ));
+
+        setupGpsPreference(handler, prefs);
+        setupButtonClickPreferences(handler);
+    }
+
+    @Override
+    public Set<String> getPreferenceKeysWithSummary() {
+        final Set<String> preferenceKeysWithSummary = super.getPreferenceKeysWithSummary();
+
+        preferenceKeysWithSummary.add(DeviceSettingsPreferenceConst.WIFI_HOTSPOT_SSID);
+        preferenceKeysWithSummary.add(DeviceSettingsPreferenceConst.WIFI_HOTSPOT_PASSWORD);
+        preferenceKeysWithSummary.add(DeviceSettingsPreferenceConst.WIFI_HOTSPOT_STATUS);
+
+        preferenceKeysWithSummary.add(DeviceSettingsPreferenceConst.FTP_SERVER_ROOT_DIR);
+        preferenceKeysWithSummary.add(DeviceSettingsPreferenceConst.FTP_SERVER_ADDRESS);
+        preferenceKeysWithSummary.add(DeviceSettingsPreferenceConst.FTP_SERVER_USERNAME);
+        preferenceKeysWithSummary.add(DeviceSettingsPreferenceConst.FTP_SERVER_STATUS);
+
+        return preferenceKeysWithSummary;
+    }
+
+    private void setupGpsPreference(final DeviceSpecificSettingsHandler handler, final Prefs prefs) {
+        final ListPreference prefGpsPreset = handler.findPreference(DeviceSettingsPreferenceConst.PREF_GPS_MODE_PRESET);
+        final ListPreference prefGpsBand = handler.findPreference(DeviceSettingsPreferenceConst.PREF_GPS_BAND);
+        final ListPreference prefGpsCombination = handler.findPreference(DeviceSettingsPreferenceConst.PREF_GPS_COMBINATION);
+        final ListPreference prefGpsSatelliteSearch = handler.findPreference(DeviceSettingsPreferenceConst.PREF_GPS_SATELLITE_SEARCH);
+
+        if (prefGpsPreset != null) {
+            // When the preset preference is changed, update the band, combination and satellite search to the corresponding values
+            final Preference.OnPreferenceChangeListener onGpsPresetUpdated = (preference, newVal) -> {
+                final boolean isCustomPreset = GpsCapability.Preset.CUSTOM.name().toLowerCase(Locale.ROOT).equals(newVal);
+                final GpsCapability.Preset preset = GpsCapability.Preset.valueOf(newVal.toString().toUpperCase(Locale.ROOT));
+                final GpsCapability.Band presetBand;
+                final GpsCapability.Combination presetCombination;
+                final GpsCapability.SatelliteSearch presetSatelliteSearch;
+                switch (preset) {
+                    case ACCURACY:
+                        presetBand = GpsCapability.Band.DUAL_BAND;
+                        presetCombination = GpsCapability.Combination.ALL_SATELLITES;
+                        presetSatelliteSearch = GpsCapability.SatelliteSearch.ACCURACY_FIRST;
+                        break;
+                    case BALANCED:
+                        presetBand = GpsCapability.Band.SINGLE_BAND;
+                        presetCombination = GpsCapability.Combination.GPS_BDS;
+                        presetSatelliteSearch = GpsCapability.SatelliteSearch.ACCURACY_FIRST;
+                        break;
+                    case POWER_SAVING:
+                        presetBand = GpsCapability.Band.SINGLE_BAND;
+                        presetCombination = GpsCapability.Combination.LOW_POWER_GPS;
+                        presetSatelliteSearch = GpsCapability.SatelliteSearch.SPEED_FIRST;
+                        break;
+                    default:
+                        presetBand = null;
+                        presetCombination = null;
+                        presetSatelliteSearch = null;
+                        break;
+                }
+
+                if (prefGpsBand != null) {
+                    prefGpsBand.setEnabled(isCustomPreset);
+                    if (!isCustomPreset && presetBand != null) {
+                        prefGpsBand.setValue(presetBand.name().toLowerCase(Locale.ROOT));
+                    }
+                }
+                if (prefGpsCombination != null) {
+                    prefGpsCombination.setEnabled(isCustomPreset);
+                    if (!isCustomPreset && presetBand != null) {
+                        prefGpsCombination.setValue(presetCombination.name().toLowerCase(Locale.ROOT));
+                    }
+                }
+                if (prefGpsSatelliteSearch != null) {
+                    prefGpsSatelliteSearch.setEnabled(isCustomPreset);
+                    if (!isCustomPreset && presetBand != null) {
+                        prefGpsSatelliteSearch.setValue(presetSatelliteSearch.name().toLowerCase(Locale.ROOT));
+                    }
+                }
+
+                return true;
+            };
+
+            handler.addPreferenceHandlerFor(DeviceSettingsPreferenceConst.PREF_GPS_MODE_PRESET, onGpsPresetUpdated);
+            onGpsPresetUpdated.onPreferenceChange(prefGpsPreset, prefGpsPreset.getValue());
+        }
+
+        // The gps combination can only be chosen if the gps band is single band
+        if (prefGpsBand != null && prefGpsCombination != null) {
+            final Preference.OnPreferenceChangeListener onGpsBandUpdate = (preference, newVal) -> {
+                final boolean isSingleBand = GpsCapability.Band.SINGLE_BAND.name().toLowerCase(Locale.ROOT).equals(newVal);
+                prefGpsCombination.setEnabled(isSingleBand);
+                return true;
+            };
+
+            handler.addPreferenceHandlerFor(DeviceSettingsPreferenceConst.PREF_GPS_BAND, onGpsBandUpdate);
+            final boolean isCustomPreset = prefGpsPreset != null &&
+                    GpsCapability.Preset.CUSTOM.name().toLowerCase(Locale.ROOT).equals(prefGpsPreset.getValue());
+            if (isCustomPreset) {
+                onGpsBandUpdate.onPreferenceChange(prefGpsPreset, prefGpsBand.getValue());
+            }
+        }
+
+        final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault());
+
+        final Preference prefAgpsUpdateTime = handler.findPreference(DeviceSettingsPreferenceConst.PREF_AGPS_UPDATE_TIME);
+        if (prefAgpsUpdateTime != null) {
+            final long ts = prefs.getLong(DeviceSettingsPreferenceConst.PREF_AGPS_UPDATE_TIME, 0L);
+            if (ts > 0) {
+                prefAgpsUpdateTime.setSummary(sdf.format(new Date(ts)));
+            } else {
+                prefAgpsUpdateTime.setSummary(handler.getContext().getString(R.string.unknown));
+            }
+        }
+
+        final Preference prefAgpsExpireTime = handler.findPreference(DeviceSettingsPreferenceConst.PREF_AGPS_EXPIRE_TIME);
+        if (prefAgpsExpireTime != null) {
+            final long ts = prefs.getLong(DeviceSettingsPreferenceConst.PREF_AGPS_EXPIRE_TIME, 0L);
+            if (ts > 0) {
+                prefAgpsExpireTime.setSummary(sdf.format(new Date(ts)));
+            } else {
+                prefAgpsExpireTime.setSummary(handler.getContext().getString(R.string.unknown));
+            }
+        }
+    }
+
+    private void setupButtonClickPreferences(final DeviceSpecificSettingsHandler handler) {
+        // Notify preference changed on button click, so we can react to them
+        final List<Preference> wifiFtpButtons = Arrays.asList(
+                handler.findPreference(DeviceSettingsPreferenceConst.PREF_BLUETOOTH_CALLS_PAIR),
+                handler.findPreference(DeviceSettingsPreferenceConst.PREF_APP_LOGS_START),
+                handler.findPreference(DeviceSettingsPreferenceConst.PREF_APP_LOGS_STOP),
+                handler.findPreference(DeviceSettingsPreferenceConst.WIFI_HOTSPOT_START),
+                handler.findPreference(DeviceSettingsPreferenceConst.WIFI_HOTSPOT_STOP),
+                handler.findPreference(DeviceSettingsPreferenceConst.FTP_SERVER_START),
+                handler.findPreference(DeviceSettingsPreferenceConst.FTP_SERVER_STOP),
+                // TODO: These are temporary for debugging and will be removed
+                handler.findPreference("zepp_os_alexa_btn_trigger"),
+                handler.findPreference("zepp_os_alexa_btn_send_simple"),
+                handler.findPreference("zepp_os_alexa_btn_send_complex")
+        );
+
+        for (final Preference btn : wifiFtpButtons) {
+            if (btn != null) {
+                btn.setOnPreferenceClickListener(preference -> {
+                    handler.notifyPreferenceChanged(btn.getKey());
+                    return true;
+                });
+            }
+        }
+    }
+
+    /**
+     * Hides prefToHide if no configuration from the list has been reported by the band.
+     */
+    private void hidePrefIfNoConfigSupported(final DeviceSpecificSettingsHandler handler,
+                                             final Prefs prefs,
+                                             final String prefToHide,
+                                             final String... supportedPref) {
+        final Preference pref = handler.findPreference(prefToHide);
+        if (pref == null) {
+            return;
+        }
+
+        for (final String prefKey : supportedPref) {
+            final boolean deviceHasConfig = prefs.getBoolean(DeviceSettingsUtils.getPrefKnownConfig(prefKey), false);
+            if (deviceHasConfig) {
+                // This preference is supported, don't hide
+                return;
+            }
+        }
+
+        // None of the configs were supported by the device, hide this preference
+        pref.setVisible(false);
+    }
+
+    private void enforceMinMax(final DeviceSpecificSettingsHandler handler, final Prefs prefs, final ZeppOsConfigService.ConfigArg config) {
+        final String prefKey = config.getPrefKey();
+        final Preference pref = handler.findPreference(prefKey);
+        if (pref == null) {
+            return;
+        }
+
+        if (!(pref instanceof EditTextPreference)) {
+            return;
+        }
+
+        final int minValue = prefs.getInt(ZeppOsConfigService.getPrefMinKey(prefKey), Integer.MAX_VALUE);
+        if (minValue == Integer.MAX_VALUE) {
+            LOG.warn("Missing min value for {}", prefKey);
+            return;
+        }
+
+        final int maxValue = prefs.getInt(ZeppOsConfigService.getPrefMaxKey(prefKey), Integer.MIN_VALUE);
+        if (maxValue == Integer.MAX_VALUE) {
+            LOG.warn("Missing max value for {}", prefKey);
+            return;
+        }
+
+        DeviceSettingsUtils.enforceMinMax((EditTextPreference) pref, minValue, maxValue);
+    }
+
+    public static final Creator<ZeppOsSettingsCustomizer> CREATOR = new Creator<ZeppOsSettingsCustomizer>() {
+        @Override
+        public ZeppOsSettingsCustomizer createFromParcel(final Parcel in) {
+            final GBDevice device = in.readParcelable(ZeppOsSettingsCustomizer.class.getClassLoader());
+            final List<HuamiVibrationPatternNotificationType> vibrationPatternNotificationTypes = new ArrayList<>();
+            in.readList(vibrationPatternNotificationTypes, HuamiVibrationPatternNotificationType.class.getClassLoader());
+            return new ZeppOsSettingsCustomizer(device, vibrationPatternNotificationTypes);
+        }
+
+        @Override
+        public ZeppOsSettingsCustomizer[] newArray(final int size) {
+            return new ZeppOsSettingsCustomizer[size];
+        }
+    };
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HeartRateZonesConfig.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HeartRateZonesConfig.java
new file mode 100644
index 0000000..540c893
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HeartRateZonesConfig.java
@@ -0,0 +1,287 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.huawei;
+
+// TODO: make this configurable
+// TODO: retrieve from device and set real Rest HR
+// NOTE: algorithms used in this class are generic. So this data can be used with other devices.
+// We can move this class to global scope.
+public class HeartRateZonesConfig {
+
+    public static final int TYPE_UPRIGHT = 1;
+    public static final int TYPE_SITTING = 2;
+    public static final int TYPE_SWIMMING = 3;
+    public static final int TYPE_OTHER = 4;
+
+    public static final int CALCULATE_METHOD_MHR = 0;
+    public static final int CALCULATE_METHOD_HRR = 1;
+    public static final int CALCULATE_METHOD_LTHR = 3;
+
+
+    private static final int DEFAULT_REST_HEART_RATE = 60;
+    public static final int MAXIMUM_HEART_RATE = 220;
+
+    private final int configType;
+    private int calculateMethod = CALCULATE_METHOD_MHR; // 0 - MHR, 1 - HRR, 3 - LTHR
+
+    private int maxHRThreshold;
+    private int restHeartRate = DEFAULT_REST_HEART_RATE;
+
+    private boolean warningEnable;
+    private int warningHRLimit;
+
+    //MHR percentage
+    private int MHRExtreme;
+    private int MHRAnaerobic;
+    private int MHRAerobic;
+    private int MHRFatBurning;
+    private int MHRWarmUp;
+
+    //HRR percentage
+    private int HRRAdvancedAnaerobic;
+    private int HRRBasicAnaerobic;
+    private int HRRLactate;
+    private int HRRAdvancedAerobic;
+    private int HRRBasicAerobic;
+
+    //LTHR percentage
+    private int LTHRAnaerobic;
+    private int LTHRLactate;
+    private int LTHRAdvancedAerobic;
+    private int LTHRBasicAerobic;
+    private int LTHRWarmUp;
+
+
+    private int LTHRThresholdHeartRate;
+
+
+    public HeartRateZonesConfig(int type, int age) {
+        this.configType = type;
+        this.warningEnable = true;
+        this.warningHRLimit = MAXIMUM_HEART_RATE - age;
+        resetHRZones(age);
+    }
+
+    public void resetHRZones(int age) {
+        this.maxHRThreshold = (MAXIMUM_HEART_RATE - age) - getHRCorrection();
+        resetHRRZonesConfig();
+        resetMHRZonesConfig();
+        //NOTE: LTHR only supported for TYPE_UPRIGHT
+        if (this.configType == TYPE_UPRIGHT) {
+            resetLTHRZonesConfig();
+        }
+    }
+
+    private void resetLTHRZonesConfig() {
+        this.LTHRThresholdHeartRate = Math.round(((float) this.restHeartRate) + (((float) ((this.maxHRThreshold - this.restHeartRate) * 85)) / 100.0f));
+        this.LTHRAnaerobic = Math.round(((float) (this.LTHRThresholdHeartRate * 102)) / 100.0f);
+        this.LTHRLactate = Math.round(((float) (this.LTHRThresholdHeartRate * 97)) / 100.0f);
+        this.LTHRAdvancedAerobic = Math.round(((float) (this.LTHRThresholdHeartRate * 89)) / 100.0f);
+        this.LTHRBasicAerobic = Math.round(((float) (this.LTHRThresholdHeartRate * 80)) / 100.0f);
+        this.LTHRWarmUp = Math.round(((float) (this.LTHRThresholdHeartRate * 67)) / 100.0f);
+    }
+
+    private void resetMHRZonesConfig() {
+        this.MHRExtreme = Math.round(((float) (this.maxHRThreshold * 90)) / 100.0f);
+        this.MHRAnaerobic = Math.round(((float) (this.maxHRThreshold * 80)) / 100.0f);
+        this.MHRAerobic = Math.round(((float) (this.maxHRThreshold * 70)) / 100.0f);
+        this.MHRFatBurning = Math.round(((float) (this.maxHRThreshold * 60)) / 100.0f);
+        this.MHRWarmUp = Math.round(((float) (this.maxHRThreshold * 50)) / 100.0f);
+    }
+
+    private void resetHRRZonesConfig() {
+        int calcHR = this.maxHRThreshold - this.restHeartRate;
+        this.HRRAdvancedAnaerobic = Math.round(((float) (calcHR * 95)) / 100.0f) + this.restHeartRate;
+        this.HRRBasicAnaerobic = Math.round(((float) (calcHR * 88)) / 100.0f) + this.restHeartRate;
+        this.HRRLactate = Math.round(((float) (calcHR * 84)) / 100.0f) + this.restHeartRate;
+        this.HRRAdvancedAerobic = Math.round(((float) (calcHR * 74)) / 100.0f) + this.restHeartRate;
+        this.HRRBasicAerobic = Math.round(((float) (calcHR * 59)) / 100.0f) + this.restHeartRate;
+    }
+
+    //TODO: I am not sure about this. But it looks correct.
+    private int getHRCorrection() {
+        switch (this.configType) {
+            case TYPE_SITTING:
+                return 6;
+            case TYPE_SWIMMING:
+                return 10;
+            case TYPE_OTHER:
+                return 5;
+            default:
+                return 0;
+        }
+    }
+
+    public int getCalculateMethod() {
+        return this.calculateMethod;
+    }
+
+    public boolean getWarningEnable() {
+        return this.warningEnable;
+    }
+
+    public int getWarningHRLimit() {
+        return this.warningHRLimit;
+    }
+
+    public int getMaxHRThreshold() {
+        return this.maxHRThreshold;
+    }
+
+    public int getRestHeartRate() {
+        return this.restHeartRate;
+    }
+
+    public int getMHRWarmUp() {
+        return this.MHRWarmUp;
+    }
+
+    public int getMHRFatBurning() {
+        return this.MHRFatBurning;
+    }
+
+    public int getMHRAerobic() {
+        return this.MHRAerobic;
+    }
+
+    public int getMHRAnaerobic() {
+        return this.MHRAnaerobic;
+    }
+
+    public int getMHRExtreme() {
+        return this.MHRExtreme;
+    }
+
+    public int getHRRBasicAerobic() {
+        return this.HRRBasicAerobic;
+    }
+
+    public int getHRRAdvancedAerobic() {
+        return this.HRRAdvancedAerobic;
+    }
+
+    public int getHRRLactate() {
+        return this.HRRLactate;
+    }
+
+    public int getHRRBasicAnaerobic() {
+        return this.HRRBasicAnaerobic;
+    }
+
+    public int getHRRAdvancedAnaerobic() {
+        return this.HRRAdvancedAnaerobic;
+    }
+
+    public int getLTHRThresholdHeartRate() {
+        return this.LTHRThresholdHeartRate;
+    }
+
+    public int getLTHRAnaerobic() {
+        return this.LTHRAnaerobic;
+    }
+
+    public int getLTHRLactate() {
+        return this.LTHRLactate;
+    }
+
+    public int getLTHRAdvancedAerobic() {
+        return this.LTHRAdvancedAerobic;
+    }
+
+    public int getLTHRBasicAerobic() {
+        return this.LTHRBasicAerobic;
+    }
+
+    public int getLTHRWarmUp() {
+        return this.LTHRWarmUp;
+    }
+
+    private boolean checkValue(int val) {
+        return val >= 0 && val < MAXIMUM_HEART_RATE;
+    }
+
+    public boolean isValid() {
+        return checkValue(this.configType) &&
+                checkValue(this.calculateMethod) &&
+                checkValue(this.warningHRLimit) &&
+                checkValue(this.maxHRThreshold) &&
+                checkValue(this.restHeartRate) &&
+                checkValue(this.MHRWarmUp) &&
+                checkValue(this.MHRFatBurning) &&
+                checkValue(this.MHRAerobic) &&
+                checkValue(this.MHRAnaerobic) &&
+                checkValue(this.MHRExtreme) &&
+                checkValue(this.HRRBasicAerobic) &&
+                checkValue(this.HRRAdvancedAerobic) &&
+                checkValue(this.HRRLactate) &&
+                checkValue(this.HRRBasicAnaerobic) &&
+                checkValue(this.HRRAdvancedAnaerobic) &&
+                checkValue(this.LTHRThresholdHeartRate) &&
+                checkValue(this.LTHRAnaerobic) &&
+                checkValue(this.LTHRLactate) &&
+                checkValue(this.LTHRAdvancedAerobic) &&
+                checkValue(this.LTHRBasicAerobic) &&
+                checkValue(this.LTHRWarmUp) &&
+                this.warningHRLimit > 0;
+    }
+
+    public boolean hasValidMHRData() {
+        return MHRWarmUp > 0 && MHRFatBurning > 0 && MHRAerobic > 0 && MHRAnaerobic > 0 && MHRExtreme > 0 && maxHRThreshold > 0;
+    }
+
+    public boolean hasValidHRRData() {
+        return restHeartRate > 0 && HRRBasicAerobic > 0 && HRRAdvancedAerobic > 0 && HRRLactate > 0 && HRRBasicAnaerobic > 0 && HRRAdvancedAnaerobic > 0;
+    }
+
+    public boolean hasValidLTHRData() {
+        return LTHRThresholdHeartRate > 0 && LTHRAnaerobic > 0 && LTHRLactate > 0 && LTHRAdvancedAerobic > 0 && LTHRBasicAerobic > 0 && LTHRWarmUp > 0;
+    }
+
+    private int getZoneForHR(int heartRate, int zone5Threshold, int zone4Threshold, int zone3Threshold, int zone2Threshold, int zone1Threshold) {
+        if (heartRate >= MAXIMUM_HEART_RATE) {
+            return -1;
+        }
+        if (heartRate >= zone5Threshold) {
+            return 4;
+        }
+        if (heartRate >= zone4Threshold) {
+            return 3;
+        }
+        if (heartRate >= zone3Threshold) {
+            return 2;
+        }
+        if (heartRate >= zone2Threshold) {
+            return 1;
+        }
+        return heartRate >= zone1Threshold ? 0 : -1;
+    }
+
+    public int getMHRZone(int heartRate) {
+        return getZoneForHR(heartRate, MHRExtreme, MHRAnaerobic, MHRAerobic, MHRFatBurning, MHRWarmUp);
+    }
+
+    public int getHHRZone(int heartRate) {
+        return getZoneForHR(heartRate, HRRAdvancedAnaerobic, HRRBasicAnaerobic, HRRLactate, HRRAdvancedAerobic, HRRBasicAerobic);
+    }
+
+    public int getLTHRZone(int heartRate) {
+        return getZoneForHR(heartRate, LTHRAnaerobic, LTHRLactate, LTHRAdvancedAerobic, LTHRBasicAerobic, LTHRWarmUp);
+    }
+
+    public int getZoneByMethod(int heartRate, int method) {
+        if(method == CALCULATE_METHOD_LTHR) {
+            return getLTHRZone(heartRate);
+        } else if(method == CALCULATE_METHOD_MHR) {
+            return getMHRZone(heartRate);
+        }
+        return getHHRZone(heartRate);
+    }
+
+    public static boolean isCalculateMethodValidFroType(int type, int method) {
+        if(method == CALCULATE_METHOD_LTHR && type == TYPE_UPRIGHT) {
+            return true;
+        }
+        return (method == CALCULATE_METHOD_MHR) || (method == CALCULATE_METHOD_HRR);
+
+    }
+
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiBRCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiBRCoordinator.java
new file mode 100644
index 0000000..447e9a1
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiBRCoordinator.java
@@ -0,0 +1,260 @@
+/*  Copyright (C) 2024 Damien Gaignon, Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huawei;
+
+import android.app.Activity;
+import android.bluetooth.le.ScanFilter;
+import android.content.Context;
+import android.net.Uri;
+import android.os.ParcelUuid;
+
+import androidx.annotation.NonNull;
+
+import java.util.Collection;
+import java.util.Collections;
+
+import nodomain.freeyourgadget.gadgetbridge.GBException;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettings;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer;
+import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLClassicDeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.TimeSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.AbstractActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryParser;
+import nodomain.freeyourgadget.gadgetbridge.model.Spo2Sample;
+import nodomain.freeyourgadget.gadgetbridge.model.TemperatureSample;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiBRSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiWorkoutGbParser;
+
+public abstract class HuaweiBRCoordinator extends AbstractBLClassicDeviceCoordinator implements HuaweiCoordinatorSupplier {
+
+    private final HuaweiCoordinator huaweiCoordinator = new HuaweiCoordinator(this);
+    private GBDevice device;
+
+    @Override
+    public HuaweiCoordinator getHuaweiCoordinator() {
+        return huaweiCoordinator;
+    }
+
+    @NonNull
+    @Override
+    public Collection<? extends ScanFilter> createBLEScanFilters() {
+        ParcelUuid huaweiService = new ParcelUuid(HuaweiConstants.UUID_SERVICE_HUAWEI_SERVICE);
+        ScanFilter filter = new ScanFilter.Builder().setServiceUuid(huaweiService).build();
+        return Collections.singletonList(filter);
+    }
+
+    @Override
+    public DeviceSpecificSettingsCustomizer getDeviceSpecificSettingsCustomizer(final GBDevice device) {
+        return new HuaweiSettingsCustomizer(device);
+    }
+
+    @Override
+    public String[] getSupportedLanguageSettings(GBDevice device) {
+        return huaweiCoordinator.getSupportedLanguageSettings(device);
+    }
+
+    @Override
+    public int[] getSupportedDeviceSpecificAuthenticationSettings() {
+        return new int[]{R.xml.devicesettings_huawei_account};
+    }
+
+    @Override
+    protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
+        huaweiCoordinator.deleteDevice(gbDevice, device, session);
+    }
+
+    @Override
+    public String getManufacturer() {
+        return "Huawei";
+    }
+
+    @Override
+    public boolean supportsSmartWakeup(GBDevice device, int position) {
+        return huaweiCoordinator.supportsSmartAlarm(device, position);
+    }
+
+    @Override
+    public boolean supportsSmartWakeupInterval(GBDevice device, int alarmPosition) {
+        return supportsSmartWakeup(device, alarmPosition);
+    }
+
+    @Override
+    public boolean forcedSmartWakeup(GBDevice device, int alarmPosition) {
+        return huaweiCoordinator.forcedSmartWakeup(device, alarmPosition);
+    }
+
+    @Override
+    public boolean supportsAlarmTitle(GBDevice device) {
+        return true;
+    }
+
+    @Override
+    public boolean supportsWeather() {
+        return huaweiCoordinator.supportsWeather();
+    }
+
+    @Override
+    public Class<? extends Activity> getAppsManagementActivity() {
+        return huaweiCoordinator.getAppManagerActivity();
+    }
+
+    @Override
+    public boolean supportsAppListFetching() {
+        return huaweiCoordinator.getSupportsAppListFetching();
+    }
+    @Override
+    public boolean supportsAppsManagement(GBDevice device) {
+        return huaweiCoordinator.getSupportsAppsManagement(device);
+    }
+
+    @Override
+    public boolean supportsWatchfaceManagement(GBDevice device) {
+        return supportsAppsManagement(device);
+    }
+
+    @Override
+    public boolean supportsInstalledAppManagement(GBDevice device) {
+        return huaweiCoordinator.getSupportsInstalledAppManagement(device);
+    }
+
+    @Override
+    public boolean supportsCachedAppManagement(GBDevice device) {
+        return huaweiCoordinator.getSupportsCachedAppManagement(device);
+    }
+
+    @Override
+    public int getAlarmSlotCount(GBDevice device) {
+        return huaweiCoordinator.getAlarmSlotCount(device);
+    }
+
+    @Override
+    public int getContactsSlotCount(GBDevice device) {
+        return huaweiCoordinator.getContactsSlotCount(device);
+    }
+
+    @Override
+    public boolean supportsCalendarEvents() {
+        return huaweiCoordinator.supportsCalendarEvents();
+    }
+
+    @Override
+    public boolean supportsActivityDataFetching() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsActiveCalories() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsActivityTracking() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsActivityTracks() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsHeartRateMeasurement(GBDevice device) {
+        return huaweiCoordinator.supportsHeartRate(device);
+    }
+
+    @Override
+    public boolean supportsSpo2(GBDevice device) {
+        return huaweiCoordinator.supportsSPo2(device);
+    }
+
+    @Override
+    public boolean supportsMusicInfo() {
+        return huaweiCoordinator.supportsMusic();
+    }
+
+    @Override
+    public boolean supportsTemperatureMeasurement() {
+        return huaweiCoordinator.supportsTemperature();
+    }
+
+    @Override
+    public boolean supportsContinuousTemperature() {
+        return huaweiCoordinator.supportsTemperature();
+    }
+
+    @Override
+    public InstallHandler findInstallHandler(Uri uri, Context context) {
+        return huaweiCoordinator.getInstallHandler(uri, context);
+    }
+
+    @Override
+    public ActivitySummaryParser getActivitySummaryParser(final GBDevice device, final Context context) {
+        return new HuaweiWorkoutGbParser(device, context);
+    }
+
+    @Override
+    public SampleProvider<? extends AbstractActivitySample> getSampleProvider(GBDevice device, DaoSession session) {
+        return new HuaweiSampleProvider(device, session);
+    }
+
+    @Override
+    public TimeSampleProvider<? extends Spo2Sample> getSpo2SampleProvider(GBDevice device, DaoSession session) {
+        return new HuaweiSpo2SampleProvider(device, session);
+    }
+
+    @Override
+    public TimeSampleProvider<? extends TemperatureSample> getTemperatureSampleProvider(final GBDevice device, final DaoSession session) {
+        return new HuaweiTemperatureSampleProvider(device, session);
+    }
+
+    public DeviceSpecificSettings getDeviceSpecificSettings(final GBDevice device) {
+        return huaweiCoordinator.getDeviceSpecificSettings(device);
+    }
+
+    @Override
+    public HuaweiDeviceType getHuaweiType() {
+        return HuaweiDeviceType.BR;
+    }
+
+    @Override
+    public void setDevice(GBDevice device) {
+        this.device = device;
+    }
+
+    @Override
+    public GBDevice getDevice() {
+        return this.device;
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return HuaweiBRSupport.class;
+    }
+
+    @Override
+    public boolean addBatteryPollingSettings() {
+        return true;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiBinAppParser.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiBinAppParser.java
new file mode 100644
index 0000000..46b619f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiBinAppParser.java
@@ -0,0 +1,111 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.huawei;
+
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+public class HuaweiBinAppParser {
+
+    public static class HuaweiBinAppParseError extends Exception {
+        public HuaweiBinAppParseError(String str) {
+            super(str);
+        }
+    }
+
+    public static class AppFileEntry {
+        public String filename;
+        public String path;
+        public byte[] content;
+    }
+
+    private static final byte BIN_APP_MAGIC = (byte) 0xbe;
+
+    private static final int SIGN_HEADER_LEN = 32;
+    private static final byte[] SIGN_MAGIC = "hw signed app   ".getBytes();
+    private static final byte[] SIGN_VERSION = "1000".getBytes();
+
+
+    private String packageName;
+    private final ArrayList<AppFileEntry> entries = new ArrayList<>();
+
+    public String getPackageName() {
+        return packageName;
+    }
+
+    public byte[] getEntryContent(String filename) {
+        for (AppFileEntry en : entries) {
+            if (en.filename.equals(filename)) {
+                return en.content;
+            }
+        }
+        return null;
+    }
+
+    private byte[] readData(ByteBuffer data) throws Exception {
+        long len = data.getLong();
+        if (len > Integer.MAX_VALUE || len <= 0) {
+            throw new HuaweiBinAppParseError("Invalid data length");
+        }
+        byte[] newPayload = new byte[(int) len];
+        data.get(newPayload, 0, (int) len);
+        return newPayload;
+    }
+
+    private String readStringInternal(ByteBuffer data, int len) throws Exception {
+        byte[] newPayload = new byte[len];
+        data.get(newPayload, 0, len);
+        return new String(newPayload, StandardCharsets.UTF_8);
+    }
+
+
+    private String readString(ByteBuffer data) throws Exception {
+        int len = data.getInt();
+        if (len <= 0 || len > data.remaining()) {
+            throw new HuaweiBinAppParseError("Invalid string length");
+        }
+        return readStringInternal(data, len);
+    }
+
+    private String readEmptyString(ByteBuffer data) throws Exception {
+        int len = data.getInt();
+        if (len < 0 || len > data.remaining()) {
+            throw new HuaweiBinAppParseError("Invalid string length");
+        }
+        return readStringInternal(data, len);
+    }
+
+    private int getSignLen(byte[] in) {
+        byte[] signatureHeader = Arrays.copyOfRange(in, in.length - SIGN_HEADER_LEN, in.length);
+        ByteBuffer signatureHeaderBuf = ByteBuffer.wrap(signatureHeader);
+        byte[] magic = new byte[SIGN_MAGIC.length];
+        signatureHeaderBuf.get(magic, 0, SIGN_MAGIC.length);
+        if (!Arrays.equals(SIGN_MAGIC, magic))
+            return 0;
+        byte[] version = new byte[SIGN_VERSION.length];
+        signatureHeaderBuf.get(version, 0, SIGN_VERSION.length);
+        if (!Arrays.equals(SIGN_VERSION, version))
+            return 0;
+        //NOTE: we need only signature size.
+        return signatureHeaderBuf.getInt();
+    }
+
+    public void parseData(byte[] in) throws Exception {
+        //NOTE: Binary app file signed. We should avoid to read this signature.
+        int signLen = getSignLen(in);
+        ByteBuffer data = ByteBuffer.wrap(in);
+
+        byte magic = data.get();
+        if (magic != BIN_APP_MAGIC) {
+            throw new HuaweiBinAppParseError("Invalid magic");
+        }
+        this.packageName = readString(data);
+        while (data.remaining() > signLen) {
+            AppFileEntry ent = new AppFileEntry();
+            ent.filename = readString(data);
+            ent.path = readEmptyString(data);
+            ent.content = readData(data);
+            entries.add(ent);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiConstants.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiConstants.java
new file mode 100644
index 0000000..e9f88ad
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiConstants.java
@@ -0,0 +1,99 @@
+/*  Copyright (C) 2024 Damien Gaignon, Vitalii Tomin
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huawei;
+
+import java.util.UUID;
+
+import static nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport.BASE_UUID;
+
+public final class HuaweiConstants {
+
+    public static final UUID UUID_SERVICE_HUAWEI_SERVICE = UUID.fromString(String.format(BASE_UUID, "FE86"));
+    public static final UUID UUID_CHARACTERISTIC_HUAWEI_WRITE = UUID.fromString(String.format(BASE_UUID, "FE01"));
+    public static final UUID UUID_CHARACTERISTIC_HUAWEI_READ = UUID.fromString(String.format(BASE_UUID, "FE02"));
+    public static final UUID UUID_SERVICE_HUAWEI_SDP = UUID.fromString("82FF3820-8411-400C-B85A-55BDB32CF060");
+
+    public static final String GROUP_ID = "7B0BC0CBCE474F6C238D9661C63400B797B166EA7849B3A370FC73A9A236E989";
+    public static final byte[] KEY_TYPE = new byte[]{0x00, 0x07};
+
+    public static final byte HUAWEI_MAGIC = 0x5A;
+
+    public static final byte PROTOCOL_VERSION = 0x02;
+
+    public static final int TAG_RESULT = 127;
+    public static final byte[] RESULT_SUCCESS = new byte[]{0x00, 0x01, (byte)0x86, (byte)0xA0};
+    public static final int RESULT_SUCCESS_INT = 0x186a0;
+
+    public static class CryptoTags {
+        public static final int encryption = 124;
+        public static final int initVector = 125;
+        public static final int cipherText = 126;
+    }
+
+    public static final String HO_BAND3_NAME = "honor band 3-";
+    public static final String HO_BAND4_NAME = "honor band 4-";
+    public static final String HO_BAND5_NAME = "honor band 5-";
+    public static final String HO_BAND6_NAME = "honor band 6-";
+    public static final String HO_BAND7_NAME = "honor band 7-";
+    public static final String HO_MAGICWATCH2_NAME = "honor magicwatch 2-";
+    public static final String HO_WATCHGS3_NAME = "honor watch gs 3-";
+    public static final String HO_WATCHGSPRO_NAME = "honor watch gs pro-";
+    public static final String HU_BAND3E_NAME = "huawei band 3e-";
+    public static final String HU_BAND4E_NAME = "huawei band 4e-";
+    public static final String HU_BAND6_NAME = "huawei band 6-";
+    public static final String HU_WATCHGT_NAME = "huawei watch gt-";
+    public static final String HU_BAND2_NAME = "huawei band 2-";
+    public static final String HU_BAND3_NAME = "huawei band 3-";
+    public static final String HU_BAND4_NAME = "huawei band 4-";
+    public static final String HU_BAND2PRO_NAME = "huawei band 2 pro-";
+    public static final String HU_BAND3PRO_NAME = "huawei band 3 pro-";
+    public static final String HU_BAND4PRO_NAME = "huawei band 4 pro-";
+    public static final String HU_WATCHGT2_NAME = "huawei watch gt 2-";
+    public static final String HU_WATCHGT2E_NAME = "huawei watch gt 2e-";
+    public static final String HU_WATCHGT2PRO_NAME = "huawei watch gt 2 pro-";
+    public static final String HU_TALKBANDB6_NAME = "huawei b6-";
+    public static final String HU_BAND7_NAME = "huawei band 7-";
+    public static final String HU_BAND8_NAME = "huawei band 8-";
+    public static final String HU_BAND9_NAME = "huawei band 9-";
+    public static final String HU_WATCHD2_NAME = "huawei watch d2-";
+    public static final String HU_WATCHGT3_NAME = "huawei watch gt 3-";
+    public static final String HU_WATCHGT3SE_NAME = "huawei watch gt 3 se-";
+    public static final String HU_WATCHGT3PRO_NAME = "huawei watch gt 3 pro-";
+    public static final String HU_WATCHGTRUNNER_NAME = "huawei watch gt runner-";
+    public static final String HU_WATCHGTCYBER_NAME = "huawei watch gt cyber-";
+    public static final String HU_WATCH3_NAME = "huawei watch 3-";
+    public static final String HU_WATCH3PRO_NAME = "huawei watch 3 pro-";
+    public static final String HU_WATCHGT4_NAME = "huawei watch gt 4-";
+    public static final String HU_WATCHGT5_NAME = "huawei watch gt 5-";
+    public static final String HU_WATCHGT5PRO_NAME = "huawei watch gt 5 pro-";
+    public static final String HU_WATCHFIT_NAME = "huawei watch fit-";
+    public static final String HU_WATCHFIT2_NAME = "huawei watch fit 2-";
+    public static final String HU_WATCHFIT3_NAME = "huawei watch fit 3-";
+    public static final String HU_WATCHULTIMATE_NAME = "huawei watch ultimate-";
+    public static final String HU_WATCH4_NAME = "huawei watch 4-";
+    public static final String HU_WATCH4PRO_NAME = "huawei watch 4 pro-";
+
+    public static final String PREF_HUAWEI_ADDRESS = "huawei_address";
+    public static final String PREF_HUAWEI_WORKMODE = "workmode";
+    public static final String PREF_HUAWEI_TRUSLEEP = "trusleep";
+    public static final String PREF_HUAWEI_ACCOUNT = "huawei_account";
+    public static final String PREF_HUAWEI_DND_LIFT_WRIST_TYPE = "dnd_lift_wrist_type"; // SharedPref for 0x01 0x1D
+    public static final String PREF_HUAWEI_DEBUG_REQUEST = "debug_huawei_request";
+    public static final String PREF_HUAWEI_CONTINUOUS_SKIN_TEMPERATURE_MEASUREMENT = "continuous_skin_temperature_measurement";
+
+    public static final String PKG_NAME = "com.huawei.devicegroupmanage";
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiCoordinator.java
new file mode 100644
index 0000000..36af870
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiCoordinator.java
@@ -0,0 +1,843 @@
+/*  Copyright (C) 2024 Damien Gaignon, Martin.JM, Vitalii Tomin
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huawei;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.net.Uri;
+
+import java.nio.ByteBuffer;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+import org.slf4j.LoggerFactory;
+import org.slf4j.Logger;
+
+import de.greenrobot.dao.query.QueryBuilder;
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.GBException;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.CameraActivity;
+import nodomain.freeyourgadget.gadgetbridge.activities.appmanager.AppManagerActivity;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettings;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsScreen;
+import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.App;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Notifications;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Notifications.NotificationConstraintsType;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Watchface;
+import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummaryDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiActivitySampleDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiDictData;
+import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiDictDataDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiDictDataValuesDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutDataSampleDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutPaceSampleDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutSummarySample;
+import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutSummarySampleDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutSwimSegmentsSampleDao;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.*;
+
+import androidx.annotation.NonNull;
+
+public class HuaweiCoordinator {
+    Logger LOG = LoggerFactory.getLogger(HuaweiCoordinator.class);
+
+    TreeMap<Integer, byte[]> commandsPerService = new TreeMap<>();
+    // Each byte of expandCapabilities represent a "service"
+    // Each bit in a "service" represent a feature so 1 or 0 is used to check is support or not
+    byte[] expandCapabilities = null;
+    byte notificationCapabilities = -0x01;
+    ByteBuffer notificationConstraints = null;
+
+    private boolean supportsTruSleepNewSync = false;
+    private boolean supportsGpsNewSync = false;
+
+    private Watchface.WatchfaceDeviceParams watchfaceDeviceParams;
+
+    private App.AppDeviceParams appDeviceParams;
+
+    private HuaweiMusicUtils.MusicCapabilities musicDeviceParams = null;
+
+    private HuaweiMusicUtils.MusicCapabilities musicExtendedDeviceParams = null;
+
+    private final HuaweiCoordinatorSupplier parent;
+
+    private boolean transactionCrypted=true;
+
+    private int maxContactsCount = 0;
+
+    public HuaweiCoordinator(HuaweiCoordinatorSupplier parent) {
+        this.parent = parent;
+
+        // Set non-numeric capabilities
+        this.expandCapabilities = GB.hexStringToByteArray(getCapabilitiesSharedPreferences().getString("expandCapabilities", "00"));
+        this.notificationCapabilities = (byte)getCapabilitiesSharedPreferences().getInt("notificationCapabilities", -0x01);
+        this.notificationConstraints = ByteBuffer.wrap(GB.hexStringToByteArray(
+                getCapabilitiesSharedPreferences().getString(
+                        "notificationConstraints",
+                        GB.hexdump(Notifications.defaultConstraints)
+                )));
+        this.maxContactsCount = getCapabilitiesSharedPreferences().getInt("maxContactsCount", 0);
+
+        for (String key : getCapabilitiesSharedPreferences().getAll().keySet()) {
+            int service;
+            try {
+                service = Integer.parseInt(key);
+                byte[] commands = GB.hexStringToByteArray(getCapabilitiesSharedPreferences().getString(key, "00"));
+                this.commandsPerService.put(service, commands);
+            } catch (NumberFormatException e) {
+                // These are the non-numeric capabilities, which have been set already
+            }
+        }
+    }
+
+    protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
+        long deviceId = device.getId();
+        QueryBuilder<?> qb = session.getHuaweiActivitySampleDao().queryBuilder();
+        qb.where(HuaweiActivitySampleDao.Properties.DeviceId.eq(deviceId)).buildDelete().executeDeleteWithoutDetachingEntities();
+
+        QueryBuilder<HuaweiWorkoutSummarySample> qb2 = session.getHuaweiWorkoutSummarySampleDao().queryBuilder();
+        List<HuaweiWorkoutSummarySample> workouts = qb2.where(HuaweiWorkoutSummarySampleDao.Properties.DeviceId.eq(deviceId)).build().list();
+        for (HuaweiWorkoutSummarySample sample : workouts) {
+            session.getHuaweiWorkoutDataSampleDao().queryBuilder().where(
+                    HuaweiWorkoutDataSampleDao.Properties.WorkoutId.eq(sample.getWorkoutId())
+            ).buildDelete().executeDeleteWithoutDetachingEntities();
+
+            session.getHuaweiWorkoutPaceSampleDao().queryBuilder().where(
+                    HuaweiWorkoutPaceSampleDao.Properties.WorkoutId.eq(sample.getWorkoutId())
+            ).buildDelete().executeDeleteWithoutDetachingEntities();
+
+            session.getHuaweiWorkoutSwimSegmentsSampleDao().queryBuilder().where(
+                    HuaweiWorkoutSwimSegmentsSampleDao.Properties.WorkoutId.eq(sample.getWorkoutId())
+            ).buildDelete().executeDeleteWithoutDetachingEntities();
+        }
+
+        session.getHuaweiWorkoutSummarySampleDao().queryBuilder().where(HuaweiWorkoutSummarySampleDao.Properties.DeviceId.eq(deviceId)).buildDelete().executeDeleteWithoutDetachingEntities();
+
+        session.getBaseActivitySummaryDao().queryBuilder().where(BaseActivitySummaryDao.Properties.DeviceId.eq(deviceId)).buildDelete().executeDeleteWithoutDetachingEntities();
+
+        QueryBuilder<HuaweiDictData> qb3 = session.getHuaweiDictDataDao().queryBuilder();
+        List<HuaweiDictData> dictData = qb3.where(HuaweiDictDataDao.Properties.DeviceId.eq(deviceId)).build().list();
+        for (HuaweiDictData data : dictData) {
+            session.getHuaweiDictDataValuesDao().queryBuilder().where(
+                    HuaweiDictDataValuesDao.Properties.DictId.eq(data.getDictId())
+            ).buildDelete().executeDeleteWithoutDetachingEntities();
+        }
+
+        session.getHuaweiDictDataDao().queryBuilder().where(HuaweiDictDataDao.Properties.DeviceId.eq(deviceId)).buildDelete().executeDeleteWithoutDetachingEntities();
+    }
+
+    private SharedPreferences getCapabilitiesSharedPreferences() {
+        return GBApplication.getContext().getSharedPreferences("huawei_coordinator_capatilities" + parent.getDeviceType().name(), Context.MODE_PRIVATE);
+    }
+
+    private SharedPreferences getDeviceSpecificSharedPreferences(GBDevice gbDevice) {
+        return GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress());
+    }
+
+    public boolean getForceOption(GBDevice gbDevice, String option) {
+        return getDeviceSpecificSharedPreferences(gbDevice).getBoolean(option, false);
+    }
+
+    private void saveCommandsForService(int service, byte[] commands) {
+        commandsPerService.put(service, commands);
+        getCapabilitiesSharedPreferences().edit().putString(String.valueOf(service), GB.hexdump(commands)).apply();
+    }
+
+    public void saveExpandCapabilities(byte[] capabilities) {
+        expandCapabilities = capabilities;
+        getCapabilitiesSharedPreferences().edit().putString("expandCapabilities", GB.hexdump(capabilities)).apply();
+    }
+
+    public void saveNotificationCapabilities(byte capabilities) {
+        notificationCapabilities = capabilities;
+        getCapabilitiesSharedPreferences().edit().putInt("notificationCapabilities", (int)capabilities).apply();
+    }
+
+    public void saveNotificationConstraints(ByteBuffer constraints) {
+        notificationConstraints = constraints;
+        getCapabilitiesSharedPreferences().edit().putString("notificationConstraints", GB.hexdump(constraints.array())).apply();
+    }
+
+    public void saveMaxContactsCount(int maxContactsCount) {
+        this.maxContactsCount = maxContactsCount;
+        getCapabilitiesSharedPreferences().edit().putInt("maxContactsCount", maxContactsCount).apply();
+    }
+
+    public void addCommandsForService(int service, byte[] commands) {
+        if (!commandsPerService.containsKey(service)) {
+            saveCommandsForService(service, commands);
+            return;
+        }
+        byte[] saved = commandsPerService.get(service);
+        if (saved == null) {
+            saveCommandsForService(service, commands);
+            return;
+        }
+        if (saved.length != commands.length) {
+            saveCommandsForService(service, commands);
+            return;
+        }
+        boolean changed = false;
+        for (int i = 0; i < saved.length; i++) {
+            if (saved[i] != commands[i]) {
+                changed = true;
+                break;
+            }
+        }
+        if (changed)
+            saveCommandsForService(service, commands);
+    }
+
+    public byte[] getCommandsForService(int service) {
+        return commandsPerService.get(service);
+    }
+
+    // Print all Services ID and Commands ID
+    public void printCommandsPerService() {
+        StringBuilder msg = new StringBuilder();
+        for(Map.Entry<Integer, byte[]> entry : commandsPerService.entrySet()) {
+            msg.append("ServiceID: ").append(Integer.toHexString(entry.getKey())).append(" => Commands: ");
+            for (byte b: entry.getValue()) {
+                msg.append(Integer.toHexString(b)).append(" ");
+            }
+            msg.append("\n");
+        }
+        LOG.info(msg.toString());
+    }
+
+    private boolean supportsCommandForService(int service, int command) {
+        byte[] commands = commandsPerService.get(service);
+        if (commands == null)
+            return false;
+        for (byte b : commands)
+            if (b == (byte) command)
+                return true;
+        return false;
+    }
+
+    private boolean supportsExpandCapability(int which) {
+        // capability is a number containing :
+        //  - the index of the "service"
+        //  - the real capability number
+        if (expandCapabilities == null) {
+            LOG.debug("Expand capabilities is null");
+            return false;
+        }
+        if (which >= expandCapabilities.length * 8) {
+            LOG.debug("Capability is not supported");
+            return false;
+        }
+        int capability = 1 << (which % 8);
+        if ((expandCapabilities[which / 8] & capability) == capability) return true;
+        return false;
+    }
+
+    private boolean supportsNotificationConstraint(byte which) {
+        return notificationConstraints.get(which) == 0x01;
+    }
+
+    private int getNotificationConstraint(byte which) {
+        return (int)notificationConstraints.getShort(which);
+    }
+
+    public DeviceSpecificSettings getDeviceSpecificSettings(final GBDevice device) {
+        final DeviceSpecificSettings deviceSpecificSettings = new DeviceSpecificSettings();
+
+        // Health
+        if (supportsInactivityWarnings())
+            deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.HEALTH, R.xml.devicesettings_inactivity_sheduled);
+        if (supportsTruSleep())
+            deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.HEALTH, R.xml.devicesettings_trusleep);
+        if (supportsHeartRate())
+            deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.HEALTH, R.xml.devicesettings_heartrate_automatic_enable);
+        if (supportsSPo2())
+            deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.HEALTH, R.xml.devicesettings_spo_automatic_enable);
+        if(supportsTemperature()) {
+            deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.HEALTH, R.xml.devicesettings_temperature_automatic_enable);
+        }
+
+        // Notifications
+        final List<Integer> notifications = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.NOTIFICATIONS);
+        notifications.add(R.xml.devicesettings_notifications_enable);
+        if (supportsNotificationOnBluetoothLoss())
+            notifications.add(R.xml.devicesettings_disconnectnotification_noshed);
+        if (supportsDoNotDisturb(device))
+            notifications.add(R.xml.devicesettings_donotdisturb_allday_liftwirst_notwear);
+
+        // Workout
+        if (supportsSendingGps())
+            deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.WORKOUT, R.xml.devicesettings_workout_send_gps_to_band);
+
+        // Other
+        deviceSpecificSettings.addRootScreen(R.xml.devicesettings_find_phone);
+        deviceSpecificSettings.addRootScreen(R.xml.devicesettings_disable_find_phone_with_dnd);
+        deviceSpecificSettings.addRootScreen(R.xml.devicesettings_allow_accept_reject_calls);
+
+        // Camera control
+        if (supportsCameraRemote())
+            deviceSpecificSettings.addRootScreen(R.xml.devicesettings_camera_remote);
+
+        //Contacts
+        if (getContactsSlotCount(device) > 0) {
+            deviceSpecificSettings.addRootScreen(R.xml.devicesettings_contacts);
+        }
+
+        //Music
+        if (supportsMusicUploading() && getMusicInfoParams() != null && device.isConnected()) {
+            deviceSpecificSettings.addRootScreen(R.xml.devicesettings_musicmanagement);
+        }
+
+        // Time
+        if (supportsDateFormat()) {
+            final List<Integer> dateTime = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DATE_TIME);
+            dateTime.add(R.xml.devicesettings_dateformat);
+            dateTime.add(R.xml.devicesettings_timeformat);
+        }
+
+        //Calendar
+        if( supportsP2PService() && supportsCalendar()) {
+            deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.CALENDAR, R.xml.devicesettings_sync_calendar);
+        }
+
+        // Display
+        if (supportsWearLocation(device))
+            deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DISPLAY, R.xml.devicesettings_wearlocation);
+        if (supportsAutoWorkMode())
+            deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DISPLAY, R.xml.devicesettings_workmode);
+        if (supportsActivateOnLift())
+            deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DISPLAY, R.xml.devicesettings_liftwrist_display_noshed);
+        if (supportsRotateToCycleInfo())
+            deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DISPLAY, R.xml.devicesettings_rotatewrist_cycleinfo);
+        // Currently on main setting menu.
+        /*if (supportsLanguageSetting())
+            deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DISPLAY, R.xml.devicesettings_language_generic);*/
+        if(supportsTemperature()) {
+            deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DISPLAY, R.xml.devicesettings_temperature_scale_cf);
+        }
+
+        // Developer
+        final List<Integer> developer = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DEVELOPER);
+        developer.add(R.xml.devicesettings_huawei_debug);
+
+        return deviceSpecificSettings;
+    }
+
+    public boolean supportsDateFormat() {
+        return supportsCommandForService(0x01, 0x04);
+    }
+
+    public boolean supportsActivateOnLift() {
+        return supportsCommandForService(0x01, 0x09);
+    }
+
+    public boolean supportsDoNotDisturb() {
+        return supportsCommandForService(0x01, 0x0a);
+    }
+
+    public boolean supportsDoNotDisturb(GBDevice gbDevice) {
+        return supportsDoNotDisturb() || getForceOption(gbDevice, PREF_FORCE_DND_SUPPORT);
+    }
+
+    public boolean supportsActivityType() {
+        return supportsCommandForService(0x01, 0x12);
+    }
+
+    public boolean supportsWearLocation() {
+        return supportsCommandForService(0x01, 0x1a);
+    }
+
+    public boolean supportsWearLocation(GBDevice gbDevice) {
+        return supportsWearLocation() || getForceOption(gbDevice, PREF_FORCE_ENABLE_WEAR_LOCATION);
+    }
+
+    public boolean supportsRotateToCycleInfo() {
+        return supportsCommandForService(0x01, 0x1b);
+    }
+
+    public boolean supportsQueryDndLiftWristDisturbType() {
+        return supportsCommandForService(0x01, 0x1d);
+    }
+
+    public boolean supportsCameraRemote() {
+        return supportsCommandForService(0x01, 0x29) && CameraActivity.supportsCamera();
+    }
+
+    public boolean supportsContacts() {
+        return supportsCommandForService(0x03, 0x1);
+    }
+
+    public boolean supportsCalendarEvents() {
+        return supportsP2PService() && supportsCalendar();
+    }
+
+    public boolean supportsAcceptAgreement() {
+        return supportsCommandForService(0x01, 0x30);
+    }
+
+    public boolean supportsSettingRelated() {
+        return supportsCommandForService(0x01, 0x31);
+    }
+
+    public boolean supportsTimeAndZoneId() {
+        return supportsCommandForService(0x01, 0x32);
+    }
+
+    public boolean supportsConnectStatus() {
+        return supportsCommandForService(0x01, 0x35);
+    }
+
+    public boolean supportsExpandCapability() {
+        return supportsCommandForService(0x01, 0x37);
+    }
+
+    public boolean supportsNotificationAlert() {
+        return supportsCommandForService(0x02, 0x01);
+    }
+
+    public boolean supportsNotification() {
+        return supportsCommandForService(0x02, 0x04);
+    }
+
+    public boolean supportsWearMessagePush() {
+        return supportsCommandForService(0x02, 0x08);
+    }
+
+    public boolean supportsMotionGoal() {
+        return supportsCommandForService(0x07, 0x01);
+    }
+    
+    public boolean supportsInactivityWarnings() {
+        return supportsCommandForService(0x07, 0x06);
+    }
+
+    public boolean supportsActivityReminder() {
+        return supportsCommandForService(0x07, 0x07);
+    }
+
+    public boolean supportsDeviceReportThreshold() {
+        return supportsCommandForService(0x07, 0x0e);
+    }
+
+    public boolean supportsTruSleep() {
+        return supportsCommandForService(0x07, 0x16);
+    }
+
+    public boolean supportsHeartRate() {
+        return supportsCommandForService(0x07, 0x17);
+    }
+
+    public boolean supportsHeartRate(GBDevice gbDevice) {
+        return supportsHeartRate() || getForceOption(gbDevice, PREF_FORCE_ENABLE_HEARTRATE_SUPPORT);
+    }
+
+    public boolean supportsHeartRateZones() {
+        return supportsCommandForService(0x07, 0x13);
+    }
+
+    public boolean supportsExtendedHeartRateZones() {
+        return supportsCommandForService(0x07, 0x21);
+    }
+
+    public boolean supportsFitnessRestHeartRate() {
+        return supportsCommandForService(0x07, 0x23);
+    }
+
+    public boolean supportsSPo2() {
+        return supportsCommandForService(0x07, 0x24);
+    }
+
+    public boolean supportsSPo2(GBDevice gbDevice) {
+        return supportsSPo2() || getForceOption(gbDevice, PREF_FORCE_ENABLE_SPO2_SUPPORT);
+    }
+
+    public boolean supportsRunPaceConfig() {
+        return supportsCommandForService(0x07, 0x28);
+    }
+
+    public boolean supportsFitnessThresholdValue() {
+        return supportsCommandForService(0x07, 0x29);
+    }
+    public boolean supportsFitnessThresholdValueV2() { return supportsExpandCapability(0x9a) || supportsExpandCapability(0x9c); }
+
+    // 0x1d - SupportTemperature
+    // 0xba - SupportTemperatureClassification
+    // 0x43 - SupportTemperatureStudy
+    public boolean supportsTemperature() { return supportsExpandCapability(0x1d); }
+
+    public boolean supportsBloodPressure() { return supportsExpandCapability(0x3b); }
+
+    public boolean supportsEventAlarm() {
+        return supportsCommandForService(0x08, 0x01);
+    }
+
+    public boolean supportsSmartAlarm() {
+        return supportsCommandForService(0x08, 0x02) ;
+    }
+
+    public boolean supportsSmartAlarm(GBDevice gbDevice) {
+        return supportsSmartAlarm() || getForceOption(gbDevice, PREF_FORCE_ENABLE_SMART_ALARM);
+    }
+
+    public boolean supportsSmartAlarm(GBDevice gbDevice, int alarmPosition) {
+        return supportsSmartAlarm(gbDevice) && alarmPosition == 0;
+    }
+
+    public boolean forcedSmartWakeup(GBDevice device, int alarmPosition) {
+        return supportsSmartAlarm(device, alarmPosition) && alarmPosition == 0;
+    }
+
+    /**
+     * @return True if alarms can be changed on the device, false otherwise
+     */
+    public boolean supportsChangingAlarm() {
+        return supportsCommandForService(0x08, 0x03);
+    }
+
+    public boolean supportsNotificationOnBluetoothLoss() {
+        return supportsCommandForService(0x0b, 0x03);
+    }
+
+    public boolean supportsLanguageSetting() {
+        return supportsCommandForService(0x0c, 0x01);
+    }
+
+    public boolean supportsWatchfaceParams(){ return supportsCommandForService(0x27, 0x01);}
+
+    public boolean supportsAppParams(){ return supportsCommandForService(0x2a, 0x06);}
+
+    public boolean supportsMusicUploading(){ return supportsCommandForService(0x25, 0x04);}
+
+    public boolean supportsWeather() {
+        return supportsCommandForService(0x0f, 0x01);
+    }
+
+    public boolean supportsWeatherUnit() {
+        return supportsCommandForService(0x0f, 0x05);
+    }
+
+    public boolean supportsWeatherExtended() {
+        return supportsCommandForService(0x0f, 0x06);
+    }
+
+    public boolean supportsWeatherErrorSimple() {
+        return supportsCommandForService(0x0f, 0x07);
+    }
+
+    public boolean supportsWeatherForecasts() {
+        return supportsCommandForService(0x0f, 0x08);
+    }
+
+    public boolean supportsWeatherMoonRiseSet() {
+        return supportsCommandForService(0x0f, 0x0a);
+    }
+
+    public boolean supportsWeatherTides() {
+        return supportsCommandForService(0x0f, 0x0b);
+    }
+
+    public boolean supportsWeatherErrorExtended() {
+        return supportsCommandForService(0x0f, 0x0c);
+    }
+
+    public boolean supportsWeatherUvIndex() {
+        return supportsExpandCapability(0x2f);
+    }
+
+    public boolean supportsWorkouts() {
+        return supportsCommandForService(0x17, 0x01);
+    }
+
+    public boolean supportsWorkoutsTrustHeartRate() {
+        return supportsCommandForService(0x17, 0x17);
+    }
+
+    public boolean supportsSendingGps() {
+        return supportsCommandForService(0x18, 0x02);
+    }
+
+    public boolean supportsAccount() {
+        return supportsCommandForService(0x1A, 0x01);
+    }
+
+    public boolean supportsAccountJudgment() {
+        return supportsCommandForService(0x1A, 0x05);
+    }
+
+    public boolean supportsAccountSwitch() {
+        return supportsCommandForService(0x1A, 0x06);
+    }
+
+    public boolean supportsDiffAccountPairingOptimization() {
+        if (supportsExpandCapability())
+            return supportsExpandCapability(0xac);
+        return false;
+    }
+
+    public boolean supportsMusic() {
+        return supportsCommandForService(0x25, 0x02);
+    }
+
+    public boolean supportsAutoWorkMode() {
+        return supportsCommandForService(0x26, 0x02);
+    }
+
+    public boolean supportsMenstrual() {
+        return supportsCommandForService(0x32, 0x01);
+    }
+
+    public boolean supportsP2PService() {
+        return supportsCommandForService(0x34, 0x1);
+    }
+
+    public boolean supportsExternalCalendarService() {
+        if (supportsExpandCapability())
+            return supportsExpandCapability(184);
+        return false;
+    }
+
+    public boolean supportsTrack() {
+        if (supportsExpandCapability())
+            return supportsExpandCapability(0x36);
+        return false;
+    }
+
+    public boolean supportsCalendar() {
+        if (supportsExpandCapability())
+            return supportsExpandCapability(171) || supportsExpandCapability(184);
+        return false;
+    }
+
+    public boolean supportsMultiDevice() {
+        if (supportsExpandCapability())
+            return supportsExpandCapability(109);
+        return false;
+    }
+
+    public boolean supportsUnknownGender() {
+        if (supportsExpandCapability())
+            return supportsExpandCapability(0x57);
+        return false;
+    }
+
+    public boolean supportsPrecisionWeight() {
+        if (supportsExpandCapability())
+            return supportsExpandCapability(0xb3);
+        return false;
+    }
+
+    public boolean supportsMoreMusic() {
+        if (supportsExpandCapability())
+            return supportsExpandCapability(122);
+        return false;
+    }
+
+
+    public boolean supportsPromptPushMessage () {
+//              do not ask for capabilities under specific condition
+//                  if (deviceType == 10 && deviceVersion == 73617766697368 && deviceSoftVersion == 372E312E31) -> leo device
+//                  if V1V0Device
+//                  if (serviceId = 0x01 && commandId = 0x03) && productType == 3
+        return (((notificationCapabilities >> 1) & 1) == 0);
+    }
+
+    public boolean supportsOutgoingCall () {
+        return (((notificationCapabilities >> 2) & 1) == 0);
+    }
+
+    public boolean supportsYellowPages() {
+        return supportsNotificationConstraint(NotificationConstraintsType.yellowPagesSupport);
+    }
+
+    public boolean supportsContentSIgn() {
+        return supportsNotificationConstraint(NotificationConstraintsType.contentSignSupport);
+    }
+
+    public boolean supportsIncomingNumber() {
+        return supportsNotificationConstraint(NotificationConstraintsType.incomingNumberSupport);
+    }
+
+    public int getContentFormat() {
+        return getNotificationConstraint(NotificationConstraintsType.contentFormat);
+    }
+
+    public int getYellowPagesFormat() {
+        return getNotificationConstraint(NotificationConstraintsType.yellowPagesFormat);
+    }
+
+    public int getContentSignFormat() {
+        return getNotificationConstraint(NotificationConstraintsType.contentSignFormat);
+    }
+
+    public int getIncomingFormatFormat() {
+        return getNotificationConstraint(NotificationConstraintsType.incomingNumberFormat);
+    }
+
+    public int getContentLength() {
+        return getNotificationConstraint(NotificationConstraintsType.contentLength);
+    }
+
+    public int getYellowPagesLength() {
+        return getNotificationConstraint(NotificationConstraintsType.yellowPagesLength);
+    }
+
+    public int getContentSignLength() {
+        return getNotificationConstraint(NotificationConstraintsType.contentSignLength);
+    }
+
+    public int getIncomingNumberLength() {
+        return getNotificationConstraint(NotificationConstraintsType.incomingNumberLength);
+    }
+
+    public int getAlarmSlotCount(GBDevice gbDevice) {
+        int alarmCount = 0;
+        if (supportsEventAlarm())
+            alarmCount += 5; // Always five event alarms
+        if (supportsSmartAlarm(gbDevice))
+            alarmCount += 1; // Always a single smart alarm
+        return alarmCount;
+    }
+
+    public int getContactsSlotCount(GBDevice device) {
+        return supportsContacts()?maxContactsCount:0;
+    }
+
+    public void setTransactionCrypted(boolean crypted) {
+        this.transactionCrypted = crypted;
+    }
+
+    public boolean isTransactionCrypted() {
+        return this.transactionCrypted;
+    }
+
+    public String[] getSupportedLanguageSettings(GBDevice device) {
+        return new String[]{
+                "auto",
+                "ar_SA",
+                "cs_CZ",
+                "da_DK",
+                "de_DE",
+                "el_GR",
+                "en_GB",
+                "en_US",
+                "es_ES",
+                "fr_FR",
+                "he_IL",
+                "it_IT",
+                "id_ID",
+                "ko_KO",
+                "nl_NL",
+                "pl_PL",
+                "pt_PT",
+                "pt_BR",
+                "ro_RO",
+                "ru_RU",
+                "sv_SE",
+                "th_TH",
+                "ja_JP",
+                "tr_TR",
+                "uk_UA",
+                "zh_CN",
+                "zh_TW",
+        };
+
+    }
+
+    public short getWidth() {
+        return (short)watchfaceDeviceParams.width;
+    }
+
+    public short getHeight() {
+        return (short)watchfaceDeviceParams.height;
+    }
+
+    public void setWatchfaceDeviceParams(Watchface.WatchfaceDeviceParams watchfaceDeviceParams) {
+        this.watchfaceDeviceParams = watchfaceDeviceParams;
+    }
+
+    public void setAppDeviceParams(App.AppDeviceParams appDeviceParams) {
+        this.appDeviceParams = appDeviceParams;
+    }
+
+    public App.AppDeviceParams getAppDeviceParams() {
+        return appDeviceParams;
+    }
+
+    public void setExtendedMusicInfoParams(HuaweiMusicUtils.MusicCapabilities musicDeviceParams) {
+        LOG.info(musicDeviceParams.toString());
+        this.musicExtendedDeviceParams = musicDeviceParams;
+    }
+    public HuaweiMusicUtils.MusicCapabilities getExtendedMusicInfoParams() {
+        return musicExtendedDeviceParams;
+    }
+
+    public void setMusicInfoParams(HuaweiMusicUtils.MusicCapabilities musicDeviceParams) {
+        LOG.info(musicDeviceParams.toString());
+        this.musicDeviceParams = musicDeviceParams;
+    }
+    public HuaweiMusicUtils.MusicCapabilities getMusicInfoParams() {
+        return musicDeviceParams;
+    }
+
+
+    public Class<? extends Activity> getAppManagerActivity() {
+        return AppManagerActivity.class;
+    }
+
+    public boolean getSupportsAppListFetching() { return true; }
+
+    public boolean getSupportsAppsManagement(GBDevice device) {
+        return true;
+    }
+
+    public boolean getSupportsInstalledAppManagement(GBDevice device) {
+        return this.supportsAppParams(); // NOTE: this check can be incorrect. But looks like it works
+    }
+
+    public boolean getSupportsCachedAppManagement(GBDevice device) {
+        return false;
+    }
+
+    public InstallHandler getInstallHandler(Uri uri, Context context) {
+        HuaweiInstallHandler handler = new HuaweiInstallHandler(uri, context);
+        return handler.isValid() ? handler : null;
+    }
+
+    public boolean getSupportsTruSleepNewSync() {
+        return supportsTruSleepNewSync;
+    }
+
+    public void setSupportsTruSleepNewSync(boolean supportsTruSleepNewSync) {
+        this.supportsTruSleepNewSync = supportsTruSleepNewSync;
+    }
+
+    public boolean isSupportsGpsNewSync() {
+        return supportsGpsNewSync;
+    }
+
+    public void setSupportsGpsNewSync(boolean supportsGpsNewSync) {
+        this.supportsGpsNewSync = supportsGpsNewSync;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiCoordinatorSupplier.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiCoordinatorSupplier.java
new file mode 100644
index 0000000..9f26012
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiCoordinatorSupplier.java
@@ -0,0 +1,47 @@
+/*  Copyright (C) 2024 Damien Gaignon, Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huawei;
+
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+
+public interface HuaweiCoordinatorSupplier {
+
+    enum HuaweiDeviceType {
+        AW(0),     //BLE behind
+        BR(1),
+        BLE(2),
+        SMART(5)   //BLE behind
+        ;
+
+        final int huaweiType;
+
+        HuaweiDeviceType(int huaweiType) {
+            this.huaweiType = huaweiType;
+        }
+
+        public int getType(){
+            return huaweiType;
+        }
+    }
+
+    HuaweiCoordinator getHuaweiCoordinator();
+    HuaweiDeviceType getHuaweiType();
+    DeviceType getDeviceType();
+    void setDevice(GBDevice Device);
+    GBDevice getDevice();
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiCrypto.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiCrypto.java
new file mode 100644
index 0000000..cbc95d4
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiCrypto.java
@@ -0,0 +1,273 @@
+/*  Copyright (C) 2024 Damien Gaignon, Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huawei;
+
+import nodomain.freeyourgadget.gadgetbridge.util.CryptoUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+import java.io.UnsupportedEncodingException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.NoSuchAlgorithmException;
+import java.security.InvalidKeyException;
+import java.security.SecureRandom;
+import java.security.spec.InvalidKeySpecException;
+import java.util.Arrays;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.NoSuchPaddingException;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class HuaweiCrypto {
+    private static final Logger LOG = LoggerFactory.getLogger(HuaweiCrypto.class);
+
+    public static class CryptoException extends Exception {
+        CryptoException(Exception e) {
+            super(e);
+        }
+    }
+
+    public static final byte[] SECRET_KEY_1_v1 = new byte[]{ 0x6F, 0x75, 0x6A, 0x79,
+                                                            0x6D, 0x77, 0x71, 0x34,
+                                                            0x63, 0x6C, 0x76, 0x39,
+                                                            0x33, 0x37, 0x38, 0x79};
+    public static final byte[] SECRET_KEY_2_v1 = new byte[]{ 0x62, 0x31, 0x30, 0x6A,
+                                                            0x67, 0x66, 0x64, 0x39,
+                                                            0x79, 0x37, 0x76, 0x73,
+                                                            0x75, 0x64, 0x61, 0x39};
+    public static final byte[] SECRET_KEY_1_v23 = new byte[]{ 0x55, 0x53, (byte)0x86, (byte)0xFC,
+                                                            0x63, 0x20, 0x07, (byte)0xAA,
+                                                            (byte)0x86, 0x49, 0x35, 0x22,
+                                                            (byte)0xB8, 0x6A, (byte)0xE2, 0x5C};
+    public static final byte[] SECRET_KEY_2_v23 = new byte[]{ 0x33, 0x07, (byte)0x9B, (byte)0xC5,
+                                                            0x7A, (byte)0x88, 0x6D, 0x3C,
+                                                            (byte)0xF5, 0x61, 0x37, 0x09,
+                                                            0x6F, 0x22, (byte)0x80, 0x00};
+
+    public static final byte[] DIGEST_SECRET_v1 = new byte[]{ 0x70, (byte)0xFB, 0x6C, 0x24,
+                                                            0x03, 0x5F, (byte)0xDB, 0x55,
+                                                            0x2F, 0x38, (byte)0x89, (byte)0x8A,
+                                                            (byte) 0xEE, (byte)0xDE, 0x3F, 0x69};
+    public static final byte[] DIGEST_SECRET_v2 = new byte[]{ (byte)0x93, (byte)0xAC, (byte)0xDE, (byte)0xF7,
+                                                            0x6A, (byte)0xCB, 0x09, (byte)0x85,
+                                                            0x7D, (byte)0xBF, (byte)0xE5, 0x26,
+                                                            0x1A, (byte)0xAB, (byte)0xCD, 0x78};
+    public static final byte[] DIGEST_SECRET_v3 = new byte[]{ (byte)0x9C, 0x27, 0x63, (byte)0xA9,
+                                                            (byte)0xCC, (byte)0xE1, 0x34, 0x76,
+                                                            0x6D, (byte)0xE3, (byte)0xFF, 0x61,
+                                                            0x18, 0x20, 0x05, 0x53};
+
+    public static final byte[] MESSAGE_RESPONSE = new byte[]{0x01, 0x10};
+    public static final byte[] MESSAGE_CHALLENGE = new byte[]{0x01, 0x00};
+
+    public static final long ENCRYPTION_COUNTER_MAX = 0xFFFFFFFF;
+
+    protected int authVersion;
+    protected int deviceSupportType;
+    protected byte authAlgo;
+    protected  byte authMode;
+
+    public HuaweiCrypto(int authVersion) {
+        this.authVersion = authVersion;
+    }
+
+    public HuaweiCrypto(int authVersion, byte authAlgo, int deviceSupportType, byte authMode) {
+        this(authVersion);
+        this.deviceSupportType = deviceSupportType;
+        this.authAlgo = authAlgo;
+        this.authMode = authMode;
+    }
+
+    public static byte[] generateNonce() {
+        // While technically not a nonce, we need it to be random and rely on the length for the chance of repitition to be small
+        byte[] returnValue = new byte[16];
+        (new SecureRandom()).nextBytes(returnValue);
+        return returnValue;
+    }
+
+    private byte[] getDigestSecret() {
+        byte[] digest;
+        if (authVersion == 1) {
+            digest = DIGEST_SECRET_v1.clone();
+        } else if (authVersion == 2) {
+            digest = DIGEST_SECRET_v2.clone();
+        } else {
+            digest = DIGEST_SECRET_v3.clone();
+        }
+        return digest;
+    }
+    public byte[] computeDigest(byte[] message, byte[] nonce) throws NoSuchAlgorithmException, InvalidKeyException {
+        byte[] digestSecret = getDigestSecret();
+        byte[] msgToDigest = ByteBuffer.allocate(16 + message.length)
+                                                .put(digestSecret)
+                                                .put(message)
+                                                .array();
+        byte[] digestStep1 = CryptoUtils.calcHmacSha256(msgToDigest, nonce);
+        byte[] challenge = ByteBuffer.allocate(0x40)
+                .put(CryptoUtils.calcHmacSha256(digestStep1, nonce))
+                .put(digestStep1)
+                .array();
+        return challenge;
+    }
+
+    public byte[] computeDigestHiChainLite(byte[] message, byte[] key, byte[] nonce) throws NoSuchAlgorithmException, InvalidKeyException, InvalidKeySpecException, UnsupportedEncodingException {
+        byte[] digestStep1;
+        byte[] hashKey = CryptoUtils.digest(GB.hexdump(key).getBytes("UTF-8"));
+        byte[] digestSecret = getDigestSecret();
+        for (int i = 0; i < digestSecret.length; i++) {
+            digestSecret[i] = (byte) (((0xFF & hashKey[i]) ^ (digestSecret[i] & 0xFF)) & 0xFF);
+        }
+        byte[] msgToDigest = ByteBuffer.allocate(18)
+                .put(digestSecret)
+                .put(message)
+                .array();
+        if (authAlgo == 0x01) {
+            digestStep1 = CryptoUtils.pbkdf2Sha256(GB.hexdump(msgToDigest), GB.hexdump(nonce), 0x3e8, 0x100);
+        } else {
+            digestStep1 = CryptoUtils.calcHmacSha256(msgToDigest, nonce);
+        }
+        byte[] challenge = ByteBuffer.allocate(0x40)
+                .put(CryptoUtils.calcHmacSha256(digestStep1, nonce))
+                .put(digestStep1)
+                .array();
+        return challenge;
+    }
+
+    public byte[] digestChallenge(byte[] secretKey, byte[] nonce) throws NoSuchAlgorithmException, InvalidKeyException, InvalidKeySpecException, UnsupportedEncodingException {
+        if (authMode == 0x02) {
+            if (secretKey == null)
+                return null;
+            if (authVersion == 0x02) {
+                byte[] key = ByteBuffer.allocate(18)
+                        .put(secretKey)
+                        .put(MESSAGE_CHALLENGE)
+                        .array();
+                byte[] challenge = ByteBuffer.allocate(0x40)
+                        .put(CryptoUtils.calcHmacSha256(key, nonce))
+                        .put(key)
+                        .array();
+                return challenge;
+            }
+            return computeDigestHiChainLite(MESSAGE_CHALLENGE, secretKey, nonce);
+        }
+        return computeDigest(MESSAGE_CHALLENGE, nonce);
+    }
+
+    public byte[] digestResponse(byte[] secretKey, byte[] nonce) throws NoSuchAlgorithmException, InvalidKeyException, InvalidKeySpecException, UnsupportedEncodingException {
+        if (authMode == 0x02) {
+            if (secretKey == null)
+                return null;
+            if (authVersion == 0x02) {
+                byte[] key = ByteBuffer.allocate(18)
+                        .put(secretKey)
+                        .put(MESSAGE_RESPONSE)
+                        .array();
+                byte[] challenge = ByteBuffer.allocate(0x40)
+                        .put(CryptoUtils.calcHmacSha256(key, nonce))
+                        .put(key)
+                        .array();
+                return challenge;
+            }
+            return computeDigestHiChainLite(MESSAGE_RESPONSE, secretKey, nonce);
+        }
+        return computeDigest(MESSAGE_RESPONSE, nonce);
+    }
+
+    public static ByteBuffer initializationVector(long counter) {
+        if (counter == ENCRYPTION_COUNTER_MAX) {
+            counter = 1;
+        } else {
+            counter += 1;
+        }
+        ByteBuffer ivCounter = ByteBuffer.allocate(16);
+        ivCounter.put(generateNonce(), 0, 12);
+        ivCounter.put(ByteBuffer.allocate(8).putLong(counter).array(), 4, 4);
+        ivCounter.rewind();
+        return ivCounter;
+    }
+
+    public byte[] createSecretKey(String macAddress) throws NoSuchAlgorithmException {
+        byte[] secret_key_1 = SECRET_KEY_1_v23;
+        byte[] secret_key_2 = SECRET_KEY_2_v23;
+        if (authVersion == 1) {
+            secret_key_1 = SECRET_KEY_1_v1;
+            secret_key_2 = SECRET_KEY_2_v1;
+        }
+
+        byte[] macAddressKey = (macAddress.replace(":", "") + "0000").getBytes(StandardCharsets.UTF_8);
+
+        byte[] mixedSecretKey = new byte[16];
+        for (int i = 0; i < 16; i++) {
+            mixedSecretKey[i] = (byte)((((0xFF & secret_key_1[i]) << 4) ^ (0xFF & secret_key_2[i])) & 0xFF);
+        }
+
+        byte[] mixedSecretKeyHash = CryptoUtils.digest(mixedSecretKey);
+        byte[] finalMixedKey = new byte[16];
+        for (int i = 0; i < 16; i++) {
+            finalMixedKey[i] = (byte)((((0xFF & mixedSecretKeyHash[i]) >> 6) ^ (0xFF & macAddressKey[i])) & 0xFF);
+        }
+        byte[] finalMixedKeyHash = CryptoUtils.digest(finalMixedKey);
+        return Arrays.copyOfRange(finalMixedKeyHash, 0, 16);
+    }
+
+    public byte[] encryptBondingKey(byte encryptMethod, byte[] data, byte[] encryptionKey, byte[] iv) throws NoSuchAlgorithmException, InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException, IllegalArgumentException {
+        if (encryptMethod == 0x01)
+            return CryptoUtils.encryptAES_GCM_NoPad(data, encryptionKey, iv, null);
+        return CryptoUtils.encryptAES_CBC_Pad(data, encryptionKey, iv);
+    }
+
+    public byte[] decryptBondingKey(byte encryptMethod, byte[] data, byte[] key, byte[] iv) throws NoSuchAlgorithmException, InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException, IllegalArgumentException {
+        if (encryptMethod == 0x1)
+            return CryptoUtils.decryptAES_GCM_NoPad(data, key, iv, null);
+        return CryptoUtils.decryptAES_CBC_Pad(data, key, iv);
+    }
+
+    public byte[] decryptPinCode(byte encryptMethod, byte[] message, byte[] iv) throws CryptoException {
+        byte[] secretKey = getDigestSecret();
+        try {
+            if (encryptMethod == 0x1)
+                return CryptoUtils.decryptAES_GCM_NoPad(message, secretKey, iv, null);
+            return CryptoUtils.decryptAES_CBC_Pad(message, secretKey, iv);
+        } catch (Exception e) {
+            throw new CryptoException(e);
+        }
+    }
+
+    public static byte[] encrypt(boolean useGCM, byte[] message, byte[] key, byte[] iv) throws CryptoException {
+        try {
+            if (useGCM)
+                return CryptoUtils.encryptAES_GCM_NoPad(message, key, iv, null);
+            return CryptoUtils.encryptAES_CBC_Pad(message, key, iv);
+        } catch (InvalidAlgorithmParameterException | NoSuchPaddingException | IllegalBlockSizeException | NoSuchAlgorithmException | BadPaddingException | InvalidKeyException | IllegalArgumentException e) {
+            throw new CryptoException(e);
+        }
+    }
+
+    public static byte[] decrypt(boolean useGCM, byte[] message, byte[] key, byte[] iv) throws CryptoException {
+        try {
+            if (useGCM)
+                return CryptoUtils.decryptAES_GCM_NoPad(message, key, iv, null);
+            return CryptoUtils.decryptAES_CBC_Pad(message, key, iv);
+        } catch (InvalidAlgorithmParameterException | NoSuchPaddingException | IllegalBlockSizeException | NoSuchAlgorithmException | BadPaddingException | InvalidKeyException | IllegalArgumentException e) {
+            throw new CryptoException(e);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiDictTypes.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiDictTypes.java
new file mode 100644
index 0000000..3d49e54
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiDictTypes.java
@@ -0,0 +1,10 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.huawei;
+
+public class HuaweiDictTypes {
+    public static final int BLOOD_PRESSURE_CLASS = 10002;
+
+    public static final int BODY_TEMPERATURE_CLASS = 400011;
+
+    public static final int SKIN_TEMPERATURE_CLASS = 400012;
+    public static final int SKIN_TEMPERATURE_VALUE = 400012430;
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiFreebudsCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiFreebudsCoordinator.java
new file mode 100644
index 0000000..a5d964e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiFreebudsCoordinator.java
@@ -0,0 +1,94 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.huawei;
+
+import androidx.annotation.NonNull;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.GBException;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettings;
+import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLClassicDeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.BatteryConfig;
+
+public abstract class HuaweiFreebudsCoordinator extends AbstractBLClassicDeviceCoordinator implements HuaweiCoordinatorSupplier {
+
+    private final HuaweiCoordinator huaweiCoordinator = new HuaweiCoordinator(this);
+    private GBDevice gbDevice;
+
+    public HuaweiFreebudsCoordinator() {
+        huaweiCoordinator.setTransactionCrypted(false);
+    }
+
+    @Override
+    protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
+        // TODO: implement
+    }
+
+    @Override
+    public String getManufacturer() {
+        return "Huawei";
+    }
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_nothingear;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_nothingear_disabled;
+    }
+
+    @Override
+    public HuaweiCoordinator getHuaweiCoordinator() {
+        return huaweiCoordinator;
+    }
+
+    @Override
+    public HuaweiDeviceType getHuaweiType() {
+        return HuaweiDeviceType.BR;
+    }
+
+    @Override
+    public void setDevice(GBDevice gbDevice) {
+        this.gbDevice = gbDevice;
+    }
+
+    @Override
+    public GBDevice getDevice() {
+        return this.gbDevice;
+    }
+
+    @Override
+    public int getBondingStyle() {
+        // TODO: Check if correct
+        return BONDING_STYLE_ASK;
+    }
+
+    @Override
+    public int getBatteryCount() {
+        return 3;
+    }
+
+    @Override
+    public BatteryConfig[] getBatteryConfig(GBDevice device) {
+        BatteryConfig battery1 = new BatteryConfig(2, R.drawable.ic_tws_case, R.string.battery_case);
+        BatteryConfig battery2 = new BatteryConfig(0, R.drawable.ic_nothing_ear_l, R.string.left_earbud);
+        BatteryConfig battery3 = new BatteryConfig(1, R.drawable.ic_nothing_ear_r, R.string.right_earbud);
+        return new BatteryConfig[]{battery1, battery2, battery3};
+    }
+
+    @Override
+    public DeviceSpecificSettings getDeviceSpecificSettings(GBDevice device) {
+        DeviceSpecificSettings deviceSpecificSettings = new DeviceSpecificSettings();
+        deviceSpecificSettings.addRootScreen(R.xml.devicesettings_huawei_freebuds);
+        deviceSpecificSettings.addRootScreen(R.xml.devicesettings_headphones);
+        return deviceSpecificSettings;
+    }
+
+    @Override
+    public boolean addBatteryPollingSettings() {
+        return true;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiGpsParser.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiGpsParser.java
new file mode 100644
index 0000000..44cd4dc
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiGpsParser.java
@@ -0,0 +1,101 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.huawei;
+
+import androidx.annotation.NonNull;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+
+public class HuaweiGpsParser {
+
+    public static class GpsPoint {
+        public int timestamp;
+        public double latitude;
+        public double longitude;
+        public boolean altitudeSupported;
+        public double altitude;
+
+        @NonNull
+        @Override
+        public String toString() {
+            return "GpsPoint{" +
+                    "timestamp=" + timestamp +
+                    ", longitude=" + longitude +
+                    ", latitude=" + latitude +
+                    ", altitudeSupported=" + altitudeSupported +
+                    ", altitude=" + altitude +
+                    '}';
+        }
+    }
+
+    public static GpsPoint[] parseHuaweiGps(byte[] data) {
+        ByteBuffer buffer = ByteBuffer.wrap(data);
+        buffer.order(ByteOrder.LITTLE_ENDIAN);
+
+        // Skip trim
+        buffer.position(0x20);
+
+        int timestamp;
+        double lon_start;
+        double lat_start;
+        boolean alt_support;
+        double alt_start;
+
+        byte fileType = buffer.get();
+        if ((fileType & 0x03) != 0x03) {
+            alt_support = false;
+            timestamp = buffer.getInt();
+            lon_start = buffer.getDouble();
+            lat_start = buffer.getDouble();
+            alt_start = 0;
+            buffer.position(62); // Skip past unknown fields/padding
+        } else {
+            alt_support = true;
+            timestamp = buffer.getInt();
+            lon_start = buffer.getDouble();
+            lat_start = buffer.getDouble();
+            alt_start = buffer.getDouble();
+            buffer.position(70); // Skip past unknown fields/padding
+        }
+
+        lat_start = lat_start * 0.017453292519943;
+        lon_start = lon_start * 0.017453292519943;
+
+        // Working values
+        int time = timestamp;
+        double lat = lat_start;
+        double lon = lon_start;
+        double alt = alt_start;
+
+        int data_size = 15;
+        if (alt_support)
+            data_size += 4;
+
+        ArrayList<GpsPoint> retv = new ArrayList<>(buffer.remaining() / data_size);
+        while (buffer.remaining() > data_size) {
+            short time_delta = buffer.getShort();
+            buffer.getShort(); // Unknown value
+            float lon_delta = buffer.getFloat();
+            float lat_delta = buffer.getFloat();
+            buffer.get(); buffer.get(); buffer.get(); // Unknown values
+
+            time = time + time_delta;
+            lat = lat + lat_delta;
+            lon = lon + lon_delta;
+
+            GpsPoint point = new GpsPoint();
+            point.timestamp = time;
+            point.latitude = (lat / 6383807.0d + lat_start) / 0.017453292519943d;
+            point.longitude = (lon / 6383807.0d / Math.cos(lat_start) + lon_start) / 0.017453292519943d;
+            point.altitudeSupported = alt_support;
+            if (alt_support) {
+                alt = buffer.getShort();
+                buffer.getShort(); // Unknown values
+                point.altitude = alt;
+            }
+            retv.add(point);
+        }
+
+        return retv.toArray(new GpsPoint[0]);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiInstallHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiInstallHandler.java
new file mode 100644
index 0000000..52ab1a8
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiInstallHandler.java
@@ -0,0 +1,272 @@
+/*  Copyright (C) 2024 Vitalii Tomin
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+
+package nodomain.freeyourgadget.gadgetbridge.devices.huawei;
+
+import android.content.Context;
+import android.net.Uri;
+import android.text.TextUtils;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Arrays;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.InstallActivity;
+import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
+
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.GenericItem;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiAppManager;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiFwHelper;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiMusicManager;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiWatchfaceManager;
+
+public class HuaweiInstallHandler implements InstallHandler {
+    private static final Logger LOG = LoggerFactory.getLogger(HuaweiInstallHandler.class);
+
+    private final Context context;
+    protected final HuaweiFwHelper helper;
+
+    boolean valid = false;
+
+    public HuaweiInstallHandler(Uri uri, Context context) {
+        this.context = context;
+        this.helper = new HuaweiFwHelper(uri, context);
+    }
+
+    private HuaweiMusicUtils.FormatRestrictions getRestriction(HuaweiMusicUtils.MusicCapabilities capabilities, String ext) {
+        List<HuaweiMusicUtils.FormatRestrictions> restrictions = capabilities.formatsRestrictions;
+        if(restrictions == null)
+            return null;
+
+        for(HuaweiMusicUtils.FormatRestrictions r: restrictions) {
+            if(ext.equals(r.getName())) {
+                return r;
+            }
+        }
+        return null;
+    }
+
+    //TODO: add proper checks
+    private boolean checkMediaCompatibility(HuaweiMusicUtils.MusicCapabilities capabilities, HuaweiMusicManager.AudioInfo currentMusicInfo) {
+        if(capabilities == null) {
+            LOG.error("No media info from device");
+            return false;
+        }
+        String ext = currentMusicInfo.getExtension();
+
+        List<String> supportedFormats = capabilities.supportedFormats;
+        if(supportedFormats == null) {
+            LOG.error("Format not supported {}", ext);
+            return false;
+        }
+        if(!supportedFormats.contains(ext)) {
+            LOG.error("Format not supported {}", ext);
+            return false;
+        }
+
+        HuaweiMusicUtils.FormatRestrictions restrictions = getRestriction(capabilities, ext);
+        if(restrictions == null) {
+            LOG.info("no restriction for: {}", ext);
+            return true;
+        }
+
+        LOG.info("bitrate {}", restrictions.bitrate);
+        LOG.info("channels {}", restrictions.channels);
+        LOG.info("musicEncode {}", restrictions.musicEncode);
+        LOG.info("sampleRate {}", Arrays.toString(restrictions.sampleRates));
+        LOG.info("unknownBitrate {}", restrictions.unknownBitrate);
+
+        if(currentMusicInfo.getChannels() > restrictions.channels) {
+            LOG.error("Not supported channels count {} > {}", currentMusicInfo.getChannels(), restrictions.channels);
+            return false;
+        }
+
+        //TODO: check other restrictions.
+
+        return true;
+    }
+
+
+    @Override
+    public void validateInstallation(InstallActivity installActivity, GBDevice device) {
+
+        final DeviceCoordinator coordinator = device.getDeviceCoordinator();
+        if (!(coordinator instanceof HuaweiCoordinatorSupplier)) {
+            LOG.warn("Coordinator is not a HuaweiCoordinatorSupplier: {}", coordinator.getClass());
+            installActivity.setInstallEnabled(false);
+            return;
+        }
+
+        if (helper.isWatchface()) {
+            final HuaweiCoordinatorSupplier huaweiCoordinatorSupplier = (HuaweiCoordinatorSupplier) coordinator;
+
+            HuaweiWatchfaceManager.WatchfaceDescription description = helper.getWatchfaceDescription();
+
+            HuaweiWatchfaceManager.Resolution resolution = new HuaweiWatchfaceManager.Resolution();
+            String deviceScreen = String.format("%d*%d", huaweiCoordinatorSupplier.getHuaweiCoordinator().getHeight(),
+                    huaweiCoordinatorSupplier.getHuaweiCoordinator().getWidth());
+            this.valid = resolution.isValid(description.screen, deviceScreen);
+
+            installActivity.setInstallEnabled(true);
+
+            GenericItem installItem = new GenericItem();
+
+            if (helper.getPreviewBitmap() != null) {
+                installItem.setPreview(helper.getPreviewBitmap());
+            }
+
+            installItem.setName(description.title);
+            installActivity.setInstallItem(installItem);
+            if (device.isBusy()) {
+                LOG.error("Watchface cannot be installed (device busy)");
+                installActivity.setInfoText("Watchface cannot be installed (device busy)");
+                installActivity.setInfoText(device.getBusyTask());
+                installActivity.setInstallEnabled(false);
+                return;
+            }
+
+            if (!device.isConnected()) {
+                LOG.error("Watchface cannot be installed (not connected or wrong device)");
+                installActivity.setInfoText("Watchface cannot be installed (not connected or wrong device)");
+                installActivity.setInstallEnabled(false);
+                return;
+            }
+
+            if (!this.valid) {
+                LOG.error("Watchface cannot be installed");
+                installActivity.setInfoText(context.getString(R.string.watchface_resolution_doesnt_match,
+                        resolution.screenByThemeVersion(description.screen), deviceScreen));
+                installActivity.setInstallEnabled(false);
+                return;
+            }
+
+            installItem.setDetails(description.version);
+
+            installItem.setIcon(R.drawable.ic_watchface);
+            installActivity.setInfoText(context.getString(R.string.watchface_install_info, installItem.getName(), description.version, description.author));
+
+            LOG.debug("Initialized HuaweiInstallHandler: Watchface");
+        } else if (helper.isAPP()) {
+            final HuaweiAppManager.AppConfig config = helper.getAppConfig();
+
+            this.valid = true; //NOTE: nothing to verify for now
+
+            installActivity.setInstallEnabled(true);
+
+            GenericItem installItem = new GenericItem();
+
+            if (helper.getPreviewBitmap() != null) {
+                installItem.setPreview(helper.getPreviewBitmap());
+            }
+
+            installItem.setName(config.bundleName);
+            installActivity.setInstallItem(installItem);
+            if (device.isBusy()) {
+                LOG.error("App cannot be installed (device busy)");
+                installActivity.setInfoText("App cannot be installed (device busy)");
+                installActivity.setInfoText(device.getBusyTask());
+                installActivity.setInstallEnabled(false);
+                return;
+            }
+
+            if (!device.isConnected()) {
+                LOG.error("App cannot be installed (not connected or wrong device)");
+                installActivity.setInfoText("App cannot be installed (not connected or wrong device)");
+                installActivity.setInstallEnabled(false);
+                return;
+            }
+
+            if (!this.valid) {
+                LOG.error("App cannot be installed");
+                installActivity.setInstallEnabled(false);
+                return;
+            }
+
+            installItem.setDetails(config.version);
+
+            installItem.setIcon(R.drawable.ic_watchapp);
+
+            installActivity.setInfoText(context.getString(R.string.app_install_info, installItem.getName(), config.version, config.vendor));
+
+            LOG.debug("Initialized HuaweiInstallHandler: App");
+        } else if (helper.isMusic()) {
+            final HuaweiCoordinatorSupplier huaweiCoordinatorSupplier = (HuaweiCoordinatorSupplier) coordinator;
+
+            HuaweiMusicUtils.MusicCapabilities capabilities = huaweiCoordinatorSupplier.getHuaweiCoordinator().getExtendedMusicInfoParams();
+            if(capabilities == null) {
+                capabilities = huaweiCoordinatorSupplier.getHuaweiCoordinator().getMusicInfoParams();
+            }
+            HuaweiMusicManager.AudioInfo currentMusicInfo = helper.getMusicInfo();
+
+            boolean isMediaCompatible = checkMediaCompatibility(capabilities, currentMusicInfo);
+
+            this.valid = isMediaCompatible && !TextUtils.isEmpty(helper.getMusicInfo().getFileName()) && !TextUtils.isEmpty(helper.getMusicInfo().getArtist()) && !TextUtils.isEmpty(helper.getMusicInfo().getTitle());
+
+            installActivity.setInstallEnabled(true);
+
+            GenericItem installItem = new GenericItem();
+
+            installItem.setName(helper.getFileName());
+            installActivity.setInstallItem(installItem);
+            if (device.isBusy()) {
+                LOG.error("Music cannot be uploaded (device busy)");
+                installActivity.setInfoText("Music cannot be uploaded (device busy)");
+                installActivity.setInfoText(device.getBusyTask());
+                installActivity.setInstallEnabled(false);
+                return;
+            }
+
+            if (!device.isConnected()) {
+                LOG.error("Music cannot be uploaded (not connected or wrong device)");
+                installActivity.setInfoText("Music cannot be uploaded (not connected or wrong device)");
+                installActivity.setInstallEnabled(false);
+                return;
+            }
+
+            if (!this.valid) {
+                LOG.error("Music cannot be uploaded");
+                installActivity.setInfoText("Music cannot be uploaded");
+                installActivity.setInstallEnabled(false);
+                return;
+            }
+
+            installItem.setDetails(helper.getMusicInfo().getFileName());
+
+            installItem.setIcon(R.drawable.ic_music);
+
+            installActivity.setInfoText(context.getString(R.string.music_upload_info, helper.getMusicInfo().getFileName(), helper.getMusicInfo().getTitle(), helper.getMusicInfo().getArtist()));
+
+            LOG.debug("Initialized HuaweiInstallHandler: Music");
+        }
+
+    }
+
+    @Override
+    public boolean isValid() {
+        return helper.isValid();
+    }
+
+    @Override
+    public void onStartInstall(GBDevice device) {
+        helper.unsetFwBytes();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiLECoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiLECoordinator.java
new file mode 100644
index 0000000..13d06cc
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiLECoordinator.java
@@ -0,0 +1,269 @@
+/*  Copyright (C) 2024 Damien Gaignon, Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+
+package nodomain.freeyourgadget.gadgetbridge.devices.huawei;
+
+import android.app.Activity;
+import android.bluetooth.le.ScanFilter;
+import android.content.Context;
+import android.net.Uri;
+import android.os.ParcelUuid;
+
+import androidx.annotation.NonNull;
+
+import java.util.Collection;
+import java.util.Collections;
+
+import nodomain.freeyourgadget.gadgetbridge.GBException;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettings;
+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.AbstractActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryParser;
+import nodomain.freeyourgadget.gadgetbridge.model.Spo2Sample;
+import nodomain.freeyourgadget.gadgetbridge.model.TemperatureSample;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiLESupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiWorkoutGbParser;
+
+public abstract class HuaweiLECoordinator extends AbstractBLEDeviceCoordinator implements HuaweiCoordinatorSupplier {
+
+    private final HuaweiCoordinator huaweiCoordinator = new HuaweiCoordinator(this);
+    private GBDevice device;
+
+    @Override
+    public HuaweiCoordinator getHuaweiCoordinator() {
+        return huaweiCoordinator;
+    }
+
+    @NonNull
+    @Override
+    public Collection<? extends ScanFilter> createBLEScanFilters() {
+        ParcelUuid huaweiService = new ParcelUuid(HuaweiConstants.UUID_SERVICE_HUAWEI_SERVICE);
+        ScanFilter filter = new ScanFilter.Builder().setServiceUuid(huaweiService).build();
+        return Collections.singletonList(filter);
+    }
+
+    @Override
+    public DeviceSpecificSettingsCustomizer getDeviceSpecificSettingsCustomizer(final GBDevice device) {
+        return new HuaweiSettingsCustomizer(device);
+    }
+
+    @Override
+    public String[] getSupportedLanguageSettings(GBDevice device) {
+        return huaweiCoordinator.getSupportedLanguageSettings(device);
+    }
+
+    @Override
+    public int[] getSupportedDeviceSpecificAuthenticationSettings() {
+        return new int[]{
+                R.xml.devicesettings_huawei_account,
+                R.xml.devicesettings_miband6_new_protocol
+        };
+    }
+
+    @Override
+    public int getBondingStyle(){
+        return BONDING_STYLE_NONE;
+    }
+
+    @Override
+    protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
+        huaweiCoordinator.deleteDevice(gbDevice, device, session);
+    }
+
+    @Override
+    public String getManufacturer() {
+        return "Huawei";
+    }
+
+    @Override
+    public boolean supportsSmartWakeup(GBDevice device, int position) {
+        return huaweiCoordinator.supportsSmartAlarm(device, position);
+    }
+
+    @Override
+    public boolean supportsSmartWakeupInterval(GBDevice device, int alarmPosition) {
+        return supportsSmartWakeup(device, alarmPosition);
+    }
+
+    @Override
+    public boolean forcedSmartWakeup(GBDevice device, int alarmPosition) {
+        return huaweiCoordinator.forcedSmartWakeup(device, alarmPosition);
+    }
+
+    @Override
+    public boolean supportsAlarmTitle(GBDevice device) {
+        return true;
+    }
+
+    @Override
+    public boolean supportsWeather() {
+        return huaweiCoordinator.supportsWeather();
+    }
+
+    @Override
+    public Class<? extends Activity> getAppsManagementActivity() {
+        return huaweiCoordinator.getAppManagerActivity();
+    }
+
+    @Override
+    public boolean supportsAppListFetching() {
+        return huaweiCoordinator.getSupportsAppListFetching();
+    }
+    @Override
+    public boolean supportsAppsManagement(GBDevice device) {
+        return huaweiCoordinator.getSupportsAppsManagement(device);
+    }
+
+    @Override
+    public boolean supportsWatchfaceManagement(GBDevice device) {
+        return supportsAppsManagement(device);
+    }
+
+    @Override
+    public boolean supportsInstalledAppManagement(GBDevice device) {
+        return huaweiCoordinator.getSupportsInstalledAppManagement(device);
+    }
+
+    @Override
+    public boolean supportsCachedAppManagement(GBDevice device) {
+        return huaweiCoordinator.getSupportsCachedAppManagement(device);
+    }
+
+    @Override
+    public int getAlarmSlotCount(GBDevice device) {
+        return huaweiCoordinator.getAlarmSlotCount(device);
+    }
+
+    @Override
+    public int getContactsSlotCount(GBDevice device) {
+        return huaweiCoordinator.getContactsSlotCount(device);
+    }
+
+    @Override
+    public boolean supportsCalendarEvents() {
+        return huaweiCoordinator.supportsCalendarEvents();
+    }
+
+    @Override
+    public boolean supportsActivityDataFetching() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsActiveCalories() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsActivityTracking() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsActivityTracks() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsHeartRateMeasurement(GBDevice device) {
+        return huaweiCoordinator.supportsHeartRate(device);
+    }
+
+    @Override
+    public boolean supportsSpo2(GBDevice device) {
+        return huaweiCoordinator.supportsSPo2(device);
+    }
+
+    @Override
+    public boolean supportsMusicInfo() {
+        return huaweiCoordinator.supportsMusic();
+    }
+
+    @Override
+    public boolean supportsTemperatureMeasurement() {
+        return huaweiCoordinator.supportsTemperature();
+    }
+
+    @Override
+    public boolean supportsContinuousTemperature() {
+        return huaweiCoordinator.supportsTemperature();
+    }
+
+    @Override
+    public InstallHandler findInstallHandler(Uri uri, Context context) {
+        return huaweiCoordinator.getInstallHandler(uri, context);
+    }
+
+    @Override
+    public ActivitySummaryParser getActivitySummaryParser(final GBDevice device, final Context context) {
+        return new HuaweiWorkoutGbParser(device, context);
+    }
+
+    @Override
+    public SampleProvider<? extends AbstractActivitySample> getSampleProvider(GBDevice device, DaoSession session) {
+        return new HuaweiSampleProvider(device, session);
+    }
+
+    @Override
+    public TimeSampleProvider<? extends Spo2Sample> getSpo2SampleProvider(GBDevice device, DaoSession session) {
+        return new HuaweiSpo2SampleProvider(device, session);
+    }
+
+    @Override
+    public TimeSampleProvider<? extends TemperatureSample> getTemperatureSampleProvider(final GBDevice device, final DaoSession session) {
+        return new HuaweiTemperatureSampleProvider(device, session);
+    }
+
+    public DeviceSpecificSettings getDeviceSpecificSettings(final GBDevice device) {
+        return huaweiCoordinator.getDeviceSpecificSettings(device);
+    }
+
+    @Override
+    public HuaweiDeviceType getHuaweiType() {
+        return HuaweiDeviceType.BLE;
+    }
+
+    @Override
+    public void setDevice(GBDevice device) {
+        this.device = device;
+    }
+
+    @Override
+    public GBDevice getDevice() {
+        return this.device;
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return HuaweiLESupport.class;
+    }
+
+    @Override
+    public boolean addBatteryPollingSettings() {
+        return true;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiMusicUtils.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiMusicUtils.java
new file mode 100644
index 0000000..31dfb78
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiMusicUtils.java
@@ -0,0 +1,130 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.huawei;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+public class HuaweiMusicUtils {
+
+    public static class PageStruct {
+        public short startFrame = 0;
+        public short endFrame = 0;
+        public short count = 0;
+        public byte[] hashCode = null;
+
+        @Override
+        public String toString() {
+            final StringBuffer sb = new StringBuffer("PageStruct{");
+            sb.append("startFrame=").append(startFrame);
+            sb.append(", endFrame=").append(endFrame);
+            sb.append(", count=").append(count);
+            sb.append(", hashCode=");
+            if (hashCode == null) sb.append("null");
+            else {
+                sb.append('[');
+                for (int i = 0; i < hashCode.length; ++i)
+                    sb.append(i == 0 ? "" : ", ").append(hashCode[i]);
+                sb.append(']');
+            }
+            sb.append('}');
+            return sb.toString();
+        }
+    }
+
+    public static class FormatRestrictions {
+        public byte formatIdx = -1;
+        public String[] sampleRates = null;
+        public byte musicEncode = -1;  // TODO: not sure
+        public short bitrate = -1;
+        public byte channels = 2;
+        public short unknownBitrate = -1;   // TODO: not sure
+
+        // TODO: I am not sure about this. Most of formats unknown for me.
+        private static final String[] formats = {"mp3", "wav", "aac", "sbc", "msbc", "hwa", "cvsd", "pcm", "ape", "m4a", "flac", "opus", "ogg", "butt", "amr", "imy"};
+
+        public String getName() {
+            return (formatIdx >= 0 && formatIdx < formats.length)?formats[formatIdx]:null;
+        }
+
+        @Override
+        public String toString() {
+            final StringBuffer sb = new StringBuffer("FormatRestrictions{");
+            sb.append("formatIdx=").append(formatIdx);
+            sb.append(", sampleRates=").append(Arrays.toString(sampleRates));
+            sb.append(", musicEncode=").append(musicEncode);
+            sb.append(", bitrate=").append(bitrate);
+            sb.append(", channels=").append(channels);
+            sb.append(", unknownBitrate=").append(unknownBitrate);
+            sb.append('}');
+            return sb.toString();
+        }
+    }
+
+    public static class MusicCapabilities {
+        public int availableSpace = 0;
+        public List<String> supportedFormats = null;
+        public int maxMusicCount = 0;
+        public int maxPlaylistCount = 0;
+        public int currentMusicCount = 0; // TODO: not sure
+        public int unknown = 0; // TODO: not sure
+        public List<FormatRestrictions> formatsRestrictions = null;
+
+        @Override
+        public String toString() {
+            final StringBuffer sb = new StringBuffer("MusicCapabilities{");
+            sb.append("availableSpace=").append(availableSpace);
+            sb.append(", supportedFormats=").append(supportedFormats);
+            sb.append(", maxMusicCount=").append(maxMusicCount);
+            sb.append(", maxPlaylistCount=").append(maxPlaylistCount);
+            sb.append(", currentMusicCount=").append(currentMusicCount);
+            sb.append(", unknown=").append(unknown);
+            sb.append(", formatsRestrictions=").append(formatsRestrictions);
+            sb.append('}');
+            return sb.toString();
+        }
+    }
+
+
+    // TODO: I am not sure about this. Most of formats unknown for me.
+    private static final String[] formatsByte0 = {"mp3", "wav", "aac", "sbc", "msbc", "hwa", "cvsd"};
+    private static final String[] formatsByte1 = {"pcm", "ape", "m4a", "flac", "opus", "ogg", "butt"};
+    private static final String[] formatsByte2 = {"amr", "imy"};
+
+    public static void parseNext(int dt, String[] info, List<String> res) {
+        for (byte k = 0; k < info.length; k++) {
+            if ((dt & (1 << k)) != 0) {
+                res.add(info[k]);
+            }
+        }
+    }
+
+    public static List<String> parseFormats(List<Integer> data) {
+        List<String> res = new ArrayList<>();
+
+        if (data.size() >= 1) {
+            parseNext(data.get(0), formatsByte0, res);
+        }
+
+        if (data.size() >= 2) {
+            parseNext(data.get(1), formatsByte1, res);
+        }
+
+        if (data.size() >= 3) {
+            parseNext(data.get(2), formatsByte2, res);
+        }
+
+        return res;
+    }
+
+    public static List<String> parseFormatBits(byte[] formatBits) {
+        if (formatBits.length == 0)
+            return null;
+        List<Integer> toDecode = new ArrayList<>();
+        for (byte formatBit : formatBits) {
+            int dt = formatBit & 0xFF;
+            toDecode.add(dt);
+            if ((dt & 128) == 0) break;
+        }
+        return parseFormats(toDecode);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiPacket.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiPacket.java
new file mode 100644
index 0000000..ac07495
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiPacket.java
@@ -0,0 +1,1006 @@
+/*  Copyright (C) 2024 Damien Gaignon, Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+
+package nodomain.freeyourgadget.gadgetbridge.devices.huawei;
+
+import static nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiConstants.HUAWEI_MAGIC;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Alarms;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.AccountRelated;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.App;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Calls;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.CameraRemote;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Contacts;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Earphones;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.EphemerisFileUpload;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FileDownloadService0A;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FileDownloadService2C;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.GpsAndTime;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.P2P;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Watchface;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Weather;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Workout;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.DeviceConfig;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FindPhone;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FitnessData;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.MusicControl;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Notifications;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FileUpload;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Ephemeris;
+import nodomain.freeyourgadget.gadgetbridge.util.CheckSums;
+
+public class HuaweiPacket {
+    private static final Logger LOG = LoggerFactory.getLogger(HuaweiPacket.class);
+
+    public static class ParamsProvider {
+
+        protected byte authVersion;
+        protected byte deviceSupportType;
+        protected byte[] secretKey;
+        protected int slicesize = 0xf4;
+        protected boolean transactionsCrypted = true;
+        protected int mtu = 65535;
+        protected long encryptionCounter = 0;
+
+        protected byte[] pinCode = null;
+
+        protected byte interval;
+        protected byte authAlgo;
+        protected byte encryptMethod;
+        protected byte[] firstKey;
+        protected  byte authMode;
+
+        public void setAuthVersion(byte authVersion) {
+            this.authVersion = authVersion;
+        }
+
+        public byte getAuthVersion() {
+            return this.authVersion;
+        }
+
+        public void setDeviceSupportType(byte deviceSupportType) {
+            this.deviceSupportType = deviceSupportType;
+        }
+
+        public byte getDeviceSupportType(){
+            return this.deviceSupportType;
+        }
+
+        public void setSecretKey(byte[] secretKey) {
+            this.secretKey = secretKey;
+        }
+
+        public byte[] getSecretKey() {
+            return this.secretKey;
+        }
+
+        public void setTransactionsCrypted(boolean transactionsCrypted) {
+            this.transactionsCrypted = transactionsCrypted;
+        }
+
+        public boolean areTransactionsCrypted() {
+            return this.transactionsCrypted;
+        }
+
+        public void setMtu(int mtu) {
+            this.mtu = mtu;
+        }
+
+        public int getMtu() {
+            return this.mtu;
+        }
+
+        public void setSliceSize(int sliceSize) {
+            this.slicesize = sliceSize;
+        }
+
+        public int getSliceSize() {
+            return this.slicesize;
+        }
+        public void setPinCode(byte[] pinCode) {
+            this.pinCode = pinCode;
+        }
+
+        public byte[] getPinCode() {
+            return this.pinCode;
+        }
+
+        public void setInterval(byte interval) {
+            this.interval = interval;
+        }
+
+        public byte getInterval() {
+            return this.interval;
+        }
+
+        public byte getAuthMode() { return this.authMode; }
+
+        public void setAuthMode(byte authMode) { this.authMode = authMode; }
+
+        public byte[] getIv() {
+            byte[] iv = null;
+            if (this.deviceSupportType == 0x04) {
+                iv = HuaweiCrypto.generateNonce();
+            } else {
+                ByteBuffer ivCounter = HuaweiCrypto.initializationVector(this.encryptionCounter);
+                iv = ivCounter.array();
+                this.encryptionCounter = (long)ivCounter.getInt(12) & 0xFFFFFFFFL;
+            }
+            return iv;
+        }
+
+        public void setEncryptionCounter(long counter) {
+            this.encryptionCounter = counter;
+        }
+
+        public void setAuthAlgo(byte authAlgo) {
+            this.authAlgo = authAlgo;
+        }
+
+        public byte getAuthAlgo () {
+            return this.authAlgo;
+        }
+
+        public void setEncryptMethod(byte encryptMethod) {
+            this.encryptMethod = encryptMethod;
+        }
+
+        public byte getEncryptMethod () {
+            return this.encryptMethod;
+        }
+
+        public void setFirstKey(byte[] firstKey) {
+            this.firstKey = firstKey;
+        }
+
+        public byte[] getFirstKey() {
+            return firstKey;
+        }
+    }
+
+    public static abstract class ParseException extends Exception {
+        ParseException(String message) {
+            super(message);
+        }
+
+        ParseException(String message, Exception e) {
+            super(message, e);
+        }
+    }
+
+    public static class LengthMismatchException extends ParseException {
+        public LengthMismatchException(String message) {
+            super(message);
+        }
+    }
+
+    public static class MagicMismatchException extends ParseException {
+        MagicMismatchException(String message) {
+            super(message);
+        }
+    }
+
+    public static class ChecksumIncorrectException extends ParseException {
+        ChecksumIncorrectException(String message) {
+            super(message);
+        }
+    }
+
+    public static class MissingTagException extends ParseException {
+        public MissingTagException(int tag) {
+            super("Missing tag: " + Integer.toHexString(tag));
+        }
+    }
+
+    public static class CryptoException extends ParseException {
+        public CryptoException(String message, Exception e) {
+            super(message, e);
+        }
+    }
+
+    public static class JsonException extends ParseException {
+        public JsonException(String message, Exception e) {
+            super(message, e);
+        }
+    }
+
+    public static class SupportedCommandsListException extends ParseException {
+        public SupportedCommandsListException(String message) {
+            super(message);
+        }
+    }
+
+    public static class SerializeException extends Exception {
+        public SerializeException(String message) {
+            super(message);
+        }
+        public SerializeException(String message, Exception e) {
+            super(message, e);
+        }
+    }
+
+    protected static final int PACKET_MINIMAL_SIZE = 3; // magic + data size
+
+    protected ParamsProvider paramsProvider;
+
+    public byte serviceId = 0;
+    public byte commandId = 0;
+    protected HuaweiTLV tlv = null;
+
+    private byte[] partialPacket = null;
+    protected byte[] payload = null;
+
+    public boolean complete = false;
+
+    // Encryption is enabled by default, packets which don't use it must disable it
+    protected boolean isEncrypted = true;
+
+    protected boolean isSliced = true;
+
+    public HuaweiPacket(ParamsProvider paramsProvider) {
+        this.paramsProvider = paramsProvider;
+    }
+
+    public boolean attemptDecrypt() throws ParseException {
+        if (paramsProvider == null || paramsProvider.getSecretKey() == null)
+            return false;
+        if (this.tlv == null)
+            return false;
+        if (this.tlv.contains(0x7C) && this.tlv.getBoolean(0x7C)) {
+            try {
+                this.tlv.decrypt(paramsProvider);
+                return true;
+            } catch (HuaweiCrypto.CryptoException e) {
+                throw new CryptoException("Decrypt exception", e);
+            }
+        } else {
+            if (this.isEncrypted && paramsProvider.areTransactionsCrypted()) {
+                // TODO: potentially a log message? We expect it to be encrypted, but it isn't.
+            }
+        }
+        return false;
+    }
+
+    /*
+     * This function is to convert the Packet into the proper subclass
+     */
+    protected HuaweiPacket fromPacket(HuaweiPacket packet) throws ParseException {
+        this.paramsProvider = packet.paramsProvider;
+        this.serviceId = packet.serviceId;
+        this.commandId = packet.commandId;
+        this.tlv = packet.tlv;
+        this.partialPacket = packet.partialPacket;
+        this.payload = packet.payload;
+        this.complete = packet.complete;
+        this.left = packet.left;
+
+        if (packet.isEncrypted)
+            this.isEncrypted = true;
+        else
+            this.isEncrypted = this.attemptDecrypt();
+
+        return this;
+    }
+
+    /*
+     * This function is to set up the subclass for easy usage
+     * Needs to be called separately so the exceptions can be used more easily
+     */
+    public void parseTlv() throws ParseException {}
+
+    private int left = 0;
+
+    public int getLeft() {
+        return this.left;
+    }
+
+    private void parseData(byte[] data) throws ParseException {
+        this.left = 0;
+        if (partialPacket != null) {
+            int newCapacity = partialPacket.length + data.length;
+            data = ByteBuffer.allocate(newCapacity)
+                    .put(partialPacket)
+                    .put(data)
+                    .array();
+        }
+
+        ByteBuffer buffer = ByteBuffer.wrap(data);
+        if (buffer.capacity() < 1) {
+            throw new LengthMismatchException("Packet length mismatch : "
+                    + buffer.capacity()
+                    + " < 1");
+        }
+
+        byte magic = buffer.get();
+
+        if (magic != HUAWEI_MAGIC) {
+            throw new MagicMismatchException("Magic mismatch : "
+                    + Integer.toHexString(magic)
+                    + " != 0x5A");
+        }
+
+        if (buffer.capacity() < PACKET_MINIMAL_SIZE) {
+            this.partialPacket = data;
+            return;
+        }
+
+        short expectedSize = buffer.getShort();
+
+        if(expectedSize < 0) {
+            throw new LengthMismatchException("Expected length mismatch : " + expectedSize);
+        }
+
+        if (expectedSize + 2 > buffer.remaining()) {
+            // Older band and BT version do not handle message with more than 256 bits.
+            this.partialPacket = data;
+            return;
+        }
+
+        this.partialPacket = null;
+
+        int addLen = 1;
+        int isSliced = buffer.get();
+        if (isSliced == 1 || isSliced == 2 || isSliced == 3) {
+            buffer.get(); // Throw away slice flag
+            addLen++;
+        }
+
+        byte[] newPayload = new byte[expectedSize - addLen];
+        buffer.get(newPayload, 0, expectedSize - addLen);
+        short expectedChecksum = buffer.getShort();
+        this.left = buffer.remaining();
+        buffer.rewind();
+
+        byte[] dataNoCRC = new byte[expectedSize + 3];
+        buffer.get(dataNoCRC, 0, expectedSize + 3);
+        short actualChecksum = (short) CheckSums.getCRC16(dataNoCRC, 0x0000);
+        if (actualChecksum != expectedChecksum) {
+            throw new ChecksumIncorrectException("Checksum mismatch : "
+                    + String.valueOf(actualChecksum)
+                    + " != "
+                    + String.valueOf(expectedChecksum));
+        }
+
+        if (isSliced == 1 || isSliced == 2 || isSliced == 3) {
+            if (payload != null) {
+                int newCapacity = payload.length + newPayload.length;
+                newPayload = ByteBuffer.allocate(newCapacity)
+                        .put(payload)
+                        .put(newPayload)
+                        .array();
+            }
+
+            if (isSliced != 3) {
+                // Sliced packet isn't complete yet
+                this.payload = newPayload;
+                return;
+            }
+        }
+
+        this.serviceId = newPayload[0];
+        this.commandId = newPayload[1];
+        this.complete = true;
+
+        if (
+                (serviceId == 0x0a && commandId == 0x05) ||
+                (serviceId == 0x28 && commandId == 0x06) ||
+                (serviceId == 0x2c && commandId == 0x05) ||
+                (serviceId == 0x1c && commandId == 0x05)
+        ) {
+            // TODO: this doesn't seem to be TLV
+            this.payload = newPayload;
+            return;
+        }
+
+        this.tlv = new HuaweiTLV();
+        this.tlv.parse(newPayload, 2, newPayload.length - 2);
+    }
+
+    public HuaweiPacket parse(byte[] data) throws ParseException {
+        this.isEncrypted = false; // Will be changed if decrypt has been performed
+
+        parseData(data);
+        if (!this.complete)
+            return this;
+
+        switch (this.serviceId) {
+            case DeviceConfig.id:
+                switch (this.commandId) {
+                    case DeviceConfig.LinkParams.id:
+                        return new DeviceConfig.LinkParams.Response(paramsProvider).fromPacket(this);
+                    case DeviceConfig.SupportedServices.id:
+                        return new DeviceConfig.SupportedServices.Response(paramsProvider).fromPacket(this);
+                    case DeviceConfig.SupportedCommands.id:
+                        return new DeviceConfig.SupportedCommands.Response(paramsProvider).fromPacket(this);
+                    case DeviceConfig.TimeRequest.id:
+                        return new DeviceConfig.TimeRequest.Response(paramsProvider).fromPacket(this);
+                    case DeviceConfig.ProductInfo.id:
+                        return new DeviceConfig.ProductInfo.Response(paramsProvider).fromPacket(this);
+                    case DeviceConfig.BondParams.id:
+                        return new DeviceConfig.BondParams.Response(paramsProvider).fromPacket(this);
+                    case DeviceConfig.PhoneInfo.id:
+                        return new DeviceConfig.PhoneInfo.Response(paramsProvider).fromPacket(this);
+                    case DeviceConfig.Auth.id:
+                        return new DeviceConfig.Auth.Response(paramsProvider).fromPacket(this);
+                    case DeviceConfig.BatteryLevel.id:
+                    case DeviceConfig.BatteryLevel.id_change:
+                        return new DeviceConfig.BatteryLevel.Response(paramsProvider).fromPacket(this);
+                    case DeviceConfig.DeviceStatus.id:
+                        return new DeviceConfig.DeviceStatus.Response(paramsProvider).fromPacket(this);
+                    case DeviceConfig.DndLiftWristType.id:
+                        return new DeviceConfig.DndLiftWristType.Response(paramsProvider).fromPacket(this);
+                    case DeviceConfig.HiChain.id:
+                        return new DeviceConfig.HiChain.Response(paramsProvider).fromPacket(this);
+                    case DeviceConfig.PinCode.id:
+                        return new DeviceConfig.PinCode.Response(paramsProvider).fromPacket(this);
+                    case DeviceConfig.ExpandCapability.id:
+                        return new DeviceConfig.ExpandCapability.Response(paramsProvider).fromPacket(this);
+                    case DeviceConfig.ActivityType.id:
+                        return new DeviceConfig.ActivityType.Response(paramsProvider).fromPacket(this);
+                    case DeviceConfig.SettingRelated.id:
+                        return new DeviceConfig.SettingRelated.Response(paramsProvider).fromPacket(this);
+                    case DeviceConfig.SecurityNegotiation.id:
+                        return new DeviceConfig.SecurityNegotiation.Response(paramsProvider).fromPacket(this);
+                    case DeviceConfig.WearStatus.id:
+                        return new DeviceConfig.WearStatus.Response(paramsProvider).fromPacket(this);
+
+                    // Camera remote has same ID as DeviceConfig
+                    case CameraRemote.CameraRemoteStatus.id:
+                        return new CameraRemote.CameraRemoteStatus.Response(paramsProvider).fromPacket(this);
+
+                    default:
+                        this.isEncrypted = this.attemptDecrypt(); // Helps with debugging
+                        return this;
+                }
+            case Notifications.id:
+                switch (this.commandId) {
+                    case Notifications.NotificationConstraints.id:
+                        return new Notifications.NotificationConstraints.Response(paramsProvider).fromPacket(this);
+                    case Notifications.NotificationCapabilities.id:
+                        return new Notifications.NotificationCapabilities.Response(paramsProvider).fromPacket(this);
+                    default:
+                        return this;
+                }
+            case Contacts.id:
+                switch (this.commandId) {
+                    case Contacts.ContactsSet.id:
+                        return new Contacts.ContactsSet.Response(paramsProvider).fromPacket(this);
+                    case Contacts.ContactsCount.id:
+                        return new Contacts.ContactsCount.Response(paramsProvider).fromPacket(this);
+                }
+                this.isEncrypted = this.attemptDecrypt(); // Helps with debugging
+                return this;
+            case Calls.id:
+                if (this.commandId == Calls.AnswerCallResponse.id)
+                    return new Calls.AnswerCallResponse(paramsProvider).fromPacket(this);
+                this.isEncrypted = this.attemptDecrypt(); // Helps with debugging
+                return this;
+            case FitnessData.id:
+                switch (this.commandId) {
+                    case FitnessData.FitnessTotals.id:
+                        return new FitnessData.FitnessTotals.Response(paramsProvider).fromPacket(this);
+                    case FitnessData.MessageCount.stepId:
+                        return new FitnessData.MessageCount.Response(paramsProvider).fromPacket(this);
+                    case FitnessData.MessageData.stepId:
+                        return new FitnessData.MessageData.StepResponse(paramsProvider).fromPacket(this);
+                    case FitnessData.MessageCount.sleepId:
+                        return new FitnessData.MessageCount.Response(paramsProvider).fromPacket(this);
+                    case FitnessData.MessageData.sleepId:
+                        return new FitnessData.MessageData.SleepResponse(paramsProvider).fromPacket(this);
+                    case FitnessData.RunPaceConfig.id:
+                        return new FitnessData.RunPaceConfig.Response(paramsProvider).fromPacket(this);
+                    default:
+                        this.isEncrypted = this.attemptDecrypt(); // Helps with debugging
+                        return this;
+                }
+            case Alarms.id:
+                switch (this.commandId) {
+                    case Alarms.EventAlarmsList.id:
+                        return new Alarms.EventAlarmsList.Response(paramsProvider).fromPacket(this);
+                    case Alarms.SmartAlarmList.id:
+                        return new Alarms.SmartAlarmList.Response(paramsProvider).fromPacket(this);
+                    default:
+                        this.isEncrypted = this.attemptDecrypt(); // Helps with debugging
+                        return this;
+                }
+            case FileDownloadService0A.id:
+                switch (this.commandId) {
+                    case FileDownloadService0A.FileDownloadInit.id:
+                        return new FileDownloadService0A.FileDownloadInit.Response(paramsProvider).fromPacket(this);
+                    case FileDownloadService0A.FileParameters.id:
+                        return new FileDownloadService0A.FileParameters.Response(paramsProvider).fromPacket(this);
+                    case FileDownloadService0A.FileInfo.id:
+                        return new FileDownloadService0A.FileInfo.Response(paramsProvider).fromPacket(this);
+                    case FileDownloadService0A.RequestBlock.id:
+                        return new FileDownloadService0A.RequestBlock.Response(paramsProvider).fromPacket(this);
+                    case FileDownloadService0A.BlockResponse.id:
+                        return new FileDownloadService0A.BlockResponse(paramsProvider).fromPacket(this);
+                    default:
+                        this.isEncrypted = this.attemptDecrypt();
+                        return this;
+                }
+            case FindPhone.id:
+                if (this.commandId == FindPhone.Response.id)
+                    return new FindPhone.Response(paramsProvider).fromPacket(this);
+                this.isEncrypted = this.attemptDecrypt(); // Helps with debugging
+                return this;
+            case Weather.id:
+                switch (this.commandId) {
+                    case Weather.WeatherSupport.id:
+                        return new Weather.WeatherSupport.Response(paramsProvider).fromPacket(this);
+                    case Weather.WeatherExtendedSupport.id:
+                        return new Weather.WeatherExtendedSupport.Response(paramsProvider).fromPacket(this);
+                    case Weather.WeatherStart.id:
+                        return new Weather.WeatherStart.Response(paramsProvider).fromPacket(this);
+                    case Weather.WeatherSunMoonSupport.id:
+                        return new Weather.WeatherSunMoonSupport.Response(paramsProvider).fromPacket(this);
+                    default:
+                        this.isEncrypted = this.attemptDecrypt(); // Helps with debugging
+                        return this;
+                }
+            case Workout.id:
+                switch (this.commandId) {
+                    case Workout.WorkoutCount.id:
+                        return new Workout.WorkoutCount.Response(paramsProvider).fromPacket(this);
+                    case Workout.WorkoutTotals.id:
+                        return new Workout.WorkoutTotals.Response(paramsProvider).fromPacket(this);
+                    case Workout.WorkoutData.id:
+                        return new Workout.WorkoutData.Response(paramsProvider).fromPacket(this);
+                    case Workout.WorkoutPace.id:
+                        return new Workout.WorkoutPace.Response(paramsProvider).fromPacket(this);
+                    case Workout.WorkoutSwimSegments.id:
+                        return new Workout.WorkoutSwimSegments.Response(paramsProvider).fromPacket(this);
+                    default:
+                        this.isEncrypted = this.attemptDecrypt(); // Helps with debugging
+                        return this;
+                }
+            case GpsAndTime.id:
+                switch (this.commandId) {
+                    case GpsAndTime.GpsParameters.id:
+                        return new GpsAndTime.GpsParameters.Response(paramsProvider).fromPacket(this);
+                    case GpsAndTime.GpsStatus.id:
+                        return new GpsAndTime.GpsStatus.Response(paramsProvider).fromPacket(this);
+                    case GpsAndTime.GpsData.id:
+                        return new GpsAndTime.GpsData.Response(paramsProvider).fromPacket(this);
+                    default:
+                        this.isEncrypted = this.attemptDecrypt(); // Helps with debugging
+                        return this;
+                }
+            case MusicControl.id:
+                switch (this.commandId) {
+                    case MusicControl.MusicStatusResponse.id:
+                        return new MusicControl.MusicStatusResponse(paramsProvider).fromPacket(this);
+                    case MusicControl.MusicInfo.id:
+                        return new MusicControl.MusicInfo.Response(paramsProvider).fromPacket(this);
+                    case MusicControl.Control.id:
+                        return new MusicControl.Control.Response(paramsProvider).fromPacket(this);
+                    case MusicControl.MusicInfoParams.id:
+                        return new MusicControl.MusicInfoParams.Response(paramsProvider).fromPacket(this);
+                    case MusicControl.MusicList.id:
+                        return new MusicControl.MusicList.Response(paramsProvider).fromPacket(this);
+                    case MusicControl.MusicPlaylists.id:
+                        return new MusicControl.MusicPlaylists.Response(paramsProvider).fromPacket(this);
+                    case MusicControl.MusicPlaylistMusics.id:
+                        return new MusicControl.MusicPlaylistMusics.Response(paramsProvider).fromPacket(this);
+                    case MusicControl.MusicOperation.id:
+                        return new MusicControl.MusicOperation.Response(paramsProvider).fromPacket(this);
+                    case MusicControl.UploadMusicFileInfo.id:
+                        return new MusicControl.UploadMusicFileInfo.UploadMusicFileInfoRequest(paramsProvider).fromPacket(this);
+                    case MusicControl.ExtendedMusicInfoParams.id:
+                        return new MusicControl.ExtendedMusicInfoParams.Response(paramsProvider).fromPacket(this);
+                    default:
+                        this.isEncrypted = this.attemptDecrypt(); // Helps with debugging
+                        return this;
+                }
+            case AccountRelated.id:
+                switch(this.commandId) {
+                    case AccountRelated.SendAccountToDevice.id:
+                        return new AccountRelated.SendAccountToDevice.Response(paramsProvider).fromPacket(this);
+                    case AccountRelated.SendExtendedAccountToDevice.id:
+                        return new AccountRelated.SendExtendedAccountToDevice.Response(paramsProvider).fromPacket(this);
+                    default:
+                        this.isEncrypted = this.attemptDecrypt(); // Helps with debugging
+                        return this;
+                }
+            case FileUpload.id:
+                switch(this.commandId) {
+                    case FileUpload.FileInfoSend.id:
+                        return new FileUpload.FileInfoSend.Response(paramsProvider).fromPacket(this);
+                    case FileUpload.FileHashSend.id:
+                        return new FileUpload.FileHashSend.Response(paramsProvider).fromPacket(this);
+                    case FileUpload.FileUploadConsultAck.id:
+                        return new FileUpload.FileUploadConsultAck.Response(paramsProvider).fromPacket(this);
+                    case FileUpload.FileNextChunkParams.id:
+                        return new FileUpload.FileNextChunkParams(paramsProvider).fromPacket(this);
+                    default:
+                        this.isEncrypted = this.attemptDecrypt(); // Helps with debugging
+                        return this;
+                }
+            case Watchface.id:
+                switch (this.commandId) {
+                    case Watchface.WatchfaceParams.id:
+                        return new Watchface.WatchfaceParams.Response(paramsProvider).fromPacket(this);
+                    case Watchface.DeviceWatchInfo.id:
+                        return new Watchface.DeviceWatchInfo.Response(paramsProvider).fromPacket(this);
+                    case Watchface.WatchfaceConfirm.id:
+                        return new Watchface.WatchfaceConfirm.Response(paramsProvider).fromPacket(this);
+                    case Watchface.WatchfaceNameInfo.id:
+                        return new Watchface.WatchfaceNameInfo.Response(paramsProvider).fromPacket(this);
+                    default:
+                        this.isEncrypted = this.attemptDecrypt(); // Helps with debugging
+                        return this;
+                }
+            case Earphones.id:
+                switch (this.commandId) {
+                    case Earphones.InEarStateResponse.id:
+                        return new Earphones.InEarStateResponse(paramsProvider).fromPacket(this);
+                    case Earphones.GetAudioModeRequest.id:
+                        return new Earphones.GetAudioModeRequest.Response(paramsProvider).fromPacket(this);
+                }
+            case FileDownloadService2C.id:
+                switch (this.commandId) {
+                    case FileDownloadService2C.FileDownloadInit.id:
+                        return new FileDownloadService2C.FileDownloadInit.Response(paramsProvider).fromPacket(this);
+                    case FileDownloadService2C.FileInfo.id:
+                        return new FileDownloadService2C.FileInfo.Response(paramsProvider).fromPacket(this);
+                    case FileDownloadService2C.BlockResponse.id:
+                        return new FileDownloadService2C.BlockResponse(paramsProvider).fromPacket(this);
+                    default:
+                        this.isEncrypted = this.attemptDecrypt(); // Helps with debugging
+                        return this;
+                }
+            case App.id:
+                switch (this.commandId) {
+                    case App.AppNames.id:
+                        return new App.AppNames.Response(paramsProvider).fromPacket(this);
+                    case App.AppInfoParams.id:
+                        return new App.AppInfoParams.Response(paramsProvider).fromPacket(this);
+                    default:
+                        this.isEncrypted = this.attemptDecrypt(); // Helps with debugging
+                        return this;
+                }
+            case P2P.id:
+                    switch (this.commandId) {
+                        case P2P.P2PCommand.id:
+                            return new P2P.P2PCommand.Response(paramsProvider).fromPacket(this);
+                        default:
+                            this.isEncrypted = this.attemptDecrypt(); // Helps with debugging
+                            return this;
+                    }
+            case Ephemeris.id:
+                switch (this.commandId) {
+                    case Ephemeris.OperatorData.id:
+                        return new Ephemeris.OperatorData.OperatorIncomingRequest(paramsProvider).fromPacket(this);
+                    case Ephemeris.ParameterConsult.id:
+                        return new Ephemeris.ParameterConsult.Response(paramsProvider).fromPacket(this);
+                    case Ephemeris.FileStatus.id:
+                        return new Ephemeris.FileStatus.Response(paramsProvider).fromPacket(this);
+                    default:
+                        this.isEncrypted = this.attemptDecrypt(); // Helps with debugging
+                        return this;
+                }
+            case EphemerisFileUpload.id:
+                switch (this.commandId) {
+                    case EphemerisFileUpload.FileList.id:
+                        return new EphemerisFileUpload.FileList.FileListIncomingRequest(paramsProvider).fromPacket(this);
+                    case EphemerisFileUpload.FileConsult.id:
+                        return new EphemerisFileUpload.FileConsult.FileConsultIncomingRequest(paramsProvider).fromPacket(this);
+                    case EphemerisFileUpload.QuerySingleFileInfo.id:
+                        return new EphemerisFileUpload.QuerySingleFileInfo.QuerySingleFileInfoIncomingRequest(paramsProvider).fromPacket(this);
+                    case EphemerisFileUpload.DataRequest.id:
+                        return new EphemerisFileUpload.DataRequest.DataRequestIncomingRequest(paramsProvider).fromPacket(this);
+                    case EphemerisFileUpload.UploadData.id:
+                        return new EphemerisFileUpload.UploadData.UploadDataResponse(paramsProvider).fromPacket(this);
+                    case EphemerisFileUpload.UploadDone.id:
+                        return new EphemerisFileUpload.UploadDone.UploadDoneIncomingRequest(paramsProvider).fromPacket(this);
+                    default:
+                        this.isEncrypted = this.attemptDecrypt(); // Helps with debugging
+                        return this;
+                }
+            default:
+                this.isEncrypted = this.attemptDecrypt(); // Helps with debugging
+                return this;
+        }
+    }
+
+    public HuaweiPacket parseOutgoing(byte[] data) throws ParseException {
+        this.isEncrypted = false; // Will be changed if decrypt has been performed
+
+        parseData(data);
+        if (!this.complete)
+            return this;
+
+        // TODO: complete
+
+        switch (this.serviceId) {
+            case DeviceConfig.id:
+                switch (this.commandId) {
+                    case DeviceConfig.SupportedServices.id:
+                        return new DeviceConfig.SupportedServices.OutgoingRequest(paramsProvider).fromPacket(this);
+                    case DeviceConfig.DateFormat.id:
+                        return new DeviceConfig.DateFormat.OutgoingRequest(paramsProvider).fromPacket(this);
+                    case DeviceConfig.Bond.id:
+                        return new DeviceConfig.Bond.OutgoingRequest(paramsProvider).fromPacket(this);
+                    case DeviceConfig.HiChain.id:
+                        return new DeviceConfig.HiChain.OutgoingRequest(paramsProvider).fromPacket(this);
+                    case DeviceConfig.Auth.id:
+                        return new DeviceConfig.Auth.OutgoingRequest(paramsProvider).fromPacket(this);
+                    default:
+                        return this;
+                }
+            case Weather.id:
+                switch (this.commandId) {
+                    case Weather.WeatherForecastData.id:
+                        return new Weather.WeatherForecastData.OutgoingRequest(paramsProvider).fromPacket(this);
+                    default:
+                        return this;
+                }
+            default:
+                return this;
+        }
+    }
+
+    private List<byte[]> serializeSliced(byte[] serializedTLV) {
+        List<byte[]> retv = new ArrayList<>();
+        int headerLength = 5; // Magic + (short)(bodyLength + 1) + 0x00 + extra slice info
+        int bodyHeaderLength = 2; // sID + cID
+        int footerLength = 2; //CRC16
+        int maxBodySize = paramsProvider.getSliceSize() - headerLength - footerLength;
+        int packetCount = (int) Math.ceil(((double) serializedTLV.length + (double) bodyHeaderLength) / (double) maxBodySize);
+
+        if (packetCount == 1)
+            return serializeUnsliced(serializedTLV);
+
+        ByteBuffer buffer = ByteBuffer.wrap(serializedTLV);
+        byte slice = 0x01;
+        byte flag = 0x00;
+        for (int i = 0; i < packetCount; i++) {
+            short packetSize = (short) Math.min(paramsProvider.getSliceSize(), buffer.remaining() + headerLength + footerLength);
+
+            ByteBuffer packet = ByteBuffer.allocate(packetSize);
+
+            short contentSize = (short) (packetSize - headerLength - footerLength);
+            int start = packet.position();
+
+            packet.put((byte) 0x5a);                                // Magic byte
+            packet.putShort((short) (packetSize - headerLength));   // Length
+
+            if (i == packetCount - 1)
+                slice = 0x03;
+
+            packet.put(slice);                                      // Slice
+            packet.put(flag);                                       // Flag
+            flag += 1;
+
+            if (slice == 0x01) {
+                packet.put(this.serviceId);                         // Service ID
+                packet.put(this.commandId);                         // Command ID
+                slice = 0x02;
+                contentSize -= 2; // To prevent taking too much data
+            }
+
+            byte[] packetContent = new byte[contentSize];
+            buffer.get(packetContent);
+            packet.put(packetContent);                              // Packet data
+
+            int length = packet.position() - start;
+            if (length != packetSize - footerLength) {
+                // TODO: exception?
+                LOG.error(String.format(GBApplication.getLanguage(), "Packet lengths don't match! %d != %d", length, packetSize + headerLength));
+            }
+
+            byte[] complete = new byte[length];
+            packet.position(start);
+            packet.get(complete, 0, length);
+            int crc16 = CheckSums.getCRC16(complete, 0x0000);
+
+            packet.putShort((short) crc16);                         // CRC16
+
+            retv.add(packet.array());
+        }
+        return retv;
+    }
+
+    private List<byte[]> serializeUnsliced(byte[] serializedTLV) {
+        List<byte[]> retv = new ArrayList<>();
+        int headerLength = 4; // Magic + (short)(bodyLength + 1) + 0x00
+        int bodyHeaderLength = 2; // sID + cID
+        int footerLength = 2; //CRC16
+        int bodyLength = bodyHeaderLength + serializedTLV.length;
+        ByteBuffer buffer = ByteBuffer.allocate(headerLength + bodyLength);
+        buffer.put((byte) 0x5A);
+        buffer.putShort((short)(bodyLength + 1));
+        buffer.put((byte) 0x00);
+        buffer.put(this.serviceId);
+        buffer.put(this.commandId);
+        buffer.put(serializedTLV);
+        int crc16 = CheckSums.getCRC16(buffer.array(), 0x0000);
+        ByteBuffer finalBuffer = ByteBuffer.allocate(buffer.capacity() + footerLength);
+        finalBuffer.put(buffer.array());
+        finalBuffer.putShort((short)crc16);
+        retv.add(finalBuffer.array());
+        return retv;
+    }
+
+    public List<byte[]> serializeFileChunk(byte[] fileChunk, int uploadPosition, int unitSize, byte fileId, boolean isEncrypted) throws SerializeException {
+        List<byte[]> retv = new ArrayList<>();
+        final int subHeaderLength = 6;
+        final int packageHeaderAndFooterLength = 6;
+
+        int packetCount = (int) Math.ceil(((double) fileChunk.length) / (double) unitSize);
+
+        ByteBuffer buffer = ByteBuffer.wrap(fileChunk);
+
+        int sliceStart = uploadPosition;
+
+        for (int i = 0; i < packetCount; i++) {
+
+            int contentSize = Math.min(unitSize, buffer.remaining());
+
+            ByteBuffer payload = ByteBuffer.allocate(contentSize + subHeaderLength);
+            payload.put(fileId);
+            payload.put((byte)i);
+            payload.putInt(sliceStart);
+
+            byte[] packetContent = new byte[contentSize];
+            buffer.get(packetContent);
+            payload.put(packetContent);
+
+
+            byte[] new_payload = null;
+            if(isEncrypted) {
+                try {
+                    HuaweiTLV encryptedTlv = HuaweiTLV.encryptRaw(this.paramsProvider, payload.array());
+                    new_payload = encryptedTlv.serialize();
+                } catch (HuaweiCrypto.CryptoException e) {
+                    throw new HuaweiPacket.SerializeException("Error to encrypt TLV");
+                }
+            } else {
+                new_payload = payload.array();
+            }
+
+            if (new_payload == null) {
+                throw new HuaweiPacket.SerializeException("new payload is null");
+            }
+
+            if ((new_payload.length + packageHeaderAndFooterLength) > paramsProvider.getSliceSize()) {
+                retv.addAll(serializeSliced(new_payload));
+            } else {
+                retv.addAll(serializeUnsliced(new_payload));
+            }
+
+            sliceStart += contentSize;
+
+        }
+        return retv;
+    }
+
+    public List<byte[]> serializeFileChunk1c(byte[] fileChunk, short transferSize, int packetCount) throws SerializeException {
+        List<byte[]> retv = new ArrayList<>();
+
+        final int subHeaderLength = 1;
+        final int packageHeaderAndFooterLength = 6;
+
+        ByteBuffer buffer = ByteBuffer.wrap(fileChunk);
+
+        for (int i = 0; i < packetCount; i++) {
+
+            short contentSize = (short) Math.min(transferSize, buffer.remaining());
+
+            ByteBuffer payload = ByteBuffer.allocate(contentSize + subHeaderLength);
+            payload.put((byte)i);
+
+            byte[] packetContent = new byte[contentSize];
+            buffer.get(packetContent);
+            payload.put(packetContent);
+
+            byte[] new_payload = payload.array();
+
+            if ((new_payload.length + packageHeaderAndFooterLength) > paramsProvider.getSliceSize()) {
+                retv.addAll(serializeSliced(new_payload));
+            } else {
+                retv.addAll(serializeUnsliced(new_payload));
+            }
+        }
+        return retv;
+    }
+
+
+
+    public List<byte[]> serialize() throws CryptoException {
+        // TODO: necessary for this to work:
+        //       - serviceId
+        //       - commandId
+        //       - tlv
+        // TODO: maybe use the complete flag to know if it can be serialized?
+
+        HuaweiTLV serializableTlv;
+        if (this.isEncrypted && this.paramsProvider.areTransactionsCrypted()) {
+            try {
+                serializableTlv = this.tlv.encrypt(paramsProvider);
+            } catch (HuaweiCrypto.CryptoException e) {
+                throw new CryptoException("Encrypt exception", e);
+            }
+        } else {
+            serializableTlv = this.tlv;
+        }
+
+        byte[] serializedTLV = serializableTlv.serialize();
+        List<byte[]> retv;
+        if (isSliced) {
+            retv = serializeSliced(serializedTLV);
+        } else {
+            retv = serializeUnsliced(serializedTLV);
+        }
+        return retv;
+    }
+
+    public HuaweiTLV getTlv() {
+        return this.tlv;
+    }
+
+    public void setTlv(HuaweiTLV tlv) {
+        this.tlv = tlv;
+    }
+
+    public void setEncryption(boolean b) {
+        this.isEncrypted = b;
+    }
+
+    public void setSliced(boolean b) {
+        this.isSliced = b;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        HuaweiPacket that = (HuaweiPacket) o;
+
+        if (serviceId != that.serviceId) return false;
+        if (commandId != that.commandId) return false;
+        if (complete != that.complete) return false;
+        if (isEncrypted != that.isEncrypted) return false;
+        return Objects.equals(tlv, that.tlv);
+    }
+
+    @Override
+    public String toString() {
+        return "HuaweiPacket{" +
+                "paramsProvider=" + paramsProvider +
+                ", serviceId=" + serviceId +
+                ", commandId=" + commandId +
+                ", tlv=" + tlv +
+                ", partialPacket=" + Arrays.toString(partialPacket) +
+                ", payload=" + Arrays.toString(payload) +
+                ", complete=" + complete +
+                ", isEncrypted=" + isEncrypted +
+                ", isSliced=" + isSliced +
+                '}';
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiReportThreshold.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiReportThreshold.java
new file mode 100644
index 0000000..b3863b9
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiReportThreshold.java
@@ -0,0 +1,41 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.huawei;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.List;
+
+public class HuaweiReportThreshold {
+    private int dataType;
+    private int valueType;
+    private int value;
+    private int action;
+
+    public HuaweiReportThreshold(int dataType, int valueType, int value, int action) {
+        this.dataType = dataType;
+        this.valueType = valueType;
+        this.value = value;
+        this.action = action;
+    }
+
+    public byte[] getBytes() {
+        ByteBuffer buffer = ByteBuffer.allocate(5);
+        buffer.put((byte) dataType);
+        buffer.put((byte) valueType);
+        buffer.putShort((short) value);
+        buffer.put((byte) action);
+        return buffer.array();
+    }
+
+    public static List<HuaweiReportThreshold> getReportThresholds() {
+        List<HuaweiReportThreshold> thresholds = new ArrayList<>();
+        thresholds.add(new HuaweiReportThreshold(1,3, 500, 2));
+        thresholds.add(new HuaweiReportThreshold(3,3, 100, 2));
+
+        int current_hour = Calendar.getInstance().get(Calendar.HOUR_OF_DAY);
+        int value = (current_hour < 6)?21600:3600;
+        thresholds.add(new HuaweiReportThreshold(4,3, value, 2));
+
+        return thresholds;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiRunPaceConfig.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiRunPaceConfig.java
new file mode 100644
index 0000000..a7402fd
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiRunPaceConfig.java
@@ -0,0 +1,62 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.huawei;
+
+// TODO: make this configurable
+// NOTE: algorithms used in this class are generic. So this data can be used with other devices.
+// We can move this class to global scope.
+public class HuaweiRunPaceConfig {
+
+    private int zone5HIITRunMax = 300;
+    private int zone5HIITRunMin = 330;
+    private int zone4AnaerobicMin = 360;
+    private int zone3LactateThresholdMin = 390;
+    private int zone2MarathonMin = 420;
+    private int zone1JogMin = 450;
+
+    public int getZone5HIITRunMax() {
+        return zone5HIITRunMax;
+    }
+
+    public void setZone5HIITRunMax(int zone5HIITRunMax) {
+        this.zone5HIITRunMax = zone5HIITRunMax;
+    }
+
+    public int getZone5HIITRunMin() {
+        return zone5HIITRunMin;
+    }
+
+    public void setZone5HIITRunMin(int zone5HIITRunMin) {
+        this.zone5HIITRunMin = zone5HIITRunMin;
+    }
+
+    public int getZone4AnaerobicMin() {
+        return zone4AnaerobicMin;
+    }
+
+    public void setZone4AnaerobicMin(int zone4AnaerobicMin) {
+        this.zone4AnaerobicMin = zone4AnaerobicMin;
+    }
+
+    public int getZone3LactateThresholdMin() {
+        return zone3LactateThresholdMin;
+    }
+
+    public void setZone3LactateThresholdMin(int zone3LactateThresholdMin) {
+        this.zone3LactateThresholdMin = zone3LactateThresholdMin;
+    }
+
+    public int getZone2MarathonMin() {
+        return zone2MarathonMin;
+    }
+
+    public void setZone2MarathonMin(int zone2MarathonMin) {
+        this.zone2MarathonMin = zone2MarathonMin;
+    }
+
+    public int getZone1JogMin() {
+        return zone1JogMin;
+    }
+
+    public void setZone1JogMin(int zone1JogMin) {
+        this.zone1JogMin = zone1JogMin;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiSampleProvider.java
new file mode 100644
index 0000000..c1e8eaf
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiSampleProvider.java
@@ -0,0 +1,540 @@
+/*  Copyright (C) 2024 Damien Gaignon, Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huawei;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import de.greenrobot.dao.AbstractDao;
+import de.greenrobot.dao.Property;
+import de.greenrobot.dao.query.QueryBuilder;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.AbstractSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiActivitySampleDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutDataSample;
+import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutDataSampleDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutSummarySample;
+import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutSummarySampleDao;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
+
+public class HuaweiSampleProvider extends AbstractSampleProvider<HuaweiActivitySample> {
+    /*
+     * We save all data by saving a marker at the begin and end. We do not actively use these for
+     * showing the data at the moment, but the samples are still saved as such, to keep the table
+     * entries consistent.
+     * Meaning of fields that are not self-explanatory:
+     *  - `otherTimestamp`
+     *    The timestamp of the other marker, if it's larger this is the begin, otherwise the end
+     *  - `source`
+     *    The source of the data, which Huawei Band message the data came from
+     *    0x0d for sleep data from activity, 0x0a for TruSleep data
+     */
+
+    private static class RawTypes {
+        public static final int NOT_MEASURED = -1;
+
+        public static final int UNKNOWN = 1;
+
+        public static final int DEEP_SLEEP = 0x07;
+        public static final int LIGHT_SLEEP = 0x06;
+    }
+
+    public HuaweiSampleProvider(GBDevice device, DaoSession session) {
+        super(device, session);
+    }
+
+    @Override
+    public ActivityKind normalizeType(int rawType) {
+        switch (rawType) {
+            case RawTypes.DEEP_SLEEP:
+                return ActivityKind.DEEP_SLEEP;
+            case RawTypes.LIGHT_SLEEP:
+                return ActivityKind.LIGHT_SLEEP;
+            default:
+                return ActivityKind.UNKNOWN;
+        }
+    }
+
+    @Override
+    public int toRawActivityKind(ActivityKind activityKind) {
+        switch (activityKind) {
+            case DEEP_SLEEP:
+                return RawTypes.DEEP_SLEEP;
+            case LIGHT_SLEEP:
+                return RawTypes.LIGHT_SLEEP;
+            default:
+                return RawTypes.NOT_MEASURED;
+        }
+    }
+
+    @Override
+    public float normalizeIntensity(int rawIntensity) {
+        return rawIntensity;
+    }
+
+    @Override
+    public AbstractDao<HuaweiActivitySample, ?> getSampleDao() {
+        return getSession().getHuaweiActivitySampleDao();
+    }
+
+    @Nullable
+    @Override
+    protected Property getRawKindSampleProperty() {
+        return HuaweiActivitySampleDao.Properties.RawKind;
+    }
+
+    @NonNull
+    @Override
+    protected Property getTimestampSampleProperty() {
+        return HuaweiActivitySampleDao.Properties.Timestamp;
+    }
+
+    @NonNull
+    @Override
+    protected Property getDeviceIdentifierSampleProperty() {
+        return HuaweiActivitySampleDao.Properties.DeviceId;
+    }
+
+    @Override
+    public HuaweiActivitySample createActivitySample() {
+        return new HuaweiActivitySample();
+    }
+
+    private int getLastFetchTimestamp(QueryBuilder<HuaweiActivitySample> qb) {
+        Device dbDevice = DBHelper.findDevice(getDevice(), getSession());
+        if (dbDevice == null)
+            return 0;
+        Property deviceProperty = HuaweiActivitySampleDao.Properties.DeviceId;
+        Property timestampProperty = HuaweiActivitySampleDao.Properties.Timestamp;
+
+        qb.where(deviceProperty.eq(dbDevice.getId()))
+                .orderDesc(timestampProperty)
+                .limit(1);
+
+        List<HuaweiActivitySample> samples = qb.build().list();
+        if (samples.isEmpty())
+            return 0;
+
+        HuaweiActivitySample sample = samples.get(0);
+        return sample.getTimestamp();
+    }
+
+    /**
+     * Gets last timestamp where the sleep data has been fully synchronized
+     * @return Last fully synchronized timestamp for sleep data
+     */
+    public int getLastSleepFetchTimestamp() {
+        QueryBuilder<HuaweiActivitySample> qb = getSampleDao().queryBuilder();
+        Property sourceProperty = HuaweiActivitySampleDao.Properties.Source;
+        Property activityTypeProperty = HuaweiActivitySampleDao.Properties.RawKind;
+
+        qb.where(
+                qb.or(
+                        sourceProperty.eq(0x0d),
+                        sourceProperty.eq(0x0a)
+                ),
+                qb.or(
+                        activityTypeProperty.eq(RawTypes.LIGHT_SLEEP),
+                        activityTypeProperty.eq(RawTypes.DEEP_SLEEP)
+                )
+        );
+
+        return getLastFetchTimestamp(qb);
+    }
+
+    /**
+     * Gets last timestamp where the step data has been fully synchronized
+     * @return Last fully synchronized timestamp for step data
+     */
+    public int getLastStepFetchTimestamp() {
+        QueryBuilder<HuaweiActivitySample> qb = getSampleDao().queryBuilder();
+        Property sourceProperty = HuaweiActivitySampleDao.Properties.Source;
+
+        qb.where(sourceProperty.eq(0x0b));
+
+        return getLastFetchTimestamp(qb);
+    }
+
+    /**
+     * Makes a copy of a sample
+     * @param sample The sample to copy
+     * @return The copy of the sample
+     */
+    private HuaweiActivitySample copySample(HuaweiActivitySample sample) {
+        HuaweiActivitySample sampleCopy = new HuaweiActivitySample(
+                sample.getTimestamp(),
+                sample.getDeviceId(),
+                sample.getUserId(),
+                sample.getOtherTimestamp(),
+                sample.getSource(),
+                sample.getRawKind(),
+                sample.getRawIntensity(),
+                sample.getSteps(),
+                sample.getCalories(),
+                sample.getDistance(),
+                sample.getSpo(),
+                sample.getHeartRate()
+        );
+        sampleCopy.setProvider(sample.getProvider());
+        return sampleCopy;
+    }
+
+    @Override
+    public void addGBActivitySample(HuaweiActivitySample activitySample) {
+        HuaweiActivitySample start = copySample(activitySample);
+        HuaweiActivitySample end = copySample(activitySample);
+        end.setTimestamp(start.getOtherTimestamp());
+        end.setSteps(ActivitySample.NOT_MEASURED);
+        end.setCalories(ActivitySample.NOT_MEASURED);
+        end.setDistance(ActivitySample.NOT_MEASURED);
+        end.setSpo(ActivitySample.NOT_MEASURED);
+        end.setHeartRate(ActivitySample.NOT_MEASURED);
+        end.setOtherTimestamp(start.getTimestamp());
+
+        getSampleDao().insertOrReplace(start);
+        getSampleDao().insertOrReplace(end);
+    }
+
+    @Override
+    public void addGBActivitySamples(HuaweiActivitySample[] activitySamples) {
+        List<HuaweiActivitySample> newSamples = new ArrayList<>();
+        for (HuaweiActivitySample sample : activitySamples) {
+            HuaweiActivitySample start = copySample(sample);
+            HuaweiActivitySample end = copySample(sample);
+            end.setTimestamp(start.getOtherTimestamp());
+            end.setSteps(ActivitySample.NOT_MEASURED);
+            end.setCalories(ActivitySample.NOT_MEASURED);
+            end.setDistance(ActivitySample.NOT_MEASURED);
+            end.setSpo(ActivitySample.NOT_MEASURED);
+            end.setHeartRate(ActivitySample.NOT_MEASURED);
+            end.setOtherTimestamp(start.getTimestamp());
+
+            newSamples.add(start);
+            newSamples.add(end);
+        }
+        getSampleDao().insertOrReplaceInTx(newSamples);
+    }
+
+    /**
+     * Gets the activity samples, ordered by timestamp
+     * @param timestampFrom Start timestamp
+     * @param timestampTo End timestamp
+     * @return List of activities between the timestamps, ordered by timestamp
+     */
+    private List<HuaweiActivitySample> getRawOrderedActivitySamples(int timestampFrom, int timestampTo) {
+        QueryBuilder<HuaweiActivitySample> qb = getSampleDao().queryBuilder();
+        Property timestampProperty = getTimestampSampleProperty();
+        Device dbDevice = DBHelper.findDevice(getDevice(), getSession());
+        if (dbDevice == null) {
+            // no device, no samples
+            return Collections.emptyList();
+        }
+        Property deviceProperty = getDeviceIdentifierSampleProperty();
+        qb.where(deviceProperty.eq(dbDevice.getId()), timestampProperty.ge(timestampFrom))
+                .where(timestampProperty.le(timestampTo))
+                .orderAsc(timestampProperty);
+        List<HuaweiActivitySample> samples = qb.build().list();
+        for (HuaweiActivitySample sample : samples) {
+            sample.setProvider(this);
+        }
+        detachFromSession();
+        return samples;
+    }
+
+    private List<HuaweiWorkoutDataSample> getRawOrderedWorkoutSamplesWithHeartRate(int timestampFrom, int timestampTo) {
+        Device dbDevice = DBHelper.findDevice(getDevice(), getSession());
+        if (dbDevice == null)
+            return Collections.emptyList();
+
+        QueryBuilder<HuaweiWorkoutDataSample> qb = getSession().getHuaweiWorkoutDataSampleDao().queryBuilder();
+        Property timestampProperty = HuaweiWorkoutDataSampleDao.Properties.Timestamp;
+        Property heartRateProperty = HuaweiWorkoutDataSampleDao.Properties.HeartRate;
+        Property deviceProperty = HuaweiWorkoutSummarySampleDao.Properties.DeviceId;
+        qb.join(HuaweiWorkoutDataSampleDao.Properties.WorkoutId, HuaweiWorkoutSummarySample.class, HuaweiWorkoutSummarySampleDao.Properties.WorkoutId)
+                .where(deviceProperty.eq(dbDevice.getId()));
+        qb.where(
+                timestampProperty.ge(timestampFrom),
+                timestampProperty.le(timestampTo),
+                heartRateProperty.notEq(ActivitySample.NOT_MEASURED)
+        ).orderAsc(timestampProperty);
+        List<HuaweiWorkoutDataSample> samples = qb.build().list();
+        getSession().getHuaweiWorkoutSummarySampleDao().detachAll();
+        return samples;
+    }
+
+    /*
+     * This takes the following three steps:
+     *  - Generate a sample every minute
+     *  - Add the activity sample data to the generated samples
+     *  - Add the workout data to the generated samples
+     */
+    @Override
+    protected List<HuaweiActivitySample> getGBActivitySamples(int timestamp_from, int timestamp_to) {
+        List<HuaweiActivitySample> processedSamples = new ArrayList<>();
+
+        for (int timestamp = timestamp_from; timestamp <= timestamp_to; timestamp += 60) {
+            processedSamples.add(createDummySample(timestamp));
+        }
+
+        overlayActivitySamples(processedSamples, timestamp_from, timestamp_to);
+        overlayWorkoutSamples(processedSamples, timestamp_from, timestamp_to);
+
+        return processedSamples;
+    }
+
+    @Override
+    protected List<HuaweiActivitySample> getGBActivitySamplesHighRes(int timestamp_from, int timestamp_to) {
+        List<HuaweiActivitySample> processedSamples = getRawOrderedActivitySamples(timestamp_from, timestamp_to);
+        addWorkoutSamples(processedSamples, timestamp_from, timestamp_to);
+        // Filter out the end markers before returning
+        return processedSamples.stream().filter(sample -> sample.getTimestamp() <= sample.getOtherTimestamp()).collect(Collectors.toList());
+    }
+
+    @Override
+    public boolean hasHighResData() {
+        return true;
+    }
+
+    private HuaweiActivitySample createDummySample(int timestamp) {
+        HuaweiActivitySample activitySample = new HuaweiActivitySample(
+                timestamp,
+                -1,
+                -1,
+                timestamp + 60, // Make sure the duration is 60
+                (byte) 0x00,
+                ActivitySample.NOT_MEASURED,
+                0,
+                ActivitySample.NOT_MEASURED,
+                ActivitySample.NOT_MEASURED,
+                ActivitySample.NOT_MEASURED,
+                ActivitySample.NOT_MEASURED,
+                ActivitySample.NOT_MEASURED);
+        activitySample.setProvider(this);
+        return activitySample;
+    }
+
+    /*
+     * For every activity sample, it adds the data into the following processed sample.
+     * If there are multiple activity samples, the steps, calories, and distance is added together.
+     * For the SpO and HR only the last value is used.
+     */
+    private void overlayActivitySamples(List<HuaweiActivitySample> processedSamples, int timestamp_from, int timestamp_to) {
+        List<HuaweiActivitySample> activitySamples = getRawOrderedActivitySamples(timestamp_from, timestamp_to);
+
+        int currentIndex = 0;
+
+        boolean hasData = false;
+
+        int stepCount = ActivitySample.NOT_MEASURED;
+        int calorieCount = ActivitySample.NOT_MEASURED;
+        int distanceCount = ActivitySample.NOT_MEASURED;
+
+        int lastSpo = ActivitySample.NOT_MEASURED;
+        int lastHr = ActivitySample.NOT_MEASURED;
+
+        int stateModifier = ActivitySample.NOT_MEASURED;
+
+        for (HuaweiActivitySample activitySample : activitySamples) {
+            // Ignore the end markers
+            if (activitySample.getTimestamp() > activitySample.getOtherTimestamp())
+                continue;
+
+            // Skip the processed samples that are before this activity sample
+            while (activitySample.getTimestamp() > processedSamples.get(currentIndex).getTimestamp()) {
+                // Add data to current index sample
+                if (hasData || stateModifier != ActivitySample.NOT_MEASURED)
+                    processedSamples.get(currentIndex).setRawIntensity(1);
+                processedSamples.get(currentIndex).setSteps(stepCount);
+                processedSamples.get(currentIndex).setCalories(calorieCount);
+                processedSamples.get(currentIndex).setDistance(distanceCount);
+                processedSamples.get(currentIndex).setSpo(lastSpo);
+                processedSamples.get(currentIndex).setHeartRate(lastHr);
+                processedSamples.get(currentIndex).setRawKind(stateModifier);
+
+                // Reset counters
+                hasData = false;
+                stepCount = ActivitySample.NOT_MEASURED;
+                calorieCount = ActivitySample.NOT_MEASURED;
+                distanceCount = ActivitySample.NOT_MEASURED;
+                lastSpo = ActivitySample.NOT_MEASURED;
+                lastHr = ActivitySample.NOT_MEASURED;
+
+                currentIndex += 1;
+                if (currentIndex >= processedSamples.size())
+                    return; // We cannot add the data to any samples, so we might as well return
+            }
+
+            // Update data
+            if (activitySample.getSteps() != ActivitySample.NOT_MEASURED) {
+                if (stepCount == ActivitySample.NOT_MEASURED)
+                    stepCount = 0;
+                stepCount += activitySample.getSteps();
+                hasData = true;
+            }
+            if (activitySample.getCalories() != ActivitySample.NOT_MEASURED) {
+                if (calorieCount == ActivitySample.NOT_MEASURED)
+                    calorieCount = 0;
+                calorieCount += activitySample.getCalories();
+                hasData = true;
+            }
+            if (activitySample.getDistance() != ActivitySample.NOT_MEASURED) {
+                if (distanceCount == ActivitySample.NOT_MEASURED)
+                    distanceCount = 0;
+                distanceCount += activitySample.getDistance();
+                hasData = true;
+            }
+            if (activitySample.getSpo() != ActivitySample.NOT_MEASURED) {
+                lastSpo = activitySample.getSpo();
+                hasData = true;
+            }
+            if (activitySample.getHeartRate() != ActivitySample.NOT_MEASURED) {
+                lastHr = activitySample.getHeartRate();
+                hasData = true;
+            }
+            if (activitySample.getRawKind() != ActivitySample.NOT_MEASURED) {
+                if (activitySample.getTimestamp() < activitySample.getOtherTimestamp()) {
+                    // Starting of modifier
+                    stateModifier = activitySample.getRawKind();
+                } else {
+                    // End of modifier, remove it if it was for the same state
+                    if (activitySample.getRawKind() == stateModifier)
+                        stateModifier = ActivitySample.NOT_MEASURED;
+                }
+            }
+        }
+
+        // If there is still data, it has to be part of the next index of processed samples
+        currentIndex += 1;
+        if (currentIndex >= processedSamples.size())
+            return;
+        if (hasData || stateModifier != ActivitySample.NOT_MEASURED)
+            processedSamples.get(currentIndex).setRawIntensity(10);
+        processedSamples.get(currentIndex).setSteps(stepCount);
+        processedSamples.get(currentIndex).setCalories(calorieCount);
+        processedSamples.get(currentIndex).setDistance(distanceCount);
+        processedSamples.get(currentIndex).setSpo(lastSpo);
+        processedSamples.get(currentIndex).setHeartRate(lastHr);
+        processedSamples.get(currentIndex).setRawKind(stateModifier);
+    }
+
+    /*
+     * For every workout sample, it adds the data into the following processed sample.
+     * It also detects if it is still in the same workout, and resets the HR and intensity for the
+     * samples in between, see #4126 for the reasoning.
+     * NOTE: Huawei devices tend to generate a lot more data - mine up to every 5 seconds. Most of
+     * this is lost in the conversion to data by the minute. It only shows the most recent value.
+     */
+    private void overlayWorkoutSamples(List<HuaweiActivitySample> processedSamples, int timestamp_from, int timestamp_to) {
+        int currentIndex = 0;
+
+        int lastHr = ActivitySample.NOT_MEASURED;
+
+        List<HuaweiWorkoutDataSample> workoutSamples = getRawOrderedWorkoutSamplesWithHeartRate(timestamp_from, timestamp_to);
+
+        for (int i = 0; i < workoutSamples.size(); i++) {
+            // Look behind to see if this is still the same workout
+            boolean inWorkout = i != 0 && workoutSamples.get(i).getWorkoutId() == workoutSamples.get(i - 1).getWorkoutId();
+
+            // Skip the processed sample that are before this workout sample
+            while (workoutSamples.get(i).getTimestamp() > processedSamples.get(currentIndex).getTimestamp()) {
+                if (inWorkout) {
+                    processedSamples.get(currentIndex).setHeartRate(lastHr);
+                    processedSamples.get(currentIndex).setRawIntensity(0);
+                }
+
+                // Reset
+                lastHr = ActivitySample.NOT_MEASURED;
+
+                currentIndex += 1;
+                if (currentIndex >= processedSamples.size())
+                    return; // We cannot add the data to any samples, so we might as well return
+            }
+
+            if (workoutSamples.get(i).getHeartRate() != ActivitySample.NOT_MEASURED)
+                lastHr = workoutSamples.get(i).getHeartRate() & 0xFF;
+        }
+
+        // If there is still data, it has to be part of the next index of processed samples
+        // Data being present implies it's still in a workout
+        currentIndex += 1;
+        if (currentIndex >= processedSamples.size())
+            return;
+        if (lastHr != ActivitySample.NOT_MEASURED) {
+            processedSamples.get(currentIndex).setHeartRate(lastHr);
+            processedSamples.get(currentIndex).setRawIntensity(0);
+        }
+    }
+
+    private void addWorkoutSamples(List<HuaweiActivitySample> processedSamples, int timestamp_from, int timestamp_to) {
+        int currentIndex = 0;
+        List<HuaweiWorkoutDataSample> workoutSamples = getRawOrderedWorkoutSamplesWithHeartRate(timestamp_from, timestamp_to);
+
+        for (int i = 0; i < workoutSamples.size(); i++) {
+            // Look behind to see if this is still the same workout
+            boolean inWorkout = i != 0 && workoutSamples.get(i).getWorkoutId() == workoutSamples.get(i - 1).getWorkoutId();
+
+            // Skip the samples that are before this workout sample, and potentially clear the HR
+            // and intensity - see #4126 for the reasoning
+            while (currentIndex < processedSamples.size() && workoutSamples.get(i).getTimestamp() > processedSamples.get(currentIndex).getTimestamp()) {
+                if (inWorkout) {
+                    processedSamples.get(currentIndex).setHeartRate(ActivitySample.NOT_MEASURED);
+                    processedSamples.get(currentIndex).setRawIntensity(0);
+                }
+
+                currentIndex += 1;
+            }
+
+            if (i < workoutSamples.size() - 1) {
+                processedSamples.add(currentIndex, convertWorkoutSampleToActivitySample(workoutSamples.get(i), workoutSamples.get(i + 1).getTimestamp()));
+            } else {
+                // For the last workout sample we assume it is over 5 seconds
+                processedSamples.add(currentIndex, convertWorkoutSampleToActivitySample(workoutSamples.get(i), workoutSamples.get(i).getTimestamp() + 5));
+            }
+            currentIndex += 1; // Prevent clearing the sample in the next loop
+        }
+    }
+
+    private HuaweiActivitySample convertWorkoutSampleToActivitySample(HuaweiWorkoutDataSample workoutSample, int nextTimestamp) {
+        int hr = workoutSample.getHeartRate() & 0xFF;
+        HuaweiActivitySample newSample = new HuaweiActivitySample(
+                workoutSample.getTimestamp(),
+                -1,
+                -1,
+                nextTimestamp - 1, // Just to prevent overlap causing issues
+                (byte) 0x00,
+                ActivitySample.NOT_MEASURED,
+                ActivitySample.NOT_MEASURED,
+                ActivitySample.NOT_MEASURED,
+                ActivitySample.NOT_MEASURED,
+                ActivitySample.NOT_MEASURED,
+                ActivitySample.NOT_MEASURED,
+                hr
+        );
+        newSample.setProvider(this);
+        return newSample;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiSettingsCustomizer.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiSettingsCustomizer.java
new file mode 100644
index 0000000..ed1ecf5
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiSettingsCustomizer.java
@@ -0,0 +1,164 @@
+/*  Copyright (C) 2024 Damien Gaignon, Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huawei;
+
+import android.content.SharedPreferences;
+import android.os.Parcel;
+import android.widget.Toast;
+
+import androidx.preference.ListPreference;
+import androidx.preference.Preference;
+import androidx.preference.SwitchPreferenceCompat;
+
+import java.util.Set;
+import java.util.Collections;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsHandler;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiWorkoutGbParser;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+import nodomain.freeyourgadget.gadgetbridge.util.XTimePreference;
+
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.*;
+import static nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiConstants.PREF_HUAWEI_DEBUG_REQUEST;
+import static nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiConstants.PREF_HUAWEI_CONTINUOUS_SKIN_TEMPERATURE_MEASUREMENT;
+import static nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiConstants.PREF_HUAWEI_TRUSLEEP;
+import static nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiConstants.PREF_HUAWEI_WORKMODE;
+
+public class HuaweiSettingsCustomizer implements DeviceSpecificSettingsCustomizer {
+    final GBDevice device;
+    final HuaweiCoordinator coordinator;
+
+    public HuaweiSettingsCustomizer(final GBDevice device) {
+        this.device = device;
+        this.coordinator = ((HuaweiCoordinatorSupplier) this.device.getDeviceCoordinator()).getHuaweiCoordinator();
+    }
+
+    @Override
+    public void onPreferenceChange(final Preference preference, final DeviceSpecificSettingsHandler handler) {
+        if (preference.getKey().equals(PREF_DO_NOT_DISTURB)) {
+            final String dndState = ((ListPreference) preference).getValue();
+            final XTimePreference dndStart = handler.findPreference(PREF_DO_NOT_DISTURB_START);
+            final XTimePreference dndEnd = handler.findPreference(PREF_DO_NOT_DISTURB_END);
+            final SwitchPreferenceCompat dndLifWrist = handler.findPreference(PREF_DO_NOT_DISTURB_LIFT_WRIST);
+            final SwitchPreferenceCompat dndNotWear = handler.findPreference(PREF_DO_NOT_DISTURB_NOT_WEAR);
+            SharedPreferences sharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(device.getAddress());
+            boolean statusLiftWrist = sharedPrefs.getBoolean(PREF_LIFTWRIST_NOSHED, false);
+
+            dndStart.setEnabled(dndState.equals("scheduled"));
+            dndEnd.setEnabled(dndState.equals("scheduled"));
+            dndLifWrist.setEnabled(statusLiftWrist && !dndState.equals("off"));
+            dndNotWear.setEnabled(dndState.equals("off"));
+        }
+        if (preference.getKey().equals("huawei_reparse_workout_data")) {
+            if (((SwitchPreferenceCompat) preference).isChecked()) {
+                GB.toast("Starting workout reparse", Toast.LENGTH_SHORT, 0);
+                new HuaweiWorkoutGbParser(handler.getDevice(), handler.getContext()).parseAllWorkouts();
+                GB.toast("Workout reparse is complete", Toast.LENGTH_SHORT, 0);
+
+                ((SwitchPreferenceCompat) preference).setChecked(false);
+            }
+        }
+        if (preference.getKey().equals(PREF_FORCE_OPTIONS)) {
+            final Preference dnd = handler.findPreference("screen_do_not_disturb");
+            if (dnd != null)
+                dnd.setVisible(this.coordinator.supportsDoNotDisturb(handler.getDevice()));
+            final ListPreference wearLocation = handler.findPreference(PREF_WEARLOCATION);
+            if (wearLocation != null)
+                wearLocation.setVisible(this.coordinator.supportsWearLocation(handler.getDevice()));
+            final ListPreference heartRate = handler.findPreference(PREF_HEARTRATE_AUTOMATIC_ENABLE);
+            if (heartRate != null)
+                heartRate.setVisible(this.coordinator.supportsHeartRate(handler.getDevice()));
+            final ListPreference spo2 = handler.findPreference(PREF_SPO_AUTOMATIC_ENABLE);
+            if (spo2 != null)
+                spo2.setVisible(this.coordinator.supportsSPo2(handler.getDevice()));
+        }
+    }
+
+    @Override
+    public void customizeSettings(final DeviceSpecificSettingsHandler handler, Prefs prefs, final String rootKey) {
+
+        handler.addPreferenceHandlerFor(PREF_FORCE_OPTIONS);
+        handler.addPreferenceHandlerFor(PREF_FORCE_ENABLE_SMART_ALARM);
+        handler.addPreferenceHandlerFor(PREF_FORCE_ENABLE_WEAR_LOCATION);
+        handler.addPreferenceHandlerFor(PREF_FORCE_DND_SUPPORT);
+        handler.addPreferenceHandlerFor(PREF_FORCE_ENABLE_HEARTRATE_SUPPORT);
+        handler.addPreferenceHandlerFor(PREF_FORCE_ENABLE_SPO2_SUPPORT);
+
+        handler.addPreferenceHandlerFor(PREF_HUAWEI_WORKMODE);
+        handler.addPreferenceHandlerFor(PREF_HUAWEI_TRUSLEEP);
+        handler.addPreferenceHandlerFor(PREF_HUAWEI_DEBUG_REQUEST);
+        handler.addPreferenceHandlerFor(PREF_HUAWEI_CONTINUOUS_SKIN_TEMPERATURE_MEASUREMENT);
+
+        final Preference forceOptions = handler.findPreference(PREF_FORCE_OPTIONS);
+        if (forceOptions != null) {
+            boolean supportsSmartAlarm = this.coordinator.supportsSmartAlarm();
+            boolean supportsWearLocation = this.coordinator.supportsWearLocation();
+            boolean supportsHeartRate = this.coordinator.supportsHeartRate();
+            boolean supportsSpO2 = this.coordinator.supportsSPo2();
+            forceOptions.setVisible(!supportsSmartAlarm || !supportsWearLocation || !supportsHeartRate || !supportsSpO2);
+            final SwitchPreferenceCompat forceSmartAlarm = handler.findPreference(PREF_FORCE_ENABLE_SMART_ALARM);
+            forceSmartAlarm.setVisible(!supportsSmartAlarm);
+            final SwitchPreferenceCompat forceWearLocation = handler.findPreference(PREF_FORCE_ENABLE_WEAR_LOCATION);
+            forceWearLocation.setVisible(!supportsWearLocation);
+            final SwitchPreferenceCompat forceHeartRate = handler.findPreference(PREF_FORCE_ENABLE_HEARTRATE_SUPPORT);
+            forceHeartRate.setVisible(!supportsHeartRate);
+            final SwitchPreferenceCompat forceSpO2 = handler.findPreference(PREF_FORCE_ENABLE_SPO2_SUPPORT);
+            forceSpO2.setVisible(!supportsSpO2);
+        }
+
+        final SwitchPreferenceCompat reparseWorkout = handler.findPreference("huawei_reparse_workout_data");
+        if (reparseWorkout != null) {
+            reparseWorkout.setVisible(false);
+            if (this.coordinator.supportsWorkouts())
+                reparseWorkout.setVisible(true);
+        }
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(final Parcel dest, final int flags) {
+        dest.writeParcelable(device, 0);
+    }
+
+    @Override
+    public Set<String> getPreferenceKeysWithSummary() {
+        return Collections.emptySet();
+    }
+
+
+    public static final Creator<HuaweiSettingsCustomizer> CREATOR= new Creator<HuaweiSettingsCustomizer>() {
+
+        @Override
+        public HuaweiSettingsCustomizer createFromParcel(Parcel parcel) {
+            final GBDevice device = parcel.readParcelable(HuaweiSettingsCustomizer.class.getClassLoader());
+            return new HuaweiSettingsCustomizer(device);
+        }
+
+        @Override
+        public HuaweiSettingsCustomizer[] newArray(int i) {
+            return new HuaweiSettingsCustomizer[0];
+        }
+    };
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiSpo2SampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiSpo2SampleProvider.java
new file mode 100644
index 0000000..62c5d5c
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiSpo2SampleProvider.java
@@ -0,0 +1,230 @@
+/*  Copyright (C) 2024 Damien Gaignon, Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huawei;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import de.greenrobot.dao.AbstractDao;
+import de.greenrobot.dao.Property;
+import de.greenrobot.dao.query.QueryBuilder;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.AbstractTimeSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.AbstractSpo2Sample;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiActivitySampleDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.User;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class HuaweiSpo2SampleProvider extends AbstractTimeSampleProvider<HuaweiSpo2SampleProvider.HuaweiSpo2Sample> {
+    private static final Logger LOG = LoggerFactory.getLogger(HuaweiSpo2SampleProvider.class);
+
+    private final HuaweiSampleProvider huaweiSampleProvider;
+
+    public HuaweiSpo2SampleProvider(GBDevice device, DaoSession session) {
+        super(device, session);
+        this.huaweiSampleProvider = new HuaweiSampleProvider(this.getDevice(), this.getSession());
+    }
+
+    /**
+     * Converts an Huawei activity sample to an SpO2 sample
+     * @param sample Activity sample to convert
+     * @return SpO sample containing the SpO value, timestamp, userID, and deviceID of the activity sample
+     */
+    @NonNull
+    private HuaweiSpo2Sample activityToSpo2Sample(HuaweiActivitySample sample) {
+        return new HuaweiSpo2Sample(
+                -1, // No difference between auto and manual for Huawei
+                sample.getTimestamp() * 1000L,
+                sample.getUserId(),
+                sample.getDeviceId(),
+                sample.getSpo()
+        );
+    }
+
+    @NonNull
+    @Override
+    public List<HuaweiSpo2Sample> getAllSamples(long timestampFrom, long timestampTo) {
+        // Using high res data is fine for the SpO2 sample provider at the time of writing
+        List<HuaweiActivitySample> activitySamples = huaweiSampleProvider.getAllActivitySamplesHighRes((int) (timestampFrom / 1000L), (int) (timestampTo / 1000L));
+        List<HuaweiSpo2Sample> spo2Samples = new ArrayList<>(activitySamples.size());
+        for (HuaweiActivitySample sample : activitySamples) {
+            if (sample.getSpo() == -1)
+                continue;
+            spo2Samples.add(activityToSpo2Sample(sample));
+        }
+        return spo2Samples;
+    }
+
+    @Override
+    public void addSample(HuaweiSpo2Sample activitySample) {
+        LOG.error("Huawei Spo2 sample provider addSample called!");
+    }
+
+    @Override
+    public void addSamples(List<HuaweiSpo2Sample> activitySamples) {
+        LOG.error("Huawei Spo2 sample provider addSamples called!");
+    }
+
+    @Nullable
+    @Override
+    public HuaweiSpo2Sample getLatestSample() {
+        QueryBuilder<HuaweiActivitySample> qb = this.huaweiSampleProvider.getSampleDao().queryBuilder();
+        final Device dbDevice = DBHelper.findDevice(getDevice(), getSession());
+        if (dbDevice == null)
+            return null;
+        final Property deviceProperty = this.huaweiSampleProvider.getDeviceIdentifierSampleProperty();
+        qb
+                .where(deviceProperty.eq(dbDevice.getId()))
+                .where(HuaweiActivitySampleDao.Properties.Spo.notEq(-1))
+                .orderDesc(this.huaweiSampleProvider.getTimestampSampleProperty())
+                .limit(1);
+        final List<HuaweiActivitySample> samples = qb.build().list();
+        if (samples.isEmpty())
+            return null;
+        return activityToSpo2Sample(samples.get(0));
+    }
+
+    @Nullable
+    @Override
+    public HuaweiSpo2Sample getFirstSample() {
+        QueryBuilder<HuaweiActivitySample> qb = this.huaweiSampleProvider.getSampleDao().queryBuilder();
+        final Device dbDevice = DBHelper.findDevice(getDevice(), getSession());
+        if (dbDevice == null)
+            return null;
+        final Property deviceProperty = this.huaweiSampleProvider.getDeviceIdentifierSampleProperty();
+        qb
+                .where(deviceProperty.eq(dbDevice.getId()))
+                .where(HuaweiActivitySampleDao.Properties.Spo.notEq(-1))
+                .orderAsc(this.huaweiSampleProvider.getTimestampSampleProperty())
+                .limit(1);
+        final List<HuaweiActivitySample> samples = qb.build().list();
+        if (samples.isEmpty())
+            return null;
+        return activityToSpo2Sample(samples.get(0));
+    }
+
+    @Override
+    protected void detachFromSession() {
+        // Not necessary to do anything here
+        LOG.warn("Huawei Spo2 sample provider detachFromSession called!");
+    }
+
+    @NonNull
+    @Override
+    public AbstractDao<HuaweiSpo2Sample, ?> getSampleDao() {
+        // This not existing is not an issue (at the time of writing), as this is only used in
+        // methods that are overwritten by this class itself.
+        LOG.error("Huawei Spo2 sample provider getSampleDao called!");
+        return null;
+    }
+
+    @NonNull
+    @Override
+    protected Property getTimestampSampleProperty() {
+        LOG.warn("Huawei Spo2 sample provider getTimestampSampleProperty called!");
+        return this.huaweiSampleProvider.getTimestampSampleProperty();
+    }
+
+    @NonNull
+    @Override
+    protected Property getDeviceIdentifierSampleProperty() {
+        LOG.warn("Huawei Spo2 sample provider getDeviceIdentifierSampleProperty called!");
+        return this.huaweiSampleProvider.getDeviceIdentifierSampleProperty();
+    }
+
+    @Override
+    public HuaweiSpo2Sample createSample() {
+        return new HuaweiSpo2Sample();
+    }
+
+    public static class HuaweiSpo2Sample extends AbstractSpo2Sample {
+        private int typeNum;
+        private long timestamp;
+        private long userId;
+        private long deviceId;
+        private int spo2;
+
+        public HuaweiSpo2Sample() { }
+
+        public HuaweiSpo2Sample(int typeNum, long timestamp, long userId, long deviceId, int spo2) {
+            this.typeNum = typeNum;
+            this.timestamp = timestamp;
+            this.userId = userId;
+            this.deviceId = deviceId;
+            this.spo2 = spo2;
+        }
+
+        @Override
+        public int getTypeNum() {
+            return typeNum;
+        }
+
+        @Override
+        public void setTimestamp(long timestamp) {
+            this.timestamp = timestamp;
+        }
+
+        @Override
+        public long getUserId() {
+            return userId;
+        }
+
+        @Override
+        public void setUserId(long userId) {
+            this.userId = userId;
+        }
+
+        @Override
+        public long getDeviceId() {
+            return deviceId;
+        }
+
+        @Override
+        public void setDeviceId(long deviceId) {
+            this.deviceId = deviceId;
+        }
+
+        @Override
+        public void setDevice(final Device device) {
+
+        }
+
+        @Override
+        public void setUser(final User user) {
+
+        }
+
+        @Override
+        public int getSpo2() {
+            return spo2;
+        }
+
+        @Override
+        public long getTimestamp() {
+            return timestamp;
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiSportHRZones.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiSportHRZones.java
new file mode 100644
index 0000000..b3208e4
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiSportHRZones.java
@@ -0,0 +1,131 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.huawei;
+
+public class HuaweiSportHRZones {
+    private final HeartRateZonesConfig otherHRZonesConfig;
+    private final HeartRateZonesConfig sittingHRZonesConfig;
+    private final HeartRateZonesConfig uprightHRZonesConfig;
+    private final HeartRateZonesConfig swimmingHRZonesConfig;
+
+    public HuaweiSportHRZones(int age) {
+        this.uprightHRZonesConfig = new HeartRateZonesConfig(HeartRateZonesConfig.TYPE_UPRIGHT, age);
+        this.sittingHRZonesConfig = new HeartRateZonesConfig(HeartRateZonesConfig.TYPE_SITTING, age);
+        this.swimmingHRZonesConfig = new HeartRateZonesConfig(HeartRateZonesConfig.TYPE_SWIMMING, age);
+        this.otherHRZonesConfig = new HeartRateZonesConfig(HeartRateZonesConfig.TYPE_OTHER, age);
+    }
+
+    public HeartRateZonesConfig getHRZonesConfigByType(int type) {
+        if (type == HeartRateZonesConfig.TYPE_SITTING) {
+            return this.sittingHRZonesConfig;
+        } else if (type == HeartRateZonesConfig.TYPE_SWIMMING) {
+            return this.swimmingHRZonesConfig;
+        } else if (type == HeartRateZonesConfig.TYPE_OTHER) {
+            return this.otherHRZonesConfig;
+        }
+        return this.uprightHRZonesConfig;
+    }
+
+    public byte[] getHRZonesData() {
+        HeartRateZonesConfig uprightConfig = getHRZonesConfigByType(HeartRateZonesConfig.TYPE_UPRIGHT);
+        HeartRateZonesConfig sittingConfig = getHRZonesConfigByType(HeartRateZonesConfig.TYPE_SITTING);
+        HeartRateZonesConfig swimmingConfig = getHRZonesConfigByType(HeartRateZonesConfig.TYPE_SWIMMING);
+        HeartRateZonesConfig otherConfig = getHRZonesConfigByType(HeartRateZonesConfig.TYPE_OTHER);
+
+        if (!uprightConfig.isValid() || !sittingConfig.isValid() || !swimmingConfig.isValid() || !otherConfig.isValid()) {
+            return null;
+        }
+        HuaweiTLV tlv = new HuaweiTLV();
+        if (uprightConfig.hasValidMHRData()) {
+            tlv.put(0x2, (byte) uprightConfig.getMHRWarmUp())
+                    .put(0x3, (byte) uprightConfig.getMHRFatBurning())
+                    .put(0x4, (byte) uprightConfig.getMHRAerobic())
+                    .put(0x5, (byte) uprightConfig.getMHRAnaerobic())
+                    .put(0x6, (byte) uprightConfig.getMHRExtreme())
+                    .put(0x7, (byte) uprightConfig.getMaxHRThreshold())
+                    .put(0x8, (byte) (uprightConfig.getWarningEnable() ? 1 : 0))
+                    .put(0x9, (byte) uprightConfig.getWarningHRLimit())
+                    .put(0xa, (byte) uprightConfig.getCalculateMethod())
+                    .put(0xb, (byte) uprightConfig.getMaxHRThreshold());
+        }
+        tlv.put(0xc, (byte) uprightConfig.getRestHeartRate());
+        if (uprightConfig.hasValidHRRData()) {
+            tlv.put(0xd, (byte) uprightConfig.getHRRBasicAerobic())
+                    .put(0xe, (byte) uprightConfig.getHRRAdvancedAerobic())
+                    .put(0xf, (byte) uprightConfig.getHRRLactate())
+                    .put(0x10, (byte) uprightConfig.getHRRBasicAnaerobic())
+                    .put(0x11, (byte) uprightConfig.getHRRAdvancedAnaerobic());
+
+        }
+        if (uprightConfig.hasValidLTHRData()) {
+            tlv.put(0x3f, (byte) uprightConfig.getLTHRThresholdHeartRate())
+                    .put(0x40, (byte) uprightConfig.getLTHRAnaerobic())
+                    .put(0x41, (byte) uprightConfig.getLTHRLactate())
+                    .put(0x42, (byte) uprightConfig.getLTHRAdvancedAerobic())
+                    .put(0x43, (byte) uprightConfig.getLTHRBasicAerobic())
+                    .put(0x44, (byte) uprightConfig.getLTHRWarmUp());
+        }
+
+        if (sittingConfig.hasValidMHRData()) {
+            tlv.put(0x12, (byte) (sittingConfig.getWarningEnable() ? 1 : 0))
+                    .put(0x13, (byte) sittingConfig.getCalculateMethod())
+                    .put(0x14, (byte) sittingConfig.getWarningHRLimit())
+                    .put(0x15, (byte) sittingConfig.getMHRWarmUp())
+                    .put(0x16, (byte) sittingConfig.getMHRFatBurning())
+                    .put(0x17, (byte) sittingConfig.getMHRAerobic())
+                    .put(0x18, (byte) sittingConfig.getMHRAnaerobic())
+                    .put(0x19, (byte) sittingConfig.getMHRExtreme())
+                    .put(0x1a, (byte) sittingConfig.getMaxHRThreshold());
+        }
+        if (sittingConfig.hasValidHRRData()) {
+            tlv.put(0x1b, (byte) sittingConfig.getRestHeartRate())
+                    .put(0x1c, (byte) sittingConfig.getHRRBasicAerobic())
+                    .put(0x1d, (byte) sittingConfig.getHRRAdvancedAerobic())
+                    .put(0x1e, (byte) sittingConfig.getHRRLactate())
+                    .put(0x1f, (byte) sittingConfig.getHRRBasicAnaerobic())
+                    .put(0x20, (byte) sittingConfig.getHRRAdvancedAnaerobic());
+        }
+
+        if (swimmingConfig.hasValidMHRData()) {
+            tlv.put(0x21, (byte) (swimmingConfig.getWarningEnable() ? 1 : 0))
+                    .put(0x22, (byte) swimmingConfig.getCalculateMethod())
+                    .put(0x23, (byte) swimmingConfig.getWarningHRLimit())
+                    .put(0x24, (byte) swimmingConfig.getMHRWarmUp())
+                    .put(0x25, (byte) swimmingConfig.getMHRFatBurning())
+                    .put(0x26, (byte) swimmingConfig.getMHRAerobic())
+                    .put(0x27, (byte) swimmingConfig.getMHRAnaerobic())
+                    .put(0x28, (byte) swimmingConfig.getMHRExtreme())
+                    .put(0x29, (byte) swimmingConfig.getMaxHRThreshold());
+        }
+        if (swimmingConfig.hasValidHRRData()) {
+            tlv.put(0x2a, (byte) swimmingConfig.getRestHeartRate())
+                    .put(0x2b, (byte) swimmingConfig.getHRRBasicAerobic())
+                    .put(0x2c, (byte) swimmingConfig.getHRRAdvancedAerobic())
+                    .put(0x2d, (byte) swimmingConfig.getHRRLactate())
+                    .put(0x2e, (byte) swimmingConfig.getHRRBasicAnaerobic())
+                    .put(0x2f, (byte) swimmingConfig.getHRRAdvancedAnaerobic());
+        }
+
+        if (otherConfig.hasValidMHRData()) {
+            tlv.put(0x30, (byte) (otherConfig.getWarningEnable() ? 1 : 0))
+                    .put(0x31, (byte) otherConfig.getCalculateMethod())
+                    .put(0x32, (byte) otherConfig.getWarningHRLimit())
+                    .put(0x33, (byte) otherConfig.getMHRWarmUp())
+                    .put(0x34, (byte) otherConfig.getMHRFatBurning())
+                    .put(0x35, (byte) otherConfig.getMHRAerobic())
+                    .put(0x36, (byte) otherConfig.getMHRAnaerobic())
+                    .put(0x37, (byte) otherConfig.getMHRExtreme())
+                    .put(0x38, (byte) otherConfig.getMaxHRThreshold());
+        }
+        if (otherConfig.hasValidHRRData()) {
+            tlv.put(0x39, (byte) otherConfig.getRestHeartRate())
+                    .put(0x3a, (byte) otherConfig.getHRRBasicAerobic())
+                    .put(0x3b, (byte) otherConfig.getHRRAdvancedAerobic())
+                    .put(0x3c, (byte) otherConfig.getHRRLactate())
+                    .put(0x3d, (byte) otherConfig.getHRRBasicAnaerobic())
+                    .put(0x3e, (byte) otherConfig.getHRRAdvancedAnaerobic());
+        }
+
+        return tlv.serialize();
+    }
+
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiTLV.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiTLV.java
new file mode 100644
index 0000000..4f902f6
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiTLV.java
@@ -0,0 +1,452 @@
+/*  Copyright (C) 2024 Damien Gaignon, Martin.JM, Vitalii Tomin
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+
+/* TLV parsing and serialisation thanks to https://github.com/yihleego/tlv */
+package nodomain.freeyourgadget.gadgetbridge.devices.huawei;
+
+import static nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiConstants.CryptoTags;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiCrypto.CryptoException;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket.ParamsProvider;
+import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
+
+public class HuaweiTLV {
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        HuaweiTLV huaweiTLV = (HuaweiTLV) o;
+        return Objects.equals(valueMap, huaweiTLV.valueMap);
+    }
+
+    public static class TLV {
+        private final byte tag;
+        private final byte[] value;
+
+        public TLV(byte tag, byte[] value) {
+            this.tag = tag;
+            this.value = value;
+        }
+
+        public byte getTag() {
+            return tag;
+        }
+
+        public byte[] getValue() {
+            return value;
+        }
+
+        public int length() {
+            return 1 + VarInt.getVarIntSize(value.length) + value.length;
+        }
+
+        public byte[] serialize() {
+            return ByteBuffer.allocate(this.length())
+                    .put(tag)
+                    .put(VarInt.putVarIntValue(value.length))
+                    .put(value)
+                    .array();
+        }
+
+        public String toString() {
+            return "{tag: " + Integer.toHexString(tag & 0xFF) + " - Value: " + StringUtils.bytesToHex(value) + "} - ";
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+            TLV tlv = (TLV) o;
+            return tag == tlv.tag && Arrays.equals(value, tlv.value);
+        }
+    }
+
+    private static final Logger LOG = LoggerFactory.getLogger(HuaweiTLV.class);
+
+    protected List<TLV> valueMap;
+
+    public HuaweiTLV() {
+        this.valueMap = new ArrayList<>();
+    }
+
+    public int length() {
+        int length = 0;
+        for (TLV tlv : valueMap)
+            length += tlv.length();
+        return length;
+    }
+
+    /**
+     * Parse byte buffer into this HuaweiTLV
+     * @param buffer The buffer to parse
+     * @param offset The offset to start parsing at
+     * @param length The length to parse
+     * @return The HuaweiTLV object itself
+     * @throws ArrayIndexOutOfBoundsException There are two general cases in which this exception
+     *  can be thrown:
+     *    1. offset + length is greater than the buffer length
+     *    2. The buffer is malformed which causes an element size to be larger than the remaining
+     *       buffer length
+     */
+    public HuaweiTLV parse(byte[] buffer, int offset, int length)  {
+        if (buffer == null)
+            return null;
+        int parsed = 0;
+        while (parsed < length) {
+            // Tag is 1 byte
+            byte tag = buffer[offset + parsed];
+            parsed += 1;
+            // It seems that there can be an extra null byte at the end of something encrypted
+            // If that happens we ignore it
+            if (parsed == length && tag == 0)
+                break;
+            // Size is a VarInt >= 1 byte
+            VarInt varInt = new VarInt(buffer, offset + parsed);
+            int size = varInt.dValue;
+            parsed += varInt.size;
+            byte[] value = new byte[size];
+            System.arraycopy(buffer, offset + parsed, value, 0, size);
+            put(tag, value);
+            parsed += size;
+        }
+        LOG.debug("Parsed TLV: " + this);
+        return this;
+    }
+
+    public HuaweiTLV parse(byte[] buffer) {
+        if (buffer == null) {
+            return null;
+        }
+        return parse(buffer, 0, buffer.length);
+    }
+
+    public byte[] serialize() {
+        int length = this.length();
+        if (length == 0)
+            return new byte[0];
+        ByteBuffer buffer = ByteBuffer.allocate(length);
+        for (TLV entry : valueMap)
+            buffer.put(entry.serialize());
+        LOG.debug("Serialized TLV: " + this);
+        return buffer.array();
+    }
+
+    public HuaweiTLV put(int tag) {
+        byte[] value = new byte[0];
+        valueMap.add(new TLV((byte)tag, value));
+        return this;
+    }
+
+    public HuaweiTLV put(int tag, byte[] value) {
+        if (value == null) {
+            return this;
+        }
+        valueMap.add(new TLV((byte)tag, value));
+        return this;
+    }
+
+    public HuaweiTLV put(int tag, byte value) {
+        return put(tag, new byte[]{value});
+    }
+
+    public HuaweiTLV put(int tag, boolean value) {
+        return put(tag, new byte[]{value ? (byte) 1 : (byte) 0});
+    }
+
+    public HuaweiTLV put(int tag, Long value) {
+        return put(tag, ByteBuffer.allocate(8).putLong(value).array());
+    }
+
+    public HuaweiTLV put(int tag, Double value) {
+        return put(tag, ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN).putDouble(value).array());
+    }
+
+    public HuaweiTLV put(int tag, int value) {
+        return put(tag, ByteBuffer.allocate(4).putInt(value).array());
+    }
+
+    public HuaweiTLV put(int tag, short value) {
+        return put(tag, ByteBuffer.allocate(2).putShort(value).array());
+    }
+
+    public HuaweiTLV put(int tag, String value) {
+        if (value == null) {
+            return this;
+        }
+        return put(tag, value.getBytes(StandardCharsets.UTF_8));
+    }
+
+    public HuaweiTLV put(int tag, HuaweiTLV value) {
+        if (value == null) {
+            return this;
+        }
+        return put(tag, value.serialize());
+    }
+
+    public List<TLV> get() {
+        return this.valueMap;
+    }
+
+    public byte[] getBytes(int tag) throws HuaweiPacket.MissingTagException {
+        for (TLV item : valueMap)
+            if (item.getTag() == (byte) tag)
+                return item.getValue();
+        throw new HuaweiPacket.MissingTagException(tag);
+    }
+
+    public byte[] getBytes(int tag, byte[] defaultValue) {
+        try {
+            return getBytes(tag);
+        } catch (HuaweiPacket.MissingTagException e) {
+            return defaultValue;
+        }
+    }
+
+    public Byte getByte(int tag) throws HuaweiPacket.MissingTagException {
+        return getBytes(tag)[0];
+    }
+
+    public Byte getByte(int tag, Byte defaultValue) {
+        try {
+            return getByte(tag);
+        } catch (HuaweiPacket.MissingTagException e) {
+            return defaultValue;
+        }
+    }
+
+    public Boolean getBoolean(int tag) throws HuaweiPacket.MissingTagException {
+        return getBytes(tag)[0] == 1;
+    }
+
+    public Boolean getBoolean(int tag, Boolean defaultValue) {
+        try {
+            return getBoolean(tag);
+        } catch (HuaweiPacket.MissingTagException e) {
+            return defaultValue;
+        }
+    }
+
+    public Integer getInteger(int tag) throws HuaweiPacket.MissingTagException {
+        return ByteBuffer.wrap(getBytes(tag)).getInt();
+    }
+
+    public Integer getInteger(int tag, Integer defaultResult) {
+        try {
+            return getInteger(tag);
+        } catch (HuaweiPacket.MissingTagException e) {
+            return defaultResult;
+        }
+    }
+
+    public Short getShort(int tag) throws HuaweiPacket.MissingTagException {
+        return ByteBuffer.wrap(getBytes(tag)).getShort();
+    }
+
+    public Short getShort(int tag, Short defaultValue) {
+        try {
+            return getShort(tag);
+        } catch (HuaweiPacket.MissingTagException e) {
+            return defaultValue;
+        }
+    }
+
+    public Long getLong(int tag) throws HuaweiPacket.MissingTagException {
+        return ByteBuffer.wrap(getBytes(tag)).getLong();
+    }
+
+    public Integer getAsInteger(int tag) throws HuaweiPacket.MissingTagException {
+        byte[] bytes = getBytes(tag);
+        if(bytes.length == 1) {
+            return bytes[0] & 0xFF;
+        } else if(bytes.length == 2) {
+            return ByteBuffer.wrap(getBytes(tag)).getShort() & 0xFFFF;
+        }
+        return ByteBuffer.wrap(getBytes(tag)).getInt();
+    }
+
+    public String getString(int tag) throws HuaweiPacket.MissingTagException {
+        return new String(getBytes(tag), StandardCharsets.UTF_8);
+    }
+
+    public HuaweiTLV getObject(int tag) throws HuaweiPacket.MissingTagException {
+        byte[] bytes = getBytes(tag);
+        return new HuaweiTLV().parse(bytes, 0, bytes.length);
+    }
+
+    public List<HuaweiTLV> getObjects(int tag) {
+        List<HuaweiTLV> returnValue = new ArrayList<>();
+        for (TLV tlv : valueMap) {
+            if (tlv.getTag() == (byte) tag)
+                returnValue.add(new HuaweiTLV().parse(tlv.getValue()));
+        }
+        return returnValue;
+    }
+
+    public boolean contains(int tag) {
+        for (TLV item : valueMap)
+            if (item.getTag() == (byte) tag)
+                return true;
+        return false;
+    }
+
+    /**
+     * Removes the last element that was added with the specified tag
+     * @param tag The tag of the element that should be removed
+     * @return The value contained in the removed tag
+     */
+    public byte[] remove(int tag) {
+        TLV foundItem = null;
+        for (TLV item : valueMap)
+            if (item.getTag() == (byte) tag)
+                foundItem = item;
+        if (foundItem != null) {
+            valueMap.remove(foundItem);
+            return foundItem.getValue();
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Get string representation of HuaweiTLV, "Empty" when no elements are present
+     * @return String
+     */
+    public String toString() {
+        if (valueMap.isEmpty())
+            return "Empty";
+
+        StringBuilder msg = new StringBuilder();
+        for (TLV entry : valueMap)
+            msg.append(entry.toString());
+        return msg.substring(0, msg.length() - 3);
+    }
+
+    public static HuaweiTLV encryptRaw(ParamsProvider paramsProvider, byte[] data) throws CryptoException {
+        byte[] key = paramsProvider.getSecretKey();
+        byte[] nonce = paramsProvider.getIv();
+        byte[] encryptedTLV = HuaweiCrypto.encrypt(
+                paramsProvider.getEncryptMethod() == 0x01 || paramsProvider.getDeviceSupportType() == 0x04,
+                data,
+                key,
+                nonce);
+        return new HuaweiTLV()
+                .put(CryptoTags.encryption, (byte) 0x01)
+                .put(CryptoTags.initVector, nonce)
+                .put(CryptoTags.cipherText, encryptedTLV);
+    }
+
+    public HuaweiTLV encrypt(ParamsProvider paramsProvider) throws CryptoException {
+        byte[] serializedTLV = serialize();
+        return encryptRaw(paramsProvider, serializedTLV);
+    }
+
+    public byte[] decryptRaw(ParamsProvider paramsProvider) throws CryptoException, HuaweiPacket.MissingTagException {
+        byte[] key = paramsProvider.getSecretKey();
+        return HuaweiCrypto.decrypt(
+                paramsProvider.getEncryptMethod() == 0x01 || paramsProvider.getDeviceSupportType() == 0x04,
+                getBytes(CryptoTags.cipherText),
+                key,
+                getBytes(CryptoTags.initVector));
+    }
+
+    public void decrypt(ParamsProvider paramsProvider) throws CryptoException, HuaweiPacket.MissingTagException {
+        byte[] decryptedTLV = decryptRaw(paramsProvider);
+        this.valueMap = new ArrayList<>();
+        parse(decryptedTLV);
+    }
+}
+
+final class VarInt {
+    int dValue; // Decoded value of the VarInt
+    int size; // Size of the encoded value
+    byte[] eValue; // Encoded value of the VarInt
+
+    public VarInt(byte[] src, int offset) {
+        this.dValue = getVarIntValue(src, offset);
+        this.eValue = putVarIntValue(this.dValue);
+        this.size = this.eValue.length;
+    }
+
+    public String toString() {
+        return "VarInt(dValue: " + this.dValue + ", size: " + this.size + ", eValue: " + StringUtils.bytesToHex(this.eValue) + ")";
+    }
+    
+    /**
+    * Returns the size of the encoded input value.
+    *
+    * @param value the integer to be measured
+    * @return the encoding size of the input value
+    */
+    public static int getVarIntSize(int value) {
+        int result = 0;
+        do {
+        result++;
+        value >>>= 7;
+        } while (value != 0);
+        return result;
+    }
+
+    /**
+    * Decode a byte array of a variable-length encoding from start,
+    * 7 bits per byte.
+    * Return the decoded value in int.
+    *
+    * @param src the byte array to get the var int from
+    * @return the decoded value in int
+    */
+    public static int getVarIntValue(byte[] src, int offset) {
+        int result = 0;
+        int b;
+        while (true) {
+            b = src[offset];
+            result += (b & 0x7F);
+            if ((b & 0x80) == 0) { break; }
+            result <<= 7;
+            offset++;
+        }
+        return result;
+    }
+
+    /**
+    * Encode an integer in a variable-length encoding, 7 bits per byte.
+    * Return the encoded value in byte[]
+    *
+    * @param value the int value to encode
+    * @return the encoded value in byte[]
+    */
+    public static byte[] putVarIntValue(int value) {
+        int size = getVarIntSize(value);
+        byte[] result = new byte[size];
+        result[size - 1] = (byte)(value & 0x7F);
+        for (int offset = size - 2; offset >= 0; offset--) {
+            value >>>= 7;
+            result[offset] = (byte)((value & 0x7F) | 0x80);
+        }
+        return result;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiTemperatureSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiTemperatureSampleProvider.java
new file mode 100644
index 0000000..0ee2824
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiTemperatureSampleProvider.java
@@ -0,0 +1,213 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.huawei;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import de.greenrobot.dao.query.QueryBuilder;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.TimeSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiDictData;
+import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiDictDataDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiDictDataValues;
+import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiDictDataValuesDao;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.TemperatureSample;
+
+public class HuaweiTemperatureSampleProvider implements TimeSampleProvider<TemperatureSample> {
+
+    private final Logger LOG = LoggerFactory.getLogger(HuaweiTemperatureSampleProvider.class);
+
+    protected static class HuaweiTemperatureSample implements TemperatureSample {
+        private final long timestamp;
+        private final float temperature;
+
+        public HuaweiTemperatureSample(long timestamp, float temperature) {
+            this.timestamp = timestamp;
+            this.temperature = temperature;
+        }
+
+        @Override
+        public long getTimestamp() {
+            return timestamp;
+        }
+
+        @Override
+        public float getTemperature() {
+            return temperature;
+        }
+
+        @Override
+        public int getTemperatureType() { return 0;}
+    }
+
+    private final GBDevice device;
+    private final DaoSession session;
+
+    public HuaweiTemperatureSampleProvider(GBDevice device, DaoSession session) {
+        this.device = device;
+        this.session = session;
+    }
+
+    private double conv2Double(byte[] b) {
+        return ByteBuffer.wrap(b).getDouble();
+    }
+
+    @NonNull
+    @Override
+    public List<TemperatureSample> getAllSamples(long timestampFrom, long timestampTo) {
+
+        List<TemperatureSample> ret = new ArrayList<>();
+
+        Long userId = DBHelper.getUser(this.session).getId();
+        Long deviceId = DBHelper.getDevice(this.device, this.session).getId();
+
+        if (deviceId == null || userId == null)
+            return ret;
+
+        QueryBuilder<HuaweiDictData> qb =  this.session.getHuaweiDictDataDao().queryBuilder();
+                qb.where(HuaweiDictDataDao.Properties.DeviceId.eq(deviceId))
+                .where(HuaweiDictDataDao.Properties.UserId.eq(userId))
+                .where(HuaweiDictDataDao.Properties.DictClass.eq(HuaweiDictTypes.SKIN_TEMPERATURE_CLASS))
+                        .where(HuaweiDictDataDao.Properties.StartTimestamp.between(timestampFrom, timestampTo));
+        final List<HuaweiDictData> dictData = qb.build().list();
+
+        if (dictData.isEmpty())
+            return ret;
+
+        List<Long> ids = dictData.stream().map(HuaweiDictData::getDictId).collect(Collectors.toList());
+
+        QueryBuilder<HuaweiDictDataValues> qbv =  this.session.getHuaweiDictDataValuesDao().queryBuilder();
+
+        qbv.where(HuaweiDictDataValuesDao.Properties.DictType.eq(HuaweiDictTypes.SKIN_TEMPERATURE_VALUE)).where(HuaweiDictDataValuesDao.Properties.Tag.eq(10)).where(HuaweiDictDataValuesDao.Properties.DictId.in(ids));
+
+        final List<HuaweiDictDataValues> valuesData = qbv.build().list();
+
+        if (valuesData.isEmpty())
+            return ret;
+
+        for(HuaweiDictDataValues vl: valuesData) {
+            double skinTemperature = conv2Double(vl.getValue());
+            if(skinTemperature >= 20 && skinTemperature <= 42) {
+                ret.add(new HuaweiTemperatureSample(vl.getHuaweiDictData().getStartTimestamp(), (float) skinTemperature));
+            }
+        }
+
+        return ret;
+    }
+
+    @Override
+    public void addSample(TemperatureSample timeSample) {
+        throw new UnsupportedOperationException("read-only sample provider");
+
+    }
+
+    @Override
+    public void addSamples(List<TemperatureSample> timeSamples) {
+        throw new UnsupportedOperationException("read-only sample provider");
+
+    }
+
+    @Override
+    public TemperatureSample createSample() {
+        throw new UnsupportedOperationException("read-only sample provider");
+    }
+
+    @Nullable
+    @Override
+    public TemperatureSample getLatestSample() {
+        Long userId = DBHelper.getUser(this.session).getId();
+        Long deviceId = DBHelper.getDevice(this.device, this.session).getId();
+
+        if (deviceId == null || userId == null)
+            return null;
+
+        QueryBuilder<HuaweiDictData> qb =  this.session.getHuaweiDictDataDao().queryBuilder();
+        qb.where(HuaweiDictDataDao.Properties.DeviceId.eq(deviceId))
+                .where(HuaweiDictDataDao.Properties.UserId.eq(userId))
+                .where(HuaweiDictDataDao.Properties.DictClass.eq(HuaweiDictTypes.SKIN_TEMPERATURE_CLASS));
+        qb.orderDesc(HuaweiDictDataDao.Properties.StartTimestamp).limit(1);
+
+        final List<HuaweiDictData> data = qb.build().list();
+        if (data.isEmpty())
+            return null;
+
+
+        QueryBuilder<HuaweiDictDataValues> qbv =  this.session.getHuaweiDictDataValuesDao().queryBuilder();
+        qbv.where(HuaweiDictDataValuesDao.Properties.DictType.eq(HuaweiDictTypes.SKIN_TEMPERATURE_VALUE)).where(HuaweiDictDataValuesDao.Properties.Tag.eq(10)).where(HuaweiDictDataValuesDao.Properties.DictId.eq(data.get(0).getDictId()));
+        final List<HuaweiDictDataValues> valuesData = qbv.build().list();
+
+        if (valuesData.isEmpty())
+            return null;
+
+        return new HuaweiTemperatureSample(valuesData.get(0).getHuaweiDictData().getStartTimestamp(), (float) conv2Double(valuesData.get(0).getValue()));
+    }
+
+    @Nullable
+    @Override
+    public TemperatureSample getLatestSample(final long until) {
+        Long userId = DBHelper.getUser(this.session).getId();
+        Long deviceId = DBHelper.getDevice(this.device, this.session).getId();
+
+        if (deviceId == null || userId == null)
+            return null;
+
+        QueryBuilder<HuaweiDictData> qb =  this.session.getHuaweiDictDataDao().queryBuilder();
+        qb.where(HuaweiDictDataDao.Properties.StartTimestamp.le(until))
+                .where(HuaweiDictDataDao.Properties.DeviceId.eq(deviceId))
+                .where(HuaweiDictDataDao.Properties.UserId.eq(userId))
+                .where(HuaweiDictDataDao.Properties.DictClass.eq(HuaweiDictTypes.SKIN_TEMPERATURE_CLASS));
+        qb.orderDesc(HuaweiDictDataDao.Properties.StartTimestamp).limit(1);
+
+        final List<HuaweiDictData> data = qb.build().list();
+        if (data.isEmpty())
+            return null;
+
+
+        QueryBuilder<HuaweiDictDataValues> qbv =  this.session.getHuaweiDictDataValuesDao().queryBuilder();
+        qbv.where(HuaweiDictDataValuesDao.Properties.DictType.eq(HuaweiDictTypes.SKIN_TEMPERATURE_VALUE)).where(HuaweiDictDataValuesDao.Properties.Tag.eq(10)).where(HuaweiDictDataValuesDao.Properties.DictId.eq(data.get(0).getDictId()));
+        final List<HuaweiDictDataValues> valuesData = qbv.build().list();
+
+        if (valuesData.isEmpty())
+            return null;
+
+        return new HuaweiTemperatureSample(valuesData.get(0).getHuaweiDictData().getStartTimestamp(), (float) conv2Double(valuesData.get(0).getValue()));
+    }
+
+    @Nullable
+    @Override
+    public TemperatureSample getFirstSample() {
+        Long userId = DBHelper.getUser(this.session).getId();
+        Long deviceId = DBHelper.getDevice(this.device, this.session).getId();
+
+        if (deviceId == null || userId == null)
+            return null;
+
+        QueryBuilder<HuaweiDictData> qb =  this.session.getHuaweiDictDataDao().queryBuilder();
+        qb.where(HuaweiDictDataDao.Properties.DeviceId.eq(deviceId))
+                .where(HuaweiDictDataDao.Properties.UserId.eq(userId))
+                .where(HuaweiDictDataDao.Properties.DictClass.eq(HuaweiDictTypes.SKIN_TEMPERATURE_CLASS));
+        qb.orderAsc(HuaweiDictDataDao.Properties.StartTimestamp).limit(1);
+
+        final List<HuaweiDictData> data = qb.build().list();
+        if (data.isEmpty())
+            return null;
+
+        QueryBuilder<HuaweiDictDataValues> qbv =  this.session.getHuaweiDictDataValuesDao().queryBuilder();
+        qbv.where(HuaweiDictDataValuesDao.Properties.DictType.eq(HuaweiDictTypes.SKIN_TEMPERATURE_VALUE)).where(HuaweiDictDataValuesDao.Properties.Tag.eq(10)).where(HuaweiDictDataValuesDao.Properties.DictId.eq(data.get(0).getDictId()));
+        final List<HuaweiDictDataValues> valuesData = qbv.build().list();
+
+        if (valuesData.isEmpty())
+            return null;
+
+        return new HuaweiTemperatureSample(valuesData.get(0).getHuaweiDictData().getStartTimestamp(), (float) conv2Double(valuesData.get(0).getValue()));
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiTruSleepParser.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiTruSleepParser.java
new file mode 100644
index 0000000..ec24dfa
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiTruSleepParser.java
@@ -0,0 +1,474 @@
+/*  Copyright (C) 2024 Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huawei;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiFileDownloadManager;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class HuaweiTruSleepParser {
+    private static final Logger LOG = LoggerFactory.getLogger(HuaweiTruSleepParser.class);
+    public static final int TAG_ACC = 1;
+    public static final int TAG_PPG = 2;
+
+    public static class TruSleepStatus {
+        public final int startTime;
+        public final int endTime;
+
+        public TruSleepStatus(int startTime, int endTime) {
+            this.startTime = startTime;
+            this.endTime = endTime;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (o == null || getClass() != o.getClass()) return false;
+            TruSleepStatus that = (TruSleepStatus) o;
+            return startTime == that.startTime && endTime == that.endTime;
+        }
+
+        @Override
+        public String toString() {
+            return "TruSleepStatus{" +
+                    "endTime=" + endTime +
+                    ", startTime=" + startTime +
+                    '}';
+        }
+    }
+    public static class TruSleepDataAcc {
+        public final int startTime;
+        public final short flags;
+
+        /*
+            Accelerometer (ACC):
+             Accelerometer data is sampled every 60 seconds, 24h a day and provides activity data on 3 axis.
+             Each axis activity is represented as byte, where 0x00 is no activity and 0xff is high activity.
+             The 3rd axis is ignored as it's always high (due to gravity??).
+         */
+        public TruSleepDataAcc(int startTime, short flags)  {
+            this.startTime = startTime;
+            this.flags = flags;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (o == null || getClass() != o.getClass()) return false;
+            TruSleepDataAcc that = (TruSleepDataAcc) o;
+            if (startTime != that.startTime)
+                return false;
+            return flags == that.flags;
+        }
+
+        @Override
+        public String toString() {
+            return "TruSleepDataAcc{" +
+                    "startTime=" + startTime +
+                    ", flags=" + flags +
+                    '}';
+        }
+    }
+
+    /*
+        Photoplethysmogram (PPG):
+         Every sample has a millisecond timestamp.
+         At night data is sampled every second, but at day it's sampled less often (about every 5 minutes).
+    */
+    public static class TruSleepDataPpg {
+        public final long startTime;
+        public final short flags;
+
+        TruSleepDataPpg(long startTime, short flags) {
+            this.startTime = startTime;
+            this.flags = flags;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (o == null || getClass() != o.getClass()) return false;
+            TruSleepDataPpg that = (TruSleepDataPpg) o;
+            if (startTime != that.startTime)
+                return false;
+            return flags == that.flags;
+        }
+
+        @Override
+        public String toString() {
+            return "TruSleepDataPpg{" +
+                    "startTime=" + startTime +
+                    ", flags=" + flags +
+                    '}';
+        }
+    }
+    public static class TruSleepData {
+        public ArrayList<TruSleepDataAcc> dataACCs;
+        public ArrayList<TruSleepDataPpg> dataPPGs;
+
+        public TruSleepData() {
+            this.dataACCs = new ArrayList<>();
+            this.dataPPGs = new ArrayList<>();
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (o == null || getClass() != o.getClass()) return false;
+            TruSleepData that = (TruSleepData) o;
+            if (!dataACCs.equals(that.dataACCs))
+                return false;
+            return dataPPGs.equals(that.dataPPGs);
+        }
+
+        @Override
+        public String toString() {
+            return "TruSleepData{" +
+                    "dataPPGs=" + dataPPGs.toString() +
+                    ", dataACCs=" + dataACCs.toString() +
+                    '}';
+        }
+
+        private static final byte TAG_COMPRESSION_RAW = (byte)0xaa;
+        private static final byte TAG_COMPRESSION_COMP = (byte)0xbb;
+        private static final byte TAG_COMPRESSION_RESTART = (byte)0xff;
+
+        public void decodeAcc(byte[] data) throws IllegalArgumentException {
+            /*
+                Format:
+                 - Timestamp (4 byte)
+                 - Flags (3 bytes)
+             */
+            ByteBuffer buffer = ByteBuffer.wrap(data);
+            buffer.order(ByteOrder.LITTLE_ENDIAN);
+            if (buffer.remaining() < 4)
+                throw new IllegalArgumentException("Timestamp is missing");
+
+            int startTime = buffer.getInt();
+            if (buffer.remaining() < 2)
+                throw new IllegalArgumentException("Flags are missing");
+
+            short flags = buffer.getShort();
+            dataACCs.add(new TruSleepDataAcc(startTime, flags));
+        }
+
+        public void decodePpg(byte[] data) throws IllegalArgumentException, IllegalStateException {
+            /*
+                Format:
+                 - Timestamp (4 byte)
+                 - Number of UINT16 (1 byte)
+                 - Compression tag (1 byte)
+                 - Compressed data
+                 - Compression tag (1 byte)
+                 - Compressed data
+             */
+            ByteBuffer buffer = ByteBuffer.wrap(data);
+            buffer.order(ByteOrder.LITTLE_ENDIAN);
+
+            if (buffer.remaining() < 4)
+                throw new IllegalArgumentException("Timestamp is missing");
+
+            int startTime = buffer.getInt();
+
+            if (buffer.remaining() < 1)
+                throw new IllegalArgumentException("Count of short is missing");
+
+            byte countShort = buffer.get();
+
+            ArrayList<Short> peak = decodePeak(countShort, buffer);
+            ArrayList<Short> amp = decodeAmp(countShort, buffer);
+
+            // Sanity check
+            if (peak.size() != countShort || amp.size() != countShort)
+                throw new IllegalStateException("Decoded arrays have different length");
+
+            for (int i = 0; i < countShort; i++) {
+                dataPPGs.add(new TruSleepDataPpg((long)startTime * 1000 + peak.get(i) * 10, amp.get(i)));
+            }
+            LOG.debug("Buffer remaining {}", buffer.remaining());
+        }
+
+        private ArrayList<Short> decodePeak(byte countShort, ByteBuffer buffer) throws IllegalArgumentException {
+            if (countShort == 0)
+                throw new IllegalArgumentException("Number of short to generate is invalid");
+
+            if (buffer.remaining() < 1)
+                throw new IllegalArgumentException("Compression tag is missing");
+
+            byte tag = buffer.get();
+
+            ArrayList<Short> al = new ArrayList<Short>();
+            if (tag == TAG_COMPRESSION_RAW) {
+                if (buffer.remaining() < countShort * 2)
+                    throw new IllegalArgumentException("Not enough elements in buffer");
+
+                while (countShort > 0) {
+                    al.add(buffer.getShort());
+                    countShort--;
+                }
+            } else if (tag == TAG_COMPRESSION_COMP) {
+                short working = buffer.getShort();
+                al.add(working);
+                countShort--;
+
+                while (countShort > 0) {
+                    byte c = buffer.get();
+                    if (c == TAG_COMPRESSION_RESTART) {
+                        if (buffer.remaining() < 2)
+                            throw new IllegalArgumentException("Not enough elements in buffer");
+                        working = buffer.getShort();
+                    } else {
+                        working += c;
+                    }
+                    al.add(working);
+                    countShort--;
+                }
+            } else {
+                throw new IllegalArgumentException("Compression " + String.format("%02x", tag) + " is unsupported");
+            }
+            return al;
+        }
+
+        public ArrayList<Short> decodeAmp(byte countShort, ByteBuffer buffer) throws IllegalArgumentException {
+            if (countShort == 0)
+                throw new IllegalArgumentException("Number of short to generate is invalid");
+
+            if (buffer.remaining() < 1)
+                throw new IllegalArgumentException("Compression tag is missing");
+
+            byte tag = buffer.get();
+
+            ArrayList<Short> al = new ArrayList<Short>();
+            if (tag == TAG_COMPRESSION_RAW) {
+                if (buffer.remaining() < countShort * 2)
+                    throw new IllegalArgumentException("Not enough elements in buffer");
+
+                while (countShort > 0) {
+                    al.add(buffer.getShort());
+                    countShort--;
+                }
+            } else if (tag == TAG_COMPRESSION_COMP) {
+                if (buffer.remaining() < 2)
+                    throw new IllegalArgumentException("Offset buffer is missing");
+
+                short offset = buffer.getShort();
+                short working = 0;
+                while (buffer.remaining() > 0) {
+                    byte c = buffer.get();
+                    if (c == TAG_COMPRESSION_RESTART) {
+                        if (buffer.remaining() < 2)
+                            throw new IllegalArgumentException("Raw buffer is missing");
+                        working = (short)(offset + buffer.getShort());
+                    } else {
+                        working = (short)(offset + c);
+                    }
+                    al.add(working);
+                }
+            } else {
+                throw new IllegalArgumentException("Compression " + String.format("%02x", tag) + " is unsupported");
+            }
+            return al;
+        }
+
+        public void parsePpgData(byte[] data) {
+            LOG.debug("Decoding PPG: len= {}, data = {}", data.length, GB.hexdump(data));
+
+            try {
+                decodePpg(data);
+            } catch (Exception e) {
+                LOG.error("Failed to parse TrueSleep PPG data: {}", e.toString());
+            }
+        }
+        public void parseAccData(byte[] data) {
+            LOG.debug("Decoding ACC: len= {}, data = {}", data.length, GB.hexdump(data));
+
+            try {
+                decodeAcc(data);
+            } catch (Exception e) {
+                LOG.error("Failed to parse TrueSleep ACC data: {}", e.toString());
+            }
+        }
+    }
+
+    public static TruSleepData parseData(byte[] data) {
+        /*
+            Format:
+            - Zero padded
+            - ACC data interleaved with PPG data
+            - PPG data is compressed and has variable length
+         */
+        TruSleepData sleepData = new TruSleepData();
+        ByteBuffer buffer = ByteBuffer.wrap(data);
+        buffer.order(ByteOrder.LITTLE_ENDIAN);
+        byte c;
+        while (buffer.remaining() > 0) {
+            c = buffer.get();
+
+            // Skip over padding
+            if (c != TAG_ACC && c != TAG_PPG)
+                continue;
+
+            // The tag identifies the data packet
+            if (c == TAG_ACC) {
+                if (buffer.remaining() < 1)
+                    break;
+
+                // ACC has 1 byte length
+                byte length = buffer.get();
+                if (buffer.remaining() < length)
+                    break;
+
+                byte[] accData = new byte[length];
+                buffer.get(accData);
+
+                sleepData.parseAccData(accData);
+            } else if (c == TAG_PPG) {
+                if (buffer.remaining() < 2)
+                    break;
+
+                // PPG has 2 byte length
+                short length = buffer.getShort();
+                if (buffer.remaining() < length)
+                    break;
+
+                byte[] ppgData = new byte[length];
+                buffer.get(ppgData);
+
+                sleepData.parsePpgData(ppgData);
+            }
+        }
+
+        return sleepData;
+    }
+    public static TruSleepStatus[] parseState(byte[] stateData) {
+        /*
+            Format:
+             - Start time (int)
+             - End time (int)
+             - Unknown (short)
+             - Unknown (byte)
+             - Padding (5 bytes)
+            Could be multiple available
+         */
+        ByteBuffer buffer = ByteBuffer.wrap(stateData);
+        buffer.order(ByteOrder.LITTLE_ENDIAN);
+        TruSleepStatus[] retv = new TruSleepStatus[buffer.remaining() / 0x10];
+        int c = 0;
+        while (stateData.length - buffer.position() >= 0x10) {
+            int startTime = buffer.getInt();
+            int endTime = buffer.getInt();
+
+            // Throw away for now because we don't know what it means, and we don't think we can implement this soon
+            buffer.get(); buffer.get(); buffer.get();
+            buffer.get(); buffer.get(); buffer.get(); buffer.get(); buffer.get();
+
+            retv[c++] = new TruSleepStatus(startTime, endTime);
+        }
+        return retv;
+    }
+
+    private static void analyzeOneTimeframe(HuaweiSupportProvider provider, TruSleepStatus status, ArrayList<TruSleepDataAcc> acc, ArrayList<TruSleepDataPpg> ppg) {
+        java.util.Date rangeStartTime = new java.util.Date((long)status.startTime*1000);
+        java.util.Date rangeEndTime = new java.util.Date((long)status.endTime*1000);
+        LOG.debug("FIXME: Analyse sleep range {} - {} not implemented", rangeStartTime, rangeEndTime);
+        //FIXME: Sleep analysis algorithm implemented here
+        for (HuaweiTruSleepParser.TruSleepDataAcc i : acc) {
+            java.util.Date startTime = new java.util.Date((long)i.startTime*1000);
+            LOG.debug("TruSleepDataAcc {} : {}", startTime, String.format("%04x", i.flags));
+        }
+        for (HuaweiTruSleepParser.TruSleepDataPpg i : ppg) {
+            java.util.Date startTime = new java.util.Date((long) i.startTime);
+            LOG.debug("TruSleepDataPpg {} : {}", startTime, String.format("%04x", i.flags));
+        }
+    }
+
+    public static void analyze(HuaweiSupportProvider provider, TruSleepStatus[] status, TruSleepData data) {
+
+        // Analyse each time range as specified by TruSleepStatus
+        for (TruSleepStatus s : status) {
+
+            ArrayList<TruSleepDataAcc> acc = new ArrayList<>();
+            ArrayList<TruSleepDataPpg> ppg = new ArrayList<>();
+
+            // Collect Accelerometer data for current time frame
+            for (TruSleepDataAcc i : data.dataACCs) {
+                if (i.startTime >= s.startTime && i.startTime <= s.endTime)
+                    acc.add(i);
+            }
+
+            // Collect PPG data for current time frame
+            for (TruSleepDataPpg i : data.dataPPGs) {
+                if (i.startTime >= s.startTime && i.startTime <= s.endTime)
+                    ppg.add(i);
+            }
+
+            // Analyse time frame
+            analyzeOneTimeframe(provider, s, acc, ppg);
+        }
+    }
+
+    public static class SleepFileDownloadCallback extends HuaweiFileDownloadManager.FileDownloadCallback {
+        private byte[] statusData;
+        private byte[] sleepData;
+
+        private boolean statusSynced;
+        private boolean dataSynced;
+        protected HuaweiSupportProvider provider;
+        public SleepFileDownloadCallback(HuaweiSupportProvider provider) {
+            this.dataSynced = false;
+            this.statusSynced = false;
+            this.provider = provider;
+        }
+
+        public void syncComplete(byte[] statusData, byte[] sleepData) { }
+        @Override
+        public void downloadException(HuaweiFileDownloadManager.HuaweiFileDownloadException e) {
+            if (e.fileRequest == null) {
+                LOG.error("Failed to download TruSleep file: {}", e.toString());
+                syncComplete(statusData, sleepData);
+                return;
+            }
+
+            if (e.fileRequest.getFileType() == HuaweiFileDownloadManager.FileType.SLEEP_STATE) {
+                statusSynced = true;
+            } else if (e.fileRequest.getFileType() == HuaweiFileDownloadManager.FileType.SLEEP_DATA) {
+                dataSynced = true;
+            }
+            if (statusSynced && dataSynced)
+                syncComplete(statusData, sleepData);
+        }
+
+        @Override
+        public void downloadComplete(HuaweiFileDownloadManager.FileRequest fileRequest) {
+            if (fileRequest.getFileType() == HuaweiFileDownloadManager.FileType.SLEEP_STATE) {
+                statusData = fileRequest.getData();
+                statusSynced = true;
+            } else if (fileRequest.getFileType() == HuaweiFileDownloadManager.FileType.SLEEP_DATA) {
+                sleepData = fileRequest.getData();
+                dataSynced = true;
+            }
+            LOG.debug("Downloaded TruSleep file {}", fileRequest.getFileType());
+            if (statusSynced && dataSynced)
+                syncComplete(statusData, sleepData);
+        }
+
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiUtil.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiUtil.java
new file mode 100644
index 0000000..b00f4bf
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiUtil.java
@@ -0,0 +1,59 @@
+/*  Copyright (C) 2024 Damien Gaignon, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huawei;
+
+import java.nio.ByteBuffer;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.Locale;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class HuaweiUtil {
+    private static final Logger LOG = LoggerFactory.getLogger(HuaweiUtil.class);
+
+    public static byte[] timeToByte(String time) {
+        Calendar calendar = Calendar.getInstance();
+        DateFormat df = new SimpleDateFormat("HH:mm", Locale.ENGLISH);
+        try {
+            Date t = df.parse(time);
+            assert t != null;
+            calendar.setTime(t);
+        } catch (ParseException e) {
+            LOG.error("Time conversion error: " + e);
+            return null;
+        }
+        return new byte[]{
+            (byte)calendar.get(Calendar.HOUR_OF_DAY),
+            (byte)calendar.get(Calendar.MINUTE)};
+    }
+
+    public static byte[] getTimeAndZoneId(final Calendar now) {
+        int zoneRawOffset = (now.get(Calendar.ZONE_OFFSET) + now.get(Calendar.DST_OFFSET)) / 1000;
+        byte[] id = now.getTimeZone().getID().getBytes();
+        return ByteBuffer.allocate(6 + id.length)
+            .putInt((int)(now.getTimeInMillis() / 1000))
+            .put((byte)(zoneRawOffset < 0 ? (-zoneRawOffset / 3600 + 128) : zoneRawOffset / 3600) )
+            .put((byte)(zoneRawOffset / 60 % 60))
+            .put(id)
+            .array();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/freebuds5i/HuaweiFreebuds5iCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/freebuds5i/HuaweiFreebuds5iCoordinator.java
new file mode 100644
index 0000000..5dae2fa
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/freebuds5i/HuaweiFreebuds5iCoordinator.java
@@ -0,0 +1,35 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.huawei.freebuds5i;
+
+import androidx.annotation.NonNull;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiFreebudsCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiFreebudsSupport;
+
+public class HuaweiFreebuds5iCoordinator extends HuaweiFreebudsCoordinator {
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("huawei freebuds 5i.*", Pattern.CASE_INSENSITIVE);
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return HuaweiFreebudsSupport.class;
+    }
+
+    @Override
+    public DeviceType getDeviceType() {
+        return DeviceType.HUAWEI_FREEBUDS5I;
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_huawei_freebuds_5i;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/honorband3/HonorBand3Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/honorband3/HonorBand3Coordinator.java
new file mode 100644
index 0000000..4296ece
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/honorband3/HonorBand3Coordinator.java
@@ -0,0 +1,52 @@
+/*  Copyright (C) 2024 Damien Gaignon, Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huawei.honorband3;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiConstants;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiLECoordinator;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+
+public class HonorBand3Coordinator extends HuaweiLECoordinator {
+    @Override
+    public String getManufacturer() {
+        return "Honor";
+    }
+
+    @Override
+    public DeviceType getDeviceType() {
+        return DeviceType.HONORBAND3;
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile(HuaweiConstants.HO_BAND3_NAME + ".*", Pattern.CASE_INSENSITIVE);
+    }
+
+    @Override
+    public boolean supportsHeartRateMeasurement(GBDevice device) {
+        return true;
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_honor_band3;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/honorband4/HonorBand4Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/honorband4/HonorBand4Coordinator.java
new file mode 100644
index 0000000..8c14725
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/honorband4/HonorBand4Coordinator.java
@@ -0,0 +1,46 @@
+/*  Copyright (C) 2024 Damien Gaignon, Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huawei.honorband4;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiConstants;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiLECoordinator;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+
+public class HonorBand4Coordinator extends HuaweiLECoordinator {
+    @Override
+    public String getManufacturer() {
+        return "Honor";
+    }
+
+    @Override
+    public DeviceType getDeviceType() {
+        return DeviceType.HONORBAND4;
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile(HuaweiConstants.HO_BAND4_NAME + ".*", Pattern.CASE_INSENSITIVE);
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_honor_band4;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/honorband5/HonorBand5Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/honorband5/HonorBand5Coordinator.java
new file mode 100644
index 0000000..0251850
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/honorband5/HonorBand5Coordinator.java
@@ -0,0 +1,46 @@
+/*  Copyright (C) 2024 Damien Gaignon, Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huawei.honorband5;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiConstants;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiLECoordinator;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+
+public class HonorBand5Coordinator extends HuaweiLECoordinator {
+    @Override
+    public String getManufacturer() {
+        return "Honor";
+    }
+
+    @Override
+    public DeviceType getDeviceType() {
+        return DeviceType.HONORBAND5;
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile(HuaweiConstants.HO_BAND5_NAME + ".*", Pattern.CASE_INSENSITIVE);
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_honor_band5;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/honorband6/HonorBand6Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/honorband6/HonorBand6Coordinator.java
new file mode 100644
index 0000000..3a37345
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/honorband6/HonorBand6Coordinator.java
@@ -0,0 +1,46 @@
+/*  Copyright (C) 2024 Damien Gaignon, Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huawei.honorband6;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiConstants;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiLECoordinator;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+
+public class HonorBand6Coordinator extends HuaweiLECoordinator {
+    @Override
+    public String getManufacturer() {
+        return "Honor";
+    }
+
+    @Override
+    public DeviceType getDeviceType() {
+        return DeviceType.HONORBAND6;
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile(HuaweiConstants.HO_BAND6_NAME + ".*", Pattern.CASE_INSENSITIVE);
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_honor_band6;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/honorband7/HonorBand7Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/honorband7/HonorBand7Coordinator.java
new file mode 100644
index 0000000..97db95d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/honorband7/HonorBand7Coordinator.java
@@ -0,0 +1,46 @@
+/*  Copyright (C) 2024 Damien Gaignon, Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huawei.honorband7;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiConstants;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiLECoordinator;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+
+public class HonorBand7Coordinator extends HuaweiLECoordinator {
+    @Override
+    public String getManufacturer() {
+        return "Honor";
+    }
+
+    @Override
+    public DeviceType getDeviceType() {
+        return DeviceType.HUAWEIBAND7;
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile(HuaweiConstants.HO_BAND7_NAME + ".*", Pattern.CASE_INSENSITIVE);
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_honor_band7;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/honormagicwatch2/HonorMagicWatch2Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/honormagicwatch2/HonorMagicWatch2Coordinator.java
new file mode 100644
index 0000000..96fa3ae
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/honormagicwatch2/HonorMagicWatch2Coordinator.java
@@ -0,0 +1,51 @@
+/*  Copyright (C) 2024 Vitaly Tomin, Gaignon Damien
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huawei.honormagicwatch2;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiBRCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiConstants;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+
+public class HonorMagicWatch2Coordinator extends HuaweiBRCoordinator {
+    public HonorMagicWatch2Coordinator() {
+        super();
+        getHuaweiCoordinator().setTransactionCrypted(false);
+    }
+
+    @Override
+    public String getManufacturer() {
+        return "Honor";
+    }
+
+    @Override
+    public DeviceType getDeviceType() {
+        return DeviceType.HONORMAGICWATCH2;
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile(HuaweiConstants.HO_MAGICWATCH2_NAME + ".*", Pattern.CASE_INSENSITIVE);
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_honor_magicwatch2;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/honorwatchgs3/HonorWatchGS3Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/honorwatchgs3/HonorWatchGS3Coordinator.java
new file mode 100644
index 0000000..0666adf
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/honorwatchgs3/HonorWatchGS3Coordinator.java
@@ -0,0 +1,51 @@
+/*  Copyright (C) 2024 Vitaly Tomin
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huawei.honorwatchgs3;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiBRCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiConstants;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+
+public class HonorWatchGS3Coordinator extends HuaweiBRCoordinator {
+    public HonorWatchGS3Coordinator() {
+        super();
+        getHuaweiCoordinator().setTransactionCrypted(false);
+    }
+
+    @Override
+    public String getManufacturer() {
+        return "Honor";
+    }
+
+    @Override
+    public DeviceType getDeviceType() {
+        return DeviceType.HONORWATCHGS3;
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile(HuaweiConstants.HO_WATCHGS3_NAME + ".*", Pattern.CASE_INSENSITIVE);
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_honor_watchgs3;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/honorwatchgspro/HonorWatchGSProCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/honorwatchgspro/HonorWatchGSProCoordinator.java
new file mode 100644
index 0000000..0d4691e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/honorwatchgspro/HonorWatchGSProCoordinator.java
@@ -0,0 +1,51 @@
+/*  Copyright (C) 2024 Vitaly Tomin, Gaignon Damien
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huawei.honorwatchgspro;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiBRCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiConstants;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+
+public class HonorWatchGSProCoordinator extends HuaweiBRCoordinator {
+    public HonorWatchGSProCoordinator() {
+        super();
+        getHuaweiCoordinator().setTransactionCrypted(false);
+    }
+
+    @Override
+    public String getManufacturer() {
+        return "Honor";
+    }
+
+    @Override
+    public DeviceType getDeviceType() {
+        return DeviceType.HONORWATCHGSPRO;
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile(HuaweiConstants.HO_WATCHGSPRO_NAME + ".*", Pattern.CASE_INSENSITIVE);
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_honor_watchgspro;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/huaweiband2pro/HuaweiBand2ProCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/huaweiband2pro/HuaweiBand2ProCoordinator.java
new file mode 100644
index 0000000..2ba218e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/huaweiband2pro/HuaweiBand2ProCoordinator.java
@@ -0,0 +1,41 @@
+/*  Copyright (C) 2024 Damien Gaignon, Guido J盲kel, Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huawei.huaweiband2pro;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiConstants;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiLECoordinator;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+
+public class HuaweiBand2ProCoordinator extends HuaweiLECoordinator {
+    @Override
+    public DeviceType getDeviceType() {
+        return DeviceType.HUAWEIBAND2PRO;
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("(" + HuaweiConstants.HU_BAND2_NAME + "|" + HuaweiConstants.HU_BAND2PRO_NAME + ").*", Pattern.CASE_INSENSITIVE);
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_huawei_band2pro;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/huaweiband3pro/HuaweiBand3ProCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/huaweiband3pro/HuaweiBand3ProCoordinator.java
new file mode 100644
index 0000000..ed5d067
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/huaweiband3pro/HuaweiBand3ProCoordinator.java
@@ -0,0 +1,41 @@
+/*  Copyright (C) 2024 Damien Gaignon, Guido J盲kel, Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huawei.huaweiband3pro;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiConstants;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiLECoordinator;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+
+public class HuaweiBand3ProCoordinator extends HuaweiLECoordinator {
+    @Override
+    public DeviceType getDeviceType() {
+        return DeviceType.HUAWEIBAND3PRO;
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("(" + HuaweiConstants.HU_BAND3_NAME + "|" + HuaweiConstants.HU_BAND3PRO_NAME + ").*", Pattern.CASE_INSENSITIVE);
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_huawei_band3pro;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/huaweiband4pro/HuaweiBand4ProCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/huaweiband4pro/HuaweiBand4ProCoordinator.java
new file mode 100644
index 0000000..edfd06b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/huaweiband4pro/HuaweiBand4ProCoordinator.java
@@ -0,0 +1,41 @@
+/*  Copyright (C) 2024 Damien Gaignon, Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huawei.huaweiband4pro;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiConstants;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiLECoordinator;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+
+public class HuaweiBand4ProCoordinator extends HuaweiLECoordinator {
+    @Override
+    public DeviceType getDeviceType() {
+        return DeviceType.HUAWEIBAND4PRO;
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("(" + HuaweiConstants.HU_BAND4_NAME + "|" + HuaweiConstants.HU_BAND4PRO_NAME + ").*", Pattern.CASE_INSENSITIVE);
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_huawei_band4pro;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/huaweiband6/HuaweiBand6Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/huaweiband6/HuaweiBand6Coordinator.java
new file mode 100644
index 0000000..c74acf5
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/huaweiband6/HuaweiBand6Coordinator.java
@@ -0,0 +1,41 @@
+/*  Copyright (C) 2024 Damien Gaignon, Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huawei.huaweiband6;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiConstants;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiLECoordinator;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+
+public class HuaweiBand6Coordinator extends HuaweiLECoordinator {
+    @Override
+    public DeviceType getDeviceType() {
+        return DeviceType.HUAWEIBAND6;
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile(HuaweiConstants.HU_BAND6_NAME + ".*", Pattern.CASE_INSENSITIVE);
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_huawei_band6;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/huaweiband7/HuaweiBand7Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/huaweiband7/HuaweiBand7Coordinator.java
new file mode 100644
index 0000000..e29347a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/huaweiband7/HuaweiBand7Coordinator.java
@@ -0,0 +1,41 @@
+/*  Copyright (C) 2024 Damien Gaignon, Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huawei.huaweiband7;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiConstants;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiLECoordinator;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+
+public class HuaweiBand7Coordinator extends HuaweiLECoordinator {
+    @Override
+    public DeviceType getDeviceType() {
+        return DeviceType.HUAWEIBAND7;
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile(HuaweiConstants.HU_BAND7_NAME + ".*", Pattern.CASE_INSENSITIVE);
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_huawei_band7;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/huaweiband8/HuaweiBand8Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/huaweiband8/HuaweiBand8Coordinator.java
new file mode 100644
index 0000000..6b3a578
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/huaweiband8/HuaweiBand8Coordinator.java
@@ -0,0 +1,41 @@
+/*  Copyright (C) 2024 Damien Gaignon, Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huawei.huaweiband8;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiConstants;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiLECoordinator;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+
+public class HuaweiBand8Coordinator extends HuaweiLECoordinator {
+    @Override
+    public DeviceType getDeviceType() {
+        return DeviceType.HUAWEIBAND8;
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile(HuaweiConstants.HU_BAND8_NAME + ".*", Pattern.CASE_INSENSITIVE);
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_huawei_band8;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/huaweiband9/HuaweiBand9Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/huaweiband9/HuaweiBand9Coordinator.java
new file mode 100644
index 0000000..cfab0e3
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/huaweiband9/HuaweiBand9Coordinator.java
@@ -0,0 +1,46 @@
+/*  Copyright (C) 2024 Damien Gaignon, Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huawei.huaweiband9;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiConstants;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiLECoordinator;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+
+public class HuaweiBand9Coordinator extends HuaweiLECoordinator {
+    @Override
+    public boolean isExperimental() {
+        return true;
+    }
+
+    @Override
+    public DeviceType getDeviceType() {
+        return DeviceType.HUAWEIBAND9;
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile(HuaweiConstants.HU_BAND9_NAME + ".*", Pattern.CASE_INSENSITIVE);
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_huawei_band9;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/huaweibandaw70/HuaweiBandAw70Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/huaweibandaw70/HuaweiBandAw70Coordinator.java
new file mode 100644
index 0000000..f8fb57e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/huaweibandaw70/HuaweiBandAw70Coordinator.java
@@ -0,0 +1,46 @@
+/*  Copyright (C) 2024 Damien Gaignon, Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huawei.huaweibandaw70;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiConstants;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiLECoordinator;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+
+public class HuaweiBandAw70Coordinator extends HuaweiLECoordinator {
+    @Override
+    public DeviceType getDeviceType() {
+        return DeviceType.HUAWEIBANDAW70;
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("(" + HuaweiConstants.HU_BAND3E_NAME + "|" + HuaweiConstants.HU_BAND4E_NAME + ").*", Pattern.CASE_INSENSITIVE);
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_huawei_band_aw70;
+    }
+
+    @Override
+    public HuaweiDeviceType getHuaweiType() {
+        return HuaweiDeviceType.AW;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/huaweitalkbandb6/HuaweiTalkBandB6Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/huaweitalkbandb6/HuaweiTalkBandB6Coordinator.java
new file mode 100644
index 0000000..dbd3cce
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/huaweitalkbandb6/HuaweiTalkBandB6Coordinator.java
@@ -0,0 +1,46 @@
+/*  Copyright (C) 2024 Damien Gaignon, Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huawei.huaweitalkbandb6;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiBRCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiConstants;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+
+public class HuaweiTalkBandB6Coordinator extends HuaweiBRCoordinator {
+    public HuaweiTalkBandB6Coordinator() {
+        super();
+        getHuaweiCoordinator().setTransactionCrypted(false);
+    }
+
+    @Override
+    public DeviceType getDeviceType() {
+        return DeviceType.HUAWEITALKBANDB6;
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile(HuaweiConstants.HU_TALKBANDB6_NAME + ".*", Pattern.CASE_INSENSITIVE);
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_huawei_talk_band_b6;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/huaweiwatch3/HuaweiWatch3Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/huaweiwatch3/HuaweiWatch3Coordinator.java
new file mode 100644
index 0000000..081f574
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/huaweiwatch3/HuaweiWatch3Coordinator.java
@@ -0,0 +1,47 @@
+/*  Copyright (C) 2024 Damien Gaignon, Martin.JM, Vitalii Tomin
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huawei.huaweiwatch3;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiBRCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiConstants;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+
+public class HuaweiWatch3Coordinator extends HuaweiBRCoordinator {
+    @Override
+    public DeviceType getDeviceType() {
+        return DeviceType.HUAWEIWATCH3;
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("(" + HuaweiConstants.HU_WATCH3_NAME + "|" + HuaweiConstants.HU_WATCH3PRO_NAME + ").*", Pattern.CASE_INSENSITIVE);
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_huawei_watch3;
+    }
+
+    @Override
+    public boolean supportsUnicodeEmojis() {
+        // HarmonyOS watch
+        return true;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/huaweiwatch4pro/HuaweiWatch4ProCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/huaweiwatch4pro/HuaweiWatch4ProCoordinator.java
new file mode 100644
index 0000000..e849d50
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/huaweiwatch4pro/HuaweiWatch4ProCoordinator.java
@@ -0,0 +1,52 @@
+/*  Copyright (C) 2024 Damien Gaignon
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huawei.huaweiwatch4pro;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiBRCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiConstants;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+
+public class HuaweiWatch4ProCoordinator extends HuaweiBRCoordinator {
+    public HuaweiWatch4ProCoordinator() {
+        super();
+        getHuaweiCoordinator().setTransactionCrypted(true);
+    }
+
+    @Override
+    public DeviceType getDeviceType() {
+        return DeviceType.HUAWEIWATCH4PRO;
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("(" + HuaweiConstants.HU_WATCH4_NAME + "|" + HuaweiConstants.HU_WATCH4PRO_NAME + ").*", Pattern.CASE_INSENSITIVE);
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_huawei_watch4pro;
+    }
+
+    @Override
+    public boolean supportsUnicodeEmojis() {
+        // HarmonyOS watch
+        return true;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/huaweiwatchd2/HuaweiWatchD2Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/huaweiwatchd2/HuaweiWatchD2Coordinator.java
new file mode 100644
index 0000000..911bb7f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/huaweiwatchd2/HuaweiWatchD2Coordinator.java
@@ -0,0 +1,46 @@
+/*  Copyright (C) 2024 Vitalii Tomin 
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huawei.huaweiwatchd2;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiBRCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiConstants;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+
+public class HuaweiWatchD2Coordinator extends HuaweiBRCoordinator {
+    @Override
+    public DeviceType getDeviceType() {
+        return DeviceType.HUAWEIWATCHD2;
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("(" + HuaweiConstants.HU_WATCHD2_NAME + ").*", Pattern.CASE_INSENSITIVE);
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_huawei_watchd2;
+    }
+
+    @Override
+    public boolean supportsUnicodeEmojis() {
+        return true;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/huaweiwatchfit/HuaweiWatchFitCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/huaweiwatchfit/HuaweiWatchFitCoordinator.java
new file mode 100644
index 0000000..ed727d1
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/huaweiwatchfit/HuaweiWatchFitCoordinator.java
@@ -0,0 +1,41 @@
+/*  Copyright (C) 2024 Damien Gaignon, Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huawei.huaweiwatchfit;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiConstants;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiLECoordinator;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+
+public class HuaweiWatchFitCoordinator extends HuaweiLECoordinator {
+    @Override
+    public DeviceType getDeviceType() {
+        return DeviceType.HUAWEIWATCHFIT;
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile(HuaweiConstants.HU_WATCHFIT_NAME + ".*", Pattern.CASE_INSENSITIVE);
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_huawei_watchfit;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/huaweiwatchfit2/HuaweiWatchFit2Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/huaweiwatchfit2/HuaweiWatchFit2Coordinator.java
new file mode 100644
index 0000000..3c9ab89
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/huaweiwatchfit2/HuaweiWatchFit2Coordinator.java
@@ -0,0 +1,46 @@
+/*  Copyright (C) 2024 Damien Gaignon
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huawei.huaweiwatchfit2;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiBRCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiConstants;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+
+public class HuaweiWatchFit2Coordinator extends HuaweiBRCoordinator {
+    public HuaweiWatchFit2Coordinator() {
+        super();
+        getHuaweiCoordinator().setTransactionCrypted(true);
+    }
+
+    @Override
+    public DeviceType getDeviceType() {
+        return DeviceType.HUAWEIWATCHFIT2;
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("(" + HuaweiConstants.HU_WATCHFIT2_NAME + ").*", Pattern.CASE_INSENSITIVE);
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_huawei_watchfit2;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/huaweiwatchfit3/HuaweiWatchFit3Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/huaweiwatchfit3/HuaweiWatchFit3Coordinator.java
new file mode 100644
index 0000000..db44d18
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/huaweiwatchfit3/HuaweiWatchFit3Coordinator.java
@@ -0,0 +1,57 @@
+/*  Copyright (C) 2024 Damien Gaignon
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huawei.huaweiwatchfit3;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiBRCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiConstants;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+
+public class HuaweiWatchFit3Coordinator extends HuaweiBRCoordinator {
+    public HuaweiWatchFit3Coordinator() {
+        super();
+        getHuaweiCoordinator().setTransactionCrypted(true);
+    }
+
+    @Override
+    public boolean isExperimental() {
+        return true;
+    }
+
+    @Override
+    public DeviceType getDeviceType() {
+        return DeviceType.HUAWEIWATCHFIT3;
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("(" + HuaweiConstants.HU_WATCHFIT3_NAME + ").*", Pattern.CASE_INSENSITIVE);
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_huawei_watchfit3;
+    }
+
+    @Override
+    public boolean supportsUnicodeEmojis() {
+        // HarmonyOS watch
+        return true;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/huaweiwatchgt/HuaweiWatchGTCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/huaweiwatchgt/HuaweiWatchGTCoordinator.java
new file mode 100644
index 0000000..89b9b12
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/huaweiwatchgt/HuaweiWatchGTCoordinator.java
@@ -0,0 +1,47 @@
+/*  Copyright (C) 2024 Damien Gaignon, Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huawei.huaweiwatchgt;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiConstants;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiLECoordinator;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+
+public class HuaweiWatchGTCoordinator extends HuaweiLECoordinator {
+    @Override
+    public DeviceType getDeviceType() {
+        return DeviceType.HUAWEIWATCHGT;
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile(HuaweiConstants.HU_WATCHGT_NAME + ".*", Pattern.CASE_INSENSITIVE);
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_huawei_watch_gt;
+    }
+
+    //@Override
+    //public HuaweiDeviceType getHuaweiType() {
+        // Could be SMART
+    //    return HuaweiDeviceType.BLE;
+    //}
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/huaweiwatchgt2/HuaweiWatchGT2Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/huaweiwatchgt2/HuaweiWatchGT2Coordinator.java
new file mode 100644
index 0000000..b55b934
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/huaweiwatchgt2/HuaweiWatchGT2Coordinator.java
@@ -0,0 +1,41 @@
+/*  Copyright (C) 2024 Damien Gaignon, Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huawei.huaweiwatchgt2;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiBRCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiConstants;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+
+public class HuaweiWatchGT2Coordinator extends HuaweiBRCoordinator {
+    @Override
+    public DeviceType getDeviceType() {
+        return DeviceType.HUAWEIWATCHGT2;
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("(" + HuaweiConstants.HU_WATCHGT2_NAME + "|" + HuaweiConstants.HU_WATCHGT2PRO_NAME + ").*", Pattern.CASE_INSENSITIVE);
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_huawei_watchgt2;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/huaweiwatchgt2e/HuaweiWatchGT2eCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/huaweiwatchgt2e/HuaweiWatchGT2eCoordinator.java
new file mode 100644
index 0000000..c064e9f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/huaweiwatchgt2e/HuaweiWatchGT2eCoordinator.java
@@ -0,0 +1,47 @@
+/*  Copyright (C) 2024 Damien Gaignon, Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huawei.huaweiwatchgt2e;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiConstants;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiLECoordinator;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+
+public class HuaweiWatchGT2eCoordinator extends HuaweiLECoordinator {
+    @Override
+    public DeviceType getDeviceType() {
+        return DeviceType.HUAWEIWATCHGT2E;
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile(HuaweiConstants.HU_WATCHGT2E_NAME + ".*", Pattern.CASE_INSENSITIVE);
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_huawei_watchgt2e;
+    }
+
+    //@Override
+    //public HuaweiDeviceType getHuaweiType() {
+        // Could be SMART
+    //    return HuaweiDeviceType.BLE;
+    //}
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/huaweiwatchgt3/HuaweiWatchGT3Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/huaweiwatchgt3/HuaweiWatchGT3Coordinator.java
new file mode 100644
index 0000000..09ba4e4
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/huaweiwatchgt3/HuaweiWatchGT3Coordinator.java
@@ -0,0 +1,47 @@
+/*  Copyright (C) 2024 Damien Gaignon, Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huawei.huaweiwatchgt3;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiBRCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiConstants;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+
+public class HuaweiWatchGT3Coordinator extends HuaweiBRCoordinator {
+    @Override
+    public DeviceType getDeviceType() {
+        return DeviceType.HUAWEIWATCHGT3;
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("(" + HuaweiConstants.HU_WATCHGT3_NAME + "|" + HuaweiConstants.HU_WATCHGT3PRO_NAME + "|" + HuaweiConstants.HU_WATCHGT3SE_NAME + ").*", Pattern.CASE_INSENSITIVE);
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_huawei_watchgt3;
+    }
+
+    @Override
+    public boolean supportsUnicodeEmojis() {
+        // HarmonyOS watch
+        return true;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/huaweiwatchgt4/HuaweiWatchGT4Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/huaweiwatchgt4/HuaweiWatchGT4Coordinator.java
new file mode 100644
index 0000000..f0b862c
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/huaweiwatchgt4/HuaweiWatchGT4Coordinator.java
@@ -0,0 +1,52 @@
+/*  Copyright (C) 2024 Vitalii Tomin
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huawei.huaweiwatchgt4;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiBRCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiConstants;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+
+public class HuaweiWatchGT4Coordinator extends HuaweiBRCoordinator {
+    public HuaweiWatchGT4Coordinator() {
+        super();
+        getHuaweiCoordinator().setTransactionCrypted(true);
+    }
+
+    @Override
+    public DeviceType getDeviceType() {
+        return DeviceType.HUAWEIWATCHGT4;
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("(" + HuaweiConstants.HU_WATCHGT4_NAME + ").*", Pattern.CASE_INSENSITIVE);
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_huawei_watchgt4;
+    }
+
+    @Override
+    public boolean supportsUnicodeEmojis() {
+        // HarmonyOS watch
+        return true;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/huaweiwatchgt5/HuaweiWatchGT5Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/huaweiwatchgt5/HuaweiWatchGT5Coordinator.java
new file mode 100644
index 0000000..b39cc48
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/huaweiwatchgt5/HuaweiWatchGT5Coordinator.java
@@ -0,0 +1,47 @@
+/*  Copyright (C) 2024 Damien Gaignon, Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huawei.huaweiwatchgt5;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiBRCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiConstants;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+
+public class HuaweiWatchGT5Coordinator extends HuaweiBRCoordinator {
+    @Override
+    public DeviceType getDeviceType() {
+        return DeviceType.HUAWEIWATCHGT5;
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("(" + HuaweiConstants.HU_WATCHGT5_NAME + "|" + HuaweiConstants.HU_WATCHGT5PRO_NAME + ").*", Pattern.CASE_INSENSITIVE);
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_huawei_watchgt5;
+    }
+
+    @Override
+    public boolean supportsUnicodeEmojis() {
+        // HarmonyOS watch
+        return true;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/huaweiwatchgtcyber/HuaweiWatchGTCyberCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/huaweiwatchgtcyber/HuaweiWatchGTCyberCoordinator.java
new file mode 100644
index 0000000..538c443
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/huaweiwatchgtcyber/HuaweiWatchGTCyberCoordinator.java
@@ -0,0 +1,47 @@
+/*  Copyright (C) 2024 Vitalii Tomin
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huawei.huaweiwatchgtcyber;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiBRCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiConstants;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+
+public class HuaweiWatchGTCyberCoordinator extends HuaweiBRCoordinator {
+    @Override
+    public DeviceType getDeviceType() {
+        return DeviceType.HUAWEIWATCHGTCYBER;
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("(" + HuaweiConstants.HU_WATCHGTCYBER_NAME + ").*", Pattern.CASE_INSENSITIVE);
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_huawei_watchgtcyber;
+    }
+
+    @Override
+    public boolean supportsUnicodeEmojis() {
+        // HarmonyOS watch
+        return true;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/huaweiwatchgtrunner/HuaweiWatchGTRunnerCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/huaweiwatchgtrunner/HuaweiWatchGTRunnerCoordinator.java
new file mode 100644
index 0000000..56328dc
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/huaweiwatchgtrunner/HuaweiWatchGTRunnerCoordinator.java
@@ -0,0 +1,47 @@
+/*  Copyright (C) 2024 Vitalii Tomin
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huawei.huaweiwatchgtrunner;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiBRCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiConstants;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+
+public class HuaweiWatchGTRunnerCoordinator extends HuaweiBRCoordinator {
+    @Override
+    public DeviceType getDeviceType() {
+        return DeviceType.HUAWEIWATCHGTRUNNER;
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("(" + HuaweiConstants.HU_WATCHGTRUNNER_NAME + ").*", Pattern.CASE_INSENSITIVE);
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_huawei_watchgtrunner;
+    }
+
+    @Override
+    public boolean supportsUnicodeEmojis() {
+        // HarmonyOS watch
+        return true;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/huaweiwatchultimate/HuaweiWatchUltimateCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/huaweiwatchultimate/HuaweiWatchUltimateCoordinator.java
new file mode 100644
index 0000000..6e7831c
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/huaweiwatchultimate/HuaweiWatchUltimateCoordinator.java
@@ -0,0 +1,52 @@
+/*  Copyright (C) 2024 Damien Gaignon
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huawei.huaweiwatchultimate;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiBRCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiConstants;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+
+public class HuaweiWatchUltimateCoordinator extends HuaweiBRCoordinator {
+    public HuaweiWatchUltimateCoordinator() {
+        super();
+        getHuaweiCoordinator().setTransactionCrypted(true);
+    }
+
+    @Override
+    public DeviceType getDeviceType() {
+        return DeviceType.HUAWEIWATCHULTIMATE;
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("(" + HuaweiConstants.HU_WATCHULTIMATE_NAME + ").*", Pattern.CASE_INSENSITIVE);
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_huawei_watchultimate;
+    }
+
+    @Override
+    public boolean supportsUnicodeEmojis() {
+        // HarmonyOS watch
+        return true;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/AccountRelated.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/AccountRelated.java
new file mode 100644
index 0000000..ecfb88d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/AccountRelated.java
@@ -0,0 +1,82 @@
+/*  Copyright (C) 2024 Damien Gaignon, Vitalii Tomin
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiTLV;
+
+public class AccountRelated {
+	public static final byte id = 0x1A;
+
+	public static class SendAccountToDevice {
+		public static final byte id = 0x01;
+
+		public static class Request extends HuaweiPacket {
+			public Request (ParamsProvider paramsProvider, String account) {
+				super(paramsProvider);
+
+				this.serviceId = AccountRelated.id;
+				this.commandId = id;
+
+				this.tlv = new HuaweiTLV();
+				if (account.length() > 0) {
+					tlv.put(0x01, account);
+				} else {
+					tlv.put(0x01);
+				}
+				this.complete = true;
+			}
+		}
+
+		public static class Response extends HuaweiPacket {
+			public Response (ParamsProvider paramsProvider) {
+				super(paramsProvider);
+			}
+		}
+	}
+
+	public static class SendExtendedAccountToDevice {
+		public static final byte id = 0x05;
+
+		public static class Request extends HuaweiPacket {
+			public Request (ParamsProvider paramsProvider, boolean accountPairingOptimization, String account) {
+				super(paramsProvider);
+
+				this.serviceId = AccountRelated.id;
+				this.commandId = id;
+
+				this.tlv = new HuaweiTLV();
+				if (account.length() > 0) {
+					tlv.put(0x01, account);
+				} else {
+					tlv.put(0x01, (byte)0x00);
+				}
+
+				if (accountPairingOptimization) {
+					this.tlv.put(0x03, (byte)0x01);
+				}
+				this.complete = true;
+			}
+		}
+
+		public static class Response extends HuaweiPacket {
+			public Response (ParamsProvider paramsProvider) {
+				super(paramsProvider);
+			}
+		}
+	}
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/Alarms.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/Alarms.java
new file mode 100644
index 0000000..abb996f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/Alarms.java
@@ -0,0 +1,257 @@
+/*  Copyright (C) 2024 Damien Gaignon, Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket.ParseException;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiTLV;
+
+// TODO: complete responses
+
+public class Alarms {
+
+    public static class EventAlarm {
+        public byte index;
+        public boolean status = false;
+        public byte startHour = -1;
+        public byte startMinute = -1;
+        public byte repeat = 0;
+        public String name = "Alarm";
+
+        public EventAlarm(HuaweiTLV tlv) throws ParseException {
+            this.index = tlv.getByte(0x03);
+            if (tlv.contains(0x04))
+                this.status = tlv.getBoolean(0x04);
+            if (this.status || tlv.contains(0x05)) {
+                // I think we should refuse to set the alarm to a default time if it's enabled.
+                // If it is enabled, it should have the time set.
+                this.startHour = (byte) ((tlv.getShort(0x05) >> 8) & 0xFF);
+                this.startMinute = (byte) (tlv.getShort(0x05) & 0xFF);
+            }
+            if (tlv.contains(0x06))
+                this.repeat = tlv.getByte(0x06);
+            if (tlv.contains(0x07))
+                this.name = tlv.getString(0x07);
+        }
+
+        public EventAlarm(byte index, boolean status, byte startHour, byte startMinute, byte repeat, String name) {
+            this.index = index;
+            this.status = status;
+            this.startHour = startHour;
+            this.startMinute = startMinute;
+            this.repeat = repeat;
+            this.name = name;
+        }
+
+        public HuaweiTLV asTlv() {
+            return new HuaweiTLV()
+                    .put(0x03, index)
+                    .put(0x04, status)
+                    .put(0x05, (short) ((startHour << 8) | (startMinute & 0xFF)))
+                    .put(0x06, repeat)
+                    .put(0x07, name);
+        }
+
+        @Override
+        public String toString() {
+            return "EventAlarm{" +
+                    "index=" + index +
+                    ", status=" + status +
+                    ", startHour=" + startHour +
+                    ", startMinute=" + startMinute +
+                    ", repeat=" + repeat +
+                    ", name='" + name + '\'' +
+                    '}';
+        }
+    }
+
+    public static class SmartAlarm {
+        public byte index;
+        public boolean status;
+        public byte startHour;
+        public byte startMinute;
+        public byte repeat;
+        public byte aheadTime;
+
+        public SmartAlarm(HuaweiTLV tlv) throws ParseException {
+            this.index = tlv.getByte(0x03);
+            this.status = tlv.getBoolean(0x04, false);
+            this.startHour = (byte) ((tlv.getShort(0x05, (short) 0) >> 8) & 0xFF);
+            this.startMinute = (byte) (tlv.getShort(0x05, (short) 0) & 0xFF);
+            this.repeat = tlv.getByte(0x06, (byte) 0);
+            this.aheadTime = tlv.getByte(0x07, (byte) 0);
+        }
+
+        public SmartAlarm(boolean status, byte startHour, byte startMinute, byte repeat, byte aheadTime) {
+            this.index = 1;
+            this.status = status;
+            this.startHour = startHour;
+            this.startMinute = startMinute;
+            this.repeat = repeat;
+            this.aheadTime = aheadTime;
+        }
+
+        public HuaweiTLV asTlv() {
+            return new HuaweiTLV()
+                    .put(0x03, index)
+                    .put(0x04, status)
+                    .put(0x05, (short) ((startHour << 8) | (startMinute & 0xFF)))
+                    .put(0x06, repeat)
+                    .put(0x07, aheadTime);
+        }
+
+        @Override
+        public String toString() {
+            return "SmartAlarm{" +
+                    "index=" + index +
+                    ", status=" + status +
+                    ", startHour=" + startHour +
+                    ", startMinute=" + startMinute +
+                    ", repeat=" + repeat +
+                    ", aheadTime=" + aheadTime +
+                    '}';
+        }
+    }
+
+    public static final byte id = 0x08;
+
+    public static class EventAlarmsRequest extends HuaweiPacket {
+        public static final byte id = 0x01;
+
+        // TODO: move to list
+        private final HuaweiTLV alarms;
+
+        public EventAlarmsRequest(ParamsProvider paramsProvider) {
+            super(paramsProvider);
+
+            this.serviceId = Alarms.id;
+            this.commandId = id;
+
+            alarms = new HuaweiTLV();
+        }
+
+        public void addEventAlarm(EventAlarm alarm) {
+            // TODO: 5 is a max and we may need to check for that and throw an exception if passed
+            alarms.put(0x82, alarm.asTlv());
+        }
+
+        @Override
+        public List<byte[]> serialize() throws CryptoException {
+            if (this.alarms.get().size() == 0) {
+                // Empty alarms - this will disable them all
+                this.alarms.put(0x82, new HuaweiTLV().put(0x03, (byte) 0x01));
+            }
+
+            this.tlv = new HuaweiTLV().put(0x81, this.alarms);
+            this.complete = true;
+
+            return super.serialize();
+        }
+    }
+
+    public static class SmartAlarmRequest extends HuaweiPacket {
+        public static final int id = 0x02;
+
+        public SmartAlarmRequest(
+                ParamsProvider paramsProvider,
+                SmartAlarm smartAlarm
+        ) {
+            super(paramsProvider);
+
+            this.serviceId = Alarms.id;
+            this.commandId = id;
+            this.tlv = new HuaweiTLV()
+                    .put(0x81, new HuaweiTLV()
+                            .put(0x82, smartAlarm.asTlv())
+                    );
+            this.complete = true;
+        }
+    }
+
+    public static class EventAlarmsList {
+        public static final int id = 0x03;
+
+        public static class Request extends HuaweiPacket {
+            public Request(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+
+                this.serviceId = Alarms.id;
+                this.commandId = id;
+
+                this.tlv = new HuaweiTLV().put(0x01);
+
+                this.complete = true;
+            }
+        }
+
+        public static class Response extends HuaweiPacket {
+            public List<EventAlarm> eventAlarms;
+
+            public Response(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+            }
+
+            @Override
+            public void parseTlv() throws ParseException {
+                eventAlarms = new ArrayList<>();
+
+                HuaweiTLV tlv = this.tlv.getObject(0x81);
+                for (HuaweiTLV subTlv : tlv.getObjects(0x82)) {
+                    eventAlarms.add(new EventAlarm(subTlv));
+                }
+            }
+        }
+    }
+
+    public static class SmartAlarmList {
+        public static final int id = 0x04;
+
+        public static class Request extends HuaweiPacket {
+            public Request(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+
+                this.serviceId = Alarms.id;
+                this.commandId = id;
+
+                this.tlv = new HuaweiTLV().put(0x01);
+
+                this.complete = true;
+            }
+        }
+
+        public static class Response extends HuaweiPacket {
+            public SmartAlarm smartAlarm;
+
+            public Response(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+            }
+
+            @Override
+            public void parseTlv() throws ParseException {
+                HuaweiTLV tlv = this.tlv.getObject(0x81);
+                if (tlv.contains(0x82)) {
+                    this.smartAlarm = new SmartAlarm(tlv.getObject(0x82));
+                } else {
+                    this.smartAlarm = null;
+                }
+            }
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/App.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/App.java
new file mode 100644
index 0000000..ff7af2c
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/App.java
@@ -0,0 +1,144 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiTLV;
+
+public class App {
+    public static final byte id = 0x2a;
+
+    public static class AppDeviceParams {
+        public byte unknown1 = 0;
+        public int unknown2 = 0;
+        public String osVersion = "";
+        public String screenShape = "";
+        public int width = 0;
+        public int height = 0;
+        public int unknown3 = 0;
+        public String buildType = "";
+    }
+
+    public static class InstalledAppInfo {
+        public String packageName;
+        public String version;
+        public int unknown1;
+        public String appName;
+        public int unknown2;
+        public int versionCode;
+        public byte unknown4;
+        public byte unknown6;
+
+        public InstalledAppInfo(HuaweiTLV tlv) throws HuaweiPacket.MissingTagException {
+            this.packageName = tlv.getString(0x03);
+            this.version = tlv.getString(0x04);
+            this.unknown1 = tlv.getInteger(0x05);
+            this.appName =  tlv.getString(0x06);
+            this.unknown2 = tlv.getInteger(0x07);
+            this.versionCode = tlv.getInteger(0x09);
+            this.unknown4 = tlv.getByte(0x0a);
+            //{tag: b - Value: } -
+            this.unknown6 = tlv.getByte(0x0d);
+        }
+    }
+
+    public static class AppDelete {
+        public static final byte id = 0x01;
+
+        public static class Request extends HuaweiPacket {
+            public Request(ParamsProvider paramsProvider,
+                           String packageName) {
+                super(paramsProvider);
+                this.serviceId = App.id;
+                this.commandId = id;
+                this.tlv = new HuaweiTLV()
+                        .put(0x01, (byte)1)
+                        .put(0x02, packageName);
+            }
+        }
+
+        public static class Response extends HuaweiPacket {
+            public Response (ParamsProvider paramsProvider) {
+                super(paramsProvider);
+            }
+        }
+    }
+
+    public static class AppNames {
+        public static final byte id = 0x03;
+
+        public static class Request extends HuaweiPacket {
+            public Request(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+                this.serviceId = App.id;
+                this.commandId = id;
+                this.tlv = new HuaweiTLV()
+                        .put(0x81);
+            }
+        }
+
+        public static class Response extends HuaweiPacket {
+
+            public List<App.InstalledAppInfo> appInfoList;
+            public Response (ParamsProvider paramsProvider) {
+                super(paramsProvider);
+            }
+
+            @Override
+            public void parseTlv() throws HuaweiPacket.ParseException {
+                appInfoList = new ArrayList<>();
+                if(this.tlv.contains(0x81)) {
+                    for (HuaweiTLV subTlv : this.tlv.getObject(0x81).getObjects(0x82)) {
+                        appInfoList.add(new InstalledAppInfo(subTlv));
+                    }
+                }
+            }
+        }
+    }
+
+    public static class AppInfoParams {
+        public static final byte id = 0x06;
+        public static class Request extends HuaweiPacket {
+
+            public Request(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+                this.serviceId = App.id;
+                this.commandId = id;
+
+                this.tlv = new HuaweiTLV()
+                        .put(0x81);
+
+                this.complete = true;
+            }
+        }
+
+        public static class Response extends HuaweiPacket {
+            public AppDeviceParams params = new AppDeviceParams();
+            public Response (ParamsProvider paramsProvider) {
+                super(paramsProvider);
+            }
+
+            @Override
+            public void parseTlv() throws ParseException {
+                if(this.tlv.contains(0x81)) {
+                    HuaweiTLV subTlv = this.tlv.getObject(0x81).getObject(0x82);
+                    this.params.unknown1 = subTlv.getByte(0x03);
+                    this.params.unknown2 = subTlv.getInteger(0x04);
+                    this.params.osVersion = subTlv.getString(0x05);
+                    if (subTlv.contains(0x06))
+                        this.params.screenShape = subTlv.getString(0x06);
+                    if (subTlv.contains(0x07))
+                        this.params.width = subTlv.getShort(0x07);
+                    if (subTlv.contains(0x08))
+                        this.params.height = subTlv.getShort(0x08);
+                    if (subTlv.contains(0x09))
+                        this.params.unknown3 = subTlv.getInteger(0x09);
+                    if(subTlv.contains(0x0a))
+                        this.params.buildType = subTlv.getString(0x0a);
+                }
+            }
+        }
+    }
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/Calls.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/Calls.java
new file mode 100644
index 0000000..bfaf160
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/Calls.java
@@ -0,0 +1,60 @@
+/*  Copyright (C) 2024 Damien Gaignon, Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+
+public class Calls {
+
+    // This doesn't include the initial calling notification, as that is handled
+    // by the Notifications class.
+
+    public static final byte id = 0x04;
+
+    // TODO: tests
+
+    public static class AnswerCallResponse extends HuaweiPacket {
+        public static final byte id = 0x01;
+
+        public enum Action {
+            CALL_ACCEPT,
+            CALL_REJECT,
+            UNKNOWN
+        }
+
+        public Action action = Action.UNKNOWN;
+
+        public AnswerCallResponse(ParamsProvider paramsProvider) {
+            super(paramsProvider);
+
+            this.serviceId = Calls.id;
+            this.commandId = id;
+
+            this.isEncrypted = false;
+        }
+
+        @Override
+        public void parseTlv() throws MissingTagException {
+            if (this.tlv.getByte(0x01) == 0x01) {
+                this.action = Action.CALL_REJECT;
+            } else if (this.tlv.getByte(0x01) == 0x02) {
+                this.action = Action.CALL_ACCEPT;
+            }
+            // TODO: find more values, if there are any
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/CameraRemote.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/CameraRemote.java
new file mode 100644
index 0000000..232d939
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/CameraRemote.java
@@ -0,0 +1,110 @@
+/*  Copyright (C) 2024 Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiTLV;
+
+public class CameraRemote {
+    public static final byte id = 0x01;
+
+    public static class CameraRemoteSetup {
+        public static final byte id = 0x2a;
+
+        public static class Request extends HuaweiPacket {
+            public enum Event {
+                ENABLE_CAMERA,
+                CAMERA_STARTED,
+                CAMERA_STOPPED
+            }
+
+            public Request(ParamsProvider paramsProvider, Event event) {
+                super(paramsProvider);
+
+                this.serviceId = CameraRemote.id;
+                this.commandId = id;
+
+                this.tlv = new HuaweiTLV();
+                switch (event) {
+                    case ENABLE_CAMERA:
+                        this.tlv.put(0x01, (byte) 0x00);
+                        break;
+                    case CAMERA_STARTED:
+                        this.tlv.put(0x01, (byte) 0x01);
+                        break;
+                    case CAMERA_STOPPED:
+                        this.tlv.put(0x01, (byte) 0x02);
+                        break;
+                }
+
+                this.complete = true;
+                this.isEncrypted = true;
+            }
+        }
+    }
+
+    public static class CameraRemoteStatus {
+        public static final byte id = 0x29;
+
+        public static class Request extends HuaweiPacket {
+            // All responses are async, and must be ACK-ed
+            public Request(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+
+                this.serviceId = CameraRemote.id;
+                this.commandId = id;
+
+                this.tlv = new HuaweiTLV()
+                        .put(0x7f, 0x186A0);
+
+                this.complete = true;
+                this.isEncrypted = true;
+            }
+        }
+
+        public static class Response extends HuaweiPacket {
+            public enum Event {
+                OPEN_CAMERA,
+                TAKE_PICTURE,
+                CLOSE_CAMERA
+            }
+
+            public Event event;
+
+            public Response(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+                this.serviceId = CameraRemote.id;
+                this.commandId = id;
+            }
+
+            @Override
+            public void parseTlv() throws ParseException {
+                switch (this.tlv.getByte(0x01)) {
+                    case 1:
+                        this.event = Event.OPEN_CAMERA;
+                        break;
+                    case 2:
+                        this.event = Event.TAKE_PICTURE;
+                        break;
+                    case 3:
+                        this.event = Event.CLOSE_CAMERA;
+                        break;
+                }
+            }
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/Contacts.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/Contacts.java
new file mode 100644
index 0000000..4de952b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/Contacts.java
@@ -0,0 +1,93 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets;
+
+import java.util.ArrayList;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiTLV;
+import nodomain.freeyourgadget.gadgetbridge.model.Contact;
+
+public class Contacts {
+    public static final byte id = 0x03;
+
+    public static class ContactsSet {
+        public static final byte id = 0x01;
+
+        public static class Request extends HuaweiPacket {
+            public Request(ParamsProvider paramsProvider, ArrayList<? extends Contact> contacts, int maxCount) {
+                super(paramsProvider);
+
+                this.serviceId = Contacts.id;
+                this.commandId = id;
+
+                HuaweiTLV contacts_tlv = new HuaweiTLV();
+                for(int i = 0; i < maxCount; i++) {
+                    HuaweiTLV contact= new HuaweiTLV()
+                            .put(0x03, (byte) (i + 1));
+                    if(i < contacts.size()) {
+                        contact.put(0x4, contacts.get(i).getName())
+                                .put(0x85, new HuaweiTLV().put(0x86, new HuaweiTLV().put(0x7, "Mobile").put(0x8, contacts.get(i).getNumber())));
+                    }
+                    contacts_tlv.put(0x82, contact);
+                }
+
+                this.tlv = new HuaweiTLV()
+                        .put(0x81, contacts_tlv);
+
+                this.complete = true;
+                this.isEncrypted = true;
+            }
+        }
+
+        public static class Response extends HuaweiPacket {
+
+            public boolean isOk;
+
+            public Response(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+                this.serviceId = Contacts.id;
+                this.commandId = id;
+            }
+
+            @Override
+            public void parseTlv() throws ParseException {
+                isOk = this.tlv.getInteger(0x7f) == 0x000186A0;
+            }
+        }
+    }
+
+    public static class ContactsCount {
+        public static final byte id = 0x02;
+
+        public static class Request extends HuaweiPacket {
+            public Request(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+
+                this.serviceId = Contacts.id;
+                this.commandId = id;
+
+                this.tlv = new HuaweiTLV()
+                        .put(0x1)
+                        .put(0x2);
+
+                this.complete = true;
+                this.isEncrypted = true;
+            }
+        }
+
+        public static class Response extends HuaweiPacket {
+
+            public int maxCount;
+
+            public Response(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+                this.serviceId = Contacts.id;
+                this.commandId = id;
+            }
+
+            @Override
+            public void parseTlv() throws ParseException {
+                maxCount = this.tlv.getByte(0x01);
+            }
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/DeviceConfig.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/DeviceConfig.java
new file mode 100644
index 0000000..5a79c62
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/DeviceConfig.java
@@ -0,0 +1,1756 @@
+/*  Copyright (C) 2024 Damien Gaignon, Jos茅 Rebelo, Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets;
+
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.List;
+import java.util.TreeMap;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.NoSuchPaddingException;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiCrypto;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiTLV;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiUtil;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiCoordinatorSupplier.HuaweiDeviceType;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiTLV.TLV;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
+
+// TODO: complete responses
+
+public class DeviceConfig {
+    public static final byte id = 0x01;
+
+    public static class LinkParams {
+        public static final byte id = 0x01;
+
+        public static class Request extends HuaweiPacket {
+            public Request(ParamsProvider paramsProvider, HuaweiDeviceType deviceType) {
+                super(paramsProvider);
+                this.serviceId = DeviceConfig.id;
+                this.commandId = id;
+                this.tlv = new HuaweiTLV()
+                        .put(0x01)
+                        .put(0x02)
+                        .put(0x03)
+                        .put(0x04);
+                if (deviceType == HuaweiDeviceType.AW) {
+                    this.tlv.put(0x06);
+                }
+                this.complete = true;
+                this.isEncrypted = false;
+                this.isSliced = false;
+            }
+        }
+
+        public static class Response extends HuaweiPacket {
+            public byte protocolVersion = 0x00;
+            public short mtu = 0x0014;
+            public short sliceSize = 0x00f4;
+            public byte authVersion = 0x00;
+            public byte[] serverNonce = new byte[16];
+            public byte deviceSupportType = 0x00;
+            public byte authAlgo = 0x00;
+            public byte bondState = 0x00;
+            public short interval = 0x0;
+            public byte encryptMethod = 0x00;
+
+            public Response(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+                this.serviceId = DeviceConfig.id;
+                this.commandId = id;
+                this.isEncrypted = false;
+            }
+
+            @Override
+            public void parseTlv() throws ParseException {
+                if (this.tlv.contains(0x01))
+                    this.protocolVersion = this.tlv.getByte(0x01);
+
+                if (this.tlv.contains(0x02))
+                    this.sliceSize = this.tlv.getShort(0x02);
+
+                if (this.tlv.contains(0x03))
+                    this.mtu = this.tlv.getShort(0x03);
+
+                if (this.tlv.contains(0x04))
+                    this.interval = this.tlv.getShort(0x04);
+
+                System.arraycopy(this.tlv.getBytes(0x05), 2, this.serverNonce, 0, 16);
+                this.authVersion = (byte)this.tlv.getBytes(0x05)[1];
+
+                if (this.tlv.contains(0x07))
+                    this.deviceSupportType = this.tlv.getByte(0x07);
+
+                if (this.tlv.contains(0x08))
+                    this.authAlgo = this.tlv.getByte(0x08);
+
+                if (this.tlv.contains(0x09))
+                    this.bondState = this.tlv.getByte(0x09);
+
+                if (this.tlv.contains(0x0C))
+                    this.encryptMethod = this.tlv.getByte(0x0C);
+            }
+        }
+    }
+
+    public static class SupportedServices {
+        public static final byte id = 0x02;
+
+	    // notDeviceCapabilities = 0x1C, 0x1E, 0x1F, 0x28, 0x29, 0x2C, 0x2F, 0x31
+        // but services = 0x1E, 0x28, 0x2C, 0x31
+        // service 0x21 depends on MiddleWear support
+        public static final byte[] knownSupportedServices = new byte[] {
+            0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A,
+            0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14,
+            0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1D, 0x20,
+            0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x2A, 0x2B, 0x2D, 0x2E,
+            0x30, 0x32, 0x33, 0x34, 0x35
+        };
+
+        public static class Request extends HuaweiPacket {
+            public Request(ParamsProvider paramsProvider, byte[] allSupportedServices) {
+                super(paramsProvider);
+                this.serviceId = DeviceConfig.id;
+                this.commandId = id;
+                this.tlv = new HuaweiTLV()
+                        .put(0x01, allSupportedServices);
+                this.complete = true;
+            }
+        }
+
+        public static class Response extends HuaweiPacket {
+            public byte[] supportedServices;
+
+            public Response(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+            }
+
+            @Override
+            public void parseTlv() throws ParseException {
+                this.supportedServices = this.tlv.getBytes(0x02);
+            }
+        }
+
+        public static class OutgoingRequest extends HuaweiPacket {
+            public byte[] allSupportedServices = null;
+
+            public OutgoingRequest(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+                this.complete = false;
+            }
+
+            @Override
+            public List<byte[]> serialize() throws CryptoException {
+                return null;
+            }
+
+            @Override
+            public void parseTlv() throws ParseException {
+                this.allSupportedServices = this.tlv.getBytes(0x01);
+            }
+        }
+    }
+
+    public static class SupportedCommands {
+        public static final byte id = 0x03;
+
+        public static final TreeMap<Integer, byte[]> commandsPerService = new TreeMap<Integer, byte[]>() {{
+            put(0x01, new byte[] {0x04, 0x07, 0x08, 0x09, 0x0A, 0x0D, 0x0E, 0x10, 0x11, 0x12, 0x13, 0x14, 0x1B, 0x1A, 0x1D, 0x21, 0x22, 0x23, 0x24, 0x29, 0x2A, 0x2B, 0x32, 0x2E, 0x31, 0x30, 0x35, 0x36, 0x37, 0x2F});
+            put(0x02, new byte[] {0x01, 0x04, 0x05, 0x06, 0x07, 0x08});
+            put(0x03, new byte[] {0x01, 0x03, 0x04});
+            put(0x04, new byte[] {0x01});
+            put(0x05, new byte[] {0x01});
+            put(0x06, new byte[] {0x01});
+            put(0x07, new byte[] {0x01, 0x03, 0x05, 0x07, 0x08, 0x09, 0x0A, 0x0E, 0x10, 0x13, 0x16, 0x15, 0x17, 0x18, 0x1B, 0x1C, 0x1D, 0x1E, 0x21, 0x22, 0x23, 0x24, 0x25, 0x28, 0x29, 0x06, 0x1F});
+            put(0x08, new byte[] {0x01, 0x02, 0x03});
+            put(0x09, new byte[] {0x01, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F});
+            put(0x0A, new byte[] {0x01, 0x09, 0x0A});
+            put(0x0B, new byte[] {0x01, 0x03});
+            put(0x0C, new byte[] {0x01});
+            put(0x0D, new byte[] {0x01});
+            put(0x0F, new byte[] {0x01, 0x03, 0x05, 0x06, 0x07, 0x08, 0x0A, 0x0B, 0x0C});
+            put(0x10, new byte[] {0x01});
+            put(0x11, new byte[] {0x01});
+            put(0x12, new byte[] {0x01});
+            put(0x13, new byte[] {0x01});
+            put(0x14, new byte[] {0x01});
+            put(0x15, new byte[] {0x01});
+            put(0x16, new byte[] {0x01, 0x03, 0x07});
+            put(0x17, new byte[] {0x01, 0x04, 0x06, 0x07, 0x0B, 0x0C, 0x10, 0x12, 0x15, 0x17});
+            put(0x18, new byte[] {0x01, 0x02, 0x04, 0x05, 0x06, 0x09});
+            put(0x19, new byte[] {0x01, 0x04});
+            put(0x1A, new byte[] {0x01, 0x03, 0x07, 0x05, 0x06});
+            put(0x1B, new byte[] {0x01, 0x0F, 0x19, 0x1A});
+            // No 0x1C
+            put(0x1D, new byte[] {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A});
+            // No 0x1E
+            // No 0x1F
+            put(0x20, new byte[] {0x01, 0x02, 0x03, 0x04, 0x09, 0x0A});
+            //put(0x21, new byte[] {0x01});
+            put(0x22, new byte[] {0x01});
+            put(0x23, new byte[] {0x02, 0x0B});
+            put(0x24, new byte[] {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C});
+            put(0x25, new byte[] {0x02, 0x04, 0x0E});
+            put(0x26, new byte[] {0x02, 0x03});
+            put(0x27, new byte[] {0x01, 0x0E});
+            // No 0x28
+            // No 0x29
+            put(0x2A, new byte[] {0x01, 0x06});
+            put(0x2B, new byte[] {0x12});
+            // No 0x2C
+            put(0x2D, new byte[] {0x01});
+            put(0x2E, new byte[] {0x01, 0x02, 0x03});
+            // No 0x2F
+            put(0x30, new byte[] {0x01});
+            // No 0x31
+            put(0x32, new byte[] {0x01});
+            put(0x33, new byte[] {0x01, 0x2});
+            put(0x34, new byte[] {0x01});
+            put(0x35, new byte[] {0x03, 0x04});
+        }};
+
+
+        public static class Request extends HuaweiPacket {
+            private int maxSize;
+
+            public Request(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+                this.serviceId = DeviceConfig.id;
+                this.commandId = id;
+                this.tlv = new HuaweiTLV();
+
+                // slice size is the max size
+                // But there is a 4 byte header,
+                // a two byte body header,
+                // a two byte footer,
+                // three bytes get added for the 0x81 tag,
+                // and 21 for the encryption stuff
+                this.maxSize = this.paramsProvider.getSliceSize() - 32;
+                // Now it gets aligned to 16 byte blocks
+                // And it gets a header and two length bytes
+                // For CBC there is also at least one byte padding
+                this.maxSize = this.maxSize - (this.maxSize % 16) - 4;
+            }
+
+            public boolean addCommandsForService(byte service, byte[] commands) {
+                // tlv length is what we have already
+                // commands is what we'll add
+                // together with a 3-byte 0x02 tag and two bytes for length
+                if (this.tlv.length() + commands.length + 5 > this.maxSize) { //this.paramsProvider.mtu - 5 - 2 - 2 - 2 - 21) {
+                    return false;
+                }
+                this.tlv.put(0x02, service).put(0x03, commands);
+                return true;
+            }
+
+            @Override
+            public List<byte[]> serialize() throws CryptoException {
+                this.tlv = new HuaweiTLV()
+                        .put(0x81, this.tlv);
+                this.complete = true;
+                return super.serialize();
+            }
+        }
+
+        public static class Response extends HuaweiPacket {
+            public static class CommandsList {
+                public int service;
+                public byte[] commands;
+
+                @Override
+                public String toString() {
+                    StringBuilder sb = new StringBuilder();
+                    sb.append("CommandsList{service=");
+                    sb.append(Integer.toHexString(service));
+                    sb.append(", commands=[");
+                    for (byte b : commands) {
+                        sb.append(Integer.toHexString(b));
+                        if (b != commands[commands.length - 1]) // Elements should be unique
+                            sb.append(", ");
+                    }
+                    sb.append("]}");
+                    return sb.toString();
+                }
+            }
+
+            public List<CommandsList> commandsLists;
+
+            public Response(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+                this.serviceId = DeviceConfig.id;
+                this.commandId = id;
+                this.isEncrypted = false;
+            }
+
+            @Override
+            public void parseTlv() throws ParseException {
+                this.commandsLists = new ArrayList<>();
+                CommandsList commandsList = null;
+                HuaweiTLV containerTLV = this.tlv.getObject(0x81);
+
+                for (HuaweiTLV.TLV tlv : containerTLV.get()) {
+                    if ((int) tlv.getTag() == 0x02) {
+                        commandsList = new CommandsList();
+                        commandsList.service = ByteBuffer.wrap(tlv.getValue()).get();
+                    } else if ((int) tlv.getTag() == 0x04) {
+                        if (commandsList == null)
+                            throw new SupportedCommandsListException("Commandslist is not yet set");
+                        ByteBuffer buffer = ByteBuffer.allocate(tlv.getValue().length);
+                        for (int i = 0; i < tlv.getValue().length; i++) {
+                            if ((int) tlv.getValue()[i] == 1)
+                                buffer.put((byte) (commandsPerService.get(commandsList.service)[i]));
+                        }
+                        commandsList.commands = new byte[buffer.position()];
+                        ((ByteBuffer) buffer.rewind()).get(commandsList.commands);
+                        this.commandsLists.add(commandsList);
+                    } else
+                        throw new SupportedCommandsListException("Unknown tag encountered");
+                }
+                if (this.commandsLists.isEmpty())
+                    throw new SupportedCommandsListException("CommandLists is empty");
+            }
+        }
+
+        // TODO: LogRequest
+    }
+
+    public static class DateFormat {
+        public static final byte id = 0x04;
+
+        public static class Request extends HuaweiPacket {
+            public Request(
+                    ParamsProvider paramsProvider,
+                    byte dateFormat,
+                    byte timeFormat
+            ) {
+                super(paramsProvider);
+                this.serviceId = DeviceConfig.id;
+                this.commandId = id;
+                this.tlv = new HuaweiTLV()
+                        .put(0x81, new HuaweiTLV()
+                                .put(0x02, dateFormat)
+                                .put(0x03, timeFormat)
+                        );
+                this.complete = true;
+            }
+        }
+
+        public static class OutgoingRequest extends HuaweiPacket {
+            public byte dateFormat;
+            public byte timeFormat;
+
+            public OutgoingRequest(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+                this.complete = false;
+            }
+
+            @Override
+            public void parseTlv() throws ParseException {
+                if (this.tlv.contains(0x02))
+                    this.dateFormat = this.tlv.getByte(0x02);
+
+                if (this.tlv.contains(0x03))
+                    this.timeFormat = this.tlv.getByte(0x03);
+            }
+        }
+    }
+
+    public static class TimeRequest extends HuaweiPacket {
+        public static final byte id = 0x05;
+
+        public TimeRequest(ParamsProvider paramsProvider, final Calendar now) {
+            super(paramsProvider);
+            this.serviceId = DeviceConfig.id;
+            this.commandId = id;
+            ByteBuffer timeAndZoneId = ByteBuffer.wrap(HuaweiUtil.getTimeAndZoneId(now));
+            this.tlv = new HuaweiTLV()
+                    .put(0x01, timeAndZoneId.getInt(0))
+                    .put(0x02, timeAndZoneId.getShort(4));
+            this.complete = true;
+        }
+
+        public TimeRequest(ParamsProvider paramsProvider) {
+            this(paramsProvider, Calendar.getInstance());
+        }
+
+        public static class Response extends HuaweiPacket {
+            public int deviceTime=0;
+
+            public Response(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+            }
+
+            @Override
+            public void parseTlv() throws ParseException {
+                if (this.tlv.contains(0x01)) {
+                    this.deviceTime = this.tlv.getInteger(0x01);
+                }
+            }
+        }
+
+        // TODO: implement parsing this request for the log parser support
+    }
+
+    public static class ProductInfo {
+        public static final byte id = 0x07;
+
+        public static class Request extends HuaweiPacket {
+
+            public Request(ParamsProvider paramsProvider, HuaweiDeviceType deviceType) {
+                super(paramsProvider);
+                this.serviceId = DeviceConfig.id;
+                this.commandId = id;
+                this.tlv = new HuaweiTLV();
+                if (deviceType == HuaweiDeviceType.AW) {
+                    int[] tags = {0x01, 0x02, 0x03, 0x07, 0x09, 0x0A, 0x0c, 0x11};
+                    for (int i: tags) {
+                        this.tlv.put(i);
+                    }
+                } else {
+                    int[] tags = {0x01, 0x02, 0x07, 0x09, 0x0A, 0x11, 0x12, 0x16, 0x1A, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23};
+                    for (int i : tags) {
+                        this.tlv.put(i);
+                    }
+                } // else if (AW Compatible)
+                //  this.tlv).put(0x01).put(0x02).put(0x07).put(0x09);
+                // }
+                this.complete = true;
+            }
+        }
+
+        public static class Response extends HuaweiPacket {
+            // TODO: extend:
+            //        public static final int BTVersion = 0x01;
+            //        public static final int productType = 0x02;
+            //        public static final int phoneNumber = 0x04;
+            //        public static final int macAddress = 0x05;
+            //        public static final int IMEI = 0x06;
+            //        public static final int openSourceVersion = 0x08;
+            //        public static final int serialNumber = 0x09;
+            //        public static final int eMMCId = 0x0B;
+            //        public static final int healthAppSupport = 0x0D;
+
+            public String hardwareVersion;
+            public String softwareVersion;
+            public String productModel;
+
+            public Response(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+                this.serviceId = DeviceConfig.id;
+                this.commandId = id;
+                this.isEncrypted = false;
+            }
+
+            @Override
+            public void parseTlv() throws ParseException {
+                if (this.tlv.contains(0x03))
+                    this.hardwareVersion = this.tlv.getString(0x03);
+                this.softwareVersion = this.tlv.getString(0x07);
+                this.productModel = this.tlv.getString(0x0A).trim();
+            }
+        }
+
+        // TODO: implement parsing this request for the log parser support
+    }
+
+    public static class Bond {
+        public static final byte id = 0x0E;
+
+        public static class Request extends HuaweiPacket {
+            public Request(
+                    ParamsProvider paramsProvider,
+                    byte[] clientSerial,
+                    byte[] key,
+                    byte[] iv
+            ) {
+                super(paramsProvider);
+                this.serviceId = DeviceConfig.id;
+                this.commandId = id;
+
+                this.tlv = new HuaweiTLV()
+                        .put(0x01)
+                        .put(0x03, (byte) 0x00)
+                        .put(0x05, clientSerial)
+                        .put(0x06, key)
+                        .put(0x07, iv);
+                this.isEncrypted = false;
+                this.complete = true;
+            }
+        }
+
+        public static class OutgoingRequest extends HuaweiPacket {
+            public byte[] clientSerial;
+            public byte[] bondingKey;
+            public byte[] iv;
+
+            public OutgoingRequest(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+                this.complete = false;
+            }
+
+            @Override
+            public void parseTlv() throws ParseException {
+                this.clientSerial = this.tlv.getBytes(0x05);
+                this.bondingKey = this.tlv.getBytes(0x06);
+                this.iv = this.tlv.getBytes(0x07);
+            }
+        }
+    }
+
+    public static class BondParams {
+        public static final byte id = 0x0F;
+
+        public static class Request extends HuaweiPacket {
+            public Request(
+                    ParamsProvider paramsProvider,
+                    byte[] clientSerial,
+                    byte[] mac
+            ) {
+                super(paramsProvider);
+
+                this.serviceId = DeviceConfig.id;
+                this.commandId = id;
+
+                this.tlv = new HuaweiTLV()
+                        .put(0x01)
+                        .put(0x03, clientSerial)
+                        .put(0x04, (byte) 0x02)
+                        .put(0x05)
+                        .put(0x07, mac)
+                        .put(0x09);
+                this.isEncrypted = false;
+                this.complete = true;
+            }
+        }
+
+        public static class Response extends HuaweiPacket {
+            public byte status;
+            public long encryptionCounter;
+
+            public Response(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+
+                this.serviceId = DeviceConfig.id;
+                this.commandId = id;
+
+                this.isEncrypted = false;
+            }
+
+            @Override
+            public void parseTlv() throws ParseException {
+                this.status = this.tlv.getByte(0x01);
+                this.encryptionCounter = this.tlv.getInteger(0x09) & 0xFFFFFFFFL;
+            }
+        }
+        // TODO: implement parsing this request for the log parser support
+    }
+
+    public static class ActivityType {
+        public static final int id = 0x12;
+
+        public static class Request extends HuaweiPacket {
+            public Request(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+
+                this.serviceId = DeviceConfig.id;
+                this.commandId = id;
+
+                this.tlv = new HuaweiTLV()
+                    .put(0x01);
+
+                this.complete = true;
+            }
+        }
+
+        public static class Response extends HuaweiPacket {
+            public Response(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+                this.serviceId = DeviceConfig.id;
+                this.commandId = id;
+            }
+
+            @Override
+            public void parseTlv() throws ParseException {
+            }
+        }
+
+    }
+
+    public static class Auth {
+        public static final byte id = 0x13;
+
+        public static class Request extends HuaweiPacket {
+            public Request(
+                    ParamsProvider paramsProvider,
+                    byte[] challenge,
+                    byte[] nonce
+            ) {
+                super(paramsProvider);
+
+                this.serviceId = DeviceConfig.id;
+                this.commandId = id;
+
+                this.tlv = new HuaweiTLV()
+                        .put(0x01, challenge)
+                        .put(0x02, nonce);
+                if (paramsProvider.getAuthMode() == 0x02)
+                    this.tlv.put(0x03, paramsProvider.getAuthAlgo());
+                this.isEncrypted = false;
+                this.complete = true;
+            }
+
+        }
+
+        public static class OutgoingRequest extends HuaweiPacket {
+            public byte[] challenge;
+            public byte[] nonce;
+
+            public OutgoingRequest(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+                this.complete = false;
+            }
+
+            @Override
+            public void parseTlv() throws ParseException {
+                this.challenge = this.tlv.getBytes(0x01);
+                this.nonce = this.tlv.getBytes(0x02);
+            }
+        }
+
+        public static class Response extends HuaweiPacket {
+            public byte[] challengeResponse;
+
+            public Response(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+
+                this.serviceId = DeviceConfig.id;
+                this.commandId = id;
+
+                this.isEncrypted = false;
+            }
+
+            @Override
+            public void parseTlv() throws ParseException {
+                this.challengeResponse = this.tlv.getBytes(0x01);
+            }
+        }
+        // TODO: implement parsing this request for the log parser support
+    }
+
+    public static class BatteryLevel {
+        public static final byte id = 0x08;
+        public static final byte id_change = 0x27; // Same format, async (receive) only
+
+        public static class Request extends HuaweiPacket {
+            public Request(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+
+                this.serviceId = DeviceConfig.id;
+                this.commandId = id;
+
+                this.tlv = new HuaweiTLV()
+                        .put(0x01);
+                this.complete = true;
+            }
+        }
+
+        public static class Response extends HuaweiPacket {
+            public byte level;
+
+            public byte[] multi_level;
+            public byte[] status; // TODO: enum
+
+            public Response(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+
+                // This differs per watch, so we handle it ourselves in parseTlv
+                this.isEncrypted = false;
+            }
+
+            @Override
+            public void parseTlv() throws ParseException {
+                this.level = this.tlv.getByte(0x01);
+                this.multi_level = this.tlv.getBytes(0x02, null);
+                this.status = this.tlv.getBytes(0x03, null);
+            }
+        }
+        // TODO: implement parsing this request for the log parser support
+    }
+
+    public static class ActivateOnLiftRequest extends HuaweiPacket {
+        public static final byte id = 0x09;
+
+        public ActivateOnLiftRequest(ParamsProvider paramsProvider, boolean activate) {
+            super(paramsProvider);
+
+            this.serviceId = DeviceConfig.id;
+            this.commandId = id;
+
+            this.tlv = new HuaweiTLV()
+                    .put(0x01, activate);
+
+            this.complete = true;
+        }
+        // TODO: implement parsing this request for the log parser support
+    }
+
+    public static class DndDeleteRequest extends HuaweiPacket {
+        public static final int id = 0x0B;
+
+        public DndDeleteRequest(HuaweiPacket.ParamsProvider paramsProvider) {
+            super(paramsProvider);
+
+            this.serviceId = DeviceConfig.id;
+            this.commandId = id;
+
+            this.tlv = new HuaweiTLV()
+                .put(0x81, new HuaweiTLV()
+                    .put(0x02, (byte) 0x01)
+                );
+            this.complete = true;
+        }
+        // TODO: implement parsing this request for the log parser support
+    }
+
+    public static class DndAddRequest extends HuaweiPacket {
+        public static final int id = 0x0C;
+
+        public DndAddRequest(
+            HuaweiPacket.ParamsProvider paramsProvider,
+            boolean dndEnable,
+            byte[] start,
+            byte[] end,
+            int cycle,
+            int dndLiftWristType,
+            boolean allowDndLiftWrist
+        ) {
+            super(paramsProvider);
+
+            this.serviceId = DeviceConfig.id;
+            this.commandId = id;
+
+            HuaweiTLV dndPacket = new HuaweiTLV()
+                    .put(0x02, (byte) 0x01)
+                    .put(0x03, dndEnable)
+                    .put(0x04, (byte) 0x00)
+                    .put(0x05, start)
+                    .put(0x06, end)
+                    .put(0x07, (byte) cycle);
+
+            if (allowDndLiftWrist) {
+                dndPacket.put(0x08, (short) (dndLiftWristType));
+            }
+            this.tlv = new HuaweiTLV()
+                    .put(0x81, dndPacket);
+            this.complete = true;
+        }
+        // TODO: implement parsing this request for the log parser support
+    }
+
+    public static class FactoryResetRequest extends HuaweiPacket {
+        public static final byte id = 0x0D;
+
+        public FactoryResetRequest(HuaweiPacket.ParamsProvider paramsProvider) {
+            super(paramsProvider);
+
+            this.serviceId = DeviceConfig.id;
+            this.commandId = id;
+
+            this.tlv = new HuaweiTLV()
+                    .put(0x01, (byte) 0x01);
+
+            this.complete = true;
+        }
+        // TODO: implement parsing this request for the log parser support
+    }
+
+    public static class PhoneInfo {
+        public static final byte id = 0x10;
+
+        public static class Request extends HuaweiPacket {
+            public Request(HuaweiPacket.ParamsProvider paramsProvider, byte[] phoneInfo) {
+                super(paramsProvider);
+
+                this.serviceId = DeviceConfig.id;
+                this.commandId = id;
+
+                this.tlv = new HuaweiTLV();
+                for (byte b : phoneInfo) {
+                    switch (b) {
+                        case 0x0:
+                            break;
+                        case 0xf:
+                            break;
+                        case 0x11:
+                            this.tlv.put(b, 1400106310); // Force AppVersion to 14.1.6.310
+                            break;
+                        case 0x15:
+                            this.tlv.put(b); // Force buildOSPlatformVersion to ""
+                            break;
+                        case 0x10: // Force EmuiBuildVersion to 0x00
+                        case 0x13: // Force buildOsEnable to 0x00
+                        case 0x14: // Force buildOSApiVersion to 0x00
+                        default:
+                            this.tlv.put(b, (byte)00);
+                    }
+                }
+                this.complete = true;
+            }
+        }
+
+        public static class Response extends HuaweiPacket {
+            public boolean isAck = false;
+            public byte[] info;
+
+            public Response(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+
+                this.serviceId = DeviceConfig.id;
+                this.commandId = id;
+            }
+
+            @Override
+            public void parseTlv() throws MissingTagException {
+                if (this.tlv.get().size() == 1 && this.tlv.contains(0x7f) && this.tlv.getInteger(0x7f) == 0x186A0) {
+                    this.isAck = true;
+                    return;
+                }
+
+                info = new byte[this.tlv.length()];
+                int i = 0;
+                for (TLV tlv : this.tlv.get()) {
+                    info[i] = tlv.getTag();
+                    i += 1;
+                }
+            }
+        }
+
+        // TODO: implement parsing this request for the log parser support
+    }
+
+    public static class DeviceStatus {
+        public static final byte id = 0x16;
+
+        public static class Request extends HuaweiPacket {
+            public Request(HuaweiPacket.ParamsProvider paramsProvider, boolean askStatus) { // status or notify
+                super(paramsProvider);
+
+                this.serviceId = DeviceConfig.id;
+                this.commandId = id;
+
+                this.tlv = new HuaweiTLV();
+                if (askStatus) {
+                    this.tlv.put(0x01);
+                } else {
+                    this.tlv.put(0x02, (byte)0x00);
+                }
+                this.isEncrypted = false;
+                this.complete = true;
+            }
+        }
+
+        public static class Response extends HuaweiPacket {
+            public byte status = -1;
+
+            public Response(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+
+                this.serviceId = DeviceConfig.id;
+                this.commandId = id;
+
+                this.isEncrypted = false;
+            }
+
+            @Override
+            public void parseTlv() throws ParseException {
+                // AW70 doesn't seem to have this
+                if (this.tlv.contains(0x01))
+                    this.status = this.tlv.getByte(0x01);
+            }
+        }
+
+        // TODO: implement parsing this request for the log parser support
+    }
+
+    public static class NavigateOnRotateRequest extends HuaweiPacket {
+        public static final byte id = 0x1B;
+
+        public NavigateOnRotateRequest(HuaweiPacket.ParamsProvider paramsProvider, boolean navigate) {
+            super(paramsProvider);
+
+            this.serviceId = DeviceConfig.id;
+            this.commandId = id;
+
+            this.tlv = new HuaweiTLV()
+                    .put(0x01, navigate);
+
+            this.complete = true;
+        }
+        // TODO: implement parsing this request for the log parser support
+    }
+
+    public static class WearLocationRequest extends HuaweiPacket {
+        public static final byte id = 0x1A;
+
+        public WearLocationRequest(HuaweiPacket.ParamsProvider paramsProvider, byte location) {
+            super(paramsProvider);
+
+            this.serviceId = DeviceConfig.id;
+            this.commandId = id;
+
+            this.tlv = new HuaweiTLV()
+                    .put(0x01, location);
+
+            this.complete = true;
+        }
+        // TODO: implement parsing this request for the log parser support
+    }
+
+    public static class DndLiftWristType {
+        public static final int id = 0x1D;
+
+        public static class Request extends HuaweiPacket {
+            public Request(HuaweiPacket.ParamsProvider paramsProvider) {
+                super(paramsProvider);
+
+                this.serviceId = DeviceConfig.id;
+                this.commandId = id;
+
+                this.tlv = new HuaweiTLV()
+                    .put(0x01);
+                this.complete = true;
+            }
+        }
+
+        public static class Response extends HuaweiPacket {
+            public int dndLiftWristType;
+
+            public Response(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+            }
+
+            @Override
+            public void parseTlv() throws ParseException {
+                this.dndLiftWristType = (int) this.tlv.getShort(0x01);
+            }
+        }
+        // TODO: implement parsing this request for the log parser support
+    }
+
+    // TODO: set (earphone) double tap action 0x1f
+    // TODO: get (earphone) double tap action 0x20
+
+    public static class HiChain {
+        public static final int id = 0x28;
+
+        public static class Request {
+            private final int operationCode;
+            private final long requestId;
+            private final byte[] selfAuthId;
+            private final String groupId;
+            private JSONObject version = null;
+            private JSONObject jsonPayload = null;
+            private JSONObject value = null;
+
+            public Request (int operationCode, long requestId, byte[] selfAuthId, String groupId) {
+                this.operationCode = operationCode;
+                this.requestId = requestId;
+                this.selfAuthId = selfAuthId;
+                this.groupId = groupId;
+            }
+
+            public class BaseStep extends HuaweiPacket {
+                public BaseStep (HuaweiPacket.ParamsProvider paramsProvider, int messageId) throws SerializeException {
+                    super(paramsProvider);
+                    this.serviceId = DeviceConfig.id;
+                    this.commandId = HiChain.id;
+                    this.isSliced = true;
+                    this.isEncrypted = false;
+                    this.complete = true;
+                    version = new JSONObject();
+                    jsonPayload = new JSONObject();
+                    value = new JSONObject();
+                    createJson(messageId);
+                }
+            }
+
+            public class StepOne extends BaseStep {
+
+                public StepOne (
+                    HuaweiPacket.ParamsProvider paramsProvider,
+                    int messageId,
+                    byte[] isoSalt,
+                    byte[] seed
+                ) throws SerializeException {
+                    super(paramsProvider, messageId);
+                    // createJson(1); //messageId);
+                    try {
+                        jsonPayload
+                            .put("isoSalt", StringUtils.bytesToHex(isoSalt))
+                            .put("peerAuthId",  StringUtils.bytesToHex(selfAuthId))
+                            .put("operationCode", operationCode)
+                            .put("seed", StringUtils.bytesToHex(seed))
+                            .put("peerUserType", 0x00);
+                        if (operationCode == 0x02) {
+                            jsonPayload
+                                .put("pkgName", "com.huawei.devicegroupmanage")
+                                .put("serviceType", groupId)
+                                .put("keyLength", 0x20);
+                            value.put("isDeviceLevel", false);
+                        }
+                        this.tlv = new HuaweiTLV()
+                            .put(0x01, value.toString())
+                            .put(0x02, (byte)operationCode)
+                            .put(0x03, ByteBuffer.allocate(8).putLong(requestId).array());
+                            //.put(0x04, 0x00)
+                            //.put(0x05, 0x00);
+                    } catch (JSONException e) {
+                        throw new SerializeException("HiChain Step1 JSON exception", e);
+                    }
+                }
+            }
+
+            public class StepTwo extends BaseStep {
+                public StepTwo (
+                    HuaweiPacket.ParamsProvider paramsProvider,
+                    int messageId,
+                    byte[] token
+                ) throws SerializeException {
+                    super(paramsProvider, messageId);
+                    // createJson(2); //messageId);
+                    try {
+                        jsonPayload
+                            .put("peerAuthId", StringUtils.bytesToHex(selfAuthId))
+                            .put("token", StringUtils.bytesToHex(token));
+                        if (operationCode == 0x02) value.put("isDeviceLevel", false);
+                        this.tlv = new HuaweiTLV()
+                            .put(0x01, value.toString())
+                            .put(0x02, (byte)operationCode)
+                            .put(0x03, ByteBuffer.allocate(8).putLong(requestId).array());
+                    } catch (JSONException e) {
+                        throw new SerializeException("HiChain Step 2 JSON exception", e);
+                    }
+                }
+            }
+
+            public class StepThree extends BaseStep {
+                public StepThree (
+                    HuaweiPacket.ParamsProvider paramsProvider,
+                    int messageId,
+                    byte[] nonce,
+                    byte[] encData
+                ) throws SerializeException {
+                    super(paramsProvider, messageId);
+                    // createJson(3);
+                    try {
+                        jsonPayload
+                            .put("nonce", StringUtils.bytesToHex(nonce))
+                            .put("encData", StringUtils.bytesToHex(encData));
+                        this.tlv = new HuaweiTLV()
+                            .put(0x01, value.toString())
+                            .put(0x02, (byte)operationCode)
+                            .put(0x03, ByteBuffer.allocate(8).putLong(requestId).array());
+                    } catch (JSONException e) {
+                        throw new SerializeException("HiChain Step 3 JSON exception", e);
+                    }
+                }
+            }
+
+            public class StepFour extends BaseStep {
+                public StepFour (
+                    HuaweiPacket.ParamsProvider paramsProvider,
+                    int messageId,
+                    byte[] nonce,
+                    byte[] encResult
+                ) throws SerializeException {
+                    super(paramsProvider, messageId);
+                    // if (operationCode == 0x01) {
+                    //     createJson(4); //messageId);
+                    // } else {
+                    //     createJson(3);
+                    // }
+                    try {
+                        jsonPayload
+                            .put("nonce", StringUtils.bytesToHex(nonce)) //generateRandom
+                            .put("encResult", StringUtils.bytesToHex(encResult))
+                            .put("operationCode", operationCode);
+                        this.tlv = new HuaweiTLV()
+                            .put(0x01, value.toString())
+                            .put(0x02, (byte)operationCode)
+                            .put(0x03, ByteBuffer.allocate(8).putLong(requestId).array());
+                    } catch (JSONException e) {
+                        throw new SerializeException("HiChain Step 4 JSON exception", e);
+                    }
+                }
+            }
+
+            private void createJson(int messageId) throws HuaweiPacket.SerializeException {
+                if (operationCode == 0x02) {
+                    messageId |= 0x10;
+                }
+                try {
+                    version
+                        .put("minVersion", "1.0.0")
+                        .put("currentVersion", "2.0.16");
+                    jsonPayload
+                        .put("version", version);
+                    value
+                        .put("authForm", 0x00)
+                        .put("payload", jsonPayload)
+                        .put("groupAndModuleVersion", "2.0.1")
+                        .put("message", messageId);
+                    if (operationCode == 0x01) {
+                        value
+                            .put("requestId", Long.toString(requestId))
+                            .put("groupId", groupId)
+                            .put("groupName", "health_group_name")
+                            .put("groupOp", 2)
+                            .put("groupType", 256)
+                            .put("peerDeviceId", new String(selfAuthId, StandardCharsets.UTF_8)) 
+                            .put("connDeviceId", new String(selfAuthId, StandardCharsets.UTF_8))
+                            .put("appId", "com.huawei.health")
+                            .put("ownerName", "");
+                    }
+                } catch (JSONException e) {
+                    throw new HuaweiPacket.SerializeException("Create json Exception", e);
+                }
+            }
+        }
+
+        public static class Response extends HuaweiPacket {
+            // TODO: get rid of GB import...
+            // TODO: add operation code
+
+            public static class Step1Data {
+                public byte[] isoSalt;
+                public byte[] peerAuthId;
+                public int peerUserType;
+                public byte[] token;
+
+                public Step1Data(JSONObject payload) throws JSONException {
+                    this.isoSalt = GB.hexStringToByteArray(payload.getString("isoSalt"));
+                    this.peerAuthId = GB.hexStringToByteArray(payload.getString("peerAuthId"));
+                    this.peerUserType = payload.getInt("peerUserType");
+                    this.token = GB.hexStringToByteArray(payload.getString("token"));
+                }
+
+                @Override
+                public String toString() {
+                    return "Step1Data{" +
+                            "isoSalt=" + StringUtils.bytesToHex(isoSalt) +
+                            ", peerAuthId=" + StringUtils.bytesToHex(peerAuthId) +
+                            ", peerUserType=" + peerUserType +
+                            ", token=" + StringUtils.bytesToHex(token) +
+                            '}';
+                }
+            }
+
+            public static class Step2Data {
+                public byte[] returnCodeMac;
+
+                public Step2Data(JSONObject payload) throws JSONException {
+                    this.returnCodeMac = GB.hexStringToByteArray(payload.getString("returnCodeMac"));
+                }
+
+                @Override
+                public String toString() {
+                    return "Step2Data{" +
+                            "returnCodeMac=" + StringUtils.bytesToHex(returnCodeMac) +
+                            '}';
+                }
+            }
+
+            public static class Step3Data {
+                public byte[] nonce;
+                public byte[] encAuthToken;
+
+                public Step3Data(JSONObject payload) throws JSONException {
+                    this.nonce = GB.hexStringToByteArray(payload.getString("nonce"));
+                    this.encAuthToken = GB.hexStringToByteArray(payload.getString("encAuthToken"));
+                }
+
+                @Override
+                public String toString() {
+                    return "Step3Data{" +
+                            "nonce=" + StringUtils.bytesToHex(nonce) +
+                            ", encAuthToken=" + StringUtils.bytesToHex(encAuthToken) +
+                            '}';
+                }
+            }
+
+            public static class Step4Data {
+                public String data;
+
+                public Step4Data(HuaweiTLV tlv) throws ParseException {
+                    if (tlv.contains(0x01))
+                        this.data = tlv.getString(0x01);
+                }
+
+                @Override
+                public String toString() {
+                    return "Step4Data{" +
+                            "data='" + data + '\'' +
+                            '}';
+                }
+            }
+
+            // TODO: enum?
+            // 0 is json?
+            // 2 is raw string?
+            public byte type;
+
+            public JSONObject value;
+            public JSONObject jsonPayload;
+
+            public byte step;
+            // public int operationCode; // TODO
+
+            public Step1Data step1Data;
+            public Step2Data step2Data;
+            public Step3Data step3Data;
+            public Step4Data step4Data;
+            public int errorCode = 0;
+
+            public Response(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+                this.serviceId = DeviceConfig.id;
+                this.commandId = HiChain.id;
+                this.isEncrypted = false;
+            }
+
+            @Override
+            public void parseTlv() throws ParseException {
+                this.type = this.tlv.getByte(0x04);
+
+                if (this.type == 0x00) {
+                    try {
+                        this.value = new JSONObject(this.tlv.getString(0x01));
+                        this.jsonPayload = value.getJSONObject("payload");
+
+                        // Ugly, but should work
+                        if (jsonPayload.has("isoSalt")) {
+                            this.step = 0x01;
+                            this.step1Data = new Step1Data(jsonPayload);
+                        } else if (jsonPayload.has("returnCodeMac")) {
+                            this.step = 0x02;
+                            this.step2Data = new Step2Data(jsonPayload);
+                        } else if (jsonPayload.has("encAuthToken")) {
+                            this.step = 0x03;
+                            this.step3Data = new Step3Data(jsonPayload);
+                        }
+                        if (jsonPayload.has("errorCode")) {
+                            this.errorCode = jsonPayload.getInt("errorCode");
+                        }
+                    } catch (JSONException e) {
+                        throw new JsonException("", e);
+                    }
+                } else {
+                    this.step = 0x04;
+                    this.step4Data = new Step4Data(this.tlv);
+                }
+            }
+        }
+
+        public static class OutgoingRequest extends HuaweiPacket {
+            public static class Step1Data {
+                public byte[] isoSalt;
+                public byte[] peerAuthId;
+                public byte[] seed;
+                public int peerUserType;
+
+                String serviceType; // Optional
+
+                public Step1Data(JSONObject payload) throws JSONException {
+                    this.isoSalt = GB.hexStringToByteArray(payload.getString("isoSalt"));
+                    this.peerAuthId = GB.hexStringToByteArray(payload.getString("peerAuthId"));
+                    this.seed = GB.hexStringToByteArray(payload.getString("seed"));
+                    this.peerUserType = payload.getInt("peerUserType");
+
+                    if (payload.has("serviceType"))
+                        this.serviceType = payload.getString("serviceType");
+                }
+
+                @Override
+                public String toString() {
+                    return "Step1Data{" +
+                            "isoSalt=" + StringUtils.bytesToHex(isoSalt) +
+                            ", peerAuthId=" + StringUtils.bytesToHex(peerAuthId) +
+                            ", seed=" + StringUtils.bytesToHex(seed) +
+                            ", peerUserType=" + peerUserType +
+                            ", serviceType='" + serviceType + '\'' +
+                            '}';
+                }
+            }
+
+            public static class Step2Data {
+                public byte[] peerAuthId;
+                public byte[] token;
+
+                public boolean isDeviceLevel = false; // Optional
+
+                public Step2Data(JSONObject payload) throws JSONException {
+                    this.peerAuthId = GB.hexStringToByteArray(payload.getString("peerAuthId"));
+                    this.token = GB.hexStringToByteArray(payload.getString("token"));
+
+                    if (payload.has("isDeviceLevel"))
+                        this.isDeviceLevel = payload.getBoolean("isDeviceLevel");
+                }
+
+                @Override
+                public String toString() {
+                    return "Step2Data{" +
+                            "peerAuthId=" + StringUtils.bytesToHex(peerAuthId) +
+                            ", token=" + StringUtils.bytesToHex(token) +
+                            ", isDeviceLevel=" + isDeviceLevel +
+                            '}';
+                }
+            }
+
+            public static class Step3Data {
+                public byte[] nonce;
+                public byte[] encData;
+
+                public Step3Data(JSONObject payload) throws JSONException {
+                    this.nonce = GB.hexStringToByteArray(payload.getString("nonce"));
+                    this.encData = GB.hexStringToByteArray(payload.getString("encData"));
+                }
+
+                @Override
+                public String toString() {
+                    return "Step3Data{" +
+                            "nonce=" + StringUtils.bytesToHex(nonce) +
+                            ", encData=" + StringUtils.bytesToHex(encData) +
+                            '}';
+                }
+            }
+
+            public static class Step4Data {
+                public byte[] nonce;
+                public byte[] encResult;
+
+                public Step4Data(JSONObject payload) throws JSONException {
+                    this.nonce = GB.hexStringToByteArray(payload.getString("nonce"));
+                    this.encResult = GB.hexStringToByteArray(payload.getString("encResult"));
+                }
+
+                @Override
+                public String toString() {
+                    return "Step4Data{" +
+                            "nonce=" + StringUtils.bytesToHex(nonce) +
+                            ", encResult=" + StringUtils.bytesToHex(encResult) +
+                            '}';
+                }
+            }
+
+            public int step;
+
+            public long requestId;
+            public byte[] selfAuthId;
+            public String groupId;
+            public JSONObject jsonPayload = null;
+            public JSONObject value = null;
+
+            public Step1Data step1Data;
+            public Step2Data step2Data;
+            public Step3Data step3Data;
+            public Step4Data step4Data;
+
+            public OutgoingRequest(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+                this.complete = false;
+            }
+
+            @Override
+            public void parseTlv() throws ParseException {
+                try {
+                    value = new JSONObject(this.tlv.getString(0x01));
+                    jsonPayload = value.getJSONObject("payload");
+
+                    if (jsonPayload.has("isoSalt")) {
+                        this.step = 1;
+                        this.step1Data = new Step1Data(jsonPayload);
+                    } else if (jsonPayload.has("token")) {
+                        this.step = 2;
+                        this.step2Data = new Step2Data(jsonPayload);
+                    } else if (jsonPayload.has("encData")) {
+                        this.step = 3;
+                        this.step3Data = new Step3Data(jsonPayload);
+                    } else if (jsonPayload.has("encResult")) {
+                        this.step = 4;
+                        this.step4Data = new Step4Data(jsonPayload);
+                    }
+                } catch (JSONException e) {
+                    throw new JsonException("Cannot parse JSON", e);
+                }
+
+                if (this.tlv.contains(0x03))
+                    this.requestId = ByteBuffer.wrap(this.tlv.getBytes(0x03)).getLong();
+            }
+        }
+    }
+
+    public static class PinCode {
+        public static final int id = 0x2C;
+
+        public static class Request extends HuaweiPacket {
+
+            public Request(HuaweiPacket.ParamsProvider paramsProvider) {
+                super(paramsProvider);
+
+                this.serviceId = DeviceConfig.id;
+                this.commandId = id;
+
+                this.tlv = new HuaweiTLV()
+                    .put(0x01);
+                this.complete = true;
+                this.isEncrypted = false;
+            }
+        }
+
+        public static class Response extends HuaweiPacket {
+            public byte[] pinCode;
+
+            public Response(HuaweiPacket.ParamsProvider paramsProvider) {
+                super(paramsProvider);
+                this.serviceId = DeviceConfig.id;
+                this.commandId = id;
+                this.isEncrypted = false;
+            }
+
+            @Override
+            public void parseTlv() throws ParseException {
+                byte[] message = this.tlv.getBytes(0x01);
+                byte[] iv = this.tlv.getBytes(0x02);
+
+                HuaweiCrypto huaweiCrypto = new HuaweiCrypto(paramsProvider.getAuthVersion(), paramsProvider.getAuthAlgo(), paramsProvider.getDeviceSupportType(), paramsProvider.getAuthMode());
+                try {
+                    pinCode = huaweiCrypto.decryptPinCode(paramsProvider.getEncryptMethod(), message, iv);
+                } catch (HuaweiCrypto.CryptoException e) {
+                    throw new CryptoException("Could not decrypt pinCode", e);
+                }
+            }
+        }
+        // TODO: implement parsing this request for the log parser support
+    }
+
+    public static class AcceptAgreement {
+        public static final int id = 0x30;
+
+        public static class Request extends HuaweiPacket {
+            public Request(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+
+                this.serviceId = DeviceConfig.id;
+                this.commandId = id;
+
+                int timestamp = (int) (System.currentTimeMillis() / 1000);
+
+                HuaweiTLV software = new HuaweiTLV()
+                                .put(0x03, "software_update_service_statement")
+                                .put(0x04, 0x01)
+                                .put(0x05, "20230508-20230508-0-0")
+                                .put(0x06, timestamp);
+                HuaweiTLV device_information = new HuaweiTLV()
+                                .put(0x03, "device_information_management")
+                                .put(0x04,0x01)
+                                .put(0x05, "20230508-20230508-0-0")
+                                .put(0x06,timestamp);
+
+                HuaweiTLV user_license = new HuaweiTLV()
+                                .put(0x03, "user_license_agreement")
+                                .put(0x04,0x01)
+                                .put(0x05, "20230508-20230508-0-0")
+                                .put(0x06,timestamp);
+                HuaweiTLV tlvList = new HuaweiTLV()
+                        .put(0x82, software)
+                        .put(0x82,device_information)
+                        .put(0x82,user_license);
+                this.tlv = new HuaweiTLV()
+                        .put(0x81, tlvList);
+            }
+        }
+        
+    }
+
+
+    public static class SettingRelated {
+        public static final int id = 0x31;
+
+        public static class Request extends HuaweiPacket {
+            public Request(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+
+                this.serviceId = DeviceConfig.id;
+                this.commandId = id;
+
+                this.tlv = new HuaweiTLV()
+                    .put(0x01)
+                    .put(0x02)
+                    .put(0x03)
+                    .put(0x04)
+                    .put(0x05)
+                    .put(0x06);
+
+                this.complete = true;
+            }
+        }
+
+        public static class Response extends HuaweiPacket {
+            public boolean truSleepNewSync = false;
+            public boolean gpsNewSync = false;
+
+            public Response(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+                this.serviceId = DeviceConfig.id;
+                this.commandId = id;
+            }
+
+            @Override
+            public void parseTlv() throws ParseException {
+                // Works with bitmaps
+
+                // Tag 1 -> LegalStuff
+
+                if (this.tlv.contains(0x02)) {
+                    // Tag 2 -> File support
+                    byte value = this.tlv.getByte(0x02);
+                    truSleepNewSync = (value & 2) != 0;
+                    gpsNewSync = (value & 8) != 0;
+                }
+
+                // Tag 3 -> SmartWatchVersion
+                // Tag 4 to 6 are HMS related
+            }
+        }
+    }
+
+    public static class TimeZoneIdRequest extends HuaweiPacket {
+        public static final byte id = 0x32;
+
+        public TimeZoneIdRequest(
+                ParamsProvider paramsProvider) {
+            super(paramsProvider);
+            this.serviceId = DeviceConfig.id;
+            this.commandId = id;
+            ByteBuffer timeAndZoneId = ByteBuffer.wrap(HuaweiUtil.getTimeAndZoneId(Calendar.getInstance()));
+            this.tlv = new HuaweiTLV()
+                    .put(0x01, timeAndZoneId.getInt())
+                    .put(0x02, timeAndZoneId.getShort());
+            byte[] zoneId = new byte[timeAndZoneId.remaining()];
+            timeAndZoneId.get(zoneId, 0, timeAndZoneId.remaining());
+            this.tlv.put(0x03, zoneId);
+            this.complete = true;
+        }
+
+        // TODO: implement parsing this request for the log parser support
+    }
+
+    public static class SecurityNegotiation {
+        public static final int id = 0x33;
+
+        public static class Request extends HuaweiPacket {
+
+            public Request (
+                    HuaweiPacket.ParamsProvider paramsProvider,
+                    byte authMode,
+                    byte[] deviceUUID,
+                    String phoneModel) {
+                super(paramsProvider);
+
+                this.serviceId = DeviceConfig.id;
+                this.commandId = id;
+
+                this.tlv = new HuaweiTLV()
+                    .put(0x01, authMode);
+                if (authMode == 0x02 || authMode == 0x04)
+                    this.tlv.put(0x02, (byte)0x01); //force to not reconnected else 0x02
+                this.tlv.put(0x05, deviceUUID)
+                        .put(0x03, (byte)0x01)
+                        .put(0x04, (byte)0x00);
+                if (authMode == 0x04)
+                    this.tlv.put(0x06)
+                            .put(0x07, phoneModel);
+                if (paramsProvider.getEncryptMethod() == 1 )
+                        this.tlv.put(0xd, (byte)0x1);
+
+
+                this.complete = true;
+                this.isEncrypted = false;
+            }
+            // TODO: implement parsing this request for the log parser support
+        }
+
+        public static class Response extends HuaweiPacket {
+            public int authType;
+
+            public Response(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+                this.serviceId = DeviceConfig.id;
+                this.commandId = id;
+            }
+
+            @Override
+            public void parseTlv() throws ParseException {
+                this.authType = -0x1;
+                int pw = -0x1;
+                if (this.tlv.contains(0x01)) {
+                    if (this.tlv.getByte(0x01) == 0x01)
+                        this.authType = 0x0186A0;
+                    if (this.tlv.getByte(0x01) == 0x04)
+                        pw = 4;
+                }
+                if (this.tlv.contains(0x02)) {
+                    this.authType = (int)this.tlv.getByte(0x02);
+                    if (pw != -0x1)
+                        this.authType ^= pw;
+                }
+                if (this.tlv.contains(0x7F))
+                    this.authType = (int)this.tlv.getByte(0x7F);
+            }
+        }
+    }
+
+    public static class ConnectStatusRequest extends HuaweiPacket {
+        public static final int id = 0x35;
+
+        public ConnectStatusRequest (HuaweiPacket.ParamsProvider paramsProvider) {
+            super(paramsProvider);
+
+            this.serviceId = DeviceConfig.id;
+            this.commandId = id;
+
+            this.tlv = new HuaweiTLV()
+                .put(0x01,(byte)0x01);
+            this.complete = true;
+        }
+        // TODO: implement parsing this request for the log parser support
+    }
+
+
+    public static class ExpandCapability {
+        public static final int id = 0x37;
+
+        public static class Request extends HuaweiPacket {
+            public Request(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+
+                this.serviceId = DeviceConfig.id;
+                this.commandId = id;
+
+                this.tlv = new HuaweiTLV()
+                    .put(0x01);
+
+                this.complete = true;
+            }
+        }
+
+        public static class Response extends HuaweiPacket {
+            public byte[] expandCapabilities;
+
+            public Response(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+                this.serviceId = DeviceConfig.id;
+                this.commandId = id;
+            }
+
+            @Override
+            public void parseTlv() throws ParseException {
+               this.expandCapabilities = this.tlv.getBytes(0x01);
+            }
+        }
+
+    }
+
+    public static class WearStatus {
+        public static final int id = 0x3D;
+
+        public static class Request extends HuaweiPacket {
+            public Request(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+
+                this.serviceId = DeviceConfig.id;
+                this.commandId = id;
+
+                this.tlv = new HuaweiTLV()
+                    .put(0x01);
+
+                this.complete = true;
+            }
+        }
+
+        public static class Response extends HuaweiPacket {
+
+            public Response(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+                this.serviceId = DeviceConfig.id;
+                this.commandId = id;
+            }
+
+            @Override
+            public void parseTlv() throws ParseException {
+            }
+        }
+    }
+
+    public static class SetUpDeviceStatusRequest extends HuaweiPacket {
+        public static final int id = 0x3E;
+
+        public SetUpDeviceStatusRequest(ParamsProvider paramsProvider, int relationShip, String deviceName) {
+            super(paramsProvider);
+
+            this.serviceId = DeviceConfig.id;
+            this.commandId = id;
+
+            this.tlv = new HuaweiTLV()
+                .put(0x01, (byte) relationShip)
+                .put(0x02, deviceName)
+                .put(0x03, (byte)0x00);
+
+            this.complete = true;
+        }
+    }
+
+    // TODO: wear location enum?
+
+    public static class Date {
+        // TODO: enum?
+
+        public static final int yearFirst = 0x01;
+        public static final int monthFirst = 0x02;
+        public static final int dayFirst = 0x03;
+    }
+
+    public static class Time {
+        // TODO: enum?
+
+        public static final int hours12 = 0x01;
+        public static final int hours24 = 0x02;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/DisconnectNotification.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/DisconnectNotification.java
new file mode 100644
index 0000000..847814b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/DisconnectNotification.java
@@ -0,0 +1,45 @@
+/*  Copyright (C) 2024 Damien Gaignon, Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets;
+
+/*
+ * TODO: It isn't clear if this class should handle more at this point, and thus might need a
+ *       different name later
+ */
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiTLV;
+
+public class DisconnectNotification {
+    public static final byte id = 0x0b;
+
+    public static class DisconnectNotificationSetting {
+        public static final byte id = 0x03;
+
+        public static class Request extends HuaweiPacket {
+            public Request(ParamsProvider paramsProvider, boolean enable) {
+                super(paramsProvider);
+
+                this.serviceId = DisconnectNotification.id;
+                this.commandId = id;
+                this.tlv = new HuaweiTLV()
+                        .put(0x01, enable);
+                this.complete = true;
+            }
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/Earphones.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/Earphones.java
new file mode 100644
index 0000000..188ba14
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/Earphones.java
@@ -0,0 +1,119 @@
+/*  Copyright (C) 2024 Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+
+package nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiTLV;
+
+// Information from:
+// https://mmk.pw/en/posts/freebuds-4i-proto/ and
+// https://github.com/TheLastGimbus/FreeBuddy/blob/master/notes/mbb-protocol-wiki.md and
+// https://codeberg.org/Freeyourgadget/Gadgetbridge/pulls/4325/
+
+public class Earphones {
+    public static final byte id = 0x2b;
+
+    public static class InEarStateResponse extends HuaweiPacket {
+        public static final byte id = 0x03;
+
+        public byte leftState;
+        public byte rightState;
+
+        public InEarStateResponse(ParamsProvider paramsProvider) {
+            super(paramsProvider);
+            this.serviceId = Earphones.id;
+            this.commandId = id;
+            this.complete = true;
+        }
+
+        @Override
+        public void parseTlv() throws ParseException {
+            this.leftState = this.tlv.getByte(0x08, (byte) -1);
+            this.rightState = this.tlv.getByte(0x09, (byte) -1);
+        }
+    }
+
+    public static class SetAudioModeRequest extends HuaweiPacket {
+        public static final byte id = 0x04;
+
+        // TODO: enum for new state
+        public SetAudioModeRequest(ParamsProvider paramsProvider, byte newState) {
+            super(paramsProvider);
+            this.serviceId = Earphones.id;
+            this.commandId = id;
+
+            byte[] data = {newState, newState == 0 ? 0x00 : (byte) 0xff};
+
+            this.tlv = new HuaweiTLV().put(0x01, data);
+
+            this.complete = true;
+        }
+    }
+
+    public static class SetPauseWhenRemovedFromEar extends HuaweiPacket {
+        public static final byte id = 0x10;
+
+        // TODO: enum for new state
+        public SetPauseWhenRemovedFromEar(ParamsProvider paramsProvider, boolean newState) {
+            super(paramsProvider);
+            this.serviceId = Earphones.id;
+            this.commandId = id;
+
+            this.tlv = new HuaweiTLV().put(0x01, newState);
+
+            this.complete = true;
+        }
+    }
+
+    // TODO: get pause when removed from ear 0x11
+    // TODO: set long tap action 0x16
+    // TODO: get long tap action 0x17
+    // TODO: Audio mode cycle 0x19
+
+    public static class GetAudioModeRequest {
+        public static final byte id = 0x2a;
+
+        public static class Request extends HuaweiPacket {
+            public Request(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+                this.serviceId = Earphones.id;
+                this.commandId = id;
+                this.complete = true;
+            }
+        }
+
+        public static class Response extends HuaweiPacket {
+            public short fullState;
+
+            public byte state; // TODO: enum
+
+            public Response(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+                this.serviceId = Earphones.id;
+                this.commandId = id;
+                this.complete = true;
+            }
+
+            @Override
+            public void parseTlv() throws ParseException {
+                this.fullState = this.tlv.getShort(0x01);
+                this.state = (byte) this.fullState;
+            }
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/Ephemeris.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/Ephemeris.java
new file mode 100644
index 0000000..96757ba
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/Ephemeris.java
@@ -0,0 +1,114 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiTLV;
+
+public class Ephemeris {
+    public static final int id = 0x1f;
+
+    public static class OperatorData {
+        public static final int id = 0x01;
+
+        public static class OperatorIncomingRequest extends HuaweiPacket {
+            public byte operationInfo;
+            public int operationTime;
+
+            public OperatorIncomingRequest(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+            }
+
+            @Override
+            public void parseTlv() throws ParseException {
+
+                //TODO: can contain many elements.
+                //List<HuaweiTLV> subContainers = container.getObjects(0x81);
+                HuaweiTLV subTlv = this.tlv.getObject(0x81);
+
+                this.operationInfo = subTlv.getByte(0x02);
+                this.operationTime = subTlv.getInteger(0x03);
+            }
+        }
+
+        public static class OperatorResponse extends HuaweiPacket {
+
+            public OperatorResponse(ParamsProvider paramsProvider, int responseCode) {
+                super(paramsProvider);
+
+                this.serviceId = Ephemeris.id;
+                this.commandId = id;
+
+                this.tlv = new HuaweiTLV().put(0x7f, responseCode);
+
+                this.complete = true;
+            }
+        }
+    }
+
+    public static class ParameterConsult {
+        public static final byte id = 0x02;
+
+        public static class Request extends HuaweiPacket {
+
+
+            public Request(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+                this.serviceId = Ephemeris.id;
+                this.commandId = id;
+                this.tlv = new HuaweiTLV().put(0x81);
+            }
+        }
+
+        public static class Response extends HuaweiPacket {
+            public int consultDeviceTime;
+            public byte downloadVersion;
+            public String downloadTag;
+
+            public Response (ParamsProvider paramsProvider) {
+                super(paramsProvider);
+            }
+
+            @Override
+            public void parseTlv() throws HuaweiPacket.ParseException {
+
+                //TODO: can contain many elements.
+                //List<HuaweiTLV> subContainers = container.getObjects(0x81);
+                HuaweiTLV subTlv = this.tlv.getObject(0x81);
+
+                this.consultDeviceTime = subTlv.getByte(0x04) * 1000;
+                this.downloadVersion = subTlv.getByte(0x05);
+                this.downloadTag = subTlv.getString(0x06);
+            }
+
+        }
+    }
+
+    public static class FileStatus {
+        public static final byte id = 0x03;
+
+        public static class Request extends HuaweiPacket {
+
+
+            public Request(ParamsProvider paramsProvider, byte status) {
+                super(paramsProvider);
+                this.serviceId = Ephemeris.id;
+                this.commandId = id;
+                this.tlv = new HuaweiTLV().put(0x1, status);
+            }
+        }
+
+        public static class Response extends HuaweiPacket {
+            public int responseCode;
+
+            public Response (ParamsProvider paramsProvider) {
+                super(paramsProvider);
+            }
+
+            @Override
+            public void parseTlv() throws HuaweiPacket.ParseException {
+                this.responseCode = this.tlv.getInteger(0x7f);
+            }
+
+        }
+    }
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/EphemerisFileUpload.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/EphemerisFileUpload.java
new file mode 100644
index 0000000..4420e1e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/EphemerisFileUpload.java
@@ -0,0 +1,269 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiTLV;
+
+public class EphemerisFileUpload {
+    public static final int id = 0x1c;
+
+    public static class FileList {
+        public static final int id = 0x01;
+
+        public static class FileListIncomingRequest extends HuaweiPacket {
+            public byte fileType;
+            public String productId = "";
+
+            public FileListIncomingRequest(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+            }
+
+            @Override
+            public void parseTlv() throws ParseException {
+
+                // 0x1 - filenames
+                // 0x2 - fileType
+                // 0x3 - productId
+                // 0x4 - issuerId
+                // 0x5 - cardType
+                this.fileType = this.tlv.getByte(0x02);
+                if (this.tlv.contains(0x3))
+                    productId = this.tlv.getString(0x3);
+
+            }
+        }
+
+        public static class FileListResponse extends HuaweiPacket {
+
+            public FileListResponse(ParamsProvider paramsProvider, int responseCode, String files) {
+                super(paramsProvider);
+
+                this.serviceId = EphemerisFileUpload.id;
+                this.commandId = id;
+
+                if (responseCode != 0) {
+                    this.tlv = new HuaweiTLV().put(0x7f, responseCode);
+                } else {
+                    this.tlv = new HuaweiTLV().put(0x1, files);
+                }
+
+                this.complete = true;
+            }
+        }
+    }
+
+    public static class FileConsult {
+        public static final int id = 0x02;
+
+        public static class FileConsultIncomingRequest extends HuaweiPacket {
+            public int responseCode = 0;
+            public String protocolVersion = null;
+            public byte bitmapEnable = 0;
+            public short transferSize = 0;
+            public int maxDataSize = 0;
+            public short timeOut = 0;
+            public byte fileType = 0;
+
+            public FileConsultIncomingRequest(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+            }
+
+            @Override
+            public void parseTlv() throws ParseException {
+
+                if (this.tlv.contains(0x7f))
+                    responseCode = this.tlv.getInteger(0x7f);
+
+                // 0x1 - version
+                // 0x2 - bitmapEnable
+                // 0x3 - transferSize
+                // 0x4 - maxDataSize
+                // 0x5 - timeOut
+                // 0x6 - fileType
+
+                if (this.tlv.contains(0x1))
+                    this.protocolVersion = this.tlv.getString(0x1);
+                if (this.tlv.contains(0x2))
+                    this.bitmapEnable = this.tlv.getByte(0x2);
+                if (this.tlv.contains(0x3))
+                    this.transferSize = this.tlv.getShort(0x3);
+                if (this.tlv.contains(0x4))
+                    this.maxDataSize = this.tlv.getInteger(0x4);
+                if (this.tlv.contains(0x5))
+                    this.timeOut = this.tlv.getShort(0x5);
+                if (this.tlv.contains(0x6))
+                    this.fileType = this.tlv.getByte(0x6);
+            }
+        }
+
+        public static class FileConsultResponse extends HuaweiPacket {
+
+            public FileConsultResponse(ParamsProvider paramsProvider, int responseCode) {
+                super(paramsProvider);
+
+                this.serviceId = EphemerisFileUpload.id;
+                this.commandId = id;
+
+                this.tlv = new HuaweiTLV().put(0x7f, responseCode);
+
+                this.complete = true;
+            }
+        }
+    }
+
+    public static class QuerySingleFileInfo {
+        public static final int id = 0x03;
+
+        public static class QuerySingleFileInfoIncomingRequest extends HuaweiPacket {
+            public int responseCode = 0;
+            public String fileName = null;
+
+            public QuerySingleFileInfoIncomingRequest(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+            }
+
+            @Override
+            public void parseTlv() throws ParseException {
+
+                if (this.tlv.contains(0x7f))
+                    responseCode = this.tlv.getInteger(0x7f);
+
+                if (this.tlv.contains(0x1))
+                    this.fileName = this.tlv.getString(0x1);
+
+            }
+        }
+
+        public static class QuerySingleFileInfoResponse extends HuaweiPacket {
+
+            public QuerySingleFileInfoResponse(ParamsProvider paramsProvider, int responseCode, int fileSize, short crc) {
+                super(paramsProvider);
+
+                this.serviceId = EphemerisFileUpload.id;
+                this.commandId = id;
+
+                if(responseCode != 0) {
+                    this.tlv = new HuaweiTLV().put(0x7f, responseCode);
+                } else {
+                    this.tlv = new HuaweiTLV().put(0x2, fileSize).put(0x3, crc);
+                }
+
+                this.complete = true;
+            }
+        }
+    }
+
+    public static class DataRequest {
+        public static final int id = 0x04;
+
+        public static class DataRequestIncomingRequest extends HuaweiPacket {
+            public int responseCode = 0;
+            public String fileName = null;
+            public int offset = -1;
+            public int len = -1;
+            public byte bitmap = (byte) 0xff;
+
+
+            public DataRequestIncomingRequest(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+            }
+
+            @Override
+            public void parseTlv() throws ParseException {
+                // 1 - fileName
+                // 2 - offset
+                // 3 - length
+                // 4 - fileBitmap
+                // 7f - error
+
+                if (this.tlv.contains(0x7f))
+                    responseCode = this.tlv.getInteger(0x7f);
+
+                if (this.tlv.contains(0x1))
+                    this.fileName = this.tlv.getString(0x1);
+                if (this.tlv.contains(0x2))
+                    this.offset = this.tlv.getInteger(0x2);
+                if (this.tlv.contains(0x3))
+                    this.len = this.tlv.getInteger(0x3);
+                if (this.tlv.contains(0x4))
+                    this.bitmap = this.tlv.getByte(0x4);
+            }
+        }
+
+        public static class DataRequestResponse extends HuaweiPacket {
+            public DataRequestResponse(ParamsProvider paramsProvider, int responseCode, String filename, int offset) {
+                super(paramsProvider);
+
+                this.serviceId = EphemerisFileUpload.id;
+                this.commandId = id;
+
+                this.tlv = new HuaweiTLV().put(0x7f, responseCode);
+                if(responseCode == 100000) {
+                    this.tlv.put(0x2, filename).put(0x3, offset);
+                }
+
+                this.complete = true;
+            }
+        }
+    }
+
+    public static class UploadData {
+        public static final int id = 0x05;
+
+        public static class FileNextChunkSend extends HuaweiPacket {
+            public FileNextChunkSend(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+                this.serviceId = EphemerisFileUpload.id;
+                this.commandId = id;
+                this.complete = true;
+            }
+        }
+
+        public static class UploadDataResponse extends HuaweiPacket {
+            public int responseCode = 0;
+
+            public UploadDataResponse(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+            }
+
+            @Override
+            public void parseTlv() throws ParseException {
+                this.tlv = new HuaweiTLV();
+                this.tlv.parse(payload, 2, payload.length - 2);
+                this.responseCode = this.tlv.getInteger(0x7f);
+            }
+        }
+    }
+
+
+    public static class UploadDone {
+        public static final int id = 0x06;
+
+        public static class UploadDoneIncomingRequest extends HuaweiPacket {
+            public byte uploadResult = 0;
+
+            public UploadDoneIncomingRequest(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+            }
+
+            @Override
+            public void parseTlv() throws ParseException {
+                this.uploadResult = this.tlv.getByte(0x1);
+            }
+        }
+
+        public static class UploadDoneResponse extends HuaweiPacket {
+
+            public UploadDoneResponse(ParamsProvider paramsProvider, int responseCode) {
+                super(paramsProvider);
+
+                this.serviceId = EphemerisFileUpload.id;
+                this.commandId = id;
+
+                this.tlv = new HuaweiTLV().put(0x7f, responseCode);
+
+                this.complete = true;
+            }
+        }
+    }
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/FileDownloadService0A.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/FileDownloadService0A.java
new file mode 100644
index 0000000..29d5516
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/FileDownloadService0A.java
@@ -0,0 +1,279 @@
+/*  Copyright (C) 2024 Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiCrypto;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiTLV;
+
+/**
+ * File downloading for "older" devices/implementations
+ * Newer ones might be using service id 0x2c
+ * Which one is used is reported by the band in 0x01 0x31
+ */
+public class FileDownloadService0A {
+    public static final int id = 0x0a;
+
+    /*
+    Type of files that can be downloaded through here:
+    - debug files
+    - sleep files
+    - gps files
+    - rrisqi file
+     */
+
+    public static class FileDownloadInit {
+        public static final int id = 0x01;
+
+        public static class DebugFilesRequest extends HuaweiPacket {
+            public DebugFilesRequest(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+
+                this.serviceId = FileDownloadService0A.id;
+                this.commandId = id;
+
+                this.tlv = new HuaweiTLV(); // Empty TLV
+
+                this.complete = true;
+            }
+        }
+
+        public static class SleepFilesRequest extends HuaweiPacket {
+            public SleepFilesRequest(ParamsProvider paramsProvider, int startTime, int endTime) {
+                super(paramsProvider);
+
+                this.serviceId = FileDownloadService0A.id;
+                this.commandId = id;
+
+                this.tlv = new HuaweiTLV()
+                        .put(0x02, (byte) 0x01)
+                        .put(0x83, new HuaweiTLV()
+                                .put(0x04, startTime)
+                                .put(0x05, endTime)
+                        );
+
+                this.complete = true;
+            }
+        }
+
+        public static class GpsFileRequest extends HuaweiPacket {
+            public GpsFileRequest(ParamsProvider paramsProvider, short workoutId) {
+                super(paramsProvider);
+
+                this.serviceId = FileDownloadService0A.id;
+                this.commandId = id;
+
+                this.tlv = new HuaweiTLV()
+                        .put(0x02, (byte) 0x02)
+                        .put(0x86, new HuaweiTLV()
+                                .put(0x07, (byte) 0x01)         // Might be type?
+                                .put(0x88, new HuaweiTLV()
+                                        .put(0x09, workoutId)
+                                )
+                        );
+
+                this.complete = true;
+            }
+        }
+
+        public static class Response extends HuaweiPacket {
+            public String[] fileNames;
+
+            public Response(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+            }
+
+            @Override
+            public void parseTlv() throws ParseException {
+                String possibleNames;
+                if (this.tlv.contains(0x01))
+                    possibleNames = this.tlv.getString(0x01);
+                else // For GPS, also has workoutId (0x09) and another tag (0x0e), not sure of the meaning
+                    possibleNames = this.tlv.getObject(0x8b).getObject(0x8c).getString(0x0d);
+                fileNames = possibleNames.split(";");
+            }
+        }
+    }
+
+    public static class FileParameters {
+        public static final int id = 0x02;
+
+        public static class Request extends HuaweiPacket {
+            public Request(ParamsProvider paramsProvider, boolean truSleep) {
+                super(paramsProvider);
+
+                this.serviceId = FileDownloadService0A.id;
+                this.commandId = id;
+
+                if (truSleep)
+                    this.tlv = new HuaweiTLV().put(0x06, (byte) 0x01);
+                else
+                    this.tlv = new HuaweiTLV().put(0x06, (byte) 0x02).put(0x07, (byte) 0x03);
+
+                this.complete = true;
+            }
+        }
+
+        public static class Response extends HuaweiPacket {
+            public String version;
+            public boolean unknown;
+            public short packetSize;
+            public short maxBlockSize;
+            public short timeout;
+
+            public Response(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+            }
+
+            @Override
+            public void parseTlv() throws ParseException {
+                // TODO: below could be different for AW70?
+
+                this.version = this.tlv.getString(0x01);
+                this.unknown = this.tlv.getBoolean(0x02);
+                this.packetSize = this.tlv.getShort(0x03);
+                this.maxBlockSize = this.tlv.getShort(0x04);
+                this.timeout = this.tlv.getShort(0x05);
+            }
+        }
+    }
+
+    public static class FileInfo {
+        public static final int id = 0x03;
+
+        public static class Request extends HuaweiPacket {
+            public Request(ParamsProvider paramsProvider, String fileName) {
+                super(paramsProvider);
+
+                this.serviceId = FileDownloadService0A.id;
+                this.commandId = id;
+
+                this.tlv = new HuaweiTLV().put(0x01, fileName);
+
+                this.complete = true;
+            }
+        }
+
+        public static class Response extends HuaweiPacket {
+            public int fileLength;
+            public byte transferType = -1;
+            public int fileCreateTime = -1;
+            public byte unknown = -1;
+
+            public Response(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+            }
+
+            @Override
+            public void parseTlv() throws ParseException {
+                if (this.tlv.contains(0x02))
+                    this.fileLength = this.tlv.getInteger(0x02);
+                if (this.tlv.contains(0x04))
+                    this.transferType = this.tlv.getByte(0x04);
+                if (this.tlv.contains(0x05))
+                    this.fileCreateTime = this.tlv.getInteger(0x05);
+                if (this.tlv.contains(0x06))
+                    this.unknown = this.tlv.getByte(0x06);
+            }
+        }
+    }
+
+    public static class RequestBlock {
+        public static final int id = 0x04;
+
+        public static class Request extends HuaweiPacket {
+            public Request(ParamsProvider paramsProvider, String fileName, int offset, int size) {
+                super(paramsProvider);
+
+                this.serviceId = FileDownloadService0A.id;
+                this.commandId = id;
+
+                this.tlv = new HuaweiTLV()
+                        .put(0x01, fileName)
+                        .put(0x02, offset) // TODO: not for AW70
+                        .put(0x03, size); // TODO: not for AW70
+
+                this.complete = true;
+            }
+        }
+
+        public static class Response extends HuaweiPacket {
+            public boolean isOk;
+            public String filename;
+            public int offset;
+
+            public Response(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+            }
+
+            public void parseTlv() throws ParseException {
+                isOk = this.tlv.getInteger(0x7f) == 0x000186A0;
+                if (isOk) {
+                    if (this.tlv.contains(0x01))
+                        filename = this.tlv.getString(0x01);
+                    offset = this.tlv.getInteger(0x02);
+                }
+            }
+        }
+    }
+
+    public static class BlockResponse extends HuaweiPacket {
+        public static final int id = 0x05;
+
+        public byte number;
+        public byte[] data;
+
+        public Exception e = null;
+
+        public BlockResponse(ParamsProvider paramsProvider) {
+            super(paramsProvider);
+        }
+
+        @Override
+        public void parseTlv() throws ParseException {
+            int offset;
+            // Try to decrypt TLV, and otherwise assume the data is not encrypted
+            try {
+                HuaweiTLV tlv = new HuaweiTLV().parse(this.payload, 2 ,this.payload.length - 2);
+                this.payload = tlv.decryptRaw(paramsProvider);
+                offset = 0;
+            } catch (ArrayIndexOutOfBoundsException | HuaweiCrypto.CryptoException e) {
+                this.e = e;
+                offset = 2;
+            }
+
+            this.number = this.payload[offset];
+            this.data = new byte[this.payload.length - offset - 1];
+            System.arraycopy(this.payload, offset + 1, this.data, 0, this.payload.length - offset - 1);
+        }
+    }
+
+    public static class FileDownloadCompleteRequest extends HuaweiPacket {
+        public static final int id = 0x06;
+
+        public FileDownloadCompleteRequest(ParamsProvider paramsProvider) {
+            super(paramsProvider);
+
+            this.serviceId = FileDownloadService0A.id;
+            this.commandId = id;
+
+            this.tlv = new HuaweiTLV().put(0x06, true);
+
+            this.complete = true;
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/FileDownloadService2C.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/FileDownloadService2C.java
new file mode 100644
index 0000000..bbbea53
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/FileDownloadService2C.java
@@ -0,0 +1,223 @@
+/*  Copyright (C) 2024 Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets;
+
+import java.nio.ByteBuffer;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiCrypto;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiTLV;
+
+/**
+ * File downloading for "older" devices/implementations
+ * Newer ones might be using service id 0x2c
+ * Which one is used is reported by the band in 0x01 0x31
+ */
+public class FileDownloadService2C {
+    public static final int id = 0x2c;
+
+    public enum FileType {
+        SLEEP_STATE,
+        SLEEP_DATA,
+        GPS,
+        UNKNOWN; // Never use this as input
+
+        static byte fileTypeToByte(FileType fileType) {
+            switch (fileType) {
+                case SLEEP_STATE:
+                    return (byte) 0x0e;
+                case SLEEP_DATA:
+                    return (byte) 0x0f;
+                case GPS:
+                    return (byte) 0x11;
+                default:
+                    throw new RuntimeException();
+            }
+        }
+
+        static FileType byteToFileType(byte b) {
+            switch (b) {
+                case 0x0e:
+                    return FileType.SLEEP_STATE;
+                case 0x0f:
+                    return FileType.SLEEP_DATA;
+                case 0x11:
+                    return FileType.GPS;
+                default:
+                    return FileType.UNKNOWN;
+            }
+        }
+    }
+
+    public static class FileDownloadInit {
+        public static final int id = 0x01;
+
+        public static class Request extends HuaweiPacket {
+            public Request(ParamsProvider paramsProvider, String filename, FileType filetype, int startTime, int endTime) {
+                super(paramsProvider);
+
+                this.serviceId = FileDownloadService2C.id;
+                this.commandId = id;
+
+                // TODO: start and end time might be optional?
+                this.tlv = new HuaweiTLV()
+                        .put(0x01, filename)
+                        .put(0x02, FileType.fileTypeToByte(filetype))
+                        .put(0x05, startTime)
+                        .put(0x06, endTime);
+
+                this.complete = true;
+            }
+        }
+
+        public static class Response extends HuaweiPacket {
+            public String fileName;
+            public FileType fileType;
+            public byte fileId;
+            public int fileSize;
+
+            public Response(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+            }
+
+            @Override
+            public void parseTlv() throws ParseException {
+                fileName = this.tlv.getString(0x01);
+                fileType = FileType.byteToFileType(this.tlv.getByte(0x02));
+                fileId = this.tlv.getByte(0x03);
+                fileSize = this.tlv.getInteger(0x04);
+            }
+        }
+    }
+
+    public static class FileInfo {
+        public static final int id = 0x03;
+
+        public static class Request extends HuaweiPacket {
+            public Request(ParamsProvider paramsProvider, byte fileId) {
+                super(paramsProvider);
+
+                this.serviceId = FileDownloadService2C.id;
+                this.commandId = id;
+
+                this.tlv = new HuaweiTLV()
+                        .put(0x01, fileId)
+                        .put(0x02)
+                        .put(0x03)
+                        .put(0x04)
+                        .put(0x05);
+
+                this.complete = true;
+            }
+        }
+
+        public static class Response extends HuaweiPacket {
+            public byte fileId;
+            public byte timeout; // TODO: not sure about unit here - maybe seconds?
+            // TODO: following two might not have the best names...
+            public short burstSize; // How large each 0x2c 0x05 will be
+            public int maxBlockSize; // How much we can ask for before needing another 0x2c 0x04
+            public boolean noEncrypt;
+
+            public Response(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+            }
+
+            @Override
+            public void parseTlv() throws ParseException {
+                fileId = this.tlv.getByte(0x01);
+                timeout = this.tlv.getByte(0x02);
+                burstSize = this.tlv.getShort(0x03);
+                maxBlockSize = this.tlv.getInteger(0x04);
+                noEncrypt = this.tlv.getBoolean(0x05); // True if command 0x04 cannot be encrypted
+            }
+        }
+    }
+
+    public static class RequestBlock extends HuaweiPacket {
+        public static final int id = 0x04;
+
+        public RequestBlock(ParamsProvider paramsProvider, byte fileId, int offset, int size, boolean noEncrypt) {
+            super(paramsProvider);
+
+            this.serviceId = FileDownloadService2C.id;
+            this.commandId = id;
+
+            this.tlv = new HuaweiTLV()
+                    .put(0x01, fileId)
+                    .put(0x02, offset)
+                    .put(0x03, size);
+
+            this.complete = true;
+            this.isEncrypted = !noEncrypt;
+        }
+    }
+
+    public static class BlockResponse extends HuaweiPacket {
+        public static final int id = 0x05;
+
+        public byte fileId;
+        public int offset;
+        public byte unknown;
+        public byte[] data;
+
+        public BlockResponse(ParamsProvider paramsProvider) {
+            super(paramsProvider);
+        }
+
+        @Override
+        public void parseTlv() throws ParseException {
+            ByteBuffer byteBuffer;
+            if (this.payload[2] == 0x7c && this.payload[3] == 0x01 && this.payload[4] == 0x01) {
+                // Encrypted TLV, so we decrypt first
+                this.tlv = new HuaweiTLV();
+                this.tlv.parse(this.payload, 2, this.payload.length - 2);
+                try {
+                    byteBuffer = ByteBuffer.wrap(this.tlv.decryptRaw(paramsProvider));
+                } catch (HuaweiCrypto.CryptoException e) {
+                    throw new CryptoException("File download decryption exception", e);
+                }
+                this.tlv = null; // Prevent using it accidentally
+            } else {
+                byteBuffer = ByteBuffer.wrap(this.payload, 2, this.payload.length - 2);
+            }
+
+            fileId = byteBuffer.get();
+            offset = byteBuffer.getInt();
+            unknown = byteBuffer.get();
+            data = new byte[byteBuffer.remaining()];
+            System.arraycopy(byteBuffer.array(), byteBuffer.position(), data, 0, byteBuffer.remaining());
+        }
+    }
+
+    public static class FileDownloadCompleteRequest extends HuaweiPacket {
+        public static final int id = 0x06;
+
+        public FileDownloadCompleteRequest(ParamsProvider paramsProvider, byte fileId) {
+            super(paramsProvider);
+
+            this.serviceId = FileDownloadService2C.id;
+            this.commandId = id;
+
+            this.tlv = new HuaweiTLV()
+                    .put(0x01, fileId)
+                    .put(0x02, (byte) 1);
+
+            this.complete = true;
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/FileUpload.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/FileUpload.java
new file mode 100644
index 0000000..b8eae70
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/FileUpload.java
@@ -0,0 +1,239 @@
+/*  Copyright (C) 2024 Vitalii Tomin
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+
+package nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiTLV;
+
+public class FileUpload {
+    public static final byte id = 0x28;
+
+    public static class FileUploadParams {
+        public byte file_id = 0;
+        public String protocolVersion = "";
+        public int app_wait_time = 0;
+        public int bitmap_enable = 0;
+        public int unit_size = 0;
+        public int max_apply_data_size = 0;
+        public int interval = 0;
+        public int received_file_size = 0;
+        public byte no_encrypt = 0;
+    }
+
+    public static class Filetype {
+        public static final byte watchface = 1;
+        public static final byte music = 2;
+        public static final byte backgroundImage = 3;
+        public static final byte app = 6;
+        public static final byte firmware = 7;
+    }
+
+
+    public static class FileInfoSend {
+        public static final byte id = 0x02;
+
+        public static class Request extends HuaweiPacket {
+
+            public Request(ParamsProvider paramsProvider,
+                           int fileSize,
+                           String fileName,
+                           byte fileType,
+                           String srcPackage,
+                           String dstPackage,
+                           String srcFingerprint,
+                           String dstFingerprint
+            ) {
+                super(paramsProvider);
+                this.serviceId = FileUpload.id;
+                this.commandId = id;
+
+                this.tlv = new HuaweiTLV()
+                        .put(0x01, fileName)
+                        .put(0x02, fileSize)
+                        .put(0x03, fileType);
+
+                if (fileType == Filetype.watchface) {
+                    String watchfaceName = fileName.split("_")[0];
+                    String watchfaceVersion = fileName.split("_")[1];
+                    this.tlv.put(0x05, watchfaceName)
+                            .put(0x06, watchfaceVersion);
+                }
+
+                if (srcPackage != null && dstPackage != null) {
+                    this.tlv.put(0x08, srcPackage)
+                            .put(0x09, dstPackage)
+                            .put(0x0a)
+                            .put(0x0b, srcFingerprint)
+                            .put(0x0c, dstFingerprint);
+                }
+
+
+                this.complete = true;
+            }
+        }
+
+        public static class Response extends HuaweiPacket {
+            public int result = 0;
+
+            public Response(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+            }
+
+            @Override
+            public void parseTlv() throws HuaweiPacket.ParseException {
+                this.result = this.tlv.getInteger(0x7f);
+            }
+        }
+    }
+
+    public static class FileHashSend {
+        public static final byte id = 0x03;
+
+        public static class Request extends HuaweiPacket {
+
+            public Request(ParamsProvider paramsProvider,
+                           byte[] hash,
+                           byte fileId) {
+                super(paramsProvider);
+                this.serviceId = FileUpload.id;
+                this.commandId = id;
+
+                this.tlv = new HuaweiTLV()
+                        .put(0x01, fileId)
+                        .put(0x03, hash);
+                this.complete = true;
+            }
+        }
+
+        public static class Response extends HuaweiPacket {
+            public byte fileId = 0;
+
+            public Response(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+            }
+
+            @Override
+            public void parseTlv() throws HuaweiPacket.ParseException {
+                this.fileId = this.tlv.getByte(0x01);
+            }
+
+        }
+    }
+
+    public static class FileUploadConsultAck {
+        public static final byte id = 0x04;
+
+        public static class Request extends HuaweiPacket {
+            public Request(ParamsProvider paramsProvider, byte noEncryption, byte fileId) {
+                super(paramsProvider);
+                this.serviceId = FileUpload.id;
+                this.commandId = id;
+                this.tlv = new HuaweiTLV()
+                        .put(0x7f, 0x000186A0) //ok
+                        .put(0x01, fileId);
+                if (noEncryption == 1)
+                    this.tlv.put(0x09, (byte) 0x01); // need on devices which generally encrypted, but files
+                this.complete = true;
+            }
+        }
+
+        public static class Response extends HuaweiPacket {
+
+            public FileUploadParams fileUploadParams = new FileUploadParams();
+
+            public Response(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+            }
+
+            @Override
+            public void parseTlv() throws HuaweiPacket.ParseException {
+                this.fileUploadParams.file_id = this.tlv.getByte(0x01);
+                this.fileUploadParams.protocolVersion = this.tlv.getString(0x02);
+                this.fileUploadParams.app_wait_time = this.tlv.getAsInteger(0x03);
+                this.fileUploadParams.bitmap_enable = this.tlv.getAsInteger(0x04);
+                this.fileUploadParams.unit_size = this.tlv.getAsInteger(0x05);
+                this.fileUploadParams.max_apply_data_size = this.tlv.getAsInteger(0x06);
+                this.fileUploadParams.interval = this.tlv.getAsInteger(0x07);
+                this.fileUploadParams.received_file_size = this.tlv.getInteger(0x08);
+                if (this.tlv.contains(0x09)) // optional for older devices
+                    this.fileUploadParams.no_encrypt = this.tlv.getByte(0x09);
+            }
+        }
+    }
+
+    public static class FileNextChunkParams extends HuaweiPacket {
+        public static final byte id = 0x05;
+
+        public int bytesUploaded = 0;
+        public int nextchunkSize = 0;
+
+        public FileNextChunkParams(ParamsProvider paramsProvider) {
+            super(paramsProvider);
+            this.serviceId = FileUpload.id;
+            this.commandId = id;
+            this.complete = true;
+        }
+
+        @Override
+        public void parseTlv() throws HuaweiPacket.ParseException {
+            this.bytesUploaded = this.tlv.getInteger(0x02);
+            this.nextchunkSize = this.tlv.getInteger(0x03);
+        }
+    }
+
+    public static class FileNextChunkSend extends HuaweiPacket {
+        public static final byte id = 0x06;
+
+        public FileNextChunkSend(ParamsProvider paramsProvider) {
+            super(paramsProvider);
+            this.serviceId = FileUpload.id;
+            this.commandId = id;
+            this.complete = true;
+        }
+    }
+
+    public static class FileUploadResult {
+        public static final byte id = 0x07;
+
+        public static class Request extends HuaweiPacket {
+            public Request(ParamsProvider paramsProvider, byte fileId) {
+                super(paramsProvider);
+                this.serviceId = FileUpload.id;
+                this.commandId = id;
+                this.tlv = new HuaweiTLV()
+                        .put(0x7f, 0x000186A0) //ok
+                        .put(0x01, fileId);
+
+                this.complete = true;
+            }
+        }
+
+        public static class Response extends HuaweiPacket {
+            byte status = 0;
+
+            public Response(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+            }
+
+            @Override
+            public void parseTlv() throws HuaweiPacket.ParseException {
+                this.status = this.tlv.getByte(0x02);
+            }
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/FindPhone.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/FindPhone.java
new file mode 100644
index 0000000..826be51
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/FindPhone.java
@@ -0,0 +1,62 @@
+/*  Copyright (C) 2024 Damien Gaignon, Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiTLV;
+
+public class FindPhone {
+    public static final byte id = 0x0b;
+
+    public static class Response extends HuaweiPacket {
+        public static final byte id = 0x01;
+
+        public boolean start = false;
+
+        public Response(ParamsProvider paramsProvider) {
+            super(paramsProvider);
+
+            this.serviceId = FindPhone.id;
+            this.commandId = id;
+
+            this.isEncrypted = false;
+        }
+
+        @Override
+        public void parseTlv() throws ParseException {
+            if (this.tlv.contains(0x01))
+                this.start = this.tlv.getBoolean(0x01);
+            // No missing tag exception so it will stop by default
+        }
+    }
+
+    public static class StopRequest extends HuaweiPacket {
+        public static final byte id = 0x02;
+
+        public StopRequest(ParamsProvider paramsProvider) {
+            super(paramsProvider);
+
+            this.serviceId = FindPhone.id;
+            this.commandId = id;
+
+            this.tlv = new HuaweiTLV()
+                    .put(0x01, (byte) 2);
+
+            this.complete = true;
+        }
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/FitnessData.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/FitnessData.java
new file mode 100644
index 0000000..e01db68
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/FitnessData.java
@@ -0,0 +1,850 @@
+/*  Copyright (C) 2024 Damien Gaignon, Martin.JM, Vitalii Tomin
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HeartRateZonesConfig;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiReportThreshold;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiRunPaceConfig;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiTLV;
+
+public class FitnessData {
+
+    public static final byte id = 0x07;
+
+    public static class MotionGoal {
+        public static final byte id = 0x01;
+
+        public static class Request extends HuaweiPacket {
+            public Request(ParamsProvider paramsProvider,
+                           byte goalType,
+                           byte frameType,
+                           int stepGoal,
+                           int calorieGoal,
+                           short durationGoal) {
+                super(paramsProvider);
+
+                this.serviceId = FitnessData.id;
+                this.commandId = id;
+
+                frameType = (frameType == 0x01) ? 0x01 : Type.motion;
+                HuaweiTLV subTlv = new HuaweiTLV()
+                        .put(0x03, goalType)
+                        .put(0x04, frameType);
+                stepGoal = ((Type.data & 0x01) != 0x00) ? stepGoal : 0xffffffff;
+                if (stepGoal != 0xffffffff)
+                    subTlv.put(0x05, stepGoal);
+                int calorieGoalFinal = ((Type.data & 0x02) != 0x00) ? calorieGoal : 0xffffffff;
+                if (calorieGoalFinal != 0xffffffff) {
+                    subTlv.put(0x06, calorieGoalFinal);
+                } else if (frameType == 0x01) {
+                    subTlv.put(0x06, stepGoal / 0x1e);
+                }
+                int distanceGoal = ((Type.data & 0x04) != 0x00) ? durationGoal : 0xffffffff;
+                if (distanceGoal != 0xffffffff) {
+                    subTlv.put(0x07, distanceGoal);
+                } else if (frameType == 0x01) {
+                    subTlv.put(0x06, stepGoal);
+                }
+                short durationGoalFinal = ((Type.data & 0x08) != 0x00) ? durationGoal : 0xffffffff;
+                if (durationGoalFinal != 0xffffffff) {
+                    subTlv.put(0x08, durationGoalFinal);
+                }
+                HuaweiTLV containerTlv = new HuaweiTLV().put(0x82, subTlv);
+                this.tlv = new HuaweiTLV()
+                        .put(0x81, containerTlv);
+            }
+        }
+    }
+
+    public static class UserInfo {
+        public static final byte id = 0x02;
+
+        public static class UserInfoData {
+            public static final int GENDER_MALE = 1;
+            public static final int GENDER_FEMALE = 2;
+            public static final int GENDER_UNKNOWN = 3;
+
+            private static final float RUN_CF = 0.83f;
+            private static final float WALK_CF = 0.42f;
+
+            public static final int DEFAULT_HEIGHT = 175;
+            public static final int DEFAULT_WEIGHT = 65;
+
+            private final int height;
+            private final float weight;
+            private final int age;
+            private final int birthday;
+            private final byte gender;
+
+            private final boolean unknownGenderSupported;
+
+            private final boolean vo2Supported;
+            private final int vo2Max;
+            private final int vo2Time;
+
+            private final boolean precisionWeightSupported;
+
+            public UserInfoData(int height, float weight, int age, int birthday, byte gender, boolean unknownGenderSupported, boolean vo2Supported, int vo2Max, int vo2Time, boolean precisionWeightSupported) {
+                this.height = height;
+                this.weight = weight;
+                this.age = age;
+                this.birthday = birthday;
+                this.gender = gender;
+                this.unknownGenderSupported = unknownGenderSupported;
+                this.vo2Supported = vo2Supported;
+                this.vo2Max = vo2Max;
+                this.vo2Time = vo2Time;
+                this.precisionWeightSupported = precisionWeightSupported;
+            }
+
+            public int getHeight() {
+                if (this.height > 0)
+                    return this.height;
+                return DEFAULT_HEIGHT;
+            }
+
+            public float getWeight() {
+                if (this.weight > 0)
+                    return this.weight;
+                return DEFAULT_WEIGHT;
+            }
+
+            public int getAge() {
+                return age;
+            }
+
+            public int getBirthday() {
+                return birthday;
+            }
+
+            public byte getGender() {
+                return gender;
+            }
+
+            public boolean isUnknownGenderSupported() {
+                return unknownGenderSupported;
+            }
+
+            public boolean isVo2Supported() {
+                return vo2Supported;
+            }
+
+            public int getVo2Max() {
+                return vo2Max;
+            }
+
+            public int getVo2Time() {
+                return vo2Time;
+            }
+
+            public boolean isPrecisionWeightSupported() {
+                return precisionWeightSupported;
+            }
+
+            public boolean isGenderValid() {
+                return this.gender == GENDER_MALE || this.gender == GENDER_FEMALE || this.gender == GENDER_UNKNOWN;
+            }
+
+            public boolean isBirthdayValid() {
+                return this.birthday > 0;
+            }
+
+        }
+
+        public static class Request extends HuaweiPacket {
+            public Request(ParamsProvider paramsProvider, UserInfoData info) {
+                super(paramsProvider);
+
+                this.serviceId = FitnessData.id;
+                this.commandId = id;
+
+                int height = info.getHeight();
+                float weight = info.getWeight();
+
+                byte bmiWalk = (byte) Math.round(UserInfoData.WALK_CF * height);
+                byte bmiRun = (byte) Math.round(UserInfoData.RUN_CF * height);
+
+                this.tlv = new HuaweiTLV()
+                        .put(0x01, (byte) height)
+                        .put(0x02, (byte) weight)
+                        .put(0x03, (byte) info.getAge());
+                if (info.isBirthdayValid())
+                    this.tlv.put(0x04, info.getBirthday());
+                if (info.isGenderValid()) {
+                    if (info.unknownGenderSupported || info.gender != UserInfoData.GENDER_UNKNOWN)
+                        this.tlv.put(0x05, info.gender);
+                }
+                this.tlv.put(0x06, bmiWalk);
+                this.tlv.put(0x07, bmiRun);
+
+                if (info.vo2Supported) {
+                    this.tlv.put(0x08, info.getVo2Max());
+                    this.tlv.put(0x09, info.getVo2Time());
+                }
+
+                if (info.precisionWeightSupported) {
+                    int precisionWeight = Math.round(weight * 100f);
+                    this.tlv.put(0x0b, precisionWeight);
+                }
+
+            }
+        }
+    }
+
+    public static class MessageCount {
+        public static final byte sleepId = 0x0C;
+        public static final byte stepId = 0x0A;
+
+        public static class Request extends HuaweiPacket {
+            public Request(
+                    ParamsProvider paramsProvider,
+                    byte commandId,
+                    int start,
+                    int end
+            ) {
+                super(paramsProvider);
+
+                this.serviceId = FitnessData.id;
+                this.commandId = commandId;
+
+                this.tlv = new HuaweiTLV()
+                        .put(0x81)
+                        .put(0x03, start)
+                        .put(0x04, end);
+
+                this.complete = true;
+            }
+        }
+
+        public static class Response extends HuaweiPacket {
+            public short count;
+
+            public Response(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+            }
+
+            @Override
+            public void parseTlv() throws ParseException {
+                this.count = this.tlv.getObject(0x81).getShort(0x02);
+                this.complete = true;
+            }
+        }
+    }
+
+    public static class MessageData {
+        public static final byte sleepId = 0x0D;
+        public static final byte stepId = 0x0B;
+
+        public static class Request extends HuaweiPacket {
+            public Request(ParamsProvider paramsProvider, byte commandId, short count) {
+                super(paramsProvider);
+
+                this.serviceId = FitnessData.id;
+                this.commandId = commandId;
+
+                this.tlv = new HuaweiTLV()
+                        .put(0x81, new HuaweiTLV()
+                                .put(0x02, count)
+                        );
+
+                this.complete = true;
+            }
+        }
+
+        public static class SleepResponse extends HuaweiPacket {
+            public static class SubContainer {
+                public byte type;
+                public byte[] timestamp;
+
+                @Override
+                public String toString() {
+                    return "SubContainer{" +
+                            "type=" + type +
+                            ", timestamp=" + Arrays.toString(timestamp) +
+                            '}';
+                }
+            }
+
+            public short number;
+            public List<SubContainer> containers;
+
+            public SleepResponse(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+
+                this.serviceId = FitnessData.id;
+                this.commandId = sleepId;
+            }
+
+            @Override
+            public void parseTlv() throws ParseException {
+                HuaweiTLV container = this.tlv.getObject(0x81);
+                List<HuaweiTLV> subContainers = container.getObjects(0x83);
+
+                this.number = container.getShort(0x02);
+                this.containers = new ArrayList<>();
+                for (HuaweiTLV subContainerTlv : subContainers) {
+                    SubContainer subContainer = new SubContainer();
+                    subContainer.type = subContainerTlv.getByte(0x04);
+                    subContainer.timestamp = subContainerTlv.getBytes(0x05);
+                    this.containers.add(subContainer);
+                }
+            }
+        }
+
+        public static class StepResponse extends HuaweiPacket {
+            public static class SubContainer {
+                public static class TV {
+                    public final byte bitmap;
+                    public final byte tag;
+                    public final short value;
+
+                    public TV(byte bitmap, byte tag, short value) {
+                        this.bitmap = bitmap;
+                        this.tag = tag;
+                        this.value = value;
+                    }
+
+                    @Override
+                    public String toString() {
+                        return "TV{" +
+                                "bitmap=" + bitmap +
+                                ", tag=" + tag +
+                                ", value=" + value +
+                                '}';
+                    }
+                }
+
+                /*
+                 * Data directly from packet
+                 */
+                public byte timestampOffset;
+                public byte[] data;
+
+                /*
+                 * Inferred data
+                 */
+                public int timestamp;
+
+                public List<TV> parsedData = null;
+                public String parsedDataError = "";
+
+                public int steps = -1;
+                public int calories = -1;
+                public int distance = -1;
+                public int heartrate = -1;
+
+                public int spo = -1;
+
+                public List<TV> unknownTVs = null;
+
+                @Override
+                public String toString() {
+                    return "SubContainer{" +
+                            "timestampOffset=" + timestampOffset +
+                            ", data=" + Arrays.toString(data) +
+                            ", timestamp=" + timestamp +
+                            ", parsedData=" + parsedData +
+                            ", parsedDataError='" + parsedDataError + '\'' +
+                            ", steps=" + steps +
+                            ", calories=" + calories +
+                            ", distance=" + distance +
+                            ", spo=" + spo +
+                            ", unknownTVs=" + unknownTVs +
+                            '}';
+                }
+            }
+
+            public short number;
+            public int timestamp;
+            public List<SubContainer> containers;
+
+            private static final List<Byte> singleByteTagListBitmap1 = new ArrayList<>();
+
+            static {
+                singleByteTagListBitmap1.add((byte) 0x20);
+                singleByteTagListBitmap1.add((byte) 0x40);
+            }
+
+            public StepResponse(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+
+                this.serviceId = FitnessData.id;
+                this.commandId = stepId;
+            }
+
+            @Override
+            public void parseTlv() throws ParseException {
+                HuaweiTLV container = this.tlv.getObject(0x81);
+                List<HuaweiTLV> subContainers = container.getObjects(0x84);
+
+                this.number = container.getShort(0x02);
+                this.timestamp = container.getInteger(0x03);
+                this.containers = new ArrayList<>();
+                for (HuaweiTLV subContainerTlv : subContainers) {
+                    SubContainer subContainer = new SubContainer();
+                    subContainer.timestampOffset = subContainerTlv.getByte(0x05);
+                    subContainer.timestamp = this.timestamp + 60 * subContainer.timestampOffset;
+                    subContainer.data = subContainerTlv.getBytes(0x06);
+                    parseData(subContainer, subContainer.data);
+                    this.containers.add(subContainer);
+                }
+            }
+
+            private static void parseData(SubContainer returnValue, byte[] data) {
+                int i = 0;
+
+                if (data.length <= 0) {
+                    returnValue.parsedData = null;
+                    returnValue.parsedDataError = "Data is missing feature bitmap.";
+                    return;
+                }
+                byte featureBitmap1 = data[i++];
+
+                byte featureBitmap2 = 0;
+                if ((featureBitmap1 & 128) != 0) {
+                    if (data.length <= i) {
+                        returnValue.parsedData = null;
+                        returnValue.parsedDataError = "Data is missing second feature bitmap.";
+                        return;
+                    }
+                    featureBitmap2 = data[i++];
+                }
+
+                returnValue.parsedData = new ArrayList<>();
+                returnValue.unknownTVs = new ArrayList<>();
+
+                // The greater than zero check is because Java is always signed, so we only check 7 bits
+                for (byte bitToCheck = 1; bitToCheck > 0; bitToCheck <<= 1) {
+                    if ((featureBitmap1 & bitToCheck) != 0) {
+                        short value;
+
+                        if (singleByteTagListBitmap1.contains(bitToCheck)) {
+                            if (data.length - 1 < i) {
+                                returnValue.parsedData = null;
+                                returnValue.parsedDataError = "Data is too short for selected features.";
+                                return;
+                            }
+
+                            value = data[i++];
+                        } else {
+                            if (data.length - 2 < i) {
+                                returnValue.parsedData = null;
+                                returnValue.parsedDataError = "Data is too short for selected features.";
+                                return;
+                            }
+
+                            value = (short) ((data[i++] & 0xFF) << 8 | (data[i++] & 0xFF));
+                        }
+
+                        // The bitToCheck is used as tag, which may not be optimal, but works
+                        SubContainer.TV tv = new SubContainer.TV((byte) 1, bitToCheck, value);
+                        returnValue.parsedData.add(tv);
+
+                        if (bitToCheck == 0x02)
+                            returnValue.steps = value;
+                        else if (bitToCheck == 0x04)
+                            returnValue.calories = value;
+                        else if (bitToCheck == 0x08)
+                            returnValue.distance = value;
+                        else if (bitToCheck == 0x40)
+                            returnValue.heartrate = value;
+                        else
+                            returnValue.unknownTVs.add(tv);
+                    }
+                }
+
+                if (featureBitmap2 != 0) {
+                    // We want to check 8 bits here, and java is java, so we use a short
+                    for (short bitToCheck = 1; bitToCheck < 0x0100; bitToCheck <<= 1) {
+                        if ((featureBitmap2 & bitToCheck) != 0) {
+                            if (data.length - 1 < i) {
+                                returnValue.parsedData = null;
+                                returnValue.parsedDataError = "Data is too short for selected features.";
+                                return;
+                            }
+
+                            byte value = data[i++];
+
+                            SubContainer.TV tv = new SubContainer.TV((byte) 2, (byte) bitToCheck, value);
+                            returnValue.parsedData.add(tv);
+
+                            if (bitToCheck == 0x01)
+                                returnValue.spo = value;
+                            else
+                                returnValue.unknownTVs.add(tv);
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    public static class FitnessTotals {
+        public static final byte id = 0x03;
+
+        public static class Request extends HuaweiPacket {
+            public Request(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+
+                this.serviceId = FitnessData.id;
+                this.commandId = id;
+
+                this.tlv = new HuaweiTLV()
+                        .put(0x01);
+
+                this.complete = true;
+            }
+        }
+
+        public static class Response extends HuaweiPacket {
+
+            public int totalSteps = 0;
+            public int totalCalories = 0;
+            public int totalDistance = 0;
+
+            public Response(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+
+                this.serviceId = FitnessData.id;
+                this.commandId = id;
+            }
+
+            @Override
+            public void parseTlv() throws ParseException {
+                HuaweiTLV container = this.tlv.getObject(0x81);
+                List<HuaweiTLV> containers = container.getObjects(0x83);
+
+                for (HuaweiTLV tlv : containers) {
+                    if (tlv.contains(0x05))
+                        totalSteps += tlv.getInteger(0x05);
+                    if (tlv.contains(0x06))
+                        totalCalories += tlv.getShort(0x06);
+                    if (tlv.contains(0x07))
+                        totalDistance += tlv.getInteger(0x07);
+                }
+
+                this.complete = true;
+            }
+        }
+    }
+
+    public static class ActivityReminder {
+        public static final byte id = 0x07;
+
+        public static class Request extends HuaweiPacket {
+            public Request(
+                    ParamsProvider paramsProvider,
+                    boolean longSitSwitch,
+                    byte longSitInterval,
+                    byte[] longSitStart,
+                    byte[] longSitEnd,
+                    byte cycle
+            ) {
+                super(paramsProvider);
+
+                this.serviceId = FitnessData.id;
+                this.commandId = id;
+
+                this.tlv = new HuaweiTLV()
+                        .put(0x81, new HuaweiTLV()
+                                .put(0x02, longSitSwitch)
+                                .put(0x03, longSitInterval)
+                                .put(0x04, longSitStart)
+                                .put(0x05, longSitEnd)
+                                .put(0x06, cycle)
+                        );
+
+                this.complete = true;
+            }
+        }
+    }
+
+    public static class DeviceReportThreshold {
+        public static final byte id = 0xe;
+
+        public static class Request extends HuaweiPacket {
+            public Request(ParamsProvider paramsProvider, List<HuaweiReportThreshold> thresholds) {
+                super(paramsProvider);
+
+                this.serviceId = FitnessData.id;
+                this.commandId = id;
+
+
+                HuaweiTLV subTlv = new HuaweiTLV();
+                for (HuaweiReportThreshold th : thresholds) {
+                    subTlv.put(0x02, th.getBytes());
+                }
+                this.tlv = new HuaweiTLV().put(0x81, subTlv);
+                this.complete = true;
+            }
+        }
+    }
+
+    public static class HeartRateZoneConfigPacket {
+        // It can use two IDs with basically the same format.
+        public static final byte id_simple = 0x13;
+        public static final byte id_extended = 0x21;
+
+        public static class Request extends HuaweiPacket {
+            private Request(
+                    ParamsProvider paramsProvider,
+                    byte id,
+                    HeartRateZonesConfig heartRateZonesConfig
+            ) {
+                super(paramsProvider);
+
+                this.serviceId = FitnessData.id;
+                this.commandId = id;
+
+                HuaweiTLV subTlv = new HuaweiTLV().
+                        put(0x08, heartRateZonesConfig.getWarningEnable());
+
+                if (
+                        heartRateZonesConfig.hasValidMHRData() &&
+                        heartRateZonesConfig.getWarningHRLimit() > 0 &&
+                        heartRateZonesConfig.getMaxHRThreshold() > 0
+                ) {
+                    subTlv
+                            .put(0x09, (byte) heartRateZonesConfig.getWarningHRLimit())
+                            .put(0x02, (byte) heartRateZonesConfig.getMHRWarmUp())
+                            .put(0x03, (byte) heartRateZonesConfig.getMHRFatBurning())
+                            .put(0x04, (byte) heartRateZonesConfig.getMHRAerobic())
+                            .put(0x05, (byte) heartRateZonesConfig.getMHRAnaerobic())
+                            .put(0x06, (byte) heartRateZonesConfig.getMHRExtreme())
+                            .put(0x07, (byte) heartRateZonesConfig.getMaxHRThreshold())
+                            .put(0x0b, (byte) heartRateZonesConfig.getMaxHRThreshold());
+                }
+
+                if (id == id_extended && heartRateZonesConfig.hasValidHRRData()) {
+                    subTlv
+                            .put(0x0d, (byte) heartRateZonesConfig.getHRRBasicAerobic())
+                            .put(0x0e, (byte) heartRateZonesConfig.getHRRAdvancedAerobic())
+                            .put(0x0f, (byte) heartRateZonesConfig.getHRRLactate())
+                            .put(0x10, (byte) heartRateZonesConfig.getHRRBasicAnaerobic())
+                            .put(0x11, (byte) heartRateZonesConfig.getHRRAdvancedAnaerobic());
+                }
+
+                if (id == id_extended && heartRateZonesConfig.getRestHeartRate() > 0) {
+                    subTlv
+                            .put(0x0a, (byte) heartRateZonesConfig.getCalculateMethod())
+                            .put(0x0c, (byte) heartRateZonesConfig.getRestHeartRate());
+                }
+
+                this.tlv = new HuaweiTLV().put(0x81, subTlv);
+
+                this.complete = true;
+            }
+
+            public static Request requestSimple(ParamsProvider paramsProvider, HeartRateZonesConfig heartRateZonesConfig) {
+                return new Request(paramsProvider, id_simple, heartRateZonesConfig);
+            }
+
+            public static Request requestExtended(ParamsProvider paramsProvider, HeartRateZonesConfig heartRateZonesConfig) {
+                return new Request(paramsProvider, id_extended, heartRateZonesConfig);
+            }
+        }
+    }
+
+    public static class TruSleep {
+        public static final byte id = 0x16;
+
+        public static class Request extends HuaweiPacket {
+            public Request(ParamsProvider paramsProvider, boolean truSleepSwitch) {
+                super(paramsProvider);
+
+                this.serviceId = FitnessData.id;
+                this.commandId = id;
+
+                this.tlv = new HuaweiTLV()
+                        .put(0x01, truSleepSwitch);
+
+                this.complete = true;
+            }
+        }
+    }
+
+    public static class EnableAutomaticHeartrate {
+        public static final byte id = 0x17;
+
+        public static class Request extends HuaweiPacket {
+            public Request(ParamsProvider paramsProvider, boolean enableAutomaticHeartrate) {
+                super(paramsProvider);
+
+                this.serviceId = FitnessData.id;
+                this.commandId = id;
+
+                this.tlv = new HuaweiTLV()
+                        .put(0x01, enableAutomaticHeartrate);
+
+                this.isEncrypted = true;
+                this.complete = true;
+            }
+        }
+    }
+
+    public static class NotifyRestHeartRate {
+        public static final byte id = 0x23;
+
+        public static class Request extends HuaweiPacket {
+            public Request(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+
+                this.serviceId = FitnessData.id;
+                this.commandId = id;
+
+                this.tlv = new HuaweiTLV()
+                        .put(0x01, 0x01);
+
+                this.complete = true;
+            }
+        }
+    }
+
+    public static class EnableAutomaticSpo {
+        public static final byte id = 0x24;
+
+        public static class Request extends HuaweiPacket {
+            public Request(ParamsProvider paramsProvider, boolean enableAutomaticSpo) {
+                super(paramsProvider);
+
+                this.serviceId = FitnessData.id;
+                this.commandId = id;
+
+                this.tlv = new HuaweiTLV()
+                        .put(0x01, enableAutomaticSpo);
+
+                this.isEncrypted = true;
+                this.complete = true;
+            }
+        }
+    }
+
+    public static class RunPaceConfig {
+        public static final byte id = 0x28;
+
+        public static class Request extends HuaweiPacket {
+            public Request(ParamsProvider paramsProvider, final HuaweiRunPaceConfig runPaceConfig) {
+                super(paramsProvider);
+
+                this.serviceId = FitnessData.id;
+                this.commandId = id;
+
+                this.tlv = new HuaweiTLV()
+                        .put(0x01, (short) runPaceConfig.getZone1JogMin())
+                        .put(0x02, (short) runPaceConfig.getZone2MarathonMin())
+                        .put(0x03, (short) runPaceConfig.getZone3LactateThresholdMin())
+                        .put(0x04, (short) runPaceConfig.getZone4AnaerobicMin())
+                        .put(0x05, (short) runPaceConfig.getZone5HIITRunMin())
+                        .put(0x06, (short) runPaceConfig.getZone5HIITRunMax());
+                this.complete = true;
+            }
+        }
+
+        public static class Response extends HuaweiPacket {
+
+            public boolean isOk;
+
+            public Response(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+                this.serviceId = FitnessData.id;
+                this.commandId = id;
+            }
+
+            @Override
+            public void parseTlv() throws ParseException {
+                isOk = this.tlv.getInteger(0x7f) == 0x000186A0;
+            }
+        }
+
+    }
+
+    public static class MediumToStrengthThreshold {
+        public static final byte id = 0x29;
+
+        public static class Request extends HuaweiPacket {
+            public Request(ParamsProvider paramsProvider,
+                           byte walkRun,
+                           byte climb,
+                           byte heartRate,
+                           byte cycleSpeed,
+                           byte sample,
+                           byte countLength,
+                           int walkRunSpeed,
+                           int walkRunWithHeartRate) {
+                super(paramsProvider);
+
+                this.serviceId = FitnessData.id;
+                this.commandId = id;
+
+                if (walkRun < 0x00 || walkRun > 0xc8) walkRun = 0x6E;
+                if (climb < 0x0 || climb > 0xc8) climb = 0x3c;
+                if (heartRate < 0x0 || heartRate > 0x64) heartRate = 0x40;
+                if (cycleSpeed < 0x0 || cycleSpeed > 0xff) cycleSpeed = 0x50;
+                if (sample < 0x1 || sample > 0xa) sample = 0x3;
+                if (countLength < 0x1 || countLength > 0xa) countLength = 0x5;
+                if (countLength < sample) countLength = sample;
+
+                this.tlv = new HuaweiTLV()
+                        .put(0x01, walkRun)
+                        .put(0x02, climb)
+                        .put(0x03, heartRate)
+                        .put(0x04, cycleSpeed)
+                        .put(0x05, sample)
+                        .put(0x06, countLength);
+                if (walkRunSpeed != -1) {
+                    this.tlv.put(0x07, (byte) walkRunSpeed);
+                }
+                if (walkRunWithHeartRate != -1) {
+                    this.tlv.put(0x08, (byte) walkRunWithHeartRate);
+                }
+                this.complete = true;
+            }
+        }
+    }
+
+    public static class SkinTemperatureMeasurement {
+        public static final byte id = 0x2a;
+
+        public static class Request extends HuaweiPacket {
+            public Request(ParamsProvider paramsProvider, boolean temperatureSwitch) {
+                super(paramsProvider);
+
+                this.serviceId = FitnessData.id;
+                this.commandId = id;
+
+                this.tlv = new HuaweiTLV()
+                        .put(0x01, (byte) 0x01)
+                        .put(0x02, temperatureSwitch);
+
+                this.complete = true;
+            }
+        }
+    }
+
+    public static class Type {
+        // TODO: enum?
+
+        public static final byte goal = 0x01;
+        public static final byte motion = 0x00;
+        public static final byte data = 0x01;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/GpsAndTime.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/GpsAndTime.java
new file mode 100644
index 0000000..d2b1c16
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/GpsAndTime.java
@@ -0,0 +1,238 @@
+/*  Copyright (C) 2024 Vitalii Tomin, Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+
+package nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiTLV;
+
+public class GpsAndTime {
+    public static final byte id = 0x18;
+
+    public static class GpsParameters {
+        public static final byte id = 0x01;
+
+        public static class Request extends HuaweiPacket {
+            public Request(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+
+                this.serviceId = GpsAndTime.id;
+                this.commandId = id;
+
+                this.tlv = new HuaweiTLV().put(0x81);
+
+                this.isEncrypted = true;
+                this.complete = true;
+            }
+        }
+
+        public static class Response extends HuaweiPacket {
+            public byte supportBitmap; // Supported bitmap, parsed into the booleans below
+            public byte format = 0; // 1 to send a single location per message (id 3), 2 for multiple
+            public byte locationsPerMessage = 0; // Only for format 2, the number of locations to send per message
+            public byte threshold = 0;
+
+            public boolean supportsSpeed;
+            public boolean supportsDistance;
+            public boolean supportsAltitude;
+            public boolean supportsTotalDistance;
+            public boolean supportsLatLon;
+            public boolean supportsMarsLatLon;
+            public boolean supportsDirection;
+            public boolean supportsPrecision;
+
+            public Response(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+            }
+
+            @Override
+            public void parseTlv() throws ParseException {
+                HuaweiTLV subTlv = this.tlv.getObject(0x81);
+
+                supportBitmap = subTlv.getByte(0x02);
+                supportsSpeed = (supportBitmap & 0x01) != 0;
+                supportsDistance = (supportBitmap & 0x02) != 0;
+                supportsAltitude = (supportBitmap & 0x04) != 0;
+                supportsTotalDistance = (supportBitmap & 0x08) != 0;
+                supportsLatLon = (supportBitmap & 0x10) != 0;
+                supportsMarsLatLon = (supportBitmap & 0x20) != 0;
+                supportsDirection = (supportBitmap & 0x40) != 0;
+                supportsPrecision = (supportBitmap & 0x80) != 0;
+
+                if (subTlv.contains(0x03))
+                    format = subTlv.getByte(0x03);
+                if (subTlv.contains(0x04))
+                    locationsPerMessage = subTlv.getByte(0x04);
+                if (subTlv.contains(0x05))
+                    threshold = subTlv.getByte(0x05);
+            }
+        }
+    }
+
+    public static class GpsStatus {
+        public static final byte id = 0x02;
+
+        public static class Request extends HuaweiPacket {
+            public Request(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+
+                this.serviceId = GpsAndTime.id;
+                this.commandId = id;
+
+                // Currently only sends "OK" value
+                this.tlv = new HuaweiTLV().put(0x7f, 0x186A0);
+
+                this.isEncrypted = true;
+                this.complete = true;
+            }
+        }
+
+        public static class Response extends HuaweiPacket {
+            public boolean enableGps;
+
+            public Response(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+            }
+
+            @Override
+            public void parseTlv() throws ParseException {
+                enableGps = this.tlv.getByte(0x01) == 0x01;
+            }
+        }
+    }
+
+    public static class GpsData {
+        public static final byte id = 0x03;
+
+        public static class Request extends HuaweiPacket {
+            public static class GpsDataContainer {
+                public boolean hasSpeed = false;
+                public short speed;
+
+                public boolean hasDistance = false;
+                public int distance;
+
+                public boolean hasAltitude = false;
+                public short altitude;
+
+                public boolean hasTotalDistance = false;
+                public int totalDistance;
+
+                public boolean hasStartTime = false;
+                public int startTime;
+
+                public boolean hasEndTime = false;
+                public int endTime;
+
+                public boolean hasLatLon = false;
+                public double lat;
+                public double lon;
+
+                public boolean hasMarsLatLon = false;
+                public double marsLat;
+                public double marsLon;
+
+                public boolean hasBearing = false;
+                public double bearing;
+
+                public boolean hasAccuracy = false;
+                public double accuracy;
+            }
+
+            public Request(
+                    ParamsProvider paramsProvider,
+                    List<GpsDataContainer> gpsDataList
+            ) {
+                super(paramsProvider);
+
+                this.serviceId = GpsAndTime.id;
+                this.commandId = id;
+
+                HuaweiTLV subTlv = new HuaweiTLV();
+                for (GpsDataContainer gpsData : gpsDataList) {
+                    HuaweiTLV gpsTlv = new HuaweiTLV();
+                    if (gpsData.hasSpeed)
+                        gpsTlv.put(0x03, gpsData.speed);
+                    if (gpsData.hasDistance)
+                        gpsTlv.put(0x04, gpsData.distance);
+                    if (gpsData.hasAltitude)
+                        gpsTlv.put(0x05, gpsData.altitude);
+                    if (gpsData.hasTotalDistance)
+                        gpsTlv.put(0x06, gpsData.totalDistance);
+                    if (gpsData.hasStartTime)
+                        gpsTlv.put(0x07, gpsData.startTime);
+                    if (gpsData.hasEndTime)
+                        gpsTlv.put(0x08, gpsData.endTime);
+                    if (gpsData.hasLatLon) {
+                        gpsTlv.put(0x09, gpsData.lat);
+                        gpsTlv.put(0x0a, gpsData.lon);
+                    }
+                    if (gpsData.hasMarsLatLon) {
+                        gpsTlv.put(0x0b, gpsData.marsLat);
+                        gpsTlv.put(0x0c, gpsData.marsLon);
+                    }
+                    if (gpsData.hasBearing)
+                        gpsTlv.put(0x0d, gpsData.bearing);
+                    if (gpsData.hasAccuracy)
+                        gpsTlv.put(0x0e, gpsData.accuracy);
+                    subTlv.put(0x82, gpsTlv);
+                }
+                this.tlv = new HuaweiTLV().put(0x81, subTlv);
+
+                this.isEncrypted = true;
+                this.complete = true;
+            }
+        }
+
+        public static class Response extends HuaweiPacket {
+            boolean shouldStop;
+
+            public Response(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+            }
+
+            @Override
+            public void parseTlv() throws ParseException {
+                // If the response is not 0x000186A0, sending should be stopped
+                shouldStop = this.tlv.getInteger(0x7f) != 0x000186A0;
+            }
+        }
+    }
+
+    public static class CurrentGPSRequest extends HuaweiPacket {
+        public static final byte id = 0x07;
+        public CurrentGPSRequest (
+                ParamsProvider paramsProvider,
+                int timestamp,
+                double lat,
+                double lon
+        ) {
+            super(paramsProvider);
+
+            this.serviceId = GpsAndTime.id;
+            this.commandId = id;
+            this.tlv = new HuaweiTLV()
+                    .put(0x01, timestamp)
+                    .put(0x02, lon)
+                    .put(0x03, lat);
+            this.isEncrypted = true;
+            this.complete = true;
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/LocaleConfig.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/LocaleConfig.java
new file mode 100644
index 0000000..cd6e0bb
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/LocaleConfig.java
@@ -0,0 +1,72 @@
+/*  Copyright (C) 2024 Damien Gaignon, Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiTLV;
+
+public class LocaleConfig {
+    public static final byte id = 0x0C;
+
+    public static class SetLanguageSetting extends HuaweiPacket {
+        public static final byte id = 0x01;
+
+        public SetLanguageSetting(
+                ParamsProvider paramsProvider,
+                byte[] locale,
+                byte measurement
+        ) {
+            super(paramsProvider);
+
+            this.serviceId = LocaleConfig.id;
+            this.commandId = id;
+
+            this.tlv = new HuaweiTLV()
+                    .put(0x01, locale)
+                    .put(0x02, measurement);
+
+            this.complete = true;
+        }
+    }
+
+    public static class MeasurementSystem {
+        // TODO: enum?
+
+        public static final byte metric = 0x00;
+        public static final byte imperial = 0x01;
+    }
+
+    public static class SetTemperatureUnitSetting extends HuaweiPacket {
+        public static final byte id = 0x05;
+
+        public SetTemperatureUnitSetting(
+                ParamsProvider paramsProvider,
+                byte isFahrenheit
+        ) {
+            super(paramsProvider);
+
+            this.serviceId = LocaleConfig.id;
+            this.commandId = id;
+
+            this.tlv = new HuaweiTLV()
+                    .put(0x01, isFahrenheit);
+
+            this.complete = true;
+        }
+    }
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/Menstrual.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/Menstrual.java
new file mode 100644
index 0000000..a1210e3
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/Menstrual.java
@@ -0,0 +1,75 @@
+/*  Copyright (C) 2024 Damien Gaignon
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiTLV;
+
+public class Menstrual {
+    public static final byte id = 0x32;
+
+    public static class ModifyTime {
+        public static final byte id = 0x02;
+
+        public static class Request extends HuaweiPacket {
+            public Request(ParamsProvider paramsProvider, int errorCode, long time) {
+                super(paramsProvider);
+
+                this.serviceId = Menstrual.id;
+                this.commandId = id;
+
+                this.tlv = new HuaweiTLV();
+                if (errorCode == 0) {
+                    this.tlv.put(0x01, time);
+                } else {
+                    this.tlv.put(0x7f, (int)0x249F1);
+                }
+                this.complete = true;
+            }
+        }
+
+        public static class Response extends HuaweiPacket {
+            public Response(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+
+                this.serviceId = Menstrual.id;
+                this.commandId = id;
+            }
+
+            @Override
+            public void parseTlv() throws ParseException {
+                // Do not know data yet
+            }
+        }
+    }
+
+    public static class CapabilityRequest extends HuaweiPacket {
+        public static final byte id = 0x05;
+
+        public CapabilityRequest(ParamsProvider paramsProvider) {
+            super(paramsProvider);
+
+            this.serviceId = Menstrual.id;
+            this.commandId = id;
+
+            this.tlv = new HuaweiTLV()
+                    .put(0x01, (byte)0x02);
+
+            this.complete = true;
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/MusicControl.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/MusicControl.java
new file mode 100644
index 0000000..d2d054b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/MusicControl.java
@@ -0,0 +1,555 @@
+/*  Copyright (C) 2024 Damien Gaignon, Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets;
+
+import static nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiMusicUtils.parseFormatBits;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiMusicUtils;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiTLV;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceMusic;
+
+public class MusicControl {
+    public static final byte id = 0x25;
+
+
+    // TODO: should this be in HuaweiConstants?
+    public static final int successValue = 0x000186A0;
+
+    public static class MusicStatusRequest extends HuaweiPacket {
+        public MusicStatusRequest(ParamsProvider paramsProvider, byte commandId, int returnValue) {
+            super(paramsProvider);
+
+            this.serviceId = MusicControl.id;
+            this.commandId = commandId;
+            this.tlv = new HuaweiTLV()
+                    .put(0x7F, returnValue);
+            this.isEncrypted = true;
+            this.complete = true;
+        }
+    }
+
+    public static class MusicStatusResponse extends HuaweiPacket {
+        public static final byte id = 0x01;
+
+        public int status = -1;
+
+        public MusicStatusResponse(ParamsProvider paramsProvider) {
+            super(paramsProvider);
+
+            this.serviceId = MusicControl.id;
+            this.commandId = id;
+        }
+
+        @Override
+        public void parseTlv() throws ParseException {
+            if (this.tlv.contains(0x7F) && this.tlv.getBytes(0x7F).length == 4)
+                this.status = this.tlv.getInteger(0x7F);
+        }
+    }
+
+    public static class MusicInfo {
+        public static final byte id = 0x02;
+
+        public static class Request extends HuaweiPacket {
+            public Request(
+                    ParamsProvider paramsProvider,
+                    String artistName,
+                    String songName,
+                    byte playState,
+                    byte maxVolume,
+                    byte currentVolume
+            ) {
+                super(paramsProvider);
+                this.serviceId = MusicControl.id;
+                this.commandId = id;
+                this.tlv = new HuaweiTLV()
+                        .put(0x01, artistName)
+                        .put(0x02, songName)
+                        .put(0x03, playState)
+                        .put(0x04, maxVolume)
+                        .put(0x05, currentVolume);
+                this.complete = true;
+            }
+        }
+
+        public static class Response extends HuaweiPacket {
+            public boolean ok = false;
+            public String error = "No input has been parsed yet";
+
+            public Response(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+
+                this.serviceId = MusicControl.id;
+                this.commandId = id;
+
+                this.isEncrypted = true;
+            }
+
+            @Override
+            public void parseTlv() throws ParseException {
+                if (this.tlv.contains(0x7F)) {
+                    if (this.tlv.getInteger(0x7F) == successValue) {
+                        this.ok = true;
+                        this.error = "";
+                    } else {
+                        this.ok = false;
+                        this.error = "Music information error code: " + Integer.toHexString(this.tlv.getInteger(0x7F));
+                    }
+                } else {
+                    this.ok = false;
+                    this.error = "Music information response no status tag";
+                }
+            }
+        }
+    }
+
+    public static class Control {
+        public static final byte id = 0x03;
+
+        public static class Response extends HuaweiPacket {
+            public enum Button {
+                Unknown,
+                Play,
+                Pause,
+                Previous,
+                Next,
+                Volume_up,
+                Volume_down
+            }
+
+            public boolean buttonPresent = false;
+            public byte rawButton = 0x00;
+            public boolean volumePresent = false;
+            public byte volume = 0x00;
+
+            public Button button = null;
+
+            public Response(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+
+                this.serviceId = MusicControl.id;
+                this.commandId = id;
+
+                this.isEncrypted = false;
+            }
+
+            @Override
+            public void parseTlv() throws ParseException {
+                if (this.tlv.contains(0x01)) {
+                    this.buttonPresent = true;
+                    // Only grab the lowest byte
+                    byte[] bytes = this.tlv.getBytes(0x01);
+                    this.rawButton = bytes[bytes.length - 1];
+                    switch (this.rawButton) {
+                        case 1:
+                            this.button = Button.Play;
+                            break;
+                        case 2:
+                            this.button = Button.Pause;
+                            break;
+                        case 3:
+                            this.button = Button.Previous;
+                            break;
+                        case 4:
+                            this.button = Button.Next;
+                            break;
+                        case 5:
+                            this.button = Button.Volume_up;
+                            break;
+                        case 6:
+                            this.button = Button.Volume_down;
+                            break;
+                        case 64:
+                            // Unknown button on Huawei Band 4
+
+                        case 100:
+                            // Seems like exit from music control screen to other screen
+                            this.buttonPresent = false;
+                        default:
+                            this.button = Button.Unknown;
+                    }
+                }
+
+                if (this.tlv.contains(0x02)) {
+                    this.volumePresent = true;
+                    // Only grab the lowest byte
+                    byte[] bytes = this.tlv.getBytes(0x02);
+                    this.volume = bytes[bytes.length - 1];
+                }
+            }
+        }
+    }
+
+    public static class UploadMusicFileInfo {
+        public static final int id = 0x09;
+
+        public static class UploadMusicFileInfoRequest extends HuaweiPacket {
+            public short songIndex;
+            public String songFileName;
+
+            public UploadMusicFileInfoRequest(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+            }
+
+            @Override
+            public void parseTlv() throws ParseException {
+                this.songIndex = this.tlv.getShort(0x01);
+                this.songFileName = this.tlv.getString(0x02);
+            }
+        }
+
+        public static class UploadMusicFileInfoResponse extends HuaweiPacket {
+            public UploadMusicFileInfoResponse(ParamsProvider paramsProvider, short songIndex, String songName, String songArtist) {
+                super(paramsProvider);
+
+                this.serviceId = MusicControl.id;
+                this.commandId = id;
+
+                this.tlv = new HuaweiTLV().put(0x01, songIndex).put(0x03, songName).put(0x04, songArtist);
+
+                this.complete = true;
+            }
+        }
+    }
+
+    public static class MusicInfoParams {
+        public static final byte id = 0x04;
+
+        public static class Request extends HuaweiPacket {
+
+            public Request(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+                this.serviceId = MusicControl.id;
+                this.commandId = id;
+
+                this.tlv = new HuaweiTLV()
+                        .put(0x01)
+                        .put(0x02)
+                        .put(0x03);
+
+                this.complete = true;
+            }
+        }
+
+        public static class Response extends HuaweiPacket {
+            public HuaweiMusicUtils.MusicCapabilities params = new HuaweiMusicUtils.MusicCapabilities();
+
+            public int frameCount = 0;
+            public List<HuaweiMusicUtils.PageStruct> pageStruct = null;
+
+            public Response(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+            }
+
+            @Override
+            public void parseTlv() throws ParseException {
+                this.frameCount = this.tlv.getAsInteger(0x01);
+
+                if (this.tlv.contains(0x02))
+                    params.availableSpace = this.tlv.getAsInteger(0x02);
+                if (this.tlv.contains(0x03)) {
+                    byte[] formatBits = this.tlv.getBytes(0x03);
+                    params.supportedFormats = parseFormatBits(formatBits);
+                }
+                if (this.tlv.contains(0x04))
+                    params.maxMusicCount = this.tlv.getAsInteger(0x04);
+                if (this.tlv.contains(0x05))
+                    params.currentMusicCount = this.tlv.getAsInteger(0x05);
+
+                if (this.tlv.contains(0x86)) {
+                    this.pageStruct = new ArrayList<>();
+                    List<HuaweiTLV> subTlvs = this.tlv.getObject(0x86).getObjects(0x87);
+                    for (HuaweiTLV subTlv : subTlvs) {
+                        HuaweiMusicUtils.PageStruct pageStruct = new HuaweiMusicUtils.PageStruct();
+                        if (subTlv.contains(0x08))
+                            pageStruct.startFrame = subTlv.getShort(0x08);
+                        if (subTlv.contains(0x09))
+                            pageStruct.endFrame = subTlv.getShort(0x09);
+                        if (subTlv.contains(0x0a))
+                            pageStruct.count = subTlv.getShort(0x0a);
+                        if (subTlv.contains(0x0b))
+                            pageStruct.hashCode = subTlv.getBytes(0x0b);
+                        this.pageStruct.add(pageStruct);
+                    }
+                }
+            }
+        }
+    }
+
+    public static class MusicList {
+        public static final byte id = 0x05;
+
+        public static class Request extends HuaweiPacket {
+            public Request(ParamsProvider paramsProvider, short startFrame, short endIndex) {
+                super(paramsProvider);
+                this.serviceId = MusicControl.id;
+                this.commandId = id;
+                this.tlv = new HuaweiTLV()
+                        .put(0x01, startFrame)
+                        .put(0x04, endIndex);
+            }
+        }
+
+        public static class Response extends HuaweiPacket {
+
+            public short startFrame = 0;
+            public short endIndex = 0;
+
+            public List<GBDeviceMusic> musicList;
+            public Response (ParamsProvider paramsProvider) {
+                super(paramsProvider);
+            }
+
+            @Override
+            public void parseTlv() throws HuaweiPacket.ParseException {
+                if(tlv.contains(0x1))
+                    startFrame = tlv.getShort(0x1);
+                if(tlv.contains(0x4))
+                    endIndex = tlv.getShort(0x4);
+                musicList = new ArrayList<>();
+                if(this.tlv.contains(0x82)) {
+                    for (HuaweiTLV subTlv : this.tlv.getObject(0x82).getObjects(0x83)) {
+                        int index = subTlv.getAsInteger(0x4);
+                        String title = subTlv.getString(0x5);
+                        String artist = subTlv.getString(0x6);
+                        String fileName = subTlv.getString(0x7);
+                        musicList.add(new GBDeviceMusic(index, title, artist, fileName));
+                    }
+                }
+            }
+        }
+    }
+
+    public static class MusicPlaylists {
+        public static final byte id = 0x06;
+
+        public static class Request extends HuaweiPacket {
+            public Request(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+                this.serviceId = MusicControl.id;
+                this.commandId = id;
+                this.tlv = new HuaweiTLV()
+                        .put(0x01);
+            }
+        }
+
+        public static class Response extends HuaweiPacket {
+
+            public static class PlaylistData {
+                public int id;
+                public String name;
+                public int frameCount;
+            }
+
+            public List<PlaylistData> playlists = new ArrayList<>();
+
+            public Response (ParamsProvider paramsProvider) {
+                super(paramsProvider);
+            }
+
+            @Override
+            public void parseTlv() throws HuaweiPacket.ParseException {
+                if(this.tlv.contains(0x81)) {
+                    for (HuaweiTLV subTlv : this.tlv.getObject(0x81).getObjects(0x82)) {
+                        PlaylistData data = new PlaylistData();
+                        data.id = subTlv.getAsInteger(0x3);
+                        data.name = subTlv.getString(0x4);
+                        data.frameCount = subTlv.getAsInteger(0x5);
+                        playlists.add(data);
+                    }
+                }
+            }
+        }
+    }
+
+    public static class MusicPlaylistMusics {
+        public static final byte id = 0x07;
+
+        public static class Request extends HuaweiPacket {
+            public Request(ParamsProvider paramsProvider, short playlist, short index) {
+                super(paramsProvider);
+                this.serviceId = MusicControl.id;
+                this.commandId = id;
+                this.tlv = new HuaweiTLV()
+                        .put(0x01, playlist)
+                        .put(0x02, index);
+            }
+        }
+
+        public static class Response extends HuaweiPacket {
+
+            public int id = -1;
+            public int index = -1;
+            public ArrayList<Integer> musicIds = null;
+
+            public Response (ParamsProvider paramsProvider) {
+                super(paramsProvider);
+            }
+
+            @Override
+            public void parseTlv() throws HuaweiPacket.ParseException {
+                if(this.tlv.contains(0x1))
+                    id = tlv.getAsInteger(0x1);
+                if(this.tlv.contains(0x2))
+                    index = tlv.getAsInteger(0x2);
+
+                if(this.tlv.contains(0x3)) {
+                    musicIds = new ArrayList<>();
+                    ByteBuffer dt = ByteBuffer.wrap(this.tlv.getBytes(0x3));
+                    while (dt.hasRemaining())
+                        musicIds.add((int) dt.getShort());
+                }
+            }
+        }
+    }
+
+    public static class MusicOperation {
+        public static final byte id = 0x08;
+
+        public static class Request extends HuaweiPacket {
+            public Request(ParamsProvider paramsProvider, int operation, int playlistIndex, String playlistName, ArrayList<Integer> musicIds) {
+                super(paramsProvider);
+                this.serviceId = MusicControl.id;
+                this.commandId = id;
+                this.tlv = new HuaweiTLV()
+                        .put(0x01, (byte)operation);
+
+                if(operation == 1 || operation == 2 || operation == 3  || operation == 4) {
+                    this.tlv.put(0x02, (short)playlistIndex);
+                }
+                if (operation == 0 || operation == 2) {
+                    this.tlv.put(0x03, playlistName);
+                }
+
+                if (operation == 3 || operation == 4) {
+                    ByteBuffer ids = ByteBuffer.allocate(musicIds.size() * 2);
+                    for (Integer id : musicIds) {
+                        ids.putShort(id.shortValue());
+                    }
+                    this.tlv.put(0x04, ids.array());
+                }
+            }
+        }
+
+        public static class Response extends HuaweiPacket {
+
+            public int operation = -1;
+            public int playlistIndex = -1;
+            public String playlistName;
+            public ArrayList<Integer> musicIds = null;
+            public int resultCode = -1;
+
+            public Response (ParamsProvider paramsProvider) {
+                super(paramsProvider);
+            }
+
+            @Override
+            public void parseTlv() throws HuaweiPacket.ParseException {
+                if(this.tlv.contains(0x7f))
+                    resultCode = tlv.getInteger(0x7f);
+                if(this.tlv.contains(0x1))
+                    operation = tlv.getByte(0x1);
+                if(this.tlv.contains(0x2))
+                    playlistIndex = tlv.getAsInteger(0x2);
+                if(this.tlv.contains(0x3))
+                    playlistName = tlv.getString(0x3);
+
+                if(this.tlv.contains(0x4)) {
+                    musicIds = new ArrayList<>();
+                    ByteBuffer dt = ByteBuffer.wrap(this.tlv.getBytes(0x4));
+                    while (dt.hasRemaining())
+                        musicIds.add((int) dt.getShort());
+                    }
+                }
+            }
+        }
+
+
+    public static class ExtendedMusicInfoParams {
+        public static final byte id = 0x0d;
+
+        public static class Request extends HuaweiPacket {
+
+            public Request(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+                this.serviceId = MusicControl.id;
+                this.commandId = id;
+
+                this.tlv = new HuaweiTLV()
+                        .put(0x01)
+                        .put(0x02)
+                        .put(0x03)
+                        .put(0x04)
+                        .put(0x05);
+
+                this.complete = true;
+            }
+        }
+
+        public static class Response extends HuaweiPacket {
+            public HuaweiMusicUtils.MusicCapabilities params = new HuaweiMusicUtils.MusicCapabilities();
+
+            public Response(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+            }
+
+            @Override
+            public void parseTlv() throws ParseException {
+                if (this.tlv.contains(0x01))
+                    params.availableSpace = this.tlv.getAsInteger(0x01);
+                if (this.tlv.contains(0x02)) {
+                    byte[] formatBits = this.tlv.getBytes(0x02);
+                    params.supportedFormats = parseFormatBits(formatBits);
+                }
+                if (this.tlv.contains(0x03))
+                    params.maxMusicCount = this.tlv.getAsInteger(0x03);
+                if (this.tlv.contains(0x04))
+                    params.maxPlaylistCount = this.tlv.getAsInteger(0x04);
+                if (this.tlv.contains(0x05))
+                    params.unknown = this.tlv.getByte(0x05);
+
+                if (this.tlv.contains(0x86)) {
+                    params.formatsRestrictions = new ArrayList<>();
+                    List<HuaweiTLV> subTlvs = this.tlv.getObject(0x86).getObjects(0x87);
+                    for (HuaweiTLV subTlv : subTlvs) {
+                        HuaweiMusicUtils.FormatRestrictions restriction = new HuaweiMusicUtils.FormatRestrictions();
+                        if (subTlv.contains(0x08))
+                            restriction.formatIdx = subTlv.getByte(0x08);
+                        if (subTlv.contains(0x09)) {
+                            String sampleRates = subTlv.getString(0x09);
+                            restriction.sampleRates = sampleRates.split(",");
+                        }
+                        if (subTlv.contains(0x0a))
+                            restriction.musicEncode = subTlv.getByte(0x0a);
+                        if (subTlv.contains(0x0b))
+                            restriction.bitrate = subTlv.getShort(0x0b);
+                        if (subTlv.contains(0x0c))
+                            restriction.channels = subTlv.getByte(0x0c);
+                        if (subTlv.contains(0x0d))
+                            restriction.unknownBitrate = subTlv.getShort(0x0b);
+                        params.formatsRestrictions.add(restriction);
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/Notifications.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/Notifications.java
new file mode 100644
index 0000000..22a1b8f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/Notifications.java
@@ -0,0 +1,300 @@
+/*  Copyright (C) 2024 Damien Gaignon, Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets;
+
+import java.nio.ByteBuffer;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiTLV;
+
+public class Notifications {
+    public static final byte id = 0x02;
+    public static final byte[] defaultConstraints = new byte[]{
+            0x00, 0x02,   0x00, 0x0F,
+            0x00, 0x00,   0x00, 0x02,   0x00, 0x1E,
+            0x00, 0x00,   0x00, 0x02,   0x00, 0x1E,
+            0x00, 0x00,   0x00, 0x02,   0x00, 0x1E
+    };
+
+    public static class NotificationActionRequest extends HuaweiPacket {
+        public static final byte id = 0x01;
+
+        // TODO: support other types of notifications
+        //        public static final int send = 0x01;
+        //        public static final int notificationId = 0x01;
+        //        public static final int notificationType = 0x02;
+        //        public static final int vibrate = 0x03;
+        //        public static final int payloadEmpty = 0x04;
+        //        public static final int imageHeight = 0x08;
+        //        public static final int imageWidth = 0x09;
+        //        public static final int imageColor = 0x0A;
+        //        public static final int imageData = 0x0B;
+        //        public static final int textType = 0x0E;
+        //        public static final int textEncoding = 0x0F;
+        //        public static final int textContent = 0x10;
+        //        public static final int sourceAppId = 0x11;
+        //        public static final int payloadText = 0x84;
+        //        public static final int payloadImage = 0x86;
+        //        public static final int textList = 0x8C;
+        //        public static final int textItem = 0x8D;
+
+        public NotificationActionRequest(
+                ParamsProvider paramsProvider,
+                short notificationId,
+                byte notificationType,
+                int encoding,
+                String titleContent,
+                String senderContent,
+                String bodyContent,
+                String sourceAppId
+        ) {
+            super(paramsProvider);
+
+            this.serviceId = Notifications.id;
+            this.commandId = id;
+
+            // TODO: Add notification information per type if necessary
+
+            this.tlv = new HuaweiTLV()
+                    .put(0x01, notificationId)
+                    .put(0x02, notificationType)
+                    .put(0x03, true); // This used to be vibrate, but doesn't work
+
+            HuaweiTLV subTlv = new HuaweiTLV();
+            if (titleContent != null && !titleContent.isEmpty())
+                subTlv.put(0x8D, new HuaweiTLV()
+                        .put(0x0E, (byte) TextType.title)
+                        .put(0x0F, (byte) encoding)
+                        .put(0x10, titleContent)
+                );
+
+            if (senderContent != null && !senderContent.isEmpty())
+                subTlv.put(0x8D, new HuaweiTLV()
+                        .put(0x0E, (byte) TextType.sender)
+                        .put(0x0F, (byte) encoding)
+                        .put(0x10, senderContent)
+                );
+
+            if (bodyContent != null && !bodyContent.isEmpty())
+                subTlv.put(0x8D, new HuaweiTLV()
+                        .put(0x0E, (byte) TextType.text)
+                        .put(0x0F, (byte) encoding)
+                        .put(0x10, bodyContent)
+                );
+
+            if (subTlv.length() != 0) {
+                this.tlv.put(0x84, new HuaweiTLV().put(0x8C, subTlv));
+            } else {
+                this.tlv.put(0x04);
+            }
+
+            if (sourceAppId != null)
+                this.tlv.put(0x11, sourceAppId);
+
+            this.complete = true;
+        }
+    }
+
+    public static class NotificationConstraints {
+        public static final byte id = 0x02;
+
+        public static class Request extends HuaweiPacket {
+            public Request(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+                this.serviceId = Notifications.id;
+                this.commandId = id;
+                this.tlv = new HuaweiTLV()
+                    .put(0x01);
+                this.complete = true;
+            }
+        }
+
+        public static class Response extends HuaweiPacket {
+            public ByteBuffer constraints;
+
+            public Response(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+                this.serviceId = Notifications.id;
+                this.commandId = id;
+                this.complete = true;
+            }
+
+            private void putByteBuffer(ByteBuffer bBuffer, byte position, byte[] value) {
+                ByteBuffer bValue = ByteBuffer.wrap(value);
+                if (bValue.capacity() == 4) {
+                    short highbytes = bValue.getShort();
+                    if (highbytes != 0) {
+                        throw new RuntimeException("This should not happen until very large messages allowed");
+                    }
+                    bBuffer.putShort(position, bValue.getShort());
+                } else if (bValue.capacity() == 2) {
+                    bBuffer.putShort(position, bValue.getShort());
+                } else {
+                    bBuffer.put(position, (byte) 0x00);
+                    bBuffer.put(position + 1, bValue.get());
+                }
+            }
+
+            @Override
+            public void parseTlv() throws ParseException {
+                this.constraints = ByteBuffer.allocate(22);
+                HuaweiTLV container = this.tlv
+                        .getObject(0x81)
+                        .getObject(0x82)
+                        .getObject(0x90);
+                for (HuaweiTLV subContainer : container.getObjects(0x91)) {
+                    if (subContainer.getByte(0x12) == 0x01) {
+                        putByteBuffer(constraints, NotificationConstraintsType.contentFormat, new byte[] {0x02}); //Always 0x02 even if gadget report 0x03
+                        putByteBuffer(constraints, NotificationConstraintsType.contentLength, subContainer.getBytes(0x14));
+                    }
+                    if (subContainer.getByte(0x12) == 0x05) {
+                        constraints.putShort(NotificationConstraintsType.yellowPagesSupport,(short)0x01);
+                        putByteBuffer(constraints, NotificationConstraintsType.yellowPagesFormat,subContainer.getBytes(0x13));
+                        putByteBuffer(constraints, NotificationConstraintsType.yellowPagesLength,subContainer.getBytes(0x14));
+                    }
+                    if (subContainer.getByte(0x12) == 0x06) {
+                        constraints.putShort(NotificationConstraintsType.contentSignSupport,(short)0x01);
+                        putByteBuffer(constraints, NotificationConstraintsType.contentSignFormat,subContainer.getBytes(0x13));
+                        putByteBuffer(constraints, NotificationConstraintsType.contentSignLength,subContainer.getBytes(0x14));
+                    }
+                    if (subContainer.getByte(0x12) == 0x07 ) {
+                        constraints.putShort(NotificationConstraintsType.incomingNumberSupport,(short)0x01);
+                        putByteBuffer(constraints, NotificationConstraintsType.incomingNumberFormat,subContainer.getBytes(0x13));
+                        putByteBuffer(constraints, NotificationConstraintsType.incomingNumberLength,subContainer.getBytes(0x14));
+                    }
+                }
+                constraints.rewind();
+            }
+        }
+    }
+
+    public static class NotificationConstraintsType {
+        // TODO: enum?
+        public static final byte contentFormat = 0x00;
+        public static final byte contentLength = 0x02;
+        public static final byte yellowPagesSupport = 0x04;
+        public static final byte yellowPagesFormat = 0x06;
+        public static final byte yellowPagesLength = 0x08;
+        public static final byte contentSignSupport = 0x0A;
+        public static final byte contentSignFormat = 0x0C;
+        public static final byte contentSignLength = 0x0E;
+        public static final byte incomingNumberSupport = 0x10;
+        public static final byte incomingNumberFormat = 0x12;
+        public static final byte incomingNumberLength = 0x14;
+    }
+
+    public static class NotificationType {
+        // TODO: enum?
+        public static final byte call = 0x01;
+        public static final byte sms = 0x02;
+        public static final byte weChat = 0x03;
+        public static final byte qq = 0x0B;
+        public static final byte stopNotification = 0x0C; // To stop showing a (call) notification
+        public static final byte missedCall = 0x0E;
+        public static final byte email = 0x0F;
+        public static final byte generic = 0x7F;
+    }
+
+    public static class TextType {
+        // TODO: enum?
+        public static final int text = 0x01;
+        public static final int sender = 0x02;
+        public static final int title = 0x03;
+        public static final int yellowPage = 0x05;
+        public static final int contentSign = 0x06;
+        public static final int flight = 0x07;
+        public static final int train = 0x08;
+        public static final int warmRemind = 0x09;
+        public static final int weather = 0x0A;
+    }
+
+    public static class NotificationStateRequest extends HuaweiPacket {
+        public static final byte id = 0x04;
+
+        public NotificationStateRequest(
+                ParamsProvider paramsProvider,
+                boolean status
+        ) {
+            super(paramsProvider);
+
+            this.serviceId = Notifications.id;
+            this.commandId = id;
+
+            this.tlv = new HuaweiTLV()
+                    .put(0x81, new HuaweiTLV()
+                            .put(0x02, status)
+                            .put(0x03, status)
+                    );
+
+            this.complete = true;
+        }
+    }
+
+    public static class NotificationCapabilities {
+        public static final byte id = 0x05;
+
+        public static class Request extends HuaweiPacket {
+            public Request(
+                    ParamsProvider paramsProvider
+            ){
+                super(paramsProvider);
+                this.serviceId = Notifications.id;
+                this.commandId = id;
+                this.tlv = new HuaweiTLV()
+                        .put(0x01);
+
+                this.complete = true;
+            }
+        }
+
+        public static class Response extends HuaweiPacket {
+            public byte capabilities = 0x00;
+
+            public Response(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+                this.serviceId = DeviceConfig.id;
+                this.commandId = id;
+            }
+
+            @Override
+            public void parseTlv() throws ParseException {
+                if (this.tlv.contains(0x01))
+                    this.capabilities = this.tlv.getByte(0x01);
+            }
+        }
+    }
+
+    public static class WearMessagePushRequest extends HuaweiPacket {
+        public static final byte id = 0x08;
+
+        public WearMessagePushRequest(
+                ParamsProvider paramsProvider,
+                boolean status
+        ) {
+            super(paramsProvider);
+
+            this.serviceId = Notifications.id;
+            this.commandId = id;
+
+            /* Value sent is the opposite of the switch status */
+            this.tlv = new HuaweiTLV()
+                    .put(0x01, !status);
+
+            this.complete = true;
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/P2P.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/P2P.java
new file mode 100644
index 0000000..30eabb5
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/P2P.java
@@ -0,0 +1,80 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiTLV;
+
+public class P2P {
+    public static final byte id = 0x34;
+
+    public static class P2PCommand {
+        public static final byte id = 0x01;
+
+        public static class Request extends HuaweiPacket {
+
+
+            public Request(ParamsProvider paramsProvider,
+                           byte cmdId,
+                           short sequenceId,
+                           String srcPackage,
+                           String dstPackage,
+                           String srcFingerprint,
+                           String dstFingerprint,
+                           byte[] sendData,
+                           int sendCode) {
+                super(paramsProvider);
+                this.serviceId = P2P.id;
+                this.commandId = id;
+
+                this.tlv = new HuaweiTLV()
+                           .put(0x01, cmdId)
+                        .put(0x02, sequenceId)
+                        .put(0x03, srcPackage)
+                        .put(0x04, dstPackage);
+                if(cmdId == 0x2) {
+                    this.tlv.put(0x05, srcFingerprint);
+                    this.tlv.put(0x06, dstFingerprint);
+                }
+                if(sendData != null && sendData.length > 0)
+                    this.tlv.put(0x07, sendData);
+                if(cmdId == 0x3) {
+                    this.tlv.put(0x08, sendCode);
+                }
+            }
+        }
+
+        public static class Response extends HuaweiPacket {
+            public byte cmdId;
+            public short sequenceId;
+            public String srcPackage;
+            public String dstPackage;
+            public String srcFingerprint = null;
+            public String dstFingerprint = null;
+            public byte[] respData = null;
+            public int respCode = 0;
+            public Response (ParamsProvider paramsProvider) {
+                super(paramsProvider);
+            }
+
+            @Override
+            public void parseTlv() throws HuaweiPacket.ParseException {
+                cmdId = this.tlv.getByte(0x01);
+                sequenceId = this.tlv.getShort(0x02);
+                srcPackage = this.tlv.getString(0x03);
+                dstPackage = this.tlv.getString(0x04);
+                if(this.tlv.contains(0x05))
+                    srcFingerprint = this.tlv.getString(0x05);
+                if(this.tlv.contains(0x06))
+                    dstFingerprint = this.tlv.getString(0x06);
+                if(this.tlv.contains(0x07))
+                    respData = this.tlv.getBytes(0x07);
+                // NOTE: P2P service uses different data types in responseCode(TLV 0x08).
+                // It sends byte from wearable but send integer to wearable
+                // So we have a change that other device can send different type.
+                if(this.tlv.contains(0x08))
+                    respCode = this.tlv.getByte(0x08);
+            }
+
+        }
+    }
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/Watchface.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/Watchface.java
new file mode 100644
index 0000000..674aa7b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/Watchface.java
@@ -0,0 +1,274 @@
+/*  Copyright (C) 2024 Vitalii Tomin
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+
+package nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiTLV;
+
+public class Watchface {
+    public static final byte id = 0x27;
+
+    public static class WatchfaceDeviceParams {
+        public String maxVersion = "";
+        public int width = 0;
+        public int height = 0;
+        public byte supportFileType = 1;
+        public byte sort = 1;
+        public String otherWatchfaceVersions = "";
+    }
+
+    public static class InstalledWatchfaceInfo {
+        public String fileName = "";
+        public String version = "";
+        public byte type = 0;
+        // bit 0 - is current
+        // bit 1 - is factory preset
+        // bit 2 - ???
+        // bit 3 - editable
+        // bit 4 - video
+        // bit 5 - photo
+        // bit 6 - tryout (trial version)
+        // bit 7 - kaleidoskop
+        public byte expandedtype = 0;
+
+        public InstalledWatchfaceInfo(HuaweiTLV tlv) throws HuaweiPacket.MissingTagException {
+            this.fileName = tlv.getString(0x03);
+            this.version = tlv.getString(0x04);
+            this.type = tlv.getByte(0x05);
+            if (tlv.contains(0x07)) // optional
+                this.expandedtype = tlv.getByte(0x07);
+        }
+
+        public boolean isCurrent() {
+            return (this.type & 1) == 1;
+        }
+        public boolean isFactory() {
+            return ((this.type  >> 1 )& 1) == 1;
+        }
+        public boolean isEditable() {
+            return ((this.type  >> 3 )& 1) == 1;
+        }
+        public boolean isVideo() {
+            return ((this.type  >> 4 )& 1) == 1;
+        }
+        public boolean isPhoto() {
+            return ((this.type  >> 5 )& 1) == 1;
+        }
+        public boolean isTryout() {
+            return ((this.type  >> 6 )& 1) == 1;
+        }
+        public boolean isKaleidoskop() {
+            return ((this.type  >> 7 )& 1) == 1;
+        }
+    }
+
+    public static class WatchfaceParams {
+        public static final byte id = 0x01;
+        public static class Request extends HuaweiPacket {
+
+            public Request(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+                this.serviceId = Watchface.id;
+                this.commandId = id;
+
+                this.tlv = new HuaweiTLV()
+                        .put(0x01)
+                        .put(0x02)
+                        .put(0x03)
+                        .put(0x04)
+                        .put(0x05)
+                        .put(0x0e)
+                        .put(0x0f);
+
+                this.complete = true;
+            }
+        }
+
+        public static class Response extends HuaweiPacket {
+            public WatchfaceDeviceParams params = new WatchfaceDeviceParams();
+            public Response (ParamsProvider paramsProvider) {
+                super(paramsProvider);
+            }
+
+            @Override
+            public void parseTlv() throws ParseException {
+                this.params.maxVersion = this.tlv.getString(0x01);
+                if (this.tlv.getBytes(0x02).length == 4)
+                    this.params.width = this.tlv.getInteger(0x02);
+                else
+                    this.params.width = this.tlv.getShort(0x02);
+                if (this.tlv.getBytes(0x03).length == 4)
+                    this.params.height = this.tlv.getInteger(0x03);
+                else
+                    this.params.height = this.tlv.getShort(0x03);
+                this.params.supportFileType = this.tlv.getByte(0x04);
+                this.params.sort = this.tlv.getByte(0x05);
+                if (this.tlv.contains(0x06))
+                    this.params.otherWatchfaceVersions = this.tlv.getString(0x06);
+            }
+        }
+    }
+
+    public static class DeviceWatchInfo {
+        public static final byte id = 0x02;
+
+        public static class Request extends HuaweiPacket {
+            public Request(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+                this.serviceId = Watchface.id;
+                this.commandId = id;
+                this.tlv = new HuaweiTLV()
+                        .put(0x01)
+                        .put(0x06, (byte) 0x03); //3 -overseas non-test, 2 - test, 1 -null?
+            }
+        }
+
+        public static class Response extends HuaweiPacket {
+
+            public List<InstalledWatchfaceInfo> watchfaceInfoList;
+            public Response (ParamsProvider paramsProvider) {
+                super(paramsProvider);
+            }
+
+            @Override
+            public void parseTlv() throws HuaweiPacket.ParseException {
+                watchfaceInfoList = new ArrayList<>();
+                if(this.tlv.contains(0x81)) {
+                    for (HuaweiTLV subTlv : this.tlv.getObject(0x81).getObjects(0x82)) {
+                        watchfaceInfoList.add(new Watchface.InstalledWatchfaceInfo(subTlv));
+                    }
+                }
+            }
+        }
+    }
+
+    public static class WatchfaceOperation {
+        public static final byte id = 0x03;
+
+        public static final byte  operationActive = 1;
+        public static final byte operationDelete = 2;
+
+        public static class Request extends HuaweiPacket {
+            public Request(ParamsProvider paramsProvider,
+                           String fileName, byte operation) {
+                super(paramsProvider);
+                this.serviceId = Watchface.id;
+                this.tlv = new HuaweiTLV()
+                        .put(0x01, fileName.split("_")[0])
+                        .put(0x02, fileName.split("_")[1])
+                        .put(0x03, operation);
+
+                this.commandId = id;
+            }
+        }
+
+        public static class Response extends HuaweiPacket {
+            public Response (ParamsProvider paramsProvider) {
+                super(paramsProvider);
+            }
+        }
+    }
+
+    public static class WatchfaceConfirm {
+        public static final byte id = 0x05;
+
+        public static class Request extends HuaweiPacket {
+            public Request(ParamsProvider paramsProvider,
+                           String fileName) {
+                super(paramsProvider);
+                this.serviceId = Watchface.id;
+                this.commandId = id;
+                String file = fileName.split("_")[0];
+                String version = "";
+                try {
+                    version = fileName.split("_")[1];
+                } catch (ArrayIndexOutOfBoundsException e) {
+
+                }
+                this.tlv = new HuaweiTLV()
+                        .put(0x01, file)
+                        .put(0x02, version)
+                        .put(0x7f, 0x000186A0);
+            }
+        }
+
+        public static class Response extends HuaweiPacket {
+            public static byte reportType = 0;
+            public static String fileName;
+            public Response (ParamsProvider paramsProvider) {
+                super(paramsProvider);
+            }
+
+            @Override
+            public void parseTlv() throws HuaweiPacket.ParseException {
+                String name = this.tlv.getString(0x01);
+                String version = this.tlv.getString(0x02);
+                if(this.tlv.contains(0x03)) {
+                    this.reportType = this.tlv.getByte(0x03);
+                }
+                this.fileName = name + "_" + version;
+            }
+
+        }
+    }
+
+    public static class WatchfaceNameInfo {
+        public static final byte id = 0x06;
+
+        public static class Request extends HuaweiPacket {
+            public Request(ParamsProvider paramsProvider,
+                           List<InstalledWatchfaceInfo> watchfaceList) {
+                super(paramsProvider);
+                this.serviceId = Watchface.id;
+                this.commandId = id;
+
+                HuaweiTLV tlvList = new HuaweiTLV();
+                for (InstalledWatchfaceInfo watchface : watchfaceList) {
+                    //TODO: ask name only for custom watchfaces
+                    HuaweiTLV wfTlv = new HuaweiTLV().put(0x04, watchface.fileName);
+                    tlvList.put(0x83, wfTlv);
+                }
+
+                this.tlv = new HuaweiTLV()
+                        .put(0x01, (byte) 0x01)
+                        .put(0x82, tlvList);
+
+            }
+        }
+
+        public static class Response extends HuaweiPacket {
+            public HashMap<String, String> watchFaceNames = new HashMap<String, String>();
+            public Response (ParamsProvider paramsProvider) {
+                super(paramsProvider);
+            }
+
+            @Override
+            public void parseTlv() throws HuaweiPacket.ParseException {
+                if(this.tlv.contains(0x82)) {
+                    for (HuaweiTLV subTlv : this.tlv.getObject(0x82).getObjects(0x83)) {
+                        watchFaceNames.put(subTlv.getString(0x04), subTlv.getString(0x05));
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/Weather.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/Weather.java
new file mode 100644
index 0000000..8f51423
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/Weather.java
@@ -0,0 +1,829 @@
+/*  Copyright (C) 2024 Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets;
+
+import androidx.annotation.NonNull;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiTLV;
+
+public class Weather {
+    public static final byte id = 0x0f;
+
+    public static class Settings {
+        // WeatherSupport
+        public boolean weatherSupported = false;
+        public boolean windSupported = false;
+        public boolean pm25Supported = false;
+        public boolean temperatureSupported = false;
+        public boolean locationNameSupported = false;
+        public boolean currentTemperatureSupported = false;
+        public boolean unitSupported = false;
+        public boolean airQualityIndexSupported = false;
+
+        // WeatherExtendedSupport
+        public boolean timeSupported = false;
+        public boolean sourceSupported = false;
+        public boolean weatherIconSupported = false;
+
+        // WeatherSunMoonSupport
+        public boolean sunRiseSetSupported = false;
+        public boolean moonPhaseSupported = false;
+
+        // ExpandCapabilityRequest
+        public boolean uvIndexSupported = false;
+    }
+
+    public enum WeatherIcon {
+        // Also used for the text on the watch
+        SUNNY,
+        CLOUDY,
+        OVERCAST,
+        SHOWERS,
+        THUNDERSTORMS,
+        THUNDER_AND_HAIL,
+        SLEET,
+        LIGHT_RAIN,
+        RAIN,
+        HEAVY_RAIN,
+        RAIN_STORM,
+        HEAVY_RAIN_STORMS,
+        SEVERE_RAIN_STORMS,
+        SNOW_FLURRIES,
+        LIGHT_SNOW,
+        SNOW,
+        HEAVY_SNOW,
+        SNOWSTORMS,
+        FOG,
+        FREEZING_RAIN,
+        DUST_STORM,
+        LIGHT_TO_MODERATE_RAIN,
+        MODERATE_TO_HEAVY_RAIN,
+        HEAVY_TO_SEVERE_RAIN,
+        HEAVY_TO_TORRENTIAL_RAIN,
+        SEVERE_TO_TORRENTIAL_RAIN,
+        LIGHT_TO_MODERATE_SNOW,
+        MODERATE_TO_HEAVY_SNOW,
+        HEAVY_SNOW_TO_BLIZZARD,
+        DUST,
+        SAND,
+        SANDSTORMS,
+        FREEZING, // misses small/non-moving icon
+        HOT, // misses small/non-moving icon
+        COLD, // misses small/non-moving icon
+        WINDY,
+        HAZY,
+        UNKNOWN // Good to have probably
+    }
+
+    private static byte iconToByte(WeatherIcon weatherIcon) {
+        switch (weatherIcon) {
+            case SUNNY:
+                return 0x00;
+            case CLOUDY:
+                return 0x01;
+            case OVERCAST:
+                return 0x02;
+            case SHOWERS:
+                return 0x03;
+            case THUNDERSTORMS:
+                return 0x04;
+            case THUNDER_AND_HAIL:
+                return 0x05;
+            case SLEET:
+                return 0x06;
+            case LIGHT_RAIN:
+                return 0x07;
+            case RAIN:
+                return 0x08;
+            case HEAVY_RAIN:
+                return 0x09;
+            case RAIN_STORM:
+                return 0x0a;
+            case HEAVY_RAIN_STORMS:
+                return 0x0b;
+            case SEVERE_RAIN_STORMS:
+                return 0x0c;
+            case SNOW_FLURRIES:
+                return 0x0d;
+            case LIGHT_SNOW:
+                return 0x0e;
+            case SNOW:
+                return 0x0f;
+            case HEAVY_SNOW:
+                return 0x10;
+            case SNOWSTORMS:
+                return 0x11;
+            case FOG:
+                return 0x12;
+            case FREEZING_RAIN:
+                return 0x13;
+            case DUST_STORM:
+                return 0x14;
+            case LIGHT_TO_MODERATE_RAIN:
+                return 0x15;
+            case MODERATE_TO_HEAVY_RAIN:
+                return 0x16;
+            case HEAVY_TO_SEVERE_RAIN:
+                return 0x17;
+            case HEAVY_TO_TORRENTIAL_RAIN:
+                return 0x18;
+            case SEVERE_TO_TORRENTIAL_RAIN:
+                return 0x19;
+            case LIGHT_TO_MODERATE_SNOW:
+                return 0x1a;
+            case MODERATE_TO_HEAVY_SNOW:
+                return 0x1b;
+            case HEAVY_SNOW_TO_BLIZZARD:
+                return 0x1c;
+            case DUST:
+                return 0x1d;
+            case SAND:
+                return 0x1e;
+            case SANDSTORMS:
+                return 0x1f;
+            case FREEZING:
+                return 0x20;
+            case HOT:
+                return 0x21;
+            case COLD:
+                return 0x22;
+            case WINDY:
+                return 0x23;
+            case HAZY:
+                return 0x35;
+            default:
+                return 0x63; // Any higher and the current weather breaks
+        }
+    }
+
+    private static WeatherIcon byteToIcon(byte weatherIcon) {
+        switch (weatherIcon) {
+            case 0x00:
+                return WeatherIcon.SUNNY;
+            case 0x01:
+                return WeatherIcon.CLOUDY;
+            case 0x02:
+                return WeatherIcon.OVERCAST;
+            case 0x03:
+                return WeatherIcon.SHOWERS;
+            case 0x04:
+                return WeatherIcon.THUNDERSTORMS;
+            case 0x05:
+                return WeatherIcon.THUNDER_AND_HAIL;
+            case 0x06:
+                return WeatherIcon.SLEET;
+            case 0x07:
+                return WeatherIcon.LIGHT_RAIN;
+            case 0x08:
+                return WeatherIcon.RAIN;
+            case 0x09:
+                return WeatherIcon.HEAVY_RAIN;
+            case 0x0a:
+                return WeatherIcon.RAIN_STORM;
+            case 0x0b:
+                return WeatherIcon.HEAVY_RAIN_STORMS;
+            case 0x0c:
+                return WeatherIcon.SEVERE_RAIN_STORMS;
+            case 0x0d:
+                return WeatherIcon.SNOW_FLURRIES;
+            case 0x0e:
+                return WeatherIcon.LIGHT_SNOW;
+            case 0x0f:
+                return WeatherIcon.SNOW;
+            case 0x10:
+                return WeatherIcon.HEAVY_SNOW;
+            case 0x11:
+                return WeatherIcon.SNOWSTORMS;
+            case 0x12:
+                return WeatherIcon.FOG;
+            case 0x13:
+                return WeatherIcon.FREEZING_RAIN;
+            case 0x14:
+                return WeatherIcon.DUST_STORM;
+            case 0x15:
+                return WeatherIcon.LIGHT_TO_MODERATE_RAIN;
+            case 0x16:
+                return WeatherIcon.MODERATE_TO_HEAVY_RAIN;
+            case 0x17:
+                return WeatherIcon.HEAVY_TO_SEVERE_RAIN;
+            case 0x18:
+                return WeatherIcon.HEAVY_TO_TORRENTIAL_RAIN;
+            case 0x19:
+                return WeatherIcon.SEVERE_TO_TORRENTIAL_RAIN;
+            case 0x1a:
+                return WeatherIcon.LIGHT_TO_MODERATE_SNOW;
+            case 0x1b:
+                return WeatherIcon.MODERATE_TO_HEAVY_SNOW;
+            case 0x1c:
+                return WeatherIcon.HEAVY_SNOW_TO_BLIZZARD;
+            case 0x1d:
+                return WeatherIcon.DUST;
+            case 0x1e:
+                return WeatherIcon.SAND;
+            case 0x1f:
+                return WeatherIcon.SANDSTORMS;
+            case 0x20:
+                return WeatherIcon.FREEZING;
+            case 0x21:
+                return WeatherIcon.HOT;
+            case 0x22:
+                return WeatherIcon.COLD;
+            case 0x23:
+                return WeatherIcon.WINDY;
+            case 0x35:
+                return WeatherIcon.HAZY;
+            default:
+                return WeatherIcon.UNKNOWN;
+        }
+    }
+
+    public enum HuaweiTemperatureFormat {
+        CELSIUS,
+        FAHRENHEIT
+    }
+
+    private static byte temperatureFormatToByte(HuaweiTemperatureFormat temperatureFormat) {
+        if (temperatureFormat == HuaweiTemperatureFormat.FAHRENHEIT)
+            return 1;
+        return 0;
+    }
+
+    public enum MoonPhase {
+        NEW_MOON,
+        WAXING_CRESCENT,
+        FIRST_QUARTER,
+        WAXING_GIBBOUS,
+        FULL_MOON,
+        WANING_GIBBOUS,
+        THIRD_QUARTER,
+        WANING_CRESCENT
+    }
+
+    public static MoonPhase degreesToMoonPhase(int degrees) {
+        final int leeway = 6; // Give some leeway for the new moon, first quarter, full moon, and third quarter
+        if (degrees < 0 || degrees > 360)
+            return null;
+        else if (degrees >= 360 - leeway || degrees <= leeway)
+            return MoonPhase.NEW_MOON;
+        else if (degrees < 90)
+            return MoonPhase.WAXING_CRESCENT;
+        else if (degrees <= 90 + leeway)
+            return MoonPhase.FIRST_QUARTER;
+        else if (degrees < 180 - leeway)
+            return MoonPhase.WAXING_GIBBOUS;
+        else if (degrees <= 180 + leeway)
+            return MoonPhase.FULL_MOON;
+        else if (degrees < 270 - leeway)
+            return MoonPhase.WANING_GIBBOUS;
+        else if (degrees <= 270 + leeway)
+            return MoonPhase.THIRD_QUARTER;
+        else
+            return MoonPhase.WANING_CRESCENT;
+    }
+
+    private static byte moonPhaseToByte (MoonPhase moonPhase) {
+        switch (moonPhase) {
+            case NEW_MOON:
+                return 1;
+            case WAXING_CRESCENT:
+                return 2;
+            case FIRST_QUARTER:
+                return 3;
+            case WAXING_GIBBOUS:
+                return 4;
+            case FULL_MOON:
+                return 5;
+            case WANING_GIBBOUS:
+                return 6;
+            case THIRD_QUARTER:
+                return 7;
+            case WANING_CRESCENT:
+                return 8;
+            default:
+                return -1;
+        }
+    }
+
+    private static MoonPhase byteToMoonPhase(byte moonPhase) {
+        switch (moonPhase) {
+            case 1:
+                return MoonPhase.NEW_MOON;
+            case 2:
+                return MoonPhase.WAXING_CRESCENT;
+            case 3:
+                return MoonPhase.FIRST_QUARTER;
+            case 4:
+                return MoonPhase.WAXING_GIBBOUS;
+            case 5:
+                return MoonPhase.FULL_MOON;
+            case 6:
+                return MoonPhase.WANING_GIBBOUS;
+            case 7:
+                return MoonPhase.THIRD_QUARTER;
+            case 8:
+                return MoonPhase.WANING_CRESCENT;
+            default:
+                return null;
+        }
+    }
+
+    public enum ErrorCode {
+        NETWORK_ERROR,
+        GPS_PERMISSION_ERROR,
+        WEATHER_DISABLED
+    }
+
+    private static byte errorCodeToByte(ErrorCode errorCode) {
+        switch (errorCode) {
+            case NETWORK_ERROR:
+                return 0;
+            case GPS_PERMISSION_ERROR:
+                return 1;
+            case WEATHER_DISABLED:
+                return 2;
+        }
+        throw new RuntimeException(); // Shouldn't happen
+    }
+
+    public static class CurrentWeatherRequest extends HuaweiPacket {
+        public static final byte id = 0x01;
+
+        public CurrentWeatherRequest(
+                ParamsProvider paramsProvider,
+                Settings settings,
+                WeatherIcon icon,
+                Byte windDirection,
+                Byte windSpeed,
+                Byte lowestTemperature,
+                Byte highestTemperature,
+                Short pm25, // TODO: might be float?
+                String locationName,
+                Byte currentTemperature,
+                HuaweiTemperatureFormat temperatureUnit,
+                Short airQualityIndex,
+                Integer observationTime,
+                Float uvIndex,
+                String sourceName
+        ) {
+            super(paramsProvider);
+
+            this.serviceId = Weather.id;
+            this.commandId = id;
+            this.tlv = new HuaweiTLV();
+
+            HuaweiTLV tlv81 = new HuaweiTLV();
+
+            if (icon != null && settings.weatherIconSupported) {
+                tlv81.put(0x02, iconToByte(icon));
+            }
+
+            if (settings.windSupported) {
+                short wind = 0;
+                if (windSpeed != null)
+                    wind = (short) windSpeed;
+
+                if (windDirection != null) {
+                    if (windDirection > 0)
+                        wind |= (short) (((short) (windDirection * 8 / 360)) << 8);
+                    else
+                        wind |= (short) (((short) (360 + windDirection) * 8 / 360) << 8);
+                }
+                tlv81.put(0x03, wind);
+            }
+
+            if (settings.weatherIconSupported || settings.windSupported)
+                this.tlv.put(0x81, tlv81);
+
+            if (lowestTemperature != null && highestTemperature != null && settings.temperatureSupported) {
+                this.tlv.put(0x85, new HuaweiTLV()
+                        .put(0x06, lowestTemperature)
+                        .put(0x07, highestTemperature)
+                );
+            }
+            if (windDirection != null && windSpeed != null && settings.windSupported)
+                this.tlv.put(0x03, (short) ((((short) windDirection) << 8) | ((short) windSpeed)));
+            if (pm25 != null && settings.pm25Supported)
+                this.tlv.put(0x04, pm25);
+            if (locationName != null && settings.locationNameSupported)
+                this.tlv.put(0x08, locationName);
+            if (currentTemperature != null && settings.currentTemperatureSupported)
+                this.tlv.put(0x09, currentTemperature);
+            if (temperatureUnit != null && settings.unitSupported)
+                this.tlv.put(0x0a, temperatureFormatToByte(temperatureUnit));
+            if (airQualityIndex != null && settings.airQualityIndexSupported)
+                this.tlv.put(0x0b, airQualityIndex);
+            if (observationTime != null && settings.timeSupported)
+                this.tlv.put(0x0c, observationTime);
+            if (sourceName != null && settings.sourceSupported)
+                this.tlv.put(0x0e, sourceName);
+            if (uvIndex != null && settings.uvIndexSupported)
+                this.tlv.put(0x0f, uvIndex.byteValue());
+
+            this.isEncrypted = true;
+            this.complete = true;
+        }
+    }
+
+    public static class WeatherSupport {
+        public static final byte id = 0x02;
+
+        public static class Request extends HuaweiPacket {
+            public Request(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+
+                this.serviceId = Weather.id;
+                this.commandId = id;
+                this.tlv = new HuaweiTLV().put(0x01);
+                this.isEncrypted = true;
+                this.complete = true;
+            }
+        }
+
+        public static class Response extends HuaweiPacket {
+            public byte supportedBitmap = 0;
+
+            public boolean weatherSupported = false;
+            public boolean windSupported = false;
+            public boolean pm25Supported = false;
+            public boolean temperatureSupported = false;
+            public boolean locationNameSupported = false;
+            public boolean currentTemperatureSupported = false;
+            public boolean unitSupported = false;
+            public boolean airQualityIndexSupported = false;
+
+            public Response(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+                this.serviceId = Weather.id;
+                this.commandId = id;
+            }
+
+            @Override
+            public void parseTlv() throws ParseException {
+                if (!this.tlv.contains(0x01))
+                    throw new MissingTagException(0x01);
+                this.supportedBitmap = this.tlv.getByte(0x01);
+
+                this.weatherSupported = (this.supportedBitmap & 0x01) != 0;
+                this.windSupported = (this.supportedBitmap & 0x02) != 0;
+                this.pm25Supported = (this.supportedBitmap & 0x04) != 0;
+                this.temperatureSupported = (this.supportedBitmap & 0x08) != 0;
+                this.locationNameSupported = (this.supportedBitmap & 0x10) != 0;
+                this.currentTemperatureSupported = (this.supportedBitmap & 0x20) != 0;
+                this.unitSupported = (this.supportedBitmap & 0x40) != 0;
+                this.airQualityIndexSupported = (this.supportedBitmap & 0x80) != 0;
+            }
+        }
+    }
+
+    public static class WeatherDeviceRequest extends HuaweiPacket {
+        public static final byte id = 0x04;
+
+        public WeatherDeviceRequest(ParamsProvider paramsProvider, int responseValue) {
+            super(paramsProvider);
+
+            this.serviceId = Weather.id;
+            this.commandId = id;
+            this.tlv = new HuaweiTLV().put(0x01, responseValue);
+            this.isEncrypted = false;
+            this.complete = true;
+        }
+    }
+
+    public static class WeatherUnitRequest extends HuaweiPacket {
+        public static final byte id = 0x05;
+
+        public WeatherUnitRequest(ParamsProvider paramsProvider, HuaweiTemperatureFormat temperatureFormat) {
+            super(paramsProvider);
+
+            this.serviceId = Weather.id;
+            this.commandId = id;
+            this.tlv = new HuaweiTLV().put(0x01, temperatureFormatToByte(temperatureFormat));
+            this.isEncrypted = true;
+            this.complete = true;
+        }
+    }
+
+    public static class WeatherExtendedSupport {
+        public static final byte id = 0x06;
+
+        public static class Request extends HuaweiPacket {
+            public Request(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+
+                this.serviceId = Weather.id;
+                this.commandId = id;
+                this.tlv = new HuaweiTLV().put(0x01);
+                this.isEncrypted = true;
+                this.complete = true;
+            }
+        }
+
+        public static class Response extends HuaweiPacket {
+            public short supportedBitmap = 0;
+
+            public boolean timeSupported = false;
+            public boolean sourceSupported = false;
+            public boolean weatherIconSupported = false;
+
+            public Response(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+                this.serviceId = Weather.id;
+                this.commandId = id;
+            }
+
+            @Override
+            public void parseTlv() throws ParseException {
+                if (!this.tlv.contains(0x01))
+                    throw new MissingTagException(0x01);
+                this.supportedBitmap = this.tlv.getShort(0x01);
+
+                this.timeSupported = (this.supportedBitmap & 0x01) != 0;
+                this.sourceSupported = (this.supportedBitmap & 0x02) != 0;
+                this.weatherIconSupported = (this.supportedBitmap & 0x04) != 0;
+            }
+        }
+    }
+
+    public static class WeatherErrorSimple {
+        public static final byte id = 0x07;
+
+        public static class Request extends HuaweiPacket {
+            public Request(ParamsProvider paramsProvider, ErrorCode errorCode) {
+                super(paramsProvider);
+
+                this.serviceId = Weather.id;
+                this.commandId = id;
+                this.tlv = new HuaweiTLV().put(0x01, errorCodeToByte(errorCode));
+                this.isEncrypted = true;
+                this.complete = true;
+            }
+        }
+    }
+
+    public static class WeatherForecastData {
+        public static final byte id = 0x08;
+
+        public static class TimeData {
+            public int timestamp;
+            public WeatherIcon icon;
+            public Byte temperature;
+
+            @NonNull
+            @Override
+            public String toString() {
+                String timestampStr = new Date(timestamp * 1000L).toString();
+                return "TimeData{" +
+                        "timestamp=" + timestamp +
+                        ", timestamp=" + timestampStr +
+                        ", icon=" + icon +
+                        ", temperature=" + temperature +
+                        '}';
+            }
+        }
+
+        public static class DayData {
+            public int timestamp;
+            public WeatherIcon icon;
+            public Byte highTemperature;
+            public Byte lowTemperature;
+            public Integer sunriseTime;
+            public Integer sunsetTime;
+            public Integer moonRiseTime;
+            public Integer moonSetTime;
+            public MoonPhase moonPhase;
+
+            @NonNull
+            @Override
+            public String toString() {
+                String timestampStr = new Date(timestamp * 1000L).toString();
+                return "DayData{" +
+                        "timestamp=" + timestamp +
+                        ", timestamp=" + timestampStr +
+                        ", icon=" + icon +
+                        ", highTemperature=" + highTemperature +
+                        ", lowTemperature=" + lowTemperature +
+                        ", sunriseTime=" + sunriseTime +
+                        ", sunsetTime=" + sunsetTime +
+                        ", moonRiseTime=" + moonRiseTime +
+                        ", moonSetTime=" + moonSetTime +
+                        ", moonPhase=" + moonPhase +
+                        '}';
+            }
+        }
+
+        public static class Request extends HuaweiPacket {
+            public Request(
+                    ParamsProvider paramsProvider,
+                    Settings settings,
+                    List<TimeData> timeDataList,
+                    List<DayData> dayDataList
+            ) {
+                super(paramsProvider);
+
+                this.serviceId = Weather.id;
+                this.commandId = id;
+                this.tlv = new HuaweiTLV();
+
+                if (timeDataList != null && !timeDataList.isEmpty()) {
+                    HuaweiTLV timeDataTlv = new HuaweiTLV();
+                    for (TimeData timeData : timeDataList) {
+                        HuaweiTLV timeTlv = new HuaweiTLV();
+                        timeTlv.put(0x03, timeData.timestamp);
+                        if (timeData.icon != null && settings.weatherIconSupported)
+                            timeTlv.put(0x04, iconToByte(timeData.icon));
+                        if (timeData.temperature != null && (settings.temperatureSupported || settings.currentTemperatureSupported))
+                            timeTlv.put(0x05, timeData.temperature);
+                        timeDataTlv.put(0x82, timeTlv);
+                    }
+                    this.tlv.put(0x81, timeDataTlv);
+                }
+
+                if (dayDataList != null && !dayDataList.isEmpty()) {
+                    HuaweiTLV dayDataTlv = new HuaweiTLV();
+                    for (DayData dayData : dayDataList) {
+                        HuaweiTLV dayTlv = new HuaweiTLV();
+                        dayTlv.put(0x12, dayData.timestamp);
+                        if (dayData.icon != null && settings.weatherIconSupported)
+                            dayTlv.put(0x13, iconToByte(dayData.icon));
+                        if (settings.temperatureSupported) {
+                            if (dayData.highTemperature != null)
+                                dayTlv.put(0x14, dayData.highTemperature);
+                            if (dayData.lowTemperature != null)
+                                dayTlv.put(0x15, dayData.lowTemperature);
+                        }
+                        if (settings.sunRiseSetSupported) {
+                            if (dayData.sunriseTime != null && dayData.sunriseTime != 0)
+                                dayTlv.put(0x16, dayData.sunriseTime);
+                            if (dayData.sunsetTime != null && dayData.sunsetTime != 0)
+                                dayTlv.put(0x17, dayData.sunsetTime);
+                            if (dayData.moonRiseTime != null && dayData.moonRiseTime != 0)
+                                dayTlv.put(0x1a, dayData.moonRiseTime);
+                            if (dayData.moonSetTime != null && dayData.moonSetTime != 0)
+                                dayTlv.put(0x1b, dayData.moonSetTime);
+                        }
+                        if (dayData.moonPhase != null && settings.moonPhaseSupported)
+                            dayTlv.put(0x1e, moonPhaseToByte(dayData.moonPhase));
+                        dayDataTlv.put(0x91, dayTlv);
+                    }
+                    this.tlv.put(0x90, dayDataTlv);
+                }
+
+                this.isEncrypted = true;
+                this.isSliced = true;
+                this.complete = true;
+            }
+        }
+
+        public static class OutgoingRequest extends HuaweiPacket {
+            List<TimeData> timeDataList;
+            List<DayData> dayDataList;
+
+            public OutgoingRequest(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+                this.complete = false;
+            }
+
+            @Override
+            public void parseTlv() throws ParseException {
+                timeDataList = new ArrayList<>(this.tlv.getObject(0x81).getObjects(0x82).size());
+                for (HuaweiTLV timeTlv : this.tlv.getObject(0x81).getObjects(0x82)) {
+                    TimeData timeData = new TimeData();
+                    timeData.timestamp = timeTlv.getInteger(0x03);
+                    timeData.icon = byteToIcon(timeTlv.getByte(0x04));
+                    timeData.temperature = timeTlv.getByte(0x05);
+                    timeDataList.add(timeData);
+                }
+
+                dayDataList = new ArrayList<>(this.tlv.getObject(0x90).getObjects(0x91).size());
+                for (HuaweiTLV dayTlv : this.tlv.getObject(0x90).getObjects(0x91)) {
+                    DayData dayData = new DayData();
+                    dayData.timestamp = dayTlv.getInteger(0x12);
+                    dayData.icon = byteToIcon(dayTlv.getByte(0x13));
+                    dayData.highTemperature = dayTlv.getByte(0x14);
+                    dayData.lowTemperature = dayTlv.getByte(0x15);
+                    dayData.sunriseTime = dayTlv.getInteger(0x16);
+                    dayData.sunsetTime = dayTlv.getInteger(0x17);
+                    dayData.moonRiseTime = dayTlv.getInteger(0x1a);
+                    dayData.moonSetTime = dayTlv.getInteger(0x1b);
+                    dayData.moonPhase = byteToMoonPhase(dayTlv.getByte(0x1e));
+                    dayDataList.add(dayData);
+                }
+            }
+        }
+    }
+
+    public static class WeatherStart {
+        public static final byte id = 0x09;
+
+        public static class Request extends HuaweiPacket {
+
+            public Request(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+
+                this.serviceId = Weather.id;
+                this.commandId = id;
+                this.tlv = new HuaweiTLV().put(0x01, (byte) 0x03); // TODO: find out what this means
+                this.isEncrypted = true;
+                this.complete = true;
+            }
+        }
+
+        public static class Response extends HuaweiPacket {
+            public int successCode = -1;
+            public boolean success = false;
+
+            public Response(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+                this.serviceId = Weather.id;
+                this.commandId = id;
+            }
+
+            @Override
+            public void parseTlv() throws ParseException {
+                this.successCode = this.tlv.getInteger(0x7f);
+                this.success = this.successCode == 0x000186A0 || this.successCode == 0x000186A3;
+            }
+        }
+    }
+
+    public static class WeatherSunMoonSupport {
+        public static final byte id = 0x0a;
+
+        public static class Request extends HuaweiPacket {
+            public Request(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+
+                this.serviceId = Weather.id;
+                this.commandId = id;
+                this.tlv = new HuaweiTLV().put(0x01);
+                this.isEncrypted = true;
+                this.complete = true;
+            }
+        }
+
+        public static class Response extends HuaweiPacket {
+            public int supportedBitmap = 0;
+
+            public boolean sunRiseSetSupported = false;
+            public boolean moonPhaseSupported = false;
+
+            public Response(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+                this.serviceId = Weather.id;
+                this.commandId = id;
+            }
+
+            @Override
+            public void parseTlv() throws ParseException {
+                if (!this.tlv.contains(0x01))
+                    throw new MissingTagException(0x01);
+                this.supportedBitmap = this.tlv.getInteger(0x01);
+
+                this.sunRiseSetSupported = (this.supportedBitmap & 0x01) != 0;
+                this.moonPhaseSupported = (this.supportedBitmap & 0x02) != 0;
+            }
+        }
+    }
+
+    public static class WeatherErrorExtended {
+        public static final byte id = 0x0c;
+
+        public static class Request extends HuaweiPacket {
+            public Request(ParamsProvider paramsProvider, ErrorCode errorCode, boolean serverError) {
+                super(paramsProvider);
+
+                this.serviceId = Weather.id;
+                this.commandId = id;
+
+                HuaweiTLV innerTlv = new HuaweiTLV();
+                innerTlv.put(0x02, errorCodeToByte(errorCode));
+                if (errorCode == ErrorCode.NETWORK_ERROR && serverError)
+                    innerTlv.put(0x03, (byte) 0x01);
+
+                this.tlv = new HuaweiTLV().put(0x81, innerTlv);
+
+                this.isEncrypted = true;
+                this.complete = true;
+            }
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/WorkMode.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/WorkMode.java
new file mode 100644
index 0000000..10d93e0
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/WorkMode.java
@@ -0,0 +1,57 @@
+/*  Copyright (C) 2024 Damien Gaignon, Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiTLV;
+
+public class WorkMode {
+    public static final byte id = 0x26;
+
+    /*
+     * public static class ModeStatus {
+     *     public static final byte id = 0x01;
+     *     public static final int autoDetectMode  = 0x01;
+     *     public static final int footWear  = 0x02;
+     * }
+     */
+
+    public static class SwitchStatusRequest extends HuaweiPacket {
+        public static final byte id = 0x02;
+        public static final int setStatus  = 0x01;
+
+        public SwitchStatusRequest(ParamsProvider paramsProvider, boolean autoWorkMode) {
+            super(paramsProvider);
+
+            this.serviceId = WorkMode.id;
+            this.commandId = id;
+
+            this.tlv = new HuaweiTLV()
+                    .put(0x01, autoWorkMode);
+
+            this.complete = true;
+        }
+    }
+
+    /*
+     * public static class FootWear {
+     *     public static final byte id = 0x03;
+     *     public static final int AutoDetectMode  = 0x01;
+     *     public static final int FootWear  = 0x02;
+     * }
+     */
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/Workout.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/Workout.java
new file mode 100644
index 0000000..19c604f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/Workout.java
@@ -0,0 +1,740 @@
+/*  Copyright (C) 2024 Damien Gaignon, Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiTLV;
+
+public class Workout {
+    public static final byte id = 0x17;
+
+    public static class WorkoutCount {
+        public static final byte id = 0x07;
+
+        public static class Request extends HuaweiPacket {
+            public Request(
+                    ParamsProvider paramsProvider,
+                    int start,
+                    int end
+            ) {
+                super(paramsProvider);
+
+                this.serviceId = Workout.id;
+                this.commandId = id;
+
+                this.tlv = new HuaweiTLV()
+                        .put(0x81, new HuaweiTLV()
+                                .put(0x03, start)
+                                .put(0x04, end)
+                        );
+
+                this.complete = true;
+            }
+        }
+
+        public static class Response extends HuaweiPacket {
+            public static class WorkoutNumbers {
+                public byte[] rawData;
+
+                public short workoutNumber;
+                public short dataCount;
+                public short paceCount;
+                public short segmentsCount = 0;
+
+            }
+
+            public short count;
+            public List<WorkoutNumbers> workoutNumbers;
+
+            public Response(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+            }
+
+            @Override
+            public void parseTlv() throws ParseException {
+                HuaweiTLV container = this.tlv.getObject(0x81);
+
+                this.count = container.getShort(0x02);
+                this.workoutNumbers = new ArrayList<>();
+
+                if (this.count == 0)
+                    return;
+
+                List<HuaweiTLV> subContainers = container.getObjects(0x85);
+                for (HuaweiTLV subContainerTlv : subContainers) {
+                    WorkoutNumbers workoutNumber = new WorkoutNumbers();
+                    workoutNumber.rawData = subContainerTlv.serialize();
+                    workoutNumber.workoutNumber = subContainerTlv.getShort(0x06);
+                    workoutNumber.dataCount = subContainerTlv.getShort(0x07);
+                    workoutNumber.paceCount = subContainerTlv.getShort(0x08);
+                    if(subContainerTlv.contains(0x09)) {
+                        workoutNumber.segmentsCount = subContainerTlv.getShort(0x09);
+                    }
+                    this.workoutNumbers.add(workoutNumber);
+                }
+
+                // Has to be sorted for the timestamp-based sync start that we use in the HuaweiSupportProvider
+                this.workoutNumbers.sort(Comparator.comparingInt(o -> o.workoutNumber));
+            }
+        }
+    }
+
+    public static class WorkoutTotals {
+        public static final byte id = 0x08;
+
+        public static class Request extends HuaweiPacket {
+
+            public Request(ParamsProvider paramsProvider, short number) {
+                super(paramsProvider);
+
+                this.serviceId = Workout.id;
+                this.commandId = id;
+
+                this.tlv = new HuaweiTLV().put(0x81, new HuaweiTLV()
+                        .put(0x02, number)
+                );
+
+                this.complete = true;
+            }
+        }
+
+        public static class Response extends HuaweiPacket {
+            public byte[] rawData;
+
+            public short number;
+            public byte status = -1; // TODO: enum?
+            public int startTime;
+            public int endTime;
+            public int calories = -1;
+            public int distance = -1;
+            public int stepCount = -1;
+            public int totalTime = -1;
+            public int duration = -1;
+            public byte type = -1; // TODO: enum?
+            public short strokes = -1;
+            public short avgStrokeRate = -1;
+            public short poolLength = -1; // In cm
+            public short laps = -1;
+            public short avgSwolf = -1;
+
+            public Integer maxAltitude = null;
+            public Integer minAltitude = null;
+            public Integer elevationGain = null;
+            public Integer elevationLoss = null;
+
+            public int workoutLoad = 0;
+            public int workoutAerobicEffect = 0;
+            public byte workoutAnaerobicEffect = -1;
+            public short recoveryTime = 0;
+
+            public byte minHeartRatePeak = 0;
+            public byte maxHeartRatePeak = 0;
+
+            public byte[] recoveryHeartRates = null;
+
+            public byte swimType = -1;
+
+            public int maxMET = 0;
+
+            public byte hrZoneType = -1;
+
+            public short runPaceZone1Min = -1;
+            public short runPaceZone2Min = -1;
+            public short runPaceZone3Min = -1;
+            public short runPaceZone4Min = -1;
+            public short runPaceZone5Min = -1;
+            public short runPaceZone5Max = -1;
+
+            public short runPaceZone1Time = -1;
+            public short runPaceZone2Time = -1;
+            public short runPaceZone3Time = -1;
+            public short runPaceZone4Time = -1;
+            public short runPaceZone5Time = -1;
+
+            public byte algType = 0;
+
+            public int trainingPoints = -1;
+
+            public Response(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+            }
+
+            @Override
+            public void parseTlv() throws ParseException {
+                HuaweiTLV container = this.tlv.getObject(0x81);
+
+                this.rawData = container.serialize();
+                this.number = container.getShort(0x02);
+                if (container.contains(0x03))
+                    this.status = container.getByte(0x03);
+                this.startTime = container.getInteger(0x04);
+                this.endTime = container.getInteger(0x05);
+
+                if (container.contains(0x06))
+                    this.calories = container.getInteger(0x06);
+                if (container.contains(0x07))
+                    this.distance = container.getInteger(0x07);
+                if (container.contains(0x08))
+                    this.stepCount = container.getInteger(0x08);
+                if (container.contains(0x09))
+                    this.totalTime = container.getInteger(0x09);
+                if (container.contains(0x0b))
+                    this.elevationGain = container.getInteger(0x0b);
+                if (container.contains(0x0c)) {
+                    byte[] hrData = container.getBytes(0x0c);
+                    minHeartRatePeak = hrData[0];
+                    maxHeartRatePeak = hrData[1];
+                }
+                if (container.contains(0x0d))
+                    this.workoutLoad = container.getInteger(0x0d);
+                if (container.contains(0x0e))
+                    this.workoutAerobicEffect = container.getInteger(0x0e);
+                if (container.contains(0x10))
+                    this.maxMET = container.getInteger(0x10);
+                if (container.contains(0x11))
+                    this.recoveryTime = container.getShort(0x11);
+                if (container.contains(0x12))
+                    this.duration = container.getInteger(0x12);
+                if (container.contains(0x14))
+                    this.type = container.getByte(0x14);
+                if (container.contains(0x15))
+                    this.swimType = container.getByte(0x15);
+                if (container.contains(0x16))
+                    this.strokes = container.getShort(0x16);
+                if (container.contains(0x17))
+                    this.avgStrokeRate = container.getShort(0x17);
+                if (container.contains(0x18))
+                    this.poolLength = container.getShort(0x18);
+                if (container.contains(0x19))
+                    this.laps = container.getShort(0x19);
+                if (container.contains(0x1a))
+                    this.avgSwolf = container.getShort(0x1a);
+                if (container.contains(0x1b))
+                    this.elevationLoss = container.getInteger(0x1b);
+                if (container.contains(0x1c))
+                    this.maxAltitude = container.getInteger(0x1c);
+                if (container.contains(0x1d))
+                    this.minAltitude = container.getInteger(0x1d);
+                if (container.contains(0x20))
+                    this.workoutAnaerobicEffect = container.getByte(0x20);
+                if (container.contains(0x24))
+                    this.hrZoneType = container.getByte(0x24);
+                if (container.contains(0x50))
+                    this.runPaceZone1Min = container.getShort(0x50);
+                if (container.contains(0x51))
+                    this.runPaceZone2Min = container.getShort(0x51);
+                if (container.contains(0x52))
+                    this.runPaceZone3Min = container.getShort(0x52);
+                if (container.contains(0x53))
+                    this.runPaceZone4Min = container.getShort(0x53);
+                if (container.contains(0x54))
+                    this.runPaceZone5Min = container.getShort(0x54);
+                if (container.contains(0x55))
+                    this.runPaceZone5Max = container.getShort(0x55);
+                if (container.contains(0x56))
+                    this.runPaceZone1Time = container.getShort(0x56);
+                if (container.contains(0x57))
+                    this.runPaceZone2Time = container.getShort(0x57);
+                if (container.contains(0x58))
+                    this.runPaceZone3Time = container.getShort(0x58);
+                if (container.contains(0x59))
+                    this.runPaceZone4Time = container.getShort(0x59);
+                if (container.contains(0x5a))
+                    this.runPaceZone5Time = container.getShort(0x5a);
+                if (container.contains(0x5d))
+                    this.algType = container.getByte(0x5d);
+                if (container.contains(0x63))
+                    this.trainingPoints = container.getShort(0x63);
+                if (container.contains(0x66))
+                    this.recoveryHeartRates = container.getBytes(0x66);
+            }
+        }
+    }
+
+    public static class WorkoutData {
+        public static final int id = 0x0a;
+
+        public static class Request extends HuaweiPacket {
+
+            public Request(
+                    ParamsProvider paramsProvider,
+                    short workoutNumber,
+                    short dataNumber
+            ) {
+                super(paramsProvider);
+
+                this.serviceId = Workout.id;
+                this.commandId = id;
+
+                this.tlv = new HuaweiTLV().put(0x81, new HuaweiTLV()
+                        .put(0x02, workoutNumber)
+                        .put(0x03, dataNumber)
+                );
+
+                this.complete = true;
+            }
+        }
+
+        public static class Response extends HuaweiPacket {
+            public static class Header {
+                public short workoutNumber;
+                public short dataNumber;
+                public int timestamp;
+                public byte interval;
+                public short dataCount;
+                public byte dataLength;
+                public short bitmap; // TODO: can this be enum-like?
+
+                @Override
+                public String toString() {
+                    return "Header{" +
+                            "workoutNumber=" + workoutNumber +
+                            ", dataNumber=" + dataNumber +
+                            ", timestamp=" + timestamp +
+                            ", interval=" + interval +
+                            ", dataCount=" + dataCount +
+                            ", dataLength=" + dataLength +
+                            ", bitmap=" + bitmap +
+                            '}';
+                }
+            }
+
+            public static class Data {
+                // If unknown data is encountered, the whole tlv will be in here so it can be parsed again later
+                public byte[] unknownData = null;
+
+                public byte heartRate = -1;
+                public short speed = -1;
+                public byte stepRate = -1;
+
+                public short cadence = -1;
+                public short stepLength = -1;
+                public short groundContactTime = -1;
+                public byte impact = -1;
+                public short swingAngle = -1;
+                public byte foreFootLanding = -1;
+                public byte midFootLanding = -1;
+                public byte backFootLanding = -1;
+                public byte eversionAngle = -1;
+
+                public short swolf = -1;
+                public short strokeRate = -1;
+
+                public short calories = -1;
+                public short cyclingPower = -1;
+                public short frequency = -1;
+                public Integer altitude = null;
+
+                public int timestamp = -1; // Calculated timestamp for this data point
+
+                @Override
+                public String toString() {
+                    return "Data{" +
+                            "unknownData=" + Arrays.toString(unknownData) +
+                            ", heartRate=" + heartRate +
+                            ", speed=" + speed +
+                            ", stepRate=" + stepRate +
+                            ", cadence=" + cadence +
+                            ", stepLength=" + stepLength +
+                            ", groundContactTime=" + groundContactTime +
+                            ", impact=" + impact +
+                            ", swingAngle=" + swingAngle +
+                            ", foreFootLanding=" + foreFootLanding +
+                            ", midFootLanding=" + midFootLanding +
+                            ", backFootLanding=" + backFootLanding +
+                            ", eversionAngle=" + eversionAngle +
+                            ", swolf=" + swolf +
+                            ", strokeRate=" + strokeRate +
+                            ", calories=" + calories +
+                            ", cyclingPower=" + cyclingPower +
+                            ", frequency=" + frequency +
+                            ", altitude=" + altitude +
+                            ", timestamp=" + timestamp +
+                            '}';
+                }
+            }
+
+            private final byte[] bitmapLengths = {1, 2, 1, 2, 2, 4, -1, 2, 2, 2};
+            private final byte[] innerBitmapLengths = {2, 2, 2, 1, 2, 1, 1, 1, 1, 2, 2, 1, 2, 2, 2, 2, 1, 1, 1, 2};
+
+            public short workoutNumber;
+            public short dataNumber;
+            public byte[] rawHeader;
+            public byte[] rawData;
+            public short innerBitmap;
+
+            public Header header;
+            public List<Data> dataList;
+
+            public Response(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+            }
+
+            /**
+             * This is to be able to easily reparse the error data, only accepts tlv bytes
+             *
+             * @param rawData The TLV bytes
+             */
+            public Response(byte[] rawData) throws ParseException {
+                super(null);
+                this.tlv = new HuaweiTLV().parse(rawData);
+                this.parseTlv();
+            }
+
+            @Override
+            public void parseTlv() throws ParseException {
+                HuaweiTLV container = this.tlv.getObject(0x81);
+
+                this.workoutNumber = container.getShort(0x02);
+                this.dataNumber = container.getShort(0x03);
+                this.rawHeader = container.getBytes(0x04);
+                this.rawData = container.getBytes(0x05); // TODO: not sure if 5 can also be omitted
+
+                if (container.contains(0x09))
+                    innerBitmap = container.getShort(0x09);
+                else
+                    innerBitmap = 0x01FF; // This seems to be the default
+
+                int innerDataLength = 0;
+                for (byte i = 0; i < innerBitmapLengths.length; i++) {
+                    if ((innerBitmap & (1 << i)) != 0) {
+                        innerDataLength += innerBitmapLengths[i];
+                    }
+                }
+
+                if (this.rawHeader.length != 14)
+                    throw new LengthMismatchException("Workout data header length mismatch.");
+
+                this.header = new Header();
+                ByteBuffer buf = ByteBuffer.wrap(this.rawHeader);
+                header.workoutNumber = buf.getShort();
+                header.dataNumber = buf.getShort();
+                header.timestamp = buf.getInt();
+                header.interval = buf.get();
+                header.dataCount = buf.getShort();
+                header.dataLength = buf.get();
+                header.bitmap = buf.getShort();
+
+                // Check data lengths from header
+                if (this.header.dataCount * this.header.dataLength != this.rawData.length)
+                    throw new LengthMismatchException("Workout data length mismatch with header.");
+
+                // Check data lengths from bitmap
+                int dataLength = 0;
+                for (byte i = 0; i < bitmapLengths.length; i++) {
+                    if ((header.bitmap & (1 << i)) != 0) {
+                        if (i == 6) {
+                            dataLength += innerDataLength;
+                        } else {
+                            dataLength += bitmapLengths[i];
+                        }
+                    }
+                }
+                dataLength = dataLength * header.dataCount;
+                if (dataLength != this.rawData.length)
+                    throw new LengthMismatchException("Workout data length mismatch with bitmap.");
+
+                this.dataList = new ArrayList<>();
+                buf = ByteBuffer.wrap(this.rawData);
+                for (short i = 0; i < header.dataCount; i++) {
+                    Data data = new Data();
+                    data.timestamp = header.timestamp + header.interval * i;
+                    for (byte j = 0; j < bitmapLengths.length; j++) {
+                        if ((header.bitmap & (1 << j)) != 0) {
+                            switch (j) {
+                                case 0:
+                                    data.heartRate = buf.get();
+                                    break;
+                                case 1:
+                                    data.speed = buf.getShort();
+                                    break;
+                                case 2:
+                                    data.stepRate = buf.get();
+                                    break;
+                                case 3:
+                                    data.swolf = buf.getShort();
+                                    break;
+                                case 4:
+                                    data.strokeRate = buf.getShort();
+                                    break;
+                                case 5:
+                                    data.altitude = buf.getInt();
+                                    break;
+                                case 6:
+                                    // Inner data, parsing into data
+                                    // TODO: function for readability?
+                                    for (byte k = 0; k < innerBitmapLengths.length; k++) {
+                                        if ((innerBitmap & (1 << k)) != 0) {
+                                            switch (k) {
+                                                case 0:
+                                                    data.cadence = buf.getShort();
+                                                    break;
+                                                case 1:
+                                                    data.stepLength = buf.getShort();
+                                                    break;
+                                                case 2:
+                                                    data.groundContactTime = buf.getShort();
+                                                    break;
+                                                case 3:
+                                                    data.impact = buf.get();
+                                                    break;
+                                                case 4:
+                                                    data.swingAngle = buf.getShort();
+                                                    break;
+                                                case 5:
+                                                    data.foreFootLanding = buf.get();
+                                                    break;
+                                                case 6:
+                                                    data.midFootLanding = buf.get();
+                                                    break;
+                                                case 7:
+                                                    data.backFootLanding = buf.get();
+                                                    break;
+                                                case 8:
+                                                    data.eversionAngle = buf.get();
+                                                    break;
+                                                default:
+                                                    data.unknownData = this.tlv.serialize();
+                                                    // Fix alignment
+                                                    for (int l = 0; l < innerBitmapLengths[k]; l++)
+                                                        buf.get();
+                                                    break;
+                                            }
+                                        }
+                                    }
+                                    break;
+                                case 7:
+                                    data.calories = buf.getShort();
+                                    break;
+                                case 8:
+                                    data.frequency = buf.getShort();
+                                    break;
+                                case 9:
+                                    data.cyclingPower = buf.getShort();
+                                    break;
+                                default:
+                                    data.unknownData = this.tlv.serialize();
+                                    // Fix alignment
+                                    for (int k = 0; k < bitmapLengths[j]; k++)
+                                        buf.get();
+                                    break;
+                            }
+                        }
+                    }
+                    this.dataList.add(data);
+                }
+            }
+        }
+    }
+
+    public static class WorkoutPace {
+        public static final int id = 0x0c;
+
+        public static class Request extends HuaweiPacket {
+
+            public Request(
+                    ParamsProvider paramsProvider,
+                    short workoutNumber,
+                    short paceNumber
+            ) {
+                super(paramsProvider);
+
+                this.serviceId = Workout.id;
+                this.commandId = id;
+
+                this.tlv = new HuaweiTLV().put(0x81, new HuaweiTLV()
+                        .put(0x02, workoutNumber)
+                        .put(0x08, paceNumber)
+                );
+
+                this.complete = true;
+            }
+        }
+
+        public static class Response extends HuaweiPacket {
+            public static class Block {
+                public short distance = -1;
+                public byte type = -1;
+                public int pace = -1;
+                public short pointIndex = 0;
+                public short correction = 0;
+                public boolean hasCorrection = false;
+
+                @Override
+                public String toString() {
+                    return "Block{" +
+                            "distance=" + distance +
+                            ", type=" + type +
+                            ", pace=" + pace +
+                            ", pointIndex=" + pointIndex +
+                            ", correction=" + correction +
+                            ", hasCorrection=" + hasCorrection +
+                            '}';
+                }
+            }
+
+            public short workoutNumber;
+            public short paceNumber;
+            public List<Block> blocks;
+
+            public Response(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+            }
+
+            @Override
+            public void parseTlv() throws ParseException {
+                HuaweiTLV container = this.tlv.getObject(0x81);
+
+                this.workoutNumber = container.getShort(0x02);
+                this.paceNumber = container.getShort(0x08);
+
+                this.blocks = new ArrayList<>();
+                for (HuaweiTLV blockTlv : container.getObjects(0x83)) {
+                    Block block = new Block();
+                    block.distance = blockTlv.getShort(0x04);
+                    block.type = blockTlv.getByte(0x05);
+                    block.pace = blockTlv.getInteger(0x06);
+                    if (blockTlv.contains(0x07))
+                        block.pointIndex = blockTlv.getShort(0x07);
+                    if (blockTlv.contains(0x09)) {
+                        block.hasCorrection = true;
+                        block.correction = blockTlv.getShort(0x09);
+                    }
+                    blocks.add(block);
+                }
+            }
+        }
+    }
+
+    public static class WorkoutSwimSegments {
+        public static final int id = 0x0e;
+
+        public static class Request extends HuaweiPacket {
+
+            public Request(
+                    ParamsProvider paramsProvider,
+                    short workoutNumber,
+                    short segmentNumber
+            ) {
+                super(paramsProvider);
+
+                this.serviceId = Workout.id;
+                this.commandId = id;
+
+                this.tlv = new HuaweiTLV().put(0x81, new HuaweiTLV()
+                        .put(0x02, workoutNumber)
+                        .put(0x08, segmentNumber)
+                );
+
+                this.complete = true;
+            }
+        }
+
+        public static class Response extends HuaweiPacket {
+            public static class Block {
+                public short distance = -1;
+                public byte type = -1;
+                public int pace = -1;
+                public short pointIndex = 0;
+                public short segment = -1;
+                public byte swimType= -1;
+                public short strokes = -1;
+                public short avgSwolf = -1;
+                public int time= -1;
+
+                @Override
+                public String toString() {
+                    final StringBuffer sb = new StringBuffer("Block{");
+                    sb.append("distance=").append(distance);
+                    sb.append(", type=").append(type);
+                    sb.append(", pace=").append(pace);
+                    sb.append(", pointIndex=").append(pointIndex);
+                    sb.append(", segment=").append(segment);
+                    sb.append(", swimType=").append(swimType);
+                    sb.append(", strokes=").append(strokes);
+                    sb.append(", awgSwolf=").append(avgSwolf);
+                    sb.append(", time=").append(time);
+                    sb.append('}');
+                    return sb.toString();
+                }
+            }
+
+            public short workoutNumber;
+            public short segmentNumber;
+            public List<Block> blocks;
+
+            public Response(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+            }
+
+            @Override
+            public void parseTlv() throws ParseException {
+                HuaweiTLV container = this.tlv.getObject(0x81);
+
+                this.workoutNumber = container.getShort(0x02);
+                this.segmentNumber = container.getShort(0x08);
+
+                this.blocks = new ArrayList<>();
+                for (HuaweiTLV blockTlv : container.getObjects(0x83)) {
+                    Block block = new Block();
+
+                    block.distance = blockTlv.getShort(0x04);
+                    block.type = blockTlv.getByte(0x05);
+                    block.pace = blockTlv.getInteger(0x06);
+                    if (blockTlv.contains(0x07))
+                        block.pointIndex = blockTlv.getShort(0x07);
+                    if (blockTlv.contains(0x09))
+                        block.segment = blockTlv.getShort(0x09);
+                    if (blockTlv.contains(0x0a))
+                        block.swimType= blockTlv.getByte(0x0a);
+                    if (blockTlv.contains(0x0b))
+                        block.strokes = blockTlv.getShort(0x0b);
+                    if (blockTlv.contains(0x0c))
+                        block.avgSwolf = blockTlv.getShort(0x0c);
+                    if (blockTlv.contains(0x0d))
+                        block.time= blockTlv.getInteger(0x0d);
+
+                    blocks.add(block);
+                }
+            }
+        }
+    }
+
+    public static class NotifyHeartRate {
+        public static final int id = 0x17;
+
+        public static class Request extends HuaweiPacket {
+            public Request(ParamsProvider paramsProvider) {
+                super(paramsProvider);
+
+                this.serviceId = Workout.id;
+                this.commandId = id;
+
+                this.tlv = new HuaweiTLV().put(0x01, 0x03);
+
+                this.complete = true;
+            }
+        }
+
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/id115/ID115Constants.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/id115/ID115Constants.java
new file mode 100644
index 0000000..ed546f6
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/id115/ID115Constants.java
@@ -0,0 +1,107 @@
+/*  Copyright (C) 2018-2024 Andreas Shimokawa, Vadim Kaushan
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.id115;
+
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.model.NotificationType;
+
+import static nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport.BASE_UUID;
+
+public class ID115Constants {
+    public static final UUID UUID_SERVICE_ID115 = UUID.fromString(String.format(BASE_UUID, "0AF0"));
+    public static final UUID UUID_CHARACTERISTIC_WRITE_NORMAL = UUID.fromString(String.format(BASE_UUID, "0AF6"));
+    public static final UUID UUID_CHARACTERISTIC_NOTIFY_NORMAL = UUID.fromString(String.format(BASE_UUID, "0AF7"));
+    public static final UUID UUID_CHARACTERISTIC_WRITE_HEALTH = UUID.fromString(String.format(BASE_UUID, "0AF1"));
+    public static final UUID UUID_CHARACTERISTIC_NOTIFY_HEALTH = UUID.fromString(String.format(BASE_UUID, "0AF2"));
+
+    public static final byte CMD_ID_WARE_UPDATE = 0x01;
+    public static final byte CMD_ID_GET_INFO = 0x02;
+    public static final byte CMD_ID_SETTINGS = 0x03;
+    public static final byte CMD_ID_BIND_UNBIND = 0x04;
+    public static final byte CMD_ID_NOTIFY = 0x05;
+    public static final byte CMD_ID_APP_CONTROL = 0x06;
+    public static final byte CMD_ID_BLE_CONTROL = 0x07;
+    public static final byte CMD_ID_HEALTH_DATA = 0x08;
+    public static final byte CMD_ID_DUMP_STACK = 0x20;
+    public static final byte CMD_ID_LOG = 0x21;
+    public static final byte CMD_ID_FACTORY = (byte)0xaa;
+    public static final byte CMD_ID_DEVICE_RESTART = (byte)0xf0;
+
+    // CMD_ID_SETTINGS
+    public static final byte CMD_KEY_SET_TIME = 0x01;
+    public static final byte CMD_KEY_SET_GOAL = 0x03;
+    public static final byte CMD_KEY_SET_HAND = 0x22;
+    public static final byte CMD_ARG_LEFT = 0x00;
+    public static final byte CMD_ARG_RIGHT = 0x01;
+    public static final byte CMD_KEY_SET_DISPLAY_MODE = 0x2B;
+    public static final byte CMD_ARG_HORIZONTAL = 0x00;
+    public static final byte CMD_ARG_VERTICAL = 0x02;
+
+    // CMD_ID_NOTIFY
+    public static final byte CMD_KEY_NOTIFY_CALL = 0x01;
+    public static final byte CMD_KEY_NOTIFY_STOP = 0x02;
+    public static final byte CMD_KEY_NOTIFY_MSG = 0x03;
+
+    // CMD_ID_HEALTH_DATA
+    public static final byte CMD_KEY_FETCH_ACTIVITY_TODAY = 0x03;
+
+    // CMD_ID_DEVICE_RESTART
+    public static final byte CMD_KEY_REBOOT = 0x01;
+
+    public static byte getNotificationType(NotificationType type) {
+        switch (type) {
+//            case GENERIC_EMAIL:
+//                return 2; // Icon is not supported
+            case WECHAT:
+                return 3;
+//            case QQ:
+//                return 4;
+            case FACEBOOK:
+                return 6;
+            case TWITTER:
+                return 7;
+            case WHATSAPP:
+                return 8;
+            case FACEBOOK_MESSENGER:
+                return 9;
+            case INSTAGRAM:
+                return 10;
+            case LINKEDIN:
+                return 11;
+//            case GENERIC_CALENDAR:
+//                return 12; // Icon is not supported
+//            case SKYPE:
+//                return 13; // Icon is not supported
+//            case LINE:
+//                return 17; // Icon is not supported
+//            case VIBER:
+//                return 18; // Icon is not supported
+//            case KAKAO_TALK:
+//                return 19; // Icon is not supported
+//            case VK:
+//                return 16; // Icon is not supported
+//            case GMAIL:
+//                return 20; // Icon is not supported
+//            case OUTLOOK:
+//                return 21; // Icon is not supported
+//            case SNAPCHAT:
+//                return 22; // Icon is not supported
+        }
+        return 1;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/id115/ID115Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/id115/ID115Coordinator.java
new file mode 100644
index 0000000..ece3c9f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/id115/ID115Coordinator.java
@@ -0,0 +1,179 @@
+/*  Copyright (C) 2018-2024 Andreas Shimokawa, Damien Gaignon, Daniel Dakhno,
+    Daniele Gobbetti, Jos茅 Rebelo, Petr Van臎k, Vadim Kaushan
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.id115;
+
+import android.app.Activity;
+import android.bluetooth.le.ScanFilter;
+import android.content.Context;
+import android.net.Uri;
+import android.os.ParcelUuid;
+
+import java.util.Collection;
+import java.util.Collections;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import nodomain.freeyourgadget.gadgetbridge.GBException;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLEDeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.id115.ID115Support;
+
+public class ID115Coordinator extends AbstractBLEDeviceCoordinator {
+    @NonNull
+    @Override
+    public Collection<? extends ScanFilter> createBLEScanFilters() {
+        ParcelUuid service = new ParcelUuid(ID115Constants.UUID_SERVICE_ID115);
+        ScanFilter filter = new ScanFilter.Builder().setServiceUuid(service).build();
+        return Collections.singletonList(filter);
+    }
+
+    @Override
+    protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
+    }
+
+    @NonNull
+    @Override
+    public boolean supports(GBDeviceCandidate candidate) {
+        if (candidate.supportsService(ID115Constants.UUID_SERVICE_ID115)) {
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public int getBondingStyle(){
+        return BONDING_STYLE_NONE;
+    }
+
+    @Nullable
+    @Override
+    public Class<? extends Activity> getPairingActivity() {
+        return null;
+    }
+
+    @Override
+    public boolean supportsActivityDataFetching() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsActivityTracking() {
+        return true;
+    }
+
+    @Override
+    public SampleProvider<? extends ActivitySample> getSampleProvider(GBDevice device, DaoSession session) {
+        return new ID115SampleProvider(device, session);
+    }
+
+    @Override
+    public InstallHandler findInstallHandler(Uri uri, Context context) {
+        return null;
+    }
+
+    @Override
+    public boolean supportsScreenshots(final GBDevice device) {
+        return false;
+    }
+
+    @Override
+    public int getAlarmSlotCount(GBDevice device) {
+        return 0;
+    }
+
+    @Override
+    public boolean supportsHeartRateMeasurement(GBDevice device) {
+        return false;
+    }
+
+    @Override
+    public String getManufacturer() {
+        return "VeryFit";
+    }
+
+    @Override
+    public boolean supportsAppsManagement(final GBDevice device) {
+        return false;
+    }
+
+    @Override
+    public Class<? extends Activity> getAppsManagementActivity() {
+        return null;
+    }
+
+    @Override
+    public boolean supportsCalendarEvents() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsRealtimeData() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsWeather() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsFindDevice() {
+        return false;
+    }
+
+    @Override
+    public int[] getSupportedDeviceSpecificSettings(GBDevice device) {
+        return new int[]{
+                R.xml.devicesettings_wearlocation,
+                R.xml.devicesettings_screenorientation,
+                R.xml.devicesettings_transliteration
+        };
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return ID115Support.class;
+    }
+
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_id115;
+    }
+
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_h30_h10;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_h30_h10_disabled;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/id115/ID115SampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/id115/ID115SampleProvider.java
new file mode 100644
index 0000000..4882226
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/id115/ID115SampleProvider.java
@@ -0,0 +1,77 @@
+/*  Copyright (C) 2018-2024 Daniele Gobbetti, Vadim Kaushan
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.id115;
+
+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.ID115ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.entities.ID115ActivitySampleDao;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+
+public class ID115SampleProvider extends AbstractSampleProvider<ID115ActivitySample> {
+    public ID115SampleProvider(GBDevice device, DaoSession session) {
+        super(device, session);
+    }
+
+    @Override
+    public AbstractDao<ID115ActivitySample, ?> getSampleDao() {
+        return getSession().getID115ActivitySampleDao();
+    }
+
+    @Nullable
+    @Override
+    protected Property getRawKindSampleProperty() {
+        return ID115ActivitySampleDao.Properties.RawKind;
+    }
+
+    @NonNull
+    @Override
+    protected Property getTimestampSampleProperty() {
+        return ID115ActivitySampleDao.Properties.Timestamp;
+    }
+
+    @NonNull
+    @Override
+    protected Property getDeviceIdentifierSampleProperty() {
+        return ID115ActivitySampleDao.Properties.DeviceId;
+    }
+
+    @Override
+    public ActivityKind normalizeType(int rawType) {
+        return ActivityKind.fromCode(rawType);
+    }
+
+    @Override
+    public int toRawActivityKind(ActivityKind activityKind) {
+        return activityKind.getCode();
+    }
+
+    @Override
+    public float normalizeIntensity(int rawIntensity) {
+        return rawIntensity;
+    }
+
+    @Override
+    public ID115ActivitySample createActivitySample() {
+        return new ID115ActivitySample();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/idasen/IdasenConstants.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/idasen/IdasenConstants.java
new file mode 100644
index 0000000..08402ac
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/idasen/IdasenConstants.java
@@ -0,0 +1,30 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.idasen;
+
+import java.util.UUID;
+
+public class IdasenConstants {
+    public static final UUID CHARACTERISTIC_SVC_HEIGHT = UUID.fromString("99fa0020-338a-1024-8a49-009c0215f78a");
+    public static final UUID CHARACTERISTIC_SVC_COMMAND = UUID.fromString("99fa0001-338a-1024-8a49-009c0215f78a");
+    public static final UUID CHARACTERISTIC_COMMAND = UUID.fromString("99fa0002-338a-1024-8a49-009c0215f78a");
+    public static final UUID CHARACTERISTIC_SVC_REF_HEIGHT = UUID.fromString("99fa0030-338a-1024-8a49-009c0215f78a");
+    public static final UUID CHARACTERISTIC_SVC_DPG = UUID.fromString("99fa0010-338a-1024-8a49-009c0215f78a");
+    public static final UUID CHARACTERISTIC_REF_HEIGHT = UUID.fromString("99fa0031-338a-1024-8a49-009c0215f78a");
+    public static final UUID CHARACTERISTIC_HEIGHT = UUID.fromString("99fa0021-338a-1024-8a49-009c0215f78a");
+    public static final UUID CHARACTERISTIC_DPG = UUID.fromString("99fa0011-338a-1024-8a49-009c0215f78a");
+    public static final String ACTION_REALTIME_DESK_VALUES = ".action.realtime_desk_values";
+    public static final String EXTRA_DESK_HEIGHT = "EXTRA_DESK_HEIGHT";
+    public static final String EXTRA_DESK_SPEED = "EXTRA_DESK_SPEED";
+
+    public static final double MIN_HEIGHT = 0.62;
+    public static final double MAX_HEIGHT = 1.27;
+
+    public static final byte[] CMD_UP = new byte[]{0x47, 0x00};
+    public static final byte[] CMD_DOWN = new byte[]{0x46, 0x00};
+    public static final byte[] CMD_STOP = new byte[]{(byte)0xFF, 0x00};
+    public static final byte[] CMD_WAKEUP = new byte[]{(byte)0xFE, 0x00};
+    public static final byte[] CMD_REF_INPUT_STOP = new byte[]{0x01, (byte)0x80};
+    public static final byte[] CMD_DPG_WAKEUP_PREP = new byte[]{0x7F, (byte)0x86, 0x00};
+    public static final byte[] CMD_DPG_WAKEUP = new byte[]{0x7F, (byte)0x86, (byte)0x80, 0x01,
+        0x02, 0x03, 0x04, 0x05, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D,
+        0x0E, 0x0F, 0x10, 0x11};
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/idasen/IdasenControlActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/idasen/IdasenControlActivity.java
new file mode 100644
index 0000000..cb08e64
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/idasen/IdasenControlActivity.java
@@ -0,0 +1,105 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.idasen;
+
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.TextView;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.text.DecimalFormat;
+import java.util.Objects;
+
+
+import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBActivity;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.workouts.WorkoutValueFormatter;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.idasen.IdasenDeviceSupport;
+
+public class IdasenControlActivity extends AbstractGBActivity {
+    private final Logger logger = LoggerFactory.getLogger(getClass());
+    public TextView mDeskHeight, mDeskSpeed;
+
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            if (Objects.requireNonNull(action).equals(IdasenConstants.ACTION_REALTIME_DESK_VALUES)){
+                final WorkoutValueFormatter formatter = new WorkoutValueFormatter();
+                float height = (float) intent.getSerializableExtra(IdasenConstants.EXTRA_DESK_HEIGHT);
+                float speed = (float) intent.getSerializableExtra(IdasenConstants.EXTRA_DESK_SPEED);
+                logger.debug("Received desk values: {} {}", speed, height);
+                mDeskHeight.setText(formatter.formatValue(height * 100F, "cm"));
+                mDeskSpeed.setText(formatter.formatValue(speed * 1000F, "mm/s"));
+            }
+        }
+    };
+
+    LocalBroadcastManager localBroadcastManager;
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_idasen_control);
+
+        localBroadcastManager = LocalBroadcastManager.getInstance(this);
+
+        mDeskHeight = findViewById(R.id.idasen_desk_height);
+        mDeskSpeed = findViewById(R.id.idasen_desk_speed);
+
+        IntentFilter filterLocal = new IntentFilter();
+        filterLocal.addAction(IdasenConstants.ACTION_REALTIME_DESK_VALUES);
+        LocalBroadcastManager.getInstance(getApplicationContext()).registerReceiver(mReceiver, filterLocal);
+        Intent intent = new Intent(IdasenDeviceSupport.COMMAND_GET_DESK_VALUES);
+        sendLocalBroadcast(intent);
+
+        View.OnTouchListener controlTouchListener = new View.OnTouchListener() {
+            @Override
+            public boolean onTouch(View v, MotionEvent event) {
+                Intent intent = null;
+
+                if (event.getAction() == MotionEvent.ACTION_DOWN) {
+                    if (v.getId() == R.id.idasen_control_button_up) {
+                         intent = new Intent(IdasenDeviceSupport.COMMAND_UP);
+                    } else if (v.getId() == R.id.idasen_control_button_down) {
+                         intent = new Intent(IdasenDeviceSupport.COMMAND_DOWN);
+                    } else if (v.getId() == R.id.idasen_control_button_sit) {
+                         intent = new Intent(IdasenDeviceSupport.COMMAND_SET_HEIGHT);
+                        intent.putExtra(IdasenDeviceSupport.TARGET_HEIGHT, IdasenDeviceSupport.TARGET_POS_SIT);
+                    } else if (v.getId() == R.id.idasen_control_button_stand) {
+                         intent = new Intent(IdasenDeviceSupport.COMMAND_SET_HEIGHT);
+                        intent.putExtra(IdasenDeviceSupport.TARGET_HEIGHT, IdasenDeviceSupport.TARGET_POS_STAND);
+                    } else if (v.getId() == R.id.idasen_control_button_mid) {
+                         intent = new Intent(IdasenDeviceSupport.COMMAND_SET_HEIGHT);
+                        intent.putExtra(IdasenDeviceSupport.TARGET_HEIGHT, IdasenDeviceSupport.TARGET_POS_MID);
+                    }
+                    sendLocalBroadcast(intent);
+                } else {
+                    return false;
+                }
+                return true;
+            }
+        };
+        findViewById(R.id.idasen_control_button_up).setOnTouchListener(controlTouchListener);
+        findViewById(R.id.idasen_control_button_down).setOnTouchListener(controlTouchListener);
+        findViewById(R.id.idasen_control_button_sit).setOnTouchListener(controlTouchListener);
+        findViewById(R.id.idasen_control_button_mid).setOnTouchListener(controlTouchListener);
+        findViewById(R.id.idasen_control_button_stand).setOnTouchListener(controlTouchListener);
+    }
+
+    private void sendLocalBroadcast(Intent intent) {
+        localBroadcastManager.sendBroadcast(intent);
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        localBroadcastManager.unregisterReceiver(mReceiver);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/idasen/IdasenCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/idasen/IdasenCoordinator.java
new file mode 100644
index 0000000..7523dc0
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/idasen/IdasenCoordinator.java
@@ -0,0 +1,70 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.idasen;
+
+import android.app.Activity;
+
+import androidx.annotation.NonNull;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.GBException;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer;
+import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLEDeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.idasen.IdasenDeviceSupport;
+
+public class IdasenCoordinator extends AbstractBLEDeviceCoordinator {
+    @Override
+    protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
+
+    }
+    @Override
+    public String getManufacturer() {
+        return "IKEA";
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("Desk 1000", Pattern.CASE_INSENSITIVE);
+    }
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_idasen;
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return IdasenDeviceSupport.class;
+    }
+
+    @Override
+    public DeviceSpecificSettingsCustomizer getDeviceSpecificSettingsCustomizer(final GBDevice device) {
+        return new IdasenSettingsCustomizer();
+    }
+
+    @Override
+    public int getBondingStyle() {
+        return BONDING_STYLE_BOND;
+    }
+
+    @Override
+    public boolean supportsAppsManagement(final GBDevice device) {
+        return true;
+    }
+
+    @Override
+    public Class<? extends Activity> getAppsManagementActivity() {
+        return IdasenControlActivity.class;
+    }
+
+    @Override
+    public int[] getSupportedDeviceSpecificSettings(GBDevice device) {
+        return new int[]{
+            R.xml.devicesettings_idasen
+        };
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/idasen/IdasenSettingsCustomizer.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/idasen/IdasenSettingsCustomizer.java
new file mode 100644
index 0000000..c36faa0
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/idasen/IdasenSettingsCustomizer.java
@@ -0,0 +1,83 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.idasen;
+
+import android.os.Parcel;
+import android.text.InputType;
+import android.widget.Toast;
+
+import androidx.annotation.NonNull;
+import androidx.preference.EditTextPreference;
+import androidx.preference.Preference;
+
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsHandler;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+import nodomain.freeyourgadget.gadgetbridge.R;
+
+import static nodomain.freeyourgadget.gadgetbridge.util.GB.toast;
+
+public class IdasenSettingsCustomizer implements DeviceSpecificSettingsCustomizer {
+    public static final Creator<IdasenSettingsCustomizer> CREATOR = new Creator<IdasenSettingsCustomizer>() {
+        @Override
+        public IdasenSettingsCustomizer createFromParcel(final Parcel in) {
+            return new IdasenSettingsCustomizer();
+        }
+
+        @Override
+        public IdasenSettingsCustomizer[] newArray(final int size) {
+            return new IdasenSettingsCustomizer[size];
+        }
+    };
+    private static final Logger LOG = LoggerFactory.getLogger(IdasenSettingsCustomizer.class);
+
+    @Override
+    public void onPreferenceChange(final Preference preference, final DeviceSpecificSettingsHandler handler) {
+    }
+
+    @Override
+    public void customizeSettings(final DeviceSpecificSettingsHandler handler, final Prefs prefs, final String rootKey) {
+        final EditTextPreference prefMidHeight = handler.findPreference(DeviceSettingsPreferenceConst.PREF_IDASEN_MID_HEIGHT);
+        final EditTextPreference prefSitHeight = handler.findPreference(DeviceSettingsPreferenceConst.PREF_IDASEN_SIT_HEIGHT);
+        final EditTextPreference prefStandHeight = handler.findPreference(DeviceSettingsPreferenceConst.PREF_IDASEN_STAND_HEIGHT);
+        if (prefSitHeight == null || prefMidHeight == null || prefStandHeight == null) {
+            return;
+        }
+        Preference.OnPreferenceChangeListener prefListener = (preference, newValue) -> {
+            final double val = Double.parseDouble(newValue.toString());
+            if (val > IdasenConstants.MAX_HEIGHT * 100F || val < IdasenConstants.MIN_HEIGHT * 100F) {
+                toast(handler.getContext(), R.string.idasen_pref_value_warning, Toast.LENGTH_SHORT, 0);
+                return false;
+            }
+            return true;
+        };
+        for (EditTextPreference pref: List.of(prefMidHeight, prefSitHeight, prefStandHeight)){
+            pref.setOnBindEditTextListener(p -> {
+                p.setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL);
+                p.setSelection(p.getText().length());
+            });
+            pref.setOnPreferenceChangeListener(prefListener);
+        }
+    }
+
+    @Override
+    public Set<String> getPreferenceKeysWithSummary() {
+        return Collections.emptySet();
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/itag/ITagConstants.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/itag/ITagConstants.java
new file mode 100644
index 0000000..a63749e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/itag/ITagConstants.java
@@ -0,0 +1,26 @@
+/*  Copyright (C) 2020-2024 Taavi Eom盲e
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.itag;
+
+import java.util.UUID;
+
+public final class ITagConstants {
+    /** Contains information about the button state */
+    public static final UUID UUID_SERVICE_BUTTON = UUID.fromString("0000ffe1-0000-1000-8000-00805f9b34fb");
+    /** Contains information about the button state */
+    public static final UUID UUID_LINK_LOSS_ALERT_LEVEL = UUID.fromString("00002a06-0000-1000-8000-00805f9b34fb");
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/itag/ITagCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/itag/ITagCoordinator.java
new file mode 100644
index 0000000..960cbf2
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/itag/ITagCoordinator.java
@@ -0,0 +1,172 @@
+/*  Copyright (C) 2020-2024 Andreas Shimokawa, Damien Gaignon, Daniel Dakhno,
+    Jos茅 Rebelo, Petr Van臎k, Taavi Eom盲e
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.itag;
+
+import android.app.Activity;
+import android.bluetooth.le.ScanFilter;
+import android.content.Context;
+import android.net.Uri;
+
+import androidx.annotation.NonNull;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLEDeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.itag.ITagSupport;
+
+public class ITagCoordinator extends AbstractBLEDeviceCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("itag.*", Pattern.CASE_INSENSITIVE);
+    }
+
+    @Override
+    public int getBondingStyle() {
+        // Some iTag devices do not support bonding but some do
+        return BONDING_STYLE_ASK;
+    }
+
+    @NonNull
+    @Override
+    public Collection<? extends ScanFilter> createBLEScanFilters() {
+        ScanFilter filter = new ScanFilter.Builder()
+                .setDeviceName("iTag")
+                .setDeviceName("iTAG")
+                .setDeviceName("ITAG")
+                .setDeviceName("ITag")
+                .setDeviceName("Itag")
+                .setDeviceName("itag")
+                .build();
+        return Collections.singletonList(filter);
+    }
+
+    @Override
+    public Class<? extends Activity> getPairingActivity() {
+        return null;
+    }
+
+    @Override
+    public InstallHandler findInstallHandler(Uri uri, Context context) {
+        return null;
+    }
+
+    @Override
+    public boolean supportsActivityDataFetching() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsActivityTracking() {
+        return false;
+    }
+
+    @Override
+    public SampleProvider<? extends ActivitySample> getSampleProvider(GBDevice device, DaoSession session) {
+        return null;
+    }
+
+    @Override
+    public boolean supportsScreenshots(final GBDevice device) {
+        return false;
+    }
+
+    @Override
+    public int getAlarmSlotCount(GBDevice device) {
+        return 0;
+    }
+
+    @Override
+    public boolean supportsHeartRateMeasurement(GBDevice device) {
+        return false;
+    }
+
+    @Override
+    public String getManufacturer() {
+        return "Unspecified"; //TODO: Show chip manufacturer?
+    }
+
+    @Override
+    public boolean supportsAppsManagement(final GBDevice device) {
+        return false;
+    }
+
+    @Override
+    public Class<? extends Activity> getAppsManagementActivity() {
+        return null;
+    }
+
+    @Override
+    public boolean supportsCalendarEvents() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsRealtimeData() {
+        return false; //TODO: RRSI
+    }
+
+    @Override
+    public boolean supportsWeather() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsFindDevice() {
+        return true;
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return ITagSupport.class;
+    }
+
+    @Override
+    protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) {
+        // nothing to delete, yet
+    }
+
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_itag;
+    }
+
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_itag;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_itag_disabled;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/jyou/BFH16Constants.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/jyou/BFH16Constants.java
new file mode 100644
index 0000000..a86fd10
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/jyou/BFH16Constants.java
@@ -0,0 +1,125 @@
+/*  Copyright (C) 2019-2024 Sophanimus
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+
+package nodomain.freeyourgadget.gadgetbridge.devices.jyou;
+
+import java.util.UUID;
+
+public final class BFH16Constants {
+
+    //Known Services
+    public static final UUID BFH16_GENERIC_ACCESS_SERVICE = UUID.fromString("00001800-0000-1000-8000-00805f9b34fb");
+    public static final UUID BFH16_GENERIC_ATTRIBUTE_SERVICE = UUID.fromString("00001801-0000-1000-8000-00805f9b34fb");
+
+    public static final UUID BFH16_IDENTIFICATION_SERVICE1 = UUID.fromString("0000fef5-0000-1000-8000-00805f9b34fb");
+    public static final UUID BFH16_IDENTIFICATION_SERVICE2 = UUID.fromString("0000fee7-0000-1000-8000-00805f9b34fb");
+
+
+    public static final UUID BFH16_SERVICE1         = UUID.fromString("000056ff-0000-1000-8000-00805f9b34fb");
+    public static final UUID BFH16_SERVICE1_WRITE   = UUID.fromString("000033f3-0000-1000-8000-00805f9b34fb");
+    public static final UUID BFH16_SERVICE1_NOTIFY  = UUID.fromString("000033f4-0000-1000-8000-00805f9b34fb");
+
+    public static final UUID BFH16_SERVICE2         = UUID.fromString("0000fee7-0000-1000-8000-00805f9b34fb");
+    public static final UUID BFH16_SERVICE2_WRITE   = UUID.fromString("0000fec7-0000-1000-8000-00805f9b34fb");
+    public static final UUID BFH16_SERVICE2_INDICATE= UUID.fromString("0000fec8-0000-1000-8000-00805f9b34fb");
+    public static final UUID BFH16_SERVICE2_READ    = UUID.fromString("0000fec9-0000-1000-8000-00805f9b34fb");
+
+
+    //Verified command bytes
+    public static final byte CMD_SET_ALARM_1 = (byte)0x09;
+    public static final byte CMD_SET_ALARM_2 = (byte)0x22;
+    public static final byte CMD_SET_ALARM_3 = (byte)0x23;
+
+    public static final byte CMD_SET_DATE_AND_TIME = 0x08;
+
+
+    public static final byte CMD_MEASURE_HEART = (byte)0x0D;    //param1: 0, param2: 0 -> STOP | 1 -> START
+    public static final byte CMD_VIBRATE = (byte)0x07;  //param1: 0, param2: 1
+
+
+    public static final byte CMD_SWITCH_PHOTO_MODE = (byte)0x25;        //param1: 0, param2: 0 -> OFF | 1 -> ON
+    public static final byte CMD_SWITCH_12HOUR_MODE = (byte)0x3E;       //byte1: 1 -> 12HourMode | 0 -> 24HourMode
+    public static final byte CMD_SWITCH_METRIC_IMPERIAL = (byte)0x3A;   //param1: 0, param2: 0 -> METRIC | 1 -> IMPERIAL //Also requests walked steps
+
+
+    //Verified receive bytes
+    public static final byte RECEIVE_DEVICE_INFO = (byte)0xF6;
+    public static final byte RECEIVE_BATTERY_LEVEL = (byte)0xF7;
+    public static final byte RECEIVE_STEPS_DATA = (byte)0xF9;
+    public static final byte RECEIVE_HEART_DATA = (byte)0xE8;
+    public static final byte RECEIVE_PHOTO_TRIGGER = (byte)0xF3;
+
+    //Verified icon bytes
+    public static final byte ICON_CALL = (byte)0x00;
+    public static final byte ICON_SMS = (byte)0x01;
+    public static final byte ICON_WECHAT = (byte)0x02;
+    public static final byte ICON_QQ = (byte)0x03;
+    public static final byte ICON_FACEBOOK = (byte)0x04;
+    public static final byte ICON_SKYPE = (byte)0x05;
+    public static final byte ICON_TWITTER = (byte)0x06;
+    public static final byte ICON_WHATSAPP = (byte)0x07;
+    public static final byte ICON_LINE = (byte)0x08;
+    public static final byte ICON_TALK = (byte)0x09;
+    public static final byte ICON_RUNNER = (byte)0x0A;
+
+
+
+    //Most probably correct command bytes
+    public static final byte CMD_SET_STEPLENGTH = (byte)0x3F;  //param1: 0, param2: STEPLENGTH
+
+
+    //Probably correct command bytes
+    public static final byte CMD_SET_INACTIVITY_WARNING_TIME = (byte)0x24;   //param1: 0, param2: time
+
+    public static final byte CMD_SET_HEART_TARGET = (byte)0x01;  //param1: 0, param2: HEART TARGET
+    public static final byte CMD_SET_STEP_TARGET = (byte)0x03;  //param1: 0, param2: STEP TARGET
+
+    public static final byte CMD_FIND_DEVICE = (byte)0x36;          //param1: 0, param2: 1
+    public static final byte CMD_SET_DISCONNECT_REMIND = (byte)0x37;    //param1: 0, param2: 0 -> ??? | 1 -> ???
+    public static final byte CMD_SET_AUTODETECT_HEART = (byte)0x38;     //param1: 0, param2: 0 -> ??? | 1 -> ???
+
+    public static final byte CMD_READ_HISTORY_SLEEP_COUNT = (byte)0x32; //param1: 0, param2: 0
+
+    public static final byte CMD_SET_NOON_TIME = (byte)0x26;    //param1: start time, param2: end time
+    public static final byte CMD_SET_SLEEP_TIME = (byte)0x27;   //param1: start time, param2: end time
+
+
+    //Could be correct command bytes
+        //Send PhoneName 0x17 and 0x18
+        //Send PhoneNumber 0x19 and 0x20
+        //Weather 0x3B
+        //Power Management 0x39
+        //User Id 0x35
+        //
+
+
+
+    //______________________________________________________________________________________________
+    //It may be that BFH16 uses the same communication protocol as JYOU
+    //copied the following JYOU vars:
+
+    public static final byte CMD_SET_HEARTRATE_AUTO = 0x38;
+    public static final byte CMD_SET_HEARTRATE_WARNING_VALUE = 0x01;
+    public static final byte CMD_SET_TARGET_STEPS = 0x03;
+    //public static final byte CMD_GET_STEP_COUNT = 0x1D;
+    public static final byte CMD_GET_SLEEP_TIME = 0x32;
+    public static final byte CMD_SET_DND_SETTINGS = 0x39;
+
+    public static final byte CMD_ACTION_HEARTRATE_SWITCH = 0x0D;
+    public static final byte CMD_ACTION_SHOW_NOTIFICATION = 0x2C;
+    public static final byte CMD_ACTION_REBOOT_DEVICE = 0x0E;
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/jyou/BFH16DeviceCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/jyou/BFH16DeviceCoordinator.java
new file mode 100644
index 0000000..d1fc793
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/jyou/BFH16DeviceCoordinator.java
@@ -0,0 +1,202 @@
+/*  Copyright (C) 2019-2024 Andreas Shimokawa, Damien Gaignon, Daniel Dakhno,
+    Jos茅 Rebelo, Petr Van臎k, Sophanimus
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+
+package nodomain.freeyourgadget.gadgetbridge.devices.jyou;
+
+import android.app.Activity;
+import android.bluetooth.le.ScanFilter;
+import android.content.Context;
+import android.net.Uri;
+import android.os.ParcelUuid;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.regex.Pattern;
+
+import androidx.annotation.NonNull;
+import nodomain.freeyourgadget.gadgetbridge.GBException;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLEDeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.jyou.BFH16DeviceSupport;
+
+public class BFH16DeviceCoordinator extends AbstractBLEDeviceCoordinator
+{
+    protected static final Logger LOG = LoggerFactory.getLogger(BFH16DeviceCoordinator.class);
+
+    @Override
+    public String getManufacturer() {
+        return "Denver";
+    }
+
+    @NonNull
+    @Override
+    public Collection<? extends ScanFilter> createBLEScanFilters() {
+
+        ParcelUuid bfhService1 = new ParcelUuid(BFH16Constants.BFH16_IDENTIFICATION_SERVICE1);
+        ParcelUuid bfhService2 = new ParcelUuid(BFH16Constants.BFH16_IDENTIFICATION_SERVICE2);
+
+        ScanFilter filter = new ScanFilter.Builder()
+                .setServiceUuid(bfhService1)
+                .setServiceUuid(bfhService2)
+                .build();
+
+        return Collections.singletonList(filter);
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("BFH-16.*");
+    }
+
+    @Override
+    public int getBondingStyle(){
+        return BONDING_STYLE_NONE;
+    }
+
+    @Override
+    public Class<? extends Activity> getPairingActivity(){
+        return null;
+    }
+
+    //Additional required functions ______________________________________
+
+    @Override
+    protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
+
+    }
+
+    @Override
+    public SampleProvider<? extends ActivitySample> getSampleProvider(GBDevice device, DaoSession session) {
+        return null;
+    }
+
+    @Override
+    public InstallHandler findInstallHandler(Uri uri, Context context) {
+        return null;
+    }
+
+    @Override
+    public Class<? extends Activity> getAppsManagementActivity()
+    {
+        return null;
+    }
+
+
+    //Supported ________________________________________________________
+
+    @Override
+    public int getAlarmSlotCount(GBDevice device)
+    {
+        return 3;
+    }
+
+    @Override
+    public boolean supportsFindDevice()
+    {
+        return true;
+    }
+
+    @Override
+    public boolean supportsHeartRateMeasurement(GBDevice device)
+    {
+        return true;
+    }
+
+    @Override
+    public boolean supportsRealtimeData()
+    {
+        return true;
+    }
+
+
+    //NOT Supported ________________________________________________________
+
+    @Override
+    public boolean supportsActivityDataFetching(){
+        return false;
+    }
+
+    @Override
+    public boolean supportsActivityTracking()
+    {
+        return false;
+    }
+
+    @Override
+    public boolean supportsAppsManagement(final GBDevice device) {
+        return false;
+    }
+
+    @Override
+    public boolean supportsCalendarEvents()
+    {
+        return false;
+    }
+
+    @Override
+    public boolean supportsLedColor()
+    {
+        return false;
+    }
+
+    @Override
+    public boolean supportsMusicInfo()
+    {
+        return false;
+    }
+
+    @Override
+    public boolean supportsRgbLedColor()
+    {
+        return false;
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return BFH16DeviceSupport.class;
+    }
+
+    @Override
+    public boolean supportsScreenshots(final GBDevice device) {
+        return false;
+    }
+
+    @Override
+    public boolean supportsWeather()
+    {
+        return false;
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_bfh16;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/jyou/JYouConstants.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/jyou/JYouConstants.java
new file mode 100644
index 0000000..315e36a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/jyou/JYouConstants.java
@@ -0,0 +1,62 @@
+/*  Copyright (C) 2017-2024 Da Pa, Pavel Elagin, Sami Alaoui
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.jyou;
+
+import java.util.UUID;
+
+public final class JYouConstants {
+    public static final UUID UUID_CHARACTERISTIC_CONTROL = UUID.fromString("000033f3-0000-1000-8000-00805f9b34fb");
+    public static final UUID UUID_CHARACTERISTIC_MEASURE = UUID.fromString("000033f4-0000-1000-8000-00805f9b34fb");
+    public static final UUID UUID_SERVICE_JYOU = UUID.fromString("000056ff-0000-1000-8000-00805f9b34fb");
+
+    public static final byte CMD_SET_DATE_AND_TIME = 0x08;
+    public static final byte CMD_SET_HEARTRATE_AUTO = 0x38;
+    public static final byte CMD_SET_HEARTRATE_WARNING_VALUE = 0x01;
+    public static final byte CMD_SET_TARGET_STEPS = 0x03;
+    public static final byte CMD_SET_ALARM_1 = 0x09;
+    public static final byte CMD_SET_ALARM_2 = 0x22;
+    public static final byte CMD_SET_ALARM_3 = 0x23;
+    public static final byte CMD_GET_STEP_COUNT = 0x1D;
+    public static final byte CMD_GET_SLEEP_TIME = 0x32;
+    public static final byte CMD_SET_NOON_TIME = 0x26;
+    public static final byte CMD_SET_SLEEP_TIME = 0x27;
+    public static final byte CMD_SET_DND_SETTINGS = 0x39;
+    public static final byte CMD_SET_INACTIVITY_WARNING_TIME = 0x24;
+
+    public static final byte CMD_ACTION_HEARTRATE_SWITCH = 0x0D;
+    public static final byte CMD_ACTION_SHOW_NOTIFICATION = 0x2C;
+    public static final byte CMD_ACTION_REBOOT_DEVICE = 0x0E;
+
+    public static final byte RECEIVE_HISTORY_SLEEP_COUNT = 0x32;
+    public static final byte RECEIVE_BLOOD_PRESSURE = (byte) 0xE8;
+    public static final byte RECEIVE_WATCH_MAC = (byte)0xEC;
+    public static final byte RECEIVE_GET_PHOTO = (byte)0xF3;
+    public static final byte RECEIVE_DEVICE_INFO = (byte)0xF6;
+    public static final byte RECEIVE_BATTERY_LEVEL = (byte)0xF7;
+    public static final byte RECEIVE_STEPS_DATA = (byte)0xF9;
+    public static final byte RECEIVE_HEARTRATE = (byte)0xFC;
+
+    public static final byte ICON_CALL = 0;
+    public static final byte ICON_SMS = 1;
+    public static final byte ICON_WECHAT = 2;
+    public static final byte ICON_QQ = 3;
+    public static final byte ICON_FACEBOOK = 4;
+    public static final byte ICON_SKYPE = 5;
+    public static final byte ICON_TWITTER = 6;
+    public static final byte ICON_WHATSAPP = 7;
+    public static final byte ICON_LINE = 8;
+}
\ No newline at end of file
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/jyou/JYouSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/jyou/JYouSampleProvider.java
new file mode 100644
index 0000000..91bdd5d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/jyou/JYouSampleProvider.java
@@ -0,0 +1,87 @@
+/*  Copyright (C) 2018-2024 Da Pa, Pavel Elagin
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.jyou;
+
+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.JYouActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.entities.JYouActivitySampleDao;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+
+public class JYouSampleProvider extends AbstractSampleProvider<JYouActivitySample> {
+
+    public static final int TYPE_ACTIVITY = -1;
+    private final float movementDivisor = 6000.0f;
+    private GBDevice mDevice;
+    private DaoSession mSession;
+
+    public JYouSampleProvider(GBDevice device, DaoSession session) {
+        super(device, session);
+
+        mSession = session;
+        mDevice = device;
+    }
+
+    @Override
+    public ActivityKind normalizeType(int rawType) {
+        return ActivityKind.fromCode(rawType);
+    }
+
+    @Override
+    public int toRawActivityKind(ActivityKind activityKind) {
+        return activityKind.getCode();
+    }
+
+    @Override
+    public float normalizeIntensity(int rawIntensity) {
+        return rawIntensity/movementDivisor;
+    }
+
+    @Override
+    public JYouActivitySample createActivitySample() {
+        return new JYouActivitySample();
+    }
+
+    @Override
+    public AbstractDao<JYouActivitySample, ?> getSampleDao() {
+        return getSession().getJYouActivitySampleDao();
+    }
+
+    @Nullable
+    @Override
+    protected Property getRawKindSampleProperty() {
+        return JYouActivitySampleDao.Properties.RawKind;
+    }
+
+    @NonNull
+    @Override
+    protected Property getTimestampSampleProperty() {
+        return JYouActivitySampleDao.Properties.Timestamp;
+    }
+
+    @NonNull
+    @Override
+    protected Property getDeviceIdentifierSampleProperty() {
+        return JYouActivitySampleDao.Properties.DeviceId;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/jyou/TeclastH30/TeclastH30Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/jyou/TeclastH30/TeclastH30Coordinator.java
new file mode 100644
index 0000000..8119ff9
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/jyou/TeclastH30/TeclastH30Coordinator.java
@@ -0,0 +1,184 @@
+/*  Copyright (C) 2018-2024 Andreas Shimokawa, Damien Gaignon, Daniel Dakhno,
+    Da Pa, Jos茅 Rebelo, Pavel Elagin, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.jyou.TeclastH30;
+
+import android.app.Activity;
+import android.bluetooth.le.ScanFilter;
+import android.content.Context;
+import android.net.Uri;
+import android.os.ParcelUuid;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import androidx.annotation.NonNull;
+import nodomain.freeyourgadget.gadgetbridge.GBException;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLEDeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.jyou.JYouConstants;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.jyou.TeclastH30.TeclastH30Support;
+
+public class TeclastH30Coordinator extends AbstractBLEDeviceCoordinator {
+
+    protected static final Logger LOG = LoggerFactory.getLogger(TeclastH30Coordinator.class);
+
+    @NonNull
+    @Override
+    public Collection<? extends ScanFilter> createBLEScanFilters() {
+        ParcelUuid uuid = new ParcelUuid(JYouConstants.UUID_SERVICE_JYOU);
+        ScanFilter filter = new ScanFilter.Builder().setServiceUuid(uuid).build();
+        return Collections.singletonList(filter);
+    }
+
+    @NonNull
+    @Override
+    public boolean supports(GBDeviceCandidate candidate) {
+        if (candidate.supportsService(JYouConstants.UUID_SERVICE_JYOU)) {
+            return true;
+        }
+
+        return super.supports(candidate);
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("TECLAST_H[13]0.*|H[13]-[ABCDEF0123456789]{4}");
+    }
+
+    @Override
+    public int getBondingStyle(){
+        return BONDING_STYLE_NONE;
+    }
+
+    @Override
+    public boolean supportsCalendarEvents() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsRealtimeData() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsWeather() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsFindDevice() {
+        return true;
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return TeclastH30Support.class;
+    }
+
+    @Override
+    public Class<? extends Activity> getPairingActivity() {
+        return null;
+    }
+
+    @Override
+    public InstallHandler findInstallHandler(Uri uri, Context context) {
+        return null;
+    }
+
+    @Override
+    public boolean supportsActivityDataFetching() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsActivityTracking() {
+        return false;
+    }
+
+    @Override
+    public SampleProvider<? extends ActivitySample> getSampleProvider(GBDevice device, DaoSession session) {
+        return null;
+    }
+
+    @Override
+    public boolean supportsScreenshots(final GBDevice device) {
+        return false;
+    }
+
+    @Override
+    public int getAlarmSlotCount(GBDevice device) {
+        return 3;
+    }
+
+    @Override
+    public boolean supportsHeartRateMeasurement(GBDevice device) {
+        return true;
+    }
+
+    @Override
+    public String getManufacturer() {
+        return "Teclast";
+    }
+
+    @Override
+    public boolean supportsAppsManagement(final GBDevice device) {
+        return false;
+    }
+
+    @Override
+    public Class<? extends Activity> getAppsManagementActivity() {
+        return null;
+    }
+
+    @Override
+    protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
+
+    }
+
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_teclast_h30;
+    }
+
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_h30_h10;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_h30_h10_disabled;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/jyou/y5/Y5Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/jyou/y5/Y5Coordinator.java
new file mode 100644
index 0000000..4cf29cc
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/jyou/y5/Y5Coordinator.java
@@ -0,0 +1,161 @@
+/*  Copyright (C) 2018-2024 Damien Gaignon, Daniel Dakhno, Da Pa, Jos茅
+    Rebelo, Pavel Elagin, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.jyou.y5;
+
+import android.app.Activity;
+import android.content.Context;
+import android.net.Uri;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.regex.Pattern;
+
+import de.greenrobot.dao.query.QueryBuilder;
+import nodomain.freeyourgadget.gadgetbridge.GBException;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLEDeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.jyou.JYouSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.entities.JYouActivitySampleDao;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.jyou.y5.Y5Support;
+
+public class Y5Coordinator extends AbstractBLEDeviceCoordinator {
+    @Override
+    protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
+        Long deviceId = device.getId();
+        QueryBuilder<?> qb = session.getJYouActivitySampleDao().queryBuilder();
+        qb.where(JYouActivitySampleDao.Properties.DeviceId.eq(deviceId)).buildDelete().executeDeleteWithoutDetachingEntities();
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile(".*Y5.*");
+    }
+
+    @Nullable
+    @Override
+    public Class<? extends Activity> getPairingActivity() {
+        return null;
+    }
+
+    @Override
+    public boolean supportsActivityDataFetching() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsActivityTracking() {
+        return true;
+    }
+
+    @Override
+    public SampleProvider<? extends ActivitySample> getSampleProvider(GBDevice device, DaoSession session) {
+        return new JYouSampleProvider(device, session);
+    }
+
+    @Override
+    public InstallHandler findInstallHandler(Uri uri, Context context) {
+        return null;
+    }
+
+    @Override
+    public boolean supportsScreenshots(final GBDevice device) {
+        return false;
+    }
+
+    @Override
+    public int getAlarmSlotCount(GBDevice device) {
+        return 3;
+    }
+
+    @Override
+    public boolean supportsSmartWakeup(GBDevice device, int position) {
+        return true;
+    }
+
+    @Override
+    public boolean supportsHeartRateMeasurement(GBDevice device) {
+        return true;
+    }
+
+    @Override
+    public String getManufacturer() {
+        return "Y5";
+    }
+
+    @Override
+    public boolean supportsAppsManagement(final GBDevice device) {
+        return false;
+    }
+
+    @Override
+    public Class<? extends Activity> getAppsManagementActivity() {
+        return null;
+    }
+
+    @Override
+    public boolean supportsCalendarEvents() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsRealtimeData() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsWeather() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsFindDevice() {
+        return true;
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return Y5Support.class;
+    }
+
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_y5;
+    }
+
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_h30_h10;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_h30_h10_disabled;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/BohemicSmartBraceletDeviceCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/BohemicSmartBraceletDeviceCoordinator.java
new file mode 100644
index 0000000..fd70447
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/BohemicSmartBraceletDeviceCoordinator.java
@@ -0,0 +1,63 @@
+/*  Copyright (C) 2023-2024 Daniel Dakhno, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.lefun;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
+
+public class BohemicSmartBraceletDeviceCoordinator extends LefunDeviceCoordinator {
+    public boolean supports(GBDeviceCandidate candidate) {
+        // Since the Lefun coordinator overrides supports, we also need to
+        return ".bohemic".equals(candidate.getName());
+    }
+
+    @Override
+    public int[] getSupportedDeviceSpecificSettings(GBDevice device) {
+        return new int[]{
+                R.xml.devicesettings_liftwrist_display_noshed,
+                R.xml.devicesettings_timeformat,
+                // R.xml.devicesettings_antilost, not supported
+                R.xml.devicesettings_inactivity,
+                R.xml.devicesettings_hydration_reminder,
+                R.xml.devicesettings_lefun_interface_language,
+                R.xml.devicesettings_transliteration
+        };
+    }
+
+    @Override
+    public boolean supportsRealtimeData() {
+        return false;  // not supported
+    }
+
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_bohemic_smart_bracelet;
+    }
+
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_h30_h10;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_h30_h10_disabled;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/LefunConstants.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/LefunConstants.java
new file mode 100644
index 0000000..50c4f69
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/LefunConstants.java
@@ -0,0 +1,86 @@
+/*  Copyright (C) 2020-2024 Yukai Li
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.lefun;
+
+import java.util.UUID;
+
+import static nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport.BASE_UUID;
+
+/**
+ * Constants used with Lefun device support
+ */
+public class LefunConstants {
+    // BLE UUIDs
+    public static final UUID UUID_SERVICE_LEFUN = UUID.fromString(String.format(BASE_UUID, "18D0"));
+    public static final UUID UUID_CHARACTERISTIC_LEFUN_WRITE = UUID.fromString(String.format(BASE_UUID, "2D01"));
+    public static final UUID UUID_CHARACTERISTIC_LEFUN_NOTIFY = UUID.fromString(String.format(BASE_UUID, "2D00"));
+
+    // Coordinator constants
+    public static final String ADVERTISEMENT_NAME = "Lefun";
+    public static final String MANUFACTURER_NAME = "Teng Jin Da";
+    // Commands
+    public static final byte CMD_REQUEST_ID = (byte) 0xab;
+    public static final byte CMD_RESPONSE_ID = 0x5a;
+    public static final int CMD_MAX_LENGTH = 20;
+    // 3 header bytes plus checksum
+    public static final int CMD_HEADER_LENGTH = 4;
+    public static final byte CMD_FIRMWARE_INFO = 0x00;
+    public static final byte CMD_BONDING_REQUEST = 0x01;
+    public static final byte CMD_SETTINGS = 0x02;
+    public static final byte CMD_BATTERY_LEVEL = 0x03;
+    public static final byte CMD_TIME = 0x04;
+    public static final byte CMD_ALARM = 0x05;
+    public static final byte CMD_PROFILE = 0x06;
+    public static final byte CMD_UI_PAGES = 0x07;
+    public static final byte CMD_FEATURES = 0x08;
+    public static final byte CMD_FIND_DEVICE = 0x09;
+    public static final byte CMD_FIND_PHONE = 0x0a;
+    public static final byte CMD_SEDENTARY_REMINDER_INTERVAL = 0x0b;
+    public static final byte CMD_HYDRATION_REMINDER_INTERVAL = 0x0c;
+    public static final byte CMD_REMOTE_CAMERA = 0x0d;
+    public static final byte CMD_REMOTE_CAMERA_TRIGGERED = 0x0e;
+    public static final byte CMD_PPG_START = 0x0f;
+    public static final byte CMD_PPG_RESULT = 0x10;
+    public static final byte CMD_PPG_DATA = 0x11;
+    public static final byte CMD_STEPS_DATA = 0x12;
+    public static final byte CMD_ACTIVITY_DATA = 0x13;
+    public static final byte CMD_SLEEP_TIME_DATA = 0x14;
+    public static final byte CMD_SLEEP_DATA = 0x15;
+    public static final byte CMD_NOTIFICATION = 0x17;
+    public static final byte CMD_LANGUAGE = 0x21;
+    public static final byte CMD_UNKNOWN_22 = 0x22;
+    public static final byte CMD_UNKNOWN_25 = 0x25;
+    public static final byte CMD_UNKNOWN_80 = (byte) 0x80;
+    public static final int PPG_TYPE_INVALID = -1;
+    public static final int PPG_TYPE_HEART_RATE = 0;
+    public static final int PPG_TYPE_BLOOD_PRESSURE = 1;
+    public static final int PPG_TYPE_BLOOD_OXYGEN = 2;
+    public static final int PPG_TYPE_COUNT = 3;
+    // DB activity kinds
+    public static final int DB_ACTIVITY_KIND_UNKNOWN = 0;
+    public static final int DB_ACTIVITY_KIND_ACTIVITY = 1;
+    public static final int DB_ACTIVITY_KIND_HEART_RATE = 2;
+    public static final int DB_ACTIVITY_KIND_LIGHT_SLEEP = 3;
+    public static final int DB_ACTIVITY_KIND_DEEP_SLEEP = 4;
+    // Pseudo-intensity
+    public static final int INTENSITY_MIN = 0;
+    public static final int INTENSITY_DEEP_SLEEP = 1;
+    public static final int INTENSITY_LIGHT_SLEEP = 2;
+    public static final int INTENSITY_AWAKE = 3;
+    public static final int INTENSITY_MAX = 4;
+    public static int NUM_ALARM_SLOTS = 5;
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/LefunDeviceCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/LefunDeviceCoordinator.java
new file mode 100644
index 0000000..c660219
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/LefunDeviceCoordinator.java
@@ -0,0 +1,166 @@
+/*  Copyright (C) 2020-2024 Damien Gaignon, Daniel Dakhno, Jos茅 Rebelo,
+    Petr Van臎k, Yukai Li
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.lefun;
+
+import android.app.Activity;
+import android.content.Context;
+import android.net.Uri;
+
+import androidx.annotation.NonNull;
+
+import nodomain.freeyourgadget.gadgetbridge.GBException;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLEDeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.model.BatteryConfig;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.LefunDeviceSupport;
+
+import static nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants.ADVERTISEMENT_NAME;
+import static nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants.MANUFACTURER_NAME;
+import static nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants.NUM_ALARM_SLOTS;
+
+/**
+ * Device coordinator for Lefun band
+ */
+public class LefunDeviceCoordinator extends AbstractBLEDeviceCoordinator {
+    @Override
+    protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
+
+    }
+
+    @Override
+    public int getBondingStyle() {
+        return BONDING_STYLE_NONE;
+    }
+
+    @Override
+    public boolean supports(GBDeviceCandidate candidate) {
+        // There's a bunch of other names other than "Lefun", but let's just focus on one for now.
+        if (ADVERTISEMENT_NAME.equals(candidate.getName())) {
+            // The device does not advertise service UUIDs, so can't check whether it supports
+            // the proper service. We can check that it doesn't advertise any services, though.
+            // We're actually supposed to check for presence of the string "TJDR" within the
+            // manufacturer specific data, which consists of the device's MAC address and said
+            // string. But we're not being given it, so *shrug*.
+            if (candidate.getServiceUuids().length == 0) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    @Override
+    public boolean supportsActivityDataFetching() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsActivityTracking() {
+        return true;
+    }
+
+    @Override
+    public SampleProvider<? extends ActivitySample> getSampleProvider(GBDevice device, DaoSession session) {
+        return new LefunSampleProvider(device, session);
+    }
+
+    @Override
+    public InstallHandler findInstallHandler(Uri uri, Context context) {
+        return null;
+    }
+
+    @Override
+    public int getAlarmSlotCount(GBDevice device) {
+        return NUM_ALARM_SLOTS;
+    }
+
+    @Override
+    public boolean supportsHeartRateMeasurement(GBDevice device) {
+        return true;
+    }
+
+    @Override
+    public String getManufacturer() {
+        return MANUFACTURER_NAME;
+    }
+
+    @Override
+    public boolean supportsRealtimeData() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsFindDevice() {
+        return true;
+    }
+
+    @Override
+    public BatteryConfig[] getBatteryConfig(final GBDevice device) {
+        return new BatteryConfig[]{
+                new BatteryConfig(
+                        0,
+                        GBDevice.BATTERY_ICON_DEFAULT,
+                        GBDevice.BATTERY_LABEL_DEFAULT,
+                        15,
+                        100
+                )
+        };
+    }
+
+    @Override
+    public int[] getSupportedDeviceSpecificSettings(GBDevice device) {
+        return new int[]{
+                R.xml.devicesettings_liftwrist_display_noshed,
+                R.xml.devicesettings_timeformat,
+                R.xml.devicesettings_antilost,
+                R.xml.devicesettings_inactivity,
+                R.xml.devicesettings_hydration_reminder,
+                R.xml.devicesettings_lefun_interface_language,
+                R.xml.devicesettings_transliteration
+        };
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return LefunDeviceSupport.class;
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_lefun;
+    }
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_h30_h10;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_h30_h10_disabled;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/LefunFeatureSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/LefunFeatureSupport.java
new file mode 100644
index 0000000..06ca0e7
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/LefunFeatureSupport.java
@@ -0,0 +1,59 @@
+/*  Copyright (C) 2020-2024 Yukai Li
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.lefun;
+
+/**
+ * Feature support utilities for Lefun devices
+ */
+public class LefunFeatureSupport {
+    public static final int SUPPORT_HEART_RATE = 1 << 2;
+    public static final int SUPPORT_BLOOD_PRESSURE = 1 << 3;
+    public static final int SUPPORT_FAKE_ECG = 1 << 10;
+    public static final int SUPPORT_ECG = 1 << 11;
+    public static final int SUPPORT_WALLPAPER_UPLOAD = 1 << 12;
+
+    public static final int RESERVE_BLOOD_OXYGEN = 1 << 0;
+    public static final int RESERVE_CLOCK_FACE_UPLOAD = 1 << 3;
+    public static final int RESERVE_CONTACTS = 1 << 5;
+    public static final int RESERVE_WALLPAPER = 1 << 6;
+    public static final int RESERVE_REMOTE_CAMERA = 1 << 7;
+
+    /**
+     * Checks whether a feature is supported
+     *
+     * @param deviceSupport  the feature flags from the device
+     * @param featureSupport the feature you want to check
+     * @return whether feature is supported
+     */
+    public static boolean checkSupported(short deviceSupport, int featureSupport) {
+        return (deviceSupport & featureSupport) == featureSupport;
+    }
+
+    /**
+     * Checks whether a feature is not reserved
+     * <p>
+     * Reserve flags indicate a feature is not available if set. This function takes care of the
+     * inverting for you, so if you get true, the feature is available.
+     *
+     * @param deviceReserve  the reserve flags from the device
+     * @param featureReserve the reserve flag you want to check
+     * @return whether feature is supported
+     */
+    public static boolean checkNotReserved(short deviceReserve, int featureReserve) {
+        return !((deviceReserve & featureReserve) == featureReserve);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/LefunSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/LefunSampleProvider.java
new file mode 100644
index 0000000..3fa2b48
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/LefunSampleProvider.java
@@ -0,0 +1,100 @@
+/*  Copyright (C) 2020-2024 Yukai Li
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.lefun;
+
+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.LefunActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.entities.LefunActivitySampleDao;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+
+/**
+ * Sample provider for Lefun devices
+ */
+public class LefunSampleProvider extends AbstractSampleProvider<LefunActivitySample> {
+    public LefunSampleProvider(GBDevice device, DaoSession session) {
+        super(device, session);
+    }
+
+    @Override
+    public AbstractDao<LefunActivitySample, ?> getSampleDao() {
+        return getSession().getLefunActivitySampleDao();
+    }
+
+    @Nullable
+    @Override
+    protected Property getRawKindSampleProperty() {
+        return LefunActivitySampleDao.Properties.RawKind;
+    }
+
+    @NonNull
+    @Override
+    protected Property getTimestampSampleProperty() {
+        return LefunActivitySampleDao.Properties.Timestamp;
+    }
+
+    @NonNull
+    @Override
+    protected Property getDeviceIdentifierSampleProperty() {
+        return LefunActivitySampleDao.Properties.DeviceId;
+    }
+
+    @Override
+    public ActivityKind normalizeType(int rawType) {
+        switch (rawType) {
+            case LefunConstants.DB_ACTIVITY_KIND_ACTIVITY:
+            case LefunConstants.DB_ACTIVITY_KIND_HEART_RATE:
+                return ActivityKind.ACTIVITY;
+            case LefunConstants.DB_ACTIVITY_KIND_LIGHT_SLEEP:
+                return ActivityKind.LIGHT_SLEEP;
+            case LefunConstants.DB_ACTIVITY_KIND_DEEP_SLEEP:
+                return ActivityKind.DEEP_SLEEP;
+            default:
+                return ActivityKind.UNKNOWN;
+        }
+    }
+
+    @Override
+    public int toRawActivityKind(ActivityKind activityKind) {
+        switch (activityKind) {
+            case ACTIVITY:
+                return LefunConstants.DB_ACTIVITY_KIND_ACTIVITY;
+            case LIGHT_SLEEP:
+                return LefunConstants.DB_ACTIVITY_KIND_LIGHT_SLEEP;
+            case DEEP_SLEEP:
+                return LefunConstants.DB_ACTIVITY_KIND_DEEP_SLEEP;
+            default:
+                return LefunConstants.DB_ACTIVITY_KIND_UNKNOWN;
+        }
+    }
+
+    @Override
+    public float normalizeIntensity(int rawIntensity) {
+        return rawIntensity / (float) LefunConstants.INTENSITY_MAX;
+    }
+
+    @Override
+    public LefunActivitySample createActivitySample() {
+        return new LefunActivitySample();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/VivitarHrBpMonitorActivityTrackerCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/VivitarHrBpMonitorActivityTrackerCoordinator.java
new file mode 100644
index 0000000..137769b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/VivitarHrBpMonitorActivityTrackerCoordinator.java
@@ -0,0 +1,61 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.lefun;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
+
+public class VivitarHrBpMonitorActivityTrackerCoordinator extends LefunDeviceCoordinator {
+    public boolean supports(GBDeviceCandidate candidate) {
+        // Since the Lefun coordinator overrides supports, we also need to
+        return "IMP-2027".equals(candidate.getName());
+    }
+
+    @Override
+    public int[] getSupportedDeviceSpecificSettings(GBDevice device) {
+        return new int[]{
+                R.xml.devicesettings_liftwrist_display_noshed,
+                R.xml.devicesettings_timeformat,
+                // R.xml.devicesettings_antilost, not supported
+                R.xml.devicesettings_inactivity,
+                R.xml.devicesettings_hydration_reminder,
+                R.xml.devicesettings_lefun_interface_language,
+                R.xml.devicesettings_transliteration
+        };
+    }
+
+    @Override
+    public boolean supportsRealtimeData() {
+        return false;  // not supported
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_vivitar_hr_bp_monitor_activity_tracker;
+    }
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_h30_h10;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_h30_h10_disabled;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/AlarmCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/AlarmCommand.java
new file mode 100644
index 0000000..e9387e7
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/AlarmCommand.java
@@ -0,0 +1,173 @@
+/*  Copyright (C) 2020-2024 Yukai Li
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands;
+
+import java.nio.ByteBuffer;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
+
+public class AlarmCommand extends BaseCommand {
+    public static final int DOW_SUNDAY = 0;
+    public static final int DOW_MONDAY = 1;
+    public static final int DOW_TUESDAY = 2;
+    public static final int DOW_WEDNESDAY = 3;
+    public static final int DOW_THURSDAY = 4;
+    public static final int DOW_FRIDAY = 5;
+    public static final int DOW_SATURDAY = 6;
+
+    private byte op;
+    private byte index;
+    private boolean enabled;
+    // Snooze is not implemented how you think it would be
+    // Number of snoozes is decremented every time the alarm triggers, and the alarm time
+    // is moved forward by number of minutes in snooze time. It never gets reset to the
+    // original time.
+    private byte numOfSnoozes;
+    private byte snoozeTime;
+    private byte dayOfWeek;
+    private byte hour;
+    private byte minute;
+
+    private boolean setSuccess;
+
+    public byte getOp() {
+        return op;
+    }
+
+    public void setOp(byte op) {
+        if (op != OP_GET && op != OP_SET)
+            throw new IllegalArgumentException("Operation must be get or set");
+        this.op = op;
+    }
+
+    public byte getIndex() {
+        return index;
+    }
+
+    public void setIndex(byte index) {
+        if (index < 0 || index >= LefunConstants.NUM_ALARM_SLOTS)
+            throw new IllegalArgumentException("Index must be between 0 and "
+                    + (LefunConstants.NUM_ALARM_SLOTS - 1) + " inclusive");
+        this.index = index;
+    }
+
+    public boolean isEnabled() {
+        return enabled;
+    }
+
+    public void setEnabled(boolean enabled) {
+        this.enabled = enabled;
+    }
+
+    public byte getNumOfSnoozes() {
+        return numOfSnoozes;
+    }
+
+    public void setNumOfSnoozes(byte numOfSnoozes) {
+        this.numOfSnoozes = numOfSnoozes;
+    }
+
+    public byte getSnoozeTime() {
+        return snoozeTime;
+    }
+
+    public void setSnoozeTime(byte snoozeTime) {
+        this.snoozeTime = snoozeTime;
+    }
+
+    public boolean getDayOfWeek(int day) {
+        if (day < 0 || day > 6)
+            throw new IllegalArgumentException("Invalid day of week");
+        return getBit(dayOfWeek, 1 << day);
+    }
+
+    public void setDayOfWeek(int day, boolean enabled) {
+        if (day < 0 || day > 6)
+            throw new IllegalArgumentException("Invalid day of week");
+        dayOfWeek = setBit(dayOfWeek, 1 << day, enabled);
+    }
+
+    public byte getHour() {
+        return hour;
+    }
+
+    public void setHour(byte hour) {
+        if (hour < 0 || hour > 23)
+            throw new IllegalArgumentException("Hour must be between 0 and 23 inclusive");
+        this.hour = hour;
+    }
+
+    public byte getMinute() {
+        return minute;
+    }
+
+    public void setMinute(byte minute) {
+        if (minute < 0 || minute > 59)
+            throw new IllegalArgumentException("Minute must be between 0 and 59 inclusive");
+        this.minute = minute;
+    }
+
+    public boolean isSetSuccess() {
+        return setSuccess;
+    }
+
+    @Override
+    protected void deserializeParams(byte id, ByteBuffer params) {
+        validateId(id, LefunConstants.CMD_ALARM);
+
+        int paramsLength = params.limit() - params.position();
+        if (paramsLength < 2)
+            throwUnexpectedLength();
+
+        op = params.get();
+        index = params.get();
+        if (op == OP_GET) {
+            if (paramsLength != 8)
+                throwUnexpectedLength();
+
+            enabled = params.get() == 1;
+            numOfSnoozes = params.get();
+            snoozeTime = params.get();
+            dayOfWeek = params.get();
+            hour = params.get();
+            minute = params.get();
+        } else if (op == OP_SET) {
+            if (paramsLength != 3)
+                throwUnexpectedLength();
+
+            setSuccess = params.get() == 1;
+        } else {
+            throw new IllegalArgumentException("Invalid operation type received");
+        }
+    }
+
+    @Override
+    protected byte serializeParams(ByteBuffer params) {
+        params.put(op);
+        // No alarm ID for get; all of them are returned
+        if (op == OP_SET) {
+            params.put(index);
+            params.put((byte)(enabled ? 1: 0));
+            params.put(numOfSnoozes);
+            params.put(snoozeTime);
+            params.put(dayOfWeek);
+            params.put(hour);
+            params.put(minute);
+        }
+        return LefunConstants.CMD_ALARM;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/BaseCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/BaseCommand.java
new file mode 100644
index 0000000..3bb2fed
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/BaseCommand.java
@@ -0,0 +1,235 @@
+/*  Copyright (C) 2020-2024 Yukai Li
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
+
+/**
+ * Base class for Lefun Bluetooth commands and responses
+ */
+public abstract class BaseCommand {
+    // Common constants
+    /**
+     * Common get operation type
+     */
+    public static final byte OP_GET = 0;
+    /**
+     * Common set operation type
+     */
+    public static final byte OP_SET = 1;
+
+    /**
+     * Calculates command checksum
+     *
+     * @param data   the data to generate checksum from
+     * @param offset the offset in data to start calculating from
+     * @param length the number of bytes to include in calculation
+     * @return the computed checksum
+     */
+    public static byte calculateChecksum(byte[] data, int offset, int length) {
+        int checksum = 0;
+        for (int i = offset; i < offset + length; ++i) {
+            byte b = data[i];
+            for (int j = 0; j < 8; ++j) {
+                if (((b ^ checksum) & 1) == 0) {
+                    checksum >>= 1;
+                } else {
+                    checksum = (checksum ^ 0x18) >> 1 | 0x80;
+                }
+                b >>= 1;
+            }
+        }
+        return (byte) checksum;
+    }
+
+    /**
+     * When implemented in a subclass, parses the response from a device
+     *
+     * @param id     the command ID
+     * @param params the params buffer
+     */
+    abstract protected void deserializeParams(byte id, ByteBuffer params);
+
+    /**
+     * When implemented in a subclass, provides the arguments to send in the command
+     *
+     * @param params the params buffer to write to
+     * @return the command ID
+     */
+    abstract protected byte serializeParams(ByteBuffer params);
+
+    /**
+     * Deserialize a response from the device
+     *
+     * @param response the response data to deserialize
+     */
+    public void deserialize(byte[] response) {
+        if (response.length < LefunConstants.CMD_HEADER_LENGTH || response.length < response[1])
+            throw new IllegalArgumentException("Response is too short");
+        if (calculateChecksum(response, 0, response[1] - 1) != response[response[1] - 1])
+            throw new IllegalArgumentException("Incorrect message checksum");
+        ByteBuffer buffer = ByteBuffer.wrap(response, LefunConstants.CMD_HEADER_LENGTH - 1,
+                response[1] - LefunConstants.CMD_HEADER_LENGTH);
+        buffer.order(ByteOrder.BIG_ENDIAN);
+        deserializeParams(response[2], buffer);
+    }
+
+    /**
+     * Serializes a command to send to the device
+     *
+     * @return the data to send to the device
+     */
+    public byte[] serialize() {
+        ByteBuffer buffer = ByteBuffer.allocate(LefunConstants.CMD_MAX_LENGTH - LefunConstants.CMD_HEADER_LENGTH);
+        buffer.order(ByteOrder.BIG_ENDIAN);
+        byte id = serializeParams(buffer);
+        return makeCommand(id, buffer);
+    }
+
+    /**
+     * Builds a command given ID and parameters buffer.
+     *
+     * @param id     the command ID
+     * @param params the parameters buffer
+     * @return the assembled command buffer
+     */
+    protected byte[] makeCommand(byte id, ByteBuffer params) {
+        if (params.position() > LefunConstants.CMD_MAX_LENGTH - LefunConstants.CMD_HEADER_LENGTH)
+            throw new IllegalArgumentException("params is too long to fit");
+
+        int paramsLength = params.position();
+        byte[] request = new byte[paramsLength + LefunConstants.CMD_HEADER_LENGTH];
+        request[0] = LefunConstants.CMD_REQUEST_ID;
+        request[1] = (byte) request.length;
+        request[2] = id;
+        params.flip();
+        params.get(request, LefunConstants.CMD_HEADER_LENGTH - 1, paramsLength);
+        request[request.length - 1] = calculateChecksum(request, 0, request.length - 1);
+        return request;
+    }
+
+    /**
+     * Throws a standard parameters length exception
+     */
+    protected void throwUnexpectedLength() {
+        throw new IllegalArgumentException("Unexpected parameters length");
+    }
+
+    /**
+     * Checks for valid command ID and throws if wrong ID provided
+     *
+     * @param id         command ID from device
+     * @param expectedId expected command ID
+     */
+    protected void validateId(byte id, byte expectedId) {
+        if (id != expectedId)
+            throw new IllegalArgumentException("Wrong command ID");
+    }
+
+    /**
+     * Checks for valid command ID and command length
+     *
+     * @param id             command ID from device
+     * @param params         params buffer from device
+     * @param expectedId     expected command ID
+     * @param expectedLength expected params length
+     */
+    protected void validateIdAndLength(byte id, ByteBuffer params, byte expectedId, int expectedLength) {
+        validateId(id, expectedId);
+        if (params.limit() - params.position() != expectedLength)
+            throwUnexpectedLength();
+    }
+
+    /**
+     * Gets whether a bit is set
+     *
+     * @param value the value to check against
+     * @param mask  the bitmask
+     * @return whether the bits indicated by the bitmask are set
+     */
+    protected boolean getBit(int value, int mask) {
+        return (value & mask) != 0;
+    }
+
+    /**
+     * Sets a bit in a value
+     *
+     * @param value the value to modify
+     * @param mask  the bitmask
+     * @param set   whether to set or clear the bits
+     * @return the modified value
+     */
+    protected int setBit(int value, int mask, boolean set) {
+        if (set) {
+            return value | mask;
+        } else {
+            return value & ~mask;
+        }
+    }
+
+    /**
+     * Sets a bit in a value
+     *
+     * @param value the value to modify
+     * @param mask  the bitmask
+     * @param set   whether to set or clear the bits
+     * @return the modified value
+     */
+    protected short setBit(short value, int mask, boolean set) {
+        if (set) {
+            return (short) (value | mask);
+        } else {
+            return (short) (value & ~mask);
+        }
+    }
+
+    /**
+     * Sets a bit in a value
+     *
+     * @param value the value to modify
+     * @param mask  the bitmask
+     * @param set   whether to set or clear the bits
+     * @return the modified value
+     */
+    protected byte setBit(byte value, int mask, boolean set) {
+        if (set) {
+            return (byte) (value | mask);
+        } else {
+            return (byte) (value & ~mask);
+        }
+    }
+
+    /**
+     * Find index of first bit that is set
+     *
+     * @param value the value to look at
+     * @return the index of the lowest set bit, starting at 0 for least significant bit; -1 if no bits set
+     */
+    protected int getLowestSetBitIndex(int value) {
+        if (value == 0) return -1;
+
+        int i = 0;
+        while ((value & 1) == 0) {
+            ++i;
+            value >>= 1;
+        }
+        return i;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/Cmd22Command.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/Cmd22Command.java
new file mode 100644
index 0000000..ff399fe
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/Cmd22Command.java
@@ -0,0 +1,83 @@
+/*  Copyright (C) 2020-2024 Yukai Li
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands;
+
+import java.nio.ByteBuffer;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
+
+public class Cmd22Command extends BaseCommand {
+    private byte op;
+    private short unknown;
+
+    private boolean setSuccess;
+
+    public byte getOp() {
+        return op;
+    }
+
+    public void setOp(byte op) {
+        if (op != OP_GET && op != OP_SET)
+            throw new IllegalArgumentException("Operation must be get or set");
+        this.op = op;
+    }
+
+    public short getUnknown() {
+        return unknown;
+    }
+
+    public void setUnknown(short unknown) {
+        this.unknown = unknown;
+    }
+
+    public boolean isSetSuccess() {
+        return setSuccess;
+    }
+
+    @Override
+    protected void deserializeParams(byte id, ByteBuffer params) {
+        validateId(id, LefunConstants.CMD_UNKNOWN_22);
+
+        int paramsLength = params.limit() - params.position();
+        if (paramsLength < 1)
+            throwUnexpectedLength();
+
+        op = params.get();
+        if (op == OP_GET) {
+            if (paramsLength != 3)
+                throwUnexpectedLength();
+
+            unknown = params.getShort();
+        } else if (op == OP_SET) {
+            if (paramsLength != 2)
+                throwUnexpectedLength();
+
+            setSuccess = params.get() == 1;
+        } else {
+            throw new IllegalArgumentException("Invalid operation type received");
+        }
+    }
+
+    @Override
+    protected byte serializeParams(ByteBuffer params) {
+        params.put(op);
+        if (op == OP_SET) {
+            params.putShort(unknown);
+        }
+        return LefunConstants.CMD_UNKNOWN_22;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/Cmd25Command.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/Cmd25Command.java
new file mode 100644
index 0000000..abd2b8b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/Cmd25Command.java
@@ -0,0 +1,85 @@
+/*  Copyright (C) 2020-2024 Yukai Li
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands;
+
+import java.nio.ByteBuffer;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
+
+public class Cmd25Command extends BaseCommand {
+    private byte op;
+    private int unknown;
+
+    private boolean setSuccess;
+
+    public byte getOp() {
+        return op;
+    }
+
+    public void setOp(byte op) {
+        if (op != OP_GET && op != OP_SET)
+            throw new IllegalArgumentException("Operation must be get or set");
+        this.op = op;
+    }
+
+    public int getUnknown() {
+        return unknown;
+    }
+
+    public void setUnknown(int unknown) {
+        if (unknown < 0 || unknown > 65000)
+            throw new IllegalArgumentException("Value must be between 0 and 65000 inclusive");
+        this.unknown = unknown;
+    }
+
+    public boolean isSetSuccess() {
+        return setSuccess;
+    }
+
+    @Override
+    protected void deserializeParams(byte id, ByteBuffer params) {
+        validateId(id, LefunConstants.CMD_UNKNOWN_25);
+
+        int paramsLength = params.limit() - params.position();
+        if (paramsLength < 1)
+            throwUnexpectedLength();
+
+        op = params.get();
+        if (op == OP_GET) {
+            if (paramsLength != 5)
+                throwUnexpectedLength();
+
+            unknown = params.getInt();
+        } else if (op == OP_SET) {
+            if (paramsLength != 2)
+                throwUnexpectedLength();
+
+            setSuccess = params.get() == 1;
+        } else {
+            throw new IllegalArgumentException("Invalid operation type received");
+        }
+    }
+
+    @Override
+    protected byte serializeParams(ByteBuffer params) {
+        params.put(op);
+        if (op == OP_SET) {
+            params.putInt(unknown);
+        }
+        return LefunConstants.CMD_UNKNOWN_25;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/FeaturesCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/FeaturesCommand.java
new file mode 100644
index 0000000..b6901b0
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/FeaturesCommand.java
@@ -0,0 +1,94 @@
+/*  Copyright (C) 2020-2024 Yukai Li
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands;
+
+import java.nio.ByteBuffer;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
+
+public class FeaturesCommand extends BaseCommand {
+    public static final int FEATURE_RAISE_TO_WAKE = 0;
+    public static final int FEATURE_SEDENTARY_REMINDER = 1;
+    public static final int FEATURE_HYDRATION_REMINDER = 2;
+    public static final int FEATURE_REMOTE_CAMERA = 3;
+    public static final int FEATURE_UNKNOWN_4 = 4;
+    public static final int FEATURE_ANTI_LOST = 5;
+
+    private byte op;
+    private short features;
+
+    private boolean setSuccess;
+
+    public byte getOp() {
+        return op;
+    }
+
+    public void setOp(byte op) {
+        if (op != OP_GET && op != OP_SET)
+            throw new IllegalArgumentException("Operation must be get or set");
+        this.op = op;
+    }
+
+    public boolean getFeature(int index) {
+        if (index < 0 || index > 5)
+            throw new IllegalArgumentException("Index must be between 0 and 5 inclusive");
+        return getBit(features, 1 << index);
+    }
+
+    public void setFeature(int index, boolean enabled) {
+        if (index < 0 || index > 5)
+            throw new IllegalArgumentException("Index must be between 0 and 5 inclusive");
+        features = setBit(features, 1 << index, enabled);
+    }
+
+    public boolean isSetSuccess() {
+        return setSuccess;
+    }
+
+    @Override
+    protected void deserializeParams(byte id, ByteBuffer params) {
+        validateId(id, LefunConstants.CMD_FEATURES);
+
+        int paramsLength = params.limit() - params.position();
+        if (paramsLength < 1)
+            throwUnexpectedLength();
+
+        op = params.get();
+        if (op == OP_GET) {
+            if (paramsLength != 3)
+                throwUnexpectedLength();
+
+            features = params.getShort();
+        } else if (op == OP_SET) {
+            if (paramsLength != 2)
+                throwUnexpectedLength();
+
+            setSuccess = params.get() == 1;
+        } else {
+            throw new IllegalArgumentException("Invalid operation type received");
+        }
+    }
+
+    @Override
+    protected byte serializeParams(ByteBuffer params) {
+        params.put(op);
+        if (op == OP_SET) {
+            params.putShort(features);
+        }
+        return LefunConstants.CMD_FEATURES;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/FindDeviceCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/FindDeviceCommand.java
new file mode 100644
index 0000000..2ccec76
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/FindDeviceCommand.java
@@ -0,0 +1,41 @@
+/*  Copyright (C) 2020-2024 Yukai Li
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands;
+
+import java.nio.ByteBuffer;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
+
+public class FindDeviceCommand extends BaseCommand {
+    private boolean success;
+
+    public boolean isSuccess() {
+        return success;
+    }
+
+    @Override
+    protected void deserializeParams(byte id, ByteBuffer params) {
+        validateIdAndLength(id, params, LefunConstants.CMD_FIND_DEVICE, 1);
+
+        success = params.get() == 1;
+    }
+
+    @Override
+    protected byte serializeParams(ByteBuffer params) {
+        return LefunConstants.CMD_FIND_DEVICE;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/FindPhoneCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/FindPhoneCommand.java
new file mode 100644
index 0000000..71c6aff
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/FindPhoneCommand.java
@@ -0,0 +1,33 @@
+/*  Copyright (C) 2020-2024 Yukai Li
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands;
+
+import java.nio.ByteBuffer;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
+
+public class FindPhoneCommand extends BaseCommand {
+    @Override
+    protected void deserializeParams(byte id, ByteBuffer params) {
+        validateIdAndLength(id, params, LefunConstants.CMD_FIND_PHONE, 0);
+    }
+
+    @Override
+    protected byte serializeParams(ByteBuffer params) {
+        return LefunConstants.CMD_FIND_PHONE;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/GetActivityDataCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/GetActivityDataCommand.java
new file mode 100644
index 0000000..b28d78a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/GetActivityDataCommand.java
@@ -0,0 +1,108 @@
+/*  Copyright (C) 2020-2024 Yukai Li
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands;
+
+import java.nio.ByteBuffer;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
+
+public class GetActivityDataCommand extends BaseCommand {
+    private byte daysAgo;
+    private byte totalRecords;
+    private byte currentRecord;
+    private byte year;
+    private byte month;
+    private byte day;
+    private byte hour;
+    private byte minute;
+    private short steps;
+    private short distance; // m
+    private short calories; // calories
+
+    public byte getDaysAgo() {
+        return daysAgo;
+    }
+
+    public void setDaysAgo(byte daysAgo) {
+        if (daysAgo < 0 || daysAgo > 6)
+            throw new IllegalArgumentException("Days ago must be between 0 and 6 inclusive");
+        this.daysAgo = daysAgo;
+    }
+
+    public byte getTotalRecords() {
+        return totalRecords;
+    }
+
+    public byte getCurrentRecord() {
+        return currentRecord;
+    }
+
+    public byte getYear() {
+        return year;
+    }
+
+    public byte getMonth() {
+        return month;
+    }
+
+    public byte getDay() {
+        return day;
+    }
+
+    public byte getHour() {
+        return hour;
+    }
+
+    public byte getMinute() {
+        return minute;
+    }
+
+    public short getSteps() {
+        return steps;
+    }
+
+    public short getDistance() {
+        return distance;
+    }
+
+    public short getCalories() {
+        return calories;
+    }
+
+    @Override
+    protected void deserializeParams(byte id, ByteBuffer params) {
+        validateIdAndLength(id, params, LefunConstants.CMD_ACTIVITY_DATA, 14);
+
+        daysAgo = params.get();
+        totalRecords = params.get();
+        currentRecord = params.get();
+        year = params.get();
+        month = params.get();
+        day = params.get();
+        hour = params.get();
+        minute = params.get();
+        steps = params.getShort();
+        distance = params.getShort();
+        calories = params.getShort();
+    }
+
+    @Override
+    protected byte serializeParams(ByteBuffer params) {
+        params.put(daysAgo);
+        return LefunConstants.CMD_ACTIVITY_DATA;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/GetBatteryLevelCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/GetBatteryLevelCommand.java
new file mode 100644
index 0000000..0612ace
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/GetBatteryLevelCommand.java
@@ -0,0 +1,41 @@
+/*  Copyright (C) 2020-2024 Yukai Li
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands;
+
+import java.nio.ByteBuffer;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
+
+public class GetBatteryLevelCommand extends BaseCommand {
+    private byte batteryLevel;
+
+    public byte getBatteryLevel() {
+        return batteryLevel;
+    }
+
+    @Override
+    protected void deserializeParams(byte id, ByteBuffer params) {
+        validateIdAndLength(id, params, LefunConstants.CMD_BATTERY_LEVEL, 1);
+
+        batteryLevel = params.get();
+    }
+
+    @Override
+    protected byte serializeParams(ByteBuffer params) {
+        return LefunConstants.CMD_BATTERY_LEVEL;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/GetFirmwareInfoCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/GetFirmwareInfoCommand.java
new file mode 100644
index 0000000..a156466
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/GetFirmwareInfoCommand.java
@@ -0,0 +1,76 @@
+/*  Copyright (C) 2020-2024 Yukai Li
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands;
+
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
+
+public class GetFirmwareInfoCommand extends BaseCommand {
+    private short supportCode;
+    private short devTypeReserveCode;
+    private String typeCode;
+    private short hardwareVersion;
+    private short softwareVersion;
+    private String vendorCode;
+
+    public short getSupportCode() {
+        return supportCode;
+    }
+
+    public short getDevTypeReserveCode() {
+        return devTypeReserveCode;
+    }
+
+    public String getTypeCode() {
+        return typeCode;
+    }
+
+    public short getHardwareVersion() {
+        return hardwareVersion;
+    }
+
+    public short getSoftwareVersion() {
+        return softwareVersion;
+    }
+
+    public String getVendorCode() {
+        return vendorCode;
+    }
+
+    @Override
+    protected void deserializeParams(byte id, ByteBuffer params) {
+        validateIdAndLength(id, params, LefunConstants.CMD_FIRMWARE_INFO, 16);
+
+        supportCode = (short) (params.get() | (params.get() << 8));
+        devTypeReserveCode = params.getShort();
+        byte[] typeCodeBytes = new byte[4];
+        params.get(typeCodeBytes);
+        typeCode = new String(typeCodeBytes, StandardCharsets.US_ASCII);
+        hardwareVersion = params.getShort();
+        softwareVersion = params.getShort();
+        byte[] vendorCodeBytes = new byte[4];
+        params.get(vendorCodeBytes);
+        vendorCode = new String(vendorCodeBytes, StandardCharsets.US_ASCII);
+    }
+
+    @Override
+    protected byte serializeParams(ByteBuffer params) {
+        return LefunConstants.CMD_FIRMWARE_INFO;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/GetPpgDataCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/GetPpgDataCommand.java
new file mode 100644
index 0000000..6dbca28
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/GetPpgDataCommand.java
@@ -0,0 +1,135 @@
+/*  Copyright (C) 2020-2024 Yukai Li
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands;
+
+import java.nio.ByteBuffer;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
+
+public class GetPpgDataCommand extends BaseCommand {
+    private byte ppgType;
+    private short totalRecords;
+    private short currentRecord;
+    private byte year;
+    private byte month;
+    private byte day;
+    private byte hour;
+    private byte minute;
+    private byte second;
+    private byte[] ppgData;
+
+    public int getPpgType() {
+        return getLowestSetBitIndex(ppgType);
+    }
+
+    public void setPpgType(int type) {
+        if (type < 0 || type > 2)
+            throw new IllegalArgumentException("Invalid PPG type");
+        this.ppgType = (byte)(1 << type);
+    }
+
+    public short getTotalRecords() {
+        return totalRecords;
+    }
+
+    public short getCurrentRecord() {
+        return currentRecord;
+    }
+
+    public byte getYear() {
+        return year;
+    }
+
+    public byte getMonth() {
+        return month;
+    }
+
+    public byte getDay() {
+        return day;
+    }
+
+    public byte getHour() {
+        return hour;
+    }
+
+    public byte getMinute() {
+        return minute;
+    }
+
+    public byte getSecond() {
+        return second;
+    }
+
+    public byte[] getPpgData() {
+        return ppgData;
+    }
+
+    @Override
+    protected void deserializeParams(byte id, ByteBuffer params) {
+        validateId(id, LefunConstants.CMD_PPG_DATA);
+
+        int paramsLength = params.limit() - params.position();
+        if (paramsLength < 9)
+            throwUnexpectedLength();
+
+        ppgType = params.get();
+        totalRecords = params.get();
+        currentRecord = params.get();
+        year = params.get();
+        month = params.get();
+        day = params.get();
+        hour = params.get();
+        minute = params.get();
+        second = params.get();
+
+        int typeIndex = getPpgType();
+        int dataLength;
+        switch (typeIndex) {
+            case LefunConstants.PPG_TYPE_HEART_RATE:
+            case LefunConstants.PPG_TYPE_BLOOD_OXYGEN:
+                dataLength = 1;
+                break;
+            case LefunConstants.PPG_TYPE_BLOOD_PRESSURE:
+                dataLength = 2;
+                break;
+            default:
+                throw new IllegalArgumentException("Unknown PPG type");
+        }
+
+        if (paramsLength < dataLength + 9)
+            throwUnexpectedLength();
+
+        ppgData = new byte[dataLength];
+        params.get(ppgData);
+
+        // Extended count/index
+        if (paramsLength == dataLength + 11)
+        {
+            totalRecords |= params.get() << 8;
+            currentRecord |= params.get() << 8;
+        }
+        else if (paramsLength > dataLength + 11) {
+            throwUnexpectedLength();
+        }
+    }
+
+    @Override
+    protected byte serializeParams(ByteBuffer params) {
+        params.put(ppgType);
+        return LefunConstants.CMD_PPG_DATA;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/GetSleepDataCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/GetSleepDataCommand.java
new file mode 100644
index 0000000..dba937f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/GetSleepDataCommand.java
@@ -0,0 +1,100 @@
+/*  Copyright (C) 2020-2024 Yukai Li
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands;
+
+import java.nio.ByteBuffer;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
+
+public class GetSleepDataCommand extends BaseCommand {
+    public static final int SLEEP_TYPE_AWAKE = 1;
+    public static final int SLEEP_TYPE_LIGHT_SLEEP = 2;
+    public static final int SLEEP_TYPE_DEEP_SLEEP = 3;
+
+    private byte daysAgo;
+    private short totalRecords;
+    private short currentRecord;
+    private byte year;
+    private byte month;
+    private byte day;
+    private byte hour;
+    private byte minute;
+    private byte sleepType;
+
+    public byte getDaysAgo() {
+        return daysAgo;
+    }
+
+    public void setDaysAgo(byte daysAgo) {
+        if (daysAgo < 0 || daysAgo > 6)
+            throw new IllegalArgumentException("Days ago must be between 0 and 6 inclusive");
+        this.daysAgo = daysAgo;
+    }
+
+    public short getTotalRecords() {
+        return totalRecords;
+    }
+
+    public short getCurrentRecord() {
+        return currentRecord;
+    }
+
+    public byte getYear() {
+        return year;
+    }
+
+    public byte getMonth() {
+        return month;
+    }
+
+    public byte getDay() {
+        return day;
+    }
+
+    public byte getHour() {
+        return hour;
+    }
+
+    public byte getMinute() {
+        return minute;
+    }
+
+    public byte getSleepType() {
+        return sleepType;
+    }
+
+    @Override
+    protected void deserializeParams(byte id, ByteBuffer params) {
+        validateIdAndLength(id, params, LefunConstants.CMD_SLEEP_DATA, 11);
+
+        daysAgo = params.get();
+        totalRecords = params.getShort();
+        currentRecord = params.getShort();
+        year = params.get();
+        month = params.get();
+        day = params.get();
+        hour = params.get();
+        minute = params.get();
+        sleepType = params.get();
+    }
+
+    @Override
+    protected byte serializeParams(ByteBuffer params) {
+        params.put(daysAgo);
+        return LefunConstants.CMD_SLEEP_DATA;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/GetSleepTimeCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/GetSleepTimeCommand.java
new file mode 100644
index 0000000..fe4ae4e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/GetSleepTimeCommand.java
@@ -0,0 +1,73 @@
+/*  Copyright (C) 2020-2024 Yukai Li
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands;
+
+import java.nio.ByteBuffer;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
+
+public class GetSleepTimeCommand extends BaseCommand {
+    private byte daysAgo;
+    private byte year;
+    private byte month;
+    private byte day;
+    private short minutes;
+
+    public byte getDaysAgo() {
+        return daysAgo;
+    }
+
+    public void setDaysAgo(byte daysAgo) {
+        if (daysAgo < 0 || daysAgo > 6)
+            throw new IllegalArgumentException("Days ago must be between 0 and 6 inclusive");
+        this.daysAgo = daysAgo;
+    }
+
+    public byte getYear() {
+        return year;
+    }
+
+    public byte getMonth() {
+        return month;
+    }
+
+    public byte getDay() {
+        return day;
+    }
+
+    public short getMinutes() {
+        return minutes;
+    }
+
+
+    @Override
+    protected void deserializeParams(byte id, ByteBuffer params) {
+        validateIdAndLength(id, params, LefunConstants.CMD_SLEEP_TIME_DATA, 6);
+
+        daysAgo = params.get();
+        year = params.get();
+        month = params.get();
+        day = params.get();
+        minutes = params.getShort();
+    }
+
+    @Override
+    protected byte serializeParams(ByteBuffer params) {
+        params.put(daysAgo);
+        return LefunConstants.CMD_SLEEP_TIME_DATA;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/GetStepsDataCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/GetStepsDataCommand.java
new file mode 100644
index 0000000..3010fb7
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/GetStepsDataCommand.java
@@ -0,0 +1,84 @@
+/*  Copyright (C) 2020-2024 Yukai Li
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands;
+
+import java.nio.ByteBuffer;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
+
+public class GetStepsDataCommand extends BaseCommand {
+    private byte daysAgo;
+    private byte year;
+    private byte month;
+    private byte day;
+    private int steps;
+    private int distance; // m
+    private int calories; // calories
+
+    public byte getDaysAgo() {
+        return daysAgo;
+    }
+
+    public void setDaysAgo(byte daysAgo) {
+        if (daysAgo < 0 || daysAgo > 6)
+            throw new IllegalArgumentException("Days ago must be between 0 and 6 inclusive");
+        this.daysAgo = daysAgo;
+    }
+
+    public byte getYear() {
+        return year;
+    }
+
+    public byte getMonth() {
+        return month;
+    }
+
+    public byte getDay() {
+        return day;
+    }
+
+    public int getSteps() {
+        return steps;
+    }
+
+    public int getDistance() {
+        return distance;
+    }
+
+    public int getCalories() {
+        return calories;
+    }
+
+    @Override
+    protected void deserializeParams(byte id, ByteBuffer params) {
+        validateIdAndLength(id, params, LefunConstants.CMD_STEPS_DATA, 16);
+
+        daysAgo = params.get();
+        year = params.get();
+        month = params.get();
+        day = params.get();
+        steps = params.getInt();
+        distance = params.getInt();
+        calories = params.getInt();
+    }
+
+    @Override
+    protected byte serializeParams(ByteBuffer params) {
+        params.put(daysAgo);
+        return LefunConstants.CMD_STEPS_DATA;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/HydrationReminderIntervalCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/HydrationReminderIntervalCommand.java
new file mode 100644
index 0000000..1194020
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/HydrationReminderIntervalCommand.java
@@ -0,0 +1,85 @@
+/*  Copyright (C) 2020-2024 Yukai Li
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands;
+
+import java.nio.ByteBuffer;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
+
+public class HydrationReminderIntervalCommand extends BaseCommand {
+    private byte op;
+    private byte hydrationReminderInterval;
+
+    private boolean setSuccess;
+
+    public byte getOp() {
+        return op;
+    }
+
+    public void setOp(byte op) {
+        if (op != OP_GET && op != OP_SET)
+            throw new IllegalArgumentException("Operation must be get or set");
+        this.op = op;
+    }
+
+    public byte getHydrationReminderInterval() {
+        return hydrationReminderInterval;
+    }
+
+    public void setHydrationReminderInterval(byte hydrationReminderInterval) {
+        if (hydrationReminderInterval == 0)
+            throw new IllegalArgumentException("Interval must be non-zero");
+        this.hydrationReminderInterval = hydrationReminderInterval;
+    }
+
+    public boolean isSetSuccess() {
+        return setSuccess;
+    }
+
+    @Override
+    protected void deserializeParams(byte id, ByteBuffer params) {
+        validateId(id, LefunConstants.CMD_HYDRATION_REMINDER_INTERVAL);
+
+        int paramsLength = params.limit() - params.position();
+        if (paramsLength < 1)
+            throwUnexpectedLength();
+
+        op = params.get();
+        if (op == OP_GET) {
+            if (paramsLength != 2)
+                throwUnexpectedLength();
+
+            hydrationReminderInterval = params.get();
+        } else if (op == OP_SET) {
+            if (paramsLength != 2)
+                throwUnexpectedLength();
+
+            setSuccess = params.get() == 1;
+        } else {
+            throw new IllegalArgumentException("Invalid operation type received");
+        }
+    }
+
+    @Override
+    protected byte serializeParams(ByteBuffer params) {
+        params.put(op);
+        if (op == OP_SET) {
+            params.put(hydrationReminderInterval);
+        }
+        return LefunConstants.CMD_HYDRATION_REMINDER_INTERVAL;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/NotificationCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/NotificationCommand.java
new file mode 100644
index 0000000..04facc7
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/NotificationCommand.java
@@ -0,0 +1,123 @@
+/*  Copyright (C) 2020-2024 Yukai Li
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands;
+
+import java.nio.ByteBuffer;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
+
+public class NotificationCommand extends BaseCommand {
+    public static final byte SERVICE_TYPE_CALL = 0;
+    public static final byte SERVICE_TYPE_TEXT = 1;
+    public static final byte SERVICE_TYPE_QQ = 2;
+    public static final byte SERVICE_TYPE_WECHAT = 3;
+    public static final byte SERVICE_TYPE_EXTENDED = 4;
+
+    public static final byte EXTENDED_SERVICE_TYPE_FACEBOOK = 1;
+    public static final byte EXTENDED_SERVICE_TYPE_TWITTER = 2;
+    public static final byte EXTENDED_SERVICE_TYPE_LINKEDIN = 3;
+    public static final byte EXTENDED_SERVICE_TYPE_WHATSAPP = 4;
+    public static final byte EXTENDED_SERVICE_TYPE_LINE = 5;
+    public static final byte EXTENDED_SERVICE_TYPE_KAKAOTALK = 6;
+
+    public static final int MAX_PAYLOAD_LENGTH = 13;
+    public static final int MAX_MESSAGE_LENGTH = 254;
+
+    private byte serviceType;
+    private byte totalPieces;
+    private byte currentPiece;
+    private byte extendedServiceType;
+    private byte[] payload;
+
+    public int getServiceType() {
+        return getLowestSetBitIndex(serviceType);
+    }
+
+    public void setServiceType(int type) {
+        if (type < 0 || type > 4)
+            throw new IllegalArgumentException("Invalid service type");
+        this.serviceType = (byte) (1 << type);
+    }
+
+    public byte getTotalPieces() {
+        return totalPieces;
+    }
+
+    public void setTotalPieces(byte totalPieces) {
+        // This check isn't on device, but should probably be added
+        if (totalPieces == 0)
+            throw new IllegalArgumentException("Total pieces must not be 0");
+        this.totalPieces = totalPieces;
+    }
+
+    public byte getCurrentPiece() {
+        return currentPiece;
+    }
+
+    public void setCurrentPiece(byte currentPiece) {
+        // This check isn't on device, but should probably be added
+        if (currentPiece == 0)
+            throw new IllegalArgumentException("Current piece must not be 0");
+        this.currentPiece = currentPiece;
+    }
+
+    public byte getExtendedServiceType() {
+        return extendedServiceType;
+    }
+
+    public void setExtendedServiceType(byte extendedServiceType) {
+        this.extendedServiceType = extendedServiceType;
+    }
+
+    public byte[] getPayload() {
+        return payload;
+    }
+
+    public void setPayload(byte[] payload) {
+        if (payload == null)
+            throw new IllegalArgumentException("Payload must not be null");
+        if (payload.length > 13)
+            throw new IllegalArgumentException("Payload is too long");
+        this.payload = payload;
+    }
+
+    @Override
+    protected void deserializeParams(byte id, ByteBuffer params) {
+        // We should not receive a response for this
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    protected byte serializeParams(ByteBuffer params) {
+        boolean hasExtendedServiceType = (serviceType & (1 << SERVICE_TYPE_EXTENDED)) != 0
+                && (extendedServiceType & 0x0f) != 0;
+        int maxPayloadLength = MAX_PAYLOAD_LENGTH;
+        if (hasExtendedServiceType) maxPayloadLength -= 1;
+
+        if (payload.length > maxPayloadLength)
+            throw new IllegalStateException("Payload is too long");
+
+        params.put(serviceType);
+        params.put(totalPieces);
+        params.put(currentPiece);
+        if (hasExtendedServiceType)
+            params.put(extendedServiceType);
+        params.put(payload);
+
+        return LefunConstants.CMD_NOTIFICATION;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/PpgResultCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/PpgResultCommand.java
new file mode 100644
index 0000000..ffdb6b0
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/PpgResultCommand.java
@@ -0,0 +1,71 @@
+/*  Copyright (C) 2020-2024 Yukai Li
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands;
+
+import java.nio.ByteBuffer;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
+
+public class PpgResultCommand extends BaseCommand {
+    private byte ppgType;
+    private byte[] ppgData;
+
+    public int getPpgType() {
+        return getLowestSetBitIndex(ppgType);
+    }
+
+    public byte[] getPpgData() {
+        return ppgData;
+    }
+
+    @Override
+    protected void deserializeParams(byte id, ByteBuffer params) {
+        validateId(id, LefunConstants.CMD_PPG_RESULT);
+
+        int paramsLength = params.limit() - params.position();
+        if (paramsLength < 1)
+            throwUnexpectedLength();
+
+        ppgType = params.get();
+
+        int typeIndex = getPpgType();
+        int dataLength;
+        switch (typeIndex) {
+            case LefunConstants.PPG_TYPE_HEART_RATE:
+            case LefunConstants.PPG_TYPE_BLOOD_OXYGEN:
+                dataLength = 1;
+                break;
+            case LefunConstants.PPG_TYPE_BLOOD_PRESSURE:
+                dataLength = 2;
+                break;
+            default:
+                throw new IllegalArgumentException("Unknown PPG type");
+        }
+
+        if (paramsLength != dataLength + 1)
+            throwUnexpectedLength();
+
+        ppgData = new byte[dataLength];
+        params.get(ppgData);
+    }
+
+    @Override
+    protected byte serializeParams(ByteBuffer params) {
+        // No handler on device side
+        throw new UnsupportedOperationException();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/ProfileCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/ProfileCommand.java
new file mode 100644
index 0000000..edf59cf
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/ProfileCommand.java
@@ -0,0 +1,129 @@
+/*  Copyright (C) 2020-2024 Yukai Li
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands;
+
+import java.nio.ByteBuffer;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
+
+public class ProfileCommand extends BaseCommand {
+    public static final byte GENDER_FEMALE = 0;
+    public static final byte GENDER_MALE = 1;
+
+    private byte op;
+    private byte gender;
+    private byte height; // cm
+    private byte weight; // kg
+    private byte age; // years
+
+    private boolean setSuccess;
+
+    public byte getOp() {
+        return op;
+    }
+
+    public void setOp(byte op) {
+        if (op != OP_GET && op != OP_SET)
+            throw new IllegalArgumentException("Operation must be get or set");
+        this.op = op;
+    }
+
+    public byte getGender() {
+        return gender;
+    }
+
+    public void setGender(byte gender) {
+        if (gender != GENDER_FEMALE && gender != GENDER_MALE)
+            throw new IllegalArgumentException("Invalid gender");
+        this.gender = gender;
+    }
+
+    public byte getHeight() {
+        return height;
+    }
+
+    public void setHeight(byte height) {
+        int intHeight = (int)height & 0xff;
+        if (intHeight < 40 || intHeight > 210)
+            throw new IllegalArgumentException("Height must be between 40 and 210 cm inclusive");
+        this.height = height;
+    }
+
+    public byte getWeight() {
+        return weight;
+    }
+
+    public void setWeight(byte weight) {
+        int intWeight = (int)weight & 0xff;
+        if (intWeight < 5 || intWeight > 200)
+            throw new IllegalArgumentException("Weight must be between 5 and 200 kg inclusive");
+        this.weight = weight;
+    }
+
+    public byte getAge() {
+        return age;
+    }
+
+    public void setAge(byte age) {
+        if (age < 0 || age > 110)
+            throw new IllegalArgumentException("Age must be between 0 and 110 years inclusive");
+        this.age = age;
+    }
+
+    public boolean isSetSuccess() {
+        return setSuccess;
+    }
+
+    @Override
+    protected void deserializeParams(byte id, ByteBuffer params) {
+        validateId(id, LefunConstants.CMD_PROFILE);
+
+        int paramsLength = params.limit() - params.position();
+        if (paramsLength < 1)
+            throwUnexpectedLength();
+
+        op = params.get();
+        if (op == OP_GET) {
+            if (paramsLength != 5)
+                throwUnexpectedLength();
+
+            gender = params.get();
+            height = params.get();
+            weight = params.get();
+            age = params.get();
+        } else if (op == OP_SET) {
+            if (paramsLength != 2)
+                throwUnexpectedLength();
+
+            setSuccess = params.get() == 1;
+        } else {
+            throw new IllegalArgumentException("Invalid operation type received");
+        }
+    }
+
+    @Override
+    protected byte serializeParams(ByteBuffer params) {
+        params.put(op);
+        if (op == OP_SET) {
+            params.put(gender);
+            params.put(height);
+            params.put(weight);
+            params.put(age);
+        }
+        return LefunConstants.CMD_PROFILE;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/RemoteCameraTriggeredCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/RemoteCameraTriggeredCommand.java
new file mode 100644
index 0000000..5c0528b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/RemoteCameraTriggeredCommand.java
@@ -0,0 +1,34 @@
+/*  Copyright (C) 2020-2024 Yukai Li
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands;
+
+import java.nio.ByteBuffer;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
+
+public class RemoteCameraTriggeredCommand extends BaseCommand {
+    @Override
+    protected void deserializeParams(byte id, ByteBuffer params) {
+        validateIdAndLength(id, params, LefunConstants.CMD_REMOTE_CAMERA_TRIGGERED, 0);
+    }
+
+    @Override
+    protected byte serializeParams(ByteBuffer params) {
+        // No handler on device side
+        throw new UnsupportedOperationException();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/RequestBondingCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/RequestBondingCommand.java
new file mode 100644
index 0000000..671a65d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/RequestBondingCommand.java
@@ -0,0 +1,44 @@
+/*  Copyright (C) 2020-2024 Yukai Li
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands;
+
+import java.nio.ByteBuffer;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
+
+public class RequestBondingCommand extends BaseCommand {
+    public static final byte STATUS_ALREADY_BONDED = 0;
+    public static final byte STATUS_BONDING_SUCCESSFUL = 1;
+
+    private byte status;
+
+    public byte getStatus() {
+        return status;
+    }
+
+    @Override
+    protected void deserializeParams(byte id, ByteBuffer params) {
+        validateIdAndLength(id, params, LefunConstants.CMD_BONDING_REQUEST, 1);
+
+        status = params.get();
+    }
+
+    @Override
+    protected byte serializeParams(ByteBuffer params) {
+        return LefunConstants.CMD_BONDING_REQUEST;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/SedentaryReminderIntervalCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/SedentaryReminderIntervalCommand.java
new file mode 100644
index 0000000..c359311
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/SedentaryReminderIntervalCommand.java
@@ -0,0 +1,85 @@
+/*  Copyright (C) 2020-2024 Yukai Li
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands;
+
+import java.nio.ByteBuffer;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
+
+public class SedentaryReminderIntervalCommand extends BaseCommand {
+    private byte op;
+    private byte sedentaryReminderInterval;
+
+    private boolean setSuccess;
+
+    public byte getOp() {
+        return op;
+    }
+
+    public void setOp(byte op) {
+        if (op != OP_GET && op != OP_SET)
+            throw new IllegalArgumentException("Operation must be get or set");
+        this.op = op;
+    }
+
+    public byte getSedentaryReminderInterval() {
+        return sedentaryReminderInterval;
+    }
+
+    public void setSedentaryReminderInterval(byte sedentaryReminderInterval) {
+        if (sedentaryReminderInterval == 0)
+            throw new IllegalArgumentException("Interval must be non-zero");
+        this.sedentaryReminderInterval = sedentaryReminderInterval;
+    }
+
+    public boolean isSetSuccess() {
+        return setSuccess;
+    }
+
+    @Override
+    protected void deserializeParams(byte id, ByteBuffer params) {
+        validateId(id, LefunConstants.CMD_SEDENTARY_REMINDER_INTERVAL);
+
+        int paramsLength = params.limit() - params.position();
+        if (paramsLength < 1)
+            throwUnexpectedLength();
+
+        op = params.get();
+        if (op == OP_GET) {
+            if (paramsLength != 2)
+                throwUnexpectedLength();
+
+            sedentaryReminderInterval = params.get();
+        } else if (op == OP_SET) {
+            if (paramsLength != 2)
+                throwUnexpectedLength();
+
+            setSuccess = params.get() == 1;
+        } else {
+            throw new IllegalArgumentException("Invalid operation type received");
+        }
+    }
+
+    @Override
+    protected byte serializeParams(ByteBuffer params) {
+        params.put(op);
+        if (op == OP_SET) {
+            params.put(sedentaryReminderInterval);
+        }
+        return LefunConstants.CMD_SEDENTARY_REMINDER_INTERVAL;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/SetLanguageCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/SetLanguageCommand.java
new file mode 100644
index 0000000..79ed798
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/SetLanguageCommand.java
@@ -0,0 +1,52 @@
+/*  Copyright (C) 2020-2024 Yukai Li
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands;
+
+import java.nio.ByteBuffer;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
+
+public class SetLanguageCommand extends BaseCommand {
+    private byte language;
+
+    private boolean setSuccess;
+
+    public byte getLanguage() {
+        return language;
+    }
+
+    public void setLanguage(byte language) {
+        this.language = language;
+    }
+
+    public boolean isSetSuccess() {
+        return setSuccess;
+    }
+
+    @Override
+    protected void deserializeParams(byte id, ByteBuffer params) {
+        validateIdAndLength(id, params, LefunConstants.CMD_LANGUAGE, 1);
+
+        setSuccess = params.get() == 1;
+    }
+
+    @Override
+    protected byte serializeParams(ByteBuffer params) {
+        params.put(language);
+        return LefunConstants.CMD_LANGUAGE;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/SetRemoteCameraCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/SetRemoteCameraCommand.java
new file mode 100644
index 0000000..91fab2c
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/SetRemoteCameraCommand.java
@@ -0,0 +1,52 @@
+/*  Copyright (C) 2020-2024 Yukai Li
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands;
+
+import java.nio.ByteBuffer;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
+
+public class SetRemoteCameraCommand extends BaseCommand {
+    private boolean remoteCameraEnabled;
+
+    private boolean setSuccess;
+
+    public boolean getRemoteCameraEnabled() {
+        return remoteCameraEnabled;
+    }
+
+    public void setRemoteCameraEnabled(boolean remoteCameraEnabled) {
+        this.remoteCameraEnabled = remoteCameraEnabled;
+    }
+
+    public boolean isSetSuccess() {
+        return setSuccess;
+    }
+
+    @Override
+    protected void deserializeParams(byte id, ByteBuffer params) {
+        validateIdAndLength(id, params, LefunConstants.CMD_REMOTE_CAMERA, 1);
+
+        setSuccess = params.get() == 1;
+    }
+
+    @Override
+    protected byte serializeParams(ByteBuffer params) {
+        params.put((byte)(remoteCameraEnabled ? 1 : 0));
+        return LefunConstants.CMD_REMOTE_CAMERA;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/SettingsCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/SettingsCommand.java
new file mode 100644
index 0000000..04beba4
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/SettingsCommand.java
@@ -0,0 +1,116 @@
+/*  Copyright (C) 2020-2024 Yukai Li
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands;
+
+import java.nio.ByteBuffer;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
+
+public class SettingsCommand extends BaseCommand {
+    public static final byte AM_PM_24_HOUR = 0;
+    public static final byte AM_PM_12_HOUR = 1;
+    public static final byte MEASUREMENT_UNIT_METRIC = 0;
+    public static final byte MEASUREMENT_UNIT_IMPERIAL = 1;
+
+    private byte op;
+    private byte option1;
+    private byte amPmIndicator;
+    private byte measurementUnit;
+
+    private boolean setSuccess;
+
+    public byte getOp() {
+        return op;
+    }
+
+    public void setOp(byte op) {
+        if (op != OP_GET && op != OP_SET)
+            throw new IllegalArgumentException("Operation must be get or set");
+        this.op = op;
+    }
+
+    public byte getOption1() {
+        return option1;
+    }
+
+    public void setOption1(byte option1) {
+        if (option1 != (byte)0xff && (option1 < 0 || option1 > 24))
+            throw new IllegalArgumentException("option1 must be between 0 and 24 inclusive");
+        this.option1 = option1;
+    }
+
+    public byte getAmPmIndicator() {
+        return amPmIndicator;
+    }
+
+    public void setAmPmIndicator(byte amPmIndicator) {
+        if (amPmIndicator != (byte)0xff && (amPmIndicator != AM_PM_12_HOUR && amPmIndicator != AM_PM_24_HOUR))
+            throw new IllegalArgumentException("Indicator must be 12 or 24 hours");
+        this.amPmIndicator = amPmIndicator;
+    }
+
+    public byte getMeasurementUnit() {
+        return measurementUnit;
+    }
+
+    public void setMeasurementUnit(byte measurementUnit) {
+        if (measurementUnit != (byte)0xff && (measurementUnit != MEASUREMENT_UNIT_METRIC && measurementUnit != MEASUREMENT_UNIT_IMPERIAL))
+            throw new IllegalArgumentException(("Unit must be metric or imperial"));
+        this.measurementUnit = measurementUnit;
+    }
+
+    public boolean isSetSuccess() {
+        return setSuccess;
+    }
+
+    @Override
+    protected void deserializeParams(byte id, ByteBuffer params) {
+        validateId(id, LefunConstants.CMD_SETTINGS);
+
+        int paramsLength = params.limit() - params.position();
+        if (paramsLength < 1)
+            throwUnexpectedLength();
+
+        op = params.get();
+        if (op == OP_GET) {
+            if (paramsLength != 4)
+                throwUnexpectedLength();
+
+            option1 = params.get();
+            amPmIndicator = params.get();
+            measurementUnit = params.get();
+        } else if (op == OP_SET) {
+            if (paramsLength != 2)
+                throwUnexpectedLength();
+
+            setSuccess = params.get() == 1;
+        } else {
+            throw new IllegalArgumentException("Invalid operation type received");
+        }
+    }
+
+    @Override
+    protected byte serializeParams(ByteBuffer params) {
+        params.put(op);
+        if (op == OP_SET) {
+            params.put(option1);
+            params.put(amPmIndicator);
+            params.put(measurementUnit);
+        }
+        return LefunConstants.CMD_SETTINGS;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/StartPpgSensingCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/StartPpgSensingCommand.java
new file mode 100644
index 0000000..6b02161
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/StartPpgSensingCommand.java
@@ -0,0 +1,55 @@
+/*  Copyright (C) 2020-2024 Yukai Li
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands;
+
+import java.nio.ByteBuffer;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
+
+public class StartPpgSensingCommand extends BaseCommand {
+    private byte ppgType;
+
+    private boolean setSuccess;
+
+    public int getPpgType() {
+        return getLowestSetBitIndex(ppgType);
+    }
+
+    public void setPpgType(int type) {
+        if (type < 0 || type > 2)
+            throw new IllegalArgumentException("Invalid PPG type");
+        this.ppgType = (byte)(1 << type);
+    }
+
+    public boolean isSetSuccess() {
+        return setSuccess;
+    }
+
+    @Override
+    protected void deserializeParams(byte id, ByteBuffer params) {
+        validateIdAndLength(id, params, LefunConstants.CMD_PPG_START, 2);
+
+        ppgType = params.get();
+        setSuccess = params.get() == 1;
+    }
+
+    @Override
+    protected byte serializeParams(ByteBuffer params) {
+        params.put(ppgType);
+        return LefunConstants.CMD_PPG_START;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/TimeCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/TimeCommand.java
new file mode 100644
index 0000000..1083ce8
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/TimeCommand.java
@@ -0,0 +1,148 @@
+/*  Copyright (C) 2020-2024 Yukai Li
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands;
+
+import java.nio.ByteBuffer;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
+
+public class TimeCommand extends BaseCommand {
+    private byte op;
+    private byte year;
+    private byte month;
+    private byte day;
+    private byte hour;
+    private byte minute;
+    private byte second;
+
+    private boolean setSuccess;
+
+    public byte getOp() {
+        if (op != OP_GET && op != OP_SET)
+            throw new IllegalArgumentException("Operation must be get or set");
+        return op;
+    }
+
+    public void setOp(byte op) {
+        this.op = op;
+    }
+
+    public byte getYear() {
+        return year;
+    }
+
+    public void setYear(byte year) {
+        this.year = year;
+    }
+
+    public byte getMonth() {
+        return month;
+    }
+
+    public void setMonth(byte month) {
+        if (month < 1 || month > 12)
+            throw new IllegalArgumentException("Month must be between 1 and 12 inclusive");
+        this.month = month;
+    }
+
+    public byte getDay() {
+        return day;
+    }
+
+    public void setDay(byte day) {
+        if (day < 1 || day > 31)
+            throw new IllegalArgumentException("Day must be between 1 and 31 inclusive");
+        this.day = day;
+    }
+
+    public byte getHour() {
+        return hour;
+    }
+
+    public void setHour(byte hour) {
+        if (hour < 0 || hour > 23)
+            throw new IllegalArgumentException("Hour must be between 0 and 23 inclusive");
+        this.hour = hour;
+    }
+
+    public byte getMinute() {
+        return minute;
+    }
+
+    public void setMinute(byte minute) {
+        if (minute < 0 || minute > 59)
+            throw new IllegalArgumentException("Minute must be between 0 and 59 inclusive");
+        this.minute = minute;
+    }
+
+    public byte getSecond() {
+        return second;
+    }
+
+    public void setSecond(byte second) {
+        if (second < 0 || second > 59)
+            throw new IllegalArgumentException("Second must be between 0 and 59 inclusive");
+        this.second = second;
+    }
+
+    public boolean isSetSuccess() {
+        return setSuccess;
+    }
+
+    @Override
+    protected void deserializeParams(byte id, ByteBuffer params) {
+        validateId(id, LefunConstants.CMD_TIME);
+
+        int paramsLength = params.limit() - params.position();
+        if (paramsLength < 1)
+            throwUnexpectedLength();
+
+        op = params.get();
+        if (op == OP_GET) {
+            if (paramsLength != 7)
+                throwUnexpectedLength();
+
+            year = params.get();
+            month = params.get();
+            day = params.get();
+            hour = params.get();
+            minute = params.get();
+            second = params.get();
+        } else if (op == OP_SET) {
+            if (paramsLength != 2)
+                throwUnexpectedLength();
+
+            setSuccess = params.get() == 1;
+        } else {
+            throw new IllegalArgumentException("Invalid operation type received");
+        }
+    }
+
+    @Override
+    protected byte serializeParams(ByteBuffer params) {
+        params.put(op);
+        if (op == OP_SET) {
+            params.put(year);
+            params.put(month);
+            params.put(day);
+            params.put(hour);
+            params.put(minute);
+            params.put(second);
+        }
+        return LefunConstants.CMD_TIME;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/UiPagesCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/UiPagesCommand.java
new file mode 100644
index 0000000..0ff5af6
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/UiPagesCommand.java
@@ -0,0 +1,90 @@
+/*  Copyright (C) 2020-2024 Yukai Li
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands;
+
+import java.nio.ByteBuffer;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
+
+public class UiPagesCommand extends BaseCommand {
+    // I don't know which pages since they're not implemented in my watch, so no
+    // constants here for now
+
+    private byte op;
+    private short pages;
+
+    private boolean setSuccess;
+
+    public byte getOp() {
+        return op;
+    }
+
+    public void setOp(byte op) {
+        if (op != OP_GET && op != OP_SET)
+            throw new IllegalArgumentException("Operation must be get or set");
+        this.op = op;
+    }
+
+    public boolean getPage(int index) {
+        if (index < 0 || index >= 16)
+            throw new IllegalArgumentException("Index must be between 0 and 15 inclusive");
+        return getBit(pages, 1 << index);
+    }
+
+    public void setPage(int index, boolean enabled) {
+        if (index < 0 || index >= 16)
+            throw new IllegalArgumentException("Index must be between 0 and 15 inclusive");
+        pages = setBit(pages, 1 << index, enabled);
+    }
+
+    public boolean isSetSuccess() {
+        return setSuccess;
+    }
+
+    @Override
+    protected void deserializeParams(byte id, ByteBuffer params) {
+        validateId(id, LefunConstants.CMD_UI_PAGES);
+
+        int paramsLength = params.limit() - params.position();
+        if (paramsLength < 1)
+            throwUnexpectedLength();
+
+        op = params.get();
+        if (op == OP_GET) {
+            if (paramsLength != 3)
+                throwUnexpectedLength();
+
+            pages = params.getShort();
+        } else if (op == OP_SET) {
+            if (paramsLength != 2)
+                throwUnexpectedLength();
+
+            setSuccess = params.get() == 1;
+        } else {
+            throw new IllegalArgumentException("Invalid operation type received");
+        }
+    }
+
+    @Override
+    protected byte serializeParams(ByteBuffer params) {
+        params.put(op);
+        if (op == OP_SET) {
+            params.putShort(pages);
+        }
+        return LefunConstants.CMD_UI_PAGES;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lenovo/DataType.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lenovo/DataType.java
new file mode 100644
index 0000000..b94f8eb
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lenovo/DataType.java
@@ -0,0 +1,47 @@
+/*  Copyright (C) 2019-2024 mamucho, mkusnierz
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.lenovo;
+
+public enum DataType {
+    STEPS(new byte[]{0x00, 0x00}),
+    SLEEP(new byte[]{0x00, 0x01}),
+    HEART_RATE(new byte[]{0x00, 0x02}),
+    BLOOD_PRESSURE(new byte[]{0x00, 0x06}),
+    INFRARED_TEMPERATURE(new byte[]{0x00, 0x08}),
+    ENVIRONMENT_TEMPERATURE(new byte[]{0x00, 0x09}),
+    AIR_PRESSURE(new byte[]{0x00, 0x0A});
+
+    private final byte[] value;
+
+    DataType(byte[] value) {
+        this.value = value;
+    }
+
+    public byte[] getValue() {
+        return value;
+    }
+
+    public static DataType getType(int value) {
+        for(DataType type : values()) {
+            int intVal = (type.getValue()[1] & 0xff) | ((type.getValue()[0] & 0xff) << 8);
+            if(intVal == value) {
+                return type;
+            }
+        }
+        throw new RuntimeException("No value defined for " + value);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lenovo/LenovoWatchCalibrationActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lenovo/LenovoWatchCalibrationActivity.java
new file mode 100644
index 0000000..ae913c1
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lenovo/LenovoWatchCalibrationActivity.java
@@ -0,0 +1,124 @@
+/*  Copyright (C) 2019-2024 mamucho, mkusnierz
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.lenovo;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Handler;
+import android.view.View;
+import android.widget.Button;
+import android.widget.NumberPicker;
+
+import androidx.annotation.NonNull;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBActivity;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class LenovoWatchCalibrationActivity extends AbstractGBActivity {
+
+    private static final String STATE_DEVICE = "stateDevice";
+    private GBDevice device;
+
+    private NumberPicker pickerHour, pickerMinute, pickerSecond;
+
+    private Handler handler;
+    private Runnable holdCalibration;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_watchxplus_calibration);
+
+        pickerHour = findViewById(R.id.np_hour);
+        pickerMinute = findViewById(R.id.np_minute);
+        pickerSecond = findViewById(R.id.np_second);
+
+        pickerHour.setMinValue(1);
+        pickerHour.setMaxValue(12);
+        pickerHour.setValue(12);
+        pickerMinute.setMinValue(0);
+        pickerMinute.setMaxValue(59);
+        pickerMinute.setValue(0);
+        pickerSecond.setMinValue(0);
+        pickerSecond.setMaxValue(59);
+        pickerSecond.setValue(0);
+
+        handler = new Handler();
+        holdCalibration = new Runnable() {
+            @Override
+            public void run() {
+                LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(new Intent(LenovoWatchConstants.ACTION_CALIBRATION_HOLD));
+                handler.postDelayed(this, 10000);
+            }
+        };
+
+        Intent intent = getIntent();
+        device = intent.getParcelableExtra(GBDevice.EXTRA_DEVICE);
+        if (device == null && savedInstanceState != null) {
+            device = savedInstanceState.getParcelable(STATE_DEVICE);
+        }
+        if (device == null) {
+            finish();
+        }
+
+        final Button btCalibrate = findViewById(R.id.watch9_bt_calibrate);
+        btCalibrate.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                btCalibrate.setEnabled(false);
+                handler.removeCallbacks(holdCalibration);
+                Intent calibrationData = new Intent(LenovoWatchConstants.ACTION_CALIBRATION_SEND);
+                calibrationData.putExtra(LenovoWatchConstants.VALUE_CALIBRATION_HOUR, pickerHour.getValue());
+                calibrationData.putExtra(LenovoWatchConstants.VALUE_CALIBRATION_MINUTE, pickerMinute.getValue());
+                calibrationData.putExtra(LenovoWatchConstants.VALUE_CALIBRATION_SECOND, pickerSecond.getValue());
+                LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(calibrationData);
+                finish();
+            }
+        });
+    }
+
+    @Override
+    protected void onSaveInstanceState(@NonNull Bundle outState) {
+        super.onSaveInstanceState(outState);
+        outState.putParcelable(STATE_DEVICE, device);
+    }
+
+    @Override
+    protected void onRestoreInstanceState(Bundle savedInstanceState) {
+        super.onRestoreInstanceState(savedInstanceState);
+        device = savedInstanceState.getParcelable(STATE_DEVICE);
+    }
+
+    @Override
+    protected void onStart() {
+        super.onStart();
+        Intent calibration = new Intent(LenovoWatchConstants.ACTION_CALIBRATION);
+        calibration.putExtra(LenovoWatchConstants.ACTION_ENABLE, true);
+        LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(calibration);
+        handler.postDelayed(holdCalibration, 1000);
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+        Intent calibration = new Intent(LenovoWatchConstants.ACTION_CALIBRATION);
+        calibration.putExtra(LenovoWatchConstants.ACTION_ENABLE, false);
+        LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(calibration);
+        handler.removeCallbacks(holdCalibration);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lenovo/LenovoWatchConstants.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lenovo/LenovoWatchConstants.java
new file mode 100644
index 0000000..ecf8c39
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lenovo/LenovoWatchConstants.java
@@ -0,0 +1,79 @@
+/*  Copyright (C) 2019-2024 mkusnierz
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.lenovo;
+
+public class LenovoWatchConstants {
+
+    public static final byte RESPONSE = 0x13;
+    public static final byte REQUEST = 0x31;
+
+    public static final byte WRITE_VALUE = 0x01;
+    public static final byte READ_VALUE = 0x02;
+    public static final byte TASK = 0x04;
+    public static final byte KEEP_ALIVE = -0x80;
+
+    public static final byte[] CMD_HEADER = new byte[]{0x23, 0x01, 0x00, 0x00, 0x00};
+
+    // byte[] COMMAND = new byte[]{0x23, 0x01, 0x00, 0x31, 0x00, ... , 0x00}
+    //                              |     |     |     |     |     |     鈹� Checksum
+    //                              |     |     |     |     |     鈹� Command + value
+    //                              |     |     |     |     鈹� Sequence number
+    //                              |     |     |     鈹� Response/Request indicator
+    //                              |     |     鈹� Value length
+    //                              |     |
+    //                              鈹�-----鈹� Header
+
+    public static final byte[] CMD_FIRMWARE_INFO                = new byte[]{0x01, 0x02};
+    public static final byte[] CMD_AUTHORIZATION_TASK           = new byte[]{0x01, 0x05};
+    public static final byte[] CMD_TIME_SETTINGS                = new byte[]{0x01, 0x08};
+    public static final byte[] CMD_ALARM_SETTINGS               = new byte[]{0x01, 0x0A};
+    public static final byte[] CMD_BATTERY_INFO                 = new byte[]{0x01, 0x14};
+
+    public static final byte[] CMD_NOTIFICATION_TASK            = new byte[]{0x03, 0x01};
+    public static final byte[] CMD_NOTIFICATION_SETTINGS        = new byte[]{0x03, 0x02};
+    public static final byte[] CMD_CALIBRATION_INIT_TASK        = new byte[]{0x03, 0x31};
+    public static final byte[] CMD_CALIBRATION_TASK             = new byte[]{0x03, 0x33, 0x01};
+    public static final byte[] CMD_CALIBRATION_KEEP_ALIVE       = new byte[]{0x03, 0x34};
+    public static final byte[] CMD_DO_NOT_DISTURB_SETTINGS      = new byte[]{0x03, 0x61};
+
+    public static final byte[] CMD_FITNESS_GOAL_SETTINGS        = new byte[]{0x10, 0x02};
+
+    public static final byte[] RESP_AUTHORIZATION_TASK          = new byte[]{0x01, 0x01, 0x05};
+    public static final byte[] RESP_BUTTON_INDICATOR            = new byte[]{0x04, 0x03, 0x11};
+    public static final byte[] RESP_ALARM_INDICATOR             = new byte[]{-0x80, 0x01, 0x0A};
+
+    public static final byte[] RESP_FIRMWARE_INFO               = new byte[]{0x08, 0x01, 0x02};
+    public static final byte[] RESP_TIME_SETTINGS               = new byte[]{0x08, 0x01, 0x08};
+    public static final byte[] RESP_BATTERY_INFO                = new byte[]{0x08, 0x01, 0x14};
+    public static final byte[] RESP_NOTIFICATION_SETTINGS       = new byte[]{0x01, 0x03, 0x02};
+
+    public static final String ACTION_ENABLE = "action.watch9.enable";
+
+    public static final String ACTION_CALIBRATION
+            = "nodomain.freeyourgadget.gadgetbridge.devices.action.lenovowatch.start_calibration";
+    public static final String ACTION_CALIBRATION_SEND
+            = "nodomain.freeyourgadget.gadgetbridge.devices.action.lenovowatch.send_calibration";
+    public static final String ACTION_CALIBRATION_HOLD
+            = "nodomain.freeyourgadget.gadgetbridge.devices.action.lenovowatch.keep_calibrating";
+    public static final String VALUE_CALIBRATION_HOUR
+            = "value.lenovowatch.calibration_hour";
+    public static final String VALUE_CALIBRATION_MINUTE
+            = "value.lenovowatch.calibration_minute";
+    public static final String VALUE_CALIBRATION_SECOND
+            = "value.lenovowatch.calibration_second";
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lenovo/LenovoWatchPairingActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lenovo/LenovoWatchPairingActivity.java
new file mode 100644
index 0000000..c845d92
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lenovo/LenovoWatchPairingActivity.java
@@ -0,0 +1,166 @@
+/*  Copyright (C) 2019-2024 Andreas B枚hler, Jos茅 Rebelo, mamucho, mkusnierz,
+    Taavi Eom盲e
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.lenovo;
+
+import static nodomain.freeyourgadget.gadgetbridge.util.BondingUtil.STATE_DEVICE_CANDIDATE;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.annotation.NonNull;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBActivity;
+import nodomain.freeyourgadget.gadgetbridge.activities.ControlCenterv2;
+import nodomain.freeyourgadget.gadgetbridge.activities.discovery.DiscoveryActivityV2;
+import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
+import nodomain.freeyourgadget.gadgetbridge.util.AndroidUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.BondingInterface;
+import nodomain.freeyourgadget.gadgetbridge.util.BondingUtil;
+
+public class LenovoWatchPairingActivity extends AbstractGBActivity implements BondingInterface {
+    private static final Logger LOG = LoggerFactory.getLogger(LenovoWatchPairingActivity.class);
+
+    private TextView message;
+    private GBDeviceCandidate deviceCandidate;
+
+    private final BroadcastReceiver pairingReceiver = BondingUtil.getPairingReceiver(this);
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_watch9_pairing);
+
+        message = findViewById(R.id.watch9_pair_message);
+        Intent intent = getIntent();
+        deviceCandidate = intent.getParcelableExtra(DeviceCoordinator.EXTRA_DEVICE_CANDIDATE);
+        if (deviceCandidate == null && savedInstanceState != null) {
+            deviceCandidate = savedInstanceState.getParcelable(STATE_DEVICE_CANDIDATE);
+        }
+        if (deviceCandidate == null) {
+            Toast.makeText(this, getString(R.string.message_cannot_pair_no_mac), Toast.LENGTH_SHORT).show();
+            startActivity(new Intent(this, DiscoveryActivityV2.class).setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP));
+            finish();
+            return;
+        }
+
+        startPairing(deviceCandidate);
+    }
+
+    @Override
+    protected void onSaveInstanceState(@NonNull Bundle outState) {
+        super.onSaveInstanceState(outState);
+        outState.putParcelable(STATE_DEVICE_CANDIDATE, deviceCandidate);
+    }
+
+    @Override
+    protected void onRestoreInstanceState(Bundle savedInstanceState) {
+        super.onRestoreInstanceState(savedInstanceState);
+        deviceCandidate = savedInstanceState.getParcelable(STATE_DEVICE_CANDIDATE);
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        super.onActivityResult(requestCode, resultCode, data);
+        BondingUtil.handleActivityResult(this, requestCode, resultCode, data);
+    }
+
+    private void startPairing(GBDeviceCandidate deviceCandidate) {
+        message.setText(getString(R.string.pairing, deviceCandidate));
+
+        registerBroadcastReceivers();
+
+        BondingUtil.connectThenComplete(this, deviceCandidate);
+    }
+
+    @Override
+    public void onBondingComplete(boolean success) {
+        startActivity(new Intent(this, ControlCenterv2.class).setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP));
+        finish();
+    }
+
+    @Override
+    public GBDeviceCandidate getCurrentTarget() {
+        return this.deviceCandidate;
+    }
+
+    @Override
+    public String getMacAddress() {
+        return deviceCandidate.getDevice().getAddress();
+    }
+
+    @Override
+    public boolean getAttemptToConnect() {
+        return true;
+    }
+
+    @Override
+    protected void onResume() {
+        registerBroadcastReceivers();
+        super.onResume();
+    }
+
+    @Override
+    protected void onStart() {
+        registerBroadcastReceivers();
+        super.onStart();
+    }
+
+    @Override
+    protected void onDestroy() {
+        unregisterBroadcastReceivers();
+        super.onDestroy();
+    }
+
+    @Override
+    protected void onStop() {
+        unregisterBroadcastReceivers();
+        super.onStop();
+    }
+
+    @Override
+    protected void onPause() {
+        // WARN: Do not remove listeners on pause
+        // Bonding process can pause the activity and you might miss broadcasts
+        super.onPause();
+    }
+
+    public void unregisterBroadcastReceivers() {
+        AndroidUtils.safeUnregisterBroadcastReceiver(LocalBroadcastManager.getInstance(this), pairingReceiver);
+    }
+
+    public void registerBroadcastReceivers() {
+        LocalBroadcastManager.getInstance(this).registerReceiver(pairingReceiver, new IntentFilter(GBDevice.ACTION_DEVICE_CHANGED));
+    }
+
+    @Override
+    public Context getContext() {
+        return this;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lenovo/watchxplus/WatchXPlusConstants.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lenovo/watchxplus/WatchXPlusConstants.java
new file mode 100644
index 0000000..87e71f8
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lenovo/watchxplus/WatchXPlusConstants.java
@@ -0,0 +1,103 @@
+/*  Copyright (C) 2019-2024 Damien Gaignon, mamucho, mkusnierz
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.lenovo.watchxplus;
+
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.lenovo.LenovoWatchConstants;
+
+public final class WatchXPlusConstants extends LenovoWatchConstants {
+    public static final UUID UUID_SERVICE_WATCHXPLUS = UUID.fromString("0000a800-0000-1000-8000-00805f9b34fb");
+
+    public static final UUID UUID_UNKNOWN_DESCRIPTOR = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");
+
+    public static final UUID UUID_CHARACTERISTIC_WRITE = UUID.fromString("0000a801-0000-1000-8000-00805f9b34fb");
+    public static final UUID UUID_CHARACTERISTIC_DATABASE_READ = UUID.fromString("0000a802-0000-1000-8000-00805f9b34fb");
+    public static final UUID UUID_CHARACTERISTIC_UNKNOWN_3 = UUID.fromString("0000a803-0000-1000-8000-00805f9b34fb");
+    public static final UUID UUID_CHARACTERISTIC_UNKNOWN_4 = UUID.fromString("0000a804-0000-1000-8000-00805f9b34fb");
+
+
+    // new
+    public static final String PREF_CONTINIOUS_RING = "notification_enable_continious_ring";
+    public static final String PREF_REPEAT_RING = "notification_repeat_ring";
+    public static final String PREF_MISSED_CALL_ENABLE = "notification_enable_missed_call";
+    public static final String PREF_MISSED_CALL_REPEAT = "notification_repeat_missed_call";
+    public static final String PREF_BUTTON_REJECT = "notification_button_reject";
+    public static final String PREF_SHAKE_REJECT = "notification_shake_reject";
+    public static final String PREF_FORCE_TIME = "pref_device_spec_settings_force_time";
+    public static final String PREF_BP_CAL_LOW = "pref_sensors_bp_calibration_low";
+    public static final String PREF_BP_CAL_HIGH = "pref_sensors_bp_calibration_high";
+
+    public static final String PREF_SHOW_RAW_GRAPH = "show_raw_graph";
+
+    // time format constants
+    public static final byte ARG_SET_TIMEMODE_24H = 0x00;
+    public static final byte ARG_SET_TIMEMODE_12H = 0x01;
+
+    public static final int NOTIFICATION_CHANNEL_DEFAULT = 0;
+    public static final int NOTIFICATION_CHANNEL_PHONE_CALL = 10;
+
+    public static final byte[] CMD_WEATHER_SET                  = new byte[]{0x01, 0x10};
+    public static final byte[] CMD_RETRIEVE_DATA_COUNT          = new byte[]{(byte)0xF0, 0x10};
+    public static final byte[] CMD_RETRIEVE_DATA_DETAILS        = new byte[]{(byte)0xF0, 0x11};
+    public static final byte[] CMD_RETRIEVE_DATA_CONTENT        = new byte[]{(byte)0xF0, 0x12};
+    public static final byte[] CMD_REMOVE_DATA_CONTENT          = new byte[]{(byte)0xF0, 0x32};
+    public static final byte[] CMD_BLOOD_PRESSURE_MEASURE       = new byte[]{0x05, 0x0D};
+    public static final byte[] CMD_HEART_RATE_MEASURE           = new byte[]{0x03, 0x23};
+    public static final byte[] CMD_IS_BP_CALIBRATED             = new byte[]{0x05, 0x0B};
+    public static final byte[] CMD_BP_CALIBRATION               = new byte[]{0x05, 0x0C};
+
+    public static final byte[] CMD_NOTIFICATION_TEXT_TASK       = new byte[]{0x03, 0x06};
+    public static final byte[] CMD_NOTIFICATION_CANCEL          = new byte[]{0x03, 0x04};
+    public static final byte[] CMD_NOTIFICATION_SETTINGS        = new byte[]{0x03, 0x02};
+    public static final byte[] CMD_POWER_MODE                   = new byte[]{0x03, -0x7F};
+    public static final byte[] CMD_SET_DND_HOURS_TIME         = new byte[]{0x03, 0x62};
+    public static final byte[] CMD_SET_DND_HOURS_SWITCH       = new byte[]{0x03, 0x61};
+    public static final byte[] CMD_SET_PERSONAL_INFO            = new byte[]{0x01, 0x0E};
+    public static final byte[] CMD_INACTIVITY_REMINDER_SWITCH   = new byte[]{0x03, 0x51};
+    public static final byte[] CMD_INACTIVITY_REMINDER_SET      = new byte[]{0x03, 0x52};
+    public static final byte[] CMD_SET_UNITS                    = new byte[]{0x03, -0x6D};
+
+    public static final byte[] CMD_FITNESS_GOAL_SETTINGS        = new byte[]{0x10, 0x02};
+    public static final byte[] CMD_DAY_STEPS_INFO               = new byte[]{0x10, 0x03};
+
+    public static final byte[] CMD_SHAKE_SWITCH                 = new byte[]{0x03, -0x6E};
+    public static final byte[] CMD_DISCONNECT_REMIND            = new byte[]{0x00, 0x11};
+    public static final byte[] CMD_TIME_LANGUAGE                = new byte[]{0x03, -0x6F};
+    public static final byte[] CMD_ALTITUDE                     = new byte[]{0x05, 0x0A};
+
+    public static final byte[] RESP_SHAKE_SWITCH                = new byte[]{0x08, 0x03, -0x6E};
+    public static final byte[] RESP_DISCONNECT_REMIND           = new byte[]{0x08, 0x00, 0x11};
+    public static final byte[] RESP_IS_BP_CALIBRATED            = new byte[]{0x08, 0x05, 0x0B};
+    public static final byte[] RESP_BUTTON_WHILE_RING           = new byte[]{0x04, 0x03, 0x03};
+    public static final byte[] RESP_BP_CALIBRATION              = new byte[]{0x08, 0x05, 0x0C};
+    public static final byte[] RESP_SET_PERSONAL_INFO           = new byte[]{0x08, 0x01, 0x0E};
+    public static final byte[] RESP_GOAL_AIM_STATUS             = new byte[]{0x08, 0x10, 0x02};
+    public static final byte[] RESP_INACTIVITY_REMINDER_SWITCH  = new byte[]{0x08, 0x03, 0x51};
+    public static final byte[] RESP_INACTIVITY_REMINDER_SET     = new byte[]{0x08, 0x03, 0x52};
+
+    public static final byte[] RESP_AUTHORIZATION_TASK          = new byte[]{0x01, 0x01, 0x05};
+    public static final byte[] RESP_DAY_STEPS_INDICATOR         = new byte[]{0x08, 0x10, 0x03};
+    public static final byte[] RESP_HEARTRATE                   = new byte[]{(byte) 0x80, 0x15, 0x03};
+
+    public static final byte[] RESP_DATA_COUNT                  = new byte[]{0x08, (byte)0xF0, 0x10};
+    public static final byte[] RESP_DATA_DETAILS                = new byte[]{0x08, (byte)0xF0, 0x11};
+    public static final byte[] RESP_DATA_CONTENT                = new byte[]{0x08, (byte)0xF0, 0x12};
+    public static final byte[] RESP_DATA_CONTENT_REMOVE         = new byte[]{-0x80, (byte)0xF0, 0x32};
+    public static final byte[] RESP_BP_MEASURE_STARTED          = new byte[]{0x08, 0x05, 0x0D};
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lenovo/watchxplus/WatchXPlusDeviceCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lenovo/watchxplus/WatchXPlusDeviceCoordinator.java
new file mode 100644
index 0000000..2fd76a4
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lenovo/watchxplus/WatchXPlusDeviceCoordinator.java
@@ -0,0 +1,319 @@
+/*  Copyright (C) 2019-2024 Andreas Shimokawa, Damien Gaignon, Daniel Dakhno,
+    Jos茅 Rebelo, mamucho, mkusnierz, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.lenovo.watchxplus;
+
+import android.app.Activity;
+import android.bluetooth.le.ScanFilter;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.net.Uri;
+import android.os.ParcelUuid;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLEDeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.lenovo.LenovoWatchCalibrationActivity;
+import nodomain.freeyourgadget.gadgetbridge.devices.lenovo.LenovoWatchPairingActivity;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.ServiceDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.lenovo.watchxplus.WatchXPlusDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+import static nodomain.freeyourgadget.gadgetbridge.GBApplication.getContext;
+
+
+public class WatchXPlusDeviceCoordinator extends AbstractBLEDeviceCoordinator {
+    private static final Logger LOG = LoggerFactory.getLogger(WatchXPlusDeviceSupport.class);
+    private static final int FindPhone_ON = -1;
+    public static final int FindPhone_OFF = 0;
+    public static boolean isBPCalibrated = false;
+
+    private static final Prefs prefs  = GBApplication.getPrefs();
+
+    @NonNull
+    @Override
+    public Collection<? extends ScanFilter> createBLEScanFilters() {
+        ParcelUuid watchXpService = new ParcelUuid(WatchXPlusConstants.UUID_SERVICE_WATCHXPLUS);
+        ScanFilter filter = new ScanFilter.Builder().setServiceUuid(watchXpService).build();
+        return Collections.singletonList(filter);
+    }
+
+    @Override
+    protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) {
+
+    }
+
+    @Override
+    public int getBondingStyle() {
+        return BONDING_STYLE_NONE;
+    }
+
+    @NonNull
+    @Override
+    public boolean supports(GBDeviceCandidate candidate) {
+        String macAddress = candidate.getMacAddress().toUpperCase();
+        String deviceName = candidate.getName().toUpperCase();
+        if (candidate.supportsService(WatchXPlusConstants.UUID_SERVICE_WATCHXPLUS)) {
+            return true;
+        } else if (macAddress.startsWith("DC:41:E5")) {
+            return true;
+        } else if (deviceName.equalsIgnoreCase("WATCH XPLUS")) {
+            return true;
+            // add initial support for Watch X non-plus (forces Watch X to be recognized as Watch XPlus)
+            // Watch X non-plus have same MAC address as Watch 9 (starts with "1C:87:79")
+        } else if (deviceName.equalsIgnoreCase("WATCH X")) {
+            return true;
+        }
+        return false;
+    }
+
+    @Nullable
+    @Override
+    public Class<? extends Activity> getPairingActivity() {
+        return LenovoWatchPairingActivity.class;
+    }
+
+    @Override
+    public boolean supportsActivityDataFetching() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsActivityTracking() {
+        return true;
+    }
+
+    @Override
+    public SampleProvider<? extends ActivitySample> getSampleProvider(GBDevice device, DaoSession session) {
+        return new WatchXPlusSampleProvider(device, session);
+    }
+
+    @Override
+    public InstallHandler findInstallHandler(Uri uri, Context context) {
+        return null;
+    }
+
+    @Override
+    public boolean supportsScreenshots(final GBDevice device) {
+        return false;
+    }
+
+    @Override
+    public int getAlarmSlotCount(GBDevice device) {
+        return 3;
+    }
+
+    @Override
+    public boolean supportsHeartRateMeasurement(GBDevice device) {
+        return true;
+    }
+
+    @Override
+    public String getManufacturer() {
+        return "Lenovo";
+    }
+
+    @Override
+    public boolean supportsAppsManagement(final GBDevice device) {
+        return false;
+    }
+
+    @Override
+    public Class<? extends Activity> getAppsManagementActivity() {
+        return null;
+    }
+
+    @Override
+    public boolean supportsCalendarEvents() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsRealtimeData() { return false; }
+    @Override
+    public boolean supportsWeather() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsFindDevice() { return false; }
+
+    @Override
+    public int[] getSupportedDeviceSpecificSettings(GBDevice device) {
+        return new int[]{
+                R.xml.devicesettings_liftwrist_display_noshed,
+                R.xml.devicesettings_disconnectnotification_noshed,
+                R.xml.devicesettings_donotdisturb_no_auto,
+                R.xml.devicesettings_inactivity,
+                R.xml.devicesettings_find_phone,
+                R.xml.devicesettings_timeformat,
+                R.xml.devicesettings_power_mode,
+                R.xml.devicesettings_watchxplus,
+                R.xml.devicesettings_transliteration
+        };
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return WatchXPlusDeviceSupport.class;
+    }
+
+    @Override
+    public EnumSet<ServiceDeviceSupport.Flags> getInitialFlags() {
+        return EnumSet.of(ServiceDeviceSupport.Flags.THROTTLING, ServiceDeviceSupport.Flags.BUSY_CHECKING);
+    }
+
+// find phone settings
+    /**
+     * @return {@link #FindPhone_OFF}, {@link #FindPhone_ON}, or the duration
+     */
+    public static int getFindPhone(SharedPreferences sharedPrefs) {
+        String findPhone = sharedPrefs.getString(DeviceSettingsPreferenceConst.PREF_FIND_PHONE, getContext().getString(R.string.p_off));
+
+        assert findPhone != null;
+        if (findPhone.equals(getContext().getString(R.string.p_off))) {
+            return FindPhone_OFF;
+        } else if (findPhone.equals(getContext().getString(R.string.p_on))) {
+            return FindPhone_ON;
+        } else { // Duration
+            String duration = sharedPrefs.getString(DeviceSettingsPreferenceConst.PREF_FIND_PHONE_DURATION, "0");
+
+            try {
+                int iDuration;
+
+                try {
+                    assert duration != null;
+                    iDuration = Integer.valueOf(duration);
+                } catch (Exception ex) {
+                    iDuration = 60;
+                }
+
+                return iDuration;
+            } catch (Exception e) {
+                return FindPhone_ON;
+            }
+        }
+    }
+
+
+    /**
+     * @param startOut out Only hour/minute are used.
+     * @param endOut   out Only hour/minute are used.
+     * @return True if DND hours are enabled.
+     */
+    public static boolean getDNDHours(String deviceAddress, Calendar startOut, Calendar endOut) {
+        SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(deviceAddress);
+        String doNotDisturb = prefs.getString(DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_NOAUTO, getContext().getString(R.string.p_off));
+
+        assert doNotDisturb != null;
+        if (doNotDisturb.equals(getContext().getString(R.string.p_off))) {
+            LOG.info(" DND is disabled ");
+            return false;
+        } else {
+
+            String start = prefs.getString(DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_NOAUTO_START, "01:00");
+            String end = prefs.getString(DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_NOAUTO_END, "06:00");
+
+            DateFormat df = new SimpleDateFormat("HH:mm");
+
+            try {
+                startOut.setTime(df.parse(start));
+                endOut.setTime(df.parse(end));
+
+                return true;
+            } catch (Exception e) {
+                return false;
+            }
+        }
+    }
+
+    /**
+     * @param startOut out Only hour/minute are used.
+     * @param endOut   out Only hour/minute are used.
+     * @return True if DND hours are enabled.
+     */
+    public static boolean getLongSitHours(String deviceAddress, Calendar startOut, Calendar endOut) {
+        SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(deviceAddress);
+        boolean enabled = prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_INACTIVITY_ENABLE, false);
+
+        if (!enabled) {
+            LOG.info(" Long sit reminder is disabled ");
+            return false;
+        } else {
+            String start = prefs.getString(DeviceSettingsPreferenceConst.PREF_INACTIVITY_START, "06:00");
+            String end = prefs.getString(DeviceSettingsPreferenceConst.PREF_INACTIVITY_END, "23:00");
+
+            DateFormat df = new SimpleDateFormat("HH:mm");
+
+            try {
+                startOut.setTime(df.parse(start));
+                endOut.setTime(df.parse(end));
+
+                return true;
+            } catch (Exception e) {
+                return false;
+            }
+        }
+    }
+
+    @Nullable
+    @Override
+    public Class<? extends Activity> getCalibrationActivity() {
+        return LenovoWatchCalibrationActivity.class;
+    }
+
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_watchxplus;
+    }
+
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_watchxplus;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_watchxplus_disabled;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lenovo/watchxplus/WatchXPlusSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lenovo/watchxplus/WatchXPlusSampleProvider.java
new file mode 100644
index 0000000..f42a6d7
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lenovo/watchxplus/WatchXPlusSampleProvider.java
@@ -0,0 +1,541 @@
+/*  Copyright (C) 2019-2024 mamucho, mkusnierz
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.lenovo.watchxplus;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+
+import de.greenrobot.dao.AbstractDao;
+import de.greenrobot.dao.Property;
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.devices.AbstractSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.WatchXPlusActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.entities.WatchXPlusActivitySampleDao;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
+
+public class WatchXPlusSampleProvider extends AbstractSampleProvider<WatchXPlusActivitySample> {
+    private GBDevice mDevice;
+    private DaoSession mSession;
+
+    private final float movementDivisor = 950.0f;
+
+    private static final Logger LOG = LoggerFactory.getLogger(WatchXPlusSampleProvider.class);
+
+    public WatchXPlusSampleProvider(GBDevice device, DaoSession session) {
+        super(device, session);
+        mSession = session;
+        mDevice = device;
+    }
+
+    @Override
+    public ActivityKind normalizeType(int rawType) {
+        return ActivityKind.fromCode(rawType);
+    }
+
+    @Override
+    public int toRawActivityKind(ActivityKind activityKind) {
+        LOG.debug("toRawActivityKind: {}", activityKind);
+        return activityKind.getCode();
+    }
+
+    @Override
+    public float normalizeIntensity(int rawIntensity) {
+        float newIntensity = 0;
+        if (rawIntensity <= 0) {
+            //newIntensity = (rawIntensity * 0.5f) + 0.7f;
+            newIntensity = rawIntensity;
+        } else {
+            newIntensity = rawIntensity / movementDivisor;
+        }
+        //LOG.info(" normalizeIntensity: " + rawIntensity + " to " + newIntensity);
+        return newIntensity;
+        //return rawIntensity;
+    }
+
+    @Override
+    public WatchXPlusActivitySample createActivitySample() {
+        return new WatchXPlusActivitySample();
+    }
+
+    @Override
+    public AbstractDao<WatchXPlusActivitySample, ?> getSampleDao() {
+        return getSession().getWatchXPlusActivitySampleDao();
+    }
+
+    @Nullable
+    @Override
+    protected Property getRawKindSampleProperty() {
+        return WatchXPlusActivitySampleDao.Properties.RawKind;
+    }
+
+    @NonNull
+    @Override
+    protected Property getTimestampSampleProperty() {
+        return WatchXPlusActivitySampleDao.Properties.Timestamp;
+    }
+
+    @NonNull
+    @Override
+    protected Property getDeviceIdentifierSampleProperty() {
+        return WatchXPlusActivitySampleDao.Properties.DeviceId;
+    }
+
+    // generate ActivityKind.NOT_MEASURED if there are no data for more than 15 min. and less than 60 min.
+    // generate ActivityKind.NOT_WORN if there are no data for more than 60 min.
+    @NonNull
+    private List<WatchXPlusActivitySample> checkActivityData(List<WatchXPlusActivitySample> samples, int notMeasuredTS, int notWornTS) {
+        int oldTS = 0;
+        int newTS = 0;
+        oldTS = samples.get(0).getTimestamp();
+        for (int i = 0; i < samples.size(); i++) {
+            //oldTS = resultList.get(i).getTimestamp();
+            newTS = samples.get(i).getTimestamp();
+            if ((newTS - oldTS) < notMeasuredTS) { //check data timestamp diff is more than 15 min
+                oldTS = samples.get(i).getTimestamp();
+            } else if (((newTS - oldTS) > notMeasuredTS) && ((newTS - oldTS) < notWornTS)) { //set data to ActivityKind.NOT_MEASURED) if timestamp diff is more than 15 min
+                samples.get(i-1).setRawKind(ActivityKind.NOT_MEASURED.getCode());
+                samples.get(i).setRawKind(ActivityKind.NOT_MEASURED.getCode());
+                oldTS = samples.get(i).getTimestamp();
+            } else if ((newTS - oldTS) > notWornTS) { //set data to ActivityKind.NOT_WORN if timestamp diff is more than 60 min
+                samples.get(i-1).setRawKind(ActivityKind.NOT_WORN.getCode());
+                samples.get(i).setRawKind(ActivityKind.NOT_WORN.getCode());
+                oldTS = samples.get(i).getTimestamp();
+            }
+        }
+        return samples;
+    }
+
+
+
+
+    @NonNull
+    @Override
+    public List<WatchXPlusActivitySample> getAllActivitySamples(int timestamp_from, int timestamp_to) {
+        boolean showRawData = GBApplication.getDeviceSpecificSharedPrefs(mDevice.getAddress()).getBoolean(WatchXPlusConstants.PREF_SHOW_RAW_GRAPH, false);
+        if (showRawData) {
+            return getGBActivitySamples(timestamp_from, timestamp_to);
+        }
+        List<WatchXPlusActivitySample> samples = getGBActivitySamples(timestamp_from, timestamp_to);
+        int numEntries = samples.size();
+        if (numEntries < 3) {
+            return samples;
+        }
+
+        List<WatchXPlusActivitySample> resultList = new ArrayList<>(numEntries);
+
+        // how many elements to scan for sleep sate before and after sleep block
+        int seekAhead = 10;
+        boolean secondBlock = false;
+
+// find sleep start and sleep stop index based on ActivityKind.DEEP_SLEEP BLOCK 1
+        int sleepStartIndex_1 = 0;
+        int sleepStopIndex_1 = numEntries;
+        int countNextSleepStart_1 = 0;
+        int countNextSleepStop_1 = 0;
+
+        for (int i = 0; i < numEntries; i++) {
+            if (samples.get(i).getRawKind() == ActivityKind.DEEP_SLEEP.getCode()) {
+                // normalize RawIntensity
+                samples.get(i).setRawIntensity(1000);
+                // find sleep start index
+                if (sleepStartIndex_1 == 0) {
+                    sleepStartIndex_1 = i;
+                    sleepStopIndex_1 = sleepStartIndex_1;
+                    countNextSleepStop_1 = sleepStopIndex_1;
+                } else {
+                    if (countNextSleepStart_1 == 0) {
+                        countNextSleepStart_1 = i;
+                        // reset start index if next index is far ahead
+                        if ((countNextSleepStart_1 - sleepStartIndex_1) > seekAhead * 3) {
+                            sleepStartIndex_1 = countNextSleepStart_1;
+                            sleepStopIndex_1 = sleepStartIndex_1;
+                            countNextSleepStop_1 = sleepStopIndex_1;
+                        }
+                    }
+                }
+
+
+                        if ((i - sleepStopIndex_1) < (seekAhead * 4)) {
+                            sleepStopIndex_1 = i;
+                        }
+                        countNextSleepStop_1 = i;
+            }
+        }
+
+// find sleep start and sleep stop index based on ActivityKind.DEEP_SLEEP BLOCK 2
+        int sleepStartIndex_2 = 0;
+        int sleepStopIndex_2 = numEntries;
+        int countNextSleepStart_2 = 0;
+        int countNextSleepStop_2 = 0;
+        int next_block = numEntries;
+
+        for (int i = sleepStopIndex_1 + 1; i < numEntries; i++) {
+            if (samples.get(i).getRawKind() == ActivityKind.DEEP_SLEEP.getCode()) {
+                // find sleep start index
+                if (sleepStartIndex_2 == 0) {
+                    sleepStartIndex_2 = i;
+                    sleepStopIndex_2 = sleepStartIndex_2;
+                    countNextSleepStop_2 = sleepStopIndex_2;
+                } else {
+                    if (countNextSleepStart_2 == 0) {
+                        countNextSleepStart_2 = i;
+                        // reset start index if next index is far ahead
+                        if ((countNextSleepStart_2 - sleepStartIndex_2) > seekAhead * 3) {
+                            sleepStartIndex_2 = countNextSleepStart_2;
+                            sleepStopIndex_2 = sleepStartIndex_2;
+                            countNextSleepStop_2 = sleepStopIndex_2;
+                        }
+                    }
+                }
+                if ((i - sleepStopIndex_2) < (seekAhead * 4)) {
+                    sleepStopIndex_2 = i;
+                }
+                countNextSleepStop_2 = i;
+            }
+        }
+        if (sleepStartIndex_2 != 0) {
+            secondBlock = true;
+            LOG.info(" Found second block ");
+        }
+
+        LOG.info(" sleep_1 begin index:" + sleepStartIndex_1 + " next index: " + countNextSleepStart_1 + " sleep end index: " + sleepStopIndex_1 + " sleep end: " + countNextSleepStop_1);
+        if (secondBlock) {
+            LOG.info(" sleep_2 begin index:" + sleepStartIndex_2 + " next index: " + countNextSleepStart_2 + " sleep end index: " + sleepStopIndex_2 + " sleep end: " + countNextSleepStop_2);
+        }
+
+// SLEEP BLOCK 1
+        // add all activity before sleep start
+        if (secondBlock) {
+            next_block = sleepStartIndex_2;
+        }
+        int newSleepStartIndex_1 = 0;
+        if (sleepStartIndex_1 >= seekAhead) {
+          newSleepStartIndex_1 = sleepStartIndex_1 - seekAhead;
+        } else {
+            newSleepStartIndex_1 = 0;
+        }
+        for (int i = 0; i < newSleepStartIndex_1; i++) {
+            if (samples.get(i).getRawKind() == ActivityKind.LIGHT_SLEEP.getCode()) {
+                if (samples.get(i).getRawIntensity() <= 300) {
+                    samples.get(i).setRawIntensity(200);
+                } else if ((samples.get(i).getRawIntensity() <= 1000) && (samples.get(i).getRawIntensity() > 100)) {
+                    samples.get(i).setRawIntensity(400);
+                } if (samples.get(i).getRawIntensity() > 1000) {
+                    samples.get(i).setRawIntensity(600);
+                }
+                samples.get(i).setRawKind(1);
+                resultList.add(samples.get(i));
+            } else {
+                if (samples.get(i).getRawKind() == ActivityKind.ACTIVITY.getCode()) {
+                    if (i < (newSleepStartIndex_1 - 3)) {
+                        if ((samples.get(i + 1).getRawKind() == ActivityKind.LIGHT_SLEEP.getCode()) || (samples.get(i + 2).getRawKind() == ActivityKind.LIGHT_SLEEP.getCode()) || (samples.get(i + 3).getRawKind() == ActivityKind.LIGHT_SLEEP.getCode())) {
+                            samples.get(i).setRawKind(1);
+                            //samples.get(i).setRawIntensity(700);
+                        } else {
+                            samples.get(i).setRawIntensity(1000);
+                        }
+                    }
+                    //samples.get(i).setRawIntensity(1000);
+                } else {
+                    samples.get(i).setRawIntensity(1000);
+                }
+                resultList.add(samples.get(i));
+            }
+        }
+
+// add sleep activity
+        int newSleepStopIndex_1;
+
+        if ((sleepStopIndex_1 + seekAhead * 2) < next_block) {
+            newSleepStopIndex_1 = sleepStopIndex_1 + seekAhead * 2;
+        } else {
+            newSleepStopIndex_1 = next_block;
+        }
+
+        boolean replaceActivity_1 = false;
+        for (int i = newSleepStartIndex_1; i < newSleepStopIndex_1; i++) {
+            ActivitySample sample = samples.get(i);
+            if (i < sleepStartIndex_1) {
+                if (samples.get(i).getRawKind() == ActivityKind.LIGHT_SLEEP.getCode()) {
+                    replaceActivity_1 = true;
+                    samples.get(i).setRawIntensity(600);
+                    resultList.add(samples.get(i));
+                } else {
+                    if (replaceActivity_1) {
+                        samples.get(i).setRawKind(2);
+                        samples.get(i).setRawIntensity(600);
+                        resultList.add(samples.get(i));
+                    } else {
+                        samples.get(i).setRawIntensity(600);
+                        resultList.add(samples.get(i));
+                    }
+                }
+            }
+            if ((samples.get(i).getRawKind() == ActivityKind.DEEP_SLEEP.getCode()) || (samples.get(i).getRawKind() == ActivityKind.LIGHT_SLEEP.getCode())) {
+                if (samples.get(i).getRawKind() == ActivityKind.LIGHT_SLEEP.getCode()) {
+                    if (i > 0) {
+                        if (samples.get(i - 1).getHeartRate() > 0) {
+                            samples.get(i).setHeartRate(samples.get(i - 1).getHeartRate());
+                        }
+                    } else {
+                        if (samples.get(i + 1).getHeartRate() > 0) {
+                            samples.get(i).setHeartRate(samples.get(i + 1).getHeartRate());
+                        }
+                    }
+                    samples.get(i).setRawIntensity(600);
+                    resultList.add(samples.get(i));
+                } else {
+                    samples.get(i).setRawIntensity(1000);
+                    resultList.add(samples.get(i));
+                }
+            }
+
+            if ((samples.get(i).getRawKind() == ActivityKind.LIGHT_SLEEP.getCode()) && (i > sleepStopIndex_1)) {
+                samples.get(i).setRawIntensity(600);
+                resultList.add(samples.get(i));
+            }
+      }
+
+
+// add remaining activity
+        if (newSleepStopIndex_1 < next_block) {
+            for (int i = newSleepStopIndex_1; i < (next_block-1); i++) {
+                if (samples.get(i).getRawKind() == ActivityKind.LIGHT_SLEEP.getCode()) {
+                    if (samples.get(i).getRawIntensity() <= 300) {
+                        samples.get(i).setRawIntensity(200);
+                    } else if ((samples.get(i).getRawIntensity() <= 1000) && (samples.get(i).getRawIntensity() > 100)) {
+                        samples.get(i).setRawIntensity(400);
+                    } if (samples.get(i).getRawIntensity() > 1000) {
+                        samples.get(i).setRawIntensity(600);
+                    }
+                    samples.get(i).setRawKind(1);
+                    resultList.add(samples.get(i));
+                } else {
+                    if (samples.get(i).getRawKind() == ActivityKind.ACTIVITY.getCode()) {
+                        if (i < (next_block - 3)) {
+                            if ((samples.get(i + 1).getRawKind() == ActivityKind.LIGHT_SLEEP.getCode()) || (samples.get(i + 2).getRawKind() == ActivityKind.LIGHT_SLEEP.getCode()) || (samples.get(i + 3).getRawKind() == ActivityKind.LIGHT_SLEEP.getCode())) {
+                                samples.get(i).setRawKind(1);
+                                //samples.get(i).setRawIntensity(700);
+                            } else {
+                                samples.get(i).setRawIntensity(1000);
+                            }
+                        }
+                        //samples.get(i).setRawIntensity(1000);
+                    } else {
+                        samples.get(i).setRawIntensity(1000);
+                    }
+                    resultList.add(samples.get(i));
+                }
+            }
+        }
+// SLEEP BLOCK 2
+      if (secondBlock) {
+// add sleep activity
+          int newSleepStopIndex_2;
+          int newSleepStartIndex_2 = 0;
+          boolean replaceActivity_2 = false;
+          if (sleepStartIndex_2 >= next_block + seekAhead) {
+              newSleepStartIndex_2 = sleepStartIndex_2 - seekAhead;
+          } else {
+              newSleepStartIndex_2 = next_block;
+          }
+          if ((sleepStopIndex_2 + seekAhead * 2) < numEntries) {
+              newSleepStopIndex_2 = sleepStopIndex_2 + seekAhead * 2;
+          } else {
+              newSleepStopIndex_2 = numEntries;
+          }
+          for (int i = newSleepStartIndex_2; i < newSleepStopIndex_2; i++) {
+              ActivitySample sample = samples.get(i);
+              if (i < sleepStartIndex_2) {
+                  if (samples.get(i).getRawKind() == ActivityKind.LIGHT_SLEEP.getCode()) {
+                      replaceActivity_2 = true;
+                      samples.get(i).setRawIntensity(600);
+                      resultList.add(samples.get(i));
+                  } else {
+                      if (replaceActivity_2) {
+                          samples.get(i).setRawKind(2);
+                          samples.get(i).setRawIntensity(600);
+                          resultList.add(samples.get(i));
+                      } else {
+                          samples.get(i).setRawIntensity(600);
+                          resultList.add(samples.get(i));
+                      }
+                  }
+              }
+
+
+              if ((samples.get(i).getRawKind() == ActivityKind.DEEP_SLEEP.getCode()) || (samples.get(i).getRawKind() == ActivityKind.LIGHT_SLEEP.getCode())) {
+                  if (samples.get(i).getRawKind() == ActivityKind.LIGHT_SLEEP.getCode()) {
+                      if (i > 0) {
+                          if (samples.get(i - 1).getHeartRate() > 0) {
+                              samples.get(i).setHeartRate(samples.get(i - 1).getHeartRate());
+                          }
+                      } else {
+                          if (samples.get(i + 1).getHeartRate() > 0) {
+                              samples.get(i).setHeartRate(samples.get(i + 1).getHeartRate());
+                          }
+                      }
+                      samples.get(i).setRawIntensity(600);
+                      resultList.add(samples.get(i));
+                  } else {
+                      samples.get(i).setRawIntensity(1000);
+                      resultList.add(samples.get(i));
+                  }
+              }
+              if ((samples.get(i).getRawKind() == ActivityKind.LIGHT_SLEEP.getCode()) && (i > sleepStopIndex_2)) {
+                  samples.get(i).setRawIntensity(600);
+                  resultList.add(samples.get(i));
+              }
+          }
+
+          // add remaining activity
+          if (newSleepStopIndex_2 < numEntries) {
+              for (int i = newSleepStopIndex_2; i < (numEntries - 1); i++) {
+                  if (samples.get(i).getRawKind() == ActivityKind.LIGHT_SLEEP.getCode()) {
+                      if (samples.get(i).getRawIntensity() <= 300) {
+                          samples.get(i).setRawIntensity(200);
+                      } else if ((samples.get(i).getRawIntensity() <= 1000) && (samples.get(i).getRawIntensity() > 100)) {
+                          samples.get(i).setRawIntensity(400);
+                      }
+                      if (samples.get(i).getRawIntensity() > 1000) {
+                          samples.get(i).setRawIntensity(600);
+                      }
+                      samples.get(i).setRawKind(1);
+                      resultList.add(samples.get(i));
+                  } else {
+                      if (samples.get(i).getRawKind() == ActivityKind.ACTIVITY.getCode()) {
+                          if (i < (numEntries - 3)) {
+                              if ((samples.get(i + 1).getRawKind() == ActivityKind.LIGHT_SLEEP.getCode()) || (samples.get(i + 2).getRawKind() == ActivityKind.LIGHT_SLEEP.getCode()) || (samples.get(i + 3).getRawKind() == ActivityKind.LIGHT_SLEEP.getCode())) {
+                                  samples.get(i).setRawKind(1);
+                                  //samples.get(i).setRawIntensity(700);
+                              } else {
+                                  samples.get(i).setRawIntensity(1000);
+                              }
+                          }
+                          //samples.get(i).setRawIntensity(1000);
+                      } else {
+                          samples.get(i).setRawIntensity(1000);
+                      }
+                      resultList.add(samples.get(i));
+                  }
+              }
+          }
+      }
+    // add one ActivityKind.NOT_MEASURED at end of data
+        samples.get(numEntries-1).setRawIntensity(0);
+        samples.get(numEntries-1).setRawKind(ActivityKind.NOT_MEASURED.getCode());
+        samples.get(numEntries-1).setHeartRate(0);
+        resultList.add(samples.get(numEntries-1));
+
+    // find all steps, total activity intensity  and maxHR
+        int totalSteps = 0;
+        int maxHeartRate = 10;
+        numEntries = resultList.size();
+        for (int i = 0; i < numEntries-1; i++) {
+            if (resultList.get(i).getRawKind() == ActivityKind.ACTIVITY.getCode()) {
+                if (resultList.get(i).getSteps() > 0) {
+                    totalSteps = totalSteps + resultList.get(i).getSteps();
+                }
+            }
+            if (resultList.get(i).getHeartRate() > maxHeartRate) {
+                maxHeartRate = resultList.get(i).getHeartRate();
+            }
+        }
+
+    // reformat activity data based on heart rate
+        int newIntensity, correctedSteps;
+        int totalIntensity = 0;
+        for (int i = 0; i < numEntries-1; i++) {
+            if ((resultList.get(i).getRawKind() == ActivityKind.ACTIVITY.getCode()) || (resultList.get(i).getRawKind() == ActivityKind.LIGHT_SLEEP.getCode())) {
+                if (resultList.get(i).getRawIntensity() <= 600) { // set interpolated intensity based on heart rate for every TYPE_ACTIVITY which are converted from TYPE_LIGHT_SLEEP
+                    if (resultList.get(i).getHeartRate() < 10) {
+                        newIntensity = resultList.get(i).getRawIntensity() + ((maxHeartRate - resultList.get(i+1).getHeartRate()) * 2);
+                    } else {
+                        newIntensity = resultList.get(i).getRawIntensity() + ((maxHeartRate - resultList.get(i).getHeartRate()) * 2);
+                    }
+                } else { // because there are not RAW intensity values for every TYPE_ACTIVITY set interpolated intensity based on heart rate
+                    newIntensity = resultList.get(i).getRawIntensity() - ((maxHeartRate - resultList.get(i).getHeartRate()) * 2);
+                }
+                /*
+                if (stepsPerActivity > 0.0f) { // because there are not steps values for every TYPE_ACTIVITY set interpolated steps
+                    correctedSteps = (int) (resultList.get(i).getRawIntensity() / stepsPerActivity);
+                    resultList.get(i).setSteps(correctedSteps);
+                }
+                 */
+                resultList.get(i).setRawIntensity(newIntensity);
+                if (resultList.get(i).getRawIntensity() > 0) {
+                    totalIntensity = totalIntensity + newIntensity;
+                }
+            } else { // because there are not TYPE_DEEP_SLEEP intensity set random DEEP_SLEEP intensity
+                Random r = new Random();
+                newIntensity = resultList.get(i).getRawIntensity() - ((maxHeartRate - (int)(r.nextFloat() * maxHeartRate)) * 2);
+                resultList.get(i).setRawIntensity(newIntensity);
+                if (resultList.get(i).getRawIntensity() > 0) {
+                    totalIntensity = totalIntensity + newIntensity;
+                }
+            }
+        }
+
+        // because there are not steps values for every TYPE_ACTIVITY set interpolated steps
+        float stepsPerActivity = 0.000f;
+        int newTotalSteps = 0;
+        int activityCount = 0;
+        if (totalSteps > 0) {
+            stepsPerActivity = totalIntensity / totalSteps;
+            for (int i = 0; i < numEntries - 1; i++) {
+                if (resultList.get(i).getRawKind() == ActivityKind.ACTIVITY.getCode()) {
+                    if (stepsPerActivity > 0.0f) {
+                        correctedSteps = (int) (resultList.get(i).getRawIntensity() / stepsPerActivity);
+                        resultList.get(i).setSteps(correctedSteps);
+                        newTotalSteps = newTotalSteps + correctedSteps;
+                        activityCount = activityCount + 1;
+                    }
+                }
+            }
+        }
+        if (newTotalSteps < totalSteps) {
+            int stepsDiff = newTotalSteps - totalSteps;
+            int increaseStepsWith = stepsDiff / activityCount;
+            if (increaseStepsWith <= 1) {
+                increaseStepsWith = 2;
+            }
+            newTotalSteps = 0;
+            for (int i = 0; i < numEntries - 1; i++) {
+                if (resultList.get(i).getRawKind() == ActivityKind.ACTIVITY.getCode()) {
+                    correctedSteps = resultList.get(i).getSteps() + increaseStepsWith;
+                    newTotalSteps = newTotalSteps + correctedSteps;
+                    if (newTotalSteps <= totalSteps) {
+                        resultList.get(i).setSteps(correctedSteps);
+                    } else {
+                        break;
+                    }
+                }
+            }
+        }
+        return checkActivityData(resultList, 900, 3600);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/liveview/LiveviewConstants.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/liveview/LiveviewConstants.java
new file mode 100644
index 0000000..0055834
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/liveview/LiveviewConstants.java
@@ -0,0 +1,126 @@
+/*  Copyright (C) 2016-2024 Daniele Gobbetti
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.liveview;
+//Changed by Renze: Fixed brightness constants
+
+import java.nio.ByteOrder;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * Message constants reverse-engineered by Andrew de Quincey (<a
+ * href="http://adq.livejournal.com">http://adq.livejournal.com</a>).
+ *
+ * @author Robert &lt;xperimental@solidproject.de&gt;
+ */
+public final class LiveviewConstants {
+
+    public static Charset ENCODING = StandardCharsets.ISO_8859_1;
+    public static ByteOrder BYTE_ORDER = ByteOrder.BIG_ENDIAN;
+
+    public static final byte CLOCK_24H = 0;
+    public static final byte CLOCK_12H = 1;
+
+    public static final byte MSG_GETCAPS = 1;
+    public static final byte MSG_GETCAPS_RESP = 2;
+
+    public static final byte MSG_DISPLAYTEXT = 3;
+    public static final byte MSG_DISPLAYTEXT_ACK = 4;
+
+    public static final byte MSG_DISPLAYPANEL = 5;
+    public static final byte MSG_DISPLAYPANEL_ACK = 6;
+
+    public static final byte MSG_DEVICESTATUS = 7;
+    public static final byte MSG_DEVICESTATUS_ACK = 8;
+
+    public static final byte MSG_DISPLAYBITMAP = 19;
+    public static final byte MSG_DISPLAYBITMAP_ACK = 20;
+
+    public static final byte MSG_CLEARDISPLAY = 21;
+    public static final byte MSG_CLEARDISPLAY_ACK = 22;
+
+    public static final byte MSG_SETMENUSIZE = 23;
+    public static final byte MSG_SETMENUSIZE_ACK = 24;
+
+    public static final byte MSG_GETMENUITEM = 25;
+    public static final byte MSG_GETMENUITEM_RESP = 26;
+
+    public static final byte MSG_GETALERT = 27;
+    public static final byte MSG_GETALERT_RESP = 28;
+
+    public static final byte MSG_NAVIGATION = 29;
+    public static final byte MSG_NAVIGATION_RESP = 30;
+
+    public static final byte MSG_SETSTATUSBAR = 33;
+    public static final byte MSG_SETSTATUSBAR_ACK = 34;
+
+    public static final byte MSG_GETMENUITEMS = 35;
+
+    public static final byte MSG_SETMENUSETTINGS = 36;
+    public static final byte MSG_SETMENUSETTINGS_ACK = 37;
+
+    public static final byte MSG_GETTIME = 38;
+    public static final byte MSG_GETTIME_RESP = 39;
+
+    public static final byte MSG_SETLED = 40;
+    public static final byte MSG_SETLED_ACK = 41;
+
+    public static final byte MSG_SETVIBRATE = 42;
+    public static final byte MSG_SETVIBRATE_ACK = 43;
+
+    public static final byte MSG_ACK = 44;
+
+    public static final byte MSG_SETSCREENMODE = 64;
+    public static final byte MSG_SETSCREENMODE_ACK = 65;
+
+    public static final byte MSG_GETSCREENMODE = 66;
+    public static final byte MSG_GETSCREENMODE_RESP = 67;
+
+    public static final int DEVICESTATUS_OFF = 0;
+    public static final int DEVICESTATUS_ON = 1;
+    public static final int DEVICESTATUS_MENU = 2;
+
+    public static final byte RESULT_OK = 0;
+    public static final byte RESULT_ERROR = 1;
+    public static final byte RESULT_OOM = 2;
+    public static final byte RESULT_EXIT = 3;
+    public static final byte RESULT_CANCEL = 4;
+
+    public static final int NAVACTION_PRESS = 0;
+    public static final int NAVACTION_LONGPRESS = 1;
+    public static final int NAVACTION_DOUBLEPRESS = 2;
+
+    public static final int NAVTYPE_UP = 0;
+    public static final int NAVTYPE_DOWN = 1;
+    public static final int NAVTYPE_LEFT = 2;
+    public static final int NAVTYPE_RIGHT = 3;
+    public static final int NAVTYPE_SELECT = 4;
+    public static final int NAVTYPE_MENUSELECT = 5;
+
+    public static final int ALERTACTION_CURRENT = 0;
+    public static final int ALERTACTION_FIRST = 1;
+    public static final int ALERTACTION_LAST = 2;
+    public static final int ALERTACTION_NEXT = 3;
+    public static final int ALERTACTION_PREV = 4;
+
+    public static final int BRIGHTNESS_OFF = 49;
+    public static final int BRIGHTNESS_DIM = 50;
+    public static final int BRIGHTNESS_MAX = 51;
+
+    public static final String CLIENT_SOFTWARE_VERSION = "0.0.3";
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/liveview/LiveviewCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/liveview/LiveviewCoordinator.java
new file mode 100644
index 0000000..4fa298f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/liveview/LiveviewCoordinator.java
@@ -0,0 +1,138 @@
+/*  Copyright (C) 2016-2024 Andreas Shimokawa, Carsten Pfeiffer, Damien
+    Gaignon, Daniel Dakhno, Daniele Gobbetti, Jos茅 Rebelo, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.liveview;
+
+import android.app.Activity;
+import android.content.Context;
+import android.net.Uri;
+
+import androidx.annotation.NonNull;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.GBException;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLClassicDeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.liveview.LiveviewSupport;
+
+public class LiveviewCoordinator extends AbstractBLClassicDeviceCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("LiveView.*");
+    }
+
+    @Override
+    public Class<? extends Activity> getPairingActivity() {
+        return null;
+    }
+
+    @Override
+    public InstallHandler findInstallHandler(Uri uri, Context context) {
+        return null;
+    }
+
+    @Override
+    public boolean supportsActivityDataFetching() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsActivityTracking() {
+        return false;
+    }
+
+    @Override
+    public SampleProvider<? extends ActivitySample> getSampleProvider(GBDevice device, DaoSession session) {
+        return null;
+    }
+
+    @Override
+    public boolean supportsScreenshots(final GBDevice device) {
+        return false;
+    }
+
+    @Override
+    public int getAlarmSlotCount(GBDevice device) {
+        return 0;
+    }
+
+    @Override
+    public boolean supportsHeartRateMeasurement(GBDevice device) {
+        return false;
+    }
+
+    @Override
+    public String getManufacturer() {
+        return "Sony Ericsson";
+    }
+
+    @Override
+    public boolean supportsAppsManagement(final GBDevice device) {
+        return false;
+    }
+
+    @Override
+    public Class<? extends Activity> getAppsManagementActivity() {
+        return null;
+    }
+
+    @Override
+    public boolean supportsCalendarEvents() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsRealtimeData() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsWeather() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsFindDevice() {
+        return true;
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return LiveviewSupport.class;
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_liveview;
+    }
+
+    @Override
+    protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
+        // nothing to delete, yet
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/makibeshr3/MakibesHR3Constants.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/makibeshr3/MakibesHR3Constants.java
new file mode 100644
index 0000000..83b793f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/makibeshr3/MakibesHR3Constants.java
@@ -0,0 +1,338 @@
+/*  Copyright (C) 2019-2024 Andreas Shimokawa, Cre3per, Damien Gaignon
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.makibeshr3;
+
+import java.util.UUID;
+
+public final class MakibesHR3Constants {
+
+    public static final UUID UUID_SERVICE = UUID.fromString("6e400001-b5a3-f393-e0a9-e50e24dcca9e");
+    public static final UUID UUID_CHARACTERISTIC_CONTROL = UUID.fromString("6e400002-b5a3-f393-e0a9-e50e24dcca9e");
+    public static final UUID UUID_CHARACTERISTIC_REPORT = UUID.fromString("6e400003-b5a3-f393-e0a9-e50e24dcca9e");
+
+    // Services and Characteristics
+    // 00001801-0000-1000-8000-00805f9b34fb
+    //  00002a05-0000-1000-8000-00805f9b34fb
+    // 00001800-0000-1000-8000-00805f9b34fb
+    //  00002a00-0000-1000-8000-00805f9b34fb
+    //  00002a01-0000-1000-8000-00805f9b34fb
+    //  00002a02-0000-1000-8000-00805f9b34fb
+    //  00002a04-0000-1000-8000-00805f9b34fb
+    //  00002aa6-0000-1000-8000-00805f9b34fb
+    // 6e400001-b5a3-f393-e0a9-e50e24dcca9e // Nordic UART Service
+    //  6e400002-b5a3-f393-e0a9-e50e24dcca9e // control (RX)
+    //  6e400003-b5a3-f393-e0a9-e50e24dcca9e // report
+    // 0000fee7-0000-1000-8000-00805f9b34fb
+    //  0000fec9-0000-1000-8000-00805f9b34fb
+    //  0000fea1-0000-1000-8000-00805f9b34fb
+    //  0000fea2-0000-1000-8000-00805f9b34fb
+
+    // Command structure
+    // ab 00 [argument_count] ff [command] 80 [arguments]
+    // where [argument_count] is [arguments].length + 3
+    // 80 might by different.
+
+
+    public static final byte[] DATA_TEMPLATE = {
+            (byte) 0xab,
+            (byte) 0x00,
+            (byte) 0, // argument_count
+            (byte) 0xff,
+            (byte) 0, // command
+            (byte) 0x80
+//           ,arguments
+    };
+
+    public static final int DATA_ARGUMENT_COUNT_INDEX = 2;
+    public static final int DATA_COMMAND_INDEX = 4;
+    public static final int DATA_ARGUMENTS_INDEX = 6;
+
+    // blood oxygen percentage
+    public static final byte[] RPRT_BLOOD_OXYGEN = new byte[]{ (byte) 0x31, (byte) 0x12 };
+
+
+    // blood oxygen percentage
+    // blood oxygen percentage
+    public static final byte[] RPRT_SINGLE_BLOOD_OXYGEN = new byte[]{ (byte) 0x31, (byte) 0x11 };
+
+
+    // steps might take up more bytes. I don't know which ones and I won't walk that much.
+    // Only sent after we send CMD_51
+    // 00 (maybe also used for steps)
+    // [steps hi]
+    // [steps lo]
+    // 00
+    // 00
+    // 01 (also was 0b. Maybe minutes of activity.)
+    // 00
+    // 00
+    // 00
+    // 00
+    // 00
+    public static final byte[] RPRT_FITNESS = new byte[]{ (byte) 0x51, 0x08 };
+
+
+    // year (+2000)
+    // month
+    // day
+    // hour
+    // minute
+    // heart rate
+    // heart rate
+    public static final byte[] RPRT_HEART_RATE_SAMPLE = new byte[]{ (byte) 0x51, (byte) 0x11 };
+
+
+    // WearFit says "walking" in the step details. This is probably also in here, but
+    // I don't run :O
+    // year (+2000)
+    // month
+    // day
+    // hour (start of measurement. interval is 1h. Might be longer when running.)
+    // 00 (either used for steps or minute)
+    // accumulated steps (hi)
+    // accumulated steps (lo)
+    // 00
+    // 00
+    // ?? (changes whenever steps change. Ranges from 00 to 16.)
+    // 00
+    // 00
+    // 00
+    // 00
+    public static final byte[] RPRT_STEPS_SAMPLE = new byte[]{ (byte) 0x51, (byte) 0x20 };
+
+
+    // enable (00/01)
+    public static final byte RPRT_REVERSE_FIND_DEVICE = (byte) 0x7d;
+
+
+    // The proximity sensor sees air..
+    public static final byte ARG_HEARTRATE_NO_TARGET = (byte) 0xff;
+    // The hr sensor didn't find the heart rate yet.
+    public static final byte ARG_HEARTRATE_NO_READING = (byte) 0x00;
+
+    // heart rate
+    public static final byte RPRT_HEARTRATE = (byte) 0x84;
+
+
+    // charging (00/01)
+    // battery percentage (step size is 20).
+    public static final byte RPRT_BATTERY = (byte) 0x91;
+
+    // firmware_major
+    // firmware_minor
+    // 37
+    // 00
+    // 00
+    // 00
+    // 00
+    // 00
+    // 00
+    // 20
+    // 0e
+    public static final byte RPRT_SOFTWARE = (byte) 0x92;
+
+    // 00
+    public static final byte CMD_FACTORY_RESET = (byte) 0x23;
+
+
+    // enable (00/01)
+    public static final byte[] CMD_SET_REAL_TIME_BLOOD_OXYGEN = new byte[]{ (byte) 0x31, (byte) 0x12 };
+
+
+    // After disabling, the watch replies with RPRT_SINGLE_BLOOD_OXYGEN
+    // enable (00/01)
+    public static final byte[] CMD_SET_SINGLE_BLOOD_OXYGEN = new byte[]{ (byte) 0x31, (byte) 0x11 };
+
+    // device replies with
+    // {@link MakibesHR3Constants#RPRT_HEART_RATE_SAMPLE}
+    // {@link MakibesHR3Constants#RPRT_STEPS_SAMPLE} (Only if steps are non-zero)
+    // {@link MakibesHR3Constants#RPRT_FITNESS}
+    // there are also multiple 6 * 00 reports
+    // 00
+    // year (+2000) steps after
+    // month steps after
+    // day steps after
+    // hour steps after
+    // minute steps after
+    // year (+2000) heart rate after
+    // month heart rate after
+    // day heart rate after
+    // hour heart rate after
+    // minute heart rate after
+    public static final byte CMD_REQUEST_FITNESS = (byte) 0x51;
+
+
+    // Manually sending this doesn't yield a reply. The heart rate history is sent in response to
+    // CMD_CMD_REQUEST_FITNESS.
+    // 00
+    // year (+2000) (probably not current)
+    // month (not current!)
+    // day (not current!)
+    // hour (current)
+    // minute (current)
+    public static final byte CMD_52 = (byte) 0x52;
+
+
+    // vibrates 6 times
+    public static final byte CMD_FIND_DEVICE = (byte) 0x71;
+
+
+    // WearFit writes uses other sources as well. They don't do anything though.
+    public static final byte ARG_SEND_NOTIFICATION_SOURCE_CALL = (byte) 0x01;
+    public static final byte ARG_SEND_NOTIFICATION_SOURCE_STOP_CALL = (byte) 0x02;
+    public static final byte ARG_SEND_NOTIFICATION_SOURCE_MESSAGE = (byte) 0x03;
+    public static final byte ARG_SEND_NOTIFICATION_SOURCE_QQ = (byte) 0x07;
+    public static final byte ARG_SEND_NOTIFICATION_SOURCE_WECHAT = (byte) 0x09;
+    public static final byte ARG_SEND_NOTIFICATION_SOURCE_WHATSAPP = (byte) 0x0a;
+    public static final byte ARG_SEND_NOTIFICATION_SOURCE_LINE = (byte) 0x0e;
+    public static final byte ARG_SEND_NOTIFICATION_SOURCE_TWITTER = (byte) 0x0f;
+    public static final byte ARG_SEND_NOTIFICATION_SOURCE_FACEBOOK = (byte) 0x10;
+    public static final byte ARG_SEND_NOTIFICATION_SOURCE_FACEBOOK2 = (byte) 0x11;
+    public static final byte ARG_SEND_NOTIFICATION_SOURCE_WEIBO = (byte) 0x13;
+    public static final byte ARG_SEND_NOTIFICATION_SOURCE_KAKOTALK = (byte) 0x14;
+    // ARG_SET_NOTIFICATION_SOURCE_*
+    // 02 (This is 00 and 01 during connection. Doesn't seem to do anything.)
+    // ASCII
+    public static final byte CMD_SEND_NOTIFICATION = (byte) 0x72;
+
+
+    public static final byte ARG_SET_ALARM_REMINDER_REPEAT_WEEKDAY = (byte) 0x1F;
+    public static final byte ARG_SET_ALARM_REMINDER_REPEAT_EVERY_DAY = (byte) 0x7F;
+    public static final byte ARG_SET_ALARM_REMINDER_REPEAT_ONE_TIME = (byte) 0x80;
+    public static final byte ARG_SET_ALARM_REMINDER_REPEAT_MONDAY = (byte) 0x01;
+    public static final byte ARG_SET_ALARM_REMINDER_REPEAT_TUESDAY = (byte) 0x02;
+    public static final byte ARG_SET_ALARM_REMINDER_REPEAT_WEDNESDAY = (byte) 0x04;
+    public static final byte ARG_SET_ALARM_REMINDER_REPEAT_THURSDAY = (byte) 0x08;
+    public static final byte ARG_SET_ALARM_REMINDER_REPEAT_FRIDAY = (byte) 0x10;
+    public static final byte ARG_SET_ALARM_REMINDER_REPEAT_SATURDAY = (byte) 0x20;
+    public static final byte ARG_SET_ALARM_REMINDER_REPEAT_SUNDAY = (byte) 0x40;
+
+    // reminder id starting at 0
+    // enable (00/01)
+    // hour
+    // minute
+    // bit field of ARG_SET_ALARM_REMINDER_REPEAT_*
+    public static final byte CMD_SET_ALARM_REMINDER = (byte) 0x73;
+
+
+    public static final byte ARG_SET_PERSONAL_INFORMATION_UNIT_DISTANCE_MILES = (byte) 0x00;
+    public static final byte ARG_SET_PERSONAL_INFORMATION_UNIT_DISTANCE_KILOMETERS = (byte) 0x01;
+    // step length (in/cm)
+    // step length (in/cm)
+    // age (years)
+    // height (in/cm)
+    // weight (lb/kg)
+    // ARG_SET_PERSONAL_INFORMATION_UNIT_DISTANCE_*
+    // target step count (kilo)
+    // 5a
+    // 82
+    // 3c
+    // 5a
+    // 28
+    // b4
+    // 5d
+    // 64
+    public static final byte CMD_SET_PERSONAL_INFORMATION = (byte) 0x74;
+
+
+    // enable (00/01)
+    // start hour
+    // start minute
+    // end hour
+    // end minute
+    // 2d
+    public static final byte CMD_SET_SEDENTARY_REMINDER = (byte) 0x75;
+
+
+    // enable (00/01)
+    // start hour
+    // start minute
+    // end hour
+    // end minute
+    public static final byte CMD_SET_QUITE_HOURS = (byte) 0x76;
+
+
+    // enable (00/01)
+    public static final byte CMD_SET_HEADS_UP_SCREEN = (byte) 0x77;
+
+
+    // Looks like enable/disable.
+    public static final byte CMD_78 = (byte) 0x78;
+
+
+    // The watch enters photograph mode, but doesn't appear to send a trigger signal.
+    // enable (00/01)
+    public static final byte CMD_SET_PHOTOGRAPH_MODE = (byte) 0x79;
+
+
+    // enable (00/01)
+    public static final byte CMD_SET_LOST_REMINDER = (byte) 0x7a;
+
+
+    // 7b has 1 argument. Looks like enable/disable.
+
+    public static final byte ARG_SET_TIMEMODE_24H = 0x00;
+    public static final byte ARG_SET_TIMEMODE_12H = 0x01;
+    // ARG_SET_TIMEMODE_*
+    public static final byte CMD_SET_TIMEMODE = (byte) 0x7c;
+
+
+    // 14 arguments. Watch might reply with RPRT_BATTERY.
+    public static final byte CMD_7e = (byte) 0x7e;
+
+
+    // 01
+    // fall hour
+    // fall minute
+    // awake hour
+    // awake minute
+    public static final byte CMD_SET_SLEEP_TIME = (byte) 0x7f;
+
+
+    // enable (00/01)
+    public static final byte CMD_SET_REAL_TIME_HEART_RATE = (byte) 0x84;
+
+
+    // looks like enable/disable.
+    public static final byte CMD_85 = (byte) 0x85;
+
+
+    // 00
+    // year hi
+    // year lo
+    // month
+    // day
+    // hour
+    // minute
+    // second
+    public static final byte CMD_SET_DATE_TIME = (byte) 0x93;
+
+    // 3 arguments. Sent when saving personal information.
+    public static final byte CMD_95 = (byte) 0x95;
+
+    // looks like enable/disable.
+    public static final byte CMD_96 = (byte) 0x96;
+
+
+    // looks like enable/disable.
+    public static final byte CMD_e5 = (byte) 0xe5;
+
+
+    // If this is sent after {@link CMD_FACTORY_RESET}, it's a shutdown, not a reboot.
+    // Rebooting resets the watch face and wallpaper.
+    public static final byte CMD_REBOOT = (byte) 0xff;
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/makibeshr3/MakibesHR3Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/makibeshr3/MakibesHR3Coordinator.java
new file mode 100644
index 0000000..bcfeb5f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/makibeshr3/MakibesHR3Coordinator.java
@@ -0,0 +1,255 @@
+/*  Copyright (C) 2019-2024 Andreas Shimokawa, Cre3per, Damien Gaignon,
+    Daniel Dakhno, Jos茅 Rebelo, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.makibeshr3;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.List;
+import java.util.regex.Pattern;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.net.Uri;
+
+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.GBException;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLEDeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.entities.MakibesHR3ActivitySampleDao;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.makibeshr3.MakibesHR3DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+import static nodomain.freeyourgadget.gadgetbridge.GBApplication.getContext;
+
+
+public class MakibesHR3Coordinator extends AbstractBLEDeviceCoordinator {
+
+    public static final int FindPhone_ON = -1;
+    public static final int FindPhone_OFF = 0;
+
+    private static final Logger LOG = LoggerFactory.getLogger(MakibesHR3Coordinator.class);
+
+
+    public static boolean shouldEnableHeadsUpScreen(SharedPreferences sharedPrefs) {
+        String liftMode = sharedPrefs.getString(DeviceSettingsPreferenceConst.PREF_ACTIVATE_DISPLAY_ON_LIFT, getContext().getString(R.string.p_on));
+
+        // Makibes HR3 doesn't support scheduled intervals. Treat it as "on".
+        return !liftMode.equals(getContext().getString(R.string.p_off));
+    }
+
+    public static boolean shouldEnableLostReminder(SharedPreferences sharedPrefs) {
+        String lostReminder = sharedPrefs.getString(DeviceSettingsPreferenceConst.PREF_DISCONNECT_NOTIFICATION, getContext().getString(R.string.p_on));
+
+        // Makibes HR3 doesn't support scheduled intervals. Treat it as "on".
+        return !lostReminder.equals(getContext().getString(R.string.p_off));
+    }
+
+    /**
+     * @param startOut out Only hour/minute are used.
+     * @param endOut   out Only hour/minute are used.
+     * @return True if quite hours are enabled.
+     */
+    public static boolean getQuiteHours(SharedPreferences sharedPrefs, Calendar startOut, Calendar endOut) {
+        String doNotDisturb = sharedPrefs.getString(DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_NOAUTO, getContext().getString(R.string.p_off));
+
+        if (doNotDisturb.equals(getContext().getString(R.string.p_off))) {
+            return false;
+        } else {
+            String start = sharedPrefs.getString(DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_NOAUTO_START, "00:00");
+            String end = sharedPrefs.getString(DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_NOAUTO_END, "00:00");
+
+            DateFormat df = new SimpleDateFormat("HH:mm");
+
+            try {
+                startOut.setTime(df.parse(start));
+                endOut.setTime(df.parse(end));
+
+                return true;
+            } catch (Exception e) {
+                LOG.error("Unexpected exception in MiBand2Coordinator.getTime: " + e.getMessage());
+                return false;
+            }
+        }
+    }
+
+    /**
+     * @return {@link #FindPhone_OFF}, {@link #FindPhone_ON}, or the duration
+     */
+    public static int getFindPhone(SharedPreferences sharedPrefs) {
+        String findPhone = sharedPrefs.getString(DeviceSettingsPreferenceConst.PREF_FIND_PHONE, getContext().getString(R.string.p_off));
+
+        if (findPhone.equals(getContext().getString(R.string.p_off))) {
+            return FindPhone_OFF;
+        } else if (findPhone.equals(getContext().getString(R.string.p_on))) {
+            return FindPhone_ON;
+        } else { // Duration
+            String duration = sharedPrefs.getString(DeviceSettingsPreferenceConst.PREF_FIND_PHONE_DURATION, "0");
+
+            try {
+                int iDuration;
+
+                try {
+                    iDuration = Integer.valueOf(duration);
+                } catch (Exception ex) {
+                    LOG.warn(ex.getMessage());
+                    iDuration = 60;
+                }
+
+                return iDuration;
+            } catch (Exception e) {
+                LOG.error("Unexpected exception in MiBand2Coordinator.getTime: " + e.getMessage());
+                return FindPhone_ON;
+            }
+        }
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("Y808|MAKIBES HR3");
+    }
+
+    @Override
+    protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
+        Long deviceId = device.getId();
+        QueryBuilder<?> qb = session.getMakibesHR3ActivitySampleDao().queryBuilder();
+        qb.where(MakibesHR3ActivitySampleDao.Properties.DeviceId.eq(deviceId)).buildDelete().executeDeleteWithoutDetachingEntities();
+    }
+
+    @Override
+    public int getBondingStyle() {
+        return BONDING_STYLE_BOND;
+    }
+
+    @Override
+    public boolean supportsCalendarEvents() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsRealtimeData() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsWeather() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsFindDevice() {
+        return true;
+    }
+
+    @Nullable
+    @Override
+    public Class<? extends Activity> getPairingActivity() {
+        return null;
+    }
+
+    @Override
+    public boolean supportsActivityDataFetching() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsActivityTracking() {
+        return true;
+    }
+
+    @Override
+    public SampleProvider<? extends ActivitySample> getSampleProvider(GBDevice device, DaoSession session) {
+        return new MakibesHR3SampleProvider(device, session);
+    }
+
+    @Override
+    public InstallHandler findInstallHandler(Uri uri, Context context) {
+        return null;
+    }
+
+    @Override
+    public boolean supportsScreenshots(final GBDevice device) {
+        return false;
+    }
+
+    @Override
+    public int getAlarmSlotCount(GBDevice device) {
+        return 8;
+    }
+
+    @Override
+    public boolean supportsHeartRateMeasurement(GBDevice device) {
+        return true;
+    }
+
+    @Override
+    public String getManufacturer() {
+        return "Makibes";
+    }
+
+    @Override
+    public boolean supportsAppsManagement(final GBDevice device) {
+        return false;
+    }
+
+    @Override
+    public Class<? extends Activity> getAppsManagementActivity() {
+        return null;
+    }
+
+    @Override
+    public int[] getSupportedDeviceSpecificSettings(GBDevice device) {
+        return new int[]{
+                R.xml.devicesettings_timeformat,
+                R.xml.devicesettings_liftwrist_display,
+                R.xml.devicesettings_disconnectnotification,
+                R.xml.devicesettings_donotdisturb_no_auto,
+                R.xml.devicesettings_find_phone,
+                R.xml.devicesettings_transliteration
+        };
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return MakibesHR3DeviceSupport.class;
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_makibes_hr3;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/makibeshr3/MakibesHR3SampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/makibeshr3/MakibesHR3SampleProvider.java
new file mode 100644
index 0000000..9a2dbc7
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/makibeshr3/MakibesHR3SampleProvider.java
@@ -0,0 +1,78 @@
+/*  Copyright (C) 2019-2024 Cre3per
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.makibeshr3;
+
+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.MakibesHR3ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.entities.MakibesHR3ActivitySampleDao;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+
+public class MakibesHR3SampleProvider extends AbstractSampleProvider<MakibesHR3ActivitySample> {
+    public MakibesHR3SampleProvider(GBDevice device, DaoSession session) {
+        super(device, session);
+    }
+
+    @Override
+    public ActivityKind normalizeType(int rawType) {
+        return ActivityKind.fromCode(rawType);
+    }
+
+    @Override
+    public int toRawActivityKind(ActivityKind activityKind) {
+        return activityKind.getCode();
+    }
+
+    @Override
+    public float normalizeIntensity(int rawIntensity) {
+        return rawIntensity;
+    }
+
+    @Override
+    public MakibesHR3ActivitySample createActivitySample() {
+        return new MakibesHR3ActivitySample();
+    }
+
+    @Override
+    public AbstractDao<MakibesHR3ActivitySample, ?> getSampleDao() {
+        return getSession().getMakibesHR3ActivitySampleDao();
+    }
+
+    @Nullable
+    @Override
+    protected Property getRawKindSampleProperty() {
+        return MakibesHR3ActivitySampleDao.Properties.RawKind;
+    }
+
+    @NonNull
+    @Override
+    protected Property getTimestampSampleProperty() {
+        return MakibesHR3ActivitySampleDao.Properties.Timestamp;
+    }
+
+    @NonNull
+    @Override
+    protected Property getDeviceIdentifierSampleProperty() {
+        return MakibesHR3ActivitySampleDao.Properties.DeviceId;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/marstek/MarstekB2500DeviceCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/marstek/MarstekB2500DeviceCoordinator.java
new file mode 100644
index 0000000..c08919a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/marstek/MarstekB2500DeviceCoordinator.java
@@ -0,0 +1,102 @@
+/*  Copyright (C) 2024 Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.marstek;
+
+
+import android.app.Activity;
+
+import androidx.annotation.NonNull;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.GBException;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.AbstractDeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.marstek.MarstekB2500DeviceSupport;
+
+
+public class MarstekB2500DeviceCoordinator extends AbstractDeviceCoordinator {
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_marstek_b2500;
+    }
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_vesc;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_vesc_disabled;
+    }
+
+    @Override
+    public String getManufacturer() {
+        return "Marstek";
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return MarstekB2500DeviceSupport.class;
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("HM_B2500_.*");
+    }
+
+    @Override
+    protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
+
+    }
+
+    @Override
+    public boolean supportsAppsManagement(final GBDevice device) {
+        return true;
+    }
+
+    @Override
+    public Class<? extends Activity> getAppsManagementActivity() {
+        return SolarEquipmentStatusActivity.class;
+    }
+
+    @Override
+    public int getBondingStyle() {
+        return BONDING_STYLE_NONE;
+    }
+
+    @Override
+    public int[] getSupportedDeviceSpecificSettings(GBDevice device) {
+        return new int[]{
+                R.xml.devicesettings_battery_allow_pass_through,
+                R.xml.devicesettings_battery_minimum_charge,
+                R.xml.devicesettings_battery_discharge_5
+        };
+    }
+
+    @Override
+    public boolean isExperimental() {
+        return true;
+    }
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/marstek/SolarEquipmentStatusActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/marstek/SolarEquipmentStatusActivity.java
new file mode 100644
index 0000000..0816469
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/marstek/SolarEquipmentStatusActivity.java
@@ -0,0 +1,190 @@
+/*  Copyright (C) 2024 Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.marstek;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.core.content.ContextCompat;
+import androidx.gridlayout.widget.GridLayout;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
+
+import com.google.android.material.card.MaterialCardView;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBActivity;
+import nodomain.freeyourgadget.gadgetbridge.activities.dashboard.GaugeDrawer;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class SolarEquipmentStatusActivity extends AbstractGBActivity {
+    public static String ACTION_SEND_SOLAR_EQUIPMENT_STATUS = "send_solar_equipment_status";
+    public static String EXTRA_BATTERY_PCT = "battery_pct";
+    public static String EXTRA_PANEL1_WATT = "panel1_watt";
+    public static String EXTRA_PANEL2_WATT = "panel2_watt";
+    public static String EXTRA_OUTPUT1_WATT = "output1_watt";
+    public static String EXTRA_OUTPUT2_WATT = "output2_watt";
+
+    private final Map<String, View> widgetMap = new HashMap<>();
+    private GridLayout gridLayout;
+    private SwipeRefreshLayout swipeLayout;
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            if (Objects.requireNonNull(action).equals(ACTION_SEND_SOLAR_EQUIPMENT_STATUS)) {
+                Bundle extras = intent.getExtras();
+                if (extras != null) {
+                    int battery_pct = extras.getInt(EXTRA_BATTERY_PCT);
+                    int panel1_watt = extras.getInt(EXTRA_PANEL1_WATT);
+                    int panel2_watt = extras.getInt(EXTRA_PANEL2_WATT);
+                    int output1_watt = extras.getInt(EXTRA_OUTPUT1_WATT);
+                    int output2_watt = extras.getInt(EXTRA_OUTPUT2_WATT);
+                    updateWidget("battery", battery_pct + "%", (float) (battery_pct / 100.0));
+                    updateWidget("panel1", panel1_watt + "W", (float) (panel1_watt / 380.0));
+                    updateWidget("panel2", panel2_watt + "W", (float) (panel1_watt / 380.0));
+                    updateWidget("output1", output1_watt + "W", (float) (output1_watt / 400.0));
+                    updateWidget("output2", output2_watt + "W", (float) (output1_watt / 400.0));
+
+                    swipeLayout.setRefreshing(false);
+                }
+            }
+        }
+    };
+    private GBDevice gBDevice = null;
+
+    public static int[] getColors() {
+        return new int[]{
+                ContextCompat.getColor(GBApplication.getContext(), R.color.chart_stress_unknown),
+                ContextCompat.getColor(GBApplication.getContext(), R.color.vo2max_value_poor_color),
+                ContextCompat.getColor(GBApplication.getContext(), R.color.vo2max_value_fair_color),
+                ContextCompat.getColor(GBApplication.getContext(), R.color.vo2max_value_good_color),
+        };
+    }
+
+    public static int[] getColorsOutput() {
+        return new int[]{
+                ContextCompat.getColor(GBApplication.getContext(), R.color.chart_stress_unknown),
+                ContextCompat.getColor(GBApplication.getContext(), R.color.vo2max_value_good_color),
+                ContextCompat.getColor(GBApplication.getContext(), R.color.vo2max_value_fair_color),
+                ContextCompat.getColor(GBApplication.getContext(), R.color.vo2max_value_poor_color),
+        };
+    }
+
+    public static float[] getSegments() {
+        return new float[]{
+                0.01f,
+                0.09f,
+                0.2f,
+                0.7f,
+        };
+    }
+
+    public static float[] getSegmentsOutput() {
+        return new float[]{
+                0.01f,
+                0.33f,
+                0.33f,
+                0.33f,
+        };
+    }
+
+    private void updateWidget(String name, String value, float gaugeFill) {
+        View view = widgetMap.get(name);
+        if (view != null) {
+            TextView gaugeValue = view.findViewById(R.id.gauge_value);
+            gaugeValue.setText(value);
+            GaugeDrawer gaugeDrawer = new GaugeDrawer();
+            ImageView gaugeBar = view.findViewById(R.id.gauge_bar);
+            gaugeDrawer.drawSegmentedGauge(gaugeBar, name.startsWith("output") ? getColorsOutput() : getColors(), name.startsWith("output") ? getSegmentsOutput() : getSegments(), gaugeFill, true, false);
+        }
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        Bundle extras = getIntent().getExtras();
+        if (extras != null) {
+            gBDevice = extras.getParcelable(GBDevice.EXTRA_DEVICE);
+        }
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_solar_equipment_status);
+        gridLayout = findViewById(R.id.solarequipmentview_gridlayout);
+        createWidget("panel1", "Panel 1", 1);
+        createWidget("panel2", "Panel 2", 1);
+        createWidget("battery", "Battery", 2);
+        createWidget("output1", "Output 1", 1);
+        createWidget("output2", "Output 2", 1);
+
+        // Set pull-down-to-refresh action
+        swipeLayout = findViewById(R.id.solarequipmentview_swipe_layout);
+        swipeLayout.setOnRefreshListener(() -> {
+            if (gBDevice == null || !gBDevice.isInitialized()) {
+                swipeLayout.setRefreshing(false);
+                GB.toast(getString(R.string.info_no_devices_connected), Toast.LENGTH_LONG, GB.WARN);
+                return;
+            }
+            GBApplication.deviceService(gBDevice).onFetchRecordedData(0);
+        });
+
+        IntentFilter filterLocal = new IntentFilter();
+        filterLocal.addAction(ACTION_SEND_SOLAR_EQUIPMENT_STATUS);
+        LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, filterLocal);
+    }
+
+    private void createWidget(String name, String label, int columnSpan) {
+        final float scale = getResources().getDisplayMetrics().density;
+
+        GridLayout.LayoutParams layoutParams = new GridLayout.LayoutParams(
+                GridLayout.spec(GridLayout.UNDEFINED, GridLayout.FILL, 1f),
+                GridLayout.spec(GridLayout.UNDEFINED, columnSpan, GridLayout.FILL, 1f)
+        );
+        layoutParams.width = 0;
+        int pixels_8dp = (int) (8 * scale + 0.5f);
+        layoutParams.setMargins(pixels_8dp, pixels_8dp, pixels_8dp, pixels_8dp);
+
+        MaterialCardView card = new MaterialCardView(this);
+        int pixels_4dp = (int) (4 * scale + 0.5f);
+        card.setRadius(pixels_4dp);
+        card.setCardElevation(pixels_4dp);
+        card.setContentPadding(pixels_4dp, pixels_4dp, pixels_4dp, pixels_4dp);
+        card.setLayoutParams(layoutParams);
+        LayoutInflater inflater = getLayoutInflater();
+
+        final View gaugeView = inflater.inflate(R.layout.dashboard_widget_generic_gauge, card, false);
+        final TextView gaugeLabel = gaugeView.findViewById(R.id.gauge_label);
+        gaugeLabel.setText(label);
+
+        card.addView(gaugeView);
+        widgetMap.put(name, gaugeView);
+        gridLayout.addView(card);
+    }
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/AbstractMiBandFWHelper.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/AbstractMiBandFWHelper.java
new file mode 100644
index 0000000..f01e4b3
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/AbstractMiBandFWHelper.java
@@ -0,0 +1,155 @@
+/*  Copyright (C) 2016-2024 Andreas Shimokawa, Carsten Pfeiffer, Daniele
+    Gobbetti, Jos茅 Rebelo, Taavi Eom盲e
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.miband;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.net.Uri;
+
+import androidx.annotation.NonNull;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Locale;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareType;
+import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.UriHelper;
+
+/**
+ * Also see Mi1SFirmwareInfo.
+ */
+public abstract class AbstractMiBandFWHelper {
+    private static final Logger LOG = LoggerFactory.getLogger(AbstractMiBandFWHelper.class);
+
+    private byte[] fw;
+
+    public AbstractMiBandFWHelper(Uri uri, Context context) throws IOException {
+        UriHelper uriHelper = UriHelper.get(uri, context);
+        String pebblePattern = ".*\\.(pbw|pbz|pbl)";
+        if (uriHelper.getFileName().matches(pebblePattern)) {
+            throw new IOException("Firmware has a filename that looks like a Pebble app/firmware.");
+        }
+
+        if (uriHelper.getFileSize() > getMaxExpectedFileSize()) {
+            throw new IOException("Firmware size is larger than the maximum expected file size of " + getMaxExpectedFileSize());
+        }
+
+        try (InputStream in = new BufferedInputStream(uriHelper.openInputStream())) {
+            this.fw = FileUtils.readAll(in, getMaxExpectedFileSize());
+            determineFirmwareInfo(fw);
+        } catch (IOException ex) {
+            throw ex; // pass through
+        } catch (IllegalArgumentException ex) {
+            throw new IOException("This doesn't seem to be a Mi Band firmware: " + ex.getLocalizedMessage(), ex);
+        } catch (Exception e) {
+            throw new IOException("Error reading firmware file: " + uri.toString(), e);
+        }
+    }
+
+    /**
+     * Returns a localized, user-visible representation of the kind of firmware to be installed.
+     */
+    @NonNull
+    public abstract String getFirmwareKind();
+
+    public abstract int getFirmwareVersion();
+
+    public abstract int getFirmware2Version();
+
+    public static String formatFirmwareVersion(int version) {
+        if (version == -1) {
+            return GBApplication.getContext().getString(R.string._unknown_);
+        }
+
+        return String.format(Locale.UK,
+                "%d.%d.%d.%d",
+                version >> 24 & 255,
+                version >> 16 & 255,
+                version >> 8 & 255,
+                version & 255);
+    }
+
+    public String getHumanFirmwareVersion() {
+        return format(getFirmwareVersion());
+    }
+
+    public abstract String getHumanFirmwareVersion2();
+
+    public String format(int version) {
+        return formatFirmwareVersion(version);
+    }
+
+    @NonNull
+    public byte[] getFw() {
+        if (fw == null) {
+            throw new IllegalStateException("fw is null");
+        }
+
+        return fw;
+    }
+
+    public void unsetFwBytes() {
+        this.fw = null;
+    }
+
+    public boolean isFirmwareWhitelisted() {
+        for (int wlf : getWhitelistedFirmwareVersions()) {
+            if (wlf == getFirmwareVersion()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * The maximum expected file size, in bytes. Files larger than this are assumed to be invalid.
+     */
+    public long getMaxExpectedFileSize() {
+        return 1024 * 1024 * 16; // 16.0MB
+    }
+
+    protected abstract int[] getWhitelistedFirmwareVersions();
+
+    public abstract boolean isFirmwareGenerallyCompatibleWith(GBDevice device);
+
+    public abstract boolean isSingleFirmware();
+
+    /**
+     * @param wholeFirmwareBytes
+     * @return
+     * @throws IllegalArgumentException when the data is not recognized as firmware data
+     */
+    @NonNull
+    protected abstract void determineFirmwareInfo(byte[] wholeFirmwareBytes);
+
+    public abstract void checkValid() throws IllegalArgumentException;
+
+    public abstract HuamiFirmwareType getFirmwareType();
+
+    public Bitmap getPreview() {
+        return null;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/AbstractMiBandFWInstallHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/AbstractMiBandFWInstallHandler.java
new file mode 100644
index 0000000..d27b4f8
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/AbstractMiBandFWInstallHandler.java
@@ -0,0 +1,158 @@
+/*  Copyright (C) 2016-2024 Andreas Shimokawa, Carsten Pfeiffer, Daniel
+    Dakhno, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.miband;
+
+import android.content.Context;
+import android.net.Uri;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.InstallActivity;
+import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.GenericItem;
+import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
+
+import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareType.AGPS_UIHH;
+import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareType.WATCHFACE;
+
+public abstract class AbstractMiBandFWInstallHandler implements InstallHandler {
+    private static final Logger LOG = LoggerFactory.getLogger(AbstractMiBandFWInstallHandler.class);
+
+    protected final Uri mUri;
+    protected final Context mContext;
+    protected AbstractMiBandFWHelper helper;
+    private String errorMessage;
+
+    public AbstractMiBandFWInstallHandler(Uri uri, Context context) {
+        mUri = uri;
+        mContext = context;
+
+        try {
+            helper = createHelper(uri, context);
+        } catch (IOException e) {
+            errorMessage = e.getMessage();
+            LOG.warn(errorMessage, e);
+        }
+    }
+
+    public Uri getUri() {
+        return mUri;
+    }
+
+    public Context getContext() {
+        return mContext;
+    }
+
+    public AbstractMiBandFWHelper getHelper() {
+        return helper;
+    }
+
+    protected abstract AbstractMiBandFWHelper createHelper(Uri uri, Context context) throws IOException;
+
+    private GenericItem createInstallItem(GBDevice device) {
+        DeviceCoordinator coordinator = device.getDeviceCoordinator();
+        return new GenericItem(mContext.getString(R.string.installhandler_firmware_name, mContext.getString(coordinator.getDeviceNameResource()), helper.getFirmwareKind(), helper.getHumanFirmwareVersion()));
+    }
+
+    protected String getFwUpgradeNotice() {
+        return mContext.getString(R.string.fw_upgrade_notice, helper.getHumanFirmwareVersion());
+    }
+
+    @Override
+    public void validateInstallation(InstallActivity installActivity, GBDevice device) {
+        if (device.isBusy()) {
+            installActivity.setInfoText(device.getBusyTask());
+            installActivity.setInstallEnabled(false);
+            return;
+        }
+
+        if (!isSupportedDeviceType(device)) {
+            installActivity.setInfoText(mContext.getString(R.string.fwapp_install_device_not_supported));
+            installActivity.setInstallEnabled(false);
+            return;
+        } else if (!device.isInitialized()) {
+            installActivity.setInfoText(mContext.getString(R.string.fwapp_install_device_not_ready));
+            installActivity.setInstallEnabled(false);
+            return;
+        }
+
+        try {
+            helper.checkValid();
+        } catch (IllegalArgumentException ex) {
+            installActivity.setInfoText(ex.getLocalizedMessage());
+            installActivity.setInstallEnabled(false);
+            return;
+        }
+
+        DeviceCoordinator coordinator = device.getDeviceCoordinator();
+
+        GenericItem fwItem = createInstallItem(device);
+        fwItem.setIcon(coordinator.getDefaultIconResource());
+
+        if (!helper.isFirmwareGenerallyCompatibleWith(device)) {
+            fwItem.setDetails(mContext.getString(R.string.miband_fwinstaller_incompatible_version));
+            installActivity.setInfoText(mContext.getString(R.string.fwinstaller_firmware_not_compatible_to_device));
+            installActivity.setInstallEnabled(false);
+            return;
+        }
+        StringBuilder builder = new StringBuilder();
+        if (!helper.getFirmwareType().isWatchface() && !helper.getFirmwareType().isApp() && helper.getFirmwareType() != AGPS_UIHH) {
+            if (helper.isSingleFirmware()) {
+                builder.append(getFwUpgradeNotice());
+            } else {
+                builder.append(mContext.getString(R.string.fw_multi_upgrade_notice, helper.getHumanFirmwareVersion(), helper.getHumanFirmwareVersion2()));
+            }
+
+
+            if (helper.isFirmwareWhitelisted()) {
+                builder.append(" ").append(mContext.getString(R.string.miband_firmware_known));
+                fwItem.setDetails(mContext.getString(R.string.miband_fwinstaller_compatible_version));
+                // TODO: set a CHECK (OKAY) button
+            } else {
+                builder.append("  ").append(mContext.getString(R.string.miband_firmware_unknown_warning)).append(" \n\n")
+                        .append(mContext.getString(R.string.miband_firmware_suggest_whitelist, String.valueOf(helper.getFirmwareVersion())));
+                fwItem.setDetails(mContext.getString(R.string.miband_fwinstaller_untested_version));
+                // TODO: set a UNKNOWN (question mark) button
+            }
+        }
+
+        installActivity.setPreview(helper.getPreview());
+
+        installActivity.setInfoText(builder.toString());
+        installActivity.setInstallItem(fwItem);
+        installActivity.setInstallEnabled(true);
+    }
+
+    protected abstract boolean isSupportedDeviceType(GBDevice device);
+
+    @Override
+    public void onStartInstall(GBDevice device) {
+
+    }
+
+    @Override
+    public boolean isValid() {
+        return helper != null;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/AbstractMiBandSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/AbstractMiBandSampleProvider.java
new file mode 100644
index 0000000..896472c
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/AbstractMiBandSampleProvider.java
@@ -0,0 +1,73 @@
+/*  Copyright (C) 2016-2024 Andreas Shimokawa, Carsten Pfeiffer, Daniele
+    Gobbetti
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.miband;
+
+import androidx.annotation.NonNull;
+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.MiBandActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.entities.MiBandActivitySampleDao;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+/**
+ * Base class for Mi1 and Mi2 sample providers. At the moment they both share the
+ * same activity sample class.
+ */
+public abstract class AbstractMiBandSampleProvider extends AbstractSampleProvider<MiBandActivitySample> {
+
+    // maybe this should be configurable 256 seems way off, though.
+    private final float movementDivisor = 180.0f; //256.0f;
+
+    public AbstractMiBandSampleProvider(GBDevice device, DaoSession session) {
+        super(device, session);
+    }
+
+    @Override
+    public float normalizeIntensity(int rawIntensity) {
+        return rawIntensity / movementDivisor;
+    }
+
+    @Override
+    public AbstractDao<MiBandActivitySample, ?> getSampleDao() {
+        return getSession().getMiBandActivitySampleDao();
+    }
+
+    @NonNull
+    @Override
+    protected Property getTimestampSampleProperty() {
+        return MiBandActivitySampleDao.Properties.Timestamp;
+    }
+
+    @NonNull
+    @Override
+    protected Property getDeviceIdentifierSampleProperty() {
+        return MiBandActivitySampleDao.Properties.DeviceId;
+    }
+
+    @Override
+    protected Property getRawKindSampleProperty() {
+        return MiBandActivitySampleDao.Properties.RawKind;
+    }
+
+    @Override
+    public MiBandActivitySample createActivitySample() {
+        return new MiBandActivitySample();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/DateTimeDisplay.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/DateTimeDisplay.java
new file mode 100644
index 0000000..9e34fdd
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/DateTimeDisplay.java
@@ -0,0 +1,22 @@
+/*  Copyright (C) 2016-2024 Carsten Pfeiffer
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.miband;
+
+public enum DateTimeDisplay {
+    TIME,
+    DATE_TIME
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/DoNotDisturb.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/DoNotDisturb.java
new file mode 100644
index 0000000..ebd8bc9
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/DoNotDisturb.java
@@ -0,0 +1,24 @@
+/*  Copyright (C) 2017-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.miband;
+
+public enum DoNotDisturb {
+    OFF,
+    AUTOMATIC,
+    SCHEDULED,
+    ALWAYS,
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBand2SampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBand2SampleProvider.java
new file mode 100644
index 0000000..9b5238e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBand2SampleProvider.java
@@ -0,0 +1,101 @@
+/*  Copyright (C) 2016-2024 Andreas Shimokawa, Carsten Pfeiffer, Daniele
+    Gobbetti
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.miband;
+
+import java.util.List;
+
+import de.greenrobot.dao.query.QueryBuilder;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.MiBandActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.entities.MiBandActivitySampleDao;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+
+import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.TYPE_IGNORE;
+import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.TYPE_NO_CHANGE;
+import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.TYPE_UNSET;
+
+public class MiBand2SampleProvider extends AbstractMiBandSampleProvider {
+
+    public MiBand2SampleProvider(GBDevice device, DaoSession session) {
+        super(device, session);
+    }
+
+    @Override
+    protected List<MiBandActivitySample> getGBActivitySamples(int timestamp_from, int timestamp_to) {
+        List<MiBandActivitySample> samples = super.getGBActivitySamples(timestamp_from, timestamp_to);
+        postprocess(samples);
+        return samples;
+    }
+
+    /**
+     * "Temporary" runtime post processing of activity kinds.
+     */
+    private void postprocess(List<MiBandActivitySample> samples) {
+        if (samples.isEmpty()) {
+            return;
+        }
+
+        int lastValidKind = determinePreviousValidActivityType(samples.get(0));
+        for (MiBandActivitySample sample : samples) {
+            int rawKind = sample.getRawKind();
+            if (rawKind != TYPE_UNSET) {
+                rawKind &= 0xf;
+                sample.setRawKind(rawKind);
+            }
+
+            switch (rawKind) {
+                case TYPE_IGNORE:
+                case TYPE_NO_CHANGE:
+                    if (lastValidKind != TYPE_UNSET) {
+                        sample.setRawKind(lastValidKind);
+                    }
+                    break;
+                default:
+                    lastValidKind = rawKind;
+                    break;
+            }
+        }
+    }
+
+    private int determinePreviousValidActivityType(MiBandActivitySample sample) {
+        QueryBuilder<MiBandActivitySample> qb = getSampleDao().queryBuilder();
+        qb.where(MiBandActivitySampleDao.Properties.DeviceId.eq(sample.getDeviceId()),
+                MiBandActivitySampleDao.Properties.UserId.eq(sample.getUserId()),
+                MiBandActivitySampleDao.Properties.Timestamp.lt(sample.getTimestamp()),
+                MiBandActivitySampleDao.Properties.RawKind.notIn(TYPE_NO_CHANGE, TYPE_IGNORE, TYPE_UNSET, 16, 80, 96, 112)); // all I ever had that are 0 when doing &=0xf
+        qb.orderDesc(MiBandActivitySampleDao.Properties.Timestamp);
+        qb.limit(1);
+        List<MiBandActivitySample> result = qb.build().list();
+        if (!result.isEmpty()) {
+            return result.get(0).getRawKind() & 0xf;
+        }
+        return TYPE_UNSET;
+    }
+
+    @Override
+    public ActivityKind normalizeType(int rawType) {
+        return HuamiConst.toActivityKind(rawType);
+    }
+
+    @Override
+    public int toRawActivityKind(ActivityKind activityKind) {
+        return HuamiConst.toRawActivityType(activityKind);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandConst.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandConst.java
new file mode 100644
index 0000000..fd21bd2
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandConst.java
@@ -0,0 +1,95 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, Carsten Pfeiffer, Christian
+    Fischer, Damien Gaignon, Daniele Gobbetti, Jos茅 Rebelo, Michal Novotny,
+    Szymon Tomasz Stefanek
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.miband;
+
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+import nodomain.freeyourgadget.gadgetbridge.util.Version;
+
+public final class MiBandConst {
+    public static final String PREF_MIBAND_ALARMS = "mi_alarms";
+    public static final String PREF_MIBAND_DEVICE_TIME_OFFSET_HOURS = "device_time_offset_hours";
+	public static final String PREF_MI2_DATEFORMAT = "mi2_dateformat";
+	public static final String PREF_MI2_DISPLAY_ITEM_CLOCK = "clock";
+	public static final String PREF_MI2_DISPLAY_ITEM_STEPS = "steps";
+	public static final String PREF_MI2_DISPLAY_ITEM_DISTANCE = "distance";
+	public static final String PREF_MI2_DISPLAY_ITEM_CALORIES = "calories";
+	public static final String PREF_MI2_DISPLAY_ITEM_HEART_RATE = "heart_rate";
+	public static final String PREF_MI2_DISPLAY_ITEM_BATTERY = "battery";
+    public static final String PREF_MI2_ROTATE_WRIST_TO_SWITCH_INFO = "rotate_wrist_to_cycle_info";
+	public static final String PREF_MI2_ENABLE_TEXT_NOTIFICATIONS = "mi2_enable_text_notifications";
+
+    public static final String PREF_SWIPE_UNLOCK = "swipe_unlock";
+    public static final String PREF_NIGHT_MODE = "night_mode";
+    public static final String PREF_NIGHT_MODE_START = "night_mode_start";
+    public static final String PREF_NIGHT_MODE_END = "night_mode_end";
+    public static final String PREF_NIGHT_MODE_OFF = "off";
+    public static final String PREF_NIGHT_MODE_SUNSET = "sunset";
+    public static final String PREF_NIGHT_MODE_SCHEDULED = "scheduled";
+
+    public static final String ORIGIN_INCOMING_CALL = "incoming_call";
+    public static final String ORIGIN_ALARM_CLOCK = "alarm_clock";
+    public static final String MI_GENERAL_NAME_PREFIX = "MI";
+    public static final String MI_1 = "1";
+    public static final String MI_1A = "1A";
+    public static final String MI_1S = "1S";
+    public static final String MI_AMAZFIT = "Amazfit";
+    public static final String MI_PRO = "2";
+
+    public static final Version MI2_FW_VERSION_MIN_TEXT_NOTIFICATIONS = new Version("1.0.1.28");
+    public static final Version MI2_FW_VERSION_INTERMEDIATE_UPGRADE_53 = new Version("1.0.0.53");
+
+    public enum DistanceUnit {
+        METRIC,
+        IMPERIAL
+    }
+
+    public static int getNotificationPrefIntValue(String pref, String origin, Prefs prefs, int defaultValue) {
+        String key = getNotificationPrefKey(pref, origin);
+        return prefs.getInt(key, defaultValue);
+    }
+
+    public static String getNotificationPrefStringValue(String pref, String origin, Prefs prefs, String defaultValue) {
+        String key = getNotificationPrefKey(pref, origin);
+        return prefs.getString(key, defaultValue);
+    }
+
+    public static String getNotificationPrefKey(String pref, String origin) {
+        return pref + '_' + origin;
+    }
+
+    public static final String VIBRATION_PROFILE = "mi_vibration_profile";
+    public static final String VIBRATION_COUNT = "mi_vibration_count";
+    public static final String VIBRATION_DURATION = "mi_vibration_duration";
+    public static final String VIBRATION_PAUSE = "mi_vibration_pause";
+    public static final String FLASH_COUNT = "mi_flash_count";
+    public static final String FLASH_DURATION = "mi_flash_duration";
+    public static final String FLASH_PAUSE = "mi_flash_pause";
+    public static final String FLASH_COLOUR = "mi_flash_colour";
+    public static final String FLASH_ORIGINAL_COLOUR = "mi_flash_original_colour";
+
+    public static final String DEFAULT_VALUE_VIBRATION_PROFILE = "short";
+    public static final int DEFAULT_VALUE_VIBRATION_COUNT = 3;
+    public static final int DEFAULT_VALUE_VIBRATION_DURATION = 500; // ms
+    public static final int DEFAULT_VALUE_VIBRATION_PAUSE = 500; // ms
+    public static final int DEFAULT_VALUE_FLASH_COUNT = 10; // ms
+    public static final int DEFAULT_VALUE_FLASH_DURATION = 500; // ms
+    public static final int DEFAULT_VALUE_FLASH_PAUSE = 500; // ms
+    public static final int DEFAULT_VALUE_FLASH_COLOUR = 1; // TODO: colour!
+    public static final int DEFAULT_VALUE_FLASH_ORIGINAL_COLOUR = 1; // TODO: colour!
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandCoordinator.java
new file mode 100644
index 0000000..e6719c9
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandCoordinator.java
@@ -0,0 +1,309 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, Carsten Pfeiffer, Christian
+    Fischer, Damien Gaignon, Daniel Dakhno, Daniele Gobbetti, Jos茅 Rebelo,
+    Petr Van臎k, Szymon Tomasz Stefanek
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.miband;
+
+import android.app.Activity;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.le.ScanFilter;
+import android.content.Context;
+import android.net.Uri;
+import android.os.ParcelUuid;
+
+import androidx.annotation.DrawableRes;
+import androidx.annotation.NonNull;
+import androidx.annotation.StringRes;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.List;
+
+import de.greenrobot.dao.query.QueryBuilder;
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.GBException;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettings;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsScreen;
+import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLEDeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.AbstractActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.entities.MiBandActivitySampleDao;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.ServiceDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.MiBandSupport;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivityUser.PREF_USER_NAME;
+
+public class MiBandCoordinator extends AbstractBLEDeviceCoordinator {
+    private static final Logger LOG = LoggerFactory.getLogger(MiBandCoordinator.class);
+
+    public MiBandCoordinator() {
+    }
+
+    @NonNull
+    @Override
+    public Collection<? extends ScanFilter> createBLEScanFilters() {
+        ParcelUuid mi1Service = new ParcelUuid(MiBandService.UUID_SERVICE_MIBAND_SERVICE);
+        ScanFilter filter = new ScanFilter.Builder().setServiceUuid(mi1Service).build();
+        return Collections.singletonList(filter);
+    }
+
+    @Override
+    public boolean supports(GBDeviceCandidate candidate) {
+        String macAddress = candidate.getMacAddress().toUpperCase();
+        if (macAddress.startsWith(MiBandService.MAC_ADDRESS_FILTER_1_1A)
+                || macAddress.startsWith(MiBandService.MAC_ADDRESS_FILTER_1S)) {
+            return true;
+        }
+        if (candidate.supportsService(MiBandService.UUID_SERVICE_MIBAND_SERVICE)
+                && !candidate.supportsService(MiBandService.UUID_SERVICE_MIBAND2_SERVICE)) {
+            return true;
+        }
+        // and a heuristic
+        try {
+            BluetoothDevice device = candidate.getDevice();
+            if (isHealthWearable(device)) {
+                String name = candidate.getName();
+                if (name != null && name.toUpperCase().startsWith(MiBandConst.MI_GENERAL_NAME_PREFIX.toUpperCase())) {
+                    return true;
+                }
+            }
+        } catch (Exception ex) {
+            LOG.error("unable to check device support", ex);
+        }
+        return false;
+    }
+
+    @Override
+    protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
+        Long deviceId = device.getId();
+        QueryBuilder<?> qb = session.getMiBandActivitySampleDao().queryBuilder();
+        qb.where(MiBandActivitySampleDao.Properties.DeviceId.eq(deviceId)).buildDelete().executeDeleteWithoutDetachingEntities();
+    }
+
+    @Override
+    public Class<? extends Activity> getPairingActivity() {
+        return MiBandPairingActivity.class;
+    }
+
+    @Override
+    public SampleProvider<? extends AbstractActivitySample> getSampleProvider(GBDevice device, DaoSession session) {
+        return new MiBandSampleProvider(device, session);
+    }
+
+    @Override
+    public InstallHandler findInstallHandler(Uri uri, Context context) {
+        MiBandFWInstallHandler handler = new MiBandFWInstallHandler(uri, context);
+        return handler.isValid() ? handler : null;
+    }
+
+    @Override
+    public boolean supportsActivityDataFetching() {
+        return true;
+    }
+
+    @Override
+    public int getAlarmSlotCount(GBDevice device) {
+        return 3;
+    }
+
+    @Override
+    public boolean supportsSmartWakeup(GBDevice device, int position) {
+        return true;
+    }
+
+    @Override
+    public boolean supportsActivityTracking() {
+        return true;
+    }
+
+    @Override
+    public String getManufacturer() {
+        return "Xiaomi";
+    }
+
+    @Override
+    public boolean supportsRealtimeData() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsFindDevice() {
+        return true;
+    }
+
+    public static boolean hasValidUserInfo() {
+        String dummyMacAddress = MiBandService.MAC_ADDRESS_FILTER_1_1A + ":00:00:00";
+        try {
+            UserInfo userInfo = getConfiguredUserInfo(dummyMacAddress);
+            return true;
+        } catch (IllegalArgumentException ex) {
+            return false;
+        }
+    }
+
+    /**
+     * Returns the configured user info, or, if that is not available or invalid,
+     * a default user info.
+     *
+     * @param miBandAddress
+     */
+    public static UserInfo getAnyUserInfo(String miBandAddress) {
+        try {
+            return getConfiguredUserInfo(miBandAddress);
+        } catch (Exception ex) {
+            LOG.error("Error creating user info from settings, using default user instead: " + ex);
+            return UserInfo.getDefault(miBandAddress);
+        }
+    }
+
+    /**
+     * Returns the user info from the user configured data in the preferences.
+     *
+     * @param miBandAddress
+     * @throws IllegalArgumentException when the user info can not be created
+     */
+    public static UserInfo getConfiguredUserInfo(String miBandAddress) throws IllegalArgumentException {
+        ActivityUser activityUser = new ActivityUser();
+        Prefs prefs = GBApplication.getPrefs();
+
+        UserInfo info = UserInfo.create(
+                miBandAddress,
+                prefs.getString(PREF_USER_NAME, null),
+                activityUser.getGender(),
+                activityUser.getAge(),
+                activityUser.getHeightCm(),
+                activityUser.getWeightKg(),
+                0
+        );
+        return info;
+    }
+
+    public static int getWearLocation(String deviceAddress) throws IllegalArgumentException {
+        int location = 0; //left hand
+        Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(deviceAddress));
+        if ("right".equals(prefs.getString(DeviceSettingsPreferenceConst.PREF_WEARLOCATION, "left"))) {
+            location = 1; // right hand
+        }
+        return location;
+    }
+
+    public static int getDeviceTimeOffsetHours(String deviceAddress) throws IllegalArgumentException {
+        Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(deviceAddress));
+		return prefs.getInt(MiBandConst.PREF_MIBAND_DEVICE_TIME_OFFSET_HOURS, 0);
+	}
+
+    public static boolean getHeartrateSleepSupport(String deviceAddress) throws IllegalArgumentException {
+        Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(deviceAddress));
+        return prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_HEARTRATE_USE_FOR_SLEEP_DETECTION, false);
+    }
+
+    public static int getReservedAlarmSlots(String miBandAddress) throws IllegalArgumentException {
+        Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(miBandAddress));
+        return prefs.getInt(DeviceSettingsPreferenceConst.PREF_RESERVER_ALARMS_CALENDAR, 0);
+    }
+
+    @Override
+    public boolean supportsHeartRateMeasurement(GBDevice device) {
+        String hwVersion = device.getModel();
+        return isMi1S(hwVersion) || isMiPro(hwVersion);
+    }
+
+    @Override
+    public DeviceSpecificSettings getDeviceSpecificSettings(final GBDevice device) {
+        final DeviceSpecificSettings deviceSpecificSettings = new DeviceSpecificSettings();
+
+        deviceSpecificSettings.addRootScreen(R.xml.devicesettings_wearlocation);
+        deviceSpecificSettings.addRootScreen(R.xml.devicesettings_heartrate_sleep);
+        deviceSpecificSettings.addRootScreen(R.xml.devicesettings_miband_vibrationpatterns);
+        deviceSpecificSettings.addRootScreen(R.xml.devicesettings_reserve_alarms_calendar);
+        deviceSpecificSettings.addRootScreen(R.xml.devicesettings_fake_timeoffset);
+
+        final List<Integer> connection = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.CONNECTION);
+        connection.add(R.xml.devicesettings_lowlatency_fwupdate);
+
+        final List<Integer> developer = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DEVELOPER);
+        developer.add(R.xml.devicesettings_keep_activity_data_on_device);
+
+        return deviceSpecificSettings;
+    }
+
+    @Override
+    public DeviceSpecificSettingsCustomizer getDeviceSpecificSettingsCustomizer(final GBDevice device) {
+        return new MiBandSettingsCustomizer(device);
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return MiBandSupport.class;
+    }
+
+    private boolean isMi1S(String hardwareVersion) {
+        return MiBandConst.MI_1S.equals(hardwareVersion);
+    }
+
+    private boolean isMiPro(String hardwareVersion) {
+        return MiBandConst.MI_PRO.equals(hardwareVersion);
+    }
+
+    @Override
+    public EnumSet<ServiceDeviceSupport.Flags> getInitialFlags() {
+        return EnumSet.of(ServiceDeviceSupport.Flags.THROTTLING, ServiceDeviceSupport.Flags.BUSY_CHECKING);
+    }
+
+    @Override
+    public int getOrderPriority(){
+        // all Coordinators have a priority of 0, thus get checked before this one
+        // PLEASE DO NOT EXTEND THE PRIORITIES
+        // PLEASE BUILD NEW COORDINATORS ORDER INDEPENDENT
+        // this ordering mechanism is a temporary hack and will hopefully be gone soon...
+        return 1;
+    }
+
+    @Override
+    @StringRes
+    public int getDeviceNameResource() {
+        return R.string.devicetype_miband;
+    }
+
+    @Override
+    @DrawableRes
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_miband;
+    }
+
+    @Override
+    @DrawableRes
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_miband_disabled;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandDateConverter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandDateConverter.java
new file mode 100644
index 0000000..e46275d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandDateConverter.java
@@ -0,0 +1,98 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, Carsten Pfeiffer, Daniele
+    Gobbetti, Szymon Tomasz Stefanek
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.miband;
+
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+
+public class MiBandDateConverter {
+    /**
+     * Creates a calendar object representing the current date and time.
+     */
+    public static GregorianCalendar createCalendar() {
+        return new GregorianCalendar();
+    }
+
+    /**
+     * uses the standard algorithm to convert bytes received from the MiBand to a Calendar object
+     *
+     * @param value
+     * @return
+     */
+    public static GregorianCalendar rawBytesToCalendar(byte[] value, String deviceAddress) {
+        if (value.length == 6) {
+            return rawBytesToCalendar(value, 0, deviceAddress);
+        }
+        return createCalendar();
+    }
+
+    /**
+     * uses the standard algorithm to convert bytes received from the MiBand to a Calendar object
+     *
+     * @param value
+     * @return
+     */
+    public static GregorianCalendar rawBytesToCalendar(byte[] value, int offset, String deviceAddress) {
+        if (value.length - offset >= 6) {
+            GregorianCalendar timestamp = new GregorianCalendar(
+                    value[offset] + 2000,
+                    value[offset + 1],
+                    value[offset + 2],
+                    value[offset + 3],
+                    value[offset + 4],
+                    value[offset + 5]);
+
+            int offsetInHours = MiBandCoordinator.getDeviceTimeOffsetHours(deviceAddress);
+            if (offsetInHours != 0)
+                timestamp.add(Calendar.HOUR_OF_DAY, -offsetInHours);
+
+            return timestamp;
+        }
+
+        return createCalendar();
+    }
+
+    /**
+     * uses the standard algorithm to convert a Calendar object to a byte array to send to MiBand
+     *
+     * @param timestamp
+     * @return
+     */
+    public static byte[] calendarToRawBytes(Calendar timestamp, String deviceAddress) {
+
+	    // The mi-band device currently records sleep
+	    // only if it happens after 10pm and before 7am.
+	    // The offset is used to trick the device to record sleep
+	    // in non-standard hours.
+	    // If you usually sleep, say, from 6am to 2pm, set the
+	    // shift to -8, so at 6am the device thinks it's still 10pm
+	    // of the day before.
+        int offsetInHours = MiBandCoordinator.getDeviceTimeOffsetHours(deviceAddress);
+        if (offsetInHours != 0)
+            timestamp.add(Calendar.HOUR_OF_DAY, offsetInHours);
+
+        return new byte[]{
+                (byte) (timestamp.get(Calendar.YEAR) - 2000),
+                (byte) timestamp.get(Calendar.MONTH),
+                (byte) timestamp.get(Calendar.DATE),
+                (byte) timestamp.get(Calendar.HOUR_OF_DAY),
+                (byte) timestamp.get(Calendar.MINUTE),
+                (byte) timestamp.get(Calendar.SECOND)
+        };
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandFWHelper.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandFWHelper.java
new file mode 100644
index 0000000..1108c4c
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandFWHelper.java
@@ -0,0 +1,151 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, Carsten Pfeiffer, Daniele
+    Gobbetti, Taavi Eom盲e
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.miband;
+
+import android.content.Context;
+import android.net.Uri;
+
+import androidx.annotation.NonNull;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.AbstractMiFirmwareInfo;
+
+/**
+ * Also see Mi1SFirmwareInfo.
+ */
+public class MiBandFWHelper extends AbstractMiBandFWHelper {
+    private static final Logger LOG = LoggerFactory.getLogger(MiBandFWHelper.class);
+
+    /**
+     * The backing firmware info instance, which in general supports the provided
+     * given firmware. You must call AbstractMiFirmwareInfo#checkValid() before
+     * attempting to flash it.
+     */
+    @NonNull
+    private AbstractMiFirmwareInfo firmwareInfo;
+
+    /**
+     * Provides a different notification API which is also used on Mi1A devices.
+     */
+    public static final int FW_16779790 = 16779790;
+
+    private final int[] whitelistedFirmwareVersion = {
+            16779534, // 1.0.9.14 tested by developer
+            16779547,  //1.0.9.27 tested by developer
+            16779568, //1.0.9.48 tested by developer
+            16779585, //1.0.9.65 tested by developer
+            16779779, //1.0.10.3 reported on the wiki
+            16779782, //1.0.10.6 reported on the wiki
+            16779787, //1.0.10.11 tested by developer
+            //FW_16779790, //1.0.10.14 reported on the wiki (vibration does not work currently)
+            68094986, // 4.15.12.10 tested by developer
+            68158215, // 4.16.3.7 tested by developer
+            68158486, // 4.16.4.22 tested by developer and user
+            68160271, // 4.16.11.15 tested by developer
+            84870926, // 5.15.7.14 tested by developer
+    };
+
+    public MiBandFWHelper(Uri uri, Context context) throws IOException {
+        super(uri, context);
+    }
+
+    @NonNull
+    @Override
+    public String getFirmwareKind() {
+        return GBApplication.getContext().getString(R.string.kind_firmware);
+    }
+
+    @Override
+    public int getFirmwareVersion() {
+        // FIXME: UnsupportedOperationException!
+        return firmwareInfo.getFirst().getFirmwareVersion();
+    }
+
+    @Override
+    public int getFirmware2Version() {
+        return firmwareInfo.getFirst().getFirmwareVersion();
+    }
+
+    @Override
+    public String getHumanFirmwareVersion2() {
+        return format(firmwareInfo.getSecond().getFirmwareVersion());
+    }
+
+    @Override
+    protected int[] getWhitelistedFirmwareVersions() {
+        return whitelistedFirmwareVersion;
+    }
+
+    @Override
+    public boolean isFirmwareGenerallyCompatibleWith(GBDevice device) {
+        return firmwareInfo.isGenerallyCompatibleWith(device);
+    }
+
+    @Override
+    public boolean isSingleFirmware() {
+        return firmwareInfo.isSingleMiBandFirmware();
+    }
+
+    /**
+     * @param wholeFirmwareBytes
+     * @return
+     * @throws IllegalArgumentException when the data is not recognized as firmware data
+     */
+    @Override
+    protected void determineFirmwareInfo(byte[] wholeFirmwareBytes) {
+        firmwareInfo = AbstractMiFirmwareInfo.determineFirmwareInfoFor(wholeFirmwareBytes);
+    }
+
+    @Override
+    public void checkValid() throws IllegalArgumentException {
+        firmwareInfo.checkValid();
+    }
+
+    @Override
+    public HuamiFirmwareType getFirmwareType() {
+        return null;
+    }
+
+    /**
+     * @param wholeFirmwareBytes
+     * @return
+     * @throws IllegalArgumentException when the data is not recognized as firmware data
+     */
+    @NonNull
+    public static AbstractMiFirmwareInfo determineFirmwareInfoFor(byte[] wholeFirmwareBytes) {
+        return AbstractMiFirmwareInfo.determineFirmwareInfoFor(wholeFirmwareBytes);
+    }
+
+    /**
+     * The backing firmware info instance, which in general supports the provided
+     * given firmware. You MUST call AbstractMiFirmwareInfo#checkValid() AND
+     * isGenerallyCompatibleWithDevice() before attempting to flash it.
+     */
+    @NonNull
+    public AbstractMiFirmwareInfo getFirmwareInfo() {
+        return firmwareInfo;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandFWInstallHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandFWInstallHandler.java
new file mode 100644
index 0000000..c2196cc
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandFWInstallHandler.java
@@ -0,0 +1,46 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, Carsten Pfeiffer
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.miband;
+
+import android.content.Context;
+import android.net.Uri;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+
+public class MiBandFWInstallHandler extends AbstractMiBandFWInstallHandler {
+    private static final Logger LOG = LoggerFactory.getLogger(MiBandFWInstallHandler.class);
+
+    public MiBandFWInstallHandler(Uri uri, Context context) {
+        super(uri, context);
+    }
+
+    @Override
+    protected AbstractMiBandFWHelper createHelper(Uri uri, Context context) throws IOException {
+        return new MiBandFWHelper(uri, context);
+    }
+
+    @Override
+    protected boolean isSupportedDeviceType(GBDevice device) {
+        return device.getType() == DeviceType.MIBAND;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandPairingActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandPairingActivity.java
new file mode 100644
index 0000000..9c34e62
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandPairingActivity.java
@@ -0,0 +1,266 @@
+/*  Copyright (C) 2015-2024 Andreas B枚hler, Andreas Shimokawa, Carsten
+    Pfeiffer, Daniel Dakhno, Daniele Gobbetti, Jos茅 Rebelo, Petr Van臎k,
+    Taavi Eom盲e
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.miband;
+
+import static nodomain.freeyourgadget.gadgetbridge.util.BondingUtil.STATE_DEVICE_CANDIDATE;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.core.content.ContextCompat;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import com.google.android.material.dialog.MaterialAlertDialogBuilder;
+
+import org.apache.commons.lang3.RandomStringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.AboutUserPreferencesActivity;
+import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBActivity;
+import nodomain.freeyourgadget.gadgetbridge.activities.ControlCenterv2;
+import nodomain.freeyourgadget.gadgetbridge.activities.discovery.DiscoveryActivityV2;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.util.AndroidUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.BondingInterface;
+import nodomain.freeyourgadget.gadgetbridge.util.BondingUtil;
+import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class MiBandPairingActivity extends AbstractGBActivity implements BondingInterface {
+    private static final Logger LOG = LoggerFactory.getLogger(MiBandPairingActivity.class);
+
+    private static final int REQ_CODE_USER_SETTINGS = 52;
+
+    private final BroadcastReceiver pairingReceiver = BondingUtil.getPairingReceiver(this);
+    private final BroadcastReceiver bondingReceiver = BondingUtil.getBondingReceiver(this);
+    private TextView message;
+    private boolean isPairing;
+    private GBDeviceCandidate deviceCandidate;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_mi_band_pairing);
+
+        message = findViewById(R.id.miband_pair_message);
+        this.deviceCandidate = getIntent().getParcelableExtra(DeviceCoordinator.EXTRA_DEVICE_CANDIDATE);
+        if (deviceCandidate == null && savedInstanceState != null) {
+            this.deviceCandidate = savedInstanceState.getParcelable(STATE_DEVICE_CANDIDATE);
+        }
+
+        if (deviceCandidate == null) {
+            Toast.makeText(this, getString(R.string.message_cannot_pair_no_mac), Toast.LENGTH_SHORT).show();
+            startActivity(new Intent(this, DiscoveryActivityV2.class).setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP));
+
+            finish();
+            return;
+        }
+
+        DeviceCoordinator coordinator = DeviceHelper.getInstance().resolveCoordinator(deviceCandidate);
+        GBDevice device = DeviceHelper.getInstance().toSupportedDevice(deviceCandidate);
+
+        if (coordinator.getSupportedDeviceSpecificAuthenticationSettings() != null) { // FIXME: this will no longer be sane in the future
+            SharedPreferences sharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(device.getAddress());
+            String authKey = sharedPrefs.getString("authkey", null);
+            if (authKey == null || authKey.isEmpty()) {
+                SharedPreferences.Editor editor = sharedPrefs.edit();
+
+                String randomAuthkey = RandomStringUtils.random(16, true, true);
+                editor.putString("authkey", randomAuthkey);
+                editor.apply();
+            }
+        }
+
+        if (!MiBandCoordinator.hasValidUserInfo()) {
+            Intent userSettingsIntent = new Intent(this, AboutUserPreferencesActivity.class);
+            startActivityForResult(userSettingsIntent, REQ_CODE_USER_SETTINGS, null);
+            return;
+        }
+
+        // already valid user info available, use that and pair
+        startPairing();
+    }
+
+    @Override
+    protected void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        outState.putParcelable(STATE_DEVICE_CANDIDATE, deviceCandidate);
+    }
+
+    @Override
+    protected void onRestoreInstanceState(Bundle savedInstanceState) {
+        super.onRestoreInstanceState(savedInstanceState);
+        deviceCandidate = savedInstanceState.getParcelable(STATE_DEVICE_CANDIDATE);
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        super.onActivityResult(requestCode, resultCode, data);
+        // start pairing immediately when we return from the user settings
+        if (requestCode == REQ_CODE_USER_SETTINGS) {
+            if (!MiBandCoordinator.hasValidUserInfo()) {
+                GB.toast(this, getString(R.string.miband_pairing_using_dummy_userdata), Toast.LENGTH_LONG, GB.WARN);
+            }
+            startPairing();
+        }
+
+        BondingUtil.handleActivityResult(this, requestCode, resultCode, data);
+    }
+
+
+    private void startPairing() {
+        isPairing = true;
+        message.setText(getString(R.string.pairing, deviceCandidate));
+
+        final BondingInterface thiz = this;
+        new MaterialAlertDialogBuilder(getContext())
+                .setCancelable(true)
+                .setTitle(getContext().getString(R.string.discovery_pair_title, deviceCandidate.getName()))
+                .setMessage(getContext().getString(R.string.discovery_pair_question))
+                .setNegativeButton(R.string.discovery_dont_pair, (dialog, which) -> {
+                    BondingUtil.attemptToFirstConnect(getCurrentTarget().getDevice());
+                })
+                .setPositiveButton(getContext().getString(R.string.discovery_yes_pair), (dialog, which) -> {
+                    BondingUtil.tryBondThenComplete(thiz, deviceCandidate.getDevice(), deviceCandidate.getMacAddress());
+                })
+                .show();
+    }
+
+
+    private void stopPairing() {
+        isPairing = false;
+        BondingUtil.stopBluetoothBonding(deviceCandidate.getDevice());
+    }
+
+    @Override
+    public void onBondingComplete(boolean success) {
+        LOG.debug("pairingFinished: " + success);
+        if (!isPairing) {
+            // already gone?
+            return;
+        } else {
+            isPairing = false;
+        }
+
+        if (success) {
+            // remember the device since we do not necessarily pair... temporary -- we probably need
+            // to query the db for available devices in ControlCenter. But only remember un-bonded
+            // devices, as bonded devices are displayed anyway.
+            String macAddress = deviceCandidate.getMacAddress();
+            BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(macAddress);
+            if (device != null && device.getBondState() == BluetoothDevice.BOND_NONE) {
+                // Persist the device directly to the database
+                try (DBHandler db = GBApplication.acquireDB()) {
+                    final DaoSession daoSession = db.getDaoSession();
+                    final GBDevice gbDevice = DeviceHelper.getInstance().toSupportedDevice(deviceCandidate);
+                    DBHelper.getDevice(gbDevice, daoSession);
+                    gbDevice.sendDeviceUpdateIntent(this);
+                } catch (final Exception e) {
+                    GB.log("Error accessing database", GB.ERROR, e);
+                }
+            }
+            Intent intent = new Intent(this, ControlCenterv2.class).setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+            startActivity(intent);
+        }
+        finish();
+    }
+
+    @Override
+    public GBDeviceCandidate getCurrentTarget() {
+        return this.deviceCandidate;
+    }
+
+    @Override
+    protected void onResume() {
+        registerBroadcastReceivers();
+        super.onResume();
+    }
+
+    @Override
+    public String getMacAddress() {
+        return deviceCandidate.getDevice().getAddress();
+    }
+
+    @Override
+    public boolean getAttemptToConnect() {
+        return true;
+    }
+
+    @Override
+    protected void onStart() {
+        registerBroadcastReceivers();
+        super.onStart();
+    }
+
+    @Override
+    protected void onDestroy() {
+        unregisterBroadcastReceivers();
+        if (isPairing) {
+            stopPairing();
+        }
+        super.onDestroy();
+    }
+
+    @Override
+    protected void onStop() {
+        unregisterBroadcastReceivers();
+        if (isPairing) {
+            stopPairing();
+        }
+        super.onStop();
+    }
+
+    @Override
+    protected void onPause() {
+        // WARN: Do not stop pairing or unregister receivers pause!
+        // Bonding process can pause the activity and you might miss broadcasts
+        super.onPause();
+    }
+
+    public void unregisterBroadcastReceivers() {
+        AndroidUtils.safeUnregisterBroadcastReceiver(LocalBroadcastManager.getInstance(this), pairingReceiver);
+        AndroidUtils.safeUnregisterBroadcastReceiver(this, bondingReceiver);
+    }
+
+    public void registerBroadcastReceivers() {
+        LocalBroadcastManager.getInstance(this).registerReceiver(pairingReceiver, new IntentFilter(GBDevice.ACTION_DEVICE_CHANGED));
+        ContextCompat.registerReceiver(this, bondingReceiver, new IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED), ContextCompat.RECEIVER_EXPORTED);
+    }
+
+    public Context getContext() {
+        return this;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandSampleProvider.java
new file mode 100644
index 0000000..0f74749
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandSampleProvider.java
@@ -0,0 +1,79 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, Carsten Pfeiffer, Daniele
+    Gobbetti
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.miband;
+
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+
+public class MiBandSampleProvider extends AbstractMiBandSampleProvider {
+    public static final int TYPE_DEEP_SLEEP = 4;
+    public static final int TYPE_LIGHT_SLEEP = 5;
+    public static final int TYPE_ACTIVITY = -1;
+    public static final int TYPE_UNKNOWN = -1;
+    public static final int TYPE_NONWEAR = 3;
+    public static final int TYPE_CHARGING = 6;
+
+//    public static final byte TYPE_NREM = 5; // DEEP SLEEP
+//    public static final byte TYPE_ONBED = 7;
+//    public static final byte TYPE_REM = 4; // LIGHT SLEEP
+//    public static final byte TYPE_RUNNING = 2;
+//    public static final byte TYPE_SLIENT = 0;
+//    public static final byte TYPE_USER = 100;
+//    public static final byte TYPE_WALKING = 1;
+
+    public MiBandSampleProvider(GBDevice device, DaoSession session) {
+        super(device, session);
+    }
+
+    @Override
+    public ActivityKind normalizeType(int rawType) {
+        switch (rawType) {
+            case TYPE_DEEP_SLEEP:
+                return ActivityKind.DEEP_SLEEP;
+            case TYPE_LIGHT_SLEEP:
+                return ActivityKind.LIGHT_SLEEP;
+            case TYPE_ACTIVITY:
+                return ActivityKind.ACTIVITY;
+            case TYPE_NONWEAR:
+                return ActivityKind.NOT_WORN;
+            case TYPE_CHARGING:
+                return ActivityKind.NOT_WORN; //I believe it's a safe assumption
+            default:
+//            case TYPE_UNKNOWN: // fall through
+                return ActivityKind.UNKNOWN;
+        }
+    }
+
+    @Override
+    public int toRawActivityKind(ActivityKind activityKind) {
+        switch (activityKind) {
+            case ACTIVITY:
+                return TYPE_ACTIVITY;
+            case DEEP_SLEEP:
+                return TYPE_DEEP_SLEEP;
+            case LIGHT_SLEEP:
+                return TYPE_LIGHT_SLEEP;
+            case NOT_WORN:
+                return TYPE_NONWEAR;
+            case UNKNOWN: // fall through
+            default:
+                return TYPE_UNKNOWN;
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandService.java
new file mode 100644
index 0000000..6f0f6a5
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandService.java
@@ -0,0 +1,275 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, Carsten Pfeiffer, Daniele
+    Gobbetti, Kasha
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.miband;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.service.btle.GattCharacteristic;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.GattService;
+
+import static nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport.BASE_UUID;
+
+public class MiBandService {
+
+
+    public static final String MAC_ADDRESS_FILTER_1_1A = "88:0F:10";
+    public static final String MAC_ADDRESS_FILTER_1S = "C8:0F:10";
+
+    public static final UUID UUID_SERVICE_MIBAND_SERVICE = UUID.fromString(String.format(BASE_UUID, "FEE0"));
+    public static final UUID UUID_SERVICE_MIBAND2_SERVICE = UUID.fromString(String.format(BASE_UUID, "FEE1"));
+    public static final UUID UUID_SERVICE_HEART_RATE = GattService.UUID_SERVICE_HEART_RATE;
+    public static final String UUID_SERVICE_WEIGHT_SERVICE = "00001530-0000-3512-2118-0009af100700";
+
+    public static final UUID UUID_CHARACTERISTIC_DEVICE_INFO = UUID.fromString(String.format(BASE_UUID, "FF01"));
+
+    public static final UUID UUID_CHARACTERISTIC_DEVICE_NAME = UUID.fromString(String.format(BASE_UUID, "FF02"));
+
+    public static final UUID UUID_CHARACTERISTIC_NOTIFICATION = UUID.fromString(String.format(BASE_UUID, "FF03"));
+
+    public static final UUID UUID_CHARACTERISTIC_USER_INFO = UUID.fromString(String.format(BASE_UUID, "FF04"));
+
+    public static final UUID UUID_CHARACTERISTIC_CONTROL_POINT = UUID.fromString(String.format(BASE_UUID, "FF05"));
+
+    public static final UUID UUID_CHARACTERISTIC_REALTIME_STEPS = UUID.fromString(String.format(BASE_UUID, "FF06"));
+
+    public static final UUID UUID_CHARACTERISTIC_ACTIVITY_DATA = UUID.fromString(String.format(BASE_UUID, "FF07"));
+
+    public static final UUID UUID_CHARACTERISTIC_FIRMWARE_DATA = UUID.fromString(String.format(BASE_UUID, "FF08"));
+
+    public static final UUID UUID_CHARACTERISTIC_LE_PARAMS = UUID.fromString(String.format(BASE_UUID, "FF09"));
+
+    public static final UUID UUID_CHARACTERISTIC_DATE_TIME = UUID.fromString(String.format(BASE_UUID, "FF0A"));
+
+    public static final UUID UUID_CHARACTERISTIC_STATISTICS = UUID.fromString(String.format(BASE_UUID, "FF0B"));
+
+    public static final UUID UUID_CHARACTERISTIC_BATTERY = UUID.fromString(String.format(BASE_UUID, "FF0C"));
+
+    public static final UUID UUID_CHARACTERISTIC_TEST = UUID.fromString(String.format(BASE_UUID, "FF0D"));
+
+    public static final UUID UUID_CHARACTERISTIC_SENSOR_DATA = UUID.fromString(String.format(BASE_UUID, "FF0E"));
+
+    public static final UUID UUID_CHARACTERISTIC_PAIR = UUID.fromString(String.format(BASE_UUID, "FF0F"));
+
+    public static final UUID UUID_CHARACTERISTIC_HEART_RATE_CONTROL_POINT = GattCharacteristic.UUID_CHARACTERISTIC_HEART_RATE_CONTROL_POINT;
+    public static final UUID UUID_CHARACTERISTIC_HEART_RATE_MEASUREMENT = GattCharacteristic.UUID_CHARACTERISTIC_HEART_RATE_MEASUREMENT;
+
+
+
+    /* FURTHER UUIDS that were mixed with the other params below. The base UUID for these is unknown */
+
+    public static final byte ALIAS_LEN = 0xa;
+
+    /*NOTIFICATIONS: usually received on the UUID_CHARACTERISTIC_NOTIFICATION characteristic */
+
+    public static final byte NOTIFY_NORMAL = 0x0;
+
+    public static final byte NOTIFY_FIRMWARE_UPDATE_FAILED = 0x1;
+
+    public static final byte NOTIFY_FIRMWARE_UPDATE_SUCCESS = 0x2;
+
+    public static final byte NOTIFY_CONN_PARAM_UPDATE_FAILED = 0x3;
+
+    public static final byte NOTIFY_CONN_PARAM_UPDATE_SUCCESS = 0x4;
+
+    public static final byte NOTIFY_AUTHENTICATION_SUCCESS = 0x5;
+
+    public static final byte NOTIFY_AUTHENTICATION_FAILED = 0x6;
+
+    public static final byte NOTIFY_FITNESS_GOAL_ACHIEVED = 0x7;
+
+    public static final byte NOTIFY_SET_LATENCY_SUCCESS = 0x8;
+
+    public static final byte NOTIFY_RESET_AUTHENTICATION_FAILED = 0x9;
+
+    public static final byte NOTIFY_RESET_AUTHENTICATION_SUCCESS = 0xa;
+
+    public static final byte NOTIFY_FW_CHECK_FAILED = 0xb;
+
+    public static final byte NOTIFY_FW_CHECK_SUCCESS = 0xc;
+
+    public static final byte NOTIFY_STATUS_MOTOR_NOTIFY = 0xd;
+
+    public static final byte NOTIFY_STATUS_MOTOR_CALL = 0xe;
+
+    public static final byte NOTIFY_STATUS_MOTOR_DISCONNECT = 0xf;
+
+    public static final byte NOTIFY_STATUS_MOTOR_SMART_ALARM = 0x10;
+
+    public static final byte NOTIFY_STATUS_MOTOR_ALARM = 0x11;
+
+    public static final byte NOTIFY_STATUS_MOTOR_GOAL = 0x12;
+
+    public static final byte NOTIFY_STATUS_MOTOR_AUTH = 0x13;
+
+    public static final byte NOTIFY_STATUS_MOTOR_SHUTDOWN = 0x14;
+
+    public static final byte NOTIFY_STATUS_MOTOR_AUTH_SUCCESS = 0x15;
+
+    public static final byte NOTIFY_STATUS_MOTOR_TEST = 0x16;
+
+    // 0x18 is returned when we cancel data sync, perhaps is an ack for this message
+
+    public static final byte NOTIFY_UNKNOWN = -0x1;
+
+    public static final int NOTIFY_PAIR_CANCEL = 0xef;
+
+    public static final int NOTIFY_DEVICE_MALFUNCTION = 0xff;
+
+
+    /* MESSAGES: unknown */
+
+    public static final byte MSG_CONNECTED = 0x0;
+
+    public static final byte MSG_DISCONNECTED = 0x1;
+
+    public static final byte MSG_CONNECTION_FAILED = 0x2;
+
+    public static final byte MSG_INITIALIZATION_FAILED = 0x3;
+
+    public static final byte MSG_INITIALIZATION_SUCCESS = 0x4;
+
+    public static final byte MSG_STEPS_CHANGED = 0x5;
+
+    public static final byte MSG_DEVICE_STATUS_CHANGED = 0x6;
+
+    public static final byte MSG_BATTERY_STATUS_CHANGED = 0x7;
+
+    /* COMMANDS: usually sent to UUID_CHARACTERISTIC_CONTROL_POINT characteristic */
+
+    public static final byte COMMAND_SET_TIMER = 0x4;
+
+    public static final byte COMMAND_SET_FITNESS_GOAL = 0x5;
+
+    public static final byte COMMAND_FETCH_DATA = 0x6;
+
+    public static final byte COMMAND_SEND_FIRMWARE_INFO = 0x7;
+
+    public static final byte COMMAND_SEND_NOTIFICATION = 0x8;
+
+    public static final byte COMMAND_FACTORYRESET = 0x9;
+
+    public static final byte COMMAND_CONFIRM_ACTIVITY_DATA_TRANSFER_COMPLETE = 0xa;
+
+    public static final byte COMMAND_SYNC = 0xb;
+
+    public static final byte COMMAND_REBOOT = 0xc;
+
+    public static final byte COMMAND_SET_WEAR_LOCATION = 0xf;
+
+    public static final byte COMMAND_STOP_SYNC_DATA = 0x11;
+
+    public static final byte COMMAND_STOP_MOTOR_VIBRATE = 0x13;
+
+    public static final byte COMMAND_SET_REALTIME_STEPS_NOTIFICATION = 0x3;
+
+    public static final byte COMMAND_SET_REALTIME_STEP = 0x10;
+
+    // Test HR
+    public static final byte COMMAND_SET_HR_SLEEP = 0x0;
+    public static final byte COMMAND_SET__HR_CONTINUOUS = 0x1;
+    public static final byte COMMAND_SET_HR_MANUAL = 0x2;
+
+    public static final byte COMMAND_GET_SENSOR_DATA = 0x12;
+
+    /* FURTHER COMMANDS: unchecked therefore left commented
+
+
+	public static final byte COMMAND_FACTORY_RESET = 0x9t;
+
+	public static final int COMMAND_SET_COLOR_THEME = et;
+
+	*/
+
+    /* CONNECTION: unknown
+
+   	public static final CONNECTION_LATENCY_LEVEL_LOW = 0x0t;
+
+	public static final CONNECTION_LATENCY_LEVEL_MEDIUM = 0x1t;
+
+	public static final CONNECTION_LATENCY_LEVEL_HIGH = 0x2t;
+
+    */
+
+    /* MODES: probably related to the sample data structure
+    */
+
+    public static final byte MODE_REGULAR_DATA_LEN_BYTE = 0x0;
+
+    // was MODE_REGULAR_DATA_LEN_MINITE
+    public static final byte MODE_REGULAR_DATA_LEN_MINUTE = 0x1;
+
+    /* PROFILE: unknown
+
+	public static final PROFILE_STATE_UNKNOWN:I = 0x0
+
+	public static final PROFILE_STATE_INITIALIZATION_SUCCESS:I = 0x1
+
+	public static final PROFILE_STATE_INITIALIZATION_FAILED:I = 0x2
+
+	public static final PROFILE_STATE_AUTHENTICATION_SUCCESS:I = 0x3
+
+	public static final PROFILE_STATE_AUTHENTICATION_FAILED:I = 0x4
+
+	*/
+
+    // TEST_*: sent to UUID_CHARACTERISTIC_TEST characteristic
+
+	public static final byte TEST_DISCONNECTED_REMINDER = 0x5;
+
+	public static final byte TEST_NOTIFICATION = 0x3;
+
+	public static final byte TEST_REMOTE_DISCONNECT = 0x1;
+
+	public static final byte TEST_SELFTEST = 0x2;
+
+    private static final Map<UUID, String> MIBAND_DEBUG;
+
+    static {
+        MIBAND_DEBUG = new HashMap<>();
+        MIBAND_DEBUG.put(UUID_SERVICE_MIBAND_SERVICE, "MiBand Service");
+        MIBAND_DEBUG.put(UUID_SERVICE_HEART_RATE, "MiBand HR Service");
+
+        MIBAND_DEBUG.put(UUID_CHARACTERISTIC_DEVICE_INFO, "Device Info");
+        MIBAND_DEBUG.put(UUID_CHARACTERISTIC_DEVICE_NAME, "Device Name");
+        MIBAND_DEBUG.put(UUID_CHARACTERISTIC_NOTIFICATION, "Notification");
+        MIBAND_DEBUG.put(UUID_CHARACTERISTIC_USER_INFO, "User Info");
+        MIBAND_DEBUG.put(UUID_CHARACTERISTIC_CONTROL_POINT, "Control Point");
+        MIBAND_DEBUG.put(UUID_CHARACTERISTIC_REALTIME_STEPS, "Realtime Steps");
+        MIBAND_DEBUG.put(UUID_CHARACTERISTIC_ACTIVITY_DATA, "Activity Data");
+        MIBAND_DEBUG.put(UUID_CHARACTERISTIC_FIRMWARE_DATA, "Firmware Data");
+        MIBAND_DEBUG.put(UUID_CHARACTERISTIC_LE_PARAMS, "LE Params");
+        MIBAND_DEBUG.put(UUID_CHARACTERISTIC_DATE_TIME, "Date/Time");
+        MIBAND_DEBUG.put(UUID_CHARACTERISTIC_STATISTICS, "Statistics");
+        MIBAND_DEBUG.put(UUID_CHARACTERISTIC_BATTERY, "Battery");
+        MIBAND_DEBUG.put(UUID_CHARACTERISTIC_TEST, "Test");
+        MIBAND_DEBUG.put(UUID_CHARACTERISTIC_SENSOR_DATA, "Sensor Data");
+        MIBAND_DEBUG.put(UUID_CHARACTERISTIC_PAIR, "Pair");
+        MIBAND_DEBUG.put(UUID_CHARACTERISTIC_HEART_RATE_CONTROL_POINT, "Heart Rate Control Point");
+        MIBAND_DEBUG.put(UUID_CHARACTERISTIC_HEART_RATE_MEASUREMENT, "Heart Rate Measure");
+    }
+
+    public static String lookup(UUID uuid, String fallback) {
+        String name = MIBAND_DEBUG.get(uuid);
+        if (name == null) {
+            name = fallback;
+        }
+        return name;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandSettingsCustomizer.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandSettingsCustomizer.java
new file mode 100644
index 0000000..213b948
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandSettingsCustomizer.java
@@ -0,0 +1,96 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.miband;
+
+import android.os.Parcel;
+import android.text.InputType;
+
+import androidx.preference.Preference;
+
+import java.util.Collections;
+import java.util.Set;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsHandler;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.NotificationType;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+public class MiBandSettingsCustomizer implements DeviceSpecificSettingsCustomizer {
+    private final GBDevice device;
+
+    public MiBandSettingsCustomizer(final GBDevice device) {
+        this.device = device;
+    }
+
+    @Override
+    public void onPreferenceChange(final Preference preference, final DeviceSpecificSettingsHandler handler) {
+
+    }
+
+    @Override
+    public void customizeSettings(final DeviceSpecificSettingsHandler handler, final Prefs prefs, final String rootKey) {
+        for (final NotificationType type : NotificationType.values()) {
+            String countPrefKey = "mi_vibration_count_" + type.getGenericType();
+            handler.setInputTypeFor(countPrefKey, InputType.TYPE_CLASS_NUMBER);
+            String tryPrefKey = "mi_try_" + type.getGenericType();
+            final Preference tryPref = handler.findPreference(tryPrefKey);
+            if (tryPref != null) {
+                tryPref.setOnPreferenceClickListener(preference -> {
+                    tryVibration(type);
+                    return true;
+                });
+            }
+        }
+    }
+
+    private void tryVibration(NotificationType type) {
+        NotificationSpec spec = new NotificationSpec();
+        spec.type = type;
+        GBApplication.deviceService(device).onNotification(spec);
+    }
+
+    @Override
+    public Set<String> getPreferenceKeysWithSummary() {
+        return Collections.emptySet();
+    }
+
+    public static final Creator<MiBandSettingsCustomizer> CREATOR = new Creator<MiBandSettingsCustomizer>() {
+        @Override
+        public MiBandSettingsCustomizer createFromParcel(final Parcel in) {
+            final GBDevice device = in.readParcelable(MiBandSettingsCustomizer.class.getClassLoader());
+            return new MiBandSettingsCustomizer(device);
+        }
+
+        @Override
+        public MiBandSettingsCustomizer[] newArray(final int size) {
+            return new MiBandSettingsCustomizer[size];
+        }
+    };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(final Parcel dest, final int flags) {
+        dest.writeParcelable(device, 0);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/UserInfo.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/UserInfo.java
new file mode 100644
index 0000000..e6d8cc8
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/UserInfo.java
@@ -0,0 +1,119 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, Carsten Pfeiffer, Daniele
+    Gobbetti, Sergey Trofimov
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.miband;
+
+import java.util.Arrays;
+
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.DeviceInfo;
+import nodomain.freeyourgadget.gadgetbridge.util.CheckSums;
+
+public class UserInfo {
+
+    private final String btAddress;
+    private final String alias;
+    private final int gender;
+    private final int age;
+    private final int height;
+    private final int weight;
+    private final int type;
+
+    private byte[] data = new byte[20];
+
+    /**
+     * Creates a default user info.
+     *
+     * @param btAddress the address of the MI Band to connect to.
+     */
+    public static UserInfo getDefault(String btAddress) {
+        return new UserInfo(btAddress, "1550050550", ActivityUser.defaultUserGender, ActivityUser.defaultUserAge, ActivityUser.defaultUserHeightCm, ActivityUser.defaultUserWeightKg, 0);
+    }
+
+    /**
+     * Creates a user info with the given data
+     *
+     * @param address the address of the MI Band to connect to.
+     * @throws IllegalArgumentException when the given values are not valid
+     */
+    public static UserInfo create(String address, String alias, int gender, int age, int height, int weight, int type) throws IllegalArgumentException {
+        if (address == null || address.length() == 0 || alias == null || alias.length() == 0 || gender < 0 || age <= 0 || weight <= 0 || type < 0) {
+            throw new IllegalArgumentException("Invalid parameters");
+        }
+        try {
+            return new UserInfo(address, alias, gender, age, height, weight, type);
+        } catch (Exception ex) {
+            throw new IllegalArgumentException("Illegal user info data", ex);
+        }
+    }
+
+    /**
+     * Creates a user info with the given data
+     *
+     * @param address the address of the MI Band to connect to.
+     */
+    private UserInfo(String address, String alias, int gender, int age, int height, int weight, int type) {
+        this.btAddress = address;
+        this.alias = alias;
+        this.gender = gender;
+        this.age = age;
+        this.height = height;
+        this.weight = weight;
+        this.type = type;
+    }
+
+    private int calculateUidFrom(String alias) {
+        int uid;
+        try {
+            uid = Integer.parseInt(alias);
+        } catch (NumberFormatException ex) {
+            uid = alias.hashCode(); // simple as that
+        }
+        return uid;
+    }
+
+    public byte[] getData(DeviceInfo mDeviceInfo) {
+        byte[] sequence = new byte[20];
+        int uid = calculateUidFrom(alias);
+
+        sequence[0] = (byte) uid;
+        sequence[1] = (byte) (uid >>> 8);
+        sequence[2] = (byte) (uid >>> 16);
+        sequence[3] = (byte) (uid >>> 24);
+
+        sequence[4] = (byte) (gender & 0xff);
+        sequence[5] = (byte) (age & 0xff);
+        sequence[6] = (byte) (height & 0xff);
+        sequence[7] = (byte) (weight & 0xff);
+        sequence[8] = (byte) (type & 0xff);
+
+        int aliasFrom = 9;
+        if (!mDeviceInfo.isMili1()) {
+            sequence[9] = (byte) (mDeviceInfo.feature & 255);
+            sequence[10] = (byte) (mDeviceInfo.appearance & 255);
+            aliasFrom = 11;
+        }
+
+        byte[] aliasBytes = alias.substring(0, Math.min(alias.length(), 19 - aliasFrom)).getBytes();
+        System.arraycopy(aliasBytes, 0, sequence, aliasFrom, aliasBytes.length);
+
+        byte[] crcSequence = Arrays.copyOf(sequence, 19);
+        sequence[19] = (byte) ((CheckSums.getCRC8(crcSequence) ^ Integer.parseInt(this.btAddress.substring(this.btAddress.length() - 2), 16)) & 0xff);
+
+        return sequence;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/VibrationProfile.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/VibrationProfile.java
new file mode 100644
index 0000000..26428cb
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/VibrationProfile.java
@@ -0,0 +1,110 @@
+/*  Copyright (C) 2015-2024 Carsten Pfeiffer, Jos茅 Rebelo, Uwe Hermann
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.miband;
+
+import android.content.Context;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotification.AlertLevel;
+
+public class VibrationProfile {
+    public static final String ID_STACCATO;
+    public static final String ID_SHORT;
+    public static final String ID_MEDIUM;
+    public static final String ID_LONG;
+    public static final String ID_WATERDROP;
+    public static final String ID_RING;
+    public static final String ID_ALARM_CLOCK;
+
+    static {
+        Context CONTEXT = GBApplication.getContext();
+        ID_STACCATO = CONTEXT.getString(R.string.p_staccato);
+        ID_SHORT = CONTEXT.getString(R.string.p_short);
+        ID_MEDIUM = CONTEXT.getString(R.string.p_medium);
+        ID_LONG = CONTEXT.getString(R.string.p_long);
+        ID_WATERDROP = CONTEXT.getString(R.string.p_waterdrop);
+        ID_RING = CONTEXT.getString(R.string.p_ring);
+        ID_ALARM_CLOCK = CONTEXT.getString(R.string.p_alarm_clock);
+    }
+
+    public static VibrationProfile getProfile(String id, short repeat) {
+        if (ID_STACCATO.equals(id)) {
+            return new VibrationProfile(id, new int[]{100, 0}, repeat);
+        }
+        if (ID_SHORT.equals(id)) {
+            return new VibrationProfile(id, new int[]{200, 200}, repeat);
+        }
+        if (ID_LONG.equals(id)) {
+            return new VibrationProfile(id, new int[]{500, 1000}, repeat);
+        }
+        if (ID_WATERDROP.equals(id)) {
+            return new VibrationProfile(id, new int[]{100, 1500}, repeat);
+        }
+        if (ID_RING.equals(id)) {
+            return new VibrationProfile(id, new int[]{300, 200, 600, 2000}, repeat);
+        }
+        if (ID_ALARM_CLOCK.equals(id)) {
+            return new VibrationProfile(id, new int[]{30, 35, 30, 35, 30, 35, 30, 800}, repeat);
+        }
+        // medium
+        return new VibrationProfile(id, new int[]{300, 600}, repeat);
+    }
+
+    private final String id;
+
+    private final int[] onOffSequence;
+    private final short repeat;
+    private int alertLevel = AlertLevel.MildAlert.getId();
+
+    /**
+     * Creates a new profile instance.
+     *
+     * @param id            the ID, used as preference key.
+     * @param onOffSequence a sequence of alternating on and off durations, in milliseconds
+     * @param repeat        how often the sequence shall be repeated
+     */
+    public VibrationProfile(String id, int[] onOffSequence, short repeat) {
+        if (onOffSequence.length % 2 != 0) {
+            throw new IllegalArgumentException("Each on duration must have a subsequent off duration");
+        }
+
+        this.id = id;
+        this.repeat = repeat;
+        this.onOffSequence = onOffSequence;
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    public int[] getOnOffSequence() {
+        return onOffSequence;
+    }
+
+    public short getRepeat() {
+        return repeat;
+    }
+
+    public void setAlertLevel(int alertLevel) {
+        this.alertLevel = alertLevel;
+    }
+
+    public int getAlertLevel() {
+        return alertLevel;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/miligatt.txt b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/miligatt.txt
new file mode 100644
index 0000000..8398d52
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/miligatt.txt
@@ -0,0 +1,34 @@
+00001800-0000-1000-8000-00805f9b34fb:Generic Access Service
+- 00002a00-0000-1000-8000-00805f9b34fb:Device Name
+- 00002a01-0000-1000-8000-00805f9b34fb:Appearance
+- 00002a02-0000-1000-8000-00805f9b34fb:Peripheral Privacy Flag
+- 00002a04-0000-1000-8000-00805f9b34fb:Peripheral Preferred Connection Parameters
+
+00001801-0000-1000-8000-00805f9b34fb:Generic Attribute Service
+- 00002a05-0000-1000-8000-00805f9b34fb:Service Changed
+
+0000fee0-0000-1000-8000-00805f9b34fb:MiBand Service
+- 0000ff01-0000-1000-8000-00805f9b34fb:Device Info
+- 0000ff02-0000-1000-8000-00805f9b34fb:Device Name
+- 0000ff03-0000-1000-8000-00805f9b34fb:Notification
+- 0000ff04-0000-1000-8000-00805f9b34fb:User Info
+- 0000ff05-0000-1000-8000-00805f9b34fb:Control Point
+- 0000ff06-0000-1000-8000-00805f9b34fb:Realtime Steps
+- 0000ff07-0000-1000-8000-00805f9b34fb:Activity Data
+- 0000ff08-0000-1000-8000-00805f9b34fb:Firmware Data
+- 0000ff09-0000-1000-8000-00805f9b34fb:LE Params
+- 0000ff0a-0000-1000-8000-00805f9b34fb:Date/Time
+- 0000ff0b-0000-1000-8000-00805f9b34fb:Statistics
+- 0000ff0c-0000-1000-8000-00805f9b34fb:Battery
+- 0000ff0d-0000-1000-8000-00805f9b34fb:Test
+- 0000ff0e-0000-1000-8000-00805f9b34fb:Sensor Data
+
+0000fee1-0000-1000-8000-00805f9b34fb:Unknown service
+- 0000fedd-0000-1000-8000-00805f9b34fb:Unknown characteristic
+- 0000fede-0000-1000-8000-00805f9b34fb:Unknown characteristic
+- 0000fedf-0000-1000-8000-00805f9b34fb:Unknown characteristic
+
+0000fee7-0000-1000-8000-00805f9b34fb:Unknown service
+- 0000fec7-0000-1000-8000-00805f9b34fb:Unknown characteristic
+- 0000fec8-0000-1000-8000-00805f9b34fb:Unknown characteristic
+- 0000fec9-0000-1000-8000-00805f9b34fb:Unknown characteristic
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/mijia_lywsd/AbstractMijiaLywsdCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/mijia_lywsd/AbstractMijiaLywsdCoordinator.java
new file mode 100644
index 0000000..0cad253
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/mijia_lywsd/AbstractMijiaLywsdCoordinator.java
@@ -0,0 +1,80 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.mijia_lywsd;
+
+import android.content.Context;
+import android.net.Uri;
+
+import androidx.annotation.NonNull;
+
+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.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.mijia_lywsd.MijiaLywsdSupport;
+
+public abstract class AbstractMijiaLywsdCoordinator extends AbstractBLEDeviceCoordinator {
+    @Override
+    public int getBondingStyle() {
+        return BONDING_STYLE_NONE;
+    }
+
+    @Override
+    public InstallHandler findInstallHandler(Uri uri, Context context) {
+        return null;
+    }
+
+    @Override
+    public boolean supportsActivityDataFetching() {
+        return false;
+    }
+
+    @Override
+    public String getManufacturer() {
+        return "Xiaomi";
+    }
+
+    @Override
+    public DeviceSpecificSettingsCustomizer getDeviceSpecificSettingsCustomizer(final GBDevice device) {
+        return new MijiaLywsdSettingsCustomizer();
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return MijiaLywsdSupport.class;
+    }
+
+    @Override
+    public int[] getSupportedDeviceSpecificSettings(GBDevice device) {
+        return new int[]{
+                R.xml.devicesettings_mijia_lywsd,
+                R.xml.devicesettings_temperature_scale_cf,
+        };
+    }
+
+    @Override
+    protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) {
+        // nothing to delete, yet
+    }
+
+    public abstract boolean supportsSetTime();
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/mijia_lywsd/MijiaLywsd02Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/mijia_lywsd/MijiaLywsd02Coordinator.java
new file mode 100644
index 0000000..4e5ead2
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/mijia_lywsd/MijiaLywsd02Coordinator.java
@@ -0,0 +1,48 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.mijia_lywsd;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+
+public class MijiaLywsd02Coordinator extends AbstractMijiaLywsdCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("LYWSD02|LYWSD02MMC");
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_mijia_lywsd02;
+    }
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_pebble;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_pebble_disabled;
+    }
+
+    @Override
+    public boolean supportsSetTime() {
+        return true;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/mijia_lywsd/MijiaLywsd03Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/mijia_lywsd/MijiaLywsd03Coordinator.java
new file mode 100644
index 0000000..8bcbb15
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/mijia_lywsd/MijiaLywsd03Coordinator.java
@@ -0,0 +1,48 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.mijia_lywsd;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+
+public class MijiaLywsd03Coordinator extends AbstractMijiaLywsdCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("LYWSD03MMC");
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_mijia_lywsd03;
+    }
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_thermometer;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_thermometer_disabled;
+    }
+
+    @Override
+    public boolean supportsSetTime() {
+        return false;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/mijia_lywsd/MijiaLywsdSettingsCustomizer.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/mijia_lywsd/MijiaLywsdSettingsCustomizer.java
new file mode 100644
index 0000000..ce20175
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/mijia_lywsd/MijiaLywsdSettingsCustomizer.java
@@ -0,0 +1,95 @@
+/*  Copyright (C) 2024 Severin von Wnuck-Lipinski
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.mijia_lywsd;
+
+import android.os.Parcel;
+import androidx.preference.Preference;
+import androidx.preference.SeekBarPreference;
+
+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.service.devices.mijia_lywsd.MijiaLywsdSupport;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.*;
+
+public class MijiaLywsdSettingsCustomizer implements DeviceSpecificSettingsCustomizer {
+    public static final Creator<MijiaLywsdSettingsCustomizer> CREATOR = new Creator<MijiaLywsdSettingsCustomizer>() {
+        @Override
+        public MijiaLywsdSettingsCustomizer createFromParcel(final Parcel in) {
+            return new MijiaLywsdSettingsCustomizer();
+        }
+
+        @Override
+        public MijiaLywsdSettingsCustomizer[] newArray(final int size) {
+            return new MijiaLywsdSettingsCustomizer[size];
+        }
+    };
+
+    @Override
+    public void onPreferenceChange(final Preference preference, final DeviceSpecificSettingsHandler handler) {
+        String key = preference.getKey();
+
+        if (!key.equals(PREF_MIJIA_LYWSD_COMFORT_TEMPERATURE_LOWER) && !key.equals(PREF_MIJIA_LYWSD_COMFORT_TEMPERATURE_UPPER) &&
+            !key.equals(PREF_MIJIA_LYWSD_COMFORT_HUMIDITY_LOWER) && !key.equals(PREF_MIJIA_LYWSD_COMFORT_HUMIDITY_UPPER))
+            return;
+
+        SeekBarPreference temperatureLower = handler.findPreference(PREF_MIJIA_LYWSD_COMFORT_TEMPERATURE_LOWER);
+        SeekBarPreference temperatureUpper = handler.findPreference(PREF_MIJIA_LYWSD_COMFORT_TEMPERATURE_UPPER);
+        SeekBarPreference humidityLower = handler.findPreference(PREF_MIJIA_LYWSD_COMFORT_HUMIDITY_LOWER);
+        SeekBarPreference humidityUpper = handler.findPreference(PREF_MIJIA_LYWSD_COMFORT_HUMIDITY_UPPER);
+
+        if (temperatureLower == null || temperatureUpper == null || humidityLower == null || humidityUpper == null)
+            return;
+
+        // Clamp minimum value of upper limit to lower limit
+        if (temperatureLower.getValue() > temperatureUpper.getValue())
+            temperatureUpper.setValue(temperatureLower.getValue());
+
+        if (humidityLower.getValue() > humidityUpper.getValue())
+            humidityUpper.setValue(humidityLower.getValue());
+    }
+
+    @Override
+    public void customizeSettings(final DeviceSpecificSettingsHandler handler, final Prefs prefs, final String rootKey) {
+        Preference comfortLevel = handler.findPreference(PREF_MIJIA_LYWSD_COMFORT_LEVEL);
+
+        if (comfortLevel != null) {
+            int length = prefs.getInt(PREF_MIJIA_LYWSD_COMFORT_CHARACTERISTIC_LENGTH, 0);
+
+            // Hide comfort level for unknown characteristic length
+            comfortLevel.setVisible(length == MijiaLywsdSupport.COMFORT_LEVEL_LENGTH_LYWSD03 ||
+                                    length == MijiaLywsdSupport.COMFORT_LEVEL_LENGTH_XMWSDJ04);
+        }
+    }
+
+    @Override
+    public Set<String> getPreferenceKeysWithSummary() {
+        return Collections.emptySet();
+    }
+
+    @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/mijia_lywsd/MijiaMhoC303Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/mijia_lywsd/MijiaMhoC303Coordinator.java
new file mode 100644
index 0000000..faa9966
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/mijia_lywsd/MijiaMhoC303Coordinator.java
@@ -0,0 +1,48 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.mijia_lywsd;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+
+public class MijiaMhoC303Coordinator extends AbstractMijiaLywsdCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("MHO-C303");
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_mijia_mho_c303;
+    }
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_pebble;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_pebble_disabled;
+    }
+
+    @Override
+    public boolean supportsSetTime() {
+        return true;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/mijia_lywsd/MijiaXmwsdj04Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/mijia_lywsd/MijiaXmwsdj04Coordinator.java
new file mode 100644
index 0000000..7a87e2f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/mijia_lywsd/MijiaXmwsdj04Coordinator.java
@@ -0,0 +1,48 @@
+/*  Copyright (C) 2024 Severin von Wnuck-Lipinski
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.mijia_lywsd;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+
+public class MijiaXmwsdj04Coordinator extends AbstractMijiaLywsdCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("XMWSDJ04MMC");
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_mijia_xmwsdj04;
+    }
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_thermometer;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_thermometer_disabled;
+    }
+
+    @Override
+    public boolean supportsSetTime() {
+        return false;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miscale/MiCompositionScaleCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miscale/MiCompositionScaleCoordinator.java
new file mode 100644
index 0000000..2ae348d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miscale/MiCompositionScaleCoordinator.java
@@ -0,0 +1,142 @@
+/*  Copyright (C) 2019-2024 Andreas Shimokawa, Damien Gaignon, Daniel Dakhno,
+    Jean-Fran莽ois Greffier, Jos茅 Rebelo, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.miscale;
+
+import android.bluetooth.le.ScanFilter;
+import android.os.ParcelUuid;
+
+import androidx.annotation.NonNull;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.regex.Pattern;
+
+import de.greenrobot.dao.query.QueryBuilder;
+import nodomain.freeyourgadget.gadgetbridge.GBException;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLEDeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.TimeSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.entities.MiScaleWeightSampleDao;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.WeightSample;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.GattService;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.miscale.MiCompositionScaleDeviceSupport;
+
+public class MiCompositionScaleCoordinator extends AbstractBLEDeviceCoordinator {
+    @Override
+    protected void deleteDevice(@NonNull final GBDevice gbDevice, @NonNull final Device device, @NonNull final DaoSession session) throws GBException {
+        final Long deviceId = device.getId();
+        final QueryBuilder<?> qb = session.getMiScaleWeightSampleDao().queryBuilder();
+
+        qb.where(MiScaleWeightSampleDao.Properties.DeviceId.eq(deviceId)).buildDelete().executeDeleteWithoutDetachingEntities();
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("MIBCS|MIBFS", Pattern.CASE_INSENSITIVE);
+    }
+
+    @NonNull
+    @Override
+    public Collection<? extends ScanFilter> createBLEScanFilters() {
+        final ParcelUuid bodyCompositionService = new ParcelUuid(GattService.UUID_SERVICE_BODY_COMPOSITION);
+
+        final ScanFilter.Builder builder = new ScanFilter.Builder();
+        builder.setServiceUuid(bodyCompositionService);
+
+        final int manufacturerId = 0x0157; // Huami
+        builder.setManufacturerData(manufacturerId, new byte[6], new byte[6]);
+
+        return Collections.singletonList(builder.build());
+    }
+
+    @Override
+    public int getBatteryCount() {
+        return 0;
+    }
+
+    @Override
+    public int getBondingStyle() {
+        return super.BONDING_STYLE_NONE;
+    }
+
+    @Override
+    public String getManufacturer() {
+        // Actual manufacturer is Huami
+        return "Xiaomi";
+    }
+
+    @Override
+    public TimeSampleProvider<? extends WeightSample> getWeightSampleProvider(final GBDevice device, final DaoSession session) {
+        return new MiScaleSampleProvider(device, session);
+    }
+
+    @Override
+    public boolean supportsWeightMeasurement() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsActivityTracking() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsActivityTabs() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsSleepMeasurement() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsStepCounter() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsSpeedzones() {
+        return false;
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return MiCompositionScaleDeviceSupport.class;
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_micompositionscale;
+    }
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_miscale;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_miscale_disabled;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miscale/MiScaleSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miscale/MiScaleSampleProvider.java
new file mode 100644
index 0000000..9de9b70
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miscale/MiScaleSampleProvider.java
@@ -0,0 +1,54 @@
+/*  Copyright (C) 2024 Severin von Wnuck-Lipinski
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.miscale;
+
+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.MiScaleWeightSample;
+import nodomain.freeyourgadget.gadgetbridge.entities.MiScaleWeightSampleDao;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class MiScaleSampleProvider extends AbstractTimeSampleProvider<MiScaleWeightSample> {
+    public MiScaleSampleProvider(GBDevice device, DaoSession session) {
+        super(device, session);
+    }
+
+    @Override
+    @NonNull
+    public AbstractDao<MiScaleWeightSample, ?> getSampleDao() {
+        return getSession().getMiScaleWeightSampleDao();
+    }
+
+    @NonNull
+    protected Property getTimestampSampleProperty() {
+        return MiScaleWeightSampleDao.Properties.Timestamp;
+    }
+
+    @NonNull
+    protected Property getDeviceIdentifierSampleProperty() {
+        return MiScaleWeightSampleDao.Properties.DeviceId;
+    }
+
+    @Override
+    public MiScaleWeightSample createSample() {
+        return new MiScaleWeightSample();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miscale/MiSmartScaleCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miscale/MiSmartScaleCoordinator.java
new file mode 100644
index 0000000..96b8848
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miscale/MiSmartScaleCoordinator.java
@@ -0,0 +1,131 @@
+/*  Copyright (C) 2024 Severin von Wnuck-Lipinski
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.miscale;
+
+import androidx.annotation.NonNull;
+
+import java.util.regex.Pattern;
+
+import de.greenrobot.dao.query.QueryBuilder;
+import nodomain.freeyourgadget.gadgetbridge.GBException;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettings;
+import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLEDeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.TimeSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.entities.MiScaleWeightSampleDao;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.WeightSample;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.miscale.MiSmartScaleDeviceSupport;
+
+public class MiSmartScaleCoordinator extends AbstractBLEDeviceCoordinator {
+    @Override
+    public String getManufacturer() {
+        // Actual manufacturer is Huami
+        return "Xiaomi";
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("MI SCALE2");
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_mismartscale;
+    }
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_miscale;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_miscale_disabled;
+    }
+
+    @Override
+    public int getBatteryCount() {
+        return 0;
+    }
+
+    @Override
+    public int getBondingStyle() {
+        return BONDING_STYLE_NONE;
+    }
+
+    @Override
+    protected void deleteDevice(@NonNull final GBDevice gbDevice, @NonNull final Device device, @NonNull final DaoSession session) throws GBException {
+        final Long deviceId = device.getId();
+        final QueryBuilder<?> qb = session.getMiScaleWeightSampleDao().queryBuilder();
+
+        qb.where(MiScaleWeightSampleDao.Properties.DeviceId.eq(deviceId)).buildDelete().executeDeleteWithoutDetachingEntities();
+    }
+
+    @Override
+    public DeviceSpecificSettings getDeviceSpecificSettings(final GBDevice device) {
+        final DeviceSpecificSettings settings = new DeviceSpecificSettings();
+
+        settings.addRootScreen(R.xml.devicesettings_mismartscale);
+
+        return settings;
+    }
+
+    @Override
+    public TimeSampleProvider<? extends WeightSample> getWeightSampleProvider(final GBDevice device, final DaoSession session) {
+        return new MiScaleSampleProvider(device, session);
+    }
+
+    @Override
+    public boolean supportsWeightMeasurement() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsActivityTracking() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsActivityTabs() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsSleepMeasurement() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsStepCounter() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsSpeedzones() {
+        return false;
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return MiSmartScaleDeviceSupport.class;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/moondrop/MoondropSpaceTravelCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/moondrop/MoondropSpaceTravelCoordinator.java
new file mode 100644
index 0000000..8456745
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/moondrop/MoondropSpaceTravelCoordinator.java
@@ -0,0 +1,82 @@
+/*  Copyright (C) 2024 Severin von Wnuck-Lipinski
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.moondrop;
+
+import androidx.annotation.NonNull;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.GBException;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettings;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsScreen;
+import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLClassicDeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.moondrop.MoondropSpaceTravelDeviceSupport;
+
+public class MoondropSpaceTravelCoordinator extends AbstractBLClassicDeviceCoordinator {
+    @Override
+    public String getManufacturer() {
+        return "Moondrop";
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("Moondrop Space Travel");
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_moondrop_space_travel;
+    }
+
+    @Override
+    public int getBatteryCount() {
+        return 0;
+    }
+
+    @Override
+    protected void deleteDevice(
+            @NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session)
+            throws GBException {}
+
+    @Override
+    public DeviceSpecificSettings getDeviceSpecificSettings(final GBDevice device) {
+        final DeviceSpecificSettings settings = new DeviceSpecificSettings();
+
+        settings.addRootScreen(R.xml.devicesettings_moondrop_space_travel_equalizer);
+        settings.addRootScreen(DeviceSpecificSettingsScreen.TOUCH_OPTIONS);
+        settings.addSubScreen(
+                DeviceSpecificSettingsScreen.TOUCH_OPTIONS,
+                R.xml.devicesettings_moondrop_space_travel_touch);
+        settings.addRootScreen(DeviceSpecificSettingsScreen.CALLS_AND_NOTIFICATIONS);
+        settings.addSubScreen(
+                DeviceSpecificSettingsScreen.CALLS_AND_NOTIFICATIONS,
+                R.xml.devicesettings_headphones);
+
+        return settings;
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return MoondropSpaceTravelDeviceSupport.class;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/no1f1/No1F1Constants.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/no1f1/No1F1Constants.java
new file mode 100644
index 0000000..dcb0ca3
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/no1f1/No1F1Constants.java
@@ -0,0 +1,52 @@
+/*  Copyright (C) 2017-2024 protomors
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.no1f1;
+
+import java.util.UUID;
+
+public final class No1F1Constants {
+
+    public static final UUID UUID_CHARACTERISTIC_CONTROL = UUID.fromString("000033f1-0000-1000-8000-00805f9b34fb");
+    public static final UUID UUID_CHARACTERISTIC_MEASURE = UUID.fromString("000033f2-0000-1000-8000-00805f9b34fb");
+    public static final UUID UUID_SERVICE_NO1 = UUID.fromString("000055ff-0000-1000-8000-00805f9b34fb");
+
+    public static final byte CMD_DISPLAY_SETTINGS = (byte) 0xa0;
+    public static final byte CMD_FIRMWARE_VERSION = (byte) 0xa1;
+    public static final byte CMD_BATTERY = (byte) 0xa2;
+    public static final byte CMD_DATETIME = (byte) 0xa3;
+    public static final byte CMD_USER_DATA = (byte) 0xa9;
+    public static final byte CMD_ALARM = (byte) 0xab;
+    public static final byte CMD_FACTORY_RESET = (byte) 0xad;
+    public static final byte CMD_REALTIME_STEPS = (byte) 0xb1;
+    public static final byte CMD_FETCH_STEPS = (byte) 0xb2;
+    public static final byte CMD_FETCH_SLEEP = (byte) 0xb3;
+    public static final byte CMD_REALTIME_HEARTRATE = (byte) 0xe5;
+    public static final byte CMD_FETCH_HEARTRATE = (byte) 0xe6;
+    public static final byte CMD_NOTIFICATION = (byte) 0xc1;
+    public static final byte CMD_ICON = (byte) 0xc3;
+    public static final byte CMD_DEVICE_SETTINGS = (byte) 0xd3;
+    public static final byte CMD_HEARTRATE_SETTINGS = (byte) 0xd6;
+
+    public static final byte NOTIFICATION_HEADER = (byte) 0x01;
+    public static final byte NOTIFICATION_CALL = (byte) 0x02;
+    public static final byte NOTIFICATION_SMS = (byte) 0x03;
+    public static final byte NOTIFICATION_STOP = (byte) 0x04; // to stop showing incoming call
+
+    public static final byte ICON_QQ = (byte) 0x01;
+    public static final byte ICON_WECHAT = (byte) 0x02;
+    public static final byte ICON_ALARM = (byte) 0x04;
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/no1f1/No1F1Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/no1f1/No1F1Coordinator.java
new file mode 100644
index 0000000..99c168f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/no1f1/No1F1Coordinator.java
@@ -0,0 +1,173 @@
+/*  Copyright (C) 2017-2024 Andreas Shimokawa, Carsten Pfeiffer, Damien
+    Gaignon, Daniel Dakhno, Daniele Gobbetti, Jos茅 Rebelo, Petr Kadlec, Petr
+    Van臎k, protomors
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.no1f1;
+
+import android.app.Activity;
+import android.bluetooth.le.ScanFilter;
+import android.content.Context;
+import android.net.Uri;
+import android.os.ParcelUuid;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.regex.Pattern;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import de.greenrobot.dao.query.QueryBuilder;
+import nodomain.freeyourgadget.gadgetbridge.GBException;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLEDeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.entities.No1F1ActivitySampleDao;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.no1f1.No1F1Support;
+
+public class No1F1Coordinator extends AbstractBLEDeviceCoordinator {
+
+    @NonNull
+    @Override
+    public Collection<? extends ScanFilter> createBLEScanFilters() {
+        ParcelUuid hpService = new ParcelUuid(No1F1Constants.UUID_SERVICE_NO1);
+        ScanFilter filter = new ScanFilter.Builder().setServiceUuid(hpService).build();
+        return Collections.singletonList(filter);
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("X-RUN.*|MH30.*");
+    }
+    @Override
+    public int getBondingStyle() {
+        return BONDING_STYLE_NONE;
+    }
+
+    @Nullable
+    @Override
+    public Class<? extends Activity> getPairingActivity() {
+        return null;
+    }
+
+    @Override
+    public boolean supportsActivityDataFetching() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsActivityTracking() {
+        return true;
+    }
+
+    @Override
+    public SampleProvider<? extends ActivitySample> getSampleProvider(GBDevice device, DaoSession session) {
+        return new No1F1SampleProvider(device, session);
+    }
+
+    @Override
+    public InstallHandler findInstallHandler(Uri uri, Context context) {
+        return null;
+    }
+
+    @Override
+    public boolean supportsScreenshots(final GBDevice device) {
+        return false;
+    }
+
+    @Override
+    public int getAlarmSlotCount(GBDevice device) {
+        return 3;
+    }
+
+    @Override
+    public boolean supportsHeartRateMeasurement(GBDevice device) {
+        return true;
+    }
+
+    @Override
+    public String getManufacturer() {
+        return "NO1";
+    }
+
+    @Override
+    public boolean supportsAppsManagement(final GBDevice device) {
+        return false;
+    }
+
+    @Override
+    public Class<? extends Activity> getAppsManagementActivity() {
+        return null;
+    }
+
+    @Override
+    public boolean supportsCalendarEvents() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsRealtimeData() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsWeather() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsFindDevice() {
+        return true;
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return No1F1Support.class;
+    }
+
+    @Override
+    protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
+        Long deviceId = device.getId();
+        QueryBuilder<?> qb = session.getNo1F1ActivitySampleDao().queryBuilder();
+        qb.where(No1F1ActivitySampleDao.Properties.DeviceId.eq(deviceId)).buildDelete().executeDeleteWithoutDetachingEntities();
+    }
+
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_no1_f1;
+    }
+
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_hplus;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_hplus_disabled;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/no1f1/No1F1SampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/no1f1/No1F1SampleProvider.java
new file mode 100644
index 0000000..8110fba
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/no1f1/No1F1SampleProvider.java
@@ -0,0 +1,84 @@
+/*  Copyright (C) 2017-2024 Daniele Gobbetti, protomors
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.no1f1;
+
+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.No1F1ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.entities.No1F1ActivitySampleDao;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+
+public class No1F1SampleProvider extends AbstractSampleProvider<No1F1ActivitySample> {
+
+    private GBDevice mDevice;
+    private DaoSession mSession;
+
+    public No1F1SampleProvider(GBDevice device, DaoSession session) {
+        super(device, session);
+
+        mSession = session;
+        mDevice = device;
+    }
+
+    @Override
+    public ActivityKind normalizeType(int rawType) {
+        return ActivityKind.fromCode(rawType);
+    }
+
+    @Override
+    public int toRawActivityKind(ActivityKind activityKind) {
+        return activityKind.getCode();
+    }
+
+    @Override
+    public float normalizeIntensity(int rawIntensity) {
+        return rawIntensity / (float) 8000.0;
+    }
+
+    @Override
+    public No1F1ActivitySample createActivitySample() {
+        return new No1F1ActivitySample();
+    }
+
+    @Override
+    public AbstractDao<No1F1ActivitySample, ?> getSampleDao() {
+        return getSession().getNo1F1ActivitySampleDao();
+    }
+
+    @Nullable
+    @Override
+    protected Property getRawKindSampleProperty() {
+        return No1F1ActivitySampleDao.Properties.RawKind;
+    }
+
+    @NonNull
+    @Override
+    protected Property getTimestampSampleProperty() {
+        return No1F1ActivitySampleDao.Properties.Timestamp;
+    }
+
+    @NonNull
+    @Override
+    protected Property getDeviceIdentifierSampleProperty() {
+        return No1F1ActivitySampleDao.Properties.DeviceId;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/nothing/AbstractEarCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/nothing/AbstractEarCoordinator.java
new file mode 100644
index 0000000..5ffca1c
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/nothing/AbstractEarCoordinator.java
@@ -0,0 +1,103 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.nothing;
+
+import android.content.Context;
+import android.net.Uri;
+
+import androidx.annotation.NonNull;
+
+import nodomain.freeyourgadget.gadgetbridge.GBException;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettings;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer;
+import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLClassicDeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.BatteryConfig;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.nothing.Ear1Support;
+
+public abstract class AbstractEarCoordinator extends AbstractBLClassicDeviceCoordinator {
+    @Override
+    public InstallHandler findInstallHandler(Uri uri, Context context) {
+        return null;
+    }
+
+    @Override
+    public String getManufacturer() {
+        return "Nothing";
+    }
+
+    @Override
+    public boolean supportsFindDevice() {
+        return true;
+    }
+
+    @Override
+    protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
+
+    }
+
+    @Override
+    public int getBatteryCount() {
+        return 3;
+    }
+
+    @Override
+    public BatteryConfig[] getBatteryConfig(final GBDevice device) {
+        BatteryConfig battery1 = new BatteryConfig(0, R.drawable.ic_tws_case, R.string.battery_case);
+        BatteryConfig battery2 = new BatteryConfig(1, R.drawable.ic_nothing_ear_l, R.string.left_earbud);
+        BatteryConfig battery3 = new BatteryConfig(2, R.drawable.ic_nothing_ear_r, R.string.right_earbud);
+        return new BatteryConfig[]{battery1, battery2, battery3};
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return Ear1Support.class;
+    }
+
+    @Override
+    public DeviceSpecificSettings getDeviceSpecificSettings(final GBDevice device) {
+        final DeviceSpecificSettings deviceSpecificSettings = new DeviceSpecificSettings();
+        deviceSpecificSettings.addRootScreen(R.xml.devicesettings_nothing_ear1);
+        deviceSpecificSettings.addRootScreen(R.xml.devicesettings_headphones);
+        return deviceSpecificSettings;
+    }
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_nothingear;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_nothingear_disabled;
+    }
+
+    @Override
+    public DeviceSpecificSettingsCustomizer getDeviceSpecificSettingsCustomizer(final GBDevice device) {
+        return new EarSettingsCustomizer();
+    }
+
+    public abstract boolean incrementCounter();
+
+    public abstract boolean supportsLightAncAndTransparency();
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/nothing/CmfBudsPro2Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/nothing/CmfBudsPro2Coordinator.java
new file mode 100644
index 0000000..6f2f3e4
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/nothing/CmfBudsPro2Coordinator.java
@@ -0,0 +1,43 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.nothing;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+
+public class CmfBudsPro2Coordinator extends AbstractEarCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("^CMF Buds Pro 2$");
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_nothing_cmf_buds_pro_2;
+    }
+
+    @Override
+    public boolean incrementCounter() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsLightAncAndTransparency() {
+        return true;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/nothing/Ear1Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/nothing/Ear1Coordinator.java
new file mode 100644
index 0000000..6707d89
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/nothing/Ear1Coordinator.java
@@ -0,0 +1,44 @@
+/*  Copyright (C) 2021-2024 Damien Gaignon, Daniel Dakhno, Daniele Gobbetti,
+    Jos茅 Rebelo, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.nothing;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+
+public class Ear1Coordinator extends AbstractEarCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("Nothing ear (1)", Pattern.LITERAL);
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_nothingear1;
+    }
+
+    @Override
+    public boolean incrementCounter() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsLightAncAndTransparency() {
+        return true;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/nothing/Ear2Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/nothing/Ear2Coordinator.java
new file mode 100644
index 0000000..e5b1a0e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/nothing/Ear2Coordinator.java
@@ -0,0 +1,43 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.nothing;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+
+public class Ear2Coordinator extends AbstractEarCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("Ear (2)", Pattern.LITERAL);
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_nothingear2;
+    }
+
+    @Override
+    public boolean incrementCounter() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsLightAncAndTransparency() {
+        return true;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/nothing/EarSettingsCustomizer.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/nothing/EarSettingsCustomizer.java
new file mode 100644
index 0000000..0d5564b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/nothing/EarSettingsCustomizer.java
@@ -0,0 +1,92 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.nothing;
+
+import android.os.Parcel;
+
+import androidx.preference.ListPreference;
+import androidx.preference.Preference;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsHandler;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+public class EarSettingsCustomizer implements DeviceSpecificSettingsCustomizer {
+    public static final Creator<EarSettingsCustomizer> CREATOR = new Creator<EarSettingsCustomizer>() {
+        @Override
+        public EarSettingsCustomizer createFromParcel(final Parcel in) {
+            return new EarSettingsCustomizer();
+        }
+
+        @Override
+        public EarSettingsCustomizer[] newArray(final int size) {
+            return new EarSettingsCustomizer[size];
+        }
+    };
+
+    @Override
+    public void onPreferenceChange(final Preference preference, final DeviceSpecificSettingsHandler handler) {
+    }
+
+    @Override
+    public void customizeSettings(final DeviceSpecificSettingsHandler handler, final Prefs prefs, final String rootKey) {
+        final AbstractEarCoordinator earCoordinator = (AbstractEarCoordinator) handler.getDevice().getDeviceCoordinator();
+
+        if (!earCoordinator.supportsLightAncAndTransparency()) {
+            // If light anc and transparency is not supported, remove the values from the preference
+            final Preference audioModePref = handler.findPreference(DeviceSettingsPreferenceConst.PREF_NOTHING_EAR1_AUDIOMODE);
+
+            if (audioModePref != null) {
+                final CharSequence[] originalEntries = ((ListPreference) audioModePref).getEntries();
+                final CharSequence[] originalEntryValues = ((ListPreference) audioModePref).getEntryValues();
+
+                final List<CharSequence> entries = new ArrayList<>();
+                final List<CharSequence> entryValues = new ArrayList<>();
+
+                for (int i = 0; i < originalEntries.length; i++) {
+                    if ("anc".equals(originalEntryValues[i].toString()) || "off".equals(originalEntryValues[i].toString())) {
+                        entries.add(originalEntries[i]);
+                        entryValues.add(originalEntryValues[i]);
+                    }
+                }
+
+                ((ListPreference) audioModePref).setEntries(entries.toArray(new CharSequence[0]));
+                ((ListPreference) audioModePref).setEntryValues(entryValues.toArray(new CharSequence[0]));
+            }
+        }
+    }
+
+    @Override
+    public Set<String> getPreferenceKeysWithSummary() {
+        return Collections.emptySet();
+    }
+
+    @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/nothing/EarStickCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/nothing/EarStickCoordinator.java
new file mode 100644
index 0000000..1200ec4
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/nothing/EarStickCoordinator.java
@@ -0,0 +1,43 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.nothing;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+
+public class EarStickCoordinator extends AbstractEarCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("Ear (stick)", Pattern.LITERAL);
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_nothingearstick;
+    }
+
+    @Override
+    public boolean incrementCounter() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsLightAncAndTransparency() {
+        return false;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/nut/NutConstants.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/nut/NutConstants.java
new file mode 100644
index 0000000..5f0d4b5
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/nut/NutConstants.java
@@ -0,0 +1,113 @@
+/*  Copyright (C) 2020-2024 Taavi Eom盲e
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.nut;
+
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.service.btle.GattService;
+
+public class NutConstants {
+    /**
+     * Just battery info
+     */
+    public static final UUID SERVICE_BATTERY = GattService.UUID_SERVICE_BATTERY_SERVICE;
+    public static final UUID CHARAC_BATTERY_INFO = UUID.fromString("00002a19-0000-1000-8000-00805f9b34fb");
+
+    /**
+     * Device info available.
+     **/
+    public static final UUID SERVICE_DEVICE_INFO = GattService.UUID_SERVICE_DEVICE_INFORMATION;
+    /**
+     * Firmware version.
+     * Used with {@link NutConstants#SERVICE_DEVICE_INFO}
+     */
+    public static final UUID CHARAC_FIRMWARE_VERSION = UUID.fromString("00002a26-0000-1000-8000-00805f9b34fb");
+    /**
+     * System ID.
+     * Used with {@link NutConstants#SERVICE_DEVICE_INFO}
+     */
+    public static final UUID CHARAC_SYSTEM_ID = UUID.fromString("00002a23-0000-1000-8000-00805f9b34fb");
+    /**
+     * Hardware version.
+     * Used with {@link NutConstants#SERVICE_DEVICE_INFO}
+     */
+    public static final UUID CHARAC_HARDWARE_VERSION = UUID.fromString("00002a27-0000-1000-8000-00805f9b34fb");
+    /**
+     * Manufacturer name.
+     * Used with {@link NutConstants#SERVICE_DEVICE_INFO}
+     */
+    public static final UUID CHARAC_MANUFACTURER_NAME = UUID.fromString("00002a29-0000-1000-8000-00805f9b34fb");
+
+
+    /**
+     * Link loss alert service.
+     */
+    public static final UUID SERVICE_LINK_LOSS = UUID.fromString("00001803-0000-1000-8000-00805f9b34fb");
+
+
+    /**
+     * Immediate alert service.
+     */
+    public static final UUID SERVICE_IMMEDIATE_ALERT = UUID.fromString("00001802-0000-1000-8000-00805f9b34fb");
+    /**
+     * Immediate alert level
+     * Used with {@link NutConstants#SERVICE_IMMEDIATE_ALERT}
+     */
+    public static final UUID CHARAC_LINK_LOSS_ALERT_LEVEL = UUID.fromString("00002a06-0000-1000-8000-00805f9b34fb");
+
+
+    /**
+     * Proprietary command endpoint.
+     * TODO: Anything else in this service on other devices?
+     */
+    public static final UUID SERVICE_PROPRIETARY_NUT = UUID.fromString("0000ff00-0000-1000-8000-00805f9b34fb");
+    /**
+     * Shutdown or reset.
+     * Used with {@link NutConstants#SERVICE_PROPRIETARY_NUT}
+     */
+    public static final UUID CHARAC_CHANGE_POWER = UUID.fromString("0000ff01-0000-1000-8000-00805f9b34fb");
+    /**
+     * Commands for proprietary service.
+     * Used with {@link NutConstants#SERVICE_PROPRIETARY_NUT}
+     */
+    public static final UUID CHARAC_DFU_PW = UUID.fromString("0000ff02-0000-1000-8000-00805f9b34fb");
+    /**
+     * Authentication using 16-byte key?
+     * Used with {@link NutConstants#SERVICE_PROPRIETARY_NUT}
+     * TODO: Exists only on Nut Mini?
+     */
+    public static final UUID CHARAC_AUTH_STATUS = UUID.fromString("0000ff05-0000-1000-8000-00805f9b34fb");
+
+
+    /**
+     * Ringing configuration.
+     * TODO: Exact purpose?
+     */
+    public static final UUID SERVICE_UNKNOWN_2 = UUID.fromString("0000ffe0-0000-1000-8000-00805f9b34fb");
+    /**
+     * Ringing configuration.
+     * Used with {@link NutConstants#SERVICE_UNKNOWN_2}
+     * TODO: Something else on other devices?
+     */
+    public static final UUID CHARAC_UNKNOWN_2 = UUID.fromString("0000ffe1-0000-1000-8000-00805f9b34fb");
+
+
+    /**
+     * Very little mention online, specific to Nut devices?
+     */
+    public static final UUID UNKNOWN_3 = UUID.fromString("00001530-0000-1000-8000-00805f9b34fb");
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/nut/NutCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/nut/NutCoordinator.java
new file mode 100644
index 0000000..5953502
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/nut/NutCoordinator.java
@@ -0,0 +1,172 @@
+/*  Copyright (C) 2020-2024 Andreas Shimokawa, Damien Gaignon, Daniel Dakhno,
+    Jos茅 Rebelo, Petr Van臎k, Taavi Eom盲e
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.nut;
+
+import android.app.Activity;
+import android.bluetooth.le.ScanFilter;
+import android.content.Context;
+import android.net.Uri;
+
+import androidx.annotation.NonNull;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLEDeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.nut.NutSupport;
+
+public class NutCoordinator extends AbstractBLEDeviceCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("nut.*", Pattern.CASE_INSENSITIVE);
+    }
+
+    @Override
+    public int getBondingStyle() {
+        return BONDING_STYLE_ASK;
+    }
+
+    @NonNull
+    @Override
+    public Collection<? extends ScanFilter> createBLEScanFilters() {
+        ScanFilter filter = new ScanFilter.Builder()
+                .setDeviceName("nut") // Nut Mini
+                .build();
+        return Collections.singletonList(filter);
+    }
+
+    @Override
+    public int[] getSupportedDeviceSpecificSettings(GBDevice device) {
+        return new int[]{
+                R.xml.devicesettings_nutmini,
+        };
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return NutSupport.class;
+    }
+
+    @Override
+    public Class<? extends Activity> getPairingActivity() {
+        return null;
+    }
+
+    @Override
+    public InstallHandler findInstallHandler(Uri uri, Context context) {
+        return null;
+    }
+
+    @Override
+    public boolean supportsActivityDataFetching() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsActivityTracking() {
+        return false;
+    }
+
+    @Override
+    public SampleProvider<? extends ActivitySample> getSampleProvider(GBDevice device, DaoSession session) {
+        return null;
+    }
+
+    @Override
+    public boolean supportsScreenshots(final GBDevice device) {
+        return false;
+    }
+
+    @Override
+    public int getAlarmSlotCount(GBDevice device) {
+        return 0;
+    }
+
+    @Override
+    public boolean supportsHeartRateMeasurement(GBDevice device) {
+        return false;
+    }
+
+    @Override
+    public String getManufacturer() {
+        return "Nut";
+    }
+
+    @Override
+    public boolean supportsAppsManagement(final GBDevice device) {
+        return false;
+    }
+
+    @Override
+    public Class<? extends Activity> getAppsManagementActivity() {
+        return null;
+    }
+
+    @Override
+    public boolean supportsCalendarEvents() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsRealtimeData() {
+        return false; //TODO: RRSI
+    }
+
+    @Override
+    public boolean supportsWeather() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsFindDevice() {
+        return true;
+    }
+
+    @Override
+    protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) {
+    }
+
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_nut_mini;
+    }
+
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_itag;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_itag_disabled;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/nut/NutKey.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/nut/NutKey.java
new file mode 100644
index 0000000..b1e8b1b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/nut/NutKey.java
@@ -0,0 +1,265 @@
+/*  Copyright (C) 2020-2024 Taavi Eom盲e
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.nut;
+
+import org.apache.commons.lang3.ArrayUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.math.BigInteger;
+import java.util.AbstractMap;
+import java.util.Map;
+
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class NutKey {
+    private static final Logger LOG = LoggerFactory.getLogger(NutKey.class);
+
+    /**
+     * Different from {@link GB#hexStringToByteArray} because
+     * it returns an array of zero bytes when it's given zero bytes
+     * <p>
+     * https://stackoverflow.com/a/140430/4636860
+     *
+     * @param encoded hexadecimal string like "0xcafebabe", "DEADBEEF" or "feeddead"
+     * @return resulting byte array
+     */
+    public static byte[] hexStringToByteArrayNut(String encoded) {
+        if (encoded.startsWith("0x")) {
+            encoded = encoded.substring(2);
+        }
+
+        if ((encoded.length() % 2) != 0) {
+            throw new IllegalArgumentException("Input string must contain an even number of characters");
+        }
+
+        final byte[] result = new byte[encoded.length() / 2];
+        final char[] enc = encoded.toCharArray();
+        for (int i = 0; i < enc.length; i += 2) {
+            result[i / 2] = (byte) Integer.parseInt(String.valueOf(enc[i]) + enc[i + 1], 16);
+        }
+        return result;
+    }
+
+    /**
+     * Returns the array as hexadecimal string space delimited
+     *
+     * @param bytes bytes to return
+     * @return returns
+     */
+    public static String bytesToHex2(byte[] bytes) {
+        char[] hexChars = new char[bytes.length * 3];
+        for (int j = 0; j < bytes.length; j++) {
+            hexChars[j * 3] = GB.HEX_CHARS[(bytes[j] & 0xFF) >>> 4];
+            hexChars[j * 3 + 1] = GB.HEX_CHARS[(bytes[j] & 0xFF) & 0x0F];
+            hexChars[j * 3 + 2] = " ".toCharArray()[0];
+        }
+        return new String(hexChars).toLowerCase();
+    }
+
+    /**
+     * Generates an assembled packet based on inputs
+     *
+     * @param mac       the mac of the target device (AA:BB:CC:DD:EE:FF)
+     * @param challenge the received challenge, THE ENTIRE PAYLOAD WITH PREAMBLE!
+     * @param key1      first key
+     * @param key2      second key
+     * @return assembled packet (without the preamble!)
+     */
+    public static byte[] passwordGeneration(String mac, byte[] challenge, BigInteger key1, BigInteger key2) {
+        if (challenge[0] != 0x01) {
+            throw new IllegalArgumentException("Challenge must be given with the preamble");
+        }
+
+        byte[] mac_as_bytes = macAsByteArray(mac);
+        byte[] correct_challenge = new byte[challenge.length - 1];
+        System.arraycopy(challenge, 1, correct_challenge, 0, challenge.length - 1);
+        ArrayUtils.reverse(correct_challenge);
+        BigInteger c = new BigInteger(1, mac_as_bytes).add(new BigInteger(1, correct_challenge));
+
+        BigInteger max64 = BigInteger.ONE.add(BigInteger.ONE).pow(64).subtract(BigInteger.ONE);
+        BigInteger tmp1 = key2.xor(max64);
+        BigInteger result1;
+        if (c.compareTo(tmp1) > 0) {
+            result1 = c.add(key1).subtract(tmp1);
+        } else {
+            result1 = key1;
+        }
+
+        BigInteger result2;
+        if (key2.remainder(BigInteger.ONE.add(BigInteger.ONE)).compareTo(BigInteger.ONE) == 0) {
+            result2 = key2.add(c);
+        } else {
+            result2 = key2.multiply(BigInteger.ONE.add(BigInteger.ONE)).add(c);
+        }
+
+        return byteArraysConcatReverseWithPad(result1.toByteArray(), result2.toByteArray());
+    }
+
+    /**
+     * Reverses the password generation into keys
+     * <p>
+     * This assumes you have:
+     * The MAC of the device, the challenge and response payload
+     * <p>
+     * See also {@link NutKey#passwordGeneration}
+     *
+     * @param challenge the RECEIVED and COMPLETE payload (truncated accordingly)
+     * @param response  the SENT and COMPLETE payload
+     * @param deviceMac colon-separated MAC address of the Nut as a string
+     */
+    public static Map.Entry<BigInteger, BigInteger> reversePasswordGeneration(byte[] challenge,
+                                                                              byte[] response,
+                                                                              String deviceMac) {
+        // The two arrays that were concat. with byteArraysConcatReverseWithPad(orig1, orig2)
+        byte[] original1 = new byte[8];
+        byte[] original2 = new byte[8];
+
+        // The response without preamble
+        if (response[0] != 0x02) {
+            throw new IllegalArgumentException("Response always begins with 0x02");
+        }
+        byte[] cleanResponse = new byte[16];
+        System.arraycopy(response, 1, cleanResponse, 0, cleanResponse.length);
+
+        // The challenge without preamble
+        byte[] cleanChall = new byte[4];
+        System.arraycopy(challenge, 1, cleanChall, 0, cleanChall.length);
+
+        // Reverse the two arrays sent as a response
+        byteArraysDeConcatReverseWithPad(cleanResponse, original1, original2);
+
+        // Two common components in the equation
+        BigInteger a = new BigInteger(1, macAsByteArray(deviceMac));
+        byte[] cleanChallTmp = cleanChall.clone();
+        ArrayUtils.reverse(cleanChallTmp);
+        BigInteger b = new BigInteger(1, cleanChallTmp);
+        BigInteger c = a.add(b);
+        BigInteger max64 = BigInteger.ONE.add(BigInteger.ONE).pow(64).subtract(BigInteger.ONE);
+
+        // We don't know actual keys used yet
+        // There's two possibilities,
+        // either it's directly what's in the packet
+        // orig1 = key1
+        BigInteger key1a = new BigInteger(1, original1);
+        // Or it's derived from key2 using this formula :S
+        // orig1 = c + key1 - (key2 XOR (2^64 - 1)
+        // see below when it might be needed
+
+        // It's either just
+        // orig2 = key2 + c
+        BigInteger key2a = new BigInteger(1, original2).subtract(c);
+        // alternatively
+        // orig2 = 2 * key2 + c
+        BigInteger key2b = new BigInteger(1, original2).multiply(BigInteger.ONE.add(BigInteger.ONE)).subtract(c);
+
+        // Now we have key2a, key2b, key1a,
+        // trying to determine if we can do with just those,
+        // or need to continue
+        byte[] key1a2aresult = passwordGeneration(deviceMac, challenge, key1a, key2a);
+        LOG.debug("Result1a2a:02 %s\n", bytesToHex2(key1a2aresult));
+
+        if (java.util.Arrays.equals(key1a2aresult, cleanResponse)) {
+            LOG.debug("Found key1a & key2a are correct, DONE!");
+            return new AbstractMap.SimpleEntry<>(key1a, key2a);
+        }
+        // Unsuccessful, let's try key1a with key2b
+
+        byte[] key1a2bresult = passwordGeneration(deviceMac, challenge, key1a, key2b);
+        LOG.debug("Result1a2b:02 %s\n", bytesToHex2(key1a2bresult));
+
+        if (java.util.Arrays.equals(key1a2bresult, cleanResponse)) {
+            LOG.debug("Found key1a & key2b are correct, DONE!");
+            return new AbstractMap.SimpleEntry<>(key1a, key2b);
+        }
+
+        // If we're still not done, we have to calculate two possible key1b-s
+        // one for key2a, other for key2b
+        // key1 = c + (key2 XOR (2^64 - 1) + orig1
+        BigInteger key1b2a = c.add(key2a.xor(max64)).add(new BigInteger(1, original1));
+        byte[] key1b2aresult = passwordGeneration(deviceMac, challenge, key1b2a, key2a);
+        LOG.debug("Result1b2a:02 %s\n", bytesToHex2(key1b2aresult));
+
+        if (java.util.Arrays.equals(key1b2aresult, cleanResponse)) {
+            LOG.debug("Found key1b2a & key2b are correct, DONE!");
+            return new AbstractMap.SimpleEntry<>(key1b2a, key2b);
+        }
+
+        BigInteger key1b2b = c.add(key2b.xor(max64)).add(new BigInteger(1, original1));
+        byte[] key1b2bresult = passwordGeneration(deviceMac, challenge, key1b2b, key2b);
+        LOG.debug("Result1b2b:02 %s\n", bytesToHex2(key1b2bresult));
+
+        if (java.util.Arrays.equals(key1b2bresult, cleanResponse)) {
+            LOG.debug("Found key1b2b & key2b are correct, DONE!");
+            return new AbstractMap.SimpleEntry<>(key1b2b, key2b);
+        }
+
+        LOG.warn("Input might be incorrect, a correct key was not found");
+        return null;
+    }
+
+    /**
+     * Turns the MAC address into an array of bytes
+     *
+     * @param address MAC address
+     * @return byte[] containing the MAC address bytes
+     */
+    public static byte[] macAsByteArray(String address) {
+        //noinspection DynamicRegexReplaceableByCompiledPattern
+        return hexStringToByteArrayNut(address.replace(":", ""));
+    }
+
+    /**
+     * Concatenates two byte arrays in reverse and pads with zeros
+     *
+     * @param arr1 first array to concatenate
+     * @param arr2 second array to concatenate
+     * @return 16 bytes that contain the array in reverse, zeros if any parameter is empty
+     */
+    public static byte[] byteArraysConcatReverseWithPad(byte[] arr1, byte[] arr2) {
+        byte[] result = new byte[16];
+        for (int i = 0; i < Math.min(arr2.length, 8); i++) {
+            // Reverse the array - 0-indexed - start shorter arrays from "0" - byte index to handle
+            result[8 - 1 - (8 - Math.min(arr2.length, 8)) - i] = arr2[i + Math.max((arr2.length - 8), 0)];
+        }
+        for (int i = 0; i < Math.min(arr1.length, 8); i++) {
+            result[16 - 1 - (8 - Math.min(arr1.length, 8)) - i] = arr1[i + Math.max((arr1.length - 8), 0)];
+        }
+        return result;
+    }
+
+    /**
+     * De-concatenates two byte arrays in reverse,
+     * places them in specified destinations,
+     * <p>
+     * 16 bytes that contain the array in reverse,
+     * zeros if any parameter is empty
+     *
+     * @param input array to de-concatenate, 16 bytes
+     */
+    public static void byteArraysDeConcatReverseWithPad(byte[] input, byte[] dest1, byte[] dest2) {
+        if (input.length != 16) {
+            throw new IllegalArgumentException("Input must be 16 bytes!");
+        }
+        for (int i = 0; i < 8; i++) {
+            dest2[8 - 1 - i] = input[i];
+        }
+        for (int i = 8; i < 16; i++) {
+            dest1[16 - 1 - i] = input[i];
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/oppo/OppoEncoAir2Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/oppo/OppoEncoAir2Coordinator.java
new file mode 100644
index 0000000..698c328
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/oppo/OppoEncoAir2Coordinator.java
@@ -0,0 +1,33 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.oppo;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+
+public class OppoEncoAir2Coordinator extends OppoEncoAirCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("OPPO Enco Air2", Pattern.LITERAL);
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_oppo_enco_air2;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/oppo/OppoEncoAirCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/oppo/OppoEncoAirCoordinator.java
new file mode 100644
index 0000000..937a8d8
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/oppo/OppoEncoAirCoordinator.java
@@ -0,0 +1,75 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.oppo;
+
+import android.util.Pair;
+
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.oppo.commands.TouchConfigSide;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.oppo.commands.TouchConfigType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.oppo.commands.TouchConfigValue;
+
+public class OppoEncoAirCoordinator extends OppoHeadphonesCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("OPPO Enco Air", Pattern.LITERAL);
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_oppo_enco_air;
+    }
+
+    @Override
+    public boolean supportsFindDevice() {
+        return true;
+    }
+
+    @Override
+    protected Map<Pair<TouchConfigSide, TouchConfigType>, List<TouchConfigValue>> getTouchOptions() {
+        return new LinkedHashMap<Pair<TouchConfigSide, TouchConfigType>, List<TouchConfigValue>>() {{
+            put(Pair.create(TouchConfigSide.LEFT, TouchConfigType.TAP_2), Arrays.asList(
+                TouchConfigValue.OFF,
+                TouchConfigValue.PLAY_PAUSE,
+                TouchConfigValue.PREVIOUS,
+                TouchConfigValue.NEXT,
+                TouchConfigValue.VOICE_ASSISTANT
+            ));
+            put(Pair.create(TouchConfigSide.LEFT, TouchConfigType.TAP_3), Arrays.asList(
+                    TouchConfigValue.OFF,
+                    TouchConfigValue.VOICE_ASSISTANT,
+                    TouchConfigValue.GAME_MODE
+            ));
+            put(Pair.create(TouchConfigSide.LEFT, TouchConfigType.HOLD), Arrays.asList(
+                    TouchConfigValue.OFF,
+                    TouchConfigValue.VOLUME_UP,
+                    TouchConfigValue.VOLUME_DOWN
+            ));
+
+            // Right side is the same
+            put(Pair.create(TouchConfigSide.RIGHT, TouchConfigType.TAP_2), get(Pair.create(TouchConfigSide.LEFT, TouchConfigType.TAP_2)));
+            put(Pair.create(TouchConfigSide.RIGHT, TouchConfigType.TAP_3), get(Pair.create(TouchConfigSide.LEFT, TouchConfigType.TAP_3)));
+            put(Pair.create(TouchConfigSide.RIGHT, TouchConfigType.HOLD), get(Pair.create(TouchConfigSide.LEFT, TouchConfigType.HOLD)));
+        }};
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/oppo/OppoHeadphonesCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/oppo/OppoHeadphonesCoordinator.java
new file mode 100644
index 0000000..a05297d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/oppo/OppoHeadphonesCoordinator.java
@@ -0,0 +1,101 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.oppo;
+
+import android.util.Pair;
+
+import androidx.annotation.NonNull;
+
+import java.util.List;
+import java.util.Map;
+
+import nodomain.freeyourgadget.gadgetbridge.GBException;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettings;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsScreen;
+import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLClassicDeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.BatteryConfig;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.oppo.OppoHeadphonesSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.oppo.commands.TouchConfigSide;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.oppo.commands.TouchConfigType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.oppo.commands.TouchConfigValue;
+
+public abstract class OppoHeadphonesCoordinator extends AbstractBLClassicDeviceCoordinator {
+    @Override
+    protected void deleteDevice(@NonNull final GBDevice gbDevice, @NonNull final Device device, @NonNull final DaoSession session) throws GBException {
+
+    }
+
+    @Override
+    public String getManufacturer() {
+        return "Oppo";
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return OppoHeadphonesSupport.class;
+    }
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_nothingear;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_nothingear_disabled;
+    }
+
+    @Override
+    public int getBatteryCount() {
+        return 3;
+    }
+
+    @Override
+    public BatteryConfig[] getBatteryConfig(final GBDevice device) {
+        final BatteryConfig battery1 = new BatteryConfig(0, R.drawable.ic_nothing_ear_l, R.string.left_earbud);
+        final BatteryConfig battery2 = new BatteryConfig(1, R.drawable.ic_nothing_ear_r, R.string.right_earbud);
+        final BatteryConfig battery3 = new BatteryConfig(2, R.drawable.ic_tws_case, R.string.battery_case);
+        return new BatteryConfig[]{battery1, battery2, battery3};
+    }
+
+    @Override
+    public DeviceSpecificSettings getDeviceSpecificSettings(final GBDevice device) {
+        final DeviceSpecificSettings settings = new DeviceSpecificSettings();
+
+        settings.addRootScreen(DeviceSpecificSettingsScreen.TOUCH_OPTIONS);
+        settings.addSubScreen(DeviceSpecificSettingsScreen.TOUCH_OPTIONS, R.xml.devicesettings_oppo_headphones_touch_options);
+
+        settings.addRootScreen(DeviceSpecificSettingsScreen.CALLS_AND_NOTIFICATIONS);
+        settings.addSubScreen(DeviceSpecificSettingsScreen.CALLS_AND_NOTIFICATIONS, R.xml.devicesettings_headphones);
+
+        return settings;
+    }
+
+    @Override
+    public DeviceSpecificSettingsCustomizer getDeviceSpecificSettingsCustomizer(final GBDevice device) {
+        return new OppoHeadphonesSettingsCustomizer(getTouchOptions());
+    }
+
+    protected abstract Map<Pair<TouchConfigSide, TouchConfigType>, List<TouchConfigValue>> getTouchOptions();
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/oppo/OppoHeadphonesPreferences.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/oppo/OppoHeadphonesPreferences.java
new file mode 100644
index 0000000..c5538e8
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/oppo/OppoHeadphonesPreferences.java
@@ -0,0 +1,33 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.oppo;
+
+import java.util.Locale;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.oppo.commands.TouchConfigSide;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.oppo.commands.TouchConfigType;
+
+public class OppoHeadphonesPreferences {
+    public static String getKey(final TouchConfigSide side, final TouchConfigType type) {
+        return String.format(
+                Locale.ROOT,
+                "oppo_touch__%s__%s",
+                side.name().toLowerCase(Locale.ROOT),
+                type.name().toLowerCase(Locale.ROOT)
+        );
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/oppo/OppoHeadphonesSettingsCustomizer.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/oppo/OppoHeadphonesSettingsCustomizer.java
new file mode 100644
index 0000000..c081027
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/oppo/OppoHeadphonesSettingsCustomizer.java
@@ -0,0 +1,152 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.oppo;
+
+import android.os.Parcel;
+import android.util.Pair;
+
+import androidx.preference.ListPreference;
+import androidx.preference.Preference;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsHandler;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.oppo.commands.TouchConfigSide;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.oppo.commands.TouchConfigType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.oppo.commands.TouchConfigValue;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+public class OppoHeadphonesSettingsCustomizer implements DeviceSpecificSettingsCustomizer {
+    private final Map<Pair<TouchConfigSide, TouchConfigType>, List<TouchConfigValue>> touchOptions;
+
+    public static final Creator<OppoHeadphonesSettingsCustomizer> CREATOR = new Creator<OppoHeadphonesSettingsCustomizer>() {
+        @Override
+        public OppoHeadphonesSettingsCustomizer createFromParcel(final Parcel in) {
+            final Map<Pair<TouchConfigSide, TouchConfigType>, List<TouchConfigValue>> touchOptions = new LinkedHashMap<>();
+            final int numOptions = in.readInt();
+            for (int i = 0; i < numOptions; i++) {
+                final TouchConfigSide touchConfigSide = TouchConfigSide.valueOf(in.readString());
+                final TouchConfigType touchConfigType = TouchConfigType.valueOf(in.readString());
+                final List<TouchConfigValue> values = new ArrayList<>();
+                in.readList(values, TouchConfigValue.class.getClassLoader());
+                touchOptions.put(Pair.create(touchConfigSide, touchConfigType), values);
+            }
+            return new OppoHeadphonesSettingsCustomizer(touchOptions);
+        }
+
+        @Override
+        public OppoHeadphonesSettingsCustomizer[] newArray(final int size) {
+            return new OppoHeadphonesSettingsCustomizer[size];
+        }
+    };
+
+    public OppoHeadphonesSettingsCustomizer(final Map<Pair<TouchConfigSide, TouchConfigType>, List<TouchConfigValue>> touchOptions) {
+        this.touchOptions = touchOptions;
+    }
+
+    @Override
+    public void onPreferenceChange(final Preference preference, final DeviceSpecificSettingsHandler handler) {
+    }
+
+    @Override
+    public void customizeSettings(final DeviceSpecificSettingsHandler handler, final Prefs prefs, final String rootKey) {
+        final Set<TouchConfigSide> knownSides = new HashSet<>();
+        final Set<TouchConfigType> knownTypes = new HashSet<>();
+
+        for (final Map.Entry<Pair<TouchConfigSide, TouchConfigType>, List<TouchConfigValue>> e : touchOptions.entrySet()) {
+            final TouchConfigSide side = e.getKey().first;
+            final TouchConfigType type = e.getKey().second;
+            final Set<TouchConfigValue> possibleValues = new HashSet<>(e.getValue());
+
+            knownSides.add(side);
+            knownTypes.add(type);
+
+            final String key = OppoHeadphonesPreferences.getKey(side, type);
+            final ListPreference pref = handler.findPreference(key);
+            if (pref == null) {
+                continue;
+            }
+
+            final CharSequence[] originalEntries = pref.getEntries();
+            final CharSequence[] originalValues = pref.getEntryValues();
+            final CharSequence[] entries = new CharSequence[possibleValues.size()];
+            final CharSequence[] values = new CharSequence[possibleValues.size()];
+            int j = 0;
+            for (int i = 0; i < originalValues.length; i++) {
+                if (possibleValues.contains(TouchConfigValue.valueOf(originalValues[i].toString().toUpperCase(Locale.ROOT)))) {
+                    entries[j] = originalEntries[i];
+                    values[j] = originalValues[i];
+                    j++;
+                }
+            }
+
+            pref.setEntries(entries);
+            pref.setEntryValues(values);
+
+            handler.addPreferenceHandlerFor(key);
+        }
+
+        for (final TouchConfigSide side : TouchConfigSide.values()) {
+            if (!knownSides.contains(side)) {
+                // Side not configurable, hide it completely
+                final Preference header = handler.findPreference("oppo_touch_header_" + side.name().toLowerCase(Locale.ROOT));
+                if (header != null) {
+                    header.setVisible(false);
+                    continue;
+                }
+            }
+
+            for (final TouchConfigType type : TouchConfigType.values()) {
+                if (!knownTypes.contains(type)) {
+                    final String key = OppoHeadphonesPreferences.getKey(side, type);
+                    final Preference pref = handler.findPreference(key);
+                    if (pref != null) {
+                        pref.setVisible(false);
+                    }
+                }
+            }
+        }
+    }
+
+    @Override
+    public Set<String> getPreferenceKeysWithSummary() {
+        return Collections.emptySet();
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(final Parcel dest, final int flags) {
+        dest.writeInt(touchOptions.size());
+        for (final Map.Entry<Pair<TouchConfigSide, TouchConfigType>, List<TouchConfigValue>> e : touchOptions.entrySet()) {
+            dest.writeString(e.getKey().first.name());
+            dest.writeString(e.getKey().second.name());
+            dest.writeList(e.getValue());
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PBWInstallHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PBWInstallHandler.java
new file mode 100644
index 0000000..9fa65d9
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PBWInstallHandler.java
@@ -0,0 +1,213 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, Carsten Pfeiffer, Daniele
+    Gobbetti, Uwe Hermann
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.pebble;
+
+import android.content.Context;
+import android.net.Uri;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Writer;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.InstallActivity;
+import nodomain.freeyourgadget.gadgetbridge.activities.appmanager.AppManagerActivity;
+import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.model.GenericItem;
+import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.PebbleUtils;
+
+public class PBWInstallHandler implements InstallHandler {
+    private static final Logger LOG = LoggerFactory.getLogger(PBWInstallHandler.class);
+
+    private final Context mContext;
+    private PBWReader mPBWReader;
+    private final Uri mUri;
+
+    public PBWInstallHandler(Uri uri, Context context) {
+        mContext = context;
+        mUri = uri;
+    }
+
+    @Override
+    public void validateInstallation(InstallActivity installActivity, GBDevice device) {
+        if (device.isBusy()) {
+            installActivity.setInfoText(device.getBusyTask());
+            installActivity.setInstallEnabled(false);
+            return;
+        }
+        if (device.getType() != DeviceType.PEBBLE || !device.isConnected()) {
+            installActivity.setInfoText("Element cannot be installed");
+            installActivity.setInstallEnabled(false);
+            return;
+        }
+
+        String platformName = PebbleUtils.getPlatformName(device.getModel());
+
+        try {
+            mPBWReader = new PBWReader(mUri, mContext, platformName);
+        } catch (FileNotFoundException e) {
+            installActivity.setInfoText("file not found");
+            installActivity.setInstallEnabled(false);
+            return;
+        } catch (IOException e) {
+            installActivity.setInfoText("error reading file");
+            installActivity.setInstallEnabled(false);
+            return;
+        }
+
+        if (!mPBWReader.isValid()) {
+            installActivity.setInfoText("pbw/pbz is broken or incompatible with your Hardware or Firmware.");
+            installActivity.setInstallEnabled(false);
+            return;
+        }
+
+        GenericItem installItem = new GenericItem();
+        installItem.setIcon(R.drawable.ic_watchapp); // FIXME: do not set twice
+
+        if (mPBWReader.isFirmware()) {
+            installItem.setIcon(R.drawable.ic_firmware);
+
+            String hwRevision = mPBWReader.getHWRevision();
+            if (hwRevision != null && hwRevision.equals(device.getModel())) {
+                installItem.setName(mContext.getString(R.string.pbw_installhandler_pebble_firmware, ""));
+                installItem.setDetails(mContext.getString(R.string.pbwinstallhandler_correct_hw_revision));
+
+                installActivity.setInfoText(mContext.getString(R.string.firmware_install_warning, hwRevision));
+                installActivity.setInstallEnabled(true);
+            } else {
+                if (hwRevision != null) {
+                    installItem.setName(mContext.getString(R.string.pbw_installhandler_pebble_firmware, hwRevision));
+                    installItem.setDetails(mContext.getString(R.string.pbwinstallhandler_incorrect_hw_revision));
+                }
+                installActivity.setInfoText(mContext.getString(R.string.pbw_install_handler_hw_revision_mismatch));
+                installActivity.setInstallEnabled(false);
+            }
+        } else {
+            GBDeviceApp app = mPBWReader.getGBDeviceApp();
+            if (app != null) {
+                installItem.setName(app.getName());
+                installItem.setDetails(mContext.getString(R.string.pbwinstallhandler_app_item, app.getCreator(), app.getVersion()));
+                int drawable;
+                if (mPBWReader.isLanguage()) {
+                    drawable = R.drawable.ic_languagepack;
+                } else {
+                    switch (app.getType()) {
+                        case WATCHFACE:
+                            drawable = R.drawable.ic_watchface;
+                            break;
+                        case APP_ACTIVITYTRACKER:
+                            drawable = R.drawable.ic_activitytracker;
+                            break;
+                        default:
+                            drawable = R.drawable.ic_watchapp;
+                    }
+                }
+                installItem.setIcon(drawable);
+
+                installActivity.setInfoText(mContext.getString(R.string.app_install_info, app.getName(), app.getVersion(), app.getCreator()));
+                installActivity.setInstallEnabled(true);
+            } else {
+                installActivity.setInfoText(mContext.getString(R.string.pbw_install_handler_unable_to_install, mUri.getPath()));
+                installActivity.setInstallEnabled(false);
+            }
+        }
+
+        if (installItem.getName() != null) {
+            installActivity.setInstallItem(installItem);
+        }
+    }
+
+    @Override
+    public void onStartInstall(GBDevice device) {
+        if (mPBWReader.isFirmware() || mPBWReader.isLanguage()) {
+            return;
+        }
+
+        File destDir;
+        GBDeviceApp app = mPBWReader.getGBDeviceApp();
+        try {
+            destDir = PebbleUtils.getPbwCacheDir();
+            destDir.mkdirs();
+            FileUtils.copyURItoFile(mContext, mUri, new File(destDir, app.getUUID().toString() + ".pbw"));
+
+            AppManagerActivity.addToAppOrderFile("pbwcacheorder.txt", app.getUUID());
+        } catch (IOException e) {
+            LOG.error("Installation failed: " + e.getMessage(), e);
+            return;
+        }
+
+        File outputFile = new File(destDir, app.getUUID().toString() + ".json");
+        Writer writer;
+        try {
+            writer = new BufferedWriter(new FileWriter(outputFile));
+        } catch (IOException e) {
+            LOG.error("Failed to open output file: " + e.getMessage(), e);
+            return;
+        }
+        try {
+            LOG.info(app.getJSON().toString());
+            JSONObject appJSON = app.getJSON();
+            JSONObject appKeysJSON = mPBWReader.getAppKeysJSON();
+            if (appKeysJSON != null) {
+                appJSON.put("appKeys", appKeysJSON);
+            }
+            writer.write(appJSON.toString());
+
+            writer.close();
+        } catch (IOException e) {
+            LOG.error("Failed to write to output file: " + e.getMessage(), e);
+        } catch (JSONException e) {
+            LOG.error(e.getMessage(), e);
+        }
+
+        InputStream jsConfigFile = mPBWReader.getInputStreamFile("pebble-js-app.js");
+        if (jsConfigFile != null) {
+            try {
+                outputFile = new File(destDir, app.getUUID().toString() + "_config.js");
+                FileUtils.copyStreamToFile(jsConfigFile, outputFile);
+            } catch (IOException e) {
+                LOG.error("Failed to open output file: " + e.getMessage(), e);
+            } finally {
+                try {
+                    jsConfigFile.close();
+                } catch (IOException e) {
+                }
+            }
+        }
+    }
+
+    @Override
+    public boolean isValid() {
+        // always pretend it is valid, as we can't know yet about hw/fw version
+        return true;
+    }
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PBWReader.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PBWReader.java
new file mode 100644
index 0000000..d4f5cdc
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PBWReader.java
@@ -0,0 +1,358 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, Carsten Pfeiffer, Daniele
+    Gobbetti, Uwe Hermann
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.pebble;
+
+import android.content.Context;
+import android.net.Uri;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.ByteArrayOutputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.PebbleProtocol;
+import nodomain.freeyourgadget.gadgetbridge.util.UriHelper;
+
+public class PBWReader {
+    private static final Logger LOG = LoggerFactory.getLogger(PBWReader.class);
+    private static final HashMap<String, Byte> appFileTypesMap;
+    private static final HashMap<String, Byte> fwFileTypesMap;
+
+    static {
+        appFileTypesMap = new HashMap<>();
+        appFileTypesMap.put("application", PebbleProtocol.PUTBYTES_TYPE_BINARY);
+        appFileTypesMap.put("resources", PebbleProtocol.PUTBYTES_TYPE_RESOURCES);
+        appFileTypesMap.put("worker", PebbleProtocol.PUTBYTES_TYPE_WORKER);
+    }
+
+    static {
+        fwFileTypesMap = new HashMap<>();
+        fwFileTypesMap.put("firmware", PebbleProtocol.PUTBYTES_TYPE_FIRMWARE);
+        fwFileTypesMap.put("resources", PebbleProtocol.PUTBYTES_TYPE_SYSRESOURCES);
+    }
+
+    private final UriHelper uriHelper;
+    private GBDeviceApp app;
+    private ArrayList<PebbleInstallable> pebbleInstallables = null;
+    private boolean isFirmware = false;
+    private boolean isLanguage = false;
+    private boolean isValid = false;
+    private String hwRevision = null;
+    private short mSdkVersion;
+    private short mAppVersion;
+    private int mIconId;
+    private int mFlags;
+
+    private JSONObject mAppKeys = null;
+
+    public PBWReader(Uri uri, Context context, String platform) throws IOException {
+        uriHelper = UriHelper.get(uri, context);
+
+        if (uriHelper.getFileName().endsWith(".pbl")) {
+            STM32CRC stm32crc = new STM32CRC();
+            try (InputStream fin = uriHelper.openInputStream()) {
+                byte[] buf = new byte[2000];
+                while (fin.available() > 0) {
+                    int count = fin.read(buf);
+                    stm32crc.addData(buf, count);
+                }
+            }
+            int crc = stm32crc.getResult();
+            // language file
+            app = new GBDeviceApp(UUID.randomUUID(), "Language File", "unknown", "unknown", GBDeviceApp.Type.UNKNOWN);
+            pebbleInstallables = new ArrayList<>();
+            pebbleInstallables.add(new PebbleInstallable("lang", (int) uriHelper.getFileSize(), crc, PebbleProtocol.PUTBYTES_TYPE_FILE));
+
+            isValid = true;
+            isLanguage = true;
+            return;
+        }
+
+        String platformDir = "";
+        if (!uriHelper.getFileName().endsWith(".pbz")) {
+            platformDir = determinePlatformDir(uriHelper, platform);
+
+            if (platform.equals("chalk") && platformDir.equals("")) {
+                return;
+            }
+        }
+
+        LOG.info("using platformdir: '" + platformDir + "'");
+        String appName = null;
+        String appCreator = null;
+        String appVersion = null;
+        UUID appUUID = null;
+
+        ZipEntry ze;
+        pebbleInstallables = new ArrayList<>();
+        byte[] buffer = new byte[1024];
+        int count;
+        try (ZipInputStream zis = new ZipInputStream(uriHelper.openInputStream())) {
+            while ((ze = zis.getNextEntry()) != null) {
+                String fileName = ze.getName();
+                if (fileName.equals(platformDir + "manifest.json")) {
+                    long bytes = ze.getSize();
+                    if (bytes > 8192) // that should be too much
+                        break;
+
+                    ByteArrayOutputStream baos = new ByteArrayOutputStream();
+                    while ((count = zis.read(buffer)) != -1) {
+                        baos.write(buffer, 0, count);
+                    }
+
+                    String jsonString = baos.toString();
+                    try {
+                        JSONObject json = new JSONObject(jsonString);
+                        HashMap<String, Byte> fileTypeMap;
+
+                        try {
+                            JSONObject firmware = json.getJSONObject("firmware");
+                            fileTypeMap = fwFileTypesMap;
+                            isFirmware = true;
+                            hwRevision = firmware.getString("hwrev");
+                        } catch (JSONException e) {
+                            fileTypeMap = appFileTypesMap;
+                            isFirmware = false;
+                        }
+                        for (Map.Entry<String, Byte> entry : fileTypeMap.entrySet()) {
+                            try {
+                                JSONObject jo = json.getJSONObject(entry.getKey());
+                                String name = jo.getString("name");
+                                int size = jo.getInt("size");
+                                long crc = jo.getLong("crc");
+                                byte type = entry.getValue();
+                                pebbleInstallables.add(new PebbleInstallable(platformDir + name, size, (int) crc, type));
+                                LOG.info("found file to install: " + platformDir + name);
+                                isValid = true;
+                            } catch (JSONException e) {
+                                // not fatal
+                            }
+                        }
+                    } catch (JSONException e) {
+                        // no JSON at all that is a problem
+                        isValid = false;
+                        e.printStackTrace();
+                        break;
+                    }
+
+                } else if (fileName.equals("appinfo.json")) {
+                    long bytes = ze.getSize();
+                    if (bytes > 500000) {
+                        LOG.warn(fileName + " exeeds maximum of 500000 bytes");
+                        // that should be too much
+                        break;
+                    }
+
+                    ByteArrayOutputStream baos = new ByteArrayOutputStream();
+                    while ((count = zis.read(buffer)) != -1) {
+                        baos.write(buffer, 0, count);
+                    }
+
+                    String jsonString = baos.toString();
+                    try {
+                        JSONObject json = new JSONObject(jsonString);
+                        appName = json.getString("shortName");
+                        appCreator = json.getString("companyName");
+                        appVersion = json.getString("versionLabel");
+                        appUUID = UUID.fromString(json.getString("uuid"));
+                        if (json.has("appKeys")) {
+                            mAppKeys = json.getJSONObject("appKeys");
+                            LOG.info("found appKeys:" + mAppKeys.toString());
+                        }
+                    } catch (JSONException e) {
+                        isValid = false;
+                        e.printStackTrace();
+                        break;
+                    }
+                } else if (fileName.equals(platformDir + "pebble-app.bin")) {
+                    zis.read(buffer, 0, 108);
+                    byte[] tmp_buf = new byte[32];
+                    ByteBuffer buf = ByteBuffer.wrap(buffer);
+                    buf.order(ByteOrder.LITTLE_ENDIAN);
+                    buf.getLong();  // header, TODO: verify
+                    buf.getShort(); // struct version, TODO: verify
+                    mSdkVersion = buf.getShort();
+                    mAppVersion = buf.getShort();
+                    buf.getShort(); // size
+                    buf.getInt(); // offset
+                    buf.getInt(); // crc
+                    buf.get(tmp_buf, 0, 32); // app name
+                    buf.get(tmp_buf, 0, 32); // author
+                    mIconId = buf.getInt();
+                    LOG.info("got icon id from pebble-app.bin: " + mIconId);
+                    buf.getInt(); // symbol table addr
+                    mFlags = buf.getInt();
+                    LOG.info("got flags from pebble-app.bin: " + mFlags);
+                    // more follows but, not interesting for us
+                }
+            }
+            if (appUUID != null && appName != null && appCreator != null && appVersion != null) {
+                GBDeviceApp.Type appType = GBDeviceApp.Type.APP_GENERIC;
+
+                if ((mFlags & 16) == 16) {
+                    appType = GBDeviceApp.Type.APP_ACTIVITYTRACKER;
+                } else if ((mFlags & 1) == 1) {
+                    appType = GBDeviceApp.Type.WATCHFACE;
+                }
+                app = new GBDeviceApp(appUUID, appName, appCreator, appVersion, appType);
+            }
+            else if (!isFirmware) {
+                isValid = false;
+            }
+        }
+    }
+
+    /**
+     * Determines the platform dir to use for the given uri and platform.
+     * @param uriHelper
+     * @param platform
+     * @return the platform dir to use
+     * @throws IOException
+     */
+    private String determinePlatformDir(UriHelper uriHelper, String platform) throws IOException {
+        String platformDir = "";
+
+        /*
+         * for aplite and basalt it is possible to install 2.x apps which have no subfolder
+         * we still prefer the subfolders if present.
+         * chalk needs to be its subfolder
+         */
+        String[] platformDirs;
+        switch (platform) {
+            case "basalt":
+                platformDirs = new String[]{"basalt/"};
+                break;
+            case "chalk":
+                platformDirs = new String[]{"chalk/"};
+                break;
+            case "diorite":
+                platformDirs = new String[]{"diorite/", "aplite/"};
+                break;
+            case "emery":
+                platformDirs = new String[]{"emery/", "basalt/"};
+                break;
+            default:
+                platformDirs = new String[]{"aplite/"};
+        }
+
+        for (String dir : platformDirs) {
+            try (ZipInputStream zis = new ZipInputStream(uriHelper.openInputStream())) {
+                ZipEntry ze;
+                while ((ze = zis.getNextEntry()) != null) {
+                    if (ze.getName().startsWith(dir)) {
+                        return dir;
+                    }
+                }
+            }
+        }
+        return platformDir;
+    }
+
+    public boolean isFirmware() {
+        return isFirmware;
+    }
+
+    public boolean isLanguage() {
+        return isLanguage;
+    }
+
+    public boolean isValid() {
+        return isValid;
+    }
+
+    public GBDeviceApp getGBDeviceApp() {
+        return app;
+    }
+
+    public InputStream getInputStreamFile(String filename) {
+        if (isLanguage) {
+            try {
+                return uriHelper.openInputStream();
+            } catch (FileNotFoundException e) {
+                LOG.warn("file not found: " + e);
+                return null;
+            }
+        }
+        ZipInputStream zis = null;
+        ZipEntry ze;
+        try {
+            zis = new ZipInputStream(uriHelper.openInputStream());
+            while ((ze = zis.getNextEntry()) != null) {
+                if (ze.getName().equals(filename)) {
+                    return zis; // return WITHOUT closing the stream!
+                }
+            }
+            zis.close();
+        } catch (Throwable e) {
+            try {
+                if (zis != null) {
+                    zis.close();
+                }
+            } catch (IOException e1) {
+                // ignore
+            }
+            e.printStackTrace();
+        }
+        return null;
+    }
+
+    public PebbleInstallable[] getPebbleInstallables() {
+        if (pebbleInstallables == null) {
+            return null;
+        }
+        return pebbleInstallables.toArray(new PebbleInstallable[pebbleInstallables.size()]);
+    }
+
+    public String getHWRevision() {
+        return hwRevision;
+    }
+
+    public short getSdkVersion() {
+        return mSdkVersion;
+    }
+
+    public short getAppVersion() {
+        return mAppVersion;
+    }
+
+    public int getFlags() {
+        return mFlags;
+    }
+
+    public int getIconId() {
+        return mIconId;
+    }
+
+    public JSONObject getAppKeysJSON() {
+        return mAppKeys;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebbleColor.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebbleColor.java
new file mode 100644
index 0000000..4e9fa9a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebbleColor.java
@@ -0,0 +1,88 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.pebble;
+
+public final class PebbleColor {
+
+    public static final byte Black = (byte) 0b11000000;
+    public static final byte OxfordBlue = (byte) 0b11000001;
+    public static final byte DukeBlue = (byte) 0b11000010;
+    public static final byte Blue = (byte) 0b11000011;
+    public static final byte DarkGreen = (byte) 0b11000100;
+    public static final byte MidnightGreen = (byte) 0b11000101;
+    public static final byte CobaltBlue = (byte) 0b11000110;
+    public static final byte BlueMoon = (byte) 0b11000111;
+    public static final byte IslamicGreen = (byte) 0b11001000;
+    public static final byte JaegerGreen = (byte) 0b11001001;
+    public static final byte TiffanyBlue = (byte) 0b11001010;
+    public static final byte VividCerulean = (byte) 0b11001011;
+    public static final byte Green = (byte) 0b11001100;
+    public static final byte Malachite = (byte) 0b11001101;
+    public static final byte MediumSpringGreen = (byte) 0b11001110;
+    public static final byte Cyan = (byte) 0b11001111;
+    public static final byte BulgarianRose = (byte) 0b11010000;
+    public static final byte ImperialPurple = (byte) 0b11010001;
+    public static final byte Indigo = (byte) 0b11010010;
+    public static final byte ElectricUltramarine = (byte) 0b11010011;
+    public static final byte ArmyGreen = (byte) 0b11010100;
+    public static final byte DarkGray = (byte) 0b11010101;
+    public static final byte Liberty = (byte) 0b11010110;
+    public static final byte VeryLightBlue = (byte) 0b11010111;
+    public static final byte KellyGreen = (byte) 0b11011000;
+    public static final byte MayGreen = (byte) 0b11011001;
+    public static final byte CadetBlue = (byte) 0b11011010;
+    public static final byte PictonBlue = (byte) 0b11011011;
+    public static final byte BrightGreen = (byte) 0b11011100;
+    public static final byte ScreaminGreen = (byte) 0b11011101;
+    public static final byte MediumAquamarine = (byte) 0b11011110;
+    public static final byte ElectricBlue = (byte) 0b11011111;
+    public static final byte DarkCandyAppleRed = (byte) 0b11100000;
+    public static final byte JazzberryJam = (byte) 0b11100001;
+    public static final byte Purple = (byte) 0b11100010;
+    public static final byte VividViolet = (byte) 0b11100011;
+    public static final byte WindsorTan = (byte) 0b11100100;
+    public static final byte RoseVale = (byte) 0b11100101;
+    public static final byte Purpureus = (byte) 0b11100110;
+    public static final byte LavenderIndigo = (byte) 0b11100111;
+    public static final byte Limerick = (byte) 0b11101000;
+    public static final byte Brass = (byte) 0b11101001;
+    public static final byte LightGray = (byte) 0b11101010;
+    public static final byte BabyBlueEyes = (byte) 0b11101011;
+    public static final byte SpringBud = (byte) 0b11101100;
+    public static final byte Inchworm = (byte) 0b11101101;
+    public static final byte MintGreen = (byte) 0b11101110;
+    public static final byte Celeste = (byte) 0b11101111;
+    public static final byte Red = (byte) 0b11110000;
+    public static final byte Folly = (byte) 0b11110001;
+    public static final byte FashionMagenta = (byte) 0b11110010;
+    public static final byte Magenta = (byte) 0b11110011;
+    public static final byte Orange = (byte) 0b11110100;
+    public static final byte SunsetOrange = (byte) 0b11110101;
+    public static final byte BrilliantRose = (byte) 0b11110110;
+    public static final byte ShockingPink = (byte) 0b11110111;
+    public static final byte ChromeYellow = (byte) 0b11111000;
+    public static final byte Rajah = (byte) 0b11111001;
+    public static final byte Melon = (byte) 0b11111010;
+    public static final byte RichBrilliantLavender = (byte) 0b11111011;
+    public static final byte Yellow = (byte) 0b11111100;
+    public static final byte Icterine = (byte) 0b11111101;
+    public static final byte PastelYellow = (byte) 0b11111110;
+    public static final byte White = (byte) 0b11111111;
+    public static final byte Clear = (byte) 0b00000000;
+
+}
+
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebbleCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebbleCoordinator.java
new file mode 100644
index 0000000..ac07249
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebbleCoordinator.java
@@ -0,0 +1,292 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, Arjan Schrijver, Carsten
+    Pfeiffer, Damien Gaignon, Daniel Dakhno, Daniele Gobbetti, Jos茅 Rebelo,
+    Matthieu Baerts, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.pebble;
+
+import android.app.Activity;
+import android.content.Context;
+import android.net.Uri;
+
+import androidx.annotation.DrawableRes;
+import androidx.annotation.NonNull;
+import androidx.annotation.StringRes;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+import java.util.regex.Pattern;
+
+import de.greenrobot.dao.query.QueryBuilder;
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.GBException;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.appmanager.AppManagerActivity;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettings;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsScreen;
+import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLClassicDeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminSettingsCustomizer;
+import nodomain.freeyourgadget.gadgetbridge.entities.AbstractActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.entities.PebbleHealthActivityOverlayDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.PebbleHealthActivitySampleDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.PebbleMisfitSampleDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.PebbleMorpheuzSampleDao;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.PebbleSupport;
+import nodomain.freeyourgadget.gadgetbridge.util.PebbleUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.preferences.DevicePrefs;
+
+public class PebbleCoordinator extends AbstractBLClassicDeviceCoordinator {
+    private static final String BG_JS_ENABLED = "pebble_enable_background_javascript";
+    private static final boolean BG_JS_ENABLED_DEFAULT = false;
+
+    public PebbleCoordinator() {
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("Pebble.*");
+    }
+
+    @Override
+    public Class<? extends Activity> getPairingActivity() {
+        return PebblePairingActivity.class;
+    }
+
+    @Override
+    protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
+        Long deviceId = device.getId();
+        QueryBuilder<?> qb = session.getPebbleHealthActivitySampleDao().queryBuilder();
+        qb.where(PebbleHealthActivitySampleDao.Properties.DeviceId.eq(deviceId)).buildDelete().executeDeleteWithoutDetachingEntities();
+        qb = session.getPebbleHealthActivityOverlayDao().queryBuilder();
+        qb.where(PebbleHealthActivityOverlayDao.Properties.DeviceId.eq(deviceId)).buildDelete().executeDeleteWithoutDetachingEntities();
+        qb = session.getPebbleMisfitSampleDao().queryBuilder();
+        qb.where(PebbleMisfitSampleDao.Properties.DeviceId.eq(deviceId)).buildDelete().executeDeleteWithoutDetachingEntities();
+        qb = session.getPebbleMorpheuzSampleDao().queryBuilder();
+        qb.where(PebbleMorpheuzSampleDao.Properties.DeviceId.eq(deviceId)).buildDelete().executeDeleteWithoutDetachingEntities();
+    }
+
+    @Override
+    public SampleProvider<? extends AbstractActivitySample> getSampleProvider(GBDevice device, DaoSession session) {
+        DevicePrefs prefs = GBApplication.getDevicePrefs(device);
+        int activityTracker = prefs.getInt("pebble_activitytracker", SampleProvider.PROVIDER_PEBBLE_HEALTH);
+        switch (activityTracker) {
+            case SampleProvider.PROVIDER_PEBBLE_HEALTH:
+                return new PebbleHealthSampleProvider(device, session);
+            case SampleProvider.PROVIDER_PEBBLE_MISFIT:
+                return new PebbleMisfitSampleProvider(device, session);
+            case SampleProvider.PROVIDER_PEBBLE_MORPHEUZ:
+                return new PebbleMorpheuzSampleProvider(device, session);
+            default:
+                return new PebbleHealthSampleProvider(device, session);
+        }
+    }
+
+    @Override
+    public InstallHandler findInstallHandler(Uri uri, Context context) {
+        PBWInstallHandler installHandler = new PBWInstallHandler(uri, context);
+        return installHandler.isValid() ? installHandler : null;
+    }
+
+    @Override
+    public boolean supportsFlashing() { return true; }
+
+    @Override
+    public boolean supportsActivityDataFetching() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsActivityTracking() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsScreenshots(final GBDevice device) {
+        return true;
+    }
+
+    @Override
+    public int getAlarmSlotCount(GBDevice device) {
+        return 0;
+    }
+
+    @Override
+    public int getCannedRepliesSlotCount(final GBDevice device) {
+        return 16;
+    }
+
+    @Override
+    public boolean supportsHeartRateMeasurement(GBDevice device) {
+        return PebbleUtils.hasHRM(device.getModel());
+    }
+
+    @Override
+    public String getManufacturer() {
+        return "Pebble";
+    }
+
+    @Override
+    public boolean supportsAppsManagement(final GBDevice device) {
+        return true;
+    }
+
+    @Override
+    public Class<? extends Activity> getAppsManagementActivity() {
+        return AppManagerActivity.class;
+    }
+
+    @Override
+    public File getAppCacheDir() throws IOException {
+        return PebbleUtils.getPbwCacheDir();
+    }
+
+    @Override
+    public String getAppCacheSortFilename() {
+        return "pbwcacheorder.txt";
+    }
+
+    @Override
+    public String getAppFileExtension() {
+        return ".pbw";
+    }
+
+    @Override
+    public boolean supportsAppListFetching() {
+        List<GBDevice> devices = GBApplication.app().getDeviceManager().getSelectedDevices();
+        for(GBDevice device : devices){
+            if(device.getType() == DeviceType.PEBBLE){
+                if (device.getFirmwareVersion() != null) {
+                    return PebbleUtils.getFwMajor(device.getFirmwareVersion()) < 3;
+                }
+            }
+        }
+
+        return false;
+    }
+
+    @Override
+    public boolean supportsAppReordering() {
+        List<GBDevice> devices = GBApplication.app().getDeviceManager().getSelectedDevices();
+        for(GBDevice device : devices){
+            if(device.getType() == DeviceType.PEBBLE){
+                if (device.getFirmwareVersion() != null) {
+                    return PebbleUtils.getFwMajor(device.getFirmwareVersion()) >= 3;
+                }
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public boolean supportsCalendarEvents() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsRealtimeData() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsWeather() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsFindDevice() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsMusicInfo() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsUnicodeEmojis() {
+        return true;
+    }
+
+    @Override
+    public DeviceSpecificSettings getDeviceSpecificSettings(final GBDevice device) {
+        final DeviceSpecificSettings deviceSpecificSettings = new DeviceSpecificSettings();
+
+        final List<Integer> notifications = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.CALLS_AND_NOTIFICATIONS);
+        notifications.add(R.xml.devicesettings_autoremove_notifications);
+        notifications.add(R.xml.devicesettings_pebble_calls_notifications);
+        notifications.add(R.xml.devicesettings_canned_reply_16);
+        notifications.add(R.xml.devicesettings_canned_dismisscall_16);
+        notifications.add(R.xml.devicesettings_transliteration);
+
+        final List<Integer> calendar = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.CALENDAR);
+        calendar.add(R.xml.devicesettings_sync_calendar);
+        calendar.add(R.xml.devicesettings_pebble_calendar);
+
+        final List<Integer> activity = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.ACTIVITY_INFO);
+        activity.add(R.xml.devicesettings_pebble_activity);
+
+        final List<Integer> dev = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DEVELOPER);
+        dev.add(R.xml.devicesettings_pebble_developer);
+
+        return deviceSpecificSettings;
+    }
+
+    @Override
+    public DeviceSpecificSettingsCustomizer getDeviceSpecificSettingsCustomizer(GBDevice device) {
+        return new PebbleSettingsCustomizer();
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return PebbleSupport.class;
+    }
+
+
+    @Override
+    @StringRes
+    public int getDeviceNameResource() {
+        return R.string.devicetype_pebble;
+    }
+
+
+    @Override
+    @DrawableRes
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_pebble;
+    }
+
+    @Override
+    @DrawableRes
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_pebble_disabled;
+    }
+
+    public boolean isBackgroundJsEnabled(final GBDevice device) {
+        DevicePrefs deviceSpecificPreferences = GBApplication.getDevicePrefs(device);
+        return deviceSpecificPreferences.getBoolean(BG_JS_ENABLED, BG_JS_ENABLED_DEFAULT);
+    }
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebbleHealthSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebbleHealthSampleProvider.java
new file mode 100644
index 0000000..21013b1
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebbleHealthSampleProvider.java
@@ -0,0 +1,149 @@
+/*  Copyright (C) 2016-2024 Andreas Shimokawa, Carsten Pfeiffer, Daniele
+    Gobbetti
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.pebble;
+
+import androidx.annotation.NonNull;
+
+import java.util.Collections;
+import java.util.List;
+
+import de.greenrobot.dao.AbstractDao;
+import de.greenrobot.dao.Property;
+import de.greenrobot.dao.query.QueryBuilder;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.AbstractSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.entities.PebbleHealthActivityOverlay;
+import nodomain.freeyourgadget.gadgetbridge.entities.PebbleHealthActivityOverlayDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.PebbleHealthActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.entities.PebbleHealthActivitySampleDao;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+
+public class PebbleHealthSampleProvider extends AbstractSampleProvider<PebbleHealthActivitySample> {
+    public static final int TYPE_LIGHT_SLEEP = 1;
+    public static final int TYPE_DEEP_SLEEP = 2;
+    public static final int TYPE_LIGHT_NAP = 3;
+    public static final int TYPE_DEEP_NAP = 4;
+    public static final int TYPE_WALK = 5;
+    public static final int TYPE_RUN = 6;
+    public static final int TYPE_ACTIVITY = -1;
+
+
+    protected final float movementDivisor = 8000f;
+
+    public PebbleHealthSampleProvider(GBDevice device, DaoSession session) {
+        super(device, session);
+    }
+
+    @NonNull
+    @Override
+    public List<PebbleHealthActivitySample> getAllActivitySamples(int timestamp_from, int timestamp_to) {
+        List<PebbleHealthActivitySample> samples = super.getGBActivitySamples(timestamp_from, timestamp_to);
+
+        Device dbDevice = DBHelper.findDevice(getDevice(), getSession());
+        if (dbDevice == null) {
+            // no device, no samples
+            return Collections.emptyList();
+        }
+
+        QueryBuilder<PebbleHealthActivityOverlay> qb = getSession().getPebbleHealthActivityOverlayDao().queryBuilder();
+
+        // I assume it returns the records by id ascending ... (last overlay is dominant)
+        qb.where(PebbleHealthActivityOverlayDao.Properties.DeviceId.eq(dbDevice.getId()), PebbleHealthActivityOverlayDao.Properties.TimestampTo.ge(timestamp_from))
+                .where(PebbleHealthActivityOverlayDao.Properties.TimestampFrom.le(timestamp_to));
+        List<PebbleHealthActivityOverlay> overlayRecords = qb.build().list();
+
+        for (PebbleHealthActivityOverlay overlay : overlayRecords) {
+            for (PebbleHealthActivitySample sample : samples) {
+                if (overlay.getTimestampFrom() <= sample.getTimestamp() && sample.getTimestamp() < overlay.getTimestampTo()) {
+                    // patch in the raw kind
+                    sample.setRawKind(overlay.getRawKind());
+                }
+            }
+        }
+        detachFromSession();
+        return samples;
+    }
+
+    @Override
+    public AbstractDao<PebbleHealthActivitySample, ?> getSampleDao() {
+        return getSession().getPebbleHealthActivitySampleDao();
+    }
+
+    @NonNull
+    @Override
+    protected Property getTimestampSampleProperty() {
+        return PebbleHealthActivitySampleDao.Properties.Timestamp;
+    }
+
+    @Override
+    protected Property getRawKindSampleProperty() {
+        return null;
+        // it is still in the database just hide it for now. remove these two commented lines later
+        //return PebbleHealthActivitySampleDao.Properties.RawKind;
+    }
+
+    @NonNull
+    @Override
+    protected Property getDeviceIdentifierSampleProperty() {
+        return PebbleHealthActivitySampleDao.Properties.DeviceId;
+    }
+
+    @Override
+    public PebbleHealthActivitySample createActivitySample() {
+        return new PebbleHealthActivitySample();
+    }
+
+    @Override
+    public ActivityKind normalizeType(int rawType) {
+        switch (rawType) {
+            case TYPE_DEEP_NAP:
+            case TYPE_DEEP_SLEEP:
+                return ActivityKind.DEEP_SLEEP;
+            case TYPE_LIGHT_NAP:
+            case TYPE_LIGHT_SLEEP:
+                return ActivityKind.LIGHT_SLEEP;
+            case TYPE_ACTIVITY:
+            case TYPE_WALK:
+            case TYPE_RUN:
+                return ActivityKind.ACTIVITY;
+            default:
+                return ActivityKind.UNKNOWN;
+        }
+    }
+
+    @Override
+    public int toRawActivityKind(ActivityKind activityKind) {
+        switch (activityKind) {
+            case DEEP_SLEEP:
+                return TYPE_DEEP_SLEEP;
+            case LIGHT_SLEEP:
+                return TYPE_LIGHT_SLEEP;
+            case ACTIVITY:
+            default:
+                return TYPE_ACTIVITY;
+        }
+    }
+
+    @Override
+    public float normalizeIntensity(int rawIntensity) {
+        return rawIntensity / movementDivisor;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebbleIconID.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebbleIconID.java
new file mode 100644
index 0000000..59ff076
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebbleIconID.java
@@ -0,0 +1,125 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.pebble;
+
+public final class PebbleIconID {
+
+    public static final int NOTIFICATION_GENERIC = 1;
+    public static final int TIMELINE_MISSED_CALL = 2;
+    public static final int NOTIFICATION_REMINDER = 3;
+    public static final int NOTIFICATION_FLAG = 4;
+    public static final int NOTIFICATION_WHATSAPP = 5;
+    public static final int NOTIFICATION_TWITTER = 6;
+    public static final int NOTIFICATION_TELEGRAM = 7;
+    public static final int NOTIFICATION_GOOGLE_HANGOUTS = 8;
+    public static final int NOTIFICATION_GMAIL = 9;
+    public static final int NOTIFICATION_FACEBOOK_MESSENGER = 10;
+    public static final int NOTIFICATION_FACEBOOK = 11;
+    public static final int AUDIO_CASSETTE = 12;
+    public static final int ALARM_CLOCK = 13;
+    public static final int TIMELINE_WEATHER = 14;
+    public static final int TIMELINE_SUN = 16;
+    public static final int TIMELINE_SPORTS = 17;
+    public static final int GENERIC_EMAIL = 19;
+    public static final int AMERICAN_FOOTBALL = 20;
+    public static final int TIMELINE_CALENDAR = 21;
+    public static final int TIMELINE_BASEBALL = 22;
+    public static final int BIRTHDAY_EVENT = 23;
+    public static final int CAR_RENTAL = 24;
+    public static final int CLOUDY_DAY = 25;
+    public static final int CRICKET_GAME = 26;
+    public static final int DINNER_RESERVATION = 27;
+    public static final int GENERIC_WARNING = 28;
+    public static final int GLUCOSE_MONITOR = 29;
+    public static final int HOCKEY_GAME = 30;
+    public static final int HOTEL_RESERVATION = 31;
+    public static final int LIGHT_RAIN = 32;
+    public static final int LIGHT_SNOW = 33;
+    public static final int MOVIE_EVENT = 34;
+    public static final int MUSIC_EVENT = 35;
+    public static final int NEWS_EVENT = 36;
+    public static final int PARTLY_CLOUDY = 37;
+    public static final int PAY_BILL = 38;
+    public static final int RADIO_SHOW = 39;
+    public static final int SCHEDULED_EVENT = 40;
+    public static final int SOCCER_GAME = 41;
+    public static final int STOCKS_EVENT = 42;
+    public static final int RESULT_DELETED = 43;
+    public static final int CHECK_INTERNET_CONNECTION = 44;
+    public static final int GENERIC_SMS = 45;
+    public static final int RESULT_MUTE = 46;
+    public static final int RESULT_SENT = 47;
+    public static final int WATCH_DISCONNECTED = 48;
+    public static final int DURING_PHONE_CALL = 49;
+    public static final int TIDE_IS_HIGH = 50;
+    public static final int RESULT_DISMISSED = 51;
+    public static final int HEAVY_RAIN = 52;
+    public static final int HEAVY_SNOW = 53;
+    public static final int SCHEDULED_FLIGHT = 54;
+    public static final int GENERIC_CONFIRMATION = 55;
+    public static final int DAY_SEPARATOR = 56;
+    public static final int NO_EVENTS = 57;
+    public static final int NOTIFICATION_BLACKBERRY_MESSENGER = 58;
+    public static final int NOTIFICATION_INSTAGRAM = 59;
+    public static final int NOTIFICATION_MAILBOX = 60;
+    public static final int NOTIFICATION_GOOGLE_INBOX = 61;
+    public static final int RESULT_FAILED = 62;
+    public static final int GENERIC_QUESTION = 63;
+    public static final int NOTIFICATION_OUTLOOK = 64;
+    public static final int RAINING_AND_SNOWING = 65;
+    public static final int REACHED_FITNESS_GOAL = 66;
+    public static final int NOTIFICATION_LINE = 67;
+    public static final int NOTIFICATION_SKYPE = 68;
+    public static final int NOTIFICATION_SNAPCHAT = 69;
+    public static final int NOTIFICATION_VIBER = 70;
+    public static final int NOTIFICATION_WECHAT = 71;
+    public static final int NOTIFICATION_YAHOO_MAIL = 72;
+    public static final int TV_SHOW = 73;
+    public static final int BASKETBALL = 74;
+    public static final int DISMISSED_PHONE_CALL = 75;
+    public static final int NOTIFICATION_GOOGLE_MESSENGER = 76;
+    public static final int NOTIFICATION_HIPCHAT = 77;
+    public static final int INCOMING_PHONE_CALL = 78;
+    public static final int NOTIFICATION_KAKAOTALK = 79;
+    public static final int NOTIFICATION_KIK = 80;
+    public static final int NOTIFICATION_LIGHTHOUSE = 81;
+    public static final int LOCATION = 82;
+    public static final int SETTINGS = 83;
+    public static final int SUNRISE = 84;
+    public static final int SUNSET = 85;
+    public static final int RESULT_UNMUTE = 86;
+    public static final int RESULT_UNMUTE_ALT = 94;
+    public static final int DURING_PHONE_CALL_CENTERED = 95;
+    public static final int TIMELINE_EMPTY_CALENDAR = 96;
+    public static final int THUMBS_UP = 97;
+    public static final int ARROW_UP = 98;
+    public static final int ARROW_DOWN = 99;
+    public static final int ACTIVITY = 100;
+    public static final int SLEEP = 101;
+    public static final int REWARD_BAD = 102;
+    public static final int REWARD_GOOD = 103;
+    public static final int REWARD_AVERAGE = 104;
+    public static final int NOTIFICATION_FACETIME = 110;
+
+    // 4.x only from here
+    public static final int NOTIFICATION_AMAZON = 111;
+    public static final int NOTIFICATION_GOOGLE_MAPS = 112;
+    public static final int NOTIFICATION_GOOGLE_PHOTOS = 113;
+    public static final int NOTIFICATION_IOS_PHOTOS = 114;
+    public static final int NOTIFICATION_LINKEDIN = 115;
+    public static final int NOTIFICATION_SLACK = 116;
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebbleInstallable.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebbleInstallable.java
new file mode 100644
index 0000000..3c8a204
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebbleInstallable.java
@@ -0,0 +1,47 @@
+/*  Copyright (C) 2015-2024 Carsten Pfeiffer
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.pebble;
+
+public class PebbleInstallable {
+    final private byte type;
+    final private int crc;
+    final private String fileName;
+    final private int fileSize;
+
+    public PebbleInstallable(String fileName, int fileSize, int crc, byte type) {
+        this.fileName = fileName;
+        this.fileSize = fileSize;
+        this.crc = crc;
+        this.type = type;
+    }
+
+    public String getFileName() {
+        return fileName;
+    }
+
+    public int getFileSize() {
+        return fileSize;
+    }
+
+    public byte getType() {
+        return type;
+    }
+
+    public int getCRC() {
+        return crc;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebbleMisfitSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebbleMisfitSampleProvider.java
new file mode 100644
index 0000000..cbcb13a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebbleMisfitSampleProvider.java
@@ -0,0 +1,77 @@
+/*  Copyright (C) 2016-2024 Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.pebble;
+
+import androidx.annotation.NonNull;
+
+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.PebbleMisfitSample;
+import nodomain.freeyourgadget.gadgetbridge.entities.PebbleMisfitSampleDao;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+
+public class PebbleMisfitSampleProvider extends AbstractSampleProvider<PebbleMisfitSample> {
+
+    protected final float movementDivisor = 300f;
+
+    public PebbleMisfitSampleProvider(GBDevice device, DaoSession session) {
+        super(device, session);
+    }
+
+    @Override
+    public ActivityKind normalizeType(int rawType) {
+        return ActivityKind.fromCode(rawType);
+    }
+
+    @Override
+    public int toRawActivityKind(ActivityKind activityKind) {
+        return activityKind.getCode();
+    }
+
+    @Override
+    public float normalizeIntensity(int rawIntensity) {
+        return rawIntensity / movementDivisor;
+    }
+
+    @Override
+    public PebbleMisfitSample createActivitySample() {
+        return new PebbleMisfitSample();
+    }
+
+    @Override
+    public AbstractDao<PebbleMisfitSample, ?> getSampleDao() {
+        return getSession().getPebbleMisfitSampleDao();
+    }
+
+    @Override
+    protected Property getRawKindSampleProperty() {
+        return null;
+    }
+
+    @NonNull
+    protected Property getTimestampSampleProperty() {
+        return PebbleMisfitSampleDao.Properties.Timestamp;
+    }
+
+    @NonNull
+    protected Property getDeviceIdentifierSampleProperty() {
+        return PebbleMisfitSampleDao.Properties.DeviceId;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebbleMorpheuzSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebbleMorpheuzSampleProvider.java
new file mode 100644
index 0000000..a316284
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebbleMorpheuzSampleProvider.java
@@ -0,0 +1,80 @@
+/*  Copyright (C) 2016-2024 Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.pebble;
+
+import androidx.annotation.NonNull;
+
+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.PebbleMorpheuzSample;
+import nodomain.freeyourgadget.gadgetbridge.entities.PebbleMorpheuzSampleDao;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+
+public class PebbleMorpheuzSampleProvider extends AbstractSampleProvider<PebbleMorpheuzSample> {
+
+    protected float movementDivisor = 5000f;
+
+    public PebbleMorpheuzSampleProvider(GBDevice device, DaoSession session) {
+        super(device, session);
+    }
+
+    @Override
+    public AbstractDao<PebbleMorpheuzSample, ?> getSampleDao() {
+        return getSession().getPebbleMorpheuzSampleDao();
+    }
+
+    @NonNull
+    @Override
+    protected Property getTimestampSampleProperty() {
+        return PebbleMorpheuzSampleDao.Properties.Timestamp;
+    }
+
+    @Override
+    protected Property getRawKindSampleProperty() {
+        return null; // not supported
+    }
+
+    @NonNull
+    @Override
+    protected Property getDeviceIdentifierSampleProperty() {
+        return PebbleMorpheuzSampleDao.Properties.DeviceId;
+    }
+
+    @Override
+    public PebbleMorpheuzSample createActivitySample() {
+        return new PebbleMorpheuzSample();
+    }
+
+
+    @Override
+    public float normalizeIntensity(int rawIntensity) {
+        return rawIntensity / movementDivisor;
+    }
+
+    @Override
+    public ActivityKind normalizeType(int rawType) {
+        return ActivityKind.fromCode(rawType);
+    }
+
+    @Override
+    public int toRawActivityKind(ActivityKind activityKind) {
+        return activityKind.getCode();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebblePairingActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebblePairingActivity.java
new file mode 100644
index 0000000..b3b1849
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebblePairingActivity.java
@@ -0,0 +1,269 @@
+/*  Copyright (C) 2016-2024 Andreas B枚hler, Andreas Shimokawa, Carsten
+    Pfeiffer, Daniele Gobbetti, Jos茅 Rebelo, Taavi Eom盲e
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.pebble;
+
+import static nodomain.freeyourgadget.gadgetbridge.util.BondingUtil.STATE_DEVICE_CANDIDATE;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.annotation.NonNull;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+import de.greenrobot.dao.query.Query;
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBActivity;
+import nodomain.freeyourgadget.gadgetbridge.activities.ControlCenterv2;
+import nodomain.freeyourgadget.gadgetbridge.activities.discovery.DiscoveryActivityV2;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.entities.DeviceDao;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
+import nodomain.freeyourgadget.gadgetbridge.util.AndroidUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.BondingInterface;
+import nodomain.freeyourgadget.gadgetbridge.util.BondingUtil;
+import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+
+public class PebblePairingActivity extends AbstractGBActivity implements BondingInterface {
+    private static final Logger LOG = LoggerFactory.getLogger(PebblePairingActivity.class);
+    private final BroadcastReceiver pairingReceiver = BondingUtil.getPairingReceiver(this);
+    private final BroadcastReceiver bondingReceiver = BondingUtil.getBondingReceiver(this);
+
+    private TextView message;
+    private boolean isPairing;
+
+    private GBDeviceCandidate deviceCandidate;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_pebble_pairing);
+
+        message = findViewById(R.id.pebble_pair_message);
+        Intent intent = getIntent();
+        intent.setExtrasClassLoader(GBDeviceCandidate.class.getClassLoader());
+        deviceCandidate = intent.getParcelableExtra(DeviceCoordinator.EXTRA_DEVICE_CANDIDATE);
+
+        String macAddress = null;
+        if (deviceCandidate != null) {
+            macAddress = deviceCandidate.getMacAddress();
+        }
+
+        if (macAddress == null) {
+            Toast.makeText(this, getString(R.string.message_cannot_pair_no_mac), Toast.LENGTH_SHORT).show();
+            onBondingComplete(false);
+            return;
+        }
+
+        BluetoothDevice btDevice = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(macAddress);
+        if (btDevice == null) {
+            GB.toast(this, "No such Bluetooth Device: " + macAddress, Toast.LENGTH_LONG, GB.ERROR);
+            onBondingComplete(false);
+            return;
+        }
+
+        startBonding(btDevice);
+    }
+
+    private void startBonding(BluetoothDevice btDevice) {
+        isPairing = true;
+        message.setText(getString(R.string.pairing, btDevice.getAddress()));
+
+        GBDevice device;
+        if (BondingUtil.isLePebble(btDevice)) {
+            if (!GBApplication.getPrefs().getBoolean("pebble_force_le", false)) {
+                GB.toast(this, "Please switch on \"Always prefer BLE\" option in Pebble settings before pairing you Pebble LE", Toast.LENGTH_LONG, GB.ERROR);
+                onBondingComplete(false);
+                return;
+            }
+
+            device = getMatchingParentDeviceFromDBAndSetVolatileAddress(btDevice);
+            if (device == null) {
+                onBondingComplete(false);
+                return;
+            }
+
+            registerBroadcastReceivers();
+            BondingUtil.connectThenComplete(this, device);
+            return;
+        }
+
+        if (btDevice.getBondState() == BluetoothDevice.BOND_BONDED ||
+                btDevice.getBondState() == BluetoothDevice.BOND_BONDING) {
+            BondingUtil.connectThenComplete(this, deviceCandidate);
+        } else {
+            BondingUtil.tryBondThenComplete(this, deviceCandidate.getDevice(), deviceCandidate.getDevice().getAddress());
+        }
+    }
+
+    private void stopBonding() {
+        isPairing = false;
+        BondingUtil.stopBluetoothBonding(deviceCandidate.getDevice());
+    }
+
+    private GBDevice getMatchingParentDeviceFromDBAndSetVolatileAddress(BluetoothDevice btDevice) {
+        String expectedSuffix = btDevice.getName();
+        expectedSuffix = expectedSuffix.replace("Pebble-LE ", "");
+        expectedSuffix = expectedSuffix.replace("Pebble Time LE ", "");
+        expectedSuffix = expectedSuffix.substring(0, 2) + ":" + expectedSuffix.substring(2);
+        LOG.info("Trying to find a Pebble with BT address suffix " + expectedSuffix);
+        try (DBHandler dbHandler = GBApplication.acquireDB()) {
+            DaoSession session = dbHandler.getDaoSession();
+            DeviceDao deviceDao = session.getDeviceDao();
+            Query<Device> query = deviceDao.queryBuilder().where(DeviceDao.Properties.TypeName.eq("PEBBLE"), DeviceDao.Properties.Identifier.like("%" + expectedSuffix)).build();
+
+            List<Device> devices = query.list();
+            if (devices.size() == 0) {
+                GB.toast("Please pair your non-LE Pebble before pairing the LE one", Toast.LENGTH_SHORT, GB.INFO);
+                onBondingComplete(false);
+                return null;
+            } else if (devices.size() > 1) {
+                GB.toast("Can not match this Pebble LE to a unique device", Toast.LENGTH_SHORT, GB.INFO);
+                onBondingComplete(false);
+                return null;
+            }
+
+            GBDevice gbDevice = DeviceHelper.getInstance().toGBDevice(devices.get(0));
+            gbDevice.setVolatileAddress(btDevice.getAddress());
+            return gbDevice;
+        } catch (Exception e) {
+            GB.toast(getString(R.string.error_retrieving_devices_database), Toast.LENGTH_SHORT, GB.ERROR, e);
+            onBondingComplete(false);
+            return null;
+        }
+    }
+
+    @Override
+    public void onBondingComplete(boolean success) {
+        unregisterBroadcastReceivers();
+        if (success) {
+            startActivity(new Intent(this, ControlCenterv2.class).setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP));
+        } else {
+            startActivity(new Intent(this, DiscoveryActivityV2.class).setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP));
+        }
+
+        // If it's not a LE Pebble, initiate a connection when bonding is complete
+        if (!BondingUtil.isLePebble(getCurrentTarget().getDevice()) && success) {
+            BondingUtil.attemptToFirstConnect(getCurrentTarget().getDevice());
+        }
+        finish();
+    }
+
+    @Override
+    public GBDeviceCandidate getCurrentTarget() {
+        return this.deviceCandidate;
+    }
+
+    @Override
+    public String getMacAddress() {
+        return deviceCandidate.getDevice().getAddress();
+    }
+
+    @Override
+    public boolean getAttemptToConnect() {
+        return true;
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        super.onActivityResult(requestCode, resultCode, data);
+        BondingUtil.handleActivityResult(this, requestCode, resultCode, data);
+    }
+
+    @Override
+    protected void onSaveInstanceState(@NonNull Bundle outState) {
+        super.onSaveInstanceState(outState);
+        outState.putParcelable(STATE_DEVICE_CANDIDATE, deviceCandidate);
+    }
+
+    @Override
+    protected void onRestoreInstanceState(Bundle savedInstanceState) {
+        super.onRestoreInstanceState(savedInstanceState);
+        deviceCandidate = savedInstanceState.getParcelable(STATE_DEVICE_CANDIDATE);
+    }
+
+    @Override
+    protected void onStart() {
+        registerBroadcastReceivers();
+        super.onStart();
+    }
+
+    @Override
+    protected void onResume() {
+        registerBroadcastReceivers();
+        super.onResume();
+    }
+
+    @Override
+    protected void onDestroy() {
+        unregisterBroadcastReceivers();
+        if (isPairing) {
+            stopBonding();
+        }
+        super.onDestroy();
+    }
+
+    @Override
+    protected void onStop() {
+        unregisterBroadcastReceivers();
+        if (isPairing) {
+            stopBonding();
+        }
+        super.onStop();
+    }
+
+    @Override
+    protected void onPause() {
+        // WARN: Do not stop bonding process during pause!
+        // Bonding process can pause the activity and you might miss broadcasts
+        super.onPause();
+    }
+
+    public void unregisterBroadcastReceivers() {
+        AndroidUtils.safeUnregisterBroadcastReceiver(LocalBroadcastManager.getInstance(this), pairingReceiver);
+        AndroidUtils.safeUnregisterBroadcastReceiver(this, bondingReceiver);
+    }
+
+    public void registerBroadcastReceivers() {
+        LocalBroadcastManager.getInstance(this).registerReceiver(pairingReceiver, new IntentFilter(GBDevice.ACTION_DEVICE_CHANGED));
+        registerReceiver(bondingReceiver, new IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED));
+    }
+
+    @Override
+    public Context getContext() {
+        return this;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebbleSettingsCustomizer.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebbleSettingsCustomizer.java
new file mode 100644
index 0000000..b55735e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebbleSettingsCustomizer.java
@@ -0,0 +1,94 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.pebble;
+
+import android.content.ClipData;
+import android.content.ClipboardManager;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Parcel;
+import android.text.Editable;
+import android.text.InputFilter;
+import android.text.InputType;
+import android.text.TextWatcher;
+import android.widget.Toast;
+
+import androidx.activity.result.ActivityResultLauncher;
+import androidx.activity.result.contract.ActivityResultContracts;
+import androidx.annotation.NonNull;
+import androidx.documentfile.provider.DocumentFile;
+import androidx.preference.EditTextPreference;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceCategory;
+
+import com.google.android.material.dialog.MaterialAlertDialogBuilder;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.text.SimpleDateFormat;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsUtils;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminPreferences;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminRealtimeSettingsActivity;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.agps.GarminAgpsStatus;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+import static nodomain.freeyourgadget.gadgetbridge.util.GB.toast;
+
+public class PebbleSettingsCustomizer implements DeviceSpecificSettingsCustomizer {
+    public static final Creator<PebbleSettingsCustomizer> CREATOR = new Creator<PebbleSettingsCustomizer>() {
+        @Override
+        public PebbleSettingsCustomizer createFromParcel(final Parcel in) {
+            return new PebbleSettingsCustomizer();
+        }
+
+        @Override
+        public PebbleSettingsCustomizer[] newArray(final int size) {
+            return new PebbleSettingsCustomizer[size];
+        }
+    };
+    private static final Logger LOG = LoggerFactory.getLogger(PebbleSettingsCustomizer.class);
+    private final SimpleDateFormat SDF = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault());
+
+    @Override
+    public void onPreferenceChange(final Preference preference, final DeviceSpecificSettingsHandler handler) {
+    }
+
+    @Override
+    public void customizeSettings(final DeviceSpecificSettingsHandler handler, final Prefs prefs, final String rootKey) {
+        final EditTextPreference pref = handler.findPreference("pebble_mtu_limit");
+        if (pref == null) {
+            return;
+        }
+        pref.setOnBindEditTextListener(p -> {
+            p.setInputType(InputType.TYPE_CLASS_NUMBER);
+            p.setSelection(p.getText().length());
+        });
+    }
+
+    @Override
+    public Set<String> getPreferenceKeysWithSummary() {
+        return Collections.emptySet();
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/STM32CRC.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/STM32CRC.java
new file mode 100644
index 0000000..b40c2f0
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/STM32CRC.java
@@ -0,0 +1,180 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, Carsten Pfeiffer
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.pebble;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+/*
+ * This code was ported to java from the following cpp source file
+ * https://github.com/smokku/pebble/blob/8f0905197f7cead299c00006ada482095fe9d1a4/daemon/stm32crc.cpp
+ * (Unofficial Pebble watch support for SailfishOS/Jolla)
+ *
+ * Tail code was (hopefully) fixed by @ashimokawa on 7th October 2015
+ */
+
+public class STM32CRC {
+    private int crc;
+    private byte rem;
+    private final byte[] buffer = {0, 0, 0, 0};
+
+    private static final int[] crc_table = {
+            0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9,
+            0x130476dc, 0x17c56b6b, 0x1a864db2, 0x1e475005,
+            0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61,
+            0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd,
+            0x4c11db70, 0x48d0c6c7, 0x4593e01e, 0x4152fda9,
+            0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75,
+            0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011,
+            0x791d4014, 0x7ddc5da3, 0x709f7b7a, 0x745e66cd,
+            0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039,
+            0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5,
+            0xbe2b5b58, 0xbaea46ef, 0xb7a96036, 0xb3687d81,
+            0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d,
+            0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49,
+            0xc7361b4c, 0xc3f706fb, 0xceb42022, 0xca753d95,
+            0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1,
+            0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d,
+            0x34867077, 0x30476dc0, 0x3d044b19, 0x39c556ae,
+            0x278206ab, 0x23431b1c, 0x2e003dc5, 0x2ac12072,
+            0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16,
+            0x018aeb13, 0x054bf6a4, 0x0808d07d, 0x0cc9cdca,
+            0x7897ab07, 0x7c56b6b0, 0x71159069, 0x75d48dde,
+            0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02,
+            0x5e9f46bf, 0x5a5e5b08, 0x571d7dd1, 0x53dc6066,
+            0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba,
+            0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e,
+            0xbfa1b04b, 0xbb60adfc, 0xb6238b25, 0xb2e29692,
+            0x8aad2b2f, 0x8e6c3698, 0x832f1041, 0x87ee0df6,
+            0x99a95df3, 0x9d684044, 0x902b669d, 0x94ea7b2a,
+            0xe0b41de7, 0xe4750050, 0xe9362689, 0xedf73b3e,
+            0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2,
+            0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686,
+            0xd5b88683, 0xd1799b34, 0xdc3abded, 0xd8fba05a,
+            0x690ce0ee, 0x6dcdfd59, 0x608edb80, 0x644fc637,
+            0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb,
+            0x4f040d56, 0x4bc510e1, 0x46863638, 0x42472b8f,
+            0x5c007b8a, 0x58c1663d, 0x558240e4, 0x51435d53,
+            0x251d3b9e, 0x21dc2629, 0x2c9f00f0, 0x285e1d47,
+            0x36194d42, 0x32d850f5, 0x3f9b762c, 0x3b5a6b9b,
+            0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff,
+            0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623,
+            0xf12f560e, 0xf5ee4bb9, 0xf8ad6d60, 0xfc6c70d7,
+            0xe22b20d2, 0xe6ea3d65, 0xeba91bbc, 0xef68060b,
+            0xd727bbb6, 0xd3e6a601, 0xdea580d8, 0xda649d6f,
+            0xc423cd6a, 0xc0e2d0dd, 0xcda1f604, 0xc960ebb3,
+            0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7,
+            0xae3afba2, 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b,
+            0x9b3660c6, 0x9ff77d71, 0x92b45ba8, 0x9675461f,
+            0x8832161a, 0x8cf30bad, 0x81b02d74, 0x857130c3,
+            0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640,
+            0x4e8ee645, 0x4a4ffbf2, 0x470cdd2b, 0x43cdc09c,
+            0x7b827d21, 0x7f436096, 0x7200464f, 0x76c15bf8,
+            0x68860bfd, 0x6c47164a, 0x61043093, 0x65c52d24,
+            0x119b4be9, 0x155a565e, 0x18197087, 0x1cd86d30,
+            0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec,
+            0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088,
+            0x2497d08d, 0x2056cd3a, 0x2d15ebe3, 0x29d4f654,
+            0xc5a92679, 0xc1683bce, 0xcc2b1d17, 0xc8ea00a0,
+            0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, 0xdbee767c,
+            0xe3a1cbc1, 0xe760d676, 0xea23f0af, 0xeee2ed18,
+            0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4,
+            0x89b8fd09, 0x8d79e0be, 0x803ac667, 0x84fbdbd0,
+            0x9abc8bd5, 0x9e7d9662, 0x933eb0bb, 0x97ffad0c,
+            0xafb010b1, 0xab710d06, 0xa6322bdf, 0xa2f33668,
+            0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4,
+    };
+
+    public void reset() {
+        crc = 0xffffffff;
+        rem = 0;
+        buffer[0] = 0;
+        buffer[1] = 0;
+        buffer[2] = 0;
+        buffer[3] = 0;
+    }
+
+    public STM32CRC() {
+        reset();
+    }
+
+    private int calc_crc(int crc, int word) {
+        crc ^= word;
+        crc = (crc << 8) ^ crc_table[(crc >>> 24) & 0xFF];
+        crc = (crc << 8) ^ crc_table[(crc >>> 24) & 0xFF];
+        crc = (crc << 8) ^ crc_table[(crc >>> 24) & 0xFF];
+        crc = (crc << 8) ^ crc_table[(crc >>> 24) & 0xFF];
+        return crc;
+    }
+
+    public void addData(byte[] data, int length) {
+        ByteBuffer buf = ByteBuffer.wrap(data, 0, length);
+        buf.order(ByteOrder.LITTLE_ENDIAN);
+
+        if (rem > 0) {
+            for (; rem < 4 && buf.hasRemaining(); ++rem) {
+                buffer[rem] = buf.get();
+            }
+
+            if (rem == 4) {
+                int word = (buffer[0] & 0xff) |
+                        ((buffer[1] & 0xff) << 8) |
+                        ((buffer[2] & 0xff) << 16) |
+                        ((buffer[3] & 0xff) << 24);
+                crc = calc_crc(crc, word);
+                buffer[0] = 0;
+                buffer[1] = 0;
+                buffer[2] = 0;
+                buffer[3] = 0;
+                rem = 0;
+            }
+        }
+
+
+        while (buf.remaining() >= 4) {
+            int word = buf.getInt();
+            crc = calc_crc(crc, word);
+        }
+
+        while (buf.hasRemaining()) {
+            buffer[rem++] = buf.get();
+        }
+    }
+
+
+    public int getResult() {
+        if (rem > 0) {
+            int word = 0;
+            switch (rem) {
+                case 3:
+                    word = (buffer[2] & 0xff) |
+                            ((buffer[1] & 0xff) << 8) |
+                            ((buffer[0] & 0xff) << 16);
+                    break;
+                case 2:
+                    word = ((buffer[1] & 0xff)) |
+                            ((buffer[0] & 0xff) << 8);
+                    break;
+                case 1:
+                    word = buffer[0] & 0xff;
+                    break;
+            }
+            crc = calc_crc(crc, word);
+        }
+        return crc;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pinetime/InfiniTimeDFUPackage.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pinetime/InfiniTimeDFUPackage.java
new file mode 100644
index 0000000..52ac08c
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pinetime/InfiniTimeDFUPackage.java
@@ -0,0 +1,59 @@
+/*  Copyright (C) 2021-2024 Taavi Eom盲e
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.pinetime;
+
+import com.google.gson.annotations.SerializedName;
+
+import java.math.BigInteger;
+import java.util.List;
+
+/**
+ * If this or any of the classes below are renamed, update the proguard rules.
+ */
+public class InfiniTimeDFUPackage {
+    @SerializedName("manifest")
+    InfiniTimeDFUPackageManifest manifest;
+}
+
+class InfiniTimeDFUPackageManifest {
+    @SerializedName("application")
+    InfiniTimeDFUPackageApplication application;
+    @SerializedName("dfu_version")
+    Float dfu_version;
+}
+
+class InfiniTimeDFUPackageApplication {
+    @SerializedName("bin_file")
+    String bin_file;
+    @SerializedName("dat_file")
+    String dat_file;
+    @SerializedName("init_packet_data")
+    InfiniTimeDFUPackagePacketData init_packet_data;
+}
+
+class InfiniTimeDFUPackagePacketData {
+    @SerializedName("application_version")
+    BigInteger application_version;
+    @SerializedName("device_revision")
+    BigInteger device_revision;
+    @SerializedName("device_type")
+    BigInteger device_type;
+    @SerializedName("firmware_crc16")
+    BigInteger firmware_crc16;
+    @SerializedName("softdevice_req")
+    List<Integer> softdevice_req;
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pinetime/PineTimeActivitySampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pinetime/PineTimeActivitySampleProvider.java
new file mode 100644
index 0000000..a71bc54
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pinetime/PineTimeActivitySampleProvider.java
@@ -0,0 +1,94 @@
+/*  Copyright (C) 2021-2024 ITCactus, Patric Gruber
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.pinetime;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.List;
+import java.util.Optional;
+
+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.PineTimeActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.entities.PineTimeActivitySampleDao;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+
+public class PineTimeActivitySampleProvider extends AbstractSampleProvider<PineTimeActivitySample> {
+    public PineTimeActivitySampleProvider(GBDevice device, DaoSession session) {
+        super(device, session);
+    }
+
+    @Override
+    public AbstractDao<PineTimeActivitySample, ?> getSampleDao() {
+        return getSession().getPineTimeActivitySampleDao();
+    }
+
+    @Nullable
+    @Override
+    protected Property getRawKindSampleProperty() {
+        return PineTimeActivitySampleDao.Properties.RawKind;
+    }
+
+    @NonNull
+    @Override
+    protected Property getTimestampSampleProperty() {
+        return PineTimeActivitySampleDao.Properties.Timestamp;
+    }
+
+    @NonNull
+    @Override
+    protected Property getDeviceIdentifierSampleProperty() {
+        return PineTimeActivitySampleDao.Properties.DeviceId;
+    }
+
+    @Override
+    public ActivityKind normalizeType(int rawType) {
+        return ActivityKind.fromCode(rawType);
+    }
+
+    @Override
+    public int toRawActivityKind(ActivityKind activityKind) {
+        return activityKind.getCode();
+    }
+
+    @Override
+    public float normalizeIntensity(int rawIntensity) {
+        return rawIntensity;
+    }
+
+    /**
+     * Factory method to creates an empty sample of the correct type for this sample provider
+     *
+     * @return the newly created "empty" sample
+     */
+    @Override
+    public PineTimeActivitySample createActivitySample() {
+        return new PineTimeActivitySample();
+    }
+
+    public Optional<PineTimeActivitySample> getSampleForTimestamp(int timestamp) {
+        List<PineTimeActivitySample> foundSamples = this.getGBActivitySamples(timestamp, timestamp);
+        if (foundSamples.isEmpty()) {
+            return Optional.empty();
+        }
+        return Optional.of(foundSamples.get(0));
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pinetime/PineTimeDFUService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pinetime/PineTimeDFUService.java
new file mode 100644
index 0000000..bf2787e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pinetime/PineTimeDFUService.java
@@ -0,0 +1,35 @@
+/*  Copyright (C) 2020-2024 Taavi Eom盲e
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.pinetime;
+
+import android.app.Activity;
+
+import no.nordicsemi.android.dfu.DfuBaseService;
+import nodomain.freeyourgadget.gadgetbridge.BuildConfig;
+import nodomain.freeyourgadget.gadgetbridge.activities.FwAppInstallerActivity;
+
+public class PineTimeDFUService extends DfuBaseService {
+    @Override
+    protected Class<? extends Activity> getNotificationTarget() {
+        return FwAppInstallerActivity.class;
+    }
+
+    @Override
+    protected boolean isDebug() {
+        return BuildConfig.DEBUG;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pinetime/PineTimeInstallHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pinetime/PineTimeInstallHandler.java
new file mode 100644
index 0000000..5bbfcdc
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pinetime/PineTimeInstallHandler.java
@@ -0,0 +1,132 @@
+/*  Copyright (C) 2020-2024 MPeter, Taavi Eom盲e
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.pinetime;
+
+import android.content.Context;
+import android.net.Uri;
+
+import com.google.gson.Gson;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.InstallActivity;
+import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.model.GenericItem;
+import nodomain.freeyourgadget.gadgetbridge.util.UriHelper;
+import nodomain.freeyourgadget.gadgetbridge.util.GBZipFile;
+import nodomain.freeyourgadget.gadgetbridge.util.ZipFileException;
+
+public class PineTimeInstallHandler implements InstallHandler {
+    private static final Logger LOG = LoggerFactory.getLogger(PineTimeInstallHandler.class);
+    private static final Pattern binNameVersionPattern = Pattern.compile(".*-((?:\\d+\\.){2}\\d+).bin$");
+
+    private final Context context;
+
+    private InfiniTimeDFUPackage dfuPackageManifest;
+
+    public PineTimeInstallHandler(Uri uri, Context context) {
+        this.context = context;
+
+        UriHelper uriHelper;
+
+        try {
+            uriHelper = UriHelper.get(uri, this.context);
+
+            GBZipFile dfuPackage = new GBZipFile(uriHelper.openInputStream());
+            String manifest = new String(dfuPackage.getFileFromZip("manifest.json"));
+
+            if (!manifest.trim().isEmpty()) {
+                dfuPackageManifest = new Gson().fromJson(manifest.trim(), InfiniTimeDFUPackage.class);
+            }
+
+        } catch (ZipFileException e) {
+            LOG.error("Unable to read manifest file.", e);
+        } catch (FileNotFoundException e) {
+            LOG.error("The DFU file was not found.", e);
+        } catch (IOException e) {
+            LOG.error("General IO error occurred.", e);
+        } catch (Exception e) {
+            LOG.error("Unknown error occurred.", e);
+        }
+    }
+
+    @Override
+    public void validateInstallation(InstallActivity installActivity, GBDevice device) {
+        installActivity.setInstallEnabled(true);
+
+        if (device.isBusy()) {
+            LOG.error("Firmware cannot be installed (device busy)");
+            installActivity.setInfoText("Firmware cannot be installed (device busy)");
+            installActivity.setInfoText(device.getBusyTask());
+            installActivity.setInstallEnabled(false);
+            return;
+        }
+
+        if (device.getType() != DeviceType.PINETIME_JF || !device.isConnected()) {
+            LOG.error("Firmware cannot be installed (not connected or wrong device)");
+            installActivity.setInfoText("Firmware cannot be installed (not connected or wrong device)");
+            installActivity.setInstallEnabled(false);
+            return;
+        }
+
+        if (!isValid()) {
+            LOG.error("Firmware cannot be installed (not valid)");
+            installActivity.setInfoText("Firmware cannot be installed (not valid)");
+            installActivity.setInstallEnabled(false);
+        }
+
+        GenericItem installItem = new GenericItem();
+        installItem.setIcon(R.drawable.ic_firmware);
+        installItem.setName("PineTime firmware");
+        installItem.setDetails(getVersion());
+
+        installActivity.setInfoText(context.getString(R.string.firmware_install_warning, "(unknown)"));
+        installActivity.setInstallItem(installItem);
+        LOG.debug("Initialized PineTimeInstallHandler");
+    }
+
+    @Override
+    public void onStartInstall(GBDevice device) {
+    }
+
+    @Override
+    public boolean isValid() {
+        return dfuPackageManifest != null &&
+            dfuPackageManifest.manifest != null &&
+            dfuPackageManifest.manifest.application != null &&
+            dfuPackageManifest.manifest.application.bin_file != null;
+    }
+
+    // TODO: obtain version information from manifest file instead
+    private String getVersion() {
+        String binFileName = dfuPackageManifest.manifest.application.bin_file;
+        Matcher regexMatcher = binNameVersionPattern.matcher(binFileName);
+
+        if (regexMatcher.matches())
+            return regexMatcher.group(1);
+        return "(Unknown version)";
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pinetime/PineTimeJFConstants.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pinetime/PineTimeJFConstants.java
new file mode 100644
index 0000000..0b0d893
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pinetime/PineTimeJFConstants.java
@@ -0,0 +1,66 @@
+/*  Copyright (C) 2020-2024 Andreas Shimokawa, FintasticMan, ITCactus,
+    Patric Gruber, Stephan Lachnit, Taavi Eom盲e, uli
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.pinetime;
+
+import nodomain.freeyourgadget.gadgetbridge.service.btle.GattService;
+import java.util.UUID;
+
+public class PineTimeJFConstants {
+    public static final UUID UUID_SERVICE_MUSIC_CONTROL = UUID.fromString("00000000-78fc-48fe-8e23-433b3a1942d0");
+
+    public static final UUID UUID_CHARACTERISTICS_MUSIC_EVENT = UUID.fromString("00000001-78fc-48fe-8e23-433b3a1942d0");
+    public static final UUID UUID_CHARACTERISTICS_MUSIC_STATUS = UUID.fromString("00000002-78fc-48fe-8e23-433b3a1942d0");
+    public static final UUID UUID_CHARACTERISTICS_MUSIC_ARTIST = UUID.fromString("00000003-78fc-48fe-8e23-433b3a1942d0");
+    public static final UUID UUID_CHARACTERISTICS_MUSIC_TRACK = UUID.fromString("00000004-78fc-48fe-8e23-433b3a1942d0");
+    public static final UUID UUID_CHARACTERISTICS_MUSIC_ALBUM = UUID.fromString("00000005-78fc-48fe-8e23-433b3a1942d0");
+    public static final UUID UUID_CHARACTERISTICS_MUSIC_POSITION = UUID.fromString("00000006-78fc-48fe-8e23-433b3a1942d0");
+    public static final UUID UUID_CHARACTERISTICS_MUSIC_LENGTH_TOTAL = UUID.fromString("00000007-78fc-48fe-8e23-433b3a1942d0");
+    public static final UUID UUID_CHARACTERISTICS_MUSIC_TRACK_NUMBER = UUID.fromString("00000008-78fc-48fe-8e23-433b3a1942d0");
+    public static final UUID UUID_CHARACTERISTICS_MUSIC_TRACK_TOTAL = UUID.fromString("00000009-78fc-48fe-8e23-433b3a1942d0");
+    public static final UUID UUID_CHARACTERISTICS_MUSIC_PLAYBACK_SPEED = UUID.fromString("0000000a-78fc-48fe-8e23-433b3a1942d0");
+    public static final UUID UUID_CHARACTERISTICS_MUSIC_REPEAT = UUID.fromString("0000000b-78fc-48fe-8e23-433b3a1942d0");
+    public static final UUID UUID_CHARACTERISTICS_MUSIC_SHUFFLE = UUID.fromString("0000000c-78fc-48fe-8e23-433b3a1942d0");
+
+    public static final UUID UUID_SERVICE_NAVIGATION = UUID.fromString("00010000-78fc-48fe-8e23-433b3a1942d0");
+
+    public static final UUID UUID_CHARACTERISTICS_NAVIGATION_FLAGS = UUID.fromString("00010001-78fc-48fe-8e23-433b3a1942d0");
+    public static final UUID UUID_CHARACTERISTICS_NAVIGATION_NARRATIVE = UUID.fromString("00010002-78fc-48fe-8e23-433b3a1942d0");
+    public static final UUID UUID_CHARACTERISTICS_NAVIGATION_MAN_DISTANCE = UUID.fromString("00010003-78fc-48fe-8e23-433b3a1942d0");
+    public static final UUID UUID_CHARACTERISTICS_NAVIGATION_PROGRESS = UUID.fromString("00010004-78fc-48fe-8e23-433b3a1942d0");
+
+    public static final UUID UUID_CHARACTERISTIC_ALERT_NOTIFICATION_EVENT = UUID.fromString("00020001-78fc-48fe-8e23-433b3a1942d0");
+
+    public static final UUID UUID_SERVICE_WEATHER = UUID.fromString("00040000-78fc-48fe-8e23-433b3a1942d0");
+    public static final UUID UUID_CHARACTERISTIC_WEATHER_DATA = UUID.fromString("00040001-78fc-48fe-8e23-433b3a1942d0");
+    public static final UUID UUID_CHARACTERISTIC_WEATHER_CONTROL = UUID.fromString("00040002-78fc-48fe-8e23-433b3a1942d0");
+
+    public static final UUID UUID_SERVICE_SIMPLE_WEATHER = UUID.fromString("00050000-78fc-48fe-8e23-433b3a1942d0");
+    public static final UUID UUID_CHARACTERISTIC_SIMPLE_WEATHER_DATA = UUID.fromString("00050001-78fc-48fe-8e23-433b3a1942d0");
+
+    // since 1.7. https://github.com/InfiniTimeOrg/InfiniTime/blob/develop/doc/MotionService.md
+    public static final UUID UUID_SERVICE_MOTION = UUID.fromString("00030000-78fc-48fe-8e23-433b3a1942d0");
+    public static final UUID UUID_CHARACTERISTIC_MOTION_STEP_COUNT = UUID.fromString("00030001-78fc-48fe-8e23-433b3a1942d0");
+    public static final UUID UUID_CHARACTERISTIC_MOTION_RAW_XYZ_VALUES = UUID.fromString("00030002-78fc-48fe-8e23-433b3a1942d0");
+
+    // since https://github.com/InfiniTimeOrg/InfiniTime/pull/1454
+    public static final UUID UUID_CHARACTERISTIC_WORLD_TIME = UUID.fromString("00050001-78fc-48fe-8e23-433b3a1942d0");
+
+    public static final UUID UUID_SERVICE_HEART_RATE = GattService.UUID_SERVICE_HEART_RATE;
+    public static final UUID UUID_CHARACTERISTIC_HEART_RATE_MEASUREMENT = UUID.fromString("00002a37-0000-1000-8000-00805f9b34fb");
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pinetime/PineTimeJFCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pinetime/PineTimeJFCoordinator.java
new file mode 100644
index 0000000..9bce7b0
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pinetime/PineTimeJFCoordinator.java
@@ -0,0 +1,186 @@
+/*  Copyright (C) 2020-2024 Andreas Shimokawa, Damien Gaignon, Daniel Dakhno,
+    Davis Mosenkovs, ITCactus, Jos茅 Rebelo, Patric Gruber, Petr Van臎k, Taavi
+    Eom盲e, uli
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.pinetime;
+
+import android.app.Activity;
+import android.content.Context;
+import android.net.Uri;
+
+import androidx.annotation.NonNull;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLEDeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.pinetime.PineTimeJFSupport;
+
+public class PineTimeJFCoordinator extends AbstractBLEDeviceCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("Pinetime-JF.*|InfiniTime.*");
+    }
+
+    @Override
+    public Class<? extends Activity> getPairingActivity() {
+        return null;
+    }
+
+    @Override
+    public InstallHandler findInstallHandler(Uri uri, Context context) {
+        PineTimeInstallHandler handler = new PineTimeInstallHandler(uri, context);
+        return handler.isValid() ? handler : null;
+    }
+
+    @Override
+    public boolean supportsFlashing() { return true; }
+
+    @Override
+    public boolean supportsActivityDataFetching() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsActivityTracking() {
+        return true;
+    }
+
+    @Override
+    public SampleProvider<? extends ActivitySample> getSampleProvider(GBDevice device, DaoSession session) {
+        return new PineTimeActivitySampleProvider(device, session);
+    }
+
+    @Override
+    public boolean supportsScreenshots(final GBDevice device) {
+        return false;
+    }
+
+    @Override
+    public int getAlarmSlotCount(GBDevice device) {
+        return 0;
+    }
+
+    @Override
+    public boolean supportsHeartRateMeasurement(GBDevice device) {
+        return true;
+    }
+
+    @Override
+    public boolean supportsManualHeartRateMeasurement(GBDevice device) {
+        return false;
+    }
+
+    @Override
+    public String getManufacturer() {
+        return "Pine64";
+    }
+
+    @Override
+    public boolean supportsAppsManagement(final GBDevice device) {
+        return false;
+    }
+
+    @Override
+    public Class<? extends Activity> getAppsManagementActivity() {
+        return null;
+    }
+
+    @Override
+    public boolean supportsCalendarEvents() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsRealtimeData() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsWeather() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsFindDevice() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsMusicInfo() {
+        return true;
+    }
+
+    @Override
+    public int getWorldClocksSlotCount() {
+        return 4;
+    }
+
+    @Override
+    public int getWorldClocksLabelLength() {
+        return 8;
+    }
+
+    public boolean supportsNavigation() {
+        return true;
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return PineTimeJFSupport.class;
+    }
+
+    @Override
+    protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) {
+        // nothing to delete, yet
+    }
+
+    public int[] getSupportedDeviceSpecificSettings(GBDevice device) {
+        return new int[]{
+                R.xml.devicesettings_transliteration,
+                R.xml.devicesettings_world_clocks,
+                R.xml.devicesettings_prefix_notification_with_app
+        };
+    }
+
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_pinetime_jf;
+    }
+
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_pinetime;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_pinetime_disabled;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pinetime/weather/WeatherData.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pinetime/weather/WeatherData.java
new file mode 100644
index 0000000..6918cdd
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pinetime/weather/WeatherData.java
@@ -0,0 +1,1059 @@
+/*  Copyright (C) 2022-2024 Andreas Shimokawa, FintasticMan, Taavi Eom盲e
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.pinetime.weather;
+
+/**
+ * Implemented based on and other material:
+ * https://en.wikipedia.org/wiki/METAR
+ * https://www.weather.gov/jetstream/obscurationtypes
+ * http://www.faraim.org/aim/aim-4-03-14-493.html
+ */
+public class WeatherData {
+    /**
+     * OpenWeather's condition codes are a bit annoying,
+     * they've mixed precipitation with other weather events.
+     * <p>
+     * Even if they're absolutely not mutually exclusive.
+     * <p>
+     * So, this function only returns PrecipitationType
+     */
+    public static final PrecipitationType mapOpenWeatherConditionToPineTimePrecipitation(int openWeatherMapCondition) {
+        switch (openWeatherMapCondition) {
+            // Group 2xx: Thunderstorm
+            case 200:  // Thunderstorm with light rain
+            case 201:  // Thunderstorm with rain
+            case 202:  // Thunderstorm with heavy rain
+            case 210:  // Light thunderstorm
+            case 211:  // Thunderstorm
+            case 230:  // Thunderstorm with light drizzle
+            case 231:  // Thunderstorm with drizzle
+            case 232:  // Thunderstorm with heavy drizzle
+            case 212:  // Heavy thunderstorm
+            case 221:  // Ragged thunderstorm
+                return PrecipitationType.Rain;
+            // Group 3xx: Drizzle
+            case 300:  // Light intensity drizzle
+            case 301:  // Drizzle
+            case 302:  // Heavy intensity drizzle
+            case 310:  // Light intensity drizzle rain
+            case 311:  // Drizzle rain
+            case 312:  // Heavy intensity drizzle rain
+            case 313:  // Shower rain and drizzle
+            case 314:  // Heavy shower rain and drizzle
+            case 321:  // Shower drizzle
+                return PrecipitationType.Drizzle;
+            // Group 5xx: Rain
+            case 500:  // Light rain
+            case 501:  // Moderate rain
+            case 502:  // Heavy intensity rain
+            case 503:  // Very heavy rain
+            case 504:  // Extreme rain
+                return PrecipitationType.Rain;
+            case 511:  // Freezing rain
+                return PrecipitationType.FreezingRain;
+            case 520:  // Light intensity shower rain
+            case 521:  // Shower rain
+            case 522:  // Heavy intensity shower rain
+            case 531:  // Ragged shower rain
+                return PrecipitationType.Rain;
+            // Group 6xx: Snow
+            case 600:  // Light snow
+            case 601:  // Snow
+            case 620:  // Light shower snow
+            case 602:  // Heavy snow
+                return PrecipitationType.Snow;
+            case 611:  // Sleet
+            case 612:  // Shower sleet
+                return PrecipitationType.Sleet;
+            case 621:  // Shower snow
+            case 622:  // Heavy shower snow
+                return PrecipitationType.Snow;
+            case 615:  // Light rain and snow
+            case 616:  // Rain and snow
+                return PrecipitationType.Sleet;
+            // Group 7xx: Atmosphere
+            case 701:  // Mist
+            case 711:  // Smoke
+            case 721:  // Haze
+            case 731:  // Sandcase/dust whirls
+            case 741:  // Fog
+            case 751:  // Sand
+            case 761:  // Dust
+            case 762:  // Volcanic ash
+                return PrecipitationType.Ash;
+            case 771:  // Squalls
+            case 781:  // Tornado
+            case 900:  // Tornado
+                // Group 800: Clear
+            case 800:  // Clear sky
+                return PrecipitationType.None;
+            // Group 80x: Clouds
+            case 801:  // Few clouds
+            case 802:  // Scattered clouds
+            case 803:  // Broken clouds
+            case 804:  // Overcast clouds
+                // Group 90x: Extreme
+            case 901:  // Tropical storm
+            case 903:  // Cold
+            case 904:  // Hot
+            case 905:  // Windy
+            case 906:  // Hail
+                return PrecipitationType.Hail;
+            // Group 9xx: Additional
+            case 951:  // Calm
+            case 952:  // Light breeze
+            case 953:  // Gentle breeze
+            case 954:  // Moderate breeze
+            case 955:  // Fresh breeze
+            case 956:  // Strong breeze
+            case 957:  // High windcase  near gale
+            case 958:  // Gale
+            case 959:  // Severe gale
+            case 960:  // Storm
+            case 961:  // Violent storm
+            case 902:  // Hurricane
+            case 962:  // Hurricane
+            default:
+                return PrecipitationType.Length;
+        }
+    }
+
+    ;
+
+    /**
+     * OpenWeather's condition codes are a bit annoying,
+     * they've mixed obscuration with other weather events.
+     *
+     * <p>
+     * Even if they're absolutely not mutually exclusive.
+     * <p>
+     * So, this function only returns ObscurationType
+     */
+    public static final ObscurationType mapOpenWeatherConditionToPineTimeObscuration(int openWeatherMapCondition) {
+        switch (openWeatherMapCondition) {
+            // Group 2xx: Thunderstorm
+            case 200:  // Thunderstorm with light rain
+            case 201:  // Thunderstorm with rain
+            case 202:  // Thunderstorm with heavy rain
+            case 210:  // Light thunderstorm
+            case 211:  // Thunderstorm
+            case 230:  // Thunderstorm with light drizzle
+            case 231:  // Thunderstorm with drizzle
+            case 232:  // Thunderstorm with heavy drizzle
+            case 212:  // Heavy thunderstorm
+            case 221:  // Ragged thunderstorm
+                return ObscurationType.Precipitation;
+            // Group 3xx: Drizzle
+            case 300:  // Light intensity drizzle
+            case 301:  // Drizzle
+            case 302:  // Heavy intensity drizzle
+            case 310:  // Light intensity drizzle rain
+            case 311:  // Drizzle rain
+            case 312:  // Heavy intensity drizzle rain
+            case 313:  // Shower rain and drizzle
+            case 314:  // Heavy shower rain and drizzle
+            case 321:  // Shower drizzle
+                return ObscurationType.Precipitation;
+            // Group 5xx: Rain
+            case 500:  // Light rain
+            case 501:  // Moderate rain
+            case 502:  // Heavy intensity rain
+            case 503:  // Very heavy rain
+            case 504:  // Extreme rain
+            case 511:  // Freezing rain
+            case 520:  // Light intensity shower rain
+            case 521:  // Shower rain
+            case 522:  // Heavy intensity shower rain
+            case 531:  // Ragged shower rain
+                return ObscurationType.Precipitation;
+            // Group 6xx: Snow
+            case 600:  // Light snow
+            case 601:  // Snow
+            case 620:  // Light shower snow
+            case 602:  // Heavy snow
+            case 611:  // Sleet
+            case 612:  // Shower sleet
+            case 621:  // Shower snow
+            case 622:  // Heavy shower snow
+            case 615:  // Light rain and snow
+            case 616:  // Rain and snow
+                return ObscurationType.Precipitation;
+            // Group 7xx: Atmosphere
+            case 701:  // Mist
+                return ObscurationType.Mist;
+            case 711:  // Smoke
+                return ObscurationType.Smoke;
+            case 721:  // Haze
+                return ObscurationType.Haze;
+            case 731:  // Sandcase/dust whirls
+                return ObscurationType.Sand;
+            case 741:  // Fog
+                return ObscurationType.Fog;
+            case 751:  // Sand
+                return ObscurationType.Sand;
+            case 761:  // Dust
+                return ObscurationType.Dust;
+            case 762:  // Volcanic ash
+                return ObscurationType.Ash;
+            case 771:  // Squalls
+                return ObscurationType.Length;
+            case 781:  // Tornado
+            case 900:  // Tornado
+                return ObscurationType.Precipitation;
+            // Group 800: Clear
+            case 800:  // Clear sky
+                return ObscurationType.Length;
+            // Group 80x: Clouds
+            case 801:  // Few clouds
+            case 802:  // Scattered clouds
+            case 803:  // Broken clouds
+            case 804:  // Overcast clouds
+                return ObscurationType.Length;
+            // Group 90x: Extreme
+            case 901:  // Tropical storm
+                return ObscurationType.Precipitation;
+            case 903:  // Cold
+            case 904:  // Hot
+                return ObscurationType.Length;
+            case 905:  // Windy
+            case 906:  // Hail
+                return ObscurationType.Precipitation;
+            // Group 9xx: Additional
+            case 951:  // Calm
+            case 952:  // Light breeze
+            case 953:  // Gentle breeze
+            case 954:  // Moderate breeze
+            case 955:  // Fresh breeze
+            case 956:  // Strong breeze
+            case 957:  // High windcase near gale
+            case 958:  // Gale
+            case 959:  // Severe gale
+                return ObscurationType.Length;
+            case 960:  // Storm
+            case 961:  // Violent storm
+                return ObscurationType.Precipitation;
+            case 902:  // Hurricane
+                return ObscurationType.Precipitation;
+            case 962:  // Hurricane
+                return ObscurationType.Precipitation;
+            default:
+                return ObscurationType.Length;
+        }
+    }
+
+    ;
+
+    /**
+     * OpenWeather's condition codes are a bit annoying,
+     * they've mixed precipitation with other weather events.
+     * <p>
+     * Even if they're absolutely not mutually exclusive.
+     *
+     * <p>
+     * So, this function only returns SpecialType
+     */
+    public static final SpecialType mapOpenWeatherConditionToPineTimeSpecial(int openWeatherMapCondition) {
+        switch (openWeatherMapCondition) {
+            // Group 2xx: Thunderstorm
+            case 200:  // Thunderstorm with light rain
+            case 201:  // Thunderstorm with rain
+            case 202:  // Thunderstorm with heavy rain
+            case 210:  // Light thunderstorm
+            case 211:  // Thunderstorm
+            case 230:  // Thunderstorm with light drizzle
+            case 231:  // Thunderstorm with drizzle
+            case 232:  // Thunderstorm with heavy drizzle
+            case 212:  // Heavy thunderstorm
+            case 221:  // Ragged thunderstorm
+                // Group 3xx: Drizzle
+            case 300:  // Light intensity drizzle
+            case 301:  // Drizzle
+            case 302:  // Heavy intensity drizzle
+            case 310:  // Light intensity drizzle rain
+            case 311:  // Drizzle rain
+            case 312:  // Heavy intensity drizzle rain
+            case 313:  // Shower rain and drizzle
+            case 314:  // Heavy shower rain and drizzle
+            case 321:  // Shower drizzle
+                // Group 5xx: Rain
+            case 500:  // Light rain
+            case 501:  // Moderate rain
+            case 502:  // Heavy intensity rain
+            case 503:  // Very heavy rain
+            case 504:  // Extreme rain
+            case 511:  // Freezing rain
+            case 520:  // Light intensity shower rain
+            case 521:  // Shower rain
+            case 522:  // Heavy intensity shower rain
+            case 531:  // Ragged shower rain
+                // Group 6xx: Snow
+            case 600:  // Light snow
+            case 601:  // Snow
+            case 620:  // Light shower snow
+            case 602:  // Heavy snow
+            case 611:  // Sleet
+            case 612:  // Shower sleet
+            case 621:  // Shower snow
+            case 622:  // Heavy shower snow
+            case 615:  // Light rain and snow
+            case 616:  // Rain and snow
+                // Group 7xx: Atmosphere
+            case 701:  // Mist
+            case 711:  // Smoke
+            case 721:  // Haze
+            case 731:  // Sandcase/dust whirls
+            case 741:  // Fog
+            case 751:  // Sand
+            case 761:  // Dust
+            case 762:  // Volcanic ash
+                return SpecialType.Length;
+            case 771:  // Squalls
+                return SpecialType.Squall;
+            case 781:  // Tornado
+            case 900:  // Tornado
+                // Group 800: Clear
+            case 800:  // Clear sky
+                // Group 80x: Clouds
+            case 801:  // Few clouds
+            case 802:  // Scattered clouds
+            case 803:  // Broken clouds
+            case 804:  // Overcast clouds
+                // Group 90x: Extreme
+            case 901:  // Tropical storm
+            case 903:  // Cold
+                return SpecialType.Length;
+            case 904:  // Hot
+                return SpecialType.Fire;
+            case 905:  // Windy
+            case 906:  // Hail
+                // Group 9xx: Additional
+            case 951:  // Calm
+            case 952:  // Light breeze
+            case 953:  // Gentle breeze
+            case 954:  // Moderate breeze
+            case 955:  // Fresh breeze
+            case 956:  // Strong breeze
+            case 957:  // High windcase near gale
+            case 958:  // Gale
+            case 959:  // Severe gale
+            case 960:  // Storm
+            case 961:  // Violent storm
+            case 902:  // Hurricane
+            case 962:  // Hurricane
+            default:
+                return SpecialType.Length;
+        }
+    }
+
+    ;
+
+    /**
+     * OpenWeather's condition codes are a bit annoying,
+     * they've mixed precipitation with other weather events.
+     * <p>
+     * Even if they're absolutely not mutually exclusive.
+     *
+     * <p>
+     * So, this function only returns cloudyness 0-100%
+     */
+    public static final int mapOpenWeatherConditionToCloudCover(int openWeatherMapCondition) {
+        switch (openWeatherMapCondition) {
+            // Group 2xx: Thunderstorm
+            case 200:  // Thunderstorm with light rain
+            case 201:  // Thunderstorm with rain
+            case 202:  // Thunderstorm with heavy rain
+            case 210:  // Light thunderstorm
+            case 211:  // Thunderstorm
+            case 230:  // Thunderstorm with light drizzle
+            case 231:  // Thunderstorm with drizzle
+            case 232:  // Thunderstorm with heavy drizzle
+            case 212:  // Heavy thunderstorm
+            case 221:  // Ragged thunderstorm
+                return 100;
+            // Group 3xx: Drizzle
+            case 300:  // Light intensity drizzle
+            case 301:  // Drizzle
+            case 302:  // Heavy intensity drizzle
+            case 310:  // Light intensity drizzle rain
+            case 311:  // Drizzle rain
+            case 312:  // Heavy intensity drizzle rain
+            case 313:  // Shower rain and drizzle
+            case 314:  // Heavy shower rain and drizzle
+            case 321:  // Shower drizzle
+                return 75;
+            // Group 5xx: Rain
+            case 500:  // Light rain
+            case 501:  // Moderate rain
+            case 502:  // Heavy intensity rain
+            case 503:  // Very heavy rain
+            case 504:  // Extreme rain
+            case 511:  // Freezing rain
+            case 520:  // Light intensity shower rain
+            case 521:  // Shower rain
+            case 522:  // Heavy intensity shower rain
+            case 531:  // Ragged shower rain
+                return 75;
+            // Group 6xx: Snow
+            case 600:  // Light snow
+            case 601:  // Snow
+            case 620:  // Light shower snow
+            case 602:  // Heavy snow
+            case 611:  // Sleet
+            case 612:  // Shower sleet
+            case 621:  // Shower snow
+            case 622:  // Heavy shower snow
+            case 615:  // Light rain and snow
+            case 616:  // Rain and snow
+                return 75;
+            // Group 7xx: Atmosphere
+            case 701:  // Mist
+            case 711:  // Smoke
+            case 721:  // Haze
+            case 731:  // Sandcase/dust whirls
+            case 741:  // Fog
+            case 751:  // Sand
+            case 761:  // Dust
+                return -1;
+            case 762:  // Volcanic ash
+                return 100;
+            case 771:  // Squalls
+            case 781:  // Tornado
+            case 900:  // Tornado
+                // Group 800: Clouds & Clear
+            case 800:  // Clear sky
+                return 0;
+            case 801:  // Few clouds
+                return 25;
+            case 802:  // Scattered clouds
+                return 50;
+            case 803:  // Broken clouds
+                return 75;
+            case 804:  // Overcast clouds
+                return 100;
+            // Group 90x: Extreme
+            case 901:  // Tropical storm
+            case 903:  // Cold
+            case 904:  // Hot
+            case 905:  // Windy
+            case 906:  // Hail
+                return 75;
+            // Group 9xx: Additional
+            case 951:  // Calm
+            case 952:  // Light breeze
+            case 953:  // Gentle breeze
+            case 954:  // Moderate breeze
+            case 955:  // Fresh breeze
+            case 956:  // Strong breeze
+            case 957:  // High windcase near gale
+            case 958:  // Gale
+            case 959:  // Severe gale
+            case 960:  // Storm
+            case 961:  // Violent storm
+            case 902:  // Hurricane
+            case 962:  // Hurricane
+            default:
+                return -1;
+        }
+    }
+
+    ;
+
+    /**
+     * List of weather condition codes used to determine display
+     * https://openweathermap.org/weather-conditions
+     */
+    public static final ConditionType mapOpenWeatherConditionToPineTimeCondition(int openWeatherMapCondition) {
+        switch (openWeatherMapCondition) {
+            // Group 2xx: Thunderstorm
+            case 200:  // Thunderstorm with light rain
+            case 201:  // Thunderstorm with rain
+            case 202:  // Thunderstorm with heavy rain
+            case 210:  // Light thunderstorm
+            case 211:  // Thunderstorm
+            case 212:  // Heavy thunderstorm
+            case 221:  // Ragged thunderstorm
+            case 230:  // Thunderstorm with light drizzle
+            case 231:  // Thunderstorm with drizzle
+            case 232:  // Thunderstorm with heavy drizzle
+                return ConditionType.Thunderstorm;
+            // Group 3xx: Drizzle
+            case 300:  // Light intensity drizzle
+            case 301:  // Drizzle
+            case 302:  // Heavy intensity drizzle
+            case 310:  // Light intensity drizzle rain
+            case 311:  // Drizzle rain
+            case 312:  // Heavy intensity drizzle rain
+            case 313:  // Shower rain and drizzle
+            case 314:  // Heavy shower rain and drizzle
+            case 321:  // Shower drizzle
+                return ConditionType.CloudsAndRain;
+            // Group 5xx: Rain
+            case 500:  // Light rain
+            case 501:  // Moderate rain
+            case 502:  // Heavy intensity rain
+            case 503:  // Very heavy rain
+            case 504:  // Extreme rain
+                return ConditionType.Rain;
+            case 511:  // Freezing rain
+                return ConditionType.Snow;
+            case 520:  // Light intensity shower rain
+            case 521:  // Shower rain
+            case 522:  // Heavy intensity shower rain
+            case 531:  // Ragged shower rain
+                return ConditionType.CloudsAndRain;
+            // Group 6xx: Snow
+            case 600:  // Light snow
+            case 601:  // Snow
+            case 602:  // Heavy snow
+            case 611:  // Sleet
+            case 612:  // Light shower sleet
+            case 613:  // Shower sleet
+            case 615:  // Light rain and snow
+            case 616:  // Rain and snow
+            case 620:  // Light shower snow
+            case 621:  // Shower snow
+            case 622:  // Heavy shower snow
+                return ConditionType.Snow;
+            // Group 7xx: Atmosphere
+            case 701:  // Mist
+            case 711:  // Smoke
+            case 721:  // Haze
+            case 731:  // Sandcase/dust whirls
+            case 741:  // Fog
+            case 751:  // Sand
+            case 761:  // Dust
+            case 762:  // Volcanic ash
+            case 771:  // Squalls
+            case 781:  // Tornado
+                return ConditionType.Mist;
+            // Group 800: Clear
+            case 800:  // Clear sky
+                return ConditionType.ClearSky;
+            // Group 80x: Clouds
+            case 801:  // Few clouds
+                return ConditionType.FewClouds;
+            case 802:  // Scattered clouds
+                return ConditionType.Clouds;
+            case 803:  // Broken clouds
+            case 804:  // Overcast clouds
+                return ConditionType.HeavyClouds;
+            default:
+                return ConditionType.Length;
+        }
+    }
+
+    ;
+
+    /**
+     * Visibility obscuration types
+     */
+    public enum ObscurationType {
+        /**
+         * No obscuration
+         */
+        None(0),
+        /**
+         * Water particles suspended in the air; low visibility; does not fall
+         */
+        Fog(1),
+        /**
+         * Tiny, dry particles in the air; invisible to the eye; opalescent
+         */
+        Haze(2),
+        /**
+         * Small fire-created particles suspended in the air
+         */
+        Smoke(3),
+        /**
+         * Fine rock powder, from for example volcanoes
+         */
+        Ash(4),
+        /**
+         * Fine particles of earth suspended in the air by the wind
+         */
+        Dust(5),
+        /**
+         * Fine particles of sand suspended in the air by the wind
+         */
+        Sand(6),
+        /**
+         * Water particles suspended in the air; low-ish visibility; temperature is near dewpoint
+         */
+        Mist(7),
+        /**
+         * This is special in the sense that the thing falling down is doing the obscuration
+         */
+        Precipitation(8),
+        Length(9);
+
+        public final int value;
+
+        ObscurationType(int value) {
+            this.value = value;
+        }
+    }
+
+    /**
+     * Types of precipitation
+     */
+    public enum PrecipitationType {
+        /**
+         * No precipitation
+         * <p>
+         * Theoretically we could just _not_ send the event, but then
+         * how do we differentiate between no precipitation and
+         * no information about precipitation
+         */
+        None(0),
+        /**
+         * Drops larger than a drizzle; also widely separated drizzle
+         */
+        Rain(1),
+        /**
+         * Fairly uniform rain consisting of fine drops
+         */
+        Drizzle(2),
+        /**
+         * Rain that freezes upon contact with objects and ground
+         */
+        FreezingRain(3),
+        /**
+         * Rain + hail; ice pellets; small translucent frozen raindrops
+         */
+        Sleet(4),
+        /**
+         * Larger ice pellets; falling separately or in irregular clumps
+         */
+        Hail(5),
+        /**
+         * Hail with smaller grains of ice; mini-snowballs
+         */
+        SmallHail(6),
+        /**
+         * Snow...
+         */
+        Snow(7),
+        /**
+         * Frozen drizzle; very small snow crystals
+         */
+        SnowGrains(8),
+        /**
+         * Needles; columns or plates of ice. Sometimes described as "diamond dust". In very cold regions
+         */
+        IceCrystals(9),
+        /**
+         * It's raining down ash, e.g. from a volcano
+         */
+        Ash(10),
+        Length(11);
+
+        public final int value;
+
+        PrecipitationType(int value) {
+            this.value = value;
+        }
+    }
+
+    /**
+     * These are special events that can "enhance" the "experience" of existing weather events
+     */
+    public enum SpecialType {
+        /**
+         * Strong wind with a sudden onset that lasts at least a minute
+         */
+        Squall(0),
+        /**
+         * Series of waves in a water body caused by the displacement of a large volume of water
+         */
+        Tsunami(1),
+        /**
+         * Violent; rotating column of air
+         */
+        Tornado(2),
+        /**
+         * Unplanned; unwanted; uncontrolled fire in an area
+         */
+        Fire(3),
+        /**
+         * Thunder and/or lightning
+         */
+        Thunder(4),
+        Length(5);
+        public final int value;
+
+        SpecialType(int value) {
+            this.value = value;
+        }
+    }
+
+    /**
+     * List of weather condition codes used to determine display
+     */
+    public enum ConditionType {
+        /**
+         * Clear sky
+         */
+        ClearSky(0),
+        /**
+         * Few clouds
+         */
+        FewClouds(1),
+        /**
+         * Scattered clouds
+         */
+        Clouds(2),
+        /**
+         * Broken/heavy clouds
+         */
+        HeavyClouds(3),
+        /**
+         * Shower rain
+         */
+        CloudsAndRain(4),
+        /**
+         * Rain
+         */
+        Rain(5),
+        /**
+         * Thunderstorm
+         */
+        Thunderstorm(6),
+        /**
+         * Snow
+         */
+        Snow(7),
+        /**
+         * Mist
+         */
+        Mist(8),
+        Length(9);
+        public final byte value;
+
+        ConditionType(int value) { this.value = (byte) value; }
+    }
+
+    /**
+     * These are used for weather timeline manipulation
+     * that isn't just adding to the stack of weather events
+     */
+    public enum ControlCodes {
+        /**
+         * How much is stored already
+         */
+        GetLength(0),
+        /**
+         * This wipes the entire timeline
+         */
+        DelTimeline(1),
+        /**
+         * There's a currently valid timeline event with the given type
+         */
+        HasValidEvent(3),
+        Length(4);
+
+        public final int value;
+
+        ControlCodes(int value) {
+            this.value = value;
+        }
+    }
+
+    /**
+     * Events have types
+     * then they're easier to parse after sending them over the air
+     */
+    public enum EventType {
+        /**
+         * @see WeatherData.Obscuration
+         */
+        Obscuration(0),
+        /**
+         * @see WeatherData.Precipitation
+         */
+        Precipitation(1),
+        /**
+         * @see WeatherData.Wind
+         */
+        Wind(2),
+        /**
+         * @see WeatherData.Temperature
+         */
+        Temperature(3),
+        /**
+         * @see WeatherData.AirQuality
+         */
+        AirQuality(4),
+        /**
+         * @see WeatherData.Special
+         */
+        Special(5),
+        /**
+         * @see WeatherData.Pressure
+         */
+        Pressure(6),
+        /**
+         * @see WeatherData.Location
+         */
+        Location(7),
+        /**
+         * @see WeatherData.Clouds
+         */
+        Clouds(8),
+        /**
+         * @see WeatherData.Humidity
+         */
+        Humidity(9),
+        Length(10);
+
+        public final int value;
+
+        EventType(int value) {
+            this.value = value;
+        }
+    }
+
+    ;
+
+    /**
+     * Valid event query
+     */
+    static public class ValidEventQuery {
+        ControlCodes code = ControlCodes.HasValidEvent;
+        EventType eventType;
+    }
+
+    ;
+
+    /**
+     * The header used for further parsing
+     */
+    static public class TimelineHeader {
+        /**
+         * UNIX timestamp
+         */
+        long timestamp;
+        /**
+         * Time in seconds until the event expires
+         * <p>
+         * 32 bits ought to be enough for everyone
+         * <p>
+         * If there's a newer event of the same type then it overrides this one, even if it hasn't expired
+         */
+        int expires;
+        /**
+         * What type of weather-related event
+         */
+        EventType eventType;
+    }
+
+    /**
+     * Specifies how cloudiness is stored
+     */
+    static public class Clouds extends TimelineHeader {
+        /**
+         * Cloud coverage in percentage, 0-100%
+         */
+        byte amount;
+    }
+
+    /**
+     * Specifies how obscuration is stored
+     */
+    static public class Obscuration extends TimelineHeader {
+        /**
+         * Type
+         */
+        ObscurationType type;
+        /**
+         * Visibility distance in meters (0-65535)
+         */
+        int amount;
+    }
+
+    /**
+     * Specifies how precipitation is stored
+     */
+    static public class Precipitation extends TimelineHeader {
+        /**
+         * Type
+         */
+        PrecipitationType type;
+        /**
+         * How much is it going to rain? In millimeters (0-255)
+         */
+        int amount;
+    }
+
+    /**
+     * How wind speed is stored
+     * <p>
+     * In order to represent bursts of wind instead of constant wind,
+     * you have minimum and maximum speeds.
+     * <p>
+     * As direction can fluctuate wildly and some watchfaces might wish to display it nicely,
+     * we're following the aerospace industry weather report option of specifying a range.
+     */
+    static public class Wind extends TimelineHeader {
+        /**
+         * Meters per second (0-255)
+         */
+        byte speedMin;
+        /**
+         * Meters per second (0-255)
+         */
+        byte speedMax;
+        /**
+         * Unitless direction between 0-255; approximately 1 unit per 0.71 degrees
+         */
+        byte directionMin;
+        /**
+         * Unitless direction between 0-255; approximately 1 unit per 0.71 degrees
+         */
+        byte directionMax;
+    }
+
+    /**
+     * How temperature is stored
+     * <p>
+     * As it's annoying to figure out the dewpoint on the watch,
+     * please send it from the companion
+     * <p>
+     * We don't do floats, microdegrees are not useful. Make sure to multiply.
+     */
+    static public class Temperature extends TimelineHeader {
+        /**
+         * Temperature 掳C but multiplied by 100 (e.g. -12.50掳C becomes -1250, 0-65535)
+         */
+        short temperature;
+        /**
+         * Dewpoint 掳C but multiplied by 100 (e.g. -12.50掳C becomes -1250, 0-65535)
+         */
+        short dewPoint;
+    }
+
+    /**
+     * How location info is stored
+     * <p>
+     * This can be mostly static with long expiration,
+     * as it usually is, but it could change during a trip for ex.
+     * so we allow changing it dynamically.
+     * <p>
+     * Location info can be for some kind of map watchface
+     * or daylight calculations, should those be required.
+     */
+    static public class Location extends TimelineHeader {
+        /**
+         * Location name
+         */
+        String location;
+        /**
+         * Altitude relative to sea level in meters (0-65535)
+         */
+        short altitude;
+        /**
+         * Latitude, EPSG:3857 (Google Maps, Openstreetmaps datum, 0-4294967295)
+         */
+        int latitude;
+        /**
+         * Longitude, EPSG:3857 (Google Maps, Openstreetmaps datum, 0-4294967295)
+         */
+        int longitude;
+    }
+
+    /**
+     * How humidity is stored
+     */
+    static public class Humidity extends TimelineHeader {
+        /**
+         * Relative humidity, 0-100%
+         */
+        byte humidity;
+    }
+
+    /**
+     * How air pressure is stored
+     */
+    static public class Pressure extends TimelineHeader {
+        /**
+         * Air pressure in hectopascals (hPa, 0-65535)
+         */
+        short pressure;
+    }
+
+    /**
+     * How special events are stored
+     */
+    static public class Special extends TimelineHeader {
+        /**
+         * Special event's type
+         */
+        SpecialType type;
+    }
+
+    /**
+     * How air quality is stored
+     * <p>
+     * These events are a bit more complex because the topic is not simple,
+     * the intention is to heavy-lift the annoying preprocessing from the watch
+     * this allows watchface or watchapp makers to generate accurate alerts and graphics
+     * <p>
+     * If this needs further enforced standardization, pull requests are welcome
+     */
+    static public class AirQuality extends TimelineHeader {
+        /**
+         * The name of the pollution
+         * <p>
+         * for the sake of better compatibility with watchapps
+         * that might want to use this data for say visuals
+         * don't localize the name.
+         * <p>
+         * Ideally watchapp itself localizes the name, if it's at all needed.
+         * <p>
+         * E.g.
+         * For generic ones use "PM0.1", "PM5", "PM10"
+         * For chemical compounds use the molecular formula e.g. "NO2", "CO2", "O3"
+         * For pollen use the genus, e.g. "Betula" for birch or "Alternaria" for that mold's spores
+         */
+        String polluter;
+        /**
+         * Amount of the pollution in SI units,
+         * otherwise it's going to be difficult to create UI, alerts
+         * and so on and for.
+         * <p>
+         * See more:
+         * https://ec.europa.eu/environment/air/quality/standards.htm
+         * http://www.ourair.org/wp-content/uploads/2012-aaqs2.pdf
+         * <p>
+         * Example units:
+         * count/m鲁 for pollen
+         * 碌gC/m鲁 for micrograms of organic carbon
+         * 碌g/m鲁 sulfates, PM0.1, PM1, PM2, PM10 and so on, dust
+         * mg/m鲁 CO2, CO
+         * ng/m鲁 for heavy metals
+         * <p>
+         * List is not comprehensive, should be improved.
+         * The current ones are what watchapps assume.
+         * <p>
+         * Note: ppb and ppm to concentration should be calculated on the companion, using
+         * the correct formula (taking into account temperature and air pressure)
+         * <p>
+         * Note2: The amount is off by times 100, for two decimal places of precision.
+         * E.g. 54.32碌g/m鲁 is 5432
+         */
+        int amount;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qc35/QC35Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qc35/QC35Coordinator.java
new file mode 100644
index 0000000..69bbb0c
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qc35/QC35Coordinator.java
@@ -0,0 +1,91 @@
+/*  Copyright (C) 2021-2024 Damien Gaignon, Daniel Dakhno, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.qc35;
+
+import android.app.Activity;
+
+import androidx.annotation.NonNull;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.GBException;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLClassicDeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.BatteryConfig;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qc35.QC35BaseSupport;
+
+public class QC35Coordinator extends AbstractBLClassicDeviceCoordinator {
+    @Override
+    protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
+
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("Bose QC 35.*");
+    }
+
+    @Override
+    public int[] getSupportedDeviceSpecificSettings(GBDevice device) {
+        return new int[]{
+                R.xml.devicesettings_qc35
+        };
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return QC35BaseSupport.class;
+    }
+
+    @Override
+    public String getManufacturer() {
+        return "Bose";
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_bose_qc35;
+    }
+
+    @Override
+    public BatteryConfig[] getBatteryConfig(final GBDevice device) {
+        return new BatteryConfig[]{
+                new BatteryConfig(
+                        0,
+                        GBDevice.BATTERY_ICON_DEFAULT,
+                        GBDevice.BATTERY_LABEL_DEFAULT,
+                        25,
+                        100
+                )
+        };
+    }
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_headphones;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_headphones_disabled;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/AppsManagementActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/AppsManagementActivity.java
new file mode 100644
index 0000000..103c1cd
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/AppsManagementActivity.java
@@ -0,0 +1,162 @@
+/*  Copyright (C) 2021-2024 Daniel Dakhno, Hasan Ammar
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.qhybrid;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.ListView;
+import android.widget.PopupMenu;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.core.widget.PopupMenuCompat;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBActivity;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.QHybridSupport;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class AppsManagementActivity extends AbstractGBActivity {
+    ListView appsListView;
+    String[] appNames;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_qhybrid_apps_management);
+
+        initViews();
+        refreshInstalledApps();
+    }
+
+    private void toast(String data) {
+        GB.toast(data, Toast.LENGTH_LONG, GB.INFO);
+    }
+
+    private void refreshInstalledApps() {
+        try {
+            List<GBDevice> devices = GBApplication.app().getDeviceManager().getSelectedDevices();
+            boolean deviceFound = false;
+            for(GBDevice device : devices){
+                if (
+                        device.getType() == DeviceType.FOSSILQHYBRID &&
+                                device.isConnected() &&
+                                (device.getModel().startsWith("DN") || device.getModel().startsWith("IV")) &&
+                                device.getState() == GBDevice.State.INITIALIZED
+                ) {
+                    String installedAppsJson = device.getDeviceInfo("INSTALLED_APPS").getDetails();
+                    if (installedAppsJson == null || installedAppsJson.isEmpty()) {
+                        throw new RuntimeException("can't get installed apps");
+                    }
+                    JSONArray apps = new JSONArray(installedAppsJson);
+                    appNames = new String[apps.length()];
+                    for (int i = 0; i < apps.length(); i++) {
+                        appNames[i] = apps.getString(i);
+                    }
+                    appsListView.setAdapter(new AppsListAdapter(this, appNames));
+                }
+                return;
+            }
+        } catch (JSONException e) {
+            toast(e.getMessage());
+            finish();
+            return;
+        }
+        throw new RuntimeException("Device not connected");
+    }
+
+    class AppsListAdapter extends ArrayAdapter<String> {
+        public AppsListAdapter(@NonNull Context context, @NonNull String[] objects) {
+            super(context, 0, objects);
+        }
+
+        @NonNull
+        @Override
+        public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
+            if (convertView == null) {
+                LayoutInflater inflater = (LayoutInflater.from(getContext()));
+                convertView = inflater.inflate(R.layout.fossil_hr_row_installed_app, null);
+            }
+            TextView nameView = convertView.findViewById(R.id.fossil_hr_row_app_name);
+            nameView.setText(getItem(position));
+            return nameView;
+        }
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+        LocalBroadcastManager.getInstance(this).unregisterReceiver(deviceUpdateReceiver);
+        finish();
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        LocalBroadcastManager.getInstance(this).registerReceiver(deviceUpdateReceiver, new IntentFilter(GBDevice.ACTION_DEVICE_CHANGED));
+    }
+
+    BroadcastReceiver deviceUpdateReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            refreshInstalledApps();
+        }
+    };
+
+    private void initViews() {
+        appsListView = findViewById(R.id.qhybrid_apps_list);
+        appsListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+            @Override
+            public void onItemClick(AdapterView<?> parent, View view, final int position, long id) {
+                PopupMenu menu = new PopupMenu(AppsManagementActivity.this, view);
+                menu.getMenu()
+                        .add("uninstall")
+                        .setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
+                            @Override
+                            public boolean onMenuItemClick(MenuItem item) {
+                                Intent intent = new Intent(QHybridSupport.QHYBRID_COMMAND_UNINSTALL_APP);
+                                intent.putExtra("EXTRA_APP_NAME", appNames[position]);
+                                LocalBroadcastManager.getInstance(AppsManagementActivity.this).sendBroadcast(intent);
+                                return true;
+                            }
+                        });
+                menu.show();
+            }
+        });
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/CalibrationActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/CalibrationActivity.java
new file mode 100644
index 0000000..b0e4157
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/CalibrationActivity.java
@@ -0,0 +1,158 @@
+/*  Copyright (C) 2020-2024 Daniel Dakhno, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.qhybrid;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.Spinner;
+import android.widget.SpinnerAdapter;
+import android.widget.Toast;
+
+import androidx.annotation.NonNull;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBActivity;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.QHybridSupport;
+
+public class CalibrationActivity extends AbstractGBActivity {
+    enum HAND{
+        MINUTE,
+        HOUR,
+        SUB;
+
+        public String getDisplayName(){
+            return name().substring(0, 1).toUpperCase() + name().substring(1).toLowerCase();
+        }
+
+        public String getVariableName(){
+            return name();
+        }
+
+
+        @NonNull
+        @Override
+        public String toString() {
+            return getDisplayName();
+        }
+    }
+
+    enum MOVE_BUTTON{
+        CLOCKWISE_ONE(R.id.qhybrid_calibration_clockwise_1, 1),
+        CLOCKWISE_TEN(R.id.qhybrid_calibration_clockwise_10, 10),
+        CLOCKWISE_HUNRED(R.id.qhybrid_calibration_clockwise_100, 100),
+
+        COUNTER_CLOCKWISE_ONE(R.id.qhybrid_calibration_counter_clockwise_1, -1),
+        COUNTER_CLOCKWISE_TEN(R.id.qhybrid_calibration_counter_clockwise_10, -10),
+        COUNTER_CLOCKWISE_HUNRED(R.id.qhybrid_calibration_counter_clockwise_100, -100),
+        ;
+
+        int layoutId;
+        int distance;
+
+        MOVE_BUTTON(int layoutId, int distance) {
+            this.layoutId = layoutId;
+            this.distance = distance;
+        }
+    }
+
+    HAND selectedHand = HAND.MINUTE;
+    LocalBroadcastManager localBroadcastManager;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_qhybrid_calibration);
+
+        List<GBDevice> devices = GBApplication.app().getDeviceManager().getSelectedDevices();
+        boolean atLeastOneConnected = false;
+        for(GBDevice device : devices){
+            if(device.getType() == DeviceType.FOSSILQHYBRID){
+                atLeastOneConnected = true;
+                break;
+            }
+        }
+
+        if(!atLeastOneConnected){
+            Toast.makeText(this, R.string.watch_not_connected, Toast.LENGTH_LONG).show();
+            finish();
+            return;
+        }
+
+        localBroadcastManager = LocalBroadcastManager.getInstance(this);
+
+        localBroadcastManager.sendBroadcast(
+                new Intent(QHybridSupport.QHYBRID_COMMAND_CONTROL)
+        );
+
+        initViews();
+    }
+
+    private void initViews(){
+        Spinner handSpinner = findViewById(R.id.qhybrid_calibration_hand_spinner);
+        handSpinner.setAdapter(new ArrayAdapter<HAND>(this, android.R.layout.simple_spinner_dropdown_item, HAND.values()));
+        handSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
+            @Override
+            public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
+                selectedHand = (HAND) parent.getSelectedItem();
+            }
+
+            @Override
+            public void onNothingSelected(AdapterView<?> parent) {
+
+            }
+        });
+
+        for(final MOVE_BUTTON buttonDeclaration : MOVE_BUTTON.values()) {
+            final Button button = findViewById(buttonDeclaration.layoutId);
+
+            button.setOnClickListener(new View.OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    Intent intent = new Intent(QHybridSupport.QHYBRID_COMMAND_MOVE);
+                    intent.putExtra("EXTRA_DISTANCE_" + selectedHand.getVariableName(), (short) buttonDeclaration.distance);
+
+                    localBroadcastManager.sendBroadcast(intent);
+                }
+            });
+        }
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+        finish();
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        if (localBroadcastManager != null) {
+            localBroadcastManager.sendBroadcast(new Intent(QHybridSupport.QHYBRID_COMMAND_SAVE_CALIBRATION));
+            localBroadcastManager.sendBroadcast(new Intent(QHybridSupport.QHYBRID_COMMAND_UNCONTROL));
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/CommuteActionsActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/CommuteActionsActivity.java
new file mode 100644
index 0000000..7f0b38a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/CommuteActionsActivity.java
@@ -0,0 +1,213 @@
+/*  Copyright (C) 2021-2024 Arjan Schrijver
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.qhybrid;
+
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+
+import androidx.appcompat.app.AlertDialog;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+import androidx.recyclerview.widget.ItemTouchHelper;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.google.android.material.dialog.MaterialAlertDialogBuilder;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBActivity;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.QHybridSupport;
+
+public class CommuteActionsActivity extends AbstractGBActivity implements CommuteActionsListAdapter.ItemClickListener, DialogInterface.OnClickListener, View.OnClickListener {
+    protected final List<String> actionsList = new ArrayList<>();
+    private static final Logger LOG = LoggerFactory.getLogger(CommuteActionsActivity.class);
+    private SharedPreferences sharedPreferences;
+    private ItemTouchHelper actionTouchHelper;
+    private CommuteActionsListAdapter actionsListAdapter;
+    static public final String CONFIG_KEY_Q_ACTIONS = "Q_ACTIONS";
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_commute_actions);
+        sharedPreferences = GBApplication.getPrefs().getPreferences();
+
+        findViewById(R.id.actionAddFab).setOnClickListener(this);
+
+        // set up the RecyclerView
+        RecyclerView recyclerView = findViewById(R.id.actionsListView);
+        recyclerView.setLayoutManager(new LinearLayoutManager(this));
+        actionsListAdapter = new CommuteActionsListAdapter(this, actionsList);
+        actionsListAdapter.setClickListener(this);
+        recyclerView.setAdapter(actionsListAdapter);
+        refreshActions();
+
+        // set up touch helper for reordering items
+        ItemTouchHelper.Callback actionTouchHelperCallback = new ActionTouchHelperCallback(actionsListAdapter);
+        actionTouchHelper = new ItemTouchHelper(actionTouchHelperCallback);
+        actionTouchHelper.attachToRecyclerView(recyclerView);
+    }
+
+    private void refreshActions() {
+        JSONArray actionArray = null;
+        try {
+            actionArray = new JSONArray(sharedPreferences.getString(CONFIG_KEY_Q_ACTIONS, "[]"));
+            actionsList.clear();
+            for (int i = 0; i < actionArray.length(); i++)
+                actionsList.add(actionArray.getString(i));
+
+            actionsListAdapter.notifyDataSetChanged();
+        } catch (JSONException e) {
+            LOG.error("Error retrieving commute actions", e);
+        }
+    }
+
+    private void putActionItems(List<String> actions) {
+        JSONArray array = new JSONArray();
+        for (String action : actions) array.put(action);
+
+        sharedPreferences.edit().putString(CONFIG_KEY_Q_ACTIONS, array.toString()).apply();
+    }
+
+    @Override
+    public void onClick(View v) {
+        if (v.getId() == R.id.actionAddFab) {
+            final EditText input = new EditText(this);
+            input.setId(0);
+            LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
+                    LinearLayout.LayoutParams.MATCH_PARENT,
+                    LinearLayout.LayoutParams.MATCH_PARENT);
+            input.setLayoutParams(lp);
+
+            new MaterialAlertDialogBuilder(this)
+                    .setView(input)
+                    .setNegativeButton(R.string.fossil_hr_new_action_cancel, null)
+                    .setPositiveButton(R.string.ok, this)
+                    .setTitle(R.string.fossil_hr_new_action)
+                    .show();
+        }
+    }
+
+    @Override
+    public void onItemClick(View view, final int position) {
+        final EditText input = new EditText(this);
+        input.setId(0);
+        input.setText(actionsListAdapter.getItem(position));
+        LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
+                LinearLayout.LayoutParams.MATCH_PARENT,
+                LinearLayout.LayoutParams.MATCH_PARENT);
+        input.setLayoutParams(lp);
+
+        new MaterialAlertDialogBuilder(this)
+                .setView(input)
+                .setNegativeButton(R.string.fossil_hr_edit_action_delete, new DialogInterface.OnClickListener() {
+                    @Override
+                    public void onClick(DialogInterface dialog, int which) {
+                        actionsList.remove(position);
+                        putActionItems(actionsList);
+                        refreshActions();
+
+                        LocalBroadcastManager.getInstance(CommuteActionsActivity.this).sendBroadcast(new Intent(QHybridSupport.QHYBRID_COMMAND_OVERWRITE_BUTTONS));
+                    }
+                })
+                .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
+                    @Override
+                    public void onClick(DialogInterface dialog, int which) {
+                        actionsList.set(position, input.getText().toString());
+                        putActionItems(actionsList);
+                        refreshActions();
+
+                        LocalBroadcastManager.getInstance(CommuteActionsActivity.this).sendBroadcast(new Intent(QHybridSupport.QHYBRID_COMMAND_OVERWRITE_BUTTONS));
+                    }
+                })
+                .setTitle(R.string.fossil_hr_edit_action)
+                .show();
+    }
+
+    @Override
+    public void onClick(DialogInterface dialog, int which) {
+        EditText actionEditText = ((AlertDialog) dialog).findViewById(0);
+
+        String action = actionEditText.getText().toString();
+        try {
+            JSONArray actionArray = new JSONArray(sharedPreferences.getString(CONFIG_KEY_Q_ACTIONS, "[]"));
+            actionArray.put(action);
+            sharedPreferences.edit().putString(CONFIG_KEY_Q_ACTIONS, actionArray.toString()).apply();
+            refreshActions();
+
+            LocalBroadcastManager.getInstance(CommuteActionsActivity.this).sendBroadcast(new Intent(QHybridSupport.QHYBRID_COMMAND_OVERWRITE_BUTTONS));
+        } catch (JSONException e) {
+            LOG.error("Error adding new commute action", e);
+        }
+    }
+
+    public void startDragging(RecyclerView.ViewHolder viewHolder) {
+        actionTouchHelper.startDrag(viewHolder);
+    }
+
+    public class ActionTouchHelperCallback extends ItemTouchHelper.Callback {
+
+        private final CommuteActionsListAdapter actionsListAdapter;
+
+        public ActionTouchHelperCallback(CommuteActionsListAdapter actionsListAdapter) {
+            this.actionsListAdapter = actionsListAdapter;
+        }
+
+        @Override
+        public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
+            //we only support up and down movement and only for moving, not for swiping apps away
+            return makeMovementFlags(ItemTouchHelper.UP | ItemTouchHelper.DOWN, 0);
+        }
+
+        @Override
+        public boolean isLongPressDragEnabled() {
+            return false;
+        }
+
+        @Override
+        public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder source, RecyclerView.ViewHolder target) {
+            actionsListAdapter.onItemMove(source.getAdapterPosition(), target.getAdapterPosition());
+            putActionItems(actionsList);
+            return true;
+        }
+
+        @Override
+        public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
+            //nothing to do
+        }
+
+        @Override
+        public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
+            super.clearView(recyclerView, viewHolder);
+            putActionItems(actionsList);
+            LocalBroadcastManager.getInstance(CommuteActionsActivity.this).sendBroadcast(new Intent(QHybridSupport.QHYBRID_COMMAND_OVERWRITE_BUTTONS));
+        }
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/CommuteActionsListAdapter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/CommuteActionsListAdapter.java
new file mode 100644
index 0000000..ab9f069
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/CommuteActionsListAdapter.java
@@ -0,0 +1,111 @@
+/*  Copyright (C) 2021-2024 Arjan Schrijver
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.qhybrid;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import androidx.recyclerview.widget.RecyclerView;
+
+import java.util.Collections;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+
+public class CommuteActionsListAdapter extends RecyclerView.Adapter<CommuteActionsListAdapter.ViewHolder> {
+    private CommuteActionsActivity parentActivity;
+    private List<String> mData;
+    private LayoutInflater mInflater;
+    private ItemClickListener mClickListener;
+
+    // data is passed into the constructor
+    public CommuteActionsListAdapter(Context context, List<String> data) {
+        this.mInflater = LayoutInflater.from(context);
+        this.mData = data;
+        this.parentActivity = (CommuteActionsActivity) context;
+    }
+
+    // inflates the row layout from xml when needed
+    @Override
+    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+        View view = mInflater.inflate(R.layout.fossil_hr_row_commute_action, parent, false);
+        return new ViewHolder(view);
+    }
+
+    // binds the data to the TextView in each row
+    @Override
+    public void onBindViewHolder(final ViewHolder holder, int position) {
+        String line = mData.get(position);
+        holder.mTextView.setText(line);
+        holder.mDragHandle.setOnTouchListener(new View.OnTouchListener() {
+            @Override
+            public boolean onTouch(View view, MotionEvent motionEvent) {
+                parentActivity.startDragging(holder);
+                return true;
+            }
+        });
+    }
+
+    // total number of rows
+    @Override
+    public int getItemCount() {
+        return mData.size();
+    }
+
+    public void onItemMove(int from, int to) {
+        Collections.swap(mData, from, to);
+        notifyItemMoved(from, to);
+    }
+
+    // stores and recycles views as they are scrolled off screen
+    public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
+        TextView mTextView;
+        ImageView mDragHandle;
+
+        ViewHolder(View itemView) {
+            super(itemView);
+            mTextView = itemView.findViewById(R.id.fossil_hr_row_commute_action);
+            mDragHandle = (ImageView) itemView.findViewById(R.id.drag_handle);
+            itemView.setOnClickListener(this);
+        }
+
+        @Override
+        public void onClick(View view) {
+            if (mClickListener != null) mClickListener.onItemClick(view, getAdapterPosition());
+        }
+    }
+
+    // convenience method for getting data at click position
+    String getItem(int id) {
+        return mData.get(id);
+    }
+
+    // allows clicks events to be caught
+    void setClickListener(ItemClickListener itemClickListener) {
+        this.mClickListener = itemClickListener;
+    }
+
+    // parent activity will implement this method to respond to click events
+    public interface ItemClickListener {
+        void onItemClick(View view, int position);
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/FileManagementActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/FileManagementActivity.java
new file mode 100644
index 0000000..bb09113
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/FileManagementActivity.java
@@ -0,0 +1,194 @@
+/*  Copyright (C) 2020-2024 Andreas Shimokawa, Arjan Schrijver, Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.qhybrid;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.ArrayAdapter;
+import android.widget.CompoundButton;
+import android.widget.Spinner;
+import android.widget.Toast;
+
+import androidx.annotation.Nullable;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import com.google.android.material.dialog.MaterialAlertDialogBuilder;
+import com.google.android.material.materialswitch.MaterialSwitch;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBActivity;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.QHybridSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.file.FileHandle;
+import nodomain.freeyourgadget.gadgetbridge.util.AndroidUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class FileManagementActivity extends AbstractGBActivity implements View.OnClickListener {
+    private final int REQUEST_CODE_PICK_UPLOAD_FILE = 0;
+
+    private Spinner fileTypesSpinner;
+    private MaterialSwitch encryptedFile;
+    private boolean generateFileHeader = false;
+
+    private boolean warningDisplayed = false;
+
+    BroadcastReceiver fileResultReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (intent.getAction().equals(QHybridSupport.QHYBRID_ACTION_DOWNLOADED_FILE)) {
+                boolean success = intent.getBooleanExtra("EXTRA_SUCCESS", false);
+                if (!success) {
+                    Toast.makeText(FileManagementActivity.this, "error downloading file, check logcat", Toast.LENGTH_LONG).show();
+                    return;
+                }
+                String path = intent.getStringExtra("EXTRA_PATH");
+                Toast.makeText(FileManagementActivity.this, "downloaded file " + path, Toast.LENGTH_LONG).show();
+            }else if(intent.getAction().equals(QHybridSupport.QHYBRID_ACTION_UPLOADED_FILE)) {
+                boolean success = intent.getBooleanExtra("EXTRA_SUCCESS", false);
+                if (!success) {
+                    Toast.makeText(FileManagementActivity.this, "error uploading file, check logcat", Toast.LENGTH_LONG).show();
+                    return;
+                }
+                Toast.makeText(FileManagementActivity.this, "uploaded file", Toast.LENGTH_LONG).show();
+            }
+        }
+    };
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_qhybrid_file_management);
+
+        initViews();
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(QHybridSupport.QHYBRID_ACTION_DOWNLOADED_FILE);
+        filter.addAction(QHybridSupport.QHYBRID_ACTION_UPLOADED_FILE);
+        LocalBroadcastManager.getInstance(this).registerReceiver(fileResultReceiver, filter);
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+        LocalBroadcastManager.getInstance(this).unregisterReceiver(fileResultReceiver);
+    }
+
+    private void initViews() {
+        FileHandle[] handles = FileHandle.values();
+        fileTypesSpinner = findViewById(R.id.qhybrid_file_types);
+        fileTypesSpinner.setAdapter(new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, handles));
+
+        encryptedFile = findViewById(R.id.qhybrid_switch_encrypted_file);
+
+        findViewById(R.id.qhybrid_button_download_file).setOnClickListener(this);
+        findViewById(R.id.qhybrid_button_upload_file).setOnClickListener(this);
+
+        ((MaterialSwitch) findViewById(R.id.sqhybrid_switch_generate_file_header)).setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+            @Override
+            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+                generateFileHeader = isChecked;
+                fileTypesSpinner.setClickable(isChecked);
+                fileTypesSpinner.setAlpha(isChecked ? 1f : 0.2f);
+            }
+        });
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
+        super.onActivityResult(requestCode, resultCode, data);
+        if (requestCode != REQUEST_CODE_PICK_UPLOAD_FILE) return;
+        if (resultCode != RESULT_OK) return;
+        String fileName;
+        try {
+            fileName = AndroidUtils.getFilePath(this, data.getData());
+            if (fileName == null) {
+                return;
+            }
+        } catch (IllegalArgumentException e) {
+            GB.toast("please choose a local file", Toast.LENGTH_LONG, GB.WARN);
+            return;
+        }
+
+        try {
+            if (!warningDisplayed) {
+                FileInputStream fis = new FileInputStream(fileName);
+                short fileHandle = (short) (fis.read() | (fis.read() << 8));
+                fis.close();
+
+                boolean handleFound = FileHandle.fromHandle(fileHandle) != null;
+                if (handleFound == generateFileHeader) {
+                    warningDisplayed = true;
+                    String text = "File seems to contain file handle. Are you sure you want to generate a potentially already existing header?";
+                    if (!handleFound)
+                        text = "File does not start with a known handle. Are you sure the header is already generated?";
+                    text += " Repeat to continue anyway.";
+                    new MaterialAlertDialogBuilder(this)
+                            .setTitle("warning")
+                            .setMessage(text)
+                            .setPositiveButton("ok", null)
+                            .show();
+                    return;
+                }
+            }
+
+            Intent callIntent = new Intent(QHybridSupport.QHYBRID_COMMAND_UPLOAD_FILE);
+            callIntent.putExtra("EXTRA_HANDLE", (FileHandle) fileTypesSpinner.getSelectedItem());
+            callIntent.putExtra("EXTRA_ENCRYPTED", encryptedFile.isChecked());
+            callIntent.putExtra("EXTRA_GENERATE_FILE_HEADER", generateFileHeader);
+            callIntent.putExtra("EXTRA_PATH", AndroidUtils.getFilePath(this, data.getData()));
+            // callIntent.setData(data.getData());
+
+            LocalBroadcastManager.getInstance(this).sendBroadcast(callIntent);
+        } catch (IOException e) {
+            GB.toast("cannot open file", Toast.LENGTH_LONG, GB.ERROR, e);
+        }
+    }
+
+    @Override
+    public void onClick(View v) {
+        boolean isEncrypted = encryptedFile.isChecked();
+
+        if (v.getId() == R.id.qhybrid_button_download_file) {
+            Intent fileIntent = new Intent();
+            fileIntent.putExtra("EXTRA_ENCRYPTED", isEncrypted);
+            fileIntent.setAction(QHybridSupport.QHYBRID_COMMAND_DOWNLOAD_FILE);
+            fileIntent.putExtra("EXTRA_MAJORHANDLE", ((FileHandle) fileTypesSpinner.getSelectedItem()).getMajorHandle());
+            fileIntent.putExtra("EXTRA_MINORHANDLE", ((FileHandle) fileTypesSpinner.getSelectedItem()).getMinorHandle());
+            fileIntent.putExtra("EXTRA_NAME", ((FileHandle) fileTypesSpinner.getSelectedItem()).name());
+            LocalBroadcastManager.getInstance(this).sendBroadcast(fileIntent);
+        } else if (v.getId() == R.id.qhybrid_button_upload_file) {
+            Intent chooserIntent = new Intent()
+                    .addCategory(Intent.CATEGORY_OPENABLE)
+                    .setType("*/*")
+                    .setAction(Intent.ACTION_GET_CONTENT);
+
+            Intent intent = Intent.createChooser(chooserIntent, "Select a file");
+
+            startActivityForResult(intent, REQUEST_CODE_PICK_UPLOAD_FILE);
+        }
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/FossilAppWriter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/FossilAppWriter.java
new file mode 100644
index 0000000..c0c2b40
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/FossilAppWriter.java
@@ -0,0 +1,170 @@
+/*  Copyright (C) 2021-2024 Arjan Schrijver
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.qhybrid;
+
+import android.content.Context;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.LinkedHashMap;
+
+import nodomain.freeyourgadget.gadgetbridge.util.CRC32C;
+import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
+
+/**
+ * Writes watch apps to a file in the Fossil Hybrid HR .wapp format.
+ */
+public class FossilAppWriter {
+    private final Logger LOG = LoggerFactory.getLogger(FossilAppWriter.class);
+    private Context mContext;
+    private String version;
+    private LinkedHashMap<String, InputStream> code;
+    private LinkedHashMap<String, InputStream> icons;
+    private LinkedHashMap<String, InputStream> layout;
+    private LinkedHashMap<String, String> displayName;
+    private LinkedHashMap<String, String> config;
+
+    public FossilAppWriter(Context context, String version, LinkedHashMap<String, InputStream> code, LinkedHashMap<String, InputStream> icons, LinkedHashMap<String, InputStream> layout, LinkedHashMap<String, String> displayName, LinkedHashMap<String, String> config) {
+        this.mContext = context;
+        if (this.mContext == null) throw new AssertionError("context cannot be null");
+        this.version = version;
+        if (!this.version.matches("^[0-9]\\.[0-9]+$")) throw new AssertionError("Version must be in x.x format");
+        this.code = code;
+        if (this.code.size() == 0) throw new AssertionError("At least one code file InputStream must be supplied");
+        this.icons = icons;
+        if (this.icons == null) throw new AssertionError("icons cannot be null");
+        this.layout = layout;
+        if (this.layout == null) throw new AssertionError("layout cannot be null");
+        this.displayName = displayName;
+        if (this.displayName == null) throw new AssertionError("displayName cannot be null");
+        this.config = config;
+        if (this.config == null) throw new AssertionError("config cannot be null");
+    }
+
+    public byte[] getWapp() throws IOException {
+        byte[] codeData = loadFiles(code, false);
+        byte[] iconsData = loadFiles(icons, false);
+        byte[] layoutData = loadFiles(layout, true);
+        byte[] displayNameData = loadStringFiles(displayName);
+        byte[] configData = loadStringFiles(config);
+
+        int offsetCode = 88;
+        int offsetIcons = offsetCode + codeData.length;
+        int offsetLayout = offsetIcons + iconsData.length;
+        int offsetDisplayName = offsetLayout + layoutData.length;
+        int offsetConfig = offsetDisplayName + displayNameData.length;
+        int offsetFileEnd = offsetConfig + configData.length;
+
+        ByteArrayOutputStream filePart = new ByteArrayOutputStream();
+        filePart.write(0x1);  // 1 = watchface, 2 = app
+        String[] versionParts = this.version.split("\\.");
+        for (String versionPart : versionParts) {
+            filePart.write(Integer.valueOf(versionPart).byteValue());
+        }
+        filePart.write(0x0);
+        filePart.write(intToLEBytes(0));
+        filePart.write(intToLEBytes(0));
+        filePart.write(intToLEBytes(offsetCode));
+        filePart.write(intToLEBytes(offsetIcons));
+        filePart.write(intToLEBytes(offsetLayout));
+        filePart.write(intToLEBytes(offsetDisplayName));
+        filePart.write(intToLEBytes(offsetDisplayName));
+        filePart.write(intToLEBytes(offsetConfig));
+        filePart.write(intToLEBytes(offsetFileEnd));
+        filePart.write(intToLEBytes(0));
+        filePart.write(intToLEBytes(0));
+        filePart.write(intToLEBytes(0));
+        filePart.write(intToLEBytes(0));
+        filePart.write(intToLEBytes(0));
+        filePart.write(intToLEBytes(0));
+        filePart.write(intToLEBytes(0));
+        filePart.write(intToLEBytes(0));
+        filePart.write(intToLEBytes(0));
+        filePart.write(codeData);
+        filePart.write(iconsData);
+        filePart.write(layoutData);
+        filePart.write(displayNameData);
+        filePart.write(configData);
+        byte[] filePartBytes = filePart.toByteArray();
+
+        ByteArrayOutputStream wapp = new ByteArrayOutputStream();
+        wapp.write(new byte[]{(byte)0xFE, (byte)0x15});  // file handle
+        wapp.write(new byte[]{(byte)0x03, (byte)0x00});  // file version
+        wapp.write(intToLEBytes(0));  // file offset
+        wapp.write(intToLEBytes(filePartBytes.length));
+        wapp.write(filePartBytes);
+
+        CRC32C crc = new CRC32C();
+        crc.update(filePartBytes,0,filePartBytes.length);
+        wapp.write(intToLEBytes((int)crc.getValue()));
+
+        return wapp.toByteArray();
+    }
+
+    public byte[] loadFiles(LinkedHashMap<String, InputStream> filesMap, boolean appendNull) throws IOException {
+        ByteArrayOutputStream output = new ByteArrayOutputStream();
+        for (String filename : filesMap.keySet()) {
+            InputStream in = filesMap.get(filename);
+            output.write((byte)filename.length() + 1);
+            output.write(StringUtils.terminateNull(filename).getBytes(StandardCharsets.UTF_8));
+            int fileLength = in.available();
+            if(appendNull){
+                fileLength++;
+            }
+            output.write(shortToLEBytes((short)fileLength));
+            byte[] fileBytes = new byte[in.available()];
+            in.read(fileBytes);
+            output.write(fileBytes);
+            if(appendNull){
+                output.write(0x00);
+            }
+        }
+        return output.toByteArray();
+    }
+
+    public byte[] loadStringFiles(LinkedHashMap<String, String> stringsMap) throws IOException {
+        ByteArrayOutputStream output = new ByteArrayOutputStream();
+        for (String filename : stringsMap.keySet()) {
+            output.write((byte)filename.length() + 1);
+            output.write(StringUtils.terminateNull(filename).getBytes(StandardCharsets.UTF_8));
+            output.write(shortToLEBytes((short)(stringsMap.get(filename).getBytes(StandardCharsets.UTF_8).length + 1)));
+            output.write(StringUtils.terminateNull(stringsMap.get(filename)).getBytes(StandardCharsets.UTF_8));
+        }
+        return output.toByteArray();
+    }
+
+    private static byte[] intToLEBytes(int number) {
+        byte[] b = new byte[4];
+        b[0] = (byte) (number & 0xFF);
+        b[1] = (byte) ((number >> 8) & 0xFF);
+        b[2] = (byte) ((number >> 16) & 0xFF);
+        b[3] = (byte) ((number >> 24) & 0xFF);
+        return b;
+    }
+
+    private static byte[] shortToLEBytes(short number) {
+        byte[] b = new byte[2];
+        b[0] = (byte) (number & 0xFF);
+        b[1] = (byte) ((number >> 8) & 0xFF);
+        return b;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/FossilFileReader.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/FossilFileReader.java
new file mode 100644
index 0000000..937986f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/FossilFileReader.java
@@ -0,0 +1,328 @@
+/*  Copyright (C) 2021-2024 Andreas Shimokawa, Arjan Schrijver
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.qhybrid;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.net.Uri;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.json.JSONTokener;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.BufferedInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.image.ImageConverter;
+import nodomain.freeyourgadget.gadgetbridge.util.BitmapUtil;
+import nodomain.freeyourgadget.gadgetbridge.util.UriHelper;
+
+/**
+ * Reads and parses files meant to be uploaded to Fossil Hybrid Q & HR watches.
+ * These can be firmware files, watch apps and watchfaces (HR only).
+ */
+public class FossilFileReader {
+    private static final Logger LOG = LoggerFactory.getLogger(FossilFileReader.class);
+    private Uri uri;
+    private final UriHelper uriHelper;
+    private boolean isValid = false;
+    private boolean isFirmware = false;
+    private boolean isApp = false;
+    private boolean isWatchface = false;
+    private String foundVersion = "(Unknown version)";
+    private String foundName = "(unknown)";
+    private GBDeviceApp app;
+    private JSONObject mAppKeys;
+
+    private int jerryStart;
+    private int appIconStart;
+    private int layout_start;
+    private int display_name_start;
+    private int display_name_start_2;
+    private int config_start;
+    private int file_end;
+
+    ArrayList<String> filenamesIcons;
+    ArrayList<String> filenamesLayout;
+    ArrayList<String> filenamesDisplayName;
+    ArrayList<String> filenamesConfig;
+
+
+    public FossilFileReader(Uri uri, Context context) throws IOException {
+        this.uri = uri;
+        uriHelper = UriHelper.get(uri, context);
+
+        try (InputStream in = new BufferedInputStream(uriHelper.openInputStream())) {
+            // Read just the first 32 bytes for file type detection
+            byte[] bytes = new byte[32];
+            int read = in.read(bytes);
+            in.close();
+            if (read < 32) {
+                isValid = false;
+                return;
+            }
+            ByteBuffer buf = ByteBuffer.wrap(bytes);
+            buf.order(ByteOrder.LITTLE_ENDIAN);
+
+            short handle = buf.getShort();
+            byte version = buf.get();
+            if (handle == 5630 && version == 3) {
+                // This is a watch app or watch face
+                isValid = true;
+                isApp = true;
+                parseApp();
+                return;
+            }
+
+            // Back to byte 0 for firmware detection
+            buf.rewind();
+            int header0 = buf.getInt();
+            buf.getInt(); // size
+            int header2 = buf.getInt();
+            int header3 = buf.getInt();
+            if (header0 != 1 ||
+                (header2 != 0x00012000 && header2 != 0x00020000) ||
+                (header3 != 0x00012000 && header3 != 0x00020000)) {
+                return;
+            }
+
+            buf.getInt(); // unknown
+            isValid = true;
+            isFirmware = true;
+            parseFirmware();
+        } catch (Exception e) {
+            LOG.warn("Error during Fossil file parsing", e);
+        }
+    }
+
+    private void parseFirmware() throws IOException {
+        InputStream in = new BufferedInputStream(uriHelper.openInputStream());
+        byte[] bytes = new byte[in.available()];
+        in.read(bytes);
+        in.close();
+        ByteBuffer buf = ByteBuffer.wrap(bytes);
+        buf.order(ByteOrder.LITTLE_ENDIAN);
+        buf.position(20);
+        int version1 = buf.get() % 0xff;
+        int version2 = buf.get() & 0xff;
+        foundVersion = version1 + "." + version2;
+        foundName = "Fossil/Skagen Hybrid HR firmware";
+    }
+
+    private void parseApp() throws IOException, JSONException {
+        mAppKeys = new JSONObject();
+        mAppKeys.put("creator", "(unknown)");
+        InputStream in = new BufferedInputStream(uriHelper.openInputStream());
+        byte[] bytes = new byte[in.available()];
+        in.read(bytes);
+        in.close();
+        ByteBuffer buf = ByteBuffer.wrap(bytes);
+        buf.order(ByteOrder.LITTLE_ENDIAN);
+        buf.position(8);  // skip file handle and version
+        int fileSize = buf.getInt();
+        buf.get();  // 1 = watchface, 2 = app
+        foundVersion = (int)buf.get() + "." + (int)buf.get();
+        buf.get();  // unknown
+        mAppKeys.put("version", foundVersion);
+        buf.position(buf.position() + 8);  // skip null bytes
+        jerryStart = buf.getInt();
+        appIconStart = buf.getInt();
+        layout_start = buf.getInt();
+        display_name_start = buf.getInt();
+        display_name_start_2 = buf.getInt();
+        config_start = buf.getInt();
+        file_end = buf.getInt();
+        buf.position(jerryStart);
+
+        ArrayList<String> filenamesCode = parseAppFilenames(buf, appIconStart,false);
+        if (filenamesCode.size() > 0) {
+            foundName = filenamesCode.get(0);
+            mAppKeys.put("name", foundName);
+            mAppKeys.put("uuid", UUID.nameUUIDFromBytes(foundName.getBytes(StandardCharsets.UTF_8)));
+        }
+        filenamesIcons = parseAppFilenames(buf, layout_start,false);
+        filenamesLayout = parseAppFilenames(buf, display_name_start,true);
+        filenamesDisplayName = parseAppFilenames(buf, config_start,true);
+        filenamesConfig = parseAppFilenames(buf, file_end,true);
+
+        if (filenamesDisplayName.contains("theme_class")) {
+            isApp = false;
+            isWatchface = true;
+            mAppKeys.put("type", "WATCHFACE");
+        } else {
+            mAppKeys.put("type", "APP_GENERIC");
+        }
+        app = new GBDeviceApp(mAppKeys, false, null);
+    }
+
+    private ArrayList<String> parseAppFilenames(ByteBuffer buf, int untilPosition, boolean cutTrailingNull) {
+        ArrayList<String> list = new ArrayList<>();
+        while (buf.position() < untilPosition) {
+            int filenameLength = (int)buf.get();
+            byte[] filenameBytes = new byte[filenameLength - 1];
+            buf.get(filenameBytes);
+            buf.get();
+            list.add(new String(filenameBytes, Charset.forName("UTF8")));
+            int filesize = buf.getShort();
+            if (cutTrailingNull) {
+                filesize -= 1;
+            }
+            buf.position(buf.position() + filesize);  // skip file data for now
+            if (cutTrailingNull) {
+                buf.get();
+            }
+        }
+        return list;
+    }
+
+    public JSONObject getConfigJSON(String filename) throws IOException, JSONException {
+        byte[] fileBytes = getFileContentsByName(filename, config_start, file_end, true);
+        String fileString = new String(fileBytes, StandardCharsets.UTF_8);
+        JSONTokener jsonTokener = new JSONTokener(fileString);
+        return new JSONObject(jsonTokener);
+    }
+
+    public Bitmap getPreview() {
+        try {
+            if ((filenamesIcons != null) && (filenamesIcons.contains("!preview.rle"))) {
+                return BitmapUtil.getCircularBitmap(ImageConverter.decodeFromRLEImage(getImageFileContents("!preview.rle")));
+            }
+            if ((filenamesIcons != null) && (filenamesIcons.contains("!preview"))) {
+                return BitmapUtil.getCircularBitmap(ImageConverter.decodeFromRLEImage(getImageFileContents("!preview")));
+            }
+        } catch (IOException e) {
+            LOG.warn("Couldn't read preview image from wapp file: ", e);
+            return null;
+        }
+        LOG.warn("No preview image found in wapp file");
+        return null;
+    }
+
+    public Bitmap getBackground() {
+        try {
+            if (filenamesIcons != null) {
+                String filename = null;
+                if (filenamesIcons.contains("background.raw")) {
+                    filename = "background.raw";
+                } else if (filenamesIcons.contains("background")) {
+                    filename = "background";
+                } else {
+                    JSONObject config = getConfigJSON("customWatchFace");
+                    JSONArray layout = config.getJSONArray("layout");
+                    JSONObject firstLayoutItem = layout.getJSONObject(0);
+                    if (firstLayoutItem.getString("type").equals("image")) {
+                        filename = firstLayoutItem.getString("name");
+                    }
+                }
+                if (filename != null) {
+                    byte[] rawImage = getImageFileContents(filename);
+                    Bitmap decodedImage = ImageConverter.decodeFromRAWImage(rawImage, 240, 240);
+                    return BitmapUtil.getCircularBitmap(decodedImage);
+                }
+            }
+        } catch (Exception e) {
+            LOG.warn("Couldn't read background image from wapp file: ", e);
+            return null;
+        }
+        LOG.warn("No background image found in wapp file");
+        return null;
+    }
+
+    private byte[] getImageFileContents(String filename) throws IOException {
+        return getFileContentsByName(filename, appIconStart, layout_start, false);
+    }
+
+    private byte[] getFileContentsByName(String filename, int startPos, int endPos, boolean cutTrailingNull) throws IOException {
+        InputStream in = new BufferedInputStream(uriHelper.openInputStream());
+        byte[] bytes = new byte[in.available()];
+        in.read(bytes);
+        in.close();
+        ByteBuffer buf = ByteBuffer.wrap(bytes);
+        buf.order(ByteOrder.LITTLE_ENDIAN);
+        buf.position(startPos);
+        while (buf.position() < endPos) {
+            int filenameLength = (int)buf.get();
+            byte[] filenameBytes = new byte[filenameLength - 1];
+            buf.get(filenameBytes);
+            buf.get();
+            String foundFilename = new String(filenameBytes, StandardCharsets.UTF_8);
+            int filesize = buf.getShort();
+            if (cutTrailingNull) {
+                filesize -= 1;
+            }
+            if (foundFilename.equals(filename)) {
+                byte[] fileBytes = new byte[filesize];
+                buf.get(fileBytes);
+                return fileBytes;
+            } else {
+                buf.position(buf.position() + filesize);
+            }
+            if (cutTrailingNull) {
+                buf.get();
+            }
+        }
+        throw new FileNotFoundException();
+    }
+
+    public boolean isValid() {
+        return isValid;
+    }
+
+    public boolean isFirmware() {
+        return isFirmware;
+    }
+
+    public boolean isApp() {
+        return isApp;
+    }
+
+    public boolean isWatchface() {
+        return isWatchface;
+    }
+
+    public Uri getUri() {
+        return uri;
+    }
+
+    public String getVersion() {
+        return foundVersion;
+    }
+
+    public String getName() {
+        return foundName;
+    }
+
+    public GBDeviceApp getGBDeviceApp() {
+        return app;
+    }
+
+    public JSONObject getAppKeysJSON() {
+        return mAppKeys;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/FossilHRInstallHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/FossilHRInstallHandler.java
new file mode 100644
index 0000000..50589f2
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/FossilHRInstallHandler.java
@@ -0,0 +1,189 @@
+/*  Copyright (C) 2020-2024 Andreas Shimokawa, Arjan Schrijver, Daniel Dakhno,
+    Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.qhybrid;
+
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.net.Uri;
+
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.Writer;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.InstallActivity;
+import nodomain.freeyourgadget.gadgetbridge.activities.appmanager.AbstractAppManagerFragment;
+import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.model.GenericItem;
+import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
+import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class FossilHRInstallHandler implements InstallHandler {
+    private static final Logger LOG = LoggerFactory.getLogger(FossilHRInstallHandler.class);
+
+    private final Uri mUri;
+    private final Context mContext;
+    private FossilFileReader fossilFile;
+
+    FossilHRInstallHandler(Uri uri, Context context) {
+        mUri = uri;
+        mContext = context;
+        try {
+            fossilFile = new FossilFileReader(uri, mContext);
+        } catch (IOException ignored) {
+        }
+    }
+
+    @Override
+    public void validateInstallation(InstallActivity installActivity, GBDevice device) {
+        if (device.isBusy()) {
+            installActivity.setInfoText(device.getBusyTask());
+            installActivity.setInstallEnabled(false);
+            return;
+        }
+        if (device.getType() != DeviceType.FOSSILQHYBRID || !device.isConnected() || !fossilFile.isValid()) {
+            installActivity.setInfoText("Element cannot be installed 1, type: " + device.getType() + " device: "+ device  + " is connected" + device.isConnected() + " is valid file:" + fossilFile.isValid());
+            installActivity.setInstallEnabled(false);
+            return;
+        }
+        GenericItem installItem = new GenericItem();
+        installItem.setName(fossilFile.getName());
+        installItem.setDetails(fossilFile.getVersion());
+        Bitmap preview = fossilFile.getPreview();
+        if (preview != null) {
+            preview = Bitmap.createScaledBitmap(preview, preview.getWidth() * 3, preview.getHeight() * 3, false);
+        }
+        installItem.setPreview(preview);
+        if (fossilFile.isFirmware()) {
+            installItem.setIcon(R.drawable.ic_firmware);
+            installActivity.setInfoText(mContext.getString(R.string.firmware_install_warning, "(unknown)"));
+        } else if (fossilFile.isApp()) {
+            installItem.setIcon(R.drawable.ic_watchapp);
+            installActivity.setInfoText(mContext.getString(R.string.app_install_info, installItem.getName(), fossilFile.getVersion(), "(unknown)"));
+        } else if (fossilFile.isWatchface()) {
+            installItem.setIcon(R.drawable.ic_watchface);
+            installActivity.setInfoText(mContext.getString(R.string.watchface_install_info, installItem.getName(), fossilFile.getVersion(), "(unknown)"));
+        } else {
+            installActivity.setInfoText("Element cannot be installed 2");
+            installActivity.setInstallEnabled(false);
+            return;
+        }
+        installActivity.setInstallEnabled(true);
+        installActivity.setInstallItem(installItem);
+    }
+
+    @Override
+    public void onStartInstall(GBDevice device) {
+        DeviceCoordinator mCoordinator = device.getDeviceCoordinator();
+        LocalBroadcastManager manager = LocalBroadcastManager.getInstance(mContext);
+        manager.sendBroadcast(new Intent(GB.ACTION_SET_PROGRESS_BAR).putExtra(GB.PROGRESS_BAR_INDETERMINATE, true));
+        if (fossilFile.isFirmware()) {
+            return;
+        }
+        saveAppInCache(fossilFile, fossilFile.getBackground(), fossilFile.getPreview(), mCoordinator, mContext);
+        // refresh list
+        manager.sendBroadcast(new Intent(AbstractAppManagerFragment.ACTION_REFRESH_APPLIST));
+    }
+
+    public static boolean saveAppInCache(FossilFileReader fossilFile, Bitmap backgroundImg, Bitmap previewImg, DeviceCoordinator mCoordinator, Context mContext) {
+        GBDeviceApp app;
+        File destDir;
+        // write app file
+        try {
+            app = fossilFile.getGBDeviceApp();
+            destDir = mCoordinator.getAppCacheDir();
+            destDir.mkdirs();
+            FileUtils.copyURItoFile(mContext, fossilFile.getUri(), new File(destDir, app.getUUID().toString() + mCoordinator.getAppFileExtension()));
+        } catch (IOException e) {
+            LOG.error("Saving app in cache failed: " + e.getMessage(), e);
+            return false;
+        }
+        // write app metadata
+        File outputFile = new File(destDir, app.getUUID().toString() + ".json");
+        Writer writer;
+        try {
+            writer = new BufferedWriter(new FileWriter(outputFile));
+        } catch (IOException e) {
+            LOG.error("Failed to open output file: " + e.getMessage(), e);
+            return false;
+        }
+        try {
+            LOG.info(app.getJSON().toString());
+            JSONObject appJSON = app.getJSON();
+            JSONObject appKeysJSON = fossilFile.getAppKeysJSON();
+            if (appKeysJSON != null) {
+                appJSON.put("appKeys", appKeysJSON);
+            }
+            writer.write(appJSON.toString());
+
+            writer.close();
+        } catch (IOException e) {
+            LOG.error("Failed to write to output file: " + e.getMessage(), e);
+            return false;
+        } catch (JSONException e) {
+            LOG.error("Failed to load or write appKeys JSON: " + e.getMessage(), e);
+            return false;
+        }
+        // write watchface background image
+        if (backgroundImg != null) {
+            outputFile = new File(destDir, app.getUUID().toString() + "_bg.png");
+            try {
+                FileOutputStream fos = new FileOutputStream(outputFile);
+                backgroundImg.compress(Bitmap.CompressFormat.PNG, 9, fos);
+                fos.close();
+            } catch (IOException e) {
+                LOG.error("Failed to write to output file: " + e.getMessage(), e);
+                return false;
+            }
+        }
+        // write watchface preview image
+        if (previewImg != null) {
+            outputFile = new File(destDir, app.getUUID().toString() + "_preview.png");
+            try {
+                FileOutputStream fos = new FileOutputStream(outputFile);
+                previewImg.compress(Bitmap.CompressFormat.PNG, 9, fos);
+                fos.close();
+            } catch (IOException e) {
+                LOG.error("Failed to write to output file: " + e.getMessage(), e);
+                return false;
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public boolean isValid() {
+        return fossilFile.isValid();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/FossilInstallHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/FossilInstallHandler.java
new file mode 100644
index 0000000..e1dcceb
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/FossilInstallHandler.java
@@ -0,0 +1,111 @@
+/*  Copyright (C) 2020-2024 Daniel Dakhno, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.qhybrid;
+
+import android.content.Context;
+import android.net.Uri;
+
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.InstallActivity;
+import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.model.GenericItem;
+import nodomain.freeyourgadget.gadgetbridge.util.UriHelper;
+
+public class FossilInstallHandler implements InstallHandler {
+    private final Context mContext;
+    private boolean mIsValid;
+    private String mVersion = "(Unknown version)";
+
+    FossilInstallHandler(Uri uri, Context context) {
+        mContext = context;
+        UriHelper uriHelper;
+        try {
+            uriHelper = UriHelper.get(uri, mContext);
+        } catch (IOException e) {
+            mIsValid = false;
+            return;
+        }
+        try (InputStream in = new BufferedInputStream(uriHelper.openInputStream())) {
+            byte[] bytes = new byte[32];
+            int read = in.read(bytes);
+            if (read < 32) {
+                mIsValid = false;
+                return;
+            }
+
+            ByteBuffer buf = ByteBuffer.wrap(bytes);
+            buf.order(ByteOrder.LITTLE_ENDIAN);
+            int header0 = buf.getInt();
+            buf.getInt(); // size
+            int header2 = buf.getInt();
+            int header3 = buf.getInt();
+            if (header0 != 1 || header2 != 0x00012000 || header3 != 0x00012000) {
+                mIsValid = false;
+                return;
+            }
+
+            buf.getInt(); // unknown
+            int version1 = buf.get() % 0xff;
+            int version2 = buf.get() & 0xff;
+            mVersion = "DN0.0." + version1 + "." + version2;
+        } catch (Exception e) {
+            mIsValid = false;
+            return;
+        }
+        mIsValid = true;
+    }
+
+    @Override
+    public void validateInstallation(InstallActivity installActivity, GBDevice device) {
+        if (device.isBusy()) {
+            installActivity.setInfoText(device.getBusyTask());
+            installActivity.setInstallEnabled(false);
+            return;
+        }
+        if (device.getType() != DeviceType.FOSSILQHYBRID || !device.isConnected()) {
+            installActivity.setInfoText("Element cannot be installed 3");
+            installActivity.setInstallEnabled(false);
+            return;
+        }
+        GenericItem installItem = new GenericItem();
+        installItem.setIcon(R.drawable.ic_firmware);
+        installItem.setName("Fossil Hybrid Firmware");
+        installItem.setDetails(mVersion);
+
+        installActivity.setInfoText(mContext.getString(R.string.firmware_install_warning, "(unknown)"));
+        installActivity.setInstallEnabled(true);
+        installActivity.setInstallItem(installItem);
+    }
+
+
+    @Override
+    public void onStartInstall(GBDevice device) {
+    }
+
+    @Override
+    public boolean isValid() {
+        return mIsValid;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/HRConfigActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/HRConfigActivity.java
new file mode 100644
index 0000000..415d0eb
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/HRConfigActivity.java
@@ -0,0 +1,426 @@
+/*  Copyright (C) 2019-2024 Andreas Shimokawa, Arjan Schrijver, Daniel Dakhno,
+    Hasan Ammar
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.qhybrid;
+
+import static nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.QHybridSupport.QHYBRID_COMMAND_UPDATE_WIDGETS;
+
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.net.Uri;
+import android.os.Bundle;
+import android.util.SparseArray;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.ListView;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import com.google.android.material.dialog.MaterialAlertDialogBuilder;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBActivity;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.QHybridSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.widget.CustomBackgroundWidgetElement;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.widget.CustomTextWidgetElement;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.widget.CustomWidget;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.widget.CustomWidgetElement;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.widget.Widget;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+import nodomain.freeyourgadget.gadgetbridge.util.Version;
+
+public class HRConfigActivity extends AbstractGBActivity {
+    private SharedPreferences sharedPreferences;
+    private WidgetListAdapter widgetListAdapter;
+    private ArrayList<CustomWidget> customWidgets = new ArrayList<>();
+
+    SparseArray<String> widgetButtonsMapping = new SparseArray<>(4);
+
+    private static final int REQUEST_CODE_WIDGET_EDIT = 0;
+    private static final int REQUEST_CODE_IMAGE_PICK = 1;
+    private static final int REQUEST_CODE_IMAGE_EDIT = 2;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_qhybrid_hr_settings);
+
+        sharedPreferences = GBApplication.getPrefs().getPreferences();
+
+        initMappings();
+        loadWidgetConfigs();
+
+        final ListView widgetListView = findViewById(R.id.qhybrid_widget_list);
+        widgetListAdapter = new WidgetListAdapter(customWidgets);
+        widgetListView.setAdapter(widgetListAdapter);
+        widgetListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+            @Override
+            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+                Widget widget = widgetListAdapter.getItem(position);
+
+                Intent startIntent = new Intent(HRConfigActivity.this, WidgetSettingsActivity.class);
+                startIntent.putExtra("EXTRA_WIDGET", widget);
+                startIntent.putExtra("EXTRA_WIDGET_IDNEX", position);
+                startIntent.putExtra("EXTRA_WIDGET_INITIAL_NAME", ((CustomWidget) widget).getName());
+
+                startActivityForResult(startIntent, REQUEST_CODE_WIDGET_EDIT);
+            }
+        });
+        loadCustomWidgetList();
+
+        findViewById(R.id.qhybrid_widget_add).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                Intent startIntent = new Intent(HRConfigActivity.this, WidgetSettingsActivity.class);
+
+                startActivityForResult(startIntent, REQUEST_CODE_WIDGET_EDIT);
+            }
+        });
+
+        findViewById(R.id.qhybrid_set_background).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                new MaterialAlertDialogBuilder(HRConfigActivity.this)
+                        .setTitle("whoop whoop")
+                        .setMessage("background has to be pushed every time a custom widget changes, causing traffic and battery drain. Consider that when using custom widgets.")
+                        .setPositiveButton("ok", new DialogInterface.OnClickListener() {
+                            @Override
+                            public void onClick(DialogInterface dialogInterface, int i) {
+                                Intent pickIntent = new Intent(Intent.ACTION_PICK);
+                                pickIntent.setType("image/*");
+
+                                startActivityForResult(pickIntent, REQUEST_CODE_IMAGE_PICK);
+                            }
+                        })
+                        .setNegativeButton("nah", null)
+                        .show();
+            }
+        });
+
+        findViewById(R.id.qhybrid_unset_background).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                Intent intent = new Intent(QHybridSupport.QHYBRID_COMMAND_SET_BACKGROUND_IMAGE);
+                intent.putIntegerArrayListExtra("EXTRA_PIXELS", null);
+                LocalBroadcastManager.getInstance(HRConfigActivity.this).sendBroadcast(intent);
+            }
+        });
+
+        for (int i = 0; i < widgetButtonsMapping.size(); i++) {
+            final int widgetButtonId = widgetButtonsMapping.keyAt(i);
+            findViewById(widgetButtonId).setOnClickListener(new View.OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    Widget.WidgetType[] types = Widget.WidgetType.values();
+                    final ArrayList<String> names = new ArrayList<>(types.length);
+
+                    for (Widget.WidgetType type : types) {
+                        names.add(getResources().getString(type.getStringResource()));
+                    }
+
+                    for(CustomWidget customWidget : customWidgets){
+                        names.add(customWidget.getName());
+                    }
+
+                    final String[] nameStrings = names.toArray(new String[0]);
+                    new MaterialAlertDialogBuilder(HRConfigActivity.this)
+                            .setItems(
+                                    nameStrings,
+                                    new DialogInterface.OnClickListener() {
+                                        @Override
+                                        public void onClick(DialogInterface dialog, int which) {
+                                            saveWidgetSetting(widgetButtonId, which, nameStrings);
+                                        }
+                                    }
+                            )
+                            .show();
+
+                }
+            });
+        }
+
+        // Disable some functions on watches with too new firmware (from official app 4.6.0 and higher)
+        List<GBDevice> devices = GBApplication.app().getDeviceManager().getSelectedDevices();
+        for (GBDevice device : devices) {
+            if (device.getType() == DeviceType.FOSSILQHYBRID) {
+                String fwVersion_str = device.getFirmwareVersion2();
+                Version fwVersion = new Version(fwVersion_str);
+                if (fwVersion.compareTo(new Version("2.20")) >= 0) {
+                    findViewById(R.id.qhybrid_widget_add).setEnabled(false);
+                    for (int i = 0; i < widgetButtonsMapping.size(); i++) {
+                        final int widgetButtonId = widgetButtonsMapping.keyAt(i);
+                        findViewById(widgetButtonId).setEnabled(false);
+                    }
+                    findViewById(R.id.qhybrid_set_background).setEnabled(false);
+                    findViewById(R.id.qhybrid_unset_background).setEnabled(false);
+                    GB.toast(getString(R.string.fossil_hr_warning_firmware_too_new), Toast.LENGTH_LONG, GB.INFO);
+                }
+            }
+        }
+
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
+        super.onActivityResult(requestCode, resultCode, data);
+        if(data == null) return;
+        if(requestCode == REQUEST_CODE_WIDGET_EDIT) {
+            if (resultCode == WidgetSettingsActivity.RESULT_CODE_WIDGET_CREATED) {
+                CustomWidget widget = (CustomWidget) data.getExtras().get("EXTRA_WIDGET");
+                this.customWidgets.add(widget);
+                refreshWidgetList();
+                saveCustomWidgetList();
+
+                LocalBroadcastManager.getInstance(HRConfigActivity.this).sendBroadcast(new Intent(QHYBRID_COMMAND_UPDATE_WIDGETS));
+            } else if (resultCode == WidgetSettingsActivity.RESULT_CODE_WIDGET_UPDATED) {
+                CustomWidget widget = (CustomWidget) data.getExtras().get("EXTRA_WIDGET");
+                int updateIndex = data.getIntExtra("EXTRA_WIDGET_IDNEX", -1);
+
+                String initialName = data.getStringExtra("EXTRA_WIDGET_INITIAL_NAME");
+                String newName = widget.getName();
+
+                String widgetJSON = sharedPreferences.getString("FOSSIL_HR_WIDGETS", "{}");
+                widgetJSON = widgetJSON.replace("custom_" + initialName, "custom_" + newName);
+                sharedPreferences.edit().putString("FOSSIL_HR_WIDGETS", widgetJSON).apply();
+
+                this.customWidgets.set(updateIndex, widget);
+
+                loadWidgetConfigs();
+                refreshWidgetList();
+                saveCustomWidgetList();
+
+                LocalBroadcastManager.getInstance(HRConfigActivity.this).sendBroadcast(new Intent(QHYBRID_COMMAND_UPDATE_WIDGETS));
+            } else if (resultCode == WidgetSettingsActivity.RESULT_CODE_WIDGET_DELETED) {
+                int updateIndex = data.getIntExtra("EXTRA_WIDGET_IDNEX", -1);
+
+                this.customWidgets.remove(updateIndex);
+
+                refreshWidgetList();
+                saveCustomWidgetList();
+
+                LocalBroadcastManager.getInstance(HRConfigActivity.this).sendBroadcast(new Intent(QHYBRID_COMMAND_UPDATE_WIDGETS));
+            }
+        }else if(requestCode == REQUEST_CODE_IMAGE_PICK){
+            if (resultCode == RESULT_OK)
+            {
+                Uri imageUri = data.getData();
+                Intent activityIntent = new Intent();
+                activityIntent.setClass(this, ImageEditActivity.class);
+                activityIntent.setData(imageUri);
+
+                startActivityForResult(activityIntent, REQUEST_CODE_IMAGE_EDIT);
+            }
+        }else if(requestCode == REQUEST_CODE_IMAGE_EDIT){
+            if(resultCode == ImageEditActivity.RESULT_CODE_EDIT_SUCCESS){
+                data.setAction(QHybridSupport.QHYBRID_COMMAND_SET_BACKGROUND_IMAGE);
+                LocalBroadcastManager.getInstance(this).sendBroadcast(data);
+            }
+        }
+    }
+
+    private void saveCustomWidgetList() {
+        try {
+            JSONArray widgetArray = new JSONArray();
+            for(CustomWidget widget : customWidgets){
+                JSONArray elementArray = new JSONArray();
+
+                for(CustomWidgetElement element : widget.getElements()){
+                    JSONObject elementObject = new JSONObject();
+                    elementObject
+                            .put("type", element.getWidgetElementType().getJsonIdentifier())
+                            .put("id", element.getId())
+                            .put("value", element.getValue())
+                            .put("x", element.getX())
+                            .put("y", element.getY());
+                    elementArray.put(elementObject);
+                }
+
+                JSONObject widgetObject = new JSONObject();
+                widgetObject
+                        .put("name", widget.getName())
+                        .put("elements", elementArray);
+
+                widgetArray.put(widgetObject);
+            }
+            sharedPreferences.edit().putString("QHYBRID_CUSTOM_WIDGETS", widgetArray.toString()).apply();
+        } catch (JSONException e) {
+            e.printStackTrace();
+        }
+    }
+
+    private void loadCustomWidgetList() {
+        String customWidgetJson = sharedPreferences.getString("QHYBRID_CUSTOM_WIDGETS", "[]");
+
+        try {
+            JSONArray customWidgets = new JSONArray(customWidgetJson);
+            this.customWidgets.clear();
+
+            for (int i = 0; i < customWidgets.length(); i++) {
+                JSONObject customWidgetObject = customWidgets.getJSONObject(i);
+                CustomWidget widget = new CustomWidget(
+                        customWidgetObject.getString("name"), 0, 0, "default" // FIXME: handle force white background
+                );
+                JSONArray elements = customWidgetObject.getJSONArray("elements");
+
+                for (int i2 = 0; i2 < elements.length(); i2++) {
+                    JSONObject element = elements.getJSONObject(i2);
+                    if (element.getString("type").equals("text")) {
+                        widget.addElement(new CustomTextWidgetElement(
+                                element.getString("id"),
+                                element.getString("value"),
+                                element.getInt("x"),
+                                element.getInt("y")
+                        ));
+                    } else if (element.getString("type").equals("background")) {
+                        widget.addElement(new CustomBackgroundWidgetElement(
+                                element.getString("id"),
+                                element.getString("value")
+                        ));
+                    }
+                }
+
+                this.customWidgets.add(widget);
+            }
+
+            refreshWidgetList();
+        } catch (JSONException e) {
+            e.printStackTrace();
+        }
+    }
+
+    private void refreshWidgetList() {
+        widgetListAdapter.notifyDataSetChanged();
+    }
+
+    private void saveWidgetSetting(int buttonId, int option, String[] names) {
+        String jsonKey = widgetButtonsMapping.get(buttonId);
+        Widget.WidgetType[] types = Widget.WidgetType.values();
+        String identifier = null;
+        if(option < types.length){
+            Widget.WidgetType type = types[option];
+            identifier = type.getIdentifier();
+        }else{
+            identifier = "custom_" + names[option];
+        }
+
+        try {
+            JSONObject keyConfig = new JSONObject(sharedPreferences.getString("FOSSIL_HR_WIDGETS", "{}"));
+            if (identifier != null) {
+                keyConfig.put(jsonKey, identifier);
+            } else {
+                keyConfig.remove(jsonKey);
+            }
+            sharedPreferences.edit().putString("FOSSIL_HR_WIDGETS", keyConfig.toString()).apply();
+            LocalBroadcastManager.getInstance(this).sendBroadcast(new Intent(QHYBRID_COMMAND_UPDATE_WIDGETS));
+
+            loadWidgetConfigs();
+        } catch (JSONException e) {
+            e.printStackTrace();
+        }
+
+    }
+
+
+    private void loadWidgetConfigs() {
+        try {
+            for (int i = 0; i < widgetButtonsMapping.size(); i++) {
+                ((TextView) findViewById(widgetButtonsMapping.keyAt(i))).setText(widgetButtonsMapping.valueAt(i) + " widget");
+            }
+
+            JSONObject keyConfig = new JSONObject(sharedPreferences.getString("FOSSIL_HR_WIDGETS", "{}"));
+            Iterator<String> keyIterator = keyConfig.keys();
+
+            loop:
+            while (keyIterator.hasNext()) {
+                String position = keyIterator.next();
+
+                for (int widgetButtonIndex = 0; widgetButtonIndex < widgetButtonsMapping.size(); widgetButtonIndex++) {
+                    if (position.equals(widgetButtonsMapping.valueAt(widgetButtonIndex))) {
+                        int buttonId = widgetButtonsMapping.keyAt(widgetButtonIndex);
+                        String function = keyConfig.getString(position);
+
+                        Widget.WidgetType[] types = Widget.WidgetType.values();
+                        if(function.startsWith("custom_")){
+                            ((TextView) findViewById(buttonId)).setText(
+                                    position + " widget: " + function.substring(7)
+                            );
+                            continue loop;
+                        }
+                        for (int widgetIdIndex = 0; widgetIdIndex < types.length; widgetIdIndex++) {
+                            String widgetIdMappingValue = types[widgetIdIndex].getIdentifier();
+                            if (widgetIdMappingValue != null && widgetIdMappingValue.equals(function)) {
+                                ((TextView) findViewById(buttonId)).setText(
+                                        position + " widget: "
+                                                + getResources().getText(
+                                                types[widgetIdIndex].getStringResource()
+                                        )
+                                );
+                                break;
+                            }
+                        }
+                    }
+                }
+            }
+        } catch (JSONException e) {
+            e.printStackTrace();
+        }
+    }
+
+    private void initMappings() {
+        widgetButtonsMapping.put(R.id.qhybrid_button_widget_top, "top");
+        widgetButtonsMapping.put(R.id.qhybrid_button_widget_right, "right");
+        widgetButtonsMapping.put(R.id.qhybrid_button_widget_bottom, "bottom");
+        widgetButtonsMapping.put(R.id.qhybrid_button_widget_left, "left");
+    }
+
+    class WidgetListAdapter extends ArrayAdapter<CustomWidget> {
+        public WidgetListAdapter(@NonNull List<CustomWidget> objects) {
+            super(HRConfigActivity.this, 0, objects);
+        }
+
+        @NonNull
+        @Override
+        public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
+            if (convertView == null) convertView = new TextView(getContext());
+            TextView view = (TextView) convertView;
+
+            view.setText(getItem(position).getName());
+            // view.setTextColor(Color.WHITE);
+            view.setTextSize(25);
+
+            return view;
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/HybridHRActivitySampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/HybridHRActivitySampleProvider.java
new file mode 100644
index 0000000..a5eed76
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/HybridHRActivitySampleProvider.java
@@ -0,0 +1,80 @@
+/*  Copyright (C) 2020-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.qhybrid;
+
+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.HybridHRActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.entities.HybridHRActivitySampleDao;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.parser.ActivityEntry;
+
+public class HybridHRActivitySampleProvider extends AbstractSampleProvider<HybridHRActivitySample> {
+    public HybridHRActivitySampleProvider(GBDevice device, DaoSession session) {
+        super(device, session);
+    }
+
+    @Override
+    public AbstractDao<HybridHRActivitySample, ?> getSampleDao() {
+        return getSession().getHybridHRActivitySampleDao();
+    }
+
+    @Nullable
+    @Override
+    protected Property getRawKindSampleProperty() {
+        return null;
+    }
+
+    @NonNull
+    @Override
+    protected Property getTimestampSampleProperty() {
+        return HybridHRActivitySampleDao.Properties.Timestamp;
+    }
+
+    @NonNull
+    @Override
+    protected Property getDeviceIdentifierSampleProperty() {
+        return HybridHRActivitySampleDao.Properties.DeviceId;
+    }
+
+    @Override
+    public ActivityKind normalizeType(int rawType) {
+        if (rawType == -1) return ActivityKind.UNKNOWN;
+        return ActivityEntry.WEARING_STATE.fromValue((byte) rawType).getActivityKind();
+    }
+
+    @Override
+    public int toRawActivityKind(ActivityKind activityKind) {
+        return 0;
+    }
+
+    @Override
+    public float normalizeIntensity(int rawIntensity) {
+        return Math.min(rawIntensity / 128f, 1f);
+    }
+
+    @Override
+    public HybridHRActivitySample createActivitySample() {
+        return new HybridHRActivitySample();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/HybridHRSpo2SampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/HybridHRSpo2SampleProvider.java
new file mode 100644
index 0000000..3bad94b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/HybridHRSpo2SampleProvider.java
@@ -0,0 +1,56 @@
+/*  Copyright (C) 2024 Benjamin Swartley
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.qhybrid;
+
+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.HybridHRSpo2Sample;
+import nodomain.freeyourgadget.gadgetbridge.entities.HybridHRSpo2SampleDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class HybridHRSpo2SampleProvider extends AbstractTimeSampleProvider<HybridHRSpo2Sample> {
+    public HybridHRSpo2SampleProvider(GBDevice device, DaoSession session) {
+        super(device, session);
+    }
+
+    @NonNull
+    @Override
+    public AbstractDao<HybridHRSpo2Sample, ?> getSampleDao() {
+        return getSession().getHybridHRSpo2SampleDao();
+    }
+
+    @NonNull
+    @Override
+    protected Property getTimestampSampleProperty() {
+        return HybridHRSpo2SampleDao.Properties.Timestamp;
+    }
+
+    @NonNull
+    @Override
+    protected Property getDeviceIdentifierSampleProperty() {
+        return HybridHRSpo2SampleDao.Properties.DeviceId;
+    }
+
+    @Override
+    public HybridHRSpo2Sample createSample() {
+        return new HybridHRSpo2Sample();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/HybridHRWatchfaceDesignerActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/HybridHRWatchfaceDesignerActivity.java
new file mode 100644
index 0000000..2484ead
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/HybridHRWatchfaceDesignerActivity.java
@@ -0,0 +1,676 @@
+/*  Copyright (C) 2021-2024 Arjan Schrijver, Daniel Dakhno, HelloCodeberg,
+    Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.qhybrid;
+
+import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.SharedPreferences;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.provider.MediaStore;
+import android.util.DisplayMetrics;
+import android.view.DragEvent;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import com.google.android.material.dialog.MaterialAlertDialogBuilder;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBActivity;
+import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.QHybridSupport;
+import nodomain.freeyourgadget.gadgetbridge.util.AndroidUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.BitmapUtil;
+import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class HybridHRWatchfaceDesignerActivity extends AbstractGBActivity implements View.OnClickListener, View.OnLongClickListener, View.OnDragListener {
+    private final Logger LOG = LoggerFactory.getLogger(HybridHRWatchfaceDesignerActivity.class);
+    private GBDevice mGBDevice;
+    private DeviceCoordinator mCoordinator;
+    private int displayImageSize = 0;
+    private float scaleFactor = 0;
+    private ImageView backgroundImageView;
+    private Bitmap selectedBackgroundImage, processedBackgroundImage;
+    private String watchfaceName = "NewWatchface";
+    final private ArrayList<HybridHRWatchfaceWidget> widgets = new ArrayList<>();
+    private HybridHRWatchfaceSettings watchfaceSettings = new HybridHRWatchfaceSettings();
+    private int defaultWidgetColor = HybridHRWatchfaceWidget.COLOR_WHITE;
+    private boolean readyToCloseActivity = false;
+
+    private final int CHILD_ACTIVITY_IMAGE_CHOOSER = 0;
+    private final int CHILD_ACTIVITY_SETTINGS = 1;
+    private final int CHILD_ACTIVITY_WIDGET = 2;
+
+    BroadcastReceiver fileUploadReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (intent.getAction().equals(QHybridSupport.QHYBRID_ACTION_UPLOADED_FILE)) {
+                boolean success = intent.getBooleanExtra("EXTRA_SUCCESS", false);
+                findViewById(R.id.watchface_upload_progress_bar).setVisibility(View.GONE);
+                if (success) {
+                    if (readyToCloseActivity) {
+                        finish();
+                    }
+                } else {
+                    readyToCloseActivity = false;
+                    new MaterialAlertDialogBuilder(HybridHRWatchfaceDesignerActivity.this)
+                            .setMessage(R.string.watchface_upload_failed)
+                            .setPositiveButton(R.string.ok, null)
+                            .show();
+                }
+            }
+        }
+    };
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_hybridhr_watchface_designer);
+
+        Intent intent = getIntent();
+        Bundle bundle = intent.getExtras();
+        if (bundle != null) {
+            mGBDevice = bundle.getParcelable(GBDevice.EXTRA_DEVICE);
+        } else {
+            throw new IllegalArgumentException("Must provide a device when invoking this activity");
+        }
+
+        IntentFilter intentFilter = new IntentFilter();
+        intentFilter.addAction(QHybridSupport.QHYBRID_ACTION_UPLOADED_FILE);
+        LocalBroadcastManager.getInstance(this).registerReceiver(fileUploadReceiver, intentFilter);
+
+        mCoordinator = mGBDevice.getDeviceCoordinator();
+        calculateDisplayImageSize();
+        backgroundImageView = findViewById(R.id.hybridhr_background_image);
+
+        if (bundle.containsKey(GBDevice.EXTRA_UUID)) {
+            String appUUID = bundle.getString(GBDevice.EXTRA_UUID);
+            loadConfigurationFromApp(appUUID);
+        }
+
+        renderWatchfacePreview();
+
+        backgroundImageView.setOnDragListener(this);
+        findViewById(R.id.watchface_widget_delete_droparea).setOnDragListener(this);
+        findViewById(R.id.watchface_invert_colors).setOnClickListener(this);
+        findViewById(R.id.button_edit_name).setOnClickListener(this);
+        findViewById(R.id.button_set_background).setOnClickListener(this);
+        findViewById(R.id.button_add_widget).setOnClickListener(this);
+        findViewById(R.id.button_watchface_settings).setOnClickListener(this);
+        findViewById(R.id.watchface_rotate_left).setOnClickListener(this);
+        findViewById(R.id.watchface_rotate_right).setOnClickListener(this);
+        findViewById(R.id.watchface_remove_image).setOnClickListener(this);
+        findViewById(R.id.button_watchface_open_menu_companion).setOnClickListener(this);
+        findViewById(R.id.button_watchface_reset_menu_structure).setOnClickListener(this);
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        reloadMenuStructureIndicator();
+    }
+
+    @Override
+    public void onActivityResult(int requestCode, int resultCode, Intent resultData) {
+        super.onActivityResult(requestCode, resultCode, resultData);
+        if (requestCode == CHILD_ACTIVITY_IMAGE_CHOOSER && resultCode == Activity.RESULT_OK) {
+            Uri imageUri = resultData.getData();
+            if (imageUri == null) {
+                LOG.warn("No image selected");
+                return;
+            }
+            try {
+                selectedBackgroundImage = createImageFromURI(imageUri);
+            } catch (IOException e) {
+                LOG.warn("Error converting selected image to Bitmap", e);
+                return;
+            }
+            renderWatchfacePreview();
+        } else if (requestCode == CHILD_ACTIVITY_SETTINGS && resultCode == Activity.RESULT_OK && resultData != null) {
+            watchfaceSettings = (HybridHRWatchfaceSettings) resultData.getSerializableExtra("watchfaceSettings");
+        } else if (requestCode == CHILD_ACTIVITY_WIDGET && resultCode == Activity.RESULT_OK && resultData != null) {
+            int widgetIndex = (int) resultData.getSerializableExtra("widgetIndex");
+            HybridHRWatchfaceWidget editedWidget = (HybridHRWatchfaceWidget) resultData.getSerializableExtra("widgetSettings");
+            if (widgetIndex >= 0) {
+                widgets.set(widgetIndex, editedWidget);
+            } else {
+                widgets.add(editedWidget);
+            }
+            renderWatchfacePreview();
+        }
+    }
+
+    // Add action bar buttons
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        getMenuInflater().inflate(R.menu.activity_hybridhr_watchface_designer_actionbar, menu);
+        return super.onCreateOptionsMenu(menu);
+    }
+
+    // Handle action bar button presses
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        int id = item.getItemId();
+        if (id == R.id.button_save_watchface) {
+            sendToWatch(false);
+        } else if (id == R.id.button_preview_watchface) {
+            sendToWatch(true);
+        }
+        return super.onOptionsItemSelected(item);
+    }
+
+    @Override
+    public void onClick(View v) {
+        int buttonId = v.getId();
+        if (buttonId == R.id.button_edit_name) {
+            final EditText input = new EditText(this);
+            input.setText(watchfaceName);
+            input.setId(0);
+            LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
+                    LinearLayout.LayoutParams.MATCH_PARENT,
+                    LinearLayout.LayoutParams.MATCH_PARENT);
+            input.setLayoutParams(lp);
+            new MaterialAlertDialogBuilder(this)
+                    .setView(input)
+                    .setNegativeButton(R.string.fossil_hr_new_action_cancel, null)
+                    .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
+                        @Override
+                        public void onClick(DialogInterface dialog, int which) {
+                            setWatchfaceName(input.getText().toString());
+                        }
+                    })
+                    .setTitle(R.string.watchface_dialog_title_set_name)
+                    .show();
+        } else if (buttonId == R.id.watchface_invert_colors) {
+            if (selectedBackgroundImage != null) {
+                selectedBackgroundImage = BitmapUtil.invertBitmapColors(selectedBackgroundImage);
+                for (int i=0; i<widgets.size(); i++) {
+                    HybridHRWatchfaceWidget widget = widgets.get(i);
+                    widget.setColor(widget.getColor() ^ 1);
+                    widgets.set(i, widget);
+                }
+                renderWatchfacePreview();
+                if (defaultWidgetColor == HybridHRWatchfaceWidget.COLOR_WHITE) {
+                    defaultWidgetColor = HybridHRWatchfaceWidget.COLOR_BLACK;
+                } else {
+                    defaultWidgetColor = HybridHRWatchfaceWidget.COLOR_WHITE;
+                }
+            }
+        } else if (buttonId == R.id.button_set_background) {
+            Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
+            intent.addCategory(Intent.CATEGORY_OPENABLE);
+            intent.setType("image/*");
+            startActivityForResult(intent, CHILD_ACTIVITY_IMAGE_CHOOSER);
+        } else if (buttonId == R.id.button_add_widget) {
+            showWidgetEditPopup(-1);
+        } else if (buttonId == R.id.button_watchface_settings) {
+            showWatchfaceSettingsPopup();
+        } else if (buttonId == R.id.watchface_rotate_left) {
+            if (selectedBackgroundImage != null) {
+                selectedBackgroundImage = BitmapUtil.rotateImage(selectedBackgroundImage, -90);
+                renderWatchfacePreview();
+            }
+        } else if (buttonId== R.id.watchface_rotate_right) {
+            if (selectedBackgroundImage != null) {
+                selectedBackgroundImage = BitmapUtil.rotateImage(selectedBackgroundImage, 90);
+                renderWatchfacePreview();
+            }
+        } else if (buttonId == R.id.watchface_remove_image) {
+            deleteWatchfaceBackground();
+            renderWatchfacePreview();
+        } else if(buttonId == R.id.button_watchface_open_menu_companion){
+            try {
+                AndroidUtils.openApp("d.d.hrmenucompanion");
+            } catch (Exception e) {
+                GB.toast(getString(R.string.error_menu_companion_not_installed), Toast.LENGTH_SHORT, GB.INFO);
+
+                AndroidUtils.openWebsite("https://github.com/dakhnod/Fossil-HR-Menu-Companion/releases/latest");
+            }
+        } else if(buttonId == R.id.button_watchface_reset_menu_structure) {
+            SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(mGBDevice.getAddress());
+            prefs.edit().remove("MENU_STRUCTURE_JSON").apply();
+            GB.toast(getString(R.string.info_menu_structure_removed), Toast.LENGTH_SHORT, GB.INFO);
+            reloadMenuStructureIndicator();
+        }
+    }
+
+    private void reloadMenuStructureIndicator(){
+        SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(mGBDevice.getAddress());
+        String menuStructureJson = prefs.getString("MENU_STRUCTURE_JSON", "");
+
+        boolean active = !menuStructureJson.isEmpty();
+
+        ((Button) findViewById(R.id.button_watchface_reset_menu_structure))
+                .setEnabled(active);
+        findViewById(R.id.fossil_menu_structure_hint_container).setVisibility(active ? View.VISIBLE : View.GONE);
+
+        if(active){
+            ((TextView)findViewById(R.id.text_watchface_menu_structure))
+                    .setText(getString(R.string.info_menu_structure_contents, menuStructureJson));
+        }
+    }
+
+    @Override
+    public boolean onLongClick(View view) {
+        view.startDrag(null, new View.DragShadowBuilder(view), view, 0);
+        view.setVisibility(View.INVISIBLE);
+        return true;
+    }
+
+    @Override
+    public boolean onDrag(View targetView, DragEvent event) {
+        View draggedWidget = (View) event.getLocalState();
+        switch (event.getAction()) {
+            case DragEvent.ACTION_DRAG_STARTED:
+                findViewById(R.id.watchface_widget_delete_layout).setVisibility(View.VISIBLE);
+                break;
+            case DragEvent.ACTION_DRAG_ENTERED:
+                if (targetView.getId() == R.id.watchface_widget_delete_droparea) {
+                    findViewById(R.id.watchface_widget_delete_droparea).setBackgroundColor(Color.RED);
+                }
+                break;
+            case DragEvent.ACTION_DRAG_EXITED:
+                if (targetView.getId() == R.id.watchface_widget_delete_droparea) {
+                    findViewById(R.id.watchface_widget_delete_droparea).setBackgroundColor(Color.TRANSPARENT);
+                }
+                break;
+            case DragEvent.ACTION_DROP:
+                if (targetView.getId() == R.id.watchface_widget_delete_droparea) {
+                    widgets.remove(draggedWidget.getId());
+                    renderWatchfacePreview();
+                } else if (targetView.getId() == R.id.hybridhr_background_image) {
+                    int posX = (int) (event.getX() / scaleFactor);
+                    int posY = (int) (event.getY() / scaleFactor);
+                    widgets.get(draggedWidget.getId()).setPosX(posX);
+                    widgets.get(draggedWidget.getId()).setPosY(posY);
+                    renderWatchfacePreview();
+                }
+                break;
+            case DragEvent.ACTION_DRAG_ENDED:
+                if (!event.getResult()) {
+                    draggedWidget.setVisibility(View.VISIBLE);
+                }
+                findViewById(R.id.watchface_widget_delete_droparea).setBackgroundColor(Color.TRANSPARENT);
+                findViewById(R.id.watchface_widget_delete_layout).setVisibility(View.GONE);
+                break;
+        }
+        return true;
+    }
+
+    private void loadConfigurationFromApp(String appUUID) {
+        File appCacheDir;
+        try {
+            appCacheDir = mCoordinator.getAppCacheDir();
+        } catch (IOException e) {
+            LOG.warn("Could not get external dir while trying to access app cache.", e);
+            return;
+        }
+        File backgroundFile = new File(appCacheDir, appUUID + "_bg.png");
+        if (!backgroundFile.exists()) {
+            backgroundFile = new File(appCacheDir, appUUID + ".png");
+        }
+        try {
+            Bitmap cachedBackground = BitmapFactory.decodeStream(new FileInputStream(backgroundFile));
+            selectedBackgroundImage = BitmapUtil.convertToGrayscale(BitmapUtil.getCircularBitmap(cachedBackground));
+        } catch (IOException e) {
+            LOG.warn("Error loading cached background image", e);
+        }
+        File cachedFile = new File(appCacheDir, appUUID + mCoordinator.getAppFileExtension());
+        FossilFileReader fileReader;
+        try {
+            fileReader = new FossilFileReader(Uri.fromFile(cachedFile), this);
+        } catch (IOException e) {
+            LOG.warn("Could not open cached app file", e);
+            return;
+        }
+        setWatchfaceName(fileReader.getName());
+        JSONObject configJSON;
+        try {
+            configJSON = fileReader.getConfigJSON("customWatchFace");
+        } catch (IOException e) {
+            LOG.warn("Could not read config from cached app file", e);
+            return;
+        } catch (JSONException e) {
+            LOG.warn("JSON parsing error", e);
+            return;
+        }
+        if (configJSON == null) {
+            return;
+        }
+        for (Iterator<String> it = configJSON.keys(); it.hasNext(); ) {
+            String key = it.next();
+            if (key.equals("layout")) {
+                try {
+                    JSONArray layout = configJSON.getJSONArray(key);
+                    for (int i = 0; i < layout.length(); i++) {
+                        JSONObject layoutItem = layout.getJSONObject(i);
+                        if (layoutItem.getString("type").equals("comp")) {
+                            widgets.add(HybridHRWatchfaceFactory.parseWidgetJSON(layoutItem));
+                        }
+                    }
+                } catch (JSONException e) {
+                    LOG.warn("JSON parsing error", e);
+                }
+            } else if (key.equals("config")) {
+                try {
+                    JSONObject watchfaceConfig = configJSON.getJSONObject(key);
+                    if (watchfaceConfig.has("timeout_display_full")) {
+                        watchfaceSettings.setDisplayTimeoutFull(watchfaceConfig.getInt("timeout_display_full") / 60 / 1000);
+                    }
+                    if (watchfaceConfig.has("timeout_display_partial")) {
+                        watchfaceSettings.setDisplayTimeoutPartial(watchfaceConfig.getInt("timeout_display_partial") / 60 / 1000);
+                    }
+                    if (watchfaceConfig.has("wrist_flick_hands_relative")) {
+                        watchfaceSettings.setWristFlickHandsMoveRelative(watchfaceConfig.getBoolean("wrist_flick_hands_relative"));
+                    }
+                    if (watchfaceConfig.has("wrist_flick_duration")) {
+                        watchfaceSettings.setWristFlickDuration(watchfaceConfig.getInt("wrist_flick_duration"));
+                    }
+                    if (watchfaceConfig.has("wrist_flick_move_hour")) {
+                        watchfaceSettings.setWristFlickMoveHour(watchfaceConfig.getInt("wrist_flick_move_hour"));
+                    }
+                    if (watchfaceConfig.has("wrist_flick_move_minute")) {
+                        watchfaceSettings.setWristFlickMoveMinute(watchfaceConfig.getInt("wrist_flick_move_minute"));
+                    }
+                    if (watchfaceConfig.has("toggle_widgets_event")) {
+                        watchfaceSettings.setToggleWidgetsEvent(watchfaceConfig.getString("toggle_widgets_event"));
+                    }
+                    if (watchfaceConfig.has("toggle_backlight_event")) {
+                        watchfaceSettings.setToggleBacklightEvent(watchfaceConfig.getString("toggle_backlight_event"));
+                    }
+                    if (watchfaceConfig.has("move_hands_event")) {
+                        watchfaceSettings.setMoveHandsEvent(watchfaceConfig.getString("move_hands_event"));
+                    }
+                    if (watchfaceConfig.has("powersave_display")) {
+                        watchfaceSettings.setPowersaveDisplay(watchfaceConfig.getBoolean("powersave_display"));
+                    }
+                    if (watchfaceConfig.has("powersave_hands")) {
+                        watchfaceSettings.setPowersaveHands(watchfaceConfig.getBoolean("powersave_hands"));
+                    }
+                    if (watchfaceConfig.has("light_up_on_notification")) {
+                        watchfaceSettings.setLightUpOnNotification(watchfaceConfig.getBoolean("light_up_on_notification"));
+                    }
+                } catch (JSONException e) {
+                    LOG.warn("JSON parsing error", e);
+                }
+            }
+        }
+    }
+
+    private void setWatchfaceName(String name) {
+        watchfaceName = name;
+        TextView watchfaceNameView = findViewById(R.id.watchface_name);
+        watchfaceNameView.setText(watchfaceName);
+    }
+
+    private void renderWatchfacePreview() {
+        int widgetSize = QHybridConstants.HYBRID_HR_WATCHFACE_WIDGET_SIZE;
+        if (selectedBackgroundImage == null) {
+            try {
+                selectedBackgroundImage = BitmapUtil.getCircularBitmap(BitmapFactory.decodeStream(getAssets().open("fossil_hr/default_background.png")));
+            } catch (IOException e) {
+                LOG.warn("Loading default watchface background failed", e);
+            }
+        }
+        if (selectedBackgroundImage == null) {
+            deleteWatchfaceBackground();
+        } else {
+            processedBackgroundImage = Bitmap.createScaledBitmap(selectedBackgroundImage, displayImageSize, displayImageSize, true);
+        }
+        // Remove existing widget ImageViews
+        RelativeLayout previewLayout = this.findViewById(R.id.watchface_preview_layout);
+        boolean onlyPreviewIsRemaining = false;
+        while (!onlyPreviewIsRemaining) {
+            int childCount = previewLayout.getChildCount();
+            int i;
+            for(i=0; i<childCount; i++) {
+                View currentChild = previewLayout.getChildAt(i);
+                if (currentChild.getId() != R.id.hybridhr_background_image) {
+                    previewLayout.removeView(currentChild);
+                    break;
+                }
+            }
+            if (i == childCount) {
+                onlyPreviewIsRemaining = true;
+            }
+        }
+        // Paint a gray circle around the watchface
+        Canvas backgroundImageCanvas = new Canvas(processedBackgroundImage);
+        Paint circlePaint = new Paint();
+        circlePaint.setColor(Color.GRAY);
+        circlePaint.setAntiAlias(true);
+        circlePaint.setStrokeWidth(3);
+        circlePaint.setStyle(Paint.Style.STROKE);
+        backgroundImageCanvas.drawCircle(displayImageSize/2f, displayImageSize/2f, displayImageSize/2f - 2, circlePaint);
+        // Dynamically add an ImageView for each widget
+        Paint widgetPaint = new Paint();
+        widgetPaint.setColor(Color.RED);
+        widgetPaint.setStyle(Paint.Style.STROKE);
+        widgetPaint.setStrokeWidth(5);
+        Bitmap widgetNoPreviewBitmap = Bitmap.createBitmap((int)(widgetSize * scaleFactor), (int)(widgetSize * scaleFactor), Bitmap.Config.ARGB_8888);
+        Canvas widgetCanvas = new Canvas(widgetNoPreviewBitmap);
+        widgetCanvas.drawRect(0, 0, widgetSize * scaleFactor, widgetSize * scaleFactor, widgetPaint);
+        for (int i=0; i<widgets.size(); i++) {
+            HybridHRWatchfaceWidget widget = widgets.get(i);
+            RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
+            layoutParams.addRule(RelativeLayout.ALIGN_LEFT, backgroundImageView.getId());
+            layoutParams.addRule(RelativeLayout.ALIGN_TOP, backgroundImageView.getId());
+            layoutParams.setMargins((int) ((widget.getPosX() - widgetSize/2) * scaleFactor), (int) ((widget.getPosY() - widgetSize/2) * scaleFactor), 0, 0);
+            ImageView widgetView = new ImageView(this);
+            widgetView.setId(i);
+            try {
+                widgetView.setImageBitmap(Bitmap.createScaledBitmap(widget.getPreviewImage(this), (int)(widgetSize * scaleFactor), (int)(widgetSize * scaleFactor), true));
+            } catch (IOException e) {
+                widgetView.setImageBitmap(widgetNoPreviewBitmap);
+            }
+            widgetView.setLayoutParams(layoutParams);
+            widgetView.setOnClickListener(new View.OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    showWidgetEditPopup(v.getId());
+                }
+            });
+            widgetView.setOnLongClickListener(this);
+            previewLayout.addView(widgetView);
+        }
+        backgroundImageView.setImageBitmap(processedBackgroundImage);
+    }
+
+    private void showWidgetEditPopup(int index) {
+        HybridHRWatchfaceWidget widget;
+        if (index >= 0) {
+            widget = widgets.get(index);
+        } else {
+            int posX = 120;
+            int posY = 120;
+            int currentIndex = widgets.size();
+            if (currentIndex < 4) {
+                HybridHRWidgetPosition newPosition = HybridHRWatchfaceWidget.defaultPositions[currentIndex];
+                posX = newPosition.posX;
+                posY = newPosition.posY;
+                GB.toast(getString(R.string.watchface_dialog_pre_setting_position, getString(newPosition.hintStringResource)), Toast.LENGTH_SHORT, GB.INFO);
+            }
+            int color = defaultWidgetColor;
+            if (widgets.size() > 0) {
+                color = widgets.get(0).getColor();
+            }
+            widget = new HybridHRWatchfaceWidget("widgetDate", posX, posY, 76, 76, color, null);
+        }
+        Intent intent = new Intent(this, HybridHRWatchfaceWidgetActivity.class);
+        intent.putExtra("widgetIndex", index);
+        intent.putExtra("widgetSettings", widget);
+        startActivityForResult(intent, CHILD_ACTIVITY_WIDGET);
+    }
+
+    private void showWatchfaceSettingsPopup() {
+        Intent intent = new Intent(this, HybridHRWatchfaceSettingsActivity.class);
+        intent.putExtra("watchfaceSettings", watchfaceSettings);
+        startActivityForResult(intent, CHILD_ACTIVITY_SETTINGS);
+    }
+
+    private void calculateDisplayImageSize() {
+        DisplayMetrics displayMetrics = new DisplayMetrics();
+        getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
+        displayImageSize = (int) Math.round(displayMetrics.widthPixels * 0.75);
+        scaleFactor = displayImageSize / 240f;
+    }
+
+    private Bitmap createImageFromURI(Uri imageUri) throws IOException, RuntimeException {
+        if (imageUri == null) {
+            throw new RuntimeException("No image selected");
+        }
+
+//        UriHelper uriHelper = UriHelper.get(imageUri, this);
+//        InputStream in = new BufferedInputStream(uriHelper.openInputStream());
+//        Bitmap bitmap = BitmapFactory.decodeStream(in);
+
+        ContentResolver resolver = getContentResolver();
+        Cursor c = resolver.query(imageUri, new String[]{MediaStore.Images.ImageColumns.ORIENTATION}, null, null, null);
+        c.moveToFirst();
+        int orientation = c.getInt(c.getColumnIndex(MediaStore.Images.ImageColumns.ORIENTATION));
+        c.close();
+        Bitmap bitmap = MediaStore.Images.Media.getBitmap(this.getContentResolver(), imageUri);
+        if (orientation != 0) {  // FIXME: doesn't seem to work
+            Matrix matrix = new Matrix();
+            matrix.postRotate(90);
+            bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
+        }
+
+        return BitmapUtil.convertToGrayscale(BitmapUtil.getCircularBitmap(bitmap));
+    }
+
+    private void deleteWatchfaceBackground() {
+        selectedBackgroundImage = Bitmap.createBitmap(displayImageSize, displayImageSize, Bitmap.Config.ARGB_8888);
+        selectedBackgroundImage.eraseColor(Color.BLACK);
+        selectedBackgroundImage = BitmapUtil.getCircularBitmap(selectedBackgroundImage);
+    }
+
+    private void sendToWatch(boolean preview) {
+        final Context mContext = this;
+        final HybridHRWatchfaceFactory wfFactory;
+        if (preview) {
+            wfFactory = new HybridHRWatchfaceFactory("previewWatchface");
+        } else {
+            wfFactory = new HybridHRWatchfaceFactory(watchfaceName);
+        }
+        wfFactory.setSettings(watchfaceSettings);
+        wfFactory.setBackground(selectedBackgroundImage);
+        wfFactory.addWidgets(widgets);
+
+        SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(mGBDevice.getAddress());
+        String menuStructureJson = prefs.getString("MENU_STRUCTURE_JSON", "");
+        if(!menuStructureJson.isEmpty()){
+            try {
+                JSONObject menuStructure = new JSONObject(menuStructureJson);
+                wfFactory.setMenuStructure(menuStructure);
+            } catch (JSONException e) {
+                LOG.error("Error loading menu structure", e);
+            }
+        }
+
+        try {
+            File tempFile = File.createTempFile("tmpWatchfaceFile", null);
+            tempFile.deleteOnExit();
+            FileOutputStream fos = new FileOutputStream(tempFile);
+            BufferedOutputStream bos = new BufferedOutputStream(fos);
+            bos.write(wfFactory.getWapp(this));
+            bos.close();
+            fos.close();
+            final Uri tempAppFileUri = Uri.fromFile(tempFile);
+            if (preview) {
+                findViewById(R.id.watchface_upload_progress_bar).setVisibility(View.VISIBLE);
+                GBApplication.deviceService(mGBDevice).onInstallApp(tempAppFileUri);
+                new Handler().postDelayed(new Runnable() {
+                    @Override
+                    public void run() {
+                        GBApplication.deviceService(mGBDevice).onAppDelete(UUID.nameUUIDFromBytes("previewWatchface".getBytes(StandardCharsets.UTF_8)));
+                    }
+                }, 15000);
+            } else {
+                readyToCloseActivity = true;
+                final FossilFileReader fossilFile = new FossilFileReader(tempAppFileUri, this);
+                GBDeviceApp app = fossilFile.getGBDeviceApp();
+                File cacheDir = mCoordinator.getAppCacheDir();
+                File destFile = new File(cacheDir, app.getUUID().toString() + mCoordinator.getAppFileExtension());
+                if (destFile.exists()) {
+                    new MaterialAlertDialogBuilder(this)
+                            .setMessage(R.string.watchface_cache_confirm_overwrite)
+                            .setNegativeButton(R.string.no, null)
+                            .setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
+                                @Override
+                                public void onClick(DialogInterface dialog, int which) {
+                                    findViewById(R.id.watchface_upload_progress_bar).setVisibility(View.VISIBLE);
+                                    GBApplication.deviceService(mGBDevice).onInstallApp(tempAppFileUri);
+                                    FossilHRInstallHandler.saveAppInCache(fossilFile, selectedBackgroundImage, wfFactory.getPreviewImage(mContext), mCoordinator, HybridHRWatchfaceDesignerActivity.this);
+                                }
+                            })
+                            .show();
+                } else {
+                    findViewById(R.id.watchface_upload_progress_bar).setVisibility(View.VISIBLE);
+                    GBApplication.deviceService(mGBDevice).onInstallApp(tempAppFileUri);
+                    FossilHRInstallHandler.saveAppInCache(fossilFile, selectedBackgroundImage, wfFactory.getPreviewImage(mContext), mCoordinator, HybridHRWatchfaceDesignerActivity.this);
+                }
+            }
+        } catch (IOException e) {
+            LOG.warn("Error while creating and uploading watchface", e);
+        }
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/HybridHRWatchfaceFactory.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/HybridHRWatchfaceFactory.java
new file mode 100644
index 0000000..1fa1c9a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/HybridHRWatchfaceFactory.java
@@ -0,0 +1,408 @@
+/*  Copyright (C) 2021-2024 Arjan Schrijver, Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.qhybrid;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.LinkedHashMap;
+import java.util.TimeZone;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.image.ImageConverter;
+import nodomain.freeyourgadget.gadgetbridge.util.BitmapUtil;
+
+public class HybridHRWatchfaceFactory {
+    private static final Logger LOG = LoggerFactory.getLogger(HybridHRWatchfaceFactory.class);
+    private String watchfaceName;
+    private HybridHRWatchfaceSettings settings;
+    private Bitmap background;
+    private Bitmap previewImage;
+    private static final int PREVIEW_WIDTH = 192;
+    private static final int PREVIEW_HEIGHT = 192;
+    private ArrayList<JSONObject> widgets = new ArrayList<>();
+    private JSONObject menuStructure = new JSONObject();
+
+    public HybridHRWatchfaceFactory(String name) {
+        watchfaceName = name.replaceAll("[^-A-Za-z0-9]", "");
+        if (watchfaceName.equals("")) throw new AssertionError("name cannot be empty");
+        if (watchfaceName.endsWith("App")) watchfaceName += "Watchface";
+    }
+
+    public void setSettings(HybridHRWatchfaceSettings settings) {
+        this.settings = settings;
+    }
+
+    public void setBackground(Bitmap background) {
+        if ((background.getWidth() == 240) && (background.getHeight() == 240)) {
+            this.background = background;
+        } else {
+            this.background = Bitmap.createScaledBitmap(background, 240, 240, true);
+        }
+    }
+
+    public void addWidget(HybridHRWatchfaceWidget widgetDesc) {
+        JSONObject widget = new JSONObject();
+        try {
+            switch (widgetDesc.getWidgetType()) {
+                case "widgetCustom":
+                    if (widgetDesc.getExtraConfigInt("update_timeout", -1) >= 0) {
+                        JSONObject data = new JSONObject();
+                        data.put("update_timeout", widgetDesc.getExtraConfigInt("update_timeout", -1));
+                        data.put("timeout_hide_text", widgetDesc.getExtraConfigBoolean("timeout_hide_text", true));
+                        data.put("timeout_show_circle", widgetDesc.getExtraConfigBoolean("timeout_show_circle", true));
+                        if (widgetDesc.getBackground() != "") {
+                            data.put("background", widgetDesc.getBackground() + widgetDesc.getColor() + ".rle");
+                        }
+                        widget.put("data", data);
+                    }
+                    // fall through
+                case "widget2ndTZ":
+                    if (widgetDesc.getExtraConfigString("tzName", null) != null) {
+                        JSONObject data = new JSONObject();
+                        TimeZone tz = TimeZone.getTimeZone(widgetDesc.getExtraConfigString("tzName", null));
+                        String tzShortName = widgetDesc.getExtraConfigString("tzName", null).replaceAll(".*/", "");
+                        int tzOffsetMins = tz.getOffset(Calendar.getInstance().getTimeInMillis()) / 1000 / 60;
+                        data.put("tzName", widgetDesc.getExtraConfigString("tzName", null));
+                        data.put("loc", tzShortName);
+                        data.put("utc", tzOffsetMins);
+                        data.put("timeout_secs", widgetDesc.getExtraConfigInt("timeout_secs", 0));
+                        widget.put("data", data);
+                    }
+                    // fall through
+                case "widgetDate":
+                case "widgetWeather":
+                case "widgetSteps":
+                case "widgetHR":
+                case "widgetBattery":
+                case "widgetCalories":
+                case "widgetActiveMins":
+                case "widgetChanceOfRain":
+                case "widgetUV":
+                case "widgetSpO2":
+                    widget.put("type", "comp");
+                    widget.put("name", widgetDesc.getWidgetType());
+                    widget.put("goal_ring", false);
+                    widget.put("color", widgetDesc.getColor() == HybridHRWatchfaceWidget.COLOR_WHITE ? "white" : "black");
+                    if (!widgetDesc.getBackground().equals("")) {
+                        widget.put("bg", widgetDesc.getBackground() + widgetDesc.getColor() + ".rle");
+                    }
+                    break;
+                default:
+                    LOG.warn("Invalid widget name: " + widgetDesc.getWidgetType());
+                    return;
+            }
+            JSONObject size = new JSONObject();
+            size.put("w", widgetDesc.getWidth());
+            size.put("h", widgetDesc.getHeight());
+            widget.put("size", size);
+            JSONObject pos = new JSONObject();
+            pos.put("x", widgetDesc.getPosX());
+            pos.put("y", widgetDesc.getPosY());
+            widget.put("pos", pos);
+            widgets.add(widget);
+        } catch (JSONException e) {
+            LOG.warn("JSON error", e);
+        }
+    }
+
+    public void setMenuStructure(JSONObject menuStructure){
+        this.menuStructure = menuStructure;
+    }
+
+    public void addWidgets(ArrayList<HybridHRWatchfaceWidget> widgets) {
+        for (HybridHRWatchfaceWidget widget : widgets) {
+            addWidget(widget);
+        }
+    }
+
+    private int includeWidget(String name) {
+        int count = 0;
+        for (JSONObject widget : this.widgets) {
+            try {
+                if (widget.get("name").equals(name)) {
+                    count++;
+                }
+            } catch (JSONException e) {
+            }
+        }
+        return count;
+    }
+
+    private Boolean includeBackground(String name, int color) {
+        for (JSONObject widget : this.widgets) {
+            try {
+                if (widget.get("bg").toString().startsWith(name + color)) {
+                    return true;
+                }
+            } catch (JSONException e) {
+            }
+        }
+        return false;
+    }
+
+    private InputStream getWidgetBackgroundStream(Context context, String name, Boolean invert) throws IOException {
+        Bitmap bgImage = BitmapFactory.decodeStream(context.getAssets().open("fossil_hr/" + name + ".png"));
+        if (invert) {
+            bgImage = BitmapUtil.invertBitmapColors(bgImage);
+        }
+        return new ByteArrayInputStream(ImageConverter.encodeToRLEImage(ImageConverter.get2BitsRLEImageBytes(bgImage), QHybridConstants.HYBRID_HR_WATCHFACE_WIDGET_SIZE, QHybridConstants.HYBRID_HR_WATCHFACE_WIDGET_SIZE));
+    }
+
+    public byte[] getWapp(Context context) throws IOException {
+        byte[] backgroundBytes = ImageConverter.encodeToRawImage(ImageConverter.get2BitsRAWImageBytes(background));
+        InputStream backgroundStream = new ByteArrayInputStream(backgroundBytes);
+        byte[] previewBytes = ImageConverter.encodeToRLEImage(ImageConverter.get2BitsRLEImageBytes(Bitmap.createScaledBitmap(getPreviewImage(context), PREVIEW_WIDTH, PREVIEW_HEIGHT, true)), PREVIEW_HEIGHT, PREVIEW_WIDTH);
+        InputStream previewStream = new ByteArrayInputStream(previewBytes);
+        LinkedHashMap<String, InputStream> code = new LinkedHashMap<>();
+        try {
+            code.put(watchfaceName, context.getAssets().open("fossil_hr/openSourceWatchface.bin"));
+            if (includeWidget("widgetDate") > 0) code.put("widgetDate", context.getAssets().open("fossil_hr/widgetDate.bin"));
+            if (includeWidget("widgetWeather") > 0) code.put("widgetWeather", context.getAssets().open("fossil_hr/widgetWeather.bin"));
+            if (includeWidget("widgetSteps") > 0) code.put("widgetSteps", context.getAssets().open("fossil_hr/widgetSteps.bin"));
+            if (includeWidget("widgetHR") > 0) code.put("widgetHR", context.getAssets().open("fossil_hr/widgetHR.bin"));
+            if (includeWidget("widgetBattery") > 0) code.put("widgetBattery", context.getAssets().open("fossil_hr/widgetBattery.bin"));
+            if (includeWidget("widgetCalories") > 0) code.put("widgetCalories", context.getAssets().open("fossil_hr/widgetCalories.bin"));
+            if (includeWidget("widgetActiveMins") > 0) code.put("widgetActiveMins", context.getAssets().open("fossil_hr/widgetActiveMins.bin"));
+            if (includeWidget("widgetChanceOfRain") > 0) code.put("widgetChanceOfRain", context.getAssets().open("fossil_hr/widgetChanceOfRain.bin"));
+            if (includeWidget("widgetUV") > 0) code.put("widgetUV", context.getAssets().open("fossil_hr/widgetUV.bin"));
+            if (includeWidget("widgetSpO2") > 0) code.put("widgetSpO2", context.getAssets().open("fossil_hr/widgetSpO2.bin"));
+            for (int i=0; i<includeWidget("widget2ndTZ"); i++) {
+                code.put("widget2ndTZ" + i, context.getAssets().open("fossil_hr/widget2ndTZ.bin"));
+            }
+            for (int i=0; i<includeWidget("widgetCustom"); i++) {
+                code.put("widgetCustom" + i, context.getAssets().open("fossil_hr/widgetCustom.bin"));
+            }
+        } catch (IOException e) {
+            LOG.warn("Unable to read asset file", e);
+        }
+        LinkedHashMap<String, InputStream> icons = new LinkedHashMap<>();
+        try {
+            icons.put("background.raw", backgroundStream);
+            icons.put("!preview.rle", previewStream);
+            icons.put("icTrophy", context.getAssets().open("fossil_hr/icTrophy.rle"));
+            if (includeWidget("widgetWeather") > 0) icons.put("icWthClearDay", context.getAssets().open("fossil_hr/icWthClearDay.rle"));
+            if (includeWidget("widgetWeather") > 0) icons.put("icWthClearNite", context.getAssets().open("fossil_hr/icWthClearNite.rle"));
+            if (includeWidget("widgetWeather") > 0) icons.put("icWthCloudy", context.getAssets().open("fossil_hr/icWthCloudy.rle"));
+            if (includeWidget("widgetWeather") > 0) icons.put("icWthPartCloudyDay", context.getAssets().open("fossil_hr/icWthPartCloudyDay.rle"));
+            if (includeWidget("widgetWeather") > 0) icons.put("icWthPartCloudyNite", context.getAssets().open("fossil_hr/icWthPartCloudyNite.rle"));
+            if (includeWidget("widgetWeather") > 0) icons.put("icWthRainy", context.getAssets().open("fossil_hr/icWthRainy.rle"));
+            if (includeWidget("widgetWeather") > 0) icons.put("icWthSnowy", context.getAssets().open("fossil_hr/icWthSnowy.rle"));
+            if (includeWidget("widgetWeather") > 0) icons.put("icWthStormy", context.getAssets().open("fossil_hr/icWthStormy.rle"));
+            if (includeWidget("widgetWeather") > 0) icons.put("icWthWindy", context.getAssets().open("fossil_hr/icWthWindy.rle"));
+            if (includeWidget("widgetSteps") > 0) icons.put("icSteps", context.getAssets().open("fossil_hr/icSteps.rle"));
+            if (includeWidget("widgetHR") > 0) icons.put("icHeart", context.getAssets().open("fossil_hr/icHeart.rle"));
+            if (includeWidget("widgetBattery") > 0) icons.put("icBattCharging", context.getAssets().open("fossil_hr/icBattCharging.rle"));
+            if (includeWidget("widgetBattery") > 0) icons.put("icBattEmpty", context.getAssets().open("fossil_hr/icBattEmpty.rle"));
+            if (includeWidget("widgetBattery") > 0) icons.put("icBattery", context.getAssets().open("fossil_hr/icBattery.rle"));
+            if (includeWidget("widgetCalories") > 0) icons.put("icCalories", context.getAssets().open("fossil_hr/icCalories.rle"));
+            if (includeWidget("widgetActiveMins") > 0) icons.put("icActiveMins", context.getAssets().open("fossil_hr/icActiveMins.rle"));
+            if (includeWidget("widgetChanceOfRain") > 0) icons.put("icRainChance", context.getAssets().open("fossil_hr/icRainChance.rle"));
+            if (includeWidget("widgetSpO2") > 0) icons.put("icSpO2", context.getAssets().open("fossil_hr/icSpO2.rle"));
+            if (includeWidget("widgetCustom") > 0) icons.put("widget_bg_error.rle", context.getAssets().open("fossil_hr/widget_bg_error.rle"));
+            // Note: we have to check and invert every used widget background here,
+            // because the watch doesn't invert the background image when the widget color is inverted
+            if (includeBackground("widget_bg_thin_circle", HybridHRWatchfaceWidget.COLOR_WHITE)) {
+                icons.put("widget_bg_thin_circle" + HybridHRWatchfaceWidget.COLOR_WHITE + ".rle", getWidgetBackgroundStream(context, "widget_bg_thin_circle", false));
+            }
+            if (includeBackground("widget_bg_thin_circle", HybridHRWatchfaceWidget.COLOR_BLACK)) {
+                icons.put("widget_bg_thin_circle" + HybridHRWatchfaceWidget.COLOR_BLACK + ".rle", getWidgetBackgroundStream(context, "widget_bg_thin_circle", true));
+            }
+            if (includeBackground("widget_bg_double_circle", HybridHRWatchfaceWidget.COLOR_WHITE)) {
+                icons.put("widget_bg_double_circle" + HybridHRWatchfaceWidget.COLOR_WHITE + ".rle", getWidgetBackgroundStream(context, "widget_bg_double_circle", false));
+            }
+            if (includeBackground("widget_bg_double_circle", HybridHRWatchfaceWidget.COLOR_BLACK)) {
+                icons.put("widget_bg_double_circle" + HybridHRWatchfaceWidget.COLOR_BLACK + ".rle", getWidgetBackgroundStream(context, "widget_bg_double_circle", true));
+            }
+            if (includeBackground("widget_bg_dashed_circle", HybridHRWatchfaceWidget.COLOR_WHITE)) {
+                icons.put("widget_bg_dashed_circle" + HybridHRWatchfaceWidget.COLOR_WHITE + ".rle", getWidgetBackgroundStream(context, "widget_bg_dashed_circle", false));
+            }
+            if (includeBackground("widget_bg_dashed_circle", HybridHRWatchfaceWidget.COLOR_BLACK)) {
+                icons.put("widget_bg_dashed_circle" + HybridHRWatchfaceWidget.COLOR_BLACK + ".rle", getWidgetBackgroundStream(context, "widget_bg_dashed_circle", true));
+            }
+        } catch (IOException e) {
+            LOG.warn("Unable to read asset file", e);
+        }
+        LinkedHashMap<String, InputStream> layout = new LinkedHashMap<>();
+        layout.put("complication_layout", context.getAssets().open("fossil_hr/complication_layout.json"));
+        layout.put("image_layout", context.getAssets().open("fossil_hr/image_layout.json"));
+        layout.put("menu_layout", context.getAssets().open("fossil_hr/menu_layout.json"));
+
+        if (includeWidget("widgetBattery") > 0) {
+            layout.put("battery_layout", context.getAssets().open("fossil_hr/battery_layout.json"));
+        }
+
+        LinkedHashMap<String, String> displayName = new LinkedHashMap<>();
+        displayName.put("display_name", watchfaceName);
+        displayName.put("theme_class", "complications");
+        LinkedHashMap<String, String> config = new LinkedHashMap<>();
+        try {
+            config.put("customWatchFace", getConfiguration());
+        } catch (JSONException e) {
+            LOG.warn("Could not generate configuration", e);
+        }
+        FossilAppWriter appWriter = new FossilAppWriter(context, QHybridConstants.HYBRIDHR_WATCHFACE_VERSION, code, icons, layout, displayName, config);
+        return appWriter.getWapp();
+    }
+
+    private String getConfiguration() throws JSONException {
+        JSONObject configuration = new JSONObject();
+
+        JSONArray layout = new JSONArray();
+        JSONObject background = new JSONObject();
+        background.put("type", "image");
+        background.put("name", "background.raw");
+        JSONObject size = new JSONObject();
+        size.put("w", 240);
+        size.put("h", 240);
+        background.put("size", size);
+        JSONObject pos = new JSONObject();
+        pos.put("x", 120);
+        pos.put("y", 120);
+        background.put("pos", pos);
+        layout.put(background);
+        int count_widget2ndTZ = 0;
+        int count_widgetCustom = 0;
+        for (JSONObject widget : widgets) {
+            if (widget.get("name").equals("widget2ndTZ")) {
+                widget.put("name", "widget2ndTZ" + count_widget2ndTZ);
+                layout.put(widget);
+                count_widget2ndTZ++;
+            } else if (widget.get("name").equals("widgetCustom")) {
+                widget.put("name", "widgetCustom" + count_widgetCustom);
+                layout.put(widget);
+                count_widgetCustom++;
+            } else {
+                layout.put(widget);
+            }
+        }
+        configuration.put("layout", layout);
+
+        JSONObject config = new JSONObject();
+        config.put("timeout_display_full", settings.getDisplayTimeoutFull() * 60 * 1000);
+        config.put("timeout_display_partial", settings.getDisplayTimeoutPartial() * 60 * 1000);
+        config.put("wrist_flick_hands_relative", settings.isWristFlickHandsMoveRelative());
+        config.put("wrist_flick_duration", settings.getWristFlickDuration());
+        config.put("wrist_flick_move_hour", settings.getWristFlickMoveHour());
+        config.put("wrist_flick_move_minute", settings.getWristFlickMoveMinute());
+        config.put("toggle_widgets_event", settings.getToggleWidgetsEvent());
+        config.put("toggle_backlight_event", settings.getToggleBacklightEvent());
+        config.put("move_hands_event", settings.getMoveHandsEvent());
+        config.put("powersave_display", settings.getPowersaveDisplay());
+        config.put("powersave_hands", settings.getPowersaveHands());
+        config.put("light_up_on_notification", settings.getLightUpOnNotification());
+        configuration.put("config", config);
+
+        configuration.put("menu_structure", menuStructure);
+
+        return configuration.toString();
+    }
+
+    public static HybridHRWatchfaceWidget parseWidgetJSON(JSONObject widgetJSON) throws JSONException {
+        HybridHRWatchfaceWidget parsedWidget = null;
+        String widgetName = widgetJSON.getString("name");
+        String widgetTimezone = null;
+        int widgetUpdateTimeout = -1;
+        boolean widgetTimeoutHideText = true;
+        boolean widgetTimeoutShowCircle = true;
+        switch (widgetName) {
+            case "dateSSE":
+                widgetName = "widgetDate";
+                break;
+            case "weatherSSE":
+                widgetName = "widgetWeather";
+                break;
+            case "stepsSSE":
+                widgetName = "widgetSteps";
+                break;
+            case "hrSSE":
+                widgetName = "widgetHR";
+                break;
+            case "batterySSE":
+                widgetName = "widgetBattery";
+                break;
+            case "caloriesSSE":
+                widgetName = "widgetCalories";
+                break;
+            case "activeMinutesSSE":
+                widgetName = "widgetActiveMins";
+                break;
+            case "chanceOfRainSSE":
+                widgetName = "widgetChanceOfRain";
+                break;
+            case "timeZone2SSE":
+                widgetName = "widget2ndTZ";
+                break;
+            case "spo2SSE":
+                widgetName = "widgetSpO2";
+                break;
+        }
+        if (widgetName.startsWith("widget2ndTZ")) {
+            widgetName = "widget2ndTZ";
+        } else if (widgetName.startsWith("widgetCustom")) {
+            widgetName = "widgetCustom";
+        }
+        int widgetColor = widgetJSON.getString("color").equals("white") ? HybridHRWatchfaceWidget.COLOR_WHITE : HybridHRWatchfaceWidget.COLOR_BLACK;
+        JSONObject widgetData = widgetJSON.optJSONObject("data");
+        parsedWidget = new HybridHRWatchfaceWidget(widgetName,
+                widgetJSON.getJSONObject("pos").getInt("x"),
+                widgetJSON.getJSONObject("pos").getInt("y"),
+                widgetJSON.getJSONObject("size").getInt("w"),
+                widgetJSON.getJSONObject("size").getInt("h"),
+                widgetColor,
+                widgetData);
+        String widgetBackground = widgetJSON.optString("bg", "");
+        if (widgetBackground != "") {
+            parsedWidget.setBackground(widgetBackground.replaceAll("[0-9]?\\.rle$", ""));
+        }
+        return parsedWidget;
+    }
+
+    public Bitmap getPreviewImage(Context context) {
+        if (previewImage == null) {
+            previewImage = BitmapUtil.getCircularBitmap(background);
+            Canvas previewCanvas = new Canvas(previewImage);
+            int widgetSize = QHybridConstants.HYBRID_HR_WATCHFACE_WIDGET_SIZE;
+            float scaleFactor = previewImage.getWidth() / 240;
+            for (int i=0; i<widgets.size(); i++) {
+                try {
+                    HybridHRWatchfaceWidget parsedWidget = parseWidgetJSON(widgets.get(i));
+                    Bitmap widgetPreview = Bitmap.createScaledBitmap(parsedWidget.getPreviewImage(context), (int)(widgetSize * scaleFactor), (int)(widgetSize * scaleFactor), true);
+                    int offsetFromCenter = (int)((widgetSize/2) * scaleFactor);
+                    previewCanvas.drawBitmap(widgetPreview, parsedWidget.getPosX() - offsetFromCenter, parsedWidget.getPosY() - offsetFromCenter, null);
+                } catch (JSONException e) {
+                    LOG.warn("Couldn't parse widget JSON", e);
+                } catch (IOException e) {
+                    LOG.warn("Couldn't parse widget JSON", e);
+                }
+            }
+        }
+        return previewImage;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/HybridHRWatchfaceSettings.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/HybridHRWatchfaceSettings.java
new file mode 100644
index 0000000..d29992c
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/HybridHRWatchfaceSettings.java
@@ -0,0 +1,145 @@
+/*  Copyright (C) 2021-2024 Arjan Schrijver
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.qhybrid;
+
+import java.io.Serializable;
+
+public class HybridHRWatchfaceSettings implements Serializable {
+    private int displayTimeoutFull = 60;
+    private int displayTimeoutPartial = 15;
+    private boolean wristFlickHandsMoveRelative = true;
+    private int wristFlickDuration = 2200;
+    private int wristFlickMoveHour = 360;
+    private int wristFlickMoveMinute = -360;
+    private String toggleWidgetsEvent = "";
+    private String toggleBacklightEvent = "";
+    private String moveHandsEvent = "flick_away";
+    private boolean powersaveDisplay = false;
+    private boolean powersaveHands = false;
+    private boolean lightUpOnNotification = false;
+
+    public HybridHRWatchfaceSettings() {
+    }
+
+    public int getDisplayTimeoutFull() {
+        return displayTimeoutFull;
+    }
+
+    public void setDisplayTimeoutFull(int displayTimeoutFull) {
+        this.displayTimeoutFull = displayTimeoutFull;
+    }
+
+    public int getDisplayTimeoutPartial() {
+        return displayTimeoutPartial;
+    }
+
+    public void setDisplayTimeoutPartial(int displayTimeoutPartial) {
+        this.displayTimeoutPartial = displayTimeoutPartial;
+    }
+
+    public boolean isWristFlickHandsMoveRelative() {
+        return wristFlickHandsMoveRelative;
+    }
+
+    public void setWristFlickHandsMoveRelative(boolean wristFlickHandsMoveRelative) {
+        this.wristFlickHandsMoveRelative = wristFlickHandsMoveRelative;
+    }
+
+    public int getWristFlickDuration() {
+        return wristFlickDuration;
+    }
+
+    public void setWristFlickDuration(int wristFlickDuration) {
+        this.wristFlickDuration = wristFlickDuration;
+    }
+
+    public boolean getLightUpOnNotification() {
+        return lightUpOnNotification;
+    }
+
+    public void setLightUpOnNotification(boolean lightUpOnNotification) {
+        this.lightUpOnNotification = lightUpOnNotification;
+    }
+
+    public int getWristFlickMoveHour() {
+        return wristFlickMoveHour;
+    }
+
+    public void setWristFlickMoveHour(int wristFlickMoveHour) {
+        if (wristFlickMoveHour < -360) {
+            this.wristFlickMoveHour = -360;
+        } else if (wristFlickMoveHour > 360) {
+            this.wristFlickMoveHour = 360;
+        } else {
+            this.wristFlickMoveHour = wristFlickMoveHour;
+        }
+    }
+
+    public int getWristFlickMoveMinute() {
+        return wristFlickMoveMinute;
+    }
+
+    public void setWristFlickMoveMinute(int wristFlickMoveMinute) {
+        if (wristFlickMoveMinute < -360) {
+            this.wristFlickMoveMinute = -360;
+        } else if (wristFlickMoveMinute > 360) {
+            this.wristFlickMoveMinute = 360;
+        } else {
+            this.wristFlickMoveMinute = wristFlickMoveMinute;
+        }
+    }
+
+    public String getToggleWidgetsEvent() {
+        return toggleWidgetsEvent;
+    }
+
+    public void setToggleWidgetsEvent(String event) {
+        this.toggleWidgetsEvent = event;
+    }
+
+    public String getToggleBacklightEvent() {
+        return toggleBacklightEvent;
+    }
+
+    public void setToggleBacklightEvent(String event) {
+        this.toggleBacklightEvent = event;
+    }
+
+    public String getMoveHandsEvent() {
+        return moveHandsEvent;
+    }
+
+    public void setMoveHandsEvent(String event) {
+        this.moveHandsEvent = event;
+    }
+
+    public boolean getPowersaveDisplay() {
+        return powersaveDisplay;
+    }
+
+    public void setPowersaveDisplay(boolean powersaveDisplay) {
+        this.powersaveDisplay = powersaveDisplay;
+    }
+
+    public boolean getPowersaveHands() {
+        return powersaveHands;
+    }
+
+    public void setPowersaveHands(boolean powersaveHands) {
+        this.powersaveHands = powersaveHands;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/HybridHRWatchfaceSettingsActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/HybridHRWatchfaceSettingsActivity.java
new file mode 100644
index 0000000..65b096f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/HybridHRWatchfaceSettingsActivity.java
@@ -0,0 +1,207 @@
+/*  Copyright (C) 2021-2024 Arjan Schrijver, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.qhybrid;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.text.InputType;
+import android.view.MenuItem;
+
+import androidx.annotation.NonNull;
+import androidx.preference.EditTextPreference;
+import androidx.preference.ListPreference;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceFragmentCompat;
+import androidx.preference.SwitchPreferenceCompat;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.AbstractPreferenceFragment;
+import nodomain.freeyourgadget.gadgetbridge.activities.AbstractSettingsActivityV2;
+
+public class HybridHRWatchfaceSettingsActivity extends AbstractSettingsActivityV2 {
+    static HybridHRWatchfaceSettings settings;
+
+    @Override
+    protected String fragmentTag() {
+        return HybridHRWatchfaceSettingsFragment.FRAGMENT_TAG;
+    }
+
+    @Override
+    protected PreferenceFragmentCompat newFragment() {
+        return new HybridHRWatchfaceSettingsFragment();
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        Intent intent = getIntent();
+        Bundle bundle = intent.getExtras();
+        if (bundle != null) {
+            settings = (HybridHRWatchfaceSettings) bundle.getSerializable("watchfaceSettings");
+        } else {
+            throw new IllegalArgumentException("Must provide a settings object when invoking this activity");
+        }
+
+        super.onCreate(savedInstanceState);
+    }
+
+    @Override
+    public void onBackPressed() {
+        // Hardware back button
+        Intent output = new Intent();
+        output.putExtra("watchfaceSettings", settings);
+        setResult(RESULT_OK, output);
+        finish();
+        super.onBackPressed();
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(@NonNull final MenuItem item) {
+        if (item.getItemId() == android.R.id.home) {
+            // Action bar back button
+            Intent output = new Intent();
+            output.putExtra("watchfaceSettings", settings);
+            setResult(RESULT_OK, output);
+            finish();
+            return true;
+        }
+        return super.onOptionsItemSelected(item);
+    }
+
+    public static class HybridHRWatchfaceSettingsFragment extends AbstractPreferenceFragment implements Preference.OnPreferenceChangeListener {
+        static final String FRAGMENT_TAG = "HYBRID_HR_WATCHFACE_SETTINGS_FRAGMENT";
+
+        @Override
+        public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) {
+            setPreferencesFromResource(R.xml.fossil_hr_watchface_settings, rootKey);
+
+            EditTextPreference refresh_full = findPreference("pref_hybridhr_watchface_refresh_full");
+            refresh_full.setOnPreferenceChangeListener(this);
+            refresh_full.setText(Integer.toString(settings.getDisplayTimeoutFull()));
+            refresh_full.setSummary(Integer.toString(settings.getDisplayTimeoutFull()));
+            setInputTypeFor("pref_hybridhr_watchface_refresh_full", InputType.TYPE_CLASS_NUMBER);
+
+            EditTextPreference refresh_partial = findPreference("pref_hybridhr_watchface_refresh_partial");
+            refresh_partial.setOnPreferenceChangeListener(this);
+            refresh_partial.setText(Integer.toString(settings.getDisplayTimeoutPartial()));
+            refresh_partial.setSummary(Integer.toString(settings.getDisplayTimeoutPartial()));
+            setInputTypeFor("pref_hybridhr_watchface_refresh_partial", InputType.TYPE_CLASS_NUMBER);
+
+            SwitchPreferenceCompat wrist_flick_relative = findPreference("pref_hybridhr_watchface_wrist_flick_relative");
+            wrist_flick_relative.setOnPreferenceChangeListener(this);
+            wrist_flick_relative.setChecked(settings.isWristFlickHandsMoveRelative());
+
+            EditTextPreference wrist_flick_hour_hand = findPreference("pref_hybridhr_watchface_wrist_flick_hour_hand");
+            wrist_flick_hour_hand.setOnPreferenceChangeListener(this);
+            wrist_flick_hour_hand.setText(Integer.toString(settings.getWristFlickMoveHour()));
+            wrist_flick_hour_hand.setSummary(Integer.toString(settings.getWristFlickMoveHour()));
+            setInputTypeFor("pref_hybridhr_watchface_wrist_flick_hour_hand", InputType.TYPE_CLASS_NUMBER);
+
+            EditTextPreference wrist_flick_minute_hand = findPreference("pref_hybridhr_watchface_wrist_flick_minute_hand");
+            wrist_flick_minute_hand.setOnPreferenceChangeListener(this);
+            wrist_flick_minute_hand.setText(Integer.toString(settings.getWristFlickMoveMinute()));
+            wrist_flick_minute_hand.setSummary(Integer.toString(settings.getWristFlickMoveMinute()));
+            setInputTypeFor("pref_hybridhr_watchface_wrist_flick_minute_hand", InputType.TYPE_CLASS_NUMBER);
+
+            EditTextPreference wrist_flick_duration = findPreference("pref_hybridhr_watchface_wrist_flick_duration");
+            wrist_flick_duration.setOnPreferenceChangeListener(this);
+            wrist_flick_duration.setText(Integer.toString(settings.getWristFlickDuration()));
+            wrist_flick_duration.setSummary(Integer.toString(settings.getWristFlickDuration()));
+            setInputTypeFor("pref_hybridhr_watchface_wrist_flick_duration", InputType.TYPE_CLASS_NUMBER);
+
+            ListPreference toggle_widgets = findPreference("pref_hybridhr_watchface_toggle_widgets");
+            toggle_widgets.setOnPreferenceChangeListener(this);
+            toggle_widgets.setValue(settings.getToggleWidgetsEvent());
+            toggle_widgets.setSummary(toggle_widgets.getEntry());
+
+            ListPreference toggle_backlight = findPreference("pref_hybridhr_watchface_toggle_backlight");
+            toggle_backlight.setOnPreferenceChangeListener(this);
+            toggle_backlight.setValue(settings.getToggleBacklightEvent());
+            toggle_backlight.setSummary(toggle_backlight.getEntry());
+
+            ListPreference move_hands = findPreference("pref_hybridhr_watchface_move_hands");
+            move_hands.setOnPreferenceChangeListener(this);
+            move_hands.setValue(settings.getMoveHandsEvent());
+            move_hands.setSummary(move_hands.getEntry());
+
+            SwitchPreferenceCompat power_saving_display = findPreference("pref_hybridhr_watchface_power_saving_display");
+            power_saving_display.setOnPreferenceChangeListener(this);
+            power_saving_display.setChecked(settings.getPowersaveDisplay());
+
+            SwitchPreferenceCompat power_saving_hands = findPreference("pref_hybridhr_watchface_power_saving_hands");
+            power_saving_hands.setOnPreferenceChangeListener(this);
+            power_saving_hands.setChecked(settings.getPowersaveHands());
+
+            SwitchPreferenceCompat light_up_on_notification = findPreference("pref_hybridhr_watchface_light_up_on_notification");
+            light_up_on_notification.setOnPreferenceChangeListener(this);
+            light_up_on_notification.setChecked(settings.getLightUpOnNotification());
+        }
+
+        @Override
+        public boolean onPreferenceChange(Preference preference, Object newValue) {
+            switch (preference.getKey()) {
+                case "pref_hybridhr_watchface_refresh_full":
+                    settings.setDisplayTimeoutFull(Integer.parseInt(newValue.toString()));
+                    preference.setSummary(newValue.toString());
+                    break;
+                case "pref_hybridhr_watchface_refresh_partial":
+                    settings.setDisplayTimeoutPartial(Integer.parseInt(newValue.toString()));
+                    preference.setSummary(newValue.toString());
+                    break;
+                case "pref_hybridhr_watchface_wrist_flick_relative":
+                    settings.setWristFlickHandsMoveRelative((boolean) newValue);
+                    break;
+                case "pref_hybridhr_watchface_wrist_flick_hour_hand":
+                    settings.setWristFlickMoveHour(Integer.parseInt(newValue.toString()));
+                    preference.setSummary(newValue.toString());
+                    break;
+                case "pref_hybridhr_watchface_wrist_flick_minute_hand":
+                    settings.setWristFlickMoveMinute(Integer.parseInt(newValue.toString()));
+                    preference.setSummary(newValue.toString());
+                    break;
+                case "pref_hybridhr_watchface_wrist_flick_duration":
+                    settings.setWristFlickDuration(Integer.parseInt(newValue.toString()));
+                    preference.setSummary(newValue.toString());
+                    break;
+                case "pref_hybridhr_watchface_toggle_widgets":
+                    settings.setToggleWidgetsEvent(newValue.toString());
+                    ((ListPreference)preference).setValue(newValue.toString());
+                    preference.setSummary(((ListPreference)preference).getEntry());
+                    break;
+                case "pref_hybridhr_watchface_toggle_backlight":
+                    settings.setToggleBacklightEvent(newValue.toString());
+                    ((ListPreference)preference).setValue(newValue.toString());
+                    preference.setSummary(((ListPreference)preference).getEntry());
+                    break;
+                case "pref_hybridhr_watchface_move_hands":
+                    settings.setMoveHandsEvent(newValue.toString());
+                    ((ListPreference)preference).setValue(newValue.toString());
+                    preference.setSummary(((ListPreference)preference).getEntry());
+                    break;
+                case "pref_hybridhr_watchface_power_saving_display":
+                    settings.setPowersaveDisplay((boolean) newValue);
+                    break;
+                case "pref_hybridhr_watchface_power_saving_hands":
+                    settings.setPowersaveHands((boolean) newValue);
+                    break;
+                case "pref_hybridhr_watchface_light_up_on_notification":
+                    settings.setLightUpOnNotification((boolean) newValue);
+                    break;
+            }
+            return true;
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/HybridHRWatchfaceWidget.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/HybridHRWatchfaceWidget.java
new file mode 100644
index 0000000..5b2eb73
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/HybridHRWatchfaceWidget.java
@@ -0,0 +1,217 @@
+/*  Copyright (C) 2021-2024 Arjan Schrijver, Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.qhybrid;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+
+import org.json.JSONObject;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.util.LinkedHashMap;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.util.BitmapUtil;
+
+import static nodomain.freeyourgadget.gadgetbridge.util.BitmapUtil.invertBitmapColors;
+
+public class HybridHRWatchfaceWidget implements Serializable {
+    private String widgetType;
+    private int posX;
+    private int posY;
+    private int width;
+    private int height;
+    private int color;
+    private String background;
+    private String extraConfigJSON;
+
+    public static int COLOR_WHITE = 0;
+    public static int COLOR_BLACK = 1;
+
+    static HybridHRWidgetPosition[] defaultPositions = new HybridHRWidgetPosition[]{
+        new HybridHRWidgetPosition(120, 58, R.string.watchface_dialog_widget_preset_top),
+        new HybridHRWidgetPosition(182, 120, R.string.watchface_dialog_widget_preset_right),
+        new HybridHRWidgetPosition(120, 182, R.string.watchface_dialog_widget_preset_bottom),
+        new HybridHRWidgetPosition(58, 120, R.string.watchface_dialog_widget_preset_left),
+    };
+
+    public HybridHRWatchfaceWidget(String widgetType, int posX, int posY, int width, int height, int color, JSONObject extraConfig) {
+        this.widgetType = widgetType;
+        this.posX = posX;
+        this.posY = posY;
+        this.width = width;
+        this.height = height;
+        this.color = color;
+        this.background = "";
+        try {
+            this.extraConfigJSON = extraConfig.toString();
+        } catch (Exception e) {
+            this.extraConfigJSON = "{}";
+        }
+    }
+
+    public static LinkedHashMap<String, String> getAvailableWidgetTypes(Context context) {
+        LinkedHashMap<String, String> widgetTypes = new LinkedHashMap<>();
+        widgetTypes.put("widgetDate", context.getString(R.string.watchface_widget_type_date));
+        widgetTypes.put("widgetWeather", context.getString(R.string.watchface_widget_type_weather));
+        widgetTypes.put("widgetSteps", context.getString(R.string.watchface_widget_type_steps));
+        widgetTypes.put("widgetHR", context.getString(R.string.watchface_widget_type_heart_rate));
+        widgetTypes.put("widgetBattery", context.getString(R.string.watchface_widget_type_battery));
+        widgetTypes.put("widgetCalories", context.getString(R.string.watchface_widget_type_calories));
+        widgetTypes.put("widget2ndTZ", context.getString(R.string.watchface_widget_type_2nd_tz));
+        widgetTypes.put("widgetActiveMins", context.getString(R.string.watchface_widget_type_active_mins));
+        widgetTypes.put("widgetChanceOfRain", context.getString(R.string.watchface_widget_type_chance_rain));
+        widgetTypes.put("widgetUV", context.getString(R.string.watchface_widget_type_uv_index));
+        widgetTypes.put("widgetSpO2", context.getString(R.string.watchface_widget_type_sp02));
+        widgetTypes.put("widgetCustom", context.getString(R.string.watchface_widget_type_custom));
+        return widgetTypes;
+    }
+
+    public void setWidgetType(String widgetType) {
+        this.widgetType = widgetType;
+    }
+    public String getWidgetType() {
+        return widgetType;
+    }
+
+
+    public Bitmap getPreviewImage(Context context) throws IOException {
+        Bitmap preview = BitmapFactory.decodeStream(context.getAssets().open("fossil_hr/" + widgetType + "_preview.png"));
+        if (getBackground() != "") {
+            try {
+                Bitmap background = BitmapFactory.decodeStream(context.getAssets().open("fossil_hr/" + getBackground() + ".png"));
+                preview = BitmapUtil.overlay(background, preview);
+            } catch (Exception e) {
+                // continue silently without background
+            }
+        }
+        if (color == COLOR_WHITE) {
+            return preview;
+        } else {
+            return invertBitmapColors(preview);
+        }
+    }
+
+    public int getPosX() {
+        return posX;
+    }
+    public int getPosY() {
+        return posY;
+    }
+
+    public void setPosX(int posX) {
+        this.posX = posX;
+    }
+    public void setPosY(int posY) {
+        this.posY = posY;
+    }
+
+    public int getWidth() {
+        return width;
+    }
+    public int getHeight() {
+        return height;
+    }
+
+    public void setWidth(int width) {
+        this.width = width;
+    }
+    public void setHeight(int height) {
+        this.height = height;
+    }
+
+    public int getColor() {
+        return color;
+    }
+    public void setColor(int color) {
+        this.color = color;
+    }
+
+    public String getBackground() {
+        return background;
+    }
+    public void setBackground(String background) {
+        this.background = background;
+    }
+
+    public int getExtraConfigInt(String name, int fallback) {
+        try {
+            return new JSONObject(extraConfigJSON).optInt(name, fallback);
+        } catch (Exception e) {
+            return fallback;
+        }
+    }
+    public String getExtraConfigString(String name, String fallback) {
+        try {
+            return new JSONObject(extraConfigJSON).optString(name, fallback);
+        } catch (Exception e) {
+            return fallback;
+        }
+    }
+    public Boolean getExtraConfigBoolean(String name, Boolean fallback) {
+        try {
+            return new JSONObject(extraConfigJSON).optBoolean(name, fallback);
+        } catch (Exception e) {
+            return fallback;
+        }
+    }
+
+    public void setExtraConfigInt(String name, int value) {
+        JSONObject extraConfig;
+        try {
+            extraConfig = new JSONObject(extraConfigJSON);
+        } catch (Exception e) {
+            extraConfig = new JSONObject();
+        }
+        try {
+            extraConfig.put(name, value);
+            extraConfigJSON = extraConfig.toString();
+        } catch (Exception e) {
+            return;
+        }
+    }
+    public void setExtraConfigString(String name, String value) {
+        JSONObject extraConfig;
+        try {
+            extraConfig = new JSONObject(extraConfigJSON);
+        } catch (Exception e) {
+            extraConfig = new JSONObject();
+        }
+        try {
+            extraConfig.put(name, value);
+            extraConfigJSON = extraConfig.toString();
+        } catch (Exception e) {
+            return;
+        }
+    }
+    public void setExtraConfigBoolean(String name, Boolean value) {
+        JSONObject extraConfig;
+        try {
+            extraConfig = new JSONObject(extraConfigJSON);
+        } catch (Exception e) {
+            extraConfig = new JSONObject();
+        }
+        try {
+            extraConfig.put(name, value);
+            extraConfigJSON = extraConfig.toString();
+        } catch (Exception e) {
+            return;
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/HybridHRWatchfaceWidgetActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/HybridHRWatchfaceWidgetActivity.java
new file mode 100644
index 0000000..6fcdf5f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/HybridHRWatchfaceWidgetActivity.java
@@ -0,0 +1,273 @@
+/*  Copyright (C) 2022-2024 Arjan Schrijver, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.qhybrid;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.text.InputType;
+import android.view.MenuItem;
+
+import androidx.annotation.NonNull;
+import androidx.preference.EditTextPreference;
+import androidx.preference.ListPreference;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceCategory;
+import androidx.preference.PreferenceFragmentCompat;
+import androidx.preference.SwitchPreferenceCompat;
+
+import java.util.LinkedHashMap;
+import java.util.TimeZone;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.AbstractPreferenceFragment;
+import nodomain.freeyourgadget.gadgetbridge.activities.AbstractSettingsActivityV2;
+
+public class HybridHRWatchfaceWidgetActivity extends AbstractSettingsActivityV2 {
+    private static int widgetIndex;
+    private static HybridHRWatchfaceWidget widget;
+
+    private static LinkedHashMap<String, String> widgetTypes;
+    private static String[] widgetColors;
+
+    private static final String WIDGET_2NDTZ_DEFAULT_TZ = "Etc/UTC";
+    private static final int WIDGET_2NDTZ_DEFAULT_TIMEOUT = 15;
+    private static final int WIDGET_CUSTOM_DEFAULT_TIMEOUT = 60;
+    private static final Boolean WIDGET_CUSTOM_DEFAULT_HIDE_TEXT = true;
+    private static final Boolean WIDGET_CUSTOM_DEFAULT_SHOW_CIRCLE = true;
+
+    @Override
+    protected String fragmentTag() {
+        return HybridHRWatchfaceWidgetFragment.FRAGMENT_TAG;
+    }
+
+    @Override
+    protected PreferenceFragmentCompat newFragment() {
+        return new HybridHRWatchfaceWidgetFragment();
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        Intent intent = getIntent();
+        Bundle bundle = intent.getExtras();
+        if (bundle != null) {
+            widgetIndex = (int) bundle.getSerializable("widgetIndex");
+            widget = (HybridHRWatchfaceWidget) bundle.getSerializable("widgetSettings");
+        } else {
+            throw new IllegalArgumentException("Must provide a widget object when invoking this activity");
+        }
+
+        super.onCreate(savedInstanceState);
+    }
+
+    @Override
+    public void onBackPressed() {
+        // Hardware back button
+        Intent output = new Intent();
+        output.putExtra("widgetIndex", widgetIndex);
+        output.putExtra("widgetSettings", widget);
+        setResult(RESULT_OK, output);
+        finish();
+        super.onBackPressed();
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(@NonNull final MenuItem item) {
+        if (item.getItemId() == android.R.id.home) {
+            // Action bar back button
+            Intent output = new Intent();
+            output.putExtra("widgetIndex", widgetIndex);
+            output.putExtra("widgetSettings", widget);
+            setResult(RESULT_OK, output);
+            finish();
+            return true;
+        }
+        return super.onOptionsItemSelected(item);
+    }
+
+    public static class HybridHRWatchfaceWidgetFragment extends AbstractPreferenceFragment implements Preference.OnPreferenceChangeListener {
+        static final String FRAGMENT_TAG = "HYBRID_HR_WATCHFACE_WIDGET_FRAGMENT";
+
+        @Override
+        public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) {
+            setPreferencesFromResource(R.xml.fossil_hr_widget_settings, rootKey);
+
+            widgetTypes = HybridHRWatchfaceWidget.getAvailableWidgetTypes(requireActivity().getBaseContext());
+            ListPreference widgetType = findPreference("pref_hybridhr_widget_type");
+            widgetType.setOnPreferenceChangeListener(this);
+            widgetType.setEntries(widgetTypes.values().toArray(new String[0]));
+            widgetType.setEntryValues(widgetTypes.keySet().toArray(new String[0]));
+            widgetType.setValue(widget.getWidgetType());
+            widgetType.setSummary(widgetTypes.get(widget.getWidgetType()));
+            updateEnabledCategories();
+
+            widgetColors = new String[]{getString(R.string.watchface_dialog_widget_color_white), getString(R.string.watchface_dialog_widget_color_black)};
+            ListPreference widgetColor = findPreference("pref_hybridhr_widget_color");
+            widgetColor.setOnPreferenceChangeListener(this);
+            widgetColor.setEntries(widgetColors);
+            widgetColor.setEntryValues(new String[]{"0", "1"});
+            widgetColor.setValueIndex(widget.getColor());
+            widgetColor.setSummary(widgetColors[widget.getColor()]);
+
+            ListPreference widgetBg = findPreference("pref_hybridhr_widget_background");
+            widgetBg.setOnPreferenceChangeListener(this);
+            widgetBg.setValue(widget.getBackground());
+            widgetBg.setSummary(widgetBg.getEntry());
+
+            EditTextPreference posX = findPreference("pref_hybridhr_widget_pos_x");
+            posX.setOnPreferenceChangeListener(this);
+            posX.setText(Integer.toString(widget.getPosX()));
+            posX.setSummary(Integer.toString(widget.getPosX()));
+            setInputTypeFor("pref_hybridhr_widget_pos_x", InputType.TYPE_CLASS_NUMBER);
+
+            EditTextPreference posY = findPreference("pref_hybridhr_widget_pos_y");
+            posY.setOnPreferenceChangeListener(this);
+            posY.setText(Integer.toString(widget.getPosY()));
+            posY.setSummary(Integer.toString(widget.getPosY()));
+            setInputTypeFor("pref_hybridhr_widget_pos_y", InputType.TYPE_CLASS_NUMBER);
+
+            LinkedHashMap<String, String> positionPresets = new LinkedHashMap<String, String>();
+            for (final HybridHRWidgetPosition position : widget.defaultPositions) {
+                positionPresets.put(String.valueOf(position.hintStringResource), getString(position.hintStringResource));
+            }
+            ListPreference widgetPositionPreset = findPreference("pref_hybridhr_widget_pos_preset");
+            widgetPositionPreset.setOnPreferenceChangeListener(this);
+            widgetPositionPreset.setEntries(positionPresets.values().toArray(new String[0]));
+            widgetPositionPreset.setEntryValues(positionPresets.keySet().toArray(new String[0]));
+
+            String[] timezonesList = TimeZone.getAvailableIDs();
+            ListPreference widgetTimezone = findPreference("pref_hybridhr_widget_timezone");
+            widgetTimezone.setOnPreferenceChangeListener(this);
+            widgetTimezone.setEntries(timezonesList);
+            widgetTimezone.setEntryValues(timezonesList);
+            widgetTimezone.setValue(widget.getExtraConfigString("tzName", WIDGET_2NDTZ_DEFAULT_TZ));
+            widgetTimezone.setSummary(widget.getExtraConfigString("tzName", WIDGET_2NDTZ_DEFAULT_TZ));
+
+            EditTextPreference timezoneDuration = findPreference("pref_hybridhr_widget_timezone_timeout");
+            timezoneDuration.setOnPreferenceChangeListener(this);
+            timezoneDuration.setText(Integer.toString(widget.getExtraConfigInt("timeout_secs", WIDGET_2NDTZ_DEFAULT_TIMEOUT)));
+            timezoneDuration.setSummary(Integer.toString(widget.getExtraConfigInt("timeout_secs", WIDGET_2NDTZ_DEFAULT_TIMEOUT)));
+            setInputTypeFor("pref_hybridhr_widget_timezone_timeout", InputType.TYPE_CLASS_NUMBER);
+
+            EditTextPreference width = findPreference("pref_hybridhr_widget_width");
+            width.setOnPreferenceChangeListener(this);
+            width.setText(Integer.toString(widget.getWidth()));
+            width.setSummary(Integer.toString(widget.getWidth()));
+            setInputTypeFor("pref_hybridhr_widget_width", InputType.TYPE_CLASS_NUMBER);
+
+            EditTextPreference customWidgetTimeout = findPreference("pref_hybridhr_widget_custom_timeout");
+            customWidgetTimeout.setOnPreferenceChangeListener(this);
+            customWidgetTimeout.setText(Integer.toString(widget.getExtraConfigInt("update_timeout", WIDGET_CUSTOM_DEFAULT_TIMEOUT)));
+            customWidgetTimeout.setSummary(Integer.toString(widget.getExtraConfigInt("update_timeout", WIDGET_CUSTOM_DEFAULT_TIMEOUT)));
+            setInputTypeFor("pref_hybridhr_widget_custom_timeout", InputType.TYPE_CLASS_NUMBER);
+
+            SwitchPreferenceCompat customWidgetHideText = findPreference("pref_hybridhr_widget_custom_hide_text");
+            customWidgetHideText.setOnPreferenceChangeListener(this);
+            customWidgetHideText.setChecked(widget.getExtraConfigBoolean("timeout_hide_text", WIDGET_CUSTOM_DEFAULT_HIDE_TEXT));
+
+            SwitchPreferenceCompat customWidgetShowCircle = findPreference("pref_hybridhr_widget_custom_show_circle");
+            customWidgetShowCircle.setOnPreferenceChangeListener(this);
+            customWidgetShowCircle.setChecked(widget.getExtraConfigBoolean("timeout_show_circle", WIDGET_CUSTOM_DEFAULT_SHOW_CIRCLE));
+        }
+
+        @Override
+        public boolean onPreferenceChange(Preference preference, Object newValue) {
+            switch (preference.getKey()) {
+                case "pref_hybridhr_widget_type":
+                    widget.setWidgetType(newValue.toString());
+                    preference.setSummary(widgetTypes.get(widget.getWidgetType()));
+                    updateEnabledCategories();
+                    if (newValue.toString().equals("widget2ndTZ")) {
+                        widget.setExtraConfigString("tzName", widget.getExtraConfigString("tzName", WIDGET_2NDTZ_DEFAULT_TZ));
+                        widget.setExtraConfigInt("timeout_secs", widget.getExtraConfigInt("timeout_secs", WIDGET_2NDTZ_DEFAULT_TIMEOUT));
+                    } else if (newValue.toString().equals("widgetCustom")) {
+                        widget.setExtraConfigInt("update_timeout", widget.getExtraConfigInt("update_timeout", WIDGET_CUSTOM_DEFAULT_TIMEOUT));
+                        widget.setExtraConfigBoolean("timeout_hide_text", widget.getExtraConfigBoolean("timeout_hide_text", WIDGET_CUSTOM_DEFAULT_HIDE_TEXT));
+                        widget.setExtraConfigBoolean("timeout_show_circle", widget.getExtraConfigBoolean("timeout_show_circle", WIDGET_CUSTOM_DEFAULT_SHOW_CIRCLE));
+                    }
+                    break;
+                case "pref_hybridhr_widget_color":
+                    widget.setColor(Integer.parseInt(newValue.toString()));
+                    preference.setSummary(widgetColors[widget.getColor()]);
+                    break;
+                case "pref_hybridhr_widget_background":
+                    widget.setBackground(newValue.toString());
+                    ((ListPreference)preference).setValue(newValue.toString());
+                    preference.setSummary(((ListPreference)preference).getEntry());
+                    break;
+                case "pref_hybridhr_widget_pos_x":
+                    widget.setPosX(Integer.parseInt(newValue.toString()));
+                    preference.setSummary(newValue.toString());
+                    break;
+                case "pref_hybridhr_widget_pos_y":
+                    widget.setPosY(Integer.parseInt(newValue.toString()));
+                    preference.setSummary(newValue.toString());
+                    break;
+                case "pref_hybridhr_widget_pos_preset":
+                    LinkedHashMap<String, String> positionPresets = new LinkedHashMap<String, String>();
+                    for (final HybridHRWidgetPosition position : widget.defaultPositions){
+                        if (newValue.toString().equals(String.valueOf(position.hintStringResource))) {
+                            widget.setPosX(position.posX);
+                            widget.setPosY(position.posY);
+                            EditTextPreference prefPosX = findPreference("pref_hybridhr_widget_pos_x");
+                            EditTextPreference prefPosY = findPreference("pref_hybridhr_widget_pos_y");
+                            prefPosX.setSummary(String.valueOf(position.posX));
+                            prefPosY.setSummary(String.valueOf(position.posY));
+                        }
+                    }
+                    break;
+                case "pref_hybridhr_widget_timezone":
+                    widget.setExtraConfigString("tzName", newValue.toString());
+                    preference.setSummary(newValue.toString());
+                    break;
+                case "pref_hybridhr_widget_timezone_timeout":
+                    widget.setExtraConfigInt("timeout_secs", Integer.parseInt(newValue.toString()));
+                    preference.setSummary(newValue.toString());
+                    break;
+                case "pref_hybridhr_widget_width":
+                    widget.setWidth(Integer.parseInt(newValue.toString()));
+                    preference.setSummary(newValue.toString());
+                    break;
+                case "pref_hybridhr_widget_custom_timeout":
+                    widget.setExtraConfigInt("update_timeout", Integer.parseInt(newValue.toString()));
+                    preference.setSummary(newValue.toString());
+                    break;
+                case "pref_hybridhr_widget_custom_hide_text":
+                    widget.setExtraConfigBoolean("timeout_hide_text", (boolean) newValue);
+                    break;
+                case "pref_hybridhr_widget_custom_show_circle":
+                    widget.setExtraConfigBoolean("timeout_show_circle", (boolean) newValue);
+                    break;
+            }
+            return true;
+        }
+
+        private void updateEnabledCategories() {
+            PreferenceCategory cat2ndTZ = findPreference("widget_pref_category_2nd_tz_widget");
+            if (widget.getWidgetType().equals("widget2ndTZ")) {
+                cat2ndTZ.setEnabled(true);
+            } else {
+                cat2ndTZ.setEnabled(false);
+            }
+            PreferenceCategory catCustom = findPreference("widget_pref_category_custom_widget");
+            if (widget.getWidgetType().equals("widgetCustom")) {
+                catCustom.setEnabled(true);
+            } else {
+                catCustom.setEnabled(false);
+            }
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/HybridHRWidgetPosition.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/HybridHRWidgetPosition.java
new file mode 100644
index 0000000..45e6ca7
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/HybridHRWidgetPosition.java
@@ -0,0 +1,27 @@
+/*  Copyright (C) 2022-2024 Arjan Schrijver
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.qhybrid;
+
+class HybridHRWidgetPosition{
+    int posX, posY, hintStringResource;
+
+    public HybridHRWidgetPosition(int posX, int posY, int hintStringResource) {
+        this.posX = posX;
+        this.posY = posY;
+        this.hintStringResource = hintStringResource;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/ImageEditActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/ImageEditActivity.java
new file mode 100644
index 0000000..a484275
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/ImageEditActivity.java
@@ -0,0 +1,276 @@
+/*  Copyright (C) 2020-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.qhybrid;
+
+import android.content.ContentResolver;
+import android.content.Intent;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.MediaStore;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.RelativeLayout;
+import android.widget.Toast;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBActivity;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.image.AssetImage;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.image.AssetImageFactory;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class ImageEditActivity extends AbstractGBActivity implements View.OnTouchListener {
+    static final public int RESULT_CODE_EDIT_SUCCESS = 0;
+
+    ImageView overlay;
+    Canvas overlayCanvas;
+    Paint overlayPaint;
+    Bitmap overlayBitmap, mainBitmap;
+
+    float x = 0, y = 0, diameter = 0, imageDimension = 0;
+    int imageWidth, imageHeight;
+
+    private enum MovementState {
+        MOVE_UPPER_LEFT,
+        MOVE_LOWER_RIGHT,
+        MOVE_FRAME
+    }
+    private MovementState movementState;
+    float movementStartX, movementStartY, movementStartFrameX, movementStartFrameY, movementStartDiameter, leftUpperDeltaX, leftUpperDeltaY;
+
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_qhybrid_image_edit);
+
+        final RelativeLayout mainLayout = findViewById(R.id.qhybrid_image_edit_container);
+        overlay = findViewById(R.id.qhybrid_image_edit_image_overlay);
+        overlay.setOnTouchListener(this);
+
+        findViewById(R.id.qhybrid_image_edit_okay).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                finalizeImage();
+            }
+        });
+
+        mainLayout.post(new Runnable() {
+            @Override
+            public void run() {
+                try {
+                    fitImageToLayout(mainLayout);
+                } catch (IOException | RuntimeException e) {
+                    GB.log("Error formatting image", GB.ERROR, e);
+                    GB.toast("Error formatting image", Toast.LENGTH_LONG, GB.ERROR);
+                    finish();
+                }
+            }
+        });
+    }
+
+    private void finalizeImage(){
+        Bitmap cropped = Bitmap.createBitmap(this.mainBitmap, (int) this.x, (int) this.y, (int) this.diameter, (int) this.diameter);
+        Bitmap scaled = Bitmap.createScaledBitmap(cropped, 400, 400, false);
+        cropped.recycle();
+
+        try {
+            AssetImage image = AssetImageFactory.createAssetImage(scaled, false, 0, 0, 0);
+
+            Intent resultIntent = new Intent();
+            resultIntent.putExtra("EXTRA_PIXELS_ENCODED", image.getFileData());
+            setResult(RESULT_CODE_EDIT_SUCCESS, resultIntent);
+            finish();
+        } catch (IOException e) {
+            e.printStackTrace();
+        } finally {
+
+            scaled.recycle();
+        }
+    }
+
+    private void fitImageToLayout(RelativeLayout mainLayout) throws IOException, RuntimeException {
+        float containerHeight = mainLayout.getHeight();
+        float containerWidth = mainLayout.getWidth();
+        float containerRelation = containerHeight / containerWidth;
+
+        Bitmap bitmap = this.createImageFromURI();
+        float imageHeight = bitmap.getHeight();
+        float imageWidth = bitmap.getWidth();
+        float imageRelation = imageHeight / imageWidth;
+
+        float scaleRatio;
+        if(imageRelation > containerRelation){
+            scaleRatio = containerHeight / imageHeight;
+        }else{
+            scaleRatio = containerWidth / imageWidth;
+        }
+
+        int scaledHeight = (int)(imageHeight * scaleRatio);
+        int scaledWidth = (int)(imageWidth * scaleRatio);
+
+        this.imageHeight = scaledHeight;
+        this.imageWidth = scaledWidth;
+
+        this.imageDimension = this.diameter = Math.min(scaledHeight, scaledWidth);
+
+        mainBitmap = Bitmap.createScaledBitmap(bitmap, scaledWidth, scaledHeight, false);
+
+        ImageView mainImageView = findViewById(R.id.qhybrid_image_edit_image);
+        mainImageView.setImageBitmap(mainBitmap);
+        createOverlay(scaledWidth, scaledHeight);
+    }
+
+    private void createOverlay(int width, int height){
+        this.overlayBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_4444);
+        this.overlayCanvas = new Canvas(this.overlayBitmap);
+
+        this.overlayPaint = new Paint();
+        this.overlayPaint.setColor(Color.BLACK);
+        this.overlayPaint.setStyle(Paint.Style.STROKE);
+        this.overlayPaint.setStrokeWidth(imageDimension / 100);
+
+        renderOverlay();
+    }
+
+    private Bitmap createImageFromURI() throws IOException, RuntimeException {
+        Uri imageURI = getIntent().getData();
+        if (imageURI == null) {
+            throw new RuntimeException("no image attached");
+        }
+
+        ContentResolver resolver = getContentResolver();
+        Cursor c = resolver.query(imageURI, new String[]{MediaStore.Images.ImageColumns.ORIENTATION}, null, null, null);
+        c.moveToFirst();
+        int orientation = c.getInt(c.getColumnIndex(MediaStore.Images.ImageColumns.ORIENTATION));
+        c.close();
+        Bitmap bitmap = MediaStore.Images.Media.getBitmap(this.getContentResolver(), imageURI);
+        if (orientation != 0) {
+            Matrix matrix = new Matrix();
+            matrix.postRotate(90);
+
+            bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
+        }
+
+        return bitmap;
+    }
+
+    private void renderOverlay() {
+        overlayCanvas.drawColor(0, PorterDuff.Mode.CLEAR);
+
+        overlayCanvas.drawCircle(x, y, imageDimension / 15, overlayPaint);
+        overlayCanvas.drawCircle(x + diameter, y + diameter, imageDimension / 15, overlayPaint);
+
+        overlayCanvas.drawCircle(x + diameter / 2, y + diameter / 2, diameter / 2, overlayPaint);
+
+        overlay.setImageBitmap(overlayBitmap);
+    }
+
+    private void forceImageBoundaries(){
+        this.x = Math.max(this.x, 0);
+        this.y = Math.max(this.y, 0);
+
+        this.x = Math.min(this.x, this.imageWidth - diameter);
+        this.y = Math.min(this.y, this.imageHeight - diameter);
+    }
+
+    @Override
+    public boolean onTouch(View view, MotionEvent motionEvent) {
+        if(motionEvent.getAction() == MotionEvent.ACTION_DOWN){
+            handleTouchDown(motionEvent);
+        }else if(motionEvent.getAction() == MotionEvent.ACTION_MOVE){
+            handleTouchMove(motionEvent);
+        }
+        return true;
+    }
+
+    private void handleTouchMove(MotionEvent motionEvent){
+        if(movementState == MovementState.MOVE_UPPER_LEFT){
+            float moveDeltaX = motionEvent.getX() - this.movementStartX;
+            float moveDeltaY = motionEvent.getY() - this.movementStartY;
+
+            float mid = (moveDeltaX + moveDeltaY) / 2;
+
+            this.diameter = this.movementStartDiameter - mid;
+            this.x = this.movementStartX + mid + this.leftUpperDeltaX;
+            this.y = this.movementStartY + mid + this.leftUpperDeltaY;
+        }else if(movementState == MovementState.MOVE_LOWER_RIGHT) {
+            float moveDeltaX = motionEvent.getX() - this.movementStartX;
+            float moveDeltaY = motionEvent.getY() - this.movementStartY;
+
+            float mid = (moveDeltaX + moveDeltaY) / 2;
+
+            this.diameter = this.movementStartDiameter + mid;
+        }else if(movementState == MovementState.MOVE_FRAME){
+            this.x = this.movementStartFrameX + (motionEvent.getX() - this.movementStartX);
+            this.y = this.movementStartFrameY + (motionEvent.getY() - this.movementStartY);
+        }
+
+        this.forceImageBoundaries();
+        renderOverlay();
+    }
+
+    private void handleTouchDown(MotionEvent motionEvent) {
+        this.movementStartX = motionEvent.getX();
+        this.movementStartY = motionEvent.getY();
+        this.movementStartFrameX = this.x;
+        this.movementStartFrameY = this.y;
+        this.movementStartDiameter = this.diameter;
+
+        final float threshold = imageDimension / 15;
+
+        float upperLeftDeltaX = this.x - motionEvent.getX();
+        float upperLeftDeltaY = this.y - motionEvent.getY();
+
+        float upperLeftDistance = (float) Math.sqrt(upperLeftDeltaX * upperLeftDeltaX + upperLeftDeltaY * upperLeftDeltaY);
+
+        if(upperLeftDistance < threshold){
+            // Toast.makeText(this, "upper left", 0).show();
+            this.leftUpperDeltaX = upperLeftDeltaX;
+            this.leftUpperDeltaY = upperLeftDeltaY;
+            this.movementState = MovementState.MOVE_UPPER_LEFT;
+            return;
+        }
+
+        float lowerLeftX = this.x + diameter;
+        float lowerLeftY = this.y + diameter;
+
+        float lowerRightDeltaX = lowerLeftX - motionEvent.getX();
+        float lowerRightDeltaY = lowerLeftY - motionEvent.getY();
+
+        float lowerRightDistance = (float) Math.sqrt(lowerRightDeltaX * lowerRightDeltaX + lowerRightDeltaY * lowerRightDeltaY);
+
+        if(lowerRightDistance < threshold){
+            // Toast.makeText(this, "lower right", 0).show();
+            this.movementState = MovementState.MOVE_LOWER_RIGHT;
+            return;
+        }
+
+        // Toast.makeText(this, "anywhere else", 0).show();
+        this.movementState = MovementState.MOVE_FRAME;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/NotificationConfiguration.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/NotificationConfiguration.java
new file mode 100644
index 0000000..8181902
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/NotificationConfiguration.java
@@ -0,0 +1,131 @@
+/*  Copyright (C) 2019-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.qhybrid;
+
+import android.util.Log;
+
+import java.io.Serializable;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.PlayNotificationRequest;
+
+public class NotificationConfiguration implements Serializable {
+    private short min, hour, subEye = -1;
+    private String packageName, appName;
+    private PlayNotificationRequest.VibrationType vibration;
+    private boolean respectSilentMode;
+    private long id = -1;
+
+    NotificationConfiguration(short min, short hour, String packageName, String appName, boolean respectSilentMode, PlayNotificationRequest.VibrationType vibration) {
+        this.min = min;
+        this.hour = hour;
+        this.packageName = packageName;
+        this.appName = appName;
+        this.respectSilentMode = respectSilentMode;
+        this.vibration = vibration;
+    }
+
+    public NotificationConfiguration(short min, short hour, short subEye, PlayNotificationRequest.VibrationType vibration) {
+        this.min = min;
+        this.hour = hour;
+        this.subEye = subEye;
+        this.vibration = vibration;
+    }
+
+    public NotificationConfiguration(short min, short hour, String packageName, String appName, boolean respectSilentMode, PlayNotificationRequest.VibrationType vibration, long id) {
+        this.min = min;
+        this.hour = hour;
+        this.packageName = packageName;
+        this.appName = appName;
+        this.respectSilentMode = respectSilentMode;
+        this.vibration = vibration;
+        this.id = id;
+    }
+    NotificationConfiguration(String packageName, String appName) {
+        this.min = -1;
+        this.hour = -1;
+        this.packageName = packageName;
+        this.appName = appName;
+        this.respectSilentMode = false;
+        this.vibration = PlayNotificationRequest.VibrationType.SINGLE_NORMAL;
+        this.id = -1;
+    }
+
+    public PlayNotificationRequest.VibrationType getVibration() {
+        return vibration;
+    }
+
+    public void setVibration(PlayNotificationRequest.VibrationType vibration) {
+        this.vibration = vibration;
+    }
+
+    public long getId() {
+        return id;
+    }
+
+    public void setId(long id) {
+        this.id = id;
+    }
+
+    public boolean getRespectSilentMode() {
+        Log.d("Config", "respect: " + respectSilentMode);
+        return respectSilentMode;
+    }
+
+    public void setRespectSilentMode(boolean respectSilentMode) {
+        this.respectSilentMode = respectSilentMode;
+    }
+
+    public void setMin(short min) {
+        this.min = min;
+    }
+
+    public void setHour(short hour) {
+        this.hour = hour;
+    }
+
+    public void setPackageName(String packageName) {
+        this.packageName = packageName;
+    }
+
+    public void setAppName(String appName) {
+        this.appName = appName;
+    }
+
+    public short getMin() {
+        return min;
+    }
+
+    public short getHour() {
+        return hour;
+    }
+
+    public short getSubEye() {
+        return subEye;
+    }
+
+    public void setSubEye(short subEye) {
+        this.subEye = subEye;
+    }
+
+    public String getPackageName() {
+        return packageName;
+    }
+
+    public String getAppName() {
+        return appName;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/NotificationHRConfiguration.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/NotificationHRConfiguration.java
new file mode 100644
index 0000000..3974cff
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/NotificationHRConfiguration.java
@@ -0,0 +1,60 @@
+/*  Copyright (C) 2019-2024 Arjan Schrijver, Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.qhybrid;
+
+import java.io.Serializable;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.zip.CRC32;
+
+public class NotificationHRConfiguration implements Serializable {
+    private String packageName;
+    private String iconName;
+    private byte[] packageCrc;
+
+    public NotificationHRConfiguration(String packageName, String iconName) {
+        this.packageName = packageName;
+        this.iconName = iconName;
+
+        CRC32 crc = new CRC32();
+        crc.update(packageName.getBytes());
+
+        this.packageCrc = ByteBuffer
+                .allocate(4)
+                .order(ByteOrder.LITTLE_ENDIAN)
+                .putInt((int) crc.getValue())
+                .array();
+    }
+
+    public NotificationHRConfiguration(String packageName, byte[] packageCrc, String iconName) {
+        this.packageCrc = packageCrc;
+        this.packageName = packageName;
+        this.iconName = iconName;
+    }
+
+    public String getPackageName() {
+        return packageName;
+    }
+
+    public String getIconName() {
+        return iconName;
+    }
+
+    public byte[] getPackageCrc() {
+        return packageCrc;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/PackageConfigHelper.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/PackageConfigHelper.java
new file mode 100644
index 0000000..aee688f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/PackageConfigHelper.java
@@ -0,0 +1,156 @@
+/*  Copyright (C) 2019-2024 Carsten Pfeiffer, Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.qhybrid;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.util.Log;
+
+import java.util.ArrayList;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.PlayNotificationRequest;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class PackageConfigHelper {
+    public static final String DB_NAME = "qhybridNotifications.db";
+    public static final String DB_ID = "id";
+    public static final String DB_TABLE = "notifications";
+    public static final String DB_PACKAGE = "package";
+    public static final String DB_APPNAME = "appName";
+    public static final String DB_VIBRATION = "vibrationTtype";
+    public static final String DB_MINUTE = "minDegress";
+    public static final String DB_HOUR = "hourDegrees";
+    public static final String DB_RESPECT_SILENT = "respectSilent";
+
+
+    public PackageConfigHelper(Context context) {
+        try {
+            initDB();
+        } catch (Exception e) {
+            GB.log("error getting database", GB.ERROR, e);
+        }
+    }
+
+    public void saveNotificationConfiguration(NotificationConfiguration settings) throws Exception {
+        ContentValues values = new ContentValues(6);
+        values.put(DB_PACKAGE, settings.getPackageName());
+        values.put(DB_APPNAME, settings.getAppName());
+        values.put(DB_HOUR, settings.getHour());
+        values.put(DB_MINUTE, settings.getMin());
+        values.put(DB_VIBRATION, settings.getVibration().getValue());
+        values.put(DB_RESPECT_SILENT, settings.getRespectSilentMode());
+
+        try (DBHandler db = GBApplication.acquireDB()) {
+            SQLiteDatabase database = db.getDatabase();
+
+            if (settings.getId() == -1) {
+                settings.setId(database.insert(DB_TABLE, null, values));
+            } else {
+                database.update(DB_TABLE, values, DB_ID + "=?", new String[]{String.valueOf(settings.getId())});
+            }
+        }
+        //LocalBroadcastManager.getInstance(this).sendBroadcast(new Intent());
+    }
+
+    public ArrayList<NotificationConfiguration> getNotificationConfigurations() throws Exception {
+        try (DBHandler db = GBApplication.acquireDB()) {
+            SQLiteDatabase database = db.getDatabase();
+
+            try (Cursor cursor = database.query(DB_TABLE, new String[]{"*"}, null, null, null, null, null)) {
+                int size = cursor.getCount();
+                ArrayList<NotificationConfiguration> list = new ArrayList<>(size);
+                if (size > 0) {
+                    int appNamePos = cursor.getColumnIndex(DB_APPNAME);
+                    int packageNamePos = cursor.getColumnIndex(DB_PACKAGE);
+                    int hourPos = cursor.getColumnIndex(DB_HOUR);
+                    int minPos = cursor.getColumnIndex(DB_MINUTE);
+                    int silentPos = cursor.getColumnIndex(DB_RESPECT_SILENT);
+                    int vibrationPos = cursor.getColumnIndex(DB_VIBRATION);
+                    int idPos = cursor.getColumnIndex(DB_ID);
+                    cursor.moveToFirst();
+                    do {
+                        list.add(new NotificationConfiguration(
+                                (short) cursor.getInt(minPos),
+                                (short) cursor.getInt(hourPos),
+                                cursor.getString(packageNamePos),
+                                cursor.getString(appNamePos),
+                                cursor.getInt(silentPos) == 1,
+                                PlayNotificationRequest.VibrationType.fromValue((byte) cursor.getInt(vibrationPos)),
+                                cursor.getInt(idPos)
+                        ));
+                        Log.d("Settings", "setting #" + cursor.getPosition() + ": " + cursor.getInt(silentPos));
+                    } while (cursor.moveToNext());
+                }
+                return list;
+            }
+        }
+    }
+
+    public NotificationConfiguration getNotificationConfiguration(String appName) throws Exception {
+        if(appName == null) return null;
+
+        try (DBHandler db = GBApplication.acquireDB()) {
+            SQLiteDatabase database = db.getDatabase();
+
+            try (Cursor c = database.query(DB_TABLE, new String[]{"*"}, DB_APPNAME + "=?", new String[]{appName}, null, null, null)) {
+                if (c.getCount() == 0) {
+                    return null;
+                }
+                c.moveToFirst();
+                NotificationConfiguration settings = new NotificationConfiguration(
+                        (short) c.getInt(c.getColumnIndex(DB_MINUTE)),
+                        (short) c.getInt(c.getColumnIndex(DB_HOUR)),
+                        c.getString(c.getColumnIndex(DB_PACKAGE)),
+                        c.getString(c.getColumnIndex(DB_APPNAME)),
+                        c.getInt(c.getColumnIndex(DB_RESPECT_SILENT)) == 1,
+                        PlayNotificationRequest.VibrationType.fromValue((byte) c.getInt(c.getColumnIndex(DB_VIBRATION))),
+                        c.getInt(c.getColumnIndex(DB_ID))
+                );
+                return settings;
+            }
+        }
+    }
+
+    private void initDB() throws Exception {
+        try (DBHandler db = GBApplication.acquireDB()) {
+            SQLiteDatabase database = db.getDatabase();
+
+            database.execSQL("CREATE TABLE IF NOT EXISTS notifications(" +
+                    DB_ID + " INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, " +
+                    DB_PACKAGE + " TEXT, " +
+                    DB_VIBRATION + " INTEGER, " +
+                    DB_MINUTE + " INTEGER DEFAULT -1, " +
+                    DB_APPNAME + " TEXT," +
+                    DB_RESPECT_SILENT + " INTEGER," +
+                    DB_HOUR + " INTEGER DEFAULT -1);");
+        }
+    }
+
+    public void deleteNotificationConfiguration(NotificationConfiguration packageSettings) throws Exception {
+        Log.d("DB", "deleting id " + packageSettings.getId());
+        if(packageSettings.getId() == -1) return;
+
+        try (DBHandler db = GBApplication.acquireDB()) {
+            SQLiteDatabase database = db.getDatabase();
+            database.delete(DB_TABLE, DB_ID + "=?", new String[]{String.valueOf(packageSettings.getId())});
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/QHybridAppChoserActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/QHybridAppChoserActivity.java
new file mode 100644
index 0000000..657616f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/QHybridAppChoserActivity.java
@@ -0,0 +1,197 @@
+/*  Copyright (C) 2019-2024 Carsten Pfeiffer, Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.qhybrid;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.IdentityHashMap;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBActivity;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.QHybridSupport;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+import static android.view.View.GONE;
+
+public class QHybridAppChoserActivity extends AbstractGBActivity {
+    boolean hasControl = false;
+
+    private PackageConfigHelper helper;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_qhybrid_app_choser);
+
+        helper = new PackageConfigHelper(getApplicationContext());
+
+        final ListView appList = findViewById(R.id.qhybrid_appChooserList);
+        final PackageManager manager = getPackageManager();
+        final List<PackageInfo> packages = manager.getInstalledPackages(0);
+        new Thread(new Runnable() {
+            @Override
+            public void run() {
+                final IdentityHashMap<PackageInfo, String> nameMap = new IdentityHashMap(packages.size());
+                for(PackageInfo info : packages){
+                    CharSequence label = manager.getApplicationLabel(info.applicationInfo);
+                    if(label == null) label = info.packageName;
+                    nameMap.put(info, label.toString());
+                }
+
+                Collections.sort(packages, new Comparator<PackageInfo>() {
+                    @Override
+                    public int compare(PackageInfo packageInfo, PackageInfo t1) {
+                        return nameMap.get(packageInfo)
+                                .compareToIgnoreCase(
+                                        nameMap.get(t1)
+                                );
+                    }
+                });
+                runOnUiThread(new Runnable() {
+                    @Override
+                    public void run() {
+                        appList.setAdapter(new ConfigArrayAdapter(QHybridAppChoserActivity.this, R.layout.qhybrid_app_view, packages, manager));
+                        findViewById(R.id.qhybrid_packageChooserLoading).setVisibility(GONE);
+                    }
+                });
+            }
+        }).start();
+        appList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+            @Override
+            public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
+                showPackageDialog(packages.get(i));
+            }
+        });
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+    }
+
+    private void setControl(boolean control) {
+        if (hasControl == control) return;
+        Intent intent = new Intent(control ? QHybridSupport.QHYBRID_COMMAND_CONTROL : QHybridSupport.QHYBRID_COMMAND_UNCONTROL);
+        LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
+        this.hasControl = control;
+    }
+
+    private void setHands(NotificationConfiguration config){
+        sendControl(config, QHybridSupport.QHYBRID_COMMAND_SET);
+    }
+
+    private void vibrate(NotificationConfiguration config){
+        sendControl(config, QHybridSupport.QHYBRID_COMMAND_VIBRATE);
+    }
+
+    private void sendControl(NotificationConfiguration config, String request){
+        Intent intent = new Intent(request);
+        intent.putExtra("CONFIG", config);
+        LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
+    }
+
+    @SuppressLint("ClickableViewAccessibility")
+    private void showPackageDialog(PackageInfo info) {
+        TimePicker picker = new TimePicker(this, info);
+
+        picker.finishListener = new TimePicker.OnFinishListener() {
+            @Override
+            public void onFinish(boolean success, NotificationConfiguration config) {
+                setControl(false);
+                if(success){
+                    try {
+                        helper.saveNotificationConfiguration(config);
+                        LocalBroadcastManager.getInstance(QHybridAppChoserActivity.this).sendBroadcast(new Intent(QHybridSupport.QHYBRID_COMMAND_NOTIFICATION_CONFIG_CHANGED));
+                    } catch (Exception e) {
+                        GB.toast("error saving configuration", Toast.LENGTH_SHORT, GB.ERROR, e);
+                    }
+                    finish();
+                }
+            }
+        };
+
+        picker.handsListener = new TimePicker.OnHandsSetListener() {
+            @Override
+            public void onHandsSet(NotificationConfiguration config) {
+                setHands(config);
+            }
+        };
+
+        picker.vibrationListener = new TimePicker.OnVibrationSetListener() {
+            @Override
+            public void onVibrationSet(NotificationConfiguration config) {
+                vibrate(config);
+            }
+        };
+
+        setControl(true);
+    }
+
+
+
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+        setControl(false);
+        finish();
+    }
+
+    class ConfigArrayAdapter extends ArrayAdapter<PackageInfo> {
+        PackageManager manager;
+
+        public ConfigArrayAdapter(@NonNull Context context, int resource, @NonNull List<PackageInfo> objects, PackageManager manager) {
+            super(context, resource, objects);
+            this.manager = manager;
+        }
+
+        @NonNull
+        @Override
+        public View getView(int position, @Nullable View view, @NonNull ViewGroup parent) {
+            if (view == null)
+                view = ((LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE)).inflate(R.layout.qhybrid_app_view, null);
+
+            ApplicationInfo info = getItem(position).applicationInfo;
+            ((ImageView) view.findViewById(R.id.qhybrid_appChooserItemIcon)).setImageDrawable(manager.getApplicationIcon(info));
+            ((TextView) view.findViewById(R.id.qhybrid_appChooserItemText)).setText(manager.getApplicationLabel(info));
+
+            return view;
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/QHybridConfigActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/QHybridConfigActivity.java
new file mode 100644
index 0000000..197b5a6
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/QHybridConfigActivity.java
@@ -0,0 +1,671 @@
+/*  Copyright (C) 2019-2024 Andreas Shimokawa, Arjan Schrijver, Carsten
+    Pfeiffer, Daniel Dakhno, Taavi Eom盲e
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.qhybrid;
+
+import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.os.Bundle;
+import android.view.Gravity;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import android.widget.EditText;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.ListView;
+import android.widget.NumberPicker;
+import android.widget.PopupMenu;
+import android.widget.RelativeLayout;
+import android.widget.SeekBar;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AlertDialog;
+import androidx.core.content.ContextCompat;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import com.google.android.material.dialog.MaterialAlertDialogBuilder;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+
+import java.text.DecimalFormat;
+import java.util.ArrayList;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBActivity;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.model.GenericItem;
+import nodomain.freeyourgadget.gadgetbridge.model.ItemWithDetails;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.QHybridSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil.FossilWatchAdapter;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.buttonconfig.ConfigPayload;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class QHybridConfigActivity extends AbstractGBActivity {
+    PackageAdapter adapter;
+    ArrayList<NotificationConfiguration> list;
+    PackageConfigHelper helper;
+
+    final int REQUEST_CODE_ADD_APP = 0;
+
+    private boolean hasControl = false;
+
+    SharedPreferences prefs;
+
+    TextView timeOffsetView, timezoneOffsetView;
+
+    GBDevice device;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        device = getIntent().getParcelableExtra(GBDevice.EXTRA_DEVICE);
+
+        super.onCreate(savedInstanceState);
+
+        if (device == null) {
+            GB.toast(this, "Device is null", Toast.LENGTH_LONG, GB.ERROR);
+            finish();
+            return;
+        }
+
+        setContentView(R.layout.activity_qhybrid_settings);
+
+        findViewById(R.id.buttonOverwriteButtons).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                LocalBroadcastManager.getInstance(QHybridConfigActivity.this).sendBroadcast(new Intent(QHybridSupport.QHYBRID_COMMAND_OVERWRITE_BUTTONS));
+            }
+        });
+
+        prefs = GBApplication.getDeviceSpecificSharedPrefs(device.getAddress());
+        timeOffsetView = findViewById(R.id.qhybridTimeOffset);
+        timeOffsetView.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                int timeOffset = prefs.getInt("QHYBRID_TIME_OFFSET", 0);
+                LinearLayout layout2 = new LinearLayout(QHybridConfigActivity.this);
+                layout2.setOrientation(LinearLayout.HORIZONTAL);
+
+                final NumberPicker hourPicker = new NumberPicker(QHybridConfigActivity.this);
+                hourPicker.setMinValue(0);
+                hourPicker.setMaxValue(23);
+                hourPicker.setValue(timeOffset / 60);
+
+                final NumberPicker minPicker = new NumberPicker(QHybridConfigActivity.this);
+                minPicker.setMinValue(0);
+                minPicker.setMaxValue(59);
+                minPicker.setValue(timeOffset % 60);
+
+                layout2.addView(hourPicker);
+                TextView tw = new TextView(QHybridConfigActivity.this);
+                tw.setText(":");
+                layout2.addView(tw);
+                layout2.addView(minPicker);
+
+                layout2.setGravity(Gravity.CENTER);
+
+                new MaterialAlertDialogBuilder(QHybridConfigActivity.this)
+                        .setTitle(getString(R.string.qhybrid_offset_time_by))
+                        .setView(layout2)
+                        .setPositiveButton("ok", new DialogInterface.OnClickListener() {
+                            @Override
+                            public void onClick(DialogInterface dialogInterface, int i) {
+                                prefs.edit().putInt("QHYBRID_TIME_OFFSET", hourPicker.getValue() * 60 + minPicker.getValue()).apply();
+                                updateTimeOffset();
+                                LocalBroadcastManager.getInstance(QHybridConfigActivity.this).sendBroadcast(new Intent(QHybridSupport.QHYBRID_COMMAND_UPDATE));
+                                GB.toast(getString(R.string.qhybrid_changes_delay_prompt), Toast.LENGTH_SHORT, GB.INFO);
+                            }
+                        })
+                        .setNegativeButton("cancel", null)
+                        .show();
+            }
+        });
+        updateTimeOffset();
+
+
+        timezoneOffsetView = findViewById(R.id.timezoneOffset);
+        timezoneOffsetView.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                int timeOffset = prefs.getInt("QHYBRID_TIMEZONE_OFFSET", 0);
+                LinearLayout layout2 = new LinearLayout(QHybridConfigActivity.this);
+                layout2.setOrientation(LinearLayout.HORIZONTAL);
+
+                final NumberPicker hourPicker = new NumberPicker(QHybridConfigActivity.this);
+                hourPicker.setMinValue(0);
+                hourPicker.setMaxValue(23);
+                hourPicker.setValue(timeOffset / 60);
+
+                final NumberPicker minPicker = new NumberPicker(QHybridConfigActivity.this);
+                minPicker.setMinValue(0);
+                minPicker.setMaxValue(59);
+                minPicker.setValue(timeOffset % 60);
+
+                layout2.addView(hourPicker);
+                TextView tw = new TextView(QHybridConfigActivity.this);
+                tw.setText(":");
+                layout2.addView(tw);
+                layout2.addView(minPicker);
+
+                layout2.setGravity(Gravity.CENTER);
+
+                new MaterialAlertDialogBuilder(QHybridConfigActivity.this)
+                        .setTitle(getString(R.string.qhybrid_offset_timezone))
+                        .setView(layout2)
+                        .setPositiveButton("ok", new DialogInterface.OnClickListener() {
+                            @Override
+                            public void onClick(DialogInterface dialogInterface, int i) {
+                                prefs.edit().putInt("QHYBRID_TIMEZONE_OFFSET", hourPicker.getValue() * 60 + minPicker.getValue()).apply();
+                                updateTimezoneOffset();
+                                LocalBroadcastManager.getInstance(QHybridConfigActivity.this).sendBroadcast(new Intent(QHybridSupport.QHYBRID_COMMAND_UPDATE_TIMEZONE));
+                                GB.toast(getString(R.string.qhybrid_changes_delay_prompt), Toast.LENGTH_SHORT, GB.INFO);
+                            }
+                        })
+                        .setNegativeButton("cancel", null)
+                        .show();
+            }
+        });
+        updateTimezoneOffset();
+
+        setTitle(R.string.preferences_qhybrid_settings);
+
+        ListView appList = findViewById(R.id.qhybrid_appList);
+
+        try {
+            helper = new PackageConfigHelper(getApplicationContext());
+            list = helper.getNotificationConfigurations();
+        } catch (Exception e) {
+            GB.toast("error getting configurations", Toast.LENGTH_SHORT, GB.ERROR, e);
+            list = new ArrayList<>();
+        }
+        // null is added to indicate the plus button added handled in PackageAdapter#getView
+        list.add(null);
+        appList.setAdapter(adapter = new PackageAdapter(this, R.layout.qhybrid_package_settings_item, list));
+        appList.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
+            @Override
+            public boolean onItemLongClick(final AdapterView<?> adapterView, View view, final int i, long l) {
+                PopupMenu menu = new PopupMenu(QHybridConfigActivity.this, view);
+                menu.getMenu().add(0, 0, 0, "edit");
+                menu.getMenu().add(0, 1, 1, "delete");
+                menu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
+                    @Override
+                    public boolean onMenuItemClick(MenuItem menuItem) {
+                        switch (menuItem.getItemId()) {
+                            case 0: {
+                                TimePicker picker = new TimePicker(QHybridConfigActivity.this, (NotificationConfiguration) adapterView.getItemAtPosition(i));
+                                picker.finishListener = new TimePicker.OnFinishListener() {
+                                    @Override
+                                    public void onFinish(boolean success, NotificationConfiguration config) {
+                                        setControl(false, null);
+                                        if (success) {
+                                            try {
+                                                helper.saveNotificationConfiguration(config);
+                                                LocalBroadcastManager.getInstance(QHybridConfigActivity.this).sendBroadcast(new Intent(QHybridSupport.QHYBRID_COMMAND_NOTIFICATION_CONFIG_CHANGED));
+                                            } catch (Exception e) {
+                                                GB.toast("error saving notification", Toast.LENGTH_SHORT, GB.ERROR, e);
+                                            }
+                                            refreshList();
+                                        }
+                                    }
+                                };
+                                picker.handsListener = new TimePicker.OnHandsSetListener() {
+                                    @Override
+                                    public void onHandsSet(NotificationConfiguration config) {
+                                        setHands(config);
+                                    }
+                                };
+                                picker.vibrationListener = new TimePicker.OnVibrationSetListener() {
+                                    @Override
+                                    public void onVibrationSet(NotificationConfiguration config) {
+                                        vibrate(config);
+                                    }
+                                };
+                                setControl(true, picker.getSettings());
+                                break;
+                            }
+                            case 1: {
+                                try {
+                                    helper.deleteNotificationConfiguration((NotificationConfiguration) adapterView.getItemAtPosition(i));
+                                    LocalBroadcastManager.getInstance(QHybridConfigActivity.this).sendBroadcast(new Intent(QHybridSupport.QHYBRID_COMMAND_NOTIFICATION_CONFIG_CHANGED));
+                                } catch (Exception e) {
+                                    GB.toast("error deleting setting", Toast.LENGTH_SHORT, GB.ERROR, e);
+                                }
+                                refreshList();
+                                break;
+                            }
+                        }
+                        return true;
+                    }
+                });
+                menu.show();
+                return true;
+            }
+        });
+
+        appList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+            @Override
+            public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
+                Intent notificationIntent = new Intent(QHybridSupport.QHYBRID_COMMAND_NOTIFICATION);
+                notificationIntent.putExtra("CONFIG", (NotificationConfiguration) adapterView.getItemAtPosition(i));
+                LocalBroadcastManager.getInstance(QHybridConfigActivity.this).sendBroadcast(notificationIntent);
+            }
+        });
+        SeekBar vibeBar = findViewById(R.id.vibrationStrengthBar);
+        vibeBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
+            int start;
+
+            @Override
+            public void onProgressChanged(SeekBar seekBar, int i, boolean b) {
+            }
+
+            @Override
+            public void onStartTrackingTouch(SeekBar seekBar) {
+                start = seekBar.getProgress();
+            }
+
+            @Override
+            public void onStopTrackingTouch(SeekBar seekBar) {
+                int progress;
+                if ((progress = seekBar.getProgress()) == start) return;
+                String[] values = {"25", "50", "100"};
+                device.addDeviceInfo(new GenericItem(QHybridSupport.ITEM_VIBRATION_STRENGTH, values[progress]));
+                Intent intent = new Intent(QHybridSupport.QHYBRID_COMMAND_UPDATE_SETTINGS);
+                intent.putExtra("EXTRA_SETTING", QHybridSupport.ITEM_VIBRATION_STRENGTH);
+                LocalBroadcastManager.getInstance(QHybridConfigActivity.this).sendBroadcast(intent);
+            }
+        });
+
+        if (device.getType() == DeviceType.FOSSILQHYBRID && device.isInitialized() && device.getFirmwareVersion().charAt(2) == '0') {
+            updateSettings();
+        } else {
+            setSettingsError(getString(R.string.watch_not_connected));
+        }
+    }
+
+    private void updateTimeOffset() {
+        int timeOffset = prefs.getInt("QHYBRID_TIME_OFFSET", 0);
+        DecimalFormat format = new DecimalFormat("00");
+        timeOffsetView.setText(
+                format.format(timeOffset / 60) + ":" +
+                        format.format(timeOffset % 60)
+        );
+    }
+
+
+    private void updateTimezoneOffset() {
+        int timeOffset = prefs.getInt("QHYBRID_TIMEZONE_OFFSET", 0);
+        DecimalFormat format = new DecimalFormat("00");
+        timezoneOffsetView.setText(
+                format.format(timeOffset / 60) + ":" +
+                        format.format(timeOffset % 60)
+        );
+    }
+
+
+    private void setSettingsEnabled(boolean enables) {
+        findViewById(R.id.settingsLayout).setAlpha(enables ? 1f : 0.2f);
+    }
+
+    private void updateSettings() {
+        runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                EditText et = findViewById(R.id.stepGoalEt);
+                et.setOnEditorActionListener(null);
+                final String text = device.getDeviceInfo(QHybridSupport.ITEM_STEP_GOAL).getDetails();
+                et.setText(text);
+                et.setSelection(text.length());
+                et.setOnEditorActionListener(new TextView.OnEditorActionListener() {
+                    @Override
+                    public boolean onEditorAction(TextView textView, int i, KeyEvent keyEvent) {
+                        if (i == EditorInfo.IME_ACTION_DONE || i == EditorInfo.IME_ACTION_NEXT) {
+                            String t = textView.getText().toString();
+                            if (!t.isEmpty()) {
+                                device.addDeviceInfo(new GenericItem(QHybridSupport.ITEM_STEP_GOAL, t));
+                                Intent intent = new Intent(QHybridSupport.QHYBRID_COMMAND_UPDATE_SETTINGS);
+                                intent.putExtra("EXTRA_SETTING", QHybridSupport.ITEM_STEP_GOAL);
+                                LocalBroadcastManager.getInstance(QHybridConfigActivity.this).sendBroadcast(intent);
+                                updateSettings();
+                            }
+                            ((InputMethodManager) getApplicationContext().getSystemService(Activity.INPUT_METHOD_SERVICE)).hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), 0);
+                        }
+                        return true;
+                    }
+                });
+
+                if ("true".equals(device.getDeviceInfo(QHybridSupport.ITEM_EXTENDED_VIBRATION_SUPPORT).getDetails())) {
+                    final int strengthProgress = (int) (Math.log(Double.parseDouble(device.getDeviceInfo(QHybridSupport.ITEM_VIBRATION_STRENGTH).getDetails()) / 25) / Math.log(2));
+
+                    setSettingsEnabled(true);
+                    SeekBar seekBar = findViewById(R.id.vibrationStrengthBar);
+                    seekBar.setProgress(strengthProgress);
+                } else {
+                    findViewById(R.id.vibrationStrengthBar).setEnabled(false);
+                    findViewById(R.id.vibrationStrengthLayout).setAlpha(0.5f);
+                }
+                CheckBox activityHandCheckbox = findViewById(R.id.checkBoxUserActivityHand);
+                if (device.getDeviceInfo(QHybridSupport.ITEM_HAS_ACTIVITY_HAND).getDetails().equals("true")) {
+                    if (device.getDeviceInfo(QHybridSupport.ITEM_USE_ACTIVITY_HAND).getDetails().equals("true")) {
+                        activityHandCheckbox.setChecked(true);
+                    }
+                    activityHandCheckbox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+                        @Override
+                        public void onCheckedChanged(CompoundButton buttonView, boolean checked) {
+                            if (!device.getDeviceInfo(QHybridSupport.ITEM_STEP_GOAL).getDetails().equals("1000000")) {
+                                new MaterialAlertDialogBuilder(QHybridConfigActivity.this)
+                                        .setMessage(getString(R.string.qhybrid_prompt_million_steps))
+                                        .setPositiveButton("ok", null)
+                                        .show();
+                                buttonView.setChecked(false);
+                                return;
+                            }
+                            device.addDeviceInfo(new GenericItem(QHybridSupport.ITEM_USE_ACTIVITY_HAND, String.valueOf(checked)));
+                            Intent intent = new Intent(QHybridSupport.QHYBRID_COMMAND_UPDATE_SETTINGS);
+                            intent.putExtra("EXTRA_SETTING", QHybridSupport.ITEM_USE_ACTIVITY_HAND);
+                            LocalBroadcastManager.getInstance(QHybridConfigActivity.this).sendBroadcast(intent);
+                        }
+                    });
+                } else {
+                    // activityHandCheckbox.setEnabled(false);
+                    activityHandCheckbox.setAlpha(0.2f);
+                    activityHandCheckbox.setOnClickListener(new View.OnClickListener() {
+                        @Override
+                        public void onClick(View v) {
+                            GB.toast("nah.", Toast.LENGTH_SHORT, GB.INFO);
+                            ((CheckBox) v).setChecked(false);
+                        }
+                    });
+                }
+
+                ItemWithDetails item = device.getDeviceInfo(FossilWatchAdapter.ITEM_BUTTONS);
+                String buttonJson = null;
+                if(item != null) {
+                    buttonJson = item.getDetails();
+                }
+                try {
+                    JSONArray buttonConfig_;
+                    if (buttonJson == null || buttonJson.isEmpty()) {
+                        buttonConfig_ = new JSONArray(new String[]{"", "", ""});
+                    }else{
+                        buttonConfig_ = new JSONArray(buttonJson);
+                    }
+
+                    final JSONArray buttonConfig = buttonConfig_;
+
+                    LinearLayout buttonLayout = findViewById(R.id.buttonConfigLayout);
+                    buttonLayout.removeAllViews();
+                    findViewById(R.id.buttonOverwriteButtons).setVisibility(View.GONE);
+                    final ConfigPayload[] payloads = ConfigPayload.values();
+                    final String[] names = new String[payloads.length];
+                    for (int i = 0; i < payloads.length; i++)
+                        names[i] = payloads[i].getDescription();
+                    for (int i = 0; i < buttonConfig.length(); i++) {
+                        final int currentIndex = i;
+                        String configName = buttonConfig.getString(i);
+                        TextView buttonTextView = new TextView(QHybridConfigActivity.this);
+                        buttonTextView.setTextSize(20);
+                        try {
+                            ConfigPayload payload = ConfigPayload.valueOf(configName);
+                            buttonTextView.setText("Button " + (i + 1) + ": " + payload.getDescription());
+                        } catch (IllegalArgumentException e) {
+                            buttonTextView.setText("Button " + (i + 1) + ": Unknown");
+                        }
+
+                        buttonTextView.setOnClickListener(new View.OnClickListener() {
+                            @Override
+                            public void onClick(View v) {
+                                AlertDialog dialog = new MaterialAlertDialogBuilder(QHybridConfigActivity.this)
+                                        .setItems(names, new DialogInterface.OnClickListener() {
+                                            @Override
+                                            public void onClick(DialogInterface dialog, int which) {
+                                                dialog.cancel();
+                                                ConfigPayload selected = payloads[which];
+
+                                                try {
+                                                    buttonConfig.put(currentIndex, selected.toString());
+                                                    device.addDeviceInfo(new GenericItem(FossilWatchAdapter.ITEM_BUTTONS, buttonConfig.toString()));
+                                                    updateSettings();
+                                                    Intent buttonIntent = new Intent(QHybridSupport.QHYBRID_COMMAND_OVERWRITE_BUTTONS);
+                                                    buttonIntent.putExtra(FossilWatchAdapter.ITEM_BUTTONS, buttonConfig.toString());
+                                                    LocalBroadcastManager.getInstance(QHybridConfigActivity.this).sendBroadcast(buttonIntent);
+                                                } catch (JSONException e) {
+                                                    GB.log("error", GB.ERROR, e);
+                                                }
+                                            }
+                                        })
+                                        .setTitle("Button " + (currentIndex + 1))
+                                        .create();
+                                dialog.show();
+                            }
+                        });
+
+                        buttonLayout.addView(buttonTextView);
+                    }
+                } catch (JSONException e) {
+                    GB.toast("error parsing button config", Toast.LENGTH_LONG, GB.ERROR);
+                }
+            }
+        });
+    }
+
+    private void setControl(boolean control, NotificationConfiguration config) {
+        if (hasControl == control) return;
+        Intent intent = new Intent(control ? QHybridSupport.QHYBRID_COMMAND_CONTROL : QHybridSupport.QHYBRID_COMMAND_UNCONTROL);
+        intent.putExtra("CONFIG", config);
+        LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
+        this.hasControl = control;
+    }
+
+    private void setHands(NotificationConfiguration config) {
+        sendControl(config, QHybridSupport.QHYBRID_COMMAND_SET);
+    }
+
+    private void vibrate(NotificationConfiguration config) {
+        sendControl(config, QHybridSupport.QHYBRID_COMMAND_VIBRATE);
+    }
+
+    private void sendControl(NotificationConfiguration config, String request) {
+        Intent intent = new Intent(request);
+        intent.putExtra("CONFIG", config);
+        LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
+    }
+
+    private void refreshList() {
+        list.clear();
+        try {
+            list.addAll(helper.getNotificationConfigurations());
+        } catch (Exception e) {
+            GB.toast("error getting configurations", Toast.LENGTH_SHORT, GB.ERROR, e);
+        }
+        // null is added to indicate the plus button added handled in PackageAdapter#getView
+        list.add(null);
+        adapter.notifyDataSetChanged();
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        refreshList();
+        ContextCompat.registerReceiver(this, buttonReceiver, new IntentFilter(QHybridSupport.QHYBRID_EVENT_BUTTON_PRESS), ContextCompat.RECEIVER_NOT_EXPORTED);
+        LocalBroadcastManager.getInstance(this).registerReceiver(settingsReceiver, new IntentFilter(QHybridSupport.QHYBRID_EVENT_SETTINGS_UPDATED));
+        LocalBroadcastManager.getInstance(this).registerReceiver(fileReceiver, new IntentFilter(QHybridSupport.QHYBRID_EVENT_FILE_UPLOADED));
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+        unregisterReceiver(buttonReceiver);
+        LocalBroadcastManager.getInstance(this).unregisterReceiver(settingsReceiver);
+        LocalBroadcastManager.getInstance(this).unregisterReceiver(fileReceiver);
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        super.onActivityResult(requestCode, resultCode, data);
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(@NonNull final MenuItem item) {
+        final int itemId = item.getItemId();
+        if (itemId == android.R.id.home) {
+            this.onBackPressed();
+            return true;
+        }
+
+        return super.onOptionsItemSelected(item);
+    }
+
+    private void setSettingsError(final String error) {
+        runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                setSettingsEnabled(false);
+                ((TextView) findViewById(R.id.settingsErrorText)).setVisibility(View.VISIBLE);
+                ((TextView) findViewById(R.id.settingsErrorText)).setText(error);
+            }
+        });
+    }
+
+    class PackageAdapter extends ArrayAdapter<NotificationConfiguration> {
+        PackageManager manager;
+
+        PackageAdapter(@NonNull Context context, int resource, @NonNull List<NotificationConfiguration> objects) {
+            super(context, resource, objects);
+            manager = context.getPackageManager();
+        }
+
+        @NonNull
+        @Override
+        public View getView(int position, @Nullable View view, @NonNull ViewGroup parent) {
+            if (!(view instanceof RelativeLayout))
+                view = ((LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE)).inflate(R.layout.qhybrid_package_settings_item, null);
+            NotificationConfiguration settings = getItem(position);
+
+            if (settings == null) {
+                Button addButton = new Button(QHybridConfigActivity.this);
+                addButton.setText("+");
+                addButton.setLayoutParams(new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
+                addButton.setOnClickListener(new View.OnClickListener() {
+                    @Override
+                    public void onClick(View view) {
+                        startActivityForResult(new Intent(QHybridConfigActivity.this, QHybridAppChoserActivity.class), REQUEST_CODE_ADD_APP);
+                    }
+                });
+                return addButton;
+            }
+
+            try {
+                ((ImageView) view.findViewById(R.id.packageIcon)).setImageDrawable(manager.getApplicationIcon(settings.getPackageName()));
+            } catch (PackageManager.NameNotFoundException e) {
+                GB.log("error", GB.ERROR, e);
+            }
+            final int square_side = 100;
+            ((TextView) view.findViewById(R.id.packageName)).setText(settings.getAppName());
+            Bitmap bitmap = Bitmap.createBitmap(square_side, square_side, Bitmap.Config.ARGB_8888);
+            Canvas c = new Canvas(bitmap);
+
+            Paint black = new Paint();
+            black.setColor(Color.BLACK);
+            black.setStyle(Paint.Style.STROKE);
+            black.setStrokeWidth(5);
+
+            c.drawCircle(square_side / 2, square_side / 2, square_side / 2 - 3, black);
+
+            int center = square_side / 2;
+            if (settings.getHour() != -1) {
+                c.drawLine(
+                        center,
+                        center,
+                        (float) (center + Math.sin(Math.toRadians(settings.getHour())) * (square_side / 4)),
+                        (float) (center - Math.cos(Math.toRadians(settings.getHour())) * (square_side / 4)),
+                        black
+                );
+            }
+            if (settings.getMin() != -1) {
+                c.drawLine(
+                        center,
+                        center,
+                        (float) (center + Math.sin(Math.toRadians(settings.getMin())) * (square_side / 3)),
+                        (float) (center - Math.cos(Math.toRadians(settings.getMin())) * (square_side / 3)),
+                        black
+                );
+            }
+
+            ((ImageView) view.findViewById(R.id.packageClock)).setImageBitmap(bitmap);
+
+            return view;
+        }
+    }
+
+    BroadcastReceiver fileReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            boolean error = intent.getBooleanExtra("EXTRA_ERROR", false);
+            if (error) {
+                GB.toast(getString(R.string.qhybrid_buttons_overwrite_error), Toast.LENGTH_SHORT, GB.ERROR);
+                return;
+            }
+            GB.toast(getString(R.string.qhybrid_buttons_overwrite_success), Toast.LENGTH_SHORT, GB.INFO);
+        }
+    };
+
+    BroadcastReceiver buttonReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            GB.toast("Button " + intent.getIntExtra("BUTTON", -1) + " pressed", Toast.LENGTH_SHORT, GB.INFO);
+        }
+    };
+
+    BroadcastReceiver settingsReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            GB.toast("Setting updated", Toast.LENGTH_SHORT, GB.INFO);
+            updateSettings();
+        }
+    };
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/QHybridConstants.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/QHybridConstants.java
new file mode 100644
index 0000000..7ce5fd8
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/QHybridConstants.java
@@ -0,0 +1,54 @@
+/*  Copyright (C) 2022-2024 Arjan Schrijver, Daniel Dakhno, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.qhybrid;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+
+public final class QHybridConstants {
+    public static final String HYBRIDHR_WATCHFACE_VERSION = "1.13";
+    public static final int HYBRID_HR_WATCHFACE_WIDGET_SIZE = 76;
+
+    public static Map<String, String> KNOWN_WAPP_VERSIONS = new HashMap<String, String>() {
+        {
+            put("buddyChallengeApp", "2.10");
+            put("commuteApp", "2.5");
+            put("launcherApp", "3.9");
+            put("musicApp", "3.13");
+            put("notificationsPanelApp", "3.7");
+            put("ringPhoneApp", "3.8");
+            put("settingApp", "3.13");
+            put("stopwatchApp", "3.8");
+            put("timerApp", "3.9");
+            put("weatherApp", "3.11");
+            put("wellnessApp", "3.16");
+            put("AlexaApp", "3.11");
+            put("navigationApp", "1.1");
+        }
+    };
+
+    public static Map<Integer, ActivityKind> WORKOUT_TYPES_TO_ACTIVITY_KIND = new HashMap<Integer, ActivityKind>() {
+        {
+            put(1, ActivityKind.RUNNING);
+            put(2, ActivityKind.CYCLING);
+            put(8, ActivityKind.WALKING);
+            put(12, ActivityKind.HIKING);
+        }
+    };
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/QHybridCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/QHybridCoordinator.java
new file mode 100644
index 0000000..35bc728
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/QHybridCoordinator.java
@@ -0,0 +1,353 @@
+/*  Copyright (C) 2019-2024 Andreas Shimokawa, Arjan Schrijver, Carsten
+    Pfeiffer, Damien Gaignon, Daniel Dakhno, Hasan Ammar, Jos茅 Rebelo, Morten
+    Rieger Hannemose, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.qhybrid;
+
+import android.app.Activity;
+import android.bluetooth.le.ScanFilter;
+import android.content.Context;
+import android.net.Uri;
+import android.os.ParcelUuid;
+
+import androidx.annotation.NonNull;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.GBException;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.appmanager.AppManagerActivity;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettings;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsScreen;
+import nodomain.freeyourgadget.gadgetbridge.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.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.model.Spo2Sample;
+import nodomain.freeyourgadget.gadgetbridge.model.BatteryConfig;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.QHybridSupport;
+import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.Version;
+
+public class QHybridCoordinator extends AbstractBLEDeviceCoordinator {
+    private static final Logger LOG = LoggerFactory.getLogger(QHybridCoordinator.class);
+
+    @NonNull
+    @Override
+    public boolean supports(GBDeviceCandidate candidate) {
+        for(ParcelUuid uuid : candidate.getServiceUuids()){
+            if(uuid.getUuid().toString().equals("3dda0001-957f-7d4a-34a6-74696673696d")){
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @NonNull
+    @Override
+    public Collection<? extends ScanFilter> createBLEScanFilters() {
+        return Collections.singletonList(new ScanFilter.Builder().setServiceUuid(ParcelUuid.fromString("3dda0001-957f-7d4a-34a6-74696673696d")).build());
+    }
+
+    @Override
+    public boolean supportsActivityDataFetching() {
+        List<GBDevice> devices = GBApplication.app().getDeviceManager().getSelectedDevices();
+        for(GBDevice device : devices){
+            if(isFossilHybrid(device) && device.getState() == GBDevice.State.INITIALIZED){
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public boolean supportsActivityTracking() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsUnicodeEmojis() {
+        return true;
+    }
+
+    @Override
+    public SampleProvider<? extends ActivitySample> getSampleProvider(GBDevice device, DaoSession session) {
+        return new HybridHRActivitySampleProvider(device, session);
+    }
+
+    @Override
+    public TimeSampleProvider<? extends Spo2Sample> getSpo2SampleProvider(GBDevice device, DaoSession session) {
+        return new HybridHRSpo2SampleProvider(device, session);
+    }
+
+    @Override
+    public InstallHandler findInstallHandler(Uri uri, Context context) {
+        if (isHybridHR()) {
+            FossilHRInstallHandler installHandler = new FossilHRInstallHandler(uri, context);
+            if (!installHandler.isValid()) {
+                LOG.warn("Not a Fossil Hybrid firmware or app!");
+                return null;
+            } else {
+                return installHandler;
+            }
+        }
+        FossilInstallHandler installHandler = new FossilInstallHandler(uri, context);
+        return installHandler.isValid() ? installHandler : null;
+    }
+
+    private boolean supportsAlarmConfiguration() {
+        List<GBDevice> devices = GBApplication.app().getDeviceManager().getSelectedDevices();
+        LOG.debug("devices count: " + devices.size());
+        for(GBDevice device : devices){
+            if(isFossilHybrid(device) && device.getState() == GBDevice.State.INITIALIZED){
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public int getAlarmSlotCount(GBDevice device) {
+        return this.supportsAlarmConfiguration() ? 5 : 0;
+    }
+
+    @Override
+    public int getCannedRepliesSlotCount(final GBDevice device) {
+        if (isHybridHR(device)) {
+            return 16;
+        }
+
+        return 0;
+    }
+
+    @Override
+    public boolean supportsAlarmTitle(GBDevice device) {
+        return isHybridHR(device);
+    }
+
+    @Override
+    public boolean supportsAlarmDescription(GBDevice device) {
+        return isHybridHR(device);
+    }
+
+    @Override
+    public boolean supportsHeartRateMeasurement(GBDevice device) {
+        return isHybridHR(device);
+    }
+
+    @Override
+    public String getManufacturer() {
+        return "Fossil";
+    }
+
+    @Override
+    public boolean supportsAppsManagement(final GBDevice device) {
+        return true;
+    }
+
+    @Override
+    public boolean supportsAppListFetching() {
+        return true;
+    }
+
+    @Override
+    public Class<? extends Activity> getAppsManagementActivity() {
+        return isHybridHR() ? AppManagerActivity.class : QHybridConfigActivity.class;
+    }
+
+    @Override
+    public Class<? extends Activity> getWatchfaceDesignerActivity() {
+        return isHybridHR() ? HybridHRWatchfaceDesignerActivity.class : null;
+    }
+
+    /**
+     * Returns the directory containing the watch app cache.
+     * @throws IOException when the external files directory cannot be accessed
+     */
+    public File getAppCacheDir() throws IOException {
+        return new File(FileUtils.getExternalFilesDir(), "qhybrid-app-cache");
+    }
+
+    /**
+     * Returns a String containing the device app sort order filename.
+     */
+    @Override
+    public String getAppCacheSortFilename() {
+        return "wappcacheorder.txt";
+    }
+
+    /**
+     * Returns a String containing the file extension for watch apps.
+     */
+    @Override
+    public String getAppFileExtension() {
+        return ".wapp";
+    }
+
+    @Override
+    public boolean supportsWeather() {
+        return isHybridHR();
+    }
+
+    @Override
+    public boolean supportsFindDevice() {
+        return true;
+    }
+
+    @Override
+    protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
+
+    }
+
+    @Override
+    public BatteryConfig[] getBatteryConfig(final GBDevice device) {
+        return new BatteryConfig[]{
+                new BatteryConfig(
+                        0,
+                        GBDevice.BATTERY_ICON_DEFAULT,
+                        GBDevice.BATTERY_LABEL_DEFAULT,
+                        isHybridHR(device) ? 10 : 2,
+                        100
+                )
+        };
+    }
+
+    @Override
+    public DeviceSpecificSettings getDeviceSpecificSettings(final GBDevice device) {
+        final DeviceSpecificSettings deviceSpecificSettings = new DeviceSpecificSettings();
+        if (!isHybridHR(device)) {
+            deviceSpecificSettings.addRootScreen(R.xml.devicesettings_fossilqhybrid_legacy);
+            return deviceSpecificSettings;
+        }
+        final List<Integer> generic = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.GENERIC);
+        // Firmware version specific settings
+        if (getFirmwareVersion() != null && getFirmwareVersion().smallerThan(new Version("3.0"))) {
+            generic.add(R.xml.devicesettings_fossilhybridhr_pre_fw300);
+        } else {
+            generic.add(R.xml.devicesettings_fossilhybridhr_post_fw300);
+        }
+        if (getFirmwareVersion() != null && getFirmwareVersion().smallerThan(new Version("2.20"))) {
+            generic.add(R.xml.devicesettings_fossilhybridhr_pre_fw220);
+        }
+        // Settings applicable to all firmware versions
+        generic.add(R.xml.devicesettings_fossilhybridhr_calibration);
+        generic.add(R.xml.devicesettings_fossilhybridhr_navigation);
+        final List<Integer> health = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.HEALTH);
+        health.add(R.xml.devicesettings_fossilhybridhr_workout_detection);
+        health.add(R.xml.devicesettings_inactivity);
+        final List<Integer> notifications = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.NOTIFICATIONS);
+        notifications.add(R.xml.devicesettings_fossilhybridhr_vibration);
+        notifications.add(R.xml.devicesettings_autoremove_notifications);
+        notifications.add(R.xml.devicesettings_canned_dismisscall_16);
+        notifications.add(R.xml.devicesettings_reject_call_method);
+        notifications.add(R.xml.devicesettings_transliteration);
+        notifications.add(R.xml.devicesettings_custom_deviceicon);
+        final List<Integer> developer = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DEVELOPER);
+        developer.add(R.xml.devicesettings_fossilhybridhr_dev);
+        return deviceSpecificSettings;
+    }
+
+    @Override
+    public DeviceSpecificSettingsCustomizer getDeviceSpecificSettingsCustomizer(final GBDevice device) {
+        return new QHybridSettingsCustomizer();
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return QHybridSupport.class;
+    }
+
+    @Override
+    public int[] getSupportedDeviceSpecificAuthenticationSettings() {
+        return new int[]{
+                R.xml.devicesettings_pairingkey
+        };
+    }
+
+    @Deprecated // we should use the isHybridHR(GBDevice) instead of iterating every single device
+    private boolean isHybridHR() {
+        List<GBDevice> devices = GBApplication.app().getDeviceManager().getSelectedDevices();
+        for(GBDevice device : devices){
+            if(isHybridHR(device)){
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private boolean isHybridHR(GBDevice device){
+        if(!isFossilHybrid(device)) return false;
+        return device.getName().startsWith("Hybrid HR") || device.getName().equals("Fossil Gen. 6 Hybrid");
+    }
+
+    private Version getFirmwareVersion() {
+        List<GBDevice> devices = GBApplication.app().getDeviceManager().getSelectedDevices();
+        for (GBDevice device : devices) {
+            if (isFossilHybrid(device)) {
+                return new Version(device.getFirmwareVersion2());
+            }
+        }
+
+        return null;
+    }
+
+    private boolean isFossilHybrid(GBDevice device){
+        return device.getType() == DeviceType.FOSSILQHYBRID;
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_qhybrid;
+    }
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_zetime;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_zetime_disabled;
+    }
+
+    @Override
+    public boolean supportsNavigation() {
+        return isHybridHR();
+    }
+
+    @Override
+    public boolean supportsSpo2(GBDevice device) {
+        return device.getName().equals("Fossil Gen. 6 Hybrid");
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/QHybridSettingsCustomizer.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/QHybridSettingsCustomizer.java
new file mode 100644
index 0000000..1ebc04d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/QHybridSettingsCustomizer.java
@@ -0,0 +1,60 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.qhybrid;
+
+import android.content.Intent;
+import android.os.Parcel;
+
+import androidx.annotation.NonNull;
+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.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+public class QHybridSettingsCustomizer implements DeviceSpecificSettingsCustomizer {
+    @Override
+    public void onPreferenceChange(final Preference preference, final DeviceSpecificSettingsHandler handler) {
+    }
+
+    @Override
+    public void customizeSettings(final DeviceSpecificSettingsHandler handler, final Prefs prefs, final String rootKey) {
+        final Preference pref = handler.findPreference("pref_key_qhybrid_legacy");
+        if (pref != null) {
+            pref.setOnPreferenceClickListener(preference -> {
+                final Intent intent = new Intent(handler.getContext(), QHybridConfigActivity.class);
+                intent.putExtra(GBDevice.EXTRA_DEVICE, handler.getDevice());
+                handler.getContext().startActivity(intent);
+                return true;
+            });
+        }
+    }
+
+    @Override
+    public Set<String> getPreferenceKeysWithSummary() {
+        return Collections.emptySet();
+    }
+
+    public static final Creator<QHybridSettingsCustomizer> CREATOR = new Creator<QHybridSettingsCustomizer>() {
+        @Override
+        public QHybridSettingsCustomizer createFromParcel(final Parcel in) {
+            return new QHybridSettingsCustomizer();
+        }
+
+        @Override
+        public QHybridSettingsCustomizer[] newArray(final int size) {
+            return new QHybridSettingsCustomizer[size];
+        }
+    };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull final Parcel dest, final int flags) {
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/TimePicker.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/TimePicker.java
new file mode 100644
index 0000000..be026be
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/TimePicker.java
@@ -0,0 +1,297 @@
+/*  Copyright (C) 2019-2024 Arjan Schrijver, Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.qhybrid;
+
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.pm.PackageInfo;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.RadioButton;
+import android.widget.RadioGroup;
+import android.widget.ScrollView;
+
+import androidx.annotation.NonNull;
+import androidx.appcompat.app.AlertDialog;
+
+import com.google.android.material.dialog.MaterialAlertDialogBuilder;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.PlayNotificationRequest;
+
+public class TimePicker extends MaterialAlertDialogBuilder {
+    ImageView pickerView;
+    Canvas pickerCanvas;
+    Bitmap pickerBitmap;
+
+    NotificationConfiguration settings;
+
+    int height, width, radius;
+    int radius1, radius2, radius3;
+    int controlledHand = 0;
+    int handRadius;
+
+    AlertDialog dialog;
+
+    OnFinishListener finishListener;
+    OnHandsSetListener handsListener;
+    OnVibrationSetListener vibrationListener;
+
+    protected TimePicker(@NonNull Context context, PackageInfo info) {
+        super(context);
+
+        settings = new NotificationConfiguration(info.packageName, context.getApplicationContext().getPackageManager().getApplicationLabel(info.applicationInfo).toString());
+        initGraphics(context);
+    }
+
+    protected TimePicker(Context context, NotificationConfiguration config){
+        super(context);
+
+        settings = config;
+        initGraphics(context);
+    }
+
+    private void initGraphics(Context context){
+        int w = (int) (((WindowManager) context.getApplicationContext().getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay().getWidth() * 0.8);
+        height = w;
+        width = w;
+        radius = (int) (w * 0.06);
+        radius1 = 0;
+        radius2 = (int) (radius * 2.3);
+        radius3 = (int)(radius2 * 2.15);
+        int offset = (int) (w * 0.1);
+        radius1 += offset;
+        radius2 += offset;
+        radius3 += offset;
+
+        pickerView = new ImageView(context);
+        pickerBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+
+        pickerCanvas = new Canvas(pickerBitmap);
+
+        drawClock();
+
+        LinearLayout layout = new LinearLayout(context);
+        layout.setOrientation(LinearLayout.VERTICAL);
+        layout.addView(pickerView);
+
+        CheckBox box = new CheckBox(context);
+        box.setText("Respect silent mode");
+        box.setChecked(settings.getRespectSilentMode());
+        box.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+            @Override
+            public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
+                settings.setRespectSilentMode(b);
+            }
+        });
+        layout.addView(box);
+
+        RadioGroup group = new RadioGroup(context);
+        for(PlayNotificationRequest.VibrationType vibe: PlayNotificationRequest.VibrationType.values()){
+            RadioButton button = new RadioButton(context);
+            button.setText(vibe.toString());
+            button.setId(vibe.getValue());
+            group.addView(button);
+        }
+
+        group.check(settings.getVibration().getValue());
+        group.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
+            @Override
+            public void onCheckedChanged(RadioGroup radioGroup, int i) {
+                settings.setVibration(PlayNotificationRequest.VibrationType.fromValue((byte)i));
+                if(TimePicker.this.vibrationListener != null) TimePicker.this.vibrationListener.onVibrationSet(settings);
+            }
+        });
+
+        ScrollView scrollView = new ScrollView(context);
+        scrollView.addView(group);
+        layout.addView(scrollView);
+
+        setView(layout);
+
+
+
+        setNegativeButton("cancel", null);
+        setPositiveButton("ok", new DialogInterface.OnClickListener() {
+            @Override
+            public void onClick(DialogInterface dialogInterface, int i) {
+                if(finishListener == null) return;
+                finishListener.onFinish(true, settings);
+            }
+        });
+        setOnCancelListener(new DialogInterface.OnCancelListener() {
+            @Override
+            public void onCancel(DialogInterface dialogInterface) {
+                if(finishListener == null) return;
+                finishListener.onFinish(false, settings);
+            }
+        });
+        setOnDismissListener(new DialogInterface.OnDismissListener() {
+            @Override
+            public void onDismiss(DialogInterface dialogInterface) {
+                if(finishListener == null) return;
+                finishListener.onFinish(false, settings);
+            }
+        });
+        dialog = show();
+
+        pickerView.setOnTouchListener(new View.OnTouchListener() {
+            @Override
+            public boolean onTouch(View view, MotionEvent motionEvent) {
+                handleTouch(dialog, motionEvent);
+                return true;
+            }
+        });
+    }
+
+    public NotificationConfiguration getSettings() {
+        return settings;
+    }
+
+    private void handleTouch(AlertDialog dialog, MotionEvent event) {
+        int centerX = width / 2;
+        int centerY = height / 2;
+        int difX = centerX - (int) event.getX();
+        int difY = (int) event.getY() - centerY;
+        int dist = (int) Math.sqrt(Math.abs(difX) * Math.abs(difX) + Math.abs(difY) * Math.abs(difY));
+        switch (event.getAction()) {
+            case MotionEvent.ACTION_DOWN: {
+                int radiusHalf = radius;
+                if (dist < (radius1 + radiusHalf) && dist > (radius1 - radiusHalf)) {
+                    Log.d("Settings", "hit sub");
+                    handRadius = (int) (height / 2f - radius1);
+                    controlledHand = 3;
+                } else if (dist < (radius2 + radiusHalf) && dist > (radius2 - radiusHalf)) {
+                    Log.d("Settings", "hit hour");
+                    controlledHand = 1;
+                    handRadius = (int) (height / 2f - radius2);
+                } else if (dist < (radius3 + radiusHalf) && dist > (radius3 - radiusHalf)) {
+                    Log.d("Settings", "hit minute");
+                    controlledHand = 2;
+                    handRadius = (int) (height / 2f - radius3);
+                } else {
+                    Log.d("Settings", "hit nothing");
+                }
+                break;
+            }
+            case MotionEvent.ACTION_MOVE: {
+                if (controlledHand == 0) return;
+                double degree = difY == 0 ? (difX < 0 ? 90 : 270) : Math.toDegrees(Math.atan((float) difX / (float) difY));
+                if (difY > 0) degree = 180 + degree;
+                if (degree < 0) degree = 360 + degree;
+                switch (controlledHand) {
+                    case 1: {
+                        settings.setHour((short) (((int)(degree + 15) / 30) * 30 % 360));
+                        break;
+                    }
+                    case 2: {
+                        settings.setMin((short) (((int)(degree + 15) / 30) * 30 % 360));
+                        break;
+                    }
+                }
+                break;
+            }
+            case MotionEvent.ACTION_UP: {
+                dialog.getButton(AlertDialog.BUTTON_POSITIVE).setClickable(true);
+                dialog.getButton(AlertDialog.BUTTON_POSITIVE).setAlpha(1f);
+                if(handsListener != null) handsListener.onHandsSet(settings);
+                break;
+            }
+        }
+        drawClock();
+    }
+
+
+    private void drawClock() {
+        //pickerCanvas.drawColor(Color.WHITE);
+        Paint white = new Paint();
+        white.setColor(Color.WHITE);
+        white.setStyle(Paint.Style.FILL);
+        pickerCanvas.drawCircle(width / 2, width / 2, width / 2, white);
+
+        Paint paint = new Paint();
+        paint.setStyle(Paint.Style.FILL_AND_STROKE);
+        paint.setColor(Color.BLUE);
+        Paint text = new Paint();
+        text.setStyle(Paint.Style.FILL);
+        text.setTextSize(radius * 1.5f);
+        text.setColor(Color.BLACK);
+        text.setTextAlign(Paint.Align.CENTER);
+        int textShiftY = (int) ((text.descent() + text.ascent()) / 2);
+
+        Paint linePaint = new Paint();
+        linePaint.setStrokeWidth(10);
+        linePaint.setStyle(Paint.Style.FILL_AND_STROKE);
+        linePaint.setColor(Color.BLACK);
+
+        if (settings.getMin() != -1) {
+            paint.setAlpha(255);
+            float x = (float) (width / 2f + Math.sin(Math.toRadians(settings.getMin())) * (float) radius3);
+            float y = (float) (height / 2f - Math.cos(Math.toRadians(settings.getMin())) * (float) radius3);
+            linePaint.setAlpha(255);
+            pickerCanvas.drawLine(width / 2, height / 2, x, y, linePaint);
+            pickerCanvas.drawCircle(
+                    x,
+                    y,
+                    radius,
+                    paint
+            );
+            pickerCanvas.drawText(String.valueOf(settings.getMin() / 6), x, y - textShiftY, text);
+        }
+        if (settings.getHour() != -1) {
+            paint.setAlpha(255);
+            float x = (float) (width / 2f + Math.sin(Math.toRadians(settings.getHour())) * (float) radius2);
+            float y = (float) (height / 2f - Math.cos(Math.toRadians(settings.getHour())) * (float) radius2);
+            linePaint.setAlpha(255);
+            pickerCanvas.drawLine(width / 2, height / 2, x, y, linePaint);
+            pickerCanvas.drawCircle(
+                    x,
+                    y,
+                    radius,
+                    paint
+            );
+            pickerCanvas.drawText(settings.getHour() == 0 ? "12" : String.valueOf(settings.getHour() / 30), x, y - textShiftY, text);
+        }
+
+        Paint paint2 = new Paint();
+        paint2.setColor(Color.BLACK);
+        paint2.setStyle(Paint.Style.FILL_AND_STROKE);
+        pickerCanvas.drawCircle(width / 2, height / 2, 5, paint2);
+        pickerView.setImageBitmap(pickerBitmap);
+    }
+
+    interface OnFinishListener{
+        public void onFinish(boolean success, NotificationConfiguration config);
+    }
+
+    interface OnHandsSetListener{
+        public void onHandsSet(NotificationConfiguration config);
+    }
+
+    interface OnVibrationSetListener{
+        public void onVibrationSet(NotificationConfiguration config);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/WidgetSettingsActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/WidgetSettingsActivity.java
new file mode 100644
index 0000000..a284949
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/WidgetSettingsActivity.java
@@ -0,0 +1,220 @@
+/*  Copyright (C) 2020-2024 Andreas Shimokawa, Arjan Schrijver, Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.qhybrid;
+
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.EditText;
+import android.widget.ListView;
+import android.widget.RadioButton;
+import android.widget.RadioGroup;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AlertDialog;
+
+import com.google.android.material.dialog.MaterialAlertDialogBuilder;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBActivity;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.widget.CustomBackgroundWidgetElement;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.widget.CustomTextWidgetElement;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.widget.CustomWidget;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.widget.CustomWidgetElement;
+
+public class WidgetSettingsActivity extends AbstractGBActivity {
+    private CustomWidget subject;
+    private WidgetElementAdapter widgetElementAdapter;
+
+    public static final int RESULT_CODE_WIDGET_CREATED = 0;
+    public static final int RESULT_CODE_WIDGET_UPDATED = 1;
+    public static final int RESULT_CODE_WIDGET_DELETED = 2;
+    public static final int RESULT_CODE_CANCELED = 3;
+
+    private int resultCode;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.qhybrid_activity_widget_settings);
+
+        setResult(RESULT_CODE_CANCELED);
+
+        if(getIntent().hasExtra("EXTRA_WIDGET")){
+            subject = (CustomWidget) getIntent().getExtras().get("EXTRA_WIDGET");
+            ((EditText) findViewById(R.id.qhybrid_widget_name)).setText(subject.getName());
+            resultCode = RESULT_CODE_WIDGET_UPDATED;
+        }else{
+            subject = new CustomWidget("", 0, 63, "default"); // FIXME: handle force white background
+            resultCode = RESULT_CODE_WIDGET_CREATED;
+            findViewById(R.id.qhybrid_widget_delete).setEnabled(false);
+        }
+
+        findViewById(R.id.qhybrid_widget_save).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                subject.setName(((EditText) findViewById(R.id.qhybrid_widget_name)).getText().toString());
+
+                Intent resultIntent = getIntent();
+                resultIntent.putExtra("EXTRA_WIDGET", WidgetSettingsActivity.this.subject);
+                setResult(resultCode, resultIntent);
+
+                finish();
+            }
+        });
+
+        findViewById(R.id.qhybrid_widget_delete).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                setResult(RESULT_CODE_WIDGET_DELETED, getIntent());
+
+                finish();
+            }
+        });
+
+        widgetElementAdapter = new WidgetElementAdapter(subject.getElements());
+        ListView elementList = findViewById(R.id.qhybrid_widget_elements_list);
+        elementList.setAdapter(widgetElementAdapter);
+        elementList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+            @Override
+            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+                showElementDialog(widgetElementAdapter.getItem(position));
+            }
+        });
+
+        findViewById(R.id.qhybrid_widget_elements_add).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                showElementDialog(null);
+            }
+        });
+    }
+
+    private void showElementDialog(@Nullable final CustomWidgetElement element){
+        MaterialAlertDialogBuilder dialogBuilder = new MaterialAlertDialogBuilder(WidgetSettingsActivity.this)
+                .setView(R.layout.qhybrid_element_popup_view);
+
+        if(element == null) {
+            dialogBuilder
+                    .setTitle("create element")
+                    .setNegativeButton("cancel", null)
+                    .setPositiveButton("ok", new DialogInterface.OnClickListener() {
+                        @Override
+                        public void onClick(DialogInterface dialog, int which) {
+                            if(((RadioButton)((AlertDialog)dialog).findViewById(R.id.qhybrid_widget_elements_type_text)).isChecked()){
+                                subject.addElement(new CustomTextWidgetElement(
+                                        ((EditText)((AlertDialog)dialog).findViewById(R.id.qhybrid_widget_element_id)).getText().toString(),
+                                        ((EditText)((AlertDialog)dialog).findViewById(R.id.qhybrid_widget_element_value)).getText().toString(),
+                                        CustomWidgetElement.X_CENTER,
+                                        ((RadioButton)((AlertDialog)dialog).findViewById(R.id.qhybrid_widget_elements_position_uppper)).isChecked() ? CustomTextWidgetElement.Y_UPPER_HALF : CustomTextWidgetElement.Y_LOWER_HALF
+                                ));
+                            }else{
+                                subject.addElement(new CustomBackgroundWidgetElement(
+                                        ((EditText)((AlertDialog)dialog).findViewById(R.id.qhybrid_widget_element_id)).getText().toString(),
+                                        ((EditText)((AlertDialog)dialog).findViewById(R.id.qhybrid_widget_element_value)).getText().toString()
+                                ));
+                            }
+                            refreshElementsList();
+                        }
+                    });
+        }else{
+            dialogBuilder
+                    .setTitle("edit element")
+                    .setNegativeButton("delete", new DialogInterface.OnClickListener() {
+                        @Override
+                        public void onClick(DialogInterface dialog, int which) {
+                            WidgetSettingsActivity.this.subject.getElements().remove(element);
+
+                            refreshElementsList();
+                        }
+                    })
+                    .setPositiveButton("ok", new DialogInterface.OnClickListener() {
+                        @Override
+                        public void onClick(DialogInterface dialog, int which) {
+                            element.setId(((EditText)((AlertDialog)dialog).findViewById(R.id.qhybrid_widget_element_id)).getText().toString());
+                            element.setValue(((EditText)((AlertDialog)dialog).findViewById(R.id.qhybrid_widget_element_value)).getText().toString());
+                            element.setY(((RadioButton)((AlertDialog)dialog).findViewById(R.id.qhybrid_widget_elements_position_uppper)).isChecked() ? CustomTextWidgetElement.Y_UPPER_HALF : CustomTextWidgetElement.Y_LOWER_HALF);
+                            element.setWidgetElementType(CustomWidgetElement.WidgetElementType.fromRadioButtonRessource(((RadioGroup)((AlertDialog)dialog).findViewById(R.id.qhybrid_widget_element_type)).getCheckedRadioButtonId()));
+
+                            refreshElementsList();
+                        }
+                    });
+        }
+
+        AlertDialog dialog = dialogBuilder.show();
+
+
+        if(element != null){
+            String elementId = element.getId();
+            String elementValue = element.getValue();
+            CustomWidgetElement.WidgetElementType type = element.getWidgetElementType();
+
+            ((EditText)dialog.findViewById(R.id.qhybrid_widget_element_id)).setText(elementId);
+            ((EditText)dialog.findViewById(R.id.qhybrid_widget_element_value)).setText(elementValue);
+            ((RadioGroup)dialog.findViewById(R.id.qhybrid_widget_element_type)).check(type.getRadioButtonResource());
+            ((RadioGroup)dialog.findViewById(R.id.qhybrid_widget_element_position)).check(element.getY() == CustomWidgetElement.Y_UPPER_HALF ? R.id.qhybrid_widget_elements_position_uppper : R.id.qhybrid_widget_elements_position_lower);
+        }
+    }
+
+    private void refreshElementsList(){
+        this.widgetElementAdapter.notifyDataSetChanged();
+    }
+
+    class WidgetElementAdapter extends ArrayAdapter<CustomWidgetElement>{
+        public WidgetElementAdapter(@NonNull List<CustomWidgetElement> objects) {
+            super(WidgetSettingsActivity.this, 0, objects);
+        }
+
+        @NonNull
+        @Override
+        public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
+            RelativeLayout layout = new RelativeLayout(WidgetSettingsActivity.this);
+            layout.setLayoutParams(new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
+
+
+            TextView idView = new TextView(WidgetSettingsActivity.this);
+
+            idView.setText(getItem(position).getId());
+            // view.setTextColor(Color.WHITE);
+            idView.setTextSize(25);
+            RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+            params.addRule(RelativeLayout.ALIGN_PARENT_START, RelativeLayout.TRUE);
+            idView.setLayoutParams(params);
+
+            TextView contentView = new TextView(WidgetSettingsActivity.this);
+            contentView.setText(getItem(position).getValue());
+            contentView.setTextSize(25);
+            params = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+            params.addRule(RelativeLayout.ALIGN_PARENT_END, RelativeLayout.TRUE);
+            contentView.setLayoutParams(params);
+
+            layout.addView(idView);
+            layout.addView(contentView);
+
+            return layout;
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/realme/RealmeBudsT110Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/realme/RealmeBudsT110Coordinator.java
new file mode 100644
index 0000000..9ffea1b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/realme/RealmeBudsT110Coordinator.java
@@ -0,0 +1,73 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.realme;
+
+import android.util.Pair;
+
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.oppo.OppoHeadphonesCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.oppo.commands.TouchConfigSide;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.oppo.commands.TouchConfigType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.oppo.commands.TouchConfigValue;
+
+public class RealmeBudsT110Coordinator extends OppoHeadphonesCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("realme Buds T110", Pattern.LITERAL);
+    }
+
+    @Override
+    public String getManufacturer() {
+        return "Realme";
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_realme_buds_t110;
+    }
+
+    @Override
+    protected Map<Pair<TouchConfigSide, TouchConfigType>, List<TouchConfigValue>> getTouchOptions() {
+        return new LinkedHashMap<Pair<TouchConfigSide, TouchConfigType>, List<TouchConfigValue>>() {{
+            final List<TouchConfigValue> options = Arrays.asList(
+                    TouchConfigValue.OFF,
+                    TouchConfigValue.PLAY_PAUSE,
+                    TouchConfigValue.PREVIOUS,
+                    TouchConfigValue.NEXT,
+                    TouchConfigValue.VOLUME_UP,
+                    TouchConfigValue.VOLUME_DOWN,
+                    TouchConfigValue.VOICE_ASSISTANT_REALME
+            );
+            put(Pair.create(TouchConfigSide.LEFT, TouchConfigType.TAP_2), options);
+            put(Pair.create(TouchConfigSide.LEFT, TouchConfigType.TAP_3), options);
+            put(Pair.create(TouchConfigSide.LEFT, TouchConfigType.HOLD), options);
+            put(Pair.create(TouchConfigSide.RIGHT, TouchConfigType.TAP_2), options);
+            put(Pair.create(TouchConfigSide.RIGHT, TouchConfigType.TAP_3), options);
+            put(Pair.create(TouchConfigSide.RIGHT, TouchConfigType.HOLD), options);
+            put(Pair.create(TouchConfigSide.BOTH, TouchConfigType.HOLD), Arrays.asList(
+                    TouchConfigValue.OFF,
+                    TouchConfigValue.GAME_MODE
+            ));
+        }};
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/roidmi/Roidmi1Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/roidmi/Roidmi1Coordinator.java
new file mode 100644
index 0000000..6ab296d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/roidmi/Roidmi1Coordinator.java
@@ -0,0 +1,65 @@
+/*  Copyright (C) 2018-2024 Daniel Dakhno, Daniele Gobbetti, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.roidmi;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import androidx.annotation.NonNull;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.roidmi.RoidmiSupport;
+
+public class Roidmi1Coordinator extends RoidmiCoordinator {
+    private static final Logger LOG = LoggerFactory.getLogger(Roidmi1Coordinator.class);
+
+    @NonNull
+    @Override
+    public boolean supports(final GBDeviceCandidate candidate) {
+        try {
+            final String name = candidate.getName();
+
+            if (name != null && name.contains("鐫跨背杞﹁浇钃濈墮鎾斁鍣�")) {
+                return true;
+            }
+        } catch (final Exception ex) {
+            LOG.error("unable to check device support", ex);
+        }
+
+        return false;
+    }
+
+    @Override
+    public int getBatteryCount() {
+        // Roidmi 1 does not have voltage support
+        return 0;
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return RoidmiSupport.class;
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_roidmi;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/roidmi/Roidmi3Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/roidmi/Roidmi3Coordinator.java
new file mode 100644
index 0000000..28e7406
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/roidmi/Roidmi3Coordinator.java
@@ -0,0 +1,68 @@
+/*  Copyright (C) 2018-2024 Daniel Dakhno, Daniele Gobbetti, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.roidmi;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import androidx.annotation.NonNull;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.roidmi.RoidmiSupport;
+
+public class Roidmi3Coordinator extends RoidmiCoordinator {
+    private static final Logger LOG = LoggerFactory.getLogger(Roidmi3Coordinator.class);
+
+    @NonNull
+    @Override
+    public boolean supports(final GBDeviceCandidate candidate) {
+        try {
+            final String name = candidate.getName();
+
+            if (name == null) {
+                return false;
+            }
+
+            if (name.contains("Roidmi Music Blue C") || name.contains("Roidmi C BLE") || name.contains("Mojietu Music Blue C")) {
+                return true;
+            }
+        } catch (Exception ex) {
+            LOG.error("unable to check device support", ex);
+        }
+
+        return false;
+    }
+
+    @Override
+    public boolean supportsRgbLedColor() {
+        return true;
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return RoidmiSupport.class;
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_roidmi3;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/roidmi/RoidmiConst.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/roidmi/RoidmiConst.java
new file mode 100644
index 0000000..cc55564
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/roidmi/RoidmiConst.java
@@ -0,0 +1,36 @@
+/*  Copyright (C) 2018-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.roidmi;
+
+import android.graphics.Color;
+
+public class RoidmiConst {
+    public static final String ACTION_GET_LED_COLOR = "roidmi_get_led_color";
+    public static final String ACTION_GET_FM_FREQUENCY = "roidmi_get_frequency";
+    public static final String ACTION_GET_VOLTAGE = "roidmi_get_voltage";
+
+    public static final int[] COLOR_PRESETS = new int[]{
+            Color.rgb(0xFF, 0x00, 0x00), // red
+            Color.rgb(0x00, 0xFF, 0x00), // green
+            Color.rgb(0x00, 0x00, 0xFF), // blue
+            Color.rgb(0xFF, 0xFF, 0x01), // yellow
+            Color.rgb(0x00, 0xAA, 0xE5), // sky blue
+            Color.rgb(0xF0, 0x6E, 0xAA), // pink
+            Color.rgb(0xFF, 0xFF, 0xFF), // white
+            Color.rgb(0x00, 0x00, 0x00), // black
+    };
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/roidmi/RoidmiCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/roidmi/RoidmiCoordinator.java
new file mode 100644
index 0000000..8d19544
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/roidmi/RoidmiCoordinator.java
@@ -0,0 +1,147 @@
+/*  Copyright (C) 2018-2024 Andreas Shimokawa, Damien Gaignon, Daniel Dakhno,
+    Daniele Gobbetti, Jos茅 Rebelo, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.roidmi;
+
+import android.app.Activity;
+import android.content.Context;
+import android.net.Uri;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import nodomain.freeyourgadget.gadgetbridge.GBException;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLClassicDeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
+
+public abstract class RoidmiCoordinator extends AbstractBLClassicDeviceCoordinator {
+    private static final Logger LOG = LoggerFactory.getLogger(RoidmiCoordinator.class);
+
+    @Override
+    public String getManufacturer() {
+        return "Roidmi";
+    }
+
+    @Override
+    public int getBondingStyle() {
+        return BONDING_STYLE_BOND;
+    }
+
+    @Override
+    protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
+    }
+
+    @Nullable
+    @Override
+    public Class<? extends Activity> getPairingActivity() {
+        return null;
+    }
+
+    @Override
+    public boolean supportsActivityDataFetching() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsActivityTracking() {
+        return false;
+    }
+
+    @Override
+    public SampleProvider<? extends ActivitySample> getSampleProvider(GBDevice device, DaoSession session) {
+        return null;
+    }
+
+    @Override
+    public InstallHandler findInstallHandler(Uri uri, Context context) {
+        return null;
+    }
+
+    @Override
+    public boolean supportsScreenshots(final GBDevice device) {
+        return false;
+    }
+
+    @Override
+    public int getAlarmSlotCount(GBDevice device) {
+        return 0;
+    }
+
+    @Override
+    public boolean supportsHeartRateMeasurement(GBDevice device) {
+        return false;
+    }
+
+    @Override
+    public boolean supportsAppsManagement(final GBDevice device) {
+        return false;
+    }
+
+    @Override
+    public Class<? extends Activity> getAppsManagementActivity() {
+        return null;
+    }
+
+    @Override
+    public boolean supportsCalendarEvents() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsRealtimeData() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsWeather() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsFindDevice() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsLedColor() {
+        return true;
+    }
+
+    @NonNull
+    @Override
+    public int[] getColorPresets() {
+        return RoidmiConst.COLOR_PRESETS;
+    }
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_roidmi;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_roidmi_disabled;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/scannable/ScannableDeviceCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/scannable/ScannableDeviceCoordinator.java
new file mode 100644
index 0000000..a3ba837
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/scannable/ScannableDeviceCoordinator.java
@@ -0,0 +1,68 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.scannable;
+
+import androidx.annotation.NonNull;
+
+import nodomain.freeyourgadget.gadgetbridge.GBException;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLEDeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.unknown.UnknownDeviceSupport;
+
+public class ScannableDeviceCoordinator extends AbstractBLEDeviceCoordinator {
+    @Override
+    public boolean supports(GBDeviceCandidate candidate) {
+        return false;
+    }
+
+    @Override
+    protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
+
+    }
+
+    @Override
+    public int[] getSupportedDeviceSpecificSettings(GBDevice device) {
+        return new int[]{
+                R.xml.devicesettings_scannable
+        };
+    }
+
+    @Override
+    public boolean isConnectable() {
+        return false;
+    }
+
+    @Override
+    public String getManufacturer() {
+        return "Generic";
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return UnknownDeviceSupport.class;
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_scannable;
+    }
+
+    @Override
+    public int getBatteryCount() {
+        return 0;
+    }
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_scannable;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_scannable_disabled;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/smaq2oss/SMAQ2OSSConstants.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/smaq2oss/SMAQ2OSSConstants.java
new file mode 100644
index 0000000..ad02081
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/smaq2oss/SMAQ2OSSConstants.java
@@ -0,0 +1,60 @@
+/*  Copyright (C) 2021-2024 x29a
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.smaq2oss;
+
+import java.util.UUID;
+
+import static nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport.BASE_UUID;
+
+
+public class SMAQ2OSSConstants {
+
+    // Nordic UART Service UUID
+//    public static final UUID UUID_SERVICE_SMAQ2OSS = UUID.fromString("6E400001-B5A3-F393-E0A9-E50E24DCCA9E");
+//    public static final UUID UUID_CHARACTERISTIC_WRITE_NORMAL = UUID.fromString("6E400002-B5A3-F393-E0A9-E50E24DCCA9E");
+//    public static final UUID UUID_CHARACTERISTIC_NOTIFY_NORMAL = UUID.fromString("6E400003-B5A3-F393-E0A9-E50E24DCCA9E");
+    // SMA-Q2-OSS watch service UUID 51be0000-c182-4f3a-9359-21337bce51f6
+    public static final UUID UUID_SERVICE_SMAQ2OSS = UUID.fromString("51be0001-c182-4f3a-9359-21337bce51f6");
+    public static final UUID UUID_CHARACTERISTIC_WRITE_NORMAL = UUID.fromString("51be0002-c182-4f3a-9359-21337bce51f6");
+    public static final UUID UUID_CHARACTERISTIC_NOTIFY_NORMAL = UUID.fromString("51be0003-c182-4f3a-9359-21337bce51f6");
+
+    public static final byte MSG_SET_TIME = 0x01;
+    public static final byte MSG_BATTERY_STATE = 0x02;
+    public static final byte MSG_MUSIC_EVENT = 0x03;
+    public static final byte MSG_SET_WEATHER = 0x04;
+    public static final byte MSG_SET_MUSIC_INFO = 0x05;
+    public static final byte MSG_CALL_NOTIFICATION = 0x06;
+    public static final byte MSG_CALL_COMMAND = 0x07;
+    public static final byte MSG_NOTIFICATION = 0x08;
+
+    public static final byte EVT_PLAY_PAUSE = 0x00;
+    public static final byte EVT_FWD = 0x01;
+    public static final byte EVT_REV = 0x02;
+    public static final byte EVT_VOL_UP = 0x03;
+    public static final byte EVT_VOL_DOWN = 0x04;
+
+    public static final int MUSIC_ARTIST_MAX_LEN = 32;
+    public static final int MUSIC_ALBUM_MAX_LEN = 32;
+    public static final int MUSIC_TRACK_MAX_LEN = 64;
+
+    public static final int CALL_NAME_MAX_LEN = 32;
+    public static final int CALL_NUMBER_MAX_LEN = 16;
+
+    public static final int NOTIFICATION_SENDER_MAX_LEN = 32;
+    public static final int NOTIFICATION_SUBJECT_MAX_LEN = 32;
+    public static final int NOTIFICATION_BODY_MAX_LEN = 200;
+}
\ No newline at end of file
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/smaq2oss/SMAQ2OSSCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/smaq2oss/SMAQ2OSSCoordinator.java
new file mode 100644
index 0000000..dec9a0c
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/smaq2oss/SMAQ2OSSCoordinator.java
@@ -0,0 +1,165 @@
+/*  Copyright (C) 2021-2024 Andreas Shimokawa, Arjan Schrijver, Damien Gaignon,
+    Daniel Dakhno, Jos茅 Rebelo, Petr Van臎k, x29a
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.smaq2oss;
+
+import android.app.Activity;
+import android.bluetooth.le.ScanFilter;
+import android.content.Context;
+import android.net.Uri;
+import android.os.ParcelUuid;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.GBException;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLEDeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+
+public class SMAQ2OSSCoordinator extends AbstractBLEDeviceCoordinator {
+    private static final Logger LOG = LoggerFactory.getLogger(SMAQ2OSSCoordinator.class);
+
+    @NonNull
+    @Override
+    public Collection<? extends ScanFilter> createBLEScanFilters() {
+        ParcelUuid service = new ParcelUuid(SMAQ2OSSConstants.UUID_SERVICE_SMAQ2OSS);
+        ScanFilter filter = new ScanFilter.Builder().setServiceUuid(service).build();
+        return Collections.singletonList(filter);
+    }
+
+    @Override
+    protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("SMAQ2-.*|SMA-Q2-OSS", Pattern.CASE_INSENSITIVE);
+    }
+
+    @Override
+    public int getBondingStyle(){
+        return BONDING_STYLE_NONE;
+    }
+
+    @Nullable
+    @Override
+    public Class<? extends Activity> getPairingActivity() {
+        return null;
+    }
+
+    @Override
+    public boolean supportsActivityDataFetching() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsActivityTracking() {
+        return false;
+    }
+
+    @Override
+    public SampleProvider<? extends ActivitySample> getSampleProvider(GBDevice device, DaoSession session) {
+        return null;
+    }
+
+    @Override
+    public InstallHandler findInstallHandler(Uri uri, Context context) {
+        return null;
+    }
+
+    @Override
+    public boolean supportsScreenshots(final GBDevice device) {
+        return false;
+    }
+
+    @Override
+    public int getAlarmSlotCount(GBDevice device) {
+        return 0;
+    }
+
+    @Override
+    public boolean supportsHeartRateMeasurement(GBDevice device) {
+        return false;
+    }
+
+    @Override
+    public String getManufacturer() {
+        return "SMA";
+    }
+
+    @Override
+    public boolean supportsAppsManagement(final GBDevice device) {
+        return false;
+    }
+
+    @Override
+    public Class<? extends Activity> getAppsManagementActivity() {
+        return null;
+    }
+
+    @Override
+    public boolean supportsCalendarEvents() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsRealtimeData() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsWeather() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsFindDevice() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsMusicInfo() {
+        return true;
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return SMAQ2OSSSupport.class;
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_smaq2oss;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/smaq2oss/SMAQ2OSSSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/smaq2oss/SMAQ2OSSSupport.java
new file mode 100644
index 0000000..ea51e51
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/smaq2oss/SMAQ2OSSSupport.java
@@ -0,0 +1,345 @@
+/*  Copyright (C) 2021-2024 Arjan Schrijver, Jos茅 Rebelo, x29a
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.smaq2oss;
+
+
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.UUID;
+import java.nio.ByteBuffer;
+
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventCallControl;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventMusicControl;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
+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.GattService;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
+import nodomain.freeyourgadget.gadgetbridge.proto.SMAQ2OSSProtos;
+import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+public class SMAQ2OSSSupport extends AbstractBTLEDeviceSupport {
+    private static final Logger LOG = LoggerFactory.getLogger(SMAQ2OSSSupport.class);
+
+    public BluetoothGattCharacteristic normalWriteCharacteristic = null;
+
+    public SMAQ2OSSSupport() {
+        super(LOG);
+        addSupportedService(GattService.UUID_SERVICE_GENERIC_ACCESS);
+        addSupportedService(GattService.UUID_SERVICE_GENERIC_ATTRIBUTE);
+        addSupportedService(SMAQ2OSSConstants.UUID_SERVICE_SMAQ2OSS);
+    }
+
+    @Override
+    protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
+        normalWriteCharacteristic = getCharacteristic(SMAQ2OSSConstants.UUID_CHARACTERISTIC_WRITE_NORMAL);
+        normalWriteCharacteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT);
+
+        builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext()));
+
+        setTime(builder)
+                .setInitialized(builder);
+
+        getDevice().setFirmwareVersion("N/A");
+        getDevice().setFirmwareVersion2("N/A");
+
+        builder.notify(getCharacteristic(SMAQ2OSSConstants.UUID_CHARACTERISTIC_NOTIFY_NORMAL), true);
+
+        return builder;
+    }
+
+    @Override
+    public boolean onCharacteristicChanged(BluetoothGatt gatt,
+                                           BluetoothGattCharacteristic characteristic) {
+        super.onCharacteristicChanged(gatt, characteristic);
+
+        UUID characteristicUUID = characteristic.getUuid();
+        if (SMAQ2OSSConstants.UUID_CHARACTERISTIC_NOTIFY_NORMAL.equals(characteristicUUID)) {
+            handleDeviceEvent(characteristic.getValue());
+        }
+        return true;
+    }
+
+    private void handleDeviceEvent(byte[] value){
+        if (value == null || value.length == 0) {
+            return;
+        }
+
+        switch (value[0]) {
+            case SMAQ2OSSConstants.MSG_MUSIC_EVENT:
+                LOG.info("got music control");
+                handleMusicEvent(value[1]);
+                break;
+            case SMAQ2OSSConstants.MSG_CALL_COMMAND:
+                LOG.info("got call control");
+                handleCallCommand(value[1]);
+                break;
+
+        }
+
+    }
+
+    private void handleMusicEvent(byte value){
+        GBDeviceEventMusicControl deviceEventMusicControl = new GBDeviceEventMusicControl();
+
+        switch (value){
+            case SMAQ2OSSConstants.EVT_PLAY_PAUSE:
+                deviceEventMusicControl.event = GBDeviceEventMusicControl.Event.PLAYPAUSE;
+                break;
+            case SMAQ2OSSConstants.EVT_FWD:
+                deviceEventMusicControl.event = GBDeviceEventMusicControl.Event.NEXT;
+                break;
+            case SMAQ2OSSConstants.EVT_REV:
+                deviceEventMusicControl.event = GBDeviceEventMusicControl.Event.PREVIOUS;
+                break;
+            case SMAQ2OSSConstants.EVT_VOL_UP:
+                deviceEventMusicControl.event = GBDeviceEventMusicControl.Event.VOLUMEUP;
+                break;
+            case SMAQ2OSSConstants.EVT_VOL_DOWN:
+                deviceEventMusicControl.event = GBDeviceEventMusicControl.Event.VOLUMEDOWN;
+                break;
+        }
+        evaluateGBDeviceEvent(deviceEventMusicControl);
+
+    }
+
+    private void handleCallCommand(byte command){
+        GBDeviceEventCallControl callCmd = new GBDeviceEventCallControl();
+
+        switch (command){
+            case CallSpec.CALL_ACCEPT:
+                callCmd.event = GBDeviceEventCallControl.Event.ACCEPT;
+                evaluateGBDeviceEvent(callCmd);
+                break;
+            case CallSpec.CALL_REJECT:
+                callCmd.event = GBDeviceEventCallControl.Event.REJECT;
+                evaluateGBDeviceEvent(callCmd);
+                break;
+        }
+
+    }
+
+    @Override
+    public boolean useAutoConnect() {
+        return true;
+    }
+
+    @Override
+    public void onNotification(NotificationSpec notificationSpec) {
+
+        SMAQ2OSSProtos.MessageNotification.Builder notification = SMAQ2OSSProtos.MessageNotification.newBuilder();
+
+
+        notification.setTimestamp(getTimestamp());
+        String sender = StringUtils.getFirstOf(StringUtils.getFirstOf(notificationSpec.sender,notificationSpec.phoneNumber),notificationSpec.title);
+        notification.setSender(truncateUTF8(sender,SMAQ2OSSConstants.NOTIFICATION_SENDER_MAX_LEN));
+//        notification.setSubject(truncateUTF8(notificationSpec.subject,SMAQ2OSSConstants.NOTIFICATION_SUBJECT_MAX_LEN));
+        notification.setBody(truncateUTF8(notificationSpec.body,SMAQ2OSSConstants.NOTIFICATION_BODY_MAX_LEN));
+
+
+        try {
+            TransactionBuilder builder;
+            builder = performInitialized("Sending notification");
+
+            builder.write(normalWriteCharacteristic,createMessage(SMAQ2OSSConstants.MSG_NOTIFICATION,notification.build().toByteArray()));
+
+            builder.queue(getQueue());
+        } catch (Exception ex) {
+            LOG.error("Error sending notification", ex);
+        }
+    }
+
+    @Override
+    public void onSetTime() {
+        try {
+            TransactionBuilder builder = performInitialized("time");
+            setTime(builder);
+            performConnected(builder.getTransaction());
+        } catch(IOException e) {
+        }
+    }
+
+    @Override
+    public void onSetCallState(CallSpec callSpec) {
+
+        SMAQ2OSSProtos.CallNotification.Builder callnotif = SMAQ2OSSProtos.CallNotification.newBuilder();
+
+        callnotif.setName(truncateUTF8(callSpec.name,SMAQ2OSSConstants.CALL_NAME_MAX_LEN));
+        callnotif.setNumber(truncateUTF8(callSpec.number,SMAQ2OSSConstants.CALL_NUMBER_MAX_LEN));
+        callnotif.setCommand(callSpec.command);
+
+        try {
+            TransactionBuilder builder;
+            builder = performInitialized("Sending call state");
+
+            builder.write(normalWriteCharacteristic,createMessage(SMAQ2OSSConstants.MSG_CALL_NOTIFICATION,callnotif.build().toByteArray()));
+
+            builder.queue(getQueue());
+        } catch (Exception ex) {
+            LOG.error("Error sending call state", ex);
+        }
+    }
+
+    @Override
+    public void onSetCannedMessages(CannedMessagesSpec cannedMessagesSpec) {
+
+    }
+
+    @Override
+    public void onSetMusicInfo(MusicSpec musicSpec) {
+        SMAQ2OSSProtos.MusicInfo.Builder musicInfo = SMAQ2OSSProtos.MusicInfo.newBuilder();
+
+        musicInfo.setArtist(truncateUTF8(musicSpec.artist,SMAQ2OSSConstants.MUSIC_ARTIST_MAX_LEN));
+        musicInfo.setAlbum(truncateUTF8(musicSpec.album,SMAQ2OSSConstants.MUSIC_ALBUM_MAX_LEN));
+        musicInfo.setTrack(truncateUTF8(musicSpec.track,SMAQ2OSSConstants.MUSIC_TRACK_MAX_LEN));
+
+        try {
+            TransactionBuilder builder;
+            builder = performInitialized("Sending music info");
+            LOG.info(musicInfo.getArtist());
+            LOG.info(musicInfo.getAlbum());
+            LOG.info(musicInfo.getTrack());
+
+            builder.write(normalWriteCharacteristic,createMessage(SMAQ2OSSConstants.MSG_SET_MUSIC_INFO,musicInfo.build().toByteArray()));
+
+            builder.queue(getQueue());
+        } catch (Exception ex) {
+            LOG.error("Error sending music info", ex);
+        }
+    }
+
+    @Override
+    public void onReset(int flags) {
+//        try {
+//            getQueue().clear();
+//
+//            TransactionBuilder builder = performInitialized("reboot");
+//            builder.write(normalWriteCharacteristic, new byte[] {
+//                    SMAQ2OSSConstants.CMD_ID_DEVICE_RESTART, SMAQ2OSSConstants.CMD_KEY_REBOOT
+//            });
+//            performConnected(builder.getTransaction());
+//        } catch(Exception e) {
+//        }
+    }
+
+    @Override
+    public void onSendWeather(ArrayList<WeatherSpec> weatherSpecs) {
+        WeatherSpec weatherSpec = weatherSpecs.get(0);
+        try {
+            TransactionBuilder builder;
+            builder = performInitialized("Sending current weather");
+
+            SMAQ2OSSProtos.SetWeather.Builder setWeather= SMAQ2OSSProtos.SetWeather.newBuilder();
+
+            setWeather.setTimestamp(weatherSpec.timestamp);
+            setWeather.setCondition(weatherSpec.currentConditionCode);
+            setWeather.setTemperature(weatherSpec.currentTemp-273);
+            setWeather.setTemperatureMin(weatherSpec.todayMinTemp-273);
+            setWeather.setTemperatureMax(weatherSpec.todayMaxTemp-273);
+            setWeather.setHumidity(weatherSpec.currentHumidity);
+
+            for (WeatherSpec.Daily f:weatherSpec.forecasts) {
+
+                SMAQ2OSSProtos.Forecast.Builder fproto = SMAQ2OSSProtos.Forecast.newBuilder();
+
+                fproto.setCondition(f.conditionCode);
+                fproto.setTemperatureMin(f.minTemp-273);
+                fproto.setTemperatureMax(f.maxTemp-273);
+
+                setWeather.addForecasts(fproto);
+            }
+
+            builder.write(normalWriteCharacteristic,createMessage(SMAQ2OSSConstants.MSG_SET_WEATHER,setWeather.build().toByteArray()));
+            builder.queue(getQueue());
+        } catch (Exception ex) {
+            LOG.error("Error sending current weather", ex);
+        }
+    }
+
+    private void setInitialized(TransactionBuilder builder) {
+        builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZED, getContext()));
+    }
+
+    byte[] createMessage(byte msgid, byte[] data){
+
+        ByteBuffer buf=ByteBuffer.allocate(data.length+1);
+        buf.put(msgid);
+        buf.put(data);
+
+        return buf.array();
+    }
+
+    String truncateUTF8(String str, int len){
+
+        if (str ==null)
+            return new String();
+
+        int currLen = str.getBytes(UTF_8).length;
+
+        while (currLen>len-1){
+
+            str=str.substring(0,str.length()-1);
+            currLen = str.getBytes(UTF_8).length;
+        }
+
+        return str;
+    }
+
+    private int getTimestamp(){
+
+        Calendar c = GregorianCalendar.getInstance();
+
+        long offset = (c.get(Calendar.ZONE_OFFSET) + c.get(Calendar.DST_OFFSET));
+        long ts =  (c.getTimeInMillis()+ offset)/1000 ;
+
+        return (int)ts;
+
+    }
+
+    SMAQ2OSSSupport setTime(TransactionBuilder builder) {
+
+        SMAQ2OSSProtos.SetTime.Builder settime = SMAQ2OSSProtos.SetTime.newBuilder();
+        settime.setTimestamp(getTimestamp());
+        builder.write(normalWriteCharacteristic,createMessage(SMAQ2OSSConstants.MSG_SET_TIME,settime.build().toByteArray()));
+        return this;
+    }
+
+    @Override
+    public boolean getImplicitCallbackModify() {
+        return true;
+    }
+
+    @Override
+    public boolean getSendWriteRequestResponse() {
+        return false;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/soflow/SoFlowCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/soflow/SoFlowCoordinator.java
new file mode 100644
index 0000000..cd42ae8
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/soflow/SoFlowCoordinator.java
@@ -0,0 +1,166 @@
+/*  Copyright (C) 2022-2024 Andreas Shimokawa, Damien Gaignon, Daniel Dakhno,
+    Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.soflow;
+
+import android.app.Activity;
+import android.content.Context;
+import android.net.Uri;
+
+import androidx.annotation.NonNull;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLEDeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.soflow.SoFlowSupport;
+
+public class SoFlowCoordinator extends AbstractBLEDeviceCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("SoFlow-.*");
+    }
+
+    @Override
+    public Class<? extends Activity> getPairingActivity() {
+        return null;
+    }
+
+    @Override
+    public InstallHandler findInstallHandler(Uri uri, Context context) {
+        return null;
+    }
+
+    @Override
+    public boolean supportsActivityDataFetching() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsActivityTracking() {
+        return false;
+    }
+
+    @Override
+    public SampleProvider<? extends ActivitySample> getSampleProvider(GBDevice device, DaoSession session) {
+        return null;
+    }
+
+    @Override
+    public boolean supportsScreenshots(final GBDevice device) {
+        return false;
+    }
+
+    @Override
+    public int getAlarmSlotCount(GBDevice device) {
+        return 0;
+    }
+
+    @Override
+    public boolean supportsHeartRateMeasurement(GBDevice device) {
+        return false;
+    }
+
+    @Override
+    public String getManufacturer() {
+        return "SoFlow";
+    }
+
+    @Override
+    public boolean supportsAppsManagement(final GBDevice device) {
+        return false;
+    }
+
+    @Override
+    public Class<? extends Activity> getAppsManagementActivity() {
+        return null;
+    }
+
+    @Override
+    public boolean supportsCalendarEvents() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsRealtimeData() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsWeather() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsFindDevice() {
+        return true;
+    }
+
+    @Override
+    protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) {
+        // nothing to delete, yet
+    }
+
+    @Override
+    public int getBondingStyle() {
+        return BONDING_STYLE_NONE;
+    }
+
+    @Override
+    public int[] getSupportedDeviceSpecificAuthenticationSettings() {
+        return new int[]{R.xml.devicesettings_pairingkey};
+    }
+
+    @Override
+    public int[] getSupportedDeviceSpecificSettings(GBDevice device) {
+        return new int[]{
+                R.xml.devicesettings_lock_unlock
+        };
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return SoFlowSupport.class;
+    }
+
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_soflow_s06;
+    }
+
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_vesc;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_vesc_disabled;
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/SonyHeadphonesCapabilities.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/SonyHeadphonesCapabilities.java
new file mode 100644
index 0000000..49a9f3a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/SonyHeadphonesCapabilities.java
@@ -0,0 +1,49 @@
+/*  Copyright (C) 2022-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones;
+
+public enum SonyHeadphonesCapabilities {
+    BatterySingle,
+    BatteryDual,
+    BatteryDual2,
+    BatteryCase,
+    PowerOffFromPhone,
+    AmbientSoundControl,
+    AmbientSoundControl2,
+    WindNoiseReduction,
+    SpeakToChatEnabled,
+    SpeakToChatConfig,
+    SpeakToChatFocusOnVoice,
+    AncOptimizer,
+    AudioSettingsOnlyOnSbcCodec,
+    AudioUpsampling,
+    ButtonModesLeftRight,
+    AmbientSoundControlButtonMode,
+    VoiceNotifications,
+    AutomaticPowerOffWhenTakenOff,
+    AutomaticPowerOffByTime,
+    TouchSensorSingle,
+    EqualizerSimple,
+    EqualizerWithCustomBands,
+    SoundPosition,
+    SurroundMode,
+    QuickAccess,
+    PauseWhenTakenOff,
+    Volume,
+    WideAreaTap,
+    AdaptiveVolumeControl,
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/SonyHeadphonesCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/SonyHeadphonesCoordinator.java
new file mode 100644
index 0000000..8172f4e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/SonyHeadphonesCoordinator.java
@@ -0,0 +1,232 @@
+/*  Copyright (C) 2021-2024 Damien Gaignon, Daniel Dakhno, Jos茅 Rebelo,
+    Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones;
+
+import androidx.annotation.NonNull;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import nodomain.freeyourgadget.gadgetbridge.GBException;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettings;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsScreen;
+import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLClassicDeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.BatteryConfig;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.SonyHeadphonesSupport;
+
+public abstract class SonyHeadphonesCoordinator extends AbstractBLClassicDeviceCoordinator {
+    @Override
+    public String getManufacturer() {
+        return "Sony";
+    }
+
+    @Override
+    public DeviceSpecificSettingsCustomizer getDeviceSpecificSettingsCustomizer(final GBDevice device) {
+        return new SonyHeadphonesSettingsCustomizer(device);
+    }
+
+    @Override
+    public boolean suggestUnbindBeforePair() {
+        return false;
+    }
+
+    @Override
+    protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
+    }
+
+    @Override
+    public boolean supportsPowerOff() {
+        return supports(SonyHeadphonesCapabilities.PowerOffFromPhone);
+    }
+
+    @Override
+    public int getBatteryCount() {
+        if (supports(SonyHeadphonesCapabilities.BatterySingle)) {
+            if (supports(SonyHeadphonesCapabilities.BatteryDual) || supports(SonyHeadphonesCapabilities.BatteryDual2)) {
+                throw new IllegalStateException("A device can't have both single and dual battery");
+            } else if (supports(SonyHeadphonesCapabilities.BatteryCase)) {
+                throw new IllegalStateException("Devices with single battery + case are not supported by the protocol");
+            }
+        }
+
+        int batteryCount = 0;
+
+        if (supports(SonyHeadphonesCapabilities.BatterySingle)) {
+            batteryCount += 1;
+        }
+
+        if (supports(SonyHeadphonesCapabilities.BatteryCase)) {
+            batteryCount += 1;
+        }
+
+        if (supports(SonyHeadphonesCapabilities.BatteryDual) || supports(SonyHeadphonesCapabilities.BatteryDual2)) {
+            batteryCount += 2;
+        }
+
+        return batteryCount;
+    }
+
+    @Override
+    public BatteryConfig[] getBatteryConfig(final GBDevice device) {
+        final List<BatteryConfig> batteries = new ArrayList<>(3);
+
+        if (supports(SonyHeadphonesCapabilities.BatterySingle)) {
+            batteries.add(new BatteryConfig(batteries.size()));
+        }
+
+        if (supports(SonyHeadphonesCapabilities.BatteryCase)) {
+            batteries.add(new BatteryConfig(batteries.size(), R.drawable.ic_tws_case, R.string.battery_case));
+        }
+
+        if (supports(SonyHeadphonesCapabilities.BatteryDual) || supports(SonyHeadphonesCapabilities.BatteryDual2)) {
+            batteries.add(new BatteryConfig(batteries.size(), R.drawable.ic_galaxy_buds_l, R.string.left_earbud));
+            batteries.add(new BatteryConfig(batteries.size(), R.drawable.ic_galaxy_buds_r, R.string.right_earbud));
+        }
+
+        return batteries.toArray(new BatteryConfig[0]);
+    }
+
+    @Override
+    public DeviceSpecificSettings getDeviceSpecificSettings(final GBDevice device) {
+        final DeviceSpecificSettings deviceSpecificSettings = new DeviceSpecificSettings();
+
+        if (supports(SonyHeadphonesCapabilities.AmbientSoundControl) || supports(SonyHeadphonesCapabilities.AmbientSoundControl2)) {
+            if (supports(SonyHeadphonesCapabilities.WindNoiseReduction)) {
+                deviceSpecificSettings.addRootScreen(R.xml.devicesettings_sony_headphones_ambient_sound_control_wind_noise_reduction);
+            } else {
+                deviceSpecificSettings.addRootScreen(R.xml.devicesettings_sony_headphones_ambient_sound_control);
+            }
+
+            if (supports(SonyHeadphonesCapabilities.AncOptimizer)) {
+                deviceSpecificSettings.addRootScreen(R.xml.devicesettings_sony_headphones_anc_optimizer);
+            }
+        }
+
+        if (supports(SonyHeadphonesCapabilities.AdaptiveVolumeControl)) {
+            deviceSpecificSettings.addRootScreen(R.xml.devicesettings_sony_headphones_adaptive_volume_control);
+        }
+
+        if (supports(SonyHeadphonesCapabilities.SpeakToChatConfig)) {
+            deviceSpecificSettings.addRootScreen(R.xml.devicesettings_sony_headphones_speak_to_chat_with_settings);
+        } else if (supports(SonyHeadphonesCapabilities.SpeakToChatEnabled)) {
+            deviceSpecificSettings.addRootScreen(R.xml.devicesettings_sony_headphones_speak_to_chat_simple);
+        }
+
+        addSettingsUnderHeader(deviceSpecificSettings, R.xml.devicesettings_header_other, new LinkedHashMap<SonyHeadphonesCapabilities, Integer>() {{
+            put(SonyHeadphonesCapabilities.AudioSettingsOnlyOnSbcCodec, R.xml.devicesettings_sony_warning_wh1000xm3);
+            put(SonyHeadphonesCapabilities.EqualizerSimple, R.xml.devicesettings_sony_headphones_equalizer);
+            put(SonyHeadphonesCapabilities.EqualizerWithCustomBands, R.xml.devicesettings_sony_headphones_equalizer_with_custom_bands);
+            put(SonyHeadphonesCapabilities.SoundPosition, R.xml.devicesettings_sony_headphones_sound_position);
+            put(SonyHeadphonesCapabilities.SurroundMode, R.xml.devicesettings_sony_headphones_surround_mode);
+            put(SonyHeadphonesCapabilities.AudioUpsampling, R.xml.devicesettings_sony_headphones_audio_upsampling);
+            put(SonyHeadphonesCapabilities.Volume, R.xml.devicesettings_volume);
+        }});
+
+        final List<Integer> callsAndNotif = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.CALLS_AND_NOTIFICATIONS);
+        callsAndNotif.add(R.xml.devicesettings_headphones);
+
+        addSettingsUnderHeader(deviceSpecificSettings, R.xml.devicesettings_header_system, new LinkedHashMap<SonyHeadphonesCapabilities, Integer>() {{
+            put(SonyHeadphonesCapabilities.WideAreaTap, R.xml.devicesettings_sony_headphones_wide_area_tap);
+            put(SonyHeadphonesCapabilities.ButtonModesLeftRight, R.xml.devicesettings_sony_headphones_button_modes_left_right);
+            put(SonyHeadphonesCapabilities.AmbientSoundControlButtonMode, R.xml.devicesettings_sony_headphones_ambient_sound_control_button_modes);
+            put(SonyHeadphonesCapabilities.QuickAccess, R.xml.devicesettings_sony_headphones_quick_access);
+            put(SonyHeadphonesCapabilities.TouchSensorSingle, R.xml.devicesettings_sony_headphones_touch_sensor_single);
+            put(SonyHeadphonesCapabilities.PauseWhenTakenOff, R.xml.devicesettings_sony_headphones_pause_when_taken_off);
+            put(SonyHeadphonesCapabilities.AutomaticPowerOffWhenTakenOff, R.xml.devicesettings_automatic_power_off_when_taken_off);
+            put(SonyHeadphonesCapabilities.AutomaticPowerOffByTime, R.xml.devicesettings_automatic_power_off_by_time);
+            put(SonyHeadphonesCapabilities.VoiceNotifications, R.xml.devicesettings_sony_headphones_notifications_voice_guide);
+        }});
+
+        deviceSpecificSettings.addRootScreen(R.xml.devicesettings_header_developer);
+        deviceSpecificSettings.addRootScreen(R.xml.devicesettings_sony_headphones_protocol_version);
+
+        deviceSpecificSettings.addRootScreen(R.xml.devicesettings_sony_headphones_device_info);
+
+        return deviceSpecificSettings;
+    }
+
+    public List<SonyHeadphonesCapabilities> getCapabilities() {
+        return Collections.emptyList();
+    }
+
+    public boolean supports(final SonyHeadphonesCapabilities capability) {
+        return getCapabilities().contains(capability);
+    }
+
+    /**
+     * Add the preference screens for capabilities under a header. The header is also only added if at least one capability is supported by the device.
+     *
+     * @param deviceSpecificSettings the device specific settings
+     * @param header                 the header to add, if any capability supported
+     * @param capabilities           the map of capability to preference screen
+     */
+    private void addSettingsUnderHeader(final DeviceSpecificSettings deviceSpecificSettings,
+                                        final int header,
+                                        final Map<SonyHeadphonesCapabilities, Integer> capabilities) {
+        final Set<SonyHeadphonesCapabilities> supportedCapabilities = new HashSet<>(capabilities.keySet());
+        for (SonyHeadphonesCapabilities capability : capabilities.keySet()) {
+            if (!supports(capability)) {
+                supportedCapabilities.remove(capability);
+            }
+        }
+
+        if (supportedCapabilities.isEmpty()) {
+            // None of the capabilities in the map are supported
+            return;
+        }
+
+        deviceSpecificSettings.addRootScreen(header);
+
+        for (Map.Entry<SonyHeadphonesCapabilities, Integer> capabilitiesSetting : capabilities.entrySet()) {
+            if (supports(capabilitiesSetting.getKey())) {
+                deviceSpecificSettings.addRootScreen(capabilitiesSetting.getValue());
+            }
+        }
+    }
+
+    public boolean preferServiceV2() {
+        return false;
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return SonyHeadphonesSupport.class;
+    }
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_sony_overhead;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_sony_overhead_disabled;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/SonyHeadphonesSettingsCustomizer.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/SonyHeadphonesSettingsCustomizer.java
new file mode 100644
index 0000000..20d072c
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/SonyHeadphonesSettingsCustomizer.java
@@ -0,0 +1,226 @@
+/*  Copyright (C) 2021-2024 Arjan Schrijver, Jos茅 Rebelo, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones;
+
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONY_AMBIENT_SOUND_CONTROL;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONY_AMBIENT_SOUND_LEVEL;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONY_AUDIO_CODEC;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_BAND_1000;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_BAND_16000;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_BAND_2500;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_BAND_400;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_BAND_6300;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_BASS;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_MODE;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONY_FOCUS_VOICE;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONY_NOISE_OPTIMIZER_CANCEL;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONY_NOISE_OPTIMIZER_START;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONY_NOISE_OPTIMIZER_STATUS;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONY_SOUND_POSITION;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONY_SPEAK_TO_CHAT_FOCUS_ON_VOICE;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONY_SURROUND_MODE;
+
+import android.app.ProgressDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.os.Parcel;
+
+import androidx.preference.EditTextPreference;
+import androidx.preference.ListPreference;
+import androidx.preference.Preference;
+
+import com.google.android.material.dialog.MaterialAlertDialogBuilder;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.Set;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.AmbientSoundControl;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.impl.v1.params.NoiseCancellingOptimizerStatus;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+public class SonyHeadphonesSettingsCustomizer implements DeviceSpecificSettingsCustomizer {
+    private ProgressDialog ancOptimizerProgressDialog;
+
+    final GBDevice device;
+
+    public SonyHeadphonesSettingsCustomizer(final GBDevice device) {
+        this.device = device;
+    }
+
+    @Override
+    public void onPreferenceChange(final Preference preference, final DeviceSpecificSettingsHandler handler) {
+        // Disable equalizer, sound position and surround mode if not in SBC codec, for WH-1000XM3
+        // TODO: Should the coordinator be responsible for this compatibility check?
+        if (preference.getKey().equals(PREF_SONY_AUDIO_CODEC) && device.getType().equals(DeviceType.SONY_WH_1000XM3)) {
+            final boolean isSbcCodec = ((EditTextPreference) preference).getText().equalsIgnoreCase("sbc");
+
+            final List<Preference> prefsToDisable = Arrays.asList(
+                    handler.findPreference(PREF_SONY_EQUALIZER),
+                    handler.findPreference(PREF_SONY_EQUALIZER_MODE),
+                    handler.findPreference(PREF_SONY_EQUALIZER_BAND_400),
+                    handler.findPreference(PREF_SONY_EQUALIZER_BAND_1000),
+                    handler.findPreference(PREF_SONY_EQUALIZER_BAND_2500),
+                    handler.findPreference(PREF_SONY_EQUALIZER_BAND_6300),
+                    handler.findPreference(PREF_SONY_EQUALIZER_BAND_16000),
+                    handler.findPreference(PREF_SONY_EQUALIZER_BASS),
+                    handler.findPreference(PREF_SONY_SOUND_POSITION),
+                    handler.findPreference(PREF_SONY_SURROUND_MODE)
+            );
+
+            for (Preference pref : prefsToDisable) {
+                if (pref != null) {
+                    pref.setEnabled(isSbcCodec);
+                }
+            }
+        }
+
+        // Handle ANC Optimizer status
+        if (preference.getKey().equals(PREF_SONY_NOISE_OPTIMIZER_STATUS)) {
+            final EditTextPreference optimizerStatusPreference = (EditTextPreference) preference;
+            final NoiseCancellingOptimizerStatus optimizerStatus = NoiseCancellingOptimizerStatus.valueOf(optimizerStatusPreference.getText().toUpperCase(Locale.ROOT));
+
+            if (ancOptimizerProgressDialog != null) {
+                switch (optimizerStatus) {
+                    case FINISHED:
+                    case NOT_RUNNING:
+                        ancOptimizerProgressDialog.dismiss();
+                        ancOptimizerProgressDialog = null;
+                        break;
+                    default:
+                        ancOptimizerProgressDialog.setMessage(optimizerStatus.i18n(preference.getContext()));
+                }
+            }
+        }
+    }
+
+    @Override
+    public void customizeSettings(final DeviceSpecificSettingsHandler handler, Prefs prefs, final String rootKey) {
+        final SonyHeadphonesCoordinator coordinator = (SonyHeadphonesCoordinator) device.getDeviceCoordinator();
+
+        // Only enable the focus on voice check and voice level slider if the ambient sound control mode is ambient sound
+
+        final ListPreference ambientSoundControl = handler.findPreference(PREF_SONY_AMBIENT_SOUND_CONTROL);
+        if (ambientSoundControl != null) {
+            final Preference focusOnVoice = handler.findPreference(PREF_SONY_FOCUS_VOICE);
+            final Preference ambientSoundLevel = handler.findPreference(PREF_SONY_AMBIENT_SOUND_LEVEL);
+
+            final Preference.OnPreferenceChangeListener ambientSoundControlPrefListener = new Preference.OnPreferenceChangeListener() {
+                public boolean onPreferenceChange(Preference preference, Object newVal) {
+                    boolean isAmbientSoundEnabled = AmbientSoundControl.Mode.AMBIENT_SOUND.name().toLowerCase(Locale.getDefault()).equals(newVal);
+                    focusOnVoice.setEnabled(isAmbientSoundEnabled);
+                    ambientSoundLevel.setEnabled(isAmbientSoundEnabled);
+
+                    return true;
+                }
+            };
+
+            ambientSoundControlPrefListener.onPreferenceChange(ambientSoundControl, ambientSoundControl.getValue());
+            handler.addPreferenceHandlerFor(PREF_SONY_AMBIENT_SOUND_CONTROL, ambientSoundControlPrefListener);
+        }
+
+        // ANC Optimizer
+
+        final Preference ancOptimizer = handler.findPreference("pref_sony_anc_optimizer");
+
+        if (ancOptimizer != null) {
+            ancOptimizer.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
+                @Override
+                public boolean onPreferenceClick(final Preference preference) {
+                    if (ancOptimizerProgressDialog != null) {
+                        // Already optimizing
+                        return true;
+                    }
+
+                    final Context context = preference.getContext();
+
+                    new MaterialAlertDialogBuilder(context)
+                            .setTitle(R.string.sony_anc_optimize_confirmation_title)
+                            .setMessage(R.string.sony_anc_optimize_confirmation_description)
+                            .setIcon(R.drawable.ic_hearing)
+                            .setPositiveButton(R.string.start, new DialogInterface.OnClickListener() {
+                                public void onClick(final DialogInterface dialog, final int whichButton) {
+                                    handler.notifyPreferenceChanged(PREF_SONY_NOISE_OPTIMIZER_START);
+
+                                    ancOptimizerProgressDialog = new ProgressDialog(context);
+                                    ancOptimizerProgressDialog.setCancelable(false);
+                                    ancOptimizerProgressDialog.setMessage(context.getString(R.string.sony_anc_optimizer_status_starting));
+                                    ancOptimizerProgressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
+                                    ancOptimizerProgressDialog.setProgress(0);
+                                    ancOptimizerProgressDialog.setButton(DialogInterface.BUTTON_NEGATIVE, context.getString(R.string.Cancel), new DialogInterface.OnClickListener() {
+                                        @Override
+                                        public void onClick(final DialogInterface dialog, final int which) {
+                                            dialog.dismiss();
+                                            ancOptimizerProgressDialog = null;
+                                            handler.notifyPreferenceChanged(PREF_SONY_NOISE_OPTIMIZER_CANCEL);
+                                        }
+                                    });
+
+                                    ancOptimizerProgressDialog.show();
+                                }
+                            })
+                            .setNegativeButton(android.R.string.cancel, null)
+                            .show();
+
+                    return true;
+                }
+            });
+        }
+
+        // Hide unsupported preferences
+        final Preference speakToChatFocusVoice = handler.findPreference(PREF_SONY_SPEAK_TO_CHAT_FOCUS_ON_VOICE);
+        if (speakToChatFocusVoice != null && !coordinator.supports(SonyHeadphonesCapabilities.SpeakToChatFocusOnVoice)) {
+            speakToChatFocusVoice.setVisible(false);
+        }
+    }
+
+    @Override
+    public Set<String> getPreferenceKeysWithSummary() {
+        return Collections.emptySet();
+    }
+
+    public static final Creator<SonyHeadphonesSettingsCustomizer> CREATOR = new Creator<SonyHeadphonesSettingsCustomizer>() {
+        @Override
+        public SonyHeadphonesSettingsCustomizer createFromParcel(final Parcel in) {
+            final GBDevice device = in.readParcelable(SonyHeadphonesSettingsCustomizer.class.getClassLoader());
+            return new SonyHeadphonesSettingsCustomizer(device);
+        }
+
+        @Override
+        public SonyHeadphonesSettingsCustomizer[] newArray(final int size) {
+            return new SonyHeadphonesSettingsCustomizer[size];
+        }
+    };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(final Parcel dest, final int flags) {
+        dest.writeParcelable(device, 0);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/coordinators/SonyLinkBudsCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/coordinators/SonyLinkBudsCoordinator.java
new file mode 100644
index 0000000..43116c0
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/coordinators/SonyLinkBudsCoordinator.java
@@ -0,0 +1,73 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.coordinators;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.SonyHeadphonesCapabilities;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.SonyHeadphonesCoordinator;
+
+public class SonyLinkBudsCoordinator extends SonyHeadphonesCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        // TODO fully exclude LE_LinkBuds?
+        return Pattern.compile("^LinkBuds$");
+    }
+
+    @Override
+    public List<SonyHeadphonesCapabilities> getCapabilities() {
+        return Arrays.asList(
+                SonyHeadphonesCapabilities.BatteryDual,
+                SonyHeadphonesCapabilities.BatteryCase,
+                SonyHeadphonesCapabilities.SpeakToChatEnabled,
+                SonyHeadphonesCapabilities.SpeakToChatConfig,
+                SonyHeadphonesCapabilities.AdaptiveVolumeControl,
+                SonyHeadphonesCapabilities.EqualizerWithCustomBands,
+                SonyHeadphonesCapabilities.AudioUpsampling,
+                SonyHeadphonesCapabilities.ButtonModesLeftRight,
+                SonyHeadphonesCapabilities.PauseWhenTakenOff,
+                SonyHeadphonesCapabilities.AutomaticPowerOffWhenTakenOff,
+                SonyHeadphonesCapabilities.WideAreaTap,
+                SonyHeadphonesCapabilities.VoiceNotifications
+                // TODO spacial sound optimization
+                // TODO factory reset
+        );
+    }
+
+    @Override
+    public boolean preferServiceV2() {
+        return true;
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_sony_linkbuds;
+    }
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_galaxy_buds;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_galaxy_buds_disabled;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/coordinators/SonyLinkBudsSCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/coordinators/SonyLinkBudsSCoordinator.java
new file mode 100644
index 0000000..e2dc00f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/coordinators/SonyLinkBudsSCoordinator.java
@@ -0,0 +1,72 @@
+/*  Copyright (C) 2022-2024 Daniel Dakhno, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.coordinators;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.SonyHeadphonesCapabilities;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.SonyHeadphonesCoordinator;
+
+public class SonyLinkBudsSCoordinator extends SonyHeadphonesCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile(".*LinkBuds S.*");
+    }
+
+    @Override
+    public List<SonyHeadphonesCapabilities> getCapabilities() {
+        return Arrays.asList(
+                SonyHeadphonesCapabilities.BatteryDual,
+                SonyHeadphonesCapabilities.BatteryCase,
+                SonyHeadphonesCapabilities.AmbientSoundControl,
+                SonyHeadphonesCapabilities.AudioUpsampling,
+                SonyHeadphonesCapabilities.ButtonModesLeftRight,
+                SonyHeadphonesCapabilities.AmbientSoundControlButtonMode,
+                SonyHeadphonesCapabilities.QuickAccess,
+                SonyHeadphonesCapabilities.PauseWhenTakenOff,
+                SonyHeadphonesCapabilities.AutomaticPowerOffWhenTakenOff,
+                SonyHeadphonesCapabilities.PowerOffFromPhone,
+                SonyHeadphonesCapabilities.SpeakToChatEnabled,
+                SonyHeadphonesCapabilities.SpeakToChatConfig,
+                SonyHeadphonesCapabilities.VoiceNotifications,
+                SonyHeadphonesCapabilities.EqualizerWithCustomBands
+        );
+    }
+
+    @Override
+    public boolean preferServiceV2() {
+        return true;
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_sony_linkbuds_s;
+    }
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_galaxy_buds;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_galaxy_buds_disabled;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/coordinators/SonyWF1000XM3Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/coordinators/SonyWF1000XM3Coordinator.java
new file mode 100644
index 0000000..dbec95e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/coordinators/SonyWF1000XM3Coordinator.java
@@ -0,0 +1,81 @@
+/*  Copyright (C) 2022-2024 Daniel Dakhno, Jos茅 Rebelo, Quang Ng么
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.coordinators;
+
+import androidx.annotation.NonNull;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.SonyHeadphonesCapabilities;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.SonyHeadphonesCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
+import nodomain.freeyourgadget.gadgetbridge.model.BatteryConfig;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+
+public class SonyWF1000XM3Coordinator extends SonyHeadphonesCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile(".*WF-1000XM3.*");
+    }
+
+    @Override
+    public BatteryConfig[] getBatteryConfig(final GBDevice device) {
+        final BatteryConfig battery1 = new BatteryConfig(0, R.drawable.ic_tws_case, R.string.battery_case);
+        final BatteryConfig battery2 = new BatteryConfig(1, R.drawable.ic_galaxy_buds_l, R.string.left_earbud);
+        final BatteryConfig battery3 = new BatteryConfig(2, R.drawable.ic_galaxy_buds_r, R.string.right_earbud);
+
+        return new BatteryConfig[]{battery1, battery2, battery3};
+    }
+
+    @Override
+    public List<SonyHeadphonesCapabilities> getCapabilities() {
+        return Arrays.asList(
+                SonyHeadphonesCapabilities.BatteryDual,
+                SonyHeadphonesCapabilities.BatteryCase,
+                SonyHeadphonesCapabilities.PowerOffFromPhone,
+                SonyHeadphonesCapabilities.AmbientSoundControl,
+                SonyHeadphonesCapabilities.WindNoiseReduction,
+                SonyHeadphonesCapabilities.EqualizerWithCustomBands,
+                SonyHeadphonesCapabilities.AudioUpsampling,
+                SonyHeadphonesCapabilities.ButtonModesLeftRight,
+                SonyHeadphonesCapabilities.PauseWhenTakenOff,
+                SonyHeadphonesCapabilities.AutomaticPowerOffWhenTakenOff,
+                SonyHeadphonesCapabilities.VoiceNotifications
+        );
+    }
+
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_sony_wf_1000xm3;
+    }
+
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_galaxy_buds;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_galaxy_buds_disabled;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/coordinators/SonyWF1000XM4Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/coordinators/SonyWF1000XM4Coordinator.java
new file mode 100644
index 0000000..3eaddac
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/coordinators/SonyWF1000XM4Coordinator.java
@@ -0,0 +1,74 @@
+/*  Copyright (C) 2022-2024 Daniel Dakhno, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.coordinators;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.SonyHeadphonesCapabilities;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.SonyHeadphonesCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.BatteryConfig;
+
+public class SonyWF1000XM4Coordinator extends SonyHeadphonesCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile(".*WF-1000XM4.*");
+    }
+
+    @Override
+    public BatteryConfig[] getBatteryConfig(final GBDevice device) {
+        final BatteryConfig battery1 = new BatteryConfig(0, R.drawable.ic_tws_case, R.string.battery_case);
+        final BatteryConfig battery2 = new BatteryConfig(1, R.drawable.ic_galaxy_buds_l, R.string.left_earbud);
+        final BatteryConfig battery3 = new BatteryConfig(2, R.drawable.ic_galaxy_buds_r, R.string.right_earbud);
+
+        return new BatteryConfig[]{battery1, battery2, battery3};
+    }
+
+    @Override
+    public List<SonyHeadphonesCapabilities> getCapabilities() {
+        return Arrays.asList(
+                SonyHeadphonesCapabilities.BatteryDual,
+                SonyHeadphonesCapabilities.BatteryCase,
+                SonyHeadphonesCapabilities.AmbientSoundControl,
+                SonyHeadphonesCapabilities.WindNoiseReduction,
+                SonyHeadphonesCapabilities.EqualizerSimple,
+                SonyHeadphonesCapabilities.AudioUpsampling,
+                SonyHeadphonesCapabilities.ButtonModesLeftRight,
+                SonyHeadphonesCapabilities.PauseWhenTakenOff,
+                SonyHeadphonesCapabilities.AutomaticPowerOffWhenTakenOff
+        );
+    }
+
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_sony_wf_1000xm4;
+    }
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_galaxy_buds;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_galaxy_buds_disabled;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/coordinators/SonyWF1000XM5Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/coordinators/SonyWF1000XM5Coordinator.java
new file mode 100644
index 0000000..284e8d0
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/coordinators/SonyWF1000XM5Coordinator.java
@@ -0,0 +1,85 @@
+/*  Copyright (C) 2023-2024 Daniel Dakhno, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.coordinators;
+
+import androidx.annotation.NonNull;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.SonyHeadphonesCapabilities;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.SonyHeadphonesCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
+import nodomain.freeyourgadget.gadgetbridge.model.BatteryConfig;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+
+public class SonyWF1000XM5Coordinator extends SonyHeadphonesCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile(".*WF-1000XM5.*");
+    }
+
+    @Override
+    public BatteryConfig[] getBatteryConfig(final GBDevice device) {
+        final BatteryConfig battery1 = new BatteryConfig(0, R.drawable.ic_tws_case, R.string.battery_case);
+        final BatteryConfig battery2 = new BatteryConfig(1, R.drawable.ic_galaxy_buds_l, R.string.left_earbud);
+        final BatteryConfig battery3 = new BatteryConfig(2, R.drawable.ic_galaxy_buds_r, R.string.right_earbud);
+
+        return new BatteryConfig[]{battery1, battery2, battery3};
+    }
+
+    @Override
+    public List<SonyHeadphonesCapabilities> getCapabilities() {
+        return Arrays.asList(
+                SonyHeadphonesCapabilities.BatteryDual,
+                SonyHeadphonesCapabilities.BatteryCase,
+                SonyHeadphonesCapabilities.AmbientSoundControl,
+                SonyHeadphonesCapabilities.WindNoiseReduction,
+                SonyHeadphonesCapabilities.EqualizerSimple,
+                SonyHeadphonesCapabilities.AudioUpsampling,
+                SonyHeadphonesCapabilities.ButtonModesLeftRight,
+                SonyHeadphonesCapabilities.PauseWhenTakenOff,
+                SonyHeadphonesCapabilities.AutomaticPowerOffWhenTakenOff
+        );
+    }
+
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_sony_wf_1000xm5;
+    }
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_galaxy_buds;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_galaxy_buds_disabled;
+    }
+
+    @Override
+    public boolean isExperimental() {
+        // Ambient Sound Control is not 100% working
+        // Volume control from headphones is not working?
+        return true;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/coordinators/SonyWFC500Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/coordinators/SonyWFC500Coordinator.java
new file mode 100644
index 0000000..0ae78f4
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/coordinators/SonyWFC500Coordinator.java
@@ -0,0 +1,59 @@
+/*  Copyright (C) 2024 Zahnstocher
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.coordinators;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.SonyHeadphonesCapabilities;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.SonyHeadphonesCoordinator;
+
+public class SonyWFC500Coordinator extends SonyHeadphonesCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile(".*WF-C500.*");
+    }
+
+    @Override
+    public List<SonyHeadphonesCapabilities> getCapabilities() {
+        return Arrays.asList(
+                SonyHeadphonesCapabilities.BatteryDual2,
+                SonyHeadphonesCapabilities.EqualizerSimple,
+                SonyHeadphonesCapabilities.EqualizerWithCustomBands,
+                SonyHeadphonesCapabilities.AudioUpsampling,
+                SonyHeadphonesCapabilities.VoiceNotifications,
+                SonyHeadphonesCapabilities.PowerOffFromPhone
+        );
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_sony_wf_c500;
+    }
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_galaxy_buds;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_galaxy_buds_disabled;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/coordinators/SonyWFC700NCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/coordinators/SonyWFC700NCoordinator.java
new file mode 100644
index 0000000..1dc6917
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/coordinators/SonyWFC700NCoordinator.java
@@ -0,0 +1,66 @@
+/*  Copyright (C) 2024 Marcel
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.coordinators;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.SonyHeadphonesCapabilities;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.SonyHeadphonesCoordinator;
+
+public class SonyWFC700NCoordinator extends SonyHeadphonesCoordinator {
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile(".*WF-C700N.*");
+    }
+
+    @Override
+    public List<SonyHeadphonesCapabilities> getCapabilities() {
+        return Arrays.asList(
+                SonyHeadphonesCapabilities.BatteryDual2,
+                SonyHeadphonesCapabilities.BatteryCase,
+                SonyHeadphonesCapabilities.AmbientSoundControl2,
+                SonyHeadphonesCapabilities.EqualizerSimple,
+                SonyHeadphonesCapabilities.EqualizerWithCustomBands,
+                SonyHeadphonesCapabilities.AudioUpsampling,
+                SonyHeadphonesCapabilities.ButtonModesLeftRight,
+                SonyHeadphonesCapabilities.PowerOffFromPhone,
+                SonyHeadphonesCapabilities.AmbientSoundControlButtonMode
+                // AutoOff is supported, but current Payload is incorrect.
+                // Available options in Sony App: 15min, 30min, 1h, 3h, off
+                // TODO: SonyHeadphonesCapabilities.AutomaticPowerOffByTime
+        );
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_sony_wf_c700n;
+    }
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_galaxy_buds;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_galaxy_buds_disabled;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/coordinators/SonyWFSP800NCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/coordinators/SonyWFSP800NCoordinator.java
new file mode 100644
index 0000000..f129961
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/coordinators/SonyWFSP800NCoordinator.java
@@ -0,0 +1,78 @@
+/*  Copyright (C) 2021-2024 Daniel Dakhno, Jos茅 Rebelo, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.coordinators;
+
+import androidx.annotation.NonNull;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.SonyHeadphonesCapabilities;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.SonyHeadphonesCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.BatteryConfig;
+
+public class SonyWFSP800NCoordinator extends SonyHeadphonesCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile(".*WF-SP800N.*");
+    }
+
+    @Override
+    public BatteryConfig[] getBatteryConfig(final GBDevice device) {
+        final BatteryConfig battery1 = new BatteryConfig(0, R.drawable.ic_sony_wf_800n_case, R.string.battery_case);
+        final BatteryConfig battery2 = new BatteryConfig(1, R.drawable.ic_sony_wf_800n_left, R.string.left_earbud);
+        final BatteryConfig battery3 = new BatteryConfig(2, R.drawable.ic_sony_wf_800n_right, R.string.right_earbud);
+
+        return new BatteryConfig[]{battery1, battery2, battery3};
+    }
+
+    @Override
+    public List<SonyHeadphonesCapabilities> getCapabilities() {
+        return Arrays.asList(
+                SonyHeadphonesCapabilities.BatteryDual,
+                SonyHeadphonesCapabilities.BatteryCase,
+                SonyHeadphonesCapabilities.PowerOffFromPhone,
+                SonyHeadphonesCapabilities.AmbientSoundControl,
+                SonyHeadphonesCapabilities.EqualizerWithCustomBands,
+                SonyHeadphonesCapabilities.ButtonModesLeftRight,
+                SonyHeadphonesCapabilities.PauseWhenTakenOff,
+                SonyHeadphonesCapabilities.AutomaticPowerOffWhenTakenOff,
+                SonyHeadphonesCapabilities.VoiceNotifications,
+                SonyHeadphonesCapabilities.Volume
+        );
+    }
+
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_sony_wf_sp800n;
+    }
+
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_sony_wf_800n;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_sony_wf_800n_disabled;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/coordinators/SonyWH1000XM2Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/coordinators/SonyWH1000XM2Coordinator.java
new file mode 100644
index 0000000..e06c4fd
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/coordinators/SonyWH1000XM2Coordinator.java
@@ -0,0 +1,56 @@
+/*  Copyright (C) 2022-2024 Daniel Dakhno, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.coordinators;
+
+import androidx.annotation.NonNull;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.SonyHeadphonesCapabilities;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.SonyHeadphonesCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+
+public class SonyWH1000XM2Coordinator extends SonyHeadphonesCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile(".*WH-1000XM2.*");
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_sony_wh_1000xm2;
+    }
+
+    @Override
+    public List<SonyHeadphonesCapabilities> getCapabilities() {
+        return Arrays.asList(
+                SonyHeadphonesCapabilities.BatterySingle,
+                SonyHeadphonesCapabilities.AmbientSoundControl,
+                SonyHeadphonesCapabilities.WindNoiseReduction,
+                SonyHeadphonesCapabilities.AncOptimizer,
+                SonyHeadphonesCapabilities.AudioSettingsOnlyOnSbcCodec,
+                SonyHeadphonesCapabilities.EqualizerWithCustomBands,
+                SonyHeadphonesCapabilities.SoundPosition,
+                SonyHeadphonesCapabilities.SurroundMode,
+                SonyHeadphonesCapabilities.AudioUpsampling
+        );
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/coordinators/SonyWH1000XM3Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/coordinators/SonyWH1000XM3Coordinator.java
new file mode 100644
index 0000000..b533c87
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/coordinators/SonyWH1000XM3Coordinator.java
@@ -0,0 +1,59 @@
+/*  Copyright (C) 2021-2024 Daniel Dakhno, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.coordinators;
+
+import androidx.annotation.NonNull;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.SonyHeadphonesCapabilities;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.SonyHeadphonesCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+
+public class SonyWH1000XM3Coordinator extends SonyHeadphonesCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile(".*WH-1000XM3.*");
+    }
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_sony_wh_1000xm3;
+    }
+
+    @Override
+    public List<SonyHeadphonesCapabilities> getCapabilities() {
+        return Arrays.asList(
+                SonyHeadphonesCapabilities.BatterySingle,
+                SonyHeadphonesCapabilities.AmbientSoundControl,
+                SonyHeadphonesCapabilities.WindNoiseReduction,
+                SonyHeadphonesCapabilities.AncOptimizer,
+                SonyHeadphonesCapabilities.AudioSettingsOnlyOnSbcCodec,
+                SonyHeadphonesCapabilities.EqualizerWithCustomBands,
+                SonyHeadphonesCapabilities.SoundPosition,
+                SonyHeadphonesCapabilities.SurroundMode,
+                SonyHeadphonesCapabilities.AudioUpsampling,
+                SonyHeadphonesCapabilities.TouchSensorSingle,
+                SonyHeadphonesCapabilities.AutomaticPowerOffByTime,
+                SonyHeadphonesCapabilities.VoiceNotifications,
+                SonyHeadphonesCapabilities.Volume
+        );
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/coordinators/SonyWH1000XM4Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/coordinators/SonyWH1000XM4Coordinator.java
new file mode 100644
index 0000000..0ed65d3
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/coordinators/SonyWH1000XM4Coordinator.java
@@ -0,0 +1,58 @@
+/*  Copyright (C) 2022-2024 Daniel Dakhno, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.coordinators;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.SonyHeadphonesCapabilities;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.SonyHeadphonesCoordinator;
+
+public class SonyWH1000XM4Coordinator extends SonyHeadphonesCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile(".*WH-1000XM4.*");
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_sony_wh_1000xm4;
+    }
+
+    @Override
+    public List<SonyHeadphonesCapabilities> getCapabilities() {
+        return Arrays.asList(
+                // TODO: Function of [CUSTOM] button
+                // TODO R.xml.devicesettings_connect_two_devices,
+                SonyHeadphonesCapabilities.BatterySingle,
+                SonyHeadphonesCapabilities.AmbientSoundControl,
+                SonyHeadphonesCapabilities.WindNoiseReduction,
+                SonyHeadphonesCapabilities.SpeakToChatEnabled,
+                SonyHeadphonesCapabilities.SpeakToChatConfig,
+                SonyHeadphonesCapabilities.SpeakToChatFocusOnVoice,
+                SonyHeadphonesCapabilities.AncOptimizer,
+                SonyHeadphonesCapabilities.EqualizerWithCustomBands,
+                SonyHeadphonesCapabilities.AudioUpsampling,
+                SonyHeadphonesCapabilities.TouchSensorSingle,
+                SonyHeadphonesCapabilities.PauseWhenTakenOff,
+                SonyHeadphonesCapabilities.AutomaticPowerOffWhenTakenOff,
+                SonyHeadphonesCapabilities.VoiceNotifications
+        );
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/coordinators/SonyWH1000XM5Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/coordinators/SonyWH1000XM5Coordinator.java
new file mode 100644
index 0000000..c137ebe
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/coordinators/SonyWH1000XM5Coordinator.java
@@ -0,0 +1,59 @@
+/*  Copyright (C) 2023-2024 Daniel Dakhno, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.coordinators;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.SonyHeadphonesCapabilities;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.SonyHeadphonesCoordinator;
+
+public class SonyWH1000XM5Coordinator extends SonyHeadphonesCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile(".*WH-1000XM5.*");
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_sony_wh_1000xm5;
+    }
+
+    @Override
+    public List<SonyHeadphonesCapabilities> getCapabilities() {
+        return Arrays.asList(
+                // TODO R.xml.devicesettings_connect_two_devices,
+                // TODO automatic ANC depending on state (might need phone?)
+                SonyHeadphonesCapabilities.BatterySingle,
+                SonyHeadphonesCapabilities.PowerOffFromPhone,
+                SonyHeadphonesCapabilities.AmbientSoundControl,
+                SonyHeadphonesCapabilities.SpeakToChatEnabled,
+                SonyHeadphonesCapabilities.SpeakToChatConfig,
+                SonyHeadphonesCapabilities.SpeakToChatFocusOnVoice,
+                // TODO SonyHeadphonesCapabilities.AudioUpsampling,
+                // TODO SonyHeadphonesCapabilities.AmbientSoundControlButtonMode,
+                SonyHeadphonesCapabilities.VoiceNotifications,
+                SonyHeadphonesCapabilities.AutomaticPowerOffWhenTakenOff,
+                // TODO SonyHeadphonesCapabilities.TouchSensorSingle,
+                SonyHeadphonesCapabilities.EqualizerWithCustomBands,
+                SonyHeadphonesCapabilities.QuickAccess,
+                SonyHeadphonesCapabilities.PauseWhenTakenOff
+        );
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/coordinators/SonyWIC100Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/coordinators/SonyWIC100Coordinator.java
new file mode 100644
index 0000000..372e9f3
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/coordinators/SonyWIC100Coordinator.java
@@ -0,0 +1,59 @@
+/*  Copyright (C) 2024 Zahnstocher
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.coordinators;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.SonyHeadphonesCapabilities;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.SonyHeadphonesCoordinator;
+
+public class SonyWIC100Coordinator extends SonyHeadphonesCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile(".*WI-C100.*");
+    }
+
+    @Override
+    public List<SonyHeadphonesCapabilities> getCapabilities() {
+        return Arrays.asList(
+                SonyHeadphonesCapabilities.BatterySingle,
+                SonyHeadphonesCapabilities.EqualizerSimple,
+                SonyHeadphonesCapabilities.EqualizerWithCustomBands,
+                SonyHeadphonesCapabilities.AudioUpsampling,
+                SonyHeadphonesCapabilities.VoiceNotifications,
+                SonyHeadphonesCapabilities.PowerOffFromPhone
+        );
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_sony_wi_c100;
+    }
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_galaxy_buds;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_galaxy_buds_disabled;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/coordinators/SonyWISP600NCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/coordinators/SonyWISP600NCoordinator.java
new file mode 100644
index 0000000..c1c4800
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/coordinators/SonyWISP600NCoordinator.java
@@ -0,0 +1,56 @@
+/*  Copyright (C) 2021-2024 Daniel Dakhno, Jos茅 Rebelo, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.coordinators;
+
+import androidx.annotation.NonNull;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.SonyHeadphonesCapabilities;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.SonyHeadphonesCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+
+public class SonyWISP600NCoordinator extends SonyHeadphonesCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile(".*WI-SP600N.*");
+    }
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_sony_wi_sp600n;
+    }
+
+    @Override
+    public List<SonyHeadphonesCapabilities> getCapabilities() {
+        return Arrays.asList(
+                SonyHeadphonesCapabilities.BatterySingle,
+                SonyHeadphonesCapabilities.AmbientSoundControl,
+                SonyHeadphonesCapabilities.WindNoiseReduction,
+                SonyHeadphonesCapabilities.EqualizerWithCustomBands,
+                SonyHeadphonesCapabilities.SoundPosition,
+                SonyHeadphonesCapabilities.SurroundMode,
+                SonyHeadphonesCapabilities.PowerOffFromPhone,
+                SonyHeadphonesCapabilities.AutomaticPowerOffByTime,
+                SonyHeadphonesCapabilities.VoiceNotifications,
+                SonyHeadphonesCapabilities.Volume
+        );
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/prefs/AdaptiveVolumeControl.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/prefs/AdaptiveVolumeControl.java
new file mode 100644
index 0000000..96f1461
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/prefs/AdaptiveVolumeControl.java
@@ -0,0 +1,46 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs;
+
+import android.content.SharedPreferences;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+
+public class AdaptiveVolumeControl {
+    private final boolean enabled;
+
+    public AdaptiveVolumeControl(final boolean enabled) {
+        this.enabled = enabled;
+    }
+
+    public boolean isEnabled() {
+        return enabled;
+    }
+
+    public Map<String, Object> toPreferences() {
+        return new HashMap<String, Object>() {{
+            put(DeviceSettingsPreferenceConst.PREF_SONY_ADAPTIVE_VOLUME_CONTROL, enabled);
+        }};
+    }
+
+    public static AdaptiveVolumeControl fromPreferences(final SharedPreferences prefs) {
+        return new AdaptiveVolumeControl(prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_SONY_ADAPTIVE_VOLUME_CONTROL, true));
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/prefs/AmbientSoundControl.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/prefs/AmbientSoundControl.java
new file mode 100644
index 0000000..3cc364e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/prefs/AmbientSoundControl.java
@@ -0,0 +1,87 @@
+/*  Copyright (C) 2021-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs;
+
+import android.content.SharedPreferences;
+
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+
+public class AmbientSoundControl {
+    public enum Mode {
+        OFF,
+        NOISE_CANCELLING,
+        WIND_NOISE_REDUCTION,
+        AMBIENT_SOUND
+    }
+
+    private final Mode mode;
+    private final boolean focusOnVoice;
+    private final int ambientSound;
+
+    public AmbientSoundControl(final Mode mode, final boolean focusOnVoice, final int ambientSound) {
+        if (ambientSound < 0 || ambientSound > 20) {
+            throw new IllegalArgumentException(String.format("Level must be between 0 and 20 (was %d)", ambientSound));
+        }
+
+        this.mode = mode;
+        this.focusOnVoice = focusOnVoice;
+        this.ambientSound = ambientSound;
+    }
+
+    public String toString() {
+        return String.format(Locale.getDefault(), "AmbientSoundControl{mode=%s, focusOnVoice=%s, ambientSound=%d}", mode, focusOnVoice, ambientSound);
+    }
+
+    public Mode getMode() {
+        return mode;
+    }
+
+    public boolean isFocusOnVoice() {
+        return focusOnVoice;
+    }
+
+    public int getAmbientSound() {
+        return ambientSound;
+    }
+
+    public Map<String, Object> toPreferences() {
+        return new HashMap<String, Object>() {{
+            put(DeviceSettingsPreferenceConst.PREF_SONY_AMBIENT_SOUND_CONTROL, mode.name().toLowerCase(Locale.getDefault()));
+
+            if (AmbientSoundControl.Mode.AMBIENT_SOUND.equals(mode)) {
+                // Only use the ambient sound levels and focus on voice if we're on ambient sound mode,
+                // to prevent overriding the user settings
+                put(DeviceSettingsPreferenceConst.PREF_SONY_FOCUS_VOICE, focusOnVoice);
+                // Level is offset by 1 because we can't configure the SeekBarPreference min level on the current api level
+                put(DeviceSettingsPreferenceConst.PREF_SONY_AMBIENT_SOUND_LEVEL, ambientSound - 1);
+            }
+        }};
+    }
+
+    public static AmbientSoundControl fromPreferences(final SharedPreferences prefs) {
+        final String soundControl = prefs.getString(DeviceSettingsPreferenceConst.PREF_SONY_AMBIENT_SOUND_CONTROL, "noise_cancelling");
+        final boolean focusVoice = prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_SONY_FOCUS_VOICE, false);
+        // Level is offset by 1 because we can't configure the SeekBarPreference min level on the current api level
+        final int ambientSound = prefs.getInt(DeviceSettingsPreferenceConst.PREF_SONY_AMBIENT_SOUND_LEVEL, 0) + 1;
+
+        return new AmbientSoundControl(AmbientSoundControl.Mode.valueOf(soundControl.toUpperCase()), focusVoice, ambientSound);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/prefs/AmbientSoundControlButtonMode.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/prefs/AmbientSoundControlButtonMode.java
new file mode 100644
index 0000000..0b3c9d2
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/prefs/AmbientSoundControlButtonMode.java
@@ -0,0 +1,63 @@
+/*  Copyright (C) 2022-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs;
+
+import android.content.SharedPreferences;
+
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+
+public enum AmbientSoundControlButtonMode {
+    NC_AS_OFF((byte) 0x01),
+    NC_AS((byte) 0x02),
+    NC_OFF((byte) 0x03),
+    AS_OFF((byte) 0x04),
+    ;
+
+    private final byte code;
+
+    AmbientSoundControlButtonMode(final byte code) {
+        this.code = code;
+    }
+
+    public byte getCode() {
+        return this.code;
+    }
+
+    public Map<String, Object> toPreferences() {
+        return new HashMap<String, Object>() {{
+            put(DeviceSettingsPreferenceConst.PREF_SONY_AMBIENT_SOUND_CONTROL_BUTTON_MODE, name().toLowerCase(Locale.getDefault()));
+        }};
+    }
+
+    public static AmbientSoundControlButtonMode fromCode(final byte code) {
+        for (AmbientSoundControlButtonMode value : AmbientSoundControlButtonMode.values()) {
+            if (value.getCode() == code) {
+                return value;
+            }
+        }
+
+        return null;
+    }
+
+    public static AmbientSoundControlButtonMode fromPreferences(final SharedPreferences prefs) {
+        return AmbientSoundControlButtonMode.valueOf(prefs.getString(DeviceSettingsPreferenceConst.PREF_SONY_AMBIENT_SOUND_CONTROL_BUTTON_MODE, "nc_as_off").toUpperCase());
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/prefs/AudioUpsampling.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/prefs/AudioUpsampling.java
new file mode 100644
index 0000000..9ce9cdc
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/prefs/AudioUpsampling.java
@@ -0,0 +1,46 @@
+/*  Copyright (C) 2021-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs;
+
+import android.content.SharedPreferences;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+
+public class AudioUpsampling {
+    private final boolean enabled;
+
+    public AudioUpsampling(final boolean enabled) {
+        this.enabled = enabled;
+    }
+
+    public boolean isEnabled() {
+        return enabled;
+    }
+
+    public Map<String, Object> toPreferences() {
+        return new HashMap<String, Object>() {{
+            put(DeviceSettingsPreferenceConst.PREF_SONY_AUDIO_UPSAMPLING, enabled);
+        }};
+    }
+
+    public static AudioUpsampling fromPreferences(final SharedPreferences prefs) {
+        return new AudioUpsampling(prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_SONY_AUDIO_UPSAMPLING, false));
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/prefs/AutomaticPowerOff.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/prefs/AutomaticPowerOff.java
new file mode 100644
index 0000000..3c0c00b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/prefs/AutomaticPowerOff.java
@@ -0,0 +1,64 @@
+/*  Copyright (C) 2021-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs;
+
+import android.content.SharedPreferences;
+
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+
+public enum AutomaticPowerOff {
+    OFF(new byte[]{(byte) 0x11, (byte) 0x00}),
+    AFTER_5_MIN(new byte[]{(byte) 0x00, (byte) 0x00}),
+    AFTER_30_MIN(new byte[]{(byte) 0x01, (byte) 0x01}),
+    AFTER_1_HOUR(new byte[]{(byte) 0x02, (byte) 0x02}),
+    AFTER_3_HOUR(new byte[]{(byte) 0x03, (byte) 0x03}),
+    WHEN_TAKEN_OFF(new byte[]{(byte) 0x10, (byte) 0x00});
+
+    private final byte[] code;
+
+    AutomaticPowerOff(final byte[] code) {
+        this.code = code;
+    }
+
+    public byte[] getCode() {
+        return this.code;
+    }
+
+    public Map<String, Object> toPreferences() {
+        return new HashMap<String, Object>() {{
+            put(DeviceSettingsPreferenceConst.PREF_SONY_AUTOMATIC_POWER_OFF, name().toLowerCase(Locale.getDefault()));
+        }};
+    }
+
+    public static AutomaticPowerOff fromCode(final byte b1, final byte b2) {
+        for (AutomaticPowerOff value : AutomaticPowerOff.values()) {
+            if (value.getCode()[0] == b1 && value.getCode()[1] == b2) {
+                return value;
+            }
+        }
+
+        return null;
+    }
+
+    public static AutomaticPowerOff fromPreferences(final SharedPreferences prefs) {
+        return AutomaticPowerOff.valueOf(prefs.getString(DeviceSettingsPreferenceConst.PREF_SONY_AUTOMATIC_POWER_OFF, "off").toUpperCase());
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/prefs/ButtonModes.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/prefs/ButtonModes.java
new file mode 100644
index 0000000..4971848
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/prefs/ButtonModes.java
@@ -0,0 +1,65 @@
+/*  Copyright (C) 2021-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs;
+
+import android.content.SharedPreferences;
+
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+
+public class ButtonModes {
+    public enum Mode {
+        OFF,
+        AMBIENT_SOUND_CONTROL,
+        PLAYBACK_CONTROL,
+        VOLUME_CONTROL,
+        ;
+    }
+
+    final Mode left;
+    final Mode right;
+
+    public ButtonModes(final Mode left, final Mode right) {
+        this.left = left;
+        this.right = right;
+    }
+
+    public Mode getModeLeft() {
+        return left;
+    }
+
+    public Mode getModeRight() {
+        return right;
+    }
+
+    public Map<String, Object> toPreferences() {
+        return new HashMap<String, Object>() {{
+            put(DeviceSettingsPreferenceConst.PREF_SONY_BUTTON_MODE_LEFT, left.name().toLowerCase(Locale.getDefault()));
+            put(DeviceSettingsPreferenceConst.PREF_SONY_BUTTON_MODE_RIGHT, right.name().toLowerCase(Locale.getDefault()));
+        }};
+    }
+
+    public static ButtonModes fromPreferences(final SharedPreferences prefs) {
+        return new ButtonModes(
+                Mode.valueOf(prefs.getString(DeviceSettingsPreferenceConst.PREF_SONY_BUTTON_MODE_LEFT, "off").toUpperCase()),
+                Mode.valueOf(prefs.getString(DeviceSettingsPreferenceConst.PREF_SONY_BUTTON_MODE_RIGHT, "off").toUpperCase())
+        );
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/prefs/EqualizerCustomBands.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/prefs/EqualizerCustomBands.java
new file mode 100644
index 0000000..6d424cc
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/prefs/EqualizerCustomBands.java
@@ -0,0 +1,86 @@
+/*  Copyright (C) 2021-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs;
+
+import android.content.SharedPreferences;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+
+public class EqualizerCustomBands {
+    private final List<Integer> bands;
+    private final int bass;
+
+    public EqualizerCustomBands(final List<Integer> bands, final int bass) {
+        if (bands.size() != 5) {
+            throw new IllegalArgumentException("Equalizer needs exactly 5 bands");
+        }
+
+        for (final Integer band : bands) {
+            if (band < -10 || band > 10) {
+                throw new IllegalArgumentException(String.format("Bands should be between -10 and 10, got %d", band));
+            }
+        }
+
+        if (bass < -10 || bass > 10) {
+            throw new IllegalArgumentException(String.format("Clear Bass value shoulud be between -10 and 10, got %d", bass));
+        }
+
+        this.bands = bands;
+        this.bass = bass;
+    }
+
+    public List<Integer> getBands() {
+        return bands;
+    }
+
+    public int getBass() {
+        return bass;
+    }
+
+    public String toString() {
+        return String.format(Locale.ROOT, "EqualizerCustomBands{clearBass=%d, bands=%s}", bass, bands);
+    }
+
+    public Map<String, Object> toPreferences() {
+        return new HashMap<String, Object>() {{
+            put(DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_BAND_400, bands.get(0) + 10);
+            put(DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_BAND_1000, bands.get(1) + 10);
+            put(DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_BAND_2500, bands.get(2) + 10);
+            put(DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_BAND_6300, bands.get(3) + 10);
+            put(DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_BAND_16000, bands.get(4) + 10);
+            put(DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_BASS, bass + 10);
+            ;
+        }};
+    }
+
+    public static EqualizerCustomBands fromPreferences(final SharedPreferences prefs) {
+        int band1 = prefs.getInt(DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_BAND_400, 10) - 10;
+        int band2 = prefs.getInt(DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_BAND_1000, 10) - 10;
+        int band3 = prefs.getInt(DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_BAND_2500, 10) - 10;
+        int band4 = prefs.getInt(DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_BAND_6300, 10) - 10;
+        int band5 = prefs.getInt(DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_BAND_16000, 10) - 10;
+        int bass = prefs.getInt(DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_BASS, 10) - 10;
+
+        return new EqualizerCustomBands(Arrays.asList(band1, band2, band3, band4, band5), bass);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/prefs/EqualizerPreset.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/prefs/EqualizerPreset.java
new file mode 100644
index 0000000..9a2b066
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/prefs/EqualizerPreset.java
@@ -0,0 +1,70 @@
+/*  Copyright (C) 2021-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs;
+
+import android.content.SharedPreferences;
+
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+
+public enum EqualizerPreset {
+    OFF((byte) 0x00),
+    BRIGHT((byte) 0x10),
+    EXCITED((byte) 0x11),
+    MELLOW((byte) 0x12),
+    RELAXED((byte) 0x13),
+    VOCAL((byte) 0x14),
+    TREBLE_BOOST((byte) 0x15),
+    BASS_BOOST((byte) 0x16),
+    SPEECH((byte) 0x17),
+    MANUAL((byte) 0xa0),
+    CUSTOM_1((byte) 0xa1),
+    CUSTOM_2((byte) 0xa2);
+
+    private final byte code;
+
+    EqualizerPreset(final byte code) {
+        this.code = code;
+    }
+
+    public byte getCode() {
+        return this.code;
+    }
+
+    public Map<String, Object> toPreferences() {
+        return new HashMap<String, Object>() {{
+            put(DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_MODE, name().toLowerCase(Locale.getDefault()));
+        }};
+    }
+
+    public static EqualizerPreset fromCode(final byte code) {
+        for (EqualizerPreset value : EqualizerPreset.values()) {
+            if (value.getCode() == code) {
+                return value;
+            }
+        }
+
+        return null;
+    }
+
+    public static EqualizerPreset fromPreferences(final SharedPreferences prefs) {
+        return EqualizerPreset.valueOf(prefs.getString(DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_MODE, "off").toUpperCase());
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/prefs/PauseWhenTakenOff.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/prefs/PauseWhenTakenOff.java
new file mode 100644
index 0000000..53fd2aa
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/prefs/PauseWhenTakenOff.java
@@ -0,0 +1,46 @@
+/*  Copyright (C) 2021-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs;
+
+import android.content.SharedPreferences;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+
+public class PauseWhenTakenOff {
+    private final boolean enabled;
+
+    public PauseWhenTakenOff(final boolean enabled) {
+        this.enabled = enabled;
+    }
+
+    public boolean isEnabled() {
+        return enabled;
+    }
+
+    public Map<String, Object> toPreferences() {
+        return new HashMap<String, Object>() {{
+            put(DeviceSettingsPreferenceConst.PREF_SONY_PAUSE_WHEN_TAKEN_OFF, enabled);
+        }};
+    }
+
+    public static PauseWhenTakenOff fromPreferences(final SharedPreferences prefs) {
+        return new PauseWhenTakenOff(prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_SONY_PAUSE_WHEN_TAKEN_OFF, false));
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/prefs/QuickAccess.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/prefs/QuickAccess.java
new file mode 100644
index 0000000..40e68b9
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/prefs/QuickAccess.java
@@ -0,0 +1,83 @@
+/*  Copyright (C) 2022-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs;
+
+import android.content.SharedPreferences;
+
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+
+public class QuickAccess {
+    public enum Mode {
+        OFF((byte) 0x00),
+        SPOTIFY((byte) 0x01),
+        ;
+
+        private final byte code;
+
+        Mode(final byte code) {
+            this.code = code;
+        }
+
+        public byte getCode() {
+            return this.code;
+        }
+
+        public static Mode fromCode(final byte code) {
+            for (Mode value : Mode.values()) {
+                if (value.getCode() == code) {
+                    return value;
+                }
+            }
+
+            return null;
+        }
+    }
+
+    final Mode doubleTap;
+    final Mode tripleTap;
+
+    public QuickAccess(final Mode doubleTap, final Mode tripleTap) {
+        this.doubleTap = doubleTap;
+        this.tripleTap = tripleTap;
+    }
+
+    public Mode getModeDoubleTap() {
+        return doubleTap;
+    }
+
+    public Mode getModeTripleTap() {
+        return tripleTap;
+    }
+
+    public Map<String, Object> toPreferences() {
+        return new HashMap<String, Object>() {{
+            put(DeviceSettingsPreferenceConst.PREF_SONY_QUICK_ACCESS_DOUBLE_TAP, doubleTap.name().toLowerCase(Locale.getDefault()));
+            put(DeviceSettingsPreferenceConst.PREF_SONY_QUICK_ACCESS_TRIPLE_TAP, tripleTap.name().toLowerCase(Locale.getDefault()));
+        }};
+    }
+
+    public static QuickAccess fromPreferences(final SharedPreferences prefs) {
+        return new QuickAccess(
+                Mode.valueOf(prefs.getString(DeviceSettingsPreferenceConst.PREF_SONY_QUICK_ACCESS_DOUBLE_TAP, "off").toUpperCase()),
+                Mode.valueOf(prefs.getString(DeviceSettingsPreferenceConst.PREF_SONY_QUICK_ACCESS_TRIPLE_TAP, "off").toUpperCase())
+        );
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/prefs/SoundPosition.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/prefs/SoundPosition.java
new file mode 100644
index 0000000..97f80a8
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/prefs/SoundPosition.java
@@ -0,0 +1,54 @@
+/*  Copyright (C) 2021-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs;
+
+import android.content.SharedPreferences;
+
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+
+public enum SoundPosition {
+    OFF((byte) 0x00),
+    FRONT((byte) 0x03),
+    FRONT_LEFT((byte) 0x01),
+    FRONT_RIGHT((byte) 0x02),
+    REAR_LEFT((byte) 0x11),
+    REAR_RIGHT((byte) 0x12);
+
+    private final byte code;
+
+    SoundPosition(final byte code) {
+        this.code = code;
+    }
+
+    public byte getCode() {
+        return this.code;
+    }
+
+    public Map<String, Object> toPreferences() {
+        return new HashMap<String, Object>() {{
+            put(DeviceSettingsPreferenceConst.PREF_SONY_SOUND_POSITION, name().toLowerCase(Locale.getDefault()));
+        }};
+    }
+
+    public static SoundPosition fromPreferences(final SharedPreferences prefs) {
+        return SoundPosition.valueOf(prefs.getString(DeviceSettingsPreferenceConst.PREF_SONY_SOUND_POSITION, "off").toUpperCase());
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/prefs/SpeakToChatConfig.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/prefs/SpeakToChatConfig.java
new file mode 100644
index 0000000..e66386c
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/prefs/SpeakToChatConfig.java
@@ -0,0 +1,124 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs;
+
+import android.content.SharedPreferences;
+
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+
+public class SpeakToChatConfig {
+    public enum Sensitivity {
+        AUTO(0x00),
+        HIGH(0x01),
+        LOW(0x02),
+        ;
+
+        private final byte code;
+
+        Sensitivity(final int code) {
+            this.code = (byte) code;
+        }
+
+        public byte getCode() {
+            return this.code;
+        }
+
+        public static Sensitivity fromCode(final byte b) {
+            for (Sensitivity value : Sensitivity.values()) {
+                if (value.getCode() == b) {
+                    return value;
+                }
+            }
+
+            return null;
+        }
+    }
+
+    public enum Timeout {
+        SHORT(0x00),
+        STANDARD(0x01),
+        LONG(0x02),
+        OFF(0x03),
+        ;
+
+        private final byte code;
+
+        Timeout(final int code) {
+            this.code = (byte) code;
+        }
+
+        public byte getCode() {
+            return this.code;
+        }
+
+        public static Timeout fromCode(final byte b) {
+            for (Timeout value : Timeout.values()) {
+                if (value.getCode() == b) {
+                    return value;
+                }
+            }
+
+            return null;
+        }
+    }
+
+    private final boolean focusOnVoice;
+    private final Sensitivity sensitivity;
+    private final Timeout timeout;
+
+    public SpeakToChatConfig(final boolean focusOnVoice, final Sensitivity sensitivity, final Timeout timeout) {
+        this.focusOnVoice = focusOnVoice;
+        this.sensitivity = sensitivity;
+        this.timeout = timeout;
+    }
+
+    public boolean isFocusOnVoice() {
+        return focusOnVoice;
+    }
+
+    public Sensitivity getSensitivity() {
+        return sensitivity;
+    }
+
+    public Timeout getTimeout() {
+        return timeout;
+    }
+
+    public String toString() {
+        return String.format(Locale.getDefault(), "SpeakToChatConfig{focusOnVoice=%s, sensitivity=%s, timeout=%s}", focusOnVoice, sensitivity, timeout);
+    }
+
+    public Map<String, Object> toPreferences() {
+        return new HashMap<String, Object>() {{
+            put(DeviceSettingsPreferenceConst.PREF_SONY_SPEAK_TO_CHAT_FOCUS_ON_VOICE, focusOnVoice);
+            put(DeviceSettingsPreferenceConst.PREF_SONY_SPEAK_TO_CHAT_SENSITIVITY, sensitivity.name().toLowerCase(Locale.getDefault()));
+            put(DeviceSettingsPreferenceConst.PREF_SONY_SPEAK_TO_CHAT_TIMEOUT, timeout.name().toLowerCase(Locale.getDefault()));
+        }};
+    }
+
+    public static SpeakToChatConfig fromPreferences(final SharedPreferences prefs) {
+        final boolean focusOnVoice = prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_SONY_SPEAK_TO_CHAT_FOCUS_ON_VOICE, false);
+        final Sensitivity sensitivity = Sensitivity.valueOf(prefs.getString(DeviceSettingsPreferenceConst.PREF_SONY_SPEAK_TO_CHAT_SENSITIVITY, "auto").toUpperCase());
+        final Timeout timeout = Timeout.valueOf(prefs.getString(DeviceSettingsPreferenceConst.PREF_SONY_SPEAK_TO_CHAT_TIMEOUT, "standard").toUpperCase());
+
+        return new SpeakToChatConfig(focusOnVoice, sensitivity, timeout);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/prefs/SpeakToChatEnabled.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/prefs/SpeakToChatEnabled.java
new file mode 100644
index 0000000..f38a1a5
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/prefs/SpeakToChatEnabled.java
@@ -0,0 +1,53 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs;
+
+import android.content.SharedPreferences;
+
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+
+public class SpeakToChatEnabled {
+    private final boolean enabled;
+
+    public SpeakToChatEnabled(final boolean enabled) {
+        this.enabled = enabled;
+    }
+
+    public String toString() {
+        return String.format(Locale.getDefault(), "SpeakToChatEnabled{enabled=%s}", enabled);
+    }
+
+    public boolean isEnabled() {
+        return enabled;
+    }
+
+    public Map<String, Object> toPreferences() {
+        return new HashMap<String, Object>() {{
+            put(DeviceSettingsPreferenceConst.PREF_SONY_SPEAK_TO_CHAT, enabled);
+        }};
+    }
+
+    public static SpeakToChatEnabled fromPreferences(final SharedPreferences prefs) {
+        final boolean enabled = prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_SONY_SPEAK_TO_CHAT, false);
+
+        return new SpeakToChatEnabled(enabled);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/prefs/SurroundMode.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/prefs/SurroundMode.java
new file mode 100644
index 0000000..8eea63e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/prefs/SurroundMode.java
@@ -0,0 +1,53 @@
+/*  Copyright (C) 2021-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs;
+
+import android.content.SharedPreferences;
+
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+
+public enum SurroundMode {
+    OFF((byte) 0x00),
+    ARENA((byte) 0x02),
+    CLUB((byte) 0x04),
+    OUTDOOR_STAGE((byte) 0x01),
+    CONCERT_HALL((byte) 0x03);
+
+    private final byte code;
+
+    SurroundMode(final byte code) {
+        this.code = code;
+    }
+
+    public byte getCode() {
+        return this.code;
+    }
+
+    public Map<String, Object> toPreferences() {
+        return new HashMap<String, Object>() {{
+            put(DeviceSettingsPreferenceConst.PREF_SONY_SURROUND_MODE, name().toLowerCase(Locale.getDefault()));
+        }};
+    }
+
+    public static SurroundMode fromPreferences(final SharedPreferences prefs) {
+        return SurroundMode.valueOf(prefs.getString(DeviceSettingsPreferenceConst.PREF_SONY_SURROUND_MODE, "off").toUpperCase());
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/prefs/TouchSensor.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/prefs/TouchSensor.java
new file mode 100644
index 0000000..ae2567d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/prefs/TouchSensor.java
@@ -0,0 +1,46 @@
+/*  Copyright (C) 2021-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs;
+
+import android.content.SharedPreferences;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+
+public class TouchSensor {
+    private final boolean enabled;
+
+    public TouchSensor(final boolean enabled) {
+        this.enabled = enabled;
+    }
+
+    public boolean isEnabled() {
+        return enabled;
+    }
+
+    public Map<String, Object> toPreferences() {
+        return new HashMap<String, Object>() {{
+            put(DeviceSettingsPreferenceConst.PREF_SONY_TOUCH_SENSOR, enabled);
+        }};
+    }
+
+    public static TouchSensor fromPreferences(final SharedPreferences prefs) {
+        return new TouchSensor(prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_SONY_TOUCH_SENSOR, true));
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/prefs/VoiceNotifications.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/prefs/VoiceNotifications.java
new file mode 100644
index 0000000..00083e6
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/prefs/VoiceNotifications.java
@@ -0,0 +1,46 @@
+/*  Copyright (C) 2021-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs;
+
+import android.content.SharedPreferences;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+
+public class VoiceNotifications {
+    private final boolean enabled;
+
+    public VoiceNotifications(final boolean enabled) {
+        this.enabled = enabled;
+    }
+
+    public boolean isEnabled() {
+        return enabled;
+    }
+
+    public Map<String, Object> toPreferences() {
+        return new HashMap<String, Object>() {{
+            put(DeviceSettingsPreferenceConst.PREF_SONY_NOTIFICATION_VOICE_GUIDE, enabled);
+        }};
+    }
+
+    public static VoiceNotifications fromPreferences(final SharedPreferences prefs) {
+        return new VoiceNotifications(prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_SONY_NOTIFICATION_VOICE_GUIDE, true));
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/prefs/WideAreaTap.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/prefs/WideAreaTap.java
new file mode 100644
index 0000000..0e9b08e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/prefs/WideAreaTap.java
@@ -0,0 +1,46 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs;
+
+import android.content.SharedPreferences;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+
+public class WideAreaTap {
+    private final boolean enabled;
+
+    public WideAreaTap(final boolean enabled) {
+        this.enabled = enabled;
+    }
+
+    public boolean isEnabled() {
+        return enabled;
+    }
+
+    public Map<String, Object> toPreferences() {
+        return new HashMap<String, Object>() {{
+            put(DeviceSettingsPreferenceConst.PREF_SONY_WIDE_AREA_TAP, enabled);
+        }};
+    }
+
+    public static WideAreaTap fromPreferences(final SharedPreferences prefs) {
+        return new WideAreaTap(prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_SONY_WIDE_AREA_TAP, true));
+    }
+}
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 0000000..a3baed0
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/wena3/SonyWena3ActivitySampleCombiner.java
@@ -0,0 +1,78 @@
+/*  Copyright (C) 2023-2024 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 <https://www.gnu.org/licenses/>. */
+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<Wena3BehaviorSample> behaviorSamples = behaviorSampleProvider.getAllSamples(startDate.getTime(), Long.MAX_VALUE);
+        List<Wena3ActivitySample> alteredSamples = new ArrayList<>();
+        for(Wena3BehaviorSample behaviorSample: behaviorSamples) {
+            List<Wena3ActivitySample> 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<Wena3HeartRateSample> heartRateSamples = heartRateSampleProvider.getAllSamples(startDate.getTime(), Long.MAX_VALUE);
+        List<Wena3ActivitySample> 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<Wena3ActivitySample> 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 0000000..7a499d5
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/wena3/SonyWena3ActivitySampleProvider.java
@@ -0,0 +1,123 @@
+/*  Copyright (C) 2023-2024 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 <https://www.gnu.org/licenses/>. */
+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<Wena3ActivitySample> {
+    public SonyWena3ActivitySampleProvider(GBDevice device, DaoSession session) {
+        super(device, session);
+    }
+
+    @Override
+    public AbstractDao<Wena3ActivitySample, ?> 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 ActivityKind normalizeType(int rawType) {
+        if(rawType < 0 || rawType >= BehaviorSample.Type.LUT.length) return ActivityKind.UNKNOWN;
+
+        BehaviorSample.Type internalType = BehaviorSample.Type.LUT[rawType];
+        switch(internalType) {
+            case NOT_WEARING:
+                return ActivityKind.NOT_WORN;
+
+            case WALK:
+                return ActivityKind.WALKING;
+            case RUN:
+                return ActivityKind.RUNNING;
+            case EXERCISE:
+                return ActivityKind.EXERCISE;
+
+            case SLEEP_LIGHT:
+                return ActivityKind.LIGHT_SLEEP;
+            case SLEEP_REM:
+                return ActivityKind.REM_SLEEP;
+            case SLEEP_DEEP:
+                return ActivityKind.DEEP_SLEEP;
+
+            case STATIC:
+            case SLEEP_AWAKE:
+            case UNKNOWN:
+            default:
+                return ActivityKind.UNKNOWN;
+        }
+    }
+
+    @Override
+    public int toRawActivityKind(ActivityKind activityKind) {
+        switch(activityKind) {
+            case NOT_MEASURED:
+            case NOT_WORN:
+                return BehaviorSample.Type.NOT_WEARING.ordinal();
+            case WALKING:
+                return BehaviorSample.Type.WALK.ordinal();
+            case RUNNING:
+                return BehaviorSample.Type.RUN.ordinal();
+            case LIGHT_SLEEP:
+                return BehaviorSample.Type.SLEEP_LIGHT.ordinal();
+            case REM_SLEEP:
+                return BehaviorSample.Type.SLEEP_REM.ordinal();
+            case DEEP_SLEEP:
+                return BehaviorSample.Type.SLEEP_DEEP.ordinal();
+            case 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 0000000..952662f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/wena3/SonyWena3BehaviorSampleProvider.java
@@ -0,0 +1,56 @@
+/*  Copyright (C) 2023-2024 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 <https://www.gnu.org/licenses/>. */
+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<Wena3BehaviorSample> {
+    public SonyWena3BehaviorSampleProvider(final GBDevice device, final DaoSession session) {
+        super(device, session);
+    }
+
+    @NonNull
+    @Override
+    public AbstractDao<Wena3BehaviorSample, ?> 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 0000000..29bb304
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/wena3/SonyWena3CaloriesSampleProvider.java
@@ -0,0 +1,56 @@
+/*  Copyright (C) 2023-2024 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 <https://www.gnu.org/licenses/>. */
+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<Wena3CaloriesSample> {
+    public SonyWena3CaloriesSampleProvider(final GBDevice device, final DaoSession session) {
+        super(device, session);
+    }
+
+    @NonNull
+    @Override
+    public AbstractDao<Wena3CaloriesSample, ?> 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 0000000..d76ca48
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/wena3/SonyWena3Constants.java
@@ -0,0 +1,64 @@
+/*  Copyright (C) 2023-2024 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 <https://www.gnu.org/licenses/>. */
+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 0000000..38af0c7
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/wena3/SonyWena3Coordinator.java
@@ -0,0 +1,297 @@
+/*  Copyright (C) 2023-2024 akasaka / Genjitsu Labs, Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.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 java.util.regex.Pattern;
+
+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 {
+    @Nullable
+    @Override
+    public Class<? extends Activity> getPairingActivity() {
+        return null;
+    }
+
+    @Override
+    public String getManufacturer() {
+        return "Sony";
+    }
+
+    @Override
+    public boolean supportsAppsManagement(GBDevice device) {
+        return false;
+    }
+
+    @Override
+    public Class<? extends Activity> getAppsManagementActivity() {
+        return null;
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return SonyWena3DeviceSupport.class;
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_sony_wena3;
+    }
+
+    @Override
+    public DeviceSpecificSettingsCustomizer getDeviceSpecificSettingsCustomizer(GBDevice device) {
+        return new SonyWena3SettingsCustomizer();
+    }
+
+    @NonNull
+    @Override
+    public Collection<? extends ScanFilter> createBLEScanFilters() {
+        ScanFilter.Builder builder = new ScanFilter.Builder();
+        builder.setDeviceName(SonyWena3Constants.BT_DEVICE_NAME);
+        ArrayList<ScanFilter> 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();
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile(SonyWena3Constants.BT_DEVICE_NAME);
+    }
+    @Override
+    public int getBondingStyle() {
+        return BONDING_STYLE_BOND;
+    }
+
+    @Override
+    public boolean supportsCalendarEvents() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsRealtimeData() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsActivityDataFetching() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsActivityTracking() {
+        return true;
+    }
+
+
+    @Override
+    public boolean supportsAppReordering() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsStressMeasurement() {
+        return true;
+    }
+
+    @Override
+    public TimeSampleProvider<? extends StressSample> getStressSampleProvider(GBDevice device, DaoSession session) {
+        return new SonyWena3StressSampleProvider(device, session);
+    }
+
+    @Override
+    public boolean supportsSpo2(GBDevice device) {
+        return false;
+    }
+
+    @Override
+    public boolean supportsHeartRateMeasurement(GBDevice device) {
+        return true;
+    }
+
+    @Override
+    public boolean supportsHeartRateStats() {
+        return true;
+    }
+
+    @Override
+    public SampleProvider<? extends ActivitySample> getSampleProvider(GBDevice device, DaoSession session) {
+        return new SonyWena3ActivitySampleProvider(device, session);
+    }
+
+    @Override
+    public TimeSampleProvider<? extends HeartRateSample> getHeartRateRestingSampleProvider(GBDevice device, DaoSession session) {
+        return new SonyWena3HeartRateSampleProvider(device, session);
+    }
+
+
+    @Override
+    public InstallHandler findInstallHandler(Uri uri, Context context) {
+        return null;
+    }
+
+    @Override
+    public boolean supportsScreenshots(final GBDevice device) {
+        return false;
+    }
+
+    @Override
+    public int getAlarmSlotCount(GBDevice device) {
+        return SonyWena3Constants.ALARM_SLOTS;
+    }
+
+    @Override
+    public boolean supportsSmartWakeup(GBDevice device, int position) {
+        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 0000000..ae7b63c
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/wena3/SonyWena3EnergySampleProvider.java
@@ -0,0 +1,56 @@
+/*  Copyright (C) 2023-2024 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 <https://www.gnu.org/licenses/>. */
+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<Wena3EnergySample> {
+    public SonyWena3EnergySampleProvider(final GBDevice device, final DaoSession session) {
+        super(device, session);
+    }
+
+    @NonNull
+    @Override
+    public AbstractDao<Wena3EnergySample, ?> 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 0000000..3dbbc65
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/wena3/SonyWena3HeartRateSampleProvider.java
@@ -0,0 +1,58 @@
+/*  Copyright (C) 2023-2024 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 <https://www.gnu.org/licenses/>. */
+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<Wena3HeartRateSample> {
+    public SonyWena3HeartRateSampleProvider(final GBDevice device, final DaoSession session) {
+        super(device, session);
+    }
+
+    @NonNull
+    @Override
+    public AbstractDao<Wena3HeartRateSample, ?> 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 0000000..e8efc76
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/wena3/SonyWena3SettingKeys.java
@@ -0,0 +1,51 @@
+/*  Copyright (C) 2023-2024 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 <https://www.gnu.org/licenses/>. */
+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 0000000..ba9394e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/wena3/SonyWena3SettingsCustomizer.java
@@ -0,0 +1,80 @@
+/*  Copyright (C) 2023-2024 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 <https://www.gnu.org/licenses/>. */
+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, final String rootKey) {
+        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<String> getPreferenceKeysWithSummary() {
+        return Collections.emptySet();
+    }
+    public static final Creator<SonyWena3SettingsCustomizer> CREATOR = new Creator<SonyWena3SettingsCustomizer>() {
+        @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 0000000..f64e399
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/wena3/SonyWena3StressSampleProvider.java
@@ -0,0 +1,73 @@
+/*  Copyright (C) 2023-2024 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 <https://www.gnu.org/licenses/>. */
+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<Wena3StressSample> {
+    public SonyWena3StressSampleProvider(final GBDevice device, final DaoSession session) {
+        super(device, session);
+    }
+
+    @NonNull
+    @Override
+    public AbstractDao<Wena3StressSample, ?> 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<Wena3StressSample> getAllSamples(long timestampFrom, long timestampTo) {
+        List<Wena3StressSample> 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 0000000..9e38dfd
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/wena3/SonyWena3Vo2SampleProvider.java
@@ -0,0 +1,56 @@
+/*  Copyright (C) 2023-2024 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 <https://www.gnu.org/licenses/>. */
+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<Wena3Vo2Sample> {
+    public SonyWena3Vo2SampleProvider(final GBDevice device, final DaoSession session) {
+        super(device, session);
+    }
+
+    @NonNull
+    @Override
+    public AbstractDao<Wena3Vo2Sample, ?> 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/devices/sonyswr12/SonySWR12DeviceCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sonyswr12/SonySWR12DeviceCoordinator.java
new file mode 100644
index 0000000..b91b838
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sonyswr12/SonySWR12DeviceCoordinator.java
@@ -0,0 +1,150 @@
+/*  Copyright (C) 2020-2024 Damien Gaignon, Daniel Dakhno, Jos茅 Rebelo,
+    opavlov, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.sonyswr12;
+
+import android.app.Activity;
+import android.content.Context;
+import android.net.Uri;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.GBException;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLEDeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.SonySWR12DeviceSupport;
+
+public class SonySWR12DeviceCoordinator extends AbstractBLEDeviceCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile(".*swr12.*", Pattern.CASE_INSENSITIVE);
+    }
+
+    @Override
+    public String getManufacturer() {
+        return "Sony";
+    }
+
+    @Override
+    protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
+
+    }
+
+    @Nullable
+    @Override
+    public Class<? extends Activity> getPairingActivity() {
+        return null;
+    }
+
+    @Override
+    public boolean supportsActivityDataFetching() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsActivityTracking() {
+        return true;
+    }
+
+    @Override
+    public SampleProvider<? extends ActivitySample> getSampleProvider(GBDevice device, DaoSession session) {
+        return new SonySWR12SampleProvider(device, session);
+    }
+
+    @Override
+    public InstallHandler findInstallHandler(Uri uri, Context context) {
+        return null;
+    }
+
+    @Override
+    public boolean supportsScreenshots(final GBDevice device) {
+        return false;
+    }
+
+    @Override
+    public int getAlarmSlotCount(GBDevice device) {
+        return 5;
+    }
+
+    @Override
+    public boolean supportsSmartWakeup(GBDevice device, int position) {
+        return true;
+    }
+
+    @Override
+    public boolean supportsHeartRateMeasurement(GBDevice device) {
+        return true;
+    }
+
+    @Override
+    public boolean supportsAppsManagement(final GBDevice device) {
+        return false;
+    }
+
+    @Override
+    public Class<? extends Activity> getAppsManagementActivity() {
+        return null;
+    }
+
+    @Override
+    public boolean supportsCalendarEvents() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsRealtimeData() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsWeather() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsFindDevice() {
+        return false;
+    }
+
+    @Override
+    public int[] getSupportedDeviceSpecificSettings(GBDevice device) {
+        return new int[]{R.xml.devicesettings_sonyswr12};
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return SonySWR12DeviceSupport.class;
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_sonyswr12;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sonyswr12/SonySWR12SampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sonyswr12/SonySWR12SampleProvider.java
new file mode 100644
index 0000000..a7fdaf4
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sonyswr12/SonySWR12SampleProvider.java
@@ -0,0 +1,99 @@
+/*  Copyright (C) 2020-2024 opavlov
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.sonyswr12;
+
+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.SonySWR12Sample;
+import nodomain.freeyourgadget.gadgetbridge.entities.SonySWR12SampleDao;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.SonySWR12Constants;
+
+public class SonySWR12SampleProvider extends AbstractSampleProvider<SonySWR12Sample> {
+    public SonySWR12SampleProvider(GBDevice device, DaoSession session) {
+        super(device, session);
+    }
+
+    @Override
+    public AbstractDao<SonySWR12Sample, ?> getSampleDao() {
+        return getSession().getSonySWR12SampleDao();
+    }
+
+    @Nullable
+    @Override
+    protected Property getRawKindSampleProperty() {
+        return SonySWR12SampleDao.Properties.RawKind;
+    }
+
+    @NonNull
+    @Override
+    protected Property getTimestampSampleProperty() {
+        return SonySWR12SampleDao.Properties.Timestamp;
+    }
+
+    @NonNull
+    @Override
+    protected Property getDeviceIdentifierSampleProperty() {
+        return SonySWR12SampleDao.Properties.DeviceId;
+    }
+
+    @Override
+    public ActivityKind normalizeType(int rawType) {
+        switch (rawType) {
+            case SonySWR12Constants.TYPE_ACTIVITY:
+                return ActivityKind.ACTIVITY;
+            case SonySWR12Constants.TYPE_LIGHT:
+                return ActivityKind.LIGHT_SLEEP;
+            case SonySWR12Constants.TYPE_DEEP:
+                return ActivityKind.DEEP_SLEEP;
+            case SonySWR12Constants.TYPE_NOT_WORN:
+                return ActivityKind.NOT_WORN;
+        }
+        return ActivityKind.UNKNOWN;
+    }
+
+    @Override
+    public int toRawActivityKind(ActivityKind activityKind) {
+        switch (activityKind) {
+            case ACTIVITY:
+                return SonySWR12Constants.TYPE_ACTIVITY;
+            case LIGHT_SLEEP:
+                return SonySWR12Constants.TYPE_LIGHT;
+            case DEEP_SLEEP:
+                return SonySWR12Constants.TYPE_DEEP;
+            case NOT_WORN:
+                return SonySWR12Constants.TYPE_NOT_WORN;
+        }
+        return SonySWR12Constants.TYPE_ACTIVITY;
+    }
+
+    @Override
+    public float normalizeIntensity(int rawIntensity) {
+        return rawIntensity;
+    }
+
+    @Override
+    public SonySWR12Sample createActivitySample() {
+        return new SonySWR12Sample();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/soundcore/liberty3_pro/SoundcoreLiberty3ProCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/soundcore/liberty3_pro/SoundcoreLiberty3ProCoordinator.java
new file mode 100644
index 0000000..586de14
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/soundcore/liberty3_pro/SoundcoreLiberty3ProCoordinator.java
@@ -0,0 +1,83 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.soundcore.liberty3_pro;
+
+import androidx.annotation.NonNull;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.GBException;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettings;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsScreen;
+import nodomain.freeyourgadget.gadgetbridge.devices.AbstractDeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.BatteryConfig;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.soundcore.liberty.SoundcoreLiberty3ProDeviceSupport;
+
+public class SoundcoreLiberty3ProCoordinator extends AbstractDeviceCoordinator {
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_soundcore_liberty3_pro;
+    }
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_galaxy_buds;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_galaxy_buds_disabled;
+    }
+
+    @Override
+    public String getManufacturer() {
+        return "Anker";
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("Soundcore Liberty 3 Pro");
+    }
+
+    @Override
+    public int getBondingStyle(){
+        return BONDING_STYLE_NONE;
+    }
+
+    @Override
+    protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
+    }
+
+
+    @Override
+    public int getBatteryCount() {
+        return 3;
+    }
+
+    @Override
+    public BatteryConfig[] getBatteryConfig(final GBDevice device) {
+        BatteryConfig battery1 = new BatteryConfig(0, R.drawable.ic_buds_pro_case, R.string.battery_case);
+        BatteryConfig battery2 = new BatteryConfig(1, R.drawable.ic_nothing_ear_l, R.string.left_earbud);
+        BatteryConfig battery3 = new BatteryConfig(2, R.drawable.ic_nothing_ear_r, R.string.right_earbud);
+        return new BatteryConfig[]{battery1, battery2, battery3};
+    }
+
+    @Override
+    public DeviceSpecificSettings getDeviceSpecificSettings(final GBDevice device) {
+        final DeviceSpecificSettings deviceSpecificSettings = new DeviceSpecificSettings();
+        deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.TOUCH_OPTIONS);
+        deviceSpecificSettings.addSubScreen(DeviceSpecificSettingsScreen.TOUCH_OPTIONS, R.xml.devicesettings_sony_headphones_ambient_sound_control_button_modes);
+        deviceSpecificSettings.addSubScreen(DeviceSpecificSettingsScreen.TOUCH_OPTIONS, R.xml.devicesettings_soundcore_touch_options);
+        deviceSpecificSettings.addRootScreen(R.xml.devicesettings_soundcore_headphones);
+        return deviceSpecificSettings;
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return SoundcoreLiberty3ProDeviceSupport.class;
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/soundcore/liberty4_nc/SoundcoreLiberty4NCCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/soundcore/liberty4_nc/SoundcoreLiberty4NCCoordinator.java
new file mode 100644
index 0000000..a630071
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/soundcore/liberty4_nc/SoundcoreLiberty4NCCoordinator.java
@@ -0,0 +1,83 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.soundcore.liberty4_nc;
+
+import androidx.annotation.NonNull;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.GBException;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettings;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsScreen;
+import nodomain.freeyourgadget.gadgetbridge.devices.AbstractDeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.BatteryConfig;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.soundcore.liberty.SoundcoreLiberty4NCDeviceSupport;
+
+public class SoundcoreLiberty4NCCoordinator extends AbstractDeviceCoordinator {
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_soundcore_liberty4_nc;
+    }
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_galaxy_buds;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_galaxy_buds_disabled;
+    }
+
+    @Override
+    public String getManufacturer() {
+        return "Anker";
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("soundcore Liberty 4 NC");
+    }
+
+    @Override
+    public int getBondingStyle(){
+        return BONDING_STYLE_NONE;
+    }
+
+    @Override
+    protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
+    }
+
+
+    @Override
+    public int getBatteryCount() {
+        return 3;
+    }
+
+    @Override
+    public BatteryConfig[] getBatteryConfig(final GBDevice device) {
+        BatteryConfig battery1 = new BatteryConfig(0, R.drawable.ic_buds_pro_case, R.string.battery_case);
+        BatteryConfig battery2 = new BatteryConfig(1, R.drawable.ic_nothing_ear_l, R.string.left_earbud);
+        BatteryConfig battery3 = new BatteryConfig(2, R.drawable.ic_nothing_ear_r, R.string.right_earbud);
+        return new BatteryConfig[]{battery1, battery2, battery3};
+    }
+
+    @Override
+    public DeviceSpecificSettings getDeviceSpecificSettings(final GBDevice device) {
+        final DeviceSpecificSettings deviceSpecificSettings = new DeviceSpecificSettings();
+        deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.TOUCH_OPTIONS);
+        deviceSpecificSettings.addSubScreen(DeviceSpecificSettingsScreen.TOUCH_OPTIONS, R.xml.devicesettings_sony_headphones_ambient_sound_control_button_modes);
+        deviceSpecificSettings.addSubScreen(DeviceSpecificSettingsScreen.TOUCH_OPTIONS, R.xml.devicesettings_soundcore_touch_options);
+        deviceSpecificSettings.addRootScreen(R.xml.devicesettings_soundcore_headphones);
+        return deviceSpecificSettings;
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return SoundcoreLiberty4NCDeviceSupport.class;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/soundcore/motion300/SoundcoreMotion300Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/soundcore/motion300/SoundcoreMotion300Coordinator.java
new file mode 100644
index 0000000..1299e86
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/soundcore/motion300/SoundcoreMotion300Coordinator.java
@@ -0,0 +1,93 @@
+/*  Copyright (C) 2024 Severin von Wnuck-Lipinski
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.soundcore.motion300;
+
+import androidx.annotation.NonNull;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.GBException;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettings;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsScreen;
+import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLClassicDeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.soundcore.motion300.SoundcoreMotion300DeviceSupport;
+
+public class SoundcoreMotion300Coordinator extends AbstractBLClassicDeviceCoordinator {
+    @Override
+    public String getManufacturer() {
+        return "Anker";
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("soundcore Motion 300");
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_soundcore_motion300;
+    }
+
+    @Override
+    public DeviceSpecificSettingsCustomizer getDeviceSpecificSettingsCustomizer(final GBDevice device) {
+        return new SoundcoreMotion300SettingsCustomizer();
+    }
+
+    @Override
+    public int getBatteryCount() {
+        return 1;
+    }
+
+    @Override
+    public boolean supportsPowerOff() {
+        return true;
+    }
+
+    @Override
+    protected void deleteDevice(
+            @NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session)
+            throws GBException {}
+
+    @Override
+    public DeviceSpecificSettings getDeviceSpecificSettings(final GBDevice device) {
+        final DeviceSpecificSettings settings = new DeviceSpecificSettings();
+
+        settings.addRootScreen(R.xml.devicesettings_soundcore_motion300);
+        settings.addRootScreen(DeviceSpecificSettingsScreen.AUDIO);
+        settings.addSubScreen(
+                DeviceSpecificSettingsScreen.AUDIO,
+                R.xml.devicesettings_soundcore_motion300_audio);
+        settings.addRootScreen(DeviceSpecificSettingsScreen.CALLS_AND_NOTIFICATIONS);
+        settings.addSubScreen(
+                DeviceSpecificSettingsScreen.CALLS_AND_NOTIFICATIONS,
+                R.xml.devicesettings_headphones);
+
+        return settings;
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return SoundcoreMotion300DeviceSupport.class;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/soundcore/motion300/SoundcoreMotion300SettingsCustomizer.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/soundcore/motion300/SoundcoreMotion300SettingsCustomizer.java
new file mode 100644
index 0000000..799c636
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/soundcore/motion300/SoundcoreMotion300SettingsCustomizer.java
@@ -0,0 +1,117 @@
+/*  Copyright (C) 2024 Severin von Wnuck-Lipinski
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.soundcore.motion300;
+
+import android.os.Parcel;
+import androidx.preference.ListPreference;
+import androidx.preference.Preference;
+import androidx.preference.SeekBarPreference;
+
+import java.util.Collections;
+import java.util.Set;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsHandler;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.soundcore.motion300.SoundcoreMotion300Protocol;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.*;
+
+public class SoundcoreMotion300SettingsCustomizer implements DeviceSpecificSettingsCustomizer {
+    public static final Creator<SoundcoreMotion300SettingsCustomizer> CREATOR = new Creator<SoundcoreMotion300SettingsCustomizer>() {
+        @Override
+        public SoundcoreMotion300SettingsCustomizer createFromParcel(final Parcel in) {
+            return new SoundcoreMotion300SettingsCustomizer();
+        }
+
+        @Override
+        public SoundcoreMotion300SettingsCustomizer[] newArray(final int size) {
+            return new SoundcoreMotion300SettingsCustomizer[size];
+        }
+    };
+
+    @Override
+    public void onPreferenceChange(final Preference preference, final DeviceSpecificSettingsHandler handler) {
+        if (!preference.getKey().equals(PREF_SOUNDCORE_EQUALIZER_PRESET))
+            return;
+
+        CharSequence preset = ((ListPreference)preference).getEntry();
+
+        if (preset == null)
+            return;
+
+        Preference pref = handler.findPreference(PREF_SOUNDCORE_EQUALIZER_CUSTOM);
+        boolean customEnabled = preset.toString().equals(handler.getContext().getString(R.string.custom));
+
+        if (pref != null)
+            pref.setEnabled(customEnabled);
+    }
+
+    @Override
+    public void customizeSettings(final DeviceSpecificSettingsHandler handler, final Prefs prefs, final String rootKey) {
+        ListPreference equalizerDirection = handler.findPreference(PREF_SOUNDCORE_EQUALIZER_DIRECTION);
+
+        if (equalizerDirection != null) {
+            boolean enabled = prefs.getBoolean(PREF_SOUNDCORE_ADAPTIVE_DIRECTION, true);
+
+            equalizerDirection.setVisible(enabled);
+        }
+
+        Preference equalizerReset = handler.findPreference(PREF_SOUNDCORE_EQUALIZER_RESET);
+
+        if (equalizerReset != null)
+            equalizerReset.setOnPreferenceClickListener(pref -> resetEqualizer(handler));
+    }
+
+    private boolean resetEqualizer(final DeviceSpecificSettingsHandler handler) {
+        // Reset all bands to default settings
+        for (int i = 0; i < SoundcoreMotion300Protocol.EQUALIZER_PREFS_FREQ.length; i++) {
+            ListPreference prefFreq = handler.findPreference(SoundcoreMotion300Protocol.EQUALIZER_PREFS_FREQ[i]);
+            SeekBarPreference prefValue = handler.findPreference(SoundcoreMotion300Protocol.EQUALIZER_PREFS_VALUE[i]);
+
+            // Neutral configuration
+            prefValue.setValue(60);
+
+            // Default configuration: 80 Hz, 150 Hz, 300 Hz, 600 Hz, 1.2 kHz, 2.5 kHz, 5 kHz, 9 kHz, 13 kHz
+            if (i < 7)
+                prefFreq.setValue("7");
+            else if (i == 7)
+                prefFreq.setValue("6");
+            else if (i == 8)
+                prefFreq.setValue("1");
+        }
+
+        // Send updated equalizer configuration
+        handler.notifyPreferenceChanged(PREF_SOUNDCORE_EQUALIZER_BAND9_FREQ);
+
+        return true;
+    }
+
+    @Override
+    public Set<String> getPreferenceKeysWithSummary() {
+        return Collections.emptySet();
+    }
+
+    @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/supercars/ControlActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/supercars/ControlActivity.java
new file mode 100644
index 0000000..d80d896
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/supercars/ControlActivity.java
@@ -0,0 +1,294 @@
+/*  Copyright (C) 2022-2024 Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.supercars;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.os.CountDownTimer;
+import android.view.View;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import android.widget.ImageButton;
+import android.widget.TextView;
+
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBActivity;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.supercars.SuperCarsSupport;
+
+public class ControlActivity extends AbstractGBActivity implements JoystickView.JoystickListener {
+    private static final Logger LOG = LoggerFactory.getLogger(ControlActivity.class);
+    LocalBroadcastManager localBroadcastManager;
+    CountDownTimer periodicDataSenderRunner;
+    boolean periodicDataSenderRunnerIsRunning = false;
+
+    private GBDevice device;
+    TextView batteryPercentage;
+    boolean lights = false;
+    boolean blinking = false;
+    boolean turbo = false;
+    int stepCounter = 0;
+
+    SuperCarsConstants.Direction direction = SuperCarsConstants.Direction.CENTER;
+    SuperCarsConstants.Movement movement = SuperCarsConstants.Movement.IDLE;
+    SuperCarsConstants.Speed speed = SuperCarsConstants.Speed.NORMAL;
+    SuperCarsConstants.Light light = SuperCarsConstants.Light.OFF;
+    SuperCarsConstants.Tricks tricks = SuperCarsConstants.Tricks.OFF;
+
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_supercars_control);
+
+        Intent intent = getIntent();
+        Bundle bundle = intent.getExtras();
+        if (bundle != null) {
+            device = bundle.getParcelable(GBDevice.EXTRA_DEVICE);
+        } else {
+            throw new IllegalArgumentException("Must provide a device when invoking this activity");
+        }
+
+        CheckBox turboMode = findViewById(R.id.turboMode);
+        CheckBox lightsOn = findViewById(R.id.lightsOn);
+        CheckBox lightsBlinking = findViewById(R.id.lightsBlinking);
+
+        batteryPercentage = findViewById(R.id.battery_percentage_label);
+        setBatteryLabel();
+
+        localBroadcastManager = LocalBroadcastManager.getInstance(ControlActivity.this);
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(GBDevice.ACTION_DEVICE_CHANGED);
+        localBroadcastManager.registerReceiver(commandReceiver, filter);
+
+        turboMode.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+            @Override
+            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+                turbo = isChecked;
+            }
+        });
+
+        lightsOn.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+            @Override
+            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+                if (isChecked) {
+                    if (lightsBlinking.isChecked()) {
+                        lightsBlinking.setChecked(false);
+                        blinking = false;
+                    }
+                }
+                lights = isChecked;
+                setLights();
+            }
+        });
+
+        lightsBlinking.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+            @Override
+            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+
+                if (isChecked) {
+                    if (lightsOn.isChecked()) {
+                        lightsOn.setChecked(false);
+                        lights = false;
+                    }
+                }
+                blinking = isChecked;
+                setLights();
+            }
+        });
+
+        ImageButton trick1 = findViewById(R.id.trick1);
+        ImageButton trick2 = findViewById(R.id.trick2);
+        ImageButton trick3 = findViewById(R.id.trick3);
+        ImageButton trick4 = findViewById(R.id.trick4);
+
+        trick1.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                tricks = SuperCarsConstants.Tricks.CIRCLE_LEFT;
+                stepCounter = 0;
+            }
+        });
+
+        trick2.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                tricks = SuperCarsConstants.Tricks.CIRCLE_RIGHT;
+                stepCounter = 0;
+            }
+        });
+        trick3.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                tricks = SuperCarsConstants.Tricks.U_TURN_LEFT;
+                stepCounter = 0;
+            }
+        });
+        trick4.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                tricks = SuperCarsConstants.Tricks.U_TURN_RIGHT;
+                stepCounter = 0;
+            }
+        });
+
+        //when this activity is open, data is sent continuously every 100ms
+
+        if (!periodicDataSenderRunnerIsRunning) {
+            periodicDataSender();
+        }
+    }
+
+    private void setLights() {
+        if (!blinking) {
+            if (lights) {
+                light = SuperCarsConstants.Light.ON;
+            } else {
+                light = SuperCarsConstants.Light.OFF;
+            }
+        } else if (blinking) {
+            if (light.equals(SuperCarsConstants.Light.ON)) {
+                light = SuperCarsConstants.Light.OFF;
+            } else {
+                light = SuperCarsConstants.Light.ON;
+            }
+        }
+    }
+
+    private void setBatteryLabel() {
+        String level = device.getBatteryLevel() > 0 ? String.format("%1s%%", device.getBatteryLevel()) : device.getName();
+        batteryPercentage.setText(level);
+    }
+
+    private void sendLocalBroadcast(Intent intent) {
+        localBroadcastManager.sendBroadcast(intent);
+    }
+
+    public void periodicDataSender() {
+        periodicDataSenderRunner = new CountDownTimer(Long.MAX_VALUE, 100) {
+
+            public void onTick(long millisUntilFinished) {
+                periodicDataSenderRunnerIsRunning = true;
+                setLights();
+
+                if (tricks != SuperCarsConstants.Tricks.OFF) {
+                    Enum[][] trick_steps = SuperCarsConstants.get_trick(tricks);
+                    int steps = trick_steps.length;
+                    if (stepCounter < steps) {
+                        Enum[] step = trick_steps[stepCounter];
+                        movement = (SuperCarsConstants.Movement) step[0];
+                        direction = (SuperCarsConstants.Direction) step[1];
+                        stepCounter++;
+                    } else {
+                        tricks = SuperCarsConstants.Tricks.OFF;
+                        stepCounter = 0;
+                    }
+                }
+
+                create_intent_with_data();
+            }
+
+            public void onFinish() {
+                start();
+            }
+        };
+    }
+
+    private void create_intent_with_data() {
+        Intent intent = new Intent(SuperCarsSupport.COMMAND_DRIVE_CONTROL);
+        intent.putExtra(SuperCarsSupport.EXTRA_DIRECTION, direction);
+        intent.putExtra(SuperCarsSupport.EXTRA_MOVEMENT, movement);
+        intent.putExtra(SuperCarsSupport.EXTRA_SPEED, speed);
+        intent.putExtra(SuperCarsSupport.EXTRA_LIGHT, light);
+        sendLocalBroadcast(intent);
+    }
+
+    @Override
+    public void onJoystickMoved(float xPercent, float yPercent, int id) {
+
+        tricks = SuperCarsConstants.Tricks.OFF;
+        stepCounter = 0;
+
+        if (yPercent < 0.2 && yPercent != 0) {
+            movement = SuperCarsConstants.Movement.UP;
+        } else if (yPercent > 0.2) {
+            movement = SuperCarsConstants.Movement.DOWN;
+        } else {
+            movement = SuperCarsConstants.Movement.IDLE;
+        }
+
+        if (xPercent < -0.5) {
+            direction = SuperCarsConstants.Direction.LEFT;
+        } else if (xPercent > 0.5) {
+            direction = SuperCarsConstants.Direction.RIGHT;
+        } else {
+            direction = SuperCarsConstants.Direction.CENTER;
+        }
+
+        if (turbo) {
+            speed = SuperCarsConstants.Speed.TURBO;
+        } else {
+            speed = SuperCarsConstants.Speed.NORMAL;
+        }
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        periodicDataSenderRunner.cancel();
+        periodicDataSenderRunnerIsRunning = false;
+        LocalBroadcastManager.getInstance(this).unregisterReceiver(commandReceiver);
+    }
+
+    @Override
+    protected void onPostResume() {
+        super.onPostResume();
+        if (!periodicDataSenderRunnerIsRunning) {
+            periodicDataSenderRunner.start();
+        }
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+        periodicDataSenderRunner.cancel();
+        periodicDataSenderRunnerIsRunning = false;
+    }
+
+    BroadcastReceiver commandReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            LOG.debug("device receiver received " + intent.getAction());
+            if (intent.getAction().equals(GBDevice.ACTION_DEVICE_CHANGED)) {
+                GBDevice newDevice = intent.getParcelableExtra(GBDevice.EXTRA_DEVICE);
+                if (newDevice.equals(device)) {
+                    device = newDevice;
+                    setBatteryLabel();
+                }
+
+            }
+        }
+    };
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/supercars/JoystickView.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/supercars/JoystickView.java
new file mode 100644
index 0000000..44999e0
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/supercars/JoystickView.java
@@ -0,0 +1,126 @@
+/*  Original code taken from https://github.com/efficientisoceles/JoystickView
+    Licenced under the terms of the GNU General Public License version 3
+    Copyright (C) Daniel Su <goldenember@gmail.com>
+ */
+package nodomain.freeyourgadget.gadgetbridge.devices.supercars;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.View;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+
+public class JoystickView extends SurfaceView implements SurfaceHolder.Callback, View.OnTouchListener {
+    private float centerX;
+    private float centerY;
+    private float baseRadius;
+    private float hatRadius;
+    private JoystickListener joystickCallback;
+    private final int ratio = 5;
+
+    private void setupDimensions() {
+        centerX = getWidth() / 2;
+        centerY = getHeight() / 2;
+        baseRadius = Math.min(getWidth(), getHeight()) / 3;
+        hatRadius = Math.min(getWidth(), getHeight()) / 5;
+    }
+
+    public JoystickView(Context context) {
+        super(context);
+        getHolder().addCallback(this);
+        setOnTouchListener(this);
+        if (context instanceof JoystickListener)
+            joystickCallback = (JoystickListener) context;
+    }
+
+    public JoystickView(Context context, AttributeSet attributes, int style) {
+        super(context, attributes, style);
+        getHolder().addCallback(this);
+        setOnTouchListener(this);
+        if (context instanceof JoystickListener)
+            joystickCallback = (JoystickListener) context;
+    }
+
+    public JoystickView(Context context, AttributeSet attributes) {
+        super(context, attributes);
+        getHolder().addCallback(this);
+        setOnTouchListener(this);
+        if (context instanceof JoystickListener)
+            joystickCallback = (JoystickListener) context;
+    }
+
+    private void drawJoystick(float newX, float newY) {
+        if (getHolder().getSurface().isValid()) {
+            Canvas myCanvas = this.getHolder().lockCanvas();
+            Paint colors = new Paint();
+            myCanvas.drawColor(GBApplication.getWindowBackgroundColor(getContext()));
+
+            float hypotenuse = (float) Math.sqrt(Math.pow(newX - centerX, 2) + Math.pow(newY - centerY, 2));
+            float sin = (newY - centerY) / hypotenuse; //sin = o/h
+            float cos = (newX - centerX) / hypotenuse; //cos = a/h
+
+            colors.setColor(this.getResources().getColor(R.color.primarydark_light));
+            myCanvas.drawCircle(centerX, centerY, baseRadius, colors);
+
+            colors.setColor(this.getResources().getColor(R.color.chart_deep_sleep_light));
+
+            myCanvas.drawCircle(newX - cos * hypotenuse * (ratio / baseRadius) * 30,
+                    newY - sin * hypotenuse * (ratio / baseRadius) * 30, 100, colors);
+
+            colors.setColor(this.getResources().getColor(R.color.accent));
+            myCanvas.drawCircle(newX, newY, hatRadius, colors);
+
+            getHolder().unlockCanvasAndPost(myCanvas);
+        }
+    }
+
+    @Override
+    public void surfaceCreated(SurfaceHolder holder) {
+        setupDimensions();
+        drawJoystick(centerX, centerY);
+    }
+
+    @Override
+    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+
+    }
+
+    @Override
+    public void surfaceDestroyed(SurfaceHolder holder) {
+
+    }
+
+    public boolean onTouch(View v, MotionEvent e) {
+        if (v.equals(this)) {
+            if (e.getAction() != MotionEvent.ACTION_UP) {
+                float displacement = (float) Math.sqrt((Math.pow(e.getX() - centerX, 2)) + Math.pow(e.getY() - centerY, 2));
+                if (displacement < baseRadius) {
+                    drawJoystick(e.getX(), e.getY());
+                    joystickCallback.onJoystickMoved((e.getX() - centerX) / baseRadius, (e.getY() - centerY) / baseRadius, getId());
+                } else {
+                    float ratio = baseRadius / displacement;
+                    float constrainedX = centerX + (e.getX() - centerX) * ratio;
+                    float constrainedY = centerY + (e.getY() - centerY) * ratio;
+                    drawJoystick(constrainedX, constrainedY);
+                    joystickCallback.onJoystickMoved((constrainedX - centerX) / baseRadius, (constrainedY - centerY) / baseRadius, getId());
+                }
+            } else if (e.getAction() == MotionEvent.ACTION_UP) {
+                drawJoystick(centerX, centerY);
+                joystickCallback.onJoystickMoved(0, 0, getId());
+            } else
+                drawJoystick(centerX, centerY);
+            //joystickCallback.onJoystickMoved(0, 0, getId());
+        }
+        return true;
+    }
+
+    public interface JoystickListener {
+        void onJoystickMoved(float xPercent, float yPercent, int id);
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/supercars/SuperCarsConstants.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/supercars/SuperCarsConstants.java
new file mode 100644
index 0000000..b231e38
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/supercars/SuperCarsConstants.java
@@ -0,0 +1,101 @@
+/*  Copyright (C) 2022-2024 Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.supercars;
+
+import java.util.UUID;
+
+public class SuperCarsConstants {
+
+    //https://gist.github.com/scrool/e79d6a4cb50c26499746f4fe473b3768#encryption
+    public static final byte[] aes_key = new byte[]{(byte) 0x34, (byte) 0x52, (byte) 0x2A, (byte) 0x5B, (byte) 0x7A, (byte) 0x6E, (byte) 0x49, (byte) 0x2C, (byte) 0x08, (byte) 0x09, (byte) 0x0A, (byte) 0x9D, (byte) 0x8D, (byte) 0x2A, (byte) 0x23, (byte) 0xF8};
+
+    public static final UUID SERVICE_UUID_FFF = UUID.fromString("0000fff0-0000-1000-8000-00805f9b34fb");
+    public static final UUID CHARACTERISTIC_UUID_FFF1 = UUID.fromString("d44bc439-abfd-45a2-b575-925416129600");
+    public static final UUID CHARACTERISTIC_UUID_FFF2 = UUID.fromString("d44bc439-abfd-45a2-b575-92541612960a");
+    public static final UUID CHARACTERISTIC_UUID_FFF3 = UUID.fromString("d44bc439-abfd-45a2-b575-92541612960b");
+    public static final UUID CHARACTERISTIC_UUID_FFF4 = UUID.fromString("d44bc439-abfd-45a2-b575-925416129601");
+
+    public static final UUID SERVICE_UUID_FD = UUID.fromString("0000fd00-0000-1000-8000-00805f9b34fb");
+    public static final UUID CHARACTERISTIC_UUID_FD1 = UUID.fromString("0000fd01-0000-1000-8000-00805f9b34fb");
+    public static final UUID CHARACTERISTIC_UUID_FD2 = UUID.fromString("0000fd02-0000-1000-8000-00805f9b34fb");
+
+    public enum Speed {
+        NORMAL, TURBO
+    }
+
+    public enum Light {
+        ON, OFF
+    }
+
+    public enum Movement {
+        UP, DOWN, IDLE
+    }
+
+    public enum Direction {
+        LEFT, RIGHT, CENTER
+    }
+
+    public enum Tricks {
+        OFF, CIRCLE_RIGHT, CIRCLE_LEFT, U_TURN_LEFT, U_TURN_RIGHT
+    }
+
+    static Enum[] fwd_r = {Movement.UP, Direction.RIGHT};
+    static Enum[] fwd_l = {Movement.UP, Direction.LEFT};
+    static Enum[] stop = {Movement.IDLE, Direction.CENTER};
+
+    public static final Enum[][] tricks_circle_right = {
+            fwd_r, fwd_r, fwd_r, fwd_r, fwd_r, fwd_r, fwd_r, fwd_r, fwd_r, fwd_r,
+            fwd_r, fwd_r, fwd_r, fwd_r, fwd_r, fwd_r, fwd_r, fwd_r, fwd_r, fwd_r,
+            fwd_r, fwd_r, fwd_r, fwd_r, fwd_r, fwd_r, fwd_r, fwd_r, fwd_r, fwd_r,
+            stop
+    };
+
+    public static final Enum[][] tricks_circle_left = {
+            fwd_l, fwd_l, fwd_l, fwd_l, fwd_l, fwd_l, fwd_l, fwd_l, fwd_l, fwd_l,
+            fwd_l, fwd_l, fwd_l, fwd_l, fwd_l, fwd_l, fwd_l, fwd_l, fwd_l, fwd_l,
+            fwd_l, fwd_l, fwd_l, fwd_l, fwd_l, fwd_l, fwd_l, fwd_l, fwd_l, fwd_l,
+            stop
+    };
+
+    public static final Enum[][] tricks_u_turn_right = {
+            fwd_r, fwd_r, fwd_r, fwd_r, fwd_r, fwd_r, fwd_r, fwd_r, fwd_r, fwd_r,
+            fwd_r, fwd_r, fwd_r, fwd_r, fwd_r, fwd_r, fwd_r, fwd_r, fwd_r, fwd_r,
+            stop
+    };
+
+    public static final Enum[][] tricks_u_turn_left = {
+            fwd_l, fwd_l, fwd_l, fwd_l, fwd_l, fwd_l, fwd_l, fwd_l, fwd_l, fwd_l,
+            fwd_l, fwd_l, fwd_l, fwd_l, fwd_l, fwd_l, fwd_l, fwd_l, fwd_l, fwd_l,
+            stop
+    };
+
+
+    public static final Enum[][] get_trick(Tricks trick) {
+        switch (trick) {
+            case CIRCLE_RIGHT:
+                return tricks_circle_right;
+            case CIRCLE_LEFT:
+                return tricks_circle_left;
+            case U_TURN_LEFT:
+                return tricks_u_turn_left;
+            case U_TURN_RIGHT:
+                return tricks_u_turn_right;
+        }
+        return tricks_circle_right;
+    }
+}
+
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/supercars/SuperCarsCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/supercars/SuperCarsCoordinator.java
new file mode 100644
index 0000000..f5f4b58
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/supercars/SuperCarsCoordinator.java
@@ -0,0 +1,157 @@
+/*  Copyright (C) 2022-2024 Damien Gaignon, Daniel Dakhno, Jos茅 Rebelo,
+    Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.supercars;
+
+import android.app.Activity;
+import android.content.Context;
+import android.net.Uri;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.GBException;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.AbstractDeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.supercars.SuperCarsSupport;
+
+public class SuperCarsCoordinator extends AbstractDeviceCoordinator {
+    private static final Logger LOG = LoggerFactory.getLogger(SuperCarsCoordinator.class);
+
+    @Override
+    protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
+
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("QCAR-.*");
+    }
+
+    @Override
+    public int getBondingStyle() {
+        return BONDING_STYLE_NONE;
+    }
+
+    @Override
+    public int getBatteryCount() {
+        return 1;
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return SuperCarsSupport.class;
+    }
+
+    @Override
+    public Class<? extends Activity> getAppsManagementActivity() {
+        return ControlActivity.class;
+    }
+
+    @Nullable
+    @Override
+    public Class<? extends Activity> getPairingActivity() {
+        return null;
+    }
+
+    @Override
+    public boolean supportsActivityDataFetching() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsActivityTracking() {
+        return false;
+    }
+
+    @Override
+    public SampleProvider<? extends ActivitySample> getSampleProvider(GBDevice device, DaoSession session) {
+        return null;
+    }
+
+    @Override
+    public InstallHandler findInstallHandler(Uri uri, Context context) {
+        return null;
+    }
+
+    @Override
+    public boolean supportsScreenshots(final GBDevice device) {
+        return false;
+    }
+
+    @Override
+    public int getAlarmSlotCount(GBDevice device) {
+        return 0;
+    }
+
+    @Override
+    public String getManufacturer() {
+        return "Brand Base";
+    }
+
+    @Override
+    public boolean supportsAppsManagement(final GBDevice device) {
+        return true;
+    }
+
+    @Override
+    public boolean supportsCalendarEvents() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsRealtimeData() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsFindDevice() {
+        return false;
+    }
+
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_super_cars;
+    }
+
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_supercars;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_supercars_disabled;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/test/TestDeviceConst.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/test/TestDeviceConst.java
new file mode 100644
index 0000000..f9a3d6e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/test/TestDeviceConst.java
@@ -0,0 +1,25 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.test;
+
+public class TestDeviceConst {
+    public static final String PREF_TEST_FEATURES = "test_features";
+
+    private TestDeviceConst() {
+        // utility class
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/test/TestDeviceCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/test/TestDeviceCoordinator.java
new file mode 100644
index 0000000..9e4459f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/test/TestDeviceCoordinator.java
@@ -0,0 +1,697 @@
+/*  Copyright (C) 2023-2024 Daniel Dakhno, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.test;
+
+import android.app.Activity;
+import android.content.Context;
+import android.net.Uri;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import org.apache.commons.lang3.ArrayUtils;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.GBException;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.appmanager.AppManagerActivity;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettings;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsScreen;
+import nodomain.freeyourgadget.gadgetbridge.capabilities.HeartRateCapability;
+import nodomain.freeyourgadget.gadgetbridge.capabilities.password.PasswordCapabilityImpl;
+import nodomain.freeyourgadget.gadgetbridge.capabilities.widgets.WidgetManager;
+import nodomain.freeyourgadget.gadgetbridge.devices.AbstractDeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.TimeSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.roidmi.RoidmiConst;
+import nodomain.freeyourgadget.gadgetbridge.devices.test.activity.TestActivitySummaryParser;
+import nodomain.freeyourgadget.gadgetbridge.devices.test.samples.TestBodyEnergySampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.test.samples.TestHrvSummarySampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.test.samples.TestHrvValueSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.test.samples.TestPaiSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.test.samples.TestRespiratoryRateSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.test.samples.TestSpo2SampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.test.samples.TestStressSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.test.samples.TestSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.test.samples.TestTemperatureSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+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;
+import nodomain.freeyourgadget.gadgetbridge.model.BodyEnergySample;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.model.HeartRateSample;
+import nodomain.freeyourgadget.gadgetbridge.model.HrvSummarySample;
+import nodomain.freeyourgadget.gadgetbridge.model.HrvValueSample;
+import nodomain.freeyourgadget.gadgetbridge.model.PaiSample;
+import nodomain.freeyourgadget.gadgetbridge.model.RespiratoryRateSample;
+import nodomain.freeyourgadget.gadgetbridge.model.Spo2Sample;
+import nodomain.freeyourgadget.gadgetbridge.model.StressSample;
+import nodomain.freeyourgadget.gadgetbridge.model.TemperatureSample;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.ServiceDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.test.TestDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+public class TestDeviceCoordinator extends AbstractDeviceCoordinator {
+    @Override
+    protected void deleteDevice(@NonNull final GBDevice gbDevice,
+                                @NonNull final Device device,
+                                @NonNull final DaoSession session) throws GBException {
+        // Nothing to do
+    }
+
+    @Override
+    public boolean supports(final GBDeviceCandidate candidate) {
+        return false;
+    }
+
+    @Override
+    public String getManufacturer() {
+        return "Gadgetbridge";
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return TestDeviceSupport.class;
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_test;
+    }
+
+    @Override
+    public SampleProvider<? extends ActivitySample> getSampleProvider(final GBDevice device, final DaoSession session) {
+        return supportsActivityTracking() ? new TestSampleProvider(device, session) : super.getSampleProvider(device, session);
+    }
+
+    @Override
+    public TimeSampleProvider<? extends StressSample> getStressSampleProvider(final GBDevice device, final DaoSession session) {
+        return supportsStressMeasurement() ? new TestStressSampleProvider() : super.getStressSampleProvider(device, session);
+    }
+
+    @Override
+    public TimeSampleProvider<? extends BodyEnergySample> getBodyEnergySampleProvider(final GBDevice device, final DaoSession session) {
+        return supportsBodyEnergy() ? new TestBodyEnergySampleProvider() : super.getBodyEnergySampleProvider(device ,session);
+    }
+
+    @Override
+    public TimeSampleProvider<? extends HrvSummarySample> getHrvSummarySampleProvider(final GBDevice device, final DaoSession session) {
+        return supportsBodyEnergy() ? new TestHrvSummarySampleProvider() : super.getHrvSummarySampleProvider(device ,session);
+
+    }
+
+    @Override
+    public TimeSampleProvider<? extends HrvValueSample> getHrvValueSampleProvider(final GBDevice device, final DaoSession session) {
+        return supportsBodyEnergy() ? new TestHrvValueSampleProvider() : super.getHrvValueSampleProvider(device ,session);
+    }
+
+    @Override
+    public int[] getStressRanges() {
+        // TODO getStressRanges
+        return super.getStressRanges();
+    }
+
+    @Override
+    public TimeSampleProvider<? extends TemperatureSample> getTemperatureSampleProvider(final GBDevice device, final DaoSession session) {
+        return supportsTemperatureMeasurement() ? new TestTemperatureSampleProvider() : super.getTemperatureSampleProvider(device, session);
+    }
+
+    @Override
+    public TimeSampleProvider<? extends Spo2Sample> getSpo2SampleProvider(final GBDevice device, final DaoSession session) {
+        return supportsSpo2(device) ? new TestSpo2SampleProvider() : super.getSpo2SampleProvider(device, session);
+    }
+
+    @Override
+    public TimeSampleProvider<? extends HeartRateSample> getHeartRateMaxSampleProvider(final GBDevice device, final DaoSession session) {
+        // TODO getHeartRateMaxSampleProvider
+        return super.getHeartRateMaxSampleProvider(device, session);
+    }
+
+    @Override
+    public TimeSampleProvider<? extends HeartRateSample> getHeartRateRestingSampleProvider(final GBDevice device, final DaoSession session) {
+        // TODO getHeartRateRestingSampleProvider
+        return super.getHeartRateRestingSampleProvider(device, session);
+    }
+
+    @Override
+    public TimeSampleProvider<? extends HeartRateSample> getHeartRateManualSampleProvider(final GBDevice device, final DaoSession session) {
+        // TODO getHeartRateManualSampleProvider
+        return super.getHeartRateManualSampleProvider(device, session);
+    }
+
+    @Override
+    public TimeSampleProvider<? extends PaiSample> getPaiSampleProvider(final GBDevice device, final DaoSession session) {
+        return supportsPai() ? new TestPaiSampleProvider() : super.getPaiSampleProvider(device, session);
+    }
+
+    @Override
+    public TimeSampleProvider<? extends RespiratoryRateSample> getRespiratoryRateSampleProvider(final GBDevice device, final DaoSession session) {
+        return supportsRespiratoryRate() ? new TestRespiratoryRateSampleProvider() : super.getRespiratoryRateSampleProvider(device, session);
+    }
+
+    @Nullable
+    @Override
+    public ActivitySummaryParser getActivitySummaryParser(final GBDevice device, final Context context) {
+        return supportsActivityTracks() ? new TestActivitySummaryParser() : super.getActivitySummaryParser(device, context);
+    }
+
+    @Override
+    public File getAppCacheDir() throws IOException {
+        return new File(FileUtils.getExternalFilesDir(), "test-app-cache");
+    }
+
+    @Override
+    public String getAppCacheSortFilename() {
+        return "test-app-cache-order.txt";
+    }
+
+    @Override
+    public String getAppFileExtension() {
+        return ".txt";
+    }
+
+    @Override
+    public boolean supportsAppListFetching() {
+        return supports(getTestDevice(), TestFeature.APP_LIST_FETCHING);
+    }
+
+    @Override
+    public boolean supportsFlashing() {
+        return supports(getTestDevice(), TestFeature.FLASHING);
+    }
+
+    @Nullable
+    @Override
+    public InstallHandler findInstallHandler(final Uri uri, final Context context) {
+        // TODO findInstallHandler?
+        return super.findInstallHandler(uri, context);
+    }
+
+    @Override
+    public boolean supportsScreenshots(final GBDevice device) {
+        return supports(getTestDevice(), TestFeature.SCREENSHOTS);
+    }
+
+    @Override
+    public int getAlarmSlotCount(final GBDevice device) {
+        /*
+          0: Forced smart, no interval
+          1: Forced smart, with interval
+          2: Unforced smart, no interval
+          3: Unforced smart, with interval
+          4: Not smart
+         */
+        return 5;
+    }
+
+    @Override
+    public boolean supportsSmartWakeup(final GBDevice device, int position) {
+        return supports(getTestDevice(), TestFeature.SMART_WAKEUP) && position <= 3;
+    }
+
+    @Override
+    public boolean supportsSmartWakeupInterval(GBDevice device, int alarmPosition) {
+        return supports(getTestDevice(), TestFeature.SMART_WAKEUP_INTERVAL) && (alarmPosition == 1 || alarmPosition == 3);
+    }
+
+    @Override
+    public boolean forcedSmartWakeup(final GBDevice device, final int alarmPosition) {
+        return supports(getTestDevice(), TestFeature.SMART_WAKEUP_FORCED_SLOT) && alarmPosition <= 1;
+    }
+
+    @Override
+    public boolean supportsAppReordering() {
+        return supports(getTestDevice(), TestFeature.APP_REORDERING);
+    }
+
+    @Override
+    public boolean supportsAppsManagement(final GBDevice device) {
+        return supports(getTestDevice(), TestFeature.APPS_MANAGEMENT);
+    }
+
+    @Override
+    public boolean supportsCachedAppManagement(final GBDevice device) {
+        return supports(getTestDevice(), TestFeature.CACHED_APP_MANAGEMENT);
+    }
+
+    @Override
+    public boolean supportsInstalledAppManagement(final GBDevice device) {
+        return supports(getTestDevice(), TestFeature.INSTALLED_APP_MANAGEMENT);
+    }
+
+    @Override
+    public boolean supportsWatchfaceManagement(final GBDevice device) {
+        return supports(getTestDevice(), TestFeature.WATCHFACE_MANAGEMENT);
+    }
+
+    @Nullable
+    @Override
+    public Class<? extends Activity> getAppsManagementActivity() {
+        return AppManagerActivity.class;
+    }
+
+    @Nullable
+    @Override
+    public Class<? extends Activity> getWatchfaceDesignerActivity() {
+        // TODO getWatchfaceDesignerActivity
+        return super.getWatchfaceDesignerActivity();
+    }
+
+    @Override
+    public boolean supportsCalendarEvents() {
+        return supports(getTestDevice(), TestFeature.CALENDAR_EVENTS);
+    }
+
+    @Override
+    public boolean supportsActivityDataFetching() {
+        return supports(getTestDevice(), TestFeature.ACTIVITY_DATA_FETCHING);
+    }
+
+    @Override
+    public boolean supportsActivityTracking() {
+        return supports(getTestDevice(), TestFeature.ACTIVITY_TRACKING);
+    }
+
+    @Override
+    public boolean supportsSleepMeasurement() {
+        return supports(getTestDevice(), TestFeature.SLEEP_MEASUREMENT);
+    }
+
+    @Override
+    public boolean supportsStepCounter() {
+        return supports(getTestDevice(), TestFeature.STEP_COUNTER);
+    }
+
+    @Override
+    public boolean supportsSpeedzones() {
+        return supports(getTestDevice(), TestFeature.SPEEDZONES);
+    }
+
+    @Override
+    public boolean supportsActivityTabs() {
+        return supports(getTestDevice(), TestFeature.ACTIVITY_TABS);
+    }
+
+    @Override
+    public boolean supportsTemperatureMeasurement() {
+        return supports(getTestDevice(), TestFeature.TEMPERATURE_MEASUREMENT);
+    }
+
+    @Override
+    public boolean supportsActivityTracks() {
+        return supports(getTestDevice(), TestFeature.ACTIVITY_TRACKS);
+    }
+
+    @Override
+    public boolean supportsStressMeasurement() {
+        return supports(getTestDevice(), TestFeature.STRESS_MEASUREMENT);
+    }
+
+    @Override
+    public boolean supportsBodyEnergy() {
+        return supports(getTestDevice(), TestFeature.BODY_ENERGY);
+    }
+
+    @Override
+    public boolean supportsHrvMeasurement() {
+        return supports(getTestDevice(), TestFeature.HRV_MEASUREMENT);
+    }
+
+    @Override
+    public boolean supportsSpo2(GBDevice device) {
+        return supports(getTestDevice(), TestFeature.SPO2);
+    }
+
+    @Override
+    public boolean supportsHeartRateStats() {
+        return supports(getTestDevice(), TestFeature.HEART_RATE_STATS);
+    }
+
+    @Override
+    public boolean supportsPai() {
+        return supports(getTestDevice(), TestFeature.PAI);
+    }
+
+    @Override
+    public int getPaiName() {
+        return R.string.menuitem_pai;
+    }
+
+    @Override
+    public boolean supportsPaiTime() {
+        return supports(getTestDevice(), TestFeature.PAI_TIME);
+    }
+
+    @Override
+    public boolean supportsRespiratoryRate() {
+        return supports(getTestDevice(), TestFeature.RESPIRATORY_RATE);
+    }
+
+    @Override
+    public boolean supportsSleepRespiratoryRate() {
+        return supports(getTestDevice(), TestFeature.SLEEP_RESPIRATORY_RATE);
+    }
+
+    @Override
+    public boolean supportsAlarmSnoozing() {
+        return supports(getTestDevice(), TestFeature.ALARM_SNOOZING);
+    }
+
+    @Override
+    public boolean supportsAlarmTitle(final GBDevice device) {
+        return supports(getTestDevice(), TestFeature.ALARM_TITLE);
+    }
+
+    @Override
+    public int getAlarmTitleLimit(final GBDevice device) {
+        return supports(getTestDevice(), TestFeature.ALARM_TITLE_LIMIT) ? 10 : -1;
+    }
+
+    @Override
+    public boolean supportsAlarmDescription(final GBDevice device) {
+        return supports(getTestDevice(), TestFeature.ALARM_DESCRIPTION);
+    }
+
+    @Override
+    public boolean supportsMusicInfo() {
+        return supports(getTestDevice(), TestFeature.MUSIC_INFO);
+    }
+
+    @Override
+    public boolean supportsLedColor() {
+        return supports(getTestDevice(), TestFeature.LED_COLOR);
+    }
+
+    @Override
+    public int getMaximumReminderMessageLength() {
+        // TODO getMaximumReminderMessageLength
+        return 16;
+    }
+
+    @Override
+    public int getReminderSlotCount(final GBDevice device) {
+        return supports(getTestDevice(), TestFeature.REMINDERS) ? 3 : 0;
+    }
+
+    @Override
+    public int getCannedRepliesSlotCount(final GBDevice device) {
+        return supports(getTestDevice(), TestFeature.CANNED_REPLIES) ? 3 : 0;
+    }
+
+    @Override
+    public int getWorldClocksSlotCount() {
+        return supports(getTestDevice(), TestFeature.WORLD_CLOCKS) ? 3 : 0;
+    }
+
+    @Override
+    public int getWorldClocksLabelLength() {
+        // TODO getWorldClocksLabelLength
+        return 10;
+    }
+
+    @Override
+    public boolean supportsDisabledWorldClocks() {
+        return supports(getTestDevice(), TestFeature.DISABLED_WORLD_CLOCKS);
+    }
+
+    @Override
+    public int getContactsSlotCount(final GBDevice device) {
+        return supports(getTestDevice(), TestFeature.CONTACTS) ? 3 : 0;
+    }
+
+    @Override
+    public boolean supportsRgbLedColor() {
+        return supports(getTestDevice(), TestFeature.RGB_LED_COLOR);
+    }
+
+    @NonNull
+    @Override
+    public int[] getColorPresets() {
+        return RoidmiConst.COLOR_PRESETS;
+    }
+
+    @Override
+    public boolean supportsHeartRateMeasurement(final GBDevice device) {
+        return supports(getTestDevice(), TestFeature.HEART_RATE_MEASUREMENT);
+    }
+
+    @Override
+    public boolean supportsManualHeartRateMeasurement(final GBDevice device) {
+        return supports(getTestDevice(), TestFeature.MANUAL_HEART_RATE_MEASUREMENT);
+    }
+
+    @Override
+    public boolean supportsRealtimeData() {
+        return supports(getTestDevice(), TestFeature.REALTIME_DATA);
+    }
+
+    @Override
+    public boolean supportsRemSleep() {
+        return supports(getTestDevice(), TestFeature.REM_SLEEP);
+    }
+
+    @Override
+    public boolean supportsWeather() {
+        return supports(getTestDevice(), TestFeature.WEATHER);
+    }
+
+    @Override
+    public boolean supportsFindDevice() {
+        return supports(getTestDevice(), TestFeature.FIND_DEVICE);
+    }
+
+    @Override
+    public boolean supportsUnicodeEmojis() {
+        return supports(getTestDevice(), TestFeature.UNICODE_EMOJIS);
+    }
+
+    @Override
+    public int[] getSupportedDeviceSpecificConnectionSettings() {
+        return super.getSupportedDeviceSpecificConnectionSettings();
+    }
+
+    @Override
+    public DeviceSpecificSettings getDeviceSpecificSettings(final GBDevice device) {
+        final DeviceSpecificSettings deviceSpecificSettings = new DeviceSpecificSettings();
+
+        deviceSpecificSettings.addRootScreen(R.xml.devicesettings_loyalty_cards);
+
+        if (getWorldClocksSlotCount() > 0) {
+            final List<Integer> dateTime = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DATE_TIME);
+            dateTime.add(R.xml.devicesettings_world_clocks);
+        }
+
+        if (getContactsSlotCount(device) > 0) {
+            deviceSpecificSettings.addRootScreen(R.xml.devicesettings_contacts);
+        }
+
+        deviceSpecificSettings.addRootScreen(R.xml.devicesettings_test_features);
+
+        final List<Integer> developer = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DEVELOPER);
+        developer.add(R.xml.devicesettings_developer_add_test_activities);
+
+        return deviceSpecificSettings;
+    }
+
+    @Override
+    public int[] getSupportedDeviceSpecificAuthenticationSettings() {
+        return new int[]{R.xml.devicesettings_pairingkey};
+    }
+
+    @Override
+    public DeviceSpecificSettingsCustomizer getDeviceSpecificSettingsCustomizer(final GBDevice device) {
+        return new TestDeviceSpecificSettingsCustomizer();
+    }
+
+    @Override
+    public String[] getSupportedLanguageSettings(final GBDevice device) {
+        return new String[]{
+                "auto",
+                "en_US",
+        };
+    }
+
+    @Nullable
+    @Override
+    public Class<? extends Activity> getCalibrationActivity() {
+        // TODO getCalibrationActivity
+        return super.getCalibrationActivity();
+    }
+
+    @Override
+    public int getBatteryCount() {
+        return supports(getTestDevice(), TestFeature.BATTERIES_MULTIPLE) ? 3 : 1;
+    }
+
+    @Override
+    public BatteryConfig[] getBatteryConfig(final GBDevice device) {
+        if (getBatteryCount() == 1) {
+            return super.getBatteryConfig(device);
+        }
+
+        final BatteryConfig[] ret = new BatteryConfig[getBatteryCount()];
+
+        for (int i = 0; i < getBatteryCount(); i++) {
+            ret[i] = new BatteryConfig(i, R.drawable.ic_battery_full, R.string.battery);
+        }
+
+        return ret;
+    }
+
+    @Override
+    public boolean supportsPowerOff() {
+        return supports(getTestDevice(), TestFeature.POWER_OFF);
+    }
+
+    @Override
+    public PasswordCapabilityImpl.Mode getPasswordCapability() {
+        return super.getPasswordCapability();
+    }
+
+    @Override
+    public List<HeartRateCapability.MeasurementInterval> getHeartRateMeasurementIntervals() {
+        return super.getHeartRateMeasurementIntervals();
+    }
+
+    @Override
+    public boolean supportsWidgets(final GBDevice device) {
+        return supports(getTestDevice(), TestFeature.WIDGETS);
+    }
+
+    @Nullable
+    @Override
+    public WidgetManager getWidgetManager(final GBDevice device) {
+        return super.getWidgetManager(device);
+    }
+
+    @Override
+    public boolean supportsNavigation() {
+        return supports(getTestDevice(), TestFeature.NAVIGATION);
+    }
+
+    @Override
+    public EnumSet<ServiceDeviceSupport.Flags> getInitialFlags() {
+        return EnumSet.noneOf(ServiceDeviceSupport.Flags.class);
+    }
+
+    @Override
+    public int getDefaultIconResource() {
+        // TODO getDefaultIconResource
+        return super.getDefaultIconResource();
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        // TODO getDisabledIconResource
+        return super.getDisabledIconResource();
+    }
+
+    @Override
+    public boolean supportsNotificationVibrationPatterns() {
+        return supports(getTestDevice(), TestFeature.NOTIFICATION_VIBRATION_PATTERNS);
+    }
+
+    @Override
+    public boolean supportsNotificationVibrationRepetitionPatterns() {
+        return supports(getTestDevice(), TestFeature.NOTIFICATION_VIBRATION_REPETITION_PATTERNS);
+    }
+
+    @Override
+    public boolean supportsNotificationLedPatterns() {
+        return supports(getTestDevice(), TestFeature.NOTIFICATION_LED_PATTERNS);
+    }
+
+    @Override
+    public boolean addBatteryPollingSettings() {
+        return supports(getTestDevice(), TestFeature.BATTERY_POLLING_SETTINGS);
+    }
+
+    @Override
+    public AbstractNotificationPattern[] getNotificationVibrationPatterns() {
+        // TODO getNotificationVibrationPatterns
+        return new AbstractNotificationPattern[0];
+    }
+
+    @Override
+    public AbstractNotificationPattern[] getNotificationVibrationRepetitionPatterns() {
+        // TODO getNotificationVibrationRepetitionPatterns
+        return new AbstractNotificationPattern[0];
+    }
+
+    @Override
+    public AbstractNotificationPattern[] getNotificationLedPatterns() {
+        // TODO getNotificationLedPatterns
+        return new AbstractNotificationPattern[0];
+    }
+
+    @Override
+    public boolean validateAuthKey(final String authKey) {
+        return authKey.equals("hunter2");
+    }
+
+    public boolean supports(final GBDevice device, final TestFeature feature) {
+        if (device == null) {
+            return false;
+        }
+
+        final Set<String> features = new HashSet<>(getPrefs(device).getStringSet(TestDeviceConst.PREF_TEST_FEATURES, Collections.emptySet()));
+        // if nothing enable, enable everything
+        return features.isEmpty() || features.contains(feature.name());
+    }
+
+    @Nullable
+    private GBDevice getTestDevice() {
+        // HACK: Avoid the refactor of adding a device as a parameter on all functions
+        // This means that it's only possible to have a single test device
+
+        if (GBApplication.app() == null || GBApplication.app().getDeviceManager() == null) {
+            return null;
+        }
+
+        final List<GBDevice> devices = GBApplication.app().getDeviceManager().getDevices();
+        for (GBDevice device : devices) {
+            if (DeviceType.TEST == device.getType()) {
+                return device;
+            }
+        }
+
+        return null;
+    }
+
+    protected static Prefs getPrefs(final GBDevice device) {
+        return new Prefs(GBApplication.getDeviceSpecificSharedPrefs(device.getAddress()));
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/test/TestDeviceRand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/test/TestDeviceRand.java
new file mode 100644
index 0000000..e04d429
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/test/TestDeviceRand.java
@@ -0,0 +1,47 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.test;
+
+import java.util.Random;
+
+/**
+ * Utility class to try and get deterministic random values across multiple users. This is not efficient
+ * and should definitely be improved.
+ */
+public class TestDeviceRand {
+    public static final long BASE_TIMESTAMP = 1420499943000L;
+
+    private TestDeviceRand() {
+        // utility class
+    }
+
+    public static int randInt(final long ts, final int min, final int max) {
+        return new Random(ts - BASE_TIMESTAMP).nextInt((max - min) + 1) + min;
+    }
+
+    public static long randLong(final long ts, final long min, final long max) {
+        return (long) (new Random(ts - BASE_TIMESTAMP).nextFloat() * (max - min) + min);
+    }
+
+    public static float randFloat(final long ts, final float min, final float max) {
+        return new Random(ts - BASE_TIMESTAMP).nextFloat() * (max - min) + min;
+    }
+
+    public static boolean randBool(final long ts, final float pTrue) {
+        return new Random(ts - BASE_TIMESTAMP).nextFloat() < pTrue;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/test/TestDeviceSpecificSettingsCustomizer.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/test/TestDeviceSpecificSettingsCustomizer.java
new file mode 100644
index 0000000..30547cb
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/test/TestDeviceSpecificSettingsCustomizer.java
@@ -0,0 +1,134 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.test;
+
+import android.os.Parcel;
+import android.widget.Toast;
+
+import androidx.annotation.NonNull;
+import androidx.preference.MultiSelectListPreference;
+import androidx.preference.Preference;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.Random;
+import java.util.Set;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsHandler;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.entities.User;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+public class TestDeviceSpecificSettingsCustomizer implements DeviceSpecificSettingsCustomizer {
+    @Override
+    public void onPreferenceChange(final Preference preference, final DeviceSpecificSettingsHandler handler) {
+        if (TestDeviceConst.PREF_TEST_FEATURES.equals(preference.getKey())) {
+            handler.getDevice().sendDeviceUpdateIntent(handler.getContext());
+        }
+    }
+
+    @Override
+    public void customizeSettings(final DeviceSpecificSettingsHandler handler, final Prefs prefs, final String rootKey) {
+        final Preference pref = handler.findPreference(TestDeviceConst.PREF_TEST_FEATURES);
+        if (pref != null) {
+            // Populate the preference directly from the enum
+            final CharSequence[] entries = new CharSequence[TestFeature.values().length];
+            final CharSequence[] values = new CharSequence[TestFeature.values().length];
+            for (int i = 0; i < TestFeature.values().length; i++) {
+                entries[i] = TestFeature.values()[i].name();
+                values[i] = TestFeature.values()[i].name();
+            }
+            if (pref instanceof MultiSelectListPreference) {
+                ((MultiSelectListPreference) pref).setEntries(entries);
+                ((MultiSelectListPreference) pref).setEntryValues(values);
+            }
+        }
+
+        final Preference addTestActivities = handler.findPreference("pref_developer_add_test_activities");
+        if (addTestActivities != null) {
+            addTestActivities.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
+                @Override
+                public boolean onPreferenceClick(@NonNull final Preference preference) {
+                    try (DBHandler dbHandler = GBApplication.acquireDB()) {
+                        final DaoSession session = dbHandler.getDaoSession();
+                        final Device device = DBHelper.getDevice(handler.getDevice(), session);
+                        final User user = DBHelper.getUser(session);
+
+                        //final QueryBuilder<?> qb = session.getBaseActivitySummaryDao().queryBuilder();
+                        //qb.where(BaseActivitySummaryDao.Properties.DeviceId.eq(device.getId())).buildDelete().executeDeleteWithoutDetachingEntities();
+
+                        final List<BaseActivitySummary> summaries = new ArrayList<>();
+
+                        for (final ActivityKind activityKind : ActivityKind.values()) {
+                            final BaseActivitySummary summary = new BaseActivitySummary();
+                            summary.setStartTime(new Date(System.currentTimeMillis() - new Random().nextInt(31 * 24 * 60 * 60) * 1000L));
+                            summary.setEndTime(new Date(summary.getStartTime().getTime() + new Random().nextInt(60 * 60 * 2) * 1000L));
+                            summary.setDevice(device);
+                            summary.setUser(user);
+                            summary.setActivityKind(activityKind.getCode());
+                            // TODO data
+                            summaries.add(summary);
+                        }
+
+                        session.getBaseActivitySummaryDao().insertOrReplaceInTx(summaries);
+                    } catch (final Exception e) {
+                        GB.toast(handler.getContext(), "Error saving activity summary", Toast.LENGTH_LONG, GB.ERROR, e);
+                        return false;
+                    }
+
+                    return true;
+                }
+            });
+        }
+    }
+
+    @Override
+    public Set<String> getPreferenceKeysWithSummary() {
+        return Collections.emptySet();
+    }
+
+    public static final Creator<TestDeviceSpecificSettingsCustomizer> CREATOR = new Creator<TestDeviceSpecificSettingsCustomizer>() {
+        @Override
+        public TestDeviceSpecificSettingsCustomizer createFromParcel(final Parcel in) {
+            return new TestDeviceSpecificSettingsCustomizer();
+        }
+
+        @Override
+        public TestDeviceSpecificSettingsCustomizer[] newArray(final int size) {
+            return new TestDeviceSpecificSettingsCustomizer[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/test/TestFeature.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/test/TestFeature.java
new file mode 100644
index 0000000..5ee6d41
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/test/TestFeature.java
@@ -0,0 +1,77 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.test;
+
+public enum TestFeature {
+    ACTIVITY_DATA_FETCHING,
+    ACTIVITY_TABS,
+    ACTIVITY_TRACKING,
+    ACTIVITY_TRACKS,
+    ALARM_DESCRIPTION,
+    ALARM_SNOOZING,
+    ALARM_TITLE_LIMIT,
+    ALARM_TITLE,
+    APP_LIST_FETCHING,
+    APP_REORDERING,
+    APPS_MANAGEMENT,
+    BATTERIES_MULTIPLE,
+    CACHED_APP_MANAGEMENT,
+    CALENDAR_EVENTS,
+    CANNED_REPLIES,
+    CONTACTS,
+    DISABLED_WORLD_CLOCKS,
+    FIND_DEVICE,
+    FLASHING,
+    FM_FREQUENCY,
+    HEART_RATE_MEASUREMENT,
+    HEART_RATE_STATS,
+    INSTALLED_APP_MANAGEMENT,
+    LED_COLOR,
+    MANUAL_HEART_RATE_MEASUREMENT,
+    MUSIC_INFO,
+    NAVIGATION,
+    NOTIFICATION_LED_PATTERNS,
+    NOTIFICATION_VIBRATION_PATTERNS,
+    NOTIFICATION_VIBRATION_REPETITION_PATTERNS,
+    PAI_TIME,
+    PAI,
+    POWER_OFF,
+    REALTIME_DATA,
+    REM_SLEEP,
+    REMINDERS,
+    RGB_LED_COLOR,
+    SCREENSHOTS,
+    SLEEP_MEASUREMENT,
+    RESPIRATORY_RATE,
+    SLEEP_RESPIRATORY_RATE,
+    SMART_WAKEUP,
+    SMART_WAKEUP_INTERVAL,
+    SMART_WAKEUP_FORCED_SLOT,
+    SPEEDZONES,
+    SPO2,
+    STEP_COUNTER,
+    STRESS_MEASUREMENT,
+    BODY_ENERGY,
+    HRV_MEASUREMENT,
+    TEMPERATURE_MEASUREMENT,
+    UNICODE_EMOJIS,
+    WATCHFACE_MANAGEMENT,
+    WEATHER,
+    WIDGETS,
+    WORLD_CLOCKS,
+    BATTERY_POLLING_SETTINGS,
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/test/activity/TestActivitySummaryParser.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/test/activity/TestActivitySummaryParser.java
new file mode 100644
index 0000000..a62ceb7
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/test/activity/TestActivitySummaryParser.java
@@ -0,0 +1,27 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.test.activity;
+
+import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryParser;
+
+public class TestActivitySummaryParser implements ActivitySummaryParser {
+    @Override
+    public BaseActivitySummary parseBinaryData(final BaseActivitySummary summary, final boolean forDetails) {
+        return summary;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/test/samples/AbstractTestSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/test/samples/AbstractTestSampleProvider.java
new file mode 100644
index 0000000..ae3b3f2
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/test/samples/AbstractTestSampleProvider.java
@@ -0,0 +1,48 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.test.samples;
+
+import androidx.annotation.Nullable;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.TimeSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.model.TimeSample;
+
+public abstract class AbstractTestSampleProvider<S extends TimeSample> implements TimeSampleProvider<S> {
+    @Nullable
+    @Override
+    public S getLatestSample(final long until) {
+        final List<S> allSamples = getAllSamples(until - 60, until);
+        return !allSamples.isEmpty() ? allSamples.get(allSamples.size() - 1) : null;
+    }
+
+    @Override
+    public void addSample(final S timeSample) {
+        throw new UnsupportedOperationException("read-only sample provider");
+    }
+
+    @Override
+    public void addSamples(final List<S> timeSamples) {
+        throw new UnsupportedOperationException("read-only sample provider");
+    }
+
+    @Override
+    public S createSample() {
+        throw new UnsupportedOperationException("read-only sample provider");
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/test/samples/TestBodyEnergySampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/test/samples/TestBodyEnergySampleProvider.java
new file mode 100644
index 0000000..f754dab
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/test/samples/TestBodyEnergySampleProvider.java
@@ -0,0 +1,71 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.test.samples;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.test.TestDeviceRand;
+import nodomain.freeyourgadget.gadgetbridge.model.BodyEnergySample;
+
+public class TestBodyEnergySampleProvider extends AbstractTestSampleProvider<BodyEnergySample> {
+    @NonNull
+    @Override
+    public List<BodyEnergySample> getAllSamples(final long timestampFrom, final long timestampTo) {
+        final List<BodyEnergySample> samples = new ArrayList<>();
+
+        for (long ts = timestampFrom; ts < timestampTo; ts += 15 * 60 * 1000L) {
+            samples.add(new TestBodyEnergySample(ts));
+        }
+
+        return samples;
+    }
+
+    @Nullable
+    @Override
+    public BodyEnergySample getLatestSample() {
+        final long ts = System.currentTimeMillis();
+        return new TestBodyEnergySample(ts - TestDeviceRand.randLong(ts, 10 * 1000L, 2 * 60 * 60 * 1000L));
+    }
+
+    @Nullable
+    @Override
+    public BodyEnergySample getFirstSample() {
+        return new TestBodyEnergySample(TestDeviceRand.BASE_TIMESTAMP);
+    }
+
+    protected static class TestBodyEnergySample implements BodyEnergySample {
+        private final long timestamp;
+
+        public TestBodyEnergySample(final long timestamp) {
+            this.timestamp = timestamp;
+        }
+
+        @Override
+        public long getTimestamp() {
+            return timestamp;
+        }
+
+        @Override
+        public int getEnergy() {
+            return (int) Math.round(45 * Math.sin(timestamp / (86400000 / (2 * Math.PI))) + 55);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/test/samples/TestHrvSummarySampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/test/samples/TestHrvSummarySampleProvider.java
new file mode 100644
index 0000000..c937052
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/test/samples/TestHrvSummarySampleProvider.java
@@ -0,0 +1,115 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.test.samples;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.test.TestDeviceRand;
+import nodomain.freeyourgadget.gadgetbridge.model.HrvSummarySample;
+
+public class TestHrvSummarySampleProvider extends AbstractTestSampleProvider<HrvSummarySample> {
+    @NonNull
+    @Override
+    public List<HrvSummarySample> getAllSamples(final long timestampFrom, final long timestampTo) {
+        final List<HrvSummarySample> samples = new ArrayList<>();
+
+        for (long ts = timestampFrom; ts < timestampTo; ts += 24 * 60 * 60 * 1000L) {
+            samples.add(new TestHrvSummarySample(ts));
+        }
+
+        return samples;
+    }
+
+    @Nullable
+    @Override
+    public HrvSummarySample getLatestSample() {
+        final long ts = System.currentTimeMillis();
+        return new TestHrvSummarySample(ts - TestDeviceRand.randLong(ts, 10 * 1000L, 2 * 60 * 60 * 1000L));
+    }
+
+    @Nullable
+    @Override
+    public HrvSummarySample getFirstSample() {
+        return new TestHrvSummarySample(TestDeviceRand.BASE_TIMESTAMP);
+    }
+
+    protected static class TestHrvSummarySample implements HrvSummarySample {
+        private final long timestamp;
+        private final int weeklyAverage;
+
+        public TestHrvSummarySample(final long timestamp) {
+            this(timestamp, TestDeviceRand.randInt(timestamp, 30, 95));
+        }
+
+        public TestHrvSummarySample(final long timestamp, final int weeklyAverage) {
+            this.timestamp = timestamp;
+            this.weeklyAverage = weeklyAverage;
+        }
+
+        @Override
+        public long getTimestamp() {
+            return timestamp;
+        }
+
+        @Override
+        public Integer getWeeklyAverage() {
+            return weeklyAverage;
+        }
+
+        @Override
+        public Integer getLastNightAverage() {
+            return TestDeviceRand.randInt(timestamp, 55, 90);
+        }
+
+        @Override
+        public Integer getLastNight5MinHigh() {
+            return TestDeviceRand.randInt(timestamp, 90, 110);
+        }
+
+        @Override
+        public Integer getBaselineLowUpper() {
+            return 50;
+        }
+
+        @Override
+        public Integer getBaselineBalancedLower() {
+            return 65;
+        }
+
+        @Override
+        public Integer getBaselineBalancedUpper() {
+            return 80;
+        }
+
+        @Override
+        public Status getStatus() {
+            if (getWeeklyAverage() > getBaselineBalancedUpper()) {
+                return Status.UNBALANCED;
+            } else if (getWeeklyAverage() < getBaselineLowUpper()) {
+                return Status.POOR;
+            } else if (getWeeklyAverage() < getBaselineBalancedLower()) {
+                return Status.LOW;
+            }
+
+            return Status.BALANCED;
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/test/samples/TestHrvValueSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/test/samples/TestHrvValueSampleProvider.java
new file mode 100644
index 0000000..00d845b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/test/samples/TestHrvValueSampleProvider.java
@@ -0,0 +1,82 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.test.samples;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.test.TestDeviceRand;
+import nodomain.freeyourgadget.gadgetbridge.model.HrvValueSample;
+
+public class TestHrvValueSampleProvider extends AbstractTestSampleProvider<HrvValueSample> {
+    @NonNull
+    @Override
+    public List<HrvValueSample> getAllSamples(final long timestampFrom, final long timestampTo) {
+        final List<HrvValueSample> samples = new ArrayList<>();
+
+        int hrv = TestDeviceRand.randInt(timestampFrom, 50, 95);
+
+        for (long ts = timestampFrom; ts < timestampTo; ts += 5 * 60 * 1000L) {
+            samples.add(new TestHrvValueSample(ts, hrv));
+            hrv += TestDeviceRand.randInt(ts, (10 - hrv) / 10, (90 - hrv) / 10);
+        }
+
+        return samples;
+    }
+
+    @Nullable
+    @Override
+    public HrvValueSample getLatestSample() {
+        final long ts = System.currentTimeMillis();
+        return new TestHrvValueSample(
+                ts - TestDeviceRand.randLong(ts, 10 * 1000L, 2 * 60 * 60 * 1000L),
+                TestDeviceRand.randInt(ts, 50, 95)
+        );
+    }
+
+    @Nullable
+    @Override
+    public HrvValueSample getFirstSample() {
+        return new TestHrvValueSample(
+                TestDeviceRand.BASE_TIMESTAMP,
+                TestDeviceRand.randInt(TestDeviceRand.BASE_TIMESTAMP, 50, 95)
+        );
+    }
+
+    protected static class TestHrvValueSample implements HrvValueSample {
+        private final long timestamp;
+        private final int hrv;
+
+        public TestHrvValueSample(final long timestamp, final int hrv) {
+            this.timestamp = timestamp;
+            this.hrv = hrv;
+        }
+
+        @Override
+        public long getTimestamp() {
+            return timestamp;
+        }
+
+        @Override
+        public int getValue() {
+            return hrv;
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/test/samples/TestPaiSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/test/samples/TestPaiSampleProvider.java
new file mode 100644
index 0000000..de9c9b8
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/test/samples/TestPaiSampleProvider.java
@@ -0,0 +1,147 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.test.samples;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.test.TestDeviceRand;
+import nodomain.freeyourgadget.gadgetbridge.model.PaiSample;
+
+public class TestPaiSampleProvider extends AbstractTestSampleProvider<PaiSample> {
+    @NonNull
+    @Override
+    public List<PaiSample> getAllSamples(final long timestampFrom, final long timestampTo) {
+        final List<PaiSample> samples = new ArrayList<>();
+
+        int paiLow = TestDeviceRand.randInt(timestampFrom, 0, 15);
+        int paiMod = TestDeviceRand.randInt(timestampFrom, 0, 15);
+        int paiHigh = TestDeviceRand.randInt(timestampFrom, 0, 15);
+        int paiTotal = paiLow + paiMod + paiHigh;
+
+        for (long ts = timestampFrom; ts < timestampTo; ts += 12 * 60 * 60 * 1000L) {
+            if (TestDeviceRand.randBool(ts, 0.75f)) {
+                samples.add(new TestPaiSample(
+                        ts,
+                        paiLow,
+                        paiMod,
+                        paiHigh,
+                        paiTotal
+                ));
+            }
+            paiTotal += paiLow + paiMod + paiHigh;
+            paiLow += TestDeviceRand.randInt(ts, - paiLow, 15 - paiLow);
+            paiMod += TestDeviceRand.randInt(ts, - paiMod, 15 - paiMod);
+            paiHigh += TestDeviceRand.randInt(ts, - paiHigh, 15 - paiHigh);
+        }
+
+        return samples;
+    }
+
+    @Nullable
+    @Override
+    public PaiSample getLatestSample() {
+        final long ts = System.currentTimeMillis();
+        return new TestPaiSample(
+                ts - TestDeviceRand.randLong(ts, 10 * 1000L, 2 * 60 * 60 * 1000L),
+                TestDeviceRand.randInt(ts, 85, 99),
+                TestDeviceRand.randInt(ts, 85, 99),
+                TestDeviceRand.randInt(ts, 85, 99),
+                TestDeviceRand.randInt(ts, 85, 99)
+        );
+    }
+
+    @Nullable
+    @Override
+    public PaiSample getFirstSample() {
+        return new TestPaiSample(
+                TestDeviceRand.BASE_TIMESTAMP,
+                TestDeviceRand.randInt(TestDeviceRand.BASE_TIMESTAMP, 0, 15),
+                TestDeviceRand.randInt(TestDeviceRand.BASE_TIMESTAMP, 0, 15),
+                TestDeviceRand.randInt(TestDeviceRand.BASE_TIMESTAMP, 0, 15),
+                TestDeviceRand.randInt(TestDeviceRand.BASE_TIMESTAMP, 70, 90)
+        );
+    }
+
+    public static class TestPaiSample implements PaiSample {
+        private final long timestamp;
+        private final int paiLow;
+        private final int paiModerate;
+        private final int paiHigh;
+        private final int paiTotal;
+
+        public TestPaiSample(final long timestamp,
+                             final int low,
+                             final int moderate,
+                             final int high,
+                             final int total) {
+            this.timestamp = timestamp;
+            this.paiLow = low;
+            this.paiModerate = moderate;
+            this.paiHigh = high;
+            this.paiTotal = total;
+        }
+
+        @Override
+        public long getTimestamp() {
+            return timestamp;
+        }
+
+        @Override
+        public float getPaiLow() {
+            return paiLow;
+        }
+
+        @Override
+        public float getPaiModerate() {
+            return paiModerate;
+        }
+
+        @Override
+        public float getPaiHigh() {
+            return paiHigh;
+        }
+
+        @Override
+        public int getTimeLow() {
+            return paiLow;
+        }
+
+        @Override
+        public int getTimeModerate() {
+            return paiModerate;
+        }
+
+        @Override
+        public int getTimeHigh() {
+            return paiHigh;
+        }
+
+        @Override
+        public float getPaiToday() {
+            return paiLow + paiModerate + paiHigh;
+        }
+
+        @Override
+        public float getPaiTotal() {
+            return paiTotal;
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/test/samples/TestRespiratoryRateSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/test/samples/TestRespiratoryRateSampleProvider.java
new file mode 100644
index 0000000..dc736b1
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/test/samples/TestRespiratoryRateSampleProvider.java
@@ -0,0 +1,71 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.test.samples;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.test.TestDeviceRand;
+import nodomain.freeyourgadget.gadgetbridge.model.RespiratoryRateSample;
+
+public class TestRespiratoryRateSampleProvider extends AbstractTestSampleProvider<RespiratoryRateSample> {
+    @NonNull
+    @Override
+    public List<RespiratoryRateSample> getAllSamples(final long timestampFrom, final long timestampTo) {
+        final List<RespiratoryRateSample> samples = new ArrayList<>();
+
+        for (long ts = timestampFrom; ts < timestampTo; ts += 15 * 60 * 1000L) {
+            samples.add(new TestRespiratoryRateSample(ts));
+        }
+
+        return samples;
+    }
+
+    @Nullable
+    @Override
+    public RespiratoryRateSample getLatestSample() {
+        final long ts = System.currentTimeMillis();
+        return new TestRespiratoryRateSample(ts - TestDeviceRand.randLong(ts, 10 * 1000L, 2 * 60 * 60 * 1000L));
+    }
+
+    @Nullable
+    @Override
+    public RespiratoryRateSample getFirstSample() {
+        return new TestRespiratoryRateSample(TestDeviceRand.BASE_TIMESTAMP);
+    }
+
+    protected static class TestRespiratoryRateSample implements RespiratoryRateSample {
+        private final long timestamp;
+
+        public TestRespiratoryRateSample(final long timestamp) {
+            this.timestamp = timestamp;
+        }
+
+        @Override
+        public long getTimestamp() {
+            return timestamp;
+        }
+
+        @Override
+        public float getRespiratoryRate() {
+            return TestDeviceRand.randFloat(timestamp, 10, 15);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/test/samples/TestSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/test/samples/TestSampleProvider.java
new file mode 100644
index 0000000..7f6d188
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/test/samples/TestSampleProvider.java
@@ -0,0 +1,262 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.test.samples;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import org.apache.commons.lang3.ArrayUtils;
+
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.List;
+
+import de.greenrobot.dao.AbstractDao;
+import de.greenrobot.dao.Property;
+import nodomain.freeyourgadget.gadgetbridge.devices.AbstractSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.test.TestDeviceRand;
+import nodomain.freeyourgadget.gadgetbridge.entities.AbstractActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+
+public class TestSampleProvider extends AbstractSampleProvider<TestSampleProvider.TestActivitySample> {
+    public TestSampleProvider(final GBDevice device, final DaoSession session) {
+        super(device, session);
+    }
+
+    @Override
+    public AbstractDao<TestActivitySample, ?> getSampleDao() {
+        return null;
+    }
+
+    @Nullable
+    @Override
+    protected Property getRawKindSampleProperty() {
+        return null;
+    }
+
+    @NonNull
+    @Override
+    protected Property getTimestampSampleProperty() {
+        //noinspection DataFlowIssue not database-backed
+        return null;
+    }
+
+    @NonNull
+    @Override
+    protected Property getDeviceIdentifierSampleProperty() {
+        //noinspection DataFlowIssue not database-backed
+        return null;
+    }
+
+    @Override
+    public ActivityKind normalizeType(final int rawType) {
+        return ActivityKind.fromCode(rawType);
+    }
+
+    @Override
+    public int toRawActivityKind(final ActivityKind activityKind) {
+        return activityKind.getCode();
+    }
+
+    @Override
+    public float normalizeIntensity(final int rawIntensity) {
+        return rawIntensity / 100f;
+    }
+
+    @Override
+    public TestActivitySample createActivitySample() {
+        throw new UnsupportedOperationException("read-only sample provider");
+    }
+
+    @Override
+    protected List<TestActivitySample> getGBActivitySamples(final int timestamp_from, final int timestamp_to) {
+        final List<TestActivitySample> samples = new ArrayList<>();
+
+        int[] sleepStages = new int[] {
+                ActivityKind.LIGHT_SLEEP.getCode(),
+                ActivityKind.DEEP_SLEEP.getCode(),
+        };
+        if (getDevice().getDeviceCoordinator().supportsRemSleep()) {
+            sleepStages = ArrayUtils.add(sleepStages, ActivityKind.REM_SLEEP.getCode());
+        }
+        int sleepStageCurrent = 0;
+        int sleepStageDirection = 1;
+        int sleepStageDurationRemaining = TestDeviceRand.randInt(timestamp_from * 1000L, 30, 90);
+
+        boolean isActive = false;
+        float dayActivityFactor = TestDeviceRand.randFloat(timestamp_from * 1000L, 0f, 1f);
+        int steps = (int) (TestDeviceRand.randInt(timestamp_from * 1000L, 0, 100) * dayActivityFactor);
+        int intensity = TestDeviceRand.randInt(timestamp_from * 1000L, 0, 100);
+        int hr = TestDeviceRand.randInt(timestamp_from * 1000L, 90, 153);
+
+        final long bedtimeHour = TestDeviceRand.randInt(timestamp_from, 21, 22);
+        final long bedtimeMinute = TestDeviceRand.randInt(timestamp_from, 0, 59);
+        final long wakeHour = TestDeviceRand.randInt(timestamp_from, 6, 9);
+        final long wakeMinute = TestDeviceRand.randInt(timestamp_from, 0, 59);
+
+        final Calendar cal = GregorianCalendar.getInstance();
+
+        for (long ts = timestamp_from * 1000L; ts < timestamp_to * 1000L; ts += 60 * 1000L) {
+            cal.setTimeInMillis(ts);
+            final int h = cal.get(Calendar.HOUR_OF_DAY);
+            final int m = cal.get(Calendar.MINUTE);
+            boolean isSleep = false;
+            if (h == bedtimeHour) {
+                isSleep = m >= bedtimeMinute;
+            } else if (h > bedtimeHour) {
+                isSleep = true;
+            } else if (h == wakeHour) {
+                isSleep = m <= wakeMinute;
+            } else if (h < wakeHour) {
+                isSleep = true;
+            }
+            if (isSleep) {
+                isActive = false;
+            } else if (isActive) {
+                isActive = TestDeviceRand.randBool(ts, 0.8F);
+            } else {
+                isActive = TestDeviceRand.randBool(ts, 0.05F);
+            }
+
+            if (TestDeviceRand.randBool(ts, 0.85f)) {
+                samples.add(new TestActivitySample(
+                        (int) (ts / 1000),
+                        isSleep ? sleepStages[sleepStageCurrent] : ActivityKind.UNKNOWN.getCode(),
+                        isActive ? steps : 0,
+                        intensity,
+                        hr
+                ));
+            }
+
+            if (isSleep) {
+                sleepStageDurationRemaining--;
+                if (sleepStageDurationRemaining <= 0) {
+                    sleepStageDurationRemaining = TestDeviceRand.randInt(ts * 1000L, 30, 90);
+                    sleepStageCurrent += sleepStageDirection;
+                    if (sleepStageCurrent == 0 || sleepStageCurrent == sleepStages.length - 1) {
+                        sleepStageDirection *= -1;
+                    }
+                }
+            }
+
+            steps += (int) (TestDeviceRand.randInt(ts, -steps, 100 - steps) * dayActivityFactor);
+            intensity += TestDeviceRand.randInt(ts, -1, 1);
+            hr += TestDeviceRand.randInt(ts, -2, 2);
+        }
+
+        return samples;
+    }
+
+    public class TestActivitySample extends AbstractActivitySample {
+        private final int timestamp;
+        private final int kind;
+        private final int steps;
+        private final int intensity;
+        private final int hr;
+
+        public TestActivitySample(final int timestamp,
+                                  final int kind,
+                                  final int steps,
+                                  final int intensity,
+                                  final int hr) {
+            this.timestamp = timestamp;
+            this.kind = kind;
+            this.steps = steps;
+            this.intensity = intensity;
+            this.hr = hr;
+        }
+
+        @Override
+        public SampleProvider<?> getProvider() {
+            return TestSampleProvider.this;
+        }
+
+        @Override
+        public void setTimestamp(final int timestamp) {
+
+        }
+
+        @Override
+        public void setUserId(final long userId) {
+
+        }
+
+        @Override
+        public void setDeviceId(final long deviceId) {
+
+        }
+
+        @Override
+        public long getDeviceId() {
+            return 0;
+        }
+
+        @Override
+        public long getUserId() {
+            return 0;
+        }
+
+        @Override
+        public int getTimestamp() {
+            return timestamp;
+        }
+
+        @Override
+        public ActivityKind getKind() {
+            return ActivityKind.fromCode(kind);
+        }
+
+        @Override
+        public int getRawKind() {
+            return kind;
+        }
+
+        @Override
+        public float getIntensity() {
+            return intensity / 100f;
+        }
+
+        @Override
+        public int getHeartRate() {
+            return hr;
+        }
+
+        @Override
+        public int getRawIntensity() {
+            return intensity;
+        }
+
+        @Override
+        public int getSteps() {
+            return steps;
+        }
+
+        @Override
+        public int getDistanceCm() {
+            return steps * 67;
+        }
+
+        @Override
+        public int getActiveCalories() {
+            return (int) Math.round(steps * 0.04);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/test/samples/TestSpo2SampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/test/samples/TestSpo2SampleProvider.java
new file mode 100644
index 0000000..2e66f79
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/test/samples/TestSpo2SampleProvider.java
@@ -0,0 +1,89 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.test.samples;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.test.TestDeviceRand;
+import nodomain.freeyourgadget.gadgetbridge.model.Spo2Sample;
+
+public class TestSpo2SampleProvider extends AbstractTestSampleProvider<Spo2Sample> {
+    @NonNull
+    @Override
+    public List<Spo2Sample> getAllSamples(final long timestampFrom, final long timestampTo) {
+        final List<Spo2Sample> samples = new ArrayList<>();
+
+        int spo2 = TestDeviceRand.randInt(timestampFrom, 85, 99);
+
+        for (long ts = timestampFrom; ts < timestampTo; ts += 15 * 60 * 1000L) {
+            if (TestDeviceRand.randBool(ts, 0.3f)) {
+                samples.add(new TestSpo2Sample(ts, spo2));
+            }
+            spo2 += TestDeviceRand.randInt(ts, 85 - spo2, 99 - spo2);
+        }
+
+        return samples;
+    }
+
+    @Nullable
+    @Override
+    public Spo2Sample getLatestSample() {
+        final long ts = System.currentTimeMillis();
+        return new TestSpo2Sample(
+                ts - TestDeviceRand.randLong(ts, 10 * 1000L, 2 * 60 * 60 * 1000L),
+                TestDeviceRand.randInt(ts, 85, 99)
+        );
+    }
+
+    @Nullable
+    @Override
+    public Spo2Sample getFirstSample() {
+        return new TestSpo2Sample(
+                TestDeviceRand.BASE_TIMESTAMP,
+                TestDeviceRand.randInt(TestDeviceRand.BASE_TIMESTAMP, 85, 99)
+        );
+    }
+
+    protected static class TestSpo2Sample implements Spo2Sample {
+        private final long timestamp;
+        private final int spo2;
+
+        public TestSpo2Sample(final long timestamp, final int spo2) {
+            this.timestamp = timestamp;
+            this.spo2 = spo2;
+        }
+
+        @Override
+        public long getTimestamp() {
+            return timestamp;
+        }
+
+        @Override
+        public Type getType() {
+            return Type.UNKNOWN;
+        }
+
+        @Override
+        public int getSpo2() {
+            return spo2;
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/test/samples/TestStressSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/test/samples/TestStressSampleProvider.java
new file mode 100644
index 0000000..5a890c3
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/test/samples/TestStressSampleProvider.java
@@ -0,0 +1,89 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.test.samples;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.test.TestDeviceRand;
+import nodomain.freeyourgadget.gadgetbridge.model.StressSample;
+
+public class TestStressSampleProvider extends AbstractTestSampleProvider<StressSample> {
+    @NonNull
+    @Override
+    public List<StressSample> getAllSamples(final long timestampFrom, final long timestampTo) {
+        final List<StressSample> samples = new ArrayList<>();
+
+        int stress = TestDeviceRand.randInt(timestampFrom, 10, 90);
+
+        for (long ts = timestampFrom; ts < timestampTo; ts += 15 * 60 * 1000L) {
+            if (TestDeviceRand.randBool(ts, 0.3f)) {
+                samples.add(new TestStressSample(ts, stress));
+            }
+            stress += TestDeviceRand.randInt(ts, (10 - stress) / 10, (90 - stress) / 10);
+        }
+
+        return samples;
+    }
+
+    @Nullable
+    @Override
+    public StressSample getLatestSample() {
+        final long ts = System.currentTimeMillis();
+        return new TestStressSample(
+                ts - TestDeviceRand.randLong(ts, 10 * 1000L, 2 * 60 * 60 * 1000L),
+                TestDeviceRand.randInt(ts, 10, 90)
+        );
+    }
+
+    @Nullable
+    @Override
+    public StressSample getFirstSample() {
+        return new TestStressSample(
+                TestDeviceRand.BASE_TIMESTAMP,
+                TestDeviceRand.randInt(TestDeviceRand.BASE_TIMESTAMP, 10, 90)
+        );
+    }
+
+    protected static class TestStressSample implements StressSample {
+        private final long timestamp;
+        private final int stress;
+
+        public TestStressSample(final long timestamp, final int stress) {
+            this.timestamp = timestamp;
+            this.stress = stress;
+        }
+
+        @Override
+        public long getTimestamp() {
+            return timestamp;
+        }
+
+        @Override
+        public Type getType() {
+            return Type.UNKNOWN;
+        }
+
+        @Override
+        public int getStress() {
+            return stress;
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/test/samples/TestTemperatureSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/test/samples/TestTemperatureSampleProvider.java
new file mode 100644
index 0000000..6e7005f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/test/samples/TestTemperatureSampleProvider.java
@@ -0,0 +1,87 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.test.samples;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.test.TestDeviceRand;
+import nodomain.freeyourgadget.gadgetbridge.model.TemperatureSample;
+
+public class TestTemperatureSampleProvider extends AbstractTestSampleProvider<TemperatureSample> {
+    @NonNull
+    @Override
+    public List<TemperatureSample> getAllSamples(final long timestampFrom, final long timestampTo) {
+        final List<TemperatureSample> samples = new ArrayList<>();
+
+        int temp = TestDeviceRand.randInt(timestampFrom, 33, 40);
+
+        for (long ts = timestampFrom; ts < timestampTo; ts += 120 * 60 * 1000L) {
+            if (TestDeviceRand.randBool(ts, 0.3f)) {
+                samples.add(new TestTemperatureSample(ts, temp));
+            }
+            temp += TestDeviceRand.randInt(ts, 33 - temp, 40 - temp);
+            break;
+        }
+
+        return samples;
+    }
+
+    @Nullable
+    @Override
+    public TemperatureSample getLatestSample() {
+        // TODO
+        return null;
+    }
+
+    @Nullable
+    @Override
+    public TemperatureSample getFirstSample() {
+        return new TestTemperatureSample(
+                TestDeviceRand.BASE_TIMESTAMP,
+                TestDeviceRand.randFloat(TestDeviceRand.BASE_TIMESTAMP, 36f, 38f)
+        );
+    }
+
+    protected static class TestTemperatureSample implements TemperatureSample {
+        private final long timestamp;
+        private final float temperature;
+
+        public TestTemperatureSample(final long timestamp, final float temperature) {
+            this.timestamp = timestamp;
+            this.temperature = temperature;
+        }
+
+        @Override
+        public long getTimestamp() {
+            return timestamp;
+        }
+
+        @Override
+        public float getTemperature() {
+            return temperature;
+        }
+
+        @Override
+        public int getTemperatureType() {
+            return 0; // ?
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/tlw64/TLW64Constants.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/tlw64/TLW64Constants.java
new file mode 100644
index 0000000..cf0127d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/tlw64/TLW64Constants.java
@@ -0,0 +1,62 @@
+/*  Copyright (C) 2020-2024 115ek
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+
+package nodomain.freeyourgadget.gadgetbridge.devices.tlw64;
+
+import java.util.UUID;
+
+public final class TLW64Constants {
+
+    public static final UUID UUID_SERVICE_NO1            = UUID.fromString("000055ff-0000-1000-8000-00805f9b34fb");
+    public static final UUID UUID_CHARACTERISTIC_CONTROL = UUID.fromString("000033f1-0000-1000-8000-00805f9b34fb");
+    public static final UUID UUID_CHARACTERISTIC_NOTIFY  = UUID.fromString("000033f2-0000-1000-8000-00805f9b34fb");
+
+    // Command bytes
+    public static final byte CMD_DISPLAY_SETTINGS = (byte) 0xa0;
+    public static final byte CMD_FIRMWARE_VERSION = (byte) 0xa1;
+    public static final byte CMD_BATTERY          = (byte) 0xa2;
+    public static final byte CMD_DATETIME         = (byte) 0xa3;
+    public static final byte CMD_USER_DATA        = (byte) 0xa9;
+    public static final byte CMD_ALARM            = (byte) 0xab;
+    public static final byte CMD_FACTORY_RESET    = (byte) 0xad;
+    public static final byte CMD_FETCH_STEPS      = (byte) 0xb2;
+    public static final byte CMD_FETCH_SLEEP      = (byte) 0xb3;
+    public static final byte CMD_NOTIFICATION     = (byte) 0xc1;
+    public static final byte CMD_ICON             = (byte) 0xc3;
+    public static final byte CMD_DEVICE_SETTINGS  = (byte) 0xd3;
+
+    // Notifications
+    public static final byte NOTIFICATION_HEADER = (byte) 0x01;
+    public static final byte NOTIFICATION_CALL   = (byte) 0x02;    // displays "call" on screen
+    public static final byte NOTIFICATION_SMS    = (byte) 0x03;    // displays "mms" on screen
+    public static final byte NOTIFICATION_STOP   = (byte) 0x04;    // to stop showing incoming call
+
+    // Icons
+    public static final byte ICON_QQ     = (byte) 0x01;
+    public static final byte ICON_WECHAT = (byte) 0x02;
+    public static final byte ICON_MAIL   = (byte) 0x04;
+
+    // Alarm arguments
+    public static final byte ARG_SET_ALARM_REMINDER_REPEAT_SUNDAY    =  (byte) 0x01;
+    public static final byte ARG_SET_ALARM_REMINDER_REPEAT_MONDAY    =  (byte) 0x02;
+    public static final byte ARG_SET_ALARM_REMINDER_REPEAT_TUESDAY   =  (byte) 0x04;
+    public static final byte ARG_SET_ALARM_REMINDER_REPEAT_WEDNESDAY =  (byte) 0x08;
+    public static final byte ARG_SET_ALARM_REMINDER_REPEAT_THURSDAY  =  (byte) 0x10;
+    public static final byte ARG_SET_ALARM_REMINDER_REPEAT_FRIDAY    =  (byte) 0x20;
+    public static final byte ARG_SET_ALARM_REMINDER_REPEAT_SATURDAY  =  (byte) 0x40;
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/tlw64/TLW64Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/tlw64/TLW64Coordinator.java
new file mode 100644
index 0000000..13d5292
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/tlw64/TLW64Coordinator.java
@@ -0,0 +1,151 @@
+/*  Copyright (C) 2020-2024 115ek, Damien Gaignon, Daniel Dakhno, Jos茅
+    Rebelo, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+
+package nodomain.freeyourgadget.gadgetbridge.devices.tlw64;
+
+import android.app.Activity;
+import android.content.Context;
+import android.net.Uri;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.GBException;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLEDeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.tlw64.TLW64Support;
+
+public class TLW64Coordinator extends AbstractBLEDeviceCoordinator {
+
+    @Override
+    protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("Smart Bee.*");
+    }
+
+    @Nullable
+    @Override
+    public Class<? extends Activity> getPairingActivity() {
+        return null;
+    }
+
+    @Override
+    public boolean supportsActivityDataFetching() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsActivityTracking() {
+        return true;
+    }
+
+    @Override
+    public SampleProvider<? extends ActivitySample> getSampleProvider(GBDevice device, DaoSession session) {
+        return new TLW64SampleProvider(device, session);
+    }
+
+    @Override
+    public InstallHandler findInstallHandler(Uri uri, Context context) {
+        return null;
+    }
+
+    @Override
+    public boolean supportsScreenshots(final GBDevice device) {
+        return false;
+    }
+
+    @Override
+    public int getAlarmSlotCount(GBDevice device) {
+        return 3;
+    }
+
+    @Override
+    public boolean supportsHeartRateMeasurement(GBDevice device) {
+        return false;
+    }
+
+    @Override
+    public String getManufacturer() {
+        return "Toleda";
+    }
+
+    @Override
+    public boolean supportsAppsManagement(final GBDevice device) {
+        return false;
+    }
+
+    @Override
+    public Class<? extends Activity> getAppsManagementActivity() {
+        return null;
+    }
+
+    @Override
+    public boolean supportsCalendarEvents() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsRealtimeData() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsWeather() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsFindDevice() {
+        return true;
+    }
+
+    @Override
+    public int[] getSupportedDeviceSpecificSettings(GBDevice device) {
+        return new int[]{
+                R.xml.devicesettings_liftwrist_display_noshed,
+                R.xml.devicesettings_inactivity_noshed,
+                R.xml.devicesettings_timeformat,
+                R.xml.devicesettings_transliteration
+        };
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return TLW64Support.class;
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_tlw64;
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/tlw64/TLW64SampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/tlw64/TLW64SampleProvider.java
new file mode 100644
index 0000000..8b3de42
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/tlw64/TLW64SampleProvider.java
@@ -0,0 +1,85 @@
+/*  Copyright (C) 2020-2024 115ek
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.tlw64;
+
+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.TLW64ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.entities.TLW64ActivitySampleDao;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+
+public class TLW64SampleProvider extends AbstractSampleProvider<TLW64ActivitySample> {
+
+    private GBDevice mDevice;
+    private DaoSession mSession;
+
+    public TLW64SampleProvider(GBDevice device, DaoSession session) {
+        super(device, session);
+
+        mSession = session;
+        mDevice = device;
+    }
+
+    @Override
+    public AbstractDao<TLW64ActivitySample, ?> getSampleDao() {
+        return getSession().getTLW64ActivitySampleDao();
+    }
+
+    @Nullable
+    @Override
+    protected Property getRawKindSampleProperty() {
+        return TLW64ActivitySampleDao.Properties.RawKind;
+    }
+
+    @NonNull
+    @Override
+    protected Property getTimestampSampleProperty() {
+        return TLW64ActivitySampleDao.Properties.Timestamp;
+    }
+
+    @NonNull
+    @Override
+    protected Property getDeviceIdentifierSampleProperty() {
+        return TLW64ActivitySampleDao.Properties.DeviceId;
+    }
+
+    @Override
+    public ActivityKind normalizeType(int rawType) {
+        return ActivityKind.fromCode(rawType);
+    }
+
+    @Override
+    public int toRawActivityKind(ActivityKind activityKind) {
+        return activityKind.getCode();
+    }
+
+    @Override
+    public float normalizeIntensity(int rawIntensity) {
+        return rawIntensity / (float) 4000.0;
+    }
+
+    @Override
+    public TLW64ActivitySample createActivitySample() {
+        return new TLW64ActivitySample();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/um25/Activity/DataActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/um25/Activity/DataActivity.java
new file mode 100644
index 0000000..1fe9666
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/um25/Activity/DataActivity.java
@@ -0,0 +1,151 @@
+/*  Copyright (C) 2020-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.um25.Activity;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import java.lang.reflect.Field;
+import java.util.HashMap;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBActivity;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.um25.Data.MeasurementData;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.um25.Support.UM25Support;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class DataActivity extends AbstractGBActivity {
+    private HashMap<Integer, TextView> valueViews = new HashMap<>(ValueDisplay.values().length);
+
+    private TextView chargeDurationTextView;
+
+    private enum ValueDisplay{
+        VOLTAGE("voltage", "%.3fV", R.id.um25_text_voltage, 1000),
+        CURRENT("current", "%.4fA", R.id.um25_text_current, 10000),
+        CURRENT_SUM("chargedCurrent", "%.0fmAh", R.id.um25_text_current_sum, 1),
+        WATTAGE("wattage", "%.3fW", R.id.um25_text_wattage, 1000),
+        WATTAGE_SUM("chargedWattage", "%.3fWh", R.id.um25_text_wattage_sum, 1000),
+        TEMPERATURE_CELCIUS("temperatureCelcius", "%.0f掳", R.id.um25_text_temperature, 1),
+        CABLE_RESISTANCE("cableResistance", "%.1f惟", R.id.um25_cable_resistance, 10),
+        ;
+
+        private String variableName;
+        private String formatString;
+        private int textViewResource;
+        private float divisor;
+
+        ValueDisplay(String variableName, String formatString, int textViewResource, float divisor) {
+            this.variableName = variableName;
+            this.formatString = formatString;
+            this.textViewResource = textViewResource;
+            this.divisor = divisor;
+        }
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_um25_data);
+
+        chargeDurationTextView = findViewById(R.id.um25_text_charge_duration);
+        TextView wattHoursTextView = findViewById(R.id.um25_text_wattage_sum);
+        TextView currentAccumulatedTextView = findViewById(R.id.um25_text_current_sum);
+
+        View.OnLongClickListener longClickListener = new View.OnLongClickListener() {
+            @Override
+            public boolean onLongClick(View v) {
+                GB.toast("resetting", Toast.LENGTH_SHORT, GB.INFO);
+                LocalBroadcastManager.getInstance(DataActivity.this).sendBroadcast(
+                        new Intent(UM25Support.ACTION_RESET_STATS)
+                );
+                return true;
+            }
+        };
+
+        chargeDurationTextView.setOnLongClickListener(longClickListener);
+        wattHoursTextView.setOnLongClickListener(longClickListener);
+        currentAccumulatedTextView.setOnLongClickListener(longClickListener);
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        LocalBroadcastManager.getInstance(this)
+                .registerReceiver(
+                        measurementReceiver,
+                        new IntentFilter(UM25Support.ACTION_MEASUREMENT_TAKEN)
+                );
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+        LocalBroadcastManager.getInstance(this)
+                .unregisterReceiver(measurementReceiver);
+        for(TextView view : valueViews.values()){
+            view.setText("-");
+        }
+    }
+
+    private void displayMeasurementData(MeasurementData data){
+        for(ValueDisplay display : ValueDisplay.values()){
+            try {
+                TextView textView = valueViews.get(display.textViewResource);
+                if(textView == null){
+                    valueViews.put(display.textViewResource, textView = findViewById(display.textViewResource));
+                }
+                Field field = data.getClass().getDeclaredField(display.variableName);
+                field.setAccessible(true);
+                float value = ((int) field.get(data)) / display.divisor;
+                String result = String.format(display.formatString, value);
+                textView.setText(result);
+            } catch (NoSuchFieldException | IllegalAccessException e) {
+                e.printStackTrace();
+            }
+        }
+
+        int durationSeconds = data.getChargingSeconds();
+        int hours = durationSeconds / 3600;
+        int minutes = durationSeconds % 3600 / 60;
+        int seconds = durationSeconds % 60;
+
+        String timeString = String.format("%02d:%02d:%02d", hours, minutes, seconds);
+
+        int thresholdCurrent = data.getThresholdCurrent();
+        int current = data.getCurrent() / 10;
+
+        chargeDurationTextView.setTextColor(current > thresholdCurrent ? 0xff669900 : 0xffcc0000);
+        chargeDurationTextView.setText(timeString);
+    }
+
+    private BroadcastReceiver measurementReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            MeasurementData data = (MeasurementData) intent.getSerializableExtra(UM25Support.EXTRA_KEY_MEASUREMENT_DATA);
+            displayMeasurementData(data);
+        }
+    };
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/um25/Coordinator/UM25Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/um25/Coordinator/UM25Coordinator.java
new file mode 100644
index 0000000..122bd05
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/um25/Coordinator/UM25Coordinator.java
@@ -0,0 +1,120 @@
+/*  Copyright (C) 2020-2024 Damien Gaignon, Daniel Dakhno, Jos茅 Rebelo,
+    Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.um25.Coordinator;
+
+import android.app.Activity;
+import android.bluetooth.le.ScanFilter;
+import android.content.Context;
+import android.net.Uri;
+import android.os.ParcelUuid;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.Collection;
+import java.util.Collections;
+
+import nodomain.freeyourgadget.gadgetbridge.GBException;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLEDeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.um25.Activity.DataActivity;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.um25.Support.UM25Support;
+
+public class UM25Coordinator extends AbstractBLEDeviceCoordinator {
+    @Override
+    protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
+
+    }
+
+    @NonNull
+    @Override
+    public Collection<? extends ScanFilter> createBLEScanFilters() {
+        return Collections.singletonList(
+                new ScanFilter.Builder()
+                        .setServiceUuid(ParcelUuid.fromString(UM25Support.UUID_SERVICE))
+                        .build()
+        );
+    }
+
+    @NonNull
+    @Override
+    public boolean supports(GBDeviceCandidate candidate) {
+        if(!"UM25C".equals(candidate.getName())) return false;
+        for(ParcelUuid service : candidate.getServiceUuids()){
+            if(service.getUuid().toString().equals(UM25Support.UUID_SERVICE)) return true;
+        }
+        return false;
+    }
+
+    @Override
+    public int[] getSupportedDeviceSpecificSettings(GBDevice device) {
+        return new int[]{
+                R.xml.devicesettings_um25
+        };
+    }
+
+    @Override
+    public InstallHandler findInstallHandler(Uri uri, Context context) {
+        return null;
+    }
+
+    @Override
+    public String getManufacturer() {
+        return "Ruideng";
+    }
+
+    @Override
+    public boolean supportsAppsManagement(final GBDevice device) {
+        return true;
+    }
+
+    @Override
+    public Class<? extends Activity> getAppsManagementActivity() {
+        return DataActivity.class;
+    }
+
+    @Override
+    public int getBatteryCount() {
+        return 0;
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return UM25Support.class;
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_um25;
+    }
+
+    @Override
+    public int getBondingStyle() {
+        return BONDING_STYLE_NONE;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/vesc/VescControlActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/vesc/VescControlActivity.java
new file mode 100644
index 0000000..197d93c
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/vesc/VescControlActivity.java
@@ -0,0 +1,251 @@
+/*  Copyright (C) 2021-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.vesc;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import android.widget.EditText;
+
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBActivity;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.vesc.VescDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+public class VescControlActivity extends AbstractGBActivity {
+    private static final String TAG = "VescControlActivity";
+    private boolean volumeKeyPressed = false;
+    private boolean volumeKeysControl = false;
+    private int currentRPM = 0;
+    private int currentBreakCurrentMa = 0;
+    LocalBroadcastManager localBroadcastManager;
+
+    private Logger logger = LoggerFactory.getLogger(getClass());
+
+    EditText rpmEditText, breakCurrentEditText;
+
+    private final int DELAY_SAVE = 1000;
+
+    Prefs preferences;
+
+    final String PREFS_KEY_LAST_RPM = "VESC_LAST_RPM";
+    final String PREFS_KEY_LAST_BREAK_CURRENT = "VESC_LAST_BREAK_CURRENT";
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_vesc_control);
+
+        localBroadcastManager = LocalBroadcastManager.getInstance(this);
+
+        preferences = GBApplication.getPrefs();
+
+        initViews();
+
+        restoreValues();
+    }
+
+    private void restoreValues(){
+        rpmEditText.setText(String.valueOf(preferences.getInt(PREFS_KEY_LAST_RPM, 0)));
+        breakCurrentEditText.setText(String.valueOf(preferences.getInt(PREFS_KEY_LAST_BREAK_CURRENT, 0) / 1000));
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+        setCurrent(0);
+    }
+
+    private boolean handleKeyPress(int keyCode, boolean isPressed) {
+        if (!volumeKeysControl) {
+            return false;
+        }
+
+        if (keyCode != 24 && keyCode != 25) {
+            return false;
+        }
+
+        if (volumeKeyPressed == isPressed) {
+            return true;
+        }
+        volumeKeyPressed = isPressed;
+
+        logger.debug("volume " + (keyCode == 25 ? "down" : "up") + (isPressed ? " pressed" : " released"));
+        if (!isPressed) {
+            setCurrent(0);
+            return true;
+        }
+        if (keyCode == 24) {
+            setRPM(currentRPM);
+        } else {
+            setBreakCurrent(VescControlActivity.this.currentBreakCurrentMa);
+        }
+
+        return true;
+    }
+
+    Runnable rpmSaveRunnable = new Runnable() {
+        @Override
+        public void run() {
+            preferences.getPreferences().edit().putInt(PREFS_KEY_LAST_RPM, currentRPM).apply();
+        }
+    };
+
+    Runnable breakCurrentSaveRunnable = new Runnable() {
+        @Override
+        public void run() {
+            preferences.getPreferences().edit().putInt(PREFS_KEY_LAST_BREAK_CURRENT, currentBreakCurrentMa).apply();
+        }
+    };
+
+    private void initViews() {
+        ((CheckBox) findViewById(R.id.vesc_control_checkbox_volume_keys))
+                .setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+                    @Override
+                    public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+                        VescControlActivity.this.volumeKeysControl = isChecked;
+                        if (!isChecked) {
+                            setRPM(0);
+                        }
+                    }
+                });
+
+        rpmEditText = ((EditText) findViewById(R.id.vesc_control_input_rpm));
+        rpmEditText.addTextChangedListener(new TextWatcher() {
+            @Override
+            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+
+            }
+
+            @Override
+            public void onTextChanged(CharSequence s, int start, int before, int count) {
+
+            }
+
+            @Override
+            public void afterTextChanged(Editable s) {
+                rpmEditText.removeCallbacks(rpmSaveRunnable);
+                rpmEditText.postDelayed(rpmSaveRunnable, DELAY_SAVE);
+
+                String text = s.toString();
+                if (text.isEmpty()) {
+                    currentRPM = 0;
+                    return;
+                }
+                VescControlActivity.this.currentRPM = Integer.parseInt(text);
+            }
+        });
+
+        breakCurrentEditText = ((EditText) findViewById(R.id.vesc_control_input_break_current));
+        breakCurrentEditText.addTextChangedListener(new TextWatcher() {
+            @Override
+            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+
+            }
+
+            @Override
+            public void onTextChanged(CharSequence s, int start, int before, int count) {
+
+            }
+
+            @Override
+            public void afterTextChanged(Editable s) {
+                breakCurrentEditText.removeCallbacks(breakCurrentSaveRunnable);
+                breakCurrentEditText.postDelayed(breakCurrentSaveRunnable, DELAY_SAVE);
+
+                String text = s.toString();
+                if (text.isEmpty()) {
+                    currentBreakCurrentMa = 0;
+                    return;
+                }
+                try {
+                    VescControlActivity.this.currentBreakCurrentMa = Integer.parseInt(text) * 1000;
+                }catch (NumberFormatException e){
+                    VescControlActivity.this.currentBreakCurrentMa = 0;
+                }
+            }
+        });
+
+        View.OnTouchListener controlTouchListener = new View.OnTouchListener() {
+            @Override
+            public boolean onTouch(View v, MotionEvent event) {
+                if (event.getAction() == MotionEvent.ACTION_DOWN) {
+                    if (v.getId() == R.id.vesc_control_button_fwd) {
+                        setRPM(VescControlActivity.this.currentRPM);
+                    } else {
+                        setBreakCurrent(VescControlActivity.this.currentBreakCurrentMa);
+                    }
+                } else if (event.getAction() == MotionEvent.ACTION_UP) {
+                    setCurrent(0);
+                } else {
+                    return false;
+                }
+                return true;
+            }
+        };
+
+        findViewById(R.id.vesc_control_button_fwd).setOnTouchListener(controlTouchListener);
+        findViewById(R.id.vesc_control_button_break).setOnTouchListener(controlTouchListener);
+    }
+
+    private void setBreakCurrent(int breakCurrentMa) {
+        logger.debug("setting break current to {}", breakCurrentMa);
+        Intent intent = new Intent(VescDeviceSupport.COMMAND_SET_BREAK_CURRENT);
+        intent.putExtra(VescDeviceSupport.EXTRA_CURRENT, breakCurrentMa);
+        sendLocalBroadcast(intent);
+    }
+
+    private void setCurrent(int currentMa) {
+        logger.debug("setting current to {}", currentMa);
+        Intent intent = new Intent(VescDeviceSupport.COMMAND_SET_CURRENT);
+        intent.putExtra(VescDeviceSupport.EXTRA_CURRENT, currentMa);
+        sendLocalBroadcast(intent);
+    }
+
+    private void setRPM(int rpm) {
+        logger.debug("setting rpm to {}", rpm);
+        Intent intent = new Intent(VescDeviceSupport.COMMAND_SET_RPM);
+        intent.putExtra(VescDeviceSupport.EXTRA_RPM, rpm);
+        sendLocalBroadcast(intent);
+    }
+
+    private void sendLocalBroadcast(Intent intent) {
+        localBroadcastManager.sendBroadcast(intent);
+    }
+
+    @Override
+    public boolean onKeyUp(int keyCode, KeyEvent event) {
+        return handleKeyPress(keyCode, false);
+    }
+
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        return handleKeyPress(keyCode, true);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/vesc/VescCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/vesc/VescCoordinator.java
new file mode 100644
index 0000000..12d0ba1
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/vesc/VescCoordinator.java
@@ -0,0 +1,184 @@
+/*  Copyright (C) 2021-2024 Damien Gaignon, Daniel Dakhno, Jos茅 Rebelo,
+    Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.vesc;
+
+import android.app.Activity;
+import android.content.Context;
+import android.net.Uri;
+import android.os.ParcelUuid;
+
+import androidx.annotation.NonNull;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.GBException;
+import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLEDeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.vesc.VescDeviceSupport;
+
+public class VescCoordinator extends AbstractBLEDeviceCoordinator {
+    public final static String UUID_SERVICE_SERIAL_HM10 = "0000ffe0-0000-1000-8000-00805f9b34fb";
+    public final static String UUID_CHARACTERISTIC_SERIAL_TX_HM10 = "0000ffe1-0000-1000-8000-00805f9b34fb";
+    public final static String UUID_CHARACTERISTIC_SERIAL_RX_HM10 = "0000ffe1-0000-1000-8000-00805f9b34fb";
+
+    public final static String UUID_SERVICE_SERIAL_NRF = "0000ffe0-0000-1000-8000-00805f9b34fb";
+    public final static String UUID_CHARACTERISTIC_SERIAL_TX_NRF = "0000ffe0-0000-1000-8000-00805f9b34fb";
+    public final static String UUID_CHARACTERISTIC_SERIAL_RX_NRF = "0000ffe1-0000-1000-8000-00805f9b34fb";
+
+
+    @Override
+    protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
+
+    }
+
+    @Override
+    public int[] getSupportedDeviceSpecificSettings(GBDevice device) {
+        return new int[]{
+            R.xml.devicesettings_vesc
+        };
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return VescDeviceSupport.class;
+    }
+
+    @Override
+    public boolean supports(GBDeviceCandidate candidate) {
+        if(!candidate.getName().toLowerCase().contains("vesc")){
+            return false;
+        }
+        ParcelUuid[] uuids = candidate.getServiceUuids();
+        for(ParcelUuid uuid : uuids){
+            if(uuid.getUuid().toString().equals(UUID_SERVICE_SERIAL_NRF)){
+                return true;
+            }else if(uuid.getUuid().toString().equals(UUID_SERVICE_SERIAL_HM10)){
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public Class<? extends Activity> getPairingActivity() {
+        return null;
+    }
+
+    @Override
+    public boolean supportsActivityDataFetching() {
+        return true;
+    }
+
+    @Override
+    public int getBondingStyle() {
+        return BONDING_STYLE_NONE;
+    }
+
+    @Override
+    public boolean supportsActivityTracking() {
+        return false;
+    }
+
+    @Override
+    public SampleProvider<? extends ActivitySample> getSampleProvider(GBDevice device, DaoSession session) {
+        return null;
+    }
+
+    @Override
+    public InstallHandler findInstallHandler(Uri uri, Context context) {
+        return null;
+    }
+
+    @Override
+    public boolean supportsScreenshots(final GBDevice device) {
+        return false;
+    }
+
+    @Override
+    public int getAlarmSlotCount(GBDevice device) {
+        return 0;
+    }
+
+    @Override
+    public boolean supportsHeartRateMeasurement(GBDevice device) {
+        return false;
+    }
+
+    @Override
+    public String getManufacturer() {
+        return "Benjamin Vedder";
+    }
+
+    @Override
+    public boolean supportsAppsManagement(final GBDevice device) {
+        return true;
+    }
+
+    @Override
+    public Class<? extends Activity> getAppsManagementActivity() {
+        return VescControlActivity.class;
+    }
+
+    @Override
+    public boolean supportsCalendarEvents() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsRealtimeData() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsWeather() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsFindDevice() {
+        return false;
+    }
+
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_vesc;
+    }
+
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_vesc;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_vesc_disabled;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/vibratissimo/VibratissimoCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/vibratissimo/VibratissimoCoordinator.java
new file mode 100644
index 0000000..08bf613
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/vibratissimo/VibratissimoCoordinator.java
@@ -0,0 +1,156 @@
+/*  Copyright (C) 2016-2024 Andreas Shimokawa, Carsten Pfeiffer, Damien
+    Gaignon, Daniel Dakhno, Daniele Gobbetti, Jos茅 Rebelo, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.vibratissimo;
+
+import android.app.Activity;
+import android.content.Context;
+import android.net.Uri;
+
+import androidx.annotation.NonNull;
+
+import java.util.EnumSet;
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLEDeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.ServiceDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.vibratissimo.VibratissimoSupport;
+
+public class VibratissimoCoordinator extends AbstractBLEDeviceCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("Vibratissimo.*");
+    }
+
+    @Override
+    public Class<? extends Activity> getPairingActivity() {
+        return null;
+    }
+
+    @Override
+    public InstallHandler findInstallHandler(Uri uri, Context context) {
+        return null;
+    }
+
+    @Override
+    public boolean supportsActivityDataFetching() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsActivityTracking() {
+        return false;
+    }
+
+    @Override
+    public SampleProvider<? extends ActivitySample> getSampleProvider(GBDevice device, DaoSession session) {
+        return null;
+    }
+
+    @Override
+    public boolean supportsScreenshots(final GBDevice device) {
+        return false;
+    }
+
+    @Override
+    public int getAlarmSlotCount(GBDevice device) {
+        return 0;
+    }
+
+    @Override
+    public boolean supportsHeartRateMeasurement(GBDevice device) {
+        return false;
+    }
+
+    @Override
+    public String getManufacturer() {
+        return "Amor AG";
+    }
+
+    @Override
+    public boolean supportsAppsManagement(final GBDevice device) {
+        return false;
+    }
+
+    @Override
+    public Class<? extends Activity> getAppsManagementActivity() {
+        return null;
+    }
+
+    @Override
+    public boolean supportsCalendarEvents() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsRealtimeData() {
+        return false; // hmmm well, it has a temperature sensor :D
+    }
+
+    @Override
+    public boolean supportsWeather() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsFindDevice() {
+        return true;
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return VibratissimoSupport.class;
+    }
+
+    @Override
+    public EnumSet<ServiceDeviceSupport.Flags> getInitialFlags() {
+        return EnumSet.of(ServiceDeviceSupport.Flags.THROTTLING, ServiceDeviceSupport.Flags.BUSY_CHECKING);
+    }
+
+    @Override
+    protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) {
+        // nothing to delete, yet
+    }
+
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_vibratissimo;
+    }
+
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_lovetoy;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_lovetoy_disabled;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/vivomovehr/GarminCapability.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/vivomovehr/GarminCapability.java
new file mode 100644
index 0000000..25663c6
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/vivomovehr/GarminCapability.java
@@ -0,0 +1,183 @@
+/*  Copyright (C) 2023-2024 Petr Kadlec
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.vivomovehr;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+public enum GarminCapability {
+    CONNECT_MOBILE_FIT_LINK,
+    GOLF_FIT_LINK,
+    VIVOKID_JR_FIT_LINK,
+    SYNC,
+    DEVICE_INITIATES_SYNC,
+    HOST_INITIATED_SYNC_REQUESTS,
+    GNCS,
+    ADVANCED_MUSIC_CONTROLS,
+    FIND_MY_PHONE,
+    FIND_MY_WATCH,
+    CONNECTIQ_HTTP,
+    CONNECTIQ_SETTINGS,
+    CONNECTIQ_WATCH_APP_DOWNLOAD,
+    CONNECTIQ_WIDGET_DOWNLOAD,
+    CONNECTIQ_WATCH_FACE_DOWNLOAD,
+    CONNECTIQ_DATA_FIELD_DOWNLOAD,
+    CONNECTIQ_APP_MANAGEMENT,
+    COURSE_DOWNLOAD,
+    WORKOUT_DOWNLOAD,
+    GOLF_COURSE_DOWNLOAD,
+    DELTA_SOFTWARE_UPDATE_FILES,
+    FITPAY,
+    LIVETRACK,
+    LIVETRACK_AUTO_START,
+    LIVETRACK_MESSAGING,
+    GROUP_LIVETRACK,
+    WEATHER_CONDITIONS,
+    WEATHER_ALERTS,
+    GPS_EPHEMERIS_DOWNLOAD,
+    EXPLICIT_ARCHIVE,
+    SWING_SENSOR,
+    SWING_SENSOR_REMOTE,
+    INCIDENT_DETECTION,
+    TRUEUP,
+    INSTANT_INPUT,
+    SEGMENTS,
+    AUDIO_PROMPT_LAP,
+    AUDIO_PROMPT_PACE_SPEED,
+    AUDIO_PROMPT_HEART_RATE,
+    AUDIO_PROMPT_POWER,
+    AUDIO_PROMPT_NAVIGATION,
+    AUDIO_PROMPT_CADENCE,
+    SPORT_GENERIC,
+    SPORT_RUNNING,
+    SPORT_CYCLING,
+    SPORT_TRANSITION,
+    SPORT_FITNESS_EQUIPMENT,
+    SPORT_SWIMMING,
+    STOP_SYNC_AFTER_SOFTWARE_UPDATE,
+    CALENDAR,
+    WIFI_SETUP,
+    SMS_NOTIFICATIONS,
+    BASIC_MUSIC_CONTROLS,
+    AUDIO_PROMPTS_SPEECH,
+    DELTA_SOFTWARE_UPDATES,
+    GARMIN_DEVICE_INFO_FILE_TYPE,
+    SPORT_PROFILE_SETUP,
+    HSA_SUPPORT,
+    SPORT_STRENGTH,
+    SPORT_CARDIO,
+    UNION_PAY,
+    IPASS,
+    CIQ_AUDIO_CONTENT_PROVIDER,
+    UNION_PAY_INTERNATIONAL,
+    REQUEST_PAIR_FLOW,
+    LOCATION_UPDATE,
+    LTE_SUPPORT,
+    DEVICE_DRIVEN_LIVETRACK_SUPPORT,
+    CUSTOM_CANNED_TEXT_LIST_SUPPORT,
+    EXPLORE_SYNC,
+    INCIDENT_DETECT_AND_ASSISTANCE,
+    CURRENT_TIME_REQUEST_SUPPORT,
+    CONTACTS_SUPPORT,
+    LAUNCH_REMOTE_CIQ_APP_SUPPORT,
+    DEVICE_MESSAGES,
+    WAYPOINT_TRANSFER,
+    MULTI_LINK_SERVICE,
+    OAUTH_CREDENTIALS,
+    GOLF_9_PLUS_9,
+    ANTI_THEFT_ALARM,
+    INREACH,
+    EVENT_SHARING,
+    UNK_82,
+    UNK_83,
+    UNK_84,
+    UNK_85,
+    UNK_86,
+    UNK_87,
+    UNK_88,
+    UNK_89,
+    UNK_90,
+    UNK_91,
+    REALTIME_SETTINGS,
+    UNK_93,
+    UNK_94,
+    UNK_95,
+    UNK_96,
+    UNK_97,
+    UNK_98,
+    UNK_99,
+    UNK_100,
+    UNK_101,
+    UNK_102,
+    UNK_103,
+    ;
+
+    public static final Set<GarminCapability> ALL_CAPABILITIES = new HashSet<>(values().length);
+    private static final Map<Integer, GarminCapability> FROM_ORDINAL = new HashMap<>(values().length);
+
+    static {
+        for (final GarminCapability cap : values()) {
+            FROM_ORDINAL.put(cap.ordinal(), cap);
+            ALL_CAPABILITIES.add(cap);
+        }
+    }
+
+    public static Set<GarminCapability> setFromBinary(final byte[] bytes) {
+        final Set<GarminCapability> result = new HashSet<>(GarminCapability.values().length);
+        int current = 0;
+        for (int b : bytes) {
+            for (int i = 0; i < 8; i++) {
+                if ((b & (1 << i)) != 0) {
+                    if (FROM_ORDINAL.containsKey(current)) {
+                        result.add(FROM_ORDINAL.get(current));
+                    }
+                }
+                ++current;
+            }
+        }
+        return result;
+    }
+
+    public static byte[] setToBinary(final Set<GarminCapability> capabilities) {
+        final GarminCapability[] values = values();
+        final byte[] result = new byte[(values.length + 7) / 8];
+        int bytePos = 0;
+        int bitPos = 0;
+        for (int i = 0; i < values.length; ++i) {
+            if (capabilities.contains(FROM_ORDINAL.get(i))) {
+                result[bytePos] |= (1 << bitPos);
+            }
+            ++bitPos;
+            if (bitPos >= 8) {
+                bitPos = 0;
+                ++bytePos;
+            }
+        }
+        return result;
+    }
+
+    public static String setToString(final Set<GarminCapability> capabilities) {
+        final StringBuilder result = new StringBuilder();
+        for (final GarminCapability cap : capabilities) {
+            if (result.length() > 0) result.append(", ");
+            result.append(cap.name());
+        }
+        return result.toString();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/vivomovehr/VivomoveHrSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/vivomovehr/VivomoveHrSampleProvider.java
new file mode 100644
index 0000000..d6b29cf
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/vivomovehr/VivomoveHrSampleProvider.java
@@ -0,0 +1,235 @@
+/*  Copyright (C) 2023-2024 Petr Kadlec
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.vivomovehr;
+
+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.VivomoveHrActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.entities.VivomoveHrActivitySampleDao;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+
+public class VivomoveHrSampleProvider extends AbstractSampleProvider<VivomoveHrActivitySample> {
+    public static final int RAW_TYPE_KIND_MASK = 0x0F000000;
+    public static final int RAW_TYPE_KIND_ACTIVITY = 0x00000000;
+    public static final int RAW_TYPE_KIND_SLEEP = 0x01000000;
+    // public static final int RAW_TYPE_KIND_NOT_WORN = 0x0F000000;
+    public static final int RAW_NOT_WORN = 0x0F000000;
+
+    public VivomoveHrSampleProvider(GBDevice device, DaoSession session) {
+        super(device, session);
+    }
+
+    @Override
+    public ActivityKind normalizeType(int rawType) {
+        if (rawType == RAW_NOT_WORN) {
+            return ActivityKind.NOT_WORN;
+        }
+
+        switch (rawType & RAW_TYPE_KIND_MASK) {
+            case RAW_TYPE_KIND_ACTIVITY:
+                return normalizeActivityType(rawType & ~RAW_TYPE_KIND_MASK);
+            case RAW_TYPE_KIND_SLEEP:
+                return normalizeSleepType(rawType & ~RAW_TYPE_KIND_MASK);
+            default:
+                // ???
+                return ActivityKind.UNKNOWN;
+        }
+    }
+
+    private static ActivityKind normalizeActivityType(int rawType) {
+        switch (rawType) {
+            case 0:
+                // generic
+                return ActivityKind.ACTIVITY;
+            case 1:
+                // running
+                return ActivityKind.RUNNING;
+            case 2:
+                // cycling
+                return ActivityKind.CYCLING;
+            case 3:
+                // transition
+                return ActivityKind.VIVOMOVE_HR_TRANSITION;
+            case 4:
+                // fitness_equipment
+                return ActivityKind.EXERCISE;
+            case 5:
+                // swimming
+                return ActivityKind.SWIMMING;
+            case 6:
+                // walking
+                return ActivityKind.WALKING;
+            case 8:
+                // sedentary
+                // TODO?
+                return ActivityKind.ACTIVITY;
+
+            default:
+                return ActivityKind.UNKNOWN;
+        }
+    }
+
+    private static ActivityKind normalizeSleepType(int rawType) {
+        switch (rawType) {
+            case 0:
+                // deep_sleep
+                return ActivityKind.DEEP_SLEEP;
+            case 1:
+                // light_sleep
+                return ActivityKind.LIGHT_SLEEP;
+            case 2:
+                // awake
+            case 3:
+                // more_awake
+                return ActivityKind.ACTIVITY;
+            default:
+                // ?
+                return ActivityKind.UNKNOWN;
+        }
+    }
+
+    @Override
+    public int toRawActivityKind(ActivityKind activityKind) {
+        switch (activityKind) {
+            case NOT_WORN:
+                return RAW_NOT_WORN;
+
+            case ACTIVITY:
+                // generic
+                //noinspection PointlessBitwiseExpression
+                return RAW_TYPE_KIND_ACTIVITY | 0;
+            case RUNNING:
+                // running
+                return RAW_TYPE_KIND_ACTIVITY | 1;
+            case CYCLING:
+                // cycling
+                return RAW_TYPE_KIND_ACTIVITY | 2;
+            case VIVOMOVE_HR_TRANSITION:
+                return RAW_TYPE_KIND_ACTIVITY | 3;
+            case EXERCISE:
+                // fitness_equipment
+                return RAW_TYPE_KIND_ACTIVITY | 4;
+            case SWIMMING:
+                // swimming
+                return RAW_TYPE_KIND_ACTIVITY | 5;
+            case WALKING:
+                // walking
+                return RAW_TYPE_KIND_ACTIVITY | 6;
+            case LIGHT_SLEEP:
+                return RAW_TYPE_KIND_SLEEP | 1;
+            case DEEP_SLEEP:
+                //noinspection PointlessBitwiseExpression
+                return RAW_TYPE_KIND_SLEEP | 0;
+            default:
+                return 0;
+        }
+    }
+
+    @Override
+    public float normalizeIntensity(int rawIntensity) {
+        return rawIntensity / 255.0f;
+    }
+
+    @Override
+    public VivomoveHrActivitySample createActivitySample() {
+        return new VivomoveHrActivitySample();
+    }
+
+    @Override
+    public AbstractDao<VivomoveHrActivitySample, ?> getSampleDao() {
+        return getSession().getVivomoveHrActivitySampleDao();
+    }
+
+    @Nullable
+    @Override
+    protected Property getRawKindSampleProperty() {
+        return VivomoveHrActivitySampleDao.Properties.RawKind;
+    }
+
+    @NonNull
+    @Override
+    protected Property getTimestampSampleProperty() {
+        return VivomoveHrActivitySampleDao.Properties.Timestamp;
+    }
+
+    @NonNull
+    @Override
+    protected Property getDeviceIdentifierSampleProperty() {
+        return VivomoveHrActivitySampleDao.Properties.DeviceId;
+    }
+
+    public static String rawKindToString(int rawType) {
+        if (rawType == RAW_NOT_WORN) {
+            return "not worn";
+        }
+
+        switch (rawType & RAW_TYPE_KIND_MASK) {
+            case RAW_TYPE_KIND_ACTIVITY:
+                return activityTypeToString(rawType & ~RAW_TYPE_KIND_MASK);
+            case RAW_TYPE_KIND_SLEEP:
+                return sleepTypeToString(rawType & ~RAW_TYPE_KIND_MASK);
+            default:
+                // ???
+                return "unknown " + rawType;
+        }
+    }
+
+    private static String activityTypeToString(int rawType) {
+        switch (rawType) {
+            case 0:
+                return "generic";
+            case 1:
+                return "running";
+            case 2:
+                return "cycling";
+            case 3:
+                return "transition";
+            case 4:
+                return "fitness equipment";
+            case 5:
+                return "swimming";
+            case 6:
+                return "walking";
+            case 8:
+                return "sedentary";
+            default:
+                return "unknown activity " + rawType;
+        }
+    }
+
+    private static String sleepTypeToString(int rawType) {
+        switch (rawType) {
+            case 0:
+                return "deep sleep";
+            case 1:
+                return "light sleep";
+            case 2:
+                // awake
+                return "awake";
+            case 3:
+                // more_awake
+                return "more awake";
+            default:
+                return "unknown sleep " + rawType;
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/waspos/WaspOSConstants.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/waspos/WaspOSConstants.java
new file mode 100644
index 0000000..8ea8901
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/waspos/WaspOSConstants.java
@@ -0,0 +1,28 @@
+/*  Copyright (C) 2020-2024 Daniel Thompson
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.waspos;
+
+import java.util.UUID;
+
+public final class WaspOSConstants {
+
+
+    public static final UUID UUID_SERVICE_NORDIC_UART = UUID.fromString("6e400001-b5a3-f393-e0a9-e50e24dcca9e");
+    public static final UUID UUID_CHARACTERISTIC_NORDIC_UART_TX = UUID.fromString("6e400002-b5a3-f393-e0a9-e50e24dcca9e");
+    public static final UUID UUID_CHARACTERISTIC_NORDIC_UART_RX = UUID.fromString("6e400003-b5a3-f393-e0a9-e50e24dcca9e");
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/waspos/WaspOSCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/waspos/WaspOSCoordinator.java
new file mode 100644
index 0000000..eedff2a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/waspos/WaspOSCoordinator.java
@@ -0,0 +1,173 @@
+/*  Copyright (C) 2020-2024 Andreas Shimokawa, Damien Gaignon, Daniel Dakhno,
+    Daniel Thompson, Jos茅 Rebelo, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.waspos;
+
+import android.app.Activity;
+import android.bluetooth.le.ScanFilter;
+import android.content.Context;
+import android.net.Uri;
+import android.os.ParcelUuid;
+
+import androidx.annotation.NonNull;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLEDeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.waspos.WaspOSDeviceSupport;
+
+public class WaspOSCoordinator extends AbstractBLEDeviceCoordinator {
+
+    @Override
+    public String getManufacturer() {
+        return "MicroPython";
+    }
+
+    @NonNull
+    @Override
+    public Collection<? extends ScanFilter> createBLEScanFilters() {
+        // TODO: filter on name beginning Bangle.js? Doesn't appear to be built-in :(
+        // https://developer.android.com/reference/android/bluetooth/le/ScanFilter.Builder.html#setDeviceName(java.lang.String)
+        ParcelUuid hpService = new ParcelUuid(WaspOSConstants.UUID_SERVICE_NORDIC_UART);
+        ScanFilter filter = new ScanFilter.Builder().setServiceUuid(hpService).build();
+        return Collections.singletonList(filter);
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("DS-D6.*|K9.*|PineTime.*|P8.*");
+    }
+
+    @Override
+    public int getBondingStyle(){
+        return BONDING_STYLE_NONE;
+    }
+
+    @Override
+    public boolean supportsCalendarEvents() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsRealtimeData() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsWeather() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsFindDevice() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsActivityDataFetching() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsActivityTracking() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsScreenshots(final GBDevice device) {
+        return false;
+    }
+
+    @Override
+    public boolean supportsHeartRateMeasurement(GBDevice device) {
+        return false;
+    }
+
+    @Override
+    public boolean supportsAppsManagement(final GBDevice device) {
+        return false;
+    }
+
+    @Override
+    public int getAlarmSlotCount(GBDevice device) {
+        return 0;
+    }
+
+    @Override
+    public Class<? extends Activity> getAppsManagementActivity() {
+        return null;
+    }
+
+
+    @Override
+    protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) {
+    }
+
+    @Override
+    public Class<? extends Activity> getPairingActivity() {
+        return null;
+    }
+
+    @Override
+    public SampleProvider<? extends ActivitySample> getSampleProvider(GBDevice device, DaoSession session) {
+        return null;//new WaspOSSampleProvider(device, session);
+    }
+
+    @Override
+    public InstallHandler findInstallHandler(Uri uri, Context context) {
+        return null;
+    }
+    public int[] getSupportedDeviceSpecificSettings(GBDevice device) {
+        return new int[]{
+                R.xml.devicesettings_transliteration
+        };
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return WaspOSDeviceSupport.class;
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_waspos;
+    }
+
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_pebble;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_pebble_disabled;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/watch9/Watch9CalibrationActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/watch9/Watch9CalibrationActivity.java
new file mode 100644
index 0000000..c869486
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/watch9/Watch9CalibrationActivity.java
@@ -0,0 +1,123 @@
+/*  Copyright (C) 2018-2024 Daniele Gobbetti, maxirnilian
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.watch9;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Handler;
+import android.view.View;
+import android.widget.Button;
+import android.widget.NumberPicker;
+
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBActivity;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class Watch9CalibrationActivity extends AbstractGBActivity {
+
+    private static final String STATE_DEVICE = "stateDevice";
+    GBDevice device;
+
+    NumberPicker pickerHour, pickerMinute, pickerSecond;
+
+    Handler handler;
+    Runnable holdCalibration;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_watch9_calibration);
+
+        pickerHour = findViewById(R.id.np_hour);
+        pickerMinute = findViewById(R.id.np_minute);
+        pickerSecond = findViewById(R.id.np_second);
+
+        pickerHour.setMinValue(1);
+        pickerHour.setMaxValue(12);
+        pickerHour.setValue(12);
+        pickerMinute.setMinValue(0);
+        pickerMinute.setMaxValue(59);
+        pickerMinute.setValue(0);
+        pickerSecond.setMinValue(0);
+        pickerSecond.setMaxValue(59);
+        pickerSecond.setValue(0);
+
+        handler = new Handler();
+        holdCalibration = new Runnable() {
+            @Override
+            public void run() {
+                LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(new Intent(Watch9Constants.ACTION_CALIBRATION_HOLD));
+                handler.postDelayed(this, 10000);
+            }
+        };
+
+        Intent intent = getIntent();
+        device = intent.getParcelableExtra(GBDevice.EXTRA_DEVICE);
+        if (device == null && savedInstanceState != null) {
+            device = savedInstanceState.getParcelable(STATE_DEVICE);
+        }
+        if (device == null) {
+            finish();
+        }
+
+        final Button btCalibrate = findViewById(R.id.watch9_bt_calibrate);
+        btCalibrate.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                btCalibrate.setEnabled(false);
+                handler.removeCallbacks(holdCalibration);
+                Intent calibrationData = new Intent(Watch9Constants.ACTION_CALIBRATION_SEND);
+                calibrationData.putExtra(Watch9Constants.VALUE_CALIBRATION_HOUR, pickerHour.getValue());
+                calibrationData.putExtra(Watch9Constants.VALUE_CALIBRATION_MINUTE, pickerMinute.getValue());
+                calibrationData.putExtra(Watch9Constants.VALUE_CALIBRATION_SECOND, pickerSecond.getValue());
+                LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(calibrationData);
+                finish();
+            }
+        });
+    }
+
+    @Override
+    protected void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        outState.putParcelable(STATE_DEVICE, device);
+    }
+
+    @Override
+    protected void onRestoreInstanceState(Bundle savedInstanceState) {
+        super.onRestoreInstanceState(savedInstanceState);
+        device = savedInstanceState.getParcelable(STATE_DEVICE);
+    }
+
+    @Override
+    protected void onStart() {
+        super.onStart();
+        Intent calibration = new Intent(Watch9Constants.ACTION_CALIBRATION);
+        calibration.putExtra(Watch9Constants.ACTION_ENABLE, true);
+        LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(calibration);
+        handler.postDelayed(holdCalibration, 1000);
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+        Intent calibration = new Intent(Watch9Constants.ACTION_CALIBRATION);
+        calibration.putExtra(Watch9Constants.ACTION_ENABLE, false);
+        LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(calibration);
+        handler.removeCallbacks(holdCalibration);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/watch9/Watch9Constants.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/watch9/Watch9Constants.java
new file mode 100644
index 0000000..27426f9
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/watch9/Watch9Constants.java
@@ -0,0 +1,92 @@
+/*  Copyright (C) 2018-2024 maxirnilian
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.watch9;
+
+import java.util.UUID;
+
+public final class Watch9Constants {
+    public static final UUID UUID_SERVICE_WATCH9 = UUID.fromString("0000a800-0000-1000-8000-00805f9b34fb");
+
+    public static final UUID UUID_UNKNOWN_DESCRIPTOR = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");
+
+    public static final UUID UUID_CHARACTERISTIC_WRITE = UUID.fromString("0000a801-0000-1000-8000-00805f9b34fb");
+    public static final UUID UUID_CHARACTERISTIC_UNKNOWN_2 = UUID.fromString("0000a802-0000-1000-8000-00805f9b34fb");
+    public static final UUID UUID_CHARACTERISTIC_UNKNOWN_3 = UUID.fromString("0000a803-0000-1000-8000-00805f9b34fb");
+    public static final UUID UUID_CHARACTERISTIC_UNKNOWN_4 = UUID.fromString("0000a804-0000-1000-8000-00805f9b34fb");
+
+    public static final int NOTIFICATION_CHANNEL_DEFAULT = 128;
+    public static final int NOTIFICATION_CHANNEL_PHONE_CALL = 1024;
+
+    public static final byte RESPONSE = 0x13;
+    public static final byte REQUEST = 0x31;
+
+    public static final byte WRITE_VALUE = 0x01;
+    public static final byte READ_VALUE = 0x02;
+    public static final byte TASK = 0x04;
+    public static final byte KEEP_ALIVE = -0x80;
+
+    public static final byte[] CMD_HEADER = new byte[]{0x23, 0x01, 0x00, 0x00, 0x00};
+
+    // byte[] COMMAND = new byte[]{0x23, 0x01, 0x00, 0x31, 0x00, ... , 0x00}
+    //                              |     |     |     |     |     |     鈹� Checksum
+    //                              |     |     |     |     |     鈹� Command + value
+    //                              |     |     |     |     鈹� Sequence number
+    //                              |     |     |     鈹� Response/Request indicator
+    //                              |     |     鈹� Value length
+    //                              |     |
+    //                              鈹�-----鈹� Header
+
+    public static final byte[] CMD_FIRMWARE_INFO                = new byte[]{0x01, 0x02};
+    public static final byte[] CMD_AUTHORIZATION_TASK           = new byte[]{0x01, 0x05};
+    public static final byte[] CMD_TIME_SETTINGS                = new byte[]{0x01, 0x08};
+    public static final byte[] CMD_ALARM_SETTINGS               = new byte[]{0x01, 0x0A};
+    public static final byte[] CMD_BATTERY_INFO                 = new byte[]{0x01, 0x14};
+
+    public static final byte[] CMD_NOTIFICATION_TASK            = new byte[]{0x03, 0x01};
+    public static final byte[] CMD_NOTIFICATION_SETTINGS        = new byte[]{0x03, 0x02};
+    public static final byte[] CMD_CALIBRATION_INIT_TASK        = new byte[]{0x03, 0x31};
+    public static final byte[] CMD_CALIBRATION_TASK             = new byte[]{0x03, 0x33, 0x01};
+    public static final byte[] CMD_CALIBRATION_KEEP_ALIVE       = new byte[]{0x03, 0x34};
+    public static final byte[] CMD_DO_NOT_DISTURB_SETTINGS      = new byte[]{0x03, 0x61};
+
+    public static final byte[] CMD_FITNESS_GOAL_SETTINGS        = new byte[]{0x10, 0x02};
+
+    public static final byte[] RESP_AUTHORIZATION_TASK          = new byte[]{0x01, 0x01, 0x05};
+    public static final byte[] RESP_BUTTON_INDICATOR            = new byte[]{0x04, 0x03, 0x11};
+    public static final byte[] RESP_ALARM_INDICATOR             = new byte[]{-0x80, 0x01, 0x0A};
+
+    public static final byte[] RESP_FIRMWARE_INFO               = new byte[]{0x08, 0x01, 0x02};
+    public static final byte[] RESP_TIME_SETTINGS               = new byte[]{0x08, 0x01, 0x08};
+    public static final byte[] RESP_BATTERY_INFO                = new byte[]{0x08, 0x01, 0x14};
+    public static final byte[] RESP_NOTIFICATION_SETTINGS       = new byte[]{0x08, 0x03, 0x02};
+
+    public static final String ACTION_ENABLE = "action.watch9.enable";
+
+    public static final String ACTION_CALIBRATION
+            = "nodomain.freeyourgadget.gadgetbridge.devices.action.watch9.start_calibration";
+    public static final String ACTION_CALIBRATION_SEND
+            = "nodomain.freeyourgadget.gadgetbridge.devices.action.watch9.send_calibration";
+    public static final String ACTION_CALIBRATION_HOLD
+            = "nodomain.freeyourgadget.gadgetbridge.devices.action.watch9.keep_calibrating";
+    public static final String VALUE_CALIBRATION_HOUR
+            = "value.watch9.calibration_hour";
+    public static final String VALUE_CALIBRATION_MINUTE
+            = "value.watch9.calibration_minute";
+    public static final String VALUE_CALIBRATION_SECOND
+            = "value.watch9.calibration_second";
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/watch9/Watch9DeviceCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/watch9/Watch9DeviceCoordinator.java
new file mode 100644
index 0000000..eaa4202
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/watch9/Watch9DeviceCoordinator.java
@@ -0,0 +1,181 @@
+/*  Copyright (C) 2018-2024 Andreas Shimokawa, Damien Gaignon, Daniel Dakhno,
+    Daniele Gobbetti, Jos茅 Rebelo, mamucho, maxirnilian, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.watch9;
+
+import android.app.Activity;
+import android.bluetooth.le.ScanFilter;
+import android.content.Context;
+import android.net.Uri;
+import android.os.ParcelUuid;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import nodomain.freeyourgadget.gadgetbridge.GBException;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLEDeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.ServiceDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.watch9.Watch9DeviceSupport;
+
+public class Watch9DeviceCoordinator extends AbstractBLEDeviceCoordinator {
+    @Override
+    protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
+
+    }
+
+    @NonNull
+    @Override
+    public Collection<? extends ScanFilter> createBLEScanFilters() {
+        ParcelUuid watch9Service = new ParcelUuid(Watch9Constants.UUID_SERVICE_WATCH9);
+        ScanFilter filter = new ScanFilter.Builder().setServiceUuid(watch9Service).build();
+        return Collections.singletonList(filter);
+    }
+
+    @NonNull
+    @Override
+    public boolean supports(GBDeviceCandidate candidate) {
+        String macAddress = candidate.getMacAddress().toUpperCase();
+        String deviceName = candidate.getName().toUpperCase();
+        if (candidate.supportsService(Watch9Constants.UUID_SERVICE_WATCH9)) {
+            return true;
+            // add support for Watch X non-plus (same MAC address)
+            // add support for Watch X Plus (same MAC address)
+        } else if ((macAddress.startsWith("1C:87:79")) && ((!deviceName.equalsIgnoreCase("WATCH X")) && (!deviceName.equalsIgnoreCase("WATCH XPLUS")))) {
+            return true;
+        } else if (deviceName.equals("WATCH 9")) {
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public int getBondingStyle() {
+        return BONDING_STYLE_NONE;
+    }
+
+    @Nullable
+    @Override
+    public Class<? extends Activity> getPairingActivity() {
+        return Watch9PairingActivity.class;
+    }
+
+    @Override
+    public boolean supportsActivityDataFetching() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsActivityTracking() {
+        return false;
+    }
+
+    @Override
+    public SampleProvider<? extends ActivitySample> getSampleProvider(GBDevice device, DaoSession session) {
+        return null;
+    }
+
+    @Override
+    public InstallHandler findInstallHandler(Uri uri, Context context) {
+        return null;
+    }
+
+    @Override
+    public boolean supportsScreenshots(final GBDevice device) {
+        return false;
+    }
+
+    @Override
+    public int getAlarmSlotCount(GBDevice device) {
+        return 3; // FIXME - check the real value
+    }
+
+
+    @Override
+    public boolean supportsHeartRateMeasurement(GBDevice device) {
+        return false;
+    }
+
+    @Override
+    public String getManufacturer() {
+        return "Lenovo";
+    }
+
+    @Override
+    public boolean supportsAppsManagement(final GBDevice device) {
+        return false;
+    }
+
+    @Override
+    public Class<? extends Activity> getAppsManagementActivity() {
+        return null;
+    }
+
+    @Override
+    public boolean supportsCalendarEvents() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsRealtimeData() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsWeather() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsFindDevice() {
+        return false;
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return Watch9DeviceSupport.class;
+    }
+
+    @Override
+    public EnumSet<ServiceDeviceSupport.Flags> getInitialFlags() {
+        return EnumSet.of(ServiceDeviceSupport.Flags.THROTTLING, ServiceDeviceSupport.Flags.BUSY_CHECKING);
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_watch9;
+    }
+
+    @Nullable
+    @Override
+    public Class<? extends Activity> getCalibrationActivity() {
+        return Watch9CalibrationActivity.class;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/watch9/Watch9PairingActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/watch9/Watch9PairingActivity.java
new file mode 100644
index 0000000..afa7e7d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/watch9/Watch9PairingActivity.java
@@ -0,0 +1,166 @@
+/*  Copyright (C) 2018-2024 Andreas B枚hler, Daniele Gobbetti, Jos茅 Rebelo,
+    maxirnilian, Taavi Eom盲e
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.watch9;
+
+import static nodomain.freeyourgadget.gadgetbridge.util.BondingUtil.STATE_DEVICE_CANDIDATE;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBActivity;
+import nodomain.freeyourgadget.gadgetbridge.activities.ControlCenterv2;
+import nodomain.freeyourgadget.gadgetbridge.activities.discovery.DiscoveryActivityV2;
+import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
+import nodomain.freeyourgadget.gadgetbridge.util.AndroidUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.BondingInterface;
+import nodomain.freeyourgadget.gadgetbridge.util.BondingUtil;
+
+public class Watch9PairingActivity extends AbstractGBActivity implements BondingInterface {
+    private static final Logger LOG = LoggerFactory.getLogger(Watch9PairingActivity.class);
+
+    private final BroadcastReceiver pairingReceiver = BondingUtil.getPairingReceiver(this);
+    private TextView message;
+    private GBDeviceCandidate deviceCandidate;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_watch9_pairing);
+
+        message = findViewById(R.id.watch9_pair_message);
+
+        deviceCandidate = getIntent().getParcelableExtra(DeviceCoordinator.EXTRA_DEVICE_CANDIDATE);
+        if (deviceCandidate == null && savedInstanceState != null) {
+            deviceCandidate = savedInstanceState.getParcelable(STATE_DEVICE_CANDIDATE);
+        }
+
+        if (deviceCandidate == null) {
+            Toast.makeText(this, getString(R.string.message_cannot_pair_no_mac), Toast.LENGTH_SHORT).show();
+            startActivity(new Intent(this, DiscoveryActivityV2.class).setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP));
+            finish();
+            return;
+        }
+
+        startConnecting(deviceCandidate);
+    }
+
+    @Override
+    protected void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        outState.putParcelable(STATE_DEVICE_CANDIDATE, deviceCandidate);
+    }
+
+    @Override
+    protected void onRestoreInstanceState(Bundle savedInstanceState) {
+        super.onRestoreInstanceState(savedInstanceState);
+        deviceCandidate = savedInstanceState.getParcelable(STATE_DEVICE_CANDIDATE);
+    }
+
+    private void startConnecting(GBDeviceCandidate deviceCandidate) {
+        message.setText(getString(R.string.pairing, deviceCandidate));
+
+        registerBroadcastReceivers();
+        BondingUtil.connectThenComplete(this, deviceCandidate);
+    }
+
+    @Override
+    public void onBondingComplete(boolean success) {
+        startActivity(new Intent(this, ControlCenterv2.class).setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP));
+        finish();
+    }
+
+    @Override
+    public GBDeviceCandidate getCurrentTarget() {
+        return this.deviceCandidate;
+    }
+
+    @Override
+    public String getMacAddress() {
+        return deviceCandidate.getDevice().getAddress();
+    }
+
+    @Override
+    public boolean getAttemptToConnect() {
+        return true;
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        super.onActivityResult(requestCode, resultCode, data);
+        BondingUtil.handleActivityResult(this, requestCode, resultCode, data);
+    }
+
+    @Override
+    public void unregisterBroadcastReceivers() {
+        AndroidUtils.safeUnregisterBroadcastReceiver(LocalBroadcastManager.getInstance(this), pairingReceiver);
+    }
+
+    @Override
+    public void registerBroadcastReceivers() {
+        LocalBroadcastManager.getInstance(this).registerReceiver(pairingReceiver, new IntentFilter(GBDevice.ACTION_DEVICE_CHANGED));
+    }
+
+    @Override
+    public Context getContext() {
+        return this;
+    }
+
+    @Override
+    protected void onResume() {
+        registerBroadcastReceivers();
+        super.onResume();
+    }
+
+    @Override
+    protected void onStart() {
+        registerBroadcastReceivers();
+        super.onStart();
+    }
+
+    @Override
+    protected void onDestroy() {
+        unregisterBroadcastReceivers();
+        super.onDestroy();
+    }
+
+    @Override
+    protected void onStop() {
+        unregisterBroadcastReceivers();
+        super.onStop();
+    }
+
+    @Override
+    protected void onPause() {
+        // WARN: Do not remove listeners on pause
+        // Bonding process can pause the activity and you might miss broadcasts
+        super.onPause();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/withingssteelhr/RotaryControl.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/withingssteelhr/RotaryControl.java
new file mode 100644
index 0000000..0a42d3a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/withingssteelhr/RotaryControl.java
@@ -0,0 +1,201 @@
+/*  Copyright (C) 2023-2024 Frank Ertl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.withingssteelhr;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.DashPathEffect;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+
+public class RotaryControl extends View {
+
+    public interface RotationListener {
+        void onRotation(short movementAmount);
+    }
+
+    private Path circlePath;
+
+    private int controlPointX;
+    private int controlPointY;
+
+    private int controlCenterX;
+    private int controlCenterY;
+    private int controlRadius;
+
+    private int padding;
+    private int controlPointSize;
+    private int controlPointColor;
+    private int lineColor;
+    private int lineThickness;
+    private double startAngle;
+    private double angle ;
+    private boolean isControlPointSelected = false;
+    private Paint paint = new Paint();
+    private Paint controlPointPaint = new Paint();
+    private RotationListener rotationListener;
+
+    public RotaryControl(Context context) {
+        this(context, null);
+    }
+
+    public RotaryControl(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public RotaryControl(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        init(context, attrs, defStyleAttr);
+    }
+
+    private void init(Context context, AttributeSet attrs, int defStyleAttr) {
+        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RotaryControl, defStyleAttr, 0);
+
+        startAngle = a.getFloat(R.styleable.RotaryControl_start_angle, (float) Math.PI / 2);
+        angle = startAngle;
+        controlPointSize = a.getDimensionPixelSize(R.styleable.RotaryControl_controlpoint_size, 50);
+        controlPointColor = a.getColor(R.styleable.RotaryControl_controlpoint_color, Color.GRAY);
+        lineThickness = a.getDimensionPixelSize(R.styleable.RotaryControl_line_thickness, 20);
+        lineColor = a.getColor(R.styleable.RotaryControl_line_color, Color.RED);
+        calculateAndSetPadding();
+        a.recycle();
+    }
+
+    private void calculateAndSetPadding() {
+        int totalPadding = getPaddingLeft() + getPaddingRight() + getPaddingBottom() + getPaddingTop() + getPaddingEnd() + getPaddingStart();
+        padding = totalPadding / 6;
+    }
+
+    @Override
+    protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) {
+        int smallerDim = width > height ? height : width;
+        int largestCenteredSquareLeft = (width - smallerDim) / 2;
+        int largestCenteredSquareTop = (height - smallerDim) / 2;
+        int largestCenteredSquareRight = largestCenteredSquareLeft + smallerDim;
+        int largestCenteredSquareBottom = largestCenteredSquareTop + smallerDim;
+        controlCenterX = largestCenteredSquareRight / 2 + (width - largestCenteredSquareRight) / 2;
+        controlCenterY = largestCenteredSquareBottom / 2 + (height - largestCenteredSquareBottom) / 2;
+        controlRadius = smallerDim / 2 - lineThickness / 2 - padding;
+
+        super.onSizeChanged(width, height, oldWidth, oldHeight);
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+
+        drawRotationCircle(canvas);
+        drawControlPoint(canvas);
+
+    }
+
+    private void drawControlPoint(Canvas canvas) {
+        controlPointX = (int) (controlCenterX + controlRadius * Math.cos(angle));
+        controlPointY = (int) (controlCenterY - controlRadius * Math.sin(angle));
+        controlPointPaint.setColor(controlPointColor);
+        controlPointPaint.setStyle(Paint.Style.FILL);
+        controlPointPaint.setAlpha(128);
+        Path controlPointPath = new Path();
+        controlPointPath.addCircle(controlPointX, controlPointY, controlPointSize, Path.Direction.CW);
+        canvas.drawPath(controlPointPath, controlPointPaint);
+    }
+
+    private void drawRotationCircle(Canvas canvas) {
+        DashPathEffect dashPath = new DashPathEffect(new float[]{8,22}, (float)1.0);
+        paint.setPathEffect(dashPath);
+        paint.setStyle(Paint.Style.STROKE);
+        paint.setStrokeWidth(lineThickness);
+        paint.setAntiAlias(true);
+        paint.setColor(lineColor);
+        circlePath = new Path();
+        circlePath.addCircle(controlCenterX, controlCenterY, controlRadius, Path.Direction.CW);
+        canvas.drawPath(circlePath, paint);
+    }
+
+    private void updateRotationPosition(double touchX, double touchY) {
+        double distanceX = touchX - controlCenterX;
+        double distanceY = controlCenterY - touchY;
+        double c = Math.sqrt(Math.pow(distanceX, 2) + Math.pow(distanceY, 2));
+        double currentAngle = Math.acos(distanceX / c);
+        if (distanceY < 0) {
+            currentAngle = -currentAngle;
+        }
+
+        int movementAmount = (int) ((currentAngle - angle) * 100);
+
+        int i = (int) movementAmount;
+        if (movementAmount != 0) {
+            if (Math.abs(movementAmount) > 15) {
+                movementAmount /= movementAmount;
+            }
+
+            rotationListener.onRotation((short) -movementAmount);
+        }
+
+        angle = currentAngle;
+    }
+
+    public void setRotationListener(RotationListener listener) {
+        rotationListener = listener;
+    }
+
+    public void reset() {
+        angle = startAngle;
+        invalidate();
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent ev) {
+        switch (ev.getAction()) {
+            case MotionEvent.ACTION_DOWN: {
+                double x = ev.getX();
+                double y = ev.getY();
+                if (x < controlPointX + controlPointSize && x > controlPointX - controlPointSize && y < controlPointY + controlPointSize && y > controlPointY - controlPointSize) {
+                    getParent().requestDisallowInterceptTouchEvent(true);
+                    isControlPointSelected = true;
+                    updateRotationPosition(x, y);
+                }
+                break;
+            }
+
+            case MotionEvent.ACTION_MOVE: {
+                if (isControlPointSelected) {
+                    double x = ev.getX();
+                    double y = ev.getY();
+                    updateRotationPosition(x, y);
+                }
+                break;
+            }
+
+            case MotionEvent.ACTION_UP: {
+                getParent().requestDisallowInterceptTouchEvent(false);
+                isControlPointSelected = false;
+                break;
+            }
+        }
+
+        invalidate();
+        return true;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/withingssteelhr/WithingsCalibrationActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/withingssteelhr/WithingsCalibrationActivity.java
new file mode 100644
index 0000000..49030e7
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/withingssteelhr/WithingsCalibrationActivity.java
@@ -0,0 +1,159 @@
+/*  Copyright (C) 2023-2024 Frank Ertl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.withingssteelhr;
+
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.Button;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBActivity;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.WithingsSteelHRDeviceSupport;
+
+public class WithingsCalibrationActivity extends AbstractGBActivity {
+
+    enum Hands {
+        HOURS((short)1),
+        MINUTES((short)0),
+        ACTIVITY_TARGET((short)2);
+
+        private final short code;
+        Hands(short code) {
+            this.code = code;
+        }
+
+    }
+
+    private GBDevice device;
+    private LocalBroadcastManager localBroadcastManager;
+    private final String[] calibrationAdvices = new String[3];
+    private final Hands[] hands = new Hands[]{Hands.HOURS, Hands.MINUTES, Hands.ACTIVITY_TARGET};
+    private short handIndex = 0;
+    private Button previousButton;
+    private Button nextButton;
+    private Button okButton;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_withings_calibration);
+        List<GBDevice> devices = GBApplication.app().getDeviceManager().getSelectedDevices();
+        for(GBDevice device : devices){
+            if(device.getType() == DeviceType.WITHINGS_STEEL_HR ){
+                this.device = device;
+                break;
+            }
+        }
+
+        if (device == null) {
+            Toast.makeText(this, R.string.watch_not_connected, Toast.LENGTH_LONG).show();
+            finish();
+            return;
+        }
+
+        initView();
+        localBroadcastManager = LocalBroadcastManager.getInstance(this);
+        localBroadcastManager.sendBroadcast(new Intent(WithingsSteelHRDeviceSupport.START_HANDS_CALIBRATION_CMD));
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        if (localBroadcastManager != null) {
+            localBroadcastManager.sendBroadcast(new Intent(WithingsSteelHRDeviceSupport.STOP_HANDS_CALIBRATION_CMD));
+        }
+    }
+
+    private void initView() {
+
+        calibrationAdvices[0] = getString(R.string.withings_calibration_text_hours);
+        calibrationAdvices[1] = getString(R.string.withings_calibration_text_minutes);
+        calibrationAdvices[2] = getString(R.string.withings_calibration_text_activity_target);
+
+        RotaryControl rotaryControl = findViewById(R.id.rotary_control);
+        rotaryControl.setRotationListener(new RotaryControl.RotationListener() {
+            @Override
+            public void onRotation(short movementAmount) {
+                Intent calibration = new Intent(WithingsSteelHRDeviceSupport.HANDS_CALIBRATION_CMD);
+                calibration.putExtra("hand", hands[handIndex].code);
+                calibration.putExtra("movementAmount", movementAmount);
+                localBroadcastManager.sendBroadcast(calibration);
+            }
+        });
+
+        TextView textView = findViewById(R.id.withings_calibration_textview);
+        textView.setText(calibrationAdvices[0]);
+        previousButton = findViewById(R.id.withings_calibration_button_previous);
+        previousButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                handIndex--;
+                enableButtons();
+                textView.setText(calibrationAdvices[handIndex]);
+                rotaryControl.reset();
+            }
+        });
+        nextButton = findViewById(R.id.withings_calibration_button_next);
+        nextButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                handIndex++;
+                enableButtons();
+                textView.setText(calibrationAdvices[handIndex]);
+                rotaryControl.reset();
+            }
+        });
+
+        okButton = findViewById(R.id.withings_calibration_button_ok);
+        okButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                finish();
+            }
+        });
+
+        enableButtons();
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(final MenuItem item) {
+        final int itemId = item.getItemId();
+        if (itemId == android.R.id.home) {
+            finish();
+            return true;
+        }
+
+        return super.onOptionsItemSelected(item);
+    }
+
+    private void enableButtons() {
+        nextButton.setEnabled(handIndex < 2);
+        previousButton.setEnabled(handIndex > 0);
+        okButton.setEnabled(handIndex == 2);
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/withingssteelhr/WithingsSteelHRDeviceCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/withingssteelhr/WithingsSteelHRDeviceCoordinator.java
new file mode 100644
index 0000000..7466c5a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/withingssteelhr/WithingsSteelHRDeviceCoordinator.java
@@ -0,0 +1,212 @@
+/*  Copyright (C) 2023-2024 Ascense, Daniel Dakhno, Frank Ertl, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.withingssteelhr;
+
+import android.app.Activity;
+import android.content.Context;
+import android.net.Uri;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.Locale;
+
+import de.greenrobot.dao.query.QueryBuilder;
+import nodomain.freeyourgadget.gadgetbridge.GBException;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.AbstractDeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.entities.WithingsSteelHRActivitySampleDao;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.WithingsSteelHRDeviceSupport;
+
+public class WithingsSteelHRDeviceCoordinator extends AbstractDeviceCoordinator {
+
+    @Override
+    protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
+        Long deviceId = device.getId();
+        QueryBuilder<?> qb = session.getWithingsSteelHRActivitySampleDao().queryBuilder();
+        qb.where(WithingsSteelHRActivitySampleDao.Properties.DeviceId.eq(deviceId)).buildDelete().executeDeleteWithoutDetachingEntities();
+    }
+
+    @NonNull
+    @Override
+    public boolean supports(GBDeviceCandidate candidate) {
+        String name = candidate.getName();
+        if (name != null && (name.toLowerCase(Locale.ROOT).startsWith("steel") || name.toLowerCase(Locale.ROOT).startsWith("activite"))) {
+            return true;
+        }
+
+        return false;
+    }
+
+    @Override
+    public int[] getSupportedDeviceSpecificSettings(GBDevice device) {
+        return new int[]{
+                R.xml.devicesettings_withingssteelhr
+        };
+    }
+
+    @Override
+    public int getBondingStyle(){
+        return BONDING_STYLE_BOND;
+    }
+
+
+    @Nullable
+    @Override
+    public Class<? extends Activity> getPairingActivity() {
+        return null;
+    }
+
+    @Override
+    public boolean supportsRemSleep() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsActivityDataFetching() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsActivityTracking() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsActivityTracks() {
+        return true;
+    }
+
+    @Override
+    public SampleProvider<? extends ActivitySample> getSampleProvider(GBDevice device, DaoSession session) {
+        return new WithingsSteelHRSampleProvider(device, session);
+    }
+
+    @Override
+    public InstallHandler findInstallHandler(Uri uri, Context context) {
+        return null;
+    }
+
+    @Override
+    public boolean supportsScreenshots(final GBDevice device) {
+        return false;
+    }
+
+    @Override
+    public int getAlarmSlotCount(GBDevice gbDevice) {
+        return 3;
+    }
+
+    @Override
+    public boolean supportsAlarmTitle(GBDevice device) {
+        return true;
+    }
+
+    @Override
+    public boolean supportsAlarmDescription(GBDevice device) {
+        return true;
+    }
+
+    @Override
+    public boolean supportsSmartWakeup(GBDevice device, int position) {
+        return true;
+    }
+
+    @Override
+    public boolean supportsHeartRateMeasurement(GBDevice device) {
+        return true;
+    }
+
+    @Override
+    public String getManufacturer() {
+        return "Withings";
+    }
+
+    @Override
+    public boolean supportsAppsManagement(GBDevice gbDevice) {
+        return false;
+    }
+
+    @Override
+    public Class<? extends Activity> getAppsManagementActivity() {
+        return null;
+    }
+
+    @Override
+    public boolean supportsCalendarEvents() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsRealtimeData() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsWeather() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsFindDevice() {
+        return false;
+    }
+
+    @Override
+    public String[] getSupportedLanguageSettings(GBDevice device) {
+        return new String[]{
+                "auto",
+                "de_DE",
+                "en_US",
+                "es_ES",
+                "fr_FR",
+                "it_IT",
+        };
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return WithingsSteelHRDeviceSupport.class;
+    }
+
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.withings_steel_hr;
+    }
+
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_watchxplus;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_watchxplus_disabled;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/withingssteelhr/WithingsSteelHRSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/withingssteelhr/WithingsSteelHRSampleProvider.java
new file mode 100644
index 0000000..8b79d04
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/withingssteelhr/WithingsSteelHRSampleProvider.java
@@ -0,0 +1,98 @@
+/*  Copyright (C) 2023-2024 Frank Ertl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.withingssteelhr;
+
+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.WithingsSteelHRActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.entities.WithingsSteelHRActivitySampleDao;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+
+public class WithingsSteelHRSampleProvider extends AbstractSampleProvider<WithingsSteelHRActivitySample> {
+    public WithingsSteelHRSampleProvider(GBDevice device, DaoSession session) {
+        super(device, session);
+    }
+
+    @Override
+    public AbstractDao<WithingsSteelHRActivitySample, ?> getSampleDao() {
+        return getSession().getWithingsSteelHRActivitySampleDao();
+    }
+
+    @Nullable
+    @Override
+    protected Property getRawKindSampleProperty() {
+        return WithingsSteelHRActivitySampleDao.Properties.RawKind;
+    }
+
+    @NonNull
+    @Override
+    protected Property getTimestampSampleProperty() {
+        return WithingsSteelHRActivitySampleDao.Properties.Timestamp;
+    }
+
+    @NonNull
+    @Override
+    protected Property getDeviceIdentifierSampleProperty() {
+        return WithingsSteelHRActivitySampleDao.Properties.DeviceId;
+    }
+
+    @Override
+    public ActivityKind normalizeType(int rawType) {
+        switch (rawType) {
+            case 1:
+                return ActivityKind.LIGHT_SLEEP;
+            case 2:
+                return ActivityKind.DEEP_SLEEP;
+            default:
+                return ActivityKind.fromCode(rawType);
+        }
+    }
+
+    @Override
+    public int toRawActivityKind(ActivityKind activityKind) {
+        switch (activityKind) {
+            case UNKNOWN:
+                return 0;
+            case LIGHT_SLEEP:
+                return 1;
+            case DEEP_SLEEP:
+                return 2;
+            default:
+                return activityKind.getCode();
+        }
+    }
+
+    @Override
+    public float normalizeIntensity(int rawIntensity) {
+        if (rawIntensity > 0) {
+            return (float) (Math.log(rawIntensity) / 8);
+        }
+
+        return 0;
+    }
+
+    @Override
+    public WithingsSteelHRActivitySample createActivitySample() {
+        return new WithingsSteelHRActivitySample();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiCoordinator.java
new file mode 100644
index 0000000..3dd99b0
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiCoordinator.java
@@ -0,0 +1,608 @@
+/*  Copyright (C) 2023-2024 Andreas Shimokawa, Jos茅 Rebelo, Yoran Vulker
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.xiaomi;
+
+import static nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.XiaomiPreferences.*;
+
+import android.app.Activity;
+import android.bluetooth.le.ScanFilter;
+import android.content.Context;
+import android.net.Uri;
+import android.os.ParcelUuid;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import org.apache.commons.lang3.ArrayUtils;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.UUID;
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.GBException;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.appmanager.AppManagerActivity;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsScreen;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettings;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer;
+import nodomain.freeyourgadget.gadgetbridge.capabilities.HeartRateCapability;
+import nodomain.freeyourgadget.gadgetbridge.capabilities.password.PasswordCapabilityImpl;
+import nodomain.freeyourgadget.gadgetbridge.capabilities.widgets.WidgetManager;
+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.XiaomiActivitySampleDao;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryParser;
+import nodomain.freeyourgadget.gadgetbridge.model.HeartRateSample;
+import nodomain.freeyourgadget.gadgetbridge.model.PaiSample;
+import nodomain.freeyourgadget.gadgetbridge.model.RespiratoryRateSample;
+import nodomain.freeyourgadget.gadgetbridge.model.Spo2Sample;
+import nodomain.freeyourgadget.gadgetbridge.model.StressSample;
+import nodomain.freeyourgadget.gadgetbridge.model.TemperatureSample;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.XiaomiUuids;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.XiaomiPreferences;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.XiaomiSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.activity.impl.WorkoutSummaryParser;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+public abstract class XiaomiCoordinator extends AbstractBLEDeviceCoordinator {
+    // On plaintext devices, user id is used as auth key - numeric
+    private static final Pattern AUTH_KEY_PATTERN = Pattern.compile("^[0-9]+$");
+
+    @NonNull
+    @Override
+    public Collection<? extends ScanFilter> createBLEScanFilters() {
+        final List<ScanFilter> filters = new ArrayList<>();
+        for (final UUID uuid : XiaomiUuids.BLE_UUIDS.keySet()) {
+            final ParcelUuid service = new ParcelUuid(uuid);
+            final ScanFilter filter = new ScanFilter.Builder().setServiceUuid(service).build();
+            filters.add(filter);
+        }
+        return filters;
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return XiaomiSupport.class;
+    }
+
+    @Override
+    public boolean validateAuthKey(final String authKey) {
+        final byte[] authKeyBytes = authKey.trim().getBytes();
+        // At this point we don't know if it's encrypted or not, so let's accept both:
+        return authKeyBytes.length == 32 || (authKey.startsWith("0x") && authKeyBytes.length == 34)
+                || AUTH_KEY_PATTERN.matcher(authKey.trim()).matches();
+    }
+
+    @Nullable
+    @Override
+    public InstallHandler findInstallHandler(final Uri uri, final Context context) {
+        final XiaomiInstallHandler handler = new XiaomiInstallHandler(uri, context);
+        return handler.isValid() ? handler : null;
+    }
+
+    @Override
+    protected void deleteDevice(@NonNull final GBDevice gbDevice,
+                                @NonNull final Device device,
+                                @NonNull final DaoSession session) throws GBException {
+        final Long deviceId = device.getId();
+
+        session.getXiaomiActivitySampleDao().queryBuilder()
+                .where(XiaomiActivitySampleDao.Properties.DeviceId.eq(deviceId))
+                .buildDelete().executeDeleteWithoutDetachingEntities();
+    }
+
+    @Override
+    public SampleProvider<? extends ActivitySample> getSampleProvider(final GBDevice device, DaoSession session) {
+        return new XiaomiSampleProvider(device, session);
+    }
+
+    @Override
+    public TimeSampleProvider<? extends StressSample> getStressSampleProvider(final GBDevice device, final DaoSession session) {
+        return new XiaomiStressSampleProvider(device, session);
+    }
+
+    @Override
+    public int[] getStressRanges() {
+        // 1-25 = relaxed
+        // 26-50 = mild
+        // 51-80 = moderate
+        // 81-100 = high
+        return new int[]{1, 26, 51, 81};
+    }
+
+    @Override
+    public TimeSampleProvider<? extends TemperatureSample> getTemperatureSampleProvider(final GBDevice device, final DaoSession session) {
+        return new XiaomiTemperatureSampleProvider(device, session);
+    }
+
+    @Override
+    public TimeSampleProvider<? extends Spo2Sample> getSpo2SampleProvider(final GBDevice device, final DaoSession session) {
+        return new XiaomiSpo2SampleProvider(device, session);
+    }
+
+    @Override
+    public TimeSampleProvider<? extends HeartRateSample> getHeartRateMaxSampleProvider(final GBDevice device, final DaoSession session) {
+        // TODO XiaomiHeartRateMaxSampleProvider
+        return super.getHeartRateMaxSampleProvider(device, session);
+    }
+
+    @Override
+    public TimeSampleProvider<? extends HeartRateSample> getHeartRateRestingSampleProvider(GBDevice device, DaoSession session) {
+        return new XiaomiHeartRateRestingSampleProvider(device, session);
+    }
+
+    @Override
+    public TimeSampleProvider<? extends HeartRateSample> getHeartRateManualSampleProvider(final GBDevice device, final DaoSession session) {
+        // TODO XiaomiHeartRateManualSampleProvider
+        return super.getHeartRateManualSampleProvider(device, session);
+    }
+
+    @Override
+    public TimeSampleProvider<? extends PaiSample> getPaiSampleProvider(final GBDevice device, final DaoSession session) {
+        return new XiaomiPaiSampleProvider(device, session);
+    }
+
+    @Override
+    public TimeSampleProvider<? extends RespiratoryRateSample> getRespiratoryRateSampleProvider(final GBDevice device, final DaoSession session) {
+        // TODO XiaomiSleepRespiratoryRateSampleProvider
+        return super.getRespiratoryRateSampleProvider(device, session);
+    }
+
+    @Nullable
+    @Override
+    public ActivitySummaryParser getActivitySummaryParser(final GBDevice device, final Context context) {
+        return new WorkoutSummaryParser();
+    }
+
+    @Override
+    public boolean supportsFlashing() {
+        return true;
+    }
+
+    @Override
+    public int getAlarmSlotCount(final GBDevice device) {
+        return getPrefs(device).getInt(XiaomiPreferences.PREF_ALARM_SLOTS, 0);
+    }
+
+    @Override
+    public boolean supportsSmartWakeup(final GBDevice device, int position) {
+        return true;
+    }
+
+    public boolean supportsAppsManagement(final GBDevice device) {
+        return true;
+    }
+
+    @Override
+    public boolean supportsCachedAppManagement(GBDevice device) {
+        return false;
+    }
+
+    @Override
+    public boolean supportsInstalledAppManagement(GBDevice device) {
+        return false;
+    }
+
+    @Override
+    public boolean supportsWatchfaceManagement(GBDevice device) {
+        return supportsAppsManagement(device);
+    }
+
+    @Override
+    public Class<? extends Activity> getAppsManagementActivity() {
+        return AppManagerActivity.class;
+    }
+
+    @Override
+    public boolean supportsAppListFetching() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsAppReordering() {
+        return false;
+    }
+
+    @Override
+    public int getBondingStyle() {
+        return BONDING_STYLE_REQUIRE_KEY;
+    }
+
+    @Override
+    public boolean supportsCalendarEvents() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsActivityDataFetching() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsActivityTracking() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsActivityTracks() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsStressMeasurement() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsSpo2(GBDevice device) {
+        return true;
+    }
+
+    @Override
+    public boolean supportsHeartRateStats() {
+        // TODO it does, and they're persisted - see DailySummaryParser
+        return false;
+    }
+
+    @Override
+    public boolean supportsHeartRateRestingMeasurement(GBDevice device) {
+        return true;
+    }
+
+    @Override
+    public boolean supportsPai() {
+        // Vitality Score
+        return true;
+    }
+
+    @Override
+    public int getPaiName() {
+        return R.string.pref_vitality_score_title;
+    }
+
+    @Override
+    public boolean supportsPaiTime() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsSleepRespiratoryRate() {
+        // TODO it does
+        return false;
+    }
+
+    @Override
+    public boolean supportsMusicInfo() {
+        return true;
+    }
+
+    @Override
+    public int getMaximumReminderMessageLength() {
+        // TODO does it?
+        return 20;
+    }
+
+    @Override
+    public int getReminderSlotCount(final GBDevice device) {
+        return getPrefs(device).getInt(XiaomiPreferences.PREF_REMINDER_SLOTS, 0);
+    }
+
+    @Override
+    public int getCannedRepliesSlotCount(final GBDevice device) {
+        return getPrefs(device).getInt(XiaomiPreferences.PREF_CANNED_MESSAGES_MAX, 0);
+    }
+
+    @Override
+    public int getWorldClocksSlotCount() {
+        // TODO how many? also, map world clocks
+        return 0;
+    }
+
+    @Override
+    public int getWorldClocksLabelLength() {
+        // TODO no labels
+        // TODO list of supported timezones
+        return 5;
+    }
+
+    @Override
+    public boolean supportsDisabledWorldClocks() {
+        // TODO does it?
+        return false;
+    }
+
+    @Override
+    public boolean supportsHeartRateMeasurement(final GBDevice device) {
+        return true;
+    }
+
+    @Override
+    public boolean supportsManualHeartRateMeasurement(final GBDevice device) {
+        return true;
+    }
+
+    @Override
+    public String getManufacturer() {
+        return "Xiaomi";
+    }
+
+    @Override
+    public boolean supportsRealtimeData() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsRemSleep() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsWeather() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsFindDevice() {
+        return true;
+    }
+
+    @Override
+    public boolean addBatteryPollingSettings() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsUnicodeEmojis() {
+        return true;
+    }
+
+    @Override
+    public int[] getSupportedDeviceSpecificConnectionSettings() {
+        final List<Integer> settings = new ArrayList<>();
+
+        if (getConnectionType().equals(ConnectionType.BOTH)) {
+            settings.add(R.xml.devicesettings_force_connection_type);
+        }
+
+        return ArrayUtils.addAll(
+                super.getSupportedDeviceSpecificConnectionSettings(),
+                ArrayUtils.toPrimitive(settings.toArray(new Integer[0]))
+        );
+    }
+
+    @Override
+    public DeviceSpecificSettings getDeviceSpecificSettings(final GBDevice device) {
+        final DeviceSpecificSettings deviceSpecificSettings = new DeviceSpecificSettings();
+
+        if (supports(device, FEAT_WEAR_MODE)) {
+            // TODO we should be able to get this from the band - right now it must be changed
+            // at least once from the band itself
+            deviceSpecificSettings.addRootScreen(R.xml.devicesettings_wearmode);
+        }
+
+        //
+        // Time
+        //
+        final List<Integer> dateTime = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DATE_TIME);
+        dateTime.add(R.xml.devicesettings_timeformat);
+        if (getWorldClocksSlotCount() > 0) {
+            dateTime.add(R.xml.devicesettings_world_clocks);
+        }
+
+        //
+        // Display
+        //
+        final List<Integer> display = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DISPLAY);
+        if (supports(device, FEAT_DISPLAY_ITEMS)) {
+            display.add(R.xml.devicesettings_xiaomi_displayitems);
+        }
+        if (this.supportsWidgets(device)) {
+            display.add(R.xml.devicesettings_widgets);
+        }
+        if (supports(device, FEAT_PASSWORD)) {
+            display.add(R.xml.devicesettings_password);
+        }
+
+        //
+        // Health
+        //
+        if (supportsStressMeasurement() && supports(device, FEAT_STRESS) && supportsSpo2(device) && supports(device, FEAT_SPO2)) {
+            deviceSpecificSettings.addRootScreen(R.xml.devicesettings_heartrate_sleep_alert_activity_stress_spo2);
+        } else if (supportsStressMeasurement() && supports(device, FEAT_STRESS)) {
+            deviceSpecificSettings.addRootScreen(R.xml.devicesettings_heartrate_sleep_alert_activity_stress);
+        } else {
+            deviceSpecificSettings.addRootScreen(R.xml.devicesettings_heartrate_sleep_activity);
+        }
+        if (supports(device, FEAT_INACTIVITY)) {
+            deviceSpecificSettings.addRootScreen(R.xml.devicesettings_inactivity_dnd_no_threshold);
+        }
+        if (supports(device, FEAT_SLEEP_MODE_SCHEDULE)) {
+            deviceSpecificSettings.addRootScreen(R.xml.devicesettings_sleep_mode_schedule);
+        }
+        if (supports(device, FEAT_GOAL_NOTIFICATION)) {
+            deviceSpecificSettings.addRootScreen(R.xml.devicesettings_goal_notification);
+        }
+        if (supports(device, FEAT_GOAL_SECONDARY)) {
+            deviceSpecificSettings.addRootScreen(R.xml.devicesettings_goal_secondary);
+        }
+        if (supports(device, FEAT_VITALITY_SCORE)) {
+            deviceSpecificSettings.addRootScreen(R.xml.devicesettings_vitality_score);
+        }
+
+        //
+        // Workout
+        //
+        final List<Integer> workout = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.WORKOUT);
+        workout.add(R.xml.devicesettings_workout_start_on_phone);
+        workout.add(R.xml.devicesettings_workout_send_gps_to_band);
+
+        //
+        // Notifications
+        //
+        final List<Integer> notifications = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.NOTIFICATIONS);
+        // TODO not implemented settings.add(R.xml.devicesettings_vibrationpatterns);
+        // TODO not implemented settings.add(R.xml.devicesettings_donotdisturb_withauto_and_always);
+        notifications.add(R.xml.devicesettings_send_app_notifications);
+        if (supports(device, FEAT_SCREEN_ON_ON_NOTIFICATIONS)) {
+            notifications.add(R.xml.devicesettings_screen_on_on_notifications);
+        }
+        notifications.add(R.xml.devicesettings_autoremove_notifications);
+        if (getCannedRepliesSlotCount(device) > 0) {
+            notifications.add(R.xml.devicesettings_canned_dismisscall_16);
+        }
+        notifications.add(R.xml.devicesettings_transliteration);
+
+        //
+        // Calendar
+        //
+        if (supportsCalendarEvents()) {
+            deviceSpecificSettings.addRootScreen(
+                    DeviceSpecificSettingsScreen.CALENDAR,
+                    R.xml.devicesettings_header_calendar,
+                    R.xml.devicesettings_sync_calendar
+            );
+        }
+
+        //
+        // Other
+        //
+        if (getContactsSlotCount(device) > 0) {
+            deviceSpecificSettings.addRootScreen(R.xml.devicesettings_contacts);
+        }
+        if (supports(device, FEAT_CAMERA_REMOTE)) {
+            deviceSpecificSettings.addRootScreen(R.xml.devicesettings_camera_remote);
+        }
+        if (supports(device, FEAT_DEVICE_ACTIONS)) {
+            deviceSpecificSettings.addRootScreen(R.xml.devicesettings_device_actions);
+        }
+        deviceSpecificSettings.addRootScreen(R.xml.devicesettings_phone_silent_mode);
+
+        //
+        // Developer
+        //
+        deviceSpecificSettings.addRootScreen(
+                DeviceSpecificSettingsScreen.DEVELOPER,
+                R.xml.devicesettings_keep_activity_data_on_device
+        );
+
+        return deviceSpecificSettings;
+    }
+
+    @Override
+    public int[] getSupportedDeviceSpecificAuthenticationSettings() {
+        return new int[]{R.xml.devicesettings_pairingkey};
+    }
+
+    @Override
+    public DeviceSpecificSettingsCustomizer getDeviceSpecificSettingsCustomizer(final GBDevice device) {
+        return new XiaomiSettingsCustomizer();
+    }
+
+    @Override
+    public String[] getSupportedLanguageSettings(final GBDevice device) {
+        return new String[]{
+                "auto",
+                "ar_SA",
+                "cs_CZ",
+                "da_DK",
+                "de_DE",
+                "el_GR",
+                "en_US",
+                "es_ES",
+                "fr_FR",
+                "he_IL",
+                "id_ID",
+                "it_IT",
+                "ja_JP",
+                "ko_KO",
+                "nl_NL",
+                "nb_NO",
+                "pl_PL",
+                "pt_BR",
+                "pt_PT",
+                "ro_RO",
+                "ru_RU",
+                "sv_SE",
+                "th_TH",
+                "tr_TR",
+                "uk_UA",
+                "vi_VN",
+                "zh_CN",
+                "zh_TW",
+        };
+    }
+
+    @Override
+    public PasswordCapabilityImpl.Mode getPasswordCapability() {
+        return PasswordCapabilityImpl.Mode.NUMBERS_6;
+    }
+
+    @Override
+    public List<HeartRateCapability.MeasurementInterval> getHeartRateMeasurementIntervals() {
+        return Arrays.asList(
+                HeartRateCapability.MeasurementInterval.OFF,
+                HeartRateCapability.MeasurementInterval.SMART,
+                HeartRateCapability.MeasurementInterval.MINUTES_1,
+                HeartRateCapability.MeasurementInterval.MINUTES_10,
+                HeartRateCapability.MeasurementInterval.MINUTES_30
+        );
+    }
+
+    @Override
+    public boolean supportsWidgets(final GBDevice device) {
+        return getPrefs(device).getBoolean(XiaomiPreferences.FEAT_WIDGETS, false);
+    }
+
+    @Override
+    public WidgetManager getWidgetManager(final GBDevice device) {
+        return new XiaomiWidgetManager(device);
+    }
+
+    protected static Prefs getPrefs(final GBDevice device) {
+        return new Prefs(GBApplication.getDeviceSpecificSharedPrefs(device.getAddress()));
+    }
+
+    public boolean supports(final GBDevice device, final String feature) {
+        return getPrefs(device).getBoolean(feature, false);
+    }
+
+    public boolean checkDecryptionMac() {
+        return true;
+    }
+
+    /**
+     * Whether the device supports alarms. This differs from {@link #getAlarmSlotCount} since that
+     * returns the number of alarms dynamically after requesting them, but some devices will crash
+     * if we even attempt to request alarms and they do not support them - see
+     * <a href="https://codeberg.org/Freeyourgadget/Gadgetbridge/issues/3766">#3766</a>
+     */
+    public boolean supportsAlarms() {
+        return true;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiDailySummarySampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiDailySummarySampleProvider.java
new file mode 100644
index 0000000..5926994
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiDailySummarySampleProvider.java
@@ -0,0 +1,56 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.xiaomi;
+
+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.XiaomiDailySummarySample;
+import nodomain.freeyourgadget.gadgetbridge.entities.XiaomiDailySummarySampleDao;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class XiaomiDailySummarySampleProvider extends AbstractTimeSampleProvider<XiaomiDailySummarySample> {
+    public XiaomiDailySummarySampleProvider(final GBDevice device, final DaoSession session) {
+        super(device, session);
+    }
+
+    @NonNull
+    @Override
+    public AbstractDao<XiaomiDailySummarySample, ?> getSampleDao() {
+        return getSession().getXiaomiDailySummarySampleDao();
+    }
+
+    @NonNull
+    @Override
+    protected Property getTimestampSampleProperty() {
+        return XiaomiDailySummarySampleDao.Properties.Timestamp;
+    }
+
+    @NonNull
+    @Override
+    protected Property getDeviceIdentifierSampleProperty() {
+        return XiaomiDailySummarySampleDao.Properties.DeviceId;
+    }
+
+    @Override
+    public XiaomiDailySummarySample createSample() {
+        return new XiaomiDailySummarySample();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiFWHelper.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiFWHelper.java
new file mode 100644
index 0000000..5e71aef
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiFWHelper.java
@@ -0,0 +1,368 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.xiaomi;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.net.Uri;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.StandardCharsets;
+import java.util.Locale;
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.XiaomiBitmapUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.ArrayUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.UriHelper;
+
+public class XiaomiFWHelper {
+    private static final Logger LOG = LoggerFactory.getLogger(XiaomiFWHelper.class);
+    // TODO use to determine translatable face name displayed in UI
+    private static final String[] FACE_LOCALE_BITMAP_LIST = new String[]{
+            "en_US",
+            "zh_CN",
+            "zh_TW",
+            "ja_JP",
+            "es_ES",
+            "fr_FR",
+            "de_DE",
+            "ru_RU",
+            "pt_BR",
+            "pt_PT",
+            "it_IT",
+            "ko_KR",
+            "tr_TR",
+            "nl_NL",
+            "th_TH",
+            "sv_SE",
+            "da_DK",
+            "vi_VN",
+            "nb_NO",
+            "pl_PL",
+            "fi_FI",
+            "in_ID", // old ISO-639
+            "el_GR",
+            "ro_RO",
+            "cs_CZ",
+            "uk_UA",
+            "hu_HU",
+            "sk_SK",
+            "ar_EG",
+            "iw_IL", // old ISO-639
+            "zh_HK",
+    };
+
+    private final Uri uri;
+    private byte[] fw;
+    private boolean valid;
+    private boolean typeFirmware;
+    private boolean typeWatchface;
+
+    private String id;
+    private String name;
+    private String version;
+
+    public XiaomiFWHelper(final Uri uri, final Context context) {
+        this.uri = uri;
+
+        final UriHelper uriHelper;
+        try {
+            uriHelper = UriHelper.get(uri, context);
+        } catch (final IOException e) {
+            LOG.error("Failed to get uri helper for {}", uri, e);
+            return;
+        }
+
+        final int maxExpectedFileSize = 1024 * 1024 * 128; // 64MB
+
+        if (uriHelper.getFileSize() > maxExpectedFileSize) {
+            LOG.warn("Firmware size is larger than the maximum expected file size of {}", maxExpectedFileSize);
+            return;
+        }
+
+        try (final InputStream in = new BufferedInputStream(uriHelper.openInputStream())) {
+            this.fw = FileUtils.readAll(in, maxExpectedFileSize);
+        } catch (final IOException e) {
+            LOG.error("Failed to read bytes from {}", uri, e);
+            return;
+        }
+
+        parseBytes();
+    }
+
+    public boolean isValid() {
+        return valid;
+    }
+
+    public boolean isWatchface() {
+        return typeWatchface;
+    }
+
+    public boolean isFirmware() {
+        return typeFirmware;
+    }
+
+    public String getDetails() {
+        return name != null ? name : (version != null ? version : "UNKNOWN");
+    }
+
+    public byte[] getBytes() {
+        return fw;
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public String getVersion() {
+        return version;
+    }
+
+    public void unsetFwBytes() {
+        this.fw = null;
+    }
+
+    private void parseBytes() {
+        if (parseAsWatchface()) {
+            assert id != null;
+            valid = true;
+            typeWatchface = true;
+        } else if (parseAsFirmware()) {
+            assert version != null;
+            valid = true;
+            typeFirmware = true;
+        } else {
+            valid = false;
+        }
+    }
+
+    private int findBestI18n(final long availableLocales) {
+        // try to find a locale matching the user's preferences amongst locales available
+        final Locale userLocale = GBApplication.getLanguage();
+        int found = -1, fallbackLocale = -1;
+        for (int i = 0; i < FACE_LOCALE_BITMAP_LIST.length; i++) {
+            if ((availableLocales & (1L << i)) != 0) {
+                final String[] localeParts = FACE_LOCALE_BITMAP_LIST[i].split("_");
+                final Locale locale = new Locale(localeParts[0], localeParts[1]);
+
+                if (locale.getLanguage().equals(userLocale.getLanguage())) {
+                    // return for this entry if locale matches on language and country
+                    if (locale.getCountry().equals(userLocale.getCountry())) {
+                        LOG.debug("Found locale match: {}", FACE_LOCALE_BITMAP_LIST[i]);
+                        return i;
+                    }
+
+                    // keep scanning as a better locale might exist (Portuguese)
+                    LOG.debug("Found language match for {}_{}: {}",
+                            userLocale.getLanguage(),
+                            userLocale.getCountry(),
+                            FACE_LOCALE_BITMAP_LIST[i]);
+                    found = i;
+                }
+
+                // keep first found available locale as fallback
+                if (fallbackLocale == -1) {
+                    fallbackLocale = i;
+                }
+            }
+        }
+
+        if (found != -1) {
+            return found;
+        }
+
+        if (fallbackLocale != -1) {
+            LOG.debug("Using fallback locale {}", FACE_LOCALE_BITMAP_LIST[fallbackLocale]);
+            return fallbackLocale;
+        }
+
+        LOG.debug("No translation available");
+        return -1;
+    }
+
+    private String extractTranslatableString(final int tableOffset, final int tableSize) {
+        if (tableOffset < 0 || tableSize < 0 || tableOffset + tableSize > fw.length) {
+            LOG.error("i18n table out-of-bounds");
+            return null;
+        }
+
+        final ByteBuffer bb = ByteBuffer.wrap(fw, tableOffset, tableSize).order(ByteOrder.LITTLE_ENDIAN);
+        long availableLocalizations = bb.getLong();
+        final int localizationsCount = Long.bitCount(availableLocalizations);
+
+        if (tableSize < localizationsCount * 4 + 8) {
+            LOG.error("cannot decode i18n table (at least {} bytes required for length table, but localization block is only {} bytes)",
+                    localizationsCount * 4 + 8,
+                    tableSize);
+            return null;
+        }
+
+        final int targetLocale = findBestI18n(availableLocalizations);
+        if (targetLocale == -1) {
+            return null;
+        }
+
+        int targetOffset = 0, targetSize = 0;
+        for (int i = 0; availableLocalizations != 0; i++) {
+            if ((availableLocalizations & 1L) != 0) {
+                final int size = bb.getInt();
+
+                if (i < targetLocale) {
+                    targetOffset += size;
+                }
+
+                if (i == targetLocale) {
+                    targetSize = size;
+                }
+            }
+            availableLocalizations >>= 1;
+        }
+
+        if (bb.remaining() < targetOffset + targetSize) {
+            LOG.error("cannot extract localization (at least {} bytes required, but only {} bytes remaining)",
+                    targetOffset + targetSize,
+                    bb.remaining());
+            return null;
+        }
+
+        bb.position(bb.position() + targetOffset);
+        final byte[] localizationBytes = new byte[targetSize];
+        bb.get(localizationBytes);
+        return new String(localizationBytes, StandardCharsets.UTF_8);
+    }
+
+    public Bitmap getWatchfacePreview() {
+        if (!isWatchface() || fw == null) {
+            return null;
+        }
+
+        final ByteBuffer bb = ByteBuffer.wrap(fw).order(ByteOrder.LITTLE_ENDIAN);
+        final int previewOffset = bb.getInt(0x20);
+        if (previewOffset == 0) {
+            LOG.debug("No preview available (at offset 0)");
+            return null;
+        }
+
+        if (previewOffset + 12 > fw.length) {
+            LOG.debug("No preview available (header out-of-bounds)");
+            return null;
+        }
+
+        bb.position(previewOffset);
+        final int bitmapType = bb.get() & 0xff;
+        final int compressionType = bb.get() & 0xff;
+        bb.getShort(); // ignore
+        final int width = bb.getShort() & 0xffff;
+        final int height = bb.getShort() & 0xffff;
+        final int bitmapSize = bb.getInt();
+
+        byte[] bitmapData = new byte[bitmapSize];
+        bb.get(bitmapData);
+
+        if (compressionType != 0) {
+            LOG.debug("Preview image compression type: {}", compressionType);
+            switch (compressionType) {
+                case 4:
+                    bitmapData = XiaomiBitmapUtils.decompressLvglRleV2(bitmapData);
+                    break;
+                case 8:
+                    bitmapData = XiaomiBitmapUtils.decompressLvglRleV1(bitmapData);
+                    break;
+                default:
+                    LOG.error("unknown compression type {}", compressionType);
+                    return null;
+            }
+
+            if (bitmapData == null) {
+                LOG.error("decompression returned null");
+                return null;
+            }
+        }
+
+        return XiaomiBitmapUtils.decodeWatchfaceImage(
+                bitmapData,
+                bitmapType,
+                compressionType == 8,
+                width,
+                height
+        );
+    }
+
+    private boolean parseAsWatchface() {
+        if (fw[0] != (byte) 0x5A || fw[1] != (byte) 0xA5) {
+            LOG.warn("File header not a watchface");
+            return false;
+        }
+
+        id = StringUtils.untilNullTerminator(fw, 0x28);
+
+        if (id == null) {
+            LOG.warn("id not found in {}", uri);
+            return false;
+        }
+
+        if (!Pattern.matches("^\\d+$", id)) {
+            LOG.warn("Id {} not a number", id);
+            return false;
+        }
+
+        if (ArrayUtils.equals(fw, new byte[]{(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff}, 0x68)) {
+            // name is localized
+            LOG.debug("detected translatable face name");
+            name = ""; // default value in case localization fails
+            final int tableOffset = BLETypeConversions.toUint32(fw, 0x74);
+            final int tableSize = BLETypeConversions.toUint32(fw, 0x78);
+
+            if (tableSize > 8) {
+                final String localized = extractTranslatableString(tableOffset, tableSize);
+
+                if (localized != null) {
+                    name = localized;
+                }
+            }
+        } else {
+            name = StringUtils.untilNullTerminator(fw, 0x68);
+
+            if (name == null) {
+                LOG.warn("name not found in {}", uri);
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    private boolean parseAsFirmware() {
+        // TODO parse and set version
+        return false;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiHeartRateRestingSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiHeartRateRestingSampleProvider.java
new file mode 100644
index 0000000..1a66b82
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiHeartRateRestingSampleProvider.java
@@ -0,0 +1,113 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.xiaomi;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.TimeSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.XiaomiDailySummarySample;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.HeartRateSample;
+
+public class XiaomiHeartRateRestingSampleProvider implements TimeSampleProvider<HeartRateSample> {
+    private final XiaomiDailySummarySampleProvider dailySummarySampleProvider;
+
+    public XiaomiHeartRateRestingSampleProvider(final GBDevice device, final DaoSession session) {
+        this.dailySummarySampleProvider = new XiaomiDailySummarySampleProvider(device, session);
+    }
+
+    @NonNull
+    @Override
+    public List<HeartRateSample> getAllSamples(final long timestampFrom, final long timestampTo) {
+        final List<XiaomiDailySummarySample> allSamples = dailySummarySampleProvider.getAllSamples(timestampFrom, timestampTo);
+        final List<HeartRateSample> ret = new ArrayList<>(allSamples.size());
+        for (final XiaomiDailySummarySample sample : allSamples) {
+            ret.add(new XiaomiHeartRateRestingSample(sample));
+        }
+        return ret;
+    }
+
+    @Override
+    public void addSample(final HeartRateSample timeSample) {
+        throw new UnsupportedOperationException("This sample provider is read-only!");
+    }
+
+    @Override
+    public void addSamples(final List<HeartRateSample> timeSamples) {
+        throw new UnsupportedOperationException("This sample provider is read-only!");
+    }
+
+    @Override
+    public HeartRateSample createSample() {
+        throw new UnsupportedOperationException("This sample provider is read-only!");
+    }
+
+    @Nullable
+    @Override
+    public HeartRateSample getLatestSample() {
+        final XiaomiDailySummarySample sample = dailySummarySampleProvider.getLatestSample();
+        if (sample != null) {
+            return new XiaomiHeartRateRestingSample(sample);
+        }
+        return null;
+    }
+
+    @Nullable
+    @Override
+    public HeartRateSample getLatestSample(final long until) {
+        final XiaomiDailySummarySample sample = dailySummarySampleProvider.getLatestSample(until);
+        if (sample != null) {
+            return new XiaomiHeartRateRestingSample(sample);
+        }
+        return null;
+    }
+
+    @Nullable
+    @Override
+    public HeartRateSample getFirstSample() {
+        final XiaomiDailySummarySample sample = dailySummarySampleProvider.getFirstSample();
+        if (sample != null) {
+            return new XiaomiHeartRateRestingSample(sample);
+        }
+        return null;
+    }
+
+    public static class XiaomiHeartRateRestingSample implements HeartRateSample {
+        private final long timestamp;
+        private final int heartRate;
+
+        public XiaomiHeartRateRestingSample(final XiaomiDailySummarySample sample) {
+            this.timestamp = sample.getTimestamp();
+            this.heartRate = sample.getHrResting();
+        }
+
+        @Override
+        public long getTimestamp() {
+            return timestamp;
+        }
+
+        @Override
+        public int getHeartRate() {
+            return heartRate;
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiInstallHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiInstallHandler.java
new file mode 100644
index 0000000..86f1e25
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiInstallHandler.java
@@ -0,0 +1,93 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.xiaomi;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.net.Uri;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.InstallActivity;
+import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.GenericItem;
+
+public class XiaomiInstallHandler implements InstallHandler {
+    protected final Uri mUri;
+    protected final Context mContext;
+    protected final XiaomiFWHelper helper;
+
+    public XiaomiInstallHandler(final Uri uri, final Context context) {
+        this.mUri = uri;
+        this.mContext = context;
+        this.helper = new XiaomiFWHelper(uri, context);
+    }
+
+    @Override
+    public boolean isValid() {
+        return helper.isValid();
+    }
+
+    @Override
+    public void validateInstallation(final InstallActivity installActivity, final GBDevice device) {
+        if (device.isBusy()) {
+            installActivity.setInfoText(device.getBusyTask());
+            installActivity.setInstallEnabled(false);
+            return;
+        }
+
+        if (!device.isInitialized()) {
+            installActivity.setInfoText(mContext.getString(R.string.fwapp_install_device_not_ready));
+            installActivity.setInstallEnabled(false);
+            return;
+        }
+
+        if (!helper.isValid()) {
+            installActivity.setInfoText(mContext.getString(R.string.fwapp_install_device_not_supported));
+            installActivity.setInstallEnabled(false);
+            return;
+        }
+
+        final GenericItem installItem = new GenericItem();
+        if (helper.isWatchface()) {
+            installItem.setIcon(R.drawable.ic_watchface);
+            installItem.setName(mContext.getString(R.string.kind_watchface));
+
+            final Bitmap preview = helper.getWatchfacePreview();
+            if (preview != null) {
+                installItem.setPreview(preview);
+            }
+        } else if (helper.isFirmware()) {
+            installItem.setIcon(R.drawable.ic_firmware);
+            installItem.setName(mContext.getString(R.string.kind_firmware));
+        } else {
+            installItem.setIcon(R.drawable.ic_device_unknown);
+            installItem.setName(mContext.getString(R.string.kind_invalid));
+        }
+
+        installItem.setDetails(helper.getDetails());
+
+        installActivity.setInfoText(mContext.getString(R.string.firmware_install_warning, "(unknown)"));
+        installActivity.setInstallItem(installItem);
+        installActivity.setInstallEnabled(true);
+    }
+
+    @Override
+    public void onStartInstall(final GBDevice device) {
+        helper.unsetFwBytes(); // free up memory
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiManualSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiManualSampleProvider.java
new file mode 100644
index 0000000..55c0d76
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiManualSampleProvider.java
@@ -0,0 +1,125 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.xiaomi;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.List;
+
+import de.greenrobot.dao.AbstractDao;
+import de.greenrobot.dao.Property;
+import de.greenrobot.dao.query.QueryBuilder;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.AbstractTimeSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.entities.XiaomiManualSample;
+import nodomain.freeyourgadget.gadgetbridge.entities.XiaomiManualSampleDao;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class XiaomiManualSampleProvider extends AbstractTimeSampleProvider<XiaomiManualSample> {
+    public static final int TYPE_HR = 0x11;
+    public static final int TYPE_SPO2 = 0x12;
+    public static final int TYPE_STRESS = 0x13;
+    public static final int TYPE_TEMPERATURE = 0x44;
+
+    public XiaomiManualSampleProvider(final GBDevice device, final DaoSession session) {
+        super(device, session);
+    }
+
+    @NonNull
+    @Override
+    public AbstractDao<XiaomiManualSample, ?> getSampleDao() {
+        return getSession().getXiaomiManualSampleDao();
+    }
+
+    @NonNull
+    @Override
+    protected Property getTimestampSampleProperty() {
+        return XiaomiManualSampleDao.Properties.Timestamp;
+    }
+
+    @NonNull
+    @Override
+    protected Property getDeviceIdentifierSampleProperty() {
+        return XiaomiManualSampleDao.Properties.DeviceId;
+    }
+
+    @Override
+    public XiaomiManualSample createSample() {
+        return new XiaomiManualSample();
+    }
+
+    @Nullable
+    public XiaomiManualSample getLatestSample(final int type) {
+        final QueryBuilder<XiaomiManualSample> qb = getSampleDao().queryBuilder();
+        final Device dbDevice = DBHelper.findDevice(getDevice(), getSession());
+        if (dbDevice == null) {
+            // no device, no sample
+            return null;
+        }
+        final Property deviceProperty = getDeviceIdentifierSampleProperty();
+        qb.where(deviceProperty.eq(dbDevice.getId()))
+                .where(XiaomiManualSampleDao.Properties.Type.eq(type))
+                .orderDesc(getTimestampSampleProperty()).limit(1);
+        final List<XiaomiManualSample> samples = qb.build().list();
+        if (samples.isEmpty()) {
+            return null;
+        }
+        return samples.get(0);
+    }
+
+    @Nullable
+    public XiaomiManualSample getLatestSample(final int type, final long until) {
+        final QueryBuilder<XiaomiManualSample> qb = getSampleDao().queryBuilder();
+        final Device dbDevice = DBHelper.findDevice(getDevice(), getSession());
+        if (dbDevice == null) {
+            // no device, no sample
+            return null;
+        }
+        final Property deviceProperty = getDeviceIdentifierSampleProperty();
+        qb.where(deviceProperty.eq(dbDevice.getId()))
+                .where(XiaomiManualSampleDao.Properties.Timestamp.le(until))
+                .where(XiaomiManualSampleDao.Properties.Type.eq(type))
+                .orderDesc(getTimestampSampleProperty()).limit(1);
+        final List<XiaomiManualSample> samples = qb.build().list();
+        if (samples.isEmpty()) {
+            return null;
+        }
+        return samples.get(0);
+    }
+
+    @Nullable
+    public XiaomiManualSample getFirstSample(final int type) {
+        final QueryBuilder<XiaomiManualSample> qb = getSampleDao().queryBuilder();
+        final Device dbDevice = DBHelper.findDevice(getDevice(), getSession());
+        if (dbDevice == null) {
+            // no device, no sample
+            return null;
+        }
+        final Property deviceProperty = getDeviceIdentifierSampleProperty();
+        qb.where(deviceProperty.eq(dbDevice.getId()))
+                .where(XiaomiManualSampleDao.Properties.Type.eq(type))
+                .orderAsc(getTimestampSampleProperty()).limit(1);
+        final List<XiaomiManualSample> samples = qb.build().list();
+        if (samples.isEmpty()) {
+            return null;
+        }
+        return samples.get(0);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiPaiSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiPaiSampleProvider.java
new file mode 100644
index 0000000..1908046
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiPaiSampleProvider.java
@@ -0,0 +1,154 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.xiaomi;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.TimeSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.XiaomiDailySummarySample;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.PaiSample;
+
+public class XiaomiPaiSampleProvider implements TimeSampleProvider<PaiSample> {
+    private final XiaomiDailySummarySampleProvider dailySummarySampleProvider;
+
+    public XiaomiPaiSampleProvider(final GBDevice device, final DaoSession session) {
+        this.dailySummarySampleProvider = new XiaomiDailySummarySampleProvider(device, session);
+    }
+
+    @NonNull
+    @Override
+    public List<PaiSample> getAllSamples(final long timestampFrom, final long timestampTo) {
+        final List<XiaomiDailySummarySample> allSamples = dailySummarySampleProvider.getAllSamples(timestampFrom, timestampTo);
+        final List<PaiSample> ret = new ArrayList<>(allSamples.size());
+        for (final XiaomiDailySummarySample sample : allSamples) {
+            ret.add(new XiaomiPaiSample(sample));
+        }
+        return ret;
+    }
+
+    @Override
+    public void addSample(final PaiSample timeSample) {
+        throw new UnsupportedOperationException("This sample provider is read-only!");
+    }
+
+    @Override
+    public void addSamples(final List<PaiSample> timeSamples) {
+        throw new UnsupportedOperationException("This sample provider is read-only!");
+    }
+
+    @Override
+    public PaiSample createSample() {
+        throw new UnsupportedOperationException("This sample provider is read-only!");
+    }
+
+    @Nullable
+    @Override
+    public PaiSample getLatestSample() {
+        final XiaomiDailySummarySample sample = dailySummarySampleProvider.getLatestSample();
+        if (sample != null) {
+            return new XiaomiPaiSample(sample);
+        }
+        return null;
+    }
+
+    @Nullable
+    @Override
+    public PaiSample getLatestSample(final long until) {
+        final XiaomiDailySummarySample sample = dailySummarySampleProvider.getLatestSample(until);
+        if (sample != null) {
+            return new XiaomiPaiSample(sample);
+        }
+        return null;
+    }
+
+    @Nullable
+    @Override
+    public PaiSample getFirstSample() {
+        final XiaomiDailySummarySample sample = dailySummarySampleProvider.getFirstSample();
+        if (sample != null) {
+            return new XiaomiPaiSample(sample);
+        }
+        return null;
+    }
+
+    public static class XiaomiPaiSample implements PaiSample {
+        private final long timestamp;
+        private final int paiLow;
+        private final int paiModerate;
+        private final int paiHigh;
+        private final int paiTotal;
+
+        public XiaomiPaiSample(final XiaomiDailySummarySample sample) {
+            this.timestamp = sample.getTimestamp();
+            this.paiLow = sample.getVitalityIncreaseLight();
+            this.paiModerate = sample.getVitalityIncreaseModerate();
+            this.paiHigh = sample.getVitalityIncreaseHigh();
+            this.paiTotal = sample.getVitalityCurrent();
+        }
+
+        @Override
+        public long getTimestamp() {
+            return timestamp;
+        }
+
+        @Override
+        public float getPaiLow() {
+            return paiLow;
+        }
+
+        @Override
+        public float getPaiModerate() {
+            return paiModerate;
+        }
+
+        @Override
+        public float getPaiHigh() {
+            return paiHigh;
+        }
+
+        @Override
+        public int getTimeLow() {
+            return 0; // not supported
+        }
+
+        @Override
+        public int getTimeModerate() {
+            return 0; // not supported
+        }
+
+        @Override
+        public int getTimeHigh() {
+            return 0; // not supported
+        }
+
+        @Override
+        public float getPaiToday() {
+            return paiLow + paiModerate + paiHigh;
+        }
+
+        @Override
+        public float getPaiTotal() {
+            return paiTotal;
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiSampleProvider.java
new file mode 100644
index 0000000..c760688
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiSampleProvider.java
@@ -0,0 +1,211 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.xiaomi;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+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.XiaomiActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.entities.XiaomiActivitySampleDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.XiaomiSleepStageSample;
+import nodomain.freeyourgadget.gadgetbridge.entities.XiaomiSleepTimeSample;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.util.RangeMap;
+
+public class XiaomiSampleProvider extends AbstractSampleProvider<XiaomiActivitySample> {
+    private static final Logger LOG = LoggerFactory.getLogger(XiaomiSampleProvider.class);
+
+    public XiaomiSampleProvider(final GBDevice device, final DaoSession session) {
+        super(device, session);
+    }
+
+    @Override
+    public AbstractDao<XiaomiActivitySample, ?> getSampleDao() {
+        return getSession().getXiaomiActivitySampleDao();
+    }
+
+    @Nullable
+    @Override
+    protected Property getRawKindSampleProperty() {
+        return XiaomiActivitySampleDao.Properties.RawKind;
+    }
+
+    @NonNull
+    @Override
+    protected Property getTimestampSampleProperty() {
+        return XiaomiActivitySampleDao.Properties.Timestamp;
+    }
+
+    @NonNull
+    @Override
+    protected Property getDeviceIdentifierSampleProperty() {
+        return XiaomiActivitySampleDao.Properties.DeviceId;
+    }
+
+    @Override
+    public ActivityKind normalizeType(final int rawType) {
+        // TODO
+        return ActivityKind.fromCode(rawType);
+    }
+
+    @Override
+    public int toRawActivityKind(final ActivityKind activityKind) {
+        // TODO
+        return activityKind.getCode();
+    }
+
+    @Override
+    public float normalizeIntensity(final int rawIntensity) {
+        return rawIntensity / 100f;
+    }
+
+    @Override
+    public XiaomiActivitySample createActivitySample() {
+        return new XiaomiActivitySample();
+    }
+
+    @Override
+    protected List<XiaomiActivitySample> getGBActivitySamples(final int timestamp_from, final int timestamp_to) {
+        final List<XiaomiActivitySample> samples = super.getGBActivitySamples(timestamp_from, timestamp_to);
+
+        overlaySleep(samples, timestamp_from, timestamp_to);
+
+        return samples;
+    }
+
+    /**
+     * See {@link nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.activity.impl.SleepDetailsParser}
+     */
+    private static ActivityKind getActivityKindForSample(final XiaomiSleepStageSample sample) {
+        switch (sample.getStage()) {
+            case 2:
+                return ActivityKind.DEEP_SLEEP;
+            case 3:
+                return ActivityKind.LIGHT_SLEEP;
+            case 4:
+                return ActivityKind.REM_SLEEP;
+            default: // default to awake
+                return ActivityKind.UNKNOWN;
+        }
+    }
+
+    /**
+     * Overlay sleep states on activity samples, since they are stored on a separate table.
+     *
+     * @implNote In order to determine whether a sleep session was ongoing at the start of the
+     * given range and what the detected sleep stage was at that time, the last sleep stage and
+     * sleep time sample before the given range will be queried and included in the results if
+     * found.
+     */
+    public void overlaySleep(final List<XiaomiActivitySample> samples, final int timestamp_from, final int timestamp_to) {
+        final RangeMap<Long, ActivityKind> stagesMap = new RangeMap<>(RangeMap.Mode.LOWER_BOUND);
+
+        final XiaomiSleepTimeSampleProvider sleepTimeSampleProvider = new XiaomiSleepTimeSampleProvider(getDevice(), getSession());
+        final XiaomiSleepStageSampleProvider sleepStagesSampleProvider = new XiaomiSleepStageSampleProvider(getDevice(), getSession());
+
+        // First populate all samples within this range
+        final List<XiaomiSleepTimeSample> sleepTimesWithinRange = sleepTimeSampleProvider.getAllSamples(timestamp_from * 1000L, timestamp_to * 1000L);
+        LOG.trace("Found {} sleep samples between {} and {}", sleepTimesWithinRange.size(), timestamp_from, timestamp_to);
+
+        for (final XiaomiSleepTimeSample sleepTimeSample : sleepTimesWithinRange) {
+            stagesMap.put(sleepTimeSample.getWakeupTime(), ActivityKind.UNKNOWN);
+            stagesMap.put(sleepTimeSample.getTimestamp(), ActivityKind.LIGHT_SLEEP);
+        }
+
+        // Retrieve the last stage before this time range, as the user could have been asleep during
+        // the range transition
+        final XiaomiSleepStageSample lastSleepStageBeforeRange = sleepStagesSampleProvider.getLastSampleBefore(timestamp_from * 1000L);
+
+        if (lastSleepStageBeforeRange != null) {
+            LOG.debug("Last sleep stage before range: ts={}, stage={}", lastSleepStageBeforeRange.getTimestamp(), lastSleepStageBeforeRange.getStage());
+            stagesMap.put(lastSleepStageBeforeRange.getTimestamp(), getActivityKindForSample(lastSleepStageBeforeRange));
+        }
+
+        // Retrieve all sleep stage samples during the range
+        final List<XiaomiSleepStageSample> sleepStagesInRange = sleepStagesSampleProvider.getAllSamples(
+                timestamp_from * 1000L,
+                timestamp_to * 1000L
+        );
+
+        if (!sleepStagesInRange.isEmpty()) {
+            // We got actual sleep stages
+            LOG.debug("Found {} sleep stage samples between {} and {}", sleepStagesInRange.size(), timestamp_from, timestamp_to);
+
+            for (final XiaomiSleepStageSample stageSample : sleepStagesInRange) {
+                stagesMap.put(stageSample.getTimestamp(), getActivityKindForSample(stageSample));
+            }
+        }
+
+
+        // Find last sleep sample before the requested range, as the recorded wake up time may be
+        // in the current range
+        final XiaomiSleepTimeSample lastSleepTimesBeforeRange = sleepTimeSampleProvider.getLastSampleBefore(timestamp_from * 1000L);
+
+        if (lastSleepTimesBeforeRange != null) {
+            LOG.debug("Last sleep time before range: ts={}, stage={}", lastSleepTimesBeforeRange.getTimestamp(), lastSleepTimesBeforeRange);
+
+            stagesMap.put(lastSleepTimesBeforeRange.getWakeupTime(), ActivityKind.UNKNOWN);
+            stagesMap.put(lastSleepTimesBeforeRange.getTimestamp(), ActivityKind.LIGHT_SLEEP);
+        }
+
+        // Find all wake up and sleep samples in the current time range
+        final List<XiaomiSleepTimeSample> sleepTimesInRange = sleepTimeSampleProvider.getAllSamples(
+                timestamp_from * 1000L,
+                timestamp_to * 1000L
+        );
+
+        if (!sleepTimesInRange.isEmpty()) {
+            LOG.debug("Found {} sleep samples between {} and {}", sleepTimesInRange.size(), timestamp_from, timestamp_to);
+            for (final XiaomiSleepTimeSample stageSample : sleepTimesInRange) {
+                if (sleepStagesInRange.isEmpty()) {
+                    // Only overlay them as light sleep if we don't have actual sleep stages
+                    stagesMap.put(stageSample.getTimestamp(), ActivityKind.LIGHT_SLEEP);
+                }
+
+                // We need to set the wakeup times, because some bands don't report them in the stage samples (see #3502)
+                stagesMap.put(stageSample.getWakeupTime(), ActivityKind.UNKNOWN);
+            }
+        }
+
+        if (!stagesMap.isEmpty()) {
+            LOG.debug("Found {} sleep stage samples between {} and {}", stagesMap.size(), timestamp_from, timestamp_to);
+            // FIXME if no samples were retrieved from the database that were generated by other
+            //       activity files, the stages will not get overlayed/inserted and the sleep charts
+            //       will stay empty.
+
+            for (final XiaomiActivitySample sample : samples) {
+                final long ts = sample.getTimestamp() * 1000L;
+                final ActivityKind sleepType = stagesMap.get(ts);
+                if (sleepType != null && !sleepType.equals(ActivityKind.UNKNOWN)) {
+                    sample.setRawKind(sleepType.getCode());
+                    sample.setRawIntensity(ActivitySample.NOT_MEASURED);
+                }
+            }
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiSettingsCustomizer.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiSettingsCustomizer.java
new file mode 100644
index 0000000..8ba3acc
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiSettingsCustomizer.java
@@ -0,0 +1,92 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.xiaomi;
+
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsUtils.hidePrefIfNoneVisible;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsUtils.populateOrHideListPreference;
+
+import android.os.Parcel;
+
+import androidx.preference.Preference;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Set;
+
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+public class XiaomiSettingsCustomizer implements DeviceSpecificSettingsCustomizer {
+    @Override
+    public void onPreferenceChange(final Preference preference, final DeviceSpecificSettingsHandler handler) {
+    }
+
+    @Override
+    public void customizeSettings(final DeviceSpecificSettingsHandler handler, final Prefs prefs, final String rootKey) {
+        final Preference activityMonitoringPref = handler.findPreference(DeviceSettingsPreferenceConst.PREF_HEARTRATE_ACTIVITY_MONITORING);
+        if (activityMonitoringPref != null) {
+            activityMonitoringPref.setVisible(false);
+        }
+
+        final Preference hrAlertActivePref = handler.findPreference(DeviceSettingsPreferenceConst.PREF_HEARTRATE_ALERT_ACTIVE_HIGH_THRESHOLD);
+        if (hrAlertActivePref != null) {
+            hrAlertActivePref.setVisible(false);
+        }
+
+        populateOrHideListPreference(HuamiConst.PREF_DISPLAY_ITEMS_SORTABLE, handler, prefs);
+
+        hidePrefIfNoneVisible(handler, DeviceSettingsPreferenceConst.PREF_HEADER_DISPLAY, Arrays.asList(
+                HuamiConst.PREF_DISPLAY_ITEMS_SORTABLE,
+                DeviceSettingsPreferenceConst.PREF_SCREEN_PASSWORD
+        ));
+        hidePrefIfNoneVisible(handler, "pref_header_other", Arrays.asList(
+                "pref_contacts",
+                "camera_remote",
+                "screen_events_forwarding",
+                "phone_silent_mode"
+        ));
+    }
+
+    @Override
+    public Set<String> getPreferenceKeysWithSummary() {
+        return Collections.emptySet();
+    }
+
+    public static final Creator<XiaomiSettingsCustomizer> CREATOR = new Creator<XiaomiSettingsCustomizer>() {
+        @Override
+        public XiaomiSettingsCustomizer createFromParcel(final Parcel in) {
+            return new XiaomiSettingsCustomizer();
+        }
+
+        @Override
+        public XiaomiSettingsCustomizer[] newArray(final int size) {
+            return new XiaomiSettingsCustomizer[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/xiaomi/XiaomiSleepStageSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiSleepStageSampleProvider.java
new file mode 100644
index 0000000..f26bf3e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiSleepStageSampleProvider.java
@@ -0,0 +1,56 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.xiaomi;
+
+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.XiaomiSleepStageSample;
+import nodomain.freeyourgadget.gadgetbridge.entities.XiaomiSleepStageSampleDao;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class XiaomiSleepStageSampleProvider extends AbstractTimeSampleProvider<XiaomiSleepStageSample> {
+    public XiaomiSleepStageSampleProvider(final GBDevice device, final DaoSession session) {
+        super(device, session);
+    }
+
+    @NonNull
+    @Override
+    public AbstractDao<XiaomiSleepStageSample, ?> getSampleDao() {
+        return getSession().getXiaomiSleepStageSampleDao();
+    }
+
+    @NonNull
+    @Override
+    protected Property getTimestampSampleProperty() {
+        return XiaomiSleepStageSampleDao.Properties.Timestamp;
+    }
+
+    @NonNull
+    @Override
+    protected Property getDeviceIdentifierSampleProperty() {
+        return XiaomiSleepStageSampleDao.Properties.DeviceId;
+    }
+
+    @Override
+    public XiaomiSleepStageSample createSample() {
+        return new XiaomiSleepStageSample();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiSleepTimeSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiSleepTimeSampleProvider.java
new file mode 100644
index 0000000..2254886
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiSleepTimeSampleProvider.java
@@ -0,0 +1,56 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.xiaomi;
+
+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.XiaomiSleepTimeSample;
+import nodomain.freeyourgadget.gadgetbridge.entities.XiaomiSleepTimeSampleDao;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class XiaomiSleepTimeSampleProvider extends AbstractTimeSampleProvider<XiaomiSleepTimeSample> {
+    public XiaomiSleepTimeSampleProvider(final GBDevice device, final DaoSession session) {
+        super(device, session);
+    }
+
+    @NonNull
+    @Override
+    public AbstractDao<XiaomiSleepTimeSample, ?> getSampleDao() {
+        return getSession().getXiaomiSleepTimeSampleDao();
+    }
+
+    @NonNull
+    @Override
+    protected Property getTimestampSampleProperty() {
+        return XiaomiSleepTimeSampleDao.Properties.Timestamp;
+    }
+
+    @NonNull
+    @Override
+    protected Property getDeviceIdentifierSampleProperty() {
+        return XiaomiSleepTimeSampleDao.Properties.DeviceId;
+    }
+
+    @Override
+    public XiaomiSleepTimeSample createSample() {
+        return new XiaomiSleepTimeSample();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiSpo2SampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiSpo2SampleProvider.java
new file mode 100644
index 0000000..93dc821
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiSpo2SampleProvider.java
@@ -0,0 +1,66 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.xiaomi;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.AbstractSampleToTimeSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.XiaomiActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.Spo2Sample;
+
+public class XiaomiSpo2SampleProvider extends AbstractSampleToTimeSampleProvider<Spo2Sample, XiaomiActivitySample> {
+    public XiaomiSpo2SampleProvider(final GBDevice device, final DaoSession session) {
+        super(new XiaomiSampleProvider(device, session), device, session);
+    }
+
+    @Override
+    protected Spo2Sample convertSample(final XiaomiActivitySample sample) {
+        if (sample.getSpo2() == null || sample.getSpo2() == 0) {
+            return null;
+        }
+
+        return new XiaomiSpo2Sample(
+                sample.getTimestamp() * 1000L,
+                sample.getSpo2()
+        );
+    }
+
+    protected static class XiaomiSpo2Sample implements Spo2Sample {
+        private final long timestamp;
+        private final int spo2;
+
+        public XiaomiSpo2Sample(final long timestamp, final int spo2) {
+            this.timestamp = timestamp;
+            this.spo2 = spo2;
+        }
+
+        @Override
+        public long getTimestamp() {
+            return timestamp;
+        }
+
+        @Override
+        public Type getType() {
+            return Type.UNKNOWN;
+        }
+
+        @Override
+        public int getSpo2() {
+            return spo2;
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiStressSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiStressSampleProvider.java
new file mode 100644
index 0000000..2695ae9
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiStressSampleProvider.java
@@ -0,0 +1,66 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.xiaomi;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.AbstractSampleToTimeSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.XiaomiActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.StressSample;
+
+public class XiaomiStressSampleProvider extends AbstractSampleToTimeSampleProvider<StressSample, XiaomiActivitySample> {
+    public XiaomiStressSampleProvider(final GBDevice device, final DaoSession session) {
+        super(new XiaomiSampleProvider(device, session), device, session);
+    }
+
+    @Override
+    protected StressSample convertSample(final XiaomiActivitySample sample) {
+        if (sample.getStress() == null || sample.getStress() == 0) {
+            return null;
+        }
+
+        return new XiaomiStressSample(
+                sample.getTimestamp() * 1000L,
+                sample.getStress()
+        );
+    }
+
+    protected static class XiaomiStressSample implements StressSample {
+        private final long timestamp;
+        private final int stress;
+
+        public XiaomiStressSample(final long timestamp, final int stress) {
+            this.timestamp = timestamp;
+            this.stress = stress;
+        }
+
+        @Override
+        public long getTimestamp() {
+            return timestamp;
+        }
+
+        @Override
+        public Type getType() {
+            return Type.UNKNOWN;
+        }
+
+        @Override
+        public int getStress() {
+            return stress;
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiTemperatureSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiTemperatureSampleProvider.java
new file mode 100644
index 0000000..78ce1cc
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiTemperatureSampleProvider.java
@@ -0,0 +1,126 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.xiaomi;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.TimeSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.XiaomiManualSample;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.TemperatureSample;
+
+public class XiaomiTemperatureSampleProvider implements TimeSampleProvider<TemperatureSample> {
+    private final XiaomiManualSampleProvider manualSampleProvider;
+
+    public XiaomiTemperatureSampleProvider(final GBDevice device, final DaoSession session) {
+        manualSampleProvider = new XiaomiManualSampleProvider(device, session);
+    }
+
+    @NonNull
+    @Override
+    public List<TemperatureSample> getAllSamples(final long timestampFrom, final long timestampTo) {
+        final List<XiaomiManualSample> manualSamples = manualSampleProvider.getAllSamples(timestampFrom, timestampTo);
+
+        final List<TemperatureSample> temperatureSamples = new ArrayList<>();
+
+        for (final XiaomiManualSample manualSample : manualSamples) {
+            if (XiaomiManualSampleProvider.TYPE_TEMPERATURE == manualSample.getType()) {
+                temperatureSamples.add(new XiaomiTemperatureSample(manualSample));
+            }
+        }
+
+        return temperatureSamples;
+    }
+
+    @Override
+    public void addSample(final TemperatureSample timeSample) {
+        throw new UnsupportedOperationException("read-only sample provider");
+    }
+
+    @Override
+    public void addSamples(final List<TemperatureSample> timeSamples) {
+        throw new UnsupportedOperationException("read-only sample provider");
+    }
+
+    @Override
+    public TemperatureSample createSample() {
+        throw new UnsupportedOperationException("read-only sample provider");
+    }
+
+    @Nullable
+    @Override
+    public TemperatureSample getLatestSample() {
+        final XiaomiManualSample sample = manualSampleProvider.getLatestSample(XiaomiManualSampleProvider.TYPE_TEMPERATURE);
+        if (sample != null) {
+            return new XiaomiTemperatureSample(sample);
+        }
+        return null;
+    }
+
+    @Nullable
+    @Override
+    public TemperatureSample getLatestSample(final long until) {
+        final XiaomiManualSample sample = manualSampleProvider.getLatestSample(XiaomiManualSampleProvider.TYPE_TEMPERATURE, until);
+        if (sample != null) {
+            return new XiaomiTemperatureSample(sample);
+        }
+        return null;
+    }
+
+    @Nullable
+    @Override
+    public TemperatureSample getFirstSample() {
+        final XiaomiManualSample sample = manualSampleProvider.getFirstSample(XiaomiManualSampleProvider.TYPE_TEMPERATURE);
+        if (sample != null) {
+            return new XiaomiTemperatureSample(sample);
+        }
+        return null;
+    }
+
+    protected static class XiaomiTemperatureSample implements TemperatureSample {
+        private final long timestamp;
+        private final float temperature;
+
+        public XiaomiTemperatureSample(final XiaomiManualSample sample) {
+            this.timestamp = sample.getTimestamp();
+            // first 2 bytes are body temperature
+            // last 2 bytes are skin temperature
+            // since the body temperature seems to always be 0, we only display skin temperature
+            this.temperature = (sample.getValue() & 0xffff) / 100f;
+        }
+
+        @Override
+        public long getTimestamp() {
+            return timestamp;
+        }
+
+        @Override
+        public float getTemperature() {
+            return temperature;
+        }
+
+        @Override
+        public int getTemperatureType() {
+            return 0; // ?
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiWeatherConditions.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiWeatherConditions.java
new file mode 100644
index 0000000..d32cc12
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiWeatherConditions.java
@@ -0,0 +1,186 @@
+/*  Copyright (C) 2017-2024 Andreas Shimokawa, Yoran Vulker
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.xiaomi;
+
+public class XiaomiWeatherConditions {
+    // Most of these conditions are the same on Huami and ZeppOS (see HuamiWeatherConditions), but
+    // some have been renamed and a few were added. Nonetheless, some more detailed conditions,
+    // such as drizzle, tornado, and tropical storm, do not have an equivalent
+    // These conditions were mapped by sending the current condition and iterating over the condition
+    // code.
+    // At least the Xiaomi Watch S1 Active is known to crash if the condition cannot be mapped
+    // to an icon while shown (above 33), so take that into account when adding new conditions below.
+    public static final byte CLEAR_SKY                   = 0;
+    public static final byte CLOUDY                      = 1; // may appear with a moon on some models
+    public static final byte OVERCAST                    = 2;
+    public static final byte SHOWER                      = 3; // 'light rain' (two drops) on MB8P
+    public static final byte THUNDERSTORM                = 4;
+    public static final byte HAIL                        = 5;
+    public static final byte SLEET                       = 6;
+    public static final byte LIGHT_RAIN                  = 7; // 'light rain' (two drops) on MB8P
+    public static final byte MODERATE_RAIN               = 8; // 'moderate rain' (three drops) on MB8P
+    public static final byte HEAVY_RAINFALL              = 9; // 'heavy rain' (four drops) on MB8P
+    public static final byte RAINSTORM                   = 10; // 'heavy rain' (four drops) on MB8P
+    public static final byte DOWNPOUR                    = 11; // 'heavy rain' (four drops) on MB8P
+    public static final byte HEAVY_RAINSTORM             = 12; // 'heavy rain' (four drops) on MB8P
+    public static final byte SNOW_SHOWERS                = 13; // 'light snow' (1 flake) on MB8P
+    public static final byte LIGHT_SNOW                  = 14; // 'light snow' (1 flake) on MB8P
+    public static final byte MODERATE_SNOW               = 15; // 'moderate snow' (1 large and 1 small flake) on MB8P
+    public static final byte HEAVY_SNOW                  = 16; // 'heavy snow' (multiple flakes}) on MB8P
+    public static final byte BLIZZARD                    = 17; // 'heavy snow' (multiple flakes}) on MB8P
+    public static final byte MIST                        = 18;
+    public static final byte FREEZING_RAIN               = 19; // 'sleet' on MB8P
+    public static final byte SANDSTORM                   = 20; // sandstorm
+    public static final byte LIGHT_TO_MODERATE_RAIN      = 21; // 'moderate rain' on MB8P
+    public static final byte MODERATE_TO_HEAVY_RAIN      = 22; // 'heavy rain' on MB8P
+    public static final byte HEAVY_RAIN_TO_RAINSTORM     = 23; // 'heavy rain' on MB8P
+    public static final byte RAINSTORM_TO_DOWNPOUR       = 24; // 'heavy rain' on MB8P
+    public static final byte DOWNPOUR_TO_HEAVY_RAINSTORM = 25; // 'heavy rain' on MB8P
+    public static final byte LIGHT_TO_MODERATE_SNOW      = 26; // 'moderate snow' on MB8P
+    public static final byte MODERATE_TO_HEAVY_SNOW      = 27; // 'heavy snow' on MB8P
+    public static final byte HEAVY_SNOW_TO_BLIZZARD      = 28; // 'heavy snow' on MB8P
+    public static final byte DUST                        = 29; // 'sandstorm' on MB8P
+    public static final byte WINDY                       = 30; // 'sandstorm' on MB8P
+    public static final byte STRONG_SANDSTORM            = 31; // 'sandstorm' on MB8P
+    public static final byte MODERATE_FOG                = 32; // 'mist' on MB8P
+    public static final byte SNOW                        = 33; // 'light snow' on MB8P
+    // Please read the comment above before adding new condition codes here
+
+    public static byte convertOwmConditionToXiaomi(int openWeatherMapCondition) {
+        // openweathermap.org conditions:
+        // http://openweathermap.org/weather-conditions
+        switch (openWeatherMapCondition) {
+//Group 2xx: Thunderstorm
+            case 200:  // thunderstorm with light rain
+            case 201:  // thunderstorm with rain
+            case 202:  // thunderstorm with heavy rain
+            case 210:  // light thunderstorm
+            case 211:  // thunderstorm
+            case 212:  // heavy thunderstorm
+            case 221:  // ragged thunderstorm
+            case 230:  // thunderstorm with light drizzle
+            case 231:  // thunderstorm with drizzle
+            case 232:  // thunderstorm with heavy drizzle
+                return THUNDERSTORM;
+//Group 3xx: Drizzle
+            case 300:  // light intensity drizzle
+            case 301:  // drizzle
+            case 302:  // heavy intensity drizzle
+            case 310:  // light intensity drizzle rain
+            case 311:  // drizzle rain
+            case 312:  // heavy intensity drizzle rain
+            case 313:  // shower rain and drizzle
+            case 314:  // heavy shower rain and drizzle
+            case 321:  // shower drizzle
+                // for lack of a better transcription, drizzle -> light rain
+//Group 5xx: Rain
+            case 500:  // light rain
+                return LIGHT_RAIN;
+            case 501:  // moderate rain
+                return MODERATE_RAIN;
+            case 502:  // heavy intensity rain
+                return RAINSTORM;
+            case 503:  // very heavy rain
+                return DOWNPOUR;
+            case 504:  // extreme rain
+                return HEAVY_RAINSTORM;
+            case 511:  // freezing rain
+                return FREEZING_RAIN;
+            case 520:  // light intensity shower rain
+            case 521:  // shower rain
+                return SHOWER;
+            case 522:  // heavy intensity shower rain
+            case 531:  // ragged shower rain
+                return HEAVY_RAINFALL;
+
+//Group 6xx: Snow
+            case 600:  // light snow
+                return LIGHT_SNOW;
+            case 601:  // snow
+                return MODERATE_SNOW;
+            case 602:  // heavy snow
+                return HEAVY_SNOW;
+            case 611:  // sleet
+            case 612:  // shower sleet
+                return SLEET;
+            case 615:  // light rain and snow
+            case 616:  // rain and snow
+                return SNOW_SHOWERS;
+            case 620:  // light shower snow
+            case 621:  // shower snow
+                return HEAVY_SNOW;
+            case 622:  // heavy shower snow
+                return BLIZZARD;
+
+//Group 7xx: Atmosphere
+            case 701:  // mist
+            case 711:  // smoke
+            case 721:  // haze
+            case 731:  // sand/dust whirls
+            case 741:  // fog
+                return MIST;
+            case 751:  // sand
+                return SANDSTORM;
+            case 761:  // dust
+            case 762:  // volcanic ash
+            case 771:  // squalls
+                return DUST;
+            case 781:  // tornado
+            case 900:  // tornado
+                return WINDY;
+//Group 800: Clear
+            case 800:  // clear sky
+                return CLEAR_SKY;
+//Group 80x: Clouds
+            case 801:  // few clouds
+            case 802:  // scattered clouds
+            case 803:  // broken clouds
+            case 804:  // overcast clouds
+                return OVERCAST;
+//Group 90x: Extreme
+            case 901:  // tropical storm
+                return WINDY;
+            case 903:  // cold
+            case 904:  // hot
+                return CLEAR_SKY;
+            case 905:  // windy
+                return WINDY;
+            case 906:  // hail
+                return HAIL;
+//Group 9xx: Additional
+            case 951:  // calm
+            case 952:  // light breeze
+            case 953:  // gentle breeze
+            case 954:  // moderate breeze
+            case 955:  // fresh breeze
+                return CLEAR_SKY;
+
+            case 956:  // strong breeze
+            case 957:  // high wind/near gale
+            case 958:  // gale
+            case 959:  // severe gale
+            case 960:  // storm
+            case 961:  // violent storm
+            case 902:  // hurricane
+            case 962:  // hurricane
+                return WINDY;
+
+            default:
+                return CLEAR_SKY;
+        }
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiWidgetManager.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiWidgetManager.java
new file mode 100644
index 0000000..ccc46bf
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiWidgetManager.java
@@ -0,0 +1,525 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.xiaomi;
+
+import android.text.TextUtils;
+
+import androidx.annotation.Nullable;
+
+import com.google.protobuf.InvalidProtocolBufferException;
+
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Objects;
+import java.util.Set;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+import nodomain.freeyourgadget.gadgetbridge.capabilities.widgets.WidgetLayout;
+import nodomain.freeyourgadget.gadgetbridge.capabilities.widgets.WidgetManager;
+import nodomain.freeyourgadget.gadgetbridge.capabilities.widgets.WidgetPart;
+import nodomain.freeyourgadget.gadgetbridge.capabilities.widgets.WidgetPartSubtype;
+import nodomain.freeyourgadget.gadgetbridge.capabilities.widgets.WidgetScreen;
+import nodomain.freeyourgadget.gadgetbridge.capabilities.widgets.WidgetType;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.proto.xiaomi.XiaomiProto;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.XiaomiPreferences;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+public class XiaomiWidgetManager implements WidgetManager {
+    private static final Logger LOG = LoggerFactory.getLogger(XiaomiWidgetManager.class);
+
+    private final GBDevice device;
+
+    public XiaomiWidgetManager(final GBDevice device) {
+        this.device = device;
+    }
+
+    @Override
+    public List<WidgetLayout> getSupportedWidgetLayouts() {
+        final List<WidgetLayout> layouts = new ArrayList<>();
+
+        final XiaomiProto.WidgetScreens widgetScreens = getRawWidgetScreens();
+        if (!widgetScreens.hasWidgetsCapabilities() || !widgetScreens.getWidgetsCapabilities().hasSupportedLayoutStyles()) {
+            return Collections.emptyList();
+        }
+
+        final int supportedBitmap = getRawWidgetScreens().getWidgetsCapabilities().getSupportedLayoutStyles();
+
+        // highest known layout style is 0x4000 (1 << 14)
+        for (int i = 0; i < 15; i++) {
+            final int layoutStyleId = 1 << i;
+            if ((supportedBitmap & layoutStyleId) != 0) {
+                layouts.add(fromRawLayout(layoutStyleId));
+            }
+        }
+
+        return layouts;
+    }
+
+    private static Collection<WidgetPartSubtype> convertWorkoutTypesToPartSubtypes(final Collection<XiaomiWorkoutType> workoutTypes) {
+        final List<WidgetPartSubtype> subtypes = new ArrayList<>(workoutTypes.size());
+
+        // convert workout types to subtypes
+        for (final XiaomiWorkoutType workoutType : workoutTypes) {
+            subtypes.add(new WidgetPartSubtype(
+                    String.valueOf(workoutType.getCode()),
+                    workoutType.getName()
+            ));
+        }
+
+        // sort by name before returning
+        Collections.sort(subtypes, (it, other) -> it.getName().compareToIgnoreCase(other.getName()));
+
+        return subtypes;
+    }
+
+    @Override
+    public List<WidgetPart> getSupportedWidgetParts(final WidgetType targetWidgetType) {
+        final List<WidgetPart> parts = new LinkedList<>();
+
+        final XiaomiProto.WidgetParts rawWidgetParts = getRawWidgetParts();
+
+        final Set<String> seenNames = new HashSet<>();
+        final Set<String> duplicatedNames = new HashSet<>();
+
+        // get supported workout types and convert to subtypes for workout widgets
+        final Collection<WidgetPartSubtype> subtypes = convertWorkoutTypesToPartSubtypes(XiaomiWorkoutType.getWorkoutTypesSupportedByDevice(getDevice()));
+
+        for (final XiaomiProto.WidgetPart widgetPart : rawWidgetParts.getWidgetPartList()) {
+            final WidgetPart convertedPart = fromRawWidgetPart(widgetPart, subtypes);
+
+            if (convertedPart == null) {
+                continue;
+            }
+
+            if (!convertedPart.getType().equals(targetWidgetType)) {
+                continue;
+            }
+
+            final String convertedPartName = convertedPart.getName();
+            if (seenNames.contains(convertedPartName)) {
+                duplicatedNames.add(convertedPartName);
+            } else {
+                seenNames.add(convertedPartName);
+            }
+
+            parts.add(convertedPart);
+            seenNames.add(convertedPart.getName());
+        }
+
+        // Ensure that all names are unique
+        for (final WidgetPart part : parts) {
+            if (duplicatedNames.contains(part.getFullName())) {
+                part.setName(String.format(Locale.ROOT, "%s (%s)", part.getName(), part.getId()));
+            }
+        }
+
+        Collections.sort(parts, (it, other) -> it.getName().compareToIgnoreCase(other.getName()));
+        return parts;
+    }
+
+    private WidgetPart fromRawWidgetPart(final XiaomiProto.WidgetPart widgetPart, final Collection<WidgetPartSubtype> subtypes) {
+        final WidgetType type = fromRawWidgetType(widgetPart.getType());
+
+        if (type == null) {
+            LOG.warn("Unknown widget type {}", widgetPart.getType());
+            return null;
+        }
+
+        final String stringifiedId = String.valueOf(widgetPart.getId());
+        final WidgetPart convertedPart = new WidgetPart(
+                stringifiedId,
+                GBApplication.getContext().getString(R.string.widget_name_untitled, stringifiedId),
+                type
+        );
+
+        if (!TextUtils.isEmpty(widgetPart.getTitle())) {
+            convertedPart.setName(widgetPart.getTitle());
+        } else {
+            // some models do not provide the name of the widget in the screens list, resolve it here
+            final XiaomiProto.WidgetPart resolvedPart = findRawPart(widgetPart.getType(), widgetPart.getId());
+            if (resolvedPart != null) {
+                convertedPart.setName(resolvedPart.getTitle());
+            }
+        }
+
+        if (widgetPart.getFunction() == 16) {
+            if (StringUtils.isBlank(convertedPart.getName())) {
+                convertedPart.setName(GBApplication.getContext().getString(R.string.menuitem_workout));
+            }
+
+            if (subtypes != null) {
+                convertedPart.getSupportedSubtypes().addAll(subtypes);
+
+                if (widgetPart.getSubType() != 0) {
+                    final String widgetSubtype = String.valueOf(widgetPart.getSubType());
+
+                    for (final WidgetPartSubtype availableSubtype : subtypes) {
+                        if (availableSubtype.getId().equals(widgetSubtype)) {
+                            convertedPart.setSubtype(availableSubtype);
+                            break;
+                        }
+                    }
+                }
+            }
+        }
+
+        if ((widgetPart.getId() & 256) != 0) {
+            convertedPart.setName(GBApplication.getContext().getString(R.string.widget_name_colored_tile, convertedPart.getName()));
+        }
+
+        return convertedPart;
+    }
+
+    @Override
+    public List<WidgetScreen> getWidgetScreens() {
+        final XiaomiProto.WidgetScreens rawWidgetScreens = getRawWidgetScreens();
+
+        final List<WidgetScreen> convertedScreens = new ArrayList<>(rawWidgetScreens.getWidgetScreenCount());
+        final Collection<WidgetPartSubtype> workoutTypes = convertWorkoutTypesToPartSubtypes(XiaomiWorkoutType.getWorkoutTypesSupportedByDevice(getDevice()));
+
+        for (final XiaomiProto.WidgetScreen rawScreen : rawWidgetScreens.getWidgetScreenList()) {
+            final WidgetLayout layout = fromRawLayout(rawScreen.getLayout());
+
+            final List<WidgetPart> convertedParts = new ArrayList<>(rawScreen.getWidgetPartCount());
+
+            for (final XiaomiProto.WidgetPart rawPart : rawScreen.getWidgetPartList()) {
+                final WidgetPart convertedPart = fromRawWidgetPart(rawPart, workoutTypes);
+
+                if (convertedPart == null) {
+                    LOG.warn("Widget cannot be converted, result was null for following raw widget: {}", rawPart);
+                    continue;
+                }
+
+                convertedParts.add(convertedPart);
+            }
+
+            convertedScreens.add(new WidgetScreen(
+                    String.valueOf(rawScreen.getId()),
+                    layout,
+                    convertedParts
+            ));
+        }
+
+        return convertedScreens;
+    }
+
+    @Override
+    public GBDevice getDevice() {
+        return device;
+    }
+
+    @Override
+    public int getMinScreens() {
+        return getRawWidgetScreens().getWidgetsCapabilities().getMinWidgets();
+    }
+
+    @Override
+    public int getMaxScreens() {
+        return getRawWidgetScreens().getWidgetsCapabilities().getMaxWidgets();
+    }
+
+    @Override
+    public void saveScreen(final WidgetScreen widgetScreen) {
+        final XiaomiProto.WidgetScreens rawWidgetScreens = getRawWidgetScreens();
+
+        final int layoutNum = toRawLayout(widgetScreen.getLayout());
+        if (layoutNum == -1) {
+            return;
+        }
+
+        XiaomiProto.WidgetScreen.Builder rawScreen = null;
+        if (widgetScreen.getId() == null) {
+            // new screen
+            rawScreen = XiaomiProto.WidgetScreen.newBuilder()
+                    .setId(rawWidgetScreens.getWidgetScreenCount() + 1); // ids start at 1
+        } else {
+            for (final XiaomiProto.WidgetScreen screen : rawWidgetScreens.getWidgetScreenList()) {
+                if (String.valueOf(screen.getId()).equals(widgetScreen.getId())) {
+                    rawScreen = XiaomiProto.WidgetScreen.newBuilder(screen);
+                    break;
+                }
+
+                LOG.warn("Failed to find original screen for {}", widgetScreen.getId());
+            }
+
+            if (rawScreen == null) {
+                rawScreen = XiaomiProto.WidgetScreen.newBuilder()
+                        .setId(rawWidgetScreens.getWidgetScreenCount() + 1);
+            }
+        }
+
+        rawScreen.setLayout(layoutNum);
+        rawScreen.clearWidgetPart();
+
+        final Collection<XiaomiWorkoutType> workoutTypes = XiaomiWorkoutType.getWorkoutTypesSupportedByDevice(getDevice());
+
+        for (final WidgetPart newPart : widgetScreen.getParts()) {
+            // Find the existing raw part
+            final XiaomiProto.WidgetPart knownRawPart = findRawPart(
+                    toRawWidgetType(newPart.getType()),
+                    Integer.parseInt(Objects.requireNonNull(newPart.getId()))
+            );
+
+            final XiaomiProto.WidgetPart.Builder newRawPartBuilder = XiaomiProto.WidgetPart.newBuilder(knownRawPart);
+
+            // TODO only support subtypes on widget with type 16
+            if (newPart.getSubtype() != null) {
+                try {
+                    final int rawSubtype = Integer.parseInt(newPart.getSubtype().getId());
+
+                    // Get the workout type as subtype
+                    for (final XiaomiWorkoutType workoutType : workoutTypes) {
+                        if (rawSubtype == workoutType.getCode()) {
+                            newRawPartBuilder.setSubType(workoutType.getCode());
+                            break;
+                        }
+                    }
+                } catch (final NumberFormatException ex) {
+                    LOG.error("Failed to convert workout type {} to a number, defaulting to 1", newPart.getSubtype());
+                    newRawPartBuilder.setSubType(1);
+                }
+            }
+
+            rawScreen.addWidgetPart(newRawPartBuilder);
+        }
+
+        final XiaomiProto.WidgetScreens.Builder builder = XiaomiProto.WidgetScreens.newBuilder(rawWidgetScreens);
+        if (rawScreen.getId() == rawWidgetScreens.getWidgetScreenCount() + 1) {
+            // Append at the end
+            builder.addWidgetScreen(rawScreen);
+        } else {
+            // Replace existing
+            builder.clearWidgetScreen();
+
+            for (final XiaomiProto.WidgetScreen screen : rawWidgetScreens.getWidgetScreenList()) {
+                if (screen.getId() == rawScreen.getId()) {
+                    builder.addWidgetScreen(rawScreen);
+                } else {
+                    builder.addWidgetScreen(screen);
+                }
+            }
+        }
+
+        builder.setIsFullList(1);
+
+        getPrefs().getPreferences().edit()
+                .putString(XiaomiPreferences.PREF_WIDGET_SCREENS, GB.hexdump(builder.build().toByteArray()))
+                .apply();
+    }
+
+    @Override
+    public void deleteScreen(final WidgetScreen widgetScreen) {
+        if (widgetScreen.getId() == null) {
+            LOG.warn("Can't delete screen without id");
+            return;
+        }
+
+        final XiaomiProto.WidgetScreens rawWidgetScreens = getRawWidgetScreens();
+
+        final XiaomiProto.WidgetScreens.Builder builder = XiaomiProto.WidgetScreens.newBuilder(rawWidgetScreens)
+                .clearWidgetScreen();
+
+        int i = 1;
+        for (final XiaomiProto.WidgetScreen screen : rawWidgetScreens.getWidgetScreenList()) {
+            if (String.valueOf(screen.getId()).equals(widgetScreen.getId())) {
+                continue;
+            }
+
+            // Ensure the IDs stay sequential and start at 1
+            builder.addWidgetScreen(
+                    XiaomiProto.WidgetScreen.newBuilder()
+                            .mergeFrom(screen)
+                            .setId(i++)
+                            .build()
+            );
+        }
+
+        getPrefs().getPreferences().edit()
+                .putString(XiaomiPreferences.PREF_WIDGET_SCREENS, GB.hexdump(builder.build().toByteArray()))
+                .apply();
+    }
+
+    @Override
+    public void sendToDevice() {
+        GBApplication.deviceService(getDevice()).onSendConfiguration(DeviceSettingsPreferenceConst.PREF_WIDGETS);
+    }
+
+    private Prefs getPrefs() {
+        return new Prefs(GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress()));
+    }
+
+    @Nullable
+    private WidgetType fromRawWidgetType(final int rawType) {
+        switch (rawType) {
+            case 1:
+                return WidgetType.SMALL;
+            case 2:
+                return WidgetType.WIDE;
+            case 3:
+                return WidgetType.TALL;
+            case 4:
+                return WidgetType.LARGE;
+            case 5:
+                return WidgetType.PORTRAIT_LARGE;
+            default:
+                LOG.warn("Unknown widget type {}", rawType);
+                return null;
+        }
+    }
+
+    private int toRawWidgetType(final WidgetType widgetType) {
+        switch (widgetType) {
+            case SMALL:
+                return 1;
+            case WIDE:
+                return 2;
+            case TALL:
+                return 3;
+            case LARGE:
+                return 4;
+            case PORTRAIT_LARGE:
+                return 5;
+            default:
+                throw new IllegalArgumentException("Unknown widget type " + widgetType);
+        }
+    }
+
+    @Nullable
+    private WidgetLayout fromRawLayout(final int rawLayout) {
+        switch (rawLayout) {
+            case 1: // 2x2, top 2x small, bottom 2x small
+                return WidgetLayout.TOP_2_BOT_2;
+            case 2: // 2x2, top wide, bottom 2x small
+                return WidgetLayout.TOP_1_BOT_2;
+            case 4: // 2x2, top 2x small, bottom wide
+                return WidgetLayout.TOP_2_BOT_1;
+            case 128: // 2x2, full screen
+                return WidgetLayout.TWO_BY_TWO_SINGLE;
+            case 256: // 1x2, top small, bottom small
+                return WidgetLayout.TWO;
+            case 512: // 1x2, full screen
+                return WidgetLayout.ONE_BY_TWO_SINGLE;
+            case 1024: // 2x3, top 2x small, bottom 2x2 square
+                return WidgetLayout.TOP_2_BOT_2X2;
+            case 2048: // 2x3, top 2x2 square, bottom 2x small
+                return WidgetLayout.TOP_2X2_BOT_2;
+            case 4096: // 2x3, top wide, bottom 2x2 small
+                return WidgetLayout.TOP_1_BOT_2X2;
+            case 8192: // 2x3, top 2x2 small, bottom wide
+                return WidgetLayout.TOP_2X2_BOT_1;
+            case 16384: // 2x3, full screen
+                return WidgetLayout.TWO_BY_THREE_SINGLE;
+            case 8: // 2x2, left tall, right 2x small
+            case 16: // 2x2, left 2x small, right tall
+            case 32: // 2x2, top wide, bottom wide
+            case 64: // 2x2, left tall, right tall
+            default:
+                LOG.warn("Unknown widget screens layout {}", rawLayout);
+                return null;
+        }
+    }
+
+    private int toRawLayout(final WidgetLayout layout) {
+        if (layout == null) {
+            return -1;
+        }
+
+        switch (layout) {
+            case TOP_2_BOT_2:
+                return 1;
+            case TOP_1_BOT_2:
+                return 2;
+            case TOP_2_BOT_1:
+                return 4;
+            case TWO_BY_TWO_SINGLE:
+                return 128;
+            case TWO:
+                return 256;
+            case ONE_BY_TWO_SINGLE:
+                return 512;
+            case TOP_2_BOT_2X2:
+                return 1024;
+            case TOP_2X2_BOT_2:
+                return 2048;
+            case TOP_1_BOT_2X2:
+                return 4096;
+            case TOP_2X2_BOT_1:
+                return 8192;
+            case TWO_BY_THREE_SINGLE:
+                return 16384;
+            default:
+                LOG.warn("Widget layout {} cannot be converted to raw variant", layout);
+                return -1;
+        }
+    }
+
+    @Nullable
+    private XiaomiProto.WidgetPart findRawPart(final int type, final int id) {
+        final XiaomiProto.WidgetParts rawWidgetParts = getRawWidgetParts();
+
+        for (final XiaomiProto.WidgetPart rawPart : rawWidgetParts.getWidgetPartList()) {
+            if (rawPart.getType() == type && rawPart.getId() == id) {
+                return rawPart;
+            }
+        }
+
+        return null;
+    }
+
+    private XiaomiProto.WidgetScreens getRawWidgetScreens() {
+        final String hex = getPrefs().getString(XiaomiPreferences.PREF_WIDGET_SCREENS, null);
+        if (hex == null) {
+            LOG.warn("raw widget screens hex is null");
+            return XiaomiProto.WidgetScreens.newBuilder().build();
+        }
+
+        try {
+            return XiaomiProto.WidgetScreens.parseFrom(GB.hexStringToByteArray(hex));
+        } catch (final InvalidProtocolBufferException e) {
+            LOG.warn("failed to parse raw widget screns hex");
+            return XiaomiProto.WidgetScreens.newBuilder().build();
+        }
+    }
+
+    private XiaomiProto.WidgetParts getRawWidgetParts() {
+        final String hex = getPrefs().getString(XiaomiPreferences.PREF_WIDGET_PARTS, null);
+        if (hex == null) {
+            LOG.warn("raw widget parts hex is null");
+            return XiaomiProto.WidgetParts.newBuilder().build();
+        }
+
+        try {
+            return XiaomiProto.WidgetParts.parseFrom(GB.hexStringToByteArray(hex));
+        } catch (final InvalidProtocolBufferException e) {
+            LOG.warn("failed to parse raw widget parts hex");
+            return XiaomiProto.WidgetParts.newBuilder().build();
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiWorkoutType.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiWorkoutType.java
new file mode 100644
index 0000000..0141710
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiWorkoutType.java
@@ -0,0 +1,89 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.xiaomi;
+
+import androidx.annotation.StringRes;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.XiaomiPreferences;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+public class XiaomiWorkoutType {
+    private final int code;
+    private final String name;
+
+    public XiaomiWorkoutType(final int code, final String name) {
+        this.code = code;
+        this.name = name;
+    }
+
+    public int getCode() {
+        return code;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    @StringRes
+    public static int mapWorkoutName(final int code) {
+        switch (code) {
+            case 1:
+                return R.string.activity_type_outdoor_running;
+            case 2:
+                return R.string.activity_type_walking;
+            case 3:
+                return R.string.activity_type_hiking;
+            case 4:
+                return R.string.activity_type_trekking;
+            case 5:
+                return R.string.activity_type_trail_run;
+            case 6:
+                return R.string.activity_type_outdoor_cycling;
+            case 501:
+                return R.string.activity_type_wrestling;
+        }
+
+        return -1;
+    }
+
+    public static Collection<XiaomiWorkoutType> getWorkoutTypesSupportedByDevice(final GBDevice device) {
+        final Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(device.getAddress()));
+        final List<String> codes = prefs.getList(XiaomiPreferences.PREF_WORKOUT_TYPES, Collections.emptyList());
+        final List<XiaomiWorkoutType> ret = new ArrayList<>(codes.size());
+
+        for (final String code : codes) {
+            final int codeInt = Integer.parseInt(code);
+            final int codeNameStringRes = XiaomiWorkoutType.mapWorkoutName(codeInt);
+            ret.add(new XiaomiWorkoutType(
+                    codeInt,
+                    codeNameStringRes != -1 ?
+                            GBApplication.getContext().getString(codeNameStringRes) :
+                            GBApplication.getContext().getString(R.string.widget_unknown_workout, code)
+            ));
+        }
+
+        return ret;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/miband7pro/MiBand7ProCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/miband7pro/MiBand7ProCoordinator.java
new file mode 100644
index 0000000..c96c803
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/miband7pro/MiBand7ProCoordinator.java
@@ -0,0 +1,56 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.miband7pro;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst;
+import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.XiaomiCoordinator;
+
+public class MiBand7ProCoordinator extends XiaomiCoordinator {
+    @Override
+    public boolean isExperimental() {
+        return true;
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile(HuamiConst.XIAOMI_SMART_BAND7_PRO_NAME + ".*");
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_miband7pro;
+    }
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_default;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_default_disabled;
+    }
+
+    @Override
+    public boolean supportsPai() {
+        // no PAI nor vitality score
+        return false;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/miband8/MiBand8Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/miband8/MiBand8Coordinator.java
new file mode 100644
index 0000000..db608a3
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/miband8/MiBand8Coordinator.java
@@ -0,0 +1,49 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.miband8;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.XiaomiCoordinator;
+
+public class MiBand8Coordinator extends XiaomiCoordinator {
+    @Override
+    public boolean isExperimental() {
+        return true;
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("^Xiaomi Smart Band 8 [A-Z0-9]{4}$");
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_miband8;
+    }
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_miband6;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_miband6_disabled;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/miband8active/MiBand8ActiveCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/miband8active/MiBand8ActiveCoordinator.java
new file mode 100644
index 0000000..a0ce84e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/miband8active/MiBand8ActiveCoordinator.java
@@ -0,0 +1,49 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.miband8active;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.XiaomiCoordinator;
+
+public class MiBand8ActiveCoordinator extends XiaomiCoordinator {
+    @Override
+    public boolean isExperimental() {
+        return true;
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("^Xiaomi( Smart)? Band 8 Active [A-Z0-9]{4}$");
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_miband8active;
+    }
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_default;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_default_disabled;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/miband8pro/MiBand8ProCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/miband8pro/MiBand8ProCoordinator.java
new file mode 100644
index 0000000..567def8
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/miband8pro/MiBand8ProCoordinator.java
@@ -0,0 +1,55 @@
+/*  Copyright (C) 2023 Yoran Vulker
+
+    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 <http://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.miband8pro;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.XiaomiCoordinator;
+
+public class MiBand8ProCoordinator extends XiaomiCoordinator {
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_miband8pro;
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("^Xiaomi Smart Band 8 Pro [0-9A-F]{4}$");
+    }
+
+    @Override
+    public boolean isExperimental() {
+        return true;
+    }
+
+    @Override
+    public ConnectionType getConnectionType() {
+        return ConnectionType.BT_CLASSIC;
+    }
+
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_default;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_default_disabled;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/miband9/MiBand9Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/miband9/MiBand9Coordinator.java
new file mode 100644
index 0000000..e096baf
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/miband9/MiBand9Coordinator.java
@@ -0,0 +1,54 @@
+/*  Copyright (C) 2024 Yoran Vulker
+
+    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 <http://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.miband9;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.XiaomiCoordinator;
+
+public class MiBand9Coordinator extends XiaomiCoordinator {
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_miband9;
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("^Xiaomi Smart Band 9 [0-9A-F]{4}$");
+    }
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_miband6;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_miband6_disabled;
+    }
+
+    @Override
+    public ConnectionType getConnectionType() {
+        return ConnectionType.BT_CLASSIC;
+    }
+
+    @Override
+    public boolean isExperimental() {
+        return true;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/miband9pro/MiBand9ProCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/miband9pro/MiBand9ProCoordinator.java
new file mode 100644
index 0000000..f4e9b99
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/miband9pro/MiBand9ProCoordinator.java
@@ -0,0 +1,55 @@
+/*  Copyright (C) 2023 Yoran Vulker
+
+    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 <http://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.miband9pro;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.XiaomiCoordinator;
+
+public class MiBand9ProCoordinator extends XiaomiCoordinator {
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_miband9pro;
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("^Xiaomi Smart Band 9 Pro [0-9A-F]{4}$");
+    }
+
+    @Override
+    public boolean isExperimental() {
+        return true;
+    }
+
+    @Override
+    public ConnectionType getConnectionType() {
+        return ConnectionType.BT_CLASSIC;
+    }
+
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_default;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_default_disabled;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/miwatch/MiWatchLiteCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/miwatch/MiWatchLiteCoordinator.java
new file mode 100644
index 0000000..6730592
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/miwatch/MiWatchLiteCoordinator.java
@@ -0,0 +1,70 @@
+/*  Copyright (C) 2023-2024 Andreas Shimokawa, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.miwatch;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.XiaomiCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class MiWatchLiteCoordinator extends XiaomiCoordinator {
+    @Override
+    public boolean isExperimental() {
+        return true;
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("^Mi Watch Lite_[A-Z0-9]{4}$");
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_xiaomi_watch_lite;
+    }
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_amazfit_bip;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_amazfit_bip_disabled;
+    }
+
+    @Override
+    public boolean supportsCalendarEvents() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsStressMeasurement() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsSpo2(GBDevice device) {
+        return false;
+    }
+
+    @Override
+    public boolean supportsRealtimeData() {
+        return false;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/miwatchcolorsport/MiWatchColorSportCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/miwatchcolorsport/MiWatchColorSportCoordinator.java
new file mode 100644
index 0000000..d5f7e02
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/miwatchcolorsport/MiWatchColorSportCoordinator.java
@@ -0,0 +1,50 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.miwatchcolorsport;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.XiaomiCoordinator;
+
+public class MiWatchColorSportCoordinator extends XiaomiCoordinator {
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_mi_watch_color_sport;
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("^Mi ColorS [0-9A-Z]{4}$");
+    }
+
+    @Override
+    public boolean isExperimental() {
+        return true;
+    }
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_miwatch;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_miwatch_disabled;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/redmibuds5pro/RedmiBuds5ProCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/redmibuds5pro/RedmiBuds5ProCoordinator.java
new file mode 100644
index 0000000..e9b5fdf
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/redmibuds5pro/RedmiBuds5ProCoordinator.java
@@ -0,0 +1,103 @@
+/*  Copyright (C) 2024 Jonathan Gobbo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.redmibuds5pro;
+
+import androidx.annotation.NonNull;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.GBException;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettings;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer;
+import nodomain.freeyourgadget.gadgetbridge.devices.AbstractDeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.BatteryConfig;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.redmibuds5pro.RedmiBuds5ProDeviceSupport;
+
+public class RedmiBuds5ProCoordinator extends AbstractDeviceCoordinator {
+    @Override
+    protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
+
+    }
+
+    @Override
+    public String getManufacturer() {
+        return "Xiaomi";
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return RedmiBuds5ProDeviceSupport.class;
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_redmi_buds_5_pro;
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("Redmi Buds 5 Pro");
+    }
+
+    @Override
+    public int getBondingStyle() {
+        return BONDING_STYLE_NONE;
+    }
+
+    @Override
+    public int getBatteryCount() {
+        return 3;
+    }
+
+    @Override
+    public BatteryConfig[] getBatteryConfig(GBDevice device) {
+        BatteryConfig battery1 = new BatteryConfig(0, R.drawable.ic_tws_case, R.string.battery_case);
+        BatteryConfig battery2 = new BatteryConfig(1, R.drawable.ic_nothing_ear_l, R.string.left_earbud);
+        BatteryConfig battery3 = new BatteryConfig(2, R.drawable.ic_nothing_ear_r, R.string.right_earbud);
+        return new BatteryConfig[]{battery1, battery2, battery3};
+    }
+
+    @Override
+    public DeviceSpecificSettings getDeviceSpecificSettings(final GBDevice device) {
+        final DeviceSpecificSettings deviceSpecificSettings = new DeviceSpecificSettings();
+        deviceSpecificSettings.addRootScreen(R.xml.devicesettings_redmibuds5pro_headphones);
+        deviceSpecificSettings.addRootScreen(R.xml.devicesettings_redmibuds5pro_gestures);
+        deviceSpecificSettings.addRootScreen(R.xml.devicesettings_redmibuds5pro_sound);
+        return deviceSpecificSettings;
+    }
+
+    @Override
+    public DeviceSpecificSettingsCustomizer getDeviceSpecificSettingsCustomizer(final GBDevice device) {
+        return new RedmiBuds5ProSettingsCustomizer(device);
+    }
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_nothingear;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_nothingear_disabled;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/redmibuds5pro/RedmiBuds5ProSettingsCustomizer.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/redmibuds5pro/RedmiBuds5ProSettingsCustomizer.java
new file mode 100644
index 0000000..9d0e5c9
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/redmibuds5pro/RedmiBuds5ProSettingsCustomizer.java
@@ -0,0 +1,161 @@
+/*  Copyright (C) 2024 Jonathan Gobbo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.redmibuds5pro;
+
+import android.os.Parcel;
+
+import androidx.annotation.NonNull;
+import androidx.preference.ListPreference;
+import androidx.preference.Preference;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsHandler;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+public class RedmiBuds5ProSettingsCustomizer implements DeviceSpecificSettingsCustomizer {
+
+    final GBDevice device;
+
+    public RedmiBuds5ProSettingsCustomizer(final GBDevice device) {
+        this.device = device;
+    }
+
+
+    @Override
+    public void onPreferenceChange(Preference preference, DeviceSpecificSettingsHandler handler) {
+
+        // Hide or Show ANC/Transparency settings according to the current ambient sound control mode
+        if (preference.getKey().equals(DeviceSettingsPreferenceConst.PREF_REDMI_BUDS_5_PRO_AMBIENT_SOUND_CONTROL)) {
+            String mode = ((ListPreference) preference).getValue();
+            final Preference ancLevel = handler.findPreference(DeviceSettingsPreferenceConst.PREF_REDMI_BUDS_5_PRO_NOISE_CANCELLING_STRENGTH);
+            final Preference transparencyLevel = handler.findPreference(DeviceSettingsPreferenceConst.PREF_REDMI_BUDS_5_PRO_TRANSPARENCY_STRENGTH);
+            final Preference adaptiveAnc = handler.findPreference(DeviceSettingsPreferenceConst.PREF_REDMI_BUDS_5_PRO_ADAPTIVE_NOISE_CANCELLING);
+//            final Preference customizedAnc = handler.findPreference(DeviceSettingsPreferenceConst.PREF_REDMI_BUDS_5_PRO_PERSONALIZED_NOISE_CANCELLING);
+            if (ancLevel != null) {
+                ancLevel.setVisible(mode.equals("1"));
+            }
+            if (transparencyLevel != null) {
+                transparencyLevel.setVisible(mode.equals("2"));
+            }
+            if (adaptiveAnc != null) {
+                adaptiveAnc.setVisible(mode.equals("1"));
+            }
+//            if (customizedAnc != null) {
+//                customizedAnc.setVisible(mode.equals("1"));
+//            }
+        }
+    }
+
+    @Override
+    public void customizeSettings(DeviceSpecificSettingsHandler handler, Prefs prefs, String rootKey) {
+
+        final ListPreference longPressLeft = handler.findPreference(DeviceSettingsPreferenceConst.PREF_REDMI_BUDS_5_PRO_CONTROL_LONG_TAP_MODE_LEFT);
+        final ListPreference longPressRight = handler.findPreference(DeviceSettingsPreferenceConst.PREF_REDMI_BUDS_5_PRO_CONTROL_LONG_TAP_MODE_RIGHT);
+
+        final Preference longPressLeftSettings = handler.findPreference(DeviceSettingsPreferenceConst.PREF_REDMI_BUDS_5_PRO_CONTROL_LONG_TAP_SETTINGS_LEFT);
+        final Preference longPressRightSettings = handler.findPreference(DeviceSettingsPreferenceConst.PREF_REDMI_BUDS_5_PRO_CONTROL_LONG_TAP_SETTINGS_RIGHT);
+
+        final ListPreference equalizerPreset = handler.findPreference(DeviceSettingsPreferenceConst.PREF_REDMI_BUDS_5_PRO_EQUALIZER_PRESET);
+
+        if (longPressLeft != null) {
+            final Preference.OnPreferenceChangeListener longPressLeftButtonListener = (preference, newVal) -> {
+                String mode = newVal.toString();
+                if (longPressLeftSettings != null) {
+                    longPressLeftSettings.setVisible(mode.equals("6"));
+                }
+                return true;
+            };
+            longPressLeftButtonListener.onPreferenceChange(longPressLeft, prefs.getString(DeviceSettingsPreferenceConst.PREF_REDMI_BUDS_5_PRO_CONTROL_LONG_TAP_MODE_LEFT, "6"));
+            handler.addPreferenceHandlerFor(DeviceSettingsPreferenceConst.PREF_REDMI_BUDS_5_PRO_CONTROL_LONG_TAP_MODE_LEFT, longPressLeftButtonListener);
+        }
+        if (longPressRight != null) {
+            final Preference.OnPreferenceChangeListener longPressRightButtonListener = (preference, newVal) -> {
+                String mode = newVal.toString();
+                if (longPressRightSettings != null) {
+                    longPressRightSettings.setVisible(mode.equals("6"));
+                }
+                return true;
+            };
+            longPressRightButtonListener.onPreferenceChange(longPressRight, prefs.getString(DeviceSettingsPreferenceConst.PREF_REDMI_BUDS_5_PRO_CONTROL_LONG_TAP_MODE_RIGHT, "6"));
+            handler.addPreferenceHandlerFor(DeviceSettingsPreferenceConst.PREF_REDMI_BUDS_5_PRO_CONTROL_LONG_TAP_MODE_RIGHT, longPressRightButtonListener);
+        }
+
+        if (equalizerPreset != null) {
+
+            final Preference.OnPreferenceChangeListener equalizerPresetListener = (preference, newVal) -> {
+
+                final List<Preference> prefsToDisable = Arrays.asList(
+                        handler.findPreference(DeviceSettingsPreferenceConst.PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_62),
+                        handler.findPreference(DeviceSettingsPreferenceConst.PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_125),
+                        handler.findPreference(DeviceSettingsPreferenceConst.PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_250),
+                        handler.findPreference(DeviceSettingsPreferenceConst.PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_500),
+                        handler.findPreference(DeviceSettingsPreferenceConst.PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_1k),
+                        handler.findPreference(DeviceSettingsPreferenceConst.PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_2k),
+                        handler.findPreference(DeviceSettingsPreferenceConst.PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_4k),
+                        handler.findPreference(DeviceSettingsPreferenceConst.PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_8k),
+                        handler.findPreference(DeviceSettingsPreferenceConst.PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_12k),
+                        handler.findPreference(DeviceSettingsPreferenceConst.PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_16k)
+                );
+
+                String mode = newVal.toString();
+                for (Preference pref : prefsToDisable) {
+                    if (pref != null) {
+                        pref.setEnabled(mode.equals("10"));
+                    }
+                }
+                return true;
+            };
+            equalizerPresetListener.onPreferenceChange(equalizerPreset, prefs.getString(DeviceSettingsPreferenceConst.PREF_REDMI_BUDS_5_PRO_EQUALIZER_PRESET, "0"));
+            handler.addPreferenceHandlerFor(DeviceSettingsPreferenceConst.PREF_REDMI_BUDS_5_PRO_EQUALIZER_PRESET, equalizerPresetListener);
+        }
+    }
+
+    @Override
+    public Set<String> getPreferenceKeysWithSummary() {
+        return Collections.emptySet();
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel parcel, int i) {
+        parcel.writeParcelable(device, 0);
+    }
+
+    public static final Creator<RedmiBuds5ProSettingsCustomizer> CREATOR = new Creator<RedmiBuds5ProSettingsCustomizer>() {
+        @Override
+        public RedmiBuds5ProSettingsCustomizer createFromParcel(final Parcel in) {
+            final GBDevice device = in.readParcelable(RedmiBuds5ProSettingsCustomizer.class.getClassLoader());
+            return new RedmiBuds5ProSettingsCustomizer(device);
+        }
+
+        @Override
+        public RedmiBuds5ProSettingsCustomizer[] newArray(final int size) {
+            return new RedmiBuds5ProSettingsCustomizer[size];
+        }
+    };
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/redmibuds5pro/prefs/Configuration.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/redmibuds5pro/prefs/Configuration.java
new file mode 100644
index 0000000..e42ada1
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/redmibuds5pro/prefs/Configuration.java
@@ -0,0 +1,47 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.redmibuds5pro.prefs;
+
+public class Configuration {
+
+    public enum Config {
+        GESTURES((byte) 0x02),
+        AUTO_ANSWER((byte) 0x03),
+        DOUBLE_CONNECTION((byte) 0x04),
+        EAR_DETECTION((byte) 0x06),
+        EQ_PRESET((byte) 0x07),
+        LONG_GESTURES((byte) 0x0a),
+        EFFECT_STRENGTH((byte) 0x0b),
+        ADAPTIVE_ANC((byte) 0x25),
+        ADAPTIVE_SOUND((byte) 0x29),
+        EQ_CURVE((byte) 0x37),
+        CUSTOMIZED_ANC((byte) 0x3b),
+        UNKNOWN((byte) 0xff);
+
+        public final byte value;
+
+        Config(byte value) {
+            this.value = value;
+        }
+
+        public static Config fromCode(final byte code) {
+            for (final Config config : values()) {
+                if (config.value == code) {
+                    return config;
+                }
+            }
+            return Config.UNKNOWN;
+        }
+
+    }
+
+    public enum StrengthTarget {
+        ANC((byte) 0x01),
+        TRANSPARENCY((byte) 0x02);
+
+        public final byte value;
+
+        StrengthTarget(byte value) {
+            this.value = value;
+        }
+    }
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/redmibuds5pro/prefs/Gestures.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/redmibuds5pro/prefs/Gestures.java
new file mode 100644
index 0000000..e7b8971
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/redmibuds5pro/prefs/Gestures.java
@@ -0,0 +1,22 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.redmibuds5pro.prefs;
+
+public class Gestures {
+
+    public enum InteractionType {
+        SINGLE((byte) 0x04),
+        DOUBLE((byte) 0x01),
+        TRIPLE((byte) 0x02),
+        LONG((byte) 0x03);
+
+        public final byte value;
+
+        InteractionType(byte value) {
+            this.value = value;
+        }
+    }
+
+    public enum Position {
+        LEFT,
+        RIGHT
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/redmismartband2/RedmiSmartBand2Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/redmismartband2/RedmiSmartBand2Coordinator.java
new file mode 100644
index 0000000..1ed86dd
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/redmismartband2/RedmiSmartBand2Coordinator.java
@@ -0,0 +1,49 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.redmismartband2;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.XiaomiCoordinator;
+
+public class RedmiSmartBand2Coordinator extends XiaomiCoordinator {
+    @Override
+    public boolean isExperimental() {
+        return true;
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("^Redmi Smart Band 2 [A-Z0-9]{4}$");
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_redmi_smart_band_2;
+    }
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_default;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_default_disabled;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/redmismartbandpro/RedmiSmartBandProCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/redmismartbandpro/RedmiSmartBandProCoordinator.java
new file mode 100644
index 0000000..cc6fd55
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/redmismartbandpro/RedmiSmartBandProCoordinator.java
@@ -0,0 +1,68 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.redmismartbandpro;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.capabilities.password.PasswordCapabilityImpl;
+import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.XiaomiCoordinator;
+
+public class RedmiSmartBandProCoordinator extends XiaomiCoordinator {
+    @Override
+    public boolean isExperimental() {
+        return true;
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("^Redmi Band Pro [A-Z0-9]{4}$");
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_redmi_smart_band_pro;
+    }
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_default;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_default_disabled;
+    }
+
+    @Override
+    public PasswordCapabilityImpl.Mode getPasswordCapability() {
+        return PasswordCapabilityImpl.Mode.NUMBERS_4_DIGITS_0_TO_9;
+    }
+
+    @Override
+    public boolean checkDecryptionMac() {
+        // Sometimes it fails to decrypt with an invalid mac
+        // https://codeberg.org/Freeyourgadget/Gadgetbridge/issues/3766
+        return false;
+    }
+
+    @Override
+    public boolean supportsAlarms() {
+        // Crashes when requesting alarms
+        return false;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/redmiwatch2/RedmiWatch2Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/redmiwatch2/RedmiWatch2Coordinator.java
new file mode 100644
index 0000000..e85485b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/redmiwatch2/RedmiWatch2Coordinator.java
@@ -0,0 +1,49 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.redmiwatch2;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.XiaomiCoordinator;
+
+public class RedmiWatch2Coordinator extends XiaomiCoordinator {
+    @Override
+    public boolean isExperimental() {
+        return true;
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("^Redmi Watch 2 [A-Z0-9]{4}$");
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_redmi_watch_2;
+    }
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_amazfit_bip;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_amazfit_bip_disabled;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/redmiwatch2lite/RedmiWatch2LiteCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/redmiwatch2lite/RedmiWatch2LiteCoordinator.java
new file mode 100644
index 0000000..baccce9
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/redmiwatch2lite/RedmiWatch2LiteCoordinator.java
@@ -0,0 +1,49 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.redmiwatch2lite;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.XiaomiCoordinator;
+
+public class RedmiWatch2LiteCoordinator extends XiaomiCoordinator {
+    @Override
+    public boolean isExperimental() {
+        return true;
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("^Redmi Watch 2 Lite [A-Z0-9]{4}$");
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_redmi_watch_2_lite;
+    }
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_amazfit_bip;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_amazfit_bip_disabled;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/redmiwatch3/RedmiWatch3Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/redmiwatch3/RedmiWatch3Coordinator.java
new file mode 100644
index 0000000..87c8b06
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/redmiwatch3/RedmiWatch3Coordinator.java
@@ -0,0 +1,60 @@
+/*  Copyright (C) 2023-2024 Andreas Shimokawa, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.redmiwatch3;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.XiaomiCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class RedmiWatch3Coordinator extends XiaomiCoordinator {
+    @Override
+    public boolean isExperimental() {
+        return true;
+    }
+
+    @Override
+    public ConnectionType getConnectionType() {
+        return ConnectionType.BOTH;
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("^Redmi Watch 3 [A-Z0-9]{4}$");
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_redmiwatch3;
+    }
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_amazfit_bip;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_amazfit_bip_disabled;
+    }
+
+    @Override
+    public int getContactsSlotCount(final GBDevice device) {
+        return 10; // TODO:verify
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/redmiwatch3active/RedmiWatch3ActiveCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/redmiwatch3active/RedmiWatch3ActiveCoordinator.java
new file mode 100644
index 0000000..4df03f9
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/redmiwatch3active/RedmiWatch3ActiveCoordinator.java
@@ -0,0 +1,60 @@
+/*  Copyright (C) 2023-2024 Andreas Shimokawa, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.redmiwatch3active;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.XiaomiCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class RedmiWatch3ActiveCoordinator extends XiaomiCoordinator {
+    @Override
+    public boolean isExperimental() {
+        return true;
+    }
+
+    @Override
+    public ConnectionType getConnectionType() {
+        return ConnectionType.BOTH;
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("^Redmi Watch 3 Active [A-Z0-9]{4}$");
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_redmiwatch3active;
+    }
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_amazfit_bip;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_amazfit_bip_disabled;
+    }
+
+    @Override
+    public int getContactsSlotCount(final GBDevice device) {
+        return 10; // TODO:verify
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/redmiwatch4/RedmiWatch4Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/redmiwatch4/RedmiWatch4Coordinator.java
new file mode 100644
index 0000000..4a01980
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/redmiwatch4/RedmiWatch4Coordinator.java
@@ -0,0 +1,61 @@
+/* Copyright (C) 2024 Yoran Vulker
+
+   This file is part of Gadgetbridge.
+
+   Gadgetbridge is free software: you can redistribute it and/or modify
+   it under the terms of the GNU Affero General Public License as published
+   by the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   Gadgetbridge is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU Affero General Public License for more details.
+
+   You should have received a copy of the GNU Affero General Public License
+   along with this program. If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.redmiwatch4;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.XiaomiCoordinator;
+
+public class RedmiWatch4Coordinator extends XiaomiCoordinator {
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_redmi_watch_4;
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("^Redmi Watch 4 [0-9A-F]{4}$");
+    }
+
+    @Override
+    public boolean isExperimental() {
+        return true;
+    }
+
+    @Override
+    public ConnectionType getConnectionType() {
+        // this device likely supports both connection types if we indicate
+        // that we are an iOS device in the final auth step
+        return ConnectionType.BT_CLASSIC;
+    }
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_amazfit_bip;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_amazfit_bip_disabled;
+    }
+
+    @Override
+    public int getWorldClocksSlotCount() {
+        return 20;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/redmiwatch5active/RedmiWatch5ActiveCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/redmiwatch5active/RedmiWatch5ActiveCoordinator.java
new file mode 100644
index 0000000..2268f5a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/redmiwatch5active/RedmiWatch5ActiveCoordinator.java
@@ -0,0 +1,55 @@
+/*  Copyright (C) 2024 Yoran Vulker
+
+    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 <http://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.redmiwatch5active;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.XiaomiCoordinator;
+
+public class RedmiWatch5ActiveCoordinator extends XiaomiCoordinator {
+
+    @Override
+    public ConnectionType getConnectionType() {
+        return ConnectionType.BT_CLASSIC;
+    }
+
+    @Override
+    public boolean isExperimental() {
+        return true;
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_redmi_watch_5_active;
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("^Redmi Watch 5 Active [0-9A-F]{4}$");
+    }
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_amazfit_bip;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_amazfit_bip_disabled;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/redmiwatch5lite/RedmiWatch5LiteCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/redmiwatch5lite/RedmiWatch5LiteCoordinator.java
new file mode 100644
index 0000000..0d520b0
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/redmiwatch5lite/RedmiWatch5LiteCoordinator.java
@@ -0,0 +1,50 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.redmiwatch5lite;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.XiaomiCoordinator;
+
+public class RedmiWatch5LiteCoordinator extends XiaomiCoordinator {
+
+    @Override
+    public ConnectionType getConnectionType() {
+        return ConnectionType.BT_CLASSIC;
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_redmi_watch_5_lite;
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("^Redmi Watch 5 Lite [0-9A-F]{4}$");
+    }
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_amazfit_bip;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_amazfit_bip_disabled;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/watchs1/XiaomiWatchS1Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/watchs1/XiaomiWatchS1Coordinator.java
new file mode 100644
index 0000000..a557e8c
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/watchs1/XiaomiWatchS1Coordinator.java
@@ -0,0 +1,61 @@
+/*  Copyright (C) 2023 Yoran Vulker
+
+    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 <http://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.watchs1;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.XiaomiCoordinator;
+
+public class XiaomiWatchS1Coordinator extends XiaomiCoordinator {
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_xiaomi_watch_s1;
+    }
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_miwatch;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_miwatch_disabled;
+    }
+
+    @Override
+    public ConnectionType getConnectionType() {
+        // TODO make sure that the device can actually communicate in BLE mode
+        return ConnectionType.BOTH;
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("^Xiaomi Watch S1 [0-9A-F]{4}$");
+    }
+
+    @Override
+    public boolean isExperimental() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsFindDevice() {
+        return false;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/watchs1active/XiaomiWatchS1ActiveCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/watchs1active/XiaomiWatchS1ActiveCoordinator.java
new file mode 100644
index 0000000..602d5b9
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/watchs1active/XiaomiWatchS1ActiveCoordinator.java
@@ -0,0 +1,66 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo, Yoran Vulker
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.watchs1active;
+
+import android.content.Context;
+import android.net.Uri;
+
+import androidx.annotation.Nullable;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.XiaomiCoordinator;
+
+public class XiaomiWatchS1ActiveCoordinator extends XiaomiCoordinator {
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_xiaomi_watch_s1_active;
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("^XiaomiWatchS1Active [0-9A-Z]{4}$");
+    }
+
+    @Override
+    public boolean isExperimental() {
+        return true;
+    }
+
+    @Override
+    public ConnectionType getConnectionType() {
+        return ConnectionType.BOTH;
+    }
+
+    @Override
+    public boolean supportsFindDevice() {
+        return false;
+    }
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_miwatch;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_miwatch_disabled;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/watchs1pro/XiaomiWatchS1ProCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/watchs1pro/XiaomiWatchS1ProCoordinator.java
new file mode 100644
index 0000000..e06e057
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/watchs1pro/XiaomiWatchS1ProCoordinator.java
@@ -0,0 +1,66 @@
+/*  Copyright (C) 2023 Yoran Vulker
+
+    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 <http://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.watchs1pro;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.XiaomiCoordinator;
+
+public class XiaomiWatchS1ProCoordinator extends XiaomiCoordinator {
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        // TODO confirm that the secondary name is actually used in prod somewhere
+        return Pattern.compile("^(Xiaomi Watch S1 Pro [0-9A-F]{4}|[Ll]61.*[0-9A-F]{4})$");
+    }
+
+    @Override
+    public ConnectionType getConnectionType() {
+        return ConnectionType.BT_CLASSIC;
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_xiaomi_watch_s1_pro;
+    }
+
+    @Override
+    public boolean isExperimental() {
+        return true;
+    }
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_miwatch;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_miwatch_disabled;
+    }
+
+    @Override
+    public boolean supportsFindDevice() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsTemperatureMeasurement() {
+        return true;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/watchs3/XiaomiWatchS3Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/watchs3/XiaomiWatchS3Coordinator.java
new file mode 100644
index 0000000..d13a40c
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/watchs3/XiaomiWatchS3Coordinator.java
@@ -0,0 +1,55 @@
+/*  Copyright (C) 2023 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.watchs3;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.XiaomiCoordinator;
+
+public class XiaomiWatchS3Coordinator extends XiaomiCoordinator {
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_xiaomi_watch_s3;
+    }
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_miwatch;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_miwatch_disabled;
+    }
+
+    @Override
+    public ConnectionType getConnectionType() {
+        return ConnectionType.BT_CLASSIC;
+    }
+
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        // only saw it with eSIM in #3506, but allowing both just in case
+        return Pattern.compile("^Xiaomi Watch S3( eSIM)? [0-9A-F]{4}$");
+    }
+
+    @Override
+    public boolean isExperimental() {
+        return true;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xwatch/XWatchCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xwatch/XWatchCoordinator.java
new file mode 100644
index 0000000..af293b3
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xwatch/XWatchCoordinator.java
@@ -0,0 +1,141 @@
+/*  Copyright (C) 2018-2024 Andreas Shimokawa, Damien Gaignon, Daniel Dakhno,
+    Daniele Gobbetti, Jos茅 Rebelo, ladbsoft, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.xwatch;
+
+import android.app.Activity;
+import android.content.Context;
+import android.net.Uri;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.GBException;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLEDeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.xwatch.XWatchSupport;
+
+public class XWatchCoordinator extends AbstractBLEDeviceCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("XWatch.*");
+    }
+
+    @Override
+    protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
+
+    }
+
+    @Nullable
+    @Override
+    public Class<? extends Activity> getPairingActivity() {
+        return null;
+    }
+
+    @Override
+    public boolean supportsActivityDataFetching() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsActivityTracking() {
+        return true;
+    }
+
+    @Override
+    public SampleProvider<? extends ActivitySample> getSampleProvider(GBDevice device, DaoSession session) {
+        return new XWatchSampleProvider(device, session);
+    }
+
+    @Override
+    public InstallHandler findInstallHandler(Uri uri, Context context) {
+        return null;
+    }
+
+    @Override
+    public boolean supportsScreenshots(final GBDevice device) {
+        return false;
+    }
+
+    @Override
+    public int getAlarmSlotCount(GBDevice device) {
+        return 0;
+    }
+
+    @Override
+    public boolean supportsHeartRateMeasurement(GBDevice device) {
+        return false;
+    }
+
+    @Override
+    public String getManufacturer() {
+        return "Generic";
+    }
+
+    @Override
+    public boolean supportsAppsManagement(final GBDevice device) {
+        return false;
+    }
+
+    @Override
+    public Class<? extends Activity> getAppsManagementActivity() {
+        return null;
+    }
+
+    @Override
+    public boolean supportsCalendarEvents() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsRealtimeData() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsWeather() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsFindDevice() {
+        return true;
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return XWatchSupport.class;
+    }
+
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_xwatch;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xwatch/XWatchSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xwatch/XWatchSampleProvider.java
new file mode 100644
index 0000000..164ca23
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xwatch/XWatchSampleProvider.java
@@ -0,0 +1,80 @@
+/*  Copyright (C) 2018-2024 Daniele Gobbetti, ladbsoft
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.xwatch;
+
+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.XWatchActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.entities.XWatchActivitySampleDao;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+
+//TODO: extend own class
+public class XWatchSampleProvider extends AbstractSampleProvider<XWatchActivitySample> {
+    public static final int TYPE_ACTIVITY = -1;
+
+    public XWatchSampleProvider(GBDevice device, DaoSession session) {
+        super(device, session);
+    }
+
+    @Override
+    public ActivityKind normalizeType(int rawType) {
+        return ActivityKind.ACTIVITY;
+    }
+
+    @Override
+    public int toRawActivityKind(ActivityKind activityKind) {
+        return TYPE_ACTIVITY;
+    }
+
+    @Override
+    public float normalizeIntensity(int rawIntensity) {
+        return rawIntensity / 180.0f;
+    }
+
+    @Override
+    public XWatchActivitySample createActivitySample() {
+        return new XWatchActivitySample();
+    }
+
+    @Override
+    public AbstractDao<XWatchActivitySample, ?> getSampleDao() {
+        return getSession().getXWatchActivitySampleDao();
+    }
+
+    @Nullable
+    @Override
+    protected Property getRawKindSampleProperty() {
+        return XWatchActivitySampleDao.Properties.RawKind;
+    }
+
+    @NonNull
+    @Override
+    protected Property getTimestampSampleProperty() {
+        return XWatchActivitySampleDao.Properties.Timestamp;
+    }
+
+    @NonNull
+    @Override
+    protected Property getDeviceIdentifierSampleProperty() {
+        return XWatchActivitySampleDao.Properties.DeviceId;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xwatch/XWatchService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xwatch/XWatchService.java
new file mode 100644
index 0000000..8d764fd
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xwatch/XWatchService.java
@@ -0,0 +1,53 @@
+/*  Copyright (C) 2018-2024 ladbsoft, SalavatR
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.xwatch;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+public class XWatchService {
+    public static final UUID UUID_NOTIFY = UUID.fromString("0000fff7-0000-1000-8000-00805f9b34fb");
+    public static final UUID UUID_SERVICE = UUID.fromString("0000fff0-0000-1000-8000-00805f9b34fb");
+    public static final UUID UUID_WRITE = UUID.fromString("0000fff6-0000-1000-8000-00805f9b34fb");
+
+    public static final byte COMMAND_CONNECTED = 0x01;
+    public static final byte COMMAND_ACTION_BUTTON = 0x4c;
+    public static final byte COMMAND_ACTIVITY_DATA = 0x43;
+    public static final byte COMMAND_ACTIVITY_TOTALS = 0x46;
+    public static final byte COMMAND_NOTIFICATION = 0x4d;
+    public static final byte COMMAND_NOTIFICATION_PHONE = 0x00;
+    public static final byte COMMAND_NOTIFICATION_MESSAGE = 0x01;
+
+    private static final Map<UUID, String> XWATCH_DEBUG;
+
+    static {
+        XWATCH_DEBUG = new HashMap<>();
+
+        XWATCH_DEBUG.put(UUID_NOTIFY, "Read data");
+        XWATCH_DEBUG.put(UUID_WRITE, "Write data");
+        XWATCH_DEBUG.put(UUID_SERVICE, "Get service");
+    }
+
+    public static String lookup(UUID uuid, String fallback) {
+        String name = XWATCH_DEBUG.get(uuid);
+        if (name == null) {
+            name = fallback;
+        }
+        return name;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/zetime/ZeTimeConstants.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/zetime/ZeTimeConstants.java
new file mode 100644
index 0000000..af97b08
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/zetime/ZeTimeConstants.java
@@ -0,0 +1,156 @@
+/*  Copyright (C) 2018-2024 Andreas Shimokawa, Damien Gaignon, Sebastian Kranz
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.zetime;
+
+import java.util.UUID;
+
+public class ZeTimeConstants {
+    public static final UUID UUID_WRITE_CHARACTERISTIC = UUID.fromString("00008001-0000-1000-8000-00805f9b34fb");
+    public static final UUID UUID_ACK_CHARACTERISTIC = UUID.fromString("00008002-0000-1000-8000-00805f9b34fb");
+    public static final UUID UUID_REPLY_CHARACTERISTIC = UUID.fromString("00008003-0000-1000-8000-00805f9b34fb");
+    public static final UUID UUID_NOTIFY_CHARACTERISTIC = UUID.fromString("00008004-0000-1000-8000-00805f9b34fb");
+    public static final UUID UUID_CHARACTERISTIC_8005 = UUID.fromString("00008005-0000-1000-8000-00805f9b34fb");
+    public static final UUID UUID_CHARACTERISTIC_HEART_RATE = UUID.fromString("00002a37-0000-1000-8000-00805f9b34fb");
+    public static final UUID UUID_CONFIG_DESCRIPTOR = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");
+    public static final UUID UUID_SERVICE_BASE = UUID.fromString("00006006-0000-1000-8000-00805f9b34fb");
+    public static final UUID UUID_SERVICE_EXTEND = UUID.fromString("00007006-0000-1000-8000-00805f9b34fb");
+    public static final UUID UUID_SERVICE_HEART_RATE = UUID.fromString("0000180d-0000-1000-8000-00805f9b34fb");
+
+    public static final byte CMD_PREAMBLE = (byte) 0x6f;
+    // list all available commands
+    public static final byte CMD_RESPOND = (byte) 0x01;
+    public static final byte CMD_WATCH_ID = (byte) 0x02;
+    public static final byte CMD_DEVICE_VERSION = (byte) 0x03;
+    public static final byte CMD_DATE_TIME = (byte) 0x04;
+    public static final byte CMD_TIME_SURFACE_SETTINGS = (byte) 0x05;
+    public static final byte CMD_SURFACE_DISPLAY_SETTIGNS = (byte) 0x06;
+    public static final byte CMD_SCREEN_BRIGHTNESS = (byte) 0x07;
+    public static final byte CMD_BATTERY_POWER = (byte) 0x08;
+    public static final byte CMD_VOLUME_SETTINGS = (byte) 0x09;
+    public static final byte CMD_SHOCK_MODE = (byte) 0x0A;
+    public static final byte CMD_LANGUAGE_SETTINGS = (byte) 0x0B;
+    public static final byte CMD_UNIT_SETTINGS = (byte) 0x0C;
+    public static final byte CMD_FACTORY_RESTORE = (byte) 0x0D;
+    public static final byte CMD_ENTER_UPGRADE_MODE = (byte) 0x0E;
+    public static final byte CMD_SHOCK_STRENGTH = (byte) 0x10;
+    public static final byte CMD_WORK_MODE = (byte) 0x12;
+    public static final byte CMD_SCREEN_ON_TIME = (byte) 0x13;
+    public static final byte CMD_SNOOZE = (byte) 0x14;
+    public static final byte CMD_DO_NOT_DISTURB = (byte) 0x15;
+    public static final byte CMD_ANALOG_MODE = (byte) 0x17;
+    public static final byte CMD_CONTROL_DEVICE = (byte) 0x1A;
+    public static final byte CMD_DISPLAY_TIMEOUT = (byte) 0x25;
+    public static final byte CMD_USER_INFO = (byte) 0x30;
+    public static final byte CMD_USAGE_HABITS = (byte) 0x31;
+    public static final byte CMD_USER_NAME = (byte) 0x32;
+    public static final byte CMD_GOALS = (byte) 0x50;
+    public static final byte CMD_AVAIABLE_DATA = (byte) 0x52;
+    public static final byte CMD_DELETE_STEP_COUNT = (byte) 0x53;
+    public static final byte CMD_GET_STEP_COUNT = (byte) 0x54;
+    public static final byte CMD_DELETE_SLEEP_DATA = (byte) 0x55;
+    public static final byte CMD_GET_SLEEP_DATA = (byte) 0x56;
+    public static final byte CMD_DELETE_HEARTRATE_DATA = (byte) 0x5A;
+    public static final byte CMD_GET_HEARTRATE_DATA = (byte) 0x5B;
+    public static final byte CMD_AUTO_HEARTRATE = (byte) 0x5C;
+    public static final byte CMD_HEARTRATE_ALARM_LIMITS = (byte) 0x5D;
+    public static final byte CMD_INACTIVITY_ALERT = (byte) 0x5E;
+    public static final byte CMD_CALORIES_TYPE = (byte) 0x60;
+    public static final byte CMD_GET_HEARTRATE_EXDATA = (byte) 0x61;
+    public static final byte CMD_PUSH_EX_MSG = (byte) 0x76;
+    public static final byte CMD_PUSH_WEATHER_DATA = (byte) 0x77;
+    public static final byte CMD_SWITCH_SETTINGS = (byte) 0x90;
+    public static final byte CMD_REMINDERS = (byte) 0x97;
+    public static final byte CMD_PUSH_CALENDAR_DAY = (byte) 0x99;
+    public static final byte CMD_MUSIC_CONTROL = (byte) 0xD0;
+    public static final byte CMD_CALL_CONTROL = (byte) 0xDC;
+    public static final byte CMD_TEST_SIGNALING = (byte) 0xFA;
+    // here are the action commands
+    public static final byte CMD_REQUEST = (byte) 0x70;
+    public static final byte CMD_SEND = (byte) 0x71;
+    public static final byte CMD_REQUEST_RESPOND = (byte) 0x80;
+    // further commands
+    public static final byte CMD_END = (byte) 0x8f;
+    public static final byte CMD_ACK_WRITE = (byte) 0x03;
+    // notification types and icons
+    public static final byte NOTIFICATION_MISSED_CALL = (byte) 0x00;
+    public static final byte NOTIFICATION_SMS = (byte) 0x01;
+    public static final byte NOTIFICATION_SOCIAL = (byte) 0x02;
+    public static final byte NOTIFICATION_EMAIL = (byte) 0x03;
+    public static final byte NOTIFICATION_CALENDAR = (byte) 0x04;
+    public static final byte NOTIFICATION_INCOME_CALL = (byte) 0x05;
+    public static final byte NOTIFICATION_CALL_OFF = (byte) 0x06;
+    public static final byte NOTIFICATION_WECHAT = (byte) 0x07;
+    public static final byte NOTIFICATION_VIBER = (byte) 0x08;
+    public static final byte NOTIFICATION_SNAPCHAT = (byte) 0x09;
+    public static final byte NOTIFICATION_WHATSAPP = (byte) 0x0A;
+    public static final byte NOTIFICATION_QQ = (byte) 0x0B;
+    public static final byte NOTIFICATION_FACEBOOK = (byte) 0x0C;
+    public static final byte NOTIFICATION_HANGOUTS = (byte) 0x0D;
+    public static final byte NOTIFICATION_GMAIL = (byte) 0x0E;
+    public static final byte NOTIFICATION_MESSENGER = (byte) 0x0F;
+    public static final byte NOTIFICATION_INSTAGRAM = (byte) 0x10;
+    public static final byte NOTIFICATION_TWITTER = (byte) 0x11;
+    public static final byte NOTIFICATION_LINKEDIN = (byte) 0x12;
+    public static final byte NOTIFICATION_UBER = (byte) 0x13;
+    public static final byte NOTIFICATION_LINE = (byte) 0x14;
+    public static final byte NOTIFICATION_SKYPE = (byte) 0x15;
+    // reminders types
+    public static final byte REMINDER_ALARM = (byte) 0x04;
+    // signaling types
+    public static final byte ANTI_LOSS_TYPE = (byte) 0x00;
+    public static final byte CALL_TYPE = (byte) 0x02;
+    public static final byte MISSED_CALL_TYPE = (byte) 0x03;
+    public static final byte SMS_TYPE = (byte) 0x04;
+    public static final byte SOCIAL_TYPE = (byte) 0x05;
+    public static final byte EMAIL_TYPE = (byte) 0x06;
+    public static final byte CALENDAR_TYPE = (byte) 0x07;
+    public static final byte INACTIVITY_TYPE = (byte) 0x08;
+    public static final byte LOW_POWER_TYPE = (byte) 0x09;
+    // watch settings
+    public static final byte WEAR_ON_LEFT_WRIST = (byte) 0x00;
+    public static final byte WEAR_ON_RIGHT_WRIST = (byte) 0x01;
+
+    public static final String PREF_SCREENTIME = "zetime_screentime";
+    public static final int MAX_SCREEN_ON_TIME = 0xffff;
+    public static final int MIN_SCREEN_ON_TIME = 0x000a;
+
+    public static final String PREF_ZETIME_DONT_DEL_ACTDATA = "zetime_dont_del_actdata";
+
+    public static final String PREF_ZETIME_HEARTRATE_ALARM = "zetime_heartrate_alarm_enable";
+    public static final String PREF_ZETIME_MAX_HEARTRATE = "alarm_max_heart_rate";
+    public static final String PREF_ZETIME_MIN_HEARTRATE = "alarm_min_heart_rate";
+    public static final String PREF_ZETIME_HEARTRATE_INTERVAL = "heartrate_measurement_interval";
+
+    public static final String PREF_ANALOG_MODE = "zetime_analog_mode";
+    public static final String PREF_ACTIVITY_TRACKING = "zetime_activity_tracking";
+    public static final String PREF_HANDMOVE_DISPLAY = "zetime_handmove_display";
+    public static final String PREF_CALORIES_TYPE = "zetime_calories_type";
+    public static final String PREF_DATE_FORMAT = "zetime_date_format";
+
+    public static final String PREF_ALARM_SIGNALING = "zetime_alarm_signaling";
+    public static final String PREF_SMS_SIGNALING = "zetime_vibration_profile_sms";
+    public static final String PREF_CALL_SIGNALING = "zetime_vibration_profile_incoming_call";
+    public static final String PREF_MISSED_CALL_SIGNALING = "zetime_vibration_profile_missed_call";
+    public static final String PREF_EMAIL_SIGNALING = "zetime_vibration_profile_generic_email";
+    public static final String PREF_SOCIAL_SIGNALING = "zetime_vibration_profile_generic_social";
+    public static final String PREF_CALENDAR_SIGNALING = "zetime_vibration_profile_calendar";
+    public static final String PREF_INACTIVITY_SIGNALING = "zetime_vibration_profile_inactivity";
+    public static final String PREF_LOW_POWER_SIGNALING = "zetime_vibration_profile_lowpower";
+    public static final String PREF_ANTI_LOSS_SIGNALING = "zetime_vibration_profile_antiloss";
+
+    public static final String PREF_SHOCK_STRENGTH = "zetime_shock_strength";
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/zetime/ZeTimeCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/zetime/ZeTimeCoordinator.java
new file mode 100644
index 0000000..32c5ab1
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/zetime/ZeTimeCoordinator.java
@@ -0,0 +1,209 @@
+/*  Copyright (C) 2018-2024 Andreas Shimokawa, Damien Gaignon, Daniel Dakhno,
+    Daniele Gobbetti, Jos茅 Rebelo, Petr Van臎k, Sebastian Kranz
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.zetime;
+
+import androidx.annotation.NonNull;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettings;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsScreen;
+import nodomain.freeyourgadget.gadgetbridge.capabilities.HeartRateCapability;
+import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLEDeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.zetime.ZeTimeDeviceSupport;
+
+public class ZeTimeCoordinator extends AbstractBLEDeviceCoordinator {
+    @Override
+    protected Pattern getSupportedDeviceName() {
+        return Pattern.compile("ZeTime.*");
+    }
+
+    @Override
+    public int getAlarmSlotCount(final GBDevice device) {
+        return 3; // FIXME - check the real value
+    }
+
+    @Override
+    public boolean supportsWeather() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsFindDevice() {
+        return true;
+    }
+
+    @Override
+    public String getManufacturer() {
+        return "MyKronoz";
+    }
+
+    @Override
+    public boolean supportsActivityTracking() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsHeartRateMeasurement(GBDevice device) {
+        return true;
+    }
+
+    @Override
+    public boolean supportsActivityDataFetching() {
+        return true;
+    }
+
+    @Override
+    public SampleProvider<? extends ActivitySample> getSampleProvider(GBDevice device, DaoSession session) {
+        return new ZeTimeSampleProvider(device, session);
+    }
+
+    @Override
+    protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) {
+
+    }
+
+    @Override
+    public boolean supportsRealtimeData() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsMusicInfo() {
+        return true;
+    }
+
+    @Override
+    public int getBondingStyle() {
+        return BONDING_STYLE_NONE;
+    }
+
+    @Override
+    public boolean supportsUnicodeEmojis() {
+        return  true;
+    }
+
+    @Override
+    public DeviceSpecificSettings getDeviceSpecificSettings(final GBDevice device) {
+        final DeviceSpecificSettings deviceSpecificSettings = new DeviceSpecificSettings();
+
+        final List<Integer> generic = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.GENERIC);
+        generic.add(R.xml.devicesettings_wearlocation);
+
+        final List<Integer> datetime = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DATE_TIME);
+        datetime.add(R.xml.devicesettings_timeformat);
+        datetime.add(R.xml.devicesettings_zetime_date_format);
+
+        final List<Integer> display = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DISPLAY);
+        display.add(R.xml.devicesettings_zetime_display);
+
+        final List<Integer> health = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.HEALTH);
+        health.add(R.xml.devicesettings_zetime_health);
+
+        final List<Integer> notifications = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.CALLS_AND_NOTIFICATIONS);
+        notifications.add(R.xml.devicesettings_donotdisturb_no_auto);
+        notifications.add(R.xml.devicesettings_zetime_vibrationpatterns);
+        notifications.add(R.xml.devicesettings_transliteration);
+
+        final List<Integer> calendar = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.CALENDAR);
+        calendar.add(R.xml.devicesettings_sync_calendar);
+
+        final List<Integer> activityInfo = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.ACTIVITY_INFO);
+        activityInfo.add(R.xml.devicesettings_zetime_activity_info);
+
+        final List<Integer> developer = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DEVELOPER);
+        developer.add(R.xml.devicesettings_keep_activity_data_on_device);
+
+        return deviceSpecificSettings;
+    }
+
+    @Override
+    public DeviceSpecificSettingsCustomizer getDeviceSpecificSettingsCustomizer(final GBDevice device) {
+        return new ZeTimeSettingsCustomizer(device);
+    }
+
+    @Override
+    public List<HeartRateCapability.MeasurementInterval> getHeartRateMeasurementIntervals() {
+        return Arrays.asList(
+                HeartRateCapability.MeasurementInterval.OFF,
+                HeartRateCapability.MeasurementInterval.MINUTES_5,
+                HeartRateCapability.MeasurementInterval.MINUTES_10,
+                HeartRateCapability.MeasurementInterval.MINUTES_15,
+                HeartRateCapability.MeasurementInterval.MINUTES_30,
+                HeartRateCapability.MeasurementInterval.MINUTES_45,
+                HeartRateCapability.MeasurementInterval.HOUR_1
+        );
+    }
+
+    @Override
+    public String[] getSupportedLanguageSettings(final GBDevice device) {
+        return new String[]{
+                "auto",
+                "zh_CN",
+                "zh_TW",
+                "en_US",
+                "es_ES",
+                "de_DE",
+                "it_IT",
+                "fr_FR",
+                "pt_PT",
+                "nl_NL",
+                "sv_SE",
+                "pl_PL",
+                "cs_CZ",
+                "el_GR",
+                "ru_RU",
+                "he_IL",
+                "ar_SA",
+                "ja_JP",
+                "ko_KO",
+                "th_TH",
+        };
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return ZeTimeDeviceSupport.class;
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_mykronoz_zetime;
+    }
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_zetime;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_zetime_disabled;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/zetime/ZeTimeSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/zetime/ZeTimeSampleProvider.java
new file mode 100644
index 0000000..b75f5ab
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/zetime/ZeTimeSampleProvider.java
@@ -0,0 +1,85 @@
+/*  Copyright (C) 2018-2024 Daniele Gobbetti, Sebastian Kranz
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.zetime;
+
+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.ZeTimeActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.entities.ZeTimeActivitySampleDao;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+
+public class ZeTimeSampleProvider extends AbstractSampleProvider<ZeTimeActivitySample> {
+
+    private final float movementDivisor = 6000.0f;
+    private GBDevice mDevice;
+    private DaoSession mSession;
+
+    public ZeTimeSampleProvider(GBDevice device, DaoSession session) {
+        super(device, session);
+
+        mSession = session;
+        mDevice = device;
+    }
+
+    @Override
+    public ActivityKind normalizeType(int rawType) {
+        return ActivityKind.fromCode(rawType);
+    }
+
+    @Override
+    public int toRawActivityKind(ActivityKind activityKind) {
+        return activityKind.getCode();
+    }
+
+    @Override
+    public float normalizeIntensity(int rawIntensity) {
+        return rawIntensity/movementDivisor;
+    }
+
+    @Override
+    public ZeTimeActivitySample createActivitySample() {
+        return new ZeTimeActivitySample();
+    }
+
+    @Override
+    public AbstractDao<ZeTimeActivitySample, ?> getSampleDao() {
+        return getSession().getZeTimeActivitySampleDao();
+    }
+
+    @Nullable
+    @Override
+    protected Property getRawKindSampleProperty() {
+        return ZeTimeActivitySampleDao.Properties.RawKind;
+    }
+
+    @NonNull
+    @Override
+    protected Property getTimestampSampleProperty() {
+        return ZeTimeActivitySampleDao.Properties.Timestamp;
+    }
+
+    @NonNull
+    @Override
+    protected Property getDeviceIdentifierSampleProperty() {
+        return ZeTimeActivitySampleDao.Properties.DeviceId;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/zetime/ZeTimeSettingsCustomizer.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/zetime/ZeTimeSettingsCustomizer.java
new file mode 100644
index 0000000..1d4973f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/zetime/ZeTimeSettingsCustomizer.java
@@ -0,0 +1,111 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.zetime;
+
+import android.os.Parcel;
+import android.text.InputType;
+
+import androidx.preference.Preference;
+
+import java.util.Collections;
+import java.util.Set;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsHandler;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+public class ZeTimeSettingsCustomizer implements DeviceSpecificSettingsCustomizer {
+    private final GBDevice device;
+
+    public ZeTimeSettingsCustomizer(final GBDevice device) {
+        this.device = device;
+    }
+
+    @Override
+    public void onPreferenceChange(final Preference preference, final DeviceSpecificSettingsHandler handler) {
+
+    }
+
+    @Override
+    public void customizeSettings(final DeviceSpecificSettingsHandler handler, final Prefs prefs, final String rootKey) {
+        if (rootKey == null) {
+            // Main screen - read the settings
+            GBApplication.deviceService(device).onReadConfiguration("do_it");
+        }
+
+        // Date time
+        handler.addPreferenceHandlerFor(ZeTimeConstants.PREF_DATE_FORMAT);
+
+        // Display
+        handler.addPreferenceHandlerFor(ZeTimeConstants.PREF_SCREENTIME);
+        handler.setInputTypeFor(ZeTimeConstants.PREF_SCREENTIME, InputType.TYPE_CLASS_NUMBER);
+        handler.addPreferenceHandlerFor(ZeTimeConstants.PREF_ANALOG_MODE);
+        handler.addPreferenceHandlerFor(ZeTimeConstants.PREF_HANDMOVE_DISPLAY);
+
+        // Vibration patterns
+        handler.addPreferenceHandlerFor(ZeTimeConstants.PREF_SMS_SIGNALING);
+        handler.addPreferenceHandlerFor(ZeTimeConstants.PREF_ANTI_LOSS_SIGNALING);
+        handler.addPreferenceHandlerFor(ZeTimeConstants.PREF_CALENDAR_SIGNALING);
+        handler.addPreferenceHandlerFor(ZeTimeConstants.PREF_CALL_SIGNALING);
+        handler.addPreferenceHandlerFor(ZeTimeConstants.PREF_MISSED_CALL_SIGNALING);
+        handler.addPreferenceHandlerFor(ZeTimeConstants.PREF_EMAIL_SIGNALING);
+        handler.addPreferenceHandlerFor(ZeTimeConstants.PREF_INACTIVITY_SIGNALING);
+        handler.addPreferenceHandlerFor(ZeTimeConstants.PREF_LOW_POWER_SIGNALING);
+        handler.addPreferenceHandlerFor(ZeTimeConstants.PREF_SOCIAL_SIGNALING);
+
+        // Heart rate
+        handler.addPreferenceHandlerFor(ZeTimeConstants.PREF_ZETIME_HEARTRATE_ALARM);
+        handler.addPreferenceHandlerFor(ZeTimeConstants.PREF_ZETIME_MAX_HEARTRATE);
+        handler.addPreferenceHandlerFor(ZeTimeConstants.PREF_ZETIME_MIN_HEARTRATE);
+        handler.setInputTypeFor(ZeTimeConstants.PREF_ZETIME_MAX_HEARTRATE, InputType.TYPE_CLASS_NUMBER);
+        handler.setInputTypeFor(ZeTimeConstants.PREF_ZETIME_MIN_HEARTRATE, InputType.TYPE_CLASS_NUMBER);
+
+        // Activity info
+        handler.addPreferenceHandlerFor(ZeTimeConstants.PREF_ACTIVITY_TRACKING);
+        handler.addPreferenceHandlerFor(ZeTimeConstants.PREF_CALORIES_TYPE);
+    }
+
+    @Override
+    public Set<String> getPreferenceKeysWithSummary() {
+        return Collections.emptySet();
+    }
+
+    public static final Creator<ZeTimeSettingsCustomizer> CREATOR = new Creator<ZeTimeSettingsCustomizer>() {
+        @Override
+        public ZeTimeSettingsCustomizer createFromParcel(final Parcel in) {
+            final GBDevice device = in.readParcelable(ZeTimeSettingsCustomizer.class.getClassLoader());
+            return new ZeTimeSettingsCustomizer(device);
+        }
+
+        @Override
+        public ZeTimeSettingsCustomizer[] newArray(final int size) {
+            return new ZeTimeSettingsCustomizer[size];
+        }
+    };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(final Parcel dest, final int flags) {
+        dest.writeParcelable(device, 0);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/.gitignore b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/.gitignore
new file mode 100644
index 0000000..b2099c8
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/.gitignore
@@ -0,0 +1,2 @@
+*.java
+!Abstract*.java
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/AbstractActivitySample.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/AbstractActivitySample.java
new file mode 100644
index 0000000..60be8c8
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/AbstractActivitySample.java
@@ -0,0 +1,127 @@
+/*  Copyright (C) 2016-2024 Andreas Shimokawa, Carsten Pfeiffer
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.entities;
+
+import androidx.annotation.NonNull;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
+
+public abstract class AbstractActivitySample implements ActivitySample {
+    private SampleProvider<?> mProvider;
+
+    @Override
+    public SampleProvider<?> getProvider() {
+        return mProvider;
+    }
+
+    public void setProvider(SampleProvider<?> provider) {
+        mProvider = provider;
+    }
+
+    @Override
+    public ActivityKind getKind() {
+        return getProvider().normalizeType(getRawKind());
+    }
+
+    @Override
+    public int getRawKind() {
+        return NOT_MEASURED;
+    }
+
+    @Override
+    public float getIntensity() {
+        return getProvider().normalizeIntensity(getRawIntensity());
+    }
+
+    public void setRawKind(int kind) {
+    }
+
+    public void setRawIntensity(int intensity) {
+    }
+
+    public void setSteps(int steps) {
+    }
+
+    public void setDistanceCm(int distance) {
+    }
+
+    public void setActiveCalories(int activeCalories) {
+    }
+
+    /**
+     * Unix timestamp of the sample, i.e. the number of seconds since 1970-01-01 00:00:00 UTC.
+     */
+    public abstract void setTimestamp(int timestamp);
+
+    public abstract void setUserId(long userId);
+
+    @Override
+    public void setHeartRate(int heartRate) {
+    }
+
+    @Override
+    public int getHeartRate() {
+        return NOT_MEASURED;
+    }
+
+    public abstract void setDeviceId(long deviceId);
+
+    public abstract long getDeviceId();
+
+    public abstract long getUserId();
+
+    @Override
+    public int getRawIntensity() {
+        return NOT_MEASURED;
+    }
+
+    @Override
+    public int getSteps() {
+        return NOT_MEASURED;
+    }
+
+    @Override
+    public int getDistanceCm() {
+        return NOT_MEASURED;
+    }
+
+    @Override
+    public int getActiveCalories() {
+        return NOT_MEASURED;
+    }
+
+    @NonNull
+    @Override
+    public String toString() {
+        ActivityKind kind = getProvider() != null ? getKind() : ActivityKind.NOT_MEASURED;
+        float intensity = getProvider() != null ? getIntensity() : ActivitySample.NOT_MEASURED;
+        return getClass().getSimpleName() + "{" +
+                "timestamp=" + DateTimeUtils.formatDateTime(DateTimeUtils.parseTimeStamp(getTimestamp())) +
+                ", intensity=" + intensity +
+                ", steps=" + getSteps() +
+                ", distanceCm=" + getDistanceCm() +
+                ", activeCalories=" + getActiveCalories() +
+                ", heartRate=" + getHeartRate() +
+                ", type=" + kind +
+                ", userId=" + getUserId() +
+                ", deviceId=" + getDeviceId() +
+                '}';
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/AbstractBodyEnergySample.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/AbstractBodyEnergySample.java
new file mode 100644
index 0000000..6c0bcee
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/AbstractBodyEnergySample.java
@@ -0,0 +1,36 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.entities;
+
+import androidx.annotation.NonNull;
+
+import nodomain.freeyourgadget.gadgetbridge.model.BodyEnergySample;
+import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
+
+public abstract class AbstractBodyEnergySample extends AbstractTimeSample implements BodyEnergySample {
+
+    @NonNull
+    @Override
+    public String toString() {
+        return getClass().getSimpleName() + "{" +
+                "timestamp=" + DateTimeUtils.formatDateTime(DateTimeUtils.parseTimestampMillis(getTimestamp())) +
+                ", energy=" + getEnergy() +
+                ", userId=" + getUserId() +
+                ", deviceId=" + getDeviceId() +
+                "}";
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/AbstractColmiActivitySample.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/AbstractColmiActivitySample.java
new file mode 100644
index 0000000..97d88c7
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/AbstractColmiActivitySample.java
@@ -0,0 +1,37 @@
+/*  Copyright (C) 2024 Arjan Schrijver
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.entities;
+
+public abstract class AbstractColmiActivitySample extends AbstractActivitySample {
+    private int rawIntensity = 0;
+
+    abstract public int getCalories();
+
+    @Override
+    public void setRawIntensity(int rawIntensity) {
+        this.rawIntensity = rawIntensity;
+    }
+
+    @Override
+    public int getRawIntensity() {
+        if (rawIntensity != 0) {
+            return rawIntensity;
+        } else {
+            return getCalories();
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/AbstractFitProActivitySample.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/AbstractFitProActivitySample.java
new file mode 100644
index 0000000..f72fc4a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/AbstractFitProActivitySample.java
@@ -0,0 +1,60 @@
+/*  Copyright (C) 2021-2024 Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.entities;
+
+public abstract class AbstractFitProActivitySample extends AbstractActivitySample {
+
+    abstract public int getSteps();
+
+    @Override
+    public int getRawIntensity() {
+        return getSteps();
+    }
+
+    @Override
+    public void setTimestamp(int timestamp) {
+
+    }
+
+    @Override
+    public void setUserId(long userId) {
+
+    }
+
+    @Override
+    public void setDeviceId(long deviceId) {
+
+    }
+
+    @Override
+    public long getDeviceId() {
+        return 0;
+    }
+
+    @Override
+    public long getUserId() {
+        return 0;
+    }
+
+    @Override
+    public int getTimestamp() {
+        return 0;
+    }
+}
+
+
+
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/AbstractGBX100ActivitySample.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/AbstractGBX100ActivitySample.java
new file mode 100644
index 0000000..bd32330
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/AbstractGBX100ActivitySample.java
@@ -0,0 +1,27 @@
+/*  Copyright (C) 2020-2024 Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.entities;
+
+public abstract class AbstractGBX100ActivitySample extends AbstractActivitySample {
+    abstract public int getCalories();
+    abstract public int getSteps();
+
+    @Override
+    public int getRawIntensity() {
+        return getCalories();
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/AbstractHeartRateSample.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/AbstractHeartRateSample.java
new file mode 100644
index 0000000..7c2327c
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/AbstractHeartRateSample.java
@@ -0,0 +1,35 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.entities;
+
+import androidx.annotation.NonNull;
+
+import nodomain.freeyourgadget.gadgetbridge.model.HeartRateSample;
+import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
+
+public abstract class AbstractHeartRateSample extends AbstractTimeSample implements HeartRateSample {
+    @NonNull
+    @Override
+    public String toString() {
+        return getClass().getSimpleName() + "{" +
+                "timestamp=" + DateTimeUtils.formatDateTime(DateTimeUtils.parseTimestampMillis(getTimestamp())) +
+                ", hr=" + getHeartRate() +
+                ", userId=" + getUserId() +
+                ", deviceId=" + getDeviceId() +
+                "}";
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/AbstractHrvSummarySample.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/AbstractHrvSummarySample.java
new file mode 100644
index 0000000..1c4ba56
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/AbstractHrvSummarySample.java
@@ -0,0 +1,50 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.entities;
+
+import androidx.annotation.NonNull;
+
+import nodomain.freeyourgadget.gadgetbridge.model.HrvSummarySample;
+import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
+
+public abstract class AbstractHrvSummarySample extends AbstractTimeSample implements HrvSummarySample {
+    public Integer getStatusNum() {
+        return Status.NONE.getNum();
+    }
+
+    @Override
+    public Status getStatus() {
+        return Status.fromNum(getStatusNum());
+    }
+
+    @NonNull
+    @Override
+    public String toString() {
+        return getClass().getSimpleName() + "{" +
+                "timestamp=" + DateTimeUtils.formatDateTime(DateTimeUtils.parseTimestampMillis(getTimestamp())) +
+                ", weeklyAverage=" + getWeeklyAverage() +
+                ", lastNightAverage=" + getLastNightAverage() +
+                ", lastNight5MinHigh=" + getLastNight5MinHigh() +
+                ", baselineLowUpper=" + getBaselineLowUpper() +
+                ", baselineBalancedLower=" + getBaselineBalancedLower() +
+                ", baselineBalancedUpper=" + getBaselineBalancedUpper() +
+                ", status=" + getStatus() +
+                ", userId=" + getUserId() +
+                ", deviceId=" + getDeviceId() +
+                "}";
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/AbstractHrvValueSample.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/AbstractHrvValueSample.java
new file mode 100644
index 0000000..8e80e94
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/AbstractHrvValueSample.java
@@ -0,0 +1,35 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.entities;
+
+import androidx.annotation.NonNull;
+
+import nodomain.freeyourgadget.gadgetbridge.model.HrvValueSample;
+import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
+
+public abstract class AbstractHrvValueSample extends AbstractTimeSample implements HrvValueSample {
+    @NonNull
+    @Override
+    public String toString() {
+        return getClass().getSimpleName() + "{" +
+                "timestamp=" + DateTimeUtils.formatDateTime(DateTimeUtils.parseTimestampMillis(getTimestamp())) +
+                ", value=" + getValue() +
+                ", userId=" + getUserId() +
+                ", deviceId=" + getDeviceId() +
+                "}";
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/AbstractHybridHRActivitySample.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/AbstractHybridHRActivitySample.java
new file mode 100644
index 0000000..d4dd3f2
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/AbstractHybridHRActivitySample.java
@@ -0,0 +1,40 @@
+/*  Copyright (C) 2020-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.entities;
+
+public abstract class AbstractHybridHRActivitySample extends AbstractActivitySample {
+    abstract public int getCalories();
+    abstract public byte getWear_type();
+
+    @Override
+    public int getRawKind() {
+        return getWear_type();
+    }
+
+    @Override
+    public int getRawIntensity() {
+        return getSteps();
+    }
+
+    @Override
+    public void setUserId(long userId) {}
+
+    @Override
+    public long getUserId() {
+        return 0;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/AbstractPaiSample.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/AbstractPaiSample.java
new file mode 100644
index 0000000..e8ff17b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/AbstractPaiSample.java
@@ -0,0 +1,42 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.entities;
+
+import androidx.annotation.NonNull;
+
+import nodomain.freeyourgadget.gadgetbridge.model.PaiSample;
+import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
+
+public abstract class AbstractPaiSample extends AbstractTimeSample implements PaiSample {
+    @NonNull
+    @Override
+    public String toString() {
+        return getClass().getSimpleName() + "{" +
+                "timestamp=" + DateTimeUtils.formatDateTime(DateTimeUtils.parseTimestampMillis(getTimestamp())) +
+                ", paiLow=" + getPaiLow() +
+                ", paiModerate=" + getPaiModerate() +
+                ", paiHigh=" + getPaiHigh() +
+                ", timeLow=" + getTimeLow() +
+                ", timeModerate=" + getTimeModerate() +
+                ", timeHigh=" + getTimeHigh() +
+                ", paiToday=" + getPaiToday() +
+                ", paiTotal=" + getPaiTotal() +
+                ", userId=" + getUserId() +
+                ", deviceId=" + getDeviceId() +
+                "}";
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/AbstractPebbleHealthActivitySample.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/AbstractPebbleHealthActivitySample.java
new file mode 100644
index 0000000..d18015b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/AbstractPebbleHealthActivitySample.java
@@ -0,0 +1,35 @@
+/*  Copyright (C) 2016-2024 Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.entities;
+
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+
+public abstract class AbstractPebbleHealthActivitySample extends AbstractActivitySample {
+    abstract public byte[] getRawPebbleHealthData();
+
+    private transient int rawActivityKind = ActivityKind.UNKNOWN.getCode();
+
+    @Override
+    public int getRawKind() {
+        return rawActivityKind;
+    }
+
+    @Override
+    public void setRawKind(int kind) {
+        this.rawActivityKind = kind;
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/AbstractPebbleMisfitActivitySample.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/AbstractPebbleMisfitActivitySample.java
new file mode 100644
index 0000000..a1b482e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/AbstractPebbleMisfitActivitySample.java
@@ -0,0 +1,70 @@
+/*  Copyright (C) 2016-2024 Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.entities;
+
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+
+public abstract class AbstractPebbleMisfitActivitySample extends AbstractActivitySample {
+    abstract public int getRawPebbleMisfitSample();
+
+    private transient int intensity = 0;
+    private transient int steps = 0;
+    private transient ActivityKind activityKind = ActivityKind.UNKNOWN;
+
+    private void calculate() {
+        int sample = getRawPebbleMisfitSample();
+
+        if (((sample & 0x83ff) == 0x0001) && ((sample & 0xff00) <= 0x4800)) {
+            // sleep seems to be from 0x2401 to 0x4801  (0b0IIIII0000000001) where I = intensity ?
+            intensity = (sample & 0x7c00) >>> 10;
+            // 9-18 decimal after shift
+            if (intensity <= 13) {
+                activityKind = ActivityKind.DEEP_SLEEP;
+            } else {
+                // FIXME: this leads to too much false positives, ignore for now
+                //activityKind = ActivityKind.LIGHT_SLEEP;
+                //intensity *= 2; // better visual distinction
+            }
+        } else {
+            if ((sample & 0x0001) == 0) { // 16-??? steps encoded in bits 1-7
+                steps = (sample & 0x00fe);
+            } else { // 0-14 steps encoded in bits 1-3, most of the time fc71 bits are set in that case
+                steps = (sample & 0x000e);
+            }
+            intensity = steps;
+            activityKind = ActivityKind.ACTIVITY;
+        }
+    }
+
+    @Override
+    public int getSteps() {
+        calculate();
+        return steps;
+    }
+
+    @Override
+    public ActivityKind getKind() {
+        calculate();
+        return activityKind;
+    }
+
+    @Override
+    public int getRawIntensity() {
+        calculate();
+        return intensity;
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/AbstractPebbleMorpheuzActivitySample.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/AbstractPebbleMorpheuzActivitySample.java
new file mode 100644
index 0000000..7e3a28a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/AbstractPebbleMorpheuzActivitySample.java
@@ -0,0 +1,33 @@
+/*  Copyright (C) 2016-2024 Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.entities;
+
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+
+public abstract class AbstractPebbleMorpheuzActivitySample extends AbstractActivitySample {
+
+    @Override
+    public ActivityKind getKind() {
+        int rawIntensity = getRawIntensity();
+        if (rawIntensity <= 120) {
+            return ActivityKind.DEEP_SLEEP;
+        } else if (rawIntensity <= 1000) {
+            return ActivityKind.LIGHT_SLEEP;
+        }
+        return ActivityKind.ACTIVITY;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/AbstractRespiratoryRateSample.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/AbstractRespiratoryRateSample.java
new file mode 100644
index 0000000..fb51f89
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/AbstractRespiratoryRateSample.java
@@ -0,0 +1,35 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.entities;
+
+import androidx.annotation.NonNull;
+
+import nodomain.freeyourgadget.gadgetbridge.model.RespiratoryRateSample;
+import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
+
+public abstract class AbstractRespiratoryRateSample extends AbstractTimeSample implements RespiratoryRateSample {
+    @NonNull
+    @Override
+    public String toString() {
+        return getClass().getSimpleName() + "{" +
+                "timestamp=" + DateTimeUtils.formatDateTime(DateTimeUtils.parseTimestampMillis(getTimestamp())) +
+                ", respiratoryRate=" + getRespiratoryRate() +
+                ", userId=" + getUserId() +
+                ", deviceId=" + getDeviceId() +
+                "}";
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/AbstractSpo2Sample.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/AbstractSpo2Sample.java
new file mode 100644
index 0000000..9bae4eb
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/AbstractSpo2Sample.java
@@ -0,0 +1,45 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.entities;
+
+import androidx.annotation.NonNull;
+
+import nodomain.freeyourgadget.gadgetbridge.model.Spo2Sample;
+import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
+
+public abstract class AbstractSpo2Sample extends AbstractTimeSample implements Spo2Sample {
+    public int getTypeNum() {
+        return Spo2Sample.Type.UNKNOWN.getNum();
+    }
+
+    @Override
+    public Type getType() {
+        return Type.fromNum(getTypeNum());
+    }
+
+    @NonNull
+    @Override
+    public String toString() {
+        return getClass().getSimpleName() + "{" +
+                "timestamp=" + DateTimeUtils.formatDateTime(DateTimeUtils.parseTimestampMillis(getTimestamp())) +
+                ", spo2=" + getSpo2() +
+                ", type=" + getType() +
+                ", userId=" + getUserId() +
+                ", deviceId=" + getDeviceId() +
+                "}";
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/AbstractStressSample.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/AbstractStressSample.java
new file mode 100644
index 0000000..e2df694
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/AbstractStressSample.java
@@ -0,0 +1,45 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.entities;
+
+import androidx.annotation.NonNull;
+
+import nodomain.freeyourgadget.gadgetbridge.model.StressSample;
+import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
+
+public abstract class AbstractStressSample extends AbstractTimeSample implements StressSample {
+    public int getTypeNum() {
+        return Type.UNKNOWN.getNum();
+    }
+
+    @Override
+    public Type getType() {
+        return Type.fromNum(getTypeNum());
+    }
+
+    @NonNull
+    @Override
+    public String toString() {
+        return getClass().getSimpleName() + "{" +
+                "timestamp=" + DateTimeUtils.formatDateTime(DateTimeUtils.parseTimestampMillis(getTimestamp())) +
+                ", stress=" + getStress() +
+                ", type=" + getType() +
+                ", userId=" + getUserId() +
+                ", deviceId=" + getDeviceId() +
+                "}";
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/AbstractTemperatureSample.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/AbstractTemperatureSample.java
new file mode 100644
index 0000000..d337660
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/AbstractTemperatureSample.java
@@ -0,0 +1,36 @@
+/*  Copyright (C) 2023-2024 Alicia Hormann
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.entities;
+
+import androidx.annotation.NonNull;
+
+import nodomain.freeyourgadget.gadgetbridge.model.TemperatureSample;
+import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
+
+public abstract class AbstractTemperatureSample extends AbstractTimeSample implements TemperatureSample {
+    @NonNull
+    @Override
+    public String toString() {
+        return getClass().getSimpleName() + "{" +
+                "timestamp=" + DateTimeUtils.formatDateTime(DateTimeUtils.parseTimestampMillis(getTimestamp())) +
+                ", temperature=" + getTemperature() +
+                ", temperatureType=" + getTemperatureType() +
+                ", userId=" + getUserId() +
+                ", deviceId=" + getDeviceId() +
+                "}";
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/AbstractTimeSample.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/AbstractTimeSample.java
new file mode 100644
index 0000000..fcd8de0
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/AbstractTimeSample.java
@@ -0,0 +1,49 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.entities;
+
+import androidx.annotation.NonNull;
+
+import nodomain.freeyourgadget.gadgetbridge.model.TimeSample;
+import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
+
+public abstract class AbstractTimeSample implements TimeSample {
+    // Unix timestamp in milliseconds
+    public abstract void setTimestamp(long timestamp);
+
+    public abstract long getUserId();
+
+    public abstract void setUserId(long userId);
+
+    public abstract long getDeviceId();
+
+    public abstract void setDeviceId(long deviceId);
+
+    public abstract void setDevice(Device device);
+
+    public abstract void setUser(User user);
+
+    @NonNull
+    @Override
+    public String toString() {
+        return getClass().getSimpleName() + "{" +
+                "timestamp=" + DateTimeUtils.formatDateTime(DateTimeUtils.parseTimestampMillis(getTimestamp())) +
+                ", userId=" + getUserId() +
+                ", deviceId=" + getDeviceId() +
+                "}";
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/AbstractWeightSample.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/AbstractWeightSample.java
new file mode 100644
index 0000000..e4422c5
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/AbstractWeightSample.java
@@ -0,0 +1,35 @@
+/*  Copyright (C) 2024 Severin von Wnuck-Lipinski
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.entities;
+
+import androidx.annotation.NonNull;
+
+import nodomain.freeyourgadget.gadgetbridge.model.WeightSample;
+import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
+
+public abstract class AbstractWeightSample extends AbstractTimeSample implements WeightSample {
+    @NonNull
+    @Override
+    public String toString() {
+        return getClass().getSimpleName() + "{" +
+                "timestamp=" + DateTimeUtils.formatDateTime(DateTimeUtils.parseTimestampMillis(getTimestamp())) +
+                ", weightKg=" + getWeightKg() +
+                ", userId=" + getUserId() +
+                ", deviceId=" + getDeviceId() +
+                "}";
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/export/ActivityTrackExporter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/export/ActivityTrackExporter.java
new file mode 100644
index 0000000..9548cbe
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/export/ActivityTrackExporter.java
@@ -0,0 +1,29 @@
+/*  Copyright (C) 2017-2024 Carsten Pfeiffer, Daniele Gobbetti, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.export;
+
+import java.io.File;
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityTrack;
+
+public interface ActivityTrackExporter {
+    void performExport(ActivityTrack track, File targetFile) throws IOException, GPXTrackEmptyException;
+
+    class GPXTrackEmptyException extends Exception {
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/export/GPXExporter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/export/GPXExporter.java
new file mode 100644
index 0000000..806433d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/export/GPXExporter.java
@@ -0,0 +1,246 @@
+/*  Copyright (C) 2017-2024 Andreas Shimokawa, AndrewH, Carsten Pfeiffer,
+    Daniele Gobbetti, Dikay900, Jos茅 Rebelo, Nick Spacek, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.export;
+
+import android.util.Xml;
+
+import androidx.annotation.Nullable;
+
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.nio.charset.StandardCharsets;
+import java.util.Date;
+import java.util.List;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.activities.HeartRateUtils;
+import nodomain.freeyourgadget.gadgetbridge.entities.User;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityPoint;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityTrack;
+import nodomain.freeyourgadget.gadgetbridge.model.GPSCoordinate;
+import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
+
+public class GPXExporter implements ActivityTrackExporter {
+    private static final String NS_GPX_URI = "http://www.topografix.com/GPX/1/1";
+    private static final String NS_GPX_PREFIX = "";
+    private static final String NS_TRACKPOINT_EXTENSION = "gpxtpx";
+    private static final String NS_TRACKPOINT_EXTENSION_URI = "https://www8.garmin.com/xmlschemas/TrackPointExtensionv2.xsd";
+    private static final String NS_XSI_URI = "http://www.w3.org/2001/XMLSchema-instance";
+    private static final String TOPOGRAFIX_NAMESPACE_XSD = "http://www.topografix.com/GPX/1/1/gpx.xsd";
+    private static final String OPENTRACKS_PREFIX = "opentracks";
+    private static final String OPENTRACKS_NAMESPACE_URI = "http://opentracksapp.com/xmlschemas/v1";
+
+    private String creator;
+    private boolean includeHeartRate = true;
+    private boolean includeHeartRateOfNearestSample = true;
+
+    @Override
+    public void performExport(ActivityTrack track, File targetFile) throws IOException, GPXTrackEmptyException {
+        String encoding = StandardCharsets.UTF_8.name();
+        XmlSerializer ser = Xml.newSerializer();
+        try (FileOutputStream outputStream = new FileOutputStream(targetFile)) {
+            ser.setOutput(outputStream, encoding);
+            ser.startDocument(encoding, Boolean.TRUE);
+            ser.setPrefix("xsi", NS_XSI_URI);
+            ser.setPrefix(NS_TRACKPOINT_EXTENSION, NS_TRACKPOINT_EXTENSION_URI);
+            ser.setPrefix(NS_GPX_PREFIX, NS_GPX_URI);
+            ser.setPrefix(OPENTRACKS_PREFIX, OPENTRACKS_NAMESPACE_URI);
+
+            ser.startTag(NS_GPX_URI, "gpx");
+            ser.attribute(null, "version", "1.1");
+            if (creator != null) {
+                ser.attribute(null, "creator", creator);
+            } else {
+                ser.attribute(null, "creator", GBApplication.app().getNameAndVersion());
+            }
+            ser.attribute(NS_XSI_URI, "schemaLocation",NS_GPX_URI + " " + TOPOGRAFIX_NAMESPACE_XSD);
+
+            exportMetadata(ser, track);
+            exportTrack(ser, track);
+
+            ser.endTag(NS_GPX_URI, "gpx");
+            ser.endDocument();
+            ser.flush();
+        }
+    }
+
+    private void exportMetadata(XmlSerializer ser, ActivityTrack track) throws IOException {
+        ser.startTag(NS_GPX_URI, "metadata");
+        if (track.getName() != null) {
+            ser.startTag(NS_GPX_URI, "name").text(track.getName()).endTag(NS_GPX_URI, "name");
+        }
+
+        final User user = track.getUser();
+        if (user != null) {
+            ser.startTag(NS_GPX_URI, "author");
+            ser.startTag(NS_GPX_URI, "name").text(user.getName()).endTag(NS_GPX_URI, "name");
+            ser.endTag(NS_GPX_URI, "author");
+        }
+
+        ser.startTag(NS_GPX_URI, "time").text(formatTime(new Date())).endTag(NS_GPX_URI, "time");
+
+        ser.endTag(NS_GPX_URI, "metadata");
+    }
+
+    private String formatTime(Date date) {
+        return DateTimeUtils.formatIso8601(date);
+    }
+
+    private void exportTrack(XmlSerializer ser, ActivityTrack track) throws IOException, GPXTrackEmptyException {
+        String uuid = UUID.randomUUID().toString();
+        ser.startTag(NS_GPX_URI, "trk");
+        ser.startTag(NS_GPX_URI, "extensions");
+        ser.startTag(NS_GPX_URI, OPENTRACKS_PREFIX + ":trackid").text(uuid).endTag(NS_GPX_URI, OPENTRACKS_PREFIX + ":trackid");
+        ser.endTag(NS_GPX_URI, "extensions");
+
+        List<List<ActivityPoint>> segments = track.getSegments();
+        boolean atLeastOnePointExported = false;
+        for (List<ActivityPoint> segment : segments) {
+            if (segment.isEmpty()) {
+                // Skip empty segments
+                continue;
+            }
+
+            ser.startTag(NS_GPX_URI, "trkseg");
+            for (ActivityPoint point : segment) {
+                atLeastOnePointExported |= exportTrackPoint(ser, point, segment);
+            }
+            ser.endTag(NS_GPX_URI, "trkseg");
+        }
+
+        if (!atLeastOnePointExported) {
+            throw new GPXTrackEmptyException();
+        }
+
+        ser.endTag(NS_GPX_URI, "trk");
+    }
+
+    private boolean exportTrackPoint(XmlSerializer ser, ActivityPoint point, List<ActivityPoint> trackPoints) throws IOException {
+        GPSCoordinate location = point.getLocation();
+        if (location == null) {
+            return false; // skip invalid points, that just contain hr data, for example
+        }
+        ser.startTag(NS_GPX_URI, "trkpt");
+        // lon and lat attributes do not have an explicit namespace
+        ser.attribute(null, "lon", formatDouble(location.getLongitude()));
+        ser.attribute(null, "lat", formatDouble(location.getLatitude()));
+        if (location.getAltitude() != GPSCoordinate.UNKNOWN_ALTITUDE) {
+            ser.startTag(NS_GPX_URI, "ele").text(formatDouble(location.getAltitude())).endTag(NS_GPX_URI, "ele");
+        }
+        ser.startTag(NS_GPX_URI, "time").text(DateTimeUtils.formatIso8601UTC(point.getTime())).endTag(NS_GPX_URI, "time");
+        String description = point.getDescription();
+        if (description != null) {
+            ser.startTag(NS_GPX_URI, "desc").text(description).endTag(NS_GPX_URI, "desc");
+        }
+        //ser.startTag(NS_GPX_URI, "src").text(source).endTag(NS_GPX_URI, "src");
+
+        exportTrackpointExtensions(ser, point, trackPoints);
+
+        ser.endTag(NS_GPX_URI, "trkpt");
+
+        return true;
+    }
+
+    private void exportTrackpointExtensions(XmlSerializer ser, ActivityPoint point, List<ActivityPoint> trackPoints) throws IOException {
+        if (!includeHeartRate) {
+            return;
+        }
+
+        float speed = point.getSpeed();
+        int cadence = point.getCadence();
+        int hr = point.getHeartRate();
+        if (!HeartRateUtils.getInstance().isValidHeartRateValue(hr) && includeHeartRateOfNearestSample) {
+
+            ActivityPoint closestPointItem = findClosestSensibleActivityPoint(point.getTime(), trackPoints);
+            if (closestPointItem != null) {
+                hr = closestPointItem.getHeartRate();
+            }
+
+        }
+
+        boolean exportHr = HeartRateUtils.getInstance().isValidHeartRateValue(hr) && includeHeartRate;
+
+        if (!exportHr && speed < 0 && cadence < 0) {
+            // No valid data to export in extensions
+            return;
+        }
+
+        ser.startTag(NS_GPX_URI, "extensions");
+        ser.setPrefix(NS_TRACKPOINT_EXTENSION, NS_TRACKPOINT_EXTENSION_URI);
+        ser.startTag(NS_TRACKPOINT_EXTENSION_URI, "TrackPointExtension");
+        if (exportHr) {
+            ser.startTag(NS_TRACKPOINT_EXTENSION_URI, "hr").text(String.valueOf(hr)).endTag(NS_TRACKPOINT_EXTENSION_URI, "hr");
+        }
+        if (cadence >= 0) {
+            ser.startTag(NS_TRACKPOINT_EXTENSION_URI, "cad").text(String.valueOf(cadence)).endTag(NS_TRACKPOINT_EXTENSION_URI, "cad");
+        }
+        if (speed >= 0) {
+            ser.startTag(NS_TRACKPOINT_EXTENSION_URI, "speed").text(formatDouble(speed)).endTag(NS_TRACKPOINT_EXTENSION_URI, "speed");
+        }
+        ser.endTag(NS_TRACKPOINT_EXTENSION_URI, "TrackPointExtension");
+        ser.endTag(NS_GPX_URI, "extensions");
+    }
+
+    private @Nullable ActivityPoint findClosestSensibleActivityPoint(Date time, List<ActivityPoint> trackPoints) {
+        ActivityPoint closestPointItem = null;
+        HeartRateUtils heartRateUtilsInstance = HeartRateUtils.getInstance();
+
+        long lowestDifference = 60 * 2 * 1000; // minimum distance is 2min
+        for (ActivityPoint pointItem : trackPoints) {
+            int hrItem = pointItem.getHeartRate();
+            if (heartRateUtilsInstance.isValidHeartRateValue(hrItem)) {
+                Date timeItem = pointItem.getTime();
+                if (timeItem.after(time) || timeItem.equals(time)) {
+                    break; // we assume that the given trackPoints are sorted in time ascending order (oldest first)
+                }
+                long difference = time.getTime() - timeItem.getTime();
+                if (difference < lowestDifference) {
+                    lowestDifference = difference;
+                    closestPointItem = pointItem;
+                }
+            }
+        }
+        return closestPointItem;
+    }
+
+    private String formatDouble(double value) {
+        return new BigDecimal(value).setScale(GPSCoordinate.GPS_DECIMAL_DEGREES_SCALE, RoundingMode.HALF_UP).toPlainString();
+    }
+
+    public String getCreator() {
+        return creator; // TODO: move to some kind of BrandingInfo class
+    }
+
+    public void setCreator(String creator) {
+        this.creator = creator;
+    }
+
+    public void setIncludeHeartRate(boolean includeHeartRate) {
+        this.includeHeartRate = includeHeartRate;
+    }
+
+    public boolean isIncludeHeartRate() {
+        return includeHeartRate;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/AlarmClockReceiver.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/AlarmClockReceiver.java
new file mode 100644
index 0000000..33fcb61
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/AlarmClockReceiver.java
@@ -0,0 +1,94 @@
+/*  Copyright (C) 2017-2024 Andreas Shimokawa, Carsten Pfeiffer, Daniele
+    Gobbetti
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.externalevents;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+import java.util.ArrayList;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.NotificationType;
+
+public class AlarmClockReceiver extends BroadcastReceiver {
+    /**
+     * AlarmActivity and AlarmService (when unbound) listen for this broadcast intent
+     * so that other applications can snooze the alarm (after ALARM_ALERT_ACTION and before
+     * ALARM_DONE_ACTION).
+     */
+    public static final String ALARM_SNOOZE_ACTION = "com.android.deskclock.ALARM_SNOOZE";
+
+    /**
+     * AlarmActivity and AlarmService listen for this broadcast intent so that other
+     * applications can dismiss the alarm (after ALARM_ALERT_ACTION and before ALARM_DONE_ACTION).
+     */
+    public static final String ALARM_DISMISS_ACTION = "com.android.deskclock.ALARM_DISMISS";
+
+    /** A public action sent by AlarmService when the alarm has started. */
+    public static final String ALARM_ALERT_ACTION = "com.android.deskclock.ALARM_ALERT";
+    public static final String GOOGLE_CLOCK_ALARM_ALERT_ACTION = "com.google.android.deskclock.action.ALARM_ALERT";
+
+    /** A public action sent by AlarmService when the alarm has stopped for any reason. */
+    public static final String ALARM_DONE_ACTION = "com.android.deskclock.ALARM_DONE";
+    public static final String GOOGLE_CLOCK_ALARM_DONE_ACTION = "com.google.android.deskclock.action.ALARM_DONE";
+    private int lastId;
+
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        String action = intent.getAction();
+        if (ALARM_ALERT_ACTION.equals(action) || GOOGLE_CLOCK_ALARM_ALERT_ACTION.equals(action)) {
+            sendAlarm(true);
+        } else if (ALARM_DONE_ACTION.equals(action) || GOOGLE_CLOCK_ALARM_DONE_ACTION.equals(action)) {
+            sendAlarm(false);
+        }
+    }
+
+
+
+    private synchronized void sendAlarm(boolean on) {
+        dismissLastAlarm();
+        if (on) {
+            NotificationSpec notificationSpec = new NotificationSpec();
+            //TODO: can we attach a dismiss action to the notification and not use the notification ID explicitly?
+            lastId = notificationSpec.getId();
+            notificationSpec.type = NotificationType.GENERIC_ALARM_CLOCK;
+            notificationSpec.sourceName = "ALARMCLOCKRECEIVER";
+            notificationSpec.attachedActions = new ArrayList<>();
+
+            // DISMISS ALL action
+            NotificationSpec.Action dismissAllAction = new NotificationSpec.Action();
+            dismissAllAction.title = "Dismiss All";
+            dismissAllAction.type = NotificationSpec.Action.TYPE_SYNTECTIC_DISMISS_ALL;
+            notificationSpec.attachedActions.add(dismissAllAction);
+
+            // can we get the alarm title somehow?
+            GBApplication.deviceService().onNotification(notificationSpec);
+        }
+    }
+
+    private void dismissLastAlarm() {
+        if (lastId != 0) {
+            GBApplication.deviceService().onDeleteNotification(lastId);
+            lastId = 0;
+        }
+    }
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/AlarmReceiver.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/AlarmReceiver.java
new file mode 100644
index 0000000..985bc8a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/AlarmReceiver.java
@@ -0,0 +1,113 @@
+/*  Copyright (C) 2016-2024 Andreas Shimokawa, Daniele Gobbetti, Ganblejs
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.externalevents;
+
+
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+import net.e175.klaus.solarpositioning.DeltaT;
+import net.e175.klaus.solarpositioning.SPA;
+import net.e175.klaus.solarpositioning.SunriseTransitSet;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.TimeZone;
+
+import nodomain.freeyourgadget.gadgetbridge.BuildConfig;
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
+import nodomain.freeyourgadget.gadgetbridge.util.GBPrefs;
+import nodomain.freeyourgadget.gadgetbridge.util.PendingIntentUtils;
+
+public class AlarmReceiver extends BroadcastReceiver {
+    private static final Logger LOG = LoggerFactory.getLogger(AlarmReceiver.class);
+
+    public AlarmReceiver() {
+        Context context = GBApplication.getContext();
+        Intent intent = new Intent("DAILY_ALARM");
+        intent.setPackage(BuildConfig.APPLICATION_ID);
+        PendingIntent pendingIntent = PendingIntentUtils.getBroadcast(context, 0, intent, 0, false);
+        AlarmManager am = (AlarmManager) (context.getSystemService(Context.ALARM_SERVICE));
+
+        if (am != null) {
+            am.setInexactRepeating(AlarmManager.RTC_WAKEUP, Calendar.getInstance().getTimeInMillis() + 10000, AlarmManager.INTERVAL_DAY, pendingIntent);
+        }
+        else {
+            LOG.warn("could not get alarm manager!");
+        }
+    }
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        LOG.info("will resend sunrise and sunset events");
+
+        final GregorianCalendar dateTimeTomorrow = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
+        dateTimeTomorrow.set(Calendar.HOUR, 0);
+        dateTimeTomorrow.set(Calendar.MINUTE, 0);
+        dateTimeTomorrow.set(Calendar.SECOND, 0);
+        dateTimeTomorrow.set(Calendar.MILLISECOND, 0);
+        dateTimeTomorrow.add(GregorianCalendar.DAY_OF_MONTH, 1);
+
+        /*
+         * rotate ids ud reuse the id from two days ago for tomorrow, this way we will have
+         * sunrise /sunset for 3 days while sending only sunrise/sunset per day
+         */
+        byte id_tomorrow = (byte) ((dateTimeTomorrow.getTimeInMillis() / (1000L * 60L * 60L * 24L)) % 3);
+
+        GBApplication.deviceService().onDeleteCalendarEvent(CalendarEventSpec.TYPE_SUNRISE, id_tomorrow);
+        GBApplication.deviceService().onDeleteCalendarEvent(CalendarEventSpec.TYPE_SUNSET, id_tomorrow);
+
+        GBPrefs gbPrefs = GBApplication.getPrefs();
+        float[] longlat = gbPrefs.getLongLat(context);
+        float longitude = longlat[0];
+        float latitude = longlat[1];
+
+        final SunriseTransitSet sunriseTransitSetTomorrow = SPA.calculateSunriseTransitSet(
+                dateTimeTomorrow.toZonedDateTime(),
+                latitude,
+                longitude,
+                DeltaT.estimate(dateTimeTomorrow.toZonedDateTime().toLocalDate())
+        );
+
+        CalendarEventSpec calendarEventSpec = new CalendarEventSpec();
+        calendarEventSpec.durationInSeconds = 0;
+        calendarEventSpec.description = null;
+
+        calendarEventSpec.type = CalendarEventSpec.TYPE_SUNRISE;
+        calendarEventSpec.title = "Sunrise";
+        if (sunriseTransitSetTomorrow.getSunrise() != null) {
+            calendarEventSpec.id = id_tomorrow;
+            calendarEventSpec.timestamp = (int) (sunriseTransitSetTomorrow.getSunrise().toInstant().getEpochSecond());
+            GBApplication.deviceService().onAddCalendarEvent(calendarEventSpec);
+        }
+
+        calendarEventSpec.type = CalendarEventSpec.TYPE_SUNSET;
+        calendarEventSpec.title = "Sunset";
+        if (sunriseTransitSetTomorrow.getSunset() != null) {
+            calendarEventSpec.id = id_tomorrow;
+            calendarEventSpec.timestamp = (int) (sunriseTransitSetTomorrow.getSunset().toInstant().getEpochSecond());
+            GBApplication.deviceService().onAddCalendarEvent(calendarEventSpec);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/AutoStartReceiver.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/AutoStartReceiver.java
new file mode 100644
index 0000000..5ce081a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/AutoStartReceiver.java
@@ -0,0 +1,47 @@
+/*  Copyright (C) 2017-2024 Carsten Pfeiffer, Daniele Gobbetti, Felix
+    Konstantin Maurer, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.externalevents;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.database.PeriodicExporter;
+
+public class AutoStartReceiver extends BroadcastReceiver {
+    private static final String TAG = AutoStartReceiver.class.getName();
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+
+        if (GBApplication.getPrefs().getAutoStart() &&
+                (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction()) ||
+                        Intent.ACTION_MY_PACKAGE_REPLACED.equals(intent.getAction())
+                )) {
+            Log.i(TAG, "Boot or reinstall completed, starting Gadgetbridge");
+            if (GBApplication.getPrefs().getBoolean("general_autoconnectonbluetooth", false)) {
+                Log.i(TAG, "Autoconnect is enabled, attempting to connect");
+                GBApplication.deviceService().connect();
+            }
+            Log.i(TAG, "Going to enable periodic exporter");
+            PeriodicExporter.enablePeriodicExport(context);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/BluetoothConnectReceiver.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/BluetoothConnectReceiver.java
new file mode 100644
index 0000000..c143565
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/BluetoothConnectReceiver.java
@@ -0,0 +1,77 @@
+/*  Copyright (C) 2016-2024 Andreas Shimokawa, Daniel Dakhno, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.externalevents;
+
+import android.bluetooth.BluetoothDevice;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceCommunicationService;
+import nodomain.freeyourgadget.gadgetbridge.util.GBPrefs;
+
+public class BluetoothConnectReceiver extends BroadcastReceiver {
+    private static final Logger LOG = LoggerFactory.getLogger(BluetoothConnectReceiver.class);
+
+    final DeviceCommunicationService service;
+
+    public BluetoothConnectReceiver(final DeviceCommunicationService service) {
+        this.service = service;
+    }
+
+    @Override
+    public void onReceive(final Context context, final Intent intent) {
+        final String action = intent.getAction();
+        if (action == null) {
+            return;
+        }
+
+        if (!action.equals(BluetoothDevice.ACTION_ACL_CONNECTED) || !intent.hasExtra(BluetoothDevice.EXTRA_DEVICE)) {
+            return;
+        }
+
+        final BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+        if (device == null) {
+            LOG.error("Got no device for {}", action);
+            return;
+        }
+
+        LOG.info("connection attempt detected from {}", device.getAddress());
+
+        final GBDevice gbDevice = GBApplication.app().getDeviceManager().getDeviceByAddress(device.getAddress());
+        if (gbDevice == null) {
+            LOG.info("Connected device {} unknown", device.getAddress());
+            return;
+        }
+        final SharedPreferences deviceSpecificPreferences = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress());
+        boolean reactToConnection = deviceSpecificPreferences.getBoolean(GBPrefs.DEVICE_CONNECT_BACK, false);
+        reactToConnection |= gbDevice.getState() == GBDevice.State.WAITING_FOR_RECONNECT;
+        reactToConnection |= gbDevice.getState() == GBDevice.State.WAITING_FOR_SCAN;
+        if (!reactToConnection) {
+            LOG.info("Ignoring connection attempt from {}", device.getAddress());
+            return;
+        }
+        LOG.info("Will re-connect to {} ({})", gbDevice.getAddress(), gbDevice.getName());
+        GBApplication.deviceService(gbDevice).connect();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/BluetoothPairingRequestReceiver.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/BluetoothPairingRequestReceiver.java
new file mode 100644
index 0000000..fcd8a3b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/BluetoothPairingRequestReceiver.java
@@ -0,0 +1,72 @@
+/*  Copyright (C) 2017-2024 Andreas Shimokawa, Daniel Dakhno, Daniele Gobbetti,
+    Joa虄o Paulo Barraca, Taavi Eom盲e
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.externalevents;
+
+import android.bluetooth.BluetoothDevice;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceCommunicationService;
+import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
+
+public class BluetoothPairingRequestReceiver extends BroadcastReceiver {
+    private static final Logger LOG = LoggerFactory.getLogger(BluetoothConnectReceiver.class);
+
+    final DeviceCommunicationService service;
+
+    public BluetoothPairingRequestReceiver(DeviceCommunicationService service) {
+        this.service = service;
+    }
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        String action = intent.getAction();
+
+
+        if (!action.equals(BluetoothDevice.ACTION_PAIRING_REQUEST)) {
+            return;
+        }
+        BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+        if (device == null) {
+            return;
+        }
+
+        GBDevice gbDevice = null;
+        try {
+            gbDevice = service.getDeviceByAddress(device.getAddress());
+
+            DeviceCoordinator coordinator = gbDevice.getDeviceCoordinator();
+            try {
+                if (coordinator.getBondingStyle() == DeviceCoordinator.BONDING_STYLE_NONE) {
+                    LOG.info("Aborting unwanted pairing request");
+                    abortBroadcast();
+                }
+            } catch (Exception e) {
+                LOG.warn("Could not abort pairing request process");
+            }
+        } catch (DeviceCommunicationService.DeviceNotFoundException e) {
+            e.printStackTrace();
+        }
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/BluetoothStateChangeReceiver.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/BluetoothStateChangeReceiver.java
new file mode 100644
index 0000000..663cafd
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/BluetoothStateChangeReceiver.java
@@ -0,0 +1,70 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, Carsten Pfeiffer, Daniele
+    Gobbetti, Taavi Eom盲e
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.externalevents;
+
+import android.bluetooth.BluetoothAdapter;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.devices.DeviceManager;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceCommunicationService;
+import nodomain.freeyourgadget.gadgetbridge.util.GBPrefs;
+
+public class BluetoothStateChangeReceiver extends BroadcastReceiver {
+    private static final Logger LOG = LoggerFactory.getLogger(BluetoothStateChangeReceiver.class);
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        final String action = intent.getAction();
+
+        if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(action)) {
+            if (intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1) == BluetoothAdapter.STATE_ON) {
+                final Intent refreshIntent = new Intent(DeviceManager.ACTION_REFRESH_DEVICELIST);
+                LocalBroadcastManager.getInstance(context).sendBroadcast(refreshIntent);
+
+                final GBPrefs prefs = GBApplication.getPrefs();
+                if (!DeviceCommunicationService.isRunning(context) && !prefs.getAutoStart()) {
+                    // Prevent starting the service if it isn't yet running
+                    LOG.debug("DeviceCommunicationService not running, ignoring bluetooth on");
+                    return;
+                }
+
+                if (!prefs.getBoolean("general_autoconnectonbluetooth", false)) {
+                    return;
+                }
+
+                LOG.info("Bluetooth turned on (ACTION_STATE_CHANGED) => connecting...");
+                GBApplication.deviceService().connect();
+            } else if (intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1) == BluetoothAdapter.STATE_OFF) {
+                if (!DeviceCommunicationService.isRunning(context)) {
+                    // Prevent starting the service if it isn't yet running
+                    LOG.debug("DeviceCommunicationService not running, ignoring bluetooth off");
+                    return;
+                }
+                LOG.info("Bluetooth turned off => disconnecting...");
+                GBApplication.deviceService().disconnect();
+            }
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/CMWeatherReceiver.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/CMWeatherReceiver.java
new file mode 100644
index 0000000..7501ab8
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/CMWeatherReceiver.java
@@ -0,0 +1,224 @@
+/*  Copyright (C) 2017-2024 Andreas Shimokawa, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.externalevents;
+
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.List;
+
+import cyanogenmod.weather.CMWeatherManager;
+import cyanogenmod.weather.WeatherInfo;
+import cyanogenmod.weather.WeatherLocation;
+import cyanogenmod.weather.util.WeatherUtils;
+import nodomain.freeyourgadget.gadgetbridge.BuildConfig;
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.model.Weather;
+import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
+import nodomain.freeyourgadget.gadgetbridge.util.PendingIntentUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+import static cyanogenmod.providers.WeatherContract.WeatherColumns.TempUnit.FAHRENHEIT;
+import static cyanogenmod.providers.WeatherContract.WeatherColumns.WeatherCode.ISOLATED_THUNDERSHOWERS;
+import static cyanogenmod.providers.WeatherContract.WeatherColumns.WeatherCode.NOT_AVAILABLE;
+import static cyanogenmod.providers.WeatherContract.WeatherColumns.WeatherCode.SCATTERED_SNOW_SHOWERS;
+import static cyanogenmod.providers.WeatherContract.WeatherColumns.WeatherCode.SCATTERED_THUNDERSTORMS;
+import static cyanogenmod.providers.WeatherContract.WeatherColumns.WeatherCode.SHOWERS;
+import static cyanogenmod.providers.WeatherContract.WeatherColumns.WindSpeedUnit.MPH;
+
+public class CMWeatherReceiver extends BroadcastReceiver implements CMWeatherManager.WeatherUpdateRequestListener, CMWeatherManager.LookupCityRequestListener {
+
+    private static final Logger LOG = LoggerFactory.getLogger(CMWeatherReceiver.class);
+
+    private WeatherLocation weatherLocation = null;
+    private Context mContext;
+    private PendingIntent mPendingIntent = null;
+
+    public CMWeatherReceiver() {
+        mContext = GBApplication.getContext();
+        final CMWeatherManager weatherManager = CMWeatherManager.getInstance(mContext);
+        if (weatherManager == null) {
+            return;
+        }
+
+        Prefs prefs = GBApplication.getPrefs();
+
+        String city = prefs.getString("weather_city", null);
+        String cityId = prefs.getString("weather_cityid", null);
+
+        if ((cityId == null || cityId.equals("")) && city != null && !city.equals("")) {
+            lookupCity(city);
+        } else if (city != null && cityId != null) {
+            weatherLocation = new WeatherLocation.Builder(cityId, city).build();
+            enablePeriodicAlarm(true);
+        }
+    }
+
+    private void lookupCity(String city) {
+        final CMWeatherManager weatherManager = CMWeatherManager.getInstance(mContext);
+        if (weatherManager == null) {
+            return;
+        }
+
+        if (city != null && !city.equals("")) {
+            if (weatherManager.getActiveWeatherServiceProviderLabel() != null) {
+                weatherManager.lookupCity(city, this);
+            }
+        }
+    }
+
+    private void enablePeriodicAlarm(boolean enable) {
+        if ((mPendingIntent != null && enable) || (mPendingIntent == null && !enable)) {
+            return;
+        }
+
+        AlarmManager am = (AlarmManager) (mContext.getSystemService(Context.ALARM_SERVICE));
+        if (am == null) {
+            LOG.warn("could not get alarm manager!");
+            return;
+        }
+
+        if (enable) {
+            Intent intent = new Intent("GB_UPDATE_WEATHER");
+            intent.setPackage(BuildConfig.APPLICATION_ID);
+            mPendingIntent = PendingIntentUtils.getBroadcast(mContext, 0, intent, 0, false);
+            am.setInexactRepeating(AlarmManager.RTC_WAKEUP, Calendar.getInstance().getTimeInMillis() + 10000, AlarmManager.INTERVAL_HOUR, mPendingIntent);
+        } else {
+            am.cancel(mPendingIntent);
+            mPendingIntent = null;
+        }
+    }
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        Prefs prefs = GBApplication.getPrefs();
+
+        String city = prefs.getString("weather_city", null);
+        String cityId = prefs.getString("weather_cityid", null);
+
+        if (city != null && !city.equals("") && cityId == null) {
+            lookupCity(city);
+        } else {
+            requestWeather();
+        }
+    }
+
+    private void requestWeather() {
+        final CMWeatherManager weatherManager = CMWeatherManager.getInstance(GBApplication.getContext());
+        if (weatherManager.getActiveWeatherServiceProviderLabel() != null && weatherLocation != null) {
+            weatherManager.requestWeatherUpdate(weatherLocation, this);
+        }
+    }
+
+    @Override
+    public void onWeatherRequestCompleted(int status, WeatherInfo weatherInfo) {
+        if (weatherInfo != null) {
+            LOG.info("weather: " + weatherInfo.toString());
+            WeatherSpec weatherSpec = new WeatherSpec();
+            weatherSpec.timestamp = (int) (weatherInfo.getTimestamp() / 1000);
+            weatherSpec.location = weatherInfo.getCity();
+
+            if (weatherInfo.getTemperatureUnit() == FAHRENHEIT) {
+                weatherSpec.currentTemp = (int) WeatherUtils.fahrenheitToCelsius(weatherInfo.getTemperature()) + 273;
+                weatherSpec.todayMaxTemp = (int) WeatherUtils.fahrenheitToCelsius(weatherInfo.getTodaysHigh()) + 273;
+                weatherSpec.todayMinTemp = (int) WeatherUtils.fahrenheitToCelsius(weatherInfo.getTodaysLow()) + 273;
+            } else {
+                weatherSpec.currentTemp = (int) weatherInfo.getTemperature() + 273;
+                weatherSpec.todayMaxTemp = (int) weatherInfo.getTodaysHigh() + 273;
+                weatherSpec.todayMinTemp = (int) weatherInfo.getTodaysLow() + 273;
+            }
+            if (weatherInfo.getWindSpeedUnit() == MPH) {
+                weatherSpec.windSpeed = (float) weatherInfo.getWindSpeed() * 1.609344f;
+            } else {
+                weatherSpec.windSpeed = (float) weatherInfo.getWindSpeed();
+            }
+            weatherSpec.windDirection = (int) weatherInfo.getWindDirection();
+
+            weatherSpec.currentConditionCode = Weather.mapToOpenWeatherMapCondition(CMtoYahooCondintion(weatherInfo.getConditionCode()));
+            weatherSpec.currentCondition = Weather.getConditionString(weatherSpec.currentConditionCode);
+            weatherSpec.currentHumidity = (int) weatherInfo.getHumidity();
+
+            weatherSpec.forecasts = new ArrayList<>();
+            List<WeatherInfo.DayForecast> forecasts = weatherInfo.getForecasts();
+            for (int i = 1; i < forecasts.size(); i++) {
+                WeatherInfo.DayForecast cmForecast = forecasts.get(i);
+                WeatherSpec.Daily gbForecast = new WeatherSpec.Daily();
+                if (weatherInfo.getTemperatureUnit() == FAHRENHEIT) {
+                    gbForecast.maxTemp = (int) WeatherUtils.fahrenheitToCelsius(cmForecast.getHigh()) + 273;
+                    gbForecast.minTemp = (int) WeatherUtils.fahrenheitToCelsius(cmForecast.getLow()) + 273;
+                } else {
+                    gbForecast.maxTemp = (int) cmForecast.getHigh() + 273;
+                    gbForecast.minTemp = (int) cmForecast.getLow() + 273;
+                }
+                gbForecast.conditionCode = Weather.mapToOpenWeatherMapCondition(CMtoYahooCondintion(cmForecast.getConditionCode()));
+                weatherSpec.forecasts.add(gbForecast);
+            }
+            ArrayList<WeatherSpec> weatherSpecs = new ArrayList<>(Collections.singletonList(weatherSpec));
+            Weather.getInstance().setWeatherSpec(weatherSpecs);
+            GBApplication.deviceService().onSendWeather(weatherSpecs);
+        } else {
+            LOG.info("request has returned null for WeatherInfo");
+        }
+    }
+
+    /**
+     * @param cmCondition
+     * @return
+     */
+    private int CMtoYahooCondintion(int cmCondition) {
+        int yahooCondition;
+        if (cmCondition <= SHOWERS) {
+            yahooCondition = cmCondition;
+        } else if (cmCondition <= SCATTERED_THUNDERSTORMS) {
+            yahooCondition = cmCondition + 1;
+        } else if (cmCondition <= SCATTERED_SNOW_SHOWERS) {
+            yahooCondition = cmCondition + 2;
+        } else if (cmCondition <= ISOLATED_THUNDERSHOWERS) {
+            yahooCondition = cmCondition + 3;
+        } else {
+            yahooCondition = NOT_AVAILABLE;
+        }
+        return yahooCondition;
+    }
+
+    @Override
+    public void onLookupCityRequestCompleted(int result, List<WeatherLocation> list) {
+        if (list != null) {
+            weatherLocation = list.get(0);
+            String cityId = weatherLocation.getCityId();
+            String city = weatherLocation.getCity();
+
+            SharedPreferences.Editor editor = GBApplication.getPrefs().getPreferences().edit();
+            editor.putString("weather_city", city).apply();
+            editor.putString("weather_cityid", cityId).apply();
+            enablePeriodicAlarm(true);
+            requestWeather();
+        } else {
+            enablePeriodicAlarm(false);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/CalendarReceiver.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/CalendarReceiver.java
new file mode 100644
index 0000000..2e86fef
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/CalendarReceiver.java
@@ -0,0 +1,281 @@
+/*  Copyright (C) 2017-2024 Andreas Shimokawa, Carsten Pfeiffer, Daniele
+    Gobbetti, Daniel Hauck, Gabriele Monaco, Jos茅 Rebelo, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.externalevents;
+
+
+import static androidx.core.content.ContextCompat.RECEIVER_NOT_EXPORTED;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.provider.CalendarContract;
+import android.widget.Toast;
+
+import androidx.core.content.ContextCompat;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.concurrent.TimeUnit;
+import java.util.Hashtable;
+import java.util.List;
+
+import de.greenrobot.dao.query.QueryBuilder;
+import nodomain.freeyourgadget.gadgetbridge.BuildConfig;
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.entities.CalendarSyncState;
+import nodomain.freeyourgadget.gadgetbridge.entities.CalendarSyncStateDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
+import nodomain.freeyourgadget.gadgetbridge.util.calendar.CalendarEvent;
+import nodomain.freeyourgadget.gadgetbridge.util.calendar.CalendarManager;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class CalendarReceiver extends ContentObserver {
+    private static final Logger LOG = LoggerFactory.getLogger(CalendarReceiver.class);
+
+    private static final String ACTION_FORCE_SYNC = "FORCE_CALENDAR_SYNC";
+
+    private final Hashtable<Long, EventSyncState> eventState = new Hashtable<>();
+
+    private final Context mContext;
+    private final GBDevice mGBDevice;
+    private final Handler mSyncHandler;
+    private final BroadcastReceiver mForceSyncReceiver;
+
+    private static class EventSyncState {
+        private int state;
+        private final CalendarEvent event;
+
+        EventSyncState(CalendarEvent event, int state) {
+            this.state = state;
+            this.event = event;
+        }
+
+        public int getState() {
+            return state;
+        }
+
+        public void setState(int state) {
+            this.state = state;
+        }
+
+        public CalendarEvent getEvent() {
+            return event;
+        }
+    }
+
+    private static class EventState {
+        private static final int NOT_SYNCED = 0;
+        private static final int SYNCED = 1;
+        private static final int NEEDS_UPDATE = 2;
+        private static final int NEEDS_DELETE = 3;
+    }
+
+    public CalendarReceiver(final Context context, final GBDevice gbDevice) {
+        super(new Handler());
+        LOG.info("Created calendar receiver");
+        mContext = context;
+        mGBDevice = gbDevice;
+        mSyncHandler = new Handler();
+        mForceSyncReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(final Context context, final Intent intent) {
+                LOG.info("Got force sync: {}", intent.getAction());
+                scheduleSync();
+            }
+        };
+    }
+
+    public GBDevice getGBDevice() {
+        return mGBDevice;
+    }
+
+    @Override
+    public void onChange(boolean selfChange, Uri uri) {
+        super.onChange(selfChange, uri);
+        LOG.info("Got calendar change: {}", uri);
+        scheduleSync();
+    }
+
+    public void scheduleSync() {
+        LOG.debug("Scheduling calendar sync");
+        mSyncHandler.removeCallbacksAndMessages(null);
+        mSyncHandler.postDelayed(this::syncCalendar, 2500L);
+    }
+
+    public void syncCalendar() {
+        List<CalendarEvent> eventList = (new CalendarManager(mContext, mGBDevice.getAddress())).getCalendarEventList();
+        LOG.debug("Syncing {} calendar events", eventList.size());
+        syncCalendar(eventList);
+    }
+
+    public void syncCalendar(List<CalendarEvent> eventList) {
+        try (DBHandler dbHandler = GBApplication.acquireDB()) {
+            DaoSession session = dbHandler.getDaoSession();
+            syncCalendar(eventList, session);
+        } catch (Exception e1) {
+            GB.toast("Database Error while syncing Calendar", Toast.LENGTH_SHORT, GB.ERROR, e1);
+        }
+    }
+
+    public void syncCalendar(List<CalendarEvent> eventList, DaoSession session) {
+        LOG.info("Syncing with calendar.");
+        Hashtable<Long, CalendarEvent> eventTable = new Hashtable<>();
+        Long deviceId = DBHelper.getDevice(mGBDevice, session).getId();
+        QueryBuilder<CalendarSyncState> qb = session.getCalendarSyncStateDao().queryBuilder();
+
+        for (CalendarEvent e : eventList) {
+            long id = e.getId();
+            eventTable.put(id, e);
+            if (!eventState.containsKey(e.getId())) {
+                qb = session.getCalendarSyncStateDao().queryBuilder();
+
+                CalendarSyncState calendarSyncState = qb.where(qb.and(CalendarSyncStateDao.Properties.DeviceId.eq(deviceId), CalendarSyncStateDao.Properties.CalendarEntryId.eq(id)))
+                        .build().unique();
+                if (calendarSyncState == null) {
+                    eventState.put(id, new EventSyncState(e, EventState.NOT_SYNCED));
+                    LOG.info("event id={} is yet unknown to device id={}", id, deviceId);
+                } else if (calendarSyncState.getHash() == e.hashCode()) {
+                    eventState.put(id, new EventSyncState(e, EventState.SYNCED));
+                    LOG.info("event id={} is up to date on device id={}", id, deviceId);
+                } else {
+                    eventState.put(id, new EventSyncState(e, EventState.NEEDS_UPDATE));
+                    LOG.info("event id={} is not up to date on device id={}", id, deviceId);
+                }
+            }
+        }
+
+        // add all missing calendar ids on the device to sync status (so that they are deleted later)
+        List<CalendarSyncState> CalendarSyncStateList = qb.where(CalendarSyncStateDao.Properties.DeviceId.eq(deviceId)).build().list();
+        for (CalendarSyncState CalendarSyncState : CalendarSyncStateList) {
+            if (!eventState.containsKey(CalendarSyncState.getCalendarEntryId())) {
+                eventState.put(CalendarSyncState.getCalendarEntryId(), new EventSyncState(null, EventState.NEEDS_DELETE));
+                LOG.info("insert null event for orphaned calendar id={} for device={}", CalendarSyncState.getCalendarEntryId(), mGBDevice.getName());
+            }
+        }
+
+        Enumeration<Long> ids = eventState.keys();
+        while (ids.hasMoreElements()) {
+            qb = session.getCalendarSyncStateDao().queryBuilder();
+            Long i = ids.nextElement();
+            EventSyncState es = eventState.get(i);
+            if (es == null) {
+                LOG.error("Failed to get event state for {}", i);
+                continue;
+            }
+            if (eventTable.containsKey(i)) {
+                if (es.getState() == EventState.SYNCED) {
+                    if (!es.getEvent().equals(eventTable.get(i))) {
+                        eventState.put(i, new EventSyncState(eventTable.get(i), EventState.NEEDS_UPDATE));
+                    }
+                }
+            } else {
+                if (es.getState() == EventState.NOT_SYNCED) {
+                    // delete for current device only
+                    qb.where(qb.and(CalendarSyncStateDao.Properties.DeviceId.eq(deviceId), CalendarSyncStateDao.Properties.CalendarEntryId.eq(i)))
+                            .buildDelete().executeDeleteWithoutDetachingEntities();
+                    eventState.remove(i);
+                } else {
+                    es.setState(EventState.NEEDS_DELETE);
+                    eventState.put(i, es);
+                }
+            }
+            updateEvents(deviceId, session);
+        }
+    }
+
+    private void updateEvents(Long deviceId, DaoSession session) {
+        Enumeration<Long> ids = eventState.keys();
+        while (ids.hasMoreElements()) {
+            Long i = ids.nextElement();
+            EventSyncState es = eventState.get(i);
+            if (es == null) {
+                LOG.error("Failed to get event state {} for sync", i);
+                continue;
+            }
+            int syncState = es.getState();
+            if (syncState == EventState.NOT_SYNCED || syncState == EventState.NEEDS_UPDATE) {
+                CalendarEvent calendarEvent = es.getEvent();
+                CalendarEventSpec calendarEventSpec = new CalendarEventSpec();
+                calendarEventSpec.id = i;
+                calendarEventSpec.title = calendarEvent.getTitle();
+                calendarEventSpec.allDay = calendarEvent.isAllDay();
+                calendarEventSpec.reminders = new ArrayList<>(calendarEvent.getRemindersAbsoluteTs());
+                calendarEventSpec.timestamp = calendarEvent.getBeginSeconds();
+                calendarEventSpec.durationInSeconds = calendarEvent.getDurationSeconds(); //FIXME: leads to problems right now
+                if (calendarEvent.isAllDay()) {
+                    // As per the CalendarContract, for all-day events, the start timestamp is always in UTC
+                    // and corresponds to the midnight boundary
+                    final int numDays = (int) TimeUnit.DAYS.convert(
+                            calendarEvent.getEnd() - calendarEvent.getBegin(),
+                            TimeUnit.MILLISECONDS
+                    );
+                    calendarEventSpec.durationInSeconds = 24 * 60 * 60 * numDays;
+                }
+                calendarEventSpec.description = calendarEvent.getDescription();
+                calendarEventSpec.location = calendarEvent.getLocation();
+                calendarEventSpec.type = CalendarEventSpec.TYPE_UNKNOWN;
+                calendarEventSpec.calName = calendarEvent.getUniqueCalName();
+                calendarEventSpec.color = calendarEvent.getColor();
+                if (syncState == EventState.NEEDS_UPDATE) {
+                    GBApplication.deviceService(mGBDevice).onDeleteCalendarEvent(CalendarEventSpec.TYPE_UNKNOWN, i);
+                }
+                GBApplication.deviceService(mGBDevice).onAddCalendarEvent(calendarEventSpec);
+                es.setState(EventState.SYNCED);
+                eventState.put(i, es);
+                // update db
+                session.insertOrReplace(new CalendarSyncState(null, deviceId, i, es.event.hashCode()));
+            } else if (syncState == EventState.NEEDS_DELETE) {
+                GBApplication.deviceService(mGBDevice).onDeleteCalendarEvent(CalendarEventSpec.TYPE_UNKNOWN, i);
+                eventState.remove(i);
+                // delete from db for current device only
+                QueryBuilder<CalendarSyncState> qb = session.getCalendarSyncStateDao().queryBuilder();
+                qb.where(qb.and(CalendarSyncStateDao.Properties.DeviceId.eq(deviceId), CalendarSyncStateDao.Properties.CalendarEntryId.eq(i)))
+                        .buildDelete().executeDeleteWithoutDetachingEntities();
+            }
+        }
+    }
+
+    public void registerBroadcastReceivers() {
+        mContext.getContentResolver().registerContentObserver(CalendarContract.Events.CONTENT_URI, true, this);
+        // Add a receiver to allow us to quickly force as calendar sync (without having to provide data)
+        ContextCompat.registerReceiver(mContext, mForceSyncReceiver, new IntentFilter(ACTION_FORCE_SYNC), RECEIVER_NOT_EXPORTED);
+    }
+
+    public void dispose() {
+        mContext.getContentResolver().unregisterContentObserver(this);
+        mContext.unregisterReceiver(mForceSyncReceiver);
+        mSyncHandler.removeCallbacksAndMessages(null);
+    }
+
+    public static void forceSync() {
+        final Intent intent = new Intent(ACTION_FORCE_SYNC);
+        intent.setPackage(BuildConfig.APPLICATION_ID);
+        GBApplication.getContext().sendBroadcast(intent);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/DeviceSettingsReceiver.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/DeviceSettingsReceiver.java
new file mode 100644
index 0000000..682bb8f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/DeviceSettingsReceiver.java
@@ -0,0 +1,110 @@
+/*  Copyright (C) 2022-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.externalevents;
+
+import android.annotation.SuppressLint;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Set;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class DeviceSettingsReceiver extends BroadcastReceiver {
+    private static final Logger LOG = LoggerFactory.getLogger(DeviceSettingsReceiver.class);
+
+    public static final String COMMAND = "nodomain.freeyourgadget.gadgetbridge.action.SET_DEVICE_SETTING";
+
+    private static final String MAC_ADDR_PATTERN = "^([0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}$";
+
+    @Override
+    @SuppressLint("ApplySharedPref")
+    // use commit to ensure it's already applied when we call the device
+    public void onReceive(final Context context, final Intent intent) {
+        if (!COMMAND.equals(intent.getAction())) {
+            LOG.warn("Unexpected action {}", intent.getAction());
+        }
+
+        final String deviceAddress = intent.getStringExtra("device");
+
+        if (deviceAddress == null) {
+            LOG.warn("Missing device address");
+            return;
+        }
+
+        if (!deviceAddress.matches(MAC_ADDR_PATTERN)) {
+            LOG.warn("Device address '{}' does not match '{}'", deviceAddress, MAC_ADDR_PATTERN);
+            return;
+        }
+
+        final GBDevice targetDevice = GBApplication.app()
+                .getDeviceManager()
+                .getDeviceByAddress(deviceAddress);
+
+        if (targetDevice == null) {
+            LOG.warn("Unknown device {}", deviceAddress);
+            return;
+        }
+
+        final SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(targetDevice.getAddress());
+
+        if (!prefs.getBoolean("third_party_apps_set_settings", false)) {
+            LOG.warn("Setting device settings from 3rd party apps not allowed for {}", deviceAddress);
+            return;
+        }
+
+        final String key = intent.getStringExtra("key");
+        final Object value = intent.getExtras().get("value");
+
+        if (key == null) {
+            LOG.warn("No key specified");
+            return;
+        }
+
+        LOG.info("Setting '{}' to '{}'", key, value);
+
+        final SharedPreferences.Editor editor = prefs.edit();
+
+        if (value == null) {
+            editor.remove(key);
+        } else if (value instanceof Integer) {
+            editor.putInt(key, (Integer) value);
+        } else if (value instanceof Boolean) {
+            editor.putBoolean(key, (Boolean) value);
+        } else if (value instanceof String) {
+            editor.putString(key, (String) value);
+        } else if (value instanceof Float) {
+            editor.putFloat(key, (Float) value);
+        } else if (value instanceof Long) {
+            editor.putLong(key, (Long) value);
+        } else if (value instanceof Set) {
+            editor.putStringSet(key, (Set) value);
+        } else {
+            LOG.warn("Unknown preference value type {} for {}", value.getClass(), key);
+        }
+
+        editor.commit();
+
+        GBApplication.deviceService().onSendConfiguration(key);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/GenericWeatherReceiver.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/GenericWeatherReceiver.java
new file mode 100644
index 0000000..ee52677
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/GenericWeatherReceiver.java
@@ -0,0 +1,216 @@
+/*  Copyright (C) 2022-2024 Daniele Gobbetti, Enrico Brambilla, Jos茅 Rebelo,
+    TylerWilliamson
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+
+package nodomain.freeyourgadget.gadgetbridge.externalevents;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.widget.Toast;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Objects;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.model.Weather;
+import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class GenericWeatherReceiver extends BroadcastReceiver {
+    private static final Logger LOG = LoggerFactory.getLogger(GenericWeatherReceiver.class);
+
+    public final static String ACTION_GENERIC_WEATHER = "nodomain.freeyourgadget.gadgetbridge.ACTION_GENERIC_WEATHER";
+    public final static String EXTRA_WEATHER_JSON = "WeatherJson";
+    public final static String EXTRA_WEATHER_SECONDARY_JSON = "WeatherSecondaryJson";
+
+    @Override
+    public void onReceive(final Context context, final Intent intent) {
+        if (intent == null) {
+            LOG.warn("Intent is null");
+            return;
+        }
+
+        if (!ACTION_GENERIC_WEATHER.equals(intent.getAction())) {
+            LOG.warn("Unknown action {}", intent.getAction());
+            return;
+        }
+
+        final Bundle bundle = intent.getExtras();
+        if (bundle == null) {
+            LOG.warn("Intent has no extras");
+            return;
+        }
+
+        if (!bundle.containsKey(EXTRA_WEATHER_JSON)) {
+            LOG.warn("Bundle key {} not found", EXTRA_WEATHER_JSON);
+            return;
+        }
+
+        try {
+            final JSONObject primaryWeatherJson = new JSONObject(Objects.requireNonNull(bundle.getString(EXTRA_WEATHER_JSON)));
+            final WeatherSpec primaryWeather = weatherFromJson(primaryWeatherJson);
+
+            final ArrayList<WeatherSpec> weathers = new ArrayList<>();
+            weathers.add(primaryWeather);
+
+            if (bundle.containsKey(EXTRA_WEATHER_SECONDARY_JSON)) {
+                final JSONArray secondaryWeatherJson = new JSONArray(bundle.getString(EXTRA_WEATHER_SECONDARY_JSON, "[]"));
+
+                for (int i = 0; i < secondaryWeatherJson.length(); i++) {
+                    weathers.add(weatherFromJson(secondaryWeatherJson.getJSONObject(i)));
+                }
+            }
+
+            LOG.info("Got generic weather for {} locations", weathers.size());
+            Weather.getInstance().setWeatherSpec(weathers);
+            GBApplication.deviceService().onSendWeather(weathers);
+        } catch (final Exception e) {
+            GB.toast("Gadgetbridge received broken or incompatible weather data", Toast.LENGTH_SHORT, GB.ERROR, e);
+        }
+    }
+
+    private WeatherSpec weatherFromJson(final JSONObject weatherJson) throws JSONException {
+        final WeatherSpec weatherSpec = new WeatherSpec();
+
+        weatherSpec.timestamp = safelyGet(weatherJson, Integer.class, "timestamp", (int) (System.currentTimeMillis() / 1000));
+        weatherSpec.location = safelyGet(weatherJson, String.class, "location", "");
+        weatherSpec.currentTemp = safelyGet(weatherJson, Integer.class, "currentTemp", 0);
+        weatherSpec.todayMinTemp = safelyGet(weatherJson, Integer.class, "todayMinTemp", 0);
+        weatherSpec.todayMaxTemp = safelyGet(weatherJson, Integer.class, "todayMaxTemp", 0);
+        weatherSpec.currentCondition = safelyGet(weatherJson, String.class, "currentCondition", "");
+        weatherSpec.currentConditionCode = safelyGet(weatherJson, Integer.class, "currentConditionCode", 0);
+        weatherSpec.currentHumidity = safelyGet(weatherJson, Integer.class, "currentHumidity", 0);
+        weatherSpec.windSpeed = safelyGet(weatherJson, Number.class, "windSpeed", 0d).floatValue();
+        weatherSpec.windDirection = safelyGet(weatherJson, Integer.class, "windDirection", 0);
+        weatherSpec.uvIndex = safelyGet(weatherJson, Number.class, "uvIndex", 0d).floatValue();
+        weatherSpec.precipProbability = safelyGet(weatherJson, Integer.class, "precipProbability", 0);
+        weatherSpec.dewPoint = safelyGet(weatherJson, Integer.class, "dewPoint", 0);
+        weatherSpec.pressure = safelyGet(weatherJson, Number.class, "pressure", 0).floatValue();
+        weatherSpec.cloudCover = safelyGet(weatherJson, Integer.class, "cloudCover", 0);
+        weatherSpec.visibility = safelyGet(weatherJson, Number.class, "visibility", 0).floatValue();
+        weatherSpec.sunRise = safelyGet(weatherJson, Integer.class, "sunRise", 0);
+        weatherSpec.sunSet = safelyGet(weatherJson, Integer.class, "sunSet", 0);
+        weatherSpec.moonRise = safelyGet(weatherJson, Integer.class, "moonRise", 0);
+        weatherSpec.moonSet = safelyGet(weatherJson, Integer.class, "moonSet", 0);
+        weatherSpec.moonPhase = safelyGet(weatherJson, Integer.class, "moonPhase", 0);
+        weatherSpec.latitude = safelyGet(weatherJson, Number.class, "latitude", 0).floatValue();
+        weatherSpec.longitude = safelyGet(weatherJson, Number.class, "longitude", 0).floatValue();
+        weatherSpec.feelsLikeTemp = safelyGet(weatherJson, Integer.class, "feelsLikeTemp", 0);
+        weatherSpec.isCurrentLocation = safelyGet(weatherJson, Integer.class, "isCurrentLocation", -1);
+
+        if (weatherJson.has("airQuality")) {
+            weatherSpec.airQuality = toAirQuality(weatherJson.getJSONObject("airQuality"));
+        }
+
+        if (weatherJson.has("forecasts")) {
+            final JSONArray forecastArray = weatherJson.getJSONArray("forecasts");
+            weatherSpec.forecasts = new ArrayList<>();
+
+            for (int i = 0, l = forecastArray.length(); i < l; i++) {
+                final JSONObject forecastJson = forecastArray.getJSONObject(i);
+
+                final WeatherSpec.Daily forecast = new WeatherSpec.Daily();
+
+                forecast.conditionCode = safelyGet(forecastJson, Integer.class, "conditionCode", 0);
+                forecast.humidity = safelyGet(forecastJson, Integer.class, "humidity", 0);
+                forecast.maxTemp = safelyGet(forecastJson, Integer.class, "maxTemp", 0);
+                forecast.minTemp = safelyGet(forecastJson, Integer.class, "minTemp", 0);
+                forecast.windSpeed = safelyGet(forecastJson, Number.class, "windSpeed", 0).floatValue();
+                forecast.windDirection = safelyGet(forecastJson, Integer.class, "windDirection", 0);
+                forecast.uvIndex = safelyGet(forecastJson, Number.class, "uvIndex", 0d).floatValue();
+                forecast.precipProbability = safelyGet(forecastJson, Integer.class, "precipProbability", 0);
+                forecast.sunRise = safelyGet(forecastJson, Integer.class, "sunRise", 0);
+                forecast.sunSet = safelyGet(forecastJson, Integer.class, "sunSet", 0);
+                forecast.moonRise = safelyGet(forecastJson, Integer.class, "moonRise", 0);
+                forecast.moonSet = safelyGet(forecastJson, Integer.class, "moonSet", 0);
+                forecast.moonPhase = safelyGet(forecastJson, Integer.class, "moonPhase", 0);
+
+                if (forecastJson.has("airQuality")) {
+                    forecast.airQuality = toAirQuality(forecastJson.getJSONObject("airQuality"));
+                }
+
+                weatherSpec.forecasts.add(forecast);
+            }
+        }
+
+        if (weatherJson.has("hourly")) {
+            final JSONArray forecastArray = weatherJson.getJSONArray("hourly");
+            weatherSpec.hourly = new ArrayList<>();
+
+            for (int i = 0, l = forecastArray.length(); i < l; i++) {
+                final JSONObject forecastJson = forecastArray.getJSONObject(i);
+
+                final WeatherSpec.Hourly forecast = new WeatherSpec.Hourly();
+
+                forecast.timestamp = safelyGet(forecastJson, Integer.class, "timestamp", 0);
+                forecast.temp = safelyGet(forecastJson, Integer.class, "temp", 0);
+                forecast.conditionCode = safelyGet(forecastJson, Integer.class, "conditionCode", 0);
+                forecast.humidity = safelyGet(forecastJson, Integer.class, "humidity", 0);
+                forecast.windSpeed = safelyGet(forecastJson, Number.class, "windSpeed", 0).floatValue();
+                forecast.windDirection = safelyGet(forecastJson, Integer.class, "windDirection", 0);
+                forecast.uvIndex = safelyGet(forecastJson, Number.class, "uvIndex", 0d).floatValue();
+                forecast.precipProbability = safelyGet(forecastJson, Integer.class, "precipProbability", 0);
+
+                weatherSpec.hourly.add(forecast);
+            }
+        }
+
+        return weatherSpec;
+    }
+
+    private WeatherSpec.AirQuality toAirQuality(final JSONObject jsonObject) {
+        final WeatherSpec.AirQuality airQuality = new WeatherSpec.AirQuality();
+        airQuality.aqi = safelyGet(jsonObject, Integer.class, "aqi", -1);
+        airQuality.co = safelyGet(jsonObject, Number.class, "co", -1).floatValue();
+        airQuality.no2 = safelyGet(jsonObject, Number.class, "no2", -1).floatValue();
+        airQuality.o3 = safelyGet(jsonObject, Number.class, "o3", -1).floatValue();
+        airQuality.pm10 = safelyGet(jsonObject, Number.class, "pm10", -1).floatValue();
+        airQuality.pm25 = safelyGet(jsonObject, Number.class, "pm25", -1).floatValue();
+        airQuality.so2 = safelyGet(jsonObject, Number.class, "so2", -1).floatValue();
+        airQuality.coAqi = safelyGet(jsonObject, Integer.class, "coAqi", -1);
+        airQuality.no2Aqi = safelyGet(jsonObject, Integer.class, "no2Aqi", -1);
+        airQuality.o3Aqi = safelyGet(jsonObject, Integer.class, "o3Aqi", -1);
+        airQuality.pm10Aqi = safelyGet(jsonObject, Integer.class, "pm10Aqi", -1);
+        airQuality.pm25Aqi = safelyGet(jsonObject, Integer.class, "pm25Aqi", -1);
+        airQuality.so2Aqi = safelyGet(jsonObject, Integer.class, "so2Aqi", -1);
+
+        return airQuality;
+    }
+
+    private <T> T safelyGet(JSONObject jsonObject, Class<T> tClass, String name, T defaultValue) {
+        try {
+            if (jsonObject.has(name)) {
+                Object value = jsonObject.get(name);
+
+                if (tClass.isInstance(value)) {
+                    return (T) value;
+                }
+            }
+        } catch (Exception e) {
+            //
+        }
+        return defaultValue;
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/IntentApiReceiver.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/IntentApiReceiver.java
new file mode 100644
index 0000000..4b62949
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/IntentApiReceiver.java
@@ -0,0 +1,302 @@
+/*  Copyright (C) 2022-2024 Jos茅 Rebelo, octospacc
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.externalevents;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.SharedPreferences;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.BuildConfig;
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.database.PeriodicExporter;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.NotificationType;
+import nodomain.freeyourgadget.gadgetbridge.model.RecordedDataTypes;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+public class IntentApiReceiver extends BroadcastReceiver {
+    private static final Logger LOG = LoggerFactory.getLogger(IntentApiReceiver.class);
+
+    private static final String msgDebugNotAllowed = "Intent API Allow Debug Commands not allowed";
+
+    public static final String COMMAND_ACTIVITY_SYNC = "nodomain.freeyourgadget.gadgetbridge.command.ACTIVITY_SYNC";
+    public static final String COMMAND_TRIGGER_EXPORT = "nodomain.freeyourgadget.gadgetbridge.command.TRIGGER_EXPORT";
+    public static final String COMMAND_DEBUG_SEND_NOTIFICATION = "nodomain.freeyourgadget.gadgetbridge.command.DEBUG_SEND_NOTIFICATION";
+    public static final String COMMAND_DEBUG_INCOMING_CALL = "nodomain.freeyourgadget.gadgetbridge.command.DEBUG_INCOMING_CALL";
+    public static final String COMMAND_DEBUG_END_CALL = "nodomain.freeyourgadget.gadgetbridge.command.DEBUG_END_CALL";
+    public static final String COMMAND_DEBUG_SET_DEVICE_ADDRESS = "nodomain.freeyourgadget.gadgetbridge.command.DEBUG_SET_DEVICE_ADDRESS";
+    public static final String COMMAND_DEBUG_TEST_NEW_FUNCTION = "nodomain.freeyourgadget.gadgetbridge.command.DEBUG_TEST_NEW_FUNCTION";
+
+    private static final String MAC_ADDR_PATTERN = "^([0-9A-F]{2}:){5}[0-9A-F]{2}$";
+
+    @Override
+    public void onReceive(final Context context, final Intent intent) {
+        if (intent.getAction() == null) {
+            LOG.warn("Action is null");
+            return;
+        }
+
+        final Prefs prefs = GBApplication.getPrefs();
+
+        switch (intent.getAction()) {
+            case COMMAND_ACTIVITY_SYNC:
+                if (!prefs.getBoolean("intent_api_allow_activity_sync", false)) {
+                    LOG.warn("Intent API activity sync trigger not allowed");
+                    return;
+                }
+
+                final int dataTypes;
+                final String dataTypesHex = intent.getStringExtra("dataTypesHex");
+                if (dataTypesHex != null) {
+                    final Matcher matcher = Pattern.compile("^0[xX]([0-9a-fA-F]+)$").matcher(dataTypesHex);
+                    if (!matcher.find()) {
+                        LOG.warn("Failed to parse dataTypesHex '{}' as hex", dataTypesHex);
+                        return;
+                    }
+                    dataTypes = Integer.parseInt(matcher.group(1), 16);
+                } else {
+                    dataTypes = RecordedDataTypes.TYPE_SYNC;
+                }
+
+                LOG.info("Triggering activity sync for data types {}", String.format("0x%08x", dataTypes));
+
+                GBApplication.deviceService().onFetchRecordedData(dataTypes);
+                break;
+
+            case COMMAND_TRIGGER_EXPORT:
+                if (!prefs.getBoolean("intent_api_allow_trigger_export", false)) {
+                    LOG.warn("Intent API export trigger not allowed");
+                    return;
+                }
+
+                LOG.info("Triggering export");
+
+                final Intent exportIntent = new Intent(context, PeriodicExporter.class);
+                context.sendBroadcast(exportIntent);
+                break;
+
+            case COMMAND_DEBUG_SEND_NOTIFICATION:
+                if (!prefs.getBoolean("intent_api_allow_debug_commands", false)) {
+                    LOG.warn(msgDebugNotAllowed);
+                    return;
+                }
+                LOG.info("Triggering Debug Send notification message");
+                NotificationSpec notificationSpec = new NotificationSpec();
+                notificationSpec.sender = intent.getStringExtra("sender");
+                if (notificationSpec.sender == null) {
+                    notificationSpec.sender = "DEBUG Sender";
+                }
+                notificationSpec.phoneNumber = intent.getStringExtra("phoneNumber");
+                if (notificationSpec.phoneNumber == null) {
+                    notificationSpec.phoneNumber = "DEBUG PhoneNumber";
+                }
+                notificationSpec.subject = intent.getStringExtra("subject");
+                if (notificationSpec.subject == null) {
+                    notificationSpec.subject = "DEBUG Subject";
+                }
+                notificationSpec.body = intent.getStringExtra("body");
+                if (notificationSpec.body == null) {
+                    notificationSpec.body = "DEBUG Body";
+                }
+                notificationSpec.type = NotificationType.GENERIC_SMS;
+                if (intent.getStringExtra("type") != null) {
+                    try {
+                        notificationSpec.type = NotificationType.valueOf(intent.getStringExtra("type"));
+                    } catch (IllegalArgumentException e) {
+                    }
+                }
+                if (notificationSpec.type != NotificationType.GENERIC_SMS) {
+                    // SMS notifications don't have a source app ID when sent by the SMSReceiver,
+                    // so let's not set it here as well for consistency
+                    notificationSpec.sourceAppId = BuildConfig.APPLICATION_ID;
+                }
+                notificationSpec.sourceName = context.getApplicationInfo()
+                        .loadLabel(context.getPackageManager())
+                        .toString();
+                notificationSpec.pebbleColor = notificationSpec.type.color;
+                notificationSpec.attachedActions = new ArrayList<>();
+                if (notificationSpec.type == NotificationType.GENERIC_SMS) {
+                    // REPLY action
+                    NotificationSpec.Action replyAction = new NotificationSpec.Action();
+                    replyAction.title = "Reply";
+                    replyAction.type = NotificationSpec.Action.TYPE_SYNTECTIC_REPLY_PHONENR;
+                    notificationSpec.attachedActions.add(replyAction);
+                }
+                GBApplication.deviceService().onNotification(notificationSpec);
+                break;
+
+            case COMMAND_DEBUG_INCOMING_CALL:
+                if (!prefs.getBoolean("intent_api_allow_debug_commands", false)) {
+                    LOG.warn(msgDebugNotAllowed);
+                    return;
+                }
+                LOG.info("Triggering Debug Incoming Call");
+                CallSpec callSpec = new CallSpec();
+                callSpec.command = CallSpec.CALL_INCOMING;
+                callSpec.number = intent.getStringExtra("caller");
+                if (callSpec.number == null) {
+                    callSpec.number = "DEBUG_INCOMING_CALL";
+                }
+                GBApplication.deviceService().onSetCallState(callSpec);
+                break;
+
+            case COMMAND_DEBUG_END_CALL:
+                if (!prefs.getBoolean("intent_api_allow_debug_commands", false)) {
+                    LOG.warn(msgDebugNotAllowed);
+                    return;
+                }
+                LOG.info("Triggering Debug End Call");
+                CallSpec callSpecEnd = new CallSpec();
+                callSpecEnd.command = CallSpec.CALL_END;
+                GBApplication.deviceService().onSetCallState(callSpecEnd);
+                break;
+
+            case COMMAND_DEBUG_SET_DEVICE_ADDRESS:
+                if (!prefs.getBoolean("intent_api_allow_debug_commands", false)) {
+                    LOG.warn(msgDebugNotAllowed);
+                    return;
+                }
+                setDeviceAddress(intent);
+                break;
+
+            case COMMAND_DEBUG_TEST_NEW_FUNCTION:
+                if (!prefs.getBoolean("intent_api_allow_debug_commands", false)) {
+                    LOG.warn(msgDebugNotAllowed);
+                    return;
+                }
+                LOG.info("Triggering Debug Test New Function");
+                GBApplication.deviceService().onTestNewFunction();
+                break;
+        }
+    }
+
+    public IntentFilter buildFilter() {
+        final IntentFilter intentFilter = new IntentFilter();
+        intentFilter.addAction(COMMAND_ACTIVITY_SYNC);
+        intentFilter.addAction(COMMAND_TRIGGER_EXPORT);
+        intentFilter.addAction(COMMAND_DEBUG_SEND_NOTIFICATION);
+        intentFilter.addAction(COMMAND_DEBUG_INCOMING_CALL);
+        intentFilter.addAction(COMMAND_DEBUG_END_CALL);
+        intentFilter.addAction(COMMAND_DEBUG_SET_DEVICE_ADDRESS);
+        intentFilter.addAction(COMMAND_DEBUG_TEST_NEW_FUNCTION);
+        return intentFilter;
+    }
+
+    private void setDeviceAddress(final Intent intent) {
+        final String oldAddress = intent.getStringExtra("oldAddress");
+        if (!validAddress(oldAddress)) {
+            return;
+        }
+
+        final String newAddress = intent.getStringExtra("newAddress");
+        if (!validAddress(newAddress)) {
+            return;
+        }
+
+        if (oldAddress.equals(newAddress)) {
+            LOG.warn("Old and new addresses are the same");
+            return;
+        }
+
+        final GBDevice oldDevice = GBApplication.app()
+                .getDeviceManager()
+                .getDeviceByAddress(oldAddress);
+        if (oldDevice == null) {
+            LOG.warn("Old device with address {} not found", oldAddress);
+            return;
+        }
+
+        final GBDevice newDevice = GBApplication.app()
+                .getDeviceManager()
+                .getDeviceByAddress(newAddress);
+        if (newDevice != null) {
+            LOG.warn("New device address {} already exists", newAddress);
+            return;
+        }
+
+        LOG.info("Updating device address from {} to {}", oldAddress, newAddress);
+
+        final SharedPreferences settingsOld = GBApplication.getDeviceSpecificSharedPrefs(oldAddress);
+        final SharedPreferences settingsNew = GBApplication.getDeviceSpecificSharedPrefs(newAddress);
+        final SharedPreferences.Editor editorNew = settingsNew.edit().clear();
+        final Map<String, ?> allSettings = settingsOld.getAll();
+        LOG.debug("Copying {} preferences to new device", allSettings.size());
+        for (final Map.Entry<String, ?> e : allSettings.entrySet()) {
+            if (e.getValue().getClass().equals(Boolean.class)) {
+                editorNew.putBoolean(e.getKey(), (Boolean) e.getValue());
+            } else if (e.getValue().getClass().equals(Float.class)) {
+                editorNew.putFloat(e.getKey(), (Float) e.getValue());
+            } else if (e.getValue().getClass().equals(Integer.class)) {
+                editorNew.putInt(e.getKey(), (Integer) e.getValue());
+            } else if (e.getValue().getClass().equals(Long.class)) {
+                editorNew.putLong(e.getKey(), (Long) e.getValue());
+            } else if (e.getValue().getClass().equals(String.class)) {
+                editorNew.putString(e.getKey(), (String) e.getValue());
+            } else if (e.getValue().getClass().equals(HashSet.class)) {
+                editorNew.putStringSet(e.getKey(), (HashSet<String>) e.getValue());
+            } else {
+                LOG.error("Unexpected preference type {}", e.getValue().getClass());
+                return;
+            }
+        }
+        if (!editorNew.commit()) {
+            LOG.error("Failed to persist preferences for new address");
+            return;
+        }
+
+        try (DBHandler dbHandler = GBApplication.acquireDB()) {
+            final DaoSession session = dbHandler.getDaoSession();
+            DBHelper.updateDeviceMacAddress(session, oldAddress, newAddress);
+        } catch (final Exception e) {
+            LOG.error("Failed to update device address", e);
+            return;
+        }
+
+        LOG.info("Quitting GB after device address update");
+
+        GBApplication.quit();
+    }
+
+    private boolean validAddress(final String address) {
+        if (address == null) {
+            return false;
+        }
+
+        if (!address.matches(MAC_ADDR_PATTERN)) {
+            LOG.warn("Device address '{}' does not match '{}'", address, MAC_ADDR_PATTERN);
+            return false;
+        }
+
+        return true;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/LineageOsWeatherReceiver.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/LineageOsWeatherReceiver.java
new file mode 100644
index 0000000..4da0da7
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/LineageOsWeatherReceiver.java
@@ -0,0 +1,229 @@
+/*  Copyright (C) 2019-2024 Andreas Shimokawa, Jos茅 Rebelo, keeshii,
+    Taavi Eom盲e
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.externalevents;
+
+import static lineageos.providers.WeatherContract.WeatherColumns.TempUnit.FAHRENHEIT;
+import static lineageos.providers.WeatherContract.WeatherColumns.WeatherCode.ISOLATED_THUNDERSHOWERS;
+import static lineageos.providers.WeatherContract.WeatherColumns.WeatherCode.NOT_AVAILABLE;
+import static lineageos.providers.WeatherContract.WeatherColumns.WeatherCode.SCATTERED_SNOW_SHOWERS;
+import static lineageos.providers.WeatherContract.WeatherColumns.WeatherCode.SCATTERED_THUNDERSTORMS;
+import static lineageos.providers.WeatherContract.WeatherColumns.WeatherCode.SHOWERS;
+import static lineageos.providers.WeatherContract.WeatherColumns.WindSpeedUnit.MPH;
+
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.os.Build;
+
+import androidx.annotation.RequiresApi;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.List;
+
+import lineageos.weather.LineageWeatherManager;
+import lineageos.weather.WeatherInfo;
+import lineageos.weather.WeatherLocation;
+import lineageos.weather.util.WeatherUtils;
+import nodomain.freeyourgadget.gadgetbridge.BuildConfig;
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.model.Weather;
+import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
+import nodomain.freeyourgadget.gadgetbridge.util.PendingIntentUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+@RequiresApi(api = Build.VERSION_CODES.M)
+public class LineageOsWeatherReceiver extends BroadcastReceiver implements LineageWeatherManager.WeatherUpdateRequestListener, LineageWeatherManager.LookupCityRequestListener {
+
+    private static final Logger LOG = LoggerFactory.getLogger(LineageOsWeatherReceiver.class);
+
+    private WeatherLocation weatherLocation = null;
+    private Context mContext;
+    private PendingIntent mPendingIntent = null;
+
+    public LineageOsWeatherReceiver() {
+        mContext = GBApplication.getContext();
+        final LineageWeatherManager weatherManager = LineageWeatherManager.getInstance(mContext);
+        if (weatherManager == null) {
+            return;
+        }
+
+        Prefs prefs = GBApplication.getPrefs();
+
+        String city = prefs.getString("weather_city", null);
+        String cityId = prefs.getString("weather_cityid", null);
+
+        if ((cityId == null || cityId.equals("")) && city != null && !city.equals("")) {
+            lookupCity(city);
+        } else if (city != null && cityId != null) {
+            weatherLocation = new WeatherLocation.Builder(cityId, city).build();
+            enablePeriodicAlarm(true);
+        }
+    }
+
+    private void lookupCity(String city) {
+        final LineageWeatherManager weatherManager = LineageWeatherManager.getInstance(mContext);
+        if (weatherManager == null) {
+            return;
+        }
+
+        if (city != null && !city.equals("")) {
+            if (weatherManager.getActiveWeatherServiceProviderLabel() != null) {
+                weatherManager.lookupCity(city, this);
+            }
+        }
+    }
+
+    private void enablePeriodicAlarm(boolean enable) {
+        if ((mPendingIntent != null && enable) || (mPendingIntent == null && !enable)) {
+            return;
+        }
+
+        AlarmManager am = (AlarmManager) (mContext.getSystemService(Context.ALARM_SERVICE));
+        if (am == null) {
+            LOG.warn("could not get alarm manager!");
+            return;
+        }
+
+        if (enable) {
+            Intent intent = new Intent("GB_UPDATE_WEATHER");
+            intent.setPackage(BuildConfig.APPLICATION_ID);
+            mPendingIntent = PendingIntentUtils.getBroadcast(mContext, 0, intent, 0, false);
+            am.setInexactRepeating(AlarmManager.RTC_WAKEUP, Calendar.getInstance().getTimeInMillis() + 10000, AlarmManager.INTERVAL_HOUR, mPendingIntent);
+        } else {
+            am.cancel(mPendingIntent);
+            mPendingIntent = null;
+        }
+    }
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        Prefs prefs = GBApplication.getPrefs();
+
+        String city = prefs.getString("weather_city", null);
+        String cityId = prefs.getString("weather_cityid", null);
+
+        if (city != null && !city.equals("") && cityId == null) {
+            lookupCity(city);
+        } else {
+            requestWeather();
+        }
+    }
+
+    private void requestWeather() {
+        final LineageWeatherManager weatherManager = LineageWeatherManager.getInstance(GBApplication.getContext());
+        if (weatherManager.getActiveWeatherServiceProviderLabel() != null && weatherLocation != null) {
+            weatherManager.requestWeatherUpdate(weatherLocation, this);
+        }
+    }
+
+    /**
+     * @param cmCondition LineageOS/CyanogenMod condition code
+     * @return Yahoo condition code
+     */
+    private static int LineageOSToYahooCondition(int cmCondition) {
+        int yahooCondition;
+        if (cmCondition <= SHOWERS) {
+            yahooCondition = cmCondition;
+        } else if (cmCondition <= SCATTERED_THUNDERSTORMS) {
+            yahooCondition = cmCondition + 1;
+        } else if (cmCondition <= SCATTERED_SNOW_SHOWERS) {
+            yahooCondition = cmCondition + 2;
+        } else if (cmCondition <= ISOLATED_THUNDERSHOWERS) {
+            yahooCondition = cmCondition + 3;
+        } else {
+            yahooCondition = NOT_AVAILABLE;
+        }
+        return yahooCondition;
+    }
+
+    @Override
+    public void onWeatherRequestCompleted(int status, WeatherInfo weatherInfo) {
+        if (weatherInfo != null) {
+            LOG.info("weather: " + weatherInfo.toString());
+            WeatherSpec weatherSpec = new WeatherSpec();
+            weatherSpec.timestamp = (int) (weatherInfo.getTimestamp() / 1000);
+            weatherSpec.location = weatherInfo.getCity();
+
+            if (weatherInfo.getTemperatureUnit() == FAHRENHEIT) {
+                weatherSpec.currentTemp = (int) WeatherUtils.fahrenheitToCelsius(weatherInfo.getTemperature()) + 273;
+                weatherSpec.todayMaxTemp = (int) WeatherUtils.fahrenheitToCelsius(weatherInfo.getTodaysHigh()) + 273;
+                weatherSpec.todayMinTemp = (int) WeatherUtils.fahrenheitToCelsius(weatherInfo.getTodaysLow()) + 273;
+            } else {
+                weatherSpec.currentTemp = (int) weatherInfo.getTemperature() + 273;
+                weatherSpec.todayMaxTemp = (int) weatherInfo.getTodaysHigh() + 273;
+                weatherSpec.todayMinTemp = (int) weatherInfo.getTodaysLow() + 273;
+            }
+            if (weatherInfo.getWindSpeedUnit() == MPH) {
+                weatherSpec.windSpeed = (float) weatherInfo.getWindSpeed() * 1.609344f;
+            } else {
+                weatherSpec.windSpeed = (float) weatherInfo.getWindSpeed();
+            }
+            weatherSpec.windDirection = (int) weatherInfo.getWindDirection();
+
+            weatherSpec.currentConditionCode = Weather.mapToOpenWeatherMapCondition(LineageOSToYahooCondition(weatherInfo.getConditionCode()));
+            weatherSpec.currentCondition = Weather.getConditionString(weatherSpec.currentConditionCode);
+            weatherSpec.currentHumidity = (int) weatherInfo.getHumidity();
+
+            weatherSpec.forecasts = new ArrayList<>();
+            List<WeatherInfo.DayForecast> forecasts = weatherInfo.getForecasts();
+            for (int i = 1; i < forecasts.size(); i++) {
+                WeatherInfo.DayForecast cmForecast = forecasts.get(i);
+                WeatherSpec.Daily gbForecast = new WeatherSpec.Daily();
+                if (weatherInfo.getTemperatureUnit() == FAHRENHEIT) {
+                    gbForecast.maxTemp = (int) WeatherUtils.fahrenheitToCelsius(cmForecast.getHigh()) + 273;
+                    gbForecast.minTemp = (int) WeatherUtils.fahrenheitToCelsius(cmForecast.getLow()) + 273;
+                } else {
+                    gbForecast.maxTemp = (int) cmForecast.getHigh() + 273;
+                    gbForecast.minTemp = (int) cmForecast.getLow() + 273;
+                }
+                gbForecast.conditionCode = Weather.mapToOpenWeatherMapCondition(LineageOSToYahooCondition(cmForecast.getConditionCode()));
+                weatherSpec.forecasts.add(gbForecast);
+            }
+            ArrayList<WeatherSpec> weatherSpecs = new ArrayList<>(Collections.singletonList(weatherSpec));
+            Weather.getInstance().setWeatherSpec(weatherSpecs);
+            GBApplication.deviceService().onSendWeather(weatherSpecs);
+        } else {
+            LOG.info("request has returned null for WeatherInfo");
+        }
+    }
+
+    @Override
+    public void onLookupCityRequestCompleted(int result, List<WeatherLocation> list) {
+        if (list != null) {
+            weatherLocation = list.get(0);
+            String cityId = weatherLocation.getCityId();
+            String city = weatherLocation.getCity();
+
+            SharedPreferences.Editor editor = GBApplication.getPrefs().getPreferences().edit();
+            editor.putString("weather_city", city).apply();
+            editor.putString("weather_cityid", cityId).apply();
+            enablePeriodicAlarm(true);
+            requestWeather();
+        } else {
+            enablePeriodicAlarm(false);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/MusicPlaybackReceiver.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/MusicPlaybackReceiver.java
new file mode 100644
index 0000000..ff5c1e1
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/MusicPlaybackReceiver.java
@@ -0,0 +1,117 @@
+/*  Copyright (C) 2015-2024 andre, Andreas Shimokawa, Avamander, Carsten
+    Pfeiffer, Daniele Gobbetti
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.externalevents;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
+
+public class MusicPlaybackReceiver extends BroadcastReceiver {
+    private static final Logger LOG = LoggerFactory.getLogger(MusicPlaybackReceiver.class);
+    private static MusicSpec lastMusicSpec = new MusicSpec();
+    private static MusicStateSpec lastStateSpec = new MusicStateSpec();
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        /*
+        Bundle bundle = intent.getExtras();
+        for (String key : bundle.keySet()) {
+            Object value = bundle.get(key);
+            LOG.info(String.format("%s %s (%s)", key,
+                    value != null ? value.toString() : "null", value != null ? value.getClass().getName() : "no class"));
+        }
+        */
+        MusicSpec musicSpec = new MusicSpec(lastMusicSpec);
+        MusicStateSpec stateSpec = new MusicStateSpec(lastStateSpec);
+
+        Bundle incomingBundle = intent.getExtras();
+
+        if (incomingBundle == null) {
+            LOG.warn("Not processing incoming null bundle.");
+            return;
+        }
+
+        for (String key : incomingBundle.keySet()) {
+            Object incoming = incomingBundle.get(key);
+            if (incoming instanceof String && "artist".equals(key)) {
+                musicSpec.artist = (String) incoming;
+            } else if (incoming instanceof String && "album".equals(key)) {
+                musicSpec.album = (String) incoming;
+            } else if (incoming instanceof String && "track".equals(key)) {
+                musicSpec.track = (String) incoming;
+            } else if (incoming instanceof String && "title".equals(key) && musicSpec.track == null) {
+                musicSpec.track = (String) incoming;
+            } else if (incoming instanceof Integer && "duration".equals(key)) {
+                musicSpec.duration = (Integer) incoming / 1000;
+            } else if (incoming instanceof Long && "duration".equals(key)) {
+                musicSpec.duration = ((Long) incoming).intValue() / 1000;
+            } else if (incoming instanceof Integer && "position".equals(key)) {
+                stateSpec.position = (Integer) incoming / 1000;
+            } else if (incoming instanceof Long && "position".equals(key)) {
+                stateSpec.position = ((Long) incoming).intValue() / 1000;
+            } else if (incoming instanceof Boolean && "playing".equals(key)) {
+                stateSpec.state = (byte) (((Boolean) incoming) ? MusicStateSpec.STATE_PLAYING : MusicStateSpec.STATE_PAUSED);
+                stateSpec.playRate = (byte) (((Boolean) incoming) ? 100 : 0);
+            } else if (incoming instanceof String && "duration".equals(key)) {
+                musicSpec.duration = Integer.parseInt((String) incoming) / 1000;
+            } else if (incoming instanceof String && "trackno".equals(key)) {
+                musicSpec.trackNr = Integer.parseInt((String) incoming);
+            } else if (incoming instanceof String && "totaltrack".equals(key)) {
+                musicSpec.trackCount = Integer.parseInt((String) incoming);
+            } else if (incoming instanceof Integer && "pos".equals(key)) {
+                stateSpec.position = (Integer) incoming;
+            } else if (incoming instanceof Integer && "repeat".equals(key)) {
+                if ((Integer) incoming > 0) {
+                    stateSpec.repeat = 1;
+                } else {
+                    stateSpec.repeat = 0;
+                }
+            } else if (incoming instanceof Integer && "shuffle".equals(key)) {
+                if ((Integer) incoming > 0) {
+                    stateSpec.shuffle = 1;
+                } else {
+                    stateSpec.shuffle = 0;
+                }
+            }
+        }
+
+        if (!lastMusicSpec.equals(musicSpec)) {
+            lastMusicSpec = musicSpec;
+            LOG.info("Update Music Info: " + musicSpec.artist + " / " + musicSpec.album + " / " + musicSpec.track);
+            GBApplication.deviceService().onSetMusicInfo(musicSpec);
+        } else {
+            LOG.info("Got metadata changed intent, but nothing changed, ignoring.");
+        }
+
+        if (!lastStateSpec.equals(stateSpec)) {
+            lastStateSpec = stateSpec;
+            LOG.info("Update Music State: state=" + stateSpec.state + ", position= " + stateSpec.position);
+            GBApplication.deviceService().onSetMusicState(stateSpec);
+        } else {
+            LOG.info("Got state changed intent, but not enough has changed, ignoring.");
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/NotificationListener.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/NotificationListener.java
new file mode 100644
index 0000000..cb2754f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/NotificationListener.java
@@ -0,0 +1,1113 @@
+/*  Copyright (C) 2015-2024 abettenburg, Andreas B枚hler, Andreas Shimokawa,
+    AndrewBedscastle, Arjan Schrijver, Carsten Pfeiffer, Daniel Dakhno, Daniele
+    Gobbetti, Davis Mosenkovs, Dmitriy Bogdanov, Dmitry Markin, Frank Slezak,
+    gnufella, Gordon Williams, Hasan Ammar, Jos茅 Rebelo, Julien Pivotto,
+    Kevin Richter, mamucho, Matthieu Baerts, mvn23, Normano64, Petr Kadlec,
+    Petr Van臎k, Steffen Liebergeld, Taavi Eom盲e, theghostofheathledger, t-m-w,
+    veecue, Zhong Jianxin
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.externalevents;
+
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.graphics.drawable.Drawable;
+import android.media.session.MediaController;
+import android.media.session.MediaSession;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.PowerManager;
+import android.os.Process;
+import android.os.UserHandle;
+import android.provider.MediaStore;
+import android.service.notification.NotificationListenerService;
+import android.service.notification.StatusBarNotification;
+
+import androidx.annotation.NonNull;
+import androidx.core.app.NotificationCompat;
+import androidx.core.app.RemoteInput;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+import androidx.palette.graphics.Palette;
+
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.time.LocalTime;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+import de.greenrobot.dao.query.Query;
+import nodomain.freeyourgadget.gadgetbridge.BuildConfig;
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.pebble.PebbleColor;
+import nodomain.freeyourgadget.gadgetbridge.entities.NotificationFilter;
+import nodomain.freeyourgadget.gadgetbridge.entities.NotificationFilterDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.NotificationFilterEntry;
+import nodomain.freeyourgadget.gadgetbridge.entities.NotificationFilterEntryDao;
+import nodomain.freeyourgadget.gadgetbridge.externalevents.notifications.GoogleMapsNotificationHandler;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.AppNotificationType;
+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.NotificationType;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceCommunicationService;
+import nodomain.freeyourgadget.gadgetbridge.util.BitmapUtil;
+import nodomain.freeyourgadget.gadgetbridge.util.GBPrefs;
+import nodomain.freeyourgadget.gadgetbridge.util.LimitedQueue;
+import nodomain.freeyourgadget.gadgetbridge.util.MediaManager;
+import nodomain.freeyourgadget.gadgetbridge.util.NotificationUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.PebbleUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+import static nodomain.freeyourgadget.gadgetbridge.activities.NotificationFilterActivity.NOTIFICATION_FILTER_MODE_BLACKLIST;
+import static nodomain.freeyourgadget.gadgetbridge.activities.NotificationFilterActivity.NOTIFICATION_FILTER_MODE_WHITELIST;
+import static nodomain.freeyourgadget.gadgetbridge.activities.NotificationFilterActivity.NOTIFICATION_FILTER_SUBMODE_ALL;
+import static nodomain.freeyourgadget.gadgetbridge.util.StringUtils.ensureNotNull;
+
+public class NotificationListener extends NotificationListenerService {
+
+    private static final Logger LOG = LoggerFactory.getLogger(NotificationListener.class);
+
+    public static final String ACTION_DISMISS
+            = "nodomain.freeyourgadget.gadgetbridge.notificationlistener.action.dismiss";
+    public static final String ACTION_DISMISS_ALL
+            = "nodomain.freeyourgadget.gadgetbridge.notificationlistener.action.dismiss_all";
+    public static final String ACTION_OPEN
+            = "nodomain.freeyourgadget.gadgetbridge.notificationlistener.action.open";
+    public static final String ACTION_MUTE
+            = "nodomain.freeyourgadget.gadgetbridge.notificationlistener.action.mute";
+    public static final String ACTION_REPLY
+            = "nodomain.freeyourgadget.gadgetbridge.notificationlistener.action.reply";
+
+    private final LimitedQueue<Integer, NotificationCompat.Action> mActionLookup = new LimitedQueue<>(32);
+    private final LimitedQueue<Integer, String> mPackageLookup = new LimitedQueue<>(64);
+    private final LimitedQueue<Integer, Long> mNotificationHandleLookup = new LimitedQueue<>(128);
+
+    private final HashMap<String, Long> notificationBurstPrevention = new HashMap<>();
+    private final HashMap<String, Long> notificationOldRepeatPrevention = new HashMap<>();
+
+    private static final Set<String> GROUP_SUMMARY_WHITELIST = new HashSet<String>() {{
+        add("com.microsoft.office.lync15");
+        add("com.skype.raider");
+        add("mikado.bizcalpro");
+    }};
+
+    public static final ArrayList<String> notificationStack = new ArrayList<>();
+    private static final ArrayList<Integer> notificationsActive = new ArrayList<>();
+
+    private static final Set<String> supportedPictureMimeTypes = new HashSet<String>() {{
+        add("image/"); //for im.vector.app
+        add("image/jpeg");
+        add("image/png");
+        add("image/gif");
+        add("image/bmp");
+        add("image/webp");
+    }};
+
+    private File notificationPictureCacheDirectory;
+
+    private long activeCallPostTime;
+    private int mLastCallCommand = CallSpec.CALL_UNDEFINED;
+
+    private final Handler mHandler = new Handler();
+    private Runnable mSetMusicInfoRunnable = null;
+    private Runnable mSetMusicStateRunnable = null;
+
+    private final GoogleMapsNotificationHandler googleMapsNotificationHandler = new GoogleMapsNotificationHandler();
+
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            if (action == null) {
+                LOG.warn("no action");
+                return;
+            }
+
+            int handle = (int) intent.getLongExtra("handle", -1);
+            switch (action) {
+                case GBApplication.ACTION_QUIT:
+                    stopSelf();
+                    break;
+
+                case ACTION_OPEN: {
+                    StatusBarNotification[] sbns = NotificationListener.this.getActiveNotifications();
+                    Long ts = mNotificationHandleLookup.lookup(handle);
+                    if (ts == null) {
+                        LOG.info("could not lookup handle for open action");
+                        break;
+                    }
+
+                    for (StatusBarNotification sbn : sbns) {
+                        if (sbn.getPostTime() == ts) {
+                            try {
+                                PendingIntent pi = sbn.getNotification().contentIntent;
+                                if (pi != null) {
+                                    pi.send();
+                                }
+                            } catch (final PendingIntent.CanceledException e) {
+                                LOG.error("Failed to open notification {}", sbn.getId());
+                            }
+                        }
+                    }
+                    break;
+                }
+                case ACTION_MUTE:
+                    String packageName = mPackageLookup.lookup(handle);
+                    if (packageName == null) {
+                        LOG.info("could not lookup handle for mute action");
+                        break;
+                    }
+                    LOG.info("going to mute {}", packageName);
+                    if (GBApplication.getPrefs().getString("notification_list_is_blacklist", "true").equals("true")) {
+                        GBApplication.addAppToNotifBlacklist(packageName);
+                    } else {
+                        GBApplication.removeFromAppsNotifBlacklist(packageName);
+                    }
+                    break;
+                case ACTION_DISMISS: {
+                    StatusBarNotification[] sbns = NotificationListener.this.getActiveNotifications();
+                    Long ts = mNotificationHandleLookup.lookup(handle);
+                    if (ts == null) {
+                        LOG.info("could not lookup handle for dismiss action");
+                        break;
+                    }
+                    for (StatusBarNotification sbn : sbns) {
+                        if (sbn.getPostTime() == ts) {
+                            String key = sbn.getKey();
+                            NotificationListener.this.cancelNotification(key);
+                        }
+                    }
+                    break;
+                }
+                case ACTION_DISMISS_ALL:
+                    NotificationListener.this.cancelAllNotifications();
+                    break;
+                case ACTION_REPLY:
+                    NotificationCompat.Action wearableAction = mActionLookup.lookup(handle);
+                    String reply = intent.getStringExtra("reply");
+                    if (wearableAction != null) {
+                        PendingIntent actionIntent = wearableAction.getActionIntent();
+                        if (actionIntent == null) {
+                            LOG.warn("Action intent is null");
+                            break;
+                        }
+                        Intent localIntent = new Intent();
+                        localIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                        if (wearableAction.getRemoteInputs() != null && wearableAction.getRemoteInputs().length > 0) {
+                            RemoteInput[] remoteInputs = wearableAction.getRemoteInputs();
+                            Bundle extras = new Bundle();
+                            extras.putCharSequence(remoteInputs[0].getResultKey(), reply);
+                            RemoteInput.addResultsToIntent(remoteInputs, localIntent, extras);
+                        }
+                        try {
+                            LOG.info("will send exec intent to remote application");
+                            actionIntent.send(context, 0, localIntent);
+                            mActionLookup.remove(handle);
+                        } catch (final PendingIntent.CanceledException e) {
+                            LOG.warn("replyToLastNotification error", e);
+                        }
+                    } else {
+                        LOG.warn("Received ACTION_REPLY but cannot find the corresponding wearableAction");
+                    }
+                    break;
+            }
+        }
+    };
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        IntentFilter filterLocal = new IntentFilter();
+        filterLocal.addAction(GBApplication.ACTION_QUIT);
+        filterLocal.addAction(ACTION_OPEN);
+        filterLocal.addAction(ACTION_DISMISS);
+        filterLocal.addAction(ACTION_DISMISS_ALL);
+        filterLocal.addAction(ACTION_MUTE);
+        filterLocal.addAction(ACTION_REPLY);
+        LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, filterLocal);
+        createNotificationPictureCacheDirectory();
+        cleanUpNotificationPictureProvider();
+    }
+
+    @Override
+    public void onDestroy() {
+        LocalBroadcastManager.getInstance(this).unregisterReceiver(mReceiver);
+        notificationStack.clear();
+        notificationsActive.clear();
+        cleanUpNotificationPictureProvider();
+        super.onDestroy();
+    }
+
+    @Override
+    public void onNotificationPosted(StatusBarNotification sbn) {
+        onNotificationPosted(sbn, null);
+    }
+
+    @Override
+    public void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) {
+        logNotification(sbn, true);
+
+        notificationStack.remove(sbn.getPackageName());
+        notificationStack.add(sbn.getPackageName());
+
+        if (isServiceNotRunningAndShouldIgnoreNotifications()) return;
+
+        final GBPrefs prefs = GBApplication.getPrefs();
+
+        if (isOutsideNotificationTimes(prefs)) {
+            return;
+        }
+
+        final boolean ignoreWorkProfile = prefs.getBoolean("notifications_ignore_work_profile", false);
+        if (ignoreWorkProfile && isWorkProfile(sbn)) {
+            LOG.debug("Ignoring notification from work profile");
+            return;
+        }
+
+        final boolean mediaIgnoresAppList = prefs.getBoolean("notification_media_ignores_application_list", false);
+
+        // If media notifications ignore app list, check them before
+        if (mediaIgnoresAppList && handleMediaSessionNotification(sbn)) return;
+
+        if (shouldIgnoreSource(sbn)) return;
+
+        /* Check for navigation notifications and ignore if we're handling them */
+        if (googleMapsNotificationHandler.handle(getApplicationContext(), sbn)) return;
+
+        // If media notifications do NOT ignore app list, check them after
+        if (!mediaIgnoresAppList && handleMediaSessionNotification(sbn)) return;
+
+        int dndSuppressed = 0;
+        if (rankingMap != null) {
+            // Handle priority notifications for Do Not Disturb
+            Ranking ranking = new Ranking();
+            if (rankingMap.getRanking(sbn.getKey(), ranking)) {
+                if (!ranking.matchesInterruptionFilter()) dndSuppressed = 1;
+            }
+        }
+
+        if (prefs.getBoolean("notification_filter", false) && dndSuppressed == 1) {
+            LOG.debug("Ignoring notification because of do not disturb");
+            return;
+        }
+
+        if (NotificationCompat.CATEGORY_CALL.equals(sbn.getNotification().category)
+                && prefs.getBoolean("notification_support_voip_calls", false)
+                && (sbn.isOngoing() || shouldDisplayNonOngoingCallNotification(sbn))) {
+            handleCallNotification(sbn);
+            return;
+        }
+
+        if (shouldIgnoreNotification(sbn, false)) {
+            if (!"com.sec.android.app.clockpackage".equals(sbn.getPackageName())) {  // workaround to allow phone alarm notification
+                LOG.info("Ignoring notification: {}", sbn.getPackageName());          // need to fix
+                return;
+            }
+        }
+
+        String source = sbn.getPackageName();
+        Notification notification = sbn.getNotification();
+
+        Long notificationOldRepeatPreventionValue = notificationOldRepeatPrevention.get(source);
+        if (notificationOldRepeatPreventionValue != null
+                && notification.when <= notificationOldRepeatPreventionValue
+                && !shouldIgnoreRepeatPrevention(sbn)
+        ) {
+            LOG.info("NOT processing notification, already sent newer notifications from this source.");
+            return;
+        }
+
+        // Ignore too frequent notifications, according to user preference
+        long curTime = System.nanoTime();
+        Long notificationBurstPreventionValue = notificationBurstPrevention.get(source);
+        if (notificationBurstPreventionValue != null) {
+            long diff = curTime - notificationBurstPreventionValue;
+            if (diff < TimeUnit.SECONDS.toNanos(prefs.getInt("notifications_timeout", 0))) {
+                LOG.info("Ignoring frequent notification, last one was {} ms ago", TimeUnit.NANOSECONDS.toMillis(diff));
+                return;
+            }
+        }
+
+        NotificationSpec notificationSpec = new NotificationSpec();
+        notificationSpec.key = sbn.getKey();
+        notificationSpec.when = notification.when;
+
+        // determinate Source App Name ("Label")
+        String name = NotificationUtils.getApplicationLabel(this, source);
+        if (name != null) {
+            notificationSpec.sourceName = name;
+        }
+
+        // Get the app ID that generated this notification. For now only used by pebble color, but may be more useful later.
+        notificationSpec.sourceAppId = source;
+
+        // Get the icon of the notification
+        notificationSpec.iconId = notification.icon;
+
+        notificationSpec.type = AppNotificationType.getInstance().get(source);
+
+        //FIXME: some quirks lookup table would be the minor evil here
+        if (source.startsWith("com.fsck.k9")) {
+            if (NotificationCompat.isGroupSummary(notification)) {
+                LOG.info("ignore K9 group summary");
+                return;
+            }
+        }
+
+        if (notificationSpec.type == null) {
+            notificationSpec.type = NotificationType.UNKNOWN;
+        }
+
+        // Get color
+        notificationSpec.pebbleColor = getPebbleColorForNotification(notificationSpec);
+
+        LOG.info(
+                "Processing notification {}, age: {}, source: {}, flags: {}",
+                notificationSpec.getId(),
+                (System.currentTimeMillis() - notification.when) ,
+                source,
+                notification.flags
+        );
+
+        boolean preferBigText = prefs.getBoolean("notification_prefer_long_text", true);
+
+        dissectNotificationTo(notification, notificationSpec, preferBigText);
+
+        if (notificationSpec.title != null || notificationSpec.body != null) {
+            final String textToCheck = ensureNotNull(notificationSpec.title) + " " + ensureNotNull(notificationSpec.body);
+            if (!checkNotificationContentForWhiteAndBlackList(sbn.getPackageName().toLowerCase(), textToCheck)) {
+                return;
+            }
+        }
+
+        // ignore Gadgetbridge's very own notifications, except for those from the debug screen
+        if (getApplicationContext().getPackageName().equals(source)) {
+            if (!getApplicationContext().getString(R.string.test_notification).equals(notificationSpec.title)) {
+                return;
+            }
+        }
+
+        NotificationCompat.WearableExtender wearableExtender = new NotificationCompat.WearableExtender(notification);
+        List<NotificationCompat.Action> actions = wearableExtender.getActions();
+
+        // Some apps such as Telegram send both a group + normal notifications, which would get sent in duplicate to the devices
+        // Others only send the group summary, so they need to be whitelisted
+        if (actions.isEmpty() && NotificationCompat.isGroupSummary(notification)
+                && !GROUP_SUMMARY_WHITELIST.contains(source)) { //this could cause #395 to come back
+            LOG.info("Not forwarding notification, FLAG_GROUP_SUMMARY is set and no wearable action present. Notification flags: " + notification.flags);
+            return;
+        }
+
+        notificationSpec.attachedActions = new ArrayList<>();
+        notificationSpec.dndSuppressed = dndSuppressed;
+
+        // DISMISS action
+        NotificationSpec.Action dismissAction = new NotificationSpec.Action();
+        dismissAction.title = "Dismiss";
+        dismissAction.type = NotificationSpec.Action.TYPE_SYNTECTIC_DISMISS;
+        notificationSpec.attachedActions.add(dismissAction);
+
+        for (NotificationCompat.Action act : actions) {
+            if (act != null) {
+                NotificationSpec.Action wearableAction = new NotificationSpec.Action();
+                wearableAction.title = act.getTitle().toString();
+                if (act.getRemoteInputs() != null && act.getRemoteInputs().length > 0) {
+                    wearableAction.type = NotificationSpec.Action.TYPE_WEARABLE_REPLY;
+                } else {
+                    wearableAction.type = NotificationSpec.Action.TYPE_WEARABLE_SIMPLE;
+                }
+                notificationSpec.attachedActions.add(wearableAction);
+                wearableAction.handle = (notificationSpec.getId() << 4) + notificationSpec.attachedActions.size();
+                mActionLookup.add((int)wearableAction.handle, act);
+                LOG.info("Found wearable action: {} - {}  {}", notificationSpec.attachedActions.size(), act.getTitle(), sbn.getTag());
+            }
+        }
+
+        // OPEN action
+        NotificationSpec.Action openAction = new NotificationSpec.Action();
+        openAction.title = getString(R.string._pebble_watch_open_on_phone);
+        openAction.type = NotificationSpec.Action.TYPE_SYNTECTIC_OPEN;
+        notificationSpec.attachedActions.add(openAction);
+
+        // MUTE action
+        NotificationSpec.Action muteAction = new NotificationSpec.Action();
+        muteAction.title = getString(R.string._pebble_watch_mute);
+        muteAction.type = NotificationSpec.Action.TYPE_SYNTECTIC_MUTE;
+        notificationSpec.attachedActions.add(muteAction);
+
+        mNotificationHandleLookup.add(notificationSpec.getId(), sbn.getPostTime()); // for both DISMISS and OPEN
+        mPackageLookup.add(notificationSpec.getId(), sbn.getPackageName()); // for MUTE
+
+        notificationBurstPrevention.put(source, curTime);
+        if (notification.when == 0) {
+            LOG.info("This app might show old/duplicate notifications. notification.when is 0 for {}", source);
+        } else if ((notification.when - System.currentTimeMillis()) > 30_000L) {
+            // #4327 - Some apps such as outlook send reminder notifications in the future
+            // If we add them to the oldRepeatPrevention, they never show up again
+            LOG.info("This app might show old/duplicate notifications. notification.when is in the future for {}", source);
+        } else {
+            notificationOldRepeatPrevention.put(source, notification.when);
+        }
+        notificationsActive.add(notificationSpec.getId());
+        // NOTE for future developers: this call goes to implementations of DeviceService.onNotification(NotificationSpec), like in GBDeviceService
+        // this does NOT directly go to implementations of DeviceSupport.onNotification(NotificationSpec)!
+        GBApplication.deviceService().onNotification(notificationSpec);
+    }
+
+    private static boolean isOutsideNotificationTimes(final GBPrefs prefs) {
+        if (!prefs.getNotificationTimesEnabled()) {
+            return false;
+        }
+
+        final LocalTime now = LocalTime.now();
+        final LocalTime start = prefs.getNotificationTimesStart();
+        final LocalTime end = prefs.getNotificationTimesEnd();
+        final boolean shouldIgnore;
+        if (start.isBefore(end)) {
+            // eg. 06:00 -> 22:00
+            shouldIgnore = now.isAfter(start) && now.isBefore(end);
+        } else {
+            // goes past midnight, eg. 22:00 -> 06:00
+            shouldIgnore = now.isAfter(start) || now.isBefore(end);
+        }
+
+        if (shouldIgnore) {
+            LOG.debug("Ignoring notification outside of notification times {}/{}", start, end);
+        }
+
+        return shouldIgnore;
+    }
+
+    private boolean checkNotificationContentForWhiteAndBlackList(String packageName, String body) {
+        long start = System.currentTimeMillis();
+
+        List<String> wordsList = new ArrayList<>();
+        NotificationFilter notificationFilter;
+
+        try (DBHandler db = GBApplication.acquireDB()) {
+
+            NotificationFilterDao notificationFilterDao = db.getDaoSession().getNotificationFilterDao();
+            NotificationFilterEntryDao notificationFilterEntryDao = db.getDaoSession().getNotificationFilterEntryDao();
+
+            Query<NotificationFilter> query = notificationFilterDao.queryBuilder().where(NotificationFilterDao.Properties.AppIdentifier.eq(packageName.toLowerCase())).build();
+            notificationFilter = query.unique();
+
+            if (notificationFilter == null) {
+                LOG.debug("No Notification Filter found");
+                return true;
+            }
+
+            LOG.debug("Loaded notification filter for '{}'", packageName);
+            Query<NotificationFilterEntry> queryEntries = notificationFilterEntryDao.queryBuilder().where(NotificationFilterEntryDao.Properties.NotificationFilterId.eq(notificationFilter.getId())).build();
+
+            List<NotificationFilterEntry> filterEntries = queryEntries.list();
+
+            if (BuildConfig.DEBUG) {
+                LOG.info("Database lookup took '{}' ms", System.currentTimeMillis() - start);
+            }
+
+            if (!filterEntries.isEmpty()) {
+                for (NotificationFilterEntry temp : filterEntries) {
+                    wordsList.add(temp.getNotificationFilterContent());
+                    LOG.debug("Loaded filter word: " + temp.getNotificationFilterContent());
+                }
+            }
+
+        } catch (Exception e) {
+            LOG.error("Could not acquire DB.", e);
+            return true;
+        }
+
+        return shouldContinueAfterFilter(body, wordsList, notificationFilter);
+    }
+
+    private void handleCallNotification(StatusBarNotification sbn) {
+        String app = sbn.getPackageName();
+        LOG.debug("got call from: " + app);
+        if (app.equals("com.android.dialer") || app.equals("com.android.incallui") || app.equals("com.google.android.dialer") || app.equals("com.asus.asusincallui") || app.equals("com.samsung.android.incallui")) {
+            LOG.debug("Ignoring non-voip call");
+            return;
+        }
+        Notification noti = sbn.getNotification();
+        dumpExtras(noti.extras);
+        boolean callStarted = false;
+        if (noti.actions != null && noti.actions.length > 0) {
+            for (Notification.Action action : noti.actions) {
+                LOG.info("Found call action: " + action.title);
+            }
+            if (noti.actions.length == 1) {
+                if (mLastCallCommand == CallSpec.CALL_INCOMING) {
+                    LOG.info("There is only one call action and previous state was CALL_INCOMING, assuming call started");
+                    callStarted = true;
+                } else {
+                    LOG.info("There is only one call action and previous state was not CALL_INCOMING, assuming outgoing call / duplicate notification and ignoring");
+                    // FIXME: is there a way to detect transition CALL_OUTGOING -> CALL_START for more complete VoIP call state tracking?
+                    return;
+                }
+            }
+            /*try {
+                LOG.info("Executing first action");
+                noti.actions[0].actionIntent.send();
+            } catch (PendingIntent.CanceledException e) {
+                e.printStackTrace();
+            }*/
+        }
+
+        // figure out sender
+        String number;
+        String appName = NotificationUtils.getApplicationLabel(this, app);
+        if (noti.extras.containsKey(Notification.EXTRA_PEOPLE)) {
+            number = noti.extras.getString(Notification.EXTRA_PEOPLE);
+        } else if (noti.extras.containsKey(Notification.EXTRA_TITLE)) {
+            number = noti.extras.getString(Notification.EXTRA_TITLE);
+        } else {
+            number = appName != null ? appName : app;
+        }
+        activeCallPostTime = sbn.getPostTime();
+        CallSpec callSpec = new CallSpec();
+        callSpec.number = number;
+        callSpec.sourceAppId = app;
+        if (appName != null) {
+            callSpec.sourceName = appName;
+        }
+        callSpec.command = callStarted ? CallSpec.CALL_START : CallSpec.CALL_INCOMING;
+        mLastCallCommand = callSpec.command;
+        GBApplication.deviceService().onSetCallState(callSpec);
+    }
+
+    boolean shouldContinueAfterFilter(String body, @NonNull List<String> wordsList, @NonNull NotificationFilter notificationFilter) {
+        LOG.debug("Mode: '{}' Submode: '{}' WordsList: '{}'", notificationFilter.getNotificationFilterMode(), notificationFilter.getNotificationFilterSubMode(), wordsList);
+
+        boolean allMode = notificationFilter.getNotificationFilterSubMode() == NOTIFICATION_FILTER_SUBMODE_ALL;
+
+        switch (notificationFilter.getNotificationFilterMode()) {
+            case NOTIFICATION_FILTER_MODE_BLACKLIST:
+                if (allMode) {
+                    for (String word : wordsList) {
+                        if (!body.contains(word)) {
+                            LOG.info("Not every word was found, blacklist has no effect, processing continues.");
+                            return true;
+                        }
+                    }
+                    LOG.info("Every word was found, blacklist has effect, processing stops.");
+                    return false;
+                } else {
+                    boolean containsAny = StringUtils.containsAny(body, wordsList.toArray(new CharSequence[0]));
+                    if (!containsAny) {
+                        LOG.info("No matching word was found, blacklist has no effect, processing continues.");
+                    } else {
+                        LOG.info("At least one matching word was found, blacklist has effect, processing stops.");
+                    }
+                    return !containsAny;
+                }
+
+            case NOTIFICATION_FILTER_MODE_WHITELIST:
+                if (allMode) {
+                    for (String word : wordsList) {
+                        if (!body.contains(word)) {
+                            LOG.info("Not every word was found, whitelist has no effect, processing stops.");
+                            return false;
+                        }
+                    }
+                    LOG.info("Every word was found, whitelist has effect, processing continues.");
+                    return true;
+                } else {
+                    boolean containsAny = StringUtils.containsAny(body, wordsList.toArray(new CharSequence[0]));
+                    if (containsAny) {
+                        LOG.info("At least one matching word was found, whitelist has effect, processing continues.");
+                    } else {
+                        LOG.info("No matching word was found, whitelist has no effect, processing stops.");
+                    }
+                    return containsAny;
+                }
+
+            default:
+                return true;
+        }
+    }
+
+    // Strip Unicode control sequences: some apps like Telegram add a lot of them for unknown reasons.
+    // Keep newline and whitespace characters
+    private String sanitizeUnicode(String orig) {
+        return orig.replaceAll("[\\p{C}&&\\S]", "");
+    }
+
+    private void dissectNotificationTo(Notification notification, NotificationSpec notificationSpec,
+                                       boolean preferBigText) {
+
+        Bundle extras = NotificationCompat.getExtras(notification);
+
+        //dumpExtras(extras);
+        if (extras == null) {
+            return;
+        }
+
+        CharSequence title = extras.getCharSequence(Notification.EXTRA_TITLE);
+        if (title != null) {
+            notificationSpec.title = sanitizeUnicode(title.toString());
+        }
+
+        CharSequence contentCS = null;
+        final CharSequence bigText = extras.getCharSequence(NotificationCompat.EXTRA_BIG_TEXT);
+        if (preferBigText && !StringUtils.isBlank(bigText)) {
+            contentCS = bigText;
+        } else if (extras.containsKey(Notification.EXTRA_TEXT)) {
+            contentCS = extras.getCharSequence(NotificationCompat.EXTRA_TEXT);
+        }
+        if (contentCS != null) {
+            notificationSpec.body = sanitizeUnicode(contentCS.toString());
+        }
+
+        NotificationCompat.MessagingStyle messagingStyle = NotificationCompat.MessagingStyle.extractMessagingStyleFromNotification(notification);
+        if (messagingStyle != null) {
+            List<NotificationCompat.MessagingStyle.Message> messages = messagingStyle.getMessages();
+            if (!messages.isEmpty()) {
+                // Get the last message (assumed to be the most recent)
+                NotificationCompat.MessagingStyle.Message lastMessage = messages.get(messages.size() - 1);
+
+                if (supportedPictureMimeTypes.contains(lastMessage.getDataMimeType())) {
+                    ContentResolver contentResolver = getContentResolver();
+                    try (Cursor cursor = contentResolver.query(lastMessage.getDataUri(), null, null, null, null)) {
+                        if (cursor != null && cursor.moveToFirst()) {
+                            int dataIndex = cursor.getColumnIndex(MediaStore.Images.Media.DATA);
+                            notificationSpec.picturePath = cursor.getString(dataIndex);
+                        }
+                    } catch (Exception e) {
+                        LOG.error(e.getMessage());
+                    }
+                }
+            }
+        }
+
+        if (extras.containsKey(NotificationCompat.EXTRA_PICTURE)) {
+            final Bitmap bmp = (Bitmap) extras.get(NotificationCompat.EXTRA_PICTURE);
+            File pictureFile = new File(this.notificationPictureCacheDirectory, String.valueOf(notificationSpec.getId()));
+
+            try (FileOutputStream fos = new FileOutputStream(pictureFile)) {
+                bmp.compress(Bitmap.CompressFormat.PNG, 100, fos);
+                notificationSpec.picturePath = pictureFile.getAbsolutePath();
+            } catch (IOException e) {
+                LOG.error("Failed to save picture to notification cache: {}", e.getMessage());
+            } finally {
+                bmp.recycle();
+            }
+        }
+
+        if (notificationSpec.type == NotificationType.COL_REMINDER
+                && notificationSpec.body == null
+                && notificationSpec.title != null) {
+            notificationSpec.body = notificationSpec.title;
+            notificationSpec.title = null;
+        }
+    }
+
+    private boolean handleMediaSessionNotification(final StatusBarNotification sbn) {
+        final MediaSession.Token token = sbn.getNotification().extras.getParcelable(Notification.EXTRA_MEDIA_SESSION);
+        return token != null && handleMediaSessionNotification(token);
+    }
+
+    /**
+     * Try to handle media session notifications that tell info about the current play state.
+     *
+     * @param mediaSession The mediasession to handle.
+     * @return true if notification was handled, false otherwise
+     */
+    public boolean handleMediaSessionNotification(MediaSession.Token mediaSession) {
+        try {
+            final MediaController c = new MediaController(getApplicationContext(), mediaSession);
+            if (c.getMetadata() == null) {
+                return false;
+            }
+
+            final MusicStateSpec stateSpec = MediaManager.extractMusicStateSpec(c.getPlaybackState());
+            final MusicSpec musicSpec = MediaManager.extractMusicSpec(c.getMetadata());
+
+            // finally, tell the device about it
+            if (mSetMusicInfoRunnable != null) {
+                mHandler.removeCallbacks(mSetMusicInfoRunnable);
+            }
+            mSetMusicInfoRunnable = new Runnable() {
+                @Override
+                public void run() {
+                    GBApplication.deviceService().onSetMusicInfo(musicSpec);
+                }
+            };
+            mHandler.postDelayed(mSetMusicInfoRunnable, 100);
+
+            if (stateSpec != null) {
+                if (mSetMusicStateRunnable != null) {
+                    mHandler.removeCallbacks(mSetMusicStateRunnable);
+                }
+                mSetMusicStateRunnable = new Runnable() {
+                    @Override
+                    public void run() {
+                        GBApplication.deviceService().onSetMusicState(stateSpec);
+                    }
+                };
+            }
+            mHandler.postDelayed(mSetMusicStateRunnable, 100);
+
+            return true;
+        } catch (final NullPointerException | SecurityException e) {
+            LOG.error("Failed to handle media session notification", e);
+            return false;
+        }
+    }
+
+    @Override
+    public void onNotificationRemoved(StatusBarNotification sbn) {
+        logNotification(sbn, false);
+
+        notificationStack.remove(sbn.getPackageName());
+
+        if (isServiceNotRunningAndShouldIgnoreNotifications()) return;
+
+        final GBPrefs prefs = GBApplication.getPrefs();
+
+        if (isOutsideNotificationTimes(prefs)) {
+            return;
+        }
+
+        final boolean ignoreWorkProfile = prefs.getBoolean("notifications_ignore_work_profile", false);
+        if (ignoreWorkProfile && isWorkProfile(sbn)) {
+            LOG.debug("Ignoring notification removal from work profile");
+            return;
+        }
+
+        final boolean mediaIgnoresAppList = prefs.getBoolean("notification_media_ignores_application_list", false);
+
+        // If media notifications ignore app list, check them before
+        if (mediaIgnoresAppList && handleMediaSessionNotification(sbn)) return;
+
+        if (shouldIgnoreSource(sbn)) return;
+
+        googleMapsNotificationHandler.handleRemove(sbn);
+
+        // If media notifications do NOT ignore app list, check them after
+        if (!mediaIgnoresAppList && handleMediaSessionNotification(sbn)) return;
+
+        if (Notification.CATEGORY_CALL.equals(sbn.getNotification().category)
+                && activeCallPostTime == sbn.getPostTime()) {
+            activeCallPostTime = 0;
+            CallSpec callSpec = new CallSpec();
+            callSpec.command = CallSpec.CALL_END;
+            mLastCallCommand = callSpec.command;
+            GBApplication.deviceService().onSetCallState(callSpec);
+        }
+
+        if (shouldIgnoreNotification(sbn, true)) return;
+
+        // Build list of all currently active notifications
+        ArrayList<Integer> activeNotificationsIds = new ArrayList<>();
+        for (StatusBarNotification notification : getActiveNotifications()) {
+            Integer id = mNotificationHandleLookup.lookupByValue(notification.getPostTime());
+            if (id != null) {
+                activeNotificationsIds.add(id);
+            }
+        }
+
+        // Build list of notifications that aren't active anymore
+        ArrayList<Integer> notificationsToRemove = new ArrayList<>();
+        for (int notificationId : notificationsActive) {
+            if (!activeNotificationsIds.contains(notificationId)) {
+                notificationsToRemove.add(notificationId);
+                deleteNotificationPicture(notificationId);
+            }
+        }
+
+        // Clean up removed notifications from internal list
+        notificationsActive.removeAll(notificationsToRemove);
+
+        // Send notification remove request to device
+        List<GBDevice> devices = GBApplication.app().getDeviceManager().getSelectedDevices();
+        for (GBDevice device : devices) {
+            if (!device.isInitialized()) {
+                continue;
+            }
+
+            Prefs devicePrefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(device.getAddress()));
+            if (devicePrefs.getBoolean("autoremove_notifications", true)) {
+                for (int id : notificationsToRemove) {
+                    LOG.info("Notification {} removed, deleting from {}", id, device.getAliasOrName());
+                    GBApplication.deviceService(device).onDeleteNotification(id);
+                }
+            }
+        }
+    }
+
+    private void deleteNotificationPicture(int notificationId) {
+        File pictureFile = new File(this.notificationPictureCacheDirectory, String.valueOf(notificationId));
+        if (pictureFile.exists())
+            pictureFile.delete();
+    }
+
+    private void cleanUpNotificationPictureProvider() {
+        File[] pictureFiles = this.notificationPictureCacheDirectory.listFiles();
+        if (pictureFiles == null)
+            return;
+
+        for (File pictureFile : pictureFiles) {
+            pictureFile.delete();
+        }
+    }
+
+    private void createNotificationPictureCacheDirectory() {
+        final File cacheDir = getApplicationContext().getExternalCacheDir();
+        this.notificationPictureCacheDirectory = new File(cacheDir, "notification-pictures");
+        this.notificationPictureCacheDirectory.mkdir();
+    }
+    private void logNotification(StatusBarNotification sbn, boolean posted) {
+        LOG.debug(
+                "Notification {} {}: packageName={}, when={}, priority={}, category={}",
+                sbn.getId(),
+                posted ? "posted" : "removed",
+                sbn.getPackageName(),
+                sbn.getNotification().when,
+                sbn.getNotification().priority,
+                sbn.getNotification().category
+        );
+    }
+
+    private void dumpExtras(Bundle bundle) {
+        for (String key : bundle.keySet()) {
+            Object value = bundle.get(key);
+            if (value == null) {
+                continue;
+            }
+            LOG.debug(String.format("Notification extra: %s %s (%s)", key, value.toString(), value.getClass().getName()));
+        }
+    }
+
+    private boolean isServiceNotRunningAndShouldIgnoreNotifications() {
+        /*
+         * return early if DeviceCommunicationService is not running,
+         * else the service would get started every time we get a notification.
+         * unfortunately we cannot enable/disable NotificationListener at runtime like we do with
+         * broadcast receivers because it seems to invalidate the permissions that are
+         * necessary for NotificationListenerService
+         */
+        if (!DeviceCommunicationService.isRunning(this)) {
+            LOG.trace("Service is not running, ignoring notification");
+            return true;
+        }
+        return false;
+    }
+
+    private boolean shouldIgnoreSource(StatusBarNotification sbn) {
+        String source = sbn.getPackageName();
+
+        Prefs prefs = GBApplication.getPrefs();
+
+        /* do not display messages from "android"
+         * This includes keyboard selection message, usb connection messages, etc
+         * Hope it does not filter out too much, we will see...
+         */
+
+        if (source.equals("android") ||
+                source.equals("com.android.systemui") ||
+                source.equals("com.android.dialer") ||
+                source.equals("com.google.android.dialer") ||
+                source.equals("com.cyanogenmod.eleven")) {
+            LOG.info("Ignoring notification, is a system event");
+            return true;
+        }
+
+        if (source.equals("com.moez.QKSMS") ||
+                source.equals("com.android.mms") ||
+                source.equals("com.sonyericsson.conversations") ||
+                source.equals("com.android.messaging") ||
+                source.equals("org.smssecure.smssecure")) {
+            if (!"never".equals(prefs.getString("notification_mode_sms", "when_screen_off"))) {
+                LOG.info("Ignoring notification, it's an sms notification");
+                return true;
+            }
+        }
+
+        if (GBApplication.getPrefs().getString("notification_list_is_blacklist", "true").equals("true")) {
+            if (GBApplication.appIsNotifBlacklisted(source)) {
+                LOG.info("Ignoring notification, application is blacklisted");
+                return true;
+            }
+        } else {
+            if (GBApplication.appIsNotifBlacklisted(source)) {
+                LOG.info("Allowing notification, application is whitelisted");
+                return false;
+            } else {
+                LOG.info("Ignoring notification, application is not whitelisted");
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    private boolean shouldIgnoreRepeatPrevention(StatusBarNotification sbn) {
+        if (isFitnessApp(sbn)) {
+            return true;
+        }
+        return false;
+    }
+
+    private boolean shouldIgnoreOngoing(StatusBarNotification sbn, NotificationType type) {
+        if (isFitnessApp(sbn)) {
+            return true;
+        }
+        if (type == NotificationType.COL_REMINDER) {
+            return true;
+        }
+        return false;
+    }
+
+    private boolean isFitnessApp(StatusBarNotification sbn) {
+        String source = sbn.getPackageName();
+        if (source.equals("de.dennisguse.opentracks")
+                || source.equals("de.dennisguse.opentracks.debug")
+                || source.equals("de.dennisguse.opentracks.nightly")
+                || source.equals("de.dennisguse.opentracks.playstore")
+                || source.equals("de.tadris.fitness")
+                || source.equals("de.tadris.fitness.debug")
+        ) {
+            return true;
+        }
+
+        return false;
+    }
+
+    private boolean isWorkProfile(StatusBarNotification sbn) {
+        final UserHandle currentUser = Process.myUserHandle();
+        return !sbn.getUser().equals(currentUser);
+    }
+
+    private boolean shouldDisplayNonOngoingCallNotification(StatusBarNotification sbn) {
+        String source = sbn.getPackageName();
+        NotificationType type = AppNotificationType.getInstance().get(source);
+
+        if (type == NotificationType.TELEGRAM) {
+            return true;
+        }
+
+        return false;
+    }
+
+    private boolean shouldIgnoreNotification(StatusBarNotification sbn, boolean remove) {
+        Notification notification = sbn.getNotification();
+        String source = sbn.getPackageName();
+
+        NotificationType type = AppNotificationType.getInstance().get(source);
+        //ignore notifications marked as LocalOnly https://developer.android.com/reference/android/app/Notification.html#FLAG_LOCAL_ONLY
+        //some Apps always mark their notifcations as read-only
+        if (NotificationCompat.getLocalOnly(notification) &&
+                type != NotificationType.WECHAT &&
+                type != NotificationType.TELEGRAM &&
+                type != NotificationType.OUTLOOK &&
+                type != NotificationType.COL_REMINDER &&
+                type != NotificationType.SKYPE) { //see https://github.com/Freeyourgadget/Gadgetbridge/issues/1109
+            LOG.info("Ignoring notification, local only");
+            return true;
+        }
+
+        Prefs prefs = GBApplication.getPrefs();
+
+        // Check for screen on when posting the notification; for removal, the screen
+        // has to be on (obviously)
+        if (!remove) {
+            if (!prefs.getBoolean("notifications_generic_whenscreenon", false)) {
+                PowerManager powermanager = (PowerManager) getSystemService(POWER_SERVICE);
+                if (powermanager != null && powermanager.isScreenOn()) {
+                    LOG.info("Not forwarding notification, screen seems to be on and settings do not allow this");
+                    return true;
+                }
+            }
+        }
+
+        if (sbn.getNotification().priority < Notification.PRIORITY_DEFAULT) {
+            if (prefs.getBoolean("notifications_ignore_low_priority", true)) {
+                LOG.info("Ignoring low priority notification");
+                return true;
+            }
+        }
+
+        if (shouldIgnoreOngoing(sbn, type)) {
+            return false;
+        }
+
+        return (notification.flags & Notification.FLAG_ONGOING_EVENT) == Notification.FLAG_ONGOING_EVENT;
+
+    }
+
+
+    /**
+     * Get the notification color that should be used for this Pebble notification.
+     *
+     * Note that this method will *not* edit the NotificationSpec passed in. It will only evaluate the PebbleColor.
+     *
+     * See Issue #815 on GitHub to see how notification colors are set.
+     *
+     * @param notificationSpec The NotificationSpec to read from.
+     * @return Returns a PebbleColor that best represents this notification.
+     */
+    private byte getPebbleColorForNotification(NotificationSpec notificationSpec) {
+        String appId = notificationSpec.sourceAppId;
+        NotificationType existingType = notificationSpec.type;
+
+        // If the notification type is known, return the associated color.
+        if (existingType != NotificationType.UNKNOWN) {
+            return existingType.color;
+        }
+
+        // Otherwise, we go and attempt to find the color from the app icon.
+        Drawable icon;
+        try {
+            icon = NotificationUtils.getAppIcon(getApplicationContext(), appId);
+            Objects.requireNonNull(icon);
+        } catch (Exception ex) {
+            // If we can't get the icon, we go with the default defined above.
+            LOG.warn("Could not get icon for AppID " + appId, ex);
+            return PebbleColor.IslamicGreen;
+        }
+
+        Bitmap bitmapIcon = BitmapUtil.convertDrawableToBitmap(icon);
+        int iconPrimaryColor = new Palette.Builder(bitmapIcon)
+                .generate()
+                .getVibrantColor(Color.parseColor("#aa0000"));
+
+        return PebbleUtils.getPebbleColor(iconPrimaryColor);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/OmniJawsObserver.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/OmniJawsObserver.java
new file mode 100644
index 0000000..34ca688
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/OmniJawsObserver.java
@@ -0,0 +1,194 @@
+/*  Copyright (C) 2017-2024 Carsten Pfeiffer, Daniele Gobbetti, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.externalevents;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.database.ContentObserver;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Handler;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.model.Weather;
+import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
+
+
+public class OmniJawsObserver extends ContentObserver {
+
+    private static final Logger LOG = LoggerFactory.getLogger(OmniJawsObserver.class);
+
+    private static final String SERVICE_PACKAGE = "org.omnirom.omnijaws";
+    public static final Uri WEATHER_URI = Uri.parse("content://org.omnirom.omnijaws.provider/weather");
+    private static final Uri SETTINGS_URI = Uri.parse("content://org.omnirom.omnijaws.provider/settings");
+
+    private Context mContext;
+    private boolean mInstalled;
+    private boolean mEnabled = false;
+    private boolean mMetric = true;
+
+
+    private final String[] WEATHER_PROJECTION = new String[]{
+            "city",
+            "condition", //unused, see below
+            "condition_code",
+            "temperature",
+            "humidity",
+            "forecast_low",
+            "forecast_high",
+            "forecast_condition",
+            "forecast_condition_code",
+            "time_stamp",
+            "forecast_date",
+            "wind_speed",
+            "wind_direction"
+    };
+
+    private final String[] SETTINGS_PROJECTION = new String[]{
+            "enabled",
+            "units"
+    };
+
+    public OmniJawsObserver(Handler handler) throws NameNotFoundException {
+        super(handler);
+        mContext = GBApplication.getContext();
+        mInstalled = isOmniJawsServiceAvailable();
+        LOG.info("OmniJaws installation status: " + mInstalled);
+        checkSettings();
+        LOG.info("OmniJaws is enabled: " + mEnabled);
+        if (!mEnabled) {
+            throw new NameNotFoundException();
+        }
+
+        updateWeather();
+    }
+
+    @Override
+    public void onChange(boolean selfChange) {
+        super.onChange(selfChange);
+        LOG.info("Weather update received");
+        checkSettings();
+        if (!mEnabled) {
+            LOG.info("Provider was disabled, ignoring.");
+            return;
+        }
+        queryWeather();
+    }
+
+    private void queryWeather() {
+        Cursor c = mContext.getContentResolver().query(WEATHER_URI, WEATHER_PROJECTION, null, null, null);
+        if (c != null) {
+            try {
+
+                WeatherSpec weatherSpec = new WeatherSpec();
+                weatherSpec.forecasts = new ArrayList<>();
+
+                int count = c.getCount();
+                if (count > 0) {
+                    for (int i = 0; i < count; i++) {
+                        c.moveToPosition(i);
+                        if (i == 0) {
+
+                            weatherSpec.location = c.getString(0);
+                            weatherSpec.currentConditionCode = Weather.mapToOpenWeatherMapCondition(c.getInt(2));
+                            weatherSpec.currentCondition = Weather.getConditionString(weatherSpec.currentConditionCode);
+                            //alternatively the following would also be possible
+                            //weatherSpec.currentCondition = c.getString(1);
+
+                            weatherSpec.currentTemp = toKelvin(c.getFloat(3));
+                            weatherSpec.currentHumidity = (int) c.getFloat(4);
+
+                            weatherSpec.windSpeed = toKmh(c.getFloat(11));
+                            weatherSpec.windDirection = c.getInt(12);
+                            weatherSpec.timestamp = (int) (Long.parseLong(c.getString(9)) / 1000);
+                        } else if (i == 1) {
+                            weatherSpec.todayMinTemp = toKelvin(c.getFloat(5));
+                            weatherSpec.todayMaxTemp = toKelvin(c.getFloat(6));
+                        } else {
+
+                            WeatherSpec.Daily gbForecast = new WeatherSpec.Daily();
+                            gbForecast.minTemp = toKelvin(c.getFloat(5));
+                            gbForecast.maxTemp = toKelvin(c.getFloat(6));
+                            gbForecast.conditionCode = Weather.mapToOpenWeatherMapCondition(c.getInt(8));
+                            weatherSpec.forecasts.add(gbForecast);
+                        }
+                    }
+                }
+
+                ArrayList<WeatherSpec> weatherSpecs = new ArrayList<>(Collections.singletonList(weatherSpec));
+                Weather.getInstance().setWeatherSpec(weatherSpecs);
+                GBApplication.deviceService().onSendWeather(weatherSpecs);
+
+            } finally {
+                c.close();
+            }
+        }
+
+    }
+
+    private void updateWeather() {
+        Intent updateIntent = new Intent(Intent.ACTION_MAIN)
+                .setClassName(SERVICE_PACKAGE, SERVICE_PACKAGE + ".WeatherService");
+        updateIntent.setAction(SERVICE_PACKAGE + ".ACTION_UPDATE");
+        mContext.startService(updateIntent);
+    }
+
+    private boolean isOmniJawsServiceAvailable() throws NameNotFoundException {
+        final PackageManager pm = mContext.getPackageManager();
+        pm.getPackageInfo("org.omnirom.omnijaws", 0);
+        return true;
+    }
+
+    private void checkSettings() {
+        if (!mInstalled) {
+            return;
+        }
+        final Cursor c = mContext.getContentResolver().query(SETTINGS_URI, SETTINGS_PROJECTION,
+                null, null, null);
+        if (c != null) {
+            int count = c.getCount();
+            if (count == 1) {
+                c.moveToPosition(0);
+                mEnabled = c.getInt(0) == 1 ? true : false;
+                mMetric = c.getInt(1) == 0 ? true : false;
+            }
+        }
+    }
+
+    private int toKelvin(double temperature) {
+        if (mMetric) {
+            return (int) (temperature + 273.15);
+        }
+        return (int) ((temperature - 32) * 0.5555555555555556D + 273.15);
+    }
+
+    private float toKmh(float speed) {
+        if (mMetric) {
+            return speed;
+        }
+        return (speed * 1.61f);
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/OsmandEventReceiver.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/OsmandEventReceiver.java
new file mode 100644
index 0000000..c33a508
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/OsmandEventReceiver.java
@@ -0,0 +1,237 @@
+/*  Copyright (C) 2021-2024 Andreas Shimokawa, Arjan Schrijver, Gordon
+    Williams, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.externalevents;
+
+import android.app.Application;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
+import android.os.IBinder;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.view.KeyEvent;
+
+import net.osmand.aidlapi.IOsmAndAidlCallback;
+import net.osmand.aidlapi.IOsmAndAidlInterface;
+import net.osmand.aidlapi.gpx.AGpxBitmap;
+import net.osmand.aidlapi.navigation.ADirectionInfo;
+import net.osmand.aidlapi.navigation.ANavigationUpdateParams;
+import net.osmand.aidlapi.navigation.ANavigationVoiceRouterMessageParams;
+import net.osmand.aidlapi.navigation.OnVoiceNavigationParams;
+import net.osmand.aidlapi.search.SearchResult;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.model.NavigationInfoSpec;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+public class OsmandEventReceiver {
+    private static final Logger LOG = LoggerFactory.getLogger(OsmandEventReceiver.class);
+
+    private final Application app;
+    private IOsmAndAidlInterface mIOsmAndAidlInterface;
+
+    private NavigationInfoSpec navigationInfoSpec = new NavigationInfoSpec();
+
+    private final IOsmAndAidlCallback.Stub mIOsmAndAidlCallback = new IOsmAndAidlCallback.Stub() {
+        @Override
+        public void onSearchComplete(List<SearchResult> resultSet) {
+        }
+
+        @Override
+        public void onUpdate() {
+        }
+
+        @Override
+        public void onAppInitialized() {
+        }
+
+        @Override
+        public void onGpxBitmapCreated(AGpxBitmap bitmap) {
+        }
+
+        @Override
+        public void updateNavigationInfo(ADirectionInfo directionInfo) {
+            navigationInfoSpec.nextAction = directionInfo.getTurnType();
+            navigationInfoSpec.distanceToTurn = directionInfo.getDistanceTo()+"m";
+
+            if (shouldSendNavigation()) {
+                GBApplication.deviceService().onSetNavigationInfo(navigationInfoSpec);
+            }
+
+            LOG.debug("Distance: {}, turnType: {}", directionInfo.getDistanceTo(), directionInfo.getTurnType());
+        }
+
+        @Override
+        public void onContextMenuButtonClicked(int buttonId, String pointId, String layerId) {
+        }
+
+        @Override
+        public void onVoiceRouterNotify(OnVoiceNavigationParams params) {
+            List<String> played = params.getPlayed();
+            for (String instuction : played) {
+                navigationInfoSpec.instruction = instuction;
+                LOG.debug("instruction: {}", instuction);
+                // only first one for now
+                break;
+            }
+        }
+
+        @Override
+        public void onKeyEvent(KeyEvent keyEvent) {
+
+        }
+    };
+
+    private final ServiceConnection mConnection = new ServiceConnection() {
+        public void onServiceConnected(ComponentName className,
+                                       IBinder service) {
+            mIOsmAndAidlInterface = IOsmAndAidlInterface.Stub.asInterface(service);
+            LOG.info("OsmAnd service connected");
+            registerForNavigationUpdates(true, 6666); // what is this id for?
+            registerForVoiceRouterMessages(true, 6667);
+//            Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("osmand.api://GET_INFO"));
+//            OsmandEventReceiver.this.startActivityForResult(intent,666);
+        }
+
+        public void onServiceDisconnected(ComponentName className) {
+            mIOsmAndAidlInterface = null;
+            LOG.info("OsmAnd service disconnected");
+        }
+    };
+
+    public OsmandEventReceiver(Application application) {
+        this.app = application;
+        if (!bindService()) {
+            LOG.warn("Could not bind to OsmAnd");
+        }
+    }
+
+    private boolean bindService() {
+        if (mIOsmAndAidlInterface == null) {
+            List<CharSequence> installedOsmandPackages = findInstalledOsmandPackages();
+            if (installedOsmandPackages.isEmpty()) {
+                LOG.warn("OsmAnd is not installed");
+                return false;
+            }
+            Prefs prefs = GBApplication.getPrefs();
+            String packageName = prefs.getString("pref_key_osmand_packagename", "autodetect");
+            if (packageName.equals("autodetect")) packageName = installedOsmandPackages.get(0).toString();
+            Intent intent = new Intent("net.osmand.aidl.OsmandAidlServiceV2");
+            intent.setPackage(packageName);
+            boolean res = app.bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
+            if (res) {
+                LOG.info("Bound to OsmAnd service (package "+packageName+")");
+                return true;
+            } else {
+                LOG.warn("Could not bind to OsmAnd service (package "+packageName+")");
+                return false;
+            }
+        } else {
+            return true;
+        }
+    }
+
+    public void cleanupResources() {
+        if (mIOsmAndAidlInterface != null) {
+            app.unbindService(mConnection);
+        }
+    }
+
+    public long registerForNavigationUpdates(boolean subscribeToUpdates, long callbackId) {
+        if (mIOsmAndAidlInterface != null) {
+            try {
+                ANavigationUpdateParams params = new ANavigationUpdateParams();
+                params.setCallbackId(callbackId);
+                params.setSubscribeToUpdates(subscribeToUpdates);
+
+                return mIOsmAndAidlInterface.registerForNavigationUpdates(params, mIOsmAndAidlCallback);
+            } catch (RemoteException e) {
+                LOG.error("could not subscribe to navigation updates", e);
+            }
+        }
+        return -1L;
+    }
+
+    private boolean shouldSendNavigation() {
+        Prefs prefs = GBApplication.getPrefs();
+
+        boolean navigationForward = prefs.getBoolean("navigation_forward", true);
+        boolean navigationOsmAnd = prefs.getBoolean("navigation_app_osmand", true);
+        if (!navigationForward || !navigationOsmAnd) {
+            return false;
+        }
+
+        boolean navigationScreenOn = prefs.getBoolean("nagivation_screen_on", true);
+        if (!navigationScreenOn) {
+            PowerManager powermanager = (PowerManager) app.getSystemService(Context.POWER_SERVICE);
+            if (powermanager != null && powermanager.isScreenOn()) {
+                LOG.info("Not forwarding navigation instructions, screen seems to be on and settings do not allow this");
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Method to register for Voice Router voice messages during navigation. Notifies user about voice messages.
+     *
+     * @param subscribeToUpdates (boolean) - boolean flag to subscribe or unsubscribe from messages
+     * @param callbackId         (long) - id of callback, needed to unsubscribe from messages
+     */
+    public long registerForVoiceRouterMessages(boolean subscribeToUpdates, long callbackId) {
+        ANavigationVoiceRouterMessageParams params = new ANavigationVoiceRouterMessageParams();
+        params.setCallbackId(callbackId);
+        params.setSubscribeToUpdates(subscribeToUpdates);
+        if (mIOsmAndAidlInterface != null) {
+            try {
+                return mIOsmAndAidlInterface.registerForVoiceRouterMessages(params, mIOsmAndAidlCallback);
+            } catch (RemoteException e) {
+                LOG.error("could not register for voice router messages", e);
+            }
+        }
+        return -1L;
+    }
+
+    public List<CharSequence> findInstalledOsmandPackages() {
+        List<CharSequence> installedPackages = new ArrayList<>();
+        for (String knownPackage : app.getBaseContext().getResources().getStringArray(R.array.osmand_package_names)) {
+            if (isPackageInstalled(knownPackage)) {
+                installedPackages.add(knownPackage);
+            }
+        }
+        return installedPackages;
+    }
+
+    private boolean isPackageInstalled(final String packageName) {
+        try {
+            return app.getBaseContext().getPackageManager().getApplicationInfo(packageName, 0).enabled;
+        } catch (PackageManager.NameNotFoundException e) {
+            return false;
+        }
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/PebbleReceiver.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/PebbleReceiver.java
new file mode 100644
index 0000000..8e4ca40
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/PebbleReceiver.java
@@ -0,0 +1,100 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, Carsten Pfeiffer, Daniele
+    Gobbetti, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.externalevents;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.PowerManager;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.NotificationType;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+public class PebbleReceiver extends BroadcastReceiver {
+
+    private static final Logger LOG = LoggerFactory.getLogger(PebbleReceiver.class);
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+
+        Prefs prefs = GBApplication.getPrefs();
+        if ("never".equals(prefs.getString("notification_mode_pebblemsg", "when_screen_off"))) {
+            return;
+        }
+        if ("when_screen_off".equals(prefs.getString("notification_mode_pebblemsg", "when_screen_off"))) {
+            PowerManager powermanager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+            if (powermanager.isScreenOn()) {
+                return;
+            }
+        }
+
+        String messageType = intent.getStringExtra("messageType");
+        if (!messageType.equals("PEBBLE_ALERT")) {
+            LOG.info("non PEBBLE_ALERT message type not supported");
+            return;
+        }
+
+        if (!intent.hasExtra("notificationData")) {
+            LOG.info("missing notificationData extra");
+            return;
+        }
+
+        NotificationSpec notificationSpec = new NotificationSpec();
+
+        String notificationData = intent.getStringExtra("notificationData");
+        try {
+            JSONArray notificationJSON = new JSONArray(notificationData);
+            notificationSpec.title = notificationJSON.getJSONObject(0).getString("title");
+            notificationSpec.body = notificationJSON.getJSONObject(0).getString("body");
+        } catch (JSONException e) {
+            e.printStackTrace();
+            return;
+        }
+
+        if (notificationSpec.title != null) {
+            notificationSpec.type = NotificationType.UNKNOWN;
+            String sender = intent.getStringExtra("sender");
+            if (GBApplication.getPrefs().getString("notification_list_is_blacklist", "true").equals("true")) {
+                if (GBApplication.appIsPebbleBlacklisted(sender)) {
+                    LOG.info("Ignoring Pebble message, application " + sender + " is blacklisted");
+                    return;
+                }
+            } else {
+                if (!GBApplication.appIsPebbleBlacklisted(sender)) {
+                    LOG.info("Ignoring Pebble message, application " + sender + " is not whitelisted");
+                    return;
+                }
+            }
+
+            if ("Conversations".equals(sender)) {
+                notificationSpec.type = NotificationType.CONVERSATIONS;
+            }
+            else if ("OsmAnd".equals(sender)) {
+                notificationSpec.type = NotificationType.GENERIC_NAVIGATION;
+            }
+            GBApplication.deviceService().onNotification(notificationSpec);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/PhoneCallReceiver.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/PhoneCallReceiver.java
new file mode 100644
index 0000000..7b700a5
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/PhoneCallReceiver.java
@@ -0,0 +1,172 @@
+/*  Copyright (C) 2015-2024 Andreas B枚hler, Andreas Shimokawa, Carsten
+    Pfeiffer, Daniele Gobbetti, Johannes Tysiak, Jos茅 Rebelo, mvn23, Normano64,
+    Taavi Eom盲e
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.externalevents;
+
+import android.app.NotificationManager;
+import android.app.NotificationManager.Policy;
+import android.app.Service;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.media.AudioManager;
+import android.os.Handler;
+import android.os.Looper;
+import android.telephony.TelephonyManager;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+
+public class PhoneCallReceiver extends BroadcastReceiver {
+    private static final Logger LOG = LoggerFactory.getLogger(PhoneCallReceiver.class);
+
+    private static int mLastState = TelephonyManager.CALL_STATE_IDLE;
+    private static String mSavedNumber;
+    private boolean mRestoreMutedCall = false;
+    private int mLastRingerMode;
+
+    private final Handler delayHandler = new Handler(Looper.getMainLooper());
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        TelephonyManager tm = (TelephonyManager) context.getSystemService(Service.TELEPHONY_SERVICE);
+        if (intent.getAction().equals("android.intent.action.NEW_OUTGOING_CALL")) {
+            mSavedNumber = intent.getExtras().getString("android.intent.extra.PHONE_NUMBER");
+        } else if (intent.getAction().equals("nodomain.freeyourgadget.gadgetbridge.MUTE_CALL")) {
+            // Handle the mute request only if the phone is currently ringing
+            if (mLastState != TelephonyManager.CALL_STATE_RINGING)
+                return;
+
+            AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+            mLastRingerMode = audioManager.getRingerMode();
+            try {
+                audioManager.setRingerMode(AudioManager.RINGER_MODE_SILENT);
+            } catch (SecurityException e) {
+                LOG.error("SecurityException when trying to set ringer (no permission granted :/ ?), not setting it then.");
+            }
+            mRestoreMutedCall = true;
+        } else {
+            if (intent.hasExtra(TelephonyManager.EXTRA_INCOMING_NUMBER)) {
+                String number = intent.getExtras().getString(TelephonyManager.EXTRA_INCOMING_NUMBER);
+                int state = tm.getCallState();
+                onCallStateChanged(context, state, number);
+            }
+        }
+    }
+
+    public void onCallStateChanged(Context context, int state, String number) {
+        if (mLastState == state) {
+            return;
+        }
+
+        int callCommand = CallSpec.CALL_UNDEFINED;
+        switch (state) {
+            case TelephonyManager.CALL_STATE_RINGING:
+                mSavedNumber = number;
+                callCommand = CallSpec.CALL_INCOMING;
+                break;
+            case TelephonyManager.CALL_STATE_OFFHOOK:
+                if (mLastState == TelephonyManager.CALL_STATE_RINGING) {
+                    callCommand = CallSpec.CALL_START;
+                } else {
+                    callCommand = CallSpec.CALL_OUTGOING;
+                    mSavedNumber = number;
+                }
+                break;
+            case TelephonyManager.CALL_STATE_IDLE:
+                if (mLastState == TelephonyManager.CALL_STATE_RINGING) {
+                    //missed call would be correct here
+                    callCommand = CallSpec.CALL_END;
+                } else {
+                    callCommand = CallSpec.CALL_END;
+                }
+                if (mRestoreMutedCall) {
+                    mRestoreMutedCall = false;
+                    AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+                    audioManager.setRingerMode(mLastRingerMode);
+                }
+                break;
+        }
+        if (callCommand != CallSpec.CALL_UNDEFINED) {
+            Prefs prefs = GBApplication.getPrefs();
+            if ("never".equals(prefs.getString("notification_mode_calls", "always"))) {
+                return;
+            }
+            int dndSuppressed = 0;
+            switch (GBApplication.getGrantedInterruptionFilter()) {
+                case NotificationManager.INTERRUPTION_FILTER_ALL:
+                    break;
+                case NotificationManager.INTERRUPTION_FILTER_ALARMS:
+                case NotificationManager.INTERRUPTION_FILTER_NONE:
+                    dndSuppressed = 1;
+                    break;
+                case NotificationManager.INTERRUPTION_FILTER_PRIORITY:
+                    if (GBApplication.isPriorityNumber(Policy.PRIORITY_CATEGORY_CALLS, mSavedNumber)) {
+                        break;
+                    }
+                    // FIXME: Handle Repeat callers if it is enabled in Do Not Disturb
+                    dndSuppressed = 1;
+            }
+            if (prefs.getBoolean("notification_filter", false) && dndSuppressed == 1) {
+                return;
+            }
+            CallSpec callSpec = new CallSpec();
+            callSpec.number = mSavedNumber;
+            callSpec.command = callCommand;
+            callSpec.dndSuppressed = dndSuppressed;
+
+            int callDelay = prefs.getInt("notification_delay_calls", 0);
+            if (callCommand == CallSpec.CALL_INCOMING) {
+                // Delay incoming call notifications by a configurable number of seconds
+                if (callDelay <= 0) {
+                    GBApplication.deviceService().onSetCallState(callSpec);
+                } else {
+                    scheduleOnSetCallState(callSpec, callDelay);
+                }
+            } else {
+                if (callCommand == CallSpec.CALL_START || callCommand == CallSpec.CALL_END) {
+                    // Call started or ended, unschedule any outstanding notifications
+                    unscheduleAllOnSetCallState();
+                }
+
+                // propagate the event to the device
+                GBApplication.deviceService().onSetCallState(callSpec);
+            }
+        }
+        mLastState = state;
+    }
+
+    private void scheduleOnSetCallState(final CallSpec callSpec, final int delaySeconds) {
+        final Runnable runnable = new Runnable() {
+            @Override public void run() {
+                GBApplication.deviceService().onSetCallState(callSpec);
+            }
+        };
+
+        delayHandler.postDelayed(runnable, delaySeconds * 1000);
+    }
+
+    private void unscheduleAllOnSetCallState() {
+        delayHandler.removeCallbacksAndMessages(null);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/SMSReceiver.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/SMSReceiver.java
new file mode 100644
index 0000000..d05ba28
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/SMSReceiver.java
@@ -0,0 +1,114 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, Carsten Pfeiffer, Daniele
+    Gobbetti, mvn23, Normano64, Zhong Jianxin
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.externalevents;
+
+import android.app.NotificationManager;
+import android.app.NotificationManager.Policy;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.PowerManager;
+import android.telephony.SmsMessage;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.NotificationType;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+public class SMSReceiver extends BroadcastReceiver {
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        Prefs prefs = GBApplication.getPrefs();
+        if ("never".equals(prefs.getString("notification_mode_sms", "when_screen_off"))) {
+            return;
+        }
+        if ("when_screen_off".equals(prefs.getString("notification_mode_sms", "when_screen_off"))) {
+            PowerManager powermanager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+            if (powermanager != null && powermanager.isScreenOn()) {
+                return;
+            }
+        }
+
+        NotificationSpec notificationSpec = new NotificationSpec();
+        notificationSpec.type = NotificationType.GENERIC_SMS;
+
+        Bundle bundle = intent.getExtras();
+        if (bundle != null) {
+            Object[] pdus = (Object[]) bundle.get("pdus");
+            if (pdus != null) {
+                int pduSize = pdus.length;
+                Map<String, StringBuilder> messageMap = new LinkedHashMap<>();
+                SmsMessage[] messages = new SmsMessage[pduSize];
+                for (int i = 0; i < pduSize; i++) {
+                    messages[i] = SmsMessage.createFromPdu((byte[]) pdus[i]);
+                    String originatingAddress = messages[i].getOriginatingAddress();
+                    if (!messageMap.containsKey(originatingAddress)) {
+                        messageMap.put(originatingAddress, new StringBuilder());
+                    }
+                    messageMap.get(originatingAddress).append(messages[i].getMessageBody());
+                }
+                for (Map.Entry<String, StringBuilder> entry : messageMap.entrySet()) {
+                    String originatingAddress = entry.getKey();
+                    if (originatingAddress != null) {
+                        notificationSpec.body = entry.getValue().toString();
+                        notificationSpec.phoneNumber = originatingAddress;
+                        notificationSpec.attachedActions = new ArrayList<>();
+
+                        // REPLY action
+                        NotificationSpec.Action replyAction = new NotificationSpec.Action();
+                        replyAction.title = "Reply";
+                        replyAction.type = NotificationSpec.Action.TYPE_SYNTECTIC_REPLY_PHONENR;
+                        notificationSpec.attachedActions.add(replyAction);
+
+                        // DISMISS ALL action
+                        NotificationSpec.Action dismissAllAction = new NotificationSpec.Action();
+                        dismissAllAction.title = "Dismiss All";
+                        dismissAllAction.type = NotificationSpec.Action.TYPE_SYNTECTIC_DISMISS_ALL;
+                        notificationSpec.attachedActions.add(dismissAllAction);
+
+                        int dndSuppressed = 0;
+                        switch (GBApplication.getGrantedInterruptionFilter()) {
+                            case NotificationManager.INTERRUPTION_FILTER_ALL:
+                                break;
+                            case NotificationManager.INTERRUPTION_FILTER_ALARMS:
+                            case NotificationManager.INTERRUPTION_FILTER_NONE:
+                                dndSuppressed = 1;
+                                break;
+                            case NotificationManager.INTERRUPTION_FILTER_PRIORITY:
+                                if (GBApplication.isPriorityNumber(Policy.PRIORITY_CATEGORY_MESSAGES, notificationSpec.phoneNumber)) {
+                                    break;
+                                }
+                                dndSuppressed = 1;
+                        }
+                        if (prefs.getBoolean("notification_filter", false) && dndSuppressed == 1) {
+                            return;
+                        }
+                        notificationSpec.dndSuppressed = dndSuppressed;
+                        GBApplication.deviceService().onNotification(notificationSpec);
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/SilentModeReceiver.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/SilentModeReceiver.java
new file mode 100644
index 0000000..6b8032d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/SilentModeReceiver.java
@@ -0,0 +1,48 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.externalevents;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.media.AudioManager;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+
+public class SilentModeReceiver extends BroadcastReceiver {
+    private static final Logger LOG = LoggerFactory.getLogger(SilentModeReceiver.class);
+
+    @Override
+    public void onReceive(final Context context, final Intent intent) {
+        if (intent.getAction() == null) {
+            return;
+        }
+
+        if (!AudioManager.RINGER_MODE_CHANGED_ACTION.equals(intent.getAction())) {
+            LOG.warn("Unexpected action {}", intent.getAction());
+            return;
+        }
+
+        final AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+        final int ringerMode = audioManager.getRingerMode();
+
+        GBApplication.deviceService().onChangePhoneSilentMode(ringerMode);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/TimeChangeReceiver.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/TimeChangeReceiver.java
new file mode 100644
index 0000000..12f62e4
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/TimeChangeReceiver.java
@@ -0,0 +1,165 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, Carsten Pfeiffer, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.externalevents;
+
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Build;
+import android.os.SystemClock;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.zone.ZoneOffsetTransition;
+import java.time.zone.ZoneRules;
+import java.util.Date;
+import java.util.GregorianCalendar;
+
+import nodomain.freeyourgadget.gadgetbridge.BuildConfig;
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.util.AndroidUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+import nodomain.freeyourgadget.gadgetbridge.util.PendingIntentUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+
+public class TimeChangeReceiver extends BroadcastReceiver {
+    private static final Logger LOG = LoggerFactory.getLogger(TimeChangeReceiver.class);
+
+    public static final String ACTION_DST_CHANGED_OR_PERIODIC_SYNC = "nodomain.freeyourgadget.gadgetbridge.DST_CHANGED_OR_PERIODIC_SYNC";
+    public static final long PERIODIC_SYNC_INTERVAL_MS = 158003000; // 43:53:23.000
+    public static final long PERIODIC_SYNC_INTERVAL_MAX_MS = 172800000; // 48 hours
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        final Prefs prefs = GBApplication.getPrefs();
+        final String action = intent.getAction();
+        if (action == null) {
+            LOG.warn("Null action");
+            return;
+        }
+
+        if (!prefs.getBoolean("datetime_synconconnect", true)) {
+            LOG.warn("Ignoring time change for {}, time sync is disabled", action);
+            return;
+        }
+
+        switch (action) {
+            case Intent.ACTION_TIME_CHANGED:
+            case Intent.ACTION_TIMEZONE_CHANGED:
+            case ACTION_DST_CHANGED_OR_PERIODIC_SYNC:
+                // Continue after the switch
+                break;
+            default:
+                LOG.warn("Unknown action {}", action);
+                return;
+        }
+
+        // acquire wake lock, otherwise device might enter deep sleep immediately after returning from onReceive()
+        AndroidUtils.acquirePartialWakeLock(context, "TimeSyncWakeLock", 10100);
+
+        final Date newTime = GregorianCalendar.getInstance().getTime();
+        LOG.info("Time/Timezone changed or periodic sync, syncing with device: {} ({}), {}", DateTimeUtils.formatDate(newTime), newTime.toGMTString(), intent.getAction());
+        GBApplication.deviceService().onSetTime();
+
+        // Reschedule the next DST change (since the timezone may have changed) or periodic sync
+        scheduleNextDstChangeOrPeriodicSync(context);
+    }
+
+    /**
+     * Schedule an alarm to trigger on the next DST change, since ACTION_TIMEZONE_CHANGED is not broadcast otherwise
+     * or schedule an alarm to trigger after PERIODIC_SYNC_INTERVAL_MS (whichever is earlier).
+     *
+     * @param context the context
+     */
+    public static void scheduleNextDstChangeOrPeriodicSync(final Context context) {
+        final ZoneId zoneId = ZoneId.systemDefault();
+        final ZoneRules zoneRules = zoneId.getRules();
+        final Instant now = Instant.now();
+        final ZoneOffsetTransition transition = zoneRules.nextTransition(now);
+
+        final Intent i = new Intent(ACTION_DST_CHANGED_OR_PERIODIC_SYNC);
+        i.setPackage(BuildConfig.APPLICATION_ID);
+        final PendingIntent pi = PendingIntentUtils.getBroadcast(context, 0, i, 0, false);
+
+        final AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+
+        boolean exactAlarm = false;
+        long delayMillis = PERIODIC_SYNC_INTERVAL_MS;
+
+        if (transition != null) {
+            final long nextDstMillis = transition.getInstant().toEpochMilli();
+            final long dstDelayMillis = nextDstMillis - now.toEpochMilli() + 5000L;
+            if (dstDelayMillis < PERIODIC_SYNC_INTERVAL_MAX_MS) {
+                exactAlarm = canScheduleExactAlarms(context, am);
+                delayMillis = dstDelayMillis;
+                LOG.info("Scheduling next DST change: {} (in {} millis) (exact = {})", nextDstMillis, delayMillis, exactAlarm);
+            }
+        } else {
+            LOG.warn("No DST transition found for {}", zoneId);
+        }
+
+        if (delayMillis == PERIODIC_SYNC_INTERVAL_MS) {
+            LOG.info("Scheduling next periodic time sync in {} millis (exact = {})", delayMillis, exactAlarm);
+        }
+
+        am.cancel(pi);
+
+        boolean scheduledExact = false;
+        if (exactAlarm) {
+            try {
+                am.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + delayMillis, pi);
+                scheduledExact = true;
+            } catch (final Exception e) {
+                LOG.error("Failed to schedule exact alarm for next DST change or periodic time sync", e);
+            }
+        }
+
+        // Fallback to inexact alarm if the exact one failed
+        if (!scheduledExact) {
+            try {
+                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+                    am.setAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + delayMillis, pi);
+                } else {
+                    am.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + delayMillis, pi);
+                }
+            } catch (final Exception e) {
+                LOG.error("Failed to schedule inexact alarm for next DST change or periodic time sync", e);
+            }
+        }
+    }
+
+    public static void ifEnabledScheduleNextDstChangeOrPeriodicSync(final Context context) {
+        if (GBApplication.getPrefs().getBoolean("datetime_synconconnect", true)) {
+            scheduleNextDstChangeOrPeriodicSync(context);
+        }
+    }
+
+    private static boolean canScheduleExactAlarms(final Context context, final AlarmManager am) {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+            return am.canScheduleExactAlarms();
+        } else {
+            return GB.checkPermission(context, "android.permission.SCHEDULE_EXACT_ALARM");
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/TinyWeatherForecastGermanyReceiver.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/TinyWeatherForecastGermanyReceiver.java
new file mode 100644
index 0000000..f8f3ab4
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/TinyWeatherForecastGermanyReceiver.java
@@ -0,0 +1,55 @@
+/*  Copyright (C) 2020-2024 Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+
+package nodomain.freeyourgadget.gadgetbridge.externalevents;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.widget.Toast;
+
+import java.util.ArrayList;
+import java.util.Collections;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.model.Weather;
+import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class TinyWeatherForecastGermanyReceiver extends BroadcastReceiver {
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        if (intent != null) {
+            Bundle bundle = intent.getExtras();
+            if (bundle != null) {
+                try {
+                    WeatherSpec weatherSpec = bundle.getParcelable("WeatherSpec");
+                    if (weatherSpec != null) {
+                        ArrayList<WeatherSpec> weatherSpecs = new ArrayList<>(Collections.singletonList(weatherSpec));
+                        weatherSpec.timestamp = (int) (System.currentTimeMillis() / 1000);
+                        Weather.getInstance().setWeatherSpec(weatherSpecs);
+                        GBApplication.deviceService().onSendWeather(weatherSpecs);
+                    }
+                } catch (Exception e) {
+                    GB.toast("Gadgetbridge received broken or incompatible weather data", Toast.LENGTH_SHORT, GB.ERROR, e);
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/WeatherNotificationConfig.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/WeatherNotificationConfig.java
new file mode 100644
index 0000000..e07254d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/WeatherNotificationConfig.java
@@ -0,0 +1,31 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, Carsten Pfeiffer, Daniele
+    Gobbetti
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.externalevents;
+
+import android.os.Bundle;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBActivity;
+
+public class WeatherNotificationConfig extends AbstractGBActivity {
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_weather_notification);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/WeatherNotificationReceiver.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/WeatherNotificationReceiver.java
new file mode 100644
index 0000000..fa49c9c
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/WeatherNotificationReceiver.java
@@ -0,0 +1,64 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, Daniele Gobbetti
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.externalevents;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Collections;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.model.Weather;
+import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
+import ru.gelin.android.weather.notification.ParcelableWeather2;
+
+
+public class WeatherNotificationReceiver extends BroadcastReceiver {
+    private static final Logger LOG = LoggerFactory.getLogger(WeatherNotificationReceiver.class);
+
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        if (intent.getAction() == null || !intent.getAction().contains("WEATHER_UPDATE_2")) {
+            LOG.info("Wrong action");
+            return;
+        }
+        ParcelableWeather2 parcelableWeather2 = null;
+        try {
+            parcelableWeather2 = intent.getParcelableExtra("ru.gelin.android.weather.notification.EXTRA_WEATHER");
+        } catch (RuntimeException e) {
+            LOG.error("cannot get ParcelableWeather2", e);
+        }
+
+        if (parcelableWeather2 != null) {
+            Weather weather = Weather.getInstance();
+            weather.setReconstructedOWMForecast(parcelableWeather2.reconstructedOWMForecast);
+
+            WeatherSpec weatherSpec = parcelableWeather2.weatherSpec;
+            LOG.info("weather in " + weatherSpec.location + " is " + weatherSpec.currentCondition + " (" + (weatherSpec.currentTemp - 273) + "掳C)");
+
+            ArrayList<WeatherSpec> weatherSpecs = new ArrayList<>(Collections.singletonList(weatherSpec));
+            Weather.getInstance().setWeatherSpec(weatherSpecs);
+            GBApplication.deviceService().onSendWeather(weatherSpecs);
+        }
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/gps/GBLocationListener.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/gps/GBLocationListener.java
new file mode 100644
index 0000000..59c51b4
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/gps/GBLocationListener.java
@@ -0,0 +1,90 @@
+/*  Copyright (C) 2022-2024 illis, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.externalevents.gps;
+
+import android.location.Location;
+import android.location.LocationListener;
+import android.os.Bundle;
+import android.os.SystemClock;
+
+import androidx.annotation.NonNull;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.devices.EventHandler;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+/**
+ * An implementation of a {@link LocationListener} that forwards the location updates to the
+ * provided {@link EventHandler}.
+ */
+public class GBLocationListener implements LocationListener {
+    private static final Logger LOG = LoggerFactory.getLogger(GBLocationListener.class);
+
+    private final GBDevice device;
+
+    private Location previousLocation;
+    // divide by 3.6 to get km/h to m/s
+    private static final double SPEED_THRESHOLD = 1.0 / 3.6;
+
+    public GBLocationListener(final GBDevice device) {
+        this.device = device;
+    }
+
+    @Override
+    public void onLocationChanged(@NonNull final Location location) {
+        LOG.info("Location changed: {}", location);
+
+        // Correct the location time
+        location.setTime(getLocationTimestamp(location));
+
+        // The location usually doesn't contain speed, compute it from the previous location
+        // Some devices report hasSpeed() as true, and yet only return a 0 value, so we have to check against a speed threshold
+        boolean hasValidSpeed = location.hasSpeed() && (location.getSpeed() > SPEED_THRESHOLD);
+        if (previousLocation != null && !hasValidSpeed) {
+            long timeInterval = (location.getTime() - previousLocation.getTime());
+            float distanceInMeters = previousLocation.distanceTo(location);
+            location.setSpeed(distanceInMeters / timeInterval * 1000L);
+        }
+
+        previousLocation = location;
+
+        GBApplication.deviceService(device).onSetGpsLocation(location);
+    }
+
+    @Override
+    public void onProviderDisabled(@NonNull final String provider) {
+        LOG.info("onProviderDisabled: {}", provider);
+    }
+
+    @Override
+    public void onProviderEnabled(@NonNull final String provider) {
+        LOG.info("onProviderDisabled: {}", provider);
+    }
+
+    @Override
+    public void onStatusChanged(final String provider, final int status, final Bundle extras) {
+        LOG.info("onStatusChanged: {}, {}", provider, status);
+    }
+
+    private static long getLocationTimestamp(final Location location) {
+        long nanosSinceLocation = SystemClock.elapsedRealtimeNanos() - location.getElapsedRealtimeNanos();
+        return System.currentTimeMillis() - (nanosSinceLocation / 100_000L);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/gps/GBLocationProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/gps/GBLocationProvider.java
new file mode 100644
index 0000000..efb8f77
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/gps/GBLocationProvider.java
@@ -0,0 +1,51 @@
+/*  Copyright (C) 2022-2024 Jos茅 Rebelo, LukasEdl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.externalevents.gps;
+
+import android.content.Context;
+import android.location.LocationListener;
+
+/**
+ * An abstract location provider, which periodically sends a location update to the provided {@link LocationListener}.
+ */
+public abstract class GBLocationProvider {
+    private final Context context;
+    private final LocationListener locationListener;
+
+    public GBLocationProvider(final Context context, final LocationListener locationListener) {
+        this.context = context;
+        this.locationListener = locationListener;
+    }
+
+    public final Context getContext() {
+        return this.context;
+    }
+
+    public final LocationListener getLocationListener() {
+        return this.locationListener;
+    }
+
+    /**
+     * Start sending periodic location updates.
+     */
+    public abstract void start(final int interval);
+
+    /**
+     * Stop sending periodic location updates.
+     */
+    public abstract void stop();
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/gps/GBLocationProviderType.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/gps/GBLocationProviderType.java
new file mode 100644
index 0000000..0834fb3
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/gps/GBLocationProviderType.java
@@ -0,0 +1,47 @@
+/*  Copyright (C) 2022-2024 LukasEdl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.externalevents.gps;
+
+import android.content.Context;
+import android.location.LocationManager;
+
+import nodomain.freeyourgadget.gadgetbridge.externalevents.gps.providers.MockLocationProvider;
+import nodomain.freeyourgadget.gadgetbridge.externalevents.gps.providers.PhoneLocationProvider;
+
+public enum GBLocationProviderType {
+    GPS {
+        @Override
+        public GBLocationProvider newInstance(final Context context, final GBLocationListener locationListener) {
+            return new PhoneLocationProvider(context, locationListener, LocationManager.GPS_PROVIDER);
+        }
+    },
+    NETWORK {
+        @Override
+        public GBLocationProvider newInstance(final Context context, final GBLocationListener locationListener) {
+            return new PhoneLocationProvider(context, locationListener, LocationManager.NETWORK_PROVIDER);
+        }
+    },
+    MOCK {
+        @Override
+        public GBLocationProvider newInstance(final Context context, final GBLocationListener locationListener) {
+            return new MockLocationProvider(context, locationListener);
+        }
+    },
+    ;
+
+    public abstract GBLocationProvider newInstance(final Context context, final GBLocationListener locationListener);
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/gps/GBLocationService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/gps/GBLocationService.java
new file mode 100644
index 0000000..8dc948a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/gps/GBLocationService.java
@@ -0,0 +1,186 @@
+/*  Copyright (C) 2022-2024 halemmerich, Jos茅 Rebelo, LukasEdl, Martin Boonk
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.externalevents.gps;
+
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.core.app.NotificationCompat;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+import nodomain.freeyourgadget.gadgetbridge.BuildConfig;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+import nodomain.freeyourgadget.gadgetbridge.util.PendingIntentUtils;
+
+
+/**
+ * A static location manager, which keeps track of what providers are currently running. A notification is kept
+ * while there is at least one provider running.
+ */
+public class GBLocationService extends BroadcastReceiver {
+    private static final Logger LOG = LoggerFactory.getLogger(GBLocationService.class);
+
+    public static final String ACTION_START = "GBLocationService.START";
+    public static final String ACTION_STOP = "GBLocationService.STOP";
+    public static final String ACTION_STOP_ALL = "GBLocationService.STOP_ALL";
+
+    public static final String EXTRA_TYPE = "extra_type";
+    public static final String EXTRA_INTERVAL = "extra_interval";
+
+    private final Context context;
+    private final Map<GBDevice, List<GBLocationProvider>> providersByDevice = new HashMap<>();
+
+    public GBLocationService(final Context context) {
+        this.context = context;
+    }
+
+    @Override
+    public void onReceive(final Context context, final Intent intent) {
+        if (intent.getAction() == null) {
+            LOG.warn("Action is null");
+            return;
+        }
+
+        final GBDevice device = intent.getParcelableExtra(GBDevice.EXTRA_DEVICE);
+
+        switch (intent.getAction()) {
+            case ACTION_START:
+                if (device == null) {
+                    LOG.error("Device is null for {}", intent.getAction());
+                    return;
+                }
+
+                final GBLocationProviderType providerType = GBLocationProviderType.valueOf(
+                        intent.hasExtra(EXTRA_TYPE) ? intent.getStringExtra(EXTRA_TYPE) : "GPS"
+                );
+                final int updateInterval = intent.getIntExtra(EXTRA_INTERVAL, 1000);
+
+                LOG.debug("Starting location provider {} for {}", providerType, device.getAliasOrName());
+
+                if (!providersByDevice.containsKey(device)) {
+                    providersByDevice.put(device, new ArrayList<>());
+                }
+
+                updateNotification();
+
+                final List<GBLocationProvider> existingProviders = providersByDevice.get(device);
+
+                final GBLocationListener locationListener = new GBLocationListener(device);
+                final GBLocationProvider locationProvider = providerType.newInstance(context, locationListener);
+                locationProvider.start(updateInterval);
+                Objects.requireNonNull(existingProviders).add(locationProvider);
+                return;
+            case ACTION_STOP:
+                if (device != null) {
+                    stopDevice(device);
+                    updateNotification();
+                } else {
+                    stopAll();
+                }
+                return;
+            case ACTION_STOP_ALL:
+                stopAll();
+                return;
+            default:
+                LOG.warn("Unknown action {}", intent.getAction());
+        }
+    }
+
+    public void stopDevice(final GBDevice device) {
+        LOG.debug("Stopping location providers for {}", device.getAliasOrName());
+
+        final List<GBLocationProvider> providers = providersByDevice.remove(device);
+        if (providers != null) {
+            for (final GBLocationProvider provider : providers) {
+                provider.stop();
+            }
+        }
+    }
+
+    public IntentFilter buildFilter() {
+        final IntentFilter intentFilter = new IntentFilter();
+        intentFilter.addAction(ACTION_START);
+        intentFilter.addAction(ACTION_STOP);
+        return intentFilter;
+    }
+
+    public void stopAll() {
+        LOG.info("Stopping location service for all devices");
+
+        final List<GBDevice> gbDevices = new ArrayList<>(providersByDevice.keySet());
+        for (GBDevice d : gbDevices) {
+            stopDevice(d);
+        }
+
+        updateNotification();
+    }
+
+    public static void start(final Context context,
+                             @NonNull final GBDevice device,
+                             final GBLocationProviderType providerType,
+                             final int updateInterval) {
+        final Intent intent = new Intent(ACTION_START);
+        intent.putExtra(GBDevice.EXTRA_DEVICE, device);
+        intent.putExtra(EXTRA_TYPE, providerType.name());
+        intent.putExtra(EXTRA_INTERVAL, updateInterval);
+        LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
+    }
+
+    public static void stop(final Context context, @Nullable final GBDevice device) {
+        final Intent intent = new Intent(ACTION_STOP);
+        intent.putExtra(GBDevice.EXTRA_DEVICE, device);
+        LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
+    }
+
+    private void updateNotification() {
+        if (!providersByDevice.isEmpty()) {
+            final Intent notificationIntent = new Intent(context, GBLocationService.class);
+            notificationIntent.setPackage(BuildConfig.APPLICATION_ID);
+            notificationIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+            final PendingIntent pendingIntent = PendingIntentUtils.getActivity(context, 0, notificationIntent, 0, false);
+
+            final NotificationCompat.Builder nb = new NotificationCompat.Builder(context, GB.NOTIFICATION_CHANNEL_ID_GPS)
+                    .setTicker(context.getString(R.string.notification_gps_title))
+                    .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
+                    .setContentTitle(context.getString(R.string.notification_gps_title))
+                    .setContentText(context.getString(R.string.notification_gps_text, providersByDevice.size()))
+                    .setContentIntent(pendingIntent)
+                    .setSmallIcon(R.drawable.ic_gps_location)
+                    .setOngoing(true);
+
+            GB.notify(GB.NOTIFICATION_ID_GPS, nb.build(), context);
+        } else {
+            GB.removeNotification(GB.NOTIFICATION_ID_GPS, context);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/gps/providers/MockLocationProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/gps/providers/MockLocationProvider.java
new file mode 100644
index 0000000..06cce41
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/gps/providers/MockLocationProvider.java
@@ -0,0 +1,97 @@
+/*  Copyright (C) 2022-2024 Jos茅 Rebelo, LukasEdl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.externalevents.gps.providers;
+
+import android.content.Context;
+import android.location.Location;
+import android.location.LocationListener;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.SystemClock;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import nodomain.freeyourgadget.gadgetbridge.externalevents.gps.GBLocationProvider;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.webview.CurrentPosition;
+
+/**
+ * A mock location provider which keeps updating the location at a constant speed, starting from the
+ * last known location. Useful for local tests.
+ */
+public class MockLocationProvider extends GBLocationProvider {
+    private static final Logger LOG = LoggerFactory.getLogger(MockLocationProvider.class);
+
+    private Location previousLocation = new CurrentPosition().getLastKnownLocation();
+
+    /**
+     * Interval between location updates, in milliseconds.
+     */
+    private static final int DEFAULT_INTERVAL = 1000;
+
+    /**
+     * Difference between location updates, in degrees.
+     */
+    private static final float COORD_DIFF = 0.0002f;
+
+    /**
+     * Whether the handler is running.
+     */
+    private boolean running = false;
+
+    private final Handler handler = new Handler(Looper.getMainLooper());
+
+    public MockLocationProvider(final Context context, final LocationListener locationListener) {
+        super(context, locationListener);
+    }
+
+    @Override
+    public void start(final int interval) {
+        LOG.info("Starting mock location provider");
+
+        running = true;
+        handler.postDelayed(new Runnable() {
+            @Override
+            public void run() {
+                if (!running) {
+                    return;
+                }
+
+                final Location newLocation = new Location(previousLocation);
+                newLocation.setLatitude(previousLocation.getLatitude() + COORD_DIFF);
+                newLocation.setTime(System.currentTimeMillis());
+                newLocation.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos());
+
+                getLocationListener().onLocationChanged(newLocation);
+
+                previousLocation = newLocation;
+
+                if (running) {
+                    handler.postDelayed(this, interval);
+                }
+            }
+        }, interval > 0 ? interval : DEFAULT_INTERVAL);
+    }
+
+    @Override
+    public void stop() {
+        LOG.info("Stopping mock location provider");
+
+        running = false;
+        handler.removeCallbacksAndMessages(null);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/gps/providers/PhoneLocationProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/gps/providers/PhoneLocationProvider.java
new file mode 100644
index 0000000..7049b7c
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/gps/providers/PhoneLocationProvider.java
@@ -0,0 +1,78 @@
+/*  Copyright (C) 2022-2024 Jos茅 Rebelo, LukasEdl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.externalevents.gps.providers;
+
+import android.Manifest;
+import android.content.Context;
+import android.location.Location;
+import android.location.LocationListener;
+import android.location.LocationManager;
+import android.os.Looper;
+import android.widget.Toast;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import nodomain.freeyourgadget.gadgetbridge.externalevents.gps.GBLocationProvider;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+/**
+ * A location provider that uses the phone GPS, using {@link LocationManager}.
+ */
+public class PhoneLocationProvider extends GBLocationProvider {
+    private static final Logger LOG = LoggerFactory.getLogger(PhoneLocationProvider.class);
+
+    private final String provider;
+
+    private static final int INTERVAL_MIN_DISTANCE = 0;
+
+    public PhoneLocationProvider(final Context context, final LocationListener locationListener, final String provider) {
+        super(context, locationListener);
+        this.provider = provider;
+    }
+
+    @Override
+    public void start(final int interval) {
+        LOG.info("Starting phone gps location provider");
+
+        if (!GB.checkPermission(getContext(), Manifest.permission.ACCESS_FINE_LOCATION) && !GB.checkPermission(getContext(), Manifest.permission.ACCESS_COARSE_LOCATION)) {
+            GB.toast("Location permission not granted", Toast.LENGTH_SHORT, GB.ERROR);
+            return;
+        }
+
+        final LocationManager locationManager = (LocationManager) getContext().getSystemService(Context.LOCATION_SERVICE);
+        locationManager.removeUpdates(getLocationListener());
+        locationManager.requestLocationUpdates(
+                provider,
+                interval > 0 ? interval : 1_000,
+                INTERVAL_MIN_DISTANCE,
+                getLocationListener(),
+                Looper.getMainLooper()
+        );
+
+        final Location lastKnownLocation = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
+        LOG.debug("Last known gps location: {}", lastKnownLocation);
+    }
+
+    @Override
+    public void stop() {
+        LOG.info("Stopping phone gps location provider");
+
+        final LocationManager locationManager = (LocationManager) getContext().getSystemService(Context.LOCATION_SERVICE);
+        locationManager.removeUpdates(getLocationListener());
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/notifications/GoogleMapsNotificationHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/notifications/GoogleMapsNotificationHandler.java
new file mode 100644
index 0000000..9a50712
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/notifications/GoogleMapsNotificationHandler.java
@@ -0,0 +1,1301 @@
+/*  Copyright (C) 2023-2024 Arjan Schrijver, Gordon Williams
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.externalevents.notifications;
+
+import android.app.Notification;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+import android.os.PowerManager;
+import android.service.notification.StatusBarNotification;
+
+import androidx.core.app.NotificationCompat;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.model.NavigationInfoSpec;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+public class GoogleMapsNotificationHandler {
+
+    private static final Logger LOG = LoggerFactory.getLogger(GoogleMapsNotificationHandler.class);
+
+    private boolean shouldSendNavigation = false;
+
+    static class IconType {
+        int[] icon;
+        int iconType;
+
+        public IconType(int iconType, int[] icon) {
+            this.icon = icon;
+            this.iconType = iconType;
+        }
+    }
+    List<IconType> knownImages;
+
+    public GoogleMapsNotificationHandler() {
+        knownImages = new ArrayList<>();
+        knownImages.add(new IconType(NavigationInfoSpec.ACTION_CONTINUE, new int[]{
+                0b00000000000000000000000000000000, // Straight on with gaps
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000001000000000000000,
+                0b00000000000000011100000000000000,
+                0b00000000000000111110000000000000,
+                0b00000000000011111111000000000000,
+                0b00000000000111111111100000000000,
+                0b00000000001111111111110000000000,
+                0b00000000011111111111111000000000,
+                0b00000000111110111101111100000000,
+                0b00000000111100111100111100000000,
+                0b00000000011000111100011100000000,
+                0b00000000000000111100000000000000,
+                0b00000000000000111100000000000000,
+                0b00000000000000111100000000000000,
+                0b00000000000000111100000000000000,
+                0b00000000000000111100000000000000,
+                0b00000000000000111100000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000111100000000000000,
+                0b00000000000000111100000000000000,
+                0b00000000000000111100000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000111100000000000000,
+                0b00000000000000111100000000000000,
+                0b00000000000000111100000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+        }));
+        knownImages.add(new IconType(NavigationInfoSpec.ACTION_CONTINUE, new int[]{
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000001000000000000000,
+                0b00000000000000011100000000000000,
+                0b00000000000000111110000000000000,
+                0b00000000000011111111000000000000,
+                0b00000000000111111111100000000000,
+                0b00000000001111111111110000000000,
+                0b00000000011111111111111000000000,
+                0b00000000111110111101111100000000,
+                0b00000000111100111100111100000000,
+                0b00000000011000111100011100000000,
+                0b00000000000000111100000000000000,
+                0b00000000000000111100000000000000,
+                0b00000000000000111100000000000000,
+                0b00000000000000111100000000000000,
+                0b00000000000000111100000000000000,
+                0b00000000000000111100000000000000,
+                0b00000000000000111100000000000000,
+                0b00000000000000111100000000000000,
+                0b00000000000000111100000000000000,
+                0b00000000000000111100000000000000,
+                0b00000000000000111100000000000000,
+                0b00000000000000111100000000000000,
+                0b00000000000000111100000000000000,
+                0b00000000000000111100000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000
+        }));
+        knownImages.add(new IconType(NavigationInfoSpec.ACTION_TURN_LEFT, new int[]{
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000001100000000000000000000,
+                0b00000000011110000000000000000000,
+                0b00000000111110000000000000000000,
+                0b00000001111100000000000000000000,
+                0b00000011111000000000000000000000,
+                0b00000111110000000000000000000000,
+                0b00001111111111111111111110000000,
+                0b00011111111111111111111111100000,
+                0b00001111111111111111111111110000,
+                0b00000111100000000000000111111000,
+                0b00000011110000000000000001111000,
+                0b00000011111000000000000000111000,
+                0b00000001111100000000000000111000,
+                0b00000000111110000000000000111000,
+                0b00000000011110000000000000111000,
+                0b00000000000100000000000000111000,
+                0b00000000000000000000000000111000,
+                0b00000000000000000000000000111000,
+                0b00000000000000000000000000111000,
+                0b00000000000000000000000000111000,
+                0b00000000000000000000000000111000,
+                0b00000000000000000000000000111000,
+                0b00000000000000000000000000111000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000
+        }));
+        knownImages.add(new IconType(NavigationInfoSpec.ACTION_TURN_RIGHT, new int[]{
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000110000000000,
+                0b00000000000000000001111000000000,
+                0b00000000000000000001111100000000,
+                0b00000000000000000000111110000000,
+                0b00000000000000000000011111000000,
+                0b00000000000000000000001111100000,
+                0b00000001111111111111111111110000,
+                0b00000111111111111111111111111000,
+                0b00001111111111111111111111111000,
+                0b00001111100000000000000111110000,
+                0b00011110000000000000001111100000,
+                0b00011100000000000000011111000000,
+                0b00011100000000000000111110000000,
+                0b00011100000000000001111100000000,
+                0b00011100000000000001111000000000,
+                0b00011100000000000000100000000000,
+                0b00011100000000000000000000000000,
+                0b00011100000000000000000000000000,
+                0b00011100000000000000000000000000,
+                0b00011100000000000000000000000000,
+                0b00011100000000000000000000000000,
+                0b00011100000000000000000000000000,
+                0b00011100000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000
+        }));
+        knownImages.add(new IconType(NavigationInfoSpec.ACTION_TURN_LEFT_SLIGHTLY, new int[]{
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000111111111111000000000000,
+                0b00000000111111111111000000000000,
+                0b00000000111111111111000000000000,
+                0b00000000111111000000000000000000,
+                0b00000000111111100000000000000000,
+                0b00000000111111110000000000000000,
+                0b00000000111011111000000000000000,
+                0b00000000111001111100000000000000,
+                0b00000000111000111110000000000000,
+                0b00000000111000011111000000000000,
+                0b00000000111000001111100000000000,
+                0b00000000111000000111110000000000,
+                0b00000000000000000011111000000000,
+                0b00000000000000000001111000000000,
+                0b00000000000000000000111100000000,
+                0b00000000000000000000011100000000,
+                0b00000000000000000000011110000000,
+                0b00000000000000000000011110000000,
+                0b00000000000000000000001110000000,
+                0b00000000000000000000001110000000,
+                0b00000000000000000000001110000000,
+                0b00000000000000000000001110000000,
+                0b00000000000000000000001110000000,
+                0b00000000000000000000001110000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000
+        }));
+        knownImages.add(new IconType(NavigationInfoSpec.ACTION_TURN_LEFT_SLIGHTLY, new int[]{
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000011111111111000000000000000,
+                0b00000111111111111000000000000000,
+                0b00000111111111111000000000000000,
+                0b00000111111100000000000000000000,
+                0b00000111111100000000000000000000,
+                0b00000111111110000000000000000000,
+                0b00000111111111000000000000000000,
+                0b00000111101111100000000000000000,
+                0b00000111100011110000000000000000,
+                0b00000111100011111000000000000000,
+                0b00000111100001111100000000000000,
+                0b00000011000000111110000000000000,
+                0b00000000000000011110000000000000,
+                0b00000000000000011111000000000000,
+                0b00000000000000001111000000000000,
+                0b00000000000000001111000000000000,
+                0b00000000000000001111000000000000,
+                0b00000000000000001111000000000000,
+                0b00000000000000001111000000000000,
+                0b00000000000000001111000000000000,
+                0b00000000000000001111000000000000,
+                0b00000000000000001111000000000000,
+                0b00000000000000001111000000000000,
+                0b00000000000000001111000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000
+        }));
+        knownImages.add(new IconType(NavigationInfoSpec.ACTION_TURN_LEFT_SLIGHTLY, new int[]{
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000111111111111000000000000000,
+                0b00000111111111111000000000000000,
+                0b00000111111111111000000000000000,
+                0b00000111111000000000000000000000,
+                0b00000111111100000000000000000000,
+                0b00000111111110000000000000000000,
+                0b00000111111111000000000000000000,
+                0b00000111101111100000000000000000,
+                0b00000111100111110000000000000000,
+                0b00000111100011111000000000000000,
+                0b00000111100001111100000000000000,
+                0b00000011000000111100000000000000,
+                0b00000000000000011110000000000000,
+                0b00000000000000011110000000000000,
+                0b00000000000000001111000000000000,
+                0b00000000000000001111000000000000,
+                0b00000000000000001111000000000000,
+                0b00000000000000001111000000000000,
+                0b00000000000000001111000000000000,
+                0b00000000000000001111000000000000,
+                0b00000000000000001111000000000000,
+                0b00000000000000001111000000000000,
+                0b00000000000000001111000000000000,
+                0b00000000000000001111000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000
+        }));
+        knownImages.add(new IconType(NavigationInfoSpec.ACTION_TURN_RIGHT_SLIGHTLY, new int[]{
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000011111111111100000000,
+                0b00000000000011111111111100000000,
+                0b00000000000011111111111100000000,
+                0b00000000000000000011111100000000,
+                0b00000000000000000111111100000000,
+                0b00000000000000001111111100000000,
+                0b00000000000000011111011100000000,
+                0b00000000000000111110011100000000,
+                0b00000000000001111100011100000000,
+                0b00000000000011111000011100000000,
+                0b00000000000111110000011100000000,
+                0b00000000001111100000011100000000,
+                0b00000000011111000000000000000000,
+                0b00000000011110000000000000000000,
+                0b00000000111100000000000000000000,
+                0b00000000111000000000000000000000,
+                0b00000001111000000000000000000000,
+                0b00000001111000000000000000000000,
+                0b00000001110000000000000000000000,
+                0b00000001110000000000000000000000,
+                0b00000001110000000000000000000000,
+                0b00000001110000000000000000000000,
+                0b00000001110000000000000000000000,
+                0b00000001110000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000
+        }));
+        knownImages.add(new IconType(NavigationInfoSpec.ACTION_TURN_RIGHT_SLIGHTLY, new int[]{
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000111110000000000,
+                0b00000000000000000111111000000000,
+                0b00000000000000000011111110000000,
+                0b00000000000000000000111111100000,
+                0b00000000000000000000011111110000,
+                0b00000000000000000001111111110000,
+                0b00000000000000001111111111110000,
+                0b00000000000000111111111111100000,
+                0b00000000000011111111101111100000,
+                0b00000000000111111100001111000000,
+                0b00000000011111100000001110000000,
+                0b00000011111110000000011110000000,
+                0b00000011111100000000111100000000,
+                0b00000011111000000000011100000000,
+                0b00000011111000000000001000000000,
+                0b00000011110000000000000000000000,
+                0b00000011100000000000000000000000,
+                0b00000111100000000000000000000000,
+                0b00000111100000000000000000000000,
+                0b00000111100000000000000000000000,
+                0b00000111100000000000000000000000,
+                0b00000111100000000000000000000000,
+                0b00000111100000000000000000000000,
+                0b00000111100000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000
+        }));
+        knownImages.add(new IconType(NavigationInfoSpec.ACTION_TURN_RIGHT_SLIGHTLY, new int[]{
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000011111111111100000,
+                0b00000000000000011111111111100000,
+                0b00000000000000011111111111100000,
+                0b00000000000000000000011111100000,
+                0b00000000000000000000111111100000,
+                0b00000000000000000001111111100000,
+                0b00000000000000000011111111100000,
+                0b00000000000000000111110111100000,
+                0b00000000000000001111100111100000,
+                0b00000000000000011111000111100000,
+                0b00000000000000111110000111100000,
+                0b00000000000000111100000011000000,
+                0b00000000000001111000000000000000,
+                0b00000000000001111000000000000000,
+                0b00000000000011110000000000000000,
+                0b00000000000011110000000000000000,
+                0b00000000000011110000000000000000,
+                0b00000000000011110000000000000000,
+                0b00000000000011110000000000000000,
+                0b00000000000011110000000000000000,
+                0b00000000000011110000000000000000,
+                0b00000000000011110000000000000000,
+                0b00000000000011110000000000000000,
+                0b00000000000011110000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000
+        }));
+        knownImages.add(new IconType(NavigationInfoSpec.ACTION_TURN_RIGHT_SLIGHTLY, new int[]{
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000011000000000000,
+                0b00000000000000000011100000000000,
+                0b00000000000000000111111000000000,
+                0b00000000000000000011111110000000,
+                0b00000000000000000000111111100000,
+                0b00000000000000000000001111110000,
+                0b00000000000000000000111111110000,
+                0b00000000000000001111111111110000,
+                0b00000000000000111111111111100000,
+                0b00000000000011111111110111100000,
+                0b00000000000111111100001111000000,
+                0b00000000001111100000001111000000,
+                0b00000000111110000000011110000000,
+                0b00000001111100000000011100000000,
+                0b00000001111000000000111100000000,
+                0b00000011111000000000001000000000,
+                0b00000011110000000000000000000000,
+                0b00000011110000000000000000000000,
+                0b00000111100000000000000000000000,
+                0b00000111100000000000000000000000,
+                0b00000111100000000000000000000000,
+                0b00000111100000000000000000000000,
+                0b00000111100000000000000000000000,
+                0b00000111100000000000000000000000,
+                0b00000111100000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000
+        }));
+        knownImages.add(new IconType(NavigationInfoSpec.ACTION_TURN_RIGHT_SLIGHTLY, new int[]{
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000011111111111100000,
+                0b00000000000000011111111111100000,
+                0b00000000000000011111111111100000,
+                0b00000000000000000000011111100000,
+                0b00000000000000000000111111100000,
+                0b00000000000000000001111111100000,
+                0b00000000000000000011111111100000,
+                0b00000000000000000111110111100000,
+                0b00000000000000001111100111100000,
+                0b00000000000000011111000111100000,
+                0b00000000000000111110000111100000,
+                0b00000000000000111100000011000000,
+                0b00000000000001111000000000000000,
+                0b00000000000001111000000000000000,
+                0b00000000000011110000000000000000,
+                0b00000000000011110000000000000000,
+                0b00000000000011110000000000000000,
+                0b00000000000011110000000000000000,
+                0b00000000000011110000000000000000,
+                0b00000000000011110000000000000000,
+                0b00000000000011110000000000000000,
+                0b00000000000011110000000000000000,
+                0b00000000000011110000000000000000,
+                0b00000000000011110000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000
+        }));
+        knownImages.add(new IconType(NavigationInfoSpec.ACTION_UTURN_LEFT, new int[]{
+                0b00000000000000000000000000000000, // LHD
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000001111110000000000,
+                0b00000000000000111111111100000000,
+                0b00000000000001111111111110000000,
+                0b00000000000011111000111111000000,
+                0b00000000000111100000001111100000,
+                0b00000000001111000000000111100000,
+                0b00000000001111000000000011110000,
+                0b00000000001110000000000011110000,
+                0b00000000001110000000000001110000,
+                0b00000000001110000000000001110000,
+                0b00000000001110000000000001110000,
+                0b00001100001110001100000001110000,
+                0b00011110001110011110000001110000,
+                0b00001111001110111110000001110000,
+                0b00001111101111111100000001110000,
+                0b00000111111111111000000001110000,
+                0b00000011111111110000000001110000,
+                0b00000001111111100000000001110000,
+                0b00000000111111000000000001110000,
+                0b00000000001110000000000001110000,
+                0b00000000001100000000000001110000,
+                0b00000000000000000000000001110000,
+                0b00000000000000000000000001110000,
+                0b00000000000000000000000001110000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000
+        }));
+        knownImages.add(new IconType(NavigationInfoSpec.ACTION_UTURN_RIGHT, new int[]{
+                0b00000000000000000000000000000000, // RHD
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000001111110000000000000000,
+                0b00000000111111111100000000000000,
+                0b00000001111111111110000000000000,
+                0b00000011111100011111000000000000,
+                0b00000111110000000111100000000000,
+                0b00000111100000000011110000000000,
+                0b00001111000000000011110000000000,
+                0b00001111000000000001110000000000,
+                0b00001110000000000001110000000000,
+                0b00001110000000000001110000000000,
+                0b00001110000000000001110000000000,
+                0b00001110000000110001110000110000,
+                0b00001110000001111001110001111000,
+                0b00001110000001111101110011110000,
+                0b00001110000000111111110111110000,
+                0b00001110000000011111111111100000,
+                0b00001110000000001111111111000000,
+                0b00001110000000000111111110000000,
+                0b00001110000000000011111100000000,
+                0b00001110000000000001110000000000,
+                0b00001110000000000000110000000000,
+                0b00001110000000000000000000000000,
+                0b00001110000000000000000000000000,
+                0b00001110000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000
+        }));
+        knownImages.add(new IconType(NavigationInfoSpec.ACTION_KEEP_LEFT, new int[]{
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000001000000000000000,
+                0b00000000000000011100000000000000,
+                0b00000000000001111110000000000000,
+                0b00000000000001111111000000000000,
+                0b00000000000011111111100000000000,
+                0b00000000001111111111110000000000,
+                0b00000000011111011111111000000000,
+                0b00000000011110011101111100000000,
+                0b00000000111100011100111100000000,
+                0b00000000011000011100011000000000,
+                0b00000000000000011100000000000000,
+                0b00000000000000011100000000000000,
+                0b00000000000000011110000000000000,
+                0b00000000000000011110000000000000,
+                0b00000000000000011111000000000000,
+                0b00000000000000001111100000000000,
+                0b00000000000000000111110000000000,
+                0b00000000000000000011111000000000,
+                0b00000000000000000001111000000000,
+                0b00000000000000000000111100000000,
+                0b00000000000000000000011100000000,
+                0b00000000000000000000011110000000,
+                0b00000000000000000000001110000000,
+                0b00000000000000000000001110000000,
+                0b00000000000000000000001110000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+        }));
+        knownImages.add(new IconType(NavigationInfoSpec.ACTION_KEEP_RIGHT, new int[]{
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000010000000000000000,
+                0b00000000000000111000000000000000,
+                0b00000000000001111110000000000000,
+                0b00000000000011111110000000000000,
+                0b00000000000111111111000000000000,
+                0b00000000001111111111110000000000,
+                0b00000000011111111011111000000000,
+                0b00000000111110111001111000000000,
+                0b00000000111100111000111100000000,
+                0b00000000011000111000011000000000,
+                0b00000000000000111000000000000000,
+                0b00000000000000111000000000000000,
+                0b00000000000001111000000000000000,
+                0b00000000000001111000000000000000,
+                0b00000000000011111000000000000000,
+                0b00000000000111110000000000000000,
+                0b00000000001111100000000000000000,
+                0b00000000011111000000000000000000,
+                0b00000000011110000000000000000000,
+                0b00000000111100000000000000000000,
+                0b00000000111000000000000000000000,
+                0b00000001111000000000000000000000,
+                0b00000001110000000000000000000000,
+                0b00000001110000000000000000000000,
+                0b00000001110000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+        }));
+
+        knownImages.add(new IconType(NavigationInfoSpec.ACTION_FINISH, new int[]{
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000011111111000000000000,
+                0b00000000001111111111110000000000,
+                0b00000000011111000011111000000000,
+                0b00000000111100000000111100000000,
+                0b00000001111000000000011110000000,
+                0b00000001110000000000001110000000,
+                0b00000011100000000000000111000000,
+                0b00000011100000000000000111000000,
+                0b00000011000000111100000011000000,
+                0b00000111000001111110000011100000,
+                0b00000111000001111110000011100000,
+                0b00000111000000111100000011100000,
+                0b00000111000000111100000011100000,
+                0b00000011000000000000000011000000,
+                0b00000011100000000000000111000000,
+                0b00000011100000000000000111000000,
+                0b00000001110000000000001110000000,
+                0b00000000111000000000011100000000,
+                0b00000000111100000000111100000000,
+                0b00000000011100000000111000000000,
+                0b00000000001110000001110000000000,
+                0b00000000000111000011100000000000,
+                0b00000000000011111111000000000000,
+                0b00000000000001111110000000000000,
+                0b00000000000000111100000000000000,
+                0b00000000000000011000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000
+        }));
+        knownImages.add(new IconType(NavigationInfoSpec.ACTION_FINISH, new int[]{
+                0b00000000000000000000000000000000, // RHD
+                0b00000000111000000000000000000000,
+                0b00000111111110000000000000000000,
+                0b00001111111111100000000000000000,
+                0b00011110000011110000000000000000,
+                0b00111100000001110000000000000000,
+                0b00111000000000111000000000000000,
+                0b00110000010000111000000000000000,
+                0b01110000111000011000000000000000,
+                0b01110001111000011000000000000000,
+                0b01110000111000011000000000000000,
+                0b00110000000000111000000000000000,
+                0b00111000000000111000000000000000,
+                0b00111000000001110000000000000000,
+                0b00011100000001110000000000000000,
+                0b00011100000011100011000111100000,
+                0b00001110000011100111000111100000,
+                0b00001111000111000111111111110000,
+                0b00000111001111001111111111110000,
+                0b00000011101110011111111111110000,
+                0b00000011111100011111000111110000,
+                0b00000001111000111111000111111000,
+                0b00000000111000111111000111111000,
+                0b00000000010001111111111111111000,
+                0b00000000000001111111111111111100,
+                0b00000000000001111111000111111100,
+                0b00000000000011111111000111111100,
+                0b00000000000011111111000111111110,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000
+        }));
+        knownImages.add(new IconType(NavigationInfoSpec.ACTION_FINISH, new int[]{
+                0b00000000000000000000000000000000, // LHD
+                0b00000000000000000000011100000000,
+                0b00000000000000000011111111100000,
+                0b00000000000000000111111111110000,
+                0b00000000000000001111000001111000,
+                0b00000000000000001110000000111100,
+                0b00000000000000011100000000011100,
+                0b00000000000000011100001000001100,
+                0b00000000000000011000011100001110,
+                0b00000000000000011000011110001110,
+                0b00000000000000011000011100001110,
+                0b00000000000000011100000000001100,
+                0b00000000000000011100000000011100,
+                0b00000000000000001110000000011100,
+                0b00000000000000001110000000111000,
+                0b00000111100011000111000000111000,
+                0b00000111100011100111000001110000,
+                0b00000111111111100011100011110000,
+                0b00001111111111110011110011100000,
+                0b00001111111111111001110111000000,
+                0b00001111100011111000111110000000,
+                0b00011111100011111100011110000000,
+                0b00011111100011111100011100000000,
+                0b00011111111111111110001000000000,
+                0b00111111111111111110000000000000,
+                0b00111111100011111110000000000000,
+                0b00111111100011111111000000000000,
+                0b01111111100011111111000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000
+        }));
+        knownImages.add(new IconType(NavigationInfoSpec.ACTION_ROUNDABOUT_LEFT, new int[]{
+                0b000000000000000000000000000000000,
+                0b000000000000000000000000000000000,
+                0b000000000000000000000000000000000,
+                0b000000000000000000000000000000000,
+                0b000000001111111111100000000000000,
+                0b000000001111111111110000000000000,
+                0b000000001111111111110000000000000,
+                0b000000001111100000000000000000000,
+                0b000000001111110000000000000000000,
+                0b000000001111111000000000000000000,
+                0b000000001111111100000000000000000,
+                0b000000001110111110000000000000000,
+                0b000000001110011111000000000000000,
+                0b000000001110001111100000000000000,
+                0b000000001110001111000000000000000,
+                0b000000001110011110000000000000000,
+                0b000000000000011110000000000000000,
+                0b000000000000011100000000000000000,
+                0b000000000000011100000000000000000,
+                0b000000000000011110000000000000000,
+                0b000000000000011110000000000000000,
+                0b000000000000001111000000000000000,
+                0b000000000000000111111100000000000,
+                0b000000000000000111111100000000000,
+                0b000000000000000001111100000000000,
+                0b000000000000000000011100000000000,
+                0b000000000000000000011100000000000,
+                0b000000000000000000011100000000000,
+                0b000000000000000000000000000000000,
+                0b000000000000000000000000000000000,
+                0b000000000000000000000000000000000,
+                0b000000000000000000000000000000000
+        }));
+        knownImages.add(new IconType(NavigationInfoSpec.ACTION_ROUNDABOUT_RIGHT, new int[]{
+                0b00000000000000000000000000000000,// GUESSED - this may not be an exact match
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000111111111110000000,
+                0b00000000000001111111111110000000,
+                0b00000000000001111111111110000000,
+                0b00000000000000000000111110000000,
+                0b00000000000000000001111110000000,
+                0b00000000000000000011111110000000,
+                0b00000000000000000111111110000000,
+                0b00000000000000001111101110000000,
+                0b00000000000000011111001110000000,
+                0b00000000000000111110001110000000,
+                0b00000000000000011110001110000000,
+                0b00000000000000001111001110000000,
+                0b00000000000000001111000000000000,
+                0b00000000000000000111000000000000,
+                0b00000000000000000111000000000000,
+                0b00000000000000001111000000000000,
+                0b00000000000000001111000000000000,
+                0b00000000000000011110000000000000,
+                0b00000000000111111100000000000000,
+                0b00000000000111111100000000000000,
+                0b00000000000111110000000000000000,
+                0b00000000000111000000000000000000,
+                0b00000000000111000000000000000000,
+                0b00000000000111000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000
+        }));
+        knownImages.add(new IconType(NavigationInfoSpec.ACTION_ROUNDABOUT_STRAIGHT, new int[]{
+                0b00000000000000000000000000000000,// GUESSED - this may not be an exact match
+                0b00000000000000011000000000000000,
+                0b00000000000000111100000000000000,
+                0b00000000000001111110000000000000,
+                0b00000000000011111111000000000000,
+                0b00000000000111111111100000000000,
+                0b00000000001111111111110000000000,
+                0b00000000011110111101111000000000,
+                0b00000000111100111100111100000000,
+                0b00000000111000111100011100000000,
+                0b00000000000000111100000000000000,
+                0b00000000000011111100000000000000,
+                0b00000000000111111100000000000000,
+                0b00000000001111111100000000000000,
+                0b00000000011111000000000000000000,
+                0b00000000011110000000000000000000,
+                0b00000000011100000000000000000000,
+                0b00000000011100000000000000000000,
+                0b00000000011100000000000000000000,
+                0b00000000011100000000000000000000,
+                0b00000000011110000000000000000000,
+                0b00000000011111000000000000000000,
+                0b00000000001111111100000000000000,
+                0b00000000000111111100000000000000,
+                0b00000000000011111100000000000000,
+                0b00000000000000111100000000000000,
+                0b00000000000000111100000000000000,
+                0b00000000000000111100000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000
+        }));
+        knownImages.add(new IconType(NavigationInfoSpec.ACTION_ROUNDABOUT_STRAIGHT, new int[]{
+                0b00000000000000000000000000000000,// GUESSED - this may not be an exact match
+                0b00000000000000011000000000000000,
+                0b00000000000000111100000000000000,
+                0b00000000000001111110000000000000,
+                0b00000000000011111111000000000000,
+                0b00000000000111111111100000000000,
+                0b00000000001111111111110000000000,
+                0b00000000011110111101111000000000,
+                0b00000000111100111100111100000000,
+                0b00000000111000111100011100000000,
+                0b00000000000000111100000000000000,
+                0b00000000000000111111000000000000,
+                0b00000000000000111111100000000000,
+                0b00000000000000111111110000000000,
+                0b00000000000000000011111000000000,
+                0b00000000000000000001111000000000,
+                0b00000000000000000000111000000000,
+                0b00000000000000000000111000000000,
+                0b00000000000000000000111000000000,
+                0b00000000000000000000111000000000,
+                0b00000000000000000001111000000000,
+                0b00000000000000000011111000000000,
+                0b00000000000000111111110000000000,
+                0b00000000000000111111100000000000,
+                0b00000000000000111111000000000000,
+                0b00000000000000111100000000000000,
+                0b00000000000000111100000000000000,
+                0b00000000000000111100000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000
+        }));
+        knownImages.add(new IconType(NavigationInfoSpec.ACTION_ROUNDABOUT_LEFT, new int[]{
+                0b00000000000000000000000000000000,// GUESSED - this may not be an exact match
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000011000000000000000000000,
+                0b00000000111100000000000000000000,
+                0b00000001111100000000000000000000,
+                0b00000011111000000000000000000000,
+                0b00000111110000000000000000000000,
+                0b00001111100000000000000000000000,
+                0b00011111111111111110000000000000,
+                0b00111111111111111110000000000000,
+                0b00111111111111111111000000000000,
+                0b00011111111111111111000000000000,
+                0b00001111100000001111000000000000,
+                0b00000111110000000111110000000000,
+                0b00000011111000000011111110000000,
+                0b00000001111100000011111111000000,
+                0b00000000111100000000111110000000,
+                0b00000000011000000000001110000000,
+                0b00000000000000000000001110000000,
+                0b00000000000000000000001110000000,
+                0b00000000000000000000001110000000,
+                0b00000000000000000000001110000000,
+                0b00000000000000000000001110000000,
+                0b00000000000000000000001110000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000
+        }));
+        knownImages.add(new IconType(NavigationInfoSpec.ACTION_ROUNDABOUT_RIGHT, new int[]{
+                0b00000000000000000000000000000000,// GUESSED - this may not be an exact match
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000011000000000,
+                0b00000000000000000000111100000000,
+                0b00000000000000000000111110000000,
+                0b00000000000000000000011111000000,
+                0b00000000000000000000001111100000,
+                0b00000000000000000000000111110000,
+                0b00000000000001111111111111111000,
+                0b00000000000001111111111111111100,
+                0b00000000000011111111111111111100,
+                0b00000000000011111111111111111000,
+                0b00000000000011110000000111110000,
+                0b00000000001111100000001111100000,
+                0b00000001111111000000011111000000,
+                0b00000011111111000000111110000000,
+                0b00000001111100000000111100000000,
+                0b00000001110000000000011000000000,
+                0b00000001110000000000000000000000,
+                0b00000001110000000000000000000000,
+                0b00000001110000000000000000000000,
+                0b00000001110000000000000000000000,
+                0b00000001110000000000000000000000,
+                0b00000001110000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000
+        }));
+        knownImages.add(new IconType(NavigationInfoSpec.ACTION_ROUNDABOUT_LEFT, new int[]{
+                0b00000000000000000000000000000000,// GUESSED - this may not be an exact match
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000111111111111000000000000000,
+                0b00000111111111111000000000000000,
+                0b00000111111111111000000000000000,
+                0b00000111111100000000000000000000,
+                0b00000111111100000000000000000000,
+                0b00000111111110000000000000000000,
+                0b00000111111111000000000000000000,
+                0b00000111101111101111111000000000,
+                0b00000111100111111111111100000000,
+                0b00000111100011111111111110000000,
+                0b00000111100001111100011111000000,
+                0b00000011000000110000001111000000,
+                0b00000000000000000000000111100000,
+                0b00000000000000000000000111100000,
+                0b00000000000000000000000111100000,
+                0b00000000000000000000000111100000,
+                0b00000000000000000000001111000000,
+                0b00000000000000000000011111000000,
+                0b00000000000000000111111110000000,
+                0b00000000000000000111111100000000,
+                0b00000000000000000111111000000000,
+                0b00000000000000000011100000000000,
+                0b00000000000000000011100000000000,
+                0b00000000000000000011100000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000
+        }));
+        knownImages.add(new IconType(NavigationInfoSpec.ACTION_ROUNDABOUT_RIGHT, new int[]{
+                0b00000000000000000000000000000000,// GUESSED - this may not be an exact match
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000011111111111100000,
+                0b00000000000000011111111111100000,
+                0b00000000000000011111111111100000,
+                0b00000000000000000000111111100000,
+                0b00000000000000000000111111100000,
+                0b00000000000000000001111111100000,
+                0b00000000000000000011111111100000,
+                0b00000000011111110111110111100000,
+                0b00000000111111111111100111100000,
+                0b00000001111111111111000111100000,
+                0b00000011111000111110000111100000,
+                0b00000011110000001100000011000000,
+                0b00000111100000000000000000000000,
+                0b00000111100000000000000000000000,
+                0b00000111100000000000000000000000,
+                0b00000111100000000000000000000000,
+                0b00000011110000000000000000000000,
+                0b00000011111000000000000000000000,
+                0b00000001111111100000000000000000,
+                0b00000000111111100000000000000000,
+                0b00000000011111100000000000000000,
+                0b00000000000111000000000000000000,
+                0b00000000000111000000000000000000,
+                0b00000000000111000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000
+        }));
+        knownImages.add(new IconType(NavigationInfoSpec.ACTION_ROUNDABOUT_LEFT, new int[]{
+                0b00000000000000000000000000000000,// GUESSED - this may not be an exact match
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000011000000000000000000000,
+                0b00000000111100000000111111100000,
+                0b00000001111100000011111111110000,
+                0b00000011111000000111111111111000,
+                0b00000111110000000111110001111100,
+                0b00001111100000001111000000111100,
+                0b00011111111111111111000000011110,
+                0b00111111111111111110000000011110,
+                0b00111111111111111110000000011110,
+                0b00011111111111111110000000011110,
+                0b00001111100000000000000000111100,
+                0b00000111110000000000000001111100,
+                0b00000011111000000000011111111000,
+                0b00000001111100000000011111110000,
+                0b00000000111100000000011111100000,
+                0b00000000011000000000001110000000,
+                0b00000000000000000000001110000000,
+                0b00000000000000000000001110000000,
+                0b00000000000000000000001110000000,
+                0b00000000000000000000001110000000,
+                0b00000000000000000000001110000000,
+                0b00000000000000000000001110000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000
+        }));
+        knownImages.add(new IconType(NavigationInfoSpec.ACTION_ROUNDABOUT_RIGHT, new int[]{
+                0b00000000000000000000000000000000,// GUESSED - this may not be an exact match
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000011000000000,
+                0b00000111111100000000111100000000,
+                0b00001111111111000000111110000000,
+                0b00011111111111100000011111000000,
+                0b00111110001111100000001111100000,
+                0b00111100000011110000000111110000,
+                0b01111000000011111111111111111000,
+                0b01111000000001111111111111111100,
+                0b01111000000001111111111111111100,
+                0b01111000000001111111111111111000,
+                0b00111100000000000000000111110000,
+                0b00111110000000000000001111100000,
+                0b00011111111000000000011111000000,
+                0b00001111111000000000111110000000,
+                0b00000111111000000000111100000000,
+                0b00000001110000000000011000000000,
+                0b00000001110000000000000000000000,
+                0b00000001110000000000000000000000,
+                0b00000001110000000000000000000000,
+                0b00000001110000000000000000000000,
+                0b00000001110000000000000000000000,
+                0b00000001110000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000
+        }));
+        knownImages.add(new IconType(NavigationInfoSpec.ACTION_ROUNDABOUT_UTURN, new int[]{
+                0b00000000000000000000000000000000, // LHD
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000001111111000000000000,
+                0b00000000000011111111110000000000,
+                0b00000000000111111111110000000000,
+                0b00000000001111100001111000000000,
+                0b00000000001111000000111100000000,
+                0b00000000011110000000111100000000,
+                0b00000000011110000000011100000000,
+                0b00000000011110000000011100000000,
+                0b00000000011110000000111100000000,
+                0b00000000001111000000111100000000,
+                0b00000000001111100001111000000000,
+                0b00000000000111110011110000000000,
+                0b00000000000011110011110000000000,
+                0b00000000000011110010000000000000,
+                0b00000000000011110000000000000000,
+                0b00000001100011110001100000000000,
+                0b00000011110011110011110000000000,
+                0b00000001111011110111110000000000,
+                0b00000000111111111111100000000000,
+                0b00000000011111111111000000000000,
+                0b00000000001111111110100000000000,
+                0b00000000000111111101100000000000,
+                0b00000000000011111011100000000000,
+                0b00000000000001110011100000000000,
+                0b00000000000000100011100000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+        }));
+        knownImages.add(new IconType(NavigationInfoSpec.ACTION_ROUNDABOUT_UTURN, new int[]{
+                0b00000000000000000000000000000000, // RHD
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000011111110000000000000,
+                0b00000000001111111111000000000000,
+                0b00000000001111111111100000000000,
+                0b00000000011110000111110000000000,
+                0b00000000111100000011110000000000,
+                0b00000000111100000001111000000000,
+                0b00000000111000000001111000000000,
+                0b00000000111000000001111000000000,
+                0b00000000111100000001111000000000,
+                0b00000000111100000011110000000000,
+                0b00000000011110000111110000000000,
+                0b00000000001111001111100000000000,
+                0b00000000001111001111000000000000,
+                0b00000000000001001111000000000000,
+                0b00000000000000001111000000000000,
+                0b00000000000110001111000110000000,
+                0b00000000001111001111001111000000,
+                0b00000000001111101111011110000000,
+                0b00000000000111111111111100000000,
+                0b00000000000011111111111000000000,
+                0b00000000000101111111110000000000,
+                0b00000000000110111111100000000000,
+                0b00000000000111011111000000000000,
+                0b00000000000111001110000000000000,
+                0b00000000000111000100000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+        }));
+        knownImages.add(new IconType(NavigationInfoSpec.ACTION_MERGE, new int[]{
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000011000000000000000,
+                0b00000000000000111100000000000000,
+                0b00000000000001111110000000000000,
+                0b00000000000011111111000000000000,
+                0b00000000000111111111100000000000,
+                0b00000000001111111111110000000000,
+                0b00000000011111111111111000000000,
+                0b00000000011111111111111000000000,
+                0b00000000111100111100111100000000,
+                0b00000000011000111100011000000000,
+                0b00000000000000111100000000000000,
+                0b00000000000000111100000000000000,
+                0b00000000000001111110000000000000,
+                0b00000000000001111110000000000000,
+                0b00000000000011111111000000000000,
+                0b00000000000111100111100000000000,
+                0b00000000001111100111110000000000,
+                0b00000000011111000011110000000000,
+                0b00000000011110000001111000000000,
+                0b00000000011100000000111000000000,
+                0b00000000111100000000111100000000,
+                0b00000000111100000000111100000000,
+                0b00000000111000000000011100000000,
+                0b00000000111000000000011100000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000
+        }));
+        knownImages.add(new IconType(NavigationInfoSpec.ACTION_MERGE, new int[]{
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000011000000000000000,
+                0b00000000000000111100000000000000,
+                0b00000000000001111110000000000000,
+                0b00000000000011111111000000000000,
+                0b00000000000111111111100000000000,
+                0b00000000001111111111110000000000,
+                0b00000000011111111111111000000000,
+                0b00000000111110111101111100000000,
+                0b00000000111100111100111100000000,
+                0b00000000011000111100011000000000,
+                0b00000000000000111100000000000000,
+                0b00000000000000111100000000000000,
+                0b00000000000001111110000000000000,
+                0b00000000000001111110000000000000,
+                0b00000000000011111111000000000000,
+                0b00000000000111100111100000000000,
+                0b00000000001111000011110000000000,
+                0b00000000001111000011110000000000,
+                0b00000000011110000001111000000000,
+                0b00000000011100000000111000000000,
+                0b00000000111100000000111100000000,
+                0b00000000111000000000011100000000,
+                0b00000000111000000000011100000000,
+                0b00000000111000000000011100000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000,
+                0b00000000000000000000000000000000
+        }));
+    }
+
+    public boolean handle(Context context, StatusBarNotification sbn) {
+        if (sbn.getPackageName().equals("com.google.android.apps.maps")) {
+            checkShouldSendNavigation(context);
+            if (!shouldSendNavigation) return false;
+            Notification notification = sbn.getNotification();
+            if (!NotificationCompat.getLocalOnly(notification))
+                return false; // ignore non-local notifications
+            
+            String distance = (notification.extras.get("android.title")==null) ? "" :
+                    notification.extras.get("android.title").toString(); // eg 100 yd
+            String instruction = (notification.extras.get("android.text")==null) ? "" :
+                    notification.extras.get("android.text").toString(); // eg: High St towards Blah
+            String navLine = (notification.extras.get("android.subText")==null) ? "" :
+                    notification.extras.get("android.subText").toString().replaceAll("\u00A0", " ");  // eg: 13 min 路 4.6聽mi 路 11:55 ETA
+            String[] navLines = navLine.split("路");
+            for (int i = 0; i < navLines.length; i++) navLines[i] = navLines[i].trim();
+            if (navLines.length < 3) return false; // not in the format we expected - might be 'Shared with you' notification
+            // navLines now has at least 3 elements
+            LOG.info("Navigation: " + instruction + "," + distance + "," + navLines[0] + "," + navLines[1] + "," + navLines[2]);
+            int matchedIcon = -1;
+            // getLargeIcon only works in API 23+ - don't try and get icons on older devices
+            if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
+                Icon icon = notification.getLargeIcon();
+                if (icon != null) {
+                    // We convert the icon to a bitmap, then to x 32x32 1bpp which we'll check against known images
+                    Drawable drawable = icon.loadDrawable(context);
+                    Bitmap bitmap = Bitmap.createBitmap(32, 32, Bitmap.Config.ARGB_8888); // Single color bitmap will be created of 1x1 pixel
+                    Canvas canvas = new Canvas(bitmap);
+                    drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
+                    drawable.draw(canvas);
+                    int[] pixelsRGBA = new int[32 * 32];
+                    bitmap.getPixels(pixelsRGBA, 0, 32, 0, 0, 32, 32);
+                    int[] pixelPack = new int[32]; // pixels packed down 1bpp
+                    char[] pixelStringChars = new char[33 * 32];
+
+                    for (int y = 0; y < 32; y++) {
+                        int pack = 0;
+                        for (int x = 0; x < 32; x++) {
+                            int pixel = ((pixelsRGBA[(y * 32) + x] & 0x80000000) != 0) ? 1 : 0;
+                            pack = (pack << 1) | pixel; // check top bit (=alpha)
+                            pixelStringChars[x + y * 33] = (pixel != 0) ? '1' : '0';
+                        }
+                        pixelStringChars[32 + y * 33] = '\n';
+                        pixelPack[y] = pack;
+                    }
+                    // This is now a printable string showing the image, which we can log if we don't recognise it
+                    String pixelString = new String(pixelStringChars);
+
+                    int bestDiff = 100;
+                    for (int i = 0; i < knownImages.size(); i++) {
+                        IconType knownImage = knownImages.get(i);
+                        int diff = 0;
+                        // Work out how many bits differ over the whole image
+                        for (int j = 0; j < 32; j++)
+                            diff += Integer.bitCount(knownImage.icon[j] ^ pixelPack[j]);
+                        // if it's close enough, match
+                        if (diff < 32 && diff < bestDiff) {
+                            matchedIcon = knownImage.iconType;
+                            bestDiff = diff;
+                        }
+                    }
+                    if (matchedIcon < 0) {
+                        LOG.info("Icon NEW:\n" + pixelString);
+                        knownImages.add(new IconType(255, pixelPack));
+                    }
+                }
+            }
+            NavigationInfoSpec navInfo = new NavigationInfoSpec();
+            if (matchedIcon>=0)
+                navInfo.nextAction = matchedIcon;
+            navInfo.instruction = instruction;
+            if (distance != null)
+                navInfo.distanceToTurn = distance;
+            if (navLines[2].contains("ETA"))
+              navInfo.ETA = navLines[2].replace("ETA","").trim();
+            GBApplication.deviceService().onSetNavigationInfo(navInfo);
+
+            return true;
+        }
+        return false;
+    }
+
+    public boolean handleRemove(StatusBarNotification sbn) {
+        if (sbn.getPackageName().equals("com.google.android.apps.maps")) {
+            if (!shouldSendNavigation) return false;
+            Notification notification = sbn.getNotification();
+            if (!NotificationCompat.getLocalOnly(notification))
+                return false; // ignore non-local notifications
+            NavigationInfoSpec navInfo = new NavigationInfoSpec(); // send a blank one to remove
+            GBApplication.deviceService().onSetNavigationInfo(navInfo);
+            return true;
+        }
+        return false;
+    }
+
+    private void checkShouldSendNavigation(Context context) {
+        Prefs prefs = GBApplication.getPrefs();
+
+        boolean navigationForward = prefs.getBoolean("navigation_forward", true);
+        boolean navigationGMaps = prefs.getBoolean("navigation_app_gmaps", true);
+        if (!navigationForward || !navigationGMaps) {
+            shouldSendNavigation = false;
+            return;
+        }
+
+        boolean navigationScreenOn = prefs.getBoolean("nagivation_screen_on", true);
+        if (!navigationScreenOn) {
+            PowerManager powermanager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+            if (powermanager != null && powermanager.isScreenOn()) {
+                LOG.info("Not forwarding navigation instructions, screen seems to be on and settings do not allow this");
+                shouldSendNavigation = false;
+                return;
+            }
+        }
+
+        shouldSendNavigation = true;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/opentracks/OpenTracksContentObserver.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/opentracks/OpenTracksContentObserver.java
new file mode 100644
index 0000000..f9c60d1
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/opentracks/OpenTracksContentObserver.java
@@ -0,0 +1,100 @@
+/*  Copyright (C) 2022-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+
+package nodomain.freeyourgadget.gadgetbridge.externalevents.opentracks;
+
+import android.app.Activity;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+
+import java.util.List;
+
+
+public class OpenTracksContentObserver extends ContentObserver {
+    private Context mContext;
+    private Uri tracksUri;
+    private int protocolVersion;
+    private int totalTimeMillis;
+    private float totalDistanceMeter;
+
+    private long previousTimeMillis = 0;
+    private float previousDistanceMeter = 0;
+
+    public int getTotalTimeMillis() {
+        return totalTimeMillis;
+    }
+    public float getTotalDistanceMeter() {
+        return totalDistanceMeter;
+    }
+
+    public long getTimeMillisChange() {
+        /**
+         * We don't use the timeMillis received from OpenTracks here, because those updates do not
+         * come in very regularly when GPS reception is bad
+         */
+        long timeMillisDelta = System.currentTimeMillis() - previousTimeMillis;
+        previousTimeMillis = System.currentTimeMillis();
+        return timeMillisDelta;
+    }
+
+    public float getDistanceMeterChange() {
+        float distanceMeterDelta = totalDistanceMeter - previousDistanceMeter;
+        previousDistanceMeter = totalDistanceMeter;
+        return distanceMeterDelta;
+    }
+
+
+    public OpenTracksContentObserver(Context context, final Uri tracksUri, final int protocolVersion) {
+        super(new Handler());
+        this.mContext = context;
+        this.tracksUri = tracksUri;
+        this.protocolVersion = protocolVersion;
+        this.previousTimeMillis = System.currentTimeMillis();
+    }
+
+    @Override
+    public void onChange(final boolean selfChange, final Uri uri) {
+        if (uri == null) {
+            return; // nothing can be done without an uri
+        }
+        if (tracksUri.toString().startsWith(uri.toString())) {
+            final List<Track> tracks = Track.readTracks(mContext.getContentResolver(), tracksUri, protocolVersion);
+            if (!tracks.isEmpty()) {
+                final TrackStatistics statistics = new TrackStatistics(tracks);
+                totalTimeMillis = statistics.getTotalTimeMillis();
+                totalDistanceMeter = statistics.getTotalDistanceMeter();
+            }
+        }
+    }
+
+    public void unregister() {
+        if (mContext != null) {
+            mContext.getContentResolver().unregisterContentObserver(this);
+        }
+    }
+
+    public void finish() {
+        unregister();
+        if (mContext != null) {
+            ((Activity) mContext).finish();
+            mContext = null;
+        }
+    }
+}
+
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/opentracks/OpenTracksController.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/opentracks/OpenTracksController.java
new file mode 100644
index 0000000..eee0ce3
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/opentracks/OpenTracksController.java
@@ -0,0 +1,154 @@
+/*  Copyright (C) 2022-2024 Arjan Schrijver, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+
+package nodomain.freeyourgadget.gadgetbridge.externalevents.opentracks;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.widget.Toast;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+public class OpenTracksController extends Activity {
+    /*
+     * A short explanation of how this integration with OpenTracks works:
+     * Starting the recording from a device requires calling `startRecording()`
+     * on this class. For a simple example, check out the implementation in
+     * `WorkoutRequestHandler`, used by the Fossil HR series.
+     * The OpenTracks class can be set in the Gadgetbridge settings and depends
+     * on the installation source used for OpenTracks. Details can be found in
+     * their documentation here: https://github.com/OpenTracksApp/OpenTracks#api
+     * `startRecording()` sends an explicit Intent to OpenTracks signalling it
+     * to start recording. It passes along the package name and class name of
+     * our `OpenTracksController` which OpenTracks will use to send the 
+     * statistics URIs to. After starting the recording service, OpenTracks
+     * uses a new explicit Intent to start our `OpenTracksController` and passes
+     * along the URIs and the read permissions for those URIs (using
+     * `Intent.FLAG_GRANT_READ_URI_PERMISSION`). So at that point
+     * `OpenTracksController` is started as a new `Activity` (or `Context`)
+     * which has the read permissions for the statistics URIs. The controller
+     * saves its `Context` into the `OpenTracksContentObserver` in the GB main
+     * process, so it can keep running and read the statistics with the correct
+     * `Context`. So, whatever class, device or activity calls the methods on
+     * the `OpenTracksContentObserver` from, it will always work.
+     */
+    private static final String EXTRAS_PROTOCOL_VERSION = "PROTOCOL_VERSION";
+    private static final String ACTION_DASHBOARD = "Intent.OpenTracks-Dashboard";
+    private static final String ACTION_DASHBOARD_PAYLOAD = ACTION_DASHBOARD + ".Payload";
+
+    private static final Logger LOG = LoggerFactory.getLogger(OpenTracksController.class);
+
+    @Override
+    public void onCreate(Bundle bundle) {
+        super.onCreate(bundle);
+        GBApplication gbApp = GBApplication.app();
+        Intent intent = getIntent();
+        int protocolVersion = intent.getIntExtra(EXTRAS_PROTOCOL_VERSION, 1);
+        final ArrayList<Uri> uris = intent.getParcelableArrayListExtra(ACTION_DASHBOARD_PAYLOAD);
+        if (uris != null) {
+            if (gbApp.getOpenTracksObserver() != null) {
+                LOG.info("Unregistering old OpenTracksContentObserver");
+                gbApp.getOpenTracksObserver().unregister();
+            }
+            Uri tracksUri = uris.get(0);
+            LOG.info("Registering OpenTracksContentObserver with tracks URI: " + tracksUri);
+            gbApp.setOpenTracksObserver(new OpenTracksContentObserver(this, tracksUri, protocolVersion));
+            try {
+                getContentResolver().registerContentObserver(tracksUri, false, gbApp.getOpenTracksObserver());
+            } catch (final SecurityException se) {
+                LOG.error("Error registering OpenTracksContentObserver", se);
+            }
+        }
+        moveTaskToBack(true);
+    }
+
+    public static void sendIntent(Context context, String className, String category, String icon) {
+        Prefs prefs = GBApplication.getPrefs();
+        String packageName = prefs.getString("opentracks_packagename", "de.dennisguse.opentracks");
+        Intent intent = new Intent();
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        intent.setClassName(packageName, className);
+        intent.putExtra("STATS_TARGET_PACKAGE", context.getPackageName());
+        intent.putExtra("STATS_TARGET_CLASS", OpenTracksController.class.getName());
+        if (category != null) {
+            intent.putExtra("TRACK_CATEGORY", category);
+        }
+        if (icon != null) {
+            intent.putExtra("TRACK_ICON", icon);
+        }
+        try {
+            context.startActivity(intent);
+        } catch (Exception e) {
+            GB.toast(e.getMessage(), Toast.LENGTH_LONG, GB.WARN);
+        }
+    }
+
+    public static void startRecording(Context context) {
+        sendIntent(context, "de.dennisguse.opentracks.publicapi.StartRecording", null, null);
+    }
+
+    public static void startRecording(Context context, ActivityKind activityKind) {
+        final String category = activityKind.getLabel(context);
+        final String icon;
+        switch (activityKind) {
+            case CYCLING:
+                icon = "BIKE";
+                break;
+            case HIKING:
+            case WALKING:
+                icon = "WALK";
+                break;
+            case RUNNING:
+                icon = "RUN";
+                break;
+            default:
+                LOG.warn("Unmapped activity kind icon for {}", activityKind);
+                icon = null;
+        }
+
+        sendIntent(context, "de.dennisguse.opentracks.publicapi.StartRecording", category, icon);
+    }
+
+    public static void stopRecording(Context context) {
+        sendIntent(context, "de.dennisguse.opentracks.publicapi.StopRecording", null, null);
+        OpenTracksContentObserver openTracksObserver = GBApplication.app().getOpenTracksObserver();
+        if (openTracksObserver != null) {
+            openTracksObserver.finish();
+        }
+        GBApplication.app().setOpenTracksObserver(null);
+    }
+
+    public static void toggleRecording(Context context) {
+        OpenTracksContentObserver openTracksObserver = GBApplication.app().getOpenTracksObserver();
+        if (openTracksObserver == null) {
+            startRecording(context);
+        } else {
+            stopRecording(context);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/opentracks/Track.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/opentracks/Track.java
new file mode 100644
index 0000000..04a932e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/opentracks/Track.java
@@ -0,0 +1,205 @@
+/*  Copyright (C) 2022-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+
+package nodomain.freeyourgadget.gadgetbridge.externalevents.opentracks;
+
+import android.content.ContentResolver;
+import android.database.Cursor;
+import android.net.Uri;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class was copied and modified from
+ * https://github.com/OpenTracksApp/OSMDashboard/blob/main/src/main/java/de/storchp/opentracks/osmplugin/dashboardapi/Track.java
+ */
+class Track {
+    private static final Logger LOG = LoggerFactory.getLogger(Track.class);
+
+    private static final String TAG = Track.class.getSimpleName();
+
+    public static final String _ID = "_id";
+    public static final String NAME = "name"; // track name
+    public static final String DESCRIPTION = "description"; // track description
+    public static final String CATEGORY = "category"; // track activity type
+    public static final String STARTTIME = "starttime"; // track start time
+    public static final String STOPTIME = "stoptime"; // track stop time
+    public static final String TOTALDISTANCE = "totaldistance"; // total distance
+    public static final String TOTALTIME = "totaltime"; // total time
+    public static final String MOVINGTIME = "movingtime"; // moving time
+    public static final String AVGSPEED = "avgspeed"; // average speed
+    public static final String AVGMOVINGSPEED = "avgmovingspeed"; // average moving speed
+    public static final String MAXSPEED = "maxspeed"; // maximum speed
+    public static final String MINELEVATION = "minelevation"; // minimum elevation
+    public static final String MAXELEVATION = "maxelevation"; // maximum elevation
+    public static final String ELEVATIONGAIN = "elevationgain"; // elevation gain
+
+    public static final String[] PROJECTION = {
+            _ID,
+            NAME,
+            DESCRIPTION,
+            CATEGORY,
+            STARTTIME,
+            STOPTIME,
+            TOTALDISTANCE,
+            TOTALTIME,
+            MOVINGTIME,
+            AVGSPEED,
+            AVGMOVINGSPEED,
+            MAXSPEED,
+            MINELEVATION,
+            MAXELEVATION,
+            ELEVATIONGAIN
+    };
+
+    private final long id;
+    private final String trackname;
+    private final String description;
+    private final String category;
+    private final int startTimeEpochMillis;
+    private final int stopTimeEpochMillis;
+    private final float totalDistanceMeter;
+    private final int totalTimeMillis;
+    private final int movingTimeMillis;
+    private final float avgSpeedMeterPerSecond;
+    private final float avgMovingSpeedMeterPerSecond;
+    private final float maxSpeedMeterPerSecond;
+    private final float minElevationMeter;
+    private final float maxElevationMeter;
+    private final float elevationGainMeter;
+
+    public Track(final long id, final String trackname, final String description, final String category, final int startTimeEpochMillis, final int stopTimeEpochMillis, final float totalDistanceMeter, final int totalTimeMillis, final int movingTimeMillis, final float avgSpeedMeterPerSecond, final float avgMovingSpeedMeterPerSecond, final float maxSpeedMeterPerSecond, final float minElevationMeter, final float maxElevationMeter, final float elevationGainMeter) {
+        this.id = id;
+        this.trackname = trackname;
+        this.description = description;
+        this.category = category;
+        this.startTimeEpochMillis = startTimeEpochMillis;
+        this.stopTimeEpochMillis = stopTimeEpochMillis;
+        this.totalDistanceMeter = totalDistanceMeter;
+        this.totalTimeMillis = totalTimeMillis;
+        this.movingTimeMillis = movingTimeMillis;
+        this.avgSpeedMeterPerSecond = avgSpeedMeterPerSecond;
+        this.avgMovingSpeedMeterPerSecond = avgMovingSpeedMeterPerSecond;
+        this.maxSpeedMeterPerSecond = maxSpeedMeterPerSecond;
+        this.minElevationMeter = minElevationMeter;
+        this.maxElevationMeter = maxElevationMeter;
+        this.elevationGainMeter = elevationGainMeter;
+    }
+
+    /**
+     * Reads the Tracks from the Content Uri
+     */
+    public static List<Track> readTracks(final ContentResolver resolver, final Uri data, final int protocolVersion) {
+        LOG.info("Loading track(s) from " + data);
+
+        final ArrayList<Track> tracks = new ArrayList<Track>();
+        try (final Cursor cursor = resolver.query(data, Track.PROJECTION, null, null, null)) {
+            while (cursor.moveToNext()) {
+                final long id = cursor.getLong(cursor.getColumnIndexOrThrow(Track._ID));
+                final String trackname = cursor.getString(cursor.getColumnIndexOrThrow(Track.NAME));
+                final String description = cursor.getString(cursor.getColumnIndexOrThrow(Track.DESCRIPTION));
+                final String category = cursor.getString(cursor.getColumnIndexOrThrow(Track.CATEGORY));
+                final int startTimeEpochMillis = cursor.getInt(cursor.getColumnIndexOrThrow(Track.STARTTIME));
+                final int stopTimeEpochMillis = cursor.getInt(cursor.getColumnIndexOrThrow(Track.STOPTIME));
+                final float totalDistanceMeter = cursor.getFloat(cursor.getColumnIndexOrThrow(Track.TOTALDISTANCE));
+                final int totalTimeMillis = cursor.getInt(cursor.getColumnIndexOrThrow(Track.TOTALTIME));
+                final int movingTimeMillis = cursor.getInt(cursor.getColumnIndexOrThrow(Track.MOVINGTIME));
+                final float avgSpeedMeterPerSecond = cursor.getFloat(cursor.getColumnIndexOrThrow(Track.AVGSPEED));
+                final float avgMovingSpeedMeterPerSecond = cursor.getFloat(cursor.getColumnIndexOrThrow(Track.AVGMOVINGSPEED));
+                final float maxSpeedMeterPerSecond = cursor.getFloat(cursor.getColumnIndexOrThrow(Track.MAXSPEED));
+                final float minElevationMeter = cursor.getFloat(cursor.getColumnIndexOrThrow(Track.MINELEVATION));
+                final float maxElevationMeter = cursor.getFloat(cursor.getColumnIndexOrThrow(Track.MAXELEVATION));
+                final float elevationGainMeter = cursor.getFloat(cursor.getColumnIndexOrThrow(Track.ELEVATIONGAIN));
+
+                LOG.info("New Track data received: distance=" + totalDistanceMeter + " time=" + totalTimeMillis);
+
+                tracks.add(new Track(id, trackname, description, category, startTimeEpochMillis, stopTimeEpochMillis,
+                        totalDistanceMeter, totalTimeMillis, movingTimeMillis, avgSpeedMeterPerSecond, avgMovingSpeedMeterPerSecond, maxSpeedMeterPerSecond,
+                        minElevationMeter, maxElevationMeter, elevationGainMeter));
+            }
+        } catch (final SecurityException e) {
+            LOG.warn("No permission to read track", e);
+        } catch (final Exception e) {
+            LOG.warn("Reading track failed", e);
+        }
+        return tracks;
+    }
+
+    public float getElevationGainMeter() {
+        return elevationGainMeter;
+    }
+
+    public float getMaxElevationMeter() {
+        return maxElevationMeter;
+    }
+
+    public float getMinElevationMeter() {
+        return minElevationMeter;
+    }
+
+    public float getMaxSpeedMeterPerSecond() {
+        return maxSpeedMeterPerSecond;
+    }
+
+    public float getAvgMovingSpeedMeterPerSecond() {
+        return avgMovingSpeedMeterPerSecond;
+    }
+
+    public float getAvgSpeedMeterPerSecond() {
+        return avgSpeedMeterPerSecond;
+    }
+
+    public int getMovingTimeMillis() {
+        return movingTimeMillis;
+    }
+
+    public int getTotalTimeMillis() {
+        return totalTimeMillis;
+    }
+
+    public float getTotalDistanceMeter() {
+        return totalDistanceMeter;
+    }
+
+    public int getStopTimeEpochMillis() {
+        return stopTimeEpochMillis;
+    }
+
+    public int getStartTimeEpochMillis() {
+        return startTimeEpochMillis;
+    }
+
+    public String getCategory() {
+        return category;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public String getTrackname() {
+        return trackname;
+    }
+
+    public long getId() {
+        return id;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/opentracks/TrackStatistics.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/opentracks/TrackStatistics.java
new file mode 100644
index 0000000..efd5fc2
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/opentracks/TrackStatistics.java
@@ -0,0 +1,130 @@
+/*  Copyright (C) 2022-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+
+package nodomain.freeyourgadget.gadgetbridge.externalevents.opentracks;
+
+import java.util.List;
+
+/**
+ * This class was copied and modified from
+ * https://github.com/OpenTracksApp/OSMDashboard/blob/main/src/main/java/de/storchp/opentracks/osmplugin/utils/TrackStatistics.java
+ */
+class TrackStatistics {
+    private String category = "unknown";
+    private int startTimeEpochMillis;
+    private int stopTimeEpochMillis;
+    private float totalDistanceMeter;
+    private int totalTimeMillis;
+    private int movingTimeMillis;
+    private float avgSpeedMeterPerSecond;
+    private float avgMovingSpeedMeterPerSecond;
+    private float maxSpeedMeterPerSecond;
+    private float minElevationMeter;
+    private float maxElevationMeter;
+    private float elevationGainMeter;
+
+    public TrackStatistics(final List<Track> tracks) {
+        if (tracks.isEmpty()) {
+            return;
+        }
+        final Track first = tracks.get(0);
+        category = first.getCategory();
+        startTimeEpochMillis = first.getStartTimeEpochMillis();
+        stopTimeEpochMillis = first.getStopTimeEpochMillis();
+        totalDistanceMeter = first.getTotalDistanceMeter();
+        totalTimeMillis = first.getTotalTimeMillis();
+        movingTimeMillis = first.getMovingTimeMillis();
+        avgSpeedMeterPerSecond = first.getAvgSpeedMeterPerSecond();
+        avgMovingSpeedMeterPerSecond = first.getAvgMovingSpeedMeterPerSecond();
+        maxSpeedMeterPerSecond = first.getMaxSpeedMeterPerSecond();
+        minElevationMeter = first.getMinElevationMeter();
+        maxElevationMeter = first.getMaxElevationMeter();
+        elevationGainMeter = first.getElevationGainMeter();
+
+        if (tracks.size() > 1) {
+            float totalAvgSpeedMeterPerSecond = avgSpeedMeterPerSecond;
+            float totalAvgMovingSpeedMeterPerSecond = avgMovingSpeedMeterPerSecond;
+            for (final Track track : tracks.subList(1, tracks.size())) {
+                if (!category.equals(track.getCategory())) {
+                    category = "mixed";
+                }
+                startTimeEpochMillis = Math.min(startTimeEpochMillis, track.getStartTimeEpochMillis());
+                stopTimeEpochMillis = Math.max(stopTimeEpochMillis, track.getStopTimeEpochMillis());
+                totalDistanceMeter += track.getTotalDistanceMeter();
+                totalTimeMillis += track.getTotalTimeMillis();
+                movingTimeMillis += track.getMovingTimeMillis();
+                totalAvgSpeedMeterPerSecond += track.getAvgSpeedMeterPerSecond();
+                totalAvgMovingSpeedMeterPerSecond += track.getAvgMovingSpeedMeterPerSecond();
+                maxSpeedMeterPerSecond = Math.max(maxSpeedMeterPerSecond, track.getMaxSpeedMeterPerSecond());
+                minElevationMeter = Math.min(minElevationMeter, track.getMinElevationMeter());
+                maxElevationMeter = Math.max(maxElevationMeter, track.getMaxElevationMeter());
+                elevationGainMeter += track.getElevationGainMeter();
+            }
+
+            avgSpeedMeterPerSecond = totalAvgSpeedMeterPerSecond / tracks.size();
+            avgMovingSpeedMeterPerSecond = totalAvgMovingSpeedMeterPerSecond / tracks.size();
+        }
+    }
+
+    public String getCategory() {
+        return category;
+    }
+
+    public int getStartTimeEpochMillis() {
+        return startTimeEpochMillis;
+    }
+
+    public int getStopTimeEpochMillis() {
+        return stopTimeEpochMillis;
+    }
+
+    public float getTotalDistanceMeter() {
+        return totalDistanceMeter;
+    }
+
+    public int getTotalTimeMillis() {
+        return totalTimeMillis;
+    }
+
+    public int getMovingTimeMillis() {
+        return movingTimeMillis;
+    }
+
+    public float getAvgSpeedMeterPerSecond() {
+        return avgSpeedMeterPerSecond;
+    }
+
+    public float getAvgMovingSpeedMeterPerSecond() {
+        return avgMovingSpeedMeterPerSecond;
+    }
+
+    public float getMaxSpeedMeterPerSecond() {
+        return maxSpeedMeterPerSecond;
+    }
+
+    public float getMinElevationMeter() {
+        return minElevationMeter;
+    }
+
+    public float getMaxElevationMeter() {
+        return maxElevationMeter;
+    }
+
+    public float getElevationGainMeter() {
+        return elevationGainMeter;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/sleepasandroid/SleepAsAndroidAction.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/sleepasandroid/SleepAsAndroidAction.java
new file mode 100644
index 0000000..b63daf4
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/sleepasandroid/SleepAsAndroidAction.java
@@ -0,0 +1,16 @@
+package nodomain.freeyourgadget.gadgetbridge.externalevents.sleepasandroid;
+
+public class SleepAsAndroidAction {
+    public static final String START_TRACKING = "com.urbandroid.sleep.watch.START_TRACKING";
+    public static final String STOP_TRACKING = "com.urbandroid.sleep.watch.STOP_TRACKING";
+    public static final String SET_PAUSE = "com.urbandroid.sleep.watch.SET_PAUSE";
+    public static final String SET_SUSPENDED = "com.urbandroid.sleep.watch.SET_SUSPENDED";
+    public static final String SET_BATCH_SIZE = "com.urbandroid.sleep.watch.SET_BATCH_SIZE";
+    public static final String START_ALARM = "com.urbandroid.sleep.watch.START_ALARM";
+    public static final String STOP_ALARM = "com.urbandroid.sleep.watch.STOP_ALARM";
+    public static final String UPDATE_ALARM = "com.urbandroid.sleep.watch.UPDATE_ALARM";
+    public static final String SHOW_NOTIFICATION = "com.urbandroid.sleep.watch.SHOW_NOTIFICATION";
+    public static final String HINT = "com.urbandroid.sleep.watch.HINT";
+    public static final String CHECK_CONNECTED = "com.urbandroid.sleep.watch.CHECK_CONNECTED";
+    public static final String CONFIRM_CONNECTED = "com.urbandroid.sleep.watch.CONFIRM_CONNECTED";
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/sleepasandroid/SleepAsAndroidReceiver.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/sleepasandroid/SleepAsAndroidReceiver.java
new file mode 100644
index 0000000..61974d1
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/sleepasandroid/SleepAsAndroidReceiver.java
@@ -0,0 +1,23 @@
+package nodomain.freeyourgadget.gadgetbridge.externalevents.sleepasandroid;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
+
+public class SleepAsAndroidReceiver extends BroadcastReceiver {
+    private static final Logger LOG = LoggerFactory.getLogger(SleepAsAndroidReceiver.class);
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        String action = intent.getAction();
+        if (GBApplication.getPrefs().getBoolean("pref_key_sleepasandroid_enable", false)) {
+            GBApplication.deviceService().onSleepAsAndroidAction(action, intent.getExtras());
+        }
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDevice.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDevice.java
new file mode 100644
index 0000000..78dfba1
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDevice.java
@@ -0,0 +1,754 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, Arjan Schrijver, Carsten
+    Pfeiffer, Daniel Dakhno, Daniele Gobbetti, Jos茅 Rebelo, Petr Van臎k, Taavi
+    Eom盲e, Uwe Hermann
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.impl;
+
+import static nodomain.freeyourgadget.gadgetbridge.model.BatteryState.UNKNOWN;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.model.BatteryState;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.model.GenericItem;
+import nodomain.freeyourgadget.gadgetbridge.model.ItemWithDetails;
+
+public class GBDevice implements Parcelable {
+    public static final String ACTION_DEVICE_CHANGED
+            = "nodomain.freeyourgadget.gadgetbridge.gbdevice.action.device_changed";
+    public static final Creator<GBDevice> CREATOR = new Creator<GBDevice>() {
+        @Override
+        public GBDevice createFromParcel(Parcel source) {
+            return new GBDevice(source);
+        }
+
+        @Override
+        public GBDevice[] newArray(int size) {
+            return new GBDevice[size];
+        }
+    };
+    private static final Logger LOG = LoggerFactory.getLogger(GBDevice.class);
+    public static final short RSSI_UNKNOWN = 0;
+    public static final short BATTERY_UNKNOWN = -1;
+    public static final short BATTERY_ICON_DEFAULT = -1;
+    public static final short BATTERY_LABEL_DEFAULT = -1;
+    public static final String EXTRA_DEVICE = "device";
+    public static final String EXTRA_UUID = "extraUUID";
+    public static final String EXTRA_UPDATE_SUBJECT = "EXTRA_UPDATE_SUBJECT";
+    private static final String DEVINFO_HW_VER = "HW: ";
+    private static final String DEVINFO_FW_VER = "FW: ";
+    private static final String DEVINFO_FW2_VER = "FW2: ";
+    private static final String DEVINFO_ADDR = "ADDR: ";
+    private static final String DEVINFO_ADDR2 = "ADDR2: ";
+    public static final String BATTERY_INDEX = "battery_index";
+    private String mName;
+    private String mAlias;
+    private String parentFolder;
+    private final String mAddress;
+    private String mVolatileAddress;
+    private final DeviceType mDeviceType;
+    private String mFirmwareVersion;
+    private String mFirmwareVersion2;
+    private String mModel;
+    private State mState = State.NOT_CONNECTED;
+
+    // multiple battery support: at this point we support up to three batteries
+    private int[] mBatteryLevel = {BATTERY_UNKNOWN, BATTERY_UNKNOWN, BATTERY_UNKNOWN};
+    private float[] mBatteryVoltage = {BATTERY_UNKNOWN, BATTERY_UNKNOWN, BATTERY_UNKNOWN};
+    private BatteryState[] mBatteryState = {UNKNOWN, UNKNOWN, UNKNOWN};
+    private int[] mBatteryIcons = {BATTERY_ICON_DEFAULT, BATTERY_ICON_DEFAULT, BATTERY_ICON_DEFAULT};
+    private int[] mBatteryLabels = {BATTERY_LABEL_DEFAULT, BATTERY_LABEL_DEFAULT, BATTERY_LABEL_DEFAULT};
+
+    private short mRssi = RSSI_UNKNOWN;
+    private String mBusyTask;
+    private List<ItemWithDetails> mDeviceInfos;
+    private HashMap<String, Object> mExtraInfos;
+
+    private int mNotificationIconConnected = R.drawable.ic_notification;
+    private int mNotificationIconDisconnected = R.drawable.ic_notification_disconnected;
+    private int mNotificationIconLowBattery = R.drawable.ic_notification_low_battery;
+
+    public static enum DeviceUpdateSubject {
+        UNKNOWN,
+        NOTHING,
+        CONNECTION_STATE,
+        DEVICE_STATE,
+    }
+
+    public GBDevice(String address, String name, String alias, String parentFolder, DeviceType deviceType) {
+        this(address, null, name, alias, parentFolder, deviceType);
+    }
+
+    public GBDevice(String address, String address2, String name, String alias, String parentFolder, DeviceType deviceType) {
+        mAddress = address;
+        mVolatileAddress = address2;
+        mName = (name != null) ? name : mAddress;
+        mAlias = alias;
+        mDeviceType = deviceType;
+        this.parentFolder = parentFolder;
+        validate();
+    }
+
+    private GBDevice(Parcel in) {
+        mName = in.readString();
+        mAlias = in.readString();
+        parentFolder = in.readString();
+        mAddress = in.readString();
+        mVolatileAddress = in.readString();
+        mDeviceType = DeviceType.values()[in.readInt()];
+        mFirmwareVersion = in.readString();
+        mFirmwareVersion2 = in.readString();
+        mModel = in.readString();
+        mState = State.values()[in.readInt()];
+        mBatteryLevel = in.createIntArray();
+        mBatteryVoltage = in.createFloatArray();
+        mBatteryState = ordinalsToEnums(in.createIntArray());
+        mBatteryIcons = in.createIntArray();
+        mBatteryLabels = in.createIntArray();
+        mRssi = (short) in.readInt();
+        mBusyTask = in.readString();
+        mDeviceInfos = in.readArrayList(getClass().getClassLoader());
+        mExtraInfos = (HashMap) in.readSerializable();
+        mNotificationIconConnected = in.readInt();
+        mNotificationIconDisconnected = in.readInt();
+        mNotificationIconLowBattery = in.readInt();
+
+        validate();
+    }
+
+    public void copyFromDevice(GBDevice device){
+        if(!device.mAddress.equals(mAddress)){
+            throw new RuntimeException("Cannot copy from device with other address");
+        }
+
+        mName = device.mName;
+        mAlias = device.mAlias;
+        parentFolder = device.parentFolder;
+        mVolatileAddress = device.mVolatileAddress;
+        mFirmwareVersion = device.mFirmwareVersion;
+        mFirmwareVersion2 = device.mFirmwareVersion2;
+        mModel = device.mModel;
+        mState = device.mState;
+        mBatteryLevel = device.mBatteryLevel;
+        mBatteryVoltage = device.mBatteryVoltage;
+        mBatteryState = device.mBatteryState;
+        mBatteryIcons = device.mBatteryIcons;
+        mBatteryLabels = device.mBatteryLabels;
+        mRssi = device.mRssi;
+        mBusyTask = device.mBusyTask;
+        mDeviceInfos = device.mDeviceInfos;
+        mExtraInfos = device.mExtraInfos;
+        mNotificationIconConnected = device.mNotificationIconConnected;
+        mNotificationIconDisconnected = device.mNotificationIconDisconnected;
+        mNotificationIconLowBattery = device.mNotificationIconLowBattery;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(mName);
+        dest.writeString(mAlias);
+        dest.writeString(parentFolder);
+        dest.writeString(mAddress);
+        dest.writeString(mVolatileAddress);
+        dest.writeInt(mDeviceType.ordinal());
+        dest.writeString(mFirmwareVersion);
+        dest.writeString(mFirmwareVersion2);
+        dest.writeString(mModel);
+        dest.writeInt(mState.ordinal());
+        dest.writeIntArray(mBatteryLevel);
+        dest.writeFloatArray(mBatteryVoltage);
+        dest.writeIntArray(enumsToOrdinals(mBatteryState));
+        dest.writeIntArray(mBatteryIcons);
+        dest.writeIntArray(mBatteryLabels);
+        dest.writeInt(mRssi);
+        dest.writeString(mBusyTask);
+        dest.writeList(mDeviceInfos);
+        dest.writeSerializable(mExtraInfos);
+        dest.writeInt(mNotificationIconConnected);
+        dest.writeInt(mNotificationIconDisconnected);
+        dest.writeInt(mNotificationIconLowBattery);
+    }
+
+    private void validate() {
+        if (getAddress() == null) {
+            throw new IllegalArgumentException("address must not be null");
+        }
+    }
+
+    private int[] enumsToOrdinals(BatteryState[] arrayEnum) {
+        int[] ordinals = new int[arrayEnum.length];
+        for (int i = 0; i < arrayEnum.length; i++) {
+            ordinals[i] = arrayEnum[i].ordinal();
+        }
+        return ordinals;
+    }
+
+    private BatteryState[] ordinalsToEnums(int[] arrayInt){
+        BatteryState[] enums = new BatteryState[arrayInt.length];
+        for(int i = 0; i<arrayInt.length; i++){
+            enums[i]=BatteryState.values()[arrayInt[i]];
+        }
+        return enums;
+    }
+
+    public String getParentFolder() {
+        return parentFolder;
+    }
+
+    public void setParentFolder(String parentFolder) {
+        this.parentFolder = parentFolder;
+    }
+
+    public String getName() {
+        return mName;
+    }
+
+    public String getAlias() {
+        return mAlias;
+    }
+
+    public String getAliasOrName() {
+        if (mAlias != null && !mAlias.equals("")) {
+            return mAlias;
+        }
+        return mName;
+    }
+
+    public void setName(String name) {
+        if (name == null) {
+            LOG.warn("Ignoring setting of GBDevice name to null for " + this);
+            return;
+        }
+        mName = name;
+    }
+
+    public void setAlias(String alias) {
+        mAlias = alias;
+    }
+
+    public String getAddress() {
+        return mAddress;
+    }
+
+    public String getVolatileAddress() {
+        return mVolatileAddress;
+    }
+
+    public String getFirmwareVersion() {
+        return mFirmwareVersion;
+    }
+    public String getFirmwareVersion2() {
+        return mFirmwareVersion2;
+    }
+
+    public void setFirmwareVersion(String firmwareVersion) {
+        mFirmwareVersion = firmwareVersion;
+    }
+
+    /**
+     * Sets the second firmware version (HR or GPS or other component)
+     * @param firmwareVersion2
+     */
+    public void setFirmwareVersion2(String firmwareVersion2) {
+        mFirmwareVersion2 = firmwareVersion2;
+    }
+
+    public void setVolatileAddress(String volatileAddress) {
+        mVolatileAddress = volatileAddress;
+    }
+
+    /**
+     * Returns the specific model/hardware revision of this device.
+     * This information is not always available, typically only when the device is initialized
+     * @return the model/hardware revision of this device
+     * @see #getType()
+     */
+    @Nullable
+    public String getModel() {
+        return mModel;
+    }
+
+    public void setModel(String model) {
+        mModel = model;
+    }
+
+    public boolean isConnected() {
+        return mState == State.SCANNED || mState.equalsOrHigherThan(State.CONNECTED);
+    }
+
+    public boolean isInitializing() {
+        return mState == State.INITIALIZING;
+    }
+
+    public boolean isInitialized() {
+        return mState == State.SCANNED || mState.equalsOrHigherThan(State.INITIALIZED);
+    }
+
+    public boolean isConnecting() {
+        return mState == State.CONNECTING;
+    }
+
+    public boolean isBusy() {
+        return mBusyTask != null;
+    }
+
+    public String getBusyTask() {
+        return mBusyTask;
+    }
+
+    public int getNotificationIconConnected() {
+        return mNotificationIconConnected;
+    }
+
+    public void setNotificationIconConnected(int mNotificationIconConnected) {
+        this.mNotificationIconConnected = mNotificationIconConnected;
+    }
+
+    public int getNotificationIconDisconnected() {
+        return mNotificationIconDisconnected;
+    }
+
+    public void setNotificationIconDisconnected(int notificationIconDisconnected) {
+        this.mNotificationIconDisconnected = notificationIconDisconnected;
+    }
+
+    public int getNotificationIconLowBattery() {
+        return mNotificationIconLowBattery;
+    }
+
+    public void setNotificationIconLowBattery(int mNotificationIconLowBattery) {
+        this.mNotificationIconLowBattery = mNotificationIconLowBattery;
+    }
+
+    /**
+     * Marks the device as busy, performing a certain task. While busy, no other operations will
+     * be performed on the device.
+     * <p/>
+     * Note that nested busy tasks are not supported, every single call to #setBusyTask()
+     * or unsetBusy() has an effect.
+     *
+     * @param task a textual name of the task to be performed, possibly displayed to the user
+     */
+    public void setBusyTask(String task) {
+        if (task == null) {
+            throw new IllegalArgumentException("busy task must not be null");
+        }
+        if (mBusyTask != null) {
+            LOG.warn("Attempt to mark device as busy with: " + task + ", but is already busy with: " + mBusyTask);
+        }
+        LOG.info("Mark device as busy: " + task);
+        mBusyTask = task;
+    }
+
+    /**
+     * Marks the device as not busy anymore.
+     */
+    public void unsetBusyTask() {
+        if (mBusyTask == null) {
+            LOG.error("Attempt to mark device as not busy anymore, but was not busy before.");
+            return;
+        }
+        LOG.info("Mark device as NOT busy anymore: " + mBusyTask);
+        mBusyTask = null;
+    }
+
+    public State getState() {
+        return mState;
+    }
+
+    public int getStateOrdinal() {
+        return mState.ordinal();
+    }
+
+    public void setState(State state) {
+        mState = state;
+        if (state.ordinal() <= State.CONNECTED.ordinal()) {
+            unsetDynamicState();
+        }
+    }
+
+    private void unsetDynamicState() {
+        setBatteryLevel(BATTERY_UNKNOWN, 0);
+        setBatteryLevel(BATTERY_UNKNOWN, 1);
+        setBatteryLevel(BATTERY_UNKNOWN, 2);
+        setBatteryState(UNKNOWN, 0);
+        setBatteryState(UNKNOWN, 1);
+        setBatteryState(UNKNOWN, 2);
+        setRssi(RSSI_UNKNOWN);
+        resetExtraInfos();
+        if (mBusyTask != null) {
+            unsetBusyTask();
+        }
+    }
+
+    public String getStateString(final Context context) {
+        return getStateString(context, true);
+    }
+
+    /**
+     * for simplicity the user won't see all internal states, just connecting -> connected
+     * instead of connecting->connected->initializing->initialized
+     * Set simple to true to get this behavior.
+     */
+    private String getStateString(Context context, boolean simple) {
+        try{
+            // TODO: not sure if this is really neccessary...
+            if(simple){
+                return context.getString(mState.getSimpleStringId());
+            }
+            return context.getString(mState.getStringId());
+        }catch (Exception e){}
+
+        return context.getString(R.string.unknown_state);
+    }
+
+    /**
+     * Returns the general type of this device. For more detailed information,
+     * soo #getModel()
+     * @return the general type of this device
+     */
+    @NonNull
+    public DeviceType getType() {
+        return mDeviceType;
+    }
+
+    @NonNull
+    public DeviceCoordinator getDeviceCoordinator(){
+        return mDeviceType.getDeviceCoordinator();
+    }
+
+    public void setRssi(short rssi) {
+        if (rssi < 0) {
+            LOG.warn("Illegal RSSI value " + rssi + ", setting to RSSI_UNKNOWN");
+            mRssi = RSSI_UNKNOWN;
+        } else {
+            mRssi = rssi;
+        }
+    }
+
+    /**
+     * Returns the device specific signal strength value, or #RSSI_UNKNOWN
+     */
+    public short getRssi() {
+        return mRssi;
+    }
+
+    // TODO: this doesn't really belong here
+    public void sendDeviceUpdateIntent(Context context) {
+        sendDeviceUpdateIntent(context, DeviceUpdateSubject.UNKNOWN);
+    }
+
+    // TODO: this doesn't really belong here
+    public void sendDeviceUpdateIntent(Context context, DeviceUpdateSubject subject) {
+        Intent deviceUpdateIntent = new Intent(ACTION_DEVICE_CHANGED);
+        deviceUpdateIntent.putExtra(EXTRA_DEVICE, this);
+        deviceUpdateIntent.putExtra(EXTRA_UPDATE_SUBJECT, subject);
+        LocalBroadcastManager.getInstance(context).sendBroadcast(deviceUpdateIntent);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == this) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (!(obj instanceof GBDevice)) {
+            return false;
+        }
+        return ((GBDevice) obj).getAddress().equals(this.mAddress);
+    }
+
+    @Override
+    public int hashCode() {
+        return mAddress.hashCode() ^ 37;
+    }
+
+
+    /**
+     * Returns the extra info value if it is set, null otherwise
+     * @param key the extra info key
+     * @return the extra info value if set, null otherwise
+     */
+    public Object getExtraInfo(String key) {
+        if (mExtraInfos == null) {
+            return null;
+        }
+
+        return mExtraInfos.get(key);
+    }
+
+    /**
+     * Sets an extra info value, overwriting the current one, if any
+     * @param key the extra info key
+     * @param info the extra info value
+     */
+    public void setExtraInfo(String key, Object info) {
+        if (mExtraInfos == null) {
+            mExtraInfos = new HashMap<>();
+        }
+
+        mExtraInfos.put(key, info);
+    }
+
+    /**
+     * Deletes all the extra infos
+     */
+    public void resetExtraInfos() {
+        mExtraInfos = null;
+    }
+
+    /**
+     * Ranges from 0-100 (percent), or -1 if unknown
+     *
+     * @return the battery level in range 0-100, or -1 if unknown
+     */
+    public int getBatteryLevel() {
+        return getBatteryLevel(0);
+    }
+
+    public int getBatteryLevel(int index) {
+        return mBatteryLevel[index];
+    }
+
+
+    public void setBatteryLevel(int batteryLevel) {
+        setBatteryLevel(batteryLevel, 0);
+    }
+
+    public void setBatteryLevel(int batteryLevel, int index) {
+        if ((batteryLevel >= 0 && batteryLevel <= 100) || batteryLevel == BATTERY_UNKNOWN) {
+            mBatteryLevel[index] = batteryLevel;
+        } else {
+            LOG.error("Battery level musts be within range 0-100: " + batteryLevel);
+        }
+    }
+
+    public void setBatteryVoltage(float batteryVoltage) {
+        setBatteryVoltage(batteryVoltage, 0);
+    }
+
+
+    public void setBatteryVoltage(float batteryVoltage, int index) {
+        if (batteryVoltage >= 0 || batteryVoltage == BATTERY_UNKNOWN) {
+            mBatteryVoltage[index] = batteryVoltage;
+        } else {
+            LOG.error("Battery voltage must be > 0: " + batteryVoltage);
+        }
+    }
+
+    /**
+     * Voltage greater than zero (unit: Volt), or -1 if unknown
+     *
+     * @return the battery voltage, or -1 if unknown
+     */
+    public float getBatteryVoltage() {
+        return getBatteryVoltage(0);
+    }
+
+    public float getBatteryVoltage(int index) {
+        return mBatteryVoltage[index];
+    }
+
+    public BatteryState getBatteryState() {
+        return getBatteryState(0);
+    }
+
+    public BatteryState getBatteryState(int index) {
+        return mBatteryState[index];
+    }
+
+    public void setBatteryState(BatteryState mBatteryState) {
+        setBatteryState(mBatteryState, 0);
+    }
+
+    public void setBatteryState(BatteryState mBatteryState, int index) {
+        this.mBatteryState[index] = mBatteryState;
+    }
+
+    public int getBatteryIcon(int index) {
+        return this.mBatteryIcons[index];
+    }
+
+    public void setBatteryIcon(int icon, int index) {
+        this.mBatteryIcons[index] = icon;
+    }
+
+    public int getBatteryLabel(int index) {
+        return this.mBatteryLabels[index];
+    }
+
+    public void setBatteryLabel(int label, int index) {
+        this.mBatteryLabels[index] = label;
+    }
+
+    public int getEnabledDisabledIconResource(){
+        return isInitialized() ?
+                getDeviceCoordinator().getDefaultIconResource() :
+                getDeviceCoordinator().getDisabledIconResource();
+    }
+
+    @NonNull
+    @Override
+    public String toString() {
+        return "Device " + getName() + ", " + getAddress() + ", " + mState;
+    }
+
+    /**
+     * Returns a shortened form of the device's address, in order to form a
+     * unique name in companion with #getName().
+     */
+    @NonNull
+    public String getShortAddress() {
+        String address = getAddress();
+        if (address != null) {
+            if (address.length() > 5) {
+                return address.substring(address.length() - 5);
+            }
+            return address;
+        }
+        return "";
+    }
+
+    public boolean hasDeviceInfos() {
+        return getDeviceInfos().size() > 0;
+    }
+
+    public ItemWithDetails getDeviceInfo(String name) {
+        for (ItemWithDetails item : getDeviceInfos()) {
+            if (name.equals(item.getName())) {
+                return item;
+            }
+        }
+        return null;
+    }
+
+    public List<ItemWithDetails> getDeviceInfos() {
+        List<ItemWithDetails> result = new ArrayList<>();
+        if (mDeviceInfos != null) {
+            result.addAll(mDeviceInfos);
+        }
+        if (mModel != null) {
+            result.add(new GenericItem(DEVINFO_HW_VER, mModel));
+        }
+        if (mFirmwareVersion != null) {
+            result.add(new GenericItem(DEVINFO_FW_VER, mFirmwareVersion));
+        }
+        if (mFirmwareVersion2 != null) {
+            result.add(new GenericItem(DEVINFO_FW2_VER, mFirmwareVersion2));
+        }
+        if (mAddress != null) {
+            result.add(new GenericItem(DEVINFO_ADDR, mAddress));
+        }
+        if (mVolatileAddress != null) {
+            result.add(new GenericItem(DEVINFO_ADDR2, mVolatileAddress));
+        }
+        Collections.sort(result);
+        return result;
+    }
+
+    public void setDeviceInfos(List<ItemWithDetails> deviceInfos) {
+        this.mDeviceInfos = deviceInfos;
+    }
+
+    public void addDeviceInfo(ItemWithDetails info) {
+        if (mDeviceInfos == null) {
+            mDeviceInfos = new ArrayList<>();
+        } else {
+            int index = mDeviceInfos.indexOf(info);
+            if (index >= 0) {
+                mDeviceInfos.set(index, info); // replace item with new one
+                return;
+            }
+        }
+        mDeviceInfos.add(info);
+    }
+
+    public boolean removeDeviceInfo(ItemWithDetails info) {
+        if (mDeviceInfos == null) {
+            return false;
+        }
+        return mDeviceInfos.remove(info);
+    }
+
+    public enum State {
+        NOT_CONNECTED(R.string.not_connected),
+        WAITING_FOR_RECONNECT(R.string.waiting_for_reconnect),
+        WAITING_FOR_SCAN(R.string.device_state_waiting_scan),
+        SCANNED(R.string.state_scanned),
+        CONNECTING(R.string.connecting),
+        CONNECTED(R.string.connected, R.string.connecting),
+        INITIALIZING(R.string.initializing, R.string.connecting),
+        AUTHENTICATION_REQUIRED(R.string.authentication_required), // some kind of pairing is required by the device
+        AUTHENTICATING(R.string.authenticating), // some kind of pairing is requested by the device
+        /**
+         * Means that the device is connected AND all the necessary initialization steps
+         * have been performed. At the very least, this means that basic information like
+         * device name, firmware version, hardware revision (as applicable) is available
+         * in the GBDevice.
+         */
+        INITIALIZED(R.string.initialized, R.string.connected);
+
+
+        private int stringId, simpleStringId;
+
+        State(int stringId, int simpleStringId) {
+            this.stringId = stringId;
+            this.simpleStringId = simpleStringId;
+        }
+
+        State(int stringId) {
+            this(stringId, stringId);
+        }
+
+        public int getStringId() {
+            return stringId;
+        }
+
+        public int getSimpleStringId() {
+            return simpleStringId;
+        }
+
+        public boolean equalsOrHigherThan(State otherState){
+            return compareTo(otherState) >= 0;
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceApp.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceApp.java
new file mode 100644
index 0000000..45d66d7
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceApp.java
@@ -0,0 +1,163 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, Arjan Schrijver, Carsten
+    Pfeiffer, Daniele Gobbetti
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.impl;
+
+import android.graphics.Bitmap;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.UUID;
+
+public class GBDeviceApp {
+    private final String name;
+    private final String creator;
+    private final String version;
+    private final UUID uuid;
+    private final Type type;
+    private final boolean inCache;
+    private boolean isOnDevice;
+    private final boolean configurable;
+    private final Bitmap previewImage;
+    private boolean isUpToDate = true;
+
+    public GBDeviceApp(UUID uuid, String name, String creator, String version, Type type, Bitmap previewImage) {
+        this.uuid = uuid;
+        this.name = name;
+        this.creator = creator;
+        this.version = version;
+        this.type = type;
+        this.previewImage = previewImage;
+        //FIXME: do not assume
+        this.inCache = false;
+        this.configurable = false;
+        this.isOnDevice = false;
+    }
+
+    public GBDeviceApp(UUID uuid, String name, String creator, String version, Type type) {
+        this.uuid = uuid;
+        this.name = name;
+        this.creator = creator;
+        this.version = version;
+        this.type = type;
+        this.previewImage = null;
+        //FIXME: do not assume
+        this.inCache = false;
+        this.configurable = false;
+        this.isOnDevice = false;
+    }
+
+    public GBDeviceApp(JSONObject json, boolean configurable, Bitmap previewImage) {
+        UUID uuid = UUID.fromString("00000000-0000-0000-0000-000000000000");
+        String name = "";
+        String creator = "";
+        String version = "";
+        Type type = Type.UNKNOWN;
+
+        try {
+            uuid = UUID.fromString(json.getString("uuid"));
+            name = json.getString("name");
+            creator = json.getString("creator");
+            version = json.getString("version");
+            type = Type.valueOf(json.getString("type"));
+        } catch (JSONException e) {
+            e.printStackTrace();
+        }
+
+        this.uuid = uuid;
+        this.name = name;
+        this.creator = creator;
+        this.version = version;
+        this.type = type;
+        this.previewImage = previewImage;
+        //FIXME: do not assume
+        this.inCache = true;
+        this.configurable = configurable;
+    }
+
+    public void setOnDevice(boolean isOnDevice) {
+        this.isOnDevice = isOnDevice;
+    }
+
+    public boolean isInCache() {
+        return inCache;
+    }
+
+    public boolean isOnDevice() {
+        return isOnDevice;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public String getCreator() {
+        return creator;
+    }
+
+    public String getVersion() {
+        return version;
+    }
+
+    public UUID getUUID() {
+        return uuid;
+    }
+
+    public Type getType() {
+        return type;
+    }
+
+    public Bitmap getPreviewImage() {
+        return previewImage;
+    }
+
+    public enum Type {
+        UNKNOWN,
+        WATCHFACE,
+        WATCHFACE_SYSTEM,
+        APP_GENERIC,
+        APP_ACTIVITYTRACKER,
+        APP_SYSTEM,
+    }
+
+    public JSONObject getJSON() {
+        JSONObject json = new JSONObject();
+        try {
+            json.put("uuid", uuid.toString());
+            json.put("name", name);
+            json.put("creator", creator);
+            json.put("version", version);
+            json.put("type", type.name());
+        } catch (JSONException e) {
+            e.printStackTrace();
+        }
+        return json;
+    }
+
+    public boolean isConfigurable() {
+        return configurable;
+    }
+
+    public void setUpToDate(boolean isUpToDate) {
+        this.isUpToDate = isUpToDate;
+    }
+
+    public boolean isUpToDate() {
+        return isUpToDate;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceCandidate.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceCandidate.java
new file mode 100644
index 0000000..4975a5b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceCandidate.java
@@ -0,0 +1,248 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, Carsten Pfeiffer, Daniel
+    Dakhno, Daniele Gobbetti, Jos茅 Rebelo, Taavi Eom盲e
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.impl;
+
+import android.bluetooth.BluetoothDevice;
+import android.os.Parcel;
+import android.os.ParcelUuid;
+import android.os.Parcelable;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.LinkedHashSet;
+import java.util.Set;
+import java.util.UUID;
+
+import androidx.annotation.NonNull;
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.util.AndroidUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
+
+/**
+ * A device candidate is a Bluetooth device that is not yet managed by
+ * Gadgetbridge. Only if a DeviceCoordinator steps up and confirms to
+ * support this candidate, will the candidate be promoted to a GBDevice.
+ */
+public class GBDeviceCandidate implements Parcelable, Cloneable {
+    private static final Logger LOG = LoggerFactory.getLogger(GBDeviceCandidate.class);
+
+    private BluetoothDevice device;
+    private short rssi;
+    private ParcelUuid[] serviceUuids;
+    // Cached values for device name and bond status, to avoid querying the remote bt device
+    private String deviceName;
+    private Boolean isBonded = null;
+
+    public GBDeviceCandidate(BluetoothDevice device, short rssi, ParcelUuid[] serviceUuids) {
+        this.device = device;
+        this.rssi = rssi;
+        this.serviceUuids = serviceUuids != null ? serviceUuids : new ParcelUuid[0];
+    }
+
+    private GBDeviceCandidate(Parcel in) {
+        device = in.readParcelable(getClass().getClassLoader());
+        if (device == null) {
+            throw new IllegalStateException("Unable to read state from Parcel");
+        }
+        rssi = (short) in.readInt();
+
+        serviceUuids = AndroidUtils.toParcelUuids(in.readParcelableArray(getClass().getClassLoader()));
+
+        deviceName = in.readString();
+        final int isBondedInt = in.readInt();
+        if (isBondedInt != -1) {
+            isBonded = (isBondedInt == 1);
+        }
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeParcelable(device, 0);
+        dest.writeInt(rssi);
+        dest.writeParcelableArray(serviceUuids, 0);
+        dest.writeString(deviceName);
+        if (isBonded == null) {
+            dest.writeInt(-1);
+        } else {
+            dest.writeInt(isBonded ? 1 : 0);
+        }
+    }
+
+    public static final Creator<GBDeviceCandidate> CREATOR = new Creator<GBDeviceCandidate>() {
+        @Override
+        public GBDeviceCandidate createFromParcel(Parcel in) {
+            return new GBDeviceCandidate(in);
+        }
+
+        @Override
+        public GBDeviceCandidate[] newArray(int size) {
+            return new GBDeviceCandidate[size];
+        }
+    };
+
+    public BluetoothDevice getDevice() {
+        return device;
+    }
+
+    public String getMacAddress() {
+        return device != null ? device.getAddress() : GBApplication.getContext().getString(R.string._unknown_);
+    }
+
+    private ParcelUuid[] mergeServiceUuids(ParcelUuid[] serviceUuids, ParcelUuid[] deviceUuids) {
+        Set<ParcelUuid> uuids = new LinkedHashSet<>();
+        if (serviceUuids != null) {
+            uuids.addAll(Arrays.asList(serviceUuids));
+        }
+        if (deviceUuids != null) {
+            uuids.addAll(Arrays.asList(deviceUuids));
+        }
+        return uuids.toArray(new ParcelUuid[0]);
+    }
+
+    public void addUuids(ParcelUuid[] newUuids) {
+        this.serviceUuids = mergeServiceUuids(serviceUuids, newUuids);
+    }
+
+    public void setRssi(short rssi) {
+        this.rssi = rssi;
+    }
+
+    public boolean isBonded() {
+        if (isBonded == null) {
+            try {
+                isBonded = device.getBondState() == BluetoothDevice.BOND_BONDED;
+            } catch (final SecurityException e) {
+                /* This should never happen because we need all the permissions
+                    to get to the point where we can even scan, but 'SecurityException' check
+                    is added to stop Android Studio errors */
+                LOG.error("SecurityException on getBonded");
+                isBonded = false;
+            }
+        }
+
+        return isBonded;
+    }
+
+    @NonNull
+    public ParcelUuid[] getServiceUuids() {
+        return serviceUuids;
+    }
+
+    public boolean supportsService(UUID aService) {
+        ParcelUuid[] uuids = getServiceUuids();
+        if (uuids == null || uuids.length == 0) {
+            LOG.warn("no cached services available for " + this);
+            return false;
+        }
+
+        for (ParcelUuid uuid : uuids) {
+            if (uuid != null && aService.equals(uuid.getUuid())) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public String getName() {
+        if (isNameKnown()) {
+            return deviceName;
+        }
+        return "(unknown)";
+    }
+
+    public void refreshNameIfUnknown() {
+        if (isNameKnown()) {
+            return;
+        }
+
+        try {
+            final Method method = device.getClass().getMethod("getAliasName");
+            deviceName = (String) method.invoke(device);
+        } catch (final NoSuchMethodException ignore) {
+            // ignored
+        } catch (final IllegalAccessException | InvocationTargetException ignore) {
+            LOG.warn("Could not get device alias for {}", device.getAddress());
+        }
+        if (deviceName == null || deviceName.isEmpty()) {
+            try {
+                deviceName = device.getName();
+            } catch (final SecurityException e) {
+                // Should never happen
+                LOG.error("SecurityException on device.getName");
+            }
+        }
+    }
+
+    public boolean isNameKnown() {
+        return deviceName != null && !deviceName.isEmpty();
+    }
+
+    public short getRssi() {
+        return rssi;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        GBDeviceCandidate that = (GBDeviceCandidate) o;
+        return device.getAddress().equals(that.device.getAddress());
+    }
+
+    @Override
+    public int hashCode() {
+        return device.getAddress().hashCode() ^ 37;
+    }
+
+    @Override
+    public String toString() {
+        return getName() + ": " + getMacAddress();
+    }
+
+    @NonNull
+    @Override
+    public GBDeviceCandidate clone() {
+        try {
+            final GBDeviceCandidate clone = (GBDeviceCandidate) super.clone();
+            clone.device = this.device;
+            clone.rssi = this.rssi;
+            clone.serviceUuids = this.serviceUuids;
+            clone.deviceName = this.deviceName;
+            clone.isBonded = this.isBonded;
+            return clone;
+        } catch (final CloneNotSupportedException e) {
+            throw new RuntimeException(e);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceFolder.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceFolder.java
new file mode 100644
index 0000000..36c2480
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceFolder.java
@@ -0,0 +1,25 @@
+/*  Copyright (C) 2022-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.impl;
+
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+
+public class GBDeviceFolder extends GBDevice{
+    public GBDeviceFolder(String name) {
+        super("", name, null, null, null);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceMusic.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceMusic.java
new file mode 100644
index 0000000..b15ce88
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceMusic.java
@@ -0,0 +1,33 @@
+package nodomain.freeyourgadget.gadgetbridge.impl;
+
+import java.io.Serializable;
+
+public class GBDeviceMusic implements Serializable {
+    private final int id;
+    private final String title;
+    private final String artist;
+    private final String fileName;
+
+    public GBDeviceMusic(int id, String title, String artist, String fileName) {
+        this.id = id;
+        this.title = title;
+        this.artist = artist;
+        this.fileName = fileName;
+    }
+
+    public int getId() {
+        return id;
+    }
+
+    public String getTitle() {
+        return title;
+    }
+
+    public String getArtist() {
+        return artist;
+    }
+
+    public String getFileName() {
+        return fileName;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceMusicPlaylist.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceMusicPlaylist.java
new file mode 100644
index 0000000..a6e2f3d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceMusicPlaylist.java
@@ -0,0 +1,41 @@
+package nodomain.freeyourgadget.gadgetbridge.impl;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+
+public class GBDeviceMusicPlaylist implements Serializable {
+    private final int id;
+    private String name;
+    private ArrayList<Integer> musicIds;
+
+    public GBDeviceMusicPlaylist(int id, String name, ArrayList<Integer> musicIds) {
+        this.id = id;
+        this.name = name;
+        this.musicIds = musicIds;
+    }
+
+    public int getId() {
+        return id;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public ArrayList<Integer> getMusicIds() {
+        return musicIds;
+    }
+
+    public void setMusicIds(ArrayList<Integer> musicIds) {
+        this.musicIds = musicIds;
+    }
+
+    @Override
+    public String toString() {
+        return name;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceService.java
new file mode 100644
index 0000000..b88bd98
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceService.java
@@ -0,0 +1,588 @@
+/*  Copyright (C) 2015-2024 Alberto, Andreas B枚hler, Andreas Shimokawa,
+    Arjan Schrijver, Carsten Pfeiffer, criogenic, Daniel Dakhno, Daniele Gobbetti,
+    Davis Mosenkovs, Frank Slezak, Gabriele Monaco, Gordon Williams, ivanovlev,
+    Jos茅 Rebelo, Julien Pivotto, Kasha, mvn23, Petr Van臎k, Roi Greenberg,
+    Sebastian Kranz, Steffen Liebergeld
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.impl;
+
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+import android.location.Location;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.ContactsContract;
+
+import java.util.ArrayList;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.capabilities.loyaltycards.LoyaltyCard;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventCameraRemote;
+import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
+import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.Contact;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
+import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.NavigationInfoSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.Reminder;
+import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.WorldClock;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceCommunicationService;
+import nodomain.freeyourgadget.gadgetbridge.util.RtlUtils;
+
+import static nodomain.freeyourgadget.gadgetbridge.util.JavaExtensions.coalesce;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+public class GBDeviceService implements DeviceService {
+    protected final Context mContext;
+    private final GBDevice mDevice;
+    private final Class<? extends Service> mServiceClass;
+    public static final String[] transliterationExtras = new String[]{
+            EXTRA_NOTIFICATION_SENDER,
+            EXTRA_NOTIFICATION_SUBJECT,
+            EXTRA_NOTIFICATION_TITLE,
+            EXTRA_NOTIFICATION_BODY,
+            EXTRA_NOTIFICATION_SOURCENAME,
+            EXTRA_CALL_DISPLAYNAME,
+            EXTRA_CALL_SOURCENAME,
+            EXTRA_MUSIC_ARTIST,
+            EXTRA_MUSIC_ALBUM,
+            EXTRA_MUSIC_TRACK,
+            EXTRA_CALENDAREVENT_TITLE,
+            EXTRA_CALENDAREVENT_DESCRIPTION,
+            EXTRA_CALENDAREVENT_LOCATION,
+            EXTRA_CALENDAREVENT_CALNAME,
+    };
+    private static final Logger LOG = LoggerFactory.getLogger(GBDeviceService.class);
+
+    public GBDeviceService(Context context) {
+        this(context, null);
+    }
+
+    public GBDeviceService(Context context, GBDevice device) {
+        mContext = context;
+        mDevice = device;
+        mServiceClass = DeviceCommunicationService.class;
+    }
+
+    @Override
+    public DeviceService forDevice(final GBDevice device) {
+        return new GBDeviceService(mContext, device);
+    }
+
+    public GBDevice getDevice() {
+        return mDevice;
+    }
+
+    protected Intent createIntent() {
+        return new Intent(mContext, mServiceClass);
+    }
+
+    protected void invokeService(Intent intent) {
+
+        if (RtlUtils.rtlSupport()) {
+            for (String extra : transliterationExtras) {
+                if (intent.hasExtra(extra)) {
+                    intent.putExtra(extra, RtlUtils.fixRtl(intent.getStringExtra(extra)));
+                }
+            }
+        }
+
+        if (mDevice != null) {
+            intent.putExtra(GBDevice.EXTRA_DEVICE, mDevice);
+        }
+        try {
+            mContext.startService(intent);
+        } catch (IllegalStateException e) {
+            LOG.error("IllegalStateException during startService ("+intent.getAction()+")");
+        }
+    }
+
+    protected void stopService(Intent intent) {
+        mContext.stopService(intent);
+    }
+
+    @Override
+    public void connect() {
+        connect(false);
+    }
+
+    @Override
+    public void connect(boolean firstTime) {
+        Intent intent = createIntent().setAction(ACTION_CONNECT)
+                .putExtra(EXTRA_CONNECT_FIRST_TIME, firstTime);
+        invokeService(intent);
+    }
+
+    @Override
+    public void disconnect() {
+        Intent intent = createIntent().setAction(ACTION_DISCONNECT);
+        invokeService(intent);
+    }
+
+    @Override
+    public void quit() {
+        Intent intent = createIntent();
+        stopService(intent);
+    }
+
+    @Override
+    public void requestDeviceInfo() {
+        Intent intent = createIntent().setAction(ACTION_REQUEST_DEVICEINFO);
+        invokeService(intent);
+    }
+
+    @Override
+    public void onNotification(NotificationSpec notificationSpec) {
+        String messagePrivacyMode = GBApplication.getPrefs().getString("pref_message_privacy_mode",
+                GBApplication.getContext().getString(R.string.p_message_privacy_mode_off));
+        boolean hideMessageDetails = messagePrivacyMode.equals(GBApplication.getContext().getString(R.string.p_message_privacy_mode_complete));
+        boolean hideMessageBodyOnly = messagePrivacyMode.equals(GBApplication.getContext().getString(R.string.p_message_privacy_mode_bodyonly));
+
+        Intent intent = createIntent().setAction(ACTION_NOTIFICATION)
+                .putExtra(EXTRA_NOTIFICATION_FLAGS, notificationSpec.flags)
+                .putExtra(EXTRA_NOTIFICATION_PHONENUMBER, hideMessageDetails ? null : notificationSpec.phoneNumber)
+                .putExtra(EXTRA_NOTIFICATION_SENDER, hideMessageDetails ? null : coalesce(notificationSpec.sender, getContactDisplayNameByNumber(notificationSpec.phoneNumber)))
+                .putExtra(EXTRA_NOTIFICATION_SUBJECT, hideMessageDetails ? null : notificationSpec.subject)
+                .putExtra(EXTRA_NOTIFICATION_TITLE, hideMessageDetails ? null : notificationSpec.title)
+                .putExtra(EXTRA_NOTIFICATION_BODY, hideMessageDetails || hideMessageBodyOnly ? null : notificationSpec.body)
+                .putExtra(EXTRA_NOTIFICATION_ID, notificationSpec.getId())
+                .putExtra(EXTRA_NOTIFICATION_KEY, notificationSpec.key)
+                .putExtra(EXTRA_NOTIFICATION_TYPE, notificationSpec.type)
+                .putExtra(EXTRA_NOTIFICATION_ACTIONS, notificationSpec.attachedActions)
+                .putExtra(EXTRA_NOTIFICATION_SOURCENAME, notificationSpec.sourceName)
+                .putExtra(EXTRA_NOTIFICATION_PEBBLE_COLOR, notificationSpec.pebbleColor)
+                .putExtra(EXTRA_NOTIFICATION_SOURCEAPPID, notificationSpec.sourceAppId)
+                .putExtra(EXTRA_NOTIFICATION_ICONID, notificationSpec.iconId)
+                .putExtra(NOTIFICATION_PICTURE_PATH, notificationSpec.picturePath)
+                .putExtra(EXTRA_NOTIFICATION_DNDSUPPRESSED, notificationSpec.dndSuppressed);
+        invokeService(intent);
+    }
+
+    @Override
+    public void onDeleteNotification(int id) {
+        Intent intent = createIntent().setAction(ACTION_DELETE_NOTIFICATION)
+                .putExtra(EXTRA_NOTIFICATION_ID, id);
+        invokeService(intent);
+
+    }
+
+    @Override
+    public void onSetTime() {
+        Intent intent = createIntent().setAction(ACTION_SETTIME);
+        invokeService(intent);
+    }
+
+    @Override
+    public void onSetAlarms(ArrayList<? extends Alarm> alarms) {
+        Intent intent = createIntent().setAction(ACTION_SET_ALARMS)
+                .putExtra(EXTRA_ALARMS, alarms);
+        invokeService(intent);
+    }
+
+    @Override
+    public void onSetCallState(CallSpec callSpec) {
+        Context context = GBApplication.getContext();
+        String currentPrivacyMode = GBApplication.getPrefs().getString("pref_call_privacy_mode", GBApplication.getContext().getString(R.string.p_call_privacy_mode_off));
+        if (currentPrivacyMode.equals(context.getString(R.string.p_call_privacy_mode_name))) {
+            callSpec.name = callSpec.number;
+        } else if (currentPrivacyMode.equals(context.getString(R.string.p_call_privacy_mode_complete))) {
+            callSpec.number = null;
+            callSpec.name = null;
+        } else if (currentPrivacyMode.equals(context.getString(R.string.p_call_privacy_mode_number))){
+            callSpec.name = coalesce(callSpec.name, getContactDisplayNameByNumber(callSpec.number));
+            if (callSpec.name != null && !callSpec.name.equals(callSpec.number)) {
+                callSpec.number = null;
+            }
+        } else {
+            callSpec.name = coalesce(callSpec.name, getContactDisplayNameByNumber(callSpec.number));
+        }
+
+        Intent intent = createIntent().setAction(ACTION_CALLSTATE)
+                .putExtra(EXTRA_CALL_PHONENUMBER, callSpec.number)
+                .putExtra(EXTRA_CALL_DISPLAYNAME, callSpec.name)
+                .putExtra(EXTRA_CALL_SOURCENAME, callSpec.sourceName)
+                .putExtra(EXTRA_CALL_SOURCEAPPID, callSpec.sourceAppId)
+                .putExtra(EXTRA_CALL_COMMAND, callSpec.command)
+                .putExtra(EXTRA_CALL_DNDSUPPRESSED, callSpec.dndSuppressed);
+        invokeService(intent);
+    }
+
+    @Override
+    public void onSetCannedMessages(CannedMessagesSpec cannedMessagesSpec) {
+        Intent intent = createIntent().setAction(ACTION_SETCANNEDMESSAGES)
+                .putExtra(EXTRA_CANNEDMESSAGES_TYPE, cannedMessagesSpec.type)
+                .putExtra(EXTRA_CANNEDMESSAGES, cannedMessagesSpec.cannedMessages);
+        invokeService(intent);
+    }
+
+    @Override
+    public void onSetMusicState(MusicStateSpec stateSpec) {
+        Intent intent = createIntent().setAction(ACTION_SETMUSICSTATE)
+                .putExtra(EXTRA_MUSIC_REPEAT, stateSpec.repeat)
+                .putExtra(EXTRA_MUSIC_RATE, stateSpec.playRate)
+                .putExtra(EXTRA_MUSIC_STATE, stateSpec.state)
+                .putExtra(EXTRA_MUSIC_SHUFFLE, stateSpec.shuffle)
+                .putExtra(EXTRA_MUSIC_POSITION, stateSpec.position);
+        invokeService(intent);
+    }
+
+    @Override
+    public void onSetPhoneVolume(final float volume) {
+        Intent intent = createIntent().setAction(ACTION_SET_PHONE_VOLUME)
+                .putExtra(EXTRA_PHONE_VOLUME, volume);
+        invokeService(intent);
+    }
+
+    @Override
+    public void onChangePhoneSilentMode(int ringerMode) {
+        Intent intent = createIntent().setAction(ACTION_SET_PHONE_SILENT_MODE)
+                .putExtra(EXTRA_PHONE_RINGER_MODE, ringerMode);
+        invokeService(intent);
+    }
+
+    @Override
+    public void onSetReminders(ArrayList<? extends Reminder> reminders) {
+        Intent intent = createIntent().setAction(ACTION_SET_REMINDERS)
+                .putExtra(EXTRA_REMINDERS, reminders);
+        invokeService(intent);
+    }
+
+    @Override
+    public void onSetLoyaltyCards(final ArrayList<LoyaltyCard> cards) {
+        final Intent intent = createIntent().setAction(ACTION_SET_LOYALTY_CARDS)
+                .putExtra(EXTRA_LOYALTY_CARDS, cards);
+        invokeService(intent);
+    }
+
+    @Override
+    public void onSetWorldClocks(ArrayList<? extends WorldClock> clocks) {
+        Intent intent = createIntent().setAction(ACTION_SET_WORLD_CLOCKS)
+                .putExtra(EXTRA_WORLD_CLOCKS, clocks);
+        invokeService(intent);
+    }
+
+    @Override
+    public void onSetContacts(ArrayList<? extends Contact> contacts) {
+        Intent intent = createIntent().setAction(ACTION_SET_CONTACTS)
+                .putExtra(EXTRA_CONTACTS, contacts);
+        invokeService(intent);
+    }
+
+    @Override
+    public void onSetMusicInfo(MusicSpec musicSpec) {
+        Intent intent = createIntent().setAction(ACTION_SETMUSICINFO)
+                .putExtra(EXTRA_MUSIC_ARTIST, musicSpec.artist)
+                .putExtra(EXTRA_MUSIC_ALBUM, musicSpec.album)
+                .putExtra(EXTRA_MUSIC_TRACK, musicSpec.track)
+                .putExtra(EXTRA_MUSIC_DURATION, musicSpec.duration)
+                .putExtra(EXTRA_MUSIC_TRACKCOUNT, musicSpec.trackCount)
+                .putExtra(EXTRA_MUSIC_TRACKNR, musicSpec.trackNr);
+        invokeService(intent);
+    }
+
+    @Override
+    public void onSetNavigationInfo(NavigationInfoSpec navigationInfoSpec) {
+        Intent intent = createIntent().setAction(ACTION_SETNAVIGATIONINFO)
+                .putExtra(EXTRA_NAVIGATION_INSTRUCTION, navigationInfoSpec.instruction)
+                .putExtra(EXTRA_NAVIGATION_NEXT_ACTION, navigationInfoSpec.nextAction)
+                .putExtra(EXTRA_NAVIGATION_DISTANCE_TO_TURN, navigationInfoSpec.distanceToTurn)
+                .putExtra(EXTRA_NAVIGATION_ETA, navigationInfoSpec.ETA);
+        invokeService(intent);
+    }
+
+    @Override
+    public void onInstallApp(Uri uri) {
+        Intent intent = createIntent().setAction(ACTION_INSTALL)
+                .putExtra(EXTRA_URI, uri);
+        invokeService(intent);
+    }
+
+    @Override
+    public void onAppInfoReq() {
+        Intent intent = createIntent().setAction(ACTION_REQUEST_APPINFO);
+        invokeService(intent);
+    }
+
+    @Override
+    public void onAppStart(UUID uuid, boolean start) {
+        Intent intent = createIntent().setAction(ACTION_STARTAPP)
+                .putExtra(EXTRA_APP_UUID, uuid)
+                .putExtra(EXTRA_APP_START, start);
+        invokeService(intent);
+    }
+
+    @Override
+    public void onAppDownload(UUID uuid) {
+        Intent intent = createIntent().setAction(ACTION_DOWNLOADAPP)
+                .putExtra(EXTRA_APP_UUID, uuid);
+        invokeService(intent);
+    }
+
+    @Override
+    public void onAppDelete(UUID uuid) {
+        Intent intent = createIntent().setAction(ACTION_DELETEAPP)
+                .putExtra(EXTRA_APP_UUID, uuid);
+        invokeService(intent);
+    }
+
+    @Override
+    public void onAppConfiguration(UUID uuid, String config, Integer id) {
+        Intent intent = createIntent().setAction(ACTION_APP_CONFIGURE)
+                .putExtra(EXTRA_APP_UUID, uuid)
+                .putExtra(EXTRA_APP_CONFIG, config);
+
+        if (id != null) {
+            intent.putExtra(EXTRA_APP_CONFIG_ID, id);
+        }
+        invokeService(intent);
+    }
+
+    @Override
+    public void onAppReorder(UUID[] uuids) {
+        Intent intent = createIntent().setAction(ACTION_APP_REORDER)
+                .putExtra(EXTRA_APP_UUID, uuids);
+        invokeService(intent);
+    }
+
+    @Override
+    public void onFetchRecordedData(int dataTypes) {
+        Intent intent = createIntent().setAction(ACTION_FETCH_RECORDED_DATA)
+                .putExtra(EXTRA_RECORDED_DATA_TYPES, dataTypes);
+        invokeService(intent);
+    }
+
+    @Override
+    public void onReset(int flags) {
+        Intent intent = createIntent().setAction(ACTION_RESET)
+                .putExtra(EXTRA_RESET_FLAGS, flags);
+        invokeService(intent);
+    }
+
+    @Override
+    public void onHeartRateTest() {
+        Intent intent = createIntent().setAction(ACTION_HEARTRATE_TEST);
+        invokeService(intent);
+    }
+
+    @Override
+    public void onFindDevice(boolean start) {
+        Intent intent = createIntent().setAction(ACTION_FIND_DEVICE)
+                .putExtra(EXTRA_FIND_START, start);
+        invokeService(intent);
+    }
+
+    @Override
+    public void onFindPhone(final boolean start) {
+        Intent intent = createIntent().setAction(ACTION_PHONE_FOUND)
+                        .putExtra(EXTRA_FIND_START, start);
+        invokeService(intent);
+    }
+
+    @Override
+    public void onSetConstantVibration(int intensity) {
+        Intent intent = createIntent().setAction(ACTION_SET_CONSTANT_VIBRATION)
+                .putExtra(EXTRA_VIBRATION_INTENSITY, intensity);
+        invokeService(intent);
+    }
+
+    @Override
+    public void onScreenshotReq() {
+        Intent intent = createIntent().setAction(ACTION_REQUEST_SCREENSHOT);
+        invokeService(intent);
+    }
+
+    @Override
+    public void onEnableRealtimeSteps(boolean enable) {
+        Intent intent = createIntent().setAction(ACTION_ENABLE_REALTIME_STEPS)
+                .putExtra(EXTRA_BOOLEAN_ENABLE, enable);
+        invokeService(intent);
+    }
+
+    @Override
+    public void onEnableHeartRateSleepSupport(boolean enable) {
+        Intent intent = createIntent().setAction(ACTION_ENABLE_HEARTRATE_SLEEP_SUPPORT)
+                .putExtra(EXTRA_BOOLEAN_ENABLE, enable);
+        invokeService(intent);
+    }
+
+    @Override
+    public void onSetHeartRateMeasurementInterval(int seconds) {
+        Intent intent = createIntent().setAction(ACTION_SET_HEARTRATE_MEASUREMENT_INTERVAL)
+                .putExtra(EXTRA_INTERVAL_SECONDS, seconds);
+        invokeService(intent);
+    }
+
+    @Override
+    public void onEnableRealtimeHeartRateMeasurement(boolean enable) {
+        Intent intent = createIntent().setAction(ACTION_ENABLE_REALTIME_HEARTRATE_MEASUREMENT)
+                .putExtra(EXTRA_BOOLEAN_ENABLE, enable);
+        invokeService(intent);
+    }
+
+    @Override
+    public void onAddCalendarEvent(CalendarEventSpec calendarEventSpec) {
+        Intent intent = createIntent().setAction(ACTION_ADD_CALENDAREVENT)
+                .putExtra(EXTRA_CALENDAREVENT_ID, calendarEventSpec.id)
+                .putExtra(EXTRA_CALENDAREVENT_TYPE, calendarEventSpec.type)
+                .putExtra(EXTRA_CALENDAREVENT_TIMESTAMP, calendarEventSpec.timestamp)
+                .putExtra(EXTRA_CALENDAREVENT_DURATION, calendarEventSpec.durationInSeconds)
+                .putExtra(EXTRA_CALENDAREVENT_ALLDAY, calendarEventSpec.allDay)
+                .putExtra(EXTRA_CALENDAREVENT_REMINDERS, calendarEventSpec.reminders)
+                .putExtra(EXTRA_CALENDAREVENT_TITLE, calendarEventSpec.title)
+                .putExtra(EXTRA_CALENDAREVENT_DESCRIPTION, calendarEventSpec.description)
+                .putExtra(EXTRA_CALENDAREVENT_CALNAME, calendarEventSpec.calName)
+                .putExtra(EXTRA_CALENDAREVENT_COLOR, calendarEventSpec.color)
+                .putExtra(EXTRA_CALENDAREVENT_LOCATION, calendarEventSpec.location);
+        invokeService(intent);
+    }
+
+    @Override
+    public void onDeleteCalendarEvent(byte type, long id) {
+        Intent intent = createIntent().setAction(ACTION_DELETE_CALENDAREVENT)
+                .putExtra(EXTRA_CALENDAREVENT_TYPE, type)
+                .putExtra(EXTRA_CALENDAREVENT_ID, id);
+        invokeService(intent);
+    }
+
+    @Override
+    public void onSendConfiguration(String config) {
+        Intent intent = createIntent().setAction(ACTION_SEND_CONFIGURATION)
+                .putExtra(EXTRA_CONFIG, config);
+        invokeService(intent);
+    }
+
+    @Override
+    public void onReadConfiguration(String config) {
+        Intent intent = createIntent().setAction(ACTION_READ_CONFIGURATION)
+                .putExtra(EXTRA_CONFIG, config);
+        invokeService(intent);
+    }
+
+    @Override
+    public void onTestNewFunction() {
+        Intent intent = createIntent().setAction(ACTION_TEST_NEW_FUNCTION);
+        invokeService(intent);
+    }
+
+    @Override
+    public void onSendWeather(ArrayList<WeatherSpec> weatherSpecs) {
+        Intent intent = createIntent().setAction(ACTION_SEND_WEATHER)
+                .putExtra(EXTRA_WEATHER, weatherSpecs);
+        invokeService(intent);
+    }
+
+    /**
+     * Returns contact DisplayName by call number
+     *
+     * @param number contact number
+     * @return contact DisplayName, if found it
+     */
+    private String getContactDisplayNameByNumber(String number) {
+        Uri uri = Uri.withAppendedPath(ContactsContract.PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI, Uri.encode(number));
+
+        String name = number;
+
+        if (number == null || number.equals("")) {
+            return name;
+        }
+
+        try (Cursor contactLookup = mContext.getContentResolver().query(uri, null, null, null, null)) {
+            if (contactLookup != null && contactLookup.getCount() > 0) {
+                contactLookup.moveToNext();
+                name = contactLookup.getString(contactLookup.getColumnIndex(ContactsContract.Data.DISPLAY_NAME));
+            }
+        } catch (SecurityException e) {
+            // ignore, just return name below
+        }
+
+        return name;
+    }
+
+    @Override
+    public void onSetFmFrequency(float frequency) {
+        Intent intent = createIntent().setAction(ACTION_SET_FM_FREQUENCY)
+                .putExtra(EXTRA_FM_FREQUENCY, frequency);
+        invokeService(intent);
+    }
+
+    @Override
+    public void onSetLedColor(int color) {
+        Intent intent = createIntent().setAction(ACTION_SET_LED_COLOR)
+                .putExtra(EXTRA_LED_COLOR, color);
+        invokeService(intent);
+    }
+
+    @Override
+    public void onPowerOff() {
+        Intent intent = createIntent().setAction(ACTION_POWER_OFF);
+        invokeService(intent);
+    }
+
+    @Override
+    public void onSetGpsLocation(Location location) {
+        Intent intent = createIntent().setAction(ACTION_SET_GPS_LOCATION);
+        intent.putExtra(EXTRA_GPS_LOCATION, location);
+        invokeService(intent);
+    }
+
+    @Override
+    public void onSleepAsAndroidAction(String action, Bundle extras) {
+        Intent intent = createIntent().setAction(ACTION_SLEEP_AS_ANDROID);
+        intent.putExtra(EXTRA_SLEEP_AS_ANDROID_ACTION, action);
+        if (extras != null) {
+            intent.putExtras(extras);
+        }
+        invokeService(intent);
+    }
+
+    @Override
+    public void onCameraStatusChange(GBDeviceEventCameraRemote.Event event, String filename) {
+        Intent intent = createIntent().setAction(ACTION_CAMERA_STATUS_CHANGE);
+        intent.putExtra(EXTRA_CAMERA_EVENT, GBDeviceEventCameraRemote.eventToInt(event));
+        if (event == GBDeviceEventCameraRemote.Event.TAKE_PICTURE)
+            intent.putExtra(EXTRA_CAMERA_FILENAME, filename);
+        invokeService(intent);
+    }
+
+    @Override
+    public void onMusicListReq() {
+        Intent intent = createIntent().setAction(ACTION_REQUEST_MUSIC_LIST);
+        invokeService(intent);
+    }
+
+    @Override
+    public void onMusicOperation(int operation, int playlistIndex, String playlistName, ArrayList<Integer> musicIds) {
+        Intent intent = createIntent().setAction(ACTION_REQUEST_MUSIC_OPERATION);
+        intent.putExtra("operation", operation);
+        intent.putExtra("playlistIndex", playlistIndex);
+        intent.putExtra("playlistName", playlistName);
+        intent.putExtra("musicIds", musicIds);
+        invokeService(intent);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBSummaryOfDay.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBSummaryOfDay.java
new file mode 100644
index 0000000..eeb3397
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBSummaryOfDay.java
@@ -0,0 +1,44 @@
+/*  Copyright (C) 2015-2024 Carsten Pfeiffer
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.impl;
+
+import nodomain.freeyourgadget.gadgetbridge.model.SummaryOfDay;
+
+public class GBSummaryOfDay implements SummaryOfDay {
+    private byte provider;
+    private int steps;
+    private int dayStartWakeupTime;
+    private int dayEndFallAsleepTime;
+
+    public byte getProvider() {
+        return provider;
+    }
+
+    public int getSteps() {
+        return steps;
+    }
+
+    public int getDayStartWakeupTime() {
+        return dayStartWakeupTime;
+    }
+
+    public int getDayEndFallAsleepTime() {
+        return dayEndFallAsleepTime;
+    }
+
+
+}
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 0000000..56a26bf
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/AbstractNotificationPattern.java
@@ -0,0 +1,24 @@
+/*  Copyright (C) 2023-2024 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 <https://www.gnu.org/licenses/>. */
+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/ActivityAmount.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivityAmount.java
new file mode 100644
index 0000000..e89909f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivityAmount.java
@@ -0,0 +1,95 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, Carsten Pfeiffer, Pavel Elagin
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.model;
+
+import java.util.Date;
+
+public class ActivityAmount {
+    private final ActivityKind activityKind;
+    private short percent;
+    private long totalSeconds;
+    private long totalSteps;
+    private long totalDistance;
+    private long totalActiveCalories;
+    private Date startDate = null;
+    private Date endDate = null;
+
+    public ActivityAmount(ActivityKind activityKind) {
+        this.activityKind = activityKind;
+    }
+
+    public void addSeconds(long seconds) {
+        totalSeconds += seconds;
+    }
+
+    public void addSteps(long steps) {
+        totalSteps += steps;
+    }
+
+    public void addDistance(long distance) {
+        totalDistance += distance;
+    }
+
+    public void addActiveCalories(long activeCalories) {
+        totalActiveCalories += activeCalories;
+    }
+
+    public long getTotalSeconds() {
+        return totalSeconds;
+    }
+
+    public long getTotalSteps() {
+        return totalSteps;
+    }
+
+    public long getTotalDistance() {
+        return totalDistance;
+    }
+
+    public long getTotalActiveCalories() {
+        return totalActiveCalories;
+    }
+
+    public ActivityKind getActivityKind() {
+        return activityKind;
+    }
+
+    public short getPercent() {
+        return percent;
+    }
+
+    public void setPercent(short percent) {
+        this.percent = percent;
+    }
+
+    public Date getStartDate() {
+        return startDate;
+    }
+
+    public void setStartDate(int seconds) {
+        if(startDate == null)
+            this.startDate = new Date((long)seconds * 1000);
+    }
+
+    public Date getEndDate() {
+        return endDate;
+    }
+
+    public void setEndDate(int seconds) {
+        this.endDate = new Date((long)seconds * 1000);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivityAmounts.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivityAmounts.java
new file mode 100644
index 0000000..812983e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivityAmounts.java
@@ -0,0 +1,45 @@
+/*  Copyright (C) 2015-2024 Carsten Pfeiffer
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.model;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ActivityAmounts {
+    private final List<ActivityAmount> amounts = new ArrayList<>(4);
+    private long totalSeconds;
+
+    public void addAmount(ActivityAmount amount) {
+        amounts.add(amount);
+        totalSeconds += amount.getTotalSeconds();
+    }
+
+    public List<ActivityAmount> getAmounts() {
+        return amounts;
+    }
+
+    public long getTotalSeconds() {
+        return totalSeconds;
+    }
+
+    public void calculatePercentages() {
+        for (ActivityAmount amount : amounts) {
+            float fraction = amount.getTotalSeconds() / (float) totalSeconds;
+            amount.setPercent((short) (fraction * 100));
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivityKind.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivityKind.java
new file mode 100644
index 0000000..89e5f4a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivityKind.java
@@ -0,0 +1,360 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, Carsten Pfeiffer, Daniele
+    Gobbetti, Jos茅 Rebelo, Petr Van臎k, Sebastian Krey, Your Name
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.model;
+
+import android.content.Context;
+
+import androidx.annotation.DrawableRes;
+import androidx.annotation.StringRes;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+
+public enum ActivityKind {
+    NOT_MEASURED(-1, R.string.activity_type_not_measured, R.drawable.ic_activity_not_measured),
+    UNKNOWN(0x00000000, R.string.activity_type_unknown),
+    ACTIVITY(0x00000001, R.string.activity_type_activity),
+    LIGHT_SLEEP(0x00000002, R.string.activity_type_light_sleep, R.drawable.ic_activity_sleep),
+    DEEP_SLEEP(0x00000004, R.string.activity_type_deep_sleep, R.drawable.ic_activity_sleep),
+    NOT_WORN(0x00000008, R.string.activity_type_not_worn),
+    RUNNING(0x00000010, R.string.activity_type_running, R.drawable.ic_activity_running),
+    WALKING(0x00000020, R.string.activity_type_walking, R.drawable.ic_activity_walking),
+    SWIMMING(0x00000040, R.string.activity_type_swimming, R.drawable.ic_activity_swimming),
+    CYCLING(0x00000080, R.string.activity_type_biking, R.drawable.ic_activity_biking),
+    TREADMILL(0x00000100, R.string.activity_type_treadmill, R.drawable.ic_activity_threadmill),
+    EXERCISE(0x00000200, R.string.activity_type_exercise, R.drawable.ic_activity_exercise),
+    SWIMMING_OPENWATER(0x00000400, R.string.activity_type_swimming_openwater, R.drawable.ic_activity_swimming),
+    INDOOR_CYCLING(0x00000800, R.string.activity_type_indoor_cycling, R.drawable.ic_activity_bike_trainer),
+    ELLIPTICAL_TRAINER(0x00001000, R.string.activity_type_elliptical_trainer, R.drawable.ic_activity_eliptical),
+    JUMP_ROPING(0x00002000, R.string.activity_type_jump_roping, R.drawable.ic_activity_rope_jump),
+    YOGA(0x00004000, R.string.activity_type_yoga, R.drawable.ic_activity_yoga),
+    SOCCER(0x00008000, R.string.activity_type_soccer, R.drawable.ic_activity_soccer),
+    ROWING_MACHINE(0x00010000, R.string.activity_type_rowing_machine, R.drawable.ic_activity_rowing),
+    CRICKET(0x00020000, R.string.activity_type_cricket, R.drawable.ic_activity_cricket),
+    BASKETBALL(0x00040000, R.string.activity_type_basketball, R.drawable.ic_activity_basketball),
+    PINGPONG(0x00080000, R.string.activity_type_pingpong, R.drawable.ic_activity_pingpong),
+    BADMINTON(0x00100000, R.string.activity_type_badminton, R.drawable.ic_activity_badmington),
+    STRENGTH_TRAINING(0x00200000, R.string.activity_type_strength_training, R.drawable.ic_activity_dumbbell),
+    HIKING(0x00400000, R.string.activity_type_hiking, R.drawable.ic_activity_hiking),
+    CLIMBING(0x00800000, R.string.activity_type_climbing, R.drawable.ic_activity_climbing),
+    REM_SLEEP(0x01000000, R.string.abstract_chart_fragment_kind_rem_sleep, R.drawable.ic_activity_sleep),
+    SLEEP_ANY(0x00000002 | 0x00000004 | 0x01000000 | 0x02000000, R.string.menuitem_sleep, R.drawable.ic_activity_sleep),
+    AWAKE_SLEEP(0x02000000, R.string.abstract_chart_fragment_kind_awake_sleep, R.drawable.ic_activity_sleep),
+
+    // FIXME: Deprecate these - they're just kept around while we do not support reading from the old db
+    VIVOMOVE_HR_TRANSITION(0x00000001 | 0x00000010 | 0x00000020 | 0x00000200 | 0x00000040, R.string.transition),
+
+    // Non-legacy activity kinds after 0x04000000
+    NAVIGATE(0x04000000, R.string.activity_type_navigate, R.drawable.ic_navigation),
+    INDOOR_TRACK(0x04000001, R.string.activity_type_indoor_track),
+    HANDCYCLING(0x04000002, R.string.activity_type_handcycling),
+    E_BIKE(0x04000003, R.string.activity_type_e_bike, R.drawable.ic_activity_biking),
+    BIKE_COMMUTE(0x04000004, R.string.activity_type_bike_commute, R.drawable.ic_activity_biking),
+    HANDCYCLING_INDOOR(0x04000005, R.string.activity_type_handcycling_indoor),
+    TRANSITION(0x04000006, R.string.activity_type_transition),
+    FITNESS_EQUIPMENT(0x04000007, R.string.activity_type_fitness_equipment),
+    STAIR_STEPPER(0x04000008, R.string.activity_type_stair_stepper, R.drawable.ic_activity_stair_stepper),
+    PILATES(0x04000009, R.string.activity_type_pilates, R.drawable.ic_activity_pilates),
+    POOL_SWIM(0x0400000a, R.string.activity_type_pool_swimming, R.drawable.ic_activity_swimming),
+    TENNIS(0x0400000b, R.string.activity_type_tennis),
+    PLATFORM_TENNIS(0x0400000c, R.string.activity_type_platform_tennis),
+    TABLE_TENNIS(0x0400000d, R.string.activity_type_table_tennis, R.drawable.ic_activity_pingpong),
+    AMERICAN_FOOTBALL(0x0400000e, R.string.activity_type_american_football),
+    TRAINING(0x0400000f, R.string.activity_type_training),
+    CARDIO(0x04000010, R.string.activity_type_cardio, R.drawable.ic_heartrate),
+    BREATHWORK(0x04000011, R.string.activity_type_breathwork),
+    INDOOR_WALKING(0x04000012, R.string.activity_type_indoor_walking, R.drawable.ic_activity_walking),
+    XC_CLASSIC_SKI(0x04000013, R.string.activity_type_xc_classic_ski),
+    SKIING(0x04000014, R.string.activity_type_skiing),
+    SNOWBOARDING(0x04000015, R.string.activity_type_snowboarding),
+    ROWING(0x04000016, R.string.activity_type_rowing, R.drawable.ic_activity_rowing),
+    MOUNTAINEERING(0x04000017, R.string.activity_type_mountaineering, R.drawable.ic_activity_climbing),
+    MULTISPORT(0x04000019, R.string.activity_type_multisport),
+    PADDLING(0x0400001a, R.string.activity_type_paddling, R.drawable.ic_activity_rowing),
+    FLYING(0x0400001b, R.string.activity_type_flying),
+    MOTORCYCLING(0x0400001d, R.string.activity_type_motorcycling),
+    BOATING(0x0400001e, R.string.activity_type_boating, R.drawable.ic_activity_boating),
+    DRIVING(0x0400001f, R.string.activity_type_driving),
+    GOLF(0x04000020, R.string.activity_type_golf),
+    HANG_GLIDING(0x04000021, R.string.activity_type_hang_gliding),
+    HUNTING(0x04000023, R.string.activity_type_hunting),
+    FISHING(0x04000024, R.string.activity_type_fishing),
+    INLINE_SKATING(0x04000025, R.string.activity_type_inline_skating),
+    ROCK_CLIMBING(0x04000026, R.string.activity_type_rock_climbing, R.drawable.ic_activity_rock_climbing),
+    CLIMB_INDOOR(0x04000027, R.string.activity_type_climb_indoor),
+    BOULDERING(0x04000028, R.string.activity_type_bouldering),
+    SAIL_RACE(0x0400002a, R.string.activity_type_sail_race, R.drawable.ic_activity_sailing),
+    SAIL_EXPEDITION(0x0400002b, R.string.activity_type_sail_expedition, R.drawable.ic_activity_sailing),
+    ICE_SKATING(0x0400002c, R.string.activity_type_ice_skating, R.drawable.ic_activity_ice_skating),
+    SKY_DIVING(0x0400002d, R.string.activity_type_sky_diving),
+    SNOWSHOE(0x0400002e, R.string.activity_type_snowshoe),
+    SNOWMOBILING(0x0400002f, R.string.activity_type_snowmobiling),
+    STAND_UP_PADDLEBOARDING(0x04000030, R.string.activity_type_stand_up_paddleboarding, R.drawable.ic_activity_sup),
+    SURFING(0x04000031, R.string.activity_type_surfing, R.drawable.ic_activity_surfing),
+    WAKEBOARDING(0x04000032, R.string.activity_type_wakeboarding, R.drawable.ic_activity_wakeboarding),
+    WATER_SKIING(0x04000033, R.string.activity_type_water_skiing, R.drawable.ic_activity_waterskiing),
+    KAYAKING(0x04000034, R.string.activity_type_kayaking, R.drawable.ic_activity_rowing),
+    RAFTING(0x04000035, R.string.activity_type_rafting, R.drawable.ic_activity_rowing),
+    WINDSURFING(0x04000036, R.string.activity_type_windsurfing, R.drawable.ic_activity_windsurfing),
+    KITESURFING(0x04000037, R.string.activity_type_kitesurfing, R.drawable.ic_activity_kitesurfing),
+    TACTICAL(0x04000038, R.string.activity_type_tactical),
+    JUMPMASTER(0x04000039, R.string.activity_type_jumpmaster),
+    BOXING(0x0400003a, R.string.activity_type_boxing),
+    FLOOR_CLIMBING(0x0400003b, R.string.activity_type_floor_climbing),
+    BASEBALL(0x0400003c, R.string.activity_type_baseball),
+    SOFTBALL(0x0400003d, R.string.activity_type_softball),
+    SOFTBALL_SLOW_PITCH(0x0400003e, R.string.activity_type_softball_slow_pitch),
+    SHOOTING(0x0400003f, R.string.activity_type_shooting),
+    AUTO_RACING(0x04000040, R.string.activity_type_auto_racing),
+    WINTER_SPORT(0x04000041, R.string.activity_type_winter_sport),
+    GRINDING(0x04000042, R.string.activity_type_grinding),
+    HEALTH_SNAPSHOT(0x04000043, R.string.activity_type_health_snapshot),
+    MARINE(0x04000044, R.string.activity_type_marine),
+    HIIT(0x04000045, R.string.activity_type_hiit),
+    VIDEO_GAMING(0x04000046, R.string.activity_type_video_gaming, R.drawable.ic_videogame),
+    RACKET(0x04000047, R.string.activity_type_racket),
+    PICKLEBALL(0x04000048, R.string.activity_type_pickleball),
+    PADEL(0x04000049, R.string.activity_type_padel),
+    SQUASH(0x0400004a, R.string.activity_type_squash),
+    RACQUETBALL(0x0400004b, R.string.activity_type_racquetball),
+    PUSH_WALK_SPEED(0x0400004c, R.string.activity_type_push_walk_speed),
+    INDOOR_PUSH_WALK_SPEED(0x0400004d, R.string.activity_type_indoor_push_walk_speed),
+    PUSH_RUN_SPEED(0x0400004e, R.string.activity_type_push_run_speed),
+    INDOOR_PUSH_RUN_SPEED(0x0400004f, R.string.activity_type_indoor_push_run_speed),
+    MEDITATION(0x04000050, R.string.activity_type_meditation),
+    PARA_SPORT(0x04000051, R.string.activity_type_para_sport),
+    DISC_GOLF(0x04000052, R.string.activity_type_disc_golf),
+    ULTIMATE_DISC(0x04000053, R.string.activity_type_ultimate_disc),
+    TEAM_SPORT(0x04000054, R.string.activity_type_team_sport),
+    RUGBY(0x04000055, R.string.activity_type_rugby),
+    HOCKEY(0x04000056, R.string.activity_type_hockey),
+    LACROSSE(0x04000057, R.string.activity_type_lacrosse),
+    VOLLEYBALL(0x04000058, R.string.activity_type_volleyball),
+    WATER_TUBING(0x04000059, R.string.activity_type_water_tubing, R.drawable.ic_activity_watertubing),
+    WAKESURFING(0x0400005a, R.string.activity_type_wakesurfing),
+    MIXED_MARTIAL_ARTS(0x0400005b, R.string.activity_type_mixed_martial_arts), // aka MMA
+    DANCE(0x0400005c, R.string.activity_type_dance),
+    MOUNTAIN_HIKE(0x040000e2, R.string.activity_type_mountain_hike, R.drawable.ic_activity_climbing),
+    CROSS_TRAINER(0x0400005d, R.string.activity_type_cross_trainer),
+    FREE_TRAINING(0x0400005e, R.string.activity_type_free_training, R.drawable.ic_activity_free_training),
+    DYNAMIC_CYCLE(0x0400005f, R.string.activity_type_dynamic_cycle),
+    KICKBOXING(0x04000060, R.string.activity_type_kickboxing),
+    FITNESS_EXERCISES(0x04000061, R.string.activity_type_fitness_exercises),
+    CROSSFIT(0x04000062, R.string.activity_type_crossfit),
+    FUNCTIONAL_TRAINING(0x04000063, R.string.activity_type_functional_training),
+    PHYSICAL_TRAINING(0x04000064, R.string.activity_type_physical_training),
+    TAEKWONDO(0x04000065, R.string.activity_type_taekwondo),
+    TAE_BO(0x04000066, R.string.activity_type_tae_bo),
+    CROSS_COUNTRY_RUNNING(0x04000067, R.string.activity_type_cross_country_running),
+    KARATE(0x04000068, R.string.activity_type_karate),
+    FENCING(0x04000069, R.string.activity_type_fencing),
+    CORE_TRAINING(0x0400006a, R.string.activity_type_core_training),
+    KENDO(0x0400006b, R.string.activity_type_kendo),
+    HORIZONTAL_BAR(0x0400006c, R.string.activity_type_horizontal_bar),
+    PARALLEL_BAR(0x0400006d, R.string.activity_type_parallel_bar),
+    COOLDOWN(0x0400006e, R.string.activity_type_cooldown),
+    CROSS_TRAINING(0x0400006f, R.string.activity_type_cross_training),
+    SIT_UPS(0x04000070, R.string.activity_type_sit_ups),
+    FITNESS_GAMING(0x04000071, R.string.activity_type_fitness_gaming),
+    AEROBIC_EXERCISE(0x04000072, R.string.activity_type_aerobic_exercise),
+    ROLLING(0x04000073, R.string.activity_type_rolling),
+    FLEXIBILITY(0x04000074, R.string.activity_type_flexibility),
+    GYMNASTICS(0x04000075, R.string.activity_type_gymnastics),
+    TRACK_AND_FIELD(0x04000076, R.string.activity_type_track_and_field),
+    PUSH_UPS(0x04000077, R.string.activity_type_push_ups),
+    BATTLE_ROPE(0x04000078, R.string.activity_type_battle_rope),
+    SMITH_MACHINE(0x04000079, R.string.activity_type_smith_machine),
+    PULL_UPS(0x0400007a, R.string.activity_type_pull_ups),
+    PLANK(0x0400007b, R.string.activity_type_plank),
+    JAVELIN(0x0400007c, R.string.activity_type_javelin),
+    LONG_JUMP(0x0400007d, R.string.activity_type_long_jump),
+    HIGH_JUMP(0x0400007e, R.string.activity_type_high_jump),
+    TRAMPOLINE(0x0400007f, R.string.activity_type_trampoline),
+    DUMBBELL(0x04000080, R.string.activity_type_dumbbell),
+    BELLY_DANCE(0x04000081, R.string.activity_type_belly_dance),
+    JAZZ_DANCE(0x04000082, R.string.activity_type_jazz_dance),
+    LATIN_DANCE(0x04000083, R.string.activity_type_latin_dance),
+    BALLET(0x04000084, R.string.activity_type_ballet),
+    STREET_DANCE(0x04000085, R.string.activity_type_street_dance),
+    ZUMBA(0x04000086, R.string.activity_type_zumba),
+    ROLLER_SKATING(0x04000087, R.string.activity_type_roller_skating),
+    MARTIAL_ARTS(0x04000088, R.string.activity_type_martial_arts),
+    TAI_CHI(0x04000089, R.string.activity_type_tai_chi),
+    HULA_HOOPING(0x0400008a, R.string.activity_type_hula_hooping, R.drawable.ic_activity_hula_hoop),
+    DISC_SPORTS(0x0400008b, R.string.activity_type_disc_sports),
+    DARTS(0x0400008c, R.string.activity_type_darts),
+    ARCHERY(0x0400008d, R.string.activity_type_archery, R.drawable.ic_activity_archery),
+    HORSE_RIDING(0x0400008e, R.string.activity_type_horse_riding),
+    KITE_FLYING(0x0400008f, R.string.activity_type_kite_flying),
+    SWING(0x04000090, R.string.activity_type_swing),
+    STAIRS(0x04000091, R.string.activity_type_stairs, R.drawable.ic_activity_stairs),
+    MIND_AND_BODY(0x04000092, R.string.activity_type_mind_and_body),
+    WRESTLING(0x04000093, R.string.activity_type_wrestling),
+    KABADDI(0x04000094, R.string.activity_type_kabaddi),
+    KARTING(0x04000095, R.string.activity_type_karting),
+    BILLIARDS(0x04000096, R.string.activity_type_billiards),
+    BOWLING(0x04000097, R.string.activity_type_bowling, R.drawable.ic_activity_bowling),
+    SHUTTLECOCK(0x04000098, R.string.activity_type_shuttlecock),
+    HANDBALL(0x04000099, R.string.activity_type_handball),
+    DODGEBALL(0x0400009a, R.string.activity_type_dodgeball),
+    AUSTRALIAN_FOOTBALL(0x0400009b, R.string.activity_type_australian_football),
+    LACROSS(0x0400009c, R.string.activity_type_lacross),
+    SHOT(0x0400009d, R.string.activity_type_shot),
+    BEACH_SOCCER(0x0400009e, R.string.activity_type_beach_soccer),
+    BEACH_VOLLEYBALL(0x0400009f, R.string.activity_type_beach_volleyball),
+    GATEBALL(0x040000a0, R.string.activity_type_gateball),
+    SEPAK_TAKRAW(0x040000a1, R.string.activity_type_sepak_takraw),
+    SAILING(0x040000a2, R.string.activity_type_sailing, R.drawable.ic_activity_sailing),
+    JET_SKIING(0x040000a3, R.string.activity_type_jet_skiing),
+    SKATING(0x040000a4, R.string.activity_type_skating),
+    ICE_HOCKEY(0x040000a5, R.string.activity_type_ice_hockey),
+    CURLING(0x040000a6, R.string.activity_type_curling, R.drawable.ic_activity_curling),
+    CROSS_COUNTRY_SKIING(0x040000a8, R.string.activity_type_cross_country_skiing),
+    SNOW_SPORTS(0x040000a9, R.string.activity_type_snow_sports),
+    LUGE(0x040000ab, R.string.activity_type_luge),
+    SKATEBOARDING(0x040000ac, R.string.activity_type_skateboarding),
+    PARACHUTING(0x040000ae, R.string.activity_type_parachuting),
+    PARKOUR(0x040000af, R.string.activity_type_parkour),
+    INDOOR_RUNNING(0x040000b0, R.string.activity_type_indoor_running, R.drawable.ic_activity_indoor_running),
+    OUTDOOR_RUNNING(0x040000b1, R.string.activity_type_outdoor_running, R.drawable.ic_activity_running),
+    OUTDOOR_WALKING(0x040000b2, R.string.activity_type_outdoor_walking, R.drawable.ic_activity_outdoor_walking),
+    OUTDOOR_CYCLING(0x040000b3, R.string.activity_type_outdoor_cycling, R.drawable.ic_activity_biking),
+    AEROBIC_COMBO(0x040000b4, R.string.activity_type_aerobic_combo),
+    AEROBICS(0x040000b5, R.string.activity_type_aerobics),
+    AIR_WALKER(0x040000b6, R.string.activity_type_air_walker),
+    ARTISTIC_SWIMMING(0x040000b7, R.string.activity_type_artistic_swimming),
+    BALLROOM_DANCE(0x040000b8, R.string.activity_type_ballroom_dance),
+    BMX(0x040000b9, R.string.activity_type_bmx),
+    BOARD_GAME(0x040000ba, R.string.activity_type_board_game),
+    BOCCE(0x040000bb, R.string.activity_type_bocce),
+    BREAKING(0x040000bc, R.string.activity_type_breaking),
+    BRIDGE(0x040000bd, R.string.activity_type_bridge),
+    CARDIO_COMBAT(0x040000be, R.string.activity_type_cardio_combat),
+    CHECKERS(0x040000bf, R.string.activity_type_checkers),
+    CHESS(0x040000c0, R.string.activity_type_chess),
+    DRAGON_BOAT(0x040000c1, R.string.activity_type_dragon_boat),
+    ESPORTS(0x040000c2, R.string.activity_type_esports),
+    FINSWIMMING(0x040000c3, R.string.activity_type_finswimming),
+    FLOWRIDING(0x040000c4, R.string.activity_type_flowriding),
+    FOLK_DANCE(0x040000c5, R.string.activity_type_folk_dance),
+    FRISBEE(0x040000c6, R.string.activity_type_frisbee, R.drawable.ic_activity_frisbee),
+    FUTSAL(0x040000c7, R.string.activity_type_futsal),
+    HACKY_SACK(0x040000c8, R.string.activity_type_hacky_sack),
+    HIP_HOP(0x040000c9, R.string.activity_type_hip_hop),
+    HULA_HOOP(0x040000ca, R.string.activity_type_hula_hoop, R.drawable.ic_activity_hula_hoop),
+    INDOOR_FITNESS(0x040000cb, R.string.activity_type_indoor_fitness),
+    INDOOR_ICE_SKATING(0x040000cc, R.string.activity_type_indoor_ice_skating, R.drawable.ic_activity_ice_skating),
+    JAI_ALAI(0x040000cd, R.string.activity_type_jai_alai),
+    JUDO(0x040000ce, R.string.activity_type_judo),
+    JUJITSU(0x040000cf, R.string.activity_type_jujitsu),
+    MASS_GYMNASTICS(0x040000d0, R.string.activity_type_mass_gymnastics),
+    MODERN_DANCE(0x040000d1, R.string.activity_type_modern_dance),
+    MUAY_THAI(0x040000d2, R.string.activity_type_muay_thai),
+    PARALLEL_BARS(0x040000d3, R.string.activity_type_parallel_bars),
+    POLE_DANCE(0x040000d4, R.string.activity_type_pole_dance),
+    RACE_WALKING(0x040000d5, R.string.activity_type_race_walking),
+    SHUFFLEBOARD(0x040000d6, R.string.activity_type_shuffleboard),
+    SNORKELING(0x040000d7, R.string.activity_type_snorkeling, R.drawable.ic_activity_snorkeling),
+    SOMATOSENSORY_GAME(0x040000d8, R.string.activity_type_somatosensory_game),
+    SPINNING(0x040000d9, R.string.activity_type_spinning),
+    SQUARE_DANCE(0x040000da, R.string.activity_type_square_dance),
+    STAIR_CLIMBER(0x040000db, R.string.activity_type_stair_climber),
+    STEPPER(0x040000dc, R.string.activity_type_stepper),
+    STRETCHING(0x040000dd, R.string.activity_type_stretching, R.drawable.ic_activity_stretching),
+    TABLE_FOOTBALL(0x040000de, R.string.activity_type_table_football),
+    TUG_OF_WAR(0x040000df, R.string.activity_type_tug_of_war),
+    WALL_BALL(0x040000e0, R.string.activity_type_wall_ball),
+    WATER_POLO(0x040000e1, R.string.activity_type_water_polo),
+    WEIQI(0x040000e3, R.string.activity_type_weiqi),
+    FREE_SPARRING(0x040000e4, R.string.activity_type_free_sparring),
+    BODY_COMBAT(0x040000e5, R.string.activity_type_body_combat),
+    PLAZA_DANCING(0x040000e6, R.string.activity_type_plaza_dancing),
+    LASER_TAG(0x040000e7, R.string.activity_type_laser_tag),
+    OBSTACLE_RACE(0x040000e8, R.string.activity_type_obstacle_race, R.drawable.ic_activity_obstacle_race),
+    BILLIARD_POOL(0x040000e9, R.string.activity_type_billiard_pool, R.drawable.ic_activity_billiard_pool),
+    CANOEING(0x040000ea, R.string.activity_type_canoeing),
+    WATER_SCOOTER(0x040000eb, R.string.activity_type_water_scooter),
+    BOBSLEIGH(0x040000ec, R.string.activity_type_bobsleigh),
+    SLEDDING(0x040000ed, R.string.activity_type_sledding),
+    BIATHLON(0x040000ee, R.string.activity_type_biathlon),
+    BUNGEE_JUMPING(0x040000ef, R.string.activity_type_bungee_jumping),
+    ORIENTEERING(0x040000f0, R.string.activity_type_orienteering),
+    TREKKING(0x040000f1, R.string.activity_type_trekking, R.drawable.ic_activity_hiking),
+    TRAIL_RUN(0x040000f2, R.string.activity_type_trail_run, R.drawable.ic_activity_trail_run),
+    ;
+
+    private final int code;
+    private final int label;
+    private final int icon;
+
+    ActivityKind(final int code) {
+        this(code, R.string.activity_type_unknown);
+    }
+
+    ActivityKind(final int code, @StringRes final int label) {
+        this(code, label, R.drawable.ic_activity_unknown_small);
+    }
+
+    ActivityKind(final int code, @StringRes final int label, @DrawableRes final int icon) {
+        this.code = code;
+        this.label = label;
+        this.icon = icon;
+    }
+
+    public int getCode() {
+        return code;
+    }
+
+    @StringRes
+    public int getLabel() {
+        return label;
+    }
+
+    public String getLabel(final Context context) {
+        return context.getString(label);
+    }
+
+    @DrawableRes
+    public int getIcon() {
+        return icon;
+    }
+
+    public static ActivityKind fromCode(final int code) {
+        for (final ActivityKind kind : ActivityKind.values()) {
+            if (kind.code == code) {
+                return kind;
+            }
+        }
+
+        //throw new IllegalArgumentException("Unknown ActivityKind code " + code);
+        return UNKNOWN;
+    }
+
+    public static boolean isSleep(final ActivityKind activityKind) {
+        return activityKind == ActivityKind.SLEEP_ANY
+                || activityKind == ActivityKind.LIGHT_SLEEP
+                || activityKind == ActivityKind.DEEP_SLEEP
+                || activityKind == ActivityKind.REM_SLEEP
+                || activityKind == ActivityKind.AWAKE_SLEEP;
+    }
+
+    public static boolean isPaceActivity(final ActivityKind activityKind) {
+        return activityKind.name().contains("RUN") || activityKind.name().contains("SWIM") ||
+                activityKind.name().contains("TREADMILL") || activityKind.name().contains("WALK");
+    }
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivityListItem.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivityListItem.java
new file mode 100644
index 0000000..0c6c16d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivityListItem.java
@@ -0,0 +1,171 @@
+package nodomain.freeyourgadget.gadgetbridge.model;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.text.format.DateUtils;
+import android.util.TypedValue;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+
+import androidx.annotation.Nullable;
+import androidx.core.content.ContextCompat;
+
+import org.apache.commons.lang3.StringUtils;
+
+import java.text.DecimalFormat;
+import java.util.Date;
+import java.util.concurrent.TimeUnit;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.FormatUtils;
+
+public class ActivityListItem {
+    private final View rootView;
+    private final TextView timeFromView;
+    private final TextView timeToView;
+    private final TextView activityName;
+    private final TextView activityLabelText;
+    private final TextView stepLabel;
+    private final TextView distanceLabel;
+    private final TextView hrLabel;
+    private final TextView intensityLabel;
+    private final TextView durationLabel;
+    private final TextView dateLabel;
+    private final LinearLayout timeLayout;
+    private final LinearLayout hrLayout;
+    private final LinearLayout stepsLayout;
+    private final LinearLayout distanceLayout;
+    private final LinearLayout intensityLayout;
+    private final RelativeLayout parentLayout;
+    private final ImageView activityIcon;
+    private final ImageView gpsIcon;
+
+    private final int backgroundColor;
+    private final int alternateColor;
+    private final int selectedColor;
+
+    public ActivityListItem(final View itemView) {
+        this.rootView = itemView;
+
+        this.timeFromView = itemView.findViewById(R.id.line_layout_time_from);
+        this.timeToView = itemView.findViewById(R.id.line_layout_time_to);
+        this.activityName = itemView.findViewById(R.id.line_layout_activity_name);
+        this.activityLabelText = itemView.findViewById(R.id.line_layout_activity_label);
+        this.stepLabel = itemView.findViewById(R.id.line_layout_step_label);
+        this.distanceLabel = itemView.findViewById(R.id.line_layout_distance_label);
+        this.hrLabel = itemView.findViewById(R.id.line_layout_hr_label);
+        this.intensityLabel = itemView.findViewById(R.id.line_layout_intensity_label);
+        this.durationLabel = itemView.findViewById(R.id.line_layout_duration_label);
+        this.dateLabel = itemView.findViewById(R.id.line_layout_date_label);
+
+        this.timeLayout = itemView.findViewById(R.id.line_layout_time);
+        this.hrLayout = itemView.findViewById(R.id.line_layout_hr);
+        this.stepsLayout = itemView.findViewById(R.id.line_layout_step);
+        this.distanceLayout = itemView.findViewById(R.id.line_layout_distance);
+        this.intensityLayout = itemView.findViewById(R.id.line_layout_intensity);
+
+        this.parentLayout = itemView.findViewById(R.id.list_item_parent_layout);
+
+        this.activityIcon = itemView.findViewById(R.id.line_layout_activity_icon);
+        this.gpsIcon = itemView.findViewById(R.id.line_layout_gps_icon);
+
+        this.backgroundColor = 0;
+        this.alternateColor = getThemedColor(itemView.getContext(), R.attr.alternate_row_background);
+        this.selectedColor = ContextCompat.getColor(itemView.getContext(), R.color.accent);
+    }
+
+    public void update(@Nullable final Date timeFrom,
+                       @Nullable final Date timeTo,
+                       final ActivityKind activityKind,
+                       @Nullable final String activityLabel,
+                       final int steps,
+                       final float distance,
+                       final int heartRate,
+                       final float intensity,
+                       final long duration,
+                       final boolean hasGps,
+                       @Nullable final Date date,
+                       final boolean zebraStripe,
+                       final boolean selected) {
+        final String activityKindLabel = activityKind.getLabel(activityName.getContext());
+        if (StringUtils.isNotBlank(activityLabel)) {
+            activityLabelText.setText(String.format("%s", activityKindLabel));
+            activityName.setText(String.format("%s", activityLabel));
+        } else {
+            activityLabelText.setVisibility(View.GONE);
+            activityName.setText(String.format("%s", activityKindLabel));
+        }
+        durationLabel.setText(DateTimeUtils.formatDurationHoursMinutes(duration, TimeUnit.MILLISECONDS));
+
+        if (heartRate > 0) {
+            hrLabel.setText(String.valueOf(heartRate));
+            hrLayout.setVisibility(View.VISIBLE);
+        } else {
+            hrLayout.setVisibility(View.GONE);
+        }
+
+        if (intensity >= 0) {
+            final DecimalFormat df = new DecimalFormat("###");
+            intensityLabel.setText(df.format(intensity));
+            intensityLayout.setVisibility(View.VISIBLE);
+        } else {
+            intensityLayout.setVisibility(View.GONE);
+        }
+
+        if (distance > 0) {
+            distanceLabel.setText(FormatUtils.getFormattedDistanceLabel(distance));
+            distanceLayout.setVisibility(View.VISIBLE);
+        } else {
+            distanceLayout.setVisibility(View.GONE);
+        }
+
+        if (steps > 0) {
+            stepLabel.setText(String.valueOf(steps));
+            stepsLayout.setVisibility(View.VISIBLE);
+        } else {
+            stepsLayout.setVisibility(View.GONE);
+        }
+
+        if (date != null) {
+            dateLabel.setText(DateTimeUtils.formatDateTimeRelative(rootView.getContext(), date));
+            dateLabel.setVisibility(View.VISIBLE);
+        } else {
+            dateLabel.setVisibility(View.GONE);
+        }
+
+        if (timeFrom != null && timeTo != null) {
+            timeFromView.setText(DateTimeUtils.formatTime(timeFrom.getHours(), timeFrom.getMinutes()));
+            timeToView.setText(DateTimeUtils.formatTime(timeTo.getHours(), timeTo.getMinutes()));
+            timeLayout.setVisibility(View.VISIBLE);
+        } else {
+            timeLayout.setVisibility(View.GONE);
+        }
+
+        if (hasGps) {
+            gpsIcon.setVisibility(View.VISIBLE);
+        } else {
+            gpsIcon.setVisibility(View.GONE);
+        }
+
+        activityIcon.setImageResource(activityKind.getIcon());
+
+        if (parentLayout != null) {
+            if (selected) {
+                parentLayout.setBackgroundColor(selectedColor);
+            } else {
+                parentLayout.setBackgroundColor(backgroundColor);
+            }
+        }
+    }
+
+    public static int getThemedColor(Context context, int resid) {
+        TypedValue typedValue = new TypedValue();
+        Resources.Theme theme = context.getTheme();
+        theme.resolveAttribute(resid, typedValue, true);
+        return typedValue.data;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivityPoint.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivityPoint.java
new file mode 100644
index 0000000..3767e9f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivityPoint.java
@@ -0,0 +1,103 @@
+/*  Copyright (C) 2017-2024 Carsten Pfeiffer, Daniele Gobbetti, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.model;
+
+import java.util.Date;
+
+import androidx.annotation.Nullable;
+
+// https://www8.garmin.com/xmlschemas/TrackPointExtensionv1.xsd
+/*
+<trkpt lat="54.8591470" lon="-1.5754310">
+        <ele>29.2</ele>
+        <time>2015-07-26T07:43:42Z</time>
+        <extensions>
+            <gpxtpx:TrackPointExtension>
+                <gpxtpx:atemp>11</gpxtpx:atemp>
+                <gpxtpx:hr>92</gpxtpx:hr>
+                <gpxtpx:cad>0</gpxtpx:cad>
+                <gpxtpx:speed>0.5</gpxtpx:speed>
+            </gpxtpx:TrackPointExtension>
+        </extensions>
+        </trkpt>
+*/
+public class ActivityPoint {
+    private Date time;
+    private GPSCoordinate location;
+    private int heartRate;
+    private float speed = -1;
+    private int cadence = -1;
+
+    // e.g. to describe a pause during the activity
+    private @Nullable String description;
+
+    public ActivityPoint() {
+    }
+
+    public ActivityPoint(Date time) {
+        this.time = time;
+    }
+
+    public Date getTime() {
+        return time;
+    }
+
+    public void setTime(Date time) {
+        this.time = time;
+    }
+
+    @Nullable
+    public String getDescription() {
+        return description;
+    }
+
+    public void setDescription(@Nullable String description) {
+        this.description = description;
+    }
+
+    public GPSCoordinate getLocation() {
+        return location;
+    }
+
+    public void setLocation(GPSCoordinate location) {
+        this.location = location;
+    }
+
+    public int getHeartRate() {
+        return heartRate;
+    }
+
+    public void setHeartRate(int heartRate) {
+        this.heartRate = heartRate;
+    }
+
+    public float getSpeed() {
+        return speed;
+    }
+
+    public void setSpeed(float speed) {
+        this.speed = speed;
+    }
+
+    public int getCadence() {
+        return cadence;
+    }
+
+    public void setCadence(final int cadence) {
+        this.cadence = cadence;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivitySample.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivitySample.java
new file mode 100644
index 0000000..df8e94e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivitySample.java
@@ -0,0 +1,101 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, Carsten Pfeiffer
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.model;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
+
+/**
+ * The all-in-one interface for a sample measured by a device with
+ * one or more sensors.
+ *
+ * Each sample is the result of one or more measurements. The values are set for
+ * a specific point in time (see @{link #getTimestamp()}).
+ *
+ * If the sample relates to a user activity (e.g. sleeping, walking, running, ...)
+ * then the activity is provided through @{link #getKind()}.
+ *
+ * Methods will return @{link #NOT_MEASURED} in case no value is available for this
+ * sample.
+ *
+ * The frequency of samples, i.e. the how many samples are recorded per minute, is not specified
+ * and may vary.
+ */
+public interface ActivitySample extends TimeStamped {
+
+    int NOT_MEASURED = -1;
+
+    /**
+     * Returns the provider of the data.
+     *
+     * @return who created the sample data
+     */
+    SampleProvider<?> getProvider();
+
+    /**
+     * Returns the raw activity kind value as recorded by the SampleProvider
+     */
+    int getRawKind();
+
+    /**
+     * Returns the activity kind value as recorded by the SampleProvider
+     *
+     * @see ActivityKind
+     */
+    ActivityKind getKind();
+
+    /**
+     * Returns the raw intensity value as recorded by the SampleProvider
+     */
+    int getRawIntensity();
+
+    /**
+     * Returns the normalized intensity value between 0 and 1
+     */
+    float getIntensity();
+
+    /**
+     * Returns the number of steps performed during the period of this sample
+     */
+    int getSteps();
+
+    /**
+     * Returns the distance moved during the period of this sample, in cm. -1 if unknown.
+     */
+    int getDistanceCm();
+
+    /**
+     * Returns the calories burned during the period of this sample, in calories. -1 if unknown.
+     */
+    int getActiveCalories();
+
+    /**
+     * Returns the heart rate measured at the corresponding timestamp.
+     * The value is returned in heart beats per minute, in the range from
+     * 0-255, where 255 is an illegal value (e.g. due to a bad measurement)
+     *
+     * @return the heart rate value in beats per minute, or -1 if none
+     */
+    int getHeartRate();
+
+    /**
+     * Sets the heart rate value of this sample. Typically only used in
+     * generic db migration.
+     *
+     * @param value the value in bpm
+     */
+    void setHeartRate(int value);
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivitySession.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivitySession.java
new file mode 100644
index 0000000..6f379c4
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivitySession.java
@@ -0,0 +1,127 @@
+/*  Copyright (C) 2020-2024 Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.model;
+
+import java.io.Serializable;
+import java.util.Date;
+
+// ActivitySession holds activities detected by the steps/hr/intensity
+// and is used in the Activity List
+public class ActivitySession implements Serializable {
+
+    public static int SESSION_NORMAL = 1;
+    public static int SESSION_SUMMARY = 2;
+    public static int SESSION_ONGOING = 3;
+    public static int SESSION_EMPTY = 4;
+
+    private final Date startTime;
+    private final Date endTime;
+    private final int activeSteps;
+    private final int heartRateAverage;
+    private final float intensity;
+    private final float distance;
+    private final ActivityKind activityKind;
+    // following is related to step session, we hold it here for the listview
+    // it is identified by SESSION_SUMMARY
+    private int sessionCount = 0;
+    private int sessionType = SESSION_NORMAL;
+    private boolean isEmptySummary = false; // in case there is no activity on that day
+    private int totalDaySteps;
+
+
+    public ActivitySession(Date startTime,
+                           Date endTime,
+                           int steps, int heartRateAverage, float intensity, float distance, ActivityKind activityKind) {
+        this.startTime = startTime;
+        this.endTime = endTime;
+        this.activeSteps = steps;
+        this.heartRateAverage = heartRateAverage;
+        this.intensity = intensity;
+        this.distance = distance;
+        this.activityKind = activityKind;
+    }
+
+    public ActivitySession(){
+        this.startTime = null;
+        this.endTime = null;
+        this.activeSteps=0;
+        this.heartRateAverage = 0;
+        this.intensity = 0;
+        this.distance = 0;
+        this.activityKind = ActivityKind.UNKNOWN;
+    };
+
+    public Date getStartTime() {
+        return startTime;
+    }
+
+    public Date getEndTime() {
+        return endTime;
+    }
+
+    public int getActiveSteps() {
+        return activeSteps;
+    }
+
+    public int getHeartRateAverage() {
+        return heartRateAverage;
+    }
+
+    public ActivityKind getActivityKind() {
+        return activityKind;
+    }
+
+    public float getIntensity() {
+        return intensity;
+    }
+
+    public float getDistance() {
+        return distance;
+    }
+
+    public int getSessionCount() {
+        return sessionCount;
+    }
+
+    public void setSessionCount(int sessionCount) {
+        this.sessionCount = sessionCount;
+    }
+
+    public int getSessionType() {
+        return sessionType;
+    }
+
+    public void setSessionType(int sessionType) {
+        this.sessionType = sessionType;
+    }
+
+    public boolean getIsEmptySummary() {
+        return isEmptySummary;
+    }
+
+    public void setEmptySummary(boolean emptySummary) {
+        this.isEmptySummary = emptySummary;
+    }
+
+    public int getTotalDaySteps() {
+        return totalDaySteps;
+    }
+
+    public void setTotalDaySteps(int totalDaySteps) {
+        this.totalDaySteps = totalDaySteps;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivitySummary.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivitySummary.java
new file mode 100644
index 0000000..17efcb9
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivitySummary.java
@@ -0,0 +1,51 @@
+/*  Copyright (C) 2017-2024 Carsten Pfeiffer, Jos茅 Rebelo, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.model;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * Summarized information about a temporal activity.
+ *
+ * // TODO: split into separate entities?
+ */
+public interface ActivitySummary extends Serializable {
+    Long getId();
+    String getName();
+    Date getStartTime();
+    Date getEndTime();
+
+    int getActivityKind();
+    String getGpxTrack();
+
+    long getDeviceId();
+
+    long getUserId();
+    String getSummaryData();
+    //    long getSteps();
+//    float getDistanceMeters();
+//    float getAscentMeters();
+//    float getDescentMeters();
+//    float getMinAltitude();
+//    float getMaxAltitude();
+//    float getCalories();
+//
+//    float getMaxSpeed();
+//    float getMinSpeed();
+//    float getAverageSpeed();
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivitySummaryData.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivitySummaryData.java
new file mode 100644
index 0000000..ef39ef9
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivitySummaryData.java
@@ -0,0 +1,164 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.model;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.reflect.TypeToken;
+import com.google.gson.typeadapters.RuntimeTypeAdapterFactory;
+
+import org.apache.commons.lang3.StringUtils;
+
+import java.lang.reflect.Type;
+import java.util.LinkedHashMap;
+import java.util.Set;
+
+import nodomain.freeyourgadget.gadgetbridge.activities.workouts.entries.ActivitySummaryEntry;
+import nodomain.freeyourgadget.gadgetbridge.activities.workouts.entries.ActivitySummaryProgressEntry;
+import nodomain.freeyourgadget.gadgetbridge.activities.workouts.entries.ActivitySummarySimpleEntry;
+import nodomain.freeyourgadget.gadgetbridge.activities.workouts.entries.ActivitySummaryTableRowEntry;
+
+/**
+ * A small wrapper for a JSONObject, with helper methods to add activity summary data in the format
+ * Gadgetbridge expects.
+ */
+public class ActivitySummaryData {
+    private static final Gson GSON = new GsonBuilder()
+            .registerTypeAdapterFactory(RuntimeTypeAdapterFactory
+                    .of(ActivitySummaryEntry.class, "type")
+                    .registerSubtype(ActivitySummarySimpleEntry.class, null) // no type for backwards compatibility
+                    .registerSubtype(ActivitySummaryProgressEntry.class, "progress")
+                    .registerSubtype(ActivitySummaryTableRowEntry.class, "tableRow")
+                    .recognizeSubtypes()
+            )
+            //.serializeNulls()
+            //.setPrettyPrinting()
+            .create();
+
+    private final LinkedHashMap<String, ActivitySummaryEntry> entries;
+
+    public ActivitySummaryData() {
+        this.entries = new LinkedHashMap<>();
+    }
+
+    public ActivitySummaryData(final LinkedHashMap<String, ActivitySummaryEntry> entries) {
+        this.entries = entries;
+    }
+
+    public void add(final String key, final Number value, final String unit) {
+        add(null, key, value, unit, false);
+    }
+
+    public void add(final String key, final Number value, final String unit, boolean forceDisplay) {
+        add(null, key, value, unit, forceDisplay);
+    }
+
+    public void add(final String group, final String key, final Number value, final String unit) {
+        add(group, key, value, unit, false);
+    }
+
+    public void add(final String group, final String key, final Number value, final String unit, boolean forceDisplay) {
+        if (value.doubleValue() != 0 || forceDisplay) {
+            entries.put(key, new ActivitySummarySimpleEntry(group, value, unit));
+        }
+    }
+
+    public void add(final String key, final String value) {
+        add(null, key, value);
+    }
+
+    public void add(final String group, final String key, final String value) {
+        if (StringUtils.isBlank(key) || StringUtils.isBlank(value)) {
+            return;
+        }
+
+        entries.put(key, new ActivitySummarySimpleEntry(group, value, ActivitySummaryEntries.UNIT_STRING));
+    }
+
+    public void add(final String key, final ActivitySummaryEntry entry) {
+        entries.put(key, entry);
+    }
+
+    public Set<String> getKeys() {
+        return entries.keySet();
+    }
+
+    public ActivitySummaryEntry get(final String key) {
+        return entries.get(key);
+    }
+
+    public boolean has(final String key) {
+        return entries.containsKey(key);
+    }
+
+    public Number getNumber(final String key, final Number defaultValue) {
+        final ActivitySummaryEntry entry = entries.get(key);
+        if (!(entry instanceof ActivitySummarySimpleEntry)) {
+            return defaultValue;
+        }
+        final ActivitySummarySimpleEntry simpleEntry = (ActivitySummarySimpleEntry) entry;
+        final Object value = simpleEntry.getValue();
+        if (!(value instanceof Number)) {
+            return defaultValue;
+        }
+
+        return ((Number) value).doubleValue();
+    }
+
+    public boolean getBoolean(final String key, final boolean defaultValue) {
+        final ActivitySummaryEntry entry = entries.get(key);
+        if (!(entry instanceof ActivitySummarySimpleEntry)) {
+            return defaultValue;
+        }
+        final ActivitySummarySimpleEntry simpleEntry = (ActivitySummarySimpleEntry) entry;
+        final Object value = simpleEntry.getValue();
+        if (value instanceof Boolean) {
+            return (boolean) value;
+        }
+
+        if (!(value instanceof String)) {
+            return defaultValue;
+        }
+
+        return Boolean.parseBoolean((String) value);
+    }
+
+    @Nullable
+    public static ActivitySummaryData fromJson(final String string) {
+        if (StringUtils.isBlank(string)) {
+            return null;
+        }
+
+        final Type type = new TypeToken<LinkedHashMap<String, ActivitySummaryEntry>>(){}.getType();
+        final LinkedHashMap<String, ActivitySummaryEntry> entries = GSON.fromJson(string, type);
+
+        return new ActivitySummaryData(entries);
+    }
+
+    @NonNull
+    @Override
+    public String toString() {
+        return toJson();
+    }
+
+    public String toJson() {
+        return GSON.toJson(entries);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivitySummaryEntries.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivitySummaryEntries.java
new file mode 100644
index 0000000..59c456c
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivitySummaryEntries.java
@@ -0,0 +1,183 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.model;
+
+public class ActivitySummaryEntries {
+    public static final String STATUS = "status";
+    public static final String TYPE = "watchface_dialog_widget_type"; // TODO: change this?
+
+    public static final String TIME_START = "startTime";
+    public static final String TIME_END = "endTime";
+    public static final String ACTIVE_SECONDS = "activeSeconds";
+
+    public static final String ALTITUDE_AVG = "averageAltitude";
+    public static final String ALTITUDE_BASE = "baseAltitude";
+    public static final String ALTITUDE_MAX = "maxAltitude";
+    public static final String ALTITUDE_MIN = "minAltitude";
+
+    public static final String ASCENT_DISTANCE = "ascentDistance";
+    public static final String ASCENT_METERS = "ascentMeters";
+    public static final String ASCENT_SECONDS = "ascentSeconds";
+    public static final String DESCENT_DISTANCE = "descentDistance";
+    public static final String DESCENT_METERS = "descentMeters";
+    public static final String DESCENT_SECONDS = "descentSeconds";
+    public static final String FLAT_DISTANCE = "flatDistance";
+    public static final String FLAT_SECONDS = "flatSeconds";
+
+    public static final String STEP_RATE_SUM = "stepRateSum";
+    public static final String STEP_RATE_AVG = "stepRateAvg";
+    public static final String STEP_RATE_MAX = "stepRateMax";
+    public static final String STEP_LENGTH_AVG = "stepLengthAvg";
+
+    public static final String CADENCE_AVG = "averageCadence";
+    public static final String CADENCE_MAX = "maxCadence";
+    public static final String CADENCE_MIN = "minCadence";
+
+    public static final String SPEED_AVG = "averageSpeed";
+    public static final String SPEED_MAX = "maxSpeed";
+    public static final String SPEED_MIN = "minSpeed";
+
+    public static final String GROUND_CONTACT_TIME_AVG = "groundContactTimeAvg";
+    public static final String IMPACT_AVG = "impactAvg";
+    public static final String IMPACT_MAX = "impactMax";
+    public static final String SWING_ANGLE_AVG = "swingAngleAvg";
+    public static final String FORE_FOOT_LANDINGS = "foreFootLandings";
+    public static final String MID_FOOT_LANDINGS = "midFootLandings";
+    public static final String BACK_FOOT_LANDINGS = "backFootLandings";
+    public static final String EVERSION_ANGLE_AVG = "eversionAngleAvg";
+    public static final String EVERSION_ANGLE_MAX = "eversionAngleMax";
+
+    public static final String DISTANCE_METERS = "distanceMeters";
+    public static final String ELEVATION_GAIN = "elevationGain";
+    public static final String ELEVATION_LOSS = "elevationLoss";
+
+    public static final String HR_AVG = "averageHR";
+    public static final String HR_MAX = "maxHR";
+    public static final String HR_MIN = "minHR";
+    public static final String HR_ZONE_NA = "hrZoneNa";
+    public static final String HR_ZONE_WARM_UP = "hrZoneWarmUp";
+    public static final String HR_ZONE_EASY = "hrZoneEasy";
+    public static final String HR_ZONE_FAT_BURN = "hrZoneFatBurn";
+    public static final String HR_ZONE_AEROBIC = "hrZoneAerobic";
+    public static final String HR_ZONE_ANAEROBIC = "hrZoneAnaerobic";
+    public static final String HR_ZONE_THRESHOLD = "hrZoneThreshold";
+    public static final String HR_ZONE_EXTREME = "hrZoneExtreme";
+    public static final String HR_ZONE_MAXIMUM = "hrZoneMaximum";
+
+    public static final String RESPIRATION_AVG = "average_respiration_rate";
+    public static final String RESPIRATION_MAX = "max_respiration_rate";
+    public static final String RESPIRATION_MIN = "min_respiration_rate";
+    public static final String SPO2_AVG = "menuitem_spo2";
+    public static final String STRESS_AVG = "menuitem_stress";
+    public static final String HRV_SDRR = "hrv_sdrr";
+    public static final String HRV_RMSSD = "hrv_rmssd";
+
+    public static final String LANE_LENGTH = "laneLength";
+    public static final String LAPS = "laps";
+    public static final String LAP_PACE_AVERAGE = "averageLapPace";
+
+    public static final String PACE_AVG_SECONDS_KM = "averageKMPaceSeconds";
+    public static final String PACE_MAX = "maxPace";
+    public static final String PACE_MIN = "minPace";
+    public static final String STEPS = "steps";
+    public static final String STRIDE_AVG = "averageStride";
+    public static final String STRIDE_MAX = "maxStride";
+    public static final String STRIDE_MIN = "minStride";
+    public static final String STRIDE_TOTAL = "totalStride";
+
+    public static final String STROKE_DISTANCE_AVG = "averageStrokeDistance";
+    public static final String STROKE_AVG_PER_SECOND = "averageStrokesPerSecond";
+    public static final String STROKE_RATE_AVG = "avgStrokeRate";
+    public static final String STROKE_RATE_MAX = "maxStrokeRate";
+    public static final String STROKES = "strokes";
+
+    public static final String JUMP_RATE_AVG = "avgJumpRate";
+    public static final String JUMP_RATE_MAX = "maxJumpRate";
+    public static final String JUMPS = "totalJumps";
+
+    public static final String SWIM_STYLE = "swimStyle";
+    public static final String SWOLF_INDEX = "swolfIndex";
+    public static final String SWOLF_AVG = "swolfAvg";
+    public static final String SWOLF_MAX = "swolfMax";
+    public static final String SWOLF_MIN = "swolfMin";
+
+    public static final String CALORIES_BURNT = "caloriesBurnt";
+    public static final String TRAINING_EFFECT_AEROBIC = "aerobicTrainingEffect";
+    public static final String TRAINING_EFFECT_ANAEROBIC = "anaerobicTrainingEffect";
+    public static final String WORKOUT_LOAD = "currentWorkoutLoad";
+    public static final String MAXIMUM_OXYGEN_UPTAKE = "maximumOxygenUptake";
+    public static final String RECOVERY_TIME = "recoveryTime";
+    public static final String ESTIMATED_SWEAT_LOSS = "estimatedSweatLoss";
+    public static final String LACTATE_THRESHOLD_HR = "lactateThresholdHeartRate";
+
+    public static final String CYCLING_POWER_AVERAGE = "cyclingPowerAverage";
+    public static final String CYCLING_POWER_MIN = "cyclingPowerMin";
+    public static final String CYCLING_POWER_MAX = "cyclingPowerMax";
+
+    public static final String SETS = "workoutSets";
+
+    public static final String UNIT_BPM = "bpm";
+    public static final String UNIT_BREATHS_PER_MIN = "breaths_per_min";
+    public static final String UNIT_CM = "cm";
+    public static final String UNIT_UNIX_EPOCH_SECONDS = "unix_epoch_seconds";
+    public static final String UNIT_KCAL = "calories_unit";
+    public static final String UNIT_ML = "ml";
+    public static final String UNIT_LAPS = "laps_unit";
+    public static final String UNIT_KILOMETERS = "km";
+    public static final String UNIT_METERS = "meters";
+    public static final String UNIT_PERCENTAGE = "%";
+    public static final String UNIT_ML_KG_MIN = "ml/kg/min";
+    public static final String UNIT_NONE = "";
+    public static final String UNIT_HOURS = "hours";
+    public static final String UNIT_SECONDS = "seconds";
+    public static final String UNIT_MILLISECONDS = "milliseconds_ms";
+    public static final String UNIT_SECONDS_PER_KM = "seconds_km";
+    public static final String UNIT_SECONDS_PER_M = "seconds_m";
+    public static final String UNIT_METERS_PER_SECOND = "meters_second";
+    public static final String UNIT_KMPH = "km_h";
+    public static final String UNIT_SPM = "spm";
+    public static final String UNIT_STEPS = "steps_unit";
+    public static final String UNIT_STROKES = "strokes_unit";
+    public static final String UNIT_STROKES_PER_MINUTE = "strokes_minute";
+    public static final String UNIT_STROKES_PER_SECOND = "strokes_second";
+    public static final String UNIT_JUMPS = "jumps_unit";
+    public static final String UNIT_JUMPS_PER_MINUTE = "jumps_minute";
+    public static final String UNIT_YARD = "yard";
+    public static final String UNIT_DEGREES = "degrees";
+    public static final String UNIT_STRING = "string";
+    public static final String UNIT_RAW_STRING = "raw_string";
+    public static final String UNIT_KG = "kg";
+
+    public static final String GROUP_PACE = "Pace";
+    public static final String GROUP_ACTIVITY = "Activity";
+    public static final String GROUP_SPEED = "Speed";
+    public static final String GROUP_ELEVATION = "Elevation";
+    public static final String GROUP_HEART_RATE_ZONES = "HeartRateZones";
+    public static final String GROUP_STROKES = "Strokes";
+    public static final String GROUP_JUMPS = "Jumps";
+    public static final String GROUP_SWIMMING = "Swimming";
+    public static final String GROUP_TRAINING_EFFECT = "TrainingEffect";
+    public static final String GROUP_LAPS = "laps";
+    public static final String GROUP_RUNNING_FORM = "RunningForm";
+
+    /**
+     * Used to signal that this activity has a gps track. This is currently used by ActivitySummaryDetail
+     * to display the share and view gpx buttons, even though there's no gpx file.
+     * FIXME: We should have a cleaner way of doing this.
+     */
+    public static final String INTERNAL_HAS_GPS = "internal_hasGps";
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivitySummaryItems.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivitySummaryItems.java
new file mode 100644
index 0000000..052df53
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivitySummaryItems.java
@@ -0,0 +1,62 @@
+/*  Copyright (C) 2020-2024 Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.model;
+
+import android.content.Context;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.adapter.ActivitySummariesAdapter;
+import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class ActivitySummaryItems {
+    ActivitySummariesAdapter itemsAdapter;
+    private int current_position = 0;
+
+    public ActivitySummaryItems(Context context, GBDevice device, int activityKindFilter, long dateFromFilter, long dateToFilter, String nameContainsFilter, long deviceFilter, List itemsFilter) {
+
+        this.itemsAdapter = new ActivitySummariesAdapter(context, device, activityKindFilter, dateFromFilter, dateToFilter, nameContainsFilter, deviceFilter, itemsFilter);
+    }
+
+    public BaseActivitySummary getItem(int position) {
+        if (position == 0) return null;
+        current_position = position;
+        return itemsAdapter.getItem(position);
+    }
+
+    public int getPosition(BaseActivitySummary item) {
+        return itemsAdapter.getPosition(item);
+    }
+
+    public BaseActivitySummary getNextItem() {
+        // last one is empty to avoid items behind fab
+        if (current_position + 2 < itemsAdapter.getItemCount()) {
+            current_position += 1;
+            return itemsAdapter.getItem(current_position);
+        }
+        return null;
+    }
+
+    public BaseActivitySummary getPrevItem() {
+        if (current_position - 1 >= 1) { //0 is empty item for summary dashboard
+            current_position -= 1;
+            return itemsAdapter.getItem(current_position);
+        }
+        return null;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivitySummaryJsonSummary.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivitySummaryJsonSummary.java
new file mode 100644
index 0000000..d8d03d5
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivitySummaryJsonSummary.java
@@ -0,0 +1,217 @@
+/*  Copyright (C) 2020-2024 Jos茅 Rebelo, Petr Van臎k, Reiner Herrmann,
+    Sebastian Krey
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.model;
+
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.*;
+
+import org.apache.commons.lang3.tuple.Pair;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import nodomain.freeyourgadget.gadgetbridge.activities.workouts.entries.ActivitySummaryEntry;
+import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary;
+
+public class ActivitySummaryJsonSummary {
+    private static final Logger LOG = LoggerFactory.getLogger(ActivitySummaryJsonSummary.class);
+    private Map<String, List<String>> groupData;
+    private ActivitySummaryData summaryData;
+    private Map<String, List<Pair<String, ActivitySummaryEntry>>> summaryGroupedList;
+    private final ActivitySummaryParser summaryParser;
+    private final BaseActivitySummary baseActivitySummary;
+
+    public ActivitySummaryJsonSummary(final ActivitySummaryParser summaryParser, BaseActivitySummary baseActivitySummary) {
+        this.summaryParser = summaryParser;
+        this.baseActivitySummary = baseActivitySummary;
+    }
+
+    private ActivitySummaryData setSummaryData(BaseActivitySummary item, final boolean forDetails) {
+        final ActivitySummaryData summary = ActivitySummaryData.fromJson(getCorrectSummary(item, forDetails));
+        if (summary == null) {
+            return null;
+        }
+
+        //add additionally computed values here
+        if (item.getBaseAltitude() != null && item.getBaseAltitude() != -20000 && !summary.has("baseAltitude")) {
+            summary.add("baseAltitude", item.getBaseAltitude(), UNIT_METERS);
+        }
+
+        if (!summary.has("averageSpeed") && summary.has("distanceMeters") && summary.has("activeSeconds")) {
+            double distance = summary.getNumber("distanceMeters", 0).doubleValue();
+            double duration = summary.getNumber("activeSeconds", 1).doubleValue();
+            summary.add("averageSpeed", distance / duration, UNIT_METERS_PER_SECOND);
+        }
+
+        if (!summary.has(STEP_RATE_AVG) && summary.has(STEPS) && summary.has(ACTIVE_SECONDS)) {
+            double stepcount = summary.getNumber(STEPS, 0).doubleValue();
+            double duration = summary.getNumber(ACTIVE_SECONDS, 1).doubleValue();
+            summary.add(STEP_RATE_AVG, (double)((int)(((stepcount / duration) * 60)+0.5)), UNIT_SPM);
+        }
+
+        return summary;
+    }
+
+    public ActivitySummaryData getSummaryData(final boolean forDetails) {
+        //returns json with summaryData
+        if (summaryData == null) summaryData = setSummaryData(baseActivitySummary, forDetails);
+        return summaryData;
+    }
+
+    private String getCorrectSummary(BaseActivitySummary item, final boolean forDetails) {
+        if (summaryParser == null) {
+            return item.getSummaryData();
+        }
+        try {
+            item = summaryParser.parseBinaryData(item, forDetails);
+        } catch (final Exception e) {
+            LOG.error("Failed to re-parse corrected summary", e);
+        }
+        return item.getSummaryData();
+    }
+
+    public Map<String, List<Pair<String, ActivitySummaryEntry>>> getSummaryGroupedList() {
+        //returns list grouped by activity groups as per createActivitySummaryGroups
+        if (summaryData == null) summaryData = setSummaryData(baseActivitySummary, true);
+        if (summaryGroupedList == null) summaryGroupedList = setSummaryGroupedList(summaryData);
+        return summaryGroupedList;
+    }
+
+    private Map<String, List<Pair<String, ActivitySummaryEntry>>> setSummaryGroupedList(ActivitySummaryData activitySummaryData) {
+        this.groupData = createActivitySummaryGroups(); //structure for grouping activities into groups, when visualizing
+
+        if (activitySummaryData == null) return null;
+        Iterator<String> keys = activitySummaryData.getKeys().iterator();
+
+        final Map<String, List<Pair<String, ActivitySummaryEntry>>> activeGroups = new LinkedHashMap<>();
+        // Initialize activeGroups with the initial expected order and empty arrays
+        for (final String key : this.groupData.keySet()) {
+            activeGroups.put(key, new LinkedList<>());
+        }
+
+        while (keys.hasNext()) {
+            String key = keys.next();
+            if (key.startsWith("internal")) {
+                continue;
+            }
+            ActivitySummaryEntry item = activitySummaryData.get(key);
+            // Use the group if specified in the entry, otherwise fallback to the array below
+            String groupName = item.getGroup() != null ? item.getGroup() : getDefaultGroup(key);
+
+            List<Pair<String, ActivitySummaryEntry>> group = activeGroups.get(groupName);
+            if (group == null) {
+                // This group is not defined in createActivitySummaryGroups - add it to the end
+                group = new LinkedList<>();
+                activeGroups.put(groupName, group);
+            }
+
+            group.add(Pair.of(key, item));
+        }
+
+        // Convert activeGroups to the expected JSONObject
+        // activeGroups is already ordered
+        final Map<String, List<Pair<String, ActivitySummaryEntry>>> grouped = new LinkedHashMap<>();
+        for (final Map.Entry<String, List<Pair<String, ActivitySummaryEntry>>> entry : activeGroups.entrySet()) {
+            if (entry.getValue().isEmpty()) {
+                // empty group
+                continue;
+            }
+            grouped.put(entry.getKey(), entry.getValue());
+        }
+        return grouped;
+    }
+
+    private String getDefaultGroup(final String searchItem) {
+        final String defaultGroup = GROUP_ACTIVITY;
+        if (groupData == null) return defaultGroup;
+        for (final String groupKey : groupData.keySet()) {
+            final List<String> itemList = groupData.get(groupKey);
+            if (itemList == null) {
+                continue;
+            }
+            for (final String itemKey : itemList) {
+                if (itemKey.equals(searchItem)) {
+                    return groupKey;
+                }
+            }
+        }
+
+        // NB: Default group must be present in group JSONObject created by createActivitySummaryGroups
+        return defaultGroup;
+    }
+
+    /**
+     * @noinspection ArraysAsListWithZeroOrOneArgument
+     */
+    private static Map<String, List<String>> createActivitySummaryGroups() {
+        return new LinkedHashMap<String, List<String>>() {{
+            // NB: Default group Activity must be present in this definition, otherwise it wouldn't
+            // be shown.
+            put(GROUP_ACTIVITY, Arrays.asList(
+                    DISTANCE_METERS, STEPS, STEP_RATE_SUM, ACTIVE_SECONDS, CALORIES_BURNT,
+                    STRIDE_TOTAL, HR_AVG, HR_MAX, HR_MIN, STRIDE_AVG, STRIDE_MAX, STRIDE_MIN,
+                    STEP_LENGTH_AVG
+            ));
+            put(GROUP_SPEED, Arrays.asList(
+                    SPEED_AVG, SPEED_MAX, SPEED_MIN, PACE_AVG_SECONDS_KM, PACE_MIN,
+                    PACE_MAX, "averageSpeed2", CADENCE_AVG, CADENCE_MAX, CADENCE_MIN,
+                    STEP_RATE_AVG, STEP_RATE_MAX
+            ));
+            put(GROUP_ELEVATION, Arrays.asList(
+                    ASCENT_METERS, DESCENT_METERS, ALTITUDE_MAX, ALTITUDE_MIN, ALTITUDE_AVG,
+                    ALTITUDE_BASE, ASCENT_SECONDS, DESCENT_SECONDS, FLAT_SECONDS, ASCENT_DISTANCE,
+                    DESCENT_DISTANCE, FLAT_DISTANCE, ELEVATION_GAIN, ELEVATION_LOSS
+            ));
+            put(GROUP_STROKES, Arrays.asList(
+                    STROKE_DISTANCE_AVG, STROKE_AVG_PER_SECOND, STROKES,
+                    STROKE_RATE_AVG, STROKE_RATE_MAX
+            ));
+            put(GROUP_JUMPS, Arrays.asList(
+                    JUMPS, JUMP_RATE_AVG, JUMP_RATE_MAX
+            ));
+            put(GROUP_SWIMMING, Arrays.asList(
+                    SWOLF_INDEX, SWOLF_AVG, SWOLF_MAX, SWOLF_MIN, SWIM_STYLE
+            ));
+            put(GROUP_TRAINING_EFFECT, Arrays.asList(
+                    TRAINING_EFFECT_AEROBIC, TRAINING_EFFECT_ANAEROBIC, WORKOUT_LOAD,
+                    MAXIMUM_OXYGEN_UPTAKE, RECOVERY_TIME, LACTATE_THRESHOLD_HR
+            ));
+            put(GROUP_LAPS, Arrays.asList(
+                    LAP_PACE_AVERAGE, LAPS, LANE_LENGTH
+            ));
+            put(GROUP_PACE, Arrays.asList(
+            ));
+            put(GROUP_RUNNING_FORM, Arrays.asList(
+                    GROUND_CONTACT_TIME_AVG, IMPACT_AVG, IMPACT_MAX, SWING_ANGLE_AVG,
+                    FORE_FOOT_LANDINGS, MID_FOOT_LANDINGS, BACK_FOOT_LANDINGS,
+                    EVERSION_ANGLE_AVG, EVERSION_ANGLE_MAX
+            ));
+            put(GROUP_HEART_RATE_ZONES, Arrays.asList(
+                    HR_ZONE_NA, HR_ZONE_WARM_UP, HR_ZONE_FAT_BURN, HR_ZONE_EASY, HR_ZONE_AEROBIC, HR_ZONE_ANAEROBIC,
+                    HR_ZONE_THRESHOLD, HR_ZONE_EXTREME, HR_ZONE_MAXIMUM
+            ));
+            put(SETS, Arrays.asList(
+            ));
+        }};
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivitySummaryParser.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivitySummaryParser.java
new file mode 100644
index 0000000..b007dfb
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivitySummaryParser.java
@@ -0,0 +1,76 @@
+/*  Copyright (C) 2020-2024 Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.model;
+
+import java.util.Date;
+import java.util.List;
+
+import de.greenrobot.dao.query.QueryBuilder;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary;
+import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummaryDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.entities.User;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public interface ActivitySummaryParser {
+    /**
+     * Re-parse an existing {@link BaseActivitySummary}, updating it from the existing binary data.
+     *
+     * @param summary    the existing {@link BaseActivitySummary}. It's not guaranteed that it
+     *                   contains any raw binary data.
+     * @param forDetails whether the parsing is for the details page. If this is false, the parser
+     *                   should avoid slow operations such as reading and parsing raw files from
+     *                   storage.
+     * @return the update {@link BaseActivitySummary}
+     */
+    BaseActivitySummary parseBinaryData(BaseActivitySummary summary, final boolean forDetails);
+
+    static BaseActivitySummary findOrCreateBaseActivitySummary(final DaoSession session,
+                                                               final GBDevice gbDevice,
+                                                               final int timestampSeconds) {
+        final Device device = DBHelper.getDevice(gbDevice, session);
+        return findOrCreateBaseActivitySummary(session, device.getId(), timestampSeconds);
+    }
+
+    static BaseActivitySummary findOrCreateBaseActivitySummary(final DaoSession session,
+                                                               final long deviceId,
+                                                               final int timestampSeconds) {
+        final User user = DBHelper.getUser(session);
+        final BaseActivitySummaryDao summaryDao = session.getBaseActivitySummaryDao();
+        final QueryBuilder<BaseActivitySummary> qb = summaryDao.queryBuilder();
+        qb.where(BaseActivitySummaryDao.Properties.StartTime.eq(new Date(timestampSeconds * 1000L)));
+        qb.where(BaseActivitySummaryDao.Properties.DeviceId.eq(deviceId));
+        qb.where(BaseActivitySummaryDao.Properties.UserId.eq(user.getId()));
+        final List<BaseActivitySummary> summaries = qb.build().list();
+        if (summaries.isEmpty()) {
+            final BaseActivitySummary summary = new BaseActivitySummary();
+            summary.setStartTime(new Date(timestampSeconds * 1000L));
+            summary.setDeviceId(deviceId);
+            summary.setUser(user);
+
+            // These will be set later, once we parse the summary
+            summary.setEndTime(new Date(timestampSeconds * 1000L));
+            summary.setActivityKind(ActivityKind.UNKNOWN.getCode());
+
+            return summary;
+        }
+
+        return summaries.get(0);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivityTrack.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivityTrack.java
new file mode 100644
index 0000000..8161339
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivityTrack.java
@@ -0,0 +1,91 @@
+/*  Copyright (C) 2017-2024 Carsten Pfeiffer, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.model;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.entities.User;
+
+public class ActivityTrack {
+    private Date baseTime;
+    private Device device;
+    private User user;
+    private String name;
+    private List<ActivityPoint> currentSegment = new ArrayList<>();
+    private List<List<ActivityPoint>> segments = new ArrayList<List<ActivityPoint>>() {{
+        add(currentSegment);
+    }};
+
+    public void setBaseTime(Date baseTime) {
+        this.baseTime = baseTime;
+    }
+
+    public Device getDevice() {
+        return device;
+    }
+
+    public void setDevice(Device device) {
+        this.device = device;
+    }
+
+    public User getUser() {
+        return user;
+    }
+
+    public void setUser(User user) {
+        this.user = user;
+    }
+
+    /**
+     * Add a track point to the current segment.
+     */
+    public void addTrackPoint(final ActivityPoint point) {
+        currentSegment.add(point);
+    }
+
+    public void addTrackPoints(final Collection<ActivityPoint> points) {
+        currentSegment.addAll(points);
+    }
+
+    public void startNewSegment() {
+        // Only really start a new segment if the current one is not empty
+        if (!currentSegment.isEmpty()) {
+            currentSegment = new ArrayList<>();
+            segments.add(currentSegment);
+        }
+    }
+
+    public List<List<ActivityPoint>> getSegments() {
+        return segments;
+    }
+
+    public Date getBaseTime() {
+        return baseTime;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivityUser.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivityUser.java
new file mode 100644
index 0000000..c3e9e22
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivityUser.java
@@ -0,0 +1,213 @@
+/*  Copyright (C) 2016-2024 0nse, Andreas Shimokawa, Carsten Pfeiffer,
+    Damien Gaignon, Daniele Gobbetti, Jos茅 Rebelo, Petr Van臎k, Sebastian Kranz
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.model;
+
+import java.time.LocalDate;
+import java.time.Period;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+/**
+ * Class holding the common user information needed by most activity trackers
+ */
+public class ActivityUser {
+
+    public static final int GENDER_FEMALE = 0;
+    public static final int GENDER_MALE = 1;
+    public static final int GENDER_OTHER = 2;
+
+    private String activityUserName;
+    private int activityUserGender;
+    private LocalDate activityUserDateOfBirth;
+    private int activityUserHeightCm;
+    private int activityUserWeightKg;
+    private int activityUserSleepDurationGoal;
+    private int activityUserStepsGoal;
+    private int activityUserCaloriesBurntGoal;
+    private int activityUserDistanceGoalMeters;
+    private int activityUserActiveTimeGoalMinutes;
+    private int activityUserStandingTimeGoalHours;
+    private int activityUserStepLengthCm;
+
+    private static final String defaultUserName = "gadgetbridge-user";
+    public static final int defaultUserGender = GENDER_FEMALE;
+    public static final String defaultUserDateOfBirth = "1970-01-01";
+    public static final int defaultUserAge = 0;
+    public static final int defaultUserHeightCm = 175;
+    public static final int defaultUserWeightKg = 70;
+    public static final int defaultUserSleepDurationGoal = 7;
+    public static final int defaultUserStepsGoal = 8000;
+    public static final int defaultUserCaloriesBurntGoal = 350;
+    public static final int defaultUserDistanceGoalMeters = 5000;
+    public static final int defaultUserActiveTimeGoalMinutes = 60;
+    public static final int defaultUserStepLengthCm = 0;
+    public static final int defaultUserGoalWeightKg = 70;
+    public static final int defaultUserGoalStandingTimeHours = 12;
+    public static final int defaultUserFatBurnTimeMinutes = 30;
+
+    public static final String PREF_USER_NAME = "mi_user_alias";
+    public static final String PREF_USER_DATE_OF_BIRTH = "activity_user_date_of_birth";
+    public static final String PREF_USER_GENDER = "activity_user_gender";
+    public static final String PREF_USER_HEIGHT_CM = "activity_user_height_cm";
+    public static final String PREF_USER_WEIGHT_KG = "activity_user_weight_kg";
+    public static final String PREF_USER_SLEEP_DURATION = "activity_user_sleep_duration";
+    public static final String PREF_USER_STEPS_GOAL = "fitness_goal"; // FIXME: for compatibility
+    public static final String PREF_USER_CALORIES_BURNT = "activity_user_calories_burnt";
+    public static final String PREF_USER_DISTANCE_METERS = "activity_user_distance_meters";
+    public static final String PREF_USER_ACTIVETIME_MINUTES = "activity_user_activetime_minutes";
+    public static final String PREF_USER_STEP_LENGTH_CM = "activity_user_step_length_cm";
+    public static final String PREF_USER_GOAL_WEIGHT_KG = "activity_user_goal_weight_kg";
+    public static final String PREF_USER_GOAL_STANDING_TIME_HOURS = "activity_user_goal_standing_time_minutes";
+    public static final String PREF_USER_GOAL_FAT_BURN_TIME_MINUTES = "activity_user_goal_fat_burn_time_minutes";
+
+    public ActivityUser() {
+        fetchPreferences();
+    }
+
+    public String getName() {
+        return activityUserName;
+    }
+
+    public int getWeightKg() {
+        return activityUserWeightKg;
+    }
+
+    /**
+     * @see #GENDER_FEMALE
+     * @see #GENDER_MALE
+     * @see #GENDER_OTHER
+     */
+    public int getGender() {
+        return activityUserGender;
+    }
+
+    public LocalDate getDateOfBirth() {
+        return activityUserDateOfBirth;
+    }
+
+    /**
+     * @return the user defined height or a default value when none is set or the stored
+     * value is 0.
+     */
+
+    public int getHeightCm() {
+        if (activityUserHeightCm < 1) {
+            activityUserHeightCm = defaultUserHeightCm;
+        }
+        return activityUserHeightCm;
+    }
+
+    /**
+     * @return the user defined step length or the calculated default value when none is set or the stored
+     * value is 0.
+     */
+    public int getStepLengthCm() {
+        if (activityUserStepLengthCm < 1) {
+            activityUserStepLengthCm = (int) (getHeightCm() * 0.43);
+        }
+        return activityUserStepLengthCm;
+    }
+
+    /**
+     * @return the user defined sleep duration or the default value when none is set or the stored
+     * value is out of any logical bounds.
+     */
+    public int getSleepDurationGoal() {
+        if (activityUserSleepDurationGoal < 1 || activityUserSleepDurationGoal > 24) {
+            activityUserSleepDurationGoal = defaultUserSleepDurationGoal;
+        }
+        return activityUserSleepDurationGoal;
+    }
+
+    public int getStepsGoal() {
+        if (activityUserStepsGoal < 1) {
+            activityUserStepsGoal = defaultUserStepsGoal;
+        }
+        return activityUserStepsGoal;
+    }
+
+    public int getAge() {
+        return Period.between(getDateOfBirth(), LocalDate.now()).getYears();
+    }
+
+    private void fetchPreferences() {
+        Prefs prefs = GBApplication.getPrefs();
+        activityUserName = prefs.getString(PREF_USER_NAME, defaultUserName);
+        activityUserGender = prefs.getInt(PREF_USER_GENDER, defaultUserGender);
+        activityUserHeightCm = prefs.getInt(PREF_USER_HEIGHT_CM, defaultUserHeightCm);
+        activityUserWeightKg = prefs.getInt(PREF_USER_WEIGHT_KG, defaultUserWeightKg);
+        activityUserDateOfBirth = prefs.getLocalDate(PREF_USER_DATE_OF_BIRTH, defaultUserDateOfBirth);
+        activityUserSleepDurationGoal = prefs.getInt(PREF_USER_SLEEP_DURATION, defaultUserSleepDurationGoal);
+        activityUserStepsGoal = prefs.getInt(PREF_USER_STEPS_GOAL, defaultUserStepsGoal);
+        activityUserCaloriesBurntGoal = prefs.getInt(PREF_USER_CALORIES_BURNT, defaultUserCaloriesBurntGoal);
+        activityUserDistanceGoalMeters = prefs.getInt(PREF_USER_DISTANCE_METERS, defaultUserDistanceGoalMeters);
+        activityUserActiveTimeGoalMinutes = prefs.getInt(PREF_USER_ACTIVETIME_MINUTES, defaultUserActiveTimeGoalMinutes);
+        activityUserStandingTimeGoalHours = prefs.getInt(PREF_USER_GOAL_STANDING_TIME_HOURS, defaultUserGoalStandingTimeHours);
+        activityUserStepLengthCm = prefs.getInt(PREF_USER_STEP_LENGTH_CM, defaultUserStepLengthCm);
+    }
+
+    /**
+     * @deprecated use {@link #getDateOfBirth()}.
+     */
+    @Deprecated
+    public Date getUserBirthday() {
+        final LocalDate dateOfBirth = getDateOfBirth();
+        Calendar cal = DateTimeUtils.getCalendarUTC();
+        cal.set(GregorianCalendar.YEAR, dateOfBirth.getYear());
+        cal.set(GregorianCalendar.MONTH, dateOfBirth.getMonthValue() - 1);
+        cal.set(GregorianCalendar.DAY_OF_MONTH, dateOfBirth.getDayOfMonth());
+        return cal.getTime();
+    }
+
+    public int getCaloriesBurntGoal()
+    {
+        if (activityUserCaloriesBurntGoal < 1) {
+            activityUserCaloriesBurntGoal = defaultUserCaloriesBurntGoal;
+        }
+        return activityUserCaloriesBurntGoal;
+    }
+
+    public int getDistanceGoalMeters()
+    {
+        if (activityUserDistanceGoalMeters < 1) {
+            activityUserDistanceGoalMeters = defaultUserDistanceGoalMeters;
+        }
+        return activityUserDistanceGoalMeters;
+    }
+
+    public int getActiveTimeGoalMinutes()
+    {
+        if (activityUserActiveTimeGoalMinutes < 1) {
+            activityUserActiveTimeGoalMinutes = defaultUserActiveTimeGoalMinutes;
+        }
+        return activityUserActiveTimeGoalMinutes;
+    }
+
+    public int getStandingTimeGoalHours()
+    {
+        if (activityUserStandingTimeGoalHours < 1) {
+            activityUserStandingTimeGoalHours = defaultUserGoalStandingTimeHours;
+        }
+        return activityUserStandingTimeGoalHours;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/Alarm.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/Alarm.java
new file mode 100644
index 0000000..59c3a59
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/Alarm.java
@@ -0,0 +1,64 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, Carsten Pfeiffer, Dmitry
+    Markin, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.model;
+
+import java.io.Serializable;
+
+public interface Alarm extends Serializable {
+    /**
+     * The {@link android.os.Bundle} name for transferring parceled alarms.
+     */
+    String EXTRA_ALARM = "alarm";
+
+    byte ALARM_ONCE = 0;
+    byte ALARM_MON = 1;
+    byte ALARM_TUE = 2;
+    byte ALARM_WED = 4;
+    byte ALARM_THU = 8;
+    byte ALARM_FRI = 16;
+    byte ALARM_SAT = 32;
+    byte ALARM_SUN = 64;
+
+    byte ALARM_DAILY = Alarm.ALARM_MON | Alarm.ALARM_TUE | Alarm.ALARM_WED | Alarm.ALARM_THU | Alarm.ALARM_FRI | Alarm.ALARM_SAT | Alarm.ALARM_SUN;
+
+    int getPosition();
+
+    boolean getEnabled();
+
+    boolean getUnused();
+
+    boolean getSmartWakeup();
+
+    Integer getSmartWakeupInterval();
+
+    boolean getSnooze();
+
+    int getRepetition();
+
+    boolean isRepetitive();
+
+    boolean getRepetition(int dow);
+
+    int getHour();
+
+    int getMinute();
+
+    String getTitle();
+
+    String getDescription();
+}
\ No newline at end of file
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/AppNotificationType.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/AppNotificationType.java
new file mode 100644
index 0000000..145c491
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/AppNotificationType.java
@@ -0,0 +1,197 @@
+/*  Copyright (C) 2016-2024 Andreas Shimokawa, AnthonyDiGirolamo, Daniele
+    Gobbetti, Davis Mosenkovs, foxstidious, Frank Slezak, Kaz Wolfe, Kevin Richter,
+    Lukas Veneziano, Marvin D, Matthieu Baerts, Mave95, Maxim Baz, michaelneu,
+    musover, NotAFIle, Tomas Radej, w2q
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.model;
+
+import java.util.HashMap;
+
+public class AppNotificationType extends HashMap<String, NotificationType> {
+
+    private static AppNotificationType _instance;
+
+    public static AppNotificationType getInstance() {
+        if (_instance == null) {
+            return (_instance = new AppNotificationType());
+        }
+
+        return _instance;
+    }
+
+    private AppNotificationType() {
+        // Generic Email
+        put("com.fsck.k9", NotificationType.GENERIC_EMAIL);
+        put("com.fsck.k9.material", NotificationType.GENERIC_EMAIL);
+        put("com.imaeses.squeaky", NotificationType.GENERIC_EMAIL);
+        put("com.android.email", NotificationType.GENERIC_EMAIL);
+        put("ch.protonmail.android", NotificationType.GENERIC_EMAIL);
+        put("security.pEp", NotificationType.GENERIC_EMAIL);
+        put("eu.faircode.email", NotificationType.GENERIC_EMAIL);
+
+        // Generic SMS
+        put("com.moez.QKSMS", NotificationType.GENERIC_SMS);
+        put("com.android.mms", NotificationType.GENERIC_SMS);
+        put("com.android.messaging", NotificationType.GENERIC_SMS);
+        put("com.sonyericsson.conversations", NotificationType.GENERIC_SMS);
+        put("org.smssecure.smssecure", NotificationType.GENERIC_SMS);
+
+        // Generic Calendar
+        put("com.android.calendar", NotificationType.GENERIC_CALENDAR);
+        put("mikado.bizcalpro", NotificationType.BUSINESS_CALENDAR);
+
+        // Google
+        put("com.google.android.gm", NotificationType.GMAIL);
+        put("com.google.android.apps.inbox", NotificationType.GOOGLE_INBOX);
+        put("com.google.android.calendar", NotificationType.GENERIC_CALENDAR);
+        put("com.google.android.apps.messaging", NotificationType.GOOGLE_MESSENGER);
+        put("com.google.android.talk", NotificationType.GOOGLE_HANGOUTS);
+        put("com.google.android.apps.maps", NotificationType.GOOGLE_MAPS);
+        put("com.google.android.apps.photos", NotificationType.GOOGLE_PHOTOS);
+        put("com.google.android.apps.googlevoice", NotificationType.GENERIC_SMS);
+
+        // Conversations
+        put("eu.siacs.conversations", NotificationType.CONVERSATIONS);
+        put("de.pixart.messenger", NotificationType.CONVERSATIONS);
+
+        // Riot, Element, ElementX
+        put("im.vector.alpha", NotificationType.RIOT);
+        put("im.vector.app", NotificationType.ELEMENT);
+        put("io.element.android.x", NotificationType.ELEMENTX);
+
+        // Signal
+        put("org.thoughtcrime.securesms", NotificationType.SIGNAL);
+        put("im.molly.app", NotificationType.MOLLY);
+        put("im.molly.app.unifiedpush", NotificationType.MOLLY);
+
+        // Wire
+        put("com.wire", NotificationType.WIRE);
+
+        // Telegram
+        put("org.telegram.messenger", NotificationType.TELEGRAM);
+        put("org.telegram.messenger.beta", NotificationType.TELEGRAM);
+        put("org.telegram.messenger.web", NotificationType.TELEGRAM);
+        put("org.telegram.plus", NotificationType.TELEGRAM); // "Plus Messenger"
+        put("org.thunderdog.challegram", NotificationType.TELEGRAM);
+        put("nekox.messenger", NotificationType.TELEGRAM);
+
+        // Threema
+        put("ch.threema.app", NotificationType.THREEMA);
+
+        // Kontalk
+        put("org.kontalk", NotificationType.KONTALK);
+
+        // Antox
+        put("chat.tox.antox", NotificationType.ANTOX);
+
+        // Twitter
+        put("org.mariotaku.twidere", NotificationType.TWITTER);
+        put("com.twitter.android", NotificationType.TWITTER);
+        put("org.andstatus.app", NotificationType.TWITTER);
+        put("org.mustard.android", NotificationType.TWITTER);
+
+        // Facebook
+        put("me.zeeroooo.materialfb", NotificationType.FACEBOOK);
+        put("it.rignanese.leo.slimfacebook", NotificationType.FACEBOOK);
+        put("me.jakelane.wrapperforfacebook", NotificationType.FACEBOOK);
+        put("com.facebook.katana", NotificationType.FACEBOOK);
+        put("org.indywidualni.fblite", NotificationType.FACEBOOK);
+
+        // Facebook Messenger
+        put("com.facebook.orca", NotificationType.FACEBOOK_MESSENGER);
+        put("com.facebook.mlite", NotificationType.FACEBOOK_MESSENGER);
+
+        // WhatsApp
+        put("com.whatsapp", NotificationType.WHATSAPP);
+
+        // HipChat
+        put("com.hipchat", NotificationType.HIPCHAT);
+
+        // Skype
+        put("com.skype.raider", NotificationType.SKYPE);
+
+        // Skype for business
+        put("com.microsoft.office.lync15", NotificationType.SKYPE);
+
+        // Mailbox
+        put("com.mailboxapp", NotificationType.MAILBOX);
+
+        // Snapchat
+        put("com.snapchat.android", NotificationType.SNAPCHAT);
+
+        // WeChat
+        put("com.tencent.mm", NotificationType.WECHAT);
+
+        // Viber
+        put("com.viber.voip", NotificationType.VIBER);
+
+        // Instagram
+        put("com.instagram.android", NotificationType.INSTAGRAM);
+
+        // Kik
+        put("kik.android", NotificationType.KIK);
+
+        // Line
+        put("jp.naver.line.android", NotificationType.LINE);
+
+        // BBM
+        put("com.bbm", NotificationType.BBM);
+
+        // Microsoft Outlook
+        put("com.microsoft.office.outlook", NotificationType.OUTLOOK);
+
+        // Business Calendar
+        put("com.appgenix.bizcal", NotificationType.BUSINESS_CALENDAR);
+
+        // Yahoo Mail
+        put("com.yahoo.mobile.client.android.mail", NotificationType.YAHOO_MAIL);
+
+        // Kakao Talk
+        put("com.kakao.talk", NotificationType.KAKAO_TALK);
+
+        // Amazon
+        put("com.amazon.mshop.android.shopping", NotificationType.AMAZON);
+
+        // LinkedIn
+        put("com.linkedin.android", NotificationType.LINKEDIN);
+
+        // Slack
+        put("com.slack", NotificationType.SLACK);
+
+        // Transit
+        put("com.thetransitapp.droid", NotificationType.TRANSIT);
+
+        // Etar
+        put("ws.xsoh.etar", NotificationType.GENERIC_CALENDAR);
+
+        // Discord
+        put("com.discord", NotificationType.DISCORD);
+
+        // COL Reminder
+        put("com.colapps.reminder", NotificationType.COL_REMINDER);
+
+        //Deltachat
+        put("com.b44t.messenger", NotificationType.DELTACHAT);
+
+        // KDE Itinerary
+        put("org.kde.itinerary", NotificationType.TRANSIT);
+
+        // KTrip
+        put("org.kde.ktrip", NotificationType.TRANSIT);
+    }
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/BatteryConfig.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/BatteryConfig.java
new file mode 100644
index 0000000..f89406f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/BatteryConfig.java
@@ -0,0 +1,89 @@
+/*  Copyright (C) 2021-2024 Jos茅 Rebelo, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+
+package nodomain.freeyourgadget.gadgetbridge.model;
+
+import java.util.Objects;
+
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class BatteryConfig {
+    private final int batteryIndex;
+    private final int batteryIcon;
+    private final int batteryLabel;
+    private final int defaultLowThreshold;
+    private final int defaultFullThreshold;
+
+    public BatteryConfig(int batteryIndex) {
+        this(batteryIndex, GBDevice.BATTERY_ICON_DEFAULT, GBDevice.BATTERY_LABEL_DEFAULT);
+    }
+
+    public BatteryConfig(int batteryIndex, int batteryIcon, int batteryLabel) {
+        this(batteryIndex, batteryIcon, batteryLabel, 10, 100);
+    }
+
+    public BatteryConfig(final int batteryIndex, final int batteryIcon, final int batteryLabel, final int defaultLowThreshold, final int defaultFullThreshold) {
+        this.batteryIndex = batteryIndex;
+        this.batteryIcon = batteryIcon;
+        this.batteryLabel = batteryLabel;
+        this.defaultLowThreshold = defaultLowThreshold;
+        this.defaultFullThreshold = defaultFullThreshold;
+    }
+
+    public int getBatteryIndex() {
+        return batteryIndex;
+    }
+
+    public int getBatteryIcon() {
+        return batteryIcon;
+    }
+
+    public int getBatteryLabel() {
+        return batteryLabel;
+    }
+
+    public int getDefaultLowThreshold() {
+        return defaultLowThreshold;
+    }
+
+    public int getDefaultFullThreshold() {
+        return defaultFullThreshold;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof BatteryConfig)) return false;
+        BatteryConfig that = (BatteryConfig) o;
+        return getBatteryIndex() == that.getBatteryIndex() &&
+                getBatteryIcon() == that.getBatteryIcon() &&
+                getBatteryLabel() == that.getBatteryLabel() &&
+                getDefaultLowThreshold() == that.getDefaultLowThreshold() &&
+                getDefaultFullThreshold() == that.getDefaultFullThreshold();
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(
+                getBatteryIndex(),
+                getBatteryIcon(),
+                getBatteryLabel(),
+                getDefaultLowThreshold(),
+                getDefaultFullThreshold()
+        );
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/BatteryState.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/BatteryState.java
new file mode 100644
index 0000000..d54c66c
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/BatteryState.java
@@ -0,0 +1,27 @@
+/*  Copyright (C) 2015-2024 Daniele Gobbetti, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.model;
+
+public enum BatteryState {
+    UNKNOWN,
+    BATTERY_NORMAL,
+    BATTERY_LOW,
+    BATTERY_CHARGING,
+    BATTERY_CHARGING_FULL,
+    BATTERY_NOT_CHARGING_FULL,
+    NO_BATTERY
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/BodyEnergySample.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/BodyEnergySample.java
new file mode 100644
index 0000000..50a412a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/BodyEnergySample.java
@@ -0,0 +1,24 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.model;
+
+public interface BodyEnergySample extends TimeSample {
+    /**
+     * Body energy value, between 0 and 100.
+     */
+    int getEnergy();
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/CalendarEventSpec.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/CalendarEventSpec.java
new file mode 100644
index 0000000..553c63a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/CalendarEventSpec.java
@@ -0,0 +1,38 @@
+/*  Copyright (C) 2016-2024 Andreas Shimokawa, Daniele Gobbetti, Gabriele
+    Monaco
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.model;
+
+import java.util.ArrayList;
+
+public class CalendarEventSpec {
+    public static final byte TYPE_UNKNOWN = 0;
+    public static final byte TYPE_SUNRISE = 1;
+    public static final byte TYPE_SUNSET = 2;
+
+    public byte type;
+    public long id;
+    public int timestamp;
+    public int durationInSeconds;
+    public String title;
+    public String description;
+    public String location;
+    public String calName;
+    public int color;
+    public boolean allDay;
+    public ArrayList<Long> reminders; // unix epoch millis
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/CallSpec.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/CallSpec.java
new file mode 100644
index 0000000..a7561c1
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/CallSpec.java
@@ -0,0 +1,41 @@
+/*  Copyright (C) 2016-2024 Andreas Shimokawa, Davis Mosenkovs, Dmitry
+    Markin, mvn23
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.model;
+
+public class CallSpec {
+    public static final int CALL_UNDEFINED = 0;
+    public static final int CALL_ACCEPT = 1;
+    public static final int CALL_INCOMING = 2;
+    public static final int CALL_OUTGOING = 3;
+    public static final int CALL_REJECT = 4;
+    public static final int CALL_START = 5;
+    public static final int CALL_END = 6;
+
+    public String number;
+    public String name;
+
+    public String sourceName;
+
+    /**
+     * The application that generated the notification.
+     */
+    public String sourceAppId;
+
+    public int command;
+    public int dndSuppressed;
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/CannedMessagesSpec.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/CannedMessagesSpec.java
new file mode 100644
index 0000000..39351ef
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/CannedMessagesSpec.java
@@ -0,0 +1,26 @@
+/*  Copyright (C) 2016-2024 Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.model;
+
+public class CannedMessagesSpec {
+    public static final byte TYPE_GENERIC = 0;
+    public static final byte TYPE_REJECTEDCALLS = 1;
+    public static final byte TYPE_NEWSMS = 2;
+
+    public int type;
+    public String[] cannedMessages;
+}
\ No newline at end of file
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/Contact.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/Contact.java
new file mode 100644
index 0000000..115f24e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/Contact.java
@@ -0,0 +1,32 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.model;
+
+import java.io.Serializable;
+
+public interface Contact extends Serializable {
+    /**
+     * The {@link android.os.Bundle} name for transferring parceled contacts.
+     */
+    String EXTRA_CONTACT = "contact";
+
+    String getContactId();
+
+    String getName();
+
+    String getNumber();
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DailyTotals.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DailyTotals.java
new file mode 100644
index 0000000..367c825
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DailyTotals.java
@@ -0,0 +1,207 @@
+/*  Copyright (C) 2019-2024 Andreas Shimokawa, Carsten Pfeiffer, Daniel
+    Dakhno, Jos茅 Rebelo, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.model;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.Serializable;
+import java.util.Calendar;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.activities.charts.ActivityAnalysis;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.AbstractTimeSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.DefaultRestingMetabolicRateProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.AbstractActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+
+public class DailyTotals implements Serializable {
+    private static final Logger LOG = LoggerFactory.getLogger(DailyTotals.class);
+
+    private final long steps;
+    private final long distance;
+    private final long activeCalories;
+    private final long restingCalories;
+    private final long[] sleep;  // light deep rem awake
+
+    public DailyTotals() {
+        this(0, 0, new long[]{0, 0, 0 ,0}, 0, 0);
+    }
+
+    public DailyTotals(final long steps, final long distance, final long[] sleep, final long activeCalories, final long restingCalories) {
+        this.steps = steps;
+        this.distance = distance;
+        this.sleep = sleep;
+        this.activeCalories = activeCalories;
+        this.restingCalories = restingCalories;
+    }
+
+    public long getSteps() {
+        return steps;
+    }
+
+    public long getActiveCalories() {
+        return activeCalories;
+    }
+
+    public long getRestingCalories() {
+        return restingCalories;
+    }
+
+    public long getDistance() {
+        return distance;
+    }
+
+    public long getSleep() {
+        // exclude awake sleep
+        return sleep[0] + sleep[1] + sleep[2];
+    }
+
+    public static DailyTotals getDailyTotalsForDevice(GBDevice device, Calendar day) {
+
+        try (DBHandler handler = GBApplication.acquireDB()) {
+            return getDailyTotalsForDevice(device, day, handler);
+        } catch (Exception e) {
+            //GB.toast("Error loading sleep/steps widget data for device: " + device, Toast.LENGTH_SHORT, GB.ERROR, e);
+            return new DailyTotals();
+        }
+    }
+
+    public static DailyTotals getDailyTotalsForDevice(GBDevice device, Calendar day, DBHandler handler) {
+        ActivityAnalysis analysis = new ActivityAnalysis();
+        ActivityAmounts totalAmounts;
+        ActivityAmounts amountsSleep;
+
+        totalAmounts = analysis.calculateActivityAmounts(getSamplesOfDay(handler, day, 0, device));
+        amountsSleep = analysis.calculateActivityAmounts(getSamplesOfDay(handler, day, -12, device));
+
+        long[] sleep = getTotalsSleepForActivityAmounts(amountsSleep);
+
+        long totalSteps = 0;
+        long totalDistance = 0;
+        long totalActiveCalories = 0;
+        long totalRestingCalories = 0;
+        for (ActivityAmount amount : totalAmounts.getAmounts()) {
+            totalSteps += amount.getTotalSteps();
+            totalDistance += amount.getTotalDistance();
+            totalActiveCalories += amount.getTotalActiveCalories();
+        }
+        totalRestingCalories = getRestingCaloriesOfDay(handler, day, device);
+
+        // Purposely not including awake sleep
+        return new DailyTotals(totalSteps, totalDistance, sleep, totalActiveCalories, totalRestingCalories);
+    }
+
+    private static long[] getTotalsSleepForActivityAmounts(ActivityAmounts activityAmounts) {
+        long totalSecondsDeepSleep = 0;
+        long totalSecondsLightSleep = 0;
+        long totalSecondsRemSleep = 0;
+        long totalSecondsAwakeSleep = 0;
+        for (ActivityAmount amount : activityAmounts.getAmounts()) {
+            if (amount.getActivityKind() == ActivityKind.DEEP_SLEEP) {
+                totalSecondsDeepSleep += amount.getTotalSeconds();
+            } else if (amount.getActivityKind() == ActivityKind.LIGHT_SLEEP) {
+                totalSecondsLightSleep += amount.getTotalSeconds();
+            } else if (amount.getActivityKind() == ActivityKind.REM_SLEEP) {
+                totalSecondsRemSleep += amount.getTotalSeconds();
+            } else if (amount.getActivityKind() == ActivityKind.AWAKE_SLEEP) {
+                totalSecondsAwakeSleep += amount.getTotalSeconds();
+            }
+        }
+        long totalMinutesDeepSleep = (totalSecondsDeepSleep / 60);
+        long totalMinutesLightSleep = (totalSecondsLightSleep / 60);
+        long totalMinutesRemSleep = (totalSecondsRemSleep / 60);
+        long totalMinutesAwakeSleep = (totalSecondsAwakeSleep / 60);
+        return new long[]{totalMinutesLightSleep, totalMinutesDeepSleep, totalMinutesRemSleep, totalMinutesAwakeSleep};
+    }
+
+    private static List<? extends ActivitySample> getSamplesOfDay(DBHandler db, Calendar day, int offsetHours, GBDevice device) {
+        int startTs;
+        int endTs;
+
+        day = (Calendar) day.clone(); // do not modify the caller's argument
+        day.set(Calendar.HOUR_OF_DAY, 0);
+        day.set(Calendar.MINUTE, 0);
+        day.set(Calendar.SECOND, 0);
+        day.add(Calendar.HOUR, offsetHours);
+
+        startTs = (int) (day.getTimeInMillis() / 1000);
+        endTs = startTs + 24 * 60 * 60 - 1;
+
+        return getSamples(db, device, startTs, endTs);
+    }
+
+    private static int getRestingCaloriesOfDay(DBHandler db, Calendar day, GBDevice device) {
+        Calendar calendar = Calendar.getInstance();
+        day.add(Calendar.DATE, 0);
+        day.set(Calendar.HOUR_OF_DAY, 0);
+        day.set(Calendar.MINUTE, 0);
+        day.set(Calendar.SECOND, 0);
+        day.add(Calendar.HOUR, 0);
+        RestingMetabolicRateSample metabolicRate = getRestingMetabolicRate(db, day, device);
+        if (metabolicRate == null) {
+            // should never happen
+            return 0;
+        }
+        double passedDayProportion = 1;
+        boolean sameDay = calendar.get(Calendar.DAY_OF_YEAR) == day.get(Calendar.DAY_OF_YEAR) &&
+                calendar.get(Calendar.YEAR) == day.get(Calendar.YEAR);
+        if (sameDay) {
+            passedDayProportion = (double) (calendar.getTimeInMillis() - day.getTimeInMillis()) / (24L * 60 * 60 * 1000);
+        }
+        return  (int) (metabolicRate.getRestingMetabolicRate() * passedDayProportion);
+    }
+
+    public static List<? extends ActivitySample> getSamples(DBHandler db, GBDevice device, int tsFrom, int tsTo) {
+        return getAllSamples(db, device, tsFrom, tsTo);
+    }
+
+    protected static RestingMetabolicRateSample getRestingMetabolicRate(DBHandler db, Calendar day, GBDevice device) {
+        // FIXME this cast is ugly and might lead to issues
+        AbstractTimeSampleProvider<? extends RestingMetabolicRateSample> provider = (AbstractTimeSampleProvider<? extends RestingMetabolicRateSample>)
+                device.getDeviceCoordinator().getRestingMetabolicRateProvider(device, db.getDaoSession());
+        final long endOfDayTimestamp = day.getTimeInMillis() + 86_400_000L;
+        final RestingMetabolicRateSample latestSample = provider.getLastSampleBefore(endOfDayTimestamp);
+        if (latestSample != null) {
+            return latestSample;
+        }
+        DefaultRestingMetabolicRateProvider defaultProvider = new DefaultRestingMetabolicRateProvider(device, db.getDaoSession());
+        return defaultProvider.getLastSampleBefore(endOfDayTimestamp);
+    }
+
+    protected static SampleProvider<? extends AbstractActivitySample> getProvider(DBHandler db, GBDevice device) {
+        DeviceCoordinator coordinator = device.getDeviceCoordinator();
+        return coordinator.getSampleProvider(device, db.getDaoSession());
+    }
+
+    protected static List<? extends ActivitySample> getAllSamples(DBHandler db, GBDevice device, int tsFrom, int tsTo) {
+        SampleProvider<? extends ActivitySample> provider = getProvider(db, device);
+        return provider.getAllActivitySamples(tsFrom, tsTo);
+    }
+
+    public static ActivitySample getFirstSample(DBHandler db, GBDevice device) {
+        SampleProvider<? extends ActivitySample> provider = getProvider(db, device);
+        return provider.getFirstActivitySample();
+    }
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceService.java
new file mode 100644
index 0000000..1379eee
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceService.java
@@ -0,0 +1,196 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, Arjan Schrijver, Carsten
+    Pfeiffer, Daniel Dakhno, Daniele Gobbetti, Davis Mosenkovs, Frank Slezak,
+    Gabriele Monaco, Gordon Williams, ivanovlev, JohnnySun, Jos茅 Rebelo, Julien
+    Pivotto, Kasha, mvn23, Petr Van臎k, Sebastian Kranz, Steffen Liebergeld,
+    Taavi Eom盲e
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.model;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.EventHandler;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceCommunicationService;
+
+/**
+ *
+ */
+public interface DeviceService extends EventHandler {
+    String PREFIX = "nodomain.freeyourgadget.gadgetbridge.devices";
+
+    String ACTION_CONNECT = PREFIX + ".action.connect";
+    String ACTION_NOTIFICATION = PREFIX + ".action.notification";
+    String ACTION_DELETE_NOTIFICATION = PREFIX + ".action.delete_notification";
+    String ACTION_CALLSTATE = PREFIX + ".action.callstate";
+    String ACTION_SETCANNEDMESSAGES = PREFIX + ".action.setcannedmessages";
+    String ACTION_SETTIME = PREFIX + ".action.settime";
+    String ACTION_SETMUSICINFO = PREFIX + ".action.setmusicinfo";
+    String ACTION_SETMUSICSTATE = PREFIX + ".action.setmusicstate";
+    String ACTION_SET_PHONE_VOLUME = PREFIX + ".action.set_phone_volume";
+    String ACTION_SET_PHONE_SILENT_MODE = PREFIX + ".action.set_phone_silent_mode";
+    String ACTION_SETNAVIGATIONINFO = PREFIX + ".action.setnavigationinfo";
+    String ACTION_REQUEST_DEVICEINFO = PREFIX + ".action.request_deviceinfo";
+    String ACTION_REQUEST_APPINFO = PREFIX + ".action.request_appinfo";
+    String ACTION_REQUEST_SCREENSHOT = PREFIX + ".action.request_screenshot";
+    String ACTION_STARTAPP = PREFIX + ".action.startapp";
+    String ACTION_DOWNLOADAPP = PREFIX + ".action.downloadapp";
+    String ACTION_DELETEAPP = PREFIX + ".action.deleteapp";
+    String ACTION_APP_CONFIGURE = PREFIX + ".action.app_configure";
+    String ACTION_APP_REORDER = PREFIX + ".action.app_reorder";
+    String ACTION_INSTALL = PREFIX + ".action.install";
+    String ACTION_RESET = PREFIX + ".action.reset";
+    String ACTION_HEARTRATE_TEST = PREFIX + ".action.heartrate_test";
+    String ACTION_FETCH_RECORDED_DATA = PREFIX + ".action.fetch_activity_data";
+    String ACTION_DISCONNECT = PREFIX + ".action.disconnect";
+    String ACTION_FIND_DEVICE = PREFIX + ".action.find_device";
+    String ACTION_PHONE_FOUND = PREFIX + ".action.phone_found";
+    String ACTION_SET_CONSTANT_VIBRATION = PREFIX + ".action.set_constant_vibration";
+    String ACTION_SET_ALARMS = PREFIX + ".action.set_alarms";
+    String ACTION_SAVE_ALARMS = PREFIX + ".action.save_alarms";
+    String ACTION_SAVE_REMINDERS = PREFIX + ".action.save_reminders";
+    String ACTION_SET_REMINDERS = PREFIX + ".action.set_reminders";
+    String ACTION_SET_LOYALTY_CARDS = PREFIX + ".action.set_loyalty_cards";
+    String ACTION_SET_WORLD_CLOCKS = PREFIX + ".action.set_world_clocks";
+    String ACTION_SET_CONTACTS = PREFIX + ".action.set_contacts";
+    String ACTION_ENABLE_REALTIME_STEPS = PREFIX + ".action.enable_realtime_steps";
+    String ACTION_REALTIME_SAMPLES = PREFIX + ".action.realtime_samples";
+    String ACTION_ENABLE_REALTIME_HEARTRATE_MEASUREMENT = PREFIX + ".action.realtime_hr_measurement";
+    String ACTION_ENABLE_HEARTRATE_SLEEP_SUPPORT = PREFIX + ".action.enable_heartrate_sleep_support";
+    String ACTION_SET_HEARTRATE_MEASUREMENT_INTERVAL = PREFIX + ".action.set_heartrate_measurement_intervarl";
+    String ACTION_ADD_CALENDAREVENT = PREFIX + ".action.add_calendarevent";
+    String ACTION_DELETE_CALENDAREVENT = PREFIX + ".action.delete_calendarevent";
+    String ACTION_SEND_CONFIGURATION = PREFIX + ".action.send_configuration";
+    String ACTION_READ_CONFIGURATION = PREFIX + ".action.read_configuration";
+    String ACTION_SEND_WEATHER = PREFIX + ".action.send_weather";
+    String ACTION_TEST_NEW_FUNCTION = PREFIX + ".action.test_new_function";
+    String ACTION_SET_FM_FREQUENCY = PREFIX + ".action.set_fm_frequency";
+    String ACTION_SET_GPS_LOCATION = PREFIX + ".action.set_gps_location";
+    String ACTION_SET_LED_COLOR = PREFIX + ".action.set_led_color";
+    String ACTION_POWER_OFF = PREFIX + ".action.power_off";
+    String ACTION_CAMERA_STATUS_CHANGE = PREFIX + ".action.camera_status_change";
+    String ACTION_REQUEST_MUSIC_LIST = PREFIX + ".action.request_music_list";
+    String ACTION_REQUEST_MUSIC_OPERATION = PREFIX + ".action.request_music_operation";
+
+    String ACTION_SLEEP_AS_ANDROID = ".action.sleep_as_android";
+    String EXTRA_SLEEP_AS_ANDROID_ACTION = "sleepasandroid_action";
+    String EXTRA_NOTIFICATION_BODY = "notification_body";
+    String EXTRA_NOTIFICATION_FLAGS = "notification_flags";
+    String EXTRA_NOTIFICATION_ID = "notification_id";
+    String EXTRA_NOTIFICATION_KEY = "notification_key";
+    String EXTRA_NOTIFICATION_PHONENUMBER = "notification_phonenumber";
+    String EXTRA_NOTIFICATION_SENDER = "notification_sender";
+    String EXTRA_NOTIFICATION_SOURCENAME = "notification_sourcename";
+    String EXTRA_NOTIFICATION_SOURCEAPPID = "notification_sourceappid";
+    String EXTRA_NOTIFICATION_SUBJECT = "notification_subject";
+    String EXTRA_NOTIFICATION_TITLE = "notification_title";
+    String EXTRA_NOTIFICATION_TYPE = "notification_type";
+    String EXTRA_NOTIFICATION_ACTIONS = "notification_actions";
+    String EXTRA_NOTIFICATION_PEBBLE_COLOR = "notification_pebble_color";
+    String EXTRA_NOTIFICATION_ICONID = "notification_iconid";
+    String NOTIFICATION_PICTURE_PATH = "notification_picture_path";
+    String EXTRA_NOTIFICATION_DNDSUPPRESSED = "notification_dndsuppressed";
+    String EXTRA_FIND_START = "find_start";
+    String EXTRA_VIBRATION_INTENSITY = "vibration_intensity";
+    String EXTRA_CALL_COMMAND = "call_command";
+    String EXTRA_CALL_PHONENUMBER = "call_phonenumber";
+    String EXTRA_CALL_DISPLAYNAME = "call_displayname";
+    String EXTRA_CALL_SOURCENAME = "call_sourcename";
+    String EXTRA_CALL_SOURCEAPPID = "call_sourceappid";
+    String EXTRA_CALL_DNDSUPPRESSED = "call_dndsuppressed";
+    String EXTRA_CANNEDMESSAGES = "cannedmessages";
+    String EXTRA_CANNEDMESSAGES_TYPE = "cannedmessages_type";
+    String EXTRA_MUSIC_ARTIST = "music_artist";
+    String EXTRA_MUSIC_ALBUM = "music_album";
+    String EXTRA_MUSIC_TRACK = "music_track";
+    String EXTRA_MUSIC_DURATION = "music_duration";
+    String EXTRA_MUSIC_TRACKNR = "music_tracknr";
+    String EXTRA_MUSIC_TRACKCOUNT = "music_trackcount";
+    String EXTRA_MUSIC_STATE = "music_state";
+    String EXTRA_MUSIC_SHUFFLE = "music_shuffle";
+    String EXTRA_MUSIC_REPEAT = "music_repeat";
+    String EXTRA_MUSIC_POSITION = "music_position";
+    String EXTRA_MUSIC_RATE = "music_rate";
+    String EXTRA_PHONE_VOLUME = "phone_volume";
+    String EXTRA_PHONE_RINGER_MODE = "ringer_mode";
+    String EXTRA_NAVIGATION_INSTRUCTION = "navigation_instruction";
+    String EXTRA_NAVIGATION_DISTANCE_TO_TURN = "navigation_distance_to_turn";
+    String EXTRA_NAVIGATION_NEXT_ACTION = "navigation_next_action";
+    String EXTRA_NAVIGATION_ETA = "navigation_eta";
+    String EXTRA_APP_UUID = "app_uuid";
+    String EXTRA_APP_START = "app_start";
+    String EXTRA_APP_CONFIG = "app_config";
+    String EXTRA_APP_CONFIG_ID = "app_config_id";
+    String EXTRA_URI = "uri";
+    String EXTRA_CONFIG = "config";
+    String EXTRA_ALARMS = "alarms";
+    String EXTRA_REMINDERS = "reminders";
+    String EXTRA_LOYALTY_CARDS = "loyalty_cards";
+    String EXTRA_WORLD_CLOCKS = "world_clocks";
+    String EXTRA_CONTACTS = "contacts";
+    String EXTRA_CONNECT_FIRST_TIME = "connect_first_time";
+    String EXTRA_BOOLEAN_ENABLE = "enable_realtime_steps";
+    String EXTRA_INTERVAL_SECONDS = "interval_seconds";
+    String EXTRA_WEATHER = "weather";
+    String EXTRA_RECORDED_DATA_TYPES = "data_types";
+    String EXTRA_FM_FREQUENCY = "fm_frequency";
+    String EXTRA_LED_COLOR = "led_color";
+    String EXTRA_GPS_LOCATION = "gps_location";
+    String EXTRA_RESET_FLAGS = "reset_flags";
+    String EXTRA_CAMERA_EVENT = "event";
+    String EXTRA_CAMERA_FILENAME = "filename";
+
+    /**
+     * Use EXTRA_REALTIME_SAMPLE instead
+     */
+    @Deprecated
+    String EXTRA_REALTIME_STEPS = "realtime_steps";
+    String EXTRA_REALTIME_SAMPLE = "realtime_sample";
+    String EXTRA_TIMESTAMP = "timestamp";
+    /**
+     * Use EXTRA_REALTIME_SAMPLE instead
+     */
+    @Deprecated
+    String EXTRA_HEART_RATE_VALUE = "hr_value";
+    String EXTRA_CALENDAREVENT_ID = "calendarevent_id";
+    String EXTRA_CALENDAREVENT_TYPE = "calendarevent_type";
+    String EXTRA_CALENDAREVENT_TIMESTAMP = "calendarevent_timestamp";
+    String EXTRA_CALENDAREVENT_DURATION = "calendarevent_duration";
+    String EXTRA_CALENDAREVENT_ALLDAY = "calendarevent_allday";
+    String EXTRA_CALENDAREVENT_REMINDERS = "calendarevent_reminders";
+    String EXTRA_CALENDAREVENT_TITLE = "calendarevent_title";
+    String EXTRA_CALENDAREVENT_DESCRIPTION = "calendarevent_description";
+    String EXTRA_CALENDAREVENT_LOCATION = "calendarevent_location";
+    String EXTRA_CALENDAREVENT_CALNAME = "calendarevent_calname";
+    String EXTRA_CALENDAREVENT_COLOR = "calendarevent_color";
+
+    void connect();
+
+    void connect(boolean firstTime);
+
+    void disconnect();
+
+    void quit();
+
+    DeviceService forDevice(GBDevice device);
+
+    /**
+     * Requests information from the {@link DeviceCommunicationService} about the connection state,
+     * firmware info, etc.
+     * <p/>
+     * Note that this will not need a connection to the device -- only the cached information
+     * from the service will be reported.
+     */
+    void requestDeviceInfo();
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceType.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceType.java
new file mode 100644
index 0000000..4b25f28
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceType.java
@@ -0,0 +1,619 @@
+/*  Copyright (C) 2015-2024 115ek, akasaka / Genjitsu Labs, Alicia Hormann,
+    Andreas B枚hler, Andreas Shimokawa, Andrew Watkins, angelpup, Carsten Pfeiffer,
+    Cre3per, Damien Gaignon, DanialHanif, Daniel Dakhno, Daniele Gobbetti, Daniel
+    Thompson, Da Pa, Dmytro Bielik, Frank Ertl, Gabriele Monaco, GeekosaurusR3x,
+    Guido J盲kel, Gordon Williams, Jean-Fran莽ois Greffier, jfgreffier, jhey, Joa虄o Paulo
+    Barraca, Jochen S, Johannes Krude, Jos茅 Rebelo, ksiwczynski, ladbsoft,
+    Lesur Frederic, Maciej Ku艣nierz, mamucho, Manuel Ru脽, Maxime Reyrolle,
+    maxirnilian, Michael, narektor, Noodlez, odavo32nof, opavlov, pangwalla,
+    Pavel Elagin, Petr Kadlec, Petr Van臎k, protomors, Quallenauge, Quang Ng么,
+    Raghd Hamzeh, Sami Alaoui, Sebastian Kranz, sedy89, Sophanimus, Stefan Bora,
+    Taavi Eom盲e, thermatk, tiparega, Vadim Kaushan, x29a, xaos, Yoran Vulker,
+    Yukai Li
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.model;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.UnknownDeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.asteroidos.AsteroidOSDeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.bandwpseries.BandWPSeriesDeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.banglejs.BangleJSCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.binary_sensor.coordinator.BinarySensorCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.casio.ecbs100.CasioECBS100DeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.casio.gb6900.CasioGB6900DeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.casio.gbx100.CasioGBX100DeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.casio.gwb5600.CasioGMWB5000DeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.casio.gwb5600.CasioGWB5600DeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.cmfwatchpro.CmfWatchPro2Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.cmfwatchpro.CmfWatchProCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.colmi.ColmiR02Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.colmi.ColmiR03Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.colmi.ColmiR06Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.colmi.ColmiR09Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.colmi.ColmiR10Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.cycling_sensor.coordinator.CyclingSensorCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.divoom.PixooCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.domyos.DomyosT540Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.femometer.FemometerVinca2DeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProDeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.fitpro.colacao.ColaCao21Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.fitpro.colacao.ColaCao23Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.flipper.zero.FlipperZeroCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.galaxy_buds.GalaxyBuds2DeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.galaxy_buds.GalaxyBuds2ProDeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.galaxy_buds.GalaxyBudsDeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.galaxy_buds.GalaxyBudsLiveDeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.galaxy_buds.GalaxyBudsProDeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.enduro.GarminEnduro3Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.epix.GarminEpixProCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.fenix.GarminFenix5Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.fenix.GarminFenix5PlusCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.fenix.GarminFenix5XPlusCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.fenix.GarminFenix6Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.fenix.GarminFenix6SProCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.fenix.GarminFenix6SSapphireCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.fenix.GarminFenix6SapphireCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.fenix.GarminFenix7Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.fenix.GarminFenix7ProCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.fenix.GarminFenix7SCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.fenix.GarminFenix7XCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.fenix.GarminFenix8Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.forerunner.GarminForerunner165Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.forerunner.GarminForerunner235Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.forerunner.GarminForerunner245Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.forerunner.GarminForerunner245MusicCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.forerunner.GarminForerunner255Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.forerunner.GarminForerunner255MusicCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.forerunner.GarminForerunner255SCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.forerunner.GarminForerunner255SMusicCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.forerunner.GarminForerunner265Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.forerunner.GarminForerunner265SCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.forerunner.GarminForerunner55Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.forerunner.GarminForerunner620Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.forerunner.GarminForerunner955Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.forerunner.GarminForerunner965Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.instinct.GarminInstinct2Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.instinct.GarminInstinct2SCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.instinct.GarminInstinct2SSolarCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.instinct.GarminInstinct2SolTacCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.instinct.GarminInstinct2SolarCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.instinct.GarminInstinct2XSolarCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.instinct.GarminInstinctCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.instinct.GarminInstinctCrossoverCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.instinct.GarminInstinctSolarCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.swim.GarminSwim2Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.venu.GarminVenu2Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.venu.GarminVenu2PlusCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.venu.GarminVenu2SCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.venu.GarminVenu3Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.venu.GarminVenu3SCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.venu.GarminVenuCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.venu.GarminVenuSq2Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.venu.GarminVenuSqCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.vivoactive.GarminVivoActive3Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.vivoactive.GarminVivoActive4Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.vivoactive.GarminVivoActive4SCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.vivoactive.GarminVivoActive5Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.vivomove.GarminVivomoveHrCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.vivomove.GarminVivomoveStyleCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.vivomove.GarminVivomoveTrendCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.vivosmart.GarminVivosmart5Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.vivosport.GarminVivosportCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.hama.fit6900.HamaFit6900DeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.hplus.EXRIZUK8Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.hplus.HPlusCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.hplus.MakibesF68Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.hplus.Q8Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.hplus.SG2Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitactive.AmazfitActiveCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitactiveedge.AmazfitActiveEdgeCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitbalance.AmazfitBalanceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitband5.AmazfitBand5Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitband7.AmazfitBand7Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitbip.AmazfitBipCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitbip.AmazfitBipLiteCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitbip3.AmazfitBip3Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitbip3pro.AmazfitBip3ProCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitbip5.AmazfitBip5Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitbip5unity.AmazfitBip5UnityCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitbips.AmazfitBipSCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitbips.AmazfitBipSLiteCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitbipu.AmazfitBipUCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitbipupro.AmazfitBipUProCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitcheetahpro.AmazfitCheetahProCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitcheetahround.AmazfitCheetahRoundCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitcheetahsquare.AmazfitCheetahSquareCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitcor.AmazfitCorCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitcor2.AmazfitCor2Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitfalcon.AmazfitFalconCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitgtr.AmazfitGTRCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitgtr.AmazfitGTRLiteCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitgtr2.AmazfitGTR2Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitgtr2.AmazfitGTR2eCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitgtr3.AmazfitGTR3Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitgtr3pro.AmazfitGTR3ProCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitgtr4.AmazfitGTR4Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitgtrmini.AmazfitGTRMiniCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitgts.AmazfitGTSCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitgts2.AmazfitGTS2Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitgts2.AmazfitGTS2MiniCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitgts2.AmazfitGTS2eCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitgts3.AmazfitGTS3Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitgts4.AmazfitGTS4Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitgts4mini.AmazfitGTS4MiniCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitneo.AmazfitNeoCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitpop.AmazfitPopCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitpoppro.AmazfitPopProCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfittrex.AmazfitTRexCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfittrex2.AmazfitTRex2Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfittrex3.AmazfitTRex3Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfittrexpro.AmazfitTRexProCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfittrexultra.AmazfitTRexUltraCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitvergel.AmazfitVergeLCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitx.AmazfitXCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.miband2.MiBand2Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.miband2.MiBand2HRXCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.miband3.MiBand3Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.miband4.MiBand4Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.miband5.MiBand5Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.miband6.MiBand6Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.miband7.MiBand7Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.zeppe.ZeppECoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.freebuds5i.HuaweiFreebuds5iCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.honorband3.HonorBand3Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.honorband4.HonorBand4Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.honorband5.HonorBand5Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.honorband6.HonorBand6Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.honorband7.HonorBand7Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.honormagicwatch2.HonorMagicWatch2Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.honorwatchgs3.HonorWatchGS3Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.honorwatchgspro.HonorWatchGSProCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.huaweiband2pro.HuaweiBand2ProCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.huaweiband3pro.HuaweiBand3ProCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.huaweiband4pro.HuaweiBand4ProCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.huaweiband6.HuaweiBand6Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.huaweiband7.HuaweiBand7Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.huaweiband8.HuaweiBand8Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.huaweiband9.HuaweiBand9Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.huaweibandaw70.HuaweiBandAw70Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.huaweitalkbandb6.HuaweiTalkBandB6Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.huaweiwatch3.HuaweiWatch3Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.huaweiwatch4pro.HuaweiWatch4ProCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.huaweiwatchd2.HuaweiWatchD2Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.huaweiwatchfit.HuaweiWatchFitCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.huaweiwatchfit2.HuaweiWatchFit2Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.huaweiwatchfit3.HuaweiWatchFit3Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.huaweiwatchgt.HuaweiWatchGTCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.huaweiwatchgt2.HuaweiWatchGT2Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.huaweiwatchgt2e.HuaweiWatchGT2eCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.huaweiwatchgt3.HuaweiWatchGT3Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.huaweiwatchgt4.HuaweiWatchGT4Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.huaweiwatchgt5.HuaweiWatchGT5Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.huaweiwatchgtcyber.HuaweiWatchGTCyberCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.huaweiwatchgtrunner.HuaweiWatchGTRunnerCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.huaweiwatchultimate.HuaweiWatchUltimateCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.id115.ID115Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.idasen.IdasenCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.itag.ITagCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.jyou.BFH16DeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.jyou.TeclastH30.TeclastH30Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.jyou.y5.Y5Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.BohemicSmartBraceletDeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunDeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.VivitarHrBpMonitorActivityTrackerCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.lenovo.watchxplus.WatchXPlusDeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.liveview.LiveviewCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.makibeshr3.MakibesHR3Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.marstek.MarstekB2500DeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.mijia_lywsd.MijiaLywsd02Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.mijia_lywsd.MijiaLywsd03Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.mijia_lywsd.MijiaMhoC303Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.mijia_lywsd.MijiaXmwsdj04Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.miscale.MiCompositionScaleCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.miscale.MiSmartScaleCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.moondrop.MoondropSpaceTravelCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.no1f1.No1F1Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.nothing.CmfBudsPro2Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.nothing.Ear1Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.nothing.Ear2Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.nothing.EarStickCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.nut.NutCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.oppo.OppoEncoAir2Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.oppo.OppoEncoAirCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.pebble.PebbleCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.pinetime.PineTimeJFCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.qc35.QC35Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.qhybrid.QHybridCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.realme.RealmeBudsT110Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.roidmi.Roidmi1Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.roidmi.Roidmi3Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.scannable.ScannableDeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.smaq2oss.SMAQ2OSSCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.soflow.SoFlowCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.coordinators.SonyLinkBudsCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.coordinators.SonyLinkBudsSCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.coordinators.SonyWF1000XM3Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.coordinators.SonyWF1000XM4Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.coordinators.SonyWF1000XM5Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.coordinators.SonyWFC500Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.coordinators.SonyWFC700NCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.coordinators.SonyWFSP800NCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.coordinators.SonyWH1000XM2Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.coordinators.SonyWH1000XM3Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.coordinators.SonyWH1000XM4Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.coordinators.SonyWH1000XM5Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.coordinators.SonyWIC100Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.coordinators.SonyWISP600NCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.wena3.SonyWena3Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.sonyswr12.SonySWR12DeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.soundcore.liberty3_pro.SoundcoreLiberty3ProCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.soundcore.liberty4_nc.SoundcoreLiberty4NCCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.soundcore.motion300.SoundcoreMotion300Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.supercars.SuperCarsCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.test.TestDeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.tlw64.TLW64Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.um25.Coordinator.UM25Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.vesc.VescCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.vibratissimo.VibratissimoCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.waspos.WaspOSCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.watch9.Watch9DeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.withingssteelhr.WithingsSteelHRDeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.miband7pro.MiBand7ProCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.miband8.MiBand8Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.miband8active.MiBand8ActiveCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.miband8pro.MiBand8ProCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.miband9.MiBand9Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.miband9pro.MiBand9ProCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.miwatch.MiWatchLiteCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.miwatchcolorsport.MiWatchColorSportCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.redmibuds5pro.RedmiBuds5ProCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.redmismartband2.RedmiSmartBand2Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.redmismartbandpro.RedmiSmartBandProCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.redmiwatch2.RedmiWatch2Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.redmiwatch2lite.RedmiWatch2LiteCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.redmiwatch3.RedmiWatch3Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.redmiwatch3active.RedmiWatch3ActiveCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.redmiwatch4.RedmiWatch4Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.redmiwatch5active.RedmiWatch5ActiveCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.redmiwatch5lite.RedmiWatch5LiteCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.watchs1.XiaomiWatchS1Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.watchs1active.XiaomiWatchS1ActiveCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.watchs1pro.XiaomiWatchS1ProCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.watchs3.XiaomiWatchS3Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.xwatch.XWatchCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.zetime.ZeTimeCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.gatt_client.BleGattClientCoordinator;
+
+/**
+ * For every supported device, a device type constant must exist.
+ * <p>
+ * Note: they name of the enum is stored in the DB, so it is fixed forever,
+ * and may not be changed.
+ * <p>
+ * Migration note: As of <a href="https://codeberg.org/Freeyourgadget/Gadgetbridge/pulls/3347">#3347</a>,
+ * the numeric device id is not used anymore. If your database has development devices that still used
+ * the numeric ID, you need to update assets/migrations/devicetype.json before installing Gadgetbridge
+ * after rebasing, in order for your device to be migrated correctly. If you failed to do this and the
+ * device is now not being displayed, please update the file and uncomment the call to migrateDeviceTypes
+ * in GBApplication.
+ */
+public enum DeviceType {
+    UNKNOWN(UnknownDeviceCoordinator.class),
+    PEBBLE(PebbleCoordinator.class),
+    MIBAND(MiBandCoordinator.class),
+    MIBAND2(MiBand2Coordinator.class),
+    MIBAND2_HRX(MiBand2HRXCoordinator.class),
+    AMAZFITBIP(AmazfitBipCoordinator.class),
+    AMAZFITCOR(AmazfitCorCoordinator.class),
+    MIBAND3(MiBand3Coordinator.class),
+    AMAZFITCOR2(AmazfitCor2Coordinator.class),
+    MIBAND4(MiBand4Coordinator.class),
+    AMAZFITBIP_LITE(AmazfitBipLiteCoordinator.class),
+    AMAZFITGTR(AmazfitGTRCoordinator.class),
+    AMAZFITGTS(AmazfitGTSCoordinator.class),
+    AMAZFITBIPS(AmazfitBipSCoordinator.class),
+    AMAZFITGTR_LITE(AmazfitGTRLiteCoordinator.class),
+    AMAZFITTREX(AmazfitTRexCoordinator.class),
+    MIBAND5(MiBand5Coordinator.class),
+    AMAZFITBAND5(AmazfitBand5Coordinator.class),
+    AMAZFITBIPS_LITE(AmazfitBipSLiteCoordinator.class),
+    AMAZFITGTR2(AmazfitGTR2Coordinator.class),
+    AMAZFITGTS2(AmazfitGTS2Coordinator.class),
+    AMAZFITBIPU(AmazfitBipUCoordinator.class),
+    AMAZFITVERGEL(AmazfitVergeLCoordinator.class),
+    AMAZFITBIPUPRO(AmazfitBipUProCoordinator.class),
+    AMAZFITNEO(AmazfitNeoCoordinator.class),
+    AMAZFITGTS2_MINI(AmazfitGTS2MiniCoordinator.class),
+    ZEPP_E(ZeppECoordinator.class),
+    AMAZFITGTR2E(AmazfitGTR2eCoordinator.class),
+    AMAZFITGTS2E(AmazfitGTS2eCoordinator.class),
+    AMAZFITX(AmazfitXCoordinator.class),
+    MIBAND6(MiBand6Coordinator.class),
+    AMAZFITTREXPRO(AmazfitTRexProCoordinator.class),
+    AMAZFITPOP(AmazfitPopCoordinator.class),
+    AMAZFITPOPPRO(AmazfitPopProCoordinator.class),
+    MIBAND7(MiBand7Coordinator.class),
+    MIBAND7PRO(MiBand7ProCoordinator.class),
+    MIBAND8(MiBand8Coordinator.class),
+    MIBAND8ACTIVE(MiBand8ActiveCoordinator.class),
+    MIBAND8PRO(MiBand8ProCoordinator.class),
+    MIBAND9(MiBand9Coordinator.class),
+    MIBAND9PRO(MiBand9ProCoordinator.class),
+    MIWATCHLITE(MiWatchLiteCoordinator.class),
+    MIWATCHCOLORSPORT(MiWatchColorSportCoordinator.class),
+    REDMIBUDS5PRO(RedmiBuds5ProCoordinator.class),
+    REDMIWATCH3ACTIVE(RedmiWatch3ActiveCoordinator.class),
+    REDMIWATCH3(RedmiWatch3Coordinator.class),
+    REDMISMARTBAND2(RedmiSmartBand2Coordinator.class),
+    REDMIWATCH2(RedmiWatch2Coordinator.class),
+    REDMIWATCH2LITE(RedmiWatch2LiteCoordinator.class),
+    REDMISMARTBANDPRO(RedmiSmartBandProCoordinator.class),
+    REDMIWATCH4(RedmiWatch4Coordinator.class),
+    REDMIWATCH5ACTIVE(RedmiWatch5ActiveCoordinator.class),
+    REDMIWATCH5LITE(RedmiWatch5LiteCoordinator.class),
+    XIAOMI_WATCH_S1_ACTIVE(XiaomiWatchS1ActiveCoordinator.class),
+    XIAOMI_WATCH_S1_PRO(XiaomiWatchS1ProCoordinator.class),
+    XIAOMI_WATCH_S1(XiaomiWatchS1Coordinator.class),
+    XIAOMI_WATCH_S3(XiaomiWatchS3Coordinator.class),
+    AMAZFITGTS3(AmazfitGTS3Coordinator.class),
+    AMAZFITGTR3(AmazfitGTR3Coordinator.class),
+    AMAZFITGTR4(AmazfitGTR4Coordinator.class),
+    AMAZFITBAND7(AmazfitBand7Coordinator.class),
+    AMAZFITGTS4(AmazfitGTS4Coordinator.class),
+    AMAZFITGTS4MINI(AmazfitGTS4MiniCoordinator.class),
+    AMAZFITTREX2(AmazfitTRex2Coordinator.class),
+    AMAZFITTREX3(AmazfitTRex3Coordinator.class),
+    AMAZFITGTR3PRO(AmazfitGTR3ProCoordinator.class),
+    AMAZFITBIP3(AmazfitBip3Coordinator.class),
+    AMAZFITBIP3PRO(AmazfitBip3ProCoordinator.class),
+    AMAZFITCHEETAHPRO(AmazfitCheetahProCoordinator.class),
+    AMAZFITCHEETAHSQUARE(AmazfitCheetahSquareCoordinator.class),
+    AMAZFITCHEETAHROUND(AmazfitCheetahRoundCoordinator.class),
+    AMAZFITBIP5(AmazfitBip5Coordinator.class),
+    AMAZFITBIP5UNITY(AmazfitBip5UnityCoordinator.class),
+    AMAZFITTREXULTRA(AmazfitTRexUltraCoordinator.class),
+    AMAZFITGTRMINI(AmazfitGTRMiniCoordinator.class),
+    AMAZFITFALCON(AmazfitFalconCoordinator.class),
+    AMAZFITBALANCE(AmazfitBalanceCoordinator.class),
+    AMAZFITACTIVE(AmazfitActiveCoordinator.class),
+    AMAZFITACTIVEEDGE(AmazfitActiveEdgeCoordinator.class),
+    HPLUS(HPlusCoordinator.class),
+    MAKIBESF68(MakibesF68Coordinator.class),
+    EXRIZUK8(EXRIZUK8Coordinator.class),
+    Q8(Q8Coordinator.class),
+    SG2(SG2Coordinator.class),
+    NO1F1(No1F1Coordinator.class),
+    TECLASTH30(TeclastH30Coordinator.class),
+    Y5(Y5Coordinator.class),
+    XWATCH(XWatchCoordinator.class),
+    ZETIME(ZeTimeCoordinator.class),
+    ID115(ID115Coordinator.class),
+    WATCH9(Watch9DeviceCoordinator.class),
+    WATCHXPLUS(WatchXPlusDeviceCoordinator.class),
+    ROIDMI(Roidmi1Coordinator.class),
+    ROIDMI3(Roidmi3Coordinator.class),
+    CASIOECBS100(CasioECBS100DeviceCoordinator.class),
+
+    CASIOGB6900(CasioGB6900DeviceCoordinator.class),
+    CASIOGBX100(CasioGBX100DeviceCoordinator.class),
+    CASIOGWB5600(CasioGWB5600DeviceCoordinator.class),
+    CASIOGMWB5000(CasioGMWB5000DeviceCoordinator.class),
+    MISMARTSCALE(MiSmartScaleCoordinator.class),
+    MICOMPOSITIONSCALE(MiCompositionScaleCoordinator.class),
+    BFH16(BFH16DeviceCoordinator.class),
+    MAKIBESHR3(MakibesHR3Coordinator.class),
+    BANGLEJS(BangleJSCoordinator.class),
+    FOSSILQHYBRID(QHybridCoordinator.class),
+    TLW64(TLW64Coordinator.class),
+    PINETIME_JF(PineTimeJFCoordinator.class),
+    MIJIA_LYWSD02(MijiaLywsd02Coordinator.class),
+    MIJIA_LYWSD03(MijiaLywsd03Coordinator.class),
+    MIJIA_XMWSDJ04(MijiaXmwsdj04Coordinator.class),
+    MIJIA_MHO_C303(MijiaMhoC303Coordinator.class),
+    LEFUN(LefunDeviceCoordinator.class),
+    VIVITAR_HR_BP_MONITOR_ACTIVITY_TRACKER(VivitarHrBpMonitorActivityTrackerCoordinator.class),
+    BOHEMIC_SMART_BRACELET(BohemicSmartBraceletDeviceCoordinator.class),
+    SMAQ2OSS(SMAQ2OSSCoordinator.class),
+    FITPRO(FitProDeviceCoordinator.class),
+    COLACAO21(ColaCao21Coordinator.class),
+    COLACAO23(ColaCao23Coordinator.class),
+    ITAG(ITagCoordinator.class),
+    IKEA_IDASEN(IdasenCoordinator.class),
+    NUTMINI(NutCoordinator.class),
+    VIVOMOVE_HR(GarminVivomoveHrCoordinator.class),
+    GARMIN_ENDURO_3(GarminEnduro3Coordinator.class),
+    GARMIN_EPIX_PRO(GarminEpixProCoordinator.class),
+    GARMIN_FENIX_5(GarminFenix5Coordinator.class),
+    GARMIN_FENIX_5_PLUS(GarminFenix5PlusCoordinator.class),
+    GARMIN_FENIX_5X_PLUS(GarminFenix5XPlusCoordinator.class),
+    GARMIN_FENIX_6(GarminFenix6Coordinator.class),
+    GARMIN_FENIX_6_SAPPHIRE(GarminFenix6SapphireCoordinator.class),
+    GARMIN_FENIX_6S_PRO(GarminFenix6SProCoordinator.class),
+    GARMIN_FENIX_6S_SAPPHIRE(GarminFenix6SSapphireCoordinator.class),
+    GARMIN_FENIX_7(GarminFenix7Coordinator.class),
+    GARMIN_FENIX_7S(GarminFenix7SCoordinator.class),
+    GARMIN_FENIX_7X(GarminFenix7XCoordinator.class),
+    GARMIN_FENIX_7_PRO(GarminFenix7ProCoordinator.class),
+    GARMIN_FENIX_8(GarminFenix8Coordinator.class),
+    GARMIN_FORERUNNER_55(GarminForerunner55Coordinator.class),
+    GARMIN_FORERUNNER_165(GarminForerunner165Coordinator.class),
+    GARMIN_FORERUNNER_235(GarminForerunner235Coordinator.class),
+    GARMIN_FORERUNNER_245(GarminForerunner245Coordinator.class),
+    GARMIN_FORERUNNER_245_MUSIC(GarminForerunner245MusicCoordinator.class),
+    GARMIN_FORERUNNER_255(GarminForerunner255Coordinator.class),
+    GARMIN_FORERUNNER_255_MUSIC(GarminForerunner255MusicCoordinator.class),
+    GARMIN_FORERUNNER_255S(GarminForerunner255SCoordinator.class),
+    GARMIN_FORERUNNER_255S_MUSIC(GarminForerunner255SMusicCoordinator.class),
+    GARMIN_FORERUNNER_265(GarminForerunner265Coordinator.class),
+    GARMIN_FORERUNNER_265S(GarminForerunner265SCoordinator.class),
+    GARMIN_FORERUNNER_620(GarminForerunner620Coordinator.class),
+    GARMIN_FORERUNNER_955(GarminForerunner955Coordinator.class),
+    GARMIN_FORERUNNER_965(GarminForerunner965Coordinator.class),
+    GARMIN_SWIM_2(GarminSwim2Coordinator.class),
+    GARMIN_INSTINCT(GarminInstinctCoordinator.class),
+    GARMIN_INSTINCT_SOLAR(GarminInstinctSolarCoordinator.class),
+    GARMIN_INSTINCT_2(GarminInstinct2Coordinator.class),
+    GARMIN_INSTINCT_2S(GarminInstinct2SCoordinator.class),
+    GARMIN_INSTINCT_2S_SOLAR(GarminInstinct2SSolarCoordinator.class),
+    GARMIN_INSTINCT_2X_SOLAR(GarminInstinct2XSolarCoordinator.class),
+    GARMIN_INSTINCT_2_SOLAR(GarminInstinct2SolarCoordinator.class),
+    GARMIN_INSTINCT_2_SOLTAC(GarminInstinct2SolTacCoordinator.class),
+    GARMIN_INSTINCT_CROSSOVER(GarminInstinctCrossoverCoordinator.class),
+    GARMIN_VIVOMOVE_STYLE(GarminVivomoveStyleCoordinator.class),
+    GARMIN_VIVOMOVE_TREND(GarminVivomoveTrendCoordinator.class),
+    GARMIN_VENU(GarminVenuCoordinator.class),
+    GARMIN_VENU_SQ(GarminVenuSqCoordinator.class),
+    GARMIN_VENU_SQ_2(GarminVenuSq2Coordinator.class),
+    GARMIN_VENU_2(GarminVenu2Coordinator.class),
+    GARMIN_VENU_2S(GarminVenu2SCoordinator.class),
+    GARMIN_VENU_2_PLUS(GarminVenu2PlusCoordinator.class),
+    GARMIN_VENU_3(GarminVenu3Coordinator.class),
+    GARMIN_VENU_3S(GarminVenu3SCoordinator.class),
+    GARMIN_VIVOACTIVE_3(GarminVivoActive3Coordinator.class),
+    GARMIN_VIVOACTIVE_4(GarminVivoActive4Coordinator.class),
+    GARMIN_VIVOACTIVE_4S(GarminVivoActive4SCoordinator.class),
+    GARMIN_VIVOACTIVE_5(GarminVivoActive5Coordinator.class),
+    GARMIN_VIVOSMART_5(GarminVivosmart5Coordinator.class),
+    GARMIN_VIVOSPORT(GarminVivosportCoordinator.class),
+    VIBRATISSIMO(VibratissimoCoordinator.class),
+    SONY_SWR12(SonySWR12DeviceCoordinator.class),
+    LIVEVIEW(LiveviewCoordinator.class),
+    WASPOS(WaspOSCoordinator.class),
+    UM25(UM25Coordinator.class),
+    DOMYOS_T540(DomyosT540Coordinator.class),
+    NOTHING_EAR1(Ear1Coordinator.class),
+    NOTHING_EAR2(Ear2Coordinator.class),
+    NOTHING_EAR_STICK(EarStickCoordinator.class),
+    NOTHING_CMF_BUDS_PRO_2(CmfBudsPro2Coordinator.class),
+    NOTHING_CMF_WATCH_PRO(CmfWatchProCoordinator.class),
+    NOTHING_CMF_WATCH_PRO_2(CmfWatchPro2Coordinator.class),
+    GALAXY_BUDS_PRO(GalaxyBudsProDeviceCoordinator.class),
+    GALAXY_BUDS_LIVE(GalaxyBudsLiveDeviceCoordinator.class),
+    GALAXY_BUDS(GalaxyBudsDeviceCoordinator.class),
+    GALAXY_BUDS2(GalaxyBuds2DeviceCoordinator.class),
+    GALAXY_BUDS2_PRO(GalaxyBuds2ProDeviceCoordinator.class),
+    SONY_WH_1000XM3(SonyWH1000XM3Coordinator.class),
+    SONY_WF_SP800N(SonyWFSP800NCoordinator.class),
+    SONY_WI_C100(SonyWIC100Coordinator.class),
+    SONY_WI_SP600N(SonyWISP600NCoordinator.class),
+    SONY_WH_1000XM4(SonyWH1000XM4Coordinator.class),
+    SONY_WF_1000XM3(SonyWF1000XM3Coordinator.class),
+    SONY_WH_1000XM2(SonyWH1000XM2Coordinator.class),
+    SONY_WF_1000XM4(SonyWF1000XM4Coordinator.class),
+    SONY_LINKBUDS(SonyLinkBudsCoordinator.class),
+    SONY_LINKBUDS_S(SonyLinkBudsSCoordinator.class),
+    SONY_WH_1000XM5(SonyWH1000XM5Coordinator.class),
+    SONY_WF_1000XM5(SonyWF1000XM5Coordinator.class),
+    SONY_WF_C500(SonyWFC500Coordinator.class),
+    SONY_WF_C700N(SonyWFC700NCoordinator.class),
+    SOUNDCORE_LIBERTY3_PRO(SoundcoreLiberty3ProCoordinator.class),
+    SOUNDCORE_LIBERTY4_NC(SoundcoreLiberty4NCCoordinator.class),
+    SOUNDCORE_MOTION300(SoundcoreMotion300Coordinator.class),
+    MOONDROP_SPACE_TRAVEL(MoondropSpaceTravelCoordinator.class),
+    BOSE_QC35(QC35Coordinator.class),
+    HONORBAND3(HonorBand3Coordinator.class),
+    HONORBAND4(HonorBand4Coordinator.class),
+    HONORBAND5(HonorBand5Coordinator.class),
+    HUAWEIBANDAW70(HuaweiBandAw70Coordinator.class),
+    HUAWEIBAND6(HuaweiBand6Coordinator.class),
+    HUAWEIWATCHGT(HuaweiWatchGTCoordinator.class),
+    HUAWEIBAND2PRO(HuaweiBand2ProCoordinator.class),
+    HUAWEIBAND3PRO(HuaweiBand3ProCoordinator.class),
+    HUAWEIBAND4PRO(HuaweiBand4ProCoordinator.class),
+    HUAWEIWATCHGT2(HuaweiWatchGT2Coordinator.class),
+    HUAWEIWATCHGT2E(HuaweiWatchGT2eCoordinator.class),
+    HUAWEITALKBANDB6(HuaweiTalkBandB6Coordinator.class),
+    HUAWEIBAND7(HuaweiBand7Coordinator.class),
+    HONORBAND6(HonorBand6Coordinator.class),
+    HONORBAND7(HonorBand7Coordinator.class),
+    HONORMAGICWATCH2(HonorMagicWatch2Coordinator.class),
+    HONORWATCHGS3(HonorWatchGS3Coordinator.class),
+    HONORWATCHGSPRO(HonorWatchGSProCoordinator.class),
+    HUAWEIWATCHD2(HuaweiWatchD2Coordinator.class),
+    HUAWEIWATCHGT3(HuaweiWatchGT3Coordinator.class),
+    HUAWEIWATCHGT4(HuaweiWatchGT4Coordinator.class),
+    HUAWEIWATCHGT5(HuaweiWatchGT5Coordinator.class),
+    HUAWEIWATCHGTRUNNER(HuaweiWatchGTRunnerCoordinator.class),
+    HUAWEIWATCHGTCYBER(HuaweiWatchGTCyberCoordinator.class),
+    HUAWEIBAND8(HuaweiBand8Coordinator.class),
+    HUAWEIBAND9(HuaweiBand9Coordinator.class),
+    HUAWEIWATCHFIT(HuaweiWatchFitCoordinator.class),
+    HUAWEIWATCHFIT2(HuaweiWatchFit2Coordinator.class),
+    HUAWEIWATCHFIT3(HuaweiWatchFit3Coordinator.class),
+    HUAWEIWATCHULTIMATE(HuaweiWatchUltimateCoordinator.class),
+    HUAWEIWATCH3(HuaweiWatch3Coordinator.class),
+    HUAWEIWATCH4PRO(HuaweiWatch4ProCoordinator.class),
+    HUAWEI_FREEBUDS5I(HuaweiFreebuds5iCoordinator.class),
+    VESC(VescCoordinator.class),
+    BINARY_SENSOR(BinarySensorCoordinator.class),
+    FLIPPER_ZERO(FlipperZeroCoordinator.class),
+    SUPER_CARS(SuperCarsCoordinator.class),
+    ASTEROIDOS(AsteroidOSDeviceCoordinator.class),
+    OPPO_ENCO_AIR(OppoEncoAirCoordinator.class),
+    OPPO_ENCO_AIR2(OppoEncoAir2Coordinator.class),
+    REALME_BUDS_T110(RealmeBudsT110Coordinator.class),
+    SOFLOW_SO6(SoFlowCoordinator.class),
+    WITHINGS_STEEL_HR(WithingsSteelHRDeviceCoordinator.class),
+    SONY_WENA_3(SonyWena3Coordinator.class),
+    FEMOMETER_VINCA2(FemometerVinca2DeviceCoordinator.class),
+    PIXOO(PixooCoordinator.class),
+    HAMA_FIT6900(HamaFit6900DeviceCoordinator.class),
+    COLMI_R02(ColmiR02Coordinator.class),
+    COLMI_R03(ColmiR03Coordinator.class),
+    COLMI_R06(ColmiR06Coordinator.class),
+    COLMI_R09(ColmiR09Coordinator.class),
+    COLMI_R10(ColmiR10Coordinator.class),
+    B_AND_W_P_SERIES(BandWPSeriesDeviceCoordinator.class),
+    SCANNABLE(ScannableDeviceCoordinator.class),
+    CYCLING_SENSOR(CyclingSensorCoordinator.class),
+    BLE_GATT_CLIENT(BleGattClientCoordinator.class),
+    MARSTEK_B2500(MarstekB2500DeviceCoordinator.class),
+    TEST(TestDeviceCoordinator.class);
+
+    private DeviceCoordinator coordinator;
+
+    private Class<? extends DeviceCoordinator> coordinatorClass;
+
+    DeviceType(Class<? extends DeviceCoordinator> coordinatorClass) {
+        this.coordinatorClass = coordinatorClass;
+    }
+
+    public boolean isSupported() {
+        return this != UNKNOWN;
+    }
+
+    public static DeviceType fromName(String name) {
+        for (DeviceType type : values()) {
+            if (type.name().equals(name)) {
+                return type;
+            }
+        }
+        return DeviceType.UNKNOWN;
+    }
+
+    public DeviceCoordinator getDeviceCoordinator() {
+        if(coordinator == null){
+            try {
+                coordinator = coordinatorClass.newInstance();
+            } catch (ReflectiveOperationException e) {
+                throw new RuntimeException(e);
+            }
+        }
+        return coordinator;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/GPSCoordinate.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/GPSCoordinate.java
new file mode 100644
index 0000000..eb75520
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/GPSCoordinate.java
@@ -0,0 +1,143 @@
+/*  Copyright (C) 2017-2024 Carsten Pfeiffer, Jos茅 Rebelo, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.model;
+
+import android.location.Location;
+
+import androidx.annotation.NonNull;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.Comparator;
+
+public class GPSCoordinate {
+    private final double latitude;
+    private final double longitude;
+    private final double altitude;
+
+    public static final double UNKNOWN_ALTITUDE = -20000d;
+
+    public static final int GPS_DECIMAL_DEGREES_SCALE = 6; // precise to 111.132mm at equator: https://en.wikipedia.org/wiki/Decimal_degrees
+
+    public GPSCoordinate(double longitude, double latitude, double altitude) {
+        this.longitude = longitude;
+        this.latitude = latitude;
+        this.altitude = altitude;
+    }
+
+    public GPSCoordinate(double longitude, double latitude) {
+        this(longitude, latitude, UNKNOWN_ALTITUDE);
+    }
+
+    public double getLatitude() {
+        return latitude;
+    }
+
+    public double getLongitude() {
+        return longitude;
+    }
+
+    public double getAltitude() {
+        return altitude;
+    }
+
+    public double getDistance(GPSCoordinate source) {
+        final Location end = new Location("end");
+        end.setLatitude(this.getLatitude());
+        end.setLongitude(this.getLongitude());
+
+        final Location start = new Location("start");
+        start.setLatitude(source.getLatitude());
+        start.setLongitude(source.getLongitude());
+
+        return end.distanceTo(start);
+    }
+
+    public double getAltitudeDifference(GPSCoordinate source) {
+        if (this.getAltitude() == UNKNOWN_ALTITUDE)
+            return 0;
+        if (source.getAltitude() == UNKNOWN_ALTITUDE)
+            return 0;
+        return this.getAltitude() - source.getAltitude();
+    }
+
+    public double getAscent(GPSCoordinate source) {
+        return Math.max(0, this.getAltitudeDifference(source));
+    }
+
+    public double getDescent(GPSCoordinate source) {
+        return Math.max(0, -this.getAltitudeDifference(source));
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        GPSCoordinate that = (GPSCoordinate) o;
+
+        if (Double.compare(that.getLatitude(), getLatitude()) != 0) return false;
+        if (Double.compare(that.getLongitude(), getLongitude()) != 0) return false;
+        return Double.compare(that.getAltitude(), getAltitude()) == 0;
+
+    }
+
+    @Override
+    public int hashCode() {
+        int result;
+        long temp;
+        temp = Double.doubleToLongBits(getLatitude());
+        result = (int) (temp ^ (temp >>> 32));
+        temp = Double.doubleToLongBits(getLongitude());
+        result = 31 * result + (int) (temp ^ (temp >>> 32));
+        temp = Double.doubleToLongBits(getAltitude());
+        result = 31 * result + (int) (temp ^ (temp >>> 32));
+        return result;
+    }
+
+    private String formatLocation(double value) {
+        return new BigDecimal(value).setScale(8, RoundingMode.HALF_UP).toPlainString();
+    }
+
+    @NonNull
+    @Override
+    public String toString() {
+        return "lon: " + formatLocation(longitude) + ", lat: " + formatLocation(latitude) + ", alt: " + formatLocation(altitude) + "m";
+    }
+
+    public static class compareLatitude implements Comparator<GPSCoordinate> {
+        @Override
+        public int compare(GPSCoordinate trkPt1, GPSCoordinate trkPt2) {
+            return Double.compare(trkPt1.getLatitude(), trkPt2.getLatitude());
+        }
+    }
+
+    public static class compareLongitude implements Comparator<GPSCoordinate> {
+        @Override
+        public int compare(GPSCoordinate trkPt1, GPSCoordinate trkPt2) {
+            return Double.compare(trkPt1.getLongitude(), trkPt2.getLongitude());
+        }
+    }
+
+    public static class compareElevation implements Comparator<GPSCoordinate> {
+        @Override
+        public int compare(GPSCoordinate trkPt1, GPSCoordinate trkPt2) {
+            return Double.compare(trkPt1.getAltitude(), trkPt2.getAltitude());
+        }
+    }
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/GenericItem.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/GenericItem.java
new file mode 100644
index 0000000..94fbd0b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/GenericItem.java
@@ -0,0 +1,153 @@
+/*  Copyright (C) 2015-2024 Arjan Schrijver, Carsten Pfeiffer, Taavi Eom盲e
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.model;
+
+import android.graphics.Bitmap;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.text.Collator;
+import java.util.Objects;
+
+public class GenericItem implements ItemWithDetails {
+    private String name;
+    private String details;
+    private int icon;
+    private boolean warning = false;
+    private Bitmap preview;
+
+    public static final Parcelable.Creator CREATOR = new Parcelable.Creator<GenericItem>() {
+        @Override
+        public GenericItem createFromParcel(Parcel source) {
+            GenericItem item = new GenericItem();
+            item.setName(source.readString());
+            item.setDetails(source.readString());
+            item.setIcon(source.readInt());
+            return item;
+        }
+
+        @Override
+        public GenericItem[] newArray(int size) {
+            return new GenericItem[size];
+        }
+    };
+
+    public GenericItem(String name, String details) {
+        this.name = name;
+        this.details = details;
+    }
+
+    public GenericItem(String name) {
+        this.name = name;
+    }
+
+    public GenericItem() {
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(name);
+        dest.writeString(details);
+        dest.writeInt(icon);
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public void setDetails(String details) {
+        if (details == null || details.equals("(Unknown version)")) {
+            this.warning = true;
+        }
+        this.details = details;
+    }
+
+    public void setIcon(int icon) {
+        this.icon = icon;
+    }
+
+    public boolean getWarning() {
+        return this.warning;
+    }
+
+    public void setWarning(boolean enable) {
+        this.warning = enable;
+    }
+
+    @Override
+    public String getName() {
+        return name;
+    }
+
+    @Override
+    public String getDetails() {
+        return details;
+    }
+
+    @Override
+    public int getIcon() {
+        return icon;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public void setPreview(Bitmap preview) {
+        this.preview = preview;
+    }
+
+    public Bitmap getPreview() {
+        return preview;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        if (this == o) {
+            return true;
+        }
+
+        GenericItem that = (GenericItem) o;
+
+        return Objects.equals(name, that.name);
+    }
+
+    @Override
+    public int hashCode() {
+        return name != null ? name.hashCode() : 0;
+    }
+
+    @Override
+    public int compareTo(ItemWithDetails another) {
+        if (name.equals(another.getName())) {
+            return 0;
+        }
+
+        if (name == null) {
+            return +1;
+        } else if (another.getName() == null) {
+            return -1;
+        }
+
+        return Collator.getInstance().compare(name, another.getName());
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/HeartRateSample.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/HeartRateSample.java
new file mode 100644
index 0000000..d446c9b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/HeartRateSample.java
@@ -0,0 +1,24 @@
+/*  Copyright (C) 2016-2024 Andreas Shimokawa, Carsten Pfeiffer, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.model;
+
+public interface HeartRateSample extends TimeSample {
+    /**
+     * Returns the heart rate value.
+     */
+    int getHeartRate();
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/HrvSummarySample.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/HrvSummarySample.java
new file mode 100644
index 0000000..82a9d17
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/HrvSummarySample.java
@@ -0,0 +1,80 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.model;
+
+public interface HrvSummarySample extends TimeSample {
+    enum Status {
+        NONE(0),
+        POOR(1),
+        LOW(2),
+        UNBALANCED(3),
+        BALANCED(4),
+        ;
+
+        private final int num;
+
+        Status(final int num) {
+            this.num = num;
+        }
+
+        public int getNum() {
+            return num;
+        }
+
+        public static Status fromNum(final int num) {
+            for (Status value : Status.values()) {
+                if (value.getNum() == num) {
+                    return value;
+                }
+            }
+
+            throw new IllegalArgumentException("Unknown hrv status num " + num);
+        }
+    }
+
+    /**
+     * Weekly average, in milliseconds.
+     */
+    Integer getWeeklyAverage();
+
+    /**
+     * Last night average, in milliseconds.
+     */
+    Integer getLastNightAverage();
+
+    /**
+     * Last night 5-min high, in milliseconds.
+     */
+    Integer getLastNight5MinHigh();
+
+    /**
+     * Baseline low upper, in milliseconds.
+     */
+    Integer getBaselineLowUpper();
+
+    /**
+     * Baseline balanced lower, in milliseconds.
+     */
+    Integer getBaselineBalancedLower();
+
+    /**
+     * Baseline balanced upper, in milliseconds.
+     */
+    Integer getBaselineBalancedUpper();
+
+    Status getStatus();
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/HrvValueSample.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/HrvValueSample.java
new file mode 100644
index 0000000..153c70b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/HrvValueSample.java
@@ -0,0 +1,24 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.model;
+
+public interface HrvValueSample extends TimeSample {
+    /**
+     * HRV value, in milliseconds.
+     */
+    int getValue();
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ItemWithDetails.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ItemWithDetails.java
new file mode 100644
index 0000000..60a9ccb
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ItemWithDetails.java
@@ -0,0 +1,38 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, Arjan Schrijver, Carsten
+    Pfeiffer
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.model;
+
+import android.graphics.Bitmap;
+import android.os.Parcelable;
+
+public interface ItemWithDetails extends Parcelable, Comparable<ItemWithDetails> {
+    String getName();
+
+    String getDetails();
+
+    int getIcon();
+
+    Bitmap getPreview();
+
+    /**
+     * Equality is based on #getName() only.
+     *
+     * @param other
+     */
+    boolean equals(Object other);
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/Measurement.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/Measurement.java
new file mode 100644
index 0000000..2d2de2a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/Measurement.java
@@ -0,0 +1,49 @@
+/*  Copyright (C) 2016-2024 Carsten Pfeiffer
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.model;
+
+public class Measurement {
+    private final int value;
+    private final long timestamp;
+
+    public Measurement(int value, long timestamp) {
+        this.value = value;
+        this.timestamp = timestamp;
+    }
+
+    public int getValue() {
+        return value;
+    }
+
+    public long getTimestamp() {
+        return timestamp;
+    }
+
+    @Override
+    public int hashCode() {
+        return (int) (71 ^ value ^ timestamp);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o instanceof Measurement) {
+            Measurement m = (Measurement) o;
+            return timestamp == m.timestamp && value == m.value;
+        }
+        return super.equals(o);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/MusicSpec.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/MusicSpec.java
new file mode 100644
index 0000000..215f9b7
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/MusicSpec.java
@@ -0,0 +1,92 @@
+/*  Copyright (C) 2016-2024 Andreas Shimokawa, Carsten Pfeiffer, Daniele
+    Gobbetti, Taavi Eom盲e
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.model;
+
+import java.util.Objects;
+
+public class MusicSpec {
+    public static final int MUSIC_UNKNOWN = -1;
+
+    public static final int MUSIC_UNDEFINED = 0;
+    public static final int MUSIC_PLAY = 1;
+    public static final int MUSIC_PAUSE = 2;
+    public static final int MUSIC_PLAYPAUSE = 3;
+    public static final int MUSIC_NEXT = 4;
+    public static final int MUSIC_PREVIOUS = 5;
+
+    public String artist = null;
+    public String album = null;
+    public String track = null;
+    public int duration = MUSIC_UNKNOWN;
+    public int trackCount = MUSIC_UNKNOWN;
+    public int trackNr = MUSIC_UNKNOWN;
+
+    public MusicSpec() {
+
+    }
+
+    public MusicSpec(MusicSpec old) {
+        this.duration = old.duration;
+        this.trackCount = old.trackCount;
+        this.trackNr = old.trackNr;
+        this.track = old.track;
+        this.album = old.album;
+        this.artist = old.artist;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == this) {
+            return true;
+        }
+        if (!(obj instanceof MusicSpec)) {
+            return false;
+        }
+        MusicSpec musicSpec = (MusicSpec) obj;
+
+        return Objects.equals(this.artist, musicSpec.artist) &&
+                Objects.equals(this.album, musicSpec.album) &&
+                Objects.equals(this.track, musicSpec.track) &&
+                this.duration == musicSpec.duration &&
+                this.trackCount == musicSpec.trackCount &&
+                this.trackNr == musicSpec.trackNr;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = artist != null ? artist.hashCode() : 0;
+        result = 31 * result + (album != null ? album.hashCode() : 0);
+        result = 31 * result + (track != null ? track.hashCode() : 0);
+        result = 31 * result + duration;
+        result = 31 * result + trackCount;
+        result = 31 * result + trackNr;
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        return "MusicSpec{" +
+                "artist='" + artist + '\'' +
+                ", album='" + album + '\'' +
+                ", track='" + track + '\'' +
+                ", duration=" + duration +
+                ", trackCount=" + trackCount +
+                ", trackNr=" + trackNr +
+                '}';
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/MusicStateSpec.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/MusicStateSpec.java
new file mode 100644
index 0000000..9af1747
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/MusicStateSpec.java
@@ -0,0 +1,91 @@
+/*  Copyright (C) 2016-2024 Andreas Shimokawa, Avamander, Carsten Pfeiffer,
+    Daniele Gobbetti, Steffen Liebergeld, Taavi Eom盲e
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.model;
+
+public class MusicStateSpec {
+    public static final int STATE_UNKNOWN = -1;
+
+    public static final int STATE_PLAYING = 0;
+    public static final int STATE_PAUSED = 1;
+    public static final int STATE_STOPPED = 2;
+
+    public static final int STATE_SHUFFLE_ENABLED = 1;
+
+    public byte state = STATE_UNKNOWN;
+    /**
+     * Position of the current media in seconds
+     */
+    public int position = STATE_UNKNOWN;
+    /**
+     * Speed of playback, usually 0 or 100 (full speed)
+     */
+    public int playRate = STATE_UNKNOWN;
+    public byte shuffle = STATE_UNKNOWN;
+    public byte repeat = STATE_UNKNOWN;
+
+    public MusicStateSpec() {
+
+    }
+
+    public MusicStateSpec(MusicStateSpec old) {
+        this.state = old.state;
+        this.position = old.position;
+        this.playRate = old.playRate;
+        this.shuffle = old.shuffle;
+        this.repeat = old.repeat;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == this) {
+            return true;
+        }
+        if (!(obj instanceof MusicStateSpec)) {
+            return false;
+        }
+        MusicStateSpec stateSpec = (MusicStateSpec) obj;
+
+        return this.state == stateSpec.state &&
+                Math.abs(this.position - stateSpec.position)<=2 &&
+                this.playRate == stateSpec.playRate &&
+                this.shuffle == stateSpec.shuffle &&
+                this.repeat == stateSpec.repeat;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = (int) state;
+//ignore the position -- it is taken into account in equals()
+//result = 31 * result + position;
+        result = 31 * result + playRate;
+        result = 31 * result + (int) shuffle;
+        result = 31 * result + (int) repeat;
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        return "MusicStateSpec{" +
+                "state=" + state +
+                ", position=" + position +
+                ", playRate=" + playRate +
+                ", shuffle=" + shuffle +
+                ", repeat=" + repeat +
+                '}';
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/NavigationInfoSpec.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/NavigationInfoSpec.java
new file mode 100644
index 0000000..7d45506
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/NavigationInfoSpec.java
@@ -0,0 +1,44 @@
+/*  Copyright (C) 2021-2024 Andreas Shimokawa, Arjan Schrijver, Gordon Williams
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.model;
+
+public class NavigationInfoSpec {
+    public static final int ACTION_CONTINUE = 1;
+    public static final int ACTION_TURN_LEFT = 2;
+    public static final int ACTION_TURN_LEFT_SLIGHTLY = 3;
+    public static final int ACTION_TURN_LEFT_SHARPLY = 4;
+    public static final int ACTION_TURN_RIGHT = 5;
+    public static final int ACTION_TURN_RIGHT_SLIGHTLY = 6;
+    public static final int ACTION_TURN_RIGHT_SHARPLY = 7;
+    public static final int ACTION_KEEP_LEFT = 8;
+    public static final int ACTION_KEEP_RIGHT = 9;
+    public static final int ACTION_UTURN_LEFT = 10;
+    public static final int ACTION_UTURN_RIGHT = 11;
+    public static final int ACTION_OFFROUTE = 12;
+    public static final int ACTION_ROUNDABOUT_RIGHT = 13;
+    public static final int ACTION_ROUNDABOUT_LEFT = 14;
+    public static final int ACTION_ROUNDABOUT_STRAIGHT = 15;
+    public static final int ACTION_ROUNDABOUT_UTURN = 16;
+    public static final int ACTION_FINISH = 17;
+    public static final int ACTION_MERGE = 18;
+
+    // ETA? Total Distance?
+    public String instruction;
+    public String distanceToTurn; ///< Distance to turn (as a string, eg "100m")
+    public int nextAction; ///< One of the ACTION_ constants
+    public String ETA; ///< Estimated time of Arrival
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/NotificationSpec.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/NotificationSpec.java
new file mode 100644
index 0000000..778d4c7
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/NotificationSpec.java
@@ -0,0 +1,89 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, Arjan Schrijver, Daniele
+    Gobbetti, Frank Slezak, Jos茅 Rebelo, mvn23, Petr Kadlec
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.model;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class NotificationSpec {
+    public int flags;
+    private static final AtomicInteger c = new AtomicInteger((int) (System.currentTimeMillis()/1000));
+    private int id;
+    public String key;
+    public long when;
+    public String sender;
+    public String phoneNumber;
+    public String title;
+    public String subject;
+    public String body;
+    public NotificationType type;
+    public String sourceName;
+    public String[] cannedReplies;
+    /**
+     * Wearable actions that were attached to the incoming notifications and will be passed to the gadget (includes the "reply" action)
+     */
+    public ArrayList<Action> attachedActions;
+    /**
+     * The application that generated the notification.
+     */
+    public String sourceAppId;
+    /**
+     * The notification's icon ID
+     */
+    public int iconId;
+
+    public String picturePath;
+    /**
+     * The color that should be assigned to this notification when displayed on a Pebble
+     */
+    public byte pebbleColor;
+
+    public int dndSuppressed;
+
+    public NotificationSpec() {
+        this(-1);
+    }
+
+    public NotificationSpec(int id) {
+        if (id != -1)
+            this.id = id;
+        else
+            this.id = c.incrementAndGet();
+        this.when = System.currentTimeMillis();
+    }
+
+    public int getId() {
+        return id;
+    }
+
+    public static class Action implements Serializable {
+        static final int TYPE_UNDEFINED = -1;
+        public static final int TYPE_WEARABLE_SIMPLE = 0;
+        public static final int TYPE_WEARABLE_REPLY = 1;
+        public static final int TYPE_SYNTECTIC_REPLY_PHONENR = 2;
+        public static final int TYPE_SYNTECTIC_DISMISS = 3;
+        public static final int TYPE_SYNTECTIC_DISMISS_ALL = 4;
+        public static final int TYPE_SYNTECTIC_MUTE = 5;
+        public static final int TYPE_SYNTECTIC_OPEN = 6;
+
+        public int type = TYPE_UNDEFINED;
+        public long handle;
+        public String title;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/NotificationType.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/NotificationType.java
new file mode 100644
index 0000000..3e73b76
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/NotificationType.java
@@ -0,0 +1,172 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, AnthonyDiGirolamo, Carsten
+    Pfeiffer, Daniele Gobbetti, Davis Mosenkovs, Frank Slezak, Jos茅 Rebelo,
+    Kaz Wolfe, Kevin Richter, Lukas Veneziano, Maxim Baz, musover
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.model;
+
+import java.util.Arrays;
+import java.util.Comparator;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.pebble.PebbleColor;
+import nodomain.freeyourgadget.gadgetbridge.devices.pebble.PebbleIconID;
+
+public enum NotificationType {
+
+    // TODO: this this pebbleism needs to be moved somewhere else
+    UNKNOWN(PebbleIconID.NOTIFICATION_GENERIC, PebbleColor.DarkCandyAppleRed),
+
+    AMAZON(PebbleIconID.NOTIFICATION_AMAZON, PebbleColor.ChromeYellow),
+    BBM(PebbleIconID.NOTIFICATION_BLACKBERRY_MESSENGER, PebbleColor.DarkGray),
+    CONVERSATIONS(PebbleIconID.NOTIFICATION_HIPCHAT, PebbleColor.Inchworm),
+    FACEBOOK(PebbleIconID.NOTIFICATION_FACEBOOK, PebbleColor.CobaltBlue),
+    FACEBOOK_MESSENGER(PebbleIconID.NOTIFICATION_FACEBOOK_MESSENGER, PebbleColor.BlueMoon),
+    GENERIC_ALARM_CLOCK(PebbleIconID.ALARM_CLOCK, PebbleColor.Red),
+    GENERIC_CALENDAR(PebbleIconID.TIMELINE_CALENDAR, PebbleColor.BlueMoon),
+    GENERIC_EMAIL(PebbleIconID.GENERIC_EMAIL, PebbleColor.Orange),
+    GENERIC_NAVIGATION(PebbleIconID.LOCATION, PebbleColor.Orange),
+    GENERIC_PHONE(PebbleIconID.DURING_PHONE_CALL, PebbleColor.JaegerGreen),
+    GENERIC_SMS(PebbleIconID.GENERIC_SMS, PebbleColor.VividViolet),
+    GMAIL(PebbleIconID.NOTIFICATION_GMAIL, PebbleColor.Red),
+    GOOGLE_HANGOUTS(PebbleIconID.NOTIFICATION_GOOGLE_HANGOUTS, PebbleColor.JaegerGreen),
+    GOOGLE_INBOX(PebbleIconID.NOTIFICATION_GOOGLE_INBOX, PebbleColor.BlueMoon),
+    GOOGLE_MAPS(PebbleIconID.NOTIFICATION_GOOGLE_MAPS, PebbleColor.BlueMoon),
+    GOOGLE_MESSENGER(PebbleIconID.NOTIFICATION_GOOGLE_MESSENGER, PebbleColor.VividCerulean),
+    GOOGLE_PHOTOS(PebbleIconID.NOTIFICATION_GOOGLE_PHOTOS, PebbleColor.BlueMoon),
+    HIPCHAT(PebbleIconID.NOTIFICATION_HIPCHAT, PebbleColor.CobaltBlue),
+    INSTAGRAM(PebbleIconID.NOTIFICATION_INSTAGRAM, PebbleColor.CobaltBlue),
+    KAKAO_TALK(PebbleIconID.NOTIFICATION_KAKAOTALK, PebbleColor.Yellow),
+    KIK(PebbleIconID.NOTIFICATION_KIK, PebbleColor.IslamicGreen),
+    LIGHTHOUSE(PebbleIconID.NOTIFICATION_LIGHTHOUSE, PebbleColor.PictonBlue), // ??? - No idea what this is, but it works.
+    LINE(PebbleIconID.NOTIFICATION_LINE, PebbleColor.IslamicGreen),
+    LINKEDIN(PebbleIconID.NOTIFICATION_LINKEDIN, PebbleColor.CobaltBlue),
+    MAILBOX(PebbleIconID.NOTIFICATION_MAILBOX, PebbleColor.VividCerulean),
+    OUTLOOK(PebbleIconID.NOTIFICATION_OUTLOOK, PebbleColor.BlueMoon),
+    BUSINESS_CALENDAR(PebbleIconID.TIMELINE_CALENDAR, PebbleColor.BlueMoon),
+    RIOT(PebbleIconID.NOTIFICATION_HIPCHAT, PebbleColor.LavenderIndigo),
+    SIGNAL(PebbleIconID.NOTIFICATION_HIPCHAT, PebbleColor.BlueMoon),
+    WIRE(PebbleIconID.NOTIFICATION_HIPCHAT, PebbleColor.BlueMoon),
+    SKYPE(PebbleIconID.NOTIFICATION_SKYPE, PebbleColor.VividCerulean),
+    SLACK(PebbleIconID.NOTIFICATION_SLACK, PebbleColor.Folly),
+    SNAPCHAT(PebbleIconID.NOTIFICATION_SNAPCHAT, PebbleColor.Icterine),
+    TELEGRAM(PebbleIconID.NOTIFICATION_TELEGRAM, PebbleColor.VividCerulean),
+    THREEMA(PebbleIconID.NOTIFICATION_HIPCHAT, PebbleColor.JaegerGreen),
+    KONTALK(PebbleIconID.NOTIFICATION_HIPCHAT, PebbleColor.JaegerGreen),
+    ANTOX(PebbleIconID.NOTIFICATION_HIPCHAT, PebbleColor.JaegerGreen),
+    DISCORD(PebbleIconID.NOTIFICATION_HIPCHAT, PebbleColor.Purpureus),
+    TRANSIT(PebbleIconID.LOCATION, PebbleColor.JaegerGreen),
+    TWITTER(PebbleIconID.NOTIFICATION_TWITTER, PebbleColor.BlueMoon),
+    VIBER(PebbleIconID.NOTIFICATION_VIBER, PebbleColor.VividViolet),
+    WECHAT(PebbleIconID.NOTIFICATION_WECHAT, PebbleColor.KellyGreen),
+    WHATSAPP(PebbleIconID.NOTIFICATION_WHATSAPP, PebbleColor.IslamicGreen),
+    YAHOO_MAIL(PebbleIconID.NOTIFICATION_YAHOO_MAIL, PebbleColor.Indigo),
+    COL_REMINDER(PebbleIconID.NOTIFICATION_REMINDER, PebbleColor.IslamicGreen),
+    DELTACHAT(PebbleIconID.NOTIFICATION_HIPCHAT, PebbleColor.BlueMoon),
+    ELEMENT(PebbleIconID.NOTIFICATION_HIPCHAT, PebbleColor.Malachite),
+    ELEMENTX(PebbleIconID.NOTIFICATION_HIPCHAT, PebbleColor.Malachite),
+    MOLLY(PebbleIconID.NOTIFICATION_HIPCHAT, PebbleColor.LavenderIndigo);
+
+    // Note: if you add any more constants, update all clients as well
+
+    public final int icon;
+    public final byte color;
+
+    NotificationType(int icon, byte color) {
+        this.icon = icon;
+        this.color = color;
+    }
+
+    /**
+     * Returns the enum constant as a fixed String value, e.g. to be used
+     * as preference key. In case the keys are ever changed, this method
+     * may be used to bring backward compatibility.
+     */
+    public String getFixedValue() {
+        return name().toLowerCase();
+    }
+
+    public String getGenericType() {
+        switch (this) {
+            case GENERIC_EMAIL:
+            case GENERIC_NAVIGATION:
+            case GENERIC_SMS:
+            case GENERIC_PHONE:
+            case GENERIC_CALENDAR:
+            case GENERIC_ALARM_CLOCK:
+                return getFixedValue();
+            case FACEBOOK:
+            case TWITTER:
+            case SNAPCHAT:
+            case INSTAGRAM:
+            case LINKEDIN:
+                return "generic_social";
+            case CONVERSATIONS:
+            case FACEBOOK_MESSENGER:
+            case RIOT:
+            case SIGNAL:
+            case WIRE:
+            case TELEGRAM:
+            case THREEMA:
+            case KONTALK:
+            case ANTOX:
+            case WHATSAPP:
+            case GOOGLE_MESSENGER:
+            case GOOGLE_HANGOUTS:
+            case HIPCHAT:
+            case SKYPE:
+            case WECHAT:
+            case KIK:
+            case KAKAO_TALK:
+            case SLACK:
+            case LINE:
+            case VIBER:
+            case DISCORD:
+            case DELTACHAT:
+            case ELEMENT:
+            case ELEMENTX:
+            case MOLLY:
+                return "generic_chat";
+            case GMAIL:
+            case GOOGLE_INBOX:
+            case MAILBOX:
+            case OUTLOOK:
+            case YAHOO_MAIL:
+                return "generic_email";
+            case COL_REMINDER:
+            case UNKNOWN:
+            default:
+                return "generic";
+        }
+    }
+
+    public static NotificationType[] sortedValues() {
+        final NotificationType[] sorted = NotificationType.values();
+        Arrays.sort(sorted, new Comparator<NotificationType>() {
+            @Override public int compare(final NotificationType n1, final NotificationType n2) {
+                // Keep unknown first
+                if (n1.equals(NotificationType.UNKNOWN)) {
+                    return -1;
+                } else if (n2.equals(NotificationType.UNKNOWN)) {
+                    return 1;
+                }
+
+                return n1.name().compareToIgnoreCase(n2.name());
+            }
+        });
+
+        return sorted;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/PaiSample.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/PaiSample.java
new file mode 100644
index 0000000..afde7fc
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/PaiSample.java
@@ -0,0 +1,35 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.model;
+
+public interface PaiSample extends TimeSample {
+    float getPaiLow();
+
+    float getPaiModerate();
+
+    float getPaiHigh();
+
+    int getTimeLow();
+
+    int getTimeModerate();
+
+    int getTimeHigh();
+
+    float getPaiToday();
+
+    float getPaiTotal();
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/RecordedDataTypes.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/RecordedDataTypes.java
new file mode 100644
index 0000000..560de73
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/RecordedDataTypes.java
@@ -0,0 +1,39 @@
+/*  Copyright (C) 2018-2024 Andreas Shimokawa, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+
+package nodomain.freeyourgadget.gadgetbridge.model;
+
+public class RecordedDataTypes {
+    public static final int TYPE_ACTIVITY     = 0x00000001;
+    public static final int TYPE_WORKOUTS     = 0x00000002;
+    public static final int TYPE_GPS_TRACKS   = 0x00000004;
+    public static final int TYPE_TEMPERATURE  = 0x00000008;
+    public static final int TYPE_DEBUGLOGS    = 0x00000010;
+    public static final int TYPE_SPO2         = 0x00000020;
+    public static final int TYPE_STRESS       = 0x00000040;
+    public static final int TYPE_HEART_RATE   = 0x00000080;
+    public static final int TYPE_PAI          = 0x00000100;
+    public static final int TYPE_SLEEP_RESPIRATORY_RATE = 0x00000200;
+    public static final int TYPE_HUAMI_STATISTICS = 0x00000400;
+
+    public static final int TYPE_ALL          = (int)0xffffffff;
+
+    // Types to fetch during sync - scheduled, sync button, etc.
+    // Does not include debug logs or workouts
+    public static final int TYPE_SYNC = TYPE_ACTIVITY | TYPE_GPS_TRACKS | TYPE_SPO2 | TYPE_STRESS |
+            TYPE_HEART_RATE | TYPE_PAI | TYPE_SLEEP_RESPIRATORY_RATE;
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/Reminder.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/Reminder.java
new file mode 100644
index 0000000..77da461
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/Reminder.java
@@ -0,0 +1,38 @@
+/*  Copyright (C) 2021-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.model;
+
+import java.io.Serializable;
+import java.util.Date;
+
+public interface Reminder extends Serializable {
+    /**
+     * The {@link android.os.Bundle} name for transferring parceled reminders.
+     */
+    String EXTRA_REMINDER = "reminder";
+
+    int ONCE = 0;
+    int EVERY_DAY = 1;
+    int EVERY_WEEK = 2;
+    int EVERY_MONTH = 3;
+    int EVERY_YEAR = 4;
+
+    String getReminderId();
+    String getMessage();
+    Date getDate();
+    int getRepetition();
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/RespiratoryRateSample.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/RespiratoryRateSample.java
new file mode 100644
index 0000000..fa6a975
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/RespiratoryRateSample.java
@@ -0,0 +1,24 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.model;
+
+public interface RespiratoryRateSample extends TimeSample {
+    /**
+     * Returns the respiratory rate value, in breaths per minute.
+     */
+    float getRespiratoryRate();
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/RestingMetabolicRateSample.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/RestingMetabolicRateSample.java
new file mode 100644
index 0000000..312dfb1
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/RestingMetabolicRateSample.java
@@ -0,0 +1,23 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.model;
+
+import nodomain.freeyourgadget.gadgetbridge.entities.AbstractTimeSample;
+
+public abstract class RestingMetabolicRateSample extends AbstractTimeSample {
+    public abstract int getRestingMetabolicRate();
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/SleepScoreSample.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/SleepScoreSample.java
new file mode 100644
index 0000000..752215d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/SleepScoreSample.java
@@ -0,0 +1,37 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.model;
+
+import androidx.annotation.NonNull;
+
+import nodomain.freeyourgadget.gadgetbridge.entities.AbstractTimeSample;
+import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
+
+public abstract class SleepScoreSample extends AbstractTimeSample {
+    public abstract int getSleepScore();
+
+    @NonNull
+    @Override
+    public String toString() {
+        return getClass().getSimpleName() + "{" +
+                "timestamp=" + DateTimeUtils.formatDateTime(DateTimeUtils.parseTimestampMillis(getTimestamp())) +
+                ", userId=" + getUserId() +
+                ", deviceId=" + getDeviceId() +
+                ", sleepScore=" + getSleepScore() +
+                "}";
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/SleepState.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/SleepState.java
new file mode 100644
index 0000000..687a548
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/SleepState.java
@@ -0,0 +1,23 @@
+/*  Copyright (C) 2023-2024 Yoran Vulker
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.model;
+
+public enum SleepState {
+    UNKNOWN,
+    ASLEEP,
+    AWAKE,
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/Spo2Sample.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/Spo2Sample.java
new file mode 100644
index 0000000..67600ba
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/Spo2Sample.java
@@ -0,0 +1,56 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.model;
+
+public interface Spo2Sample extends TimeSample {
+    enum Type {
+        MANUAL(0),
+        AUTOMATIC(1),
+        UNKNOWN(2),
+        ;
+
+        private final int num;
+
+        Type(final int num) {
+            this.num = num;
+        }
+
+        public int getNum() {
+            return num;
+        }
+
+        public static Type fromNum(final int num) {
+            for (Type value : Type.values()) {
+                if (value.getNum() == num) {
+                    return value;
+                }
+            }
+
+            throw new IllegalArgumentException("Unknown num " + num);
+        }
+    }
+
+    /**
+     * Returns the measurement type for this SpO2 value.
+     */
+    Type getType();
+
+    /**
+     * Returns the SpO2 value between 0 and 100%.
+     */
+    int getSpo2();
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/StressSample.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/StressSample.java
new file mode 100644
index 0000000..dcab60e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/StressSample.java
@@ -0,0 +1,56 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.model;
+
+public interface StressSample extends TimeSample {
+    enum Type {
+        MANUAL(0),
+        AUTOMATIC(1),
+        UNKNOWN(2),
+        ;
+
+        private final int num;
+
+        Type(final int num) {
+            this.num = num;
+        }
+
+        public int getNum() {
+            return num;
+        }
+
+        public static Type fromNum(final int num) {
+            for (Type value : Type.values()) {
+                if (value.getNum() == num) {
+                    return value;
+                }
+            }
+
+            throw new IllegalArgumentException("Unknown num " + num);
+        }
+    }
+
+    /**
+     * Returns the measurement type for this stress value.
+     */
+    Type getType();
+
+    /**
+     * Returns the stress value between 0 and 100.
+     */
+    int getStress();
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/SummaryOfDay.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/SummaryOfDay.java
new file mode 100644
index 0000000..feb902c
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/SummaryOfDay.java
@@ -0,0 +1,28 @@
+/*  Copyright (C) 2015-2024 Carsten Pfeiffer
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.model;
+
+public interface SummaryOfDay {
+    byte getProvider();
+
+    int getSteps();
+
+    int getDayStartWakeupTime();
+
+    int getDayEndFallAsleepTime();
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/TemperatureSample.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/TemperatureSample.java
new file mode 100644
index 0000000..ea6e732
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/TemperatureSample.java
@@ -0,0 +1,28 @@
+/*  Copyright (C) 2023-2024 Alicia Hormann
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.model;
+
+public interface TemperatureSample extends TimeSample {
+    /**
+     * Returns the temperature value.
+     */
+    float getTemperature();
+    /**
+     * Returns the temperature type (the position on the body where the measurement was taken).
+     */
+    int getTemperatureType();
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/TimeSample.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/TimeSample.java
new file mode 100644
index 0000000..1514923
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/TimeSample.java
@@ -0,0 +1,24 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.model;
+
+public interface TimeSample {
+    /**
+     * Unix timestamp of the sample, i.e. the number of milliseconds since 1970-01-01 00:00:00 UTC.
+     */
+    long getTimestamp();
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/TimeStamped.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/TimeStamped.java
new file mode 100644
index 0000000..d909a54
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/TimeStamped.java
@@ -0,0 +1,24 @@
+/*  Copyright (C) 2016-2024 Carsten Pfeiffer
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.model;
+
+public interface TimeStamped {
+    /**
+     * Unix timestamp of the sample, i.e. the number of seconds since 1970-01-01 00:00:00 UTC.
+     */
+    int getTimestamp();
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ValidByDate.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ValidByDate.java
new file mode 100644
index 0000000..6785d5e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ValidByDate.java
@@ -0,0 +1,24 @@
+/*  Copyright (C) 2016-2024 Carsten Pfeiffer
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.model;
+
+import java.util.Date;
+
+public interface ValidByDate {
+    Date getValidFromUTC();
+    Date getValidToUTC();
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/Vo2MaxSample.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/Vo2MaxSample.java
new file mode 100644
index 0000000..a2fc499
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/Vo2MaxSample.java
@@ -0,0 +1,32 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.model;
+
+public interface Vo2MaxSample extends TimeSample {
+    enum Type {
+        ANY,
+        RUNNING,
+        CYCLING
+    }
+
+    Type getType();
+
+    /**
+     * The VO2 Max value, in ml/kg/min.
+     */
+    float getValue();
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/WearingState.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/WearingState.java
new file mode 100644
index 0000000..853de61
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/WearingState.java
@@ -0,0 +1,23 @@
+/*  Copyright (C) 2023-2024 Yoran Vulker
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.model;
+
+public enum WearingState {
+    UNKNOWN,
+    WEARING,
+    NOT_WEARING,
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/Weather.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/Weather.java
new file mode 100644
index 0000000..525c7be
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/Weather.java
@@ -0,0 +1,1213 @@
+/*  Copyright (C) 2016-2024 Andreas Shimokawa, Daniele Gobbetti, Jos茅 Rebelo,
+    Petr Van臎k, Sebastian Kranz
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.model;
+
+import androidx.annotation.Nullable;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.ObjectStreamException;
+import java.util.ArrayList;
+import java.util.List;
+
+public class Weather {
+    private static final Logger LOG = LoggerFactory.getLogger(Weather.class);
+
+    private final ArrayList<WeatherSpec> weatherSpecs = new ArrayList<>();
+
+    private JSONObject reconstructedOWMForecast = null;
+
+    private File cacheFile;
+
+    private Weather() {
+        // Use getInstance
+    }
+
+    @Nullable
+    public WeatherSpec getWeatherSpec() {
+        if (weatherSpecs.isEmpty()) {
+            return null;
+        }
+
+        return weatherSpecs.get(0);
+    }
+
+    public List<WeatherSpec> getWeatherSpecs() {
+        return weatherSpecs;
+    }
+
+    public void setWeatherSpec(final List<WeatherSpec> newWeatherSpecs) {
+        weatherSpecs.clear();
+        weatherSpecs.addAll(newWeatherSpecs);
+        saveToCache();
+    }
+
+    public JSONObject createReconstructedOWMWeatherReply() {
+        final WeatherSpec weatherSpec = getWeatherSpec();
+        if (weatherSpec == null) {
+            return null;
+        }
+        JSONObject reconstructedOWMWeather = new JSONObject();
+        JSONArray weather = new JSONArray();
+        JSONObject condition = new JSONObject();
+        JSONObject main = new JSONObject();
+        JSONObject wind = new JSONObject();
+
+        try {
+            condition.put("id", weatherSpec.currentConditionCode);
+            condition.put("main", weatherSpec.currentCondition);
+            condition.put("description", weatherSpec.currentCondition);
+            condition.put("icon", Weather.mapToOpenWeatherMapIcon(weatherSpec.currentConditionCode));
+            weather.put(condition);
+
+
+            main.put("temp", weatherSpec.currentTemp);
+            main.put("humidity", weatherSpec.currentHumidity);
+            main.put("temp_min", weatherSpec.todayMinTemp);
+            main.put("temp_max", weatherSpec.todayMaxTemp);
+
+            wind.put("speed", (weatherSpec.windSpeed / 3.6f)); //meter per second
+            wind.put("deg", weatherSpec.windDirection);
+
+            reconstructedOWMWeather.put("weather", weather);
+            reconstructedOWMWeather.put("main", main);
+            reconstructedOWMWeather.put("name", weatherSpec.location);
+            reconstructedOWMWeather.put("wind", wind);
+
+        } catch (JSONException e) {
+            LOG.error("Error while reconstructing OWM weather reply");
+            return null;
+        }
+        LOG.debug("Weather JSON for WEBVIEW: " + reconstructedOWMWeather.toString());
+        return reconstructedOWMWeather;
+    }
+
+    public JSONObject getReconstructedOWMForecast() {
+        return reconstructedOWMForecast;
+    }
+
+    public void setReconstructedOWMForecast(JSONObject reconstructedOWMForecast) {
+        this.reconstructedOWMForecast = reconstructedOWMForecast;
+    }
+
+    private static final Weather weather = new Weather();
+    public static Weather getInstance() { return weather; }
+
+    public static byte mapToPebbleCondition(int openWeatherMapCondition) {
+/* deducted values:
+    0 = sun + cloud
+    1 = clouds
+    2 = some snow
+    3 = some rain
+    4 = heavy rain
+    5 = heavy snow
+    6 = sun + cloud + rain (default icon?)
+    7 = sun
+    8 = rain + snow
+    9 = 6
+    10, 11, ... = empty icon
+ */
+        switch (openWeatherMapCondition) {
+//Group 2xx: Thunderstorm
+            case 200:  //thunderstorm with light rain:  //11d
+            case 201:  //thunderstorm with rain:  //11d
+            case 202:  //thunderstorm with heavy rain:  //11d
+            case 210:  //light thunderstorm::  //11d
+            case 211:  //thunderstorm:  //11d
+            case 230:  //thunderstorm with light drizzle:  //11d
+            case 231:  //thunderstorm with drizzle:  //11d
+            case 232:  //thunderstorm with heavy drizzle:  //11d
+            case 212:  //heavy thunderstorm:  //11d
+            case 221:  //ragged thunderstorm:  //11d
+                return 4;
+//Group 3xx: Drizzle
+            case 300:  //light intensity drizzle:  //09d
+            case 301:  //drizzle:  //09d
+            case 302:  //heavy intensity drizzle:  //09d
+            case 310:  //light intensity drizzle rain:  //09d
+            case 311:  //drizzle rain:  //09d
+            case 312:  //heavy intensity drizzle rain:  //09d
+            case 313:  //shower rain and drizzle:  //09d
+            case 314:  //heavy shower rain and drizzle:  //09d
+            case 321:  //shower drizzle:  //09d
+            case 500:  //light rain:  //10d
+            case 501:  //moderate rain:  //10d
+                return 3;
+//Group 5xx: Rain
+            case 502:  //heavy intensity rain:  //10d
+            case 503:  //very heavy rain:  //10d
+            case 504:  //extreme rain:  //10d
+            case 511:  //freezing rain:  //13d
+            case 520:  //light intensity shower rain:  //09d
+            case 521:  //shower rain:  //09d
+            case 522:  //heavy intensity shower rain:  //09d
+            case 531:  //ragged shower rain:  //09d
+                return 4;
+//Group 6xx: Snow
+            case 600:  //light snow:  //[[file:13d.png]]
+            case 601:  //snow:  //[[file:13d.png]]
+            case 620:  //light shower snow:  //[[file:13d.png]]
+                return 2;
+            case 602:  //heavy snow:  //[[file:13d.png]]
+            case 611:  //sleet:  //[[file:13d.png]]
+            case 612:  //shower sleet:  //[[file:13d.png]]
+            case 621:  //shower snow:  //[[file:13d.png]]
+            case 622:  //heavy shower snow:  //[[file:13d.png]]
+                return 5;
+            case 615:  //light rain and snow:  //[[file:13d.png]]
+            case 616:  //rain and snow:  //[[file:13d.png]]
+                return 8;
+//Group 7xx: Atmosphere
+            case 701:  //mist:  //[[file:50d.png]]
+            case 711:  //smoke:  //[[file:50d.png]]
+            case 721:  //haze:  //[[file:50d.png]]
+            case 731:  //sandcase  dust whirls:  //[[file:50d.png]]
+            case 741:  //fog:  //[[file:50d.png]]
+            case 751:  //sand:  //[[file:50d.png]]
+            case 761:  //dust:  //[[file:50d.png]]
+            case 762:  //volcanic ash:  //[[file:50d.png]]
+            case 771:  //squalls:  //[[file:50d.png]]
+            case 781:  //tornado:  //[[file:50d.png]]
+            case 900:  //tornado
+                return 6;
+//Group 800: Clear
+            case 800:  //clear sky:  //[[file:01d.png]] [[file:01n.png]]
+                return 7;
+//Group 80x: Clouds
+            case 801:  //few clouds:  //[[file:02d.png]] [[file:02n.png]]
+            case 802:  //scattered clouds:  //[[file:03d.png]] [[file:03d.png]]
+            case 803:  //broken clouds:  //[[file:04d.png]] [[file:03d.png]]
+            case 804:  //overcast clouds:  //[[file:04d.png]] [[file:04d.png]]
+                return 0;
+//Group 90x: Extreme
+            case 901:  //tropical storm
+            case 903:  //cold
+            case 904:  //hot
+            case 905:  //windy
+            case 906:  //hail
+//Group 9xx: Additional
+            case 951:  //calm
+            case 952:  //light breeze
+            case 953:  //gentle breeze
+            case 954:  //moderate breeze
+            case 955:  //fresh breeze
+            case 956:  //strong breeze
+            case 957:  //high windcase  near gale
+            case 958:  //gale
+            case 959:  //severe gale
+            case 960:  //storm
+            case 961:  //violent storm
+            case 902:  //hurricane
+            case 962:  //hurricane
+            default:
+                return 6;
+
+        }
+    }
+
+    public static int mapToYahooCondition(int openWeatherMapCondition) {
+        // openweathermap.org conditions:
+        // http://openweathermap.org/weather-conditions
+        switch (openWeatherMapCondition) {
+//Group 2xx: Thunderstorm
+            case 200:  //thunderstorm with light rain:  //11d
+            case 201:  //thunderstorm with rain:  //11d
+            case 202:  //thunderstorm with heavy rain:  //11d
+            case 210:  //light thunderstorm::  //11d
+            case 211:  //thunderstorm:  //11d
+            case 230:  //thunderstorm with light drizzle:  //11d
+            case 231:  //thunderstorm with drizzle:  //11d
+            case 232:  //thunderstorm with heavy drizzle:  //11d
+                return 4;
+            case 212:  //heavy thunderstorm:  //11d
+            case 221:  //ragged thunderstorm:  //11d
+                return 3;
+//Group 3xx: Drizzle
+            case 300:  //light intensity drizzle:  //09d
+            case 301:  //drizzle:  //09d
+            case 302:  //heavy intensity drizzle:  //09d
+            case 310:  //light intensity drizzle rain:  //09d
+            case 311:  //drizzle rain:  //09d
+            case 312:  //heavy intensity drizzle rain:  //09d
+                return 9;
+            case 313:  //shower rain and drizzle:  //09d
+            case 314:  //heavy shower rain and drizzle:  //09d
+            case 321:  //shower drizzle:  //09d
+                return 11;
+//Group 5xx: Rain
+            case 500:  //light rain:  //10d
+            case 501:  //moderate rain:  //10d
+            case 502:  //heavy intensity rain:  //10d
+            case 503:  //very heavy rain:  //10d
+            case 504:  //extreme rain:  //10d
+            case 511:  //freezing rain:  //13d
+                return 10;
+            case 520:  //light intensity shower rain:  //09d
+                return 40;
+            case 521:  //shower rain:  //09d
+            case 522:  //heavy intensity shower rain:  //09d
+            case 531:  //ragged shower rain:  //09d
+                return 12;
+//Group 6xx: Snow
+            case 600:  //light snow:  //[[file:13d.png]]
+                return 7;
+            case 601:  //snow:  //[[file:13d.png]]
+                return 16;
+            case 602:  //heavy snow:  //[[file:13d.png]]
+                return 15;
+            case 611:  //sleet:  //[[file:13d.png]]
+            case 612:  //shower sleet:  //[[file:13d.png]]
+                return 18;
+            case 615:  //light rain and snow:  //[[file:13d.png]]
+            case 616:  //rain and snow:  //[[file:13d.png]]
+                return 5;
+            case 620:  //light shower snow:  //[[file:13d.png]]
+                return 14;
+            case 621:  //shower snow:  //[[file:13d.png]]
+                return 46;
+            case 622:  //heavy shower snow:  //[[file:13d.png]]
+//Group 7xx: Atmosphere
+            case 701:  //mist:  //[[file:50d.png]]
+            case 711:  //smoke:  //[[file:50d.png]]
+                return 22;
+            case 721:  //haze:  //[[file:50d.png]]
+                return 21;
+            case 731:  //sandcase  dust whirls:  //[[file:50d.png]]
+                return 3200;
+            case 741:  //fog:  //[[file:50d.png]]
+                return 20;
+            case 751:  //sand:  //[[file:50d.png]]
+            case 761:  //dust:  //[[file:50d.png]]
+                return 19;
+            case 762:  //volcanic ash:  //[[file:50d.png]]
+            case 771:  //squalls:  //[[file:50d.png]]
+                return 3200;
+            case 781:  //tornado:  //[[file:50d.png]]
+            case 900:  //tornado
+                return 0;
+//Group 800: Clear
+            case 800:  //clear sky:  //[[file:01d.png]] [[file:01n.png]]
+                return 32;
+//Group 80x: Clouds
+            case 801:  //few clouds:  //[[file:02d.png]] [[file:02n.png]]
+            case 802:  //scattered clouds:  //[[file:03d.png]] [[file:03d.png]]
+                return 34;
+            case 803:  //broken clouds:  //[[file:04d.png]] [[file:03d.png]]
+            case 804:  //overcast clouds:  //[[file:04d.png]] [[file:04d.png]]
+                return 44;
+//Group 90x: Extreme
+            case 901:  //tropical storm
+                return 1;
+            case 903:  //cold
+                return 25;
+            case 904:  //hot
+                return 36;
+            case 905:  //windy
+                return 24;
+            case 906:  //hail
+                return 17;
+//Group 9xx: Additional
+            case 951:  //calm
+            case 952:  //light breeze
+            case 953:  //gentle breeze
+            case 954:  //moderate breeze
+            case 955:  //fresh breeze
+                return 34;
+            case 956:  //strong breeze
+            case 957:  //high windcase  near gale
+                return 24;
+            case 958:  //gale
+            case 959:  //severe gale
+            case 960:  //storm
+            case 961:  //violent storm
+                return 3200;
+            case 902:  //hurricane
+            case 962:  //hurricane
+                return 2;
+            default:
+                return 3200;
+
+        }
+    }
+
+    public static String mapToOpenWeatherMapIcon(int openWeatherMapCondition) {
+        //see https://openweathermap.org/weather-conditions
+        String condition = "02d"; //generic "variable" icon
+
+        if (openWeatherMapCondition >= 200 && openWeatherMapCondition < 300) {
+            condition = "11d";
+        } else if (openWeatherMapCondition >= 300 && openWeatherMapCondition < 500) {
+            condition = "09d";
+        } else if (openWeatherMapCondition >= 500 && openWeatherMapCondition < 510) {
+            condition = "10d";
+        } else if (openWeatherMapCondition >= 511 && openWeatherMapCondition < 600) {
+            condition = "09d";
+        } else if (openWeatherMapCondition >= 600 && openWeatherMapCondition < 700) {
+            condition = "13d";
+        } else if (openWeatherMapCondition >= 700 && openWeatherMapCondition < 800) {
+            condition = "50d";
+        } else if (openWeatherMapCondition == 800) {
+            condition = "01d"; //TODO: night?
+        } else if (openWeatherMapCondition == 801) {
+            condition = "02d"; //TODO: night?
+        } else if (openWeatherMapCondition == 802) {
+            condition = "03d"; //TODO: night?
+        } else if (openWeatherMapCondition == 803 || openWeatherMapCondition == 804) {
+            condition = "04d"; //TODO: night?
+        }
+
+        return condition;
+    }
+
+    public static int mapToOpenWeatherMapCondition(int yahooCondition) {
+        switch (yahooCondition) {
+//yahoo weather conditions:
+//https://developer.yahoo.com/weather/documentation.html
+            case 0:  //tornado
+                return 900;
+            case 1:  //tropical storm
+                return 901;
+            case 2:  //hurricane
+                return 962;
+            case 3:  //severe thunderstorms
+                return 212;
+            case 4:  //thunderstorms
+                return 211;
+            case 5:  //mixed rain and snow
+            case 6:  //mixed rain and sleet
+                return 616;
+            case 7:  //mixed snow and sleet
+                return 600;
+            case 8:  //freezing drizzle
+            case 9:  //drizzle
+                return 301;
+            case 10:  //freezing rain
+                return 511;
+            case 11:  //showers
+            case 12:  //showers
+                return 521;
+            case 13:  //snow flurries
+            case 14:  //light snow showers
+                return 620;
+            case 15:  //blowing snow
+            case 41:  //heavy snow
+            case 42:  //scattered snow showers
+            case 43:  //heavy snow
+            case 46:  //snow showers
+                return 602;
+            case 16:  //snow
+                return 601;
+            case 17:  //hail
+            case 35:  //mixed rain and hail
+                return 906;
+            case 18:  //sleet
+                return 611;
+            case 19:  //dust
+                return 761;
+            case 20:  //foggy
+                return 741;
+            case 21:  //haze
+                return 721;
+            case 22:  //smoky
+                return 711;
+            case 23:  //blustery
+            case 24:  //windy
+                return 905;
+            case 25:  //cold
+                return 903;
+            case 26:  //cloudy
+            case 27:  //mostly cloudy (night)
+            case 28:  //mostly cloudy (day)
+                return 804;
+            case 29:  //partly cloudy (night)
+            case 30:  //partly cloudy (day)
+                return 801;
+            case 31:  //clear (night)
+            case 32:  //sunny
+                return 800;
+            case 33:  //fair (night)
+            case 34:  //fair (day)
+                return 801;
+            case 36:  //hot
+                return 904;
+            case 37:  //isolated thunderstorms
+            case 38:  //scattered thunderstorms
+            case 39:  //scattered thunderstorms
+                return 210;
+            case 40:  //scattered showers
+                return 520;
+            case 44:  //partly cloudy
+                return 801;
+            case 45:  //thundershowers
+            case 47:  //isolated thundershowers
+                return 211;
+            case 3200:  //not available
+            default:
+                return -1;
+        }
+    }
+
+    public static String getConditionString(int openWeatherMapCondition) {
+        switch (openWeatherMapCondition) {
+            case 200:
+                return "thunderstorm with light rain";
+            case 201:
+                return "thunderstorm with rain";
+            case 202:
+                return "thunderstorm with heavy rain";
+            case 210:
+                return "light thunderstorm:";
+            case 211:
+                return "thunderstorm";
+            case 230:
+                return "thunderstorm with light drizzle";
+            case 231:
+                return "thunderstorm with drizzle";
+            case 232:
+                return "thunderstorm with heavy drizzle";
+            case 212:
+                return "heavy thunderstorm";
+            case 221:
+                return "ragged thunderstorm";
+            //Group 3xx: Drizzle
+            case 300:
+                return "light intensity drizzle";
+            case 301:
+                return "drizzle";
+            case 302:
+                return "heavy intensity drizzle";
+            case 310:
+                return "light intensity drizzle rain";
+            case 311:
+                return "drizzle rain";
+            case 312:
+                return "heavy intensity drizzle rain";
+            case 313:
+                return "shower rain and drizzle";
+            case 314:
+                return "heavy shower rain and drizzle";
+            case 321:
+                return "shower drizzle";
+            //Group 5xx: Rain
+            case 500:
+                return "light rain";
+            case 501:
+                return "moderate rain";
+            case 502:
+                return "heavy intensity rain";
+            case 503:
+                return "very heavy rain";
+            case 504:
+                return "extreme rain";
+            case 511:
+                return "freezing rain";
+            case 520:
+                return "light intensity shower rain";
+            case 521:
+                return "shower rain";
+            case 522:
+                return "heavy intensity shower rain";
+            case 531:
+                return "ragged shower rain";
+            //Group 6xx: Snow
+            case 600:
+                return "light snow";
+            case 601:
+                return "snow";
+            case 620:
+                return "light shower snow";
+            case 602:
+                return "heavy snow";
+            case 611:
+                return "sleet";
+            case 612:
+                return "shower sleet";
+            case 621:
+                return "shower snow";
+            case 622:
+                return "heavy shower snow";
+            case 615:
+                return "light rain and snow";
+            case 616:
+                return "rain and snow";
+            //Group 7xx: Atmosphere
+            case 701:
+                return "mist";
+            case 711:
+                return "smoke";
+            case 721:
+                return "haze";
+            case 731:
+                return "sandcase dust whirls";
+            case 741:
+                return "fog";
+            case 751:
+                return "sand";
+            case 761:
+                return "dust";
+            case 762:
+                return "volcanic ash";
+            case 771:
+                return "squalls";
+            case 781:
+                return "tornado";
+            case 900:
+                return "tornado";
+            case 800:
+                return "clear sky";
+            //Group 80x: Clouds
+            case 801:
+                return "few clouds";
+            case 802:
+                return "scattered clouds";
+            case 803:
+                return "broken clouds";
+            case 804:
+                return "overcast clouds";
+            //Group 90x: Extreme
+            case 901:
+                return "tropical storm";
+            case 903:
+                return "cold";
+            case 904:
+                return "hot";
+            case 905:
+                return "windy";
+            case 906:
+                return "hail";
+            //Group 9xx: Additional
+            case 951:
+                return "calm";
+            case 952:
+                return "light breeze";
+            case 953:
+                return "gentle breeze";
+            case 954:
+                return "moderate breeze";
+            case 955:
+                return "fresh breeze";
+            case 956:
+                return "strong breeze";
+            case 957:
+                return "high windcase  near gale";
+            case 958:
+                return "gale";
+            case 959:
+                return "severe gale";
+            case 960:
+                return "storm";
+            case 961:
+                return "violent storm";
+            case 902:
+                return "hurricane";
+            case 962:
+                return "hurricane";
+            default:
+                return "";
+        }
+    }
+
+    public static byte mapToZeTimeConditionOld(int openWeatherMapCondition) {
+/* deducted values:
+    0 = partly cloudy
+    1 = cloudy
+    2 = sunny
+    3 = windy/gale
+    4 = heavy rain
+    5 = snowy
+    6 = storm
+ */
+        switch (openWeatherMapCondition) {
+//Group 2xx: Thunderstorm
+            case 200:  //thunderstorm with light rain:  //11d
+            case 201:  //thunderstorm with rain:  //11d
+            case 202:  //thunderstorm with heavy rain:  //11d
+            case 210:  //light thunderstorm::  //11d
+            case 211:  //thunderstorm:  //11d
+            case 230:  //thunderstorm with light drizzle:  //11d
+            case 231:  //thunderstorm with drizzle:  //11d
+            case 232:  //thunderstorm with heavy drizzle:  //11d
+            case 212:  //heavy thunderstorm:  //11d
+            case 221:  //ragged thunderstorm:  //11d
+//Group 7xx: Atmosphere
+            case 771:  //squalls:  //[[file:50d.png]]
+            case 781:  //tornado:  //[[file:50d.png]]
+//Group 90x: Extreme
+            case 900:  //tornado
+            case 901:  //tropical storm
+//Group 9xx: Additional
+            case 960:  //storm
+            case 961:  //violent storm
+            case 902:  //hurricane
+            case 962:  //hurricane
+                return 6;
+//Group 3xx: Drizzle
+            case 300:  //light intensity drizzle:  //09d
+            case 301:  //drizzle:  //09d
+            case 302:  //heavy intensity drizzle:  //09d
+            case 310:  //light intensity drizzle rain:  //09d
+            case 311:  //drizzle rain:  //09d
+            case 312:  //heavy intensity drizzle rain:  //09d
+            case 313:  //shower rain and drizzle:  //09d
+            case 314:  //heavy shower rain and drizzle:  //09d
+            case 321:  //shower drizzle:  //09d
+//Group 5xx: Rain
+            case 500:  //light rain:  //10d
+            case 501:  //moderate rain:  //10d
+            case 502:  //heavy intensity rain:  //10d
+            case 503:  //very heavy rain:  //10d
+            case 504:  //extreme rain:  //10d
+            case 511:  //freezing rain:  //13d
+            case 520:  //light intensity shower rain:  //09d
+            case 521:  //shower rain:  //09d
+            case 522:  //heavy intensity shower rain:  //09d
+            case 531:  //ragged shower rain:  //09d
+//Group 90x: Extreme
+            case 906:  //hail
+                return 4;
+//Group 6xx: Snow
+            case 600:  //light snow:  //[[file:13d.png]]
+            case 601:  //snow:  //[[file:13d.png]]
+            case 620:  //light shower snow:  //[[file:13d.png]]
+            case 602:  //heavy snow:  //[[file:13d.png]]
+            case 611:  //sleet:  //[[file:13d.png]]
+            case 612:  //shower sleet:  //[[file:13d.png]]
+            case 621:  //shower snow:  //[[file:13d.png]]
+            case 622:  //heavy shower snow:  //[[file:13d.png]]
+            case 615:  //light rain and snow:  //[[file:13d.png]]
+            case 616:  //rain and snow:  //[[file:13d.png]]
+//Group 90x: Extreme
+            case 903:  //cold
+                return 5;
+//Group 7xx: Atmosphere
+            case 701:  //mist:  //[[file:50d.png]]
+            case 711:  //smoke:  //[[file:50d.png]]
+            case 721:  //haze:  //[[file:50d.png]]
+            case 731:  //sandcase  dust whirls:  //[[file:50d.png]]
+            case 741:  //fog:  //[[file:50d.png]]
+            case 751:  //sand:  //[[file:50d.png]]
+            case 761:  //dust:  //[[file:50d.png]]
+            case 762:  //volcanic ash:  //[[file:50d.png]]
+                return 1;
+//Group 800: Clear
+            case 800:  //clear sky:  //[[file:01d.png]] [[file:01n.png]]
+//Group 90x: Extreme
+            case 904:  //hot
+                return 2;
+//Group 80x: Clouds
+            case 801:  //few clouds:  //[[file:02d.png]] [[file:02n.png]]
+            case 802:  //scattered clouds:  //[[file:03d.png]] [[file:03d.png]]
+            case 803:  //broken clouds:  //[[file:04d.png]] [[file:03d.png]]
+            case 804:  //overcast clouds:  //[[file:04d.png]] [[file:04d.png]]
+            default:
+                return 0;
+
+//Group 9xx: Additional
+            case 905:  //windy
+            case 951:  //calm
+            case 952:  //light breeze
+            case 953:  //gentle breeze
+            case 954:  //moderate breeze
+            case 955:  //fresh breeze
+            case 956:  //strong breeze
+            case 957:  //high windcase  near gale
+            case 958:  //gale
+            case 959:  //severe gale
+                return 3;
+        }
+    }
+
+    public static byte mapToZeTimeCondition(int openWeatherMapCondition) {
+/* deducted values:
+    0 = tornado
+    1 = typhoon
+    2 = hurricane
+    3 = thunderstorm
+    4 = rain and snow
+    5 = unavailable
+    6 = freezing rain
+    7 = drizzle
+    8 = showers
+    9 = snow flurries
+    10 = blowing snow
+    11 = snow
+    12 = sleet
+    13 = foggy
+    14 = windy
+    15 = cloudy
+    16 = partly cloudy (night)
+    17 = partly cloudy (day)
+    18 = clear night
+    19 = sunny
+    20 = thundershower
+    21 = hot
+    22 = scattered thunders
+    23 = snow showers
+    24 = heavy snow
+ */
+        switch (openWeatherMapCondition) {
+//Group 2xx: Thunderstorm
+            case 210:  //light thunderstorm::  //11d
+                return 22;
+
+//Group 2xx: Thunderstorm
+            case 200:  //thunderstorm with light rain:  //11d
+            case 201:  //thunderstorm with rain:  //11d
+            case 202:  //thunderstorm with heavy rain:  //11d
+            case 230:  //thunderstorm with light drizzle:  //11d
+            case 231:  //thunderstorm with drizzle:  //11d
+            case 232:  //thunderstorm with heavy drizzle:  //11d
+                return 20;
+
+//Group 2xx: Thunderstorm
+            case 211:  //thunderstorm:  //11d
+            case 212:  //heavy thunderstorm:  //11d
+            case 221:  //ragged thunderstorm:  //11d
+                return 3;
+
+//Group 7xx: Atmosphere
+            case 781:  //tornado:  //[[file:50d.png]]
+//Group 90x: Extreme
+            case 900:  //tornado
+                return 0;
+
+//Group 90x: Extreme
+            case 901:  //tropical storm
+                return 1;
+
+// Group 7xx: Atmosphere
+            case 771:  //squalls:  //[[file:50d.png]]
+//Group 9xx: Additional
+            case 960:  //storm
+            case 961:  //violent storm
+            case 902:  //hurricane
+            case 962:  //hurricane
+                return 2;
+
+//Group 3xx: Drizzle
+            case 300:  //light intensity drizzle:  //09d
+            case 301:  //drizzle:  //09d
+            case 302:  //heavy intensity drizzle:  //09d
+            case 310:  //light intensity drizzle rain:  //09d
+            case 311:  //drizzle rain:  //09d
+            case 312:  //heavy intensity drizzle rain:  //09d
+            case 313:  //shower rain and drizzle:  //09d
+            case 314:  //heavy shower rain and drizzle:  //09d
+            case 321:  //shower drizzle:  //09d
+                return 7;
+
+//Group 5xx: Rain
+            case 500:  //light rain:  //10d
+            case 501:  //moderate rain:  //10d
+            case 502:  //heavy intensity rain:  //10d
+            case 503:  //very heavy rain:  //10d
+            case 504:  //extreme rain:  //10d
+            case 520:  //light intensity shower rain:  //09d
+            case 521:  //shower rain:  //09d
+            case 522:  //heavy intensity shower rain:  //09d
+            case 531:  //ragged shower rain:  //09d
+//Group 90x: Extreme
+            case 906:  //hail
+                return 8;
+
+//Group 5xx: Rain
+            case 511:  //freezing rain:  //13d
+                return 6;
+
+//Group 6xx: Snow
+            case 620:  //light shower snow:  //[[file:13d.png]]
+            case 621:  //shower snow:  //[[file:13d.png]]
+            case 622:  //heavy shower snow:  //[[file:13d.png]]
+                return 23;
+
+//Group 6xx: Snow
+            case 615:  //light rain and snow:  //[[file:13d.png]]
+            case 616:  //rain and snow:  //[[file:13d.png]]
+                return 4;
+
+//Group 6xx: Snow
+            case 611:  //sleet:  //[[file:13d.png]]
+            case 612:  //shower sleet:  //[[file:13d.png]]
+                return 12;
+
+//Group 6xx: Snow
+            case 600:  //light snow:  //[[file:13d.png]]
+            case 601:  //snow:  //[[file:13d.png]]
+                return 11;
+//Group 6xx: Snow
+            case 602:  //heavy snow:  //[[file:13d.png]]
+                return 24;
+
+//Group 7xx: Atmosphere
+            case 701:  //mist:  //[[file:50d.png]]
+            case 711:  //smoke:  //[[file:50d.png]]
+            case 721:  //haze:  //[[file:50d.png]]
+            case 731:  //sandcase  dust whirls:  //[[file:50d.png]]
+            case 741:  //fog:  //[[file:50d.png]]
+            case 751:  //sand:  //[[file:50d.png]]
+            case 761:  //dust:  //[[file:50d.png]]
+            case 762:  //volcanic ash:  //[[file:50d.png]]
+                return 13;
+
+//Group 800: Clear
+            case 800:  //clear sky:  //[[file:01d.png]] [[file:01n.png]]
+                return 19;
+
+//Group 90x: Extreme
+            case 904:  //hot
+                return 21;
+
+//Group 80x: Clouds
+            case 801:  //few clouds:  //[[file:02d.png]] [[file:02n.png]]
+            case 802:  //scattered clouds:  //[[file:03d.png]] [[file:03d.png]]
+            case 803:  //broken clouds:  //[[file:04d.png]] [[file:03d.png]]
+                return 17;
+
+//Group 80x: Clouds
+            case 804:  //overcast clouds:  //[[file:04d.png]] [[file:04d.png]]
+                return 15;
+
+//Group 9xx: Additional
+            case 905:  //windy
+            case 951:  //calm
+            case 952:  //light breeze
+            case 953:  //gentle breeze
+            case 954:  //moderate breeze
+            case 955:  //fresh breeze
+            case 956:  //strong breeze
+            case 957:  //high windcase  near gale
+            case 958:  //gale
+            case 959:  //severe gale
+                return 14;
+
+            default:
+//Group 90x: Extreme
+            case 903:  //cold
+                return 5;
+        }
+    }
+    public static byte mapToCmfCondition(int openWeatherMapCondition) {
+/* deducted values:
+    1 = sunny
+    2 = cloudy
+    3 = overcast
+    4 = showers
+    5 = snow showers
+    6 = fog
+
+    9 = thunder showers
+
+    14 = sleet
+
+    19 = hot (extreme)
+    20 = cold (extreme)
+
+    21 = strong wind
+    22 = (night) sunny - with moon
+    23 = (night) sunny with stars
+    24 = (night) cloudy - with moon
+    25 = sun with haze
+    26 = cloudy (sun with cloud)
+ */
+        switch (openWeatherMapCondition) {
+//Group 2xx: Thunderstorm
+            case 210:  //light thunderstorm::  //11d
+            case 200:  //thunderstorm with light rain:  //11d
+            case 201:  //thunderstorm with rain:  //11d
+            case 202:  //thunderstorm with heavy rain:  //11d
+            case 230:  //thunderstorm with light drizzle:  //11d
+            case 231:  //thunderstorm with drizzle:  //11d
+            case 232:  //thunderstorm with heavy drizzle:  //11d
+            case 211:  //thunderstorm:  //11d
+            case 212:  //heavy thunderstorm:  //11d
+            case 221:  //ragged thunderstorm:  //11d
+                return 9;
+
+//Group 90x: Extreme
+            case 901:  //tropical storm
+//Group 7xx: Atmosphere
+            case 781:  //tornado:  //[[file:50d.png]]
+//Group 90x: Extreme
+            case 900:  //tornado
+// Group 7xx: Atmosphere
+            case 771:  //squalls:  //[[file:50d.png]]
+//Group 9xx: Additional
+            case 960:  //storm
+            case 961:  //violent storm
+            case 902:  //hurricane
+            case 962:  //hurricane
+                return 21;
+
+//Group 3xx: Drizzle
+            case 300:  //light intensity drizzle:  //09d
+            case 301:  //drizzle:  //09d
+            case 302:  //heavy intensity drizzle:  //09d
+            case 310:  //light intensity drizzle rain:  //09d
+            case 311:  //drizzle rain:  //09d
+            case 312:  //heavy intensity drizzle rain:  //09d
+            case 313:  //shower rain and drizzle:  //09d
+            case 314:  //heavy shower rain and drizzle:  //09d
+            case 321:  //shower drizzle:  //09d
+//Group 5xx: Rain
+            case 500:  //light rain:  //10d
+            case 501:  //moderate rain:  //10d
+            case 502:  //heavy intensity rain:  //10d
+            case 503:  //very heavy rain:  //10d
+            case 504:  //extreme rain:  //10d
+            case 520:  //light intensity shower rain:  //09d
+            case 521:  //shower rain:  //09d
+            case 522:  //heavy intensity shower rain:  //09d
+            case 531:  //ragged shower rain:  //09d
+                return 4;
+
+//Group 90x: Extreme
+            case 906:  //hail
+            case 615:  //light rain and snow:  //[[file:13d.png]]
+            case 616:  //rain and snow:  //[[file:13d.png]]
+            case 511:  //freezing rain:  //13d
+                return 14;
+
+//Group 6xx: Snow
+            case 611:  //sleet:  //[[file:13d.png]]
+            case 612:  //shower sleet:  //[[file:13d.png]]
+//Group 6xx: Snow
+            case 600:  //light snow:  //[[file:13d.png]]
+            case 601:  //snow:  //[[file:13d.png]]
+//Group 6xx: Snow
+            case 602:  //heavy snow:  //[[file:13d.png]]
+//Group 6xx: Snow
+            case 620:  //light shower snow:  //[[file:13d.png]]
+            case 621:  //shower snow:  //[[file:13d.png]]
+            case 622:  //heavy shower snow:  //[[file:13d.png]]
+                return 5;
+
+
+//Group 7xx: Atmosphere
+            case 701:  //mist:  //[[file:50d.png]]
+            case 711:  //smoke:  //[[file:50d.png]]
+            case 721:  //haze:  //[[file:50d.png]]
+            case 731:  //sandcase  dust whirls:  //[[file:50d.png]]
+            case 741:  //fog:  //[[file:50d.png]]
+            case 751:  //sand:  //[[file:50d.png]]
+            case 761:  //dust:  //[[file:50d.png]]
+            case 762:  //volcanic ash:  //[[file:50d.png]]
+                return 6;
+
+//Group 800: Clear
+            case 800:  //clear sky:  //[[file:01d.png]] [[file:01n.png]]
+                return 1;
+
+//Group 90x: Extreme
+            case 904:  //hot
+                return 19;
+
+//Group 80x: Clouds
+            case 801:  //few clouds:  //[[file:02d.png]] [[file:02n.png]]
+            case 802:  //scattered clouds:  //[[file:03d.png]] [[file:03d.png]]
+                return 26;
+            case 803:  //broken clouds:  //[[file:04d.png]] [[file:03d.png]]
+                return 2;
+
+//Group 80x: Clouds
+            case 804:  //overcast clouds:  //[[file:04d.png]] [[file:04d.png]]
+                return 3;
+
+//Group 9xx: Additional
+            case 905:  //windy
+            case 951:  //calm
+            case 952:  //light breeze
+            case 953:  //gentle breeze
+            case 954:  //moderate breeze
+            case 955:  //fresh breeze
+            case 956:  //strong breeze
+            case 957:  //high windcase  near gale
+            case 958:  //gale
+            case 959:  //severe gale
+                return 21;
+
+            default:
+//Group 90x: Extreme
+            case 903:  //cold
+                return 20;
+        }
+    }
+
+    public static byte mapToFitProCondition(int openWeatherMapCondition) {
+        switch (openWeatherMapCondition) {
+            case 100:
+                return 1;
+            case 104:
+                return 2;
+            case 101:
+            case 102:
+            case 103:
+                return 3;
+            case 305:
+            case 309:
+                return 4;
+            case 306:
+            case 314:
+            case 399:
+                return 5;
+            case 307:
+            case 308:
+            case 310:
+            case 311:
+            case 312:
+            case 315:
+            case 316:
+            case 317:
+            case 318:
+                return 6;
+            case 300:
+            case 301:
+            case 302:
+            case 303:
+                return 7;
+            case 400:
+            case 407:
+                return 8;
+            case 401:
+            case 408:
+            case 499:
+                return 9;
+            case 402:
+            case 403:
+            case 409:
+            case 410:
+                return 10;
+            case 404:
+            case 405:
+            case 406:
+                return 11;
+            case 500:
+            case 501:
+            case 502:
+            case 509:
+            case 510:
+            case 511:
+            case 512:
+            case 513:
+            case 514:
+            case 515:
+                return 12;
+            case 304:
+            case 313:
+                return 13;
+            case 503:
+            case 504:
+            case 507:
+            case 508:
+                return 14;
+            case 200:
+            case 201:
+            case 202:
+            case 203:
+            case 204:
+                return 15;
+            case 205:
+            case 206:
+            case 207:
+            case 208:
+                return 16;
+            case 209:
+            case 210:
+            case 211:
+                return 17;
+            case 212:
+                return 18;
+            case 231:
+                return 19;
+            default:
+                return 3;
+        }
+    }
+
+    /**
+     * Set the weather cache file. If enabled and the current weather is null, load the cache file.
+     *
+     * @param cacheDir the cache directory, where the cache file will be created
+     * @param enabled whether caching is enabled
+     */
+    public void setCacheFile(final File cacheDir, final boolean enabled) {
+        // FIXME: Do not use serializable for this
+
+        cacheFile = new File(cacheDir, "weatherCache.bin");
+
+        if (enabled) {
+            LOG.info("Setting weather cache file to {}", cacheFile.getPath());
+
+            if (cacheFile.isFile() && weatherSpecs.isEmpty()) {
+                try (final ObjectInputStream o = new ObjectInputStream(new FileInputStream(cacheFile))) {
+                    final ArrayList<WeatherSpec> cachedSpecs = (ArrayList<WeatherSpec>) o.readObject();
+                    weatherSpecs.addAll(cachedSpecs);
+                } catch (final ObjectStreamException e) {
+                    LOG.error("Failed to deserialize weather from cache", e);
+                    // keep cacheFile set - it's most likely an older version
+                } catch (final IOException e) {
+                    LOG.error("Failed to read weather cache file", e);
+                    // Something is wrong with the file
+                    cacheFile = null;
+                } catch (final Throwable e) {
+                    LOG.error("Failed to read weather from cache", e);
+                    // keep cacheFile set - it's most likely an older version
+                }
+            } else if (!weatherSpecs.isEmpty()) {
+                saveToCache();
+            }
+        } else {
+            if (cacheFile.isFile()) {
+                LOG.info("Deleting weather cache file {}", cacheFile.getPath());
+
+                try {
+                    cacheFile.delete();
+                } catch (final Throwable e) {
+                    LOG.error("Failed to delete cache file", e);
+                    cacheFile = null;
+                }
+            }
+        }
+    }
+
+    /**
+     * Save the current weather to cache, if a cache file is enabled and the weather is not null.
+     */
+    public void saveToCache() {
+        if (weatherSpecs.isEmpty() || cacheFile == null) {
+            return;
+        }
+
+        LOG.info("Loading weather from cache {}", cacheFile.getPath());
+
+        try (ObjectOutputStream o = new ObjectOutputStream(new FileOutputStream(cacheFile))) {
+            o.writeObject(weatherSpecs);
+        } catch (final Throwable e) {
+            LOG.error("Failed to save weather to cache", e);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/WeatherSpec.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/WeatherSpec.java
new file mode 100644
index 0000000..8a906f0
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/WeatherSpec.java
@@ -0,0 +1,505 @@
+/*  Copyright (C) 2016-2024 Andreas Shimokawa, Arjan Schrijver, beardhatcode,
+    Carsten Pfeiffer, Daniele Gobbetti, Enrico Brambilla, Jos茅 Rebelo, Taavi
+    Eom盲e
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+
+package nodomain.freeyourgadget.gadgetbridge.model;
+
+import android.location.Location;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.Nullable;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+
+// FIXME: document me and my fields, including units
+public class WeatherSpec implements Parcelable, Serializable {
+
+    public static final Creator<WeatherSpec> CREATOR = new Creator<WeatherSpec>() {
+        @Override
+        public WeatherSpec createFromParcel(Parcel in) {
+            return new WeatherSpec(in);
+        }
+
+        @Override
+        public WeatherSpec[] newArray(int size) {
+            return new WeatherSpec[size];
+        }
+    };
+    public static final int VERSION = 4;
+    private static final long serialVersionUID = VERSION;
+    public int timestamp; // unix epoch timestamp, in seconds
+    public String location;
+    public int currentTemp; // kelvin
+    public int currentConditionCode = 3200; // OpenWeatherMap condition code
+    public String currentCondition;
+    public int currentHumidity;
+    public int todayMaxTemp; // kelvin
+    public int todayMinTemp; // kelvin
+    public float windSpeed; // km per hour
+    public int windDirection; // deg
+    public float uvIndex; // 0.0 to 15.0
+    public int precipProbability; // %
+    public int dewPoint; // kelvin
+    public float pressure; // mb
+    public int cloudCover; // %
+    public float visibility; // m
+    public int sunRise; // unix epoch timestamp, in seconds
+    public int sunSet; // unix epoch timestamp, in seconds
+    public int moonRise; // unix epoch timestamp, in seconds
+    public int moonSet; // unix epoch timestamp, in seconds
+    public int moonPhase; // deg [0, 360[
+    public float latitude;
+    public float longitude;
+    public int feelsLikeTemp; // kelvin
+    public int isCurrentLocation = -1; // 0 for false, 1 for true, -1 for unknown
+    public AirQuality airQuality;
+
+    // Forecasts from the next day onward, in chronological order, one entry per day.
+    // It should not include the current or previous days
+    public ArrayList<Daily> forecasts = new ArrayList<>();
+
+    // Hourly forecasts
+    public ArrayList<Hourly> hourly = new ArrayList<>();
+
+    public WeatherSpec() {
+
+    }
+
+    // Lower bounds of beaufort regions 1 to 12
+    // Values from https://en.wikipedia.org/wiki/Beaufort_scale
+    static final float[] beaufort = new float[] { 2, 6, 12, 20, 29, 39, 50, 62, 75, 89, 103, 118 };
+    //                                    level: 0 1  2   3   4   5   6   7   8   9   10   11   12
+
+    public static int toBeaufort(final float speed) {
+        int l = 0;
+        while (l < beaufort.length && beaufort[l] < speed) {
+            l++;
+        }
+        return l;
+    }
+
+    public int windSpeedAsBeaufort() {
+        return toBeaufort(this.windSpeed);
+    }
+
+    @Nullable
+    public Location getLocation() {
+        if (latitude == 0 && longitude == 0) {
+            return null;
+        }
+        final Location location = new Location("weatherSpec");
+        location.setLatitude(latitude);
+        location.setLongitude(longitude);
+        return location;
+    }
+
+    protected WeatherSpec(Parcel in) {
+        int version = in.readInt();
+        if (version >= 2) {
+            timestamp = in.readInt();
+            location = in.readString();
+            currentTemp = in.readInt();
+            currentConditionCode = in.readInt();
+            currentCondition = in.readString();
+            currentHumidity = in.readInt();
+            todayMaxTemp = in.readInt();
+            todayMinTemp = in.readInt();
+            windSpeed = in.readFloat();
+            windDirection = in.readInt();
+            if (version < 4) {
+                // Deserialize the old Forecast list and convert them to Daily
+                final ArrayList<Forecast> oldForecasts = new ArrayList<>();
+                in.readList(oldForecasts, Forecast.class.getClassLoader());
+                for (final Forecast forecast : oldForecasts) {
+                    final Daily d = new Daily();
+                    d.minTemp = forecast.minTemp;
+                    d.maxTemp = forecast.maxTemp;
+                    d.conditionCode = forecast.conditionCode;
+                    d.humidity = forecast.humidity;
+                    forecasts.add(d);
+                }
+            } else {
+                in.readList(forecasts, Daily.class.getClassLoader());
+            }
+        }
+        if (version >= 3) {
+            uvIndex = in.readFloat();
+            precipProbability = in.readInt();
+        }
+        if (version >= 4) {
+            dewPoint = in.readInt();
+            pressure = in.readFloat();
+            cloudCover = in.readInt();
+            visibility = in.readFloat();
+            sunRise = in.readInt();
+            sunSet = in.readInt();
+            moonRise = in.readInt();
+            moonSet = in.readInt();
+            moonPhase = in.readInt();
+            latitude = in.readFloat();
+            longitude = in.readFloat();
+            feelsLikeTemp = in.readInt();
+            isCurrentLocation = in.readInt();
+            airQuality = in.readParcelable(AirQuality.class.getClassLoader());
+            in.readList(hourly, Hourly.class.getClassLoader());
+        }
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(VERSION);
+        dest.writeInt(timestamp);
+        dest.writeString(location);
+        dest.writeInt(currentTemp);
+        dest.writeInt(currentConditionCode);
+        dest.writeString(currentCondition);
+        dest.writeInt(currentHumidity);
+        dest.writeInt(todayMaxTemp);
+        dest.writeInt(todayMinTemp);
+        dest.writeFloat(windSpeed);
+        dest.writeInt(windDirection);
+        dest.writeList(forecasts);
+        dest.writeFloat(uvIndex);
+        dest.writeInt(precipProbability);
+        dest.writeInt(dewPoint);
+        dest.writeFloat(pressure);
+        dest.writeInt(cloudCover);
+        dest.writeFloat(visibility);
+        dest.writeInt(sunRise);
+        dest.writeInt(sunSet);
+        dest.writeInt(moonRise);
+        dest.writeInt(moonSet);
+        dest.writeInt(moonPhase);
+        dest.writeFloat(latitude);
+        dest.writeFloat(longitude);
+        dest.writeInt(feelsLikeTemp);
+        dest.writeInt(isCurrentLocation);
+        dest.writeParcelable(airQuality, 0);
+        dest.writeList(hourly);
+    }
+
+    /**
+     * Convert the current day's forecast to a {@link Daily} object.
+     */
+    public Daily todayAsDaily() {
+        final Daily daily = new Daily();
+
+        daily.minTemp = this.todayMinTemp;
+        daily.maxTemp = this.todayMaxTemp;
+        daily.conditionCode = this.currentConditionCode;
+        daily.humidity = this.currentHumidity;
+        daily.windSpeed = this.windSpeed;
+        daily.windDirection = this.windDirection;
+        daily.uvIndex = this.uvIndex;
+        daily.precipProbability = this.precipProbability;
+        daily.sunRise = this.sunRise;
+        daily.sunSet = this.sunSet;
+        daily.moonRise = this.moonRise;
+        daily.moonSet = this.moonSet;
+        daily.moonPhase = this.moonPhase;
+        daily.airQuality = this.airQuality;
+
+        return daily;
+    }
+
+    @Deprecated // kept for backwards compatibility with old weather apps
+    public static class Forecast implements Parcelable, Serializable {
+        private static final long serialVersionUID = 1L;
+
+        public static final Creator<Forecast> CREATOR = new Creator<Forecast>() {
+            @Override
+            public Forecast createFromParcel(Parcel in) {
+                return new Forecast(in);
+            }
+
+            @Override
+            public Forecast[] newArray(int size) {
+                return new Forecast[size];
+            }
+        };
+        public int minTemp; // Kelvin
+        public int maxTemp; // Kelvin
+        public int conditionCode; // OpenWeatherMap condition code
+        public int humidity;
+
+        public Forecast() {
+        }
+
+        public Forecast(int minTemp, int maxTemp, int conditionCode, int humidity) {
+            this.minTemp = minTemp;
+            this.maxTemp = maxTemp;
+            this.conditionCode = conditionCode;
+            this.humidity = humidity;
+        }
+
+        Forecast(Parcel in) {
+            minTemp = in.readInt();
+            maxTemp = in.readInt();
+            conditionCode = in.readInt();
+            humidity = in.readInt();
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeInt(minTemp);
+            dest.writeInt(maxTemp);
+            dest.writeInt(conditionCode);
+            dest.writeInt(humidity);
+        }
+    }
+
+    public static class AirQuality implements Parcelable, Serializable {
+        public static final int VERSION = 1;
+        private static final long serialVersionUID = VERSION;
+
+        public static final Creator<AirQuality> CREATOR = new Creator<AirQuality>() {
+            @Override
+            public AirQuality createFromParcel(final Parcel in) {
+                return new AirQuality(in);
+            }
+
+            @Override
+            public AirQuality[] newArray(final int size) {
+                return new AirQuality[size];
+            }
+        };
+
+        public int aqi = -1; // Air Quality Index - usually the max across all AQI values for pollutants
+
+        public float co = -1; // Carbon Monoxide, mg/m^3
+        public float no2 = -1; // Nitrogen Dioxide, ug/m^3
+        public float o3 = -1; // Ozone, ug/m^3
+        public float pm10 = -1; // Particulate Matter, 10 microns or less in diameter, ug/m^3
+        public float pm25 = -1; // Particulate Matter, 2.5 microns or less in diameter, ug/m^3
+        public float so2 = -1; // Sulphur Dioxide, ug/m^3
+
+        // Air Quality Index values per pollutant
+        // These are expected to be in the Plume scale (see https://plumelabs.files.wordpress.com/2023/06/plume_aqi_2023.pdf)
+        // Some apps such as Breezy Weather fallback to the WHO 2021 AQI for pollutants that are not mapped in the Plume AQI
+        // https://www.who.int/news-room/fact-sheets/detail/ambient-(outdoor)-air-quality-and-health
+        //
+        // Breezy Weather implementation for reference:
+        // - https://github.com/breezy-weather/breezy-weather/blob/main/app/src/main/java/org/breezyweather/common/basic/models/weather/AirQuality.kt
+        // - https://github.com/breezy-weather/breezy-weather/blob/main/app/src/main/java/org/breezyweather/common/basic/models/options/index/PollutantIndex.kt
+
+        public int coAqi = -1;
+        public int no2Aqi = -1;
+        public int o3Aqi = -1;
+        public int pm10Aqi = -1;
+        public int pm25Aqi = -1;
+        public int so2Aqi = -1;
+
+        public AirQuality() {
+        }
+
+        AirQuality(final Parcel in) {
+            in.readInt(); // version
+            aqi = in.readInt();
+            co = in.readFloat();
+            no2 = in.readFloat();
+            o3 = in.readFloat();
+            pm10 = in.readFloat();
+            pm25 = in.readFloat();
+            so2 = in.readFloat();
+            coAqi = in.readInt();
+            no2Aqi = in.readInt();
+            o3Aqi = in.readInt();
+            pm10Aqi = in.readInt();
+            pm25Aqi = in.readInt();
+            so2Aqi = in.readInt();
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(final Parcel dest, final int flags) {
+            dest.writeInt(VERSION);
+            dest.writeInt(aqi);
+            dest.writeFloat(co);
+            dest.writeFloat(no2);
+            dest.writeFloat(o3);
+            dest.writeFloat(pm10);
+            dest.writeFloat(pm25);
+            dest.writeFloat(so2);
+            dest.writeInt(coAqi);
+            dest.writeInt(no2Aqi);
+            dest.writeInt(o3Aqi);
+            dest.writeInt(pm10Aqi);
+            dest.writeInt(pm25Aqi);
+            dest.writeInt(so2Aqi);
+        }
+    }
+
+    public static class Daily implements Parcelable, Serializable {
+        public static final int VERSION = 1;
+        private static final long serialVersionUID = VERSION;
+
+        public static final Creator<Daily> CREATOR = new Creator<Daily>() {
+            @Override
+            public Daily createFromParcel(final Parcel in) {
+                return new Daily(in);
+            }
+
+            @Override
+            public Daily[] newArray(final int size) {
+                return new Daily[size];
+            }
+        };
+        public int minTemp; // Kelvin
+        public int maxTemp; // Kelvin
+        public int conditionCode; // OpenWeatherMap condition code
+        public int humidity;
+        public float windSpeed; // km per hour
+        public int windDirection; // deg
+        public float uvIndex; // 0.0 to 15.0
+        public int precipProbability; // %
+        public int sunRise;
+        public int sunSet;
+        public int moonRise;
+        public int moonSet;
+        public int moonPhase;
+        public AirQuality airQuality;
+
+        public Daily() {
+        }
+
+        Daily(final Parcel in) {
+            in.readInt(); // version
+            minTemp = in.readInt();
+            maxTemp = in.readInt();
+            conditionCode = in.readInt();
+            humidity = in.readInt();
+            windSpeed = in.readFloat();
+            windDirection = in.readInt();
+            uvIndex = in.readFloat();
+            precipProbability = in.readInt();
+            sunRise = in.readInt();
+            sunSet = in.readInt();
+            moonRise = in.readInt();
+            moonSet = in.readInt();
+            moonPhase = in.readInt();
+            airQuality = in.readParcelable(AirQuality.class.getClassLoader());
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(final Parcel dest, final int flags) {
+            dest.writeInt(VERSION);
+            dest.writeInt(minTemp);
+            dest.writeInt(maxTemp);
+            dest.writeInt(conditionCode);
+            dest.writeInt(humidity);
+            dest.writeFloat(windSpeed);
+            dest.writeInt(windDirection);
+            dest.writeFloat(uvIndex);
+            dest.writeInt(precipProbability);
+            dest.writeInt(sunRise);
+            dest.writeInt(sunSet);
+            dest.writeInt(moonRise);
+            dest.writeInt(moonSet);
+            dest.writeInt(moonPhase);
+            dest.writeParcelable(airQuality, 0);
+        }
+
+        public int windSpeedAsBeaufort() {
+            return toBeaufort(this.windSpeed);
+        }
+    }
+
+    public static class Hourly implements Parcelable, Serializable {
+        public static final int VERSION = 1;
+        private static final long serialVersionUID = VERSION;
+
+        public static final Creator<Hourly> CREATOR = new Creator<Hourly>() {
+            @Override
+            public Hourly createFromParcel(final Parcel in) {
+                return new Hourly(in);
+            }
+
+            @Override
+            public Hourly[] newArray(final int size) {
+                return new Hourly[size];
+            }
+        };
+
+        public int timestamp; // unix epoch timestamp, in seconds
+        public int temp; // Kelvin
+        public int conditionCode; // OpenWeatherMap condition code
+        public int humidity;
+        public float windSpeed; // km per hour
+        public int windDirection; // deg
+        public float uvIndex; // 0.0 to 15.0
+        public int precipProbability; // %
+
+        public Hourly() {
+        }
+
+        Hourly(final Parcel in) {
+            in.readInt(); // version
+            timestamp = in.readInt();
+            temp = in.readInt();
+            conditionCode = in.readInt();
+            humidity = in.readInt();
+            windSpeed = in.readFloat();
+            windDirection = in.readInt();
+            uvIndex = in.readFloat();
+            precipProbability = in.readInt();
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(final Parcel dest, final int flags) {
+            dest.writeInt(VERSION);
+            dest.writeInt(timestamp);
+            dest.writeInt(temp);
+            dest.writeInt(conditionCode);
+            dest.writeInt(humidity);
+            dest.writeFloat(windSpeed);
+            dest.writeInt(windDirection);
+            dest.writeFloat(uvIndex);
+            dest.writeInt(precipProbability);
+        }
+
+        public int windSpeedAsBeaufort() {
+            return toBeaufort(this.windSpeed);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/WeightSample.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/WeightSample.java
new file mode 100644
index 0000000..fac2575
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/WeightSample.java
@@ -0,0 +1,24 @@
+/*  Copyright (C) 2024 Severin von Wnuck-Lipinski
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.model;
+
+public interface WeightSample extends TimeSample {
+    /**
+     * Returns the weight value.
+     */
+    float getWeightKg();
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/WorldClock.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/WorldClock.java
new file mode 100644
index 0000000..44b0a08
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/WorldClock.java
@@ -0,0 +1,32 @@
+/*  Copyright (C) 2022-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.model;
+
+import java.io.Serializable;
+
+public interface WorldClock extends Serializable {
+    /**
+     * The {@link android.os.Bundle} name for transferring parceled world clocks.
+     */
+    String EXTRA_WORLD_CLOCK = "world_clock";
+
+    Boolean getEnabled();
+    String getWorldClockId();
+    String getLabel();
+    String getCode();
+    String getTimeZoneId();
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/AbstractDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/AbstractDeviceSupport.java
new file mode 100644
index 0000000..a079eb8
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/AbstractDeviceSupport.java
@@ -0,0 +1,1299 @@
+/*  Copyright (C) 2015-2024 Alicia Hormann, Andreas B枚hler, Andreas Shimokawa,
+    Arjan Schrijver, Carsten Pfeiffer, Daniele Gobbetti, Davis Mosenkovs,
+    Dmitriy Bogdanov, foxstidious, Ganblejs, Jos茅 Rebelo, Pauli Salmenrinne,
+    Petr Van臎k, Taavi Eom盲e, Yoran Vulker
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.bluetooth.BluetoothAdapter;
+import android.companion.CompanionDeviceManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.location.Location;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.telephony.SmsManager;
+import android.text.TextUtils;
+
+import androidx.annotation.RequiresApi;
+import androidx.core.app.NotificationCompat;
+import androidx.core.content.FileProvider;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.IOException;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Locale;
+import java.util.Objects;
+import java.util.Set;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.BuildConfig;
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.CameraActivity;
+import nodomain.freeyourgadget.gadgetbridge.activities.FindPhoneActivity;
+import nodomain.freeyourgadget.gadgetbridge.activities.appmanager.AbstractAppManagerFragment;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+import nodomain.freeyourgadget.gadgetbridge.activities.musicmanager.MusicManagerActivity;
+import nodomain.freeyourgadget.gadgetbridge.capabilities.loyaltycards.LoyaltyCard;
+import nodomain.freeyourgadget.gadgetbridge.database.DBAccess;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventAppInfo;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventCallControl;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventCameraRemote;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventDisplayMessage;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventFindPhone;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventFmFrequency;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventSleepStateDetection;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventSilentMode;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdateDeviceInfo;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventLEDColor;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventMusicControl;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventNotificationControl;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdateDeviceState;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdatePreferences;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventScreenshot;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventWearState;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceMusicData;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceMusicUpdate;
+import nodomain.freeyourgadget.gadgetbridge.entities.BatteryLevel;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.externalevents.NotificationListener;
+import nodomain.freeyourgadget.gadgetbridge.externalevents.opentracks.OpenTracksController;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceMusic;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceMusicPlaylist;
+import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
+import nodomain.freeyourgadget.gadgetbridge.model.BatteryConfig;
+import nodomain.freeyourgadget.gadgetbridge.model.BatteryState;
+import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.Contact;
+import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.Reminder;
+import nodomain.freeyourgadget.gadgetbridge.model.SleepState;
+import nodomain.freeyourgadget.gadgetbridge.model.WearingState;
+import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.WorldClock;
+import nodomain.freeyourgadget.gadgetbridge.model.NavigationInfoSpec;
+import nodomain.freeyourgadget.gadgetbridge.service.receivers.GBCallControlReceiver;
+import nodomain.freeyourgadget.gadgetbridge.service.receivers.GBMusicControlReceiver;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+import nodomain.freeyourgadget.gadgetbridge.util.PendingIntentUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.SilentMode;
+import nodomain.freeyourgadget.gadgetbridge.util.preferences.DevicePrefs;
+
+import static nodomain.freeyourgadget.gadgetbridge.util.GB.NOTIFICATION_CHANNEL_HIGH_PRIORITY_ID;
+
+// TODO: support option for a single reminder notification when notifications could not be delivered?
+// conditions: app was running and received notifications, but device was not connected.
+// maybe need to check for "unread notifications" on device for that.
+
+/**
+ * Abstract implementation of DeviceSupport with some implementations for
+ * common functionality. Still transport independent.
+ */
+public abstract class AbstractDeviceSupport implements DeviceSupport {
+    private static final Logger LOG = LoggerFactory.getLogger(AbstractDeviceSupport.class);
+    private static final int NOTIFICATION_ID_SCREENSHOT = 8000;
+
+    protected GBDevice gbDevice;
+    private BluetoothAdapter btAdapter;
+    private Context context;
+    private boolean autoReconnect, scanReconnect;
+
+
+
+    @Override
+    public void setContext(GBDevice gbDevice, BluetoothAdapter btAdapter, Context context) {
+        this.gbDevice = gbDevice;
+        this.btAdapter = btAdapter;
+        this.context = context;
+    }
+
+    /**
+     * Default implementation just calls #connect()
+     */
+    @Override
+    public boolean connectFirstTime() {
+        return connect();
+    }
+
+    @Override
+    public boolean isConnected() {
+        return gbDevice.isConnected();
+    }
+
+    /**
+     * Returns true if the device is not only connected, but also
+     * initialized.
+     *
+     * @see GBDevice#isInitialized()
+     */
+    protected boolean isInitialized() {
+        return gbDevice.isInitialized();
+    }
+
+    @Override
+    public void setAutoReconnect(boolean enable) {
+        autoReconnect = enable;
+    }
+
+    @Override
+    public boolean getAutoReconnect() {
+        return autoReconnect;
+    }
+
+    @Override
+    public void setScanReconnect(boolean scanReconnect) {
+        this.scanReconnect = scanReconnect;
+    }
+
+    @Override
+    public boolean getScanReconnect(){
+        return this.scanReconnect;
+    }
+
+    @Override
+    public GBDevice getDevice() {
+        return gbDevice;
+    }
+
+    @Override
+    public BluetoothAdapter getBluetoothAdapter() {
+        return btAdapter;
+    }
+
+    @Override
+    public Context getContext() {
+        return context;
+    }
+
+    public void evaluateGBDeviceEvent(GBDeviceEvent deviceEvent) {
+        if (deviceEvent instanceof GBDeviceEventMusicControl) {
+            handleGBDeviceEvent((GBDeviceEventMusicControl) deviceEvent);
+        } else if (deviceEvent instanceof GBDeviceEventCallControl) {
+            handleGBDeviceEvent((GBDeviceEventCallControl) deviceEvent);
+        } else if (deviceEvent instanceof GBDeviceEventCameraRemote) {
+            handleGBDeviceEvent((GBDeviceEventCameraRemote) deviceEvent);
+        } else if (deviceEvent instanceof GBDeviceEventVersionInfo) {
+            handleGBDeviceEvent((GBDeviceEventVersionInfo) deviceEvent);
+        } else if (deviceEvent instanceof GBDeviceEventAppInfo) {
+            handleGBDeviceEvent((GBDeviceEventAppInfo) deviceEvent);
+        } else if (deviceEvent instanceof GBDeviceEventScreenshot) {
+            handleGBDeviceEvent((GBDeviceEventScreenshot) deviceEvent);
+        } else if (deviceEvent instanceof GBDeviceEventNotificationControl) {
+            handleGBDeviceEvent((GBDeviceEventNotificationControl) deviceEvent);
+        } else if (deviceEvent instanceof GBDeviceEventBatteryInfo) {
+            handleGBDeviceEvent((GBDeviceEventBatteryInfo) deviceEvent);
+        } else if (deviceEvent instanceof GBDeviceEventFindPhone) {
+            handleGBDeviceEvent((GBDeviceEventFindPhone) deviceEvent);
+        } else if (deviceEvent instanceof GBDeviceEventLEDColor) {
+            handleGBDeviceEvent((GBDeviceEventLEDColor) deviceEvent);
+        } else if (deviceEvent instanceof GBDeviceEventUpdateDeviceInfo) {
+            handleGBDeviceEvent((GBDeviceEventUpdateDeviceInfo) deviceEvent);
+        } else if (deviceEvent instanceof GBDeviceEventUpdatePreferences) {
+            handleGBDeviceEvent((GBDeviceEventUpdatePreferences) deviceEvent);
+        } else if (deviceEvent instanceof GBDeviceEventUpdateDeviceState) {
+            handleGBDeviceEvent((GBDeviceEventUpdateDeviceState) deviceEvent);
+        } else if (deviceEvent instanceof GBDeviceEventSilentMode) {
+            handleGBDeviceEvent((GBDeviceEventSilentMode) deviceEvent);
+        } else if (deviceEvent instanceof GBDeviceEventFmFrequency) {
+            handleGBDeviceEvent((GBDeviceEventFmFrequency) deviceEvent);
+        } else if (deviceEvent instanceof GBDeviceEventWearState) {
+            handleGBDeviceEvent((GBDeviceEventWearState) deviceEvent);
+        } else if (deviceEvent instanceof GBDeviceEventSleepStateDetection) {
+            handleGBDeviceEvent((GBDeviceEventSleepStateDetection) deviceEvent);
+        } else if (deviceEvent instanceof GBDeviceMusicData) {
+            handleGBDeviceEvent((GBDeviceMusicData) deviceEvent);
+        } else if (deviceEvent instanceof GBDeviceMusicUpdate) {
+            handleGBDeviceEvent((GBDeviceMusicUpdate) deviceEvent);
+        }
+
+    }
+
+    private void handleGBDeviceEvent(GBDeviceEventSilentMode deviceEvent) {
+        LOG.info("Got GBDeviceEventSilentMode: enabled = {}", deviceEvent.isEnabled());
+
+        SilentMode.setPhoneSilentMode(getDevice().getAddress(), deviceEvent.isEnabled());
+    }
+
+    private void handleGBDeviceEvent(final GBDeviceEventFindPhone deviceEvent) {
+        final Context context = getContext();
+        LOG.info("Got GBDeviceEventFindPhone: {}", deviceEvent.event);
+        switch (deviceEvent.event) {
+            case START:
+                handleGBDeviceEventFindPhoneStart(true);
+                break;
+            case START_VIBRATE:
+                handleGBDeviceEventFindPhoneStart(false);
+                break;
+            case VIBRATE:
+                final Intent intentVibrate = new Intent(FindPhoneActivity.ACTION_VIBRATE);
+                intentVibrate.setPackage(BuildConfig.APPLICATION_ID);
+                LocalBroadcastManager.getInstance(context).sendBroadcast(intentVibrate);
+                break;
+            case RING:
+                final Intent intentRing = new Intent(FindPhoneActivity.ACTION_RING);
+                intentRing.setPackage(BuildConfig.APPLICATION_ID);
+                LocalBroadcastManager.getInstance(context).sendBroadcast(intentRing);
+                break;
+            case STOP:
+                final Intent intentStop = new Intent(FindPhoneActivity.ACTION_FOUND);
+                intentStop.setPackage(BuildConfig.APPLICATION_ID);
+                LocalBroadcastManager.getInstance(context).sendBroadcast(intentStop);
+                break;
+            default:
+                LOG.warn("unknown GBDeviceEventFindPhone");
+        }
+    }
+
+    private void handleGBDeviceEventFindPhoneStart(final boolean ring) {
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { // this could be used if app in foreground // TODO: Below Q?
+            Intent startIntent = new Intent(getContext(), FindPhoneActivity.class);
+            startIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            startIntent.putExtra(FindPhoneActivity.EXTRA_RING, ring);
+            context.startActivity(startIntent);
+        } else {
+            handleGBDeviceEventFindPhoneStartNotification(ring);
+        }
+    }
+
+    @RequiresApi(Build.VERSION_CODES.Q)
+    private void handleGBDeviceEventFindPhoneStartNotification(final boolean ring) {
+        LOG.info("Got handleGBDeviceEventFindPhoneStartNotification");
+        final CompanionDeviceManager manager = (CompanionDeviceManager) context.getSystemService(Context.COMPANION_DEVICE_SERVICE);
+        if (manager.getAssociations().isEmpty()) {
+            // On Android Q and above, we need the device to be paired as companion. If it is not, display a notification
+            // notifying the user and linking to further instructions
+            final Intent instructionsIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://gadgetbridge.org/basics/pairing/companion-device/"));
+            final PendingIntent pi = PendingIntentUtils.getActivity(context, 0, instructionsIntent, PendingIntent.FLAG_ONE_SHOT, false);
+            final NotificationCompat.Builder notification = new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_HIGH_PRIORITY_ID)
+                    .setSmallIcon(R.drawable.ic_warning)
+                    .setOngoing(false)
+                    .setPriority(NotificationCompat.PRIORITY_HIGH)
+                    .setContentIntent(pi)
+                    .setAutoCancel(true)
+                    .setContentTitle(context.getString(R.string.find_my_phone_notification))
+                    .setContentText(context.getString(R.string.find_my_phone_companion_warning));
+
+            GB.notify(GB.NOTIFICATION_ID_PHONE_FIND, notification.build(), context);
+
+            return;
+        }
+
+        final Intent intent = new Intent(context, FindPhoneActivity.class);
+        intent.setPackage(BuildConfig.APPLICATION_ID);
+        intent.putExtra(FindPhoneActivity.EXTRA_RING, ring);
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+        final PendingIntent pi = PendingIntentUtils.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT, false);
+
+        final NotificationCompat.Builder notification = new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_HIGH_PRIORITY_ID)
+                .setSmallIcon(R.drawable.ic_notification)
+                .setOngoing(false)
+                .setFullScreenIntent(pi, true)
+                .setPriority(NotificationCompat.PRIORITY_HIGH)
+                .setAutoCancel(true)
+                .setGroup("BackgroundService")
+                .setContentTitle(context.getString(R.string.find_my_phone_notification));
+
+        GB.notify(GB.NOTIFICATION_ID_PHONE_FIND, notification.build(), context);
+        context.startActivity(intent);
+        LOG.debug("CompanionDeviceManager associations were found, starting intent");
+    }
+
+
+    private void handleGBDeviceEvent(GBDeviceEventMusicControl musicEvent) {
+        Context context = getContext();
+        LOG.info("Got event for MUSIC_CONTROL");
+        Intent musicIntent = new Intent(GBMusicControlReceiver.ACTION_MUSICCONTROL);
+        musicIntent.putExtra("event", musicEvent.event.ordinal());
+        musicIntent.setPackage(context.getPackageName());
+        context.sendBroadcast(musicIntent);
+    }
+
+    private void handleGBDeviceEvent(GBDeviceEventCallControl callEvent) {
+        Context context = getContext();
+        LOG.info("Got event for CALL_CONTROL");
+        if(callEvent.event == GBDeviceEventCallControl.Event.IGNORE) {
+            LOG.info("Sending intent for mute");
+            Intent broadcastIntent = new Intent(context.getPackageName() + ".MUTE_CALL");
+            broadcastIntent.setPackage(context.getPackageName());
+            context.sendBroadcast(broadcastIntent);
+            return;
+        }
+        Intent callIntent = new Intent(GBCallControlReceiver.ACTION_CALLCONTROL);
+        callIntent.putExtra("event", callEvent.event.ordinal());
+        callIntent.setPackage(context.getPackageName());
+        context.sendBroadcast(callIntent);
+    }
+
+    protected void handleGBDeviceEvent(GBDeviceEventCameraRemote cameraRemoteEvent) {
+        Intent cameraIntent = new Intent(getContext(), CameraActivity.class);
+        cameraIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        cameraIntent.putExtra(CameraActivity.intentExtraEvent, GBDeviceEventCameraRemote.eventToInt(cameraRemoteEvent.event));
+        getContext().startActivity(cameraIntent);
+    }
+
+    protected void handleGBDeviceEvent(GBDeviceEventVersionInfo infoEvent) {
+        Context context = getContext();
+        LOG.info("Got event for VERSION_INFO: " + infoEvent);
+        if (gbDevice == null) {
+            return;
+        }
+        if (infoEvent.fwVersion != null) {
+            gbDevice.setFirmwareVersion(infoEvent.fwVersion);
+        }
+        if (infoEvent.fwVersion2 != null) {
+            gbDevice.setFirmwareVersion2(infoEvent.fwVersion2);
+        }
+        gbDevice.setModel(infoEvent.hwVersion);
+        gbDevice.sendDeviceUpdateIntent(context);
+    }
+
+    protected void handleGBDeviceEvent(GBDeviceEventLEDColor colorEvent) {
+        Context context = getContext();
+        LOG.info("Got event for LED Color: #" + Integer.toHexString(colorEvent.color).toUpperCase(Locale.ROOT));
+        if (gbDevice == null) {
+            return;
+        }
+        gbDevice.setExtraInfo("led_color", colorEvent.color);
+        gbDevice.sendDeviceUpdateIntent(context);
+    }
+
+    protected void handleGBDeviceEvent(GBDeviceEventUpdateDeviceInfo itemEvent) {
+        if (gbDevice == null) {
+            return;
+        }
+
+        gbDevice.addDeviceInfo(itemEvent.item);
+        gbDevice.sendDeviceUpdateIntent(context);
+    }
+
+    protected void handleGBDeviceEvent(GBDeviceEventUpdatePreferences savePreferencesEvent) {
+        if (gbDevice == null) {
+            return;
+        }
+
+        savePreferencesEvent.update(GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress()));
+        gbDevice.sendDeviceUpdateIntent(context);
+    }
+
+    protected void handleGBDeviceEvent(GBDeviceEventUpdateDeviceState updateDeviceState) {
+        if (gbDevice == null) {
+            return;
+        }
+
+        gbDevice.setState(updateDeviceState.state);
+        gbDevice.sendDeviceUpdateIntent(getContext());
+    }
+
+    protected void handleGBDeviceEvent(GBDeviceEventFmFrequency frequencyEvent) {
+        Context context = getContext();
+        LOG.info("Got event for FM Frequency");
+        if (gbDevice == null) {
+            return;
+        }
+        gbDevice.setExtraInfo("fm_frequency", frequencyEvent.frequency);
+        gbDevice.sendDeviceUpdateIntent(context);
+    }
+
+    private void handleGBDeviceEvent(GBDeviceEventAppInfo appInfoEvent) {
+        Context context = getContext();
+        LOG.info("Got event for APP_INFO");
+
+        Intent appInfoIntent = new Intent(AbstractAppManagerFragment.ACTION_REFRESH_APPLIST);
+        int appCount = appInfoEvent.apps.length;
+        appInfoIntent.putExtra("app_count", appCount);
+        for (int i = 0; i < appCount; i++) {
+            appInfoIntent.putExtra("app_name" + i, appInfoEvent.apps[i].getName());
+            appInfoIntent.putExtra("app_creator" + i, appInfoEvent.apps[i].getCreator());
+            appInfoIntent.putExtra("app_version" + i, appInfoEvent.apps[i].getVersion());
+            appInfoIntent.putExtra("app_uuid" + i, appInfoEvent.apps[i].getUUID().toString());
+            appInfoIntent.putExtra("app_type" + i, appInfoEvent.apps[i].getType().ordinal());
+        }
+        LocalBroadcastManager.getInstance(context).sendBroadcast(appInfoIntent);
+    }
+
+    private void handleGBDeviceEvent(GBDeviceEventScreenshot screenshot) {
+        if (screenshot.getData() == null) {
+            LOG.warn("Screnshot data is null");
+            return;
+        }
+
+        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd-hhmmss", Locale.US);
+        String filename = "screenshot_" + dateFormat.format(new Date()) + ".bmp";
+
+        try {
+            String fullpath = GB.writeScreenshot(screenshot, filename);
+            Bitmap bmp = BitmapFactory.decodeFile(fullpath);
+            Intent intent = new Intent();
+            intent.setAction(android.content.Intent.ACTION_VIEW);
+            Uri screenshotURI = FileProvider.getUriForFile(context, context.getApplicationContext().getPackageName() + ".screenshot_provider", new File(fullpath));
+            intent.setDataAndType(screenshotURI, "image/*");
+            intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+
+            PendingIntent pIntent = PendingIntentUtils.getActivity(context, 0, intent, 0, false);
+
+            Intent shareIntent = new Intent(Intent.ACTION_SEND);
+            shareIntent.setType("image/*");
+            shareIntent.putExtra(Intent.EXTRA_STREAM, screenshotURI);
+
+            PendingIntent pendingShareIntent = PendingIntentUtils.getActivity(context, 0, Intent.createChooser(shareIntent, context.getString(R.string.share_screenshot)),
+                    PendingIntent.FLAG_UPDATE_CURRENT, false);
+
+            NotificationCompat.Action action = new NotificationCompat.Action.Builder(android.R.drawable.ic_menu_share, context.getString(R.string.share), pendingShareIntent).build();
+
+            Notification notif = new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_HIGH_PRIORITY_ID)
+                    .setContentTitle(context.getString(R.string.screenshot_taken))
+                    .setTicker(context.getString(R.string.screenshot_taken))
+                    .setContentText(filename)
+                    .setSmallIcon(R.drawable.ic_notification)
+                    .setStyle(new NotificationCompat.BigPictureStyle()
+                            .bigPicture(bmp))
+                    .setContentIntent(pIntent)
+                    .addAction(action)
+                    .setAutoCancel(true)
+                    .build();
+
+            GB.notify(NOTIFICATION_ID_SCREENSHOT, notif, context);
+        } catch (IOException ex) {
+            LOG.error("Error writing screenshot", ex);
+        }
+    }
+
+    private void handleGBDeviceEvent(GBDeviceEventNotificationControl deviceEvent) {
+        Context context = getContext();
+        LOG.info("Got NOTIFICATION CONTROL device event");
+        String action = null;
+        switch (deviceEvent.event) {
+            case DISMISS:
+                action = NotificationListener.ACTION_DISMISS;
+                break;
+            case DISMISS_ALL:
+                action = NotificationListener.ACTION_DISMISS_ALL;
+                break;
+            case OPEN:
+                action = NotificationListener.ACTION_OPEN;
+                break;
+            case MUTE:
+                action = NotificationListener.ACTION_MUTE;
+                break;
+            case REPLY:
+                if (deviceEvent.phoneNumber == null) {
+                    deviceEvent.phoneNumber = GBApplication.getIDSenderLookup().lookup((int) (deviceEvent.handle >> 4));
+                }
+                if (deviceEvent.phoneNumber != null) {
+                    LOG.info("Got notification reply for SMS from " + deviceEvent.phoneNumber + " : " + deviceEvent.reply);
+                    SmsManager.getDefault().sendTextMessage(deviceEvent.phoneNumber, null, deviceEvent.reply, null, null);
+                } else {
+                    LOG.info("Got notification reply for notification id " + deviceEvent.handle + " : " + deviceEvent.reply);
+                    action = NotificationListener.ACTION_REPLY;
+                }
+                break;
+        }
+        if (action != null) {
+            Intent notificationListenerIntent = new Intent(action);
+            notificationListenerIntent.putExtra("handle", deviceEvent.handle);
+            notificationListenerIntent.putExtra("title", deviceEvent.title);
+            if (deviceEvent.reply != null) {
+                SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress());
+                String suffix = prefs.getString("canned_reply_suffix", null);
+                if (suffix != null && !Objects.equals(suffix, "")) {
+                    deviceEvent.reply += suffix;
+                }
+                notificationListenerIntent.putExtra("reply", deviceEvent.reply);
+            }
+            LocalBroadcastManager.getInstance(context).sendBroadcast(notificationListenerIntent);
+        }
+    }
+
+    protected void handleGBDeviceEvent(GBDeviceEventBatteryInfo deviceEvent) {
+        Context context = getContext();
+        LOG.info("Got BATTERY_INFO device event");
+        gbDevice.setBatteryLevel(deviceEvent.level, deviceEvent.batteryIndex);
+        gbDevice.setBatteryState(deviceEvent.state);
+        gbDevice.setBatteryVoltage(deviceEvent.voltage, deviceEvent.batteryIndex);
+
+        final DevicePrefs devicePrefs = GBApplication.getDevicePrefs(gbDevice);
+        final BatteryConfig batteryConfig = gbDevice.getDeviceCoordinator().getBatteryConfig(gbDevice)[deviceEvent.batteryIndex];
+
+        if (deviceEvent.level == GBDevice.BATTERY_UNKNOWN) {
+            // no level available, just "high" or "low"
+            GB.removeBatteryFullNotification(context);
+            if (devicePrefs.getBatteryNotifyLowEnabled(batteryConfig) && BatteryState.BATTERY_LOW.equals(deviceEvent.state)) {
+                GB.updateBatteryLowNotification(context.getString(R.string.notif_battery_low, gbDevice.getAliasOrName()),
+                        deviceEvent.extendedInfoAvailable() ?
+                                context.getString(R.string.notif_battery_low_extended, gbDevice.getAliasOrName(),
+                                        context.getString(R.string.notif_battery_low_bigtext_last_charge_time, DateFormat.getDateTimeInstance().format(deviceEvent.lastChargeTime.getTime())) +
+                                        context.getString(R.string.notif_battery_low_bigtext_number_of_charges, String.valueOf(deviceEvent.numCharges)))
+                                : ""
+                        , context);
+            } else if (devicePrefs.getBatteryNotifyFullEnabled(batteryConfig) && BatteryState.BATTERY_CHARGING_FULL.equals(deviceEvent.state)) {
+                GB.removeBatteryLowNotification(context);
+                GB.updateBatteryFullNotification(context.getString(R.string.notif_battery_full, gbDevice.getAliasOrName()), "", context);
+            } else {
+                GB.removeBatteryLowNotification(context);
+                GB.removeBatteryFullNotification(context);
+            }
+        } else {
+            createStoreTask("Storing battery data", context, deviceEvent).execute();
+
+            final boolean batteryNotifyLowEnabled = devicePrefs.getBatteryNotifyLowEnabled(batteryConfig);
+            final boolean isBatteryLow = deviceEvent.level <= devicePrefs.getBatteryNotifyLowThreshold(batteryConfig) &&
+                    (BatteryState.BATTERY_LOW.equals(deviceEvent.state) || BatteryState.BATTERY_NORMAL.equals(deviceEvent.state));
+
+            final boolean batteryNotifyFullEnabled = devicePrefs.getBatteryNotifyFullEnabled(batteryConfig);
+            final boolean isBatteryFull = deviceEvent.level >= devicePrefs.getBatteryNotifyFullThreshold(batteryConfig) &&
+                    (BatteryState.BATTERY_CHARGING.equals(deviceEvent.state) || BatteryState.BATTERY_CHARGING_FULL.equals(deviceEvent.state));
+
+            //show the notification if the battery level is below threshold and only if not connected to charger
+            if (batteryNotifyLowEnabled && isBatteryLow) {
+                GB.removeBatteryFullNotification(context);
+                GB.updateBatteryLowNotification(context.getString(R.string.notif_battery_low_percent, gbDevice.getAliasOrName(), String.valueOf(deviceEvent.level)),
+                        deviceEvent.extendedInfoAvailable() ?
+                                context.getString(R.string.notif_battery_low_percent, gbDevice.getAliasOrName(), String.valueOf(deviceEvent.level)) + "\n" +
+                                        context.getString(R.string.notif_battery_low_bigtext_last_charge_time, DateFormat.getDateTimeInstance().format(deviceEvent.lastChargeTime.getTime())) +
+                                        context.getString(R.string.notif_battery_low_bigtext_number_of_charges, String.valueOf(deviceEvent.numCharges))
+                                : ""
+                        , context);
+            } else if (batteryNotifyFullEnabled && isBatteryFull) {
+                GB.removeBatteryLowNotification(context);
+                GB.updateBatteryFullNotification(context.getString(R.string.notif_battery_full, gbDevice.getAliasOrName()), "", context);
+            } else {
+                GB.removeBatteryLowNotification(context);
+                GB.removeBatteryFullNotification(context);
+            }
+        }
+
+        gbDevice.sendDeviceUpdateIntent(context);
+    }
+
+    /**
+     * Helper method to run specific actions configured in the device preferences, upon wear state
+     * or awake/asleep events.
+     *
+     * @param actions
+     * @param message
+     */
+    private void handleDeviceAction(Set<String> actions, String message) {
+        if (actions.isEmpty()) {
+            return;
+        }
+
+        LOG.debug("Handing device actions: {}", TextUtils.join(",", actions));
+
+        final String actionBroadcast = getContext().getString(R.string.pref_device_action_broadcast_value);
+        final String actionFitnessControlStart = getContext().getString(R.string.pref_device_action_fitness_app_control_start_value);
+        final String actionFitnessControlStop = getContext().getString(R.string.pref_device_action_fitness_app_control_stop_value);
+        final String actionFitnessControlToggle = getContext().getString(R.string.pref_device_action_fitness_app_control_toggle_value);
+        final String actionMediaPlay = getContext().getString(R.string.pref_media_play_value);
+        final String actionMediaPause = getContext().getString(R.string.pref_media_pause_value);
+        final String actionMediaPlayPause = getContext().getString(R.string.pref_media_playpause_value);
+        final String actionDndOff = getContext().getString(R.string.pref_device_action_dnd_off_value);
+        final String actionDndpriority = getContext().getString(R.string.pref_device_action_dnd_priority_value);
+        final String actionDndAlarms = getContext().getString(R.string.pref_device_action_dnd_alarms_value);
+        final String actionDndOn = getContext().getString(R.string.pref_device_action_dnd_on_value);
+
+        if (actions.contains(actionBroadcast)) {
+            if (message != null) {
+                Intent in = new Intent();
+                in.setAction(message);
+                LOG.info("Sending broadcast {}", message);
+                getContext().getApplicationContext().sendBroadcast(in);
+            }
+        }
+
+        if (actions.contains(actionFitnessControlStart)) {
+            OpenTracksController.startRecording(getContext());
+        } else if (actions.contains(actionFitnessControlStop)) {
+            OpenTracksController.stopRecording(getContext());
+        } else if (actions.contains(actionFitnessControlToggle)) {
+            OpenTracksController.toggleRecording(getContext());
+        }
+
+        final String mediaAction;
+        if (actions.contains(actionMediaPlayPause)) {
+            mediaAction = actionMediaPlayPause;
+        } else if (actions.contains(actionMediaPause)) {
+            mediaAction = actionMediaPause;
+        } else if (actions.contains(actionMediaPlay)) {
+            mediaAction = actionMediaPlay;
+        } else {
+            mediaAction = null;
+        }
+
+        if (mediaAction != null) {
+            GBDeviceEventMusicControl deviceEventMusicControl = new GBDeviceEventMusicControl();
+            deviceEventMusicControl.event = GBDeviceEventMusicControl.Event.valueOf(mediaAction);
+            evaluateGBDeviceEvent(deviceEventMusicControl);
+        }
+
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+            final NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+
+            final int interruptionFilter;
+            if (actions.contains(actionDndOff)) {
+                interruptionFilter = NotificationManager.INTERRUPTION_FILTER_ALL;
+            } else if (actions.contains(actionDndpriority)) {
+                interruptionFilter = NotificationManager.INTERRUPTION_FILTER_PRIORITY;
+            } else if (actions.contains(actionDndAlarms)) {
+                interruptionFilter = NotificationManager.INTERRUPTION_FILTER_ALARMS;
+            } else if (actions.contains(actionDndOn)) {
+                interruptionFilter = NotificationManager.INTERRUPTION_FILTER_NONE;
+            } else {
+                interruptionFilter = NotificationManager.INTERRUPTION_FILTER_UNKNOWN;
+            }
+
+            if (interruptionFilter != NotificationManager.INTERRUPTION_FILTER_UNKNOWN) {
+                LOG.debug("Setting do not disturb to {}", interruptionFilter);
+
+                if (!notificationManager.isNotificationPolicyAccessGranted()) {
+                    LOG.warn("Do not disturb permissions not granted");
+                }
+
+                notificationManager.setInterruptionFilter(interruptionFilter);
+            }
+        }
+    }
+
+    private void handleGBDeviceEvent(GBDeviceEventSleepStateDetection event) {
+        LOG.debug("Got SLEEP_STATE_DETECTION device event, detected sleep state = {}", event.sleepState);
+
+        if (event.sleepState == SleepState.UNKNOWN) {
+            return;
+        }
+
+        String actionPreferenceKey, messagePreferenceKey;
+        int defaultBroadcastMessageResource;
+
+        switch (event.sleepState) {
+            case AWAKE:
+                actionPreferenceKey = DeviceSettingsPreferenceConst.PREF_DEVICE_ACTION_WOKE_UP_SELECTIONS;
+                messagePreferenceKey = DeviceSettingsPreferenceConst.PREF_DEVICE_ACTION_WOKE_UP_BROADCAST;
+                defaultBroadcastMessageResource = R.string.prefs_events_forwarding_wokeup_broadcast_default_value;
+                break;
+            case ASLEEP:
+                actionPreferenceKey = DeviceSettingsPreferenceConst.PREF_DEVICE_ACTION_FELL_SLEEP_SELECTIONS;
+                messagePreferenceKey = DeviceSettingsPreferenceConst.PREF_DEVICE_ACTION_FELL_SLEEP_BROADCAST;
+                defaultBroadcastMessageResource = R.string.prefs_events_forwarding_fellsleep_broadcast_default_value;
+                break;
+            default:
+                LOG.warn("Unable to deduce action and broadcast message preference key for sleep state {}", event.sleepState);
+                return;
+        }
+
+        Set<String> actions = getDevicePrefs().getStringSet(actionPreferenceKey, Collections.emptySet());
+
+        if (actions.isEmpty()) {
+            return;
+        }
+
+        String broadcastMessage = getDevicePrefs().getString(messagePreferenceKey, context.getString(defaultBroadcastMessageResource));
+        handleDeviceAction(actions, broadcastMessage);
+    }
+
+    private void handleGBDeviceEvent(GBDeviceEventWearState event) {
+        LOG.debug("Got WEAR_STATE device event, wearingState = {}", event.wearingState);
+
+        if (event.wearingState == WearingState.UNKNOWN) {
+            LOG.warn("WEAR_STATE state is UNKNOWN, aborting further evaluation");
+            return;
+        }
+
+        if (event.wearingState != WearingState.NOT_WEARING) {
+            LOG.debug("WEAR_STATE state is not NOT_WEARING, aborting further evaluation");
+        }
+
+        Set<String> actionOnUnwear = getDevicePrefs().getStringSet(
+                DeviceSettingsPreferenceConst.PREF_DEVICE_ACTION_START_NON_WEAR_SELECTIONS,
+                Collections.emptySet()
+        );
+
+        // check if an action is set
+        if (actionOnUnwear.isEmpty()) {
+            return;
+        }
+
+        String broadcastMessage = getDevicePrefs().getString(
+                DeviceSettingsPreferenceConst.PREF_DEVICE_ACTION_START_NON_WEAR_BROADCAST,
+                getContext().getString(R.string.prefs_events_forwarding_startnonwear_broadcast_default_value)
+        );
+
+        handleDeviceAction(actionOnUnwear, broadcastMessage);
+    }
+
+    private void handleGBDeviceEvent(GBDeviceMusicData deviceEvent) {
+        Context context = getContext();
+        LOG.info("Got event for ACTION_MUSIC_DATA");
+
+        Intent intent = new Intent(MusicManagerActivity.ACTION_MUSIC_DATA);
+
+        intent.putExtra("type", deviceEvent.type);
+
+        if(deviceEvent.list != null) {
+            ArrayList<GBDeviceMusic> list = new ArrayList<>(deviceEvent.list);
+            intent.putExtra("musicList", list);
+        }
+
+        if(deviceEvent.playlists != null) {
+            ArrayList<GBDeviceMusicPlaylist> list = new ArrayList<>(deviceEvent.playlists);
+            intent.putExtra("musicPlaylist", list);
+        }
+
+        if(!TextUtils.isEmpty(deviceEvent.deviceInfo)) {
+            intent.putExtra("deviceInfo", deviceEvent.deviceInfo);
+        }
+
+        if(deviceEvent.maxMusicCount > 0) {
+            intent.putExtra("maxMusicCount", deviceEvent.maxMusicCount);
+        }
+        if(deviceEvent.maxPlaylistCount > 0) {
+            intent.putExtra("maxPlaylistCount", deviceEvent.maxPlaylistCount);
+        }
+
+        LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
+    }
+
+    private void handleGBDeviceEvent(GBDeviceMusicUpdate deviceEvent) {
+        Context context = getContext();
+        LOG.info("Got event for ACTION_MUSIC_UPDATE");
+
+        Intent intent = new Intent(MusicManagerActivity.ACTION_MUSIC_UPDATE);
+
+        intent.putExtra("success", deviceEvent.success);
+        intent.putExtra("operation", deviceEvent.operation);
+        intent.putExtra("playlistIndex", deviceEvent.playlistIndex);
+        intent.putExtra("playlistName", deviceEvent.playlistName);
+        intent.putExtra("musicIds", deviceEvent.musicIds);
+
+        LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
+    }
+
+    private StoreDataTask createStoreTask(String task, Context context, GBDeviceEventBatteryInfo deviceEvent) {
+        return new StoreDataTask(task, context, deviceEvent);
+    }
+
+    public class StoreDataTask extends DBAccess {
+        GBDeviceEventBatteryInfo deviceEvent;
+
+        public StoreDataTask(String task, Context context, GBDeviceEventBatteryInfo deviceEvent) {
+            super(task, context);
+            this.deviceEvent = deviceEvent;
+        }
+
+        @Override
+        protected void doInBackground(DBHandler handler) {
+            DaoSession daoSession = handler.getDaoSession();
+            Device device = DBHelper.getDevice(gbDevice, daoSession);
+            int ts = (int) (System.currentTimeMillis() / 1000);
+            BatteryLevel batteryLevel = new BatteryLevel();
+            batteryLevel.setTimestamp(ts);
+            batteryLevel.setBatteryIndex(deviceEvent.batteryIndex);
+            batteryLevel.setDevice(device);
+            batteryLevel.setLevel(deviceEvent.level);
+            handler.getDaoSession().getBatteryLevelDao().insert(batteryLevel);
+        }
+    }
+
+    public void handleGBDeviceEvent(GBDeviceEventDisplayMessage message) {
+        GB.log(message.message, message.severity, null);
+
+        Intent messageIntent = new Intent(GB.ACTION_DISPLAY_MESSAGE);
+        messageIntent.putExtra(GB.DISPLAY_MESSAGE_MESSAGE, message.message);
+        messageIntent.putExtra(GB.DISPLAY_MESSAGE_DURATION, message.duration);
+        messageIntent.putExtra(GB.DISPLAY_MESSAGE_SEVERITY, message.severity);
+
+        LocalBroadcastManager.getInstance(context).sendBroadcast(messageIntent);
+    }
+
+    public DevicePrefs getDevicePrefs() {
+        return GBApplication.getDevicePrefs(gbDevice);
+    }
+
+    @Override
+    public String customStringFilter(String inputString) {
+        return inputString;
+    }
+
+
+    // Empty functions following, leaving optional implementation up to child classes
+
+    /**
+     * If the device supports a "find phone" functionality, this method can
+     * be overridden and implemented by the device support class.
+     * @param start true if starting the search, false if stopping
+     */
+    @Override
+    public void onFindPhone(boolean start) {
+
+    }
+
+    /**
+     * If the device supports a "find device" functionality, this method can
+     * be overridden and implemented by the device support class.
+     * @param start true if starting the search, false if stopping
+     */
+    @Override
+    public void onFindDevice(boolean start) {
+
+    }
+
+    /**
+     * If the device supports a "set FM frequency" functionality, this method
+     * can be overridden and implemented by the device support class.
+     * @param frequency the FM frequency to set
+     */
+    @Override
+    public void onSetFmFrequency(float frequency) {
+
+    }
+
+    /**
+     * If the device supports a "set LED color" functionality, this method
+     * can be overridden and implemented by the device support class.
+     * @param color the new color, in ARGB, with alpha = 255
+     */
+    @Override
+    public void onSetLedColor(int color) {
+
+    }
+
+    /**
+     * If the device can be turned off by sending a command, this method
+     * can be overridden and implemented by the device support class.
+     */
+    @Override
+    public void onPowerOff() {
+
+    }
+
+    /**
+     * If the device has a functionality to set the phone volume, this method
+     * can be overridden and implemented by the device support class.
+     * @param volume the volume percentage (0 to 100).
+     */
+    @Override
+    public void onSetPhoneVolume(final float volume) {
+
+    }
+
+    /**
+     * Called when the phone's interruption filter or ringer mode is changed.
+     * @param ringerMode as per {@link android.media.AudioManager#getRingerMode()}
+     */
+    @Override
+    public void onChangePhoneSilentMode(int ringerMode) {
+
+    }
+
+    /**
+     * If the device can receive the GPS location from the phone, this method
+     * can be overridden and implemented by the device support class.
+     * @param location {@link android.location.Location} object containing the current GPS coordinates
+     */
+    @Override
+    public void onSetGpsLocation(Location location) {
+
+    }
+
+    /**
+     * If reminders can be set on the device, this method can be
+     * overridden and implemented by the device support class.
+     * @param reminders {@link java.util.ArrayList} containing {@link nodomain.freeyourgadget.gadgetbridge.model.Reminder} instances
+     */
+    @Override
+    public void onSetReminders(ArrayList<? extends Reminder> reminders) {
+
+    }
+
+    /**
+     * If loyalty cards can be set on the device, this method can be
+     * overridden and implemented by the device support class.
+     * @param cards {@link java.util.ArrayList} containing {@link LoyaltyCard} instances
+     */
+    @Override
+    public void onSetLoyaltyCards(ArrayList<LoyaltyCard> cards) {
+
+    }
+
+    /**
+     * If world clocks can be configured on the device, this method can be
+     * overridden and implemented by the device support class.
+     * @param clocks {@link java.util.ArrayList} containing {@link nodomain.freeyourgadget.gadgetbridge.model.WorldClock} instances
+     */
+    @Override
+    public void onSetWorldClocks(ArrayList<? extends WorldClock> clocks) {
+
+    }
+
+    /**
+     * If contacts can be configured on the device, this method can be
+     * overridden and implemented by the device support class.
+     * @param contacts {@link java.util.ArrayList} containing {@link nodomain.freeyourgadget.gadgetbridge.model.Contact} instances
+     */
+    @Override
+    public void onSetContacts(ArrayList<? extends Contact> contacts) {
+
+    }
+
+    /**
+     * If the device can receive and display notifications, this method
+     * can be overridden and implemented by the device support class.
+     * @param notificationSpec notification details
+     */
+    @Override
+    public void onNotification(NotificationSpec notificationSpec) {
+
+    }
+
+    /**
+     * If notifications can be deleted from the device, this method can be
+     * overridden and implemented by the device support class.
+     * @param id the unique notification identifier
+     */
+    @Override
+    public void onDeleteNotification(int id) {
+
+    }
+
+    /**
+     * If the time can be set on the device, this method can be
+     * overridden and implemented by the device support class.
+     */
+    @Override
+    public void onSetTime() {
+
+    }
+
+    /**
+     * If alarms can be set on the device, this method can be
+     * overridden and implemented by the device support class.
+     * @param alarms {@link java.util.ArrayList} containing {@link nodomain.freeyourgadget.gadgetbridge.model.Alarm} instances
+     */
+    @Override
+    public void onSetAlarms(ArrayList<? extends Alarm> alarms) {
+
+    }
+
+    /**
+     * If the device can receive and show or handle phone call details, this
+     * method can be overridden and implemented by the device support class.
+     * @param callSpec the call state details
+     */
+    @Override
+    public void onSetCallState(CallSpec callSpec) {
+
+    }
+
+    /**
+     * If the device has a "canned messages" functionality, this method
+     * can be overridden and implemented by the device support class.
+     * @param cannedMessagesSpec the canned messages to send to the device
+     */
+    @Override
+    public void onSetCannedMessages(CannedMessagesSpec cannedMessagesSpec) {
+
+    }
+
+    /**
+     * If the music play state can be set on the device, this method
+     * can be overridden and implemented by the device support class.
+     * @param stateSpec the current state of music playback
+     */
+    @Override
+    public void onSetMusicState(MusicStateSpec stateSpec) {
+
+    }
+
+    /**
+     * If the music information can be shown on the device, this method can be
+     * overridden and implemented by the device support class.
+     * @param musicSpec the current music information, like track name and artist
+     */
+    @Override
+    public void onSetMusicInfo(MusicSpec musicSpec) {
+
+    }
+
+    /**
+     * If apps can be installed on the device, this method can be
+     * overridden and implemented by the device support class.
+     * @param uri reference to a watch app file
+     */
+    @Override
+    public void onInstallApp(Uri uri) {
+
+    }
+
+    /**
+     * If the list of apps on the device can be retrieved, this method
+     * can be overridden and implemented by the device support class.
+     */
+    @Override
+    public void onAppInfoReq() {
+
+    }
+
+    /**
+     * If the device supports starting an app with a command, this method
+     * can be overridden and implemented by the device support class.
+     * @param uuid the Gadgetbridge internal UUID of the app
+     * @param start true to start, false to stop the app (if supported)
+     */
+    @Override
+    public void onAppStart(UUID uuid, boolean start) {
+
+    }
+
+    /**
+     * If apps can be downloaded from the device, this method can be
+     * overridden and implemented by the device support class.
+     * @param uuid the Gadgetbridge internal UUID of the app
+     */
+    @Override
+    public void onAppDownload(UUID uuid) {
+
+    }
+
+    /**
+     * If apps on the device can be deleted with a command, this method
+     * can be overridden and implemented by the device support class.
+     * @param uuid the Gadgetbridge internal UUID of the app
+     */
+    @Override
+    public void onAppDelete(UUID uuid) {
+
+    }
+
+    /**
+     * If apps on the device can be configured, this method can be
+     * overridden and implemented by the device support class.
+     * @param appUuid the Gadgetbridge internal UUID of the app
+     * @param config the configuration of the app
+     * @param id
+     */
+    @Override
+    public void onAppConfiguration(UUID appUuid, String config, Integer id) {
+
+    }
+
+    /**
+     * If apps on the device can be reordered, this method can be
+     * overridden and implemented by the device support class.
+     * @param uuids array of Gadgetbridge internal UUIDs of the apps
+     */
+    @Override
+    public void onAppReorder(UUID[] uuids) {
+
+    }
+
+    /**
+     * If recorded data can be fetched from the device, this method
+     * can be overridden and implemented by the device support class.
+     * @param dataTypes which data types to fetch
+     */
+    @Override
+    public void onFetchRecordedData(int dataTypes) {
+
+    }
+
+    /**
+     * If a device can be reset with a command, this method can be
+     * overridden and implemented by the device support class.
+     * @param flags can be used to pass flags with the reset command
+     */
+    @Override
+    public void onReset(int flags) {
+
+    }
+
+    /**
+     * If the device can perform a heart rate measurement on request, this
+     * method can be overridden and implemented by the device support class.
+     */
+    @Override
+    public void onHeartRateTest() {
+
+    }
+
+    /**
+     * If the device has the functionality to enable/disable realtime heart rate measurement,
+     * this method can be overridden and implemented by the device support class.
+     * @param enable true to enable, false to disable realtime heart rate measurement
+     */
+    @Override
+    public void onEnableRealtimeHeartRateMeasurement(boolean enable) {
+
+    }
+
+    /**
+     * If the device has the functionality to enable/disable realtime steps information,
+     * this method can be overridden and implemented by the device support class.
+     * @param enable true to enable, false to disable realtime steps
+     */
+    @Override
+    public void onEnableRealtimeSteps(boolean enable) {
+
+    }
+
+    /**
+     * If the device has a functionality to enable constant vibration, this
+     * method can be overridden and implemented by the device support class.
+     * @param integer the vibration intensity
+     */
+    @Override
+    public void onSetConstantVibration(int integer) {
+
+    }
+
+    /**
+     * If the device supports taking screenshots of the screen, this method can
+     * be overridden and implemented by the device support class.
+     */
+    @Override
+    public void onScreenshotReq() {
+
+    }
+
+    /**
+     * If the device has a toggle to enable the use of heart rate for sleep detection,
+     * this method can be overridden and implemented by the device support class.
+     * @param enable true to enable, false to disable using heart rate for sleep detection
+     */
+    @Override
+    public void onEnableHeartRateSleepSupport(boolean enable) {
+
+    }
+
+    /**
+     * If the heart rate measurement interval can be changed on the device,
+     * this method can be overridden and implemented by the device support class.
+     * @param seconds the interval to configure on the device
+     */
+    @Override
+    public void onSetHeartRateMeasurementInterval(int seconds) {
+
+    }
+
+    /**
+     * If calendar events can be sent to the device, this method can be
+     * overridden and implemented by the device support class.
+     * @param calendarEventSpec calendar event details
+     */
+    @Override
+    public void onAddCalendarEvent(CalendarEventSpec calendarEventSpec) {
+
+    }
+
+    /**
+     * If calendar events can be deleted from the device, this method can
+     * be overridden and implemented by the device support class.
+     * @param type type of calendar event
+     * @param id id of calendar event
+     */
+    @Override
+    public void onDeleteCalendarEvent(byte type, long id) {
+
+    }
+
+    /**
+     * If configuration options can be set on the device, this method
+     * can be overridden and implemented by the device support class.
+     * @param config the device specific option to set on the device
+     */
+    @Override
+    public void onSendConfiguration(String config) {
+
+    }
+
+    /**
+     * If the configuration can be retrieved from the device, this method
+     * can be overridden and implemented by the device support class.
+     * @param config the device specific option to get from the device
+     */
+    @Override
+    public void onReadConfiguration(String config) {
+
+    }
+
+    /**
+     * If the device can receive weather information, this method can be
+     * overridden and implemented by the device support class. It's guaranteed
+     * that there is always at least one weatherSpec, with the first being the
+     * primary weather (not necessarily current location).
+     * @param weatherSpecs weather information
+     */
+    @Override
+    public void onSendWeather(ArrayList<WeatherSpec> weatherSpecs) {
+
+    }
+
+    /**
+     * For testing new features, this method can be overridden and
+     * implemented by the device support class.
+     * It's called by clicking the "test new functionality" button
+     * in the Debug menu.
+     */
+    @Override
+    public void onTestNewFunction() {
+
+    }
+
+    @Override
+    public void onSetNavigationInfo(NavigationInfoSpec navigationInfoSpec) {
+
+    }
+
+    @Override
+    public void onSleepAsAndroidAction(String action, Bundle extras) {
+
+    }
+
+    @Override
+    public void onCameraStatusChange(GBDeviceEventCameraRemote.Event event, String filename) {}
+
+    @Override
+    public void onMusicListReq() {}
+
+    @Override
+    public void onMusicOperation(int operation, int playlistIndex, String playlistName, ArrayList<Integer> musicIds) {}
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/AbstractHeadphoneDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/AbstractHeadphoneDeviceSupport.java
new file mode 100644
index 0000000..97f1587
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/AbstractHeadphoneDeviceSupport.java
@@ -0,0 +1,65 @@
+/*  Copyright (C) 2024 Arjan Schrijver, Daniele Gobbetti
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service;
+
+import android.bluetooth.BluetoothAdapter;
+import android.content.Context;
+
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
+import nodomain.freeyourgadget.gadgetbridge.service.serial.AbstractSerialDeviceSupport;
+
+public abstract class AbstractHeadphoneDeviceSupport extends AbstractSerialDeviceSupport implements HeadphoneHelper.Callback {
+
+    private HeadphoneHelper headphoneHelper;
+
+    @Override
+    public void onSetCallState(CallSpec callSpec) {
+        headphoneHelper.onSetCallState(callSpec);
+    }
+
+    @Override
+    public void onNotification(NotificationSpec notificationSpec) {
+        headphoneHelper.onNotification(notificationSpec);
+    }
+
+    @Override
+    public void setContext(GBDevice gbDevice, BluetoothAdapter btAdapter, Context context) {
+        super.setContext(gbDevice, btAdapter, context);
+        headphoneHelper = new HeadphoneHelper(getContext(), getDevice(), this);
+    }
+
+    @Override
+    public boolean connect() {
+        getDeviceIOThread().start();
+        return true;
+    }
+
+    @Override
+    public void onSendConfiguration(String config) {
+        if (!headphoneHelper.onSendConfiguration(config))
+            super.onSendConfiguration(config);
+    }
+
+    @Override
+    public void dispose() {
+        if (headphoneHelper != null)
+            headphoneHelper.dispose();
+        super.dispose();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java
new file mode 100644
index 0000000..bcdf3c7
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java
@@ -0,0 +1,1603 @@
+/*  Copyright (C) 2015-2024 Andreas B枚hler, Andreas Shimokawa, Arjan
+    Schrijver, Avamander, Carsten Pfeiffer, Daniel Dakhno, Daniele Gobbetti,
+    Daniel Hauck, Davis Mosenkovs, Dikay900, Dmitriy Bogdanov, Frank Slezak,
+    Gabriele Monaco, Gordon Williams, ivanovlev, Joa虄o Paulo Barraca, Jos茅
+    Rebelo, Julien Pivotto, Kasha, keeshii, Martin, Matthieu Baerts, mvn23,
+    NekoBox, Nephiel, Petr Van臎k, Sebastian Kranz, Sergey Trofimov, Steffen
+    Liebergeld, Taavi Eom盲e, TylerWilliamson, Uwe Hermann, Yoran Vulker
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service;
+
+import android.Manifest;
+import android.annotation.SuppressLint;
+import android.app.ActivityManager;
+import android.app.Service;
+import android.bluetooth.BluetoothDevice;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
+import android.content.pm.ServiceInfo;
+import android.location.Location;
+import android.media.AudioManager;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.widget.Toast;
+
+import androidx.annotation.Nullable;
+import androidx.core.content.ContextCompat;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.GBException;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.HeartRateUtils;
+import nodomain.freeyourgadget.gadgetbridge.capabilities.loyaltycards.LoyaltyCard;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventCameraRemote;
+import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.externalevents.AlarmClockReceiver;
+import nodomain.freeyourgadget.gadgetbridge.externalevents.AlarmReceiver;
+import nodomain.freeyourgadget.gadgetbridge.externalevents.BluetoothConnectReceiver;
+import nodomain.freeyourgadget.gadgetbridge.externalevents.BluetoothPairingRequestReceiver;
+import nodomain.freeyourgadget.gadgetbridge.externalevents.CMWeatherReceiver;
+import nodomain.freeyourgadget.gadgetbridge.externalevents.CalendarReceiver;
+import nodomain.freeyourgadget.gadgetbridge.externalevents.DeviceSettingsReceiver;
+import nodomain.freeyourgadget.gadgetbridge.externalevents.GenericWeatherReceiver;
+import nodomain.freeyourgadget.gadgetbridge.externalevents.IntentApiReceiver;
+import nodomain.freeyourgadget.gadgetbridge.externalevents.LineageOsWeatherReceiver;
+import nodomain.freeyourgadget.gadgetbridge.externalevents.MusicPlaybackReceiver;
+import nodomain.freeyourgadget.gadgetbridge.externalevents.OmniJawsObserver;
+import nodomain.freeyourgadget.gadgetbridge.externalevents.OsmandEventReceiver;
+import nodomain.freeyourgadget.gadgetbridge.externalevents.PebbleReceiver;
+import nodomain.freeyourgadget.gadgetbridge.externalevents.PhoneCallReceiver;
+import nodomain.freeyourgadget.gadgetbridge.externalevents.SMSReceiver;
+import nodomain.freeyourgadget.gadgetbridge.externalevents.SilentModeReceiver;
+import nodomain.freeyourgadget.gadgetbridge.externalevents.TimeChangeReceiver;
+import nodomain.freeyourgadget.gadgetbridge.externalevents.TinyWeatherForecastGermanyReceiver;
+import nodomain.freeyourgadget.gadgetbridge.externalevents.gps.GBLocationService;
+import nodomain.freeyourgadget.gadgetbridge.externalevents.sleepasandroid.SleepAsAndroidReceiver;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceService;
+import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
+import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.Contact;
+import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.NavigationInfoSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.NotificationType;
+import nodomain.freeyourgadget.gadgetbridge.model.Reminder;
+import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.WorldClock;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BLEScanService;
+import nodomain.freeyourgadget.gadgetbridge.service.receivers.AutoConnectIntervalReceiver;
+import nodomain.freeyourgadget.gadgetbridge.service.receivers.GBAutoFetchReceiver;
+import nodomain.freeyourgadget.gadgetbridge.util.EmojiConverter;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+import nodomain.freeyourgadget.gadgetbridge.util.GBPrefs;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+import nodomain.freeyourgadget.gadgetbridge.util.language.LanguageUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.language.Transliterator;
+
+import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.*;
+
+public class DeviceCommunicationService extends Service implements SharedPreferences.OnSharedPreferenceChangeListener {
+    public static class DeviceStruct{
+        private GBDevice device;
+        private DeviceCoordinator coordinator;
+        private DeviceSupport deviceSupport;
+
+        public GBDevice getDevice() {
+            return device;
+        }
+
+        public void setDevice(GBDevice device) {
+            this.device = device;
+        }
+
+        public DeviceCoordinator getCoordinator() {
+            return coordinator;
+        }
+
+        public void setCoordinator(DeviceCoordinator coordinator) {
+            this.coordinator = coordinator;
+        }
+
+        public DeviceSupport getDeviceSupport() {
+            return deviceSupport;
+        }
+
+        public void setDeviceSupport(DeviceSupport deviceSupport) {
+            this.deviceSupport = deviceSupport;
+        }
+    }
+
+    private static class FeatureSet {
+        private boolean supportsWeather = false;
+        private boolean supportsActivityDataFetching = false;
+        private boolean supportsCalendarEvents = false;
+        private boolean supportsMusicInfo = false;
+        private boolean supportsNavigation = false;
+
+        private boolean supportsSleepAsAndroid = false;
+
+        public boolean supportsWeather() {
+            return supportsWeather;
+        }
+
+        public void setSupportsWeather(boolean supportsWeather) {
+            this.supportsWeather = supportsWeather;
+        }
+
+        public boolean supportsActivityDataFetching() {
+            return supportsActivityDataFetching;
+        }
+
+        public void setSupportsActivityDataFetching(boolean supportsActivityDataFetching) {
+            this.supportsActivityDataFetching = supportsActivityDataFetching;
+        }
+
+        public boolean supportsCalendarEvents() {
+            return supportsCalendarEvents;
+        }
+
+        public void setSupportsCalendarEvents(boolean supportsCalendarEvents) {
+            this.supportsCalendarEvents = supportsCalendarEvents;
+        }
+
+        public boolean supportsMusicInfo() {
+            return supportsMusicInfo;
+        }
+
+        public void setSupportsMusicInfo(boolean supportsMusicInfo) {
+            this.supportsMusicInfo = supportsMusicInfo;
+        }
+
+        public boolean supportsNavigation() {
+            return supportsNavigation;
+        }
+
+        public void setSupportsNavigation(boolean supportsNavigation) {
+            this.supportsNavigation = supportsNavigation;
+        }
+
+        public boolean supportsSleepAsAndroid() { return supportsSleepAsAndroid; }
+
+        public void setSupportsSleepAsAndroid(boolean supportsSleepAsAndroid) {
+            this.supportsSleepAsAndroid = supportsSleepAsAndroid;
+        }
+
+        public void logicalOr(DeviceCoordinator operand){
+            if(operand.supportsCalendarEvents()){
+                setSupportsCalendarEvents(true);
+            }
+            if(operand.supportsWeather()){
+                setSupportsWeather(true);
+            }
+            if(operand.supportsActivityDataFetching()){
+                setSupportsActivityDataFetching(true);
+            }
+            if(operand.supportsMusicInfo()){
+                setSupportsMusicInfo(true);
+            }
+            if(operand.supportsNavigation()){
+                setSupportsNavigation(true);
+            }
+            if (operand.supportsSleepAsAndroid()) {
+                setSupportsSleepAsAndroid(true);
+            }
+        }
+    }
+
+    public static class DeviceNotFoundException extends GBException{
+        private final String address;
+
+        public DeviceNotFoundException(GBDevice device) {
+            this.address = device.getAddress();
+        }
+
+        public DeviceNotFoundException(String address) {
+            this.address = address;
+        }
+
+        @Nullable
+        @Override
+        public String getMessage() {
+            return String.format("device %s not found cached", address);
+        }
+    }
+
+    private static final Logger LOG = LoggerFactory.getLogger(DeviceCommunicationService.class);
+    @SuppressLint("StaticFieldLeak") // only used for test cases
+    private static DeviceSupportFactory DEVICE_SUPPORT_FACTORY = null;
+
+    private DeviceSupportFactory mFactory;
+    private final ArrayList<DeviceStruct> deviceStructs = new ArrayList<>(1);
+    private final HashMap<String, ArrayList<Intent>> cachedNotifications = new HashMap<>();
+
+    private PhoneCallReceiver mPhoneCallReceiver = null;
+    private SMSReceiver mSMSReceiver = null;
+    private PebbleReceiver mPebbleReceiver = null;
+    private MusicPlaybackReceiver mMusicPlaybackReceiver = null;
+    private TimeChangeReceiver mTimeChangeReceiver = null;
+    private BluetoothConnectReceiver mBlueToothConnectReceiver = null;
+    private BluetoothPairingRequestReceiver mBlueToothPairingRequestReceiver = null;
+    private AlarmClockReceiver mAlarmClockReceiver = null;
+    private SilentModeReceiver mSilentModeReceiver = null;
+    private GBAutoFetchReceiver mGBAutoFetchReceiver = null;
+    private AutoConnectIntervalReceiver mAutoConnectInvervalReceiver = null;
+
+    private AlarmReceiver mAlarmReceiver = null;
+    private final List<CalendarReceiver> mCalendarReceiver = new ArrayList<>();
+    private CMWeatherReceiver mCMWeatherReceiver = null;
+    private LineageOsWeatherReceiver mLineageOsWeatherReceiver = null;
+    private TinyWeatherForecastGermanyReceiver mTinyWeatherForecastGermanyReceiver = null;
+    private GenericWeatherReceiver mGenericWeatherReceiver = null;
+    private OmniJawsObserver mOmniJawsObserver = null;
+    private final DeviceSettingsReceiver deviceSettingsReceiver = new DeviceSettingsReceiver();
+    private final IntentApiReceiver intentApiReceiver = new IntentApiReceiver();
+    private GBLocationService locationService = null;
+
+    private OsmandEventReceiver mOsmandAidlHelper = null;
+
+    private SleepAsAndroidReceiver mSleepAsAndroidReceiver = null;
+
+    private HashMap<String, Long> deviceLastScannedTimestamps = new HashMap<>();
+
+    private final String[] mMusicActions = {
+            "com.android.music.metachanged",
+            "com.android.music.playstatechanged",
+            "com.android.music.queuechanged",
+            "com.android.music.playbackcomplete",
+            "net.sourceforge.subsonic.androidapp.EVENT_META_CHANGED",
+            "com.maxmpz.audioplayer.TPOS_SYNC",
+            "com.maxmpz.audioplayer.STATUS_CHANGED",
+            "com.maxmpz.audioplayer.PLAYING_MODE_CHANGED",
+            "com.spotify.music.metadatachanged",
+            "com.spotify.music.playbackstatechanged"
+    };
+
+    private final int NOTIFICATIONS_CACHE_MAX = 10;  // maximum amount of notifications to cache per device while disconnected
+    private boolean allowBluetoothIntentApi = false;
+    private boolean reconnectViaScan = GBPrefs.RECONNECT_SCAN_DEFAULT;
+
+    private final String API_LEGACY_COMMAND_BLUETOOTH_CONNECT = "nodomain.freeyourgadget.gadgetbridge.BLUETOOTH_CONNECT";
+    private final String API_LEGACY_COMMAND_BLUETOOTH_DISCONNECT = "nodomain.freeyourgadget.gadgetbridge.BLUETOOTH_DISCONNECT";
+    private final String API_LEGACY_ACTION_DEVICE_CONNECTED = "nodomain.freeyourgadget.gadgetbridge.BLUETOOTH_CONNECTED";
+    private final String API_LEGACY_ACTION_DEVICE_SCANNED = "nodomain.freeyourgadget.gadgetbridge.BLUETOOTH_SCANNED";
+
+    private void sendDeviceAPIBroadcast(String address, String action){
+        if(!allowBluetoothIntentApi){
+            LOG.debug("not sending API event due to settings");
+            return;
+        }
+        Intent intent = new Intent(action);
+        intent.putExtra("EXTRA_DEVICE_ADDRESS", address);
+
+        sendBroadcast(intent);
+    }
+
+    private void sendDeviceConnectedBroadcast(String address){
+        sendDeviceAPIBroadcast(address, API_LEGACY_ACTION_DEVICE_CONNECTED);
+    }
+
+    BroadcastReceiver bluetoothCommandReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (!allowBluetoothIntentApi){
+                GB.log("Connection API not allowed in settings", GB.ERROR, null);
+                return;
+            }
+            Bundle extras = intent.getExtras();
+            if (extras == null) {
+                GB.log("no extras provided in Intent", GB.ERROR, null);
+                return;
+            }
+            final String action = intent.getAction();
+            if (action == null) {
+                GB.log("Action for bluetooth command is null", GB.ERROR, null);
+                return;
+            }
+            String address = extras.getString("EXTRA_DEVICE_ADDRESS", "");
+            if (address.isEmpty()){
+                GB.log("no bluetooth address provided in Intent", GB.ERROR, null);
+                return;
+            }
+            GBDevice targetDevice = GBApplication.app()
+                    .getDeviceManager()
+                    .getDeviceByAddress(address);
+
+            if (targetDevice == null){
+                GB.log(String.format("device %s not registered", address), GB.ERROR, null);
+                return;
+            }
+
+            switch (action) {
+                case API_LEGACY_COMMAND_BLUETOOTH_CONNECT:
+                    if (isDeviceConnected(address)){
+                        GB.log(String.format("device %s already connected", address), GB.INFO, null);
+                        sendDeviceConnectedBroadcast(address);
+
+                        return;
+                    }
+
+                    GB.log(String.format("connecting to %s", address), GB.INFO, null);
+
+                    GBApplication.deviceService(targetDevice).connect();
+                    break;
+                case API_LEGACY_COMMAND_BLUETOOTH_DISCONNECT:
+                    GB.log(String.format("disconnecting from %s", address), GB.INFO, null);
+
+                    GBApplication.deviceService(targetDevice).disconnect();
+                    break;
+            }
+        }
+    };
+
+    /**
+     * For testing!
+     *
+     * @param factory
+     */
+    @SuppressWarnings("JavaDoc")
+    public static void setDeviceSupportFactory(DeviceSupportFactory factory) {
+        DEVICE_SUPPORT_FACTORY = factory;
+    }
+
+    public static boolean isRunning(final Context context) {
+        final ActivityManager manager = (ActivityManager) context.getSystemService(ACTIVITY_SERVICE);
+        if (manager == null) {
+            return false;
+        }
+        for (ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
+            if (DeviceCommunicationService.class.getName().equals(service.service.getClassName())) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public DeviceCommunicationService() {
+
+    }
+
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            if(GBDevice.ACTION_DEVICE_CHANGED.equals(action)){
+                GBDevice device = intent.getParcelableExtra(GBDevice.EXTRA_DEVICE);
+
+                // create a new instance of the changed devices coordinator, in case it's capabilities changed
+                DeviceStruct cachedStruct = getDeviceStructOrNull(device);
+                if(cachedStruct != null) {
+                    cachedStruct.setDevice(device);
+                    DeviceCoordinator newCoordinator = device.getDeviceCoordinator();
+                    cachedStruct.setCoordinator(newCoordinator);
+                }
+                updateReceiversState();
+
+                GBDevice.DeviceUpdateSubject subject = (GBDevice.DeviceUpdateSubject) intent.getSerializableExtra(GBDevice.EXTRA_UPDATE_SUBJECT);
+
+                if(subject == GBDevice.DeviceUpdateSubject.DEVICE_STATE && device.isInitialized()){
+                    sendDeviceConnectedBroadcast(device.getAddress());
+                    sendCachedNotifications(device);
+                }else if(subject == GBDevice.DeviceUpdateSubject.DEVICE_STATE && (device.getState() == GBDevice.State.SCANNED)){
+                    sendDeviceAPIBroadcast(device.getAddress(), API_LEGACY_ACTION_DEVICE_SCANNED);
+                }
+            }else if(BLEScanService.EVENT_DEVICE_FOUND.equals(action)){
+                String deviceAddress = intent.getStringExtra(BLEScanService.EXTRA_DEVICE_ADDRESS);
+
+                GBDevice target = GBApplication
+                        .app()
+                        .getDeviceManager()
+                        .getDeviceByAddress(deviceAddress);
+
+                if(target == null){
+                    LOG.error("onReceive: device not found");
+                    return;
+                }
+
+                if(!target.getDeviceCoordinator().isConnectable()){
+                    int actualRSSI = intent.getIntExtra(BLEScanService.EXTRA_RSSI, 0);
+                    Prefs prefs = new Prefs(
+                            GBApplication.getDeviceSpecificSharedPrefs(target.getAddress())
+                    );
+                    long timeoutSeconds = prefs.getLong("devicesetting_scannable_debounce", 60);
+                    long minimumUnseenSeconds = prefs.getLong("devicesetting_scannable_unseen", 0);
+                    int thresholdRSSI = prefs.getInt("devicesetting_scannable_rssi", -100);
+
+                    if(actualRSSI < thresholdRSSI){
+                        LOG.debug("ignoring {} since RSSI is too low ({} < {})", deviceAddress, actualRSSI, thresholdRSSI);
+                        return;
+                    }
+
+                    Long lastSeenTimestamp = deviceLastScannedTimestamps.get(deviceAddress);
+                    deviceLastScannedTimestamps.put(deviceAddress, System.currentTimeMillis());
+
+                    if(lastSeenTimestamp != null){
+                        long secondsSince = (System.currentTimeMillis() - lastSeenTimestamp) / 1000;
+                        if(secondsSince < minimumUnseenSeconds){
+                            LOG.debug("ignoring {}, since only {} seconds passed (< {})", deviceAddress, secondsSince, minimumUnseenSeconds);
+                            return;
+                        }
+                    }
+
+                    target.setState(GBDevice.State.SCANNED);
+                    target.sendDeviceUpdateIntent(DeviceCommunicationService.this, GBDevice.DeviceUpdateSubject.DEVICE_STATE);
+                    new Handler().postDelayed(() -> {
+                        if(target.getState() != GBDevice.State.SCANNED){
+                            return;
+                        }
+                        deviceLastScannedTimestamps.put(target.getAddress(), System.currentTimeMillis());
+                        target.setState(GBDevice.State.WAITING_FOR_SCAN);
+                        target.sendDeviceUpdateIntent(DeviceCommunicationService.this, GBDevice.DeviceUpdateSubject.DEVICE_STATE);
+                    }, timeoutSeconds * 1000);
+                    return;
+                }
+
+                connectToDevice(target);
+            }
+        }
+    };
+
+    private void updateReceiversState(){
+        boolean enableReceivers = false;
+        boolean anyDeviceInitialized = false;
+        List <GBDevice> devicesWithCalendar = new ArrayList<>();
+
+        FeatureSet features = new FeatureSet();
+
+        for(DeviceStruct struct: deviceStructs){
+            DeviceSupport deviceSupport = struct.getDeviceSupport();
+            if((deviceSupport != null && deviceSupport.useAutoConnect()) || isDeviceInitialized(struct.getDevice())){
+                enableReceivers = true;
+            }
+            if(isDeviceInitialized(struct.getDevice())){
+                anyDeviceInitialized = true;
+            }
+
+            DeviceCoordinator coordinator = struct.getCoordinator();
+            if(coordinator != null){
+                features.logicalOr(coordinator);
+                if (coordinator.supportsCalendarEvents()){
+                    devicesWithCalendar.add(struct.getDevice());
+                }
+            }
+        }
+        setReceiversEnableState(enableReceivers, anyDeviceInitialized, features, devicesWithCalendar);
+    }
+
+    private void registerInternalReceivers(){
+        IntentFilter localFilter = new IntentFilter();
+        localFilter.addAction(GBDevice.ACTION_DEVICE_CHANGED);
+        localFilter.addAction(BLEScanService.EVENT_DEVICE_FOUND);
+        LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, localFilter);
+    }
+
+    private void registerExternalReceivers(){
+        mBlueToothConnectReceiver = new BluetoothConnectReceiver(this);
+        ContextCompat.registerReceiver(this, mBlueToothConnectReceiver, new IntentFilter(BluetoothDevice.ACTION_ACL_CONNECTED), ContextCompat.RECEIVER_EXPORTED);
+
+        mAutoConnectInvervalReceiver= new AutoConnectIntervalReceiver(this);
+        ContextCompat.registerReceiver(this, mAutoConnectInvervalReceiver, new IntentFilter("GB_RECONNECT"), ContextCompat.RECEIVER_EXPORTED);
+
+        IntentFilter bluetoothCommandFilter = new IntentFilter();
+        bluetoothCommandFilter.addAction(API_LEGACY_COMMAND_BLUETOOTH_CONNECT);
+        bluetoothCommandFilter.addAction(API_LEGACY_COMMAND_BLUETOOTH_DISCONNECT);
+        ContextCompat.registerReceiver(this, bluetoothCommandReceiver, bluetoothCommandFilter, ContextCompat.RECEIVER_EXPORTED);
+
+        final IntentFilter deviceSettingsIntentFilter = new IntentFilter();
+        deviceSettingsIntentFilter.addAction(DeviceSettingsReceiver.COMMAND);
+        ContextCompat.registerReceiver(this, deviceSettingsReceiver, deviceSettingsIntentFilter, ContextCompat.RECEIVER_EXPORTED);
+
+        ContextCompat.registerReceiver(this, intentApiReceiver, intentApiReceiver.buildFilter(), ContextCompat.RECEIVER_EXPORTED);
+    }
+
+    @Override
+    public void onCreate() {
+        LOG.debug("DeviceCommunicationService is being created");
+        super.onCreate();
+        mFactory = getDeviceSupportFactory();
+
+        registerInternalReceivers();
+        registerExternalReceivers();
+
+        if (hasPrefs()) {
+            getPrefs().getPreferences().registerOnSharedPreferenceChangeListener(this);
+            allowBluetoothIntentApi = getPrefs().getBoolean(GBPrefs.PREF_ALLOW_INTENT_API, false);
+            reconnectViaScan = getPrefs().getAutoReconnectByScan();
+        }
+
+        startForeground();
+        if(reconnectViaScan) {
+            scanAllDevices();
+
+            Intent scanServiceIntent = new Intent(this, BLEScanService.class);
+            startService(scanServiceIntent);
+        }
+    }
+
+    private void scanAllDevices(){
+        List<GBDevice> devices = GBApplication.app().getDeviceManager().getDevices();
+        for(GBDevice device : devices){
+            if(!device.getDeviceCoordinator().getConnectionType().usesBluetoothLE()){
+                continue;
+            }
+            if(device.getState() != GBDevice.State.NOT_CONNECTED){
+                continue;
+            }
+            boolean shouldAutoConnect = getPrefs().getAutoReconnect(device);
+            if(!shouldAutoConnect){
+                continue;
+            }
+            createDeviceStruct(device);
+            device.setState(GBDevice.State.WAITING_FOR_SCAN);
+            device.sendDeviceUpdateIntent(this, GBDevice.DeviceUpdateSubject.DEVICE_STATE);
+        }
+    }
+
+    private DeviceSupportFactory getDeviceSupportFactory() {
+        if (DEVICE_SUPPORT_FACTORY != null) {
+            return DEVICE_SUPPORT_FACTORY;
+        }
+        return new DeviceSupportFactory(this);
+    }
+
+    private DeviceStruct createDeviceStruct(GBDevice target){
+        DeviceStruct registeredStruct = new DeviceStruct();
+        registeredStruct.setDevice(target);
+        registeredStruct.setCoordinator(target.getDeviceCoordinator());
+        deviceStructs.add(registeredStruct);
+        return registeredStruct;
+    }
+
+    private void connectToDevice(GBDevice device){
+        connectToDevice(device, false);
+    }
+
+    private void connectToDevice(@Nullable final GBDevice device, boolean firstTime) {
+        final List<GBDevice> gbDevs = new ArrayList<>();
+        boolean fromExtra = false;
+
+        final Prefs prefs = getPrefs();
+
+        if (device != null) {
+            if (!device.getDeviceCoordinator().isConnectable()) {
+                GB.toast("Cannot connect to Scannable Device", Toast.LENGTH_SHORT, GB.INFO);
+                return;
+            }
+
+            gbDevs.add(device);
+            fromExtra = true;
+        } else {
+            List<GBDevice> gbAllDevs = GBApplication.app().getDeviceManager().getDevices();
+
+            if (gbAllDevs != null && !gbAllDevs.isEmpty()) {
+                if (prefs.getBoolean(GBPrefs.RECONNECT_ONLY_TO_CONNECTED, true)) {
+                    Set<String> lastDeviceAddresses = prefs.getStringSet(GBPrefs.LAST_DEVICE_ADDRESSES, Collections.emptySet());
+
+                    if (lastDeviceAddresses != null && !lastDeviceAddresses.isEmpty()) {
+                        for (final GBDevice gbDev : gbAllDevs) {
+                            // TODO volatile address
+                            if (lastDeviceAddresses.contains(gbDev.getAddress())) {
+                                gbDevs.add(gbDev);
+                            }
+                        }
+                    }
+                } else {
+                    gbDevs.addAll(gbAllDevs);
+                }
+            }
+        }
+
+        if (gbDevs.isEmpty()) {
+            return;
+        }
+
+        for (GBDevice gbDevice : gbDevs) {
+            if (!gbDevice.getDeviceCoordinator().isConnectable()) {
+                // we cannot connect to beacons, skip this device
+                continue;
+            }
+
+            String btDeviceAddress = gbDevice.getAddress();
+
+            boolean autoReconnect = GBPrefs.AUTO_RECONNECT_DEFAULT;
+            if (prefs != null && prefs.getPreferences() != null) {
+                autoReconnect = getPrefs().getAutoReconnect(gbDevice);
+                if (!fromExtra && !autoReconnect) {
+                    continue;
+                }
+
+                final Set<String> lastDeviceAddresses = new HashSet<>(prefs.getStringSet(GBPrefs.LAST_DEVICE_ADDRESSES, Collections.emptySet()));
+
+                if (!lastDeviceAddresses.contains(btDeviceAddress)) {
+                    lastDeviceAddresses.add(btDeviceAddress);
+                    prefs.getPreferences().edit().putStringSet(GBPrefs.LAST_DEVICE_ADDRESSES, lastDeviceAddresses).apply();
+                }
+            }
+
+            if (!fromExtra && !autoReconnect) {
+                continue;
+            }
+
+            DeviceStruct registeredStruct = getDeviceStructOrNull(gbDevice);
+            if (registeredStruct == null) {
+                registeredStruct = createDeviceStruct(gbDevice);
+            } else {
+                final GBDevice deviceFromStruct = registeredStruct.getDevice();
+
+                if (isDeviceConnecting(deviceFromStruct) || isDeviceConnected(deviceFromStruct)) {
+                    continue;
+                }
+
+                try {
+                    removeDeviceSupport(gbDevice);
+                } catch (final DeviceNotFoundException e) {
+                    LOG.error("connectToDevice(): Failed to remove device support: {}", e, e);
+                }
+            }
+
+            try {
+                final DeviceSupport deviceSupport = mFactory.createDeviceSupport(gbDevice);
+
+                if (deviceSupport != null) {
+                    setDeviceSupport(gbDevice, deviceSupport);
+
+                    if (firstTime) {
+                        deviceSupport.connectFirstTime();
+                    } else {
+                        deviceSupport.setAutoReconnect(autoReconnect);
+                        deviceSupport.setScanReconnect(reconnectViaScan);
+                        deviceSupport.connect();
+                    }
+                } else {
+                    GB.toast(this, getString(R.string.cannot_connect, "Can't create device support"), Toast.LENGTH_SHORT, GB.ERROR);
+                }
+            } catch (Exception e) {
+                GB.toast(this, getString(R.string.cannot_connect, e.getMessage()), Toast.LENGTH_SHORT, GB.ERROR, e);
+            }
+
+            registeredStruct.getDevice().sendDeviceUpdateIntent(this);
+        }
+    }
+
+    @Override
+    public synchronized int onStartCommand(Intent intent, int flags, int startId) {
+        if (intent == null) {
+            LOG.info("no intent");
+            return START_STICKY;
+        }
+
+        String action = intent.getAction();
+
+        if (action == null) {
+            LOG.info("no action");
+            return START_STICKY;
+        }
+
+        LOG.debug("Service startcommand: " + action);
+
+        // when we get past this, we should have valid mDeviceSupport and mGBDevice instances
+
+        GBDevice targetDevice = intent.getParcelableExtra(GBDevice.EXTRA_DEVICE);
+
+        Prefs prefs = getPrefs();
+        switch (action) {
+            case ACTION_CONNECT:
+                boolean firstTime = intent.getBooleanExtra(EXTRA_CONNECT_FIRST_TIME, false);
+                connectToDevice(targetDevice, firstTime);
+                break;
+            default:
+                ArrayList<GBDevice> targetedDevices = new ArrayList<>();
+                if(targetDevice != null){
+                    targetedDevices.add(targetDevice);
+                }else{
+                    for(GBDevice device : getGBDevices()){
+                        if(isDeviceInitialized(device)){
+                            targetedDevices.add(device);
+                        } else if (isDeviceReconnecting(device) && action.equals(ACTION_NOTIFICATION) && GBApplication.getPrefs().getBoolean("notification_cache_while_disconnected", false)) {
+                            if (!cachedNotifications.containsKey(device.getAddress())) {
+                                cachedNotifications.put(device.getAddress(), new ArrayList<>());
+                            }
+                            ArrayList<Intent> notifCache = cachedNotifications.get(device.getAddress());
+                            notifCache.add(intent);
+                            if (notifCache.size() > NOTIFICATIONS_CACHE_MAX) {
+                                // remove the oldest notification if the maximum is reached
+                                notifCache.remove(0);
+                            }
+                        } else if (action.equals(ACTION_DELETE_NOTIFICATION)) {
+                            ArrayList<Intent> notifCache = cachedNotifications.get(device.getAddress());
+                            if (notifCache != null) {
+                                int notifId = intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1);
+                                ArrayList<Intent> toRemove = new ArrayList<>();
+                                for (Intent cached : notifCache) {
+                                    if (notifId == cached.getIntExtra(EXTRA_NOTIFICATION_ID, -1)) {
+                                        toRemove.add(cached);
+                                    }
+                                }
+                                notifCache.removeAll(toRemove);
+                            }
+                        } else if (action.equals(ACTION_DISCONNECT) && device.getState() != GBDevice.State.NOT_CONNECTED) {
+                            targetedDevices.add(device);
+                        }
+                    }
+                }
+                for (GBDevice device1 : targetedDevices) {
+                    try {
+                        handleAction(intent, action, device1);
+                    } catch (DeviceNotFoundException e) {
+                        e.printStackTrace();
+                    } catch (Exception e) {
+                        LOG.error("An exception was raised while handling the action {} for the device {}: ", action, device1, e);
+                    }
+                }
+                break;
+        }
+        return START_STICKY;
+    }
+
+    /**
+     * @param text original text
+     * @return 'text' or a new String without non supported chars like emoticons, etc.
+     */
+    private String sanitizeNotifText(String text, GBDevice device) throws DeviceNotFoundException {
+        if (text == null || text.length() == 0)
+            return text;
+
+        text = getDeviceSupport(device).customStringFilter(text);
+
+        if (!getDeviceCoordinator(device).supportsUnicodeEmojis()) {
+            return EmojiConverter.convertUnicodeEmojiToAscii(text, getApplicationContext());
+        }
+
+        return text;
+    }
+
+    private DeviceCoordinator getDeviceCoordinator(GBDevice device) throws DeviceNotFoundException {
+        if(device == null){
+            throw new DeviceNotFoundException("null");
+        }
+        for(DeviceStruct struct : deviceStructs){
+            if(struct.getDevice().equals(device)){
+                return struct.getCoordinator();
+            }
+        }
+        throw new DeviceNotFoundException(device);
+    }
+
+    private void handleAction(Intent intent, String action, GBDevice device) throws DeviceNotFoundException {
+        if(ACTION_DISCONNECT.equals(intent.getAction())) {
+            try {
+                removeDeviceSupport(device);
+            } catch (DeviceNotFoundException e) {
+                LOG.error("Trying to disconnect unknown device: ", e);
+            }
+            device.setState(GBDevice.State.NOT_CONNECTED);
+            device.sendDeviceUpdateIntent(this);
+            updateReceiversState();
+            return;
+        }
+
+        DeviceSupport deviceSupport = getDeviceSupport(device);
+
+        Prefs devicePrefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(device.getAddress()));
+
+        final Transliterator transliterator = LanguageUtils.getTransliterator(device);
+
+        // Copy the incoming intent to make sure we don't modify it before it gets passed to other devices
+        Intent intentCopy = (Intent) intent.clone();
+
+        for (String extra : GBDeviceService.transliterationExtras) {
+            if (intentCopy.hasExtra(extra)) {
+                // Ensure the text is sanitized (eg. emoji converted to ascii) before applying the transliterators
+                // otherwise the emoji are removed before converting them
+                String sanitizedText = sanitizeNotifText(intentCopy.getStringExtra(extra), device);
+                if (transliterator != null) {
+                    sanitizedText = transliterator.transliterate(sanitizedText);
+                }
+                intentCopy.putExtra(extra, sanitizedText);
+            }
+        }
+
+        switch (action) {
+            case ACTION_REQUEST_DEVICEINFO:
+                device.sendDeviceUpdateIntent(this, GBDevice.DeviceUpdateSubject.NOTHING);
+                break;
+            case ACTION_NOTIFICATION: {
+                int desiredId = intentCopy.getIntExtra(EXTRA_NOTIFICATION_ID, -1);
+                NotificationSpec notificationSpec = new NotificationSpec(desiredId);
+                notificationSpec.phoneNumber = intentCopy.getStringExtra(EXTRA_NOTIFICATION_PHONENUMBER);
+                notificationSpec.sender = intentCopy.getStringExtra(EXTRA_NOTIFICATION_SENDER);
+                notificationSpec.subject = intentCopy.getStringExtra(EXTRA_NOTIFICATION_SUBJECT);
+                notificationSpec.title = intentCopy.getStringExtra(EXTRA_NOTIFICATION_TITLE);
+                if(notificationSpec.title == null) {
+                    notificationSpec.title = "";
+                }
+                notificationSpec.key = intentCopy.getStringExtra(EXTRA_NOTIFICATION_KEY);
+                notificationSpec.body = intentCopy.getStringExtra(EXTRA_NOTIFICATION_BODY);
+                if(notificationSpec.body == null) {
+                    notificationSpec.body = "";
+                }
+                notificationSpec.sourceName = intentCopy.getStringExtra(EXTRA_NOTIFICATION_SOURCENAME);
+                notificationSpec.type = (NotificationType) intentCopy.getSerializableExtra(EXTRA_NOTIFICATION_TYPE);
+                notificationSpec.attachedActions = (ArrayList<NotificationSpec.Action>) intentCopy.getSerializableExtra(EXTRA_NOTIFICATION_ACTIONS);
+                notificationSpec.pebbleColor = (byte) intentCopy.getSerializableExtra(EXTRA_NOTIFICATION_PEBBLE_COLOR);
+                notificationSpec.flags = intentCopy.getIntExtra(EXTRA_NOTIFICATION_FLAGS, 0);
+                notificationSpec.sourceAppId = intentCopy.getStringExtra(EXTRA_NOTIFICATION_SOURCEAPPID);
+                notificationSpec.iconId = intentCopy.getIntExtra(EXTRA_NOTIFICATION_ICONID, 0);
+                notificationSpec.picturePath = intent.getStringExtra(NOTIFICATION_PICTURE_PATH);
+                notificationSpec.dndSuppressed = intentCopy.getIntExtra(EXTRA_NOTIFICATION_DNDSUPPRESSED, 0);
+
+                if (notificationSpec.type == NotificationType.GENERIC_SMS && notificationSpec.phoneNumber != null) {
+                    GBApplication.getIDSenderLookup().add(notificationSpec.getId(), notificationSpec.phoneNumber);
+                }
+
+                //TODO: check if at least one of the attached actions is a reply action instead?
+                if ((notificationSpec.attachedActions != null && notificationSpec.attachedActions.size() > 0)
+                        || (notificationSpec.type == NotificationType.GENERIC_SMS && notificationSpec.phoneNumber != null)) {
+                    // NOTE: maybe not where it belongs
+                    // I would rather like to save that as an array in SharedPreferences
+                    // this would work but I dont know how to do the same in the Settings Activity's xml
+                    ArrayList<String> replies = new ArrayList<>();
+                    for (int i = 1; i <= 16; i++) {
+                        String reply = devicePrefs.getString("canned_reply_" + i, null);
+                        if (reply != null && !reply.equals("")) {
+                            replies.add(reply);
+                        }
+                    }
+                    notificationSpec.cannedReplies = replies.toArray(new String[0]);
+                }
+
+                deviceSupport.onNotification(notificationSpec);
+                break;
+            }
+            case ACTION_DELETE_NOTIFICATION: {
+                deviceSupport.onDeleteNotification(intentCopy.getIntExtra(EXTRA_NOTIFICATION_ID, -1));
+                break;
+            }
+            case ACTION_ADD_CALENDAREVENT: {
+                CalendarEventSpec calendarEventSpec = new CalendarEventSpec();
+                calendarEventSpec.id = intentCopy.getLongExtra(EXTRA_CALENDAREVENT_ID, -1);
+                calendarEventSpec.type = intentCopy.getByteExtra(EXTRA_CALENDAREVENT_TYPE, (byte) -1);
+                calendarEventSpec.timestamp = intentCopy.getIntExtra(EXTRA_CALENDAREVENT_TIMESTAMP, -1);
+                calendarEventSpec.durationInSeconds = intentCopy.getIntExtra(EXTRA_CALENDAREVENT_DURATION, -1);
+                calendarEventSpec.allDay = intentCopy.getBooleanExtra(EXTRA_CALENDAREVENT_ALLDAY, false);
+                calendarEventSpec.reminders =  (ArrayList<Long>) intentCopy.getSerializableExtra(EXTRA_CALENDAREVENT_REMINDERS);
+                calendarEventSpec.title = intentCopy.getStringExtra(EXTRA_CALENDAREVENT_TITLE);
+                calendarEventSpec.description = intentCopy.getStringExtra(EXTRA_CALENDAREVENT_DESCRIPTION);
+                calendarEventSpec.location = intentCopy.getStringExtra(EXTRA_CALENDAREVENT_LOCATION);
+                calendarEventSpec.calName = intentCopy.getStringExtra(EXTRA_CALENDAREVENT_CALNAME);
+                calendarEventSpec.color = intentCopy.getIntExtra(EXTRA_CALENDAREVENT_COLOR, 0);
+                deviceSupport.onAddCalendarEvent(calendarEventSpec);
+                break;
+            }
+            case ACTION_DELETE_CALENDAREVENT: {
+                long id = intentCopy.getLongExtra(EXTRA_CALENDAREVENT_ID, -1);
+                byte type = intentCopy.getByteExtra(EXTRA_CALENDAREVENT_TYPE, (byte) -1);
+                deviceSupport.onDeleteCalendarEvent(type, id);
+                break;
+            }
+            case ACTION_RESET: {
+                int flags = intentCopy.getIntExtra(EXTRA_RESET_FLAGS, 0);
+                deviceSupport.onReset(flags);
+                break;
+            }
+            case ACTION_HEARTRATE_TEST: {
+                deviceSupport.onHeartRateTest();
+                break;
+            }
+            case ACTION_FETCH_RECORDED_DATA: {
+                int dataTypes = intentCopy.getIntExtra(EXTRA_RECORDED_DATA_TYPES, 0);
+                deviceSupport.onFetchRecordedData(dataTypes);
+                break;
+            }
+            case ACTION_FIND_DEVICE: {
+                boolean start = intentCopy.getBooleanExtra(EXTRA_FIND_START, false);
+                deviceSupport.onFindDevice(start);
+                break;
+            }
+            case ACTION_PHONE_FOUND: {
+                final boolean start = intentCopy.getBooleanExtra(EXTRA_FIND_START, false);
+                deviceSupport.onFindPhone(start);
+                break;
+            }
+            case ACTION_SET_CONSTANT_VIBRATION: {
+                int intensity = intentCopy.getIntExtra(EXTRA_VIBRATION_INTENSITY, 0);
+                deviceSupport.onSetConstantVibration(intensity);
+                break;
+            }
+            case ACTION_CALLSTATE:
+                CallSpec callSpec = new CallSpec();
+                callSpec.command = intentCopy.getIntExtra(EXTRA_CALL_COMMAND, CallSpec.CALL_UNDEFINED);
+                callSpec.number = intentCopy.getStringExtra(EXTRA_CALL_PHONENUMBER);
+                callSpec.name = intentCopy.getStringExtra(EXTRA_CALL_DISPLAYNAME);
+                callSpec.sourceName = intentCopy.getStringExtra(EXTRA_CALL_SOURCENAME);
+                callSpec.sourceAppId = intentCopy.getStringExtra(EXTRA_CALL_SOURCEAPPID);
+                callSpec.dndSuppressed = intentCopy.getIntExtra(EXTRA_CALL_DNDSUPPRESSED, 0);
+                deviceSupport.onSetCallState(callSpec);
+                break;
+            case ACTION_SETCANNEDMESSAGES:
+                int type = intentCopy.getIntExtra(EXTRA_CANNEDMESSAGES_TYPE, -1);
+                String[] cannedMessages = intentCopy.getStringArrayExtra(EXTRA_CANNEDMESSAGES);
+
+                CannedMessagesSpec cannedMessagesSpec = new CannedMessagesSpec();
+                cannedMessagesSpec.type = type;
+                cannedMessagesSpec.cannedMessages = cannedMessages;
+                deviceSupport.onSetCannedMessages(cannedMessagesSpec);
+                break;
+            case ACTION_SETTIME:
+                deviceSupport.onSetTime();
+                break;
+            case ACTION_SETMUSICINFO:
+                MusicSpec musicSpec = new MusicSpec();
+                musicSpec.artist = intentCopy.getStringExtra(EXTRA_MUSIC_ARTIST);
+                musicSpec.album = intentCopy.getStringExtra(EXTRA_MUSIC_ALBUM);
+                musicSpec.track = intentCopy.getStringExtra(EXTRA_MUSIC_TRACK);
+                musicSpec.duration = intentCopy.getIntExtra(EXTRA_MUSIC_DURATION, 0);
+                musicSpec.trackCount = intentCopy.getIntExtra(EXTRA_MUSIC_TRACKCOUNT, 0);
+                musicSpec.trackNr = intentCopy.getIntExtra(EXTRA_MUSIC_TRACKNR, 0);
+                deviceSupport.onSetMusicInfo(musicSpec);
+                break;
+            case ACTION_SET_PHONE_VOLUME:
+                float phoneVolume = intentCopy.getFloatExtra(EXTRA_PHONE_VOLUME, 0);
+                deviceSupport.onSetPhoneVolume(phoneVolume);
+                break;
+            case ACTION_SET_PHONE_SILENT_MODE:
+                final int ringerMode = intentCopy.getIntExtra(EXTRA_PHONE_RINGER_MODE, -1);
+                deviceSupport.onChangePhoneSilentMode(ringerMode);
+                break;
+            case ACTION_SETMUSICSTATE:
+                MusicStateSpec stateSpec = new MusicStateSpec();
+                stateSpec.shuffle = intentCopy.getByteExtra(EXTRA_MUSIC_SHUFFLE, (byte) 0);
+                stateSpec.repeat = intentCopy.getByteExtra(EXTRA_MUSIC_REPEAT, (byte) 0);
+                stateSpec.position = intentCopy.getIntExtra(EXTRA_MUSIC_POSITION, 0);
+                stateSpec.playRate = intentCopy.getIntExtra(EXTRA_MUSIC_RATE, 0);
+                stateSpec.state = intentCopy.getByteExtra(EXTRA_MUSIC_STATE, (byte) 0);
+                deviceSupport.onSetMusicState(stateSpec);
+                break;
+            case ACTION_SETNAVIGATIONINFO:
+                NavigationInfoSpec navigationInfoSpec = new NavigationInfoSpec();
+                navigationInfoSpec.instruction = intentCopy.getStringExtra(EXTRA_NAVIGATION_INSTRUCTION);
+                navigationInfoSpec.nextAction = intentCopy.getIntExtra(EXTRA_NAVIGATION_NEXT_ACTION,0);
+                navigationInfoSpec.distanceToTurn = intentCopy.getStringExtra(EXTRA_NAVIGATION_DISTANCE_TO_TURN);
+                navigationInfoSpec.ETA = intentCopy.getStringExtra(EXTRA_NAVIGATION_ETA);
+                deviceSupport.onSetNavigationInfo(navigationInfoSpec);
+                break;
+            case ACTION_REQUEST_APPINFO:
+                deviceSupport.onAppInfoReq();
+                break;
+            case ACTION_REQUEST_SCREENSHOT:
+                deviceSupport.onScreenshotReq();
+                break;
+            case ACTION_STARTAPP: {
+                UUID uuid = (UUID) intentCopy.getSerializableExtra(EXTRA_APP_UUID);
+                boolean start = intentCopy.getBooleanExtra(EXTRA_APP_START, true);
+                deviceSupport.onAppStart(uuid, start);
+                break;
+            }
+            case ACTION_DOWNLOADAPP: {
+                UUID uuid = (UUID) intentCopy.getSerializableExtra(EXTRA_APP_UUID);
+                deviceSupport.onAppDownload(uuid);
+                break;
+            }
+            case ACTION_DELETEAPP: {
+                UUID uuid = (UUID) intentCopy.getSerializableExtra(EXTRA_APP_UUID);
+                deviceSupport.onAppDelete(uuid);
+                break;
+            }
+            case ACTION_APP_CONFIGURE: {
+                UUID uuid = (UUID) intentCopy.getSerializableExtra(EXTRA_APP_UUID);
+                String config = intentCopy.getStringExtra(EXTRA_APP_CONFIG);
+                Integer id = null;
+                if (intentCopy.hasExtra(EXTRA_APP_CONFIG_ID)) {
+                    id = intentCopy.getIntExtra(EXTRA_APP_CONFIG_ID, 0);
+                }
+                deviceSupport.onAppConfiguration(uuid, config, id);
+                break;
+            }
+            case ACTION_APP_REORDER: {
+                UUID[] uuids = (UUID[]) intentCopy.getSerializableExtra(EXTRA_APP_UUID);
+                deviceSupport.onAppReorder(uuids);
+                break;
+            }
+            case ACTION_INSTALL:
+                Uri uri = intentCopy.getParcelableExtra(EXTRA_URI);
+                if (uri != null) {
+                    LOG.info("will try to install app/fw");
+                    deviceSupport.onInstallApp(uri);
+                }
+                break;
+            case ACTION_SET_ALARMS:
+                ArrayList<? extends Alarm> alarms = (ArrayList<? extends Alarm>) intentCopy.getSerializableExtra(EXTRA_ALARMS);
+                deviceSupport.onSetAlarms(alarms);
+                break;
+            case ACTION_SET_REMINDERS:
+                ArrayList<? extends Reminder> reminders = (ArrayList<? extends Reminder>) intentCopy.getSerializableExtra(EXTRA_REMINDERS);
+                deviceSupport.onSetReminders(reminders);
+                break;
+            case ACTION_SET_LOYALTY_CARDS:
+                final ArrayList<LoyaltyCard> loyaltyCards = (ArrayList<LoyaltyCard>) intentCopy.getSerializableExtra(EXTRA_LOYALTY_CARDS);
+                deviceSupport.onSetLoyaltyCards(loyaltyCards);
+                break;
+            case ACTION_SET_WORLD_CLOCKS:
+                ArrayList<? extends WorldClock> clocks = (ArrayList<? extends WorldClock>) intentCopy.getSerializableExtra(EXTRA_WORLD_CLOCKS);
+                deviceSupport.onSetWorldClocks(clocks);
+                break;
+            case ACTION_SET_CONTACTS:
+                ArrayList<? extends Contact> contacts = (ArrayList<? extends Contact>) intentCopy.getSerializableExtra(EXTRA_CONTACTS);
+                deviceSupport.onSetContacts(contacts);
+                break;
+            case ACTION_ENABLE_REALTIME_STEPS: {
+                boolean enable = intentCopy.getBooleanExtra(EXTRA_BOOLEAN_ENABLE, false);
+                deviceSupport.onEnableRealtimeSteps(enable);
+                break;
+            }
+            case ACTION_ENABLE_HEARTRATE_SLEEP_SUPPORT: {
+                boolean enable = intentCopy.getBooleanExtra(EXTRA_BOOLEAN_ENABLE, false);
+                deviceSupport.onEnableHeartRateSleepSupport(enable);
+                break;
+            }
+            case ACTION_SET_HEARTRATE_MEASUREMENT_INTERVAL: {
+                int seconds = intentCopy.getIntExtra(EXTRA_INTERVAL_SECONDS, 0);
+                deviceSupport.onSetHeartRateMeasurementInterval(seconds);
+                break;
+            }
+            case ACTION_ENABLE_REALTIME_HEARTRATE_MEASUREMENT: {
+                boolean enable = intentCopy.getBooleanExtra(EXTRA_BOOLEAN_ENABLE, false);
+                deviceSupport.onEnableRealtimeHeartRateMeasurement(enable);
+                break;
+            }
+            case ACTION_SEND_CONFIGURATION: {
+                String config = intentCopy.getStringExtra(EXTRA_CONFIG);
+                deviceSupport.onSendConfiguration(config);
+                break;
+            }
+            case ACTION_READ_CONFIGURATION: {
+                String config = intentCopy.getStringExtra(EXTRA_CONFIG);
+                deviceSupport.onReadConfiguration(config);
+                break;
+            }
+            case ACTION_TEST_NEW_FUNCTION: {
+                deviceSupport.onTestNewFunction();
+                break;
+            }
+            case ACTION_SEND_WEATHER: {
+                ArrayList<WeatherSpec> weatherSpecs = (ArrayList<WeatherSpec>) intentCopy.getSerializableExtra(EXTRA_WEATHER);
+                if (weatherSpecs != null && !weatherSpecs.isEmpty()) {
+                    deviceSupport.onSendWeather(weatherSpecs);
+                }
+                break;
+            }
+            case ACTION_SET_LED_COLOR:
+                int color = intentCopy.getIntExtra(EXTRA_LED_COLOR, 0);
+                if (color != 0) {
+                    deviceSupport.onSetLedColor(color);
+                }
+                break;
+            case ACTION_POWER_OFF:
+                deviceSupport.onPowerOff();
+                break;
+            case ACTION_SET_FM_FREQUENCY:
+                float frequency = intentCopy.getFloatExtra(EXTRA_FM_FREQUENCY, -1);
+                if (frequency != -1) {
+                    deviceSupport.onSetFmFrequency(frequency);
+                }
+                break;
+            case ACTION_SET_GPS_LOCATION:
+                final Location location = intentCopy.getParcelableExtra(EXTRA_GPS_LOCATION);
+                deviceSupport.onSetGpsLocation(location);
+                break;
+            case ACTION_SLEEP_AS_ANDROID:
+                if(device.getDeviceCoordinator().supportsSleepAsAndroid() && GBApplication.getPrefs().getString("sleepasandroid_device", new String()).equals(device.getAddress()))
+                {
+                    final String sleepAsAndroidAction = intentCopy.getStringExtra(EXTRA_SLEEP_AS_ANDROID_ACTION);
+                    deviceSupport.onSleepAsAndroidAction(sleepAsAndroidAction, intentCopy.getExtras());
+                }
+                break;
+            case ACTION_CAMERA_STATUS_CHANGE:
+                final GBDeviceEventCameraRemote.Event event = GBDeviceEventCameraRemote.intToEvent(intentCopy.getIntExtra(EXTRA_CAMERA_EVENT, -1));
+                String filename = null;
+                if (event == GBDeviceEventCameraRemote.Event.TAKE_PICTURE) {
+                    filename = intentCopy.getStringExtra(EXTRA_CAMERA_FILENAME);
+                }
+                deviceSupport.onCameraStatusChange(event, filename);
+                break;
+            case ACTION_REQUEST_MUSIC_LIST:
+                deviceSupport.onMusicListReq();
+                break;
+            case ACTION_REQUEST_MUSIC_OPERATION:
+                int operation = intentCopy.getIntExtra("operation", -1);
+                int playlistIndex = intentCopy.getIntExtra("playlistIndex", -1);
+                String playlistName = intentCopy.getStringExtra("playlistName");
+                ArrayList<Integer> musics = (ArrayList<Integer>) intentCopy.getSerializableExtra("musicIds");
+                deviceSupport.onMusicOperation(operation, playlistIndex, playlistName, musics);
+                break;
+        }
+    }
+
+    /**
+     * Disposes the current DeviceSupport instance (if any) and sets a new device support instance
+     * (if not null).
+     *
+     * @param deviceSupport deviceSupport to reokace/add
+     */
+    private void setDeviceSupport(GBDevice device, DeviceSupport deviceSupport) throws DeviceNotFoundException {
+       DeviceStruct deviceStruct = getDeviceStruct(device);
+       DeviceSupport cachedDeviceSupport = deviceStruct.getDeviceSupport();
+       if (deviceSupport != cachedDeviceSupport && cachedDeviceSupport != null) {
+           cachedDeviceSupport.dispose();
+       }
+       deviceStruct.setDeviceSupport(deviceSupport);
+    }
+
+    private void removeDeviceSupport(GBDevice device) throws DeviceNotFoundException {
+        DeviceStruct struct = getDeviceStruct(device);
+        if(struct.getDeviceSupport() != null){
+            struct.getDeviceSupport().dispose();
+        }
+        struct.setDeviceSupport(null);
+    }
+
+    private DeviceStruct getDeviceStructOrNull(GBDevice device){
+        DeviceStruct deviceStruct = null;
+        try {
+            deviceStruct = getDeviceStruct(device);
+        } catch (DeviceNotFoundException e) {
+            e.printStackTrace();
+        }
+        return deviceStruct;
+    }
+
+    public DeviceStruct getDeviceStruct(GBDevice device) throws DeviceNotFoundException {
+        if(device == null){
+            throw new DeviceNotFoundException("null");
+        }
+        for(DeviceStruct struct : deviceStructs){
+            if(struct.getDevice().equals(device)){
+                return struct;
+            }
+        }
+        throw new DeviceNotFoundException(device);
+    }
+
+    public GBDevice getDeviceByAddress(String deviceAddress) throws DeviceNotFoundException {
+        if(deviceAddress == null){
+            throw new DeviceNotFoundException(deviceAddress);
+        }
+        for(DeviceStruct struct : deviceStructs){
+            if(struct.getDevice().getAddress().equals(deviceAddress)){
+                return struct.getDevice();
+            }
+        }
+        throw new DeviceNotFoundException(deviceAddress);
+    }
+
+    public GBDevice getDeviceByAddressOrNull(String deviceAddress){
+        GBDevice device = null;
+        try {
+            device = getDeviceByAddress(deviceAddress);
+        } catch (DeviceNotFoundException e) {
+            e.printStackTrace();
+        }
+        return device;
+    }
+
+    private DeviceSupport getDeviceSupport(GBDevice device) throws DeviceNotFoundException {
+        if(device == null){
+            throw new DeviceNotFoundException("null");
+        }
+        for(DeviceStruct struct : deviceStructs){
+            if(struct.getDevice().equals(device)){
+                if(struct.getDeviceSupport() == null)
+                    throw new DeviceNotFoundException(device);
+
+                return struct.getDeviceSupport();
+            }
+        }
+        throw new DeviceNotFoundException(device);
+    }
+
+    private void startForeground() {
+        GB.createNotificationChannels(this);
+
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE && ContextCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_SCAN) == PackageManager.PERMISSION_DENIED)
+                return;
+
+            startForeground(GB.NOTIFICATION_ID, GB.createNotification(getString(R.string.gadgetbridge_running), this), ServiceInfo.FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE);
+        } else {
+            startForeground(GB.NOTIFICATION_ID, GB.createNotification(getString(R.string.gadgetbridge_running), this));
+        }
+    }
+
+    private boolean isDeviceConnected(GBDevice device) {
+        return isDeviceConnected(device.getAddress());
+    }
+
+    private boolean isDeviceConnected(String deviceAddress) {
+        for(DeviceStruct struct : deviceStructs){
+            if(struct.getDevice().getAddress().compareToIgnoreCase(deviceAddress) == 0){
+                return struct.getDevice().isConnected();
+            }
+        }
+        return false;
+    }
+
+    private boolean isDeviceConnecting(GBDevice device) {
+        return isDeviceConnecting(device.getAddress());
+    }
+
+    private boolean isDeviceConnecting(String deviceAddress) {
+        for(DeviceStruct struct : deviceStructs){
+            if(struct.getDevice().getAddress().compareToIgnoreCase(deviceAddress) == 0){
+                return struct.getDevice().isConnecting();
+            }
+        }
+        return false;
+    }
+
+    private boolean isDeviceInitialized(GBDevice device) {
+        return isDeviceInitialized(device.getAddress());
+    }
+
+    private boolean isDeviceInitialized(String deviceAddress) {
+        for(DeviceStruct struct : deviceStructs){
+            if(struct.getDevice().getAddress().compareToIgnoreCase(deviceAddress) == 0){
+                return struct.getDevice().isInitialized();
+            }
+        }
+        return false;
+    }
+
+    private boolean deviceStateEquals(GBDevice device, GBDevice.State... states){
+        if((device = getDeviceByAddressOrNull(device.getAddress())) != null){
+            for(GBDevice.State possibleState : states){
+                if(device.getState() == possibleState){
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    private boolean isDeviceReconnecting(GBDevice device) {
+        if((device = getDeviceByAddressOrNull(device.getAddress())) != null){
+            return device.getState().equalsOrHigherThan(GBDevice.State.NOT_CONNECTED);
+        }
+        return false;
+    }
+
+    private boolean deviceHasCalendarReceiverRegistered(GBDevice device){
+        for (CalendarReceiver receiver: mCalendarReceiver){
+            if(receiver.getGBDevice().equals(device)){
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private void setReceiversEnableState(boolean enable, boolean initialized, FeatureSet features, List <GBDevice> devicesWithCalendar) {
+        LOG.info("Setting broadcast receivers to: " + enable);
+
+        if(enable && features == null){
+            throw new RuntimeException("features cannot be null when enabling receivers");
+        }
+
+        if (enable && initialized && features.supportsCalendarEvents()) {
+            for (GBDevice deviceWithCalendar : devicesWithCalendar) {
+                if (!deviceHasCalendarReceiverRegistered(deviceWithCalendar)) {
+                    if (!(GBApplication.isRunningMarshmallowOrLater() && ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CALENDAR) == PackageManager.PERMISSION_DENIED)) {
+                        CalendarReceiver receiver = new CalendarReceiver(this, deviceWithCalendar);
+                        receiver.registerBroadcastReceivers();
+                        mCalendarReceiver.add(receiver);
+                    }
+                }
+            }
+        } else {
+            for (CalendarReceiver registeredReceiver: mCalendarReceiver) {
+                registeredReceiver.dispose();
+            }
+            mCalendarReceiver.clear();
+        }
+
+        if (enable) {
+            if (mPhoneCallReceiver == null) {
+                mPhoneCallReceiver = new PhoneCallReceiver();
+                IntentFilter filter = new IntentFilter();
+                filter.addAction("android.intent.action.PHONE_STATE");
+                filter.addAction("android.intent.action.NEW_OUTGOING_CALL");
+                filter.addAction("nodomain.freeyourgadget.gadgetbridge.MUTE_CALL");
+                ContextCompat.registerReceiver(this, mPhoneCallReceiver, filter, ContextCompat.RECEIVER_EXPORTED);
+            }
+            if (mSMSReceiver == null) {
+                mSMSReceiver = new SMSReceiver();
+                ContextCompat.registerReceiver(this, mSMSReceiver, new IntentFilter("android.provider.Telephony.SMS_RECEIVED"), ContextCompat.RECEIVER_EXPORTED);
+            }
+            if (mPebbleReceiver == null) {
+                mPebbleReceiver = new PebbleReceiver();
+                ContextCompat.registerReceiver(this, mPebbleReceiver, new IntentFilter("com.getpebble.action.SEND_NOTIFICATION"), ContextCompat.RECEIVER_EXPORTED);
+            }
+            if (mMusicPlaybackReceiver == null && features.supportsMusicInfo()) {
+                mMusicPlaybackReceiver = new MusicPlaybackReceiver();
+                IntentFilter filter = new IntentFilter();
+                for (String action : mMusicActions) {
+                    filter.addAction(action);
+                }
+                ContextCompat.registerReceiver(this, mMusicPlaybackReceiver, filter, ContextCompat.RECEIVER_EXPORTED);
+            }
+            if (mTimeChangeReceiver == null) {
+                mTimeChangeReceiver = new TimeChangeReceiver();
+                IntentFilter filter = new IntentFilter();
+                filter.addAction("android.intent.action.TIME_SET");
+                filter.addAction("android.intent.action.TIMEZONE_CHANGED");
+                filter.addAction(TimeChangeReceiver.ACTION_DST_CHANGED_OR_PERIODIC_SYNC);
+                ContextCompat.registerReceiver(this, mTimeChangeReceiver, filter, ContextCompat.RECEIVER_EXPORTED);
+                // Ensure alarm is scheduled after registering broadcast receiver
+                // (this is important in case receiver was unregistered when the previous alarm arrived).
+                TimeChangeReceiver.ifEnabledScheduleNextDstChangeOrPeriodicSync(this);
+            }
+            if (mBlueToothPairingRequestReceiver == null) {
+                mBlueToothPairingRequestReceiver = new BluetoothPairingRequestReceiver(this);
+                ContextCompat.registerReceiver(this, mBlueToothPairingRequestReceiver, new IntentFilter(BluetoothDevice.ACTION_PAIRING_REQUEST), ContextCompat.RECEIVER_EXPORTED);
+            }
+            if (mAlarmClockReceiver == null) {
+                mAlarmClockReceiver = new AlarmClockReceiver();
+                IntentFilter filter = new IntentFilter();
+                filter.addAction(AlarmClockReceiver.ALARM_ALERT_ACTION);
+                filter.addAction(AlarmClockReceiver.ALARM_DONE_ACTION);
+                filter.addAction(AlarmClockReceiver.GOOGLE_CLOCK_ALARM_ALERT_ACTION);
+                filter.addAction(AlarmClockReceiver.GOOGLE_CLOCK_ALARM_DONE_ACTION);
+                ContextCompat.registerReceiver(this, mAlarmClockReceiver, filter, ContextCompat.RECEIVER_EXPORTED);
+            }
+
+            if (mSilentModeReceiver == null) {
+                mSilentModeReceiver = new SilentModeReceiver();
+                IntentFilter filter = new IntentFilter();
+                filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION);
+                ContextCompat.registerReceiver(this, mSilentModeReceiver, filter, ContextCompat.RECEIVER_EXPORTED);
+            }
+
+            if (locationService == null) {
+                locationService = new GBLocationService(this);
+                LocalBroadcastManager.getInstance(this).registerReceiver(locationService, locationService.buildFilter());
+            }
+
+            if (mOsmandAidlHelper == null && features.supportsNavigation()) {
+                mOsmandAidlHelper = new OsmandEventReceiver(this.getApplication());
+            }
+
+            // Weather receivers
+            if (features.supportsWeather()) {
+                if (GBApplication.isRunningOreoOrLater()) {
+                    if (mLineageOsWeatherReceiver == null) {
+                        mLineageOsWeatherReceiver = new LineageOsWeatherReceiver();
+                        ContextCompat.registerReceiver(this, mLineageOsWeatherReceiver, new IntentFilter("GB_UPDATE_WEATHER"), ContextCompat.RECEIVER_EXPORTED);
+                    }
+                } else {
+                    if (mCMWeatherReceiver == null) {
+                        mCMWeatherReceiver = new CMWeatherReceiver();
+                        ContextCompat.registerReceiver(this, mCMWeatherReceiver, new IntentFilter("GB_UPDATE_WEATHER"), ContextCompat.RECEIVER_EXPORTED);
+                    }
+                }
+                if (mTinyWeatherForecastGermanyReceiver == null) {
+                    mTinyWeatherForecastGermanyReceiver = new TinyWeatherForecastGermanyReceiver();
+                    ContextCompat.registerReceiver(this, mTinyWeatherForecastGermanyReceiver, new IntentFilter("de.kaffeemitkoffein.broadcast.WEATHERDATA"), ContextCompat.RECEIVER_EXPORTED);
+                }
+                if (mGenericWeatherReceiver == null) {
+                    mGenericWeatherReceiver = new GenericWeatherReceiver();
+                    ContextCompat.registerReceiver(this, mGenericWeatherReceiver, new IntentFilter(GenericWeatherReceiver.ACTION_GENERIC_WEATHER), ContextCompat.RECEIVER_EXPORTED);
+                }
+                if (mOmniJawsObserver == null) {
+                    try {
+                        mOmniJawsObserver = new OmniJawsObserver(new Handler());
+                        getContentResolver().registerContentObserver(OmniJawsObserver.WEATHER_URI, true, mOmniJawsObserver);
+                    } catch (PackageManager.NameNotFoundException e) {
+                        //Nothing wrong, it just means we're not running on omnirom.
+                    }
+                }
+            }
+
+            if (features.supportsSleepAsAndroid())
+            {
+                if (mSleepAsAndroidReceiver == null) {
+                    mSleepAsAndroidReceiver = new SleepAsAndroidReceiver();
+                    ContextCompat.registerReceiver(this, mSleepAsAndroidReceiver, new IntentFilter(), ContextCompat.RECEIVER_EXPORTED);
+                }
+            }
+
+            if (GBApplication.getPrefs().getBoolean("auto_fetch_enabled", false) &&
+                    features.supportsActivityDataFetching() && mGBAutoFetchReceiver == null) {
+                mGBAutoFetchReceiver = new GBAutoFetchReceiver();
+                ContextCompat.registerReceiver(this, mGBAutoFetchReceiver, new IntentFilter("android.intent.action.USER_PRESENT"), ContextCompat.RECEIVER_EXPORTED);
+            }
+        } else {
+            if (mPhoneCallReceiver != null) {
+                unregisterReceiver(mPhoneCallReceiver);
+                mPhoneCallReceiver = null;
+            }
+            if (mSMSReceiver != null) {
+                unregisterReceiver(mSMSReceiver);
+                mSMSReceiver = null;
+            }
+            if (mPebbleReceiver != null) {
+                unregisterReceiver(mPebbleReceiver);
+                mPebbleReceiver = null;
+            }
+            if (mMusicPlaybackReceiver != null) {
+                unregisterReceiver(mMusicPlaybackReceiver);
+                mMusicPlaybackReceiver = null;
+            }
+            if (mTimeChangeReceiver != null) {
+                unregisterReceiver(mTimeChangeReceiver);
+                mTimeChangeReceiver = null;
+            }
+
+            if (mBlueToothPairingRequestReceiver != null) {
+                unregisterReceiver(mBlueToothPairingRequestReceiver);
+                mBlueToothPairingRequestReceiver = null;
+            }
+            if (mAlarmClockReceiver != null) {
+                unregisterReceiver(mAlarmClockReceiver);
+                mAlarmClockReceiver = null;
+            }
+            if (mSilentModeReceiver != null) {
+                unregisterReceiver(mSilentModeReceiver);
+                mSilentModeReceiver = null;
+            }
+            if (locationService != null) {
+                LocalBroadcastManager.getInstance(this).unregisterReceiver(locationService);
+                locationService.stopAll();
+                locationService = null;
+            }
+            if (mCMWeatherReceiver != null) {
+                unregisterReceiver(mCMWeatherReceiver);
+                mCMWeatherReceiver = null;
+            }
+            if (mLineageOsWeatherReceiver != null) {
+                unregisterReceiver(mLineageOsWeatherReceiver);
+                mLineageOsWeatherReceiver = null;
+            }
+            if (mOmniJawsObserver != null) {
+                getContentResolver().unregisterContentObserver(mOmniJawsObserver);
+                mOmniJawsObserver = null;
+            }
+            if (mTinyWeatherForecastGermanyReceiver != null) {
+                unregisterReceiver(mTinyWeatherForecastGermanyReceiver);
+                mTinyWeatherForecastGermanyReceiver = null;
+            }
+            if (mOsmandAidlHelper != null) {
+                mOsmandAidlHelper.cleanupResources();
+                mOsmandAidlHelper = null;
+            }
+            if (mGBAutoFetchReceiver != null) {
+                unregisterReceiver(mGBAutoFetchReceiver);
+                mGBAutoFetchReceiver = null;
+            }
+            if (mGenericWeatherReceiver != null) {
+                unregisterReceiver(mGenericWeatherReceiver);
+                mGenericWeatherReceiver = null;
+            }
+            if (mSleepAsAndroidReceiver != null) {
+                unregisterReceiver(mSleepAsAndroidReceiver);
+                mSleepAsAndroidReceiver = null;
+            }
+        }
+    }
+
+    private void sendCachedNotifications(GBDevice device) {
+        ArrayList<Intent> notifCache = cachedNotifications.get(device.getAddress());
+        if (notifCache == null) return;
+        try {
+            while (notifCache.size() > 0) {
+                handleAction(notifCache.remove(0), ACTION_NOTIFICATION, device);
+            }
+        } catch (DeviceNotFoundException e) {
+            LOG.error("Error while sending cached notifications to "+device.getAliasOrName(), e);
+        }
+    }
+
+    @Override
+    public void onDestroy() {
+        if (hasPrefs()) {
+            getPrefs().getPreferences().unregisterOnSharedPreferenceChangeListener(this);
+        }
+
+        LOG.debug("DeviceCommunicationService is being destroyed");
+        super.onDestroy();
+
+        LocalBroadcastManager.getInstance(this).unregisterReceiver(mReceiver);
+        setReceiversEnableState(false, false, null, null); // disable BroadcastReceivers
+
+        unregisterReceiver(mBlueToothConnectReceiver);
+        mBlueToothConnectReceiver = null;
+
+        unregisterReceiver(mAutoConnectInvervalReceiver);
+        mAutoConnectInvervalReceiver.destroy();
+        mAutoConnectInvervalReceiver = null;
+
+        for(GBDevice device : getGBDevices()){
+            try {
+                removeDeviceSupport(device);
+            } catch (DeviceNotFoundException e) {
+                e.printStackTrace();
+            }
+        }
+        GB.removeNotification(GB.NOTIFICATION_ID, this); // need to do this because the updated notification won't be cancelled when service stops
+
+        unregisterReceiver(bluetoothCommandReceiver);
+        unregisterReceiver(deviceSettingsReceiver);
+        unregisterReceiver(intentApiReceiver);
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return null;
+    }
+
+    @Override
+    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
+        if (GBPrefs.DEVICE_AUTO_RECONNECT.equals(key)) {
+            for(DeviceStruct deviceStruct : deviceStructs){
+                boolean autoReconnect = getPrefs().getAutoReconnect(deviceStruct.getDevice());
+                deviceStruct.getDeviceSupport().setAutoReconnect(autoReconnect);
+            }
+        }
+        if (GBPrefs.CHART_MAX_HEART_RATE.equals(key) || GBPrefs.CHART_MIN_HEART_RATE.equals(key)) {
+            HeartRateUtils.getInstance().updateCachedHeartRatePreferences();
+        }
+        if (GBPrefs.PREF_ALLOW_INTENT_API.equals(key)){
+            allowBluetoothIntentApi = sharedPreferences.getBoolean(GBPrefs.PREF_ALLOW_INTENT_API, false);
+            GB.log("allowBluetoothIntentApi changed to " + allowBluetoothIntentApi, GB.INFO, null);
+        }
+    }
+
+    protected boolean hasPrefs() {
+        return getPrefs().getPreferences() != null;
+    }
+
+    public GBPrefs getPrefs() {
+        return GBApplication.getPrefs();
+    }
+
+    public GBDevice[] getGBDevices() {
+        GBDevice[] devices = new GBDevice[deviceStructs.size()];
+        for(int i = 0; i < devices.length; i++){
+            devices[i] = deviceStructs.get(i).getDevice();
+        }
+        return devices;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceSupport.java
new file mode 100644
index 0000000..ca4d482
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceSupport.java
@@ -0,0 +1,142 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, Carsten Pfeiffer, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service;
+
+import android.bluetooth.BluetoothAdapter;
+import android.content.Context;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.EventHandler;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+/**
+ * Provides the communication support for a specific device. Instances will <b>only</b>
+ * be used inside the DeviceCommunicationService. Has hooks to manage the life cycle
+ * of a device: instances of this interface will be created, initialized, and disposed
+ * as needed.
+ * <p/>
+ * Implementations need to act accordingly, in order to establish, reestablish or close
+ * the connection to the device.
+ * <p/>
+ * In principle, this interface is agnostic to the kind of transport, i.e. whether the
+ * device is connected via Bluetooth, Bluetooth LE, Wifi or something else, however at the
+ * moment, only the BluetoothAdapter is passed to implementations.
+ */
+public interface DeviceSupport extends EventHandler {
+    /**
+     * Sets all context information needed for the instance to operate.
+     *
+     * @param gbDevice  the device to operate with
+     * @param btAdapter the bluetooth adapter to use
+     * @param context   the android context, e.g. to look up resources
+     */
+    void setContext(GBDevice gbDevice, BluetoothAdapter btAdapter, Context context);
+
+    /**
+     * Returns whether a transport-level connection is established with the device
+     *
+     * @return whether the device is connected with the system running this software
+     */
+    boolean isConnected();
+
+    /**
+     * Attempts an initial connection to the device, typically after the user "discovered"
+     * and connects to it for the first time. Some implementations may perform an additional
+     * initialization or application-level pairing compared to the regular {@link #connect()}.
+     * <p/>
+     * Implementations may perform the connection in a synchronous or asynchronous way.
+     * Returns true if a connection attempt was made. If the implementation is synchronous
+     * it may also return true if the connection was successfully established, however
+     * callers shall not rely on that.
+     * <p/>
+     * The actual connection state change (successful or not) will be reported via the
+     * #getDevice device as a device change Intent.
+     *
+     * Note: the default implementation {@link AbstractDeviceSupport#connectFirstTime()} just
+     * calls {@link #connect()}
+     *
+     * @see #connect()
+     * @see GBDevice#ACTION_DEVICE_CHANGED
+     */
+    boolean connectFirstTime();
+
+    /**
+     * Attempts to establish a connection to the device. Implementations may perform
+     * the connection in a synchronous or asynchronous way.
+     * Returns true if a connection attempt was made. If the implementation is synchronous
+     * it may also return true if the connection was successfully established, however
+     * callers shall not rely on that.
+     * <p/>
+     * The actual connection state change (successful or not) will be reported via the
+     * #getDevice device as a device change Intent.
+     *
+     * @see #connectFirstTime()
+     * @see GBDevice#ACTION_DEVICE_CHANGED
+     */
+    boolean connect();
+
+    /**
+     * Disposes of this instance, closing all connections and freeing all resources.
+     * Instances will not be reused after having been disposed.
+     */
+    void dispose();
+
+    /**
+     * Returns true if a connection attempt shall be made automatically whenever
+     * needed (e.g. when a notification shall be sent to the device while not connected.
+     */
+    boolean useAutoConnect();
+
+    /**
+     * Configures this instance to automatically attempt to reconnect after a connection loss.
+     * How, how long, or how often is up to the implementation.
+     * Note that tome implementations may not support automatic reconnection at all.
+     * @param enable
+     */
+    void setAutoReconnect(boolean enable);
+
+    /**
+     * Returns whether this instance to configured to automatically attempt to reconnect after a
+     * connection loss.
+     */
+    boolean getAutoReconnect();
+
+    void setScanReconnect(boolean enable);
+
+    boolean getScanReconnect();
+
+    /**
+     * Returns the associated device this instance communicates with.
+     */
+    GBDevice getDevice();
+
+    /**
+     * Returns the bluetooth adapter. When we support different transports
+     * than Bluetooth, we should use a generic type T and rename this method
+     * to getTransportAdapter()
+     */
+    BluetoothAdapter getBluetoothAdapter();
+
+    /**
+     * Returns the Android context to use, e.g. to look up resources.
+     */
+    Context getContext();
+
+    /**
+     * converts String in a device specific way, e.g. re-map characters for a custom font
+     */
+    String customStringFilter(String inputString);
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceSupportFactory.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceSupportFactory.java
new file mode 100644
index 0000000..06488dc
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceSupportFactory.java
@@ -0,0 +1,159 @@
+/*  Copyright (C) 2015-2024 115ek, Andreas B枚hler, Andreas Shimokawa, Andrew
+    Watkins, angelpup, Carsten Pfeiffer, Cre3per, criogenic, DanialHanif, Daniel
+    Dakhno, Daniele Gobbetti, Daniel Thompson, Da Pa, Dmytro Bielik, Frank Ertl,
+    GeekosaurusR3x, Gordon Williams, Jean-Fran莽ois Greffier, jfgreffier, jhey,
+    Joa虄o Paulo Barraca, Jochen S, Johannes Krude, Jos茅 Rebelo, ladbsoft,
+    Lesur Frederic, mamucho, Manuel Ru脽, maxirnilian, mkusnierz, narektor,
+    Noodlez, odavo32nof, opavlov, pangwalla, Pavel Elagin, Petr Kadlec, Petr
+    Van臎k, protomors, Quallenauge, Quang Ng么, Raghd Hamzeh, Sami Alaoui,
+    Sebastian Kranz, sedy89, Sergey Trofimov, Sophanimus, Stefan Bora, Taavi
+    Eom盲e, thermatk, tiparega, Vadim Kaushan, x29a, xaos, Yukai Li
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service;
+
+import android.bluetooth.BluetoothAdapter;
+import android.content.Context;
+import android.util.Log;
+import android.widget.Toast;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import nodomain.freeyourgadget.gadgetbridge.GBException;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.externalevents.BluetoothConnectReceiver;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.PebbleSupport;
+import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.util.EnumSet;
+
+public class DeviceSupportFactory {
+    private static final Logger LOG = LoggerFactory.getLogger(DeviceSupportFactory.class);
+
+    private final BluetoothAdapter mBtAdapter;
+    private final Context mContext;
+
+    DeviceSupportFactory(Context context) {
+        mContext = context;
+        mBtAdapter = BluetoothAdapter.getDefaultAdapter();
+    }
+
+    public synchronized DeviceSupport createDeviceSupport(GBDevice device) throws GBException {
+        DeviceSupport deviceSupport;
+        String deviceAddress = device.getAddress();
+        int indexFirstColon = deviceAddress.indexOf(":");
+        if (indexFirstColon > 0) {
+            if (indexFirstColon == deviceAddress.lastIndexOf(":")) { // only one colon
+                deviceSupport = createTCPDeviceSupport(device);
+            } else {
+                // multiple colons -- bt?
+                deviceSupport = createBTDeviceSupport(device);
+            }
+        } else {
+            // no colon at all, maybe a class name?
+            deviceSupport = createClassNameDeviceSupport(device);
+        }
+
+        if (deviceSupport != null) {
+            return deviceSupport;
+        }
+
+        // no device found, check transport availability and warn
+        checkBtAvailability();
+        return null;
+    }
+
+    private DeviceSupport createClassNameDeviceSupport(GBDevice device) throws GBException {
+        String className = device.getAddress();
+        try {
+            Class<?> deviceSupportClass = Class.forName(className);
+            Constructor<?> constructor = deviceSupportClass.getConstructor();
+            DeviceSupport support = (DeviceSupport) constructor.newInstance();
+            // has to create the device itself
+            support.setContext(device, null, mContext);
+            return support;
+        } catch (ClassNotFoundException e) {
+            return null; // not a class, or not known at least
+        } catch (Exception e) {
+            throw new GBException("Error creating DeviceSupport instance for " + className, e);
+        }
+    }
+
+    private void checkBtAvailability() {
+        if (mBtAdapter == null) {
+            GB.toast(mContext.getString(R.string.bluetooth_is_not_supported_), Toast.LENGTH_SHORT, GB.WARN);
+        } else if (!mBtAdapter.isEnabled()) {
+            GB.toast(mContext.getString(R.string.bluetooth_is_disabled_), Toast.LENGTH_SHORT, GB.WARN);
+        }
+    }
+
+    private ServiceDeviceSupport createServiceDeviceSupport(GBDevice device) throws GBException {
+        DeviceCoordinator coordinator = device.getDeviceCoordinator();
+        Class supportClass = coordinator.getDeviceSupportClass();
+
+        try {
+            Constructor supportConstructor = supportClass.getConstructor(DeviceType.class);
+            DeviceSupport supportInstance = (DeviceSupport) supportConstructor.newInstance(device.getType());
+            return new ServiceDeviceSupport(supportInstance, coordinator.getInitialFlags());
+        } catch (NoSuchMethodException e) {
+            // ignore, let next call get the default, zero-argument constructor
+        } catch (ReflectiveOperationException e) {
+            LOG.error("error calling DeviceSupport constructor with argument 'DeviceType'");
+            throw new GBException(e);
+        }
+
+        try {
+            DeviceSupport supportInstance = (DeviceSupport) supportClass.newInstance();
+            return new ServiceDeviceSupport(supportInstance, coordinator.getInitialFlags());
+        } catch (ReflectiveOperationException e) {
+            LOG.error("error calling DeviceSupport constructor with zero arguments");
+            throw new GBException(e);
+        }
+    }
+
+    private DeviceSupport createBTDeviceSupport(GBDevice gbDevice) throws GBException {
+        if (mBtAdapter != null && mBtAdapter.isEnabled()) {
+            try {
+                DeviceSupport deviceSupport = createServiceDeviceSupport(gbDevice);
+                if (deviceSupport != null) {
+                    deviceSupport.setContext(gbDevice, mBtAdapter, mContext);
+                    return deviceSupport;
+                }
+            } catch (Exception e) {
+                throw new GBException(mContext.getString(R.string.cannot_connect_bt_address_invalid_), e);
+            }
+        }
+        return null;
+    }
+
+    private DeviceSupport createTCPDeviceSupport(GBDevice gbDevice) throws GBException {
+        try {
+            DeviceSupport deviceSupport = new ServiceDeviceSupport(new PebbleSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING));
+            deviceSupport.setContext(gbDevice, mBtAdapter, mContext);
+            return deviceSupport;
+        } catch (Exception e) {
+            throw new GBException("cannot connect to " + gbDevice, e); // FIXME: localize
+        }
+    }
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/HeadphoneHelper.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/HeadphoneHelper.java
new file mode 100644
index 0000000..b06e413
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/HeadphoneHelper.java
@@ -0,0 +1,161 @@
+/*  Copyright (C) 2024 Arjan Schrijver, Daniele Gobbetti, Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service;
+
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SPEAK_NOTIFICATIONS_FOCUS_EXCLUSIVE;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.media.AudioManager;
+import android.os.Handler;
+import android.os.Looper;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventCallControl;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
+import nodomain.freeyourgadget.gadgetbridge.util.GBTextToSpeech;
+
+public class HeadphoneHelper {
+    private static final Logger LOG = LoggerFactory.getLogger(HeadphoneHelper.class);
+
+    public interface Callback {
+        void evaluateGBDeviceEvent(GBDeviceEvent event);
+    }
+
+    private final GBDevice device;
+    private final GBTextToSpeech gbTextToSpeech;
+    private final Callback callback;
+
+    public HeadphoneHelper(Context context, GBDevice device, Callback callback) {
+        this.device = device;
+        this.callback = callback;
+        final SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(this.device.getAddress());
+        gbTextToSpeech = new GBTextToSpeech(context, new UtteranceProgressListener(),
+                prefs.getBoolean(PREF_SPEAK_NOTIFICATIONS_FOCUS_EXCLUSIVE, false) ?
+                        AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE :
+                        AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK
+        );
+    }
+
+    public void dispose() {
+        gbTextToSpeech.shutdown();
+    }
+
+    public void onSetCallState(CallSpec callSpec) {
+        SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(device.getAddress());
+
+        if (!prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_AUTO_REPLY_INCOMING_CALL, false))
+                return;
+
+        final int delayMillis = Integer.parseInt(prefs.getString(DeviceSettingsPreferenceConst.PREF_AUTO_REPLY_INCOMING_CALL_DELAY, "15")) * 1000;
+
+        if (CallSpec.CALL_INCOMING != callSpec.command)
+                return;
+
+        if (!gbTextToSpeech.isConnected()) { // schedule the automatic reply here, if the speech to text is not connected. Else it's done by the callback, and the timeout starts after the name or number have been spoken
+            Looper mainLooper = Looper.getMainLooper();
+            LOG.debug("Incoming call, scheduling auto answer in {} seconds.", delayMillis / 1000);
+
+            new Handler(mainLooper).postDelayed(() -> {
+                GBDeviceEventCallControl callCmd = new GBDeviceEventCallControl();
+                callCmd.event = GBDeviceEventCallControl.Event.ACCEPT;
+                callback.evaluateGBDeviceEvent(callCmd);
+            }, delayMillis); //15s
+
+            return;
+        }
+        String speechText = callSpec.name;
+        if (callSpec.name.equals(callSpec.number)) {
+            StringBuilder numberSpeller = new StringBuilder();
+            for (char c : callSpec.number.toCharArray()) {
+                numberSpeller.append(c).append(" ");
+            }
+            speechText = numberSpeller.toString();
+        }
+        gbTextToSpeech.speak(speechText);
+    }
+
+    public void onNotification(NotificationSpec notificationSpec) {
+        SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(device.getAddress());
+
+        if (!prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_SPEAK_NOTIFICATIONS_ALOUD, false))
+            return;
+
+        if (gbTextToSpeech.isConnected()) {
+            String notificationSpeller = new StringBuilder()
+                    .append(notificationSpec.sourceName == null ? "" : notificationSpec.sourceName).append(". ")
+                    .append(notificationSpec.title == null ? "" : notificationSpec.title).append(": ")
+                    .append(notificationSpec.body == null ? "" : notificationSpec.body).toString();
+
+            gbTextToSpeech.speakNotification(notificationSpeller);
+        }
+    }
+
+    /**
+     *
+     * @param config
+     * @return True if handled, false otherwise
+     */
+    public boolean onSendConfiguration(String config) {
+        if (PREF_SPEAK_NOTIFICATIONS_FOCUS_EXCLUSIVE.equals(config)) {
+            final SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(device.getAddress());
+            gbTextToSpeech.setAudioFocus(prefs.getBoolean(PREF_SPEAK_NOTIFICATIONS_FOCUS_EXCLUSIVE, false) ?
+                    AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE :
+                    AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK);
+            return true;
+        }
+        return false;
+    }
+
+    private class UtteranceProgressListener extends android.speech.tts.UtteranceProgressListener {
+        @Override
+        public void onStart(String utteranceId) {
+//            LOG.debug("UtteranceProgressListener onStart.");
+        }
+
+        @Override
+        public void onDone(String utteranceId) {
+//            LOG.debug("UtteranceProgressListener onDone.");
+
+            gbTextToSpeech.abandonFocus();
+            if (utteranceId.equals("call")) {
+                SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(HeadphoneHelper.this.device.getAddress());
+                final int delayMillis = Integer.parseInt(prefs.getString(DeviceSettingsPreferenceConst.PREF_AUTO_REPLY_INCOMING_CALL_DELAY, "15")) * 1000;
+
+
+                Looper mainLooper = Looper.getMainLooper();
+                new Handler(mainLooper).postDelayed(() -> {
+                    GBDeviceEventCallControl callCmd = new GBDeviceEventCallControl();
+                    callCmd.event = GBDeviceEventCallControl.Event.ACCEPT;
+                    callback.evaluateGBDeviceEvent(callCmd);
+                }, delayMillis); //15s
+            }
+        }
+
+        @Override
+        public void onError(String utteranceId) {
+            LOG.error("UtteranceProgressListener returned error.");
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/NotificationCollectorMonitorService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/NotificationCollectorMonitorService.java
new file mode 100644
index 0000000..15a3734
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/NotificationCollectorMonitorService.java
@@ -0,0 +1,95 @@
+/*  Copyright (C) 2017-2024 Daniele Gobbetti
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service;
+
+import android.app.ActivityManager;
+import android.app.Service;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.IBinder;
+import android.os.Process;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.externalevents.NotificationListener;
+
+/**
+ * Original source by xinghui - see https://gist.github.com/xinghui/b2ddd8cffe55c4b62f5d8846d5545bf9
+ * NB: no license specified in the source code as of 2017-04-19
+ */
+public class NotificationCollectorMonitorService extends Service {
+
+    private static final Logger LOG = LoggerFactory.getLogger(NotificationCollectorMonitorService.class);
+
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        ensureCollectorRunning();
+    }
+
+    @Override
+    public int onStartCommand(Intent intent, int flags, int startId) {
+        return START_STICKY;
+    }
+
+    private void ensureCollectorRunning() {
+        ComponentName collectorComponent = new ComponentName(this, NotificationListener.class);
+        LOG.info("ensureCollectorRunning collectorComponent: " + collectorComponent);
+        ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
+        boolean collectorRunning = false;
+        List<ActivityManager.RunningServiceInfo> runningServices = manager.getRunningServices(Integer.MAX_VALUE);
+        if (runningServices == null) {
+            LOG.info("ensureCollectorRunning() runningServices is NULL");
+            return;
+        }
+        for (ActivityManager.RunningServiceInfo service : runningServices) {
+            if (service.service.equals(collectorComponent)) {
+                LOG.warn("ensureCollectorRunning service - pid: " + service.pid + ", currentPID: " + Process.myPid() + ", clientPackage: " + service.clientPackage + ", clientCount: " + service.clientCount
+                        + ", clientLabel: " + ((service.clientLabel == 0) ? "0" : "(" + getResources().getString(service.clientLabel) + ")"));
+                if (service.pid == Process.myPid() /*&& service.clientCount > 0 && !TextUtils.isEmpty(service.clientPackage)*/) {
+                    collectorRunning = true;
+                }
+            }
+        }
+        if (collectorRunning) {
+            LOG.debug("ensureCollectorRunning: collector is running");
+            return;
+        }
+        LOG.debug("ensureCollectorRunning: collector not running, reviving...");
+        toggleNotificationListenerService();
+    }
+
+    private void toggleNotificationListenerService() {
+        LOG.debug("toggleNotificationListenerService() called");
+        ComponentName thisComponent = new ComponentName(this, NotificationListener.class);
+        PackageManager pm = getPackageManager();
+        pm.setComponentEnabledSetting(thisComponent, PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
+        pm.setComponentEnabledSetting(thisComponent, PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP);
+
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return null;
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/ServiceDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/ServiceDeviceSupport.java
new file mode 100644
index 0000000..407bcd6
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/ServiceDeviceSupport.java
@@ -0,0 +1,544 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, Arjan Schrijver, Carsten
+    Pfeiffer, Daniel Dakhno, Jos茅 Rebelo, Julien Pivotto, Kasha, Sebastian Kranz,
+    Steffen Liebergeld
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service;
+
+import android.bluetooth.BluetoothAdapter;
+import android.content.Context;
+import android.location.Location;
+import android.net.Uri;
+import android.os.Bundle;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.EnumSet;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.capabilities.loyaltycards.LoyaltyCard;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventCameraRemote;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceMusic;
+import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
+import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.Contact;
+import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.NavigationInfoSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.Reminder;
+import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.WorldClock;
+
+/**
+ * Wraps another device support instance and supports busy-checking and throttling of events.
+ */
+public class ServiceDeviceSupport implements DeviceSupport {
+    public static enum Flags {
+        THROTTLING,
+        BUSY_CHECKING,
+    }
+
+    private static final Logger LOG = LoggerFactory.getLogger(ServiceDeviceSupport.class);
+
+    private static final long THROTTLING_THRESHOLD = 1000; // throttle multiple events in between one second
+    private final DeviceSupport delegate;
+
+    private long lastNotificationTime = 0;
+    private String lastNotificationKind;
+    private final EnumSet<Flags> flags;
+
+    public ServiceDeviceSupport(DeviceSupport delegate, EnumSet<Flags> flags) {
+        this.delegate = delegate;
+        this.flags = flags;
+    }
+
+    @Override
+    public void setContext(GBDevice gbDevice, BluetoothAdapter btAdapter, Context context) {
+        delegate.setContext(gbDevice, btAdapter, context);
+    }
+
+    @Override
+    public boolean isConnected() {
+        return delegate.isConnected();
+    }
+
+    @Override
+    public boolean connectFirstTime() {
+        return delegate.connectFirstTime();
+    }
+
+    @Override
+    public boolean connect() {
+        return delegate.connect();
+    }
+
+    @Override
+    public void setAutoReconnect(boolean enable) {
+        delegate.setAutoReconnect(enable);
+    }
+
+    @Override
+    public boolean getAutoReconnect() {
+        return delegate.getAutoReconnect();
+    }
+
+    @Override
+    public void setScanReconnect(boolean enable) {
+        delegate.setScanReconnect(enable);
+    }
+
+    @Override
+    public boolean getScanReconnect(){
+        return delegate.getScanReconnect();
+    }
+
+    @Override
+    public void dispose() {
+        delegate.dispose();
+    }
+
+    @Override
+    public GBDevice getDevice() {
+        return delegate.getDevice();
+    }
+
+    @Override
+    public BluetoothAdapter getBluetoothAdapter() {
+        return delegate.getBluetoothAdapter();
+    }
+
+    @Override
+    public Context getContext() {
+        return delegate.getContext();
+    }
+
+    @Override
+    public String customStringFilter(String inputString) {
+        return delegate.customStringFilter(inputString);
+    }
+
+    @Override
+    public boolean useAutoConnect() {
+        return delegate.useAutoConnect();
+    }
+
+    private boolean checkBusy(String notificationKind) {
+        if (!flags.contains(Flags.BUSY_CHECKING)) {
+            return false;
+        }
+        if (getDevice().isBusy()) {
+            LOG.info("Ignoring " + notificationKind + " because we're busy with " + getDevice().getBusyTask());
+            return true;
+        }
+        return false;
+    }
+
+    private boolean checkThrottle(String notificationKind) {
+        if (!flags.contains(Flags.THROTTLING)) {
+            return false;
+        }
+        long currentTime = System.currentTimeMillis();
+        if ((currentTime - lastNotificationTime) < THROTTLING_THRESHOLD) {
+            if (notificationKind != null && notificationKind.equals(lastNotificationKind)) {
+                LOG.info("Ignoring " + notificationKind + " because of throttling threshold reached");
+                return true;
+            }
+        }
+        lastNotificationTime = currentTime;
+        lastNotificationKind = notificationKind;
+        return false;
+    }
+
+    @Override
+    public void onNotification(NotificationSpec notificationSpec) {
+        if (checkBusy("generic notification") || checkThrottle("generic notification")) {
+            return;
+        }
+        delegate.onNotification(notificationSpec);
+    }
+
+    @Override
+    public void onDeleteNotification(int id) {
+        delegate.onDeleteNotification(id);
+    }
+
+    @Override
+    public void onSetTime() {
+        if (checkBusy("set time") || checkThrottle("set time")) {
+            return;
+        }
+        delegate.onSetTime();
+    }
+
+    // No throttling for the other events
+
+    @Override
+    public void onSetCallState(CallSpec callSpec) {
+        if (checkBusy("set call state")) {
+            return;
+        }
+        delegate.onSetCallState(callSpec);
+    }
+
+    @Override
+    public void onSetCannedMessages(CannedMessagesSpec cannedMessagesSpec) {
+        if (checkBusy("set canned messages")) {
+            return;
+        }
+        delegate.onSetCannedMessages(cannedMessagesSpec);
+    }
+
+    @Override
+    public void onSetMusicState(MusicStateSpec stateSpec) {
+        if (checkBusy("set music state")) {
+            return;
+        }
+        delegate.onSetMusicState(stateSpec);
+    }
+
+    @Override
+    public void onSetMusicInfo(MusicSpec musicSpec) {
+        if (checkBusy("set music info")) {
+            return;
+        }
+        delegate.onSetMusicInfo(musicSpec);
+    }
+
+    @Override
+    public void onSetPhoneVolume(float volume) {
+        if (checkBusy("set phone volume")) {
+            return;
+        }
+        delegate.onSetPhoneVolume(volume);
+    }
+
+    @Override
+    public void onChangePhoneSilentMode(final int ringerMode) {
+        if (checkBusy("change phone silent mode")) {
+            return;
+        }
+        delegate.onChangePhoneSilentMode(ringerMode);
+    }
+
+    @Override
+    public void onSetNavigationInfo(NavigationInfoSpec navigationInfoSpec) {
+        if (checkBusy("set navigation info")) {
+            return;
+        }
+        delegate.onSetNavigationInfo(navigationInfoSpec);
+    }
+
+    @Override
+    public void onInstallApp(Uri uri) {
+        if (checkBusy("install app")) {
+            return;
+        }
+        delegate.onInstallApp(uri);
+    }
+
+    @Override
+    public void onAppInfoReq() {
+        if (checkBusy("app info request")) {
+            return;
+        }
+        delegate.onAppInfoReq();
+    }
+
+    @Override
+    public void onAppStart(UUID uuid, boolean start) {
+        if (checkBusy("app start")) {
+            return;
+        }
+        delegate.onAppStart(uuid, start);
+    }
+
+    @Override
+    public void onAppDownload(UUID uuid) {
+        if (checkBusy("app download")) {
+            return;
+        }
+        delegate.onAppDownload(uuid);
+    }
+
+    @Override
+    public void onAppDelete(UUID uuid) {
+        if (checkBusy("app delete")) {
+            return;
+        }
+        delegate.onAppDelete(uuid);
+    }
+
+    @Override
+    public void onAppConfiguration(UUID uuid, String config, Integer id) {
+        if (checkBusy("app configuration")) {
+            return;
+        }
+        delegate.onAppConfiguration(uuid, config, id);
+    }
+
+    @Override
+    public void onAppReorder(UUID[] uuids) {
+        if (checkBusy("app reorder")) {
+            return;
+        }
+        delegate.onAppReorder(uuids);
+    }
+
+    @Override
+    public void onFetchRecordedData(int dataTypes) {
+        if (checkBusy("fetch activity data")) {
+            return;
+        }
+        delegate.onFetchRecordedData(dataTypes);
+    }
+
+    @Override
+    public void onReset(int flags) {
+        if (checkBusy("reset")) {
+            return;
+        }
+        delegate.onReset(flags);
+    }
+
+    @Override
+    public void onHeartRateTest() {
+        if (checkBusy("heartrate")) {
+            return;
+        }
+        delegate.onHeartRateTest();
+    }
+
+    @Override
+    public void onFindDevice(boolean start) {
+        if (checkBusy("find device")) {
+            return;
+        }
+        delegate.onFindDevice(start);
+    }
+
+    @Override
+    public void onFindPhone(final boolean start) {
+        if (checkBusy("phone found")) {
+            return;
+        }
+        delegate.onFindPhone(start);
+    }
+
+    @Override
+    public void onSetConstantVibration(int intensity) {
+        if (checkBusy("set constant vibration")) {
+            return;
+        }
+        delegate.onSetConstantVibration(intensity);
+    }
+
+    @Override
+    public void onScreenshotReq() {
+        if (checkBusy("request screenshot")) {
+            return;
+        }
+        delegate.onScreenshotReq();
+    }
+
+    @Override
+    public void onSetAlarms(ArrayList<? extends Alarm> alarms) {
+        if (checkBusy("set alarms")) {
+            return;
+        }
+        delegate.onSetAlarms(alarms);
+    }
+
+    @Override
+    public void onSetReminders(ArrayList<? extends Reminder> reminders) {
+        if (checkBusy("set reminders")) {
+            return;
+        }
+        delegate.onSetReminders(reminders);
+    }
+
+    @Override
+    public void onSetContacts(ArrayList<? extends Contact> contacts) {
+        if (checkBusy("set contacts")) {
+            return;
+        }
+        delegate.onSetContacts(contacts);
+    }
+
+    public void onSetLoyaltyCards(final ArrayList<LoyaltyCard> cards) {
+        if (checkBusy("set loyalty cards")) {
+            return;
+        }
+        delegate.onSetLoyaltyCards(cards);
+    }
+
+    @Override
+    public void onSetWorldClocks(ArrayList<? extends WorldClock> clocks) {
+        if (checkBusy("set world clocks")) {
+            return;
+        }
+        delegate.onSetWorldClocks(clocks);
+    }
+
+    @Override
+    public void onEnableRealtimeSteps(boolean enable) {
+        if (checkBusy("enable realtime steps: " + enable)) {
+            return;
+        }
+        delegate.onEnableRealtimeSteps(enable);
+    }
+
+    @Override
+    public void onEnableHeartRateSleepSupport(boolean enable) {
+        if (checkBusy("enable heart rate sleep support: " + enable)) {
+            return;
+        }
+        delegate.onEnableHeartRateSleepSupport(enable);
+    }
+
+    @Override
+    public void onSetHeartRateMeasurementInterval(int seconds) {
+        if (checkBusy("set heart rate measurement interval: " + seconds + "s")) {
+            return;
+        }
+        delegate.onSetHeartRateMeasurementInterval(seconds);
+    }
+
+    @Override
+    public void onEnableRealtimeHeartRateMeasurement(boolean enable) {
+        if (checkBusy("enable realtime heart rate measurement: " + enable)) {
+            return;
+        }
+        delegate.onEnableRealtimeHeartRateMeasurement(enable);
+    }
+
+    @Override
+    public void onAddCalendarEvent(CalendarEventSpec calendarEventSpec) {
+        if (checkBusy("add calendar event")) {
+            return;
+        }
+        delegate.onAddCalendarEvent(calendarEventSpec);
+    }
+
+    @Override
+    public void onDeleteCalendarEvent(byte type, long id) {
+        if (checkBusy("delete calendar event")) {
+            return;
+        }
+        delegate.onDeleteCalendarEvent(type, id);
+    }
+
+    @Override
+    public void onSendConfiguration(String config) {
+        if (checkBusy("send configuration: " + config)) {
+            return;
+        }
+        delegate.onSendConfiguration(config);
+    }
+
+    @Override
+    public void onReadConfiguration(String config) {
+        if (checkBusy("read configuration: " + config)) {
+            return;
+        }
+        delegate.onReadConfiguration(config);
+    }
+
+    @Override
+    public void onTestNewFunction() {
+        if (checkBusy("test new function event")) {
+            return;
+        }
+        delegate.onTestNewFunction();
+    }
+
+    @Override
+    public void onSendWeather(ArrayList<WeatherSpec> weatherSpecs) {
+        if (checkBusy("send weather events")) {
+            return;
+        }
+        delegate.onSendWeather(weatherSpecs);
+    }
+
+    @Override
+    public void onSetFmFrequency(float frequency) {
+        if (checkBusy("set frequency event")) {
+            return;
+        }
+        delegate.onSetFmFrequency(frequency);
+    }
+
+    @Override
+    public void onSetLedColor(int color) {
+        if (checkBusy("set led color event")) {
+            return;
+        }
+        delegate.onSetLedColor(color);
+    }
+
+    @Override
+    public void onPowerOff() {
+        if (checkBusy("power off event")) {
+            return;
+        }
+        delegate.onPowerOff();
+    }
+
+    @Override
+    public void onSetGpsLocation(Location location) {
+        if (checkBusy("set gps location")) {
+            return;
+        }
+        delegate.onSetGpsLocation(location);
+    }
+
+    @Override
+    public void onSleepAsAndroidAction(String action, Bundle extras) {
+        if (checkBusy("sleep as android")) {
+            return;
+        }
+        delegate.onSleepAsAndroidAction(action, extras);
+    }
+
+    @Override
+    public void onCameraStatusChange(GBDeviceEventCameraRemote.Event event, String filename) {
+        if (checkBusy("camera status")) {
+            return;
+        }
+        delegate.onCameraStatusChange(event, filename);
+    }
+
+    @Override
+    public void onMusicListReq() {
+        if (checkBusy("music list request")) {
+            return;
+        }
+        delegate.onMusicListReq();
+    }
+
+    @Override
+    public void onMusicOperation(int operation, int playlistIndex, String playlistName, ArrayList<Integer> musicIds) {
+        if (checkBusy("music operation")) {
+            return;
+        }
+        delegate.onMusicOperation(operation, playlistIndex, playlistName, musicIds);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/SleepAsAndroidSender.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/SleepAsAndroidSender.java
new file mode 100644
index 0000000..ec37627
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/SleepAsAndroidSender.java
@@ -0,0 +1,573 @@
+package nodomain.freeyourgadget.gadgetbridge.service;
+
+import android.content.Context;
+import android.content.Intent;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Set;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.devices.SleepAsAndroidFeature;
+import nodomain.freeyourgadget.gadgetbridge.externalevents.sleepasandroid.SleepAsAndroidAction;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+public class SleepAsAndroidSender {
+
+    private final Logger LOG = LoggerFactory.getLogger(SleepAsAndroidSender.class);
+    private final String PACKAGE_SLEEP_AS_ANDROID = "com.urbandroid.sleep";
+    private final String ACTION_EXTRA_DATA_UPDATE = "com.urbandroid.sleep.ACTION_EXTRA_DATA_UPDATE";
+    private final String ACTION_MOVEMENT_DATA_UPDATE = "com.urbandroid.sleep.watch.DATA_UPDATE";
+    private final String ACTION_HEART_RATE_DATA_UPDATE = "com.urbandroid.sleep.watch.HR_DATA_UPDATE";
+    private final String ACTION_RESUME_FROM_WATCH = "com.urbandroid.sleep.watch.RESUME_FROM_WATCH";
+    private final String ACTION_PAUSE_FROM_WATCH = "com.urbandroid.sleep.watch.PAUSE_FROM_WATCH";
+    private final String ACTION_SNOOZE_FROM_WATCH = "com.urbandroid.sleep.watch.SNOOZE_FROM_WATCH";
+    private final String ACTION_DISMISS_FROM_WATCH = "com.urbandroid.sleep.watch.DISMISS_FROM_WATCH";
+
+    private final String MAX_RAW_DATA = "MAX_RAW_DATA";
+    private final String DATA = "DATA";
+    private final String EXTRA_DATA_HR = "com.urbandroid.sleep.EXTRA_DATA_HR";
+    private final String EXTRA_DATA_RR = "com.urbandroid.sleep.EXTRA_DATA_RR";
+    private final String EXTRA_DATA_SPO2 = "com.urbandroid.sleep.EXTRA_DATA_SPO2";
+    private final String EXTRA_DATA_SDNN = "com.urbandroid.sleep.EXTRA_DATA_SDNN";
+    private final String EXTRA_DATA_TIMESTAMP = "com.urbandroid.sleep.EXTRA_DATA_TIMESTAMP";
+    private final String EXTRA_DATA_FRAMERATE = "com.urbandroid.sleep.EXTRA_DATA_FRAMERATE";
+    private final String EXTRA_DATA_BATCH = "com.urbandroid.sleep.EXTRA_DATA_BATCH";
+
+
+    private GBDevice device;
+    private boolean trackingOngoing = false;
+    private boolean trackingPaused = false;
+
+    private ScheduledExecutorService trackingPauseScheduler;
+    private long batchSize = 1;
+    private long lastRawDataMs = 0;
+    private float maxRawData = 0;
+    private long lastHrDataMs = 0;
+    private ArrayList<Float> hrData = new ArrayList<>();
+
+    private ArrayList<Float> accData = new ArrayList<>();
+    private ScheduledExecutorService accDataScheduler;
+    private Set<SleepAsAndroidFeature> features;
+
+    public SleepAsAndroidSender(GBDevice gbDevice) {
+        this.device = gbDevice;
+        this.features = gbDevice.getDeviceCoordinator().getSleepAsAndroidFeatures();
+    }
+
+    /**
+     * Check if a SleepAsAndroid feature is enabled
+     *
+     * @param feature the feature
+     * @return true if the feature is enabled
+     */
+    public boolean hasFeature(SleepAsAndroidFeature feature) {
+        return this.features.contains(feature);
+    }
+
+    /**
+     * Check if a SleepAsAndroid feature is enabled
+     * @param feature
+     * @return
+     */
+    public boolean isFeatureEnabled(SleepAsAndroidFeature feature) {
+        boolean enabled = isSleepAsAndroidEnabled();
+        if (enabled) {
+            switch (feature) {
+                case ACCELEROMETER:
+                    enabled = GBApplication.getPrefs().getBoolean("pref_key_sleepasandroid_feat_movement", false);
+                    break;
+                case HEART_RATE:
+                    enabled = GBApplication.getPrefs().getBoolean("pref_key_sleepasandroid_feat_hr", false);
+                    break;
+                case SPO2:
+                    enabled = GBApplication.getPrefs().getBoolean("pref_key_sleepasandroid_feat_spo2", false);
+                    break;
+                case OXIMETRY:
+                    enabled = GBApplication.getPrefs().getBoolean("pref_key_sleepasandroid_feat_oximetry", false);
+                    break;
+                case NOTIFICATIONS:
+                    enabled = GBApplication.getPrefs().getBoolean("pref_key_sleepasandroid_feat_notifications", false);
+                    break;
+                case ALARMS:
+                    enabled = GBApplication.getPrefs().getBoolean("pref_key_sleepasandroid_feat_alarms", false);
+                    break;
+                default:
+                    break;
+            }
+        }
+        return enabled;
+    }
+
+    /**
+     * Get all enabled features
+     *
+     * @return all enabled features
+     */
+    public Set<SleepAsAndroidFeature> getFeatures() {
+        return features;
+    }
+
+    /**
+     * Start tracking
+     */
+    public void startTracking() {
+        if (!isDeviceDefault()) return;
+
+        stopTracking();
+
+        accDataScheduler = Executors.newSingleThreadScheduledExecutor();
+        accDataScheduler.scheduleAtFixedRate(new Runnable() {
+            @Override
+            public void run() {
+                aggregateAndSendAccelData();
+            }
+        }, 9999, 9999, TimeUnit.MILLISECONDS);
+
+        lastRawDataMs = System.currentTimeMillis();
+        lastHrDataMs = System.currentTimeMillis();
+
+        this.trackingOngoing = true;
+    }
+
+    /**
+     * Stop tracking
+     */
+    public void stopTracking() {
+        if (!isDeviceDefault() || !trackingOngoing) return;
+        if (accDataScheduler != null) {
+            accDataScheduler.shutdownNow();
+            accDataScheduler = null;
+        }
+
+        this.trackingOngoing = false;
+        this.hrData = new ArrayList<>();
+        this.accData = new ArrayList<>();
+        this.lastHrDataMs = 0;
+        this.lastRawDataMs = 0;
+    }
+
+    /**
+     * Pause tracking
+     *
+     * @param timeout the timeout in milliseconds before resuming
+     */
+    public void pauseTracking(long timeout) {
+        if (!isDeviceDefault() || !trackingOngoing) return;
+
+        if (timeout <= 0) {
+            resumeTracking();
+            return;
+        }
+
+        pauseTracking();
+        trackingPauseScheduler = setPauseTracking(timeout);
+    }
+
+    /**
+     * Same as {@link #pauseTracking(long)} but pausing and resuming is controlled by a toggle
+     *
+     * @param suspended true if the tracking should be paused, false if should be resumed
+     */
+    public void pauseTracking(boolean suspended) {
+        if (!isDeviceDefault() || !trackingOngoing) return;
+        trackingPaused = suspended;
+        if (!trackingPaused) {
+            resumeTracking();
+            return;
+        }
+    }
+
+    /**
+     * Set a scheduler to resume tracking
+     *
+     * @param delay the delay
+     * @return the scheduler
+     */
+    private ScheduledExecutorService setPauseTracking(long delay) {
+        trackingPaused = true;
+        trackingPauseScheduler = Executors.newSingleThreadScheduledExecutor();
+        trackingPauseScheduler.schedule(new Runnable() {
+            @Override
+            public void run() {
+                resumeTracking();
+            }
+        }, delay, TimeUnit.MILLISECONDS);
+
+        return trackingPauseScheduler;
+    }
+
+    /**
+     * Pause tracking
+     */
+    private void pauseTracking() {
+        if (!isDeviceDefault() && trackingPaused) return;
+        trackingPaused = true;
+        stopTracking();
+    }
+
+    /**
+     * Resume tracking
+     */
+    private void resumeTracking() {
+        if (!isDeviceDefault() && !trackingPaused) return;
+        if (trackingPauseScheduler != null) {
+            trackingPauseScheduler.shutdownNow();
+            trackingPauseScheduler = null;
+        }
+        trackingPaused = false;
+        startTracking();
+    }
+
+    /**
+     * Set the batch size
+     *
+     * @param batchSize the batch size
+     */
+    public void setBatchSize(long batchSize) {
+        if (!isDeviceDefault()) return;
+        LOG.debug("Setting batch size to " + batchSize);
+        this.batchSize = batchSize;
+    }
+
+    /**
+     * Confirm that the device is connected
+     */
+    public void confirmConnected() {
+        if (!isDeviceDefault()) return;
+        LOG.debug("Confirming connected");
+        Intent intent = new Intent(SleepAsAndroidAction.CONFIRM_CONNECTED);
+        broadcastToSleepAsAndroid(intent);
+    }
+
+    /**
+     * On accelerometer changed
+     *
+     * @param x the x value
+     * @param y the y value
+     * @param z the z value
+     */
+    public void onAccelChanged(float x, float y, float z) {
+        if (!isDeviceDefault() || !isFeatureEnabled(SleepAsAndroidFeature.ACCELEROMETER) || !hasFeature(SleepAsAndroidFeature.ACCELEROMETER) || !trackingOngoing)
+            return;
+        if (trackingPaused)
+            return;
+
+        updateMaxRawData(x, y, z);
+    }
+
+    /**
+     * Aggregate and send the acceleration data
+     */
+    private synchronized void aggregateAndSendAccelData() {
+        if (!trackingOngoing || trackingPaused) return;
+        if (maxRawData > 0) {
+            accData.add(maxRawData);
+            maxRawData = 0;
+            if (accData.size() == batchSize) {
+                sendAccelData();
+            }
+        }
+    }
+
+    /**
+     * Send the acceleration data
+     */
+    private void sendAccelData() {
+        LOG.debug("Sending movement data: " + this.accData + " batch size: " + batchSize + " array size: " + accData.size());
+        Intent intent = new Intent(ACTION_MOVEMENT_DATA_UPDATE);
+        intent.putExtra(MAX_RAW_DATA, convertToFloatArray(this.accData));
+        accData.clear();
+        broadcastToSleepAsAndroid(intent);
+    }
+
+    /**
+     * Update the max raw data
+     * @param x the x value
+     * @param y the y value
+     * @param z the z value
+     */
+    private void updateMaxRawData(float x, float y, float z) {
+        float maxRaw = calculateAccelerationMagnitude(x, y, z);
+        if (maxRaw > maxRawData) {
+            maxRawData = maxRaw;
+        }
+    }
+
+    /**
+     * Calculate the acceleration magnitude
+     * @param x the x value
+     * @param y the y value
+     * @param z the z value
+     * @return
+     */
+    protected float calculateAccelerationMagnitude(float x, float y, float z) {
+        double sqrt = Math.sqrt((x * x) + (y * y) + (z * z));
+        return (float)sqrt;
+    }
+
+    /**
+     * On heart rate changed
+     *
+     * @param hr        the heart rate
+     * @param sendDelay the send delay in ms. If 0 the data will be send right away. Anything bigger will gather all the data then send it all after the specified interval
+     */
+    public void onHrChanged(float hr, long sendDelay) {
+        if (!isDeviceDefault() || !isFeatureEnabled(SleepAsAndroidFeature.HEART_RATE) || !hasFeature(SleepAsAndroidFeature.HEART_RATE) || !trackingOngoing)
+            return;
+        if (trackingPaused) return;
+
+        updateLastHrData(hr);
+
+        if (lastHrDataMs == 0) {
+            lastHrDataMs = System.currentTimeMillis();
+        }
+        long ms = System.currentTimeMillis();
+        if (ms - lastHrDataMs >= sendDelay) {
+            lastHrDataMs = ms;
+            sendHrData();
+        }
+    }
+
+    /**
+     * Send the heart rate data
+     */
+    private synchronized void sendHrData() {
+        LOG.debug("Sending heart rate data: " + this.hrData);
+        Intent intent = new Intent(ACTION_HEART_RATE_DATA_UPDATE);
+        intent.putExtra(DATA, convertToFloatArray(this.hrData));
+        broadcastToSleepAsAndroid(intent);
+        this.hrData.clear();
+    }
+
+    /**
+     * Update the last heart rate data
+     * @param hr the heart rate
+     */
+    private void updateLastHrData(float hr) {
+        this.hrData.add(hr);
+    }
+
+    /**
+     * This is a generic intent to carry data from various sensors.
+     * See Sleep As Android documentation for parameters values.
+     * <a href="https://docs.sleep.urbandroid.org/devs/wearable_api.html#send-various-body-sensors-data-hr-rr-spo2-sdnn">...</a>
+     */
+    public synchronized void sendExtra(Float hr, Long extraDataTimestamp, Long extraDataFramerate, ArrayList<Float> extraDataRR, ArrayList<Float> spo2Batch, Float sdnn, ArrayList<Float> extraDataBatch) {
+
+        if (!isDeviceDefault() || !trackingOngoing) return;
+        if (trackingPaused) return;
+        Context context = GBApplication.getContext();
+        Intent intent = new Intent(ACTION_EXTRA_DATA_UPDATE);
+
+        // Heart Rate
+        if (hr != null && (hasFeature(SleepAsAndroidFeature.HEART_RATE) && isFeatureEnabled(SleepAsAndroidFeature.HEART_RATE))) {
+            intent.putExtra(EXTRA_DATA_HR, hr);
+        }
+
+        // SpO2
+        if (spo2Batch != null && (hasFeature(SleepAsAndroidFeature.SPO2) && isFeatureEnabled(SleepAsAndroidFeature.SPO2))) {
+            intent.putExtra(EXTRA_DATA_SPO2, true);
+            intent.putExtra(EXTRA_DATA_BATCH, convertToFloatArray(spo2Batch));
+        }
+
+        // SDNN
+        if (sdnn != null && (hasFeature(SleepAsAndroidFeature.HEART_RATE) && isFeatureEnabled(SleepAsAndroidFeature.HEART_RATE))) {
+            intent.putExtra(EXTRA_DATA_SDNN, sdnn);
+        }
+
+        // RR Intervals
+        if (extraDataRR != null && (hasFeature(SleepAsAndroidFeature.HEART_RATE) && isFeatureEnabled(SleepAsAndroidFeature.HEART_RATE))) {
+            intent.putExtra(EXTRA_DATA_RR, convertToFloatArray(extraDataRR));
+        }
+
+        if (extraDataBatch != null) {
+            if (isDeviceDefault()) {
+                for (int i = 0; i < extraDataBatch.size(); i++) {
+                    extraDataBatch.set(i, 0.0f);
+                }
+            }
+            intent.putExtra(EXTRA_DATA_BATCH, convertToFloatArray(extraDataBatch));
+        }
+
+        if (extraDataTimestamp != null) {
+            intent.putExtra(EXTRA_DATA_TIMESTAMP, extraDataTimestamp);
+        }
+        if (extraDataFramerate != null) {
+            intent.putExtra(EXTRA_DATA_FRAMERATE, extraDataFramerate);
+        }
+
+        context.sendBroadcast(intent);
+    }
+
+    /**
+     * This is a generic intent to carry data from various sensors.
+     * See Sleep As Android documentation for parameters values.
+     * <a href="https://docs.sleep.urbandroid.org/devs/wearable_api.html#send-various-body-sensors-data-hr-rr-spo2-sdnn">...</a>
+     */
+    public void sendExtra(Float hr, Float extraDataRR, Float spo2, Float sdnn, Long sdnnTimestamp) {
+
+        if (!isDeviceDefault() || !trackingOngoing) return;
+        if (trackingPaused) return;
+        Intent intent = new Intent(ACTION_EXTRA_DATA_UPDATE);
+
+        // Heart Rate
+        if (hr != null && (hasFeature(SleepAsAndroidFeature.HEART_RATE) && isFeatureEnabled(SleepAsAndroidFeature.HEART_RATE))) {
+            intent.putExtra(EXTRA_DATA_HR, hr);
+        }
+
+        // SpO2
+        if (spo2 != null && (hasFeature(SleepAsAndroidFeature.SPO2) && isFeatureEnabled(SleepAsAndroidFeature.SPO2))) {
+            intent.putExtra(EXTRA_DATA_SPO2, spo2);
+        }
+
+        // SDNN
+        if (sdnn != null && (hasFeature(SleepAsAndroidFeature.HEART_RATE) && isFeatureEnabled(SleepAsAndroidFeature.HEART_RATE))) {
+            intent.putExtra(EXTRA_DATA_SDNN, sdnn);
+        }
+
+        if (extraDataRR != null && (hasFeature(SleepAsAndroidFeature.HEART_RATE) && isFeatureEnabled(SleepAsAndroidFeature.HEART_RATE))) {
+            intent.putExtra(EXTRA_DATA_RR, extraDataRR);
+        }
+
+        if (sdnnTimestamp != null) {
+            intent.putExtra(EXTRA_DATA_TIMESTAMP, sdnnTimestamp);
+        }
+        broadcastToSleepAsAndroid(intent);
+    }
+
+    /**
+     * Adds 5 minutes of tracking pause time
+     */
+    public void sendPauseTracking() {
+        if (!isDeviceDefault() || !trackingOngoing) return;
+        LOG.debug("Sending pause");
+        Intent intent = new Intent(ACTION_PAUSE_FROM_WATCH);
+        broadcastToSleepAsAndroid(intent);
+    }
+
+    /**
+     * Resume tracking
+     */
+    public void sendResumeTracking() {
+        if (!isDeviceDefault() || !trackingOngoing) return;
+        LOG.debug("Sending resume");
+        Intent intent = new Intent(ACTION_RESUME_FROM_WATCH);
+        broadcastToSleepAsAndroid(intent);
+    }
+
+    /**
+     * Snooze the current alarm
+     */
+    public void sendSnoozeAlarm() {
+        if (!isDeviceDefault()) return;
+        if (!hasFeature(SleepAsAndroidFeature.ALARMS) || !isFeatureEnabled(SleepAsAndroidFeature.ALARMS)) return;
+        LOG.debug("Sending snooze");
+        Intent intent = new Intent(ACTION_SNOOZE_FROM_WATCH);
+        broadcastToSleepAsAndroid(intent);
+    }
+
+    /**
+     * Dismiss the current alarm
+     */
+    public void sendDismissAlarm() {
+        if (!isDeviceDefault()) return;
+        if (!hasFeature(SleepAsAndroidFeature.ALARMS) || !isFeatureEnabled(SleepAsAndroidFeature.ALARMS)) return;
+        LOG.debug("Sending dismiss");
+        Intent intent = new Intent(ACTION_DISMISS_FROM_WATCH);
+        broadcastToSleepAsAndroid(intent);
+    }
+
+    private void broadcastToSleepAsAndroid(Intent intent) {
+        if (!isDeviceDefault()) return;
+        intent.setPackage(PACKAGE_SLEEP_AS_ANDROID);
+        GBApplication.getContext().sendBroadcast(intent);
+    }
+
+    /**
+     * Check if the device is set as default provider for Sleep As Android
+     *
+     * @return true if the device is set as default
+     */
+    public boolean isDeviceDefault() {
+        if (device == null || !device.isInitialized()) return false;
+        if (isSleepAsAndroidEnabled()) {
+            return device.getAddress().equals(GBApplication.getPrefs().getString("sleepasandroid_device", ""));
+        }
+        return false;
+    }
+
+    public boolean isSleepAsAndroidEnabled() {
+        return GBApplication.getPrefs().getBoolean("pref_key_sleepasandroid_enable", false);
+    }
+
+    /**
+     * Validate if the device is allowed to receive a specific action
+     * @param action the action send my the broadcast receiver
+     */
+    public void validateAction(String action) {
+        if (isDeviceDefault()) {
+            switch (action) {
+                case SleepAsAndroidAction.HINT:
+                case SleepAsAndroidAction.SHOW_NOTIFICATION:
+                    if (!hasFeature(SleepAsAndroidFeature.NOTIFICATIONS) || !isFeatureEnabled(SleepAsAndroidFeature.NOTIFICATIONS)) {
+                        throw new UnsupportedOperationException("Action not valid");
+                    }
+                    break;
+                case SleepAsAndroidAction.START_ALARM:
+                case SleepAsAndroidAction.STOP_ALARM:
+                case SleepAsAndroidAction.UPDATE_ALARM:
+                    if (!hasFeature(SleepAsAndroidFeature.ALARMS) || !isFeatureEnabled(SleepAsAndroidFeature.ALARMS)) {
+                        throw new UnsupportedOperationException("Action not valid");
+                    }
+                    break;
+                case SleepAsAndroidAction.START_TRACKING:
+                case SleepAsAndroidAction.STOP_TRACKING:
+                case SleepAsAndroidAction.SET_BATCH_SIZE:
+                    if (!hasFeature(SleepAsAndroidFeature.ACCELEROMETER) || !isFeatureEnabled(SleepAsAndroidFeature.ACCELEROMETER)) {
+                        throw new UnsupportedOperationException("Action not valid");
+                    }
+                    break;
+                default:
+                    break;
+            }
+        } else {
+            throw new UnsupportedOperationException("Action not valid");
+        }
+    }
+
+    /**
+     * Convert an ArrayList to a float array
+     *
+     * @param list the ArrayList
+     * @return the float array
+     */
+    private float[] convertToFloatArray(ArrayList<Float> list) {
+        float[] result = new float[list.size()];
+        int i = 0;
+        for (float f : list) {
+            result[i++] = f;
+        }
+        return result;
+    }
+
+    /**
+     * Get configured alarm slot
+     *
+     * @return the alarm slot to be used for setting alarms on the watch
+     */
+    public static int getAlarmSlot() {
+        Prefs prefs = GBApplication.getPrefs();
+        String slotString = prefs.getString("sleepasandroid_alarm_slot", "");
+        if (!slotString.isEmpty()) {
+            return Integer.parseInt(slotString);
+        }
+        return 0;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btbr/AbstractBTBRDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btbr/AbstractBTBRDeviceSupport.java
new file mode 100644
index 0000000..479a484
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btbr/AbstractBTBRDeviceSupport.java
@@ -0,0 +1,154 @@
+/*  Copyright (C) 2022-2024 Damien Gaignon
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.btbr;
+
+import org.slf4j.Logger;
+
+import java.io.IOException;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.Logging;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.AbstractDeviceSupport;
+
+/**
+ * Abstract base class for devices connected through a serial protocol, like RFCOMM BT or TCP socket.
+ * <p/>
+ * The connection to the device and all communication is made with a generic
+ * {@link nodomain.freeyourgadget.gadgetbridge.service.btclassic.BtClassicIoThread}.
+ * Messages to the device are encoded
+ * sent via {@link nodomain.freeyourgadget.gadgetbridge.service.btclassic.BtClassicIoThread}.
+ *
+ * @see nodomain.freeyourgadget.gadgetbridge.service.btclassic.BtClassicIoThread
+ */
+public abstract class AbstractBTBRDeviceSupport extends AbstractDeviceSupport implements SocketCallback {
+    private BtBRQueue mQueue;
+    private UUID mSupportedService = null;
+    private int mBufferSize = 1024;
+    private final Logger logger;
+
+    public AbstractBTBRDeviceSupport(Logger logger) {
+        this.logger = logger;
+        if (logger == null) {
+            throw new IllegalArgumentException("logger must not be null");
+        }
+    }
+
+    @Override
+    public boolean connect() {
+        if (mQueue == null) {
+            mQueue = new BtBRQueue(getBluetoothAdapter(), getDevice(), getContext(), this, getSupportedService(), getBufferSize());
+        }
+        return mQueue.connect();
+    }
+
+    public void disconnect() {
+        if (mQueue != null) {
+            mQueue.disconnect();
+        }
+    }
+
+    /**
+     * Subclasses should populate the given builder to initialize the device (if necessary).
+     *
+     * @return the same builder as passed as the argument
+     */
+    protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
+        return builder;
+    }
+
+    @Override
+    public void dispose() {
+        if (mQueue != null) {
+            mQueue.dispose();
+            mQueue = null;
+        }
+    }
+
+    public TransactionBuilder createTransactionBuilder(String taskName) {
+        return new TransactionBuilder(taskName);
+    }
+
+    /**
+     * Ensures that the device is connected and (only then) performs the actions of the given
+     * transaction builder.
+     * <p>
+     * In contrast to {@link #performInitialized(String)}, no initialization sequence is performed
+     * with the device, only the actions of the given builder are executed.
+     * @param transaction
+     * @throws IOException if connection to the device fails
+     * @see #performInitialized(String)
+     */
+    public void performConnected(Transaction transaction) throws IOException {
+        if (!isConnected()) {
+            if (!connect()) {
+                throw new IOException("2: Unable to connect to device: " + getDevice());
+            }
+        }
+        getQueue().add(transaction);
+    }
+
+    public BtBRQueue getQueue() {
+        return mQueue;
+    }
+
+    /**
+     * Subclasses should call this method to add services they support.
+     * Only supported services will be queried for characteristics.
+     *
+     * @param aSupportedService the supported service uuid
+     */
+    protected void addSupportedService(UUID aSupportedService) {
+        mSupportedService = aSupportedService;
+    }
+
+    protected UUID getSupportedService() {
+        return mSupportedService;
+    }
+
+    protected void setBufferSize(int bufferSize) {
+        mBufferSize = bufferSize;
+    }
+
+    protected int getBufferSize() {
+        return mBufferSize;
+    }
+
+    /**
+     * Utility method that may be used to log incoming messages when we don't know how to deal with them yet.
+     */
+    public void logMessageContent(byte[] value) {
+        logger.info("RECEIVED DATA WITH LENGTH: {}", (value != null) ? value.length : "(null)");
+        Logging.logBytes(logger, value);
+    }
+
+    public void onConnectionEstablished() {
+        try {
+            initializeDevice(createTransactionBuilder("Initializing device")).queue(getQueue());
+        } catch (final Exception ex) {
+            final GBDevice device = getDevice();
+
+            if (device != null) {
+                logger.error("Exception raised while initializing device {} (address {}), disconnecting", device.getName(), device.getAddress(), ex);
+                device.setState(GBDevice.State.WAITING_FOR_RECONNECT);
+                device.sendDeviceUpdateIntent(getContext());
+            } else {
+                logger.error("Exception raised while initializing unknown device", ex);
+            }
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btbr/AbstractTransaction.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btbr/AbstractTransaction.java
new file mode 100644
index 0000000..1801eed
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btbr/AbstractTransaction.java
@@ -0,0 +1,46 @@
+/*  Copyright (C) 2022-2024 Damien Gaignon
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.btbr;
+
+import java.text.DateFormat;
+import java.util.Date;
+import java.util.Locale;
+
+public abstract class AbstractTransaction {
+    private final String mName;
+    private final long creationTimestamp = System.currentTimeMillis();
+
+    public AbstractTransaction(String taskName) {
+        this.mName = taskName;
+    }
+
+    public String getTaskName() {
+        return mName;
+    }
+
+    protected String getCreationTime() {
+        return DateFormat.getTimeInstance(DateFormat.MEDIUM).format(new Date(creationTimestamp));
+    }
+
+    public abstract int getActionCount();
+
+    @Override
+    public String toString() {
+        return String.format(Locale.US, "%s: Transaction task: %s with %d actions", getCreationTime(), getTaskName(), getActionCount());
+    }
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btbr/BtBRAction.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btbr/BtBRAction.java
new file mode 100644
index 0000000..6f80b7f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btbr/BtBRAction.java
@@ -0,0 +1,64 @@
+/*  Copyright (C) 2022-2024 Damien Gaignon
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.btbr;
+
+import android.bluetooth.BluetoothSocket;
+
+import java.util.Date;
+
+import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
+
+/**
+ * This allows performing one socket request at a time.
+ * As they are asynchronous anyway, we encapsulate every socket request (only write)
+ * inside a runnable action.
+ * <p/>
+ * These actions are then executed one after another, ensuring that every action's result
+ * has been posted before invoking the next action.
+ * <p/>
+ * As there is only write action, this class will be needed for later usage.
+ */
+public abstract class BtBRAction {
+    private final long creationTimestamp;
+
+    public BtBRAction() {
+        creationTimestamp = System.currentTimeMillis();
+    }
+
+    /**
+     * Returns true if this action expects an (async) result which must
+     * be waited for, before continuing with other actions.
+     * <p/>
+     */
+    public abstract boolean expectsResult();
+
+    /**
+     * Executes this action, e.g. reads or write a Socket characteristic.
+     *
+     * @param socket the characteristic to manipulate, or null if none.
+     * @return true if the action was successful, false otherwise
+     */
+    public abstract boolean run(BluetoothSocket socket);
+
+    protected String getCreationTime() {
+        return DateTimeUtils.formatDateTime(new Date(creationTimestamp));
+    }
+
+    public String toString() {
+        return getCreationTime() + ": " + getClass().getSimpleName();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btbr/BtBRQueue.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btbr/BtBRQueue.java
new file mode 100644
index 0000000..d7f9897
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btbr/BtBRQueue.java
@@ -0,0 +1,279 @@
+/*  Copyright (C) 2022-2024 Damien Gaignon, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.btbr;
+
+import android.annotation.SuppressLint;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothSocket;
+import android.content.Context;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Message;
+import android.os.Process;
+
+import androidx.annotation.NonNull;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Locale;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public final class BtBRQueue {
+    private static final Logger LOG = LoggerFactory.getLogger(BtBRQueue.class);
+    public static final int HANDLER_SUBJECT_CONNECT = 0;
+    public static final int HANDLER_SUBJECT_PERFORM_TRANSACTION = 1;
+
+    private BluetoothAdapter mBtAdapter = null;
+    private BluetoothSocket mBtSocket = null;
+    private final GBDevice mGbDevice;
+    private final SocketCallback mCallback;
+    private final UUID mService;
+
+    private volatile boolean mDisposed;
+
+    private final Context mContext;
+    private final int mBufferSize;
+
+    private final Handler mWriteHandler;
+    private final HandlerThread mWriteHandlerThread = new HandlerThread("Write Thread", Process.THREAD_PRIORITY_BACKGROUND);
+
+    private Thread readThread = new Thread("Read Thread") {
+        @Override
+        public void run() {
+            byte[] buffer = new byte[mBufferSize];
+            int nRead;
+
+            LOG.debug("Read thread started, entering loop");
+
+            while (!mDisposed) {
+                try {
+                    nRead = mBtSocket.getInputStream().read(buffer);
+
+                    // safety measure
+                    if (nRead == -1) {
+                        throw new IOException("End of stream");
+                    }
+                } catch (IOException ex) {
+                    LOG.error("IO exception while reading message from socket, breaking out of read thread: ", ex);
+                    break;
+                }
+
+                LOG.debug("Received {} bytes: {}", nRead, GB.hexdump(buffer, 0, nRead));
+
+                try {
+                    mCallback.onSocketRead(Arrays.copyOf(buffer, nRead));
+                } catch (Throwable ex) {
+                    LOG.error("Failed to process received bytes in onSocketRead callback: ", ex);
+                }
+            }
+
+            LOG.debug("Exited read thread loop, disconnecting");
+            GBApplication.deviceService(mGbDevice).disconnect();
+        }
+    };
+
+    public BtBRQueue(BluetoothAdapter btAdapter, GBDevice gbDevice, Context context, SocketCallback socketCallback, UUID supportedService, int bufferSize) {
+        mBtAdapter = btAdapter;
+        mGbDevice = gbDevice;
+        mContext = context;
+        mCallback = socketCallback;
+        mService = supportedService;
+        mBufferSize = bufferSize;
+
+        mWriteHandlerThread.start();
+
+        LOG.debug("Write handler thread is prepared, creating write handler");
+        mWriteHandler = new Handler(mWriteHandlerThread.getLooper()) {
+            @SuppressLint("MissingPermission")
+            @Override
+            public void handleMessage(@NonNull Message msg) {
+                switch (msg.what) {
+                    case HANDLER_SUBJECT_CONNECT: {
+                        try {
+                            mBtSocket.connect();
+
+                            LOG.info("Connected to RFCOMM socket for {}", mGbDevice.getName());
+                            setDeviceConnectionState(GBDevice.State.CONNECTED);
+
+                            // update thread names to show device names in logs
+                            readThread.setName(String.format(Locale.ENGLISH,
+                                    "Read Thread for %s", mGbDevice.getName()));
+                            mWriteHandlerThread.setName(String.format(Locale.ENGLISH,
+                                    "Write Thread for %s", mGbDevice.getName()));
+
+                            // now that connect has been created, start the threads
+                            readThread.start();
+                            onConnectionEstablished();
+                        } catch (IOException e) {
+                            LOG.error("IO exception while establishing socket connection: ", e);
+                            setDeviceConnectionState(GBDevice.State.NOT_CONNECTED);
+                        }
+
+                        return;
+                    }
+                    case HANDLER_SUBJECT_PERFORM_TRANSACTION: {
+                        try {
+                            if (!isConnected()) {
+                                LOG.debug("Not connected, updating device state to WAITING_FOR_RECONNECT");
+                                setDeviceConnectionState(GBDevice.State.WAITING_FOR_RECONNECT);
+                                return;
+                            }
+
+                            if (!(msg.obj instanceof Transaction)) {
+                                LOG.error("msg.obj is not an instance of Transaction");
+                                return;
+                            }
+
+                            Transaction transaction = (Transaction) msg.obj;
+
+                            for (BtBRAction action : transaction.getActions()) {
+                                if (LOG.isDebugEnabled()) {
+                                    LOG.debug("About to run action: {}", action);
+                                }
+
+                                if (action.run(mBtSocket)) {
+                                    LOG.debug("Action ok: {}", action);
+                                } else {
+                                    LOG.error("Action returned false, cancelling further actions in transaction: {}", action);
+                                    break;
+                                }
+                            }
+                        } catch (Throwable ex) {
+                            LOG.error("IO Write Thread died: " + ex.getMessage(), ex);
+                        }
+
+                        return;
+                    }
+                }
+
+                LOG.warn("Unhandled write handler message {}", msg.what);
+            }
+        };
+    }
+
+    /**
+     * Connects to the given remote device. Note that this does not perform any device
+     * specific initialization. This should be done in the specific {@link DeviceSupport}
+     * class.
+     *
+     * @return <code>true</code> whether the connection attempt was successfully triggered and <code>false</code> if that failed or if there is already a connection
+     */
+    @SuppressLint("MissingPermission")
+    public boolean connect() {
+        if (isConnected()) {
+            LOG.warn("Ignoring connect() because already connected.");
+            return false;
+        }
+
+        LOG.info("Attempting to connect to {} ({})", mGbDevice.getName(), mGbDevice.getAddress());
+
+        // stop discovery before connection is made
+        mBtAdapter.cancelDiscovery();
+
+        // revert to original state upon exception
+        GBDevice.State originalState = mGbDevice.getState();
+        setDeviceConnectionState(GBDevice.State.CONNECTING);
+
+        try {
+            BluetoothDevice btDevice = mBtAdapter.getRemoteDevice(mGbDevice.getAddress());
+            mBtSocket = btDevice.createRfcommSocketToServiceRecord(mService);
+        } catch (IOException e) {
+            LOG.error("Unable to connect to RFCOMM endpoint: ", e);
+            setDeviceConnectionState(originalState);
+            mBtSocket = null;
+            return false;
+        }
+
+        LOG.debug("Socket created, connecting in handler");
+        mWriteHandler.sendMessageAtFrontOfQueue(mWriteHandler.obtainMessage(HANDLER_SUBJECT_CONNECT));
+        return true;
+    }
+
+    protected void onConnectionEstablished() {
+        mCallback.onConnectionEstablished();
+    }
+
+    public void disconnect() {
+        if (mWriteHandlerThread.isAlive()) {
+            mWriteHandlerThread.quit();
+        }
+
+        if (mBtSocket != null && mBtSocket.isConnected()) {
+            try {
+                mBtSocket.close();
+            } catch (IOException e) {
+                LOG.error("IO exception while closing socket in disconnect(): ", e);
+            }
+        }
+
+        mBtSocket = null;
+        setDeviceConnectionState(GBDevice.State.NOT_CONNECTED);
+    }
+
+    /**
+     * Check whether a connection to the device exists and whether a socket connection has been
+     * initialized and connected
+     * @return true if the Bluetooth device is connected and the socket is ready, false otherwise
+     */
+    private boolean isConnected() {
+        return mGbDevice.isConnected() &&
+                mBtSocket != null &&
+                mBtSocket.isConnected();
+    }
+
+    /**
+     * Add a finalized {@link Transaction} to the write handler's queue
+     *
+     * @param transaction The transaction to be run in the handler thread's looper
+     */
+    public void add(Transaction transaction) {
+        LOG.debug("Adding transaction to looper message queue: {}", transaction);
+
+        if (!transaction.isEmpty()) {
+            mWriteHandler.obtainMessage(HANDLER_SUBJECT_PERFORM_TRANSACTION, transaction).sendToTarget();
+        }
+    }
+
+    private void setDeviceConnectionState(GBDevice.State newState) {
+        LOG.debug("New device connection state: " + newState);
+        mGbDevice.setState(newState);
+        mGbDevice.sendDeviceUpdateIntent(mContext, GBDevice.DeviceUpdateSubject.CONNECTION_STATE);
+    }
+
+    public void dispose() {
+        if (mDisposed) {
+            return;
+        }
+
+        mDisposed = true;
+        disconnect();
+
+        if (readThread != null && readThread.isAlive()) {
+            readThread.interrupt();
+            readThread = null;
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btbr/SocketCallback.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btbr/SocketCallback.java
new file mode 100644
index 0000000..e8aeaae
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btbr/SocketCallback.java
@@ -0,0 +1,34 @@
+/*  Copyright (C) 2022-2024 Damien Gaignon
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.btbr;
+
+/**
+ * Callback interface handling io events of a BluetoothSocket.
+ * It is the counterpart of GattCallback interface designed for GB.
+*/
+public interface SocketCallback {
+
+    void onConnectionEstablished();
+
+    /**
+     * Read data from InputStream of BluetoothSocket
+     *
+     * @param data
+     */
+    void onSocketRead(byte[] data);
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btbr/Transaction.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btbr/Transaction.java
new file mode 100644
index 0000000..3b1f5ad
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btbr/Transaction.java
@@ -0,0 +1,70 @@
+/*  Copyright (C) 2022-2024 Damien Gaignon
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.btbr;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import androidx.annotation.Nullable;
+
+/**
+ * Groups a bunch of {@link BtBRAction actions} together, making sure
+ * that upon failure of one action, all subsequent actions are discarded.
+ *
+ * @author TREND
+ */
+public class Transaction extends AbstractTransaction {
+    private final List<BtBRAction> mActions = new ArrayList<>(4);
+    private
+    @Nullable
+    SocketCallback socketCallback;
+
+    public Transaction(String taskName) {
+        super(taskName);
+    }
+
+    public void add(BtBRAction action) {
+        mActions.add(action);
+    }
+
+    public List<BtBRAction> getActions() {
+        return Collections.unmodifiableList(mActions);
+    }
+
+    public boolean isEmpty() {
+        return mActions.isEmpty();
+    }
+
+    public void setCallback(@Nullable SocketCallback callback) {
+        socketCallback = callback;
+    }
+
+    /**
+     * Returns the GattCallback for this transaction, or null if none.
+     */
+    public
+    @Nullable
+    SocketCallback getSocketCallback() {
+        return socketCallback;
+    }
+
+    @Override
+    public int getActionCount() {
+        return mActions.size();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btbr/TransactionBuilder.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btbr/TransactionBuilder.java
new file mode 100644
index 0000000..62489f1
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btbr/TransactionBuilder.java
@@ -0,0 +1,86 @@
+/*  Copyright (C) 2022-2024 Damien Gaignon
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.btbr;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import androidx.annotation.Nullable;
+
+import nodomain.freeyourgadget.gadgetbridge.service.btbr.actions.WaitAction;
+import nodomain.freeyourgadget.gadgetbridge.service.btbr.actions.WriteAction;
+
+public class TransactionBuilder {
+    private static final Logger LOG = LoggerFactory.getLogger(TransactionBuilder.class);
+
+    private final Transaction mTransaction;
+    private boolean mQueued;
+
+    public TransactionBuilder(String taskName) {
+        mTransaction = new Transaction(taskName);
+    }
+
+    public TransactionBuilder write(byte[] data) {
+        WriteAction action = new WriteAction(data);
+        return add(action);
+    }
+
+
+    /**
+     * Causes the queue to sleep for the specified time.
+     * Note that this is usually a bad idea, since it will not be able to process messages
+     * during that time. It is also likely to cause race conditions.
+     * @param millis the number of milliseconds to sleep
+     */
+    public TransactionBuilder wait(int millis) {
+        WaitAction action = new WaitAction(millis);
+        return add(action);
+    }
+
+    public TransactionBuilder add(BtBRAction action) {
+        mTransaction.add(action);
+        return this;
+    }
+
+    /**
+     * Sets a SocketCallback instance that will be called when the transaction is executed,
+     * resulting in SocketCallback events.
+     *
+     * @param callback the callback to set, may be null
+     */
+    public void setCallback(@Nullable SocketCallback callback) {
+        mTransaction.setCallback(callback);
+    }
+
+    /**
+     * To be used as the final step to execute the transaction by the given queue.
+     *
+     * @param queue
+     */
+    public void queue(BtBRQueue queue) {
+        if (mQueued) {
+            throw new IllegalStateException("This builder had already been queued. You must not reuse it.");
+        }
+        mQueued = true;
+        queue.add(mTransaction);
+    }
+
+    public Transaction getTransaction() {
+        return mTransaction;
+    }
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btbr/actions/AbortTransactionAction.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btbr/actions/AbortTransactionAction.java
new file mode 100644
index 0000000..a5736a8
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btbr/actions/AbortTransactionAction.java
@@ -0,0 +1,49 @@
+/*  Copyright (C) 2022-2024 Damien Gaignon
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.btbr.actions;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import android.bluetooth.BluetoothSocket;
+
+/**
+ * A special action that checks for an abort-condition, and if met, the currently
+ * executing transaction will be aborted by returning false.
+ */
+public abstract class AbortTransactionAction extends PlainAction {
+    private static final Logger LOG = LoggerFactory.getLogger(AbortTransactionAction.class);
+
+    public AbortTransactionAction() {
+    }
+
+    @Override
+    public boolean run(BluetoothSocket socket) {
+        if (shouldAbort()) {
+            LOG.info("Aborting transaction because abort criteria met.");
+            return false;
+        }
+        return true;
+    }
+
+    protected abstract boolean shouldAbort();
+
+    @Override
+    public String toString() {
+        return getCreationTime() + ": " + getClass().getSimpleName() + ": aborting? " + shouldAbort();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btbr/actions/CheckInitializedAction.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btbr/actions/CheckInitializedAction.java
new file mode 100644
index 0000000..37db3d9
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btbr/actions/CheckInitializedAction.java
@@ -0,0 +1,46 @@
+/*  Copyright (C) 2022-2024 Damien Gaignon
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.btbr.actions;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+/**
+ * A special action that is executed at the very front of the initialization
+ * sequence (transaction). It will abort the entire initialization sequence
+ * by returning false, when the device is already initialized.
+ */
+public class CheckInitializedAction extends AbortTransactionAction {
+    private static final Logger LOG = LoggerFactory.getLogger(CheckInitializedAction.class);
+
+    private final GBDevice device;
+
+    public CheckInitializedAction(GBDevice gbDevice) {
+        device = gbDevice;
+    }
+
+    @Override
+    protected boolean shouldAbort() {
+        boolean abort = device.isInitialized();
+        if (abort) {
+            LOG.info("Aborting device initialization, because already initialized: " + device);
+        }
+        return abort;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btbr/actions/PlainAction.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btbr/actions/PlainAction.java
new file mode 100644
index 0000000..53dc280
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btbr/actions/PlainAction.java
@@ -0,0 +1,39 @@
+/*  Copyright (C) 2022-2024 Damien Gaignon
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.btbr.actions;
+
+import nodomain.freeyourgadget.gadgetbridge.service.btbr.BtBRAction;
+
+/**
+ * An abstract non-BTBR action. It performs no bluetooth operation,
+ * does not have a BluetoothSocketCharacteristic instance and expects no result.
+ */
+public abstract class PlainAction extends BtBRAction {
+
+    public PlainAction() {
+    }
+
+    @Override
+    public boolean expectsResult() {
+        return false;
+    }
+
+    @Override
+    public String toString() {
+        return getCreationTime() + ": " + getClass().getSimpleName();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btbr/actions/SetDeviceBusyAction.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btbr/actions/SetDeviceBusyAction.java
new file mode 100644
index 0000000..e985897
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btbr/actions/SetDeviceBusyAction.java
@@ -0,0 +1,53 @@
+/*  Copyright (C) 2022-2024 Damien Gaignon
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.btbr.actions;
+
+import android.bluetooth.BluetoothSocket;
+import android.content.Context;
+
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class SetDeviceBusyAction extends PlainAction {
+    private final GBDevice device;
+    private final Context context;
+    private final String busyTask;
+
+    /**
+     * When run, will mark the device as busy (or not busy).
+     *
+     * @param device   the device to mark
+     * @param busyTask the task name to set as busy task, or null to mark as not busy
+     * @param context
+     */
+    public SetDeviceBusyAction(GBDevice device, String busyTask, Context context) {
+        this.device = device;
+        this.busyTask = busyTask;
+        this.context = context;
+    }
+
+    @Override
+    public boolean run(BluetoothSocket socket) {
+        device.setBusyTask(busyTask);
+        device.sendDeviceUpdateIntent(context);
+        return true;
+    }
+
+    @Override
+    public String toString() {
+        return getCreationTime() + ": " + getClass().getName() + ": " + busyTask;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btbr/actions/SetDeviceStateAction.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btbr/actions/SetDeviceStateAction.java
new file mode 100644
index 0000000..6d7b9ea
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btbr/actions/SetDeviceStateAction.java
@@ -0,0 +1,53 @@
+/*  Copyright (C) 2022-2024 Damien Gaignon
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.btbr.actions;
+
+import android.bluetooth.BluetoothSocket;
+import android.content.Context;
+
+import androidx.annotation.NonNull;
+
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class SetDeviceStateAction extends PlainAction {
+    private final GBDevice device;
+    private final GBDevice.State deviceState;
+    private final Context context;
+
+    public SetDeviceStateAction(GBDevice device, GBDevice.State deviceState, Context context) {
+        this.device = device;
+        this.deviceState = deviceState;
+        this.context = context;
+    }
+
+    @Override
+    public boolean run(BluetoothSocket socket) {
+        device.setState(deviceState);
+        device.sendDeviceUpdateIntent(getContext(), GBDevice.DeviceUpdateSubject.DEVICE_STATE);
+        return true;
+    }
+
+    public Context getContext() {
+        return context;
+    }
+
+    @NonNull
+    @Override
+    public String toString() {
+        return super.toString() + " to " + deviceState;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btbr/actions/SetProgressAction.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btbr/actions/SetProgressAction.java
new file mode 100644
index 0000000..4a5e56d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btbr/actions/SetProgressAction.java
@@ -0,0 +1,73 @@
+/*  Copyright (C) 2015-2023 Andreas Shimokawa, Carsten Pfeiffer, Daniele
+    Gobbetti, Yoran Vulker
+
+    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 <http://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.btbr.actions;
+
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothSocket;
+import android.content.Context;
+import android.content.Intent;
+
+import androidx.annotation.NonNull;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import nodomain.freeyourgadget.gadgetbridge.service.btbr.actions.PlainAction;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class SetProgressAction extends PlainAction {
+    private static final Logger LOG = LoggerFactory.getLogger(SetProgressAction.class);
+
+    private final String text;
+    private final boolean ongoing;
+    private final int percentage;
+    private final Context context;
+
+    /**
+     * When run, will update the progress notification.
+     *
+     * @param text Text shown in the notification
+     * @param ongoing State of action, true when the action is still being performed
+     * @param percentage Current percentage indicating how far along the action has progressed
+     * @param context Context in which to create the notification
+     */
+    public SetProgressAction(String text, boolean ongoing, int percentage, Context context) {
+        this.text = text;
+        this.ongoing = ongoing;
+        this.percentage = percentage;
+        this.context = context;
+    }
+
+    @Override
+    public boolean run(BluetoothSocket unused) {
+        LOG.info(toString());
+        GB.updateInstallNotification(this.text, this.ongoing, this.percentage, this.context);
+
+        final LocalBroadcastManager broadcastManager = LocalBroadcastManager.getInstance(context);
+        broadcastManager.sendBroadcast(new Intent(GB.ACTION_SET_PROGRESS_BAR).putExtra(GB.PROGRESS_BAR_PROGRESS, percentage));
+
+        return true;
+    }
+
+    @NonNull
+    @Override
+    public String toString() {
+        return getCreationTime() + ": " + getClass().getSimpleName() + ": " + text + "; " + percentage + "%";
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btbr/actions/WaitAction.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btbr/actions/WaitAction.java
new file mode 100644
index 0000000..fd36b33
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btbr/actions/WaitAction.java
@@ -0,0 +1,43 @@
+/*  Copyright (C) 2022-2024 Damien Gaignon
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.btbr.actions;
+
+import android.bluetooth.BluetoothSocket;
+
+/**
+ * An action that will cause the queue to sleep for the specified time.
+ * Note that this is usually a bad idea, since it will not be able to process messages
+ * during that time. It is also likely to cause race conditions.
+ */
+public class WaitAction extends PlainAction {
+
+    private final int mMillis;
+
+    public WaitAction(int millis) {
+        mMillis = millis;
+    }
+
+    @Override
+    public boolean run(BluetoothSocket socket) {
+        try {
+            Thread.sleep(mMillis);
+            return true;
+        } catch (InterruptedException e) {
+            return false;
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btbr/actions/WriteAction.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btbr/actions/WriteAction.java
new file mode 100644
index 0000000..1125ea4
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btbr/actions/WriteAction.java
@@ -0,0 +1,82 @@
+/*  Copyright (C) 2022-2024 Damien Gaignon
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.btbr.actions;
+
+import android.bluetooth.BluetoothSocket;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import nodomain.freeyourgadget.gadgetbridge.Logging;
+import nodomain.freeyourgadget.gadgetbridge.service.btbr.BtBRAction;
+
+/**
+ * Invokes a write operation on a given socket.
+ * The result status will be made available asynchronously through the
+ * {@link SocketCallback}
+ */
+public class WriteAction extends BtBRAction {
+    private static final Logger LOG = LoggerFactory.getLogger(WriteAction.class);
+
+    private final byte[] value;
+    private OutputStream mOutputStream = null;
+
+    public WriteAction(byte[] value) {
+        this.value = value;
+    }
+
+    @Override
+    public boolean run(BluetoothSocket socket) {
+        try {
+            mOutputStream = socket.getOutputStream();
+            if (mOutputStream == null) {
+                LOG.error("mOutStream is null");
+                return false;
+            }
+            return writeValue(value);
+        } catch (IOException e) {
+            LOG.error("Can not get the output stream");
+        }
+        return false;
+    }
+
+    protected boolean writeValue(byte[] value) {
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("writing to socket: " + Logging.formatBytes(value));
+        }
+        try {
+            mOutputStream.write(value);
+            mOutputStream.flush();
+            return true;
+        } catch (IOException e) {
+            LOG.error("Error writing to socket: ", e);
+        }
+        return false;
+    }
+
+    protected final byte[] getValue() {
+        return value;
+    }
+
+    @Override
+    public boolean expectsResult() {
+        return true;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btclassic/BtClassicIoThread.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btclassic/BtClassicIoThread.java
new file mode 100644
index 0000000..6c91b54
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btclassic/BtClassicIoThread.java
@@ -0,0 +1,214 @@
+/*  Copyright (C) 2017-2024 Carsten Pfeiffer, Daniele Gobbetti, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.btclassic;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothSocket;
+import android.content.Context;
+import android.os.ParcelUuid;
+
+import androidx.annotation.NonNull;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.SocketTimeoutException;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.serial.AbstractSerialDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceIoThread;
+import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public abstract class BtClassicIoThread extends GBDeviceIoThread {
+    private static final Logger LOG = LoggerFactory.getLogger(BtClassicIoThread.class);
+
+    private final GBDeviceProtocol mProtocol;
+    private final AbstractSerialDeviceSupport mDeviceSupport;
+
+    private BluetoothAdapter mBtAdapter = null;
+    private BluetoothSocket mBtSocket = null;
+    private InputStream mInStream = null;
+    private OutputStream mOutStream = null;
+    private boolean mQuit = false;
+
+    @Override
+    public void quit() {
+        mQuit = true;
+        if (mBtSocket != null) {
+            try {
+                mBtSocket.close();
+            } catch (IOException e) {
+                LOG.error(e.getMessage());
+            }
+        }
+    }
+
+    private boolean mIsConnected = false;
+
+
+    public BtClassicIoThread(GBDevice gbDevice, Context context, GBDeviceProtocol deviceProtocol, AbstractSerialDeviceSupport deviceSupport, BluetoothAdapter btAdapter) {
+        super(gbDevice, context);
+        mProtocol = deviceProtocol;
+        mDeviceSupport = deviceSupport;
+        mBtAdapter = btAdapter;
+    }
+
+    @Override
+    public synchronized void write(byte[] bytes) {
+        if (null == bytes)
+            return;
+        if (mOutStream == null) {
+            LOG.error("mOutStream is null");
+            return;
+        }
+        LOG.debug("writing: {}", GB.hexdump(bytes, 0, bytes.length));
+        try {
+            mOutStream.write(bytes);
+            mOutStream.flush();
+        } catch (IOException e) {
+            LOG.error("Error writing.", e);
+        }
+    }
+
+    @Override
+    public void run() {
+        mIsConnected = connect();
+        if (!mIsConnected) {
+            setUpdateState(GBDevice.State.NOT_CONNECTED);
+            return;
+        }
+        mQuit = false;
+
+        while (!mQuit) {
+            LOG.info("Ready for a new message exchange.");
+
+            try {
+                GBDeviceEvent deviceEvents[] = mProtocol.decodeResponse(parseIncoming(mInStream));
+                if (deviceEvents == null) {
+                    LOG.info("unhandled message");
+                } else {
+                    for (GBDeviceEvent deviceEvent : deviceEvents) {
+                        if (deviceEvent == null) {
+                            continue;
+                        }
+                        mDeviceSupport.evaluateGBDeviceEvent(deviceEvent);
+                    }
+                }
+            } catch (SocketTimeoutException ignore) {
+                LOG.debug("socket timeout, we can't help but ignore this");
+            } catch (IOException e) {
+                LOG.info(e.getMessage());
+                mIsConnected = false;
+                mBtSocket = null;
+                mInStream = null;
+                mOutStream = null;
+                LOG.info("Bluetooth socket closed, will quit IO Thread");
+                break;
+            }
+        }
+
+        mIsConnected = false;
+        if (mBtSocket != null) {
+            try {
+                mBtSocket.close();
+            } catch (IOException e) {
+                LOG.error(e.getMessage());
+            }
+            mBtSocket = null;
+        }
+        setUpdateState(GBDevice.State.NOT_CONNECTED);
+    }
+
+    @Override
+    protected boolean connect() {
+        GBDevice.State originalState = gbDevice.getState();
+        setUpdateState(GBDevice.State.CONNECTING);
+
+        try {
+            BluetoothDevice btDevice = mBtAdapter.getRemoteDevice(gbDevice.getAddress());
+            ParcelUuid uuids[] = btDevice.getUuids();
+            if (uuids == null) {
+                LOG.warn("Device provided no UUIDs to connect to, giving up: " + gbDevice);
+                return false;
+            }
+            for (ParcelUuid uuid : uuids) {
+                LOG.info("found service UUID " + uuid);
+            }
+            mBtSocket = btDevice.createRfcommSocketToServiceRecord(getUuidToConnect(uuids));
+            mBtSocket.connect();
+            mInStream = mBtSocket.getInputStream();
+            mOutStream = mBtSocket.getOutputStream();
+            setUpdateState(GBDevice.State.CONNECTED);
+        } catch (IOException e) {
+            LOG.error("Server socket cannot be started.", e);
+            //LOG.error(e.getMessage());
+            setUpdateState(originalState);
+            mInStream = null;
+            mOutStream = null;
+            mBtSocket = null;
+            return false;
+        } catch (SecurityException e) {
+            LOG.error("Could not connect to device.", e);
+            setUpdateState(originalState);
+            mInStream = null;
+            mOutStream = null;
+            mBtSocket = null;
+            return false;
+        }
+
+        initialize();
+
+        return true;
+    }
+
+    protected void initialize() {
+        write(mProtocol.encodeSetTime());
+        setUpdateState(GBDevice.State.INITIALIZED);
+    }
+
+    /**
+     * Returns the uuid to connect to.
+     * Default implementation returns the first of the given uuids that were
+     * read from the remote device.
+     * @param uuids
+     * @return
+     */
+    @NonNull
+    protected UUID getUuidToConnect(@NonNull ParcelUuid[] uuids) {
+        return uuids[0].getUuid();
+    }
+
+    protected void setUpdateState(GBDevice.State state) {
+        gbDevice.setState(state);
+        gbDevice.sendDeviceUpdateIntent(getContext());
+    }
+
+    /**
+     * Returns an incoming message for consuming by the GBDeviceProtocol
+     * @return
+     * @throws IOException
+     * @param inStream
+     */
+    protected abstract byte[] parseIncoming(InputStream inStream) throws IOException;
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/AbstractBTLEDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/AbstractBTLEDeviceSupport.java
new file mode 100644
index 0000000..a0d5352
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/AbstractBTLEDeviceSupport.java
@@ -0,0 +1,478 @@
+/*  Copyright (C) 2015-2024 Andreas B枚hler, Arjan Schrijver, Carsten Pfeiffer,
+    Daniel Dakhno, Daniele Gobbetti, Johannes Krude, JohnnySun, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.btle;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.bluetooth.BluetoothGattDescriptor;
+import android.bluetooth.BluetoothGattService;
+import android.content.Context;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.Logging;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.AbstractDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.CheckInitializedAction;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.AbstractBleProfile;
+
+/**
+ * Abstract base class for all devices connected through Bluetooth Low Energy (LE) aka
+ * Bluetooth Smart.
+ * <p/>
+ * The connection to the device and all communication is made with a generic {@link BtLEQueue}.
+ * Messages to the device are encoded as {@link BtLEAction actions} or {@link BtLEServerAction actions}
+ * that are grouped with a {@link Transaction} or {@link ServerTransaction} and sent via {@link BtLEQueue}.
+ *
+ * @see TransactionBuilder
+ * @see BtLEQueue
+ */
+public abstract class AbstractBTLEDeviceSupport extends AbstractDeviceSupport implements GattCallback, GattServerCallback {
+    private static final Logger LOG = LoggerFactory.getLogger(AbstractBTLEDeviceSupport.class);
+
+    private int mMTU = 23;
+    private BtLEQueue mQueue;
+    private Map<UUID, BluetoothGattCharacteristic> mAvailableCharacteristics;
+    private final Set<UUID> mSupportedServices = new HashSet<>(4);
+    private final Set<BluetoothGattService> mSupportedServerServices = new HashSet<>(4);
+    private final Logger logger;
+
+    private final List<AbstractBleProfile<?>> mSupportedProfiles = new ArrayList<>();
+    public static final String BASE_UUID = "0000%s-0000-1000-8000-00805f9b34fb"; //this is common for all BTLE devices. see http://stackoverflow.com/questions/18699251/finding-out-android-bluetooth-le-gatt-profiles
+    private final Object characteristicsMonitor = new Object();
+
+    private BleIntentApi bleApi = null;
+
+    public AbstractBTLEDeviceSupport(Logger logger) {
+        this.logger = logger;
+        if (logger == null) {
+            throw new IllegalArgumentException("logger must not be null");
+        }
+    }
+
+    @Override
+    public boolean connect() {
+        if (mQueue == null) {
+            mQueue = new BtLEQueue(getBluetoothAdapter(), getDevice(), this, this, getContext(), mSupportedServerServices);
+            if(bleApi != null) {
+                bleApi.setQueue(mQueue);
+            }
+            mQueue.setAutoReconnect(getAutoReconnect());
+            mQueue.setScanReconnect(getScanReconnect());
+            mQueue.setImplicitGattCallbackModify(getImplicitCallbackModify());
+            mQueue.setSendWriteRequestResponse(getSendWriteRequestResponse());
+        }
+
+        return mQueue.connect();
+    }
+
+    public void disconnect() {
+        if (mQueue != null) {
+            mQueue.disconnect();
+        }
+    }
+
+    public BleIntentApi getBleApi() {
+        return bleApi;
+    }
+
+    @Override
+    public void onSendConfiguration(String config) {
+        if(bleApi != null) {
+            bleApi.onSendConfiguration(config);
+        }
+    }
+
+    @Override
+    public void setContext(GBDevice gbDevice, BluetoothAdapter btAdapter, Context context) {
+        super.setContext(gbDevice, btAdapter, context);
+
+        if(BleIntentApi.isEnabled(gbDevice)) {
+            bleApi = new BleIntentApi(context, gbDevice);
+            bleApi.handleBLEApiPrefs();
+        }
+    }
+
+
+
+    /**
+     * Returns whether the gatt callback should be implicitly set to the one on the transaction,
+     * even if it was not set directly on the transaction. If true, the gatt callback will always
+     * be set to the one in the transaction, even if null and not explicitly set to null.
+     * See <a href="https://codeberg.org/Freeyourgadget/Gadgetbridge/pulls/2912">#2912</a> for
+     * more information. This is false by default, but we are making it configurable to avoid breaking
+     * older devices that rely on this behavior, so all older devices got this overridden to true.
+     */
+    public boolean getImplicitCallbackModify() {
+        return false;
+    }
+
+    /**
+     * Whether to send a write request response to the device, if requested. The standard actually
+     * expects this to happen, but Gadgetbridge did not originally support it. This is set to true
+     * on all older devices that were not confirmed to handle the response well after this was introduced.
+     * <p>
+     * See also: <a href="https://codeberg.org/Freeyourgadget/Gadgetbridge/pulls/2831#issuecomment-941568">#2831#issuecomment-941568</a>
+     *
+     * @return whether to send write request responses, if a response is requested
+     */
+    public boolean getSendWriteRequestResponse() {
+        return true;
+    }
+
+    @Override
+    public void setAutoReconnect(boolean enable) {
+        super.setAutoReconnect(enable);
+        if (mQueue != null) {
+            mQueue.setAutoReconnect(enable);
+        }
+    }
+
+    /**
+     * Subclasses should populate the given builder to initialize the device (if necessary).
+     *
+     * @return the same builder as passed as the argument
+     */
+    protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
+        return builder;
+    }
+
+    @Override
+    public void dispose() {
+        if (mQueue != null) {
+            mQueue.dispose();
+            mQueue = null;
+        }
+
+        if(bleApi != null) {
+            bleApi.dispose();
+        }
+    }
+
+    public TransactionBuilder createTransactionBuilder(String taskName) {
+        return new TransactionBuilder(taskName);
+    }
+
+    /**
+     * Send commands like this to the device:
+     * <p>
+     * <code>performInitialized("sms notification").write(someCharacteristic, someByteArray).queue(getQueue());</code>
+     * </p>
+     * This will asynchronously
+     * <ul>
+     * <li>connect to the device (if necessary)</li>
+     * <li>initialize the device (if necessary)</li>
+     * <li>execute the commands collected with the returned transaction builder</li>
+     * </ul>
+     *
+     * @see #performConnected(Transaction)
+     * @see #initializeDevice(TransactionBuilder)
+     */
+    public TransactionBuilder performInitialized(String taskName) throws IOException {
+        if (!isConnected()) {
+            LOG.debug("Connecting to device for {}", taskName);
+            if (!connect()) {
+                throw new IOException("1: Unable to connect to device: " + getDevice());
+            }
+        }
+        if (!isInitialized()) {
+            LOG.debug("Initializing device for {}", taskName);
+            // first, add a transaction that performs device initialization
+            TransactionBuilder builder = createTransactionBuilder("Initialize device");
+            builder.add(new CheckInitializedAction(gbDevice));
+            initializeDevice(builder).queue(getQueue());
+        }
+        return createTransactionBuilder(taskName);
+    }
+
+    public ServerTransactionBuilder createServerTransactionBuilder(String taskName) {
+        return new ServerTransactionBuilder(taskName);
+    }
+
+    public ServerTransactionBuilder performServer(String taskName) throws IOException {
+        if (!isConnected()) {
+            if(!connect()) {
+                throw new IOException("1: Unable to connect to device: " + getDevice());
+            }
+        }
+        return createServerTransactionBuilder(taskName);
+    }
+
+    /**
+     * Ensures that the device is connected and (only then) performs the actions of the given
+     * transaction builder.
+     * <p>
+     * In contrast to {@link #performInitialized(String)}, no initialization sequence is performed
+     * with the device, only the actions of the given builder are executed.
+     * @throws IOException if unable to connect to the device
+     * @see #performInitialized(String)
+     */
+    public void performConnected(Transaction transaction) throws IOException {
+        if (!isConnected()) {
+            if (!connect()) {
+                throw new IOException("2: Unable to connect to device: " + getDevice());
+            }
+        }
+        getQueue().add(transaction);
+    }
+
+    /**
+     * Performs the actions of the given transaction as soon as possible,
+     * that is, before any other queued transactions, but after the actions
+     * of the currently executing transaction.
+     */
+    public void performImmediately(TransactionBuilder builder) throws IOException {
+        if (!isConnected()) {
+            throw new IOException("Not connected to device: " + getDevice());
+        }
+        getQueue().insert(builder.getTransaction());
+    }
+
+    public BtLEQueue getQueue() {
+        return mQueue;
+    }
+
+    /**
+     * Subclasses should call this method to add services they support.
+     * Only supported services will be queried for characteristics.
+     *
+     * @param aSupportedService supported service uuid
+     * @see #getCharacteristic(UUID)
+     */
+    protected void addSupportedService(UUID aSupportedService) {
+        mSupportedServices.add(aSupportedService);
+    }
+
+    protected void addSupportedProfile(AbstractBleProfile<?> profile) {
+        mSupportedProfiles.add(profile);
+    }
+
+    /**
+     * Subclasses should call this method to add server services they support.
+     */
+    protected void addSupportedServerService(BluetoothGattService service) {
+        mSupportedServerServices.add(service);
+    }
+
+    /**
+     * Returns the characteristic matching the given UUID. Only characteristics
+     * are returned whose service is marked as supported.
+     *
+     * @param uuid characteristic uuid
+     * @return the characteristic for the given UUID or <code>null</code>
+     * @see #addSupportedService(UUID)
+     */
+    public BluetoothGattCharacteristic getCharacteristic(UUID uuid) {
+        synchronized (characteristicsMonitor) {
+            if (mAvailableCharacteristics == null) {
+                return null;
+            }
+            return mAvailableCharacteristics.get(uuid);
+        }
+    }
+
+    private void gattServicesDiscovered(List<BluetoothGattService> discoveredGattServices) {
+        if (discoveredGattServices == null) {
+            logger.warn("No gatt services discovered: null!");
+            return;
+        }
+        Set<UUID> supportedServices = getSupportedServices();
+        Map<UUID, BluetoothGattCharacteristic> newCharacteristics = new HashMap<>();
+        for (BluetoothGattService service : discoveredGattServices) {
+            if(bleApi != null) {
+                bleApi.addService(service);
+            }
+
+            if (supportedServices.contains(service.getUuid())) {
+                logger.debug("discovered supported service: {}: {}", BleNamesResolver.resolveServiceName(service.getUuid().toString()), service.getUuid());
+                List<BluetoothGattCharacteristic> characteristics = service.getCharacteristics();
+                if (characteristics == null || characteristics.isEmpty()) {
+                    logger.warn("Supported LE service {} did not return any characteristics", service.getUuid());
+                    continue;
+                }
+                HashMap<UUID, BluetoothGattCharacteristic> intmAvailableCharacteristics = new HashMap<>(characteristics.size());
+                for (BluetoothGattCharacteristic characteristic : characteristics) {
+                    intmAvailableCharacteristics.put(characteristic.getUuid(), characteristic);
+                    logger.info("    characteristic: {}: {}", BleNamesResolver.resolveCharacteristicName(characteristic.getUuid().toString()), characteristic.getUuid());
+                }
+                newCharacteristics.putAll(intmAvailableCharacteristics);
+
+                synchronized (characteristicsMonitor) {
+                    mAvailableCharacteristics = newCharacteristics;
+                }
+            } else {
+                logger.debug("discovered unsupported service: {}: {}", BleNamesResolver.resolveServiceName(service.getUuid().toString()), service.getUuid());
+            }
+        }
+    }
+
+    protected Set<UUID> getSupportedServices() {
+        return mSupportedServices;
+    }
+
+    /**
+     * Utility method that may be used to log incoming messages when we don't know how to deal with them yet.
+     */
+    public void logMessageContent(byte[] value) {
+        logger.info("RECEIVED DATA WITH LENGTH: {}", (value != null) ? value.length : "(null)");
+        Logging.logBytes(logger, value);
+    }
+
+    // default implementations of event handler methods (gatt callbacks)
+    @Override
+    public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
+        for (AbstractBleProfile<?> profile : mSupportedProfiles) {
+            profile.onConnectionStateChange(gatt, status, newState);
+        }
+    }
+
+    @Override
+    public void onServicesDiscovered(BluetoothGatt gatt) {
+        gattServicesDiscovered(gatt.getServices());
+
+        if (getDevice().getState().compareTo(GBDevice.State.INITIALIZING) >= 0) {
+            logger.warn("Services discovered, but device state is already " + getDevice().getState() + " for device: " + getDevice() + ", so ignoring");
+            return;
+        }
+        TransactionBuilder builder = createTransactionBuilder("Initializing device");
+
+        if(bleApi != null) {
+            bleApi.initializeDevice(builder);
+        }
+
+        initializeDevice(builder).queue(getQueue());
+    }
+
+    @Override
+    public boolean onCharacteristicRead(BluetoothGatt gatt,
+                                        BluetoothGattCharacteristic characteristic, int status) {
+        if(bleApi != null) {
+            bleApi.onCharacteristicChanged(characteristic);
+        }
+
+        for (AbstractBleProfile<?> profile : mSupportedProfiles) {
+            if (profile.onCharacteristicRead(gatt, characteristic, status)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public boolean onCharacteristicWrite(BluetoothGatt gatt,
+                                         BluetoothGattCharacteristic characteristic, int status) {
+        for (AbstractBleProfile<?> profile : mSupportedProfiles) {
+            if (profile.onCharacteristicWrite(gatt, characteristic, status)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public boolean onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
+        for (AbstractBleProfile<?> profile : mSupportedProfiles) {
+            if (profile.onDescriptorRead(gatt, descriptor, status)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public boolean onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
+        for (AbstractBleProfile<?> profile : mSupportedProfiles) {
+            if (profile.onDescriptorWrite(gatt, descriptor, status)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public boolean onCharacteristicChanged(BluetoothGatt gatt,
+                                           BluetoothGattCharacteristic characteristic) {
+        if(bleApi != null) {
+            bleApi.onCharacteristicChanged(characteristic);
+        }
+
+        for (AbstractBleProfile<?> profile : mSupportedProfiles) {
+            if (profile.onCharacteristicChanged(gatt, characteristic)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {
+        for (AbstractBleProfile<?> profile : mSupportedProfiles) {
+            profile.onReadRemoteRssi(gatt, rssi, status);
+        }
+    }
+
+    @Override
+    public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
+        this.mMTU = mtu;
+    }
+
+    @Override
+    public void onConnectionStateChange(BluetoothDevice device, int status, int newState) {
+
+    }
+
+    @Override
+    public boolean onCharacteristicReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattCharacteristic characteristic) {
+        return false;
+    }
+
+    @Override
+    public boolean onCharacteristicWriteRequest(BluetoothDevice device, int requestId, BluetoothGattCharacteristic characteristic, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
+        return false;
+    }
+
+    @Override
+    public boolean onDescriptorReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattDescriptor descriptor) {
+        return false;
+    }
+
+    @Override
+    public boolean onDescriptorWriteRequest(BluetoothDevice device, int requestId, BluetoothGattDescriptor descriptor, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
+        return false;
+    }
+
+    /**
+     * Gets the current MTU, or 0 if unknown
+     * @return the current MTU, 0 if unknown
+     */
+    public int getMTU() {
+        return mMTU;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/AbstractBTLEOperation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/AbstractBTLEOperation.java
new file mode 100644
index 0000000..8cf49a8
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/AbstractBTLEOperation.java
@@ -0,0 +1,218 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, Carsten Pfeiffer, Damien
+    Gaignon, Daniel Dakhno, Uwe Hermann
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.btle;
+
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.bluetooth.BluetoothGattDescriptor;
+import android.content.Context;
+
+import java.io.IOException;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.OperationStatus;
+
+/**
+ * Abstract base class for a BTLEOperation, i.e. an operation that does more than
+ * just sending a few bytes to the device. It typically involves exchanging many messages
+ * between the mobile and the device.
+ * <p/>
+ * One operation may execute multiple @{link Transaction transactions} with each
+ * multiple @{link BTLEAction actions}.
+ * <p/>
+ * This class implements GattCallback so that subclasses may override those methods
+ * to handle those events.
+ * Note: by default all Gatt events are forwarded to AbstractBTLEDeviceSupport, subclasses may override
+ * this behavior.
+ */
+public abstract class AbstractBTLEOperation<T extends AbstractBTLEDeviceSupport> implements GattCallback, BTLEOperation {
+    private final T mSupport;
+    protected OperationStatus operationStatus = OperationStatus.INITIAL;
+    private String name;
+
+    protected AbstractBTLEOperation(T support) {
+        mSupport = support;
+    }
+
+    /**
+     * Performs this operation. The whole operation is asynchronous, i.e.
+     * this method quickly returns before the actual operation is finished.
+     * Calls #prePerform() and, if successful, #doPerform().
+     *
+     * @throws IOException
+     */
+    @Override
+    public final void perform() throws IOException {
+        operationStatus = OperationStatus.STARTED;
+        prePerform();
+        operationStatus = OperationStatus.RUNNING;
+        doPerform();
+    }
+
+    /**
+     * Hook for subclasses to perform something before #doPerform() is invoked.
+     *
+     * @throws IOException
+     */
+    protected void prePerform() throws IOException {
+    }
+
+    /**
+     * Subclasses must implement this. When invoked, #prePerform() returned
+     * successfully.
+     * Note that subclasses HAVE TO call #operationFinished() when the entire
+     * operation is done (successful or not).
+     *
+     * @throws IOException
+     */
+    protected abstract void doPerform() throws IOException;
+
+    /**
+     * You MUST call this method when the operation has finished, either
+     * successfully or unsuccessfully.
+     *
+     * Subclasses must ensure that the {@link BtLEQueue queue's}'s gatt callback (set on the transaction builder by {@link #performInitialized(String)})
+     * is being unset, otherwise it will continue to receive events until another transaction is being executed by the queue.
+     *
+     * @throws IOException
+     */
+    protected void operationFinished() throws IOException {
+    }
+
+    /**
+     * Delegates to the DeviceSupport instance and additionally sets this instance as the Gatt
+     * callback for the transaction.
+     *
+     * @param taskName
+     * @return
+     * @throws IOException
+     */
+    public TransactionBuilder performInitialized(String taskName) throws IOException {
+        TransactionBuilder builder = mSupport.performInitialized(taskName);
+        builder.setCallback(this);
+        return builder;
+    }
+
+    public TransactionBuilder createTransactionBuilder(String taskName) {
+        TransactionBuilder builder = getSupport().createTransactionBuilder(taskName);
+        builder.setCallback(this);
+        return builder;
+    }
+
+    public void performImmediately(TransactionBuilder builder) throws IOException {
+        mSupport.performImmediately(builder);
+    }
+
+    protected Context getContext() {
+        return mSupport.getContext();
+    }
+
+    protected GBDevice getDevice() {
+        return mSupport.getDevice();
+    }
+
+    protected void setName(String name) {
+        this.name = name;
+    }
+
+    @Override
+    public String getName() {
+        if (name != null) {
+            return name;
+        }
+        String busyTask = getDevice().getBusyTask();
+        if (busyTask != null) {
+            return busyTask;
+        }
+        return getClass().getSimpleName();
+    }
+
+    protected BluetoothGattCharacteristic getCharacteristic(UUID uuid) {
+        return mSupport.getCharacteristic(uuid);
+    }
+
+    protected BtLEQueue getQueue() {
+        return mSupport.getQueue();
+    }
+
+    protected void unsetBusy() {
+        if (getDevice().isBusy()) {
+            getDevice().unsetBusyTask();
+            getDevice().sendDeviceUpdateIntent(getContext());
+        }
+    }
+
+    public boolean isOperationRunning() {
+        return operationStatus == OperationStatus.RUNNING;
+    }
+
+    public boolean isOperationFinished() {
+        return operationStatus == OperationStatus.FINISHED;
+    }
+
+    public T getSupport() {
+        return mSupport;
+    }
+
+    // All Gatt callbacks delegated to MiBandSupport
+    @Override
+    public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
+        mSupport.onConnectionStateChange(gatt, status, newState);
+    }
+
+    @Override
+    public void onServicesDiscovered(BluetoothGatt gatt) {
+        mSupport.onServicesDiscovered(gatt);
+    }
+
+    @Override
+    public boolean onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
+        return mSupport.onCharacteristicRead(gatt, characteristic, status);
+    }
+
+    @Override
+    public boolean onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
+        return mSupport.onCharacteristicWrite(gatt, characteristic, status);
+    }
+
+    @Override
+    public boolean onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
+        return mSupport.onCharacteristicChanged(gatt, characteristic);
+    }
+
+    @Override
+    public boolean onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
+        return mSupport.onDescriptorRead(gatt, descriptor, status);
+    }
+
+    @Override
+    public boolean onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
+        return mSupport.onDescriptorWrite(gatt, descriptor, status);
+    }
+
+    @Override
+    public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {
+        mSupport.onReadRemoteRssi(gatt, rssi, status);
+    }
+
+    @Override
+    public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
+        mSupport.onMtuChanged(gatt, mtu, status);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/AbstractGattCallback.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/AbstractGattCallback.java
new file mode 100644
index 0000000..3a004a2
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/AbstractGattCallback.java
@@ -0,0 +1,67 @@
+/*  Copyright (C) 2015-2024 Carsten Pfeiffer, Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.btle;
+
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.bluetooth.BluetoothGattDescriptor;
+
+/**
+ * Base class for GattCallbacks wishing to just implement a few of the methods.
+ */
+public abstract class AbstractGattCallback implements GattCallback {
+    @Override
+    public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
+    }
+
+    @Override
+    public void onServicesDiscovered(BluetoothGatt gatt) {
+    }
+
+    @Override
+    public boolean onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
+        return false;
+    }
+
+    @Override
+    public boolean onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
+        return false;
+    }
+
+    @Override
+    public boolean onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
+        return false;
+    }
+
+    @Override
+    public boolean onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
+        return false;
+    }
+
+    @Override
+    public boolean onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
+        return false;
+    }
+
+    @Override
+    public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {
+    }
+
+    @Override
+    public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/AbstractTransaction.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/AbstractTransaction.java
new file mode 100644
index 0000000..b2b6309
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/AbstractTransaction.java
@@ -0,0 +1,46 @@
+/*  Copyright (C) 2019-2024 Andreas B枚hler
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.btle;
+
+import java.text.DateFormat;
+import java.util.Date;
+import java.util.Locale;
+
+public abstract class AbstractTransaction {
+    private final String mName;
+    private final long creationTimestamp = System.currentTimeMillis();
+
+    public AbstractTransaction(String taskName) {
+        this.mName = taskName;
+    }
+
+    public String getTaskName() {
+        return mName;
+    }
+
+    protected String getCreationTime() {
+        return DateFormat.getTimeInstance(DateFormat.MEDIUM).format(new Date(creationTimestamp));
+    }
+
+    public abstract int getActionCount();
+
+    @Override
+    public String toString() {
+        return String.format(Locale.US, "%s: Transaction task: %s with %d actions", getCreationTime(), getTaskName(), getActionCount());
+    }
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BLEScanService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BLEScanService.java
new file mode 100644
index 0000000..f5d622d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BLEScanService.java
@@ -0,0 +1,430 @@
+/*  Copyright (C) 2024 Daniel Dakhno, Jos茅 Rebelo, Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+
+package nodomain.freeyourgadget.gadgetbridge.service.btle;
+
+import android.Manifest;
+import android.annotation.SuppressLint;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.Service;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothManager;
+import android.bluetooth.le.BluetoothLeScanner;
+import android.bluetooth.le.ScanCallback;
+import android.bluetooth.le.ScanFilter;
+import android.bluetooth.le.ScanResult;
+import android.bluetooth.le.ScanSettings;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.pm.ServiceInfo;
+import android.os.Build;
+import android.os.Handler;
+import android.os.IBinder;
+
+import androidx.core.app.ActivityCompat;
+import androidx.core.app.NotificationCompat;
+import androidx.core.content.ContextCompat;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class BLEScanService extends Service {
+    public static final String COMMAND_SCAN_DEVICE = "nodomain.freeyourgadget.gadgetbridge.service.ble.scan.command.START_SCAN_FOR_DEVICE";
+    public static final String COMMAND_START_SCAN_ALL = "nodomain.freeyourgadget.gadgetbridge.service.ble.scan.command.START_SCAN_ALL";
+    public static final String COMMAND_STOP_SCAN_ALL = "nodomain.freeyourgadget.gadgetbridge.service.ble.scan.command.STOP_SCAN_ALL";
+
+    public static final String EVENT_DEVICE_FOUND = "nodomain.freeyourgadget.gadgetbridge.service.ble.scan.event.DEVICE_FOUND";
+
+    public static final String EXTRA_DEVICE = "EXTRA_DEVICE";
+    public static final String EXTRA_DEVICE_ADDRESS = "EXTRA_DEVICE_ADDRESS";
+    public static final String EXTRA_RSSI = "EXTRA_RSSI";
+
+    // 5 minutes scan restart interval
+    private final int DELAY_SCAN_RESTART = 5 * 60 * 1000;
+
+    private LocalBroadcastManager localBroadcastManager;
+    private NotificationManager notificationManager;
+    private BluetoothManager bluetoothManager;
+    private BluetoothLeScanner scanner;
+
+    private Logger LOG = LoggerFactory.getLogger(getClass());
+    // private final ArrayList<ScanFilter> currentFilters = new ArrayList<>();
+
+    private enum ScanningState {
+        NOT_SCANNING,
+        SCANNING_WITHOUT_FILTERS,
+        SCANNING_WITH_FILTERS;
+
+        public boolean isDoingAnyScan() {
+            return ordinal() > NOT_SCANNING.ordinal();
+        }
+
+        public boolean shouldDiscardAfterFirstMatch() {
+            return this == SCANNING_WITH_FILTERS;
+        }
+    }
+
+    private ScanningState currentState = ScanningState.NOT_SCANNING;
+
+    private final ScanCallback scanCallback = new ScanCallback() {
+        @Override
+        public void onScanResult(int callbackType, ScanResult result) {
+            super.onScanResult(callbackType, result);
+            BluetoothDevice device = result.getDevice();
+
+            LOG.debug("onScanResult: " + result);
+
+            Intent intent = new Intent(EVENT_DEVICE_FOUND);
+            intent.putExtra(EXTRA_DEVICE_ADDRESS, device.getAddress());
+            intent.putExtra(EXTRA_RSSI, result.getRssi());
+            localBroadcastManager.sendBroadcast(intent);
+
+            // device found, attempt connection
+            // stop scanning for device for now
+            // will restart when connection attempt fails
+            if (currentState.shouldDiscardAfterFirstMatch()) {
+                // stopScanningForDevice(device.getAddress());
+            }
+        }
+
+        @Override
+        public void onScanFailed(int errorCode) {
+            super.onScanFailed(errorCode);
+
+            LOG.error("onScanFailed: " + errorCode);
+
+            updateNotification(getString(R.string.error_scan_failed, errorCode));
+        }
+    };
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+
+        bluetoothManager = (BluetoothManager) getSystemService(BLUETOOTH_SERVICE);
+        scanner = bluetoothManager.getAdapter().getBluetoothLeScanner();
+
+        localBroadcastManager = LocalBroadcastManager.getInstance(this);
+
+        notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
+
+        registerReceivers();
+
+        this.startForeground();
+
+        if (scanner == null) {
+            updateNotification(getString(R.string.waiting_for_bluetooth));
+        } else {
+            restartScan(true);
+        }
+
+        // schedule after 5 seconds to fix weird timing of both services
+        scheduleRestartScan(5000);
+    }
+
+    private void scheduleRestartScan() {
+        scheduleRestartScan(DELAY_SCAN_RESTART);
+    }
+
+    private void scheduleRestartScan(long millis) {
+        Handler handler = new Handler();
+        handler.postDelayed(() -> {
+            LOG.debug("restarting scan...");
+            try {
+                restartScan(true);
+            } catch (Exception e) {
+                LOG.error("error during scheduled scan restart", e);
+            }
+            scheduleRestartScan();
+        }, millis);
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        unregisterReceivers();
+    }
+
+    private void updateNotification(boolean isScanning, int scannedDeviceCount) {
+        notificationManager.notify(
+                GB.NOTIFICATION_ID_SCAN,
+                createNotification(isScanning, scannedDeviceCount)
+        );
+    }
+
+    private void updateNotification(String content) {
+        notificationManager.notify(
+                GB.NOTIFICATION_ID_SCAN,
+                createNotification(content, R.drawable.ic_bluetooth)
+        );
+    }
+
+    private Notification createNotification(boolean isScanning, int scannedDevicesCount) {
+        int icon = R.drawable.ic_bluetooth;
+        String content = getString(R.string.scan_not_scanning);
+        if (isScanning) {
+            icon = R.drawable.ic_bluetooth_searching;
+            if (scannedDevicesCount == 1) {
+                content = getString(R.string.scan_scanning_single_device);
+            } else if (scannedDevicesCount > 1) {
+                content = getString(R.string.scan_scanning_multiple_devices, scannedDevicesCount);
+            } else {
+                content = getString(R.string.scan_scanning_all_devices);
+            }
+        }
+
+        return createNotification(content, icon);
+    }
+
+    private Notification createNotification(String content, int icon) {
+
+        return new NotificationCompat
+                .Builder(this, GB.NOTIFICATION_CHANNEL_ID_SCAN_SERVICE)
+                .setContentTitle(getString(R.string.notification_channel_scan_service_name))
+                .setContentText(content)
+                .setSmallIcon(icon)
+                .build();
+    }
+
+    private void startForeground() {
+        Notification serviceNotification = createNotification(false, 0);
+
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+            super.startForeground(GB.NOTIFICATION_ID_SCAN, serviceNotification, ServiceInfo.FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE);
+        } else {
+            super.startForeground(GB.NOTIFICATION_ID_SCAN, serviceNotification);
+        }
+    }
+
+    @Override
+    public int onStartCommand(Intent intent, int flags, int startId) {
+        if (intent == null) {
+            return START_STICKY;
+        }
+        String action = intent.getAction();
+        if (action == null) {
+            return START_STICKY;
+        }
+        switch (action) {
+            case COMMAND_SCAN_DEVICE:
+                handleScanDevice(intent);
+                break;
+            case COMMAND_START_SCAN_ALL:
+                handleScanAll(intent);
+                break;
+            case COMMAND_STOP_SCAN_ALL:
+                handleStopScanAll(intent);
+                break;
+            default:
+                return START_STICKY;
+        }
+        return START_STICKY;
+    }
+
+    private void handleStopScanAll(Intent intent) {
+        restartScan(true);
+    }
+
+    private void handleScanAll(Intent intent) {
+        if (currentState != ScanningState.SCANNING_WITHOUT_FILTERS) {
+            restartScan(false);
+        }
+    }
+
+    private void handleScanDevice(Intent intent) {
+        restartScan(true);
+    }
+
+    BroadcastReceiver deviceStateUpdateReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (GBDevice.ACTION_DEVICE_CHANGED.equals(intent.getAction())) {
+                GBDevice.DeviceUpdateSubject subject =
+                        (GBDevice.DeviceUpdateSubject)
+                                intent.getSerializableExtra(GBDevice.EXTRA_UPDATE_SUBJECT);
+
+                if (subject != GBDevice.DeviceUpdateSubject.CONNECTION_STATE) {
+                    return;
+                }
+                restartScan(true);
+                return;
+            }
+            if (GBApplication.ACTION_QUIT.equals(intent.getAction())) {
+                LOG.debug("stopping scan service...");
+                if (currentState.isDoingAnyScan()) {
+                    if (ActivityCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.BLUETOOTH_SCAN) != PackageManager.PERMISSION_GRANTED) {
+                        // this should never happen
+                        LOG.error("No BLUETOOTH_SCAN permission");
+                        return;
+                    }
+
+                    scanner.stopScan(scanCallback);
+                }
+                stopSelf();
+            }
+        }
+    };
+
+    BroadcastReceiver bluetoothStateChangedReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (intent == null) {
+                return;
+            }
+            final int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR);
+            switch (state) {
+                case BluetoothAdapter.STATE_OFF:
+                case BluetoothAdapter.STATE_TURNING_OFF:
+                    updateNotification(getString(R.string.waiting_for_bluetooth));
+                    break;
+                case BluetoothAdapter.STATE_ON:
+                    restartScan(true);
+                    break;
+            }
+        }
+    };
+
+    private void registerReceivers() {
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(GBDevice.ACTION_DEVICE_CHANGED);
+        filter.addAction(GBApplication.ACTION_QUIT);
+        localBroadcastManager.registerReceiver(
+                deviceStateUpdateReceiver,
+                filter
+        );
+
+        ContextCompat.registerReceiver(
+                this,
+                bluetoothStateChangedReceiver,
+                new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED),
+                ContextCompat.RECEIVER_EXPORTED
+        );
+    }
+
+    private void unregisterReceivers() {
+        localBroadcastManager.unregisterReceiver(deviceStateUpdateReceiver);
+
+        unregisterReceiver(bluetoothStateChangedReceiver);
+    }
+
+    private boolean hasBluetoothPermission() {
+        if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) {
+            // workaround. Cannot give bluetooth permission on Android O
+            LOG.warn("Running on android 11, skipping bluetooth permission check");
+            return ActivityCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED;
+        }
+        return ActivityCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.BLUETOOTH_SCAN) == PackageManager.PERMISSION_GRANTED;
+    }
+
+    @SuppressLint("MissingPermission") // linter does not recognize the usage of hasBluetoothPermission
+    private void restartScan(boolean applyFilters) {
+        if (!hasBluetoothPermission()) {
+            // this should never happen
+            LOG.error("No BLUETOOTH_SCAN permission");
+            updateNotification("Missing Bluetooth scan permissions");
+            return;
+        }
+
+        if (scanner == null) {
+            scanner = bluetoothManager.getAdapter().getBluetoothLeScanner();
+        }
+        if (scanner == null) {
+            // at this point we should already be waiting for bluetooth to turn back on
+            LOG.debug("cannot enable scan since bluetooth seems off (scanner == null)");
+            return;
+        }
+        if (bluetoothManager.getAdapter().getState() != BluetoothAdapter.STATE_ON) {
+            // again, we should be waiting for the adapter to turn on again
+            LOG.debug("Bluetooth adapter state off");
+            return;
+        }
+        if (currentState.isDoingAnyScan()) {
+            if (hasBluetoothPermission()) {
+                scanner.stopScan(scanCallback);
+            }
+        }
+        ArrayList<ScanFilter> scanFilters = null;
+
+        if (applyFilters) {
+            List<GBDevice> devices = GBApplication.app().getDeviceManager().getDevices();
+
+            scanFilters = new ArrayList<>(devices.size());
+
+            for (GBDevice device : devices) {
+                if (device.getState() == GBDevice.State.WAITING_FOR_SCAN) {
+                    scanFilters.add(new ScanFilter.Builder()
+                            .setDeviceAddress(device.getAddress())
+                            .build()
+                    );
+                }
+            }
+
+            if (scanFilters.isEmpty()) {
+                // no need to start scanning
+                LOG.debug("restartScan: stopping BLE scan, no devices");
+                currentState = ScanningState.NOT_SCANNING;
+                updateNotification(false, 0);
+                return;
+            }
+        }
+
+        final ScanSettings.Builder scanSettingsBuilder = new ScanSettings.Builder()
+                .setScanMode(ScanSettings.SCAN_MODE_LOW_POWER); // enforced anyway in background
+
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+            scanSettingsBuilder
+                    .setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
+                    .setMatchMode(ScanSettings.MATCH_MODE_STICKY);
+        }
+
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+            scanSettingsBuilder.setLegacy(false);
+        }
+
+        scanner.startScan(scanFilters, scanSettingsBuilder.build(), scanCallback);
+        if (applyFilters) {
+            LOG.debug("restartScan: started scan for " + scanFilters.size() + " devices");
+            updateNotification(true, scanFilters.size());
+            currentState = ScanningState.SCANNING_WITH_FILTERS;
+        } else {
+            LOG.debug("restartScan: started scan for all devices");
+            updateNotification(true, 0);
+            currentState = ScanningState.SCANNING_WITHOUT_FILTERS;
+        }
+
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        // TODO: Return the communication channel to the service.
+        throw new UnsupportedOperationException("Not yet implemented");
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BLETypeConversions.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BLETypeConversions.java
new file mode 100644
index 0000000..8c5984a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BLETypeConversions.java
@@ -0,0 +1,422 @@
+/*  Copyright (C) 2016-2024 Andreas Shimokawa, Carsten Pfeiffer, Daniele
+    Gobbetti, Johannes Krude, Jos茅 Rebelo, Lukas Veneziano, Maxim Baz, Petr
+    Kadlec, Robbert Gurdeep Singh, uli
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.btle;
+
+import java.nio.charset.StandardCharsets;
+import java.time.ZonedDateTime;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.TimeZone;
+
+import nodomain.freeyourgadget.gadgetbridge.model.NotificationType;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotification.AlertCategory;
+
+/**
+ * Provides methods to convert standard BLE units to byte sequences and vice versa.
+ */
+public class BLETypeConversions {
+    public static final int TZ_FLAG_NONE = 0;
+    public static final int TZ_FLAG_INCLUDE_DST_IN_TZ = 1;
+
+    /**
+     * Converts a timestamp to the byte sequence to be sent to the current time characteristic
+     *
+     * @param timestamp
+     * @param reason
+     * @return
+     * @see GattCharacteristic#UUID_CHARACTERISTIC_CURRENT_TIME
+     */
+    public static byte[] calendarToCurrentTime(Calendar timestamp, int reason) {
+        // year,year,month,dayofmonth,hour,minute,second,dayofweek,fractions256,reason
+
+        byte[] year = fromUint16(timestamp.get(Calendar.YEAR));
+        return new byte[] {
+                year[0],
+                year[1],
+                fromUint8(timestamp.get(Calendar.MONTH) + 1),
+                fromUint8(timestamp.get(Calendar.DATE)),
+                fromUint8(timestamp.get(Calendar.HOUR_OF_DAY)),
+                fromUint8(timestamp.get(Calendar.MINUTE)),
+                fromUint8(timestamp.get(Calendar.SECOND)),
+                dayOfWeekToRawBytes(timestamp),
+                fromUint8((int) (timestamp.get(Calendar.MILLISECOND) / 1000. * 256)),
+                (byte) reason, // use 0 if unknown reason
+        };
+    }
+
+    /**
+     * Converts a timestamp to the byte sequence to be sent to the current time characteristic
+     *
+     * @param timestamp
+     * @param reason
+     * @return
+     * @see GattCharacteristic#UUID_CHARACTERISTIC_CURRENT_TIME
+     */
+    public static byte[] toCurrentTime(ZonedDateTime timestamp, int reason) {
+        // year,year,month,dayofmonth,hour,minute,second,dayofweek,fractions256,reason
+
+        byte[] year = fromUint16(timestamp.getYear());
+        return new byte[] {
+                year[0],
+                year[1],
+                fromUint8(timestamp.getMonthValue()),
+                fromUint8(timestamp.getDayOfMonth()),
+                fromUint8(timestamp.getHour()),
+                fromUint8(timestamp.getMinute()),
+                fromUint8(timestamp.getSecond()),
+                fromUint8(timestamp.getDayOfWeek().getValue()),
+                fromUint8((int) (timestamp.getNano() / 1000000000. * 256)),
+                (byte) reason, // use 0 if unknown reason
+        };
+    }
+
+    /**
+     * Converts a timestamp to the byte sequence to be sent to the local time characteristic
+     *
+     * Values are expressed in quarters of hours
+     * Following the BLE specification, timezone is constant over dst changes
+     *
+     * @param timestamp
+     * @return
+     * @see GattCharacteristic#UUID_CHARACTERISTIC_LOCAL_TIME
+     */
+    public static byte[] calendarToLocalTime(Calendar timestamp) {
+        TimeZone timeZone = timestamp.getTimeZone();
+        int offsetMillisTimezone = timeZone.getRawOffset();
+        int utcOffsetInQuarterHours = (offsetMillisTimezone / (1000 * 60 * 15));
+
+        int offsetMillisIncludingDST = timestamp.getTimeZone().getOffset(timestamp.getTimeInMillis());
+        int dstOffsetMillis = offsetMillisIncludingDST - offsetMillisTimezone;
+        int dstOffsetInQuarterHours = (dstOffsetMillis / (1000 * 60 * 15));
+
+        return new byte[] {
+                (byte) utcOffsetInQuarterHours,
+                (byte) dstOffsetInQuarterHours
+        };
+    }
+
+    /**
+     * Similar to calendarToRawBytes, but only up to (and including) the MINUTES.
+     * @param timestamp
+     * @return byte array of 6 bytes
+     */
+    public static byte[] shortCalendarToRawBytes(Calendar timestamp) {
+        // MiBand2:
+        // year,year,month,dayofmonth,hour,minute
+
+        byte[] year = fromUint16(timestamp.get(Calendar.YEAR));
+        return new byte[] {
+                year[0],
+                year[1],
+                fromUint8(timestamp.get(Calendar.MONTH) + 1),
+                fromUint8(timestamp.get(Calendar.DATE)),
+                fromUint8(timestamp.get(Calendar.HOUR_OF_DAY)),
+                fromUint8(timestamp.get(Calendar.MINUTE))
+        };
+    }
+
+    private static int getMiBand2TimeZone(int rawOffset) {
+        int offsetMinutes = rawOffset / 1000 / 60;
+        rawOffset = offsetMinutes < 0 ? -1 : 1;
+        offsetMinutes = Math.abs(offsetMinutes);
+        int offsetHours = offsetMinutes / 60;
+        rawOffset *= offsetMinutes % 60 / 15 + offsetHours * 4;
+        return rawOffset;
+    }
+
+    public static byte dayOfWeekToRawBytes(Calendar cal) {
+        int calValue = cal.get(Calendar.DAY_OF_WEEK);
+        switch (calValue) {
+            case Calendar.SUNDAY:
+                return 7;
+            default:
+                return (byte) (calValue - 1);
+        }
+    }
+
+    /**
+     * uses the standard algorithm to convert bytes received from the MiBand to a Calendar object
+     *
+     * @param value
+     * @return
+     */
+    public static GregorianCalendar rawBytesToCalendar(byte[] value) {
+        if (value.length >= 7) {
+            int year = toUint16(value[0], value[1]);
+            GregorianCalendar timestamp = new GregorianCalendar(
+                    year,
+                    (value[2] & 0xff) - 1,
+                    value[3] & 0xff,
+                    value[4] & 0xff,
+                    value[5] & 0xff,
+                    value[6] & 0xff
+            );
+
+            if (value.length > 7) {
+                /* when we get a timezone offset via BLE, we cannot know which timeszone this is (only its offset), so
+                   set to UTC which does not use DST to prevent bugs when setting the raw offset below */
+                TimeZone timeZone = TimeZone.getTimeZone("UTC");
+                timeZone.setRawOffset(value[7] * 15 * 60 * 1000);
+                timestamp.setTimeZone(timeZone);
+            }
+            return timestamp;
+        }
+
+        return createCalendar();
+    }
+
+    public static long toUnsigned(int unsignedInt) {
+        return ((long) unsignedInt) & 0xffffffffL;
+    }
+    public static int toUnsigned(short value) {
+        return value & 0xffff;
+    }
+
+    public static int toUnsigned(byte value) {
+        return value & 0xff;
+    }
+
+    public static int toUnsigned(byte[] bytes, int offset) {
+        return bytes[offset + 0] & 0xff;
+    }
+
+    public static int toUint16(byte value) {
+        return toUnsigned(value);
+    }
+
+    public static int toUint16(byte... bytes) {
+        return (bytes[0] & 0xff) | ((bytes[1] & 0xff) << 8);
+    }
+
+    public static int toUint16(byte[] bytes, int offset) {
+        return (bytes[offset + 0] & 0xff) | ((bytes[offset + 1] & 0xff) << 8);
+    }
+
+    public static int toInt16(byte... bytes) {
+        return (short) (bytes[0] & 0xff | ((bytes[1] & 0xff) << 8));
+    }
+
+    public static int toUint32(byte... bytes) {
+        return (bytes[0] & 0xff) | ((bytes[1] & 0xff) << 8) | ((bytes[2] & 0xff) << 16) | ((bytes[3] & 0xff) << 24);
+    }
+
+    public static int toUint32(byte[] bytes, int offset) {
+        return (bytes[offset + 0] & 0xff) | ((bytes[offset + 1] & 0xff) << 8) | ((bytes[offset + 2] & 0xff) << 16) | ((bytes[offset + 3] & 0xff) << 24);
+    }
+
+    public static long toUint64(byte... bytes) {
+        return (bytes[0] & 0xFFL) | ((bytes[1] & 0xFFL) << 8) | ((bytes[2] & 0xFFL) << 16) | ((bytes[3] & 0xFFL) << 24) |
+                ((bytes[4] & 0xFFL) << 32) | ((bytes[5] & 0xFFL) << 40) | ((bytes[6] & 0xFFL) << 48) | ((bytes[7] & 0xFFL) << 56);
+    }
+
+    public static long toUint64(byte[] bytes, int offset) {
+        return (bytes[offset + 0] & 0xFFL) | ((bytes[offset + 1] & 0xFFL) << 8) | ((bytes[offset + 2] & 0xFFL) << 16) | ((bytes[offset + 3] & 0xFFL) << 24) |
+                ((bytes[offset + 4] & 0xFFL) << 32) | ((bytes[offset + 5] & 0xFFL) << 40) | ((bytes[offset + 6] & 0xFFL) << 48) | ((bytes[offset + 7] & 0xFFL) << 56);
+    }
+
+    public static byte[] fromUint16(int value) {
+        return new byte[] {
+                (byte) (value & 0xff),
+                (byte) ((value >> 8) & 0xff),
+        };
+    }
+
+    public static byte[] fromUint24(int value) {
+        return new byte[] {
+                (byte) (value & 0xff),
+                (byte) ((value >> 8) & 0xff),
+                (byte) ((value >> 16) & 0xff),
+        };
+    }
+
+    public static byte[] fromUint32(int value) {
+        return new byte[] {
+                (byte) (value & 0xff),
+                (byte) ((value >> 8) & 0xff),
+                (byte) ((value >> 16) & 0xff),
+                (byte) ((value >> 24) & 0xff),
+        };
+    }
+
+    public static byte[] fromUint64(long value) {
+        return new byte[] {
+                (byte) (value & 0xff),
+                (byte) ((value >> 8) & 0xff),
+                (byte) ((value >> 16) & 0xff),
+                (byte) ((value >> 24) & 0xff),
+                (byte) ((value >> 32) & 0xff),
+                (byte) ((value >> 40) & 0xff),
+                (byte) ((value >> 48) & 0xff),
+                (byte) ((value >> 56) & 0xff),
+        };
+    }
+
+    public static byte fromUint8(int value) {
+        return (byte) (value & 0xff);
+    }
+
+    public static void writeUint8(byte[] array, int offset, int value) {
+        array[offset] = (byte) value;
+    }
+
+    public static void writeUint16(byte[] array, int offset, int value) {
+        array[offset] = (byte) value;
+        array[offset + 1] = (byte) (value >> 8);
+    }
+
+    public static void writeUint32(byte[] array, int offset, int value) {
+        array[offset] = (byte) value;
+        array[offset + 1] = (byte) (value >> 8);
+        array[offset + 2] = (byte) (value >> 16);
+        array[offset + 3] = (byte) (value >> 24);
+    }
+
+    public static void writeUint64(byte[] array, int offset, long value) {
+        array[offset] = (byte) value;
+        array[offset + 1] = (byte) (value >> 8);
+        array[offset + 2] = (byte) (value >> 16);
+        array[offset + 3] = (byte) (value >> 24);
+        array[offset + 4] = (byte) (value >> 32);
+        array[offset + 5] = (byte) (value >> 40);
+        array[offset + 6] = (byte) (value >> 48);
+        array[offset + 7] = (byte) (value >> 56);
+    }
+
+    /**
+     * Creates a calendar object representing the current date and time.
+     */
+    public static GregorianCalendar createCalendar() {
+        return new GregorianCalendar();
+    }
+
+    public static byte[] join(byte[] start, byte[] end) {
+        if (start == null || start.length == 0) {
+            return end;
+        }
+        if (end == null || end.length == 0) {
+            return start;
+        }
+
+        byte[] result = new byte[start.length + end.length];
+        System.arraycopy(start, 0, result, 0, start.length);
+        System.arraycopy(end, 0, result, start.length, end.length);
+        return result;
+    }
+
+    public static byte[] calendarToLocalTimeBytes(GregorianCalendar now) {
+        byte[] result = new byte[2];
+        result[0] = mapTimeZone(now.getTimeZone());
+        result[1] = mapDstOffset(now);
+        return result;
+    }
+
+    /**
+     * https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.time_zone.xml
+     * @param timeZone
+     * @return sint8 value from -48..+56
+     */
+    public static byte mapTimeZone(TimeZone timeZone) {
+        int offsetMillis = timeZone.getRawOffset();
+        int utcOffsetInQuarterHours = (offsetMillis / (1000 * 60 * 15));
+        return (byte) utcOffsetInQuarterHours;
+    }
+
+    /**
+     * https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.time_zone.xml
+     *
+     * @param calendar
+     * @return sint8 value from -48..+56
+     */
+    public static byte mapTimeZone(Calendar calendar, int timezoneFlags) {
+        int offsetMillis = calendar.getTimeZone().getRawOffset();
+        if (timezoneFlags == TZ_FLAG_INCLUDE_DST_IN_TZ) {
+            offsetMillis = calendar.getTimeZone().getOffset(calendar.getTimeInMillis());
+        }
+        int utcOffsetInQuarterHours = (offsetMillis / (1000 * 60 * 15));
+        return (byte) utcOffsetInQuarterHours;
+    }
+
+    /**
+     * https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.dst_offset.xml
+     * @param now
+     * @return the DST offset for the given time; 0 if none; 255 if unknown
+     */
+    public static byte mapDstOffset(Calendar now) {
+        TimeZone timeZone = now.getTimeZone();
+        int dstSavings = timeZone.getDSTSavings();
+        if (dstSavings == 0) {
+            return 0;
+        }
+        if (timeZone.inDaylightTime(now.getTime())) {
+            int dstInMinutes = dstSavings / (1000 * 60);
+            switch (dstInMinutes) {
+                case 30:
+                    return 2;
+                case 60:
+                    return 4;
+                case 120:
+                    return 8;
+            }
+            return fromUint8(255); // unknown
+        }
+        return 0;
+    }
+
+    public static byte[] toUtf8s(String message) {
+        return message.getBytes(StandardCharsets.UTF_8);
+    }
+
+    public static AlertCategory toAlertCategory(NotificationType type) {
+        switch (type) {
+            case GENERIC_ALARM_CLOCK:
+                return AlertCategory.HighPriorityAlert;
+            case GENERIC_SMS:
+                return AlertCategory.SMS;
+            case GENERIC_EMAIL:
+            case GMAIL:
+            case OUTLOOK:
+            case YAHOO_MAIL:
+                return AlertCategory.Email;
+            case GENERIC_NAVIGATION:
+                return AlertCategory.Simple;
+            case CONVERSATIONS:
+            case FACEBOOK_MESSENGER:
+            case GOOGLE_MESSENGER:
+            case GOOGLE_HANGOUTS:
+            case HIPCHAT:
+            case KAKAO_TALK:
+            case LINE:
+            case RIOT:
+            case SIGNAL:
+            case WIRE:
+            case SKYPE:
+            case SNAPCHAT:
+            case TELEGRAM:
+            case THREEMA:
+            case KONTALK:
+            case ANTOX:
+            case TWITTER:
+            case WHATSAPP:
+            case VIBER:
+            case WECHAT:
+                return AlertCategory.InstantMessage;
+        }
+        return AlertCategory.Simple;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BTLEOperation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BTLEOperation.java
new file mode 100644
index 0000000..02cf507
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BTLEOperation.java
@@ -0,0 +1,28 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, Carsten Pfeiffer
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.btle;
+
+import java.io.IOException;
+
+public interface BTLEOperation {
+    void perform() throws IOException;
+
+    /**
+     * Returns a human readable name of this operation, to be used e.g. in log output.
+     */
+    String getName();
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BleIntentApi.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BleIntentApi.java
new file mode 100644
index 0000000..7eb1ae6
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BleIntentApi.java
@@ -0,0 +1,241 @@
+package nodomain.freeyourgadget.gadgetbridge.service.btle;
+
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREFS_KEY_DEVICE_BLE_API_DEVICE_NOTIFY;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREFS_KEY_DEVICE_BLE_API_DEVICE_READ_WRITE;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREFS_KEY_DEVICE_BLE_API_DEVICE_STATE;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREFS_KEY_DEVICE_BLE_API_PACKAGE;
+
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.bluetooth.BluetoothGattServer;
+import android.bluetooth.BluetoothGattService;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.widget.Toast;
+
+import androidx.core.content.ContextCompat;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.HashMap;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
+
+public class BleIntentApi {
+    private Context context;
+    GBDevice device;
+    BtLEQueue queue;
+    Logger logger;
+
+    private boolean intentApiEnabledDeviceState = false;
+    private boolean intentApiEnabledReadWrite= false;
+    private boolean intentApiEnabledNotifications= false;
+    private String intentApiPackage = "";
+    private boolean intentApiCharacteristicReceiverRegistered = false;
+    private boolean intentApiDeviceStateReceiverRegistered = false;
+    private String lastReportedState = null;
+
+    private final HashMap<String, BluetoothGattCharacteristic> characteristics = new HashMap<>();
+
+    public static final String BLE_API_COMMAND_READ = "nodomain.freeyourgadget.gadgetbridge.ble_api.commands.CHARACTERISTIC_READ";
+    public static final String BLE_API_COMMAND_WRITE = "nodomain.freeyourgadget.gadgetbridge.ble_api.commands.CHARACTERISTIC_WRITE";
+    public static final String BLE_API_EVENT_CHARACTERISTIC_CHANGED = "nodomain.freeyourgadget.gadgetbridge.ble_api.events.CHARACTERISTIC_CHANGED";
+
+
+    BroadcastReceiver intentApiReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+
+            boolean isWrite = BLE_API_COMMAND_WRITE.equals(action);
+
+            boolean isRead = BLE_API_COMMAND_READ.equals(action);
+
+            if((!isWrite) && (!isRead)) {
+                return;
+            }
+
+            if (!concernsThisDevice(intent)) {
+                return;
+            }
+
+            if(!getDevice().getState().equalsOrHigherThan(GBDevice.State.INITIALIZED)) {
+                logger.error(String.format("BLE API: Device %s not initialized.", getDevice()));
+                return;
+            }
+
+            String uuid = intent.getStringExtra("EXTRA_CHARACTERISTIC_UUID");
+            if (StringUtils.isNullOrEmpty(uuid)) {
+                logger.error("BLE API: missing EXTRA_CHARACTERISTIC_UUID");
+                return;
+            }
+
+            String hexData = intent.getStringExtra("EXTRA_PAYLOAD");
+            if (isWrite && (hexData == null)) {
+                logger.error("BLE API: missing EXTRA_PAYLOAD");
+                return;
+            }
+
+            BluetoothGattCharacteristic characteristic = characteristics.get(uuid);
+
+            if(characteristic == null) {
+                logger.error("Characteristic {} not found", uuid);
+                return;
+            }
+
+            if(isWrite) {
+                new TransactionBuilder("BLE API write")
+                        .write(characteristic, StringUtils.hexToBytes(hexData))
+                        .queue(getQueue());
+                return;
+            }
+
+            if(isRead) {
+                new TransactionBuilder("BLE API read")
+                        .read(characteristic)
+                        .queue(getQueue());
+                return;
+            }
+        }
+    };
+
+    public static boolean isEnabled(GBDevice device) {
+        Prefs devicePrefs = GBApplication.getDevicePrefs(device);
+
+        boolean intentApiEnabledReadWrite = devicePrefs.getBoolean(PREFS_KEY_DEVICE_BLE_API_DEVICE_READ_WRITE, false);
+        boolean intentApiEnabledNotifications = devicePrefs.getBoolean(PREFS_KEY_DEVICE_BLE_API_DEVICE_NOTIFY, false);
+        boolean intentApiEnabledDeviceState = devicePrefs.getBoolean(PREFS_KEY_DEVICE_BLE_API_DEVICE_STATE, false);
+
+        return intentApiEnabledReadWrite | intentApiEnabledNotifications | intentApiEnabledDeviceState;
+    }
+
+    public void onCharacteristicChanged(BluetoothGattCharacteristic characteristic) {
+        if(!intentApiEnabledNotifications) {
+            return;
+        }
+        Intent intent = getBleApiIntent(BLE_API_EVENT_CHARACTERISTIC_CHANGED);
+        if(!StringUtils.isNullOrEmpty(intentApiPackage)) {
+            intent.setPackage(intentApiPackage);
+        }
+        intent.putExtra("EXTRA_CHARACTERISTIC", characteristic.getUuid().toString());
+        intent.putExtra("EXTRA_PAYLOAD", StringUtils.bytesToHex(characteristic.getValue()));
+
+        getContext().sendBroadcast(intent);
+    }
+
+    public void initializeDevice(TransactionBuilder builder) {
+        if(intentApiEnabledNotifications) {
+            for (BluetoothGattCharacteristic characteristic : characteristics.values()) {
+                builder.notify(characteristic, true);
+            }
+        }
+    }
+
+    public void dispose() {
+        registerBleApiCharacteristicReceivers(false);
+    }
+
+    public void onSendConfiguration(String config) {
+        if(StringUtils.isNullOrEmpty(config)) {
+            return;
+        }
+        if(config.startsWith("prefs_device_ble_api_")) {
+            // could subscribe here, but there is more setup to do than that...
+            // handleBLEApiPrefs();
+            GB.toast(
+                    getContext().getString(R.string.toast_setting_requires_reconnect),
+                    Toast.LENGTH_SHORT,
+                    GB.INFO
+            );
+        };
+    }
+
+    public Context getContext() {
+        return context;
+    }
+
+    public BtLEQueue getQueue() {
+        return queue;
+    }
+
+    public void setQueue(BtLEQueue queue) {
+        this.queue = queue;
+    }
+
+    private void registerBleApiCharacteristicReceivers(boolean enable){
+        if(enable == intentApiCharacteristicReceiverRegistered) {
+            return;
+        }
+
+        if(enable){
+            IntentFilter filter = new IntentFilter();
+            filter.addAction(BLE_API_COMMAND_READ);
+            filter.addAction(BLE_API_COMMAND_WRITE);
+
+            ContextCompat.registerReceiver(
+                    getContext(),
+                    intentApiReceiver,
+                    filter,
+                    ContextCompat.RECEIVER_EXPORTED
+            );
+        }else{
+            getContext().unregisterReceiver(intentApiReceiver);
+        }
+        intentApiCharacteristicReceiverRegistered = intentApiEnabledReadWrite;
+    }
+
+    public void addService(BluetoothGattService service) {
+        for (BluetoothGattCharacteristic characteristic : service.getCharacteristics()) {
+            this.characteristics.put(characteristic.getUuid().toString(), characteristic);
+        }
+    }
+
+    public void handleBLEApiPrefs(){
+        Prefs devicePrefs = GBApplication.getDevicePrefs(getDevice());
+        this.intentApiEnabledReadWrite = devicePrefs.getBoolean(PREFS_KEY_DEVICE_BLE_API_DEVICE_READ_WRITE, false);
+        this.intentApiEnabledNotifications = devicePrefs.getBoolean(PREFS_KEY_DEVICE_BLE_API_DEVICE_NOTIFY, false);
+        this.intentApiEnabledDeviceState = devicePrefs.getBoolean(PREFS_KEY_DEVICE_BLE_API_DEVICE_STATE, false);
+        this.intentApiPackage = devicePrefs.getString(PREFS_KEY_DEVICE_BLE_API_PACKAGE, "");
+
+        registerBleApiCharacteristicReceivers(this.intentApiEnabledReadWrite);
+    }
+
+    public static Intent getBleApiIntent(String deviceAddress, String action) {
+        Intent updateIntent = new Intent(action);
+        updateIntent.putExtra("EXTRA_DEVICE_ADDRESS", deviceAddress);
+        return updateIntent;
+    }
+
+    private Intent getBleApiIntent(String action) {
+        return getBleApiIntent(getDevice().getAddress(), action);
+    }
+
+    public BleIntentApi(Context context, GBDevice device) {
+        this.context = context;
+        this.device = device;
+
+        this.logger = LoggerFactory.getLogger(BleIntentApi.class);
+    }
+
+    public GBDevice getDevice() {
+        return device;
+    }
+
+    private boolean concernsThisDevice(Intent intent) {
+        String deviceAddress = intent.getStringExtra("EXTRA_DEVICE_ADDRESS");
+        if (StringUtils.isNullOrEmpty(deviceAddress)) {
+            logger.error("BLE API: missing EXTRA_DEVICE_ADDRESS");
+            return false;
+        }
+        return deviceAddress.equalsIgnoreCase(getDevice().getAddress());
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BleNamesResolver.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BleNamesResolver.java
new file mode 100644
index 0000000..e44e03e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BleNamesResolver.java
@@ -0,0 +1,234 @@
+/*  Copyright (C) 2016-2024 Carsten Pfeiffer, Joa虄o Paulo Barraca, JohnnySun
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.btle;
+
+import android.util.SparseArray;
+
+import java.util.HashMap;
+
+public class BleNamesResolver {
+	private static HashMap<String, String> mServices = new HashMap<String, String>();
+	private static HashMap<String, String> mCharacteristics = new HashMap<String, String>();
+	private static SparseArray<String> mValueFormats = new SparseArray<String>();
+	private static SparseArray<String> mAppearance = new SparseArray<String>();
+	private static SparseArray<String> mHeartRateSensorLocation = new SparseArray<String>();
+	
+	static public String resolveServiceName(final String uuid)
+	{
+		String result = mServices.get(uuid);
+		if(result == null) result = "Unknown Service";
+		return result;
+	}
+
+	static public String resolveValueTypeDescription(final int format)
+	{
+		Integer tmp = Integer.valueOf(format);
+		return mValueFormats.get(tmp, "Unknown Format");
+	}	
+	
+	static public String resolveCharacteristicName(final String uuid)
+	{
+		String result = mCharacteristics.get(uuid);
+		if(result == null) result = "Unknown Characteristic";
+		return result;
+	}
+	
+	static public String resolveUuid(final String uuid) {
+		String result = mServices.get(uuid);
+		if(result != null) return "Service: " + result;
+		
+		result = mCharacteristics.get(uuid);
+		if(result != null) return "Characteristic: " + result;
+		
+		result = "Unknown UUID";
+		return result;
+	}
+	
+	static public String resolveAppearance(int key) {
+		Integer tmp = Integer.valueOf(key);
+		return mAppearance.get(tmp, "Unknown Appearance");		
+	}
+	
+	static public String resolveHeartRateSensorLocation(int key) {
+		Integer tmp = Integer.valueOf(key);
+		return mHeartRateSensorLocation.get(tmp, "Other");		
+	}
+	
+	static public boolean isService(final String uuid) {
+		return mServices.containsKey(uuid);
+	}
+
+	static public boolean isCharacteristic(final String uuid) {
+		return mCharacteristics.containsKey(uuid);
+	}	
+	
+	static {
+		mServices.put("00001811-0000-1000-8000-00805f9b34fb", "Alert Notification Service");
+		mServices.put("0000180f-0000-1000-8000-00805f9b34fb", "Battery Service");
+		mServices.put("00001810-0000-1000-8000-00805f9b34fb", "Blood Pressure");
+		mServices.put("00001805-0000-1000-8000-00805f9b34fb", "Current Time Service");
+		mServices.put("00001818-0000-1000-8000-00805f9b34fb", "Cycling Power");
+		mServices.put("00001816-0000-1000-8000-00805f9b34fb", "Cycling Speed and Cadence");
+		mServices.put("0000180a-0000-1000-8000-00805f9b34fb", "Device Information");
+		mServices.put("00001800-0000-1000-8000-00805f9b34fb", "Generic Access");
+		mServices.put("00001801-0000-1000-8000-00805f9b34fb", "Generic Attribute");
+		mServices.put("00001808-0000-1000-8000-00805f9b34fb", "Glucose");
+		mServices.put("00001809-0000-1000-8000-00805f9b34fb", "Health Thermometer");
+		mServices.put("0000180d-0000-1000-8000-00805f9b34fb", "Heart Rate");
+		mServices.put("00001812-0000-1000-8000-00805f9b34fb", "Human Interface Device");
+		mServices.put("00001802-0000-1000-8000-00805f9b34fb", "Immediate Alert");
+		mServices.put("00001803-0000-1000-8000-00805f9b34fb", "Link Loss");
+		mServices.put("00001819-0000-1000-8000-00805f9b34fb", "Location and Navigation");
+		mServices.put("00001807-0000-1000-8000-00805f9b34fb", "Next DST Change Service");
+		mServices.put("0000180e-0000-1000-8000-00805f9b34fb", "Phone Alert Status Service");
+		mServices.put("00001806-0000-1000-8000-00805f9b34fb", "Reference Time Update Service");
+		mServices.put("00001814-0000-1000-8000-00805f9b34fb", "Running Speed and Cadence");
+		mServices.put("00001813-0000-1000-8000-00805f9b34fb", "Scan Parameters");
+		mServices.put("00001804-0000-1000-8000-00805f9b34fb", "Tx Power");
+		mServices.put("0000fdab-0000-1000-8000-00805f9b34fb", "(Propr: Xiaomi Proximity Unlock Service)");
+		mServices.put("0000fe95-0000-1000-8000-00805f9b34fb", "(Propr: Xiaomi Wear Service)");
+		mServices.put("0000fee0-0000-3512-2118-0009af100700", "(Propr: Xiaomi MiLi Service)");
+		mServices.put("00001530-0000-3512-2118-0009af100700", "(Propr: Xiaomi Weight Service)");
+		mServices.put("14701820-620a-3973-7c78-9cfff0876abd", "(Propr: HPLUS Service)");
+		mServices.put("16186f00-0000-1000-8000-00807f9b34fb", "(Propr: Xiaomi Wear Service - Mi Watch Lite/Redmi Watch)");
+		mServices.put("16187f00-0000-1000-8000-00807f9b34fb", "(Propr: Xiaomi Wear Service - Mi Smart Watch 4C/Redmi Band)");
+		mServices.put("1314f000-1000-9000-7000-301291e21220", "(Propr: Xiaomi Wear Service - Mi Watch/Mi Watch Color/Mi Watch Color Sport)");
+		mServices.put("7495fe00-a7f3-424b-92dd-4a006a3aef56", "(Propr: Xiaomi Wear Service - Mi Watch CN)");
+		//mServices.put("0000fff0-0000-1000-8000-00805f9b34fb", "(Propr: Nothing CMF Command");
+		//mServices.put("02f00000-0000-0000-0000-00000000ffe0", "(Propr: Nothing CMF Data");
+
+		mCharacteristics.put("00002a43-0000-1000-8000-00805f9b34fb", "Alert AlertCategory ID");
+		mCharacteristics.put("00002a42-0000-1000-8000-00805f9b34fb", "Alert AlertCategory ID Bit Mask");
+		mCharacteristics.put("00002a06-0000-1000-8000-00805f9b34fb", "Alert Level");
+		mCharacteristics.put("00002a44-0000-1000-8000-00805f9b34fb", "Alert Notification Control Point");
+		mCharacteristics.put("00002a3f-0000-1000-8000-00805f9b34fb", "Alert Status");
+		mCharacteristics.put("00002a01-0000-1000-8000-00805f9b34fb", "Appearance");
+		mCharacteristics.put("00002a19-0000-1000-8000-00805f9b34fb", "Battery Level");
+		mCharacteristics.put("00002a49-0000-1000-8000-00805f9b34fb", "Blood Pressure Feature");
+		mCharacteristics.put("00002a35-0000-1000-8000-00805f9b34fb", "Blood Pressure Measurement");
+		mCharacteristics.put("00002a38-0000-1000-8000-00805f9b34fb", "Body Sensor Location");
+		mCharacteristics.put("00002a22-0000-1000-8000-00805f9b34fb", "Boot Keyboard Input Report");
+		mCharacteristics.put("00002a32-0000-1000-8000-00805f9b34fb", "Boot Keyboard Output Report");
+		mCharacteristics.put("00002a33-0000-1000-8000-00805f9b34fb", "Boot Mouse Input Report");
+		mCharacteristics.put("00002a5c-0000-1000-8000-00805f9b34fb", "CSC Feature");
+		mCharacteristics.put("00002a5b-0000-1000-8000-00805f9b34fb", "CSC Measurement");
+		mCharacteristics.put("00002a2b-0000-1000-8000-00805f9b34fb", "Current Time");
+		mCharacteristics.put("00002a66-0000-1000-8000-00805f9b34fb", "Cycling Power Control Point");
+		mCharacteristics.put("00002a65-0000-1000-8000-00805f9b34fb", "Cycling Power Feature");
+		mCharacteristics.put("00002a63-0000-1000-8000-00805f9b34fb", "Cycling Power Measurement");
+		mCharacteristics.put("00002a64-0000-1000-8000-00805f9b34fb", "Cycling Power Vector");
+		mCharacteristics.put("00002a08-0000-1000-8000-00805f9b34fb", "Date Time");
+		mCharacteristics.put("00002a0a-0000-1000-8000-00805f9b34fb", "Day Date Time");
+		mCharacteristics.put("00002a09-0000-1000-8000-00805f9b34fb", "Day of Week");
+		mCharacteristics.put("00002a00-0000-1000-8000-00805f9b34fb", "Device Name");
+		mCharacteristics.put("00002a0d-0000-1000-8000-00805f9b34fb", "DST Offset");
+		mCharacteristics.put("00002a0c-0000-1000-8000-00805f9b34fb", "Exact Time 256");
+		mCharacteristics.put("00002a26-0000-1000-8000-00805f9b34fb", "Firmware Revision String");
+		mCharacteristics.put("00002a51-0000-1000-8000-00805f9b34fb", "Glucose Feature");
+		mCharacteristics.put("00002a18-0000-1000-8000-00805f9b34fb", "Glucose Measurement");
+		mCharacteristics.put("00002a34-0000-1000-8000-00805f9b34fb", "Glucose Measurement Context");
+		mCharacteristics.put("00002a27-0000-1000-8000-00805f9b34fb", "Hardware Revision String");
+		mCharacteristics.put("00002a39-0000-1000-8000-00805f9b34fb", "Heart Rate Control Point");
+		mCharacteristics.put("00002a37-0000-1000-8000-00805f9b34fb", "Heart Rate Measurement");
+		mCharacteristics.put("00002a4c-0000-1000-8000-00805f9b34fb", "HID Control Point");
+		mCharacteristics.put("00002a4a-0000-1000-8000-00805f9b34fb", "HID Information");
+		mCharacteristics.put("00002a2a-0000-1000-8000-00805f9b34fb", "IEEE 11073-20601 Regulatory Certification Data List");
+		mCharacteristics.put("00002a36-0000-1000-8000-00805f9b34fb", "Intermediate Cuff Pressure");
+		mCharacteristics.put("00002a1e-0000-1000-8000-00805f9b34fb", "Intermediate Temperature");
+		mCharacteristics.put("00002a6b-0000-1000-8000-00805f9b34fb", "LN Control Point");
+		mCharacteristics.put("00002a6a-0000-1000-8000-00805f9b34fb", "LN Feature");
+		mCharacteristics.put("00002a0f-0000-1000-8000-00805f9b34fb", "Local Time Information");
+		mCharacteristics.put("00002a67-0000-1000-8000-00805f9b34fb", "Location and Speed");
+		mCharacteristics.put("00002a29-0000-1000-8000-00805f9b34fb", "Manufacturer Name String");
+		mCharacteristics.put("00002a21-0000-1000-8000-00805f9b34fb", "Measurement Interval");
+		mCharacteristics.put("00002a24-0000-1000-8000-00805f9b34fb", "Model Number String");
+		mCharacteristics.put("00002a68-0000-1000-8000-00805f9b34fb", "Navigation");
+		mCharacteristics.put("00002a46-0000-1000-8000-00805f9b34fb", "New Alert");
+		mCharacteristics.put("00002a04-0000-1000-8000-00805f9b34fb", "Peripheral Preferred Connection Parameters");
+		mCharacteristics.put("00002a02-0000-1000-8000-00805f9b34fb", "Peripheral Privacy Flag");
+		mCharacteristics.put("00002a50-0000-1000-8000-00805f9b34fb", "PnP ID");
+		mCharacteristics.put("00002a69-0000-1000-8000-00805f9b34fb", "Position Quality");
+		mCharacteristics.put("00002a4e-0000-1000-8000-00805f9b34fb", "Protocol Mode");
+		mCharacteristics.put("00002a03-0000-1000-8000-00805f9b34fb", "Reconnection Address");
+		mCharacteristics.put("00002a52-0000-1000-8000-00805f9b34fb", "Record Access Control Point");
+		mCharacteristics.put("00002a14-0000-1000-8000-00805f9b34fb", "Reference Time Information");
+		mCharacteristics.put("00002a4d-0000-1000-8000-00805f9b34fb", "Report");
+		mCharacteristics.put("00002a4b-0000-1000-8000-00805f9b34fb", "Report Map");
+		mCharacteristics.put("00002a40-0000-1000-8000-00805f9b34fb", "Ringer Control Point");
+		mCharacteristics.put("00002a41-0000-1000-8000-00805f9b34fb", "Ringer Setting");
+		mCharacteristics.put("00002a54-0000-1000-8000-00805f9b34fb", "RSC Feature");
+		mCharacteristics.put("00002a53-0000-1000-8000-00805f9b34fb", "RSC Measurement");
+		mCharacteristics.put("00002a55-0000-1000-8000-00805f9b34fb", "SC Control Point");
+		mCharacteristics.put("00002a4f-0000-1000-8000-00805f9b34fb", "Scan Interval Window");
+		mCharacteristics.put("00002a31-0000-1000-8000-00805f9b34fb", "Scan Refresh");
+		mCharacteristics.put("00002a5d-0000-1000-8000-00805f9b34fb", "Sensor Location");
+		mCharacteristics.put("00002a25-0000-1000-8000-00805f9b34fb", "Serial Number String");
+		mCharacteristics.put("00002a05-0000-1000-8000-00805f9b34fb", "Service Changed");
+		mCharacteristics.put("00002a28-0000-1000-8000-00805f9b34fb", "Software Revision String");
+		mCharacteristics.put("00002a47-0000-1000-8000-00805f9b34fb", "Supported New Alert AlertCategory");
+		mCharacteristics.put("00002a48-0000-1000-8000-00805f9b34fb", "Supported Unread Alert AlertCategory");
+		mCharacteristics.put("00002a23-0000-1000-8000-00805f9b34fb", "System ID");
+		mCharacteristics.put("00002a1c-0000-1000-8000-00805f9b34fb", "Temperature Measurement");
+		mCharacteristics.put("00002a1d-0000-1000-8000-00805f9b34fb", "Temperature Type");
+		mCharacteristics.put("00002a12-0000-1000-8000-00805f9b34fb", "Time Accuracy");
+		mCharacteristics.put("00002a13-0000-1000-8000-00805f9b34fb", "Time Source");
+		mCharacteristics.put("00002a16-0000-1000-8000-00805f9b34fb", "Time Update Control Point");
+		mCharacteristics.put("00002a17-0000-1000-8000-00805f9b34fb", "Time Update State");
+		mCharacteristics.put("00002a11-0000-1000-8000-00805f9b34fb", "Time with DST");
+		mCharacteristics.put("00002a0e-0000-1000-8000-00805f9b34fb", "Time Zone");
+		mCharacteristics.put("00002a07-0000-1000-8000-00805f9b34fb", "Tx Power Level");
+		mCharacteristics.put("00002a45-0000-1000-8000-00805f9b34fb", "Unread Alert Status");
+		
+		mCharacteristics.put("14702856-620a-3973-7c78-9cfff0876abd", "(Propr: HPLUS Control)");
+		mCharacteristics.put("14702853-620a-3973-7c78-9cfff0876abd", "(Propr: HPLUS Measurements)");
+		//mCharacteristics.put("0000fff1-0000-1000-8000-00805f9b34fb", "(Propr: Nothing CMF Command Read");
+		//mCharacteristics.put("0000fff2-0000-1000-8000-00805f9b34fb", "(Propr: Nothing CMF Command Write");
+		//mCharacteristics.put("02f00000-0000-0000-0000-00000000ffe1", "(Propr: Nothing CMF Data Write");
+		//mCharacteristics.put("02f00000-0000-0000-0000-00000000ffe2", "(Propr: Nothing CMF Data Read");
+
+		mValueFormats.put(Integer.valueOf(52), "32bit float");
+		mValueFormats.put(Integer.valueOf(50), "16bit float");
+		mValueFormats.put(Integer.valueOf(34), "16bit signed int");
+		mValueFormats.put(Integer.valueOf(36), "32bit signed int");
+		mValueFormats.put(Integer.valueOf(33), "8bit signed int");
+		mValueFormats.put(Integer.valueOf(18), "16bit unsigned int");
+		mValueFormats.put(Integer.valueOf(20), "32bit unsigned int");
+		mValueFormats.put(Integer.valueOf(17), "8bit unsigned int");
+		
+		// lets add also couple appearance string description
+		// https://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicViewer.aspx?u=org.bluetooth.characteristic.gap.appearance.xml
+		mAppearance.put(Integer.valueOf(833), "Heart Rate Sensor: Belt");
+		mAppearance.put(Integer.valueOf(832), "Generic Heart Rate Sensor");
+		mAppearance.put(Integer.valueOf(0), "Unknown");
+		mAppearance.put(Integer.valueOf(64), "Generic Phone");
+		mAppearance.put(Integer.valueOf(1157), "Cycling: Speed and Cadence Sensor");
+		mAppearance.put(Integer.valueOf(1152), "General Cycling");
+		mAppearance.put(Integer.valueOf(1153), "Cycling Computer");
+		mAppearance.put(Integer.valueOf(1154), "Cycling: Speed Sensor");
+		mAppearance.put(Integer.valueOf(1155), "Cycling: Cadence Sensor");
+		mAppearance.put(Integer.valueOf(1156), "Cycling: Speed and Cadence Sensor");
+		mAppearance.put(Integer.valueOf(1157), "Cycling: Power Sensor");
+		
+		mHeartRateSensorLocation.put(Integer.valueOf(0), "Other");
+		mHeartRateSensorLocation.put(Integer.valueOf(1), "Chest");
+		mHeartRateSensorLocation.put(Integer.valueOf(2), "Wrist");
+		mHeartRateSensorLocation.put(Integer.valueOf(3), "Finger");
+		mHeartRateSensorLocation.put(Integer.valueOf(4), "Hand");
+		mHeartRateSensorLocation.put(Integer.valueOf(5), "Ear Lobe");
+		mHeartRateSensorLocation.put(Integer.valueOf(6), "Foot");
+	}
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BtLEAction.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BtLEAction.java
new file mode 100644
index 0000000..bdafa4d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BtLEAction.java
@@ -0,0 +1,78 @@
+/*  Copyright (C) 2015-2024 Carsten Pfeiffer, Uwe Hermann
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.btle;
+
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+
+import java.util.Date;
+
+import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
+
+/**
+ * The Bluedroid implementation only allows performing one GATT request at a time.
+ * As they are asynchronous anyway, we encapsulate every GATT request (read and write)
+ * inside a runnable action.
+ * <p/>
+ * These actions are then executed one after another, ensuring that every action's result
+ * has been posted before invoking the next action.
+ */
+public abstract class BtLEAction {
+    private final BluetoothGattCharacteristic characteristic;
+    private final long creationTimestamp;
+
+    public BtLEAction(BluetoothGattCharacteristic characteristic) {
+        this.characteristic = characteristic;
+        creationTimestamp = System.currentTimeMillis();
+    }
+
+    /**
+     * Returns true if this action expects an (async) result which must
+     * be waited for, before continuing with other actions.
+     * <p/>
+     * This is needed because the current Bluedroid stack can only deal
+     * with one single bluetooth operation at a time.
+     */
+    public abstract boolean expectsResult();
+
+    /**
+     * Executes this action, e.g. reads or write a GATT characteristic.
+     *
+     * @param gatt the characteristic to manipulate, or null if none.
+     * @return true if the action was successful, false otherwise
+     */
+    public abstract boolean run(BluetoothGatt gatt);
+
+    /**
+     * Returns the GATT characteristic being read/written/...
+     *
+     * @return the GATT characteristic, or <code>null</code>
+     */
+    public BluetoothGattCharacteristic getCharacteristic() {
+        return characteristic;
+    }
+
+    protected String getCreationTime() {
+        return DateTimeUtils.formatDateTime(new Date(creationTimestamp));
+    }
+
+    public String toString() {
+        BluetoothGattCharacteristic characteristic = getCharacteristic();
+        String uuid = characteristic == null ? "(null)" : characteristic.getUuid().toString();
+        return getCreationTime() + ": " + getClass().getSimpleName() + " on characteristic: " + uuid;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BtLEQueue.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BtLEQueue.java
new file mode 100644
index 0000000..bf585ce
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BtLEQueue.java
@@ -0,0 +1,828 @@
+/*  Copyright (C) 2015-2024 Andreas B枚hler, Andreas Shimokawa, Carsten
+    Pfeiffer, Cre3per, Daniel Dakhno, Daniele Gobbetti, Gordon Williams, Jos茅
+    Rebelo, Sergey Trofimov, Taavi Eom盲e, Uwe Hermann, Yoran Vulker
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.btle;
+
+import android.annotation.SuppressLint;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCallback;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.bluetooth.BluetoothGattDescriptor;
+import android.bluetooth.BluetoothGattServer;
+import android.bluetooth.BluetoothGattServerCallback;
+import android.bluetooth.BluetoothGattService;
+import android.bluetooth.BluetoothManager;
+import android.bluetooth.BluetoothProfile;
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+
+import androidx.annotation.Nullable;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.LinkedBlockingQueue;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.Logging;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice.State;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.WriteAction;
+
+/**
+ * One queue/thread per connectable device.
+ */
+@SuppressLint("MissingPermission") // if we're using this, we have bluetooth permissions
+public final class BtLEQueue {
+    private static final Logger LOG = LoggerFactory.getLogger(BtLEQueue.class);
+
+    private final Object mGattMonitor = new Object();
+    private final GBDevice mGbDevice;
+    private final BluetoothAdapter mBluetoothAdapter;
+    private BluetoothGatt mBluetoothGatt;
+    private BluetoothGattServer mBluetoothGattServer;
+    private final Set<BluetoothGattService> mSupportedServerServices;
+
+    private final BlockingQueue<AbstractTransaction> mTransactions = new LinkedBlockingQueue<>();
+    private volatile boolean mDisposed;
+    private volatile boolean mCrashed;
+    private volatile boolean mAbortTransaction;
+    private volatile boolean mAbortServerTransaction;
+    private volatile boolean mPauseTransaction = false;
+
+    private final Context mContext;
+    private CountDownLatch mWaitForActionResultLatch;
+    private CountDownLatch mWaitForServerActionResultLatch;
+    private CountDownLatch mConnectionLatch;
+    private BluetoothGattCharacteristic mWaitCharacteristic;
+    private final InternalGattCallback internalGattCallback;
+    private final InternalGattServerCallback internalGattServerCallback;
+    private boolean mAutoReconnect;
+    private boolean scanReconnect;
+    private boolean mImplicitGattCallbackModify = true;
+    private boolean mSendWriteRequestResponse = false;
+
+    private Thread dispatchThread = new Thread("Gadgetbridge GATT Dispatcher") {
+
+        @Override
+        public void run() {
+            LOG.debug("Queue Dispatch Thread started.");
+
+            while (!mDisposed && !mCrashed) {
+                try {
+                    AbstractTransaction qTransaction = mTransactions.take();
+
+                    if (!isConnected()) {
+                        LOG.debug("not connected, waiting for connection...");
+                        // TODO: request connection and initialization from the outside and wait until finished
+                        internalGattCallback.reset();
+
+                        // wait until the connection succeeds before running the actions
+                        // Note that no automatic connection is performed. This has to be triggered
+                        // on the outside typically by the DeviceSupport. The reason is that
+                        // devices have different kinds of initializations and this class has no
+                        // idea about them.
+                        mConnectionLatch = new CountDownLatch(1);
+                        mConnectionLatch.await();
+                        mConnectionLatch = null;
+                    }
+
+                    if(qTransaction instanceof ServerTransaction) {
+                        ServerTransaction serverTransaction = (ServerTransaction)qTransaction;
+                        internalGattServerCallback.setTransactionGattCallback(serverTransaction.getGattCallback());
+                        mAbortServerTransaction = false;
+
+                        for (BtLEServerAction action : serverTransaction.getActions()) {
+                            if (mAbortServerTransaction) { // got disconnected
+                                LOG.info("Aborting running server transaction");
+                                break;
+                            }
+                            if (LOG.isDebugEnabled()) {
+                                LOG.debug("About to run server action: " + action);
+                            }
+                            if (action.run(mBluetoothGattServer)) {
+                                // check again, maybe due to some condition, action did not need to write, so we can't wait
+                                boolean waitForResult = action.expectsResult();
+                                if (waitForResult) {
+                                    mWaitForServerActionResultLatch.await();
+                                    mWaitForServerActionResultLatch = null;
+                                    if (mAbortServerTransaction) {
+                                        break;
+                                    }
+                                }
+                            } else {
+                                LOG.error("Server action returned false: {}", action);
+                                break; // abort the transaction
+                            }
+                        }
+                    }
+
+                    if(qTransaction instanceof Transaction) {
+                        Transaction transaction = (Transaction)qTransaction;
+                        LOG.trace("Changing gatt callback for {}? {}", transaction.getTaskName(), transaction.isModifyGattCallback());
+                        if (mImplicitGattCallbackModify || transaction.isModifyGattCallback()) {
+                            internalGattCallback.setTransactionGattCallback(transaction.getGattCallback());
+                        }
+                        mAbortTransaction = false;
+                        // Run all actions of the transaction until one doesn't succeed
+                        for (BtLEAction action : transaction.getActions()) {
+                            if (mAbortTransaction) { // got disconnected
+                                LOG.info("Aborting running transaction");
+                                break;
+                            }
+                            while ((action instanceof WriteAction) && mPauseTransaction && !mAbortTransaction) {
+                              LOG.info("Pausing WriteAction");
+                              try {
+                                  Thread.sleep(100);
+                              } catch (Exception e) {
+                                  LOG.info("Exception during pause: {}", e);
+                                  break;
+                              }
+                            }
+                            mWaitCharacteristic = action.getCharacteristic();
+                            mWaitForActionResultLatch = new CountDownLatch(1);
+                            if (LOG.isDebugEnabled()) {
+                                LOG.debug("About to run action: " + action);
+                            }
+                            if (action instanceof GattListenerAction) {
+                                // this special action overwrites the transaction gatt listener (if any), it must
+                                // always be the last action in the transaction
+                                internalGattCallback.setTransactionGattCallback(((GattListenerAction) action).getGattCallback());
+                            }
+                            if (action.run(mBluetoothGatt)) {
+                                // check again, maybe due to some condition, action did not need to write, so we can't wait
+                                boolean waitForResult = action.expectsResult();
+                                if (waitForResult) {
+                                    mWaitForActionResultLatch.await();
+                                    mWaitForActionResultLatch = null;
+                                    if (mAbortTransaction) {
+                                        break;
+                                    }
+                                }
+                            } else {
+                                LOG.error("Action returned false: {}", action);
+                                break; // abort the transaction
+                            }
+                        }
+                    }
+                } catch (InterruptedException ignored) {
+                    mConnectionLatch = null;
+                    LOG.debug("Thread interrupted");
+                } catch (Throwable ex) {
+                    LOG.error("Queue Dispatch Thread died", ex);
+                    mCrashed = true;
+                    mConnectionLatch = null;
+                } finally {
+                    mWaitForActionResultLatch = null;
+                    mWaitCharacteristic = null;
+                }
+            }
+            LOG.info("Queue Dispatch Thread terminated.");
+        }
+    };
+
+    public BtLEQueue(BluetoothAdapter bluetoothAdapter, GBDevice gbDevice, GattCallback externalGattCallback, GattServerCallback externalGattServerCallback, Context context, Set<BluetoothGattService> supportedServerServices) {
+        mBluetoothAdapter = bluetoothAdapter;
+        mGbDevice = gbDevice;
+        internalGattCallback = new InternalGattCallback(externalGattCallback);
+        internalGattServerCallback = new InternalGattServerCallback(externalGattServerCallback);
+        mContext = context;
+        mSupportedServerServices = supportedServerServices;
+
+        dispatchThread.start();
+    }
+
+    public void setAutoReconnect(boolean enable) {
+        mAutoReconnect = enable;
+    }
+
+    public void setScanReconnect(boolean enable){
+        this.scanReconnect = enable;
+    }
+
+    public void setImplicitGattCallbackModify(final boolean enable) {
+        mImplicitGattCallbackModify = enable;
+    }
+
+    public void setSendWriteRequestResponse(final boolean enable) {
+        mSendWriteRequestResponse = enable;
+    }
+
+    private boolean isConnected() {
+        if (mGbDevice.isConnected()) {
+            return true;
+        }
+
+        LOG.debug("isConnected(): current state = {}", mGbDevice.getState());
+        return false;
+    }
+
+    /**
+     * Connects to the given remote device. Note that this does not perform any device
+     * specific initialization. This should be done in the specific {@link DeviceSupport}
+     * class.
+     *
+     * @return <code>true</code> whether the connection attempt was successfully triggered and <code>false</code> if that failed or if there is already a connection
+     */
+    public boolean connect() {
+        mPauseTransaction = false;
+        if (isConnected()) {
+            LOG.warn("Ingoring connect() because already connected.");
+            return false;
+        }
+        synchronized (mGattMonitor) {
+            if (mBluetoothGatt != null) {
+                // Tribal knowledge says you're better off not reusing existing BluetoothGatt connections,
+                // so create a new one.
+                LOG.info("connect() requested -- disconnecting previous connection: " + mGbDevice.getName());
+                disconnect();
+            }
+        }
+        LOG.info("Attempting to connect to {}", mGbDevice.getName());
+        mBluetoothAdapter.cancelDiscovery();
+        BluetoothDevice remoteDevice = mBluetoothAdapter.getRemoteDevice(mGbDevice.getAddress());
+        if(!mSupportedServerServices.isEmpty()) {
+            BluetoothManager bluetoothManager = (BluetoothManager) mContext.getSystemService(Context.BLUETOOTH_SERVICE);
+            if (bluetoothManager == null) {
+                LOG.error("Error getting bluetoothManager");
+                return false;
+            }
+            mBluetoothGattServer = bluetoothManager.openGattServer(mContext, internalGattServerCallback);
+            if (mBluetoothGattServer == null) {
+                LOG.error("Error opening Gatt Server");
+                return false;
+            }
+            for(BluetoothGattService service : mSupportedServerServices) {
+                mBluetoothGattServer.addService(service);
+            }
+        }
+
+        synchronized (mGattMonitor) {
+            // connectGatt with true doesn't really work ;( too often connection problems
+            if (GBApplication.isRunningMarshmallowOrLater()) {
+                mBluetoothGatt = remoteDevice.connectGatt(mContext, false, internalGattCallback, BluetoothDevice.TRANSPORT_LE);
+            } else {
+                mBluetoothGatt = remoteDevice.connectGatt(mContext, false, internalGattCallback);
+            }
+        }
+        boolean result = mBluetoothGatt != null;
+        if (result) {
+            setDeviceConnectionState(State.CONNECTING);
+        }
+        return result;
+    }
+
+    private void setDeviceConnectionState(final State newState) {
+        new Handler(Looper.getMainLooper()).post(() -> {
+            LOG.debug("new device connection state: {}", newState);
+            mGbDevice.setState(newState);
+            mGbDevice.sendDeviceUpdateIntent(mContext, GBDevice.DeviceUpdateSubject.CONNECTION_STATE);
+        });
+    }
+
+    public void disconnect() {
+        synchronized (mGattMonitor) {
+            LOG.debug("disconnect()");
+            BluetoothGatt gatt = mBluetoothGatt;
+            if (gatt != null) {
+                mBluetoothGatt = null;
+                LOG.info("Disconnecting BtLEQueue from GATT device");
+                gatt.disconnect();
+                gatt.close();
+                setDeviceConnectionState(State.NOT_CONNECTED);
+            }
+            mPauseTransaction = false;
+            BluetoothGattServer gattServer = mBluetoothGattServer;
+            if (gattServer != null) {
+                mBluetoothGattServer = null;
+                gattServer.clearServices();
+                gattServer.close();
+            }
+        }
+    }
+
+    private void handleDisconnected(int status) {
+        LOG.debug("handleDisconnected: {}", status);
+        internalGattCallback.reset();
+        mTransactions.clear();
+        mPauseTransaction = false;
+        mAbortTransaction = true;
+        mAbortServerTransaction = true;
+        if (mWaitForActionResultLatch != null) {
+            mWaitForActionResultLatch.countDown();
+        }
+        if (mWaitForServerActionResultLatch != null) {
+            mWaitForServerActionResultLatch.countDown();
+        }
+
+        setDeviceConnectionState(State.NOT_CONNECTED);
+
+        // either we've been disconnected because the device is out of range
+        // or because of an explicit @{link #disconnect())
+        // To support automatic reconnection, we keep the mBluetoothGatt instance
+        // alive (we do not close() it). Unfortunately we sometimes have problems
+        // reconnecting automatically, so we try to fix this by re-creating mBluetoothGatt.
+        // Not sure if this actually works without re-initializing the device...
+        if (mBluetoothGatt != null) {
+            if (!maybeReconnect()) {
+                disconnect(); // ensure that we start over cleanly next time
+            }
+        }
+    }
+
+    /**
+     * Depending on certain criteria, connects to the BluetoothGatt.
+     *
+     * @return true if a reconnection attempt was made, or false otherwise
+     */
+    private boolean maybeReconnect() {
+        if (mAutoReconnect && mBluetoothGatt != null) {
+            if(scanReconnect){
+                LOG.info("Waiting for BLE scan before attempting reconnection...");
+                setDeviceConnectionState(State.WAITING_FOR_SCAN);
+                return true;
+            }
+
+            LOG.info("Enabling automatic ble reconnect...");
+            boolean result = mBluetoothGatt.connect();
+            mPauseTransaction = false;
+            if (result) {
+                setDeviceConnectionState(State.WAITING_FOR_RECONNECT);
+            }
+            return result;
+        }
+        return false;
+    }
+
+    public void setPaused(boolean paused) {
+      mPauseTransaction = paused;
+    }
+
+    public void dispose() {
+        if (mDisposed) {
+            return;
+        }
+        mDisposed = true;
+//        try {
+        disconnect();
+        dispatchThread.interrupt();
+        dispatchThread = null;
+//            dispatchThread.join();
+//        } catch (InterruptedException ex) {
+//            LOG.error("Exception while disposing BtLEQueue", ex);
+//        }
+    }
+
+    /**
+     * Adds a transaction to the end of the queue.
+     *
+     * @param transaction
+     */
+    public void add(Transaction transaction) {
+        LOG.debug("about to add: {}", transaction);
+        if (!transaction.isEmpty()) {
+            mTransactions.add(transaction);
+        }
+    }
+
+    /**
+     * Aborts the currently running transaction
+     */
+    public void abortCurrentTransaction() {
+        mAbortTransaction = true;
+        if (mWaitForActionResultLatch != null) {
+            mWaitForActionResultLatch.countDown();
+        }
+    }
+
+    /**
+     * Adds a serverTransaction to the end of the queue
+     *
+     * @param transaction
+     */
+    public void add(ServerTransaction transaction) {
+        LOG.debug("about to add: {}", transaction);
+        if(!transaction.isEmpty()) {
+            mTransactions.add(transaction);
+        }
+    }
+
+    /**
+     * Adds a transaction to the beginning of the queue.
+     * Note that actions of the *currently executing* transaction
+     * will still be executed before the given transaction.
+     */
+    public void insert(Transaction transaction) {
+        LOG.debug("about to insert: {}", transaction);
+        if (!transaction.isEmpty()) {
+            List<AbstractTransaction> tail = new ArrayList<>(mTransactions.size() + 2);
+            //mTransactions.drainTo(tail);
+            tail.addAll(mTransactions);
+            mTransactions.clear();
+            mTransactions.add(transaction);
+            mTransactions.addAll(tail);
+        }
+    }
+
+    public void clear() {
+        mTransactions.clear();
+    }
+
+    /**
+     * Retrieves a list of supported GATT services on the connected device. This should be
+     * invoked only after {@code BluetoothGatt#discoverServices()} completes successfully.
+     *
+     * @return A {@code List} of supported services.
+     */
+    public List<BluetoothGattService> getSupportedGattServices() {
+        if (mBluetoothGatt == null) {
+            LOG.warn("BluetoothGatt is null => no services available.");
+            return Collections.emptyList();
+        }
+        return mBluetoothGatt.getServices();
+    }
+
+    /** @noinspection BooleanMethodIsAlwaysInverted*/
+    private boolean checkCorrectGattInstance(BluetoothGatt gatt, String where) {
+        if (gatt != mBluetoothGatt && mBluetoothGatt != null) {
+            LOG.warn("Ignoring event from wrong BluetoothGatt instance: {}; {}", where, gatt);
+            return false;
+        }
+        return true;
+    }
+
+    /** @noinspection BooleanMethodIsAlwaysInverted*/
+    private boolean checkCorrectBluetoothDevice(BluetoothDevice device) {
+        //BluetoothDevice clientDevice = mBluetoothAdapter.getRemoteDevice(mGbDevice.getAddress());
+
+        if(!device.getAddress().equals(mGbDevice.getAddress())) { // != clientDevice && clientDevice != null) {
+            LOG.warn("Ignoring request from wrong Bluetooth device: {}", device.getAddress());
+            return false;
+        }
+        return true;
+    }
+
+    // Implements callback methods for GATT events that the app cares about.  For example,
+    // connection change and services discovered.
+    private final class InternalGattCallback extends BluetoothGattCallback {
+        private
+        @Nullable
+        GattCallback mTransactionGattCallback;
+        private final GattCallback mExternalGattCallback;
+
+        public InternalGattCallback(GattCallback externalGattCallback) {
+            mExternalGattCallback = externalGattCallback;
+        }
+
+        public void setTransactionGattCallback(@Nullable GattCallback callback) {
+            mTransactionGattCallback = callback;
+        }
+
+        private GattCallback getCallbackToUse() {
+            if (mTransactionGattCallback != null) {
+                return mTransactionGattCallback;
+            }
+            return mExternalGattCallback;
+        }
+
+        @Override
+        public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
+            LOG.debug("connection state change, newState: {}{}", newState, getStatusString(status));
+
+            synchronized (mGattMonitor) {
+                if (mBluetoothGatt == null) {
+                    mBluetoothGatt = gatt;
+                }
+            }
+
+            if (!checkCorrectGattInstance(gatt, "connection state event")) {
+                return;
+            }
+
+            if (status != BluetoothGatt.GATT_SUCCESS) {
+                LOG.warn("connection state event with error status {}", status);
+            }
+
+            switch (newState) {
+                case BluetoothProfile.STATE_CONNECTED:
+                    LOG.info("Connected to GATT server.");
+                    setDeviceConnectionState(State.CONNECTED);
+                    // Attempts to discover services after successful connection.
+                    List<BluetoothGattService> cachedServices = gatt.getServices();
+                    if (cachedServices != null && !cachedServices.isEmpty()) {
+                        LOG.info("Using cached services, skipping discovery");
+                        onServicesDiscovered(gatt, BluetoothGatt.GATT_SUCCESS);
+                    } else {
+                        LOG.info("Attempting to start service discovery");
+                        // discover services in the main thread (appears to fix Samsung connection problems)
+                        new Handler(Looper.getMainLooper()).post(new Runnable() {
+                            @Override
+                            public void run() {
+                                if (mBluetoothGatt != null) {
+                                    mBluetoothGatt.discoverServices();
+                                }
+                            }
+                        });
+                    }
+                    break;
+                case BluetoothProfile.STATE_DISCONNECTED:
+                    LOG.info("Disconnected from GATT server.");
+                    handleDisconnected(status);
+                    break;
+                case BluetoothProfile.STATE_CONNECTING:
+                    LOG.info("Connecting to GATT server...");
+                    setDeviceConnectionState(State.CONNECTING);
+                    break;
+            }
+        }
+
+        @Override
+        public void onServicesDiscovered(BluetoothGatt gatt, int status) {
+            if (!checkCorrectGattInstance(gatt, "services discovered: " + getStatusString(status))) {
+                return;
+            }
+
+            if (status == BluetoothGatt.GATT_SUCCESS) {
+                if (getCallbackToUse() != null) {
+                    // only propagate the successful event
+                    getCallbackToUse().onServicesDiscovered(gatt);
+                }
+                if (mConnectionLatch != null) {
+                    mConnectionLatch.countDown();
+                }
+            } else {
+                LOG.warn("onServicesDiscovered received: {}", status);
+            }
+        }
+
+        @Override
+        public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
+            LOG.debug("characteristic write: {}{}", characteristic.getUuid(), getStatusString(status));
+            if (!checkCorrectGattInstance(gatt, "characteristic write")) {
+                return;
+            }
+            if (getCallbackToUse() != null) {
+                getCallbackToUse().onCharacteristicWrite(gatt, characteristic, status);
+            }
+            checkWaitingCharacteristic(characteristic, status);
+        }
+
+
+
+        @Override
+        public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
+            super.onMtuChanged(gatt, mtu, status);
+
+            LOG.debug("mtu changed to {}{}", mtu, getStatusString(status));
+
+            if(getCallbackToUse() != null){
+                getCallbackToUse().onMtuChanged(gatt, mtu, status);
+            }
+
+            if (mWaitForActionResultLatch != null) {
+                mWaitForActionResultLatch.countDown();
+            }
+        }
+
+        @Override
+        public void onCharacteristicRead(BluetoothGatt gatt,
+                                         BluetoothGattCharacteristic characteristic,
+                                         int status) {
+            LOG.debug(
+                    "characteristic read: {}{}{}",
+                    characteristic.getUuid(),
+                    getStatusString(status),
+                    status == BluetoothGatt.GATT_SUCCESS ? ": " + Logging.formatBytes(characteristic.getValue()) : ""
+            );
+            if (!checkCorrectGattInstance(gatt, "characteristic read")) {
+                return;
+            }
+            if (getCallbackToUse() != null) {
+                try {
+                    getCallbackToUse().onCharacteristicRead(gatt, characteristic, status);
+                } catch (Throwable ex) {
+                    LOG.error("onCharacteristicRead: {}", ex.getMessage(), ex);
+                }
+            }
+            checkWaitingCharacteristic(characteristic, status);
+        }
+
+        @Override
+        public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
+            LOG.debug("descriptor read: {}{}", descriptor.getUuid(), getStatusString(status));
+            if (!checkCorrectGattInstance(gatt, "descriptor read")) {
+                return;
+            }
+            if (getCallbackToUse() != null) {
+                try {
+                    getCallbackToUse().onDescriptorRead(gatt, descriptor, status);
+                } catch (Throwable ex) {
+                    LOG.error("onDescriptorRead failed", ex);
+                }
+            }
+            checkWaitingCharacteristic(descriptor.getCharacteristic(), status);
+        }
+
+        @Override
+        public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
+            LOG.debug("descriptor write: {}{}", descriptor.getUuid(), getStatusString(status));
+            if (!checkCorrectGattInstance(gatt, "descriptor write")) {
+                return;
+            }
+            if (getCallbackToUse() != null) {
+                try {
+                    getCallbackToUse().onDescriptorWrite(gatt, descriptor, status);
+                } catch (Throwable ex) {
+                    LOG.error("onDescriptorWrite failed", ex);
+                }
+            }
+            checkWaitingCharacteristic(descriptor.getCharacteristic(), status);
+        }
+
+        @Override
+        public void onCharacteristicChanged(BluetoothGatt gatt,
+                                            BluetoothGattCharacteristic characteristic) {
+            if (LOG.isDebugEnabled()) {
+                String content = Logging.formatBytes(characteristic.getValue());
+                LOG.debug("characteristic changed: {} value: {}", characteristic.getUuid(), content);
+            }
+            if (!checkCorrectGattInstance(gatt, "characteristic changed")) {
+                return;
+            }
+            if (getCallbackToUse() != null) {
+                try {
+                    getCallbackToUse().onCharacteristicChanged(gatt, characteristic);
+                } catch (Throwable ex) {
+                    LOG.error("onCharacteristicChanged failed", ex);
+                }
+            } else {
+                LOG.info("No gatt callback registered, ignoring characteristic change");
+            }
+        }
+
+        @Override
+        public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {
+            LOG.debug("remote rssi: {}{}", rssi, getStatusString(status));
+            if (!checkCorrectGattInstance(gatt, "remote rssi")) {
+                return;
+            }
+            if (getCallbackToUse() != null) {
+                try {
+                    getCallbackToUse().onReadRemoteRssi(gatt, rssi, status);
+                } catch (Throwable ex) {
+                    LOG.error("onReadRemoteRssi failed", ex);
+                }
+            }
+        }
+
+        private void checkWaitingCharacteristic(BluetoothGattCharacteristic characteristic, int status) {
+            if (status != BluetoothGatt.GATT_SUCCESS) {
+                if (characteristic != null) {
+                    LOG.debug("failed btle action, aborting transaction: {}{}", characteristic.getUuid(), getStatusString(status));
+                }
+                mAbortTransaction = true;
+            }
+            if (characteristic != null && BtLEQueue.this.mWaitCharacteristic != null && characteristic.getUuid().equals(BtLEQueue.this.mWaitCharacteristic.getUuid())) {
+                if (mWaitForActionResultLatch != null) {
+                    mWaitForActionResultLatch.countDown();
+                }
+            } else {
+                if (BtLEQueue.this.mWaitCharacteristic != null) {
+                    LOG.error(
+                            "checkWaitingCharacteristic: mismatched characteristic received: {}",
+                            (characteristic != null && characteristic.getUuid() != null) ? characteristic.getUuid().toString() : "(null)"
+                    );
+                }
+            }
+        }
+
+        private String getStatusString(int status) {
+            return status == BluetoothGatt.GATT_SUCCESS ? " (success)" : " (failed: " + status + ")";
+        }
+
+        public void reset() {
+            if (LOG.isDebugEnabled()) {
+                LOG.debug("internal gatt callback set to null");
+            }
+            mTransactionGattCallback = null;
+        }
+    }
+
+    // Implements callback methods for GATT server events that the app cares about.  For example,
+    // connection change and read/write requests.
+    private final class InternalGattServerCallback extends BluetoothGattServerCallback {
+        private
+        @Nullable
+        GattServerCallback mTransactionGattCallback;
+        private final GattServerCallback mExternalGattServerCallback;
+
+        public InternalGattServerCallback(GattServerCallback externalGattServerCallback) {
+            mExternalGattServerCallback = externalGattServerCallback;
+        }
+
+        public void setTransactionGattCallback(@Nullable GattServerCallback callback) {
+            mTransactionGattCallback = callback;
+        }
+
+        private GattServerCallback getCallbackToUse() {
+            if (mTransactionGattCallback != null) {
+                return mTransactionGattCallback;
+            }
+            return mExternalGattServerCallback;
+        }
+
+        @Override
+        public void onConnectionStateChange(BluetoothDevice device, int status, int newState) {
+            LOG.debug("gatt server connection state change, newState: {}{}", newState, getStatusString(status));
+
+            if(!checkCorrectBluetoothDevice(device)) {
+                return;
+            }
+
+            if (status != BluetoothGatt.GATT_SUCCESS) {
+                LOG.warn("gatt server connection state event with error status {}", status);
+            }
+        }
+
+        private String getStatusString(int status) {
+            return status == BluetoothGatt.GATT_SUCCESS ? " (success)" : " (failed: " + status + ")";
+        }
+
+        @Override
+        public void onCharacteristicReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattCharacteristic characteristic) {
+            if(!checkCorrectBluetoothDevice(device)) {
+                return;
+            }
+            LOG.debug("characteristic read request: {} characteristic: {}", device.getAddress(), characteristic.getUuid());
+            if (getCallbackToUse() != null) {
+                getCallbackToUse().onCharacteristicReadRequest(device, requestId, offset, characteristic);
+            }
+        }
+
+        @Override
+        public void onCharacteristicWriteRequest(BluetoothDevice device, int requestId, BluetoothGattCharacteristic characteristic, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
+            if(!checkCorrectBluetoothDevice(device)) {
+                return;
+            }
+            LOG.debug("characteristic write request: {} characteristic: {}", device.getAddress(), characteristic.getUuid());
+            boolean success = false;
+            if (getCallbackToUse() != null) {
+                success = getCallbackToUse().onCharacteristicWriteRequest(device, requestId, characteristic, preparedWrite, responseNeeded, offset, value);
+            }
+            if (responseNeeded && mSendWriteRequestResponse) {
+                mBluetoothGattServer.sendResponse(device, requestId, success ? BluetoothGatt.GATT_SUCCESS : BluetoothGatt.GATT_FAILURE, 0, new byte[0]);
+            }
+        }
+
+        @Override
+        public void onDescriptorReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattDescriptor descriptor) {
+            if(!checkCorrectBluetoothDevice(device)) {
+                return;
+            }
+            LOG.debug("onDescriptorReadRequest: {}", device.getAddress());
+            if(getCallbackToUse() != null) {
+                getCallbackToUse().onDescriptorReadRequest(device, requestId, offset, descriptor);
+            }
+        }
+
+        @Override
+        public void onDescriptorWriteRequest(BluetoothDevice device, int requestId, BluetoothGattDescriptor descriptor, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
+            if(!checkCorrectBluetoothDevice(device)) {
+                return;
+            }
+            LOG.debug("onDescriptorWriteRequest: {}", device.getAddress());
+            boolean success = false;
+            if(getCallbackToUse() != null) {
+                success = getCallbackToUse().onDescriptorWriteRequest(device, requestId, descriptor, preparedWrite, responseNeeded, offset, value);
+            }
+            if (responseNeeded && mSendWriteRequestResponse) {
+                mBluetoothGattServer.sendResponse(device, requestId, success ? BluetoothGatt.GATT_SUCCESS : BluetoothGatt.GATT_FAILURE, 0, new byte[0]);
+            }
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BtLEServerAction.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BtLEServerAction.java
new file mode 100644
index 0000000..29ca88d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BtLEServerAction.java
@@ -0,0 +1,75 @@
+/*  Copyright (C) 2019-2024 Andreas B枚hler
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.btle;
+
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.bluetooth.BluetoothGattServer;
+import android.bluetooth.BluetoothGattService;
+
+import java.util.Date;
+
+import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
+
+/**
+ * The Bluedroid implementation only allows performing one GATT request at a time.
+ * As they are asynchronous anyway, we encapsulate every GATT request (read and write)
+ * inside a runnable action.
+ * <p/>
+ * These actions are then executed one after another, ensuring that every action's result
+ * has been posted before invoking the next action.
+ */
+public abstract class BtLEServerAction {
+    private final BluetoothDevice device;
+    private final long creationTimestamp;
+
+    public BtLEServerAction(BluetoothDevice device) {
+        this.device = device;
+        creationTimestamp = System.currentTimeMillis();
+    }
+
+
+    public BluetoothDevice getDevice() {
+        return this.device;
+    }
+
+    /**
+     * Returns true if this action expects an (async) result which must
+     * be waited for, before continuing with other actions.
+     * <p/>
+     * This is needed because the current Bluedroid stack can only deal
+     * with one single bluetooth operation at a time.
+     */
+    public abstract boolean expectsResult();
+
+    /**
+     * Executes this action, e.g. reads or write a GATT characteristic.
+     *
+     * @return true if the action was successful, false otherwise
+     */
+    public abstract boolean run(BluetoothGattServer server);
+
+
+    protected String getCreationTime() {
+        return DateTimeUtils.formatDateTime(new Date(creationTimestamp));
+    }
+
+    public String toString() {
+        return getCreationTime() + ":" + getClass().getSimpleName() + " on device: " + getDevice().getAddress();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/GattCallback.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/GattCallback.java
new file mode 100644
index 0000000..16ec0f4
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/GattCallback.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package nodomain.freeyourgadget.gadgetbridge.service.btle;
+
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCallback;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.bluetooth.BluetoothGattDescriptor;
+
+/**
+ * Callback interface handling gatt events.
+ * Pretty much the same as {@link BluetoothGattCallback}, except it's an interface
+ * instead of an abstract class. Some handlers commented out, because not used (yet).
+ *
+ * Note: the boolean return values indicate whether this callback "consumed" this event
+ * or not. True means, the event was consumed by this instance and no further instances
+ * shall be notified. Fallse means, this instance could not handle the event.
+ */
+public interface GattCallback {
+
+    /**
+     * @param gatt
+     * @param status
+     * @param newState
+     * @see BluetoothGattCallback#onConnectionStateChange(BluetoothGatt, int, int)
+     */
+    void onConnectionStateChange(BluetoothGatt gatt, int status, int newState);
+
+    /**
+     * @param gatt
+     * @see BluetoothGattCallback#onServicesDiscovered(BluetoothGatt, int)
+     */
+    void onServicesDiscovered(BluetoothGatt gatt);
+
+    /**
+     * @param gatt
+     * @param characteristic
+     * @param status
+     * @see BluetoothGattCallback#onCharacteristicRead(BluetoothGatt, BluetoothGattCharacteristic, int)
+     */
+    boolean onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status);
+
+    /**
+     * @param gatt
+     * @param characteristic
+     * @param status
+     * @see BluetoothGattCallback#onCharacteristicWrite(BluetoothGatt, BluetoothGattCharacteristic, int)
+     */
+    boolean onCharacteristicWrite(BluetoothGatt gatt,
+                               BluetoothGattCharacteristic characteristic, int status);
+
+    /**
+     * @param gatt
+     * @param characteristic
+     * @see BluetoothGattCallback#onCharacteristicChanged(BluetoothGatt, BluetoothGattCharacteristic)
+     */
+    boolean onCharacteristicChanged(BluetoothGatt gatt,
+                                 BluetoothGattCharacteristic characteristic);
+
+    /**
+     * @param gatt
+     * @param descriptor
+     * @param status
+     * @see BluetoothGattCallback#onDescriptorRead(BluetoothGatt, BluetoothGattDescriptor, int)
+     */
+    boolean onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor,
+                          int status);
+
+    /**
+     * @param gatt
+     * @param descriptor
+     * @param status
+     * @see BluetoothGattCallback#onDescriptorWrite(BluetoothGatt, BluetoothGattDescriptor, int)
+     */
+    boolean onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor,
+                           int status);
+//
+//    /**
+//     * @see BluetoothGattCallback#onReliableWriteCompleted(BluetoothGatt, int)
+//     * @param gatt
+//     * @param status
+//     */
+//    public void onReliableWriteCompleted(BluetoothGatt gatt, int status);
+
+    /**
+     * @param gatt
+     * @param rssi
+     * @param status
+     * @see BluetoothGattCallback#onReadRemoteRssi(BluetoothGatt, int, int)
+     */
+    void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status);
+
+    void onMtuChanged(BluetoothGatt gatt, int mtu, int status);
+
+//    /**
+//     * @see BluetoothGattCallback#onMtuChanged(BluetoothGatt, int, int)
+//     * @param gatt
+//     * @param mtu
+//     * @param status
+//     */
+//    public void onMtuChanged(BluetoothGatt gatt, int mtu, int status);
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/GattCharacteristic.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/GattCharacteristic.java
new file mode 100644
index 0000000..acf6d86
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/GattCharacteristic.java
@@ -0,0 +1,414 @@
+/*  Copyright (C) 2015-2024 Carsten Pfeiffer, Daniele Gobbetti, Toby Murray,
+    uli
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.btle;
+
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.util.Log;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+public class GattCharacteristic {
+
+    //part of the generic BLE specs see https://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicsHome.aspx
+    public static final byte NO_ALERT = 0x0;
+    public static final byte MILD_ALERT = 0x1;
+    public static final byte HIGH_ALERT = 0x2;
+
+    // this list of GATT characteristics is complete as of 2020-01-18, retrieved from https://btprodspecificationrefs.blob.core.windows.net/assigned-values/16-bit%20UUID%20Numbers%20Document.pdf
+    public static final UUID UUID_CHARACTERISTIC_DEVICE_NAME = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A00")));
+    public static final UUID UUID_CHARACTERISTIC_APPEARANCE = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A01")));
+    public static final UUID UUID_CHARACTERISTIC_PERIPHERAL_PRIVACY_FLAG = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A02")));
+    public static final UUID UUID_CHARACTERISTIC_RECONNECTION_ADDRESS = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A03")));
+    public static final UUID UUID_CHARACTERISTIC_PERIPHERAL_PREFERRED_CONNECTION_PARAMETERS = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A04")));
+    public static final UUID UUID_CHARACTERISTIC_SERVICE_CHANGED = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A05")));
+    public static final UUID UUID_CHARACTERISTIC_ALERT_LEVEL = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A06")));
+    public static final UUID UUID_CHARACTERISTIC_TX_POWER_LEVEL = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A07")));
+    public static final UUID UUID_CHARACTERISTIC_DATE_TIME = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A08")));
+    public static final UUID UUID_CHARACTERISTIC_DAY_OF_WEEK = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A09")));
+    public static final UUID UUID_CHARACTERISTIC_DAY_DATE_TIME = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A0A")));
+    public static final UUID UUID_CHARACTERISTIC_EXACT_TIME_256 = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A0C")));
+    public static final UUID UUID_CHARACTERISTIC_DST_OFFSET = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A0D")));
+    public static final UUID UUID_CHARACTERISTIC_TIME_ZONE = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A0E")));
+    public static final UUID UUID_CHARACTERISTIC_LOCAL_TIME = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A0F")));
+    public static final UUID UUID_CHARACTERISTIC_TIME_WITH_DST = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A11")));
+    public static final UUID UUID_CHARACTERISTIC_TIME_ACCURACY = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A12")));
+    public static final UUID UUID_CHARACTERISTIC_TIME_SOURCE = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A13")));
+    public static final UUID UUID_CHARACTERISTIC_REFERENCE_TIME_INFORMATION = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A14")));
+    public static final UUID UUID_CHARACTERISTIC_TIME_UPDATE_CONTROL_POINT = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A16")));
+    public static final UUID UUID_CHARACTERISTIC_TIME_UPDATE_STATE = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A17")));
+    public static final UUID UUID_CHARACTERISTIC_GLUCOSE_MEASUREMENT = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A18")));
+    public static final UUID UUID_CHARACTERISTIC_BATTERY_LEVEL = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A19")));
+    public static final UUID UUID_CHARACTERISTIC_TEMPERATURE_MEASUREMENT = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A1C")));
+    public static final UUID UUID_CHARACTERISTIC_TEMPERATURE_TYPE = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A1D")));
+    public static final UUID UUID_CHARACTERISTIC_INTERMEDIATE_TEMPERATURE = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A1E")));
+    public static final UUID UUID_CHARACTERISTIC_MEASUREMENT_INTERVAL = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A21")));
+    public static final UUID UUID_CHARACTERISTIC_BOOT_KEYBOARD_INPUT_REPORT = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A22")));
+    public static final UUID UUID_CHARACTERISTIC_SYSTEM_ID = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A23")));
+    public static final UUID UUID_CHARACTERISTIC_MODEL_NUMBER_STRING = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A24")));
+    public static final UUID UUID_CHARACTERISTIC_SERIAL_NUMBER_STRING = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A25")));
+    public static final UUID UUID_CHARACTERISTIC_FIRMWARE_REVISION_STRING = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A26")));
+    public static final UUID UUID_CHARACTERISTIC_HARDWARE_REVISION_STRING = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A27")));
+    public static final UUID UUID_CHARACTERISTIC_SOFTWARE_REVISION_STRING = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A28")));
+    public static final UUID UUID_CHARACTERISTIC_MANUFACTURER_NAME_STRING = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A29")));
+    public static final UUID UUID_CHARACTERISTIC_IEEE_11073_20601_REGULATORY_CERTIFICATION_DATA_LIST = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A2A")));
+    public static final UUID UUID_CHARACTERISTIC_CURRENT_TIME = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A2B")));
+    public static final UUID UUID_CHARACTERISTIC_SCAN_REFRESH = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A31")));
+    public static final UUID UUID_CHARACTERISTIC_BOOT_KEYBOARD_OUTPUT_REPORT = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A32")));
+    public static final UUID UUID_CHARACTERISTIC_BOOT_MOUSE_INPUT_REPORT = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A33")));
+    public static final UUID UUID_CHARACTERISTIC_GLUCOSE_MEASUREMENT_CONTEXT = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A34")));
+    public static final UUID UUID_CHARACTERISTIC_BLOOD_PRESSURE_MEASUREMENT = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A35")));
+    public static final UUID UUID_CHARACTERISTIC_INTERMEDIATE_CUFF_PRESSURE = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A36")));
+    public static final UUID UUID_CHARACTERISTIC_HEART_RATE_MEASUREMENT = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A37")));
+    public static final UUID UUID_CHARACTERISTIC_BODY_SENSOR_LOCATION = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A38")));
+    public static final UUID UUID_CHARACTERISTIC_HEART_RATE_CONTROL_POINT = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A39")));
+    public static final UUID UUID_CHARACTERISTIC_ALERT_STATUS = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A3F")));
+    public static final UUID UUID_CHARACTERISTIC_RINGER_CONTROL_POINT = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A40")));
+    public static final UUID UUID_CHARACTERISTIC_RINGER_SETTING = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A41")));
+    public static final UUID UUID_CHARACTERISTIC_ALERT_CATEGORY_ID_BIT_MASK = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A42")));
+    public static final UUID UUID_CHARACTERISTIC_ALERT_CATEGORY_ID = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A43")));
+    public static final UUID UUID_CHARACTERISTIC_ALERT_NOTIFICATION_CONTROL_POINT = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A44")));
+    public static final UUID UUID_CHARACTERISTIC_UNREAD_ALERT_STATUS = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A45")));
+    public static final UUID UUID_CHARACTERISTIC_NEW_ALERT = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A46")));
+    public static final UUID UUID_CHARACTERISTIC_SUPPORTED_NEW_ALERT_CATEGORY = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A47")));
+    public static final UUID UUID_CHARACTERISTIC_SUPPORTED_UNREAD_ALERT_CATEGORY = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A48")));
+    public static final UUID UUID_CHARACTERISTIC_BLOOD_PRESSURE_FEATURE = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A49")));
+    public static final UUID UUID_CHARACTERISTIC_HID_INFORMATION = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A4A")));
+    public static final UUID UUID_CHARACTERISTIC_REPORT_MAP = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A4B")));
+    public static final UUID UUID_CHARACTERISTIC_HID_CONTROL_POINT = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A4C")));
+    public static final UUID UUID_CHARACTERISTIC_REPORT = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A4D")));
+    public static final UUID UUID_CHARACTERISTIC_PROTOCOL_MODE = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A4E")));
+    public static final UUID UUID_CHARACTERISTIC_SCAN_INTERVAL_WINDOW = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A4F")));
+    public static final UUID UUID_CHARACTERISTIC_PNP_ID = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A50")));
+    public static final UUID UUID_CHARACTERISTIC_GLUCOSE_FEATURE = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A51")));
+    public static final UUID UUID_CHARACTERISTIC_RECORD_ACCESS_CONTROL_POINT = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A52")));
+    public static final UUID UUID_CHARACTERISTIC_RSC_MEASUREMENT = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A53")));
+    public static final UUID UUID_CHARACTERISTIC_RSC_FEATURE = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A54")));
+    public static final UUID UUID_CHARACTERISTIC_SC_CONTROL_POINT = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A55")));
+    public static final UUID UUID_CHARACTERISTIC_AGGREGATE = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A5A")));
+    public static final UUID UUID_CHARACTERISTIC_CSC_MEASUREMENT = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A5B")));
+    public static final UUID UUID_CHARACTERISTIC_CSC_FEATURE = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A5C")));
+    public static final UUID UUID_CHARACTERISTIC_SENSOR_LOCATION = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A5D")));
+    public static final UUID UUID_CHARACTERISTIC_PLX_SPOT_CHECK_MEASUREMENT = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A5E")));
+    public static final UUID UUID_CHARACTERISTIC_PLX_CONTINUOUS_MEASUREMENT = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A5F")));
+    public static final UUID UUID_CHARACTERISTIC_PLX_FEATURES = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A60")));
+    public static final UUID UUID_CHARACTERISTIC_CYCLING_POWER_MEASUREMENT = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A63")));
+    public static final UUID UUID_CHARACTERISTIC_CYCLING_POWER_VECTOR = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A64")));
+    public static final UUID UUID_CHARACTERISTIC_CYCLING_POWER_FEATURE = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A65")));
+    public static final UUID UUID_CHARACTERISTIC_CYCLING_POWER_CONTROL_POINT = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A66")));
+    public static final UUID UUID_CHARACTERISTIC_LOCATION_AND_SPEED = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A67")));
+    public static final UUID UUID_CHARACTERISTIC_NAVIGATION = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A68")));
+    public static final UUID UUID_CHARACTERISTIC_POSITION_QUALITY = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A69")));
+    public static final UUID UUID_CHARACTERISTIC_LN_FEATURE = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A6A")));
+    public static final UUID UUID_CHARACTERISTIC_LN_CONTROL_POINT = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A6B")));
+    public static final UUID UUID_CHARACTERISTIC_ELEVATION = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A6C")));
+    public static final UUID UUID_CHARACTERISTIC_PRESSURE = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A6D")));
+    public static final UUID UUID_CHARACTERISTIC_TEMPERATURE = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A6E")));
+    public static final UUID UUID_CHARACTERISTIC_HUMIDITY = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A6F")));
+    public static final UUID UUID_CHARACTERISTIC_TRUE_WIND_SPEED = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A70")));
+    public static final UUID UUID_CHARACTERISTIC_TRUE_WIND_DIRECTION = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A71")));
+    public static final UUID UUID_CHARACTERISTIC_APPARENT_WIND_SPEED = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A72")));
+    public static final UUID UUID_CHARACTERISTIC_APPARENT_WIND_DIRECTION = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A73")));
+    public static final UUID UUID_CHARACTERISTIC_GUST_FACTOR = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A74")));
+    public static final UUID UUID_CHARACTERISTIC_POLLEN_CONCENTRATION = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A75")));
+    public static final UUID UUID_CHARACTERISTIC_UV_INDEX = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A76")));
+    public static final UUID UUID_CHARACTERISTIC_IRRADIANCE = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A77")));
+    public static final UUID UUID_CHARACTERISTIC_RAINFALL = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A78")));
+    public static final UUID UUID_CHARACTERISTIC_WIND_CHILL = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A79")));
+    public static final UUID UUID_CHARACTERISTIC_HEAT_INDEX = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A7A")));
+    public static final UUID UUID_CHARACTERISTIC_DEW_POINT = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A7B")));
+    public static final UUID UUID_CHARACTERISTIC_DESCRIPTOR_VALUE_CHANGED = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A7D")));
+    public static final UUID UUID_CHARACTERISTIC_AEROBIC_HEART_RATE_LOWER_LIMIT = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A7E")));
+    public static final UUID UUID_CHARACTERISTIC_AEROBIC_THRESHOLD = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A7F")));
+    public static final UUID UUID_CHARACTERISTIC_AGE = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A80")));
+    public static final UUID UUID_CHARACTERISTIC_ANAEROBIC_HEART_RATE_LOWER_LIMIT = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A81")));
+    public static final UUID UUID_CHARACTERISTIC_ANAEROBIC_HEART_RATE_UPPER_LIMIT = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A82")));
+    public static final UUID UUID_CHARACTERISTIC_ANAEROBIC_THRESHOLD = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A83")));
+    public static final UUID UUID_CHARACTERISTIC_AEROBIC_HEART_RATE_UPPER_LIMIT = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A84")));
+    public static final UUID UUID_CHARACTERISTIC_DATE_OF_BIRTH = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A85")));
+    public static final UUID UUID_CHARACTERISTIC_DATE_OF_THRESHOLD_ASSESSMENT = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A86")));
+    public static final UUID UUID_CHARACTERISTIC_EMAIL_ADDRESS = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A87")));
+    public static final UUID UUID_CHARACTERISTIC_FAT_BURN_HEART_RATE_LOWER_LIMIT = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A88")));
+    public static final UUID UUID_CHARACTERISTIC_FAT_BURN_HEART_RATE_UPPER_LIMIT = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A89")));
+    public static final UUID UUID_CHARACTERISTIC_FIRST_NAME = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A8A")));
+    public static final UUID UUID_CHARACTERISTIC_FIVE_ZONE_HEART_RATE_LIMITS = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A8B")));
+    public static final UUID UUID_CHARACTERISTIC_GENDER = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A8C")));
+    public static final UUID UUID_CHARACTERISTIC_HEART_RATE_MAX = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A8D")));
+    public static final UUID UUID_CHARACTERISTIC_HEIGHT = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A8E")));
+    public static final UUID UUID_CHARACTERISTIC_HIP_CIRCUMFERENCE = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A8F")));
+    public static final UUID UUID_CHARACTERISTIC_LAST_NAME = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A90")));
+    public static final UUID UUID_CHARACTERISTIC_MAXIMUM_RECOMMENDED_HEART_RATE = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A91")));
+    public static final UUID UUID_CHARACTERISTIC_RESTING_HEART_RATE = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A92")));
+    public static final UUID UUID_CHARACTERISTIC_SPORT_TYPE_FOR_AEROBIC_AND_ANAEROBIC_THRESHOLDS = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A93")));
+    public static final UUID UUID_CHARACTERISTIC_THREE_ZONE_HEART_RATE_LIMITS = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A94")));
+    public static final UUID UUID_CHARACTERISTIC_TWO_ZONE_HEART_RATE_LIMITS = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A95")));
+    public static final UUID UUID_CHARACTERISTIC_VO2_MAX = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A96")));
+    public static final UUID UUID_CHARACTERISTIC_WAIST_CIRCUMFERENCE = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A97")));
+    public static final UUID UUID_CHARACTERISTIC_WEIGHT = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A98")));
+    public static final UUID UUID_CHARACTERISTIC_DATABASE_CHANGE_INCREMENT = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A99")));
+    public static final UUID UUID_CHARACTERISTIC_USER_INDEX = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A9A")));
+    public static final UUID UUID_CHARACTERISTIC_BODY_COMPOSITION_FEATURE = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A9B")));
+    public static final UUID UUID_CHARACTERISTIC_BODY_COMPOSITION_MEASUREMENT = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A9C")));
+    public static final UUID UUID_CHARACTERISTIC_WEIGHT_MEASUREMENT = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A9D")));
+    public static final UUID UUID_CHARACTERISTIC_WEIGHT_SCALE_FEATURE = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A9E")));
+    public static final UUID UUID_CHARACTERISTIC_USER_CONTROL_POINT = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A9F")));
+    public static final UUID UUID_CHARACTERISTIC_MAGNETIC_FLUX_DENSITY___2D = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2AA0")));
+    public static final UUID UUID_CHARACTERISTIC_MAGNETIC_FLUX_DENSITY___3D = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2AA1")));
+    public static final UUID UUID_CHARACTERISTIC_LANGUAGE = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2AA2")));
+    public static final UUID UUID_CHARACTERISTIC_BAROMETRIC_PRESSURE_TREND = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2AA3")));
+    public static final UUID UUID_CHARACTERISTIC_BOND_MANAGEMENT_CONTROL_POINT = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2AA4")));
+    public static final UUID UUID_CHARACTERISTIC_BOND_MANAGEMENT_FEATURE = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2AA5")));
+    public static final UUID UUID_CHARACTERISTIC_CENTRAL_ADDRESS_RESOLUTION = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2AA6")));
+    public static final UUID UUID_CHARACTERISTIC_CGM_MEASUREMENT = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2AA7")));
+    public static final UUID UUID_CHARACTERISTIC_CGM_FEATURE = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2AA8")));
+    public static final UUID UUID_CHARACTERISTIC_CGM_STATUS = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2AA9")));
+    public static final UUID UUID_CHARACTERISTIC_CGM_SESSION_START_TIME = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2AAA")));
+    public static final UUID UUID_CHARACTERISTIC_CGM_SESSION_RUN_TIME = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2AAB")));
+    public static final UUID UUID_CHARACTERISTIC_CGM_SPECIFIC_OPS_CONTROL_POINT = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2AAC")));
+    public static final UUID UUID_CHARACTERISTIC_INDOOR_POSITIONING_CONFIGURATION = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2AAD")));
+    public static final UUID UUID_CHARACTERISTIC_LATITUDE = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2AAE")));
+    public static final UUID UUID_CHARACTERISTIC_LONGITUDE = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2AAF")));
+    public static final UUID UUID_CHARACTERISTIC_LOCAL_NORTH_COORDINATE = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2AB0")));
+    public static final UUID UUID_CHARACTERISTIC_LOCAL_EAST_COORDINATE = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2AB1")));
+    public static final UUID UUID_CHARACTERISTIC_FLOOR_NUMBER = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2AB2")));
+    public static final UUID UUID_CHARACTERISTIC_ALTITUDE = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2AB3")));
+    public static final UUID UUID_CHARACTERISTIC_UNCERTAINTY = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2AB4")));
+    public static final UUID UUID_CHARACTERISTIC_LOCATION_NAME = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2AB5")));
+    public static final UUID UUID_CHARACTERISTIC_URI = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2AB6")));
+    public static final UUID UUID_CHARACTERISTIC_HTTP_HEADERS = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2AB7")));
+    public static final UUID UUID_CHARACTERISTIC_HTTP_STATUS_CODE = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2AB8")));
+    public static final UUID UUID_CHARACTERISTIC_HTTP_ENTITY_BODY = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2AB9")));
+    public static final UUID UUID_CHARACTERISTIC_HTTP_CONTROL_POINT = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2ABA")));
+    public static final UUID UUID_CHARACTERISTIC_HTTPS_SECURITY = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2ABB")));
+    public static final UUID UUID_CHARACTERISTIC_TDS_CONTROL_POINT = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2ABC")));
+    public static final UUID UUID_CHARACTERISTIC_OTS_FEATURE = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2ABD")));
+    public static final UUID UUID_CHARACTERISTIC_OBJECT_NAME = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2ABE")));
+    public static final UUID UUID_CHARACTERISTIC_OBJECT_TYPE = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2ABF")));
+    public static final UUID UUID_CHARACTERISTIC_OBJECT_SIZE = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2AC0")));
+    public static final UUID UUID_CHARACTERISTIC_OBJECT_FIRST_CREATED = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2AC1")));
+    public static final UUID UUID_CHARACTERISTIC_OBJECT_LAST_MODIFIED = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2AC2")));
+    public static final UUID UUID_CHARACTERISTIC_OBJECT_ID = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2AC3")));
+    public static final UUID UUID_CHARACTERISTIC_OBJECT_PROPERTIES = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2AC4")));
+    public static final UUID UUID_CHARACTERISTIC_OBJECT_ACTIONCONTROL_POINT = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2AC5")));
+    public static final UUID UUID_CHARACTERISTIC_OBJECT_LIST_CONTROL_POINT = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2AC6")));
+    public static final UUID UUID_CHARACTERISTIC_OBJECT_LIST_FILTER = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2AC7")));
+    public static final UUID UUID_CHARACTERISTIC_OBJECT_CHANGED = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2AC8")));
+    public static final UUID UUID_CHARACTERISTIC_RESOLVABLE_PRIVATE_ADDRESS_ONLY = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2AC9")));
+    public static final UUID UUID_CHARACTERISTIC_UNSPECIFIED = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2ACA")));
+    public static final UUID UUID_CHARACTERISTIC_DIRECTORY_LISTING = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2ACB")));
+    public static final UUID UUID_CHARACTERISTIC_FITNESS_MACHINE_FEATURE = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2ACC")));
+    public static final UUID UUID_CHARACTERISTIC_TREADMILL_DATA = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2ACD")));
+    public static final UUID UUID_CHARACTERISTIC_CROSS_TRAINER_DATA = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2ACE")));
+    public static final UUID UUID_CHARACTERISTIC_STEP_CLIMBER_DATA = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2ACF")));
+    public static final UUID UUID_CHARACTERISTIC_STAIR_CLIMBER_DATA = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2AD0")));
+    public static final UUID UUID_CHARACTERISTIC_ROWER_DATA = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2AD1")));
+    public static final UUID UUID_CHARACTERISTIC_INDOOR_BIKE_DATA = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2AD2")));
+    public static final UUID UUID_CHARACTERISTIC_TRAINING_STATUS = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2AD3")));
+    public static final UUID UUID_CHARACTERISTIC_SUPPORTED_SPEED_RANGE = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2AD4")));
+    public static final UUID UUID_CHARACTERISTIC_SUPPORTED_INCLINATION_RANGE = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2AD5")));
+    public static final UUID UUID_CHARACTERISTIC_SUPPORTED_RESISTANCE_LEVEL_RANGE = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2AD6")));
+    public static final UUID UUID_CHARACTERISTIC_SUPPORTED_HEART_RATE_RANGE = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2AD7")));
+    public static final UUID UUID_CHARACTERISTIC_SUPPORTED_POWER_RANGE = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2AD8")));
+    public static final UUID UUID_CHARACTERISTIC_FITNESS_MACHINE_CONTROL_POINT = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2AD9")));
+    public static final UUID UUID_CHARACTERISTIC_FITNESS_MACHINE_STATUS = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2ADA")));
+    public static final UUID UUID_CHARACTERISTIC_MESH_PROVISIONING_DATA_IN = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2ADB")));
+    public static final UUID UUID_CHARACTERISTIC_MESH_PROVISIONING_DATA_OUT = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2ADC")));
+    public static final UUID UUID_CHARACTERISTIC_MESH_PROXY_DATA_IN = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2ADD")));
+    public static final UUID UUID_CHARACTERISTIC_MESH_PROXY_DATA_OUT = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2ADE")));
+    public static final UUID UUID_CHARACTERISTIC_AVERAGE_CURRENT = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2AE0")));
+    public static final UUID UUID_CHARACTERISTIC_AVERAGE_VOLTAGE = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2AE1")));
+    public static final UUID UUID_CHARACTERISTIC_BOOLEAN = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2AE2")));
+    public static final UUID UUID_CHARACTERISTIC_CHROMATIC_DISTANCE_FROM_PLANCKIAN = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2AE3")));
+    public static final UUID UUID_CHARACTERISTIC_CHROMATICITY_COORDINATES = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2AE4")));
+    public static final UUID UUID_CHARACTERISTIC_CHROMATICITY_IN_CCT_AND_DUV_VALUES = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2AE5")));
+    public static final UUID UUID_CHARACTERISTIC_CHROMATICITY_TOLERANCE = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2AE6")));
+    public static final UUID UUID_CHARACTERISTIC_CIE_13_3_1995_COLOR_RENDERING_INDEX = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2AE7")));
+    public static final UUID UUID_CHARACTERISTIC_COEFFICIENT = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2AE8")));
+    public static final UUID UUID_CHARACTERISTIC_CORRELATED_COLOR_TEMPERATURE = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2AE9")));
+    public static final UUID UUID_CHARACTERISTIC_COUNT_16 = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2AEA")));
+    public static final UUID UUID_CHARACTERISTIC_COUNT_24 = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2AEB")));
+    public static final UUID UUID_CHARACTERISTIC_COUNTRY_CODE = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2AEC")));
+    public static final UUID UUID_CHARACTERISTIC_DATE_UTC = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2AED")));
+    public static final UUID UUID_CHARACTERISTIC_ELECTRIC_CURRENT = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2AEE")));
+    public static final UUID UUID_CHARACTERISTIC_ELECTRIC_CURRENT_RANGE = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2AEF")));
+    public static final UUID UUID_CHARACTERISTIC_ELECTRIC_CURRENT_SPECIFICATION = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2AF0")));
+    public static final UUID UUID_CHARACTERISTIC_ELECTRIC_CURRENT_STATISTICS = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2AF1")));
+    public static final UUID UUID_CHARACTERISTIC_ENERGY = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2AF2")));
+    public static final UUID UUID_CHARACTERISTIC_ENERGY_IN_A_PERIOD_OF_DAY = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2AF3")));
+    public static final UUID UUID_CHARACTERISTIC_EVENT_STATISTICS = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2AF4")));
+    public static final UUID UUID_CHARACTERISTIC_FIXED_STRING_16 = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2AF5")));
+    public static final UUID UUID_CHARACTERISTIC_FIXED_STRING_24 = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2AF6")));
+    public static final UUID UUID_CHARACTERISTIC_FIXED_STRING_36 = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2AF7")));
+    public static final UUID UUID_CHARACTERISTIC_FIXED_STRING_8 = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2AF8")));
+    public static final UUID UUID_CHARACTERISTIC_GENERIC_LEVEL = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2AF9")));
+    public static final UUID UUID_CHARACTERISTIC_GLOBAL_TRADE_ITEM_NUMBER = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2AFA")));
+    public static final UUID UUID_CHARACTERISTIC_ILLUMINANCE = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2AFB")));
+    public static final UUID UUID_CHARACTERISTIC_LUMINOUS_EFFICACY = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2AFC")));
+    public static final UUID UUID_CHARACTERISTIC_LUMINOUS_ENERGY = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2AFD")));
+    public static final UUID UUID_CHARACTERISTIC_LUMINOUS_EXPOSURE = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2AFE")));
+    public static final UUID UUID_CHARACTERISTIC_LUMINOUS_FLUX = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2AFF")));
+    public static final UUID UUID_CHARACTERISTIC_LUMINOUS_FLUX_RANGE = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2B00")));
+    public static final UUID UUID_CHARACTERISTIC_LUMINOUS_INTENSITY = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2B01")));
+    public static final UUID UUID_CHARACTERISTIC_MASS_FLOW = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2B02")));
+    public static final UUID UUID_CHARACTERISTIC_PERCEIVED_LIGHTNESS = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2B03")));
+    public static final UUID UUID_CHARACTERISTIC_PERCENTAGE_8 = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2B04")));
+    public static final UUID UUID_CHARACTERISTIC_POWER = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2B05")));
+    public static final UUID UUID_CHARACTERISTIC_POWER_SPECIFICATION = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2B06")));
+    public static final UUID UUID_CHARACTERISTIC_RELATIVE_RUNTIME_IN_A_CURRENT_RANGE = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2B07")));
+    public static final UUID UUID_CHARACTERISTIC_RELATIVE_RUNTIME_IN_A_GENERIC_LEVEL_RANGE = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2B08")));
+    public static final UUID UUID_CHARACTERISTIC_RELATIVE_VALUE_IN_A_VOLTAGE_RANGE = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2B09")));
+    public static final UUID UUID_CHARACTERISTIC_RELATIVE_VALUE_IN_AN_ILLUMINANCE_RANGE = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2B0A")));
+    public static final UUID UUID_CHARACTERISTIC_RELATIVE_VALUE_IN_A_PERIOD_OF_DAY = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2B0B")));
+    public static final UUID UUID_CHARACTERISTIC_RELATIVE_VALUE_IN_A_TEMPERATURE_RANGE = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2B0C")));
+    public static final UUID UUID_CHARACTERISTIC_TEMPERATURE_8 = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2B0D")));
+    public static final UUID UUID_CHARACTERISTIC_TEMPERATURE_8_IN_A_PERIOD_OF_DAY = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2B0E")));
+    public static final UUID UUID_CHARACTERISTIC_TEMPERATURE_8_STATISTICS = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2B0F")));
+    public static final UUID UUID_CHARACTERISTIC_TEMPERATURE_RANGE = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2B10")));
+    public static final UUID UUID_CHARACTERISTIC_TEMPERATURE_STATISTICS = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2B11")));
+    public static final UUID UUID_CHARACTERISTIC_TIME_DECIHOUR_8 = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2B12")));
+    public static final UUID UUID_CHARACTERISTIC_TIME_EXPONENTIAL_8 = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2B13")));
+    public static final UUID UUID_CHARACTERISTIC_TIME_HOUR_24 = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2B14")));
+    public static final UUID UUID_CHARACTERISTIC_TIME_MILLISECOND_24 = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2B15")));
+    public static final UUID UUID_CHARACTERISTIC_TIME_SECOND_16 = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2B16")));
+    public static final UUID UUID_CHARACTERISTIC_TIME_SECOND_8 = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2B17")));
+    public static final UUID UUID_CHARACTERISTIC_VOLTAGE = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2B18")));
+    public static final UUID UUID_CHARACTERISTIC_VOLTAGE_SPECIFICATION = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2B19")));
+    public static final UUID UUID_CHARACTERISTIC_VOLTAGE_STATISTICS = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2B1A")));
+    public static final UUID UUID_CHARACTERISTIC_VOLUME_FLOW = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2B1B")));
+    public static final UUID UUID_CHARACTERISTIC_CHROMATICITY_COORDINATE = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2B1C")));
+    public static final UUID UUID_CHARACTERISTIC_RC_FEATURE = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2B1D")));
+    public static final UUID UUID_CHARACTERISTIC_RC_SETTINGS = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2B1E")));
+    public static final UUID UUID_CHARACTERISTIC_RECONNECTION_CONFIGURATION_CONTROL_POINT = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2B1F")));
+    public static final UUID UUID_CHARACTERISTIC_IDD_STATUS_CHANGED = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2B20")));
+    public static final UUID UUID_CHARACTERISTIC_IDD_STATUS = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2B21")));
+    public static final UUID UUID_CHARACTERISTIC_IDD_ANNUNCIATION_STATUS = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2B22")));
+    public static final UUID UUID_CHARACTERISTIC_IDD_FEATURES = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2B23")));
+    public static final UUID UUID_CHARACTERISTIC_IDD_STATUS_READER_CONTROL_POINT = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2B24")));
+    public static final UUID UUID_CHARACTERISTIC_IDD_COMMAND_CONTROL_POINT = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2B25")));
+    public static final UUID UUID_CHARACTERISTIC_IDD_COMMAND_DATA = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2B26")));
+    public static final UUID UUID_CHARACTERISTIC_IDD_RECORD_ACCESS_CONTROL_POINT = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2B27")));
+    public static final UUID UUID_CHARACTERISTIC_IDD_HISTORY_DATA = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2B28")));
+    public static final UUID UUID_CHARACTERISTIC_CLIENT_SUPPORTED_FEATURES = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2B29")));
+    public static final UUID UUID_CHARACTERISTIC_DATABASE_HASH = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2B2A")));
+    public static final UUID UUID_CHARACTERISTIC_BSS_CONTROL_POINT = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2B2B")));
+    public static final UUID UUID_CHARACTERISTIC_BSS_RESPONSE = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2B2C")));
+    public static final UUID UUID_CHARACTERISTIC_EMERGENCY_ID = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2B2D")));
+    public static final UUID UUID_CHARACTERISTIC_EMERGENCY_TEXT = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2B2E")));
+    public static final UUID UUID_CHARACTERISTIC_ENHANCED_BLOOD_PRESSURE_MEASUREMENT = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2B34")));
+    public static final UUID UUID_CHARACTERISTIC_ENHANCED_INTERMEDIATE_CUFF_PRESSURE = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2B35")));
+    public static final UUID UUID_CHARACTERISTIC_BLOOD_PRESSURE_RECORD = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2B36")));
+    public static final UUID UUID_CHARACTERISTIC_BR_EDR_HANDOVER_DATA = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2B38")));
+    public static final UUID UUID_CHARACTERISTIC_BLUETOOTH_SIG_DATA = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2B39")));
+    public static final UUID UUID_CHARACTERISTIC_SERVER_SUPPORTED_FEATURES = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2B3A")));
+    public static final UUID UUID_CHARACTERISTIC_PHYSICAL_ACTIVITY_MONITOR_FEATURES = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2B3B")));
+    public static final UUID UUID_CHARACTERISTIC_GENERAL_ACTIVITY_INSTANTANEOUS_DATA = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2B3C")));
+    public static final UUID UUID_CHARACTERISTIC_GENERAL_ACTIVITY_SUMMARY_DATA = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2B3D")));
+    public static final UUID UUID_CHARACTERISTIC_CARDIORESPIRATORY_ACTIVITY_INSTANTANEOUS_DATA = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2B3E")));
+    public static final UUID UUID_CHARACTERISTIC_CARDIORESPIRATORY_ACTIVITY_SUMMARY_DATA = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2B3F")));
+    public static final UUID UUID_CHARACTERISTIC_STEP_COUNTER_ACTIVITY_SUMMARY_DATA = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2B40")));
+    public static final UUID UUID_CHARACTERISTIC_SLEEP_ACTIVITY_INSTANTANEOUS_DATA = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2B41")));
+    public static final UUID UUID_CHARACTERISTIC_SLEEP_ACTIVITY_SUMMARY_DATA = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2B42")));
+    public static final UUID UUID_CHARACTERISTIC_PHYSICAL_ACTIVITY_MONITOR_CONTROL_POINT = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2B43")));
+    public static final UUID UUID_CHARACTERISTIC_CURRENT_SESSION = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2B44")));
+    public static final UUID UUID_CHARACTERISTIC_SESSION = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2B45")));
+    public static final UUID UUID_CHARACTERISTIC_PREFERRED_UNITS = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2B46")));
+    public static final UUID UUID_CHARACTERISTIC_HIGH_RESOLUTION_HEIGHT = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2B47")));
+    public static final UUID UUID_CHARACTERISTIC_MIDDLE_NAME = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2B48")));
+    public static final UUID UUID_CHARACTERISTIC_STRIDE_LENGTH = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2B49")));
+    public static final UUID UUID_CHARACTERISTIC_HANDEDNESS = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2B4A")));
+    public static final UUID UUID_CHARACTERISTIC_DEVICE_WEARING_POSITION = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2B4B")));
+    public static final UUID UUID_CHARACTERISTIC_FOUR_ZONE_HEART_RATE_LIMITS = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2B4C")));
+    public static final UUID UUID_CHARACTERISTIC_HIGH_INTENSITY_EXERCISE_THRESHOLD = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2B4D")));
+    public static final UUID UUID_CHARACTERISTIC_ACTIVITY_GOAL = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2B4E")));
+    public static final UUID UUID_CHARACTERISTIC_SEDENTARY_INTERVAL_NOTIFICATION = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2B4F")));
+    public static final UUID UUID_CHARACTERISTIC_CALORIC_INTAKE = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2B50")));
+    public static final UUID UUID_CHARACTERISTIC_AUDIO_INPUT_STATE = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2B77")));
+    public static final UUID UUID_CHARACTERISTIC_GAIN_SETTINGS_ATTRIBUTE = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2B78")));
+    public static final UUID UUID_CHARACTERISTIC_AUDIO_INPUT_TYPE = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2B79")));
+    public static final UUID UUID_CHARACTERISTIC_AUDIO_INPUT_STATUS = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2B7A")));
+    public static final UUID UUID_CHARACTERISTIC_AUDIO_INPUT_CONTROL_POINT = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2B7B")));
+    public static final UUID UUID_CHARACTERISTIC_AUDIO_INPUT_DESCRIPTION = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2B7C")));
+    public static final UUID UUID_CHARACTERISTIC_VOLUME_STATE = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2B7D")));
+    public static final UUID UUID_CHARACTERISTIC_VOLUME_CONTROL_POINT = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2B7E")));
+    public static final UUID UUID_CHARACTERISTIC_VOLUME_FLAGS = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2B7F")));
+    public static final UUID UUID_CHARACTERISTIC_OFFSET_STATE = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2B80")));
+    public static final UUID UUID_CHARACTERISTIC_AUDIO_LOCATION = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2B81")));
+    public static final UUID UUID_CHARACTERISTIC_VOLUME_OFFSET_CONTROL_POINT = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2B82")));
+    public static final UUID UUID_CHARACTERISTIC_AUDIO_OUTPUT_DESCRIPTION = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2B83")));
+    public static final UUID UUID_CHARACTERISTIC_DEVICE_TIME_FEATURE = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2B8E")));
+    public static final UUID UUID_CHARACTERISTIC_DEVICE_TIME_PARAMETERS = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2B8F")));
+    public static final UUID UUID_CHARACTERISTIC_DEVICE_TIME = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2B90")));
+    public static final UUID UUID_CHARACTERISTIC_DEVICE_TIME_CONTROL_POINT = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2B91")));
+    public static final UUID UUID_CHARACTERISTIC_TIME_CHANGE_LOG_DATA = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2B92")));
+
+    private static Map<UUID, String> GATTCHARACTERISTIC_DEBUG;
+
+    public static synchronized String lookup(UUID uuid, String fallback) {
+        if (GATTCHARACTERISTIC_DEBUG == null) {
+            GATTCHARACTERISTIC_DEBUG = initDebugMap();
+        }
+        String name = GATTCHARACTERISTIC_DEBUG.get(uuid);
+        if (name == null) {
+            name = fallback;
+        }
+        return name;
+    }
+
+    private static Map<UUID, String> initDebugMap() {
+        Map<UUID,String> map = new HashMap<>();
+
+        try {
+            for (Field field : GattCharacteristic.class.getDeclaredFields()) {
+                if ((field.getModifiers() & Modifier.STATIC) != 0 && field.getType() == UUID.class) {
+                    UUID uuid = (UUID) field.get(null);
+                    if (uuid != null) {
+                        map.put(uuid, toPrettyName(field.getName()));
+                    }
+                }
+            }
+        } catch (Exception ex) {
+            Log.w(GattCharacteristic.class.getName(), "Error reading UUID fields by reflection: " + ex.getMessage(), ex);
+        }
+        return map;
+    }
+
+    private static String toPrettyName(String fieldName) {
+        String[] words = fieldName.split("_");
+        if (words.length <= 1) {
+            return fieldName.toLowerCase();
+        }
+        StringBuilder builder = new StringBuilder(fieldName.length());
+        for (String word : words) {
+            if (word.length() == 0 || "UUID".equals(word) || "CHARACTERISTIC".equals(word)) {
+                continue;
+            }
+            if (builder.length() > 0) {
+                builder.append(" ");
+            }
+            builder.append(word.toLowerCase());
+        }
+        return builder.toString();
+    }
+
+    public static String toString(BluetoothGattCharacteristic characteristic) {
+        return characteristic.getUuid() + " (" + lookup(characteristic.getUuid(), "unknown") + ")";
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/GattDescriptor.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/GattDescriptor.java
new file mode 100644
index 0000000..9e6a8a9
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/GattDescriptor.java
@@ -0,0 +1,41 @@
+/*  Copyright (C) 2015-2024 Daniele Gobbetti
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.btle;
+
+import java.util.UUID;
+
+public class GattDescriptor {
+
+    //part of the generic BLE specs see https://developer.bluetooth.org/gatt/descriptors/Pages/DescriptorsHomePage.aspx
+    //the list is complete as of 2015-09-28
+    public static final UUID UUID_DESCRIPTOR_GATT_CHARACTERISTIC_EXTENDED_PROPERTIES = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2900")));
+    public static final UUID UUID_DESCRIPTOR_GATT_CHARACTERISTIC_USER_DESCRIPTION = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2901")));
+    public static final UUID UUID_DESCRIPTOR_GATT_CLIENT_CHARACTERISTIC_CONFIGURATION = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2902")));
+    public static final UUID UUID_DESCRIPTOR_GATT_SERVER_CHARACTERISTIC_CONFIGURATION = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2903")));
+    public static final UUID UUID_DESCRIPTOR_GATT_CHARACTERISTIC_PRESENTATION_FORMAT = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2904")));
+    public static final UUID UUID_DESCRIPTOR_GATT_CHARACTERISTIC_AGGREGATE_FORMAT = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2905")));
+    public static final UUID UUID_DESCRIPTOR_VALID_RANGE = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2906")));
+    public static final UUID UUID_DESCRIPTOR_EXTERNAL_REPORT_REFERENCE = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2907")));
+    public static final UUID UUID_DESCRIPTOR_REPORT_REFERENCE = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2908")));
+    public static final UUID UUID_DESCRIPTOR_NUMBER_OF_DIGITALS = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2909")));
+    public static final UUID UUID_DESCRIPTOR_VALUE_TRIGGER_SETTING = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "290A")));
+    public static final UUID UUID_DESCRIPTOR_ES_CONFIGURATION = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "290B")));
+    public static final UUID UUID_DESCRIPTOR_ES_MEASUREMENT = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "290C")));
+    public static final UUID UUID_DESCRIPTOR_ES_TRIGGER_SETTING = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "290D")));
+    public static final UUID UUID_DESCRIPTOR_TIME_TRIGGER_SETTING = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "290E")));
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/GattListenerAction.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/GattListenerAction.java
new file mode 100644
index 0000000..c8f3471
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/GattListenerAction.java
@@ -0,0 +1,21 @@
+/*  Copyright (C) 2018-2024 Carsten Pfeiffer
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.btle;
+
+public interface GattListenerAction {
+    GattCallback getGattCallback();
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/GattServerCallback.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/GattServerCallback.java
new file mode 100644
index 0000000..80ed2ff
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/GattServerCallback.java
@@ -0,0 +1,76 @@
+/*  Copyright (C) 2019-2024 Andreas B枚hler
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.btle;
+
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.bluetooth.BluetoothGattDescriptor;
+import android.bluetooth.BluetoothGattServerCallback;
+
+public interface GattServerCallback {
+
+    /**
+     * @param device
+     * @param status
+     * @param newState
+     * @see BluetoothGattServerCallback#onConnectionStateChange(BluetoothDevice, int, int)
+     */
+    void onConnectionStateChange(BluetoothDevice device, int status, int newState);
+
+    /**
+     * @param device
+     * @param requestId
+     * @param offset
+     * @param characteristic
+     * @see BluetoothGattServerCallback#onCharacteristicReadRequest(BluetoothDevice, int, int, BluetoothGattCharacteristic)
+     */
+    boolean onCharacteristicReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattCharacteristic characteristic);
+
+    /**
+     * @param device
+     * @param requestId
+     * @param characteristic
+     * @param preparedWrite
+     * @param responseNeeded
+     * @param offset
+     * @param value
+     * @see BluetoothGattServerCallback#onCharacteristicWriteRequest(BluetoothDevice, int, BluetoothGattCharacteristic, boolean, boolean, int, byte[])
+     */
+    boolean onCharacteristicWriteRequest(BluetoothDevice device, int requestId, BluetoothGattCharacteristic characteristic, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value);
+
+    /**
+     * @param device
+     * @param requestId
+     * @param offset
+     * @param descriptor
+     * @see BluetoothGattServerCallback#onDescriptorReadRequest(BluetoothDevice, int, int, BluetoothGattDescriptor)
+     */
+    boolean onDescriptorReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattDescriptor descriptor);
+
+    /**
+     * @param device
+     * @param requestId
+     * @param descriptor
+     * @param preparedWrite
+     * @param responseNeeded
+     * @param offset
+     * @param value
+     * @see BluetoothGattServerCallback#onDescriptorWriteRequest(BluetoothDevice, int, BluetoothGattDescriptor, boolean, boolean, int, byte[])
+     */
+    boolean onDescriptorWriteRequest(BluetoothDevice device, int requestId, BluetoothGattDescriptor descriptor, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value);
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/GattService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/GattService.java
new file mode 100644
index 0000000..9c353b5
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/GattService.java
@@ -0,0 +1,80 @@
+/*  Copyright (C) 2015-2024 Carsten Pfeiffer, Daniele Gobbetti
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.btle;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+public class GattService {
+
+    //part of the generic BLE specs see https://developer.bluetooth.org/gatt/services/Pages/ServicesHome.aspx
+    //the list is complete as of 2015-09-28
+    public static final UUID UUID_SERVICE_ALERT_NOTIFICATION = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "1811")));
+    public static final UUID UUID_SERVICE_AUTOMATION_IO = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "1815")));
+    public static final UUID UUID_SERVICE_BATTERY_SERVICE = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "180F")));
+    public static final UUID UUID_SERVICE_BLOOD_PRESSURE = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "1810")));
+    public static final UUID UUID_SERVICE_BODY_COMPOSITION = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "181B")));
+    public static final UUID UUID_SERVICE_BOND_MANAGEMENT = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "181E")));
+    public static final UUID UUID_SERVICE_CONTINUOUS_GLUCOSE_MONITORING = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "181F")));
+    public static final UUID UUID_SERVICE_CURRENT_TIME = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "1805")));
+    public static final UUID UUID_SERVICE_CYCLING_POWER = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "1818")));
+    public static final UUID UUID_SERVICE_CYCLING_SPEED_AND_CADENCE = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "1816")));
+    public static final UUID UUID_SERVICE_DEVICE_INFORMATION = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "180A")));
+    public static final UUID UUID_SERVICE_ENVIRONMENTAL_SENSING = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "181A")));
+    public static final UUID UUID_SERVICE_GENERIC_ACCESS = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "1800")));
+    public static final UUID UUID_SERVICE_GENERIC_ATTRIBUTE = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "1801")));
+    public static final UUID UUID_SERVICE_GLUCOSE = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "1808")));
+    public static final UUID UUID_SERVICE_HEALTH_THERMOMETER = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "1809")));
+    public static final UUID UUID_SERVICE_HEART_RATE = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "180D")));
+    public static final UUID UUID_SERVICE_HUMAN_INTERFACE_DEVICE = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "1812")));
+    public static final UUID UUID_SERVICE_IMMEDIATE_ALERT = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "1802")));
+    public static final UUID UUID_SERVICE_INDOOR_POSITIONING = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "1821")));
+    public static final UUID UUID_SERVICE_INTERNET_PROTOCOL_SUPPORT = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "1820")));
+    public static final UUID UUID_SERVICE_LINK_LOSS = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "1803")));
+    public static final UUID UUID_SERVICE_LOCATION_AND_NAVIGATION = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "1819")));
+    public static final UUID UUID_SERVICE_NEXT_DST_CHANGE = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "1807")));
+    public static final UUID UUID_SERVICE_PHONE_ALERT_STATUS = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "180E")));
+    public static final UUID UUID_SERVICE_PULSE_OXIMETER = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "1822")));
+    public static final UUID UUID_SERVICE_REFERENCE_TIME_UPDATE = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "1806")));
+    public static final UUID UUID_SERVICE_RUNNING_SPEED_AND_CADENCE = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "1814")));
+    public static final UUID UUID_SERVICE_SCAN_PARAMETERS = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "1813")));
+    public static final UUID UUID_SERVICE_TX_POWER = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "1804")));
+    public static final UUID UUID_SERVICE_USER_DATA = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "181C")));
+    public static final UUID UUID_SERVICE_WEIGHT_SCALE = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "181D")));
+
+    //do we need this?
+
+    private static final Map<UUID, String> GATTSERVICE_DEBUG;
+
+    static {
+        GATTSERVICE_DEBUG = new HashMap<>();
+        GATTSERVICE_DEBUG.put(UUID_SERVICE_GENERIC_ACCESS, "Generic Access Service");
+        GATTSERVICE_DEBUG.put(UUID_SERVICE_GENERIC_ATTRIBUTE, "Generic Attribute Service");
+        GATTSERVICE_DEBUG.put(UUID_SERVICE_IMMEDIATE_ALERT, "Immediate Alert");
+
+    }
+
+    public static String lookup(UUID uuid, String fallback) {
+        String name = GATTSERVICE_DEBUG.get(uuid);
+        if (name == null) {
+            name = fallback;
+        }
+        return name;
+    }
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/ServerTransaction.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/ServerTransaction.java
new file mode 100644
index 0000000..bee6de0
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/ServerTransaction.java
@@ -0,0 +1,76 @@
+/*  Copyright (C) 2019-2024 Andreas B枚hler, Damien Gaignon
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.btle;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+
+import androidx.annotation.Nullable;
+
+/**
+ * Groups a bunch of {@link BtLEServerAction actions} together, making sure
+ * that upon failure of one action, all subsequent actions are discarded.
+ *
+ * @author TREND
+ */
+public class ServerTransaction extends AbstractTransaction {
+    private final List<BtLEServerAction> mActions = new ArrayList<>(4);
+    private
+    @Nullable
+    GattServerCallback gattCallback;
+
+    public ServerTransaction(String taskName) {
+        super(taskName);
+    }
+
+    public void add(BtLEServerAction action) {
+        mActions.add(action);
+    }
+
+    public List<BtLEServerAction> getActions() {
+        return Collections.unmodifiableList(mActions);
+    }
+
+    public boolean isEmpty() {
+        return mActions.isEmpty();
+    }
+
+    @Override
+    public String toString() {
+        return String.format(Locale.US, "%s: Transaction task: %s with %d actions", getCreationTime(), getTaskName(), mActions.size());
+    }
+
+    public void setCallback(@Nullable GattServerCallback callback) {
+        gattCallback = callback;
+    }
+
+    /**
+     * Returns the GattServerCallback for this transaction, or null if none.
+     */
+    public
+    @Nullable
+    GattServerCallback getGattCallback() {
+        return gattCallback;
+    }
+
+    @Override
+    public int getActionCount() {
+        return mActions.size();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/ServerTransactionBuilder.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/ServerTransactionBuilder.java
new file mode 100644
index 0000000..8c2d31b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/ServerTransactionBuilder.java
@@ -0,0 +1,87 @@
+/*  Copyright (C) 2019-2024 Andreas B枚hler, Damien Gaignon
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.btle;
+
+import android.bluetooth.BluetoothDevice;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import androidx.annotation.Nullable;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.ServerResponseAction;
+
+public class ServerTransactionBuilder {
+    private static final Logger LOG = LoggerFactory.getLogger(ServerTransactionBuilder.class);
+
+    private final ServerTransaction mTransaction;
+    private boolean mQueued;
+
+    public ServerTransactionBuilder(String taskName) {
+        mTransaction = new ServerTransaction(taskName);
+    }
+
+    public ServerTransactionBuilder writeServerResponse(BluetoothDevice device, int requestId, int status, int offset, byte[] data) {
+        if(device == null) {
+            LOG.warn("Unable to write to device: null");
+            return this;
+        }
+        ServerResponseAction action = new ServerResponseAction(device, requestId, status, offset, data);
+        return add(action);
+    }
+
+    public ServerTransactionBuilder add(BtLEServerAction action) {
+        mTransaction.add(action);
+        return this;
+    }
+
+    /**
+     * Sets a GattServerCallback instance that will be called when the transaction is executed,
+     * resulting in GattServerCallback events.
+     *
+     * @param callback the callback to set, may be null
+     */
+    public void setCallback(@Nullable GattServerCallback callback) {
+        mTransaction.setCallback(callback);
+    }
+
+    public
+    @Nullable
+    GattServerCallback getGattCallback() {
+        return mTransaction.getGattCallback();
+    }
+
+    /**
+     * To be used as the final step to execute the transaction by the given queue.
+     *
+     * @param queue
+     */
+    public void queue(BtLEQueue queue) {
+        if (mQueued) {
+            throw new IllegalStateException("This builder had already been queued. You must not reuse it.");
+        }
+        mQueued = true;
+        queue.add(mTransaction);
+    }
+
+    public ServerTransaction getTransaction() {
+        return mTransaction;
+    }
+
+    public String getTaskName() {
+        return mTransaction.getTaskName();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/Transaction.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/Transaction.java
new file mode 100644
index 0000000..4c5bf4a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/Transaction.java
@@ -0,0 +1,83 @@
+/*  Copyright (C) 2015-2024 Andreas B枚hler, Andreas Shimokawa, Carsten
+    Pfeiffer, Damien Gaignon, Daniele Gobbetti, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.btle;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import androidx.annotation.Nullable;
+
+/**
+ * Groups a bunch of {@link BtLEAction actions} together, making sure
+ * that upon failure of one action, all subsequent actions are discarded.
+ *
+ * @author TREND
+ */
+public class Transaction extends AbstractTransaction {
+    private final List<BtLEAction> mActions = new ArrayList<>(4);
+
+    private
+    @Nullable
+    GattCallback gattCallback;
+
+    private boolean modifyGattCallback;
+
+    public Transaction(String taskName) {
+        super(taskName);
+    }
+
+    public void add(BtLEAction action) {
+        mActions.add(action);
+    }
+
+    public List<BtLEAction> getActions() {
+        return Collections.unmodifiableList(mActions);
+    }
+
+    public boolean isEmpty() {
+        return mActions.isEmpty();
+    }
+
+    public void setCallback(@Nullable GattCallback callback) {
+        gattCallback = callback;
+        modifyGattCallback = true;
+    }
+
+    /**
+     * Returns the GattCallback for this transaction, or null if none.
+     */
+    public
+    @Nullable
+    GattCallback getGattCallback() {
+        return gattCallback;
+    }
+
+    /**
+     * Returns whether the gatt callback should be modified for this transaction (either set, or
+     * unset if {@code getGattCallback} is null.
+     */
+    public boolean isModifyGattCallback() {
+        return modifyGattCallback;
+    }
+
+    @Override
+    public int getActionCount() {
+        return mActions.size();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/TransactionBuilder.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/TransactionBuilder.java
new file mode 100644
index 0000000..0eef3c5
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/TransactionBuilder.java
@@ -0,0 +1,167 @@
+/*  Copyright (C) 2015-2024 Andreas B枚hler, Andreas Shimokawa, Carsten
+    Pfeiffer, Damien Gaignon, Daniel Dakhno, Daniele Gobbetti, Frank Ertl,
+    Jos茅 Rebelo, Johannes Krude
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.btle;
+
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.os.Build;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
+
+import java.util.Arrays;
+
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.BondAction;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.FunctionAction;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.NotifyAction;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.ReadAction;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.RequestConnectionPriorityAction;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.RequestMtuAction;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.WaitAction;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.WriteAction;
+
+public class TransactionBuilder {
+    private static final Logger LOG = LoggerFactory.getLogger(TransactionBuilder.class);
+
+    private final Transaction mTransaction;
+    private boolean mQueued;
+
+    public TransactionBuilder(String taskName) {
+        mTransaction = new Transaction(taskName);
+    }
+
+    public TransactionBuilder read(BluetoothGattCharacteristic characteristic) {
+        if (characteristic == null) {
+            LOG.warn("Unable to read characteristic: null");
+            return this;
+        }
+        ReadAction action = new ReadAction(characteristic);
+        return add(action);
+    }
+
+    public TransactionBuilder write(BluetoothGattCharacteristic characteristic, byte[] data) {
+        if (characteristic == null) {
+            LOG.warn("Unable to write characteristic: null");
+            return this;
+        }
+        WriteAction action = new WriteAction(characteristic, data);
+        return add(action);
+    }
+
+    public TransactionBuilder writeChunkedData(BluetoothGattCharacteristic characteristic, byte[] data, int chunkSize) {
+        for (int start = 0; start < data.length; start += chunkSize) {
+            int end = start + chunkSize;
+            if (end > data.length) end = data.length;
+            WriteAction action = new WriteAction(characteristic, Arrays.copyOfRange(data, start, end));
+            add(action);
+        }
+
+        return this;
+    }
+
+    public TransactionBuilder requestMtu(int mtu){
+        return add(
+                new RequestMtuAction(mtu)
+        );
+    }
+
+    public TransactionBuilder requestConnectionPriority(int priority){
+        return add(
+                new RequestConnectionPriorityAction(priority)
+        );
+    }
+
+    public TransactionBuilder bond() {
+        BondAction action = new BondAction();
+        return add(action);
+    }
+
+    public TransactionBuilder notify(BluetoothGattCharacteristic characteristic, boolean enable) {
+        if (characteristic == null) {
+            LOG.warn("Unable to notify characteristic: null");
+            return this;
+        }
+        NotifyAction action = createNotifyAction(characteristic, enable);
+        return add(action);
+    }
+
+    protected NotifyAction createNotifyAction(BluetoothGattCharacteristic characteristic, boolean enable) {
+        return new NotifyAction(characteristic, enable);
+    }
+
+    /**
+     * Causes the queue to sleep for the specified time.
+     * Note that this is usually a bad idea, since it will not be able to process messages
+     * during that time. It is also likely to cause race conditions.
+     * @param millis the number of milliseconds to sleep
+     */
+    public TransactionBuilder wait(int millis) {
+        WaitAction action = new WaitAction(millis);
+        return add(action);
+    }
+
+    // Runs the given function/lambda
+    public TransactionBuilder run(FunctionAction.Function function) {
+        return add(new FunctionAction(function));
+    }
+
+    public TransactionBuilder add(BtLEAction action) {
+        mTransaction.add(action);
+        return this;
+    }
+
+    /**
+     * Sets a GattCallback instance that will be called when the transaction is executed,
+     * resulting in GattCallback events.
+     *
+     * @param callback the callback to set, may be null
+     */
+    public void setCallback(@Nullable GattCallback callback) {
+        mTransaction.setCallback(callback);
+    }
+
+    public
+    @Nullable
+    GattCallback getGattCallback() {
+        return mTransaction.getGattCallback();
+    }
+
+    /**
+     * To be used as the final step to execute the transaction by the given queue.
+     *
+     * @param queue
+     */
+    public void queue(BtLEQueue queue) {
+        if (mQueued) {
+            throw new IllegalStateException("This builder had already been queued. You must not reuse it.");
+        }
+        mQueued = true;
+        queue.add(mTransaction);
+    }
+
+    public Transaction getTransaction() {
+        return mTransaction;
+    }
+
+    public String getTaskName() {
+        return mTransaction.getTaskName();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/AbortTransactionAction.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/AbortTransactionAction.java
new file mode 100644
index 0000000..f32f2cd
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/AbortTransactionAction.java
@@ -0,0 +1,49 @@
+/*  Copyright (C) 2015-2024 Carsten Pfeiffer
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.btle.actions;
+
+import android.bluetooth.BluetoothGatt;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A special action that checks for an abort-condition, and if met, the currently
+ * executing transaction will be aborted by returning false.
+ */
+public abstract class AbortTransactionAction extends PlainAction {
+    private static final Logger LOG = LoggerFactory.getLogger(AbortTransactionAction.class);
+
+    public AbortTransactionAction() {
+    }
+
+    @Override
+    public boolean run(BluetoothGatt gatt) {
+        if (shouldAbort()) {
+            LOG.info("Aborting transaction because abort criteria met.");
+            return false;
+        }
+        return true;
+    }
+
+    protected abstract boolean shouldAbort();
+
+    @Override
+    public String toString() {
+        return getCreationTime() + ": " + getClass().getSimpleName() + ": aborting? " + shouldAbort();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/AbstractGattListenerWriteAction.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/AbstractGattListenerWriteAction.java
new file mode 100644
index 0000000..91c9527
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/AbstractGattListenerWriteAction.java
@@ -0,0 +1,49 @@
+/*  Copyright (C) 2018-2024 Carsten Pfeiffer
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.btle.actions;
+
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+
+import java.util.Objects;
+
+import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractGattCallback;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEQueue;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.GattCallback;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.GattListenerAction;
+
+public abstract class AbstractGattListenerWriteAction extends WriteAction implements GattListenerAction {
+    private final BtLEQueue queue;
+
+    public AbstractGattListenerWriteAction(BtLEQueue queue, BluetoothGattCharacteristic characteristic, byte[] value) {
+        super(characteristic, value);
+        this.queue = queue;
+        Objects.requireNonNull(queue, "queue must not be null");
+    }
+
+    @Override
+    public GattCallback getGattCallback() {
+        return new AbstractGattCallback() {
+            @Override
+            public boolean onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
+                return AbstractGattListenerWriteAction.this.onCharacteristicChanged(gatt, characteristic);
+            }
+        };
+    }
+
+    protected abstract boolean onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic);
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/BondAction.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/BondAction.java
new file mode 100644
index 0000000..8e88e59
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/BondAction.java
@@ -0,0 +1,83 @@
+/*  Copyright (C) 2023-2024 Andreas B枚hler
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.btle.actions;
+
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothGatt;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.IntentFilter;
+
+import androidx.core.content.ContextCompat;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
+import nodomain.freeyourgadget.gadgetbridge.util.AndroidUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.BondingInterface;
+import nodomain.freeyourgadget.gadgetbridge.util.BondingUtil;
+
+public class BondAction extends PlainAction implements BondingInterface {
+    private String mMacAddress;
+    private final BroadcastReceiver pairingReceiver = BondingUtil.getPairingReceiver(this);
+    private final BroadcastReceiver bondingReceiver = BondingUtil.getBondingReceiver(this);
+
+    @Override
+    public void onBondingComplete(boolean success) {
+        unregisterBroadcastReceivers();
+    }
+
+    @Override
+    public GBDeviceCandidate getCurrentTarget() {
+        return null;
+    }
+
+    @Override
+    public String getMacAddress() {
+        return mMacAddress;
+    }
+
+    @Override
+    public boolean getAttemptToConnect() {
+        return false;
+    }
+
+    @Override
+    public void unregisterBroadcastReceivers() {
+        AndroidUtils.safeUnregisterBroadcastReceiver(LocalBroadcastManager.getInstance(GBApplication.getContext()), pairingReceiver);
+        AndroidUtils.safeUnregisterBroadcastReceiver(GBApplication.getContext(), bondingReceiver);
+    }
+
+    @Override
+    public void registerBroadcastReceivers() {
+        LocalBroadcastManager.getInstance(GBApplication.getContext()).registerReceiver(pairingReceiver, new IntentFilter(GBDevice.ACTION_DEVICE_CHANGED));
+        ContextCompat.registerReceiver(getContext(), bondingReceiver, new IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED), ContextCompat.RECEIVER_EXPORTED);
+    }
+
+    @Override
+    public Context getContext() {
+        return GBApplication.getContext();
+    }
+
+    @Override
+    public boolean run(BluetoothGatt gatt) {
+        mMacAddress = gatt.getDevice().getAddress();
+        BondingUtil.tryBondThenComplete(this, gatt.getDevice(), gatt.getDevice().getAddress());
+        return true;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/CheckInitializedAction.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/CheckInitializedAction.java
new file mode 100644
index 0000000..3dc83b5
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/CheckInitializedAction.java
@@ -0,0 +1,46 @@
+/*  Copyright (C) 2016-2024 Carsten Pfeiffer
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.btle.actions;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+/**
+ * A special action that is executed at the very front of the initialization
+ * sequence (transaction). It will abort the entire initialization sequence
+ * by returning false, when the device is already initialized.
+ */
+public class CheckInitializedAction extends AbortTransactionAction {
+    private static final Logger LOG = LoggerFactory.getLogger(CheckInitializedAction.class);
+
+    private final GBDevice device;
+
+    public CheckInitializedAction(GBDevice gbDevice) {
+        device = gbDevice;
+    }
+
+    @Override
+    protected boolean shouldAbort() {
+        boolean abort = device.isInitialized();
+        if (abort) {
+            LOG.info("Aborting device initialization, because already initialized: " + device);
+        }
+        return abort;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/ConditionalWriteAction.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/ConditionalWriteAction.java
new file mode 100644
index 0000000..1c9b788
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/ConditionalWriteAction.java
@@ -0,0 +1,46 @@
+/*  Copyright (C) 2016-2024 Andreas Shimokawa, Carsten Pfeiffer
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.btle.actions;
+
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+
+public abstract class ConditionalWriteAction extends WriteAction {
+    public ConditionalWriteAction(BluetoothGattCharacteristic characteristic) {
+        super(characteristic, null);
+    }
+
+    @Override
+    protected boolean writeValue(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, byte[] value) {
+        byte[] conditionalValue = checkCondition();
+        if (conditionalValue != null) {
+            return super.writeValue(gatt, characteristic, conditionalValue);
+        }
+        return true;
+    }
+
+    /**
+     * Checks the condition whether the write shall happen or not.
+     * Returns the actual value to be written or null in case nothing shall be written.
+     * <p/>
+     * Note that returning null will not cause run() to return false, in other words,
+     * the rest of the queue will still be executed.
+     *
+     * @return the value to be written or null to not write anything
+     */
+    protected abstract byte[] checkCondition();
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/FunctionAction.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/FunctionAction.java
new file mode 100644
index 0000000..404c649
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/FunctionAction.java
@@ -0,0 +1,47 @@
+/*  Copyright (C) 2023 Johannes Krude
+
+    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 <http://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.btle.actions;
+
+import android.bluetooth.BluetoothGatt;
+
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.PlainAction;
+
+/**
+ * Invokes the given function
+ */
+public class FunctionAction extends PlainAction {
+
+    public interface Function {
+        public void apply(BluetoothGatt gatt);
+    }
+    private Function function;
+
+    public FunctionAction(Function function) {
+        this.function = function;
+    }
+
+    @Override
+    public boolean run(BluetoothGatt gatt) {
+        function.apply(gatt);
+        return true;
+    }
+
+    @Override
+    public boolean expectsResult() {
+        return false;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/NotifyAction.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/NotifyAction.java
new file mode 100644
index 0000000..29c6eb2
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/NotifyAction.java
@@ -0,0 +1,136 @@
+/*  Copyright (C) 2015-2024 Alicia Hormann, Carsten Pfeiffer, Daniele Gobbetti
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.btle.actions;
+
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCallback;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.bluetooth.BluetoothGattDescriptor;
+import android.bluetooth.BluetoothStatusCodes;
+import android.os.Build;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEAction;
+
+import static nodomain.freeyourgadget.gadgetbridge.service.btle.GattDescriptor.UUID_DESCRIPTOR_GATT_CLIENT_CHARACTERISTIC_CONFIGURATION;
+
+import androidx.annotation.RequiresPermission;
+
+/**
+ * Enables or disables notifications for a given GATT characteristic.
+ * The result will be made available asynchronously through the
+ * {@link BluetoothGattCallback}.
+ */
+public class NotifyAction extends BtLEAction {
+
+    private static final Logger LOG = LoggerFactory.getLogger(NotifyAction.class);
+    protected final boolean enableFlag;
+    private boolean hasWrittenDescriptor = false;
+
+    public NotifyAction(BluetoothGattCharacteristic characteristic, boolean enable) {
+        super(characteristic);
+        enableFlag = enable;
+    }
+
+    @RequiresPermission("android.permission.BLUETOOTH_CONNECT")
+    private boolean writeDescriptor(final BluetoothGatt gatt, final BluetoothGattDescriptor descriptor, final byte[] value) {
+        if (gatt == null) {
+            LOG.error("gatt == null");
+            return false;
+        }
+
+        if (descriptor == null) {
+            LOG.error("descriptor == null");
+            return false;
+        }
+
+        final String charUuid = descriptor.getCharacteristic().getUuid().toString();
+
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+            // use API introduced in SDK level 33 to catch exceptions and more specific errors
+            try {
+                final int result = gatt.writeDescriptor(descriptor, value);
+
+                if (result != BluetoothStatusCodes.SUCCESS) {
+                    LOG.error("Writing characteristic {} descriptor failed: {}", charUuid, result);
+                    return false;
+                }
+            } catch (final SecurityException ex) {
+                LOG.error("SecurityException while writing to characteristic {} descriptor: {}", charUuid, ex.getMessage(), ex);
+                return false;
+            }
+        } else {
+            if (!descriptor.setValue(value)) {
+                LOG.error("Updating descriptor value on characteristic {} failed", charUuid);
+                return false;
+            }
+
+            if (!gatt.writeDescriptor(descriptor)) {
+                LOG.error("Writing descriptor on characteristic {} failed", charUuid);
+                return false;
+            }
+        }
+
+        LOG.debug("Successfully written characteristic {} descriptor", charUuid);
+        return true;
+    }
+
+    @Override
+    @RequiresPermission("android.permission.BLUETOOTH_CONNECT")
+    public boolean run(BluetoothGatt gatt) {
+        // register gatt's callback to receive notifications
+        boolean result = gatt.setCharacteristicNotification(getCharacteristic(), enableFlag);
+
+        if (result) {
+            BluetoothGattDescriptor clientCharConfigDescriptor = getCharacteristic().getDescriptor(UUID_DESCRIPTOR_GATT_CLIENT_CHARACTERISTIC_CONFIGURATION);
+
+            if (clientCharConfigDescriptor != null) {
+                int properties = getCharacteristic().getProperties();
+
+                if ((properties & BluetoothGattCharacteristic.PROPERTY_NOTIFY) > 0) {
+                    LOG.debug("use NOTIFICATION for Characteristic " + getCharacteristic().getUuid());
+                    final byte[] value = enableFlag ? BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE : BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE;
+                    result = writeDescriptor(gatt, clientCharConfigDescriptor, value);
+                    hasWrittenDescriptor = true;
+                } else if ((properties & BluetoothGattCharacteristic.PROPERTY_INDICATE) > 0) {
+                    LOG.debug("use INDICATION for Characteristic " + getCharacteristic().getUuid());
+                    final byte[] value = enableFlag ? BluetoothGattDescriptor.ENABLE_INDICATION_VALUE : BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE;
+                    result =  writeDescriptor(gatt, clientCharConfigDescriptor, value);
+                    hasWrittenDescriptor = true;
+                } else {
+                    LOG.debug("use neither NOTIFICATION nor INDICATION for Characteristic " + getCharacteristic().getUuid());
+                    hasWrittenDescriptor = false;
+                }
+            } else {
+                LOG.warn("Descriptor CLIENT_CHARACTERISTIC_CONFIGURATION for characteristic " + getCharacteristic().getUuid() + " is null");
+                hasWrittenDescriptor = false;
+            }
+        } else {
+            LOG.error("Unable to enable notifications for " + getCharacteristic().getUuid());
+            hasWrittenDescriptor = false;
+        }
+
+        return result;
+    }
+
+    @Override
+    public boolean expectsResult() {
+        return hasWrittenDescriptor;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/PlainAction.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/PlainAction.java
new file mode 100644
index 0000000..56f4c2c
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/PlainAction.java
@@ -0,0 +1,40 @@
+/*  Copyright (C) 2015-2024 Carsten Pfeiffer
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.btle.actions;
+
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEAction;
+
+/**
+ * An abstract non-BTLE action. It performs no bluetooth operation,
+ * does not have a BluetoothGattCharacteristic instance and expects no result.
+ */
+public abstract class PlainAction extends BtLEAction {
+
+    public PlainAction() {
+        super(null);
+    }
+
+    @Override
+    public boolean expectsResult() {
+        return false;
+    }
+
+    @Override
+    public String toString() {
+        return getCreationTime() + ": " + getClass().getSimpleName();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/ReadAction.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/ReadAction.java
new file mode 100644
index 0000000..ee0e8db
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/ReadAction.java
@@ -0,0 +1,49 @@
+/*  Copyright (C) 2015-2024 Carsten Pfeiffer, Daniele Gobbetti
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.btle.actions;
+
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCallback;
+import android.bluetooth.BluetoothGattCharacteristic;
+
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEAction;
+
+/**
+ * Invokes a read operation on a given GATT characteristic.
+ * The result will be made available asynchronously through the
+ * {@link BluetoothGattCallback}
+ */
+public class ReadAction extends BtLEAction {
+
+    public ReadAction(BluetoothGattCharacteristic characteristic) {
+        super(characteristic);
+    }
+
+    @Override
+    public boolean run(BluetoothGatt gatt) {
+        int properties = getCharacteristic().getProperties();
+        if ((properties & BluetoothGattCharacteristic.PROPERTY_READ) > 0) {
+            return gatt.readCharacteristic(getCharacteristic());
+        }
+        return false;
+    }
+
+    @Override
+    public boolean expectsResult() {
+        return true;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/RequestConnectionPriorityAction.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/RequestConnectionPriorityAction.java
new file mode 100644
index 0000000..ab5c916
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/RequestConnectionPriorityAction.java
@@ -0,0 +1,42 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.btle.actions;
+
+import android.annotation.SuppressLint;
+import android.bluetooth.BluetoothGatt;
+
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEAction;
+
+public class RequestConnectionPriorityAction extends BtLEAction {
+    private int priority;
+
+    public RequestConnectionPriorityAction(int priority) {
+        super(null);
+        this.priority = priority;
+    }
+
+    @Override
+    public boolean expectsResult() {
+        return true;
+    }
+
+    @Override
+    @SuppressLint("MissingPermission")
+    public boolean run(final BluetoothGatt gatt) {
+        return gatt.requestConnectionPriority(priority);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/RequestMtuAction.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/RequestMtuAction.java
new file mode 100644
index 0000000..5c20b28
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/RequestMtuAction.java
@@ -0,0 +1,45 @@
+/*  Copyright (C) 2019-2024 Andreas Shimokawa, Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.btle.actions;
+
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.os.Build;
+
+import androidx.annotation.RequiresApi;
+
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEAction;
+
+public class RequestMtuAction extends BtLEAction {
+    private int mtu;
+
+    public RequestMtuAction(int mtu) {
+        super(null);
+        this.mtu = mtu;
+    }
+
+
+    @Override
+    public boolean expectsResult() {
+        return true;
+    }
+
+    @Override
+    public boolean run(BluetoothGatt gatt) {
+        return gatt.requestMtu(this.mtu);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/ServerResponseAction.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/ServerResponseAction.java
new file mode 100644
index 0000000..f49664b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/ServerResponseAction.java
@@ -0,0 +1,71 @@
+/*  Copyright (C) 2019-2024 Andreas B枚hler
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.btle.actions;
+
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothGattCallback;
+import android.bluetooth.BluetoothGattServer;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import nodomain.freeyourgadget.gadgetbridge.Logging;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEServerAction;
+
+/**
+ * Invokes a response on a given GATT characteristic read.
+ * The result status will be made available asynchronously through the
+ * {@link BluetoothGattCallback}
+ */
+public class ServerResponseAction extends BtLEServerAction {
+    private static final Logger LOG = LoggerFactory.getLogger(ServerResponseAction.class);
+
+    private final byte[] value;
+    private final int requestId;
+    private final int status;
+    private final int offset;
+
+    public ServerResponseAction(BluetoothDevice device, int requestId, int status, int offset, byte[] data) {
+        super(device);
+        this.value = data;
+        this.requestId = requestId;
+        this.status = status;
+        this.offset = offset;
+    }
+
+    @Override
+    public boolean run(BluetoothGattServer server) {
+        return writeValue(server, getDevice(), requestId, status, offset, value);
+    }
+
+    protected boolean writeValue(BluetoothGattServer gattServer, BluetoothDevice device, int requestId, int status, int offset, byte[] value) {
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("writing to server: " + device.getAddress() + ": " + Logging.formatBytes(value));
+        }
+
+        return gattServer.sendResponse(device, requestId, 0, offset, value);
+    }
+
+    protected final byte[] getValue() {
+        return value;
+    }
+
+    @Override
+    public boolean expectsResult() {
+        return false;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/SetDeviceBusyAction.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/SetDeviceBusyAction.java
new file mode 100644
index 0000000..293298d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/SetDeviceBusyAction.java
@@ -0,0 +1,53 @@
+/*  Copyright (C) 2015-2024 Carsten Pfeiffer
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.btle.actions;
+
+import android.bluetooth.BluetoothGatt;
+import android.content.Context;
+
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class SetDeviceBusyAction extends PlainAction {
+    private final GBDevice device;
+    private final Context context;
+    private final String busyTask;
+
+    /**
+     * When run, will mark the device as busy (or not busy).
+     *
+     * @param device   the device to mark
+     * @param busyTask the task name to set as busy task, or null to mark as not busy
+     * @param context
+     */
+    public SetDeviceBusyAction(GBDevice device, String busyTask, Context context) {
+        this.device = device;
+        this.busyTask = busyTask;
+        this.context = context;
+    }
+
+    @Override
+    public boolean run(BluetoothGatt gatt) {
+        device.setBusyTask(busyTask);
+        device.sendDeviceUpdateIntent(context);
+        return true;
+    }
+
+    @Override
+    public String toString() {
+        return getCreationTime() + ": " + getClass().getName() + ": " + busyTask;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/SetDeviceStateAction.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/SetDeviceStateAction.java
new file mode 100644
index 0000000..dcc3fd0
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/SetDeviceStateAction.java
@@ -0,0 +1,53 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, Carsten Pfeiffer, Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.btle.actions;
+
+import android.bluetooth.BluetoothGatt;
+import android.content.Context;
+
+import androidx.annotation.NonNull;
+
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class SetDeviceStateAction extends PlainAction {
+    private final GBDevice device;
+    private final GBDevice.State deviceState;
+    private final Context context;
+
+    public SetDeviceStateAction(GBDevice device, GBDevice.State deviceState, Context context) {
+        this.device = device;
+        this.deviceState = deviceState;
+        this.context = context;
+    }
+
+    @Override
+    public boolean run(BluetoothGatt gatt) {
+        device.setState(deviceState);
+        device.sendDeviceUpdateIntent(getContext(), GBDevice.DeviceUpdateSubject.DEVICE_STATE);
+        return true;
+    }
+
+    public Context getContext() {
+        return context;
+    }
+
+    @NonNull
+    @Override
+    public String toString() {
+        return super.toString() + " to " + deviceState;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/SetProgressAction.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/SetProgressAction.java
new file mode 100644
index 0000000..7996978
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/SetProgressAction.java
@@ -0,0 +1,69 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, Carsten Pfeiffer, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.btle.actions;
+
+import android.bluetooth.BluetoothGatt;
+import android.content.Context;
+import android.content.Intent;
+
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class SetProgressAction extends PlainAction {
+    private static final Logger LOG = LoggerFactory.getLogger(SetProgressAction.class);
+
+    private final String text;
+    private final boolean ongoing;
+    private final int percentage;
+    private final Context context;
+
+    /**
+     * When run, will update the progress notification.
+     *
+     * @param text
+     * @param ongoing
+     * @param percentage
+     * @param context
+     */
+
+    public SetProgressAction(String text, boolean ongoing, int percentage, Context context) {
+        this.text = text;
+        this.ongoing = ongoing;
+        this.percentage = percentage;
+        this.context = context;
+    }
+
+    @Override
+    public boolean run(BluetoothGatt gatt) {
+        LOG.info(toString());
+        GB.updateInstallNotification(this.text, this.ongoing, this.percentage, this.context);
+
+        final LocalBroadcastManager broadcastManager = LocalBroadcastManager.getInstance(context);
+        broadcastManager.sendBroadcast(new Intent(GB.ACTION_SET_PROGRESS_BAR).putExtra(GB.PROGRESS_BAR_PROGRESS, percentage));
+
+        return true;
+    }
+
+    @Override
+    public String toString() {
+        return getCreationTime() + ": " + getClass().getSimpleName() + ": " + text + "; " + percentage + "%";
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/WaitAction.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/WaitAction.java
new file mode 100644
index 0000000..89c1526
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/WaitAction.java
@@ -0,0 +1,43 @@
+/*  Copyright (C) 2015-2024 Carsten Pfeiffer
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.btle.actions;
+
+import android.bluetooth.BluetoothGatt;
+
+/**
+ * An action that will cause the queue to sleep for the specified time.
+ * Note that this is usually a bad idea, since it will not be able to process messages
+ * during that time. It is also likely to cause race conditions.
+ */
+public class WaitAction extends PlainAction {
+
+    private final int mMillis;
+
+    public WaitAction(int millis) {
+        mMillis = millis;
+    }
+
+    @Override
+    public boolean run(BluetoothGatt gatt) {
+        try {
+            Thread.sleep(mMillis);
+            return true;
+        } catch (InterruptedException e) {
+            return false;
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/WriteAction.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/WriteAction.java
new file mode 100644
index 0000000..8019aed
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/WriteAction.java
@@ -0,0 +1,74 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, Carsten Pfeiffer, Daniele
+    Gobbetti, Jos茅 Rebelo, Uwe Hermann
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.btle.actions;
+
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCallback;
+import android.bluetooth.BluetoothGattCharacteristic;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import nodomain.freeyourgadget.gadgetbridge.Logging;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEAction;
+
+/**
+ * Invokes a write operation on a given GATT characteristic.
+ * The result status will be made available asynchronously through the
+ * {@link BluetoothGattCallback}
+ */
+public class WriteAction extends BtLEAction {
+    private static final Logger LOG = LoggerFactory.getLogger(WriteAction.class);
+
+    private final byte[] value;
+
+    public WriteAction(BluetoothGattCharacteristic characteristic, byte[] value) {
+        super(characteristic);
+        this.value = value;
+    }
+
+    @Override
+    public boolean run(BluetoothGatt gatt) {
+        BluetoothGattCharacteristic characteristic = getCharacteristic();
+        int properties = characteristic.getProperties();
+        //TODO: expectsResult should return false if PROPERTY_WRITE_NO_RESPONSE is true, but this leads to timing issues
+        if ((properties & BluetoothGattCharacteristic.PROPERTY_WRITE) > 0 || ((properties & BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE) > 0)) {
+            return writeValue(gatt, characteristic, value);
+        }
+        return false;
+    }
+
+    protected boolean writeValue(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, byte[] value) {
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("writing to characteristic: " + characteristic.getUuid() + ": " + Logging.formatBytes(value));
+        }
+        if (characteristic.setValue(value)) {
+            return gatt.writeCharacteristic(characteristic);
+        }
+        return false;
+    }
+
+    public final byte[] getValue() {
+        return value;
+    }
+
+    @Override
+    public boolean expectsResult() {
+        return true;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/AbstractBleProfile.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/AbstractBleProfile.java
new file mode 100644
index 0000000..1a9a8b4
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/AbstractBleProfile.java
@@ -0,0 +1,115 @@
+/*  Copyright (C) 2016-2024 Andreas Shimokawa, Carsten Pfeiffer, Damien
+    Gaignon, Daniele Gobbetti, Taavi Eom盲e
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.btle.profiles;
+
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.content.Context;
+import android.content.Intent;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractGattCallback;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEQueue;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+
+/**
+ * Base class for all BLE profiles, with things that all implementations are
+ * expected to use.
+ * <p>
+ * Instances are used in the context of a concrete AbstractBTLEDeviceSupport instance,
+ * i.e. a concrete device.
+ *
+ * @see nodomain.freeyourgadget.gadgetbridge.service.btle.GattService
+ * @see nodomain.freeyourgadget.gadgetbridge.service.btle.GattCharacteristic
+ * @see https://www.bluetooth.com/specifications/assigned-numbers
+ */
+public abstract class AbstractBleProfile<T extends AbstractBTLEDeviceSupport> extends AbstractGattCallback {
+    private final T mSupport;
+
+    private List<IntentListener> listeners = new ArrayList<IntentListener>(1);
+
+    public AbstractBleProfile(T support) {
+        this.mSupport = support;
+    }
+
+    public void addListener(IntentListener listener) {
+        if (listener != null && !listeners.contains(listener)) {
+            listeners.add(listener);
+        }
+    }
+
+    public boolean removeListener(IntentListener listener) {
+        return listeners.remove(listener);
+    }
+
+    protected List<IntentListener> getListeners() {
+        return Collections.unmodifiableList(listeners);
+    }
+
+    /**
+     * All notifications should be sent through this methods to make them testable.
+     * @param intent the intent to broadcast
+     */
+    protected void notify(Intent intent) {
+        // note: we send synchronously in order to keep the processing order of BLE events
+        // in BtleQueue and the reception of results.
+        for (IntentListener listener : listeners) {
+            listener.notify(intent);
+        }
+    }
+
+    /**
+     * Delegates to the DeviceSupport instance and additionally sets this instance as the Gatt
+     * callback for the transaction.
+     *
+     * @param taskName
+     * @return
+     * @throws IOException
+     */
+    public TransactionBuilder performInitialized(String taskName) throws IOException {
+        TransactionBuilder builder = mSupport.performInitialized(taskName);
+        builder.setCallback(this);
+        return builder;
+    }
+
+    public Context getContext() {
+        return mSupport.getContext();
+    }
+
+    protected GBDevice getDevice() {
+        return mSupport.getDevice();
+    }
+
+    protected BluetoothGattCharacteristic getCharacteristic(UUID uuid) {
+        return mSupport.getCharacteristic(uuid);
+    }
+
+    protected BtLEQueue getQueue() {
+        return mSupport.getQueue();
+    }
+
+    public void enableNotify(TransactionBuilder builder, boolean enable) {
+    }
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/IntentListener.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/IntentListener.java
new file mode 100644
index 0000000..6213a4b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/IntentListener.java
@@ -0,0 +1,26 @@
+/*  Copyright (C) 2018-2024 Carsten Pfeiffer
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.btle.profiles;
+
+import android.content.Intent;
+
+/**
+ * Callback interface for delivering results of ble requests.
+ */
+public interface IntentListener {
+    void notify(Intent intent);
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/ValueDecoder.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/ValueDecoder.java
new file mode 100644
index 0000000..d3e9c0d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/ValueDecoder.java
@@ -0,0 +1,41 @@
+/*  Copyright (C) 2016-2024 Alicia Hormann, Carsten Pfeiffer
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.btle.profiles;
+
+import android.bluetooth.BluetoothGattCharacteristic;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import nodomain.freeyourgadget.gadgetbridge.service.btle.GattCharacteristic;
+
+public class ValueDecoder {
+    private static final Logger LOG = LoggerFactory.getLogger(ValueDecoder.class);
+
+    public static int decodeInt(BluetoothGattCharacteristic characteristic) {
+        return characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 0);
+    }
+
+    public static int decodePercent(BluetoothGattCharacteristic characteristic) {
+        int percent = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 0);
+        if (percent > 100 || percent < 0) {
+            LOG.warn("Unexpected percent value: " + percent + ": " + GattCharacteristic.toString(characteristic));
+            percent = Math.min(100, Math.max(0, percent));
+        }
+        return percent;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/alertnotification/AlertCategory.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/alertnotification/AlertCategory.java
new file mode 100644
index 0000000..faa726d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/alertnotification/AlertCategory.java
@@ -0,0 +1,97 @@
+/*  Copyright (C) 2016-2024 Andreas Shimokawa, Carsten Pfeiffer, Uwe Hermann
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotification;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.alert_category_id.xml
+ * https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.alert_category_id_bit_mask.xml
+ */
+public enum AlertCategory {
+    Simple(0),
+    Email(1),
+    News(2),
+    IncomingCall(3),
+    MissedCall(4),
+    SMS(5),
+    VoiceMail(6),
+    Schedule(7),
+    HighPriorityAlert(8),
+    InstantMessage(9),
+    // 10-250 reserved for future use
+    // 251-255 defined by service specification
+    Any(255),
+    Custom(-1),
+    CustomHuami(-6);
+
+    private final int id;
+
+    AlertCategory(int id) {
+        this.id = id;
+    }
+
+    /**
+     * Returns the numerical ID value of this category
+     * To be used as uint8 value
+     * @return the uint8 value for this category
+     */
+    public int getId() {
+        return id;
+    }
+
+    private int realBitNumber() {
+        // the ID corresponds to the bit for the bitset
+        return id;
+    }
+
+    private int bitNumberPerByte() {
+        // the ID corresponds to the bit for the bitset (per byte)
+        return realBitNumber() % 8;
+    }
+
+    private int asBit() {
+        return 1 << bitNumberPerByte();
+    }
+
+    private int byteNumber() {
+        return id <= 7 ? 0 : 1;
+    }
+
+    /**
+     * Converts the given categories to an array of bytes.
+     * @param categories
+     * @return
+     */
+    public static byte[] toBitmask(AlertCategory... categories) {
+        byte[] result = new byte[2];
+
+        for (AlertCategory category : categories) {
+            result[category.byteNumber()] |= category.asBit();
+        }
+        return result;
+    }
+
+//    SupportedNewAlertCategory
+    public static AlertCategory[] fromBitMask(byte[] bytes) {
+        List<AlertCategory> result = new ArrayList<>();
+        byte b = bytes[0];
+
+        return null;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/alertnotification/AlertLevel.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/alertnotification/AlertLevel.java
new file mode 100644
index 0000000..0df96b4
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/alertnotification/AlertLevel.java
@@ -0,0 +1,43 @@
+/*  Copyright (C) 2016-2024 Carsten Pfeiffer
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotification;
+
+
+/**
+ * https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.alert_level.xml
+ */
+public enum AlertLevel {
+    NoAlert(0),
+    MildAlert(1),
+    HighAlert(2);
+    // 3-255 reserved
+
+    private final int id;
+
+    AlertLevel(int id) {
+        this.id = id;
+    }
+
+    /**
+     * The alert level ID
+     * To be used as uint8 value
+     * @return
+     */
+    public int getId() {
+        return id;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/alertnotification/AlertNotificationControl.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/alertnotification/AlertNotificationControl.java
new file mode 100644
index 0000000..60615fb
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/alertnotification/AlertNotificationControl.java
@@ -0,0 +1,54 @@
+/*  Copyright (C) 2016-2024 Carsten Pfeiffer
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotification;
+
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
+
+/**
+ * https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.alert_notification_control_point.xml
+ */
+public class AlertNotificationControl {
+    private AlertCategory category;
+    private Command command;
+
+    public void setCategory(AlertCategory category) {
+        this.category = category;
+    }
+
+    public void setCommand(Command command) {
+        this.command = command;
+    }
+
+    public AlertCategory getCategory() {
+        return category;
+    }
+
+    public Command getCommand() {
+        return command;
+    }
+
+    /**
+     * Returns the formatted message to be written to the alert notification control point
+     * characteristic
+     */
+    public byte[] getControlMessage() {
+        return new byte[] {
+                BLETypeConversions.fromUint8(command.getId()),
+                BLETypeConversions.fromUint8(category.getId())
+        };
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/alertnotification/AlertNotificationProfile.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/alertnotification/AlertNotificationProfile.java
new file mode 100644
index 0000000..23f7920
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/alertnotification/AlertNotificationProfile.java
@@ -0,0 +1,118 @@
+/*  Copyright (C) 2016-2024 Andreas Shimokawa, Carsten Pfeiffer
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotification;
+
+import android.bluetooth.BluetoothGattCharacteristic;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.GattCharacteristic;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.AbstractBleProfile;
+import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
+
+public class AlertNotificationProfile<T extends AbstractBTLEDeviceSupport> extends AbstractBleProfile<T> {
+    private static final Logger LOG = LoggerFactory.getLogger(AlertNotificationProfile.class);
+    private int maxLength = 18; // Mi2-ism?
+
+    public AlertNotificationProfile(T support) {
+        super(support);
+    }
+
+    public void setMaxLength(int maxLength) {
+        this.maxLength = maxLength;
+    }
+
+    public void configure(TransactionBuilder builder, AlertNotificationControl control) {
+        BluetoothGattCharacteristic characteristic = getCharacteristic(GattCharacteristic.UUID_CHARACTERISTIC_ALERT_NOTIFICATION_CONTROL_POINT);
+        if (characteristic != null) {
+            builder.write(characteristic, control.getControlMessage());
+        }
+    }
+
+    public void updateAlertLevel(TransactionBuilder builder, AlertLevel level) {
+        BluetoothGattCharacteristic characteristic = getCharacteristic(GattCharacteristic.UUID_CHARACTERISTIC_ALERT_LEVEL);
+        if (characteristic != null) {
+            builder.write(characteristic, new byte[] {BLETypeConversions.fromUint8(level.getId())});
+        }
+    }
+
+    public void newAlert(TransactionBuilder builder, NewAlert alert, OverflowStrategy strategy) {
+        BluetoothGattCharacteristic characteristic = getCharacteristic(GattCharacteristic.UUID_CHARACTERISTIC_NEW_ALERT);
+        if (characteristic != null) {
+            String message = StringUtils.ensureNotNull(alert.getMessage());
+            if (message.length() > maxLength && strategy == OverflowStrategy.TRUNCATE) {
+                message = StringUtils.truncate(message, maxLength);
+            }
+
+            int numChunks = message.length() / maxLength;
+            if (message.length() % maxLength > 0) {
+                numChunks++;
+            }
+
+            try {
+                boolean hasAlerted = false;
+                for (int i = 0; i < numChunks; i++) {
+                    int offset = i * maxLength;
+                    int restLength = message.length() - offset;
+                    message = message.substring(offset, offset + Math.min(maxLength, restLength));
+                    if (hasAlerted && message.length() == 0) {
+                        // no need to do it again when there is no text content
+                        break;
+                    }
+                    builder.write(characteristic, getAlertMessage(alert, message, 1));
+                    hasAlerted = true;
+                }
+                if (!hasAlerted) {
+                    builder.write(characteristic, getAlertMessage(alert, "", 1));
+                }
+            } catch (IOException ex) {
+                // ain't gonna happen
+                LOG.error("Error writing alert message to ByteArrayOutputStream");
+            }
+        } else {
+            LOG.warn("NEW_ALERT characteristic not available");
+        }
+    }
+
+    public void newAlert(TransactionBuilder builder, NewAlert alert) {
+        newAlert(builder, alert, OverflowStrategy.TRUNCATE);
+    }
+
+    protected byte[] getAlertMessage(NewAlert alert, String message, int chunk) throws IOException {
+        ByteArrayOutputStream stream = new ByteArrayOutputStream(100);
+        stream.write(BLETypeConversions.fromUint8(alert.getCategory().getId()));
+        stream.write(BLETypeConversions.fromUint8(alert.getNumAlerts()));
+        if (alert.getCategory() == AlertCategory.CustomHuami) {
+            stream.write(BLETypeConversions.fromUint8(alert.getCustomIcon()));
+        }
+
+        if (message.length() > 0) {
+            stream.write(BLETypeConversions.toUtf8s(message));
+        } else {
+            // some write a null byte instead of leaving out this optional value
+//                stream.write(new byte[] {0});
+        }
+        return stream.toByteArray();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/alertnotification/AlertStatus.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/alertnotification/AlertStatus.java
new file mode 100644
index 0000000..36f587a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/alertnotification/AlertStatus.java
@@ -0,0 +1,37 @@
+/*  Copyright (C) 2016-2024 Carsten Pfeiffer
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotification;
+
+/**
+ * https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.alert_status.xml
+ * uint8 value (bitmask) of the given values
+ */
+public class AlertStatus {
+    public static final int RINGER_ACTIVE_BIT = 1;
+    public static final int VIBRATE_ACTIVE = 1 << 1;
+    public static final int DISPLAY_ALERT_ACTIVE = 1 << 2;
+
+    public static boolean isRingerActive(int status) {
+        return (status & RINGER_ACTIVE_BIT) == RINGER_ACTIVE_BIT;
+    }
+    public static boolean isVibrateActive(int status) {
+        return (status & VIBRATE_ACTIVE) == VIBRATE_ACTIVE;
+    }
+    public static boolean isDisplayAlertActive(int status) {
+        return (status & DISPLAY_ALERT_ACTIVE) == DISPLAY_ALERT_ACTIVE;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/alertnotification/Command.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/alertnotification/Command.java
new file mode 100644
index 0000000..7e0908b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/alertnotification/Command.java
@@ -0,0 +1,40 @@
+/*  Copyright (C) 2017-2024 Carsten Pfeiffer
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotification;
+
+/**
+ * https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.alert_notification_control_point.xml
+ */
+public enum Command {
+    EnableNewIncomingAlertNotification(0),
+    EnableUnreadCategoryStatusNotification(1),
+    DisableNewIncomingAlertNotification(2),
+    DisbleUnreadCategoryStatusNotification(3),
+    NotifyNewIncomingAlertImmediately(4),
+    NotifyUnreadCategoryStatusImmediately(5),;
+    // 6-255 reserved for future use
+
+    private final int id;
+
+    Command(int id) {
+        this.id = id;
+    }
+
+    public int getId() {
+        return id;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/alertnotification/NewAlert.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/alertnotification/NewAlert.java
new file mode 100644
index 0000000..fa04035
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/alertnotification/NewAlert.java
@@ -0,0 +1,81 @@
+/*  Copyright (C) 2017-2024 Andreas Shimokawa, Carsten Pfeiffer, Daniele
+    Gobbetti
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotification;
+
+/**
+ * https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.new_alert.xml&u=org.bluetooth.characteristic.new_alert.xml
+ *
+ Recommended Usage for Text String Information Field in New Incoming Alert:
+
+ The usage of this text is up to the implementation, but the recommended text for the category is defined as following for best user experience:
+
+ Category: Simple Alert - The title of the alert
+
+ Category: Email - Sender name
+
+ Category: News - Title of the news feed
+
+ Category: Call - Caller name or caller ID
+
+ Category: Missed call - Caller name or caller ID
+
+ Category: SMS - Sender name or caller ID
+
+ Category: Voice mail - Sender name or caller ID
+
+ Category: Schedule - Title of the schedule
+
+ Category Hig:h Prioritized Aler - Title of the alert
+
+ Category: Instant Messaging - Sender name
+ */
+public class NewAlert {
+    private final AlertCategory category;
+    private final int numAlerts;
+    private final String message;
+    private byte customIcon = -1;
+
+    public NewAlert(AlertCategory category, int /*uint8*/ numAlerts, String /*utf8s*/ message) {
+        this.category = category;
+        this.numAlerts = numAlerts;
+        this.message = message;
+    }
+
+    public NewAlert(AlertCategory category, int /*uint8*/ numAlerts, String /*utf8s*/ message, byte customIcon) {
+        this.category = category;
+        this.numAlerts = numAlerts;
+        this.message = message;
+        this.customIcon = customIcon;
+    }
+
+    public AlertCategory getCategory() {
+        return category;
+    }
+
+    public int getNumAlerts() {
+        return numAlerts;
+    }
+
+    public String getMessage() {
+        return message;
+    }
+
+    public byte getCustomIcon() {
+        return customIcon;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/alertnotification/OverflowStrategy.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/alertnotification/OverflowStrategy.java
new file mode 100644
index 0000000..9aee0ff
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/alertnotification/OverflowStrategy.java
@@ -0,0 +1,22 @@
+/*  Copyright (C) 2017-2024 Carsten Pfeiffer
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotification;
+
+public enum OverflowStrategy {
+    TRUNCATE,
+    MAKE_MULTIPLE
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/alertnotification/SupportedNewAlertCategory.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/alertnotification/SupportedNewAlertCategory.java
new file mode 100644
index 0000000..e5734a4
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/alertnotification/SupportedNewAlertCategory.java
@@ -0,0 +1,74 @@
+/*  Copyright (C) 2016-2024 Carsten Pfeiffer, Uwe Hermann
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotification;
+
+/**
+ * https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.alert_category_id.xml
+ * https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.alert_category_id_bit_mask.xml
+ */
+public class SupportedNewAlertCategory {
+    private final int id;
+//
+//    public static Ca(byte[] categoryBytes) {
+//
+//    }
+
+    public SupportedNewAlertCategory(int id) {
+        this.id = id;
+    }
+
+    /**
+     * Returns the numerical ID value of this category
+     * To be used as uint8 value
+     * @return the uint8 value for this category
+     */
+    public int getId() {
+        return id;
+    }
+
+    private int realBitNumber() {
+        // the ID corresponds to the bit for the bitset
+        return id;
+    }
+
+    private int bitNumberPerByte() {
+        // the ID corresponds to the bit for the bitset (per byte)
+        return realBitNumber() % 8;
+    }
+
+    private int asBit() {
+        return 1 << bitNumberPerByte();
+    }
+
+    private int byteNumber() {
+        return id <= 7 ? 0 : 1;
+    }
+
+    /**
+     * Converts the given categories to an array of bytes.
+     * @param categories
+     * @return
+     */
+    public static byte[] toBitmask(SupportedNewAlertCategory... categories) {
+        byte[] result = new byte[2];
+
+        for (SupportedNewAlertCategory category : categories) {
+            result[category.byteNumber()] |= category.asBit();
+        }
+        return result;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/battery/BatteryInfo.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/battery/BatteryInfo.java
new file mode 100644
index 0000000..98d70eb
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/battery/BatteryInfo.java
@@ -0,0 +1,62 @@
+/*  Copyright (C) 2016-2024 Carsten Pfeiffer
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.battery;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+public class BatteryInfo implements Parcelable{
+
+    private int percentCharged;
+
+    public BatteryInfo() {
+    }
+
+    protected BatteryInfo(Parcel in) {
+        percentCharged = in.readInt();
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(percentCharged);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final Creator<BatteryInfo> CREATOR = new Creator<BatteryInfo>() {
+        @Override
+        public BatteryInfo createFromParcel(Parcel in) {
+            return new BatteryInfo(in);
+        }
+
+        @Override
+        public BatteryInfo[] newArray(int size) {
+            return new BatteryInfo[size];
+        }
+    };
+
+    public int getPercentCharged() {
+        return percentCharged;
+    }
+
+    public void setPercentCharged(int percentCharged) {
+        this.percentCharged = percentCharged;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/battery/BatteryInfoProfile.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/battery/BatteryInfoProfile.java
new file mode 100644
index 0000000..324a983
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/battery/BatteryInfoProfile.java
@@ -0,0 +1,94 @@
+/*  Copyright (C) 2016-2024 Andreas Shimokawa, Carsten Pfeiffer
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.battery;
+
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.content.Intent;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.GattCharacteristic;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.GattService;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.AbstractBleProfile;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.ValueDecoder;
+
+public class BatteryInfoProfile<T extends AbstractBTLEDeviceSupport> extends AbstractBleProfile {
+    private static final Logger LOG = LoggerFactory.getLogger(BatteryInfoProfile.class);
+
+    private static final String ACTION_PREFIX = BatteryInfoProfile.class.getName() + "_";
+
+    public static final String ACTION_BATTERY_INFO = ACTION_PREFIX + "BATTERY_INFO";
+    public static final String EXTRA_BATTERY_INFO = "BATTERY_INFO";
+
+    public static final UUID SERVICE_UUID = GattService.UUID_SERVICE_BATTERY_SERVICE;
+
+    public static final UUID UUID_CHARACTERISTIC_BATTERY_LEVEL = GattCharacteristic.UUID_CHARACTERISTIC_BATTERY_LEVEL;
+    private final BatteryInfo batteryInfo = new BatteryInfo();
+
+    public BatteryInfoProfile(T support) {
+        super(support);
+    }
+
+    public void requestBatteryInfo(TransactionBuilder builder) {
+        builder.read(getCharacteristic(UUID_CHARACTERISTIC_BATTERY_LEVEL));
+    }
+
+    @Override
+    public void enableNotify(TransactionBuilder builder, boolean enable) {
+        builder.notify(getCharacteristic(BatteryInfoProfile.UUID_CHARACTERISTIC_BATTERY_LEVEL), enable);
+    }
+
+    @Override
+    public boolean onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
+        if (status == BluetoothGatt.GATT_SUCCESS) {
+            UUID charUuid = characteristic.getUuid();
+            if (charUuid.equals(UUID_CHARACTERISTIC_BATTERY_LEVEL)) {
+                handleBatteryLevel(gatt, characteristic);
+                return true;
+            } else {
+                LOG.info("Unexpected onCharacteristicRead: " + GattCharacteristic.toString(characteristic));
+            }
+        } else {
+            LOG.warn("error reading from characteristic:" + GattCharacteristic.toString(characteristic));
+        }
+        return false;
+    }
+
+    @Override
+    public boolean onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
+        return onCharacteristicRead(gatt, characteristic, BluetoothGatt.GATT_SUCCESS);
+    }
+
+    private void handleBatteryLevel(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
+        int percent = ValueDecoder.decodePercent(characteristic);
+        batteryInfo.setPercentCharged(percent);
+
+        notify(createIntent(batteryInfo));
+    }
+
+    private Intent createIntent(BatteryInfo batteryInfo) {
+        Intent intent = new Intent(ACTION_BATTERY_INFO);
+        intent.putExtra(EXTRA_BATTERY_INFO, batteryInfo);
+        return intent;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/deviceinfo/DeviceInfo.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/deviceinfo/DeviceInfo.java
new file mode 100644
index 0000000..67c4505
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/deviceinfo/DeviceInfo.java
@@ -0,0 +1,164 @@
+/*  Copyright (C) 2016-2024 Carsten Pfeiffer
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+public class DeviceInfo implements Parcelable{
+    private String manufacturerName;
+    private String modelNumber;
+    private String serialNumber;
+    private String hardwareRevision;
+    private String firmwareRevision;
+    private String softwareRevision;
+    private String systemId;
+    private String regulatoryCertificationDataList;
+    private String pnpId;
+
+    public DeviceInfo() {
+    }
+
+    protected DeviceInfo(Parcel in) {
+        manufacturerName = in.readString();
+        modelNumber = in.readString();
+        serialNumber = in.readString();
+        hardwareRevision = in.readString();
+        firmwareRevision = in.readString();
+        softwareRevision = in.readString();
+        systemId = in.readString();
+        regulatoryCertificationDataList = in.readString();
+        pnpId = in.readString();
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(manufacturerName);
+        dest.writeString(modelNumber);
+        dest.writeString(serialNumber);
+        dest.writeString(hardwareRevision);
+        dest.writeString(firmwareRevision);
+        dest.writeString(softwareRevision);
+        dest.writeString(systemId);
+        dest.writeString(regulatoryCertificationDataList);
+        dest.writeString(pnpId);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final Creator<DeviceInfo> CREATOR = new Creator<DeviceInfo>() {
+        @Override
+        public DeviceInfo createFromParcel(Parcel in) {
+            return new DeviceInfo(in);
+        }
+
+        @Override
+        public DeviceInfo[] newArray(int size) {
+            return new DeviceInfo[size];
+        }
+    };
+
+    public String getManufacturerName() {
+        return manufacturerName;
+    }
+
+    public void setManufacturerName(String manufacturerName) {
+        this.manufacturerName = manufacturerName;
+    }
+
+    public String getModelNumber() {
+        return modelNumber;
+    }
+
+    public void setModelNumber(String modelNumber) {
+        this.modelNumber = modelNumber;
+    }
+
+    public String getSerialNumber() {
+        return serialNumber;
+    }
+
+    public void setSerialNumber(String serialNumber) {
+        this.serialNumber = serialNumber;
+    }
+
+    public String getHardwareRevision() {
+        return hardwareRevision;
+    }
+
+    public void setHardwareRevision(String hardwareRevision) {
+        this.hardwareRevision = hardwareRevision;
+    }
+
+    public String getFirmwareRevision() {
+        return firmwareRevision;
+    }
+
+    public void setFirmwareRevision(String firmwareRevision) {
+        this.firmwareRevision = firmwareRevision;
+    }
+
+    public String getSoftwareRevision() {
+        return softwareRevision;
+    }
+
+    public void setSoftwareRevision(String softwareRevision) {
+        this.softwareRevision = softwareRevision;
+    }
+
+    public String getSystemId() {
+        return systemId;
+    }
+
+    public void setSystemId(String systemId) {
+        this.systemId = systemId;
+    }
+
+    public String getRegulatoryCertificationDataList() {
+        return regulatoryCertificationDataList;
+    }
+
+    public void setRegulatoryCertificationDataList(String regulatoryCertificationDataList) {
+        this.regulatoryCertificationDataList = regulatoryCertificationDataList;
+    }
+
+    public String getPnpId() {
+        return pnpId;
+    }
+
+    public void setPnpId(String pnpId) {
+        this.pnpId = pnpId;
+    }
+
+    @Override
+    public String toString() {
+        return "DeviceInfo{" +
+                "manufacturerName='" + manufacturerName + '\'' +
+                ", modelNumber='" + modelNumber + '\'' +
+                ", serialNumber='" + serialNumber + '\'' +
+                ", hardwareRevision='" + hardwareRevision + '\'' +
+                ", firmwareRevision='" + firmwareRevision + '\'' +
+                ", softwareRevision='" + softwareRevision + '\'' +
+                ", systemId='" + systemId + '\'' +
+                ", regulatoryCertificationDataList='" + regulatoryCertificationDataList + '\'' +
+                ", pnpId='" + pnpId + '\'' +
+                '}';
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/deviceinfo/DeviceInfoProfile.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/deviceinfo/DeviceInfoProfile.java
new file mode 100644
index 0000000..1f8a4bc
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/deviceinfo/DeviceInfoProfile.java
@@ -0,0 +1,186 @@
+/*  Copyright (C) 2016-2024 Andreas Shimokawa, Carsten Pfeiffer
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo;
+
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.content.Intent;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.GattCharacteristic;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.GattService;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.AbstractBleProfile;
+
+public class DeviceInfoProfile<T extends AbstractBTLEDeviceSupport> extends AbstractBleProfile<T> {
+    private static final Logger LOG = LoggerFactory.getLogger(DeviceInfoProfile.class);
+
+    private static final String ACTION_PREFIX = DeviceInfoProfile.class.getName() + "_";
+
+    public static final String ACTION_DEVICE_INFO = ACTION_PREFIX + "DEVICE_INFO";
+    public static final String EXTRA_DEVICE_INFO = "DEVICE_INFO";
+
+    public static final UUID SERVICE_UUID = GattService.UUID_SERVICE_DEVICE_INFORMATION;
+
+    public static final UUID UUID_CHARACTERISTIC_MANUFACTURER_NAME_STRING = GattCharacteristic.UUID_CHARACTERISTIC_MANUFACTURER_NAME_STRING;
+    public static final UUID UUID_CHARACTERISTIC_MODEL_NUMBER_STRING = GattCharacteristic.UUID_CHARACTERISTIC_MODEL_NUMBER_STRING;
+    public static final UUID UUID_CHARACTERISTIC_SERIAL_NUMBER_STRING = GattCharacteristic.UUID_CHARACTERISTIC_SERIAL_NUMBER_STRING;
+    public static final UUID UUID_CHARACTERISTIC_HARDWARE_REVISION_STRING = GattCharacteristic.UUID_CHARACTERISTIC_HARDWARE_REVISION_STRING;
+    public static final UUID UUID_CHARACTERISTIC_FIRMWARE_REVISION_STRING = GattCharacteristic.UUID_CHARACTERISTIC_FIRMWARE_REVISION_STRING;
+    public static final UUID UUID_CHARACTERISTIC_SOFTWARE_REVISION_STRING = GattCharacteristic.UUID_CHARACTERISTIC_SOFTWARE_REVISION_STRING;
+    public static final UUID UUID_CHARACTERISTIC_SYSTEM_ID = GattCharacteristic.UUID_CHARACTERISTIC_SYSTEM_ID;
+    public static final UUID UUID_CHARACTERISTIC_IEEE_11073_20601_REGULATORY_CERTIFICATION_DATA_LIST = GattCharacteristic.UUID_CHARACTERISTIC_IEEE_11073_20601_REGULATORY_CERTIFICATION_DATA_LIST;
+    public static final UUID UUID_CHARACTERISTIC_PNP_ID = GattCharacteristic.UUID_CHARACTERISTIC_PNP_ID;
+    private final DeviceInfo deviceInfo = new DeviceInfo();
+
+    public DeviceInfoProfile(final T support) {
+        super(support);
+    }
+
+    public void requestDeviceInfo(final TransactionBuilder builder) {
+        builder.read(getCharacteristic(UUID_CHARACTERISTIC_MANUFACTURER_NAME_STRING))
+                .read(getCharacteristic(UUID_CHARACTERISTIC_MODEL_NUMBER_STRING))
+                .read(getCharacteristic(UUID_CHARACTERISTIC_SERIAL_NUMBER_STRING))
+                .read(getCharacteristic(UUID_CHARACTERISTIC_HARDWARE_REVISION_STRING))
+                .read(getCharacteristic(UUID_CHARACTERISTIC_FIRMWARE_REVISION_STRING))
+                .read(getCharacteristic(UUID_CHARACTERISTIC_SOFTWARE_REVISION_STRING))
+                .read(getCharacteristic(UUID_CHARACTERISTIC_SYSTEM_ID))
+                .read(getCharacteristic(UUID_CHARACTERISTIC_IEEE_11073_20601_REGULATORY_CERTIFICATION_DATA_LIST))
+                .read(getCharacteristic(UUID_CHARACTERISTIC_PNP_ID));
+    }
+
+    @Override
+    public boolean onCharacteristicRead(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic, final int status) {
+        final UUID charUuid = characteristic.getUuid();
+
+        if (status == BluetoothGatt.GATT_SUCCESS) {
+            if (charUuid.equals(UUID_CHARACTERISTIC_MANUFACTURER_NAME_STRING)) {
+                handleManufacturerName(characteristic);
+                return true;
+            } else if (charUuid.equals(UUID_CHARACTERISTIC_MODEL_NUMBER_STRING)) {
+                handleModelNumber(characteristic);
+                return true;
+            } else if (charUuid.equals(UUID_CHARACTERISTIC_SERIAL_NUMBER_STRING)) {
+                handleSerialNumber(characteristic);
+                return true;
+            } else if (charUuid.equals(UUID_CHARACTERISTIC_HARDWARE_REVISION_STRING)) {
+                handleHardwareRevision(characteristic);
+                return true;
+            } else if (charUuid.equals(UUID_CHARACTERISTIC_FIRMWARE_REVISION_STRING)) {
+                handleFirmwareRevision(characteristic);
+                return true;
+            } else if (charUuid.equals(UUID_CHARACTERISTIC_SOFTWARE_REVISION_STRING)) {
+                handleSoftwareRevision(characteristic);
+                return true;
+            } else if (charUuid.equals(UUID_CHARACTERISTIC_SYSTEM_ID)) {
+                handleSystemId(characteristic);
+                return true;
+            } else if (charUuid.equals(UUID_CHARACTERISTIC_IEEE_11073_20601_REGULATORY_CERTIFICATION_DATA_LIST)) {
+                handleRegulatoryCertificationData(characteristic);
+                return true;
+            } else if (charUuid.equals(UUID_CHARACTERISTIC_PNP_ID)) {
+                handlePnpId(characteristic);
+                return true;
+            }
+        } else {
+            if (charUuid.equals(UUID_CHARACTERISTIC_MANUFACTURER_NAME_STRING) ||
+                    charUuid.equals(UUID_CHARACTERISTIC_MODEL_NUMBER_STRING) ||
+                    charUuid.equals(UUID_CHARACTERISTIC_SERIAL_NUMBER_STRING) ||
+                    charUuid.equals(UUID_CHARACTERISTIC_HARDWARE_REVISION_STRING) ||
+                    charUuid.equals(UUID_CHARACTERISTIC_FIRMWARE_REVISION_STRING) ||
+                    charUuid.equals(UUID_CHARACTERISTIC_SOFTWARE_REVISION_STRING) ||
+                    charUuid.equals(UUID_CHARACTERISTIC_SYSTEM_ID) ||
+                    charUuid.equals(UUID_CHARACTERISTIC_IEEE_11073_20601_REGULATORY_CERTIFICATION_DATA_LIST) ||
+                    charUuid.equals(UUID_CHARACTERISTIC_PNP_ID)) {
+                LOG.warn("error reading from characteristic: {}, status={}", GattCharacteristic.toString(characteristic), status);
+            }
+        }
+        return false;
+    }
+
+    private void handleManufacturerName(final BluetoothGattCharacteristic characteristic) {
+        String name = characteristic.getStringValue(0).trim();
+        deviceInfo.setManufacturerName(name);
+        notify(createIntent(deviceInfo));
+    }
+
+    private void handleModelNumber(final BluetoothGattCharacteristic characteristic) {
+        String modelNumber = characteristic.getStringValue(0).trim();
+        deviceInfo.setModelNumber(modelNumber);
+        notify(createIntent(deviceInfo));
+    }
+
+    private void handleSerialNumber(final BluetoothGattCharacteristic characteristic) {
+        String serialNumber = characteristic.getStringValue(0).trim();
+        deviceInfo.setSerialNumber(serialNumber);
+        notify(createIntent(deviceInfo));
+    }
+
+    private void handleHardwareRevision(final BluetoothGattCharacteristic characteristic) {
+        String hardwareRevision = characteristic.getStringValue(0).trim();
+        deviceInfo.setHardwareRevision(hardwareRevision);
+        notify(createIntent(deviceInfo));
+    }
+
+    private void handleFirmwareRevision(final BluetoothGattCharacteristic characteristic) {
+        String firmwareRevision = characteristic.getStringValue(0).trim();
+        deviceInfo.setFirmwareRevision(firmwareRevision);
+        notify(createIntent(deviceInfo));
+    }
+
+    private void handleSoftwareRevision(final BluetoothGattCharacteristic characteristic) {
+        String softwareRevision = characteristic.getStringValue(0).trim();
+        deviceInfo.setSoftwareRevision(softwareRevision);
+        notify(createIntent(deviceInfo));
+    }
+
+    private void handleSystemId(final BluetoothGattCharacteristic characteristic) {
+        String systemId = characteristic.getStringValue(0).trim();
+        deviceInfo.setSystemId(systemId);
+        notify(createIntent(deviceInfo));
+    }
+
+    private void handleRegulatoryCertificationData(final BluetoothGattCharacteristic characteristic) {
+        // TODO: regulatory certification data list not supported yet
+//        String regulatoryCertificationData = characteristic.getStringValue(0).trim();
+//        deviceInfo.setRegulatoryCertificationDataList(regulatoryCertificationData);
+//        notify(createIntent(deviceInfo));
+    }
+
+    private void handlePnpId(final BluetoothGattCharacteristic characteristic) {
+        byte[] value = characteristic.getValue();
+        if (value.length == 7) {
+//            int vendorSource
+//
+//            deviceInfo.setPnpId(pnpId);
+            notify(createIntent(deviceInfo));
+        } else {
+            // TODO: LOG warning
+        }
+    }
+
+    private Intent createIntent(final DeviceInfo deviceInfo) {
+        final Intent intent = new Intent(ACTION_DEVICE_INFO);
+        intent.putExtra(EXTRA_DEVICE_INFO, deviceInfo); // TODO: broadcast a clone of the info
+        return intent;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/healthThermometer/HealthThermometerProfile.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/healthThermometer/HealthThermometerProfile.java
new file mode 100644
index 0000000..45cf1c5
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/healthThermometer/HealthThermometerProfile.java
@@ -0,0 +1,140 @@
+/*  Copyright (C) 2023-2024 Alicia Hormann
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.healthThermometer;
+
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.content.Intent;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.GattCharacteristic;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.GattService;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.AbstractBleProfile;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.ValueDecoder;
+
+/***
+ * This class handles the HealthThermometer as implemented on the Femometer Vinca II.
+ * This might or might not be up to GATT standard.
+ * @param <T>
+ */
+public class HealthThermometerProfile <T extends AbstractBTLEDeviceSupport> extends AbstractBleProfile<T> {
+    private static final Logger LOG = LoggerFactory.getLogger(HealthThermometerProfile.class);
+
+    private static final String ACTION_PREFIX = HealthThermometerProfile.class.getName() + "_";
+
+    public static final String ACTION_TEMPERATURE_INFO = ACTION_PREFIX + "TEMPERATURE_INFO";
+    public static final String EXTRA_TEMPERATURE_INFO = "TEMPERATURE_INFO";
+
+    public static final UUID SERVICE_UUID = GattService.UUID_SERVICE_HEALTH_THERMOMETER;
+    public static final UUID UUID_CHARACTERISTIC_TEMPERATURE_MEASUREMENT = GattCharacteristic.UUID_CHARACTERISTIC_TEMPERATURE_MEASUREMENT;
+    public static final UUID UUID_CHARACTERISTIC_MEASUREMENT_INTERVAL = GattCharacteristic.UUID_CHARACTERISTIC_MEASUREMENT_INTERVAL;
+    private final TemperatureInfo temperatureInfo = new TemperatureInfo();
+
+    public HealthThermometerProfile(T support) {
+        super(support);
+    }
+
+    public void requestMeasurementInterval(TransactionBuilder builder) {
+        builder.read(getCharacteristic(UUID_CHARACTERISTIC_MEASUREMENT_INTERVAL));
+    }
+
+    public void setMeasurementInterval(TransactionBuilder builder, byte[] value) {
+        builder.write(getCharacteristic(GattCharacteristic.UUID_CHARACTERISTIC_MEASUREMENT_INTERVAL), value);
+    }
+
+    @Override
+    public void enableNotify(TransactionBuilder builder, boolean enable) {
+        builder.notify(getCharacteristic(UUID_CHARACTERISTIC_TEMPERATURE_MEASUREMENT), enable);
+    }
+
+    @Override
+    public boolean onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
+        if (status == BluetoothGatt.GATT_SUCCESS) {
+            UUID charUuid = characteristic.getUuid();
+            if (charUuid.equals(UUID_CHARACTERISTIC_MEASUREMENT_INTERVAL)) {
+                handleMeasurementInterval(gatt, characteristic);
+                return true;
+            } else if (charUuid.equals(UUID_CHARACTERISTIC_TEMPERATURE_MEASUREMENT)) {
+                handleTemperatureMeasurement(gatt, characteristic);
+                return true;
+            } else {
+                LOG.info("Unexpected onCharacteristicRead: " + GattCharacteristic.toString(characteristic));
+            }
+        } else {
+            LOG.warn("error reading from characteristic:" + GattCharacteristic.toString(characteristic));
+        }
+        return false;
+    }
+
+    @Override
+    public boolean onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
+        return onCharacteristicRead(gatt, characteristic, BluetoothGatt.GATT_SUCCESS);
+    }
+
+
+    private void handleMeasurementInterval(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
+        // todo: not implemented
+        LOG.debug("Health thermometer received Measurement Interval: " + ValueDecoder.decodeInt(characteristic));
+    }
+
+    private void handleTemperatureMeasurement(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
+        /*
+         * This metadata contains as bits:
+         * the unit (celsius (0) or fahrenheit (1)) (bit 7 (last bit))
+         * if a timestamp is present (1) or not present (0) (bit 6)
+         * if a temperature type is present (1) or not present (0) (bit 5)
+         */
+        byte metadata = characteristic.getValue()[0];
+        // todo: evaluate this byte to enable support for devices without timestamp or temperature-type
+
+        int year = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, 5);
+        int month = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 7);
+        int day = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 8);
+        int hour = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 9);
+        int minute = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 10);
+        int second = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 11);
+
+        Calendar c = GregorianCalendar.getInstance();
+        c.set(year, month - 1, day, hour, minute, second);
+        Date date = c.getTime();
+
+        float temperature = characteristic.getFloatValue(BluetoothGattCharacteristic.FORMAT_FLOAT, 1); // bytes 1 - 4
+        int temperature_type = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 12); // encodes where the measurement was taken
+
+        LOG.debug("Received measurement of " + temperature + "掳 with Timestamp " + date + ", metadata is " + Integer.toBinaryString((metadata & 0xFF) + 0x100).substring(1));
+
+        temperatureInfo.setTemperature(temperature);
+        temperatureInfo.setTemperatureType(temperature_type);
+        temperatureInfo.setTimestamp(date);
+        notify(createIntent(temperatureInfo));
+    }
+
+    private Intent createIntent(TemperatureInfo temperatureInfo) {
+        Intent intent = new Intent(ACTION_TEMPERATURE_INFO);
+        intent.putExtra(EXTRA_TEMPERATURE_INFO, temperatureInfo);
+        return intent;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/healthThermometer/TemperatureInfo.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/healthThermometer/TemperatureInfo.java
new file mode 100644
index 0000000..f17d0e7
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/healthThermometer/TemperatureInfo.java
@@ -0,0 +1,84 @@
+/*  Copyright (C) 2023-2024 Alicia Hormann
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.healthThermometer;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Date;
+
+public class TemperatureInfo implements Parcelable{
+
+    private float temperature;
+    private int temperatureType;
+    private Date timestamp;
+
+    public TemperatureInfo() {
+    }
+
+    protected TemperatureInfo(Parcel in) {
+        timestamp = new Date(in.readLong());
+        temperature = in.readFloat();
+        temperatureType = in.readInt();
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeLong(timestamp.getTime());
+        dest.writeFloat(temperature);
+        dest.writeInt(temperatureType);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final Creator<TemperatureInfo> CREATOR = new Creator<TemperatureInfo>() {
+        @Override
+        public TemperatureInfo createFromParcel(Parcel in) {
+            return new TemperatureInfo(in);
+        }
+
+        @Override
+        public TemperatureInfo[] newArray(int size) {
+            return new TemperatureInfo[size];
+        }
+    };
+
+    public float getTemperature() {
+        return temperature;
+    }
+    public Date getTimestamp() {
+        return timestamp;
+    }
+    public void setTimestamp(Date date) {
+        timestamp = date;
+    }
+
+    public void setTemperature(float temperature) {
+        this.temperature = temperature;
+    }
+
+    public int getTemperatureType() {
+        return temperatureType;
+    }
+
+    public void setTemperatureType(int temperatureType) {
+        this.temperatureType = temperatureType;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/heartrate/BodySensorLocation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/heartrate/BodySensorLocation.java
new file mode 100644
index 0000000..e3ff66d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/heartrate/BodySensorLocation.java
@@ -0,0 +1,39 @@
+/*  Copyright (C) 2016-2024 Carsten Pfeiffer, Uwe Hermann
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.heartrate;
+
+/**
+ * The Body Sensor Location characteristic of the device is used to describe the intended location of the heart rate measurement for the device.
+ *
+ * https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.body_sensor_location.xml
+ */
+public enum BodySensorLocation {
+    Other(0),
+    Chest(1),
+    Wrist(2),
+    Finger(3),
+    Hand(4),
+    EarLobe(5),
+    Foot(6);
+    // others are reserved
+
+    private final int val;
+
+    BodySensorLocation(int val) {
+        this.val = val;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/heartrate/HeartRateProfile.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/heartrate/HeartRateProfile.java
new file mode 100644
index 0000000..53e1ec9
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/heartrate/HeartRateProfile.java
@@ -0,0 +1,77 @@
+/*  Copyright (C) 2016-2024 Andreas Shimokawa, Carsten Pfeiffer, Daniele
+    Gobbetti
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.heartrate;
+
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.GattCharacteristic;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.AbstractBleProfile;
+
+/**
+ * https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.service.heart_rate.xml
+ */
+public class HeartRateProfile<T extends AbstractBTLEDeviceSupport> extends AbstractBleProfile<T> {
+    private static final Logger LOG = LoggerFactory.getLogger(HeartRateProfile.class);
+
+    /**
+     * Returned when a request to the heart rate control point is not supported by the device
+     */
+    public static final int ERR_CONTROL_POINT_NOT_SUPPORTED = 0x80;
+
+    public HeartRateProfile(T support) {
+        super(support);
+    }
+
+    public void resetEnergyExpended(TransactionBuilder builder) {
+        writeToControlPoint((byte) 0x01, builder);
+    }
+
+    protected void writeToControlPoint(byte value, TransactionBuilder builder) {
+        writeToControlPoint(new byte[] { value }, builder);
+    }
+
+    protected void writeToControlPoint(byte[] value, TransactionBuilder builder) {
+        builder.write(getCharacteristic(GattCharacteristic.UUID_CHARACTERISTIC_HEART_RATE_CONTROL_POINT), value);
+    }
+
+    public void requestBodySensorLocation(TransactionBuilder builder) {
+
+    }
+
+    @Override
+    public boolean onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
+        if (GattCharacteristic.UUID_CHARACTERISTIC_HEART_RATE_MEASUREMENT.equals(characteristic.getUuid())) {
+            int flag = characteristic.getProperties();
+            int format = -1;
+            if ((flag & 0x01) != 0) {
+                format = BluetoothGattCharacteristic.FORMAT_UINT16;
+            } else {
+                format = BluetoothGattCharacteristic.FORMAT_UINT8;
+            }
+            final int heartRate = characteristic.getIntValue(format, 1);
+            LOG.info("Heart rate: " + heartRate);
+        }
+        return false;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/asteroidos/AsteroidOSDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/asteroidos/AsteroidOSDeviceSupport.java
new file mode 100644
index 0000000..bae9b9e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/asteroidos/AsteroidOSDeviceSupport.java
@@ -0,0 +1,291 @@
+/*  Copyright (C) 2022-2024 Noodlez
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.asteroidos;
+
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+
+import androidx.annotation.NonNull;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.ByteArrayOutputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.Objects;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventMusicControl;
+import nodomain.freeyourgadget.gadgetbridge.devices.asteroidos.AsteroidOSConstants;
+import nodomain.freeyourgadget.gadgetbridge.devices.asteroidos.AsteroidOSMediaCommand;
+import nodomain.freeyourgadget.gadgetbridge.devices.asteroidos.AsteroidOSNotification;
+import nodomain.freeyourgadget.gadgetbridge.devices.asteroidos.AsteroidOSWeather;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+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.BLETypeConversions;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.GattService;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.IntentListener;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.battery.BatteryInfoProfile;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.SetTimeRequest;
+
+public class AsteroidOSDeviceSupport extends AbstractBTLEDeviceSupport {
+    private static final Logger LOG = LoggerFactory.getLogger(AsteroidOSDeviceSupport.class);
+    private final BatteryInfoProfile<AsteroidOSDeviceSupport> batteryInfoProfile;
+    private final GBDeviceEventBatteryInfo batteryCmd = new GBDeviceEventBatteryInfo();
+
+    public AsteroidOSDeviceSupport() {
+        super(LOG);
+        addSupportedService(AsteroidOSConstants.SERVICE_UUID);
+        addSupportedService(AsteroidOSConstants.TIME_SERVICE_UUID);
+        addSupportedService(GattService.UUID_SERVICE_BATTERY_SERVICE);
+        addSupportedService(AsteroidOSConstants.WEATHER_SERVICE_UUID);
+        addSupportedService(AsteroidOSConstants.NOTIFICATION_SERVICE_UUID);
+        addSupportedService(AsteroidOSConstants.MEDIA_SERVICE_UUID);
+
+        IntentListener mListener = intent -> {
+            String action = intent.getAction();
+            if (BatteryInfoProfile.ACTION_BATTERY_INFO.equals(action)) {
+                handleBatteryInfo(Objects.requireNonNull(intent.getParcelableExtra(BatteryInfoProfile.EXTRA_BATTERY_INFO)));
+            }
+        };
+
+        batteryInfoProfile = new BatteryInfoProfile<>(this);
+        batteryInfoProfile.addListener(mListener);
+        addSupportedProfile(batteryInfoProfile);
+    }
+
+    private void handleBatteryInfo(@NonNull nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.battery.BatteryInfo info) {
+        batteryCmd.level = info.getPercentCharged();
+        handleGBDeviceEvent(batteryCmd);
+    }
+
+    public boolean onCharacteristicChanged(BluetoothGatt gatt,
+                                           BluetoothGattCharacteristic characteristic) {
+        super.onCharacteristicChanged(gatt, characteristic);
+
+        UUID characteristicUUID = characteristic.getUuid();
+
+        if (characteristicUUID.equals(AsteroidOSConstants.MEDIA_COMMANDS_CHAR)) {
+            handleMediaCommand(characteristic);
+            return true;
+        }
+
+        LOG.info("Characteristic changed UUID: " + characteristicUUID);
+        LOG.info("Characteristic changed value: " + Arrays.toString(characteristic.getValue()));
+        return false;
+    }
+
+
+    @Override
+    protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
+        builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext()));
+        getDevice().setFirmwareVersion("N/A");
+        getDevice().setFirmwareVersion2("N/A");
+
+        builder.notify(getCharacteristic(AsteroidOSConstants.MEDIA_COMMANDS_CHAR), true);
+        builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZED, getContext()));
+
+        batteryInfoProfile.requestBatteryInfo(builder);
+        batteryInfoProfile.enableNotify(builder, true);
+        // Gadgetbridge doesn't seem to do this itself, so we force it to set its time
+        onSetTime();
+        return builder;
+    }
+
+    @Override
+    public void onNotification(NotificationSpec notificationSpec) {
+        AsteroidOSNotification notif = new AsteroidOSNotification(notificationSpec);
+        TransactionBuilder builder = new TransactionBuilder("send notification");
+        safeWriteToCharacteristic(builder, AsteroidOSConstants.NOTIFICATION_UPDATE_CHAR, notif.toString().getBytes(StandardCharsets.UTF_8));
+        builder.queue(getQueue());
+    }
+
+    @Override
+    public void onDeleteNotification(int id) {
+        AsteroidOSNotification notif = new AsteroidOSNotification(id);
+        TransactionBuilder builder = new TransactionBuilder("delete notification");
+        safeWriteToCharacteristic(builder, AsteroidOSConstants.NOTIFICATION_UPDATE_CHAR, notif.toString().getBytes(StandardCharsets.UTF_8));
+        builder.queue(getQueue());
+    }
+
+    @Override
+    public void onSetTime() {
+        GregorianCalendar now = BLETypeConversions.createCalendar();
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        baos.write((byte) now.get(Calendar.YEAR) - 1900);
+        baos.write((byte) now.get(Calendar.MONTH));
+        baos.write((byte) now.get(Calendar.DAY_OF_MONTH));
+        baos.write((byte) now.get(Calendar.HOUR_OF_DAY));
+        baos.write((byte) now.get(Calendar.MINUTE));
+        baos.write((byte) now.get(Calendar.SECOND));
+        TransactionBuilder builder = new TransactionBuilder("set time");
+        safeWriteToCharacteristic(builder, AsteroidOSConstants.TIME_SET_CHAR, baos.toByteArray());
+        builder.queue(getQueue());
+    }
+
+
+    @Override
+    public void onSetCallState(CallSpec callSpec) {
+        AsteroidOSNotification call = new AsteroidOSNotification(callSpec);
+        TransactionBuilder builder = new TransactionBuilder("send call");
+        safeWriteToCharacteristic(builder, AsteroidOSConstants.NOTIFICATION_UPDATE_CHAR, call.toString().getBytes(StandardCharsets.UTF_8));
+        builder.queue(getQueue());
+    }
+
+    @Override
+    public void onSetMusicState(MusicStateSpec stateSpec) {
+        TransactionBuilder builder = new TransactionBuilder("set music state");
+        if (stateSpec.state == MusicStateSpec.STATE_PLAYING) {
+            safeWriteToCharacteristic(builder, AsteroidOSConstants.MEDIA_PLAYING_CHAR, new byte[]{1});
+        } else {
+            safeWriteToCharacteristic(builder, AsteroidOSConstants.MEDIA_PLAYING_CHAR, new byte[]{0});
+        }
+        builder.queue(getQueue());
+    }
+
+    @Override
+    public void onSetMusicInfo(MusicSpec musicSpec) {
+        TransactionBuilder builder = new TransactionBuilder("send music information");
+        // Send title
+        {
+            byte[] track_bytes;
+            if (musicSpec.track != null)
+                track_bytes = musicSpec.track.getBytes(StandardCharsets.UTF_8);
+            else
+                track_bytes = "\"\"".getBytes(StandardCharsets.UTF_8);
+            safeWriteToCharacteristic(builder, AsteroidOSConstants.MEDIA_TITLE_CHAR, track_bytes);
+        }
+        // Send album
+        {
+            byte[] album_bytes;
+            if (musicSpec.album != null)
+                album_bytes = musicSpec.album.getBytes(StandardCharsets.UTF_8);
+            else
+                album_bytes = "\"\"".getBytes(StandardCharsets.UTF_8);
+            safeWriteToCharacteristic(builder, AsteroidOSConstants.MEDIA_ALBUM_CHAR, album_bytes);
+        }
+        // Send artist
+        {
+            byte[] artist_bytes;
+            if (musicSpec.artist != null)
+                artist_bytes = musicSpec.artist.getBytes(StandardCharsets.UTF_8);
+            else
+                artist_bytes = "\"\"".getBytes(StandardCharsets.UTF_8);
+            safeWriteToCharacteristic(builder, AsteroidOSConstants.MEDIA_ARTIST_CHAR, artist_bytes);
+        }
+        builder.queue(getQueue());
+    }
+
+    @Override
+    public void onSetPhoneVolume(float volume) {
+        TransactionBuilder builder = new TransactionBuilder("send volume information");
+        byte volByte = (byte) Math.round(volume);
+        safeWriteToCharacteristic(builder, AsteroidOSConstants.MEDIA_VOLUME_CHAR, new byte[]{volByte});
+        builder.queue(getQueue());
+    }
+
+    @Override
+    public void onFindDevice(boolean start) {
+        final CallSpec callSpec = new CallSpec();
+        callSpec.command = start ? CallSpec.CALL_INCOMING : CallSpec.CALL_END;
+        callSpec.name = "Gadgetbridge";
+        onSetCallState(callSpec);
+    }
+
+    @Override
+    public void onSendWeather(ArrayList<WeatherSpec> weatherSpecs) {
+        WeatherSpec weatherSpec = weatherSpecs.get(0);
+        AsteroidOSWeather asteroidOSWeather = new AsteroidOSWeather(weatherSpec);
+        TransactionBuilder builder = new TransactionBuilder("send weather info");
+        // Send city name
+        safeWriteToCharacteristic(builder, AsteroidOSConstants.WEATHER_CITY_CHAR, asteroidOSWeather.getCityName());
+        // Send conditions
+        safeWriteToCharacteristic(builder, AsteroidOSConstants.WEATHER_IDS_CHAR, asteroidOSWeather.getWeatherConditions());
+        // Send min temps
+        safeWriteToCharacteristic(builder, AsteroidOSConstants.WEATHER_MIN_TEMPS_CHAR, asteroidOSWeather.getMinTemps());
+        // Send max temps
+        safeWriteToCharacteristic(builder, AsteroidOSConstants.WEATHER_MAX_TEMPS_CHAR, asteroidOSWeather.getMaxTemps());
+        // Flush queue
+        builder.queue(getQueue());
+    }
+
+    @Override
+    public boolean useAutoConnect() {
+        return false;
+    }
+
+    /**
+     * This will check if the characteristic exists and can be written
+     * <p>
+     * Keeps backwards compatibility with firmware that can't take all the information
+     */
+    private void safeWriteToCharacteristic(TransactionBuilder builder, UUID uuid, byte[] data) {
+        BluetoothGattCharacteristic characteristic = getCharacteristic(uuid);
+        if (characteristic != null &&
+                (characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_WRITE) > 0) {
+            builder.write(characteristic, data);
+        } else {
+            LOG.warn("Tried to write to a characteristic that did not exist or was not writable!");
+        }
+    }
+
+    @Override
+    public boolean onCharacteristicRead(BluetoothGatt gatt,
+                                        BluetoothGattCharacteristic characteristic, int status) {
+        if (super.onCharacteristicRead(gatt, characteristic, status)) {
+            return true;
+        }
+        UUID characteristicUUID = characteristic.getUuid();
+
+        LOG.info("Unhandled characteristic read: " + characteristicUUID);
+        return false;
+    }
+
+
+    /**
+     * Handles a media command sent from the AsteroidOS device
+     * @param characteristic The Characteristic information
+     */
+    public void handleMediaCommand (BluetoothGattCharacteristic characteristic) {
+        LOG.info("handle media command");
+        AsteroidOSMediaCommand command = new AsteroidOSMediaCommand(characteristic.getValue()[0]);
+        GBDeviceEventMusicControl event = command.toMusicControlEvent();
+        evaluateGBDeviceEvent(event);
+    }
+
+    @Override
+    public boolean getImplicitCallbackModify() {
+        return true;
+    }
+
+    @Override
+    public boolean getSendWriteRequestResponse() {
+        return false;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/bandwpseries/BandWBLEProfile.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/bandwpseries/BandWBLEProfile.java
new file mode 100644
index 0000000..2ed04b0
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/bandwpseries/BandWBLEProfile.java
@@ -0,0 +1,88 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.bandwpseries;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.AbstractBleProfile;
+
+public class BandWBLEProfile<T extends AbstractBTLEDeviceSupport> extends AbstractBleProfile<T> {
+    private static final Logger LOG = LoggerFactory.getLogger(BandWBLEProfile.class);
+
+    private static final String ACTION_PREFIX = BandWBLEProfile.class.getName() + "_";
+
+    public static final String ACTION_DEVICE_INFO = ACTION_PREFIX + "DEVICE_INFO";
+    public static final String EXTRA_DEVICE_INFO = "DEVICE_INFO";
+
+    public static final byte ANC_MODE_OFF = 0x01;
+    public static final byte ANC_MODE_ON = 0x03;
+
+    public static final UUID UUID_RPC_REQUEST_CHARACTERISTIC = UUID.fromString("ada50ce9-67b8-4a97-9d8e-37e1d083156c");
+
+    public BandWBLEProfile(final T support) {
+        super(support);
+    }
+
+    public void requestAncModeState(final TransactionBuilder builder) {
+        sendRequest(builder, (byte) 0x03, (byte) 0x01);
+    }
+    public void requestDeviceName(final TransactionBuilder builder) {
+        sendRequest(builder, (byte) 0x05, (byte) 0x01);
+    }
+
+    public void requestFirmware(final TransactionBuilder builder) {
+        sendRequest(builder, (byte) 0x02, (byte) 0x01);
+    }
+
+    public void requestBatteryLevels(final TransactionBuilder builder) {
+        sendRequest(builder, (byte) 0x08, (byte) 0x17);
+    }
+
+    public void requestVptEnabled(final TransactionBuilder builder) {
+        sendRequest(builder, (byte) 0x03, (byte) 0x05);
+    }
+
+    public void requestVptLevel(final TransactionBuilder builder) {
+        sendRequest(builder, (byte) 0x03, (byte) 0x03);
+    }
+
+    public void requestWearSensorEnabled(final TransactionBuilder builder) {
+        sendRequest(builder, (byte) 0x0a, (byte) 0x01);
+    }
+
+    public void setAncModeState(final TransactionBuilder builder, final boolean mode) throws IOException {
+        BandWPSeriesRequest req = new BandWPSeriesRequest((byte) 0x03, (byte) 0x02).addToPayload(mode ? ANC_MODE_ON : ANC_MODE_OFF);
+        builder.write(getCharacteristic(UUID_RPC_REQUEST_CHARACTERISTIC), req.finishAndGetBytes());
+    }
+
+    public void setVptLevel(final TransactionBuilder builder, final int level) throws IOException {
+        BandWPSeriesRequest req = new BandWPSeriesRequest((byte) 0x03, (byte) 0x04).addToPayload(level);
+        builder.write(getCharacteristic(UUID_RPC_REQUEST_CHARACTERISTIC), req.finishAndGetBytes());
+    }
+
+    public void setVptEnabled(final TransactionBuilder builder, final boolean mode) throws IOException {
+        BandWPSeriesRequest req = new BandWPSeriesRequest((byte) 0x03, (byte) 0x06).addToPayload(mode);
+        builder.write(getCharacteristic(UUID_RPC_REQUEST_CHARACTERISTIC), req.finishAndGetBytes());
+    }
+
+    public void setWearSensorEnabled(final TransactionBuilder builder, final boolean mode) throws IOException {
+        BandWPSeriesRequest req = new BandWPSeriesRequest((byte) 0x0a, (byte) 0x02).addToPayload(mode);
+        builder.write(getCharacteristic(UUID_RPC_REQUEST_CHARACTERISTIC), req.finishAndGetBytes());
+    }
+
+    private void sendRequest(final TransactionBuilder builder, byte namespace, byte commandID) {
+        BandWPSeriesRequest req;
+        try {
+            req = new BandWPSeriesRequest(namespace, commandID);
+        } catch (IOException e) {
+            LOG.error("Failed to send request: namespace {}, commandID {}", namespace, commandID);
+            return;
+        }
+        builder.write(getCharacteristic(UUID_RPC_REQUEST_CHARACTERISTIC), req.finishAndGetBytes());
+    }
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/bandwpseries/BandWMessageType.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/bandwpseries/BandWMessageType.java
new file mode 100644
index 0000000..98f42d9
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/bandwpseries/BandWMessageType.java
@@ -0,0 +1,27 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.bandwpseries;
+public enum BandWMessageType {
+    REQUEST_WITH_PAYLOAD(0x920b, true),
+    REQUEST_WITHOUT_PAYLOAD(0x120b, false),
+    RESPONSE_WITH_PAYLOAD(0x920c, true),
+    RESPONSE_WITHOUT_PAYLOAD(0x120c, false),
+    NOTIFICATION_WITH_PAYLOAD(0x920d, true),
+    NOTIFICATION_WITHOUT_PAYLOAD(0x120d, false);
+
+
+    public final int value;
+    public final boolean hasPayload;
+
+    BandWMessageType(int mType, boolean hasPayload) {
+        this.value = mType;
+        this.hasPayload = hasPayload;
+    }
+
+    public static BandWMessageType getByType(int mType) {
+        for (BandWMessageType t: values()) {
+            if (t.value == mType) {
+                return t;
+            }
+        }
+        return null;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/bandwpseries/BandWPSeriesDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/bandwpseries/BandWPSeriesDeviceSupport.java
new file mode 100644
index 0000000..fcf67c8
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/bandwpseries/BandWPSeriesDeviceSupport.java
@@ -0,0 +1,315 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.bandwpseries;
+
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_ACTIVE_NOISE_CANCELLING_TOGGLE;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_BANDW_PSERIES_GUI_VPT_LEVEL;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_BANDW_PSERIES_VPT_ENABLED;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_BANDW_PSERIES_VPT_LEVEL;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_WEAR_SENSOR_TOGGLE;
+import static nodomain.freeyourgadget.gadgetbridge.impl.GBDevice.BATTERY_UNKNOWN;
+import static nodomain.freeyourgadget.gadgetbridge.service.devices.bandwpseries.BandWBLEProfile.ANC_MODE_ON;
+
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.bluetooth.BluetoothGatt;
+import android.content.SharedPreferences.Editor;
+import android.widget.Toast;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo;
+import nodomain.freeyourgadget.gadgetbridge.devices.jyou.BFH16Constants;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class BandWPSeriesDeviceSupport extends AbstractBTLEDeviceSupport {
+
+    private static final Logger LOG = LoggerFactory.getLogger(BandWPSeriesDeviceSupport.class);
+
+    private static final UUID UUID_RPC_SERVICE = UUID.fromString("85ba93a5-09ac-439a-8cc4-1c3f0cb4f29f");
+    private static final UUID UUID_RPC_RESPONSE_CHARACTERISTIC = UUID.fromString("cb909093-3559-4b0c-9a7f-3f1773122fdc");
+    private static final UUID UUID_RPC_NOTIFICATION_CHARACTERISTIC = UUID.fromString("df55d475-9a32-457a-9e20-38cf14e853fb");
+
+    private final BandWBLEProfile<BandWPSeriesDeviceSupport> BandWBLEProfile;
+    private final GBDeviceEventBatteryInfo[] batteryInfo = new GBDeviceEventBatteryInfo[3];
+
+    public BandWPSeriesDeviceSupport() {
+        super(LOG);
+        addSupportedService(BFH16Constants.BFH16_GENERIC_ATTRIBUTE_SERVICE);
+        addSupportedService(BFH16Constants.BFH16_GENERIC_ACCESS_SERVICE);
+        addSupportedService(UUID_RPC_SERVICE);
+
+        BandWBLEProfile = new BandWBLEProfile<>(this);
+        addSupportedProfile(BandWBLEProfile);
+    }
+
+    @Override
+    protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
+        // mark the device as initializing
+        builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext()));
+
+        getDevice().setBatteryLabel(R.string.left_earbud, 0);
+        getDevice().setBatteryLabel(R.string.right_earbud, 1);
+        getDevice().setBatteryLabel(R.string.battery_case, 2);
+
+        for (int i = 0; i < 3; i++) {
+            batteryInfo[i] = new GBDeviceEventBatteryInfo();
+            batteryInfo[i].batteryIndex = i;
+            batteryInfo[i].level = BATTERY_UNKNOWN;
+            handleGBDeviceEvent(batteryInfo[i]);
+        }
+
+        // mark the device as initialized
+        builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZED, getContext()));
+
+        builder.notify(getCharacteristic(UUID_RPC_RESPONSE_CHARACTERISTIC), true);
+        builder.notify(getCharacteristic(UUID_RPC_NOTIFICATION_CHARACTERISTIC), true);
+        BandWBLEProfile.requestFirmware(builder);
+        BandWBLEProfile.requestDeviceName(builder);
+        BandWBLEProfile.requestBatteryLevels(builder);
+        BandWBLEProfile.requestAncModeState(builder);
+        BandWBLEProfile.requestVptEnabled(builder);
+        BandWBLEProfile.requestVptLevel(builder);
+        BandWBLEProfile.requestWearSensorEnabled(builder);
+        return builder;
+    }
+
+    public boolean onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
+        super.onCharacteristicChanged(gatt, characteristic);
+
+        UUID characteristicUUID = characteristic.getUuid();
+
+        if (UUID_RPC_RESPONSE_CHARACTERISTIC.equals(characteristicUUID) || UUID_RPC_NOTIFICATION_CHARACTERISTIC.equals(characteristicUUID)) {
+            return handleRPCResponse(characteristic);
+        }
+        return false;
+    }
+
+    private boolean handleRPCResponse(BluetoothGattCharacteristic characteristic) {
+        BandWPSeriesResponse response = new BandWPSeriesResponse(characteristic.getValue());
+        LOG.debug("Got RPC response: Type {}, commandID {}, namespace {}, errorCode {}, payload {}",
+                response.messageType,
+                response.commandId,
+                response.namespace,
+                response.errorCode,
+                response.payload);
+        if (response.errorCode != 0) {
+            return false;
+        }
+        if (response.namespace == 0x02) {
+            if (response.commandId == 0x01) {
+                return handleFirmwareVersionResponse(response);
+            }
+        } else if (response.namespace == 0x03) {
+            switch (response.commandId) {
+                case 0x01:
+                    return handleGetAncModeStateResponse(response);
+                case 0x02:
+                case 0x04:
+                    return getIntResponseStatus(response);
+                case 0x03:
+                    return handleGetVptLevelResponse(response);
+                case 0x05:
+                    return handleGetVptEnabledResponse(response);
+                case 0x06:
+                    return getBooleanResponseStatus(response);
+            }
+        } else if (response.namespace == 0x05) {
+            if (response.commandId == 0x01) {
+                return handleDeviceNameResponse(response);
+            }
+        } else if (response.namespace == 0x08) {
+            if (response.commandId == 0x17) {
+                return handleBatteryLevels(response);
+            }
+        } else if (response.namespace == 0x0a) {
+            if (response.commandId == 0x01) {
+                return handleGetWearSensorEnabledResponse(response);
+            } else if (response.commandId == 0x02) {
+                return getBooleanResponseStatus(response);
+            }
+        }
+        return true;
+    }
+
+    private boolean handleGetAncModeStateResponse(BandWPSeriesResponse response) {
+        if (!response.messageType.hasPayload) {
+            GB.toast("No payload in response!", Toast.LENGTH_SHORT, GB.ERROR);
+            return false;
+        }
+        int payloadValue;
+        try {
+            payloadValue = response.payloadUnpacker.unpackInt();
+        } catch (IOException e) {
+            GB.toast("Could not extract ancMode from payload: " + Arrays.toString(response.payload), Toast.LENGTH_SHORT, GB.ERROR);
+            return false;
+        }
+        Editor editor = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).edit();
+        editor.putBoolean(PREF_ACTIVE_NOISE_CANCELLING_TOGGLE, payloadValue == ANC_MODE_ON);
+        editor.apply();
+        return true;
+    }
+
+    private boolean handleBatteryLevels(BandWPSeriesResponse response) {
+        int[] levels = response.getPayloadFixArray();
+        if (levels == null) {
+            return false;
+        }
+        for (int i = 0; i < levels.length; i++) {
+            if (i >= 3) {
+                break;
+            }
+            int level = (levels[i] == 0xff) ? BATTERY_UNKNOWN : levels[i];
+            LOG.debug("Battery {} has level {}", i, levels[i]);
+            batteryInfo[i].level = level;
+            handleGBDeviceEvent(batteryInfo[i]);
+        }
+        return true;
+    }
+
+    private boolean handleGetWearSensorEnabledResponse(BandWPSeriesResponse response) {
+        if (!response.messageType.hasPayload) {
+            GB.toast("No payload in response!", Toast.LENGTH_SHORT, GB.ERROR);
+            return false;
+        }
+        boolean wearSensorEnabled;
+        try {
+            wearSensorEnabled = response.getPayloadBoolean();
+        } catch (IOException e) {
+            GB.toast("Failed to unpack wear sensor status from payload " + Arrays.toString(response.payload), Toast.LENGTH_SHORT, GB.ERROR);
+            return false;
+        }
+        Editor editor = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).edit();
+        editor.putBoolean(PREF_WEAR_SENSOR_TOGGLE, wearSensorEnabled);
+        editor.apply();
+        return true;
+    }
+
+    private boolean handleFirmwareVersionResponse(BandWPSeriesResponse response) {
+        String firmwareString = response.getPayloadString();
+        if (firmwareString == null) {
+            return false;
+        }
+        String[] versions = firmwareString.split("\\(");
+        String main_version = versions[0];
+        String sub_version = versions[1].substring(0, versions[1].length()-1);
+        GBDeviceEventVersionInfo versionInfo = new GBDeviceEventVersionInfo();
+        versionInfo.fwVersion = main_version;
+        versionInfo.fwVersion2 = sub_version;
+        LOG.debug("Got firmware version {}/{}", main_version, sub_version);
+        handleGBDeviceEvent(versionInfo);
+        return true;
+    }
+
+    private boolean handleDeviceNameResponse(BandWPSeriesResponse response) {
+        String deviceName = response.getPayloadString();
+        if (deviceName == null) {
+            return false;
+        }
+        getDevice().setName(deviceName);
+        LOG.debug("Set device name to {}", deviceName);
+        return true;
+    }
+
+    private boolean handleGetVptEnabledResponse(BandWPSeriesResponse response) {
+        if (!response.messageType.hasPayload) {
+            GB.toast("No payload in response!", Toast.LENGTH_SHORT, GB.ERROR);
+            return false;
+        }
+        boolean payloadValue;
+        try {
+            payloadValue = response.payloadUnpacker.unpackBoolean();
+        } catch (IOException e) {
+            GB.toast("Could not extract vptEnabled from payload: " + Arrays.toString(response.payload), Toast.LENGTH_SHORT, GB.ERROR);
+            return false;
+        }
+        int vptLevel = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getInt(PREF_BANDW_PSERIES_VPT_LEVEL, 0);
+        Editor editor = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).edit();
+        editor.putBoolean(PREF_BANDW_PSERIES_VPT_ENABLED, payloadValue);
+        editor.putInt(PREF_BANDW_PSERIES_GUI_VPT_LEVEL, payloadValue ? vptLevel + 1 : 0);
+        editor.apply();
+        return true;
+    }
+
+    private boolean handleGetVptLevelResponse(BandWPSeriesResponse response) {
+        if (!response.messageType.hasPayload) {
+            GB.toast("No payload in response!", Toast.LENGTH_SHORT, GB.ERROR);
+            return false;
+        }
+        int payloadValue;
+        try {
+            payloadValue = response.payloadUnpacker.unpackInt();
+        } catch (IOException e) {
+            GB.toast("Could not extract vptLevel from payload: " + Arrays.toString(response.payload), Toast.LENGTH_SHORT, GB.ERROR);
+            return false;
+        }
+        boolean vptEnabled = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getBoolean(PREF_BANDW_PSERIES_VPT_ENABLED, false);
+        Editor editor = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).edit();
+        editor.putInt(PREF_BANDW_PSERIES_VPT_LEVEL, payloadValue);
+        editor.putInt(PREF_BANDW_PSERIES_GUI_VPT_LEVEL, vptEnabled ? payloadValue + 1 : 0);
+        editor.apply();
+        return true;
+    }
+
+    public void onSendConfiguration(String config) {
+        try {
+            TransactionBuilder builder = performInitialized("sendConfig");
+            switch (config) {
+                case PREF_ACTIVE_NOISE_CANCELLING_TOGGLE:
+                    boolean ancMode = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getBoolean(PREF_ACTIVE_NOISE_CANCELLING_TOGGLE, true);
+                    BandWBLEProfile.setAncModeState(builder, ancMode);
+                    break;
+                case PREF_BANDW_PSERIES_GUI_VPT_LEVEL:
+                    int level = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getInt(PREF_BANDW_PSERIES_GUI_VPT_LEVEL, 0);
+                    BandWBLEProfile.setVptEnabled(builder, level != 0);
+                    if (level != 0) {
+                        BandWBLEProfile.setVptLevel(builder, level - 1);
+                    }
+                    break;
+                case PREF_WEAR_SENSOR_TOGGLE:
+                    boolean wearSensorEnabled = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getBoolean(PREF_WEAR_SENSOR_TOGGLE, true);
+                    BandWBLEProfile.setWearSensorEnabled(builder, wearSensorEnabled);
+                    break;
+            }
+            performImmediately(builder);
+        } catch (IOException e) {
+            GB.toast("Failed to send settings update", Toast.LENGTH_SHORT, GB.ERROR);
+        }
+    }
+
+    @Override
+    public boolean useAutoConnect() {
+        return true;
+    }
+
+    private boolean getBooleanResponseStatus(BandWPSeriesResponse response) {
+        boolean payloadValue;
+        try {
+            payloadValue = response.payloadUnpacker.unpackBoolean();
+        } catch (IOException e) {
+            GB.toast("Could not extract response from payload: " + Arrays.toString(response.payload), Toast.LENGTH_SHORT, GB.ERROR);
+            return false;
+        }
+        return payloadValue;
+    }
+
+    private boolean getIntResponseStatus(BandWPSeriesResponse response) {
+        int payloadValue;
+        try {
+            payloadValue = response.payloadUnpacker.unpackInt();
+        } catch (IOException e) {
+            GB.toast("Could not extract response from payload: " + Arrays.toString(response.payload), Toast.LENGTH_SHORT, GB.ERROR);
+            return false;
+        }
+        return payloadValue == 0;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/bandwpseries/BandWPSeriesRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/bandwpseries/BandWPSeriesRequest.java
new file mode 100644
index 0000000..a0e2339
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/bandwpseries/BandWPSeriesRequest.java
@@ -0,0 +1,80 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.bandwpseries;
+
+import org.msgpack.core.MessageBufferPacker;
+import org.msgpack.core.MessagePack;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+
+public class BandWPSeriesRequest {
+
+    private static final Logger LOG = LoggerFactory.getLogger(BandWPSeriesRequest.class);
+
+    BandWMessageType messageType;
+    final byte namespace;
+    final byte commandId;
+
+    private final MessageBufferPacker payloadPacker = MessagePack.newDefaultBufferPacker();
+
+    public BandWPSeriesRequest(byte mNamespace, byte mCommandId) throws IOException {
+        messageType = BandWMessageType.REQUEST_WITHOUT_PAYLOAD;
+        namespace = mNamespace;
+        commandId = mCommandId;
+    }
+
+    public BandWPSeriesRequest addToPayload(int value) throws IOException {
+        payloadPacker.packInt(value);
+        messageType = BandWMessageType.REQUEST_WITH_PAYLOAD;
+        return this;
+    }
+
+    public BandWPSeriesRequest addToPayload(byte value) throws IOException {
+        payloadPacker.packByte(value);
+        messageType = BandWMessageType.REQUEST_WITH_PAYLOAD;
+        return this;
+    }
+
+    public BandWPSeriesRequest addToPayload(boolean value) throws IOException {
+        payloadPacker.packBoolean(value);
+        messageType = BandWMessageType.REQUEST_WITH_PAYLOAD;
+        return this;
+    }
+
+    public BandWPSeriesRequest addToPayload(String value) throws IOException {
+        payloadPacker.packString(value);
+        messageType = BandWMessageType.REQUEST_WITH_PAYLOAD;
+        return this;
+    }
+
+    public byte[] finishAndGetBytes() {
+        byte[] payload = payloadPacker.toByteArray();
+        byte len = (byte) ((this.messageType == BandWMessageType.REQUEST_WITHOUT_PAYLOAD) ? 4 : 6 + payload.length);
+        byte[] out = addMessageType(new byte[len+1], messageType.value);
+        out[0] = len;
+        out[3] = commandId;
+        out[4] = namespace;
+        if (messageType == BandWMessageType.REQUEST_WITH_PAYLOAD) {
+            addShort(out, 5, payload.length);
+            System.arraycopy(payload, 0, out, 7, payload.length);
+        }
+        try {
+            payloadPacker.close();
+        } catch (IOException e) {
+            LOG.warn("Failed to close payloadPacker");
+        }
+        return out;
+    }
+
+    private byte[] addMessageType(byte[] target, int value) {
+        return addShort(target, 1, value);
+    }
+
+    private byte[] addShort(byte[] target, int position, int value) {
+        byte valueLo = (byte) (value & 0xff);
+        byte valueHi = (byte) (value >> 8);
+        target[position] = valueLo;
+        target[position+1] = valueHi;
+        return target;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/bandwpseries/BandWPSeriesResponse.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/bandwpseries/BandWPSeriesResponse.java
new file mode 100644
index 0000000..6e80c4c
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/bandwpseries/BandWPSeriesResponse.java
@@ -0,0 +1,84 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.bandwpseries;
+
+import org.bouncycastle.shaded.util.Arrays;
+import org.msgpack.core.MessagePack;
+import org.msgpack.core.MessageUnpacker;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+
+public class BandWPSeriesResponse {
+
+    private static final Logger LOG = LoggerFactory.getLogger(BandWPSeriesResponse.class);
+
+    BandWMessageType messageType;
+    final byte namespace;
+    final byte commandId;
+    final int errorCode;
+    final int payloadLength;
+    final byte[] payload;
+
+    public final MessageUnpacker payloadUnpacker;
+
+    BandWPSeriesResponse(byte[] contents) {
+        messageType = BandWMessageType.getByType(getUInt16(Arrays.copyOfRange(contents, 0, 2)));
+        commandId = contents[2];
+        namespace = contents[3];
+        int payloadOffset = 6;
+        if (messageType == BandWMessageType.RESPONSE_WITH_PAYLOAD || messageType == BandWMessageType.RESPONSE_WITHOUT_PAYLOAD) {
+            errorCode = getUInt16(Arrays.copyOfRange(contents, 4, 6));
+        } else {
+            errorCode = 0;
+            payloadOffset = 4;
+        }
+        if (messageType == null || !messageType.hasPayload || errorCode != 0) {
+            payloadLength = 0;
+            payload = null;
+            payloadUnpacker = null;
+        } else {
+            payloadLength = getUInt16(Arrays.copyOfRange(contents, payloadOffset, payloadOffset + 2));
+            payload = Arrays.copyOfRange(contents, payloadOffset + 2, contents.length);
+            payloadUnpacker = MessagePack.newDefaultUnpacker(payload);
+        }
+    }
+
+    private int getUInt16(byte[] buffer) {
+        return (0xff & buffer[0]) | ((0xff & buffer[1]) << 8);
+    }
+
+    public String getPayloadString() {
+        String value;
+        try {
+            value = payloadUnpacker.unpackString();
+        } catch (IOException e) {
+            LOG.warn("Failed to unpack String from payload {}", payload);
+            return null;
+        }
+        return value;
+    }
+
+    public int[] getPayloadFixArray() {
+        int length;
+        try {
+            length = payloadUnpacker.unpackArrayHeader();
+        } catch (IOException e) {
+            LOG.warn("Failed to unpack ArrayHeader from payload {}", payload);
+            return null;
+        }
+        int[] values = new int[length];
+        try {
+            for (int i = 0; i < length; i++) {
+                values[i] = payloadUnpacker.unpackInt();
+            }
+        } catch (IOException e) {
+            LOG.warn("Failed to unpack byte from fixarray in payload {}", payload);
+            return null;
+        }
+        return values;
+    }
+
+    public boolean getPayloadBoolean() throws IOException{
+        return payloadUnpacker.unpackBoolean();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/banglejs/BangleJSActivityTrack.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/banglejs/BangleJSActivityTrack.java
new file mode 100644
index 0000000..9ebed5a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/banglejs/BangleJSActivityTrack.java
@@ -0,0 +1,437 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.banglejs;
+
+import static java.lang.Integer.parseInt;
+
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.HR_AVG;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.INTERNAL_HAS_GPS;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.SPEED_AVG;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.widget.Toast;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+import java.util.Timer;
+import java.util.TimerTask;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.banglejs.BangleJSActivityPoint;
+import nodomain.freeyourgadget.gadgetbridge.devices.banglejs.BangleJSWorkoutParser;
+import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.entities.User;
+import nodomain.freeyourgadget.gadgetbridge.export.ActivityTrackExporter;
+import nodomain.freeyourgadget.gadgetbridge.export.GPXExporter;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityPoint;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryData;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityTrack;
+import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+class BangleJSActivityTrack {
+
+    private static final Logger LOG = LoggerFactory.getLogger(BangleJSActivityTrack.class);
+
+    static JSONObject compileTracksListRequest(GBDevice device, Context context) {
+        stopAndRestartTimeout(device, context);
+        signalFetchingStarted(device, context);
+        //GB.toast("TYPE_GPS_TRACKS says hi!", Toast.LENGTH_LONG, GB.INFO);
+
+        String lastSyncedID = getLatestFetchedRecorderLog(device);
+
+        JSONObject o = new JSONObject();
+        try {
+            o.put("t", "listRecs");
+            o.put("id", lastSyncedID);
+            //uartTxJSON("requestActivityTracksList", o);
+        } catch (JSONException e) {
+            LOG.error("JSONException: " + e.getLocalizedMessage());
+        }
+
+        return o;
+    }
+
+    private static JSONArray tracksList;
+    static JSONObject handleActTrksList(JSONObject json, GBDevice device, Context context) throws JSONException {
+        stopAndRestartTimeout(device, context);
+        tracksList = json.getJSONArray("list");
+        LOG.debug("trksList says hi!");
+        //GB.toast(getContext(), "trksList says hi!", Toast.LENGTH_LONG, GB.INFO);
+        LOG.info("New recorder logs since last fetch: " + String.valueOf(tracksList));
+        if (tracksList.length()==0) {
+            signalFetchingEnded(device, context);
+            return null;
+        } else {
+            JSONObject requestTrackObj = BangleJSActivityTrack.compileTrackRequest(tracksList.getString(0), 1==tracksList.length());
+            tracksList.remove(0);
+            return requestTrackObj;
+        }
+    }
+
+    private static int lastPacketCount = -1;
+    static JSONObject handleActTrk(JSONObject json, GBDevice device, Context context) throws JSONException {
+        stopAndRestartTimeout(device, context);
+
+        JSONObject returnObj;
+
+        JSONObject stopObj = new JSONObject().put("t","fetchRec").put("id","stop");
+        int currPacketCount;
+        if (!json.has("cnt")) {
+            currPacketCount = 0;
+        } else {
+            currPacketCount = json.getInt("cnt");
+        }
+        if (currPacketCount != lastPacketCount+1) {
+            LOG.error("Activity Track Packets came out of order - aborting.");
+            LOG.debug("packetCount Aborting: " + lastPacketCount);
+            signalFetchingEnded(device, context);
+            stopTimeoutTask();
+            return stopObj;
+        }
+
+        LOG.debug("actTrk says hi!");
+        //GB.toast(context, "actTrk says hi!", Toast.LENGTH_LONG, GB.INFO);
+        String log = json.getString("log");
+        LOG.debug(log);
+        String filename = "recorder.log" + log + ".csv";
+        File dir;
+        try {
+            dir = new File(FileUtils.getExternalFilesDir() + "/" + FileUtils.makeValidFileName(device.getName()));
+            if (!dir.isDirectory()) {
+                if (!dir.mkdir()) {
+                    throw new IOException("Cannot create device specific directory for " + device.getName());
+                }
+            }
+        } catch (IOException e) {
+            LOG.error("Failed at getting external files directory with error: " + e);
+            resetPacketCount();
+            return null;
+        }
+
+        if (!json.has("lines")) { // if no lines were sent with this json object, it signifies that the whole recorder log has been transmitted.
+            setLatestFetchedRecorderLog(log, device);
+            parseFetchedRecorderCSV(dir, filename, log, device, context); // I tried refactoring to parse all fetched logs in one go at the end instead. But that only gave me more troubles. This seems like a more stable approach at least in the Bangle.js case.
+            if (tracksList.length()==0) {
+                signalFetchingEnded(device, context);
+                LOG.debug("packetCount reset1: " + lastPacketCount);
+                returnObj = null;
+            } else {
+                JSONObject requestTrackObj = BangleJSActivityTrack.compileTrackRequest(tracksList.getString(0), 1==tracksList.length());
+                tracksList.remove(0);
+                resetPacketCount();
+                LOG.debug("packetCount reset2: " + lastPacketCount);
+                returnObj = requestTrackObj;
+            }
+        } else { // We received a lines of the csv, now we append it to the file in storage.
+
+            String lines = json.getString("lines");
+            LOG.debug(lines);
+
+            writeToRecorderCSV(lines, dir, filename);
+
+            lastPacketCount += 1;
+            LOG.debug("packetCount continue: " + lastPacketCount);
+            returnObj = null;
+        }
+
+        return returnObj;
+    }
+
+    private static void parseFetchedRecorderCSV(File dir, String filename, String log, GBDevice device, Context context) {
+        stopTimeoutTask(); // Parsing can take a while if there are many data. Restart at end of parsing.
+
+        File inputFile = new File(dir, filename);
+        try {
+            BaseActivitySummary summary = new BaseActivitySummary();
+
+            final List<BangleJSActivityPoint> banglePoints = BangleJSActivityPoint.fromCsv(inputFile);
+            if (banglePoints == null || banglePoints.isEmpty()) {
+                // Should never happen?
+                return;
+            }
+            Date startTime = new Date(banglePoints.get(0).getTime());
+            Date endTime = new Date(banglePoints.get(banglePoints.size() - 1).getTime());
+            summary.setName(log);
+            summary.setStartTime(startTime);
+            summary.setEndTime(endTime);
+            ActivitySummaryData summaryData = BangleJSWorkoutParser.dataFromPoints(banglePoints);
+            summary.setSummaryData(summaryData.toString());
+            ActivityKind activityKind;
+            final double speedAvg = summaryData.getNumber(SPEED_AVG, -1).doubleValue();
+            if (speedAvg >= 10) {
+                activityKind = ActivityKind.ACTIVITY;
+            } else if (speedAvg >= 3) {
+                activityKind = ActivityKind.RUNNING;
+            } else if (speedAvg >= 0) {
+                activityKind = ActivityKind.WALKING;
+            } else {
+                activityKind = ActivityKind.ACTIVITY;
+            }
+            summary.setActivityKind(activityKind.getCode()); // TODO: Make this depend on info from watch (currently this info isn't supplied in Bangle.js recorder logs).
+            summary.setRawDetailsPath(String.valueOf(inputFile));
+
+            ActivityTrack track = new ActivityTrack(); // detailsParser.parse(buffer.toByteArray());
+            track.startNewSegment();
+            track.setBaseTime(startTime);
+            track.setName(log);
+            try (DBHandler dbHandler = GBApplication.acquireDB()) {
+                DaoSession session = dbHandler.getDaoSession();
+                Device deviceDB = DBHelper.getDevice(device, session);
+                User user = DBHelper.getUser(session);
+                track.setDevice(deviceDB);
+                track.setUser(user);
+            } catch (Exception ex) {
+                GB.toast(context, "Error setting user for activity track.", Toast.LENGTH_LONG, GB.ERROR, ex);
+            }
+            boolean hasGPXReading = summaryData.has(INTERNAL_HAS_GPS);
+            boolean hasHRMReading = summaryData.has(HR_AVG);
+            for (final BangleJSActivityPoint banglePoint : banglePoints) {
+                track.addTrackPoint(banglePoint.toActivityPoint());
+            }
+
+            ActivityTrackExporter exporter = new GPXExporter();
+            String trackType = "track";
+            switch (ActivityKind.fromCode(summary.getActivityKind())) {
+                case CYCLING:
+                    trackType = context.getString(R.string.activity_type_biking);
+                    break;
+                case RUNNING:
+                    trackType = context.getString(R.string.activity_type_running);
+                    break;
+                case WALKING:
+                    trackType = context.getString(R.string.activity_type_walking);
+                    break;
+                case HIKING:
+                    trackType = context.getString(R.string.activity_type_hiking);
+                    break;
+                case CLIMBING:
+                    trackType = context.getString(R.string.activity_type_climbing);
+                    break;
+                case SWIMMING:
+                    trackType = context.getString(R.string.activity_type_swimming);
+                    break;
+            }
+
+            String fileName = FileUtils.makeValidFileName("gadgetbridge-" + trackType.toLowerCase() + "-" + summary.getName() + ".gpx");
+            dir = new File(FileUtils.getExternalFilesDir() + "/" + FileUtils.makeValidFileName(device.getName()));
+            File targetFile = new File(dir, fileName);
+
+            if (hasGPXReading /*|| hasHRMReading*/) {
+                try {
+                    exporter.performExport(track, targetFile);
+                    summary.setGpxTrack(targetFile.getAbsolutePath());
+                } catch (ActivityTrackExporter.GPXTrackEmptyException ex) {
+                    GB.toast(context, "This activity does not contain GPX tracks.", Toast.LENGTH_LONG, GB.ERROR, ex);
+                }
+            }
+
+            //summary.setSummaryData(null); // remove json before saving to database,
+
+            try (DBHandler dbHandler = GBApplication.acquireDB()) {
+                DaoSession session = dbHandler.getDaoSession();
+                Device deviceDB = DBHelper.getDevice(device, session);
+                User user = DBHelper.getUser(session);
+                summary.setDevice(deviceDB);
+                summary.setUser(user);
+                session.getBaseActivitySummaryDao().insertOrReplace(summary);
+            } catch (Exception ex) {
+                GB.toast(context, "Error saving activity summary", Toast.LENGTH_LONG, GB.ERROR, ex);
+            }
+
+            LOG.debug("Activity track:\n" + track.getSegments());
+
+        } catch (IOException e) {
+            LOG.error("IOException when parsing fetched CSV: " + e);
+        }
+
+        stopAndRestartTimeout(device,context);
+    }
+
+    private static void resetPacketCount() {
+        lastPacketCount = -1;
+    }
+
+    private static JSONObject compileTrackRequest(String id, Boolean isLastId) {
+        JSONObject o = new JSONObject();
+        try {
+            o.put("t", "fetchRec");
+            o.put("id", id);
+            o.put("last", String.valueOf(isLastId));
+        } catch (JSONException e) {
+            LOG.error("JSONException: " + e.getLocalizedMessage());
+        }
+        return o;
+    }
+
+    private static void signalFetchingStarted(GBDevice device, Context context) {
+        GB.updateTransferNotification(context.getString(R.string.activity_detail_start_label) + " : " + context.getString(R.string.busy_task_fetch_sports_details),"", true, 0, context);
+        device.setBusyTask(context.getString(R.string.busy_task_fetch_sports_details));
+        GB.toast(context.getString(R.string.activity_detail_start_label) + " : " + context.getString(R.string.busy_task_fetch_sports_details), Toast.LENGTH_SHORT, GB.INFO);
+    }
+
+    private static void signalFetchingEnded(GBDevice device, Context context) {
+        stopTimeoutTask();
+        resetPacketCount();
+        device.unsetBusyTask();
+        device.sendDeviceUpdateIntent(context);
+        GB.signalActivityDataFinish(device);
+        GB.updateTransferNotification(null, "", false, 100, context);
+        GB.toast(context.getString(R.string.activity_detail_end_label) + " : " + context.getString(R.string.busy_task_fetch_sports_details), Toast.LENGTH_SHORT, GB.INFO);
+    }
+
+    private static Timer timeout;
+    private static TimerTask timeoutTask;
+
+    private static void startTimeout(GBDevice device, Context context) {
+        timeout = new Timer();
+
+        initializeTimeoutTask(device, context);
+
+        timeout.schedule(timeoutTask, 5000);
+    }
+
+    private static void stopTimeoutTask() {
+        if (timeout != null) {
+            timeout.cancel();
+            timeout = null;
+        }
+    }
+
+    private static void initializeTimeoutTask(GBDevice device, Context context) {
+
+        timeoutTask = new TimerTask() {
+            public void run() {
+                signalFetchingEnded(device, context);
+                LOG.warn(context.getString(R.string.busy_task_fetch_sports_details_interrupted));
+                GB.toast(context.getString(R.string.busy_task_fetch_sports_details_interrupted), Toast.LENGTH_LONG, GB.INFO);
+                // TODO: We could send a stop message back to Bangle.js here if we want to hinder
+                //  the potential event it would start sending lines again after Gadgetbridge
+                //  determined the fetch had been interrupted. On the other hand I think if we
+                //  started receiving lines again, they would only be appended to the file in
+                //  storage and the fetch would continue as if nothing happend.
+            }
+        };
+    }
+
+    private static void stopAndRestartTimeout(GBDevice device, Context context) {
+        stopTimeoutTask();
+        startTimeout(device, context);
+    }
+
+    private static String getLatestFetchedRecorderLog(GBDevice device) {
+        // "lastSportsActivityTimeMillis" is what
+        // `ActivitySummaryActivity.resetFetchTimestampToChosenDate()` uses, so we have to
+        // control if the user changed that value, and if so recompile a new sync id from that info.
+
+        String lastSyncedId = GBApplication.getDeviceSpecificSharedPrefs(device.getAddress()).
+                getString("lastSportsActivityIdBangleJS","19700101a");
+        LOG.debug("lastSyncedId: " + lastSyncedId);
+
+        long lastSportsActivityTimeMillis = GBApplication.getDeviceSpecificSharedPrefs(device.getAddress()).
+                getLong("lastSportsActivityTimeMillis",0);
+        LOG.debug("lastSportsActivityTimeMillis: " + lastSportsActivityTimeMillis);
+
+        Calendar dateFromId = parseCalendarFromBangleJSLogId(lastSyncedId);
+        Calendar dateFromMillis = parseCalendarFromMillis(lastSportsActivityTimeMillis);
+
+        LOG.debug("lastSyncedIdMillis: " + dateFromId.getTimeInMillis());
+
+       int intMillis = parseInt(compileDateStringFromCalendar(dateFromMillis));
+       int intBangle = parseInt(lastSyncedId.substring(0,lastSyncedId.length()-1));
+
+        LOG.debug("intMillis: " + intMillis);
+        LOG.debug("intBangle: " + intBangle);
+
+        //if (dateFromMillis.before(dateFromId)) { // This would not work b/c the millis didn't ever become the same in my testing, even if compiled from the same sync id string.
+        if (intMillis < intBangle) {
+            dateFromMillis.add(Calendar.DATE, -1); // We want the day before so we fetch from the day the user reset to.
+            int year = dateFromMillis.get(Calendar.YEAR);
+            int month = dateFromMillis.get(Calendar.MONTH);
+            int dayBefore = dateFromMillis.get(Calendar.DATE);
+
+
+            String yearString = String.valueOf(year);
+            String monthString = String.valueOf(month);
+            if (month<10) monthString = "0" + monthString;
+            String dayBeforeString = String.valueOf(dayBefore);
+            if (dayBefore<10) dayBeforeString = "0" + dayBeforeString;
+            String letter = "z";
+
+            return yearString + monthString + dayBeforeString + letter;
+        } else {
+           return lastSyncedId;
+        }
+    }
+
+    private static void setLatestFetchedRecorderLog(String log, GBDevice device) {
+        Calendar date = parseCalendarFromBangleJSLogId(log);
+
+        SharedPreferences.Editor editor = GBApplication.getDeviceSpecificSharedPrefs(device.getAddress()).edit();
+        editor.remove("lastSportsActivityIdBangleJS"); //FIXME: key reconstruction is BAD (FIXME inherited from `ActivitySummaryActivity`.
+        editor.remove("lastSportsActivityTimeMillis"); //FIXME: key reconstruction is BAD
+        editor.putString("lastSportsActivityIdBangleJS", log);
+        editor.putLong("lastSportsActivityTimeMillis", date.getTimeInMillis());
+        editor.apply();
+    }
+
+    private static Calendar parseCalendarFromBangleJSLogId(String log) {
+        int year = parseInt(log.substring(0,4));
+        int month = parseInt(log.substring(4,6));
+        int day = parseInt(log.substring(6,8));
+
+        LOG.debug("DateFromId: " + year+ "|"+ month + "|" +day);
+
+        Calendar date = Calendar.getInstance();
+        date.set(year,month,day);
+
+        return date;
+    }
+
+    private static Calendar parseCalendarFromMillis(long millis) {
+        Calendar date = Calendar.getInstance();
+        date.setTimeInMillis(millis);
+
+        return date;
+    }
+
+    private static String compileDateStringFromCalendar(Calendar date) {
+        int year = date.get(Calendar.YEAR);
+        int month = date.get(Calendar.MONTH);
+        int day = date.get(Calendar.DATE);
+
+        return String.format(Locale.ROOT, "%d%02d%02d", year, month, day);
+    }
+
+    private static void writeToRecorderCSV(String lines, File dir, String filename) {
+        String mode = "append";
+        if (lines.equals("erase")) {
+            mode = "write";
+            lines = "";
+        }
+
+        File outputFile = new File(dir, filename);
+        try {
+            FileUtils.copyStringToFile(lines,outputFile,mode);
+            //GB.toast(context, "Log written to " + filename, Toast.LENGTH_LONG, GB.INFO);
+        } catch (IOException e) {
+            LOG.error("Could not write to file", e);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/banglejs/BangleJSDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/banglejs/BangleJSDeviceSupport.java
new file mode 100644
index 0000000..55e849a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/banglejs/BangleJSDeviceSupport.java
@@ -0,0 +1,2156 @@
+/*  Copyright (C) 2019-2024 Albert, Andreas Shimokawa, Arjan Schrijver, Damien
+    Gaignon, Gabriele Monaco, Ganblejs, gfwilliams, glemco, Gordon Williams,
+    halemmerich, illis, Jos茅 Rebelo, Lukas, LukasEdl, Marc Nause, Martin Boonk,
+    rarder44, Richard de Boer, Simon Sievert
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.banglejs;
+
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_ALLOW_HIGH_MTU;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_BANGLEJS_TEXT_BITMAP;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_BANGLEJS_TEXT_BITMAP_SIZE;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_DEVICE_GPS_UPDATE;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_DEVICE_GPS_UPDATE_INTERVAL;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_DEVICE_GPS_USE_NETWORK_ONLY;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_DEVICE_INTENTS;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_DEVICE_INTERNET_ACCESS;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_NOTIFICATION_WAKE_ON_OPEN;
+import static nodomain.freeyourgadget.gadgetbridge.database.DBHelper.getUser;
+import static nodomain.freeyourgadget.gadgetbridge.devices.banglejs.BangleJSConstants.PREF_BANGLEJS_ACTIVITY_FULL_SYNC_START;
+import static nodomain.freeyourgadget.gadgetbridge.devices.banglejs.BangleJSConstants.PREF_BANGLEJS_ACTIVITY_FULL_SYNC_STATUS;
+
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.SharedPreferences;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.location.Location;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.util.Base64;
+import android.widget.Toast;
+
+import androidx.core.content.ContextCompat;
+import androidx.core.text.HtmlCompat;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import com.android.volley.AuthFailureError;
+import com.android.volley.Request;
+import com.android.volley.RequestQueue;
+import com.android.volley.Response;
+import com.android.volley.VolleyError;
+import com.android.volley.toolbox.StringRequest;
+import com.android.volley.toolbox.Volley;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.w3c.dom.NodeList;
+import org.xml.sax.InputSource;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.StringReader;
+import java.lang.reflect.Field;
+import java.nio.charset.StandardCharsets;
+import java.sql.Timestamp;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.SimpleTimeZone;
+
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathConstants;
+import javax.xml.xpath.XPathFactory;
+
+import de.greenrobot.dao.query.QueryBuilder;
+import io.wax911.emojify.EmojiManager;
+import io.wax911.emojify.parser.EmojiParserKt;
+import nodomain.freeyourgadget.gadgetbridge.BuildConfig;
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.WakeActivity;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+import nodomain.freeyourgadget.gadgetbridge.capabilities.loyaltycards.BarcodeFormat;
+import nodomain.freeyourgadget.gadgetbridge.capabilities.loyaltycards.LoyaltyCard;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventCallControl;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventFindPhone;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventMusicControl;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventNotificationControl;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventScreenshot;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdatePreferences;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo;
+import nodomain.freeyourgadget.gadgetbridge.devices.banglejs.BangleJSConstants;
+import nodomain.freeyourgadget.gadgetbridge.devices.banglejs.BangleJSSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.BangleJSActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.entities.CalendarSyncState;
+import nodomain.freeyourgadget.gadgetbridge.entities.CalendarSyncStateDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.externalevents.CalendarReceiver;
+import nodomain.freeyourgadget.gadgetbridge.externalevents.gps.GBLocationProviderType;
+import nodomain.freeyourgadget.gadgetbridge.externalevents.gps.GBLocationService;
+import nodomain.freeyourgadget.gadgetbridge.externalevents.sleepasandroid.SleepAsAndroidAction;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
+import nodomain.freeyourgadget.gadgetbridge.model.BatteryState;
+import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
+import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.NavigationInfoSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.NotificationType;
+import nodomain.freeyourgadget.gadgetbridge.model.RecordedDataTypes;
+import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
+import nodomain.freeyourgadget.gadgetbridge.service.SleepAsAndroidSender;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEQueue;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
+import nodomain.freeyourgadget.gadgetbridge.util.EmojiConverter;
+import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+import nodomain.freeyourgadget.gadgetbridge.util.LimitedQueue;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport {
+    private static final Logger LOG = LoggerFactory.getLogger(BangleJSDeviceSupport.class);
+
+    private BluetoothGattCharacteristic rxCharacteristic = null;
+    private BluetoothGattCharacteristic txCharacteristic = null;
+    private boolean allowHighMTU = false;
+    private int mtuSize = 20;
+    int bangleCommandSeq = 0; // to attempt to stop duplicate packets when sending Local Intents
+
+    /// Current line of data received from Bangle.js
+    private String receivedLine = "";
+    /// All characters received from Bangle.js for debug purposes (limited to MAX_RECEIVE_HISTORY_CHARS). Can be dumped with 'Fetch Device Debug Logs' from Debug menu
+    private String receiveHistory = "";
+    private boolean realtimeHRM = false;
+    private boolean realtimeStep = false;
+    /// How often should activity data be sent - in seconds
+    private int realtimeHRMInterval = 10;
+    /// Last battery percentage reported (or -1) to help with smoothing reported battery levels
+    private int lastBatteryPercent = -1;
+
+    private final LimitedQueue<Integer, Long> mNotificationReplyAction = new LimitedQueue<>(16);
+
+    private boolean gpsUpdateSetup = false;
+
+    // this stores the globalUartReceiver (for uart.tx intents)
+    private BroadcastReceiver globalUartReceiver = null;
+
+    // used to make HTTP requests and handle responses
+    private RequestQueue requestQueue = null;
+
+    /// Maximum amount of characters to store in receiveHistory
+    public static final int MAX_RECEIVE_HISTORY_CHARS = 100000;
+    /// Used to avoid spamming logs with ACTION_DEVICE_CHANGED messages
+    static String lastStateString;
+
+    // Local Intents - for app manager communication
+    public static final String BANGLEJS_COMMAND_TX = "banglejs_command_tx";
+    public static final String BANGLEJS_COMMAND_RX = "banglejs_command_rx";
+    // Global Intents
+    private static final String BANGLE_ACTION_UART_TX = "com.banglejs.uart.tx";
+
+    private SleepAsAndroidSender sleepAsAndroidSender;
+
+    public BangleJSDeviceSupport() {
+        super(LOG);
+        addSupportedService(BangleJSConstants.UUID_SERVICE_NORDIC_UART);
+
+        registerLocalIntents();
+        registerGlobalIntents();
+    }
+
+    @Override
+    public void dispose() {
+        super.dispose();
+        stopGlobalUartReceiver();
+        stopLocationUpdate();
+        stopRequestQueue();
+    }
+
+    private void stopGlobalUartReceiver(){
+        if(globalUartReceiver != null){
+            GBApplication.getContext().unregisterReceiver(globalUartReceiver); // remove uart.tx intent listener
+        }
+    }
+
+
+    private void stopLocationUpdate() {
+        if (!gpsUpdateSetup)
+            return;
+        LOG.info("Stop location updates");
+        GBLocationService.stop(getContext(), getDevice());
+        gpsUpdateSetup = false;
+    }
+
+    private void stopRequestQueue() {
+        if (requestQueue != null) {
+            requestQueue.stop();
+        }
+    }
+
+    private RequestQueue getRequestQueue() {
+        if (requestQueue == null) {
+            requestQueue = Volley.newRequestQueue(getContext());
+        }
+        return requestQueue;
+    }
+
+    private void addReceiveHistory(String s) {
+        receiveHistory += s;
+        if (receiveHistory.length() > MAX_RECEIVE_HISTORY_CHARS)
+            receiveHistory = receiveHistory.substring(receiveHistory.length() - MAX_RECEIVE_HISTORY_CHARS);
+    }
+
+    private void registerLocalIntents() {
+        IntentFilter commandFilter = new IntentFilter();
+        commandFilter.addAction(GBDevice.ACTION_DEVICE_CHANGED);
+        commandFilter.addAction(BANGLEJS_COMMAND_TX);
+        BroadcastReceiver commandReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                switch (intent.getAction()) {
+                    case BANGLEJS_COMMAND_TX: {
+                        String data = String.valueOf(intent.getExtras().get("DATA"));
+                        BtLEQueue queue = getQueue();
+                        if (queue==null) {
+                            LOG.warn("BANGLEJS_COMMAND_TX received, but getQueue()==null (state=" + gbDevice.getStateString(context) + ")");
+                        } else {
+                            try {
+                                TransactionBuilder builder = performInitialized("TX");
+                                uartTx(builder, data);
+                                builder.queue(queue);
+                            } catch (IOException e) {
+                                GB.toast(getContext(), "Error in TX: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
+                            }
+                        }
+                        break;
+                    }
+                    case GBDevice.ACTION_DEVICE_CHANGED: {
+                        String stateString = (gbDevice!=null ? gbDevice.getStateString(context):"");
+                        if (!stateString.equals(lastStateString)) {
+                          lastStateString = stateString;
+                          LOG.info("ACTION_DEVICE_CHANGED " + stateString);
+                          addReceiveHistory("\n================================================\nACTION_DEVICE_CHANGED "+stateString+" "+(new SimpleDateFormat("yyyy-MM-dd hh:mm:ss", Locale.US)).format(Calendar.getInstance().getTime())+"\n================================================\n");
+                        }
+                        if (gbDevice!=null && (gbDevice.getState() == GBDevice.State.NOT_CONNECTED || gbDevice.getState() == GBDevice.State.WAITING_FOR_RECONNECT)) {
+                            stopLocationUpdate();
+                        }
+                    }
+                }
+            }
+        };
+        LocalBroadcastManager.getInstance(GBApplication.getContext()).registerReceiver(commandReceiver, commandFilter);
+    }
+
+    private void registerGlobalIntents() {
+        IntentFilter commandFilter = new IntentFilter();
+        commandFilter.addAction(BANGLE_ACTION_UART_TX);
+        globalUartReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                switch (intent.getAction()) {
+                    case BANGLE_ACTION_UART_TX: {
+                        /* In Tasker:
+                          Action: com.banglejs.uart.tx
+                          Cat: None
+                          Extra: line:Terminal.println(%avariable)
+                          Target: Broadcast Receiver
+
+                          Variable: Number, Configure on Import, NOT structured, Value set, Nothing Exported, NOT Same as value
+                         */
+                        Prefs devicePrefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()));
+                        if (!devicePrefs.getBoolean(PREF_DEVICE_INTENTS, false)) return;
+                        String data = intent.getStringExtra("line");
+                        if (data==null) {
+                            GB.toast(getContext(), "UART TX Intent, but no 'line' supplied", Toast.LENGTH_LONG, GB.ERROR);
+                            return;
+                        }
+                        if (!data.endsWith("\n")) data += "\n";
+                        try {
+                            TransactionBuilder builder = performInitialized("TX");
+                            uartTx(builder, data);
+                            builder.queue(getQueue());
+                        } catch (IOException e) {
+                            GB.toast(getContext(), "Error in TX: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
+                        }
+                        break;
+                    }
+                }
+            }
+        };
+
+        ContextCompat.registerReceiver(GBApplication.getContext(), globalUartReceiver, commandFilter, ContextCompat.RECEIVER_EXPORTED);
+    }
+
+    @Override
+    protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
+        LOG.info("Initializing");
+
+        if (sleepAsAndroidSender == null) {
+            sleepAsAndroidSender = new SleepAsAndroidSender(gbDevice);
+        }
+
+        gbDevice.setState(GBDevice.State.INITIALIZING);
+        gbDevice.sendDeviceUpdateIntent(getContext());
+
+        rxCharacteristic = getCharacteristic(BangleJSConstants.UUID_CHARACTERISTIC_NORDIC_UART_RX);
+        txCharacteristic = getCharacteristic(BangleJSConstants.UUID_CHARACTERISTIC_NORDIC_UART_TX);
+        if (rxCharacteristic==null || txCharacteristic==null) {
+            // https://codeberg.org/Freeyourgadget/Gadgetbridge/issues/2996 - sometimes we get
+            // initializeDevice called but no characteristics have been fetched - try and reconnect in that case
+            LOG.warn("RX/TX characteristics are null, will attempt to reconnect");
+            builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.WAITING_FOR_RECONNECT, getContext()));
+        }
+        builder.setCallback(this);
+        builder.notify(rxCharacteristic, true);
+
+        Prefs devicePrefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()));
+        allowHighMTU = devicePrefs.getBoolean(PREF_ALLOW_HIGH_MTU, true);
+
+        if (allowHighMTU) {
+            builder.requestMtu(131);
+        }
+        // No need to clear active line with Ctrl-C now - firmwares in 2023 auto-clear on connect
+
+        Prefs prefs = GBApplication.getPrefs();
+        if (prefs.getBoolean("datetime_synconconnect", true))
+          transmitTime(builder);
+        //sendSettings(builder);
+
+        // get version
+        gbDevice.setState(GBDevice.State.INITIALIZED);
+        gbDevice.sendDeviceUpdateIntent(getContext());
+        if (getDevice().getFirmwareVersion() == null) {
+            getDevice().setFirmwareVersion("N/A");
+            getDevice().setFirmwareVersion2("N/A");
+        }
+        lastBatteryPercent = -1;
+
+        LOG.info("Initialization Done");
+
+        requestBangleGPSPowerStatus();
+
+        return builder;
+    }
+
+    /// Write a string of data, and chunk it up
+    private void uartTx(TransactionBuilder builder, String str) {
+        byte[] bytes = str.getBytes(StandardCharsets.ISO_8859_1);
+        LOG.info("UART TX: " + str);
+        addReceiveHistory("\n================================================\nSENDING "+str+"\n================================================\n");
+        // FIXME: somehow this is still giving us UTF8 data when we put images in strings. Maybe JSON.stringify is converting to UTF-8?
+        for (int i=0;i<bytes.length;i+=mtuSize) {
+            int l = bytes.length-i;
+            if (l>mtuSize) l=mtuSize;
+            byte[] packet = new byte[l];
+            System.arraycopy(bytes, i, packet, 0, l);
+            builder.write(txCharacteristic, packet);
+        }
+    }
+
+    /// Converts an object to a JSON string. see jsonToString
+    private String jsonToStringInternal(Object v) {
+        if (v instanceof String) {
+            /* Convert a string, escaping chars we can't send over out UART connection */
+            String s = (String)v;
+            StringBuilder json = new StringBuilder("\"");
+            boolean hasUnicode = false;
+            //String rawString = "";
+            for (int i=0;i<s.length();i++) {
+                int ch = (int)s.charAt(i); // unicode, so 0..65535 (usually)
+                int nextCh = (int)(i+1<s.length() ? s.charAt(i+1) : 0); // 0..65535
+                //rawString = rawString+ch+",";
+                if (ch>255) hasUnicode = true;
+                if (ch<8) {
+                    // if the next character is a digit, it'd be interpreted
+                    // as a 2 digit octal character, so we can't use `\0` to escape it
+                    if (nextCh>='0' && nextCh<='7') json.append("\\x0").append(ch);
+                    else json.append("\\").append(ch);
+                } else if (ch==8) json.append("\\b");
+                else if (ch==9) json.append("\\t");
+                else if (ch==10) json.append("\\n");
+                else if (ch==11) json.append("\\v");
+                else if (ch==12) json.append("\\f");
+                else if (ch==16) json.append("\\20"); // DLE - not entirely safe to use
+                else if (ch==34) json.append("\\\""); // quote
+                else if (ch==92) json.append("\\\\"); // slash
+                else if (ch<32 || ch==127 || ch==173 ||
+                         ((ch>=0xC2) && (ch<=0xF4))) // unicode start char range
+                    json.append("\\x").append(Integer.toHexString((ch & 255) | 256).substring(1));
+                else if (ch>255)
+                    json.append("\\u").append(Integer.toHexString((ch & 65535) | 65536).substring(1));
+                else json.append(s.charAt(i));
+            }
+            // if it was less characters to send base64, do that!
+            if (!hasUnicode && (json.length() > 5+(s.length()*4/3))) {
+                byte[] bytes = s.getBytes(StandardCharsets.ISO_8859_1);
+                return "atob(\""+Base64.encodeToString(bytes, Base64.DEFAULT).replaceAll("\n","")+"\")";
+            }
+            // for debugging...
+            //addReceiveHistory("\n---------------------\n"+rawString+"\n---------------------\n");
+            return json.append("\"").toString();
+        } else if (v instanceof JSONArray) {
+            JSONArray a = (JSONArray)v;
+            StringBuilder json = new StringBuilder("[");
+            for (int i=0;i<a.length();i++) {
+                if (i>0) json.append(",");
+                Object o = null;
+                try {
+                    o = a.get(i);
+                } catch (JSONException e) {
+                    LOG.warn("jsonToString array error: " + e.getLocalizedMessage());
+                }
+                json.append(jsonToStringInternal(o));
+            }
+            return json.append("]").toString();
+        } else if (v instanceof JSONObject) {
+            JSONObject obj = (JSONObject)v;
+            StringBuilder json = new StringBuilder("{");
+            Iterator<String> iter = obj.keys();
+            while (iter.hasNext()) {
+                String key = iter.next();
+                Object o = null;
+                try {
+                    o = obj.get(key);
+                } catch (JSONException e) {
+                    LOG.warn("jsonToString object error: " + e.getLocalizedMessage());
+                }
+                json.append("\"").append(key).append("\":").append(jsonToStringInternal(o));
+                if (iter.hasNext()) json.append(",");
+            }
+            return json.append("}").toString();
+        } else if (v==null) {
+            // else int/double/null
+            return "null";
+        }
+        return v.toString();
+    }
+
+    /// Convert a JSON object to a JSON String (NOT 100% JSON compliant)
+    public String jsonToString(JSONObject jsonObj) {
+        /* jsonObj.toString() works but breaks char codes>128 (encodes as UTF8?) and also uses
+        \u0000 when just \0 would do (and so on).
+
+        So we do it manually, which can be more compact anyway.
+        This is JSON-ish, so not exactly as per JSON1 spec but good enough for Espruino.
+        */
+        return jsonToStringInternal(jsonObj);
+    }
+
+    /// Write a JSON object of data
+    private void uartTxJSON(String taskName, JSONObject json) {
+        try {
+            TransactionBuilder builder = performInitialized(taskName);
+            uartTx(builder, "\u0010GB("+jsonToString(json)+")\n");
+            builder.queue(getQueue());
+        } catch (IOException e) {
+            GB.toast(getContext(), "Error in "+taskName+": " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
+        }
+    }
+
+    private void uartTxJSONError(String taskName, String message, String id) {
+        JSONObject o = new JSONObject();
+        try {
+            o.put("t", taskName);
+            if( id!=null)
+                o.put("id", id);
+            o.put("err", message);
+        } catch (JSONException e) {
+            GB.toast(getContext(), "uartTxJSONError: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
+        }
+        uartTxJSON(taskName, o);
+    }
+
+
+
+    private void handleUartRxLine(String line) {
+        LOG.info("UART RX LINE: " + line);
+        if (line.length()==0) return;
+        if (">Uncaught ReferenceError: \"GB\" is not defined".equals(line))
+          GB.toast(getContext(), "'Android Integration' plugin not installed on Bangle.js", Toast.LENGTH_LONG, GB.ERROR);
+        else if (line.charAt(0)=='{') {
+            // JSON - we hope!
+            try {
+                JSONObject json = new JSONObject(line);
+                if (json.has("t")) {
+                    handleUartRxJSON(json);
+                    LOG.info("UART RX JSON parsed successfully");
+                } else
+                    LOG.warn("UART RX JSON parsed but doesn't contain 't' - ignoring");
+            } catch (JSONException e) {
+                LOG.error("UART RX JSON parse failure: "+ e.getLocalizedMessage());
+                GB.toast(getContext(), "Malformed JSON from Bangle.js: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
+            }
+        } else if (line.startsWith("data:image/bmp;base64,")) {
+            LOG.debug("Got screenshot bmp");
+            final byte[] screenshotBytes = Base64.decode(line.substring(21), Base64.DEFAULT);
+            final GBDeviceEventScreenshot gbDeviceEventScreenshot = new GBDeviceEventScreenshot(screenshotBytes);
+            evaluateGBDeviceEvent(gbDeviceEventScreenshot);
+        } else {
+            LOG.info("UART RX line started with "+(int)line.charAt(0)+" - ignoring");
+        }
+    }
+
+    private void handleUartRxJSON(JSONObject json) throws JSONException {
+        String packetType = json.getString("t");
+        switch (packetType) {
+            case "info":
+                GB.toast(getContext(), "Bangle.js: " + json.getString("msg"), Toast.LENGTH_LONG, GB.INFO);
+                break;
+            case "warn":
+                GB.toast(getContext(), "Bangle.js: " + json.getString("msg"), Toast.LENGTH_LONG, GB.WARN);
+                break;
+            case "error":
+                GB.toast(getContext(), "Bangle.js: " + json.getString("msg"), Toast.LENGTH_LONG, GB.ERROR);
+                break;
+            case "ver": {
+                final GBDeviceEventVersionInfo gbDeviceEventVersionInfo = new GBDeviceEventVersionInfo();
+                if (json.has("fw"))
+                    gbDeviceEventVersionInfo.fwVersion = json.getString("fw");
+                if (json.has("hw"))
+                    gbDeviceEventVersionInfo.hwVersion = json.getString("hw");
+                evaluateGBDeviceEvent(gbDeviceEventVersionInfo);
+            } break;
+            case "findPhone": {
+                boolean start = json.has("n") && json.getBoolean("n");
+                GBDeviceEventFindPhone deviceEventFindPhone = new GBDeviceEventFindPhone();
+                deviceEventFindPhone.event = start ? GBDeviceEventFindPhone.Event.START : GBDeviceEventFindPhone.Event.STOP;
+                evaluateGBDeviceEvent(deviceEventFindPhone);
+            } break;
+            case "music": {
+                GBDeviceEventMusicControl deviceEventMusicControl = new GBDeviceEventMusicControl();
+                deviceEventMusicControl.event = GBDeviceEventMusicControl.Event.valueOf(json.getString("n").toUpperCase(Locale.US));
+                evaluateGBDeviceEvent(deviceEventMusicControl);
+            } break;
+            case "call": {
+                GBDeviceEventCallControl deviceEventCallControl = new GBDeviceEventCallControl();
+                deviceEventCallControl.event = GBDeviceEventCallControl.Event.valueOf(json.getString("n").toUpperCase(Locale.US));
+                evaluateGBDeviceEvent(deviceEventCallControl);
+            } break;
+            case "status":
+                handleBatteryStatus(json);
+                break;
+            case "notify" :
+                handleNotificationControl(json);
+                break;
+            case "actfetch":
+                handleActivityFetch(json);
+                break;
+            case "act":
+                handleActivity(json);
+                break;
+            case "actTrksList": {
+                JSONObject requestTrackObj = BangleJSActivityTrack.handleActTrksList(json, getDevice(), getContext());
+                if (requestTrackObj!=null) uartTxJSON("requestActivityTrackLog", requestTrackObj);
+            } break;
+            case "actTrk": {
+                JSONObject requestTrackObj = BangleJSActivityTrack.handleActTrk(json, getDevice(), getContext());
+                if (requestTrackObj!=null) uartTxJSON("requestActivityTrackLog", requestTrackObj);
+            } break;
+            case "http":
+                handleHttp(json);
+                break;
+            case "force_calendar_sync":
+                handleCalendarSync(json);
+                break;
+            case "intent":
+                handleIntent(json);
+                break;
+            case "file":
+                handleFile(json);
+                break;
+            case "gps_power": {
+                boolean status = json.getBoolean("status");
+                LOG.info("Got gps power status: " + status);
+                if (status) {
+                    setupGPSUpdateTimer();
+                } else {
+                    stopLocationUpdate();
+                }
+            } break;
+            case "accel":
+                handleAcceleration(json);
+                break;
+            default : {
+                LOG.info("UART RX JSON packet type '"+packetType+"' not understood.");
+            }
+        }
+    }
+
+    @Override
+    public void onSleepAsAndroidAction(String action, Bundle extras) {
+        // Validate if our device can work with an action
+        try {
+            sleepAsAndroidSender.validateAction(action);
+        } catch (UnsupportedOperationException e) {
+            return;
+        }
+
+        // Consult the SleepAsAndroid documentation for a set of actions and their extra
+        // https://docs.sleep.urbandroid.org/devs/wearable_api.html
+        switch (action) {
+            case SleepAsAndroidAction.CHECK_CONNECTED:
+                sleepAsAndroidSender.confirmConnected();
+                break;
+            // Received when the app starts sleep tracking
+            case SleepAsAndroidAction.START_TRACKING:
+                this.enableAccelSender(true);
+                sleepAsAndroidSender.startTracking();
+                break;
+            // Received when the app stops sleep tracking
+            case SleepAsAndroidAction.STOP_TRACKING:
+                this.enableAccelSender(false);
+                sleepAsAndroidSender.stopTracking();
+                break;
+            case SleepAsAndroidAction.SET_SUSPENDED:
+                boolean suspended = extras.getBoolean("SUSPENDED", false);
+                this.enableAccelSender(false);
+                sleepAsAndroidSender.pauseTracking(suspended);
+                break;
+                // Received when the app changes the batch size for the movement data
+            case SleepAsAndroidAction.SET_BATCH_SIZE:
+                long batchSize = extras.getLong("SIZE", 12L);
+                sleepAsAndroidSender.setBatchSize(batchSize);
+                break;
+            // Received when the app sends a notificaation
+            case SleepAsAndroidAction.SHOW_NOTIFICATION:
+                NotificationSpec notificationSpec = new NotificationSpec();
+                notificationSpec.title = extras.getString("TITLE");
+                notificationSpec.body = extras.getString("BODY");
+                this.onNotification(notificationSpec);
+                break;
+            case SleepAsAndroidAction.UPDATE_ALARM:
+                long alarmTimestamp = extras.getLong("TIMESTAMP");
+
+                // Sets the alarm at a giver hour and minute
+                // Snoozing from the app will create a new alarm in the future
+                this.setSleepAsAndroidAlarm(alarmTimestamp);
+                break;
+            default:
+                LOG.warn("Received unsupported " + action);
+                break;
+        }
+    }
+
+    private void enableAccelSender(boolean enable) {
+        /**
+         * Sends an event to the Banglejs to enable/disable Acceleration tracking
+         * @param enable: whether to enable tracking
+         **/
+        try {
+            JSONObject o = new JSONObject();
+            o.put("t", "accelsender");
+            o.put("enable", enable);
+            o.put("interval", 1000);
+            uartTxJSON("enableAccelSender", o);
+        } catch (JSONException e) {
+            LOG.info("JSONException: " + e.getLocalizedMessage());
+        }
+    }
+
+    private void setSleepAsAndroidAlarm(long alarmTimestamp) {
+        /**
+         * Updates the Sleep as Android Alarm slot.
+         * @param alarmTimestamp: Unix timestamp of the upcoming alarm.
+         **/
+        Calendar calendar = Calendar.getInstance();
+        calendar.setTimeInMillis(new Timestamp(alarmTimestamp).getTime());
+
+        // Get Alarm in relevant slot
+        nodomain.freeyourgadget.gadgetbridge.entities.Alarm currentAlarm = DBHelper.getAlarms(gbDevice).get(SleepAsAndroidSender.getAlarmSlot());
+        currentAlarm.setRepetition(Alarm.ALARM_ONCE);
+        currentAlarm.setHour(calendar.get(Calendar.HOUR_OF_DAY));
+        currentAlarm.setMinute(calendar.get(Calendar.MINUTE));
+        currentAlarm.setEnabled(true);
+        currentAlarm.setUnused(false);
+
+        // Store modified alarm
+        DBHelper.store(currentAlarm);
+
+        // Send alarms to Gadgetbridge
+        this.onSetAlarms(new ArrayList<Alarm>(DBHelper.getAlarms(gbDevice)));
+
+    }
+
+    /**
+     * Handle "accel" packets: Acceleration data streaming
+     */
+    private void handleAcceleration(JSONObject json) throws JSONException {
+        if (json.has("accel")) {
+            JSONObject accel = json.getJSONObject("accel");
+            sleepAsAndroidSender.onAccelChanged((float) (accel.getDouble("x") * 9.80665),
+                    (float) (accel.getDouble("y") * 9.80665), (float) (accel.getDouble("z") * 9.80665));
+        }
+    }
+
+    /**
+     * Handle "status" packets: battery info updates
+     */
+    private void handleBatteryStatus(JSONObject json) throws JSONException {
+        GBDeviceEventBatteryInfo batteryInfo = new GBDeviceEventBatteryInfo();
+        batteryInfo.state = BatteryState.UNKNOWN;
+        if (json.has("chg")) {
+            batteryInfo.state = (json.getInt("chg") == 1) ? BatteryState.BATTERY_CHARGING : BatteryState.BATTERY_NORMAL;
+        }
+        if (json.has("bat")) {
+            int b = json.getInt("bat");
+            if (b < 0) b = 0;
+            if (b > 100) b = 100;
+            // smooth out battery level reporting (it can only go up if charging, or down if discharging)
+            // http://forum.espruino.com/conversations/379294
+            if (lastBatteryPercent<0) lastBatteryPercent = b;
+            if (batteryInfo.state == BatteryState.BATTERY_NORMAL && b > lastBatteryPercent)
+                b = lastBatteryPercent;
+            if (batteryInfo.state == BatteryState.BATTERY_CHARGING && b < lastBatteryPercent)
+                b = lastBatteryPercent;
+            lastBatteryPercent = b;
+            batteryInfo.level = b;
+        }
+
+        if (json.has("volt"))
+            batteryInfo.voltage = (float) json.getDouble("volt");
+        handleGBDeviceEvent(batteryInfo);
+    }
+
+    /**
+     * Handle "notify" packet, used to send notification control from device to GB
+     */
+    private void handleNotificationControl(JSONObject json) throws JSONException {
+
+        String response = json.getString("n").toUpperCase(Locale.US);
+        LOG.debug("Notification response: " + response);
+
+        // Wake the Android device if the setting is toggled on by user.
+        // Doesn't work if run after the notification handling below for some reason (which
+        // I'd rather do since the screen would only be opened once the content was ready).
+        Prefs devicePrefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()));
+        if (devicePrefs.getBoolean(PREF_NOTIFICATION_WAKE_ON_OPEN, false) && response.equals("OPEN")) {
+            WakeActivity.start(getContext());
+        }
+
+        GBDeviceEventNotificationControl deviceEvtNotificationControl = new GBDeviceEventNotificationControl();
+        // .title appears unused
+        deviceEvtNotificationControl.event = GBDeviceEventNotificationControl.Event.valueOf(response);
+        if (json.has("id"))
+            deviceEvtNotificationControl.handle = json.getInt("id");
+        if (json.has("tel"))
+            deviceEvtNotificationControl.phoneNumber = json.getString("tel");
+        if (json.has("msg"))
+            deviceEvtNotificationControl.reply = json.getString("msg");
+        /* REPLY responses don't use the ID from the event (MUTE/etc seem to), but instead
+         * they use a handle that was provided in an action list on the onNotification.. event  */
+        if (deviceEvtNotificationControl.event == GBDeviceEventNotificationControl.Event.REPLY) {
+            Long foundHandle = mNotificationReplyAction.lookup((int)deviceEvtNotificationControl.handle);
+            if (foundHandle!=null)
+                deviceEvtNotificationControl.handle = foundHandle;
+        }
+        evaluateGBDeviceEvent(deviceEvtNotificationControl);
+    }
+
+    private void handleActivityFetch(final JSONObject json) throws JSONException {
+        final String state = json.getString("state");
+        if ("start".equals(state)) {
+            GB.updateTransferNotification(getContext().getString(R.string.busy_task_fetch_activity_data),"", true, 0, getContext());
+            getDevice().setBusyTask(getContext().getString(R.string.busy_task_fetch_activity_data));
+        } else if ("end".equals(state)) {
+            saveLastSyncTimestamp(System.currentTimeMillis() - 1000L * 60);
+            getDevice().unsetBusyTask();
+            GB.updateTransferNotification(null, "", false, 100, getContext());
+        } else {
+            LOG.warn("Unknown actfetch state {}", state);
+        }
+
+        final GBDeviceEventUpdatePreferences event = new GBDeviceEventUpdatePreferences()
+                .withPreference(PREF_BANGLEJS_ACTIVITY_FULL_SYNC_STATUS, state);
+        evaluateGBDeviceEvent(event);
+
+        getDevice().sendDeviceUpdateIntent(getContext());
+    }
+
+    /**
+     * Handle "act" packet, used to send activity reports
+     */
+    private void handleActivity(JSONObject json) {
+        BangleJSActivitySample sample = new BangleJSActivitySample();
+        int timestamp = (int) (json.optLong("ts", System.currentTimeMillis()) / 1000);
+        int hrm = json.optInt("hrm", 0);
+        int steps = json.optInt("stp", 0);
+        int intensity = json.optInt("mov", ActivitySample.NOT_MEASURED);
+        boolean realtime = json.optInt("rt", 0) == 1;
+        ActivityKind activity = ActivityKind.ACTIVITY;
+        if (json.has("act")) {
+            try {
+                String actName = json.optString("act","").toUpperCase(Locale.US);
+                activity = ActivityKind.valueOf(actName);
+            } catch (final Exception e) {
+                LOG.warn("JSON activity not known", e);
+                activity = ActivityKind.UNKNOWN;
+            }
+        }
+        if(hrm>0) {
+            sleepAsAndroidSender.onHrChanged(hrm, 0);
+        }
+        sample.setTimestamp(timestamp);
+        sample.setRawKind(activity.getCode());
+        sample.setHeartRate(hrm);
+        sample.setSteps(steps);
+        sample.setRawIntensity(intensity);
+        if (!realtime) {
+            try (DBHandler dbHandler = GBApplication.acquireDB()) {
+                final Long userId = getUser(dbHandler.getDaoSession()).getId();
+                final Long deviceId = DBHelper.getDevice(getDevice(), dbHandler.getDaoSession()).getId();
+                BangleJSSampleProvider provider = new BangleJSSampleProvider(getDevice(), dbHandler.getDaoSession());
+                sample.setDeviceId(deviceId);
+                sample.setUserId(userId);
+                provider.upsertSample(sample);
+            } catch (final Exception ex) {
+                LOG.warn("Error saving activity: " + ex.getLocalizedMessage());
+            }
+        }
+
+        // push realtime data
+        if (realtime && (realtimeHRM || realtimeStep)) {
+            Intent intent = new Intent(DeviceService.ACTION_REALTIME_SAMPLES)
+                    .putExtra(GBDevice.EXTRA_DEVICE, getDevice())
+                    .putExtra(DeviceService.EXTRA_REALTIME_SAMPLE, sample);
+            LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent);
+        }
+    }
+
+    /**
+     * Handle "http" packet: make an HTTP request and return a "http" response
+     */
+    private void handleHttp(JSONObject json) throws JSONException {
+        String _id = null;
+        try {
+            _id = json.getString("id");
+        } catch (JSONException e) {
+        }
+        final String id = _id;
+
+        if (! BuildConfig.INTERNET_ACCESS) {
+            uartTxJSONError("http", "Internet access not enabled, check Gadgetbridge Device Settings", id);
+            return;
+        }
+
+        Prefs devicePrefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()));
+        if (! devicePrefs.getBoolean(PREF_DEVICE_INTERNET_ACCESS, false)) {
+            uartTxJSONError("http", "Internet access not enabled in this Gadgetbridge build", id);
+            return;
+        }
+
+        String url = json.getString("url");
+
+        int method = Request.Method.GET;
+        if (json.has("method")) {
+            String m = json.getString("method").toLowerCase(Locale.US);
+            if (m.equals("get")) method = Request.Method.GET;
+            else if (m.equals("post")) method = Request.Method.POST;
+            else if (m.equals("head")) method = Request.Method.HEAD;
+            else if (m.equals("put")) method = Request.Method.PUT;
+            else if (m.equals("patch")) method = Request.Method.PATCH;
+            else if (m.equals("delete")) method = Request.Method.DELETE;
+            else uartTxJSONError("http", "Unknown HTTP method "+m,id);
+        }
+
+        byte[] _body = null;
+        if (json.has("body"))
+            _body = json.getString("body").getBytes();
+        final byte[] body = _body;
+
+        Map<String,String> _headers = null;
+        if (json.has("headers")) {
+            JSONObject h = json.getJSONObject("headers");
+            _headers = new HashMap<String,String>();
+            Iterator<String> iter = h.keys();
+            while (iter.hasNext()) {
+                String key = iter.next();
+                try {
+                    String value = h.getString(key);
+                    _headers.put(key, value);
+                } catch (JSONException e) {
+                }
+            }
+        }
+        final Map<String,String> headers = _headers;
+
+        String _xmlPath = "";
+        String _xmlReturn = "";
+        try {
+            _xmlPath = json.getString("xpath");
+            _xmlReturn = json.getString("return");
+        } catch (JSONException e) {
+        }
+        final String xmlPath = _xmlPath;
+        final String xmlReturn = _xmlReturn;
+        // Request a string response from the provided URL.
+        StringRequest stringRequest = new StringRequest(method, url,
+                new Response.Listener<String>() {
+                    @Override
+                    public void onResponse(String response) {
+                        JSONObject o = new JSONObject();
+                        if (xmlPath.length() != 0) {
+                            try {
+                                InputSource inputXML = new InputSource(new StringReader(response));
+                                XPath xPath = XPathFactory.newInstance().newXPath();
+                                if (xmlReturn.equals("array")) {
+                                    NodeList result = (NodeList) xPath.evaluate(xmlPath, inputXML, XPathConstants.NODESET);
+                                    response = null; // don't add it below
+                                    JSONArray arr = new JSONArray();
+                                    if (result != null) {
+                                        for (int i = 0; i < result.getLength(); i++)
+                                            arr.put(result.item(i).getTextContent());
+                                    }
+                                    o.put("resp", arr);
+                                } else {
+                                    response = xPath.evaluate(xmlPath, inputXML);
+                                }
+                            } catch (Exception error) {
+                                uartTxJSONError("http", error.toString(), id);
+                                return;
+                            }
+                        }
+                        try {
+                            o.put("t", "http");
+                            if( id!=null)
+                                o.put("id", id);
+                            if (response!=null)
+                                o.put("resp", response);
+                        } catch (JSONException e) {
+                            GB.toast(getContext(), "HTTP: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
+                        }
+                        uartTxJSON("http", o);
+                    }
+                }, new Response.ErrorListener() {
+            @Override
+            public void onErrorResponse(VolleyError error) {
+                uartTxJSONError("http", error.toString(), id);
+            }
+        }) {
+            @Override
+            public byte[] getBody() throws AuthFailureError {
+                if (body == null) return super.getBody();
+                return body;
+            }
+
+            @Override
+            public Map<String, String> getHeaders() throws AuthFailureError {
+                // clone the data from super.getHeaders() so we can write to it
+                Map<String, String> h = new HashMap<>(super.getHeaders());
+                if (headers != null) {
+                    for (String key : headers.keySet()) {
+                        String value = headers.get(key);
+                        h.put(key, value);
+                    }
+                }
+                return h;
+            }
+        };
+        RequestQueue queue = getRequestQueue();
+        queue.add(stringRequest);
+    }
+
+    /**
+     * Handle "force_calendar_sync" packet
+     */
+    private void handleCalendarSync(JSONObject json) throws JSONException {
+        if (!getDevicePrefs().getBoolean("sync_calendar", false)) {
+            LOG.debug("Ignoring calendar sync request, sync is disabled");
+            return;
+        }
+        //pretty much like the updateEvents in CalendarReceiver, but would need a lot of libraries here
+        JSONArray ids = json.getJSONArray("ids");
+        ArrayList<Long> idsList = new ArrayList<>(ids.length());
+        ArrayList<Long> idsDeletedList = new ArrayList<>(ids.length());
+        try (DBHandler dbHandler = GBApplication.acquireDB()) {
+            DaoSession session = dbHandler.getDaoSession();
+            Long deviceId = DBHelper.getDevice(gbDevice, session).getId();
+            QueryBuilder<CalendarSyncState> qb = session.getCalendarSyncStateDao().queryBuilder();
+            //FIXME just use that and don't query every time?
+            List<CalendarSyncState> states = qb.where(
+                    CalendarSyncStateDao.Properties.DeviceId.eq(deviceId)).build().list();
+
+            LOG.info("force_calendar_sync on banglejs: "+ ids.length() +" events on the device, "+ states.size() +" on our db");
+            for (int i = 0; i < ids.length(); i++) {
+                Long id = ids.getLong(i);
+                qb = session.getCalendarSyncStateDao().queryBuilder(); //is this needed again?
+                CalendarSyncState calendarSyncState = qb.where(
+                        qb.and(CalendarSyncStateDao.Properties.DeviceId.eq(deviceId),
+                                CalendarSyncStateDao.Properties.CalendarEntryId.eq(id))).build().unique();
+                if(calendarSyncState == null) {
+                    idsDeletedList.add(id);
+                    onDeleteCalendarEvent((byte)0, id);
+                    LOG.info("event id="+ id +" is on device id="+ deviceId +", removing it there");
+                } else {
+                    //used for later, no need to check twice the ones that do not match
+                    idsList.add(id);
+                }
+            }
+            // Now issue the command to delete from the Bangle
+            if (idsDeletedList.size() > 0)
+                deleteCalendarEvents(idsDeletedList);
+
+            //remove all elements not in ids from database (we don't have them)
+            for(CalendarSyncState calendarSyncState : states) {
+                long id = calendarSyncState.getCalendarEntryId();
+                if(!idsList.contains(id)) {
+                    qb = session.getCalendarSyncStateDao().queryBuilder(); //is this needed again?
+                    qb.where(qb.and(CalendarSyncStateDao.Properties.DeviceId.eq(deviceId),
+                                    CalendarSyncStateDao.Properties.CalendarEntryId.eq(id)))
+                            .buildDelete().executeDeleteWithoutDetachingEntities();
+                    LOG.info("event id="+ id +" is not on device id="+ deviceId +", removing from our db");
+                }
+            }
+        } catch (Exception e1) {
+            GB.toast("Database Error while forcefully syncing Calendar", Toast.LENGTH_SHORT, GB.ERROR, e1);
+        }
+        //force a syncCalendar now, send missing events
+        CalendarReceiver.forceSync();
+    }
+
+    /**
+     * Handle "intent" packet: broadcast an Android intent
+     */
+    private void handleIntent(JSONObject json) throws JSONException {
+        Prefs devicePrefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()));
+        if (!devicePrefs.getBoolean(PREF_DEVICE_INTENTS, false)) {
+            uartTxJSONError("intent", "Android Intents not enabled, check Gadgetbridge Device Settings", null);
+            return;
+        }
+
+        String target = json.has("target") ? json.getString("target") : "broadcastreceiver";
+        Intent in = new Intent();
+        if (json.has("action")) in.setAction(json.getString("action"));
+        if (json.has("flags")) {
+            JSONArray flags = json.getJSONArray("flags");
+            for (int i = 0; i < flags.length(); i++) {
+                in = addIntentFlag(in, flags.getString(i));
+            }
+        }
+        if (json.has("categories")) {
+            JSONArray categories = json.getJSONArray("categories");
+            for (int i = 0; i < categories.length(); i++) {
+                in.addCategory(categories.getString(i));
+            }
+        }
+        if (json.has("package") && !json.has("class")) {
+            in = json.getString("package").equals("gadgetbridge") ?
+                    in.setPackage(this.getContext().getPackageName()) :
+                    in.setPackage(json.getString("package"));
+        }
+        if (json.has("package") && json.has("class")) {
+            in = json.getString("package").equals("gadgetbridge") ?
+                    in.setClassName(this.getContext().getPackageName(), json.getString("class")) :
+                    in.setClassName(json.getString("package"), json.getString("class"));
+        }
+
+        if (json.has("mimetype")) in.setType(json.getString("mimetype"));
+        if (json.has("data")) in.setData(Uri.parse(json.getString("data")));
+        if (json.has("extra")) {
+            JSONObject extra = json.getJSONObject("extra");
+            Iterator<String> iter = extra.keys();
+            while (iter.hasNext()) {
+                String key = iter.next();
+                in.putExtra(key, extra.getString(key)); // Should this be implemented for other types, e.g. extra.getInt(key)? Or will this always work even if receiving ints/doubles/etc.?
+            }
+        }
+        LOG.info("Executing intent:\n\t" + String.valueOf(in) + "\n\tTargeting: " + target);
+        //GB.toast(getContext(), String.valueOf(in), Toast.LENGTH_LONG, GB.INFO);
+        switch (target) {
+            case "broadcastreceiver":
+                getContext().sendBroadcast(in);
+                break;
+            case "activity": // See wakeActivity.java if you want to start activities from under the keyguard/lock sceen.
+                getContext().startActivity(in);
+                break;
+            case "service": // Should this be implemented differently, e.g. workManager?
+                getContext().startService(in);
+                break;
+            case "foregroundservice": // Should this be implemented differently, e.g. workManager?
+                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+                    getContext().startForegroundService(in);
+                } else {
+                    getContext().startService(in);
+                }
+                break;
+            default:
+                LOG.info("Targeting '"+target+"' isn't implemented or doesn't exist.");
+                GB.toast(getContext(), "Targeting '"+target+"' isn't implemented or it doesn't exist.", Toast.LENGTH_LONG, GB.INFO);
+        }
+    }
+
+    private Intent addIntentFlag(Intent intent, String flag) {
+        try {
+            final Class<Intent> intentClass = Intent.class;
+            final Field flagField = intentClass.getDeclaredField(flag);
+            intent.addFlags(flagField.getInt(null));
+        } catch (final Exception e) {
+            // The user sent an invalid flag
+            LOG.info("Flag '"+flag+"' isn't implemented or doesn't exist and was therefore not set.");
+            GB.toast(getContext(), "Flag '"+flag+"' isn't implemented or it doesn't exist and was therefore not set.", Toast.LENGTH_LONG, GB.INFO);
+        }
+        return intent;
+    }
+
+    private void handleFile(JSONObject json) throws JSONException {
+
+        File dir;
+        try {
+            dir = new File(FileUtils.getExternalFilesDir() + "/" + FileUtils.makeValidFileName(getDevice().getName()));
+            if (!dir.isDirectory()) {
+                if (!dir.mkdir()) {
+                    throw new IOException("Cannot create device specific directory for " + getDevice().getName());
+                }
+            }
+        } catch (IOException e) {
+            LOG.error("Could not get directory to write to with error: " + e);
+            return;
+        }
+        String filename = json.getString("n");
+        String filenameThatCantEscapeDir = filename.replaceAll("/","");
+
+        LOG.debug("Compare filename and filenameThatCantEscapeDir:\n" + filename + "\n" + filenameThatCantEscapeDir);
+        File outputFile = new File(dir, filenameThatCantEscapeDir);
+        String mode = "append";
+        if (json.getString("m").equals("w")) {
+            mode = "write";
+        }
+        try {
+            FileUtils.copyStringToFile(json.getString("c"), outputFile, mode);
+            LOG.info("Writing to "+outputFile);
+        } catch (IOException e) {
+            LOG.warn("Could not write to " + outputFile + "with error: " + e);
+        }
+    }
+
+    @Override
+    public void onSendConfiguration(final String config) {
+        switch (config) {
+            case PREF_BANGLEJS_ACTIVITY_FULL_SYNC_START:
+                fetchActivityData(0);
+                return;
+        }
+
+        LOG.warn("Unknown config changed: {}", config);
+    }
+
+    @Override
+    public boolean onCharacteristicChanged(BluetoothGatt gatt,
+                                           BluetoothGattCharacteristic characteristic) {
+        if (super.onCharacteristicChanged(gatt, characteristic)) {
+            return true;
+        }
+        if (BangleJSConstants.UUID_CHARACTERISTIC_NORDIC_UART_RX.equals(characteristic.getUuid())) {
+            byte[] chars = characteristic.getValue();
+            // check to see if we get more data - if so, increase out MTU for sending
+            if (allowHighMTU && chars.length > mtuSize)
+                mtuSize = chars.length;
+            // Scan for flow control characters
+            for (int i=0;i<chars.length;i++) {
+                boolean ignoreChar = false;
+                if (chars[i]==19 /* XOFF */) {
+                    getQueue().setPaused(true);
+                    LOG.info("RX: XOFF");
+                    ignoreChar = true;
+                }
+                if (chars[i]==17 /* XON */) {
+                    getQueue().setPaused(false);
+                    LOG.info("RX: XON");
+                    ignoreChar = true;
+                }
+                if (ignoreChar) {
+                    // remove char from the array. Generally only one XON/XOFF per stream so creating a new array each time is fine
+                    byte[] c = new byte[chars.length - 1];
+                    System.arraycopy(chars, 0, c, 0, i); // copy before
+                    System.arraycopy(chars, i+1, c, i, chars.length - i - 1); // copy after
+                    chars = c;
+                    i--; // back up one (because we deleted it)
+                }
+            }
+            String packetStr = new String(chars, StandardCharsets.ISO_8859_1);
+            LOG.debug("RX: " + packetStr);
+            // logging
+            addReceiveHistory(packetStr);
+            // split into input lines
+            receivedLine += packetStr;
+            while (receivedLine.contains("\n")) {
+                int p = receivedLine.indexOf("\n");
+                String line = receivedLine.substring(0,(p>0) ? (p-1) : 0);
+                receivedLine = receivedLine.substring(p+1);
+                handleUartRxLine(line);
+            }
+            // Send an intent with new data
+            Intent intent = new Intent(BangleJSDeviceSupport.BANGLEJS_COMMAND_RX);
+            intent.putExtra("DATA", packetStr);
+            intent.putExtra("SEQ", bangleCommandSeq++);
+            LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent);
+        }
+        return false;
+    }
+
+
+    void transmitTime(TransactionBuilder builder) {
+      long ts = System.currentTimeMillis();
+      float tz = SimpleTimeZone.getDefault().getOffset(ts) / (1000 * 60 * 60.0f);
+      // set time
+      String cmd = "\u0010setTime("+(ts/1000)+");";
+      // set timezone
+      cmd += "E.setTimeZone("+tz+");";
+      // write timezone to settings
+      cmd += "(s=>s&&(s.timezone="+tz+",require('Storage').write('setting.json',s)))(require('Storage').readJSON('setting.json',1))";
+      uartTx(builder, cmd+"\n");
+    }
+
+    void requestBangleGPSPowerStatus() {
+        try {
+            JSONObject o = new JSONObject();
+            o.put("t", "is_gps_active");
+            LOG.debug("Requesting gps power status: " + o.toString());
+            uartTxJSON("is_gps_active", o);
+        } catch (JSONException e) {
+            GB.toast(getContext(), "uartTxJSONError: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
+        }
+    }
+
+    void setupGPSUpdateTimer() {
+        if (gpsUpdateSetup) {
+            LOG.debug("GPS position timer is already setup");
+            return;
+        }
+        Prefs devicePrefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress()));
+        if(devicePrefs.getBoolean(PREF_DEVICE_GPS_UPDATE, false)) {
+            int intervalLength = devicePrefs.getInt(PREF_DEVICE_GPS_UPDATE_INTERVAL, 1000);
+            LOG.info("Setup location listener with an update interval of " + intervalLength + " ms");
+            boolean onlyUseNetworkGPS = devicePrefs.getBoolean(PREF_DEVICE_GPS_USE_NETWORK_ONLY, false);
+            LOG.info("Using combined GPS and NETWORK based location: " + onlyUseNetworkGPS);
+            if (!onlyUseNetworkGPS) {
+                try {
+                    GBLocationService.start(getContext(), getDevice(), GBLocationProviderType.GPS, intervalLength);
+                } catch (IllegalArgumentException e) {
+                    LOG.warn("GPS provider could not be started", e);
+                }
+            }
+
+            try {
+                GBLocationService.start(getContext(), getDevice(), GBLocationProviderType.NETWORK, intervalLength);
+            } catch (IllegalArgumentException e) {
+                LOG.warn("NETWORK provider could not be started", e);
+            }
+        } else {
+            GB.toast("Phone gps data update is deactivated in the settings", Toast.LENGTH_SHORT, GB.INFO);
+        }
+        gpsUpdateSetup = true;
+    }
+
+    @Override
+    public void onSetGpsLocation(final Location location) {
+        if (!GBApplication.getPrefs().getBoolean("use_updated_location_if_available", false)) return;
+        LOG.debug("new location: " + location.toString());
+        JSONObject o = new JSONObject();
+        try {
+            o.put("t", "gps");
+            o.put("lat", location.getLatitude());
+            o.put("lon", location.getLongitude());
+            o.put("alt", location.getAltitude());
+            o.put("speed", location.getSpeed()*3.6); // m/s to kph
+            if (location.hasBearing()) o.put("course", location.getBearing());
+            o.put("time", location.getTime());
+            if (location.getExtras() != null) {
+                LOG.debug("Found number of satellites: " + location.getExtras().getInt("satellites", -1));
+                o.put("satellites",location.getExtras().getInt("satellites"));
+            } else {
+                o.put("satellites", 0);
+            }
+            o.put("hdop", location.getAccuracy());
+            o.put("externalSource", true);
+            o.put("gpsSource", location.getProvider());
+            LOG.debug("Sending gps value: " + o.toString());
+            uartTxJSON("gps", o);
+        } catch (JSONException e) {
+            GB.toast(getContext(), "uartTxJSONError: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
+        }
+    }
+
+
+
+    @Override
+    public boolean useAutoConnect() {
+        return true;
+    }
+
+    private String renderUnicodeWordPartAsImage(final String word) {
+        // check for emoji
+        final EmojiManager emojiManager = EmojiConverter.getEmojiManager(getContext());
+        final boolean hasEmoji = !EmojiParserKt.extractEmojis(emojiManager, word).isEmpty();
+        // if we had emoji, ensure we create 3 bit color (not 1 bit B&W)
+        final BangleJSBitmapStyle style = hasEmoji ? BangleJSBitmapStyle.RGB_3BPP_TRANSPARENT : BangleJSBitmapStyle.MONOCHROME_TRANSPARENT;
+        return "\0"+bitmapToEspruinoString(textToBitmap(word), style);
+    }
+
+    private String renderUnicodeWordAsImage(String word) {
+        // if we have Chinese/Japanese/Korean chars, split into 2 char chunks to allow easier text wrapping
+        // it's not perfect but better than nothing
+        boolean hasCJK = false;
+        for (int i=0;i<word.length();i++) {
+            char ch = word.charAt(i);
+            hasCJK |= ch>=0x4E00 && ch<=0x9FFF; // "CJK Unified Ideographs" block
+        }
+        if (hasCJK) {
+            // split every 2 chars
+            StringBuilder result = new StringBuilder();
+            for (int i=0;i<word.length();i+=2) {
+                int len = 2;
+                if (i+len > word.length())
+                    len = word.length()-i;
+                result.append(renderUnicodeWordPartAsImage(word.substring(i, i + len)));
+            }
+            return result.toString();
+        }
+        // else just render the word as-is
+        return renderUnicodeWordPartAsImage(word);
+    }
+
+    /* is the given character code an emoji? Note that `char` is only 0..65535, so many emoji will
+      actually be made of 2 surrogate chars. To work around this, we just assume that anything with a surrogate
+      is an emoji, which will be true in most cases */
+    public boolean isCharCodeEmoji(char ch) {
+        if (ch>=0x2190 && ch<=0x21FF) return true;
+        if (ch>=0x2600 && ch<=0x26FF) return true;
+        if (ch>=0x2700 && ch<=0x27BF) return true;
+        if (ch>=0x3000 && ch<=0x303F) return true;
+        if (ch>=0xD800 && ch<=0xDFFF) return true; // high/low surrogate
+        if (ch>=0xFE00 && ch<=0xFE0F) return true; // variation selector 1 (2 is U+E0100 so is in a surrogate)
+        //if (ch>=0x1F300 && ch<=0x1F64F) return true; // needs a surrogate
+        //if (ch>=0x1F680 && ch<=0x1F6FF) return true; // needs a surrogate
+        return false;
+    }
+
+    public String renderUnicodeAsImage(String txt) {
+        // FIXME: it looks like we could implement this as customStringFilter now so it happens automatically
+        if (txt==null) return null;
+        // Simple conversions
+        txt = txt.replaceAll("鈥�", "...");
+        /* If we're not doing conversion, pass this right back (we use the EmojiConverter
+        As we would have done if BangleJSCoordinator.supportsUnicodeEmojis had reported false */
+        Prefs devicePrefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()));
+        if (!devicePrefs.getBoolean(PREF_BANGLEJS_TEXT_BITMAP, false))
+            return EmojiConverter.convertUnicodeEmojiToAscii(txt, GBApplication.getContext());
+         // Otherwise split up and check each word
+        String word = "";
+        StringBuilder result = new StringBuilder();
+        boolean needsTranslate = false;
+        boolean wordIsAllEmoji = true;
+        for (int i=0;i<txt.length();i++) {
+            char ch = txt.charAt(i);
+            // Special cases where we can just use a built-in character...
+            // Based on https://op.europa.eu/en/web/eu-vocabularies/formex/physical-specifications/character-encoding
+            if (ch=='鈥�' || ch=='鈥�' || ch=='鈥�') ch='-';
+            else if (ch =='鈥�' || ch=='锛�' || ch=='銆�') ch=',';
+            else if (ch =='銆�') ch='.';
+            else if (ch =='銆�') ch='[';
+            else if (ch =='銆�') ch=']';
+            else if (ch=='鈥�' || ch=='鈥�' || ch=='鈥�' || ch=='鈥�' || ch=='痛') ch='\'';
+            else if (ch=='鈥�' || ch=='鈥�' || ch =='鈥�' || ch=='鈥�' || ch=='鈥�') ch='"';
+            else if (ch == 0xFEFF) continue; // nonbreaking space - ignore
+            boolean isCharEmoji = isCharCodeEmoji(ch);
+            if (isCharEmoji) {
+                if (!wordIsAllEmoji) {
+                    // if the word is all emoji we are fine, just fall through.
+                    // but here we have characters already and we want to translate those
+                    // separately, so we can have a color emoji on its own.
+                    if (needsTranslate) { // convert word
+                        LOG.info("renderUnicodeAsImage converting " + word);
+                        result.append(renderUnicodeWordAsImage(word));
+                    } else { // or just copy across
+                        result.append(word);
+                    }
+                    word = "";
+                    wordIsAllEmoji = true;
+                }
+                needsTranslate = true;
+            } else {
+                if (!word.isEmpty() && wordIsAllEmoji) {
+                    // this isn't am emoji, but it follows one - render that separately!
+                    result.append(renderUnicodeWordAsImage(word));
+                    word = "";
+                    needsTranslate = false;
+                }
+                wordIsAllEmoji = ch>=0xFE00;
+            }
+            // chars which break words up
+            if (" -_/:.,?!'\"&*()[]".indexOf(ch)>=0) {
+                // word split
+                if (needsTranslate) { // convert word
+                    LOG.info("renderUnicodeAsImage converting " + word);
+                    result.append(renderUnicodeWordAsImage(word)).append(ch);
+                } else { // or just copy across
+                    result.append(word).append(ch);
+                }
+                word = "";
+                needsTranslate = false;
+                wordIsAllEmoji = true;
+            } else {
+                // TODO: better check?
+                if (ch>255) needsTranslate = true;
+                word += ch;
+            }
+        }
+        if (needsTranslate) { // convert word
+            LOG.info("renderUnicodeAsImage converting " + word);
+            result.append(renderUnicodeWordAsImage(word));
+        } else { // or just copy across
+            result.append(word);
+        }
+        return result.toString();
+    }
+
+    /// Crop a text string to ensure it's not longer than requested
+    public String cropToLength(String txt, int len) {
+        if (txt==null) return "";
+        if (txt.length()<=len) return txt;
+        return txt.substring(0,len-3)+"...";
+    }
+
+    @Override
+    public void onNotification(NotificationSpec notificationSpec) {
+        if (!getDevicePrefs().getBoolean(DeviceSettingsPreferenceConst.PREF_SEND_APP_NOTIFICATIONS, true)) {
+            LOG.debug("App notifications disabled - ignoring");
+            return;
+        }
+
+        boolean canReply = false;
+        if (notificationSpec.attachedActions!=null)
+            for (int i=0;i<notificationSpec.attachedActions.size();i++) {
+                NotificationSpec.Action action = notificationSpec.attachedActions.get(i);
+                if (action.type==NotificationSpec.Action.TYPE_WEARABLE_REPLY) {
+                    mNotificationReplyAction.add(notificationSpec.getId(), action.handle);
+                    canReply = true;
+                }
+            }
+        // sourceName isn't set for SMS messages
+        String src = notificationSpec.sourceName;
+        if (notificationSpec.type == NotificationType.GENERIC_SMS)
+            src = "SMS Message";
+        // Send JSON to Bangle.js
+        try {
+            JSONObject o = new JSONObject();
+            o.put("t", "notify");
+            o.put("id", notificationSpec.getId());
+            o.put("src", src);
+            o.put("title", renderUnicodeAsImage(cropToLength(notificationSpec.title,80)));
+            o.put("subject", renderUnicodeAsImage(cropToLength(notificationSpec.subject,80)));
+            o.put("body", renderUnicodeAsImage(cropToLength(notificationSpec.body, 400)));
+            o.put("sender", renderUnicodeAsImage(cropToLength(notificationSpec.sender,40)));
+            o.put("tel", notificationSpec.phoneNumber);
+            if (canReply) o.put("reply", true);
+            uartTxJSON("onNotification", o);
+        } catch (JSONException e) {
+            LOG.info("JSONException: " + e.getLocalizedMessage());
+        }
+    }
+
+    @Override
+    public void onDeleteNotification(int id) {
+        try {
+            JSONObject o = new JSONObject();
+            o.put("t", "notify-");
+            o.put("id", id);
+            uartTxJSON("onDeleteNotification", o);
+        } catch (JSONException e) {
+            LOG.info("JSONException: " + e.getLocalizedMessage());
+        }
+    }
+
+    @Override
+    public void onSetTime() {
+        try {
+            TransactionBuilder builder = performInitialized("setTime");
+            transmitTime(builder);
+            //TODO: once we have a common strategy for sending events (e.g. EventHandler), remove this call from here. Meanwhile it does no harm.
+            // = we should generalize the pebble calender code
+            forceCalendarSync();
+            builder.queue(getQueue());
+        } catch (Exception e) {
+            GB.toast(getContext(), "Error setting time: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
+        }
+    }
+
+    @Override
+    public void onSetCannedMessages(CannedMessagesSpec cannedMessagesSpec) {
+        try {
+            JSONObject o = new JSONObject();
+            o.put("t", "canned_responses_sync");
+            JSONArray jsonMessages = new JSONArray();
+            o.put("d", jsonMessages);
+
+            for (String message : cannedMessagesSpec.cannedMessages) {
+                JSONObject jsonMessage = new JSONObject();
+                jsonMessages.put(jsonMessage);
+                // Render unicode (emojis etc.) as an image for BangleJS to display
+                String unicodeRenderedAsImage = renderUnicodeAsImage(message);
+                // If the initial and rendered messages are not the same, include the rendered message as "disp(lay)" text so unicode is rendered on device
+                if (!unicodeRenderedAsImage.equals(message)) {
+                    jsonMessage.put("disp", unicodeRenderedAsImage);
+                }
+                jsonMessage.put("text", message);
+            }
+            uartTxJSON("onSetCannedMessages", o);
+        } catch (JSONException e) {
+            LOG.info("JSONException: " + e.getLocalizedMessage());
+        }
+    }
+
+    @Override
+    public void onSetAlarms(ArrayList<? extends Alarm> alarms) {
+        try {
+            JSONObject o = new JSONObject();
+            o.put("t", "alarm");
+            JSONArray jsonalarms = new JSONArray();
+            o.put("d", jsonalarms);
+
+            for (Alarm alarm : alarms) {
+                if (alarm.getUnused()) continue;
+                JSONObject jsonalarm = new JSONObject();
+                jsonalarms.put(jsonalarm);
+                //Calendar calendar = AlarmUtils.toCalendar(alarm);
+                jsonalarm.put("h", alarm.getHour());
+                jsonalarm.put("m", alarm.getMinute());
+                jsonalarm.put("rep", alarm.getRepetition());
+                jsonalarm.put("on", alarm.getEnabled());
+            }
+            uartTxJSON("onSetAlarms", o);
+        } catch (JSONException e) {
+            LOG.info("JSONException: " + e.getLocalizedMessage());
+        }
+    }
+
+    @Override
+    public void onSetCallState(CallSpec callSpec) {
+        try {
+            JSONObject o = new JSONObject();
+            o.put("t", "call");
+            String cmdName = "";
+            try {
+                Field[] fields = callSpec.getClass().getDeclaredFields();
+                for (Field field : fields)
+                    if (field.getName().startsWith("CALL_") && field.getInt(callSpec) == callSpec.command)
+                        cmdName = field.getName().substring(5).toLowerCase(Locale.US);
+            } catch (IllegalAccessException e) {}
+            o.put("cmd", cmdName);
+            o.put("name", renderUnicodeAsImage(callSpec.name));
+            o.put("number", callSpec.number);
+            uartTxJSON("onSetCallState", o);
+        } catch (JSONException e) {
+            LOG.info("JSONException: " + e.getLocalizedMessage());
+        }
+    }
+
+    @Override
+    public void onSetMusicState(MusicStateSpec stateSpec) {
+        try {
+            JSONObject o = new JSONObject();
+            o.put("t", "musicstate");
+            int musicState = stateSpec.state;
+            String[] musicStates = {"play", "pause", "stop", ""};
+            if (musicState<0) musicState=3;
+            if (musicState>=musicStates.length) musicState = musicStates.length-1;
+            o.put("state", musicStates[musicState]);
+            o.put("position", stateSpec.position);
+            o.put("shuffle", stateSpec.shuffle);
+            o.put("repeat", stateSpec.repeat);
+            uartTxJSON("onSetMusicState", o);
+        } catch (JSONException e) {
+            LOG.info("JSONException: " + e.getLocalizedMessage());
+        }
+    }
+
+    @Override
+    public void onSetMusicInfo(MusicSpec musicSpec) {
+        try {
+            JSONObject o = new JSONObject();
+            o.put("t", "musicinfo");
+            o.put("artist", renderUnicodeAsImage(musicSpec.artist));
+            o.put("album", renderUnicodeAsImage(musicSpec.album));
+            o.put("track", renderUnicodeAsImage(musicSpec.track));
+            o.put("dur", musicSpec.duration);
+            o.put("c", musicSpec.trackCount);
+            o.put("n", musicSpec.trackNr);
+            uartTxJSON("onSetMusicInfo", o);
+        } catch (JSONException e) {
+            LOG.info("JSONException: " + e.getLocalizedMessage());
+        }
+    }
+
+    private void transmitActivityStatus() {
+        try {
+            JSONObject o = new JSONObject();
+            o.put("t", "act");
+            o.put("hrm", realtimeHRM);
+            o.put("stp", realtimeStep);
+            o.put("int", realtimeHRMInterval);
+            uartTxJSON("onEnableRealtimeSteps", o);
+        } catch (JSONException e) {
+            LOG.info("JSONException: " + e.getLocalizedMessage());
+        }
+    }
+
+    @Override
+    public void onEnableRealtimeSteps(boolean enable) {
+        if (enable == realtimeHRM) return;
+        realtimeStep = enable;
+        transmitActivityStatus();
+    }
+
+    @Override
+    public void onFetchRecordedData(int dataTypes) {
+        // FIXME: The fetches are currently mutually exclusive, otherwise the operations will
+        // interrupt one-another
+        if ((dataTypes & RecordedDataTypes.TYPE_ACTIVITY) != 0)  {
+            fetchActivityData(getLastSuccessfulSyncTime());
+        } else if ((dataTypes & RecordedDataTypes.TYPE_GPS_TRACKS) !=0) {
+            JSONObject requestTracksListObj = BangleJSActivityTrack.compileTracksListRequest(getDevice(), getContext());
+            uartTxJSON("requestActivityTracksList", requestTracksListObj);
+        } else if ((dataTypes & RecordedDataTypes.TYPE_DEBUGLOGS) !=0) {
+            File dir;
+            try {
+                dir = FileUtils.getExternalFilesDir();
+            } catch (IOException e) {
+                return;
+            }
+            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd-HHmmss", Locale.US);
+            String filename = "banglejs_debug_" + dateFormat.format(new Date()) + ".log";
+            File outputFile = new File(dir, filename);
+            LOG.warn("Writing log to "+outputFile.toString());
+            try {
+                BufferedWriter writer = new BufferedWriter(new FileWriter(outputFile));
+                writer.write(receiveHistory);
+                writer.close();
+                receiveHistory = "";
+                GB.toast(getContext(), "Log written to "+filename, Toast.LENGTH_LONG, GB.INFO);
+            } catch (IOException e) {
+                LOG.warn("Could not write to file", e);
+            }
+        }
+    }
+
+    protected void fetchActivityData(final long timestampMillis) {
+        try {
+            JSONObject o = new JSONObject();
+            o.put("t", "actfetch");
+            o.put("ts", timestampMillis);
+            uartTxJSON("fetch activity data", o);
+        } catch (final JSONException e) {
+            LOG.warn("Failed to fetch activity data", e);
+        }
+    }
+
+    protected String getLastSyncTimeKey() {
+        return "lastSyncTimeMillis";
+    }
+
+    protected void saveLastSyncTimestamp(final long timestamp) {
+        final SharedPreferences.Editor editor = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress()).edit();
+        editor.putLong(getLastSyncTimeKey(), timestamp);
+        editor.apply();
+    }
+
+    protected long getLastSuccessfulSyncTime() {
+        long timeStampMillis = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress()).getLong(getLastSyncTimeKey(), 0);
+        if (timeStampMillis != 0) {
+            return timeStampMillis;
+        }
+        final GregorianCalendar calendar = BLETypeConversions.createCalendar();
+        calendar.add(Calendar.DAY_OF_MONTH, -1);
+        return calendar.getTimeInMillis();
+    }
+
+    @Override
+    public void onEnableRealtimeHeartRateMeasurement(boolean enable) {
+        if (enable == realtimeHRM) return;
+        realtimeHRM = enable;
+        transmitActivityStatus();
+    }
+
+    @Override
+    public void onFindDevice(boolean start) {
+        try {
+            JSONObject o = new JSONObject();
+            o.put("t", "find");
+            o.put("n", start);
+            uartTxJSON("onFindDevice", o);
+        } catch (JSONException e) {
+            LOG.info("JSONException: " + e.getLocalizedMessage());
+        }
+    }
+
+    @Override
+    public void onSetConstantVibration(int integer) {
+        try {
+            JSONObject o = new JSONObject();
+            o.put("t", "vibrate");
+            o.put("n", integer);
+            uartTxJSON("onSetConstantVibration", o);
+        } catch (JSONException e) {
+            LOG.info("JSONException: " + e.getLocalizedMessage());
+        }
+    }
+
+    @Override
+    public void onScreenshotReq() {
+        try {
+            final TransactionBuilder builder = performInitialized("screenshot");
+            uartTx(builder, "\u0010g.dump()\n");
+            builder.queue(getQueue());
+        } catch (final IOException e) {
+            GB.toast(getContext(), "Failed to get screenshot: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
+        }
+    }
+
+    @Override
+    public void onSetHeartRateMeasurementInterval(int seconds) {
+        realtimeHRMInterval = seconds;
+        transmitActivityStatus();
+    }
+
+    private List<LoyaltyCard> filterSupportedCards(final List<LoyaltyCard> cards) {
+        final List<LoyaltyCard> ret = new ArrayList<>();
+        for (final LoyaltyCard card : cards) {
+            // we hardcode here what is supported
+            if (card.getBarcodeFormat() == BarcodeFormat.CODE_39 ||
+                    card.getBarcodeFormat() == BarcodeFormat.CODABAR ||
+                    card.getBarcodeFormat() == BarcodeFormat.EAN_8 ||
+                    card.getBarcodeFormat() == BarcodeFormat.EAN_13 ||
+                    card.getBarcodeFormat() == BarcodeFormat.UPC_A ||
+                    card.getBarcodeFormat() == BarcodeFormat.UPC_E ||
+                    card.getBarcodeFormat() == BarcodeFormat.QR_CODE) {
+                ret.add(card);
+            }
+        }
+        return ret;
+    }
+
+    @Override
+    public void onSetLoyaltyCards(final ArrayList<LoyaltyCard> cards) {
+        final List<LoyaltyCard> supportedCards = filterSupportedCards(cards);
+        try {
+            JSONObject encoded_cards = new JSONObject();
+            JSONArray a = new JSONArray();
+            for (final LoyaltyCard card : supportedCards) {
+                JSONObject o = new JSONObject();
+                o.put("id", card.getId());
+                o.put("name", renderUnicodeAsImage(cropToLength(card.getName(),40)));
+                if (card.getBarcodeId() != null) {
+                    o.put("value", card.getBarcodeId());
+                } else {
+                    o.put("value", card.getCardId());
+                }
+                if (card.getBarcodeFormat() != null)
+                    o.put("type", card.getBarcodeFormat().toString());
+                if (card.getExpiry() != null)
+                    o.put("expiration", card.getExpiry().getTime()/1000);
+                o.put("color", card.getColor());
+                // we somehow cannot distinguish no balance defined with 0 P
+                if (card.getBalance() != null && card.getBalance().signum() != 0
+                        || card.getBalanceType() != null) {
+                    // if currency is points it is not reported
+                    String balanceType = card.getBalanceType() != null ?
+                        card.getBalanceType().toString() : "P";
+                    o.put("balance", renderUnicodeAsImage(cropToLength(card.getBalance() +
+                                    " " + balanceType, 20)));
+                }
+                if (card.getNote() != null)
+                    o.put("note", renderUnicodeAsImage(cropToLength(card.getNote(),200)));
+                a.put(o);
+            }
+            encoded_cards.put("t", "cards");
+            encoded_cards.put("d", a);
+            uartTxJSON("onSetLoyaltyCards", encoded_cards);
+        } catch (JSONException e) {
+            LOG.info("JSONException: " + e.getLocalizedMessage());
+        }
+    }
+
+    @Override
+    public void onAddCalendarEvent(CalendarEventSpec calendarEventSpec) {
+        if (!getDevicePrefs().getBoolean("sync_calendar", false)) {
+            LOG.debug("Ignoring add calendar event {}, sync is disabled", calendarEventSpec.id);
+            return;
+        }
+        String description = calendarEventSpec.description;
+        if (description != null) {
+            // remove any HTML formatting
+            if (description.startsWith("<html"))
+                description = androidx.core.text.HtmlCompat.fromHtml(description, HtmlCompat.FROM_HTML_MODE_LEGACY).toString();
+            // Replace "-::~:~::~:~:~:~:~:~:~:~:~:~:~:~:~:~:~:~:~:~:~:~:~:~:~:~:~:~:~:~:~:~:~:~:~:~:~:~::~:~::-" lines from Google meet
+            description = ("\n"+description+"\n").replaceAll("\n-[:~-]*\n","");
+            // Replace ____________________ from MicrosoftTeams
+            description = description.replaceAll("__________+", "");
+            // replace double newlines and trim beginning and end
+            description = description.replaceAll("\n\\s*\n","\n").trim();
+        }
+        try {
+            JSONObject o = new JSONObject();
+            o.put("t", "calendar");
+            o.put("id", calendarEventSpec.id);
+            o.put("type", calendarEventSpec.type); //implement this too? (sunrise and set)
+            o.put("timestamp", calendarEventSpec.timestamp);
+            o.put("durationInSeconds", calendarEventSpec.durationInSeconds);
+            o.put("title", renderUnicodeAsImage(cropToLength(calendarEventSpec.title,40)));
+            o.put("description", renderUnicodeAsImage(cropToLength(description,200)));
+            o.put("location", renderUnicodeAsImage(cropToLength(calendarEventSpec.location,40)));
+            o.put("calName", cropToLength(calendarEventSpec.calName,20));
+            o.put("color", calendarEventSpec.color);
+            o.put("allDay", calendarEventSpec.allDay);
+            uartTxJSON("onAddCalendarEvent", o);
+        } catch (JSONException e) {
+            LOG.info("JSONException: " + e.getLocalizedMessage());
+        }
+    }
+
+    @Override
+    public void onDeleteCalendarEvent(byte type, long id) {
+        // FIXME: CalenderReceiver will call this directly - can we somehow batch up delete calls and use deleteCalendarEvents?
+        if (!getDevicePrefs().getBoolean("sync_calendar", false)) {
+            LOG.debug("Ignoring delete calendar event {}, sync is disabled", id);
+            return;
+        }
+        try {
+            JSONObject o = new JSONObject();
+            o.put("t", "calendar-");
+            o.put("id", id);
+            uartTxJSON("onDeleteCalendarEvent", o);
+        } catch (JSONException e) {
+            LOG.info("JSONException: " + e.getLocalizedMessage());
+        }
+    }
+
+    /* Called when we need to get rid of multiple calendar events */
+    public void deleteCalendarEvents(ArrayList<Long> ids) {
+        if (!getDevicePrefs().getBoolean("sync_calendar", false)) {
+            LOG.debug("Ignoring delete calendar events {}, sync is disabled", ids);
+            return;
+        }
+        if (!ids.isEmpty())
+            try {
+                JSONObject o = new JSONObject();
+                o.put("t", "calendar-");
+                if (ids.size() == 1) {
+                    o.put("id", ids.get(0));
+                } else {
+                    JSONArray a = new JSONArray();
+                    for (long id : ids) a.put(id);
+                    o.put("id", a);
+                }
+                uartTxJSON("onDeleteCalendarEvent", o);
+            } catch (JSONException e) {
+                LOG.info("JSONException: " + e.getLocalizedMessage());
+            }
+    }
+
+    @Override
+    public void onSendWeather(ArrayList<WeatherSpec> weatherSpecs) {
+        WeatherSpec weatherSpec = weatherSpecs.get(0);
+        try {
+            JSONObject o = new JSONObject();
+            o.put("t", "weather");
+            o.put("temp", weatherSpec.currentTemp);
+            o.put("hi", weatherSpec.todayMaxTemp);
+            o.put("lo", weatherSpec.todayMinTemp );
+            o.put("hum", weatherSpec.currentHumidity);
+            o.put("rain", weatherSpec.precipProbability);
+            o.put("uv", Math.round(weatherSpec.uvIndex*10)/10);
+            o.put("code", weatherSpec.currentConditionCode);
+            o.put("txt", weatherSpec.currentCondition);
+            o.put("wind", Math.round(weatherSpec.windSpeed*100)/100.0);
+            o.put("wdir", weatherSpec.windDirection);
+            o.put("loc", weatherSpec.location);
+            uartTxJSON("onSendWeather", o);
+        } catch (JSONException e) {
+            LOG.info("JSONException: " + e.getLocalizedMessage());
+        }
+    }
+
+    public Bitmap textToBitmap(String text) {
+        Paint paint = new Paint(0); // Paint.ANTI_ALIAS_FLAG not wanted as 1bpp
+        Prefs devicePrefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()));
+        paint.setTextSize(devicePrefs.getInt(PREF_BANGLEJS_TEXT_BITMAP_SIZE, 18));
+        paint.setColor(0xFFFFFFFF);
+        paint.setTextAlign(Paint.Align.LEFT);
+        float baseline = -paint.ascent(); // ascent() is negative
+        int width = (int) (paint.measureText(text) + 0.5f); // round
+        int height = (int) (baseline + paint.descent() + 0.5f);
+        if (width<1) width=1;
+        if (height<1) height=1;
+        Bitmap image = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+        Canvas canvas = new Canvas(image);
+        canvas.drawText(text, 0, baseline, paint);
+        return image;
+    }
+
+    @Override
+    public boolean getImplicitCallbackModify() {
+        return true;
+    }
+
+    @Override
+    public boolean getSendWriteRequestResponse() {
+        return false;
+    }
+
+    public enum BangleJSBitmapStyle {
+        MONOCHROME, // 1bpp
+        MONOCHROME_TRANSPARENT, // 1bpp, black = transparent
+        RGB_3BPP, // 3bpp
+        RGB_3BPP_TRANSPARENT // 3bpp, least used color as transparent
+    }
+
+    /** Used for writing single bits to an array */
+    public static class BitWriter {
+        int n;
+        final byte[] bits;
+        int currentByte, bitIdx;
+
+        public BitWriter(byte[] array, int offset) {
+            bits = array;
+            n = offset;
+        }
+
+        public void push(boolean v) {
+            currentByte = (currentByte << 1) | (v?1:0);
+            bitIdx++;
+            if (bitIdx == 8) {
+                bits[n++] = (byte)currentByte;
+                bitIdx = 0;
+                currentByte = 0;
+            }
+        }
+
+        public void finish() {
+            if (bitIdx > 0) bits[n++] = (byte)currentByte;
+        }
+    }
+
+    /** Convert an Android bitmap to a base64 string for use in Espruino.
+     * Currently only 1bpp, no scaling */
+    public static byte[] bitmapToEspruinoArray(Bitmap bitmap, BangleJSBitmapStyle style) {
+        int width = bitmap.getWidth();
+        int height = bitmap.getHeight();
+        if (width>255) {
+            LOG.warn("bitmapToEspruinoArray width of "+width+" > 255 (Espruino max) - cropping");
+            width = 255;
+        }
+        if (height>255) {
+            LOG.warn("bitmapToEspruinoArray height of "+height+" > 255 (Espruino max) - cropping");
+            height = 255;
+        }
+        int bpp = (style==BangleJSBitmapStyle.RGB_3BPP ||
+                   style==BangleJSBitmapStyle.RGB_3BPP_TRANSPARENT) ? 3 : 1;
+        byte[] pixels = new byte[width * height];
+        final byte PIXELCOL_TRANSPARENT = -1;
+        final int[] ditherMatrix = {1*16,5*16,7*16,3*16}; // for bayer dithering
+        // if doing RGB_3BPP_TRANSPARENT, check image to see if it's transparent
+        // MONOCHROME_TRANSPARENT is handled later on...
+        boolean allowTransparency = (style == BangleJSBitmapStyle.RGB_3BPP_TRANSPARENT);
+        boolean isTransparent = false;
+        byte transparentColorIndex = 0;
+        /* Work out what colour index each pixel should be and write to pixels.
+         Also figure out if we're transparent at all, and how often each color is used */
+        int[] colUsage = new int[8];
+        int n = 0;
+        for (int y = 0; y < height; y++) {
+            for (int x = 0; x < width; x++) {
+                int pixel = bitmap.getPixel(x, y);
+                int r = pixel & 255;
+                int g = (pixel >> 8) & 255;
+                int b = (pixel >> 16) & 255;
+                int a = (pixel >> 24) & 255;
+                boolean pixelTransparent = allowTransparency && (a < 128);
+                if (pixelTransparent) {
+                    isTransparent = true;
+                    r = g = b = 0;
+                }
+                // do dithering here
+                int ditherAmt = ditherMatrix[(x&1) + (y&1)*2];
+                r += ditherAmt;
+                g += ditherAmt;
+                b += ditherAmt;
+                int col = 0;
+                if (bpp==1)
+                    col = ((r+g+b) >= 768)?1:0;
+                else if (bpp==3)
+                    col = ((r>=256)?1:0) | ((g>=256)?2:0) | ((b>=256)?4:0);
+                if (!pixelTransparent) colUsage[col]++; // if not transparent, record usage
+                // save colour, mark transparent separately
+                pixels[n++] = (byte)(pixelTransparent ? PIXELCOL_TRANSPARENT : col);
+            }
+        }
+        // if we're transparent, find the least-used color, and use that for transparency
+        if (isTransparent) {
+            // find least used
+            int minColUsage = -1;
+            for (int c=0;c<8;c++) {
+                if (minColUsage<0 || colUsage[c]<minColUsage) {
+                    minColUsage = colUsage[c];
+                    transparentColorIndex = (byte)c;
+                }
+            }
+            // rewrite any transparent pixels as the correct color for transparency
+            for (n=0;n<pixels.length;n++)
+                if (pixels[n]==PIXELCOL_TRANSPARENT)
+                    pixels[n] = transparentColorIndex;
+        }
+        // if we're MONOCHROME_TRANSPARENT, force transparency on bg color
+        if (style == BangleJSBitmapStyle.MONOCHROME_TRANSPARENT) {
+            isTransparent = true;
+            transparentColorIndex = 0;
+        }
+        // Write the header
+        int headerLen = isTransparent ? 4 : 3;
+        byte[] bmp = new byte[(((height * width * bpp) + 7) >> 3) + headerLen];
+        bmp[0] = (byte)width;
+        bmp[1] = (byte)height;
+        bmp[2] = (byte)(bpp + (isTransparent?128:0));
+        if (isTransparent) bmp[3] = transparentColorIndex;
+        // Now write the image out bit by bit
+        BitWriter bits = new BitWriter(bmp, headerLen);
+        n = 0;
+        for (int y = 0; y < height; y++) {
+            for (int x = 0; x < width; x++) {
+                int pixel = pixels[n++];
+                for (int b=bpp-1;b>=0;b--)
+                    bits.push(((pixel>>b)&1) != 0);
+            }
+        }
+        bits.finish();
+        return bmp;
+    }
+
+    /** Convert an Android bitmap to a base64 string for use in Espruino.
+     * Currently only 1bpp, no scaling */
+    public static String bitmapToEspruinoString(Bitmap bitmap, BangleJSBitmapStyle style) {
+        return new String(bitmapToEspruinoArray(bitmap, style), StandardCharsets.ISO_8859_1);
+    }
+
+    /** Convert an Android bitmap to a base64 string for use in Espruino.
+     * Currently only 1bpp, no scaling */
+    public static String bitmapToEspruinoBase64(Bitmap bitmap, BangleJSBitmapStyle style) {
+        return Base64.encodeToString(bitmapToEspruinoArray(bitmap, style), Base64.DEFAULT).replaceAll("\n","");
+    }
+
+    /** Convert a drawable to a bitmap, for use with bitmapToEspruino */
+    public static Bitmap drawableToBitmap(Drawable drawable) {
+        final int maxWidth = 32;
+        final int maxHeight = 32;
+        /* Return bitmap directly but only if it's small enough. It could be
+        we have a bitmap but it's just too big to send direct to the bangle */
+        if (drawable instanceof BitmapDrawable) {
+            BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable;
+            Bitmap bmp = bitmapDrawable.getBitmap();
+            if (bmp != null && bmp.getWidth()<=maxWidth && bmp.getHeight()<=maxHeight)
+                return bmp;
+        }
+        /* Otherwise render this to a bitmap ourselves.. work out size */
+        int w = maxWidth;
+        int h = maxHeight;
+        if (drawable.getIntrinsicWidth() > 0 && drawable.getIntrinsicHeight() > 0) {
+            w = drawable.getIntrinsicWidth();
+            h = drawable.getIntrinsicHeight();
+            // don't allocate anything too big, but keep the ratio
+            if (w>maxWidth) {
+                h = h * maxWidth / w;
+                w = maxWidth;
+            }
+            if (h>maxHeight) {
+                w = w * maxHeight / h;
+                h = maxHeight;
+            }
+        }
+        /* render */
+        Bitmap bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); // Single color bitmap will be created of 1x1 pixel
+        Canvas canvas = new Canvas(bitmap);
+        drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
+        drawable.draw(canvas);
+        return bitmap;
+    }
+
+    /*
+     * Request the banglejs to send all ids to sync with our database
+     * TODO perhaps implement a minimum interval between consecutive requests
+     */
+    private void forceCalendarSync() {
+        try {
+            JSONObject o = new JSONObject();
+            o.put("t", "force_calendar_sync_start");
+            uartTxJSON("forceCalendarSync", o);
+        } catch(JSONException e) {
+            LOG.info("JSONException: " + e.getLocalizedMessage());
+        }
+    }
+
+    @Override
+    public void onSetNavigationInfo(NavigationInfoSpec navigationInfoSpec) {
+        try {
+            JSONObject o = new JSONObject();
+            o.put("t", "nav");
+            if (navigationInfoSpec.instruction!=null)
+                o.put("instr", navigationInfoSpec.instruction);
+            o.put("distance", navigationInfoSpec.distanceToTurn);
+            String[] navActions = {
+                    "","continue", "left", "left_slight", "left_sharp",  "right", "right_slight",
+                    "right_sharp", "keep_left", "keep_right", "uturn_left", "uturn_right",
+                    "offroute", "roundabout_right", "roundabout_left", "roundabout_straight", "roundabout_uturn", "finish"};
+            if (navigationInfoSpec.nextAction>0 && navigationInfoSpec.nextAction<navActions.length)
+                o.put("action", navActions[navigationInfoSpec.nextAction]);
+            if (navigationInfoSpec.ETA!=null)
+                o.put("eta", navigationInfoSpec.ETA);
+            uartTxJSON("onSetNavigationInfo", o);
+        } catch (JSONException e) {
+            LOG.info("JSONException: " + e.getLocalizedMessage());
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/binary_sensor/BinarySensorBaseSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/binary_sensor/BinarySensorBaseSupport.java
new file mode 100644
index 0000000..4e6e339
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/binary_sensor/BinarySensorBaseSupport.java
@@ -0,0 +1,42 @@
+/*  Copyright (C) 2022-2024 Arjan Schrijver, Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.binary_sensor;
+
+import org.slf4j.Logger;
+
+import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
+
+public class BinarySensorBaseSupport extends AbstractBTLEDeviceSupport {
+    public BinarySensorBaseSupport(Logger logger) {
+        super(logger);
+    }
+
+    @Override
+    public boolean useAutoConnect() {
+        return false;
+    }
+
+    @Override
+    public boolean getImplicitCallbackModify() {
+        return true;
+    }
+
+    @Override
+    public boolean getSendWriteRequestResponse() {
+        return false;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/binary_sensor/BinarySensorSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/binary_sensor/BinarySensorSupport.java
new file mode 100644
index 0000000..c733c5e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/binary_sensor/BinarySensorSupport.java
@@ -0,0 +1,203 @@
+/*  Copyright (C) 2022-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.binary_sensor;
+
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.bluetooth.BluetoothGattDescriptor;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.widget.Toast;
+
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.binary_sensor.protocol.constants.MessageId;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.binary_sensor.protocol.constants.ParameterId;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.binary_sensor.protocol.constants.ReportState;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.binary_sensor.protocol.constants.SensorType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.binary_sensor.protocol.message.GetSensorRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.binary_sensor.protocol.message.Response;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.binary_sensor.protocol.message.SetSensorRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.binary_sensor.protocol.parameter.Parameter;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.binary_sensor.protocol.parameter.SensorState;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class BinarySensorSupport extends BinarySensorBaseSupport {
+    final public static String BINARY_SENSOR_SERVICE_UUID = "0000183b-0000-1000-8000-00805f9b34fb";
+    final public static String BINARY_SENSOR_CONTROL_CHARACTERISTIC_UUID = "00002b2b-0000-1000-8000-00805f9b34fb";
+    final public static String BINARY_SENSOR_RESPONSE_CHARACTERISTIC_UUID = "00002b2c-0000-1000-8000-00805f9b34fb";
+
+    final public static String ACTION_SENSOR_STATE_CHANGED = "nodomain.freeyourgadget.gadgetbridge.binary_sensor.STATE_CHANGED";
+    final public static String ACTION_SENSOR_STATE_REQUEST = "nodomain.freeyourgadget.gadgetbridge.binary_sensor.STATE_REQUEST";
+
+    private static final Logger logger = LoggerFactory.getLogger(BinarySensorSupport.class);
+
+    private nodomain.freeyourgadget.gadgetbridge.service.devices.binary_sensor.protocol.constants.SensorState sensorState = null;
+    private int sensorCount = -1;
+
+    public BinarySensorSupport() {
+        super(logger);
+        addSupportedService(UUID.fromString(BINARY_SENSOR_SERVICE_UUID));
+
+        LocalBroadcastManager.getInstance(getContext())
+                .registerReceiver(
+                        stateRequestReceiver,
+                        new IntentFilter(ACTION_SENSOR_STATE_REQUEST)
+                );
+    }
+
+    @Override
+    public void dispose() {
+        super.dispose();
+
+        LocalBroadcastManager.getInstance(getContext())
+                .unregisterReceiver(stateRequestReceiver);
+    }
+
+    BroadcastReceiver stateRequestReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            sendStateChangeIntent(false);
+        }
+    };
+
+    @Override
+    public boolean onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
+        if (characteristic.getUuid().toString().equals(BINARY_SENSOR_RESPONSE_CHARACTERISTIC_UUID)) {
+            handleResponseValue(characteristic.getValue());
+            return true;
+        }
+
+        return false;
+    }
+
+    Response decodeResponse(byte[] value) {
+        ByteBuffer buffer = ByteBuffer.wrap(value);
+        buffer.get(); // split packet header
+        buffer.get(); // RFU
+        byte messageIdByte = buffer.get();
+        buffer.get(); // RFU
+        int parameterCount = buffer.get();
+
+        Parameter[] parameters = new Parameter[parameterCount];
+
+        MessageId messageId = MessageId.fromMessageIdByte(messageIdByte);
+        for (int i = 0; i < parameterCount; i++) {
+            byte parameterIdByte = buffer.get();
+            byte payloadLength = buffer.get();
+            buffer.get(); // RFU
+            buffer.get(); // RFU
+
+            ParameterId parameterId = ParameterId.fromParameterIdByte(parameterIdByte);
+
+            byte[] payload = new byte[payloadLength];
+            buffer.get(payload);
+
+            parameters[i] = Parameter.decode(parameterId, payload);
+        }
+
+        return new Response(
+                messageId,
+                parameters
+        );
+    }
+
+    void sendStateChangeIntent(boolean sendGlobally){
+        Intent intent = new Intent(ACTION_SENSOR_STATE_CHANGED);
+
+        intent.putExtra("EXTRA_SENSOR_CLOSED", sensorState == nodomain.freeyourgadget.gadgetbridge.service.devices.binary_sensor.protocol.constants.SensorState.SENSOR_STATE_CLOSED);
+        intent.putExtra("EXTRA_SENSOR_COUNT", sensorCount);
+
+        LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent);
+        if(sendGlobally) {
+            getContext().sendBroadcast(intent);
+        }
+    }
+
+    void handleResponseValue(byte[] value) {
+        Response response = decodeResponse(value);
+
+        for (Parameter parameter : response.getParameters()) {
+            if (parameter instanceof SensorState) {
+                if(getDevice().getState() != GBDevice.State.INITIALIZED){
+                    new TransactionBuilder("set device state")
+                            .add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZED, getContext()))
+                            .queue(getQueue());
+                }
+
+                SensorState stateParameter = (SensorState) parameter;
+                logger.debug("sensor state: " + stateParameter.getSensorState() + "  count: " + stateParameter.getCount());
+
+                this.sensorState = stateParameter.getSensorState();
+                this.sensorCount = stateParameter.getCount();
+
+                sendStateChangeIntent(true);
+            }
+        }
+    }
+
+    private void sendPacketToDevice(byte[] data, TransactionBuilder builder) {
+        byte[] fullData = new byte[data.length + 1];
+        fullData[0] = 0x00;
+        System.arraycopy(data, 0, fullData, 1, data.length);
+
+        builder.write(getCharacteristic(UUID.fromString(BINARY_SENSOR_CONTROL_CHARACTERISTIC_UUID)), fullData);
+    }
+
+    private void sendPacketToDevice(byte[] data) {
+        TransactionBuilder builder = new TransactionBuilder("BSS control");
+        sendPacketToDevice(data, builder);
+        builder.queue(getQueue());
+    }
+
+    @Override
+    public boolean onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
+        if (status != BluetoothGatt.GATT_SUCCESS) {
+            GB.toast("error setting indication", Toast.LENGTH_LONG, GB.ERROR);
+        }
+        return true;
+    }
+
+    @Override
+    protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
+        logger.debug("initializing device");
+
+        builder
+                .add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext()))
+                .notify(getCharacteristic(UUID.fromString(BINARY_SENSOR_RESPONSE_CHARACTERISTIC_UUID)), true)
+        ;
+
+        SetSensorRequest setSensorRequest = new SetSensorRequest(SensorType.SENSOR_TYPE_OPENING_CLOSING, ReportState.REPORT_STATUS_ENABLED);
+        GetSensorRequest getSensorRequest = new GetSensorRequest(SensorType.SENSOR_TYPE_OPENING_CLOSING);
+
+        sendPacketToDevice(getSensorRequest.encode(), builder);
+        sendPacketToDevice(setSensorRequest.encode(), builder);
+
+        return builder;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/binary_sensor/protocol/constants/MessageId.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/binary_sensor/protocol/constants/MessageId.java
new file mode 100644
index 0000000..2350e77
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/binary_sensor/protocol/constants/MessageId.java
@@ -0,0 +1,38 @@
+/*  Copyright (C) 2022-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.binary_sensor.protocol.constants;
+
+public enum MessageId {
+    MESSAGE_ID_GET_SENSOR_REQUEST,
+    MESSAGE_ID_GET_SENSOR_RESPONSE,
+    MESSAGE_ID_SET_SENSOR_REQUEST,
+    MESSAGE_ID_SET_SENSOR_RESPONSE,
+    MESSAGE_ID_SENSOR_STATUS_EVENT;
+
+    public byte getMessageIdByte(){
+        return (byte) ordinal();
+    }
+
+    public static MessageId fromMessageIdByte(byte messageIdByte){
+        for(MessageId value:MessageId.values()){
+            if(value.getMessageIdByte() == messageIdByte){
+                return value;
+            }
+        }
+        return null;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/binary_sensor/protocol/constants/ParameterId.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/binary_sensor/protocol/constants/ParameterId.java
new file mode 100644
index 0000000..f30b485
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/binary_sensor/protocol/constants/ParameterId.java
@@ -0,0 +1,46 @@
+/*  Copyright (C) 2022-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.binary_sensor.protocol.constants;
+
+public enum ParameterId {
+    PARAMETER_ID_RESULT_CODE((byte) 0x00),
+    PARAMETER_ID_CANCEL((byte) 0x01),
+    PARAMETER_ID_SENSOR_TYPE((byte) 0x02),
+    PARAMETER_ID_REPORT_STATUS((byte) 0x03),
+    PARAMETER_ID_SENSOR_STATUS((byte) 0x0A),
+    PARAMETER_ID_MULTIPLE_SENSOR_STATUS((byte) 0x0B),
+    PARAMETER_ID_NAME((byte) 0x0C);
+
+    private byte id;
+
+    ParameterId(byte id){
+        this.id = id;
+    }
+
+    public byte getParameterIdByte(){
+        return this.id;
+    }
+
+    public static ParameterId fromParameterIdByte(byte parameterId){
+        for(ParameterId id:ParameterId.values()){
+            if(id.getParameterIdByte() == parameterId){
+                return id;
+            }
+        }
+        return null;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/binary_sensor/protocol/constants/ReportState.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/binary_sensor/protocol/constants/ReportState.java
new file mode 100644
index 0000000..8c6b29a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/binary_sensor/protocol/constants/ReportState.java
@@ -0,0 +1,35 @@
+/*  Copyright (C) 2022-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.binary_sensor.protocol.constants;
+
+public enum ReportState {
+    REPORT_STATUS_DISABLED,
+    REPORT_STATUS_ENABLED;
+
+    public byte getReportStateByte(){
+        return (byte) ordinal();
+    }
+
+    public static ReportState fromReportStateByte(byte reportState){
+        for(ReportState value:ReportState.values()){
+            if(value.getReportStateByte() == reportState){
+                return value;
+            }
+        }
+        return null;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/binary_sensor/protocol/constants/ResultCode.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/binary_sensor/protocol/constants/ResultCode.java
new file mode 100644
index 0000000..1f593ab
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/binary_sensor/protocol/constants/ResultCode.java
@@ -0,0 +1,35 @@
+/*  Copyright (C) 2022-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.binary_sensor.protocol.constants;
+
+public enum ResultCode {
+    RESULT_CODE_SUCCESS,
+    RESULT_CODE_FAILURE;
+
+    public byte getResultCodeByte(){
+        return (byte) ordinal();
+    }
+
+    public static ResultCode fromResultCodeByte(byte resultCode){
+        for(ResultCode value:ResultCode.values()){
+            if(value.getResultCodeByte() == resultCode){
+                return value;
+            }
+        }
+        return null;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/binary_sensor/protocol/constants/SensorState.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/binary_sensor/protocol/constants/SensorState.java
new file mode 100644
index 0000000..78a6d35
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/binary_sensor/protocol/constants/SensorState.java
@@ -0,0 +1,35 @@
+/*  Copyright (C) 2022-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.binary_sensor.protocol.constants;
+
+public enum SensorState {
+    SENSOR_STATE_CLOSED,
+    SENSOR_STATE_OPEN;
+
+    public byte getSensorStateByte(){
+        return (byte) ordinal();
+    }
+
+    public static SensorState fromSensorStateByte(byte sensorState){
+        for(SensorState value:SensorState.values()){
+            if(value.getSensorStateByte() == sensorState){
+                return value;
+            }
+        }
+        return null;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/binary_sensor/protocol/constants/SensorType.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/binary_sensor/protocol/constants/SensorType.java
new file mode 100644
index 0000000..be2a5bd
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/binary_sensor/protocol/constants/SensorType.java
@@ -0,0 +1,36 @@
+/*  Copyright (C) 2022-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.binary_sensor.protocol.constants;
+
+public enum SensorType {
+    SENSOR_TYPE_OPENING_CLOSING,
+    SENSOR_TYPE_VIBRATION_DETECTION,
+    SENSOR_TYPE_HUMAN_DETECTION;
+
+    public byte getSensorTypeByte(){
+        return (byte) ordinal();
+    }
+
+    public static SensorType fromSensorTypeByte(byte sensorType){
+        for(SensorType value:SensorType.values()){
+            if(value.getSensorTypeByte() == sensorType){
+                return value;
+            }
+        }
+        return null;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/binary_sensor/protocol/message/GetSensorRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/binary_sensor/protocol/message/GetSensorRequest.java
new file mode 100644
index 0000000..0c968a2
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/binary_sensor/protocol/message/GetSensorRequest.java
@@ -0,0 +1,32 @@
+/*  Copyright (C) 2022-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.binary_sensor.protocol.message;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.binary_sensor.protocol.constants.MessageId;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.binary_sensor.protocol.constants.SensorType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.binary_sensor.protocol.parameter.Parameter;
+
+public class GetSensorRequest extends Message{
+    public GetSensorRequest(SensorType sensorType) {
+        super(
+                MessageId.MESSAGE_ID_GET_SENSOR_REQUEST,
+                new Parameter[]{
+                        new nodomain.freeyourgadget.gadgetbridge.service.devices.binary_sensor.protocol.parameter.SensorType(sensorType)
+                }
+        );
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/binary_sensor/protocol/message/Message.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/binary_sensor/protocol/message/Message.java
new file mode 100644
index 0000000..fd799af
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/binary_sensor/protocol/message/Message.java
@@ -0,0 +1,49 @@
+/*  Copyright (C) 2022-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.binary_sensor.protocol.message;
+
+import java.nio.ByteBuffer;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.binary_sensor.protocol.constants.MessageId;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.binary_sensor.protocol.parameter.Parameter;
+
+public class Message {
+    MessageId messageId;
+    Parameter[] parameters;
+
+    public Message(MessageId messageId, Parameter[] parameters) {
+        this.messageId = messageId;
+        this.parameters = parameters;
+    }
+
+    public byte[] encode(){
+        int dataLength = 4;
+        for(Parameter parameter : parameters){
+            dataLength += parameter.getPayloadLength() + 4;
+        }
+        ByteBuffer buffer = ByteBuffer.allocate(dataLength);
+        buffer
+                .put((byte) 0x00) // RFU
+                .put(messageId.getMessageIdByte()) // RFU
+                .put((byte) 0x00) // RFU
+                .put((byte) parameters.length);
+        for(Parameter parameter : parameters){
+            buffer.put(parameter.encode());
+        }
+        return buffer.array();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/binary_sensor/protocol/message/Response.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/binary_sensor/protocol/message/Response.java
new file mode 100644
index 0000000..bbae40c
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/binary_sensor/protocol/message/Response.java
@@ -0,0 +1,38 @@
+/*  Copyright (C) 2022-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.binary_sensor.protocol.message;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.binary_sensor.protocol.constants.MessageId;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.binary_sensor.protocol.parameter.Parameter;
+
+public class Response {
+    MessageId messageId;
+    Parameter[] parameters;
+
+    public MessageId getMessageId() {
+        return messageId;
+    }
+
+    public Parameter[] getParameters() {
+        return parameters;
+    }
+
+    public Response(MessageId messageId, Parameter[] parameters) {
+        this.messageId = messageId;
+        this.parameters = parameters;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/binary_sensor/protocol/message/SetSensorRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/binary_sensor/protocol/message/SetSensorRequest.java
new file mode 100644
index 0000000..97faac5
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/binary_sensor/protocol/message/SetSensorRequest.java
@@ -0,0 +1,34 @@
+/*  Copyright (C) 2022-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.binary_sensor.protocol.message;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.binary_sensor.protocol.constants.MessageId;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.binary_sensor.protocol.constants.ReportState;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.binary_sensor.protocol.constants.SensorType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.binary_sensor.protocol.parameter.Parameter;
+
+public class SetSensorRequest extends Message{
+    public SetSensorRequest(SensorType sensorType, ReportState reportState) {
+        super(
+                MessageId.MESSAGE_ID_SET_SENSOR_REQUEST,
+                new Parameter[]{
+                        new nodomain.freeyourgadget.gadgetbridge.service.devices.binary_sensor.protocol.parameter.SensorType(sensorType),
+                        new nodomain.freeyourgadget.gadgetbridge.service.devices.binary_sensor.protocol.parameter.ReportStatus(reportState)
+                }
+        );
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/binary_sensor/protocol/parameter/Parameter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/binary_sensor/protocol/parameter/Parameter.java
new file mode 100644
index 0000000..55d2f13
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/binary_sensor/protocol/parameter/Parameter.java
@@ -0,0 +1,65 @@
+/*  Copyright (C) 2022-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.binary_sensor.protocol.parameter;
+
+import java.nio.ByteBuffer;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.binary_sensor.protocol.constants.ParameterId;
+
+public class Parameter {
+    ParameterId parameterId;
+    byte[] payload;
+
+    public Parameter(ParameterId parameterId, byte... payload) {
+        this.parameterId = parameterId;
+        this.payload = payload;
+    }
+
+    public ParameterId getParameterId() {
+        return parameterId;
+    }
+
+    public int getPayloadLength(){
+        return payload.length;
+    }
+
+    public byte[] encode(){
+        ByteBuffer buffer = ByteBuffer.allocate(payload.length + 4);
+        buffer
+                .put(parameterId.getParameterIdByte())
+                .put((byte) payload.length)
+                .put((byte) 0x00) // RFU
+                .put((byte) 0x00) // RFU
+                .put(payload);
+
+        return buffer.array();
+    }
+
+    public static Parameter decode(ParameterId parameterId, byte[] payload){
+        if(parameterId == ParameterId.PARAMETER_ID_RESULT_CODE){
+            return ResultCode.decode(payload);
+        }else if(parameterId == ParameterId.PARAMETER_ID_REPORT_STATUS){
+            return ReportStatus.decode(payload);
+        }else if(parameterId == ParameterId.PARAMETER_ID_SENSOR_STATUS){
+            return SensorState.decode(payload);
+        }else if(parameterId == ParameterId.PARAMETER_ID_SENSOR_TYPE){
+            return SensorType.decode(payload);
+        }
+
+        return null;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/binary_sensor/protocol/parameter/ReportStatus.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/binary_sensor/protocol/parameter/ReportStatus.java
new file mode 100644
index 0000000..1cea9c1
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/binary_sensor/protocol/parameter/ReportStatus.java
@@ -0,0 +1,34 @@
+/*  Copyright (C) 2022-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.binary_sensor.protocol.parameter;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.binary_sensor.protocol.constants.ParameterId;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.binary_sensor.protocol.constants.ReportState;
+
+public class ReportStatus extends Parameter{
+    ReportState reportState;
+    public ReportStatus(nodomain.freeyourgadget.gadgetbridge.service.devices.binary_sensor.protocol.constants.ReportState reportState) {
+        super(ParameterId.PARAMETER_ID_REPORT_STATUS, reportState.getReportStateByte());
+        this.reportState = reportState;
+    }
+
+    public static ReportStatus decode(byte[] data){
+        return new ReportStatus(
+                nodomain.freeyourgadget.gadgetbridge.service.devices.binary_sensor.protocol.constants.ReportState.fromReportStateByte(data[0])
+        );
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/binary_sensor/protocol/parameter/ResultCode.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/binary_sensor/protocol/parameter/ResultCode.java
new file mode 100644
index 0000000..008e244
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/binary_sensor/protocol/parameter/ResultCode.java
@@ -0,0 +1,33 @@
+/*  Copyright (C) 2022-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.binary_sensor.protocol.parameter;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.binary_sensor.protocol.constants.ParameterId;
+
+public class ResultCode extends Parameter{
+    nodomain.freeyourgadget.gadgetbridge.service.devices.binary_sensor.protocol.constants.ResultCode resultCode;
+    public ResultCode(nodomain.freeyourgadget.gadgetbridge.service.devices.binary_sensor.protocol.constants.ResultCode resultCode) {
+        super(ParameterId.PARAMETER_ID_RESULT_CODE, resultCode.getResultCodeByte());
+        this.resultCode = resultCode;
+    }
+
+    public static ResultCode decode(byte[] data){
+        return new ResultCode(
+                nodomain.freeyourgadget.gadgetbridge.service.devices.binary_sensor.protocol.constants.ResultCode.fromResultCodeByte(data[0])
+        );
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/binary_sensor/protocol/parameter/SensorState.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/binary_sensor/protocol/parameter/SensorState.java
new file mode 100644
index 0000000..0d392f6
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/binary_sensor/protocol/parameter/SensorState.java
@@ -0,0 +1,48 @@
+/*  Copyright (C) 2022-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.binary_sensor.protocol.parameter;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.binary_sensor.protocol.constants.ParameterId;
+
+public class SensorState extends Parameter{
+    nodomain.freeyourgadget.gadgetbridge.service.devices.binary_sensor.protocol.constants.SensorState sensorState;
+    int count;
+
+    public SensorState(nodomain.freeyourgadget.gadgetbridge.service.devices.binary_sensor.protocol.constants.SensorState sensorState, int count) {
+        super(ParameterId.PARAMETER_ID_SENSOR_STATUS, sensorState.getSensorStateByte());
+        this.sensorState = sensorState;
+        this.count = count;
+    }
+
+    public nodomain.freeyourgadget.gadgetbridge.service.devices.binary_sensor.protocol.constants.SensorState getSensorState() {
+        return sensorState;
+    }
+
+    public int getCount() {
+        return count;
+    }
+
+    public static SensorState decode(byte[] data){
+        int dataInt = ((data[1] & 0xFF) << 8) | (data[0] & 0xFF);
+        byte stateByte = (byte)((dataInt >> 11) & 0x01);
+        int count = dataInt & 0b11111111111;
+        return new SensorState(
+                nodomain.freeyourgadget.gadgetbridge.service.devices.binary_sensor.protocol.constants.SensorState.fromSensorStateByte(stateByte),
+                count
+        );
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/binary_sensor/protocol/parameter/SensorType.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/binary_sensor/protocol/parameter/SensorType.java
new file mode 100644
index 0000000..1be77cc
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/binary_sensor/protocol/parameter/SensorType.java
@@ -0,0 +1,34 @@
+/*  Copyright (C) 2022-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.binary_sensor.protocol.parameter;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.binary_sensor.protocol.constants.ParameterId;
+
+public class SensorType extends Parameter{
+    nodomain.freeyourgadget.gadgetbridge.service.devices.binary_sensor.protocol.constants.SensorType sensorType;
+    public SensorType(nodomain.freeyourgadget.gadgetbridge.service.devices.binary_sensor.protocol.constants.SensorType sensorType) {
+        super(ParameterId.PARAMETER_ID_SENSOR_TYPE, sensorType.getSensorTypeByte());
+        this.sensorType = sensorType;
+    }
+
+    public static SensorType decode(byte[] data){
+        return new SensorType(
+                nodomain.freeyourgadget.gadgetbridge.service.devices.binary_sensor.protocol.constants.SensorType.fromSensorTypeByte(data[0])
+        );
+    }
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casio/BasicCasio2C2DSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casio/BasicCasio2C2DSupport.java
new file mode 100644
index 0000000..7625f83
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casio/BasicCasio2C2DSupport.java
@@ -0,0 +1,114 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.casio;
+
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+
+import org.slf4j.Logger;
+
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.time.format.TextStyle;
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.Map;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventFindPhone;
+import nodomain.freeyourgadget.gadgetbridge.devices.casio.CasioConstants;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
+
+public abstract class BasicCasio2C2DSupport extends Casio2C2DSupport {
+    public BasicCasio2C2DSupport(Logger logger) {
+        super(logger);
+    }
+
+    @Override
+    protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
+        // remove this workaround once Gadgetbridge does discovery on initial pairing
+        if (getCharacteristic(CasioConstants.CASIO_READ_REQUEST_FOR_ALL_FEATURES_CHARACTERISTIC_UUID) == null ||
+                getCharacteristic(CasioConstants.CASIO_ALL_FEATURES_CHARACTERISTIC_UUID) == null) {
+            LOG.info("Reconnecting to discover characteristics");
+            disconnect();
+            connect();
+            return builder;
+        }
+
+        builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext()));
+
+        super.initializeDevice(builder);
+
+        // which button was pressed?
+        requestFeature(builder, new FeatureRequest(FEATURE_BLE_FEATURES), data -> {
+            if (data.length > 8 && data[8] == CasioConstants.CONNECT_FIND) {
+                setInitialized();
+            } else {
+                requestWorldClocks();
+            }
+        });
+
+        return builder;
+    }
+
+    protected void requestWorldClocks() {
+        TransactionBuilder builder = createTransactionBuilder("requestWorldClocks");
+        HashSet<FeatureRequest> requests = new HashSet();
+
+        for (byte i = 0; i < 6; i++) {
+            requests.addAll(CasioTimeZone.requests(i));
+        }
+
+        requestFeatures(builder, requests, responses -> {
+            TransactionBuilder clockBuilder = createTransactionBuilder("setClocks");
+            setClocks(clockBuilder, responses);
+            clockBuilder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZED, getContext()));
+            clockBuilder.queue(getQueue());
+        });
+        builder.queue(getQueue());
+    }
+
+    private void setClocks(TransactionBuilder builder, Map<FeatureRequest, byte[]> responses) {
+        ZoneId tz = ZoneId.systemDefault();
+        Instant now = Instant.now().plusSeconds(2);
+        CasioTimeZone[] timezones = {
+                CasioTimeZone.fromZoneId(tz, now, tz.getDisplayName(TextStyle.SHORT, Locale.getDefault())),
+                CasioTimeZone.fromWatchResponses(responses, 1),
+                CasioTimeZone.fromWatchResponses(responses, 2),
+                CasioTimeZone.fromWatchResponses(responses, 3),
+                CasioTimeZone.fromWatchResponses(responses, 4),
+                CasioTimeZone.fromWatchResponses(responses, 5),
+        };
+        for (int i = 5; i >= 0; i--) {
+            if (i%2 == 0)
+                writeAllFeatures(builder, CasioTimeZone.dstWatchStateBytes(i, timezones[i], i+1, timezones[i+1]));
+            writeAllFeatures(builder, timezones[i].dstSettingBytes(i));
+            writeAllFeatures(builder, timezones[i].worldCityBytes(i));
+        }
+        writeCurrentTime(builder, ZonedDateTime.ofInstant(now, tz));
+    }
+
+    @Override
+    public boolean onCharacteristicChanged(BluetoothGatt gatt,
+                                           BluetoothGattCharacteristic characteristic) {
+        UUID characteristicUUID = characteristic.getUuid();
+        byte[] data = characteristic.getValue();
+        if (data.length == 0)
+            return true;
+
+        if (characteristicUUID.equals(CasioConstants.CASIO_ALL_FEATURES_CHARACTERISTIC_UUID)) {
+            if(data[0] == FEATURE_ALERT_LEVEL) {
+                GBDeviceEventFindPhone event = new GBDeviceEventFindPhone();
+                if(data[1] == 0x02) {
+                    event.event = GBDeviceEventFindPhone.Event.START_VIBRATE;
+                } else {
+                    event.event = GBDeviceEventFindPhone.Event.STOP;
+                }
+                evaluateGBDeviceEvent(event);
+                return true;
+            }
+        }
+        return super.onCharacteristicChanged(gatt, characteristic);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casio/Casio2C2DSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casio/Casio2C2DSupport.java
new file mode 100644
index 0000000..60df138
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casio/Casio2C2DSupport.java
@@ -0,0 +1,997 @@
+/*  Copyright (C) 2023-2024 Johannes Krude
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.casio;
+
+import java.time.ZonedDateTime;
+import java.time.ZoneId;
+import java.time.LocalDate;
+import java.nio.charset.StandardCharsets;
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.widget.Toast;
+import android.content.Intent;
+import android.content.SharedPreferences;
+
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import java.util.UUID;
+import java.util.Arrays;
+import java.util.List;
+import java.util.LinkedList;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.Set;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.Locale;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import nodomain.freeyourgadget.gadgetbridge.Logging;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.Logging;
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+import nodomain.freeyourgadget.gadgetbridge.util.preferences.DevicePrefs;
+import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
+import nodomain.freeyourgadget.gadgetbridge.util.AlarmUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.BcdUtil;
+import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
+import nodomain.freeyourgadget.gadgetbridge.model.Reminder;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.casio.CasioConstants;
+
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_LANGUAGE;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_LANGUAGE_AUTO;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_TIMEFORMAT;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_TIMEFORMAT_AUTO;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_TIMEFORMAT_12H;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_TIMEFORMAT_24H;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_DATEFORMAT;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_DATEFORMAT_AUTO;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_DATEFORMAT_DAY_MONTH;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_DATEFORMAT_MONTH_DAY;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_OPERATING_SOUNDS;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_HOURLY_CHIME_ENABLE;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_AUTOLIGHT;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_LIGHT_DURATION_LONGER;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_POWER_SAVING;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_CONNECTION_DURATION;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_TIME_SYNC;
+
+
+// this class is for those Casio watches which request reads on the 2C characteristic and write on the 2D characteristic
+
+public abstract class Casio2C2DSupport extends CasioSupport {
+
+    public static final byte FEATURE_CURRENT_TIME = 0x09;
+    public static final byte FEATURE_ALERT_LEVEL = 0x0a;
+    public static final byte FEATURE_BLE_FEATURES = 0x10;
+    public static final byte FEATURE_SETTING_FOR_BLE = 0x11;
+    public static final byte FEATURE_SETTING_FOR_BASIC = 0x13;
+    public static final byte FEATURE_SETTING_FOR_ALM = 0x15;
+    public static final byte FEATURE_SETTING_FOR_ALM2 = 0x16;
+    public static final byte FEATURE_VERSION_INFORMATION = 0x20;
+    public static final byte FEATURE_APP_INFORMATION = 0x22;
+    public static final byte FEATURE_WATCH_NAME = 0x23;
+    public static final byte FEATURE_MODULE_ID = 0x26;
+    public static final byte FEATURE_WATCH_CONDITION = 0x28;
+    public static final byte FEATURE_DST_WATCH_STATE = (byte) 0x1d;
+    public static final byte FEATURE_DST_SETTING = (byte) 0x1e;
+    public static final byte FEATURE_WORLD_CITY = (byte) 0x1f;
+    public static final byte FEATURE_REMINDER_TITLE = 0x30;
+    public static final byte FEATURE_REMINDER_TIME = 0x31;
+    public static final byte FEATURE_CURRENT_TIME_MANAGER = 0x39;
+    public static final byte FEATURE_CONNECTION_PARAMETER_MANAGER = 0x3a;
+    public static final byte FEATURE_ADVERTISE_PARAMETER_MANAGER = 0x3b;
+    public static final byte FEATURE_SETTING_FOR_TARGET_VALUE = 0x43;
+    public static final byte FEATURE_SETTING_FOR_USER_PROFILE = 0x45;
+    public static final byte FEATURE_SERVICE_DISCOVERY_MANAGER = 0x47;
+
+    protected static Logger LOG;
+    LinkedList<RequestWithHandler> requests = new LinkedList<>();
+
+    public Casio2C2DSupport(Logger logger) {
+        super(logger);
+        LOG = logger;
+    }
+
+    @Override
+    public boolean connect() {
+        requests.clear();
+        return super.connect();
+    }
+
+    @Override
+    protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
+        initializeDeviceSettings(builder);
+        return builder;
+    }
+
+    public void writeAllFeatures(TransactionBuilder builder, byte[] arr) {
+        if (!requests.isEmpty()) {
+            LOG.warn("writing while waiting for a response may lead to incorrect received responses");
+        }
+        builder.write(getCharacteristic(CasioConstants.CASIO_ALL_FEATURES_CHARACTERISTIC_UUID), arr);
+    }
+
+    public void writeAllFeaturesRequest(TransactionBuilder builder, byte[] arr) {
+        builder.write(getCharacteristic(CasioConstants.CASIO_READ_REQUEST_FOR_ALL_FEATURES_CHARACTERISTIC_UUID), arr);
+    }
+
+    public interface ResponseHandler {
+        void handle(byte[] response);
+    }
+
+    public class FeatureResponses extends HashMap<FeatureRequest, byte[]> {
+        public byte[][] get(FeatureRequest[] requests) {
+            byte[][] result = new byte[requests.length][];
+            for (int i = 0; i < requests.length; i++) {
+                byte[] response = get(requests[i]);
+                if (response == null)
+                    return null;
+                result[i] = response;
+            }
+            return result;
+        }
+    }
+
+    public interface ResponsesHandler {
+        void handle(FeatureResponses responses);
+    }
+
+    public static class FeatureRequest {
+        byte data[];
+
+        public FeatureRequest(byte arg0) {
+            data = new byte[] {arg0};
+        }
+
+        public FeatureRequest(byte arg0, byte arg1) {
+            data = new byte[] {arg0, arg1};
+        }
+
+        public static FeatureRequest parse(String str) {
+            byte[] data = RequestWithData.parseData(str);
+            if (data == null)
+                return null;
+            if (data.length == 1) {
+                return new FeatureRequest(data[0]);
+            } else if (data.length == 2) {
+                return new FeatureRequest(data[0], data[1]);
+            } else {
+                return null;
+            }
+        }
+
+        public byte[] getData() {
+            return data.clone();
+        }
+
+        @Override
+        public int hashCode() {
+            return Arrays.hashCode(data);
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (o == this)
+                return true;
+            if (!(o instanceof FeatureRequest))
+                return false;
+
+            FeatureRequest fr = (FeatureRequest) o;
+            return Arrays.equals(data, fr.data);
+        }
+
+        public boolean matches(byte[] response) {
+            // 0xff is of type int which goes out of range etc. byte is signed.
+            if (response.length > 2 && response[0] == (byte) 0xFF && response[1] == (byte) 0x81) {
+                if (data.length < response.length - 2)
+                    return false;
+                for (int i = 2; i < response.length; i++) {
+                    if (response[i] != data[i-2])
+                        return false;
+                }
+                return true;
+            } else {
+                if (response.length < data.length)
+                    return false;
+                for (int i = 0; i < data.length; i++) {
+                    if (response[i] != data[i])
+                        return false;
+                }
+                return true;
+            }
+        }
+    }
+
+    private static class RequestWithData {
+        public FeatureRequest request;
+        public byte[] data;
+
+        public RequestWithData(FeatureRequest request, byte[] data) {
+            this.request = request;
+            this.data = data;
+        }
+
+        public static RequestWithData parse(String str) {
+            String[] kv = str.split(";");
+            if (kv.length != 2)
+                return null;
+            FeatureRequest request = FeatureRequest.parse(kv[0]);
+            byte[] data = parseData(kv[1]);
+            if (request == null || data == null)
+                return null;
+            return new RequestWithData(request, data);
+        }
+
+        public static byte[] parseData(String str) {
+            if (!str.matches("\\A\\[[0-9]+(, [0-9]+)*\\]\\z"))
+                return null;
+            String[] strings = str.replace("[", "").replace("]", "").split(", ");
+            byte[] result = new byte[strings.length];
+            for (int i = 0; i < result.length; i++)
+                result[i] = (byte) Integer.parseInt(strings[i]);
+            return result;
+        }
+    }
+
+    private static class RequestWithHandler {
+        public FeatureRequest request;
+        public ResponseHandler handler;
+
+        public RequestWithHandler(FeatureRequest request, ResponseHandler handler) {
+            this.request = request;
+            this.handler = handler;
+        }
+    }
+
+    public void requestFeature(TransactionBuilder builder, FeatureRequest request, ResponseHandler handler) {
+        builder.notify(getCharacteristic(CasioConstants.CASIO_ALL_FEATURES_CHARACTERISTIC_UUID), true);
+        writeAllFeaturesRequest(builder, request.getData());
+        builder.run((gatt) -> requests.add(new RequestWithHandler(request, handler)));
+    }
+
+    public void requestFeatures(TransactionBuilder builder, Set<FeatureRequest> requests, ResponsesHandler handler) {
+        FeatureResponses responses = new FeatureResponses();
+
+        HashSet<FeatureRequest> missing = new HashSet();
+        for (FeatureRequest request: requests) {
+            missing.add(request);
+        }
+
+        for (FeatureRequest request: requests) {
+            requestFeature(builder, request, data -> {
+                responses.put(request, data);
+                missing.remove(request);
+                if (missing.isEmpty()) {
+                    handler.handle(responses);
+                }
+            });
+        }
+    }
+
+    @Override
+    public boolean onCharacteristicChanged(BluetoothGatt gatt,
+                                           BluetoothGattCharacteristic characteristic) {
+        UUID characteristicUUID = characteristic.getUuid();
+
+        if (characteristicUUID.equals(CasioConstants.CASIO_ALL_FEATURES_CHARACTERISTIC_UUID)) {
+            byte[] response = characteristic.getValue();
+            Iterator<RequestWithHandler> it = requests.iterator();
+            while (it.hasNext()) {
+                RequestWithHandler rh = it.next();
+                if (rh.request.matches(response)) {
+                    it.remove();
+                    rh.handler.handle(response);
+                    return true;
+                }
+            }
+            LOG.warn("unhandled response: " + Logging.formatBytes(response));
+        }
+
+        return super.onCharacteristicChanged(gatt, characteristic);
+    }
+
+    public void writeCurrentTime(TransactionBuilder builder, ZonedDateTime time) {
+        byte[] arr = new byte[11];
+        arr[0] = FEATURE_CURRENT_TIME;
+        byte[] tmp = prepareCurrentTime(time);
+        System.arraycopy(tmp, 0, arr, 1, 10);
+
+        writeAllFeatures(builder, arr);
+    }
+
+    FeatureCache featureCache = new FeatureCache();
+    public class FeatureCache {
+
+        Map<FeatureRequest, byte[]> values = null;
+
+        // can not be done on initialization, since the SharedPreferences are not yet available
+        void load() {
+            values = new HashMap();
+            Set<String> serialized = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getStringSet("casio_features_current_values", new HashSet());
+            if (serialized == null)
+                return;
+
+            for (String str: serialized) {
+                if (str == null)
+                    continue;
+                RequestWithData entry = RequestWithData.parse(str);
+                if (entry == null) {
+                    LOG.warn("invalid casio_features_current_values entry: " + str);
+                    continue;
+                }
+                values.put(entry.request, entry.data);
+            }
+        }
+
+        public void save(SharedPreferences.Editor editor) {
+            if (values == null)
+                return;
+
+            Set<String> serialized = new HashSet();
+
+            for (Map.Entry<FeatureRequest, byte[]> entry: values.entrySet()) {
+                serialized.add(Arrays.toString(entry.getKey().getData()) + ";" + Arrays.toString(entry.getValue()));
+            }
+
+            editor.putStringSet("casio_features_current_values", serialized);
+        }
+
+        public byte[] get(FeatureRequest request) {
+            if (values == null)
+                load();
+            return values.get(request);
+        }
+
+        public byte[][] get(FeatureRequest[] requests) {
+            byte[][] result = new byte[requests.length][];
+            for (int i = 0; i < requests.length; i++) {
+                byte[] response = get(requests[i]);
+                if (response == null)
+                    return null;
+                result[i] = response;
+            }
+            return result;
+        }
+
+        public void add(Map<FeatureRequest, byte[]> entries, SharedPreferences.Editor editor) {
+            if (values == null)
+                load();
+            for (Map.Entry<FeatureRequest, byte[]> entry: entries.entrySet()) {
+                values.put(entry.getKey(), entry.getValue());
+            }
+            save(editor);
+        }
+    }
+
+    public interface UpdateValuesHandler {
+        public void apply(byte[][] data);
+    }
+    public interface ReadValuesHandler {
+        public boolean apply(byte[][] data);
+    }
+
+    public abstract class DeviceSetting {
+        // which features to request
+        public abstract FeatureRequest[] getFeatureRequests();
+        // compares and updates watch data, cached previous data and GB state, returns true if data was changed
+        public abstract boolean mergeValues(byte[][] data, byte[][] previous, SharedPreferences.Editor editor);
+
+        public final boolean mergeValues(byte[][] data, byte[][] previous, UpdateValuesHandler updateValues, ReadValuesHandler readValues) {
+            boolean needsUpdating = false;
+            // check if GB state has changed
+            if (previous != null) {
+                byte[][] copies = new byte[previous.length][];
+                for (int i = 0; i < previous.length; i++) {
+                       copies[i] = previous[i].clone();
+                }
+                updateValues.apply(copies);
+                for (int i = 0; i < previous.length; i++) {
+                    if (!Arrays.equals(previous[i], copies[i])) {
+                        needsUpdating = true;
+                        break;
+                    }
+                }
+            }
+            // update GB state and check if data needs change
+            if (!needsUpdating) {
+                needsUpdating = readValues.apply(data);
+            }
+            // maybe update data
+            if (needsUpdating) {
+                updateValues.apply(data);
+                return true;
+            }
+            return false;
+        }
+    };
+
+    ArrayList<DeviceSetting> deviceSettings = new ArrayList();
+    DeviceAlarms deviceAlarms = new DeviceAlarms();
+    DeviceReminders deviceReminders = new DeviceReminders();
+    HashMap<String, DevicePreference> devicePreferenceByName = new HashMap();
+    {
+        deviceSettings.add(deviceAlarms);
+        deviceSettings.add(deviceReminders);
+
+        for (DevicePreference pref: supportedDevicePreferences()) {
+            deviceSettings.add(pref);
+            devicePreferenceByName.put(pref.getName(), pref);
+        }
+    }
+
+    void initializeDeviceSettings(TransactionBuilder builder) {
+        Set<FeatureRequest> deviceSettingFeatures = new LinkedHashSet();
+        for (DeviceSetting ds: deviceSettings)
+            deviceSettingFeatures.addAll(Arrays.asList(ds.getFeatureRequests()));
+
+        requestFeatures(builder, deviceSettingFeatures, responses -> {
+            LinkedHashSet<FeatureRequest> override = new LinkedHashSet();
+
+            SharedPreferences.Editor editor = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).edit();
+            for (DeviceSetting ds: deviceSettings) {
+                FeatureRequest[] requests = ds.getFeatureRequests();
+                byte[][] data = responses.get(requests);
+                if (data == null)
+                    continue;
+                byte[][] previous = featureCache.get(requests);
+                if (ds.mergeValues(data, previous, editor)) {
+                    override.addAll(Arrays.asList(requests));
+                }
+            }
+
+            featureCache.add(responses, editor);
+            editor.apply();
+
+            if (!override.isEmpty()) {
+                ArrayList<byte[]> updatedSettings = new ArrayList();
+                for (FeatureRequest fr: override) {
+                    updatedSettings.add(responses.get(fr));
+                }
+                sendSettingsToDevice(updatedSettings.toArray(new byte[][] {}));
+            }
+        });
+    }
+
+    void sendSettingsToDevice(byte[][] settings) {
+        TransactionBuilder builder = createTransactionBuilder("DeviceSetting.write");
+        for (byte[] data: settings) {
+            writeAllFeatures(builder, data);
+        }
+        builder.run((gatt) -> GB.toast(getContext(), getContext().getString(R.string.user_feedback_set_settings_ok), Toast.LENGTH_SHORT, GB.INFO));
+        builder.queue(getQueue());
+    }
+
+    public abstract class DeviceItems<Item> extends DeviceSetting {
+
+        public void onSet(ArrayList<? extends Item> items) {
+            if (!isInitialized()) {
+                return;
+            }
+            byte[][] currentValues = featureCache.get(getFeatureRequests());
+            if (currentValues == null) {
+                return;
+            }
+            updateValues(currentValues, items);
+
+            SharedPreferences.Editor editor = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).edit();
+            featureCache.save(editor);
+            editor.apply();
+
+            sendSettingsToDevice(currentValues);
+        }
+
+        public abstract void updateValues(byte[][] data, List<? extends Item> items);
+
+    }
+
+    public class DeviceAlarms extends DeviceItems<Alarm> {
+
+        @Override
+        public FeatureRequest[] getFeatureRequests() {
+            final int maxAlarms = gbDevice.getDeviceCoordinator().getAlarmSlotCount(gbDevice);
+
+            if (maxAlarms == 0) {
+                return new FeatureRequest[] {};
+            } else if (maxAlarms == 1) {
+                return new FeatureRequest[] {new FeatureRequest(FEATURE_SETTING_FOR_ALM)};
+            } else {
+                return new FeatureRequest[] {new FeatureRequest(FEATURE_SETTING_FOR_ALM), new FeatureRequest(FEATURE_SETTING_FOR_ALM2)};
+            }
+        };
+
+        @Override
+        public void updateValues(byte[][] data, List<? extends Alarm> alarms) {
+            for (Alarm alarm: alarms) {
+                updateValues(data, alarm);
+            }
+        }
+
+        public void updateValues(byte[][] data, Alarm alarm) {
+            int pos = alarm.getPosition();
+            int alm = pos == 0 ? 0 : 1;
+            int index = pos == 0 ? 1 : 1 + 4 * (pos-1);
+            if (data.length <= alm || index + 4 > data[alm].length) {
+                LOG.error("alarm data too small");
+            }
+            updateValue(data[alm], index, alarm);
+        }
+
+        public void updateValue(byte[] data, int index, Alarm alarm) {
+            if(alarm.getEnabled()) {
+                data[index] |= 0x40;
+            } else {
+                data[index] &= ~0x40;
+            }
+            //data[index+1] = 0x40;
+            data[index+2] = (byte) alarm.getHour();
+            data[index+3] = (byte) alarm.getMinute();
+        }
+
+        @Override
+        public final boolean mergeValues(byte[][] data, byte[][] previous, SharedPreferences.Editor editor) {
+            boolean changed = false;
+
+            LinkedHashSet<Integer> foundAlarms = new LinkedHashSet();
+            for (nodomain.freeyourgadget.gadgetbridge.entities.Alarm alarm: DBHelper.getAlarms(gbDevice)) {
+                foundAlarms.add(alarm.getPosition());
+                if (mergeValues(data, previous, uData -> updateValues(uData, alarm), rData -> readValues(rData, alarm, false))) {
+                    changed = true;
+                }
+            }
+
+            final int maxAlarms = gbDevice.getDeviceCoordinator().getAlarmSlotCount(gbDevice);
+            for (int i = 0; i < maxAlarms; i++) {
+                if (!foundAlarms.contains(i)) {
+                    nodomain.freeyourgadget.gadgetbridge.entities.Alarm alarm = AlarmUtils.createDefaultAlarm(gbDevice, i);
+                    if (alarm == null) {
+                        continue;
+                    }
+                    readValues(data, alarm, true);
+                }
+            }
+            return changed;
+        }
+
+        public boolean readValues(byte[][] data, nodomain.freeyourgadget.gadgetbridge.entities.Alarm alarm, boolean forceStore) {
+            int pos = alarm.getPosition();
+            int alm = pos == 0 ? 0 : 1;
+            int index = pos == 0 ? 1 : 1+ 4 * (pos-1);
+            if (data.length <= alm || index + 4 > data[alm].length) {
+                LOG.error("alarm data too small");
+            }
+            return readValues(data[alm], index, alarm, forceStore);
+        }
+
+        public boolean readValues(byte[] data, int index, nodomain.freeyourgadget.gadgetbridge.entities.Alarm alarm, boolean forceStore) {
+            boolean enabled = (data[index] & 0x40) == 0x40;
+            byte hour   = data[index+2];
+            byte minute = data[index+3];
+
+            if (forceStore || alarm.getEnabled() != enabled || alarm.getHour() != hour || alarm.getMinute() != minute) {
+                alarm.setEnabled(enabled);
+                alarm.setHour(hour);
+                alarm.setMinute(minute);
+                alarm.setRepetition(Alarm.ALARM_MON | Alarm.ALARM_TUE | Alarm.ALARM_WED | Alarm.ALARM_THU | Alarm.ALARM_FRI | Alarm.ALARM_SAT | Alarm.ALARM_SUN);
+                DBHelper.store(alarm);
+                Intent intent = new Intent(DeviceService.ACTION_SAVE_ALARMS);
+                LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent);
+            }
+            return false;
+        }
+    }
+
+    @Override
+    public void onSetAlarms(ArrayList<? extends Alarm> alarms) {
+        deviceAlarms.onSet(alarms);
+    }
+
+    public class DeviceReminders extends DeviceItems<Reminder> {
+
+        @Override
+        public FeatureRequest[] getFeatureRequests() {
+            final int maxReminders = gbDevice.getDeviceCoordinator().getReminderSlotCount(gbDevice);
+
+            FeatureRequest[] requests = new FeatureRequest[maxReminders*2];
+            for (int i = 0; i < maxReminders; i++) {
+                requests[2*i]   = new FeatureRequest(FEATURE_REMINDER_TITLE, (byte) (i+1));
+                requests[2*i+1] = new FeatureRequest(FEATURE_REMINDER_TIME, (byte) (i+1));
+            }
+            return requests;
+        };
+
+        @Override
+        public void updateValues(byte[][] data, List<? extends Reminder> reminders) {
+            final int maxReminders = gbDevice.getDeviceCoordinator().getReminderSlotCount(gbDevice);
+            for (int i = 0; i < maxReminders; i++) {
+                if (i < reminders.size()) {
+                    updateValues(data, i, reminders.get(i));
+                } else {
+                    Arrays.fill(data[2*i],   2, 20, (byte) 0);
+                    Arrays.fill(data[2*i+1], 2, 10, (byte) 0);
+                }
+            }
+        }
+
+        public void updateValues(byte[][] data, int pos, Reminder reminder) {
+            final byte[] message = reminder.getMessage().getBytes(StandardCharsets.US_ASCII);
+            System.arraycopy(message, 0, data[2*pos], 2, Math.min(message.length, 18));
+            Arrays.fill(data[2*pos], 2+message.length, 20, (byte) 0);
+
+            LocalDate start = LocalDate.ofInstant(reminder.getDate().toInstant(), ZoneId.of("UTC"));
+            if (start.getYear() < 2000) {
+                if (reminder.getRepetition() == Reminder.EVERY_WEEK) {
+                    final LocalDate first = LocalDate.of(2000, 1, 1);
+                    start = first.plusDays((start.getDayOfWeek().getValue()-first.getDayOfWeek().getValue())%7);
+                } else {
+                    start = start.withYear(2000);
+                }
+            } else if (start.getYear() > 2099) {
+                if (reminder.getRepetition() == Reminder.EVERY_WEEK) {
+                    final LocalDate last = LocalDate.of(2099, 31, 12);
+                    start = last.minusDays((last.getDayOfWeek().getValue()-start.getDayOfWeek().getValue())%7);
+                } else {
+                    final int dayOfMonth = start.getDayOfMonth();
+                    start = start.withYear(2099);
+                    if (start.getDayOfMonth() != dayOfMonth) {
+                        start = start.withYear(2096); // leap-year
+                    }
+                }
+            }
+
+            byte mode = 0;
+            LocalDate end = start;
+            byte weekdays = 0;
+
+            switch (reminder.getRepetition()) {
+            case Reminder.EVERY_DAY:
+                mode = 0x0B;
+                end = LocalDate.of(2099,12,31);
+                break;
+            case Reminder.EVERY_WEEK:
+                mode = 0x05;
+                weekdays = (byte) (1 << (start.getDayOfWeek().getValue()%7)); // Sunday is 1<<0
+                break;
+            case Reminder.EVERY_MONTH:
+                mode = 0x11;
+                break;
+            case Reminder.EVERY_YEAR:
+                mode = 0x09;
+                break;
+            default:
+                mode = 0x01;
+            }
+
+            data[2*pos+1][2] = mode;
+            data[2*pos+1][3] = BcdUtil.toBcd8(start.getYear()-2000);
+            data[2*pos+1][4] = BcdUtil.toBcd8(start.getMonthValue());
+            data[2*pos+1][5] = BcdUtil.toBcd8(start.getDayOfMonth());
+            data[2*pos+1][6] = BcdUtil.toBcd8(end.getYear()-2000);
+            data[2*pos+1][7] = BcdUtil.toBcd8(end.getMonthValue());
+            data[2*pos+1][8] = BcdUtil.toBcd8(end.getDayOfMonth());
+            data[2*pos+1][9] = weekdays;
+        }
+
+        @Override
+        public final boolean mergeValues(byte[][] data, byte[][] previous, SharedPreferences.Editor editor) {
+            List<? extends Reminder> reminders = DBHelper.getReminders(gbDevice);
+            // always overwrite watch values
+            return mergeValues(data, previous, uData -> updateValues(uData, reminders), rData -> false);
+        }
+    }
+
+    @Override
+    public void onSetReminders(ArrayList<? extends Reminder> reminders) {
+        deviceReminders.onSet(reminders);
+    }
+
+    public abstract class DevicePreference extends DeviceSetting {
+        byte feature;
+        public final FeatureRequest getFeatureRequest() {
+            return new FeatureRequest(feature);
+        };
+
+        @Override
+        public final FeatureRequest[] getFeatureRequests() {
+            return new FeatureRequest[] {getFeatureRequest()};
+        };
+
+        String name;
+        public final String getName() {
+            return name;
+        }
+
+        public abstract void updateValue(byte[] data);
+
+        @Override
+        public final boolean mergeValues(byte[][] data, byte[][] previous, SharedPreferences.Editor editor) {
+            return mergeValues(data, previous, uData -> updateValue(uData[0]), rData -> readValue(rData[0], editor));
+        }
+
+        public abstract boolean readValue(byte[] data, SharedPreferences.Editor editor);
+
+    };
+
+    public abstract Casio2C2DSupport.DevicePreference[] supportedDevicePreferences();
+
+    @Override
+    public void onSendConfiguration(String config) {
+        DevicePreference pref = devicePreferenceByName.get(config);
+        if (pref == null) {
+            LOG.warn("received configuration change for unsupported setting " + config);
+            return;
+        }
+        if (!isInitialized()) {
+            return;
+        }
+        byte[] currentValue = featureCache.get(pref.getFeatureRequest());
+        if (currentValue == null) {
+            LOG.error("unknown current watch value for config " + config);
+            return;
+        }
+        pref.updateValue(currentValue);
+
+        SharedPreferences.Editor editor = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).edit();
+        featureCache.save(editor);
+        editor.apply();
+
+        sendSettingsToDevice(new byte[][] {currentValue});
+    }
+
+    public class UnsignedByteDevicePreference extends DevicePreference {
+        int index;
+
+        @Override
+        public void updateValue(byte[] data) {
+            data[index] = (byte) getDevicePrefsValue();
+        }
+
+        @Override
+        public boolean readValue(byte[] data, SharedPreferences.Editor editor) {
+            return setDevicePrefsValue(editor, data[index] & 0xff);
+        }
+
+        public int getDevicePrefsValue() {
+            return getDevicePrefs().getInt(name, -1);
+        }
+
+        public boolean setDevicePrefsValue(SharedPreferences.Editor editor, int value) {
+            if (value != getDevicePrefsValue()) {
+                editor.putString(name, Integer.toString(value));
+            }
+            return false;
+        }
+    }
+
+    public class BoolDevicePreference extends DevicePreference {
+        int index;
+        byte mask;
+
+        @Override
+        public void updateValue(byte[] data) {
+            if (getDevicePrefsValue()) {
+                data[index] &= ~mask;
+            } else {
+                data[index] |= mask;
+            }
+        }
+
+        @Override
+        public boolean readValue(byte[] data, SharedPreferences.Editor editor) {
+            if ((data[index] & mask) == 0) {
+                return setDevicePrefsValue(editor, true);
+            } else {
+                return setDevicePrefsValue(editor, false);
+            }
+        }
+
+        public boolean getDevicePrefsValue() {
+            return getDevicePrefs().getBoolean(name, false);
+        }
+
+        public boolean setDevicePrefsValue(SharedPreferences.Editor editor, boolean value) {
+            if (value != getDevicePrefsValue()) {
+                editor.putBoolean(name, value);
+            }
+            return false;
+        }
+    }
+
+    public class InvertedBoolDevicePreference extends BoolDevicePreference {
+        @Override
+        public void updateValue(byte[] data) {
+            if (getDevicePrefsValue()) {
+                data[index] |= mask;
+            } else {
+                data[index] &= ~mask;
+            }
+        }
+
+        @Override
+        public boolean readValue(byte[] data, SharedPreferences.Editor editor) {
+            if ((data[index] & mask) == 0) {
+                return setDevicePrefsValue(editor, false);
+            } else {
+                return setDevicePrefsValue(editor, true);
+            }
+        }
+    }
+
+    interface AutoGetter {
+        public String get(DevicePrefs devicePrefs);
+    }
+
+    public class AutoBoolDevicePreference extends BoolDevicePreference {
+        AutoGetter getter;
+
+        String autoValue;
+        String trueValue;
+        String falseValue;
+
+        @Override
+        public boolean getDevicePrefsValue() {
+            return getter.get(getDevicePrefs()).equals(trueValue);
+        }
+
+        @Override
+        public boolean setDevicePrefsValue(SharedPreferences.Editor editor, boolean value) {
+            String strValue = value ? trueValue : falseValue;
+            if (!getter.get(getDevicePrefs()).equals(strValue)) {
+                if (getDevicePrefs().getString(name, autoValue).equals(autoValue)) {
+                    return true;
+                } else {
+                    editor.putString(name, strValue);
+                }
+            }
+            return false;
+        }
+    }
+
+    public class TimeSyncPreference extends BoolDevicePreference {
+        { name = PREF_TIME_SYNC; }
+        { feature = FEATURE_SETTING_FOR_BLE; }
+        { index = 12; mask = (byte) (0x80 & 0xff); }
+    }
+
+    public class ConnectionDurationPreference extends UnsignedByteDevicePreference {
+        { name = PREF_CONNECTION_DURATION; }
+        { feature = FEATURE_SETTING_FOR_BLE; }
+        { index = 14; }
+
+        @Override
+        public int getDevicePrefsValue() {
+            return getDevicePrefs().getInt(name, -1);
+        }
+
+        @Override
+        public boolean setDevicePrefsValue(SharedPreferences.Editor editor, int value) {
+            if (value != getDevicePrefsValue()) {
+                editor.putString(name, Integer.toString(value));
+            }
+            return false;
+        }
+    }
+
+    public class TimeFormatPreference extends AutoBoolDevicePreference {
+        { name = PREF_TIMEFORMAT; }
+        { feature = FEATURE_SETTING_FOR_BASIC; }
+        { index = 1; mask = 0x01; }
+        { getter = gbPrefs -> gbPrefs.getTimeFormat(); }
+        { autoValue = PREF_TIMEFORMAT_AUTO; }
+        { trueValue = PREF_TIMEFORMAT_12H; }
+        { falseValue = PREF_TIMEFORMAT_24H; }
+    }
+
+    public class OperatingSoundPreference extends BoolDevicePreference {
+        { name = PREF_OPERATING_SOUNDS; }
+        { feature = FEATURE_SETTING_FOR_BASIC; }
+        { index = 1; mask = 0x02; }
+    }
+
+    public class AutoLightPreference extends BoolDevicePreference {
+        { name = PREF_AUTOLIGHT; }
+        { feature = FEATURE_SETTING_FOR_BASIC; }
+        { index = 1; mask = 0x04; }
+    }
+
+    public class PowerSavingPreference extends BoolDevicePreference {
+        { name = PREF_POWER_SAVING; }
+        { feature = FEATURE_SETTING_FOR_BASIC; }
+        { index = 1; mask = 0x10; }
+    }
+
+    public class LongerLightDurationPreference extends InvertedBoolDevicePreference {
+        { name = PREF_LIGHT_DURATION_LONGER; }
+        { feature = FEATURE_SETTING_FOR_BASIC; }
+        { index = 2; mask = 0x01; }
+    }
+
+    public class DayMonthOrderPreference extends AutoBoolDevicePreference {
+        { name = PREF_DATEFORMAT; }
+        { feature = FEATURE_SETTING_FOR_BASIC; }
+        { index = 4; mask = 0x01; }
+        { getter = gbPrefs -> gbPrefs.getDateFormatDayMonthOrder(); }
+        { autoValue = PREF_DATEFORMAT_AUTO; }
+        { trueValue = PREF_DATEFORMAT_MONTH_DAY; }
+        { falseValue = PREF_DATEFORMAT_DAY_MONTH; }
+    }
+
+    public class LanguagePreference extends UnsignedByteDevicePreference {
+        { name = PREF_LANGUAGE; }
+        { feature = FEATURE_SETTING_FOR_BASIC; }
+        { index = 5; }
+
+        String[] languages = { "en_US", "es_ES", "fr_FR"," de_DE", "it_IT", "ru_RU" };
+
+        @Override
+        public int getDevicePrefsValue() {
+            String value = getDevicePrefs().getString(name, PREF_LANGUAGE_AUTO);
+            int number = 0;
+            if (value.equals(PREF_LANGUAGE_AUTO)) {
+                String lang = Locale.getDefault().getLanguage() + "_";
+                for (int i=0; i < languages.length; i++) {
+                    if (languages[i].startsWith(lang)) {
+                        number = i;
+                        break;
+                    }
+                }
+            } else {
+                for (int i=0; i < languages.length; i++) {
+                    if (value.equals(languages[i])) {
+                        number = i;
+                        break;
+                    }
+                }
+            }
+            return number;
+        }
+
+        @Override
+        public boolean setDevicePrefsValue(SharedPreferences.Editor editor, int value) {
+            if (getDevicePrefsValue() != value) {
+                if (getDevicePrefs().getString(name, PREF_LANGUAGE_AUTO).equals(PREF_LANGUAGE_AUTO)) {
+                    return true;
+                } else {
+                    if (value < languages.length) {
+                        editor.putString(name, languages[value]);
+                    } else {
+                        editor.putString(name, "unknown");
+                    }
+                }
+            }
+            return false;
+        }
+    }
+
+    public class HourlyChimePreference extends InvertedBoolDevicePreference {
+        { name = PREF_HOURLY_CHIME_ENABLE; }
+        { feature = FEATURE_SETTING_FOR_ALM; }
+        { index = 1; mask = (byte) (0x80 & 0xff); }
+    }
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casio/CasioSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casio/CasioSupport.java
new file mode 100644
index 0000000..6baa6b9
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casio/CasioSupport.java
@@ -0,0 +1,66 @@
+/*  Copyright (C) 2021-2024 Andreas B枚hler, Arjan Schrijver, Johannes Krude
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.casio;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.time.ZonedDateTime;
+
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport;
+import nodomain.freeyourgadget.gadgetbridge.devices.casio.CasioConstants;
+
+public abstract class CasioSupport extends AbstractBTLEDeviceSupport {
+
+    protected boolean mFirstConnect = false;
+    private static final Logger LOG = LoggerFactory.getLogger(HuamiSupport.class);
+
+    public CasioSupport(Logger logger) {
+        super(logger);
+        addSupportedService(CasioConstants.WATCH_FEATURES_SERVICE_UUID);
+    }
+
+    @Override
+    public boolean connectFirstTime() {
+        mFirstConnect = true;
+        return connect();
+    }
+
+    static protected byte[] prepareCurrentTime(ZonedDateTime time) {
+        // somehow everyone uses reason=1 for Casio watches
+        return BLETypeConversions.toCurrentTime(time, 1);
+    }
+
+    public void setInitialized() {
+        mFirstConnect = false;
+        gbDevice.setState(GBDevice.State.INITIALIZED);
+        gbDevice.sendDeviceUpdateIntent(getContext());
+    }
+
+    @Override
+    public boolean getImplicitCallbackModify() {
+        return true;
+    }
+
+    @Override
+    public boolean getSendWriteRequestResponse() {
+        return false;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casio/CasioTimeZone.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casio/CasioTimeZone.java
new file mode 100644
index 0000000..ddbbdd2
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casio/CasioTimeZone.java
@@ -0,0 +1,410 @@
+/*  Copyright (C) 2023-2024 Johannes Krude
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+
+package nodomain.freeyourgadget.gadgetbridge.service.devices.casio;
+
+import java.util.Set;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Arrays;
+import java.nio.charset.StandardCharsets;
+import java.time.DayOfWeek;
+import java.time.Instant;
+import java.time.LocalTime;
+import java.time.Month;
+import java.time.ZoneId;
+import java.time.ZoneOffset;
+import java.time.zone.ZoneOffsetTransition;
+import java.time.zone.ZoneOffsetTransitionRule;
+import java.time.zone.ZoneRules;
+
+
+public class CasioTimeZone {
+
+/*
+There are six clocks on the Casio GW-B5600 / S100
+0 is the main clock
+1-5 are the world clocks
+
+0x1d 00 01 DST0 DST1 TZ0A TZ0B TZ1A TZ1B ff ff ff ff ff
+0x1d 02 03 DST2 DST3 TZ2A TZ2B TZ3A TZ3B ff ff ff ff ff
+0x1d 04 05 DST4 DST5 TZ4A TZ4B TZ5A TZ5B ff ff ff ff ff
+DST: bitwise flags; bit0: DST on, bit1: DST auto
+
+0x1e 0-5 TZ_A TZ_B TZ_OFF TZ_DSTOFF TZ_DSTRULES
+A/B seem to be ignored by the watch
+OFF & DSTOFF in 15 minute intervals
+
+0x1f 0-5 (18 bytes ASCII TZ name)
+
+Timezones selectable on the watch:
+                   A  B   OFF DSTOFF DSTRULES
+BAKER ISLAND       39 01  D0  04     00
+PAGO PAGO          D7 00  D4  04     00
+HONOLULU           7B 00  D8  04     00
+MARQUESAS ISLANDS  3A 01  DA  04     00
+ANCHORAGE          0C 00  DC  04     01
+LOS ANGELES        A1 00  E0  04     01
+DENVER             54 00  E4  04     01
+CHICAGO            42 00  E8  04     01
+NEW YORK           CA 00  EC  04     01
+HALIFAX            71 00  F0  04     01
+ST.JOHN'S          0C 01  F2  04     01
+RIO DE JANEIRO     F1 00  F4  04     00
+F.DE NORONHA       62 00  F8  04     00
+PRAIA              E9 00  FC  04     00
+UTC                00 00  00  00     00
+LONDON             A0 00  00  04     02
+PARIS              DC 00  04  04     02
+ATHENS             13 00  08  04     02
+JEDDAH             85 00  0C  04     00
+TEHRAN             16 01  0E  04     2B
+DUBAI              5B 00  10  04     00
+KABUL              88 00  12  04     00
+KARACHI            8B 00  14  04     00
+DELHI              52 00  16  04     00
+KATHMANDU          8C 00  17  04     00
+DHAKA              56 00  18  04     00
+YANGON             2F 01  1A  04     00
+BANGKOK            1C 00  1C  04     00
+HONG KONG          7A 00  20  04     00
+PYONGYANG          EA 00  24  04     00
+EUCLA              36 01  23  04     00
+TOKYO              19 01  24  04     00
+ADELAIDE           05 00  26  04     04
+SYDNEY             0F 01  28  04     04
+LORD HOWE ISLAND   37 01  2A  02     12
+NOUMEA             CD 00  2C  04     00
+WELLINGTON         2B 01  30  04     05
+CHATHAM ISLANDS    3F 00  33  04     17
+NUKUALOFA          D0 00  34  04     00
+KIRITIMATI         93 00  38  04     00
+
+Timezones NOT selectable on the watch:
+                   A  B   OFF DSTOFF DSTRULES
+CASABLANCA         3A 00  00  04     0F
+BEIRUT             22 00  08  04     0C
+JERUSALEM          86 00  08  04     2A
+NORFOLK ISLAND     38 01  2C  04     04
+EASTER ISLAND      5E 00  E8  04     1C
+HAVANA             75 00  EC  04     15
+SANTIAGO           02 01  F0  04     1B
+ASUNCION           12 00  F0  04     09
+PONTA DELGADA      E4 00  FC  04     02
+
+*/
+
+    private byte[] name;
+    private byte[] number;
+    private byte offset;
+    private byte dstOffset;
+    private byte dstRules;
+    private byte dstSetting;
+
+    // bitwise flags
+    final static byte DST_SETTING_ON   = 1;
+    final static byte DST_SETTING_AUTO = 2;
+
+    private CasioTimeZone(byte[] name, byte[] number, byte offset, byte dstOffset, byte dstRules, byte dstSetting) {
+        this.name = name;
+        this.number = number;
+        this.offset = offset;
+        this.dstOffset = dstOffset;
+        this.dstRules = dstRules;
+        this.dstSetting = dstSetting;
+    }
+
+    static public Set<Casio2C2DSupport.FeatureRequest> requests(int slot) {
+        HashSet<Casio2C2DSupport.FeatureRequest> requests = new HashSet();
+        requests.add(new Casio2C2DSupport.FeatureRequest(Casio2C2DSupport.FEATURE_DST_WATCH_STATE, (byte) (slot/2*2)));
+        requests.add(new Casio2C2DSupport.FeatureRequest(Casio2C2DSupport.FEATURE_DST_SETTING, (byte) slot));
+        requests.add(new Casio2C2DSupport.FeatureRequest(Casio2C2DSupport.FEATURE_WORLD_CITY, (byte) slot));
+        return requests;
+    }
+
+    static public byte[] dstWatchStateBytes(int slotA, CasioTimeZone zoneA, int slotB, CasioTimeZone zoneB) {
+        return new byte[] {
+            Casio2C2DSupport.FEATURE_DST_WATCH_STATE,
+            (byte) slotA,
+            (byte) slotB,
+            zoneA.dstSetting,
+            zoneB.dstSetting,
+            zoneA.number[0],
+            zoneA.number[1],
+            zoneB.number[0],
+            zoneB.number[1],
+            (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff};
+    }
+
+    public byte[] dstSettingBytes(int slot) {
+        return new byte[] {
+            Casio2C2DSupport.FEATURE_DST_SETTING,
+            (byte) slot,
+            number[0],
+            number[1],
+            offset,
+            dstOffset,
+            dstRules};
+    }
+
+    public byte[] worldCityBytes(int slot) {
+        byte[] bytes = {
+            Casio2C2DSupport.FEATURE_WORLD_CITY,
+            (byte) slot,
+            0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
+        System.arraycopy(name, 0, bytes, 2, Math.min(name.length, 18));
+        return bytes;
+    }
+
+    public static CasioTimeZone fromWatchResponses(Map<Casio2C2DSupport.FeatureRequest, byte[]> responses, int slot) {
+        byte[] name = "unknown".getBytes(StandardCharsets.US_ASCII);
+        byte[] number = {0,0};
+        byte offset = 0;
+        byte dstOffset = 0;
+        byte dstRules = 0;
+        byte dstSetting = 0;
+
+        for (byte[] response: responses.values()) {
+            if (response[0] == Casio2C2DSupport.FEATURE_DST_WATCH_STATE && response.length >= 9) {
+                if (response[1] == slot) {
+                    dstSetting = response[3];
+                    number = new byte[] {response[5], response[6]};
+                }
+                if (response[2] == slot) {
+                    dstSetting = response[4];
+                    number = new byte[] {response[7], response[8]};
+                }
+            } else if (response[0] == Casio2C2DSupport.FEATURE_DST_SETTING && response.length >= 7 && response[1] == slot) {
+                number = new byte[] {response[2], response[3]};
+                offset = response[4];
+                dstOffset = response[5];
+                dstRules = response[6];
+            } else if (response[0] == Casio2C2DSupport.FEATURE_WORLD_CITY && response.length >= 2 && response[1] == slot) {
+                int size;
+                for (size = 0; size < response.length-2; size++) {
+                    if (response[2+size] == 0) {
+                        break;
+                    }
+                }
+                name = Arrays.copyOfRange(response, 2, 2+size);
+            }
+        }
+
+        return new CasioTimeZone(name, number, offset, dstOffset, dstRules, dstSetting);
+    }
+
+    public static CasioTimeZone fromZoneId(ZoneId zone, Instant time, String zoneName) {
+        ZoneRules rules = zone.getRules();
+
+        byte[] name = zoneName.getBytes(StandardCharsets.US_ASCII);
+        byte[] number = {0,0};
+        byte offset = (byte) (rules.getStandardOffset(time).getTotalSeconds() / 60 / 15);
+        byte dstOffset = 0; // can be set only later once we now the next transition
+        byte dstRules = 0;
+        byte dstSetting = 0;
+
+        ZoneOffsetTransition next = rules.nextTransition(time);
+        int nextYear = next.getInstant().atZone(zone).getYear();
+        ZoneOffsetTransition next2 = (next == null ? null: rules.nextTransition(next.getInstant()));
+        int next2Year = (next2 == null ? 0 : next2.getInstant().atZone(zone).getYear());
+
+        if (next == null) {
+        // no DST is easy
+            dstSetting = DST_SETTING_AUTO;
+        } else {
+            // we need an Instant with DST on to get the dstOffset
+            if (rules.isDaylightSavings(time)) {
+                dstOffset = (byte) (rules.getDaylightSavings(time).getSeconds() / 60 / 15);
+            } else {
+                dstOffset = (byte) (rules.getDaylightSavings(next.getInstant().plusSeconds(1)).getSeconds() / 60 / 15);
+            }
+            // find Watch DST rules
+            dstRules = findWatchDstRules(offset, dstOffset, next, nextYear, next2, next2Year);
+            if (dstRules != 0) {
+                // DST AUTO if the watch knows at least the next transition
+                // otherwise will result in incorrect time between actual DST change and next sync
+                dstSetting |= DST_SETTING_AUTO;
+            }
+            // if DST bit is incorrect, the watch will substract or add time
+            if (rules.isDaylightSavings(time)) {
+                dstSetting |= DST_SETTING_ON;
+            }
+        }
+
+        return new CasioTimeZone(name, number, offset, dstOffset, dstRules, dstSetting);
+    }
+
+    // We are searching for watch DST rules which match the next two transitions.
+    // In case only the next transition matches a rule, the rule is still used
+    static byte findWatchDstRules(byte offset, byte dstOffset, ZoneOffsetTransition next, int nextYear, ZoneOffsetTransition next2, int next2Year) {
+        WatchDstRules candidate = null;
+        for (WatchDstRules r: watchDstRules) {
+            int match = r.matches(offset, dstOffset, next, nextYear, next2, next2Year);
+            if (match == 2)
+                return r.dstRules;
+            if (match == 1 && candidate == null)
+                candidate = r;
+        }
+        if (candidate != null)
+            return candidate.dstRules;
+        return (byte) 0;
+    }
+
+    static class WatchDstRules {
+        final byte offset;
+        final byte dstOffset;
+        final byte dstRules;
+        final ZoneOffsetTransitionRule ruleA;
+        final ZoneOffsetTransitionRule ruleB;
+
+        WatchDstRules(int offset, int dstOffset, int dstRules, ZoneOffsetTransitionRule ruleA, ZoneOffsetTransitionRule ruleB) {
+            this.offset = (byte) offset;
+            this.dstOffset = (byte) dstOffset;
+            this.dstRules = (byte) dstRules;
+            this.ruleA = ruleA;
+            this.ruleB = ruleB;
+        }
+
+        // returns how many of the next transitions match the rules
+        int matches(byte offset, byte dstOffset, ZoneOffsetTransition next, int nextYear, ZoneOffsetTransition next2, int next2Year) {
+            if (offset != this.offset || dstOffset != this.dstOffset)
+                return -1;
+            if (this.ruleA.createTransition(nextYear).equals(next)) {
+                if (this.ruleB.createTransition(next2Year).equals(next2))
+                    return 2;
+                return 1;
+            }
+            if (this.ruleB.createTransition(nextYear).equals(next)) {
+                if (this.ruleA.createTransition(next2Year).equals(next2))
+                    return 2;
+                return 1;
+            }
+            return 0;
+        }
+    }
+
+    // All known Watch DST Rules
+    // Possibly incomplete and incorrect
+    //
+    // When adding  new WatchDstRules, test them:
+    // 1. Apply the following changes to CasioGWB5600InitOperation
+    //    +    static int nextNext = 0;
+    //      private void setClocks(TransactionBuilder builder) {
+    //         ZoneId tz = ZoneId.systemDefault();
+    //         Instant now = Instant.now().plusSeconds(2);
+    //    +    now = tz.getRules().nextTransition(now).getInstant();
+    //    +    if (nextNext != 0)
+    //    +        now = tz.getRules().nextTransition(now).getInstant();
+    //    +    nextNext ^= 1;
+    //    +    now = now.minusSeconds(10);
+    // 2. Sync the time on the watch and observe a DST change
+    // 3. Repeat the time sync to obseve a second DST change
+    static final WatchDstRules[] watchDstRules = {
+        // Europe/London
+        new WatchDstRules(0x00, 0x04, 0x02,
+            ZoneOffsetTransitionRule.of(Month.MARCH, 25, DayOfWeek.SUNDAY, LocalTime.of(1, 0), false, ZoneOffsetTransitionRule.TimeDefinition.UTC, ZoneOffset.ofTotalSeconds(0), ZoneOffset.ofTotalSeconds(0), ZoneOffset.ofTotalSeconds(3600)),
+            ZoneOffsetTransitionRule.of(Month.OCTOBER, 25, DayOfWeek.SUNDAY, LocalTime.of(1, 0), false, ZoneOffsetTransitionRule.TimeDefinition.UTC, ZoneOffset.ofTotalSeconds(0), ZoneOffset.ofTotalSeconds(3600), ZoneOffset.ofTotalSeconds(0))),
+        // Europe/Paris
+        new WatchDstRules(0x04, 0x04, 0x02,
+            ZoneOffsetTransitionRule.of(Month.MARCH, 25, DayOfWeek.SUNDAY, LocalTime.of(1, 0), false, ZoneOffsetTransitionRule.TimeDefinition.UTC, ZoneOffset.ofTotalSeconds(3600), ZoneOffset.ofTotalSeconds(3600), ZoneOffset.ofTotalSeconds(7200)),
+            ZoneOffsetTransitionRule.of(Month.OCTOBER, 25, DayOfWeek.SUNDAY, LocalTime.of(1, 0), false, ZoneOffsetTransitionRule.TimeDefinition.UTC, ZoneOffset.ofTotalSeconds(3600), ZoneOffset.ofTotalSeconds(7200), ZoneOffset.ofTotalSeconds(3600))),
+        // Europe/Athen
+        new WatchDstRules(0x08, 0x04, 0x02,
+            ZoneOffsetTransitionRule.of(Month.MARCH, 25, DayOfWeek.SUNDAY, LocalTime.of(1, 0), false, ZoneOffsetTransitionRule.TimeDefinition.UTC, ZoneOffset.ofTotalSeconds(7200), ZoneOffset.ofTotalSeconds(7200), ZoneOffset.ofTotalSeconds(10800)),
+            ZoneOffsetTransitionRule.of(Month.OCTOBER, 25, DayOfWeek.SUNDAY, LocalTime.of(1, 0), false, ZoneOffsetTransitionRule.TimeDefinition.UTC, ZoneOffset.ofTotalSeconds(7200), ZoneOffset.ofTotalSeconds(10800), ZoneOffset.ofTotalSeconds(7200))),
+        // Asia/Beirut
+        new WatchDstRules(0x08, 0x04, 0x0C,
+            ZoneOffsetTransitionRule.of(Month.MARCH, 25, DayOfWeek.SUNDAY, LocalTime.of(0, 0), false, ZoneOffsetTransitionRule.TimeDefinition.WALL, ZoneOffset.ofTotalSeconds(7200), ZoneOffset.ofTotalSeconds(7200), ZoneOffset.ofTotalSeconds(10800)),
+            ZoneOffsetTransitionRule.of(Month.OCTOBER, 25, DayOfWeek.SUNDAY, LocalTime.of(0, 0), false, ZoneOffsetTransitionRule.TimeDefinition.WALL, ZoneOffset.ofTotalSeconds(7200), ZoneOffset.ofTotalSeconds(10800), ZoneOffset.ofTotalSeconds(7200))),
+        // Asia/Jerusalem
+        new WatchDstRules(0x08, 0x04, 0x2A,
+            ZoneOffsetTransitionRule.of(Month.MARCH, 23, DayOfWeek.FRIDAY, LocalTime.of(2, 0), false, ZoneOffsetTransitionRule.TimeDefinition.WALL, ZoneOffset.ofTotalSeconds(7200), ZoneOffset.ofTotalSeconds(7200), ZoneOffset.ofTotalSeconds(10800)),
+            ZoneOffsetTransitionRule.of(Month.OCTOBER, 25, DayOfWeek.SUNDAY, LocalTime.of(2, 0), false, ZoneOffsetTransitionRule.TimeDefinition.WALL, ZoneOffset.ofTotalSeconds(7200), ZoneOffset.ofTotalSeconds(10800), ZoneOffset.ofTotalSeconds(7200))),
+        // Australia/Adelaide
+        new WatchDstRules(0x26, 0x04, 0x04,
+            ZoneOffsetTransitionRule.of(Month.APRIL, 1, DayOfWeek.SUNDAY, LocalTime.of(2, 0), false, ZoneOffsetTransitionRule.TimeDefinition.STANDARD, ZoneOffset.ofTotalSeconds(34200), ZoneOffset.ofTotalSeconds(37800), ZoneOffset.ofTotalSeconds(34200)),
+            ZoneOffsetTransitionRule.of(Month.OCTOBER, 1, DayOfWeek.SUNDAY, LocalTime.of(2, 0), false, ZoneOffsetTransitionRule.TimeDefinition.STANDARD, ZoneOffset.ofTotalSeconds(34200), ZoneOffset.ofTotalSeconds(34200), ZoneOffset.ofTotalSeconds(37800))),
+        // Australia/Sydney
+        new WatchDstRules(0x28, 0x04, 0x04,
+            ZoneOffsetTransitionRule.of(Month.APRIL, 1, DayOfWeek.SUNDAY, LocalTime.of(2, 0), false, ZoneOffsetTransitionRule.TimeDefinition.STANDARD, ZoneOffset.ofTotalSeconds(36000), ZoneOffset.ofTotalSeconds(39600), ZoneOffset.ofTotalSeconds(36000)),
+            ZoneOffsetTransitionRule.of(Month.OCTOBER, 1, DayOfWeek.SUNDAY, LocalTime.of(2, 0), false, ZoneOffsetTransitionRule.TimeDefinition.STANDARD, ZoneOffset.ofTotalSeconds(36000), ZoneOffset.ofTotalSeconds(36000), ZoneOffset.ofTotalSeconds(39600))),
+        // Australia/Lord_Howe
+        new WatchDstRules(0x2A, 0x02, 0x12,
+            ZoneOffsetTransitionRule.of(Month.APRIL, 1, DayOfWeek.SUNDAY, LocalTime.of(2, 0), false, ZoneOffsetTransitionRule.TimeDefinition.WALL, ZoneOffset.ofTotalSeconds(37800), ZoneOffset.ofTotalSeconds(39600), ZoneOffset.ofTotalSeconds(37800)),
+            ZoneOffsetTransitionRule.of(Month.OCTOBER, 1, DayOfWeek.SUNDAY, LocalTime.of(2, 0), false, ZoneOffsetTransitionRule.TimeDefinition.WALL, ZoneOffset.ofTotalSeconds(37800), ZoneOffset.ofTotalSeconds(37800), ZoneOffset.ofTotalSeconds(39600))),
+        // Pacific/Norfolk
+        new WatchDstRules(0x2C, 0x04, 0x04,
+            ZoneOffsetTransitionRule.of(Month.APRIL, 1, DayOfWeek.SUNDAY, LocalTime.of(2, 0), false, ZoneOffsetTransitionRule.TimeDefinition.STANDARD, ZoneOffset.ofTotalSeconds(39600), ZoneOffset.ofTotalSeconds(43200), ZoneOffset.ofTotalSeconds(39600)),
+            ZoneOffsetTransitionRule.of(Month.OCTOBER, 1, DayOfWeek.SUNDAY, LocalTime.of(2, 0), false, ZoneOffsetTransitionRule.TimeDefinition.STANDARD, ZoneOffset.ofTotalSeconds(39600), ZoneOffset.ofTotalSeconds(39600), ZoneOffset.ofTotalSeconds(43200))),
+        // Pacific/Auckland
+        new WatchDstRules(0x30, 0x04, 0x05,
+            ZoneOffsetTransitionRule.of(Month.APRIL, 1, DayOfWeek.SUNDAY, LocalTime.of(2, 0), false, ZoneOffsetTransitionRule.TimeDefinition.STANDARD, ZoneOffset.ofTotalSeconds(43200), ZoneOffset.ofTotalSeconds(46800), ZoneOffset.ofTotalSeconds(43200)),
+            ZoneOffsetTransitionRule.of(Month.SEPTEMBER, 24, DayOfWeek.SUNDAY, LocalTime.of(2, 0), false, ZoneOffsetTransitionRule.TimeDefinition.STANDARD, ZoneOffset.ofTotalSeconds(43200), ZoneOffset.ofTotalSeconds(43200), ZoneOffset.ofTotalSeconds(46800))),
+        // Pacific/Chatham
+        new WatchDstRules(0x33, 0x04, 0x17,
+            ZoneOffsetTransitionRule.of(Month.APRIL, 1, DayOfWeek.SUNDAY, LocalTime.of(2, 45), false, ZoneOffsetTransitionRule.TimeDefinition.STANDARD, ZoneOffset.ofTotalSeconds(45900), ZoneOffset.ofTotalSeconds(49500), ZoneOffset.ofTotalSeconds(45900)),
+            ZoneOffsetTransitionRule.of(Month.SEPTEMBER, 24, DayOfWeek.SUNDAY, LocalTime.of(2, 45), false, ZoneOffsetTransitionRule.TimeDefinition.STANDARD, ZoneOffset.ofTotalSeconds(45900), ZoneOffset.ofTotalSeconds(45900), ZoneOffset.ofTotalSeconds(49500))),
+        // America/Anchorage
+        new WatchDstRules(0xDC, 0x04, 0x01,
+            ZoneOffsetTransitionRule.of(Month.MARCH, 8, DayOfWeek.SUNDAY, LocalTime.of(2, 0), false, ZoneOffsetTransitionRule.TimeDefinition.WALL, ZoneOffset.ofTotalSeconds(-32400), ZoneOffset.ofTotalSeconds(-32400), ZoneOffset.ofTotalSeconds(-28800)),
+            ZoneOffsetTransitionRule.of(Month.NOVEMBER, 1, DayOfWeek.SUNDAY, LocalTime.of(2, 0), false, ZoneOffsetTransitionRule.TimeDefinition.WALL, ZoneOffset.ofTotalSeconds(-32400), ZoneOffset.ofTotalSeconds(-28800), ZoneOffset.ofTotalSeconds(-32400))),
+        // America/Los_Angeles
+        new WatchDstRules(0xE0, 0x04, 0x01,
+            ZoneOffsetTransitionRule.of(Month.MARCH, 8, DayOfWeek.SUNDAY, LocalTime.of(2, 0), false, ZoneOffsetTransitionRule.TimeDefinition.WALL, ZoneOffset.ofTotalSeconds(-28800), ZoneOffset.ofTotalSeconds(-28800), ZoneOffset.ofTotalSeconds(-25200)),
+            ZoneOffsetTransitionRule.of(Month.NOVEMBER, 1, DayOfWeek.SUNDAY, LocalTime.of(2, 0), false, ZoneOffsetTransitionRule.TimeDefinition.WALL, ZoneOffset.ofTotalSeconds(-28800), ZoneOffset.ofTotalSeconds(-25200), ZoneOffset.ofTotalSeconds(-28800))),
+        // America/Denver
+        new WatchDstRules(0xE4, 0x04, 0x01,
+            ZoneOffsetTransitionRule.of(Month.MARCH, 8, DayOfWeek.SUNDAY, LocalTime.of(2, 0), false, ZoneOffsetTransitionRule.TimeDefinition.WALL, ZoneOffset.ofTotalSeconds(-25200), ZoneOffset.ofTotalSeconds(-25200), ZoneOffset.ofTotalSeconds(-21600)),
+            ZoneOffsetTransitionRule.of(Month.NOVEMBER, 1, DayOfWeek.SUNDAY, LocalTime.of(2, 0), false, ZoneOffsetTransitionRule.TimeDefinition.WALL, ZoneOffset.ofTotalSeconds(-25200), ZoneOffset.ofTotalSeconds(-21600), ZoneOffset.ofTotalSeconds(-25200))),
+        // America/Chicago
+        new WatchDstRules(0xE8, 0x04, 0x01,
+            ZoneOffsetTransitionRule.of(Month.MARCH, 8, DayOfWeek.SUNDAY, LocalTime.of(2, 0), false, ZoneOffsetTransitionRule.TimeDefinition.WALL, ZoneOffset.ofTotalSeconds(-21600), ZoneOffset.ofTotalSeconds(-21600), ZoneOffset.ofTotalSeconds(-18000)),
+            ZoneOffsetTransitionRule.of(Month.NOVEMBER, 1, DayOfWeek.SUNDAY, LocalTime.of(2, 0), false, ZoneOffsetTransitionRule.TimeDefinition.WALL, ZoneOffset.ofTotalSeconds(-21600), ZoneOffset.ofTotalSeconds(-18000), ZoneOffset.ofTotalSeconds(-21600))),
+        // Chile/EasterIsland
+        new WatchDstRules(0xE8, 0x04, 0x1C,
+            ZoneOffsetTransitionRule.of(Month.APRIL, 2, DayOfWeek.SUNDAY, LocalTime.of(3, 0), false, ZoneOffsetTransitionRule.TimeDefinition.UTC, ZoneOffset.ofTotalSeconds(-21600), ZoneOffset.ofTotalSeconds(-18000), ZoneOffset.ofTotalSeconds(-21600)),
+            ZoneOffsetTransitionRule.of(Month.SEPTEMBER, 2, DayOfWeek.SUNDAY, LocalTime.of(4, 0), false, ZoneOffsetTransitionRule.TimeDefinition.UTC, ZoneOffset.ofTotalSeconds(-21600), ZoneOffset.ofTotalSeconds(-21600), ZoneOffset.ofTotalSeconds(-18000))),
+        // America/New_York
+        new WatchDstRules(0xEC, 0x04, 0x01,
+            ZoneOffsetTransitionRule.of(Month.MARCH, 8, DayOfWeek.SUNDAY, LocalTime.of(2, 0), false, ZoneOffsetTransitionRule.TimeDefinition.WALL, ZoneOffset.ofTotalSeconds(-18000), ZoneOffset.ofTotalSeconds(-18000), ZoneOffset.ofTotalSeconds(-14400)),
+            ZoneOffsetTransitionRule.of(Month.NOVEMBER, 1, DayOfWeek.SUNDAY, LocalTime.of(2, 0), false, ZoneOffsetTransitionRule.TimeDefinition.WALL, ZoneOffset.ofTotalSeconds(-18000), ZoneOffset.ofTotalSeconds(-14400), ZoneOffset.ofTotalSeconds(-18000))),
+        // America/Havana
+        new WatchDstRules(0xEC, 0x04, 0x15,
+            ZoneOffsetTransitionRule.of(Month.MARCH, 8, DayOfWeek.SUNDAY, LocalTime.of(0, 0), false, ZoneOffsetTransitionRule.TimeDefinition.STANDARD, ZoneOffset.ofTotalSeconds(-18000), ZoneOffset.ofTotalSeconds(-18000), ZoneOffset.ofTotalSeconds(-14400)),
+            ZoneOffsetTransitionRule.of(Month.NOVEMBER, 1, DayOfWeek.SUNDAY, LocalTime.of(0, 0), false, ZoneOffsetTransitionRule.TimeDefinition.STANDARD, ZoneOffset.ofTotalSeconds(-18000), ZoneOffset.ofTotalSeconds(-14400), ZoneOffset.ofTotalSeconds(-18000))),
+        // America/Santiago
+        new WatchDstRules(0xF0, 0x04, 0x1B,
+            ZoneOffsetTransitionRule.of(Month.APRIL, 2, DayOfWeek.SUNDAY, LocalTime.of(3, 0), false, ZoneOffsetTransitionRule.TimeDefinition.UTC, ZoneOffset.ofTotalSeconds(-14400), ZoneOffset.ofTotalSeconds(-10800), ZoneOffset.ofTotalSeconds(-14400)),
+            ZoneOffsetTransitionRule.of(Month.SEPTEMBER, 2, DayOfWeek.SUNDAY, LocalTime.of(4, 0), false, ZoneOffsetTransitionRule.TimeDefinition.UTC, ZoneOffset.ofTotalSeconds(-14400), ZoneOffset.ofTotalSeconds(-14400), ZoneOffset.ofTotalSeconds(-10800))),
+        // America/Halifax
+        new WatchDstRules(0xF0, 0x04, 0x01,
+            ZoneOffsetTransitionRule.of(Month.MARCH, 8, DayOfWeek.SUNDAY, LocalTime.of(2, 0), false, ZoneOffsetTransitionRule.TimeDefinition.WALL, ZoneOffset.ofTotalSeconds(-14400), ZoneOffset.ofTotalSeconds(-14400), ZoneOffset.ofTotalSeconds(-10800)),
+            ZoneOffsetTransitionRule.of(Month.NOVEMBER, 1, DayOfWeek.SUNDAY, LocalTime.of(2, 0), false, ZoneOffsetTransitionRule.TimeDefinition.WALL, ZoneOffset.ofTotalSeconds(-14400), ZoneOffset.ofTotalSeconds(-10800), ZoneOffset.ofTotalSeconds(-14400))),
+        // America/Asuncion
+        new WatchDstRules(0xF0, 0x04, 0x09,
+            ZoneOffsetTransitionRule.of(Month.MARCH, 22, DayOfWeek.SUNDAY, LocalTime.of(0, 0), false, ZoneOffsetTransitionRule.TimeDefinition.WALL, ZoneOffset.ofTotalSeconds(-14400), ZoneOffset.ofTotalSeconds(-10800), ZoneOffset.ofTotalSeconds(-14400)),
+            ZoneOffsetTransitionRule.of(Month.OCTOBER, 1, DayOfWeek.SUNDAY, LocalTime.of(0, 0), false, ZoneOffsetTransitionRule.TimeDefinition.WALL, ZoneOffset.ofTotalSeconds(-14400), ZoneOffset.ofTotalSeconds(-14400), ZoneOffset.ofTotalSeconds(-10800))),
+        // America/St_Johns
+        new WatchDstRules(0xF2, 0x04, 0x01,
+            ZoneOffsetTransitionRule.of(Month.MARCH, 8, DayOfWeek.SUNDAY, LocalTime.of(2, 0), false, ZoneOffsetTransitionRule.TimeDefinition.WALL, ZoneOffset.ofTotalSeconds(-12600), ZoneOffset.ofTotalSeconds(-12600), ZoneOffset.ofTotalSeconds(-9000)),
+            ZoneOffsetTransitionRule.of(Month.NOVEMBER, 1, DayOfWeek.SUNDAY, LocalTime.of(2, 0), false, ZoneOffsetTransitionRule.TimeDefinition.WALL, ZoneOffset.ofTotalSeconds(-12600), ZoneOffset.ofTotalSeconds(-9000), ZoneOffset.ofTotalSeconds(-12600))),
+        // Atlantic/Azores
+        new WatchDstRules(0xFC, 0x04, 0x02,
+            ZoneOffsetTransitionRule.of(Month.MARCH, 25, DayOfWeek.SUNDAY, LocalTime.of(1, 0), false, ZoneOffsetTransitionRule.TimeDefinition.UTC, ZoneOffset.ofTotalSeconds(-3600), ZoneOffset.ofTotalSeconds(-3600), ZoneOffset.ofTotalSeconds(0)),
+            ZoneOffsetTransitionRule.of(Month.OCTOBER, 25, DayOfWeek.SUNDAY, LocalTime.of(1, 0), false, ZoneOffsetTransitionRule.TimeDefinition.UTC, ZoneOffset.ofTotalSeconds(-3600), ZoneOffset.ofTotalSeconds(0), ZoneOffset.ofTotalSeconds(-3600))),
+    };
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casio/ecbs100/CasioECBS100DeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casio/ecbs100/CasioECBS100DeviceSupport.java
new file mode 100644
index 0000000..24e8d8f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casio/ecbs100/CasioECBS100DeviceSupport.java
@@ -0,0 +1,63 @@
+/*  Copyright (C) 2023-2024 Johannes Krude, Jisakiel
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.casio.ecbs100;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.casio.BasicCasio2C2DSupport;
+
+// TODO abstract methods out from GWB5600
+public class CasioECBS100DeviceSupport extends BasicCasio2C2DSupport {
+    private static final Logger LOG = LoggerFactory.getLogger(CasioECBS100DeviceSupport.class);
+
+    public CasioECBS100DeviceSupport() {
+        super(LOG);
+    }
+
+    @Override
+    public DevicePreference[] supportedDevicePreferences() {
+        return new DevicePreference[] {
+            new LanguagePreference(),
+            new TimeFormatPreference(),
+            new DayMonthOrderPreference(),
+            new OperatingSoundPreference(),
+            new HourlyChimePreference(),
+            new AutoLightPreference(),
+            new LongerLightDurationPreference(),
+            new PowerSavingPreference(),
+            new ConnectionDurationPreference(),
+            new TimeSyncPreference(),
+        };
+    }
+
+    @Override
+    public boolean useAutoConnect() {
+        return false;
+    }
+
+    @Override
+    public boolean connectFirstTime() {
+    // remove this workaround in case Gadgetbridge fixes
+    // https://codeberg.org/Freeyourgadget/Gadgetbridge/issues/3216
+        setAutoReconnect(true);
+        return connect();
+    }
+
+
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casio/gb6900/CasioGB6900DeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casio/gb6900/CasioGB6900DeviceSupport.java
new file mode 100644
index 0000000..e9f558a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casio/gb6900/CasioGB6900DeviceSupport.java
@@ -0,0 +1,724 @@
+/*  Copyright (C) 2023-2024 Andreas B枚hler, Johannes Krude
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.casio.gb6900;
+
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.bluetooth.BluetoothGattDescriptor;
+import android.bluetooth.BluetoothGattService;
+import android.content.SharedPreferences;
+import android.widget.Toast;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.time.ZonedDateTime;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.UUID;
+import java.util.concurrent.TimeUnit;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventCallControl;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventFindPhone;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventMusicControl;
+import nodomain.freeyourgadget.gadgetbridge.devices.casio.CasioConstants;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
+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.service.btle.GattCharacteristic;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.GattService;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.ServerTransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.casio.CasioSupport;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
+
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_DISCONNECTNOTIF_NOSHED;
+
+public class CasioGB6900DeviceSupport extends CasioSupport {
+    private static final Logger LOG = LoggerFactory.getLogger(CasioGB6900DeviceSupport.class);
+
+    private final ArrayList<BluetoothGattCharacteristic> mCasioCharacteristics = new ArrayList<BluetoothGattCharacteristic>();
+    private CasioGB6900HandlerThread mHandlerThread = null;
+    private MusicSpec mBufferMusicSpec = null;
+    private MusicStateSpec mBufferMusicStateSpec = null;
+    private BluetoothGatt mBtGatt = null;
+    private CasioConstants.Model mModel = CasioConstants.Model.MODEL_CASIO_GENERIC;
+
+    private static final int mCasioSleepTime = 50;
+
+    public CasioGB6900DeviceSupport() {
+        super(LOG);
+        addSupportedService(GattService.UUID_SERVICE_IMMEDIATE_ALERT);
+        addSupportedService(CasioConstants.CASIO_VIRTUAL_SERVER_SERVICE);
+        addSupportedService(CasioConstants.ALERT_SERVICE_UUID);
+        addSupportedService(CasioConstants.CASIO_IMMEDIATE_ALERT_SERVICE_UUID);
+        addSupportedService(CasioConstants.CURRENT_TIME_SERVICE_UUID);
+        addSupportedService(CasioConstants.WATCH_CTRL_SERVICE_UUID);
+        addSupportedService(CasioConstants.CASIO_PHONE_ALERT_STATUS_SERVICE);
+        addSupportedService(CasioConstants.MORE_ALERT_SERVICE_UUID);
+        addSupportedService(CasioConstants.TX_POWER_SERVICE_UUID);
+        addSupportedService(CasioConstants.LINK_LOSS_SERVICE);
+        addSupportedService(CasioConstants.IMMEDIATE_ALERT_SERVICE_UUID);
+
+        BluetoothGattService casioGATTService = new BluetoothGattService(CasioConstants.WATCH_CTRL_SERVICE_UUID, BluetoothGattService.SERVICE_TYPE_PRIMARY);
+        BluetoothGattCharacteristic bluetoothGATTCharacteristic = new BluetoothGattCharacteristic(CasioConstants.KEY_CONTAINER_CHARACTERISTIC_UUID, BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE, BluetoothGattCharacteristic.PERMISSION_WRITE);
+        bluetoothGATTCharacteristic.setValue(new byte[0]);
+
+        BluetoothGattCharacteristic bluetoothGATTCharacteristic2 = new BluetoothGattCharacteristic(CasioConstants.NAME_OF_APP_CHARACTERISTIC_UUID, BluetoothGattCharacteristic.PROPERTY_READ, BluetoothGattCharacteristic.PERMISSION_READ | BluetoothGattCharacteristic.PERMISSION_READ_ENCRYPTED);
+        bluetoothGATTCharacteristic2.setValue(CasioConstants.MUSIC_MESSAGE.getBytes());
+
+        BluetoothGattDescriptor bluetoothGattDescriptor = new BluetoothGattDescriptor(CasioConstants.CCC_DESCRIPTOR_UUID, BluetoothGattDescriptor.PERMISSION_READ | BluetoothGattDescriptor.PERMISSION_WRITE);
+        bluetoothGattDescriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
+
+        bluetoothGATTCharacteristic2.addDescriptor(bluetoothGattDescriptor);
+
+        casioGATTService.addCharacteristic(bluetoothGATTCharacteristic);
+        casioGATTService.addCharacteristic(bluetoothGATTCharacteristic2);
+
+        addSupportedServerService(casioGATTService);
+    }
+
+    @Override
+    public boolean useAutoConnect() {
+        return true;
+    }
+
+    @Override
+    public boolean connectFirstTime() {
+        GB.toast(getContext(), "After first connect, disable and enable bluetooth on your Casio watch to really connect", Toast.LENGTH_SHORT, GB.INFO);
+        mFirstConnect = true;
+        return super.connect();
+    }
+
+    @Override
+    public void dispose() {
+        LOG.info("Dispose");
+        close();
+
+        super.dispose();
+    }
+
+    private void close() {
+        if (mHandlerThread != null) {
+            mHandlerThread.quit();
+            mHandlerThread.interrupt();
+            mHandlerThread = null;
+        }
+    }
+
+    @Override
+    public void onServicesDiscovered(BluetoothGatt gatt) {
+        mBtGatt = gatt;
+        super.onServicesDiscovered(gatt);
+    }
+
+    @Override
+    protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
+        LOG.info("Initializing");
+
+        if(mFirstConnect) {
+            setInitialized();
+            getDevice().setFirmwareVersion("N/A");
+            getDevice().setFirmwareVersion2("N/A");
+            builder.bond();
+            return builder;
+        }
+
+        String name = gbDevice.getName();
+
+        if(name.contains("5600B")) {
+            mModel = CasioConstants.Model.MODEL_CASIO_5600B;
+        } else if(name.contains("6900B")) {
+            mModel = CasioConstants.Model.MODEL_CASIO_6900B;
+        } else if(name.contains("STB-1000")) {
+            mModel = CasioConstants.Model.MODEL_CASIO_STB1000;
+        } else {
+            mModel = CasioConstants.Model.MODEL_CASIO_GENERIC;
+        }
+
+        // We skip configuring BLE settings for the STB-1000 since it powers off
+        // BLE on the watch.
+        if(mModel != CasioConstants.Model.MODEL_CASIO_STB1000) {
+            try {
+                new InitOperation(this, builder).perform();
+            } catch (IOException e) {
+                GB.toast(getContext(), "Initializing Casio watch failed", Toast.LENGTH_SHORT, GB.ERROR, e);
+            }
+        }
+
+        getDevice().setFirmwareVersion("N/A");
+        getDevice().setFirmwareVersion2("N/A");
+
+
+        builder.setCallback(this);
+
+        configureWatch(builder);
+
+        addCharacteristics();
+        enableNotifications(builder, true);
+
+        LOG.info("Initialization Done");
+
+        return builder;
+    }
+
+    CasioConstants.Model getModel() {
+        return mModel;
+    }
+
+    private void configureWatch(TransactionBuilder builder) {
+        if (mBtGatt == null) {
+            return;
+        }
+
+        SharedPreferences sharedPreferences = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress());
+
+        boolean enable = sharedPreferences.getBoolean(PREF_DISCONNECTNOTIF_NOSHED, false);
+
+        byte[] value = new byte[1];
+
+        if(enable)
+            value[0] = GattCharacteristic.MILD_ALERT;
+        else
+            value[0] = GattCharacteristic.NO_ALERT;
+
+        BluetoothGattService llService = mBtGatt.getService(CasioConstants.LINK_LOSS_SERVICE);
+        BluetoothGattCharacteristic charact = llService.getCharacteristic(CasioConstants.ALERT_LEVEL_CHARACTERISTIC_UUID);
+        builder.write(charact, value);
+        builder.wait(mCasioSleepTime);
+    }
+
+    private void addCharacteristics() {
+        mCasioCharacteristics.clear();
+        mCasioCharacteristics.add(getCharacteristic(CasioConstants.CASIO_A_NOT_COM_SET_NOT));
+        mCasioCharacteristics.add(getCharacteristic(CasioConstants.CASIO_A_NOT_W_REQ_NOT));
+        mCasioCharacteristics.add(getCharacteristic(CasioConstants.ALERT_LEVEL_CHARACTERISTIC_UUID));
+        mCasioCharacteristics.add(getCharacteristic(CasioConstants.RINGER_CONTROL_POINT));
+    }
+
+    public boolean enableNotifications(TransactionBuilder builder, boolean enable) {
+        for(BluetoothGattCharacteristic charact : mCasioCharacteristics) {
+            builder.notify(charact, enable);
+            builder.wait(mCasioSleepTime);
+        }
+        return true;
+    }
+
+    public void readTxPowerLevel() {
+        try {
+            TransactionBuilder builder = performInitialized("readTxPowerLevel");
+            builder.read(getCharacteristic(CasioConstants.TX_POWER_LEVEL_CHARACTERISTIC_UUID));
+            builder.queue(getQueue());
+        } catch (IOException e) {
+            LOG.warn("readTxPowerLevel failed: " + e.getMessage());
+        }
+    }
+
+    private void writeCasioCurrentTime(TransactionBuilder builder) {
+        byte[] arr = prepareCurrentTime(ZonedDateTime.now());
+
+        BluetoothGattCharacteristic charact = getCharacteristic(CasioConstants.CURRENT_TIME_CHARACTERISTIC_UUID);
+        if(charact != null) {
+            charact.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT);
+            builder.write(charact, arr);
+        }
+        else {
+            LOG.warn("Characteristic not found: CURRENT_TIME_CHARACTERISTIC_UUID");
+        }
+    }
+
+    private void writeCasioLocalTimeInformation(TransactionBuilder builder) {
+        Calendar cal = Calendar.getInstance();
+        int zoneOffset = (int)TimeUnit.MILLISECONDS.toMinutes(cal.get(Calendar.ZONE_OFFSET));
+        int dstOffset = (int)TimeUnit.MILLISECONDS.toMinutes(cal.get(Calendar.DST_OFFSET));
+        byte byte0 = (byte)(zoneOffset / 15);
+        byte byte1 = (byte)(dstOffset / 15);
+        BluetoothGattCharacteristic charact = getCharacteristic(CasioConstants.LOCAL_TIME_CHARACTERISTIC_UUID);
+        if(charact != null) {
+            builder.write(charact, new byte[]{byte0, byte1});
+        }
+        else {
+            LOG.warn("Characteristic not found: LOCAL_TIME_CHARACTERISTIC_UUID");
+        }
+
+    }
+
+    private void writeCasioVirtualServerFeature(TransactionBuilder builder) {
+        byte byte0 = (byte)0;
+        byte0 |= 1; // Casio Current Time Service
+        byte0 |= 2; // Casio Alert Notification Service
+        byte0 |= 4; // Casio Phone Alert Status Service
+        byte0 |= 8; // Casio Immediate Alert Service
+
+        BluetoothGattCharacteristic charact = getCharacteristic(CasioConstants.CASIO_VIRTUAL_SERVER_FEATURES);
+        if(charact != null) {
+            builder.write(charact, new byte[]{byte0, 0x00});
+        }
+        else {
+            LOG.warn("Characteristic not found: CASIO_VIRTUAL_SERVER_FEATURES");
+        }
+    }
+
+    private boolean handleInitResponse(byte data) {
+        boolean handled = false;
+        switch (data) {
+            case (byte) 1:
+                LOG.info("Initialization done, setting state to INITIALIZED");
+                if (mHandlerThread != null) {
+                    if (mHandlerThread.isAlive()) {
+                        mHandlerThread.quit();
+                        mHandlerThread.interrupt();
+                    }
+                }
+                mHandlerThread = new CasioGB6900HandlerThread(getDevice(), getContext(), this);
+                mHandlerThread.start();
+                gbDevice.setState(GBDevice.State.INITIALIZED);
+                gbDevice.sendDeviceUpdateIntent(getContext());
+                handled = true;
+                break;
+            default:
+                LOG.warn("handleInitResponse: Error initializing device, received unexpected value: " + data);
+                gbDevice.setState(GBDevice.State.NOT_CONNECTED);
+                gbDevice.sendDeviceUpdateIntent(getContext());
+                handled = true;
+                break;
+        }
+        return handled;
+    }
+
+    private boolean handleTimeRequests(byte data) {
+        boolean handled = false;
+        switch(data) // Request Type
+        {
+            case (byte) 1:
+                try
+                {
+                    TransactionBuilder builder = createTransactionBuilder("writeCasioCurrentTime");
+                    writeCasioCurrentTime(builder);
+                    performConnected(builder.getTransaction());
+                    handled = true;
+                } catch (IOException e) {
+                    LOG.warn("handleTimeRequests::writeCasioCurrentTime failed: " + e.getMessage());
+                }
+                break;
+            case (byte) 2:
+                try
+                {
+                    TransactionBuilder builder = createTransactionBuilder("writeCasioLocalTimeInformation");
+                    writeCasioLocalTimeInformation(builder);
+                    performConnected(builder.getTransaction());
+                    handled = true;
+                } catch (IOException e) {
+                    LOG.warn("handleTimeRequests::writeCasioLocalTimeInformation failed: " + e.getMessage());
+                }
+                break;
+        }
+        return handled;
+    }
+
+    private boolean handleServerFeatureRequests(byte data) {
+        try
+        {
+            TransactionBuilder builder = createTransactionBuilder("writeCasioVirtualServerFeature");
+            writeCasioVirtualServerFeature(builder);
+            performConnected(builder.getTransaction());
+        } catch (IOException e) {
+            LOG.warn("handleServerFeatureRequests failed: " + e.getMessage());
+        }
+        return true;
+    }
+
+    private boolean handleCasioCom(byte[] data, boolean handleTime) {
+        boolean handled = false;
+
+        if(data.length < 3) {
+            LOG.warn("handleCasioCom failed: Received unexpected request (too short)");
+            return false;
+        }
+
+        switch(data[0]) // ServiceID
+        {
+            case 0:
+                handled = handleInitResponse(data[2]);
+                break;
+            case 2:
+                if(handleTime) {
+                    handled = handleTimeRequests(data[2]);
+                } else {
+                    handled = true;
+                }
+                break;
+            case 7:
+                handled = handleServerFeatureRequests(data[2]);
+                break;
+        }
+        return handled;
+    }
+
+    @Override
+    public boolean onCharacteristicRead(BluetoothGatt gatt,
+                                        BluetoothGattCharacteristic characteristic, int status) {
+
+        UUID characteristicUUID = characteristic.getUuid();
+        byte[] data = characteristic.getValue();
+
+        if(data.length == 0)
+            return true;
+
+        if(characteristicUUID.equals(CasioConstants.TX_POWER_LEVEL_CHARACTERISTIC_UUID)) {
+            StringBuilder str = new StringBuilder("onCharacteristicRead: Received power level: ");
+            for(int i=0; i<data.length; i++) {
+                str.append(String.format("0x%1x ", data[i]));
+            }
+            LOG.info(str.toString());
+        }
+        else {
+            return super.onCharacteristicRead(gatt, characteristic, status);
+        }
+
+        return true;
+    }
+
+    @Override
+    public boolean onCharacteristicChanged(BluetoothGatt gatt,
+                                           BluetoothGattCharacteristic characteristic) {
+        boolean handled = false;
+
+        UUID characteristicUUID = characteristic.getUuid();
+        byte[] data = characteristic.getValue();
+        if (data.length == 0)
+            return true;
+
+        if(characteristicUUID.equals(CasioConstants.CASIO_A_NOT_W_REQ_NOT)) {
+            handled = handleCasioCom(data, true);
+        }
+
+        if(characteristicUUID.equals(CasioConstants.CASIO_A_NOT_COM_SET_NOT)) {
+            handled = handleCasioCom(data, false);
+        }
+
+        if(characteristicUUID.equals(CasioConstants.ALERT_LEVEL_CHARACTERISTIC_UUID)) {
+            GBDeviceEventFindPhone findPhoneEvent = new GBDeviceEventFindPhone();
+            if(data[0] == 0x02) {
+                findPhoneEvent.event = GBDeviceEventFindPhone.Event.START;
+            }
+            else {
+                findPhoneEvent.event = GBDeviceEventFindPhone.Event.STOP;
+            }
+            evaluateGBDeviceEvent(findPhoneEvent);
+            handled = true;
+        }
+
+        if(characteristicUUID.equals(CasioConstants.RINGER_CONTROL_POINT)) {
+            if(data[0] == 0x02)
+            {
+                GBDeviceEventCallControl callControlEvent = new GBDeviceEventCallControl();
+                callControlEvent.event = GBDeviceEventCallControl.Event.IGNORE;
+                evaluateGBDeviceEvent(callControlEvent);
+            }
+            handled = true;
+        }
+
+        if(!handled) {
+            LOG.info("Unhandled characteristic change: " + characteristicUUID + " code: " + String.format("0x%1x ...", data[0]));
+            return super.onCharacteristicChanged(gatt, characteristic);
+        }
+        return true;
+    }
+
+    private void showNotification(byte icon, String title, String message) {
+        if(!isConnected())
+            return;
+        try {
+            TransactionBuilder builder = performInitialized("showNotification");
+            int len;
+
+            byte[] titleBytes = title.getBytes(StandardCharsets.US_ASCII);
+            len = titleBytes.length > 18 ? 18 : titleBytes.length;
+            byte[] msg = new byte[2 + len];
+            msg[0] = icon;
+            msg[1] = 1;
+            System.arraycopy(titleBytes, 0, msg, 2, len);
+
+            builder.write(getCharacteristic(CasioConstants.ALERT_CHARACTERISTIC_UUID), msg);
+            LOG.info("Showing notification, title: " + title + " message (not sent): " + message);
+            builder.queue(getQueue());
+        } catch (IOException e) {
+            LOG.warn("showNotification failed: " + e.getMessage());
+        }
+    }
+
+    @Override
+    public void onNotification(NotificationSpec notificationSpec) {
+        String notificationTitle = StringUtils.getFirstOf(notificationSpec.sender, notificationSpec.title);
+        byte icon;
+        switch (notificationSpec.type.getGenericType()) {
+            case "generic_sms":
+                icon = CasioConstants.SMS_NOTIFICATION_ID;
+                break;
+            case "generic_calendar":
+                icon = CasioConstants.CALENDAR_NOTIFICATION_ID;
+                break;
+            case "generic_email":
+                icon = CasioConstants.MAIL_NOTIFICATION_ID;
+                break;
+            default:
+                icon = CasioConstants.SNS_NOTIFICATION_ID;
+                break;
+        }
+        showNotification(icon, notificationTitle, notificationSpec.body);
+    }
+
+    @Override
+    public void onSetAlarms(ArrayList<? extends Alarm> alarms) {
+        if(!isConnected())
+            return;
+
+        try {
+            new SetAlarmOperation(this, alarms).perform();
+        } catch (IOException e) {
+            LOG.error("Setting alarm failed: " + e.getMessage());
+        }
+    }
+
+    @Override
+    public void onSetTime() {
+        if(!isConnected())
+            return;
+
+        try {
+            TransactionBuilder builder = performInitialized("SetTime");
+            writeCasioLocalTimeInformation(builder);
+            writeCasioCurrentTime(builder);
+            builder.queue(getQueue());
+        } catch(IOException e) {
+            LOG.warn("onSetTime failed: " + e.getMessage());
+        }
+    }
+
+    @Override
+    public void onSetCallState(CallSpec callSpec) {
+        switch (callSpec.command) {
+            case CallSpec.CALL_INCOMING:
+                showNotification(CasioConstants.CALL_NOTIFICATION_ID, callSpec.name, callSpec.number);
+                break;
+            default:
+                LOG.info("not sending CallSpec since only CALL_INCOMING is handled");
+                break;
+        }
+    }
+
+    @Override
+    public void onSetMusicState(MusicStateSpec stateSpec) {
+        if(stateSpec != mBufferMusicStateSpec)
+        {
+            mBufferMusicStateSpec = stateSpec;
+            sendMusicInfo();
+        }
+    }
+
+    private void sendMusicInfo()
+    {
+        if(!isConnected())
+            return;
+
+        try {
+            TransactionBuilder builder = performInitialized("sendMusicInfo");
+            String info = "";
+            if (mBufferMusicSpec.track != null && mBufferMusicSpec.track.length() > 0) {
+                info += mBufferMusicSpec.track;
+            }
+            if (mBufferMusicSpec.album != null && mBufferMusicSpec.album.length() > 0) {
+                info += mBufferMusicSpec.album;
+            }
+            if (mBufferMusicSpec.artist != null && mBufferMusicSpec.artist.length() > 0) {
+                info += mBufferMusicSpec.artist;
+            }
+            byte[] bInfo = info.getBytes(StandardCharsets.US_ASCII);
+            int len = bInfo.length > 17 ? 17 : bInfo.length;
+            byte[] arr = new byte[len + 3];
+            arr[0] = 0;
+            arr[1] = 10;
+            arr[2] = 1;
+            System.arraycopy(bInfo, 0, arr, 3, len);
+            builder.write(getCharacteristic(CasioConstants.MORE_ALERT_FOR_LONG_UUID), arr);
+            builder.queue(getQueue());
+        } catch (IOException e) {
+            LOG.warn("sendMusicInfo failed: " + e.getMessage());
+        }
+    }
+
+    @Override
+    public void onSetMusicInfo(MusicSpec musicSpec) {
+        if(musicSpec != mBufferMusicSpec)
+        {
+            mBufferMusicSpec = musicSpec;
+            sendMusicInfo();
+        }
+    }
+
+    @Override
+    public void onFindDevice(boolean start) {
+        if (!isConnected()) {
+            return;
+        }
+
+        if (mBtGatt == null) {
+            return;
+        }
+
+        if (start) {
+            try {
+                TransactionBuilder builder = performInitialized("findDevice");
+                byte[] value = new byte[]{GattCharacteristic.HIGH_ALERT};
+
+                BluetoothGattService service = mBtGatt.getService(CasioConstants.IMMEDIATE_ALERT_SERVICE_UUID);
+                BluetoothGattCharacteristic charact = service.getCharacteristic(CasioConstants.ALERT_LEVEL_CHARACTERISTIC_UUID);
+                builder.write(charact, value);
+                LOG.info("onFindDevice sent");
+                builder.queue(getQueue());
+            } catch (IOException e) {
+                LOG.warn("showNotification failed: " + e.getMessage());
+            }
+        }
+    }
+
+    @Override
+    public boolean onCharacteristicReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattCharacteristic characteristic) {
+
+        if (!characteristic.getUuid().equals(CasioConstants.NAME_OF_APP_CHARACTERISTIC_UUID)) {
+            LOG.warn("unexpected read request");
+            return false;
+        }
+
+        LOG.info("will send response to read request from device: " + device.getAddress());
+
+        try {
+            ServerTransactionBuilder builder = performServer("sendNameOfApp");
+            builder.writeServerResponse(device, requestId, 0, offset, CasioConstants.MUSIC_MESSAGE.getBytes());
+            builder.queue(getQueue());
+        } catch (IOException e) {
+            LOG.warn("sendMusicInfo failed: " + e.getMessage());
+        }
+        return true;
+    }
+
+    private GBDeviceEventMusicControl.Event parse5Button(int button) {
+        GBDeviceEventMusicControl.Event event;
+        switch(button) {
+            case 5:
+                event = GBDeviceEventMusicControl.Event.PLAYPAUSE;
+                break;
+            case 4:
+                event = GBDeviceEventMusicControl.Event.NEXT;
+                break;
+            case 3:
+                event = GBDeviceEventMusicControl.Event.VOLUMEUP;
+                break;
+            case 2:
+                event = GBDeviceEventMusicControl.Event.PREVIOUS;
+                break;
+            case 1:
+                event = GBDeviceEventMusicControl.Event.VOLUMEDOWN;
+                break;
+            default:
+                LOG.warn("Unhandled button received: " + button);
+                event =  GBDeviceEventMusicControl.Event.UNKNOWN;
+        }
+        return event;
+    }
+
+    private GBDeviceEventMusicControl.Event parse3Button(int button) {
+        GBDeviceEventMusicControl.Event event;
+        switch(button) {
+            case 3:
+                event = GBDeviceEventMusicControl.Event.NEXT;
+                break;
+            case 2:
+                event = GBDeviceEventMusicControl.Event.PREVIOUS;
+                break;
+            case 1:
+                event = GBDeviceEventMusicControl.Event.PLAYPAUSE;
+                break;
+            default:
+                LOG.warn("Unhandled button received: " + button);
+                event =  GBDeviceEventMusicControl.Event.UNKNOWN;
+        }
+        return event;
+    }
+
+    private GBDeviceEventMusicControl.Event parse2Button(int button) {
+        GBDeviceEventMusicControl.Event event;
+        switch(button) {
+            case 2:
+                event = GBDeviceEventMusicControl.Event.PLAYPAUSE;
+                break;
+            case 1:
+                event = GBDeviceEventMusicControl.Event.NEXT;
+                break;
+            default:
+                LOG.warn("Unhandled button received: " + button);
+                event =  GBDeviceEventMusicControl.Event.UNKNOWN;
+        }
+        return event;
+    }
+
+    @Override
+    public boolean onCharacteristicWriteRequest(BluetoothDevice device, int requestId, BluetoothGattCharacteristic characteristic,
+                                                boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
+
+        GBDeviceEventMusicControl musicCmd = new GBDeviceEventMusicControl();
+        if (!characteristic.getUuid().equals(CasioConstants.KEY_CONTAINER_CHARACTERISTIC_UUID)) {
+            LOG.warn("unexpected write request");
+            return false;
+        }
+
+        if((value[0] & 0x03) == 0) {
+            int button = value[1] & 0x0f;
+            LOG.info("Button pressed: " + button);
+            switch(getModel())
+            {
+                case MODEL_CASIO_5600B:
+                    musicCmd.event = parse2Button(button);
+                    break;
+                case MODEL_CASIO_6900B:
+                case MODEL_CASIO_GENERIC:
+                    musicCmd.event = parse3Button(button);
+                    break;
+                case MODEL_CASIO_STB1000:
+                    musicCmd.event = parse5Button(button);
+                    break;
+                default:
+                    LOG.warn("Unhandled device");
+                    return false;
+            }
+            evaluateGBDeviceEvent(musicCmd);
+        }
+        else {
+            LOG.info("received from device: " + value.toString());
+        }
+        return true;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casio/gb6900/CasioGB6900HandlerThread.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casio/gb6900/CasioGB6900HandlerThread.java
new file mode 100644
index 0000000..dd1c4c6
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casio/gb6900/CasioGB6900HandlerThread.java
@@ -0,0 +1,109 @@
+/*  Copyright (C) 2023-2024 Johannes Krude
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.casio.gb6900;
+
+import android.content.Context;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceIoThread;
+
+public class CasioGB6900HandlerThread extends GBDeviceIoThread {
+    private static final Logger LOG = LoggerFactory.getLogger(CasioGB6900HandlerThread.class);
+    private static final int TX_PERIOD = 60;
+    private final Object waitObject = new Object();
+    private boolean mQuit;
+    private final CasioGB6900DeviceSupport mDeviceSupport;
+
+
+    private Calendar mTxTime = GregorianCalendar.getInstance();
+
+    public CasioGB6900HandlerThread(GBDevice gbDevice, Context context, CasioGB6900DeviceSupport deviceSupport) {
+        super(gbDevice, context);
+        LOG.info("Initializing Casio Handler Thread");
+        mQuit = false;
+        mDeviceSupport = deviceSupport;
+    }
+
+    @Override
+    public void run() {
+        mQuit = false;
+
+        long waitTime = TX_PERIOD * 1000;
+        while (!mQuit) {
+
+            if (waitTime > 0) {
+                synchronized (waitObject) {
+                    try {
+                        waitObject.wait(waitTime);
+                    } catch (InterruptedException e) {
+                        e.printStackTrace();
+                    }
+                }
+            }
+
+            if (mQuit) {
+                break;
+            }
+
+            GBDevice.State state = gbDevice.getState();
+            if (state == GBDevice.State.NOT_CONNECTED || state == GBDevice.State.WAITING_FOR_RECONNECT) {
+                LOG.debug("Closing handler thread, state not connected or waiting for reconnect.");
+                quit();
+                continue;
+            }
+
+            Calendar now = GregorianCalendar.getInstance();
+
+            if (now.compareTo(mTxTime) > 0) {
+                requestTxPowerLevel();
+            }
+
+            now = GregorianCalendar.getInstance();
+            waitTime = mTxTime.getTimeInMillis() - now.getTimeInMillis();
+        }
+
+    }
+
+    public void requestTxPowerLevel() {
+        try {
+            mDeviceSupport.readTxPowerLevel();
+        } catch (Exception ignored) {
+        }
+
+        mTxTime = GregorianCalendar.getInstance();
+        mTxTime.add(Calendar.SECOND, TX_PERIOD);
+        synchronized (waitObject) {
+            waitObject.notify();
+        }
+    }
+
+    @Override
+    public void quit() {
+        LOG.info("CasioGB6900HandlerThread: Quit Handler Thread");
+        mQuit = true;
+        synchronized (waitObject) {
+            waitObject.notify();
+        }
+    }
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casio/gb6900/InitOperation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casio/gb6900/InitOperation.java
new file mode 100644
index 0000000..1538d7c
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casio/gb6900/InitOperation.java
@@ -0,0 +1,168 @@
+/*  Copyright (C) 2023-2024 Johannes Krude
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.casio.gb6900;
+
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.casio.CasioConstants;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEOperation;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.casio.gb6900.CasioGB6900DeviceSupport;
+
+public class InitOperation extends AbstractBTLEOperation<CasioGB6900DeviceSupport> {
+    private static final Logger LOG = LoggerFactory.getLogger(InitOperation.class);
+
+    private final TransactionBuilder builder;
+    private byte[] mBleSettings = null;
+
+
+    public InitOperation(CasioGB6900DeviceSupport support, TransactionBuilder builder) {
+        super(support);
+        this.builder = builder;
+        builder.setCallback(this);
+    }
+
+    @Override
+    protected void doPerform() throws IOException {
+        builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext()));
+        TransactionBuilder builder = getSupport().createTransactionBuilder("readBleSettings");
+        builder.setCallback(this);
+        builder.read(getCharacteristic(CasioConstants.CASIO_SETTING_FOR_BLE_CHARACTERISTIC_UUID));
+        getSupport().performImmediately(builder);
+    }
+
+    @Override
+    public TransactionBuilder performInitialized(String taskName) throws IOException {
+        throw new UnsupportedOperationException("This IS the initialization class, you cannot call this method");
+    }
+
+    @Override
+    public boolean onCharacteristicChanged(BluetoothGatt gatt,
+                                           BluetoothGattCharacteristic characteristic) {
+        UUID characteristicUUID = characteristic.getUuid();
+        LOG.info("Unhandled characteristic changed: " + characteristicUUID);
+        return super.onCharacteristicChanged(gatt, characteristic);
+    }
+
+    private void configureBleSettings() {
+        // These values seem to improve connection stability _on my phone_
+        // Maybe they should be configurable?
+        int slaveLatency = 2;
+        int connInterval = 300;
+
+        mBleSettings[5] = (byte)(connInterval & 0xff);
+        mBleSettings[6] = (byte)((connInterval >> 8) & 0xff);
+        mBleSettings[7] = (byte)(slaveLatency & 0xff);
+        mBleSettings[8] = (byte)((slaveLatency >> 8) & 0xff);
+
+        mBleSettings[9] = 0; // Setting for Disconnect!?
+    }
+
+    private void writeBleSettings() {
+        try {
+            TransactionBuilder builder = getSupport().createTransactionBuilder("writeBleInit");
+            builder.setCallback(this);
+            builder.write(getCharacteristic(CasioConstants.CASIO_SETTING_FOR_BLE_CHARACTERISTIC_UUID), mBleSettings);
+            getSupport().performImmediately(builder);
+        } catch(IOException e) {
+            LOG.error("Error writing BLE settings: " + e.getMessage());
+        }
+    }
+
+    @Override
+    public boolean onCharacteristicRead(BluetoothGatt gatt,
+                                        BluetoothGattCharacteristic characteristic, int status) {
+
+        UUID characteristicUUID = characteristic.getUuid();
+        byte[] data = characteristic.getValue();
+
+        if(data.length == 0)
+            return true;
+
+        if(characteristicUUID.equals(CasioConstants.CASIO_SETTING_FOR_BLE_CHARACTERISTIC_UUID)) {
+            mBleSettings = data;
+            StringBuilder str = new StringBuilder("Read Casio Setting for BLE: ");
+            for (byte datum : data) {
+                str.append(String.format("0x%1x ", datum));
+            }
+            /* Definition of parameters - for future reference */
+
+            // data[0]; // BLE alert for call, mail and other
+            // data[1]; // BLE alert for Calendar
+            // data[2]; // BLE alert for SNS
+            // data[3]; // BLE alert for vibration and alarm
+            // data[4]; // BLE alert for animation
+            // data[5]; // Connection Interval
+            // data[6]; // Connection Interval
+            // data[7]; // Slave Latency
+            // data[8]; // Slave Latency
+
+            // Alert definitions:
+            // 0 = Off
+            // 1 = Sound
+            // 2 = Vibration
+            // 3 = Sound and Vibration
+            //int callAlert = (data[0] >> 6) & 0x03;
+            //LOG.info("Call Alert: " + callAlert);
+            //int mailAlert = (data[0] >> 2) & 0x03;
+            //LOG.info("Mail Alert: " + mailAlert);
+            //int snsAlert = (data[2] >> 4) & 0x03;
+            //LOG.info("SNS Alert: " + snsAlert);
+            //int calAlert = (data[1] >> 6) & 0x03;
+            //LOG.info("Calendart Alert: " + calAlert);
+            //int otherAlert = (data[0] & 0x03);
+            //LOG.info("Other Alert: " + otherAlert);
+            //int vibrationValue = (data[3] & 0x0f);
+            //LOG.info("Vibration Value: " + vibrationValue);
+            //int alarmValue = (data[3] >> 4) & 0x0f;
+            // Vibration pattern; A = 0, B = 1, C = 2
+            //LOG.info("Alarm Value: " + alarmValue);
+            //int animationValue = data[4] & 0x40;
+            // Length of Alarm, only 2, 5 and 10 possible
+            //LOG.info("Animation Value: " + animationValue);
+            // 0 = on
+            // 64 = off
+            //int useDisableMtuReqBit = data[4] & 0x08;
+            // 8 = on
+            // 0 = off!?
+            //LOG.info("useDisableMtuReqBit: " + useDisableMtuReqBit);
+
+            //int slaveLatency = ((data[7] & 0xff) | ((data[8] & 0xff) << 8));
+            //int connInterval = ((data[5] & 0xff) | ((data[6] & 0xff) << 8));
+            //LOG.info("Slave Latency: " + slaveLatency);
+            //LOG.info("Connection Interval: " + connInterval);
+            //LOG.info(str);
+
+            configureBleSettings();
+            writeBleSettings();
+        }
+        else {
+            return super.onCharacteristicRead(gatt, characteristic, status);
+        }
+
+        return true;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casio/gb6900/SetAlarmOperation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casio/gb6900/SetAlarmOperation.java
new file mode 100644
index 0000000..26d3d5f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casio/gb6900/SetAlarmOperation.java
@@ -0,0 +1,135 @@
+/*  Copyright (C) 2023-2024 Johannes Krude
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.casio.gb6900;
+
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.casio.CasioConstants;
+import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEOperation;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.casio.gb6900.CasioGB6900DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.OperationStatus;
+
+public class SetAlarmOperation extends AbstractBTLEOperation<CasioGB6900DeviceSupport> {
+    private static final Logger LOG = LoggerFactory.getLogger(SetAlarmOperation.class);
+    private final ArrayList<? extends Alarm> mAlarms;
+
+    public SetAlarmOperation(CasioGB6900DeviceSupport support, ArrayList<? extends Alarm> alarms) {
+        super(support);
+        this.mAlarms = alarms;
+    }
+
+    @Override
+    protected void prePerform() throws IOException {
+        super.prePerform();
+        getDevice().setBusyTask("SetAlarmOperation starting..."); // mark as busy quickly to avoid interruptions from the outside
+    }
+
+    private void getSettingForAlarm() {
+        if (getDevice() != null) {
+            try {
+                TransactionBuilder builder = performInitialized("getSettingForAlarm");
+                builder.setCallback(this);
+                builder.read(getCharacteristic(CasioConstants.CASIO_SETTING_FOR_ALM_CHARACTERISTIC_UUID));
+                builder.queue(getQueue());
+            } catch (IOException ex) {
+                LOG.info("Error retrieving alarm settings: " + ex.getMessage());
+            }
+        }
+    }
+
+    @Override
+    protected void doPerform() throws IOException {
+        getSettingForAlarm();
+    }
+
+    @Override
+    protected void operationFinished() {
+        operationStatus = OperationStatus.FINISHED;
+        unsetBusy();
+        if (getDevice() != null) {
+            try {
+                TransactionBuilder builder = performInitialized("finished operation");
+                builder.wait(0);
+                builder.setCallback(null); // unset ourselves from being the queue's gatt callback
+                builder.queue(getQueue());
+            } catch (IOException ex) {
+                LOG.info("Error resetting Gatt callback: " + ex.getMessage());
+            }
+        }
+    }
+
+    @Override
+    public boolean onCharacteristicRead(BluetoothGatt gatt,
+                                        BluetoothGattCharacteristic characteristic, int status) {
+
+        UUID characteristicUUID = characteristic.getUuid();
+        byte[] data = characteristic.getValue();
+
+        if(data.length == 0)
+            return true;
+
+        if(characteristicUUID.equals(CasioConstants.CASIO_SETTING_FOR_ALM_CHARACTERISTIC_UUID)) {
+            StringBuilder str = new StringBuilder("onCharacteristicRead: Received alarm settings: ");
+            for(int i=0; i<data.length; i++) {
+                str.append(String.format("0x%1x ", data[i]));
+            }
+            LOG.info(str.toString());
+
+            int alarmOffset = 4;
+
+            for(int i=0; i<mAlarms.size(); i++)
+            {
+                Alarm alm = mAlarms.get(i);
+                data[i * alarmOffset] &= ~(0x60);
+                if(alm.getEnabled()) {
+                    data[i * alarmOffset] |= 0x40;
+                }
+                if(alm.getRepetition() == Alarm.ALARM_ONCE) {
+                    data[i * alarmOffset] |= 0x20;
+                }
+                data[i * alarmOffset + 1] = 0;
+                data[i * alarmOffset + 2] = (byte)alm.getHour();
+                data[i * alarmOffset + 3] = (byte)alm.getMinute();
+            }
+
+            try {
+                TransactionBuilder builder = performInitialized("setAlarm");
+                builder.write(getCharacteristic(CasioConstants.CASIO_SETTING_FOR_ALM_CHARACTERISTIC_UUID), data);
+                builder.queue(getQueue());
+            } catch(IOException e) {
+                LOG.error("Error setting alarm: " + e.getMessage());
+            }
+
+            operationFinished();
+        }
+        else {
+            return super.onCharacteristicRead(gatt, characteristic, status);
+        }
+
+        return true;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casio/gbx100/CasioGBX100DeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casio/gbx100/CasioGBX100DeviceSupport.java
new file mode 100644
index 0000000..02d2f52
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casio/gbx100/CasioGBX100DeviceSupport.java
@@ -0,0 +1,727 @@
+/*  Copyright (C) 2023-2024 Andreas B枚hler, foxstidious, Johannes Krude
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.casio.gbx100;
+
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.content.SharedPreferences;
+import android.os.Handler;
+import android.widget.Toast;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.text.SimpleDateFormat;
+import java.time.ZonedDateTime;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+import java.util.UUID;
+import java.util.concurrent.atomic.AtomicInteger;
+
+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.casio.CasioConstants;
+import nodomain.freeyourgadget.gadgetbridge.devices.casio.gbx100.CasioGBX100SampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.CasioGBX100ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.entities.User;
+import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
+import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.casio.Casio2C2DSupport;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
+
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_AUTOLIGHT;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_AUTOREMOVE_MESSAGE;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_CASIO_ALERT_CALENDAR;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_CASIO_ALERT_CALL;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_CASIO_ALERT_EMAIL;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_CASIO_ALERT_OTHER;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_CASIO_ALERT_SMS;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_FAKE_RING_DURATION;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_FIND_PHONE;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_FIND_PHONE_DURATION;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_KEY_VIBRATION;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_OPERATING_SOUNDS;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_PREVIEW_MESSAGE_IN_TITLE;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_TIMEFORMAT;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivityUser.PREF_USER_ACTIVETIME_MINUTES;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivityUser.PREF_USER_DATE_OF_BIRTH;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivityUser.PREF_USER_DISTANCE_METERS;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivityUser.PREF_USER_GENDER;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivityUser.PREF_USER_HEIGHT_CM;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivityUser.PREF_USER_STEPS_GOAL;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivityUser.PREF_USER_WEIGHT_KG;
+
+public class CasioGBX100DeviceSupport extends Casio2C2DSupport implements SharedPreferences.OnSharedPreferenceChangeListener {
+    private static final Logger LOG = LoggerFactory.getLogger(CasioGBX100DeviceSupport.class);
+
+    private boolean mGetConfigurationPending = false;
+    private boolean mRingNotificationPending = false;
+    private final ArrayList<Integer> mSyncedNotificationIDs = new ArrayList<>();
+    private int mLastCallId = new AtomicInteger((int) (System.currentTimeMillis()/1000)).incrementAndGet();
+    private int mFakeRingDurationCounter = 0;
+    private final Handler mFindPhoneHandler = new Handler();
+    private final Handler mFakeRingDurationHandler = new Handler();
+    private final Handler mAutoRemoveMessageHandler = new Handler();
+    private final Handler mReconnectHandler = new Handler();
+    private boolean mNeedsGetConfiguration = false;
+
+    public CasioGBX100DeviceSupport() {
+        super(LOG);
+    }
+
+    @Override
+    public DevicePreference[] supportedDevicePreferences() {
+        return new DevicePreference[] {
+        };
+    }
+
+    @Override
+    public boolean useAutoConnect() {
+        return true;
+    }
+
+    @Override
+    protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
+
+        try {
+            new InitOperation(this, builder, mFirstConnect, mNeedsGetConfiguration).perform();
+        } catch (IOException e) {
+            GB.toast(getContext(), "Initializing Casio watch failed", Toast.LENGTH_SHORT, GB.ERROR, e);
+        }
+
+        getDevice().setFirmwareVersion("N/A");
+        getDevice().setFirmwareVersion2("N/A");
+
+
+        //preferences.registerOnSharedPreferenceChangeListener(this);
+
+        SharedPreferences prefs = GBApplication.getPrefs().getPreferences();
+        prefs.registerOnSharedPreferenceChangeListener(this);
+
+        if(mFirstConnect) {
+            SharedPreferences preferences = GBApplication.getDeviceSpecificSharedPrefs(this.getDevice().getAddress());
+            SharedPreferences.Editor editor = preferences.edit();
+
+            editor.putString(DeviceSettingsPreferenceConst.PREFS_DEVICE_CHARTS_TABS, "activity,activitylist,stepsweek");
+            editor.apply();
+        }
+
+        return builder;
+    }
+
+    @Override
+    public boolean onCharacteristicRead(BluetoothGatt gatt,
+                                        BluetoothGattCharacteristic characteristic, int status) {
+
+        UUID characteristicUUID = characteristic.getUuid();
+        byte[] data = characteristic.getValue();
+
+        if(data.length == 0)
+            return true;
+
+        return super.onCharacteristicRead(gatt, characteristic, status);
+    }
+
+    public CasioGBX100ActivitySample getSumWithinRange(int timestamp_from, int timestamp_to) {
+        int steps = 0;
+        int calories = 0;
+        try (DBHandler dbHandler = GBApplication.acquireDB()) {
+
+            User user = DBHelper.getUser(dbHandler.getDaoSession());
+            Device device = DBHelper.getDevice(this.getDevice(), dbHandler.getDaoSession());
+
+            CasioGBX100SampleProvider provider = new CasioGBX100SampleProvider(this.getDevice(), dbHandler.getDaoSession());
+            List<CasioGBX100ActivitySample> samples = provider.getActivitySamples(timestamp_from, timestamp_to);
+            for(CasioGBX100ActivitySample sample : samples) {
+                if(sample.getDevice().equals(device) &&
+                        sample.getUser().equals(user)) {
+                    steps += sample.getSteps();
+                    calories += sample.getCalories();
+                }
+            }
+        } catch (Exception e) {
+            LOG.error("Error fetching activity data.");
+        }
+
+        CasioGBX100ActivitySample ret = new CasioGBX100ActivitySample();
+        ret.setCalories(calories);
+        ret.setSteps(steps);
+        LOG.debug("Fetched for today: " + calories + " cals and " + steps + " steps.");
+        return ret;
+    }
+
+    private void addGBActivitySamples(ArrayList<CasioGBX100ActivitySample> samples) {
+        try (DBHandler dbHandler = GBApplication.acquireDB()) {
+
+            User user = DBHelper.getUser(dbHandler.getDaoSession());
+            Device device = DBHelper.getDevice(this.getDevice(), dbHandler.getDaoSession());
+
+            CasioGBX100SampleProvider provider = new CasioGBX100SampleProvider(this.getDevice(), dbHandler.getDaoSession());
+
+            for (CasioGBX100ActivitySample sample : samples) {
+                sample.setDevice(device);
+                sample.setUser(user);
+                sample.setProvider(provider);
+
+                provider.addGBActivitySample(sample);
+            }
+
+        } catch (Exception ex) {
+            // Why is this a toast? The user doesn't care about the error.
+            GB.toast(getContext(), "Error saving samples: " + ex.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
+            GB.updateTransferNotification(null, "Data transfer failed", false, 0, getContext());
+
+            LOG.error(ex.getMessage());
+        }
+    }
+
+    public void stepCountDataFetched(int totalCount, int totalCalories, ArrayList<CasioGBX100ActivitySample> data) {
+        LOG.info("Got the following step count data: ");
+        LOG.info("Total Count: {}", totalCount);
+        LOG.info("Total Calories: {}", totalCalories);
+
+        addGBActivitySamples(data);
+    }
+
+    @Override
+    public boolean onCharacteristicChanged(BluetoothGatt gatt,
+                                           BluetoothGattCharacteristic characteristic) {
+        UUID characteristicUUID = characteristic.getUuid();
+        byte[] data = characteristic.getValue();
+        if (data.length == 0)
+            return true;
+
+        if (characteristicUUID.equals(CasioConstants.CASIO_ALL_FEATURES_CHARACTERISTIC_UUID)) {
+            if(data[0] == FEATURE_ALERT_LEVEL) {
+                if(data[1] == 0x02) {
+                    onReverseFindDevice(true);
+                } else {
+                    onReverseFindDevice(false);
+                }
+                return true;
+            } else if(data[0] == FEATURE_CURRENT_TIME_MANAGER) {
+                if(data[1] == 0x00) {
+                    try {
+                        TransactionBuilder builder = performInitialized("writeCurrentTime");
+                        writeCurrentTime(builder, ZonedDateTime.now());
+                        builder.queue(getQueue());
+                    } catch (IOException e) {
+                        LOG.warn("writing current time failed: " + e.getMessage());
+                    }
+                }
+            }
+
+        }
+
+        LOG.info("Unhandled characteristic change: " + characteristicUUID + " code: " + String.format("0x%1x ...", data[0]));
+        return super.onCharacteristicChanged(gatt, characteristic);
+    }
+
+    public void syncProfile() {
+        try {
+            new SetConfigurationOperation(this, CasioConstants.ConfigurationOption.OPTION_ALL).perform();
+        } catch (IOException e) {
+            GB.toast(getContext(), "Sending Casio configuration failed", Toast.LENGTH_SHORT, GB.ERROR, e);
+        }
+    }
+
+    /**
+     * Sends a notification to the watch module. Overloaded - this method does not specify a subtitle
+     * since subtitle isn't really used yet.
+     * @param icon Icon to use - see {@link CasioConstants}
+     * @param sender Sender (null if none)
+     * @param title Notification title (null if none)
+     * @param message Message body (null if none)
+     * @param id Notification id
+     * @param delete true if sending a delete of notification to the device
+     */
+    private void showNotification(byte icon, String sender, String title, String message, int id, boolean delete) {
+        showNotification(icon, sender, title, null, message, id, delete);
+    }
+
+    /**
+     * Sends a notification to the watch module. Overloaded - this method does not specify a subtitle
+     * @param icon Icon to use - see {@link CasioConstants}
+     * @param sender Sender (null if none)
+     * @param title Notification title (null if none)
+     * @param subtitle Message subtitle (null if none)
+     * @param message Message body (null if none)
+     * @param id Notification id
+     */
+    private void showNotification(byte icon, String sender, String title, String subtitle, String message, int id, boolean delete) {
+        SharedPreferences sharedPreferences = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress());
+        boolean showMessagePreview = sharedPreferences.getBoolean(PREF_PREVIEW_MESSAGE_IN_TITLE, true);
+
+        boolean shouldAlert = true;
+        switch (icon) {
+            case CasioConstants.CATEGORY_EMAIL:
+                shouldAlert = sharedPreferences.getBoolean(PREF_CASIO_ALERT_EMAIL, true);
+                break;
+            case CasioConstants.CATEGORY_OTHER:
+                shouldAlert = sharedPreferences.getBoolean(PREF_CASIO_ALERT_OTHER, true);
+                break;
+            case CasioConstants.CATEGORY_SNS:
+                shouldAlert = sharedPreferences.getBoolean(PREF_CASIO_ALERT_SMS, true);
+                break;
+            case CasioConstants.CATEGORY_INCOMING_CALL:
+                shouldAlert = sharedPreferences.getBoolean(PREF_CASIO_ALERT_CALL, true);
+                break;
+            case CasioConstants.CATEGORY_SCHEDULE_AND_ALARM:
+                shouldAlert = sharedPreferences.getBoolean(PREF_CASIO_ALERT_CALENDAR, true);
+                break;
+        }
+
+        // If not a call or email, check the sender and if null, promote the title and message preview
+        // as subtitle
+        if (showMessagePreview && icon != CasioConstants.CATEGORY_INCOMING_CALL && icon != CasioConstants.CATEGORY_EMAIL) {
+            if (StringUtils.isNullOrEmpty(sender)) {
+                // Shift title to sender slot
+                sender = title;
+            }
+            //Shift content to title
+            if (!StringUtils.isNullOrEmpty(message)) {
+                title = message.substring(0, Math.min(message.length(), 18)) + "..";
+            }
+        }
+
+        // Make sure title and sender are less than 32 characters
+        byte[] titleBytes = new byte[0];
+        if (!StringUtils.isNullOrEmpty(title)) {
+            if (title.length() > 32) {
+                title = title.substring(0, 30) + "..";
+            }
+            titleBytes = title.getBytes(StandardCharsets.UTF_8);
+        }
+
+        byte[] senderBytes = new byte[0];
+        if (!StringUtils.isNullOrEmpty(sender)) {
+            if (sender.length() > 32) {
+                sender = sender.substring(0, 30) + "..";
+            }
+            senderBytes = sender.getBytes(StandardCharsets.UTF_8);
+        }
+
+        byte[] subtitleBytes = new byte[0];
+        if (!StringUtils.isNullOrEmpty(subtitle)) {
+            subtitleBytes = subtitle.getBytes(StandardCharsets.UTF_8);
+        }
+
+        // Ensure the message is not over 250 bytes, as per #4063
+        // FIXME: We probably need to take the MTU into account too...
+        byte[] messageBytes = new byte[0];
+        if (!StringUtils.isNullOrEmpty(message)) {
+            messageBytes = StringUtils.truncateToBytes(message, 250);
+        }
+
+        byte[] arr = new byte[22];
+        arr[0] = (byte)(id & 0xff);
+        arr[1] = (byte) ((id >> 8) & 0xff);
+        arr[2] = (byte) ((id >> 16) & 0xff);
+        arr[3] = (byte) ((id >> 24) & 0xff);
+        arr[4] = delete ? (byte) 0x02 : (byte) 0x00;
+        if (shouldAlert) {
+            arr[5] = (byte) 0x01; // Set to 0x0 to vibrate/ring for this notification
+        } else {
+            arr[5] = (byte) 0x00; // Set to 0x00 to not vibrate/ring for this notification
+        }
+
+        arr[6] = icon;
+
+        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd'T'HHmmss");
+        String timestamp = dateFormat.format(new Date());
+        System.arraycopy(timestamp.getBytes(), 0, arr, 7, 15);
+
+        byte[] copy = Arrays.copyOf(arr, arr.length + 2);
+        copy[copy.length-2] = 0;
+        copy[copy.length-1] = 0;
+        if(senderBytes.length > 0) {
+            copy = Arrays.copyOf(copy, copy.length + senderBytes.length);
+            copy[copy.length-2-senderBytes.length] = (byte)(senderBytes.length & 0xff);
+            copy[copy.length-1-senderBytes.length] = (byte)((senderBytes.length >> 8) & 0xff);
+            System.arraycopy(senderBytes, 0, copy, copy.length - senderBytes.length, senderBytes.length);
+        }
+        copy = Arrays.copyOf(copy, copy.length + 2);
+        copy[copy.length-2] = 0;
+        copy[copy.length-1] = 0;
+        if(titleBytes.length > 0) {
+            copy = Arrays.copyOf(copy, copy.length + titleBytes.length);
+            copy[copy.length-2-titleBytes.length] = (byte)(titleBytes.length & 0xff);
+            copy[copy.length-1-titleBytes.length] = (byte)((titleBytes.length >> 8) & 0xff);
+            System.arraycopy(titleBytes, 0, copy, copy.length - titleBytes.length, titleBytes.length);
+        }
+        copy = Arrays.copyOf(copy, copy.length + 2);
+
+        // Subtitle
+        copy[copy.length-2] = 0;
+        copy[copy.length-1] = 0;
+        if (subtitleBytes.length > 0) {
+            copy = Arrays.copyOf(copy, copy.length + subtitleBytes.length);
+            copy[copy.length-2-subtitleBytes.length] = (byte)(subtitleBytes.length & 0xff);
+            copy[copy.length-1-subtitleBytes.length] = (byte)((subtitleBytes.length >> 8) & 0xff);
+            System.arraycopy(subtitleBytes, 0, copy, copy.length - subtitleBytes.length, subtitleBytes.length);
+        }
+
+        copy = Arrays.copyOf(copy, copy.length + 2);
+        copy[copy.length-2] = 0;
+        copy[copy.length-1] = 0;
+        if(messageBytes.length > 0) {
+            copy = Arrays.copyOf(copy, copy.length + messageBytes.length);
+            copy[copy.length-2-messageBytes.length] = (byte)(messageBytes.length & 0xff);
+            copy[copy.length-1-messageBytes.length] = (byte)((messageBytes.length >> 8) & 0xff);
+            System.arraycopy(messageBytes, 0, copy, copy.length - messageBytes.length, messageBytes.length);
+        }
+        for(int i=0; i<copy.length; i++) {
+            copy[i] = (byte)(~copy[i]);
+        }
+
+        try {
+            TransactionBuilder builder = performInitialized("showNotification");
+            builder.write(getCharacteristic(CasioConstants.CASIO_NOTIFICATION_CHARACTERISTIC_UUID), copy);
+            LOG.info("Showing notification, title: {} message: {}", title, message);
+            builder.queue(getQueue());
+        } catch (IOException e) {
+            LOG.error("showNotification failed", e);
+        }
+    }
+
+    @Override
+    public void onNotification(final NotificationSpec notificationSpec) {
+        byte icon;
+        boolean autoremove = false;
+        switch (notificationSpec.type) {
+            case GENERIC_CALENDAR:
+                icon = CasioConstants.CATEGORY_SCHEDULE_AND_ALARM;
+                break;
+            case GENERIC_EMAIL:
+            case GMAIL:
+            case GOOGLE_INBOX:
+                icon = CasioConstants.CATEGORY_EMAIL;
+                break;
+            case GENERIC_SMS:
+                icon = CasioConstants.CATEGORY_SNS;
+                SharedPreferences sharedPreferences = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress());
+                autoremove = sharedPreferences.getBoolean(PREF_AUTOREMOVE_MESSAGE, false);
+                break;
+            case GENERIC_PHONE:
+                icon = CasioConstants.CATEGORY_INCOMING_CALL;
+                break;
+            default:
+                icon = CasioConstants.CATEGORY_OTHER;
+                break;
+        }
+        LOG.info("onNotification id={}", notificationSpec.getId());
+        showNotification(icon, notificationSpec.sender, notificationSpec.title, notificationSpec.body, notificationSpec.getId(), false);
+        mSyncedNotificationIDs.add(notificationSpec.getId());
+        if(autoremove) {
+            mAutoRemoveMessageHandler.postDelayed(new Runnable() {
+                @Override
+                public void run() {
+                    onDeleteNotification(notificationSpec.getId());
+                }
+            }, CasioConstants.CASIO_AUTOREMOVE_MESSAGE_DELAY);
+        }
+        // The watch only holds up to 10 notifications. However, the user might have deleted
+        // some notifications in the meantime, so to be sure, we keep the last 100 IDs.
+        if(mSyncedNotificationIDs.size() > 100) {
+            mSyncedNotificationIDs.remove(0);
+        }
+    }
+
+    @Override
+    public void onDeleteNotification(int id) {
+        LOG.info("onDeleteNofication id=" + id);
+        Integer idInt = id;
+        if(mSyncedNotificationIDs.contains(idInt)) {
+            showNotification(CasioConstants.CATEGORY_OTHER, null, null, null, id, true);
+            mSyncedNotificationIDs.remove(idInt);
+        }
+    }
+
+    private void onReverseFindDevice(boolean start) {
+        if (start) {
+            SharedPreferences sharedPreferences = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress());
+
+            String findPhone = sharedPreferences.getString(PREF_FIND_PHONE, getContext().getString(R.string.p_off));
+
+            if(findPhone.equals(getContext().getString(R.string.p_off)))
+                return;
+
+            GBDeviceEventFindPhone findPhoneEvent = new GBDeviceEventFindPhone();
+            findPhoneEvent.event = GBDeviceEventFindPhone.Event.START;
+            evaluateGBDeviceEvent(findPhoneEvent);
+
+            if(findPhone.equals(getContext().getString(R.string.p_on))) {
+                String duration = sharedPreferences.getString(PREF_FIND_PHONE_DURATION, "0");
+
+                try {
+                    int iDuration;
+
+                    try {
+                        iDuration = Integer.parseInt(duration);
+                    } catch (Exception ex) {
+                        LOG.warn(ex.getMessage());
+                        iDuration = 60;
+                    }
+                    if(iDuration > 0) {
+                        this.mFindPhoneHandler.postDelayed(new Runnable() {
+                            @Override
+                            public void run() {
+                                GBDeviceEventFindPhone findPhoneEvent = new GBDeviceEventFindPhone();
+                                findPhoneEvent.event = GBDeviceEventFindPhone.Event.STOP;
+                                evaluateGBDeviceEvent(findPhoneEvent);
+                            }
+                        }, iDuration * 1000);
+                    }
+                } catch (Exception e) {
+                    LOG.error("Unexpected exception in MiBand2Coordinator.getTime: " + e.getMessage());
+                }
+            }
+        } else {
+            // Always send stop, ignore preferences.
+            GBDeviceEventFindPhone findPhoneEvent = new GBDeviceEventFindPhone();
+            findPhoneEvent.event = GBDeviceEventFindPhone.Event.STOP;
+            evaluateGBDeviceEvent(findPhoneEvent);
+        }
+    }
+
+    @Override
+    public void onSetAlarms(ArrayList<? extends Alarm> alarms) {
+        byte[] data1 = new byte[5];
+        byte[] data2 = new byte[17];
+
+        if(!isConnected())
+            return;
+
+        data1[0] = FEATURE_SETTING_FOR_ALM;
+        data2[0] = FEATURE_SETTING_FOR_ALM2;
+
+        for(int i=0; i<alarms.size(); i++)
+        {
+            byte[] settings = new byte[4];
+            Alarm alm = alarms.get(i);
+            if(alm.getEnabled()) {
+                settings[0] = 0x40;
+                if (alm.getSnooze()) {
+                    settings[0] = 0x50;
+                }
+            } else {
+                settings[0] = 0;
+            }
+            settings[1] = 0x40;
+            settings[2] = (byte)alm.getHour();
+            settings[3] = (byte)alm.getMinute();
+            if(i == 0) {
+                System.arraycopy(settings, 0, data1, 1, settings.length);
+            } else {
+                System.arraycopy(settings, 0, data2, 1 + (i-1)*4, settings.length);
+            }
+        }
+        try {
+            TransactionBuilder builder = performInitialized("setAlarm");
+            writeAllFeatures(builder, data1);
+            writeAllFeatures(builder, data2);
+            builder.queue(getQueue());
+        } catch(IOException e) {
+            LOG.error("Error setting alarm: " + e.getMessage());
+        }
+    }
+
+    @Override
+    public void onSetTime() {
+        LOG.debug("onSetTime called");
+        try {
+            TransactionBuilder builder = performInitialized("onSetTime");
+            writeCurrentTime(builder, ZonedDateTime.now());
+            builder.queue(getQueue());
+        } catch (IOException e) {
+            LOG.warn("onSetTime failed: " + e.getMessage());
+        }
+    }
+
+    @Override
+    public void onSetCallState(final CallSpec callSpec) {
+        switch (callSpec.command) {
+            case CallSpec.CALL_INCOMING:
+                showNotification(CasioConstants.CATEGORY_INCOMING_CALL, callSpec.name, callSpec.number, "Phone Call", mLastCallId, false);
+                SharedPreferences sharedPreferences = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress());
+                boolean fakeRingDuration = sharedPreferences.getBoolean(PREF_FAKE_RING_DURATION, false);
+                if(fakeRingDuration && mFakeRingDurationCounter < CasioConstants.CASIO_FAKE_RING_RETRIES) {
+                    mFakeRingDurationCounter++;
+                    mFakeRingDurationHandler.postDelayed(new Runnable() {
+                        @Override
+                        public void run() {
+                            showNotification(CasioConstants.CATEGORY_INCOMING_CALL, null, null, null, mLastCallId, true);
+                            onSetCallState(callSpec);
+                        }
+                    }, CasioConstants.CASIO_FAKE_RING_SLEEP_DURATION);
+                } else {
+                    mFakeRingDurationCounter = 0;
+                }
+                mRingNotificationPending = true;
+                break;
+            default:
+                if(mRingNotificationPending) {
+                    mFakeRingDurationHandler.removeCallbacksAndMessages(null);
+                    mFakeRingDurationCounter = 0;
+                    showNotification(CasioConstants.CATEGORY_INCOMING_CALL, null, null, null, mLastCallId, true);
+                    mLastCallId = new AtomicInteger((int) (System.currentTimeMillis() / 1000)).incrementAndGet();
+                }
+        }
+    }
+
+    @Override
+    public void onFetchRecordedData(int dataTypes) {
+        try {
+            new FetchStepCountDataOperation(this).perform();
+        } catch(IOException e) {
+            GB.toast(getContext(), "Error fetching data", Toast.LENGTH_SHORT, GB.ERROR, e);
+        }
+    }
+
+    @Override
+    public void onSendConfiguration(String config) {
+        LOG.info("onSendConfiguration" + config);
+        onSharedPreferenceChanged(null, config);
+    }
+
+    public void onGetConfigurationFinished() {
+        mGetConfigurationPending = false;
+    }
+
+    @Override
+    public void onReadConfiguration(String config) {
+        LOG.info("onReadConfiguration" + config);
+        // This is called upon pairing to retrieve the current watch settings, if any
+        if(config == null) {
+            try {
+                mGetConfigurationPending = true;
+                mNeedsGetConfiguration = false;
+                new GetConfigurationOperation(this, true).perform();
+            } catch (IOException e) {
+                mGetConfigurationPending = false;
+                GB.toast(getContext(), "Reading Casio configuration failed", Toast.LENGTH_SHORT, GB.ERROR, e);
+            }
+        }
+    }
+
+    @Override
+    public void onTestNewFunction() {
+        byte[] data = new byte[2];
+        data[0] = (byte)0x2e;
+        data[1] = (byte)0x03;
+        try {
+            TransactionBuilder builder = performInitialized("onTestNewFunction");
+            writeAllFeaturesRequest(builder, data);
+            builder.queue(getQueue());
+        } catch(IOException e) {
+            LOG.error("Error setting alarm: " + e.getMessage());
+        }
+    }
+
+    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
+        LOG.debug(key + " changed");
+
+        if (!this.isConnected()) {
+            LOG.debug("ignoring change, we're disconnected");
+            return;
+        }
+
+        if(mGetConfigurationPending) {
+            LOG.debug("Preferences are being fetched right now");
+            return;
+        }
+        try {
+            switch (key) {
+                case DeviceSettingsPreferenceConst.PREF_WEARLOCATION:
+                    new SetConfigurationOperation(this, CasioConstants.ConfigurationOption.OPTION_WRIST).perform();
+                    break;
+                case PREF_USER_STEPS_GOAL:
+                    new SetConfigurationOperation(this, CasioConstants.ConfigurationOption.OPTION_STEP_GOAL).perform();
+                    break;
+                case PREF_USER_ACTIVETIME_MINUTES:
+                    new SetConfigurationOperation(this, CasioConstants.ConfigurationOption.OPTION_ACTIVITY_GOAL).perform();
+                    break;
+                case PREF_USER_DISTANCE_METERS:
+                    new SetConfigurationOperation(this, CasioConstants.ConfigurationOption.OPTION_DISTANCE_GOAL).perform();
+                    break;
+                case PREF_USER_GENDER:
+                    new SetConfigurationOperation(this, CasioConstants.ConfigurationOption.OPTION_GENDER).perform();
+                    break;
+                case PREF_USER_HEIGHT_CM:
+                    new SetConfigurationOperation(this, CasioConstants.ConfigurationOption.OPTION_HEIGHT).perform();
+                    break;
+                case PREF_USER_WEIGHT_KG:
+                    new SetConfigurationOperation(this, CasioConstants.ConfigurationOption.OPTION_WEIGHT).perform();
+                    break;
+                case PREF_USER_DATE_OF_BIRTH:
+                    new SetConfigurationOperation(this, CasioConstants.ConfigurationOption.OPTION_BIRTHDAY).perform();
+                    break;
+                case PREF_TIMEFORMAT:
+                    new SetConfigurationOperation(this, CasioConstants.ConfigurationOption.OPTION_TIMEFORMAT).perform();
+                    break;
+                case PREF_KEY_VIBRATION:
+                    new SetConfigurationOperation(this, CasioConstants.ConfigurationOption.OPTION_KEY_VIBRATION).perform();
+                    break;
+                case PREF_AUTOLIGHT:
+                    new SetConfigurationOperation(this, CasioConstants.ConfigurationOption.OPTION_AUTOLIGHT).perform();
+                    break;
+                case PREF_OPERATING_SOUNDS:
+                    new SetConfigurationOperation(this, CasioConstants.ConfigurationOption.OPTION_OPERATING_SOUNDS).perform();
+                    break;
+                case PREF_FAKE_RING_DURATION:
+                case PREF_FIND_PHONE:
+                case PREF_FIND_PHONE_DURATION:
+                    // No action, we check the shared preferences when the device tries to ring the phone.
+                    break;
+                case PREF_PREVIEW_MESSAGE_IN_TITLE:
+                case PREF_CASIO_ALERT_CALENDAR:
+                case PREF_CASIO_ALERT_CALL:
+                case PREF_CASIO_ALERT_EMAIL:
+                case PREF_CASIO_ALERT_OTHER:
+                case PREF_CASIO_ALERT_SMS:
+                    // No action, we check it when message is received
+                    break;
+                default:
+            }
+        } catch (IOException e) {
+            LOG.info("Error sending configuration change to watch");
+        }
+    }
+
+    public void reconnectDelayed() {
+        setAutoReconnect(true);
+        mNeedsGetConfiguration = true;
+        mReconnectHandler.postDelayed(new Runnable() {
+            @Override
+            public void run() {
+                connect();
+            }
+        }, CasioConstants.CASIO_FAKE_RING_SLEEP_DURATION);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casio/gbx100/FetchStepCountDataOperation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casio/gbx100/FetchStepCountDataOperation.java
new file mode 100644
index 0000000..5959277
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casio/gbx100/FetchStepCountDataOperation.java
@@ -0,0 +1,310 @@
+/*  Copyright (C) 2023-2024 Johannes Krude
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.casio.gbx100;
+
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.TimeZone;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.casio.CasioConstants;
+import nodomain.freeyourgadget.gadgetbridge.entities.CasioGBX100ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEOperation;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.OperationStatus;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class FetchStepCountDataOperation  extends AbstractBTLEOperation<CasioGBX100DeviceSupport>  {
+    private static final Logger LOG = LoggerFactory.getLogger(FetchStepCountDataOperation.class);
+
+    private final CasioGBX100DeviceSupport support;
+
+    public FetchStepCountDataOperation(CasioGBX100DeviceSupport support) {
+        super(support);
+        this.support = support;
+    }
+
+    private void enableRequiredNotifications(boolean enable) {
+        try {
+            TransactionBuilder builder = performInitialized("enableRequiredNotifications");
+            builder.setCallback(this);
+            builder.notify(getCharacteristic(CasioConstants.CASIO_DATA_REQUEST_SP_CHARACTERISTIC_UUID), enable);
+            builder.notify(getCharacteristic(CasioConstants.CASIO_CONVOY_CHARACTERISTIC_UUID), enable);
+            builder.queue(getQueue());
+        } catch(IOException e) {
+            LOG.error("Error enabling required notifications", e);
+        }
+    }
+
+    private void requestStepCountData() {
+        byte[] command = {0x00, 0x11, 0x00, 0x00, 0x00};
+
+        try {
+            TransactionBuilder builder = performInitialized("requestStepCountDate");
+            builder.setCallback(this);
+            builder.write(getCharacteristic(CasioConstants.CASIO_DATA_REQUEST_SP_CHARACTERISTIC_UUID), command);
+            builder.queue(getQueue());
+        } catch(IOException e) {
+            LOG.error("Error requesting step count data", e);
+        }
+    }
+
+    private void writeStepCountAck() {
+        byte[] command = {0x04, 0x11, 0x00, 0x00, 0x00};
+
+        try {
+            TransactionBuilder builder = performInitialized("writeStepCountAck");
+            builder.setCallback(this);
+            builder.write(getCharacteristic(CasioConstants.CASIO_DATA_REQUEST_SP_CHARACTERISTIC_UUID), command);
+            builder.queue(getQueue());
+        } catch(IOException e) {
+            LOG.error("Error writing step count ack", e);
+        }
+    }
+
+    @Override
+    protected void prePerform() throws IOException {
+        super.prePerform();
+        getDevice().setBusyTask("FetchStepCountDataOperation starting..."); // mark as busy quickly to avoid interruptions from the outside
+        GB.updateTransferNotification(null, getContext().getString(R.string.busy_task_fetch_activity_data), true, 0, getContext());
+    }
+
+    @Override
+    protected void doPerform() throws IOException {
+        enableRequiredNotifications(true);
+        requestStepCountData();
+    }
+
+    @Override
+    protected void operationFinished() {
+        LOG.info("FetchStepCountDataOperation finished");
+        unsetBusy();
+        GB.updateTransferNotification(null, getContext().getString(R.string.busy_task_fetch_activity_data), false, 100, getContext());
+        GB.signalActivityDataFinish(getDevice());
+
+        operationStatus = OperationStatus.FINISHED;
+        if (getDevice() != null) {
+            try {
+                TransactionBuilder builder = performInitialized("finished operation");
+                builder.setCallback(null); // unset ourselves from being the queue's gatt callback
+                builder.wait(0);
+                builder.queue(getQueue());
+            } catch (IOException ex) {
+                LOG.error("Error resetting Gatt callback", ex);
+            }
+        }
+    }
+
+    @Override
+    public boolean onCharacteristicChanged(BluetoothGatt gatt,
+                                           BluetoothGattCharacteristic characteristic) {
+        UUID characteristicUUID = characteristic.getUuid();
+        byte[] data = characteristic.getValue();
+
+        if (data.length == 0)
+            return true;
+
+        if (characteristicUUID.equals(CasioConstants.CASIO_DATA_REQUEST_SP_CHARACTERISTIC_UUID)) {
+            int length = 0;
+            if (data.length > 3) {
+                length = (data[2] & 0xff) | ((data[3] & 0xff) << 8);
+            }
+            LOG.debug("Response is going to be {} bytes long", length);
+            GB.updateTransferNotification(null, getContext().getString(R.string.busy_task_fetch_activity_data), true, 10, getContext());
+
+            return true;
+        } else if(characteristicUUID.equals(CasioConstants.CASIO_CONVOY_CHARACTERISTIC_UUID)) {
+            if(data.length < 18) {
+                LOG.info("Data length too short.");
+            } else {
+                for(int i=0; i<data.length; i++)
+                    data[i] = (byte)(~data[i]);
+
+                int payloadLength = ((data[0] & 0xff) | ((data[1] & 0xff) << 8));
+
+                if(data.length == (payloadLength + 2)) {
+                    LOG.debug("Payload length and data length match.");
+                } else {
+                    LOG.warn("Payload length and data length do not match: {} vs. {}", payloadLength, data.length);
+                }
+
+                Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
+                ArrayList<CasioGBX100ActivitySample> stepCountData = new ArrayList<>();
+
+                int year = data[2];
+                int month = data[3] - 1; // Zero-based
+                int day = data[4];
+                int hour = data[5];
+                int minute = data[6];
+
+                int stepCount = ((data[7] & 0xff) | ((data[8] & 0xff) << 8) | ((data[9] & 0xff) << 16) | ((data[10] & 0xff) << 24));
+                 // it reports 0xfffffffe if no steps have been recorded
+                if(stepCount == 0xfffffffe)
+                    stepCount = 0;
+
+                int calories = ((data[11] & 0xff) | ((data[12] & 0xff) << 8));
+                if(calories == 0xfffe)
+                    calories = 0;
+                int yearOfBirth = ((data[13] & 0xff) | ((data[14] & 0xff) << 8));
+                int monthOfBirth = data[15];
+                int dayOfBirth = data[16];
+                LOG.debug("Current step count value: {}", stepCount);
+                LOG.debug("Current calories: {}", calories);
+                // data[17]:
+                // 0x01 probably means finished.
+                // 0x00 probably means more data.
+
+                // Set timestamps for retrieving recorded data for the current day
+
+                cal.set(year + 2000, month, day, hour, 30, 0);
+                int ts_to = (int)(cal.getTimeInMillis() / 1000);
+                cal.set(year + 2000, month, day, 0, 0, 0);
+                int ts_from = (int)(cal.getTimeInMillis() / 1000);
+
+                CasioGBX100ActivitySample sum = support.getSumWithinRange(ts_from, ts_to);
+
+                int caloriesToday = sum.getCalories();
+                int stepsToday = sum.getSteps();
+
+                // Set timestamp to currently fetched data for fetching historic data
+                cal.set(year + 2000, month, day, hour, 30, 0);
+
+                if(data[17] == 0x00 && data.length > 18) {
+                    LOG.info("We got historic step count data.");
+                    int index = 18;
+                    boolean inPacket = false;
+                    int packetIndex = 0;
+                    int packetLength = 0;
+                    int type = 0;
+                    while(index < data.length) {
+                        if(!inPacket) {
+                            type = data[index];
+                            packetLength = ((data[index + 1] & 0xff) | ((data[index + 2] & 0xff) << 8));
+                            packetIndex = 0;
+                            inPacket = true;
+                            index = index + 3;
+                            LOG.debug("Decoding packet with type: {} and length: {}", type, packetLength);
+                        }
+                        int count = ((data[index] & 0xff) | ((data[index + 1] & 0xff) << 8));
+                        if(count == 0xfffe)
+                            count = 0;
+                        LOG.debug("Got count {}", count);
+
+                        index = index+2;
+                        if(index >= data.length) {
+                            LOG.debug("End of packet.");
+                        }
+                        if(type == CasioConstants.CASIO_CONVOY_DATATYPE_STEPS) {
+                            cal.add(Calendar.HOUR, -1);
+                            int ts = (int)(cal.getTimeInMillis() / 1000);
+                            stepCountData.add(new CasioGBX100ActivitySample());
+                            stepCountData.get(packetIndex/2).setSteps(count);
+                            stepCountData.get(packetIndex/2).setTimestamp(ts);
+                            if(count > 0) {
+                                stepCountData.get(packetIndex / 2).setRawKind(ActivityKind.ACTIVITY.getCode());
+                            } else {
+                                stepCountData.get(packetIndex / 2).setRawKind(ActivityKind.NOT_MEASURED.getCode());
+                            }
+                            if(ts > ts_from && ts < ts_to) {
+                                stepsToday += count;
+                            }
+                        } else if(type == CasioConstants.CASIO_CONVOY_DATATYPE_CALORIES) {
+                            if(stepCountData.get(packetIndex/2).getSteps() > 0) {
+                                // The last packet might contain an invalid calory count
+                                // of 255, but only if the steps are also invalid.
+                                stepCountData.get(packetIndex / 2).setCalories(count);
+                                int ts = stepCountData.get(packetIndex / 2).getTimestamp();
+                                if (ts > ts_from && ts < ts_to) {
+                                    caloriesToday += count;
+                                }
+                            }
+                        }
+                        packetIndex = packetIndex + 2;
+                        if(packetIndex >= packetLength)
+                            inPacket = false;
+                    }
+                }
+
+                // This generates an artificial "now" timestamp for the current
+                // activity based on the existing data. This timestamp will be overwritten
+                // by the next fetch operation with the actual value.
+                int steps = stepCount - stepsToday;
+                int cals = calories - caloriesToday;
+
+                // For a yet unknown reason, the sum calculated by the watch is sometimes lower than
+                // the sum calculated by us. I suspect it is just refreshed at a later time!?
+
+                if(steps > 0 && cals > 0) {
+
+                    cal.set(year + 2000, month, day, hour, 30, 0);
+                    int ts = (int) (cal.getTimeInMillis() / 1000);
+
+                    LOG.debug("Artificial timestamp: {} calories and {} steps", cals, steps);
+                    CasioGBX100ActivitySample sample = new CasioGBX100ActivitySample();
+                    sample.setSteps(steps);
+                    sample.setCalories(cals);
+                    sample.setTimestamp(ts);
+                    if (steps > 0)
+                        sample.setRawKind(ActivityKind.ACTIVITY.getCode());
+                    else
+                        sample.setRawKind(ActivityKind.NOT_MEASURED.getCode());
+                    stepCountData.add(0, sample);
+                }
+
+                support.stepCountDataFetched(stepCount, calories, stepCountData);
+            }
+            GB.updateTransferNotification(null, getContext().getString(R.string.busy_task_fetch_activity_data), true, 80, getContext());
+
+            writeStepCountAck();
+            return true;
+        } else {
+            LOG.warn("Unhandled characteristic changed: {}", characteristicUUID);
+            return super.onCharacteristicChanged(gatt, characteristic);
+        }
+    }
+
+    @Override
+    public boolean onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
+
+        UUID characteristicUUID = characteristic.getUuid();
+        byte[] data = characteristic.getValue();
+
+        if (data.length == 0)
+            return true;
+
+        if (characteristicUUID.equals(CasioConstants.CASIO_DATA_REQUEST_SP_CHARACTERISTIC_UUID)) {
+            if(data[0] == 0x00) {
+                LOG.debug("Request sent successfully");
+            } else if(data[0] == 0x04) {
+                LOG.debug("Read step count operation finished");
+                enableRequiredNotifications(false);
+                operationFinished();
+            }
+            return true;
+        }
+        return super.onCharacteristicWrite(gatt, characteristic, status);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casio/gbx100/GetConfigurationOperation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casio/gbx100/GetConfigurationOperation.java
new file mode 100644
index 0000000..c918f26
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casio/gbx100/GetConfigurationOperation.java
@@ -0,0 +1,174 @@
+/*  Copyright (C) 2023-2024 Johannes Krude
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.casio.gbx100;
+
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.content.SharedPreferences;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.devices.casio.CasioConstants;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEOperation;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.casio.Casio2C2DSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.casio.gbx100.CasioGBX100DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.OperationStatus;
+import nodomain.freeyourgadget.gadgetbridge.util.BcdUtil;
+
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_AUTOLIGHT;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_KEY_VIBRATION;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_OPERATING_SOUNDS;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_WEARLOCATION;
+
+public class GetConfigurationOperation extends AbstractBTLEOperation<CasioGBX100DeviceSupport> {
+    private static final Logger LOG = LoggerFactory.getLogger(GetConfigurationOperation.class);
+    private final CasioGBX100DeviceSupport support;
+    private final boolean mFirstConnect;
+
+    public GetConfigurationOperation(CasioGBX100DeviceSupport support, boolean firstconnect) {
+        super(support);
+        this.support = support;
+        this.mFirstConnect = firstconnect;
+    }
+
+    @Override
+    protected void prePerform() throws IOException {
+        super.prePerform();
+        getDevice().setBusyTask("GetConfigurationOperation starting..."); // mark as busy quickly to avoid interruptions from the outside
+    }
+
+    @Override
+    protected void doPerform() throws IOException {
+        byte[] command = new byte[1];
+        command[0] = Casio2C2DSupport.FEATURE_SETTING_FOR_USER_PROFILE;
+        TransactionBuilder builder = performInitialized("getConfiguration");
+        builder.setCallback(this);
+        support.writeAllFeaturesRequest(builder, command);
+        builder.queue(getQueue());
+    }
+
+    @Override
+    protected void operationFinished() {
+        operationStatus = OperationStatus.FINISHED;
+        unsetBusy();
+        if (getDevice() != null) {
+            try {
+                TransactionBuilder builder = performInitialized("finished operation");
+                builder.wait(0);
+                builder.setCallback(null); // unset ourselves from being the queue's gatt callback
+                builder.queue(getQueue());
+            } catch (IOException ex) {
+                LOG.info("Error resetting Gatt callback: " + ex.getMessage());
+            }
+        }
+        support.onGetConfigurationFinished();
+    }
+
+    private void requestBasicSettings() {
+        byte[] command = new byte[1];
+        command[0] = Casio2C2DSupport.FEATURE_SETTING_FOR_BASIC;
+        try {
+            TransactionBuilder builder = performInitialized("getConfiguration");
+            builder.setCallback(this);
+            support.writeAllFeaturesRequest(builder, command);
+            builder.queue(getQueue());
+        } catch(IOException e) {
+            LOG.info("Error requesting Casio configuration");
+        }
+    }
+
+    @Override
+    public boolean onCharacteristicChanged(BluetoothGatt gatt,
+                                           BluetoothGattCharacteristic characteristic) {
+        UUID characteristicUUID = characteristic.getUuid();
+        byte[] data = characteristic.getValue();
+
+        if (data.length == 0)
+            return true;
+
+        if (characteristicUUID.equals(CasioConstants.CASIO_ALL_FEATURES_CHARACTERISTIC_UUID)) {
+            if (data[0] == Casio2C2DSupport.FEATURE_SETTING_FOR_USER_PROFILE) {
+                boolean female = ((data[1] & 0x01) == 0x01);
+                boolean right = ((data[1] & 0x02) == 0x02);
+                byte[] compData = new byte[data.length];
+                for (int i = 0; i < data.length; i++) {
+                    compData[i] = (byte) (~data[i]);
+                }
+                int height = BcdUtil.fromBcd8(compData[2]) + BcdUtil.fromBcd8(compData[3]) * 100;
+                int weight = BcdUtil.fromBcd8(compData[4]) + BcdUtil.fromBcd8(compData[5]) * 100;
+                int year = BcdUtil.fromBcd8(compData[6]) + BcdUtil.fromBcd8(compData[7]) * 100;
+                int month = BcdUtil.fromBcd8(compData[8]);
+                int day = BcdUtil.fromBcd8(compData[9]);
+
+                // Store only the device-specific settings on first-connect
+                SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress());
+                SharedPreferences.Editor editor = prefs.edit();
+
+                editor.putString(PREF_WEARLOCATION, right ? "right" : "left");
+                editor.apply();
+
+                requestBasicSettings();
+
+                return true;
+            } else if (data[0] == Casio2C2DSupport.FEATURE_SETTING_FOR_BASIC) {
+                boolean timeformat = ((data[1] & 0x01) == 0x01);
+                boolean autolight = ((data[1] & 0x04) == 0x00);
+                boolean key_vibration = (data[10] == 0x01);
+                boolean operating_sounds = ((data[1] & 0x02) == 0x00);
+
+                // Store only the device-specific settings on first-connect
+                SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress());
+                SharedPreferences.Editor editor = prefs.edit();
+
+                editor.putBoolean(PREF_AUTOLIGHT, autolight);
+                editor.putBoolean(PREF_KEY_VIBRATION, key_vibration);
+                editor.putBoolean(PREF_OPERATING_SOUNDS, operating_sounds);
+                editor.apply();
+
+
+
+                LOG.info("GetConfigurationOperation finished");
+                operationFinished();
+
+                // Retrieve all settings from the watch, this overwrites the profile
+                // on first connect, overwrite the watch settings
+                if (!mFirstConnect) {
+
+                } else {
+                    support.syncProfile();
+                }
+                return true;
+            }
+        }
+
+        LOG.info("Unhandled characteristic changed: " + characteristicUUID);
+        return super.onCharacteristicChanged(gatt, characteristic);
+    }
+
+    @Override
+    public boolean onCharacteristicRead(BluetoothGatt gatt,
+                                        BluetoothGattCharacteristic characteristic, int status) {
+
+        return super.onCharacteristicRead(gatt, characteristic, status);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casio/gbx100/InitOperation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casio/gbx100/InitOperation.java
new file mode 100644
index 0000000..468195f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casio/gbx100/InitOperation.java
@@ -0,0 +1,385 @@
+/*  Copyright (C) 2023-2024 Andreas B枚hler, Johannes Krude
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.casio.gbx100;
+
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.time.ZonedDateTime;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.casio.CasioConstants;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEOperation;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.casio.Casio2C2DSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.casio.gbx100.CasioGBX100DeviceSupport;
+
+public class InitOperation extends AbstractBTLEOperation<CasioGBX100DeviceSupport> {
+    private static final Logger LOG = LoggerFactory.getLogger(InitOperation.class);
+
+    private final TransactionBuilder builder;
+    private final CasioGBX100DeviceSupport support;
+    private final boolean mFirstConnect;
+    private final boolean mNeedsGetConfiguration;
+    private boolean mWriteAllFeaturesInitPending = false;
+
+    public InitOperation(CasioGBX100DeviceSupport support, TransactionBuilder builder, boolean firstConnect, boolean needsGetConfiguration) {
+        super(support);
+        this.builder = builder;
+        this.support = support;
+        this.mFirstConnect = firstConnect;
+        this.mNeedsGetConfiguration = needsGetConfiguration;
+        LOG.info("mFirstConnect: " + mFirstConnect);
+        builder.setCallback(this);
+    }
+
+    private void writeAllFeaturesRequest(byte[] arr) {
+        try {
+            TransactionBuilder builder = createTransactionBuilder("writeAllFeaturesRequest");
+            builder.setCallback(this);
+            support.writeAllFeaturesRequest(builder, arr);
+            support.performImmediately(builder);
+        } catch(IOException e) {
+            LOG.error("Error writing all features: " + e.getMessage());
+        }
+    }
+
+    private void writeAllFeatures(byte[] arr) {
+        try {
+            TransactionBuilder builder = createTransactionBuilder("writeAllFeatures");
+            builder.setCallback(this);
+            support.writeAllFeatures(builder, arr);
+            support.performImmediately(builder);
+        } catch(IOException e) {
+            LOG.error("Error writing all features: " + e.getMessage());
+        }
+    }
+
+    private void writeAllFeaturesInit(TransactionBuilder builder) {
+        byte[] arr = new byte[2];
+        arr[0] = 0x00;
+        arr[1] = 0x01;
+
+        mWriteAllFeaturesInitPending = true;
+
+        if(builder != null)
+            support.writeAllFeatures(builder, arr);
+        else
+            writeAllFeatures(arr);
+    }
+
+    private void writeAllFeaturesInit() {
+        writeAllFeaturesInit(null);
+    }
+
+    private void requestWatchName(TransactionBuilder builder) {
+        support.writeAllFeaturesRequest(builder, new byte[]{Casio2C2DSupport.FEATURE_WATCH_NAME});
+    }
+
+    private void requestWatchName() {
+        writeAllFeaturesRequest(new byte[]{Casio2C2DSupport.FEATURE_WATCH_NAME});
+    }
+
+    private void requestBleConfiguration() {
+        writeAllFeaturesRequest(new byte[]{Casio2C2DSupport.FEATURE_BLE_FEATURES});
+    }
+
+    private void requestBleSettings() {
+        writeAllFeaturesRequest(new byte[]{Casio2C2DSupport.FEATURE_SETTING_FOR_BLE});
+    }
+
+    private void requestAdvertisingParameters() {
+        writeAllFeaturesRequest(new byte[]{Casio2C2DSupport.FEATURE_ADVERTISE_PARAMETER_MANAGER});
+    }
+
+    private void requestConnectionParameters() {
+        writeAllFeaturesRequest(new byte[]{Casio2C2DSupport.FEATURE_CONNECTION_PARAMETER_MANAGER});
+    }
+
+    private void requestModuleId() {
+        writeAllFeaturesRequest(new byte[]{Casio2C2DSupport.FEATURE_MODULE_ID});
+    }
+
+    private void requestWatchCondition() {
+        writeAllFeaturesRequest(new byte[]{Casio2C2DSupport.FEATURE_WATCH_CONDITION});
+    }
+
+    private void requestVersionInformation() {
+        writeAllFeaturesRequest(new byte[]{Casio2C2DSupport.FEATURE_VERSION_INFORMATION});
+    }
+
+    private void requestDstWatchState() {
+        writeAllFeaturesRequest(new byte[]{Casio2C2DSupport.FEATURE_DST_WATCH_STATE});
+    }
+
+    private void requestDstSetting() {
+        writeAllFeaturesRequest(new byte[]{Casio2C2DSupport.FEATURE_DST_SETTING});
+    }
+
+    private void writeAppInformation() {
+        // FIXME: The App ID should be auto-generated and stored in the
+        // preferences instead of hard-coding it here
+        // CASIO_APP_INFORMATION:
+        byte arr[] = new byte[12];
+        arr[0] = Casio2C2DSupport.FEATURE_APP_INFORMATION;
+        for(int i=0; i<10; i++)
+            arr[i+1] = (byte) (i & 0xff);
+        arr[11] = 2;
+        writeAllFeatures(arr);
+    }
+
+    private void writeBleSettings(byte[] data) {
+        if(data.length < 14)
+            return;
+
+        // FIXME: We use hard-coded values here, should they be changeable?
+
+        // CASIO_SETTING_FOR_BLE:
+        // Don't forget first byte, we write to ALL_FEATURES!
+        // 0000000600000000000100801e00
+        // rest: mirror
+        // mBleSettings[5] = (byte)(connInterval & 0xff);
+        // mBleSettings[6] = (byte)((connInterval >> 8) & 0xff);
+        // mBleSettings[7] = (byte)(slaveLatency & 0xff);
+        // mBleSettings[8] = (byte)((slaveLatency >> 8) & 0xff);
+        // mBleSettings[11] = Auto Reconnect Hour
+        // mBleSettings[12] = Auto Reconnect Minute
+
+        // Shift-by-one b/c of ALL_FEATURES!
+        data[12] = (byte)0x80;
+        data[13] = (byte)0x1e;
+        writeAllFeatures(data);
+    }
+
+    private void writeAdvertisingParameters() {
+        // FIXME: This is hardcoded for now, based on the HCI log of my watch
+        // CASIO_ADVERTISE_PARAMETER_MANAGER
+        // Don't forget first byte, we write to ALL_FEATURES!
+        // 50000a4001050540060a02a000054001051c00
+        // byte arr[] = new byte[19];
+        // arr[0] = advertiseParamAfterSleepResetOrKeyFunctionAdvertiseInterval & 0xff;
+        // arr[1] = advertiseParamAfterSleepResetOrKeyFunctionAdvertiseInterval >> 8 & 0xff;
+        // arr[2] = getAdvertiseParamAfterSleepResetOrKeyFunctionAdvertisingTime;
+        // arr[3] = advertiseParamHighIntervalAdvertisementAfterLinkLossAdvertiseInterval & 0xff;
+        // arr[4] = advertiseParamHighIntervalAdvertisementAfterLinkLossAdvertiseInterval >> 8 & 0xff;
+        // arr[5] = getAdvertiseParamHighIntervalAdvertisementAfterLinkLossAdvertisingTime;
+        // arr[6] = getAdvertiseParamHighIntervalAdvertisementAfterLinkLossFrequencyToAdvertise;
+        // arr[7] = advertiseParamLowIntervalAdvertisementAfterLinkLossAdvertiseInterval & 0xff;
+        // arr[8] = advertiseParamLowIntervalAdvertisementAfterLinkLossAdvertiseInterval >> 8 & 0xff;
+        // arr[9] = getAdvertiseParamLowIntervalAdvertisementAfterLinkLossAdvertisingTime & 0xff;
+        // arr[10] = getAdvertiseParamLowIntervalAdvertisementAfterLinkLossFrequencyToAdvertise & 0xff;
+        // arr[11] = advertiseParamRegularBeaconAdvertisementAdvertiseInterval & 0xff;
+        // arr[12] = advertiseParamRegularBeaconAdvertisementAdvertiseInterval >> 8 & 0xff;
+        // arr[13] = getAdvertiseParamRegularBeaconAdvertisementAdvertisingTime;
+        // arr[14] = advertiseParamAdvertiseImmediatelyAfterLinkLossAdvertiseInterval & 0xff;
+        // arr[15] = advertiseParamAdvertiseImmediatelyAfterLinkLossAdvertiseInterval >> 8 & 0xff;
+        // arr[16] = getAdvertiseParamAdvertiseImmediatelyAfterLinkLossAdvertisingTime
+        // arr[17] = advertiseParamPeriodUntilSuspensionOfAutoAdvertise & 0xff;
+        // arr[18] = advertiseParamPeriodUntilSuspensionOfAutoAdvertise >> 8 & 0xff;
+
+        byte arr[] = new byte[20];
+        arr[0] = Casio2C2DSupport.FEATURE_ADVERTISE_PARAMETER_MANAGER;
+        arr[1] = (byte)0x50;
+        arr[2] = (byte)0x00;
+        arr[3] = (byte)0x0a;
+        arr[4] = (byte)0x40;
+        arr[5] = (byte)0x01;
+        arr[6] = (byte)0x05;
+        arr[7] = (byte)0x05;
+        arr[8] = (byte)0x40;
+        arr[9] = (byte)0x06;
+        arr[10] = (byte)0x0a;
+        arr[11] = (byte)0x02;
+        arr[12] = (byte)0xa0;
+        arr[13] = (byte)0x00;
+        arr[14] = (byte)0x05;
+        arr[15] = (byte)0x40;
+        arr[16] = (byte)0x01;
+        arr[17] = (byte)0x05;
+        arr[18] = (byte)0x1c;
+        arr[19] = (byte)0x00;
+        writeAllFeatures(arr);
+    }
+
+    private void writeConnectionParameters() {
+        // FIXME: This is hardcoded for now, based on the HCI log of my watch
+        // CASIO_CONNECTION_PARAMETER_MANAGER
+        // Don't forget first byte, we write to ALL_FEATURES!
+        // 00340140010400dc05
+        // byte[] arr = new byte[9];
+        // arr[0] = getConnectionParamCommand;
+        // arr[1] = connectionParamMinimumConnectionInterval & 0xff;
+        // arr[2] = connectionParamMinimumConnectionInterval >> 8 & 0xff;
+        // arr[3] = connectionParamMaxConnectionInterval & 0xff;
+        // arr[4] = connectionParamMaxConnectionInterval >> 8 & 0xff;
+        // arr[5] = connectionParamConnLatency & 0xff;
+        // arr[6] = connectionParamConnLatency >> 8 & 0xff;
+        // arr[7] = connectionParamSupervisionTimeout & 0xff;
+        // arr[8] = connectionParamSupervisionTimeout >> 8 & 0xff;
+
+        byte[] arr = new byte[10];
+        arr[0] = Casio2C2DSupport.FEATURE_CONNECTION_PARAMETER_MANAGER;
+        arr[1] = (byte)0x00;
+        arr[2] = (byte)0x34;
+        arr[3] = (byte)0x01;
+        arr[4] = (byte)0x40;
+        arr[5] = (byte)0x01;
+        arr[6] = (byte)0x04;
+        arr[7] = (byte)0x00;
+        arr[8] = (byte)0xdc;
+        arr[9] = (byte)0x05;
+        writeAllFeatures(arr);
+    }
+
+    private void enableAllFeatures(boolean enable) {
+        try {
+            TransactionBuilder builder = createTransactionBuilder("notifyAllFeatures");
+            builder.setCallback(this);
+            enableAllFeatures(builder, enable);
+            support.performImmediately(builder);
+        } catch(IOException e) {
+            LOG.error("Error setting notification value on all features: " + e.getMessage());
+        }
+    }
+
+    private void enableAllFeatures(TransactionBuilder builder, boolean enable) {
+        builder.notify(getCharacteristic(CasioConstants.CASIO_ALL_FEATURES_CHARACTERISTIC_UUID), enable);
+    }
+
+    @Override
+    protected void doPerform() throws IOException {
+        builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext()));
+        enableAllFeatures(builder, true);
+        requestWatchName(builder);
+    }
+
+    @Override
+    public TransactionBuilder performInitialized(String taskName) throws IOException {
+        throw new UnsupportedOperationException("This IS the initialization class, you cannot call this method");
+    }
+
+    @Override
+    public boolean onCharacteristicChanged(BluetoothGatt gatt,
+                                           BluetoothGattCharacteristic characteristic) {
+        UUID characteristicUUID = characteristic.getUuid();
+        byte[] data = characteristic.getValue();
+
+        if(data.length == 0)
+            return true;
+
+        // Abort a stalled allFeaturesInit write
+        if(mWriteAllFeaturesInitPending) {
+            getQueue().abortCurrentTransaction();
+            mWriteAllFeaturesInitPending = false;
+        }
+
+        if(characteristicUUID.equals(CasioConstants.CASIO_ALL_FEATURES_CHARACTERISTIC_UUID)) {
+            if(data[0] == Casio2C2DSupport.FEATURE_WATCH_NAME) {
+                LOG.debug("Got watch name, requesting BLE features; should write CASIO_APP_INFORMATION");
+                writeAppInformation();
+                // The rest of the init sequence is not strictly needed; we keep
+                // it here for future reference.
+                if(mFirstConnect)
+                    requestBleConfiguration();
+            } else if(data[0] == Casio2C2DSupport.FEATURE_BLE_FEATURES) {
+                LOG.debug("Got BLE features, requesting BLE settings");
+                requestBleSettings();
+            } else if(data[0] == Casio2C2DSupport.FEATURE_SETTING_FOR_BLE) {
+                LOG.debug("Got BLE settings, requesting advertisement parameters; should write BLE settings");
+                writeBleSettings(data);
+                requestAdvertisingParameters();
+            } else if(data[0] == Casio2C2DSupport.FEATURE_ADVERTISE_PARAMETER_MANAGER) {
+                LOG.debug("Got advertisement parameters, requesting connection parameters; should write advertisement parameters");
+                writeAdvertisingParameters();
+                requestConnectionParameters();
+            } else if(data[0] == Casio2C2DSupport.FEATURE_CONNECTION_PARAMETER_MANAGER) {
+                LOG.debug("Got connection parameters, requesting module ID; should write connection parameters");
+                writeConnectionParameters();
+                requestModuleId();
+            } else if(data[0] == Casio2C2DSupport.FEATURE_MODULE_ID) {
+                LOG.debug("Got module ID, requesting watch condition");
+                requestWatchCondition();
+            } else if(data[0] == Casio2C2DSupport.FEATURE_WATCH_CONDITION) {
+                LOG.debug("Got watch condition, requesting version information");
+                requestVersionInformation();
+            } else if(data[0] == Casio2C2DSupport.FEATURE_VERSION_INFORMATION) {
+                LOG.debug("Got version information, requesting DST watch state");
+                requestDstWatchState();
+            } else if(data[0] == Casio2C2DSupport.FEATURE_DST_WATCH_STATE) {
+                LOG.debug("Got DST watch state, requesting DST setting; should write DST watch state");
+                requestDstSetting();
+            } else if(data[0] == Casio2C2DSupport.FEATURE_DST_SETTING) {
+                LOG.debug("Got DST setting, waiting...; should write DST setting and location and radio information");
+            } else if(data[0] == Casio2C2DSupport.FEATURE_SERVICE_DISCOVERY_MANAGER) {
+                if(data[1] == 0x02) {
+                    // The writeAllFeaturesInit request triggers bonding. However, the transaction
+                    // never completes. Instead, the watch sends 0x4701 notification and we abort
+                    // the current transaction.
+                    // This is actually the request for the link loss service - it's only received
+                    // on first connection, later reconnects receive 0x47 0x01.
+                    // We also need to send the current time, if not, the watch says "Failed".
+                    LOG.debug("We need to bond here. This is actually the request for the link loss service.");
+                    try {
+                        TransactionBuilder builder = createTransactionBuilder("writeCurrentTime");
+                        support.writeCurrentTime(builder, ZonedDateTime.now());
+                        writeAllFeaturesInit(builder);
+                        support.performImmediately(builder);
+                    } catch(IOException e) {
+                        LOG.error("Error setting device to initialized: " + e.getMessage());
+                    }
+                } else if(data[1] == 0x01) {
+                    support.setInitialized();
+                }
+            } else if(data[0] == 0x3d) {
+                LOG.info("Init operation done.");
+                // Finally, we set the state to initialized here!
+                // If it's the first connection attempt, disconnect the watch and
+                // reconnect delayed. This is required on Android 13 since it's no longer
+                // possible to cancel a dangling write request.
+                // Upon first reconnection, a "getConfiguration" request is required. On further
+                // reconnections, we just sync the profile.
+                support.setInitialized();
+                if(mFirstConnect) {
+                    support.disconnect();
+                    support.reconnectDelayed();
+                } else if(mNeedsGetConfiguration) {
+                    support.onReadConfiguration(null);
+                } else {
+                    // on first connect, this is called by onReadConfiguration
+                    support.syncProfile();
+                }
+            }
+            return true;
+        } else {
+            LOG.info("Unhandled characteristic changed: " + characteristicUUID);
+            return super.onCharacteristicChanged(gatt, characteristic);
+        }
+    }
+
+    @Override
+    public boolean onCharacteristicRead(BluetoothGatt gatt,
+                                        BluetoothGattCharacteristic characteristic, int status) {
+
+        return super.onCharacteristicRead(gatt, characteristic, status);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casio/gbx100/SetConfigurationOperation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casio/gbx100/SetConfigurationOperation.java
new file mode 100644
index 0000000..02e3c3f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casio/gbx100/SetConfigurationOperation.java
@@ -0,0 +1,326 @@
+/*  Copyright (C) 2023-2024 Johannes Krude
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.casio.gbx100;
+
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.content.SharedPreferences;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.time.LocalDate;
+import java.util.Arrays;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.casio.CasioConstants;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEOperation;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.casio.Casio2C2DSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.casio.gbx100.CasioGBX100DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.OperationStatus;
+import nodomain.freeyourgadget.gadgetbridge.util.BcdUtil;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_AUTOLIGHT;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_KEY_VIBRATION;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_OPERATING_SOUNDS;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_WEARLOCATION;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivityUser.GENDER_MALE;
+
+public class SetConfigurationOperation  extends AbstractBTLEOperation<CasioGBX100DeviceSupport> {
+    private static final Logger LOG = LoggerFactory.getLogger(GetConfigurationOperation.class);
+    private final CasioGBX100DeviceSupport support;
+    private final CasioConstants.ConfigurationOption option;
+
+    public SetConfigurationOperation(CasioGBX100DeviceSupport support, CasioConstants.ConfigurationOption option) {
+        super(support);
+        this.support = support;
+        this.option = option;
+    }
+
+    @Override
+    protected void prePerform() throws IOException {
+        super.prePerform();
+        getDevice().setBusyTask("SetConfigurationOperation starting..."); // mark as busy quickly to avoid interruptions from the outside
+    }
+
+    @Override
+    protected void doPerform() throws IOException {
+        byte[] command = new byte[1];
+        command[0] = Casio2C2DSupport.FEATURE_SETTING_FOR_USER_PROFILE;
+        TransactionBuilder builder = performInitialized("getConfiguration");
+        builder.setCallback(this);
+        support.writeAllFeaturesRequest(builder, command);
+        builder.queue(getQueue());
+    }
+
+    @Override
+    public boolean onCharacteristicChanged(BluetoothGatt gatt,
+                                           BluetoothGattCharacteristic characteristic) {
+        UUID characteristicUUID = characteristic.getUuid();
+        byte[] data = characteristic.getValue();
+
+        if (data.length == 0)
+            return true;
+
+        if (characteristicUUID.equals(CasioConstants.CASIO_ALL_FEATURES_CHARACTERISTIC_UUID)) {
+            byte[] oldData = new byte[data.length];
+            System.arraycopy(data, 0, oldData, 0, data.length);
+
+            if (data[0] == Casio2C2DSupport.FEATURE_SETTING_FOR_USER_PROFILE) {
+
+                ActivityUser user = new ActivityUser();
+                boolean all = (option == CasioConstants.ConfigurationOption.OPTION_ALL);
+                if (option == CasioConstants.ConfigurationOption.OPTION_GENDER || all) {
+                    if (user.getGender() == GENDER_MALE) {
+                        data[1] = (byte) (data[1] & ~0x01);
+                    } else {
+                        data[1] = (byte) (data[1] | 0x01);
+                    }
+                }
+
+                for(int i=2; i<data.length; i++) {
+                    data[i] = (byte)~data[i];
+                }
+
+                if (option == CasioConstants.ConfigurationOption.OPTION_HEIGHT || all) {
+                    int height = user.getHeightCm();
+                    data[2] = BcdUtil.toBcd8(height % 100);
+                    data[3] = BcdUtil.toBcd8((height - (height % 100)) / 100);
+                }
+                if (option == CasioConstants.ConfigurationOption.OPTION_WEIGHT || all) {
+                    int weight = user.getWeightKg();
+                    data[4] = BcdUtil.toBcd8(weight % 100);
+                    data[5] = BcdUtil.toBcd8((weight - (weight % 100)) / 100);
+                }
+                if (option == CasioConstants.ConfigurationOption.OPTION_WRIST || all) {
+                    String location = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress()).getString(PREF_WEARLOCATION, "left");
+                    if (location == "right") {
+                        data[1] = (byte) (data[1] | 0x02);
+                    } else {
+                        data[1] = (byte) (data[1] & ~0x02);
+                    }
+                }
+                if(option == CasioConstants.ConfigurationOption.OPTION_BIRTHDAY || all) {
+                    LocalDate dateOfBirth = user.getDateOfBirth();
+                    int year = dateOfBirth.getYear();
+                    int month = dateOfBirth.getMonthValue();
+                    int day = dateOfBirth.getDayOfMonth();
+                    data[6] = BcdUtil.toBcd8(year % 100);
+                    data[7] = BcdUtil.toBcd8((year - (year % 100)) / 100);
+                    data[8] = BcdUtil.toBcd8(month);
+                    data[9] = BcdUtil.toBcd8(day);
+                }
+
+                for(int i=2; i<data.length; i++) {
+                    data[i] = (byte)~data[i];
+                }
+
+                // This allows changing month and day of birth on the watch
+                // without having it overwritten on every sync
+                boolean match = true;
+                for(int i=0; i<8; i++) {
+                    if(data[i] != oldData[i]) {
+                        match = false;
+                        break;
+                    }
+                }
+                if(match) {
+                    LOG.info("No configuration update required");
+                    requestTargetSettings();
+                } else {
+                    // Target settings will be requested in write callback
+                    try {
+                        TransactionBuilder builder = performInitialized("setConfiguration");
+                        builder.setCallback(this);
+                        support.writeAllFeatures(builder, data);
+                        builder.queue(getQueue());
+                    } catch (IOException e) {
+                        LOG.info("Error writing configuration to Casio watch");
+                    }
+                }
+                return true;
+            } else if (data[0] == Casio2C2DSupport.FEATURE_SETTING_FOR_TARGET_VALUE) {
+                ActivityUser user = new ActivityUser();
+                boolean all = (option == CasioConstants.ConfigurationOption.OPTION_ALL);
+
+                if(option == CasioConstants.ConfigurationOption.OPTION_STEP_GOAL || all) {
+                    int steps = user.getStepsGoal();
+                    data[1] = (byte)(steps & 0xff);
+                    data[2] = (byte)((steps >> 8) & 0xff);
+                }
+
+                if(option == CasioConstants.ConfigurationOption.OPTION_DISTANCE_GOAL || all) {
+                    // The watch requires a monthly goal, so we multiply that with 30
+                    // and divide it by 100 because the value is set in 100m units
+                    int distance = user.getDistanceGoalMeters() * 30;
+                    distance = distance / 100;
+                    data[6] = (byte)(distance & 0xff);
+                    data[7] = (byte)((distance >> 8) & 0xff);
+                }
+
+                if(option == CasioConstants.ConfigurationOption.OPTION_ACTIVITY_GOAL || all) {
+                    // The watch requires a monthly goal, so we multiply that with 30
+                    int time = user.getActiveTimeGoalMinutes() * 30;
+                    data[9] = (byte)(time & 0xff);
+                    data[10] = (byte)((time >> 8) & 0xff);
+                }
+
+                if(Arrays.equals(oldData, data)) {
+                    LOG.info("No configuration update required");
+                    requestBasicSettings();
+                } else {
+                    // Basic settings will be requested in Gatt callback
+                    try {
+                        TransactionBuilder builder = performInitialized("setConfiguration");
+                        builder.setCallback(this);
+                        support.writeAllFeatures(builder, data);
+                        builder.queue(getQueue());
+                    } catch (IOException e) {
+                        LOG.info("Error writing configuration to Casio watch");
+                    }
+                }
+                return true;
+            } else if(data[0] == Casio2C2DSupport.FEATURE_SETTING_FOR_BASIC) {
+                SharedPreferences sharedPreferences = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress());
+
+                String timeformat = GBApplication.getDevicePrefs(getDevice()).getTimeFormat();
+
+                if(timeformat.equals(getContext().getString(R.string.p_timeformat_24h))) {
+                    data[1]  |= 0x01;
+                } else {
+                    data[1] &= ~0x01;
+                }
+
+                boolean autolight = sharedPreferences.getBoolean(PREF_AUTOLIGHT, false);
+                if(autolight) {
+                    data[1] &= ~0x04;
+                } else {
+                    data[1] |= 0x04;
+                }
+
+                boolean key_vibration = sharedPreferences.getBoolean(PREF_KEY_VIBRATION, true);
+                if (key_vibration) {
+                    data[10] = 1;
+                } else {
+                    data[10] = 0;
+                }
+
+                boolean operating_sounds = sharedPreferences.getBoolean(PREF_OPERATING_SOUNDS, false);
+                if(operating_sounds) {
+                    data[1] &= ~0x02;
+                } else {
+                    data[1] |= 0x02;
+                }
+
+                if(Arrays.equals(oldData, data)) {
+                    LOG.info("No configuration update required");
+                    operationFinished();
+                } else {
+                    // Operation will be finished in Gatt callback
+                    try {
+                        TransactionBuilder builder = performInitialized("setConfiguration");
+                        builder.setCallback(this);
+                        support.writeAllFeatures(builder, data);
+                        builder.queue(getQueue());
+                    } catch (IOException e) {
+                        LOG.info("Error writing configuration to Casio watch");
+                    }
+                }
+                return true;
+            }
+
+        }
+        LOG.info("Unhandled characteristic changed: " + characteristicUUID);
+        return super.onCharacteristicChanged(gatt, characteristic);
+    }
+
+    @Override
+    protected void operationFinished() {
+        LOG.info("SetConfigurationOperation finished");
+
+        unsetBusy();
+        operationStatus = OperationStatus.FINISHED;
+        if (getDevice() != null) {
+            try {
+                TransactionBuilder builder = performInitialized("finished operation");
+                builder.setCallback(null); // unset ourselves from being the queue's gatt callback
+                builder.wait(0);
+                builder.queue(getQueue());
+            } catch (IOException ex) {
+                LOG.info("Error resetting Gatt callback: " + ex.getMessage());
+            }
+        }
+    }
+
+    private void requestBasicSettings() {
+        byte[] command = new byte[1];
+        command[0] = Casio2C2DSupport.FEATURE_SETTING_FOR_BASIC;
+        try {
+            TransactionBuilder builder = performInitialized("getConfiguration");
+            builder.setCallback(this);
+            support.writeAllFeaturesRequest(builder, command);
+            builder.queue(getQueue());
+        } catch(IOException e) {
+            LOG.info("Error requesting Casio configuration");
+        }
+    }
+
+    private void requestTargetSettings() {
+        byte[] command = new byte[1];
+        command[0] = Casio2C2DSupport.FEATURE_SETTING_FOR_TARGET_VALUE;
+        try {
+            TransactionBuilder builder = performInitialized("getConfiguration");
+            builder.setCallback(this);
+            support.writeAllFeaturesRequest(builder, command);
+            builder.queue(getQueue());
+        } catch(IOException e) {
+            LOG.info("Error requesting Casio configuration");
+        }
+    }
+
+    @Override
+    public boolean onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
+
+        UUID characteristicUUID = characteristic.getUuid();
+        byte[] data = characteristic.getValue();
+
+        if (data.length == 0)
+            return true;
+
+        if (characteristicUUID.equals(CasioConstants.CASIO_ALL_FEATURES_CHARACTERISTIC_UUID)) {
+            if(data[0] == Casio2C2DSupport.FEATURE_SETTING_FOR_USER_PROFILE) {
+                requestTargetSettings();
+                return true;
+            }
+            if(data[0] == Casio2C2DSupport.FEATURE_SETTING_FOR_TARGET_VALUE) {
+                requestBasicSettings();
+                return true;
+            }
+            if(data[0] == Casio2C2DSupport.FEATURE_SETTING_FOR_BASIC) {
+                operationFinished();
+                return true;
+            }
+        }
+        return super.onCharacteristicWrite(gatt, characteristic, status);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casio/gwb5600/CasioGWB5600DeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casio/gwb5600/CasioGWB5600DeviceSupport.java
new file mode 100644
index 0000000..78bb592
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casio/gwb5600/CasioGWB5600DeviceSupport.java
@@ -0,0 +1,59 @@
+/*  Copyright (C) 2023-2024 Johannes Krude
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.casio.gwb5600;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.casio.BasicCasio2C2DSupport;
+
+public class CasioGWB5600DeviceSupport extends BasicCasio2C2DSupport {
+    private static final Logger LOG = LoggerFactory.getLogger(CasioGWB5600DeviceSupport.class);
+
+    public CasioGWB5600DeviceSupport() {
+        super(LOG);
+    }
+
+    @Override
+    public DevicePreference[] supportedDevicePreferences() {
+        return new DevicePreference[] {
+            new LanguagePreference(),
+            new TimeFormatPreference(),
+            new DayMonthOrderPreference(),
+            new OperatingSoundPreference(),
+            new HourlyChimePreference(),
+            new AutoLightPreference(),
+            new LongerLightDurationPreference(),
+            new PowerSavingPreference(),
+            new ConnectionDurationPreference(),
+            new TimeSyncPreference(),
+        };
+    }
+
+    @Override
+    public boolean useAutoConnect() {
+        return false;
+    }
+
+    @Override
+    public boolean connectFirstTime() {
+    // remove this workaround in case Gadgetbridge fixes
+    // https://codeberg.org/Freeyourgadget/Gadgetbridge/issues/3216
+        setAutoReconnect(true);
+        return connect();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/cmfwatchpro/CmfActivitySync.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/cmfwatchpro/CmfActivitySync.java
new file mode 100644
index 0000000..c8e2147
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/cmfwatchpro/CmfActivitySync.java
@@ -0,0 +1,668 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.cmfwatchpro;
+
+import android.content.Context;
+import android.widget.Toast;
+
+import androidx.annotation.Nullable;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+
+import de.greenrobot.dao.query.QueryBuilder;
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.cmfwatchpro.samples.CmfActivitySampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.cmfwatchpro.samples.CmfHeartRateSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.cmfwatchpro.samples.CmfSleepSessionSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.cmfwatchpro.samples.CmfSleepStageSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.cmfwatchpro.samples.CmfSpo2SampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.cmfwatchpro.samples.CmfStressSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.cmfwatchpro.samples.CmfWorkoutGpsSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.cmfwatchpro.workout.CmfWorkoutSummaryParser;
+import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary;
+import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummaryDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.CmfActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.entities.CmfHeartRateSample;
+import nodomain.freeyourgadget.gadgetbridge.entities.CmfSleepSessionSample;
+import nodomain.freeyourgadget.gadgetbridge.entities.CmfSleepStageSample;
+import nodomain.freeyourgadget.gadgetbridge.entities.CmfSpo2Sample;
+import nodomain.freeyourgadget.gadgetbridge.entities.CmfStressSample;
+import nodomain.freeyourgadget.gadgetbridge.entities.CmfWorkoutGpsSample;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.entities.User;
+import nodomain.freeyourgadget.gadgetbridge.export.ActivityTrackExporter;
+import nodomain.freeyourgadget.gadgetbridge.export.GPXExporter;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityPoint;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityTrack;
+import nodomain.freeyourgadget.gadgetbridge.model.GPSCoordinate;
+import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class CmfActivitySync {
+    private static final Logger LOG = LoggerFactory.getLogger(CmfActivitySync.class);
+
+    private final CmfWatchProSupport mSupport;
+
+    private final List<BaseActivitySummary> activitiesWithGps = new ArrayList<>();
+
+    protected CmfActivitySync(final CmfWatchProSupport support) {
+        this.mSupport = support;
+    }
+
+    protected boolean onCommand(final CmfCommand cmd, final byte[] payload) {
+        switch (cmd) {
+            case ACTIVITY_FETCH_ACK_1:
+                handleActivityFetchAck1(payload);
+                return true;
+            case ACTIVITY_FETCH_ACK_2:
+                handleActivityFetchAck2(payload);
+                return true;
+            case ACTIVITY_DATA:
+                handleActivityData(payload);
+                return true;
+            case HEART_RATE_MANUAL_AUTO:
+            case HEART_RATE_WORKOUT:
+                handleHeartRate(payload);
+                return true;
+            case HEART_RATE_RESTING:
+                handleHeartRateResting(payload);
+                return true;
+            case SLEEP_DATA:
+                handleSleepData(payload);
+                return true;
+            case STRESS:
+                handleStress(payload);
+                return true;
+            case SPO2:
+                handleSpo2(payload);
+                return true;
+            case WORKOUT_SUMMARY:
+                handleWorkoutSummary(payload);
+                return true;
+            case WORKOUT_GPS:
+                handleWorkoutGps(payload);
+                return true;
+        }
+
+        return false;
+    }
+
+    private void handleActivityFetchAck1(final byte[] payload) {
+        switch (payload[0]) {
+            case 0x01:
+                LOG.debug("Got activity fetch ack 1, starting step 2");
+                GB.updateTransferNotification(getContext().getString(R.string.busy_task_fetch_activity_data), "", true, 0, getContext());
+                getDevice().setBusyTask(getContext().getString(R.string.busy_task_fetch_activity_data));
+                mSupport.sendCommand("fetch recorded data step 2", CmfCommand.ACTIVITY_FETCH_2, CmfWatchProSupport.A5);
+                break;
+            case 0x02:
+                LOG.debug("Got activity fetch finish");
+                // Process activities with GPS before unsetting device as busy
+                processActivitiesWithGps();
+                break;
+            default:
+                LOG.warn("Unknown activity fetch ack code {}", payload[0]);
+                return;
+        }
+
+        getDevice().sendDeviceUpdateIntent(getContext());
+    }
+
+    private static void handleActivityFetchAck2(final byte[] payload) {
+        final ByteBuffer buf = ByteBuffer.wrap(payload).order(ByteOrder.LITTLE_ENDIAN);
+
+        final int activityTs = buf.getInt();
+        final byte[] activityFlags = new byte[4]; // TODO what do they mean?
+        buf.order(ByteOrder.BIG_ENDIAN).get(activityFlags);
+        LOG.debug("Getting activity since {}, flags={}", activityTs, GB.hexdump(activityFlags));
+    }
+
+    private void handleActivityData(final byte[] payload) {
+        if (payload.length % 32 != 0) {
+            LOG.error("Activity data payload size {} not divisible by 32", payload.length);
+            return;
+        }
+
+        LOG.debug("Got {} activity samples", payload.length / 32);
+
+        final ByteBuffer buf = ByteBuffer.wrap(payload).order(ByteOrder.LITTLE_ENDIAN);
+
+        final List<CmfActivitySample> samples = new ArrayList<>();
+
+        while (buf.remaining() > 0) {
+            final CmfActivitySample sample = new CmfActivitySample();
+            sample.setTimestamp(buf.getInt());
+            sample.setSteps(buf.getInt());
+            sample.setDistance(buf.getInt());
+            sample.setCalories(buf.getInt());
+
+            final byte[] unk = new byte[16];
+            buf.get(unk);
+
+            samples.add(sample);
+        }
+
+        try (DBHandler handler = GBApplication.acquireDB()) {
+            final DaoSession session = handler.getDaoSession();
+
+            final Device device = DBHelper.getDevice(getDevice(), session);
+            final User user = DBHelper.getUser(session);
+
+            final CmfActivitySampleProvider sampleProvider = new CmfActivitySampleProvider(getDevice(), session);
+
+            for (final CmfActivitySample sample : samples) {
+                sample.setDevice(device);
+                sample.setUser(user);
+                sample.setProvider(sampleProvider);
+            }
+
+            LOG.debug("Will persist {} activity samples", samples.size());
+            sampleProvider.addGBActivitySamples(samples.toArray(new CmfActivitySample[0]));
+        } catch (final Exception e) {
+            GB.toast(getContext(), "Error saving activity samples", Toast.LENGTH_LONG, GB.ERROR, e);
+        }
+    }
+
+    private void handleHeartRate(final byte[] payload) {
+        if (payload.length % 8 != 0) {
+            LOG.error("Heart rate payload size {} not divisible by 8", payload.length);
+            return;
+        }
+
+        LOG.debug("Got {} heart rate samples", payload.length / 8);
+
+        final ByteBuffer buf = ByteBuffer.wrap(payload).order(ByteOrder.LITTLE_ENDIAN);
+
+        final List<CmfHeartRateSample> samples = new ArrayList<>();
+
+        while (buf.remaining() > 0) {
+            final CmfHeartRateSample sample = new CmfHeartRateSample();
+            sample.setTimestamp(buf.getInt() * 1000L);
+            sample.setHeartRate(buf.getInt());
+
+            samples.add(sample);
+        }
+
+        try (DBHandler handler = GBApplication.acquireDB()) {
+            final DaoSession session = handler.getDaoSession();
+
+            final Device device = DBHelper.getDevice(getDevice(), session);
+            final User user = DBHelper.getUser(session);
+
+            final CmfHeartRateSampleProvider sampleProvider = new CmfHeartRateSampleProvider(getDevice(), session);
+
+            for (final CmfHeartRateSample sample : samples) {
+                sample.setDevice(device);
+                sample.setUser(user);
+            }
+
+            LOG.debug("Will persist {} heart rate samples", samples.size());
+            sampleProvider.addSamples(samples);
+        } catch (final Exception e) {
+            GB.toast(getContext(), "Error saving heart rate samples", Toast.LENGTH_LONG, GB.ERROR, e);
+        }
+    }
+
+    private static void handleHeartRateResting(final byte[] payload) {
+        // TODO persist resting HR samples;
+        LOG.warn("Persisting resting HR samples is not implemented");
+    }
+
+    private void handleSleepData(final byte[] payload) {
+        final ByteBuffer buf = ByteBuffer.wrap(payload).order(ByteOrder.LITTLE_ENDIAN);
+
+        LOG.debug("Got sleep data samples");
+
+        final int sessionTimestamp = buf.getInt();
+        final int wakeupTime = buf.getInt();
+        final byte[] metadata = new byte[10];
+        buf.get(metadata);
+
+        final CmfSleepSessionSample sessionSample = new CmfSleepSessionSample();
+        sessionSample.setTimestamp(sessionTimestamp * 1000L);
+        sessionSample.setWakeupTime(wakeupTime * 1000L);
+        sessionSample.setMetadata(metadata);
+
+        final List<CmfSleepStageSample> stageSamples = new ArrayList<>();
+
+        while (buf.remaining() > 0) {
+            final CmfSleepStageSample sample = new CmfSleepStageSample();
+            sample.setTimestamp(buf.getInt() * 1000L);
+            sample.setDuration(buf.getShort());
+            sample.setStage(buf.getShort());
+            stageSamples.add(sample);
+        }
+
+        try (DBHandler handler = GBApplication.acquireDB()) {
+            final DaoSession session = handler.getDaoSession();
+
+            final Device device = DBHelper.getDevice(getDevice(), session);
+            final User user = DBHelper.getUser(session);
+
+            final CmfSleepSessionSampleProvider sampleProvider = new CmfSleepSessionSampleProvider(getDevice(), session);
+
+            sessionSample.setDevice(device);
+            sessionSample.setUser(user);
+
+            LOG.debug("Will persist 1 sleep session sample from {} to {}", sessionSample.getTimestamp(), sessionSample.getWakeupTime());
+            sampleProvider.addSample(sessionSample);
+        } catch (final Exception e) {
+            GB.toast(getContext(), "Error saving sleep session sample", Toast.LENGTH_LONG, GB.ERROR, e);
+        }
+
+        try (DBHandler handler = GBApplication.acquireDB()) {
+            final DaoSession session = handler.getDaoSession();
+
+            final Device device = DBHelper.getDevice(getDevice(), session);
+            final User user = DBHelper.getUser(session);
+
+            final CmfSleepStageSampleProvider sampleProvider = new CmfSleepStageSampleProvider(getDevice(), session);
+
+            for (final CmfSleepStageSample sample : stageSamples) {
+                sample.setDevice(device);
+                sample.setUser(user);
+            }
+
+            LOG.debug("Will persist {} sleep stage samples", stageSamples.size());
+            sampleProvider.addSamples(stageSamples);
+        } catch (final Exception e) {
+            GB.toast(getContext(), "Error saving sleep samples", Toast.LENGTH_LONG, GB.ERROR, e);
+        }
+    }
+
+    private void handleStress(final byte[] payload) {
+        if (payload.length % 8 != 0) {
+            LOG.error("Stress payload size {} not divisible by 8", payload.length);
+            return;
+        }
+
+        LOG.debug("Got {} stress samples", payload.length / 8);
+
+        final ByteBuffer buf = ByteBuffer.wrap(payload).order(ByteOrder.LITTLE_ENDIAN);
+
+        final List<CmfStressSample> samples = new ArrayList<>();
+
+        while (buf.remaining() > 0) {
+            final CmfStressSample sample = new CmfStressSample();
+            sample.setTimestamp(buf.getInt() * 1000L);
+            sample.setStress(buf.getInt());
+
+            samples.add(sample);
+        }
+
+        try (DBHandler handler = GBApplication.acquireDB()) {
+            final DaoSession session = handler.getDaoSession();
+
+            final Device device = DBHelper.getDevice(getDevice(), session);
+            final User user = DBHelper.getUser(session);
+
+            final CmfStressSampleProvider sampleProvider = new CmfStressSampleProvider(getDevice(), session);
+
+            for (final CmfStressSample sample : samples) {
+                sample.setDevice(device);
+                sample.setUser(user);
+            }
+
+            LOG.debug("Will persist {} stress samples", samples.size());
+            sampleProvider.addSamples(samples);
+        } catch (final Exception e) {
+            GB.toast(getContext(), "Error saving stress samples", Toast.LENGTH_LONG, GB.ERROR, e);
+        }
+    }
+
+    private void handleSpo2(final byte[] payload) {
+        if (payload.length % 8 != 0) {
+            LOG.error("Spo2 payload size {} not divisible by 8", payload.length);
+            return;
+        }
+
+        LOG.debug("Got {} spo2 samples", payload.length / 8);
+
+        final ByteBuffer buf = ByteBuffer.wrap(payload).order(ByteOrder.LITTLE_ENDIAN);
+
+        final List<CmfSpo2Sample> samples = new ArrayList<>();
+
+        while (buf.remaining() > 0) {
+            final CmfSpo2Sample sample = new CmfSpo2Sample();
+            sample.setTimestamp(buf.getInt() * 1000L);
+            sample.setSpo2(buf.getInt());
+
+            samples.add(sample);
+        }
+
+        try (DBHandler handler = GBApplication.acquireDB()) {
+            final DaoSession session = handler.getDaoSession();
+
+            final Device device = DBHelper.getDevice(getDevice(), session);
+            final User user = DBHelper.getUser(session);
+
+            final CmfSpo2SampleProvider sampleProvider = new CmfSpo2SampleProvider(getDevice(), session);
+
+            for (final CmfSpo2Sample sample : samples) {
+                sample.setDevice(device);
+                sample.setUser(user);
+            }
+
+            LOG.debug("Will persist {} spo2 samples", samples.size());
+            sampleProvider.addSamples(samples);
+        } catch (final Exception e) {
+            GB.toast(getContext(), "Error saving spo2 samples", Toast.LENGTH_LONG, GB.ERROR, e);
+        }
+    }
+
+    private void handleWorkoutSummary(final byte[] payload) {
+        final int bytesPerWorkout;
+
+        if (payload.length % 32 == 0) {
+            bytesPerWorkout = 32;
+        } else if (payload.length % 54 == 0) {
+            bytesPerWorkout = 54;
+        } else {
+            LOG.error("Workout summary payload size {} not divisible by 32 or 54", payload.length);
+            return;
+        }
+
+        LOG.debug("Got {} workout summary samples", payload.length / bytesPerWorkout);
+
+        final ByteBuffer buf = ByteBuffer.wrap(payload).order(ByteOrder.LITTLE_ENDIAN);
+
+        final CmfWorkoutSummaryParser summaryParser = new CmfWorkoutSummaryParser(getDevice());
+
+        while (buf.remaining() > 0) {
+            final byte[] summaryBytes = new byte[bytesPerWorkout];
+            buf.get(summaryBytes);
+
+            BaseActivitySummary summary = new BaseActivitySummary();
+            summary.setRawSummaryData(summaryBytes);
+            summary.setActivityKind(ActivityKind.UNKNOWN.getCode());
+
+            try {
+                summary = summaryParser.parseBinaryData(summary, true);
+            } catch (final Exception e) {
+                LOG.error("Failed to parse workout summary", e);
+                GB.toast(getContext(), "Failed to parse workout summary", Toast.LENGTH_LONG, GB.ERROR, e);
+                return;
+            }
+
+            if (summary == null) {
+                LOG.error("Workout summary is null");
+                return;
+            }
+
+            summary.setSummaryData(null); // remove json before saving to database
+
+            try (DBHandler dbHandler = GBApplication.acquireDB()) {
+                final DaoSession session = dbHandler.getDaoSession();
+                final Device device = DBHelper.getDevice(getDevice(), session);
+                final User user = DBHelper.getUser(session);
+
+                summary.setDevice(device);
+                summary.setUser(user);
+
+                LOG.debug("Persisting workout summary for {}", summary.getStartTime());
+
+                session.getBaseActivitySummaryDao().insertOrReplace(summary);
+            } catch (final Exception e) {
+                GB.toast(getContext(), "Error saving activity summary", Toast.LENGTH_LONG, GB.ERROR, e);
+                return;
+            }
+
+            // FIXME: This should be set by CmfWorkoutSummaryParser
+            if (summaryBytes[30] == 1) {
+                activitiesWithGps.add(summary);
+            }
+        }
+    }
+
+    private void handleWorkoutGps(final byte[] payload) {
+        if (payload.length % 12 != 0) {
+            LOG.error("Workout gps payload size {} not divisible by 12", payload.length);
+            return;
+        }
+
+        LOG.debug("Got {} workout gps samples", payload.length / 12);
+
+        final ByteBuffer buf = ByteBuffer.wrap(payload).order(ByteOrder.LITTLE_ENDIAN);
+
+        final List<CmfWorkoutGpsSample> samples = new ArrayList<>();
+
+        while (buf.remaining() > 0) {
+            final CmfWorkoutGpsSample sample = new CmfWorkoutGpsSample();
+            sample.setTimestamp(buf.getInt() * 1000L);
+            sample.setLongitude(buf.getInt());
+            sample.setLatitude(buf.getInt());
+
+            samples.add(sample);
+        }
+
+        try (DBHandler handler = GBApplication.acquireDB()) {
+            final DaoSession session = handler.getDaoSession();
+
+            final Device device = DBHelper.getDevice(getDevice(), session);
+            final User user = DBHelper.getUser(session);
+
+            final CmfWorkoutGpsSampleProvider sampleProvider = new CmfWorkoutGpsSampleProvider(getDevice(), session);
+
+            for (final CmfWorkoutGpsSample sample : samples) {
+                sample.setDevice(device);
+                sample.setUser(user);
+            }
+
+            LOG.debug("Will persist {} workout gps samples", samples.size());
+            sampleProvider.addSamples(samples);
+        } catch (final Exception e) {
+            GB.toast(getContext(), "Error saving workout gps samples", Toast.LENGTH_LONG, GB.ERROR, e);
+        }
+    }
+
+    private void processActivitiesWithGps() {
+        LOG.debug("There are {} activities with gps to process", activitiesWithGps.size());
+
+        for (final BaseActivitySummary summary : activitiesWithGps) {
+            processGps(summary);
+        }
+
+        activitiesWithGps.clear();
+
+        getDevice().unsetBusyTask();
+        GB.signalActivityDataFinish(getDevice());
+        GB.updateTransferNotification(null, "", false, 100, getContext());
+    }
+
+    private void processGps(final BaseActivitySummary summary) {
+        final ActivityTrack activityTrack = buildActivityTrack(summary);
+        if (activityTrack == null) {
+            return;
+        }
+
+        // Save the gpx file
+        final File gpxFile = exportGpx(summary, activityTrack);
+        if (gpxFile == null) {
+            return;
+        }
+
+        // Update the summary in the db with the gpx path
+        try (DBHandler dbHandler = GBApplication.acquireDB()) {
+            final DaoSession session = dbHandler.getDaoSession();
+            final Device device = DBHelper.getDevice(mSupport.getDevice(), session);
+            final User user = DBHelper.getUser(session);
+
+            final BaseActivitySummaryDao summaryDao = session.getBaseActivitySummaryDao();
+            final QueryBuilder<BaseActivitySummary> qb = summaryDao.queryBuilder();
+            qb.where(BaseActivitySummaryDao.Properties.StartTime.eq(summary.getStartTime()));
+            qb.where(BaseActivitySummaryDao.Properties.DeviceId.eq(device.getId()));
+            qb.where(BaseActivitySummaryDao.Properties.UserId.eq(user.getId()));
+            final List<BaseActivitySummary> summaries = qb.build().list();
+
+            if (summaries.isEmpty()) {
+                LOG.warn("Failed to find existing summary in db - this should never happen");
+                return;
+            }
+            if (summaries.size() > 1) {
+                LOG.warn("Found multiple summaries in db - this should never happen");
+            }
+
+            final BaseActivitySummary summaryToUpdate = summaries.get(0);
+            summaryToUpdate.setGpxTrack(gpxFile.getAbsolutePath());
+            session.getBaseActivitySummaryDao().insertOrReplace(summaryToUpdate);
+        } catch (final Exception e) {
+            LOG.error("Failed to update summary with gpx path", e);
+        }
+    }
+
+    @Nullable
+    private File exportGpx(final BaseActivitySummary summary, final ActivityTrack activityTrack) {
+        final GPXExporter exporter = new GPXExporter();
+
+        final String gpxFileName = FileUtils.makeValidFileName("gadgetbridge-" + DateTimeUtils.formatIso8601(summary.getStartTime()) + ".gpx");
+        final File gpxTargetFile;
+        try {
+            gpxTargetFile = new File(FileUtils.getExternalFilesDir(), gpxFileName);
+        } catch (final IOException e) {
+            LOG.error("Failed to get external files dir", e);
+            return null;
+        }
+
+        try {
+            exporter.performExport(activityTrack, gpxTargetFile);
+        } catch (final ActivityTrackExporter.GPXTrackEmptyException e) {
+            LOG.warn("Gpx is empty");
+            return null;
+        } catch (IOException e) {
+            LOG.error("Failed to write gpx", e);
+            return null;
+        }
+
+        return gpxTargetFile;
+    }
+
+    @Nullable
+    private ActivityTrack buildActivityTrack(final BaseActivitySummary summary) {
+        final ActivityTrack track = new ActivityTrack();
+        track.setUser(summary.getUser());
+        track.setDevice(summary.getDevice());
+        track.setName(createActivityName(summary));
+
+        final List<CmfWorkoutGpsSample> gpsSamples;
+        final List<CmfHeartRateSample> hrSamples;
+        try (DBHandler handler = GBApplication.acquireDB()) {
+            final DaoSession session = handler.getDaoSession();
+
+            final CmfWorkoutGpsSampleProvider gpsSampleProvider = new CmfWorkoutGpsSampleProvider(getDevice(), session);
+            gpsSamples = gpsSampleProvider.getAllSamples(summary.getStartTime().getTime(), summary.getEndTime().getTime());
+
+            final CmfHeartRateSampleProvider hrSampleProvider = new CmfHeartRateSampleProvider(getDevice(), session);
+            hrSamples = new ArrayList<>(hrSampleProvider.getAllSamples(summary.getStartTime().getTime(), summary.getEndTime().getTime()));
+        } catch (final Exception e) {
+            LOG.error("Error while building activity track", e);
+            return null;
+        }
+
+        Collections.sort(hrSamples, (a, b) -> Long.compare(a.getTimestamp(), b.getTimestamp()));
+
+        for (final CmfWorkoutGpsSample gpsSample : gpsSamples) {
+            final ActivityPoint ap = new ActivityPoint(new Date(gpsSample.getTimestamp()));
+            final GPSCoordinate coordinate = new GPSCoordinate(
+                    gpsSample.getLongitude() / 10000000d,
+                    gpsSample.getLatitude() / 10000000d
+            );
+            ap.setLocation(coordinate);
+
+            final CmfHeartRateSample hrSample = findNearestSample(hrSamples, gpsSample.getTimestamp());
+            if (hrSample != null) {
+                ap.setHeartRate(hrSample.getHeartRate());
+            }
+
+            track.addTrackPoint(ap);
+        }
+
+        return track;
+    }
+
+    @Nullable
+    private CmfHeartRateSample findNearestSample(final List<CmfHeartRateSample> samples, final long timestamp) {
+        if (samples.isEmpty()) {
+            return null;
+        }
+
+        if (timestamp < samples.get(0).getTimestamp()) {
+            return samples.get(0);
+        }
+
+        if (timestamp > samples.get(samples.size() - 1).getTimestamp()) {
+            return samples.get(samples.size() - 1);
+        }
+
+        int start = 0;
+        int end = samples.size() - 1;
+
+        while (start <= end) {
+            final int mid = (start + end) / 2;
+
+            if (timestamp < samples.get(mid).getTimestamp()) {
+                end = mid - 1;
+            } else if (timestamp > samples.get(mid).getTimestamp()) {
+                start = mid + 1;
+            } else {
+                return samples.get(mid);
+            }
+        }
+
+        // FIXME return null if too far?
+
+        if (samples.get(start).getTimestamp() - timestamp < timestamp - samples.get(end).getTimestamp()) {
+            return samples.get(start);
+        }
+
+        return samples.get(end);
+    }
+
+    protected static String createActivityName(final BaseActivitySummary summary) {
+        String name = summary.getName();
+        String nameText = "";
+        Long id = summary.getId();
+        if (name != null) {
+            nameText = name + " - ";
+        }
+        return nameText + id;
+    }
+
+    private Context getContext() {
+        return mSupport.getContext();
+    }
+
+    private GBDevice getDevice() {
+        return mSupport.getDevice();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/cmfwatchpro/CmfActivityType.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/cmfwatchpro/CmfActivityType.java
new file mode 100644
index 0000000..0f8650c
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/cmfwatchpro/CmfActivityType.java
@@ -0,0 +1,181 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.cmfwatchpro;
+
+import androidx.annotation.Nullable;
+
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+
+public enum CmfActivityType {
+    // Core (non-removable in official app)
+    INDOOR_RUNNING(0x03, ActivityKind.INDOOR_RUNNING),
+    OUTDOOR_RUNNING(0x02, ActivityKind.OUTDOOR_RUNNING),
+    // Fitness
+    OUTDOOR_WALKING(0x01, ActivityKind.OUTDOOR_WALKING),
+    INDOOR_WALKING(0x19, ActivityKind.INDOOR_WALKING),
+    OUTDOOR_CYCLING(0x05, ActivityKind.OUTDOOR_CYCLING),
+    INDOOR_CYCLING(0x72, ActivityKind.INDOOR_CYCLING),
+    MOUNTAIN_HIKE(0x04, ActivityKind.MOUNTAIN_HIKE),
+    HIKING(0x1A, ActivityKind.HIKING),
+    CROSS_TRAINER(0x18, ActivityKind.CROSS_TRAINER),
+    FREE_TRAINING(0x10, ActivityKind.FREE_TRAINING),
+    STRENGTH_TRAINING(0x13, ActivityKind.STRENGTH_TRAINING),
+    YOGA(0x0F, ActivityKind.YOGA),
+    BOXING(0x21, ActivityKind.BOXING),
+    ROWER(0x0E, ActivityKind.ROWING_MACHINE),
+    DYNAMIC_CYCLE(0x0D, ActivityKind.DYNAMIC_CYCLE),
+    STAIR_STEPPER(0x73, ActivityKind.STAIR_STEPPER),
+    TREADMILL(0x26, ActivityKind.TREADMILL),
+    KICKBOXING(0x35, ActivityKind.KICKBOXING),
+    HIIT(0x5C, ActivityKind.HIIT),
+    FITNESS_EXERCISES(0x4E, ActivityKind.FITNESS_EXERCISES),
+    JUMP_ROPING(0x06, ActivityKind.JUMP_ROPING), // moved to leisure sports in watch 2
+    PILATES(0x2C, ActivityKind.PILATES),
+    CROSSFIT(0x74, ActivityKind.CROSSFIT),
+    FUNCTIONAL_TRAINING(0x2E, ActivityKind.FUNCTIONAL_TRAINING),
+    PHYSICAL_TRAINING(0x2F, ActivityKind.PHYSICAL_TRAINING),
+    TAEKWONDO(0x25, ActivityKind.TAEKWONDO),
+    TAE_BO(0x50, ActivityKind.TAE_BO),
+    CROSS_COUNTRY_RUNNING(0x1B, ActivityKind.CROSS_COUNTRY_RUNNING),
+    KARATE(0x29, ActivityKind.KARATE),
+    FENCING(0x54, ActivityKind.FENCING),
+    CORE_TRAINING(0x4B, ActivityKind.CORE_TRAINING),
+    KENDO(0x75, ActivityKind.KENDO),
+    HORIZONTAL_BAR(0x56, ActivityKind.HORIZONTAL_BAR),
+    PARALLEL_BAR(0x57, ActivityKind.PARALLEL_BAR),
+    COOLDOWN(0x92, ActivityKind.COOLDOWN),
+    CROSS_TRAINING(0x2B, ActivityKind.CROSS_TRAINING),
+    SIT_UPS(0x11, ActivityKind.SIT_UPS),
+    FITNESS_GAMING(0x4D, ActivityKind.FITNESS_GAMING),
+    AEROBIC_EXERCISE(0x94, ActivityKind.AEROBIC_EXERCISE),
+    ROLLING(0x95, ActivityKind.ROLLING),
+    FLEXIBILITY(0x31, ActivityKind.FLEXIBILITY),
+    GYMNASTICS(0x23, ActivityKind.GYMNASTICS),
+    TRACK_AND_FIELD(0x27, ActivityKind.TRACK_AND_FIELD),
+    PUSH_UPS(0x67, ActivityKind.PUSH_UPS),
+    BATTLE_ROPE(0x99, ActivityKind.BATTLE_ROPE),
+    SMITH_MACHINE(0x9A, ActivityKind.SMITH_MACHINE),
+    PULL_UPS(0x66, ActivityKind.PULL_UPS),
+    PLANK(0x68, ActivityKind.PLANK),
+    JAVELIN(0x9E, ActivityKind.JAVELIN),
+    LONG_JUMP(0x6C, ActivityKind.LONG_JUMP),
+    HIGH_JUMP(0x6A, ActivityKind.HIGH_JUMP),
+    TRAMPOLINE(0x5F, ActivityKind.TRAMPOLINE),
+    DUMBBELL(0x9F, ActivityKind.DUMBBELL),
+    // Dance
+    BELLY_DANCE(0x76, ActivityKind.BELLY_DANCE),
+    JAZZ_DANCE(0x77, ActivityKind.JAZZ_DANCE),
+    LATIN_DANCE(0x33, ActivityKind.LATIN_DANCE),
+    BALLET(0x36, ActivityKind.BALLET),
+    STREET_DANCE(0x34, ActivityKind.STREET_DANCE),
+    ZUMBA(0x9B, ActivityKind.ZUMBA),
+    OTHER_DANCE(0x78, ActivityKind.DANCE),
+    // Leisure sports
+    ROLLER_SKATING(0x58, ActivityKind.ROLLER_SKATING),
+    MARTIAL_ARTS(0x38, ActivityKind.MARTIAL_ARTS),
+    TAI_CHI(0x1F, ActivityKind.TAI_CHI),
+    HULA_HOOPING(0x59, ActivityKind.HULA_HOOPING),
+    DISC_SPORTS(0x43, ActivityKind.DISC_SPORTS),
+    DARTS(0x5A, ActivityKind.DARTS),
+    ARCHERY(0x30, ActivityKind.ARCHERY),
+    HORSE_RIDING(0x1D, ActivityKind.HORSE_RIDING),
+    KITE_FLYING(0x70, ActivityKind.KITE_FLYING),
+    SWING(0x71, ActivityKind.SWING),
+    STAIRS(0x15, ActivityKind.STAIRS),
+    FISHING(0x42, ActivityKind.FISHING),
+    HAND_CYCLING(0x96, ActivityKind.HANDCYCLING),
+    MIND_AND_BODY(0x97, ActivityKind.MIND_AND_BODY),
+    WRESTLING(0x53, ActivityKind.WRESTLING),
+    KABADDI(0x9C, ActivityKind.KABADDI),
+    KARTING(0xA0, ActivityKind.KARTING),
+    // Ball sports
+    BADMINTON(0x09, ActivityKind.BADMINTON),
+    TABLE_TENNIS(0x0A, ActivityKind.TABLE_TENNIS),
+    TENNIS(0x0C, ActivityKind.TENNIS),
+    BILLIARDS(0x7C, ActivityKind.BILLIARDS),
+    BOWLING(0x3B, ActivityKind.BOWLING),
+    VOLLEYBALL(0x49, ActivityKind.VOLLEYBALL),
+    SHUTTLECOCK(0x20, ActivityKind.SHUTTLECOCK),
+    HANDBALL(0x39, ActivityKind.HANDBALL),
+    BASEBALL(0x3A, ActivityKind.BASEBALL),
+    SOFTBALL(0x55, ActivityKind.SOFTBALL),
+    CRICKET(0x0B, ActivityKind.CRICKET),
+    RUGBY(0x44, ActivityKind.RUGBY),
+    HOCKEY(0x1E, ActivityKind.HOCKEY),
+    SQUASH(0x3C, ActivityKind.SQUASH),
+    DODGEBALL(0x81, ActivityKind.DODGEBALL),
+    SOCCER(0x07, ActivityKind.SOCCER),
+    BASKETBALL(0x08, ActivityKind.BASKETBALL),
+    AUSTRALIAN_FOOTBALL(0x37, ActivityKind.AUSTRALIAN_FOOTBALL),
+    GOLF(0x45, ActivityKind.GOLF),
+    PICKLEBALL(0x5B, ActivityKind.PICKLEBALL),
+    LACROSS(0x98, ActivityKind.LACROSS),
+    SHOT(0x9D, ActivityKind.SHOT),
+    BEACH_SOCCER(0x7d, ActivityKind.BEACH_SOCCER),
+    BEACH_VOLLEYBALL(0x7e, ActivityKind.BEACH_VOLLEYBALL),
+    GATEBALL(0x7f, ActivityKind.GATEBALL),
+    SEPAK_TAKRAW(0x80, ActivityKind.SEPAK_TAKRAW),
+    // Water sports
+    SAILING(0x82, ActivityKind.SAILING),
+    SURFING(0x64, ActivityKind.SURFING),
+    JET_SKIING(0x87, ActivityKind.JET_SKIING),
+    // Snow sports
+    SKATING(0x4C, ActivityKind.SKATING),
+    ICE_HOCKEY(0x24, ActivityKind.ICE_HOCKEY),
+    CURLING(0x3D, ActivityKind.CURLING),
+    SNOWBOARDING(0x3E, ActivityKind.SNOWBOARDING),
+    CROSS_COUNTRY_SKIING(0x6E, ActivityKind.CROSS_COUNTRY_SKIING),
+    SNOW_SPORTS(0x48, ActivityKind.SNOW_SPORTS),
+    SKIING(0x22, ActivityKind.SKIING),
+    LUGE(0x8a, ActivityKind.LUGE),
+    // Extreme sports
+    SKATEBOARDING(0x60, ActivityKind.SKATEBOARDING),
+    ROCK_CLIMBING(0x69, ActivityKind.ROCK_CLIMBING),
+    HUNTING(0x93, ActivityKind.HUNTING),
+    // Other
+    PARACHUTING(0x8e, ActivityKind.PARACHUTING),
+    AUTO_RACING(0x8f, ActivityKind.AUTO_RACING),
+    PARKOUR(0x62, ActivityKind.PARKOUR),
+    ;
+
+    private final byte code;
+    private final ActivityKind activityKind;
+
+    CmfActivityType(final int code, final ActivityKind activityKind) {
+        this.code = (byte) code;
+        this.activityKind = activityKind;
+    }
+
+    public byte getCode() {
+        return code;
+    }
+
+    public ActivityKind getActivityKind() {
+        return activityKind;
+    }
+
+    @Nullable
+    public static CmfActivityType fromCode(final byte code) {
+        for (final CmfActivityType cmd : CmfActivityType.values()) {
+            if (cmd.getCode() == code) {
+                return cmd;
+            }
+        }
+
+        return null;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/cmfwatchpro/CmfCharacteristic.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/cmfwatchpro/CmfCharacteristic.java
new file mode 100644
index 0000000..dbeeebe
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/cmfwatchpro/CmfCharacteristic.java
@@ -0,0 +1,329 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.cmfwatchpro;
+
+import android.bluetooth.BluetoothGattCharacteristic;
+
+import androidx.annotation.Nullable;
+
+import org.apache.commons.lang3.ArrayUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.security.GeneralSecurityException;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Objects;
+import java.util.UUID;
+import java.util.zip.CRC32;
+
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.util.CryptoUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class CmfCharacteristic {
+    private final Logger LOG = LoggerFactory.getLogger(CmfCharacteristic.class);
+
+    private static final byte[] AES_IV = new byte[]{0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x5a};
+    private static final byte PAYLOAD_HEADER = (byte) 0xf5;
+
+    private final BluetoothGattCharacteristic bluetoothGattCharacteristic;
+    private final UUID characteristicUUID;
+
+    private final Handler handler;
+
+    private byte[] sessionKey;
+
+    private int mtu = 247;
+
+    private final Map<CmfCommand, ChunkBuffer> chunkBuffers = new HashMap<>();
+
+    public CmfCharacteristic(final BluetoothGattCharacteristic bluetoothGattCharacteristic,
+                             final Handler handler) {
+        this.bluetoothGattCharacteristic = bluetoothGattCharacteristic;
+        this.characteristicUUID = bluetoothGattCharacteristic.getUuid();
+        this.handler = handler;
+    }
+
+    public UUID getCharacteristicUUID() {
+        return characteristicUUID;
+    }
+
+    public void setSessionKey(final byte[] sessionKey) {
+        this.sessionKey = sessionKey;
+    }
+
+    public void setMtu(final int mtu) {
+        this.mtu = mtu;
+    }
+
+    public void sendCommand(final TransactionBuilder builder, final CmfCommand cmd, final byte[] payload) {
+        final byte[][] chunks;
+
+        final boolean encrypted = shouldEncrypt(cmd);
+        if (encrypted) {
+            chunks = makeChunksEncrypted(payload);
+        } else {
+            chunks = makeChunksPlaintext(payload);
+        }
+
+        if (chunks == null) {
+            // Something went wrong chunking - error was already printed
+            return;
+        }
+
+        LOG.debug(
+                "Send command: cmd={}{}",
+                cmd,
+                payload.length > 0 ? " payload=" + GB.hexdump(payload) : ""
+        );
+
+        for (int i = 0; i < chunks.length; i++) {
+            final byte[] chunk = chunks[i];
+
+            final ByteBuffer buf = ByteBuffer.allocate(chunk.length + 11).order(ByteOrder.BIG_ENDIAN);
+            buf.put(PAYLOAD_HEADER);
+            buf.putShort((short) chunk.length);
+            buf.putShort((short) cmd.getCmd1());
+            buf.putShort((short) chunks.length);
+            buf.putShort((short) (i + 1));
+            buf.putShort((short) cmd.getCmd2());
+            buf.put(chunk);
+
+            builder.write(bluetoothGattCharacteristic, buf.array());
+        }
+    }
+
+    private byte[][] makeChunksPlaintext(final byte[] payload) {
+        final int chunkSize = mtu - 20;
+        final int numChunks = (int) Math.ceil(payload.length / (float) chunkSize);
+        final byte[][] chunks = new byte[numChunks][];
+
+        final CRC32 crc = new CRC32();
+
+        for (int i = 0; i < numChunks; i++) {
+            final int startIdx = i * chunkSize;
+            final int endIdx = Math.min(startIdx + chunkSize, payload.length);
+            final byte[] chunk = ArrayUtils.subarray(payload, startIdx, endIdx);
+
+            crc.reset();
+            crc.update(chunk, 0, chunk.length);
+
+            chunks[i] = ArrayUtils.addAll(
+                    chunk,
+                    BLETypeConversions.fromUint32((int) crc.getValue())
+            );
+        }
+
+        return chunks;
+    }
+
+    @Nullable
+    private byte[][] makeChunksEncrypted(final byte[] payload) {
+        if (payload.length == 0) {
+            return new byte[1][0];
+        }
+
+        // AES will output 16-byte blocks, exclude the protocol overhead (11 bytes)
+        final int maxEncryptedPayloadSize = ((mtu - 11) / 16) * 16;
+        final int maxPayloadSize = maxEncryptedPayloadSize - 4 - 1; // exclude 4 bytes for crc and 1 byte of aes padding
+        final int numChunks = (int) Math.ceil(payload.length / (float) (maxPayloadSize));
+        final byte[][] chunks = new byte[numChunks][];
+
+        if (numChunks != 1) {
+            LOG.debug("Splitting payload into {} chunks of {} bytes", numChunks, maxPayloadSize);
+        }
+
+        final CRC32 crc = new CRC32();
+
+        for (int i = 0; i < numChunks; i++) {
+            final int startIdx = i * maxPayloadSize;
+            final int endIdx = Math.min(startIdx + maxPayloadSize, payload.length);
+            final byte[] chunk = ArrayUtils.subarray(payload, startIdx, endIdx);
+
+            crc.reset();
+            crc.update(chunk, 0, chunk.length);
+
+            final byte[] payloadToEncrypt = ArrayUtils.addAll(
+                    chunk,
+                    BLETypeConversions.fromUint32((int) crc.getValue())
+            );
+
+            try {
+                chunks[i] = CryptoUtils.encryptAES_CBC_Pad(payloadToEncrypt, sessionKey, AES_IV);
+            } catch (final GeneralSecurityException e) {
+                LOG.error("Failed to encrypt chunk", e);
+                return null;
+            }
+        }
+
+        return chunks;
+    }
+
+    private boolean shouldEncrypt(final CmfCommand cmd) {
+        switch (cmd) {
+            case AUTH_PAIR_REQUEST:
+            case AUTH_PAIR_REPLY:
+            case DATA_CHUNK_WRITE_AGPS:
+            case DATA_CHUNK_WRITE_WATCHFACE:
+                return false;
+        }
+
+        return true;
+    }
+
+    public void onCharacteristicChanged(final byte[] value) {
+        final ByteBuffer buf = ByteBuffer.wrap(value).order(ByteOrder.BIG_ENDIAN);
+
+        final byte header = buf.get();
+        if (header != PAYLOAD_HEADER) {
+            LOG.error("Unexpected first byte {}", String.format("0x%02x", header));
+            return;
+        }
+
+        final int payloadLength = buf.getShort();
+        final int cmd1 = buf.getShort() & 0xFFFF;
+        final int chunkCount = buf.getShort();
+        final int chunkIndex = buf.getShort();
+        final int cmd2 = buf.getShort() & 0xFFFF;
+
+        final CmfCommand cmd = CmfCommand.fromCodes(cmd1, cmd2);
+
+        final byte[] payload;
+        if (payloadLength > 0) {
+            try {
+                if (cmd == null || shouldEncrypt(cmd)) {
+                    final byte[] encryptedPayload = new byte[payloadLength];
+                    buf.get(encryptedPayload);
+
+                    final byte[] decryptedPayload = CryptoUtils.decryptAES_CBC_Pad(encryptedPayload, sessionKey, AES_IV);
+                    payload = ArrayUtils.subarray(decryptedPayload, 0, decryptedPayload.length - 4);
+                    final int expectedCrc = BLETypeConversions.toUint32(decryptedPayload, decryptedPayload.length - 4);
+                    final CRC32 crc = new CRC32();
+                    crc.update(payload, 0, payload.length);
+                    final int actualCrc = (int) crc.getValue();
+                    if (actualCrc != expectedCrc) {
+                        LOG.error("Payload CRC mismatch for {}: got {}, expected {}", cmd, String.format("%08X", actualCrc), String.format("%08X", expectedCrc));
+                        if (chunkCount > 1) {
+                            chunkBuffers.remove(cmd);
+                        }
+                        return;
+                    }
+                } else {
+                    // Plaintext payload - it does not have the crc, but the length still includes it (?)
+                    payload = new byte[buf.limit() - buf.position()];
+                    buf.get(payload);
+                }
+            } catch (final GeneralSecurityException e) {
+                LOG.error("Failed to decrypt payload for {} ({}/{})", cmd, String.format("0x%04x", cmd1), String.format("0x%04x", cmd2), e);
+                if (cmd == CmfCommand.AUTH_FAILED) {
+                    handler.onCommand(cmd, new byte[0]);
+                }
+                if (chunkCount > 1) {
+                    chunkBuffers.remove(cmd);
+                }
+                return;
+            }
+        } else {
+            payload = new byte[0];
+        }
+
+        LOG.debug(
+                "Got {}: {}{}",
+                chunkCount > 1 ? String.format(Locale.ROOT, "chunk %d/%d", chunkIndex, chunkCount) : "command",
+                cmd != null ? String.format("cmd=%s", cmd) : String.format("cmd1=0x%04x cmd2=0x%04x", cmd1, cmd2),
+                payload.length > 0 ? " payload=" + GB.hexdump(payload) : ""
+        );
+
+        if (cmd == null) {
+            // Just ignore unknown commands
+            LOG.warn("Unknown command cmd1={} cmd2={}", String.format("0x%04x", cmd1), String.format("0x%04x", cmd2));
+            return;
+        }
+
+        final byte[] fullPayload;
+        if (chunkCount == 1) {
+            // Single-chunk payload - just pass it through
+            fullPayload = payload;
+        } else {
+            final ChunkBuffer buffer;
+            if (chunkBuffers.containsKey(cmd)) {
+                buffer = Objects.requireNonNull(chunkBuffers.get(cmd));
+            } else {
+                buffer = new ChunkBuffer();
+                chunkBuffers.put(cmd, buffer);
+            }
+
+            if (chunkIndex != buffer.expectedChunk) {
+                LOG.warn("Got unexpected chunk, expected {}", buffer.expectedChunk);
+
+                if (chunkIndex != 1) {
+                    // This chunk is not the first one and we got out of sync - ignore it and do not proceed
+                    return;
+                }
+
+                // Just discard whatever we had and start over
+                buffer.baos.reset();
+            }
+
+            try {
+                buffer.baos.write(payload);
+            } catch (final IOException e) {
+                LOG.error("Failed to write payload to chunk buffer", e);
+                return;
+            }
+
+            buffer.expectedChunk = chunkIndex + 1;
+
+            if (chunkIndex != chunkCount) {
+                // Chunk buffer not full yet
+                return;
+            }
+
+            LOG.debug("Got all {} chunks for {}", chunkCount, cmd);
+
+            fullPayload = buffer.baos.toByteArray().clone();
+            chunkBuffers.remove(cmd);
+        }
+
+        if (handler == null) {
+            LOG.error("Handler is null for {}", characteristicUUID);
+            return;
+        }
+
+        try {
+            handler.onCommand(cmd, fullPayload);
+        } catch (final Exception e) {
+            LOG.error("Exception while handling command", e);
+        }
+    }
+
+    public interface Handler {
+        void onCommand(CmfCommand cmd, byte[] payload);
+    }
+
+    private static class ChunkBuffer {
+        private int expectedChunk = 1;
+        private final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/cmfwatchpro/CmfCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/cmfwatchpro/CmfCommand.java
new file mode 100644
index 0000000..c1ebe01
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/cmfwatchpro/CmfCommand.java
@@ -0,0 +1,125 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.cmfwatchpro;
+
+import androidx.annotation.Nullable;
+
+public enum CmfCommand {
+    ACTIVITY_DATA(0x0056, 0x0001),
+    ACTIVITY_FETCH_1(0xffff, 0x8005),
+    ACTIVITY_FETCH_2(0xffff, 0x9057),
+    ACTIVITY_FETCH_ACK_1(0xffff, 0x0005),
+    ACTIVITY_FETCH_ACK_2(0xffff, 0xa057),
+    ALARMS_GET(0x0063, 0x0002),
+    ALARMS_SET(0x0063, 0x0001),
+    APP_NOTIFICATION(0x0065, 0x0001),
+    AUTH_NONCE_REPLY(0xffff, 0x004c),
+    AUTH_NONCE_REQUEST(0xffff, 0x804b),
+    AUTH_PAIR_REPLY(0xffff, 0x0048),
+    AUTH_PAIR_REQUEST(0xffff, 0x8047),
+    AUTH_PHONE_NAME(0xffff, 0x8049),
+    AUTH_WATCH_MAC(0xffff, 0x0049),
+    AUTH_FAILED(0xffff, 0xa061),
+    AUTHENTICATED_CONFIRM_REPLY(0xffff, 0x0004),
+    AUTHENTICATED_CONFIRM_REQUEST(0xffff, 0x804d),
+    BATTERY(0x005c, 0x0001),
+    CALL_REMINDER(0xffff, 0x9066),
+    CONTACTS_GET(0x00d5, 0x0002),
+    CONTACTS_SET(0x00d5, 0x0001),
+    DATA_CHUNK_REQUEST_AGPS(0xffff, 0xa05f),
+    DATA_CHUNK_REQUEST_WATCHFACE(0xffff, 0xa064),
+    DATA_CHUNK_WRITE_AGPS(0xffff, 0x905f),
+    DATA_CHUNK_WRITE_WATCHFACE(0xffff, 0x9064),
+    DATA_TRANSFER_AGPS_FINISH_ACK_1(0xffff, 0xa060),
+    DATA_TRANSFER_AGPS_FINISH_ACK_2(0xffff, 0x9060),
+    DATA_TRANSFER_AGPS_INIT_REPLY(0xffff, 0xa05e),
+    DATA_TRANSFER_AGPS_INIT_REQUEST(0xffff, 0x905e),
+    DATA_TRANSFER_WATCHFACE_FINISH_ACK_1(0xffff, 0xa065),
+    DATA_TRANSFER_WATCHFACE_FINISH_ACK_2(0xffff, 0x9065),
+    DATA_TRANSFER_WATCHFACE_INIT_1_REQUEST(0xffff, 0x8052),
+    DATA_TRANSFER_WATCHFACE_INIT_1_REPLY(0xffff, 0x0052),
+    DATA_TRANSFER_WATCHFACE_INIT_2_REPLY(0xffff, 0xa063),
+    DATA_TRANSFER_WATCHFACE_INIT_2_REQUEST(0xffff, 0x9063),
+    DO_NOT_DISTURB(0x0099, 0x0001),
+    FACTORY_RESET(0x009a, 0x0001),
+    FIND_PHONE(0x005b, 0x0001),
+    FIND_WATCH(0x005d, 0x0001),
+    FIRMWARE_VERSION_GET(0xffff, 0x8006),
+    FIRMWARE_VERSION_RET(0xffff, 0x0006),
+    GOALS_SET(0x005e, 0x0001),
+    GPS_COORDS(0xffff, 0x906a),
+    HEART_MONITORING_ALERTS(0xffff, 0x9059),
+    HEART_MONITORING_ENABLED_GET(0x009b, 0x0002),
+    HEART_MONITORING_ENABLED_SET(0x009b, 0x0001),
+    HEART_RATE_RESTING(0x00da, 0x0001),
+    HEART_RATE_MANUAL_AUTO(0x0053, 0x0001),
+    HEART_RATE_WORKOUT(0x00e0, 0x0001),
+    LANGUAGE_RET(0xffff, 0xa06b),
+    LANGUAGE_SET(0xffff, 0x9058),
+    MUSIC_BUTTON(0xffff, 0xa05d),
+    MUSIC_INFO_ACK(0xffff, 0xa05c),
+    MUSIC_INFO_SET(0xffff, 0x905c),
+    SERIAL_NUMBER_GET(0x00de, 0x0002),
+    SERIAL_NUMBER_RET(0x00de, 0x0001),
+    SLEEP_DATA(0x0058, 0x0001),
+    SPO2(0x0055, 0x0001),
+    SPORTS_SET(0x00dc, 0x0001),
+    STANDING_REMINDER_GET(0x0060, 0x0002),
+    STANDING_REMINDER_SET(0x0060, 0x0001),
+    STRESS(0x009d, 0x0001),
+    TIME_FORMAT(0x005f, 0x0001),
+    TIME(0xffff, 0x8004),
+    TRIGGER_SYNC(0x005c, 0x0002),
+    UNIT_LENGTH(0xffff, 0x9067),
+    UNIT_TEMPERATURE(0xffff, 0x9068),
+    WAKE_ON_WRIST_RAISE(0x0062, 0x0001),
+    WATCHFACE(0x009f, 0x0001),
+    WATER_REMINDER_GET(0x0061, 0x0002),
+    WATER_REMINDER_SET(0x0061, 0x0001),
+    WEATHER_SET_1(0xffff, 0x906b),
+    WEATHER_SET_2(0x0066, 0x0001),
+    WORKOUT_GPS(0xffff, 0xa05a),
+    WORKOUT_SUMMARY(0x0057, 0x0001),
+    ;
+
+    private final int cmd1;
+    private final int cmd2;
+
+    CmfCommand(final int cmd1, final int cmd2) {
+        this.cmd1 = cmd1;
+        this.cmd2 = cmd2;
+    }
+
+    public int getCmd1() {
+        return cmd1;
+    }
+
+    public int getCmd2() {
+        return cmd2;
+    }
+
+    @Nullable
+    public static CmfCommand fromCodes(final int cmd1, final int cmd2) {
+        for (final CmfCommand cmd : CmfCommand.values()) {
+            if (cmd.getCmd1() == cmd1 && cmd.getCmd2() == cmd2) {
+                return cmd;
+            }
+        }
+
+        return null;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/cmfwatchpro/CmfDataUploader.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/cmfwatchpro/CmfDataUploader.java
new file mode 100644
index 0000000..9e45711
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/cmfwatchpro/CmfDataUploader.java
@@ -0,0 +1,198 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.cmfwatchpro;
+
+import android.net.Uri;
+
+import org.apache.commons.lang3.ArrayUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Random;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetProgressAction;
+
+public class CmfDataUploader implements CmfCharacteristic.Handler {
+    private static final Logger LOG = LoggerFactory.getLogger(CmfWatchProSupport.class);
+
+    private final CmfWatchProSupport mSupport;
+
+    private CmfFwHelper fwHelper;
+
+    public CmfDataUploader(final CmfWatchProSupport support) {
+        this.mSupport = support;
+    }
+
+    @Override
+    public void onCommand(final CmfCommand cmd, final byte[] payload) {
+        switch (cmd) {
+            case DATA_TRANSFER_WATCHFACE_INIT_1_REPLY:
+                if (payload[0] != 0x01) {
+                    LOG.warn("Got unexpected transfer init 1 reply {}", payload[0]);
+                    fwHelper = null;
+                    return;
+                }
+
+                final ByteBuffer buf = ByteBuffer.allocate(9).order(ByteOrder.BIG_ENDIAN);
+                buf.put((byte) (0xa5));
+                buf.putInt(fwHelper.getBytes().length);
+                buf.putInt(new Random().nextInt()); // FIXME watchface ID?
+
+                mSupport.sendData(
+                        "transfer watchface init 2 request",
+                        CmfCommand.DATA_TRANSFER_WATCHFACE_INIT_2_REQUEST,
+                        buf.array()
+                );
+                return;
+            case DATA_TRANSFER_AGPS_INIT_REPLY:
+            case DATA_TRANSFER_WATCHFACE_INIT_2_REPLY:
+                if (payload[0] != 0x01) {
+                    LOG.warn("Got unexpected transfer 2 init reply {}", payload[0]);
+                    fwHelper = null;
+                    return;
+                }
+
+                setDeviceBusy();
+                updateProgress(0, true);
+
+                return;
+            case DATA_TRANSFER_WATCHFACE_FINISH_ACK_1:
+                handleAck1(CmfCommand.DATA_TRANSFER_WATCHFACE_FINISH_ACK_2, payload);
+                return;
+            case DATA_TRANSFER_AGPS_FINISH_ACK_1:
+                handleAck1(CmfCommand.DATA_TRANSFER_AGPS_FINISH_ACK_2, payload);
+                return;
+            case DATA_CHUNK_REQUEST_AGPS:
+                if (fwHelper == null || !fwHelper.isAgps()) {
+                    LOG.warn("We are not sending AGPS - refusing request");
+                    return;
+                }
+                handleChunkRequest(CmfCommand.DATA_CHUNK_REQUEST_AGPS, payload);
+                return;
+            case DATA_CHUNK_REQUEST_WATCHFACE:
+                if (fwHelper == null || !fwHelper.isWatchface()) {
+                    LOG.warn("We are not sending a watchface - refusing request");
+                    return;
+                }
+                handleChunkRequest(CmfCommand.DATA_CHUNK_WRITE_WATCHFACE, payload);
+                return;
+        }
+
+        LOG.warn("Got unknown data command {}", cmd);
+    }
+
+    public void onInstallApp(final Uri uri) {
+        if (fwHelper != null) {
+            LOG.warn("Already installing {}", fwHelper.getUri());
+            return;
+        }
+
+        fwHelper = new CmfFwHelper(uri, mSupport.getContext());
+        if (!fwHelper.isValid()) {
+            LOG.warn("Uri {} is not valid", uri);
+            fwHelper = null;
+            return;
+        }
+
+        if (fwHelper.isWatchface()) {
+            mSupport.sendData(
+                    "transfer watchface init request",
+                    CmfCommand.DATA_TRANSFER_WATCHFACE_INIT_1_REQUEST,
+                    (byte) 0xa5
+            );
+
+            return;
+        }
+
+        LOG.warn("Unsupported fwHelper for {}", fwHelper.getUri());
+        fwHelper = null;
+    }
+
+    private void handleChunkRequest(final CmfCommand commandReply, final byte[] payload) {
+        final ByteBuffer buf = ByteBuffer.wrap(payload).order(ByteOrder.BIG_ENDIAN);
+        final int offset = buf.getInt();
+        final int length = buf.getInt();
+        final int progress = buf.get();
+
+        LOG.debug("Got chunk request: offset={}, length={}, progress={}", offset, length, progress);
+
+        final TransactionBuilder builder = mSupport.createTransactionBuilder("send chunk offset " + offset);
+        updateProgress(builder, progress, true);
+        mSupport.sendData(
+                "transfer watchface init request",
+                commandReply,
+                ArrayUtils.subarray(fwHelper.getBytes(), offset, offset + length)
+        );
+    }
+
+    private void handleAck1(final CmfCommand commandReply, final byte[] payload) {
+        if (payload[0] != 0x01) {
+            LOG.warn("Got unexpected transfer finish reply {}", payload[0]);
+            fwHelper = null;
+        }
+
+        LOG.debug("Got transfer finish ack 1");
+
+        unsetDeviceBusy();
+        updateProgress(100, false);
+        mSupport.sendData("transfer finish", commandReply, (byte) 0xa5);
+}
+
+    private void updateProgress(final int progressPercent, boolean ongoing) {
+        final TransactionBuilder builder = mSupport.createTransactionBuilder("update data upload progress to " + progressPercent);
+        updateProgress(builder, progressPercent, ongoing);
+        builder.queue(mSupport.getQueue());
+    }
+
+    private void updateProgress(final TransactionBuilder builder, final int progressPercent, boolean ongoing) {
+        final int uploadMessage;
+        if (fwHelper != null && fwHelper.isWatchface()) {
+            uploadMessage = R.string.uploading_watchface;
+        } else {
+            uploadMessage = R.string.updating_firmware;
+        }
+
+        builder.add(new SetProgressAction(
+                mSupport.getContext().getString(uploadMessage),
+                ongoing,
+                progressPercent,
+                mSupport.getContext()
+        ));
+    }
+
+    private void setDeviceBusy() {
+        final GBDevice device = mSupport.getDevice();
+        device.setBusyTask(mSupport.getContext().getString(R.string.updating_firmware));
+        device.sendDeviceUpdateIntent(mSupport.getContext());
+    }
+
+    private void unsetDeviceBusy() {
+        final GBDevice device = mSupport.getDevice();
+        if (device != null && device.isConnected()) {
+            if (device.isBusy()) {
+                device.unsetBusyTask();
+                device.sendDeviceUpdateIntent(mSupport.getContext());
+            }
+            device.sendDeviceUpdateIntent(mSupport.getContext());
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/cmfwatchpro/CmfFwHelper.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/cmfwatchpro/CmfFwHelper.java
new file mode 100644
index 0000000..95c5ceb
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/cmfwatchpro/CmfFwHelper.java
@@ -0,0 +1,187 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.cmfwatchpro;
+
+import android.content.Context;
+import android.net.Uri;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import nodomain.freeyourgadget.gadgetbridge.util.ArrayUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.UriHelper;
+
+public class CmfFwHelper {
+    private static final Logger LOG = LoggerFactory.getLogger(CmfFwHelper.class);
+
+    private static final byte[] HEADER_WATCHFACE = new byte[]{0x01, 0x00, 0x00, 0x02};
+    private static final byte[] HEADER_FIRMWARE = new byte[]{'A', 'O', 'T', 'A'};
+    private static final byte[] HEADER_AGPS = new byte[]{0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31, 0x30, 0x30, 0x30, 0x30};
+
+    private final Uri uri;
+    private byte[] fw;
+    private boolean typeFirmware;
+    private boolean typeWatchface;
+    private boolean typeAgps;
+
+    private String name;
+    private String version;
+
+    public CmfFwHelper(final Uri uri, final Context context) {
+        this.uri = uri;
+
+        final UriHelper uriHelper;
+        try {
+            uriHelper = UriHelper.get(uri, context);
+        } catch (final IOException e) {
+            LOG.error("Failed to get uri helper for {}", uri, e);
+            return;
+        }
+
+        final int maxExpectedFileSize = 1024 * 1024 * 32; // 32MB
+
+        if (uriHelper.getFileSize() > maxExpectedFileSize) {
+            LOG.warn("File size is larger than the maximum expected file size of {}", maxExpectedFileSize);
+            return;
+        }
+
+        try (final InputStream in = new BufferedInputStream(uriHelper.openInputStream())) {
+            this.fw = FileUtils.readAll(in, maxExpectedFileSize);
+        } catch (final IOException e) {
+            LOG.error("Failed to read bytes from {}", uri, e);
+            return;
+        }
+
+        parseBytes();
+    }
+
+    public Uri getUri() {
+        return uri;
+    }
+
+    public boolean isValid() {
+        return isWatchface() || isFirmware() || isAgps();
+    }
+
+    public boolean isWatchface() {
+        return typeWatchface;
+    }
+
+    public boolean isFirmware() {
+        return typeFirmware;
+    }
+
+    public boolean isAgps() {
+        return typeAgps;
+    }
+
+    public String getDetails() {
+        return name != null ? name : (version != null ? version : "UNKNOWN");
+    }
+
+    public byte[] getBytes() {
+        return fw;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public String getVersion() {
+        return version;
+    }
+
+    public void unsetFwBytes() {
+        this.fw = null;
+    }
+
+    private void parseBytes() {
+        if (parseAsWatchface()) {
+            assert name != null;
+            typeWatchface = true;
+        } else if (parseAsFirmware()) {
+            assert version != null;
+            typeFirmware = true;
+        } else if (parseAsAgps()) {
+            typeAgps = true;
+        }
+    }
+
+    private boolean parseAsWatchface() {
+        if (!ArrayUtils.equals(fw, HEADER_WATCHFACE, 4)) {
+            LOG.warn("File header not a watchface");
+            return false;
+        }
+
+        final String nameHeader = StringUtils.untilNullTerminator(fw, 8);
+        if (nameHeader == null) {
+            LOG.warn("watchface name not found in {}", uri);
+            return false;
+        }
+
+        // Confirm it's a watchface by finding the same name at the end
+        final String nameTrailer = StringUtils.untilNullTerminator(fw, fw.length - 28);
+        if (nameTrailer == null) {
+            LOG.warn("watchface name not found at the end of {}", uri);
+            return false;
+        }
+
+        if (!nameHeader.equals(nameTrailer)) {
+            LOG.warn("Names in header and trailer do not match");
+            return false;
+        }
+
+        name = nameHeader;
+
+        return true;
+    }
+
+    private boolean parseAsFirmware() {
+        if (!ArrayUtils.equals(fw, HEADER_FIRMWARE, 0)) {
+            LOG.warn("File header not a firmware");
+            return false;
+        }
+
+        // FIXME: This is not really the version, but build number?
+        final String versionHeader = StringUtils.untilNullTerminator(fw, 64);
+        if (versionHeader == null) {
+            LOG.warn("firmware version not found in {}", uri);
+            return false;
+        }
+
+        version = versionHeader;
+
+        return true;
+    }
+
+    private boolean parseAsAgps() {
+        if (!ArrayUtils.equals(fw, HEADER_AGPS, 0)) {
+            LOG.warn("File header not agps");
+            return false;
+        }
+
+        // TODO parse? and set something
+
+        return true;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/cmfwatchpro/CmfInstallHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/cmfwatchpro/CmfInstallHandler.java
new file mode 100644
index 0000000..0c9c124
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/cmfwatchpro/CmfInstallHandler.java
@@ -0,0 +1,90 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.cmfwatchpro;
+
+import android.content.Context;
+import android.net.Uri;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.InstallActivity;
+import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.GenericItem;
+
+public class CmfInstallHandler implements InstallHandler {
+    protected final Uri mUri;
+    protected final Context mContext;
+    protected final CmfFwHelper helper;
+
+    public CmfInstallHandler(final Uri uri, final Context context) {
+        this.mUri = uri;
+        this.mContext = context;
+        this.helper = new CmfFwHelper(uri, context);
+    }
+
+    @Override
+    public boolean isValid() {
+        return helper.isValid();
+    }
+
+    @Override
+    public void validateInstallation(final InstallActivity installActivity, final GBDevice device) {
+        if (device.isBusy()) {
+            installActivity.setInfoText(device.getBusyTask());
+            installActivity.setInstallEnabled(false);
+            return;
+        }
+
+        if (!device.isInitialized()) {
+            installActivity.setInfoText(mContext.getString(R.string.fwapp_install_device_not_ready));
+            installActivity.setInstallEnabled(false);
+            return;
+        }
+
+        if (!helper.isValid()) {
+            installActivity.setInfoText(mContext.getString(R.string.fwapp_install_device_not_supported));
+            installActivity.setInstallEnabled(false);
+            return;
+        }
+
+        final GenericItem installItem = new GenericItem();
+        if (helper.isWatchface()) {
+            installItem.setIcon(R.drawable.ic_watchface);
+            installItem.setName(mContext.getString(R.string.kind_watchface));
+        } else if (helper.isFirmware()) {
+            installItem.setIcon(R.drawable.ic_firmware);
+            installItem.setName(mContext.getString(R.string.kind_firmware));
+        } else if (helper.isAgps()) {
+            installItem.setIcon(R.drawable.ic_firmware);
+            installItem.setName(mContext.getString(R.string.kind_agps_bundle));
+        } else {
+            installItem.setIcon(R.drawable.ic_device_unknown);
+            installItem.setName(mContext.getString(R.string.kind_invalid));
+        }
+
+        installItem.setDetails(helper.getDetails());
+
+        installActivity.setInfoText(mContext.getString(R.string.firmware_install_warning, "(unknown)"));
+        installActivity.setInstallItem(installItem);
+        installActivity.setInstallEnabled(true);
+    }
+
+    @Override
+    public void onStartInstall(final GBDevice device) {
+        helper.unsetFwBytes(); // free up memory
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/cmfwatchpro/CmfNotificationIcon.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/cmfwatchpro/CmfNotificationIcon.java
new file mode 100644
index 0000000..a03e97a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/cmfwatchpro/CmfNotificationIcon.java
@@ -0,0 +1,75 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.cmfwatchpro;
+
+import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
+
+public enum CmfNotificationIcon {
+    GENERIC_SMS(0),
+    WHATSAPP(8),
+    SNAPCHAT(9),
+    WHATSAPP_BUSINESS(10),
+    TRUECALLER(11), // blue phone
+    TELEGRAM(12),
+    FACEBOOK_MESSENGER(13),
+    IMO(14),
+    CALLAPP(15),
+    FACEBOOK(17),
+    INSTAGRAM(18),
+    TIKTOK(19),
+    LINE(20),
+    DISCORD(21),
+    GOOGLE_VOICE(22),
+    GMAIL(27),
+    OUTLOOK(29),
+    UNKNOWN(255),
+    ;
+
+    private final byte code;
+
+    CmfNotificationIcon(final int code) {
+        this.code = (byte) code;
+    }
+
+    public byte getCode() {
+        return code;
+    }
+
+    public static CmfNotificationIcon forNotification(final NotificationSpec notificationSpec) {
+        if (notificationSpec.type == null) {
+            return UNKNOWN;
+        }
+
+        try {
+            // If there's a matching enum, just return it
+            return CmfNotificationIcon.valueOf(notificationSpec.type.name());
+        } catch (final IllegalArgumentException ignored) {
+            // ignored
+        }
+
+        switch (notificationSpec.type.getGenericType()) {
+            case "generic_chat":
+                return GENERIC_SMS;
+            case "generic_email":
+                return GMAIL;
+            case "generic_phone":
+                return TRUECALLER;
+        }
+
+        return UNKNOWN;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/cmfwatchpro/CmfPreferences.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/cmfwatchpro/CmfPreferences.java
new file mode 100644
index 0000000..348ec82
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/cmfwatchpro/CmfPreferences.java
@@ -0,0 +1,391 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.cmfwatchpro;
+
+import android.content.Context;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+import nodomain.freeyourgadget.gadgetbridge.activities.SettingsActivity;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.util.GBPrefs;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+public class CmfPreferences {
+    private static final Logger LOG = LoggerFactory.getLogger(CmfPreferences.class);
+
+    private final CmfWatchProSupport mSupport;
+
+    protected CmfPreferences(final CmfWatchProSupport support) {
+        this.mSupport = support;
+    }
+
+    protected void onSetHeartRateMeasurementInterval(final int seconds) {
+        final boolean enabled = seconds == -1;
+        LOG.debug("Set HR smart monitoring = {}", enabled);
+
+        final byte[] cmd = new byte[]{0x01, (byte) (enabled ? 0x01 : 0x00)};
+        mSupport.sendCommand("set hr monitoring", CmfCommand.HEART_MONITORING_ENABLED_SET, cmd);
+    }
+
+    protected void onSendConfiguration(final String config) {
+        switch (config) {
+            case ActivityUser.PREF_USER_STEPS_GOAL:
+            case ActivityUser.PREF_USER_DISTANCE_METERS:
+            case ActivityUser.PREF_USER_CALORIES_BURNT:
+                setGoals();
+                return;
+            case SettingsActivity.PREF_MEASUREMENT_SYSTEM:
+                setMeasurementSystem();
+                return;
+            case DeviceSettingsPreferenceConst.PREF_LANGUAGE:
+                setLanguage();
+                return;
+            case DeviceSettingsPreferenceConst.PREF_TIMEFORMAT:
+                setTimeFormat();
+                return;
+            case DeviceSettingsPreferenceConst.PREF_LIFTWRIST_NOSHED:
+                setDisplayOnLift();
+                return;
+            case DeviceSettingsPreferenceConst.PREF_HEARTRATE_ALERT_LOW_THRESHOLD:
+            case DeviceSettingsPreferenceConst.PREF_HEARTRATE_ALERT_HIGH_THRESHOLD:
+            case DeviceSettingsPreferenceConst.PREF_HEARTRATE_ALERT_ACTIVE_HIGH_THRESHOLD:
+            case DeviceSettingsPreferenceConst.PREF_SPO2_LOW_ALERT_THRESHOLD:
+                setHeartAlerts();
+                return;
+            case DeviceSettingsPreferenceConst.PREF_SPO2_ALL_DAY_MONITORING:
+                setSpo2MonitoringInterval();
+                return;
+            case DeviceSettingsPreferenceConst.PREF_HEARTRATE_STRESS_MONITORING:
+                setStressMonitoringInterval();
+                return;
+            case DeviceSettingsPreferenceConst.PREF_INACTIVITY_ENABLE:
+            case DeviceSettingsPreferenceConst.PREF_INACTIVITY_THRESHOLD:
+            case DeviceSettingsPreferenceConst.PREF_INACTIVITY_START:
+            case DeviceSettingsPreferenceConst.PREF_INACTIVITY_END:
+            case DeviceSettingsPreferenceConst.PREF_INACTIVITY_DND:
+            case DeviceSettingsPreferenceConst.PREF_INACTIVITY_DND_START:
+            case DeviceSettingsPreferenceConst.PREF_INACTIVITY_DND_END:
+                setStandingReminder();
+            case DeviceSettingsPreferenceConst.PREF_HYDRATION_SWITCH:
+            case DeviceSettingsPreferenceConst.PREF_HYDRATION_PERIOD:
+            case DeviceSettingsPreferenceConst.PREF_HYDRATION_DND:
+            case DeviceSettingsPreferenceConst.PREF_HYDRATION_DND_START:
+            case DeviceSettingsPreferenceConst.PREF_HYDRATION_DND_END:
+                setHydrationReminder();
+                return;
+            case HuamiConst.PREF_WORKOUT_ACTIVITY_TYPES_SORTABLE:
+                setActivityTypes();
+                return;
+            // TODO call reminders
+        }
+
+        LOG.warn("Unknown config changed: {}", config);
+    }
+
+    private void setGoals() {
+        final ActivityUser activityUser = new ActivityUser();
+
+        if (activityUser.getStepsGoal() <= 0) {
+            LOG.warn("Invalid steps goal {}", activityUser.getStepsGoal());
+            return;
+        }
+
+        if (activityUser.getDistanceGoalMeters() <= 0) {
+            LOG.warn("Invalid distance goal {}", activityUser.getDistanceGoalMeters());
+            return;
+        }
+
+        if (activityUser.getCaloriesBurntGoal() <= 0) {
+            LOG.warn("Invalid calories goal {}", activityUser.getCaloriesBurntGoal());
+            return;
+        }
+
+        LOG.debug(
+                "Setting goals, steps={}, distance={}, calories={}",
+                activityUser.getStepsGoal(),
+                activityUser.getDistanceGoalMeters(),
+                activityUser.getCaloriesBurntGoal()
+        );
+
+        final ByteBuffer buf = ByteBuffer.allocate(10).order(ByteOrder.BIG_ENDIAN);
+
+        buf.put((byte) 0); // ?
+        buf.put((byte) 0); // ?
+        buf.putShort((short) activityUser.getStepsGoal());
+        buf.put((byte) 0); // ?
+        buf.put((byte) 0); // ?
+        buf.putShort((short) activityUser.getDistanceGoalMeters());
+        buf.putShort((short) activityUser.getCaloriesBurntGoal());
+
+        mSupport.sendCommand("set goals", CmfCommand.GOALS_SET, buf.array());
+    }
+
+    private void setMeasurementSystem() {
+        final Prefs prefs = mSupport.getDevicePrefs();
+        final String measurementSystem = prefs.getString(SettingsActivity.PREF_MEASUREMENT_SYSTEM, "metric");
+
+        LOG.debug("Setting measurement system to {}", measurementSystem);
+
+        final byte unitByte = (byte) ("metric".equals(measurementSystem) ? 0x00 : 0x01);
+
+        final byte[] cmd = new byte[]{0x01, unitByte};
+        final TransactionBuilder builder = mSupport.createTransactionBuilder("set measurement system");
+        mSupport.sendCommand(builder, CmfCommand.UNIT_LENGTH, cmd);
+        mSupport.sendCommand(builder, CmfCommand.UNIT_TEMPERATURE, cmd);
+        builder.queue(mSupport.getQueue());
+    }
+
+    private void setLanguage() {
+        String localeString = mSupport.getDevicePrefs().getString(
+                DeviceSettingsPreferenceConst.PREF_LANGUAGE, DeviceSettingsPreferenceConst.PREF_LANGUAGE_AUTO
+        );
+        if (DeviceSettingsPreferenceConst.PREF_LANGUAGE_AUTO.equals(localeString)) {
+            String language = Locale.getDefault().getLanguage();
+            String country = Locale.getDefault().getCountry();
+
+            if (nodomain.freeyourgadget.gadgetbridge.util.StringUtils.isNullOrEmpty(country)) {
+                // sometimes country is null, no idea why, guess it.
+                country = language;
+            }
+            localeString = (language + "_" + country).toLowerCase(Locale.ROOT);
+        }
+
+        String languageCommand = null;
+        if (LANGUAGES.containsKey(localeString)) {
+            languageCommand = localeString;
+        } else {
+            // Break down the language code and attempt to find it
+            final String[] languageParts = localeString.split("_");
+            for (int i = 0; i < languageParts.length; i++) {
+                if (LANGUAGES.containsKey(languageParts[0])) {
+                    languageCommand = languageParts[0];
+                    break;
+                }
+            }
+        }
+
+        if (languageCommand == null) {
+            LOG.warn("Unknown language {}", localeString);
+            return;
+        }
+
+        LOG.info("Set language: {} -> {}", localeString, languageCommand);
+
+        // FIXME watch ignores language?
+        mSupport.sendCommand("set language", CmfCommand.LANGUAGE_SET, languageCommand.getBytes());
+    }
+
+    private void setTimeFormat() {
+        final String timeFormat = mSupport.getDevicePrefs().getTimeFormat();
+
+        LOG.info("Setting time format to {}", timeFormat);
+
+        final byte timeFormatByte = (byte) (timeFormat.equals("24h") ? 0x00 : 0x01);
+
+        mSupport.sendCommand("set time format", CmfCommand.TIME_FORMAT, timeFormatByte);
+    }
+
+    private void setDisplayOnLift() {
+        final Prefs prefs = mSupport.getDevicePrefs();
+
+        boolean enabled = prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_LIFTWRIST_NOSHED, false);
+
+        mSupport.sendCommand("set display on lift", CmfCommand.WAKE_ON_WRIST_RAISE, (byte) (enabled ? 0x01 : 0x00));
+    }
+
+    private void setHeartAlerts() {
+        final Prefs prefs = mSupport.getDevicePrefs();
+
+        final int hrAlertActiveHigh = prefs.getInt(DeviceSettingsPreferenceConst.PREF_HEARTRATE_ALERT_ACTIVE_HIGH_THRESHOLD, 0);
+        final int hrAlertHigh = prefs.getInt(DeviceSettingsPreferenceConst.PREF_HEARTRATE_ALERT_HIGH_THRESHOLD, 0);
+        final int hrAlertLow = prefs.getInt(DeviceSettingsPreferenceConst.PREF_HEARTRATE_ALERT_LOW_THRESHOLD, 0);
+        final int spo2alert = prefs.getInt(DeviceSettingsPreferenceConst.PREF_SPO2_LOW_ALERT_THRESHOLD, 0);
+
+        final ByteBuffer buf;
+        if (hrAlertActiveHigh == 0 && hrAlertHigh == 0 && hrAlertLow == 0 && spo2alert == 0) {
+            buf = ByteBuffer.allocate(1).order(ByteOrder.BIG_ENDIAN);
+            buf.put((byte) 0x00);
+        } else {
+            buf = ByteBuffer.allocate(9).order(ByteOrder.BIG_ENDIAN);
+            buf.put((byte) 0x01);
+            buf.put((byte) hrAlertLow);
+            buf.put((byte) (hrAlertHigh != 0 ? hrAlertHigh : 255));
+            buf.put((byte) (hrAlertActiveHigh != 0 ? hrAlertActiveHigh : 255));
+            buf.put((byte) spo2alert);
+            buf.put((byte) 0x00); // ?
+            buf.put((byte) 0x00); // ?
+            buf.put((byte) 0x00); // ?
+            buf.put((byte) 0x00); // ?
+        }
+
+        mSupport.sendCommand("set heart monitoring alerts", CmfCommand.HEART_MONITORING_ALERTS, buf.array());
+    }
+
+    private void setSpo2MonitoringInterval() {
+        final Prefs prefs = mSupport.getDevicePrefs();
+        final boolean enabled = prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_SPO2_ALL_DAY_MONITORING, false);
+
+        LOG.debug("Set SpO2 monitoring = {}", enabled);
+
+        final byte[] cmd = new byte[]{0x02, (byte) (enabled ? 0x01 : 0x00)};
+        mSupport.sendCommand("set spo2 monitoring", CmfCommand.HEART_MONITORING_ENABLED_SET, cmd);
+    }
+
+    private void setStressMonitoringInterval() {
+        final Prefs prefs = mSupport.getDevicePrefs();
+        final boolean enabled = prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_HEARTRATE_STRESS_MONITORING, false);
+
+        LOG.debug("Set stress monitoring = {}", enabled);
+
+        final byte[] cmd = new byte[]{0x04, (byte) (enabled ? 0x01 : 0x00)};
+        mSupport.sendCommand("set stress monitoring", CmfCommand.HEART_MONITORING_ENABLED_SET, cmd);
+    }
+
+    private void setStandingReminder() {
+        final Prefs prefs = mSupport.getDevicePrefs();
+        final boolean enabled = prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_INACTIVITY_ENABLE, false);
+        final int threshold = prefs.getInt(DeviceSettingsPreferenceConst.PREF_INACTIVITY_THRESHOLD, 60);
+        final boolean dnd = prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_INACTIVITY_DND, false);
+        final Date dndStart = prefs.getTimePreference(DeviceSettingsPreferenceConst.PREF_INACTIVITY_DND_START, "12:00");
+        final Date dndEnd = prefs.getTimePreference(DeviceSettingsPreferenceConst.PREF_INACTIVITY_DND_END, "14:00");
+
+        final Calendar calendar = GregorianCalendar.getInstance();
+
+        if (threshold < 0 || threshold > 180) {
+            LOG.error("Invalid inactivity threshold: {}", threshold);
+            return;
+        }
+
+        final ByteBuffer buf = ByteBuffer.allocate(11).order(ByteOrder.BIG_ENDIAN);
+        buf.put((byte) (enabled ? 0x01 : 0x00));
+        buf.putShort((short) threshold);
+
+        if (enabled && dnd) {
+            calendar.setTime(dndStart);
+            buf.putInt((calendar.get(Calendar.HOUR_OF_DAY) * 3600 + calendar.get(Calendar.MINUTE) * 60));
+            calendar.setTime(dndEnd);
+            buf.putInt((calendar.get(Calendar.HOUR_OF_DAY) * 3600 + calendar.get(Calendar.MINUTE) * 60));
+        } else {
+            buf.putInt(0);
+            buf.putInt(0);
+        }
+
+        mSupport.sendCommand("set standing reminders", CmfCommand.STANDING_REMINDER_SET, buf.array());
+    }
+
+    private void setHydrationReminder() {
+        final Prefs prefs = mSupport.getDevicePrefs();
+        final boolean enabled = prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_HYDRATION_SWITCH, false);
+        final int threshold = prefs.getInt(DeviceSettingsPreferenceConst.PREF_HYDRATION_PERIOD, 60);
+        final boolean dnd = prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_HYDRATION_DND, false);
+        final Date dndStart = prefs.getTimePreference(DeviceSettingsPreferenceConst.PREF_HYDRATION_DND_START, "12:00");
+        final Date dndEnd = prefs.getTimePreference(DeviceSettingsPreferenceConst.PREF_HYDRATION_DND_END, "14:00");
+
+        final Calendar calendar = GregorianCalendar.getInstance();
+
+        if (threshold < 0 || threshold > 180) {
+            LOG.error("Invalid hydration threshold: {}", threshold);
+            return;
+        }
+
+        final ByteBuffer buf = ByteBuffer.allocate(11).order(ByteOrder.BIG_ENDIAN);
+        buf.put((byte) (enabled ? 0x01 : 0x00));
+        buf.putShort((short) threshold);
+
+        if (enabled && dnd) {
+            calendar.setTime(dndStart);
+            buf.putInt((calendar.get(Calendar.HOUR_OF_DAY) * 3600 + calendar.get(Calendar.MINUTE) * 60));
+            calendar.setTime(dndEnd);
+            buf.putInt((calendar.get(Calendar.HOUR_OF_DAY) * 3600 + calendar.get(Calendar.MINUTE) * 60));
+        } else {
+            buf.putInt(0);
+            buf.putInt(0);
+        }
+
+        mSupport.sendCommand("set hydration reminders", CmfCommand.WATER_REMINDER_SET, buf.array());
+    }
+
+    private void setActivityTypes() {
+        final Prefs prefs = mSupport.getDevicePrefs();
+        List<String> activityTypes = new ArrayList<>(prefs.getList(HuamiConst.PREF_WORKOUT_ACTIVITY_TYPES_SORTABLE, Collections.emptyList()));
+
+        if (activityTypes.isEmpty()) {
+            activityTypes.add(CmfActivityType.OUTDOOR_RUNNING.name().toLowerCase(Locale.ROOT));
+            activityTypes.add(CmfActivityType.INDOOR_RUNNING.name().toLowerCase(Locale.ROOT));
+        }
+
+        if (activityTypes.size() > 36) {
+            LOG.warn("Truncating activity types list to 36");
+            activityTypes = activityTypes.subList(0, 36);
+        }
+
+        final ByteBuffer buf = ByteBuffer.allocate(activityTypes.size() + 1);
+        buf.put((byte) activityTypes.size());
+
+        for (final String activityType : activityTypes) {
+            buf.put(CmfActivityType.valueOf(activityType.toUpperCase(Locale.ROOT)).getCode());
+        }
+
+        mSupport.sendCommand("set activity types", CmfCommand.SPORTS_SET, buf.array());
+    }
+
+    protected boolean onCommand(final CmfCommand cmd, final byte[] payload) {
+        // TODO handle preference replies from watch
+        return false;
+    }
+
+    private Context getContext() {
+        return mSupport.getContext();
+    }
+
+    private GBDevice getDevice() {
+        return mSupport.getDevice();
+    }
+
+    private static final Map<String, String> LANGUAGES = new HashMap<String, String>() {{
+        put("ar", "ar_SA");
+        put("de", "de_DE");
+        put("en", "en_US");
+        put("es", "es_ES");
+        put("fr", "fr_FR");
+        put("hi", "hi_IN");
+        put("in", "id_ID");
+        put("it", "it_IT");
+        put("ja", "ja_JP");
+        put("ko", "ko_KO");
+        put("zh_cn", "zh_CN");
+        put("zh_hk", "zh_HK");
+    }};
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/cmfwatchpro/CmfWatchProSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/cmfwatchpro/CmfWatchProSupport.java
new file mode 100644
index 0000000..262c12e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/cmfwatchpro/CmfWatchProSupport.java
@@ -0,0 +1,854 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.cmfwatchpro;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.location.Location;
+import android.media.AudioManager;
+import android.net.Uri;
+import android.os.Build;
+import android.widget.Toast;
+
+import androidx.annotation.Nullable;
+
+import net.e175.klaus.solarpositioning.DeltaT;
+import net.e175.klaus.solarpositioning.SPA;
+import net.e175.klaus.solarpositioning.SunriseTransitSet;
+
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.Random;
+import java.util.TimeZone;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventFindPhone;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventMusicControl;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdateDeviceInfo;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdatePreferences;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo;
+import nodomain.freeyourgadget.gadgetbridge.devices.cmfwatchpro.CmfWatchProCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
+import nodomain.freeyourgadget.gadgetbridge.model.BatteryState;
+import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.Contact;
+import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.Weather;
+import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.webview.CurrentPosition;
+import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+import nodomain.freeyourgadget.gadgetbridge.util.MediaManager;
+
+public class CmfWatchProSupport extends AbstractBTLEDeviceSupport implements CmfCharacteristic.Handler {
+    private static final Logger LOG = LoggerFactory.getLogger(CmfWatchProSupport.class);
+
+    public static final UUID UUID_SERVICE_CMF_CMD = UUID.fromString("0000fff0-0000-1000-8000-00805f9b34fb");
+    public static final UUID UUID_CHARACTERISTIC_CMF_COMMAND_READ = UUID.fromString("0000fff1-0000-1000-8000-00805f9b34fb");
+    public static final UUID UUID_CHARACTERISTIC_CMF_COMMAND_WRITE = UUID.fromString("0000fff2-0000-1000-8000-00805f9b34fb");
+
+    public static final UUID UUID_SERVICE_CMF_DATA = UUID.fromString("02f00000-0000-0000-0000-00000000ffe0");
+    public static final UUID UUID_CHARACTERISTIC_CMF_DATA_WRITE = UUID.fromString("02f00000-0000-0000-0000-00000000ffe1");
+    public static final UUID UUID_CHARACTERISTIC_CMF_DATA_READ = UUID.fromString("02f00000-0000-0000-0000-00000000ffe2");
+
+    public static final UUID UUID_SERVICE_CMF_SHELL = UUID.fromString("77d4e67c-2fe2-2334-0d35-9ccd078f529c");
+    public static final UUID UUID_CHARACTERISTIC_CMF_SHELL_WRITE = UUID.fromString("77d4ff01-2fe2-2334-0d35-9ccd078f529c");
+    public static final UUID UUID_CHARACTERISTIC_CMF_SHELL_READ = UUID.fromString("77d4ff02-2fe2-2334-0d35-9ccd078f529c");
+
+    // An a5 byte is used a lot in single payloads, probably as a "proof of encryption"?
+    public static final byte A5 = (byte) 0xa5;
+
+    private CmfCharacteristic characteristicCommandRead;
+    private CmfCharacteristic characteristicCommandWrite;
+    private CmfCharacteristic characteristicDataRead;
+    private CmfCharacteristic characteristicDataWrite;
+
+    private final byte[] authRandom1 = new byte[16];
+    private final byte[] authAppSecret = new byte[16];
+
+    private final CmfActivitySync activitySync = new CmfActivitySync(this);
+    private final CmfPreferences preferences = new CmfPreferences(this);
+    private CmfDataUploader dataUploader;
+
+    protected MediaManager mediaManager = null;
+
+    public CmfWatchProSupport() {
+        super(LOG);
+        addSupportedService(UUID_SERVICE_CMF_CMD);
+        addSupportedService(UUID_SERVICE_CMF_DATA);
+        addSupportedService(UUID_SERVICE_CMF_SHELL);
+    }
+
+    @Override
+    public boolean useAutoConnect() {
+        return true;
+    }
+    @Override
+    protected TransactionBuilder initializeDevice(final TransactionBuilder builder) {
+        builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext()));
+
+        final BluetoothGattCharacteristic btCharacteristicCommandRead = getCharacteristic(UUID_CHARACTERISTIC_CMF_COMMAND_READ);
+        if (btCharacteristicCommandRead == null) {
+            LOG.warn("Characteristic command read is null, will attempt to reconnect");
+            builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.WAITING_FOR_RECONNECT, getContext()));
+            return builder;
+        }
+
+        final BluetoothGattCharacteristic btCharacteristicCommandWrite = getCharacteristic(UUID_CHARACTERISTIC_CMF_COMMAND_WRITE);
+        if (btCharacteristicCommandWrite == null) {
+            LOG.warn("Characteristic command write is null, will attempt to reconnect");
+            builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.WAITING_FOR_RECONNECT, getContext()));
+            return builder;
+        }
+
+        final BluetoothGattCharacteristic btCharacteristicDataWrite = getCharacteristic(UUID_CHARACTERISTIC_CMF_DATA_WRITE);
+        if (btCharacteristicDataWrite == null) {
+            LOG.warn("Characteristic data write is null, will attempt to reconnect");
+            builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.WAITING_FOR_RECONNECT, getContext()));
+            return builder;
+        }
+
+        final BluetoothGattCharacteristic btCharacteristicDataRead = getCharacteristic(UUID_CHARACTERISTIC_CMF_DATA_READ);
+        if (btCharacteristicDataRead == null) {
+            LOG.warn("Characteristic data read is null, will attempt to reconnect");
+            builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.WAITING_FOR_RECONNECT, getContext()));
+            return builder;
+        }
+
+        final BluetoothGattCharacteristic btCharacteristicShellWrite = getCharacteristic(UUID_CHARACTERISTIC_CMF_SHELL_WRITE);
+        if (btCharacteristicShellWrite == null) {
+            LOG.warn("Characteristic shell write is null");
+        }
+
+        final BluetoothGattCharacteristic btCharacteristicShellRead = getCharacteristic(UUID_CHARACTERISTIC_CMF_SHELL_READ);
+        if (btCharacteristicShellRead == null) {
+            LOG.warn("Characteristic shell read is null");
+        }
+
+        dataUploader = new CmfDataUploader(this);
+
+        characteristicCommandRead = new CmfCharacteristic(btCharacteristicCommandRead, this);
+        characteristicCommandWrite = new CmfCharacteristic(btCharacteristicCommandWrite, null);
+        characteristicDataRead = new CmfCharacteristic(btCharacteristicDataRead, dataUploader);
+        characteristicDataWrite = new CmfCharacteristic(btCharacteristicDataWrite, null);
+
+        builder.notify(btCharacteristicCommandRead, true);
+        builder.notify(btCharacteristicDataRead, true);
+        if (btCharacteristicShellRead != null) {
+            builder.notify(btCharacteristicShellRead, true);
+        }
+
+        builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.AUTHENTICATING, getContext()));
+
+        final byte[] secretKey = getSecretKey(getDevice());
+
+        if (secretKey != null) {
+            characteristicCommandRead.setSessionKey(secretKey);
+            characteristicCommandWrite.setSessionKey(secretKey);
+            characteristicDataRead.setSessionKey(secretKey);
+            characteristicDataWrite.setSessionKey(secretKey);
+
+            sendCommand(builder, CmfCommand.AUTH_PHONE_NAME, ArrayUtils.addAll(new byte[]{A5}, Build.MODEL.getBytes(StandardCharsets.UTF_8)));
+        } else if (btCharacteristicShellWrite != null) {
+            builder.write(getCharacteristic(UUID_CHARACTERISTIC_CMF_SHELL_WRITE), "AT GETSECRET".getBytes());
+        } else {
+            GB.toast(getContext(), R.string.authentication_failed_check_key, Toast.LENGTH_LONG, GB.WARN);
+            builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.NOT_CONNECTED, getContext()));
+        }
+
+        return builder;
+    }
+
+    @Override
+    public void setContext(final GBDevice device, final BluetoothAdapter adapter, final Context context) {
+        super.setContext(device, adapter, context);
+
+        mediaManager = new MediaManager(context);
+    }
+
+    @Override
+    public boolean onCharacteristicChanged(final BluetoothGatt gatt,
+                                           final BluetoothGattCharacteristic characteristic) {
+        if (super.onCharacteristicChanged(gatt, characteristic)) {
+            return true;
+        }
+
+        final UUID characteristicUUID = characteristic.getUuid();
+        final byte[] value = characteristic.getValue();
+
+        if (characteristicUUID.equals(characteristicCommandRead.getCharacteristicUUID())) {
+            characteristicCommandRead.onCharacteristicChanged(value);
+            return true;
+        } else if (characteristicUUID.equals(characteristicDataRead.getCharacteristicUUID())) {
+            characteristicDataRead.onCharacteristicChanged(value);
+            return true;
+        } else if (characteristicUUID.equals(UUID_CHARACTERISTIC_CMF_SHELL_READ)) {
+            handleShellCommand(value);
+            return true;
+        }
+
+        LOG.warn("Unhandled characteristic changed: {} {}", characteristicUUID, GB.hexdump(value));
+        return false;
+    }
+
+    @Override
+    public void onMtuChanged(final BluetoothGatt gatt, final int mtu, final int status) {
+        super.onMtuChanged(gatt, mtu, status);
+
+        characteristicCommandRead.setMtu(mtu);
+        characteristicCommandWrite.setMtu(mtu);
+        characteristicDataRead.setMtu(mtu);
+        characteristicDataWrite.setMtu(mtu);
+    }
+
+    @Override
+    public void onCommand(final CmfCommand cmd, final byte[] payload) {
+        if (activitySync.onCommand(cmd, payload)) {
+            return;
+        }
+
+        if (preferences.onCommand(cmd, payload)) {
+            return;
+        }
+
+        switch (cmd) {
+            case AUTH_PAIR_REPLY:
+                final byte[] authRandom2 = ArrayUtils.subarray(payload, 0, 16);
+                final byte[] signedAuthRandom2 = ArrayUtils.subarray(payload, 16, 48);
+
+                LOG.debug("authRandom2: {}", GB.hexdump(authRandom2));
+                LOG.debug("signedAuthRandom2: {}", GB.hexdump(signedAuthRandom2));
+
+                try {
+                    // Validate random2 signature
+                    final MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
+                    sha256.update(authRandom2);
+                    sha256.update(authAppSecret);
+                    byte[] random2verify = sha256.digest();
+
+                    if (!Arrays.equals(signedAuthRandom2, random2verify)) {
+                        LOG.error("random2 signature mismatch");
+                        authNegotiationFailed();
+                        return;
+                    }
+
+                    // Compute K1 and update preferences
+                    sha256.reset();
+                    sha256.update(authRandom1);
+                    sha256.update(authRandom2);
+                    sha256.update(authAppSecret);
+                    final byte[] k1full = sha256.digest();
+                    final byte[] secretKey = ArrayUtils.subarray(k1full, 0, 16);
+
+                    LOG.debug("Negotiated K1: {}", k1full);
+
+                    evaluateGBDeviceEvent(new GBDeviceEventUpdatePreferences("authkey", GB.hexdump(secretKey)));
+
+                    characteristicCommandRead.setSessionKey(secretKey);
+                    characteristicCommandWrite.setSessionKey(secretKey);
+                    characteristicDataRead.setSessionKey(secretKey);
+                    characteristicDataWrite.setSessionKey(secretKey);
+
+                    sendCommand("auth step 2", CmfCommand.AUTH_PHONE_NAME, ArrayUtils.addAll(new byte[]{A5}, Build.MODEL.getBytes(StandardCharsets.UTF_8)));
+                } catch (final Exception e) {
+                    LOG.error("Failed to negotiate K1", e);
+                    authNegotiationFailed();
+                    return;
+                }
+
+                return;
+            case AUTH_FAILED:
+                LOG.error("Authentication failed, disconnecting");
+                GB.toast(getContext(), R.string.authentication_failed_check_key, Toast.LENGTH_LONG, GB.WARN);
+                final GBDevice device = getDevice();
+                if (device != null) {
+                    GBApplication.deviceService(device).disconnect();
+                }
+                return;
+            case AUTH_WATCH_MAC:
+                LOG.debug("Got auth watch mac, requesting nonce");
+                sendCommand("auth request nonce", CmfCommand.AUTH_NONCE_REQUEST, A5);
+                return;
+            case AUTH_NONCE_REPLY:
+                LOG.debug("Got auth nonce");
+
+                try {
+                    final MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
+                    sha256.update(payload);
+                    sha256.update(getSecretKey(getDevice()));
+                    final byte[] digest = sha256.digest();
+                    final byte[] sessionKey = ArrayUtils.subarray(digest, 0, 16);
+                    LOG.debug("New session key: {}", GB.hexdump(sessionKey));
+                    characteristicCommandRead.setSessionKey(sessionKey);
+                    characteristicCommandWrite.setSessionKey(sessionKey);
+                    characteristicDataRead.setSessionKey(sessionKey);
+                    characteristicDataWrite.setSessionKey(sessionKey);
+                } catch (final Exception e) {
+                    LOG.error("Failed to compute session key from auth nonce", e);
+                    return;
+                }
+
+                sendCommand("auth confirm", CmfCommand.AUTHENTICATED_CONFIRM_REQUEST, A5);
+                return;
+            case AUTHENTICATED_CONFIRM_REPLY:
+                LOG.debug("Authentication confirmed, starting phase 2 initialization");
+
+                final TransactionBuilder phase2builder = createTransactionBuilder("phase 2 initialize");
+                setTime(phase2builder);
+                sendCommand(phase2builder, CmfCommand.FIRMWARE_VERSION_GET);
+                sendCommand(phase2builder, CmfCommand.SERIAL_NUMBER_GET);
+                final Location location = new CurrentPosition().getLastKnownLocation();
+                if (location.getLatitude() != 0 && location.getLongitude() != 0) {
+                    sendGpsCoords(phase2builder, location);
+                }
+                //sendCommand(phase2builder, CmfCommand.STANDING_REMINDER_GET);
+                //sendCommand(phase2builder, CmfCommand.WATER_REMINDER_GET);
+                //sendCommand(phase2builder, CmfCommand.CONTACTS_GET);
+                //sendCommand(phase2builder, CmfCommand.ALARMS_GET);
+                // TODO premature to mark as initialized?
+                phase2builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZED, getContext()));
+                phase2builder.queue(getQueue());
+                return;
+            case BATTERY:
+                final int battery = payload[0] & 0xff;
+                final boolean charging = payload[1] == 0x01;
+                LOG.debug("Got battery: level={} charging={}", battery, charging);
+                final GBDeviceEventBatteryInfo eventBatteryInfo = new GBDeviceEventBatteryInfo();
+                eventBatteryInfo.level = battery;
+                eventBatteryInfo.state = charging ? BatteryState.BATTERY_CHARGING : BatteryState.BATTERY_NORMAL;
+                evaluateGBDeviceEvent(eventBatteryInfo);
+                return;
+            case FIRMWARE_VERSION_RET:
+                final String[] fwParts = new String[payload.length];
+                for (int i = 0; i < payload.length; i++) {
+                    fwParts[i] = String.valueOf(payload[i]);
+                }
+                final String fw = String.join(".", fwParts);
+                LOG.debug("Got firmware version: {}", fw);
+                final GBDeviceEventVersionInfo gbDeviceEventVersionInfo = new GBDeviceEventVersionInfo();
+                gbDeviceEventVersionInfo.fwVersion = fw;
+                gbDeviceEventVersionInfo.fwVersion2 = "N/A";
+                //gbDeviceEventVersionInfo.hwVersion = "?"; // TODO how?
+                evaluateGBDeviceEvent(gbDeviceEventVersionInfo);
+                return;
+            case SERIAL_NUMBER_RET:
+                if (payload.length != (payload[0] & 0xff) + 1) {
+                    LOG.warn("Unexpected serial number payload length: {}, expected {}", payload.length, (payload[0] & 0xff));
+                    return;
+                }
+                final String serialNumber = new String(ArrayUtils.subarray(payload, 1, payload.length));
+                LOG.debug("Got serial number: {}", serialNumber);
+                final GBDeviceEventUpdateDeviceInfo gbDeviceEventUpdateDeviceInfo = new GBDeviceEventUpdateDeviceInfo("SERIAL: ", serialNumber);
+                evaluateGBDeviceEvent(gbDeviceEventUpdateDeviceInfo);
+                return;
+            case FIND_PHONE:
+                final GBDeviceEventFindPhone findPhoneEvent = new GBDeviceEventFindPhone();
+                if (payload[0] == 1) {
+                    findPhoneEvent.event = GBDeviceEventFindPhone.Event.START;
+                } else {
+                    findPhoneEvent.event = GBDeviceEventFindPhone.Event.STOP;
+                }
+                evaluateGBDeviceEvent(findPhoneEvent);
+                return;
+            case MUSIC_INFO_ACK:
+                LOG.debug("Got music info ack");
+                break;
+            case MUSIC_BUTTON:
+                final GBDeviceEventMusicControl deviceEventMusicControl = new GBDeviceEventMusicControl();
+                switch (BLETypeConversions.toUint16(payload)) {
+                    case 0x0003:
+                        deviceEventMusicControl.event = GBDeviceEventMusicControl.Event.VOLUMEDOWN;
+                        break;
+                    case 0x0103:
+                        deviceEventMusicControl.event = GBDeviceEventMusicControl.Event.VOLUMEUP;
+                        break;
+                    case 0x0001:
+                        deviceEventMusicControl.event = GBDeviceEventMusicControl.Event.PAUSE;
+                        break;
+                    case 0x0101:
+                        deviceEventMusicControl.event = GBDeviceEventMusicControl.Event.PLAY;
+                        break;
+                    case 0x0102:
+                        deviceEventMusicControl.event = GBDeviceEventMusicControl.Event.NEXT;
+                        break;
+                    case 0x0002:
+                        deviceEventMusicControl.event = GBDeviceEventMusicControl.Event.PREVIOUS;
+                        break;
+                    default:
+                        LOG.warn("Unexpected media button key {}", GB.hexdump(payload));
+                        return;
+                }
+                LOG.debug("Got media button {}", deviceEventMusicControl.event);
+                evaluateGBDeviceEvent(deviceEventMusicControl);
+                break;
+            default:
+                LOG.warn("Unhandled command: {}", cmd);
+        }
+    }
+
+    public void sendCommand(final String taskName, final CmfCommand cmd, final byte... payload) {
+        final TransactionBuilder builder = createTransactionBuilder(taskName);
+        sendCommand(builder, cmd, payload);
+        builder.queue(getQueue());
+    }
+
+    public void sendCommand(final TransactionBuilder builder, final CmfCommand cmd, final byte... payload) {
+        characteristicCommandWrite.sendCommand(builder, cmd, payload);
+    }
+
+    public void sendData(final String taskName, final CmfCommand cmd, final byte... payload) {
+        final TransactionBuilder builder = createTransactionBuilder(taskName);
+        characteristicDataWrite.sendCommand(builder, cmd, payload);
+        builder.queue(getQueue());
+    }
+
+    private void handleShellCommand(final byte[] bytes) {
+        final String shellCommand = new String(bytes).strip();
+        if (!shellCommand.startsWith("GETSECRET:")) {
+            LOG.error("Got unknown shell command: {}", GB.hexdump(bytes));
+            return;
+        }
+
+        if (!shellCommand.endsWith(",OK")) {
+            LOG.error("Failed to get secret: {}", GB.hexdump(bytes));
+            authNegotiationFailed();
+            return;
+        }
+
+        final byte[] signedRandom1;
+        try {
+            new Random().nextBytes(authRandom1);
+            final byte[] secretBytes = GB.hexStringToByteArray(shellCommand.substring(10, 10 + 32));
+            System.arraycopy(secretBytes, 0, authAppSecret, 0, authAppSecret.length);
+
+            final MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
+            sha256.update(authRandom1);
+            sha256.update(authAppSecret);
+            signedRandom1 = sha256.digest();
+
+            LOG.debug("authRandom1: {}", GB.hexdump(authRandom1));
+            LOG.debug("authAppSecret: {}", GB.hexdump(authAppSecret));
+            LOG.debug("signedRandom1: {}", GB.hexdump(signedRandom1));
+        } catch (final Exception e) {
+            LOG.error("Failed to generate signed random1", e);
+            authNegotiationFailed();
+            return;
+        }
+
+        sendCommand("auth send signed random1", CmfCommand.AUTH_PAIR_REQUEST, ArrayUtils.addAll(authRandom1, signedRandom1));
+    }
+
+    private void authNegotiationFailed() {
+        GB.toast(getContext(), R.string.authentication_failed_negotiation, Toast.LENGTH_LONG, GB.WARN);
+        final GBDevice device = getDevice();
+        if (device != null) {
+            GBApplication.deviceService(device).disconnect();
+        }
+    }
+
+    @Nullable
+    private static byte[] getSecretKey(final GBDevice device) {
+        final SharedPreferences sharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(device.getAddress());
+
+        final String authKey = sharedPrefs.getString("authkey", "").trim();
+        if (StringUtils.isBlank(authKey)) {
+            return null;
+        }
+
+        final byte[] authKeyBytes = new byte[16];
+
+        final byte[] srcBytes;
+        // Allow both with and without 0x, to avoid user mistakes
+        if (authKey.length() == 34 && authKey.startsWith("0x")) {
+            srcBytes = GB.hexStringToByteArray(authKey.trim().substring(2));
+        } else {
+            srcBytes = GB.hexStringToByteArray(authKey.trim());
+        }
+        System.arraycopy(srcBytes, 0, authKeyBytes, 0, Math.min(srcBytes.length, 16));
+
+        return authKeyBytes;
+    }
+
+    protected CmfWatchProCoordinator getCoordinator() {
+        return (CmfWatchProCoordinator) gbDevice.getDeviceCoordinator();
+    }
+
+    @Override
+    public void onSetGpsLocation(final Location location) {
+        final TransactionBuilder builder = createTransactionBuilder("set gps location");
+        sendGpsCoords(builder, location);
+        builder.queue(getQueue());
+    }
+
+    private void sendGpsCoords(final TransactionBuilder builder, final Location location) {
+        final ByteBuffer buf = ByteBuffer.allocate(16)
+                .order(ByteOrder.BIG_ENDIAN);
+
+        buf.putInt((int) (location.getTime() / 1000));
+        buf.putInt((int) (location.getLatitude() * 10000000));
+        buf.putInt((int) (location.getLongitude() * 10000000));
+
+        sendCommand(builder, CmfCommand.GPS_COORDS, buf.array());
+    }
+
+    @Override
+    public void onNotification(final NotificationSpec notificationSpec) {
+        if (!getDevicePrefs().getBoolean(DeviceSettingsPreferenceConst.PREF_SEND_APP_NOTIFICATIONS, true)) {
+            LOG.debug("App notifications disabled - ignoring");
+            return;
+        }
+
+        final String senderOrTitle = nodomain.freeyourgadget.gadgetbridge.util.StringUtils.getFirstOf(
+                notificationSpec.sender,
+                notificationSpec.title
+        );
+
+        final String body = nodomain.freeyourgadget.gadgetbridge.util.StringUtils.getFirstOf(notificationSpec.body, "");
+
+        final byte[] senderOrTitleBytes = nodomain.freeyourgadget.gadgetbridge.util.StringUtils.truncateToBytes(senderOrTitle, 20); // TODO confirm max
+        final byte[] bodyBytes = nodomain.freeyourgadget.gadgetbridge.util.StringUtils.truncateToBytes(body, 128); // TODO confirm max
+
+        final ByteBuffer buf = ByteBuffer.allocate(7 + senderOrTitleBytes.length + bodyBytes.length)
+                .order(ByteOrder.BIG_ENDIAN);
+
+        buf.put(CmfNotificationIcon.forNotification(notificationSpec).getCode());
+        buf.put((byte) 0x00); // ?
+        buf.putInt((int) (notificationSpec.when / 1000));
+        buf.put((byte) senderOrTitleBytes.length);
+        buf.put(senderOrTitleBytes);
+        buf.put(bodyBytes);
+
+        sendCommand("send notification", CmfCommand.APP_NOTIFICATION, buf.array());
+    }
+
+    @Override
+    public void onSetContacts(final ArrayList<? extends Contact> contacts) {
+        final ByteBuffer buf = ByteBuffer.allocate(57 * contacts.size()).order(ByteOrder.BIG_ENDIAN);
+
+        for (final Contact contact : contacts) {
+            final byte[] nameBytes = nodomain.freeyourgadget.gadgetbridge.util.StringUtils.truncateToBytes(contact.getName(), 32);
+            buf.put(nameBytes);
+            buf.put(new byte[32 - nameBytes.length]);
+
+            final byte[] numberBytes = nodomain.freeyourgadget.gadgetbridge.util.StringUtils.truncateToBytes(contact.getNumber(), 25);
+            buf.put(numberBytes);
+            buf.put(new byte[25 - numberBytes.length]);
+        }
+
+        sendCommand("set contacts", CmfCommand.CONTACTS_SET, ArrayUtils.subarray(buf.array(), 0, buf.position()));
+    }
+
+    @Override
+    public void onSetTime() {
+        final TransactionBuilder builder = createTransactionBuilder("set time");
+        setTime(builder);
+        builder.queue(getQueue());
+    }
+
+    private void setTime(final TransactionBuilder builder) {
+        final Calendar cal = Calendar.getInstance();
+        final ByteBuffer buf = ByteBuffer.allocate(8).order(ByteOrder.BIG_ENDIAN);
+        buf.putInt((int) (cal.getTimeInMillis() / 1000));
+        buf.putInt(TimeZone.getDefault().getOffset(cal.getTimeInMillis()));
+        sendCommand(builder, CmfCommand.TIME, buf.array());
+    }
+
+    @Override
+    public void onSetAlarms(final ArrayList<? extends Alarm> alarms) {
+        final ByteBuffer buf = ByteBuffer.allocate(40 * alarms.size()).order(ByteOrder.BIG_ENDIAN);
+
+        int i = 0;
+        for (final Alarm alarm : alarms) {
+            if (alarm.getUnused()) {
+                continue;
+            }
+
+            buf.putInt(alarm.getHour() * 3600 + alarm.getMinute() * 60);
+            buf.put((byte) i++);
+            buf.put((byte) (alarm.getEnabled() ? 0x01 : 0x00));
+            buf.put((byte) alarm.getRepetition());
+            buf.put((byte) 0xff); // ?
+            buf.put(new byte[24]); // ?
+
+            // alarm labels do not show up on watch, even in official app
+            final byte[] labelBytes = nodomain.freeyourgadget.gadgetbridge.util.StringUtils.truncateToBytes(alarm.getTitle(), 8);
+            buf.put(new byte[8 - labelBytes.length]);
+            buf.put(labelBytes);
+        }
+
+        sendCommand("set alarms", CmfCommand.ALARMS_SET, ArrayUtils.subarray(buf.array(), 0, buf.position()));
+    }
+
+    @Override
+    public void onSetCallState(final CallSpec callSpec) {
+        super.onSetCallState(callSpec); // TODO onSetCallState
+    }
+
+    @Override
+    public void onSetCannedMessages(final CannedMessagesSpec cannedMessagesSpec) {
+        super.onSetCannedMessages(cannedMessagesSpec); // TODO onSetCannedMessages
+    }
+
+    @Override
+    public void onSetMusicState(final MusicStateSpec stateSpec) {
+        if (mediaManager.onSetMusicState(stateSpec)) {
+            sendMusicStateToDevice();
+        }
+    }
+
+    @Override
+    public void onSetPhoneVolume(final float ignoredVolume) {
+        sendMusicStateToDevice();
+    }
+
+    @Override
+    public void onSetMusicInfo(final MusicSpec musicSpec) {
+        if (mediaManager.onSetMusicInfo(musicSpec)) {
+            sendMusicStateToDevice();
+        }
+    }
+
+    private void sendMusicStateToDevice() {
+        final MusicSpec musicSpec = mediaManager.getBufferMusicSpec();
+        final MusicStateSpec musicStateSpec = mediaManager.getBufferMusicStateSpec();
+
+        final byte stateByte;
+        if (musicSpec == null || musicStateSpec == null) {
+            stateByte = 0x00;
+        } else if (musicStateSpec.state == MusicStateSpec.STATE_PLAYING) {
+            stateByte = 0x02;
+        } else {
+            stateByte = 0x01;
+        }
+
+        final byte[] track;
+        final byte[] artist;
+
+        if (musicSpec != null) {
+            track = nodomain.freeyourgadget.gadgetbridge.util.StringUtils.truncateToBytes(musicSpec.track, 63);
+            artist = nodomain.freeyourgadget.gadgetbridge.util.StringUtils.truncateToBytes(musicSpec.artist, 63);
+        } else {
+            track = new byte[0];
+            artist = new byte[0];
+        }
+
+        final AudioManager audioManager = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
+        final int volumeLevel = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
+        final int volumeMax = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
+
+        final ByteBuffer buf = ByteBuffer.allocate(131);
+        buf.put(stateByte);
+        buf.put((byte) volumeLevel);
+        buf.put((byte) volumeMax);
+        buf.put(track);
+        buf.put(new byte[64 - track.length]);
+        buf.put(artist);
+        buf.put(new byte[64 - artist.length]);
+
+        sendCommand("set music info", CmfCommand.MUSIC_INFO_SET, buf.array());
+    }
+
+    @Override
+    public void onInstallApp(final Uri uri) {
+        dataUploader.onInstallApp(uri);
+    }
+
+    @Override
+    public void onAppInfoReq() {
+        super.onAppInfoReq(); // TODO onAppInfoReq
+    }
+
+    @Override
+    public void onAppStart(final UUID uuid, final boolean start) {
+        super.onAppStart(uuid, start); // TODO onAppStart for watchfaces
+    }
+
+    @Override
+    public void onFetchRecordedData(final int dataTypes) {
+        sendCommand("fetch recorded data step 1", CmfCommand.ACTIVITY_FETCH_1, A5);
+    }
+
+    @Override
+    public void onReset(final int flags) {
+        if ((flags & GBDeviceProtocol.RESET_FLAGS_FACTORY_RESET) != 0) {
+            sendCommand("factory reset", CmfCommand.FACTORY_RESET, A5);
+        } else {
+            LOG.warn("Unknown reset flags: {}", String.format("0x%x", flags));
+        }
+    }
+
+    @Override
+    public void onSetHeartRateMeasurementInterval(final int seconds) {
+        preferences.onSetHeartRateMeasurementInterval(seconds);
+    }
+
+    @Override
+    public void onSendConfiguration(final String config) {
+        preferences.onSendConfiguration(config);
+    }
+
+    @Override
+    public void onFindDevice(final boolean start) {
+        if (!start) {
+            return;
+        }
+
+        sendCommand("find device", CmfCommand.FIND_WATCH);
+    }
+
+    @Override
+    public void onSendWeather(final ArrayList<WeatherSpec> weatherSpecs) {
+        final WeatherSpec weatherSpec = weatherSpecs.get(0);
+        // TODO consider adjusting the condition code for clear/sunny so "clear" at night doesn't show a sunny icon (perhaps 23 decimal)?
+        // Each weather entry takes up 9 bytes
+        // There are 7 of those weather entries - 7*9 bytes
+        // Then there are 24-hour entries of temp and weather condition (2 bytes each)
+        // Then the location name as bytes - allow for 30 bytes, watch auto-scrolls. Pad it to 32 bytes if it supports sunset/sunrise
+        // Then finally the sunrise / sunset pairs, for 7 days (7*8)
+        final boolean supportsSunriseSunset = getCoordinator().supportsSunriseSunset();
+        final int payloadLength = (7 * 9) + (24 * 2) + (supportsSunriseSunset ? 32 : 30) + (supportsSunriseSunset ? 7 * 8 : 0);
+        final ByteBuffer buf = ByteBuffer.allocate(payloadLength).order(ByteOrder.BIG_ENDIAN);
+        // start with the current day's weather
+        buf.put(Weather.mapToCmfCondition(weatherSpec.currentConditionCode));
+        buf.put((byte) (weatherSpec.currentTemp - 273 + 100)); // convert Kelvin to C, add 100
+        buf.put((byte) (weatherSpec.todayMaxTemp - 273 + 100)); // convert Kelvin to C, add 100
+        buf.put((byte) (weatherSpec.todayMinTemp - 273 + 100)); // convert Kelvin to C, add 100
+        buf.put((byte) weatherSpec.currentHumidity);
+        buf.putShort((short) (weatherSpec.airQuality != null ? weatherSpec.airQuality.aqi : 0));
+        buf.put((byte) weatherSpec.uvIndex); // UV index isn't shown. uvi decimal/100, so 0x07 = 700 UVI.
+        buf.put((byte) weatherSpec.windSpeed); // isn't shown by watch, unsure of correct units
+
+        // find out how many future days' forecasts are available
+        int maxForecastsAvailable = weatherSpec.forecasts.size();
+        // For each day of the forecast
+        for (int i = 0; i < 6; i++) {
+            if (i < maxForecastsAvailable) {
+                WeatherSpec.Daily forecastDay = weatherSpec.forecasts.get(i);
+                buf.put((byte) (Weather.mapToCmfCondition(forecastDay.conditionCode)));  // weather condition flag
+                buf.put((byte) (forecastDay.maxTemp - 273 + 100)); // temp in C (not shown in future days' forecasts)
+                buf.put((byte) (forecastDay.maxTemp - 273 + 100)); // max temp in C, + 100
+                buf.put((byte) (forecastDay.minTemp - 273 + 100)); // min temp in C, + 100
+                buf.put((byte) forecastDay.humidity); // humidity as a %
+                buf.putShort((short) (forecastDay.airQuality != null ? forecastDay.airQuality.aqi : 0));
+                buf.put((byte) forecastDay.uvIndex); // UV index isn't shown. uvi decimal/100, so 0x07 = 700 UVI.
+                buf.put((byte) forecastDay.windSpeed); // isn't shown by watch, unsure of correct units
+            } else {
+                // we need to provide a dummy forecast as there's no data available
+                buf.put((byte) 0x00); // NULL weather condition
+                buf.put((byte) 0x01); // -99 C temp temp
+                buf.put((byte) 0x01); // -99 C max temp
+                buf.put((byte) 0x01); // -99 C min temp
+                buf.put((byte) 0x00); // 0 humidity
+                buf.putShort((short) 0); // aqi
+                buf.put((byte) 0x00); // 0 UV index
+                buf.put((byte) 0x00); // 0 wind speed
+            }
+
+        }
+        // now add the hourly data for today - just condition and temperature
+        int maxHourlyForecastsAvailable = weatherSpec.hourly.size();
+        for (int i = 0; i < 24; i++) {
+            if (i < maxHourlyForecastsAvailable) {
+                WeatherSpec.Hourly forecastHr = weatherSpec.hourly.get(i);
+                buf.put((byte) (forecastHr.temp - 273 + 100)); // temperature
+                buf.put((byte) forecastHr.conditionCode); // condition
+            } else {
+                buf.put((byte) (weatherSpec.currentTemp - 273 + 100)); // assume current temp
+                buf.put((byte) (Weather.mapToCmfCondition(weatherSpec.currentConditionCode))); // current condition
+            }
+        }
+        // place name - watch scrolls after ~10 chars. Pad up to 32 bytes.
+        final byte[] locationNameBytes = nodomain.freeyourgadget.gadgetbridge.util.StringUtils.truncateToBytes(weatherSpec.location, 30);
+        buf.put(locationNameBytes);
+
+        // Sunrise / sunset
+        if (supportsSunriseSunset) {
+            buf.put(new byte[32 - locationNameBytes.length]);
+
+            buf.order(ByteOrder.LITTLE_ENDIAN); // why...
+            final Location location = weatherSpec.getLocation() != null ? weatherSpec.getLocation() : new CurrentPosition().getLastKnownLocation();
+            final GregorianCalendar sunriseDate = new GregorianCalendar();
+
+            if (weatherSpec.sunRise != 0 && weatherSpec.sunSet != 0) {
+                buf.putInt(weatherSpec.sunRise);
+                buf.putInt(weatherSpec.sunSet);
+            } else {
+                putSunriseSunset(buf, location, sunriseDate);
+            }
+
+            for (int i = 0; i < 6; i++) {
+                sunriseDate.add(Calendar.DAY_OF_MONTH, 1);
+                if (i < weatherSpec.forecasts.size() && weatherSpec.forecasts.get(i).sunRise != 0 && weatherSpec.forecasts.get(i).sunSet != 0) {
+                    buf.putInt(weatherSpec.forecasts.get(i).sunRise);
+                    buf.putInt(weatherSpec.forecasts.get(i).sunSet);
+                } else {
+                    putSunriseSunset(buf, location, sunriseDate);
+                }
+            }
+        }
+
+        sendCommand("send weather", CmfCommand.WEATHER_SET_1, buf.array());
+    }
+
+    private void putSunriseSunset(final ByteBuffer buf, final Location location, final GregorianCalendar date) {
+        final SunriseTransitSet sunriseTransitSet = SPA.calculateSunriseTransitSet(
+                date.toZonedDateTime(),
+                location.getLatitude(),
+                location.getLongitude(),
+                DeltaT.estimate(date.toZonedDateTime().toLocalDate())
+        );
+
+        if (sunriseTransitSet.getSunrise() != null && sunriseTransitSet.getSunset() != null) {
+            buf.putInt((int) sunriseTransitSet.getSunrise().toInstant().getEpochSecond());
+            buf.putInt((int) sunriseTransitSet.getSunset().toInstant().getEpochSecond());
+        } else {
+            buf.putInt(0);
+            buf.putInt(0);
+        }
+    }
+
+    @Override
+    public void onTestNewFunction() {
+
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/colmi/ColmiR0xDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/colmi/ColmiR0xDeviceSupport.java
new file mode 100644
index 0000000..984a1c6
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/colmi/ColmiR0xDeviceSupport.java
@@ -0,0 +1,715 @@
+/*  Copyright (C) 2024 Arjan Schrijver
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.colmi;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+
+import androidx.annotation.NonNull;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.SettingsActivity;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo;
+import nodomain.freeyourgadget.gadgetbridge.devices.colmi.ColmiR0xConstants;
+import nodomain.freeyourgadget.gadgetbridge.devices.colmi.ColmiR0xPacketHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.colmi.samples.ColmiHeartRateSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.ColmiHeartRateSample;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
+import nodomain.freeyourgadget.gadgetbridge.model.BatteryState;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.GattService;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.IntentListener;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo.DeviceInfo;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo.DeviceInfoProfile;
+import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol;
+import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
+
+public class ColmiR0xDeviceSupport extends AbstractBTLEDeviceSupport {
+    private static final Logger LOG = LoggerFactory.getLogger(ColmiR0xDeviceSupport.class);
+    private final Handler backgroundTasksHandler = new Handler(Looper.getMainLooper());
+
+    private final DeviceInfoProfile<ColmiR0xDeviceSupport> deviceInfoProfile;
+    private String cachedFirmwareVersion = null;
+
+    private int daysAgo;
+    private int packetsTotalNr;
+    private Calendar syncingDay;
+
+    private int bigDataPacketSize;
+    private ByteBuffer bigDataPacket;
+
+    public ColmiR0xDeviceSupport() {
+        super(LOG);
+        addSupportedService(ColmiR0xConstants.CHARACTERISTIC_SERVICE_V1);
+        addSupportedService(ColmiR0xConstants.CHARACTERISTIC_SERVICE_V2);
+        addSupportedService(GattService.UUID_SERVICE_DEVICE_INFORMATION);
+
+        IntentListener mListener = intent -> {
+            String action = intent.getAction();
+            if (DeviceInfoProfile.ACTION_DEVICE_INFO.equals(action)) {
+                handleDeviceInfo(intent.getParcelableExtra(DeviceInfoProfile.EXTRA_DEVICE_INFO));
+            }
+        };
+
+        deviceInfoProfile = new DeviceInfoProfile<>(this);
+        deviceInfoProfile.addListener(mListener);
+        addSupportedProfile(deviceInfoProfile);
+    }
+
+    @Override
+    public void dispose() {
+        backgroundTasksHandler.removeCallbacksAndMessages(null);
+
+        super.dispose();
+    }
+
+    @Override
+    public void setContext(GBDevice gbDevice, BluetoothAdapter btAdapter, Context context) {
+        if (gbDevice.getFirmwareVersion() != null) {
+            setCachedFirmwareVersion(gbDevice.getFirmwareVersion());
+        }
+        super.setContext(gbDevice, btAdapter, context);
+    }
+
+    public String getCachedFirmwareVersion() {
+        return this.cachedFirmwareVersion;
+    }
+
+    public void setCachedFirmwareVersion(String version) {
+        this.cachedFirmwareVersion = version;
+    }
+
+    private void handleDeviceInfo(DeviceInfo info) {
+        LOG.debug("Device info: {}", info);
+
+        GBDeviceEventVersionInfo versionCmd = new GBDeviceEventVersionInfo();
+        versionCmd.hwVersion = info.getHardwareRevision();
+        versionCmd.fwVersion = info.getFirmwareRevision();
+        handleGBDeviceEvent(versionCmd);
+    }
+
+    @Override
+    public boolean useAutoConnect() {
+        return false;
+    }
+
+    @Override
+    protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
+        builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext()));
+
+        if (getDevice().getFirmwareVersion() == null) {
+            getDevice().setFirmwareVersion(getCachedFirmwareVersion() != null ? getCachedFirmwareVersion() : "N/A");
+        }
+        deviceInfoProfile.requestDeviceInfo(builder);
+
+        builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZED, getContext()));
+
+        builder.notify(getCharacteristic(ColmiR0xConstants.CHARACTERISTIC_NOTIFY_V1), true);
+        builder.notify(getCharacteristic(ColmiR0xConstants.CHARACTERISTIC_NOTIFY_V2), true);
+
+        // Delay initialization with 2 seconds to give the ring time to settle
+        backgroundTasksHandler.removeCallbacksAndMessages(null);
+        backgroundTasksHandler.postDelayed(this::postConnectInitialization, 2000);
+
+        return builder;
+    }
+
+    private void postConnectInitialization() {
+        setPhoneName();
+        setDateTime();
+        setUserPreferences();
+        requestBatteryInfo();
+        requestSettingsFromRing();
+    }
+
+    @Override
+    public boolean onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
+        if (super.onCharacteristicChanged(gatt, characteristic)) {
+            return true;
+        }
+
+        UUID characteristicUUID = characteristic.getUuid();
+        byte[] value = characteristic.getValue();
+
+        LOG.debug("Characteristic {} changed, value: {}", characteristicUUID, StringUtils.bytesToHex(characteristic.getValue()));
+
+        if (characteristicUUID.equals(ColmiR0xConstants.CHARACTERISTIC_NOTIFY_V1)) {
+            switch (value[0]) {
+                case ColmiR0xConstants.CMD_SET_DATE_TIME:
+                    LOG.info("Received set date/time response: {}", StringUtils.bytesToHex(value));
+                    break;
+                case ColmiR0xConstants.CMD_BATTERY:
+                    int levelResponse = value[1];
+                    boolean charging = value[2] == 1;
+                    LOG.info("Received battery level response: {}% (charging: {})", levelResponse, charging);
+                    GBDeviceEventBatteryInfo batteryEvent = createDeviceBatteryInfoEvent(levelResponse, charging);
+                    evaluateGBDeviceEvent(batteryEvent);
+                    break;
+                case ColmiR0xConstants.CMD_PHONE_NAME:
+                    LOG.info("Received phone name response: {}", StringUtils.bytesToHex(value));
+                    break;
+                case ColmiR0xConstants.CMD_PREFERENCES:
+                    LOG.info("Received user preferences response: {}", StringUtils.bytesToHex(value));
+                    break;
+                case ColmiR0xConstants.CMD_SYNC_HEART_RATE:
+                    LOG.info("Received HR history sync packet: {}", StringUtils.bytesToHex(value));
+                    int hrPacketNr = value[1] & 0xff;
+                    if (hrPacketNr == 0xff) {
+                        LOG.info("Empty HR history, sync aborted");
+                        getDevice().unsetBusyTask();
+                        getDevice().sendDeviceUpdateIntent(getContext());
+                    } else if (hrPacketNr == 0) {
+                        packetsTotalNr = value[2];
+                        LOG.info("HR history packet {} out of total {}", hrPacketNr, packetsTotalNr);
+                    } else {
+                        LOG.info("HR history packet {} out of total {} (data for {}:00-{}:00)", hrPacketNr, packetsTotalNr, hrPacketNr-1, hrPacketNr);
+                        Calendar sampleCal = (Calendar) syncingDay.clone();
+                        int startValue = hrPacketNr == 1 ? 6 : 2;  // packet 1 contains the sync-from timestamp in bytes 2-5
+                        int minutesInPreviousPackets = 0;
+                        if (hrPacketNr == 1) {
+                            int timestamp = BLETypeConversions.toUint32(value[2], value[3], value[4], value[5]);
+                            Date timestampDate = DateTimeUtils.parseTimeStamp(timestamp);
+                            LOG.info("Receiving HR history sequence with timestamp {}", DateTimeUtils.formatIso8601UTC(timestampDate));
+                        } else {
+                            minutesInPreviousPackets = 9 * 5;  // packet 1
+                            minutesInPreviousPackets += (hrPacketNr - 2) * 13 * 5;
+                        }
+                        for (int i = startValue; i < value.length - 1; i++) {
+                            if (value[i] != 0x00) {
+                                // Determine time of day
+                                int minuteOfDay = minutesInPreviousPackets + (i - startValue) * 5;
+                                sampleCal.set(Calendar.HOUR_OF_DAY, minuteOfDay / 60);
+                                sampleCal.set(Calendar.MINUTE, minuteOfDay % 60);
+                                sampleCal.set(Calendar.SECOND, 0);
+                                LOG.info("Value {} is {} bpm, time of day is {}", i, value[i] & 0xff, sampleCal.getTime());
+                                // Build sample object and save in database
+                                try (DBHandler db = GBApplication.acquireDB()) {
+                                    ColmiHeartRateSampleProvider sampleProvider = new ColmiHeartRateSampleProvider(getDevice(), db.getDaoSession());
+                                    Long userId = DBHelper.getUser(db.getDaoSession()).getId();
+                                    Long deviceId = DBHelper.getDevice(getDevice(), db.getDaoSession()).getId();
+                                    ColmiHeartRateSample gbSample = new ColmiHeartRateSample();
+                                    gbSample.setDeviceId(deviceId);
+                                    gbSample.setUserId(userId);
+                                    gbSample.setTimestamp(sampleCal.getTimeInMillis());
+                                    gbSample.setHeartRate(value[i] & 0xff);
+                                    sampleProvider.addSample(gbSample);
+                                } catch (Exception e) {
+                                    LOG.error("Error acquiring database for recording heart rate samples", e);
+                                }
+                            }
+                        }
+                        if (hrPacketNr == packetsTotalNr - 1) {
+                            getDevice().unsetBusyTask();
+                            getDevice().sendDeviceUpdateIntent(getContext());
+                        }
+                    }
+                    if (!getDevice().isBusy()) {
+                        if (daysAgo < 7) {
+                            daysAgo++;
+                            fetchHistoryHR();
+                        } else {
+                            fetchHistoryStress();
+                        }
+                    }
+                    break;
+                case ColmiR0xConstants.CMD_AUTO_HR_PREF:
+                    ColmiR0xPacketHandler.hrIntervalSettings(this, value);
+                    break;
+                case ColmiR0xConstants.CMD_GOALS:
+                    ColmiR0xPacketHandler.goalsSettings(value);
+                    break;
+                case ColmiR0xConstants.CMD_AUTO_SPO2_PREF:
+                    ColmiR0xPacketHandler.spo2Settings(this, value);
+                    break;
+                case ColmiR0xConstants.CMD_PACKET_SIZE:
+                    LOG.info("Received packet size indicator: {} bytes", value[1] & 0xff);
+                    break;
+                case ColmiR0xConstants.CMD_AUTO_STRESS_PREF:
+                    ColmiR0xPacketHandler.stressSettings(this, value);
+                    break;
+                case ColmiR0xConstants.CMD_AUTO_HRV_PREF:
+                    ColmiR0xPacketHandler.hrvSettings(this, value);
+                    break;
+                case ColmiR0xConstants.CMD_SYNC_STRESS:
+                    ColmiR0xPacketHandler.historicalStress(getDevice(), getContext(), value);
+                    if (!getDevice().isBusy()) {
+                        fetchHistorySpo2();
+                    }
+                    break;
+                case ColmiR0xConstants.CMD_SYNC_ACTIVITY:
+                    ColmiR0xPacketHandler.historicalActivity(getDevice(), getContext(), value);
+                    if (!getDevice().isBusy()) {
+                        if (daysAgo < 7) {
+                            daysAgo++;
+                            fetchHistoryActivity();
+                        } else {
+                            daysAgo = 0;
+                            fetchHistoryHR();
+                        }
+                    }
+                    break;
+                case ColmiR0xConstants.CMD_SYNC_HRV:
+                    getDevice().setBusyTask(getContext().getString(R.string.busy_task_fetch_hrv_data));
+                    ColmiR0xPacketHandler.historicalHRV(getDevice(), getContext(), value, daysAgo);
+                    if (!getDevice().isBusy()) {
+                        if (daysAgo < 6) {
+                            daysAgo++;
+                            fetchHistoryHRV();
+                        } else {
+                            fetchRecordedDataFinished();
+                        }
+                    }
+                    break;
+                case ColmiR0xConstants.CMD_FIND_DEVICE:
+                    LOG.info("Received find device response: {}", StringUtils.bytesToHex(value));
+                    break;
+                case ColmiR0xConstants.CMD_MANUAL_HEART_RATE:
+                    ColmiR0xPacketHandler.liveHeartRate(getDevice(), getContext(), value);
+                    break;
+                case ColmiR0xConstants.CMD_NOTIFICATION:
+                    switch (value[1]) {
+                        case ColmiR0xConstants.NOTIFICATION_NEW_HR_DATA:
+                            LOG.info("Received notification from ring that new HR data is available to sync");
+                            break;
+                        case ColmiR0xConstants.NOTIFICATION_NEW_SPO2_DATA:
+                            LOG.info("Received notification from ring that new SpO2 data is available to sync");
+                            break;
+                        case ColmiR0xConstants.NOTIFICATION_NEW_STEPS_DATA:
+                            LOG.info("Received notification from ring that new steps data is available to sync");
+                            break;
+                        case ColmiR0xConstants.NOTIFICATION_BATTERY_LEVEL:
+                            int levelNotif = value[2];
+                            charging = value[3] == 1;
+                            LOG.info("Received battery level notification: {}% (charging: {})", levelNotif, charging);
+                            GBDeviceEventBatteryInfo batteryNotifEvent = createDeviceBatteryInfoEvent(levelNotif, charging);
+                            evaluateGBDeviceEvent(batteryNotifEvent);
+                            break;
+                        case ColmiR0xConstants.NOTIFICATION_LIVE_ACTIVITY:
+                            ColmiR0xPacketHandler.liveActivity(value);
+                            break;
+                        default:
+                            LOG.info("Received unrecognized notification: {}", StringUtils.bytesToHex(value));
+                            break;
+                    }
+                    break;
+                default:
+                    LOG.info("Received unrecognized packet: {}", StringUtils.bytesToHex(value));
+                    break;
+            }
+        }
+        if (characteristicUUID.equals(ColmiR0xConstants.CHARACTERISTIC_NOTIFY_V2)) {
+            // Big data responses can arrive in multiple packets that need to be concatenated
+            if (bigDataPacket != null) {
+                LOG.debug("Received {} bytes on big data characteristic while waiting for follow-up data", value.length);
+                bigDataPacket.rewind();
+                ByteBuffer concatenated = ByteBuffer
+                        .allocate(bigDataPacket.limit() + value.length)
+                        .put(bigDataPacket)
+                        .put(value);
+                bigDataPacket = concatenated;
+                if (bigDataPacket.limit() < bigDataPacketSize + 6) {
+                    // If the received data is smaller than the expected packet size (+ 6 bytes header),
+                    // wait for the next packet and append it
+                    LOG.debug("Big data packet is not complete yet, got {} bytes while expecting {}+6. Waiting for more...", bigDataPacket.limit(), bigDataPacketSize);
+                    return true;
+                } else {
+                    value = bigDataPacket.array();
+                    bigDataPacket = null;
+                    LOG.debug("Big data packet complete, got {} bytes while expecting {}+6", value.length, bigDataPacketSize);
+                }
+            }
+            switch (value[0]) {
+                case ColmiR0xConstants.CMD_BIG_DATA_V2:
+                    int packetLength = BLETypeConversions.toUint16(value[2], value[3]);
+                    if (value.length < packetLength + 6) {
+                        // If the received packet is smaller than the expected packet size (+ 6 bytes header),
+                        // wait for the next packet and append it
+                        LOG.debug("Big data packet is not complete yet, got {} bytes while expecting {}+6. Waiting for more...", value.length, packetLength);
+                        bigDataPacketSize = packetLength;
+                        bigDataPacket = ByteBuffer.wrap(value);
+                        return true;
+                    }
+                    switch (value[1]) {
+                        case ColmiR0xConstants.BIG_DATA_TYPE_SLEEP:
+                            ColmiR0xPacketHandler.historicalSleep(getDevice(), getContext(), value);
+
+                            daysAgo = 0;
+                            fetchHistoryHRV();
+
+                            // Signal history sync finished at this point, since older firmwares
+                            // will not send anything back after requesting HRV history
+                            fetchRecordedDataFinished();
+                            break;
+                        case ColmiR0xConstants.BIG_DATA_TYPE_SPO2:
+                            ColmiR0xPacketHandler.historicalSpo2(getDevice(), value);
+                            fetchHistorySleep();
+                            break;
+                        default:
+                            LOG.info("Received unrecognized big data packet: {}", StringUtils.bytesToHex(value));
+                            break;
+                    }
+                    break;
+                default:
+                    LOG.info("Received unrecognized notify v2 packet: {}", StringUtils.bytesToHex(value));
+                    break;
+            }
+            return true;
+        }
+
+        return false;
+    }
+
+    @NonNull
+    private static GBDeviceEventBatteryInfo createDeviceBatteryInfoEvent(int levelResponse, boolean charging) {
+        GBDeviceEventBatteryInfo batteryEvent = new GBDeviceEventBatteryInfo();
+        batteryEvent.level = levelResponse;
+        batteryEvent.state = charging ? BatteryState.BATTERY_CHARGING : BatteryState.BATTERY_NORMAL;
+        return batteryEvent;
+    }
+
+    private byte[] buildPacket(byte[] contents) {
+        ByteBuffer buffer = ByteBuffer.allocate(16);
+        if (contents.length <= 15) {
+            buffer.put(contents);
+            int checksum = 0;
+            for (byte content : contents) {
+                checksum = (byte) (checksum + content) & 0xff;
+            }
+            buffer.put(15, (byte) checksum);
+        } else {
+            LOG.warn("Packet content too long!");
+        }
+        return buffer.array();
+    }
+
+    private void sendWrite(String taskName, byte[] contents) {
+        TransactionBuilder builder = new TransactionBuilder(taskName);
+        BluetoothGattCharacteristic characteristic = getCharacteristic(ColmiR0xConstants.CHARACTERISTIC_WRITE);
+        if (characteristic != null) {
+            builder.write(characteristic, contents);
+            builder.queue(getQueue());
+        }
+    }
+
+    private void sendCommand(String taskName, byte[] contents) {
+        TransactionBuilder builder = new TransactionBuilder(taskName);
+        BluetoothGattCharacteristic characteristic = getCharacteristic(ColmiR0xConstants.CHARACTERISTIC_COMMAND);
+        if (characteristic != null) {
+            builder.write(characteristic, contents);
+            builder.queue(getQueue());
+        }
+    }
+
+    private void requestBatteryInfo() {
+        byte[] batteryRequestPacket = buildPacket(new byte[]{ColmiR0xConstants.CMD_BATTERY});
+        LOG.info("Battery request sent: {}", StringUtils.bytesToHex(batteryRequestPacket));
+        sendWrite("batteryRequest", batteryRequestPacket);
+    }
+
+    private void setPhoneName() {
+        byte[] setPhoneNamePacket = buildPacket(new byte[]{
+                ColmiR0xConstants.CMD_PHONE_NAME,
+                0x02, // Client major version
+                0x0a, // Client minor version
+                'G',
+                'B'
+        });
+        LOG.info("Phone name sent: {}", StringUtils.bytesToHex(setPhoneNamePacket));
+        sendWrite("phoneNameRequest", setPhoneNamePacket);
+    }
+
+    private void setDateTime() {
+        Calendar now = GregorianCalendar.getInstance();
+        byte[] setDateTimePacket = buildPacket(new byte[]{
+                ColmiR0xConstants.CMD_SET_DATE_TIME,
+                Byte.parseByte(String.valueOf(now.get(Calendar.YEAR) % 2000), 16),
+                Byte.parseByte(String.valueOf(now.get(Calendar.MONTH) + 1), 16),
+                Byte.parseByte(String.valueOf(now.get(Calendar.DAY_OF_MONTH)), 16),
+                Byte.parseByte(String.valueOf(now.get(Calendar.HOUR_OF_DAY)), 16),
+                Byte.parseByte(String.valueOf(now.get(Calendar.MINUTE)), 16),
+                Byte.parseByte(String.valueOf(now.get(Calendar.SECOND)), 16)
+        });
+        LOG.info("Set date/time request sent: {}", StringUtils.bytesToHex(setDateTimePacket));
+        sendWrite("dateTimeRequest", setDateTimePacket);
+    }
+
+    @Override
+    public void onSetTime() {
+        setDateTime();
+    }
+
+    @Override
+    public void onSendConfiguration(String config) {
+        final Prefs prefs = getDevicePrefs();
+        switch (config) {
+            case SettingsActivity.PREF_MEASUREMENT_SYSTEM:
+                setUserPreferences();
+                break;
+            case DeviceSettingsPreferenceConst.PREF_SPO2_ALL_DAY_MONITORING:
+                final boolean spo2Enabled = prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_SPO2_ALL_DAY_MONITORING, false);
+                byte[] spo2PrefsPacket = buildPacket(new byte[]{ColmiR0xConstants.CMD_AUTO_SPO2_PREF, ColmiR0xConstants.PREF_WRITE, (byte) (spo2Enabled ? 0x01 : 0x00)});
+                LOG.info("SpO2 preference request sent: {}", StringUtils.bytesToHex(spo2PrefsPacket));
+                sendWrite("spo2PreferenceRequest", spo2PrefsPacket);
+                break;
+            case DeviceSettingsPreferenceConst.PREF_HEARTRATE_STRESS_MONITORING:
+                final boolean stressEnabled = prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_HEARTRATE_STRESS_MONITORING, false);
+                byte[] stressPrefsPacket = buildPacket(new byte[]{ColmiR0xConstants.CMD_AUTO_STRESS_PREF, ColmiR0xConstants.PREF_WRITE, (byte) (stressEnabled ? 0x01 : 0x00)});
+                LOG.info("Stress preference request sent: {}", StringUtils.bytesToHex(stressPrefsPacket));
+                sendWrite("stressPreferenceRequest", stressPrefsPacket);
+                break;
+            case DeviceSettingsPreferenceConst.PREF_HRV_ALL_DAY_MONITORING:
+                final boolean hrvEnabled = prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_HRV_ALL_DAY_MONITORING, false);
+                byte[] hrvPrefsPacket = buildPacket(new byte[]{ColmiR0xConstants.CMD_AUTO_HRV_PREF, ColmiR0xConstants.PREF_WRITE, (byte) (hrvEnabled ? 0x01 : 0x00)});
+                LOG.info("HRV preference request sent: {}", StringUtils.bytesToHex(hrvPrefsPacket));
+                sendWrite("hrvPreferenceRequest", hrvPrefsPacket);
+                break;
+        }
+    }
+
+    @Override
+    public void onSetHeartRateMeasurementInterval(int seconds) {
+        // Round to nearest 5 minutes and limit to 60 minutes due to device constraints
+        long hrIntervalMins = Math.min(Math.round(seconds / 60.0 / 5.0) * 5, 60);
+        byte[] hrIntervalPacket = buildPacket(new byte[]{
+                ColmiR0xConstants.CMD_AUTO_HR_PREF,
+                ColmiR0xConstants.PREF_WRITE,
+                hrIntervalMins > 0 ? (byte) 0x01 : (byte) 0x02,
+                (byte) hrIntervalMins
+        });
+        LOG.info("HR interval preference request sent: {}", StringUtils.bytesToHex(hrIntervalPacket));
+        sendWrite("hrIntervalPreferenceRequest", hrIntervalPacket);
+    }
+
+    private void setUserPreferences() {
+        final Prefs prefs = getDevicePrefs();
+        final ActivityUser user = new ActivityUser();
+        final String measurementSystem = prefs.getString(SettingsActivity.PREF_MEASUREMENT_SYSTEM, "metric");
+        byte userGender;
+        switch (user.getGender()) {
+            case ActivityUser.GENDER_FEMALE:
+                userGender = 0x01;
+                break;
+            case ActivityUser.GENDER_MALE:
+                userGender = 0x00;
+                break;
+            default:
+                userGender = 0x02;
+                break;
+        }
+        byte[] userPrefsPacket = buildPacket(new byte[]{
+                ColmiR0xConstants.CMD_PREFERENCES,
+                ColmiR0xConstants.PREF_WRITE,
+                0x00,  // 24h format, 0x01 is 12h format
+                (byte) ("metric".equals(measurementSystem) ? 0x00 : 0x01),
+                userGender,
+                (byte) user.getAge(),
+                (byte) user.getHeightCm(),
+                (byte) user.getWeightKg(),
+                0x00,  // systolic blood pressure (e.g. 120)
+                0x00,  // diastolic blood pressure (e.g. 90)
+                0x00  // heart rate value warning threshold: (e.g. 160)
+        });
+        LOG.info("User preferences request sent: {}", StringUtils.bytesToHex(userPrefsPacket));
+        sendWrite("userPreferenceRequest", userPrefsPacket);
+    }
+
+    private void requestSettingsFromRing() {
+        byte[] request = buildPacket(new byte[]{ColmiR0xConstants.CMD_AUTO_HR_PREF, ColmiR0xConstants.PREF_READ});
+        LOG.info("Request HR measurement interval from ring: {}", StringUtils.bytesToHex(request));
+        sendWrite("hrIntervalRequest", request);
+        request = buildPacket(new byte[]{ColmiR0xConstants.CMD_AUTO_STRESS_PREF, ColmiR0xConstants.PREF_READ});
+        LOG.info("Request stress measurement setting from ring: {}", StringUtils.bytesToHex(request));
+        sendWrite("stressSettingRequest", request);
+        request = buildPacket(new byte[]{ColmiR0xConstants.CMD_AUTO_SPO2_PREF, ColmiR0xConstants.PREF_READ});
+        LOG.info("Request SpO2 measurement setting from ring: {}", StringUtils.bytesToHex(request));
+        sendWrite("spo2SettingRequest", request);
+        request = buildPacket(new byte[]{ColmiR0xConstants.CMD_AUTO_HRV_PREF, ColmiR0xConstants.PREF_READ});
+        LOG.info("Request HRV measurement setting from ring: {}", StringUtils.bytesToHex(request));
+        sendWrite("hrvSettingRequest", request);
+        request = buildPacket(new byte[]{ColmiR0xConstants.CMD_GOALS, ColmiR0xConstants.PREF_READ});
+        LOG.info("Request goals from ring: {}", StringUtils.bytesToHex(request));
+        sendWrite("goalsSettingRequest", request);
+    }
+
+    @Override
+    public void onPowerOff() {
+        byte[] poweroffPacket = buildPacket(new byte[]{ColmiR0xConstants.CMD_POWER_OFF, 0x01});
+        LOG.info("Poweroff request sent: {}", StringUtils.bytesToHex(poweroffPacket));
+        sendWrite("poweroffRequest", poweroffPacket);
+    }
+
+    @Override
+    public void onReset(int flags) {
+        if ((flags & GBDeviceProtocol.RESET_FLAGS_FACTORY_RESET) != 0) {
+            byte[] resetPacket = buildPacket(new byte[]{ColmiR0xConstants.CMD_FACTORY_RESET, 0x66, 0x66});
+            LOG.info("Factory reset request sent: {}", StringUtils.bytesToHex(resetPacket));
+            sendWrite("resetRequest", resetPacket);
+        }
+    }
+
+    @Override
+    public void onFindDevice(boolean start) {
+        if (!start) return;
+
+        byte[] findDevicePacket = buildPacket(new byte[]{ColmiR0xConstants.CMD_FIND_DEVICE, 0x55, (byte) 0xAA});
+        LOG.info("Find device request sent: {}", StringUtils.bytesToHex(findDevicePacket));
+        sendWrite("findDeviceRequest", findDevicePacket);
+    }
+
+    @Override
+    public void onHeartRateTest() {
+        byte[] measureHeartRatePacket = buildPacket(new byte[]{ColmiR0xConstants.CMD_MANUAL_HEART_RATE, 0x01});
+        LOG.info("Measure HR request sent: {}", StringUtils.bytesToHex(measureHeartRatePacket));
+        sendWrite("measureHRRequest", measureHeartRatePacket);
+    }
+
+    @Override
+    public void onFetchRecordedData(int dataTypes) {
+        GB.updateTransferNotification(getContext().getString(R.string.busy_task_fetch_activity_data), "", true, 0, getContext());
+        daysAgo = 0;
+        fetchHistoryActivity();
+    }
+
+    private void fetchRecordedDataFinished() {
+        GB.updateTransferNotification(null, "", false, 100, getContext());
+        LOG.info("Sync finished!");
+        getDevice().unsetBusyTask();
+        getDevice().sendDeviceUpdateIntent(getContext());
+        GB.signalActivityDataFinish(getDevice());
+    }
+
+    private void fetchHistoryActivity() {
+        getDevice().setBusyTask(getContext().getString(R.string.busy_task_fetch_activity_data));
+        getDevice().sendDeviceUpdateIntent(getContext());
+        syncingDay = Calendar.getInstance();
+        syncingDay.add(Calendar.DAY_OF_MONTH, 0 - daysAgo);
+        syncingDay.set(Calendar.HOUR_OF_DAY, 0);
+        syncingDay.set(Calendar.MINUTE, 0);
+        syncingDay.set(Calendar.SECOND, 0);
+        syncingDay.set(Calendar.MILLISECOND, 0);
+        byte[] activityHistoryRequest = buildPacket(new byte[]{ColmiR0xConstants.CMD_SYNC_ACTIVITY, (byte) daysAgo, 0x0f, 0x00, 0x5f, 0x01});
+        LOG.info("Fetch historical activity data request sent: {}", StringUtils.bytesToHex(activityHistoryRequest));
+        sendWrite("activityHistoryRequest", activityHistoryRequest);
+    }
+
+    private void fetchHistoryHR() {
+        getDevice().setBusyTask(getContext().getString(R.string.busy_task_fetch_hr_data));
+        getDevice().sendDeviceUpdateIntent(getContext());
+        syncingDay = Calendar.getInstance();
+        if (daysAgo != 0) {
+            syncingDay.add(Calendar.DAY_OF_MONTH, 0 - daysAgo);
+            syncingDay.set(Calendar.HOUR_OF_DAY, 0);
+            syncingDay.set(Calendar.MINUTE, 0);
+            syncingDay.set(Calendar.SECOND, 0);
+        }
+        syncingDay.set(Calendar.MILLISECOND, 0);
+        ByteBuffer hrHistoryRequestBB = ByteBuffer.allocate(5);
+        hrHistoryRequestBB.order(ByteOrder.LITTLE_ENDIAN);
+        hrHistoryRequestBB.put(0, ColmiR0xConstants.CMD_SYNC_HEART_RATE);
+        long requestTimestamp = syncingDay.getTimeInMillis() + syncingDay.get(Calendar.ZONE_OFFSET) + syncingDay.get(Calendar.DST_OFFSET);
+        hrHistoryRequestBB.putInt(1, (int) (requestTimestamp / 1000));
+        byte[] hrHistoryRequest = buildPacket(hrHistoryRequestBB.array());
+        LOG.info("Fetch historical HR data request sent ({}): {}", DateTimeUtils.formatIso8601(syncingDay.getTime()), StringUtils.bytesToHex(hrHistoryRequest));
+        sendWrite("hrHistoryRequest", hrHistoryRequest);
+    }
+
+    private void fetchHistoryStress() {
+        getDevice().setBusyTask(getContext().getString(R.string.busy_task_fetch_stress_data));
+        getDevice().sendDeviceUpdateIntent(getContext());
+        syncingDay = Calendar.getInstance();
+        byte[] stressHistoryRequest = buildPacket(new byte[]{ColmiR0xConstants.CMD_SYNC_STRESS});
+        LOG.info("Fetch historical stress data request sent: {}", StringUtils.bytesToHex(stressHistoryRequest));
+        sendWrite("stressHistoryRequest", stressHistoryRequest);
+    }
+
+    private void fetchHistorySpo2() {
+        getDevice().setBusyTask(getContext().getString(R.string.busy_task_fetch_spo2_data));
+        getDevice().sendDeviceUpdateIntent(getContext());
+        byte[] spo2HistoryRequest = new byte[]{
+                ColmiR0xConstants.CMD_BIG_DATA_V2,
+                ColmiR0xConstants.BIG_DATA_TYPE_SPO2,
+                0x01,
+                0x00,
+                (byte) 0xff,
+                0x00,
+                (byte) 0xff
+        };
+        LOG.info("Fetch historical SpO2 data request sent: {}", StringUtils.bytesToHex(spo2HistoryRequest));
+        sendCommand("spo2HistoryRequest", spo2HistoryRequest);
+    }
+
+    private void fetchHistorySleep() {
+        getDevice().setBusyTask(getContext().getString(R.string.busy_task_fetch_sleep_data));
+        getDevice().sendDeviceUpdateIntent(getContext());
+        byte[] sleepHistoryRequest = new byte[]{
+                ColmiR0xConstants.CMD_BIG_DATA_V2,
+                ColmiR0xConstants.BIG_DATA_TYPE_SLEEP,
+                0x01,
+                0x00,
+                (byte) 0xff,
+                0x00,
+                (byte) 0xff
+        };
+        LOG.info("Fetch historical sleep data request sent: {}", StringUtils.bytesToHex(sleepHistoryRequest));
+        sendCommand("sleepHistoryRequest", sleepHistoryRequest);
+    }
+
+    private void fetchHistoryHRV() {
+        getDevice().sendDeviceUpdateIntent(getContext());
+        syncingDay = Calendar.getInstance();
+        if (daysAgo != 0) {
+            syncingDay.add(Calendar.DAY_OF_MONTH, 0 - daysAgo);
+            syncingDay.set(Calendar.HOUR_OF_DAY, 0);
+            syncingDay.set(Calendar.MINUTE, 0);
+        }
+        syncingDay.set(Calendar.SECOND, 0);
+        syncingDay.set(Calendar.MILLISECOND, 0);
+        ByteBuffer hrvHistoryRequestBB = ByteBuffer.allocate(5);
+        hrvHistoryRequestBB.order(ByteOrder.LITTLE_ENDIAN);
+        hrvHistoryRequestBB.put(0, ColmiR0xConstants.CMD_SYNC_HRV);
+        hrvHistoryRequestBB.putInt(1, daysAgo);
+        byte[] hrvHistoryRequest = buildPacket(hrvHistoryRequestBB.array());
+        LOG.info("Fetch historical HRV data request sent ({}): {}", syncingDay.getTime(), StringUtils.bytesToHex(hrvHistoryRequest));
+        sendWrite("hrvHistoryRequest", hrvHistoryRequest);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/common/SimpleNotification.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/common/SimpleNotification.java
new file mode 100644
index 0000000..3274a38
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/common/SimpleNotification.java
@@ -0,0 +1,44 @@
+/*  Copyright (C) 2017-2024 Andreas Shimokawa, Carsten Pfeiffer
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.common;
+
+import nodomain.freeyourgadget.gadgetbridge.model.NotificationType;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotification.AlertCategory;
+
+public class SimpleNotification {
+    private final String message;
+    private final AlertCategory alertCategory;
+    private final NotificationType notificationType;
+
+    public SimpleNotification(String message, AlertCategory alertCategory, NotificationType notificationType) {
+        this.message = message;
+        this.alertCategory = alertCategory;
+        this.notificationType = notificationType;
+    }
+
+    public AlertCategory getAlertCategory() {
+        return alertCategory;
+    }
+
+    public NotificationType getNotificationType() {
+        return notificationType;
+    }
+
+    public String getMessage() {
+        return message;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/cycling_sensor/support/CyclingSensorBaseSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/cycling_sensor/support/CyclingSensorBaseSupport.java
new file mode 100644
index 0000000..cdf4197
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/cycling_sensor/support/CyclingSensorBaseSupport.java
@@ -0,0 +1,26 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.cycling_sensor.support;
+
+import org.slf4j.Logger;
+
+import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
+
+public class CyclingSensorBaseSupport extends AbstractBTLEDeviceSupport {
+    public CyclingSensorBaseSupport(Logger logger) {
+        super(logger);
+    }
+
+    @Override
+    public boolean useAutoConnect() {
+        return false;
+    }
+
+    @Override
+    public boolean getImplicitCallbackModify() {
+        return true;
+    }
+
+    @Override
+    public boolean getSendWriteRequestResponse() {
+        return false;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/cycling_sensor/support/CyclingSensorSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/cycling_sensor/support/CyclingSensorSupport.java
new file mode 100644
index 0000000..3c74d4e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/cycling_sensor/support/CyclingSensorSupport.java
@@ -0,0 +1,268 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.cycling_sensor.support;
+
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.content.Intent;
+
+import androidx.annotation.NonNull;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
+import nodomain.freeyourgadget.gadgetbridge.devices.cycling_sensor.db.CyclingSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.CyclingSample;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.entities.User;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.NotifyAction;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.ReadAction;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.battery.BatteryInfoProfile;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+public class CyclingSensorSupport extends CyclingSensorBaseSupport {
+    static class CyclingSpeedCadenceMeasurement {
+        private static final int FLAG_REVOLUTION_DATA_PRESENT = 1 << 0;
+        private static final int FLAG_CADENCE_DATA_PRESENT    = 1 << 1;
+        public boolean revolutionDataPresent = false;
+        public boolean cadenceDataPresent = false;
+        public int revolutionCount;
+        public int lastRevolutionTimeTicks;
+        private int crankRevolutionCount;
+        private int lastCrankRevolutionTimeTicks;
+
+        public static CyclingSpeedCadenceMeasurement fromPayload(byte[] payload) throws RuntimeException {
+            if(payload.length < 7){
+                throw new RuntimeException("wrong payload length");
+            }
+
+            ByteBuffer buffer = ByteBuffer
+                    .wrap(payload)
+                    .order(ByteOrder.LITTLE_ENDIAN);
+
+            byte flags = buffer.get();
+
+            boolean revolutionDataPresent = (flags | FLAG_REVOLUTION_DATA_PRESENT) == FLAG_REVOLUTION_DATA_PRESENT;
+            boolean cadenceDataPresent = (flags | FLAG_CADENCE_DATA_PRESENT) == FLAG_CADENCE_DATA_PRESENT;
+            CyclingSpeedCadenceMeasurement result = new CyclingSpeedCadenceMeasurement();
+
+            if(revolutionDataPresent){
+                result.revolutionDataPresent = true;
+                result.revolutionCount = buffer.getInt() & 0xFFFFFFFF; // remove sign
+                result.lastRevolutionTimeTicks = buffer.getShort() & 0xFFFF;
+            }
+
+            if(cadenceDataPresent){
+                result.cadenceDataPresent = true;
+                result.crankRevolutionCount = buffer.getInt();
+                result.lastCrankRevolutionTimeTicks = buffer.getShort();
+            }
+
+            return result;
+        }
+
+        @NonNull
+        @Override
+        public String toString() {
+            return String.format("Measurement revolutions: %d, time ticks %d", revolutionCount, lastRevolutionTimeTicks);
+        }
+    }
+
+    public final static UUID UUID_CYCLING_SENSOR_SERVICE =
+            UUID.fromString("00001816-0000-1000-8000-00805f9b34fb");
+    public final static UUID UUID_CYCLING_SENSOR_CSC_MEASUREMENT =
+            UUID.fromString("00002a5b-0000-1000-8000-00805f9b34fb");
+
+    private static final Logger logger = LoggerFactory.getLogger(CyclingSensorSupport.class);
+
+    private long persistenceInterval;
+    private long nextPersistenceTimestamp = 0;
+
+    private float wheelCircumferenceMeters;
+
+    private CyclingSpeedCadenceMeasurement lastReportedMeasurement = null;
+    private long lastMeasurementTime = 0;
+
+    private BluetoothGattCharacteristic batteryCharacteristic = null;
+
+    public CyclingSensorSupport() {
+        super(logger);
+
+        addSupportedService(UUID_CYCLING_SENSOR_SERVICE);
+        addSupportedService(BatteryInfoProfile.SERVICE_UUID);
+    }
+
+    @Override
+    public void onSendConfiguration(String config) {
+        switch (config){
+            case DeviceSettingsPreferenceConst.PREF_CYCLING_SENSOR_PERSISTENCE_INTERVAL:
+            case DeviceSettingsPreferenceConst.PREF_CYCLING_SENSOR_WHEEL_DIAMETER:
+                loadConfiguration();
+                break;
+        }
+    }
+
+    private void loadConfiguration(){
+        Prefs deviceSpecificPrefs = new Prefs(
+                GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress())
+        );
+        persistenceInterval = deviceSpecificPrefs.getInt(DeviceSettingsPreferenceConst.PREF_CYCLING_SENSOR_PERSISTENCE_INTERVAL, 60) * 1000;
+        nextPersistenceTimestamp = 0;
+        
+        float wheelDiameter = deviceSpecificPrefs.getFloat(DeviceSettingsPreferenceConst.PREF_CYCLING_SENSOR_WHEEL_DIAMETER, 29);
+        wheelCircumferenceMeters = (float)(wheelDiameter * 2.54 * Math.PI) / 100;
+    }
+
+    @Override
+    protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
+        builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext()));
+
+        BluetoothGattCharacteristic measurementCharacteristic =
+                getCharacteristic(UUID_CYCLING_SENSOR_CSC_MEASUREMENT);
+
+        builder.add(new NotifyAction(measurementCharacteristic, true));
+
+        builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZED, getContext()));
+        batteryCharacteristic = getCharacteristic(BatteryInfoProfile.UUID_CHARACTERISTIC_BATTERY_LEVEL);
+
+        if(batteryCharacteristic != null){
+            builder.add(new ReadAction(batteryCharacteristic));
+        }
+
+        loadConfiguration();
+
+        gbDevice.setFirmwareVersion("1.0.0");
+
+        return builder;
+    }
+
+    private void handleMeasurementCharacteristic(BluetoothGattCharacteristic characteristic){
+        byte[] value = characteristic.getValue();
+        if(value == null || value.length < 7){
+            logger.error("Measurement characteristic value length smaller than 7");
+            return;
+        }
+
+        CyclingSpeedCadenceMeasurement measurement = null;
+
+        try {
+            measurement = CyclingSpeedCadenceMeasurement.fromPayload(value);
+        }catch (RuntimeException e){
+            // do nothing, measurement stays null
+        }
+
+        if(measurement == null){
+            return;
+        }
+
+        if(!measurement.revolutionDataPresent){
+            return;
+        }
+
+        handleCyclingSpeedMeasurement(measurement);
+    }
+
+    private void handleCyclingSpeedMeasurement(CyclingSpeedCadenceMeasurement currentMeasurement) {
+        logger.debug("Measurement " + currentMeasurement);
+
+        long now = System.currentTimeMillis();
+
+        Float speed = null;
+
+        long lastMeasurementDelta = (now - lastMeasurementTime);
+
+        if(lastMeasurementDelta <= 30_000){
+            int ticksPassed = currentMeasurement.lastRevolutionTimeTicks - lastReportedMeasurement.lastRevolutionTimeTicks;
+            // every second is subdivided in 1024 ticks
+            int millisDelta = (int)(ticksPassed * (1000f / 1024f));
+
+            if(millisDelta > 0) {
+                int revolutionsDelta = currentMeasurement.revolutionCount - lastReportedMeasurement.revolutionCount;
+
+                float revolutionsPerSecond = revolutionsDelta * (1000f / millisDelta);
+
+                speed = revolutionsPerSecond * wheelCircumferenceMeters;
+            }
+        }
+
+        lastReportedMeasurement = currentMeasurement;
+        lastMeasurementTime = now;
+
+        CyclingSample sample = new CyclingSample();
+
+        if (currentMeasurement.revolutionDataPresent) {
+            sample.setRevolutionCount(currentMeasurement.revolutionCount);
+            sample.setSpeed(speed);
+            sample.setDistance(currentMeasurement.revolutionCount * wheelCircumferenceMeters);
+        }
+
+        sample.setTimestamp(now);
+
+        Intent liveIntent = new Intent(DeviceService.ACTION_REALTIME_SAMPLES);
+        liveIntent.putExtra(GBDevice.EXTRA_DEVICE, getDevice());
+        liveIntent.putExtra(DeviceService.EXTRA_REALTIME_SAMPLE, sample);
+        liveIntent.putExtra("EXTRA_DEVICE_ADDRESS", getDevice().getAddress());
+        LocalBroadcastManager.getInstance(getContext())
+                .sendBroadcast(liveIntent);
+
+
+        if(now < nextPersistenceTimestamp){
+            // too early
+            return;
+        }
+
+        nextPersistenceTimestamp = now + persistenceInterval;
+
+        try(DBHandler handler = GBApplication.acquireDB()) {
+            DaoSession session = handler.getDaoSession();
+
+            CyclingSampleProvider sampleProvider =
+                    new CyclingSampleProvider(getDevice(), session);
+
+            Device databaseDevice = DBHelper.getDevice(getDevice(), session);
+            User databaseUser = DBHelper.getUser(session);
+            sample.setDevice(databaseDevice);
+            sample.setUser(databaseUser);
+
+            sampleProvider.addSample(sample);
+        } catch (Exception e) {
+            // throw new RuntimeException(e);
+            logger.error("failed adding DB cycling sample");
+        }
+    }
+
+    @Override
+    public boolean onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
+        byte[] value = characteristic.getValue();
+
+        if(characteristic.equals(batteryCharacteristic) && value != null && value.length == 1){
+            GBDeviceEventBatteryInfo info = new GBDeviceEventBatteryInfo();
+            info.level = characteristic.getValue()[0];
+            handleGBDeviceEvent(info);
+        }
+
+        return true;
+    }
+
+    @Override
+    public boolean onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
+        if(characteristic.getUuid().equals(UUID_CYCLING_SENSOR_CSC_MEASUREMENT)){
+            handleMeasurementCharacteristic(characteristic);
+            return true;
+        }
+        return false;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/divoom/PixooIOThread.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/divoom/PixooIOThread.java
new file mode 100644
index 0000000..40c99f2
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/divoom/PixooIOThread.java
@@ -0,0 +1,62 @@
+/*  Copyright (C) 2023-2024 Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+
+package nodomain.freeyourgadget.gadgetbridge.service.devices.divoom;
+
+import static nodomain.freeyourgadget.gadgetbridge.util.GB.hexdump;
+
+import android.bluetooth.BluetoothAdapter;
+import android.content.Context;
+import android.net.Uri;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.btclassic.BtClassicIoThread;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.nothing.NothingProtocol;
+
+public class PixooIOThread extends BtClassicIoThread {
+    private static final Logger LOG = LoggerFactory.getLogger(PixooIOThread.class);
+    private final PixooProtocol mPixooProtocol;
+
+
+    @Override
+    protected void initialize() {
+        write(mPixooProtocol.encodeReqestAlarms());
+
+        setUpdateState(GBDevice.State.INITIALIZED);
+    }
+
+    public PixooIOThread(GBDevice device, Context context, PixooProtocol deviceProtocol,
+                         PixooSupport PixooSupport, BluetoothAdapter bluetoothAdapter) {
+        super(device, context, deviceProtocol, PixooSupport, bluetoothAdapter);
+        mPixooProtocol = deviceProtocol;
+    }
+
+    @Override
+    protected byte[] parseIncoming(InputStream inStream) throws IOException {
+        byte[] buffer = new byte[1048576]; //HUGE read
+        int bytes = inStream.read(buffer);
+        LOG.debug("read " + bytes + " bytes. " + hexdump(buffer, 0, bytes));
+        return Arrays.copyOf(buffer, bytes);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/divoom/PixooProtocol.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/divoom/PixooProtocol.java
new file mode 100644
index 0000000..2046804
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/divoom/PixooProtocol.java
@@ -0,0 +1,492 @@
+/*  Copyright (C) 2023-2024 Andreas Shimokawa, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+
+package nodomain.freeyourgadget.gadgetbridge.service.devices.divoom;
+
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.provider.MediaStore;
+
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import org.apache.commons.lang3.ArrayUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+
+import lineageos.weather.util.WeatherUtils;
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.SettingsActivity;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo;
+import nodomain.freeyourgadget.gadgetbridge.entities.Alarm;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
+import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
+import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol;
+import nodomain.freeyourgadget.gadgetbridge.util.BitmapUtil;
+import nodomain.freeyourgadget.gadgetbridge.util.NotificationUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
+
+public class PixooProtocol extends GBDeviceProtocol {
+    private static final Logger LOG = LoggerFactory.getLogger(PixooProtocol.class);
+
+    private boolean isFirstExchange = true;
+
+    protected PixooProtocol(GBDevice device) {
+        super(device);
+    }
+
+    @Override
+    public GBDeviceEvent[] decodeResponse(byte[] responseData) {
+        List<GBDeviceEvent> devEvts = new ArrayList<>();
+
+        if (isFirstExchange) {
+            isFirstExchange = false;
+            devEvts.add(new GBDeviceEventVersionInfo()); //TODO: this is a weird hack to make the DBHelper happy. Replace with proper firmware detection
+        }
+
+        ByteBuffer incoming = ByteBuffer.wrap(responseData);
+        incoming.order(ByteOrder.LITTLE_ENDIAN);
+        if (incoming.get() != 0x01) {
+            LOG.warn("first byte not 0x01");
+            return devEvts.toArray(new GBDeviceEvent[0]);
+        }
+        int length = incoming.getShort() & 0xffff;
+        byte status = incoming.get(); // unsure
+        if (status != 0x04) {
+            LOG.warn("status byte not 0x04");
+            return devEvts.toArray(new GBDeviceEvent[0]);
+        }
+        byte endpoint = incoming.get(); // unsure
+        LOG.info("endpoint " + endpoint);
+        if (endpoint == 0x42) {
+            decodeAlarms(incoming);
+        }
+        return devEvts.toArray(new GBDeviceEvent[0]);
+    }
+
+    private void decodeAlarms(ByteBuffer incoming) {
+        byte unknown = incoming.get();
+        if (unknown != 0x55) { // expected
+            LOG.warn("unexpected byte when decoding Alarms " + unknown);
+            return;
+        }
+        // Map of alarm position to Alarm, as returned by the band
+        final Map<Integer, nodomain.freeyourgadget.gadgetbridge.model.Alarm> payloadAlarms = new HashMap<>();
+
+        while (incoming.remaining() > 10) {
+            int position = incoming.get();
+            boolean enabled = incoming.get() == 1;
+            int hour = incoming.get();
+            int minute = incoming.get();
+            int repeatMask = incoming.get();
+            int unknown2 = incoming.getInt();
+            byte unknown3 = incoming.get(); // normally 0x01, on fresh alarms 0x32
+            final Alarm alarm = new nodomain.freeyourgadget.gadgetbridge.entities.Alarm();
+            alarm.setEnabled(enabled);
+            alarm.setPosition(position);
+            alarm.setHour(hour);
+            alarm.setMinute(minute);
+            alarm.setRepetition(repeatMask);
+            alarm.setUnused(unknown3 == 0x32 && !enabled);
+            payloadAlarms.put(position, alarm);
+        }
+        final List<nodomain.freeyourgadget.gadgetbridge.entities.Alarm> dbAlarms = DBHelper.getAlarms(getDevice());
+        int numUpdatedAlarms = 0;
+
+        for (nodomain.freeyourgadget.gadgetbridge.entities.Alarm alarm : dbAlarms) {
+            final int pos = alarm.getPosition();
+            final nodomain.freeyourgadget.gadgetbridge.model.Alarm updatedAlarm = payloadAlarms.get(pos);
+            final boolean alarmNeedsUpdate = updatedAlarm == null ||
+                    alarm.getUnused() != updatedAlarm.getUnused() ||
+                    alarm.getEnabled() != updatedAlarm.getEnabled() ||
+                    alarm.getSmartWakeup() != updatedAlarm.getSmartWakeup() ||
+                    alarm.getHour() != updatedAlarm.getHour() ||
+                    alarm.getMinute() != updatedAlarm.getMinute() ||
+                    alarm.getRepetition() != updatedAlarm.getRepetition();
+
+            if (alarmNeedsUpdate) {
+                numUpdatedAlarms++;
+                LOG.info("Updating alarm index={}, unused={}", pos, updatedAlarm == null);
+                alarm.setUnused(updatedAlarm == null);
+                if (updatedAlarm != null) {
+                    alarm.setEnabled(updatedAlarm.getEnabled());
+                    alarm.setUnused(updatedAlarm.getUnused());
+                    alarm.setSmartWakeup(updatedAlarm.getSmartWakeup());
+                    alarm.setHour(updatedAlarm.getHour());
+                    alarm.setMinute(updatedAlarm.getMinute());
+                    alarm.setRepetition(updatedAlarm.getRepetition());
+                }
+                DBHelper.store(alarm);
+            }
+        }
+
+        if (numUpdatedAlarms > 0) {
+            final Intent intent = new Intent(DeviceService.ACTION_SAVE_ALARMS);
+            LocalBroadcastManager.getInstance(GBApplication.getContext()).sendBroadcast(intent);
+        }
+    }
+
+    @Override
+    public byte[] encodeSendConfiguration(String config) {
+        SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress());
+
+        switch (config) {
+            case DeviceSettingsPreferenceConst.PREF_DEVICE_NAME:
+                final byte[] deviceName = StringUtils.truncateToBytes(prefs.getString(DeviceSettingsPreferenceConst.PREF_DEVICE_NAME, ""), 26);
+                return encodeProtocol(ByteBuffer.allocate(2 + deviceName.length)
+                        .order(ByteOrder.LITTLE_ENDIAN)
+                        .put((byte) 0x75)
+                        .put((byte) deviceName.length)
+                        .put(deviceName)
+                        .array());
+            case DeviceSettingsPreferenceConst.PREF_SCREEN_BRIGHTNESS:
+                byte brightness = (byte) prefs.getInt(DeviceSettingsPreferenceConst.PREF_SCREEN_BRIGHTNESS, 50);
+                LOG.debug("setting brightness to " + brightness);
+                return encodeProtocol(new byte[]{
+                        0x74,
+                        brightness
+                });
+            case DeviceSettingsPreferenceConst.PREF_TIMEFORMAT:
+                final String timeFormat = getDevicePrefs().getTimeFormat();
+                final boolean is24hour = DeviceSettingsPreferenceConst.PREF_TIMEFORMAT_24H.equals(timeFormat);
+
+                return encodeProtocol(new byte[]{
+                        0x2d,
+                        (byte) (is24hour ? 1 : 0),
+                });
+            case DeviceSettingsPreferenceConst.PREF_CLAP_HANDS_TO_WAKEUP_DEVICE:
+                boolean clap = prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_CLAP_HANDS_TO_WAKEUP_DEVICE, false);
+
+                return encodeProtocol(new byte[]{
+                        (byte) 0xa7,
+                        (byte) (clap ? 1 : 0),
+                });
+            case DeviceSettingsPreferenceConst.PREF_POWER_SAVING:
+                boolean power_saving = prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_POWER_SAVING, false);
+
+                return encodeProtocol(new byte[]{
+                        (byte) 0xb2,
+                        (byte) (power_saving ? 1 : 0),
+                });
+
+        }
+
+        return super.encodeSendConfiguration(config);
+    }
+
+    @Override
+    public byte[] encodeSetCallState(String number, String name, int command) {
+        if (command == CallSpec.CALL_OUTGOING) {
+            return null;
+        }
+        return encodeProtocol(new byte[]{
+                0x50,
+                (byte) (command == CallSpec.CALL_INCOMING ? 5 : 6),
+        });
+    }
+
+    @Override
+    public byte[] encodeNotification(NotificationSpec notificationSpec) {
+        if (!getDevicePrefs().getBoolean(DeviceSettingsPreferenceConst.PREF_SEND_APP_NOTIFICATIONS, true)) {
+            LOG.debug("App notifications disabled - ignoring");
+            return null;
+        }
+
+        byte iconID;
+        switch (notificationSpec.type) {
+            case KAKAO_TALK:
+                iconID = 0;
+                break;
+            case INSTAGRAM:
+                iconID = 1;
+                break;
+            case SNAPCHAT:
+                iconID = 2;
+                break;
+            case FACEBOOK:
+                iconID = 3;
+                break;
+            case TWITTER:
+                iconID = 4;
+                break;
+            case WHATSAPP:
+            case GENERIC_SMS:
+                iconID = 8;
+                break;
+            case SKYPE:
+                iconID = 9;
+                break;
+            case LINE:
+                iconID = 10;
+                break;
+            case WECHAT:
+                iconID = 11;
+                break;
+            case VIBER:
+                iconID = 12;
+                break;
+            case GENERIC_ALARM_CLOCK:
+                iconID = 17;
+                break;
+            default:
+                iconID = 0x20;
+        }
+
+        return encodeProtocol(new byte[]{
+                0x50,
+                iconID,
+        });
+
+    }
+
+    @Override
+    public byte[] encodeSetTime() {
+        Calendar now = BLETypeConversions.createCalendar();
+        return encodeProtocol(new byte[]{
+                0x18,
+                (byte) (now.get(Calendar.YEAR) % 100),
+                (byte) (now.get(Calendar.YEAR) / 100),
+                (byte) (now.get(Calendar.MONTH) + 1),
+                (byte) now.get(Calendar.DAY_OF_MONTH),
+                (byte) now.get(Calendar.HOUR_OF_DAY),
+                (byte) now.get(Calendar.MINUTE),
+                (byte) now.get(Calendar.SECOND)
+        });
+
+    }
+
+    @Override
+    public byte[] encodeSetAlarms(ArrayList<? extends nodomain.freeyourgadget.gadgetbridge.model.Alarm> alarms) {
+        byte[] complete_command = new byte[]{};
+        for (nodomain.freeyourgadget.gadgetbridge.model.Alarm alarm : alarms) {
+            byte[] cmd = new byte[]{
+                    0x43,
+                    (byte) alarm.getPosition(),
+                    (byte) (alarm.getEnabled() && !alarm.getUnused() ? 1 : 0),
+                    (byte) alarm.getHour(),
+                    (byte) alarm.getMinute(),
+                    (byte) alarm.getRepetition(),
+                    0, 0, 0, 0,
+                    (byte) (alarm.getUnused() ? 0x32 : 0x00)};
+
+            complete_command = ArrayUtils.addAll(complete_command, encodeProtocol(cmd));
+        }
+        return complete_command;
+    }
+
+
+    @Override
+    public byte[] encodeSendWeather(WeatherSpec weatherSpec) {
+        byte pixooWeatherCode = 0;
+        if (weatherSpec.currentConditionCode >= 200 && weatherSpec.currentConditionCode <= 299) {
+            pixooWeatherCode = 5;
+        } else if (weatherSpec.currentConditionCode >= 300 && weatherSpec.currentConditionCode < 600) {
+            pixooWeatherCode = 6;
+        } else if (weatherSpec.currentConditionCode >= 600 && weatherSpec.currentConditionCode < 700) {
+            pixooWeatherCode = 8;
+        } else if (weatherSpec.currentConditionCode >= 700 && weatherSpec.currentConditionCode < 800) {
+            pixooWeatherCode = 9;
+        } else if (weatherSpec.currentConditionCode == 800) {
+            pixooWeatherCode = 1;
+        } else if (weatherSpec.currentConditionCode >= 801 && weatherSpec.currentConditionCode <= 804) {
+            pixooWeatherCode = 3;
+        }
+
+        byte temp = (byte) (weatherSpec.currentTemp - 273);
+        String units = GBApplication.getPrefs().getString(SettingsActivity.PREF_MEASUREMENT_SYSTEM, GBApplication.getContext().getString(R.string.p_unit_metric));
+        if (units.equals(GBApplication.getContext().getString(R.string.p_unit_imperial))) {
+            temp = (byte) WeatherUtils.celsiusToFahrenheit(temp);
+        }
+
+        return encodeProtocol(new byte[]{
+                0x5f,
+                temp,
+                pixooWeatherCode,
+        });
+    }
+
+    private byte[] encodeDrawIcon(String packageName) {
+        Drawable icon = NotificationUtils.getAppIcon(GBApplication.getContext(), packageName);
+        if (icon == null) {
+            LOG.warn("could not get icon for package: " + packageName);
+            return null;
+        }
+        final Bitmap bmp = BitmapUtil.toBitmap(icon);
+
+        return encodeShowFrame(bmp);
+    }
+
+    protected byte[] encodeShowFrame(Uri uri) {
+        try {
+            Bitmap bitmap = MediaStore.Images.Media.getBitmap(GBApplication.getContext().getContentResolver(), uri);
+            return encodeShowFrame(bitmap);
+        } catch (IOException e) {
+            LOG.error("could not decode Image",e);
+        }
+        return null;
+    }
+
+    private byte[] encodeShowFrame(Bitmap bitmap) {
+        final Bitmap bmpResized = Bitmap.createBitmap(16, 16, Bitmap.Config.ARGB_8888);
+        final Canvas canvas = new Canvas(bmpResized);
+        final Rect rect = new Rect(0, 0, 16, 16);
+        canvas.drawBitmap(bitmap, null, rect, null);
+
+
+        // construct palette with unique colors
+        HashSet<Integer> palette = new HashSet<>();
+        for (int y = 0; y < 16; y++) {
+            for (int x = 0; x < 16; x++) {
+                int pixel = bmpResized.getPixel(x, y);
+                palette.add(pixel);
+            }
+        }
+        // convert to lookup of index
+        HashMap<Integer, Integer> paletteLookup = new HashMap<>();
+        int index = 0;
+        for (int color : palette) {
+            paletteLookup.put(color, index++);
+        }
+
+        int paletteMaxIndex = palette.size() - 1;
+        int bpp = 1;
+        while ((paletteMaxIndex >>= 1) != 0) {
+            bpp++;
+        }
+        LOG.info("got palette of {} colors, will need {}bpp", palette.size(), bpp);
+        byte[] header = new byte[]{0x44, 0x00, 0x0a, 0x0a, 0x04, (byte) 0xaa, 0x53, 0x00, (byte) 0xf4, 0x01, 0x00, (byte) palette.size()};
+        int pixels_size = (bpp * 16 * 16) / 8;
+        byte[] pixels = new byte[pixels_size];
+        ByteBuffer buf = ByteBuffer.allocate(palette.size() * 3 + header.length + pixels_size);
+        buf.put(header);
+        for (int color : palette) {
+            buf.put((byte) ((color >> 16) & 0xff));
+            buf.put((byte) ((color >> 8) & 0xff));
+            buf.put((byte) (color & 0xff));
+        }
+        int bitposition = 0;
+        for (int y = 0; y < 16; y++) {
+            for (int x = 0; x < 16; x++) {
+                int pixel = bmpResized.getPixel(x, y);
+                index = paletteLookup.get(pixel);
+                int pos = bitposition / 8;
+                int shift = bitposition % 8;
+                pixels[pos] = (byte) (pixels[pos] | (byte) (index << shift));
+                if (shift + bpp > 8) {
+                    shift = -shift + 8;
+                    pos++;
+                    pixels[pos] = (byte) (pixels[pos] | (byte) (index >> shift));
+                }
+                bitposition += bpp;
+            }
+        }
+        buf.put(pixels);
+
+        return encodeProtocol(buf.array());
+    }
+
+    private byte[] encodeClockModeCommand(int clockMode, boolean showTime, boolean showWeather, boolean showTemperature, boolean showDate,
+                                          int r, int g, int b) {
+
+        r = Math.min(r, 127);
+        g = Math.min(g, 127);
+        b = Math.min(b, 127);
+
+        return encodeProtocol(new byte[]{
+                0x45,
+                0x00,
+                0x01, // unknown, can be 0 or 1
+                (byte) clockMode,
+                (byte) (showTime ? 0x01 : 0x00), // ignored it seems
+                (byte) (showWeather ? 0x01 : 0x00),
+                (byte) (showTemperature ? 0x01 : 0x00),
+                (byte) (showDate ? 0x01 : 0x00),
+                (byte) r, (byte) g, (byte) b
+        });
+
+    }
+
+    private byte[] encodeAudioVisualisationModeCommand(int visualisationMode) {
+        return encodeProtocol(new byte[]{
+                0x45,
+                0x04,
+                (byte) visualisationMode,
+        });
+    }
+
+    private byte[] encodeEffectModeCommand(int effectMode) {
+        return encodeProtocol(new byte[]{
+                0x45,
+                0x03,
+                (byte) effectMode,
+        });
+    }
+
+    public byte[] encodeReqestAlarms() {
+        return encodeProtocol(new byte[]{0x42});
+    }
+
+    @Override
+    public byte[] encodeTestNewFunction() {
+        //return encodeAudioModeCommand(1); // works
+        //return encodeEffectModeCommand(5); // does nothing
+        //return encodeClockModeCommand(0, true, true, false, true, 127, 127, 127); // works r,g,b up to 127
+        return encodeDrawIcon("nodomain.freeyourgadget.gadgetbridge");
+        //return encodeDrawIcon("com.benny.openlauncher");
+    }
+
+    byte[] encodeProtocol(byte[] payload) {
+
+        ByteBuffer msgBuf = ByteBuffer.allocate(6 + payload.length);
+        msgBuf.order(ByteOrder.LITTLE_ENDIAN);
+        msgBuf.put((byte) 0x01);
+        msgBuf.putShort((short) (payload.length + 2));
+        msgBuf.put(payload);
+        short crc = (short) (((payload.length + 2) & 0xff) + ((payload.length + 2) >> 8));
+        for (byte b : payload) {
+            crc += (b & 0xff);
+        }
+        msgBuf.putShort(crc);
+        msgBuf.put((byte) 0x02);
+        return msgBuf.array();
+    }
+
+}
+
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/divoom/PixooSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/divoom/PixooSupport.java
new file mode 100644
index 0000000..d08cc47
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/divoom/PixooSupport.java
@@ -0,0 +1,69 @@
+/*  Copyright (C) 2023-2024 Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+
+package nodomain.freeyourgadget.gadgetbridge.service.devices.divoom;
+
+import android.net.Uri;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.serial.AbstractSerialDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceIoThread;
+import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol;
+
+public class PixooSupport extends AbstractSerialDeviceSupport {
+    private static final Logger LOG = LoggerFactory.getLogger(PixooSupport.class);
+
+
+    @Override
+    public void onSendConfiguration(String config) {
+        super.onSendConfiguration(config);
+    }
+
+    @Override
+    public boolean connect() {
+        getDeviceIOThread().start();
+        return true;
+    }
+
+    @Override
+    public synchronized PixooIOThread getDeviceIOThread() {
+        return (PixooIOThread) super.getDeviceIOThread();
+    }
+
+    @Override
+    public boolean useAutoConnect() {
+        return false;
+    }
+
+    protected GBDeviceProtocol createDeviceProtocol() {
+        return new PixooProtocol(getDevice());
+    }
+
+    @Override
+    public void onInstallApp(Uri uri) {
+        getDeviceIOThread().write(((PixooProtocol) getDeviceProtocol()).encodeShowFrame(uri));
+    }
+
+    @Override
+    protected GBDeviceIoThread createDeviceIOThread() {
+        return new PixooIOThread(getDevice(), getContext(), (PixooProtocol) getDeviceProtocol(),
+                PixooSupport.this, getBluetoothAdapter());
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/domyos/DomyosT540Support.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/domyos/DomyosT540Support.java
new file mode 100644
index 0000000..8e62743
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/domyos/DomyosT540Support.java
@@ -0,0 +1,314 @@
+/*  Copyright (C) 2020-2024 Andreas Shimokawa, Arjan Schrijver
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.domyos;
+
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.content.Intent;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.GattService;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.IntentListener;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo.DeviceInfo;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo.DeviceInfoProfile;
+
+public class DomyosT540Support extends AbstractBTLEDeviceSupport {
+
+    private static final Logger LOG = LoggerFactory.getLogger(DomyosT540Support.class);
+    private static final UUID UUUD_SERVICE_DOMYOS = UUID.fromString("49535343-FE7D-4AE5-8FA9-9FAFD205E455");
+    private static final UUID UUUD_CHARACTERISTICS_WRITE = UUID.fromString("49535343-8841-43F4-A8D4-ECBE34729BB3");
+    private static final UUID UUUD_CHARACTERISTICS_NOTIFY = UUID.fromString("49535343-1E4D-4BD9-BA61-23C647249616");
+    // those were captured during init, most of them seem to request information on the device, we leave it out for now
+    //private static final byte[] COMMAND_UNKNOWN_INIT1 = new byte[]{(byte) 0xf0, (byte) 0xc9, (byte) 0xb9};
+    //private static final byte[] COMMAND_UNKNOWN_INIT2 = new byte[]{(byte) 0xf0, (byte) 0xa3, (byte) 0x93};
+    private static final byte[] COMMAND_STOP = new byte[]{(byte) 0xf0, (byte) 0xc8, (byte) 0x00, (byte) 0xb8};
+    private static final byte[] COMMAND_START = new byte[]{(byte) 0xf0, (byte) 0xc8, (byte) 0x01, (byte) 0xb9};
+    private static final byte[] COMMAND_SET_PARAMETERS = new byte[]{(byte) 0xf0, (byte) 0xad, (byte) 0xff, (byte) 0xff, 0x00, 0x00, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, 0x00, 0x00, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x00};
+    private static final byte[] COMMAND_INIT_DISPLAY = new byte[]{(byte) 0xf0, (byte) 0xcb, 0x02, 0x00, 0x08, (byte) 0xff, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, (byte) 0xcc};
+
+    //private static final byte[] COMMAND_UNKNOWN_INIT4 = new byte[]{(byte) 0xf0, (byte) 0xa4, (byte) 0x94};
+    //private static final byte[] COMMAND_UNKNOWN_INIT5 = new byte[]{(byte) 0xf0, (byte) 0xa5, (byte) 0x95};
+    //private static final byte[] COMMAND_UNKNOWN_INIT6 = new byte[]{(byte) 0xf0, (byte) 0xab, (byte) 0x9b};
+    private static final byte[] COMMAND_REQUEST_DATA = new byte[]{(byte) 0xf0, (byte) 0xac, (byte) 0x9c};
+    private final DeviceInfoProfile<DomyosT540Support> deviceInfoProfile;
+    private final GBDeviceEventVersionInfo versionCmd = new GBDeviceEventVersionInfo();
+    private final byte[] last_data;
+    private int start_time = 0;
+    private int last_time = 0;
+
+    public DomyosT540Support() {
+        super(LOG);
+        addSupportedService(GattService.UUID_SERVICE_GENERIC_ACCESS);
+        addSupportedService(GattService.UUID_SERVICE_GENERIC_ATTRIBUTE);
+        addSupportedService(GattService.UUID_SERVICE_DEVICE_INFORMATION);
+        addSupportedService(UUUD_SERVICE_DOMYOS);
+
+        deviceInfoProfile = new DeviceInfoProfile<>(this);
+        IntentListener mListener = new IntentListener() {
+            @Override
+            public void notify(Intent intent) {
+                String s = intent.getAction();
+                if (s.equals(DeviceInfoProfile.ACTION_DEVICE_INFO)) {
+                    DeviceInfo deviceInfo = intent.getParcelableExtra(DeviceInfoProfile.EXTRA_DEVICE_INFO);
+                    if (deviceInfo != null) {
+                        handleDeviceInfo(deviceInfo);
+                    }
+                }
+            }
+        };
+        deviceInfoProfile.addListener(mListener);
+        addSupportedProfile(deviceInfoProfile);
+        last_data = new byte[26];
+    }
+
+
+    @Override
+    protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
+        builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext()));
+        requestDeviceInfo(builder);
+        enableNotifications(builder, true);
+        setParameters(builder, 1.0f, 0, true);
+        writeChunked(builder, COMMAND_INIT_DISPLAY);
+        setInitialized(builder);
+        writeChunked(builder, COMMAND_REQUEST_DATA);
+        return builder;
+    }
+
+    private void requestDeviceInfo(TransactionBuilder builder) {
+        LOG.debug("Requesting Device Info!");
+        deviceInfoProfile.requestDeviceInfo(builder);
+    }
+
+    private void enableNotifications(TransactionBuilder builder, boolean enable) {
+        builder.notify(getCharacteristic(UUUD_CHARACTERISTICS_NOTIFY), enable);
+    }
+
+    private void setInitialized(TransactionBuilder builder) {
+        builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZED, getContext()));
+    }
+
+    private void setDisplayValues(TransactionBuilder builder, int elapsedTime, int kCal, int heartRate, float incline, float speed, float distance) {
+        ByteBuffer buffer = ByteBuffer.allocate(27);
+        buffer.order(ByteOrder.BIG_ENDIAN);
+
+        buffer.putShort((short) 0xf0cb);
+
+        buffer.put((byte) 0x03);
+        buffer.put((byte) (elapsedTime / 60));
+        buffer.put((byte) (elapsedTime % 60));
+        buffer.put((byte) 0xff);
+
+        buffer.put((byte) 0x01);
+        buffer.putShort((short) kCal);
+        buffer.put((byte) 0x00);
+
+        buffer.put((byte) 0x01);
+        buffer.putShort((short) heartRate);
+        buffer.put((byte) 0x00);
+
+        buffer.put((byte) 0x01);
+        buffer.putShort(((short) (incline * 10)));
+        buffer.put((byte) 0x01);
+
+        buffer.put((byte) 0x01);
+        buffer.putShort((short) (speed * 10));
+        buffer.put((byte) 0x01);
+
+        buffer.put((byte) 0x01);
+        buffer.putShort((short) (distance * 10));
+        buffer.put((byte) 0x01);
+
+        buffer.put((byte) 0x00);
+
+        byte[] cmdSetDisplayValues = buffer.array();
+
+        cmdSetDisplayValues[cmdSetDisplayValues.length - 1] = getChecksum(cmdSetDisplayValues);
+
+        writeChunked(builder, cmdSetDisplayValues);
+    }
+
+    private void setParameters(TransactionBuilder builder, float speed, float incline, boolean btledOn) {
+        byte[] cmdSetParameters = COMMAND_SET_PARAMETERS.clone();
+        int intSpeed = (int) (speed * 10);
+        int intIncline = (int) (incline * 10);
+        cmdSetParameters[4] = (byte) (intSpeed >> 8);
+        cmdSetParameters[5] = (byte) (intSpeed & 0xff);
+        cmdSetParameters[13] = (byte) (intIncline >> 8);
+        cmdSetParameters[14] = (byte) (intIncline & 0xff);
+        cmdSetParameters[18] = (byte) (btledOn ? 0x01 : 0x00);
+
+        cmdSetParameters[cmdSetParameters.length - 1] = getChecksum(cmdSetParameters);
+
+        writeChunked(builder, cmdSetParameters);
+    }
+
+    void writeChunked(TransactionBuilder builder, byte[] data) {
+        final int MAX_CHUNKLENGTH = 20;
+        int remaining = data.length;
+        byte count = 0;
+        while (remaining > 0) {
+            int copybytes = Math.min(remaining, MAX_CHUNKLENGTH);
+            byte[] chunk = new byte[copybytes];
+
+            System.arraycopy(data, count++ * MAX_CHUNKLENGTH, chunk, 0, copybytes);
+            builder.write(getCharacteristic(UUUD_CHARACTERISTICS_WRITE), chunk);
+            remaining -= copybytes;
+        }
+        builder.wait(100);
+    }
+
+    private byte getChecksum(byte[] command) {
+        byte checksum = 0;
+        for (byte b : command) {
+            checksum += +b;
+        }
+        return checksum;
+    }
+
+    private void handleDeviceInfo(nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo.DeviceInfo info) {
+        versionCmd.hwVersion = info.getHardwareRevision();
+        versionCmd.fwVersion = info.getFirmwareRevision();
+        handleGBDeviceEvent(versionCmd);
+    }
+
+
+    @Override
+    public boolean useAutoConnect() {
+        return false;
+    }
+
+    @Override
+    public void onFindDevice(boolean start) {
+        byte[] command = new byte[]{(byte) 0xf0, (byte) 0xaf, (byte) (start ? 0x01 : 0x00), 0x00};
+        command[3] = getChecksum(command);
+        BluetoothGattCharacteristic characteristic = getCharacteristic(UUUD_CHARACTERISTICS_WRITE);
+
+        TransactionBuilder builder = new TransactionBuilder("beep");
+        builder.write(characteristic, command);
+        builder.queue(getQueue());
+
+    }
+
+    @Override
+    public boolean onCharacteristicChanged(BluetoothGatt gatt,
+                                           BluetoothGattCharacteristic characteristic) {
+        if (super.onCharacteristicChanged(gatt, characteristic)) {
+            return true;
+        }
+
+        UUID characteristicUUID = characteristic.getUuid();
+
+        if (characteristicUUID.equals(UUUD_CHARACTERISTICS_NOTIFY)) {
+            byte[] data = characteristic.getValue();
+            if (data.length == 6) { // FIXME: this is assumed the tail of the data below which does not fit inside the MTU
+                System.arraycopy(data, 0, last_data, 20, 6);
+                ByteBuffer buf = ByteBuffer.wrap(last_data);
+                buf.order(ByteOrder.BIG_ENDIAN);
+                buf.getShort(); // command
+                float incline = buf.getShort() / 10.0f;
+                if (incline >= 100) {
+                    incline -= 100;
+                }
+                buf.getShort(); // ??
+                float speed = buf.getShort() / 10.0f;
+                buf.getShort(); // ??
+                int calories = buf.getShort();
+                float distance = buf.getShort() / 10.0f;
+                buf.getShort(); // ??
+                boolean tabletStandUsed = buf.get() > 0;
+                int heartRate = buf.getShort();
+                float averageSpeed = buf.getShort() / 10.0f;
+                boolean keyPluggedIn = buf.get() > 0;
+                byte buttonCode = buf.get();
+                buf.get(); // ??
+                boolean workoutStarted = buf.get() > 0;
+
+                TransactionBuilder builder = new TransactionBuilder("send update");
+
+                if (buttonCode == 6 || buttonCode == 7) {
+                    if (workoutStarted || buttonCode == 7) {
+                        writeChunked(builder, COMMAND_STOP);
+                    } else {
+                        writeChunked(builder, COMMAND_START);
+                        start_time = (int) (System.currentTimeMillis() / 1000);
+                    }
+                }
+                builder.wait(200);
+                writeChunked(builder, COMMAND_REQUEST_DATA);
+
+                int time = (int) (System.currentTimeMillis() / 1000);
+                if (last_time != time) {
+                    int timeElapsed = time - start_time;
+                    setDisplayValues(builder, timeElapsed, calories, heartRate, incline, speed, distance);
+                    last_time = time;
+                }
+
+                builder.queue(getQueue());
+
+                LOG.debug("speed: " + speed + " incline: " + incline + " distance: " + distance + " calories: " + calories + " average speed: " + averageSpeed + " heart rate: " + heartRate);
+                LOG.debug("key plugged in: " + keyPluggedIn + " tablet stand used: " + tabletStandUsed + " buttonCode: " + buttonCode + " workout started: " + workoutStarted);
+            } else if (data.length == 20 && data[0] == (byte) 0xf0 && data[1] == (byte) 0xbc) {
+                System.arraycopy(data, 0, last_data, 0, 20);
+            }
+            return true;
+        }
+        LOG.info("Unhandled characteristic changed: " + characteristicUUID);
+        return false;
+    }
+
+    @Override
+    public boolean onCharacteristicRead(BluetoothGatt gatt,
+                                        BluetoothGattCharacteristic characteristic, int status) {
+        if (super.onCharacteristicRead(gatt, characteristic, status)) {
+            return true;
+        }
+        UUID characteristicUUID = characteristic.getUuid();
+
+        LOG.info("Unhandled characteristic read: " + characteristicUUID);
+        return false;
+    }
+
+    @Override
+    public void onTestNewFunction() {
+        TransactionBuilder builder = new TransactionBuilder("xxx");
+        //setDisplayValues(builder, 1, 10, 10, 10, 10);
+        //writeChunked(builder, COMMAND_SET_DISPLAY);
+
+        builder.queue(getQueue());
+    }
+
+    @Override
+    public boolean getImplicitCallbackModify() {
+        return true;
+    }
+
+    @Override
+    public boolean getSendWriteRequestResponse() {
+        return false;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/femometer/FemometerVinca2DeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/femometer/FemometerVinca2DeviceSupport.java
new file mode 100644
index 0000000..8f50da0
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/femometer/FemometerVinca2DeviceSupport.java
@@ -0,0 +1,309 @@
+/*  Copyright (C) 2023-2024 Alicia Hormann
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.femometer;
+
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.content.SharedPreferences;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo;
+import nodomain.freeyourgadget.gadgetbridge.devices.femometer.FemometerVinca2SampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.FemometerVinca2TemperatureSample;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
+import nodomain.freeyourgadget.gadgetbridge.model.BatteryState;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.GattCharacteristic;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.GattService;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.IntentListener;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.battery.BatteryInfo;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.battery.BatteryInfoProfile;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo.DeviceInfo;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo.DeviceInfoProfile;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.healthThermometer.HealthThermometerProfile;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.healthThermometer.TemperatureInfo;
+
+public class FemometerVinca2DeviceSupport extends AbstractBTLEDeviceSupport {
+
+    private final DeviceInfoProfile<FemometerVinca2DeviceSupport> deviceInfoProfile;
+    private final BatteryInfoProfile<FemometerVinca2DeviceSupport> batteryInfoProfile;
+    private final HealthThermometerProfile<FemometerVinca2DeviceSupport> healthThermometerProfile;
+    private static final Logger LOG = LoggerFactory.getLogger(FemometerVinca2DeviceSupport.class);
+
+    public static final UUID UNKNOWN_SERVICE_UUID = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "fef5")));
+    // Characteristic 8082caa8-41a6-4021-91c6-56f9b954cc34 READ WRITE
+    // Characteristic 9d84b9a3-000c-49d8-9183-855b673fda31 READ WRITE
+    // Characteristic 457871e8-d516-4ca1-9116-57d0b17b9cb2 READ WRITE NO RESPONSE WRITE
+    // Characteristic 5f78df94-798c-46f5-990a-b3eb6a065c88 READ NOTIFY
+
+    public static final UUID CONFIGURATION_SERVICE_UUID = UUID.fromString("0f0e0d0c-0b0a-0908-0706-050403020100");
+    public static final UUID CONFIGURATION_SERVICE_ALARM_CHARACTERISTIC = UUID.fromString("1f1e1d1c-1b1a-1918-1716-151413121110"); // READ WRITE
+    public static final UUID CONFIGURATION_SERVICE_SETTING_CHARACTERISTIC = UUID.fromString("2f2e2d2c-2b2a-2928-2726-252423222120"); // WRITE
+    public static final UUID CONFIGURATION_SERVICE_INDICATION_CHARACTERISTIC = UUID.fromString("3f3e3d3c-3b3a-3938-3736-353433323130"); // INDICATE
+
+    public FemometerVinca2DeviceSupport() {
+        super(LOG);
+
+        /// Initialize Services
+        addSupportedService(GattService.UUID_SERVICE_GENERIC_ACCESS);
+        addSupportedService(GattService.UUID_SERVICE_GENERIC_ATTRIBUTE);
+        addSupportedService(GattService.UUID_SERVICE_BATTERY_SERVICE);
+        addSupportedService(GattService.UUID_SERVICE_DEVICE_INFORMATION);
+        addSupportedService(GattService.UUID_SERVICE_HEALTH_THERMOMETER);
+        addSupportedService(GattService.UUID_SERVICE_CURRENT_TIME);
+        addSupportedService(GattService.UUID_SERVICE_REFERENCE_TIME_UPDATE);
+        addSupportedService(UNKNOWN_SERVICE_UUID);
+        addSupportedService(CONFIGURATION_SERVICE_UUID);
+
+        /// Device Info
+        IntentListener deviceInfoListener = intent -> {
+            String action = intent.getAction();
+            if (DeviceInfoProfile.ACTION_DEVICE_INFO.equals(action)) {
+                DeviceInfo info = intent.getParcelableExtra(DeviceInfoProfile.EXTRA_DEVICE_INFO);
+                if (info == null) return;
+                GBDeviceEventVersionInfo versionCmd = new GBDeviceEventVersionInfo();
+                versionCmd.hwVersion = info.getHardwareRevision();
+                versionCmd.fwVersion = info.getSoftwareRevision(); // firmwareRevision always reported as null
+                handleGBDeviceEvent(versionCmd);
+            }
+        };
+
+        deviceInfoProfile = new DeviceInfoProfile<>(this);
+        deviceInfoProfile.addListener(deviceInfoListener);
+        addSupportedProfile(deviceInfoProfile);
+
+        /// Battery
+        IntentListener batteryListener = intent -> {
+            BatteryInfo info = intent.getParcelableExtra(BatteryInfoProfile.EXTRA_BATTERY_INFO);
+            if (info == null) return;
+            GBDeviceEventBatteryInfo batteryEvent = new GBDeviceEventBatteryInfo();
+            batteryEvent.state = BatteryState.BATTERY_NORMAL;
+            batteryEvent.level = info.getPercentCharged();
+            evaluateGBDeviceEvent(batteryEvent);
+            handleGBDeviceEvent(batteryEvent);
+        };
+        batteryInfoProfile = new BatteryInfoProfile<>(this);
+        batteryInfoProfile.addListener(batteryListener);
+        addSupportedProfile(batteryInfoProfile);
+
+
+        /// Temperature
+        IntentListener temperatureListener = intent -> {
+            TemperatureInfo info = intent.getParcelableExtra(HealthThermometerProfile.EXTRA_TEMPERATURE_INFO);
+            if (info == null) return;
+            handleMeasurement(info);
+        };
+        healthThermometerProfile = new HealthThermometerProfile<>(this);
+        healthThermometerProfile.addListener(temperatureListener);
+        addSupportedProfile(healthThermometerProfile);
+    }
+
+    @Override
+    public boolean useAutoConnect() {
+        return false;
+    }
+
+    /**
+     * @param data An int smaller equal 255 (0xff)
+     */
+    private byte[] byteArray(int data) {
+        return new byte[]{(byte) data};
+    }
+
+    @Override
+    protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
+        builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext()));
+
+        // Init Battery
+        batteryInfoProfile.requestBatteryInfo(builder);
+        batteryInfoProfile.enableNotify(builder, true);
+
+        // Init Device Info
+        getDevice().setFirmwareVersion("N/A");
+        getDevice().setFirmwareVersion2("N/A");
+        deviceInfoProfile.requestDeviceInfo(builder);
+
+        // Mystery stuff that happens in original app, not sure if its required
+        BluetoothGattCharacteristic c2 = getCharacteristic(CONFIGURATION_SERVICE_SETTING_CHARACTERISTIC);
+        builder.write(c2, byteArray(0x21));
+        builder.write(c2, byteArray(0x02));
+        builder.write(c2, byteArray(0x03));
+        builder.write(c2, byteArray(0x05));
+
+        // Sync Time
+        setCurrentTime(builder);
+
+        // Init Thermometer
+        builder.notify(getCharacteristic(CONFIGURATION_SERVICE_INDICATION_CHARACTERISTIC), true);
+        healthThermometerProfile.enableNotify(builder, true);
+        healthThermometerProfile.setMeasurementInterval(builder, new byte[]{(byte) 0x01, (byte) 0x00});
+
+        // mark the device as initialized
+        builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZED, getContext()));
+        return builder;
+    }
+
+    @Override
+    public void onSetTime() {
+        TransactionBuilder builder = new TransactionBuilder("set time");
+        setCurrentTime(builder);
+        builder.queue(getQueue());
+    }
+
+    private void setCurrentTime(TransactionBuilder builder) {
+        // Same Code as in PineTime (without the local time)
+        GregorianCalendar now = BLETypeConversions.createCalendar();
+        byte[] bytesCurrentTime = BLETypeConversions.calendarToCurrentTime(now, 0);
+        builder.write(getCharacteristic(GattCharacteristic.UUID_CHARACTERISTIC_CURRENT_TIME), bytesCurrentTime);
+    }
+
+    @Override
+    public void onSetAlarms(ArrayList<? extends Alarm> alarms) {
+        try {
+            TransactionBuilder builder = performInitialized("applyThermometerSetting");
+
+            Alarm alarm  = alarms.get(0);
+            byte[] alarm_bytes = new byte[] {
+                    (byte) (alarm.getEnabled()? 0x01 : 0x00),  // first byte 01/00: turn alarm on/off
+                    (byte) alarm.getHour(),                    // second byte: hour
+                    (byte) alarm.getMinute()                   // third byte: minute
+            };
+
+            builder.write(getCharacteristic(CONFIGURATION_SERVICE_ALARM_CHARACTERISTIC), alarm_bytes);
+            builder.write(getCharacteristic(CONFIGURATION_SERVICE_SETTING_CHARACTERISTIC), byteArray(0x01));
+            // read-request on char1 results in given alarm
+            builder.queue(getQueue());
+        } catch (IOException e) {
+            LOG.warn(" Unable to apply setting ", e);
+        }
+    }
+
+    @Override
+    public void onSendConfiguration(String config) {
+        TransactionBuilder builder;
+        SharedPreferences sharedPreferences = GBApplication.getDeviceSpecificSharedPrefs(this.getDevice().getAddress());
+        LOG.info(" onSendConfiguration: " + config);
+        try {
+            builder = performInitialized("sendConfig: " + config);
+            switch (config) {
+                case DeviceSettingsPreferenceConst.PREF_FEMOMETER_MEASUREMENT_MODE:
+                    setMeasurementMode(sharedPreferences);
+                    break;
+                case DeviceSettingsPreferenceConst.PREF_VOLUME:
+                    setVolume(sharedPreferences);
+                    break;
+                case DeviceSettingsPreferenceConst.PREF_TEMPERATURE_SCALE_CF:
+                    String scale = sharedPreferences.getString(DeviceSettingsPreferenceConst.PREF_TEMPERATURE_SCALE_CF,  "c");
+                    int value = "c".equals(scale) ? 0x0a : 0x0b;
+                    applySetting(byteArray(value), null);
+            }
+            builder.queue(getQueue());
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+
+    /** Set Measurement Mode
+     * modes (0- quick, 1- normal, 2- long)
+     */
+    private void setMeasurementMode(SharedPreferences sharedPreferences) {
+        String measurementMode = sharedPreferences.getString(DeviceSettingsPreferenceConst.PREF_FEMOMETER_MEASUREMENT_MODE, "normal");
+        byte[] confirmation = byteArray(0x1e);
+        switch (measurementMode) {
+            case "quick":
+                applySetting(byteArray(0x1a), confirmation);
+                break;
+            case "normal":
+                applySetting(byteArray(0x1c), confirmation);
+                break;
+            case "precise":
+                applySetting(byteArray(0x1d), confirmation);
+                break;
+        }
+    }
+
+    /** Set Volume
+     * volumes 0-30 (0-10: quiet, 11-20: normal, 21-30: loud)
+     */
+    private void setVolume(SharedPreferences sharedPreferences) {
+        int volume = sharedPreferences.getInt(DeviceSettingsPreferenceConst.PREF_VOLUME, 50);
+        byte[] confirmation = byteArray(0xfd);
+        if (volume < 11) {
+            applySetting(byteArray(0x09), confirmation);
+        } else if (volume < 21) {
+            applySetting(byteArray(0x14), confirmation);
+        } else {
+            applySetting(byteArray(0x16), confirmation);
+        }
+    }
+
+    private void applySetting(byte[] value, byte[] confirmation) {
+        try {
+            TransactionBuilder builder = performInitialized("applyThermometerSetting");
+            builder.write(getCharacteristic(CONFIGURATION_SERVICE_SETTING_CHARACTERISTIC), value);
+            if (confirmation != null) {
+                builder.write(getCharacteristic(CONFIGURATION_SERVICE_SETTING_CHARACTERISTIC), confirmation);
+            }
+            builder.queue(getQueue());
+        } catch (IOException e) {
+            LOG.warn(" Unable to apply setting ", e);
+        }
+    }
+
+    private void handleMeasurement(TemperatureInfo info) {
+        Date timestamp = info.getTimestamp();
+        float temperature = info.getTemperature();
+        int temperatureType = info.getTemperatureType();
+        try (DBHandler db = GBApplication.acquireDB()) {
+            Long userId = DBHelper.getUser(db.getDaoSession()).getId();
+            Long deviceId = DBHelper.getDevice(getDevice(), db.getDaoSession()).getId();
+            long time = timestamp.getTime();
+
+            FemometerVinca2SampleProvider sampleProvider = new FemometerVinca2SampleProvider(getDevice(), db.getDaoSession());
+            FemometerVinca2TemperatureSample temperatureSample = new FemometerVinca2TemperatureSample(time, deviceId, userId, temperature, temperatureType);
+            sampleProvider.addSample(temperatureSample);
+        } catch (Exception e) {
+            LOG.error("Error acquiring database", e);
+        }
+    }
+
+    @Override
+    public boolean getImplicitCallbackModify() {
+        return true;
+    }
+
+    @Override
+    public boolean getSendWriteRequestResponse() {
+        return false;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/fitpro/FitProDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/fitpro/FitProDeviceSupport.java
new file mode 100644
index 0000000..0fd37ad
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/fitpro/FitProDeviceSupport.java
@@ -0,0 +1,1461 @@
+/*  Copyright (C) 2021-2024 Arjan Schrijver, Damien Gaignon, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+
+package nodomain.freeyourgadget.gadgetbridge.service.devices.fitpro;
+
+import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.CMD_ALARM;
+import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.CMD_DND;
+import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.CMD_FIND_BAND;
+import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.CMD_GET_HW_INFO;
+import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.CMD_GROUP_BAND_INFO;
+import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.CMD_GROUP_BIND;
+import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.CMD_GROUP_GENERAL;
+import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.CMD_GROUP_HEARTRATE_SETTINGS;
+import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.CMD_GROUP_RECEIVE_BUTTON_DATA;
+import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.CMD_GROUP_RECEIVE_SPORTS_DATA;
+import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.CMD_GROUP_REQUEST_DATA;
+import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.CMD_GROUP_RESET;
+import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.CMD_HEART_RATE_MEASUREMENT;
+import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.CMD_NOTIFICATIONS_ENABLE;
+import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.CMD_NOTIFICATION_CALL;
+import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.CMD_NOTIFICATION_MESSAGE;
+import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.CMD_REQUEST_STEPS_DATA0x10;
+import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.CMD_REQUEST_STEPS_DATA0x7;
+import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.CMD_REQUEST_STEPS_DATA0x8;
+import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.CMD_REQUEST_STEPS_DATA1;
+import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.CMD_RESET;
+import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.CMD_RX_BAND_INFO;
+import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.CMD_SET_ARM;
+import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.CMD_SET_DATE_TIME;
+import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.CMD_SET_DEVICE_VIBRATIONS;
+import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.CMD_SET_DISPLAY_ON_LIFT;
+import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.CMD_SET_LANGUAGE;
+import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.CMD_SET_LONG_SIT_REMINDER;
+import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.CMD_SET_SLEEP_TIMES;
+import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.CMD_SET_STEP_GOAL;
+import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.CMD_SET_USER_DATA;
+import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.CMD_UNBIND;
+import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.CMD_WEATHER;
+import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.GENDER_FEMALE;
+import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.GENDER_MALE;
+import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.NOTIFICATION_ICON_FACEBOOK;
+import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.NOTIFICATION_ICON_INSTAGRAM;
+import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.NOTIFICATION_ICON_LINE;
+import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.NOTIFICATION_ICON_QQ;
+import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.NOTIFICATION_ICON_SMS;
+import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.NOTIFICATION_ICON_TWITTER;
+import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.NOTIFICATION_ICON_WECHAT;
+import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.NOTIFICATION_ICON_WHATSAPP;
+import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.RX_CAMERA1;
+import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.RX_CAMERA2;
+import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.RX_CAMERA3;
+import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.RX_FIND_PHONE;
+import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.RX_HEART_RATE_DATA;
+import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.RX_MEDIA_BACK;
+import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.RX_MEDIA_FORW;
+import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.RX_MEDIA_PLAY_PAUSE;
+import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.RX_SLEEP_DATA;
+import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.RX_SPORTS_DAY_DATA;
+import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.RX_STEP_DATA;
+import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.UNIT_IMPERIAL;
+import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.UNIT_METRIC;
+import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.UUID_CHARACTERISTIC_RX;
+import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.UUID_CHARACTERISTIC_TX;
+import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.VALUE_OFF;
+import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.VALUE_ON;
+import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.VALUE_SET_ARM_LEFT;
+import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.VALUE_SET_ARM_RIGHT;
+import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.VALUE_SET_DEVICE_VIBRATIONS_DISABLE;
+import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.VALUE_SET_DEVICE_VIBRATIONS_ENABLE;
+import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.VALUE_SET_LONG_SIT_REMINDER_OFF;
+import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.VALUE_SET_LONG_SIT_REMINDER_ON;
+import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.VALUE_SET_NOTIFICATIONS_ENABLE_OFF;
+import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.VALUE_SET_NOTIFICATIONS_ENABLE_ON;
+
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.content.Intent;
+import android.widget.Toast;
+
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import org.apache.commons.lang3.ArrayUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.List;
+import java.util.Locale;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.SettingsActivity;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventFindPhone;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventMusicControl;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo;
+import nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants;
+import nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.entities.FitProActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.entities.User;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
+import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
+import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
+import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.Weather;
+import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.GattService;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceBusyAction;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.IntentListener;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.battery.BatteryInfoProfile;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo.DeviceInfo;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo.DeviceInfoProfile;
+import nodomain.freeyourgadget.gadgetbridge.util.AlarmUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class FitProDeviceSupport extends AbstractBTLEDeviceSupport {
+    private static final Logger LOG = LoggerFactory.getLogger(FitProDeviceSupport.class);
+    public final GBDeviceEventBatteryInfo batteryCmd = new GBDeviceEventBatteryInfo();
+    public final GBDeviceEventVersionInfo versionCmd = new GBDeviceEventVersionInfo();
+    public final DeviceInfoProfile<FitProDeviceSupport> deviceInfoProfile;
+    public final BatteryInfoProfile<FitProDeviceSupport> batteryInfoProfile;
+
+    public BluetoothGattCharacteristic readCharacteristic;
+    public BluetoothGattCharacteristic writeCharacteristic;
+    private static final boolean debugEnabled = false;
+    private int mtuSize=20;
+
+    public FitProDeviceSupport() {
+        super(LOG);
+        addSupportedService(GattService.UUID_SERVICE_DEVICE_INFORMATION);
+        addSupportedService(GattService.UUID_SERVICE_BATTERY_SERVICE);
+
+        IntentListener mListener = new IntentListener() {
+            @Override
+            public void notify(Intent intent) {
+                String action = intent.getAction();
+                if (DeviceInfoProfile.ACTION_DEVICE_INFO.equals(action)) {
+                    handleDeviceInfo((nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo.DeviceInfo) intent.getParcelableExtra(DeviceInfoProfile.EXTRA_DEVICE_INFO));
+                } else if (BatteryInfoProfile.ACTION_BATTERY_INFO.equals(action)) {
+                    handleBatteryInfo((nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.battery.BatteryInfo) intent.getParcelableExtra(BatteryInfoProfile.EXTRA_BATTERY_INFO));
+                }
+            }
+        };
+
+        deviceInfoProfile = new DeviceInfoProfile<>(this);
+        deviceInfoProfile.addListener(mListener);
+        addSupportedProfile(deviceInfoProfile);
+
+        batteryInfoProfile = new BatteryInfoProfile<>(this);
+        batteryInfoProfile.addListener(mListener);
+        addSupportedProfile(batteryInfoProfile);
+        addSupportedService(FitProConstants.UUID_CHARACTERISTIC_RX);
+        addSupportedService(FitProConstants.UUID_CHARACTERISTIC_UART);
+    }
+
+    @Override
+    public TransactionBuilder initializeDevice(TransactionBuilder builder) {
+        builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext()));
+        readCharacteristic = getCharacteristic(UUID_CHARACTERISTIC_RX);
+        writeCharacteristic = getCharacteristic(UUID_CHARACTERISTIC_TX);
+
+        builder.notify(getCharacteristic(UUID_CHARACTERISTIC_RX), true);
+        builder.notify(getCharacteristic(GattService.UUID_SERVICE_BATTERY_SERVICE), true);
+        builder.setCallback(this);
+
+        deviceInfoProfile.requestDeviceInfo(builder);
+        batteryInfoProfile.requestBatteryInfo(builder);
+        batteryInfoProfile.enableNotify(builder, true);
+        deviceInfoProfile.enableNotify(builder, true);
+
+        // this sequence seems to be important as without it:
+        // - fetch steps doesn't work
+        // - band seems to drain battery really fast
+        // - the wait time is needed as the band must process each command
+        // - (implementation based on individual requests did not work, the wait is still needed)
+
+        builder.write(writeCharacteristic, craftData(CMD_GROUP_GENERAL, FitProConstants.CMD_INIT1, (byte) 0x2));
+        setTime(builder);
+        builder.wait(200);
+        builder.write(writeCharacteristic, craftData(CMD_GROUP_REQUEST_DATA, FitProConstants.CMD_INIT1));
+        builder.wait(200);
+        builder.write(writeCharacteristic, craftData(CMD_GROUP_REQUEST_DATA, FitProConstants.CMD_INIT2));
+        builder.wait(200);
+        setLanguage(builder);
+        builder.wait(200);
+        builder.write(writeCharacteristic, craftData(CMD_GROUP_GENERAL, FitProConstants.CMD_INIT3, VALUE_ON));
+        builder.wait(200);
+        builder.write(writeCharacteristic, craftData(CMD_GROUP_REQUEST_DATA, VALUE_ON));
+        builder.wait(200);
+        builder.write(writeCharacteristic, craftData(CMD_GROUP_REQUEST_DATA, (byte) 0xf));
+        builder.wait(200);
+        builder.write(writeCharacteristic, craftData(CMD_GROUP_REQUEST_DATA, CMD_GET_HW_INFO));
+        builder.wait(200);
+        builder.write(writeCharacteristic, craftData(CMD_GROUP_BAND_INFO, CMD_RX_BAND_INFO));
+        builder.wait(200);
+
+        builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZED, getContext()));
+        return builder;
+    }
+
+    public void handleDeviceInfo(DeviceInfo info) {
+        LOG.debug("fitpro device info: " + info);
+        versionCmd.hwVersion = "FitPro";
+        versionCmd.fwVersion = info.getFirmwareRevision();
+        handleGBDeviceEvent(versionCmd);
+    }
+
+    public void handleDeviceInfo(byte[] value) {
+        LOG.debug("FitPro device info2");
+        //test this 0xCD 0x00 0x11 0x15 0x01 0x02 0x00 0x0C 0x2B 0x27 0x00 0x01 0x33 0xA5 0x02 0x79 0x0A 0x68 0x56 0x06
+        debugPrintArray(value, "Device info:");
+        if (value.length < 20) {
+            return;
+        }
+        int start = 14;
+        int data_len = (int) value[start];
+
+        byte[] name = new byte[data_len];
+        System.arraycopy(value, start + 1, name, 0, data_len);
+        String sName = new String(name, StandardCharsets.UTF_8); //unused for now
+
+        start = start + data_len + 1;
+        data_len = (int) value[start];
+        byte[] hwname = new byte[data_len];
+        System.arraycopy(value, start + 1, hwname, 0, data_len);
+        String sHWName = new String(hwname, StandardCharsets.UTF_8);
+        LOG.debug("Device info: " + versionCmd);
+        versionCmd.hwVersion = sHWName;
+        handleGBDeviceEvent(versionCmd);
+    }
+
+    @Override
+    public boolean onCharacteristicChanged(BluetoothGatt gatt,
+                                           BluetoothGattCharacteristic characteristic) {
+        super.onCharacteristicChanged(gatt, characteristic);
+        UUID characteristicUUID = characteristic.getUuid();
+        byte[] data = characteristic.getValue();
+        debugPrintArray(data, "FitPro received value");
+        if (data[0] != FitProConstants.DATA_HEADER) {
+            if (debugEnabled) {
+                LOG.info("FitPro, packet not starting with 0xcd: " + data[0]);
+                debugPrintArray(new byte[]{data[0]}, "first byte");
+                LOG.info("Characteristic changed UUID: " + characteristicUUID);
+                LOG.info("Characteristic changed service: " + characteristic.getService().getCharacteristics());
+                debugPrintArray(data, "value bytes");
+            }
+            indicateFinishedFetchingOperation();
+            return false;
+        }
+
+        if (data != null && data.length > 5) {
+            byte command = data[3];
+            byte param = data[5];
+
+            switch (command) {
+                case CMD_GROUP_RECEIVE_BUTTON_DATA:
+                    switch (param) {
+                        case RX_FIND_PHONE:
+                            handleFindPhone();
+                            break;
+                        case RX_MEDIA_BACK:
+                        case RX_MEDIA_FORW:
+                        case RX_MEDIA_PLAY_PAUSE:
+                            handleMediaButton(param);
+                            break;
+                        case RX_CAMERA1:
+                        case RX_CAMERA2:
+                        case RX_CAMERA3:
+                            handleCamera(param);
+                            break;
+                        default:
+                    }
+                    break;
+                case CMD_GROUP_RECEIVE_SPORTS_DATA:
+                    switch (param) {
+                        case RX_HEART_RATE_DATA:
+                            handleHR(data);
+                            break;
+                        case RX_SPORTS_DAY_DATA:
+                            indicateStartingFetchingOperation();
+                            handleDayTotalsData(data);
+                            indicateFinishedFetchingOperation();
+                            break;
+                        case RX_SLEEP_DATA:
+                            indicateStartingFetchingOperation();
+                            handleSleepData(data);
+                            indicateFinishedFetchingOperation();
+                            break;
+                        case RX_STEP_DATA:
+                            indicateStartingFetchingOperation();
+                            handleStepData(data);
+                            indicateFinishedFetchingOperation();
+                            break;
+                        case CMD_REQUEST_STEPS_DATA0x7:
+                        case CMD_REQUEST_STEPS_DATA0x8:
+                        case CMD_REQUEST_STEPS_DATA0x10:
+                            //acking this makes the band to send data
+                            sendAck(data[3], data[1], data[2], data[5]);
+                            break;
+                    }
+                    break;
+                case CMD_GROUP_BAND_INFO:
+                    switch (param) {
+                        case CMD_RX_BAND_INFO:
+                            handleDeviceInfo(data);
+                            break;
+                    }
+                    sendAck(data[3], data[1], data[2], data[5]);
+                    break;
+                case CMD_GROUP_REQUEST_DATA:
+                    switch (param) {
+                        case CMD_GET_HW_INFO:
+                            handleHardwareDetails(data);
+                            break;
+                    }
+                    sendAck(data[3], data[1], data[2], data[5]);
+                    break;
+            }
+
+            LOG.info("Characteristic changed UUID: " + characteristicUUID);
+            LOG.info("Characteristic changed service: " + characteristic.getService().getCharacteristics());
+            debugPrintArray(data, "value bytes");
+        }
+        return false;
+    }
+
+    public void indicateFinishedFetchingOperation() {
+        //LOG.debug("download finish announced");
+        GB.updateTransferNotification(null, "", false, 100, getContext());
+        GB.signalActivityDataFinish(getDevice());
+        unsetBusy();
+    }
+
+    public void indicateStartingFetchingOperation() {
+        GB.updateTransferNotification(null, getContext().getString(R.string.busy_task_fetch_activity_data), true, 10, getContext());
+    }
+
+    protected void unsetBusy() {
+        if (getDevice().isBusy()) {
+            getDevice().unsetBusyTask();
+            getDevice().sendDeviceUpdateIntent(getContext());
+        }
+    }
+
+    public void handleHardwareDetails(byte[] value) {
+        LOG.debug("FitPro hardware details");
+        debugPrintArray(value, "Device info:");
+        if (value.length < 20) {
+            return;
+        }
+        int start = 8;
+        int data_len = (int) value[start];
+
+        byte[] led = new byte[data_len];
+        System.arraycopy(value, start + 1, led, 0, data_len);
+        String sLED = new String(led, StandardCharsets.UTF_8);
+
+        start = start + data_len + 1;
+        data_len = (int) value[start];
+        byte[] gsensor = new byte[data_len];
+        System.arraycopy(value, start + 1, gsensor, 0, data_len);
+        String sGsensor = new String(gsensor, StandardCharsets.UTF_8);
+
+        gbDevice.setFirmwareVersion2(sGsensor + " " + sLED);
+
+        //the band does not like to answer when asked together for both hw info, so ask now,
+        // after data is already received
+
+        TransactionBuilder builder = new TransactionBuilder("notification");
+        builder.write(writeCharacteristic, craftData(CMD_GROUP_BAND_INFO, CMD_RX_BAND_INFO));
+        builder.queue(getQueue());
+
+    }
+
+    public void handleHR(byte[] value) {
+        LOG.debug("FitPro handle heart rate measurement");
+        debugPrintArray(value, "value");
+        if (value.length < 17) {
+            LOG.debug("FitPro heartrate measurement payload too short");
+            return;
+        }
+
+        int heartRate = (int) value[19];
+        int pressureLow = (int) value[18];
+        int pressureHigh = (int) value[17];
+        int spo2 = (int) value[13];
+        int seconds = ByteBuffer.wrap(value, 12, 4).getInt();
+        sendAck(value[3], value[1], value[2], value[5]);
+
+        if (!(heartRate > 0)) {
+            return;
+        }
+        handleHR(seconds, heartRate, pressureLow, pressureHigh, spo2);
+    }
+
+    @Override
+    public void onSetCallState(CallSpec callSpec) {
+        LOG.debug("FitPro send call notification");
+        TransactionBuilder builder = new TransactionBuilder("CALL");
+
+        if (callSpec.command == CallSpec.CALL_INCOMING) {
+
+            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+            try {
+                outputStream.write(0x1);
+                outputStream.write(0x0);
+                outputStream.write(0x0);
+
+                if (callSpec.name != null) {
+                    outputStream.write(callSpec.name.getBytes(StandardCharsets.UTF_8));
+                    outputStream.write(0x20);
+                }
+                if (callSpec.number != null) {
+                    outputStream.write(callSpec.number.getBytes(StandardCharsets.UTF_8));
+                    outputStream.write(0x20);
+                }
+
+            } catch (IOException e) {
+                LOG.error("error sending call notification: " + e);
+            }
+            debugPrintArray(craftData(CMD_GROUP_GENERAL, CMD_NOTIFICATION_CALL, outputStream.toByteArray()), "crafted call notify");
+
+            writeChunkedData(builder, craftData(CMD_GROUP_GENERAL, CMD_NOTIFICATION_CALL, outputStream.toByteArray()));
+
+        } else {
+            builder.write(writeCharacteristic, craftData(CMD_GROUP_GENERAL, CMD_NOTIFICATION_CALL, VALUE_OFF));
+        }
+        builder.queue(getQueue());
+    }
+
+    @Override
+    public void onSendConfiguration(String config) {
+
+        LOG.debug("FitPro on send config: " + config);
+        try {
+            TransactionBuilder builder = performInitialized("sendConfiguration");
+            switch (config) {
+                case DeviceSettingsPreferenceConst.PREF_LANGUAGE:
+                    setLanguage(builder);
+                    break;
+                case DeviceSettingsPreferenceConst.PREF_INACTIVITY_THRESHOLD_EXTENDED:
+                case DeviceSettingsPreferenceConst.PREF_INACTIVITY_ENABLE:
+                case DeviceSettingsPreferenceConst.PREF_INACTIVITY_START:
+                case DeviceSettingsPreferenceConst.PREF_INACTIVITY_END:
+                    setLongSitReminder(builder);
+                    break;
+                case DeviceSettingsPreferenceConst.PREF_ACTIVATE_DISPLAY_ON_LIFT:
+                case DeviceSettingsPreferenceConst.PREF_DISPLAY_ON_LIFT_START:
+                case DeviceSettingsPreferenceConst.PREF_DISPLAY_ON_LIFT_END:
+                    setDisplayOnLift(builder);
+                    break;
+                case SettingsActivity.PREF_MEASUREMENT_SYSTEM:
+                case ActivityUser.PREF_USER_WEIGHT_KG:
+                case ActivityUser.PREF_USER_GENDER:
+                case ActivityUser.PREF_USER_HEIGHT_CM:
+                case ActivityUser.PREF_USER_DATE_OF_BIRTH:
+                    setUserData(builder);
+                    break;
+                case ActivityUser.PREF_USER_STEPS_GOAL:
+                    setStepsGoal(builder);
+                    break;
+                case DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_NOAUTO:
+                case DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_NOAUTO_START:
+                case DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_NOAUTO_END:
+                    setDoNotDisturb(builder);
+                    break;
+                case DeviceSettingsPreferenceConst.PREF_SLEEP_TIME:
+                case DeviceSettingsPreferenceConst.PREF_SLEEP_TIME_START:
+                case DeviceSettingsPreferenceConst.PREF_SLEEP_TIME_END:
+                    setSleepTime(builder);
+                    break;
+                case DeviceSettingsPreferenceConst.PREF_WEARLOCATION:
+                    setWearLocation(builder);
+                    break;
+                case DeviceSettingsPreferenceConst.PREF_VIBRATION_ENABLE:
+                    setVibrations(builder);
+                    break;
+                case DeviceSettingsPreferenceConst.PREF_NOTIFICATION_ENABLE:
+                    setNotifications(builder);
+                    break;
+                case DeviceSettingsPreferenceConst.PREF_AUTOHEARTRATE_SWITCH:
+                case DeviceSettingsPreferenceConst.PREF_AUTOHEARTRATE_SLEEP:
+                case DeviceSettingsPreferenceConst.PREF_AUTOHEARTRATE_INTERVAL:
+                case DeviceSettingsPreferenceConst.PREF_AUTOHEARTRATE_START:
+                case DeviceSettingsPreferenceConst.PREF_AUTOHEARTRATE_END:
+                    setAutoHeartRate(builder);
+                    break;
+            }
+            builder.queue(getQueue());
+        } catch (IOException e) {
+            GB.toast(getContext(), "Error sending configuration: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
+        }
+    }
+
+    public void sendAck(byte command_group, byte length_high, byte length_low, byte command) {
+        LOG.debug(" ACKing data: " + nodomain.freeyourgadget.gadgetbridge.util.ArrayUtils.arrayToString(new byte[]{command_group}) + " " + nodomain.freeyourgadget.gadgetbridge.util.ArrayUtils.arrayToString(new byte[]{command}));
+        TransactionBuilder builder = new TransactionBuilder("notification");
+        short size = (short) (ByteBuffer.wrap(new byte[]{length_high, length_low}).getShort() + 3);
+        byte[] sizeArray = ByteBuffer.allocate(2).putShort(size).array();
+        builder.write(writeCharacteristic, new byte[]{FitProConstants.DATA_HEADER_ACK, 0, 5, command_group, 1, sizeArray[0], sizeArray[1], 1});
+        builder.queue(getQueue());
+    }
+
+    @Override
+    public void onTestNewFunction() {
+        LOG.debug("Hello FitPro Test function");
+    }
+
+    @Override
+    public void onSendWeather(ArrayList<WeatherSpec> weatherSpecs) {
+        WeatherSpec weatherSpec = weatherSpecs.get(0);
+        LOG.debug("FitPro send weather");
+        short todayMax = (short) (weatherSpec.todayMaxTemp - 273);
+        short todayMin = (short) (weatherSpec.todayMinTemp - 273);
+        byte weatherUnit = 0;
+        String units = GBApplication.getPrefs().getString(SettingsActivity.PREF_MEASUREMENT_SYSTEM, GBApplication.getContext().getString(R.string.p_unit_metric));
+        if (units.equals(GBApplication.getContext().getString(R.string.p_unit_imperial))) {
+            todayMax = (short) (todayMax * 1.8f + 32);
+            todayMin = (short) (todayMin * 1.8f + 32);
+            weatherUnit = 1;
+        }
+
+        byte currentConditionCode = Weather.mapToFitProCondition(weatherSpec.currentConditionCode);
+        TransactionBuilder builder = new TransactionBuilder("weather");
+        writeChunkedData(builder, craftData(CMD_GROUP_GENERAL, CMD_WEATHER, new byte[]{(byte) todayMin, (byte) todayMax, (byte) currentConditionCode, (byte) weatherUnit}));
+        builder.queue(getQueue());
+    }
+
+    @Override
+    public boolean useAutoConnect() {
+        return true;
+    }
+
+    @Override
+    public void onNotification(NotificationSpec notificationSpec) {
+        LOG.debug("FitPro notification: " + notificationSpec.type);
+        TransactionBuilder builder = new TransactionBuilder("notification");
+        byte icon = NOTIFICATION_ICON_SMS;
+        switch (notificationSpec.type) {
+            case GENERIC_SMS:
+                icon = NOTIFICATION_ICON_SMS;
+                break;
+            case FACEBOOK:
+            case FACEBOOK_MESSENGER:
+                icon = NOTIFICATION_ICON_FACEBOOK;
+                break;
+            case LINE:
+                icon = NOTIFICATION_ICON_LINE;
+                break;
+            case WHATSAPP:
+                icon = NOTIFICATION_ICON_WHATSAPP;
+                break;
+            case TWITTER:
+                icon = NOTIFICATION_ICON_TWITTER;
+                break;
+            case SIGNAL:
+            case VIBER:
+            case CONVERSATIONS:
+                icon = NOTIFICATION_ICON_QQ;
+                break;
+            case WECHAT:
+            case GMAIL:
+                icon = NOTIFICATION_ICON_WECHAT;
+                break;
+            case INSTAGRAM:
+                icon = NOTIFICATION_ICON_INSTAGRAM;
+                break;
+            default:
+                icon = NOTIFICATION_ICON_SMS;
+                break;
+        }
+
+        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        try {
+            outputStream.write(icon);
+            outputStream.write(0x0);
+            outputStream.write(0x0);
+
+            if (notificationSpec.sender != null) {
+                outputStream.write(notificationSpec.sender.getBytes(StandardCharsets.UTF_8));
+                outputStream.write(0x20);
+            } else {
+                if (notificationSpec.phoneNumber != null) { //use number only if there is no sender
+                    outputStream.write(notificationSpec.phoneNumber.getBytes(StandardCharsets.UTF_8));
+                    outputStream.write(0x20);
+                }
+            }
+
+            if (notificationSpec.subject != null) {
+                outputStream.write(notificationSpec.subject.getBytes(StandardCharsets.UTF_8));
+                outputStream.write(0x20);
+            }
+            if (notificationSpec.body != null) {
+                outputStream.write(notificationSpec.body.getBytes(StandardCharsets.UTF_8));
+                outputStream.write(0x20);
+            }
+
+        } catch (IOException e) {
+            LOG.error("FitPro error sending notification: " + e);
+        }
+        String output = outputStream.toString();
+        if (outputStream.toString().length() > 250) {
+            output = outputStream.toString().substring(0, 250);
+        }
+
+        writeChunkedData(builder, craftData(CMD_GROUP_GENERAL, CMD_NOTIFICATION_MESSAGE, output.getBytes(StandardCharsets.UTF_8)));
+        builder.queue(getQueue());
+    }
+
+    public FitProDeviceSupport setLanguage(TransactionBuilder builder) {
+        LOG.debug("FitPro set language");
+        String localeString = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getString("language", "auto");
+        if (localeString == null || localeString.equals("auto")) {
+            String language = Locale.getDefault().getLanguage();
+            String country = Locale.getDefault().getCountry();
+
+            if (country == null) {
+                country = language;
+            }
+            localeString = language + "_" + country.toUpperCase();
+        }
+        LOG.info("Setting device to locale: " + localeString);
+
+        byte languageCode = FitProConstants.LANG_ENGLISH;
+
+        switch (localeString.substring(0, 2)) {
+            case "zh":
+                languageCode = FitProConstants.LANG_CHINESE;
+                break;
+            case "it":
+                languageCode = FitProConstants.LANG_ITALIAN;
+                break;
+            case "cs":
+                languageCode = FitProConstants.LANG_CZECH;
+                break;
+            case "en":
+                languageCode = FitProConstants.LANG_ENGLISH;
+                break;
+            case "tr":
+                languageCode = FitProConstants.LANG_TURKISH;
+                break;
+            case "ru":
+                languageCode = FitProConstants.LANG_RUSSIAN;
+                break;
+            case "pl":
+                languageCode = FitProConstants.LANG_POLISH;
+                break;
+            case "nl":
+                languageCode = FitProConstants.LANG_NETHERLANDS;
+                break;
+            case "fr":
+                languageCode = FitProConstants.LANG_FRENCH;
+                break;
+            case "es":
+                languageCode = FitProConstants.LANG_SPANISH;
+                break;
+            case "de":
+                languageCode = FitProConstants.LANG_GERMAN;
+                break;
+            case "pt":
+                languageCode = FitProConstants.LANG_PORTUGUESE;
+                break;
+
+            default:
+                languageCode = FitProConstants.LANG_ENGLISH;
+        }
+
+        builder.write(writeCharacteristic, craftData(CMD_GROUP_GENERAL, CMD_SET_LANGUAGE, languageCode));
+
+        return this;
+    }
+
+    public FitProDeviceSupport setUserData(TransactionBuilder builder) {
+        //0xcd 0x00 0x09 0x12 0x01 0x04 0x00 0x04 0xaf 0x59 0x09 0xe1 FitPro
+        LOG.debug("FitPro set user data");
+
+        ActivityUser activityUser = new ActivityUser();
+
+        int age = activityUser.getAge();
+
+        int gender = activityUser.getGender();
+        byte genderUnit = GENDER_FEMALE;
+        if (gender == ActivityUser.GENDER_MALE) {
+            genderUnit = GENDER_MALE;
+        }
+
+        int heightCm = activityUser.getHeightCm();
+        int weightKg = activityUser.getWeightKg();
+
+        byte distanceUnit = UNIT_METRIC;
+        String units = GBApplication.getPrefs().getString(SettingsActivity.PREF_MEASUREMENT_SYSTEM, GBApplication.getContext().getString(R.string.p_unit_metric));
+        if (units.equals(GBApplication.getContext().getString(R.string.p_unit_imperial))) {
+            distanceUnit = UNIT_IMPERIAL;
+        }
+
+        int userData = genderUnit << 31 | age << 24 | heightCm << 15 | weightKg << 5 | distanceUnit;
+        byte[] data = craftData(CMD_GROUP_GENERAL, CMD_SET_USER_DATA, ByteBuffer.allocate(4).putInt(userData).array());
+        builder.write(writeCharacteristic, data);
+        return this;
+    }
+
+    @Override
+    public void onFetchRecordedData(int dataTypes) {
+        indicateFinishedFetchingOperation();
+        TransactionBuilder builder = new TransactionBuilder("fetch data1");
+        builder.add(new SetDeviceBusyAction(getDevice(), getContext().getString(R.string.busy_task_fetch_activity_data), getContext()));
+        builder.write(writeCharacteristic, craftData(CMD_GROUP_RECEIVE_SPORTS_DATA, CMD_REQUEST_STEPS_DATA1, VALUE_ON));
+        builder.queue(getQueue());
+    }
+
+
+    public void handleDayTotalsData(byte[] value) {
+        LOG.debug("FitPro handle day data length: " + value.length);
+        debugPrintArray(value, "value");
+        if (value.length < 10) {
+            LOG.debug("FitPro payload too short");
+            return;
+        }
+        debugPrintArray(value, "processing");
+        int steps = ByteBuffer.wrap(value, 10, 4).getInt();
+        int distance = ByteBuffer.wrap(value, 14, 4).getInt();
+
+        byte[] caloriesBytes = new byte[3];
+        System.arraycopy(value, 18, caloriesBytes, 0, 2);
+        int calories = ByteBuffer.wrap(caloriesBytes, 0, 3).getShort();
+
+        LOG.debug("processing day data summary, steps: " + steps + " distance: " + distance + " calories: " + calories);
+        sendAck(value[3], value[1], value[2], value[5]);
+        //handleDayTotalsData(steps, distance, calories);
+    }
+
+    public void handleBatteryInfo(nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.battery.BatteryInfo info) {
+        LOG.debug("FitPro battery info: " + info);
+        batteryCmd.level = (short) info.getPercentCharged();
+        handleGBDeviceEvent(batteryCmd);
+    }
+
+    /*   public void handleDeviceInfo(nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo.DeviceInfo info) {
+           LOG.debug("FitPro device info: " + info);
+           versionCmd.hwVersion = "+FitPro";
+           versionCmd.fwVersion = info.getFirmwareRevision();
+           handleGBDeviceEvent(versionCmd);
+       }
+
+     */
+    public void handleCamera(byte command) {
+        GB.toast(getContext(), "Camera buttons are detected but not further handled.", Toast.LENGTH_SHORT, GB.INFO);
+    }
+
+    public void handleFindPhone() {
+        LOG.info("FitPro find phone");
+        GBDeviceEventFindPhone deviceEventFindPhone = new GBDeviceEventFindPhone();
+        deviceEventFindPhone.event = GBDeviceEventFindPhone.Event.START;
+        evaluateGBDeviceEvent(deviceEventFindPhone);
+    }
+
+    public void handleMediaButton(byte command) {
+        GBDeviceEventMusicControl deviceEventMusicControl = new GBDeviceEventMusicControl();
+        if (command == RX_MEDIA_PLAY_PAUSE) {
+            deviceEventMusicControl.event = GBDeviceEventMusicControl.Event.PLAYPAUSE;
+            evaluateGBDeviceEvent(deviceEventMusicControl);
+        } else if (command == RX_MEDIA_FORW) {
+            deviceEventMusicControl.event = GBDeviceEventMusicControl.Event.NEXT;
+            evaluateGBDeviceEvent(deviceEventMusicControl);
+        } else if (command == RX_MEDIA_BACK) {
+            deviceEventMusicControl.event = GBDeviceEventMusicControl.Event.PREVIOUS;
+            evaluateGBDeviceEvent(deviceEventMusicControl);
+        }
+    }
+
+    public FitProDeviceSupport setVibrations(TransactionBuilder builder) {
+        LOG.debug("FitPro set enable vibrations");
+        boolean vibrations = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getBoolean(DeviceSettingsPreferenceConst.PREF_VIBRATION_ENABLE, false);
+        byte[] enable = VALUE_SET_DEVICE_VIBRATIONS_ENABLE;
+        if (!vibrations) {
+            enable = VALUE_SET_DEVICE_VIBRATIONS_DISABLE;
+        }
+        builder.write(writeCharacteristic, craftData(CMD_GROUP_GENERAL, CMD_SET_DEVICE_VIBRATIONS, enable));
+        return this;
+    }
+
+    public static void debugPrintArray(byte[] bytes, String label) {
+        if (!debugEnabled) return;
+        String arrayString = nodomain.freeyourgadget.gadgetbridge.util.ArrayUtils.arrayToString(bytes);
+        LOG.debug("FitPro debug print " + label + ": " + arrayString);
+    }
+
+    public FitProDeviceSupport setNotifications(TransactionBuilder builder) {
+        LOG.debug("FitPro set enable notifications");
+        boolean notifications = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getBoolean(DeviceSettingsPreferenceConst.PREF_NOTIFICATION_ENABLE, false);
+        byte[] enable = VALUE_SET_NOTIFICATIONS_ENABLE_ON;
+        if (!notifications) {
+            enable = VALUE_SET_NOTIFICATIONS_ENABLE_OFF;
+        }
+        builder.write(writeCharacteristic, craftData(CMD_GROUP_GENERAL, CMD_NOTIFICATIONS_ENABLE, enable));
+        return this;
+    }
+
+    public static byte[] craftData(byte command_group, byte command) {
+        return craftData(command_group, command, new byte[]{});
+    }
+
+    public static byte[] craftData(byte command_group, byte command, byte value) {
+        return craftData(command_group, command, new byte[]{value});
+    }
+
+    public static byte[] craftData(byte command_group, byte command, byte[] data) {
+        //0xCD 0x00 0x09 0x12 0x01 0x01 0x00 0x04 0xA5 0x83 0x73 0xDB
+        byte[] result = new byte[FitProConstants.DATA_TEMPLATE.length + data.length];
+        System.arraycopy(FitProConstants.DATA_TEMPLATE, 0, result, 0, FitProConstants.DATA_TEMPLATE.length);
+        result[1] = (byte) (((FitProConstants.DATA_TEMPLATE.length + data.length - 3) >> 8) & 0xff);
+        result[2] = (byte) ((FitProConstants.DATA_TEMPLATE.length + data.length - 3) & 0xff);
+        result[3] = command_group;
+        result[5] = command;
+        result[6] = (byte) ((data.length >> 8) & 0xff);
+        result[7] = (byte) (data.length & 0xff);
+        System.arraycopy(data, 0, result, 8, data.length);
+        //debug
+        debugPrintArray(result, "crafted packet");
+        return result;
+    }
+
+    // send chucked up data
+    public void writeChunkedData(TransactionBuilder builder, byte[] data) {
+        for (int start = 0; start < data.length; start += mtuSize) {
+            int end = start + mtuSize;
+            if (end > data.length) end = data.length;
+            builder.write(writeCharacteristic, Arrays.copyOfRange(data, start, end));
+        }
+    }
+
+    @Override
+    public void onSetTime() {
+        LOG.debug("FitPro set date and time");
+        TransactionBuilder builder = new TransactionBuilder("Set date and time");
+        setTime(builder);
+        builder.queue(getQueue());
+    }
+
+
+    public FitProDeviceSupport setTime(TransactionBuilder builder) {
+        LOG.debug("FitPro set time");
+        Calendar calendar = Calendar.getInstance();
+
+        int datetime = calendar.get(Calendar.SECOND) | (
+                (calendar.get(Calendar.YEAR) - 2000) << 26 | calendar.get(Calendar.MONTH) + 1 << 22 |
+                        calendar.get(Calendar.DAY_OF_MONTH) << 17 |
+                        calendar.get(Calendar.HOUR_OF_DAY) << 12 | calendar.get(Calendar.MINUTE) << 6);
+
+        //this is how the values can be re-stored
+        // result is this
+        //byte[] array = new byte[]{(byte) (datetime >> 24), (byte) (datetime >> 16), (byte) (datetime >> 8), (byte) (datetime >> 0)};
+        // int datetime2 = ByteBuffer.wrap(array).getInt();
+
+        //byte[] time = craftData(LT716Constants.CMD_SET_DATE_TIME, new byte[]{(byte) (datetime >> 24), (byte) (datetime >> 16), (byte) (datetime >> 8), (byte) (datetime >> 0)});
+        byte[] time = craftData(CMD_GROUP_GENERAL, CMD_SET_DATE_TIME, (ByteBuffer.allocate(4).putInt(datetime).array()));
+        builder.write(writeCharacteristic, time);
+        return this;
+    }
+
+
+    @Override
+    public void onSetAlarms(ArrayList<? extends Alarm> alarms) {
+        LOG.debug("FitPro set alarms");
+
+        // handle one-shot alarm from the widget:
+        // this device doesn't have concept of on-off alarm, so use the last slot for this and store
+        // this alarm in the database so the user knows what is going on and can disable it
+
+        if (alarms.toArray().length == 1 && alarms.get(0).getRepetition() == 0) { //single shot?
+            Alarm oneshot = alarms.get(0);
+            alarms = (ArrayList<? extends Alarm>) AlarmUtils.mergeOneshotToDeviceAlarms(gbDevice, (nodomain.freeyourgadget.gadgetbridge.entities.Alarm) oneshot, 7);
+        }
+
+        try {
+            TransactionBuilder builder = performInitialized("Set alarm");
+            boolean anyAlarmEnabled = false;
+            byte[] all_alarms = new byte[]{};
+
+            for (Alarm alarm : alarms) {
+                Calendar calendar = AlarmUtils.toCalendar(alarm);
+                anyAlarmEnabled |= alarm.getEnabled();
+                LOG.debug("alarms: " + alarm.getPosition());
+                int maxAlarms = 8;
+                if (alarm.getPosition() >= maxAlarms) { //we should never encounter this, but just in case
+                    if (alarm.getEnabled()) {
+                        GB.toast(getContext(), "Only 8 alarms are supported.", Toast.LENGTH_LONG, GB.WARN);
+                    }
+                    return;
+                }
+                if (alarm.getEnabled()) {
+                    long datetime = (long) alarm.getRepetition() | (
+                            (long) (calendar.get(Calendar.YEAR) - 2000) << 34 |
+                                    (long) (calendar.get(Calendar.MONTH) + 1) << 30 |
+                                    (long) (calendar.get(Calendar.DAY_OF_MONTH)) << 25 |
+                                    (long) (calendar.get(Calendar.HOUR_OF_DAY)) << 20 |
+                                    (long) (calendar.get(Calendar.MINUTE)) << 14 |
+                                    1L << 11);
+                    byte[] single_alarm = new byte[]{(byte) (datetime >> 32), (byte) (datetime >> 24), (byte) (datetime >> 16), (byte) (datetime >> 8), (byte) (datetime)};
+                    all_alarms = ArrayUtils.addAll(all_alarms, single_alarm);
+                }
+            }
+
+            writeChunkedData(builder, craftData(CMD_GROUP_GENERAL, CMD_ALARM, all_alarms));
+            //builder.write(writeCharacteristic, craftData(CMD_GROUP_GENERAL, CMD_ALARM, all_alarms));
+            builder.queue(getQueue());
+            if (anyAlarmEnabled) {
+                GB.toast(getContext(), getContext().getString(R.string.user_feedback_miband_set_alarms_ok), Toast.LENGTH_SHORT, GB.INFO);
+            } else {
+                GB.toast(getContext(), getContext().getString(R.string.user_feedback_all_alarms_disabled), Toast.LENGTH_SHORT, GB.INFO);
+            }
+        } catch (IOException ex) {
+            GB.toast(getContext(), getContext().getString(R.string.user_feedback_miband_set_alarms_failed), Toast.LENGTH_LONG, GB.ERROR, ex);
+        }
+    }
+
+    @Override
+    public void onReset(int flags) {
+        LOG.debug("FitPro reset flags: " + flags);
+        byte[] command = craftData(CMD_GROUP_RESET, CMD_RESET);
+        switch (flags) {
+            case 1:
+                command = craftData(CMD_GROUP_RESET, CMD_RESET);
+                break;
+            case 2:
+                command = craftData(CMD_GROUP_BIND, CMD_UNBIND);
+                break;
+        }
+
+        getQueue().clear();
+        TransactionBuilder builder = new TransactionBuilder("resetting");
+        builder.write(writeCharacteristic, command);
+        builder.queue(getQueue());
+    }
+
+    @Override
+    public void onHeartRateTest() {
+        TransactionBuilder builder = new TransactionBuilder("notification");
+        builder.write(writeCharacteristic, craftData(CMD_GROUP_GENERAL, CMD_HEART_RATE_MEASUREMENT, VALUE_ON));
+        builder.queue(getQueue());
+    }
+
+    @Override
+    public void onFindDevice(boolean start) {
+        getQueue().clear();
+        LOG.debug("FitPro find device");
+        TransactionBuilder builder = new TransactionBuilder("searching");
+        builder.write(writeCharacteristic, craftData(CMD_GROUP_GENERAL, CMD_FIND_BAND, start ? VALUE_ON : VALUE_OFF));
+        builder.queue(getQueue());
+    }
+
+    public FitProDeviceSupport setAutoHeartRate(TransactionBuilder builder) {
+        LOG.debug("FitPro set automatic heartrate measurements");
+        boolean prefAutoheartrateSwitch = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getBoolean("pref_autoheartrate_switch", false);
+        LOG.info("Setting autoheartrate to " + prefAutoheartrateSwitch);
+
+        boolean sleep = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getBoolean("pref_autoheartrate_sleep", false);
+        String start = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getString("pref_autoheartrate_start", "06:00");
+        String end = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getString("pref_autoheartrate_end", "23:00");
+        String interval = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getString("pref_autoheartrate_interval", "2");
+
+        int intervalInt = Integer.parseInt(interval);
+        int sleepInt = sleep ? 1 : 0;
+        int autoheartrateInt = prefAutoheartrateSwitch ? 1 : 0;
+
+        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);
+        }
+
+        int startTime = (startCalendar.get(Calendar.HOUR_OF_DAY) * 60) + startCalendar.get(Calendar.MINUTE);
+        int endTime = (endCalendar.get(Calendar.HOUR_OF_DAY) * 60) + endCalendar.get(Calendar.MINUTE);
+
+        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        outputStream.write(autoheartrateInt);
+        outputStream.write(sleepInt);
+        outputStream.write(intervalInt >> 8);
+        outputStream.write(intervalInt);
+        outputStream.write(startTime >> 8);
+        outputStream.write(startTime);
+        outputStream.write(endTime >> 8);
+        outputStream.write(endTime);
+        //outputStream.write(0x7F);
+
+        builder.write(writeCharacteristic, craftData(CMD_GROUP_GENERAL, CMD_GROUP_HEARTRATE_SETTINGS, outputStream.toByteArray()));
+
+        return this;
+    }
+
+    public FitProDeviceSupport setLongSitReminder(TransactionBuilder builder) {
+        LOG.debug("FitPro set inactivity warning");
+        boolean prefLongsitSwitch = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getBoolean(DeviceSettingsPreferenceConst.PREF_INACTIVITY_ENABLE, false);
+        LOG.info("Setting long sit warning to " + prefLongsitSwitch);
+
+        if (prefLongsitSwitch) {
+
+            String inactivity = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getString(DeviceSettingsPreferenceConst.PREF_INACTIVITY_THRESHOLD_EXTENDED, "4");
+            String start = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getString(DeviceSettingsPreferenceConst.PREF_INACTIVITY_START, "08:00");
+            String end = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getString(DeviceSettingsPreferenceConst.PREF_INACTIVITY_END, "16: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.debug("settings error: " + e);
+            }
+
+            int inactivityInt = Integer.parseInt(inactivity);
+            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+            try {
+                outputStream.write(VALUE_SET_LONG_SIT_REMINDER_ON);
+                outputStream.write(inactivityInt);
+                outputStream.write(startCalendar.get(Calendar.HOUR_OF_DAY));
+                outputStream.write(endCalendar.get(Calendar.HOUR_OF_DAY));
+                outputStream.write(0x7F);
+            } catch (IOException e) {
+                LOG.error("settings error: " + e);
+            }
+
+            builder.write(writeCharacteristic, craftData(CMD_GROUP_GENERAL, CMD_SET_LONG_SIT_REMINDER, outputStream.toByteArray()));
+            LOG.info("Setting long sit warning to scheduled");
+
+        } else {
+            builder.write(writeCharacteristic, craftData(CMD_GROUP_GENERAL, CMD_SET_LONG_SIT_REMINDER, VALUE_SET_LONG_SIT_REMINDER_OFF));
+            LOG.info("Setting long sit warning to OFF");
+        }
+        return this;
+    }
+
+    public FitProDeviceSupport setDoNotDisturb(TransactionBuilder builder) {
+        LOG.debug("FitPro set DND");
+        String dnd = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getString("do_not_disturb_no_auto", "off");
+        LOG.info("Setting DND to " + dnd);
+        int dndInt = dnd.equals("scheduled") ? 1 : 0;
+
+        String start = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getString("do_not_disturb_no_auto_start", "22:00");
+        String end = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getString("do_not_disturb_no_auto_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);
+        }
+
+        int startTime = (startCalendar.get(Calendar.HOUR_OF_DAY) * 60) + startCalendar.get(Calendar.MINUTE);
+        int endTime = (endCalendar.get(Calendar.HOUR_OF_DAY) * 60) + endCalendar.get(Calendar.MINUTE);
+
+        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+
+        outputStream.write(dndInt);
+        outputStream.write(startTime >> 8);
+        outputStream.write(startTime);
+        outputStream.write(endTime >> 8);
+        outputStream.write(endTime);
+
+        debugPrintArray(craftData(CMD_GROUP_GENERAL, CMD_DND, outputStream.toByteArray()), "enable DND");
+        debugPrintArray(outputStream.toByteArray(), "payload");
+        LOG.info("Setting DND to scheduled: " + start + " " + end);
+        builder.write(writeCharacteristic, craftData(CMD_GROUP_GENERAL, CMD_DND, outputStream.toByteArray()));
+        LOG.info("Setting DND scheduled");
+
+        return this;
+    }
+
+    public FitProDeviceSupport setSleepTime(TransactionBuilder builder) {
+        LOG.debug("FitPro set sleep times");
+        String sleepTime = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getString("prefs_enable_sleep_time", "off");
+        LOG.info("Setting sleep times to " + sleepTime);
+        int sleepTimeInt = sleepTime.equals("scheduled") ? 1 : 0;
+
+        String start = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getString("prefs_sleep_time_start", "22:00");
+        String end = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getString("prefs_sleep_time_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);
+        }
+
+        int startTime = (startCalendar.get(Calendar.HOUR_OF_DAY) * 60) + startCalendar.get(Calendar.MINUTE);
+        int endTime = (endCalendar.get(Calendar.HOUR_OF_DAY) * 60) + endCalendar.get(Calendar.MINUTE);
+
+        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        outputStream.write(sleepTimeInt);
+        outputStream.write(startTime >> 8);
+        outputStream.write(startTime);
+        outputStream.write(endTime >> 8);
+        outputStream.write(endTime);
+        debugPrintArray(craftData(CMD_GROUP_GENERAL, CMD_SET_SLEEP_TIMES, outputStream.toByteArray()), "enable sleep time");
+        debugPrintArray(outputStream.toByteArray(), "payload");
+        LOG.info("Setting sleep times scheduled: " + start + " " + end);
+        builder.write(writeCharacteristic, craftData(CMD_GROUP_GENERAL, CMD_SET_SLEEP_TIMES, outputStream.toByteArray()));
+        LOG.info("Setting sleep times scheduled");
+        return this;
+    }
+
+    public FitProDeviceSupport setWearLocation(TransactionBuilder builder) {
+        LOG.debug("FitPro set wearing location");
+        byte location = VALUE_SET_ARM_LEFT;
+        String setLocation = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getString(DeviceSettingsPreferenceConst.PREF_WEARLOCATION, "left");
+        if ("right".equals(setLocation)) {
+            location = VALUE_SET_ARM_RIGHT;
+        }
+        builder.write(writeCharacteristic, craftData(CMD_GROUP_GENERAL, CMD_SET_ARM, location));
+        return this;
+    }
+
+    public FitProDeviceSupport setDisplayOnLift(TransactionBuilder builder) {
+        LOG.debug("FitPro set display on lift");
+        String displayLift = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getString("activate_display_on_lift_wrist", "off");
+
+        int displayLiftInt = displayLift.equals("scheduled") ? 1 : 0;
+
+        LOG.info("Setting activate display on lift wrist to:" + displayLift + ": " + displayLiftInt);
+
+        String start = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getString("display_on_lift_start", "08:00");
+        String end = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getString("display_on_lift_end", "16: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);
+        }
+
+        int startTime = (startCalendar.get(Calendar.HOUR_OF_DAY) * 60) + startCalendar.get(Calendar.MINUTE);
+        int endTime = (endCalendar.get(Calendar.HOUR_OF_DAY) * 60) + endCalendar.get(Calendar.MINUTE);
+
+        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        outputStream.write(displayLiftInt);
+        outputStream.write(startTime >> 8);
+        outputStream.write(startTime);
+        outputStream.write(endTime >> 8);
+        outputStream.write(endTime);
+        debugPrintArray(craftData(CMD_GROUP_GENERAL, CMD_SET_DISPLAY_ON_LIFT, outputStream.toByteArray()), "enable lift display");
+        debugPrintArray(outputStream.toByteArray(), "payload");
+        LOG.info("Setting activate display on lift wrist scheduled: " + start + " " + end);
+        builder.write(writeCharacteristic, craftData(CMD_GROUP_GENERAL, CMD_SET_DISPLAY_ON_LIFT, outputStream.toByteArray()));
+        LOG.info("Setting activate display on lift wrist scheduled");
+
+        return this;
+    }
+
+    public FitProDeviceSupport setStepsGoal(TransactionBuilder builder) {
+        LOG.debug("FitPro set step goal");
+        //cd 00 09 12 01 03 00 04 00 00 05 dc
+
+        ActivityUser activityUser = new ActivityUser();
+        int stepGoal = activityUser.getStepsGoal();
+        byte[] data = craftData(CMD_GROUP_GENERAL, CMD_SET_STEP_GOAL, ByteBuffer.allocate(4).putInt(stepGoal).array());
+
+        builder.write(writeCharacteristic, data);
+        return this;
+    }
+
+    public void handleSleepData(byte[] value) {
+        debugPrintArray(value, "sleep data value");
+        // sleep packet consists of: date + list of 4bytes of 15minutes intervals
+        // these intervals contain seconds offset from the date and type of sleep
+        byte[] dateArray = new byte[2];
+        System.arraycopy(value, 8, dateArray, 0, 2);
+        Calendar date = decodeDateTime(dateArray);
+        List<FitProActivitySample> samples = new ArrayList<>();
+        for (int i = 12; i < value.length - 3; i = i + 4) {
+            byte[] packet = new byte[4];
+            System.arraycopy(value, i, packet, 0, 4);
+            int data = ByteBuffer.wrap(packet).getInt();
+            int activity_kind = (int) (data & 0xff);
+            int encodedTime = (int) (data >> 16);
+            int seconds = getSleepSecondsOfDay(encodedTime);
+            Calendar now = (Calendar) date.clone(); // do not modify the caller's argument
+            now.add(Calendar.SECOND, seconds);
+            int timestamp = (int) (now.getTimeInMillis() / 1000L);
+            debugPrintArray(packet, "processing sleep packet");
+
+            LOG.debug("FitPro new sleep: " + activity_kind + " seconds: " + seconds + " ts: " + timestamp + " date: " + DateTimeUtils.formatDateTime(new Date(timestamp * 1000L)));
+
+            FitProActivitySample sample = new FitProActivitySample();
+            sample.setTimestamp(timestamp);
+            sample.setHeartRate(ActivitySample.NOT_MEASURED);
+            sample.setActiveTimeMinutes(15);
+            sample.setRawKind(rawSleepKindToUniqueKind(activity_kind));
+            samples.add(sample);
+        }
+        if (addGBActivitySamples(samples)) {
+            sendAck(value[3], value[1], value[2], value[5]);
+        }
+    }
+
+    public int rawSleepKindToUniqueKind(int kind) {
+        //step and sleep are the same kind so we must distinguish them
+        return kind + 10;
+    }
+
+    public int rawActivityKindToUniqueKind(int kind) {
+        return kind;
+    }
+
+    public void handleStepData(byte[] value) {
+        debugPrintArray(value, "step data value");
+        // step packet consists of: date + list of 8bytes of (always?) 5minutes intervals
+        // these intervals contain seconds offset from the date, type of activity, calories,
+        // steps, distance, duration
+
+        byte[] dateArray = new byte[2];
+        System.arraycopy(value, 8, dateArray, 0, 2);
+        Calendar date = decodeDateTime(dateArray);
+        List<FitProActivitySample> samples = new ArrayList<>();
+        for (int i = 12; i < value.length - 7; i = i + 8) {
+            byte[] packet = new byte[8];
+            System.arraycopy(value, i, packet, 0, 8);
+            long data = ByteBuffer.wrap(packet).getLong();
+            int steps = (int) Math.abs((data >> 52));
+            int calories = (int) (data & 0x7ffff);
+            int activity_kind = (int) ((data >> 19) & 0x1);
+            int duration = (int) ((data >> 48) & 0xf);
+            int distance = (int) ((data >> 32) & 0xffff);
+            int encodedTime = (int) ((data >> 21) & 0x7ff);
+            int seconds = getSecondsOfDay(encodedTime);
+            Calendar now = (Calendar) date.clone(); // do not modify the caller's argument
+            now.add(Calendar.SECOND, seconds);
+            int timestamp = (int) (now.getTimeInMillis() / 1000L);
+            debugPrintArray(packet, "processing steps packet");
+            LOG.debug("FitPro adding new steps: " + steps);
+            FitProActivitySample sample = new FitProActivitySample();
+            sample.setTimestamp(timestamp);
+            sample.setHeartRate(ActivitySample.NOT_MEASURED);
+            sample.setSteps(steps);
+            sample.setDistanceMeters(distance);
+            sample.setCaloriesBurnt(calories);
+            sample.setActiveTimeMinutes(duration);
+            sample.setRawKind(rawActivityKindToUniqueKind(activity_kind));
+            samples.add(sample);
+        }
+        if (addGBActivitySamples(samples)) {
+            sendAck(value[3], value[1], value[2], value[5]);
+        }
+    }
+
+    public void addGBActivitySample(FitProActivitySample sample) {
+        List<FitProActivitySample> samples = new ArrayList<>();
+        samples.add(sample);
+        addGBActivitySamples(samples);
+    }
+
+    private boolean addGBActivitySamples(List<FitProActivitySample> samples) {
+        try (DBHandler dbHandler = GBApplication.acquireDB()) {
+
+            User user = DBHelper.getUser(dbHandler.getDaoSession());
+            Device device = DBHelper.getDevice(this.getDevice(), dbHandler.getDaoSession());
+            FitProSampleProvider provider = new FitProSampleProvider(this.getDevice(), dbHandler.getDaoSession());
+
+            for (FitProActivitySample sample : samples) {
+                sample.setDevice(device);
+                sample.setUser(user);
+                sample.setProvider(provider);
+                provider.addGBActivitySample(sample);
+            }
+
+        } catch (Exception ex) {
+            LOG.error("Error saving samples: " + ex);
+            GB.updateTransferNotification(null, "Data transfer failed", false, 0, getContext());
+            return false;
+        }
+        return true;
+    }
+
+    public void broadcastSample(FitProActivitySample sample) {
+        Intent intent = new Intent(DeviceService.ACTION_REALTIME_SAMPLES)
+                .putExtra(GBDevice.EXTRA_DEVICE, getDevice())
+                .putExtra(DeviceService.EXTRA_REALTIME_SAMPLE, sample);
+        LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent);
+    }
+
+    public void handleHR(int seconds, int heartRate, int pressureLow, int pressureHigh, int spo2) {
+        LOG.debug("FitPro handle heart rate measurement");
+
+        Calendar date = Calendar.getInstance();
+        date.set(Calendar.HOUR_OF_DAY, 0);
+        date.set(Calendar.MINUTE, 0);
+        date.set(Calendar.SECOND, 0);
+        date.add(Calendar.SECOND, seconds);
+        LOG.debug("date: " + date);
+
+        FitProActivitySample sample = new FitProActivitySample();
+
+        sample.setHeartRate(heartRate);
+        sample.setPressureLowMmHg(pressureLow);
+        sample.setPressureHighMmHg(pressureHigh);
+        sample.setSpo2Percent(spo2);
+
+        sample.setTimestamp((int) (date.getTimeInMillis() / 1000));
+        sample.setRawKind(ActivityKind.ACTIVITY.getCode());
+
+        addGBActivitySample(sample);
+        broadcastSample(sample);
+        GB.signalActivityDataFinish(getDevice());
+    }
+
+    public void handleDayTotalsData(int steps, int distance, int calories) {
+        //this is for day data values, not used in Gb, handleStepData uses the better, 5min data
+        LOG.debug("FitPro handle day total steps");
+
+        LOG.debug("Steps: " + steps);
+        LOG.debug("Distance: " + distance);
+        LOG.debug("Calories: " + calories);
+
+        Calendar dateStart = Calendar.getInstance();
+        dateStart.set(Calendar.HOUR_OF_DAY, 0);
+        dateStart.set(Calendar.MINUTE, 0);
+        dateStart.set(Calendar.SECOND, 0);
+
+        Calendar dateEnd = Calendar.getInstance();
+        dateEnd.set(Calendar.HOUR_OF_DAY, 23);
+        dateEnd.set(Calendar.MINUTE, 59);
+        dateEnd.set(Calendar.SECOND, 59);
+
+        int dayStepCount = getStepsOnDay(dateStart, dateEnd);
+        int newSteps = (steps - dayStepCount);
+        LOG.debug("FitPro dayStepCount " + dayStepCount);
+        LOG.debug("FitPro new steps " + newSteps);
+
+        /*
+        if (newSteps > 0) {
+            LOG.debug("FitPro adding new steps " + newSteps);
+            ShenTechActivitySample sample = new ShenTechActivitySample();
+            Calendar date = Calendar.getInstance();
+            sample.setTimestamp((int) (date.getTimeInMillis() / 1000));
+            sample.setSteps(newSteps);
+            sample.setDistanceMeters(distance);
+            sample.setCaloriesBurnt(calories);
+            sample.setRawKind(ActivityKind.ACTIVITY);
+            sample.setRawIntensity(1);
+            addGBActivitySample(sample);
+            broadcastSample(sample);
+        }
+         */
+    }
+
+    private int getStepsOnDay(Calendar dayStart, Calendar dayEnd) {
+        //this is for day data values, not used in Gb, handleStepData uses 5min data which is better
+        try (DBHandler dbHandler = GBApplication.acquireDB()) {
+
+            FitProSampleProvider provider = new FitProSampleProvider(this.getDevice(), dbHandler.getDaoSession());
+
+            List<FitProActivitySample> samples = provider.getActivitySamples(
+                    (int) (dayStart.getTimeInMillis() / 1000L),
+                    (int) (dayEnd.getTimeInMillis() / 1000L));
+
+            int totalSteps = 0;
+
+            for (FitProActivitySample sample : samples) {
+                totalSteps += sample.getSteps();
+            }
+
+            return totalSteps;
+
+        } catch (Exception ex) {
+            LOG.error(ex.getMessage());
+            return 0;
+        }
+    }
+
+    public int getSecondsOfDay(int encodedTime) {
+        int hours = (int) Math.floor((encodedTime * 15) / 60);
+        int minutes = (encodedTime * 15) % 60;
+        int seconds = (hours * 3600) + (minutes * 60);
+        return seconds;
+    }
+
+    public int getSleepSecondsOfDay(int encodedTime) {
+        int hours = (int) Math.floor(encodedTime / 60);
+        int minutes = encodedTime % 60;
+        int seconds = (hours * 3600) + (minutes * 60);
+        return seconds;
+    }
+
+    public Calendar decodeDateTime(byte[] dateArray) {
+        debugPrintArray(dateArray, "array to decode to date time");
+        short dateShort = ByteBuffer.wrap(dateArray).getShort();
+
+        int day = (dateShort & 0x1f);
+        int month = ((dateShort >> 5) & 0xf);
+        int year = ((dateShort >> 9) + 2000);
+
+        Calendar date = GregorianCalendar.getInstance();
+        date.set(year, month - 1, day, 0, 0, 0);
+        return date;
+    }
+
+    @Override
+    public boolean getImplicitCallbackModify() {
+        return true;
+    }
+
+    @Override
+    public boolean getSendWriteRequestResponse() {
+        return false;
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/flipper/zero/support/FlipperZeroBaseSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/flipper/zero/support/FlipperZeroBaseSupport.java
new file mode 100644
index 0000000..628c83e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/flipper/zero/support/FlipperZeroBaseSupport.java
@@ -0,0 +1,42 @@
+/*  Copyright (C) 2022-2024 Arjan Schrijver, Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.flipper.zero.support;
+
+import org.slf4j.LoggerFactory;
+
+import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
+
+public class FlipperZeroBaseSupport extends AbstractBTLEDeviceSupport {
+    public FlipperZeroBaseSupport() {
+        super(LoggerFactory.getLogger(FlipperZeroBaseSupport.class));
+    }
+
+    @Override
+    public boolean useAutoConnect() {
+        return false;
+    }
+
+    @Override
+    public boolean getImplicitCallbackModify() {
+        return true;
+    }
+
+    @Override
+    public boolean getSendWriteRequestResponse() {
+        return false;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/flipper/zero/support/FlipperZeroSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/flipper/zero/support/FlipperZeroSupport.java
new file mode 100644
index 0000000..eeb2413
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/flipper/zero/support/FlipperZeroSupport.java
@@ -0,0 +1,249 @@
+/*  Copyright (C) 2022-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.flipper.zero.support;
+
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.widget.Toast;
+
+import androidx.core.content.ContextCompat;
+
+import java.io.IOException;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.BatteryState;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.GattCharacteristic;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.GattService;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.IntentListener;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.battery.BatteryInfo;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.battery.BatteryInfoProfile;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+import nodomain.freeyourgadget.gadgetbridge.util.protobuf.messagefields.MessageField;
+import nodomain.freeyourgadget.gadgetbridge.util.protobuf.messagefields.NestedMessageField;
+import nodomain.freeyourgadget.gadgetbridge.util.protobuf.messagefields.RootMessageField;
+import nodomain.freeyourgadget.gadgetbridge.util.protobuf.messagefields.StringMessageField;
+import nodomain.freeyourgadget.gadgetbridge.util.protobuf.messagefields.VarintMessageField;
+
+public class FlipperZeroSupport extends FlipperZeroBaseSupport{
+    private BatteryInfoProfile batteryInfoProfile = new BatteryInfoProfile(this);
+
+    private final String UUID_SERIAL_SERVICE = "8fe5b3d5-2e7f-4a98-2a48-7acc60fe0000";
+    private final String UUID_SERIAL_CHARACTERISTIC_WRITE = "19ed82ae-ed21-4c9d-4145-228e62fe0000";
+    private final String UUID_SERIAL_CHARACTERISTIC_RESPONSE = "19ed82ae-ed21-4c9d-4145-228e61fe0000";
+
+    private final String COMMAND_PLAY_FILE = "nodomain.freeyourgadget.gadgetbridge.flipper.zero.PLAY_FILE";
+    private final String ACTION_PLAY_DONE = "nodomain.freeyourgadget.gadgetbridge.flipper.zero.PLAY_DONE";
+
+    private final int REQUEST_ID_OPEN_APP = 16;
+    private final int REQUEST_ID_EXIT_APP = 47;
+    private final int REQUEST_ID_LOAD_FILE = 48;
+    private final int REQUEST_ID_PRESS_BUTTON = 49;
+    private final int REQUEST_ID_RELEASE_BUTTON = 50;
+
+    private int messageId = 0;
+
+    BroadcastReceiver receiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, final Intent intent) {
+            new Thread(new Runnable() {
+                @Override
+                public void run() {
+                    if(COMMAND_PLAY_FILE.equals(intent.getAction())){
+                        handlePlaySubGHZ(intent);
+                    }
+                }
+            }).start();
+        }
+    };
+    boolean recevierRegistered = false;
+
+    private void handlePlaySubGHZ(Intent intent) {
+        String appName = intent.getExtras().getString("EXTRA_APP_NAME", "Sub-GHz");
+
+        String filePath = intent.getStringExtra("EXTRA_FILE_PATH");
+        if(filePath == null){
+            GB.log("missing EXTRA_FILE_PATH in intent", GB.ERROR, null);
+            return;
+        }
+        if(filePath.isEmpty()){
+            GB.log("empty EXTRA_FILE_PATH in intent", GB.ERROR, null);
+            return;
+        }
+
+        String buttonName = intent.getExtras().getString("EXTRA_BUTTON_NAME", "center");
+
+        long millis = intent.getExtras().getInt("EXTRA_DURATION", 1000);
+
+        GB.toast(String.format("playing %s file", appName), Toast.LENGTH_SHORT, GB.INFO);
+        playFile(appName, filePath, buttonName, millis);
+
+        Intent response = new Intent(ACTION_PLAY_DONE);
+        getContext().sendBroadcast(response);
+    }
+
+    public FlipperZeroSupport() {
+        super();
+
+        batteryInfoProfile.addListener(new IntentListener() {
+            @Override
+            public void notify(Intent intent) {
+                BatteryInfo info = intent.getParcelableExtra(BatteryInfoProfile.EXTRA_BATTERY_INFO);
+                GBDeviceEventBatteryInfo batteryEvent = new GBDeviceEventBatteryInfo();
+                batteryEvent.state = BatteryState.BATTERY_NORMAL;
+                batteryEvent.level = info.getPercentCharged();
+                evaluateGBDeviceEvent(batteryEvent);
+            }
+        });
+        addSupportedService(GattService.UUID_SERVICE_BATTERY_SERVICE);
+        addSupportedService(GattService.UUID_SERVICE_DEVICE_INFORMATION);
+        addSupportedProfile(batteryInfoProfile);
+
+        addSupportedService(UUID.fromString(UUID_SERIAL_SERVICE));
+    }
+
+    @Override
+    protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
+        if(!recevierRegistered) {
+            ContextCompat.registerReceiver(getContext(), receiver, new IntentFilter(COMMAND_PLAY_FILE), ContextCompat.RECEIVER_EXPORTED);
+            recevierRegistered = true;
+        }
+
+        builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext()));
+        builder.read(getCharacteristic(GattCharacteristic.UUID_CHARACTERISTIC_FIRMWARE_REVISION_STRING));
+
+        batteryInfoProfile.requestBatteryInfo(builder);
+        batteryInfoProfile.enableNotify(builder, true);
+
+        return builder
+                .notify(getCharacteristic(UUID.fromString(UUID_SERIAL_CHARACTERISTIC_RESPONSE)), true)
+                .requestMtu(512)
+                .add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZED, getContext()));
+    }
+
+    @Override
+    public boolean onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
+        if(characteristic.getUuid().equals(GattCharacteristic.UUID_CHARACTERISTIC_FIRMWARE_REVISION_STRING)){
+            String revision = characteristic.getStringValue(0);
+            getDevice().setFirmwareVersion(revision);
+            getDevice().sendDeviceUpdateIntent(getContext());
+        }
+        return super.onCharacteristicRead(gatt, characteristic, status);
+    }
+
+    @Override
+    public void dispose() {
+        super.dispose();
+
+        if(recevierRegistered) {
+            getContext().unregisterReceiver(receiver);
+            recevierRegistered = false;
+        }
+    }
+
+    private void sendSerialData(byte[] data){
+        new TransactionBuilder("send serial data")
+                .write(getCharacteristic(UUID.fromString(UUID_SERIAL_CHARACTERISTIC_WRITE)), data)
+                .queue(getQueue());
+    }
+
+    private RootMessageField createMainRequest(int requestFieldNumber, MessageField... children){
+        return new RootMessageField(
+                new VarintMessageField(1, messageId++),
+                new NestedMessageField(requestFieldNumber, children)
+        );
+    }
+
+    private void sendMainRequest(int requestFieldNumber, MessageField... children) throws IOException {
+        RootMessageField root = createMainRequest(requestFieldNumber, children);
+        sendSerialData(root.encodeToBytes());
+    }
+
+    private void openApp(String appName) throws IOException {
+        sendMainRequest(
+                REQUEST_ID_OPEN_APP,
+                new StringMessageField(1, appName),
+                new StringMessageField(2, "RPC")
+        );
+    }
+
+    private void openSubGhzApp() throws IOException {
+        openApp("Sub-GHz");
+    }
+
+    private void appLoadFile(String filePath) throws IOException {
+        sendMainRequest(
+                REQUEST_ID_LOAD_FILE,
+                new StringMessageField(1, filePath)
+        );
+    }
+
+    private void appButtonPress(String button) throws IOException {
+        sendMainRequest(
+                REQUEST_ID_PRESS_BUTTON,
+                new StringMessageField(1, button)
+        );
+    }
+
+    private void appButtonRelease() throws IOException {
+        sendMainRequest(
+                REQUEST_ID_RELEASE_BUTTON
+        );
+    }
+    private void appExitRequest() throws IOException {
+        sendMainRequest(
+                REQUEST_ID_EXIT_APP
+        );
+    }
+
+    @Override
+    public void onTestNewFunction() {
+        try {
+            openApp("Infrared");
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+
+    private void playFile(String appName, String filePath, String buttonName, long durationMillis){
+        try {
+            openApp(appName);
+            Thread.sleep(1000);
+            appLoadFile(filePath);
+            Thread.sleep(500);
+            appButtonPress(buttonName);
+            Thread.sleep(durationMillis);
+            appButtonRelease();
+            Thread.sleep(100);
+            appExitRequest();
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    @Override
+    public void onFetchRecordedData(int dataTypes) {
+        onTestNewFunction();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/galaxy_buds/GalaxyBudsDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/galaxy_buds/GalaxyBudsDeviceSupport.java
new file mode 100644
index 0000000..135f210
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/galaxy_buds/GalaxyBudsDeviceSupport.java
@@ -0,0 +1,58 @@
+/*  Copyright (C) 2021-2024 Arjan Schrijver, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.galaxy_buds;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import nodomain.freeyourgadget.gadgetbridge.service.AbstractHeadphoneDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceIoThread;
+import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol;
+
+public class GalaxyBudsDeviceSupport extends AbstractHeadphoneDeviceSupport {
+    private static final Logger LOG = LoggerFactory.getLogger(GalaxyBudsDeviceSupport.class);
+
+    @Override
+    public void onSendConfiguration(String config) {
+        super.onSendConfiguration(config);
+    }
+
+    @Override
+    public void onTestNewFunction() {
+        super.onTestNewFunction();
+    }
+
+    @Override
+    public synchronized GalaxyBudsIOThread getDeviceIOThread() {
+        return (GalaxyBudsIOThread) super.getDeviceIOThread();
+    }
+
+    @Override
+    public boolean useAutoConnect() {
+        return false;
+    }
+
+    protected GBDeviceProtocol createDeviceProtocol() {
+        return new GalaxyBudsProtocol(getDevice());
+    }
+
+    @Override
+    protected GBDeviceIoThread createDeviceIOThread() {
+        return new GalaxyBudsIOThread(getDevice(), getContext(), (GalaxyBudsProtocol) getDeviceProtocol(),
+                GalaxyBudsDeviceSupport.this, getBluetoothAdapter());
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/galaxy_buds/GalaxyBudsIOThread.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/galaxy_buds/GalaxyBudsIOThread.java
new file mode 100644
index 0000000..9a742fe
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/galaxy_buds/GalaxyBudsIOThread.java
@@ -0,0 +1,68 @@
+/*  Copyright (C) 2021-2024 narektor, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.galaxy_buds;
+
+import static nodomain.freeyourgadget.gadgetbridge.util.GB.hexdump;
+
+import android.bluetooth.BluetoothAdapter;
+import android.content.Context;
+import android.os.ParcelUuid;
+
+import androidx.annotation.NonNull;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.service.btclassic.BtClassicIoThread;
+
+public class GalaxyBudsIOThread extends BtClassicIoThread {
+    private static final Logger LOG = LoggerFactory.getLogger(GalaxyBudsIOThread.class);
+
+    private final GalaxyBudsProtocol galaxyBudsProtocol;
+    private final GBDevice gbDevice;
+
+    @NonNull
+    protected UUID getUuidToConnect(@NonNull ParcelUuid[] uuids) {
+        if (gbDevice.getType().equals(DeviceType.GALAXY_BUDS)) {
+            return galaxyBudsProtocol.UUID_GALAXY_BUDS_DEVICE_CTRL;
+        }
+        return galaxyBudsProtocol.UUID_GALAXY_BUDS_LIVE_DEVICE_CTRL;
+    }
+
+    public GalaxyBudsIOThread(GBDevice device, Context context, GalaxyBudsProtocol deviceProtocol,
+                              GalaxyBudsDeviceSupport galaxyBudsDeviceSupport, BluetoothAdapter bluetoothAdapter) {
+        super(device, context, deviceProtocol, galaxyBudsDeviceSupport, bluetoothAdapter);
+        galaxyBudsProtocol = deviceProtocol;
+        gbDevice = device;
+    }
+
+    @Override
+    protected byte[] parseIncoming(InputStream inStream) throws IOException {
+        byte[] buffer = new byte[1048576]; //HUGE read
+        int bytes = inStream.read(buffer);
+        LOG.debug("read " + bytes + " bytes. " + hexdump(buffer, 0, bytes));
+        return Arrays.copyOf(buffer, bytes);
+    }
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/galaxy_buds/GalaxyBudsProtocol.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/galaxy_buds/GalaxyBudsProtocol.java
new file mode 100644
index 0000000..26aae8c
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/galaxy_buds/GalaxyBudsProtocol.java
@@ -0,0 +1,548 @@
+/*  Copyright (C) 2021-2024 narektor, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.galaxy_buds;
+
+import static nodomain.freeyourgadget.gadgetbridge.util.CheckSums.crc16_ccitt;
+import static nodomain.freeyourgadget.gadgetbridge.util.GB.hexdump;
+
+import android.content.SharedPreferences;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.BatteryState;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol;
+
+public class GalaxyBudsProtocol extends GBDeviceProtocol {
+    private static final Logger LOG = LoggerFactory.getLogger(GalaxyBudsProtocol.class);
+
+    final UUID UUID_GALAXY_BUDS_DEVICE_CTRL = UUID.fromString("00001102-0000-1000-8000-00805f9b34fd");
+    final UUID UUID_GALAXY_BUDS_LIVE_DEVICE_CTRL = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
+
+    private static final byte SOM_BUDS = (byte) 0xFE;
+    private static final byte EOM_BUDS = (byte) 0xEE;
+    private static final byte SOM_BUDS_PLUS = (byte) 0xFD;
+    private static final byte EOM_BUDS_PLUS = (byte) 0xDD;
+
+    private byte StartOfMessage = SOM_BUDS;
+    private byte EndOfMessage = EOM_BUDS;
+
+    private boolean isFirstExchange = true;
+
+    //incoming
+    private static final byte battery_status = (byte) 0x60;
+
+    private static final byte battery_status2 = (byte) 0x61;
+
+    //outgoing
+    private static final byte find_device_start = (byte) 0xa0;
+    private static final byte find_device_stop = (byte) 0xa1;
+
+    private static final byte set_ambient_mode = (byte) 0x80; //0x0/0x1
+    private static final byte set_ambient_volume = (byte) 0x84; // 0x1-0x5
+
+    private static final byte set_ambient_voice_focus = (byte) 0x85; // 0x0/0x1
+
+    private static final byte set_lock_touch = (byte) 0x90; // 0x0/0x1
+    private static final byte set_game_mode = (byte) 0x87; // 0x0/0x2 no idea if this is doing anything...
+    // this is sent dynamically based on whether the current running app is a game or not
+
+    private static final byte set_equalizer = (byte) 0x86;
+
+    private static final byte set_reset = (byte) 0x50;
+
+    private static final byte set_touchpad_options = (byte) 0x92;
+
+    private static final byte get_debug_build_info = (byte) 0x28;
+    private static final byte get_serial_number = (byte) 0x29;
+    private static final byte get_debug_get_all_data = (byte) 0x26;
+    private static final byte get_debug_get_version = (byte) 0x24;
+
+    //Live
+    private static final byte set_automatic_noise_cancelling = (byte) 0x98; //0x0/0x1
+    private static final byte set_pressure_relief = (byte) 0x9f; //0x0/0x1
+
+    //Live and Pro
+    private static final byte set_live_pro_game_mode = (byte) 0x85; // 0x0/0x1 no idea if this is doing anything
+
+    //Pro
+    // Comments thanks to phh as per https://codeberg.org/Freeyourgadget/Gadgetbridge/issues/2642#issuecomment-445962
+
+    private static final byte set_spatial_audio_control = (byte) 0xc3; //0x0/0x1
+    // takes a boolean '1' or '0' to enable or disable 360 Audio (probably useless until someone
+    // manage to send Dolby Atmos to the headset). 360 audio is a feature where (Shown only on Samsung rom)
+
+    private static final byte set_outside_double_tap = (byte) 0x95; //0x0/0x1
+    // This is an option in "Labs", to detect double tap even when not taped on touch pad (so I presume it's using accelerometer)
+
+    private static final byte set_adjust_sound_sync = (byte) 0x85; //0x0/0x1
+    // This is an option in "Labs" available only on Samsung ROM, exposed to the user as "automatic game mode".
+    // This is used in confunction with GAME_MODE. My guess is that this says to the ear buds
+    // "yes, I know that the reported latency might change on the fly, I'm fine with that".
+
+    private static final byte set_detect_conversations = (byte) 0x7a; //0x0/0x1
+    // This is called "Voice detect -- Noise controls and sound settings go back to the previous state
+    // when your voice isn't detected for 10 seconds". This triggers the feature that when you talk,
+    // the ear buds automatically go into "ambient sound" mode, and enhance the sound of your interlocutor
+
+    private static final byte set_detect_conversations_duration = (byte) 0x7b; //0x0/0x1/0x2
+    // Takes {0,1,2}: 0 means 5 seconds, 1 means 10 seconds, 2 means 15s. This is the duration after
+    // which the ear buds go back to ANC after switching to ambient sound when the user talks.
+
+    private static final byte set_ambient_mode_during_calls = (byte) 0x8b; //0x0/0x1
+    // "Use ambient sound during calls"
+
+    private static final byte set_noise_controls_with_one_earbud = (byte) 0x6f; //0x0/0x1
+    // "Noise controls with one earbud" in Accessibility menu.
+    // It allows ANC and ambient sound even if only one earbud is in-ear.
+
+    private static final byte set_balance = (byte) 0x8f;
+    // takes value in 0-32 range, it is used to change left/right balance
+
+    private static final byte extra_high_ambient = (byte) 0x96; //0x0/0x1
+    //  "Maximize ambient sound volume"/"Amplify sounds from your surroundings so you can stay aware of what's going on around you.
+
+    private static final byte set_seamless_connection = (byte) 0xaf; //0x0/0x1
+    // It is used to allow the ear buds to roam across devices, like devices being allowed to take ear buds "focus".
+
+    private static final byte set_voice_wake_up = (byte) 0x97; //0x0/0x1
+    // Enables "Hey Bixby" wake up word. Shown only on Samsung ROM
+
+    private static final byte set_speak_seamlessly = (byte) 0x7d; //0x0/0x1
+    //  "After Voice wake up, you can say the command you want right away without waiting for sound feedback."
+
+    private static final byte voice_wake_up_language = (byte) 0x99;
+    // Language for "Hey bixby" wakeup - 1 = "de-DE", 2 = "en-GB", 3 = "en-US", 4 = "es-ES", 5 = "fr-FR", 6 = "it-IT", 7 = "ko-KR", 8 = "pt-BR", 9 = "zh-CN"
+
+    private static final byte set_voice_noti_status = (byte) 0xa4; //0x0/0x1
+    // I have no idea why it does that. It sends "1" when it starts reading notification aloud, and "0" when it finished.
+
+    private static final byte set_noise_controls = (byte) 0x78; //Takes 0/1/2.
+    // 0 is Ambient Sound and ANC OFF, 1 is ANC on, 2 is Ambient sound ON
+
+    private static final byte set_mute_earbud = (byte) 0xa2;
+    // Takes two booleans (left then right). This is used in conjuction with FIND_MY_EARBUDS_START to alternate between tweeting the left and the right earbud
+
+    private static final byte set_customize_ambient_sound = (byte) 0x82;
+    //one byte for left volume 0-4, one byte for right volume 0-4, and one byte 0-4 for ambient sound tone from "soft" to "clear"
+
+    private static final byte set_noise_reduction_level = (byte) 0x83;
+    // 1 means High noise reduction, 0 low noise reduction.
+
+    private static final byte set_touch_and_hold_noise_controls = (byte) 0x79;
+    // Takes either 3 booleans or 6 booleans depending on earbuds revision.
+    // It is used when long press on touchpad is set in SET_TOUCHPAD_OPTION to "Switch noise control",
+    // to control whether the long press switches between ANC on <=> Ambient sound on, or to anc+ambient off,
+    // or ambient <=> off or anc <=> off, and when it's 6 bytes, it can have different behavior between right and left earbud.
+
+    private static final byte voice_wake_up_event = (byte) 0x9a;
+    // Just a reponse. It's a ACK for when received "Hey bixby" command ?
+
+    private static final byte in_ear_detection = (byte) 0x6e;
+
+    @Override
+    public GBDeviceEvent[] decodeResponse(byte[] responseData) {
+        List<GBDeviceEvent> devEvts = new ArrayList<>();
+        LOG.debug("received data: " + hexdump(responseData));
+        LOG.debug("received data length: " + responseData.length);
+
+        if (isFirstExchange) {
+            isFirstExchange = false;
+            devEvts.add(new GBDeviceEventVersionInfo()); //TODO: this is a weird hack to make the DBHelper happy. Replace with proper + detection
+        }
+
+        ByteBuffer incoming = ByteBuffer.wrap(responseData);
+        incoming.order(ByteOrder.LITTLE_ENDIAN);
+
+        int length = 9;
+        byte type = 0;
+
+        byte sof = incoming.get();
+        if (sof != StartOfMessage) {
+            LOG.error("Error in message, wrong start of frame: " + hexdump(responseData));
+            return null;
+        }
+
+        if (StartOfMessage == SOM_BUDS_PLUS) {
+            length = (int) (incoming.get() & 0xff);
+            type = incoming.get();
+        } else {
+            type = incoming.get();
+            length = (int) (incoming.get() & 0xff);
+        }
+
+        byte message_id = incoming.get();
+        byte[] payload;
+        try {
+            payload = Arrays.copyOfRange(responseData, incoming.position(), incoming.position() + length);
+        } catch (Exception e) {
+            LOG.error("Error getting payload data: " + length + " , " + e);
+            return null;
+        }
+
+        switch (message_id) {
+            case battery_status:
+                devEvts.addAll(handleBatteryInfo(Arrays.copyOfRange(payload, 1, 11))); //11
+                break;
+            case battery_status2:
+                devEvts.addAll(handleBatteryInfo(Arrays.copyOfRange(payload, 2, 12))); //12
+                break;
+            default:
+                LOG.debug("Unhandled: " + hexdump(responseData));
+
+        }
+        return devEvts.toArray(new GBDeviceEvent[devEvts.size()]);
+    }
+
+
+    byte[] encodeMessage(byte command) {
+        ByteBuffer msgBuf = ByteBuffer.allocate(7);
+        msgBuf.order(ByteOrder.LITTLE_ENDIAN);
+        msgBuf.put(StartOfMessage);
+        byte size = 0x3;
+        if (StartOfMessage == SOM_BUDS_PLUS) {
+            msgBuf.put((byte) size);
+            msgBuf.put((byte) 0x0); //0x0 for sending
+        } else {
+            msgBuf.put((byte) 0x0); //0x0 for sending
+            msgBuf.put((byte) size); //size
+        }
+        msgBuf.put((byte) command); //command id
+        msgBuf.putShort((short) crc16_ccitt(new byte[]{command}));
+        msgBuf.put(EndOfMessage);
+        LOG.debug("DEBUG: " + hexdump(msgBuf.array()));
+        return msgBuf.array();
+    }
+
+    byte[] encodeMessage(byte command, byte parameter) {
+        ByteBuffer msgBuf = ByteBuffer.allocate(8);
+        msgBuf.order(ByteOrder.LITTLE_ENDIAN);
+        msgBuf.put(StartOfMessage);
+        byte size = 0x4;
+        if (StartOfMessage == SOM_BUDS_PLUS) {
+            msgBuf.put((byte) size);
+            msgBuf.put((byte) 0x0); //0x0 for sending
+        } else {
+            msgBuf.put((byte) 0x0); //0x0 for sending
+            msgBuf.put((byte) size); //size
+        }
+        msgBuf.put((byte) command); //command id
+        msgBuf.put((byte) parameter);
+        msgBuf.putShort((short) crc16_ccitt(new byte[]{command, parameter}));
+        msgBuf.put(EndOfMessage);
+        LOG.debug("DEBUG: " + hexdump(msgBuf.array()));
+        return msgBuf.array();
+    }
+
+    byte[] encodeMessage(byte command, byte parameter, byte value) {
+        ByteBuffer msgBuf = ByteBuffer.allocate(9);
+        msgBuf.order(ByteOrder.LITTLE_ENDIAN);
+        msgBuf.put(StartOfMessage);
+        byte size = 0x5;
+        if (StartOfMessage == SOM_BUDS_PLUS) {
+            msgBuf.put((byte) size);
+            msgBuf.put((byte) 0x0); //0x0 for sending
+        } else {
+            msgBuf.put((byte) 0x0); //0x0 for sending
+            msgBuf.put((byte) size); //size
+        }
+        msgBuf.put((byte) command);
+        msgBuf.put((byte) parameter);
+        msgBuf.put((byte) value);
+        msgBuf.putShort((short) crc16_ccitt(new byte[]{command, parameter, value}));
+        msgBuf.put(EndOfMessage);
+        LOG.debug("DEBUG: " + hexdump(msgBuf.array()));
+        return msgBuf.array();
+    }
+
+    byte[] encodeMessage(byte command, byte[] payload) {
+        byte payload_size = (byte) (3 + payload.length);
+        ByteBuffer msgBuf = ByteBuffer.allocate(4 + payload_size);
+        msgBuf.order(ByteOrder.LITTLE_ENDIAN);
+        msgBuf.put(StartOfMessage);
+
+        if (StartOfMessage == SOM_BUDS_PLUS) {
+            msgBuf.put((byte) payload_size);
+            msgBuf.put((byte) 0x0); //0x0 for sending
+        } else {
+            msgBuf.put((byte) 0x0); //0x0 for sending
+            msgBuf.put((byte) payload_size); //size
+        }
+        msgBuf.put((byte) command);
+        msgBuf.put(payload);
+
+        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+
+        try {
+            outputStream.write(command);
+            outputStream.write(payload);
+        } catch (IOException e) {
+            LOG.warn("Assembling message failed: " + e.getMessage());
+        }
+
+        msgBuf.putShort((short) crc16_ccitt(outputStream.toByteArray()));
+        msgBuf.put(EndOfMessage);
+        LOG.debug("DEBUG: " + hexdump(msgBuf.array()));
+        return msgBuf.array();
+    }
+
+
+    @Override
+    public byte[] encodeFindDevice(boolean start) {
+        byte command = (byte) (start ? find_device_start : find_device_stop);
+        return encodeMessage(command);
+    }
+
+    @Override
+    public byte[] encodeReset(int reset) {
+        if (reset == RESET_FLAGS_FACTORY_RESET) {
+            return encodeMessage(set_reset);
+        }
+        return null;
+    }
+
+    @Override
+    public byte[] encodeTestNewFunction() {
+        //return encodeMessage(get_debug_build_info);
+        return null;
+    }
+
+    @Override
+    public byte[] encodeSendConfiguration(String config) {
+
+        SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress());
+
+        switch (config) {
+            case DeviceSettingsPreferenceConst.PREF_GALAXY_PRO_DOUBLE_TAP_EDGE:
+                byte outside_double_tap = (byte) (prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_GALAXY_PRO_DOUBLE_TAP_EDGE, false) ? 0x01 : 0x00);
+                return encodeMessage(set_outside_double_tap, outside_double_tap);
+
+            case DeviceSettingsPreferenceConst.PREF_GALAXY_BUDS_PRO_VOICE_DETECT:
+                byte detect_conversations = (byte) (prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_GALAXY_BUDS_PRO_VOICE_DETECT, false) ? 0x01 : 0x00);
+                return encodeMessage(set_detect_conversations, detect_conversations);
+
+            case DeviceSettingsPreferenceConst.PREF_GALAXY_BUDS_PRO_VOICE_DETECT_DURATION:
+                String voice_detect_duration = prefs.getString(DeviceSettingsPreferenceConst.PREF_GALAXY_BUDS_PRO_VOICE_DETECT_DURATION, "1");
+                byte voice_detect_duration_b = (byte) Integer.parseInt(voice_detect_duration);
+                return encodeMessage(set_detect_conversations_duration, voice_detect_duration_b);
+
+
+            case DeviceSettingsPreferenceConst.PREF_GALAXY_BUDS_AMBIENT_MODE_DURING_CALL:
+                byte ambient_mode_during_calls = (byte) (prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_GALAXY_BUDS_AMBIENT_MODE_DURING_CALL, false) ? 0x01 : 0x00);
+                return encodeMessage(set_ambient_mode_during_calls, ambient_mode_during_calls);
+
+            case DeviceSettingsPreferenceConst.PREFS_NOISE_CONTROL_WITH_ONE_EARBUD:
+                byte noise_controls_with_one_earbud = (byte) (prefs.getBoolean(DeviceSettingsPreferenceConst.PREFS_NOISE_CONTROL_WITH_ONE_EARBUD, false) ? 0x01 : 0x00);
+                return encodeMessage(set_noise_controls_with_one_earbud, noise_controls_with_one_earbud);
+
+            case DeviceSettingsPreferenceConst.PREF_GALAXY_BUDS_PRO_IN_EAR_DETECTION:
+                byte ear_detection = (byte) (prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_GALAXY_BUDS_PRO_IN_EAR_DETECTION, false) ? 0x01 : 0x00);
+                return encodeMessage(in_ear_detection, ear_detection);
+
+            case DeviceSettingsPreferenceConst.PREF_GALAXY_BUDS_PRO_BALANCE:
+                int hearing_enhancements = prefs.getInt(DeviceSettingsPreferenceConst.PREF_GALAXY_BUDS_PRO_BALANCE, 16);
+                return encodeMessage(set_balance, (byte) hearing_enhancements);
+
+            case DeviceSettingsPreferenceConst.PREF_GALAXY_BUDS_PRO_NOISE_CONTROL:
+                int noise_controls = Integer.parseInt(prefs.getString(DeviceSettingsPreferenceConst.PREF_GALAXY_BUDS_PRO_NOISE_CONTROL, "0"));
+                return encodeMessage(set_noise_controls, (byte) noise_controls);
+
+            case DeviceSettingsPreferenceConst.PREF_GALAXY_BUDS_2_NOISE_CONTROL:
+                int b2_noise_controls = Integer.parseInt(prefs.getString(DeviceSettingsPreferenceConst.PREF_GALAXY_BUDS_2_NOISE_CONTROL, "0"));
+                return encodeMessage(set_noise_controls, (byte) b2_noise_controls);
+
+            case DeviceSettingsPreferenceConst.PREF_GALAXY_BUDS_PRO_ANC_LEVEL:
+                int anc_level = Integer.parseInt(prefs.getString(DeviceSettingsPreferenceConst.PREF_GALAXY_BUDS_PRO_ANC_LEVEL, "0"));
+                return encodeMessage(set_noise_reduction_level, (byte) anc_level);
+
+            case DeviceSettingsPreferenceConst.PREF_GALAXY_BUDS_AMBIENT_SOUND:
+            case DeviceSettingsPreferenceConst.PREF_GALAXY_BUDS_PRO_AMBIENT_VOLUME_RIGHT:
+            case DeviceSettingsPreferenceConst.PREF_GALAXY_BUDS_PRO_AMBIENT_VOLUME_LEFT:
+            case DeviceSettingsPreferenceConst.PREF_GALAXY_BUDS_PRO_AMBIENT_SOUND_TONE:
+                byte ambient_sound = prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_GALAXY_BUDS_AMBIENT_SOUND, true) ? (byte) 1 : (byte) 0;
+                int ambient_right = prefs.getInt(DeviceSettingsPreferenceConst.PREF_GALAXY_BUDS_PRO_AMBIENT_VOLUME_RIGHT, 1);
+                int ambient_left = prefs.getInt(DeviceSettingsPreferenceConst.PREF_GALAXY_BUDS_PRO_AMBIENT_VOLUME_LEFT, 1);
+                int sound_tone = prefs.getInt(DeviceSettingsPreferenceConst.PREF_GALAXY_BUDS_PRO_AMBIENT_SOUND_TONE, 1);
+                return encodeMessage(set_customize_ambient_sound, new byte[]{ambient_sound, (byte) ambient_left, (byte) ambient_right, (byte) sound_tone});
+
+            case DeviceSettingsPreferenceConst.PREF_GALAXY_BUDS_AMBIENT_MODE:
+                byte enable_ambient = (byte) (prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_GALAXY_BUDS_AMBIENT_MODE, false) ? 0x01 : 0x00);
+                return encodeMessage(set_ambient_mode, enable_ambient);
+
+            case DeviceSettingsPreferenceConst.PREFS_GALAXY_BUDS_SEAMLESS_CONNECTION:
+                byte seamless_switch = (byte) (prefs.getBoolean(DeviceSettingsPreferenceConst.PREFS_GALAXY_BUDS_SEAMLESS_CONNECTION, false) ? 0x00 : 0x01);
+                return encodeMessage(set_seamless_connection, seamless_switch);
+
+            case DeviceSettingsPreferenceConst.PREF_GALAXY_BUDS_AMBIENT_VOICE_FOCUS:
+                byte enable_voice = (byte) (prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_GALAXY_BUDS_AMBIENT_VOICE_FOCUS, false) ? 0x01 : 0x00);
+                return encodeMessage(set_ambient_voice_focus, enable_voice);
+            case DeviceSettingsPreferenceConst.PREF_GALAXY_BUDS_AMBIENT_VOLUME:
+                int ambient_volume = prefs.getInt(DeviceSettingsPreferenceConst.PREF_GALAXY_BUDS_AMBIENT_VOLUME, 0);
+                byte ambient_volume_byte = (byte) (ambient_volume);
+                return encodeMessage(set_ambient_volume, ambient_volume_byte);
+            case DeviceSettingsPreferenceConst.PREF_GALAXY_BUDS_LOCK_TOUCH:
+                byte set_lock = (byte) (prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_GALAXY_BUDS_LOCK_TOUCH, false) ? 0x01 : 0x00);
+                return encodeMessage(set_lock_touch, set_lock);
+            case DeviceSettingsPreferenceConst.PREF_GALAXY_BUDS_GAME_MODE:
+                if (StartOfMessage == SOM_BUDS_PLUS) {
+                    byte game_mode = (byte) (prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_GALAXY_BUDS_GAME_MODE, false) ? 0x1 : 0x00);
+                    return encodeMessage(set_live_pro_game_mode, game_mode);
+                } else {
+                    byte game_mode = (byte) (prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_GALAXY_BUDS_GAME_MODE, false) ? 0x2 : 0x00);
+                    return encodeMessage(set_game_mode, game_mode);
+                }
+
+            case DeviceSettingsPreferenceConst.PREF_GALAXY_BUDS_EQUALIZER:
+            case DeviceSettingsPreferenceConst.PREF_GALAXY_BUDS_EQUALIZER_DOLBY:
+            case DeviceSettingsPreferenceConst.PREF_GALAXY_BUDS_EQUALIZER_MODE:
+                byte equalizer = (byte) (prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_GALAXY_BUDS_EQUALIZER, false) ? 0x1 : 0x00);
+                boolean equalizer_dolby = prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_GALAXY_BUDS_EQUALIZER_DOLBY, false);
+                int dolby = 0;
+                if (!equalizer_dolby) {
+                    dolby = 5;
+                }
+                String equalizer_mode = prefs.getString(DeviceSettingsPreferenceConst.PREF_GALAXY_BUDS_EQUALIZER_MODE, "0");
+
+                if (StartOfMessage == SOM_BUDS_PLUS) {
+                    return encodeMessage(set_equalizer, (byte) (Integer.parseInt(equalizer_mode)));
+                } else {
+                    byte mode = (byte) (Integer.parseInt(equalizer_mode) + dolby);
+                    return encodeMessage(set_equalizer, equalizer, mode);
+                }
+
+            case DeviceSettingsPreferenceConst.PREF_GALAXY_BUDS_TOUCH_LEFT:
+            case DeviceSettingsPreferenceConst.PREF_GALAXY_BUDS_TOUCH_RIGHT:
+                String touch_left = prefs.getString(DeviceSettingsPreferenceConst.PREF_GALAXY_BUDS_TOUCH_LEFT, "1");
+                String touch_right = prefs.getString(DeviceSettingsPreferenceConst.PREF_GALAXY_BUDS_TOUCH_RIGHT, "1");
+                byte touchmode_left = (byte) Integer.parseInt(touch_left);
+                byte touchmode_right = (byte) Integer.parseInt(touch_right);
+                return encodeMessage(set_touchpad_options, touchmode_left, touchmode_right);
+
+            case DeviceSettingsPreferenceConst.PREF_GALAXY_BUDS_TOUCH_RIGHT_SWITCH:
+            case DeviceSettingsPreferenceConst.PREF_GALAXY_BUDS_TOUCH_LEFT_SWITCH:
+                String touch_right_switch = prefs.getString(DeviceSettingsPreferenceConst.PREF_GALAXY_BUDS_TOUCH_RIGHT_SWITCH, "1");
+                String touch_left_switch = prefs.getString(DeviceSettingsPreferenceConst.PREF_GALAXY_BUDS_TOUCH_LEFT_SWITCH, "1");
+                byte[] touch_right_switch_b = encode_touch_switch(touch_right_switch);
+                byte[] touch_left_switch_b = encode_touch_switch(touch_left_switch);
+                ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+                try {
+                    outputStream.write(touch_left_switch_b);
+                    outputStream.write(touch_right_switch_b);
+                } catch (IOException e) {
+                    LOG.warn("Assembling message failed: " + e.getMessage());
+                }
+                return encodeMessage(set_touch_and_hold_noise_controls, outputStream.toByteArray());
+
+
+            case DeviceSettingsPreferenceConst.PREF_GALAXY_BUDS_LIVE_ANC:
+                byte enable_anc = (byte) (prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_GALAXY_BUDS_LIVE_ANC, false) ? 0x1 : 0x00);
+                return encodeMessage(set_automatic_noise_cancelling, enable_anc);
+
+            case DeviceSettingsPreferenceConst.PREF_GALAXY_BUDS_PRESSURE_RELIEF:
+                byte enable_pressure_relief = (byte) (prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_GALAXY_BUDS_PRESSURE_RELIEF, false) ? 0x1 : 0x00);
+                return encodeMessage(set_pressure_relief, enable_pressure_relief);
+
+            default:
+                LOG.debug("CONFIG: " + config);
+        }
+        return super.encodeSendConfiguration(config);
+    }
+
+
+    private List<GBDeviceEvent> handleBatteryInfo(byte[] payload) {
+        List<GBDeviceEvent> deviceEvents = new ArrayList<>();
+        LOG.debug("Battery payload: " + hexdump(payload));
+        LOG.debug("pl: " + payload.length);
+        LOG.debug("p0: " + payload[0]);
+        LOG.debug("p1: " + payload[1]);
+        LOG.debug("p2: " + payload[5]);
+
+        int batteryLevel1 = payload[0];
+        int batteryLevel2 = payload[1];
+        int batteryLevel3 = payload[5];
+
+        GBDeviceEventBatteryInfo evBattery1 = new GBDeviceEventBatteryInfo();
+        evBattery1.batteryIndex = 0;
+        evBattery1.level = GBDevice.BATTERY_UNKNOWN;
+        evBattery1.level = (batteryLevel1 > 0) ? batteryLevel1 : GBDevice.BATTERY_UNKNOWN;
+        evBattery1.state = (batteryLevel1 > 0) ? BatteryState.BATTERY_NORMAL : BatteryState.UNKNOWN;
+        deviceEvents.add(evBattery1);
+
+
+        GBDeviceEventBatteryInfo evBattery2 = new GBDeviceEventBatteryInfo();
+        evBattery2.batteryIndex = 1;
+        evBattery2.level = GBDevice.BATTERY_UNKNOWN;
+        evBattery2.level = (batteryLevel2 > 0) ? batteryLevel2 : GBDevice.BATTERY_UNKNOWN;
+        evBattery2.state = (batteryLevel2 > 0) ? BatteryState.BATTERY_NORMAL : BatteryState.UNKNOWN;
+        deviceEvents.add(evBattery2);
+
+
+        if (StartOfMessage == SOM_BUDS_PLUS) {
+            GBDeviceEventBatteryInfo evBattery3 = new GBDeviceEventBatteryInfo();
+            // reorder for the non OG version
+            evBattery1.batteryIndex = 1; //left
+            evBattery2.batteryIndex = 2; //right
+            evBattery3.batteryIndex = 0; //case
+
+            evBattery3.level = GBDevice.BATTERY_UNKNOWN;
+            evBattery3.level = (batteryLevel3 > 0) ? batteryLevel3 : GBDevice.BATTERY_UNKNOWN;
+            evBattery3.state = (batteryLevel3 > 0) ? BatteryState.BATTERY_NORMAL : BatteryState.UNKNOWN;
+            deviceEvents.add(evBattery3);
+        }
+
+        return deviceEvents;
+    }
+
+    private byte[] encode_touch_switch(String input) {
+        switch (input) {
+            case "1":
+                return new byte[]{0x1, 0x0, 0x1};
+            case "2":
+                return new byte[]{0x0, 0x1, 0x1};
+            default:
+                return new byte[]{0x1, 0x1, 0x0};
+        }
+    }
+
+    protected GalaxyBudsProtocol(GBDevice device) {
+        super(device);
+        DeviceType type = device.getType();
+        if (type.equals(DeviceType.GALAXY_BUDS_LIVE)
+                || type.equals(DeviceType.GALAXY_BUDS_PRO)
+                || type.equals(DeviceType.GALAXY_BUDS2)
+                || type.equals(DeviceType.GALAXY_BUDS2_PRO)) {
+            StartOfMessage = SOM_BUDS_PLUS;
+            EndOfMessage = EOM_BUDS_PLUS;
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/ChecksumCalculator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/ChecksumCalculator.java
new file mode 100644
index 0000000..8bddc9b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/ChecksumCalculator.java
@@ -0,0 +1,50 @@
+/*  Copyright (C) 2023-2024 Petr Kadlec
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin;
+
+import java.nio.ByteBuffer;
+
+public final class ChecksumCalculator {
+    private static final int[] CONSTANTS = {
+            0x0000, 0xCC01, 0xD801, 0x1400, 0xF001, 0x3C00, 0x2800, 0xE401,
+            0xA001, 0x6C00,0x7800, 0xB401, 0x5000, 0x9C01, 0x8801, 0x4400
+    };
+
+    private ChecksumCalculator() {
+    }
+
+    public static int computeCrc(byte[] data, int offset, int length) {
+        return computeCrc(0, data, offset, length);
+    }
+
+    public static int computeCrc(ByteBuffer byteBuffer, int offset, int length) {
+        byteBuffer.rewind();
+        byte[] data = new byte[length];
+        byteBuffer.get(data);
+        return computeCrc(0, data, offset, length);
+    }
+
+    public static int computeCrc(int initialCrc, byte[] data, int offset, int length) {
+        int crc = initialCrc;
+        for (int i = offset; i < offset + length; ++i) {
+            int b = data[i];
+            crc = (((crc >> 4) & 4095) ^ CONSTANTS[crc & 15]) ^ CONSTANTS[b & 15];
+            crc = (((crc >> 4) & 4095) ^ CONSTANTS[crc & 15]) ^ CONSTANTS[(b >> 4) & 15];
+        }
+        return crc;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/FileTransferHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/FileTransferHandler.java
new file mode 100644
index 0000000..5173a5a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/FileTransferHandler.java
@@ -0,0 +1,382 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin;
+
+import androidx.annotation.NonNull;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.Set;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.deviceevents.FileDownloadedDeviceEvent;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.CreateFileMessage;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.DownloadRequestMessage;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.FileTransferDataMessage;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.GFDIMessage;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.SystemEventMessage;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.UploadRequestMessage;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.status.CreateFileStatusMessage;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.status.DownloadRequestStatusMessage;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.status.FileTransferDataStatusMessage;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.status.UploadRequestStatusMessage;
+import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
+
+public class FileTransferHandler implements MessageHandler {
+    private static final Logger LOG = LoggerFactory.getLogger(FileTransferHandler.class);
+    private final GarminSupport deviceSupport;
+    private final Download download;
+    private final Upload upload;
+
+    private static final Set<FileType.FILETYPE> FILE_TYPES_TO_PROCESS = new HashSet<FileType.FILETYPE>() {{
+        add(FileType.FILETYPE.DIRECTORY);
+        add(FileType.FILETYPE.ACTIVITY);
+        add(FileType.FILETYPE.MONITOR);
+        add(FileType.FILETYPE.METRICS);
+        add(FileType.FILETYPE.CHANGELOG);
+        add(FileType.FILETYPE.HRV_STATUS);
+        add(FileType.FILETYPE.SLEEP);
+        add(FileType.FILETYPE.SKIN_TEMP);
+    }};
+
+    public FileTransferHandler(GarminSupport deviceSupport) {
+        this.deviceSupport = deviceSupport;
+        this.download = new Download();
+        this.upload = new Upload();
+    }
+
+    public boolean isDownloading() {
+        return download.getCurrentlyDownloading() != null;
+    }
+
+    public boolean isUploading() {
+        return upload.getCurrentlyUploading() != null;
+    }
+
+    public GFDIMessage handle(GFDIMessage message) {
+        if (message instanceof DownloadRequestStatusMessage)
+            download.processDownloadRequestStatusMessage((DownloadRequestStatusMessage) message);
+        else if (message instanceof FileTransferDataMessage)
+            download.processDownloadChunkedMessage((FileTransferDataMessage) message);
+        else if (message instanceof CreateFileStatusMessage)
+            return upload.setCreateFileStatusMessage((CreateFileStatusMessage) message);
+        else if (message instanceof UploadRequestStatusMessage)
+            return upload.setUploadRequestStatusMessage((UploadRequestStatusMessage) message);
+        else if (message instanceof FileTransferDataStatusMessage)
+            return upload.processUploadProgress((FileTransferDataStatusMessage) message);
+
+        return null;
+    }
+
+    public DownloadRequestMessage downloadDirectoryEntry(DirectoryEntry directoryEntry) {
+        download.setCurrentlyDownloading(new FileFragment(directoryEntry));
+        return new DownloadRequestMessage(directoryEntry.getFileIndex(), 0, DownloadRequestMessage.REQUEST_TYPE.NEW, 0, 0);
+    }
+
+    public DownloadRequestMessage initiateDownload() {
+        download.setCurrentlyDownloading(new FileFragment(new DirectoryEntry(0, FileType.FILETYPE.DIRECTORY, 0, 0, 0, 0, null)));
+        return new DownloadRequestMessage(0, 0, DownloadRequestMessage.REQUEST_TYPE.NEW, 0, 0);
+    }
+//    public DownloadRequestMessage downloadSettings() {
+//        download.setCurrentlyDownloading(new FileFragment(new DirectoryEntry(0, FileType.FILETYPE.SETTINGS, 0, 0, 0, 0, null)));
+//        return new DownloadRequestMessage(0, 0, DownloadRequestMessage.REQUEST_TYPE.NEW, 0, 0);
+//    }
+//
+public CreateFileMessage initiateUpload(byte[] fileAsByteArray, FileType.FILETYPE filetype) {
+    upload.setCurrentlyUploading(new FileFragment(new DirectoryEntry(0, filetype, 0, 0, 0, fileAsByteArray.length, null), fileAsByteArray));
+    return new CreateFileMessage(fileAsByteArray.length, filetype);
+}
+
+
+    public class Download {
+        private FileFragment currentlyDownloading;
+
+        public FileFragment getCurrentlyDownloading() {
+            return currentlyDownloading;
+        }
+
+        public void setCurrentlyDownloading(FileFragment currentlyDownloading) {
+            this.currentlyDownloading = currentlyDownloading;
+        }
+
+        private void processDownloadChunkedMessage(FileTransferDataMessage fileTransferDataMessage) {
+            if (!isDownloading())
+                throw new IllegalStateException("Received file transfer of unknown file");
+
+            currentlyDownloading.append(fileTransferDataMessage);
+            if (!currentlyDownloading.dataHolder.hasRemaining())
+                processCompleteDownload();
+        }
+
+        private void processCompleteDownload() {
+            currentlyDownloading.dataHolder.flip();
+
+            if (FileType.FILETYPE.DIRECTORY.equals(currentlyDownloading.directoryEntry.filetype)) { //is a directory
+                parseDirectoryEntries();
+            } else {
+                saveFileToExternalStorage();
+            }
+
+            currentlyDownloading = null;
+        }
+
+        public void processDownloadRequestStatusMessage(DownloadRequestStatusMessage downloadRequestStatusMessage) {
+            if (null == currentlyDownloading)
+                throw new IllegalStateException("Received file transfer of unknown file");
+            if (downloadRequestStatusMessage.canProceed())
+                currentlyDownloading.setSize(downloadRequestStatusMessage);
+            else
+                currentlyDownloading = null;
+        }
+
+        private void saveFileToExternalStorage() {
+            File dir;
+            File outputFile;
+            try {
+                dir = deviceSupport.getWritableExportDirectory();
+                outputFile = new File(dir, currentlyDownloading.getFileName());
+                FileUtils.copyStreamToFile(new ByteArrayInputStream(currentlyDownloading.dataHolder.array()), outputFile);
+                outputFile.setLastModified(currentlyDownloading.directoryEntry.fileDate.getTime());
+            } catch (final IOException e) {
+                LOG.error("Failed to save file", e);
+                return; // do not signal file as saved
+            }
+
+            FileDownloadedDeviceEvent fileDownloadedDeviceEvent = new FileDownloadedDeviceEvent();
+            fileDownloadedDeviceEvent.directoryEntry = currentlyDownloading.directoryEntry;
+            fileDownloadedDeviceEvent.localPath = outputFile.getAbsolutePath();
+            deviceSupport.evaluateGBDeviceEvent(fileDownloadedDeviceEvent);
+        }
+
+        private void parseDirectoryEntries() {
+            LOG.debug("Parsing directory entries for {}", currentlyDownloading.directoryEntry);
+            if ((currentlyDownloading.getDataSize() % 16) != 0)
+                throw new IllegalArgumentException("Invalid directory data length");
+            final GarminByteBufferReader reader = new GarminByteBufferReader(currentlyDownloading.dataHolder.array());
+            reader.setByteOrder(ByteOrder.LITTLE_ENDIAN);
+            final boolean fetchUnknownFiles = deviceSupport.getDevicePrefs().getFetchUnknownFiles();
+            while (reader.remaining() > 0) {
+                final int fileIndex = reader.readShort();//2
+                final int fileDataType = reader.readByte();//3
+                final int fileSubType = reader.readByte();//4
+                final FileType.FILETYPE filetype = FileType.FILETYPE.fromDataTypeSubType(fileDataType, fileSubType);
+                final int fileNumber = reader.readShort();//6
+                final int specificFlags = reader.readByte();//7
+                final int fileFlags = reader.readByte();//8
+                final int fileSize = reader.readInt();//12
+                final Date fileDate = new Date(GarminTimeUtils.garminTimestampToJavaMillis(reader.readInt()));//16
+                final DirectoryEntry directoryEntry = new DirectoryEntry(fileIndex, filetype, fileNumber, specificFlags, fileFlags, fileSize, fileDate);
+                if (directoryEntry.filetype == null) {
+                    // discard unsupported files
+                    LOG.warn("Unsupported directory entry of type {}/{}", fileDataType, fileSubType);
+                    continue;
+                }
+                if (!FILE_TYPES_TO_PROCESS.contains(directoryEntry.filetype) && !fetchUnknownFiles) {
+                    continue;
+                }
+                LOG.debug("Queueing {} for download", directoryEntry);
+                deviceSupport.addFileToDownloadList(directoryEntry);
+            }
+            currentlyDownloading = null;
+        }
+    }
+
+    public static class Upload {
+        private FileFragment currentlyUploading;
+
+        private UploadRequestMessage setCreateFileStatusMessage(CreateFileStatusMessage createFileStatusMessage) {
+            if (createFileStatusMessage.canProceed()) {
+                LOG.info("SENDING UPLOAD FILE");
+                return new UploadRequestMessage(createFileStatusMessage.getFileIndex(), currentlyUploading.getDataSize());
+            } else {
+                LOG.warn("Cannot proceed with upload");
+                this.currentlyUploading = null;
+            }
+            return null;
+        }
+
+        private FileTransferDataMessage setUploadRequestStatusMessage(UploadRequestStatusMessage uploadRequestStatusMessage) {
+            if (null == currentlyUploading)
+                throw new IllegalStateException("Received upload request status transfer of unknown file");
+            if (uploadRequestStatusMessage.canProceed()) {
+                if (uploadRequestStatusMessage.getDataOffset() != currentlyUploading.dataHolder.position())
+                    throw new IllegalStateException("Received upload request with unaligned offset");
+                return currentlyUploading.take();
+            } else {
+                LOG.warn("Cannot proceed with upload");
+                this.currentlyUploading = null;
+            }
+            return null;
+        }
+
+        private GFDIMessage processUploadProgress(FileTransferDataStatusMessage fileTransferDataStatusMessage) {
+            if (currentlyUploading.getDataSize() <= fileTransferDataStatusMessage.getDataOffset()) {
+                this.currentlyUploading = null;
+                LOG.info("SENDING SYNC COMPLETE!!!");
+
+                return new SystemEventMessage(SystemEventMessage.GarminSystemEventType.SYNC_COMPLETE, 0);
+            } else {
+                if (fileTransferDataStatusMessage.canProceed()) {
+                    LOG.info("SENDING NEXT CHUNK!!!");
+                    if (fileTransferDataStatusMessage.getDataOffset() != currentlyUploading.dataHolder.position())
+                        throw new IllegalStateException("Received file transfer status with unaligned offset");
+                    return currentlyUploading.take();
+                } else {
+                    LOG.warn("Cannot proceed with upload");
+                    this.currentlyUploading = null;
+                }
+
+            }
+            return null;
+        }
+
+        public FileFragment getCurrentlyUploading() {
+            return this.currentlyUploading;
+        }
+
+        public void setCurrentlyUploading(FileFragment currentlyUploading) {
+            this.currentlyUploading = currentlyUploading;
+        }
+
+    }
+
+    public static class FileFragment {
+        private final DirectoryEntry directoryEntry;
+        private final int maxBlockSize = 500; //TODO: why 500?
+        private int dataSize;
+        private ByteBuffer dataHolder;
+        private int runningCrc;
+
+        FileFragment(DirectoryEntry directoryEntry) {
+            this.directoryEntry = directoryEntry;
+            this.setRunningCrc(0);
+        }
+
+        FileFragment(DirectoryEntry directoryEntry, byte[] contents) {
+            this.directoryEntry = directoryEntry;
+            this.setDataSize(contents.length);
+            this.dataHolder = ByteBuffer.wrap(contents);
+            this.dataHolder.flip(); //we'll be only reading from here on
+            this.dataHolder.compact();
+            this.setRunningCrc(0);
+        }
+
+        private int getMaxBlockSize() {
+            return Math.min(maxBlockSize, GFDIMessage.getMaxPacketSize()); //TODO: can we use GFDIMessage.getMaxPacketSize() directly?
+        }
+
+        public String getFileName() {
+            return directoryEntry.getFileName();
+        }
+
+        private void setSize(DownloadRequestStatusMessage downloadRequestStatusMessage) {
+            if (0 != getDataSize())
+                throw new IllegalStateException("Data size already set");
+
+            this.setDataSize(downloadRequestStatusMessage.getMaxFileSize());
+            this.dataHolder = ByteBuffer.allocate(getDataSize());
+        }
+
+        private void append(FileTransferDataMessage fileTransferDataMessage) {
+            if (fileTransferDataMessage.getDataOffset() != dataHolder.position())
+                throw new IllegalStateException("Received message that was already received");
+
+            final int dataCrc = ChecksumCalculator.computeCrc(getRunningCrc(), fileTransferDataMessage.getMessage(), 0, fileTransferDataMessage.getMessage().length);
+            if (fileTransferDataMessage.getCrc() != dataCrc)
+                throw new IllegalStateException("Received message with invalid CRC");
+            setRunningCrc(dataCrc);
+
+            this.dataHolder.put(fileTransferDataMessage.getMessage());
+        }
+
+        private FileTransferDataMessage take() {
+            final int currentOffset = this.dataHolder.position();
+            final byte[] chunk = new byte[Math.min(this.dataHolder.remaining(), getMaxBlockSize() - 13)]; //actual payload in FileTransferDataMessage
+            this.dataHolder.get(chunk);
+            setRunningCrc(ChecksumCalculator.computeCrc(getRunningCrc(), chunk, 0, chunk.length));
+            return new FileTransferDataMessage(chunk, currentOffset, getRunningCrc());
+        }
+
+        private int getDataSize() {
+            return dataSize;
+        }
+
+        private void setDataSize(int dataSize) {
+            this.dataSize = dataSize;
+        }
+
+        private int getRunningCrc() {
+            return runningCrc;
+        }
+
+        private void setRunningCrc(int runningCrc) {
+            this.runningCrc = runningCrc;
+        }
+    }
+
+    public static class DirectoryEntry {
+        private static final SimpleDateFormat SDF = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss", Locale.ROOT);
+
+        private final int fileIndex;
+        private final FileType.FILETYPE filetype;
+        private final int fileNumber;
+        private final int specificFlags;
+        private final int fileFlags;
+        private final int fileSize;
+        private final Date fileDate;
+
+        public DirectoryEntry(int fileIndex, FileType.FILETYPE filetype, int fileNumber, int specificFlags, int fileFlags, int fileSize, Date fileDate) {
+            this.fileIndex = fileIndex;
+            this.filetype = filetype;
+            this.fileNumber = fileNumber;
+            this.specificFlags = specificFlags;
+            this.fileFlags = fileFlags;
+            this.fileSize = fileSize;
+            this.fileDate = fileDate;
+        }
+
+        public int getFileIndex() {
+            return fileIndex;
+        }
+
+        public FileType.FILETYPE getFiletype() {
+            return filetype;
+        }
+
+        public String getFileName() {
+            final StringBuilder sb = new StringBuilder(getFiletype().name());
+            if (fileDate.getTime() != GarminTimeUtils.GARMIN_TIME_EPOCH * 1000L) {
+                sb.append("_").append(SDF.format(fileDate));
+            }
+            sb.append("_").append(getFileIndex()).append(getFiletype().isFitFile() ? ".fit" : ".bin");
+            return sb.toString();
+        }
+
+        public String getLegacyFileName() {
+            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss");
+            String dateString = dateFormat.format(fileDate);
+            return getFiletype().name() + "_" + getFileIndex() + "_" + dateString + (getFiletype().isFitFile() ? ".fit" : ".bin");
+        }
+
+        @NonNull
+        @Override
+        public String toString() {
+            return "DirectoryEntry{" +
+                    "fileIndex=" + fileIndex +
+                    ", fileType=" + filetype +
+                    ", fileNumber=" + fileNumber +
+                    ", specificFlags=" + specificFlags +
+                    ", fileFlags=" + fileFlags +
+                    ", fileSize=" + fileSize +
+                    ", fileDate=" + fileDate +
+                    '}';
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/FileType.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/FileType.java
new file mode 100644
index 0000000..670cc78
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/FileType.java
@@ -0,0 +1,118 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin;
+
+import androidx.annotation.Nullable;
+
+public class FileType {
+    //common
+    //128/4: FIT_TYPE_4, -> garmin/activity
+    //128/32: FIT_TYPE_32,  -> garmin/monitor
+    //128/44: FIT_TYPE_44, ->garmin/metrics
+    //128/41: FIT_TYPE_41, ->garmin/chnglog
+    //128/49: FIT_TYPE_49, -> garmin/sleep
+    //255/245: ErrorShutdownReports,
+
+    //Specific Instinct 2S:
+    //128/38: FIT_TYPE_38, -> garmin/SCORCRDS
+    //255/248: KPI,
+    //128/58: FIT_TYPE_58, -> outputFromUnit garmin/device????
+    //255/247: ULFLogs,
+    //128/68: FIT_TYPE_68, -> garmin/HRVSTATUS
+    //128/70: FIT_TYPE_70, -> garmin/HSA
+    //128/72: FIT_TYPE_72, -> garmin/FBTBACKUP
+    //128/74: FIT_TYPE_74
+
+
+    private final FILETYPE fileType;
+    private final String garminDeviceFileType;
+
+    public FileType(int fileDataType, int fileSubType, String garminDeviceFileType) {
+        this.fileType = FILETYPE.fromDataTypeSubType(fileDataType, fileSubType);
+        this.garminDeviceFileType = garminDeviceFileType;
+    }
+
+    public FILETYPE getFileType() {
+        return fileType;
+    }
+
+    public enum FILETYPE { //TODO: add specialized method to parse each file type to the enum?
+        // virtual/undocumented
+        DIRECTORY(0, 0),
+        UNKNOWN_1_0(1, 0), // venu 3, fileIndex=4096
+
+        // fit files
+        DEVICE_1(128, 1), // just "-"
+        SETTINGS(128, 2),
+        SPORTS(128, 3),
+        ACTIVITY(128, 4),
+        WORKOUTS(128, 5),
+        COURSES(128, 6),
+        SCHEDULES(128, 7),
+        LOCATION(128, 8),
+        TOTALS(128, 10),
+        GOALS(128, 11),
+        SUMMARY(128, 20),
+        RECORDS(128, 29),
+        MONITOR(128, 32),
+        MLT_SPORT(128, 33),
+        SEGMENTS(128, 34),
+        SEGMENT_LIST(128, 35),
+        CLUBS(128, 37),
+        SCORE(128, 38),
+        ADJUSTMENTS(128, 39),
+        HMD(128, 40),
+        CHANGELOG(128, 41),
+        METRICS(128, 44),
+        SLEEP(128, 49),
+        PACE_BANDS(128, 56),
+        DEVICE_58(128, 58), // just "Device" in Fenix 7s
+        MUSCLE_MAP(128, 59),
+        RUNNING_TRACK(128, 60),
+        ECG(128, 61),
+        BENCHMARK(128, 62),
+        POWER_GUIDANCE(128, 63),
+        CALENDAR(128, 65),
+        HRV_STATUS(128, 68),
+        HSA(128, 70),
+        COM_ACT(128, 71),
+        FBT_BACKUP(128, 72),
+        SKIN_TEMP(128, 73),
+        FBT_PTD_BACKUP(128, 74),
+
+        // Other files
+        DOWNLOAD_COURSE(255, 4),
+        ERROR_SHUTDOWN_REPORTS(255, 245),
+        IQ_ERROR_REPORTS(255, 244),
+        ULF_LOGS(255, 247),
+        ;
+
+        private final int type;
+        private final int subtype;
+
+        FILETYPE(final int type, final int subtype) {
+            this.type = type;
+            this.subtype = subtype;
+        }
+
+        @Nullable
+        public static FILETYPE fromDataTypeSubType(int dataType, int subType) {
+            for (FILETYPE ft :
+                    FILETYPE.values()) {
+                if (ft.type == dataType && ft.subtype == subType)
+                    return ft;
+            }
+            return null;
+        }
+
+        public int getType() {
+            return type;
+        }
+
+        public int getSubType() {
+            return subtype;
+        }
+
+        public boolean isFitFile() {
+            return type == 128;
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/GarminByteBufferReader.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/GarminByteBufferReader.java
new file mode 100644
index 0000000..2623313
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/GarminByteBufferReader.java
@@ -0,0 +1,82 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.StandardCharsets;
+
+public class GarminByteBufferReader {
+    protected final ByteBuffer byteBuffer;
+
+    public GarminByteBufferReader(byte[] data) {
+        this.byteBuffer = ByteBuffer.wrap(data);
+    }
+
+    public int remaining() {
+        return byteBuffer.remaining();
+    }
+
+    public ByteBuffer asReadOnlyBuffer() {
+        return byteBuffer.asReadOnlyBuffer();
+    }
+
+    public void setByteOrder(ByteOrder byteOrder) {
+        this.byteBuffer.order(byteOrder);
+    }
+
+    public int readByte() {
+        return Byte.toUnsignedInt(byteBuffer.get());
+    }
+
+    public int getPosition() {
+        return byteBuffer.position();
+    }
+
+    public int readShort() {
+        return Short.toUnsignedInt(byteBuffer.getShort());
+    }
+
+    public int readInt() {
+        return byteBuffer.getInt();
+    }
+
+    public long readLong() {
+        return byteBuffer.getLong();
+    }
+
+    public float readFloat32() {
+        return byteBuffer.getFloat();
+    }
+
+    public double readFloat64() {
+        return byteBuffer.getDouble();
+    }
+
+    public String readString() {
+        final int size = readByte();
+        byte[] bytes = new byte[size];
+        byteBuffer.get(bytes);
+        return new String(bytes, StandardCharsets.UTF_8);
+    }
+
+    public String readNullTerminatedString() {
+        int position = byteBuffer.position();
+        int size = 0;
+        while (byteBuffer.hasRemaining()) {
+            if (byteBuffer.get() == 0)
+                break;
+            size++;
+        }
+        byteBuffer.position(position);
+        byte[] bytes = new byte[size];
+        byteBuffer.get(bytes);
+        return new String(bytes, StandardCharsets.UTF_8);
+    }
+
+    public byte[] readBytes(int size) {
+        byte[] bytes = new byte[size];
+
+        byteBuffer.get(bytes);
+
+        return bytes;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/GarminSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/GarminSupport.java
new file mode 100644
index 0000000..3faa376
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/GarminSupport.java
@@ -0,0 +1,959 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.location.Location;
+import android.net.Uri;
+import android.widget.Toast;
+
+import androidx.annotation.Nullable;
+import androidx.documentfile.provider.DocumentFile;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Queue;
+import java.util.Set;
+import java.util.UUID;
+import java.util.stream.Collectors;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
+import nodomain.freeyourgadget.gadgetbridge.devices.PendingFileProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminFitFileInstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminGpxRouteInstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminPreferences;
+import nodomain.freeyourgadget.gadgetbridge.devices.vivomovehr.GarminCapability;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.externalevents.gps.GBLocationService;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.Weather;
+import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
+import nodomain.freeyourgadget.gadgetbridge.proto.garmin.GdiCore;
+import nodomain.freeyourgadget.gadgetbridge.proto.garmin.GdiDeviceStatus;
+import nodomain.freeyourgadget.gadgetbridge.proto.garmin.GdiFindMyWatch;
+import nodomain.freeyourgadget.gadgetbridge.proto.garmin.GdiSettingsService;
+import nodomain.freeyourgadget.gadgetbridge.proto.garmin.GdiSmartProto;
+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.garmin.communicator.ICommunicator;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.communicator.v1.CommunicatorV1;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.communicator.v2.CommunicatorV2;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.deviceevents.CapabilitiesDeviceEvent;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.deviceevents.FileDownloadedDeviceEvent;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.deviceevents.NotificationSubscriptionDeviceEvent;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.deviceevents.SupportedFileTypesDeviceEvent;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.deviceevents.WeatherRequestDeviceEvent;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.FitAsyncProcessor;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.PredefinedLocalMessage;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordData;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordDefinition;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.ConfigurationMessage;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.DownloadRequestMessage;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.GFDIMessage;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.MusicControlEntityUpdateMessage;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.ProtobufMessage;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.SetDeviceSettingsMessage;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.SetFileFlagsMessage;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.SupportedFileTypesMessage;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.SystemEventMessage;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.status.NotificationSubscriptionStatusMessage;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+import nodomain.freeyourgadget.gadgetbridge.util.MediaManager;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_ALLOW_HIGH_MTU;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SEND_APP_NOTIFICATIONS;
+
+
+public class GarminSupport extends AbstractBTLEDeviceSupport implements ICommunicator.Callback {
+    private static final Logger LOG = LoggerFactory.getLogger(GarminSupport.class);
+    private final ProtocolBufferHandler protocolBufferHandler;
+    private final NotificationsHandler notificationsHandler;
+    private final FileTransferHandler fileTransferHandler;
+    private final Queue<FileTransferHandler.DirectoryEntry> filesToDownload;
+    private final List<MessageHandler> messageHandlers;
+    private final List<FileType> supportedFileTypeList = new ArrayList<>();
+    private ICommunicator communicator;
+    private MediaManager mediaManager;
+    private boolean mFirstConnect = false;
+    private boolean isBusyFetching;
+
+    public GarminSupport() {
+        super(LOG);
+        addSupportedService(CommunicatorV1.UUID_SERVICE_GARMIN_GFDI_V0);
+        addSupportedService(CommunicatorV1.UUID_SERVICE_GARMIN_GFDI_V1);
+        addSupportedService(CommunicatorV2.UUID_SERVICE_GARMIN_ML_GFDI);
+        protocolBufferHandler = new ProtocolBufferHandler(this);
+        fileTransferHandler = new FileTransferHandler(this);
+        filesToDownload = new LinkedList<>();
+        messageHandlers = new ArrayList<>();
+        notificationsHandler = new NotificationsHandler();
+        messageHandlers.add(fileTransferHandler);
+        messageHandlers.add(protocolBufferHandler);
+        messageHandlers.add(notificationsHandler);
+    }
+
+    @Override
+    public void setContext(final GBDevice gbDevice, final BluetoothAdapter btAdapter, final Context context) {
+        super.setContext(gbDevice, btAdapter, context);
+        this.mediaManager = new MediaManager(context);
+    }
+
+    @Override
+    public void dispose() {
+        LOG.info("Garmin dispose()");
+        GBLocationService.stop(getContext(), getDevice());
+        super.dispose();
+    }
+
+    public void addFileToDownloadList(FileTransferHandler.DirectoryEntry directoryEntry) {
+        filesToDownload.add(directoryEntry);
+    }
+
+    @Override
+    public boolean useAutoConnect() {
+        return false;
+    }
+
+    @Override
+    protected TransactionBuilder initializeDevice(final TransactionBuilder builder) {
+        builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext()));
+
+        if (getDevicePrefs().getBoolean(PREF_ALLOW_HIGH_MTU, true)) {
+            builder.requestMtu(515);
+        }
+
+        final CommunicatorV2 communicatorV2 = new CommunicatorV2(this);
+        if (communicatorV2.initializeDevice(builder)) {
+            communicator = communicatorV2;
+        } else {
+            // V2 did not manage to initialize, attempt V1
+            final CommunicatorV1 communicatorV1 = new CommunicatorV1(this);
+            if (!communicatorV1.initializeDevice(builder)) {
+                // Neither V1 nor V2 worked, not a Garmin device?
+                LOG.warn("Failed to find a known Garmin service");
+                builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.NOT_CONNECTED, getContext()));
+                return builder;
+            }
+
+            communicator = communicatorV1;
+        }
+
+        communicator.initializeDevice(builder);
+
+        return builder;
+    }
+
+    @Override
+    public void onMtuChanged(final BluetoothGatt gatt, final int mtu, final int status) {
+        if (mtu < 23) {
+            LOG.warn("Ignoring mtu of {}, too low", mtu);
+            return;
+        }
+        if (!getDevicePrefs().getBoolean(PREF_ALLOW_HIGH_MTU, true)) {
+            LOG.warn("Ignoring mtu change to {} - high mtu is disabled", mtu);
+            return;
+        }
+
+        communicator.onMtuChanged(mtu);
+    }
+
+    @Override
+    public boolean onCharacteristicChanged(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic) {
+        final UUID characteristicUUID = characteristic.getUuid();
+        if (super.onCharacteristicChanged(gatt, characteristic)) {
+            LOG.debug("Change of characteristic {} handled by parent", characteristicUUID);
+            return true;
+        }
+
+        return communicator.onCharacteristicChanged(gatt, characteristic);
+    }
+
+    @Override
+    public void onMessage(final byte[] message) {
+        if (null == message) {
+            return; //message is not complete yet TODO check before calling
+        }
+//        LOG.debug("COBS decoded MESSAGE: {}", GB.hexdump(message));
+
+        GFDIMessage parsedMessage = GFDIMessage.parseIncoming(message);
+
+        if (null == parsedMessage) {
+            return; //message cannot be handled
+        }
+
+
+        /*
+        the handler elaborates the followup message but might change the status message since it does
+        check the integrity of the incoming message payload. Hence we let the handlers elaborate the
+        incoming message, then we send the status message of the incoming message, then the response
+        and finally we send the followup.
+         */
+
+        GFDIMessage followup = null;
+        for (MessageHandler han : messageHandlers) {
+            followup = han.handle(parsedMessage);
+            if (followup != null) {
+                break;
+            }
+        }
+
+        final List<GBDeviceEvent> events = parsedMessage.getGBDeviceEvent();
+        for (final GBDeviceEvent event : events) {
+            evaluateGBDeviceEvent(event);
+        }
+
+        communicator.sendMessage("send status", parsedMessage.getAckBytestream()); //send status message
+
+        sendOutgoingMessage("send reply", parsedMessage); //send reply if any
+
+        sendOutgoingMessage("send followup", followup); //send followup message if any
+
+        if (parsedMessage instanceof ConfigurationMessage) { //the last forced message exchange
+            completeInitialization();
+        }
+
+        processDownloadQueue();
+
+    }
+
+    protected String getNotificationAttachmentPath(int notificationId) {
+        return notificationsHandler.getNotificationAttachmentPath(notificationId);
+    }
+
+    protected Bitmap getNotificationAttachmentBitmap(int notificationId) {
+        final String picturePath = getNotificationAttachmentPath(notificationId);
+        final Bitmap bitmap = BitmapFactory.decodeFile(picturePath);
+        if (bitmap == null) {
+            LOG.warn("Failed to load bitmap for {} from {}", notificationId, picturePath);
+        }
+        return bitmap;
+    }
+
+    @Override
+    public void onSetCallState(CallSpec callSpec) {
+        LOG.info("INCOMING CALLSPEC: {}", callSpec.command);
+        sendOutgoingMessage("send call", notificationsHandler.onSetCallState(callSpec));
+    }
+
+    @Override
+    public void evaluateGBDeviceEvent(GBDeviceEvent deviceEvent) {
+        if (deviceEvent instanceof WeatherRequestDeviceEvent) {
+            WeatherSpec weather = Weather.getInstance().getWeatherSpec();
+            if (weather != null) {
+                sendWeatherConditions(weather);
+            }
+        } else if (deviceEvent instanceof CapabilitiesDeviceEvent) {
+            final Set<GarminCapability> capabilities = ((CapabilitiesDeviceEvent) deviceEvent).capabilities;
+            if (capabilities.contains(GarminCapability.REALTIME_SETTINGS)) {
+                final String language = Locale.getDefault().getLanguage();
+                final String country = Locale.getDefault().getCountry();
+                final String localeString = language + "_" + country.toUpperCase();
+                final ProtobufMessage realtimeSettingsInit = protocolBufferHandler.prepareProtobufRequest(GdiSmartProto.Smart.newBuilder()
+                        .setSettingsService(
+                                GdiSettingsService.SettingsService.newBuilder()
+                                        .setInitRequest(
+                                                GdiSettingsService.InitRequest.newBuilder()
+                                                        .setLanguage(localeString.length() == 5 ? localeString : "en_US")
+                                                        .setRegion("us") // FIXME choose region
+                                        )
+                        )
+                        .build());
+                sendOutgoingMessage("init realtime settings", realtimeSettingsInit);
+            }
+        } else if (deviceEvent instanceof NotificationSubscriptionDeviceEvent) {
+            final boolean enable = ((NotificationSubscriptionDeviceEvent) deviceEvent).enable;
+            notificationsHandler.setEnabled(enable);
+
+            final NotificationSubscriptionStatusMessage.NotificationStatus finalStatus;
+            if (getDevicePrefs().getBoolean(PREF_SEND_APP_NOTIFICATIONS, true)) {
+                finalStatus = NotificationSubscriptionStatusMessage.NotificationStatus.ENABLED;
+            } else {
+                finalStatus = NotificationSubscriptionStatusMessage.NotificationStatus.DISABLED;
+            }
+
+            LOG.info("NOTIFICATIONS ARE NOW enabled={}, status={}", enable, finalStatus);
+
+            sendOutgoingMessage("toggle notification subscription", new NotificationSubscriptionStatusMessage(
+                    GFDIMessage.Status.ACK,
+                    finalStatus,
+                    enable,
+                    0
+            ));
+        } else if (deviceEvent instanceof SupportedFileTypesDeviceEvent) {
+            this.supportedFileTypeList.clear();
+            this.supportedFileTypeList.addAll(((SupportedFileTypesDeviceEvent) deviceEvent).getSupportedFileTypes());
+        } else if (deviceEvent instanceof FileDownloadedDeviceEvent) {
+            final FileTransferHandler.DirectoryEntry entry = ((FileDownloadedDeviceEvent) deviceEvent).directoryEntry;
+            final String filename = entry.getFileName();
+            LOG.debug("FILE DOWNLOAD COMPLETE {}", filename);
+
+            if (entry.getFiletype().isFitFile()) {
+                try (DBHandler handler = GBApplication.acquireDB()) {
+                    final DaoSession session = handler.getDaoSession();
+
+                    final PendingFileProvider pendingFileProvider = new PendingFileProvider(gbDevice, session);
+
+                    pendingFileProvider.addPendingFile(((FileDownloadedDeviceEvent) deviceEvent).localPath);
+                } catch (final Exception e) {
+                    GB.toast(getContext(), "Error saving pending file", Toast.LENGTH_LONG, GB.ERROR, e);
+                }
+            }
+
+            if (!getKeepActivityDataOnDevice()) { // delete file from watch upon successful download
+                sendOutgoingMessage("archive file " + entry.getFileIndex(), new SetFileFlagsMessage(entry.getFileIndex(), SetFileFlagsMessage.FileFlags.ARCHIVE));
+            }
+        }
+
+        super.evaluateGBDeviceEvent(deviceEvent);
+    }
+
+    /** @noinspection BooleanMethodIsAlwaysInverted*/
+    private boolean getKeepActivityDataOnDevice() {
+        return getDevicePrefs().getBoolean("keep_activity_data_on_device", false);
+    }
+
+    @Override
+    public void onFetchRecordedData(final int dataTypes) {
+        if (this.supportedFileTypeList.isEmpty()) {
+            LOG.warn("No known supported file types");
+            return;
+        }
+
+        // FIXME respect dataTypes?
+
+        sendOutgoingMessage("fetch recorded data", fileTransferHandler.initiateDownload());
+    }
+
+    @Override
+    public void onNotification(final NotificationSpec notificationSpec) {
+        sendOutgoingMessage("send notification " + notificationSpec.getId(), notificationsHandler.onNotification(notificationSpec));
+    }
+
+    @Override
+    public void onDeleteNotification(int id) {
+        sendOutgoingMessage("delete notification " + id, notificationsHandler.onDeleteNotification(id));
+    }
+
+    @Override
+    public void onSendWeather(final ArrayList<WeatherSpec> weatherSpecs) { //todo: find the closest one relative to the requested lat/long
+        sendWeatherConditions(weatherSpecs.get(0));
+    }
+
+    private void sendOutgoingMessage(final String taskName, final GFDIMessage message) {
+        if (message == null)
+            return;
+        communicator.sendMessage(taskName, message.getOutgoingMessage());
+    }
+
+    private void sendWeatherConditions(WeatherSpec weather) {
+        if (!getCoordinator().supports(getDevice(), GarminCapability.WEATHER_CONDITIONS)) {
+            // Device does not support sending weather as fit
+            return;
+        }
+
+        List<RecordData> weatherData = new ArrayList<>();
+
+        final RecordDefinition recordDefinitionToday = PredefinedLocalMessage.TODAY_WEATHER_CONDITIONS.getRecordDefinition();
+        final RecordDefinition recordDefinitionHourly = PredefinedLocalMessage.HOURLY_WEATHER_FORECAST.getRecordDefinition();
+        final RecordDefinition recordDefinitionDaily = PredefinedLocalMessage.DAILY_WEATHER_FORECAST.getRecordDefinition();
+
+        List<RecordDefinition> weatherDefinitions = new ArrayList<>(3);
+        weatherDefinitions.add(recordDefinitionToday);
+        weatherDefinitions.add(recordDefinitionHourly);
+        weatherDefinitions.add(recordDefinitionDaily);
+
+        sendOutgoingMessage("send weather definitions", new nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.FitDefinitionMessage(weatherDefinitions));
+
+        RecordData today = new RecordData(recordDefinitionToday, recordDefinitionToday.getRecordHeader());
+        today.setFieldByName("weather_report", 0); // 0 = current, 1 = hourly_forecast, 2 = daily_forecast
+        today.setFieldByName("timestamp", weather.timestamp);
+        today.setFieldByName("observed_at_time", weather.timestamp);
+        today.setFieldByName("temperature", weather.currentTemp);
+        today.setFieldByName("low_temperature", weather.todayMinTemp);
+        today.setFieldByName("high_temperature", weather.todayMaxTemp);
+        today.setFieldByName("condition", weather.currentConditionCode);
+        today.setFieldByName("wind_direction", weather.windDirection);
+        today.setFieldByName("precipitation_probability", weather.precipProbability);
+        today.setFieldByName("wind_speed", Math.round(weather.windSpeed));
+        today.setFieldByName("temperature_feels_like", weather.feelsLikeTemp);
+        today.setFieldByName("relative_humidity", weather.currentHumidity);
+        today.setFieldByName("observed_location_lat", weather.latitude);
+        today.setFieldByName("observed_location_long", weather.longitude);
+        today.setFieldByName("dew_point", weather.dewPoint);
+        if (null != weather.airQuality) {
+            today.setFieldByName("air_quality", weather.airQuality.aqi);
+        }
+        today.setFieldByName("location", weather.location);
+        weatherData.add(today);
+
+        for (int hour = 0; hour <= 11; hour++) {
+            if (hour < weather.hourly.size()) {
+                WeatherSpec.Hourly hourly = weather.hourly.get(hour);
+                RecordData weatherHourlyForecast = new RecordData(recordDefinitionHourly, recordDefinitionHourly.getRecordHeader());
+                weatherHourlyForecast.setFieldByName("weather_report", 1); // 0 = current, 1 = hourly_forecast, 2 = daily_forecast
+                weatherHourlyForecast.setFieldByName("timestamp", hourly.timestamp);
+                weatherHourlyForecast.setFieldByName("temperature", hourly.temp);
+                weatherHourlyForecast.setFieldByName("condition", hourly.conditionCode);
+                weatherHourlyForecast.setFieldByName("temperature_feels_like", hourly.temp); //TODO: switch to actual feels like field once Hourly contains this information
+                weatherHourlyForecast.setFieldByName("wind_direction", hourly.windDirection);
+                weatherHourlyForecast.setFieldByName("wind_speed", Math.round(hourly.windSpeed));
+                weatherHourlyForecast.setFieldByName("precipitation_probability", hourly.precipProbability);
+                weatherHourlyForecast.setFieldByName("relative_humidity", hourly.humidity);
+//                    weatherHourlyForecast.setFieldByName("dew_point", 0); // TODO: add once Hourly contains this information
+                weatherHourlyForecast.setFieldByName("uv_index", hourly.uvIndex);
+//                    weatherHourlyForecast.setFieldByName("air_quality", 0); // TODO: add once Hourly contains this information
+                weatherData.add(weatherHourlyForecast);
+            }
+        }
+//
+        RecordData todayDailyForecast = new RecordData(recordDefinitionDaily, recordDefinitionDaily.getRecordHeader());
+        todayDailyForecast.setFieldByName("weather_report", 2); // 0 = current, 1 = hourly_forecast, 2 = daily_forecast
+        todayDailyForecast.setFieldByName("timestamp", weather.timestamp);
+        todayDailyForecast.setFieldByName("low_temperature", weather.todayMinTemp);
+        todayDailyForecast.setFieldByName("high_temperature", weather.todayMaxTemp);
+        todayDailyForecast.setFieldByName("condition", weather.currentConditionCode);
+        todayDailyForecast.setFieldByName("precipitation_probability", weather.precipProbability);
+        todayDailyForecast.setFieldByName("day_of_week", weather.timestamp);
+        if (null != weather.airQuality) {
+            todayDailyForecast.setFieldByName("air_quality", weather.airQuality.aqi);
+        }
+        weatherData.add(todayDailyForecast);
+
+
+        for (int day = 0; day < 4; day++) {
+            if (day < weather.forecasts.size()) {
+                //noinspection ExtractMethodRecommender
+                WeatherSpec.Daily daily = weather.forecasts.get(day);
+                int ts = weather.timestamp + (day + 1) * 24 * 60 * 60;
+                RecordData weatherDailyForecast = new RecordData(recordDefinitionDaily, recordDefinitionDaily.getRecordHeader());
+                weatherDailyForecast.setFieldByName("weather_report", 2); // 0 = current, 1 = hourly_forecast, 2 = daily_forecast
+                weatherDailyForecast.setFieldByName("timestamp", weather.timestamp);
+                weatherDailyForecast.setFieldByName("low_temperature", daily.minTemp);
+                weatherDailyForecast.setFieldByName("high_temperature", daily.maxTemp);
+                weatherDailyForecast.setFieldByName("condition", daily.conditionCode);
+                weatherDailyForecast.setFieldByName("precipitation_probability", daily.precipProbability);
+                if (null != daily.airQuality) {
+                    weatherDailyForecast.setFieldByName("air_quality", daily.airQuality.aqi);
+                }
+                weatherDailyForecast.setFieldByName("day_of_week", ts);
+                weatherData.add(weatherDailyForecast);
+            }
+        }
+
+        sendOutgoingMessage("send weather data", new nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.FitDataMessage(weatherData));
+
+    }
+
+    private void completeInitialization() {
+        onSetTime();
+        enableWeather();
+
+        //following is needed for vivomove style
+        sendOutgoingMessage("set sync ready", new SystemEventMessage(SystemEventMessage.GarminSystemEventType.SYNC_READY, 0));
+
+        enableBatteryLevelUpdate();
+
+        gbDevice.setState(GBDevice.State.INITIALIZED);
+        gbDevice.sendDeviceUpdateIntent(getContext());
+
+        sendOutgoingMessage("request supported file types", new SupportedFileTypesMessage());
+
+        if (mFirstConnect) {
+            sendOutgoingMessage("set sync complete", new SystemEventMessage(SystemEventMessage.GarminSystemEventType.SYNC_COMPLETE, 0));
+            this.mFirstConnect = false;
+        }
+    }
+
+    @Override
+    public void onSendConfiguration(final String config) {
+        if (config.startsWith("protobuf:")) {
+            try {
+                final GdiSmartProto.Smart smart = GdiSmartProto.Smart.parseFrom(GB.hexStringToByteArray(config.replaceFirst("protobuf:", "")));
+                sendOutgoingMessage("send config", protocolBufferHandler.prepareProtobufRequest(smart));
+            } catch (final Exception e) {
+                LOG.error("Failed to send {} as protobuf", config, e);
+            }
+
+            return;
+        }
+
+        //noinspection SwitchStatementWithTooFewBranches
+        switch (config) {
+            case PREF_SEND_APP_NOTIFICATIONS:
+                NotificationSubscriptionDeviceEvent notificationSubscriptionDeviceEvent = new NotificationSubscriptionDeviceEvent();
+                notificationSubscriptionDeviceEvent.enable = true; // actual status is fetched from preferences
+                evaluateGBDeviceEvent(notificationSubscriptionDeviceEvent);
+                return;
+        }
+    }
+
+    private void processDownloadQueue() {
+        if (!filesToDownload.isEmpty() && !fileTransferHandler.isDownloading()) {
+            if (!gbDevice.isBusy()) {
+                isBusyFetching = true;
+                GB.updateTransferNotification(getContext().getString(R.string.busy_task_fetch_activity_data), "", true, 0, getContext());
+                getDevice().setBusyTask(getContext().getString(R.string.busy_task_fetch_activity_data));
+                getDevice().sendDeviceUpdateIntent(getContext());
+            }
+
+            while (!filesToDownload.isEmpty()) {
+                final FileTransferHandler.DirectoryEntry directoryEntry = filesToDownload.remove();
+                if (alreadyDownloaded(directoryEntry)) {
+                    LOG.debug("File: {} already downloaded, not downloading again.", directoryEntry.getFileName());
+                    if (!getKeepActivityDataOnDevice()) { // delete file from watch if already downloaded
+                        sendOutgoingMessage("archive file " + directoryEntry.getFileIndex(), new SetFileFlagsMessage(directoryEntry.getFileIndex(), SetFileFlagsMessage.FileFlags.ARCHIVE));
+                    }
+                    continue;
+                }
+
+                final DownloadRequestMessage downloadRequestMessage = fileTransferHandler.downloadDirectoryEntry(directoryEntry);
+                LOG.debug("Will download file: {}", directoryEntry.getFileName());
+                sendOutgoingMessage("download file " + directoryEntry.getFileIndex(), downloadRequestMessage);
+                return;
+            }
+        }
+
+        if (filesToDownload.isEmpty() && !fileTransferHandler.isDownloading() && isBusyFetching) {
+            final List<File> filesToProcess;
+            try (DBHandler handler = GBApplication.acquireDB()) {
+                final DaoSession session = handler.getDaoSession();
+
+                final PendingFileProvider pendingFileProvider = new PendingFileProvider(gbDevice, session);
+
+                filesToProcess = pendingFileProvider.getAllPendingFiles()
+                        .stream()
+                        .map(pf -> new File(pf.getPath()))
+                        .collect(Collectors.toList());
+            } catch (final Exception e) {
+                LOG.error("Failed to get pending files", e);
+                return;
+            }
+
+            if (filesToProcess.isEmpty()) {
+                LOG.debug("No pending files to process");
+                // No downloaded fit files to process
+                if (gbDevice.isBusy() && isBusyFetching) {
+                    getDevice().unsetBusyTask();
+                    GB.signalActivityDataFinish(getDevice());
+                    GB.updateTransferNotification(null, "", false, 100, getContext());
+                    getDevice().sendDeviceUpdateIntent(getContext());
+                }
+                isBusyFetching = false;
+                return;
+            }
+
+            // Keep the device marked as busy while we process the files asynchronously, but unset
+            // isBusyFetching so we do not start multiple processors
+            isBusyFetching = false;
+
+            final FitAsyncProcessor fitAsyncProcessor = new FitAsyncProcessor(getContext(), getDevice());
+            final long[] lastNotificationUpdateTs = new long[]{System.currentTimeMillis()};
+            fitAsyncProcessor.process(filesToProcess, new FitAsyncProcessor.Callback() {
+                @Override
+                public void onProgress(final int i) {
+                    final long now = System.currentTimeMillis();
+                    if (now - lastNotificationUpdateTs[0] > 1500L) {
+                        lastNotificationUpdateTs[0] = now;
+                        GB.updateTransferNotification(
+                                "Parsing fit files", "File " + i + " of " + filesToProcess.size(),
+                                true,
+                                (i * 100) / filesToProcess.size(), getContext()
+                        );
+                    }
+                }
+
+                @Override
+                public void onFinish() {
+                    getDevice().unsetBusyTask();
+                    GB.signalActivityDataFinish(getDevice());
+                    GB.updateTransferNotification(null, "", false, 100, getContext());
+                    getDevice().sendDeviceUpdateIntent(getContext());
+                }
+            });
+        }
+    }
+
+    private void enableBatteryLevelUpdate() {
+        final ProtobufMessage batteryLevelProtobufRequest = protocolBufferHandler.prepareProtobufRequest(GdiSmartProto.Smart.newBuilder()
+                .setDeviceStatusService(
+                        GdiDeviceStatus.DeviceStatusService.newBuilder()
+                                .setRemoteDeviceBatteryStatusRequest(
+                                        GdiDeviceStatus.DeviceStatusService.RemoteDeviceBatteryStatusRequest.newBuilder()
+                                )
+                )
+                .build());
+        sendOutgoingMessage("enable battery updates", batteryLevelProtobufRequest);
+    }
+
+    private void enableWeather() {
+        final Map<SetDeviceSettingsMessage.GarminDeviceSetting, Object> settings = new LinkedHashMap<>(3);
+        settings.put(SetDeviceSettingsMessage.GarminDeviceSetting.AUTO_UPLOAD_ENABLED, false);
+        settings.put(SetDeviceSettingsMessage.GarminDeviceSetting.WEATHER_CONDITIONS_ENABLED, true);
+        settings.put(SetDeviceSettingsMessage.GarminDeviceSetting.WEATHER_ALERTS_ENABLED, false);
+        sendOutgoingMessage("enable weather", new SetDeviceSettingsMessage(settings));
+    }
+
+    @Override
+    public void onSetTime() {
+        sendOutgoingMessage("set time", new SystemEventMessage(SystemEventMessage.GarminSystemEventType.TIME_UPDATED, 0));
+    }
+
+    @Override
+    public void onFindDevice(boolean start) {
+        final GdiFindMyWatch.FindMyWatchService.Builder a = GdiFindMyWatch.FindMyWatchService.newBuilder();
+        if (start) {
+            a.setFindRequest(
+                    GdiFindMyWatch.FindMyWatchService.FindMyWatchRequest.newBuilder()
+                            .setTimeout(60)
+            );
+        } else {
+            a.setCancelRequest(
+                    GdiFindMyWatch.FindMyWatchService.FindMyWatchCancelRequest.newBuilder()
+            );
+        }
+        final ProtobufMessage findMyWatch = protocolBufferHandler.prepareProtobufRequest(
+                GdiSmartProto.Smart.newBuilder()
+                        .setFindMyWatchService(a).build());
+
+        sendOutgoingMessage("find device", findMyWatch);
+    }
+
+    @Override
+    public void onSetCannedMessages(CannedMessagesSpec cannedMessagesSpec) {
+        sendOutgoingMessage("set canned messages", protocolBufferHandler.setCannedMessages(cannedMessagesSpec));
+    }
+
+    @Override
+    public void onSetMusicInfo(MusicSpec musicSpec) {
+        if (!mediaManager.onSetMusicInfo(musicSpec)) {
+            return;
+        }
+
+        LOG.debug("onSetMusicInfo: {}", musicSpec.toString());
+
+        Map<MusicControlEntityUpdateMessage.MusicEntity, String> attributes = new HashMap<>();
+
+        attributes.put(MusicControlEntityUpdateMessage.TRACK.ARTIST, musicSpec.artist);
+        attributes.put(MusicControlEntityUpdateMessage.TRACK.ALBUM, musicSpec.album);
+        attributes.put(MusicControlEntityUpdateMessage.TRACK.TITLE, musicSpec.track);
+        attributes.put(MusicControlEntityUpdateMessage.TRACK.DURATION, String.valueOf(musicSpec.duration));
+
+        sendOutgoingMessage("set music info", new MusicControlEntityUpdateMessage(attributes));
+
+        // Update the music state spec as well
+        final MusicStateSpec bufferMusicStateSpec = mediaManager.getBufferMusicStateSpec();
+        if (bufferMusicStateSpec != null) {
+            sendMusicState(bufferMusicStateSpec, bufferMusicStateSpec.position);
+        }
+    }
+
+    @Override
+    public void onSetMusicState(MusicStateSpec stateSpec) {
+        if (!mediaManager.onSetMusicState(stateSpec)) {
+            return;
+        }
+
+        LOG.debug("onSetMusicState: {}", stateSpec.toString());
+
+        sendMusicState(stateSpec, stateSpec.position);
+    }
+
+    private void sendMusicState(final MusicStateSpec stateSpec, final int progress) {
+        final int playing;
+        final float playRate;
+        if (stateSpec.state == MusicStateSpec.STATE_PLAYING) {
+            playing = 1;
+            playRate = stateSpec.playRate > 0 ? stateSpec.playRate / 100f : 1.0f;
+        } else {
+            playing = 0;
+            playRate = 0;
+        }
+        final Map<MusicControlEntityUpdateMessage.MusicEntity, String> attributes = new HashMap<>();
+        attributes.put(
+                MusicControlEntityUpdateMessage.PLAYER.PLAYBACK_INFO,
+                String.format(Locale.ROOT, "%d,%.1f,%.3f", playing, playRate, (float) progress)
+        );
+        sendOutgoingMessage("set music state", new MusicControlEntityUpdateMessage(attributes));
+    }
+
+    @Override
+    public void onSetPhoneVolume(final float volume) {
+        final Map<MusicControlEntityUpdateMessage.MusicEntity, String> attributes = new HashMap<>();
+
+        attributes.put(MusicControlEntityUpdateMessage.PLAYER.VOLUME, String.format(Locale.ROOT, "%.2f", volume / 100f));
+
+        sendOutgoingMessage("set phone volume", new MusicControlEntityUpdateMessage(attributes));
+    }
+
+    private boolean alreadyDownloaded(final FileTransferHandler.DirectoryEntry entry) {
+        final Optional<File> file = getFile(entry.getFileName());
+        if (file.isPresent()) {
+            if (file.get().length() == 0) {
+                LOG.warn("File {} is empty", entry.getFileName());
+                return false;
+            }
+            return true;
+        }
+
+        final Optional<File> legacyFile = getFile(entry.getLegacyFileName());
+        if (legacyFile.isPresent()) {
+            if (legacyFile.get().length() == 0) {
+                LOG.warn("Legacy file {} is empty", entry.getFileName());
+                return false;
+            }
+            return true;
+        }
+
+        return false;
+    }
+
+    private Optional<File> getFile(final String fileName) {
+        File dir;
+        try {
+            dir = getWritableExportDirectory();
+            File outputFile = new File(dir, fileName);
+            if (outputFile.exists())
+                return Optional.of(outputFile);
+        } catch (IOException e) {
+            LOG.error("Failed to get file", e);
+        }
+        return Optional.empty();
+    }
+
+    public File getWritableExportDirectory() throws IOException {
+        return getDevice().getDeviceCoordinator().getWritableExportDirectory(getDevice());
+    }
+
+    @Override
+    public void onSetGpsLocation(final Location location) {
+        final GdiCore.CoreService.LocationUpdatedNotification.Builder locationUpdatedNotification = GdiCore.CoreService.LocationUpdatedNotification.newBuilder()
+                .addLocationData(
+                        GarminUtils.toLocationData(location, GdiCore.CoreService.DataType.REALTIME_TRACKING)
+                );
+
+        final ProtobufMessage locationUpdatedNotificationRequest = protocolBufferHandler.prepareProtobufRequest(
+                GdiSmartProto.Smart.newBuilder().setCoreService(
+                        GdiCore.CoreService.newBuilder().setLocationUpdatedNotification(locationUpdatedNotification)
+                ).build()
+        );
+        sendOutgoingMessage("set gps location", locationUpdatedNotificationRequest);
+    }
+
+    @Nullable
+    public DocumentFile getAgpsFile(final String url) {
+        final Prefs prefs = getDevicePrefs();
+        final String filename = prefs.getString(GarminPreferences.agpsFilename(url), "");
+        if (filename.isEmpty()) {
+            LOG.debug("agps file not configured for {}", url);
+            return null;
+        }
+
+        final String folderUri = prefs.getString(GarminPreferences.PREF_GARMIN_AGPS_FOLDER, "");
+        if (folderUri.isEmpty()) {
+            LOG.debug("agps folder not set");
+            return null;
+        }
+        final DocumentFile folder = DocumentFile.fromTreeUri(getContext(), Uri.parse(folderUri));
+        if (folder == null) {
+            LOG.warn("Failed to find agps folder on {}", folderUri);
+            return null;
+        }
+
+        final DocumentFile localFile = folder.findFile(filename);
+        if (localFile == null) {
+            LOG.warn("Failed to find agps file '{}' for '{}' on '{}'", filename, url, folderUri);
+            return null;
+        }
+        if (!localFile.isFile()) {
+            LOG.warn("Local agps file {} for {} can't be read: isFile={} canRead={}", folderUri, url, localFile.isFile(), localFile.canRead());
+            return null;
+        }
+        return localFile;
+    }
+
+    public GarminCoordinator getCoordinator() {
+        return (GarminCoordinator) getDevice().getDeviceCoordinator();
+    }
+
+    @Override
+    public boolean connectFirstTime() {
+        mFirstConnect = true;
+        return super.connect();
+    }
+
+    @Override
+    public void onReadConfiguration(final String config) {
+        if (config.startsWith("screenId:")) {
+            final int screenId = Integer.parseInt(config.replaceFirst("screenId:", ""));
+
+            LOG.debug("Requesting screen {}", screenId);
+
+            final String language = Locale.getDefault().getLanguage();
+            final String country = Locale.getDefault().getCountry();
+            final String localeString = language + "_" + country.toUpperCase();
+
+            sendOutgoingMessage("get settings screen " + screenId, protocolBufferHandler.prepareProtobufRequest(
+                    GdiSmartProto.Smart.newBuilder()
+                            .setSettingsService(GdiSettingsService.SettingsService.newBuilder()
+                                    .setDefinitionRequest(
+                                            GdiSettingsService.ScreenDefinitionRequest.newBuilder()
+                                                    .setScreenId(screenId)
+                                                    .setUnk2(0)
+                                                    .setLanguage(localeString.length() == 5 ? localeString : "en_US")
+                                    )
+                            ).build()
+            ));
+
+            sendOutgoingMessage("get settings state " + screenId, protocolBufferHandler.prepareProtobufRequest(
+                    GdiSmartProto.Smart.newBuilder()
+                            .setSettingsService(GdiSettingsService.SettingsService.newBuilder()
+                                    .setStateRequest(
+                                            GdiSettingsService.ScreenStateRequest.newBuilder()
+                                                    .setScreenId(screenId)
+                                    )
+                            ).build()
+            ));
+        }
+    }
+
+    @Override
+    public void onInstallApp(Uri uri) {
+        final GarminFitFileInstallHandler fitFileInstallHandler = new GarminFitFileInstallHandler(uri, getContext());
+        if (fitFileInstallHandler.isValid()) {
+            communicator.sendMessage(
+                    "upload fit file",
+                    fileTransferHandler.initiateUpload(
+                            fitFileInstallHandler.getRawBytes(),
+                            fitFileInstallHandler.getFileType()
+                    ).getOutgoingMessage()
+            );
+        }
+
+        final GarminGpxRouteInstallHandler garminGpxRouteInstallHandler = new GarminGpxRouteInstallHandler(uri, getContext());
+        if (garminGpxRouteInstallHandler.isValid()) {
+            communicator.sendMessage("upload course file", fileTransferHandler.initiateUpload(garminGpxRouteInstallHandler.getGpxRouteFileConverter().getConvertedFile().getOutgoingMessage(), FileType.FILETYPE.DOWNLOAD_COURSE).getOutgoingMessage());
+        }
+    }
+
+    @Override
+    public void onHeartRateTest() {
+        communicator.onHeartRateTest();
+    }
+
+    @Override
+    public void onEnableRealtimeHeartRateMeasurement(final boolean enable) {
+        communicator.onEnableRealtimeHeartRateMeasurement(enable);
+    }
+
+    @Override
+    public void onEnableRealtimeSteps(final boolean enable) {
+        communicator.onEnableRealtimeSteps(enable);
+    }
+
+    @Override
+    public void onTestNewFunction() {
+        parseAllFitFilesFromStorage();
+    }
+
+    boolean parsingFitFilesFromStorage = false;
+
+    private void parseAllFitFilesFromStorage() {
+        if (parsingFitFilesFromStorage) {
+            GB.toast(getContext(), "Already parsing!", Toast.LENGTH_LONG, GB.ERROR);
+            return;
+        }
+
+        parsingFitFilesFromStorage = true;
+
+        LOG.info("Parsing all fit files from storage");
+
+        final File[] fitFiles;
+        try {
+            final File exportDir = getWritableExportDirectory();
+
+            if (!exportDir.exists() || !exportDir.isDirectory()) {
+                LOG.error("export directory {} not found", exportDir);
+                GB.toast(getContext(), "export directory " + exportDir + " not found", Toast.LENGTH_LONG, GB.ERROR);
+                return;
+            }
+
+            fitFiles = exportDir.listFiles((dir, name) -> name.endsWith(".fit"));
+            if (fitFiles == null) {
+                LOG.error("fitFiles is null for {}", exportDir);
+                GB.toast(getContext(), "fitFiles is null for " + exportDir, Toast.LENGTH_LONG, GB.ERROR);
+                return;
+            }
+            if (fitFiles.length == 0) {
+                LOG.error("No fit files found in {}", exportDir);
+                GB.toast(getContext(), "No fit files found in " + exportDir, Toast.LENGTH_LONG, GB.ERROR);
+                return;
+            }
+        } catch (final Exception e) {
+            LOG.error("Failed to parse from storage", e);
+            GB.toast(getContext(), "Failed to parse from storage", Toast.LENGTH_LONG, GB.ERROR, e);
+            return;
+        }
+
+        GB.toast(getContext(), "Check notification for progress", Toast.LENGTH_LONG, GB.INFO);
+
+        GB.updateTransferNotification("Parsing fit files", "...", true, 0, getContext());
+
+        //try (DBHandler handler = GBApplication.acquireDB()) {
+        //    final DaoSession session = handler.getDaoSession();
+        //    final Device device = DBHelper.getDevice(gbDevice, session);
+        //    //getCoordinator().deleteAllActivityData(device, session);
+        //} catch (final Exception e) {
+        //    GB.toast(getContext(), "Error deleting activity data", Toast.LENGTH_LONG, GB.ERROR, e);
+        //}
+
+        final long[] lastNotificationUpdateTs = new long[]{System.currentTimeMillis()};
+        final FitAsyncProcessor fitAsyncProcessor = new FitAsyncProcessor(getContext(), getDevice());
+        fitAsyncProcessor.process(Arrays.asList(fitFiles), new FitAsyncProcessor.Callback() {
+            @Override
+            public void onProgress(final int i) {
+                final long now = System.currentTimeMillis();
+                if (now - lastNotificationUpdateTs[0] > 1500L) {
+                    lastNotificationUpdateTs[0] = now;
+                    GB.updateTransferNotification(
+                            "Parsing fit files", "File " + i + " of " + fitFiles.length,
+                            true,
+                            (i * 100) / fitFiles.length, getContext()
+                    );
+                }
+            }
+
+            @Override
+            public void onFinish() {
+                parsingFitFilesFromStorage = false;
+                GB.updateTransferNotification("", "", false, 100, getContext());
+                GB.signalActivityDataFinish(getDevice());
+            }
+        });
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/GarminTimeUtils.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/GarminTimeUtils.java
new file mode 100644
index 0000000..2742482
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/GarminTimeUtils.java
@@ -0,0 +1,29 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin;
+
+import java.time.Instant;
+import java.time.ZoneId;
+
+public class GarminTimeUtils {
+
+    public static final int GARMIN_TIME_EPOCH = 631065600;
+
+    public static int unixTimeToGarminTimestamp(int unixTime) {
+        return unixTime - GARMIN_TIME_EPOCH;
+    }
+
+    public static int javaMillisToGarminTimestamp(long millis) {
+        return (int) (millis / 1000) - GARMIN_TIME_EPOCH;
+    }
+
+    public static long garminTimestampToJavaMillis(int timestamp) {
+        return (timestamp + GARMIN_TIME_EPOCH) * 1000L;
+    }
+
+    public static int garminTimestampToUnixTime(int timestamp) {
+        return timestamp + GARMIN_TIME_EPOCH;
+    }
+
+    public static int unixTimeToGarminDayOfWeek(int unixTime) {
+        return (Instant.ofEpochSecond(unixTime).atZone(ZoneId.systemDefault()).getDayOfWeek().getValue() % 7);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/GarminUtils.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/GarminUtils.java
new file mode 100644
index 0000000..099a23d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/GarminUtils.java
@@ -0,0 +1,35 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin;
+
+import android.location.Location;
+import android.os.Build;
+
+import nodomain.freeyourgadget.gadgetbridge.proto.garmin.GdiCore;
+
+public final class GarminUtils {
+    private GarminUtils() {
+        // utility class
+    }
+
+    public static GdiCore.CoreService.LocationData toLocationData(final Location location, final GdiCore.CoreService.DataType dataType) {
+        final GdiCore.CoreService.LatLon positionForWatch = GdiCore.CoreService.LatLon.newBuilder()
+                .setLat((int) ((location.getLatitude() * 2.147483648E9d) / 180.0d))
+                .setLon((int) ((location.getLongitude() * 2.147483648E9d) / 180.0d))
+                .build();
+
+        float vAccuracy = 0;
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+            vAccuracy = location.getVerticalAccuracyMeters();
+        }
+
+        return GdiCore.CoreService.LocationData.newBuilder()
+                .setPosition(positionForWatch)
+                .setAltitude((float) location.getAltitude())
+                .setTimestamp(GarminTimeUtils.javaMillisToGarminTimestamp(location.getTime()))
+                .setHAccuracy(location.getAccuracy())
+                .setVAccuracy(vAccuracy)
+                .setPositionType(dataType)
+                .setBearing(location.getBearing())
+                .setSpeed(location.getSpeed())
+                .build();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/MessageHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/MessageHandler.java
new file mode 100644
index 0000000..203433d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/MessageHandler.java
@@ -0,0 +1,7 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.GFDIMessage;
+
+public interface MessageHandler {
+    GFDIMessage handle(GFDIMessage message);
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/NotificationsHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/NotificationsHandler.java
new file mode 100644
index 0000000..de2aebe
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/NotificationsHandler.java
@@ -0,0 +1,521 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin;
+
+import android.util.SparseArray;
+
+import org.apache.commons.lang3.EnumUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Queue;
+
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventCallControl;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventNotificationControl;
+import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.NotificationType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.GFDIMessage;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.MessageWriter;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.NotificationControlMessage;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.NotificationDataMessage;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.NotificationUpdateMessage;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.status.NotificationDataStatusMessage;
+import nodomain.freeyourgadget.gadgetbridge.util.LimitedQueue;
+
+public class NotificationsHandler implements MessageHandler {
+    public static final SimpleDateFormat NOTIFICATION_DATE_FORMAT = new SimpleDateFormat("yyyyMMdd'T'HHmmss", Locale.ROOT);
+    private static final Logger LOG = LoggerFactory.getLogger(NotificationsHandler.class);
+    private final Queue<NotificationSpec> notificationSpecQueue;
+    private final Upload upload;
+    private boolean enabled = false;
+    // Keep track of Notification ID -> action handle, as BangleJSDeviceSupport.
+    // TODO: This needs to be simplified.
+    private final LimitedQueue<Integer, Long> mNotificationReplyAction = new LimitedQueue<>(16);
+
+
+    public NotificationsHandler() {
+        this.notificationSpecQueue = new LinkedList<>();
+        this.upload = new Upload();
+    }
+
+    private static void encodeNotificationAttribute(NotificationSpec notificationSpec, Map.Entry<NotificationAttribute, Integer> entry, MessageWriter messageWriter) {
+        messageWriter.writeByte(entry.getKey().code);
+        final byte[] bytes = entry.getKey().getNotificationSpecAttribute(notificationSpec, entry.getValue());
+        messageWriter.writeShort(bytes.length);
+        messageWriter.writeBytes(bytes);
+//        LOG.info("ATTRIBUTE:{} value:{}/{} length:{}", entry.getKey(), new String(bytes), GB.hexdump(bytes), bytes.length);
+    }
+
+
+    private boolean addNotificationToQueue(NotificationSpec notificationSpec) {
+        boolean found = false;
+        Iterator<NotificationSpec> iterator = notificationSpecQueue.iterator();
+        while (iterator.hasNext()) {
+            NotificationSpec e = iterator.next();
+            if (e.getId() == notificationSpec.getId()) {
+                found = true;
+                iterator.remove();
+            }
+        }
+        notificationSpecQueue.offer(notificationSpec); // Add the notificationSpec to the front of the queue
+        return found;
+    }
+
+    public NotificationUpdateMessage onSetCallState(CallSpec callSpec) {
+        if (!enabled)
+            return null;
+        final int id = StringUtils.firstNonBlank(callSpec.number, "Gadgetbridge Call").hashCode();
+        if (callSpec.command == CallSpec.CALL_INCOMING) {
+            NotificationSpec callNotificationSpec = new NotificationSpec(id);
+            callNotificationSpec.phoneNumber = callSpec.number;
+            callNotificationSpec.sourceAppId = callSpec.sourceAppId;
+            callNotificationSpec.title = StringUtils.isEmpty(callSpec.name) ? callSpec.number : callSpec.name;
+            callNotificationSpec.type = NotificationType.GENERIC_PHONE;
+            callNotificationSpec.body = StringUtils.isEmpty(callSpec.name) ? callSpec.number : callSpec.name;
+
+            // add an empty bogus action to toggle the hasActions boolean. The actions are hardcoded on the watch in case of incoming calls.
+            callNotificationSpec.attachedActions = new ArrayList<>();
+            callNotificationSpec.attachedActions.add(0, new NotificationSpec.Action());
+
+
+            return onNotification(callNotificationSpec);
+        } else {
+            return onDeleteNotification(id);
+        }
+    }
+
+    public NotificationUpdateMessage onNotification(NotificationSpec notificationSpec) {
+        if (!enabled)
+            return null;
+        final boolean isUpdate = addNotificationToQueue(notificationSpec);
+
+        NotificationUpdateMessage.NotificationUpdateType notificationUpdateType = isUpdate ? NotificationUpdateMessage.NotificationUpdateType.MODIFY : NotificationUpdateMessage.NotificationUpdateType.ADD;
+
+        if (notificationSpecQueue.size() > 10)
+            notificationSpecQueue.poll(); //remove the oldest notification TODO: should send a delete notification message to watch!
+
+        final boolean hasActions = (null != notificationSpec.attachedActions && !notificationSpec.attachedActions.isEmpty());
+        if (hasActions) {
+            for (int i = 0; i < notificationSpec.attachedActions.size(); i++) {
+                final NotificationSpec.Action action = notificationSpec.attachedActions.get(i);
+
+                if (action.type == NotificationSpec.Action.TYPE_WEARABLE_REPLY || action.type == NotificationSpec.Action.TYPE_SYNTECTIC_REPLY_PHONENR) {
+                    mNotificationReplyAction.add(notificationSpec.getId(), action.handle);
+                }
+            }
+        }
+
+        final boolean hasPicture = !StringUtils.isEmpty(notificationSpec.picturePath);
+        return new NotificationUpdateMessage(notificationUpdateType, notificationSpec.type, getNotificationsCount(notificationSpec.type), notificationSpec.getId(), hasActions, hasPicture);
+    }
+
+    private int getNotificationsCount(NotificationType notificationType) {
+        int count = 0;
+        for (NotificationSpec e : notificationSpecQueue) {
+            count += e.type == notificationType ? 1 : 0;
+        }
+        return count;
+    }
+
+    private NotificationSpec getNotificationSpecFromQueue(int id) {
+        for (NotificationSpec e : notificationSpecQueue) {
+            if (e.getId() == id) {
+                return e;
+            }
+        }
+        LOG.warn("Failed to find notificationSpec in queue for {}", id);
+        return null;
+    }
+
+    public String getNotificationAttachmentPath(int notificationId) {
+        NotificationSpec notificationSpec = getNotificationSpecFromQueue(notificationId);
+        if (null != notificationSpec)
+            return notificationSpec.picturePath;
+        return null;
+    }
+
+    public NotificationUpdateMessage onDeleteNotification(int id) {
+        if (!enabled)
+            return null;
+
+        Iterator<NotificationSpec> iterator = notificationSpecQueue.iterator();
+        while (iterator.hasNext()) {
+            NotificationSpec e = iterator.next();
+            if (e.getId() == id) {
+                iterator.remove();
+                return new NotificationUpdateMessage(NotificationUpdateMessage.NotificationUpdateType.REMOVE, e.type, getNotificationsCount(e.type), id, false, false);
+            }
+        }
+        return null;
+    }
+
+    public GFDIMessage handle(GFDIMessage message) {
+        if (!enabled)
+            return null;
+        if (message instanceof NotificationControlMessage) {
+            final NotificationSpec notificationSpec = getNotificationSpecFromQueue(((NotificationControlMessage) message).getNotificationId());
+            if (notificationSpec != null) {
+                switch (((NotificationControlMessage) message).getCommand()) {
+                    case GET_NOTIFICATION_ATTRIBUTES:
+                        return getNotificationDataMessage((NotificationControlMessage) message, notificationSpec);
+                    case PERFORM_LEGACY_NOTIFICATION_ACTION:
+                        LOG.info("Legacy Notification: {}", ((NotificationControlMessage) message).getLegacyNotificationAction());
+                        break;
+                    case PERFORM_NOTIFICATION_ACTION:
+                        performNotificationAction((NotificationControlMessage) message, notificationSpec);
+                        break;
+
+                    default:
+                        LOG.error("NOT SUPPORTED: {}", ((NotificationControlMessage) message).getCommand());
+                }
+            }
+        } else if (message instanceof NotificationDataStatusMessage) {
+            return upload.processUploadProgress((NotificationDataStatusMessage) message);
+        }
+
+        return null;
+    }
+
+    private void performNotificationAction(NotificationControlMessage message, NotificationSpec notificationSpec) {
+        final GBDeviceEventNotificationControl deviceEvtNotificationControl = new GBDeviceEventNotificationControl();
+        deviceEvtNotificationControl.handle = notificationSpec.getId();
+        final GBDeviceEventCallControl deviceEvtCallControl = new GBDeviceEventCallControl();
+        switch (message.getNotificationAction()) {
+            case REPLY_INCOMING_CALL:
+                deviceEvtCallControl.event = GBDeviceEventCallControl.Event.REJECT;
+                message.addGbDeviceEvent(deviceEvtCallControl);
+            case REPLY_MESSAGES:
+                deviceEvtNotificationControl.event = GBDeviceEventNotificationControl.Event.REPLY;
+                deviceEvtNotificationControl.reply = message.getActionString();
+                if (notificationSpec.type.equals(NotificationType.GENERIC_PHONE) || notificationSpec.type.equals(NotificationType.GENERIC_SMS)) {
+                    deviceEvtNotificationControl.phoneNumber = notificationSpec.phoneNumber;
+                } else {
+                    deviceEvtNotificationControl.handle = mNotificationReplyAction.lookup(notificationSpec.getId()); //handle of wearable action is needed
+                }
+                message.addGbDeviceEvent(deviceEvtNotificationControl);
+                break;
+            case ACCEPT_INCOMING_CALL:
+                deviceEvtCallControl.event = GBDeviceEventCallControl.Event.ACCEPT;
+                message.addGbDeviceEvent(deviceEvtCallControl);
+                break;
+            case REJECT_INCOMING_CALL:
+                deviceEvtCallControl.event = GBDeviceEventCallControl.Event.REJECT;
+                message.addGbDeviceEvent(deviceEvtCallControl);
+                break;
+            case DISMISS_NOTIFICATION:
+                deviceEvtNotificationControl.event = GBDeviceEventNotificationControl.Event.DISMISS;
+                message.addGbDeviceEvent(deviceEvtNotificationControl);
+                break;
+            case BLOCK_APPLICATION:
+                deviceEvtNotificationControl.event = GBDeviceEventNotificationControl.Event.MUTE;
+                message.addGbDeviceEvent(deviceEvtNotificationControl);
+                break;
+        }
+    }
+
+    private NotificationDataMessage getNotificationDataMessage(NotificationControlMessage message, NotificationSpec notificationSpec) {
+        final MessageWriter messageWriter = new MessageWriter();
+        messageWriter.writeByte(NotificationCommand.GET_NOTIFICATION_ATTRIBUTES.code);
+        messageWriter.writeInt(message.getNotificationId());
+        Map.Entry<NotificationAttribute, Integer> lastEntry = null;
+        for (Map.Entry<NotificationAttribute, Integer> entry : message.getNotificationAttributesMap().entrySet()) {
+            if (!NotificationAttribute.MESSAGE_SIZE.equals(entry.getKey())) {
+                encodeNotificationAttribute(notificationSpec, entry, messageWriter);
+            } else {
+                lastEntry = entry;
+            }
+        }
+        if (lastEntry != null) {
+            encodeNotificationAttribute(notificationSpec, lastEntry, messageWriter);
+        }
+        NotificationFragment notificationFragment = new NotificationFragment(messageWriter.getBytes());
+        return upload.setCurrentlyUploading(notificationFragment);
+    }
+
+
+    public void setEnabled(boolean enable) {
+        this.enabled = enable;
+    }
+
+    public enum NotificationCommand { //was AncsCommand
+        GET_NOTIFICATION_ATTRIBUTES(0),
+        GET_APP_ATTRIBUTES(1), //unknown/untested
+        PERFORM_LEGACY_NOTIFICATION_ACTION(2),
+        PERFORM_NOTIFICATION_ACTION(128);
+
+        public final int code;
+
+        NotificationCommand(int code) {
+            this.code = code;
+        }
+
+        public static NotificationCommand fromCode(int code) {
+            for (NotificationCommand value : values()) {
+                if (value.code == code)
+                    return value;
+            }
+            throw new IllegalArgumentException("Unknown notification command " + code);
+        }
+    }
+
+    public enum LegacyNotificationAction { //was AncsAction
+        ACCEPT,
+        REFUSE
+
+    }
+    public enum NotificationAttribute { //was AncsAttribute
+        APP_IDENTIFIER(0),
+        TITLE(1, true),
+        SUBTITLE(2, true),
+        MESSAGE(3, true),
+        MESSAGE_SIZE(4),
+        DATE(5),
+        //        POSITIVE_ACTION_LABEL(6), //needed only for legacy notification actions
+        NEGATIVE_ACTION_LABEL(7), //needed only for legacy notification actions
+        // Garmin extensions
+//        PHONE_NUMBER(126, true),
+        ACTIONS(127, false, true),
+        ATTACHMENTS(128),
+        ;
+        private static final SparseArray<NotificationAttribute> valueByCode;
+
+        static {
+            final NotificationAttribute[] values = values();
+            valueByCode = new SparseArray<>(values.length);
+            for (NotificationAttribute value : values) {
+                valueByCode.append(value.code, value);
+            }
+        }
+
+        public final int code;
+        public final boolean hasLengthParam;
+        public final boolean hasAdditionalParams;
+
+        NotificationAttribute(int code) {
+            this(code, false, false);
+        }
+
+        NotificationAttribute(int code, boolean hasLengthParam) {
+            this(code, hasLengthParam, false);
+        }
+
+        NotificationAttribute(int code, boolean hasLengthParam, boolean hasAdditionalParams) {
+            this.code = code;
+            this.hasLengthParam = hasLengthParam;
+            this.hasAdditionalParams = hasAdditionalParams;
+        }
+
+        public static NotificationAttribute getByCode(int code) {
+            return valueByCode.get(code);
+        }
+
+        public byte[] getNotificationSpecAttribute(NotificationSpec notificationSpec, int maxLength) {
+            String toReturn = "";
+            switch (this) {
+                case DATE:
+                    final long notificationTimestamp = notificationSpec.when == 0 ? System.currentTimeMillis() : notificationSpec.when;
+                    toReturn = NOTIFICATION_DATE_FORMAT.format(new Date(notificationTimestamp));
+                    break;
+                case TITLE:
+                    if (NotificationType.GENERIC_SMS.equals(notificationSpec.type))
+                        toReturn = notificationSpec.sender == null ? "" : notificationSpec.sender;
+                    else
+                        toReturn = notificationSpec.title == null ? "" : notificationSpec.title;
+                    break;
+                case SUBTITLE:
+                    toReturn = notificationSpec.subject == null ? "" : notificationSpec.subject;
+                    break;
+                case APP_IDENTIFIER:
+                    toReturn = notificationSpec.sourceAppId == null ? "" : notificationSpec.sourceAppId;
+                    break;
+                case MESSAGE:
+                    toReturn = notificationSpec.body == null ? "" : notificationSpec.body;
+                    break;
+                case MESSAGE_SIZE:
+                    toReturn = Integer.toString(notificationSpec.body == null ? "".length() : notificationSpec.body.length());
+                    break;
+                case ACTIONS:
+                    toReturn = encodeNotificationActionsString(notificationSpec);
+                    break;
+                case ATTACHMENTS:
+                    LOG.debug("Notification attachments requested for notification id {}", notificationSpec.getId());
+                    toReturn = "1"; // the number of attachments
+                    break;
+            }
+            if (maxLength == 0)
+                return toReturn.getBytes(StandardCharsets.UTF_8);
+            return toReturn.substring(0, Math.min(toReturn.length(), maxLength)).getBytes(StandardCharsets.UTF_8);
+        }
+
+        private String encodeNotificationActionsString(NotificationSpec notificationSpec) {
+
+            final List<byte[]> garminActions = new ArrayList<>();
+            if (notificationSpec.type.equals(NotificationType.GENERIC_PHONE)) {
+                garminActions.add(encodeNotificationAction(NotificationAction.REPLY_INCOMING_CALL, " ")); //text is not shown on watch
+                garminActions.add(encodeNotificationAction(NotificationAction.REJECT_INCOMING_CALL, " ")); //text is not shown on watch
+                garminActions.add(encodeNotificationAction(NotificationAction.ACCEPT_INCOMING_CALL, " ")); //text is not shown on watch
+            }
+            if (null != notificationSpec.attachedActions) {
+                for (NotificationSpec.Action action : notificationSpec.attachedActions) {
+                    switch (action.type) {
+                        case NotificationSpec.Action.TYPE_WEARABLE_REPLY:
+                        case NotificationSpec.Action.TYPE_SYNTECTIC_REPLY_PHONENR:
+                            garminActions.add(encodeNotificationAction(NotificationAction.REPLY_MESSAGES, action.title));
+                            break;
+                        case NotificationSpec.Action.TYPE_SYNTECTIC_DISMISS:
+                            garminActions.add(encodeNotificationAction(NotificationAction.DISMISS_NOTIFICATION, action.title));
+                            break;
+                        case NotificationSpec.Action.TYPE_SYNTECTIC_MUTE:
+                            garminActions.add(encodeNotificationAction(NotificationAction.BLOCK_APPLICATION, action.title));
+                            break;
+
+                    }
+//                    LOG.info("Notification has action {} with title {}", action.type, action.title);
+                }
+            }
+            if (garminActions.isEmpty())
+                return new String(new byte[]{0x00, 0x00, 0x00, 0x00});
+
+            try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) {
+                byteArrayOutputStream.write(garminActions.size());
+                for (byte[] item : garminActions) {
+                    byteArrayOutputStream.write(item);
+                }
+                return byteArrayOutputStream.toString();
+            } catch (IOException e) {
+                throw new RuntimeException(e);
+            }
+
+        }
+
+        private byte[] encodeNotificationAction(NotificationAction notificationAction, String description) {
+            final ByteBuffer action = ByteBuffer.allocate(3 + description.getBytes(StandardCharsets.UTF_8).length);
+            action.put((byte) notificationAction.code);
+            if (null == notificationAction.notificationActionIconPosition)
+                action.put((byte) 0x00);
+            else
+                action.put((byte) EnumUtils.generateBitVector(NotificationActionIconPosition.class, notificationAction.notificationActionIconPosition));
+            action.put((byte) description.getBytes(StandardCharsets.UTF_8).length);
+            action.put(description.getBytes());
+            return action.array();
+        }
+    }
+
+    public enum NotificationAction {
+        REPLY_INCOMING_CALL(94, NotificationActionIconPosition.BOTTOM),
+        REPLY_MESSAGES(95, NotificationActionIconPosition.BOTTOM),
+        ACCEPT_INCOMING_CALL(96, NotificationActionIconPosition.RIGHT),
+        REJECT_INCOMING_CALL(97, NotificationActionIconPosition.LEFT),
+        DISMISS_NOTIFICATION(98, NotificationActionIconPosition.LEFT),
+        BLOCK_APPLICATION(99, null),
+        ;
+
+        private final int code;
+        private final NotificationActionIconPosition notificationActionIconPosition;
+
+        NotificationAction(int code, NotificationActionIconPosition notificationActionIconPosition) {
+            this.code = code;
+            this.notificationActionIconPosition = notificationActionIconPosition;
+        }
+
+        public static NotificationAction fromCode(final int code) {
+            for (final NotificationAction notificationAction : NotificationAction.values()) {
+                if (notificationAction.code == code) {
+                    return notificationAction;
+                }
+            }
+            throw new IllegalArgumentException("Unknown notification action code " + code);
+        }
+
+    }
+
+    enum NotificationActionIconPosition { //educated guesses based on the icons' positions on v铆vomove style
+        BOTTOM, //or is it reply?
+        RIGHT, //or is it accept?
+        LEFT, //or is it dismiss/refuse?
+    }
+    public static class Upload {
+
+        private NotificationFragment currentlyUploading;
+
+        public NotificationDataMessage setCurrentlyUploading(NotificationFragment currentlyUploading) {
+            this.currentlyUploading = currentlyUploading;
+            return currentlyUploading.take();
+        }
+
+        private GFDIMessage processUploadProgress(NotificationDataStatusMessage notificationDataStatusMessage) {
+            if (null == currentlyUploading) {
+                LOG.warn("Received Upload Progress but we are not sending any notification");
+                return null;
+            }
+            if (!currentlyUploading.dataHolder.hasRemaining()) {
+                this.currentlyUploading = null;
+                LOG.info("SENT ALL");
+
+                return new NotificationDataStatusMessage(GFDIMessage.GarminMessage.NOTIFICATION_DATA, GFDIMessage.Status.ACK, NotificationDataStatusMessage.TransferStatus.OK);
+            } else {
+                if (notificationDataStatusMessage.canProceed()) {
+                    LOG.info("SENDING NEXT CHUNK!!!");
+                    return currentlyUploading.take();
+                } else {
+                    LOG.warn("Cannot proceed with upload"); //TODO: send the correct status message
+                    this.currentlyUploading = null;
+                }
+
+            }
+            return null;
+        }
+
+    }
+
+    public static class NotificationFragment {
+        private final int dataSize;
+        private final ByteBuffer dataHolder;
+        private final int maxBlockSize = 300;
+        private int runningCrc;
+
+        NotificationFragment(byte[] contents) {
+            this.dataHolder = ByteBuffer.wrap(contents);
+            this.dataSize = contents.length;
+            this.dataHolder.flip();
+            this.dataHolder.compact();
+            this.setRunningCrc(0);
+        }
+
+        public int getDataSize() {
+            return dataSize;
+        }
+
+        private int getMaxBlockSize() {
+            return maxBlockSize;
+        }
+
+        private NotificationDataMessage take() {
+            final int currentOffset = this.dataHolder.position();
+            final byte[] chunk = new byte[Math.min(this.dataHolder.remaining(), getMaxBlockSize())];
+            this.dataHolder.get(chunk);
+            setRunningCrc(ChecksumCalculator.computeCrc(getRunningCrc(), chunk, 0, chunk.length));
+            return new NotificationDataMessage(chunk, getDataSize(), currentOffset, getRunningCrc());
+        }
+
+        private int getRunningCrc() {
+            return runningCrc;
+        }
+
+        private void setRunningCrc(int runningCrc) {
+            this.runningCrc = runningCrc;
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/ProtocolBufferHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/ProtocolBufferHandler.java
new file mode 100644
index 0000000..31a2bf3
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/ProtocolBufferHandler.java
@@ -0,0 +1,601 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin;
+
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.location.Location;
+
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import com.google.protobuf.InvalidProtocolBufferException;
+
+import org.apache.commons.lang3.ArrayUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.ByteArrayOutputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdatePreferences;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminPreferences;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminRealtimeSettingsFragment;
+import nodomain.freeyourgadget.gadgetbridge.externalevents.gps.GBLocationProviderType;
+import nodomain.freeyourgadget.gadgetbridge.externalevents.gps.GBLocationService;
+import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec;
+import nodomain.freeyourgadget.gadgetbridge.proto.garmin.GdiCalendarService;
+import nodomain.freeyourgadget.gadgetbridge.proto.garmin.GdiCore;
+import nodomain.freeyourgadget.gadgetbridge.proto.garmin.GdiDataTransferService;
+import nodomain.freeyourgadget.gadgetbridge.proto.garmin.GdiDeviceStatus;
+import nodomain.freeyourgadget.gadgetbridge.proto.garmin.GdiFindMyWatch;
+import nodomain.freeyourgadget.gadgetbridge.proto.garmin.GdiHttpService;
+import nodomain.freeyourgadget.gadgetbridge.proto.garmin.GdiNotificationsService;
+import nodomain.freeyourgadget.gadgetbridge.proto.garmin.GdiSettingsService;
+import nodomain.freeyourgadget.gadgetbridge.proto.garmin.GdiSmartProto;
+import nodomain.freeyourgadget.gadgetbridge.proto.garmin.GdiSmsNotification;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.http.DataTransferHandler;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.http.HttpHandler;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.GFDIMessage;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.ProtobufMessage;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.status.ProtobufStatusMessage;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.webview.CurrentPosition;
+import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+import nodomain.freeyourgadget.gadgetbridge.util.calendar.CalendarEvent;
+import nodomain.freeyourgadget.gadgetbridge.util.calendar.CalendarManager;
+
+public class ProtocolBufferHandler implements MessageHandler {
+
+    private static final Logger LOG = LoggerFactory.getLogger(ProtocolBufferHandler.class);
+    private final GarminSupport deviceSupport;
+    private final Map<Integer, ProtobufFragment> chunkedFragmentsMap;
+    private final int maxChunkSize = 375; //tested on V铆vomove Style
+    private int lastProtobufRequestId;
+    private final HttpHandler httpHandler;
+    private final DataTransferHandler dataTransferHandler;
+
+    private final Map<GdiSmsNotification.SmsNotificationService.CannedListType, String[]> cannedListTypeMap = new HashMap<>();
+
+    public ProtocolBufferHandler(GarminSupport deviceSupport) {
+        this.deviceSupport = deviceSupport;
+        chunkedFragmentsMap = new HashMap<>();
+        httpHandler = new HttpHandler(deviceSupport);
+        dataTransferHandler = new DataTransferHandler();
+    }
+
+    private int getNextProtobufRequestId() {
+        lastProtobufRequestId = (lastProtobufRequestId + 1) % 65536;
+        return lastProtobufRequestId;
+    }
+
+    public ProtobufMessage handle(GFDIMessage protobufMessage) {
+        if (protobufMessage instanceof ProtobufMessage) {
+            return processIncoming((ProtobufMessage) protobufMessage);
+        } else if (protobufMessage instanceof ProtobufStatusMessage) {
+            return processIncoming((ProtobufStatusMessage) protobufMessage);
+        }
+        return null;
+    }
+
+    private ProtobufMessage processIncoming(ProtobufMessage message) {
+        ProtobufFragment protobufFragment = processChunkedMessage(message);
+
+        if (protobufFragment.isComplete()) { //message is now complete
+            LOG.info("Received protobuf message #{}, {}B: {}", message.getRequestId(), protobufFragment.totalLength, GB.hexdump(protobufFragment.fragmentBytes, 0, protobufFragment.totalLength));
+            chunkedFragmentsMap.remove(message.getRequestId());
+
+            final GdiSmartProto.Smart smart;
+            try {
+                smart = GdiSmartProto.Smart.parseFrom(protobufFragment.fragmentBytes);
+            } catch (InvalidProtocolBufferException e) {
+                LOG.error("Failed to parse protobuf message ({}): {}", e.getLocalizedMessage(), GB.hexdump(protobufFragment.fragmentBytes));
+                return null;
+            }
+            boolean processed = false;
+            if (smart.hasCoreService()) { //TODO: unify request and response???
+                return prepareProtobufResponse(processProtobufCoreRequest(smart.getCoreService()), message.getRequestId());
+            }
+            if (smart.hasCalendarService()) {
+                return prepareProtobufResponse(processProtobufCalendarRequest(smart.getCalendarService()), message.getRequestId());
+            }
+            if (smart.hasSmsNotificationService()) {
+                return prepareProtobufResponse(processProtobufSmsNotificationMessage(smart.getSmsNotificationService()), message.getRequestId());
+            }
+            if (smart.hasHttpService()) {
+                final GdiHttpService.HttpService response = httpHandler.handle(smart.getHttpService());
+                if (response == null) {
+                    return null;
+                }
+                return prepareProtobufResponse(GdiSmartProto.Smart.newBuilder().setHttpService(response).build(), message.getRequestId());
+            }
+            if (smart.hasDataTransferService()) {
+                final GdiDataTransferService.DataTransferService response = dataTransferHandler.handle(smart.getDataTransferService(), message.getRequestId());
+                if (response == null) {
+                    return null;
+                }
+                return prepareProtobufResponse(GdiSmartProto.Smart.newBuilder().setDataTransferService(response).build(), message.getRequestId());
+            }
+            if (smart.hasDeviceStatusService()) {
+                processed = true;
+                processProtobufDeviceStatusResponse(smart.getDeviceStatusService());
+            }
+            if (smart.hasFindMyWatchService()) {
+                processed = true;
+                processProtobufFindMyWatchResponse(smart.getFindMyWatchService());
+            }
+            if (smart.hasSettingsService()) {
+                processed = true;
+                processProtobufSettingsService(smart.getSettingsService());
+            }
+            if (smart.hasNotificationsService()) {
+                return prepareProtobufResponse(processProtobufNotificationsServiceMessage(smart.getNotificationsService()), message.getRequestId());
+            }
+            if (processed) {
+                message.setStatusMessage(new ProtobufStatusMessage(
+                        message.getMessageType(),
+                        GFDIMessage.Status.ACK,
+                        message.getRequestId(),
+                        message.getDataOffset(),
+                        ProtobufStatusMessage.ProtobufChunkStatus.KEPT,
+                        ProtobufStatusMessage.ProtobufStatusCode.NO_ERROR
+                ));
+            } else {
+                LOG.warn("Unknown protobuf request: {}", smart);
+                message.setStatusMessage(new ProtobufStatusMessage(
+                        message.getMessageType(),
+                        GFDIMessage.Status.ACK,
+                        message.getRequestId(),
+                        message.getDataOffset(),
+                        ProtobufStatusMessage.ProtobufChunkStatus.DISCARDED,
+                        ProtobufStatusMessage.ProtobufStatusCode.UNKNOWN_REQUEST_ID
+                ));
+            }
+        }
+        return null;
+    }
+
+    private ProtobufMessage processIncoming(ProtobufStatusMessage statusMessage) {
+        //TODO: check status and react accordingly, right now we blindly proceed to next chunk
+        if (statusMessage.isOK()) {
+            DataTransferHandler.onDataChunkSuccessfullyReceived(statusMessage.getRequestId());
+
+            if (chunkedFragmentsMap.containsKey(statusMessage.getRequestId())) {
+                final ProtobufFragment protobufFragment = chunkedFragmentsMap.get(statusMessage.getRequestId());
+                LOG.debug("Protobuf message #{} found in queue: {}", statusMessage.getRequestId(), GB.hexdump(protobufFragment.fragmentBytes));
+
+                if (protobufFragment.totalLength <= (statusMessage.getDataOffset() + maxChunkSize)) {
+                    chunkedFragmentsMap.remove(statusMessage.getRequestId());
+                    return null;
+                }
+                return protobufFragment.getNextChunk(statusMessage);
+            }
+        }
+        return null;
+    }
+
+    private ProtobufFragment processChunkedMessage(ProtobufMessage message) {
+        if (message.isComplete()) //comment this out if for any reason also smaller messages should end up in the map
+            return new ProtobufFragment(message.getMessageBytes());
+
+        if (message.getDataOffset() == 0) { //store new messages beginning at 0, overwrite old messages
+            chunkedFragmentsMap.put(message.getRequestId(), new ProtobufFragment(message));
+            LOG.info("Protobuf request put in queue: #{} , {}", message.getRequestId(), GB.hexdump(message.getMessageBytes()));
+        } else {
+            if (chunkedFragmentsMap.containsKey(message.getRequestId())) {
+                ProtobufFragment oldFragment = chunkedFragmentsMap.get(message.getRequestId());
+                chunkedFragmentsMap.put(message.getRequestId(),
+                        new ProtobufFragment(oldFragment, message));
+            }
+        }
+        return chunkedFragmentsMap.get(message.getRequestId());
+    }
+
+    private GdiSmartProto.Smart processProtobufCalendarRequest(GdiCalendarService.CalendarService calendarService) {
+        if (calendarService.hasCalendarRequest()) {
+            GdiCalendarService.CalendarService.CalendarServiceRequest calendarServiceRequest = calendarService.getCalendarRequest();
+
+            CalendarManager upcomingEvents = new CalendarManager(deviceSupport.getContext(), deviceSupport.getDevice().getAddress());
+            List<CalendarEvent> mEvents = upcomingEvents.getCalendarEventList();
+            List<GdiCalendarService.CalendarService.CalendarEvent> watchEvents = new ArrayList<>();
+
+            for (CalendarEvent mEvt : mEvents) {
+                if (mEvt.getEndSeconds() < calendarServiceRequest.getBegin() ||
+                        mEvt.getBeginSeconds() > calendarServiceRequest.getEnd()) {
+                    LOG.debug("CalendarService Skipping event {} that is out of requested time range", mEvt.getTitle());
+                    continue;
+                }
+                if (!calendarServiceRequest.getIncludeAllDay() && mEvt.isAllDay()) {
+                    LOG.debug("CalendarService Skipping event {} that is AllDay", mEvt.getTitle());
+                    continue;
+                }
+
+                if (watchEvents.size() >= calendarServiceRequest.getMaxEvents() * 2) { //NOTE: Tested with values higher than double of the reported max without issues
+                    LOG.debug("Reached the maximum number of events supported by the watch");
+                    break;
+                }
+
+                final int startDateSeconds;
+                final int endDateSeconds;
+
+                if (mEvt.isAllDay()) {
+                    // For all-day events, garmin expects the start and end date to match the midnight boundaries
+                    // in the user's timezone. However, the calendar event will have them in the UTC timezone,
+                    // so we need to convert it
+                    startDateSeconds = (int) (DateTimeUtils.utcDateTimeToLocal(mEvt.getBegin()) / 1000);
+                    endDateSeconds = (int) (DateTimeUtils.utcDateTimeToLocal(mEvt.getEnd()) / 1000);
+                } else {
+                    startDateSeconds = mEvt.getBeginSeconds();
+                    endDateSeconds = mEvt.getEndSeconds();
+                }
+
+                final GdiCalendarService.CalendarService.CalendarEvent.Builder event = GdiCalendarService.CalendarService.CalendarEvent.newBuilder()
+                        .setTitle(mEvt.getTitle().substring(0, Math.min(mEvt.getTitle().length(), calendarServiceRequest.getMaxTitleLength())))
+                        .setAllDay(mEvt.isAllDay())
+                        .setStartDate(startDateSeconds)
+                        .setEndDate(endDateSeconds);
+
+                if (calendarServiceRequest.getIncludeLocation() && mEvt.getLocation() != null) {
+                    event.setLocation(mEvt.getLocation().substring(0, Math.min(mEvt.getLocation().length(), calendarServiceRequest.getMaxLocationLength())));
+                }
+
+                if (calendarServiceRequest.getIncludeDescription() && mEvt.getDescription() != null) {
+                    event.setDescription(mEvt.getDescription().substring(0, Math.min(mEvt.getDescription().length(), calendarServiceRequest.getMaxDescriptionLength())));
+                }
+                if (calendarServiceRequest.getIncludeOrganizer() && mEvt.getOrganizer() != null) {
+                    event.setDescription(mEvt.getOrganizer().substring(0, Math.min(mEvt.getOrganizer().length(), calendarServiceRequest.getMaxOrganizerLength())));
+                }
+                watchEvents.add(event.build());
+            }
+
+            LOG.debug("CalendarService Sending {} events to watch", watchEvents.size());
+            return GdiSmartProto.Smart.newBuilder().setCalendarService(
+                    GdiCalendarService.CalendarService.newBuilder().setCalendarResponse(
+                            GdiCalendarService.CalendarService.CalendarServiceResponse.newBuilder()
+                                    .addAllCalendarEvent(watchEvents)
+                                    .setStatus(GdiCalendarService.CalendarService.CalendarServiceResponse.ResponseStatus.OK)
+                    )
+            ).build();
+        }
+        LOG.warn("Unknown CalendarService request: {}", calendarService);
+        return GdiSmartProto.Smart.newBuilder().setCalendarService(
+                GdiCalendarService.CalendarService.newBuilder().setCalendarResponse(
+                        GdiCalendarService.CalendarService.CalendarServiceResponse.newBuilder()
+                                .setStatus(GdiCalendarService.CalendarService.CalendarServiceResponse.ResponseStatus.UNKNOWN_RESPONSE_STATUS)
+                )
+        ).build();
+    }
+
+    private void processProtobufDeviceStatusResponse(GdiDeviceStatus.DeviceStatusService deviceStatusService) {
+        if (deviceStatusService.hasRemoteDeviceBatteryStatusResponse()) {
+            final GdiDeviceStatus.DeviceStatusService.RemoteDeviceBatteryStatusResponse batteryStatusResponse = deviceStatusService.getRemoteDeviceBatteryStatusResponse();
+            final int batteryLevel = batteryStatusResponse.getCurrentBatteryLevel();
+            LOG.info("Received remote battery status {}: level={}", batteryStatusResponse.getStatus(), batteryLevel);
+            final GBDeviceEventBatteryInfo batteryEvent = new GBDeviceEventBatteryInfo();
+            batteryEvent.level = (short) batteryLevel;
+            deviceSupport.evaluateGBDeviceEvent(batteryEvent);
+            return;
+        }
+        if (deviceStatusService.hasActivityStatusResponse()) {
+            final GdiDeviceStatus.DeviceStatusService.ActivityStatusResponse activityStatusResponse = deviceStatusService.getActivityStatusResponse();
+            LOG.info("Received activity status: {}", activityStatusResponse.getStatus());
+            return;
+        }
+        LOG.warn("Unknown DeviceStatusService response: {}", deviceStatusService);
+    }
+
+    private GdiSmartProto.Smart processProtobufCoreRequest(GdiCore.CoreService coreService) {
+        if (coreService.hasSyncResponse()) {
+            final GdiCore.CoreService.SyncResponse syncResponse = coreService.getSyncResponse();
+            LOG.info("Received sync status: {}", syncResponse.getStatus());
+            return null;
+        }
+
+        if (coreService.hasGetLocationRequest()) {
+            LOG.info("Got location request");
+            final Location location = new CurrentPosition().getLastKnownLocation();
+            final GdiCore.CoreService.GetLocationResponse.Builder response = GdiCore.CoreService.GetLocationResponse.newBuilder();
+            if (location.getLatitude() == 0 && location.getLongitude() == 0) {
+                response.setStatus(GdiCore.CoreService.GetLocationResponse.Status.NO_VALID_LOCATION);
+            } else {
+                response.setStatus(GdiCore.CoreService.GetLocationResponse.Status.OK)
+                        .setLocationData(GarminUtils.toLocationData(location, GdiCore.CoreService.DataType.GENERAL_LOCATION));
+            }
+            return GdiSmartProto.Smart.newBuilder().setCoreService(
+                    GdiCore.CoreService.newBuilder().setGetLocationResponse(response)).build();
+        }
+
+        if (coreService.hasLocationUpdatedSetEnabledRequest()) {
+            final GdiCore.CoreService.LocationUpdatedSetEnabledRequest locationUpdatedSetEnabledRequest = coreService.getLocationUpdatedSetEnabledRequest();
+
+            LOG.info("Received locationUpdatedSetEnabledRequest status: {}", locationUpdatedSetEnabledRequest.getEnabled());
+
+            GdiCore.CoreService.LocationUpdatedSetEnabledResponse.Builder response = GdiCore.CoreService.LocationUpdatedSetEnabledResponse.newBuilder()
+                    .setStatus(GdiCore.CoreService.LocationUpdatedSetEnabledResponse.Status.OK);
+
+            final boolean sendGpsPref = deviceSupport.getDevicePrefs().getBoolean(DeviceSettingsPreferenceConst.PREF_WORKOUT_SEND_GPS_TO_BAND, false);
+
+            GdiCore.CoreService.Request realtimeRequest = null;
+
+            if (locationUpdatedSetEnabledRequest.getEnabled()) {
+                for (final GdiCore.CoreService.Request request : locationUpdatedSetEnabledRequest.getRequestsList()) {
+                    final GdiCore.CoreService.LocationUpdatedSetEnabledResponse.Requested.RequestedStatus requestedStatus;
+                    if (GdiCore.CoreService.DataType.REALTIME_TRACKING.equals(request.getRequested())) {
+                        realtimeRequest = request;
+                        if (sendGpsPref) {
+                            requestedStatus = GdiCore.CoreService.LocationUpdatedSetEnabledResponse.Requested.RequestedStatus.OK;
+                        } else {
+                            requestedStatus = GdiCore.CoreService.LocationUpdatedSetEnabledResponse.Requested.RequestedStatus.KO;
+                        }
+                    } else {
+                        requestedStatus = GdiCore.CoreService.LocationUpdatedSetEnabledResponse.Requested.RequestedStatus.KO;
+                    }
+
+                    response.addRequests(
+                            GdiCore.CoreService.LocationUpdatedSetEnabledResponse.Requested.newBuilder()
+                                    .setRequested(request.getRequested())
+                                    .setStatus(requestedStatus)
+                    );
+                }
+            }
+
+            if (sendGpsPref) {
+                if (realtimeRequest != null) {
+                    GBLocationService.start(
+                            deviceSupport.getContext(),
+                            deviceSupport.getDevice(),
+                            GBLocationProviderType.GPS,
+                            1000 // TODO from realtimeRequest
+                    );
+                } else {
+                    GBLocationService.stop(deviceSupport.getContext(), deviceSupport.getDevice());
+                }
+            }
+
+            return GdiSmartProto.Smart.newBuilder().setCoreService(
+                    GdiCore.CoreService.newBuilder().setLocationUpdatedSetEnabledResponse(response)).build();
+        }
+
+        LOG.warn("Unknown CoreService request: {}", coreService);
+        return null;
+    }
+
+    private GdiSmartProto.Smart processProtobufNotificationsServiceMessage(final GdiNotificationsService.NotificationsService notificationsService) {
+        if (notificationsService.hasPictureRequest()) {
+            final GdiNotificationsService.PictureRequest pictureRequest = notificationsService.getPictureRequest();
+            final int notificationId = pictureRequest.getNotificationId();
+            final Bitmap bmp = deviceSupport.getNotificationAttachmentBitmap(notificationId);
+            if (bmp == null) {
+                return null;
+            }
+
+            final GdiNotificationsService.PictureParameters parameters = pictureRequest.getParameters();
+            final int targetHeight = (int) Math.round(bmp.getHeight() * ((double) parameters.getWidth() / bmp.getWidth()));
+
+            final Bitmap scaledBmp = Bitmap.createScaledBitmap(bmp, parameters.getWidth(), targetHeight, true);
+            final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            scaledBmp.compress(Bitmap.CompressFormat.JPEG, parameters.getQuality(), baos);
+            final byte[] imageBytes = baos.toByteArray();
+
+            final int transferId = DataTransferHandler.registerData(imageBytes);
+
+            final GdiNotificationsService.PictureResponse response = GdiNotificationsService.PictureResponse.newBuilder()
+                    .setUnk1(1)
+                    .setNotificationId(notificationId)
+                    .setUnk3(0)
+                    .setUnk4(1)
+                    .setDataTransferItem(
+                            GdiNotificationsService.DataTransferItem.newBuilder()
+                                    .setId(transferId)
+                                    .setSize(imageBytes.length)
+                                    .build()
+                    )
+                    .build();
+            return GdiSmartProto.Smart.newBuilder().setNotificationsService(
+                    GdiNotificationsService.NotificationsService.newBuilder().setPictureResponse(response)
+            ).build();
+        }
+
+        LOG.warn("Protobuf notificationsService request not implemented: {}", notificationsService);
+        return null;
+    }
+
+    private GdiSmartProto.Smart processProtobufSmsNotificationMessage(GdiSmsNotification.SmsNotificationService smsNotificationService) {
+        if (smsNotificationService.hasSmsCannedListRequest()) {
+            LOG.debug("Got request for sms canned list");
+
+            // Mark canned messages as supported
+            deviceSupport.evaluateGBDeviceEvent(new GBDeviceEventUpdatePreferences(GarminPreferences.PREF_FEAT_CANNED_MESSAGES, true));
+
+            List<GdiSmsNotification.SmsNotificationService.CannedListType> requestedTypes = smsNotificationService.getSmsCannedListRequest().getRequestedTypesList();
+
+            populateCannedListTypeMap(requestedTypes);
+
+            GdiSmsNotification.SmsNotificationService.SmsCannedListResponse.Builder builder = GdiSmsNotification.SmsNotificationService.SmsCannedListResponse.newBuilder()
+                    .setStatus(GdiSmsNotification.SmsNotificationService.ResponseStatus.SUCCESS);
+            boolean found = false;
+            for (GdiSmsNotification.SmsNotificationService.CannedListType requestedType : requestedTypes) {
+                if (this.cannedListTypeMap.containsKey(requestedType)) {
+                    found = true;
+                    builder.addLists(GdiSmsNotification.SmsNotificationService.SmsCannedList.newBuilder()
+                            .addAllResponse(Arrays.asList(Objects.requireNonNull(this.cannedListTypeMap.get(requestedType))))
+                            .setType(requestedType)
+                    );
+                } else {
+                    LOG.warn("Missing canned messages data for type {}", requestedType);
+                }
+            }
+            if (!found)
+                builder.setStatus(GdiSmsNotification.SmsNotificationService.ResponseStatus.GENERIC_ERROR);
+
+            return GdiSmartProto.Smart.newBuilder().setSmsNotificationService(GdiSmsNotification.SmsNotificationService.newBuilder().setSmsCannedListResponse(builder)).build();
+        } else {
+            LOG.warn("Protobuf smsNotificationService request not implemented: {}", smsNotificationService);
+            return null;
+        }
+    }
+
+    private void populateCannedListTypeMap(List<GdiSmsNotification.SmsNotificationService.CannedListType> requestedTypes) {
+        if (this.cannedListTypeMap.isEmpty()) {
+            for (GdiSmsNotification.SmsNotificationService.CannedListType type :
+                    requestedTypes) {
+                String preferencesPrefix = "";
+                if (GdiSmsNotification.SmsNotificationService.CannedListType.SMS_MESSAGE_RESPONSE.equals(type))
+                    preferencesPrefix = "canned_reply_";
+                else if (GdiSmsNotification.SmsNotificationService.CannedListType.PHONE_CALL_RESPONSE.equals(type))
+                    preferencesPrefix = "canned_message_dismisscall_";
+                else
+                    continue;
+                final ArrayList<String> messages = new ArrayList<>();
+                for (int i = 1; i <= 16; i++) {
+                    String message = deviceSupport.getDevicePrefs().getString(preferencesPrefix + i, null);
+                    if (message != null && !message.isEmpty()) {
+                        messages.add(message);
+                    }
+                }
+                if (!messages.isEmpty())
+                    this.cannedListTypeMap.put(type, messages.toArray(new String[0]));
+            }
+        }
+    }
+
+    private void processProtobufFindMyWatchResponse(GdiFindMyWatch.FindMyWatchService findMyWatchService) {
+        if (findMyWatchService.hasCancelRequest()) {
+            LOG.info("Watch found");
+        }
+        if (findMyWatchService.hasCancelResponse() || findMyWatchService.hasFindResponse()) {
+            LOG.debug("Received findMyWatch response");
+        }
+        LOG.warn("Unknown FindMyWatchService response: {}", findMyWatchService);
+    }
+
+    private boolean processProtobufSettingsService(final GdiSettingsService.SettingsService settingsService) {
+        boolean processed = false;
+
+        if (settingsService.hasDefinitionResponse()) {
+            processed = true;
+            final Intent intent = new Intent(GarminRealtimeSettingsFragment.ACTION_SCREEN_DEFINITION);
+            intent.putExtra(GarminRealtimeSettingsFragment.EXTRA_PROTOBUF, settingsService.getDefinitionResponse().getDefinition().toByteArray());
+            LocalBroadcastManager.getInstance(deviceSupport.getContext()).sendBroadcast(intent);
+        }
+
+        if (settingsService.hasStateResponse()) {
+            processed = true;
+            final Intent intent = new Intent(GarminRealtimeSettingsFragment.ACTION_SCREEN_STATE);
+            intent.putExtra(GarminRealtimeSettingsFragment.EXTRA_PROTOBUF, settingsService.getStateResponse().getState().toByteArray());
+            LocalBroadcastManager.getInstance(deviceSupport.getContext()).sendBroadcast(intent);
+        }
+
+        if (settingsService.hasChangeResponse()) {
+            processed = true;
+            final Intent intent = new Intent(GarminRealtimeSettingsFragment.ACTION_CHANGE);
+            intent.putExtra(GarminRealtimeSettingsFragment.EXTRA_PROTOBUF, settingsService.getChangeResponse().toByteArray());
+            LocalBroadcastManager.getInstance(deviceSupport.getContext()).sendBroadcast(intent);
+        }
+
+        return processed;
+    }
+
+    public ProtobufMessage prepareProtobufRequest(GdiSmartProto.Smart protobufPayload) {
+        if (null == protobufPayload)
+            return null;
+        final int requestId = getNextProtobufRequestId();
+        return prepareProtobufMessage(protobufPayload.toByteArray(), GFDIMessage.GarminMessage.PROTOBUF_REQUEST, requestId);
+    }
+
+    private ProtobufMessage prepareProtobufResponse(GdiSmartProto.Smart protobufPayload, int requestId) {
+        if (null == protobufPayload)
+            return null;
+        return prepareProtobufMessage(protobufPayload.toByteArray(), GFDIMessage.GarminMessage.PROTOBUF_RESPONSE, requestId);
+    }
+
+    private ProtobufMessage prepareProtobufMessage(byte[] bytes, GFDIMessage.GarminMessage garminMessage, int requestId) {
+        if (bytes == null || bytes.length == 0)
+            return null;
+        LOG.info("Preparing protobuf message. Type {}, #{}, {}B: {}", garminMessage, requestId, bytes.length, GB.hexdump(bytes, 0, bytes.length));
+
+        if (bytes.length > maxChunkSize) {
+            chunkedFragmentsMap.put(requestId, new ProtobufFragment(bytes));
+            return new ProtobufMessage(garminMessage,
+                    requestId,
+                    0,
+                    bytes.length,
+                    maxChunkSize,
+                    ArrayUtils.subarray(bytes, 0, maxChunkSize));
+        }
+        return new ProtobufMessage(garminMessage, requestId, 0, bytes.length, bytes.length, bytes);
+    }
+
+    public ProtobufMessage setCannedMessages(CannedMessagesSpec cannedMessagesSpec) {
+        final GdiSmsNotification.SmsNotificationService.CannedListType cannedListType;
+        switch (cannedMessagesSpec.type) {
+            case CannedMessagesSpec.TYPE_REJECTEDCALLS:
+                cannedListType = GdiSmsNotification.SmsNotificationService.CannedListType.PHONE_CALL_RESPONSE;
+                break;
+            case CannedMessagesSpec.TYPE_GENERIC:
+            case CannedMessagesSpec.TYPE_NEWSMS:
+                cannedListType = GdiSmsNotification.SmsNotificationService.CannedListType.SMS_MESSAGE_RESPONSE;
+                break;
+            default:
+                LOG.warn("Unknown canned messages type, ignoring.");
+                return null;
+        }
+
+        this.cannedListTypeMap.put(cannedListType, cannedMessagesSpec.cannedMessages);
+
+        GdiSmartProto.Smart smart = GdiSmartProto.Smart.newBuilder()
+                .setSmsNotificationService(GdiSmsNotification.SmsNotificationService.newBuilder()
+                        .setSmsCannedListChangedNotification(
+                                GdiSmsNotification.SmsNotificationService.SmsCannedListChangedNotification.newBuilder().addChangedType(cannedListType)
+                        )
+                ).build();
+
+        return prepareProtobufRequest(smart);
+    }
+
+    private class ProtobufFragment {
+        private final byte[] fragmentBytes;
+        private final int totalLength;
+
+        public ProtobufFragment(byte[] fragmentBytes) {
+            this.fragmentBytes = fragmentBytes;
+            this.totalLength = fragmentBytes.length;
+        }
+
+        public ProtobufFragment(ProtobufMessage message) {
+            if (message.getDataOffset() != 0)
+                throw new IllegalArgumentException("Cannot create fragment if message is not the first of the sequence");
+            this.fragmentBytes = message.getMessageBytes();
+            this.totalLength = message.getTotalProtobufLength();
+        }
+
+        public ProtobufFragment(ProtobufFragment existing, ProtobufMessage toMerge) {
+            if (toMerge.getDataOffset() != existing.fragmentBytes.length)
+                throw new IllegalArgumentException("Cannot merge fragment: incoming message has different offset than needed");
+            this.fragmentBytes = ArrayUtils.addAll(existing.fragmentBytes, toMerge.getMessageBytes());
+            this.totalLength = existing.totalLength;
+        }
+
+        public ProtobufMessage getNextChunk(ProtobufStatusMessage protobufStatusMessage) {
+            int start = protobufStatusMessage.getDataOffset() + maxChunkSize;
+            int length = Math.min(maxChunkSize, this.fragmentBytes.length - start);
+
+            return new ProtobufMessage(protobufStatusMessage.getMessageType(),
+                    protobufStatusMessage.getRequestId(),
+                    start,
+                    this.totalLength,
+                    length,
+                    ArrayUtils.subarray(this.fragmentBytes, start, start + length));
+        }
+
+        public boolean isComplete() {
+            return totalLength == fragmentBytes.length;
+        }
+    }
+
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/agps/AgpsHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/agps/AgpsHandler.java
new file mode 100644
index 0000000..ef86c64
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/agps/AgpsHandler.java
@@ -0,0 +1,143 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.agps;
+
+import androidx.documentfile.provider.DocumentFile;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.Objects;
+import java.util.concurrent.Callable;
+
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdatePreferences;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminPreferences;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.GarminSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.http.GarminHttpRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.http.GarminHttpResponse;
+import nodomain.freeyourgadget.gadgetbridge.util.CheckSums;
+import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+public class AgpsHandler {
+    private static final Logger LOG = LoggerFactory.getLogger(AgpsHandler.class);
+    private static final String QUERY_CONSTELLATIONS = "constellations";
+    private final GarminSupport deviceSupport;
+
+    public AgpsHandler(GarminSupport deviceSupport) {
+        this.deviceSupport = deviceSupport;
+    }
+
+    public GarminHttpResponse handleAgpsRequest(final GarminHttpRequest request) {
+        saveKnownUrl(request.getUrl());
+
+        try {
+            final DocumentFile agpsFile = deviceSupport.getAgpsFile(request.getUrl());
+            if (agpsFile == null) {
+                LOG.warn("File with AGPS data for {} does not exist.", request.getUrl());
+                return null;
+            }
+            try (InputStream agpsIn = deviceSupport.getContext().getContentResolver().openInputStream(agpsFile.getUri())) {
+                if (agpsIn == null) {
+                    LOG.error("Failed to open input stream for agps file {}", agpsFile.getUri());
+                    return null;
+                }
+
+                final GarminHttpResponse response = new GarminHttpResponse();
+
+                final byte[] rawBytes = FileUtils.readAll(agpsIn, 1024 * 1024); // 1MB, they're usually ~60KB
+                final String fileHash = GB.hexdump(CheckSums.md5(rawBytes)).toLowerCase(Locale.ROOT);
+                final String etag = "\"" + fileHash + "\"";
+                response.getHeaders().put("etag", etag);
+
+                if (request.getHeaders().containsKey("if-none-match")) {
+                    // Check checksum
+                    final String ifNoneMatch = request.getHeaders().get("if-none-match");
+                    LOG.debug("agps request hash = {}, file hash = {}", ifNoneMatch, etag);
+
+                    if (etag.equals(ifNoneMatch)) {
+                        response.setBody(new byte[0]);
+                        response.setStatus(304);
+                        return response;
+                    }
+                }
+                response.getHeaders().put("cache-control", "max-age=14400");
+
+                // Run some sanity checks on known agps file formats
+                final GarminAgpsFile garminAgpsFile = new GarminAgpsFile(rawBytes);
+                if (request.getQuery().containsKey(QUERY_CONSTELLATIONS)) {
+                    final String[] requestedConstellations = Objects.requireNonNull(request.getQuery().get(QUERY_CONSTELLATIONS)).split(",");
+                    if (!garminAgpsFile.isValidTar(requestedConstellations)) {
+                        reportError(request.getUrl());
+                        return null;
+                    }
+                } else if (request.getPath().contains(("/rxnetworks/"))) {
+                    if (!garminAgpsFile.isValidRxNetworks()) {
+                        reportError(request.getUrl());
+                        return null;
+                    }
+                } else if (request.getPath().startsWith(("/ephemeris/cpe/sony"))) {
+                    if (!garminAgpsFile.isValidSonyCpe()) {
+                        reportError(request.getUrl());
+                        return null;
+                    }
+                } else {
+                    LOG.warn("Refusing to send agps for unknown url");
+                    return null;
+                }
+
+                LOG.info("Sending new AGPS data (length: {}) to the device from {}", rawBytes.length, agpsFile.getUri());
+
+                if (request.getHeaders().containsKey("accept")) {
+                    response.getHeaders().put("Content-Type", request.getHeaders().get("accept"));
+                } else {
+                    response.getHeaders().put("Content-Type", "application/octet-stream");
+                }
+
+                response.setStatus(200);
+                response.setBody(rawBytes);
+                response.setOnDataSuccessfullySentListener(getOnDataSuccessfullySentListener(request.getUrl()));
+
+                return response;
+            }
+        } catch (final IOException e) {
+            LOG.error("Unable to obtain AGPS data", e);
+            reportError(request.getUrl());
+            return null;
+        }
+    }
+
+    public void saveKnownUrl(final String url) {
+        final Prefs devicePrefs = deviceSupport.getDevicePrefs();
+        final List<String> knownAgpsUrls = new ArrayList<>(devicePrefs.getList(GarminPreferences.PREF_AGPS_KNOWN_URLS, Collections.emptyList(), "\n"));
+        if (!knownAgpsUrls.contains(url)) {
+            knownAgpsUrls.add(url);
+            devicePrefs.getPreferences().edit()
+                    .putString(GarminPreferences.PREF_AGPS_KNOWN_URLS, String.join("\n", knownAgpsUrls))
+                    .apply();
+        }
+    }
+
+    private void reportError(final String url) {
+        deviceSupport.evaluateGBDeviceEvent(new GBDeviceEventUpdatePreferences(
+                GarminPreferences.agpsStatus(url), GarminAgpsStatus.ERROR.name()
+        ));
+    }
+
+    public Callable<Void> getOnDataSuccessfullySentListener(final String urlString) {
+        return () -> {
+            LOG.info("AGPS data successfully sent to the device.");
+            deviceSupport.evaluateGBDeviceEvent(new GBDeviceEventUpdatePreferences()
+                    .withPreference(GarminPreferences.agpsStatus(urlString), GarminAgpsStatus.CURRENT.name())
+                    .withPreference(GarminPreferences.agpsUpdateTime(urlString), Instant.now().toEpochMilli())
+            );
+            return null;
+        };
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/agps/GarminAgpsDataType.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/agps/GarminAgpsDataType.java
new file mode 100644
index 0000000..1ca3e33
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/agps/GarminAgpsDataType.java
@@ -0,0 +1,25 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.agps;
+
+public enum GarminAgpsDataType {
+    GLONASS("CPE_GLO.BIN"), QZSS("CPE_QZSS.BIN"), GPS("CPE_GPS.BIN"),
+    GALILEO("CPE_GAL.BIN");
+
+    private final String fileName;
+
+    GarminAgpsDataType(String fileName) {
+        this.fileName = fileName;
+    }
+
+    public String getFileName() {
+        return fileName;
+    }
+
+    public static boolean isValidAgpsDataFileName(String fileName) {
+        for (GarminAgpsDataType type: GarminAgpsDataType.values()) {
+            if (fileName.equals(type.fileName)) {
+                return true;
+            }
+        }
+        return false;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/agps/GarminAgpsFile.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/agps/GarminAgpsFile.java
new file mode 100644
index 0000000..a8a566b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/agps/GarminAgpsFile.java
@@ -0,0 +1,97 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.agps;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Arrays;
+import java.util.zip.GZIPInputStream;
+
+import nodomain.freeyourgadget.gadgetbridge.util.ArrayUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+import nodomain.freeyourgadget.gadgetbridge.util.GBTarFile;
+
+
+public class GarminAgpsFile {
+    private static final Logger LOG = LoggerFactory.getLogger(GarminAgpsFile.class);
+    private final byte[] bytes;
+
+    public static final byte[] GZ_HEADER = new byte[]{(byte) 0x1f, (byte) 0x8b};
+    public static final byte[] CPE_RXNETWORKS_HEADER = new byte[]{(byte) 0x01, (byte) 0x00};
+    public static final byte[] CPE_SONY_HEADER = new byte[]{(byte) 0x2a, (byte) 0x12, (byte) 0xa0, (byte) 0x02};
+
+    public GarminAgpsFile(final byte[] bytes) {
+        this.bytes = bytes;
+    }
+
+    public boolean isValidTar(final String[] constellations) {
+        if (!GBTarFile.isTarFile(bytes)) {
+            return false;
+        }
+
+        final GBTarFile tarFile = new GBTarFile(bytes);
+        for (final String constellation : constellations) {
+            try {
+                final GarminAgpsDataType garminAgpsDataType = GarminAgpsDataType.valueOf(constellation);
+                if (!tarFile.containsFile(garminAgpsDataType.getFileName())) {
+                    LOG.error("AGPS archive is missing requested file: {}", garminAgpsDataType.getFileName());
+                    return false;
+                }
+            } catch (final IllegalArgumentException e) {
+                LOG.error("Device requested unsupported AGPS data type: {}", constellation);
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    public boolean isValidRxNetworks() {
+        if (!ArrayUtils.startsWith(bytes, GZ_HEADER)) {
+            return false;
+        }
+
+        try (GZIPInputStream gzis = new GZIPInputStream(new ByteArrayInputStream(bytes))) {
+            final byte[] header = new byte[CPE_RXNETWORKS_HEADER.length];
+            int read = gzis.read(header);
+            if (read != CPE_RXNETWORKS_HEADER.length || !Arrays.equals(header, CPE_RXNETWORKS_HEADER)) {
+                LOG.error("Header in gz file is not agps rxnetworks: {}", GB.hexdump(header));
+                return false;
+            }
+            final byte[] timestampBytes = new byte[4];
+            read = gzis.read(timestampBytes);
+            if (read != timestampBytes.length) {
+                LOG.error("Failed to read rxnetworks timestamp");
+                return false;
+            }
+            final int agpsTimestamp = ByteBuffer.wrap(timestampBytes).order(ByteOrder.BIG_ENDIAN).getInt();
+            final int currentTimestamp = (int) (System.currentTimeMillis() / 1000L);
+            final int agpsAge = currentTimestamp - agpsTimestamp;
+            if (agpsAge < 0) {
+                LOG.error("rxnetworks AGPS timestamp {} is in the future", agpsTimestamp);
+                return false;
+            }
+            if (agpsAge > 604800) {
+                LOG.error("rxnetworks AGPS timestamp {} is older than 7 days", agpsTimestamp);
+                return false;
+            }
+            LOG.debug("rx networks AGPS age: {}", agpsAge);
+            return true;
+        } catch (final IOException e) {
+            LOG.error("Failed to decompress file as gzip", e);
+        }
+
+        return false;
+    }
+
+    public boolean isValidSonyCpe() {
+        return ArrayUtils.startsWith(bytes, CPE_SONY_HEADER);
+    }
+
+    public byte[] getBytes() {
+        return bytes.clone();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/agps/GarminAgpsStatus.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/agps/GarminAgpsStatus.java
new file mode 100644
index 0000000..370c315
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/agps/GarminAgpsStatus.java
@@ -0,0 +1,23 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.agps;
+
+import androidx.annotation.StringRes;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+
+
+public enum GarminAgpsStatus {
+    MISSING(R.string.agps_status_missing), // AGPS data file was not yet installed
+    PENDING(R.string.agps_status_pending), // AGPS data file is waiting for installation
+    CURRENT(R.string.agps_status_current), // AGPS data was successfully installed
+    ERROR(R.string.agps_status_error);     // Unable to install AGPS data file
+
+    private final @StringRes int text;
+
+    GarminAgpsStatus(@StringRes int text) {
+        this.text = text;
+    }
+
+    public @StringRes int getText() {
+        return text;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/communicator/CobsCoDec.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/communicator/CobsCoDec.java
new file mode 100644
index 0000000..9d4a002
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/communicator/CobsCoDec.java
@@ -0,0 +1,128 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.communicator;
+
+import java.nio.ByteBuffer;
+
+public class CobsCoDec {
+    private static final long BUFFER_TIMEOUT = 1500L; // turn this value up while debugging
+    private final ByteBuffer byteBuffer = ByteBuffer.allocate(10_000);
+    private long lastUpdate;
+    private byte[] cobsDecodedMessage;
+
+    /**
+     * Accumulates received bytes in a local buffer, clearing it after a timeout, and attempts to
+     * parse it.
+     *
+     * @param bytes
+     */
+    public void receivedBytes(byte[] bytes) {
+        final long now = System.currentTimeMillis();
+        if ((now - lastUpdate) > BUFFER_TIMEOUT) {
+            reset();
+        }
+        lastUpdate = now;
+
+        byteBuffer.put(bytes);
+        decode();
+    }
+
+    private void reset() {
+        cobsDecodedMessage = null;
+        byteBuffer.clear();
+    }
+
+    public byte[] retrieveMessage() {
+        final byte[] resultPacket = cobsDecodedMessage;
+        cobsDecodedMessage = null;
+        return resultPacket;
+    }
+
+    /**
+     * COBS decoding algorithm variant, which relies on a leading and a trailing 0 byte (the former
+     * is not part of default implementations).
+     * This function removes the complete message from the internal buffer, if it could be decoded.
+     */
+    private void decode() {
+        if (cobsDecodedMessage != null) {
+            // packet is waiting, unable to parse more
+            return;
+        }
+        if (byteBuffer.position() < 4) {
+            // minimal payload length including the padding
+            return;
+        }
+        if (0 != byteBuffer.get(byteBuffer.position() - 1))
+            return; //no 0x00 at the end, hence no full packet
+        byteBuffer.position(byteBuffer.position() - 1); //don't process the trailing 0
+        byteBuffer.flip();
+        if (0 != byteBuffer.get())
+            return; //no 0x00 at the start
+        ByteBuffer decodedBytesBuffer = ByteBuffer.allocate(byteBuffer.limit()); //leading and trailing 0x00 bytes
+        while (byteBuffer.hasRemaining()) {
+            byte code = byteBuffer.get();
+            if (code == 0) {
+                break;
+            }
+            int codeValue = code & 0xFF;
+            int payloadSize = codeValue - 1;
+            for (int i = 0; i < payloadSize; i++) {
+                decodedBytesBuffer.put(byteBuffer.get());
+            }
+            if (codeValue != 0xFF && byteBuffer.hasRemaining()) {
+                decodedBytesBuffer.put((byte) 0); // Append a zero byte after the payload
+            }
+        }
+
+        decodedBytesBuffer.flip();
+        cobsDecodedMessage = new byte[decodedBytesBuffer.remaining()];
+        decodedBytesBuffer.get(cobsDecodedMessage);
+        byteBuffer.compact();
+    }
+
+    // this implementation of COBS relies on a leading and a trailing 0 byte (the former is not part of default implementations)
+    public byte[] encode(byte[] data) {
+        ByteBuffer encodedBytesBuffer = ByteBuffer.allocate((data.length * 2) + 1); // Maximum expansion
+
+        encodedBytesBuffer.put((byte) 0);// Garmin initial padding
+        ByteBuffer buffer = ByteBuffer.wrap(data);
+
+        while (buffer.position() < buffer.limit()) {
+            int startPos = buffer.position();
+            int zeroIndex = buffer.position();
+
+            while (buffer.hasRemaining() && buffer.get() != 0) {
+                zeroIndex++;
+            }
+
+            int payloadSize = zeroIndex - startPos;
+
+            while (payloadSize >= 0xFE) {
+                encodedBytesBuffer.put((byte) 0xFF); // Maximum payload size indicator
+                encodedBytesBuffer.put(data, startPos, 0xFE);
+                payloadSize -= 0xFE;
+                startPos += 0xFE;
+            }
+
+            encodedBytesBuffer.put((byte) (payloadSize + 1));
+            encodedBytesBuffer.put(data, startPos, payloadSize);
+
+            if (buffer.hasRemaining()) {
+                zeroIndex++; // Include the zero byte in the next block
+            }
+
+            if (!buffer.hasRemaining() && payloadSize == 0) {
+                break;
+            }
+
+            buffer.position(zeroIndex);
+        }
+
+        encodedBytesBuffer.put((byte) 0); // Append a zero byte to indicate end of encoding
+        encodedBytesBuffer.flip();
+
+        byte[] encodedBytes = new byte[encodedBytesBuffer.remaining()];
+        encodedBytesBuffer.get(encodedBytes);
+
+        return encodedBytes;
+    }
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/communicator/ICommunicator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/communicator/ICommunicator.java
new file mode 100644
index 0000000..9ebbeaa
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/communicator/ICommunicator.java
@@ -0,0 +1,26 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.communicator;
+
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+
+public interface ICommunicator {
+    void sendMessage(String taskName, byte[] message);
+
+    void onMtuChanged(final int mtu);
+
+    boolean initializeDevice(TransactionBuilder builder);
+
+    boolean onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic);
+
+    void onHeartRateTest();
+
+    void onEnableRealtimeHeartRateMeasurement(final boolean enable);
+
+    void onEnableRealtimeSteps(final boolean enable);
+
+    interface Callback {
+        void onMessage(byte[] message);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/communicator/v1/CommunicatorV1.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/communicator/v1/CommunicatorV1.java
new file mode 100644
index 0000000..aff9304
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/communicator/v1/CommunicatorV1.java
@@ -0,0 +1,119 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.communicator.v1;
+
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Arrays;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.GarminSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.communicator.CobsCoDec;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.communicator.ICommunicator;
+
+public class CommunicatorV1 implements ICommunicator {
+    private static final Logger LOG = LoggerFactory.getLogger(CommunicatorV1.class);
+
+    // Seen in Vivovit / Forerunner 620
+    public static final UUID UUID_SERVICE_GARMIN_GFDI_V0 = UUID.fromString("9B012401-BC30-CE9A-E111-0F67E491ABDE");
+    public static final UUID UUID_CHARACTERISTIC_GARMIN_GFDI_V0_SEND = UUID.fromString("DF334C80-E6A7-D082-274D-78FC66F85E16");
+    public static final UUID UUID_CHARACTERISTIC_GARMIN_GFDI_V0_RECEIVE = UUID.fromString("4ACBCD28-7425-868E-F447-915C8F00D0CB");
+
+    // Seen in Vivomove HR
+    public static final UUID UUID_SERVICE_GARMIN_GFDI_V1 = UUID.fromString("6A4E2401-667B-11E3-949A-0800200C9A66");
+    public static final UUID UUID_CHARACTERISTIC_GARMIN_GFDI_V1_SEND = UUID.fromString("6A4E4C80-667B-11E3-949A-0800200C9A66");
+    public static final UUID UUID_CHARACTERISTIC_GARMIN_GFDI_V1_RECEIVE = UUID.fromString("6A4ECD28-667B-11E3-949A-0800200C9A66");
+
+    private BluetoothGattCharacteristic characteristicSend;
+    private BluetoothGattCharacteristic characteristicReceive;
+
+    private final GarminSupport mSupport;
+
+    public final CobsCoDec cobsCoDec;
+    public int maxWriteSize = 20;
+
+    public CommunicatorV1(final GarminSupport garminSupport) {
+        this.mSupport = garminSupport;
+        this.cobsCoDec = new CobsCoDec();
+    }
+
+    @Override
+    public void onMtuChanged(final int mtu) {
+        maxWriteSize = mtu - 3;
+    }
+
+    @Override
+    public boolean initializeDevice(final TransactionBuilder builder) {
+        characteristicSend = mSupport.getCharacteristic(UUID_CHARACTERISTIC_GARMIN_GFDI_V1_SEND);
+        characteristicReceive = mSupport.getCharacteristic(UUID_CHARACTERISTIC_GARMIN_GFDI_V1_RECEIVE);
+
+        if (characteristicSend == null || characteristicReceive == null) {
+            characteristicSend = mSupport.getCharacteristic(UUID_CHARACTERISTIC_GARMIN_GFDI_V0_SEND);
+            characteristicReceive = mSupport.getCharacteristic(UUID_CHARACTERISTIC_GARMIN_GFDI_V0_RECEIVE);
+        }
+
+        if (characteristicSend == null || characteristicReceive == null) {
+            LOG.warn("Failed to find V0/V1 GFDI characteristics");
+            return false;
+        }
+
+        builder.notify(characteristicReceive, true);
+
+        LOG.debug("Initializing as Garmin V1");
+
+        return true;
+    }
+
+    @Override
+    public void sendMessage(final String taskName, final byte[] message) {
+        if (null == message)
+            return;
+
+        final byte[] payload = cobsCoDec.encode(message);
+
+        final TransactionBuilder builder = new TransactionBuilder(taskName);
+        int remainingBytes = payload.length;
+        if (remainingBytes > maxWriteSize - 1) {
+            int position = 0;
+            while (remainingBytes > 0) {
+                final byte[] fragment = Arrays.copyOfRange(payload, position, position + Math.min(remainingBytes, maxWriteSize - 1));
+                builder.write(characteristicSend, fragment);
+                position += fragment.length;
+                remainingBytes -= fragment.length;
+            }
+        } else {
+            builder.write(characteristicSend, payload);
+        }
+        builder.queue(this.mSupport.getQueue());
+    }
+
+    @Override
+    public boolean onCharacteristicChanged(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic) {
+        if (characteristic.getUuid().equals(characteristicReceive.getUuid())) {
+            this.cobsCoDec.receivedBytes(characteristic.getValue());
+            this.mSupport.onMessage(this.cobsCoDec.retrieveMessage());
+
+            return true;
+        }
+
+        return false;
+    }
+
+    @Override
+    public void onHeartRateTest() {
+        LOG.error("onHeartRateTest is not implemented for V1");
+    }
+
+    @Override
+    public void onEnableRealtimeHeartRateMeasurement(final boolean enable) {
+        LOG.error("onEnableRealtimeHeartRateMeasurement is not implemented for V1");
+    }
+
+    @Override
+    public void onEnableRealtimeSteps(final boolean enable) {
+        LOG.error("onEnableRealtimeSteps is not implemented for V1");
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/communicator/v2/CommunicatorV2.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/communicator/v2/CommunicatorV2.java
new file mode 100644
index 0000000..3c4c853
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/communicator/v2/CommunicatorV2.java
@@ -0,0 +1,500 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.communicator.v2;
+
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.content.Intent;
+
+import androidx.annotation.Nullable;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import org.apache.commons.lang3.ArrayUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminActivitySampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.entities.GarminActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.entities.User;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.GarminSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.GarminTimeUtils;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.communicator.CobsCoDec;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.communicator.ICommunicator;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class CommunicatorV2 implements ICommunicator {
+    private static final Logger LOG = LoggerFactory.getLogger(CommunicatorV2.class);
+
+    public static final String BASE_UUID = "6A4E%04X-667B-11E3-949A-0800200C9A66";
+    public static final UUID UUID_SERVICE_GARMIN_ML_GFDI = UUID.fromString(String.format(BASE_UUID, 0x2800));
+
+    private static final long GADGETBRIDGE_CLIENT_ID = 2L;
+
+    private BluetoothGattCharacteristic characteristicSend;
+    private BluetoothGattCharacteristic characteristicReceive;
+
+    private final GarminSupport mSupport;
+
+    private int gfdiHandle = 0;
+    public int maxWriteSize = 20;
+    public final CobsCoDec cobsCoDec;
+
+    private int realtimeHrHandle = 0;
+    private boolean realtimeHrOneShot = false;
+
+    private int realtimeStepsHandle = 0;
+    private int previousSteps = -1;
+
+    private int realtimeAccelHandle = 0;
+
+    private int realtimeSpo2Handle = 0;
+    private int realtimeRespirationHandle = 0;
+    private int realtimeHrvHandle = 0;
+
+    public CommunicatorV2(final GarminSupport garminSupport) {
+        this.mSupport = garminSupport;
+        this.cobsCoDec = new CobsCoDec();
+    }
+
+    @Override
+    public void onMtuChanged(final int mtu) {
+        maxWriteSize = mtu - 3;
+    }
+
+    @Override
+    public boolean initializeDevice(final TransactionBuilder builder) {
+        // Iterate through the known ML characteristics until we find a known pair
+        // send characteristic = read characteristic + 0x10 (eg. 2810 / 2820)
+        for (int i = 0x2810; i <= 0x2814; i++) {
+            characteristicReceive = mSupport.getCharacteristic(UUID.fromString(String.format(BASE_UUID, i)));
+            characteristicSend = mSupport.getCharacteristic(UUID.fromString(String.format(BASE_UUID, i + 0x10)));
+
+            if (characteristicSend != null && characteristicReceive != null) {
+                LOG.debug("Using characteristics receive/send = {}/{}", characteristicReceive.getUuid(), characteristicSend.getUuid());
+
+                builder.notify(characteristicReceive, true);
+                builder.write(characteristicSend, closeAllServices());
+
+                return true;
+            }
+        }
+
+        LOG.warn("Failed to find any known ML characteristics");
+
+        return false;
+    }
+
+    @Override
+    public void sendMessage(final String taskName, final byte[] message) {
+        if (null == message)
+            return;
+        if (0 == gfdiHandle) {
+            LOG.error("CANNOT SENT GFDI MESSAGE, HANDLE NOT YET SET. MESSAGE {}", message);
+            return;
+        }
+        final byte[] payload = cobsCoDec.encode(message);
+//        LOG.debug("SENDING MESSAGE: {} - COBS ENCODED: {}", GB.hexdump(message), GB.hexdump(payload));
+        final TransactionBuilder builder = new TransactionBuilder(taskName);
+        int remainingBytes = payload.length;
+        if (remainingBytes > maxWriteSize - 1) {
+            int position = 0;
+            while (remainingBytes > 0) {
+                final byte[] fragment = Arrays.copyOfRange(payload, position, position + Math.min(remainingBytes, maxWriteSize - 1));
+                builder.write(characteristicSend, ArrayUtils.addAll(new byte[]{(byte) gfdiHandle}, fragment));
+                position += fragment.length;
+                remainingBytes -= fragment.length;
+            }
+        } else {
+            builder.write(characteristicSend, ArrayUtils.addAll(new byte[]{(byte) gfdiHandle}, payload));
+        }
+        builder.queue(this.mSupport.getQueue());
+    }
+
+    @Override
+    public boolean onCharacteristicChanged(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic) {
+        if (!characteristic.getUuid().equals(characteristicReceive.getUuid())) {
+            // Not ML
+            return false;
+        }
+
+        final ByteBuffer message = ByteBuffer.wrap(characteristic.getValue()).order(ByteOrder.LITTLE_ENDIAN);
+        final byte handle = message.get();
+
+        if (0x00 == handle) {
+            processHandleManagement(message);
+        } else if (this.gfdiHandle == handle) {
+            processGfdi(message);
+        } else if (this.realtimeHrHandle == handle) {
+            processRealtimeHeartRate(message);
+        } else if (this.realtimeStepsHandle == handle) {
+            processRealtimeSteps(message);
+        } else if (this.realtimeAccelHandle == handle) {
+            processRealtimeAccelerometer(message);
+        } else if (this.realtimeSpo2Handle == handle) {
+            processRealtimeSpo2(message);
+        } else if (this.realtimeRespirationHandle == handle) {
+            processRealtimeRespiration(message);
+        } else if (this.realtimeHrvHandle == handle) {
+            processRealtimeHrv(message);
+        } else {
+            LOG.warn("Got message for unknown handle {}: {}", handle, GB.hexdump(characteristic.getValue()));
+        }
+
+        return true;
+    }
+
+    @Override
+    public void onHeartRateTest() {
+        realtimeHrOneShot = true;
+        if (realtimeHrHandle == 0) {
+            new TransactionBuilder("heart rate test")
+                    .write(characteristicSend, registerService(Service.REALTIME_HR, false))
+                    .queue(this.mSupport.getQueue());
+        }
+    }
+
+    @Override
+    public void onEnableRealtimeHeartRateMeasurement(final boolean enable) {
+        toggleService(Service.REALTIME_HR, realtimeHrHandle, enable);
+    }
+
+    @Override
+    public void onEnableRealtimeSteps(final boolean enable) {
+        if (toggleService(Service.REALTIME_STEPS, realtimeStepsHandle, enable)) {
+            previousSteps = -1;
+        }
+    }
+
+    private boolean toggleService(final Service service, final int currentHandle, final boolean enable) {
+        if (enable && currentHandle == 0) {
+            new TransactionBuilder(service + " = true")
+                    .write(characteristicSend, registerService(service, false))
+                    .queue(this.mSupport.getQueue());
+            return true;
+        } else if (!enable && currentHandle != 0) {
+            new TransactionBuilder(service + " = false")
+                    .write(characteristicSend, closeService(service, currentHandle))
+                    .queue(this.mSupport.getQueue());
+            return true;
+        }
+
+        return false;
+    }
+
+    private void processHandleManagement(final ByteBuffer message) {
+        final byte type = message.get();
+        final long incomingClientID = message.getLong();
+
+        if (incomingClientID != GADGETBRIDGE_CLIENT_ID) {
+            LOG.warn("Ignoring incoming message, client ID {} is not ours. Message: {}", incomingClientID, GB.hexdump(message.array()));
+            return;
+        }
+
+        final RequestType requestType = RequestType.fromCode(type);
+        if (null == requestType) {
+            LOG.error("Unknown request type {}. Message: {}", type, message.array());
+            return;
+        }
+
+        switch (requestType) {
+            case REGISTER_ML_REQ:
+            case CLOSE_HANDLE_REQ:
+            case CLOSE_ALL_REQ:
+            case UNK_REQ:
+                LOG.warn("Received handle request, expecting responses. Message: {}", message.array());
+                return;
+            case REGISTER_ML_RESP: {
+                final short registeredServiceCode = message.getShort();
+                final Service registeredService = Service.fromCode(registeredServiceCode);
+                final byte status = message.get();
+                if (registeredService == null) {
+                    LOG.error("Got register response status={} for unknown service {}", status, registeredServiceCode);
+                    return;
+                }
+                if (status != 0) {
+                    LOG.warn("Failed to register {}, status={}", registeredService, status);
+                    return;
+                }
+                final int handle = message.get();
+                final int reliable = message.get();
+                LOG.debug("Got register response for {}, handle={}, reliable={}", registeredService, handle, reliable);
+
+                switch (registeredService) {
+                    case GFDI:
+                        this.gfdiHandle = handle;
+                        break;
+                    case REALTIME_HR:
+                        this.realtimeHrHandle = handle;
+                        break;
+                    case REALTIME_STEPS:
+                        this.realtimeStepsHandle = handle;
+                        break;
+                    case REALTIME_ACCELEROMETER:
+                        this.realtimeAccelHandle = handle;
+                        new TransactionBuilder("start realtime accel")
+                                .write(characteristicSend, new byte[]{(byte) handle, 0x01})
+                                .queue(this.mSupport.getQueue());
+                        break;
+                    case REALTIME_SPO2:
+                        this.realtimeSpo2Handle = handle;
+                        break;
+                    case REALTIME_RESPIRATION:
+                        this.realtimeRespirationHandle = handle;
+                        break;
+                    case REALTIME_HRV:
+                        this.realtimeHrvHandle = handle;
+                        break;
+                }
+                break;
+            }
+            case CLOSE_HANDLE_RESP: {
+                final short serviceCode = message.getShort();
+                final Service service = Service.fromCode(serviceCode);
+                final int handle = message.get();
+                final byte status = message.get();
+                LOG.debug("Received close handle response: service={}, handle={}, status={}", service, handle, status);
+                if (service != null) {
+                    switch (service) {
+                        case GFDI:
+                            this.gfdiHandle = 0;
+                            break;
+                        case REALTIME_HR:
+                            this.realtimeHrHandle = 0;
+                            break;
+                        case REALTIME_STEPS:
+                            this.realtimeStepsHandle = 0;
+                            break;
+                        case REALTIME_ACCELEROMETER:
+                            this.realtimeAccelHandle = 0;
+                            break;
+                        case REALTIME_SPO2:
+                            this.realtimeSpo2Handle = 0;
+                            break;
+                        case REALTIME_RESPIRATION:
+                            this.realtimeRespirationHandle = 0;
+                            break;
+                        case REALTIME_HRV:
+                            this.realtimeHrvHandle = 0;
+                            break;
+                    }
+                }
+                break;
+            }
+            case CLOSE_ALL_RESP:
+                LOG.debug("Received close all handles response. Message: {}", message.array());
+                this.gfdiHandle = 0;
+                this.realtimeHrHandle = 0;
+                this.realtimeStepsHandle = 0;
+                this.realtimeAccelHandle = 0;
+                this.realtimeSpo2Handle = 0;
+                this.realtimeRespirationHandle = 0;
+                this.realtimeHrvHandle = 0;
+                new TransactionBuilder("open GFDI")
+                        .write(characteristicSend, registerService(Service.GFDI, false))
+                        .queue(this.mSupport.getQueue());
+                break;
+            case UNK_RESP:
+                LOG.debug("Received unknown. Message: {}", message.array());
+                break;
+        }
+    }
+
+    private void processGfdi(final ByteBuffer message) {
+        final byte[] partial = new byte[message.remaining()];
+        message.get(partial);
+        this.cobsCoDec.receivedBytes(partial);
+
+        this.mSupport.onMessage(this.cobsCoDec.retrieveMessage());
+    }
+
+    private void processRealtimeHeartRate(final ByteBuffer buf) {
+        final byte type = buf.get(); // 0/2/3? 3 == realtime?
+        final int hr = buf.get();
+        final int resting = buf.get();
+        // ff ff after
+        LOG.debug("Got realtime HR: type={} hr={} resting={}", type, hr, resting);
+
+        if (hr > 0) {
+            broadcastRealtimeActivity(hr, -1);
+
+            if (realtimeHrOneShot && realtimeHrHandle != 0) {
+                onEnableRealtimeHeartRateMeasurement(false);
+            }
+        }
+    }
+
+    private void processRealtimeSteps(final ByteBuffer buf) {
+        final int steps = buf.getInt();
+        final int goal = buf.getInt();
+        LOG.debug("Got realtime steps: steps={} goal={}", steps, goal);
+
+        if (previousSteps == -1) {
+            previousSteps = steps;
+        }
+
+        broadcastRealtimeActivity(-1, steps - previousSteps);
+
+        previousSteps = steps;
+    }
+
+    private void processRealtimeAccelerometer(final ByteBuffer message) {
+        final byte[] partial = new byte[message.remaining()];
+        message.get(partial);
+
+        LOG.debug("Got realtime accel: {}", GB.hexdump(partial));
+    }
+
+    private void processRealtimeSpo2(final ByteBuffer message) {
+        final int spo2 = message.get(); // -1 when unknown, and the ts is not valid in that case
+        final int garminTs = message.getInt();
+
+        LOG.debug("Got realtime SpO2 at {}: {}", new Date(GarminTimeUtils.garminTimestampToJavaMillis(garminTs)), spo2);
+    }
+
+    private void processRealtimeRespiration(final ByteBuffer message) {
+        final int breathsPerMinute = message.get(); // can be negative if unknown, usually -2
+
+        LOG.debug("Got realtime respiration: {}", breathsPerMinute);
+    }
+
+    private void processRealtimeHrv(final ByteBuffer message) {
+        final short rr = message.getShort();
+        final int unk = message.getInt();
+
+        LOG.debug("Got realtime HRV: rr={}, unk={}", rr, unk);
+    }
+
+    private byte[] closeAllServices() {
+        final ByteBuffer toSend = ByteBuffer.allocate(13).order(ByteOrder.LITTLE_ENDIAN);
+        toSend.put((byte) 0); // handle
+        toSend.put((byte) RequestType.CLOSE_ALL_REQ.ordinal());
+        toSend.putLong(GADGETBRIDGE_CLIENT_ID);
+        toSend.putShort((short) 0);
+        return toSend.array();
+    }
+
+    private byte[] registerService(final Service service, final boolean reliable) {
+        final ByteBuffer toSend = ByteBuffer.allocate(13).order(ByteOrder.LITTLE_ENDIAN);
+        toSend.put((byte) 0);
+        toSend.put((byte) RequestType.REGISTER_ML_REQ.ordinal());
+        toSend.putLong(GADGETBRIDGE_CLIENT_ID);
+        toSend.putShort(service.getCode());
+        toSend.put((byte) (reliable ? 2 : 0));
+        return toSend.array();
+    }
+
+    private byte[] closeService(final Service service, final int handle) {
+        final ByteBuffer toSend = ByteBuffer.allocate(13).order(ByteOrder.LITTLE_ENDIAN);
+        toSend.put((byte) 0);
+        toSend.put((byte) RequestType.CLOSE_HANDLE_REQ.ordinal());
+        toSend.putLong(GADGETBRIDGE_CLIENT_ID);
+        toSend.putShort(service.getCode());
+        toSend.put((byte) handle);
+        return toSend.array();
+    }
+
+    private void broadcastRealtimeActivity(final int hr, final int steps) {
+        final GarminActivitySample sample;
+        try (final DBHandler dbHandler = GBApplication.acquireDB()) {
+            final DaoSession session = dbHandler.getDaoSession();
+
+            final GBDevice gbDevice = mSupport.getDevice();
+            final Device device = DBHelper.getDevice(gbDevice, session);
+            final User user = DBHelper.getUser(session);
+            final GarminActivitySampleProvider provider = new GarminActivitySampleProvider(gbDevice, session);
+            sample = provider.createActivitySample();
+
+            sample.setDeviceId(device.getId());
+            sample.setUserId(user.getId());
+            sample.setTimestamp((int) (System.currentTimeMillis() / 1000));
+            sample.setHeartRate(hr);
+            sample.setSteps(steps);
+            sample.setRawKind(ActivityKind.UNKNOWN.getCode());
+            sample.setRawIntensity(ActivitySample.NOT_MEASURED);
+            sample.setRawKind(ActivityKind.UNKNOWN.getCode());
+        } catch (final Exception e) {
+            LOG.error("Error creating activity sample", e);
+            return;
+        }
+
+        final Intent intent = new Intent(DeviceService.ACTION_REALTIME_SAMPLES)
+                .putExtra(GBDevice.EXTRA_DEVICE, mSupport.getDevice())
+                .putExtra(DeviceService.EXTRA_REALTIME_SAMPLE, sample);
+        LocalBroadcastManager.getInstance(mSupport.getContext()).sendBroadcast(intent);
+    }
+
+    private enum RequestType {
+        REGISTER_ML_REQ,
+        REGISTER_ML_RESP,
+        CLOSE_HANDLE_REQ,
+        CLOSE_HANDLE_RESP,
+        UNK_HANDLE,
+        CLOSE_ALL_REQ,
+        CLOSE_ALL_RESP,
+        UNK_REQ,
+        UNK_RESP;
+
+        @Nullable
+        public static RequestType fromCode(final int code) {
+            for (final RequestType requestType : RequestType.values()) {
+                if (requestType.ordinal() == code) {
+                    return requestType;
+                }
+            }
+
+            return null;
+        }
+    }
+
+    private enum Service {
+        GFDI(1),
+        REGISTRATION(4),
+        REALTIME_HR(6),
+        REALTIME_STEPS(7),
+        REALTIME_CALORIES(8),
+        REALTIME_INTENSITY(10),
+        REALTIME_HRV(12),
+        REALTIME_STRESS(13),
+        REALTIME_ACCELEROMETER(16),
+        REALTIME_SPO2(19),
+        REALTIME_BODY_BATTERY(20),
+        REALTIME_RESPIRATION(21),
+        ;
+
+        private final short code;
+
+        Service(final int code) {
+            this.code = (short) code;
+        }
+
+        public short getCode() {
+            return code;
+        }
+
+        @Nullable
+        public static Service fromCode(final int code) {
+            for (final Service service : Service.values()) {
+                if (service.code == code) {
+                    return service;
+                }
+            }
+
+            return null;
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/deviceevents/CapabilitiesDeviceEvent.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/deviceevents/CapabilitiesDeviceEvent.java
new file mode 100644
index 0000000..538252e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/deviceevents/CapabilitiesDeviceEvent.java
@@ -0,0 +1,14 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.deviceevents;
+
+import java.util.Set;
+
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
+import nodomain.freeyourgadget.gadgetbridge.devices.vivomovehr.GarminCapability;
+
+public class CapabilitiesDeviceEvent extends GBDeviceEvent {
+    public Set<GarminCapability> capabilities;
+
+    public CapabilitiesDeviceEvent(final Set<GarminCapability> capabilities) {
+        this.capabilities = capabilities;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/deviceevents/FileDownloadedDeviceEvent.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/deviceevents/FileDownloadedDeviceEvent.java
new file mode 100644
index 0000000..fb68a14
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/deviceevents/FileDownloadedDeviceEvent.java
@@ -0,0 +1,9 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.deviceevents;
+
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.FileTransferHandler;
+
+public class FileDownloadedDeviceEvent extends GBDeviceEvent {
+    public FileTransferHandler.DirectoryEntry directoryEntry;
+    public String localPath;
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/deviceevents/NotificationSubscriptionDeviceEvent.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/deviceevents/NotificationSubscriptionDeviceEvent.java
new file mode 100644
index 0000000..76ecc92
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/deviceevents/NotificationSubscriptionDeviceEvent.java
@@ -0,0 +1,9 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.deviceevents;
+
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
+
+public class NotificationSubscriptionDeviceEvent extends GBDeviceEvent {
+
+    public boolean enable;
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/deviceevents/SupportedFileTypesDeviceEvent.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/deviceevents/SupportedFileTypesDeviceEvent.java
new file mode 100644
index 0000000..979a496
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/deviceevents/SupportedFileTypesDeviceEvent.java
@@ -0,0 +1,19 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.deviceevents;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.FileType;
+
+public class SupportedFileTypesDeviceEvent extends GBDeviceEvent {
+
+    private final List<FileType> supportedFileTypes;
+
+    public SupportedFileTypesDeviceEvent(List<FileType> fileTypes) {
+        this.supportedFileTypes = fileTypes;
+    }
+
+    public List<FileType> getSupportedFileTypes() {
+        return supportedFileTypes;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/deviceevents/WeatherRequestDeviceEvent.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/deviceevents/WeatherRequestDeviceEvent.java
new file mode 100644
index 0000000..3af3ccc
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/deviceevents/WeatherRequestDeviceEvent.java
@@ -0,0 +1,18 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.deviceevents;
+
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
+
+public class WeatherRequestDeviceEvent extends GBDeviceEvent {
+    private final int format;
+    private final int latitude;
+    private final int longitude;
+    private final int hoursOfForecast;
+    public WeatherRequestDeviceEvent(int format, int latitude, int longitude, int hoursOfForecast) {
+        this.format = format;
+        this.latitude = latitude;
+        this.longitude = longitude;
+        this.hoursOfForecast = hoursOfForecast;
+    }
+
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/DevFieldDefinition.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/DevFieldDefinition.java
new file mode 100644
index 0000000..ea1c410
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/DevFieldDefinition.java
@@ -0,0 +1,60 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit;
+
+import java.nio.ByteBuffer;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.GarminByteBufferReader;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.baseTypes.BaseType;
+
+public class DevFieldDefinition {
+    public final ByteBuffer valueHolder;
+    private final int fieldDefinitionNumber;
+    private final int size;
+    private final int developerDataIndex;
+    private BaseType baseType;
+    private String name;
+
+    public DevFieldDefinition(int fieldDefinitionNumber, int size, int developerDataIndex, String name) {
+        this.fieldDefinitionNumber = fieldDefinitionNumber;
+        this.size = size;
+        this.developerDataIndex = developerDataIndex;
+        this.name = name;
+        this.valueHolder = ByteBuffer.allocate(size);
+    }
+
+    public static DevFieldDefinition parseIncoming(GarminByteBufferReader garminByteBufferReader) {
+        int number = garminByteBufferReader.readByte();
+        int size = garminByteBufferReader.readByte();
+        int developerDataIndex = garminByteBufferReader.readByte();
+
+        return new DevFieldDefinition(number, size, developerDataIndex, "");
+
+    }
+
+    public BaseType getBaseType() {
+        return baseType;
+    }
+
+    public void setBaseType(BaseType baseType) {
+        this.baseType = baseType;
+    }
+
+    public int getDeveloperDataIndex() {
+        return developerDataIndex;
+    }
+
+    public int getFieldDefinitionNumber() {
+        return fieldDefinitionNumber;
+    }
+
+    public int getSize() {
+        return size;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/FieldDefinition.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/FieldDefinition.java
new file mode 100644
index 0000000..945af6c
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/FieldDefinition.java
@@ -0,0 +1,93 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.GarminByteBufferReader;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.baseTypes.BaseType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionTimestamp;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.MessageWriter;
+
+public class FieldDefinition implements FieldInterface {
+    protected static final Logger LOG = LoggerFactory.getLogger(FieldDefinition.class);
+
+    protected final BaseType baseType;
+    protected final int scale;
+    protected final int offset;
+    private final int number;
+    private final int size;
+    private final String name;
+
+    public FieldDefinition(int number, int size, BaseType baseType, String name, int scale, int offset) {
+        this.number = number;
+        this.size = size;
+        this.baseType = baseType;
+        this.name = name;
+        this.scale = scale;
+        this.offset = offset;
+    }
+
+    public FieldDefinition(int number, int size, BaseType baseType, String name) {
+        this(number, size, baseType, name, 1, 0);
+    }
+
+    public static FieldDefinition parseIncoming(GarminByteBufferReader garminByteBufferReader, GlobalFITMessage globalFITMessage) {
+        int number = garminByteBufferReader.readByte();
+        int size = garminByteBufferReader.readByte();
+        int baseTypeIdentifier = garminByteBufferReader.readByte();
+        BaseType baseType = BaseType.fromIdentifier(baseTypeIdentifier);
+        FieldDefinition global = globalFITMessage.getFieldDefinition(number, size);
+        if (global != null) {
+            if (global.getBaseType().equals(baseType)) {
+                return global;
+            } else {
+                LOG.warn("Global is of type {}, but message declares {}", global.getBaseType(), baseType);
+            }
+        }
+
+        if (number == 253 && size == 4 && baseType.equals(BaseType.UINT32)) {
+            return new FieldDefinitionTimestamp(number, size, baseType, "253_timestamp");
+        }
+
+        return new FieldDefinition(number, size, baseType, "");
+    }
+
+    public int getNumber() {
+        return number;
+    }
+
+    public int getSize() {
+        return size;
+    }
+
+    public BaseType getBaseType() {
+        return baseType;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void generateOutgoingPayload(MessageWriter writer) {
+        writer.writeByte(number);
+        writer.writeByte(size);
+        writer.writeByte(baseType.getIdentifier());
+    }
+
+    @Override
+    public Object decode(ByteBuffer byteBuffer) {
+        return baseType.decode(byteBuffer, scale, offset);
+    }
+
+    @Override
+    public void encode(ByteBuffer byteBuffer, Object o) {
+        baseType.encode(byteBuffer, o, scale, offset);
+    }
+
+    @Override
+    public void invalidate(ByteBuffer byteBuffer) {
+        baseType.invalidate(byteBuffer);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/FieldDefinitionFactory.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/FieldDefinitionFactory.java
new file mode 100644
index 0000000..e243e0d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/FieldDefinitionFactory.java
@@ -0,0 +1,86 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.baseTypes.BaseType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionAlarm;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionArray;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionCoordinate;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionDayOfWeek;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionFileType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionGoalSource;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionGoalType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionHrTimeInZone;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionHrZoneHighBoundary;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionHrvStatus;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionLanguage;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionMeasurementSystem;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionSleepStage;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionTemperature;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionTimestamp;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionWeatherAqi;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionWeatherCondition;
+
+public class FieldDefinitionFactory {
+    public static FieldDefinition create(int localNumber, int size, FIELD field, BaseType baseType, String name, int scale, int offset) {
+        if (null == field) {
+            return new FieldDefinition(localNumber, size, baseType, name, scale, offset);
+        }
+        switch (field) {
+            case ALARM:
+                return new FieldDefinitionAlarm(localNumber, size, baseType, name);
+            case ARRAY:
+                return new FieldDefinitionArray(localNumber, size, baseType, name, scale, offset);
+            case DAY_OF_WEEK:
+                return new FieldDefinitionDayOfWeek(localNumber, size, baseType, name);
+            case FILE_TYPE:
+                return new FieldDefinitionFileType(localNumber, size, baseType, name);
+            case GOAL_SOURCE:
+                return new FieldDefinitionGoalSource(localNumber, size, baseType, name);
+            case GOAL_TYPE:
+                return new FieldDefinitionGoalType(localNumber, size, baseType, name);
+            case HRV_STATUS:
+                return new FieldDefinitionHrvStatus(localNumber, size, baseType, name);
+            case HR_TIME_IN_ZONE:
+                return new FieldDefinitionHrTimeInZone(localNumber, size, baseType, name);
+            case HR_ZONE_HIGH_BOUNDARY:
+                return new FieldDefinitionHrZoneHighBoundary(localNumber, size, baseType, name);
+            case MEASUREMENT_SYSTEM:
+                return new FieldDefinitionMeasurementSystem(localNumber, size, baseType, name);
+            case TEMPERATURE:
+                return new FieldDefinitionTemperature(localNumber, size, baseType, name);
+            case TIMESTAMP:
+                return new FieldDefinitionTimestamp(localNumber, size, baseType, name);
+            case WEATHER_CONDITION:
+                return new FieldDefinitionWeatherCondition(localNumber, size, baseType, name);
+            case LANGUAGE:
+                return new FieldDefinitionLanguage(localNumber, size, baseType, name);
+            case SLEEP_STAGE:
+                return new FieldDefinitionSleepStage(localNumber, size, baseType, name);
+            case WEATHER_AQI:
+                return new FieldDefinitionWeatherAqi(localNumber, size, baseType, name);
+            case COORDINATE:
+                return new FieldDefinitionCoordinate(localNumber, size, baseType, name);
+            default:
+                return new FieldDefinition(localNumber, size, baseType, name);
+        }
+    }
+
+    public enum FIELD {
+        ALARM,
+        ARRAY,
+        DAY_OF_WEEK,
+        FILE_TYPE,
+        GOAL_SOURCE,
+        GOAL_TYPE,
+        HRV_STATUS,
+        HR_TIME_IN_ZONE,
+        HR_ZONE_HIGH_BOUNDARY,
+        MEASUREMENT_SYSTEM,
+        TEMPERATURE,
+        TIMESTAMP,
+        WEATHER_CONDITION,
+        LANGUAGE,
+        SLEEP_STAGE,
+        WEATHER_AQI,
+        COORDINATE
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/FieldInterface.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/FieldInterface.java
new file mode 100644
index 0000000..a313528
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/FieldInterface.java
@@ -0,0 +1,12 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit;
+
+import java.nio.ByteBuffer;
+
+public interface FieldInterface {
+    Object decode(ByteBuffer byteBuffer);
+
+    void encode(ByteBuffer byteBuffer, Object o);
+
+    void invalidate(ByteBuffer byteBuffer);
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/FitAsyncProcessor.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/FitAsyncProcessor.java
new file mode 100644
index 0000000..8544874
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/FitAsyncProcessor.java
@@ -0,0 +1,78 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit;
+
+import android.content.Context;
+import android.os.Handler;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.PendingFileProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class FitAsyncProcessor {
+    private static final Logger LOG = LoggerFactory.getLogger(FitAsyncProcessor.class);
+
+    private final Context context;
+    private final GBDevice gbDevice;
+    private final Handler handler;
+
+    public FitAsyncProcessor(final Context context, final GBDevice gbDevice) {
+        this.context = context;
+        this.gbDevice = gbDevice;
+        this.handler = new Handler(context.getMainLooper());
+    }
+
+    /**
+     * Process a list of files asynchronously. Callback is executed on the UI thread.
+     */
+    public void process(final List<File> files, final Callback callback) {
+        LOG.debug("Starting processor for {} files", files.size());
+
+        new Thread(() -> {
+            try {
+                int i = 0;
+                for (final File file : files) {
+                    i++;
+                    LOG.debug("Parsing {}", file);
+
+                    final int finalI = i;
+                    FitAsyncProcessor.this.handler.post(() -> callback.onProgress(finalI));
+
+                    try {
+                        final FitImporter fitImporter = new FitImporter(context, gbDevice);
+                        fitImporter.importFile(file);
+                    } catch (final Exception ex) {
+                        LOG.error("Exception while importing {}", file, ex);
+                        continue; // do not remove from pending files
+                    }
+
+                    try (DBHandler handler = GBApplication.acquireDB()) {
+                        final DaoSession session = handler.getDaoSession();
+
+                        final PendingFileProvider pendingFileProvider = new PendingFileProvider(gbDevice, session);
+
+                        pendingFileProvider.removePendingFile(file.getPath());
+                    } catch (final Exception e) {
+                        LOG.error("Exception while removing pending file {}", file, e);
+                    }
+                }
+            } catch (final Exception e) {
+                LOG.error("Failed to parse from storage", e);
+            }
+
+            FitAsyncProcessor.this.handler.post(callback::onFinish);
+        }).start();
+    }
+
+    public interface Callback {
+        void onProgress(final int perc);
+
+        void onFinish();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/FitFile.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/FitFile.java
new file mode 100644
index 0000000..d6ff0cf
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/FitFile.java
@@ -0,0 +1,247 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit;
+
+import androidx.annotation.NonNull;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.ChecksumCalculator;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.GarminByteBufferReader;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitRecordDataFactory;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.MessageWriter;
+
+public class FitFile {
+    protected static final Logger LOG = LoggerFactory.getLogger(FitFile.class);
+    private final Header header;
+    private final List<RecordData> dataRecords;
+    private final boolean canGenerateOutput;
+
+    public FitFile(Header header, List<RecordData> dataRecords) {
+        this.header = header;
+        this.dataRecords = dataRecords;
+        this.canGenerateOutput = false;
+    }
+
+    public FitFile(List<RecordData> dataRecords) {
+        this.dataRecords = dataRecords;
+        this.header = new Header(true, 16, 21117);
+        this.canGenerateOutput = true;
+    }
+
+    private static byte[] readFileToByteArray(File file) throws IOException {
+        try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); InputStream inputStream = new FileInputStream(file)) {
+            byte[] buffer = new byte[1024];
+            int length;
+            while ((length = inputStream.read(buffer)) != -1) {
+                outputStream.write(buffer, 0, length);
+            }
+            return outputStream.toByteArray();
+        }
+    }
+
+    public static FitFile parseIncoming(File file) throws IOException {
+        return parseIncoming(readFileToByteArray(file));
+    }
+
+    //TODO: process file in chunks??
+    public static FitFile parseIncoming(byte[] fileContents) {
+
+        final GarminByteBufferReader garminByteBufferReader = new GarminByteBufferReader(fileContents);
+        garminByteBufferReader.setByteOrder(ByteOrder.LITTLE_ENDIAN);
+
+        final Header header = Header.parseIncomingHeader(garminByteBufferReader);
+
+        // needed because the headers can be redefined in the file. The last header for a local message number wins
+        Map<Integer, RecordDefinition> recordDefinitionMap = new HashMap<>();
+        List<RecordData> dataRecords = new ArrayList<>();
+        Long referenceTimestamp = null;
+
+        while (garminByteBufferReader.getPosition() < header.getHeaderSize() + header.getDataSize()) {
+            byte rawRecordHeader = (byte) garminByteBufferReader.readByte();
+            RecordHeader recordHeader = new RecordHeader(rawRecordHeader);
+            final Integer timeOffset = recordHeader.getTimeOffset();
+            if (timeOffset != null) {
+                if (referenceTimestamp == null) {
+                    throw new IllegalArgumentException("Got compressed timestamp without knowing current timestamp");
+                }
+
+                if (timeOffset >= (referenceTimestamp & 0x1FL)) {
+                    referenceTimestamp = (referenceTimestamp & ~0x1FL) + timeOffset;
+                } else if (timeOffset < (referenceTimestamp & 0x1FL)) {
+                    referenceTimestamp = (referenceTimestamp & ~0x1FL) + timeOffset + 0x20;
+                }
+            }
+            if (recordHeader.isDefinition()) {
+                final RecordDefinition recordDefinition = RecordDefinition.parseIncoming(garminByteBufferReader, recordHeader);
+                if (recordDefinition != null) {
+                    if (recordHeader.isDeveloperData())
+                        for (RecordData rd : dataRecords) {
+                            if (GlobalFITMessage.FIELD_DESCRIPTION.equals(rd.getGlobalFITMessage()))
+                                recordDefinition.populateDevFields(rd);
+                        }
+                    recordDefinitionMap.put(recordHeader.getLocalMessageType(), recordDefinition);
+                }
+            } else {
+                final RecordDefinition referenceRecordDefinition = recordDefinitionMap.get(recordHeader.getLocalMessageType());
+                if (referenceRecordDefinition != null) {
+                    final RecordData runningData = FitRecordDataFactory.create(referenceRecordDefinition, recordHeader);
+                    dataRecords.add(runningData);
+                    Long newTimestamp = runningData.parseDataMessage(garminByteBufferReader, referenceTimestamp);
+                    if (newTimestamp != null)
+                        referenceTimestamp = newTimestamp;
+                }
+            }
+        }
+        garminByteBufferReader.setByteOrder(ByteOrder.LITTLE_ENDIAN);
+        int fileCrc = garminByteBufferReader.readShort();
+        if (fileCrc != ChecksumCalculator.computeCrc(fileContents, header.getHeaderSize(), fileContents.length - header.getHeaderSize() - 2)) {
+            throw new IllegalArgumentException("Wrong CRC for FIT file");
+        }
+        return new FitFile(header, dataRecords);
+    }
+
+    public List<RecordData> getRecordsByGlobalMessage(GlobalFITMessage globalFITMessage) {
+        final List<RecordData> filtered = new ArrayList<>();
+        for (RecordData rd : dataRecords) {
+            if (globalFITMessage.equals(rd.getGlobalFITMessage()))
+                filtered.add(rd);
+        }
+        return filtered;
+    }
+
+    public List<RecordData> getRecords() {
+        return dataRecords;
+    }
+
+    public void generateOutgoingDataPayload(MessageWriter writer) {
+        if (!canGenerateOutput)
+            throw new IllegalArgumentException("Generation of previously parsed FIT file not supported.");
+
+        MessageWriter temporary = new MessageWriter(writer.getLimit());
+        temporary.setByteOrder(ByteOrder.LITTLE_ENDIAN);
+        RecordDefinition prevDefinition = null;
+        for (final RecordData rd : dataRecords) {
+            if (!rd.getRecordDefinition().equals(prevDefinition)) {
+                rd.getRecordDefinition().generateOutgoingPayload(temporary);
+                prevDefinition = rd.getRecordDefinition();
+            }
+
+            rd.generateOutgoingDataPayload(temporary);
+        }
+        this.header.setDataSize(temporary.getSize());
+
+        this.header.generateOutgoingDataPayload(writer);
+        writer.writeBytes(temporary.getBytes());
+        writer.writeShort(ChecksumCalculator.computeCrc(writer.getBytes(), this.header.getHeaderSize(), writer.getBytes().length - this.header.getHeaderSize()));
+    }
+
+    public byte[] getOutgoingMessage() {
+        // Compute the worst case scenario buffer size for the fit file
+        // A ~1.6MB gpx file with ~16k points results in a ~320KB buffer, ~150KB of which get actually used
+        final int dataRecordsSize = dataRecords.stream()
+                .mapToInt(r -> {
+                    // Worst case scenario, for each data record
+
+                    // one distinct record definition: 5 bytes + (number of field definitions * 3 + 1)
+                    final List<FieldDefinition> definitions = r.getRecordDefinition().getFieldDefinitions();
+                    final int recordDefinitionOverhead = 5 + (definitions != null ? definitions.size() * 3 + 1 : 0);
+
+                    // 1 + size of the value holder
+                    final int dataRecordOverhead = 1 + r.valueHolder.limit();
+
+                    return recordDefinitionOverhead + dataRecordOverhead;
+                }).sum();
+
+        // Final size = 14b header + data records + 2b crc
+        final MessageWriter writer = new MessageWriter(14 + dataRecordsSize + 2);
+        this.generateOutgoingDataPayload(writer);
+        return writer.getBytes();
+    }
+
+    @NonNull
+    @Override
+    public String toString() {
+        return dataRecords.toString();
+    }
+
+    public static class Header {
+        public static final int MAGIC = 0x5449462E;
+
+        private final int headerSize;
+        private final int protocolVersion;
+        private final int profileVersion;
+        private final boolean hasCRC;
+        private int dataSize;
+
+        public Header(boolean hasCRC, int protocolVersion, int profileVersion) {
+            this(hasCRC, protocolVersion, profileVersion, 0);
+        }
+
+        public Header(boolean hasCRC, int protocolVersion, int profileVersion, int dataSize) {
+            this.hasCRC = hasCRC;
+            headerSize = hasCRC ? 14 : 12;
+            this.protocolVersion = protocolVersion;
+            this.profileVersion = profileVersion;
+            this.dataSize = dataSize;
+        }
+
+        static Header parseIncomingHeader(GarminByteBufferReader garminByteBufferReader) {
+            int headerSize = garminByteBufferReader.readByte();
+            if (headerSize < 12) {
+                throw new IllegalArgumentException("Too short header in FIT file.");
+            }
+            boolean hasCRC = headerSize == 14;
+            int protocolVersion = garminByteBufferReader.readByte();
+            int profileVersion = garminByteBufferReader.readShort();
+            int dataSize = garminByteBufferReader.readInt();
+            int magic = garminByteBufferReader.readInt();
+            if (magic != MAGIC) {
+                throw new IllegalArgumentException("Wrong magic header in FIT file");
+            }
+            if (hasCRC) {
+                int incomingCrc = garminByteBufferReader.readShort();
+
+                if (incomingCrc != 0 && incomingCrc != ChecksumCalculator.computeCrc(garminByteBufferReader.asReadOnlyBuffer(), 0, headerSize - 2)) {
+                    throw new IllegalArgumentException("Wrong CRC for header in FIT file");
+                }
+                //            LOG.info("Fit File Header didn't have CRC, no check performed.");
+            }
+            return new Header(hasCRC, protocolVersion, profileVersion, dataSize);
+        }
+
+        public int getHeaderSize() {
+            return headerSize;
+        }
+
+        public int getDataSize() {
+            return dataSize;
+        }
+
+        public void setDataSize(int dataSize) {
+            this.dataSize = dataSize;
+        }
+
+        public void generateOutgoingDataPayload(MessageWriter writer) {
+            writer.setByteOrder(ByteOrder.LITTLE_ENDIAN);
+            writer.writeByte(headerSize);
+            writer.writeByte(protocolVersion);
+            writer.writeShort(profileVersion);
+            writer.writeInt(dataSize);
+            writer.writeInt(MAGIC);//magic
+            if (hasCRC)
+                writer.writeShort(ChecksumCalculator.computeCrc(writer.getBytes(), 0, writer.getBytes().length));
+        }
+
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/FitImporter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/FitImporter.java
new file mode 100644
index 0000000..e3dc7bc
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/FitImporter.java
@@ -0,0 +1,661 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit;
+
+import android.content.Context;
+import android.widget.Toast;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.AbstractTimeSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminActivitySampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminBodyEnergySampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminEventSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminHeartRateRestingSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminHrvSummarySampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminHrvValueSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminIntensityMinutesSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminRespiratoryRateSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminRestingMetabolicRateSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminSleepStatsSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminSleepStageSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminSpo2SampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminStressSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminWorkoutParser;
+import nodomain.freeyourgadget.gadgetbridge.entities.AbstractTimeSample;
+import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.entities.GarminActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.entities.GarminBodyEnergySample;
+import nodomain.freeyourgadget.gadgetbridge.entities.GarminEventSample;
+import nodomain.freeyourgadget.gadgetbridge.entities.GarminHeartRateRestingSample;
+import nodomain.freeyourgadget.gadgetbridge.entities.GarminIntensityMinutesSample;
+import nodomain.freeyourgadget.gadgetbridge.entities.GarminRestingMetabolicRateSample;
+import nodomain.freeyourgadget.gadgetbridge.entities.GarminHrvSummarySample;
+import nodomain.freeyourgadget.gadgetbridge.entities.GarminHrvValueSample;
+import nodomain.freeyourgadget.gadgetbridge.entities.GarminRespiratoryRateSample;
+import nodomain.freeyourgadget.gadgetbridge.entities.GarminSleepStageSample;
+import nodomain.freeyourgadget.gadgetbridge.entities.GarminSleepStatsSample;
+import nodomain.freeyourgadget.gadgetbridge.entities.GarminSpo2Sample;
+import nodomain.freeyourgadget.gadgetbridge.entities.GarminStressSample;
+import nodomain.freeyourgadget.gadgetbridge.entities.User;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryParser;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.FileType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionHrvStatus;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionSleepStage;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitEvent;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitFileId;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitHrvSummary;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitHrvValue;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitMonitoring;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitMonitoringHrData;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitMonitoringInfo;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitPhysiologicalMetrics;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitRecord;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitRespirationRate;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitSession;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitSleepDataInfo;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitSleepDataRaw;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitSleepStage;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitSleepStats;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitSpo2;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitSport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitStressLevel;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitTimeInZone;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class FitImporter {
+    private static final Logger LOG = LoggerFactory.getLogger(FitImporter.class);
+
+    private final Context context;
+    private final GBDevice gbDevice;
+
+    private final SortedMap<Long, List<FitMonitoring>> activitySamplesPerTimestamp = new TreeMap<>();
+    private final List<GarminStressSample> stressSamples = new ArrayList<>();
+    private final List<GarminBodyEnergySample> bodyEnergySamples = new ArrayList<>();
+    private final List<GarminSpo2Sample> spo2samples = new ArrayList<>();
+    private final List<GarminRespiratoryRateSample> respiratoryRateSamples = new ArrayList<>();
+    private final List<GarminHeartRateRestingSample> restingHrSamples = new ArrayList<>();
+    private final List<GarminEventSample> events = new ArrayList<>();
+    private final List<GarminSleepStatsSample> sleepStatsSamples = new ArrayList<>();
+    private final List<GarminSleepStageSample> sleepStageSamples = new ArrayList<>();
+    private final List<GarminHrvSummarySample> hrvSummarySamples = new ArrayList<>();
+    private final List<GarminHrvValueSample> hrvValueSamples = new ArrayList<>();
+    private final List<GarminRestingMetabolicRateSample> restingMetabolicRateSamples = new ArrayList<>();
+    private final Map<Integer, Integer> unknownRecords = new HashMap<>();
+    private FitSleepDataInfo fitSleepDataInfo = null;
+    private final List<FitSleepDataRaw> fitSleepDataRawSamples = new ArrayList<>();
+    private FitFileId fileId = null;
+
+    private final GarminWorkoutParser workoutParser;
+
+    public FitImporter(final Context context, final GBDevice gbDevice) {
+        this.context = context;
+        this.gbDevice = gbDevice;
+        this.workoutParser = new GarminWorkoutParser(context);
+    }
+
+    /**
+     * @noinspection StatementWithEmptyBody
+     */
+    public void importFile(final File file) throws IOException {
+        reset();
+
+        final FitFile fitFile = FitFile.parseIncoming(file);
+
+        Long lastMonitoringTimestamp = null;
+
+        for (final RecordData record : fitFile.getRecords()) {
+            if (fileId != null && fileId.getType() == FileType.FILETYPE.ACTIVITY) {
+                if (workoutParser.handleRecord(record)) {
+                    continue;
+                }
+            }
+
+            final Long ts = record.getComputedTimestamp();
+
+            if (record instanceof FitFileId) {
+                final FitFileId newFileId = (FitFileId) record;
+                LOG.debug("File ID: {}", newFileId);
+                if (fileId != null) {
+                    // Should not happen
+                    LOG.warn("Already had a file ID: {}", fileId);
+                }
+                fileId = newFileId;
+            } else if (record instanceof FitStressLevel) {
+                final FitStressLevel stressRecord = (FitStressLevel) record;
+                final Integer stress = stressRecord.getStressLevelValue();
+                if (stress != null && stress >= 0) {
+                    LOG.trace("Stress at {}: {}", ts, stress);
+                    final GarminStressSample sample = new GarminStressSample();
+                    sample.setTimestamp(ts * 1000L);
+                    sample.setStress(stress);
+                    stressSamples.add(sample);
+                }
+
+                final Integer energy = stressRecord.getBodyEnergy();
+                if (energy != null) {
+                    LOG.trace("Body energy at {}: {}", ts, energy);
+                    final GarminBodyEnergySample sample = new GarminBodyEnergySample();
+                    sample.setTimestamp(ts * 1000L);
+                    sample.setEnergy(energy);
+                    bodyEnergySamples.add(sample);
+                }
+            } else if (record instanceof FitSleepDataInfo) {
+                final FitSleepDataInfo newFitSleepDataInfo = (FitSleepDataInfo) record;
+                LOG.debug("Sleep Data Info: {}", newFitSleepDataInfo);
+                if (fitSleepDataInfo != null) {
+                    // Should not happen
+                    LOG.warn("Already had sleep data info: {}", fitSleepDataInfo);
+                }
+                fitSleepDataInfo = newFitSleepDataInfo;
+            } else if (record instanceof FitSleepDataRaw) {
+                final FitSleepDataRaw fitSleepDataRaw = (FitSleepDataRaw) record;
+                //LOG.debug("Sleep Data Raw: {}", fitSleepDataRaw);
+                fitSleepDataRawSamples.add(fitSleepDataRaw);
+            } else if (record instanceof FitSleepStats) {
+                final Integer score = ((FitSleepStats) record).getOverallSleepScore();
+                if (score == null) {
+                    continue;
+                }
+                LOG.trace("Sleep stats at {}: {}", ts, record);
+                final GarminSleepStatsSample sample = new GarminSleepStatsSample();
+                sample.setTimestamp(ts * 1000L);
+                sample.setSleepScore(score);
+                sleepStatsSamples.add(sample);
+            } else if (record instanceof FitSleepStage) {
+                final FieldDefinitionSleepStage.SleepStage stage = ((FitSleepStage) record).getSleepStage();
+                if (stage == null) {
+                    continue;
+                }
+                LOG.trace("Sleep stage at {}: {}", ts, record);
+                final GarminSleepStageSample sample = new GarminSleepStageSample();
+                sample.setTimestamp(ts * 1000L);
+                sample.setStage(stage.getId());
+                sleepStageSamples.add(sample);
+            } else if (record instanceof FitMonitoring) {
+                LOG.trace("Monitoring at {}: {}", ts, record);
+                final FitMonitoring monitoringRecord = (FitMonitoring) record;
+                final Long currentMonitoringTimestamp = monitoringRecord.computeTimestamp(lastMonitoringTimestamp);
+                if (!activitySamplesPerTimestamp.containsKey(currentMonitoringTimestamp)) {
+                    activitySamplesPerTimestamp.put(currentMonitoringTimestamp, new ArrayList<>());
+                }
+                Objects.requireNonNull(activitySamplesPerTimestamp.get(currentMonitoringTimestamp)).add(monitoringRecord);
+                lastMonitoringTimestamp = currentMonitoringTimestamp;
+            } else if (record instanceof FitSpo2) {
+                final Integer spo2 = ((FitSpo2) record).getReadingSpo2();
+                if (spo2 == null || spo2 <= 0) {
+                    continue;
+                }
+                LOG.trace("SpO2 at {}: {}", ts, spo2);
+                final GarminSpo2Sample sample = new GarminSpo2Sample();
+                sample.setTimestamp(ts * 1000L);
+                sample.setSpo2(spo2);
+                spo2samples.add(sample);
+            } else if (record instanceof FitRespirationRate) {
+                final Float respiratoryRate = ((FitRespirationRate) record).getRespirationRate();
+                if (respiratoryRate == null || respiratoryRate <= 0) {
+                    continue;
+                }
+                LOG.trace("Respiratory rate at {}: {}", ts, respiratoryRate);
+                final GarminRespiratoryRateSample sample = new GarminRespiratoryRateSample();
+                sample.setTimestamp(ts * 1000L);
+                sample.setRespiratoryRate(respiratoryRate);
+                respiratoryRateSamples.add(sample);
+            } else if (record instanceof FitEvent) {
+                final FitEvent event = (FitEvent) record;
+                if (event.getEvent() == null) {
+                    LOG.warn("Event in {} is null", event);
+                    continue;
+                }
+
+                LOG.trace("Event at {}: {}", ts, event);
+
+                final GarminEventSample sample = new GarminEventSample();
+                sample.setTimestamp(ts * 1000L);
+                sample.setEvent(event.getEvent());
+                if (event.getEventType() != null) {
+                    sample.setEventType(event.getEventType());
+                }
+                if (event.getData() != null) {
+                    sample.setData(event.getData());
+                }
+                events.add(sample);
+            } else if (record instanceof FitRecord) {
+                // handled in workout parser
+            } else if (record instanceof FitSession) {
+                // handled in workout parser
+            } else if (record instanceof FitPhysiologicalMetrics) {
+                // handled in workout parser
+            } else if (record instanceof FitSport) {
+                // handled in workout parser
+            } else if (record instanceof FitTimeInZone) {
+                // handled in workout parser
+            } else if (record instanceof FitHrvSummary) {
+                final FitHrvSummary hrvSummary = (FitHrvSummary) record;
+                LOG.trace("HRV summary at {}: {}", ts, record);
+                final GarminHrvSummarySample sample = new GarminHrvSummarySample();
+                sample.setTimestamp(ts * 1000L);
+                if (hrvSummary.getWeeklyAverage() != null) {
+                    sample.setWeeklyAverage(Math.round(hrvSummary.getWeeklyAverage()));
+                }
+                if (hrvSummary.getLastNightAverage() != null) {
+                    sample.setLastNightAverage(Math.round(hrvSummary.getLastNightAverage()));
+                }
+                if (hrvSummary.getLastNight5MinHigh() != null) {
+                    sample.setLastNight5MinHigh(Math.round(hrvSummary.getLastNight5MinHigh()));
+                }
+                if (hrvSummary.getBaselineLowUpper() != null) {
+                    sample.setBaselineLowUpper(Math.round(hrvSummary.getBaselineLowUpper()));
+                }
+                if (hrvSummary.getBaselineBalancedLower() != null) {
+                    sample.setBaselineBalancedLower(Math.round(hrvSummary.getBaselineBalancedLower()));
+                }
+                if (hrvSummary.getBaselineBalancedUpper() != null) {
+                    sample.setBaselineBalancedUpper(Math.round(hrvSummary.getBaselineBalancedUpper()));
+                }
+                final FieldDefinitionHrvStatus.HrvStatus status = hrvSummary.getStatus();
+                if (status != null) {
+                    sample.setStatusNum(status.getId());
+                }
+                hrvSummarySamples.add(sample);
+            } else if (record instanceof FitHrvValue) {
+                final FitHrvValue hrvValue = (FitHrvValue) record;
+                if (hrvValue.getValue() == null) {
+                    LOG.warn("HRV value at {} is null", ts);
+                    continue;
+                }
+                LOG.trace("HRV value at {}: {}", ts, hrvValue.getValue());
+                final GarminHrvValueSample sample = new GarminHrvValueSample();
+                sample.setTimestamp(ts * 1000L);
+                sample.setValue(Math.round(hrvValue.getValue()));
+                hrvValueSamples.add(sample);
+            } else if (record instanceof FitMonitoringInfo) {
+                final FitMonitoringInfo monitoringInfo = (FitMonitoringInfo) record;
+                if (monitoringInfo.getRestingMetabolicRate() == null) {
+                    continue;
+                }
+                LOG.trace("Monitoring info at {}: {}", ts, record);
+                final GarminRestingMetabolicRateSample sample = new GarminRestingMetabolicRateSample();
+                sample.setTimestamp(ts * 1000L);
+                sample.setRestingMetabolicRate(monitoringInfo.getRestingMetabolicRate());
+                restingMetabolicRateSamples.add(sample);
+            } else if (record instanceof FitMonitoringHrData) {
+                final FitMonitoringHrData monitoringHrData = (FitMonitoringHrData) record;
+                if (monitoringHrData.getRestingHeartRate() == null) {
+                    LOG.warn("Resting HR at {} is null", ts);
+                    continue;
+                }
+                LOG.trace("Resting HR at {}: {}", ts, monitoringHrData.getRestingHeartRate());
+                final GarminHeartRateRestingSample sample = new GarminHeartRateRestingSample();
+                sample.setTimestamp(ts * 1000L);
+                sample.setHeartRate(monitoringHrData.getRestingHeartRate());
+                restingHrSamples.add(sample);
+            } else {
+                LOG.trace("Unknown record: {}", record);
+
+                if (!unknownRecords.containsKey(record.getGlobalFITMessage().getNumber())) {
+                    unknownRecords.put(record.getGlobalFITMessage().getNumber(), 0);
+                }
+                unknownRecords.put(
+                        record.getGlobalFITMessage().getNumber(),
+                        Objects.requireNonNull(unknownRecords.get(record.getGlobalFITMessage().getNumber())) + 1
+                );
+            }
+        }
+
+        if (fileId == null) {
+            LOG.error("Got no file ID");
+            return;
+        }
+        if (fileId.getType() == null) {
+            LOG.error("File has no type");
+            return;
+        }
+
+        try (DBHandler handler = GBApplication.acquireDB()) {
+            final DaoSession session = handler.getDaoSession();
+
+            switch (fileId.getType()) {
+                case ACTIVITY:
+                    persistWorkout(file, session);
+                    break;
+                case MONITOR:
+                    persistActivitySamples(session);
+                    persistAbstractSamples(spo2samples, new GarminSpo2SampleProvider(gbDevice, session));
+                    persistAbstractSamples(respiratoryRateSamples, new GarminRespiratoryRateSampleProvider(gbDevice, session));
+                    persistAbstractSamples(restingHrSamples, new GarminHeartRateRestingSampleProvider(gbDevice, session));
+                    persistAbstractSamples(stressSamples, new GarminStressSampleProvider(gbDevice, session));
+                    persistAbstractSamples(bodyEnergySamples, new GarminBodyEnergySampleProvider(gbDevice, session));
+                    persistAbstractSamples(restingMetabolicRateSamples, new GarminRestingMetabolicRateSampleProvider(gbDevice, session));
+                    break;
+                case SLEEP:
+                    persistAbstractSamples(events, new GarminEventSampleProvider(gbDevice, session));
+                    persistAbstractSamples(sleepStatsSamples, new GarminSleepStatsSampleProvider(gbDevice, session));
+
+                    // We may have samples, but not sleep samples - #4048
+                    // 0 unmeasurable, 1 awake
+                    final boolean anySleepSample = sleepStageSamples.stream()
+                            .anyMatch(s -> s.getStage() != 0 && s.getStage() != 1);
+                    if (anySleepSample) {
+                        persistAbstractSamples(sleepStageSamples, new GarminSleepStageSampleProvider(gbDevice, session));
+                    }
+
+                    processRawSleepSamples(session);
+                    break;
+                case HRV_STATUS:
+                    persistAbstractSamples(hrvSummarySamples, new GarminHrvSummarySampleProvider(gbDevice, session));
+                    persistAbstractSamples(hrvValueSamples, new GarminHrvValueSampleProvider(gbDevice, session));
+                    break;
+                default:
+                    LOG.warn("Unable to handle fit file of type {}", fileId.getType());
+            }
+        } catch (final Exception e) {
+            GB.toast(context, "Error saving samples", Toast.LENGTH_LONG, GB.ERROR, e);
+        }
+
+        for (final Map.Entry<Integer, Integer> e : unknownRecords.entrySet()) {
+            LOG.warn("Unknown record of global number {} seen {} times", e.getKey(), e.getValue());
+        }
+    }
+
+    private void persistWorkout(final File file, final DaoSession session) {
+        LOG.debug("Persisting workout for {}", fileId);
+
+        final BaseActivitySummary summary;
+
+        // This ensures idempotency when re-processing
+        try {
+            summary = ActivitySummaryParser.findOrCreateBaseActivitySummary(
+                    session,
+                    gbDevice,
+                    Objects.requireNonNull(fileId.getTimeCreated()).intValue()
+            );
+        } catch (final Exception e) {
+            GB.toast(context, "Error finding base summary", Toast.LENGTH_LONG, GB.ERROR, e);
+            return;
+        }
+
+        workoutParser.updateSummary(summary);
+
+        summary.setRawDetailsPath(file.getAbsolutePath());
+
+        try {
+            final Device device = DBHelper.getDevice(gbDevice, session);
+            final User user = DBHelper.getUser(session);
+
+            summary.setDevice(device);
+            summary.setUser(user);
+
+            session.getBaseActivitySummaryDao().insertOrReplace(summary);
+        } catch (final Exception e) {
+            GB.toast(context, "Error saving workout", Toast.LENGTH_LONG, GB.ERROR, e);
+        }
+    }
+
+    private void reset() {
+        activitySamplesPerTimestamp.clear();
+        stressSamples.clear();
+        bodyEnergySamples.clear();
+        spo2samples.clear();
+        respiratoryRateSamples.clear();
+        restingHrSamples.clear();
+        events.clear();
+        sleepStatsSamples.clear();
+        sleepStageSamples.clear();
+        hrvSummarySamples.clear();
+        hrvValueSamples.clear();
+        restingMetabolicRateSamples.clear();
+        unknownRecords.clear();
+        fitSleepDataInfo = null;
+        fitSleepDataRawSamples.clear();
+        fileId = null;
+        workoutParser.reset();
+    }
+
+    private void persistActivitySamples(final DaoSession session) {
+        if (activitySamplesPerTimestamp.isEmpty()) {
+            return;
+        }
+
+        final List<GarminActivitySample> activitySamples = new ArrayList<>(activitySamplesPerTimestamp.size());
+        final List<GarminIntensityMinutesSample> intensityMinutesSamples = new ArrayList<>(activitySamplesPerTimestamp.size());
+
+        // Garmin reports the cumulative data per activity, but not always, so we need to keep
+        // track of the amounts for each activity, and set the sum of all on the sample
+        final Map<Integer, Long> stepsPerActivity = new HashMap<>();
+        final Map<Integer, Long> distancePerActivity = new HashMap<>();
+        final Map<Integer, Integer> caloriesPerActivity = new HashMap<>();
+
+        final int THRESHOLD_NOT_WORN = 10 * 60; // 10 min gap between samples = not-worn
+        int prevActivityKind = ActivityKind.UNKNOWN.getCode();
+        int prevTs = -1;
+
+        for (final long ts : activitySamplesPerTimestamp.keySet()) {
+            if (prevTs > 0 && ts - prevTs > 60) {
+                // Fill gaps between samples
+                LOG.debug("Filling gap between {} and {}", prevTs, ts);
+                for (int i = prevTs + 60; i < ts; i += 60) {
+                    final GarminActivitySample sample = new GarminActivitySample();
+                    sample.setTimestamp(i);
+                    sample.setRawKind(ts - prevTs > THRESHOLD_NOT_WORN ? ActivityKind.NOT_WORN.getCode() : prevActivityKind);
+                    sample.setRawIntensity(ActivitySample.NOT_MEASURED);
+                    sample.setSteps(ActivitySample.NOT_MEASURED);
+                    sample.setHeartRate(ActivitySample.NOT_MEASURED);
+                    sample.setDistanceCm(ActivitySample.NOT_MEASURED);
+                    sample.setActiveCalories(ActivitySample.NOT_MEASURED);
+                    activitySamples.add(sample);
+                }
+            }
+
+            final List<FitMonitoring> records = activitySamplesPerTimestamp.get(ts);
+
+            final GarminActivitySample sample = new GarminActivitySample();
+            sample.setTimestamp((int) ts);
+            sample.setRawKind(ActivityKind.ACTIVITY.getCode());
+            sample.setRawIntensity(ActivitySample.NOT_MEASURED);
+            sample.setSteps(ActivitySample.NOT_MEASURED);
+            sample.setHeartRate(ActivitySample.NOT_MEASURED);
+            sample.setDistanceCm(ActivitySample.NOT_MEASURED);
+            sample.setActiveCalories(ActivitySample.NOT_MEASURED);
+
+            int minutesModerate = 0;
+            int minutesVigorous = 0;
+
+            for (final FitMonitoring record : Objects.requireNonNull(records)) {
+                final Integer activityType = record.getComputedActivityType().orElse(ActivitySample.NOT_MEASURED);
+
+                final Integer hr = record.getHeartRate();
+                if (hr != null) {
+                    sample.setHeartRate(hr);
+                }
+
+                final Long steps = record.getCycles();
+                if (steps != null) {
+                    stepsPerActivity.put(activityType, steps);
+                }
+
+                final Long distance = record.getDistance();
+                if (distance != null) {
+                    distancePerActivity.put(activityType, distance);
+                }
+
+                final Integer calories = record.getActiveCalories();
+                if (calories != null) {
+                    caloriesPerActivity.put(activityType, calories);
+                }
+
+                final Integer intensity = record.getComputedIntensity();
+                if (intensity != null) {
+                    sample.setRawIntensity(intensity);
+                }
+
+                final Integer recordMinutesModerate = record.getModerateActivityMinutes();
+                if (recordMinutesModerate != null) {
+                    minutesModerate += recordMinutesModerate;
+                }
+
+                final Integer recordMinutesVigorous = record.getVigorousActivityMinutes();
+                if (recordMinutesVigorous != null) {
+                    minutesVigorous += recordMinutesVigorous;
+                }
+            }
+            if (!stepsPerActivity.isEmpty()) {
+                int sumSteps = 0;
+                for (final Long steps : stepsPerActivity.values()) {
+                    sumSteps += steps;
+                }
+                sample.setSteps(sumSteps);
+            }
+            if (!distancePerActivity.isEmpty()) {
+                int sumDistance = 0;
+                for (final Long distance : distancePerActivity.values()) {
+                    sumDistance += distance;
+                }
+                sample.setDistanceCm(sumDistance);
+            }
+            if (!caloriesPerActivity.isEmpty()) {
+                int sumCalories = 0;
+                for (final Integer calories : caloriesPerActivity.values()) {
+                    sumCalories += calories;
+                }
+                sample.setActiveCalories(sumCalories);
+            }
+
+            activitySamples.add(sample);
+
+            if (minutesModerate != 0 || minutesVigorous != 0) {
+                final GarminIntensityMinutesSample intensityMinutesSample = new GarminIntensityMinutesSample();
+                intensityMinutesSample.setTimestamp(ts * 1000L);
+                intensityMinutesSample.setModerate(minutesModerate);
+                intensityMinutesSample.setVigorous(minutesVigorous);
+                intensityMinutesSamples.add(intensityMinutesSample);
+            }
+
+            prevActivityKind = sample.getRawKind();
+            prevTs = (int) ts;
+        }
+
+        LOG.debug("Will persist {} activity samples", activitySamples.size());
+
+        try {
+            final Device device = DBHelper.getDevice(gbDevice, session);
+            final User user = DBHelper.getUser(session);
+
+            final GarminActivitySampleProvider sampleProvider = new GarminActivitySampleProvider(gbDevice, session);
+
+            for (final GarminActivitySample sample : activitySamples) {
+                sample.setDevice(device);
+                sample.setUser(user);
+            }
+
+            sampleProvider.addGBActivitySamples(activitySamples.toArray(new GarminActivitySample[0]));
+        } catch (final Exception e) {
+            GB.toast(context, "Error saving activity samples", Toast.LENGTH_LONG, GB.ERROR, e);
+        }
+
+        try {
+            persistAbstractSamples(intensityMinutesSamples, new GarminIntensityMinutesSampleProvider(gbDevice, session));
+        } catch (final Exception e) {
+            GB.toast(context, "Error saving intensity minutes samples", Toast.LENGTH_LONG, GB.ERROR, e);
+        }
+    }
+
+    /**
+     * As per #4048, devices that do not have a sleep widget send raw sleep samples, which we do not
+     * know how to parse. Therefore, we don't persist the sleep stages they report (they're all awake),
+     * but we fake light sleep for the duration of the raw sleep samples, in order to have some data
+     * at all.
+     */
+    private void processRawSleepSamples(final DaoSession session) {
+        if (fitSleepDataRawSamples.isEmpty()) {
+            return;
+        }
+
+        final boolean anySleepSample = sleepStageSamples.stream()
+                .anyMatch(s -> s.getStage() != 0 && s.getStage() != 1);
+        if (anySleepSample) {
+            // We have at least one real sleep sample - do nothing
+            return;
+        }
+
+        final long asleepTimeMillis = Objects.requireNonNull(fileId.getTimeCreated()).intValue() * 1000L;
+        final long wakeTimeMillis = asleepTimeMillis + fitSleepDataRawSamples.size() * 60 * 1000L;
+
+        LOG.debug("Got {} raw sleep samples - faking sleep events from {} to {}", fitSleepDataRawSamples.size(), asleepTimeMillis, wakeTimeMillis);
+
+        // We only need to fake sleep start and end times, the sample provider will take care of the rest
+        try {
+            final Device device = DBHelper.getDevice(gbDevice, session);
+            final User user = DBHelper.getUser(session);
+
+            final GarminEventSampleProvider sampleProvider = new GarminEventSampleProvider(gbDevice, session);
+
+            final GarminEventSample sampleFallAsleep = new GarminEventSample();
+            sampleFallAsleep.setTimestamp(asleepTimeMillis);
+            sampleFallAsleep.setEvent(74); // sleep
+            sampleFallAsleep.setEventType(0); // sleep start
+            sampleFallAsleep.setData(-1L); // in actual samples they're a garmin epoch, this way we can identify them
+            sampleFallAsleep.setDevice(device);
+            sampleFallAsleep.setUser(user);
+
+            final GarminEventSample sampleWakeUp = new GarminEventSample();
+            sampleWakeUp.setTimestamp(wakeTimeMillis);
+            sampleWakeUp.setEvent(74); // sleep
+            sampleWakeUp.setEventType(1); // sleep end
+            sampleWakeUp.setData(-1L); // in actual samples they're a garmin epoch, this way we can identify them
+            sampleWakeUp.setDevice(device);
+            sampleWakeUp.setUser(user);
+
+            sampleProvider.addSample(sampleFallAsleep);
+            sampleProvider.addSample(sampleWakeUp);
+        } catch (final Exception e) {
+            GB.toast(context, "Error faking event samples", Toast.LENGTH_LONG, GB.ERROR, e);
+        }
+    }
+
+    private <T extends AbstractTimeSample> void persistAbstractSamples(final List<T> samples,
+                                                                       final AbstractTimeSampleProvider<T> sampleProvider) {
+        if (samples.isEmpty()) {
+            return;
+        }
+
+        LOG.debug(
+                "Will persist {} {} samples",
+                samples.size(),
+                sampleProvider.getClass().getSimpleName().replace("Garmin", "").replace("SampleProvider", "")
+        );
+
+        try {
+            final DaoSession session = sampleProvider.getSession();
+
+            final Device device = DBHelper.getDevice(gbDevice, session);
+            final User user = DBHelper.getUser(session);
+
+            for (final T sample : samples) {
+                sample.setDevice(device);
+                sample.setUser(user);
+            }
+
+            sampleProvider.addSamples(samples);
+        } catch (final Exception e) {
+            GB.toast(context, "Error saving samples", Toast.LENGTH_LONG, GB.ERROR, e);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/GlobalFITMessage.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/GlobalFITMessage.java
new file mode 100644
index 0000000..7468684
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/GlobalFITMessage.java
@@ -0,0 +1,602 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit;
+
+import androidx.annotation.Nullable;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.baseTypes.BaseType;
+
+public class GlobalFITMessage {
+    public static GlobalFITMessage FILE_ID = new GlobalFITMessage(0, "FILE_ID", Arrays.asList(
+            new FieldDefinitionPrimitive(0, BaseType.ENUM, "type", FieldDefinitionFactory.FIELD.FILE_TYPE),
+            new FieldDefinitionPrimitive(1, BaseType.UINT16, "manufacturer"),
+            new FieldDefinitionPrimitive(2, BaseType.UINT16, "product"),
+            new FieldDefinitionPrimitive(3, BaseType.UINT32Z, "serial_number"),
+            new FieldDefinitionPrimitive(4, BaseType.UINT32, "time_created", FieldDefinitionFactory.FIELD.TIMESTAMP),
+            new FieldDefinitionPrimitive(5, BaseType.UINT16, "number"),
+            new FieldDefinitionPrimitive(6, BaseType.UINT16, "manufacturer_partner"),
+            new FieldDefinitionPrimitive(8, BaseType.STRING, 20, "product_name")
+    ));
+
+    public static GlobalFITMessage DEVICE_SETTINGS = new GlobalFITMessage(2, "DEVICE_SETTINGS", Arrays.asList(
+            new FieldDefinitionPrimitive(0, BaseType.UINT8, "active_time_zone"),
+            new FieldDefinitionPrimitive(1, BaseType.UINT32, "utc_offset"),
+            new FieldDefinitionPrimitive(2, BaseType.UINT32, "time_offset"),
+            new FieldDefinitionPrimitive(4, BaseType.ENUM, "time_mode"),
+            new FieldDefinitionPrimitive(5, BaseType.SINT8, "time_zone_offset"),
+            new FieldDefinitionPrimitive(12, BaseType.ENUM, "backlight_mode"),
+            new FieldDefinitionPrimitive(36, BaseType.ENUM, "activity_tracker_enabled"),
+            new FieldDefinitionPrimitive(46, BaseType.ENUM, "move_alert_enabled"),
+            new FieldDefinitionPrimitive(47, BaseType.ENUM, "date_mode"),
+            new FieldDefinitionPrimitive(55, BaseType.ENUM, "display_orientation"),
+            new FieldDefinitionPrimitive(56, BaseType.ENUM, "mounting_side"),
+            new FieldDefinitionPrimitive(57, BaseType.UINT16, "default_page"),
+            new FieldDefinitionPrimitive(58, BaseType.UINT16, "autosync_min_steps"),
+            new FieldDefinitionPrimitive(59, BaseType.UINT16, "autosync_min_time"),
+            new FieldDefinitionPrimitive(86, BaseType.ENUM, "ble_auto_upload_enabled"),
+            new FieldDefinitionPrimitive(90, BaseType.UINT32, "auto_activity_detect")
+    ));
+
+    public static GlobalFITMessage USER_PROFILE = new GlobalFITMessage(3, "USER_PROFILE", Arrays.asList(
+            new FieldDefinitionPrimitive(0, BaseType.STRING, 8, "friendly_name"),
+            new FieldDefinitionPrimitive(1, BaseType.ENUM, "gender"),
+            new FieldDefinitionPrimitive(2, BaseType.UINT8, "age"),
+            new FieldDefinitionPrimitive(3, BaseType.UINT8, "height"),
+            new FieldDefinitionPrimitive(4, BaseType.UINT16, "weight", 10, 0),
+            new FieldDefinitionPrimitive(5, BaseType.ENUM, "language", FieldDefinitionFactory.FIELD.LANGUAGE),
+            new FieldDefinitionPrimitive(6, BaseType.ENUM, "elev_setting", FieldDefinitionFactory.FIELD.MEASUREMENT_SYSTEM),
+            new FieldDefinitionPrimitive(7, BaseType.ENUM, "weight_setting", FieldDefinitionFactory.FIELD.MEASUREMENT_SYSTEM),
+            new FieldDefinitionPrimitive(8, BaseType.UINT8, "resting_heart_rate"),
+            new FieldDefinitionPrimitive(10, BaseType.UINT8, "default_max_biking_heart_rate"),
+            new FieldDefinitionPrimitive(11, BaseType.UINT8, "default_max_heart_rate"),
+            new FieldDefinitionPrimitive(12, BaseType.ENUM, "hr_setting"),
+            new FieldDefinitionPrimitive(13, BaseType.ENUM, "speed_setting", FieldDefinitionFactory.FIELD.MEASUREMENT_SYSTEM),
+            new FieldDefinitionPrimitive(14, BaseType.ENUM, "dist_setting", FieldDefinitionFactory.FIELD.MEASUREMENT_SYSTEM),
+            new FieldDefinitionPrimitive(16, BaseType.ENUM, "power_setting"),
+            new FieldDefinitionPrimitive(17, BaseType.ENUM, "activity_class"),
+            new FieldDefinitionPrimitive(18, BaseType.ENUM, "position_setting"),
+            new FieldDefinitionPrimitive(21, BaseType.ENUM, "temperature_setting", FieldDefinitionFactory.FIELD.MEASUREMENT_SYSTEM),
+            new FieldDefinitionPrimitive(28, BaseType.UINT32, "wake_time"),
+            new FieldDefinitionPrimitive(29, BaseType.UINT32, "sleep_time"),
+            new FieldDefinitionPrimitive(30, BaseType.ENUM, "height_setting", FieldDefinitionFactory.FIELD.MEASUREMENT_SYSTEM),
+            new FieldDefinitionPrimitive(31, BaseType.UINT16, "user_running_step_length"),
+            new FieldDefinitionPrimitive(32, BaseType.UINT16, "user_walking_step_length")
+    ));
+
+    public static GlobalFITMessage ZONES_TARGET = new GlobalFITMessage(7, "ZONES_TARGET", Arrays.asList(
+            new FieldDefinitionPrimitive(3, BaseType.UINT16, "functional_threshold_power"),
+            new FieldDefinitionPrimitive(1, BaseType.UINT8, "max_heart_rate"),
+            new FieldDefinitionPrimitive(2, BaseType.UINT8, "threshold_heart_rate"),
+            new FieldDefinitionPrimitive(5, BaseType.ENUM, "hr_calc_type"), //1=percent_max_hr
+            new FieldDefinitionPrimitive(7, BaseType.ENUM, "pwr_calc_type") //1=percent_ftp
+    ));
+
+    public static GlobalFITMessage SPORT = new GlobalFITMessage(12, "SPORT", Arrays.asList(
+            new FieldDefinitionPrimitive(0, BaseType.ENUM, "sport"),
+            new FieldDefinitionPrimitive(1, BaseType.ENUM, "sub_sport"),
+            new FieldDefinitionPrimitive(3, BaseType.STRING, 24, "name")
+    ));
+
+    public static GlobalFITMessage GOALS = new GlobalFITMessage(15, "GOALS", Arrays.asList(
+            new FieldDefinitionPrimitive(4, BaseType.ENUM, "type", FieldDefinitionFactory.FIELD.GOAL_TYPE),
+            new FieldDefinitionPrimitive(7, BaseType.UINT32, "target_value"),
+            new FieldDefinitionPrimitive(11, BaseType.ENUM, "source", FieldDefinitionFactory.FIELD.GOAL_SOURCE)
+    ));
+
+    public static GlobalFITMessage SESSION = new GlobalFITMessage(18, "SESSION", Arrays.asList(
+            new FieldDefinitionPrimitive(0, BaseType.ENUM, "event"), // 9 lap
+            new FieldDefinitionPrimitive(1, BaseType.ENUM, "event_type"), // 1 stop
+            new FieldDefinitionPrimitive(2, BaseType.UINT32, "start_time"),
+            new FieldDefinitionPrimitive(3, BaseType.SINT32, "start_latitude", FieldDefinitionFactory.FIELD.COORDINATE),
+            new FieldDefinitionPrimitive(4, BaseType.SINT32, "start_longitude", FieldDefinitionFactory.FIELD.COORDINATE),
+            new FieldDefinitionPrimitive(5, BaseType.ENUM, "sport"),
+            new FieldDefinitionPrimitive(6, BaseType.ENUM, "sub_sport"),
+            new FieldDefinitionPrimitive(7, BaseType.UINT32, "total_elapsed_time"), // with pauses
+            new FieldDefinitionPrimitive(8, BaseType.UINT32, "total_timer_time"), // no pauses
+            new FieldDefinitionPrimitive(9, BaseType.UINT32, "total_distance"),
+            new FieldDefinitionPrimitive(10, BaseType.UINT32, "total_steps"),
+            new FieldDefinitionPrimitive(11, BaseType.UINT16, "total_calories"),
+            new FieldDefinitionPrimitive(16, BaseType.UINT8, "average_heart_rate"),
+            new FieldDefinitionPrimitive(17, BaseType.UINT8, "max_heart_rate"),
+            new FieldDefinitionPrimitive(18, BaseType.UINT8, "average_cadence"),
+            new FieldDefinitionPrimitive(19, BaseType.UINT8, "max_cadence"),
+            new FieldDefinitionPrimitive(22, BaseType.UINT16, "total_ascent"),
+            new FieldDefinitionPrimitive(23, BaseType.UINT16, "total_descent"),
+            new FieldDefinitionPrimitive(25, BaseType.UINT16, "first_lap_index"),
+            new FieldDefinitionPrimitive(26, BaseType.UINT16, "num_laps"),
+            new FieldDefinitionPrimitive(29, BaseType.SINT32, "nec_latitude", FieldDefinitionFactory.FIELD.COORDINATE),
+            new FieldDefinitionPrimitive(30, BaseType.SINT32, "nec_longitude", FieldDefinitionFactory.FIELD.COORDINATE),
+            new FieldDefinitionPrimitive(31, BaseType.SINT32, "swc_latitude", FieldDefinitionFactory.FIELD.COORDINATE),
+            new FieldDefinitionPrimitive(32, BaseType.SINT32, "swc_longitude", FieldDefinitionFactory.FIELD.COORDINATE),
+            new FieldDefinitionPrimitive(38, BaseType.SINT32, "end_latitude", FieldDefinitionFactory.FIELD.COORDINATE),
+            new FieldDefinitionPrimitive(39, BaseType.SINT32, "end_longitude", FieldDefinitionFactory.FIELD.COORDINATE),
+            new FieldDefinitionPrimitive(110, BaseType.STRING, 64, "sport_profile_name"),
+            new FieldDefinitionPrimitive(124, BaseType.UINT32, "enhanced_avg_speed", 1000, 0), // m/s
+            new FieldDefinitionPrimitive(125, BaseType.UINT32, "enhanced_max_speed", 1000, 0), // m/s
+            new FieldDefinitionPrimitive(169, BaseType.UINT16, "enhanced_avg_respiration_rate", 100, 0  ), // breaths/min
+            new FieldDefinitionPrimitive(170, BaseType.UINT16, "enhanced_max_respiration_rate", 100, 0  ), // breaths/min
+            new FieldDefinitionPrimitive(178, BaseType.UINT16, "estimated_sweat_loss"), // ml
+            new FieldDefinitionPrimitive(180, BaseType.UINT16, "enhanced_min_respiration_rate", 100, 0  ), // breaths/min
+            new FieldDefinitionPrimitive(194, BaseType.UINT8, "avg_spo2"),
+            new FieldDefinitionPrimitive(195, BaseType.UINT8, "avg_stress"),
+            new FieldDefinitionPrimitive(197, BaseType.UINT8, "hrv_sdrr"), // ms
+            new FieldDefinitionPrimitive(198, BaseType.UINT8, "hrv_rmssd"), // ms
+            new FieldDefinitionPrimitive(253, BaseType.UINT32, "timestamp", FieldDefinitionFactory.FIELD.TIMESTAMP)
+    ));
+
+    public static GlobalFITMessage LAP = new GlobalFITMessage(19, "LAP", Arrays.asList(
+            new FieldDefinitionPrimitive(3, BaseType.SINT32, "start_lat", FieldDefinitionFactory.FIELD.COORDINATE),
+            new FieldDefinitionPrimitive(4, BaseType.SINT32, "start_long", FieldDefinitionFactory.FIELD.COORDINATE),
+            new FieldDefinitionPrimitive(5, BaseType.SINT32, "end_lat", FieldDefinitionFactory.FIELD.COORDINATE),
+            new FieldDefinitionPrimitive(6, BaseType.SINT32, "end_long", FieldDefinitionFactory.FIELD.COORDINATE),
+            new FieldDefinitionPrimitive(7, BaseType.UINT32, "total_elapsed_time", 1000, 0), // s
+            new FieldDefinitionPrimitive(8, BaseType.UINT32, "total_timer_time", 1000, 0), // s
+            new FieldDefinitionPrimitive(9, BaseType.UINT32, "total_distance", 100, 0), // m
+            new FieldDefinitionPrimitive(21, BaseType.UINT16, "total_ascent"), // m
+            new FieldDefinitionPrimitive(22, BaseType.UINT16, "total_descent"), // m
+            new FieldDefinitionPrimitive(253, BaseType.UINT32, "timestamp", FieldDefinitionFactory.FIELD.TIMESTAMP)
+    ));
+
+    public static GlobalFITMessage RECORD = new GlobalFITMessage(20, "RECORD", Arrays.asList(
+            new FieldDefinitionPrimitive(0, BaseType.SINT32, "latitude", FieldDefinitionFactory.FIELD.COORDINATE),
+            new FieldDefinitionPrimitive(1, BaseType.SINT32, "longitude", FieldDefinitionFactory.FIELD.COORDINATE),
+            new FieldDefinitionPrimitive(2, BaseType.UINT16, "altitude", 5, 500), // m
+            new FieldDefinitionPrimitive(3, BaseType.UINT8, "heart_rate"),
+            new FieldDefinitionPrimitive(4, BaseType.UINT8, "cadence"), // rpm
+            new FieldDefinitionPrimitive(5, BaseType.UINT32, "distance", 100, 0), // m
+            new FieldDefinitionPrimitive(6, BaseType.UINT16, "speed", 1000, 0), // m/s
+            new FieldDefinitionPrimitive(7, BaseType.UINT16, "power"), // watt
+            new FieldDefinitionPrimitive(29, BaseType.UINT32, "accumulated_power"), // watt
+            new FieldDefinitionPrimitive(39, BaseType.UINT16, "oscillation", 10, 0), // mm
+            new FieldDefinitionPrimitive(42, BaseType.ENUM, "activity"),
+            new FieldDefinitionPrimitive(53, BaseType.UINT8, "fractional_cadence", 128, 0), // rpm
+            new FieldDefinitionPrimitive(73, BaseType.UINT32, "enhanced_speed", 1000, 0), // mm/s
+            new FieldDefinitionPrimitive(78, BaseType.UINT32, "enhanced_altitude", 5, 500), // m
+            new FieldDefinitionPrimitive(83, BaseType.UINT16, "vertical_ratio", 100, 0), // %
+            new FieldDefinitionPrimitive(85, BaseType.UINT16, "step_length", 10, 0), // mm
+            new FieldDefinitionPrimitive(136, BaseType.UINT8, "wrist_heart_rate"),
+            new FieldDefinitionPrimitive(143, BaseType.UINT8, "body_battery"),
+            new FieldDefinitionPrimitive(253, BaseType.UINT32, "timestamp", FieldDefinitionFactory.FIELD.TIMESTAMP)
+    ));
+
+    public static GlobalFITMessage EVENT = new GlobalFITMessage(21, "EVENT", Arrays.asList(
+            new FieldDefinitionPrimitive(0, BaseType.ENUM, "event"), // 0 timer, 74 sleep
+            new FieldDefinitionPrimitive(1, BaseType.ENUM, "event_type"), // sleep: 0 start 1 stop, timer: 0 start 4 stop all
+            new FieldDefinitionPrimitive(3, BaseType.UINT32, "data"), // in sleep they're timestamps in garmin epoch? in timer, 0 for manual
+            new FieldDefinitionPrimitive(4, BaseType.UINT8, "event_group"), // 0?
+            new FieldDefinitionPrimitive(253, BaseType.UINT32, "timestamp", FieldDefinitionFactory.FIELD.TIMESTAMP)
+    ));
+
+    public static GlobalFITMessage DEVICE_INFO = new GlobalFITMessage(23, "DEVICE_INFO", Arrays.asList(
+            new FieldDefinitionPrimitive(2, BaseType.UINT16, "manufacturer"),
+            new FieldDefinitionPrimitive(3, BaseType.UINT32Z, "serial_number"),
+            new FieldDefinitionPrimitive(4, BaseType.UINT16, "product"),
+            new FieldDefinitionPrimitive(5, BaseType.UINT16, "software_version"),
+            new FieldDefinitionPrimitive(253, BaseType.UINT32, "timestamp", FieldDefinitionFactory.FIELD.TIMESTAMP)
+    ));
+
+    public static GlobalFITMessage WORKOUT = new GlobalFITMessage(26, "WORKOUT", Arrays.asList(
+            new FieldDefinitionPrimitive(4, BaseType.ENUM, "sport"),
+            new FieldDefinitionPrimitive(5, BaseType.UINT32Z, "capabilities"),
+            new FieldDefinitionPrimitive(6, BaseType.UINT16, "num_valid_steps"),
+            new FieldDefinitionPrimitive(8, BaseType.STRING, "name"),
+            new FieldDefinitionPrimitive(11, BaseType.ENUM, "sub_sport"),
+            new FieldDefinitionPrimitive(17, BaseType.STRING, "notes")
+    ));
+
+    public static GlobalFITMessage COURSE = new GlobalFITMessage(31, "COURSE", Arrays.asList(
+            new FieldDefinitionPrimitive(4, BaseType.ENUM, "sport"),
+            new FieldDefinitionPrimitive(5, BaseType.STRING, 16, "name")
+    ));
+
+    public static GlobalFITMessage FILE_CREATOR = new GlobalFITMessage(49, "FILE_CREATOR", Arrays.asList(
+            new FieldDefinitionPrimitive(0, BaseType.UINT16, "software_version"),
+            new FieldDefinitionPrimitive(1, BaseType.UINT8, "hardware_version")
+    ));
+
+    public static GlobalFITMessage MONITORING = new GlobalFITMessage(55, "MONITORING", Arrays.asList(
+            new FieldDefinitionPrimitive(2, BaseType.UINT32, "distance"),
+            new FieldDefinitionPrimitive(3, BaseType.UINT32, "cycles"),
+            new FieldDefinitionPrimitive(4, BaseType.UINT32, "active_time"),
+            new FieldDefinitionPrimitive(5, BaseType.ENUM, "activity_type"),
+            new FieldDefinitionPrimitive(19, BaseType.UINT16, "active_calories"),
+            new FieldDefinitionPrimitive(29, BaseType.UINT16, "duration_min"),
+            new FieldDefinitionPrimitive(24, BaseType.BASE_TYPE_BYTE, "current_activity_type_intensity"),
+            new FieldDefinitionPrimitive(26, BaseType.UINT16, "timestamp_16"),
+            new FieldDefinitionPrimitive(27, BaseType.UINT8, "heart_rate"),
+            new FieldDefinitionPrimitive(33, BaseType.UINT16, "moderate_activity_minutes"),
+            new FieldDefinitionPrimitive(34, BaseType.UINT16, "vigorous_activity_minutes"),
+            new FieldDefinitionPrimitive(253, BaseType.UINT32, "timestamp", FieldDefinitionFactory.FIELD.TIMESTAMP)
+    ));
+
+    public static GlobalFITMessage MONITORING_INFO = new GlobalFITMessage(103, "MONITORING_INFO", Arrays.asList(
+            new FieldDefinitionPrimitive(0, BaseType.UINT32, "local_timestamp"), // garmin timestamp, but in user timezone
+            new FieldDefinitionPrimitive(1, BaseType.ENUM, "activity_type", FieldDefinitionFactory.FIELD.ARRAY), // 6 walking, 1 running, 13 ?
+            new FieldDefinitionPrimitive(3, BaseType.UINT16, "steps_to_distance", FieldDefinitionFactory.FIELD.ARRAY, 5000, 0), // same size as activity_type?
+            new FieldDefinitionPrimitive(4, BaseType.UINT16, "steps_to_calories", FieldDefinitionFactory.FIELD.ARRAY, 5000, 0), // same size as activity_type?
+            new FieldDefinitionPrimitive(5, BaseType.UINT16, "resting_metabolic_rate"), // kcal/day
+            new FieldDefinitionPrimitive(253, BaseType.UINT32, "timestamp", FieldDefinitionFactory.FIELD.TIMESTAMP)
+    ));
+
+    public static GlobalFITMessage CONNECTIVITY = new GlobalFITMessage(127, "CONNECTIVITY", Arrays.asList(
+            new FieldDefinitionPrimitive(0, BaseType.ENUM, "bluetooth_enabled"),
+            new FieldDefinitionPrimitive(3, BaseType.STRING, 20, "name"),
+            new FieldDefinitionPrimitive(4, BaseType.ENUM, "live_tracking_enabled"),
+            new FieldDefinitionPrimitive(5, BaseType.ENUM, "weather_conditions_enabled"),
+            new FieldDefinitionPrimitive(6, BaseType.ENUM, "weather_alerts_enabled"),
+            new FieldDefinitionPrimitive(7, BaseType.ENUM, "auto_activity_upload_enabled"),
+            new FieldDefinitionPrimitive(8, BaseType.ENUM, "course_download_enabled"),
+            new FieldDefinitionPrimitive(9, BaseType.ENUM, "workout_download_enabled"),
+            new FieldDefinitionPrimitive(10, BaseType.ENUM, "gps_ephemeris_download_enabled")
+    ));
+
+    public static GlobalFITMessage WEATHER = new GlobalFITMessage(128, "WEATHER", Arrays.asList(
+            new FieldDefinitionPrimitive(0, BaseType.ENUM, "weather_report"),
+            new FieldDefinitionPrimitive(1, BaseType.SINT8, "temperature", FieldDefinitionFactory.FIELD.TEMPERATURE),
+            new FieldDefinitionPrimitive(2, BaseType.ENUM, "condition", FieldDefinitionFactory.FIELD.WEATHER_CONDITION),
+            new FieldDefinitionPrimitive(3, BaseType.UINT16, "wind_direction"),
+            new FieldDefinitionPrimitive(4, BaseType.UINT16, "wind_speed", 298, 0),
+            new FieldDefinitionPrimitive(5, BaseType.UINT8, "precipitation_probability"),
+            new FieldDefinitionPrimitive(6, BaseType.SINT8, "temperature_feels_like", FieldDefinitionFactory.FIELD.TEMPERATURE),
+            new FieldDefinitionPrimitive(7, BaseType.UINT8, "relative_humidity"),
+            new FieldDefinitionPrimitive(8, BaseType.STRING, 15, "location"),
+            new FieldDefinitionPrimitive(9, BaseType.UINT32, "observed_at_time", FieldDefinitionFactory.FIELD.TIMESTAMP),
+            new FieldDefinitionPrimitive(10, BaseType.SINT32, "observed_location_lat"),
+            new FieldDefinitionPrimitive(11, BaseType.SINT32, "observed_location_long"),
+            new FieldDefinitionPrimitive(12, BaseType.ENUM, "day_of_week", FieldDefinitionFactory.FIELD.DAY_OF_WEEK),
+            new FieldDefinitionPrimitive(13, BaseType.SINT8, "high_temperature", FieldDefinitionFactory.FIELD.TEMPERATURE),
+            new FieldDefinitionPrimitive(14, BaseType.SINT8, "low_temperature", FieldDefinitionFactory.FIELD.TEMPERATURE),
+            new FieldDefinitionPrimitive(15, BaseType.SINT8, "dew_point", FieldDefinitionFactory.FIELD.TEMPERATURE),
+            new FieldDefinitionPrimitive(16, BaseType.FLOAT32, "uv_index"),
+            new FieldDefinitionPrimitive(17, BaseType.ENUM, "air_quality", FieldDefinitionFactory.FIELD.WEATHER_AQI),
+            new FieldDefinitionPrimitive(253, BaseType.UINT32, "timestamp", FieldDefinitionFactory.FIELD.TIMESTAMP)
+    ));
+
+    // https://github.com/GoldenCheetah/GoldenCheetah/blob/71e3928bc614f3209d9977d90cc50b942999b855/src/FileIO/FitRideFile.cpp#L1998
+    public static GlobalFITMessage PHYSIOLOGICAL_METRICS = new GlobalFITMessage(140, "PHYSIOLOGICAL_METRICS", Arrays.asList(
+            new FieldDefinitionPrimitive(4, BaseType.UINT8, "aerobic_effect", 10, 0),
+            new FieldDefinitionPrimitive(7, BaseType.SINT32, "met_max", 65536, 0),
+            new FieldDefinitionPrimitive(9, BaseType.UINT16, "recovery_time", 1, 0), // minutes
+            new FieldDefinitionPrimitive(14, BaseType.UINT16, "lactate_threshold_heart_rate", 1, 0), // bpm
+            //new FieldDefinitionPrimitive(15, BaseType.UINT16, "lactate_threshold_speed", 1, 0), // m/s // TODO confirm scale
+            new FieldDefinitionPrimitive(20, BaseType.UINT8, "anaerobic_effect", 10, 0),
+            new FieldDefinitionPrimitive(253, BaseType.UINT32, "timestamp", FieldDefinitionFactory.FIELD.TIMESTAMP)
+    ));
+
+    public static GlobalFITMessage WATCHFACE_SETTINGS = new GlobalFITMessage(159, "WATCHFACE_SETTINGS", Arrays.asList(
+            new FieldDefinitionPrimitive(0, BaseType.ENUM, "mode"), //1=analog
+            new FieldDefinitionPrimitive(1, BaseType.BASE_TYPE_BYTE, "layout")
+    ));
+
+    public static GlobalFITMessage GPS_METADATA = new GlobalFITMessage(160, "GPS_METADATA", Arrays.asList(
+            new FieldDefinitionPrimitive(3, BaseType.UINT32, "enhanced_altitude"),
+            new FieldDefinitionPrimitive(4, BaseType.UINT32, "enhanced_speed")
+    ));
+
+    public static GlobalFITMessage TIMESTAMP_CORRELATION = new GlobalFITMessage(162, "TIMESTAMP_CORRELATION", Arrays.asList(
+            new FieldDefinitionPrimitive(3, BaseType.UINT32, "local_timestamp"), // garmin timestamp, but in user timezone
+            new FieldDefinitionPrimitive(253, BaseType.UINT32, "timestamp", FieldDefinitionFactory.FIELD.TIMESTAMP)
+    ));
+
+    public static GlobalFITMessage FIELD_DESCRIPTION = new GlobalFITMessage(206, "FIELD_DESCRIPTION", Arrays.asList(
+            new FieldDefinitionPrimitive(0, BaseType.UINT8, "developer_data_index"),
+            new FieldDefinitionPrimitive(1, BaseType.UINT8, "field_definition_number"),
+            new FieldDefinitionPrimitive(2, BaseType.UINT8, "fit_base_type_id"),
+            new FieldDefinitionPrimitive(3, BaseType.STRING, 64, "field_name"),
+            new FieldDefinitionPrimitive(8, BaseType.STRING, 16, "units")
+    ));
+
+    public static GlobalFITMessage DEVELOPER_DATA = new GlobalFITMessage(207, "DEVELOPER_DATA", Arrays.asList(
+            new FieldDefinitionPrimitive(1, BaseType.BASE_TYPE_BYTE, 16, "application_id"),
+            new FieldDefinitionPrimitive(3, BaseType.UINT8, "developer_data_index")
+    ));
+
+    public static GlobalFITMessage MONITORING_HR_DATA = new GlobalFITMessage(211, "MONITORING_HR_DATA", Arrays.asList(
+            new FieldDefinitionPrimitive(0, BaseType.UINT8, "resting_heart_rate"),
+            new FieldDefinitionPrimitive(1, BaseType.UINT8, "current_day_resting_heart_rate"),
+            new FieldDefinitionPrimitive(253, BaseType.UINT32, "timestamp", FieldDefinitionFactory.FIELD.TIMESTAMP)
+    ));
+
+    public static GlobalFITMessage TIME_IN_ZONE = new GlobalFITMessage(216, "TIME_IN_ZONE", Arrays.asList(
+            new FieldDefinitionPrimitive(0, BaseType.UINT16, "reference_message"),
+            new FieldDefinitionPrimitive(1, BaseType.UINT16, "reference_index"),
+            new FieldDefinitionPrimitive(2, BaseType.UINT32, "time_in_zone", FieldDefinitionFactory.FIELD.HR_TIME_IN_ZONE), // seconds
+            new FieldDefinitionPrimitive(6, BaseType.UINT8, "hr_zone_high_boundary", FieldDefinitionFactory.FIELD.HR_ZONE_HIGH_BOUNDARY), // bpm
+            new FieldDefinitionPrimitive(10, BaseType.ENUM, "hr_calc_type"), // 1 percent max hr
+            new FieldDefinitionPrimitive(11, BaseType.UINT8, "max_heart_rate"),
+            new FieldDefinitionPrimitive(12, BaseType.UINT8, "resting_heart_rate"),
+            new FieldDefinitionPrimitive(13, BaseType.UINT8, "threshold_heart_rate"),
+            new FieldDefinitionPrimitive(253, BaseType.UINT32, "timestamp", FieldDefinitionFactory.FIELD.TIMESTAMP)
+    ));
+
+    public static GlobalFITMessage ALARM_SETTINGS = new GlobalFITMessage(222, "ALARM_SETTINGS", Arrays.asList(
+            new FieldDefinitionPrimitive(0, BaseType.UINT16, "time", FieldDefinitionFactory.FIELD.ALARM)
+    ));
+
+    public static GlobalFITMessage SET = new GlobalFITMessage(225, "SET", Arrays.asList(
+            new FieldDefinitionPrimitive(0, BaseType.UINT32, "duration", 1000, 0), // seconds
+            new FieldDefinitionPrimitive(3, BaseType.UINT16, "repetitions"),
+            new FieldDefinitionPrimitive(4, BaseType.UINT16, "weight", 16, 0), // kg
+            new FieldDefinitionPrimitive(5, BaseType.UINT8, "set_type"), // 1 active 0 rest
+            new FieldDefinitionPrimitive(6, BaseType.UINT32, "start_time", FieldDefinitionFactory.FIELD.TIMESTAMP),
+            new FieldDefinitionPrimitive(7, BaseType.UINT16, "category"),
+            new FieldDefinitionPrimitive(10, BaseType.UINT16, "message_index"),
+            new FieldDefinitionPrimitive(254, BaseType.UINT32, "timestamp", FieldDefinitionFactory.FIELD.TIMESTAMP)
+    ));
+
+    public static GlobalFITMessage STRESS_LEVEL = new GlobalFITMessage(227, "STRESS_LEVEL", Arrays.asList(
+            new FieldDefinitionPrimitive(0, BaseType.SINT16, "stress_level_value"),
+            new FieldDefinitionPrimitive(1, BaseType.UINT32, "stress_level_time", FieldDefinitionFactory.FIELD.TIMESTAMP),
+            new FieldDefinitionPrimitive(3, BaseType.SINT8, "body_energy")
+    ));
+
+    public static GlobalFITMessage SPO2 = new GlobalFITMessage(269, "SPO2", Arrays.asList(
+            new FieldDefinitionPrimitive(0, BaseType.UINT8, "reading_spo2"),
+            new FieldDefinitionPrimitive(1, BaseType.UINT8, "reading_confidence"),
+            new FieldDefinitionPrimitive(2, BaseType.UINT8, "mode"), // 3 periodic
+            new FieldDefinitionPrimitive(253, BaseType.UINT32, "timestamp", FieldDefinitionFactory.FIELD.TIMESTAMP)
+    ));
+
+    public static GlobalFITMessage SLEEP_DATA_INFO = new GlobalFITMessage(273, "SLEEP_DATA_INFO", Arrays.asList(
+            new FieldDefinitionPrimitive(0, BaseType.UINT8, "unk0"), // 2
+            new FieldDefinitionPrimitive(1, BaseType.UINT16, "sample_length"), // 60, sample time?
+            new FieldDefinitionPrimitive(2, BaseType.UINT32, "local_timestamp"), // garmin timestamp, but in user timezone
+            new FieldDefinitionPrimitive(3, BaseType.ENUM, "unk3"), // 1
+            new FieldDefinitionPrimitive(4, BaseType.STRING, "version"), // matches ETE in settings
+            new FieldDefinitionPrimitive(253, BaseType.UINT32, "timestamp", FieldDefinitionFactory.FIELD.TIMESTAMP)
+    ));
+
+    public static GlobalFITMessage SLEEP_DATA_RAW = new GlobalFITMessage(274, "SLEEP_DATA_RAW", Arrays.asList(
+            new FieldDefinitionPrimitive(0, BaseType.BASE_TYPE_BYTE, "bytes") // arr of 20 bytes per sample
+    ));
+
+    public static GlobalFITMessage SLEEP_STAGE = new GlobalFITMessage(275, "SLEEP_STAGE", Arrays.asList(
+            new FieldDefinitionPrimitive(0, BaseType.ENUM, "sleep_stage", FieldDefinitionFactory.FIELD.SLEEP_STAGE),
+            new FieldDefinitionPrimitive(253, BaseType.UINT32, "timestamp", FieldDefinitionFactory.FIELD.TIMESTAMP)
+    ));
+
+    public static GlobalFITMessage RESPIRATION_RATE = new GlobalFITMessage(297, "RESPIRATION_RATE", Arrays.asList(
+            new FieldDefinitionPrimitive(0, BaseType.SINT16, "respiration_rate", 100, 0), // breaths / min
+            new FieldDefinitionPrimitive(253, BaseType.UINT32, "timestamp", FieldDefinitionFactory.FIELD.TIMESTAMP)
+    ));
+
+    public static GlobalFITMessage SLEEP_STATS = new GlobalFITMessage(346, "SLEEP_STATS", Arrays.asList(
+            new FieldDefinitionPrimitive(0, BaseType.UINT8, "combined_awake_score"),
+            new FieldDefinitionPrimitive(1, BaseType.UINT8, "awake_time_score"),
+            new FieldDefinitionPrimitive(2, BaseType.UINT8, "awakenings_count_score"),
+            new FieldDefinitionPrimitive(3, BaseType.UINT8, "deep_sleep_score"),
+            new FieldDefinitionPrimitive(4, BaseType.UINT8, "sleep_duration_score"),
+            new FieldDefinitionPrimitive(5, BaseType.UINT8, "light_sleep_score"),
+            new FieldDefinitionPrimitive(6, BaseType.UINT8, "overall_sleep_score"),
+            new FieldDefinitionPrimitive(7, BaseType.UINT8, "sleep_quality_score"),
+            new FieldDefinitionPrimitive(8, BaseType.UINT8, "sleep_recovery_score"),
+            new FieldDefinitionPrimitive(9, BaseType.UINT8, "rem_sleep_score"),
+            new FieldDefinitionPrimitive(10, BaseType.UINT8, "sleep_restlessness_score"),
+            new FieldDefinitionPrimitive(11, BaseType.UINT8, "awakenings_count"),
+            new FieldDefinitionPrimitive(12, BaseType.ENUM, "unk_12"),
+            new FieldDefinitionPrimitive(13, BaseType.ENUM, "unk_13"),
+            new FieldDefinitionPrimitive(14, BaseType.UINT8, "interruptions_score"),
+            new FieldDefinitionPrimitive(15, BaseType.UINT16, "average_stress_during_sleep", 100, 0),
+            new FieldDefinitionPrimitive(16, BaseType.ENUM, "unk_16")
+    ));
+
+    public static GlobalFITMessage HRV_SUMMARY = new GlobalFITMessage(370, "HRV_SUMMARY", Arrays.asList(
+            new FieldDefinitionPrimitive(0, BaseType.UINT16, "weekly_average", 128, 0), // milliseconds, scaled by 128
+            new FieldDefinitionPrimitive(1, BaseType.UINT16, "last_night_average", 128, 0), // milliseconds, scaled by 128
+            new FieldDefinitionPrimitive(2, BaseType.UINT16, "last_night_5_min_high", 128, 0), // milliseconds, scaled by 128
+            new FieldDefinitionPrimitive(3, BaseType.UINT16, "baseline_low_upper", 128, 0), // milliseconds, scaled by 128
+            new FieldDefinitionPrimitive(4, BaseType.UINT16, "baseline_balanced_lower", 128, 0), // milliseconds, scaled by 128
+            new FieldDefinitionPrimitive(5, BaseType.UINT16, "baseline_balanced_upper", 128, 0), // milliseconds, scaled by 128
+            new FieldDefinitionPrimitive(6, BaseType.ENUM, "status", FieldDefinitionFactory.FIELD.HRV_STATUS),
+            new FieldDefinitionPrimitive(253, BaseType.UINT32, "timestamp", FieldDefinitionFactory.FIELD.TIMESTAMP)
+    ));
+
+    public static GlobalFITMessage HRV_VALUE = new GlobalFITMessage(371, "HRV_VALUE", Arrays.asList(
+            new FieldDefinitionPrimitive(0, BaseType.UINT16, "value", 128, 0), // milliseconds, scaled by 128
+            new FieldDefinitionPrimitive(253, BaseType.UINT32, "timestamp", FieldDefinitionFactory.FIELD.TIMESTAMP)
+    ));
+
+    public static GlobalFITMessage SKIN_TEMP_RAW = new GlobalFITMessage(397, "SKIN_TEMP_RAW", Arrays.asList(
+            new FieldDefinitionPrimitive(1, BaseType.FLOAT32, "deviation"),
+            new FieldDefinitionPrimitive(253, BaseType.UINT32, "timestamp", FieldDefinitionFactory.FIELD.TIMESTAMP)
+    ));
+
+    public static GlobalFITMessage SKIN_TEMP_OVERNIGHT = new GlobalFITMessage(398, "SKIN_TEMP_OVERNIGHT", Arrays.asList(
+            new FieldDefinitionPrimitive(0, BaseType.UINT32, "local_timestamp"), // garmin timestamp, but in user timezone
+            new FieldDefinitionPrimitive(1, BaseType.FLOAT32, "average_deviation"),
+            new FieldDefinitionPrimitive(2, BaseType.FLOAT32, "average_7_day_deviation"),
+            new FieldDefinitionPrimitive(3, BaseType.UINT8, "unk3"),
+            new FieldDefinitionPrimitive(253, BaseType.UINT32, "timestamp", FieldDefinitionFactory.FIELD.TIMESTAMP)
+    ));
+
+    public static Map<Integer, GlobalFITMessage> KNOWN_MESSAGES = new HashMap<Integer, GlobalFITMessage>() {{
+        put(0, FILE_ID);
+        put(2, DEVICE_SETTINGS);
+        put(3, USER_PROFILE);
+        put(7, ZONES_TARGET);
+        put(12, SPORT);
+        put(15, GOALS);
+        put(18, SESSION);
+        put(19, LAP);
+        put(20, RECORD);
+        put(21, EVENT);
+        put(23, DEVICE_INFO);
+        put(26, WORKOUT);
+        put(31, COURSE);
+        put(49, FILE_CREATOR);
+        put(55, MONITORING);
+        put(103, MONITORING_INFO);
+        put(127, CONNECTIVITY);
+        put(128, WEATHER);
+        put(140, PHYSIOLOGICAL_METRICS);
+        put(159, WATCHFACE_SETTINGS);
+        put(160, GPS_METADATA);
+        put(162, TIMESTAMP_CORRELATION);
+        put(206, FIELD_DESCRIPTION);
+        put(207, DEVELOPER_DATA);
+        put(211, MONITORING_HR_DATA);
+        put(216, TIME_IN_ZONE);
+        put(222, ALARM_SETTINGS);
+        put(225, SET);
+        put(227, STRESS_LEVEL);
+        put(269, SPO2);
+        put(273, SLEEP_DATA_INFO);
+        put(274, SLEEP_DATA_RAW);
+        put(275, SLEEP_STAGE);
+        put(297, RESPIRATION_RATE);
+        put(346, SLEEP_STATS);
+        put(370, HRV_SUMMARY);
+        put(371, HRV_VALUE);
+        put(397, SKIN_TEMP_RAW);
+        put(398, SKIN_TEMP_OVERNIGHT);
+    }};
+
+    private final int number;
+    private final String name;
+
+    private final List<FieldDefinitionPrimitive> fieldDefinitionPrimitives;
+
+    GlobalFITMessage(int number, String name, List<FieldDefinitionPrimitive> fieldDefinitionPrimitives) {
+        this.number = number;
+        this.name = name;
+        this.fieldDefinitionPrimitives = fieldDefinitionPrimitives;
+    }
+
+    public static GlobalFITMessage fromNumber(final int number) {
+        final GlobalFITMessage found = KNOWN_MESSAGES.get(number);
+        if (found != null) {
+            return found;
+        }
+        return new GlobalFITMessage(number, "UNK_" + number, null);
+    }
+
+    public String name() {
+        return this.name;
+    }
+
+    public int getNumber() {
+        return number;
+    }
+
+    public List<FieldDefinitionPrimitive> getFieldDefinitionPrimitives() {
+        return fieldDefinitionPrimitives;
+    }
+
+    @Nullable
+    public List<FieldDefinition> getFieldDefinitions(int... ids) {
+        if (null == fieldDefinitionPrimitives)
+            return null;
+        List<FieldDefinition> subset = new ArrayList<>(ids.length);
+        for (int id :
+                ids) {
+            for (FieldDefinitionPrimitive fieldDefinitionPrimitive :
+                    fieldDefinitionPrimitives) {
+                if (fieldDefinitionPrimitive.number == id) {
+                    subset.add(FieldDefinitionFactory.create(
+                            fieldDefinitionPrimitive.number,
+                            fieldDefinitionPrimitive.size,
+                            fieldDefinitionPrimitive.type,
+                            fieldDefinitionPrimitive.baseType,
+                            fieldDefinitionPrimitive.name,
+                            fieldDefinitionPrimitive.scale,
+                            fieldDefinitionPrimitive.offset
+                    ));
+                }
+            }
+        }
+        return subset;
+    }
+
+    @Nullable
+    public FieldDefinition getFieldDefinition(int id, int size) {
+        if (null == fieldDefinitionPrimitives)
+            return null;
+        for (GlobalFITMessage.FieldDefinitionPrimitive fieldDefinitionPrimitive :
+                fieldDefinitionPrimitives) {
+            if (fieldDefinitionPrimitive.number == id) {
+                return FieldDefinitionFactory.create(
+                        fieldDefinitionPrimitive.number,
+                        size,
+                        fieldDefinitionPrimitive.type,
+                        fieldDefinitionPrimitive.baseType,
+                        fieldDefinitionPrimitive.name,
+                        fieldDefinitionPrimitive.scale,
+                        fieldDefinitionPrimitive.offset
+                );
+            }
+        }
+        return null;
+    }
+
+    public static class FieldDefinitionPrimitive {
+        private final int number;
+        private final BaseType baseType;
+        private final String name;
+        private final FieldDefinitionFactory.FIELD type;
+        private final int scale;
+        private final int offset;
+        private final int size;
+
+        public FieldDefinitionPrimitive(int number, BaseType baseType, int size, String name, FieldDefinitionFactory.FIELD type, int scale, int offset) {
+            this.number = number;
+            this.baseType = baseType;
+            this.size = size;
+            this.name = name;
+            this.type = type;
+            this.scale = scale;
+            this.offset = offset;
+        }
+
+        public FieldDefinitionPrimitive(int number, BaseType baseType, String name, FieldDefinitionFactory.FIELD type, int scale, int offset) {
+            this(number, baseType, baseType.getSize(), name, type, scale, offset);
+        }
+
+        public FieldDefinitionPrimitive(int number, BaseType baseType, String name, FieldDefinitionFactory.FIELD type) {
+            this(number, baseType, baseType.getSize(), name, type, 1, 0);
+        }
+
+        public FieldDefinitionPrimitive(int number, BaseType baseType, String name) {
+            this(number, baseType, baseType.getSize(), name, null, 1, 0);
+        }
+
+        public FieldDefinitionPrimitive(int number, BaseType baseType, int size, String name) {
+            this(number, baseType, size, name, null, 1, 0);
+        }
+
+        public FieldDefinitionPrimitive(int number, BaseType baseType, String name, int scale, int offset) {
+            this(number, baseType, baseType.getSize(), name, null, scale, offset);
+        }
+
+        public int getNumber() {
+            return number;
+        }
+
+        public BaseType getBaseType() {
+            return baseType;
+        }
+
+        public String getName() {
+            return name;
+        }
+
+        public FieldDefinitionFactory.FIELD getType() {
+            return type;
+        }
+
+        public int getScale() {
+            return scale;
+        }
+
+        public int getOffset() {
+            return offset;
+        }
+
+        public int getSize() {
+            return size;
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/GpxRouteFileConverter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/GpxRouteFileConverter.java
new file mode 100644
index 0000000..faec080
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/GpxRouteFileConverter.java
@@ -0,0 +1,196 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.model.GPSCoordinate;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.FileType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.enums.GarminSport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitRecordDataFactory;
+import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.gpx.GpxParser;
+import nodomain.freeyourgadget.gadgetbridge.util.gpx.model.GpxFile;
+import nodomain.freeyourgadget.gadgetbridge.util.gpx.model.GpxTrack;
+import nodomain.freeyourgadget.gadgetbridge.util.gpx.model.GpxTrackPoint;
+import nodomain.freeyourgadget.gadgetbridge.util.gpx.model.GpxTrackSegment;
+
+public class GpxRouteFileConverter {
+    private static final Logger LOG = LoggerFactory.getLogger(GpxRouteFileConverter.class);
+    final double speed = 1.4; // m/s // TODO: make this configurable (and activity dependent?)
+    final int activity = GarminSport.GENERIC.getType(); //TODO: make this configurable
+    private final long timestamp;
+    private final GpxFile gpxFile;
+    private FitFile convertedFile;
+    private String name;
+
+    public GpxRouteFileConverter(byte[] xmlBytes) {
+        this.timestamp = System.currentTimeMillis() / 1000;
+        this.gpxFile = GpxParser.parseGpx(xmlBytes);
+        try {
+            this.convertedFile = convertGpxToRoute(gpxFile);
+        } catch (Exception e) {
+            LOG.error(e.getMessage());
+            this.convertedFile = null;
+        }
+    }
+
+    private static RecordData getFileCreatorRecordData() {
+        final RecordData fileCreatorRecord = FitRecordDataFactory.create(
+                new RecordDefinition(new RecordHeader((byte) 0x41), ByteOrder.BIG_ENDIAN, GlobalFITMessage.FILE_CREATOR, GlobalFITMessage.FILE_CREATOR.getFieldDefinitions(0), null),
+                new RecordHeader((byte) 0x01));
+        fileCreatorRecord.setFieldByName("software_version", 1);
+        return fileCreatorRecord;
+    }
+
+    public FitFile getConvertedFile() {
+        return convertedFile;
+    }
+
+    public boolean isValid() {
+        return this.convertedFile != null;
+    }
+
+    public String getName() {
+        if (gpxFile == null) {
+            return "";
+        }
+
+        if (!StringUtils.isNullOrEmpty(this.name))
+            return this.name;
+
+        if (!StringUtils.isNullOrEmpty(gpxFile.getName())) {
+            return gpxFile.getName();
+        } else {
+            return String.valueOf(timestamp);
+        }
+    }
+
+    private FitFile convertGpxToRoute(GpxFile gpxFile) {
+        if (gpxFile.getTracks().isEmpty()) {
+            LOG.error("Gpx file contains no Tracks.");
+            return null;
+        }
+        //GPX files may contain multiple tracks, we use only the first one
+        final GpxTrack track = gpxFile.getTracks().get(0);
+
+        if (track.getTrackSegments().isEmpty()) {
+            LOG.error("Gpx track contains no segment.");
+            return null;
+        }
+        //GPX track may contain multiple segments, we use only the first one
+        GpxTrackSegment gpxTrackSegment = track.getTrackSegments().get(0);
+
+        List<GpxTrackPoint> gpxTrackPointList = gpxTrackSegment.getTrackPoints();
+        if (gpxTrackPointList.isEmpty()) {
+            LOG.error("Gpx track segment contains no point");
+            return null;
+        }
+
+        this.name = track.getName();
+
+        final RecordHeader gpxDataPointRecordHeader = new RecordHeader((byte) 0x05);
+        final RecordDefinition gpxDataPointRecordDefinition = new RecordDefinition(new RecordHeader((byte) 0x45), ByteOrder.BIG_ENDIAN, GlobalFITMessage.RECORD, GlobalFITMessage.RECORD.getFieldDefinitions(0, 1, 2, 5, 253), null);
+        List<RecordData> gpxPointDataRecords = new ArrayList<>();
+
+        double totalAscent = 0;
+        double totalDescent = 0;
+        double totalDistance = 0;
+        long runningTs = timestamp;
+
+        GPSCoordinate prevPoint = gpxTrackPointList.get(0);
+
+        for (GPSCoordinate point :
+                gpxTrackPointList) {
+            totalAscent += point.getAscent(prevPoint);
+            totalDescent += point.getDescent(prevPoint);
+            totalDistance += point.getDistance(prevPoint);
+            runningTs += (long) (point.getDistance(prevPoint) / speed);
+            final RecordData gpxDataPointRecord = FitRecordDataFactory.create(gpxDataPointRecordDefinition, gpxDataPointRecordHeader);
+
+            gpxDataPointRecord.setFieldByName("latitude", point.getLatitude());
+            gpxDataPointRecord.setFieldByName("longitude", point.getLongitude());
+            gpxDataPointRecord.setFieldByName("altitude", point.getAltitude());
+            gpxDataPointRecord.setFieldByName("distance", totalDistance);
+            gpxDataPointRecord.setFieldByName("timestamp", runningTs);
+
+            prevPoint = point;
+            gpxPointDataRecords.add(gpxDataPointRecord);
+        }
+
+        final RecordData lapRecord = getLapRecordData(gpxTrackPointList);
+        lapRecord.setFieldByName("total_distance", totalDistance);
+        lapRecord.setFieldByName("total_ascent", totalAscent);
+        lapRecord.setFieldByName("total_descent", totalDescent);
+        lapRecord.setFieldByName("total_elapsed_time", (runningTs - timestamp));
+        lapRecord.setFieldByName("total_timer_time", (runningTs - timestamp));
+
+        final List<RecordData> courseFileDataRecords = new ArrayList<>();
+        courseFileDataRecords.add(getFileIdRecordData());
+        courseFileDataRecords.add(getFileCreatorRecordData());
+        courseFileDataRecords.add(getCourseRecordData());
+        courseFileDataRecords.add(lapRecord);
+
+        final RecordHeader eventRecordHeader = new RecordHeader((byte) 0x04);
+        final RecordDefinition eventRecordDefinition = new RecordDefinition(new RecordHeader((byte) 0x44), ByteOrder.BIG_ENDIAN, GlobalFITMessage.EVENT, GlobalFITMessage.EVENT.getFieldDefinitions(0, 1, 4, 253), null);
+        courseFileDataRecords.add(getEventRecordData(eventRecordDefinition, eventRecordHeader, timestamp, 0));
+        courseFileDataRecords.add(getEventRecordData(eventRecordDefinition, eventRecordHeader, runningTs, 9));
+
+        courseFileDataRecords.addAll(gpxPointDataRecords);
+
+        return new FitFile(courseFileDataRecords);
+    }
+
+    private RecordData getEventRecordData(RecordDefinition eventRecordDefinition, RecordHeader eventRecordHeader, long timestamp, int eventType) {
+        final RecordData startEvent = FitRecordDataFactory.create(
+                eventRecordDefinition,
+                eventRecordHeader);
+
+        startEvent.setFieldByName("timestamp", timestamp);
+        startEvent.setFieldByName("event", 0);
+        startEvent.setFieldByName("event_group", 0);
+        startEvent.setFieldByName("event_type", eventType);
+        return startEvent;
+    }
+
+    private RecordData getLapRecordData(List<GpxTrackPoint> gpxTrackPointList) {
+        final GPSCoordinate first = gpxTrackPointList.get(0);
+        final GPSCoordinate last = gpxTrackPointList.get(gpxTrackPointList.size() - 1);
+
+        final RecordData lapRecord = FitRecordDataFactory.create(
+                new RecordDefinition(new RecordHeader((byte) 0x43), ByteOrder.BIG_ENDIAN, GlobalFITMessage.LAP, GlobalFITMessage.LAP.getFieldDefinitions(3, 4, 5, 6, 7, 8, 9, 21, 22, 253), null),
+                new RecordHeader((byte) 0x03));
+        lapRecord.setFieldByName("start_lat", first.getLatitude());
+        lapRecord.setFieldByName("start_long", first.getLongitude());
+        lapRecord.setFieldByName("end_lat", last.getLatitude());
+        lapRecord.setFieldByName("end_long", last.getLongitude());
+        lapRecord.setFieldByName("timestamp", timestamp);
+        return lapRecord;
+    }
+
+    private RecordData getCourseRecordData() {
+        final RecordData courseRecord = FitRecordDataFactory.create(
+                new RecordDefinition(new RecordHeader((byte) 0x42), ByteOrder.BIG_ENDIAN, GlobalFITMessage.COURSE, GlobalFITMessage.COURSE.getFieldDefinitions(4, 5), null),
+                new RecordHeader((byte) 0x02));
+        courseRecord.setFieldByName("sport", activity); //TODO use track.getType()
+        courseRecord.setFieldByName("name", this.getName());
+        return courseRecord;
+    }
+
+    private RecordData getFileIdRecordData() {
+        final RecordData fileIdRecord = FitRecordDataFactory.create(
+                new RecordDefinition(new RecordHeader((byte) 0x40), ByteOrder.BIG_ENDIAN, GlobalFITMessage.FILE_ID, GlobalFITMessage.FILE_ID.getFieldDefinitions(0, 1, 2, 3, 4, 5), null),
+                new RecordHeader((byte) 0x00));
+        fileIdRecord.setFieldByName("type", FileType.FILETYPE.COURSES.getSubType());
+        fileIdRecord.setFieldByName("manufacturer", 1);
+        fileIdRecord.setFieldByName("product", 65534);
+        fileIdRecord.setFieldByName("time_created", timestamp);
+        fileIdRecord.setFieldByName("serial_number", 1);
+        fileIdRecord.setFieldByName("number", 1);
+        return fileIdRecord;
+    }
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/PredefinedLocalMessage.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/PredefinedLocalMessage.java
new file mode 100644
index 0000000..a1bfc90
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/PredefinedLocalMessage.java
@@ -0,0 +1,58 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit;
+
+import androidx.annotation.Nullable;
+
+import java.nio.ByteOrder;
+import java.util.List;
+
+public enum PredefinedLocalMessage {
+    TODAY_WEATHER_CONDITIONS(6, GlobalFITMessage.WEATHER,
+            new int[]{0, 253, 9, 1, 14, 13, 2, 3, 5, 4, 6, 7, 10, 11, 17, 15, 8}
+    ),
+    HOURLY_WEATHER_FORECAST(9, GlobalFITMessage.WEATHER,
+            new int[]{0, 253, 1, 2, 3, 4, 5, 6, 7, 15, 16, 17}
+    ),
+    DAILY_WEATHER_FORECAST(10, GlobalFITMessage.WEATHER,
+            new int[]{0, 253, 14, 13, 2, 5, 12, 17}
+    );
+
+    private final int type;
+    private final GlobalFITMessage globalFITMessage;
+    private final int[] globalDefinitionIds;
+
+    PredefinedLocalMessage(int type, GlobalFITMessage globalFITMessage, int[] globalDefinitionIds) {
+        this.type = type;
+        this.globalFITMessage = globalFITMessage;
+        this.globalDefinitionIds = globalDefinitionIds;
+    }
+
+    @Nullable
+    public static PredefinedLocalMessage fromType(int type) {
+        for (final PredefinedLocalMessage predefinedLocalMessage : PredefinedLocalMessage.values()) {
+            if (predefinedLocalMessage.getType() == type) {
+                return predefinedLocalMessage;
+            }
+        }
+        return null;
+    }
+
+    public RecordDefinition getRecordDefinition() {
+        final RecordHeader recordHeader = new RecordHeader(true, false, type, null);
+        final List<FieldDefinition> fieldDefinitions = globalFITMessage.getFieldDefinitions(globalDefinitionIds);
+        return new RecordDefinition(
+                recordHeader,
+                ByteOrder.BIG_ENDIAN,
+                globalFITMessage,
+                fieldDefinitions,
+                null
+        );
+    }
+
+    public int getType() {
+        return type;
+    }
+
+    public GlobalFITMessage getGlobalFITMessage() {
+        return globalFITMessage;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/RecordData.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/RecordData.java
new file mode 100644
index 0000000..f155993
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/RecordData.java
@@ -0,0 +1,291 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit;
+
+import androidx.annotation.NonNull;
+
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.GarminByteBufferReader;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.MessageWriter;
+import nodomain.freeyourgadget.gadgetbridge.util.ArrayUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.GBToStringBuilder;
+
+import static nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.baseTypes.BaseType.STRING;
+
+import org.apache.commons.lang3.StringUtils;
+
+public class RecordData {
+
+    private final RecordDefinition recordDefinition;
+    private final RecordHeader recordHeader;
+    private final GlobalFITMessage globalFITMessage;
+    private final List<FieldData> fieldDataList;
+    protected ByteBuffer valueHolder;
+
+    /**
+     * The computed timestamp consists of the running timestamp for this record, which may come from
+     *  a timestamp field 253, or from a compressed timestamp, or simply be the same timestamp as the
+     *  previously seen sample. This does not take into account sample-specific timestamps such as
+     *  timestamp16.
+     */
+    public Long computedTimestamp = null;
+
+    public RecordData(final RecordDefinition recordDefinition, final RecordHeader recordHeader) {
+        if (null == recordDefinition.getFieldDefinitions())
+            throw new IllegalArgumentException("Cannot create record data without FieldDefinitions " + recordDefinition);
+
+        fieldDataList = new ArrayList<>();
+
+        this.recordDefinition = recordDefinition;
+        this.recordHeader = recordHeader;
+        this.globalFITMessage = recordDefinition.getGlobalFITMessage();
+
+        int totalSize = 0;
+
+        for (FieldDefinition fieldDef :
+                recordDefinition.getFieldDefinitions()) {
+            fieldDataList.add(new FieldData(fieldDef, totalSize));
+            totalSize += fieldDef.getSize();
+        }
+
+        if (recordDefinition.getDevFieldDefinitions() != null) {
+            for (DevFieldDefinition fieldDef :
+                    recordDefinition.getDevFieldDefinitions()) {
+                FieldDefinition temp = new FieldDefinition(fieldDef.getFieldDefinitionNumber(), fieldDef.getSize(), fieldDef.getBaseType(), fieldDef.getName());
+                fieldDataList.add(new FieldData(temp, totalSize));
+                totalSize += fieldDef.getSize();
+            }
+        }
+
+        this.valueHolder = ByteBuffer.allocate(totalSize);
+        valueHolder.order(recordDefinition.getByteOrder());
+
+        for (FieldData fieldData :
+                fieldDataList) {
+            fieldData.invalidate();
+        }
+
+    }
+
+    public GlobalFITMessage getGlobalFITMessage() {
+        return globalFITMessage;
+    }
+
+    public RecordDefinition getRecordDefinition() {
+        return recordDefinition;
+    }
+
+    public Long parseDataMessage(final GarminByteBufferReader garminByteBufferReader, final Long currentTimestamp) {
+        garminByteBufferReader.setByteOrder(valueHolder.order());
+        computedTimestamp = currentTimestamp;
+        Long referenceTimestamp = null;
+        for (FieldData fieldData : fieldDataList) {
+            Long runningTimestamp = fieldData.parseDataMessage(garminByteBufferReader);
+            if (runningTimestamp != null) {
+                computedTimestamp = runningTimestamp;
+                referenceTimestamp = runningTimestamp;
+            }
+        }
+        return referenceTimestamp;
+    }
+
+    public void generateOutgoingDataPayload(MessageWriter writer) {
+        writer.writeByte(recordHeader.generateOutgoingDataPayload());
+        writer.writeBytes(valueHolder.array());
+    }
+
+    public void setFieldByNumber(int number, Object... value) {
+        boolean found = false;
+        for (FieldData fieldData :
+                fieldDataList) {
+            if (fieldData.getNumber() == number) {
+                fieldData.encode(value);
+                found = true;
+                break;
+            }
+        }
+        if (!found) {
+            throw new IllegalArgumentException("Unknown field number " + number);
+        }
+    }
+
+    public void setFieldByName(String name, Object... value) {
+        boolean found = false;
+        for (FieldData fieldData :
+                fieldDataList) {
+            if (fieldData.getName().equals(name)) {
+                fieldData.encode(value);
+                found = true;
+                break;
+            }
+        }
+        if (!found) {
+            throw new IllegalArgumentException("Unknown field name " + name);
+        }
+    }
+
+    public Object getFieldByNumber(int number) {
+        for (FieldData fieldData :
+                fieldDataList) {
+            if (fieldData.getNumber() == number) {
+                return fieldData.decode();
+            }
+        }
+        return null;
+    }
+
+    public Object getFieldByName(String name) {
+        for (FieldData fieldData :
+                fieldDataList) {
+            if (fieldData.getName().equals(name)) {
+                return fieldData.decode();
+            }
+        }
+        return null;
+    }
+
+    public int[] getFieldsNumbers() {
+        int[] arr = new int[fieldDataList.size()];
+        int count = 0;
+        for (FieldData fieldData : fieldDataList) {
+            int number = fieldData.getNumber();
+            arr[count++] = number;
+        }
+        return arr;
+    }
+
+    public Long getComputedTimestamp() {
+        return computedTimestamp;
+    }
+
+    @NonNull
+    @Override
+    public String toString() {
+        final GBToStringBuilder tsb = new GBToStringBuilder(this);
+
+        if (this.getClass().getName().equals(RecordData.class.getName())) {
+            tsb.append(globalFITMessage.name());
+        }
+
+        if (getComputedTimestamp() != null) {
+            tsb.append(new Date(getComputedTimestamp() * 1000L));
+        }
+
+        for (FieldData fieldData : fieldDataList) {
+            final String fieldName;
+            if (!StringUtils.isBlank(fieldData.getName())) {
+                fieldName = fieldData.getName();
+            } else {
+                fieldName = "unknown_" + fieldData.getNumber() + fieldData;
+            }
+            Object o = fieldData.decode();
+            final String fieldValueString;
+            if (o == null) {
+                fieldValueString = null;
+            } else if (o instanceof Object[]) {
+                fieldValueString = "[" + StringUtils.join((Object[]) o, ",") + "]";
+            } else {
+                fieldValueString = o.toString();
+            }
+            tsb.append(fieldName, fieldValueString);
+        }
+        return tsb.build();
+    }
+
+    private class FieldData {
+        private final FieldDefinition fieldDefinition;
+        private final int position;
+        private final int size;
+        private final int baseSize;
+
+        public FieldData(FieldDefinition fieldDefinition, int position) {
+            this.fieldDefinition = fieldDefinition;
+            this.position = position;
+            this.size = fieldDefinition.getSize();
+            this.baseSize = fieldDefinition.getBaseType().getSize();
+        }
+
+        private String getName() {
+            return fieldDefinition.getName();
+        }
+
+        private int getNumber() {
+            return fieldDefinition.getNumber();
+        }
+
+        private void invalidate() {
+            goToPosition();
+            if (STRING.equals(fieldDefinition.getBaseType())) {
+                for (int i = 0; i < size; i++) {
+                    valueHolder.put((byte) 0);
+                }
+                return;
+            }
+            for (int i = 0; i < (size / baseSize); i++) {
+                fieldDefinition.invalidate(valueHolder);
+            }
+        }
+
+        private void goToPosition() {
+            valueHolder.position(position);
+        }
+
+        private Long parseDataMessage(GarminByteBufferReader garminByteBufferReader) {
+            goToPosition();
+            valueHolder.put(garminByteBufferReader.readBytes(size));
+            if (fieldDefinition.getNumber() == 253)
+                return (Long) decode();
+            return null;
+        }
+
+        private void encode(Object... objects) {
+            if (objects[0] instanceof boolean[] || objects[0] instanceof short[] || objects[0] instanceof int[] || objects[0] instanceof long[] || objects[0] instanceof float[] || objects[0] instanceof double[]) {
+                throw new IllegalArgumentException("Array of primitive types not supported, box them to objects");
+            }
+            goToPosition();
+            final int slots = size / baseSize;
+            int i = 0;
+            for (Object o : objects) {
+                if (i++ >= slots) {
+                    throw new IllegalArgumentException("Number of elements in array was too big for the field");
+                }
+                if (STRING.equals(fieldDefinition.getBaseType())) {
+                    final byte[] bytes = ((String) o).getBytes(StandardCharsets.UTF_8);
+                    valueHolder.put(Arrays.copyOf(bytes, Math.min(this.size - 1, bytes.length)));
+                    valueHolder.put((byte) 0);
+                    return;
+                }
+                fieldDefinition.encode(valueHolder, o);
+            }
+        }
+
+        private Object decode() {
+            goToPosition();
+            if (STRING.equals(fieldDefinition.getBaseType())) {
+                final byte[] bytes = new byte[size];
+                valueHolder.get(bytes);
+                final int zero = ArrayUtils.indexOf((byte) 0, bytes);
+                if (zero < 0) {
+                    return new String(bytes, StandardCharsets.UTF_8);
+                }
+                return new String(bytes, 0, zero, StandardCharsets.UTF_8);
+            }
+            if (size > baseSize) {
+                Object[] arr = new Object[size / baseSize];
+                for (int i = 0; i < arr.length; i++) {
+                    arr[i] = fieldDefinition.decode(valueHolder);
+                }
+                return arr;
+            }
+            return fieldDefinition.decode(valueHolder);
+        }
+
+        public String toString() {
+            return "(" + fieldDefinition.getBaseType().name() + "/" + size + ")";
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/RecordDefinition.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/RecordDefinition.java
new file mode 100644
index 0000000..7e5c8ad
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/RecordDefinition.java
@@ -0,0 +1,126 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.GarminByteBufferReader;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.baseTypes.BaseType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.MessageWriter;
+
+public class RecordDefinition {
+    private final RecordHeader recordHeader;
+    private final GlobalFITMessage globalFITMessage;
+    private final java.nio.ByteOrder byteOrder;
+    private List<FieldDefinition> fieldDefinitions;
+    private List<DevFieldDefinition> devFieldDefinitions;
+
+    public RecordDefinition(RecordHeader recordHeader, ByteOrder byteOrder, GlobalFITMessage globalFITMessage, List<FieldDefinition> fieldDefinitions, List<DevFieldDefinition> devFieldDefinitions) {
+        this.recordHeader = recordHeader;
+        this.byteOrder = byteOrder;
+        this.globalFITMessage = globalFITMessage;
+        this.fieldDefinitions = fieldDefinitions;
+        this.devFieldDefinitions = devFieldDefinitions;
+    }
+
+    public static RecordDefinition parseIncoming(GarminByteBufferReader garminByteBufferReader, RecordHeader recordHeader) {
+        if (!recordHeader.isDefinition())
+            return null;
+        garminByteBufferReader.readByte();//ignore
+        ByteOrder byteOrder = garminByteBufferReader.readByte() == 0x01 ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN;
+        garminByteBufferReader.setByteOrder(byteOrder);
+        final int globalMesgNum = garminByteBufferReader.readShort();
+        final GlobalFITMessage globalFITMessage = GlobalFITMessage.fromNumber(globalMesgNum);
+
+        RecordDefinition definitionMessage = new RecordDefinition(recordHeader, byteOrder, globalFITMessage, null, null);
+
+        final int numFields = garminByteBufferReader.readByte();
+        List<FieldDefinition> fieldDefinitions = new ArrayList<>(numFields);
+
+        for (int i = 0; i < numFields; i++) {
+            fieldDefinitions.add(FieldDefinition.parseIncoming(garminByteBufferReader, globalFITMessage));
+        }
+
+        definitionMessage.setFieldDefinitions(fieldDefinitions);
+
+        if (recordHeader.isDeveloperData()) {
+            final int numDevFields = garminByteBufferReader.readByte();
+            List<DevFieldDefinition> devFieldDefinitions = new ArrayList<>(numDevFields);
+            for (int i = 0; i < numDevFields; i++) {
+                devFieldDefinitions.add(DevFieldDefinition.parseIncoming(garminByteBufferReader));
+            }
+            definitionMessage.setDevFieldDefinitions(devFieldDefinitions);
+        }
+
+        return definitionMessage;
+    }
+
+    public GlobalFITMessage getGlobalFITMessage() {
+        return globalFITMessage;
+    }
+
+
+    public ByteOrder getByteOrder() {
+        return byteOrder;
+    }
+
+    public List<DevFieldDefinition> getDevFieldDefinitions() {
+        return devFieldDefinitions;
+    }
+
+    public void setDevFieldDefinitions(List<DevFieldDefinition> devFieldDefinitions) {
+        this.devFieldDefinitions = devFieldDefinitions;
+    }
+
+    public RecordHeader getRecordHeader() {
+        return recordHeader;
+    }
+
+    @Nullable
+    public List<FieldDefinition> getFieldDefinitions() {
+        return fieldDefinitions;
+    }
+
+    public void setFieldDefinitions(List<FieldDefinition> fieldDefinitions) {
+        this.fieldDefinitions = fieldDefinitions;
+    }
+
+    public void generateOutgoingPayload(MessageWriter writer) {
+        writer.writeByte(recordHeader.generateOutgoingDefinitionPayload());
+        writer.writeByte(0);//ignore
+        writer.writeByte(byteOrder == ByteOrder.LITTLE_ENDIAN ? 0 : 1);
+        writer.setByteOrder(byteOrder);
+        writer.writeShort(globalFITMessage.getNumber());
+
+        if (fieldDefinitions != null) {
+            writer.writeByte(fieldDefinitions.size());
+            for (FieldDefinition fieldDefinition : fieldDefinitions) {
+                fieldDefinition.generateOutgoingPayload(writer);
+            }
+        }
+    }
+
+    @NonNull
+    public String toString() {
+        return System.lineSeparator() + recordHeader.toString() +
+                " Global Message Number: " + globalFITMessage.name();
+    }
+
+    public void populateDevFields(RecordData recordData) {
+        for (DevFieldDefinition devFieldDef : getDevFieldDefinitions()) {
+            try {
+                if (devFieldDef.getFieldDefinitionNumber() == (int) recordData.getFieldByName("field_definition_number") &&
+                        devFieldDef.getDeveloperDataIndex() == (int) recordData.getFieldByName("developer_data_index")) {
+                    BaseType baseType = BaseType.fromIdentifier((int) recordData.getFieldByName("fit_base_type_id"));
+                    devFieldDef.setBaseType(baseType);
+                    devFieldDef.setName((String) recordData.getFieldByName("field_name"));
+                }
+            } catch (Exception e) {
+                //ignore
+            }
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/RecordHeader.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/RecordHeader.java
new file mode 100644
index 0000000..1440ea9
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/RecordHeader.java
@@ -0,0 +1,87 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+
+public class RecordHeader {
+    private final boolean definition;
+    private final boolean developerData;
+    private final int localMessageType;
+    private final Integer timeOffset;
+
+    public RecordHeader(boolean definition, boolean developerData, int localMessageType, Integer timeOffset) {
+        this.definition = definition;
+        this.developerData = developerData;
+        this.localMessageType = localMessageType;
+        this.timeOffset = timeOffset;
+    }
+
+    //see https://github.com/polyvertex/fitdecode/blob/master/fitdecode/reader.py#L512
+    public RecordHeader(byte header) {
+        if ((header & 0x80) == 0x80) { //compressed timestamp
+            definition = false;
+            developerData = false;
+            localMessageType = (header >> 5) & 0x3;
+            timeOffset = header & 0x1f;
+        } else {
+            definition = ((header & 0x40) == 0x40);
+            developerData = ((header & 0x20) == 0x20);
+            localMessageType = header & 0xf;
+            timeOffset = null;
+        }
+    }
+
+    public int getLocalMessageType() {
+        return localMessageType;
+    }
+
+    @Nullable
+    public Integer getTimeOffset() {
+        return timeOffset;
+    }
+
+    public boolean isCompressedTimestamp() {
+        return timeOffset != null;
+    }
+
+    public boolean isDeveloperData() {
+        return developerData;
+    }
+
+    public boolean isDefinition() {
+        return definition;
+    }
+
+    public byte generateOutgoingDefinitionPayload() {
+        if (!definition && !developerData) {
+            assert timeOffset != null;
+            return (byte) (timeOffset | (((byte) localMessageType) << 5));
+        }
+        byte base = (byte) localMessageType;
+        if (definition)
+            base = (byte) (base | 0x40);
+        if (developerData)
+            base = (byte) (base | 0x20);
+
+        return base;
+    }
+
+    public byte generateOutgoingDataPayload() { //TODO: unclear if correct
+        if (!definition && !developerData) {
+            if (timeOffset != null)
+            return (byte) (timeOffset | (((byte) localMessageType) << 5));
+        }
+        byte base = (byte) localMessageType;
+        if (developerData)
+            base = (byte) (base | 0x20);
+
+        return base;
+    }
+
+    @NonNull
+    @Override
+    public String toString() {
+        return "Local Message: " + localMessageType;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/baseTypes/BaseType.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/baseTypes/BaseType.java
new file mode 100644
index 0000000..167d812
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/baseTypes/BaseType.java
@@ -0,0 +1,100 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.baseTypes;
+
+import java.nio.ByteBuffer;
+
+//see https://github.com/dtcooper/python-fitparse/blob/master/fitparse/records.py
+public enum BaseType {
+    ENUM(0x00, new BaseTypeByte(true, 0xFF)),
+    SINT8(0x01, new BaseTypeByte(false, 0x7F)),
+    UINT8(0x02, new BaseTypeByte(true, 0xFF)),
+    SINT16(0x83, new BaseTypeShort(false, 0x7FFF)),
+    UINT16(0x84, new BaseTypeShort(true, 0xFFFF)),
+    SINT32(0x85, new BaseTypeInt(false, 0x7FFFFFFF)),
+    UINT32(0x86, new BaseTypeInt(true, 0xFFFFFFFFL)),
+    STRING(0x07, new BaseTypeByte(true, 0x00)),
+    FLOAT32(0x88, new BaseTypeFloat()),
+    FLOAT64(0x89, new BaseTypeDouble()),
+    UINT8Z(0x0A, new BaseTypeByte(true, 0x00)),
+    UINT16Z(0x8B, new BaseTypeShort(true, 0)),
+    UINT32Z(0x8C, new BaseTypeInt(true, 0)),
+    BASE_TYPE_BYTE(0x0D, new BaseTypeByte(true, 0xFF)),
+    SINT64(0x8E, new BaseTypeLong(false, 0x7FFFFFFFFFFFFFFFL)),
+    UINT64(0x8F, new BaseTypeLong(true, 0xFFFFFFFFFFFFFFFFL)),
+    UINT64Z(0x8F, new BaseTypeLong(true, 0)),
+    ;
+
+    private final int identifier;
+    private final BaseTypeInterface baseTypeInterface;
+
+    BaseType(int identifier, BaseTypeInterface byteBaseType) {
+        this.identifier = identifier;
+        this.baseTypeInterface = byteBaseType;
+    }
+
+    public static BaseType fromIdentifier(int identifier) {
+        for (final BaseType baseType : BaseType.values()) {
+            if (baseType.getIdentifier() == identifier) {
+                return baseType;
+            }
+        }
+        throw new IllegalArgumentException("Unknown type " + identifier);
+    }
+
+    public int getSize() {
+        return baseTypeInterface.getByteSize();
+    }
+
+    public int getIdentifier() {
+        return identifier;
+    }
+
+    public Object decode(ByteBuffer byteBuffer, double scale, int offset) {
+        Object raw = baseTypeInterface.decode(byteBuffer, scale, offset);
+        if (raw == null)
+            return null;
+
+        //the following returns STRING basetype but also all specialized FieldDefinition classes
+        if (!Number.class.isAssignableFrom(raw.getClass()))
+            return raw;
+
+        switch (this) {
+            case ENUM:
+            case SINT8:
+            case UINT8:
+            case SINT16:
+            case UINT16:
+            case UINT8Z:
+            case UINT16Z:
+            case BASE_TYPE_BYTE:
+                if (scale != 1) {
+                    return ((Number) raw).floatValue();
+                } else {
+                    return ((Number) raw).intValue();
+                }
+            case SINT32:
+            case UINT32:
+            case UINT32Z:
+            case SINT64:
+            case UINT64:
+            case UINT64Z:
+                if (scale != 1) {
+                    return ((Number) raw).doubleValue();
+                } else {
+                    return ((Number) raw).longValue();
+                }
+            case FLOAT32:
+                return ((Number) raw).floatValue();
+            case FLOAT64:
+                return ((Number) raw).doubleValue();
+        }
+        return raw;
+    }
+
+    public void encode(ByteBuffer byteBuffer, Object o, double scale, int offset) {
+        baseTypeInterface.encode(byteBuffer, o, scale, offset);
+    }
+
+    public void invalidate(ByteBuffer byteBuffer) {
+        baseTypeInterface.invalidate(byteBuffer);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/baseTypes/BaseTypeByte.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/baseTypes/BaseTypeByte.java
new file mode 100644
index 0000000..9d92486
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/baseTypes/BaseTypeByte.java
@@ -0,0 +1,59 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.baseTypes;
+
+import java.nio.ByteBuffer;
+
+public class BaseTypeByte implements BaseTypeInterface {
+
+    private final int min;
+    private final int max;
+    private final int invalid;
+    private final boolean unsigned;
+    private final int size = 1;
+
+    BaseTypeByte(boolean unsigned, int invalid) {
+        if (unsigned) {
+            min = 0;
+            max = 0xff;
+        } else {
+            min = Byte.MIN_VALUE;
+            max = Byte.MAX_VALUE;
+        }
+        this.invalid = invalid;
+        this.unsigned = unsigned;
+    }
+
+
+    public int getByteSize() {
+        return size;
+    }
+
+    @Override
+    public Object decode(final ByteBuffer byteBuffer, double scale, int offset) {
+        int b = unsigned ? Byte.toUnsignedInt(byteBuffer.get()) : byteBuffer.get();
+        if (b < min || b > max)
+            return null;
+        if (b == invalid)
+            return null;
+        return (b / scale) - offset;
+    }
+
+    @Override
+    public void encode(ByteBuffer byteBuffer, Object o, double scale, int offset) {
+        if (null == o) {
+            invalidate(byteBuffer);
+            return;
+        }
+        int i = (int) ((((Number) o).intValue() + offset) * scale);
+        if (i < min || i > max) {
+            invalidate(byteBuffer);
+            return;
+        }
+        byteBuffer.put((byte) i);
+    }
+
+    @Override
+    public void invalidate(ByteBuffer byteBuffer) {
+        byteBuffer.put((byte) invalid);
+    }
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/baseTypes/BaseTypeDouble.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/baseTypes/BaseTypeDouble.java
new file mode 100644
index 0000000..8dfbc30
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/baseTypes/BaseTypeDouble.java
@@ -0,0 +1,50 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.baseTypes;
+
+import java.nio.ByteBuffer;
+
+public class BaseTypeDouble implements BaseTypeInterface {
+    private final int size = 8;
+    private final double min;
+    private final double max;
+    private final double invalid;
+
+    BaseTypeDouble() {
+        this.min = -Double.MAX_VALUE;
+        this.max = Double.MAX_VALUE;
+        this.invalid = Double.longBitsToDouble(0xFFFFFFFFFFFFFFFFL);
+    }
+
+    public int getByteSize() {
+        return size;
+    }
+
+    @Override
+    public Object decode(final ByteBuffer byteBuffer, double scale, int offset) {
+        double d = byteBuffer.getDouble();
+        if (d < min || d > max) {
+            return null;
+        }
+        if (Double.isNaN(d) || d == invalid)
+            return null;
+        return (d / scale) - offset;
+    }
+
+    @Override
+    public void encode(ByteBuffer byteBuffer, Object o, double scale, int offset) {
+        if (null == o) {
+            invalidate(byteBuffer);
+            return;
+        }
+        double d = (((Number) o).doubleValue() + offset) * scale;
+        if (d < min || d > max) {
+            invalidate(byteBuffer);
+            return;
+        }
+        byteBuffer.putDouble(d);
+    }
+
+    @Override
+    public void invalidate(ByteBuffer byteBuffer) {
+        byteBuffer.putDouble(invalid);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/baseTypes/BaseTypeFloat.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/baseTypes/BaseTypeFloat.java
new file mode 100644
index 0000000..b9f3120
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/baseTypes/BaseTypeFloat.java
@@ -0,0 +1,51 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.baseTypes;
+
+import java.nio.ByteBuffer;
+
+public class BaseTypeFloat implements BaseTypeInterface {
+    private final int size = 4;
+    private final double min;
+    private final double max;
+    private final double invalid;
+
+    BaseTypeFloat() {
+        this.min = -Float.MAX_VALUE;
+        this.max = Float.MAX_VALUE;
+        this.invalid = Float.intBitsToFloat(0xFFFFFFFF);
+    }
+
+    public int getByteSize() {
+        return size;
+    }
+
+    @Override
+    public Object decode(ByteBuffer byteBuffer, double scale, int offset) {
+        float f = byteBuffer.getFloat();
+        if (f < min || f > max) {
+            return null;
+        }
+        if (Float.isNaN(f) || f == invalid)
+            return null;
+        return (f / scale) - offset;
+    }
+
+    @Override
+    public void encode(ByteBuffer byteBuffer, Object o, double scale, int offset) {
+        if (null == o) {
+            invalidate(byteBuffer);
+            return;
+        }
+        float f = (float) ((((Number) o).floatValue() + offset) * scale);
+        if (f < min || f > max) {
+            invalidate(byteBuffer);
+            return;
+        }
+        byteBuffer.putFloat((float) f);
+    }
+
+    @Override
+    public void invalidate(ByteBuffer byteBuffer) {
+        byteBuffer.putFloat((float) invalid);
+    }
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/baseTypes/BaseTypeInt.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/baseTypes/BaseTypeInt.java
new file mode 100644
index 0000000..dd99858
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/baseTypes/BaseTypeInt.java
@@ -0,0 +1,57 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.baseTypes;
+
+import java.nio.ByteBuffer;
+
+public class BaseTypeInt implements BaseTypeInterface {
+    private final long min;
+    private final long max;
+    private final long invalid;
+    private final boolean unsigned;
+    private final int size = 4;
+
+    BaseTypeInt(boolean unsigned, long invalid) {
+        if (unsigned) {
+            this.min = 0;
+            this.max = 0xffffffffL;
+        } else {
+            this.min = Integer.MIN_VALUE;
+            this.max = Integer.MAX_VALUE;
+        }
+        this.invalid = invalid;
+        this.unsigned = unsigned;
+    }
+
+    public int getByteSize() {
+        return size;
+    }
+
+    @Override
+    public Object decode(final ByteBuffer byteBuffer, double scale, int offset) {
+        long i = unsigned ? Integer.toUnsignedLong(byteBuffer.getInt()) : byteBuffer.getInt();
+        if (i < min || i > max)
+            return null;
+        if (i == invalid)
+            return null;
+        return (i / scale) - offset;
+    }
+
+    @Override
+    public void encode(ByteBuffer byteBuffer, Object o, double scale, int offset) {
+        if (null == o) {
+            invalidate(byteBuffer);
+            return;
+        }
+        long l = (long) ((((Number) o).longValue() + offset) * scale);
+        if (l < min || l > max) {
+            invalidate(byteBuffer);
+            return;
+        }
+        byteBuffer.putInt((int) l);
+    }
+
+    @Override
+    public void invalidate(ByteBuffer byteBuffer) {
+        byteBuffer.putInt((int) invalid);
+    }
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/baseTypes/BaseTypeInterface.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/baseTypes/BaseTypeInterface.java
new file mode 100644
index 0000000..60fd8e1
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/baseTypes/BaseTypeInterface.java
@@ -0,0 +1,13 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.baseTypes;
+
+import java.nio.ByteBuffer;
+
+public interface BaseTypeInterface {
+    int getByteSize();
+
+    Object decode(ByteBuffer byteBuffer, double scale, int offset);
+
+    void encode(ByteBuffer byteBuffer, Object o, double scale, int offset);
+
+    void invalidate(ByteBuffer byteBuffer);
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/baseTypes/BaseTypeLong.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/baseTypes/BaseTypeLong.java
new file mode 100644
index 0000000..479fb55
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/baseTypes/BaseTypeLong.java
@@ -0,0 +1,59 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.baseTypes;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.nio.ByteBuffer;
+
+public class BaseTypeLong implements BaseTypeInterface {
+    private final int size = 8;
+    private final BigInteger min;
+    private final BigInteger max;
+    private final long invalid;
+    private final boolean unsigned;
+
+    BaseTypeLong(boolean unsigned, long invalid) {
+        if (unsigned) {
+            this.min = BigInteger.valueOf(0);
+            this.max = BigInteger.valueOf(0xFFFFFFFFFFFFFFFFL);
+        } else {
+            this.min = BigInteger.valueOf(Long.MIN_VALUE);
+            this.max = BigInteger.valueOf(Long.MAX_VALUE);
+        }
+        this.invalid = invalid;
+        this.unsigned = unsigned;
+    }
+
+    public int getByteSize() {
+        return size;
+    }
+
+    @Override
+    public Object decode(ByteBuffer byteBuffer, double scale, int offset) {
+        BigInteger i = unsigned ? BigInteger.valueOf(byteBuffer.getLong() & 0xFFFFFFFFFFFFFFFFL) : BigInteger.valueOf(byteBuffer.getLong());
+        if (!unsigned && (i.compareTo(min) < 0 || i.compareTo(max) > 0))
+            return null;
+        if (i.compareTo(BigInteger.valueOf(invalid)) == 0)
+            return null;
+        return new BigDecimal(i).divide(BigDecimal.valueOf(scale)).subtract(BigDecimal.valueOf(offset)).toBigInteger().longValue();
+    }
+
+    @Override
+    public void encode(ByteBuffer byteBuffer, Object o, double scale, int offset) {
+        if (null == o) {
+            invalidate(byteBuffer);
+            return;
+        }
+        BigInteger i = new BigDecimal(((Number) o).longValue()).multiply(BigDecimal.valueOf(scale)).add(BigDecimal.valueOf(offset)).toBigInteger();
+        if (!unsigned && (i.compareTo(min) < 0 || i.compareTo(max) > 0)) {
+            invalidate(byteBuffer);
+            return;
+        }
+        byteBuffer.putLong(i.longValue());
+    }
+
+    @Override
+    public void invalidate(ByteBuffer byteBuffer) {
+        byteBuffer.putLong((long) invalid);
+    }
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/baseTypes/BaseTypeShort.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/baseTypes/BaseTypeShort.java
new file mode 100644
index 0000000..61e5cee
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/baseTypes/BaseTypeShort.java
@@ -0,0 +1,56 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.baseTypes;
+
+import java.nio.ByteBuffer;
+
+public class BaseTypeShort implements BaseTypeInterface {
+    private final int min;
+    private final int max;
+    private final int invalid;
+    private final boolean unsigned;
+    private final int size = 2;
+
+    BaseTypeShort(boolean unsigned, int invalid) {
+        if (unsigned) {
+            this.min = 0;
+            this.max = 0xffff;
+        } else {
+            this.min = Short.MIN_VALUE;
+            this.max = Short.MAX_VALUE;
+        }
+        this.invalid = invalid;
+        this.unsigned = unsigned;
+    }
+
+    public int getByteSize() {
+        return size;
+    }
+
+    @Override
+    public Object decode(final ByteBuffer byteBuffer, double scale, int offset) {
+        int s = unsigned ? Short.toUnsignedInt(byteBuffer.getShort()) : byteBuffer.getShort();
+        if (s < min || s > max)
+            return null;
+        if (s == invalid)
+            return null;
+        return (s / scale) - offset;
+    }
+
+    @Override
+    public void encode(ByteBuffer byteBuffer, Object o, double scale, int offset) {
+        if (null == o) {
+            invalidate(byteBuffer);
+            return;
+        }
+        int i = (int) ((((Number) o).intValue() + offset) * scale);
+        if (i < min || i > max) {
+            invalidate(byteBuffer);
+            return;
+        }
+        byteBuffer.putShort((short) i);
+    }
+
+    @Override
+    public void invalidate(ByteBuffer byteBuffer) {
+        byteBuffer.putShort((short) invalid);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/codegen/FitCodeGen.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/codegen/FitCodeGen.java
new file mode 100644
index 0000000..b8dcf0f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/codegen/FitCodeGen.java
@@ -0,0 +1,364 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.codegen;
+
+import android.os.Build;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.time.DayOfWeek;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.FileType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.GlobalFITMessage;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordData;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordDefinition;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordHeader;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionGoalSource;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionGoalType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionHrvStatus;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionLanguage;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionMeasurementSystem;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionSleepStage;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionWeatherAqi;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionWeatherCondition;
+import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
+
+// This class is only used to generate code, and will not be packaged in the final apk
+@RequiresApi(api = Build.VERSION_CODES.O)
+public class FitCodeGen {
+    public static void main(final String[] args) throws Exception {
+        new FitCodeGen().generate();
+    }
+
+    public void generate() throws IOException {
+        final File factoryFile = new File("app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitRecordDataFactory.java");
+
+        final StringBuilder sbFactory = new StringBuilder();
+        String header = getHeader(factoryFile);
+        if (!header.isEmpty()) {
+            sbFactory.append(header);
+            sbFactory.append("\n");
+        }
+
+        sbFactory.append("package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages;\n");
+        sbFactory.append("\n");
+        sbFactory.append("import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordData;\n");
+        sbFactory.append("import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordDefinition;\n");
+        sbFactory.append("import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordHeader;\n");
+        sbFactory.append("\n");
+        sbFactory.append("//\n");
+        sbFactory.append("// WARNING: This class was auto-generated, please avoid modifying it directly.\n");
+        sbFactory.append("// See ").append(getClass().getCanonicalName()).append("\n");
+        sbFactory.append("//\n");
+        sbFactory.append("public class FitRecordDataFactory {\n");
+        sbFactory.append("    private FitRecordDataFactory() {\n");
+        sbFactory.append("        // use create\n");
+        sbFactory.append("    }\n");
+        sbFactory.append("\n");
+        sbFactory.append("    public static RecordData create(final RecordDefinition recordDefinition, final RecordHeader recordHeader) {\n");
+        sbFactory.append("        switch (recordDefinition.getGlobalFITMessage().getNumber()) {\n");
+
+        final ArrayList<GlobalFITMessage> globalFITMessages = new ArrayList<>(GlobalFITMessage.KNOWN_MESSAGES.values());
+        Collections.sort(globalFITMessages, Comparator.comparingInt(GlobalFITMessage::getNumber));
+
+        for (final GlobalFITMessage value : globalFITMessages) {
+            final String className = "Fit" + capitalize(toCamelCase(value.name()));
+            sbFactory.append("            case ").append(value.getNumber()).append(":\n");
+            sbFactory.append("                return new ").append(className).append("(recordDefinition, recordHeader);\n");
+
+            process(value);
+        }
+
+        sbFactory.append("        }\n");
+        sbFactory.append("\n");
+        sbFactory.append("        return new RecordData(recordDefinition, recordHeader);\n");
+        sbFactory.append("    }\n");
+        sbFactory.append("}\n");
+
+        FileUtils.copyStringToFile(sbFactory.toString(), factoryFile, "replace");
+    }
+
+    public void process(final GlobalFITMessage globalFITMessage) throws IOException {
+        final String className = "Fit" + capitalize(toCamelCase(globalFITMessage.name()));
+        final File outputFile = new File("app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/" + className + ".java");
+
+        final List<String> imports = new ArrayList<>();
+        imports.add(Nullable.class.getCanonicalName());
+        imports.add(RecordData.class.getCanonicalName());
+        imports.add(RecordDefinition.class.getCanonicalName());
+        imports.add(RecordHeader.class.getCanonicalName());
+        //imports.add(GBToStringBuilder.class.getCanonicalName());
+        imports.addAll(getImports(outputFile));
+
+        Collections.sort(imports);
+
+        final Set<String> uniqueImports =new LinkedHashSet<>(imports);
+
+        for (final GlobalFITMessage.FieldDefinitionPrimitive primitive : globalFITMessage.getFieldDefinitionPrimitives()) {
+            final Class<?> fieldType = getFieldType(primitive);
+            if (!Objects.requireNonNull(fieldType.getCanonicalName()).startsWith("java.lang")) {
+                imports.add(fieldType.getCanonicalName());
+            }
+        }
+
+        final StringBuilder sb = new StringBuilder();
+        String header = getHeader(outputFile);
+        if (!header.isEmpty()) {
+            sb.append(header);
+            sb.append("\n");
+        }
+
+        sb.append("package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages;");
+        sb.append("\n");
+
+        sb.append("\n");
+        boolean anyImport = false;
+        for (final String i : uniqueImports) {
+            if (i.startsWith("androidx")) {
+                sb.append("import ").append(i).append(";\n");
+                anyImport = true;
+            }
+        }
+
+        if (anyImport) {
+            sb.append("\n");
+            anyImport = false;
+        }
+        for (final String i : uniqueImports) {
+            if (i.startsWith("java.")) {
+                sb.append("import ").append(i).append(";\n");
+                anyImport = true;
+            }
+        }
+
+        if (anyImport) {
+            sb.append("\n");
+            anyImport = false;
+        }
+        for (final String i : uniqueImports) {
+            if (i.startsWith("nodomain.freeyourgadget") && !i.startsWith("java.")) {
+                sb.append("import ").append(i).append(";\n");
+                anyImport = true;
+            }
+        }
+
+        if (anyImport) {
+            sb.append("\n");
+            anyImport = false;
+        }
+        for (final String i : uniqueImports) {
+            if (!i.startsWith("androidx") && !i.startsWith("nodomain.freeyourgadget") && !i.startsWith("java.")) {
+                sb.append("import ").append(i).append(";\n");
+                anyImport = true;
+            }
+        }
+
+        if (anyImport) {
+            sb.append("\n");
+        }
+        sb.append("//\n");
+        sb.append("// WARNING: This class was auto-generated, please avoid modifying it directly.\n");
+        sb.append("// See ").append(getClass().getCanonicalName()).append("\n");
+        sb.append("//\n");
+        sb.append("public class ").append(className).append(" extends RecordData {\n");
+        sb.append("    public ").append(className).append("(final RecordDefinition recordDefinition, final RecordHeader recordHeader) {\n");
+        sb.append("        super(recordDefinition, recordHeader);\n");
+        sb.append("\n");
+        sb.append("        final int globalNumber = recordDefinition.getGlobalFITMessage().getNumber();\n");
+        sb.append("        if (globalNumber != ").append(globalFITMessage.getNumber()).append(") {\n");
+        sb.append("            throw new IllegalArgumentException(\"").append(className).append(" expects global messages of \" + ").append(globalFITMessage.getNumber()).append(" + \", got \" + globalNumber);\n");
+        sb.append("        }\n");
+        sb.append("    }\n");
+
+        for (final GlobalFITMessage.FieldDefinitionPrimitive primitive : globalFITMessage.getFieldDefinitionPrimitives()) {
+            final Class<?> fieldType = getFieldType(primitive);
+            final String fieldTypeName = fieldType.getSimpleName();
+            sb.append("\n");
+            sb.append("    @Nullable\n");
+            sb.append("    public ").append(fieldTypeName).append(method(" get", primitive)).append("() {\n");
+            if (fieldTypeName.endsWith("[]")) {
+                // Special case for arrays, since these are decoded in RecordData and we can't easily decode them with the correct type
+                // FIXME this should be refactored...
+                final String simpleTypeName = fieldTypeName.replace("[]", "");
+                sb.append("        final Object[] objectsArray = (Object[]) getFieldByNumber(").append(primitive.getNumber()).append(");\n");
+                sb.append("        final ").append(fieldTypeName).append(" ret = new ").append(simpleTypeName).append("[objectsArray.length];\n");
+                sb.append("        for (int i = 0; i < objectsArray.length; i++) {\n");
+                sb.append("            ret[i] = (").append(simpleTypeName).append(") objectsArray[i];\n");
+                sb.append("        }\n");
+                sb.append("        return ret;\n");
+            } else {
+                sb.append("        return (").append(fieldTypeName).append(") getFieldByNumber(").append(primitive.getNumber()).append(");\n");
+            }
+            sb.append("    }\n");
+        }
+
+        //sb.append("\n");
+        //sb.append("    @NonNull\n");
+        //sb.append("    @Override\n");
+        //sb.append("    public String toString() {\n");
+        //sb.append("        return new GBToStringBuilder(this)\n");
+        //for (final GlobalFITMessage.FieldDefinitionPrimitive primitive : globalFITMessage.getFieldDefinitionPrimitives()) {
+        //    sb.append("                .append(\"").append(primitive.getName()).append("\",").append(method(" get", primitive)).append("())\n");
+        //}
+        //sb.append("                .build();\n");
+        //sb.append("    }\n");
+
+        if (outputFile.exists()) {
+            // Keep manual changes if any
+            final String fileContents = new String(Files.readAllBytes(outputFile.toPath()), StandardCharsets.UTF_8);
+            final int manualChangesIndex = fileContents.indexOf("// manual changes below");
+            if (manualChangesIndex > 0) {
+                sb.append("\n");
+                sb.append("    ");
+                sb.append(fileContents.substring(manualChangesIndex));
+            } else {
+                sb.append("}\n");
+            }
+        } else {
+            sb.append("}\n");
+        }
+
+        FileUtils.copyStringToFile(sb.toString(), outputFile, "replace");
+    }
+
+    public Class<?> getFieldType(final GlobalFITMessage.FieldDefinitionPrimitive primitive) {
+        if (primitive.getType() != null) {
+            switch (primitive.getType()) {
+                case ALARM:
+                    return Calendar.class;
+                case ARRAY:
+                    return Number[].class;
+                case DAY_OF_WEEK:
+                    return DayOfWeek.class;
+                case FILE_TYPE:
+                    return FileType.FILETYPE.class;
+                case GOAL_SOURCE:
+                    return FieldDefinitionGoalSource.Source.class;
+                case GOAL_TYPE:
+                    return FieldDefinitionGoalType.Type.class;
+                case HRV_STATUS:
+                    return FieldDefinitionHrvStatus.HrvStatus.class;
+                case HR_TIME_IN_ZONE:
+                    return Double[].class;
+                case HR_ZONE_HIGH_BOUNDARY:
+                    return Integer[].class;
+                case MEASUREMENT_SYSTEM:
+                    return FieldDefinitionMeasurementSystem.Type.class;
+                case TEMPERATURE:
+                    return Integer.class;
+                case TIMESTAMP:
+                    return Long.class;
+                case WEATHER_CONDITION:
+                    return FieldDefinitionWeatherCondition.Condition.class;
+                case LANGUAGE:
+                    return FieldDefinitionLanguage.Language.class;
+                case SLEEP_STAGE:
+                    return FieldDefinitionSleepStage.SleepStage.class;
+                case WEATHER_AQI:
+                    return FieldDefinitionWeatherAqi.AQI_LEVELS.class;
+                case COORDINATE:
+                    return Double.class;
+            }
+
+            throw new RuntimeException("Unknown field type " + primitive.getType());
+        }
+
+        switch (primitive.getBaseType()) {
+            case ENUM:
+            case SINT8:
+            case UINT8:
+            case SINT16:
+            case UINT16:
+            case UINT8Z:
+            case UINT16Z:
+            case BASE_TYPE_BYTE:
+                if (primitive.getScale() != 1) {
+                    return Float.class;
+                } else {
+                    return Integer.class;
+                }
+            case SINT32:
+            case UINT32:
+            case UINT32Z:
+            case SINT64:
+            case UINT64:
+            case UINT64Z:
+                if (primitive.getScale() != 1) {
+                    return Double.class;
+                } else {
+                    return Long.class;
+                }
+            case STRING:
+                return String.class;
+            case FLOAT32:
+                return Float.class;
+            case FLOAT64:
+                return Double.class;
+        }
+
+        throw new RuntimeException("Unknown base type " + primitive.getBaseType());
+    }
+
+    public String toCamelCase(final String str) {
+        final StringBuilder sb = new StringBuilder(str.toLowerCase());
+
+        for (int i = 0; i < sb.length(); i++) {
+            if (sb.charAt(i) == '_') {
+                sb.deleteCharAt(i);
+                sb.replace(i, i + 1, String.valueOf(Character.toUpperCase(sb.charAt(i))));
+            }
+        }
+
+        return sb.toString();
+    }
+
+    public String method(final String methodName, final GlobalFITMessage.FieldDefinitionPrimitive primitive) {
+        return methodName + capitalize(toCamelCase(primitive.getName()));
+    }
+
+    public String capitalize(final String str) {
+        return str.substring(0, 1).toUpperCase() + str.substring(1);
+    }
+
+    public String getHeader(final File file) throws IOException {
+        if (file.exists()) {
+            final String fileContents = new String(Files.readAllBytes(file.toPath()), StandardCharsets.UTF_8);
+            final int packageIndex = fileContents.indexOf("package") - 1;
+            if (packageIndex > 0) {
+                return fileContents.substring(0, packageIndex);
+            }
+        }
+
+        return "";
+    }
+
+    public List<String> getImports(final File file) throws IOException {
+        if (file.exists()) {
+            final String fileContents = new String(Files.readAllBytes(file.toPath()), StandardCharsets.UTF_8);
+            final List<String> imports = new ArrayList<>();
+
+            final Matcher m = Pattern.compile("import (.+);")
+                    .matcher(fileContents);
+            while (m.find()) {
+                imports.add(m.group(1));
+            }
+            return imports;
+        }
+
+        return Collections.emptyList();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/enums/GarminSport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/enums/GarminSport.java
new file mode 100644
index 0000000..5cc443b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/enums/GarminSport.java
@@ -0,0 +1,153 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.enums;
+
+import java.util.Optional;
+
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+
+// Taken from CHANGELOG.fit of a Venu 3,
+// Garmin API doc (https://developer.garmin.com/connect-iq/api-docs/Toybox/Activity.html)
+// and FIT files
+public enum GarminSport {
+    GENERIC(0, 0, ActivityKind.ACTIVITY),
+    NAVIGATE(0, 50, ActivityKind.NAVIGATE),
+    RUN(1, 0, ActivityKind.RUNNING),
+    TREADMILL(1, 1, ActivityKind.TREADMILL),
+    INDOOR_TRACK(1, 45, ActivityKind.INDOOR_TRACK),
+    BIKE(2, 0, ActivityKind.CYCLING),
+    BIKE_INDOOR(2, 6, ActivityKind.INDOOR_CYCLING),
+    HANDCYCLING(2, 12, ActivityKind.HANDCYCLING),
+    E_BIKE(2, 28, ActivityKind.E_BIKE),
+    BIKE_COMMUTE(2, 48, ActivityKind.BIKE_COMMUTE),
+    HANDCYCLING_INDOOR(2, 88, ActivityKind.HANDCYCLING_INDOOR),
+    TRANSITION(3, 0, ActivityKind.TRANSITION),
+    FITNESS_EQUIPMENT(4, 0, ActivityKind.FITNESS_EQUIPMENT),
+    ELLIPTICAL(4, 15, ActivityKind.ELLIPTICAL_TRAINER),
+    STAIR_STEPPER(4, 16, ActivityKind.STAIR_STEPPER),
+    PILATES(4, 44, ActivityKind.PILATES),
+    SWIMMING(5, 0, ActivityKind.SWIMMING),
+    POOL_SWIM(5, 17, ActivityKind.POOL_SWIM),
+    OPEN_WATER(5, 18, ActivityKind.SWIMMING_OPENWATER),
+    BASKETBALL(6, 0, ActivityKind.BASKETBALL),
+    SOCCER(7, 0, ActivityKind.SOCCER),
+    TENNIS(8, 0, ActivityKind.TENNIS),
+    PLATFORM_TENNIS(8, 93, ActivityKind.PLATFORM_TENNIS),
+    TABLE_TENNIS(8, 97, ActivityKind.TABLE_TENNIS),
+    AMERICAN_FOOTBALL(9, 0, ActivityKind.AMERICAN_FOOTBALL),
+    TRAINING(10, 0, ActivityKind.TRAINING),
+    STRENGTH(10, 20, ActivityKind.STRENGTH_TRAINING),
+    CARDIO(10, 26, ActivityKind.CARDIO),
+    YOGA(10, 43, ActivityKind.YOGA),
+    BREATHWORK(10, 62, ActivityKind.BREATHWORK),
+    WALK(11, 0, ActivityKind.WALKING),
+    WALK_INDOOR(11, 27, ActivityKind.INDOOR_WALKING),
+    XC_CLASSIC_SKI(12, 0, ActivityKind.XC_CLASSIC_SKI),
+    SKI(13, 0, ActivityKind.SKIING),
+    SNOWBOARD(14, 0, ActivityKind.SNOWBOARDING),
+    ROWING(15, 0, ActivityKind.ROWING),
+    MOUNTAINEERING(16, 0, ActivityKind.MOUNTAINEERING),
+    HIKE(17, 0, ActivityKind.HIKING),
+    MULTISPORT(18, 0, ActivityKind.MULTISPORT),
+    PADDLING(19, 0, ActivityKind.PADDLING),
+    FLYING(20, 0, ActivityKind.FLYING),
+    E_BIKING(21, 0, ActivityKind.E_BIKE),
+    MOTORCYCLING(22, 0, ActivityKind.MOTORCYCLING),
+    BOATING(23, 0, ActivityKind.BOATING),
+    DRIVING(24, 0, ActivityKind.DRIVING),
+    GOLF(25, 0, ActivityKind.GOLF),
+    HANG_GLIDING(26, 0, ActivityKind.HANG_GLIDING),
+    HORSEBACK_RIDING(27,0, ActivityKind.HORSE_RIDING),
+    HUNTING(28, 0, ActivityKind.HUNTING),
+    FISHING(29, 0, ActivityKind.FISHING),
+    INLINE_SKATING(30, 0, ActivityKind.INLINE_SKATING),
+    ROCK_CLIMBING(31, 0, ActivityKind.ROCK_CLIMBING),
+    CLIMB_INDOOR(31, 68, ActivityKind.CLIMB_INDOOR),
+    BOULDERING(31, 69, ActivityKind.BOULDERING),
+    SAIL(32, 0, ActivityKind.SAILING),
+    SAIL_RACE(32, 65, ActivityKind.SAIL_RACE),
+    SAIL_EXPEDITION(32, 66, ActivityKind.SAIL_EXPEDITION),
+    ICE_SKATING(33, 0, ActivityKind.ICE_SKATING),
+    SKY_DIVING(34, 0, ActivityKind.SKY_DIVING),
+    SNOWSHOE(35, 0, ActivityKind.SNOWSHOE),
+    SNOWMOBILING(36, 0, ActivityKind.SNOWMOBILING),
+    STAND_UP_PADDLEBOARDING(37, 0, ActivityKind.STAND_UP_PADDLEBOARDING),
+    SURFING(38, 0, ActivityKind.SURFING),
+    WAKEBOARDING(39, 0, ActivityKind.WAKEBOARDING),
+    WATER_SKIING(40, 0, ActivityKind.WATER_SKIING),
+    KAYAKING(41, 0, ActivityKind.KAYAKING),
+    RAFTING(42, 0, ActivityKind.RAFTING),
+    WINDSURFING(43, 0, ActivityKind.WINDSURFING),
+    KITESURFING(44, 0, ActivityKind.KITESURFING),
+    TACTICAL(45, 0, ActivityKind.TACTICAL),
+    JUMPMASTER(46, 0, ActivityKind.JUMPMASTER),
+    BOXING(47, 0, ActivityKind.BOXING),
+    FLOOR_CLIMBING(48, 0, ActivityKind.FLOOR_CLIMBING),
+    BASEBALL(49, 0, ActivityKind.BASEBALL),
+    SOFTBALL(50, 0, ActivityKind.SOFTBALL),
+    SOFTBALL_SLOW_PITCH(51, 0, ActivityKind.SOFTBALL_SLOW_PITCH),
+    SHOOTING(56, 0, ActivityKind.SHOOTING),
+    AUTO_RACING(57, 0, ActivityKind.AUTO_RACING),
+    WINTER_SPORT(58, 0, ActivityKind.WINTER_SPORT),
+    GRINDING(59, 0, ActivityKind.GRINDING),
+    HEALTH_SNAPSHOT(60, 0, ActivityKind.HEALTH_SNAPSHOT),
+    MARINE(61, 0, ActivityKind.MARINE),
+    HIIT(62, 0, ActivityKind.HIIT),
+    VIDEO_GAMING(63, 0, ActivityKind.VIDEO_GAMING),
+    RACKET(64, 0, ActivityKind.RACKET),
+    PICKLEBALL(64, 84, ActivityKind.PICKLEBALL),
+    PADEL(64, 85, ActivityKind.PADEL),
+    SQUASH(64, 94, ActivityKind.SQUASH),
+    RACQUETBALL(64, 96, ActivityKind.RACQUETBALL),
+    PUSH_WALK_SPEED(65, 0, ActivityKind.PUSH_WALK_SPEED),
+    INDOOR_PUSH_WALK_SPEED(65, 86, ActivityKind.INDOOR_PUSH_WALK_SPEED),
+    PUSH_RUN_SPEED(66, 0, ActivityKind.PUSH_RUN_SPEED),
+    INDOOR_PUSH_RUN_SPEED(66, 87, ActivityKind.INDOOR_PUSH_RUN_SPEED),
+    MEDITATION(67, 0, ActivityKind.MEDITATION),
+    PARA_SPORT(68, 0, ActivityKind.PARA_SPORT),
+    DISC_GOLF(69, 0, ActivityKind.DISC_GOLF),
+    ULTIMATE_DISC(69, 92, ActivityKind.ULTIMATE_DISC),
+    TEAM_SPORT(70, 0, ActivityKind.TEAM_SPORT),
+    CRICKET(71, 0, ActivityKind.CRICKET),
+    RUGBY(72, 0, ActivityKind.RUGBY),
+    HOCKEY(73, 0, ActivityKind.HOCKEY),
+    LACROSSE(74, 0, ActivityKind.LACROSSE),
+    VOLLEYBALL(75, 0, ActivityKind.VOLLEYBALL),
+    WATER_TUBING(76, 0, ActivityKind.WATER_TUBING),
+    WAKESURFING(77, 0, ActivityKind.WAKESURFING),
+    MIXED_MARTIAL_ARTS(80, 0, ActivityKind.MIXED_MARTIAL_ARTS), // aka MMA
+    SNORKELING(82, 0, ActivityKind.SNORKELING),
+    DANCE(83, 0, ActivityKind.DANCE),
+    JUMP_ROPE(84, 0, ActivityKind.JUMP_ROPING),
+    ;
+
+    private final int type;
+    private final int subtype;
+    private final ActivityKind activityKind;
+
+    GarminSport(final int type, final int subtype, final ActivityKind activityKind) {
+        this.type = type;
+        this.subtype = subtype;
+        this.activityKind = activityKind;
+    }
+
+    public int getType() {
+        return type;
+    }
+
+    public int getSubtype() {
+        return subtype;
+    }
+
+    public ActivityKind getActivityKind() {
+        return activityKind;
+    }
+
+    public static Optional<GarminSport> fromCodes(final int type, final int subtype) {
+        for (final GarminSport value : GarminSport.values()) {
+            if (value.getType() == type && value.getSubtype() == subtype) {
+                return Optional.of(value);
+            }
+        }
+
+        return Optional.empty();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/fieldDefinitions/FieldDefinitionAlarm.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/fieldDefinitions/FieldDefinitionAlarm.java
new file mode 100644
index 0000000..34f1372
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/fieldDefinitions/FieldDefinitionAlarm.java
@@ -0,0 +1,31 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions;
+
+import java.nio.ByteBuffer;
+import java.util.Calendar;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.FieldDefinition;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.baseTypes.BaseType;
+
+public class FieldDefinitionAlarm extends FieldDefinition {
+    public FieldDefinitionAlarm(int localNumber, int size, BaseType baseType, String name) {
+        super(localNumber, size, baseType, name, 1, 0);
+    }
+
+    @Override
+    public Object decode(ByteBuffer byteBuffer) {
+        int raw = (int) baseType.decode(byteBuffer, scale, offset);
+        Calendar calendar = Calendar.getInstance();
+        calendar.set(Calendar.HOUR_OF_DAY, Math.round(raw / 60));
+        calendar.set(Calendar.MINUTE, raw % 60);
+        return calendar;
+    }
+
+    @Override
+    public void encode(ByteBuffer byteBuffer, Object o) {
+        if (o instanceof Calendar) {
+            baseType.encode(byteBuffer, ((Calendar) o).get(Calendar.HOUR_OF_DAY) * 60 + ((Calendar) o).get(Calendar.MINUTE), scale, offset);
+            return;
+        }
+        baseType.encode(byteBuffer, o, scale, offset);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/fieldDefinitions/FieldDefinitionArray.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/fieldDefinitions/FieldDefinitionArray.java
new file mode 100644
index 0000000..00900ce
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/fieldDefinitions/FieldDefinitionArray.java
@@ -0,0 +1,10 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.FieldDefinition;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.baseTypes.BaseType;
+
+public class FieldDefinitionArray extends FieldDefinition {
+    public FieldDefinitionArray(final int localNumber, final int size, final BaseType baseType, final String name, int scale, int offset) {
+        super(localNumber, size, baseType, name, scale, offset);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/fieldDefinitions/FieldDefinitionCoordinate.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/fieldDefinitions/FieldDefinitionCoordinate.java
new file mode 100644
index 0000000..31d1787
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/fieldDefinitions/FieldDefinitionCoordinate.java
@@ -0,0 +1,31 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions;
+
+import java.nio.ByteBuffer;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.FieldDefinition;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.baseTypes.BaseType;
+
+public class FieldDefinitionCoordinate extends FieldDefinition {
+
+    final double conversionFactor = (180.0D / 0x80000000L);
+
+    public FieldDefinitionCoordinate(int localNumber, int size, BaseType baseType, String name) {
+        super(localNumber, size, baseType, name, 1, 0);
+    }
+
+    @Override
+    public Object decode(ByteBuffer byteBuffer) {
+        final Object rawValue = baseType.decode(byteBuffer, 1, 0);
+        if (rawValue == null) {
+            return null;
+        }
+        return ((long) rawValue) * conversionFactor;
+    }
+
+    @Override
+    public void encode(ByteBuffer byteBuffer, Object o) {
+        baseType.encode(byteBuffer, (int) Math.round((double) o / conversionFactor), 1, 0);
+    }
+
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/fieldDefinitions/FieldDefinitionDayOfWeek.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/fieldDefinitions/FieldDefinitionDayOfWeek.java
new file mode 100644
index 0000000..caea1ac
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/fieldDefinitions/FieldDefinitionDayOfWeek.java
@@ -0,0 +1,31 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions;
+
+import java.nio.ByteBuffer;
+import java.time.DayOfWeek;
+import java.time.Instant;
+import java.time.ZoneId;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.FieldDefinition;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.baseTypes.BaseType;
+
+public class FieldDefinitionDayOfWeek extends FieldDefinition {
+
+    public FieldDefinitionDayOfWeek(int localNumber, int size, BaseType baseType, String name) {
+        super(localNumber, size, baseType, name, 1, 0);
+    }
+
+    @Override
+    public Object decode(ByteBuffer byteBuffer) {
+        int raw = (int) baseType.decode(byteBuffer, scale, offset);
+        return DayOfWeek.of(raw == 0 ? 7 : raw);
+    }
+
+    @Override
+    public void encode(ByteBuffer byteBuffer, Object o) {
+        if (o instanceof DayOfWeek) {
+            baseType.encode(byteBuffer, (((DayOfWeek) o).getValue() % 7), scale, offset);
+            return;
+        }
+        baseType.encode(byteBuffer, (Instant.ofEpochSecond((int) o).atZone(ZoneId.systemDefault()).getDayOfWeek().getValue() % 7), scale, offset);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/fieldDefinitions/FieldDefinitionFileType.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/fieldDefinitions/FieldDefinitionFileType.java
new file mode 100644
index 0000000..2d21453
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/fieldDefinitions/FieldDefinitionFileType.java
@@ -0,0 +1,30 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions;
+
+import java.nio.ByteBuffer;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.FileType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.FieldDefinition;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.baseTypes.BaseType;
+
+public class FieldDefinitionFileType extends FieldDefinition {
+
+    public FieldDefinitionFileType(int localNumber, int size, BaseType baseType, String name) {
+        super(localNumber, size, baseType, name, 1, 0);
+    }
+
+    @Override
+    public Object decode(ByteBuffer byteBuffer) {
+        int raw = (int) baseType.decode(byteBuffer, scale, offset);
+        final FileType.FILETYPE fileType = FileType.FILETYPE.fromDataTypeSubType(128, raw);
+        return fileType == null ? raw : fileType;
+    }
+
+    @Override
+    public void encode(ByteBuffer byteBuffer, Object o) {
+        if (o instanceof FileType.FILETYPE) {
+            baseType.encode(byteBuffer, (((FileType.FILETYPE) o).getSubType()), scale, offset);
+            return;
+        }
+        baseType.encode(byteBuffer, o, scale, offset);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/fieldDefinitions/FieldDefinitionGoalSource.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/fieldDefinitions/FieldDefinitionGoalSource.java
new file mode 100644
index 0000000..4ab317f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/fieldDefinitions/FieldDefinitionGoalSource.java
@@ -0,0 +1,48 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions;
+
+import androidx.annotation.Nullable;
+
+import java.nio.ByteBuffer;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.FieldDefinition;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.baseTypes.BaseType;
+
+public class FieldDefinitionGoalSource extends FieldDefinition {
+
+    public FieldDefinitionGoalSource(int localNumber, int size, BaseType baseType, String name) {
+        super(localNumber, size, baseType, name, 1, 0);
+    }
+
+    @Override
+    public Object decode(ByteBuffer byteBuffer) {
+        int raw = (int) baseType.decode(byteBuffer, scale, offset);
+        return Source.fromId(raw);
+    }
+
+    @Override
+    public void encode(ByteBuffer byteBuffer, Object o) {
+        if (o instanceof Source) {
+            baseType.encode(byteBuffer, (((Source) o).ordinal()), scale, offset);
+            return;
+        }
+        baseType.encode(byteBuffer, o, scale, offset);
+    }
+
+    public enum Source {
+        auto,
+        community,
+        manual,
+        ;
+
+        @Nullable
+        public static Source fromId(int id) {
+            for (Source source :
+                    Source.values()) {
+                if (id == source.ordinal()) {
+                    return source;
+                }
+            }
+            return null;
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/fieldDefinitions/FieldDefinitionGoalType.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/fieldDefinitions/FieldDefinitionGoalType.java
new file mode 100644
index 0000000..a08f2dc
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/fieldDefinitions/FieldDefinitionGoalType.java
@@ -0,0 +1,56 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions;
+
+import androidx.annotation.Nullable;
+
+import java.nio.ByteBuffer;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.FieldDefinition;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.baseTypes.BaseType;
+
+public class FieldDefinitionGoalType extends FieldDefinition {
+
+    public FieldDefinitionGoalType(int localNumber, int size, BaseType baseType, String name) {
+        super(localNumber, size, baseType, name, 1, 0);
+    }
+
+    @Override
+    public Object decode(ByteBuffer byteBuffer) {
+        int raw = (int) baseType.decode(byteBuffer, scale, offset);
+        return Type.fromId(raw);
+    }
+
+    @Override
+    public void encode(ByteBuffer byteBuffer, Object o) {
+        if (o instanceof Type) {
+            baseType.encode(byteBuffer, (((Type) o).getId()), scale, offset);
+            return;
+        }
+        baseType.encode(byteBuffer, o, scale, offset);
+    }
+
+    public enum Type {
+        steps(4),
+        ;
+
+        private final int id;
+
+        Type(int i) {
+            id = i;
+        }
+
+        @Nullable
+        public static Type fromId(int id) {
+            for (Type type :
+                    Type.values()) {
+                if (id == type.getId()) {
+                    return type;
+                }
+            }
+            return null;
+        }
+
+        public int getId() {
+            return id;
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/fieldDefinitions/FieldDefinitionHrTimeInZone.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/fieldDefinitions/FieldDefinitionHrTimeInZone.java
new file mode 100644
index 0000000..423da31
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/fieldDefinitions/FieldDefinitionHrTimeInZone.java
@@ -0,0 +1,10 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.FieldDefinition;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.baseTypes.BaseType;
+
+public class FieldDefinitionHrTimeInZone extends FieldDefinition {
+    public FieldDefinitionHrTimeInZone(final int localNumber, final int size, final BaseType baseType, final String name) {
+        super(localNumber, size, baseType, name, 1000, 0);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/fieldDefinitions/FieldDefinitionHrZoneHighBoundary.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/fieldDefinitions/FieldDefinitionHrZoneHighBoundary.java
new file mode 100644
index 0000000..f7c04bc
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/fieldDefinitions/FieldDefinitionHrZoneHighBoundary.java
@@ -0,0 +1,10 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.FieldDefinition;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.baseTypes.BaseType;
+
+public class FieldDefinitionHrZoneHighBoundary extends FieldDefinition {
+    public FieldDefinitionHrZoneHighBoundary(final int localNumber, final int size, final BaseType baseType, final String name) {
+        super(localNumber, size, baseType, name, 1, 0);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/fieldDefinitions/FieldDefinitionHrvStatus.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/fieldDefinitions/FieldDefinitionHrvStatus.java
new file mode 100644
index 0000000..459154c
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/fieldDefinitions/FieldDefinitionHrvStatus.java
@@ -0,0 +1,58 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions;
+
+import androidx.annotation.Nullable;
+
+import java.nio.ByteBuffer;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.FieldDefinition;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.baseTypes.BaseType;
+
+public class FieldDefinitionHrvStatus extends FieldDefinition {
+    public FieldDefinitionHrvStatus(final int localNumber, final int size, final BaseType baseType, final String name) {
+        super(localNumber, size, baseType, name, 1, 0);
+    }
+
+    @Override
+    public Object decode(final ByteBuffer byteBuffer) {
+        final int raw = (int) baseType.decode(byteBuffer, scale, offset);
+        return HrvStatus.fromId(raw);
+    }
+
+    @Override
+    public void encode(final ByteBuffer byteBuffer, final Object o) {
+        if (o instanceof HrvStatus) {
+            baseType.encode(byteBuffer, (((HrvStatus) o).getId()), scale, offset);
+            return;
+        }
+        baseType.encode(byteBuffer, o, scale, offset);
+    }
+
+    public enum HrvStatus {
+        NONE(0),
+        POOR(1),
+        LOW(2),
+        UNBALANCED(3),
+        BALANCED(4),
+        ;
+
+        private final int id;
+
+        HrvStatus(final int i) {
+            id = i;
+        }
+
+        @Nullable
+        public static HrvStatus fromId(final int id) {
+            for (HrvStatus stage : HrvStatus.values()) {
+                if (id == stage.getId()) {
+                    return stage;
+                }
+            }
+            return null;
+        }
+
+        public int getId() {
+            return id;
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/fieldDefinitions/FieldDefinitionLanguage.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/fieldDefinitions/FieldDefinitionLanguage.java
new file mode 100644
index 0000000..979e990
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/fieldDefinitions/FieldDefinitionLanguage.java
@@ -0,0 +1,57 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions;
+
+import androidx.annotation.Nullable;
+
+import java.nio.ByteBuffer;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.FieldDefinition;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.baseTypes.BaseType;
+
+public class FieldDefinitionLanguage extends FieldDefinition {
+
+    public FieldDefinitionLanguage(int localNumber, int size, BaseType baseType, String name) {
+        super(localNumber, size, baseType, name, 1, 0);
+    }
+
+    @Override
+    public Object decode(ByteBuffer byteBuffer) {
+        int raw = (int) baseType.decode(byteBuffer, scale, offset);
+        return Language.fromId(raw);
+    }
+
+    @Override
+    public void encode(ByteBuffer byteBuffer, Object o) {
+        if (o instanceof Language) {
+            baseType.encode(byteBuffer, (((Language) o).getId()), scale, offset);
+            return;
+        }
+        baseType.encode(byteBuffer, o, scale, offset);
+    }
+
+    public enum Language {
+        english(0),
+        italian(2),
+        ;
+
+        private final int id;
+
+        Language(int i) {
+            id = i;
+        }
+
+        @Nullable
+        public static Language fromId(int id) {
+            for (Language language :
+                    Language.values()) {
+                if (id == language.getId()) {
+                    return language;
+                }
+            }
+            return null;
+        }
+
+        public int getId() {
+            return id;
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/fieldDefinitions/FieldDefinitionMeasurementSystem.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/fieldDefinitions/FieldDefinitionMeasurementSystem.java
new file mode 100644
index 0000000..83348f0
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/fieldDefinitions/FieldDefinitionMeasurementSystem.java
@@ -0,0 +1,44 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions;
+
+import java.nio.ByteBuffer;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.FieldDefinition;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.baseTypes.BaseType;
+
+public class FieldDefinitionMeasurementSystem extends FieldDefinition {
+
+
+    public FieldDefinitionMeasurementSystem(int localNumber, int size, BaseType baseType, String name) {
+        super(localNumber, size, baseType, name, 1, 0);
+    }
+
+    @Override
+    public Object decode(ByteBuffer byteBuffer) {
+        int raw = (int) baseType.decode(byteBuffer, scale, offset);
+        return Type.fromId(raw) == null ? raw : Type.fromId(raw);
+    }
+
+    @Override
+    public void encode(ByteBuffer byteBuffer, Object o) {
+        if (o instanceof Type) {
+            baseType.encode(byteBuffer, (((Type) o).ordinal()), scale, offset);
+            return;
+        }
+        baseType.encode(byteBuffer, o, scale, offset);
+    }
+
+    public enum Type {
+        metric,
+        ;
+
+        public static Type fromId(int id) {
+            for (Type type :
+                    Type.values()) {
+                if (type.ordinal() == id) {
+                    return type;
+                }
+            }
+            return null;
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/fieldDefinitions/FieldDefinitionSleepStage.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/fieldDefinitions/FieldDefinitionSleepStage.java
new file mode 100644
index 0000000..c9532a6
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/fieldDefinitions/FieldDefinitionSleepStage.java
@@ -0,0 +1,58 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions;
+
+import androidx.annotation.Nullable;
+
+import java.nio.ByteBuffer;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.FieldDefinition;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.baseTypes.BaseType;
+
+public class FieldDefinitionSleepStage extends FieldDefinition {
+    public FieldDefinitionSleepStage(final int localNumber, final int size, final BaseType baseType, final String name) {
+        super(localNumber, size, baseType, name, 1, 0);
+    }
+
+    @Override
+    public Object decode(final ByteBuffer byteBuffer) {
+        final int raw = (int) baseType.decode(byteBuffer, scale, offset);
+        return SleepStage.fromId(raw);
+    }
+
+    @Override
+    public void encode(final ByteBuffer byteBuffer, final Object o) {
+        if (o instanceof SleepStage) {
+            baseType.encode(byteBuffer, (((SleepStage) o).getId()), scale, offset);
+            return;
+        }
+        baseType.encode(byteBuffer, o, scale, offset);
+    }
+
+    public enum SleepStage {
+        UNMEASURABLE(0),
+        AWAKE(1),
+        LIGHT(2),
+        DEEP(3),
+        REM(4),
+        ;
+
+        private final int id;
+
+        SleepStage(final int i) {
+            id = i;
+        }
+
+        @Nullable
+        public static SleepStage fromId(final int id) {
+            for (SleepStage stage : SleepStage.values()) {
+                if (id == stage.getId()) {
+                    return stage;
+                }
+            }
+            return null;
+        }
+
+        public int getId() {
+            return id;
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/fieldDefinitions/FieldDefinitionTemperature.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/fieldDefinitions/FieldDefinitionTemperature.java
new file mode 100644
index 0000000..8dec120
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/fieldDefinitions/FieldDefinitionTemperature.java
@@ -0,0 +1,13 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.FieldDefinition;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.baseTypes.BaseType;
+
+public class FieldDefinitionTemperature extends FieldDefinition {
+
+    public FieldDefinitionTemperature(int localNumber, int size, BaseType baseType, String name) {
+        // #4313 - We do a "wrong" conversion to celsius on purpose
+        super(localNumber, size, baseType, name, 1, -273);
+    }
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/fieldDefinitions/FieldDefinitionTimestamp.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/fieldDefinitions/FieldDefinitionTimestamp.java
new file mode 100644
index 0000000..c45b051
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/fieldDefinitions/FieldDefinitionTimestamp.java
@@ -0,0 +1,26 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.FieldDefinition;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.baseTypes.BaseType;
+
+import static nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.GarminTimeUtils.GARMIN_TIME_EPOCH;
+
+public class FieldDefinitionTimestamp extends FieldDefinition {
+    public FieldDefinitionTimestamp(int localNumber, int size, BaseType baseType, String name) {
+        super(localNumber, size, baseType, name, 1, -GARMIN_TIME_EPOCH);
+    }
+
+//    @Override
+//    public Object decode(ByteBuffer byteBuffer) {
+//        return new Timestamp((long) baseType.decode(byteBuffer, scale, offset) * 1000L);
+//    }
+//
+//    @Override
+//    public void encode(ByteBuffer byteBuffer, Object o) {
+//        if(o instanceof Timestamp) {
+//            baseType.encode(byteBuffer, (int) (((Timestamp) o).getTime() / 1000L), scale, offset);
+//            return;
+//        }
+//        baseType.encode(byteBuffer, o, scale, offset);
+//    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/fieldDefinitions/FieldDefinitionWeatherAqi.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/fieldDefinitions/FieldDefinitionWeatherAqi.java
new file mode 100644
index 0000000..fa25c21
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/fieldDefinitions/FieldDefinitionWeatherAqi.java
@@ -0,0 +1,56 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions;
+
+import java.nio.ByteBuffer;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.FieldDefinition;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.baseTypes.BaseType;
+
+public class FieldDefinitionWeatherAqi extends FieldDefinition {
+
+    public FieldDefinitionWeatherAqi(int localNumber, int size, BaseType baseType, String name) {
+        super(localNumber, size, baseType, name, 1, 0);
+    }
+
+    @Override
+    public Object decode(ByteBuffer byteBuffer) {
+        int idx = (int) baseType.decode(byteBuffer, scale, offset);
+        return AQI_LEVELS.values()[idx];
+    }
+
+    @Override
+    public void encode(ByteBuffer byteBuffer, Object o) {
+        if (o instanceof AQI_LEVELS) {
+            baseType.encode(byteBuffer, ((AQI_LEVELS) o).ordinal(), scale, offset);
+            return;
+        }
+        baseType.encode(byteBuffer, aqiAbsoluteValueToIndex((int) o), scale, offset);
+    }
+
+    private int aqiAbsoluteValueToIndex(int rawValue) { //see https://github.com/breezy-weather/breezy-weather/blob/main/app/src/main/java/org/breezyweather/domain/weather/index/PollutantIndex.kt#L38
+        if (rawValue == -1) {
+            return rawValue; //invalid
+        }
+        if (rawValue < 20) {
+            return AQI_LEVELS.GOOD.ordinal();
+        } else if (rawValue < 50) {
+            return AQI_LEVELS.MODERATE.ordinal();
+        } else if (rawValue < 100) {
+            return AQI_LEVELS.UNHEALTHY_SENSITIVE.ordinal();
+        } else if (rawValue < 150) {
+            return AQI_LEVELS.UNHEALTHY.ordinal();
+        } else if (rawValue < 250) {
+            return AQI_LEVELS.VERY_UNHEALTHY.ordinal();
+        } else {
+            return AQI_LEVELS.HAZARDOUS.ordinal();
+        }
+    }
+
+    public enum AQI_LEVELS {
+        GOOD,
+        MODERATE,
+        UNHEALTHY_SENSITIVE,
+        UNHEALTHY,
+        VERY_UNHEALTHY,
+        HAZARDOUS,
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/fieldDefinitions/FieldDefinitionWeatherCondition.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/fieldDefinitions/FieldDefinitionWeatherCondition.java
new file mode 100644
index 0000000..96d61bc
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/fieldDefinitions/FieldDefinitionWeatherCondition.java
@@ -0,0 +1,171 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions;
+
+import java.nio.ByteBuffer;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.FieldDefinition;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.baseTypes.BaseType;
+
+public class FieldDefinitionWeatherCondition extends FieldDefinition {
+
+    public FieldDefinitionWeatherCondition(int localNumber, int size, BaseType baseType, String name) {
+        super(localNumber, size, baseType, name, 1, 0);
+    }
+
+    @Override
+    public Object decode(ByteBuffer byteBuffer) {
+        int idx = (int) baseType.decode(byteBuffer, scale, offset);
+        return Condition.values()[idx];
+    }
+
+    @Override
+    public void encode(ByteBuffer byteBuffer, Object o) {
+        if (o instanceof Condition) {
+            baseType.encode(byteBuffer, ((Condition) o).ordinal(), scale, offset);
+            return;
+        }
+        baseType.encode(byteBuffer, openWeatherCodeToFitWeatherStatus((int) o), scale, offset);
+    }
+
+    private int openWeatherCodeToFitWeatherStatus(int openWeatherCode) {
+        switch (openWeatherCode) {
+//Group 2xx: Thunderstorm
+            case 200:  //thunderstorm with light rain:  //11d
+            case 201:  //thunderstorm with rain:  //11d
+            case 202:  //thunderstorm with heavy rain:  //11d
+            case 210:  //light thunderstorm::  //11d
+            case 211:  //thunderstorm:  //11d
+            case 212:  //heavy thunderstorm:  //11d
+            case 230:  //thunderstorm with light drizzle:  //11d
+            case 231:  //thunderstorm with drizzle:  //11d
+            case 232:  //thunderstorm with heavy drizzle:  //11d
+                return Condition.THUNDERSTORMS.ordinal();
+            case 221:  //ragged thunderstorm:  //11d
+                return Condition.SCATTERED_THUNDERSTORMS.ordinal();
+//Group 3xx: Drizzle
+            case 300:  //light intensity drizzle:  //09d
+            case 310:  //light intensity drizzle rain:  //09d
+            case 313:  //shower rain and drizzle:  //09d
+                return Condition.LIGHT_RAIN.ordinal();
+            case 301:  //drizzle:  //09d
+            case 311:  //drizzle rain:  //09d
+                return Condition.RAIN.ordinal();
+            case 302:  //heavy intensity drizzle:  //09d
+            case 312:  //heavy intensity drizzle rain:  //09d
+            case 314:  //heavy shower rain and drizzle:  //09d
+                return Condition.HEAVY_RAIN.ordinal();
+            case 321:  //shower drizzle:  //09d
+                return Condition.SCATTERED_SHOWERS.ordinal();
+//Group 5xx: Rain
+            case 500:  //light rain:  //10d
+            case 520:  //light intensity shower rain:  //09d
+            case 521:  //shower rain:  //09d
+                return Condition.LIGHT_RAIN.ordinal();
+            case 501:  //moderate rain:  //10d
+            case 531:  //ragged shower rain:  //09d
+                return Condition.RAIN.ordinal();
+            case 502:  //heavy intensity rain:  //10d
+            case 503:  //very heavy rain:  //10d
+            case 504:  //extreme rain:  //10d
+            case 522:  //heavy intensity shower rain:  //09d
+                return Condition.HEAVY_RAIN.ordinal();
+            case 511:  //freezing rain:  //13d
+                return Condition.UNKNOWN_PRECIPITATION.ordinal();
+//Group 6xx: Snow
+            case 600:  //light snow:  //[[file:13d.png]]
+                return Condition.LIGHT_SNOW.ordinal();
+            case 601:  //snow:  //[[file:13d.png]]
+            case 620:  //light shower snow:  //[[file:13d.png]]
+            case 621:  //shower snow:  //[[file:13d.png]]
+                return Condition.SNOW.ordinal();
+            case 602:  //heavy snow:  //[[file:13d.png]]
+            case 622:  //heavy shower snow:  //[[file:13d.png]]
+                return Condition.HEAVY_SNOW.ordinal();
+            case 611:  //sleet:  //[[file:13d.png]]
+            case 612:  //light shower sleet:  //[[file:13d.png]]
+            case 613:  //shower sleet:  //[[file:13d.png]]
+                return Condition.WINTRY_MIX.ordinal();
+            case 615:  //light rain and snow:  //[[file:13d.png]]
+                return Condition.LIGHT_RAIN_SNOW.ordinal();
+            case 616:  //rain and snow:  //[[file:13d.png]]
+                return Condition.HEAVY_RAIN_SNOW.ordinal();
+
+//Group 7xx: Atmosphere
+            case 701:  //mist:  //[[file:50d.png]]
+            case 711:  //smoke:  //[[file:50d.png]]
+            case 721:  //haze:  //[[file:50d.png]]
+            case 731:  //sandcase  dust whirls:  //[[file:50d.png]]
+            case 751:  //sand:  //[[file:50d.png]]
+            case 761:  //dust:  //[[file:50d.png]]
+            case 762:  //volcanic ash:  //[[file:50d.png]]
+                return Condition.HAZY.ordinal();
+            case 741:  //fog:  //[[file:50d.png]]
+                return Condition.FOG.ordinal();
+            case 771:  //squalls:  //[[file:50d.png]]
+            case 781:  //tornado:  //[[file:50d.png]]
+                return Condition.WINDY.ordinal();
+//Group 800: Clear
+            case 800:  //clear sky:  //[[file:01d.png]] [[file:01n.png]]
+                return Condition.CLEAR.ordinal();
+
+//Group 80x: Clouds
+            case 801:  //few clouds:  //[[file:02d.png]] [[file:02n.png]]
+            case 802:  //scattered clouds:  //[[file:03d.png]] [[file:03d.png]]
+                return Condition.PARTLY_CLOUDY.ordinal();
+            case 803:  //broken clouds:  //[[file:04d.png]] [[file:03d.png]]
+                return Condition.MOSTLY_CLOUDY.ordinal();
+            case 804:  //overcast clouds:  //[[file:04d.png]] [[file:04d.png]]
+                return Condition.CLOUDY.ordinal();
+//Group 90x: Extreme
+            case 901:  //tropical storm
+                return Condition.THUNDERSTORMS.ordinal();
+            case 906:  //hail
+                return Condition.HAIL.ordinal();
+            case 903:  //cold
+            case 904:  //hot
+            case 905:  //windy
+//Group 9xx: Additional
+            case 951:  //calm
+            case 952:  //light breeze
+            case 953:  //gentle breeze
+            case 954:  //moderate breeze
+            case 955:  //fresh breeze
+            case 956:  //strong breeze
+            case 957:  //high windcase  near gale
+            case 958:  //gale
+            case 959:  //severe gale
+            case 960:  //storm
+            case 961:  //violent storm
+            case 902:  //hurricane
+            case 962:  //hurricane
+            default:
+                return 255; //invalid
+        }
+    }
+
+    public enum Condition {
+        CLEAR,
+        PARTLY_CLOUDY,
+        MOSTLY_CLOUDY,
+        RAIN,
+        SNOW,
+        WINDY,
+        THUNDERSTORMS,
+        WINTRY_MIX,
+        FOG,
+        UNK9,
+        UNK10,
+        HAZY,
+        HAIL,
+        SCATTERED_SHOWERS,
+        SCATTERED_THUNDERSTORMS,
+        UNKNOWN_PRECIPITATION,
+        LIGHT_RAIN,
+        HEAVY_RAIN,
+        LIGHT_SNOW,
+        HEAVY_SNOW,
+        LIGHT_RAIN_SNOW,
+        HEAVY_RAIN_SNOW,
+        CLOUDY,
+        ;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitAlarmSettings.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitAlarmSettings.java
new file mode 100644
index 0000000..68b0ac8
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitAlarmSettings.java
@@ -0,0 +1,29 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages;
+
+import androidx.annotation.Nullable;
+
+import java.util.Calendar;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordData;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordDefinition;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordHeader;
+
+//
+// WARNING: This class was auto-generated, please avoid modifying it directly.
+// See nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.codegen.FitCodeGen
+//
+public class FitAlarmSettings extends RecordData {
+    public FitAlarmSettings(final RecordDefinition recordDefinition, final RecordHeader recordHeader) {
+        super(recordDefinition, recordHeader);
+
+        final int globalNumber = recordDefinition.getGlobalFITMessage().getNumber();
+        if (globalNumber != 222) {
+            throw new IllegalArgumentException("FitAlarmSettings expects global messages of " + 222 + ", got " + globalNumber);
+        }
+    }
+
+    @Nullable
+    public Calendar getTime() {
+        return (Calendar) getFieldByNumber(0);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitConnectivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitConnectivity.java
new file mode 100644
index 0000000..676fe9b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitConnectivity.java
@@ -0,0 +1,67 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages;
+
+import androidx.annotation.Nullable;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordData;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordDefinition;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordHeader;
+
+//
+// WARNING: This class was auto-generated, please avoid modifying it directly.
+// See nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.codegen.FitCodeGen
+//
+public class FitConnectivity extends RecordData {
+    public FitConnectivity(final RecordDefinition recordDefinition, final RecordHeader recordHeader) {
+        super(recordDefinition, recordHeader);
+
+        final int globalNumber = recordDefinition.getGlobalFITMessage().getNumber();
+        if (globalNumber != 127) {
+            throw new IllegalArgumentException("FitConnectivity expects global messages of " + 127 + ", got " + globalNumber);
+        }
+    }
+
+    @Nullable
+    public Integer getBluetoothEnabled() {
+        return (Integer) getFieldByNumber(0);
+    }
+
+    @Nullable
+    public String getName() {
+        return (String) getFieldByNumber(3);
+    }
+
+    @Nullable
+    public Integer getLiveTrackingEnabled() {
+        return (Integer) getFieldByNumber(4);
+    }
+
+    @Nullable
+    public Integer getWeatherConditionsEnabled() {
+        return (Integer) getFieldByNumber(5);
+    }
+
+    @Nullable
+    public Integer getWeatherAlertsEnabled() {
+        return (Integer) getFieldByNumber(6);
+    }
+
+    @Nullable
+    public Integer getAutoActivityUploadEnabled() {
+        return (Integer) getFieldByNumber(7);
+    }
+
+    @Nullable
+    public Integer getCourseDownloadEnabled() {
+        return (Integer) getFieldByNumber(8);
+    }
+
+    @Nullable
+    public Integer getWorkoutDownloadEnabled() {
+        return (Integer) getFieldByNumber(9);
+    }
+
+    @Nullable
+    public Integer getGpsEphemerisDownloadEnabled() {
+        return (Integer) getFieldByNumber(10);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitCourse.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitCourse.java
new file mode 100644
index 0000000..cce2cd0
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitCourse.java
@@ -0,0 +1,32 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages;
+
+import androidx.annotation.Nullable;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordData;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordDefinition;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordHeader;
+
+//
+// WARNING: This class was auto-generated, please avoid modifying it directly.
+// See nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.codegen.FitCodeGen
+//
+public class FitCourse extends RecordData {
+    public FitCourse(final RecordDefinition recordDefinition, final RecordHeader recordHeader) {
+        super(recordDefinition, recordHeader);
+
+        final int globalNumber = recordDefinition.getGlobalFITMessage().getNumber();
+        if (globalNumber != 31) {
+            throw new IllegalArgumentException("FitCourse expects global messages of " + 31 + ", got " + globalNumber);
+        }
+    }
+
+    @Nullable
+    public Integer getSport() {
+        return (Integer) getFieldByNumber(4);
+    }
+
+    @Nullable
+    public String getName() {
+        return (String) getFieldByNumber(5);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitDeveloperData.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitDeveloperData.java
new file mode 100644
index 0000000..2cf5ab7
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitDeveloperData.java
@@ -0,0 +1,32 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages;
+
+import androidx.annotation.Nullable;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordData;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordDefinition;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordHeader;
+
+//
+// WARNING: This class was auto-generated, please avoid modifying it directly.
+// See nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.codegen.FitCodeGen
+//
+public class FitDeveloperData extends RecordData {
+    public FitDeveloperData(final RecordDefinition recordDefinition, final RecordHeader recordHeader) {
+        super(recordDefinition, recordHeader);
+
+        final int globalNumber = recordDefinition.getGlobalFITMessage().getNumber();
+        if (globalNumber != 207) {
+            throw new IllegalArgumentException("FitDeveloperData expects global messages of " + 207 + ", got " + globalNumber);
+        }
+    }
+
+    @Nullable
+    public Integer getApplicationId() {
+        return (Integer) getFieldByNumber(1);
+    }
+
+    @Nullable
+    public Integer getDeveloperDataIndex() {
+        return (Integer) getFieldByNumber(3);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitDeviceInfo.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitDeviceInfo.java
new file mode 100644
index 0000000..4c31740
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitDeviceInfo.java
@@ -0,0 +1,47 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages;
+
+import androidx.annotation.Nullable;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordData;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordDefinition;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordHeader;
+
+//
+// WARNING: This class was auto-generated, please avoid modifying it directly.
+// See nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.codegen.FitCodeGen
+//
+public class FitDeviceInfo extends RecordData {
+    public FitDeviceInfo(final RecordDefinition recordDefinition, final RecordHeader recordHeader) {
+        super(recordDefinition, recordHeader);
+
+        final int globalNumber = recordDefinition.getGlobalFITMessage().getNumber();
+        if (globalNumber != 23) {
+            throw new IllegalArgumentException("FitDeviceInfo expects global messages of " + 23 + ", got " + globalNumber);
+        }
+    }
+
+    @Nullable
+    public Integer getManufacturer() {
+        return (Integer) getFieldByNumber(2);
+    }
+
+    @Nullable
+    public Long getSerialNumber() {
+        return (Long) getFieldByNumber(3);
+    }
+
+    @Nullable
+    public Integer getProduct() {
+        return (Integer) getFieldByNumber(4);
+    }
+
+    @Nullable
+    public Integer getSoftwareVersion() {
+        return (Integer) getFieldByNumber(5);
+    }
+
+    @Nullable
+    public Long getTimestamp() {
+        return (Long) getFieldByNumber(253);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitDeviceSettings.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitDeviceSettings.java
new file mode 100644
index 0000000..4d2b5da
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitDeviceSettings.java
@@ -0,0 +1,102 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages;
+
+import androidx.annotation.Nullable;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordData;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordDefinition;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordHeader;
+
+//
+// WARNING: This class was auto-generated, please avoid modifying it directly.
+// See nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.codegen.FitCodeGen
+//
+public class FitDeviceSettings extends RecordData {
+    public FitDeviceSettings(final RecordDefinition recordDefinition, final RecordHeader recordHeader) {
+        super(recordDefinition, recordHeader);
+
+        final int globalNumber = recordDefinition.getGlobalFITMessage().getNumber();
+        if (globalNumber != 2) {
+            throw new IllegalArgumentException("FitDeviceSettings expects global messages of " + 2 + ", got " + globalNumber);
+        }
+    }
+
+    @Nullable
+    public Integer getActiveTimeZone() {
+        return (Integer) getFieldByNumber(0);
+    }
+
+    @Nullable
+    public Long getUtcOffset() {
+        return (Long) getFieldByNumber(1);
+    }
+
+    @Nullable
+    public Long getTimeOffset() {
+        return (Long) getFieldByNumber(2);
+    }
+
+    @Nullable
+    public Integer getTimeMode() {
+        return (Integer) getFieldByNumber(4);
+    }
+
+    @Nullable
+    public Integer getTimeZoneOffset() {
+        return (Integer) getFieldByNumber(5);
+    }
+
+    @Nullable
+    public Integer getBacklightMode() {
+        return (Integer) getFieldByNumber(12);
+    }
+
+    @Nullable
+    public Integer getActivityTrackerEnabled() {
+        return (Integer) getFieldByNumber(36);
+    }
+
+    @Nullable
+    public Integer getMoveAlertEnabled() {
+        return (Integer) getFieldByNumber(46);
+    }
+
+    @Nullable
+    public Integer getDateMode() {
+        return (Integer) getFieldByNumber(47);
+    }
+
+    @Nullable
+    public Integer getDisplayOrientation() {
+        return (Integer) getFieldByNumber(55);
+    }
+
+    @Nullable
+    public Integer getMountingSide() {
+        return (Integer) getFieldByNumber(56);
+    }
+
+    @Nullable
+    public Integer getDefaultPage() {
+        return (Integer) getFieldByNumber(57);
+    }
+
+    @Nullable
+    public Integer getAutosyncMinSteps() {
+        return (Integer) getFieldByNumber(58);
+    }
+
+    @Nullable
+    public Integer getAutosyncMinTime() {
+        return (Integer) getFieldByNumber(59);
+    }
+
+    @Nullable
+    public Integer getBleAutoUploadEnabled() {
+        return (Integer) getFieldByNumber(86);
+    }
+
+    @Nullable
+    public Long getAutoActivityDetect() {
+        return (Long) getFieldByNumber(90);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitEvent.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitEvent.java
new file mode 100644
index 0000000..ba9288a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitEvent.java
@@ -0,0 +1,47 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages;
+
+import androidx.annotation.Nullable;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordData;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordDefinition;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordHeader;
+
+//
+// WARNING: This class was auto-generated, please avoid modifying it directly.
+// See nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.codegen.FitCodeGen
+//
+public class FitEvent extends RecordData {
+    public FitEvent(final RecordDefinition recordDefinition, final RecordHeader recordHeader) {
+        super(recordDefinition, recordHeader);
+
+        final int globalNumber = recordDefinition.getGlobalFITMessage().getNumber();
+        if (globalNumber != 21) {
+            throw new IllegalArgumentException("FitEvent expects global messages of " + 21 + ", got " + globalNumber);
+        }
+    }
+
+    @Nullable
+    public Integer getEvent() {
+        return (Integer) getFieldByNumber(0);
+    }
+
+    @Nullable
+    public Integer getEventType() {
+        return (Integer) getFieldByNumber(1);
+    }
+
+    @Nullable
+    public Long getData() {
+        return (Long) getFieldByNumber(3);
+    }
+
+    @Nullable
+    public Integer getEventGroup() {
+        return (Integer) getFieldByNumber(4);
+    }
+
+    @Nullable
+    public Long getTimestamp() {
+        return (Long) getFieldByNumber(253);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitFieldDescription.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitFieldDescription.java
new file mode 100644
index 0000000..755c8dc
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitFieldDescription.java
@@ -0,0 +1,47 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages;
+
+import androidx.annotation.Nullable;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordData;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordDefinition;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordHeader;
+
+//
+// WARNING: This class was auto-generated, please avoid modifying it directly.
+// See nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.codegen.FitCodeGen
+//
+public class FitFieldDescription extends RecordData {
+    public FitFieldDescription(final RecordDefinition recordDefinition, final RecordHeader recordHeader) {
+        super(recordDefinition, recordHeader);
+
+        final int globalNumber = recordDefinition.getGlobalFITMessage().getNumber();
+        if (globalNumber != 206) {
+            throw new IllegalArgumentException("FitFieldDescription expects global messages of " + 206 + ", got " + globalNumber);
+        }
+    }
+
+    @Nullable
+    public Integer getDeveloperDataIndex() {
+        return (Integer) getFieldByNumber(0);
+    }
+
+    @Nullable
+    public Integer getFieldDefinitionNumber() {
+        return (Integer) getFieldByNumber(1);
+    }
+
+    @Nullable
+    public Integer getFitBaseTypeId() {
+        return (Integer) getFieldByNumber(2);
+    }
+
+    @Nullable
+    public String getFieldName() {
+        return (String) getFieldByNumber(3);
+    }
+
+    @Nullable
+    public String getUnits() {
+        return (String) getFieldByNumber(8);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitFileCreator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitFileCreator.java
new file mode 100644
index 0000000..508775e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitFileCreator.java
@@ -0,0 +1,32 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages;
+
+import androidx.annotation.Nullable;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordData;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordDefinition;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordHeader;
+
+//
+// WARNING: This class was auto-generated, please avoid modifying it directly.
+// See nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.codegen.FitCodeGen
+//
+public class FitFileCreator extends RecordData {
+    public FitFileCreator(final RecordDefinition recordDefinition, final RecordHeader recordHeader) {
+        super(recordDefinition, recordHeader);
+
+        final int globalNumber = recordDefinition.getGlobalFITMessage().getNumber();
+        if (globalNumber != 49) {
+            throw new IllegalArgumentException("FitFileCreator expects global messages of " + 49 + ", got " + globalNumber);
+        }
+    }
+
+    @Nullable
+    public Integer getSoftwareVersion() {
+        return (Integer) getFieldByNumber(0);
+    }
+
+    @Nullable
+    public Integer getHardwareVersion() {
+        return (Integer) getFieldByNumber(1);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitFileId.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitFileId.java
new file mode 100644
index 0000000..01c8905
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitFileId.java
@@ -0,0 +1,63 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages;
+
+import androidx.annotation.Nullable;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.FileType.FILETYPE;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordData;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordDefinition;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordHeader;
+
+//
+// WARNING: This class was auto-generated, please avoid modifying it directly.
+// See nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.codegen.FitCodeGen
+//
+public class FitFileId extends RecordData {
+    public FitFileId(final RecordDefinition recordDefinition, final RecordHeader recordHeader) {
+        super(recordDefinition, recordHeader);
+
+        final int globalNumber = recordDefinition.getGlobalFITMessage().getNumber();
+        if (globalNumber != 0) {
+            throw new IllegalArgumentException("FitFileId expects global messages of " + 0 + ", got " + globalNumber);
+        }
+    }
+
+    @Nullable
+    public FILETYPE getType() {
+        return (FILETYPE) getFieldByNumber(0);
+    }
+
+    @Nullable
+    public Integer getManufacturer() {
+        return (Integer) getFieldByNumber(1);
+    }
+
+    @Nullable
+    public Integer getProduct() {
+        return (Integer) getFieldByNumber(2);
+    }
+
+    @Nullable
+    public Long getSerialNumber() {
+        return (Long) getFieldByNumber(3);
+    }
+
+    @Nullable
+    public Long getTimeCreated() {
+        return (Long) getFieldByNumber(4);
+    }
+
+    @Nullable
+    public Integer getNumber() {
+        return (Integer) getFieldByNumber(5);
+    }
+
+    @Nullable
+    public Integer getManufacturerPartner() {
+        return (Integer) getFieldByNumber(6);
+    }
+
+    @Nullable
+    public String getProductName() {
+        return (String) getFieldByNumber(8);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitGoals.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitGoals.java
new file mode 100644
index 0000000..e7ebf54
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitGoals.java
@@ -0,0 +1,39 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages;
+
+import androidx.annotation.Nullable;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordData;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordDefinition;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordHeader;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionGoalSource.Source;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionGoalType.Type;
+
+//
+// WARNING: This class was auto-generated, please avoid modifying it directly.
+// See nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.codegen.FitCodeGen
+//
+public class FitGoals extends RecordData {
+    public FitGoals(final RecordDefinition recordDefinition, final RecordHeader recordHeader) {
+        super(recordDefinition, recordHeader);
+
+        final int globalNumber = recordDefinition.getGlobalFITMessage().getNumber();
+        if (globalNumber != 15) {
+            throw new IllegalArgumentException("FitGoals expects global messages of " + 15 + ", got " + globalNumber);
+        }
+    }
+
+    @Nullable
+    public Type getType() {
+        return (Type) getFieldByNumber(4);
+    }
+
+    @Nullable
+    public Long getTargetValue() {
+        return (Long) getFieldByNumber(7);
+    }
+
+    @Nullable
+    public Source getSource() {
+        return (Source) getFieldByNumber(11);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitGpsMetadata.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitGpsMetadata.java
new file mode 100644
index 0000000..922940e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitGpsMetadata.java
@@ -0,0 +1,32 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages;
+
+import androidx.annotation.Nullable;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordData;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordDefinition;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordHeader;
+
+//
+// WARNING: This class was auto-generated, please avoid modifying it directly.
+// See nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.codegen.FitCodeGen
+//
+public class FitGpsMetadata extends RecordData {
+    public FitGpsMetadata(final RecordDefinition recordDefinition, final RecordHeader recordHeader) {
+        super(recordDefinition, recordHeader);
+
+        final int globalNumber = recordDefinition.getGlobalFITMessage().getNumber();
+        if (globalNumber != 160) {
+            throw new IllegalArgumentException("FitGpsMetadata expects global messages of " + 160 + ", got " + globalNumber);
+        }
+    }
+
+    @Nullable
+    public Long getEnhancedAltitude() {
+        return (Long) getFieldByNumber(3);
+    }
+
+    @Nullable
+    public Long getEnhancedSpeed() {
+        return (Long) getFieldByNumber(4);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitHrvSummary.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitHrvSummary.java
new file mode 100644
index 0000000..c052ea2
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitHrvSummary.java
@@ -0,0 +1,63 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages;
+
+import androidx.annotation.Nullable;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordData;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordDefinition;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordHeader;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionHrvStatus.HrvStatus;
+
+//
+// WARNING: This class was auto-generated, please avoid modifying it directly.
+// See nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.codegen.FitCodeGen
+//
+public class FitHrvSummary extends RecordData {
+    public FitHrvSummary(final RecordDefinition recordDefinition, final RecordHeader recordHeader) {
+        super(recordDefinition, recordHeader);
+
+        final int globalNumber = recordDefinition.getGlobalFITMessage().getNumber();
+        if (globalNumber != 370) {
+            throw new IllegalArgumentException("FitHrvSummary expects global messages of " + 370 + ", got " + globalNumber);
+        }
+    }
+
+    @Nullable
+    public Float getWeeklyAverage() {
+        return (Float) getFieldByNumber(0);
+    }
+
+    @Nullable
+    public Float getLastNightAverage() {
+        return (Float) getFieldByNumber(1);
+    }
+
+    @Nullable
+    public Float getLastNight5MinHigh() {
+        return (Float) getFieldByNumber(2);
+    }
+
+    @Nullable
+    public Float getBaselineLowUpper() {
+        return (Float) getFieldByNumber(3);
+    }
+
+    @Nullable
+    public Float getBaselineBalancedLower() {
+        return (Float) getFieldByNumber(4);
+    }
+
+    @Nullable
+    public Float getBaselineBalancedUpper() {
+        return (Float) getFieldByNumber(5);
+    }
+
+    @Nullable
+    public HrvStatus getStatus() {
+        return (HrvStatus) getFieldByNumber(6);
+    }
+
+    @Nullable
+    public Long getTimestamp() {
+        return (Long) getFieldByNumber(253);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitHrvValue.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitHrvValue.java
new file mode 100644
index 0000000..10183bb
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitHrvValue.java
@@ -0,0 +1,32 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages;
+
+import androidx.annotation.Nullable;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordData;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordDefinition;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordHeader;
+
+//
+// WARNING: This class was auto-generated, please avoid modifying it directly.
+// See nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.codegen.FitCodeGen
+//
+public class FitHrvValue extends RecordData {
+    public FitHrvValue(final RecordDefinition recordDefinition, final RecordHeader recordHeader) {
+        super(recordDefinition, recordHeader);
+
+        final int globalNumber = recordDefinition.getGlobalFITMessage().getNumber();
+        if (globalNumber != 371) {
+            throw new IllegalArgumentException("FitHrvValue expects global messages of " + 371 + ", got " + globalNumber);
+        }
+    }
+
+    @Nullable
+    public Float getValue() {
+        return (Float) getFieldByNumber(0);
+    }
+
+    @Nullable
+    public Long getTimestamp() {
+        return (Long) getFieldByNumber(253);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitLap.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitLap.java
new file mode 100644
index 0000000..cb23ed9
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitLap.java
@@ -0,0 +1,72 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages;
+
+import androidx.annotation.Nullable;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordData;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordDefinition;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordHeader;
+
+//
+// WARNING: This class was auto-generated, please avoid modifying it directly.
+// See nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.codegen.FitCodeGen
+//
+public class FitLap extends RecordData {
+    public FitLap(final RecordDefinition recordDefinition, final RecordHeader recordHeader) {
+        super(recordDefinition, recordHeader);
+
+        final int globalNumber = recordDefinition.getGlobalFITMessage().getNumber();
+        if (globalNumber != 19) {
+            throw new IllegalArgumentException("FitLap expects global messages of " + 19 + ", got " + globalNumber);
+        }
+    }
+
+    @Nullable
+    public Double getStartLat() {
+        return (Double) getFieldByNumber(3);
+    }
+
+    @Nullable
+    public Double getStartLong() {
+        return (Double) getFieldByNumber(4);
+    }
+
+    @Nullable
+    public Double getEndLat() {
+        return (Double) getFieldByNumber(5);
+    }
+
+    @Nullable
+    public Double getEndLong() {
+        return (Double) getFieldByNumber(6);
+    }
+
+    @Nullable
+    public Double getTotalElapsedTime() {
+        return (Double) getFieldByNumber(7);
+    }
+
+    @Nullable
+    public Double getTotalTimerTime() {
+        return (Double) getFieldByNumber(8);
+    }
+
+    @Nullable
+    public Double getTotalDistance() {
+        return (Double) getFieldByNumber(9);
+    }
+
+    @Nullable
+    public Integer getTotalAscent() {
+        return (Integer) getFieldByNumber(21);
+    }
+
+    @Nullable
+    public Integer getTotalDescent() {
+        return (Integer) getFieldByNumber(22);
+    }
+
+    @Nullable
+    public Long getTimestamp() {
+        return (Long) getFieldByNumber(253);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitMonitoring.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitMonitoring.java
new file mode 100644
index 0000000..1ffad4a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitMonitoring.java
@@ -0,0 +1,121 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages;
+
+import androidx.annotation.Nullable;
+
+import java.util.Optional;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.GarminTimeUtils;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordData;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordDefinition;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordHeader;
+
+//
+// WARNING: This class was auto-generated, please avoid modifying it directly.
+// See nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.codegen.FitCodeGen
+//
+public class FitMonitoring extends RecordData {
+    public FitMonitoring(final RecordDefinition recordDefinition, final RecordHeader recordHeader) {
+        super(recordDefinition, recordHeader);
+
+        final int globalNumber = recordDefinition.getGlobalFITMessage().getNumber();
+        if (globalNumber != 55) {
+            throw new IllegalArgumentException("FitMonitoring expects global messages of " + 55 + ", got " + globalNumber);
+        }
+    }
+
+    @Nullable
+    public Long getDistance() {
+        return (Long) getFieldByNumber(2);
+    }
+
+    @Nullable
+    public Long getCycles() {
+        return (Long) getFieldByNumber(3);
+    }
+
+    @Nullable
+    public Long getActiveTime() {
+        return (Long) getFieldByNumber(4);
+    }
+
+    @Nullable
+    public Integer getActivityType() {
+        return (Integer) getFieldByNumber(5);
+    }
+
+    @Nullable
+    public Integer getActiveCalories() {
+        return (Integer) getFieldByNumber(19);
+    }
+
+    @Nullable
+    public Integer getDurationMin() {
+        return (Integer) getFieldByNumber(29);
+    }
+
+    @Nullable
+    public Integer getCurrentActivityTypeIntensity() {
+        return (Integer) getFieldByNumber(24);
+    }
+
+    @Nullable
+    public Integer getTimestamp16() {
+        return (Integer) getFieldByNumber(26);
+    }
+
+    @Nullable
+    public Integer getHeartRate() {
+        return (Integer) getFieldByNumber(27);
+    }
+
+    @Nullable
+    public Integer getModerateActivityMinutes() {
+        return (Integer) getFieldByNumber(33);
+    }
+
+    @Nullable
+    public Integer getVigorousActivityMinutes() {
+        return (Integer) getFieldByNumber(34);
+    }
+
+    @Nullable
+    public Long getTimestamp() {
+        return (Long) getFieldByNumber(253);
+    }
+
+    // manual changes below
+
+    public Long computeTimestamp(final Long lastMonitoringTimestamp) {
+        final Integer timestamp16 = getTimestamp16();
+
+        if (timestamp16 != null && lastMonitoringTimestamp != null) {
+            final int referenceGarminTs = GarminTimeUtils.unixTimeToGarminTimestamp(lastMonitoringTimestamp.intValue());
+            return (long) (lastMonitoringTimestamp.intValue() + ((timestamp16 - (referenceGarminTs & 0xffff)) & 0xffff));
+        }
+
+        return getComputedTimestamp();
+    }
+
+    public Optional<Integer> getComputedActivityType() {
+        final Integer activityType = getActivityType();
+        if (activityType != null) {
+            return Optional.of(activityType);
+        }
+
+        final Integer currentActivityTypeIntensity = getCurrentActivityTypeIntensity();
+        if (currentActivityTypeIntensity != null) {
+            return Optional.of(currentActivityTypeIntensity & 0x1F);
+        }
+
+        return Optional.empty();
+    }
+
+    public Integer getComputedIntensity() {
+        final Integer currentActivityTypeIntensity = getCurrentActivityTypeIntensity();
+        if (currentActivityTypeIntensity != null) {
+            return (currentActivityTypeIntensity >> 5) & 0x7;
+        }
+
+        return null;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitMonitoringHrData.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitMonitoringHrData.java
new file mode 100644
index 0000000..9dc3378
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitMonitoringHrData.java
@@ -0,0 +1,37 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages;
+
+import androidx.annotation.Nullable;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordData;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordDefinition;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordHeader;
+
+//
+// WARNING: This class was auto-generated, please avoid modifying it directly.
+// See nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.codegen.FitCodeGen
+//
+public class FitMonitoringHrData extends RecordData {
+    public FitMonitoringHrData(final RecordDefinition recordDefinition, final RecordHeader recordHeader) {
+        super(recordDefinition, recordHeader);
+
+        final int globalNumber = recordDefinition.getGlobalFITMessage().getNumber();
+        if (globalNumber != 211) {
+            throw new IllegalArgumentException("FitMonitoringHrData expects global messages of " + 211 + ", got " + globalNumber);
+        }
+    }
+
+    @Nullable
+    public Integer getRestingHeartRate() {
+        return (Integer) getFieldByNumber(0);
+    }
+
+    @Nullable
+    public Integer getCurrentDayRestingHeartRate() {
+        return (Integer) getFieldByNumber(1);
+    }
+
+    @Nullable
+    public Long getTimestamp() {
+        return (Long) getFieldByNumber(253);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitMonitoringInfo.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitMonitoringInfo.java
new file mode 100644
index 0000000..6380bd3
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitMonitoringInfo.java
@@ -0,0 +1,67 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages;
+
+import androidx.annotation.Nullable;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordData;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordDefinition;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordHeader;
+
+//
+// WARNING: This class was auto-generated, please avoid modifying it directly.
+// See nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.codegen.FitCodeGen
+//
+public class FitMonitoringInfo extends RecordData {
+    public FitMonitoringInfo(final RecordDefinition recordDefinition, final RecordHeader recordHeader) {
+        super(recordDefinition, recordHeader);
+
+        final int globalNumber = recordDefinition.getGlobalFITMessage().getNumber();
+        if (globalNumber != 103) {
+            throw new IllegalArgumentException("FitMonitoringInfo expects global messages of " + 103 + ", got " + globalNumber);
+        }
+    }
+
+    @Nullable
+    public Long getLocalTimestamp() {
+        return (Long) getFieldByNumber(0);
+    }
+
+    @Nullable
+    public Number[] getActivityType() {
+        final Object[] objectsArray = (Object[]) getFieldByNumber(1);
+        final Number[] ret = new Number[objectsArray.length];
+        for (int i = 0; i < objectsArray.length; i++) {
+            ret[i] = (Number) objectsArray[i];
+        }
+        return ret;
+    }
+
+    @Nullable
+    public Number[] getStepsToDistance() {
+        final Object[] objectsArray = (Object[]) getFieldByNumber(3);
+        final Number[] ret = new Number[objectsArray.length];
+        for (int i = 0; i < objectsArray.length; i++) {
+            ret[i] = (Number) objectsArray[i];
+        }
+        return ret;
+    }
+
+    @Nullable
+    public Number[] getStepsToCalories() {
+        final Object[] objectsArray = (Object[]) getFieldByNumber(4);
+        final Number[] ret = new Number[objectsArray.length];
+        for (int i = 0; i < objectsArray.length; i++) {
+            ret[i] = (Number) objectsArray[i];
+        }
+        return ret;
+    }
+
+    @Nullable
+    public Integer getRestingMetabolicRate() {
+        return (Integer) getFieldByNumber(5);
+    }
+
+    @Nullable
+    public Long getTimestamp() {
+        return (Long) getFieldByNumber(253);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitPhysiologicalMetrics.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitPhysiologicalMetrics.java
new file mode 100644
index 0000000..6410c27
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitPhysiologicalMetrics.java
@@ -0,0 +1,52 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages;
+
+import androidx.annotation.Nullable;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordData;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordDefinition;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordHeader;
+
+//
+// WARNING: This class was auto-generated, please avoid modifying it directly.
+// See nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.codegen.FitCodeGen
+//
+public class FitPhysiologicalMetrics extends RecordData {
+    public FitPhysiologicalMetrics(final RecordDefinition recordDefinition, final RecordHeader recordHeader) {
+        super(recordDefinition, recordHeader);
+
+        final int globalNumber = recordDefinition.getGlobalFITMessage().getNumber();
+        if (globalNumber != 140) {
+            throw new IllegalArgumentException("FitPhysiologicalMetrics expects global messages of " + 140 + ", got " + globalNumber);
+        }
+    }
+
+    @Nullable
+    public Float getAerobicEffect() {
+        return (Float) getFieldByNumber(4);
+    }
+
+    @Nullable
+    public Double getMetMax() {
+        return (Double) getFieldByNumber(7);
+    }
+
+    @Nullable
+    public Integer getRecoveryTime() {
+        return (Integer) getFieldByNumber(9);
+    }
+
+    @Nullable
+    public Integer getLactateThresholdHeartRate() {
+        return (Integer) getFieldByNumber(14);
+    }
+
+    @Nullable
+    public Float getAnaerobicEffect() {
+        return (Float) getFieldByNumber(20);
+    }
+
+    @Nullable
+    public Long getTimestamp() {
+        return (Long) getFieldByNumber(253);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitRecord.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitRecord.java
new file mode 100644
index 0000000..823e52b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitRecord.java
@@ -0,0 +1,146 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages;
+
+import androidx.annotation.Nullable;
+
+import java.util.Date;
+
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityPoint;
+import nodomain.freeyourgadget.gadgetbridge.model.GPSCoordinate;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.GarminUtils;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordData;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordDefinition;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordHeader;
+
+//
+// WARNING: This class was auto-generated, please avoid modifying it directly.
+// See nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.codegen.FitCodeGen
+//
+public class FitRecord extends RecordData {
+    public FitRecord(final RecordDefinition recordDefinition, final RecordHeader recordHeader) {
+        super(recordDefinition, recordHeader);
+
+        final int globalNumber = recordDefinition.getGlobalFITMessage().getNumber();
+        if (globalNumber != 20) {
+            throw new IllegalArgumentException("FitRecord expects global messages of " + 20 + ", got " + globalNumber);
+        }
+    }
+
+    @Nullable
+    public Double getLatitude() {
+        return (Double) getFieldByNumber(0);
+    }
+
+    @Nullable
+    public Double getLongitude() {
+        return (Double) getFieldByNumber(1);
+    }
+
+    @Nullable
+    public Float getAltitude() {
+        return (Float) getFieldByNumber(2);
+    }
+
+    @Nullable
+    public Integer getHeartRate() {
+        return (Integer) getFieldByNumber(3);
+    }
+
+    @Nullable
+    public Integer getCadence() {
+        return (Integer) getFieldByNumber(4);
+    }
+
+    @Nullable
+    public Double getDistance() {
+        return (Double) getFieldByNumber(5);
+    }
+
+    @Nullable
+    public Float getSpeed() {
+        return (Float) getFieldByNumber(6);
+    }
+
+    @Nullable
+    public Integer getPower() {
+        return (Integer) getFieldByNumber(7);
+    }
+
+    @Nullable
+    public Long getAccumulatedPower() {
+        return (Long) getFieldByNumber(29);
+    }
+
+    @Nullable
+    public Float getOscillation() {
+        return (Float) getFieldByNumber(39);
+    }
+
+    @Nullable
+    public Integer getActivity() {
+        return (Integer) getFieldByNumber(42);
+    }
+
+    @Nullable
+    public Float getFractionalCadence() {
+        return (Float) getFieldByNumber(53);
+    }
+
+    @Nullable
+    public Double getEnhancedSpeed() {
+        return (Double) getFieldByNumber(73);
+    }
+
+    @Nullable
+    public Double getEnhancedAltitude() {
+        return (Double) getFieldByNumber(78);
+    }
+
+    @Nullable
+    public Float getVerticalRatio() {
+        return (Float) getFieldByNumber(83);
+    }
+
+    @Nullable
+    public Float getStepLength() {
+        return (Float) getFieldByNumber(85);
+    }
+
+    @Nullable
+    public Integer getWristHeartRate() {
+        return (Integer) getFieldByNumber(136);
+    }
+
+    @Nullable
+    public Integer getBodyBattery() {
+        return (Integer) getFieldByNumber(143);
+    }
+
+    @Nullable
+    public Long getTimestamp() {
+        return (Long) getFieldByNumber(253);
+    }
+
+    // manual changes below
+
+    public ActivityPoint toActivityPoint() {
+        final ActivityPoint activityPoint = new ActivityPoint();
+        activityPoint.setTime(new Date(getComputedTimestamp() * 1000L));
+        if (getLatitude() != null && getLongitude() != null) {
+            activityPoint.setLocation(new GPSCoordinate(
+                    getLongitude(),
+                    getLatitude(),
+                    getEnhancedAltitude() != null ? getEnhancedAltitude() : GPSCoordinate.UNKNOWN_ALTITUDE
+            ));
+        }
+        if (getHeartRate() != null) {
+            activityPoint.setHeartRate(getHeartRate());
+        }
+        if (getEnhancedSpeed() != null) {
+            activityPoint.setSpeed(getEnhancedSpeed().floatValue());
+        }
+        if (getCadence() != null) {
+            activityPoint.setCadence(getCadence());
+        }
+        return activityPoint;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitRecordDataFactory.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitRecordDataFactory.java
new file mode 100644
index 0000000..583bf66
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitRecordDataFactory.java
@@ -0,0 +1,100 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordData;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordDefinition;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordHeader;
+
+//
+// WARNING: This class was auto-generated, please avoid modifying it directly.
+// See nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.codegen.FitCodeGen
+//
+public class FitRecordDataFactory {
+    private FitRecordDataFactory() {
+        // use create
+    }
+
+    public static RecordData create(final RecordDefinition recordDefinition, final RecordHeader recordHeader) {
+        switch (recordDefinition.getGlobalFITMessage().getNumber()) {
+            case 0:
+                return new FitFileId(recordDefinition, recordHeader);
+            case 2:
+                return new FitDeviceSettings(recordDefinition, recordHeader);
+            case 3:
+                return new FitUserProfile(recordDefinition, recordHeader);
+            case 7:
+                return new FitZonesTarget(recordDefinition, recordHeader);
+            case 12:
+                return new FitSport(recordDefinition, recordHeader);
+            case 15:
+                return new FitGoals(recordDefinition, recordHeader);
+            case 18:
+                return new FitSession(recordDefinition, recordHeader);
+            case 19:
+                return new FitLap(recordDefinition, recordHeader);
+            case 20:
+                return new FitRecord(recordDefinition, recordHeader);
+            case 21:
+                return new FitEvent(recordDefinition, recordHeader);
+            case 23:
+                return new FitDeviceInfo(recordDefinition, recordHeader);
+            case 26:
+                return new FitWorkout(recordDefinition, recordHeader);
+            case 31:
+                return new FitCourse(recordDefinition, recordHeader);
+            case 49:
+                return new FitFileCreator(recordDefinition, recordHeader);
+            case 55:
+                return new FitMonitoring(recordDefinition, recordHeader);
+            case 103:
+                return new FitMonitoringInfo(recordDefinition, recordHeader);
+            case 127:
+                return new FitConnectivity(recordDefinition, recordHeader);
+            case 128:
+                return new FitWeather(recordDefinition, recordHeader);
+            case 140:
+                return new FitPhysiologicalMetrics(recordDefinition, recordHeader);
+            case 159:
+                return new FitWatchfaceSettings(recordDefinition, recordHeader);
+            case 160:
+                return new FitGpsMetadata(recordDefinition, recordHeader);
+            case 162:
+                return new FitTimestampCorrelation(recordDefinition, recordHeader);
+            case 206:
+                return new FitFieldDescription(recordDefinition, recordHeader);
+            case 207:
+                return new FitDeveloperData(recordDefinition, recordHeader);
+            case 211:
+                return new FitMonitoringHrData(recordDefinition, recordHeader);
+            case 216:
+                return new FitTimeInZone(recordDefinition, recordHeader);
+            case 222:
+                return new FitAlarmSettings(recordDefinition, recordHeader);
+            case 225:
+                return new FitSet(recordDefinition, recordHeader);
+            case 227:
+                return new FitStressLevel(recordDefinition, recordHeader);
+            case 269:
+                return new FitSpo2(recordDefinition, recordHeader);
+            case 273:
+                return new FitSleepDataInfo(recordDefinition, recordHeader);
+            case 274:
+                return new FitSleepDataRaw(recordDefinition, recordHeader);
+            case 275:
+                return new FitSleepStage(recordDefinition, recordHeader);
+            case 297:
+                return new FitRespirationRate(recordDefinition, recordHeader);
+            case 346:
+                return new FitSleepStats(recordDefinition, recordHeader);
+            case 370:
+                return new FitHrvSummary(recordDefinition, recordHeader);
+            case 371:
+                return new FitHrvValue(recordDefinition, recordHeader);
+            case 397:
+                return new FitSkinTempRaw(recordDefinition, recordHeader);
+            case 398:
+                return new FitSkinTempOvernight(recordDefinition, recordHeader);
+        }
+
+        return new RecordData(recordDefinition, recordHeader);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitRespirationRate.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitRespirationRate.java
new file mode 100644
index 0000000..6d7fe0e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitRespirationRate.java
@@ -0,0 +1,32 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages;
+
+import androidx.annotation.Nullable;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordData;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordDefinition;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordHeader;
+
+//
+// WARNING: This class was auto-generated, please avoid modifying it directly.
+// See nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.codegen.FitCodeGen
+//
+public class FitRespirationRate extends RecordData {
+    public FitRespirationRate(final RecordDefinition recordDefinition, final RecordHeader recordHeader) {
+        super(recordDefinition, recordHeader);
+
+        final int globalNumber = recordDefinition.getGlobalFITMessage().getNumber();
+        if (globalNumber != 297) {
+            throw new IllegalArgumentException("FitRespirationRate expects global messages of " + 297 + ", got " + globalNumber);
+        }
+    }
+
+    @Nullable
+    public Float getRespirationRate() {
+        return (Float) getFieldByNumber(0);
+    }
+
+    @Nullable
+    public Long getTimestamp() {
+        return (Long) getFieldByNumber(253);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitSession.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitSession.java
new file mode 100644
index 0000000..7ef307b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitSession.java
@@ -0,0 +1,212 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages;
+
+import androidx.annotation.Nullable;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordData;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordDefinition;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordHeader;
+
+//
+// WARNING: This class was auto-generated, please avoid modifying it directly.
+// See nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.codegen.FitCodeGen
+//
+public class FitSession extends RecordData {
+    public FitSession(final RecordDefinition recordDefinition, final RecordHeader recordHeader) {
+        super(recordDefinition, recordHeader);
+
+        final int globalNumber = recordDefinition.getGlobalFITMessage().getNumber();
+        if (globalNumber != 18) {
+            throw new IllegalArgumentException("FitSession expects global messages of " + 18 + ", got " + globalNumber);
+        }
+    }
+
+    @Nullable
+    public Integer getEvent() {
+        return (Integer) getFieldByNumber(0);
+    }
+
+    @Nullable
+    public Integer getEventType() {
+        return (Integer) getFieldByNumber(1);
+    }
+
+    @Nullable
+    public Long getStartTime() {
+        return (Long) getFieldByNumber(2);
+    }
+
+    @Nullable
+    public Double getStartLatitude() {
+        return (Double) getFieldByNumber(3);
+    }
+
+    @Nullable
+    public Double getStartLongitude() {
+        return (Double) getFieldByNumber(4);
+    }
+
+    @Nullable
+    public Integer getSport() {
+        return (Integer) getFieldByNumber(5);
+    }
+
+    @Nullable
+    public Integer getSubSport() {
+        return (Integer) getFieldByNumber(6);
+    }
+
+    @Nullable
+    public Long getTotalElapsedTime() {
+        return (Long) getFieldByNumber(7);
+    }
+
+    @Nullable
+    public Long getTotalTimerTime() {
+        return (Long) getFieldByNumber(8);
+    }
+
+    @Nullable
+    public Long getTotalDistance() {
+        return (Long) getFieldByNumber(9);
+    }
+
+    @Nullable
+    public Long getTotalSteps() {
+        return (Long) getFieldByNumber(10);
+    }
+
+    @Nullable
+    public Integer getTotalCalories() {
+        return (Integer) getFieldByNumber(11);
+    }
+
+    @Nullable
+    public Integer getAverageHeartRate() {
+        return (Integer) getFieldByNumber(16);
+    }
+
+    @Nullable
+    public Integer getMaxHeartRate() {
+        return (Integer) getFieldByNumber(17);
+    }
+
+    @Nullable
+    public Integer getAverageCadence() {
+        return (Integer) getFieldByNumber(18);
+    }
+
+    @Nullable
+    public Integer getMaxCadence() {
+        return (Integer) getFieldByNumber(19);
+    }
+
+    @Nullable
+    public Integer getTotalAscent() {
+        return (Integer) getFieldByNumber(22);
+    }
+
+    @Nullable
+    public Integer getTotalDescent() {
+        return (Integer) getFieldByNumber(23);
+    }
+
+    @Nullable
+    public Integer getFirstLapIndex() {
+        return (Integer) getFieldByNumber(25);
+    }
+
+    @Nullable
+    public Integer getNumLaps() {
+        return (Integer) getFieldByNumber(26);
+    }
+
+    @Nullable
+    public Double getNecLatitude() {
+        return (Double) getFieldByNumber(29);
+    }
+
+    @Nullable
+    public Double getNecLongitude() {
+        return (Double) getFieldByNumber(30);
+    }
+
+    @Nullable
+    public Double getSwcLatitude() {
+        return (Double) getFieldByNumber(31);
+    }
+
+    @Nullable
+    public Double getSwcLongitude() {
+        return (Double) getFieldByNumber(32);
+    }
+
+    @Nullable
+    public Double getEndLatitude() {
+        return (Double) getFieldByNumber(38);
+    }
+
+    @Nullable
+    public Double getEndLongitude() {
+        return (Double) getFieldByNumber(39);
+    }
+
+    @Nullable
+    public String getSportProfileName() {
+        return (String) getFieldByNumber(110);
+    }
+
+    @Nullable
+    public Double getEnhancedAvgSpeed() {
+        return (Double) getFieldByNumber(124);
+    }
+
+    @Nullable
+    public Double getEnhancedMaxSpeed() {
+        return (Double) getFieldByNumber(125);
+    }
+
+    @Nullable
+    public Float getEnhancedAvgRespirationRate() {
+        return (Float) getFieldByNumber(169);
+    }
+
+    @Nullable
+    public Float getEnhancedMaxRespirationRate() {
+        return (Float) getFieldByNumber(170);
+    }
+
+    @Nullable
+    public Integer getEstimatedSweatLoss() {
+        return (Integer) getFieldByNumber(178);
+    }
+
+    @Nullable
+    public Float getEnhancedMinRespirationRate() {
+        return (Float) getFieldByNumber(180);
+    }
+
+    @Nullable
+    public Integer getAvgSpo2() {
+        return (Integer) getFieldByNumber(194);
+    }
+
+    @Nullable
+    public Integer getAvgStress() {
+        return (Integer) getFieldByNumber(195);
+    }
+
+    @Nullable
+    public Integer getHrvSdrr() {
+        return (Integer) getFieldByNumber(197);
+    }
+
+    @Nullable
+    public Integer getHrvRmssd() {
+        return (Integer) getFieldByNumber(198);
+    }
+
+    @Nullable
+    public Long getTimestamp() {
+        return (Long) getFieldByNumber(253);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitSet.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitSet.java
new file mode 100644
index 0000000..255793f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitSet.java
@@ -0,0 +1,62 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages;
+
+import androidx.annotation.Nullable;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordData;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordDefinition;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordHeader;
+
+//
+// WARNING: This class was auto-generated, please avoid modifying it directly.
+// See nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.codegen.FitCodeGen
+//
+public class FitSet extends RecordData {
+    public FitSet(final RecordDefinition recordDefinition, final RecordHeader recordHeader) {
+        super(recordDefinition, recordHeader);
+
+        final int globalNumber = recordDefinition.getGlobalFITMessage().getNumber();
+        if (globalNumber != 225) {
+            throw new IllegalArgumentException("FitSet expects global messages of " + 225 + ", got " + globalNumber);
+        }
+    }
+
+    @Nullable
+    public Double getDuration() {
+        return (Double) getFieldByNumber(0);
+    }
+
+    @Nullable
+    public Integer getRepetitions() {
+        return (Integer) getFieldByNumber(3);
+    }
+
+    @Nullable
+    public Float getWeight() {
+        return (Float) getFieldByNumber(4);
+    }
+
+    @Nullable
+    public Integer getSetType() {
+        return (Integer) getFieldByNumber(5);
+    }
+
+    @Nullable
+    public Long getStartTime() {
+        return (Long) getFieldByNumber(6);
+    }
+
+    @Nullable
+    public Integer getCategory() {
+        return (Integer) getFieldByNumber(7);
+    }
+
+    @Nullable
+    public Integer getMessageIndex() {
+        return (Integer) getFieldByNumber(10);
+    }
+
+    @Nullable
+    public Long getTimestamp() {
+        return (Long) getFieldByNumber(254);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitSkinTempOvernight.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitSkinTempOvernight.java
new file mode 100644
index 0000000..e3efa54
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitSkinTempOvernight.java
@@ -0,0 +1,47 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages;
+
+import androidx.annotation.Nullable;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordData;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordDefinition;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordHeader;
+
+//
+// WARNING: This class was auto-generated, please avoid modifying it directly.
+// See nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.codegen.FitCodeGen
+//
+public class FitSkinTempOvernight extends RecordData {
+    public FitSkinTempOvernight(final RecordDefinition recordDefinition, final RecordHeader recordHeader) {
+        super(recordDefinition, recordHeader);
+
+        final int globalNumber = recordDefinition.getGlobalFITMessage().getNumber();
+        if (globalNumber != 398) {
+            throw new IllegalArgumentException("FitSkinTempOvernight expects global messages of " + 398 + ", got " + globalNumber);
+        }
+    }
+
+    @Nullable
+    public Long getLocalTimestamp() {
+        return (Long) getFieldByNumber(0);
+    }
+
+    @Nullable
+    public Float getAverageDeviation() {
+        return (Float) getFieldByNumber(1);
+    }
+
+    @Nullable
+    public Float getAverage7DayDeviation() {
+        return (Float) getFieldByNumber(2);
+    }
+
+    @Nullable
+    public Integer getUnk3() {
+        return (Integer) getFieldByNumber(3);
+    }
+
+    @Nullable
+    public Long getTimestamp() {
+        return (Long) getFieldByNumber(253);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitSkinTempRaw.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitSkinTempRaw.java
new file mode 100644
index 0000000..231b627
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitSkinTempRaw.java
@@ -0,0 +1,32 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages;
+
+import androidx.annotation.Nullable;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordData;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordDefinition;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordHeader;
+
+//
+// WARNING: This class was auto-generated, please avoid modifying it directly.
+// See nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.codegen.FitCodeGen
+//
+public class FitSkinTempRaw extends RecordData {
+    public FitSkinTempRaw(final RecordDefinition recordDefinition, final RecordHeader recordHeader) {
+        super(recordDefinition, recordHeader);
+
+        final int globalNumber = recordDefinition.getGlobalFITMessage().getNumber();
+        if (globalNumber != 397) {
+            throw new IllegalArgumentException("FitSkinTempRaw expects global messages of " + 397 + ", got " + globalNumber);
+        }
+    }
+
+    @Nullable
+    public Float getDeviation() {
+        return (Float) getFieldByNumber(1);
+    }
+
+    @Nullable
+    public Long getTimestamp() {
+        return (Long) getFieldByNumber(253);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitSleepDataInfo.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitSleepDataInfo.java
new file mode 100644
index 0000000..596c274
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitSleepDataInfo.java
@@ -0,0 +1,52 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages;
+
+import androidx.annotation.Nullable;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordData;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordDefinition;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordHeader;
+
+//
+// WARNING: This class was auto-generated, please avoid modifying it directly.
+// See nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.codegen.FitCodeGen
+//
+public class FitSleepDataInfo extends RecordData {
+    public FitSleepDataInfo(final RecordDefinition recordDefinition, final RecordHeader recordHeader) {
+        super(recordDefinition, recordHeader);
+
+        final int globalNumber = recordDefinition.getGlobalFITMessage().getNumber();
+        if (globalNumber != 273) {
+            throw new IllegalArgumentException("FitSleepDataInfo expects global messages of " + 273 + ", got " + globalNumber);
+        }
+    }
+
+    @Nullable
+    public Integer getUnk0() {
+        return (Integer) getFieldByNumber(0);
+    }
+
+    @Nullable
+    public Integer getSampleLength() {
+        return (Integer) getFieldByNumber(1);
+    }
+
+    @Nullable
+    public Long getLocalTimestamp() {
+        return (Long) getFieldByNumber(2);
+    }
+
+    @Nullable
+    public Integer getUnk3() {
+        return (Integer) getFieldByNumber(3);
+    }
+
+    @Nullable
+    public String getVersion() {
+        return (String) getFieldByNumber(4);
+    }
+
+    @Nullable
+    public Long getTimestamp() {
+        return (Long) getFieldByNumber(253);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitSleepDataRaw.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitSleepDataRaw.java
new file mode 100644
index 0000000..8c59e99
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitSleepDataRaw.java
@@ -0,0 +1,27 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages;
+
+import androidx.annotation.Nullable;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordData;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordDefinition;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordHeader;
+
+//
+// WARNING: This class was auto-generated, please avoid modifying it directly.
+// See nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.codegen.FitCodeGen
+//
+public class FitSleepDataRaw extends RecordData {
+    public FitSleepDataRaw(final RecordDefinition recordDefinition, final RecordHeader recordHeader) {
+        super(recordDefinition, recordHeader);
+
+        final int globalNumber = recordDefinition.getGlobalFITMessage().getNumber();
+        if (globalNumber != 274) {
+            throw new IllegalArgumentException("FitSleepDataRaw expects global messages of " + 274 + ", got " + globalNumber);
+        }
+    }
+
+    @Nullable
+    public Integer getBytes() {
+        return (Integer) getFieldByNumber(0);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitSleepStage.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitSleepStage.java
new file mode 100644
index 0000000..038137f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitSleepStage.java
@@ -0,0 +1,33 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages;
+
+import androidx.annotation.Nullable;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordData;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordDefinition;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordHeader;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionSleepStage.SleepStage;
+
+//
+// WARNING: This class was auto-generated, please avoid modifying it directly.
+// See nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.codegen.FitCodeGen
+//
+public class FitSleepStage extends RecordData {
+    public FitSleepStage(final RecordDefinition recordDefinition, final RecordHeader recordHeader) {
+        super(recordDefinition, recordHeader);
+
+        final int globalNumber = recordDefinition.getGlobalFITMessage().getNumber();
+        if (globalNumber != 275) {
+            throw new IllegalArgumentException("FitSleepStage expects global messages of " + 275 + ", got " + globalNumber);
+        }
+    }
+
+    @Nullable
+    public SleepStage getSleepStage() {
+        return (SleepStage) getFieldByNumber(0);
+    }
+
+    @Nullable
+    public Long getTimestamp() {
+        return (Long) getFieldByNumber(253);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitSleepStats.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitSleepStats.java
new file mode 100644
index 0000000..75f1b0b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitSleepStats.java
@@ -0,0 +1,107 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages;
+
+import androidx.annotation.Nullable;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordData;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordDefinition;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordHeader;
+
+//
+// WARNING: This class was auto-generated, please avoid modifying it directly.
+// See nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.codegen.FitCodeGen
+//
+public class FitSleepStats extends RecordData {
+    public FitSleepStats(final RecordDefinition recordDefinition, final RecordHeader recordHeader) {
+        super(recordDefinition, recordHeader);
+
+        final int globalNumber = recordDefinition.getGlobalFITMessage().getNumber();
+        if (globalNumber != 346) {
+            throw new IllegalArgumentException("FitSleepStats expects global messages of " + 346 + ", got " + globalNumber);
+        }
+    }
+
+    @Nullable
+    public Integer getCombinedAwakeScore() {
+        return (Integer) getFieldByNumber(0);
+    }
+
+    @Nullable
+    public Integer getAwakeTimeScore() {
+        return (Integer) getFieldByNumber(1);
+    }
+
+    @Nullable
+    public Integer getAwakeningsCountScore() {
+        return (Integer) getFieldByNumber(2);
+    }
+
+    @Nullable
+    public Integer getDeepSleepScore() {
+        return (Integer) getFieldByNumber(3);
+    }
+
+    @Nullable
+    public Integer getSleepDurationScore() {
+        return (Integer) getFieldByNumber(4);
+    }
+
+    @Nullable
+    public Integer getLightSleepScore() {
+        return (Integer) getFieldByNumber(5);
+    }
+
+    @Nullable
+    public Integer getOverallSleepScore() {
+        return (Integer) getFieldByNumber(6);
+    }
+
+    @Nullable
+    public Integer getSleepQualityScore() {
+        return (Integer) getFieldByNumber(7);
+    }
+
+    @Nullable
+    public Integer getSleepRecoveryScore() {
+        return (Integer) getFieldByNumber(8);
+    }
+
+    @Nullable
+    public Integer getRemSleepScore() {
+        return (Integer) getFieldByNumber(9);
+    }
+
+    @Nullable
+    public Integer getSleepRestlessnessScore() {
+        return (Integer) getFieldByNumber(10);
+    }
+
+    @Nullable
+    public Integer getAwakeningsCount() {
+        return (Integer) getFieldByNumber(11);
+    }
+
+    @Nullable
+    public Integer getUnk12() {
+        return (Integer) getFieldByNumber(12);
+    }
+
+    @Nullable
+    public Integer getUnk13() {
+        return (Integer) getFieldByNumber(13);
+    }
+
+    @Nullable
+    public Integer getInterruptionsScore() {
+        return (Integer) getFieldByNumber(14);
+    }
+
+    @Nullable
+    public Float getAverageStressDuringSleep() {
+        return (Float) getFieldByNumber(15);
+    }
+
+    @Nullable
+    public Integer getUnk16() {
+        return (Integer) getFieldByNumber(16);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitSpo2.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitSpo2.java
new file mode 100644
index 0000000..4c89e01
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitSpo2.java
@@ -0,0 +1,42 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages;
+
+import androidx.annotation.Nullable;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordData;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordDefinition;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordHeader;
+
+//
+// WARNING: This class was auto-generated, please avoid modifying it directly.
+// See nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.codegen.FitCodeGen
+//
+public class FitSpo2 extends RecordData {
+    public FitSpo2(final RecordDefinition recordDefinition, final RecordHeader recordHeader) {
+        super(recordDefinition, recordHeader);
+
+        final int globalNumber = recordDefinition.getGlobalFITMessage().getNumber();
+        if (globalNumber != 269) {
+            throw new IllegalArgumentException("FitSpo2 expects global messages of " + 269 + ", got " + globalNumber);
+        }
+    }
+
+    @Nullable
+    public Integer getReadingSpo2() {
+        return (Integer) getFieldByNumber(0);
+    }
+
+    @Nullable
+    public Integer getReadingConfidence() {
+        return (Integer) getFieldByNumber(1);
+    }
+
+    @Nullable
+    public Integer getMode() {
+        return (Integer) getFieldByNumber(2);
+    }
+
+    @Nullable
+    public Long getTimestamp() {
+        return (Long) getFieldByNumber(253);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitSport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitSport.java
new file mode 100644
index 0000000..8fc2526
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitSport.java
@@ -0,0 +1,37 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages;
+
+import androidx.annotation.Nullable;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordData;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordDefinition;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordHeader;
+
+//
+// WARNING: This class was auto-generated, please avoid modifying it directly.
+// See nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.codegen.FitCodeGen
+//
+public class FitSport extends RecordData {
+    public FitSport(final RecordDefinition recordDefinition, final RecordHeader recordHeader) {
+        super(recordDefinition, recordHeader);
+
+        final int globalNumber = recordDefinition.getGlobalFITMessage().getNumber();
+        if (globalNumber != 12) {
+            throw new IllegalArgumentException("FitSport expects global messages of " + 12 + ", got " + globalNumber);
+        }
+    }
+
+    @Nullable
+    public Integer getSport() {
+        return (Integer) getFieldByNumber(0);
+    }
+
+    @Nullable
+    public Integer getSubSport() {
+        return (Integer) getFieldByNumber(1);
+    }
+
+    @Nullable
+    public String getName() {
+        return (String) getFieldByNumber(3);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitStressLevel.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitStressLevel.java
new file mode 100644
index 0000000..a588da4
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitStressLevel.java
@@ -0,0 +1,64 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages;
+
+import androidx.annotation.Nullable;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordData;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordDefinition;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordHeader;
+
+//
+// WARNING: This class was auto-generated, please avoid modifying it directly.
+// See nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.codegen.FitCodeGen
+//
+public class FitStressLevel extends RecordData {
+    public FitStressLevel(final RecordDefinition recordDefinition, final RecordHeader recordHeader) {
+        super(recordDefinition, recordHeader);
+
+        final int globalNumber = recordDefinition.getGlobalFITMessage().getNumber();
+        if (globalNumber != 227) {
+            throw new IllegalArgumentException("FitStressLevel expects global messages of " + 227 + ", got " + globalNumber);
+        }
+    }
+
+    @Nullable
+    public Integer getStressLevelValue() {
+        return (Integer) getFieldByNumber(0);
+    }
+
+    @Nullable
+    public Long getStressLevelTime() {
+        return (Long) getFieldByNumber(1);
+    }
+
+    @Nullable
+    public Integer getBodyEnergy() {
+        return (Integer) getFieldByNumber(3);
+    }
+
+    // manual changes below
+
+    @Override
+    public Long getComputedTimestamp() {
+        final Long stressTime = getStressLevelTime();
+        if (stressTime != null) {
+            return stressTime;
+        }
+        return super.getComputedTimestamp();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitTimeInZone.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitTimeInZone.java
new file mode 100644
index 0000000..0d69961
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitTimeInZone.java
@@ -0,0 +1,77 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages;
+
+import androidx.annotation.Nullable;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordData;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordDefinition;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordHeader;
+
+//
+// WARNING: This class was auto-generated, please avoid modifying it directly.
+// See nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.codegen.FitCodeGen
+//
+public class FitTimeInZone extends RecordData {
+    public FitTimeInZone(final RecordDefinition recordDefinition, final RecordHeader recordHeader) {
+        super(recordDefinition, recordHeader);
+
+        final int globalNumber = recordDefinition.getGlobalFITMessage().getNumber();
+        if (globalNumber != 216) {
+            throw new IllegalArgumentException("FitTimeInZone expects global messages of " + 216 + ", got " + globalNumber);
+        }
+    }
+
+    @Nullable
+    public Integer getReferenceMessage() {
+        return (Integer) getFieldByNumber(0);
+    }
+
+    @Nullable
+    public Integer getReferenceIndex() {
+        return (Integer) getFieldByNumber(1);
+    }
+
+    @Nullable
+    public Double[] getTimeInZone() {
+        final Object[] objectsArray = (Object[]) getFieldByNumber(2);
+        final Double[] ret = new Double[objectsArray.length];
+        for (int i = 0; i < objectsArray.length; i++) {
+            ret[i] = (Double) objectsArray[i];
+        }
+        return ret;
+    }
+
+    @Nullable
+    public Integer[] getHrZoneHighBoundary() {
+        final Object[] objectsArray = (Object[]) getFieldByNumber(6);
+        final Integer[] ret = new Integer[objectsArray.length];
+        for (int i = 0; i < objectsArray.length; i++) {
+            ret[i] = (Integer) objectsArray[i];
+        }
+        return ret;
+    }
+
+    @Nullable
+    public Integer getHrCalcType() {
+        return (Integer) getFieldByNumber(10);
+    }
+
+    @Nullable
+    public Integer getMaxHeartRate() {
+        return (Integer) getFieldByNumber(11);
+    }
+
+    @Nullable
+    public Integer getRestingHeartRate() {
+        return (Integer) getFieldByNumber(12);
+    }
+
+    @Nullable
+    public Integer getThresholdHeartRate() {
+        return (Integer) getFieldByNumber(13);
+    }
+
+    @Nullable
+    public Long getTimestamp() {
+        return (Long) getFieldByNumber(253);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitTimestampCorrelation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitTimestampCorrelation.java
new file mode 100644
index 0000000..fddf342
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitTimestampCorrelation.java
@@ -0,0 +1,32 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages;
+
+import androidx.annotation.Nullable;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordData;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordDefinition;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordHeader;
+
+//
+// WARNING: This class was auto-generated, please avoid modifying it directly.
+// See nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.codegen.FitCodeGen
+//
+public class FitTimestampCorrelation extends RecordData {
+    public FitTimestampCorrelation(final RecordDefinition recordDefinition, final RecordHeader recordHeader) {
+        super(recordDefinition, recordHeader);
+
+        final int globalNumber = recordDefinition.getGlobalFITMessage().getNumber();
+        if (globalNumber != 162) {
+            throw new IllegalArgumentException("FitTimestampCorrelation expects global messages of " + 162 + ", got " + globalNumber);
+        }
+    }
+
+    @Nullable
+    public Long getLocalTimestamp() {
+        return (Long) getFieldByNumber(3);
+    }
+
+    @Nullable
+    public Long getTimestamp() {
+        return (Long) getFieldByNumber(253);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitUserProfile.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitUserProfile.java
new file mode 100644
index 0000000..c0baa93
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitUserProfile.java
@@ -0,0 +1,139 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages;
+
+import androidx.annotation.Nullable;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordData;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordDefinition;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordHeader;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionLanguage.Language;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionMeasurementSystem.Type;
+
+//
+// WARNING: This class was auto-generated, please avoid modifying it directly.
+// See nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.codegen.FitCodeGen
+//
+public class FitUserProfile extends RecordData {
+    public FitUserProfile(final RecordDefinition recordDefinition, final RecordHeader recordHeader) {
+        super(recordDefinition, recordHeader);
+
+        final int globalNumber = recordDefinition.getGlobalFITMessage().getNumber();
+        if (globalNumber != 3) {
+            throw new IllegalArgumentException("FitUserProfile expects global messages of " + 3 + ", got " + globalNumber);
+        }
+    }
+
+    @Nullable
+    public String getFriendlyName() {
+        return (String) getFieldByNumber(0);
+    }
+
+    @Nullable
+    public Integer getGender() {
+        return (Integer) getFieldByNumber(1);
+    }
+
+    @Nullable
+    public Integer getAge() {
+        return (Integer) getFieldByNumber(2);
+    }
+
+    @Nullable
+    public Integer getHeight() {
+        return (Integer) getFieldByNumber(3);
+    }
+
+    @Nullable
+    public Float getWeight() {
+        return (Float) getFieldByNumber(4);
+    }
+
+    @Nullable
+    public Language getLanguage() {
+        return (Language) getFieldByNumber(5);
+    }
+
+    @Nullable
+    public Type getElevSetting() {
+        return (Type) getFieldByNumber(6);
+    }
+
+    @Nullable
+    public Type getWeightSetting() {
+        return (Type) getFieldByNumber(7);
+    }
+
+    @Nullable
+    public Integer getRestingHeartRate() {
+        return (Integer) getFieldByNumber(8);
+    }
+
+    @Nullable
+    public Integer getDefaultMaxBikingHeartRate() {
+        return (Integer) getFieldByNumber(10);
+    }
+
+    @Nullable
+    public Integer getDefaultMaxHeartRate() {
+        return (Integer) getFieldByNumber(11);
+    }
+
+    @Nullable
+    public Integer getHrSetting() {
+        return (Integer) getFieldByNumber(12);
+    }
+
+    @Nullable
+    public Type getSpeedSetting() {
+        return (Type) getFieldByNumber(13);
+    }
+
+    @Nullable
+    public Type getDistSetting() {
+        return (Type) getFieldByNumber(14);
+    }
+
+    @Nullable
+    public Integer getPowerSetting() {
+        return (Integer) getFieldByNumber(16);
+    }
+
+    @Nullable
+    public Integer getActivityClass() {
+        return (Integer) getFieldByNumber(17);
+    }
+
+    @Nullable
+    public Integer getPositionSetting() {
+        return (Integer) getFieldByNumber(18);
+    }
+
+    @Nullable
+    public Type getTemperatureSetting() {
+        return (Type) getFieldByNumber(21);
+    }
+
+    @Nullable
+    public Long getWakeTime() {
+        return (Long) getFieldByNumber(28);
+    }
+
+    @Nullable
+    public Long getSleepTime() {
+        return (Long) getFieldByNumber(29);
+    }
+
+    @Nullable
+    public Type getHeightSetting() {
+        return (Type) getFieldByNumber(30);
+    }
+
+    @Nullable
+    public Integer getUserRunningStepLength() {
+        return (Integer) getFieldByNumber(31);
+    }
+
+    @Nullable
+    public Integer getUserWalkingStepLength() {
+        return (Integer) getFieldByNumber(32);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitWatchfaceSettings.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitWatchfaceSettings.java
new file mode 100644
index 0000000..830ca77
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitWatchfaceSettings.java
@@ -0,0 +1,32 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages;
+
+import androidx.annotation.Nullable;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordData;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordDefinition;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordHeader;
+
+//
+// WARNING: This class was auto-generated, please avoid modifying it directly.
+// See nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.codegen.FitCodeGen
+//
+public class FitWatchfaceSettings extends RecordData {
+    public FitWatchfaceSettings(final RecordDefinition recordDefinition, final RecordHeader recordHeader) {
+        super(recordDefinition, recordHeader);
+
+        final int globalNumber = recordDefinition.getGlobalFITMessage().getNumber();
+        if (globalNumber != 159) {
+            throw new IllegalArgumentException("FitWatchfaceSettings expects global messages of " + 159 + ", got " + globalNumber);
+        }
+    }
+
+    @Nullable
+    public Integer getMode() {
+        return (Integer) getFieldByNumber(0);
+    }
+
+    @Nullable
+    public Integer getLayout() {
+        return (Integer) getFieldByNumber(1);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitWeather.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitWeather.java
new file mode 100644
index 0000000..908c114
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitWeather.java
@@ -0,0 +1,121 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages;
+
+import androidx.annotation.Nullable;
+
+import java.time.DayOfWeek;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordData;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordDefinition;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordHeader;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionWeatherAqi.AQI_LEVELS;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionWeatherCondition.Condition;
+
+//
+// WARNING: This class was auto-generated, please avoid modifying it directly.
+// See nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.codegen.FitCodeGen
+//
+public class FitWeather extends RecordData {
+    public FitWeather(final RecordDefinition recordDefinition, final RecordHeader recordHeader) {
+        super(recordDefinition, recordHeader);
+
+        final int globalNumber = recordDefinition.getGlobalFITMessage().getNumber();
+        if (globalNumber != 128) {
+            throw new IllegalArgumentException("FitWeather expects global messages of " + 128 + ", got " + globalNumber);
+        }
+    }
+
+    @Nullable
+    public Integer getWeatherReport() {
+        return (Integer) getFieldByNumber(0);
+    }
+
+    @Nullable
+    public Integer getTemperature() {
+        return (Integer) getFieldByNumber(1);
+    }
+
+    @Nullable
+    public Condition getCondition() {
+        return (Condition) getFieldByNumber(2);
+    }
+
+    @Nullable
+    public Integer getWindDirection() {
+        return (Integer) getFieldByNumber(3);
+    }
+
+    @Nullable
+    public Float getWindSpeed() {
+        return (Float) getFieldByNumber(4);
+    }
+
+    @Nullable
+    public Integer getPrecipitationProbability() {
+        return (Integer) getFieldByNumber(5);
+    }
+
+    @Nullable
+    public Integer getTemperatureFeelsLike() {
+        return (Integer) getFieldByNumber(6);
+    }
+
+    @Nullable
+    public Integer getRelativeHumidity() {
+        return (Integer) getFieldByNumber(7);
+    }
+
+    @Nullable
+    public String getLocation() {
+        return (String) getFieldByNumber(8);
+    }
+
+    @Nullable
+    public Long getObservedAtTime() {
+        return (Long) getFieldByNumber(9);
+    }
+
+    @Nullable
+    public Long getObservedLocationLat() {
+        return (Long) getFieldByNumber(10);
+    }
+
+    @Nullable
+    public Long getObservedLocationLong() {
+        return (Long) getFieldByNumber(11);
+    }
+
+    @Nullable
+    public DayOfWeek getDayOfWeek() {
+        return (DayOfWeek) getFieldByNumber(12);
+    }
+
+    @Nullable
+    public Integer getHighTemperature() {
+        return (Integer) getFieldByNumber(13);
+    }
+
+    @Nullable
+    public Integer getLowTemperature() {
+        return (Integer) getFieldByNumber(14);
+    }
+
+    @Nullable
+    public Integer getDewPoint() {
+        return (Integer) getFieldByNumber(15);
+    }
+
+    @Nullable
+    public Float getUvIndex() {
+        return (Float) getFieldByNumber(16);
+    }
+
+    @Nullable
+    public AQI_LEVELS getAirQuality() {
+        return (AQI_LEVELS) getFieldByNumber(17);
+    }
+
+    @Nullable
+    public Long getTimestamp() {
+        return (Long) getFieldByNumber(253);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitWorkout.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitWorkout.java
new file mode 100644
index 0000000..a9fac3b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitWorkout.java
@@ -0,0 +1,52 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages;
+
+import androidx.annotation.Nullable;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordData;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordDefinition;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordHeader;
+
+//
+// WARNING: This class was auto-generated, please avoid modifying it directly.
+// See nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.codegen.FitCodeGen
+//
+public class FitWorkout extends RecordData {
+    public FitWorkout(final RecordDefinition recordDefinition, final RecordHeader recordHeader) {
+        super(recordDefinition, recordHeader);
+
+        final int globalNumber = recordDefinition.getGlobalFITMessage().getNumber();
+        if (globalNumber != 26) {
+            throw new IllegalArgumentException("FitWorkout expects global messages of " + 26 + ", got " + globalNumber);
+        }
+    }
+
+    @Nullable
+    public Integer getSport() {
+        return (Integer) getFieldByNumber(4);
+    }
+
+    @Nullable
+    public Long getCapabilities() {
+        return (Long) getFieldByNumber(5);
+    }
+
+    @Nullable
+    public Integer getNumValidSteps() {
+        return (Integer) getFieldByNumber(6);
+    }
+
+    @Nullable
+    public String getName() {
+        return (String) getFieldByNumber(8);
+    }
+
+    @Nullable
+    public Integer getSubSport() {
+        return (Integer) getFieldByNumber(11);
+    }
+
+    @Nullable
+    public String getNotes() {
+        return (String) getFieldByNumber(17);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitZonesTarget.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitZonesTarget.java
new file mode 100644
index 0000000..c7b1a12
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitZonesTarget.java
@@ -0,0 +1,47 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages;
+
+import androidx.annotation.Nullable;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordData;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordDefinition;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordHeader;
+
+//
+// WARNING: This class was auto-generated, please avoid modifying it directly.
+// See nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.codegen.FitCodeGen
+//
+public class FitZonesTarget extends RecordData {
+    public FitZonesTarget(final RecordDefinition recordDefinition, final RecordHeader recordHeader) {
+        super(recordDefinition, recordHeader);
+
+        final int globalNumber = recordDefinition.getGlobalFITMessage().getNumber();
+        if (globalNumber != 7) {
+            throw new IllegalArgumentException("FitZonesTarget expects global messages of " + 7 + ", got " + globalNumber);
+        }
+    }
+
+    @Nullable
+    public Integer getFunctionalThresholdPower() {
+        return (Integer) getFieldByNumber(3);
+    }
+
+    @Nullable
+    public Integer getMaxHeartRate() {
+        return (Integer) getFieldByNumber(1);
+    }
+
+    @Nullable
+    public Integer getThresholdHeartRate() {
+        return (Integer) getFieldByNumber(2);
+    }
+
+    @Nullable
+    public Integer getHrCalcType() {
+        return (Integer) getFieldByNumber(5);
+    }
+
+    @Nullable
+    public Integer getPwrCalcType() {
+        return (Integer) getFieldByNumber(7);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/http/ContactsHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/http/ContactsHandler.java
new file mode 100644
index 0000000..4255718
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/http/ContactsHandler.java
@@ -0,0 +1,111 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.http;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+import java.util.Locale;
+import java.util.Random;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminPreferences;
+import nodomain.freeyourgadget.gadgetbridge.entities.Contact;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
+import nodomain.freeyourgadget.gadgetbridge.proto.garmin.GarminContacts;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.GarminSupport;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+public class ContactsHandler {
+    private static final Logger LOG = LoggerFactory.getLogger(ContactsHandler.class);
+
+    private final GarminSupport deviceSupport;
+
+    public ContactsHandler(final GarminSupport deviceSupport) {
+        this.deviceSupport = deviceSupport;
+    }
+
+    public GarminHttpResponse handleRequest(final GarminHttpRequest request) {
+        if (!request.getPath().equals("/device-gateway/usercontact/contacts")) {
+            LOG.warn("Unknown contacts path {}", request.getPath());
+            return null;
+        }
+
+        if (!"application/octet-stream".equals(request.getHeaders().get("accept"))) {
+            // We only support protobuf replies
+            LOG.warn("Requested contacts content type '{}' not supported", request.getHeaders().get("accept"));
+            return null;
+        }
+
+        // Mark contacts as supported
+        final Prefs devicePrefs = deviceSupport.getDevicePrefs();
+        devicePrefs.getPreferences().edit()
+                .putBoolean(GarminPreferences.PREF_FEAT_CONTACTS, true)
+                .apply();
+
+        final GarminContacts.Response.Builder responseContacts = GarminContacts.Response.newBuilder();
+
+        final List<Contact> contacts = DBHelper.getContacts(deviceSupport.getDevice());
+        for (final Contact contact : contacts) {
+            final GarminContacts.Contact.Builder responseContact = GarminContacts.Contact.newBuilder()
+                    .setId(randomHex(32).toUpperCase(Locale.ROOT))
+                    .setFullName(contact.getName())
+                    .setFirstName(contact.getName())
+                    .setLastName("")
+                    .addPhone(
+                            GarminContacts.Phone.newBuilder()
+                                    .setNumber(contact.getNumber())
+                                    .setUnk2(1)
+                                    .setUnk3(1)
+                                    .setUnk4(0)
+                                    .setUnk5(new Random().nextInt(65535) + 65535)
+                                    .setUnk6(0)
+                                    .setUnk7(0)
+                                    .setUnk8("")
+                                    .setUnk9(1)
+                                    .setId(UUID.randomUUID().toString())
+                                    .setUnk11(1)
+                                    .addUnk12(GarminContacts.Unk12.newBuilder().setUnk1(1).setUnk2(5))
+                                    .addUnk12(GarminContacts.Unk12.newBuilder().setUnk1(2).setUnk2(5))
+                    )
+                    .setUnk8(0)
+                    .setUnk9(0)
+                    .setUpdateTime(System.currentTimeMillis())
+                    .setUnk12(0)
+                    .setUnk21(0);
+
+            responseContacts.addContact(responseContact);
+        }
+
+        final ActivityUser activityUser = new ActivityUser();
+        final GarminContacts.Contact.Builder self = GarminContacts.Contact.newBuilder()
+                .setId("SELF")
+                .setFullName(activityUser.getName())
+                .setFirstName(activityUser.getName())
+                .setLastName("")
+                .setUnk7("")
+                .setUnk8(0)
+                .setUnk9(0)
+                .setUnk10(0)
+                .setUnk12(0)
+                .setUnk21(0)
+                .setUpdateTime(System.currentTimeMillis());
+
+        responseContacts.setSelf(self);
+
+        final GarminHttpResponse response = new GarminHttpResponse();
+        response.setStatus(200);
+        response.getHeaders().put("Content-Type", "application/octet-stream");
+        response.setBody(responseContacts.build().toByteArray());
+        return response;
+    }
+
+    public static String randomHex(final int numChars) {
+        final Random r = new Random();
+        final StringBuilder sb = new StringBuilder();
+        while (sb.length() < numChars) {
+            sb.append(Integer.toHexString(r.nextInt()));
+        }
+        return sb.toString().substring(0, numChars);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/http/DataTransferHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/http/DataTransferHandler.java
new file mode 100644
index 0000000..2472aa8
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/http/DataTransferHandler.java
@@ -0,0 +1,178 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.http;
+
+import com.google.protobuf.ByteString;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Random;
+import java.util.TreeMap;
+import java.util.concurrent.Callable;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import nodomain.freeyourgadget.gadgetbridge.proto.garmin.GdiDataTransferService;
+
+public class DataTransferHandler {
+    private static final Logger LOG = LoggerFactory.getLogger(DataTransferHandler.class);
+    private static final AtomicInteger idCounter = new AtomicInteger((new Random()).nextInt(Integer.MAX_VALUE / 2));
+    private static final Map<Integer, Data> dataById = new HashMap<>();
+    private static final Map<Integer, ChunkInfo> unprocessedChunksByRequestId = new HashMap<>();
+
+    public GdiDataTransferService.DataTransferService handle(
+            final GdiDataTransferService.DataTransferService dataTransferService,
+            final int requestId
+    ) {
+        if (dataTransferService.hasDataDownloadRequest()) {
+            final GdiDataTransferService.DataTransferService.DataDownloadResponse dataDownloadResponse
+                    = handleDataDownloadRequest(dataTransferService.getDataDownloadRequest(), requestId);
+            if (dataDownloadResponse != null) {
+                return GdiDataTransferService.DataTransferService.newBuilder()
+                        .setDataDownloadResponse(dataDownloadResponse)
+                        .build();
+            }
+            return null;
+        }
+
+        LOG.warn("Unsupported data transfer service request: {}", dataTransferService);
+
+        return null;
+    }
+
+    public GdiDataTransferService.DataTransferService.DataDownloadResponse handleDataDownloadRequest(
+            final GdiDataTransferService.DataTransferService.DataDownloadRequest dataDownloadRequest,
+            final int requestId
+    ) {
+        final int dataId = dataDownloadRequest.getId();
+        final int offset = dataDownloadRequest.getOffset();
+        LOG.debug("Received data download request (id: {}, offset: {})", dataId, offset);
+        final Data data = dataById.get(dataId);
+        if (data == null) {
+            LOG.error("Device requested data with invalid id: {}", dataId);
+            return GdiDataTransferService.DataTransferService.DataDownloadResponse.newBuilder()
+                    .setStatus(GdiDataTransferService.DataTransferService.Status.INVALID_ID)
+                    .setId(dataId)
+                    .setOffset(offset)
+                    .build();
+        }
+        final int maxChunkSize = dataDownloadRequest.hasMaxChunkSize() ? dataDownloadRequest.getMaxChunkSize() : Integer.MAX_VALUE;
+        final byte[] chunk = data.getDataChunk(offset, maxChunkSize);
+        if (chunk == null) {
+            LOG.error("Device requested data with invalid offset: {}", offset);
+            return GdiDataTransferService.DataTransferService.DataDownloadResponse.newBuilder()
+                    .setStatus(GdiDataTransferService.DataTransferService.Status.INVALID_OFFSET)
+                    .setId(dataId)
+                    .setOffset(offset)
+                    .build();
+        }
+        unprocessedChunksByRequestId.put(requestId, new ChunkInfo(dataId, offset, offset + chunk.length));
+        return GdiDataTransferService.DataTransferService.DataDownloadResponse.newBuilder()
+                .setStatus(GdiDataTransferService.DataTransferService.Status.SUCCESS)
+                .setId(dataId)
+                .setOffset(offset)
+                .setPayload(ByteString.copyFrom(chunk))
+                .build();
+    }
+
+    public static int registerData(final byte[] data) {
+        int id = idCounter.getAndIncrement();
+        LOG.info("New data will be sent to the device (id: {}, size: {})", id, data.length);
+        dataById.put(id, new Data(data));
+        return id;
+    }
+
+    public static void onDataChunkSuccessfullyReceived(final int requestId) {
+        final ChunkInfo chunkInfo = unprocessedChunksByRequestId.get(requestId);
+        if (chunkInfo == null) {
+            return;
+        }
+        unprocessedChunksByRequestId.remove(requestId);
+        final Data data = dataById.get(chunkInfo.dataId);
+        if (data == null) {
+            return;
+        }
+        data.onDataChunkSuccessfullyReceived(chunkInfo);
+        if (data.isDataSuccessfullySent()) {
+            LOG.info("Data successfully sent to the device (id: {}, size: {})", chunkInfo.dataId, data.data.length);
+            for (Callable<Void> listener : data.onDataSuccessfullySentListeners) {
+                try {
+                    listener.call();
+                } catch (Exception e) {
+                    LOG.error("Data listener failed.", e);
+                }
+            }
+            dataById.remove(chunkInfo.dataId);
+        } else {
+            LOG.debug(
+                    "Data chunk successfully sent to the device (dataId: {}, requestId: {}): {}-{}/{}",
+                    chunkInfo.dataId, requestId, chunkInfo.start, chunkInfo.end, data.data.length
+            );
+        }
+    }
+
+    public static void addOnDataSuccessfullySentListener(final int dataId, final Callable<Void> listener) {
+        Objects.requireNonNull(dataById.get(dataId)).onDataSuccessfullySentListeners.add(listener);
+    }
+
+    private static class ChunkInfo {
+        private final int dataId;
+        private final int start;
+        private final int end;
+
+        private ChunkInfo(int dataId, int start, int end) {
+            this.dataId = dataId;
+            this.start = start;
+            this.end = end;
+        }
+    }
+
+    private static class Data {
+        // TODO Wouldn't it be better to store data as streams?
+        // Because now we have to store the whole data in RAM.
+        private final byte[] data;
+        private final TreeMap<Integer, ChunkInfo> chunksReceivedByDevice;
+        private final List<Callable<Void>> onDataSuccessfullySentListeners;
+
+        private Data(byte[] data) {
+            this.data = data;
+            chunksReceivedByDevice = new TreeMap<>();
+            onDataSuccessfullySentListeners = new ArrayList<>();
+        }
+
+        private byte[] getDataChunk(final int offset, final int maxChunkSize) {
+            if (offset < 0 || offset >= data.length) {
+                return null;
+            }
+            return Arrays.copyOfRange(data, offset, Math.min(offset + maxChunkSize, data.length));
+        }
+
+        private void onDataChunkSuccessfullyReceived(ChunkInfo newlyReceivedChunk) {
+            final ChunkInfo alreadyReceivedChunk = chunksReceivedByDevice.get(newlyReceivedChunk.start);
+            if (alreadyReceivedChunk == null || alreadyReceivedChunk.end < newlyReceivedChunk.end) {
+                chunksReceivedByDevice.put(newlyReceivedChunk.start, newlyReceivedChunk);
+            }
+        }
+
+        private boolean isDataSuccessfullySent() {
+            Integer previousChunkEnd = null;
+            for (Map.Entry<Integer, ChunkInfo> chunkEntry : chunksReceivedByDevice.entrySet()) {
+                if (previousChunkEnd == null && chunkEntry.getKey() != 0) {
+                    // The head of the data wasn't received by the device.
+                    return false;
+                }
+                if (previousChunkEnd != null && chunkEntry.getKey() > previousChunkEnd) {
+                    // There is some gap between received chunks.
+                    return false;
+                }
+                previousChunkEnd = chunkEntry.getValue().end;
+            }
+            // Check if the end of the last chunk matches the data size.
+            return previousChunkEnd != null && data.length == previousChunkEnd;
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/http/GarminHttpRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/http/GarminHttpRequest.java
new file mode 100644
index 0000000..cdab877
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/http/GarminHttpRequest.java
@@ -0,0 +1,64 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.http;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+import nodomain.freeyourgadget.gadgetbridge.proto.garmin.GdiHttpService;
+import nodomain.freeyourgadget.gadgetbridge.util.HttpUtils;
+
+public class GarminHttpRequest {
+    private final GdiHttpService.HttpService.RawRequest rawRequest;
+
+    private final String url;
+    private final String path;
+    private final Map<String, String> query;
+    private final Map<String, String> headers;
+
+    public GarminHttpRequest(final GdiHttpService.HttpService.RawRequest rawRequest) {
+        this.rawRequest = rawRequest;
+
+        final URL netUrl;
+        try {
+            netUrl = new URL(rawRequest.getUrl());
+        } catch (final MalformedURLException e) {
+            throw new IllegalArgumentException("Failed to parse url", e);
+        }
+
+        this.url = rawRequest.getUrl();
+        this.path = netUrl.getPath();
+        this.query = HttpUtils.urlQueryParameters(netUrl);
+        this.headers = headersToMap(rawRequest.getHeaderList());
+    }
+
+    public GdiHttpService.HttpService.RawRequest getRawRequest() {
+        return rawRequest;
+    }
+
+    public String getUrl() {
+        return url;
+    }
+
+    public String getPath() {
+        return path;
+    }
+
+    public Map<String, String> getQuery() {
+        return query;
+    }
+
+    public Map<String, String> getHeaders() {
+        return headers;
+    }
+
+    private static Map<String, String> headersToMap(final List<GdiHttpService.HttpService.Header> headers) {
+        final Map<String, String> ret = new HashMap<>();
+        for (final GdiHttpService.HttpService.Header header : headers) {
+            ret.put(header.getKey().toLowerCase(Locale.ROOT), header.getValue());
+        }
+        return ret;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/http/GarminHttpResponse.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/http/GarminHttpResponse.java
new file mode 100644
index 0000000..9cbadaa
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/http/GarminHttpResponse.java
@@ -0,0 +1,41 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.http;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.concurrent.Callable;
+
+public class GarminHttpResponse {
+    private int status = 200;
+    private final Map<String, String> headers = new LinkedHashMap<>();
+    private byte[] body = new byte[0];
+
+    private Callable<Void> onDataSuccessfullySentListener;
+
+    public int getStatus() {
+        return status;
+    }
+
+    public void setStatus(final int status) {
+        this.status = status;
+    }
+
+    public Map<String, String> getHeaders() {
+        return headers;
+    }
+
+    public byte[] getBody() {
+        return body;
+    }
+
+    public void setBody(final byte[] body) {
+        this.body = body;
+    }
+
+    public Callable<Void> getOnDataSuccessfullySentListener() {
+        return onDataSuccessfullySentListener;
+    }
+
+    public void setOnDataSuccessfullySentListener(final Callable<Void> onDataSuccessfullySentListener) {
+        this.onDataSuccessfullySentListener = onDataSuccessfullySentListener;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/http/HttpHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/http/HttpHandler.java
new file mode 100644
index 0000000..04a8081
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/http/HttpHandler.java
@@ -0,0 +1,140 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.http;
+
+import com.google.protobuf.ByteString;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.ByteArrayOutputStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.zip.GZIPOutputStream;
+
+import nodomain.freeyourgadget.gadgetbridge.proto.garmin.GdiHttpService;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.GarminSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.agps.AgpsHandler;
+
+public class HttpHandler {
+    private static final Logger LOG = LoggerFactory.getLogger(HttpHandler.class);
+
+    private final AgpsHandler agpsHandler;
+    private final ContactsHandler contactsHandler;
+
+    public HttpHandler(GarminSupport deviceSupport) {
+        agpsHandler = new AgpsHandler(deviceSupport);
+        contactsHandler = new ContactsHandler(deviceSupport);
+    }
+
+    public GdiHttpService.HttpService handle(final GdiHttpService.HttpService httpService) {
+        if (httpService.hasRawRequest()) {
+            final GdiHttpService.HttpService.RawResponse rawResponse = handleRawRequest(httpService.getRawRequest());
+            if (rawResponse != null) {
+                return GdiHttpService.HttpService.newBuilder()
+                        .setRawResponse(rawResponse)
+                        .build();
+            }
+            return null;
+        }
+
+        LOG.warn("Unsupported http service request {}", httpService);
+
+        return null;
+    }
+
+    public GdiHttpService.HttpService.RawResponse handleRawRequest(final GdiHttpService.HttpService.RawRequest rawRequest) {
+        LOG.debug("Got rawRequest: {} - {}", rawRequest.getMethod(), rawRequest.getUrl());
+
+        final GarminHttpRequest request = new GarminHttpRequest(rawRequest);
+
+        final GarminHttpResponse response;
+        if (request.getPath().startsWith("/weather/")) {
+            LOG.info("Got weather request for {}", request.getPath());
+            response = WeatherHandler.handleWeatherRequest(request);
+        } else if (request.getPath().startsWith("/ephemeris/")) {
+            LOG.info("Got AGPS request for {}", request.getPath());
+            response = agpsHandler.handleAgpsRequest(request);
+        } else if (request.getPath().startsWith("/device-gateway/usercontact/")) {
+            LOG.info("Got contacts request for {}", request.getPath());
+            response = contactsHandler.handleRequest(request);
+        } else {
+            LOG.warn("Unhandled path {}", request.getPath());
+            response = null;
+        }
+
+        if (response == null) {
+            return GdiHttpService.HttpService.RawResponse.newBuilder()
+                    .setStatus(GdiHttpService.HttpService.Status.UNKNOWN_STATUS)
+                    .build();
+        }
+
+        LOG.debug("Http response status={}", response.getStatus());
+
+        return createRawResponse(request, response);
+    }
+
+    private static GdiHttpService.HttpService.RawResponse createRawResponse(
+            final GarminHttpRequest request,
+            final GarminHttpResponse response
+    ) {
+        final List<GdiHttpService.HttpService.Header> responseHeaders = new ArrayList<>();
+        for (final Map.Entry<String, String> h : response.getHeaders().entrySet()) {
+            responseHeaders.add(
+                    GdiHttpService.HttpService.Header.newBuilder()
+                            .setKey(h.getKey())
+                            .setValue(h.getValue())
+                            .build()
+            );
+        }
+
+        if (response.getStatus() == 200 && request.getRawRequest().hasUseDataXfer() && request.getRawRequest().getUseDataXfer()) {
+            LOG.debug("Data will be returned using data_xfer");
+            int id = DataTransferHandler.registerData(response.getBody());
+            if (response.getOnDataSuccessfullySentListener() != null) {
+                DataTransferHandler.addOnDataSuccessfullySentListener(id, response.getOnDataSuccessfullySentListener());
+            }
+            return GdiHttpService.HttpService.RawResponse.newBuilder()
+                    .setStatus(GdiHttpService.HttpService.Status.OK)
+                    .setHttpStatus(response.getStatus())
+                    .addAllHeader(responseHeaders)
+                    .setXferData(
+                            GdiHttpService.HttpService.DataTransferItem.newBuilder()
+                                    .setId(id)
+                                    .setSize(response.getBody().length)
+                                    .build()
+                    )
+                    .build();
+        }
+
+        final byte[] responseBody;
+        if ("gzip".equals(request.getHeaders().get("accept-encoding"))) {
+            LOG.debug("Compressing response");
+            responseHeaders.add(
+                    GdiHttpService.HttpService.Header.newBuilder()
+                            .setKey("Content-Encoding")
+                            .setValue("gzip")
+                            .build()
+            );
+
+            final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            try (GZIPOutputStream gzos = new GZIPOutputStream(baos)) {
+                gzos.write(response.getBody());
+                gzos.finish();
+                gzos.flush();
+                responseBody = baos.toByteArray();
+            } catch (final Exception e) {
+                LOG.error("Failed to compress response", e);
+                return null;
+            }
+        } else {
+            responseBody = response.getBody();
+        }
+
+        return GdiHttpService.HttpService.RawResponse.newBuilder()
+                .setStatus(response.getStatus() / 100 == 2 ? GdiHttpService.HttpService.Status.OK : GdiHttpService.HttpService.Status.UNKNOWN_STATUS)
+                .setHttpStatus(response.getStatus())
+                .setBody(ByteString.copyFrom(responseBody))
+                .addAllHeader(responseHeaders)
+                .build();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/http/WeatherHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/http/WeatherHandler.java
new file mode 100644
index 0000000..91441a4
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/http/WeatherHandler.java
@@ -0,0 +1,439 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.http;
+
+import android.location.Location;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+
+import net.e175.klaus.solarpositioning.DeltaT;
+import net.e175.klaus.solarpositioning.SPA;
+import net.e175.klaus.solarpositioning.SunriseTransitSet;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.List;
+import java.util.Map;
+
+import lineageos.weather.util.WeatherUtils;
+import nodomain.freeyourgadget.gadgetbridge.model.Weather;
+import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.webview.CurrentPosition;
+
+public class WeatherHandler {
+    private static final Logger LOG = LoggerFactory.getLogger(WeatherHandler.class);
+
+    private static final Gson GSON = new GsonBuilder()
+            //.serializeNulls()
+            .create();
+
+    // These get requested on connection at most every 5 minutes
+    public static GarminHttpResponse handleWeatherRequest(final GarminHttpRequest request) {
+        final String path = request.getPath();
+        final Map<String, String> query = request.getQuery();
+
+        final WeatherSpec weatherSpec = Weather.getInstance().getWeatherSpec();
+
+        if (weatherSpec == null) {
+            LOG.warn("No weather in weather instance");
+            return null;
+        }
+
+        final Object weatherData;
+        switch (path) {
+            case "/weather/v2/forecast/day": {
+                final int lat = getQueryNum(query, "lat", 0);
+                final int lon = getQueryNum(query, "lon", 0);
+                final int duration = getQueryNum(query, "duration", 5);
+                final String tempUnit = getQueryString(query, "tempUnit", "CELSIUS");
+                final String provider = getQueryString(query, "provider", "dci");
+                final String speedUnit = getQueryString(query, "speedUnit", "KILOMETERS_PER_HOUR");
+                final List<WeatherForecastDay> ret = new ArrayList<>(duration);
+                final GregorianCalendar date = new GregorianCalendar();
+                date.setTime(new Date(weatherSpec.timestamp * 1000L));
+                ret.add(new WeatherForecastDay(date, weatherSpec.todayAsDaily(), tempUnit, speedUnit));
+                for (int i = 0; i < Math.min(duration, weatherSpec.forecasts.size()) - 1; i++) {
+                    date.add(Calendar.DAY_OF_MONTH, 1);
+                    ret.add(new WeatherForecastDay(date, weatherSpec.forecasts.get(i), tempUnit, speedUnit));
+                }
+                weatherData = ret;
+                break;
+            }
+            case "/weather/v2/forecast/hour": {
+                final int lat = getQueryNum(query, "lat", 0);
+                final int lon = getQueryNum(query, "lon", 0);
+                final int duration = getQueryNum(query, "duration", 13);
+                final String speedUnit = getQueryString(query, "speedUnit", "METERS_PER_SECOND");
+                final String tempUnit = getQueryString(query, "tempUnit", "CELSIUS");
+                final String provider = getQueryString(query, "provider", "dci");
+                final String timesOfInterest = getQueryString(query, "timesOfInterest", "");
+                final List<WeatherForecastHour> ret = new ArrayList<>(duration);
+                for (int i = 0; i < Math.min(duration, weatherSpec.hourly.size()); i++) {
+                    ret.add(new WeatherForecastHour(weatherSpec.hourly.get(i), tempUnit, speedUnit));
+                }
+                weatherData = ret;
+                break;
+            }
+            case "/weather/v2/current": {
+                final int lat = getQueryNum(query, "lat", 0);
+                final int lon = getQueryNum(query, "lon", 0);
+                final String tempUnit = getQueryString(query, "tempUnit", "CELSIUS");
+                final String speedUnit = getQueryString(query, "speedUnit", "METERS_PER_SECOND");
+                final String provider = getQueryString(query, "provider", "dci");
+                weatherData = new WeatherForecastCurrent(weatherSpec, tempUnit, speedUnit);
+                break;
+            }
+            default:
+                LOG.warn("Unknown weather path {}", path);
+                return null;
+        }
+
+        final String json = GSON.toJson(weatherData);
+        LOG.debug("Weather response: {}", json);
+
+        final GarminHttpResponse response = new GarminHttpResponse();
+        response.setStatus(200);
+        response.setBody(json.getBytes(StandardCharsets.UTF_8));
+        response.getHeaders().put("Content-Type", "application/json");
+        return response;
+    }
+
+    private static int getQueryNum(final Map<String, String> query, final String key, final int defaultValue) {
+        final String str = query.get(key);
+        if (str != null) {
+            return Integer.parseInt(str);
+        } else {
+            return defaultValue;
+        }
+    }
+
+    private static String getQueryString(final Map<String, String> query, final String key, final String defaultValue) {
+        final String str = query.get(key);
+        if (str != null) {
+            return str;
+        } else {
+            return defaultValue;
+        }
+    }
+
+    public static class WeatherForecastDay {
+        public int dayOfWeek; // 1 monday .. 7 sunday
+        public String description;
+        public String summary;
+        public WeatherValue high;
+        public WeatherValue low;
+        public Integer precipProb;
+        public Integer icon;
+        public Integer epochSunrise;
+        public Integer epochSunset;
+        public Wind wind;
+        public Integer humidity;
+
+        public WeatherForecastDay(final GregorianCalendar date, final WeatherSpec.Daily dailyForecast, final String tempUnit, final String speedUnit) {
+            dayOfWeek = BLETypeConversions.dayOfWeekToRawBytes(date);
+            description = "Unknown"; // TODO from conditionCode
+            summary = "Unknown"; // TODO from conditionCode
+            high = getTemperature(dailyForecast.maxTemp, tempUnit);
+            low = getTemperature(dailyForecast.minTemp, tempUnit);
+            precipProb = dailyForecast.precipProbability;
+            icon = mapToGarminCondition(dailyForecast.conditionCode);
+
+            if (dailyForecast.sunRise != 0 && dailyForecast.sunSet != 0) {
+                epochSunrise = dailyForecast.sunRise;
+                epochSunset = dailyForecast.sunSet;
+            } else {
+                final Location lastKnownLocation = new CurrentPosition().getLastKnownLocation();
+
+                final SunriseTransitSet sunriseTransitSet = SPA.calculateSunriseTransitSet(
+                        date.toZonedDateTime(),
+                        lastKnownLocation.getLatitude(),
+                        lastKnownLocation.getLongitude(),
+                        DeltaT.estimate(date.toZonedDateTime().toLocalDate())
+                );
+
+                if (sunriseTransitSet.getSunrise() != null) {
+                    epochSunrise = (int) (sunriseTransitSet.getSunrise().toInstant().getEpochSecond());
+                }
+                if (sunriseTransitSet.getSunset() != null) {
+                    epochSunset = (int) (sunriseTransitSet.getSunset().toInstant().getEpochSecond());
+                }
+            }
+
+            wind = new Wind(getSpeed(dailyForecast.windSpeed, speedUnit), dailyForecast.windDirection);
+            humidity = dailyForecast.humidity;
+        }
+    }
+
+    public static class WeatherForecastHour {
+        public int epochSeconds;
+        public String description;
+        public WeatherValue temp;
+        public Integer precipProb;
+        public Wind wind;
+        public Integer icon;
+        public WeatherValue dewPoint;
+        public Float uvIndex;
+        public Integer relativeHumidity;
+        public WeatherValue feelsLikeTemperature;
+        public WeatherValue visibility;
+        public WeatherValue pressure;
+        public Object airQuality;
+        public Integer cloudCover;
+
+        public WeatherForecastHour(final WeatherSpec.Hourly hourlyForecast, final String tempUnit, final String speedUnit) {
+            epochSeconds = hourlyForecast.timestamp;
+            description = "Unknown"; // TODO from conditionCode
+            temp = getTemperature(hourlyForecast.temp, tempUnit);
+            precipProb = hourlyForecast.precipProbability;
+            wind = new Wind(getSpeed(hourlyForecast.windSpeed, speedUnit), hourlyForecast.windDirection);
+            icon = mapToGarminCondition(hourlyForecast.conditionCode);
+            //dewPoint = new WeatherValue(hourlyForecast.temp - 273f, "CELSIUS"); // TODO dewPoint
+            uvIndex = hourlyForecast.uvIndex;
+            relativeHumidity = hourlyForecast.humidity;
+            //feelsLikeTemperature = new WeatherValue(hourlyForecast.temp - 273f, "CELSIUS"); // TODO feelsLikeTemperature
+            //visibility = new WeatherValue(0, "METER"); // TODO visibility
+            //pressure = new WeatherValue(0f, "INCHES_OF_MERCURY"); // TODO pressure
+            //airQuality = null; // TODO airQuality
+            //cloudCover = 0; // TODO cloudCover
+        }
+    }
+
+    public static class WeatherForecastCurrent {
+        public Integer epochSeconds;
+        public WeatherValue temperature;
+        public String description;
+        public Integer icon;
+        public WeatherValue feelsLikeTemperature;
+        public WeatherValue dewPoint;
+        public Integer relativeHumidity;
+        public Wind wind;
+        public String locationName;
+        public WeatherValue visibility;
+        public WeatherValue pressure;
+        public WeatherValue pressureChange;
+
+        public WeatherForecastCurrent(final WeatherSpec weatherSpec, final String tempUnit, final String speedUnit) {
+            epochSeconds = weatherSpec.timestamp;
+            temperature = getTemperature(weatherSpec.currentTemp, tempUnit);
+            description = weatherSpec.currentCondition;
+            icon = mapToGarminCondition(weatherSpec.currentConditionCode);
+            feelsLikeTemperature = getTemperature(weatherSpec.currentTemp, tempUnit);
+            dewPoint = getTemperature(weatherSpec.dewPoint, tempUnit);
+            relativeHumidity = weatherSpec.currentHumidity;
+            wind = new Wind(getSpeed(weatherSpec.windSpeed, speedUnit), weatherSpec.windDirection);
+            locationName = weatherSpec.location;
+            visibility = new WeatherValue(weatherSpec.visibility, "METER");
+            pressure = new WeatherValue(weatherSpec.pressure * 0.02953, "INCHES_OF_MERCURY");
+            pressureChange = new WeatherValue(0f, "INCHES_OF_MERCURY");
+        }
+    }
+
+    public static class WeatherValue {
+        public Number value;
+        public String units;
+
+        public WeatherValue(final Number value, final String units) {
+            this.value = value;
+            this.units = units;
+        }
+    }
+
+    public static class Wind {
+        public WeatherValue speed;
+        public String directionString;
+        public Integer direction;
+
+        public Wind(final WeatherValue speed, final int direction) {
+            this.speed = speed;
+            this.directionString = getWindDirection(direction);
+            this.direction = direction;
+        }
+    }
+
+    public static String getWindDirection(int degrees) {
+        degrees = (degrees % 360 + 360) % 360;
+
+        final String[] directions = {"N", "NE", "E", "SE", "S", "SW", "W", "NW"};
+        final int index = (int) Math.round(((double) degrees % 360) / 45);
+
+        return directions[index % 8];
+    }
+
+    private static WeatherValue getTemperature(final int kelvin, final String unit) {
+        switch (unit) {
+            case "FAHRENHEIT":
+                return new WeatherValue(WeatherUtils.celsiusToFahrenheit(kelvin - 273.15), "FAHRENHEIT");
+            case "KELVIN":
+                return new WeatherValue(kelvin, "KELVIN");
+            case "CELSIUS":
+            default:
+                // #4313 - We do a "wrong" conversion to celsius on purpose
+                return new WeatherValue(kelvin - 273, "CELSIUS");
+        }
+    }
+
+    private static WeatherValue getSpeed(final float kmph, final String unit) {
+        switch (unit) {
+            case "METERS_PER_SECOND":
+                return new WeatherValue(kmph / 3.6, "METERS_PER_SECOND");
+            case "KILOMETERS_PER_HOUR":
+            default:
+                return new WeatherValue(kmph, "KILOMETERS_PER_HOUR");
+        }
+    }
+
+    public static int mapToGarminCondition(final int openWeatherMapCondition) {
+        // Icons mapped from a Venu 3:
+        // 0 1 2 unk
+        // 3 4 5 6 sunny
+        // 7 8 9 10 sun cloudy
+        // 11 12 cloudy with dashes below
+        // 13 14 sun cloud 2 clouds
+        // 15 16 clouds
+        // 17 rain
+        // 18 19 20 21 rain with sun (or night at night?)
+        // 22 rain
+        // 23 24 unk
+        // 25 26 thunder with rain and sun behind
+        // 27 thunder with rain
+        // 28 29 rain
+        // 30 31 32 33 34 snow with clouds
+        // 35 36 37 snowflake
+        // 38 snow with clouds, with big flake
+        // 39 snow with rain
+        // 40 41 snow with rain
+        // 42 43 44 rain with snow
+        // 45 rain with snow
+        // 46 wind
+        // 47 48 foggy (dashes?)
+        // 49 50 51 unk
+
+        switch (openWeatherMapCondition) {
+        //Group 2xx: Thunderstorm
+            case 210:  //light thunderstorm::  //11d
+            case 200:  //thunderstorm with light rain:  //11d
+            case 201:  //thunderstorm with rain:  //11d
+            case 202:  //thunderstorm with heavy rain:  //11d
+            case 230:  //thunderstorm with light drizzle:  //11d
+            case 231:  //thunderstorm with drizzle:  //11d
+            case 232:  //thunderstorm with heavy drizzle:  //11d
+            case 211:  //thunderstorm:  //11d
+            case 212:  //heavy thunderstorm:  //11d
+            case 221:  //ragged thunderstorm:  //11d
+                return 27;
+
+        //Group 90x: Extreme
+            case 901:  //tropical storm
+        //Group 7xx: Atmosphere
+            case 781:  //tornado:  //[[file:50d.png]]
+        //Group 90x: Extreme
+            case 900:  //tornado
+        // Group 7xx: Atmosphere
+            case 771:  //squalls:  //[[file:50d.png]]
+        //Group 9xx: Additional
+            case 960:  //storm
+            case 961:  //violent storm
+            case 902:  //hurricane
+            case 962:  //hurricane
+                return 46;
+
+        //Group 3xx: Drizzle
+            case 300:  //light intensity drizzle:  //09d
+            case 301:  //drizzle:  //09d
+            case 302:  //heavy intensity drizzle:  //09d
+            case 310:  //light intensity drizzle rain:  //09d
+            case 311:  //drizzle rain:  //09d
+            case 312:  //heavy intensity drizzle rain:  //09d
+            case 313:  //shower rain and drizzle:  //09d
+            case 314:  //heavy shower rain and drizzle:  //09d
+            case 321:  //shower drizzle:  //09d
+        //Group 5xx: Rain
+            case 500:  //light rain:  //10d
+            case 501:  //moderate rain:  //10d
+            case 502:  //heavy intensity rain:  //10d
+            case 503:  //very heavy rain:  //10d
+            case 504:  //extreme rain:  //10d
+            case 520:  //light intensity shower rain:  //09d
+            case 521:  //shower rain:  //09d
+            case 522:  //heavy intensity shower rain:  //09d
+            case 531:  //ragged shower rain:  //09d
+                return 17;
+
+        //Group 90x: Extreme
+            case 906:  //hail
+            case 615:  //light rain and snow:  //[[file:13d.png]]
+            case 616:  //rain and snow:  //[[file:13d.png]]
+            case 511:  //freezing rain:  //13d
+                return 40;
+
+        //Group 6xx: Snow
+            case 611:  //sleet:  //[[file:13d.png]]
+            case 612:  //shower sleet:  //[[file:13d.png]]
+        //Group 6xx: Snow
+            case 600:  //light snow:  //[[file:13d.png]]
+            case 601:  //snow:  //[[file:13d.png]]
+        //Group 6xx: Snow
+            case 602:  //heavy snow:  //[[file:13d.png]]
+        //Group 6xx: Snow
+            case 620:  //light shower snow:  //[[file:13d.png]]
+            case 621:  //shower snow:  //[[file:13d.png]]
+            case 622:  //heavy shower snow:  //[[file:13d.png]]
+                return 38;
+
+
+        //Group 7xx: Atmosphere
+            case 701:  //mist:  //[[file:50d.png]]
+            case 711:  //smoke:  //[[file:50d.png]]
+            case 721:  //haze:  //[[file:50d.png]]
+            case 731:  //sandcase  dust whirls:  //[[file:50d.png]]
+            case 741:  //fog:  //[[file:50d.png]]
+            case 751:  //sand:  //[[file:50d.png]]
+            case 761:  //dust:  //[[file:50d.png]]
+            case 762:  //volcanic ash:  //[[file:50d.png]]
+                return 47;
+
+        //Group 800: Clear
+            case 800:  //clear sky:  //[[file:01d.png]] [[file:01n.png]]
+                return 5;
+
+        //Group 90x: Extreme
+            case 904:  //hot
+                return 5;
+
+        //Group 80x: Clouds
+            case 801:  //few clouds:  //[[file:02d.png]] [[file:02n.png]]
+            case 802:  //scattered clouds:  //[[file:03d.png]] [[file:03d.png]]
+                return 8;
+            case 803:  //broken clouds:  //[[file:04d.png]] [[file:03d.png]]
+                return 15;
+
+        //Group 80x: Clouds
+            case 804:  //overcast clouds:  //[[file:04d.png]] [[file:04d.png]]
+                return 15;
+
+        //Group 9xx: Additional
+            case 905:  //windy
+            case 951:  //calm
+            case 952:  //light breeze
+            case 953:  //gentle breeze
+            case 954:  //moderate breeze
+            case 955:  //fresh breeze
+            case 956:  //strong breeze
+            case 957:  //high windcase  near gale
+            case 958:  //gale
+            case 959:  //severe gale
+                return 46;
+
+            default:
+        //Group 90x: Extreme
+            case 903:  //cold
+                return 35;
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/AuthNegotiationMessage.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/AuthNegotiationMessage.java
new file mode 100644
index 0000000..3bc1d60
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/AuthNegotiationMessage.java
@@ -0,0 +1,61 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages;
+
+import org.apache.commons.lang3.EnumUtils;
+
+import java.util.EnumSet;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.status.AuthNegotiationStatusMessage;
+
+public class AuthNegotiationMessage extends GFDIMessage {
+
+    private final int unknown;
+    private final EnumSet<AuthFlags> requestedAuthFlags;
+
+    public AuthNegotiationMessage(GarminMessage garminMessage, int unknown, EnumSet<AuthFlags> requestedAuthFlags) {
+        this.garminMessage = garminMessage;
+        this.unknown = unknown;
+        this.requestedAuthFlags = requestedAuthFlags;
+
+        LOG.info("Message {}, unkByte: {}, flags: {}", garminMessage, unknown, requestedAuthFlags);
+
+        this.statusMessage = new AuthNegotiationStatusMessage(garminMessage, Status.ACK, AuthNegotiationStatusMessage.AuthNegotiationStatus.GUESS_OK, this.unknown, requestedAuthFlags);
+    }
+
+    public static AuthNegotiationMessage parseIncoming(MessageReader reader, GarminMessage garminMessage) {
+
+        final int unk = reader.readByte();
+        final EnumSet<AuthFlags> authFlags = AuthFlags.fromBitMask(reader.readInt());
+
+        return new AuthNegotiationMessage(garminMessage, unk, authFlags);
+    }
+
+    @Override
+    protected boolean generateOutgoing() {
+
+        final MessageWriter writer = new MessageWriter(response);
+        writer.writeShort(0); // placeholder for packet size
+        writer.writeShort(this.garminMessage.getId());
+
+        //set all to 0 as we don't know what else to do
+        writer.writeByte(0);
+        writer.writeInt((int) EnumUtils.generateBitVector(AuthFlags.class, EnumSet.noneOf(AuthFlags.class)));
+
+        return false;
+    }
+
+    public enum AuthFlags {
+        UNK_00000001, //saw in logs
+        UNK_00000010,
+        UNK_00000100,
+        UNK_00001000,
+        UNK_00010000,
+        UNK_00100000,
+        UNK_01000000,
+        UNK_10000000,
+        ;
+
+        public static EnumSet<AuthFlags> fromBitMask(final int code) {
+            return EnumUtils.processBitVector(AuthFlags.class, code);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/ConfigurationMessage.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/ConfigurationMessage.java
new file mode 100644
index 0000000..a6a77b6
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/ConfigurationMessage.java
@@ -0,0 +1,60 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdatePreferences;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminPreferences;
+import nodomain.freeyourgadget.gadgetbridge.devices.vivomovehr.GarminCapability;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.deviceevents.CapabilitiesDeviceEvent;
+
+
+public class ConfigurationMessage extends GFDIMessage {
+    public final Set<GarminCapability> OUR_CAPABILITIES = GarminCapability.ALL_CAPABILITIES;
+    private final byte[] incomingConfigurationPayload;
+    private final Set<GarminCapability> capabilities;
+    private final byte[] ourConfigurationPayload = GarminCapability.setToBinary(OUR_CAPABILITIES);
+
+    public ConfigurationMessage(GarminMessage garminMessage, byte[] configurationPayload) {
+        this.garminMessage = garminMessage;
+        if (configurationPayload.length > 255)
+            throw new IllegalArgumentException("Too long payload");
+        this.incomingConfigurationPayload = configurationPayload;
+        this.capabilities = GarminCapability.setFromBinary(configurationPayload);
+        LOG.info("Received configuration message; capabilities: {}", GarminCapability.setToString(capabilities));
+
+        this.statusMessage = this.getStatusMessage();
+    }
+
+    public static ConfigurationMessage parseIncoming(MessageReader reader, GarminMessage garminMessage) {
+        final int numBytes = reader.readByte();
+        return new ConfigurationMessage(garminMessage, reader.readBytes(numBytes));
+    }
+
+    @Override
+    public List<GBDeviceEvent> getGBDeviceEvent() {
+        final Set<Object> capabilitiesPref = new HashSet<>();
+        for (final GarminCapability capability : capabilities) {
+            capabilitiesPref.add(capability.name());
+        }
+        return Arrays.asList(
+                new CapabilitiesDeviceEvent(capabilities),
+                new GBDeviceEventUpdatePreferences(GarminPreferences.PREF_GARMIN_CAPABILITIES, capabilitiesPref)
+        );
+    }
+
+    @Override
+    protected boolean generateOutgoing() {
+        final MessageWriter writer = new MessageWriter(response);
+        writer.writeShort(0); // placeholder for packet size
+        writer.writeShort(garminMessage.getId());
+        writer.writeByte(ourConfigurationPayload.length);
+        writer.writeBytes(ourConfigurationPayload);
+        return true;
+    }
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/CreateFileMessage.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/CreateFileMessage.java
new file mode 100644
index 0000000..3db6ae9
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/CreateFileMessage.java
@@ -0,0 +1,62 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages;
+
+import java.util.Random;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.FileType;
+
+public class CreateFileMessage extends GFDIMessage {
+
+    private final int fileSize;
+    private final FileType.FILETYPE filetype;
+    private final boolean generateOutgoing;
+
+    public CreateFileMessage(GarminMessage garminMessage, int fileSize, FileType.FILETYPE filetype) {
+        this.fileSize = fileSize;
+        this.filetype = filetype;
+        this.garminMessage = garminMessage;
+        this.statusMessage = this.getStatusMessage();
+        this.generateOutgoing = false;
+    }
+
+    public CreateFileMessage(int fileSize, FileType.FILETYPE filetype) {
+        this.garminMessage = GarminMessage.CREATE_FILE;
+        this.fileSize = fileSize;
+        this.filetype = filetype;
+        this.statusMessage = this.getStatusMessage();
+        this.generateOutgoing = true;
+    }
+
+    public static CreateFileMessage parseIncoming(MessageReader reader, GarminMessage garminMessage) {
+
+        final int fileSize = reader.readInt();
+        final int dataType = reader.readByte(); //SupportedFileTypesStatusMessage.FileTypeInfo.type
+        final int subType = reader.readByte();//SupportedFileTypesStatusMessage.FileTypeInfo.subtypetype
+        final FileType.FILETYPE filetype = FileType.FILETYPE.fromDataTypeSubType(dataType, subType);
+        final int fileIndex = reader.readShort(); //???
+        reader.readByte(); //unk
+        final int subTypeMask = reader.readByte(); //???
+        final int numberMask = reader.readShort(); //???
+
+        return new CreateFileMessage(garminMessage, fileSize, filetype);
+    }
+
+    @Override
+    protected boolean generateOutgoing() { //TODO: adjust variables
+        Random random = new Random();
+        final MessageWriter writer = new MessageWriter(response);
+        writer.writeShort(0); // packet size will be filled below
+        writer.writeShort(this.garminMessage.getId());
+        writer.writeInt(this.fileSize);
+        writer.writeByte(this.filetype.getType());
+        writer.writeByte(this.filetype.getSubType());
+        writer.writeShort(0); //fileIndex
+        writer.writeByte(0); //reserved
+        writer.writeByte(0); //subtypemask
+        writer.writeShort(65535); //numbermask
+        writer.writeShort(0); ///???
+        writer.writeLong(random.nextLong());
+
+        return generateOutgoing;
+    }
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/CurrentTimeRequestMessage.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/CurrentTimeRequestMessage.java
new file mode 100644
index 0000000..f04a708
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/CurrentTimeRequestMessage.java
@@ -0,0 +1,79 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages;
+
+
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.zone.ZoneOffsetTransition;
+import java.time.zone.ZoneRules;
+import java.util.TimeZone;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.GarminTimeUtils;
+
+public class CurrentTimeRequestMessage extends GFDIMessage {
+    private final int referenceID;
+
+    public CurrentTimeRequestMessage(int referenceID, GarminMessage garminMessage) {
+        this.garminMessage = garminMessage;
+        this.referenceID = referenceID;
+        this.statusMessage = null; //our outgoing message is an ACK message
+    }
+
+    public static CurrentTimeRequestMessage parseIncoming(MessageReader reader, GarminMessage garminMessage) {
+        final int referenceID = reader.readInt();
+
+        return new CurrentTimeRequestMessage(referenceID, garminMessage);
+    }
+
+    @Override
+    protected boolean generateOutgoing() {
+
+        final Instant now = Instant.now();
+        final ZoneRules zoneRules = ZoneId.systemDefault().getRules();
+        final int dstOffset = (int) zoneRules.getDaylightSavings(now).getSeconds();
+        final int timeZoneOffset = TimeZone.getDefault().getOffset(now.toEpochMilli()) / 1000;
+        final int garminTimestamp = GarminTimeUtils.unixTimeToGarminTimestamp((int) now.getEpochSecond());
+        final ZoneOffsetTransition nextTransitionStart = zoneRules.nextTransition(now);
+
+        final int nextTransitionEndsGarminTs;
+        final int nextTransitionStartsGarminTs;
+
+        if (nextTransitionStart != null) {
+            final int nextTransitionStartsTs = (int) nextTransitionStart.toEpochSecond();
+            nextTransitionStartsGarminTs = GarminTimeUtils.unixTimeToGarminTimestamp(nextTransitionStartsTs);
+
+            final ZoneOffsetTransition nextTransitionEnd = zoneRules.nextTransition(nextTransitionStart.getInstant());
+
+            if (nextTransitionEnd != null) {
+                final int nextTransitionEndsTs = (int) nextTransitionEnd.toEpochSecond();
+                nextTransitionEndsGarminTs = GarminTimeUtils.unixTimeToGarminTimestamp(nextTransitionEndsTs);
+            } else {
+                nextTransitionEndsGarminTs = 0;
+            }
+        } else {
+            nextTransitionEndsGarminTs = 0;
+            nextTransitionStartsGarminTs = 0;
+        }
+
+        LOG.info(
+                "Processing current time request #{}: time={}, DST={}, ofs={}, nextTransitionEnds={}, nextTransitionStarts={}",
+                referenceID,
+                garminTimestamp,
+                dstOffset,
+                timeZoneOffset,
+                nextTransitionEndsGarminTs,
+                nextTransitionStartsGarminTs
+        );
+
+        final MessageWriter writer = new MessageWriter(response);
+        writer.writeShort(0); // packet size will be filled below
+        writer.writeShort(GarminMessage.RESPONSE.getId());
+        writer.writeShort(this.garminMessage.getId());
+        writer.writeByte(Status.ACK.ordinal());
+        writer.writeInt(referenceID);
+        writer.writeInt(garminTimestamp);
+        writer.writeInt(timeZoneOffset);
+        writer.writeInt(nextTransitionEndsGarminTs);
+        writer.writeInt(nextTransitionStartsGarminTs);
+        return true;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/DeviceInformationMessage.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/DeviceInformationMessage.java
new file mode 100644
index 0000000..ab40605
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/DeviceInformationMessage.java
@@ -0,0 +1,113 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages;
+
+import android.annotation.SuppressLint;
+import android.bluetooth.BluetoothAdapter;
+import android.os.Build;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo;
+
+public class DeviceInformationMessage extends GFDIMessage {
+
+    final int ourUnitNumber = -1;
+    final int ourSoftwareVersion = 7791;
+    final int ourMaxPacketSize = -1;
+    private final int incomingProtocolVersion;
+    private final int ourProtocolVersion = 150;
+    private final int incomingProductNumber;
+    private final int ourProductNumber = -1;
+    private final String incomingUnitNumber;
+    private final int incomingSoftwareVersion;
+    private final int incomingMaxPacketSize;
+    private final String bluetoothFriendlyName;
+    private final String deviceName;
+    private final String deviceModel;
+    // dual-pairing flags & MAC addresses...
+
+    public DeviceInformationMessage(GarminMessage garminMessage, int protocolVersion, int productNumber, String unitNumber, int softwareVersion, int maxPacketSize, String bluetoothFriendlyName, String deviceName, String deviceModel) {
+        this.garminMessage = garminMessage;
+        this.incomingProtocolVersion = protocolVersion;
+        this.incomingProductNumber = productNumber;
+        this.incomingUnitNumber = unitNumber;
+        this.incomingSoftwareVersion = softwareVersion;
+        this.incomingMaxPacketSize = maxPacketSize;
+        this.bluetoothFriendlyName = bluetoothFriendlyName;
+        this.deviceName = deviceName;
+        this.deviceModel = deviceModel;
+
+        GFDIMessage.setMaxPacketSize(maxPacketSize);
+        this.statusMessage = getStatusMessage();
+    }
+
+    public static DeviceInformationMessage parseIncoming(MessageReader reader, GarminMessage garminMessage) {
+        final int protocolVersion = reader.readShort();
+        final int productNumber = reader.readShort();
+        final String unitNumber = Long.toString(reader.readInt() & 0xFFFFFFFFL);
+        final int softwareVersion = reader.readShort();
+        final int maxPacketSize = reader.readShort();
+        final String bluetoothFriendlyName = reader.readString();
+        final String deviceName = reader.readString();
+        final String deviceModel = reader.readString();
+
+        return new DeviceInformationMessage(garminMessage, protocolVersion, productNumber, unitNumber, softwareVersion, maxPacketSize, bluetoothFriendlyName, deviceName, deviceModel);
+    }
+
+    @SuppressLint("MissingPermission")
+    @Override
+    protected boolean generateOutgoing() {
+        final int protocolFlags = this.incomingProtocolVersion / 100 == 1 ? 1 : 0;
+
+        final MessageWriter writer = new MessageWriter(response);
+        writer.writeShort(0); // placeholder for packet size
+        writer.writeShort(GarminMessage.RESPONSE.getId());
+        writer.writeShort(this.garminMessage.getId());
+        writer.writeByte(Status.ACK.ordinal());
+        writer.writeShort(ourProtocolVersion);
+        writer.writeShort(ourProductNumber);
+        writer.writeInt(ourUnitNumber);
+        writer.writeShort(ourSoftwareVersion);
+        writer.writeShort(ourMaxPacketSize);
+        String bluetoothName;
+        try {
+            bluetoothName = BluetoothAdapter.getDefaultAdapter().getName();
+        } catch (final Exception e) {
+            LOG.error("Failed to get bluetooth name", e);
+            bluetoothName = "Unknown";
+        }
+        writer.writeString(bluetoothName);
+        writer.writeString(Build.MANUFACTURER);
+        writer.writeString(Build.DEVICE);
+        writer.writeByte(protocolFlags);
+        return true;
+    }
+
+    @Override
+    public List<GBDeviceEvent> getGBDeviceEvent() {
+        LOG.info(
+                "Received device information: protocol {}, product {}, unit {}, SW {}, max packet {}, BT name {}, device name {}, device model {}",
+                incomingProtocolVersion,
+                incomingProductNumber,
+                incomingUnitNumber,
+                getSoftwareVersionStr(),
+                incomingMaxPacketSize,
+                bluetoothFriendlyName,
+                deviceName,
+                deviceModel
+        );
+
+        GBDeviceEventVersionInfo versionCmd = new GBDeviceEventVersionInfo();
+        versionCmd.fwVersion = getSoftwareVersionStr();
+        versionCmd.hwVersion = deviceModel;
+        return Collections.singletonList(versionCmd);
+    }
+
+    private String getSoftwareVersionStr() {
+        int softwareVersionMajor = incomingSoftwareVersion / 100;
+        int softwareVersionMinor = incomingSoftwareVersion % 100;
+        return String.format(Locale.ROOT, "%d.%02d", softwareVersionMajor, softwareVersionMinor);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/DownloadRequestMessage.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/DownloadRequestMessage.java
new file mode 100644
index 0000000..225d1c2
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/DownloadRequestMessage.java
@@ -0,0 +1,45 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages;
+
+public class DownloadRequestMessage extends GFDIMessage {
+
+    private final int fileIndex;
+    private final REQUEST_TYPE requestType;
+    private final int crcSeed;
+    private final int dataSize;
+
+    private final int dataOffset;
+
+    public DownloadRequestMessage(GarminMessage garminMessage, int fileIndex, int size, REQUEST_TYPE requestType, int crcSeed, int dataSize, int dataOffset) {
+        this.requestType = requestType;
+        this.crcSeed = crcSeed;
+        this.dataSize = dataSize;
+        this.dataOffset = dataOffset;
+        this.garminMessage = garminMessage;
+        this.fileIndex = fileIndex;
+        this.statusMessage = this.getStatusMessage();
+    }
+
+    public DownloadRequestMessage(int fileIndex, int dataSize, REQUEST_TYPE requestType, int crcSeed, int dataOffset) {
+        this(GarminMessage.DOWNLOAD_REQUEST, fileIndex, dataSize, requestType, crcSeed, dataSize, dataOffset);
+    }
+
+    @Override
+    protected boolean generateOutgoing() {
+        final MessageWriter writer = new MessageWriter(response);
+        writer.writeShort(0); // packet size will be filled below
+        writer.writeShort(this.garminMessage.getId());
+        writer.writeShort(this.fileIndex);
+        writer.writeInt(this.dataOffset);
+        writer.writeByte(this.requestType.ordinal());
+        writer.writeShort(this.crcSeed);
+        writer.writeInt(this.dataSize);
+
+        return true;
+    }
+
+    public enum REQUEST_TYPE {
+        CONTINUE,
+        NEW,
+    }
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/FileTransferDataMessage.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/FileTransferDataMessage.java
new file mode 100644
index 0000000..2aff933
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/FileTransferDataMessage.java
@@ -0,0 +1,60 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.status.FileTransferDataStatusMessage;
+
+public class FileTransferDataMessage extends GFDIMessage {
+
+    private final byte[] message;
+    private final int dataOffset;
+    private final boolean sendOutgoing;
+    private final int crc;
+
+    public FileTransferDataMessage(byte[] message, int dataOffset, int crc) {
+        this(message, dataOffset, crc, true);
+    }
+
+    public FileTransferDataMessage(byte[] message, int dataOffset, int crc, boolean sendOutgoing) {
+        this.garminMessage = GarminMessage.FILE_TRANSFER_DATA;
+        this.dataOffset = dataOffset;
+        this.crc = crc;
+        this.message = message;
+
+        this.statusMessage = new FileTransferDataStatusMessage(GarminMessage.FILE_TRANSFER_DATA, Status.ACK, FileTransferDataStatusMessage.TransferStatus.OK, dataOffset);
+        this.sendOutgoing = sendOutgoing;
+    }
+
+    public static FileTransferDataMessage parseIncoming(MessageReader reader, GarminMessage garminMessage) {
+
+        final int flags = reader.readByte();
+        final int crc = reader.readShort();
+        final int dataOffset = reader.readInt();
+        final byte[] message = reader.readBytes(reader.remaining());
+
+        return new FileTransferDataMessage(message, dataOffset, crc, false);
+    }
+
+    public byte[] getMessage() {
+        return message;
+    }
+
+    public int getCrc() {
+        return crc;
+    }
+
+    public int getDataOffset() {
+        return dataOffset;
+    }
+
+    @Override
+    protected boolean generateOutgoing() {
+        final MessageWriter writer = new MessageWriter(response);
+        writer.writeShort(0); // packet size will be filled below
+        writer.writeShort(garminMessage.getId());
+        writer.writeByte(0); //flags?
+        writer.writeShort(crc);
+        writer.writeInt(dataOffset);
+        writer.writeBytes(message);
+        return sendOutgoing;
+    }
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/FindMyPhoneCancelMessage.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/FindMyPhoneCancelMessage.java
new file mode 100644
index 0000000..32ef567
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/FindMyPhoneCancelMessage.java
@@ -0,0 +1,32 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages;
+
+
+import java.util.Collections;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventFindPhone;
+
+public class FindMyPhoneCancelMessage extends GFDIMessage {
+    public FindMyPhoneCancelMessage(GarminMessage garminMessage) {
+        this.garminMessage = garminMessage;
+
+        this.statusMessage = getStatusMessage();
+    }
+
+    public static FindMyPhoneCancelMessage parseIncoming(MessageReader reader, GarminMessage garminMessage) {
+        return new FindMyPhoneCancelMessage(garminMessage);
+    }
+
+    @Override
+    public List<GBDeviceEvent> getGBDeviceEvent() {
+        final GBDeviceEventFindPhone findPhoneEvent = new GBDeviceEventFindPhone();
+        findPhoneEvent.event = GBDeviceEventFindPhone.Event.STOP;
+        return Collections.singletonList(findPhoneEvent);
+    }
+
+    @Override
+    protected boolean generateOutgoing() {
+        return false;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/FindMyPhoneRequestMessage.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/FindMyPhoneRequestMessage.java
new file mode 100644
index 0000000..1b9a057
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/FindMyPhoneRequestMessage.java
@@ -0,0 +1,36 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages;
+
+import java.util.Collections;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventFindPhone;
+
+public class FindMyPhoneRequestMessage extends GFDIMessage {
+    private final int duration;
+
+    public FindMyPhoneRequestMessage(GarminMessage garminMessage, int duration) {
+        this.garminMessage = garminMessage;
+        this.duration = duration;
+
+        this.statusMessage = getStatusMessage();
+    }
+
+    public static FindMyPhoneRequestMessage parseIncoming(MessageReader reader, GarminMessage garminMessage) {
+        final int duration = reader.readByte();
+
+        return new FindMyPhoneRequestMessage(garminMessage, duration);
+    }
+
+    @Override
+    public List<GBDeviceEvent> getGBDeviceEvent() {
+        final GBDeviceEventFindPhone findPhoneEvent = new GBDeviceEventFindPhone();
+        findPhoneEvent.event = GBDeviceEventFindPhone.Event.START;
+        return Collections.singletonList(findPhoneEvent);
+    }
+
+    @Override
+    protected boolean generateOutgoing() {
+        return false;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/FitDataMessage.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/FitDataMessage.java
new file mode 100644
index 0000000..66622df
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/FitDataMessage.java
@@ -0,0 +1,61 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.PredefinedLocalMessage;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordData;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordHeader;
+
+public class FitDataMessage extends GFDIMessage {
+    private final List<RecordData> recordDataList;
+
+    public FitDataMessage(List<RecordData> recordDataList, GarminMessage garminMessage) {
+        this.recordDataList = recordDataList;
+        this.garminMessage = garminMessage;
+    }
+
+    public FitDataMessage(List<RecordData> recordDataList) {
+        this.recordDataList = recordDataList;
+        this.garminMessage = GarminMessage.FIT_DATA;
+    }
+
+    public static FitDataMessage parseIncoming(MessageReader reader, GarminMessage garminMessage) {
+        final List<RecordData> recordDataList = new ArrayList<>();
+
+        while (reader.remaining() > 0) {
+            RecordHeader recordHeader = new RecordHeader((byte) reader.readByte());
+            if (recordHeader.isDefinition())
+                return null;
+            PredefinedLocalMessage predefinedLocalMessage = PredefinedLocalMessage.fromType(recordHeader.getLocalMessageType());
+            if (predefinedLocalMessage == null) {
+                LOG.warn("Local message is null");
+
+                return null;
+            }
+            RecordData recordData = new RecordData(
+                    predefinedLocalMessage.getRecordDefinition(),
+                    predefinedLocalMessage.getRecordDefinition().getRecordHeader()
+            );
+            recordData.parseDataMessage(reader, null);
+            recordDataList.add(recordData);
+        }
+
+        return new FitDataMessage(recordDataList, garminMessage);
+    }
+
+    public List<RecordData> getRecordDataList() {
+        return recordDataList;
+    }
+
+    @Override
+    protected boolean generateOutgoing() {
+        final MessageWriter writer = new MessageWriter(response);
+        writer.writeShort(0); // packet size will be filled below
+        writer.writeShort(this.garminMessage.getId());
+        for (RecordData recordData : recordDataList) {
+            recordData.generateOutgoingDataPayload(writer);
+        }
+        return true;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/FitDefinitionMessage.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/FitDefinitionMessage.java
new file mode 100644
index 0000000..4a48d95
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/FitDefinitionMessage.java
@@ -0,0 +1,50 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordDefinition;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordHeader;
+
+public class FitDefinitionMessage extends GFDIMessage {
+
+    private final List<RecordDefinition> recordDefinitions;
+
+    public FitDefinitionMessage(List<RecordDefinition> recordDefinitions, GarminMessage garminMessage) {
+        this.recordDefinitions = recordDefinitions;
+        this.garminMessage = garminMessage;
+        this.statusMessage = this.getStatusMessage();
+    }
+
+    public FitDefinitionMessage(List<RecordDefinition> recordDefinitions) {
+        this.recordDefinitions = recordDefinitions;
+        this.garminMessage = GarminMessage.FIT_DEFINITION;
+    }
+
+    public static FitDefinitionMessage parseIncoming(MessageReader reader, GarminMessage garminMessage) {
+        List<RecordDefinition> recordDefinitions = new ArrayList<>();
+
+        while (reader.remaining() > 0) {
+            RecordHeader recordHeader = new RecordHeader((byte) reader.readByte());
+            recordDefinitions.add(RecordDefinition.parseIncoming(reader, recordHeader));
+        }
+
+        return new FitDefinitionMessage(recordDefinitions, garminMessage);
+    }
+
+    public List<RecordDefinition> getRecordDefinitions() {
+        return recordDefinitions;
+    }
+
+    @Override
+    protected boolean generateOutgoing() {
+        final MessageWriter writer = new MessageWriter(response);
+        writer.writeShort(0); // packet size will be filled below
+        writer.writeShort(garminMessage.getId());
+        for (RecordDefinition recordDefinition : recordDefinitions) {
+            recordDefinition.generateOutgoingPayload(writer);
+        }
+        return true;
+    }
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/GFDIMessage.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/GFDIMessage.java
new file mode 100644
index 0000000..b1acb44
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/GFDIMessage.java
@@ -0,0 +1,221 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages;
+
+import androidx.annotation.Nullable;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.lang.reflect.Method;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Collections;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.ChecksumCalculator;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.GarminByteBufferReader;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.status.GFDIStatusMessage;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.status.GenericStatusMessage;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public abstract class GFDIMessage {
+    protected static final Logger LOG = LoggerFactory.getLogger(GFDIMessage.class);
+    private static int maxPacketSize = 375; //safe default?
+    protected final ByteBuffer response = ByteBuffer.allocate(1000);
+    protected GFDIStatusMessage statusMessage;
+    protected GarminMessage garminMessage;
+
+    public static int getMaxPacketSize() {
+        return maxPacketSize;
+    }
+
+    public static void setMaxPacketSize(int maxPacketSize) {
+        GFDIMessage.maxPacketSize = maxPacketSize;
+    }
+
+    public static GFDIMessage parseIncoming(byte[] message) {
+        final MessageReader messageReader = new MessageReader(message);
+
+        final int messageType = messageReader.readShort();
+        try {
+            final GarminMessage garminMessage = GarminMessage.fromId(messageType);
+            if (garminMessage == null) {
+                LOG.warn("Unknown message type {}, message {}", messageType, message);
+                return new UnhandledMessage(messageType);
+            }
+            final Method m = garminMessage.objectClass.getMethod("parseIncoming", MessageReader.class, GarminMessage.class);
+            return garminMessage.objectClass.cast(m.invoke(null, messageReader, garminMessage));
+        } catch (final Exception e) {
+            LOG.error("UNHANDLED GFDI MESSAGE TYPE {}, MESSAGE {}", messageType, message, e);
+            return new UnhandledMessage(messageType);
+        } finally {
+            messageReader.warnIfLeftover(messageType);
+        }
+    }
+
+    protected abstract boolean generateOutgoing();
+
+    public byte[] getOutgoingMessage() {
+        response.clear();
+        boolean toSend = generateOutgoing();
+        response.order(ByteOrder.LITTLE_ENDIAN);
+        if (!toSend)
+            return null;
+        addLengthAndChecksum();
+        response.flip();
+
+        final byte[] packet = new byte[response.limit()];
+        response.get(packet);
+        return packet;
+    }
+
+    protected GFDIStatusMessage getStatusMessage() {
+        return new GenericStatusMessage(garminMessage, Status.ACK);
+    }
+
+    public List<GBDeviceEvent> getGBDeviceEvent() {
+        return Collections.emptyList();
+    }
+
+    public byte[] getAckBytestream() {
+        if (null == this.statusMessage) {
+            return null;
+        }
+        return this.statusMessage.getOutgoingMessage();
+    }
+
+    private void addLengthAndChecksum() {
+        response.putShort(0, (short) (response.position() + 2));
+        response.putShort((short) ChecksumCalculator.computeCrc(response.asReadOnlyBuffer(), 0, response.position()));
+    }
+
+    public enum GarminMessage {
+        RESPONSE(5000, GFDIStatusMessage.class), //TODO: STATUS is a better name?
+        DOWNLOAD_REQUEST(5002, DownloadRequestMessage.class),
+        UPLOAD_REQUEST(5003, UploadRequestMessage.class),
+        FILE_TRANSFER_DATA(5004, FileTransferDataMessage.class),
+        CREATE_FILE(5005, CreateFileMessage.class),
+        SET_FILE_FLAG(5008, SetFileFlagsMessage.class),
+        FIT_DEFINITION(5011, FitDefinitionMessage.class),
+        FIT_DATA(5012, FitDataMessage.class),
+        WEATHER_REQUEST(5014, WeatherMessage.class),
+        DEVICE_INFORMATION(5024, DeviceInformationMessage.class),
+        DEVICE_SETTINGS(5026, SetDeviceSettingsMessage.class),
+        SYSTEM_EVENT(5030, SystemEventMessage.class),
+        SUPPORTED_FILE_TYPES_REQUEST(5031, SupportedFileTypesMessage.class),
+        NOTIFICATION_UPDATE(5033, NotificationUpdateMessage.class),
+        NOTIFICATION_CONTROL(5034, NotificationControlMessage.class),
+        NOTIFICATION_DATA(5035, NotificationDataMessage.class),
+        NOTIFICATION_SUBSCRIPTION(5036, NotificationSubscriptionMessage.class),
+        FIND_MY_PHONE_REQUEST(5039, FindMyPhoneRequestMessage.class),
+        FIND_MY_PHONE_CANCEL(5040, FindMyPhoneCancelMessage.class),
+        MUSIC_CONTROL(5041, MusicControlMessage.class),
+        MUSIC_CONTROL_CAPABILITIES(5042, MusicControlCapabilitiesMessage.class),
+        PROTOBUF_REQUEST(5043, ProtobufMessage.class),
+        PROTOBUF_RESPONSE(5044, ProtobufMessage.class),
+        MUSIC_CONTROL_ENTITY_UPDATE(5049, MusicControlEntityUpdateMessage.class),
+        CONFIGURATION(5050, ConfigurationMessage.class),
+        CURRENT_TIME_REQUEST(5052, CurrentTimeRequestMessage.class),
+        AUTH_NEGOTIATION(5101, AuthNegotiationMessage.class)
+        ;
+        private final Class<? extends GFDIMessage> objectClass;
+        private final int id;
+
+        GarminMessage(int id, Class<? extends GFDIMessage> objectClass) {
+            this.id = id;
+            this.objectClass = objectClass;
+        }
+
+        public static Class<? extends GFDIMessage> getClassFromId(final int id) {
+            for (final GarminMessage garminMessage : GarminMessage.values()) {
+                if (garminMessage.getId() == id) {
+                    return garminMessage.getObjectClass();
+                }
+            }
+            return null;
+        }
+
+        @Nullable
+        public static GarminMessage fromId(final int id) {
+            for (final GarminMessage garminMessage : GarminMessage.values()) {
+                if (garminMessage.getId() == id) {
+                    return garminMessage;
+                }
+            }
+            return null;
+        }
+
+        public int getId() {
+            return id;
+        }
+
+        private Class<? extends GFDIMessage> getObjectClass() {
+            return objectClass;
+        }
+    }
+
+    public enum Status {
+        ACK,
+        NAK,
+        UNSUPPORTED,
+        DECODE_ERROR,
+        CRC_ERROR,
+        LENGTH_ERROR;
+
+        public static Status fromCode(final int code) {
+            for (final Status status : Status.values()) {
+                if (status.ordinal() == code) {
+                    return status;
+                }
+            }
+            throw new IllegalArgumentException("Unknown status code " + code);
+        }
+
+    }
+
+    public static class MessageReader extends GarminByteBufferReader {
+
+        private final int payloadSize;
+
+        public MessageReader(byte[] data) {
+            super(data);
+            this.byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
+            this.payloadSize = readShort(); //includes CRC
+            checkSize();
+            checkCRC();
+            this.byteBuffer.limit(payloadSize - 2); //remove CRC
+        }
+
+        public void skip(int offset) {
+            if (remaining() < offset) throw new IllegalStateException();
+            byteBuffer.position(byteBuffer.position() + offset);
+        }
+
+        private void checkSize() {
+            if (payloadSize != byteBuffer.capacity()) {
+                LOG.error("Received GFDI packet with invalid length: {} vs {}", payloadSize, byteBuffer.capacity());
+                throw new IllegalArgumentException("Received GFDI packet with invalid length");
+            }
+        }
+
+        private void checkCRC() {
+            final int crc = Short.toUnsignedInt(byteBuffer.getShort(payloadSize - 2));
+            final int correctCrc = ChecksumCalculator.computeCrc(byteBuffer.asReadOnlyBuffer(), 0, payloadSize - 2);
+            if (crc != correctCrc) {
+                LOG.error("Received GFDI packet with invalid CRC: {} vs {}", crc, correctCrc);
+                throw new IllegalArgumentException("Received GFDI packet with invalid CRC");
+            }
+        }
+
+        public void warnIfLeftover(int messageType) {
+            if (byteBuffer.hasRemaining() && byteBuffer.position() < (byteBuffer.limit())) {
+                int pos = byteBuffer.position();
+                int numBytes = (byteBuffer.limit()) - byteBuffer.position();
+                byte[] leftover = new byte[numBytes];
+                byteBuffer.get(leftover);
+                byteBuffer.position(pos);
+                LOG.warn("Leftover bytes when parsing message type {}. Bytes: {}, complete message: {}", messageType, GB.hexdump(leftover), GB.hexdump(byteBuffer.array()));
+            }
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/MessageWriter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/MessageWriter.java
new file mode 100644
index 0000000..dc5ae50
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/MessageWriter.java
@@ -0,0 +1,88 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+
+public class MessageWriter {
+    private static final int DEFAULT_BUFFER_SIZE = 16384;
+    private final ByteBuffer byteBuffer;
+
+    public MessageWriter() {
+        this(DEFAULT_BUFFER_SIZE);
+    }
+
+    public MessageWriter(int bufferSize) {
+        this.byteBuffer = ByteBuffer.allocate(bufferSize);
+        this.byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
+    }
+
+    public MessageWriter(ByteBuffer byteBuffer) {
+        this.byteBuffer = byteBuffer;
+        this.byteBuffer.clear();
+        this.byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
+    }
+
+    public void setByteOrder(ByteOrder byteOrder) {
+        this.byteBuffer.order(byteOrder);
+    }
+
+    public void writeByte(int value) {
+        byteBuffer.put((byte) value);
+    }
+
+    public void writeShort(int value) {
+        byteBuffer.putShort((short) value);
+    }
+
+    public void writeInt(int value) {
+        byteBuffer.putInt(value);
+    }
+
+    public void writeLong(long value) {
+        byteBuffer.putLong(value);
+    }
+
+    public void writeFloat32(float value) {
+        byteBuffer.putFloat(value);
+    }
+
+    public void writeFloat64(double value) {
+        byteBuffer.putDouble(value);
+    }
+
+    public void writeString(String value) {
+        final byte[] bytes = value.getBytes(StandardCharsets.UTF_8);
+        final int size = bytes.length;
+        if (size > 255) throw new IllegalArgumentException("Too long string");
+
+        byteBuffer.put((byte) size);
+        byteBuffer.put(bytes);
+    }
+
+    public byte[] getBytes() {
+        //TODO: implement the correct flip()/compat() logic
+        return byteBuffer.hasRemaining() ? Arrays.copyOf(byteBuffer.array(), byteBuffer.position()) : byteBuffer.array();
+    }
+
+    public byte[] peekBytes() {
+        return byteBuffer.array();
+    }
+
+    public int getSize() {
+        return byteBuffer.position();
+    }
+
+    public int getLimit() {
+        return byteBuffer.limit();
+    }
+
+    public void writeBytes(byte[] bytes) {
+        writeBytes(bytes, 0, bytes.length);
+    }
+
+    public void writeBytes(byte[] bytes, int offset, int size) {
+        byteBuffer.put(Arrays.copyOfRange(bytes, offset, offset + size));
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/MusicControlCapabilitiesMessage.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/MusicControlCapabilitiesMessage.java
new file mode 100644
index 0000000..173229e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/MusicControlCapabilitiesMessage.java
@@ -0,0 +1,48 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages;
+
+public class MusicControlCapabilitiesMessage extends GFDIMessage {
+
+    private final int supportedCapabilities;
+    private final GarminMusicControlCommand[] commands = GarminMusicControlCommand.values();
+
+    public MusicControlCapabilitiesMessage(GarminMessage garminMessage, int supportedCapabilities) {
+        this.garminMessage = garminMessage;
+        this.supportedCapabilities = supportedCapabilities;
+        this.statusMessage = null; //our outgoing message is an ACK message
+    }
+
+    public static MusicControlCapabilitiesMessage parseIncoming(MessageReader reader, GarminMessage garminMessage) {
+        final int supportedCapabilities = reader.readByte();
+
+        return new MusicControlCapabilitiesMessage(garminMessage, supportedCapabilities);
+    }
+
+    @Override
+    protected boolean generateOutgoing() {
+        if (commands.length > 255)
+            throw new IllegalArgumentException("Too many supported commands");
+
+        final MessageWriter writer = new MessageWriter(response);
+        writer.writeShort(0); // packet size will be filled below
+        writer.writeShort(GarminMessage.RESPONSE.getId());
+        writer.writeShort(this.garminMessage.getId());
+        writer.writeByte(Status.ACK.ordinal());
+        writer.writeByte(commands.length);
+        for (GarminMusicControlCommand command : commands) {
+            writer.writeByte(command.ordinal());
+        }
+        return true;
+    }
+
+    public enum GarminMusicControlCommand {
+        TOGGLE_PLAY_PAUSE,
+        SKIP_TO_NEXT_ITEM,
+        SKIP_TO_PREVIOUS_ITEM,
+        VOLUME_UP,
+        VOLUME_DOWN,
+        PLAY,
+        PAUSE,
+        SKIP_FORWARD,
+        SKIP_BACKWARDS
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/MusicControlEntityUpdateMessage.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/MusicControlEntityUpdateMessage.java
new file mode 100644
index 0000000..fad5095
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/MusicControlEntityUpdateMessage.java
@@ -0,0 +1,112 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages;
+
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Map;
+
+public class MusicControlEntityUpdateMessage extends GFDIMessage {
+
+    private final Map<MusicEntity, String> attributes;
+
+    public MusicControlEntityUpdateMessage(Map<MusicEntity, String> attributes) {
+
+        this.attributes = attributes;
+        this.garminMessage = GarminMessage.MUSIC_CONTROL_ENTITY_UPDATE;
+
+    }
+
+    public static MusicControlEntityUpdateMessage parseIncoming(MessageReader reader, GarminMessage garminMessage) {
+        final Map<MusicEntity, String> attributes = new HashMap<>();
+
+        while (reader.remaining() > 0) {
+            final int len = reader.readByte();
+            final int entityId = reader.readByte();
+            final int ordinal = reader.readByte();
+            final int zero = reader.readByte();
+            byte[] bytes = reader.readBytes(len - 3);
+            final String str = new String(bytes, StandardCharsets.UTF_8);
+
+            switch (entityId) {
+                case 0:
+                    PLAYER player = PLAYER.values()[ordinal];
+                    break;
+                case 1:
+                    QUEUE queue = QUEUE.values()[ordinal];
+                    break;
+                case 2:
+                    TRACK track = TRACK.values()[ordinal];
+                    break;
+                default:
+                    LOG.warn("Unknown entity {}", entityId);
+                    continue;
+            }
+        }
+
+        return new MusicControlEntityUpdateMessage(attributes);
+    }
+
+    @Override
+    protected boolean generateOutgoing() {
+        final MessageWriter writer = new MessageWriter(response);
+        writer.writeShort(0); // packet size will be filled below
+        writer.writeShort(this.garminMessage.getId());
+
+        for (Map.Entry<MusicEntity, String> entry : attributes.entrySet()) {
+            MusicEntity a = entry.getKey();
+            String value = entry.getValue();
+            if (null == value)
+                value = "";
+            byte[] v = value.getBytes(StandardCharsets.UTF_8);
+            if (v.length > 252) throw new IllegalArgumentException("Too long value");
+
+            writer.writeByte((v.length + 3) & 0xff); //the three following bytes
+            writer.writeByte(a.getEntityId());
+            writer.writeByte(a.ordinal());
+            writer.writeByte(0);//TODO what is this?
+            writer.writeBytes(v);
+        }
+        return true;
+    }
+
+    public enum PLAYER implements MusicEntity {
+        NAME,
+        PLAYBACK_INFO,
+        VOLUME;
+
+        @Override
+        public int getEntityId() {
+            return 0;
+        }
+    }
+
+
+    public enum QUEUE implements MusicEntity {
+        INDEX,
+        COUNT,
+        SHUFFLE,
+        REPEAT;
+
+        @Override
+        public int getEntityId() {
+            return 1;
+        }
+    }
+
+    public enum TRACK implements MusicEntity {
+        ARTIST,
+        ALBUM,
+        TITLE,
+        DURATION;
+
+        @Override
+        public int getEntityId() {
+            return 2;
+        }
+    }
+
+    public interface MusicEntity {
+        int getEntityId();
+
+        int ordinal();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/MusicControlMessage.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/MusicControlMessage.java
new file mode 100644
index 0000000..508027d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/MusicControlMessage.java
@@ -0,0 +1,53 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages;
+
+
+import java.util.Collections;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventMusicControl;
+
+public class MusicControlMessage extends GFDIMessage {
+
+    private static final MusicControlCapabilitiesMessage.GarminMusicControlCommand[] commands = MusicControlCapabilitiesMessage.GarminMusicControlCommand.values();
+    private final GBDeviceEventMusicControl event;
+
+    public MusicControlMessage(GarminMessage garminMessage, MusicControlCapabilitiesMessage.GarminMusicControlCommand command) {
+        this.event = new GBDeviceEventMusicControl();
+        this.garminMessage = garminMessage;
+        switch (command) {
+            case TOGGLE_PLAY_PAUSE:
+                event.event = GBDeviceEventMusicControl.Event.PLAYPAUSE;
+                break;
+            case SKIP_TO_NEXT_ITEM:
+                event.event = GBDeviceEventMusicControl.Event.NEXT;
+                break;
+            case SKIP_TO_PREVIOUS_ITEM:
+                event.event = GBDeviceEventMusicControl.Event.PREVIOUS;
+                break;
+            case VOLUME_UP:
+                event.event = GBDeviceEventMusicControl.Event.VOLUMEUP;
+                break;
+            case VOLUME_DOWN:
+                event.event = GBDeviceEventMusicControl.Event.VOLUMEDOWN;
+                break;
+        }
+
+        this.statusMessage = this.getStatusMessage();
+    }
+
+    public static MusicControlMessage parseIncoming(MessageReader reader, GarminMessage garminMessage) {
+        MusicControlCapabilitiesMessage.GarminMusicControlCommand command = commands[reader.readByte()];
+
+        return new MusicControlMessage(garminMessage, command);
+    }
+
+    public List<GBDeviceEvent> getGBDeviceEvent() {
+        return Collections.singletonList(event);
+    }
+
+    @Override
+    protected boolean generateOutgoing() {
+        return false;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/NotificationControlMessage.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/NotificationControlMessage.java
new file mode 100644
index 0000000..8b7ef53
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/NotificationControlMessage.java
@@ -0,0 +1,150 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.NotificationsHandler;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.status.NotificationControlStatusMessage;
+
+import static nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.NotificationsHandler.NotificationCommand.GET_NOTIFICATION_ATTRIBUTES;
+import static nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.NotificationsHandler.NotificationCommand.PERFORM_LEGACY_NOTIFICATION_ACTION;
+import static nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.NotificationsHandler.NotificationCommand.PERFORM_NOTIFICATION_ACTION;
+
+public class NotificationControlMessage extends GFDIMessage {
+
+    private final NotificationsHandler.NotificationCommand command;
+    private final int notificationId;
+    private Map<NotificationsHandler.NotificationAttribute, Integer> notificationAttributesMap;
+    private NotificationsHandler.LegacyNotificationAction legacyNotificationAction;
+    private NotificationsHandler.NotificationAction notificationAction;
+    private String actionString;
+    private List<GBDeviceEvent> gbDeviceEventList;
+
+    public NotificationControlMessage(GarminMessage garminMessage, NotificationsHandler.NotificationCommand command, int notificationId, NotificationsHandler.NotificationAction notificationAction, String actionString) {
+        this.garminMessage = garminMessage;
+        this.command = command;
+        this.notificationId = notificationId;
+        this.notificationAction = notificationAction;
+        this.actionString = actionString;
+
+        this.statusMessage = new NotificationControlStatusMessage(garminMessage, GFDIMessage.Status.ACK, NotificationControlStatusMessage.NotificationChunkStatus.OK, NotificationControlStatusMessage.NotificationStatusCode.NO_ERROR);
+
+    }
+
+    public NotificationControlMessage(GarminMessage garminMessage, NotificationsHandler.NotificationCommand command, int notificationId, NotificationsHandler.LegacyNotificationAction legacyNotificationAction) {
+        this.garminMessage = garminMessage;
+        this.command = command;
+        this.notificationId = notificationId;
+        this.legacyNotificationAction = legacyNotificationAction;
+
+        this.statusMessage = new NotificationControlStatusMessage(garminMessage, GFDIMessage.Status.ACK, NotificationControlStatusMessage.NotificationChunkStatus.OK, NotificationControlStatusMessage.NotificationStatusCode.NO_ERROR);
+    }
+
+    public NotificationControlMessage(GarminMessage garminMessage, NotificationsHandler.NotificationCommand command, int notificationId, Map<NotificationsHandler.NotificationAttribute, Integer> notificationAttributesMap) {
+        this.garminMessage = garminMessage;
+        this.command = command;
+        this.notificationId = notificationId;
+        this.notificationAttributesMap = notificationAttributesMap;
+
+        this.statusMessage = new NotificationControlStatusMessage(garminMessage, GFDIMessage.Status.ACK, NotificationControlStatusMessage.NotificationChunkStatus.OK, NotificationControlStatusMessage.NotificationStatusCode.NO_ERROR);
+    }
+
+    //TODO: the fact that we return three versions of this object is really ugly
+    public static NotificationControlMessage parseIncoming(MessageReader reader, GarminMessage garminMessage) {
+
+        final NotificationsHandler.NotificationCommand command = NotificationsHandler.NotificationCommand.fromCode(reader.readByte());
+
+        final int notificationId = reader.readInt();
+        if (command == GET_NOTIFICATION_ATTRIBUTES) {
+            final Map<NotificationsHandler.NotificationAttribute, Integer> notificationAttributesMap = createGetNotificationAttributesCommand(reader);
+            return new NotificationControlMessage(garminMessage, command, notificationId, notificationAttributesMap);
+        } else if (command == PERFORM_LEGACY_NOTIFICATION_ACTION) {
+            NotificationsHandler.LegacyNotificationAction[] values = NotificationsHandler.LegacyNotificationAction.values();
+            final NotificationsHandler.LegacyNotificationAction legacyNotificationAction = values[reader.readByte()];
+            return new NotificationControlMessage(garminMessage, command, notificationId, legacyNotificationAction);
+        } else if (command == PERFORM_NOTIFICATION_ACTION) {
+            final int actionId = reader.readByte();
+            final NotificationsHandler.NotificationAction notificationAction = NotificationsHandler.NotificationAction.fromCode(actionId);
+            final String actionString = reader.readNullTerminatedString();
+            return new NotificationControlMessage(garminMessage, command, notificationId, notificationAction, actionString);
+        }
+        LOG.warn("Unknown NotificationCommand in NotificationControlMessage");
+
+        return null;
+    }
+
+    private static Map<NotificationsHandler.NotificationAttribute, Integer> createGetNotificationAttributesCommand(MessageReader reader) {
+        final Map<NotificationsHandler.NotificationAttribute, Integer> notificationAttributesMap = new LinkedHashMap<>();
+        while (reader.remaining() > 0) {
+            final int attributeID = reader.readByte();
+
+            final NotificationsHandler.NotificationAttribute attribute = NotificationsHandler.NotificationAttribute.getByCode(attributeID);
+            LOG.info("Requested attribute: {}", attribute);
+            if (attribute == null) {
+                LOG.error("Unknown notification attribute {}", attributeID);
+                return null;
+            }
+            final int maxLength;
+            if (attribute.hasLengthParam) {
+                maxLength = reader.readShort();
+
+            } else if (attribute.hasAdditionalParams) {
+                maxLength = reader.readShort(); //TODO this is wrong
+                // TODO: What is this??
+                reader.readByte();
+
+            } else {
+                maxLength = 0;
+            }
+            notificationAttributesMap.put(attribute, maxLength);
+        }
+        return notificationAttributesMap;
+    }
+
+    public String getActionString() {
+        return actionString;
+    }
+
+    public void addGbDeviceEvent(GBDeviceEvent gbDeviceEvent) {
+        if (null == this.gbDeviceEventList)
+            this.gbDeviceEventList = new ArrayList<>();
+        this.gbDeviceEventList.add(gbDeviceEvent);
+    }
+
+    @Override
+    public List<GBDeviceEvent> getGBDeviceEvent() {
+        if (null == this.gbDeviceEventList)
+            return Collections.emptyList();
+        return gbDeviceEventList;
+    }
+
+    public NotificationsHandler.LegacyNotificationAction getLegacyNotificationAction() {
+        return legacyNotificationAction;
+    }
+
+    public NotificationsHandler.NotificationAction getNotificationAction() {
+        return notificationAction;
+    }
+
+    public NotificationsHandler.NotificationCommand getCommand() {
+        return command;
+    }
+
+    public int getNotificationId() {
+        return notificationId;
+    }
+
+    public Map<NotificationsHandler.NotificationAttribute, Integer> getNotificationAttributesMap() {
+        return notificationAttributesMap;
+    }
+
+    @Override
+    protected boolean generateOutgoing() {
+        return false;
+    }
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/NotificationDataMessage.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/NotificationDataMessage.java
new file mode 100644
index 0000000..2c6beea
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/NotificationDataMessage.java
@@ -0,0 +1,39 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages;
+
+public class NotificationDataMessage extends GFDIMessage {
+    private final byte[] chunk;
+    private final int dataOffset;
+    private final int messageSize;
+    private final boolean sendOutgoing;
+    private final int crc;
+
+    public NotificationDataMessage(byte[] chunk, int messageSize, int dataOffset, int crc) {
+        this(chunk, messageSize, dataOffset, crc, true);
+    }
+
+    public NotificationDataMessage(byte[] chunk, int messageSize, int dataOffset, int crc, boolean sendOutgoing) {
+        this.garminMessage = GarminMessage.NOTIFICATION_DATA;
+        this.dataOffset = dataOffset;
+        this.crc = crc;
+        this.chunk = chunk;
+        this.messageSize = messageSize;
+
+        this.sendOutgoing = sendOutgoing;
+    }
+
+    public int getCrc() {
+        return crc;
+    }
+
+    @Override
+    protected boolean generateOutgoing() {
+        final MessageWriter writer = new MessageWriter(response);
+        writer.writeShort(0); // packet size will be filled below
+        writer.writeShort(garminMessage.getId());
+        writer.writeShort(messageSize);
+        writer.writeShort(crc);
+        writer.writeShort(dataOffset);
+        writer.writeBytes(chunk);
+        return sendOutgoing;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/NotificationSubscriptionMessage.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/NotificationSubscriptionMessage.java
new file mode 100644
index 0000000..792c08e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/NotificationSubscriptionMessage.java
@@ -0,0 +1,42 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages;
+
+import java.util.Collections;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.deviceevents.NotificationSubscriptionDeviceEvent;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.status.NotificationSubscriptionStatusMessage;
+
+public class NotificationSubscriptionMessage extends GFDIMessage {
+
+    private final boolean enable;
+    private final int unk;
+
+    public NotificationSubscriptionMessage(GarminMessage garminMessage, boolean enable, int unk) {
+        this.garminMessage = garminMessage;
+        this.enable = enable;
+        this.unk = unk;
+
+        // We do not set the status message here so we can reply with the proper notifications status
+        // from the device event
+    }
+
+    public static NotificationSubscriptionMessage parseIncoming(MessageReader reader, GarminMessage garminMessage) {
+        final boolean enable = reader.readByte() == 1;
+        final int unk = reader.readByte();
+
+        return new NotificationSubscriptionMessage(garminMessage, enable, unk);
+    }
+
+    @Override
+    public List<GBDeviceEvent> getGBDeviceEvent() {
+        NotificationSubscriptionDeviceEvent notificationSubscriptionDeviceEvent = new NotificationSubscriptionDeviceEvent();
+        notificationSubscriptionDeviceEvent.enable = this.enable;
+        return Collections.singletonList(notificationSubscriptionDeviceEvent);
+    }
+
+    @Override
+    protected boolean generateOutgoing() {
+        return false;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/NotificationUpdateMessage.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/NotificationUpdateMessage.java
new file mode 100644
index 0000000..1596660
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/NotificationUpdateMessage.java
@@ -0,0 +1,140 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages;
+
+import org.apache.commons.lang3.EnumUtils;
+
+import java.util.EnumSet;
+
+import nodomain.freeyourgadget.gadgetbridge.model.NotificationType;
+
+public class NotificationUpdateMessage extends GFDIMessage {
+
+    final private NotificationUpdateType notificationUpdateType;
+    final private NotificationType notificationType;
+    final private int count; //how many notifications of the same type are present
+    final private int notificationId;
+    final private boolean hasActions;
+    final private boolean hasPicture;
+    final private boolean useLegacyActions = false;
+
+    public NotificationUpdateMessage(NotificationUpdateType notificationUpdateType, NotificationType notificationType, int count, int notificationId, boolean hasActions, boolean hasPicture) {
+        this.garminMessage = GarminMessage.NOTIFICATION_UPDATE;
+        this.notificationUpdateType = notificationUpdateType;
+        this.notificationType = notificationType;
+        this.count = count;
+        this.notificationId = notificationId;
+        this.hasActions = hasActions;
+        this.hasPicture = hasPicture;
+    }
+
+    @Override
+    protected boolean generateOutgoing() {
+        final MessageWriter writer = new MessageWriter(response);
+        writer.writeShort(0); // packet size will be filled below
+        writer.writeShort(this.garminMessage.getId());
+        writer.writeByte(this.notificationUpdateType.ordinal());
+        writer.writeByte(getCategoryFlags(this.notificationType));
+        writer.writeByte(getCategoryValue(this.notificationType));
+        writer.writeByte(this.count);
+        writer.writeInt(this.notificationId);
+        writer.writeByte(getNotificationPhoneFlags());
+
+        return true;
+    }
+
+    private int getNotificationPhoneFlags() {
+        EnumSet<NotificationPhoneFlags> flags = EnumSet.noneOf(NotificationPhoneFlags.class);
+        if (this.hasActions)
+            flags.add(NotificationPhoneFlags.NEW_ACTIONS);
+        if (this.useLegacyActions)
+            flags.add(NotificationPhoneFlags.LEGACY_ACTIONS);
+        if (this.hasPicture)
+            flags.add(NotificationPhoneFlags.HAS_ATTACHMENTS);
+
+        return (int) EnumUtils.generateBitVector(NotificationPhoneFlags.class, flags);
+
+    }
+
+    private int getCategoryFlags(NotificationType notificationType) {
+        EnumSet<NotificationFlag> flags = EnumSet.noneOf(NotificationFlag.class);
+        if (this.hasActions && this.useLegacyActions) { //only needed for legacy actions
+            flags.add(NotificationFlag.ACTION_ACCEPT);
+        }
+        flags.add(NotificationFlag.ACTION_DECLINE);
+
+        switch (notificationType.getGenericType()) {
+            case "generic_phone":
+            case "generic_email":
+            case "generic_sms":
+            case "generic_chat":
+                flags.add(NotificationFlag.FOREGROUND);
+                break;
+            case "generic_navigation":
+            case "generic_social":
+            case "generic_alarm_clock":
+            case "generic":
+                // TODO: Maybe make this configurable, but most users expect all notifications
+                // to be foreground, sending them as background was generating bug reports.
+                flags.add(NotificationFlag.FOREGROUND);
+        }
+        return (int) EnumUtils.generateBitVector(NotificationFlag.class, flags);
+    }
+
+    private int getCategoryValue(NotificationType notificationType) {
+        switch (notificationType.getGenericType()) {
+            case "generic_phone":
+                return NotificationCategory.INCOMING_CALL.ordinal();
+            case "generic_email":
+                return NotificationCategory.EMAIL.ordinal();
+            case "generic_sms":
+            case "generic_chat":
+                return NotificationCategory.SMS.ordinal();
+            case "generic_navigation":
+                return NotificationCategory.LOCATION.ordinal();
+            case "generic_social":
+                return NotificationCategory.SOCIAL.ordinal();
+            case "generic_alarm_clock":
+            case "generic":
+                return NotificationCategory.OTHER.ordinal();
+        }
+        return NotificationCategory.OTHER.ordinal();
+    }
+
+    public enum NotificationUpdateType {
+        ADD,
+        MODIFY,
+        REMOVE,
+    }
+
+    enum NotificationFlag { //was AncsEventFlag
+        BACKGROUND,
+        FOREGROUND,
+        UNK,
+        ACTION_ACCEPT, //only needed for legacy actions
+        ACTION_DECLINE, //only needed for legacy actions
+
+    }
+
+    enum NotificationCategory { //was AncsCategory
+        OTHER,
+        INCOMING_CALL,
+        MISSED_CALL,
+        VOICEMAIL,
+        SOCIAL,
+        SCHEDULE,
+        EMAIL,
+        NEWS,
+        HEALTH_AND_FITNESS,
+        BUSINESS_AND_FINANCE,
+        LOCATION,
+        ENTERTAINMENT,
+        SMS
+
+    }
+
+    enum NotificationPhoneFlags {
+        LEGACY_ACTIONS,
+        NEW_ACTIONS,
+        HAS_ATTACHMENTS,
+        ;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/ProtobufMessage.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/ProtobufMessage.java
new file mode 100644
index 0000000..530c995
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/ProtobufMessage.java
@@ -0,0 +1,95 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages;
+
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.status.GenericStatusMessage;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.status.ProtobufStatusMessage;
+
+import static nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.status.ProtobufStatusMessage.ProtobufChunkStatus.KEPT;
+import static nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.status.ProtobufStatusMessage.ProtobufStatusCode.NO_ERROR;
+
+public class ProtobufMessage extends GFDIMessage {
+
+
+    private final int requestId;
+    private final int dataOffset;
+    private final int totalProtobufLength;
+    private final int protobufDataLength;
+    private final byte[] messageBytes;
+    private final boolean sendOutgoing;
+
+    public ProtobufMessage(GarminMessage garminMessage, int requestId, int dataOffset, int totalProtobufLength, int protobufDataLength, byte[] messageBytes, boolean sendOutgoing) {
+        this.garminMessage = garminMessage;
+        this.requestId = requestId;
+        this.dataOffset = dataOffset;
+        this.totalProtobufLength = totalProtobufLength;
+        this.protobufDataLength = protobufDataLength;
+        this.messageBytes = messageBytes;
+        this.sendOutgoing = sendOutgoing;
+
+        if (isComplete()) {
+            this.statusMessage = new GenericStatusMessage(garminMessage, GFDIMessage.Status.ACK);
+        } else {
+            this.statusMessage = new ProtobufStatusMessage(garminMessage, GFDIMessage.Status.ACK, requestId, dataOffset, KEPT, NO_ERROR);
+        }
+    }
+
+    public ProtobufMessage(GarminMessage garminMessage, int requestId, int dataOffset, int totalProtobufLength, int protobufDataLength, byte[] messageBytes) {
+        this(garminMessage, requestId, dataOffset, totalProtobufLength, protobufDataLength, messageBytes, true);
+    }
+
+    public void setStatusMessage(ProtobufStatusMessage protobufStatusMessage) {
+        this.statusMessage = protobufStatusMessage;
+    }
+
+    public static ProtobufMessage parseIncoming(MessageReader reader, GarminMessage garminMessage) {
+        final int requestID = reader.readShort();
+        final int dataOffset = reader.readInt();
+        final int totalProtobufLength = reader.readInt();
+        final int protobufDataLength = reader.readInt();
+        final byte[] messageBytes = reader.readBytes(protobufDataLength);
+
+        return new ProtobufMessage(garminMessage, requestID, dataOffset, totalProtobufLength, protobufDataLength, messageBytes, false);
+    }
+
+    public int getRequestId() {
+        return requestId;
+    }
+
+    public GarminMessage getMessageType() {
+        return garminMessage;
+    }
+
+    public int getDataOffset() {
+        return dataOffset;
+    }
+
+    public int getTotalProtobufLength() {
+        return totalProtobufLength;
+    }
+
+    public byte[] getMessageBytes() {
+        return messageBytes;
+    }
+
+    public boolean isChunked() {
+        return (totalProtobufLength != protobufDataLength);
+    }
+
+    public boolean isComplete() {
+        return (dataOffset == 0 && !isChunked());
+    }
+
+    @Override
+    protected boolean generateOutgoing() {
+        final MessageWriter writer = new MessageWriter(response);
+        writer.writeShort(0); // packet size will be filled below
+        writer.writeShort(garminMessage.getId());
+        writer.writeShort(requestId);
+        writer.writeInt(dataOffset);
+        writer.writeInt(totalProtobufLength);
+        writer.writeInt(protobufDataLength);
+        writer.writeBytes(messageBytes);
+        return sendOutgoing;
+    }
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/SetDeviceSettingsMessage.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/SetDeviceSettingsMessage.java
new file mode 100644
index 0000000..46ef639
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/SetDeviceSettingsMessage.java
@@ -0,0 +1,53 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages;
+
+import java.util.Map;
+
+public class SetDeviceSettingsMessage extends GFDIMessage {
+    private final Map<GarminDeviceSetting, Object> settings;
+
+    public SetDeviceSettingsMessage(Map<GarminDeviceSetting, Object> settings) {
+        this.garminMessage = GarminMessage.DEVICE_SETTINGS;
+        this.settings = settings;
+        final int settingsCount = settings.size();
+        if (settingsCount == 0) throw new IllegalArgumentException("Empty settings");
+        if (settingsCount > 255) throw new IllegalArgumentException("Too many settings");
+
+    }
+
+    @Override
+    protected boolean generateOutgoing() {
+        final MessageWriter writer = new MessageWriter(response);
+        writer.writeShort(0); // packet size will be filled below
+        writer.writeShort(this.garminMessage.getId());
+        writer.writeByte(settings.size());
+        for (Map.Entry<GarminDeviceSetting, Object> settingPair : settings.entrySet()) {
+            final GarminDeviceSetting setting = settingPair.getKey();
+            writer.writeByte(setting.ordinal());
+            final Object value = settingPair.getValue();
+            if (value instanceof String) {
+                writer.writeString((String) value);
+            } else if (value instanceof Integer) {
+                writer.writeByte(4);
+                writer.writeInt((Integer) value);
+            } else if (value instanceof Boolean) {
+                writer.writeByte(1);
+                writer.writeByte(Boolean.TRUE.equals(value) ? 1 : 0);
+            } else {
+                throw new IllegalArgumentException("Unsupported setting value type " + value);
+            }
+        }
+        return true;
+    }
+
+    public enum GarminDeviceSetting {
+        DEVICE_NAME,
+        CURRENT_TIME,
+        DAYLIGHT_SAVINGS_TIME_OFFSET,
+        TIME_ZONE_OFFSET,
+        NEXT_DAYLIGHT_SAVINGS_START,
+        NEXT_DAYLIGHT_SAVINGS_END,
+        AUTO_UPLOAD_ENABLED,
+        WEATHER_CONDITIONS_ENABLED,
+        WEATHER_ALERTS_ENABLED
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/SetFileFlagsMessage.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/SetFileFlagsMessage.java
new file mode 100644
index 0000000..0f2da90
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/SetFileFlagsMessage.java
@@ -0,0 +1,41 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages;
+
+import org.apache.commons.lang3.EnumUtils;
+
+import java.util.EnumSet;
+
+public class SetFileFlagsMessage extends GFDIMessage {
+
+    private final int fileIndex;
+    private final FileFlags flags;
+
+    public SetFileFlagsMessage(int fileIndex, FileFlags flags) {
+        this.garminMessage = GarminMessage.SET_FILE_FLAG;
+        this.fileIndex = fileIndex;
+        this.flags = flags;
+    }
+
+    @Override
+    protected boolean generateOutgoing() {
+        final MessageWriter writer = new MessageWriter(response);
+        writer.writeShort(0); // packet size will be filled below
+        writer.writeShort(this.garminMessage.getId());
+        writer.writeShort(this.fileIndex);
+        writer.writeByte((int) EnumUtils.generateBitVector(FileFlags.class, this.flags));
+        return true;
+    }
+
+    public enum FileFlags {
+        UNK_00000001,
+        UNK_00000010,
+        UNK_00000100,
+        UNK_00001000,
+        ARCHIVE,
+        ;
+
+        public static EnumSet<FileFlags> fromBitMask(final int code) {
+            return EnumUtils.processBitVector(FileFlags.class, code);
+        }
+
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/SupportedFileTypesMessage.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/SupportedFileTypesMessage.java
new file mode 100644
index 0000000..2018aee
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/SupportedFileTypesMessage.java
@@ -0,0 +1,16 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages;
+
+public class SupportedFileTypesMessage extends GFDIMessage {
+
+    public SupportedFileTypesMessage() {
+        this.garminMessage = GarminMessage.SUPPORTED_FILE_TYPES_REQUEST;
+    }
+
+    @Override
+    protected boolean generateOutgoing() {
+        final MessageWriter writer = new MessageWriter(response);
+        writer.writeShort(0); // packet size will be filled below
+        writer.writeShort(this.garminMessage.getId());
+        return true;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/SystemEventMessage.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/SystemEventMessage.java
new file mode 100644
index 0000000..4f43432
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/SystemEventMessage.java
@@ -0,0 +1,49 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages;
+
+public class SystemEventMessage extends GFDIMessage {
+
+    private final GarminSystemEventType eventType;
+    private final Object value;
+
+    public SystemEventMessage(GarminSystemEventType eventType, Object value) {
+        this.eventType = eventType;
+        this.value = value;
+        this.garminMessage = GarminMessage.SYSTEM_EVENT;
+    }
+
+    @Override
+    protected boolean generateOutgoing() {
+        final MessageWriter writer = new MessageWriter(response);
+        writer.writeShort(0); // packet size will be filled below
+        writer.writeShort(this.garminMessage.getId());
+        writer.writeByte(eventType.ordinal());
+        if (value instanceof String) {
+            writer.writeString((String) value);
+        } else if (value instanceof Integer) {
+            writer.writeByte((Integer) value);
+        } else {
+            throw new IllegalArgumentException("Unsupported event value type " + value);
+        }
+        return true;
+    }
+
+    public enum GarminSystemEventType {
+        SYNC_COMPLETE,
+        SYNC_FAIL,
+        FACTORY_RESET,
+        PAIR_START,
+        PAIR_COMPLETE,
+        PAIR_FAIL,
+        HOST_DID_ENTER_FOREGROUND,
+        HOST_DID_ENTER_BACKGROUND,
+        SYNC_READY,
+        NEW_DOWNLOAD_AVAILABLE,
+        DEVICE_SOFTWARE_UPDATE,
+        DEVICE_DISCONNECT,
+        TUTORIAL_COMPLETE,
+        SETUP_WIZARD_START,
+        SETUP_WIZARD_COMPLETE,
+        SETUP_WIZARD_SKIPPED,
+        TIME_UPDATED
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/UnhandledMessage.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/UnhandledMessage.java
new file mode 100644
index 0000000..91a8bec
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/UnhandledMessage.java
@@ -0,0 +1,20 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.status.GenericStatusMessage;
+
+public class UnhandledMessage extends GFDIMessage {
+
+    private final int messageType;
+
+    public UnhandledMessage(int messageType) {
+        this.messageType = messageType;
+
+        this.statusMessage = new GenericStatusMessage(messageType, Status.UNSUPPORTED);
+
+    }
+
+    @Override
+    protected boolean generateOutgoing() {
+        return false;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/UploadRequestMessage.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/UploadRequestMessage.java
new file mode 100644
index 0000000..c10588d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/UploadRequestMessage.java
@@ -0,0 +1,45 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages;
+
+public class UploadRequestMessage extends GFDIMessage {
+
+    private final int fileIndex;
+    private final int size;
+    private final boolean generateOutgoing;
+    private final int dataOffset;
+    private final int crcSeed;
+
+
+    public UploadRequestMessage(GarminMessage garminMessage, int fileIndex, int size, int dataOffset, int crcSeed) {
+        this.garminMessage = garminMessage;
+        this.fileIndex = fileIndex;
+        this.size = size;
+        this.dataOffset = dataOffset;
+        this.crcSeed = crcSeed;
+        this.statusMessage = this.getStatusMessage();
+        this.generateOutgoing = false;
+    }
+
+    public UploadRequestMessage(int fileIndex, int size) {
+        this.garminMessage = GarminMessage.UPLOAD_REQUEST;
+        this.fileIndex = fileIndex;
+        this.size = size;
+        this.dataOffset = 0;
+        this.crcSeed = 0;
+        this.statusMessage = this.getStatusMessage();
+        this.generateOutgoing = true;
+    }
+
+    @Override
+    protected boolean generateOutgoing() {
+        final MessageWriter writer = new MessageWriter(response);
+        writer.writeShort(0); // packet size will be filled below
+        writer.writeShort(this.garminMessage.getId());
+        writer.writeShort(this.fileIndex);
+        writer.writeInt(this.size);
+        writer.writeInt(this.dataOffset);
+        writer.writeShort(this.crcSeed);
+
+        return generateOutgoing;
+    }
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/WeatherMessage.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/WeatherMessage.java
new file mode 100644
index 0000000..f7b9cf5
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/WeatherMessage.java
@@ -0,0 +1,37 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages;
+
+import java.util.Collections;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.deviceevents.WeatherRequestDeviceEvent;
+
+public class WeatherMessage extends GFDIMessage {
+    private final WeatherRequestDeviceEvent weatherRequestDeviceEvent;
+    public WeatherMessage(int format, int latitude, int longitude, int hoursOfForecast, GarminMessage garminMessage) {
+
+        this.garminMessage = garminMessage;
+        weatherRequestDeviceEvent = new WeatherRequestDeviceEvent(format, latitude, longitude, hoursOfForecast);
+        this.statusMessage = this.getStatusMessage();
+
+    }
+
+    public static WeatherMessage parseIncoming(MessageReader reader, GarminMessage garminMessage) {
+        final int format = reader.readByte();
+        final int latitude = reader.readInt();
+        final int longitude = reader.readInt();
+        final int hoursOfForecast = reader.readByte();
+
+        return new WeatherMessage(format, latitude, longitude, hoursOfForecast, garminMessage);
+    }
+
+    public List<GBDeviceEvent> getGBDeviceEvent() {
+        return Collections.singletonList(weatherRequestDeviceEvent);
+    }
+
+    @Override
+    protected boolean generateOutgoing() {
+        return false;
+    }
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/status/AuthNegotiationStatusMessage.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/status/AuthNegotiationStatusMessage.java
new file mode 100644
index 0000000..cc4bf47
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/status/AuthNegotiationStatusMessage.java
@@ -0,0 +1,83 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.status;
+
+import androidx.annotation.Nullable;
+
+import org.apache.commons.lang3.EnumUtils;
+
+import java.util.EnumSet;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.AuthNegotiationMessage;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.MessageWriter;
+
+public class AuthNegotiationStatusMessage extends GFDIStatusMessage {
+
+    private final Status status;
+    private final AuthNegotiationStatus authNegotiationStatus;
+    private final int unknown;
+    private final EnumSet<AuthNegotiationMessage.AuthFlags> authFlags;
+    private final boolean sendOutgoing;
+
+    public AuthNegotiationStatusMessage(GarminMessage garminMessage, Status status, AuthNegotiationStatus authNegotiationStatus, int unknown, EnumSet<AuthNegotiationMessage.AuthFlags> authFlags) {
+        this(garminMessage, status, authNegotiationStatus, unknown, authFlags, true);
+    }
+
+    public AuthNegotiationStatusMessage(GarminMessage garminMessage, Status status, AuthNegotiationStatus authNegotiationStatus, int unknown, EnumSet<AuthNegotiationMessage.AuthFlags> authFlags, boolean sendOutgoing) {
+        this.garminMessage = garminMessage;
+        this.status = status;
+        this.authNegotiationStatus = authNegotiationStatus;
+        this.unknown = unknown;
+        this.authFlags = authFlags;
+        this.sendOutgoing = sendOutgoing;
+    }
+
+    public static AuthNegotiationStatusMessage parseIncoming(MessageReader reader, GarminMessage garminMessage) {
+        final Status status = Status.fromCode(reader.readByte());
+        final int authNegotiationStatusCode = reader.readByte();
+        final AuthNegotiationStatus authNegotiationStatus = AuthNegotiationStatus.fromCode(authNegotiationStatusCode);
+        if (null == authNegotiationStatus) {
+            LOG.warn("Unknown auth negotiation status code {}", authNegotiationStatusCode);
+            return null;
+        }
+        final int unk = reader.readByte();
+        final EnumSet<AuthNegotiationMessage.AuthFlags> authFlags = AuthNegotiationMessage.AuthFlags.fromBitMask(reader.readInt());
+
+        switch (authNegotiationStatus) {
+            case GUESS_OK:
+                LOG.info("Received {}/{} for message {} unkByte: {}, flags: {}", status, authNegotiationStatus, garminMessage, unk, authFlags);
+                break;
+            default:
+                LOG.warn("Received {}/{} for message {} unkByte: {}, flags: {}", status, authNegotiationStatus, garminMessage, unk, authFlags);
+        }
+        return new AuthNegotiationStatusMessage(garminMessage, status, authNegotiationStatus, unk, authFlags, false);
+    }
+
+    @Override
+    protected boolean generateOutgoing() {
+        final MessageWriter writer = new MessageWriter(response);
+        writer.writeShort(0); // packet size will be filled below
+        writer.writeShort(GarminMessage.RESPONSE.getId());
+        writer.writeShort(garminMessage.getId());
+        writer.writeByte(status.ordinal());
+        writer.writeByte(authNegotiationStatus.ordinal());
+        writer.writeByte(unknown);
+        writer.writeInt((int) EnumUtils.generateBitVector(AuthNegotiationMessage.AuthFlags.class, authFlags));
+
+        return sendOutgoing;
+    }
+
+    public enum AuthNegotiationStatus {
+        GUESS_OK,
+        GUESS_KO,
+        ;
+
+        @Nullable
+        public static AuthNegotiationStatus fromCode(final int code) {
+            for (final AuthNegotiationStatus authNegotiationStatus : AuthNegotiationStatus.values()) {
+                if (authNegotiationStatus.ordinal() == code) {
+                    return authNegotiationStatus;
+                }
+            }
+            return null;
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/status/CreateFileStatusMessage.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/status/CreateFileStatusMessage.java
new file mode 100644
index 0000000..cdc4204
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/status/CreateFileStatusMessage.java
@@ -0,0 +1,72 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.status;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.FileType;
+
+public class CreateFileStatusMessage extends GFDIStatusMessage {
+    private final Status status;
+    private final CreateStatus createStatus;
+    private final FileType.FILETYPE filetype;
+    private final int fileIndex;
+    private final int fileNumber;
+
+    public CreateFileStatusMessage(GarminMessage garminMessage, Status status, CreateStatus createStatus, int fileIndex, FileType.FILETYPE filetype, int fileNumber) {
+        this.garminMessage = garminMessage;
+        this.status = status;
+        this.createStatus = createStatus;
+        this.fileIndex = fileIndex;
+        this.filetype = filetype;
+        this.fileNumber = fileNumber;
+    }
+
+    public static CreateFileStatusMessage parseIncoming(MessageReader reader, GarminMessage garminMessage) {
+        final Status status = Status.fromCode(reader.readByte());
+
+        if (!status.equals(Status.ACK)) {
+            return null;
+        }
+        final CreateStatus createStatus = CreateStatus.fromId(reader.readByte());
+        int fileIndex = reader.readShort();
+        final int dataType = reader.readByte();
+        final int subType = reader.readByte();
+        final FileType.FILETYPE filetype = FileType.FILETYPE.fromDataTypeSubType(dataType, subType);
+        final int fileNumber = reader.readShort();
+        if (!CreateStatus.OK.equals(createStatus)) {
+            LOG.warn("Received {} / {} for message {}", status, createStatus, garminMessage);
+        } else {
+            LOG.info("Received {} / {} for message {}", status, createStatus, garminMessage);
+        }
+        return new CreateFileStatusMessage(garminMessage, status, createStatus, fileIndex, filetype, fileNumber);
+    }
+
+    public int getFileIndex() {
+        return fileIndex;
+    }
+
+    public int getFileNumber() {
+        return fileNumber;
+    }
+
+    public boolean canProceed() {
+        return status.equals(Status.ACK) && createStatus.equals(CreateStatus.OK);
+    }
+
+    public enum CreateStatus {
+        OK,
+        DUPLICATE,
+        NO_SPACE,
+        UNSUPPORTED,
+        NO_SLOTS,
+        NO_SPACE_FOR_TYPE,
+        ;
+
+        public static CreateStatus fromId(int id) {
+            for (CreateStatus createStatus :
+                    CreateStatus.values()) {
+                if (createStatus.ordinal() == id) {
+                    return createStatus;
+                }
+            }
+            return null;
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/status/DownloadRequestStatusMessage.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/status/DownloadRequestStatusMessage.java
new file mode 100644
index 0000000..365823e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/status/DownloadRequestStatusMessage.java
@@ -0,0 +1,60 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.status;
+
+public class DownloadRequestStatusMessage extends GFDIStatusMessage {
+    private final Status status;
+    private final DownloadStatus downloadStatus;
+    private final int maxFileSize;
+
+    public DownloadRequestStatusMessage(GarminMessage garminMessage, Status status, DownloadStatus downloadStatus, int maxFileSize) {
+        this.garminMessage = garminMessage;
+        this.status = status;
+        this.downloadStatus = downloadStatus;
+        this.maxFileSize = maxFileSize;
+    }
+
+    public static DownloadRequestStatusMessage parseIncoming(MessageReader reader, GarminMessage garminMessage) {
+        final Status status = Status.fromCode(reader.readByte());
+
+        if (!status.equals(Status.ACK)) {
+            return null;
+        }
+        final DownloadStatus downloadStatus = DownloadStatus.fromId(reader.readByte());
+        final int maxFileSize = reader.readInt();
+
+        if (!DownloadStatus.OK.equals(downloadStatus)) {
+            LOG.warn("Received {} / {} for message {}", status, downloadStatus, garminMessage);
+        } else {
+            LOG.info("Received {} / {} for message {}", status, downloadStatus, garminMessage);
+        }
+        return new DownloadRequestStatusMessage(garminMessage, status, downloadStatus, maxFileSize);
+    }
+
+    public int getMaxFileSize() {
+        return maxFileSize;
+    }
+
+    public boolean canProceed() {
+        return status.equals(Status.ACK) && downloadStatus.equals(DownloadStatus.OK);
+    }
+
+    public enum DownloadStatus { //was DownloadRequestResponseMessage
+        OK,
+        INDEX_UNKNOWN,
+        INDEX_NOT_READABLE,
+        NO_SPACE_LEFT,
+        INVALID,
+        NOT_READY,
+        CRC_INCORRECT,
+        ;
+
+        public static DownloadStatus fromId(int id) {
+            for (DownloadStatus downloadStatus :
+                    DownloadStatus.values()) {
+                if (downloadStatus.ordinal() == id) {
+                    return downloadStatus;
+                }
+            }
+            return null;
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/status/FileTransferDataStatusMessage.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/status/FileTransferDataStatusMessage.java
new file mode 100644
index 0000000..ee4e9ca
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/status/FileTransferDataStatusMessage.java
@@ -0,0 +1,80 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.status;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.MessageWriter;
+
+public class FileTransferDataStatusMessage extends GFDIStatusMessage {
+    private final Status status;
+    private final TransferStatus transferStatus;
+    private final int dataOffset;
+    private final boolean sendOutgoing;
+    public FileTransferDataStatusMessage(GarminMessage garminMessage, Status status, TransferStatus transferStatus, int dataOffset) {
+        this(garminMessage, status, transferStatus, dataOffset, true);
+    }
+
+
+    public FileTransferDataStatusMessage(GarminMessage garminMessage, Status status, TransferStatus transferStatus, int dataOffset, boolean sendOutgoing) {
+        this.garminMessage = garminMessage;
+        this.status = status;
+        this.transferStatus = transferStatus;
+        this.dataOffset = dataOffset;
+        this.sendOutgoing = sendOutgoing;
+    }
+
+    public static FileTransferDataStatusMessage parseIncoming(MessageReader reader, GarminMessage garminMessage) {
+        final Status status = Status.fromCode(reader.readByte());
+
+        if (!status.equals(Status.ACK)) {
+            return null;
+        }
+        final TransferStatus transferStatus = TransferStatus.fromId(reader.readByte());
+        final int dataOffset = reader.readInt();
+
+        if (!TransferStatus.OK.equals(transferStatus)) {
+            LOG.warn("Received {} / {} for message {}", status, transferStatus, garminMessage);
+        } else {
+            LOG.info("Received {} / {} for message {}", status, transferStatus, garminMessage);
+        }
+        return new FileTransferDataStatusMessage(garminMessage, status, transferStatus, dataOffset, false);
+    }
+
+    public int getDataOffset() {
+        return dataOffset;
+    }
+
+    public boolean canProceed() {
+        return status.equals(Status.ACK) && transferStatus.equals(TransferStatus.OK);
+    }
+
+    @Override
+    protected boolean generateOutgoing() {
+        final MessageWriter writer = new MessageWriter(response);
+        writer.writeShort(0); // packet size will be filled below
+        writer.writeShort(GarminMessage.RESPONSE.getId());
+        writer.writeShort(garminMessage.getId());
+        writer.writeByte(status.ordinal());
+        writer.writeByte(transferStatus.ordinal());
+        writer.writeInt(dataOffset);
+
+        return sendOutgoing;
+    }
+
+    public enum TransferStatus {
+        OK,
+        RESEND,
+        ABORT,
+        CRC_MISMATCH,
+        OFFSET_MISMATCH,
+        SYNC_PAUSED,
+        ;
+
+        public static TransferStatus fromId(int id) {
+            for (TransferStatus transferStatus :
+                    TransferStatus.values()) {
+                if (transferStatus.ordinal() == id) {
+                    return transferStatus;
+                }
+            }
+            return null;
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/status/FitDataStatusMessage.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/status/FitDataStatusMessage.java
new file mode 100644
index 0000000..f015662
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/status/FitDataStatusMessage.java
@@ -0,0 +1,51 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.status;
+
+import androidx.annotation.Nullable;
+
+public class FitDataStatusMessage extends GFDIStatusMessage {
+
+    private final Status status;
+    private final FitDataStatusCode fitDataStatusCode;
+
+    public FitDataStatusMessage(GarminMessage garminMessage, Status status, FitDataStatusCode fitDataStatusCode) {
+        this.garminMessage = garminMessage;
+        this.status = status;
+        this.fitDataStatusCode = fitDataStatusCode;
+        switch (fitDataStatusCode) {
+            case APPLIED:
+                LOG.info("FIT DATA RETURNED STATUS: {}", fitDataStatusCode.name());
+                break;
+            default:
+                LOG.warn("FIT DATA RETURNED STATUS: {}", fitDataStatusCode.name());
+        }
+    }
+
+    public static FitDataStatusMessage parseIncoming(MessageReader reader, GarminMessage garminMessage) {
+        final Status status = Status.fromCode(reader.readByte());
+        final int fitDataStatusCodeByte = reader.readByte();
+        final FitDataStatusCode fitDataStatusCode = FitDataStatusCode.fromCode(fitDataStatusCodeByte);
+        if (fitDataStatusCode == null) {
+            LOG.warn("Unknown fit data status code {}", fitDataStatusCodeByte);
+            return null;
+        }
+        return new FitDataStatusMessage(garminMessage, status, fitDataStatusCode);
+    }
+
+    public enum FitDataStatusCode {
+        APPLIED,
+        NO_DEFINITION,
+        MISMATCH,
+        NOT_READY,
+        ;
+
+        @Nullable
+        public static FitDataStatusCode fromCode(final int code) {
+            for (final FitDataStatusCode fitDataStatusCode : FitDataStatusCode.values()) {
+                if (fitDataStatusCode.ordinal() == code) {
+                    return fitDataStatusCode;
+                }
+            }
+            return null;
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/status/FitDefinitionStatusMessage.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/status/FitDefinitionStatusMessage.java
new file mode 100644
index 0000000..2b2cb31
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/status/FitDefinitionStatusMessage.java
@@ -0,0 +1,51 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.status;
+
+import androidx.annotation.Nullable;
+
+public class FitDefinitionStatusMessage extends GFDIStatusMessage {
+
+    private final Status status;
+    private final FitDefinitionStatusCode fitDefinitionStatusCode;
+
+    public FitDefinitionStatusMessage(GarminMessage garminMessage, Status status, FitDefinitionStatusCode fitDefinitionStatusCode) {
+        this.garminMessage = garminMessage;
+        this.status = status;
+        this.fitDefinitionStatusCode = fitDefinitionStatusCode;
+        switch (fitDefinitionStatusCode) {
+            case APPLIED:
+                LOG.info("FIT DEFINITION RETURNED STATUS: {}", fitDefinitionStatusCode.name());
+                break;
+            default:
+                LOG.warn("FIT DEFINITION RETURNED STATUS: {}", fitDefinitionStatusCode.name());
+        }
+    }
+
+    public static FitDefinitionStatusMessage parseIncoming(MessageReader reader, GarminMessage garminMessage) {
+        final Status status = Status.fromCode(reader.readByte());
+        final int fitDefinitionStatusCodeByte = reader.readByte();
+        final FitDefinitionStatusCode fitDefinitionStatusCode = FitDefinitionStatusCode.fromCode(fitDefinitionStatusCodeByte);
+        if (fitDefinitionStatusCode == null) {
+            LOG.warn("Unknown fit definition status code {}", fitDefinitionStatusCodeByte);
+            return null;
+        }
+        return new FitDefinitionStatusMessage(garminMessage, status, fitDefinitionStatusCode);
+    }
+
+    public enum FitDefinitionStatusCode {
+        APPLIED,
+        NOT_UNIQUE,
+        OUT_OF_RANGE,
+        NOT_READY,
+        ;
+
+        @Nullable
+        public static FitDefinitionStatusCode fromCode(final int code) {
+            for (final FitDefinitionStatusCode fitDefinitionStatusCode : FitDefinitionStatusCode.values()) {
+                if (fitDefinitionStatusCode.ordinal() == code) {
+                    return fitDefinitionStatusCode;
+                }
+            }
+            return null;
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/status/GFDIStatusMessage.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/status/GFDIStatusMessage.java
new file mode 100644
index 0000000..4559dbb
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/status/GFDIStatusMessage.java
@@ -0,0 +1,58 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.status;
+
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.GFDIMessage;
+
+public abstract class GFDIStatusMessage extends GFDIMessage {
+    private Status status;
+
+    public static GFDIStatusMessage parseIncoming(MessageReader reader, GarminMessage garminMessage) {
+        int originalMessageType = reader.readShort();
+        final GarminMessage originalGarminMessage = GFDIMessage.GarminMessage.fromId(originalMessageType);
+
+        if (GarminMessage.PROTOBUF_REQUEST.equals(originalGarminMessage) || GarminMessage.PROTOBUF_RESPONSE.equals(originalGarminMessage)) {
+            return ProtobufStatusMessage.parseIncoming(reader, originalGarminMessage);
+        } else if (GarminMessage.NOTIFICATION_DATA.equals(originalGarminMessage)) {
+            return NotificationDataStatusMessage.parseIncoming(reader, originalGarminMessage);
+        } else if (GarminMessage.UPLOAD_REQUEST.equals(originalGarminMessage)) {
+            return UploadRequestStatusMessage.parseIncoming(reader, originalGarminMessage);
+        } else if (GarminMessage.DOWNLOAD_REQUEST.equals(originalGarminMessage)) {
+            return DownloadRequestStatusMessage.parseIncoming(reader, originalGarminMessage);
+        } else if (GarminMessage.FILE_TRANSFER_DATA.equals(originalGarminMessage)) {
+            return FileTransferDataStatusMessage.parseIncoming(reader, originalGarminMessage);
+        } else if (GarminMessage.CREATE_FILE.equals(originalGarminMessage)) {
+            return CreateFileStatusMessage.parseIncoming(reader, originalGarminMessage);
+        } else if (GarminMessage.SUPPORTED_FILE_TYPES_REQUEST.equals(originalGarminMessage)) {
+            SupportedFileTypesStatusMessage supportedFileTypesStatusMessage = SupportedFileTypesStatusMessage.parseIncoming(reader, garminMessage);
+            LOG.info("{}", supportedFileTypesStatusMessage);
+            return supportedFileTypesStatusMessage;
+        } else if (GarminMessage.SET_FILE_FLAG.equals(originalGarminMessage)) {
+            return SetFileFlagsStatusMessage.parseIncoming(reader, garminMessage);
+        } else if (GarminMessage.FIT_DEFINITION.equals(originalGarminMessage)) {
+            return FitDefinitionStatusMessage.parseIncoming(reader, originalGarminMessage);
+        } else if (GarminMessage.FIT_DATA.equals(originalGarminMessage)) {
+            return FitDataStatusMessage.parseIncoming(reader, originalGarminMessage);
+        } else if (GarminMessage.AUTH_NEGOTIATION.equals(originalGarminMessage)) {
+            return AuthNegotiationStatusMessage.parseIncoming(reader, originalGarminMessage);
+        } else {
+            final Status status = Status.fromCode(reader.readByte());
+
+            if (Status.ACK == status) {
+                LOG.info("Received ACK for message {}", originalGarminMessage);
+            } else {
+                LOG.warn("Received {} for message {}", status, (null == originalGarminMessage) ? originalMessageType : originalGarminMessage.name());
+            }
+
+            return new GenericStatusMessage(originalGarminMessage, status, false); //don't ack the ack
+        }
+    }
+
+    @Override
+    protected boolean generateOutgoing() {
+        return false;
+    }
+
+    protected Status getStatus() {
+        return status;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/status/GenericStatusMessage.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/status/GenericStatusMessage.java
new file mode 100644
index 0000000..7df509c
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/status/GenericStatusMessage.java
@@ -0,0 +1,39 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.status;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.MessageWriter;
+
+public class GenericStatusMessage extends GFDIStatusMessage {
+
+    private final Status status;
+    private int messageType; // for unsupported message types
+    private boolean sendOutgoing;
+
+    public GenericStatusMessage(GarminMessage originalMessage, Status status) {
+        this.garminMessage = originalMessage;
+        this.status = status;
+        this.sendOutgoing = true;
+    }
+
+    public GenericStatusMessage(GarminMessage originalMessage, Status status, boolean sendOutgoing) {
+        this.garminMessage = originalMessage;
+        this.status = status;
+        this.sendOutgoing = sendOutgoing;
+    }
+
+    public GenericStatusMessage(int messageType, Status status) {
+        this.messageType = messageType;
+        this.status = status;
+        this.sendOutgoing = false;
+    }
+
+    @Override
+    protected boolean generateOutgoing() {
+        final MessageWriter writer = new MessageWriter(response);
+        writer.writeShort(0); // packet size will be filled below
+        writer.writeShort(GarminMessage.RESPONSE.getId());
+        writer.writeShort(messageType != 0 ? messageType : garminMessage.getId());
+        writer.writeByte(status.ordinal());
+        return sendOutgoing;
+    }
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/status/NotificationControlStatusMessage.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/status/NotificationControlStatusMessage.java
new file mode 100644
index 0000000..bb9e735
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/status/NotificationControlStatusMessage.java
@@ -0,0 +1,76 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.status;
+
+import androidx.annotation.Nullable;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.MessageWriter;
+
+public class NotificationControlStatusMessage extends GFDIStatusMessage {
+    private final Status status;
+    private final NotificationChunkStatus notificationChunkStatus;
+    private final NotificationStatusCode notificationStatusCode;
+    private final boolean sendOutgoing;
+
+    public NotificationControlStatusMessage(GarminMessage garminMessage, Status status, NotificationChunkStatus notificationChunkStatus, NotificationStatusCode notificationStatusCode) {
+        this.garminMessage = garminMessage;
+        this.status = status;
+        this.notificationChunkStatus = notificationChunkStatus;
+        this.notificationStatusCode = notificationStatusCode;
+        this.sendOutgoing = true;
+    }
+
+
+    @Override
+    protected boolean generateOutgoing() {
+        final MessageWriter writer = new MessageWriter(response);
+        writer.writeShort(0); // packet size will be filled below
+        writer.writeShort(GarminMessage.RESPONSE.getId());
+        writer.writeShort(garminMessage.getId());
+        writer.writeByte(status.ordinal());
+        writer.writeByte(notificationChunkStatus.ordinal());
+        writer.writeByte(notificationStatusCode.getCode());
+
+        return this.sendOutgoing;
+    }
+
+    public enum NotificationChunkStatus {
+        OK,
+        ERROR,
+        ;
+
+        public static NotificationChunkStatus fromId(int id) {
+            for (NotificationChunkStatus notificationChunkStatus :
+                    NotificationChunkStatus.values()) {
+                if (notificationChunkStatus.ordinal() == id) {
+                    return notificationChunkStatus;
+                }
+            }
+            return null;
+        }
+    }
+
+    public enum NotificationStatusCode {
+        NO_ERROR(0),
+        UNKNOWN_COMMAND(160),
+        ;
+
+        private final int code;
+
+        NotificationStatusCode(final int code) {
+            this.code = code;
+        }
+
+        @Nullable
+        public static NotificationStatusCode fromCode(final int code) {
+            for (final NotificationStatusCode notificationStatusCode : NotificationStatusCode.values()) {
+                if (notificationStatusCode.getCode() == code) {
+                    return notificationStatusCode;
+                }
+            }
+            return null;
+        }
+
+        public int getCode() {
+            return code;
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/status/NotificationDataStatusMessage.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/status/NotificationDataStatusMessage.java
new file mode 100644
index 0000000..40c532a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/status/NotificationDataStatusMessage.java
@@ -0,0 +1,51 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.status;
+
+public class NotificationDataStatusMessage extends GFDIStatusMessage {
+    private final Status status;
+    private final TransferStatus transferStatus;
+
+    public NotificationDataStatusMessage(GarminMessage garminMessage, Status status, TransferStatus transferStatus) {
+        this.garminMessage = garminMessage;
+        this.status = status;
+        this.transferStatus = transferStatus;
+    }
+
+    public static NotificationDataStatusMessage parseIncoming(MessageReader reader, GarminMessage garminMessage) {
+        final Status status = Status.fromCode(reader.readByte());
+
+        if (!status.equals(Status.ACK)) {
+            return null;
+        }
+        final TransferStatus transferStatus = TransferStatus.fromId(reader.readByte());
+
+        if (!TransferStatus.OK.equals(transferStatus)) {
+            LOG.warn("Received {} / {} for message {}", status, transferStatus, garminMessage);
+        } else {
+            LOG.info("Received {} / {} for message {}", status, transferStatus, garminMessage);
+        }
+        return new NotificationDataStatusMessage(garminMessage, status, transferStatus);
+    }
+
+    public boolean canProceed() {
+        return status.equals(Status.ACK) && transferStatus.equals(TransferStatus.OK);
+    }
+
+    public enum TransferStatus {
+        OK,
+        RESEND,
+        ABORT,
+        CRC_MISMATCH,
+        OFFSET_MISMATCH,
+        ;
+
+        public static TransferStatus fromId(int id) {
+            for (TransferStatus transferStatus :
+                    TransferStatus.values()) {
+                if (transferStatus.ordinal() == id) {
+                    return transferStatus;
+                }
+            }
+            return null;
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/status/NotificationSubscriptionStatusMessage.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/status/NotificationSubscriptionStatusMessage.java
new file mode 100644
index 0000000..e5af893
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/status/NotificationSubscriptionStatusMessage.java
@@ -0,0 +1,50 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.status;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.MessageWriter;
+
+public class NotificationSubscriptionStatusMessage extends GFDIStatusMessage {
+    private final Status status;
+    private final NotificationStatus notificationStatus;
+    private final int enableRaw;
+    private final int unk;
+    private final boolean sendOutgoing;
+
+    public NotificationSubscriptionStatusMessage(Status status, NotificationStatus notificationStatus, boolean enable, int unk) {
+        this.garminMessage = GarminMessage.NOTIFICATION_SUBSCRIPTION;
+        this.status = status;
+        this.notificationStatus = notificationStatus;
+        this.enableRaw = enable ? 1 : 0;
+        this.unk = unk;
+        this.sendOutgoing = true;
+    }
+
+    @Override
+    protected boolean generateOutgoing() {
+        final MessageWriter writer = new MessageWriter(response);
+        writer.writeShort(0); // packet size will be filled below
+        writer.writeShort(GarminMessage.RESPONSE.getId());
+        writer.writeShort(garminMessage.getId());
+        writer.writeByte(status.ordinal());
+        writer.writeByte(notificationStatus.ordinal());
+        writer.writeByte(this.enableRaw);
+        writer.writeByte(this.unk);
+
+        return this.sendOutgoing;
+    }
+
+    public enum NotificationStatus {
+        ENABLED,
+        DISABLED
+        ;
+
+        public static NotificationStatus fromId(int id) {
+            for (NotificationStatus notificationStatus :
+                    NotificationStatus.values()) {
+                if (notificationStatus.ordinal() == id) {
+                    return notificationStatus;
+                }
+            }
+            return null;
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/status/ProtobufStatusMessage.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/status/ProtobufStatusMessage.java
new file mode 100644
index 0000000..ce53499
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/status/ProtobufStatusMessage.java
@@ -0,0 +1,141 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.status;
+
+import androidx.annotation.Nullable;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.MessageWriter;
+
+public class ProtobufStatusMessage extends GFDIStatusMessage {
+
+    private final Status status;
+    private final int requestId;
+    private final int dataOffset;
+    private final ProtobufChunkStatus protobufChunkStatus;
+    private final ProtobufStatusCode protobufStatusCode;
+    private final boolean sendOutgoing;
+
+    public ProtobufStatusMessage(GarminMessage garminMessage, Status status, int requestId, int dataOffset, ProtobufChunkStatus protobufChunkStatus, ProtobufStatusCode protobufStatusCode) {
+        this(garminMessage, status, requestId, dataOffset, protobufChunkStatus, protobufStatusCode, true);
+    }
+
+    public ProtobufStatusMessage(GarminMessage garminMessage, Status status, int requestId, int dataOffset, ProtobufChunkStatus protobufChunkStatus, ProtobufStatusCode protobufStatusCode, boolean sendOutgoing) {
+        this.garminMessage = garminMessage;
+        this.status = status;
+        this.requestId = requestId;
+        this.dataOffset = dataOffset;
+        this.protobufChunkStatus = protobufChunkStatus;
+        this.protobufStatusCode = protobufStatusCode;
+        this.sendOutgoing = sendOutgoing;
+    }
+
+    public static ProtobufStatusMessage parseIncoming(MessageReader reader, GarminMessage garminMessage) {
+        final Status status = Status.fromCode(reader.readByte());
+        final int requestID = reader.readShort();
+        final int dataOffset = reader.readInt();
+        final ProtobufChunkStatus protobufStatus = ProtobufChunkStatus.fromCode(reader.readByte());
+        final ProtobufStatusCode error = ProtobufStatusCode.fromCode(reader.readByte());
+
+        final ProtobufStatusMessage statusMessage = new ProtobufStatusMessage(garminMessage, status, requestID, dataOffset, protobufStatus, error, false);
+
+        if (statusMessage.isOK()) {
+            LOG.info("Processing protobuf status message #{}@{}:  status={}, error={}", statusMessage.getRequestId(), statusMessage.getDataOffset(), statusMessage.getProtobufChunkStatus(), statusMessage.getProtobufStatusCode());
+        } else {
+            LOG.warn("Processing protobuf status message #{}@{}:  status={}, error={}", statusMessage.getRequestId(), statusMessage.getDataOffset(), statusMessage.getProtobufChunkStatus(), statusMessage.getProtobufStatusCode());
+        }
+
+        return statusMessage;
+    }
+
+    public int getDataOffset() {
+        return dataOffset;
+    }
+
+    public ProtobufChunkStatus getProtobufChunkStatus() {
+        return protobufChunkStatus;
+    }
+
+    public ProtobufStatusCode getProtobufStatusCode() {
+        return protobufStatusCode;
+    }
+
+    public GarminMessage getMessageType() {
+        return garminMessage;
+    }
+
+    public int getRequestId() {
+        return requestId;
+    }
+
+    public boolean isOK() {
+        return this.status.equals(Status.ACK) &&
+                this.protobufChunkStatus.equals(ProtobufChunkStatus.KEPT) &&
+                this.protobufStatusCode.equals(ProtobufStatusCode.NO_ERROR);
+    }
+
+    @Override
+    protected boolean generateOutgoing() {
+        final MessageWriter writer = new MessageWriter(response);
+        writer.writeShort(0); // packet size will be filled below
+        writer.writeShort(GarminMessage.RESPONSE.getId());
+        writer.writeShort(garminMessage.getId());
+        writer.writeByte(status.ordinal());
+        writer.writeShort(requestId);
+        writer.writeInt(dataOffset);
+        writer.writeByte(protobufChunkStatus.ordinal());
+        writer.writeByte(protobufStatusCode.code);
+        return sendOutgoing;
+    }
+
+    protected Status getStatus() {
+        return status;
+    }
+
+    public enum ProtobufChunkStatus { //based on the observations of the combination with the StatusCode
+        KEPT,
+        DISCARDED,
+        ;
+
+        @Nullable
+        public static ProtobufChunkStatus fromCode(final int code) {
+            for (final ProtobufChunkStatus status : ProtobufChunkStatus.values()) {
+                if (status.ordinal() == code) {
+                    return status;
+                }
+            }
+
+            return null;
+        }
+    }
+
+    public enum ProtobufStatusCode {
+        NO_ERROR(0),
+        UNKNOWN_REQUEST_ID(100),
+        DUPLICATE_PACKET(101),
+        MISSING_PACKET(102),
+        EXCEEDED_TOTAL_PROTOBUF_LENGTH(103),
+        PROTOBUF_PARSE_ERROR(200),
+        UNKNOWN(201),
+        ;
+
+        private final int code;
+
+        ProtobufStatusCode(final int code) {
+            this.code = code;
+        }
+
+        @Nullable
+        public static ProtobufStatusCode fromCode(final int code) {
+            for (final ProtobufStatusCode protobufStatusCode : ProtobufStatusCode.values()) {
+                if (protobufStatusCode.getCode() == code) {
+                    return protobufStatusCode;
+                }
+            }
+            return null;
+        }
+
+        public int getCode() {
+            return code;
+        }
+    }
+
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/status/SetFileFlagsStatusMessage.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/status/SetFileFlagsStatusMessage.java
new file mode 100644
index 0000000..b5416c4
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/status/SetFileFlagsStatusMessage.java
@@ -0,0 +1,58 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.status;
+
+import java.util.EnumSet;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.SetFileFlagsMessage;
+
+public class SetFileFlagsStatusMessage extends GFDIStatusMessage {
+    private final Status status;
+    private final FlagsStatus flagsStatus;
+    private final int fileIdentifier;
+    private final EnumSet<SetFileFlagsMessage.FileFlags> fileFlags;
+
+    public SetFileFlagsStatusMessage(GarminMessage garminMessage, Status status, FlagsStatus flagsStatus, int fileIdentifier, EnumSet<SetFileFlagsMessage.FileFlags> fileFlags) {
+        this.garminMessage = garminMessage;
+        this.status = status;
+        this.flagsStatus = flagsStatus;
+        this.fileIdentifier = fileIdentifier;
+        this.fileFlags = fileFlags;
+    }
+
+    public static SetFileFlagsStatusMessage parseIncoming(MessageReader reader, GarminMessage garminMessage) {
+        final Status status = Status.fromCode(reader.readByte());
+
+        if (!status.equals(Status.ACK)) {
+            return null;
+        }
+
+        final FlagsStatus flagsStatus = FlagsStatus.fromCode(reader.readByte());
+        final int originalFileIdentifier = reader.readShort() + 1; //TODO: check if always or only on archival
+        final EnumSet<SetFileFlagsMessage.FileFlags> fileFlags = SetFileFlagsMessage.FileFlags.fromBitMask(reader.readByte());
+
+        if (!FlagsStatus.APPLIED.equals(flagsStatus)) {
+            LOG.warn("Received {} / {} for file identifier {} and flags {} - message {}", status, flagsStatus, originalFileIdentifier, fileFlags, garminMessage);
+        } else {
+            LOG.info("Received {} / {} for file identifier {} and flags {} - message {}", status, flagsStatus, originalFileIdentifier, fileFlags, garminMessage);
+        }
+
+        return new SetFileFlagsStatusMessage(garminMessage, status, flagsStatus, originalFileIdentifier, fileFlags);
+
+    }
+
+
+    enum FlagsStatus {
+        APPLIED,
+        ERROR, //guessed
+        ;
+
+        public static FlagsStatus fromCode(final int code) {
+            for (final FlagsStatus status : FlagsStatus.values()) {
+                if (status.ordinal() == code) {
+                    return status;
+                }
+            }
+            throw new IllegalArgumentException("Unknown FlagsStatus code " + code);
+        }
+    }
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/status/SupportedFileTypesStatusMessage.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/status/SupportedFileTypesStatusMessage.java
new file mode 100644
index 0000000..95b62be
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/status/SupportedFileTypesStatusMessage.java
@@ -0,0 +1,49 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.status;
+
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.FileType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.deviceevents.SupportedFileTypesDeviceEvent;
+
+public class SupportedFileTypesStatusMessage extends GFDIStatusMessage {
+    private final Status status;
+    private final List<FileType> fileTypeInfoList;
+
+    public SupportedFileTypesStatusMessage(GarminMessage garminMessage, Status status, List<FileType> fileTypeInfoList) {
+        this.garminMessage = garminMessage;
+        this.status = status;
+        this.fileTypeInfoList = fileTypeInfoList;
+    }
+
+    public static SupportedFileTypesStatusMessage parseIncoming(MessageReader reader, GarminMessage garminMessage) {
+        final Status status = Status.fromCode(reader.readByte());
+
+        if (!status.equals(Status.ACK)) {
+            return null;
+        }
+        final int typeCount = reader.readByte();
+        final List<FileType> types = new ArrayList<>(typeCount);
+        for (int i = 0; i < typeCount; ++i) {
+            final int fileDataType = reader.readByte();
+            final int fileSubType = reader.readByte();
+            final String garminDeviceFileType = reader.readString();
+            FileType fileType = new FileType(fileDataType, fileSubType, garminDeviceFileType);
+            if (fileType.getFileType() == null) {
+                LOG.warn("Watch supports a filetype that we do not support: {}/{}: {}", fileDataType, fileSubType, garminDeviceFileType);
+                continue;
+            }
+            types.add(fileType);
+        }
+
+        return new SupportedFileTypesStatusMessage(garminMessage, status, types);
+    }
+
+    @Override
+    public List<GBDeviceEvent> getGBDeviceEvent() {
+        return Collections.singletonList(new SupportedFileTypesDeviceEvent(fileTypeInfoList));
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/status/UploadRequestStatusMessage.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/status/UploadRequestStatusMessage.java
new file mode 100644
index 0000000..b4ba6ed
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/status/UploadRequestStatusMessage.java
@@ -0,0 +1,70 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.status;
+
+public class UploadRequestStatusMessage extends GFDIStatusMessage {
+    private final Status status;
+    private final UploadStatus uploadStatus;
+    private final int dataOffset;
+    private final int maxFileSize;
+    private final int crcSeed;
+
+    public UploadRequestStatusMessage(GarminMessage garminMessage, Status status, UploadStatus uploadStatus, int dataOffset, int maxFileSize, int crcSeed) {
+        this.garminMessage = garminMessage;
+        this.status = status;
+        this.uploadStatus = uploadStatus;
+        this.dataOffset = dataOffset;
+        this.maxFileSize = maxFileSize;
+        this.crcSeed = crcSeed;
+    }
+
+    public static UploadRequestStatusMessage parseIncoming(MessageReader reader, GarminMessage garminMessage) {
+        final Status status = Status.fromCode(reader.readByte());
+
+        if (!status.equals(Status.ACK)) {
+            return null;
+        }
+        final UploadStatus uploadStatus = UploadStatus.fromId(reader.readByte());
+        final int dataOffset = reader.readInt();
+        final int maxFileSize = reader.readInt();
+        final int crcSeed = reader.readShort();
+
+        if (!UploadStatus.OK.equals(uploadStatus)) {
+            LOG.warn("Received {} / {} for message {}", status, uploadStatus, garminMessage);
+        } else {
+            LOG.info("Received {} / {} for message {}", status, uploadStatus, garminMessage);
+        }
+        return new UploadRequestStatusMessage(garminMessage, status, uploadStatus, dataOffset, maxFileSize, crcSeed);
+    }
+
+    public int getDataOffset() {
+        return dataOffset;
+    }
+
+    public int getCrcSeed() {
+        return crcSeed;
+    }
+
+    public boolean canProceed() {
+        return status.equals(Status.ACK) && uploadStatus.equals(UploadStatus.OK);
+    }
+
+    public enum UploadStatus {
+        OK,
+        INDEX_UNKNOWN,
+        INDEX_NOT_WRITEABLE,
+        NO_SPACE_LEFT,
+        INVALID,
+        NOT_READY,
+        CRC_INCORRECT,
+        ;
+
+        public static UploadStatus fromId(int id) {
+            for (UploadStatus uploadStatus :
+                    UploadStatus.values()) {
+                if (uploadStatus.ordinal() == id) {
+                    return uploadStatus;
+                }
+            }
+            return null;
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/gatt_client/BleGattClientCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/gatt_client/BleGattClientCoordinator.java
new file mode 100644
index 0000000..66fe6af
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/gatt_client/BleGattClientCoordinator.java
@@ -0,0 +1,51 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.gatt_client;
+
+import androidx.annotation.NonNull;
+
+import nodomain.freeyourgadget.gadgetbridge.GBException;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLEDeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+
+public class BleGattClientCoordinator extends AbstractBLEDeviceCoordinator {
+    @Override
+    public String getManufacturer() {
+        return "Generic";
+    }
+
+    @NonNull
+    @Override
+    public Class<? extends DeviceSupport> getDeviceSupportClass() {
+        return BleGattClientSupport.class;
+    }
+
+    @Override
+    public boolean supports(GBDeviceCandidate candidate) {
+        // can only add through debug settings
+        return false;
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_ble_gatt_client;
+    }
+
+    @Override
+    public int getDefaultIconResource() {
+        return R.drawable.ic_device_scannable;
+    }
+
+    @Override
+    public int getDisabledIconResource() {
+        return R.drawable.ic_device_scannable_disabled;
+    }
+
+    @Override
+    protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
+
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/gatt_client/BleGattClientSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/gatt_client/BleGattClientSupport.java
new file mode 100644
index 0000000..141ad4e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/gatt_client/BleGattClientSupport.java
@@ -0,0 +1,71 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.gatt_client;
+
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.bluetooth.BluetoothGattService;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.GattCharacteristic;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.GattService;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
+
+public class BleGattClientSupport extends AbstractBTLEDeviceSupport {
+    public static final Logger logger = LoggerFactory.getLogger(BleGattClientSupport.class);
+
+    public BleGattClientSupport() {
+        super(logger);
+
+        addSupportedService(GattService.UUID_SERVICE_BATTERY_SERVICE);
+        addSupportedService(GattService.UUID_SERVICE_DEVICE_INFORMATION);
+    }
+
+    @Override
+    public boolean useAutoConnect() {
+        return false;
+    }
+
+    @Override
+    public boolean onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
+        if(characteristic.getUuid().equals(GattCharacteristic.UUID_CHARACTERISTIC_BATTERY_LEVEL)) {
+            GBDeviceEventBatteryInfo batteryInfo = new GBDeviceEventBatteryInfo();
+            batteryInfo.level = characteristic.getValue()[0];
+            handleGBDeviceEvent(batteryInfo);
+        }else if(characteristic.getUuid().equals(GattCharacteristic.UUID_CHARACTERISTIC_FIRMWARE_REVISION_STRING)) {
+            String firmwareVersion = characteristic.getStringValue(0);
+            getDevice().setFirmwareVersion(firmwareVersion);
+            getDevice().sendDeviceUpdateIntent(getContext());
+        }
+        return super.onCharacteristicRead(gatt, characteristic, status);
+    }
+
+    void readCharacteristicIfAvailable(UUID characteristicUUID, TransactionBuilder builder) {
+        BluetoothGattCharacteristic characteristic = getCharacteristic(characteristicUUID);
+        if(characteristic == null) {
+            return;
+        }
+
+        logger.debug("found characteristic {}", characteristicUUID);
+        builder.read(characteristic);
+    }
+
+    @Override
+    protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
+        builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext()));
+
+        readCharacteristicIfAvailable(GattCharacteristic.UUID_CHARACTERISTIC_BATTERY_LEVEL, builder);
+        readCharacteristicIfAvailable(GattCharacteristic.UUID_CHARACTERISTIC_FIRMWARE_REVISION_STRING, builder);
+
+        builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZED, getContext()));
+
+        return builder;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hama/fit6900/HamaFit6900DeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hama/fit6900/HamaFit6900DeviceSupport.java
new file mode 100644
index 0000000..2327c76
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hama/fit6900/HamaFit6900DeviceSupport.java
@@ -0,0 +1,633 @@
+/*
+Copyright (C) 2024 enoint
+
+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 <http://www.gnu.org/licenses/>.
+*/
+
+package nodomain.freeyourgadget.gadgetbridge.service.devices.hama.fit6900;
+
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_FIND_PHONE;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_FIND_PHONE_DURATION;
+
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.content.SharedPreferences;
+import android.os.Handler;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.SettingsActivity;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventCallControl;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventCameraRemote;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventFindPhone;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventMusicControl;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo;
+import nodomain.freeyourgadget.gadgetbridge.devices.hama.fit6900.HamaFit6900Constants;
+
+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.GregorianCalendar;
+import java.util.Locale;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.hama.fit6900.Message;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
+import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
+import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.GattService;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
+
+public final class HamaFit6900DeviceSupport extends AbstractBTLEDeviceSupport {
+    private static final Logger LOG = LoggerFactory.getLogger(HamaFit6900DeviceSupport.class);
+
+    private BluetoothGattCharacteristic writeCharacteristic;
+
+    private int notificationCount = 0;
+    private final Handler findPhoneStopNotificationHandler = new Handler();
+
+    public HamaFit6900DeviceSupport() {
+        super(LOG);
+        addSupportedService(GattService.UUID_SERVICE_GENERIC_ATTRIBUTE);
+        addSupportedService(HamaFit6900Constants.UUID_SERVICE_RXTX);
+    }
+
+    @Override
+    public TransactionBuilder initializeDevice(TransactionBuilder builder) {
+        builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext()));
+
+        writeCharacteristic = getCharacteristic(HamaFit6900Constants.UUID_CHARACTERISTIC_TX);
+
+        builder.notify(getCharacteristic(HamaFit6900Constants.UUID_CHARACTERISTIC_RX), true);
+        builder.setCallback(this);
+
+        builder.write(writeCharacteristic, Message.encodeGetBatteryStatus());
+        builder.write(writeCharacteristic, Message.encodeGetFirmwareVersion());
+
+        if (GBApplication.getPrefs().getBoolean("datetime_synconconnect", true)) {
+            builder.write(writeCharacteristic, makeSetDateTimeMessage());
+        }
+        // sync all preferences to device
+        builder.write(writeCharacteristic, makeSetSystemDataMessage());
+        builder.write(writeCharacteristic, makeSetUnitMessage());
+        builder.write(writeCharacteristic, makeSetUserInfoMessage());
+        builder.write(writeCharacteristic, makeSetAutoHeartRate());
+        builder.write(writeCharacteristic, makeSetDoNotDisturbMessage());
+        builder.write(writeCharacteristic, makeSetHydrationReminderMessage());
+        builder.write(writeCharacteristic, makeSetLiftWristMessage());
+        builder.write(writeCharacteristic, Message.encodeSetAlarms(new ArrayList(DBHelper.getAlarms(gbDevice))));
+
+        builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZED, getContext()));
+
+        return builder;
+    }
+
+    @Override
+    public void onSendConfiguration(final String config) {
+        switch (config) {
+            case DeviceSettingsPreferenceConst.PREF_LANGUAGE:
+            case DeviceSettingsPreferenceConst.PREF_TIMEFORMAT:
+                sendMessage("update-language+timeformat", makeSetSystemDataMessage());
+                return;
+
+            case SettingsActivity.PREF_MEASUREMENT_SYSTEM:
+                sendMessage("update-units", makeSetUnitMessage());
+                return;
+
+            case ActivityUser.PREF_USER_WEIGHT_KG:
+            case ActivityUser.PREF_USER_GENDER:
+            case ActivityUser.PREF_USER_HEIGHT_CM:
+            case ActivityUser.PREF_USER_DATE_OF_BIRTH:
+            case DeviceSettingsPreferenceConst.PREF_USER_FITNESS_GOAL:
+                sendMessage("update-user-info", makeSetUserInfoMessage());
+                return;
+
+            case DeviceSettingsPreferenceConst.PREF_AUTOHEARTRATE_SWITCH:
+            case DeviceSettingsPreferenceConst.PREF_AUTOHEARTRATE_START:
+            case DeviceSettingsPreferenceConst.PREF_AUTOHEARTRATE_END:
+            case DeviceSettingsPreferenceConst.PREF_AUTOHEARTRATE_INTERVAL: {
+                byte[] msg = makeSetAutoHeartRate();
+                if (msg != null) {
+                    sendMessage("update-auto-heart-rate", msg);
+                }
+                return;
+            }
+
+            case DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_NOAUTO:
+            case DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_NOAUTO_START:
+            case DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_NOAUTO_END: {
+                byte[] msg = makeSetDoNotDisturbMessage();
+                if (msg != null)
+                    sendMessage("update-do-not-disturb", msg);
+                return;
+            }
+
+            case DeviceSettingsPreferenceConst.PREF_HYDRATION_SWITCH:
+            case DeviceSettingsPreferenceConst.PREF_HYDRATION_PERIOD: {
+                byte[] msg = makeSetHydrationReminderMessage();
+                if (msg != null)
+                    sendMessage("update-hydration-reminder", msg);
+                return;
+            }
+
+            case DeviceSettingsPreferenceConst.PREF_ACTIVATE_DISPLAY_ON_LIFT:
+                sendMessage("update-lift-wrist", makeSetLiftWristMessage());
+                return;
+        }
+    }
+
+    @Override
+    public boolean onCharacteristicChanged(BluetoothGatt gatt,
+                                           BluetoothGattCharacteristic characteristic) {
+        if (super.onCharacteristicChanged(gatt, characteristic)) {
+            return true;
+        }
+
+        if (!characteristic.getUuid().equals(HamaFit6900Constants.UUID_CHARACTERISTIC_RX)) {
+            return false;
+        }
+
+        byte[] receivedData = characteristic.getValue();
+
+        Message.CommandMessage cmdMsg = Message.decodeCommandMessage(receivedData);
+        if (cmdMsg == null) {
+            return false;
+        }
+
+        final byte[] cmdArgs = cmdMsg.cmdArgs;
+        switch (cmdMsg.cmd) {
+            case 1:
+                switch (cmdMsg.key) {
+                    case 19: // response to GetFirmwareVersion
+                        final String fwVersion = String.format("%d.%d.%d", cmdArgs[0] & 0xFF, cmdArgs[1] & 0xFF, cmdArgs[2] & 0xFF);
+                        // data[3]: bracelet type
+                        // data[>3]: ?
+                        handleFirmwareVersion(fwVersion);
+                        return true;
+                }
+                break;
+
+            case 2:
+                switch (cmdMsg.key) {
+                    case 1: // response to SetUnit; no args
+                    case 32: // response to SetDateTime; no args
+                    case 33: // response to SetAlarms; no args
+                    case 35: // response to SetUserInfo; no args
+                    case 39: // response to SetSystemData; no args
+                    case 40: // response to SetHydrationReminder; no args
+                        return true;
+                }
+                break;
+
+            case 4:
+                switch (cmdMsg.key) {
+                    case 70: // notification msg: camera - open
+                        handleCameraRemote(GBDeviceEventCameraRemote.Event.OPEN_CAMERA);
+                        return true;
+                    case 71: // notification msg: camera - capture
+                        handleCameraRemote(GBDeviceEventCameraRemote.Event.TAKE_PICTURE);
+                        return true;
+                    case 72: // notification msg: camera - close
+                        handleCameraRemote(GBDeviceEventCameraRemote.Event.CLOSE_CAMERA);
+                        return true;
+
+                    case 74: // response to SetLiftWriteDisplayOn; no args
+                        return true;
+
+                    case 65: // response to GetBatteryStatus
+                        handleBatteryStatus(cmdArgs);
+                        return true;
+                }
+                break;
+
+            case 5:
+                switch (cmdMsg.key) {
+                    case 80: // response to FindDevice; length=1, [0]
+                        return true;
+                    case 81: // notification msg: find phone; watch sends no notification to stop it
+                        handleFindPhone(true);
+                        return true;
+                }
+                break;
+
+            case 6:
+                switch (cmdMsg.key) {
+                    case 96: // response to ShowNotification; no args
+                    case 100: // response to SetDoNotDisturb; no args
+                        return true;
+                }
+                break;
+
+            case 9:
+                switch (cmdMsg.key) {
+                    case 146: // response to SetAutoHeartRate; no args
+                        return true;
+                }
+                break;
+
+            case 10:
+                switch (cmdMsg.key) {
+                    case 171: // notification msg: heart rate update
+                        // int heartRate = cmdArgs[0] & 0xFF;
+                        break;
+                    case 172: // notification msg: steps update
+                        // int32 steps; float calories [kCal]; float distance [km]
+                        //int steps = Message.decodeInt32(cmdArgs, 0);
+                        break;
+                }
+                break;
+
+            case 13:
+                switch (cmdMsg.key) {
+                    case 2: // notification msg: hang up call
+                        // in case of ShowNotification(INCOMING_CALL,..) the watch shows a hang up
+                        // button which triggers this notification. Accepting a call is not supported.
+                        handleCallReject();
+                        return true;
+
+                    case 4: // notification msg: media player - play
+                    case 5: // notification msg: media player - pause
+                        // Watch only shows a single play/pause button and has no idea of media player playing state.
+                        // So just toggle between play and pause.
+                        handleMusicControl(GBDeviceEventMusicControl.Event.PLAYPAUSE);
+                        return true;
+                    case 6: // notification msg: media player - previous
+                        handleMusicControl(GBDeviceEventMusicControl.Event.PREVIOUS);
+                        return true;
+                    case 7: // notification msg: media player - next
+                        handleMusicControl(GBDeviceEventMusicControl.Event.NEXT);
+                        return true;
+                }
+                break;
+        }
+
+        //LOG.debug(" command {},{} | NOT HANDLED", cmdMsg.cmd, cmdMsg.key);
+
+        return false;
+    }
+
+    @Override
+    public void onNotification(NotificationSpec notificationSpec) {
+        if (!getDevicePrefsNotificationEnabled()) {
+            return;
+        }
+
+        Message.NotificationType type = Message.NotificationType.UNKNOWN;
+        switch (notificationSpec.type) {
+            case FACEBOOK:
+            case FACEBOOK_MESSENGER:
+                type = Message.NotificationType.FACEBOOK;
+                break;
+            case GENERIC_SMS:
+                type = Message.NotificationType.SMS;
+                break;
+            case INSTAGRAM:
+                type = Message.NotificationType.INSTAGRAM;
+                break;
+            case LINKEDIN:
+                type = Message.NotificationType.LINKEDIN;
+                break;
+            case TWITTER:
+                type = Message.NotificationType.TWITTER;
+                break;
+            case WHATSAPP:
+                type = Message.NotificationType.WHATSAPP;
+                break;
+        }
+        final String notificationMsg = StringUtils.getFirstOf(notificationSpec.sender, notificationSpec.title);
+
+        final String uniqueTaskName = "notification" + notificationCount;
+        notificationCount++;
+
+        sendMessage(uniqueTaskName, Message.encodeShowNotification(type, notificationMsg));
+    }
+
+    @Override
+    public void onSetTime() {
+        sendMessage("set-datetime", makeSetDateTimeMessage());
+    }
+
+    @Override
+    public void onSetAlarms(ArrayList<? extends Alarm> alarms) {
+        sendMessage("set-alarms", Message.encodeSetAlarms(alarms));
+    }
+
+    @Override
+    public void onSetCallState(CallSpec callSpec) {
+        switch (callSpec.command) {
+            case CallSpec.CALL_INCOMING: {
+                if (getDevicePrefsNotificationEnabled()) {
+                    final String text = StringUtils.getFirstOf(callSpec.name, callSpec.number);
+                    sendMessage("notification-call-incoming",
+                            Message.encodeShowNotification(Message.NotificationType.INCOMING_CALL, text));
+                }
+                break;
+            }
+            case CallSpec.CALL_ACCEPT:
+                // aborts INCOMING_CALL notification
+                sendMessage("notification-call-accept",
+                        Message.encodeShowNotification(Message.NotificationType.CALL_ACCEPT, ""));
+                break;
+            case CallSpec.CALL_REJECT:
+            case CallSpec.CALL_END:
+                // aborts INCOMING_CALL notification
+                sendMessage("notification-call-reject",
+                        Message.encodeShowNotification(Message.NotificationType.CALL_REJECT, ""));
+        }
+    }
+
+    @Override
+    public void onFindDevice(boolean start) {
+        if (start) {
+            sendMessage("find-device", Message.encodeFindDevice());
+        }
+    }
+
+    @Override
+    public boolean useAutoConnect() {
+        return true;
+    }
+
+    private boolean sendMessage(String taskName, byte[] message) {
+        try {
+            TransactionBuilder builder = performInitialized(taskName);
+            builder.write(writeCharacteristic, message);
+            builder.queue(getQueue());
+        } catch (IOException ex) {
+            LOG.error(taskName, ex);
+            return false;
+        }
+        return true;
+    }
+
+    private boolean getDevicePrefsNotificationEnabled() {
+        final Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()));
+        return prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_NOTIFICATION_ENABLE, false);
+    }
+
+    private Message.TimeFormat getDevicePrefsTimeFormat() {
+        Message.TimeFormat timeFormat = null;
+        switch (getDevicePrefs().getTimeFormat()) {
+            case DeviceSettingsPreferenceConst.PREF_TIMEFORMAT_24H:
+                timeFormat = Message.TimeFormat.Format24H;
+                break;
+            case DeviceSettingsPreferenceConst.PREF_TIMEFORMAT_12H:
+                timeFormat = Message.TimeFormat.Format12H;
+                break;
+        }
+        return timeFormat;
+    }
+
+    private byte[] makeSetDateTimeMessage() {
+        return Message.encodeSetDateTime(Calendar.getInstance(), getDevicePrefsTimeFormat());
+    }
+
+    private byte[] makeSetSystemDataMessage() {
+        final Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()));
+        final String localeString = prefs.getString(DeviceSettingsPreferenceConst.PREF_LANGUAGE, DeviceSettingsPreferenceConst.PREF_LANGUAGE_AUTO);
+
+        String language;
+        String country;
+        if (localeString.equals(DeviceSettingsPreferenceConst.PREF_LANGUAGE_AUTO)) {
+            language = Locale.getDefault().getLanguage();
+            country = Locale.getDefault().getCountry();
+        } else {
+            language = localeString.substring(0, 2);
+            country = localeString.substring(3, 5);
+        }
+
+        return Message.encodeSetSystemData(language, country, getDevicePrefsTimeFormat());
+    }
+
+    private byte[] makeSetUnitMessage() {
+        final Prefs prefs = GBApplication.getPrefs();
+        String unit = prefs.getString(SettingsActivity.PREF_MEASUREMENT_SYSTEM, "metric");
+
+        return Message.encodeSetUnit(unit.equals("metric"));
+    }
+
+    private byte[] makeSetUserInfoMessage() {
+        final ActivityUser activityUser = new ActivityUser();
+        return Message.encodeSetUserInfo(
+                (activityUser.getGender() == ActivityUser.GENDER_MALE) ? Message.Gender.MALE : Message.Gender.FEMALE,
+                activityUser.getAge(),
+                activityUser.getHeightCm(),
+                activityUser.getWeightKg(),
+                activityUser.getStepsGoal());
+    }
+
+    private byte[] makeSetDoNotDisturbMessage() {
+        final Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()));
+
+        final String enabled = prefs.getString(DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_NOAUTO, "off");
+        if (!enabled.equals(DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_SCHEDULED)) {
+            return Message.encodeSetDoNotDisturb(false, 0, 0, 0, 0);
+        }
+
+        final String start = prefs.getString(DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_NOAUTO_START, "22:00");
+        final String end = prefs.getString(DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_NOAUTO_END, "6:00");
+
+        final Calendar startCalendar = GregorianCalendar.getInstance();
+        final Calendar endCalendar = GregorianCalendar.getInstance();
+        final DateFormat df = new SimpleDateFormat("HH:mm");
+
+        try {
+            startCalendar.setTime(df.parse(start));
+            endCalendar.setTime(df.parse(end));
+        } catch (ParseException e) {
+            return null;
+        }
+
+        return Message.encodeSetDoNotDisturb(true, startCalendar.get(Calendar.HOUR_OF_DAY), startCalendar.get(Calendar.MINUTE),
+                endCalendar.get(Calendar.HOUR_OF_DAY), endCalendar.get(Calendar.MINUTE));
+    }
+
+    private byte[] makeSetAutoHeartRate() {
+        final Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()));
+
+        final boolean enabled = prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_AUTOHEARTRATE_SWITCH, false);
+        if (!enabled) {
+            return Message.encodeSetAutoHeartRate(false, 0, 0, 0, 0, 0);
+        }
+
+        // PREF_AUTOHEARTRATE_SLEEP is not supported
+        final String start = prefs.getString(DeviceSettingsPreferenceConst.PREF_AUTOHEARTRATE_START, "22:00");
+        final String end = prefs.getString(DeviceSettingsPreferenceConst.PREF_AUTOHEARTRATE_END, "6:00");
+        final String intervalStr = prefs.getString(DeviceSettingsPreferenceConst.PREF_AUTOHEARTRATE_INTERVAL, "2");
+
+        final Calendar startCalendar = GregorianCalendar.getInstance();
+        final Calendar endCalendar = GregorianCalendar.getInstance();
+        final DateFormat df = new SimpleDateFormat("HH:mm");
+        final int intervalMinutes;
+
+        try {
+            startCalendar.setTime(df.parse(start));
+            endCalendar.setTime(df.parse(end));
+        } catch (ParseException e) {
+            return null;
+        }
+
+        try {
+            intervalMinutes = Integer.parseInt(intervalStr);
+        } catch (NumberFormatException e) {
+            return null;
+        }
+
+        return Message.encodeSetAutoHeartRate(true, startCalendar.get(Calendar.HOUR_OF_DAY), startCalendar.get(Calendar.MINUTE),
+                endCalendar.get(Calendar.HOUR_OF_DAY), endCalendar.get(Calendar.MINUTE), intervalMinutes);
+    }
+
+    private byte[] makeSetHydrationReminderMessage() {
+        final Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()));
+
+        boolean enabled = prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_HYDRATION_SWITCH, false);
+        final String intervalStr = prefs.getString(DeviceSettingsPreferenceConst.PREF_HYDRATION_PERIOD, "60");
+        int intervalMinutes;
+        try {
+            intervalMinutes = Integer.parseInt(intervalStr);
+        } catch (NumberFormatException e) {
+            return null;
+        }
+
+        if (intervalMinutes == 0) {
+            enabled = false;
+        }
+
+        // Drink reminder notifications honor the do-not-disturb time range.
+        // Start time must be > then end time; e.g. start=19, end=1 won't work.
+        // enable=true and start=end=0 behaves as disabled.
+
+        int startHour = 0;
+        int startMin = 0;
+        int endHour = 23;
+        int endMin = 59;
+
+        return Message.encodeSetHydrationReminder(enabled, startHour, startMin, endHour, endMin, intervalMinutes);
+    }
+
+    private byte[] makeSetLiftWristMessage() {
+        final Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()));
+        final String enabled = prefs.getString(DeviceSettingsPreferenceConst.PREF_ACTIVATE_DISPLAY_ON_LIFT, "off");
+
+        final boolean isEnabled = !enabled.equals("off");
+        return Message.encodeSetLiftWristDisplayOn(isEnabled);
+    }
+
+    private void handleFirmwareVersion(String fwVersion) {
+        final GBDeviceEventVersionInfo event = new GBDeviceEventVersionInfo();
+        event.fwVersion = fwVersion;
+        event.fwVersion2 = null;
+        event.hwVersion = null;
+        evaluateGBDeviceEvent(event);
+    }
+
+    private void handleCallReject() {
+        final GBDeviceEventCallControl event = new GBDeviceEventCallControl();
+        event.event = GBDeviceEventCallControl.Event.REJECT;
+        evaluateGBDeviceEvent(event);
+    }
+
+    private void handleCameraRemote(GBDeviceEventCameraRemote.Event eventType) {
+        final Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()));
+        if (!prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_CAMERA_REMOTE, false))
+            return;
+
+        final GBDeviceEventCameraRemote event = new GBDeviceEventCameraRemote();
+        event.event = eventType;
+        evaluateGBDeviceEvent(event);
+    }
+
+    private void handleMusicControl(GBDeviceEventMusicControl.Event eventType) {
+        final GBDeviceEventMusicControl event = new GBDeviceEventMusicControl();
+        event.event = eventType;
+        evaluateGBDeviceEvent(event);
+    }
+
+    private void handleFindPhone(boolean start) {
+        if (start) {
+            SharedPreferences sharedPreferences = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress());
+            String findPhone = sharedPreferences.getString(PREF_FIND_PHONE, getContext().getString(R.string.p_off));
+            if (findPhone.equals("off"))
+                return;
+
+            String durationSecStr = sharedPreferences.getString(PREF_FIND_PHONE_DURATION, "");
+            int durationSec;
+            try {
+                durationSec = Integer.parseInt(durationSecStr);
+            } catch (Exception ex) {
+                durationSec = 60;
+            }
+            if (durationSec <= 0)
+                return;
+
+            GBDeviceEventFindPhone findPhoneEvent = new GBDeviceEventFindPhone();
+            findPhoneEvent.event = GBDeviceEventFindPhone.Event.START;
+            evaluateGBDeviceEvent(findPhoneEvent);
+
+            try {
+                this.findPhoneStopNotificationHandler.postDelayed(new Runnable() {
+                    @Override
+                    public void run() {
+                        handleFindPhone(false);
+                    }
+                }, durationSec * 1000);
+
+            } catch (Exception ex) {
+                handleFindPhone(false);
+            }
+        } else {
+            GBDeviceEventFindPhone findPhoneEvent = new GBDeviceEventFindPhone();
+            findPhoneEvent.event = GBDeviceEventFindPhone.Event.STOP;
+            evaluateGBDeviceEvent(findPhoneEvent);
+        }
+    }
+
+    private void handleBatteryStatus(byte[] cmdArgs) {
+        /*
+        final int level = cmdArgs[0] & 0xFF;
+        final int type = cmdArgs[1] & 0xFF;
+
+        Disabled since watch always returns a level value of 60
+        Also missing: status needs to be polled regularly
+
+        GBDeviceEventBatteryInfo event = new GBDeviceEventBatteryInfo();
+        event.level = level;
+        evaluateGBDeviceEvent(event); */
+    }
+
+    @Override
+    public boolean getImplicitCallbackModify() {
+        return true;
+    }
+
+    @Override
+    public boolean getSendWriteRequestResponse() {
+        return false;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hplus/HPlusDataRecord.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hplus/HPlusDataRecord.java
new file mode 100644
index 0000000..5e05bb7
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hplus/HPlusDataRecord.java
@@ -0,0 +1,79 @@
+/*  Copyright (C) 2017-2024 Joa虄o Paulo Barraca
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.hplus;
+
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+
+/**
+* @author Jo茫o Paulo Barraca &lt;jpbarraca@gmail.com&gt;
+*/
+public  class HPlusDataRecord {
+    public final static int TYPE_UNKNOWN = 0;
+    public final static int TYPE_SLEEP = 100;
+    public final static int TYPE_DAY_SUMMARY = 101;
+    public final static int TYPE_DAY_SLOT = 102;
+    public final static int TYPE_REALTIME = 103;
+
+    public int type = TYPE_UNKNOWN;
+    public int activityKindRaw = ActivityKind.UNKNOWN.getCode();
+
+    /**
+     * Time of this record in seconds
+     */
+    public int timestamp;
+
+    /**
+     * Raw data as sent from the device
+     */
+    public byte[] rawData;
+
+    protected HPlusDataRecord(){
+
+    }
+
+    protected HPlusDataRecord(byte[] data, int type){
+        this.rawData = data;
+        this.type = type;
+    }
+
+    public byte[] getRawData() {
+        return rawData;
+    }
+
+    public static class RecordInterval {
+        /**
+         * Start time of this interval in seconds
+         */
+        public int timestampFrom;
+
+        /**
+         * End time of this interval in seconds
+         */
+        public int timestampTo;
+
+        /**
+         * Type of activity {@link ActivityKind}
+         */
+        public ActivityKind activityKind;
+
+        RecordInterval(int timestampFrom, int timestampTo, ActivityKind activityKind) {
+            this.timestampFrom = timestampFrom;
+            this.timestampTo = timestampTo;
+            this.activityKind = activityKind;
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hplus/HPlusDataRecordDaySlot.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hplus/HPlusDataRecordDaySlot.java
new file mode 100644
index 0000000..c3265d7
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hplus/HPlusDataRecordDaySlot.java
@@ -0,0 +1,122 @@
+/*  Copyright (C) 2017-2024 Joa虄o Paulo Barraca
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.hplus;
+
+/*
+* @author Jo茫o Paulo Barraca &lt;jpbarraca@gmail.com&gt;
+*/
+
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.Locale;
+
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
+
+
+public class HPlusDataRecordDaySlot extends HPlusDataRecord {
+
+    /**
+     * The device reports data aggregated in slots.
+     * There are 144 slots in a given day, summarizing 10 minutes of data
+     * Integer with the slot number from 0 to 143
+     */
+    public int slot;
+
+    /**
+     * Number of steps
+     */
+    public int steps = ActivitySample.NOT_MEASURED;
+
+    /**
+     * Number of seconds without activity (TBC)
+     */
+    public int secondsInactive = ActivitySample.NOT_MEASURED;
+
+    /**
+     * Average Heart Rate in Beats Per Minute
+     */
+    public int heartRate = ActivitySample.NOT_MEASURED;
+
+    private int age = 0;
+    /**
+     * Raw intensity calculated from calories
+     */
+    public int intensity = ActivitySample.NOT_MEASURED;
+
+    public HPlusDataRecordDaySlot(byte[] data, int age) {
+        super(data, TYPE_DAY_SLOT);
+
+        int a = (data[4] & 0xFF) * 256 + (data[5] & 0xFF);
+        if (a >= 144) {
+            throw new IllegalArgumentException("Invalid Slot Number");
+        }
+
+        slot = a;
+        heartRate = data[1] & 0xFF;
+
+        if(heartRate == 255 || heartRate == 0)
+            heartRate = ActivitySample.NOT_MEASURED;
+
+        steps = (data[2] & 0xFF) * 256 + (data[3] & 0xFF);
+
+        //?? data[6]; atemp?? always 0
+        secondsInactive = data[7] & 0xFF; // ?
+
+        Calendar slotTime = GregorianCalendar.getInstance();
+
+        slotTime.set(Calendar.MINUTE, (slot % 6) * 10);
+        slotTime.set(Calendar.HOUR_OF_DAY, slot / 6);
+        slotTime.set(Calendar.SECOND, 0);
+
+        timestamp = (int) (slotTime.getTimeInMillis() / 1000L);
+
+        this.age = age;
+
+        intensity = (int) ((100*heartRate)/(208-0.7*age));
+    }
+
+    public String toString(){
+        Calendar slotTime = GregorianCalendar.getInstance();
+        slotTime.setTimeInMillis(timestamp * 1000L);
+        return String.format(Locale.US, "Slot: %d, Time: %s, Steps: %d, InactiveSeconds: %d, HeartRate: %d", slot, slotTime.getTime(), steps, secondsInactive, heartRate);
+    }
+
+    public void accumulate(HPlusDataRecordDaySlot other){
+        if(other == null)
+            return;
+
+        if(steps == ActivitySample.NOT_MEASURED)
+            steps = other.steps;
+        else if(other.steps != ActivitySample.NOT_MEASURED)
+            steps += other.steps;
+
+        if(heartRate == ActivitySample.NOT_MEASURED)
+            heartRate = other.heartRate;
+        else if(other.heartRate != ActivitySample.NOT_MEASURED) {
+            heartRate = (heartRate + other.heartRate) / 2;
+        }
+
+        secondsInactive += other.secondsInactive;
+
+        intensity = (int) ((100*heartRate)/(208-0.7*age));
+
+    }
+
+    public boolean isValid(){
+        return steps != 0 || secondsInactive != 0 || heartRate != -1;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hplus/HPlusDataRecordDaySummary.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hplus/HPlusDataRecordDaySummary.java
new file mode 100644
index 0000000..54550d4
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hplus/HPlusDataRecordDaySummary.java
@@ -0,0 +1,117 @@
+/*  Copyright (C) 2017-2024 Joa虄o Paulo Barraca
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.hplus;
+
+/*
+* @author Jo茫o Paulo Barraca &lt;jpbarraca@gmail.com&gt;
+*/
+
+
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.Locale;
+
+
+class HPlusDataRecordDaySummary extends HPlusDataRecord{
+
+    /**
+     * Year of the record reported by the device
+     * Sometimes the device will report a low number (e.g, 116) which will be "corrected"
+     * by adding 1900
+     */
+    public int year;
+
+    /**
+     * Month of the record reported by the device from 1 to 12
+     */
+    public int month;
+
+    /**
+     * Day of the record reported by the device
+     */
+    public int day;
+
+    /**
+     * Number of steps accumulated in the day reported
+     */
+    public int steps;
+
+    /**
+     * Distance in meters accumulated in the day reported
+     */
+    public int distance;
+
+    /**
+     * Amount of time active in the day (Units are To Be Determined)
+     */
+    public int activeTime;
+
+    /**
+     * Max Heart Rate recorded in Beats Per Minute
+     */
+    public int maxHeartRate;
+
+    /**
+     * Min Heart  Rate recorded in Beats Per Minute
+     */
+    public int minHeartRate;
+
+    /**
+     * Amount of estimated calories consumed during the day in KCalories
+     */
+    public int calories;
+
+    HPlusDataRecordDaySummary(byte[] data) {
+        super(data, TYPE_DAY_SUMMARY);
+
+        year =  (data[10] & 0xFF) * 256 + (data[9] & 0xFF);
+        month = data[11] & 0xFF;
+        day = data[12] & 0xFF;
+
+        //Recover from bug in firmware where year is corrupted
+        //data[10] will be set to 0, effectively offsetting values by minus 1900 years
+        if(year < 1900)
+            year += 1900;
+
+        if (year < 2000 || month > 12 || day > 31) {
+            throw new IllegalArgumentException("Invalid record date "+year+"-"+month+"-"+day);
+        }
+        steps = (data[2] & 0xFF) * 256 + (data[1] & 0xFF);
+        distance = ((data[4] & 0xFF) * 256 + (data[3] & 0xFF)) * 10;
+        activeTime = (data[14] & 0xFF) * 256 + (data[13] & 0xFF);
+        calories = (data[6] & 0xFF) * 256 + (data[5] & 0xFF);
+        calories += (data[8] & 0xFF) * 256 + (data[7] & 0xFF);
+
+        maxHeartRate = data[15] & 0xFF;
+        minHeartRate = data[16] & 0xFF;
+
+        Calendar date = GregorianCalendar.getInstance();
+        date.set(Calendar.YEAR, year);
+        date.set(Calendar.MONTH, month - 1);
+        date.set(Calendar.DAY_OF_MONTH, day);
+        date.set(Calendar.HOUR_OF_DAY, 23);
+        date.set(Calendar.MINUTE, 59);
+        date.set(Calendar.SECOND, 59);
+        date.set(Calendar.MILLISECOND, 999);
+
+        timestamp = (int) (date.getTimeInMillis() / 1000);
+    }
+
+    public String toString(){
+        return String.format(Locale.US, "%s-%s-%s steps:%d distance:%d minHR:%d maxHR:%d calories:%d activeTime:%d", year, month, day, steps, distance,minHeartRate, maxHeartRate, calories, activeTime);
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hplus/HPlusDataRecordRealtime.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hplus/HPlusDataRecordRealtime.java
new file mode 100644
index 0000000..630dd26
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hplus/HPlusDataRecordRealtime.java
@@ -0,0 +1,137 @@
+/*  Copyright (C) 2017-2024 Daniele Gobbetti, Joa虄o Paulo Barraca
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.hplus;
+
+/*
+* @author Jo茫o Paulo Barraca &lt;jpbarraca@gmail.com&gt;
+*/
+
+
+import androidx.annotation.NonNull;
+
+import java.util.GregorianCalendar;
+import java.util.Locale;
+
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
+
+
+class HPlusDataRecordRealtime extends HPlusDataRecord {
+
+    /**
+     * Distance accumulated during the day in meters
+     */
+    public int distance;
+
+    /**
+     * Calories consumed during the day in KCalories
+     */
+    public int calories;
+
+    /**
+     * Instantaneous Heart Rate measured in Beats Per Minute
+     */
+    public int heartRate;
+
+    /**
+     * Battery level from 0 to 100
+     */
+    public byte battery;
+
+    /**
+     * Number of steps today
+     */
+    public int steps;
+
+    /**
+     * Time active (To be determined how it works)
+     */
+    public int activeTime;
+
+    /**
+     * Computing intensity
+     * To be calculated appropriately
+     */
+    public int intensity;
+
+    public HPlusDataRecordRealtime(byte[] data, int age) {
+        super(data, TYPE_REALTIME);
+
+        if (data.length < 15) {
+            throw new IllegalArgumentException("Invalid data packet");
+        }
+
+        timestamp = (int) (GregorianCalendar.getInstance().getTimeInMillis() / 1000);
+        distance = 10 * ((data[4] & 0xFF) * 256 + (data[3] & 0xFF)); // meters
+        steps = (data[2] & 0xFF) * 256 + (data[1] & 0xFF);
+        int x = (data[6] & 0xFF) * 256 + (data[5] & 0xFF);
+        int y = (data[8] & 0xFF) * 256 + (data[7] & 0xFF);
+
+        battery = data[9];
+        calories = x + y; // KCal
+        activeTime = (data[14] & 0xFF * 256) + (data[13] & 0xFF);
+
+        if (battery == 255) {
+            battery = ActivitySample.NOT_MEASURED;
+            heartRate = ActivitySample.NOT_MEASURED;
+            intensity = 0;
+            activityKindRaw = ActivityKind.NOT_WORN.getCode();
+        } else {
+            heartRate = data[11] & 0xFF; // BPM
+            if (heartRate == 255) {
+                intensity = 0;
+                activityKindRaw = ActivityKind.NOT_MEASURED.getCode();
+                heartRate = ActivitySample.NOT_MEASURED;
+            } else {
+                intensity = (int) ((100 * heartRate) / (208 - 0.7 * age));
+                activityKindRaw = HPlusDataRecord.TYPE_REALTIME;
+            }
+        }
+    }
+
+    public void computeActivity(HPlusDataRecordRealtime prev){
+        if(prev == null)
+            return;
+
+        int deltaDistance = distance - prev.distance;
+
+        if(deltaDistance <= 0)
+            return;
+
+        int deltaTime = timestamp - prev.timestamp;
+
+        if(deltaTime <= 0)
+            return;
+
+        double speed = deltaDistance / deltaTime;
+
+        if(speed >= 1.6) // ~6 KM/h
+            activityKindRaw = ActivityKind.ACTIVITY.getCode();
+    }
+
+    public boolean same(HPlusDataRecordRealtime other){
+        if(other == null)
+            return false;
+
+        return steps == other.steps && distance == other.distance && calories == other.calories && heartRate == other.heartRate && battery == other.battery;
+    }
+
+    @NonNull
+    public String toString(){
+        return String.format(Locale.US, "Distance: %d Steps: %d Calories: %d HeartRate: %d Battery: %d ActiveTime: %d Intensity: %d", distance, steps, calories, heartRate, battery, activeTime, intensity);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hplus/HPlusDataRecordSleep.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hplus/HPlusDataRecordSleep.java
new file mode 100644
index 0000000..7a5f690
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hplus/HPlusDataRecordSleep.java
@@ -0,0 +1,145 @@
+/*  Copyright (C) 2017-2024 Joa虄o Paulo Barraca
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.hplus;
+
+/*
+* @author Jo茫o Paulo Barraca &lt;jpbarraca@gmail.com&gt;
+*/
+
+
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.List;
+import java.util.Locale;
+
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+
+public class HPlusDataRecordSleep extends HPlusDataRecord {
+
+    /**
+     * Time which the device determined to be the bed time in seconds
+     */
+    public int bedTimeStart;
+
+    /**
+     * Time which the device determined to be the end of this sleep period in seconds
+     */
+    public int bedTimeEnd;
+
+    /**
+     * Number of minutes in Deep Sleep
+     */
+    public int deepSleepMinutes;
+
+    /**
+     * Number of minutes in Light Sleep
+     * This is considered as Light Sleep
+
+     */
+    public int lightSleepMinutes;
+
+    /**
+     * Number of minutes to start sleeping (??)
+     * This is considered as Light Sleep
+     */
+    public int enterSleepMinutes;
+
+    /**
+     * Number of minutes with Sleep Spindles (??)
+     * This is considered as Light Sleep
+     */
+    public int spindleMinutes;
+
+    /**
+     * Number of minutes in REM sleep
+     * This is considered as Light Sleep
+
+     */
+    public int remSleepMinutes;
+
+    /**
+     * Number of wake up minutes during the sleep period
+     * This is not considered as a sleep activity
+     */
+    public int wakeupMinutes;
+
+    /**
+     * Number of times the user woke up
+     */
+    public int wakeupCount;
+
+    public HPlusDataRecordSleep(byte[] data) {
+        super(data, TYPE_SLEEP);
+
+        int year = (data[2] & 0xFF) * 256 + (data[1] & 0xFF);
+        int month = data[3] & 0xFF;
+        int day = data[4] & 0xFF;
+
+        if (year < 2000) //Attempt to recover from bug from device.
+            year += 1900;
+
+        if (year < 2000 || month > 12 || day <= 0 || day > 31) {
+            throw new IllegalArgumentException("Invalid record date: " + year + "-" + month + "-" + day);
+        }
+
+        enterSleepMinutes = ((data[6] & 0xFF) * 256 + (data[5] & 0xFF));
+        spindleMinutes = ((data[8] & 0xFF) * 256 + (data[7] & 0xFF));
+        deepSleepMinutes = ((data[10] & 0xFF) * 256 + (data[9] & 0xFF));
+        remSleepMinutes = ((data[12] & 0xFF) * 256 + (data[11] & 0xFF));
+        wakeupMinutes = ((data[14] & 0xFF) * 256 + (data[13] & 0xFF));
+        wakeupCount = ((data[16] & 0xFF) * 256 + (data[15] & 0xFF));
+
+        int hour = data[17] & 0xFF;
+        int minute = data[18] & 0xFF;
+
+        Calendar sleepStart = GregorianCalendar.getInstance();
+        sleepStart.clear();
+        sleepStart.set(Calendar.YEAR, year);
+        sleepStart.set(Calendar.MONTH, month - 1);
+        sleepStart.set(Calendar.DAY_OF_MONTH, day);
+        sleepStart.set(Calendar.HOUR_OF_DAY, hour);
+        sleepStart.set(Calendar.MINUTE, minute);
+        sleepStart.set(Calendar.SECOND, 0);
+        sleepStart.set(Calendar.MILLISECOND, 0);
+
+        bedTimeStart = (int) (sleepStart.getTimeInMillis() / 1000);
+        bedTimeEnd = (enterSleepMinutes + spindleMinutes + deepSleepMinutes + remSleepMinutes + wakeupMinutes) * 60 + bedTimeStart;
+        lightSleepMinutes = enterSleepMinutes + spindleMinutes + remSleepMinutes;
+
+        timestamp = bedTimeStart;
+        }
+
+    public List<RecordInterval> getIntervals() {
+        List<RecordInterval> intervals = new ArrayList<>();
+
+        int ts = bedTimeStart + lightSleepMinutes * 60;
+        intervals.add(new RecordInterval(bedTimeStart, ts, ActivityKind.LIGHT_SLEEP));
+        intervals.add(new RecordInterval(ts, bedTimeEnd, ActivityKind.DEEP_SLEEP));
+        return intervals;
+    }
+
+    public String toString(){
+        Calendar s = GregorianCalendar.getInstance();
+        s.setTimeInMillis(bedTimeStart * 1000L);
+
+        Calendar end = GregorianCalendar.getInstance();
+        end.setTimeInMillis(bedTimeEnd * 1000L);
+
+        return String.format(Locale.US, "Sleep start: %s end: %s enter: %d spindles: %d rem: %d deep: %d wake: %d-%d", s.getTime(), end.getTime(), enterSleepMinutes, spindleMinutes, remSleepMinutes, deepSleepMinutes, wakeupMinutes, wakeupCount);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hplus/HPlusHandlerThread.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hplus/HPlusHandlerThread.java
new file mode 100644
index 0000000..5ba248e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hplus/HPlusHandlerThread.java
@@ -0,0 +1,650 @@
+/*  Copyright (C) 2017-2024 Daniele Gobbetti, Joa虄o Paulo Barraca, Lesur
+    Frederic
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.hplus;
+
+import android.content.Context;
+import android.content.Intent;
+
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.GregorianCalendar;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.GBException;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.hplus.HPlusConstants;
+import nodomain.freeyourgadget.gadgetbridge.devices.hplus.HPlusCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.hplus.HPlusHealthSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.HPlusHealthActivityOverlay;
+import nodomain.freeyourgadget.gadgetbridge.entities.HPlusHealthActivityOverlayDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.HPlusHealthActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceIoThread;
+
+
+class HPlusHandlerThread extends GBDeviceIoThread {
+    private static final Logger LOG = LoggerFactory.getLogger(HPlusHandlerThread.class);
+    private final Object waitObject = new Object();
+    List<HPlusDataRecordDaySlot> mDaySlotRecords = new ArrayList<>();
+    private int CURRENT_DAY_SYNC_PERIOD = 24 * 60 * 60 * 365; //Never
+    private int CURRENT_DAY_SYNC_RETRY_PERIOD = 10;
+    private int SLEEP_SYNC_PERIOD = 12 * 60 * 60;
+    private int SLEEP_SYNC_RETRY_PERIOD = 30;
+    private int DAY_SUMMARY_SYNC_PERIOD = 24 * 60 * 60;
+    private int DAY_SUMMARY_SYNC_RETRY_PERIOD = 30;
+    private int HELLO_PERIOD = 60 * 2;
+    private boolean mQuit = false;
+    private HPlusSupport mHPlusSupport;
+    private int mLastSlotReceived = -1;
+    private int mLastSlotRequested = 0;
+    private Calendar mLastSleepDayReceived = GregorianCalendar.getInstance();
+    private Calendar mGetDaySlotsTime = GregorianCalendar.getInstance();
+    private Calendar mGetSleepTime = GregorianCalendar.getInstance();
+    private Calendar mGetDaySummaryTime = GregorianCalendar.getInstance();
+    private Calendar mHelloTime = GregorianCalendar.getInstance();
+    private boolean mSlotsInitialSync = true;
+    private HPlusDataRecordRealtime prevRealTimeRecord = null;
+    private HPlusDataRecordDaySlot mCurrentDaySlot = null;
+
+    public HPlusHandlerThread(GBDevice gbDevice, Context context, HPlusSupport hplusSupport) {
+        super(gbDevice, context);
+        LOG.info("Initializing HPlus Handler Thread");
+        mQuit = false;
+
+        mHPlusSupport = hplusSupport;
+    }
+
+
+    @Override
+    public void run() {
+        mQuit = false;
+
+        sync();
+
+        long waitTime = 0;
+        while (!mQuit) {
+
+            if (waitTime > 0) {
+                synchronized (waitObject) {
+                    try {
+                        waitObject.wait(waitTime);
+                    } catch (InterruptedException e) {
+                        e.printStackTrace();
+                    }
+                }
+            }
+
+            if (mQuit) {
+                break;
+            }
+
+            if (gbDevice.getState() == GBDevice.State.NOT_CONNECTED) {
+                quit();
+            }
+
+            Calendar now = GregorianCalendar.getInstance();
+
+            if (now.compareTo(mGetDaySlotsTime) > 0) {
+                requestNextDaySlots();
+            }
+
+            if (now.compareTo(mGetSleepTime) > 0) {
+                requestNextSleepData();
+            }
+
+            if (now.compareTo(mGetDaySummaryTime) > 0) {
+                requestDaySummaryData();
+            }
+
+            if (now.compareTo(mHelloTime) > 0) {
+                sendHello();
+            }
+
+            now = GregorianCalendar.getInstance();
+            waitTime = Math.min(mGetDaySummaryTime.getTimeInMillis(), Math.min(mGetDaySlotsTime.getTimeInMillis(), Math.min(mHelloTime.getTimeInMillis(), mGetSleepTime.getTimeInMillis()))) - now.getTimeInMillis();
+        }
+
+    }
+
+    @Override
+    public void quit() {
+        LOG.info("HPlus: Quit Handler Thread");
+        mQuit = true;
+        synchronized (waitObject) {
+            waitObject.notify();
+        }
+    }
+
+
+    public void sync() {
+        LOG.info("HPlus: Starting data synchronization");
+
+        mGetSleepTime.setTimeInMillis(0);
+        mGetDaySlotsTime.setTimeInMillis(0);
+        mGetDaySummaryTime.setTimeInMillis(0);
+        mLastSleepDayReceived.setTimeInMillis(0);
+        mHelloTime = GregorianCalendar.getInstance();
+        mHelloTime.add(Calendar.SECOND, HELLO_PERIOD);
+
+        mSlotsInitialSync = true;
+        mLastSlotReceived = -1;
+        mLastSlotRequested = 0;
+        mCurrentDaySlot = null;
+        mDaySlotRecords.clear();
+
+        try {
+            if (!mHPlusSupport.isConnected())
+                mHPlusSupport.connect();
+
+            TransactionBuilder builder = new TransactionBuilder("startSyncDayStats");
+
+            builder.write(mHPlusSupport.ctrlCharacteristic, new byte[]{HPlusConstants.CMD_GET_DEVICE_ID});
+            builder.write(mHPlusSupport.ctrlCharacteristic, new byte[]{HPlusConstants.CMD_GET_VERSION});
+            builder.write(mHPlusSupport.ctrlCharacteristic, new byte[]{HPlusConstants.CMD_GET_CURR_DATA});
+
+            mHPlusSupport.performConnected(builder.getTransaction());
+        } catch (Exception e) {
+            LOG.warn("HPlus: Synchronization exception: " + e);
+        }
+
+        synchronized (waitObject) {
+            waitObject.notify();
+        }
+    }
+
+    public void sendHello() {
+        try {
+            TransactionBuilder builder = new TransactionBuilder("hello");
+            builder.write(mHPlusSupport.ctrlCharacteristic, HPlusConstants.CMD_ACTION_HELLO);
+            mHPlusSupport.performConnected(builder.getTransaction());
+
+        } catch (Exception e) {
+
+        }
+        mHelloTime = GregorianCalendar.getInstance();
+        mHelloTime.add(Calendar.SECOND, HELLO_PERIOD);
+
+        synchronized (waitObject) {
+            waitObject.notify();
+        }
+    }
+
+    /**
+     * Process a message containing information regarding a day slot
+     * A slot summarizes 10 minutes of data
+     *
+     * @param data the message from the device
+     * @return boolean indicating success or fail
+     */
+    public boolean processIncomingDaySlotData(byte[] data, int age) {
+
+        HPlusDataRecordDaySlot record;
+
+        try {
+            record = new HPlusDataRecordDaySlot(data, age);
+        } catch (IllegalArgumentException e) {
+            LOG.info((e.getMessage()));
+            return false;
+        }
+
+        Calendar now = GregorianCalendar.getInstance();
+        int nowSlot = now.get(Calendar.HOUR_OF_DAY) * 6 + (now.get(Calendar.MINUTE) / 10);
+        if (record.slot == nowSlot) {
+            if (mCurrentDaySlot != null && mCurrentDaySlot != record) {
+                mCurrentDaySlot.accumulate(record);
+                mDaySlotRecords.add(mCurrentDaySlot);
+                mCurrentDaySlot = null;
+            } else {
+                //Store it to a temp variable as this is an intermediate value
+                mCurrentDaySlot = record;
+                if (!mSlotsInitialSync)
+                    return true;
+            }
+        }
+
+        if (mSlotsInitialSync) {
+            //If the slot is in the future, actually it is from the previous day
+            //Subtract a day of seconds
+            if (record.slot > nowSlot) {
+                record.timestamp -= 3600 * 24;
+            }
+
+            if (record.slot == mLastSlotReceived + 1) {
+                mLastSlotReceived = record.slot;
+            }
+
+            //Ignore the current slot as it is incomplete
+            if (record.slot != nowSlot)
+                mDaySlotRecords.add(record);
+
+            //Still fetching ring buffer. Request the next slots
+            if (record.slot == mLastSlotRequested) {
+                mGetDaySlotsTime.clear();
+                synchronized (waitObject) {
+                    waitObject.notify();
+                }
+            }
+
+            //Keep buffering
+            if (record.slot != 143)
+                return true;
+        } else {
+            mGetDaySlotsTime = GregorianCalendar.getInstance();
+            mGetDaySlotsTime.add(Calendar.DAY_OF_MONTH, 1);
+        }
+
+        if (mDaySlotRecords.size() > 0) {
+            //Sort the samples
+            Collections.sort(mDaySlotRecords, new Comparator<HPlusDataRecordDaySlot>() {
+                public int compare(HPlusDataRecordDaySlot one, HPlusDataRecordDaySlot other) {
+                    return one.timestamp - other.timestamp;
+                }
+            });
+
+            List<Integer> notWornSlots = new ArrayList<>();
+
+            try (DBHandler dbHandler = GBApplication.acquireDB()) {
+                HPlusHealthSampleProvider provider = new HPlusHealthSampleProvider(getDevice(), dbHandler.getDaoSession());
+                List<HPlusHealthActivitySample> samples = new ArrayList<>();
+
+                for (HPlusDataRecordDaySlot storedRecord : mDaySlotRecords) {
+
+                    //Invalid records (no data) will be ignored
+                    if (!storedRecord.isValid())
+                        continue;
+
+                    HPlusHealthActivitySample sample = createSample(dbHandler, storedRecord.timestamp);
+
+                    sample.setRawHPlusHealthData(storedRecord.getRawData());
+                    sample.setSteps(storedRecord.steps);
+
+                    sample.setRawIntensity(storedRecord.intensity);
+                    sample.setHeartRate(storedRecord.heartRate);
+                    sample.setRawKind(storedRecord.type);
+                    sample.setProvider(provider);
+                    samples.add(sample);
+
+                    if (mHPlusSupport.getAllDayHR() == HPlusConstants.ARG_HEARTRATE_ALLDAY_ON && storedRecord.heartRate == ActivitySample.NOT_MEASURED && storedRecord.steps <= 0) {
+                        notWornSlots.add(sample.getTimestamp());
+                        notWornSlots.add(sample.getTimestamp() + 10 * 60);
+
+                    }
+                }
+
+                provider.getSampleDao().insertOrReplaceInTx(samples);
+                mDaySlotRecords.clear();
+
+                //Create an overlay with unused slots
+                if (notWornSlots.size() > 0) {
+                    DaoSession session = dbHandler.getDaoSession();
+                    Long userId = DBHelper.getUser(session).getId();
+                    Long deviceId = DBHelper.getDevice(getDevice(), session).getId();
+
+                    HPlusHealthActivityOverlayDao overlayDao = session.getHPlusHealthActivityOverlayDao();
+                    List<HPlusHealthActivityOverlay> overlayList = new ArrayList<>();
+
+
+                    int firstSlotTimestamp = notWornSlots.get(0);
+                    int lastSlotTimestamp = notWornSlots.get(0);
+
+                    int i = 1;
+                    for (Integer timestamp : notWornSlots) {
+
+                        //If it is the last of the samples or of the interruption period
+                        if (timestamp - lastSlotTimestamp > 10 * 60) {
+                            overlayList.add(new HPlusHealthActivityOverlay(firstSlotTimestamp, lastSlotTimestamp, ActivityKind.NOT_WORN.getCode(), deviceId, userId, null));
+                            firstSlotTimestamp = timestamp;
+                        }
+
+                        lastSlotTimestamp = timestamp;
+
+                    }
+
+                    if (firstSlotTimestamp != lastSlotTimestamp)
+                        overlayList.add(new HPlusHealthActivityOverlay(firstSlotTimestamp, lastSlotTimestamp, ActivityKind.NOT_WORN.getCode(), deviceId, userId, null));
+
+                    overlayDao.insertOrReplaceInTx(overlayList);
+                }
+
+            } catch (GBException ex) {
+                LOG.info((ex.getMessage()));
+            } catch (Exception ex) {
+                LOG.info(ex.getMessage());
+            }
+        }
+
+        return true;
+    }
+
+
+    /**
+     * Process sleep data from the device
+     * Devices send a single sleep message for each sleep period
+     * This message contains the duration of the sub-intervals (rem, deep, etc...)
+     *
+     * @param data the message from the device
+     * @return boolean indicating success or fail
+     */
+    public boolean processIncomingSleepData(byte[] data) {
+        HPlusDataRecordSleep record;
+
+        try {
+            record = new HPlusDataRecordSleep(data);
+        } catch (IllegalArgumentException e) {
+            LOG.info((e.getMessage()));
+            return false;
+        }
+
+        mLastSleepDayReceived.setTimeInMillis(record.bedTimeStart * 1000L);
+
+        try (DBHandler dbHandler = GBApplication.acquireDB()) {
+            DaoSession session = dbHandler.getDaoSession();
+            Long userId = DBHelper.getUser(session).getId();
+            Long deviceId = DBHelper.getDevice(getDevice(), session).getId();
+
+            HPlusHealthActivityOverlayDao overlayDao = session.getHPlusHealthActivityOverlayDao();
+            HPlusHealthSampleProvider provider = new HPlusHealthSampleProvider(getDevice(), dbHandler.getDaoSession());
+
+            //Get the individual Sleep overlays and insert them
+            List<HPlusHealthActivityOverlay> overlayList = new ArrayList<>();
+            List<HPlusDataRecord.RecordInterval> intervals = record.getIntervals();
+
+            for (HPlusDataRecord.RecordInterval interval : intervals) {
+                overlayList.add(new HPlusHealthActivityOverlay(interval.timestampFrom, interval.timestampTo, interval.activityKind.getCode(), deviceId, userId, null));
+            }
+
+            overlayDao.insertOrReplaceInTx(overlayList);
+
+            //Store the data
+            HPlusHealthActivitySample sample = createSample(dbHandler, record.timestamp);
+            sample.setRawHPlusHealthData(record.getRawData());
+            sample.setRawKind(record.activityKindRaw);
+
+            sample.setProvider(provider);
+
+            provider.addGBActivitySample(sample);
+        } catch (Exception ex) {
+            LOG.info(ex.getMessage());
+        }
+
+        mGetSleepTime = GregorianCalendar.getInstance();
+        mGetSleepTime.add(GregorianCalendar.SECOND, SLEEP_SYNC_PERIOD);
+
+        return true;
+    }
+
+    /**
+     * Process a message containing real time information
+     *
+     * @param data the message from the device
+     * @return boolean indicating success or fail
+     */
+    public boolean processRealtimeStats(byte[] data, int age) {
+        HPlusDataRecordRealtime record;
+
+        try {
+            record = new HPlusDataRecordRealtime(data, age);
+        } catch (IllegalArgumentException e) {
+            LOG.info((e.getMessage()));
+            return false;
+        }
+
+        //Skip duplicated messages as the device seems to send the same record multiple times
+        //This can be used to detect the user is moving (not sleeping)
+        if (prevRealTimeRecord != null && record.same(prevRealTimeRecord))
+            return true;
+
+        prevRealTimeRecord = record;
+
+        getDevice().setBatteryLevel(record.battery);
+
+        try (DBHandler dbHandler = GBApplication.acquireDB()) {
+            HPlusHealthSampleProvider provider = new HPlusHealthSampleProvider(getDevice(), dbHandler.getDaoSession());
+
+            HPlusHealthActivitySample sample = createSample(dbHandler, record.timestamp);
+            sample.setRawKind(record.type);
+            sample.setRawIntensity(record.intensity);
+            sample.setHeartRate(record.heartRate);
+            sample.setDistance(record.distance);
+            sample.setCalories(record.calories);
+            sample.setSteps(record.steps);
+
+            sample.setRawHPlusHealthData(record.getRawData());
+            sample.setProvider(provider);
+
+            provider.addGBActivitySample(sample);
+
+            sample.setSteps(sample.getSteps() - prevRealTimeRecord.steps);
+
+            Intent intent = new Intent(DeviceService.ACTION_REALTIME_SAMPLES)
+                    .putExtra(GBDevice.EXTRA_DEVICE, getDevice())
+                    .putExtra(DeviceService.EXTRA_REALTIME_SAMPLE, sample)
+                    .putExtra(DeviceService.EXTRA_TIMESTAMP, System.currentTimeMillis());
+            LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent);
+
+
+            //TODO: Handle Active Time. With Overlay?
+        } catch (GBException ex) {
+            LOG.info((ex.getMessage()));
+        } catch (Exception ex) {
+            LOG.info(ex.getMessage());
+        }
+        return true;
+    }
+
+    /**
+     * Process a day summary message
+     * This message includes aggregates regarding an entire day
+     *
+     * @param data the message from the device
+     * @return boolean indicating success or fail
+     */
+    public boolean processDaySummary(byte[] data) {
+        HPlusDataRecordDaySummary record;
+
+        try {
+            record = new HPlusDataRecordDaySummary(data);
+        } catch (IllegalArgumentException e) {
+            LOG.info((e.getMessage()));
+            return false;
+        }
+
+        try (DBHandler dbHandler = GBApplication.acquireDB()) {
+            HPlusHealthSampleProvider provider = new HPlusHealthSampleProvider(getDevice(), dbHandler.getDaoSession());
+
+            HPlusHealthActivitySample sample = createSample(dbHandler, record.timestamp);
+
+            sample.setRawKind(record.type);
+            sample.setSteps(record.steps);
+            sample.setDistance(record.distance);
+            sample.setCalories(record.calories);
+            sample.setDistance(record.distance);
+            sample.setHeartRate((record.maxHeartRate - record.minHeartRate) / 2); //TODO: Find an alternative approach for Day Summary Heart Rate
+            sample.setRawHPlusHealthData(record.getRawData());
+
+            sample.setProvider(provider);
+            provider.addGBActivitySample(sample);
+        } catch (GBException ex) {
+            LOG.info((ex.getMessage()));
+        } catch (Exception ex) {
+            LOG.info(ex.getMessage());
+        }
+
+        mGetDaySummaryTime = GregorianCalendar.getInstance();
+        mGetDaySummaryTime.add(Calendar.SECOND, DAY_SUMMARY_SYNC_PERIOD);
+        return true;
+    }
+
+    /**
+     * Process a message containing information regarding firmware version
+     *
+     * @param data the message from the device
+     * @return boolean indicating success or fail
+     */
+    public boolean processVersion(byte[] data) {
+        int major, minor;
+
+        LOG.info("Process Version Data: : '" + new String(data) + "'");
+
+        if (data.length >= 11) {
+            major = data[10] & 0xFF;
+            minor = data[9] & 0xFF;
+
+            int hwMajor = data[2] & 0xFF;
+            int hwMinor = data[1] & 0xFF;
+
+            getDevice().setFirmwareVersion2(hwMajor + "." + hwMinor);
+            mHPlusSupport.setUnicodeSupport(data[3] != 0);
+        } else {
+            major = data[2] & 0xFF;
+            minor = data[1] & 0xFF;
+        }
+
+        getDevice().setFirmwareVersion(major + "." + minor);
+        getDevice().sendDeviceUpdateIntent(getContext());
+
+        return true;
+    }
+
+    /**
+     * Issue a message requesting the next batch of sleep data
+     */
+    private void requestNextSleepData() {
+        try {
+            TransactionBuilder builder = new TransactionBuilder("requestSleepStats");
+            builder.write(mHPlusSupport.ctrlCharacteristic, new byte[]{HPlusConstants.CMD_GET_SLEEP});
+            mHPlusSupport.performConnected(builder.getTransaction());
+        } catch (Exception e) {
+
+        }
+
+        mGetSleepTime = GregorianCalendar.getInstance();
+        mGetSleepTime.add(GregorianCalendar.SECOND, SLEEP_SYNC_RETRY_PERIOD);
+    }
+
+    /**
+     * Issue a message requesting the next set of slots
+     * The process will sync 1h at a time until the device is in sync
+     * Then it will request samples until the end of the day in order to minimize data loss
+     * Messages will be provided every 10 minutes after they are available
+     */
+    private void requestNextDaySlots() {
+        Calendar now = GregorianCalendar.getInstance();
+        int currentSlot = now.get(Calendar.HOUR_OF_DAY) * 6 + now.get(Calendar.MINUTE) / 10;
+
+        //Finished dumping the entire ring buffer
+        //Sync to current time
+        mGetDaySlotsTime = now;
+
+        if (mSlotsInitialSync) {
+            if (mLastSlotReceived == 143) {
+                mSlotsInitialSync = false;
+                mGetDaySlotsTime.set(Calendar.SECOND, CURRENT_DAY_SYNC_PERIOD); //Sync complete. Delay timer forever
+                mLastSlotReceived = -1;
+                mLastSlotRequested = mLastSlotReceived + 1;
+                return;
+            } else {
+                mGetDaySlotsTime.add(Calendar.SECOND, CURRENT_DAY_SYNC_RETRY_PERIOD);
+            }
+        } else {
+            //Sync complete. Delay timer forever
+            mGetDaySlotsTime.set(Calendar.SECOND, CURRENT_DAY_SYNC_PERIOD);
+            return;
+        }
+
+        if (mLastSlotReceived == 143)
+            mLastSlotReceived = -1;
+
+        byte hour = (byte) ((mLastSlotReceived + 1) / 6);
+        byte minute = (byte) (((mLastSlotReceived + 1) % 6) * 10);
+
+        byte nextHour = hour;
+        byte nextMinute = 59;
+
+        mLastSlotRequested = nextHour * 6 + (nextMinute / 10);
+
+        byte[] msg = new byte[]{HPlusConstants.CMD_GET_ACTIVE_DAY, hour, minute, nextHour, nextMinute};
+        try {
+
+            TransactionBuilder builder = new TransactionBuilder("getNextDaySlot");
+            builder.write(mHPlusSupport.ctrlCharacteristic, msg);
+            mHPlusSupport.performConnected(builder.getTransaction());
+        } catch (Exception e) {
+
+        }
+    }
+
+    /**
+     * Request a batch of data with the summary of the previous days
+     */
+    public void requestDaySummaryData() {
+        try {
+            TransactionBuilder builder = new TransactionBuilder("startSyncDaySummary");
+            builder.write(mHPlusSupport.ctrlCharacteristic, new byte[]{HPlusConstants.CMD_GET_DAY_DATA});
+            mHPlusSupport.performConnected(builder.getTransaction());
+        } catch (Exception e) {
+
+        }
+        mGetDaySummaryTime = GregorianCalendar.getInstance();
+        mGetDaySummaryTime.add(Calendar.SECOND, DAY_SUMMARY_SYNC_RETRY_PERIOD);
+    }
+
+    /**
+     * Helper function to create a sample
+     *
+     * @param dbHandler The database handler
+     * @param timestamp The sample timestamp
+     * @return The sample just created
+     */
+    private HPlusHealthActivitySample createSample(DBHandler dbHandler, int timestamp) {
+        Long userId = DBHelper.getUser(dbHandler.getDaoSession()).getId();
+        Long deviceId = DBHelper.getDevice(getDevice(), dbHandler.getDaoSession()).getId();
+        HPlusHealthActivitySample sample = new HPlusHealthActivitySample(
+                timestamp,                      // ts
+                deviceId, userId,               // User id
+                null,            // Raw Data
+                ActivityKind.UNKNOWN.getCode(),
+                0,                              // Intensity
+                ActivitySample.NOT_MEASURED,     // Steps
+                ActivitySample.NOT_MEASURED,    // HR
+                ActivitySample.NOT_MEASURED,  // Distance
+                ActivitySample.NOT_MEASURED     // Calories
+        );
+
+        return sample;
+    }
+
+    public void setHPlusSupport(HPlusSupport HPlusSupport) {
+        LOG.info("Updating HPlusSupport object");
+        this.mHPlusSupport = HPlusSupport;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hplus/HPlusSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hplus/HPlusSupport.java
new file mode 100644
index 0000000..c61cf88
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hplus/HPlusSupport.java
@@ -0,0 +1,929 @@
+/*  Copyright (C) 2016-2024 Alberto, Andreas Shimokawa, Arjan Schrijver,
+    Carsten Pfeiffer, Damien Gaignon, ivanovlev, Joa虄o Paulo Barraca, Lesur
+    Frederic, Pavel Motyrev, Quallenauge, Sebastian Kranz
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.hplus;
+
+/*
+ * @author Jo茫o Paulo Barraca &lt;jpbarraca@gmail.com&gt;
+ */
+
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.widget.Toast;
+
+import org.apache.commons.lang3.ArrayUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.List;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.SettingsActivity;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
+import nodomain.freeyourgadget.gadgetbridge.devices.hplus.HPlusConstants;
+import nodomain.freeyourgadget.gadgetbridge.devices.hplus.HPlusCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.hplus.HPlusWeatherCode;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
+import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.model.GenericItem;
+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.profiles.deviceinfo.DeviceInfo;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.webview.CurrentPosition;
+import nodomain.freeyourgadget.gadgetbridge.util.AlarmUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class HPlusSupport extends AbstractBTLEDeviceSupport {
+    private static final Logger LOG = LoggerFactory.getLogger(HPlusSupport.class);
+    private final GBDeviceEventBatteryInfo batteryCmd = new GBDeviceEventBatteryInfo();
+    public BluetoothGattCharacteristic ctrlCharacteristic = null;
+    public BluetoothGattCharacteristic measureCharacteristic = null;
+    private HPlusHandlerThread syncHelper;
+    private DeviceType deviceType = DeviceType.UNKNOWN;
+
+    public HPlusSupport(DeviceType type) {
+        super(LOG);
+        LOG.info("HPlusSupport Instance Created");
+        deviceType = type;
+
+        addSupportedService(HPlusConstants.UUID_SERVICE_HP);
+    }
+
+    @Override
+    public void dispose() {
+        LOG.info("Dispose");
+        close();
+
+        super.dispose();
+    }
+
+    @Override
+    protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
+        LOG.info("Initializing");
+
+        gbDevice.setState(GBDevice.State.INITIALIZING);
+        gbDevice.sendDeviceUpdateIntent(getContext());
+
+        measureCharacteristic = getCharacteristic(HPlusConstants.UUID_CHARACTERISTIC_MEASURE);
+        ctrlCharacteristic = getCharacteristic(HPlusConstants.UUID_CHARACTERISTIC_CONTROL);
+
+
+        builder.notify(getCharacteristic(HPlusConstants.UUID_CHARACTERISTIC_MEASURE), true);
+        builder.setCallback(this);
+        builder.notify(measureCharacteristic, true);
+        //Initialize device
+        sendUserInfo(builder); //Sync preferences
+
+        gbDevice.setState(GBDevice.State.INITIALIZED);
+        gbDevice.sendDeviceUpdateIntent(getContext());
+
+        if (syncHelper == null) {
+            syncHelper = new HPlusHandlerThread(getDevice(), getContext(), this);
+            syncHelper.start();
+        }
+        syncHelper.sync();
+
+        getDevice().setFirmwareVersion("N/A");
+        getDevice().setFirmwareVersion2("N/A");
+
+        requestDeviceInfo(builder);
+
+        LOG.info("Initialization Done");
+
+        return builder;
+    }
+
+    private HPlusSupport sendUserInfo(TransactionBuilder builder) {
+        builder.write(ctrlCharacteristic, HPlusConstants.CMD_SET_PREF_START);
+        builder.write(ctrlCharacteristic, HPlusConstants.CMD_SET_PREF_START1);
+
+        syncPreferences(builder);
+
+        builder.write(ctrlCharacteristic, new byte[]{HPlusConstants.CMD_SET_CONF_END});
+        return this;
+    }
+
+    private HPlusSupport syncPreferences(TransactionBuilder transaction) {
+
+        if (deviceType == DeviceType.HPLUS || deviceType == DeviceType.EXRIZUK8) {
+            setSIT(transaction);          //Sync SIT Interval
+        }
+
+        setCurrentDate(transaction);
+        setCurrentTime(transaction);
+        setDayOfWeek(transaction);
+        setTimeMode(transaction);
+
+        setGender(transaction);
+        setAge(transaction);
+        setWeight(transaction);
+        setHeight(transaction);
+
+        setGoal(transaction);
+        setLanguage(transaction);
+        setScreenTime(transaction);
+        setUnit(transaction);
+        setAllDayHeart(transaction);
+
+        return this;
+    }
+
+    private HPlusSupport setLanguage(TransactionBuilder transaction) {
+        byte value = HPlusCoordinator.getLanguage(getDevice().getAddress());
+        transaction.write(ctrlCharacteristic, new byte[]{
+                HPlusConstants.CMD_SET_LANGUAGE,
+                value
+        });
+        return this;
+    }
+
+    public byte getTimeMode() {
+        if ("24h".equals(getDevicePrefs().getTimeFormat())) {
+            return HPlusConstants.ARG_TIMEMODE_24H;
+        } else {
+            return HPlusConstants.ARG_TIMEMODE_12H;
+        }
+    }
+
+    private HPlusSupport setTimeMode(TransactionBuilder transaction) {
+        transaction.write(ctrlCharacteristic, new byte[]{
+                HPlusConstants.CMD_SET_TIMEMODE,
+                getTimeMode()
+        });
+        return this;
+    }
+
+    private HPlusSupport setUnit(TransactionBuilder transaction) {
+        byte value = HPlusCoordinator.getUnit(getDevice().getAddress());
+        transaction.write(ctrlCharacteristic, new byte[]{
+                HPlusConstants.CMD_SET_UNITS,
+                value
+        });
+        return this;
+    }
+
+    private HPlusSupport setCurrentDate(TransactionBuilder transaction) {
+        Calendar c = GregorianCalendar.getInstance();
+        int year = c.get(Calendar.YEAR);
+        int month = c.get(Calendar.MONTH);
+        int day = c.get(Calendar.DAY_OF_MONTH);
+
+        transaction.write(ctrlCharacteristic, new byte[]{
+                HPlusConstants.CMD_SET_DATE,
+                (byte) ((year / 256) & 0xff),
+                (byte) (year % 256),
+                (byte) (month + 1),
+                (byte) (day)
+
+        });
+        return this;
+    }
+
+    private HPlusSupport setCurrentTime(TransactionBuilder transaction) {
+        Calendar c = GregorianCalendar.getInstance();
+
+        transaction.write(ctrlCharacteristic, new byte[]{
+                HPlusConstants.CMD_SET_TIME,
+                (byte) c.get(Calendar.HOUR_OF_DAY),
+                (byte) c.get(Calendar.MINUTE),
+                (byte) c.get(Calendar.SECOND)
+
+        });
+        return this;
+    }
+
+
+    private HPlusSupport setDayOfWeek(TransactionBuilder transaction) {
+        Calendar c = GregorianCalendar.getInstance();
+
+        transaction.write(ctrlCharacteristic, new byte[]{
+                HPlusConstants.CMD_SET_WEEK,
+                (byte) (c.get(Calendar.DAY_OF_WEEK) - 1)
+        });
+        return this;
+    }
+
+
+    private HPlusSupport setSIT(TransactionBuilder transaction) {
+
+        //Makibes F68 doesn't like this command.
+        //Just ignore.
+        if (deviceType == DeviceType.MAKIBESF68) {
+            return this;
+        }
+
+        int startTime = HPlusCoordinator.getSITStartTime(getDevice().getAddress());
+        int endTime = HPlusCoordinator.getSITEndTime(getDevice().getAddress());
+
+        Calendar now = GregorianCalendar.getInstance();
+
+        transaction.write(ctrlCharacteristic, new byte[]{
+                HPlusConstants.CMD_SET_SIT_INTERVAL,
+                (byte) ((startTime / 256) & 0xff),
+                (byte) (startTime % 256),
+                (byte) ((endTime / 256) & 0xff),
+                (byte) (endTime % 256),
+                0,
+                0,
+                (byte) ((now.get(Calendar.YEAR) / 256) & 0xff),
+                (byte) (now.get(Calendar.YEAR) % 256),
+                (byte) (now.get(Calendar.MONTH) + 1),
+                (byte) (now.get(Calendar.DAY_OF_MONTH)),
+                (byte) (now.get(Calendar.HOUR_OF_DAY)),
+                (byte) (now.get(Calendar.MINUTE)),
+                (byte) (now.get(Calendar.SECOND)),
+                0,
+                0,
+                0,
+                0
+
+        });
+        return this;
+    }
+
+    private HPlusSupport setWeight(TransactionBuilder transaction) {
+        byte value = HPlusCoordinator.getUserWeight();
+        transaction.write(ctrlCharacteristic, new byte[]{
+                HPlusConstants.CMD_SET_WEIGHT,
+                value
+
+        });
+        return this;
+    }
+
+    private HPlusSupport setHeight(TransactionBuilder transaction) {
+        byte value = HPlusCoordinator.getUserHeight();
+        transaction.write(ctrlCharacteristic, new byte[]{
+                HPlusConstants.CMD_HEIGHT,
+                value
+
+        });
+        return this;
+    }
+
+
+    private HPlusSupport setAge(TransactionBuilder transaction) {
+        byte value = HPlusCoordinator.getUserAge();
+        transaction.write(ctrlCharacteristic, new byte[]{
+                HPlusConstants.CMD_SET_AGE,
+                value
+
+        });
+        return this;
+    }
+
+    private HPlusSupport setGender(TransactionBuilder transaction) {
+        byte value = HPlusCoordinator.getUserGender();
+        transaction.write(ctrlCharacteristic, new byte[]{
+                HPlusConstants.CMD_SET_GENDER,
+                value
+
+        });
+        return this;
+    }
+
+
+    private HPlusSupport setGoal(TransactionBuilder transaction) {
+        int value = HPlusCoordinator.getGoal();
+        transaction.write(ctrlCharacteristic, new byte[]{
+                HPlusConstants.CMD_SET_GOAL,
+                (byte) ((value / 256) & 0xff),
+                (byte) (value % 256)
+
+        });
+        return this;
+    }
+
+
+    public byte getScreenTime() {
+        return (byte) (getDevicePrefs().getInt(HPlusConstants.PREF_HPLUS_SCREENTIME, 5) & 0xFF);
+    }
+
+    private HPlusSupport setScreenTime(TransactionBuilder transaction) {
+        transaction.write(ctrlCharacteristic, new byte[]{
+                HPlusConstants.CMD_SET_SCREENTIME,
+                getScreenTime()
+
+        });
+        return this;
+    }
+
+    public byte getAllDayHR() {
+        boolean value = (getDevicePrefs().getBoolean(HPlusConstants.PREF_HPLUS_ALLDAYHR, true));
+
+        if (value) {
+            return HPlusConstants.ARG_HEARTRATE_ALLDAY_ON;
+        } else {
+            return HPlusConstants.ARG_HEARTRATE_ALLDAY_OFF;
+        }
+    }
+
+    private HPlusSupport setAllDayHeart(TransactionBuilder transaction) {
+        transaction.write(ctrlCharacteristic, new byte[]{
+                HPlusConstants.CMD_SET_ALLDAY_HRM,
+                getAllDayHR()
+
+        });
+
+        return this;
+    }
+
+
+    private HPlusSupport setAlarm(TransactionBuilder transaction, Calendar t) {
+
+        byte hour = HPlusConstants.ARG_ALARM_DISABLE;
+        byte minute = HPlusConstants.ARG_ALARM_DISABLE;
+
+        if (t != null) {
+            hour = (byte) t.get(Calendar.HOUR_OF_DAY);
+            minute = (byte) t.get(Calendar.MINUTE);
+        }
+
+        transaction.write(ctrlCharacteristic, new byte[]{HPlusConstants.CMD_SET_ALARM,
+                hour,
+                minute});
+
+        return this;
+    }
+
+    private HPlusSupport setFindMe(TransactionBuilder transaction, boolean state) {
+        //TODO: Find how this works
+
+        byte[] msg = new byte[2];
+        msg[0] = HPlusConstants.CMD_SET_FINDME;
+
+        if (state)
+            msg[1] = HPlusConstants.ARG_FINDME_ON;
+        else
+            msg[1] = HPlusConstants.ARG_FINDME_OFF;
+
+        transaction.write(ctrlCharacteristic, msg);
+        return this;
+    }
+
+    private HPlusSupport requestDeviceInfo(TransactionBuilder builder) {
+        // HPlus devices seem to report some information in an alternative manner
+        builder.write(ctrlCharacteristic, new byte[]{HPlusConstants.CMD_GET_DEVICE_ID});
+        builder.write(ctrlCharacteristic, new byte[]{HPlusConstants.CMD_GET_VERSION});
+
+        return this;
+    }
+
+    @Override
+    public boolean useAutoConnect() {
+        return true;
+    }
+
+    private void handleDeviceInfo(DeviceInfo info) {
+        LOG.warn("Device info: " + info);
+    }
+
+    @Override
+    public void onNotification(NotificationSpec notificationSpec) {
+        //TODO: Show different notifications according to source as Band supports this
+        //LOG.info("OnNotification: Title: "+notificationSpec.title+" Body: "+notificationSpec.body+" Source: "+notificationSpec.sourceName+" Sender: "+notificationSpec.sender+" Subject: "+notificationSpec.subject);
+        showText(notificationSpec.title, notificationSpec.body);
+    }
+
+    @Override
+    public void onSetTime() {
+
+        try {
+            TransactionBuilder builder = performInitialized("time");
+
+            setCurrentDate(builder);
+            setCurrentTime(builder);
+            builder.queue(getQueue());
+        } catch (IOException e) {
+
+        }
+    }
+
+    @Override
+    public void onSetAlarms(ArrayList<? extends Alarm> alarms) {
+
+
+        try {
+            TransactionBuilder builder = performInitialized("alarm");
+
+            for (Alarm alarm : alarms) {
+
+                if (!alarm.getEnabled())
+                    continue;
+
+                if (alarm.getSmartWakeup()) //Not available
+                    continue;
+
+                Calendar t = AlarmUtils.toCalendar(alarm);
+                setAlarm(builder, t);
+                builder.queue(getQueue());
+
+                GB.toast(getContext(), getContext().getString(R.string.user_feedback_miband_set_alarms_ok), Toast.LENGTH_SHORT, GB.INFO);
+
+                return; //Only first alarm
+            }
+
+            setAlarm(builder, null);
+            builder.queue(getQueue());
+
+            GB.toast(getContext(), getContext().getString(R.string.user_feedback_all_alarms_disabled), Toast.LENGTH_SHORT, GB.INFO);
+        } catch (Exception e) {
+        }
+
+
+    }
+
+    @Override
+    public void onSetCallState(CallSpec callSpec) {
+        switch (callSpec.command) {
+            case CallSpec.CALL_INCOMING: {
+                showIncomingCall(callSpec.name, callSpec.number);
+                break;
+            }
+        }
+
+    }
+
+    @Override
+    public void onSetCannedMessages(CannedMessagesSpec cannedMessagesSpec) {
+        LOG.info("Canned Messages: " + cannedMessagesSpec);
+    }
+
+    @Override
+    public void onEnableRealtimeSteps(boolean enable) {
+        onEnableRealtimeHeartRateMeasurement(enable);
+    }
+
+    @Override
+    public void onFetchRecordedData(int dataTypes) {
+
+        if (syncHelper == null) {
+            syncHelper = new HPlusHandlerThread(gbDevice, getContext(), this);
+            syncHelper.start();
+        }
+
+        syncHelper.sync();
+    }
+
+    @Override
+    public void onReset(int flags) {
+        try {
+            getQueue().clear();
+
+            TransactionBuilder builder = performInitialized("Shutdown");
+            builder.write(ctrlCharacteristic, new byte[]{HPlusConstants.CMD_SHUTDOWN, HPlusConstants.ARG_SHUTDOWN_EN});
+            builder.queue(getQueue());
+        } catch (Exception e) {
+
+        }
+    }
+
+    @Override
+    public void onHeartRateTest() {
+        getQueue().clear();
+        try {
+            TransactionBuilder builder = performInitialized("HeartRateTest");
+
+            builder.write(ctrlCharacteristic, new byte[]{HPlusConstants.CMD_SET_HEARTRATE_STATE, HPlusConstants.ARG_HEARTRATE_MEASURE_ON}); //Set Real Time... ?
+            builder.queue(getQueue());
+        } catch (Exception e) {
+
+        }
+    }
+
+    @Override
+    public void onEnableRealtimeHeartRateMeasurement(boolean enable) {
+        try {
+            TransactionBuilder builder = performInitialized("realTimeHeartMeasurement");
+            byte state;
+
+            if (enable)
+                state = HPlusConstants.ARG_HEARTRATE_ALLDAY_ON;
+            else
+                state = HPlusConstants.ARG_HEARTRATE_ALLDAY_OFF;
+
+            builder.write(ctrlCharacteristic, new byte[]{HPlusConstants.CMD_SET_ALLDAY_HRM, state});
+            builder.queue(getQueue());
+        } catch (Exception e) {
+
+        }
+    }
+
+    @Override
+    public void onFindDevice(boolean start) {
+        try {
+            TransactionBuilder builder = performInitialized("findMe");
+
+            setFindMe(builder, start);
+            builder.queue(getQueue());
+        } catch (IOException e) {
+            GB.toast(getContext(), "Error toggling Find Me: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
+        }
+
+    }
+
+    @Override
+    public void onSetConstantVibration(int intensity) {
+        try {
+            TransactionBuilder builder = performInitialized("vibration");
+
+            byte[] msg = new byte[15];
+            msg[0] = HPlusConstants.CMD_SET_INCOMING_CALL_NUMBER;
+
+            for (int i = 0; i < msg.length - 1; i++)
+                msg[i + 1] = (byte) "Gadgetbridge".charAt(i);
+
+            builder.write(ctrlCharacteristic, msg);
+            builder.queue(getQueue());
+        } catch (IOException e) {
+            GB.toast(getContext(), "Error setting Vibration: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
+        }
+    }
+
+    @Override
+    public void onEnableHeartRateSleepSupport(boolean enable) {
+        onEnableRealtimeHeartRateMeasurement(enable);
+
+    }
+
+    @Override
+    public void onSendConfiguration(String config) {
+        TransactionBuilder builder;
+        try {
+            builder = performInitialized("Sending configuration for option: " + config);
+            switch (config) {
+                case SettingsActivity.PREF_MEASUREMENT_SYSTEM:
+                    setUnit(builder);
+                    break;
+            }
+            builder.queue(getQueue());
+        } catch (IOException e) {
+            GB.toast("Error setting configuration", Toast.LENGTH_LONG, GB.ERROR, e);
+        }
+    }
+
+    @Override
+    public void onTestNewFunction() {
+        LOG.info("Test New Function");
+    }
+
+    @Override
+    public void onSendWeather(ArrayList<WeatherSpec> weatherSpecs) {
+        WeatherSpec weatherSpec = weatherSpecs.get(0);
+        try {
+            TransactionBuilder builder = performInitialized("sendWeather");
+
+            int windSpeed = (int) weatherSpec.windSpeed;
+
+            CurrentPosition currentPosition = new CurrentPosition();
+
+            int altitude = 0;
+            if (currentPosition.getLastKnownLocation() != null) {
+                altitude = (int) currentPosition.getLastKnownLocation().getAltitude();
+            }
+
+            int weatherCode = HPlusWeatherCode.mapOpenWeatherConditionToHPlusCondition(weatherSpec.currentConditionCode);
+
+            LOG.info("[WEATHER] currentConditionCode={} altitude={} temp={}", weatherCode, altitude, weatherSpec.currentTemp);
+
+            byte[] weatherInfo = new byte[]{(byte) HPlusConstants.CMD_SET_WEATHER_STATE,
+                    (byte) ((weatherCode >> 8) & 255),
+                    (byte) (weatherCode & 255),
+                    (byte) weatherSpec.windDirection, (byte) 0, // weatherSpec.getWinPower(),
+                    (byte) ((windSpeed >> 8) & 255),
+                    (byte) (windSpeed & 255),
+                    (byte) (weatherSpec.currentTemp - 17),
+                    // base temperature information start at 17d celsius
+                    (byte) (weatherSpec.todayMaxTemp - 17), // base temperature information start at 18d celsius
+                    (byte) (weatherSpec.todayMinTemp - 17), // base temperature information start at 18d celsius
+                    (byte) 0, // Life Index always 0
+                    (byte) 0, // (byte) (weatherSpec.getPressure() & 255),
+                    (byte) 0, // (byte) ((weatherSpec.getPressure() >> 8) & 255),
+                    (byte) 0, // (byte) ((weatherSpec.getPressure() >> 16) & 255),
+                    (byte) 0, // (byte) ((weatherSpec.getPressure() >> 24) & 255),
+                    (byte) (byte) (altitude & 255),
+                    (byte) (byte) ((altitude >> 8) & 255),
+                    (byte) (byte) ((altitude >> 16) & 255),
+                    (byte) (byte) ((altitude >> 24) & 255),
+                    (byte) 0 // (byte)
+            };
+
+            builder.write(ctrlCharacteristic, weatherInfo);
+            builder.queue(getQueue());
+        } catch (IOException e) {
+            GB.toast(getContext(), "Error toggling Send Weather: " + e.getLocalizedMessage(), Toast.LENGTH_LONG,
+                    GB.ERROR);
+        }
+    }
+
+    public void setUnicodeSupport(boolean support) {
+        HPlusCoordinator.setUnicodeSupport(gbDevice.getAddress(), support);
+    }
+
+
+    private void showIncomingCall(String name, String rawNumber) {
+        try {
+
+            TransactionBuilder builder = performInitialized("incomingCall");
+
+            //Enable call notifications
+            builder.write(ctrlCharacteristic, new byte[]{HPlusConstants.CMD_ACTION_INCOMING_CALL, 1});
+
+            //Show Call Icon
+            builder.write(ctrlCharacteristic, new byte[]{HPlusConstants.CMD_SET_INCOMING_CALL, HPlusConstants.ARG_INCOMING_CALL});
+            builder.write(ctrlCharacteristic,
+                    new byte[]{HPlusConstants.CMD_SET_INCOMING_CALL, HPlusConstants.ARG_INCOMING_CALL});
+
+            byte space = encodeStringToDevice(" ")[0];
+
+            if (name != null) {
+                byte[] msg = new byte[13];
+
+                // Show call name
+                for (int i = 0; i < msg.length; i++)
+                    msg[i] = space;
+
+                byte[] nameBytes = encodeStringToDevice(name);
+                for (int i = 0; i < nameBytes.length && i < (msg.length - 1); i++)
+                    msg[i + 1] = nameBytes[i];
+
+                msg[0] = HPlusConstants.CMD_ACTION_DISPLAY_TEXT_NAME;
+                builder.write(ctrlCharacteristic, msg);
+
+                msg[0] = HPlusConstants.CMD_ACTION_DISPLAY_TEXT_NAME_CN;
+                builder.write(ctrlCharacteristic, msg);
+            }
+
+            if (rawNumber != null) {
+                StringBuilder number = new StringBuilder();
+
+                //Clean up number as the device only accepts digits
+                for (char c : rawNumber.toCharArray()) {
+                    if (Character.isDigit(c)) {
+                        number.append(c);
+                    }
+                }
+
+                byte[] msg = new byte[13];
+
+                //Show call number
+                for (int i = 0; i < msg.length; i++)
+                    msg[i] = space;
+
+                for (int i = 0; i < number.length() && i < (msg.length - 1); i++)
+                    msg[i + 1] = (byte) number.charAt(i);
+
+                msg[0] = HPlusConstants.CMD_SET_INCOMING_CALL_NUMBER;
+
+                builder.wait(200);
+                builder.write(ctrlCharacteristic, msg);
+            }
+
+            builder.queue(getQueue());
+        } catch (IOException e) {
+            GB.toast(getContext(), "Error showing incoming call: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
+
+        }
+    }
+
+    private void showText(String title, String body) {
+        try {
+            TransactionBuilder builder = performInitialized("notification");
+
+            String message = "";
+
+            if (title != null && title.length() > 0) {
+                message += title + " : ";
+                //message = StringUtils.pad(StringUtils.truncate(title, 16), 16); // Limit title to top row
+            }
+
+            if (body != null) {
+                message += body;
+            }
+
+            byte[] messageBytes = encodeStringToDevice(message);
+
+            int length = messageBytes.length / 17;
+
+            int linesNumbers = HPlusCoordinator.getNotificationLinesNumber(this.gbDevice.getAddress());
+            length = length > linesNumbers ? linesNumbers : length;
+            LOG.info("msglength:" + messageBytes.length + " length:" + length + " linesNumbers:" + linesNumbers);
+
+            if (HPlusCoordinator.getDisplayIncomingMessageIcon(this.gbDevice.getAddress()))
+                builder.write(ctrlCharacteristic,
+                        new byte[]{HPlusConstants.CMD_SET_INCOMING_MESSAGE, HPlusConstants.ARG_INCOMING_MESSAGE});
+
+            int remaining = Math.min(255, (messageBytes.length % 17 > 0) ? length + 1 : length);
+
+            byte[] msgSpace = new byte[20];
+            byte[] msg;
+
+            Arrays.fill(msgSpace, encodeStringToDevice(" ")[0]);
+
+            msgSpace[0] = HPlusConstants.CMD_ACTION_DISPLAY_TEXT;
+            msgSpace[1] = (byte) remaining;
+
+            int message_index = 0;
+            int i = 3;
+
+            msg = msgSpace.clone();
+            for (int j = 0; j < messageBytes.length; j++) {
+                msg[i++] = messageBytes[j];
+
+                if (i == msg.length) {
+                    message_index++;
+                    msg[2] = (byte) message_index;
+                    builder.write(ctrlCharacteristic, msg);
+
+                    msg = msgSpace.clone();
+                    if (message_index < remaining)
+                        i = 3;
+                    else
+                        break;
+                }
+            }
+
+            msg[2] = (byte) remaining;
+
+            builder.write(ctrlCharacteristic, msg);
+            builder.queue(getQueue());
+        } catch (IOException e) {
+            GB.toast(getContext(), "Error showing device Notification: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
+
+        }
+    }
+
+    private void close() {
+        if (syncHelper != null) {
+            syncHelper.quit();
+            syncHelper.interrupt();
+            syncHelper = null;
+        }
+    }
+
+    /**
+     * HPlus devices accept a subset of GB2312 with some modifications.
+     * This function will apply a custom transliteration.
+     * While related to the methods implemented in LanguageUtils. These are specific for HPLUS
+     *
+     * @param s The String to transliterate
+     * @return An array of bytes ready to be sent to the device
+     */
+    private byte[] encodeStringToDevice(String s) {
+
+        List<Byte> outBytes = new ArrayList<Byte>();
+        boolean unicode = HPlusCoordinator.getUnicodeSupport(this.gbDevice.getAddress());
+        LOG.info("Encode String: Unicode=" + unicode);
+
+        if (unicode) {
+            try {
+                return s.getBytes("Unicode");
+            } catch (UnsupportedEncodingException e) {
+                // Fallback. Result string may be strange, but better than nothing
+                LOG.error("Could not convert String to Bytes: " + e.getMessage());
+                return s.getBytes();
+            }
+        }
+
+        for (int i = 0; i < s.length(); i++) {
+            Character c = s.charAt(i);
+            byte[] cs;
+
+            if (HPlusConstants.transliterateMap.containsKey(c)) {
+                cs = HPlusConstants.transliterateMap.get(c);
+            } else {
+                try {
+                    cs = c.toString().getBytes("GB2312");
+                } catch (UnsupportedEncodingException e) {
+                    //Fallback. Result string may be strange, but better than nothing
+                    cs = c.toString().getBytes();
+                    LOG.error("Could not convert String to Bytes: " + e.getMessage());
+                }
+            }
+
+            for (int j = 0; j < cs.length; j++)
+                outBytes.add(cs[j]);
+        }
+
+        return ArrayUtils.toPrimitive(outBytes.toArray(new Byte[0]));
+    }
+
+    @Override
+    public boolean onCharacteristicChanged(BluetoothGatt gatt,
+                                           BluetoothGattCharacteristic characteristic) {
+        if (super.onCharacteristicChanged(gatt, characteristic)) {
+            return true;
+        }
+
+        UUID characteristicUUID = characteristic.getUuid();
+        byte[] data = characteristic.getValue();
+        if (data.length == 0)
+            return true;
+
+        switch (data[0]) {
+            case HPlusConstants.DATA_VERSION:
+            case HPlusConstants.DATA_VERSION1:
+                return syncHelper.processVersion(data);
+
+            case HPlusConstants.DATA_STATS:
+                boolean result = syncHelper.processRealtimeStats(data, HPlusCoordinator.getUserAge());
+                if (result) {
+                    processExtraInfo(data);
+                }
+                return result;
+
+            case HPlusConstants.DATA_SLEEP:
+                return syncHelper.processIncomingSleepData(data);
+
+            case HPlusConstants.DATA_STEPS:
+                return syncHelper.processDaySummary(data);
+
+            case HPlusConstants.DATA_DAY_SUMMARY:
+            case HPlusConstants.DATA_DAY_SUMMARY_ALT:
+                return syncHelper.processIncomingDaySlotData(data, HPlusCoordinator.getUserAge());
+            case HPlusConstants.DATA_UNKNOWN:
+                return true;
+            default:
+
+                LOG.info("Unhandled characteristic change: " + characteristicUUID + " code: " + Arrays.toString(data));
+                return true;
+        }
+    }
+
+    private void processExtraInfo(byte[] data) {
+        try {
+            HPlusDataRecordRealtime record = new HPlusDataRecordRealtime(data, HPlusCoordinator.getUserAge());
+
+            handleBatteryInfo(record.battery);
+
+            String DEVINFO_STEP = getContext().getString(R.string.chart_steps) + ": ";
+            String DEVINFO_DISTANCE = getContext().getString(R.string.distance) + ": ";
+            String DEVINFO_CALORY = getContext().getString(R.string.calories) + ": ";
+            String DEVINFO_HEART = getContext().getString(R.string.charts_legend_heartrate) + ": ";
+
+            String info = "";
+            if (record.steps > 0) {
+                info += DEVINFO_STEP + String.valueOf(record.steps) + "   ";
+            }
+            if (record.distance > 0) {
+                info += DEVINFO_DISTANCE + String.valueOf(record.distance) + "   ";
+            }
+            if (record.calories > 0) {
+                info += DEVINFO_CALORY + String.valueOf(record.calories) + "   ";
+            }
+            if (record.heartRate > 0) {
+                info += DEVINFO_HEART + String.valueOf(record.heartRate) + "   ";
+            }
+
+            if (!info.equals("")) {
+                getDevice().addDeviceInfo(new GenericItem("", info));
+            }
+        } catch (IllegalArgumentException e) {
+            LOG.info((e.getMessage()));
+        }
+    }
+
+    private void handleBatteryInfo(byte data) {
+        if (batteryCmd.level != (short) data) {
+            batteryCmd.level = (short) data;
+            handleGBDeviceEvent(batteryCmd);
+        }
+    }
+
+    @Override
+    public boolean getImplicitCallbackModify() {
+        return true;
+    }
+
+    @Override
+    public boolean getSendWriteRequestResponse() {
+        return false;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/AbstractHuamiActivityDetailsParser.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/AbstractHuamiActivityDetailsParser.java
new file mode 100644
index 0000000..89b289b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/AbstractHuamiActivityDetailsParser.java
@@ -0,0 +1,47 @@
+/*  Copyright (C) 2022-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+
+import nodomain.freeyourgadget.gadgetbridge.GBException;
+import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityTrack;
+import nodomain.freeyourgadget.gadgetbridge.model.GPSCoordinate;
+
+public abstract class AbstractHuamiActivityDetailsParser {
+    private static final BigDecimal HUAMI_TO_DECIMAL_DEGREES_DIVISOR = new BigDecimal("3000000.0");
+
+    public abstract ActivityTrack parse(final byte[] bytes) throws GBException;
+
+    public static double convertHuamiValueToDecimalDegrees(final long huamiValue) {
+        BigDecimal result = new BigDecimal(huamiValue)
+                .divide(HUAMI_TO_DECIMAL_DEGREES_DIVISOR, GPSCoordinate.GPS_DECIMAL_DEGREES_SCALE, RoundingMode.HALF_UP);
+        return result.doubleValue();
+    }
+
+    protected static String createActivityName(final BaseActivitySummary summary) {
+        String name = summary.getName();
+        String nameText = "";
+        Long id = summary.getId();
+        if (name != null) {
+            nameText = name + " - ";
+        }
+        return nameText + id;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/AbstractHuamiFirmwareInfo.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/AbstractHuamiFirmwareInfo.java
new file mode 100644
index 0000000..386f25a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/AbstractHuamiFirmwareInfo.java
@@ -0,0 +1,127 @@
+/*  Copyright (C) 2022-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami;
+
+import android.graphics.Bitmap;
+
+import androidx.annotation.Nullable;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Map;
+
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.util.ArrayUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.CheckSums;
+
+public abstract class AbstractHuamiFirmwareInfo {
+    private byte[] bytes;
+
+    private int crc16;
+    private int crc32;
+
+    protected HuamiFirmwareType firmwareType;
+
+    public AbstractHuamiFirmwareInfo(byte[] bytes) {
+        setBytes(bytes);
+        this.firmwareType = determineFirmwareType(bytes);
+    }
+
+    public boolean isHeaderValid() {
+        return getFirmwareType() != HuamiFirmwareType.INVALID;
+    }
+
+    public void checkValid() throws IllegalArgumentException {
+    }
+
+    public int[] getWhitelistedVersions() {
+        return ArrayUtils.toIntArray(getCrcMap().keySet());
+    }
+
+    /**
+     * @return the size of the firmware in number of bytes.
+     */
+    public int getSize() {
+        return bytes.length;
+    }
+
+    public byte[] getBytes() {
+        return bytes;
+    }
+
+    public int getCrc16() {
+        return crc16;
+    }
+
+    public int getCrc32() {
+        return crc32;
+    }
+
+    public void setBytes(final byte[] bytes) {
+        this.bytes = bytes;
+        this.crc16 = CheckSums.getCRC16(bytes);
+        this.crc32 = CheckSums.getCRC32(bytes);
+    }
+
+    public int getFirmwareVersion() {
+        return getCrc16(); // HACK until we know how to determine the version from the fw bytes
+    }
+
+    public HuamiFirmwareType getFirmwareType() {
+        return firmwareType;
+    }
+
+    public void unsetFwBytes() {
+        this.bytes = null;
+    }
+
+    @Nullable
+    public Bitmap getPreview() {
+        return null;
+    }
+
+    public abstract String toVersion(int crc16);
+
+    public abstract boolean isGenerallyCompatibleWith(GBDevice device);
+
+    protected abstract Map<Integer, String> getCrcMap();
+
+    protected abstract HuamiFirmwareType determineFirmwareType(byte[] bytes);
+
+    public static boolean searchString32BitAligned(byte[] fwbytes, String findString) {
+        ByteBuffer stringBuf = ByteBuffer.wrap((findString + "\0").getBytes());
+        stringBuf.order(ByteOrder.BIG_ENDIAN);
+        int[] findArray = new int[stringBuf.remaining() / 4];
+        for (int i = 0; i < findArray.length; i++) {
+            findArray[i] = stringBuf.getInt();
+        }
+
+        ByteBuffer buf = ByteBuffer.wrap(fwbytes);
+        buf.order(ByteOrder.BIG_ENDIAN);
+        while (buf.remaining() > 3) {
+            int arrayPos = 0;
+            while (arrayPos < findArray.length && buf.remaining() > 3 && (buf.getInt() == findArray[arrayPos])) {
+                arrayPos++;
+            }
+            if (arrayPos == findArray.length) {
+                return true;
+            }
+        }
+        return false;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/AbstractHuamiOperation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/AbstractHuamiOperation.java
new file mode 100644
index 0000000..33e224f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/AbstractHuamiOperation.java
@@ -0,0 +1,33 @@
+/*  Copyright (C) 2018-2024 Andreas Shimokawa, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami;
+
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.AbstractMiBandOperation;
+
+public abstract class AbstractHuamiOperation extends AbstractMiBandOperation<HuamiSupport> {
+    protected AbstractHuamiOperation(HuamiSupport support) {
+        super(support);
+    }
+
+    @Override
+    protected void enableOtherNotifications(final TransactionBuilder builder, final boolean enable) {
+        // TODO: check which notifications we should disable and re-enable here
+//        builder.notify(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_REALTIME_STEPS), enable)
+//                .notify(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_SENSOR_DATA), enable);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/Huami2021ChunkedDecoder.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/Huami2021ChunkedDecoder.java
new file mode 100644
index 0000000..d9e22ad
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/Huami2021ChunkedDecoder.java
@@ -0,0 +1,148 @@
+/*  Copyright (C) 2022-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami;
+
+import org.apache.commons.lang3.ArrayUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+
+import nodomain.freeyourgadget.gadgetbridge.util.CryptoUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class Huami2021ChunkedDecoder {
+    private static final Logger LOG = LoggerFactory.getLogger(Huami2021ChunkedDecoder.class);
+
+    private Byte currentHandle;
+    private int currentType;
+    private int currentLength;
+    ByteBuffer reassemblyBuffer;
+
+    // Keep track of last handle and count for acks
+    private byte lastHandle;
+    private byte lastCount;
+
+    private volatile byte[] sharedSessionKey;
+
+    private Huami2021Handler huami2021Handler;
+    private final boolean force2021Protocol;
+
+    public Huami2021ChunkedDecoder(final Huami2021Handler huami2021Handler,
+                                   final boolean force2021Protocol) {
+        this.huami2021Handler = huami2021Handler;
+        this.force2021Protocol = force2021Protocol;
+    }
+
+    public void setEncryptionParameters(final byte[] sharedSessionKey) {
+        this.sharedSessionKey = sharedSessionKey;
+    }
+
+    public void setHuami2021Handler(final Huami2021Handler huami2021Handler) {
+        this.huami2021Handler = huami2021Handler;
+    }
+
+    public byte getLastHandle() {
+        return lastHandle;
+    }
+
+    public byte getLastCount() {
+        return lastCount;
+    }
+
+    public boolean decode(final byte[] data) {
+        int i = 0;
+        if (data[i++] != 0x03) {
+            LOG.warn("Ignoring non-chunked payload");
+            return false;
+        }
+        final byte flags = data[i++];
+        final boolean encrypted = ((flags & 0x08) == 0x08);
+        final boolean firstChunk = ((flags & 0x01) == 0x01);
+        final boolean lastChunk = ((flags & 0x02) == 0x02);
+        final boolean needsAck = ((flags & 0x04) == 0x04);
+
+        if (force2021Protocol) {
+            i++; // skip extended header
+        }
+        final byte handle = data[i++];
+        if (currentHandle != null && currentHandle != handle) {
+            LOG.warn("ignoring handle {}, expected {}", handle, currentHandle);
+            return false;
+        }
+        lastHandle = handle;
+        lastCount = data[i++];
+        if (firstChunk) { // beginning
+            int full_length = (data[i++] & 0xff) | ((data[i++] & 0xff) << 8) | ((data[i++] & 0xff) << 16) | ((data[i++] & 0xff) << 24);
+            currentLength = full_length;
+            if (encrypted) {
+                int encrypted_length = full_length + 8;
+                int overflow = encrypted_length % 16;
+                if (overflow > 0) {
+                    encrypted_length += (16 - overflow);
+                }
+                full_length = encrypted_length;
+            }
+            reassemblyBuffer = ByteBuffer.allocate(full_length);
+            currentType = (data[i++] & 0xff) | ((data[i++] & 0xff) << 8);
+            currentHandle = handle;
+        }
+        reassemblyBuffer.put(data, i, data.length - i);
+        if (lastChunk) { // end
+            byte[] buf = reassemblyBuffer.array();
+            if (encrypted) {
+                if (sharedSessionKey == null) {
+                    // Should never happen
+                    LOG.warn("Got encrypted message, but there's no shared session key");
+                    currentHandle = null;
+                    currentType = 0;
+                    return false;
+                }
+
+                byte[] messagekey = new byte[16];
+                for (int j = 0; j < 16; j++) {
+                    messagekey[j] = (byte) (sharedSessionKey[j] ^ handle);
+                }
+                try {
+                    buf = CryptoUtils.decryptAES(buf, messagekey);
+                    buf = ArrayUtils.subarray(buf, 0, currentLength);
+                } catch (Exception e) {
+                    LOG.warn("error decrypting " + e);
+                    currentHandle = null;
+                    currentType = 0;
+                    return false;
+                }
+            }
+            LOG.debug(
+                    "{} data {}: {}",
+                    encrypted ? "Decrypted" : "Plaintext",
+                    String.format("0x%04x", currentType),
+                    GB.hexdump(buf)
+            );
+
+            try {
+                huami2021Handler.handle2021Payload((short) currentType, buf);
+            } catch (final Exception e) {
+                LOG.error("Failed to handle payload", e);
+            }
+            currentHandle = null;
+            currentType = 0;
+        }
+
+        return needsAck;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/Huami2021ChunkedEncoder.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/Huami2021ChunkedEncoder.java
new file mode 100644
index 0000000..04908ef
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/Huami2021ChunkedEncoder.java
@@ -0,0 +1,163 @@
+/*  Copyright (C) 2022-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami;
+
+import android.bluetooth.BluetoothGattCharacteristic;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.util.CheckSums;
+import nodomain.freeyourgadget.gadgetbridge.util.CryptoUtils;
+
+public class Huami2021ChunkedEncoder {
+    private static final Logger LOG = LoggerFactory.getLogger(Huami2021ChunkedEncoder.class);
+
+    private final BluetoothGattCharacteristic characteristicChunked2021Write;
+
+    private byte writeHandle;
+
+    // These must be volatile, since they are set by a different thread. Sometimes, GB might
+    // attempt to encode a payload before they were set, which will make them not be propagated
+    // to that thread later.
+    private volatile int encryptedSequenceNr;
+    private volatile byte[] sharedSessionKey;
+
+    private final boolean force2021Protocol;
+    private volatile int mMTU = 23;
+
+    public Huami2021ChunkedEncoder(final BluetoothGattCharacteristic characteristicChunked2021Write,
+                                   final boolean force2021Protocol,
+                                   final int mMTU) {
+        this.characteristicChunked2021Write = characteristicChunked2021Write;
+        this.force2021Protocol = force2021Protocol;
+        this.mMTU = mMTU;
+    }
+
+    public synchronized void setEncryptionParameters(final int encryptedSequenceNr, final byte[] sharedSessionKey) {
+        this.encryptedSequenceNr = encryptedSequenceNr;
+        this.sharedSessionKey = sharedSessionKey;
+    }
+
+    public synchronized void setMTU(int mMTU) {
+        this.mMTU = mMTU;
+    }
+
+    public synchronized void write(final TransactionBuilder builder,
+                                   final short type,
+                                   byte[] data,
+                                   final boolean extended_flags,
+                                   final boolean encrypt) {
+        if (encrypt && sharedSessionKey == null) {
+            LOG.error("Can't encrypt without the shared session key");
+            return;
+        }
+
+        writeHandle++;
+
+        int remaining = data.length;
+        int length = data.length;
+        byte count = 0;
+        int header_size = 10;
+
+        if (extended_flags) {
+            header_size++;
+        }
+
+        if (extended_flags && encrypt) {
+            byte[] messagekey = new byte[16];
+            for (int i = 0; i < 16; i++) {
+                messagekey[i] = (byte) (sharedSessionKey[i] ^ writeHandle);
+            }
+            int encrypted_length = length + 8;
+            int overflow = encrypted_length % 16;
+            if (overflow > 0) {
+                encrypted_length += (16 - overflow);
+            }
+
+            byte[] encryptable_payload = new byte[encrypted_length];
+            System.arraycopy(data, 0, encryptable_payload, 0, length);
+            encryptable_payload[length] = (byte) (encryptedSequenceNr & 0xff);
+            encryptable_payload[length + 1] = (byte) ((encryptedSequenceNr >> 8) & 0xff);
+            encryptable_payload[length + 2] = (byte) ((encryptedSequenceNr >> 16) & 0xff);
+            encryptable_payload[length + 3] = (byte) ((encryptedSequenceNr >> 24) & 0xff);
+            encryptedSequenceNr++;
+            int checksum = CheckSums.getCRC32(encryptable_payload, 0, length + 4);
+            encryptable_payload[length + 4] = (byte) (checksum & 0xff);
+            encryptable_payload[length + 5] = (byte) ((checksum >> 8) & 0xff);
+            encryptable_payload[length + 6] = (byte) ((checksum >> 16) & 0xff);
+            encryptable_payload[length + 7] = (byte) ((checksum >> 24) & 0xff);
+            remaining = encrypted_length;
+            try {
+                data = CryptoUtils.encryptAES(encryptable_payload, messagekey);
+            } catch (Exception e) {
+                LOG.error("error while encrypting", e);
+                return;
+            }
+
+        }
+
+        while (remaining > 0) {
+            int MAX_CHUNKLENGTH = mMTU - 3 - header_size;
+            int copybytes = Math.min(remaining, MAX_CHUNKLENGTH);
+            byte[] chunk = new byte[copybytes + header_size];
+
+            byte flags = 0;
+            if (encrypt) {
+                flags |= 0x08;
+            }
+            if (count == 0) {
+                flags |= 0x01;
+                int i = 4;
+                if (extended_flags) {
+                    i++;
+                }
+                chunk[i++] = (byte) (length & 0xff);
+                chunk[i++] = (byte) ((length >> 8) & 0xff);
+                chunk[i++] = (byte) ((length >> 16) & 0xff);
+                chunk[i++] = (byte) ((length >> 24) & 0xff);
+                chunk[i++] = (byte) (type & 0xff);
+                chunk[i] = (byte) ((type >> 8) & 0xff);
+            }
+            if (remaining <= MAX_CHUNKLENGTH) {
+                flags |= 0x06; // last chunk?
+            }
+            chunk[0] = 0x03;
+            chunk[1] = flags;
+            if (extended_flags) {
+                chunk[2] = 0;
+                chunk[3] = writeHandle;
+                chunk[4] = count;
+            } else {
+                chunk[2] = writeHandle;
+                chunk[3] = count;
+            }
+
+            System.arraycopy(data, data.length - remaining, chunk, header_size, copybytes);
+            builder.write(characteristicChunked2021Write, chunk);
+            remaining -= copybytes;
+            header_size = 4;
+
+            if (extended_flags) {
+                header_size++;
+            }
+
+            count++;
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/Huami2021Handler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/Huami2021Handler.java
new file mode 100644
index 0000000..3873650
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/Huami2021Handler.java
@@ -0,0 +1,21 @@
+/*  Copyright (C) 2022-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami;
+
+public interface Huami2021Handler {
+    void handle2021Payload(short type, byte[] payload);
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiActivityDetailsParser.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiActivityDetailsParser.java
new file mode 100644
index 0000000..2f81398
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiActivityDetailsParser.java
@@ -0,0 +1,295 @@
+/*  Copyright (C) 2019-2024 Andreas Shimokawa, Jos茅 Rebelo, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import nodomain.freeyourgadget.gadgetbridge.GBException;
+import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityPoint;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityTrack;
+import nodomain.freeyourgadget.gadgetbridge.model.GPSCoordinate;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class HuamiActivityDetailsParser extends AbstractHuamiActivityDetailsParser {
+    private static final Logger LOG = LoggerFactory.getLogger(HuamiActivityDetailsParser.class);
+
+    private static final byte TYPE_GPS = 0;
+    private static final byte TYPE_HR = 1;
+    private static final byte TYPE_PAUSE = 2;
+    private static final byte TYPE_RESUME = 3;
+    private static final byte TYPE_SPEED4 = 4;
+    private static final byte TYPE_SPEED5 = 5;
+    private static final byte TYPE_SPEED6 = 6;
+    private static final byte TYPE_SWIMMING = 8;
+
+    private final ActivityTrack activityTrack;
+    private final Date baseDate;
+    private long baseLongitude;
+    private long baseLatitude;
+    private int baseAltitude;
+    private ActivityPoint lastActivityPoint;
+
+    public void setSkipCounterByte(boolean skipCounterByte) {
+        this.skipCounterByte = skipCounterByte;
+    }
+
+    private boolean skipCounterByte;
+
+    public HuamiActivityDetailsParser(BaseActivitySummary summary) {
+        this.baseLongitude = summary.getBaseLongitude();
+        this.baseLatitude = summary.getBaseLatitude();
+        this.baseAltitude = summary.getBaseAltitude();
+        this.baseDate = summary.getStartTime();
+
+        this.activityTrack = new ActivityTrack();
+        activityTrack.setUser(summary.getUser());
+        activityTrack.setDevice(summary.getDevice());
+        activityTrack.setName(createActivityName(summary));
+    }
+
+    public ActivityTrack parse(byte[] bytes) throws GBException {
+        int i = 0;
+        try {
+            long totalTimeOffset = 0;
+            int lastTimeOffset = 0;
+            while (i < bytes.length) {
+                if (skipCounterByte && (i % 17) == 0) {
+                    i++;
+                }
+
+                byte type = bytes[i++]; // lgtm [java/index-out-of-bounds]]
+                int timeOffset = BLETypeConversions.toUnsigned(bytes[i++]); // lgtm [java/index-out-of-bounds]
+                // handle timeOffset overflows (1 byte, always increasing, relative to base)
+                if (lastTimeOffset <= timeOffset) {
+                    timeOffset = timeOffset - lastTimeOffset;
+                    lastTimeOffset += timeOffset;
+                } else {
+                    lastTimeOffset = timeOffset;
+                }
+                totalTimeOffset += timeOffset;
+
+                switch (type) {
+                    case TYPE_GPS:
+                        i += consumeGPSAndUpdateBaseLocation(bytes, i, totalTimeOffset);
+                        break;
+                    case TYPE_HR:
+                        i += consumeHeartRate(bytes, i, totalTimeOffset);
+                        break;
+                    case TYPE_PAUSE:
+                        i += consumePause(bytes, i);
+                        break;
+                    case TYPE_RESUME:
+                        i += consumeResume(bytes, i);
+                        break;
+                    case TYPE_SPEED4:
+                        i += consumeSpeed4(bytes, i);
+                        break;
+                    case TYPE_SPEED5:
+                        i += consumeSpeed5(bytes, i);
+                        break;
+                    case TYPE_SPEED6:
+                        i += consumeSpeed6(bytes, i);
+                        break;
+                    case TYPE_SWIMMING:
+                        i += consumeSwimming(bytes, i);
+                        break;
+                    default:
+                        LOG.warn("unknown packet type" + type);
+                        i+=6;
+                }
+            }
+        } catch (IndexOutOfBoundsException ex) {
+            throw new GBException("Error parsing activity details: " + ex.getMessage(), ex);
+        }
+
+        fixupMissingTimestamps(activityTrack);
+
+        return activityTrack;
+    }
+
+    private void fixupMissingTimestamps(ActivityTrack activityTrack) {
+        try {
+            int pointer = 0;
+            List<ActivityPoint> activityPointList = new ArrayList<>();
+            for (List<ActivityPoint> segment : activityTrack.getSegments()) {
+                activityPointList.addAll(segment);
+            }
+
+            Date gpsStartTime = null;
+            List<ActivityPoint> entriesToFixUp = new ArrayList<>();
+            while (pointer < activityPointList.size() - 1) {
+                ActivityPoint activityPoint = activityPointList.get(pointer);
+                if (activityPoint.getLocation() == null) {
+                    pointer++;
+                    continue;
+                }
+                if (activityPoint.getTime().equals(activityPointList.get(pointer + 1).getTime())) {
+                    entriesToFixUp.add(activityPoint);
+                } else {
+                    // found the first activity point with a proper timestamp
+                    entriesToFixUp.add(activityPoint);
+                    gpsStartTime = activityPointList.get(pointer + 1).getTime();
+                    break;
+                }
+                pointer++;
+            }
+            if (gpsStartTime != null) {
+                // now adjust those entries without a timestamp
+                long differenceInSec = TimeUnit.SECONDS.convert(Math.abs(gpsStartTime.getTime() - baseDate.getTime()), TimeUnit.MILLISECONDS);
+
+                double multiplier = (double) differenceInSec / (double) (entriesToFixUp.size());
+
+                for (int j = 0; j < entriesToFixUp.size(); j++) {
+                    long timeOffsetSeconds = Math.round(j * multiplier);
+                    entriesToFixUp.get(j).setTime(makeAbsolute(timeOffsetSeconds));
+                }
+            }
+        } catch (Exception ex) {
+            LOG.warn("Error cleaning activity details", ex);
+        }
+    }
+
+    private int consumeGPSAndUpdateBaseLocation(byte[] bytes, int offset, long timeOffset) {
+        int i = 0;
+        int longitudeDelta = BLETypeConversions.toInt16(bytes[offset + i++], bytes[offset + i++]);
+        int latitudeDelta = BLETypeConversions.toInt16(bytes[offset + i++], bytes[offset + i++]);
+        int altitudeDelta = BLETypeConversions.toInt16(bytes[offset + i++], bytes[offset + i++]);
+
+        baseLongitude += longitudeDelta;
+        baseLatitude += latitudeDelta;
+        if (baseAltitude != -20000) {
+            baseAltitude += altitudeDelta;
+        }
+
+        GPSCoordinate coordinate = new GPSCoordinate(
+                convertHuamiValueToDecimalDegrees(baseLongitude),
+                convertHuamiValueToDecimalDegrees(baseLatitude),
+                baseAltitude);
+
+        ActivityPoint ap = getActivityPointFor(timeOffset, coordinate);
+        ap.setLocation(coordinate);
+        add(ap);
+
+        return i;
+    }
+
+    private int consumeHeartRate(byte[] bytes, int offset, long timeOffsetSeconds) {
+        int v1 = BLETypeConversions.toUint16(bytes[offset]);
+        int v2 = BLETypeConversions.toUint16(bytes[offset + 1]);
+        int v3 = BLETypeConversions.toUint16(bytes[offset + 2]);
+        int v4 = BLETypeConversions.toUint16(bytes[offset + 3]);
+        int v5 = BLETypeConversions.toUint16(bytes[offset + 4]);
+        int v6 = BLETypeConversions.toUint16(bytes[offset + 5]);
+
+        if (v2 == 0 && v3 == 0 && v4 == 0 && v5 == 0 && v6 == 0) {
+            // new version
+            LOG.info("detected heart rate in 'new' version format");
+            ActivityPoint ap = getActivityPointFor(timeOffsetSeconds);
+            ap.setHeartRate(v1);
+            add(ap);
+        } else {
+            ActivityPoint ap = getActivityPointFor(v1);
+            ap.setHeartRate(v2);
+            add(ap);
+
+            ap = getActivityPointFor(v3);
+            ap.setHeartRate(v4);
+            add(ap);
+
+            ap = getActivityPointFor(v5);
+            ap.setHeartRate(v6);
+            add(ap);
+        }
+        return 6;
+    }
+
+    private ActivityPoint getActivityPointFor(long timeOffsetSeconds) {
+        Date time = makeAbsolute(timeOffsetSeconds);
+        if (lastActivityPoint != null) {
+            if (lastActivityPoint.getTime().equals(time)) {
+                return lastActivityPoint;
+            }
+        }
+        return new ActivityPoint(time);
+    }
+
+    private ActivityPoint getActivityPointFor(long timeOffsetSeconds, GPSCoordinate gpsCoordinate) {
+        Date time = makeAbsolute(timeOffsetSeconds);
+        if (lastActivityPoint != null) {
+            if (lastActivityPoint.getTime().equals(time)) {
+                if (lastActivityPoint.getLocation() != null && !lastActivityPoint.getLocation().equals(gpsCoordinate)) {
+                    return new ActivityPoint(time);
+                }
+                return lastActivityPoint;
+            }
+        }
+        return new ActivityPoint(time);
+    }
+
+    private Date makeAbsolute(long timeOffsetSeconds) {
+        return new Date(baseDate.getTime() + timeOffsetSeconds * 1000);
+    }
+
+    private void add(ActivityPoint ap) {
+        if (ap != lastActivityPoint) {
+            lastActivityPoint = ap;
+            activityTrack.addTrackPoint(ap);
+        } else {
+            LOG.info("skipping point!");
+        }
+    }
+
+    private int consumePause(byte[] bytes, int offset) {
+        LOG.debug("got pause packet: " + GB.hexdump(bytes, offset, 6));
+        return 6;
+    }
+
+    private int consumeResume(byte[] bytes, int offset) {
+        LOG.debug("got resume package: " + GB.hexdump(bytes, offset, 6));
+        return 6;
+    }
+
+    private int consumeSpeed4(byte[] bytes, int offset) {
+        LOG.debug("got packet type 4 (speed): " + GB.hexdump(bytes, offset, 6));
+        return 6;
+    }
+
+    private int consumeSpeed5(byte[] bytes, int offset) {
+        LOG.debug("got packet type 5 (speed): " + GB.hexdump(bytes, offset, 6));
+        return 6;
+    }
+
+    private int consumeSpeed6(byte[] bytes, int offset) {
+        LOG.debug("got packet type 6 (speed): " + GB.hexdump(bytes, offset, 6));
+        return 6;
+    }
+
+    private int consumeSwimming(byte[] bytes, int offset) {
+        LOG.debug("got packet type 8 (swimming?): " + GB.hexdump(bytes, offset, 6));
+        return 6;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiBatteryInfo.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiBatteryInfo.java
new file mode 100644
index 0000000..43af026
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiBatteryInfo.java
@@ -0,0 +1,121 @@
+/*  Copyright (C) 2018-2024 Andreas Shimokawa, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami;
+
+import java.util.GregorianCalendar;
+
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandDateConverter;
+import nodomain.freeyourgadget.gadgetbridge.model.BatteryState;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.AbstractInfo;
+
+//00000006-0000-3512-2118-0009af100700
+//
+//        f			= ?
+//        30		= 48%
+//        00		= 00 = STATUS_NORMAL, 01 = STATUS_CHARGING
+//        e0 07		= 2016
+//        0b		= 11
+//        1a		= 26
+//        12		= 18
+//        23		= 35
+//        2c		= 44
+//        04		= 4 // num charges??
+//
+//        e0 07		= 2016 // last charge time
+//        0b		= 11
+//        1a		= 26
+//        17		= 23
+//        2b		= 43
+//        3b		= 59
+//        04		= 4   // num charges??
+//        64		= 100 // how much was charged
+
+public class HuamiBatteryInfo extends AbstractInfo {
+    public static final byte DEVICE_BATTERY_NORMAL = 0;
+    public static final byte DEVICE_BATTERY_CHARGING = 1;
+//    public static final byte DEVICE_BATTERY_LOW = 1;
+//    public static final byte DEVICE_BATTERY_CHARGING_FULL = 3;
+//    public static final byte DEVICE_BATTERY_CHARGE_OFF = 4;
+
+    public HuamiBatteryInfo(byte[] data) {
+        super(data);
+    }
+
+    public int getLevelInPercent() {
+        if (mData.length >= 2) {
+            return mData[1];
+        }
+        return 50; // actually unknown
+    }
+
+    public BatteryState getState() {
+        if (mData.length >= 3) {
+            int value = mData[2];
+            switch (value) {
+                case DEVICE_BATTERY_NORMAL:
+                    return BatteryState.BATTERY_NORMAL;
+                case DEVICE_BATTERY_CHARGING:
+                    return BatteryState.BATTERY_CHARGING;
+//                case DEVICE_BATTERY_CHARGING:
+//                    return BatteryState.BATTERY_CHARGING;
+//                case DEVICE_BATTERY_CHARGING_FULL:
+//                    return BatteryState.BATTERY_CHARGING_FULL;
+//                case DEVICE_BATTERY_CHARGE_OFF:
+//                    return BatteryState.BATTERY_NOT_CHARGING_FULL;
+            }
+        }
+        return BatteryState.UNKNOWN;
+    }
+
+    public int getLastChargeLevelInParcent() {
+        if (mData.length >= 20) {
+            return mData[19];
+        }
+        return 50; // actually unknown
+    }
+
+    public GregorianCalendar getLastChargeTime() {
+        GregorianCalendar lastCharge = MiBandDateConverter.createCalendar();
+
+        if (mData.length >= 18) {
+            lastCharge = BLETypeConversions.rawBytesToCalendar(new byte[]{
+                    mData[10], mData[11], mData[12], mData[13], mData[14], mData[15], mData[16], mData[17]
+            });
+        }
+
+        return lastCharge;
+    }
+
+    public int getNumCharges() {
+//        if (mData.length >= 10) {
+//            return ((0xff & mData[7]) | ((0xff & mData[8]) << 8));
+//
+//        }
+        return -1;
+    }
+
+    public GBDeviceEventBatteryInfo toDeviceEvent() {
+        final GBDeviceEventBatteryInfo deviceEventBatteryInfo = new GBDeviceEventBatteryInfo();
+        deviceEventBatteryInfo.level = ((short) getLevelInPercent());
+        deviceEventBatteryInfo.state = getState();
+        deviceEventBatteryInfo.lastChargeTime = getLastChargeTime();
+        deviceEventBatteryInfo.numCharges = getNumCharges();
+        return deviceEventBatteryInfo;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiDeviceEvent.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiDeviceEvent.java
new file mode 100644
index 0000000..130fc76
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiDeviceEvent.java
@@ -0,0 +1,38 @@
+/*  Copyright (C) 2017-2024 Andreas Shimokawa, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami;
+
+
+public class HuamiDeviceEvent {
+    public static final byte FELL_ASLEEP = 0x01;
+    public static final byte WOKE_UP = 0x02;
+    public static final byte STEPSGOAL_REACHED = 0x03;
+    public static final byte BUTTON_PRESSED = 0x04;
+    public static final byte START_NONWEAR = 0x06;
+    public static final byte CALL_REJECT = 0x07;
+    public static final byte FIND_PHONE_START = 0x08;
+    public static final byte CALL_IGNORE = 0x09;
+    public static final byte ALARM_TOGGLED = 0x0a;
+    public static final byte BUTTON_PRESSED_LONG = 0x0b;
+    public static final byte TICK_30MIN = 0x0e; // unsure
+    public static final byte FIND_PHONE_STOP = 0x0f;
+    public static final byte SILENT_MODE = 0x10;
+    public static final byte MTU_REQUEST = 0x16;
+    public static final byte WORKOUT_STARTING = 0x14;
+    public static final byte ALARM_CHANGED = 0x1a;
+    public static final byte MUSIC_CONTROL = (byte) 0xfe;
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiFirmwareInfo.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiFirmwareInfo.java
new file mode 100644
index 0000000..7d73fb6
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiFirmwareInfo.java
@@ -0,0 +1,194 @@
+/*  Copyright (C) 2017-2024 Andreas Shimokawa, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Map;
+
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.util.ArrayUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.CheckSums;
+
+
+public abstract class HuamiFirmwareInfo extends AbstractHuamiFirmwareInfo {
+
+    protected static final byte[] RES_HEADER = new byte[]{ // HMRES resources file (*.res)
+            0x48, 0x4d, 0x52, 0x45, 0x53
+    };
+
+    protected static final byte[] NEWRES_HEADER = new byte[]{ // NERES resources file (*.res)
+            0x4e, 0x45, 0x52, 0x45, 0x53
+    };
+
+    protected static final byte[] WATCHFACE_HEADER = new byte[]{
+            0x48, 0x4d, 0x44, 0x49, 0x41, 0x4c
+    };
+
+    public static final byte[] UIHH_HEADER = new byte[]{
+            'U', 'I', 'H', 'H'
+    };
+
+    public static final byte[] AGPS_UIHH_HEADER = new byte[]{
+            'U', 'I', 'H', 'H', 0x04
+    };
+
+    protected static final byte[] FT_HEADER = new byte[]{ // HMZK font file (*.ft, *.ft.xx)
+            0x48, 0x4d, 0x5a, 0x4b
+    };
+
+    protected static final byte[] NEWFT_HEADER = new byte[]{ // NEZK font file (*.ft, *.ft.xx)
+            0x4e, 0x45, 0x5a, 0x4b
+    };
+
+    public static final byte[] GPS_ALMANAC_HEADER = new byte[]{ // probably wrong
+            (byte) 0xa0, (byte) 0x80, 0x08, 0x00, (byte) 0x8b
+    };
+
+    public static final byte[] GPS_CEP_HEADER = new byte[]{ // probably wrong
+            0x2a, 0x12, (byte) 0xa0, 0x02
+    };
+
+    // gps detection is totally bogus, just the first 16 bytes
+    protected static final byte[][] GPS_HEADERS = {
+            new byte[]{
+                    (byte) 0xcb, 0x51, (byte) 0xc1, 0x30, 0x41, (byte) 0x9e, 0x5e, (byte) 0xd3,
+                    0x51, 0x35, (byte) 0xdf, 0x66, (byte) 0xed, (byte) 0xd9, 0x5f, (byte) 0xa7
+            },
+            new byte[]{
+                    0x10, 0x50, 0x26, 0x76, (byte) 0x8f, 0x4a, (byte) 0xa1, 0x49,
+                    (byte) 0xa7, 0x26, (byte) 0xd0, (byte) 0xe6, 0x4a, 0x21, (byte) 0x88, (byte) 0xd4
+            },
+            new byte[]{
+                    (byte) 0xeb, (byte) 0xfa, (byte) 0xc5, (byte) 0x89, (byte) 0xf0, 0x5c, 0x2e, (byte) 0xcc,
+                    (byte) 0xfa, (byte) 0xf3, 0x62, (byte) 0xeb, (byte) 0x92, (byte) 0xc6, (byte) 0xa1, (byte) 0xbb
+            },
+            new byte[]{
+                    0x0b, 0x61, 0x53, (byte) 0xed, (byte) 0x83, (byte) 0xac, 0x07, 0x21,
+                    (byte) 0x8c, 0x36, 0x2e, (byte) 0x8c, (byte) 0x9c, 0x08, 0x54, (byte) 0xa6
+            },
+            new byte[]{
+                    (byte) 0xec, 0x51, 0x73, 0x22, 0x60, 0x02, 0x14, (byte) 0xb7,
+                    (byte) 0xb5, (byte) 0xea, 0x4b, 0x22, 0x5d, 0x23, (byte) 0xe5, 0x4f
+            },
+            new byte[]{
+                    0x73, 0x75, 0x68, (byte) 0xd0, 0x70, 0x73, (byte) 0xbb, 0x5a,
+                    0x3e, (byte) 0xc3, (byte) 0xd3, 0x09, (byte) 0x9e, 0x1d, (byte) 0xd3, (byte) 0xc9
+            },
+            new byte[]{
+                    0x00, 0x00, 0x00, 0x01, 0x00, 0x09, 0x6F, (byte) 0xD0,
+                    0x00, 0x01, 0x00, 0x02, 0x3D, (byte) 0xE0, 0x00, 0x69
+            },
+            new byte[]{
+                    0x00, 0x00, 0x00, 0x01, 0x00, 0x09, 0x32, 0x70,
+                    0x00, 0x01, 0x00, 0x02, 0x3D, (byte) 0xE0, 0x00, (byte) 0xCC
+            }
+    };
+
+    protected static final int FONT_TYPE_OFFSET = 0x9;
+    protected static final int COMPRESSED_RES_HEADER_OFFSET = 0x9;
+    protected static final int COMPRESSED_RES_HEADER_OFFSET_NEW = 0xd;
+
+    @Override
+    public String toVersion(int crc16) {
+        final byte[] bytes = getBytes();
+        String version = getCrcMap().get(crc16);
+        if (version == null) {
+            switch (firmwareType) {
+                case FIRMWARE:
+                    version = searchFirmwareVersion(bytes);
+                    break;
+                case RES:
+                    version = "RES " + bytes[5];
+                    break;
+                case RES_COMPRESSED:
+                    byte versionByte;
+                    // there are two possible locations of the version for compressed res, probe the format in a dirty way :P
+                    if (bytes[COMPRESSED_RES_HEADER_OFFSET + 2] == 0x52 &&
+                            bytes[COMPRESSED_RES_HEADER_OFFSET + 3] == 0x45 &&
+                            bytes[COMPRESSED_RES_HEADER_OFFSET + 4] == 0x53) {
+                        versionByte = bytes[14];
+                    } else {
+                        versionByte = bytes[18];
+                    }
+                    version = "RES " + (versionByte & 0xff);
+                    break;
+                case FONT:
+                    version = "FONT " + bytes[4];
+                    break;
+                case FONT_LATIN:
+                    version = "FONT LATIN " + bytes[4];
+                    break;
+            }
+        }
+        if (version == null) {
+            switch (firmwareType) {
+                case FIRMWARE:
+                    version = "(unknown)";
+                    break;
+                case FONT:
+                case FONT_LATIN:
+                    version = "(unknown font)";
+                    break;
+                case GPS:
+                    version = "(unknown GPS)";
+                    break;
+                case GPS_CEP:
+                    version = "(unknown CEP)";
+                    break;
+                case GPS_ALMANAC:
+                    version = "(unknown ALM)";
+                    break;
+                case AGPS_UIHH:
+                    version = "(unknown AGPS)";
+                    break;
+                case WATCHFACE:
+                    version = "(unknown watchface)";
+                    break;
+            }
+        }
+        return version;
+    }
+
+    public HuamiFirmwareInfo(byte[] bytes) {
+        super(bytes);
+    }
+
+    protected String searchFirmwareVersion(byte[] fwbytes) {
+        ByteBuffer buf = ByteBuffer.wrap(fwbytes);
+        buf.order(ByteOrder.BIG_ENDIAN);
+        while (buf.remaining() > 3) {
+            int word = buf.getInt();
+            if (word == 0x5625642e) {
+                word = buf.getInt();
+                if (word == 0x25642e25) {
+                    word = buf.getInt();
+                    if (word == 0x642e2564) {
+                        word = buf.getInt();
+                        if (word == 0x00000000) {
+                            byte[] version = new byte[8];
+                            buf.get(version);
+                            return new String(version);
+                        }
+                    }
+                }
+            }
+        }
+        return null;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiFirmwareType.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiFirmwareType.java
new file mode 100644
index 0000000..72bf258
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiFirmwareType.java
@@ -0,0 +1,67 @@
+/*  Copyright (C) 2017-2024 Andreas Shimokawa, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami;
+
+import androidx.annotation.StringRes;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+
+public enum HuamiFirmwareType {
+    FIRMWARE((byte) 0, R.string.kind_firmware),
+    CHANGELOG_TXT((byte) 16, R.string.action_changelog),
+    // MB7 firmwares are sent as UIHH packing FIRMWARE (zip) + CHANGELOG_TXT, type 0xfd
+    FIRMWARE_UIHH_2021_ZIP_WITH_CHANGELOG((byte) -3, R.string.kind_firmware),
+    FONT((byte) 1, R.string.kind_font),
+    RES((byte) 2, R.string.kind_resources),
+    RES_COMPRESSED((byte) 130, R.string.kind_resources),
+    GPS((byte) 3, R.string.kind_gps),
+    GPS_CEP((byte) 4, R.string.kind_gps_cep),
+    AGPS_UIHH((byte) -4, R.string.kind_agps_bundle),
+    GPS_ALMANAC((byte) 5, R.string.kind_gps_almanac),
+    WATCHFACE((byte) 8, R.string.kind_watchface),
+    APP((byte) 8, R.string.kind_app),
+    FONT_LATIN((byte) 11, R.string.kind_font),
+    ZEPPOS_UNKNOWN_0X13((byte) 0x13, R.string.unknown),
+    ZEPPOS_APP((byte) 0xa0, R.string.kind_app),
+    INVALID(Byte.MIN_VALUE, R.string.kind_invalid),
+    ;
+
+    private final byte value;
+    private final int nameResId;
+
+    HuamiFirmwareType(byte value, int nameResId) {
+        this.value = value;
+        this.nameResId = nameResId;
+    }
+
+    public byte getValue() {
+        return value;
+    }
+
+    @StringRes
+    public int getNameResId() {
+        return nameResId;
+    }
+
+    public boolean isApp() {
+        return this == APP || this == ZEPPOS_APP;
+    }
+
+    public boolean isWatchface() {
+        return this == WATCHFACE;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiIcon.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiIcon.java
new file mode 100644
index 0000000..6b012e2
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiIcon.java
@@ -0,0 +1,147 @@
+/*  Copyright (C) 2017-2024 Andreas Shimokawa, Daniele Gobbetti, Lukas
+    Veneziano, Maxim Baz, musover
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami;
+
+
+import nodomain.freeyourgadget.gadgetbridge.model.NotificationType;
+
+public class HuamiIcon {
+    // icons which are unsure which app they are for are suffixed with _NN
+    public static final byte WECHAT = 0;
+    public static final byte PENGUIN_1 = 1;
+    public static final byte MI_CHAT_2 = 2;
+    public static final byte FACEBOOK = 3;
+    public static final byte TWITTER = 4;
+    public static final byte MI_APP_5 = 5;
+    public static final byte SNAPCHAT = 6;
+    public static final byte WHATSAPP = 7;
+    public static final byte RED_WHITE_FIRE_8 = 8;
+    public static final byte CHINESE_9 = 9;
+    public static final byte ALARM_CLOCK = 10;
+    public static final byte APP_11 = 11;
+    public static final byte INSTAGRAM = 12;
+    public static final byte CHAT_BLUE_13 = 13;
+    public static final byte COW_14 = 14;
+    public static final byte CHINESE_15 = 15;
+    public static final byte CHINESE_16 = 16;
+    public static final byte STAR_17 = 17;
+    public static final byte APP_18 = 18;
+    public static final byte CHINESE_19 = 19;
+    public static final byte CHINESE_20 = 20;
+    public static final byte CALENDAR = 21;
+    public static final byte FACEBOOK_MESSENGER = 22;
+    public static final byte VIBER = 23;
+    public static final byte LINE = 24;
+    public static final byte TELEGRAM = 25;
+    public static final byte KAKAOTALK = 26;
+    public static final byte SKYPE = 27;
+    public static final byte VKONTAKTE = 28;
+    public static final byte POKEMONGO = 29;
+    public static final byte HANGOUTS = 30;
+    public static final byte MI_31 = 31;
+    public static final byte CHINESE_32 = 32;
+    public static final byte CHINESE_33 = 33;
+    public static final byte EMAIL = 34;
+    public static final byte WEATHER = 35;
+    public static final byte HR_WARNING_36 = 36;
+
+    public static byte mapToIconId(NotificationType type) {
+        switch (type) {
+            case UNKNOWN:
+            case GENERIC_NAVIGATION:
+                return APP_11;
+            case CONVERSATIONS:
+            case RIOT:
+            case HIPCHAT:
+            case KONTALK:
+            case ANTOX:
+            case GENERIC_SMS:
+            case WECHAT:
+                return WECHAT;
+            case GENERIC_EMAIL:
+            case GMAIL:
+            case YAHOO_MAIL:
+            case OUTLOOK:
+                return EMAIL;
+            case GENERIC_CALENDAR:
+            case BUSINESS_CALENDAR:
+                return CALENDAR;
+            case FACEBOOK:
+                return FACEBOOK;
+            case FACEBOOK_MESSENGER:
+            case SIGNAL:
+                return FACEBOOK_MESSENGER;
+            case GOOGLE_HANGOUTS:
+            case GOOGLE_MESSENGER:
+                return HANGOUTS;
+            case INSTAGRAM:
+            case GOOGLE_PHOTOS:
+                return INSTAGRAM;
+            case KAKAO_TALK:
+                return KAKAOTALK;
+            case LINE:
+                return LINE;
+            case WIRE:
+            case THREEMA:
+                return CHAT_BLUE_13;
+            case TWITTER:
+                return TWITTER;
+            case SKYPE:
+                return SKYPE;
+            case SNAPCHAT:
+                return SNAPCHAT;
+            case TELEGRAM:
+                return TELEGRAM;
+            case VIBER:
+            case DISCORD:
+                return VIBER;
+            case WHATSAPP:
+                return WHATSAPP;
+            case GENERIC_ALARM_CLOCK:
+                return ALARM_CLOCK;
+        }
+        return APP_11;
+    }
+
+    //amazfit workaround
+    public static boolean acceptsSender(byte iconId){
+        switch(iconId){
+            case WECHAT:
+            case PENGUIN_1:
+            case MI_CHAT_2:
+            case SNAPCHAT:
+            case WHATSAPP:
+            case RED_WHITE_FIRE_8:
+            case INSTAGRAM:
+            case CHAT_BLUE_13:
+            case COW_14:
+            case CHINESE_20:
+            case FACEBOOK_MESSENGER:
+            case VIBER:
+            case LINE:
+            case TELEGRAM:
+            case VKONTAKTE:
+            case CHINESE_32:
+            case EMAIL:
+                return true;
+        }
+
+        return false;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiLanguageType.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiLanguageType.java
new file mode 100644
index 0000000..8e6abac
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiLanguageType.java
@@ -0,0 +1,52 @@
+/*  Copyright (C) 2021-2024 Andreas Shimokawa, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+public class HuamiLanguageType {
+    // Use a LinkedHashMap (sorted), so that when we reverse it we get the first value as key, deterministically
+    public static final Map<String, Integer> idLookup = new LinkedHashMap<String, Integer>() {{
+        put("zh_CN", 0x00);
+        put("zh_TW", 0x01);
+        put("zh_HK", 0x01);
+        put("en_US", 0x02);
+        put("en_GB", 0x02);
+        put("en_AU", 0x02);
+        put("es_ES", 0x03);
+        put("ru_RU", 0x04);
+        put("ko_KO", 0x05);
+        put("fr_FR", 0x06);
+        put("de_DE", 0x07);
+        put("de_AT", 0x07);
+        put("de_CH", 0x07);
+        put("id_ID", 0x08);
+        put("pl_PL", 0x09);
+        put("it_IT", 0x0a);
+        put("ja_JP", 0x0b);
+        put("th_TH", 0x0c);
+        put("vi_VN", 0x0e);
+        put("pt_PT", 0x0f);
+        put("nl_NL", 0x10);
+        put("tr_TR", 0x11);
+        put("uk_UA", 0x12);
+        put("pt_BR", 0x14);
+        put("cs_CZ", 0x16);
+        put("el_GR", 0x17);
+    }};
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiMenuType.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiMenuType.java
new file mode 100644
index 0000000..f365397
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiMenuType.java
@@ -0,0 +1,67 @@
+/*  Copyright (C) 2020-2024 Andreas Shimokawa, TinfoilSubmarine, Vianney le
+    Cl茅ment de Saint-Marcq
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami;
+
+import java.util.HashMap;
+import java.util.Map;
+
+class HuamiMenuType {
+    public static final Map<String, Integer> idLookup = new HashMap<String, Integer>() {{
+        put("status", 0x01);
+        put("hr", 0x02);
+        put("workout", 0x03);
+        put("weather", 0x04);
+        put("notifications", 0x06);
+        put("more", 0x07);
+        put("dnd", 0x08);
+        put("alarm", 0x09);
+        put("takephoto", 0x0a);
+        put("music", 0x0b);
+        put("stopwatch", 0x0c);
+        put("timer", 0x0d);
+        put("findphone", 0x0e);
+        put("mutephone", 0x0f);
+        put("nfc", 0x10);
+        put("alipay", 0x11);
+        put("watchface", 0x12);
+        put("settings", 0x13);
+        put("activity", 0x14);
+        put("eventreminder", 0x15);
+        put("compass", 0x16);
+        put("pai", 0x19);
+        put("worldclock", 0x1a);
+        put("timer_stopwatch", 0x1b);
+        put("stress", 0x1c);
+        put("period", 0x1d);
+        put("goal", 0x21);
+        put("sleep", 0x23);
+        put("spo2", 0x24);
+        put("events", 0x26);
+        put("widgets", 0x28);
+        put("breathing",0x33);
+        put("steps",0x34);
+        put("distance",0x35);
+        put("calories",0x36);
+        put("pomodoro", 0x38);
+        put("alexa", 0x39);
+        put("battery", 0x3a);
+        put("temperature", 0x40);
+        put("barometer", 0x41);
+        put("flashlight", 0x43);
+    }};
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiPhoneGpsStatus.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiPhoneGpsStatus.java
new file mode 100644
index 0000000..7ce82f5
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiPhoneGpsStatus.java
@@ -0,0 +1,47 @@
+/*  Copyright (C) 2022-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami;
+
+/**
+ * The phone GPS status, to signal the band.
+ */
+public enum HuamiPhoneGpsStatus {
+    ACQUIRED(0x01),
+    SEARCHING(0x02),
+    DISABLED(0x04),
+    ;
+
+    private final byte code;
+
+    HuamiPhoneGpsStatus(final int code) {
+        this.code = (byte) code;
+    }
+
+    public byte getCode() {
+        return code;
+    }
+
+    public static HuamiPhoneGpsStatus fromCode(final byte code) {
+        for (final HuamiPhoneGpsStatus type : values()) {
+            if (type.getCode() == code) {
+                return type;
+            }
+        }
+
+        return null;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiSportsActivityType.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiSportsActivityType.java
new file mode 100644
index 0000000..4dfff7f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiSportsActivityType.java
@@ -0,0 +1,151 @@
+/*  Copyright (C) 2019-2024 Andreas Shimokawa, Sebastian Krey, Your Name
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami;
+
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+
+public enum HuamiSportsActivityType {
+    OutdoorRunning(1),
+    Treadmill(2),
+    Walking(3),
+    Cycling(4),
+    Exercise(5),
+    Swimming(6),
+    OpenWaterSwimming(7),
+    IndoorCycling(8),
+    EllipticalTrainer(9),
+    OutdoorHiking(15),
+    Climbing(10),
+    Soccer(0x12),
+    JumpRope(0x15),
+    RowingMachine(0x17),
+    StrengthTraining(0x34),
+    Yoga(0x3c),
+    Cricket(0x4e),
+    Basketball(0x55),
+    PingPong(0x59),
+    Badminton(0x5c);
+
+
+    private final int code;
+
+    HuamiSportsActivityType(final int code) {
+        this.code = code;
+    }
+
+    public ActivityKind toActivityKind() {
+        switch (this) {
+            case OutdoorRunning:
+                return ActivityKind.RUNNING;
+            case OutdoorHiking:
+                return ActivityKind.HIKING;
+            case Climbing:
+                return ActivityKind.CLIMBING;
+            case Treadmill:
+                return ActivityKind.TREADMILL;
+            case Cycling:
+                return ActivityKind.CYCLING;
+            case Walking:
+                return ActivityKind.WALKING;
+            case Exercise:
+                return ActivityKind.EXERCISE;
+            case Swimming:
+                return ActivityKind.SWIMMING;
+            case OpenWaterSwimming:
+                return ActivityKind.SWIMMING_OPENWATER;
+            case IndoorCycling:
+                return ActivityKind.INDOOR_CYCLING;
+            case EllipticalTrainer:
+                return ActivityKind.ELLIPTICAL_TRAINER;
+            case Soccer:
+                return ActivityKind.SOCCER;
+            case JumpRope:
+                return ActivityKind.JUMP_ROPING;
+            case RowingMachine:
+                return ActivityKind.ROWING_MACHINE;
+            case Yoga:
+                return ActivityKind.YOGA;
+            case Cricket:
+                return ActivityKind.CRICKET;
+            case Basketball:
+                return ActivityKind.BASKETBALL;
+            case PingPong:
+                return ActivityKind.PINGPONG;
+            case Badminton:
+                return ActivityKind.BADMINTON;
+            case StrengthTraining:
+                return ActivityKind.STRENGTH_TRAINING;
+        }
+        throw new RuntimeException("Not mapped activity kind for: " + this);
+    }
+
+    public static HuamiSportsActivityType fromCode(int huamiCode) {
+        for (HuamiSportsActivityType type : values()) {
+            if (type.code == huamiCode) {
+                return type;
+            }
+        }
+        throw new RuntimeException("No matching HuamiSportsActivityType for code: " + huamiCode);
+    }
+
+    public static HuamiSportsActivityType fromActivityKind(ActivityKind activityKind) {
+        switch (activityKind) {
+            case RUNNING:
+                return OutdoorRunning;
+            case HIKING:
+                return OutdoorHiking;
+            case CLIMBING:
+                return Climbing;
+            case TREADMILL:
+                return Treadmill;
+            case CYCLING:
+                return Cycling;
+            case WALKING:
+                return Walking;
+            case EXERCISE:
+                return Exercise;
+            case SWIMMING:
+                return Swimming;
+            case SWIMMING_OPENWATER:
+                return OpenWaterSwimming;
+            case INDOOR_CYCLING:
+                return IndoorCycling;
+            case ELLIPTICAL_TRAINER:
+                return EllipticalTrainer;
+            case SOCCER:
+                return Soccer;
+            case JUMP_ROPING:
+                return JumpRope;
+            case ROWING_MACHINE:
+                return RowingMachine;
+            case YOGA:
+                return Yoga;
+            case CRICKET:
+                return Cricket;
+            case BASKETBALL:
+                return Basketball;
+            case PINGPONG:
+                return PingPong;
+            case BADMINTON:
+                return Badminton;
+            case STRENGTH_TRAINING:
+                return StrengthTraining;
+
+        }
+        throw new RuntimeException("No matching activity activityKind: " + activityKind);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiSupport.java
new file mode 100644
index 0000000..398d68d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiSupport.java
@@ -0,0 +1,4358 @@
+/*  Copyright (C) 2018-2024 Andreas Shimokawa, Arjan Schrijver, beardhatcode,
+    Carsten Pfeiffer, Damien Gaignon, Daniel Dakhno, Daniele Gobbetti, Dmitry
+    Markin, Jos茅 Rebelo, musover, Nathan Philipp Bo Seddig, NekoBox, Petr
+    Van臎k, Robbert Gurdeep Singh, Sebastian Kranz, Taavi Eom盲e, Toby Murray,
+    uli, Yoran Vulker, Zhong Jianxin
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.location.Location;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+import android.widget.Toast;
+
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import net.e175.klaus.solarpositioning.DeltaT;
+import net.e175.klaus.solarpositioning.SPA;
+import net.e175.klaus.solarpositioning.SunriseTransitSet;
+
+import org.apache.commons.lang3.ArrayUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.StandardCharsets;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.ZoneId;
+import java.time.zone.ZoneOffsetTransition;
+import java.time.zone.ZoneRules;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.SimpleTimeZone;
+import java.util.TimeZone;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.UUID;
+import java.util.concurrent.TimeUnit;
+
+import cyanogenmod.weather.util.WeatherUtils;
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.Logging;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.SettingsActivity;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+import nodomain.freeyourgadget.gadgetbridge.capabilities.password.PasswordCapabilityImpl;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventCallControl;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventFindPhone;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventMusicControl;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventNotificationControl;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventSleepStateDetection;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventSilentMode;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventWearState;
+import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.ActivateDisplayOnLift;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.ActivateDisplayOnLiftSensitivity;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.DisconnectNotificationSetting;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.zeppos.ZeppOsCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.Huami2021Service;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiWeatherConditions;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitbip.AmazfitBipService;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.miband3.MiBand3Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.miband3.MiBand3Service;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.DateTimeDisplay;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.DoNotDisturb;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBand2SampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandService;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.VibrationProfile;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.entities.MiBandActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.entities.User;
+import nodomain.freeyourgadget.gadgetbridge.externalevents.gps.GBLocationProviderType;
+import nodomain.freeyourgadget.gadgetbridge.externalevents.gps.GBLocationService;
+import nodomain.freeyourgadget.gadgetbridge.externalevents.opentracks.OpenTracksController;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice.State;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
+import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
+import nodomain.freeyourgadget.gadgetbridge.model.RecordedDataTypes;
+import nodomain.freeyourgadget.gadgetbridge.model.SleepState;
+import nodomain.freeyourgadget.gadgetbridge.model.WearingState;
+import nodomain.freeyourgadget.gadgetbridge.service.SleepAsAndroidSender;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.fetch.AbstractFetchOperation;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.fetch.FetchStatisticsOperation;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.fetch.FetchTemperatureOperation;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.fetch.FetchHeartRateManualOperation;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.fetch.FetchHeartRateMaxOperation;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.fetch.FetchHeartRateRestingOperation;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.fetch.FetchPaiOperation;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.fetch.FetchSleepRespiratoryRateOperation;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.fetch.FetchSpo2NormalOperation;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.fetch.FetchSportsSummaryOperation;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.fetch.FetchStressAutoOperation;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.fetch.FetchStressManualOperation;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.fetch.FetchDebugLogsOperation;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsCannedMessagesService;
+import nodomain.freeyourgadget.gadgetbridge.util.MediaManager;
+import nodomain.freeyourgadget.gadgetbridge.util.SilentMode;
+import nodomain.freeyourgadget.gadgetbridge.util.calendar.CalendarEvent;
+import nodomain.freeyourgadget.gadgetbridge.util.calendar.CalendarManager;
+import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.NotificationType;
+import nodomain.freeyourgadget.gadgetbridge.model.Reminder;
+import nodomain.freeyourgadget.gadgetbridge.model.Weather;
+import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.WorldClock;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEAction;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.GattCharacteristic;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.GattService;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.AbortTransactionAction;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.ConditionalWriteAction;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.IntentListener;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotification.AlertCategory;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotification.AlertNotificationProfile;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotification.NewAlert;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo.DeviceInfoProfile;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.common.SimpleNotification;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.actions.StopNotificationAction;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.miband2.Mi2NotificationStrategy;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.miband2.Mi2TextNotificationStrategy;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.fetch.FetchActivityOperation;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.init.InitOperation;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.init.InitOperation2021;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.update.UpdateFirmwareOperation;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.NotificationStrategy;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.RealtimeSamplesSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol;
+import nodomain.freeyourgadget.gadgetbridge.util.AlarmUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+import nodomain.freeyourgadget.gadgetbridge.util.NotificationUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.Version;
+
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_ACTIVATE_DISPLAY_ON_LIFT;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_ALLOW_HIGH_MTU;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_BT_CONNECTED_ADVERTISEMENT;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_DATEFORMAT;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_DISPLAY_ON_LIFT_SENSITIVITY;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_DISPLAY_ON_LIFT_START;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_DISPLAY_ON_LIFT_END;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_DISCONNECT_NOTIFICATION;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_DISCONNECT_NOTIFICATION_START;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_DISCONNECT_NOTIFICATION_END;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_START;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_END;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_LIFT_WRIST;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_HEARTRATE_ACTIVITY_MONITORING;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_HEARTRATE_ALERT_ENABLED;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_HEARTRATE_ALERT_HIGH_THRESHOLD;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_HEARTRATE_ALERT_LOW_THRESHOLD;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_HEARTRATE_STRESS_MONITORING;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_HOURLY_CHIME_ENABLE;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_HOURLY_CHIME_END;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_HOURLY_CHIME_START;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_INACTIVITY_ENABLE;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_INACTIVITY_START;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_INACTIVITY_END;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_INACTIVITY_THRESHOLD;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_INACTIVITY_DND;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_INACTIVITY_DND_START;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_INACTIVITY_DND_END;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_LANGUAGE;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_RESERVER_ALARMS_CALENDAR;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SOUNDS;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SYNC_CALENDAR;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_TIMEFORMAT;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_USER_FITNESS_GOAL_NOTIFICATION;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_WEARLOCATION;
+import static nodomain.freeyourgadget.gadgetbridge.devices.huami.Huami2021Service.WORKOUT_GPS_FLAG_POSITION;
+import static nodomain.freeyourgadget.gadgetbridge.devices.huami.Huami2021Service.WORKOUT_GPS_FLAG_STATUS;
+import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_BUTTON_ACTION_SELECTION_BROADCAST;
+import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_BUTTON_ACTION_SELECTION_FITNESS_APP_START;
+import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_BUTTON_ACTION_SELECTION_FITNESS_APP_STOP;
+import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_BUTTON_ACTION_SELECTION_FITNESS_APP_TOGGLE;
+import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_BUTTON_ACTION_SELECTION_OFF;
+import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_HUAMI_VIBRATION_COUNT_ALARM;
+import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_HUAMI_VIBRATION_COUNT_APP_ALERTS;
+import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_HUAMI_VIBRATION_COUNT_EVENT_REMINDER;
+import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_HUAMI_VIBRATION_COUNT_FIND_BAND;
+import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_HUAMI_VIBRATION_COUNT_GOAL_NOTIFICATION;
+import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_HUAMI_VIBRATION_COUNT_IDLE_ALERTS;
+import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_HUAMI_VIBRATION_COUNT_INCOMING_CALL;
+import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_HUAMI_VIBRATION_COUNT_INCOMING_SMS;
+import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_HUAMI_VIBRATION_COUNT_PREFIX;
+import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_HUAMI_VIBRATION_COUNT_SCHEDULE;
+import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_HUAMI_VIBRATION_COUNT_TODO_LIST;
+import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_HUAMI_VIBRATION_PROFILE_ALARM;
+import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_HUAMI_VIBRATION_PROFILE_APP_ALERTS;
+import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_HUAMI_VIBRATION_PROFILE_EVENT_REMINDER;
+import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_HUAMI_VIBRATION_PROFILE_FIND_BAND;
+import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_HUAMI_VIBRATION_PROFILE_GOAL_NOTIFICATION;
+import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_HUAMI_VIBRATION_PROFILE_IDLE_ALERTS;
+import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_HUAMI_VIBRATION_PROFILE_INCOMING_CALL;
+import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_HUAMI_VIBRATION_PROFILE_INCOMING_SMS;
+import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_HUAMI_VIBRATION_PROFILE_PREFIX;
+import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_HUAMI_VIBRATION_PROFILE_SCHEDULE;
+import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_HUAMI_VIBRATION_PROFILE_TODO_LIST;
+import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_HUAMI_VIBRATION_TRY_ALARM;
+import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_HUAMI_VIBRATION_TRY_APP_ALERTS;
+import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_HUAMI_VIBRATION_TRY_EVENT_REMINDER;
+import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_HUAMI_VIBRATION_TRY_FIND_BAND;
+import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_HUAMI_VIBRATION_TRY_GOAL_NOTIFICATION;
+import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_HUAMI_VIBRATION_TRY_IDLE_ALERTS;
+import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_HUAMI_VIBRATION_TRY_INCOMING_CALL;
+import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_HUAMI_VIBRATION_TRY_INCOMING_SMS;
+import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_HUAMI_VIBRATION_TRY_PREFIX;
+import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_HUAMI_VIBRATION_TRY_SCHEDULE;
+import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_HUAMI_VIBRATION_TRY_TODO_LIST;
+import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService.COMMAND_ALARMS;
+import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService.COMMAND_ALARMS_WITH_TIMES;
+import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService.COMMAND_GPS_VERSION;
+import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService.COMMAND_WORKOUT_ACTIVITY_TYPES;
+import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService.DISPLAY_ITEM_BIT_CLOCK;
+import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService.ENDPOINT_DISPLAY;
+import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService.ENDPOINT_DISPLAY_ITEMS;
+import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService.MUSIC_FLAG_ALBUM;
+import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService.MUSIC_FLAG_ARTIST;
+import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService.MUSIC_FLAG_DURATION;
+import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService.MUSIC_FLAG_STATE;
+import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService.MUSIC_FLAG_TRACK;
+import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService.MUSIC_FLAG_VOLUME;
+import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.DEFAULT_VALUE_VIBRATION_COUNT;
+import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.DEFAULT_VALUE_VIBRATION_PROFILE;
+import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.VIBRATION_COUNT;
+import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.VIBRATION_PROFILE;
+import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.getNotificationPrefIntValue;
+import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.getNotificationPrefStringValue;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivityUser.PREF_USER_DATE_OF_BIRTH;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivityUser.PREF_USER_GENDER;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivityUser.PREF_USER_HEIGHT_CM;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivityUser.PREF_USER_NAME;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivityUser.PREF_USER_WEIGHT_KG;
+import static nodomain.freeyourgadget.gadgetbridge.service.btle.GattCharacteristic.UUID_CHARACTERISTIC_ALERT_LEVEL;
+
+public abstract class HuamiSupport extends AbstractBTLEDeviceSupport implements Huami2021Handler {
+
+    // We introduce key press counter for notification purposes
+    private static int currentButtonActionId = 0;
+    private static int currentButtonPressCount = 0;
+    private static long currentButtonPressTime = 0;
+    private static long currentButtonTimerActivationTime = 0;
+
+    private Timer buttonActionTimer = null;
+    private Timer findDeviceLoopTimer = null;
+
+    private static final Logger LOG = LoggerFactory.getLogger(HuamiSupport.class);
+    private final DeviceInfoProfile<HuamiSupport> deviceInfoProfile;
+    private final IntentListener mListener = new IntentListener() {
+        @Override
+        public void notify(Intent intent) {
+            String s = intent.getAction();
+            if (DeviceInfoProfile.ACTION_DEVICE_INFO.equals(s)) {
+                handleDeviceInfo((nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo.DeviceInfo) intent.getParcelableExtra(DeviceInfoProfile.EXTRA_DEVICE_INFO));
+            }
+        }
+    };
+
+    protected BluetoothGattCharacteristic characteristicHRControlPoint;
+    private BluetoothGattCharacteristic characteristicChunked;
+
+    private BluetoothGattCharacteristic characteristicChunked2021Write;
+    private BluetoothGattCharacteristic characteristicChunked2021Read;
+
+    private boolean needsAuth;
+    private volatile boolean telephoneRinging;
+
+    private final GBDeviceEventVersionInfo versionCmd = new GBDeviceEventVersionInfo();
+    private final GBDeviceEventBatteryInfo batteryCmd = new GBDeviceEventBatteryInfo();
+    private final GBDeviceEventFindPhone findPhoneEvent = new GBDeviceEventFindPhone();
+
+    private RealtimeSamplesSupport realtimeSamplesSupport;
+
+    protected boolean isMusicAppStarted = false;
+    protected MediaManager mediaManager;
+    private boolean heartRateNotifyEnabled;
+    private static final int MIN_MTU = 23;
+    private int mMTU = MIN_MTU;
+    // Keep track of the previous MTU before reconnection, so that we can request it after reconnection
+    private int previousMtu = -1;
+    protected int mActivitySampleSize = 4;
+
+    protected Huami2021ChunkedEncoder huami2021ChunkedEncoder;
+    protected Huami2021ChunkedDecoder huami2021ChunkedDecoder;
+
+    private final LinkedList<AbstractFetchOperation> fetchOperationQueue = new LinkedList<>();
+
+    protected SleepAsAndroidSender sleepAsAndroidSender;
+    public HuamiSupport() {
+        this(LOG);
+    }
+
+    public HuamiSupport(Logger logger) {
+        super(logger);
+        addSupportedService(GattService.UUID_SERVICE_GENERIC_ACCESS);
+        addSupportedService(GattService.UUID_SERVICE_GENERIC_ATTRIBUTE);
+        addSupportedService(GattService.UUID_SERVICE_HEART_RATE);
+        addSupportedService(GattService.UUID_SERVICE_IMMEDIATE_ALERT);
+        addSupportedService(GattService.UUID_SERVICE_DEVICE_INFORMATION);
+        addSupportedService(GattService.UUID_SERVICE_ALERT_NOTIFICATION);
+
+        addSupportedService(MiBandService.UUID_SERVICE_MIBAND_SERVICE);
+        addSupportedService(MiBandService.UUID_SERVICE_MIBAND2_SERVICE);
+        addSupportedService(HuamiService.UUID_SERVICE_FIRMWARE_SERVICE);
+
+        deviceInfoProfile = new DeviceInfoProfile<>(this);
+        deviceInfoProfile.addListener(mListener);
+        addSupportedProfile(deviceInfoProfile);
+    }
+
+    @Override
+    public void setContext(final GBDevice gbDevice, final BluetoothAdapter btAdapter, final Context context) {
+        super.setContext(gbDevice, btAdapter, context);
+        this.mediaManager = new MediaManager(context);
+
+    }
+
+    @Override
+    protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
+        if (getMTU() != MIN_MTU) {
+            // Reset the MTU before re-initializing the device, otherwise initialization will sometimes fail
+            previousMtu = getMTU();
+            setMtu(MIN_MTU);
+        }
+
+        try {
+            byte authFlags = getAuthFlags();
+            byte cryptFlags = getCryptFlags();
+            heartRateNotifyEnabled = false;
+            boolean authenticate = needsAuth && (cryptFlags == 0x00);
+            needsAuth = false;
+            characteristicChunked2021Read = getCharacteristic(HuamiService.UUID_CHARACTERISTIC_CHUNKEDTRANSFER_2021_READ);
+            if (characteristicChunked2021Read != null && huami2021ChunkedDecoder == null) {
+                huami2021ChunkedDecoder = new Huami2021ChunkedDecoder(this, force2021Protocol());
+            }
+            characteristicChunked2021Write = getCharacteristic(HuamiService.UUID_CHARACTERISTIC_CHUNKEDTRANSFER_2021_WRITE);
+            if (characteristicChunked2021Write != null && huami2021ChunkedEncoder == null) {
+                huami2021ChunkedEncoder = new Huami2021ChunkedEncoder(characteristicChunked2021Write, force2021Protocol(), mMTU);
+            }
+            if (force2021Protocol()) {
+                if (characteristicChunked2021Write != null && characteristicChunked2021Read != null) {
+                    new InitOperation2021(authenticate, authFlags, cryptFlags, this, builder, huami2021ChunkedEncoder, huami2021ChunkedDecoder).perform();
+                } else {
+                    LOG.warn("Chunked 2021 characteristics are null, will attempt to reconnect");
+                    builder.add(new SetDeviceStateAction(getDevice(), State.WAITING_FOR_RECONNECT, getContext()));
+                }
+            } else {
+                new InitOperation(authenticate, authFlags, cryptFlags, this, builder).perform();
+            }
+            if (sleepAsAndroidSender == null) {
+                sleepAsAndroidSender = new SleepAsAndroidSender(gbDevice);
+            }
+            characteristicHRControlPoint = getCharacteristic(GattCharacteristic.UUID_CHARACTERISTIC_HEART_RATE_CONTROL_POINT);
+            characteristicChunked = getCharacteristic(HuamiService.UUID_CHARACTERISTIC_CHUNKEDTRANSFER);
+        } catch (IOException e) {
+            GB.toast(getContext(), "Initializing Huami device failed", Toast.LENGTH_SHORT, GB.ERROR, e);
+        }
+        return builder;
+    }
+
+    protected byte getAuthFlags() {
+        return HuamiService.AUTH_BYTE;
+    }
+
+    public byte getCryptFlags() {
+        return 0x00;
+    }
+
+    public boolean supportsSunriseSunsetWindHumidity() {
+        return false;
+    }
+
+    /**
+     * Return the wind speed as sting in a format that is supported by the device.
+     *
+     * A lot of devices only support "levels", in GB we send the Beaufort speed.
+     * Override this in the device specific support class if other, more clear,
+     * formats are supported.
+     *
+     * @param weatherSpec
+     * @return
+     */
+    public String windSpeedString(WeatherSpec weatherSpec){
+        return weatherSpec.windSpeedAsBeaufort() + ""; // cast to string
+    }
+
+    /**
+     * Returns the given date/time (calendar) as a byte sequence, suitable for sending to the
+     * Mi Band 2 (or derivative). The band appears to not handle DST offsets, so we simply add this
+     * to the timezone.
+     *
+     * @param calendar
+     * @param precision
+     * @return
+     */
+    public byte[] getTimeBytes(Calendar calendar, TimeUnit precision) {
+        byte[] bytes;
+        if (precision == TimeUnit.MINUTES) {
+            bytes = BLETypeConversions.shortCalendarToRawBytes(calendar);
+        } else if (precision == TimeUnit.SECONDS) {
+            bytes = calendarToRawBytes(calendar);
+        } else {
+            throw new IllegalArgumentException("Unsupported precision, only MINUTES and SECONDS are supported till now");
+        }
+        byte[] tail = new byte[] { 0, BLETypeConversions.mapTimeZone(calendar, BLETypeConversions.TZ_FLAG_INCLUDE_DST_IN_TZ) };
+        // 0 = adjust reason bitflags? or DST offset?? , timezone
+//        byte[] tail = new byte[] { 0x2 }; // reason
+        byte[] all = BLETypeConversions.join(bytes, tail);
+        return all;
+    }
+
+    /**
+     * Converts a timestamp to the byte sequence to be sent to the current time characteristic
+     *
+     * @param timestamp
+     * @return
+     * @see GattCharacteristic#UUID_CHARACTERISTIC_CURRENT_TIME
+     */
+    public static byte[] calendarToRawBytes(Calendar timestamp) {
+        // MiBand2:
+        // year,year,month,dayofmonth,hour,minute,second,dayofweek,0,0,tz
+
+        byte[] year = BLETypeConversions.fromUint16(timestamp.get(Calendar.YEAR));
+        return new byte[] {
+                year[0],
+                year[1],
+                BLETypeConversions.fromUint8(timestamp.get(Calendar.MONTH) + 1),
+                BLETypeConversions.fromUint8(timestamp.get(Calendar.DATE)),
+                BLETypeConversions.fromUint8(timestamp.get(Calendar.HOUR_OF_DAY)),
+                BLETypeConversions.fromUint8(timestamp.get(Calendar.MINUTE)),
+                BLETypeConversions.fromUint8(timestamp.get(Calendar.SECOND)),
+                BLETypeConversions.dayOfWeekToRawBytes(timestamp),
+                0, // fractions256 (not set)
+                // 0 (DST offset?) Mi2
+                // k (tz) Mi2
+        };
+    }
+
+    public Calendar fromTimeBytes(byte[] bytes) {
+        GregorianCalendar timestamp = BLETypeConversions.rawBytesToCalendar(bytes);
+        return timestamp;
+    }
+
+    public HuamiSupport setCurrentTimeWithService(TransactionBuilder builder) {
+        final Calendar now = createCalendar();
+        byte[] bytes = getTimeBytes(now, TimeUnit.SECONDS);
+        builder.write(getCharacteristic(GattCharacteristic.UUID_CHARACTERISTIC_CURRENT_TIME), bytes);
+        return this;
+    }
+
+    /**
+     * Allow for the calendar to be overridden to a fixed date, for tests.
+     */
+    protected Calendar createCalendar() {
+        return BLETypeConversions.createCalendar();
+    }
+
+    /**
+     * Last action of initialization sequence. Sets the device to initialized.
+     * It is only invoked if all other actions were successfully run, so the device
+     * must be initialized, then.
+     *
+     * @param builder
+     */
+    public void setInitialized(TransactionBuilder builder) {
+        builder.add(new SetDeviceStateAction(gbDevice, State.INITIALIZED, getContext()));
+    }
+
+    // MB2: AVL
+    // TODO: tear down the notifications on quit
+    public HuamiSupport enableNotifications(TransactionBuilder builder, boolean enable) {
+        builder.notify(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_NOTIFICATION), enable);
+        // Notify CHARACTERISTIC9 to receive random auth code
+        builder.notify(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_AUTH), enable);
+        if (characteristicChunked2021Read != null) {
+            builder.notify(characteristicChunked2021Read, enable);
+        }
+
+        return this;
+    }
+
+    public HuamiSupport enableFurtherNotifications(TransactionBuilder builder, boolean enable) {
+        builder.notify(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_3_CONFIGURATION), enable);
+        builder.notify(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_6_BATTERY_INFO), enable);
+        builder.notify(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_AUDIO), enable);
+        builder.notify(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_AUDIODATA), enable);
+        builder.notify(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_DEVICEEVENT), enable);
+        builder.notify(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_WORKOUT), enable);
+        if (characteristicChunked2021Read != null) {
+            builder.notify(characteristicChunked2021Read, enable);
+        }
+
+        return this;
+    }
+
+    @Override
+    public boolean useAutoConnect() {
+        return true;
+    }
+
+    @Override
+    public boolean connectFirstTime() {
+        needsAuth = true;
+        return connect();
+    }
+
+    private HuamiSupport sendDefaultNotification(TransactionBuilder builder, SimpleNotification simpleNotification, short repeat, BtLEAction extraAction) {
+        LOG.info("Sending notification to MiBand: (" + repeat + " times)");
+        NotificationStrategy strategy = getNotificationStrategy();
+        for (short i = 0; i < repeat; i++) {
+            strategy.sendDefaultNotification(builder, simpleNotification, extraAction);
+        }
+        return this;
+    }
+
+    public NotificationStrategy getNotificationStrategy() {
+        String firmwareVersion = gbDevice.getFirmwareVersion();
+        if (firmwareVersion != null) {
+            Version ver = new Version(firmwareVersion);
+            if (MiBandConst.MI2_FW_VERSION_MIN_TEXT_NOTIFICATIONS.compareTo(ver) > 0) {
+                return new Mi2NotificationStrategy(this);
+            }
+        }
+        if (GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getBoolean(MiBandConst.PREF_MI2_ENABLE_TEXT_NOTIFICATIONS, true)) {
+            return new Mi2TextNotificationStrategy(this);
+        }
+        return new Mi2NotificationStrategy(this);
+    }
+
+    private static final byte[] startHeartMeasurementManual = new byte[]{0x15, MiBandService.COMMAND_SET_HR_MANUAL, 1};
+    private static final byte[] stopHeartMeasurementManual = new byte[]{0x15, MiBandService.COMMAND_SET_HR_MANUAL, 0};
+    private static final byte[] startHeartMeasurementContinuous = new byte[]{0x15, MiBandService.COMMAND_SET__HR_CONTINUOUS, 1};
+    private static final byte[] stopHeartMeasurementContinuous = new byte[]{0x15, MiBandService.COMMAND_SET__HR_CONTINUOUS, 0};
+
+    protected HuamiSupport requestBatteryInfo(TransactionBuilder builder) {
+        LOG.debug("Requesting Battery Info!");
+        BluetoothGattCharacteristic characteristic = getCharacteristic(HuamiService.UUID_CHARACTERISTIC_6_BATTERY_INFO);
+        builder.read(characteristic);
+        return this;
+    }
+
+    public HuamiSupport requestDeviceInfo(TransactionBuilder builder) {
+        LOG.debug("Requesting Device Info!");
+        deviceInfoProfile.requestDeviceInfo(builder);
+        return this;
+    }
+
+    /**
+     * Part of device initialization process. Do not call manually.
+     *
+     * @param transaction
+     * @return
+     */
+
+    protected HuamiSupport setFitnessGoal(TransactionBuilder transaction) {
+        LOG.info("Attempting to set Fitness Goal...");
+        BluetoothGattCharacteristic characteristic = getCharacteristic(HuamiService.UUID_CHARACTERISTIC_8_USER_SETTINGS);
+        if (characteristic != null) {
+            int fitnessGoal = GBApplication.getPrefs().getInt(ActivityUser.PREF_USER_STEPS_GOAL, ActivityUser.defaultUserStepsGoal);
+            byte[] bytes = ArrayUtils.addAll(
+                    HuamiService.COMMAND_SET_FITNESS_GOAL_START,
+                    BLETypeConversions.fromUint16(fitnessGoal));
+            bytes = ArrayUtils.addAll(bytes,
+                    HuamiService.COMMAND_SET_FITNESS_GOAL_END);
+            transaction.write(characteristic, bytes);
+        } else {
+            LOG.info("Unable to set Fitness Goal");
+        }
+        return this;
+    }
+
+    /**
+     * Part of device initialization process. Do not call manually.
+     *
+     * @param transaction
+     * @return
+     */
+    protected HuamiSupport setUserInfo(TransactionBuilder transaction) {
+        BluetoothGattCharacteristic characteristic = getCharacteristic(HuamiService.UUID_CHARACTERISTIC_8_USER_SETTINGS);
+        if (characteristic == null) {
+            return this;
+        }
+
+        LOG.info("Attempting to set user info...");
+        Prefs prefs = GBApplication.getPrefs();
+        String alias = prefs.getString(PREF_USER_NAME, null);
+        ActivityUser activityUser = new ActivityUser();
+        int height = activityUser.getHeightCm();
+        int weight = activityUser.getWeightKg();
+        LocalDate dateOfBirth = activityUser.getDateOfBirth();
+        int birth_year = dateOfBirth.getYear();
+        byte birth_month = (byte) dateOfBirth.getMonthValue();
+        byte birth_day = (byte) dateOfBirth.getDayOfMonth();
+
+        if (alias == null || weight == 0 || height == 0 || birth_year == 0) {
+            LOG.warn("Unable to set user info, make sure it is set up");
+            return this;
+        }
+
+        byte sex = 2; // other
+        switch (activityUser.getGender()) {
+            case ActivityUser.GENDER_MALE:
+                sex = 0;
+                break;
+            case ActivityUser.GENDER_FEMALE:
+                sex = 1;
+        }
+        int userid = alias.hashCode(); // hash from alias like mi1
+
+        // FIXME: Do encoding like in PebbleProtocol, this is ugly
+        byte[] bytes = new byte[]{
+                HuamiService.COMMAND_SET_USERINFO,
+                0,
+                0,
+                (byte) (birth_year & 0xff),
+                (byte) ((birth_year >> 8) & 0xff),
+                birth_month,
+                birth_day,
+                sex,
+                (byte) (height & 0xff),
+                (byte) ((height >> 8) & 0xff),
+                (byte) ((weight * 200) & 0xff),
+                (byte) (((weight * 200) >> 8) & 0xff),
+                (byte) (userid & 0xff),
+                (byte) ((userid >> 8) & 0xff),
+                (byte) ((userid >> 16) & 0xff),
+                (byte) ((userid >> 24) & 0xff)
+        };
+
+        transaction.write(characteristic, bytes);
+        return this;
+    }
+
+    /**
+     * Part of device initialization process. Do not call manually.
+     *
+     * @param builder
+     * @return
+     */
+    protected HuamiSupport setWearLocation(TransactionBuilder builder) {
+        LOG.info("Attempting to set wear location...");
+        BluetoothGattCharacteristic characteristic = getCharacteristic(HuamiService.UUID_CHARACTERISTIC_8_USER_SETTINGS);
+        if (characteristic != null) {
+            builder.notify(characteristic, true);
+            int location = MiBandCoordinator.getWearLocation(gbDevice.getAddress());
+            switch (location) {
+                case 0: // left hand
+                    builder.write(characteristic, HuamiService.WEAR_LOCATION_LEFT_WRIST);
+                    break;
+                case 1: // right hand
+                    builder.write(characteristic, HuamiService.WEAR_LOCATION_RIGHT_WRIST);
+                    break;
+            }
+            builder.notify(characteristic, false); // TODO: this should actually be in some kind of finally-block in the queue. It should also be sent asynchronously after the notifications have completely arrived and processed.
+        }
+        return this;
+    }
+
+    @Override
+    public void onEnableHeartRateSleepSupport(boolean enable) {
+        try {
+            TransactionBuilder builder = performInitialized("enable heart rate sleep support: " + enable);
+            setHeartrateSleepSupport(builder);
+            builder.queue(getQueue());
+        } catch (IOException e) {
+            GB.toast(getContext(), "Error toggling heart rate sleep support: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
+        }
+    }
+
+    @Override
+    public void onSetHeartRateMeasurementInterval(int seconds) {
+        try {
+            int minuteInterval = seconds / 60;
+            minuteInterval = Math.min(minuteInterval, 120);
+            minuteInterval = Math.max(0,minuteInterval);
+            TransactionBuilder builder = performInitialized("set heart rate interval to: " + minuteInterval + " minutes");
+            setHeartrateMeasurementInterval(builder, minuteInterval);
+            builder.queue(getQueue());
+        } catch (IOException e) {
+            GB.toast(getContext(), "Error toggling heart rate sleep support: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
+        }
+    }
+
+    protected HuamiSupport setPassword(final TransactionBuilder builder) {
+        final boolean passwordEnabled = HuamiCoordinator.getPasswordEnabled(gbDevice.getAddress());
+        final String password = HuamiCoordinator.getPassword(gbDevice.getAddress());
+
+        LOG.info("Setting password: {}, {}", passwordEnabled, password);
+
+        if (password == null || password.isEmpty()) {
+            LOG.warn("Invalid password: {}", password);
+            return this;
+        }
+
+        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+
+        try {
+            baos.write(ENDPOINT_DISPLAY);
+            baos.write(0x21);
+            baos.write(0x00);
+            baos.write((byte) (passwordEnabled ? 0x01 : 0x00));
+            baos.write(password.getBytes());
+            baos.write(0x00);
+        } catch (final IOException e) {
+            LOG.error("Failed to build password command", e);
+            return this;
+        }
+
+        writeToConfiguration(builder, baos.toByteArray());
+
+        return this;
+    }
+
+    /**
+     * Part of device initialization process. Do not call manually.
+     *
+     * @param builder
+     */
+    protected HuamiSupport setHeartrateSleepSupport(TransactionBuilder builder) {
+        final boolean enableHrSleepSupport = MiBandCoordinator.getHeartrateSleepSupport(gbDevice.getAddress());
+        if (characteristicHRControlPoint != null) {
+            builder.notify(characteristicHRControlPoint, true);
+            if (enableHrSleepSupport) {
+                LOG.info("Enabling heartrate sleep support...");
+                builder.write(characteristicHRControlPoint, HuamiService.COMMAND_ENABLE_HR_SLEEP_MEASUREMENT);
+            } else {
+                LOG.info("Disabling heartrate sleep support...");
+                builder.write(characteristicHRControlPoint, HuamiService.COMMAND_DISABLE_HR_SLEEP_MEASUREMENT);
+            }
+            builder.notify(characteristicHRControlPoint, false); // TODO: this should actually be in some kind of finally-block in the queue. It should also be sent asynchronously after the notifications have completely arrived and processed.
+        }
+        return this;
+    }
+
+    protected HuamiSupport setHeartrateActivityMonitoring(TransactionBuilder builder) {
+        final boolean enableHrActivityMonitoring = HuamiCoordinator.getHeartrateActivityMonitoring(gbDevice.getAddress());
+        final byte[] cmd = {ENDPOINT_DISPLAY, 0x22, 0x00, (byte) (enableHrActivityMonitoring ? 0x01 : 0x00)};
+        writeToConfiguration(builder, cmd);
+        return this;
+    }
+
+    protected HuamiSupport setHeartrateAlert(TransactionBuilder builder) {
+        final boolean enableHrAlert = HuamiCoordinator.getHeartrateAlert(gbDevice.getAddress());
+        final int hrAlertThreshold = HuamiCoordinator.getHeartrateAlertHighThreshold(gbDevice.getAddress());
+
+        final byte[] cmd = {
+                ENDPOINT_DISPLAY,
+                0x1a,
+                0x00,
+                (byte) (enableHrAlert ? 0x01 : 0x00),
+                (byte) hrAlertThreshold
+        };
+
+        writeToConfiguration(builder, cmd);
+
+        return this;
+    }
+
+    protected HuamiSupport setHeartrateStressMonitoring(TransactionBuilder builder) {
+        final boolean enableHrStressMonitoring = HuamiCoordinator.getHeartrateStressMonitoring(gbDevice.getAddress());
+        LOG.info("Setting heart rate stress monitoring to {}", enableHrStressMonitoring);
+        final byte[] cmd = new byte[] {(byte) 0xfe, 0x06, 0x00, (byte) (enableHrStressMonitoring ? 0x01 : 0x00)};
+        writeToConfiguration(builder, cmd);
+        return this;
+    }
+
+    protected HuamiSupport setHeartrateMeasurementInterval(TransactionBuilder builder, int minutes) {
+        if (characteristicHRControlPoint != null) {
+            builder.notify(characteristicHRControlPoint, true);
+            LOG.info("Setting heart rate measurement interval to " + minutes + " minutes");
+            builder.write(characteristicHRControlPoint, new byte[]{HuamiService.COMMAND_SET_PERIODIC_HR_MEASUREMENT_INTERVAL, (byte) minutes});
+            builder.notify(characteristicHRControlPoint, false); // TODO: this should actually be in some kind of finally-block in the queue. It should also be sent asynchronously after the notifications have completely arrived and processed.
+        }
+        return this;
+    }
+
+    private void performDefaultNotification(String task, SimpleNotification simpleNotification, short repeat, BtLEAction extraAction) {
+        try {
+            TransactionBuilder builder = performInitialized(task);
+            sendDefaultNotification(builder, simpleNotification, repeat, extraAction);
+            builder.queue(getQueue());
+        } catch (IOException ex) {
+            LOG.error("Unable to send notification to MI device", ex);
+        }
+    }
+
+    protected void performPreferredNotification(String task, String notificationOrigin, SimpleNotification simpleNotification, int alertLevel, BtLEAction extraAction) {
+        try {
+            TransactionBuilder builder = performInitialized(task);
+            Prefs prefs = getDevicePrefs();
+            short vibrateTimes = getPreferredVibrateCount(notificationOrigin, prefs);
+            VibrationProfile profile = getPreferredVibrateProfile(notificationOrigin, prefs, vibrateTimes);
+            profile.setAlertLevel(alertLevel);
+
+            getNotificationStrategy().sendCustomNotification(profile, simpleNotification, 0, 0, 0, 0, extraAction, builder);
+
+            builder.queue(getQueue());
+        } catch (IOException ex) {
+            LOG.error("Unable to send notification to device", ex);
+        }
+    }
+
+    private short getPreferredVibrateCount(String notificationOrigin, Prefs prefs) {
+        return (short) Math.min(Short.MAX_VALUE, getNotificationPrefIntValue(VIBRATION_COUNT, notificationOrigin, prefs, DEFAULT_VALUE_VIBRATION_COUNT));
+    }
+
+    private VibrationProfile getPreferredVibrateProfile(String notificationOrigin, Prefs prefs, short repeat) {
+        String profileId = getNotificationPrefStringValue(VIBRATION_PROFILE, notificationOrigin, prefs, DEFAULT_VALUE_VIBRATION_PROFILE);
+        return VibrationProfile.getProfile(profileId, repeat);
+    }
+
+    @Override
+    public void onSetAlarms(ArrayList<? extends Alarm> alarms) {
+        final DeviceCoordinator coordinator = gbDevice.getDeviceCoordinator();
+        int maxAlarms = coordinator.getAlarmSlotCount(gbDevice);
+
+        try {
+            TransactionBuilder builder = performInitialized("Set alarm");
+            boolean anyAlarmEnabled = false;
+            for (Alarm alarm : alarms) {
+                if (alarm.getPosition() >= maxAlarms) {
+                    if (alarm.getEnabled()) {
+                        GB.toast(getContext(), "Only " + maxAlarms + " alarms are currently supported.", Toast.LENGTH_LONG, GB.WARN);
+                    }
+                    break;
+                }
+
+                anyAlarmEnabled |= alarm.getEnabled();
+                queueAlarm(alarm, builder);
+            }
+            builder.queue(getQueue());
+            if (anyAlarmEnabled) {
+                GB.toast(getContext(), getContext().getString(R.string.user_feedback_miband_set_alarms_ok), Toast.LENGTH_SHORT, GB.INFO);
+            } else {
+                GB.toast(getContext(), getContext().getString(R.string.user_feedback_all_alarms_disabled), Toast.LENGTH_SHORT, GB.INFO);
+            }
+        } catch (IOException ex) {
+            GB.toast(getContext(), getContext().getString(R.string.user_feedback_miband_set_alarms_failed), Toast.LENGTH_LONG, GB.ERROR, ex);
+        }
+    }
+
+    /**
+     * Contains the logic to build the text content that will be sent to the device.
+     * Some huami devices will omit some of the content.
+     * @param notificationSpec
+     * @return
+     */
+    public String getNotificationBody(NotificationSpec notificationSpec) {
+        String senderOrTitle = StringUtils.getFirstOf(notificationSpec.sender, notificationSpec.title);
+
+        String message = StringUtils.truncate(senderOrTitle, 32) + "\0";
+        if (notificationSpec.subject != null) {
+            message += StringUtils.truncate(notificationSpec.subject, 128) + "\n\n";
+        }
+        if (notificationSpec.body != null) {
+            message += StringUtils.truncate(notificationSpec.body, 512);
+        }
+        if (notificationSpec.body == null && notificationSpec.subject == null) {
+            message += " "; // if we have no body we have to send at least something on some devices, else they reboot (Bip S)
+        }
+
+        return message;
+    }
+
+    /**
+     * #2987 / #4419 - Some devices do not show the sender / title for certain notification types
+     */
+    public String getNotificationBodyCheckAcceptsSender(NotificationSpec notificationSpec) {
+        String senderOrTitle = StringUtils.getFirstOf(notificationSpec.sender, notificationSpec.title);
+        byte customIconId = HuamiIcon.mapToIconId(notificationSpec.type);
+        boolean acceptsSender = HuamiIcon.acceptsSender(customIconId);
+        String message;
+
+        /* The title will be displayed beside the icon depending on the icon ID sent to the
+           device. If the icon ID does not admit a title, it will display the app's name, and
+           we will repeat the subject as part of the notification body, but only if the app name
+           is different from the subject. That way it's aesthetically pleasing.
+         */
+        if (!acceptsSender && !senderOrTitle.equals(notificationSpec.sourceName)) {
+            message = "-\0"; //if the sender is not accepted, whatever goes in this field is ignored
+            message += senderOrTitle + "\n";
+        } else {
+            message = senderOrTitle + "\0";
+        }
+
+        if (notificationSpec.subject != null) {
+            message += StringUtils.truncate(notificationSpec.subject, 128) + "\n\n";
+        }
+
+        if (notificationSpec.body != null) {
+            message += StringUtils.truncate(notificationSpec.body, 512);
+        }
+
+        if (notificationSpec.body == null && notificationSpec.subject == null) {
+            message += " ";
+        }
+
+        return message;
+    }
+
+    @Override
+    public void onNotification(NotificationSpec notificationSpec) {
+        final boolean hasExtraHeader = notificationHasExtraHeader();
+        final int maxLength = notificationMaxLength();
+
+        String message = getNotificationBody(notificationSpec);
+
+        try {
+            TransactionBuilder builder = performInitialized("new notification");
+
+            byte customIconId = HuamiIcon.mapToIconId(notificationSpec.type);
+            AlertCategory alertCategory = AlertCategory.CustomHuami;
+
+            // The SMS icon for AlertCategory.SMS is unique and not available as iconId
+            if (notificationSpec.type == NotificationType.GENERIC_SMS) {
+                alertCategory = AlertCategory.SMS;
+            }
+            // EMAIL icon does not work in FW 0.0.8.74, it did in 0.0.7.90
+            else if (customIconId == HuamiIcon.EMAIL) {
+                alertCategory = AlertCategory.Email;
+            }
+
+            if (characteristicChunked != null) {
+                int prefixlength = 2;
+
+                // We also need a (fake) source name for Mi Band 3 for SMS/EMAIL, else the message is not displayed
+                byte[] appSuffix = "\0 \0".getBytes();
+                int suffixlength = appSuffix.length;
+
+                if (alertCategory == AlertCategory.CustomHuami) {
+                    String appName = "\0" + StringUtils.getFirstOf(notificationSpec.sourceName, "UNKNOWN") + "\0";
+                    prefixlength = 3;
+
+                    appSuffix = appName.getBytes();
+                    suffixlength = appSuffix.length;
+                }
+                if (hasExtraHeader) {
+                    prefixlength += 4;
+                }
+
+                // final step: build command
+                byte[] rawmessage = message.getBytes();
+                int length = Math.min(rawmessage.length, maxLength - prefixlength);
+                if (length < rawmessage.length) {
+                    length = StringUtils.utf8ByteLength(message, length);
+                }
+
+                byte[] command = new byte[length + prefixlength + suffixlength];
+                int pos = 0;
+                command[pos++] = (byte) alertCategory.getId();
+                if (hasExtraHeader) {
+                    command[pos++] = 0; // TODO
+                    command[pos++] = 0;
+                    command[pos++] = 0;
+                    command[pos++] = 0;
+                }
+                command[pos++] = 1;
+                if (alertCategory == AlertCategory.CustomHuami) {
+                    command[pos] = customIconId;
+                }
+
+                System.arraycopy(rawmessage, 0, command, prefixlength, length);
+                System.arraycopy(appSuffix, 0, command, prefixlength + length, appSuffix.length);
+
+                writeToChunked(builder, 0, command);
+            } else {
+                AlertNotificationProfile<?> profile = new AlertNotificationProfile(this);
+                NewAlert alert = new NewAlert(alertCategory, 1, message, customIconId);
+                profile.setMaxLength(maxLength);
+                profile.newAlert(builder, alert);
+            }
+            builder.queue(getQueue());
+        } catch (IOException ex) {
+            LOG.error("Unable to send notification to device", ex);
+        }
+    }
+
+    protected boolean notificationHasExtraHeader() {
+        return false;
+    }
+
+    protected int notificationMaxLength() {
+        return 230;
+    }
+
+    @Override
+    public void onSetReminders(ArrayList<? extends Reminder> reminders) {
+        final TransactionBuilder builder;
+        try {
+            builder = performInitialized("onSetReminders");
+        } catch (final IOException e) {
+            LOG.error("Unable to send reminders to device", e);
+            return;
+        }
+
+        sendReminders(builder, reminders);
+
+        builder.queue(getQueue());
+    }
+
+    private void sendReminders(final TransactionBuilder builder) {
+        final List<? extends Reminder> reminders = DBHelper.getReminders(gbDevice);
+        sendReminders(builder, reminders);
+    }
+
+    private void sendReminders(final TransactionBuilder builder, final List<? extends Reminder> reminders) {
+        final DeviceCoordinator coordinator = gbDevice.getDeviceCoordinator();
+
+        int reservedSlots = GBApplication.getDevicePrefs(gbDevice).getReservedReminderCalendarSlots();
+        LOG.info("On Set Reminders. Reminders: {}, Reserved slots: {}", reminders.size(), reservedSlots);
+
+        // Send the reminders, skipping the reserved slots for calendar events
+        for (int i = 0; i < reminders.size(); i++) {
+            LOG.debug("Sending reminder at position {}", i + reservedSlots);
+
+            sendReminderToDevice(builder, i + reservedSlots, reminders.get(i));
+        }
+
+        // Delete the remaining slots, skipping the sent reminders and reserved slots
+        final int reminderSlotCount = coordinator.getReminderSlotCount(getDevice());
+        for (int i = reminders.size() + reservedSlots; i < reminderSlotCount; i++) {
+            LOG.debug("Deleting reminder at position {}", i);
+
+            sendReminderToDevice(builder, i, null);
+        }
+    }
+
+    private void sendReminderToDevice(final TransactionBuilder builder, int position, final Reminder reminder) {
+        if (characteristicChunked == null) {
+            LOG.warn("characteristicChunked is null, not sending reminder");
+            return;
+        }
+
+        final DeviceCoordinator coordinator = gbDevice.getDeviceCoordinator();
+        final int reminderSlotCount = coordinator.getReminderSlotCount(getDevice());
+
+        if (position + 1 > reminderSlotCount) {
+            LOG.error("Reminder for position {} is over the limit of {} reminders", position, reminderSlotCount);
+            return;
+        }
+
+        if (reminder == null) {
+            // Delete reminder
+            writeToChunked(builder, 2, new byte[]{(byte) 0x0b, (byte) (position & 0xFF), 0x08, 0, 0, 0, 0});
+
+            return;
+        }
+
+        final byte[] reminderMessage = StringUtils.truncate(reminder.getMessage(), coordinator.getMaximumReminderMessageLength())
+                .getBytes(StandardCharsets.UTF_8);
+        final ByteBuffer buf = ByteBuffer.allocate(14 + reminderMessage.length).order(ByteOrder.LITTLE_ENDIAN);
+
+        buf.put((byte) 0x0B);
+        buf.put((byte) (position & 0xFF));
+
+        final Calendar cal = Calendar.getInstance();
+        cal.setTime(reminder.getDate());
+
+        int eventConfig = 0x01 | 0x08; // flags 0x01 = enable, 0x04 = end date present (not on reminders), 0x08 = has text
+
+        switch(reminder.getRepetition()) {
+            case Reminder.ONCE:
+                // Default is once, nothing to do
+                break;
+            case Reminder.EVERY_DAY:
+                eventConfig |= 0x0fe0; // all week day bits set
+                break;
+            case Reminder.EVERY_WEEK:
+                int dayOfWeek = BLETypeConversions.dayOfWeekToRawBytes(cal) - 1; // Monday = 0
+                eventConfig |= 0x20 << dayOfWeek;
+                break;
+            case Reminder.EVERY_MONTH:
+                eventConfig |= 0x1000;
+                break;
+            case Reminder.EVERY_YEAR:
+                eventConfig |= 0x2000;
+                break;
+            default:
+                LOG.warn("Unknown repetition for reminder in position {}, defaulting to once", position);
+        }
+
+        buf.putInt(eventConfig);
+
+        buf.put(BLETypeConversions.shortCalendarToRawBytes(cal));
+        buf.put((byte) 0x00);
+        buf.put(reminderMessage);
+        buf.put((byte) 0x00);
+
+        writeToChunked(builder, 2, buf.array());
+    }
+
+    @Override
+    public void onSetWorldClocks(ArrayList<? extends WorldClock> clocks) {
+        final TransactionBuilder builder;
+        try {
+            builder = performInitialized("onSetWorldClocks");
+        } catch (final IOException e) {
+            LOG.error("Unable to send world clocks to device", e);
+            return;
+        }
+
+        sendWorldClocks(builder, clocks);
+
+        builder.queue(getQueue());
+    }
+
+    private void setWorldClocks(final TransactionBuilder builder) {
+        final List<? extends WorldClock> clocks = DBHelper.getWorldClocks(gbDevice);
+        sendWorldClocks(builder, clocks);
+    }
+
+    protected void sendWorldClocks(final TransactionBuilder builder, final List<? extends WorldClock> clocks) {
+        final DeviceCoordinator coordinator = gbDevice.getDeviceCoordinator();
+        if (coordinator.getWorldClocksSlotCount() == 0) {
+            return;
+        }
+
+        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+
+        try {
+            baos.write(0x03);
+
+            if (clocks.size() != 0) {
+                baos.write(clocks.size());
+                int i = 0;
+                for (final WorldClock clock : clocks) {
+                    baos.write(i++);
+                    baos.write(encodeWorldClock(clock));
+                }
+            } else {
+                baos.write(0);
+            }
+        } catch (final IOException e) {
+            LOG.error("Unable to send world clocks to device", e);
+            return;
+        }
+
+        writeToChunked2021(builder, (short) 0x0008, baos.toByteArray(), isWorldClocksEncrypted());
+    }
+
+    protected boolean isWorldClocksEncrypted() {
+        return false;
+    }
+
+    private byte[] encodeWorldClock(final WorldClock clock) {
+        final DeviceCoordinator coordinator = gbDevice.getDeviceCoordinator();
+
+        try {
+            final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+
+            final TimeZone timezone = TimeZone.getTimeZone(clock.getTimeZoneId());
+            final ZoneId zoneId = ZoneId.of(clock.getTimeZoneId());
+
+            // Usually the 3-letter city code (eg. LIS for Lisbon)
+            if (clock.getCode() != null) {
+                baos.write(StringUtils.truncate(clock.getCode(), 3).toUpperCase().getBytes(StandardCharsets.UTF_8));
+            } else {
+                baos.write(StringUtils.truncate(clock.getLabel(), 3).toUpperCase().getBytes(StandardCharsets.UTF_8));
+            }
+            baos.write(0x00);
+
+            // Some other string? Seems to be empty
+            baos.write(0x00);
+
+            // The city name / label that shows up on the band
+            baos.write(StringUtils.truncate(clock.getLabel(), coordinator.getWorldClocksLabelLength()).getBytes(StandardCharsets.UTF_8));
+            baos.write(0x00);
+
+            // The raw offset from UTC, in number of 15-minute blocks
+            baos.write((int) (timezone.getRawOffset() / (1000L * 60L * 15L)));
+
+            // Daylight savings
+            final boolean useDaylightTime = timezone.useDaylightTime();
+            final boolean inDaylightTime = timezone.inDaylightTime(new Date());
+            byte daylightByte = 0;
+            // The daylight savings offset, either currently (the previous transition) or future (the next transition), in minutes
+            byte daylightOffsetMinutes = 0;
+
+            final ZoneRules zoneRules = zoneId.getRules();
+            if (useDaylightTime) {
+                final ZoneOffsetTransition transition;
+                if (inDaylightTime) {
+                    daylightByte = 0x01;
+                    transition = zoneRules.previousTransition(Instant.now());
+                } else {
+                    daylightByte = 0x02;
+                    transition = zoneRules.nextTransition(Instant.now());
+                }
+                daylightOffsetMinutes = (byte) transition.getDuration().toMinutes();
+            }
+
+            baos.write(daylightByte);
+            baos.write(daylightOffsetMinutes);
+
+            // The timestamp of the next daylight savings transition, if any
+            final ZoneOffsetTransition nextTransition = zoneRules.nextTransition(Instant.now());
+            long nextTransitionTs = 0;
+            if (nextTransition != null) {
+                nextTransitionTs = nextTransition
+                        .getDateTimeBefore()
+                        .atZone(zoneId)
+                        .toEpochSecond();
+            }
+
+            for (int i = 0; i < 4; i++) {
+                baos.write((byte) ((nextTransitionTs >> (i * 8)) & 0xff));
+            }
+
+            if (coordinator.supportsDisabledWorldClocks()) {
+                baos.write((byte) (clock.getEnabled() ? 0x01 : 0x00));
+            }
+
+            return baos.toByteArray();
+        } catch (final IOException e) {
+            throw new RuntimeException("This should never happen", e);
+        }
+    }
+
+    @Override
+    public void onSetTime() {
+        try {
+            TransactionBuilder builder = performInitialized("Set date and time");
+            setCurrentTimeWithService(builder);
+            //TODO: once we have a common strategy for sending events (e.g. EventHandler), remove this call from here. Meanwhile it does no harm.
+            // = we should genaralize the pebble calender code
+            sendCalendarEvents(builder);
+            builder.queue(getQueue());
+        } catch (IOException ex) {
+            LOG.error("Unable to set time on Huami device", ex);
+        }
+    }
+
+    @Override
+    public void onSetCallState(CallSpec callSpec) {
+        if (callSpec.command == CallSpec.CALL_INCOMING) {
+            telephoneRinging = true;
+            AbortTransactionAction abortAction = new StopNotificationAction(getCharacteristic(UUID_CHARACTERISTIC_ALERT_LEVEL)) {
+                @Override
+                protected boolean shouldAbort() {
+                    return !isTelephoneRinging();
+                }
+            };
+            String message = NotificationUtils.getPreferredTextFor(callSpec);
+            SimpleNotification simpleNotification = new SimpleNotification(message, AlertCategory.IncomingCall, null);
+            performPreferredNotification("incoming call", MiBandConst.ORIGIN_INCOMING_CALL, simpleNotification, HuamiService.ALERT_LEVEL_PHONE_CALL, abortAction);
+        } else if ((callSpec.command == CallSpec.CALL_START) || (callSpec.command == CallSpec.CALL_END)) {
+            telephoneRinging = false;
+            stopCurrentCallNotification();
+        }
+    }
+
+    public void onSetCallStateNew(CallSpec callSpec) {
+        if (callSpec.command == CallSpec.CALL_INCOMING) {
+            byte[] message = NotificationUtils.getPreferredTextFor(callSpec).getBytes();
+            int length = 10 + message.length;
+            ByteBuffer buf = ByteBuffer.allocate(length);
+            buf.order(ByteOrder.LITTLE_ENDIAN);
+            buf.put(new byte[]{3, 0, 0, 0, 0, 0});
+            buf.put(message);
+            buf.put(new byte[]{0, 0, 0, 2});
+            try {
+                TransactionBuilder builder = performInitialized("incoming call");
+                writeToChunked(builder, 0, buf.array());
+                builder.queue(getQueue());
+            } catch (IOException e) {
+                LOG.error("Unable to send incoming call");
+            }
+        } else if ((callSpec.command == CallSpec.CALL_START) || (callSpec.command == CallSpec.CALL_END)) {
+            try {
+                TransactionBuilder builder = performInitialized("end call");
+                writeToChunked(builder, 0, new byte[]{3, 3, 0, 0, 0, 0});
+                builder.queue(getQueue());
+            } catch (IOException e) {
+                LOG.error("Unable to send end call");
+            }
+        }
+    }
+
+    private void stopCurrentCallNotification() {
+        try {
+            TransactionBuilder builder = performInitialized("stop notification");
+            getNotificationStrategy().stopCurrentNotification(builder);
+            builder.queue(getQueue());
+        } catch (IOException e) {
+            LOG.error("Error stopping call notification");
+        }
+    }
+
+    @Override
+    public void onSetCannedMessages(CannedMessagesSpec cannedMessagesSpec) {
+        if (cannedMessagesSpec.type == CannedMessagesSpec.TYPE_REJECTEDCALLS) {
+            try {
+                TransactionBuilder builder = performInitialized("Set canned messages");
+                int handle = 0x12345678;
+
+                for (int i = 0; i < 16; i++) {
+                    byte[] delete_command = new byte[]{0x07, (byte) (handle & 0xff), (byte) ((handle & 0xff00) >> 8), (byte) ((handle & 0xff0000) >> 16), (byte) ((handle & 0xff000000) >> 24)};
+                    writeToChunked2021(builder, (short) 0x0013, delete_command, false);
+                    handle++;
+                }
+                handle = 0x12345678;
+                for (String cannedMessage : cannedMessagesSpec.cannedMessages) {
+                    int length = cannedMessage.getBytes().length + 6;
+                    ByteBuffer buf = ByteBuffer.allocate(length);
+                    buf.order(ByteOrder.LITTLE_ENDIAN);
+                    buf.put((byte) 0x05); // create
+                    buf.putInt(handle++);
+                    buf.put(cannedMessage.getBytes());
+                    buf.put((byte) 0x00);
+                    writeToChunked2021(builder, (short) 0x0013, buf.array(), false);
+                }
+                builder.queue(getQueue());
+            } catch (IOException ex) {
+                LOG.error("Unable to set canned messages on Huami device", ex);
+            }
+        }
+    }
+
+    private boolean isTelephoneRinging() {
+        // don't synchronize, this is not really important
+        return telephoneRinging;
+    }
+
+    @Override
+    public void onSetMusicState(final MusicStateSpec stateSpec) {
+        if (!getCoordinator().supportsMusicInfo()) {
+            return;
+        }
+
+        if (mediaManager.onSetMusicState(stateSpec) && isMusicAppStarted) {
+            sendMusicStateToDevice(null, mediaManager.getBufferMusicStateSpec());
+        }
+    }
+
+    @Override
+    public void onSetMusicInfo(final MusicSpec musicSpec) {
+        if (!getCoordinator().supportsMusicInfo()) {
+            return;
+        }
+
+        if (mediaManager.onSetMusicInfo(musicSpec) && isMusicAppStarted) {
+            sendMusicStateToDevice(mediaManager.getBufferMusicSpec(), mediaManager.getBufferMusicStateSpec());
+        }
+    }
+
+    public void onMusicAppOpen() {
+        LOG.info("Music app started");
+        isMusicAppStarted = true;
+        sendMusicStateDelayed();
+    }
+
+    public void onMusicAppClosed() {
+        LOG.info("Music app terminated");
+        isMusicAppStarted = false;
+    }
+
+    /**
+     * Send the music state after a small delay. If we send it right as the app notifies us that it opened,
+     * it won't be recognized.
+     */
+    private void sendMusicStateDelayed() {
+        final Looper mainLooper = Looper.getMainLooper();
+        new Handler(mainLooper).postDelayed(() -> {
+            mediaManager.refresh();
+            sendMusicStateToDevice(mediaManager.getBufferMusicSpec(), mediaManager.getBufferMusicStateSpec());
+            onSetPhoneVolume(mediaManager.getPhoneVolume());
+        }, 100);
+    }
+
+    @Override
+    public void onSetPhoneVolume(final float volume) {
+        if (characteristicChunked == null) {
+            return;
+        }
+
+        final byte[] volumeCommand = new byte[]{MUSIC_FLAG_VOLUME, (byte) Math.round(volume)};
+
+        try {
+            final TransactionBuilder builder = performInitialized("send volume");
+            writeToChunked(builder, 3, volumeCommand);
+
+            builder.queue(getQueue());
+        } catch (final IOException e) {
+            LOG.error("Unable to send volume", e);
+        }
+
+        LOG.info("sendVolumeStateToDevice: {}", volume);
+    }
+
+    protected void sendMusicStateToDevice(final MusicSpec musicSpec, final MusicStateSpec musicStateSpec) {
+        if (characteristicChunked == null) {
+            return;
+        }
+
+        if (musicStateSpec == null) {
+            return;
+        }
+
+        try {
+            TransactionBuilder builder = performInitialized("send playback info");
+            writeToChunked(builder, 3, encodeMusicState(getContext(), musicSpec, musicStateSpec, false));
+            builder.queue(getQueue());
+        } catch (IOException e) {
+            LOG.error("Unable to send playback state");
+        }
+        LOG.info("sendMusicStateToDevice: {}, {}", musicSpec, musicStateSpec);
+    }
+
+    public static byte[] encodeMusicState(final Context context,
+                                          final MusicSpec musicSpec,
+                                          final MusicStateSpec musicStateSpec,
+                                          final boolean includeVolume) {
+        String artist = "";
+        String album = "";
+        String track = "";
+
+        byte flags = 0x00;
+        int length = 1;
+
+        if (musicStateSpec != null) {
+            length += 4;
+            flags |= MUSIC_FLAG_STATE;
+        }
+
+        if (includeVolume) {
+            length += 1;
+            flags |= MUSIC_FLAG_VOLUME;
+        }
+
+        if (musicSpec != null) {
+            artist = StringUtils.truncate(musicSpec.artist, 80);
+            album = StringUtils.truncate(musicSpec.album, 80);
+            track = StringUtils.truncate(musicSpec.track, 80);
+
+            if (artist.getBytes().length > 0) {
+                length += artist.getBytes().length + 1;
+                flags |= MUSIC_FLAG_ARTIST;
+            }
+            if (album.getBytes().length > 0) {
+                length += album.getBytes().length + 1;
+                flags |= MUSIC_FLAG_ALBUM;
+            }
+            if (track.getBytes().length > 0) {
+                length += track.getBytes().length + 1;
+                flags |= MUSIC_FLAG_TRACK;
+            }
+            if (musicSpec.duration != 0) {
+                length += 2;
+                flags |= MUSIC_FLAG_DURATION;
+            }
+        }
+
+        ByteBuffer buf = ByteBuffer.allocate(length);
+        buf.order(ByteOrder.LITTLE_ENDIAN);
+        buf.put(flags);
+
+        if (musicStateSpec != null) {
+            byte state;
+            switch (musicStateSpec.state) {
+                case MusicStateSpec.STATE_PLAYING:
+                    state = 1;
+                    break;
+                default:
+                    state = 0;
+            }
+
+            buf.put(state);
+            buf.put((byte) 0);
+            buf.putShort((short) musicStateSpec.position);
+        }
+
+        if (musicSpec != null) {
+            if (artist.getBytes().length > 0) {
+                buf.put(artist.getBytes());
+                buf.put((byte) 0);
+            }
+            if (album.getBytes().length > 0) {
+                buf.put(album.getBytes());
+                buf.put((byte) 0);
+            }
+            if (track.getBytes().length > 0) {
+                buf.put(track.getBytes());
+                buf.put((byte) 0);
+            }
+            if (musicSpec.duration != 0) {
+                buf.putShort((short) musicSpec.duration);
+            }
+        }
+
+        if (includeVolume) {
+            buf.put((byte) MediaManager.getPhoneVolume(context));
+        }
+
+        return buf.array();
+    }
+
+    @Override
+    public void onReset(int flags) {
+        try {
+            TransactionBuilder builder = performInitialized("Reset");
+            if ((flags & GBDeviceProtocol.RESET_FLAGS_FACTORY_RESET) != 0) {
+                sendFactoryReset(builder);
+            } else {
+                sendReboot(builder);
+            }
+            builder.queue(getQueue());
+        } catch (IOException ex) {
+            LOG.error("Unable to reset", ex);
+        }
+    }
+
+    public HuamiSupport sendReboot(TransactionBuilder builder) {
+        builder.write(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_FIRMWARE), new byte[] { HuamiService.COMMAND_FIRMWARE_REBOOT});
+        return this;
+    }
+
+    public HuamiSupport sendFactoryReset(TransactionBuilder builder) {
+        writeToConfiguration(builder,  HuamiService.COMMAND_FACTORY_RESET);
+        return this;
+    }
+
+    @Override
+    public void onHeartRateTest() {
+        if (characteristicHRControlPoint == null) {
+            return;
+        }
+        try {
+            TransactionBuilder builder = performInitialized("HeartRateTest");
+            enableNotifyHeartRateMeasurements(true, builder);
+            builder.write(characteristicHRControlPoint, stopHeartMeasurementContinuous);
+            builder.write(characteristicHRControlPoint, stopHeartMeasurementManual);
+            builder.write(characteristicHRControlPoint, startHeartMeasurementManual);
+            builder.queue(getQueue());
+        } catch (IOException ex) {
+            LOG.error("Unable to read heart rate from Huami device", ex);
+        }
+    }
+
+    @Override
+    public void onEnableRealtimeHeartRateMeasurement(boolean enable) {
+        if (characteristicHRControlPoint == null) {
+            return;
+        }
+        try {
+            TransactionBuilder builder = performInitialized("Enable realtime heart rate measurement");
+            enableNotifyHeartRateMeasurements(enable, builder);
+            if (enable) {
+                builder.write(characteristicHRControlPoint, stopHeartMeasurementManual);
+                builder.write(characteristicHRControlPoint, startHeartMeasurementContinuous);
+            } else {
+                builder.write(characteristicHRControlPoint, stopHeartMeasurementContinuous);
+            }
+            builder.queue(getQueue());
+            enableRealtimeSamplesTimer(enable);
+        } catch (IOException ex) {
+            LOG.error("Unable to enable realtime heart rate measurement", ex);
+        }
+    }
+
+    protected void enableNotifyHeartRateMeasurements(boolean enable, TransactionBuilder builder) {
+        if (heartRateNotifyEnabled != enable) {
+            BluetoothGattCharacteristic heartrateCharacteristic = getCharacteristic(GattCharacteristic.UUID_CHARACTERISTIC_HEART_RATE_MEASUREMENT);
+            if (heartrateCharacteristic != null) {
+                builder.notify(heartrateCharacteristic, enable);
+                heartRateNotifyEnabled = enable;
+            }
+        }
+    }
+
+    @Override
+    public void onFindDevice(boolean start) {
+        if(findDeviceLoopTimer != null)
+            findDeviceLoopTimer.cancel();
+
+        if (start) {
+            int loopInterval = getFindDeviceInterval();
+            LOG.info("Sending find device, interval: " + loopInterval);
+            findDeviceLoopTimer = new Timer("Huami Find Loop Timer");
+            findDeviceLoopTimer.scheduleAtFixedRate(new TimerTask() {
+                @Override
+                public void run() {
+                    sendFindDeviceCommand(true);
+                }
+            }, loopInterval, loopInterval);
+        }
+        sendFindDeviceCommand(start);
+    }
+
+    protected int getFindDeviceInterval() {
+        final VibrationProfile findBand = HuamiCoordinator.getVibrationProfile(
+                getDevice().getAddress(),
+                HuamiVibrationPatternNotificationType.FIND_BAND,
+                supportsDeviceDefaultVibrationProfiles()
+        );
+        int findDeviceInterval = 0;
+
+        if (findBand != null) {
+            // It can be null if the device supports continuous find mode
+            // If that's the case, this function shouldn't even have been called
+            for(int len : findBand.getOnOffSequence())
+                findDeviceInterval += len;
+
+            if(findBand.getRepeat() > 0)
+                findDeviceInterval *= findBand.getRepeat();
+
+            if(findDeviceInterval > 10000) // 10 seconds, about as long as Mi Fit allows
+                findDeviceInterval = 10000;
+        } else {
+            findDeviceInterval = 10000;
+        }
+
+        return findDeviceInterval;
+    }
+
+    protected void sendFindDeviceCommand(boolean start) {
+        BluetoothGattCharacteristic characteristic = getCharacteristic(UUID_CHARACTERISTIC_ALERT_LEVEL);
+        try {
+            TransactionBuilder builder = performInitialized("find huami");
+            builder.write(characteristic, start ? new byte[] {3} : new byte[] {0});
+            builder.queue(getQueue());
+        } catch (IOException e) {
+            LOG.error("error while sending find Huami device command", e);
+        }
+    }
+
+    @Override
+    public void onFetchRecordedData(int dataTypes) {
+        final HuamiCoordinator coordinator = getCoordinator();
+
+        if ((dataTypes & RecordedDataTypes.TYPE_ACTIVITY) != 0) {
+            this.fetchOperationQueue.add(new FetchActivityOperation(this));
+        }
+
+        if ((dataTypes & RecordedDataTypes.TYPE_GPS_TRACKS) != 0 && coordinator.supportsActivityTracks()) {
+            this.fetchOperationQueue.add(new FetchSportsSummaryOperation(this, 1));
+        }
+
+        if ((dataTypes & RecordedDataTypes.TYPE_DEBUGLOGS) != 0 && coordinator.supportsDebugLogs()) {
+            this.fetchOperationQueue.add(new FetchDebugLogsOperation(this));
+        }
+
+        if ((dataTypes & RecordedDataTypes.TYPE_STRESS) != 0 && coordinator.supportsStressMeasurement()) {
+            this.fetchOperationQueue.add(new FetchStressAutoOperation(this));
+            this.fetchOperationQueue.add(new FetchStressManualOperation(this));
+        }
+
+        if ((dataTypes & RecordedDataTypes.TYPE_PAI) != 0 && coordinator.supportsPai()) {
+            this.fetchOperationQueue.add(new FetchPaiOperation(this));
+        }
+
+        if ((dataTypes & RecordedDataTypes.TYPE_SPO2) != 0 && coordinator.supportsSpo2(gbDevice)) {
+            this.fetchOperationQueue.add(new FetchSpo2NormalOperation(this));
+        }
+
+        if (ZeppOsCoordinator.experimentalFeatures(getDevice())) {
+            if ((dataTypes & RecordedDataTypes.TYPE_HEART_RATE) != 0 && coordinator.supportsHeartRateStats()) {
+                this.fetchOperationQueue.add(new FetchHeartRateManualOperation(this));
+                this.fetchOperationQueue.add(new FetchHeartRateMaxOperation(this));
+                this.fetchOperationQueue.add(new FetchHeartRateRestingOperation(this));
+            }
+
+            if ((dataTypes & RecordedDataTypes.TYPE_SLEEP_RESPIRATORY_RATE) != 0 && coordinator.supportsSleepRespiratoryRate()) {
+                this.fetchOperationQueue.add(new FetchSleepRespiratoryRateOperation(this));
+            }
+
+            if ((dataTypes & RecordedDataTypes.TYPE_TEMPERATURE) != 0) {
+                this.fetchOperationQueue.add(new FetchTemperatureOperation(this));
+            }
+        }
+
+        if ((dataTypes & RecordedDataTypes.TYPE_HUAMI_STATISTICS) != 0) {
+            this.fetchOperationQueue.add(new FetchStatisticsOperation(this));
+        }
+
+        final AbstractFetchOperation nextOperation = this.fetchOperationQueue.poll();
+        if (nextOperation != null) {
+            try {
+                nextOperation.perform();
+            } catch (final IOException e) {
+                LOG.error("Unable to fetch recorded data", e);
+            }
+        }
+    }
+
+    public AbstractFetchOperation getNextFetchOperation() {
+        return fetchOperationQueue.poll();
+    }
+
+    public LinkedList<AbstractFetchOperation> getFetchOperationQueue() {
+        return fetchOperationQueue;
+    }
+
+    @Override
+    public void onEnableRealtimeSteps(boolean enable) {
+        try {
+            TransactionBuilder builder = performInitialized(enable ? "Enabling realtime steps notifications" : "Disabling realtime steps notifications");
+            if (enable) {
+                builder.read(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_7_REALTIME_STEPS));
+            }
+            builder.notify(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_7_REALTIME_STEPS), enable);
+            builder.queue(getQueue());
+            enableRealtimeSamplesTimer(enable);
+        } catch (IOException e) {
+            LOG.error("Unable to change realtime steps notification to: " + enable, e);
+        }
+    }
+
+    @Override
+    public void onInstallApp(Uri uri) {
+        try {
+            createUpdateFirmwareOperation(uri).perform();
+        } catch (IOException ex) {
+            GB.toast(getContext(), "Firmware cannot be installed: " + ex.getMessage(), Toast.LENGTH_LONG, GB.ERROR, ex);
+        }
+    }
+
+    // this could go though onion code with preferred notification, but I this should work on all huami devices
+    private void vibrateOnce() {
+        BluetoothGattCharacteristic characteristic = getCharacteristic(UUID_CHARACTERISTIC_ALERT_LEVEL);
+        try {
+            TransactionBuilder builder = performInitialized("Vibrate once");
+            builder.write(characteristic,new byte[] {3});
+            builder.queue(getQueue());
+        } catch (IOException e) {
+            LOG.error("error while sending simple vibrate command", e);
+        }
+    }
+
+    private void processButtonAction() {
+        if (currentButtonTimerActivationTime != currentButtonPressTime) {
+            return;
+        }
+        //handle user events settings. 0 is long press, rest are button_id 1-3
+        switch (currentButtonActionId) {
+            case 0:
+                executeButtonAction("button_long_press_action_selection");
+                break;
+            case 1:
+                executeButtonAction("button_single_press_action_selection");
+                break;
+            case 2:
+                executeButtonAction("button_double_press_action_selection");
+                break;
+            case 3:
+                executeButtonAction("button_triple_press_action_selection");
+                break;
+            default:
+                break;
+        }
+
+        currentButtonActionId = 0;
+        currentButtonPressCount = 0;
+        currentButtonPressTime = System.currentTimeMillis();
+    }
+
+    private void executeButtonAction(String buttonKey) {
+        String buttonPreference = getDevicePrefs().getString(buttonKey, PREF_BUTTON_ACTION_SELECTION_OFF);
+
+        if (buttonPreference.equals(PREF_BUTTON_ACTION_SELECTION_OFF)) {
+            return;
+        }
+        if (getDevicePrefs().getBoolean(HuamiConst.PREF_BUTTON_ACTION_VIBRATE, false)) {
+            vibrateOnce();
+        }
+        switch (buttonPreference) {
+            case PREF_BUTTON_ACTION_SELECTION_BROADCAST:
+                sendSystemBroadcastWithButtonId();
+                break;
+            case PREF_BUTTON_ACTION_SELECTION_FITNESS_APP_START:
+                OpenTracksController.startRecording(this.getContext());
+                break;
+            case PREF_BUTTON_ACTION_SELECTION_FITNESS_APP_STOP:
+                OpenTracksController.stopRecording(this.getContext());
+                break;
+            case PREF_BUTTON_ACTION_SELECTION_FITNESS_APP_TOGGLE:
+                OpenTracksController.toggleRecording(this.getContext());
+                break;
+            default:
+                handleMediaButton(buttonPreference);
+        }
+    }
+
+    private void sendSystemBroadcastWithButtonId() {
+        Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()));
+        String requiredButtonPressMessage = prefs.getString(HuamiConst.PREF_BUTTON_ACTION_BROADCAST,
+                this.getContext().getString(R.string.mi2_prefs_button_press_broadcast_default_value));
+        Intent in = new Intent();
+        in.setAction(requiredButtonPressMessage);
+        in.putExtra("button_id", currentButtonActionId);
+        LOG.info("Sending " + requiredButtonPressMessage + " with button_id " + currentButtonActionId);
+        this.getContext().getApplicationContext().sendBroadcast(in);
+    }
+
+    private void handleMediaButton(String mediaAction) {
+        if (mediaAction.equals(PREF_BUTTON_ACTION_SELECTION_OFF)) {
+            return;
+        }
+        GBDeviceEventMusicControl deviceEventMusicControl = new GBDeviceEventMusicControl();
+        deviceEventMusicControl.event = GBDeviceEventMusicControl.Event.valueOf(mediaAction);
+        evaluateGBDeviceEvent(deviceEventMusicControl);
+    }
+
+    private void handleDeviceEvent(byte[] value) {
+        if (value == null || value.length == 0) {
+            return;
+        }
+        GBDeviceEventCallControl callCmd = new GBDeviceEventCallControl();
+
+        switch (value[0]) {
+            case HuamiDeviceEvent.CALL_REJECT:
+                LOG.info("call rejected");
+                callCmd.event = GBDeviceEventCallControl.Event.REJECT;
+                evaluateGBDeviceEvent(callCmd);
+                break;
+            case HuamiDeviceEvent.CALL_IGNORE:
+                LOG.info("call ignored");
+                callCmd.event = GBDeviceEventCallControl.Event.IGNORE;
+                evaluateGBDeviceEvent(callCmd);
+                break;
+            case HuamiDeviceEvent.BUTTON_PRESSED:
+                LOG.info("button pressed");
+                handleButtonEvent();
+                break;
+            case HuamiDeviceEvent.BUTTON_PRESSED_LONG:
+                LOG.info("button long-pressed ");
+                handleLongButtonEvent();
+                break;
+            case HuamiDeviceEvent.START_NONWEAR:
+                LOG.info("non-wear start detected");
+                processDeviceEvent(HuamiDeviceEvent.START_NONWEAR);
+                break;
+            case HuamiDeviceEvent.ALARM_TOGGLED:
+            case HuamiDeviceEvent.ALARM_CHANGED:
+                LOG.info("An alarm was toggled or changed");
+                TransactionBuilder builder = new TransactionBuilder("requestAlarms");
+                requestAlarms(builder);
+                builder.queue(getQueue());
+                break;
+            case HuamiDeviceEvent.FELL_ASLEEP:
+                LOG.info("Fell asleep");
+                processDeviceEvent(HuamiDeviceEvent.FELL_ASLEEP);
+                break;
+            case HuamiDeviceEvent.WOKE_UP:
+                LOG.info("Woke up");
+                processDeviceEvent(HuamiDeviceEvent.WOKE_UP);
+                break;
+            case HuamiDeviceEvent.STEPSGOAL_REACHED:
+                LOG.info("Steps goal reached");
+                break;
+            case HuamiDeviceEvent.TICK_30MIN:
+                LOG.info("Tick 30 min (?)");
+                break;
+            case HuamiDeviceEvent.FIND_PHONE_START:
+                LOG.info("find phone started");
+                acknowledgeFindPhone(); // FIXME: premature
+                findPhoneEvent.event = GBDeviceEventFindPhone.Event.START;
+                evaluateGBDeviceEvent(findPhoneEvent);
+                break;
+            case HuamiDeviceEvent.FIND_PHONE_STOP:
+                LOG.info("find phone stopped");
+                findPhoneEvent.event = GBDeviceEventFindPhone.Event.STOP;
+                evaluateGBDeviceEvent(findPhoneEvent);
+                break;
+            case HuamiDeviceEvent.SILENT_MODE:
+                final boolean silentModeEnabled = value[1] == 1;
+                LOG.info("silent mode = {}", silentModeEnabled);
+                sendPhoneSilentMode(silentModeEnabled);
+                evaluateGBDeviceEvent(new GBDeviceEventSilentMode(silentModeEnabled));
+                break;
+            case HuamiDeviceEvent.MUSIC_CONTROL:
+                LOG.info("got music control");
+                GBDeviceEventMusicControl deviceEventMusicControl = new GBDeviceEventMusicControl();
+
+                switch (value[1]) {
+                    case 0:
+                        deviceEventMusicControl.event = GBDeviceEventMusicControl.Event.PLAY;
+                        break;
+                    case 1:
+                        deviceEventMusicControl.event = GBDeviceEventMusicControl.Event.PAUSE;
+                        break;
+                    case 3:
+                        deviceEventMusicControl.event = GBDeviceEventMusicControl.Event.NEXT;
+                        break;
+                    case 4:
+                        deviceEventMusicControl.event = GBDeviceEventMusicControl.Event.PREVIOUS;
+                        break;
+                    case 5:
+                        deviceEventMusicControl.event = GBDeviceEventMusicControl.Event.VOLUMEUP;
+                        break;
+                    case 6:
+                        deviceEventMusicControl.event = GBDeviceEventMusicControl.Event.VOLUMEDOWN;
+                        break;
+                    case (byte) 224:
+                        onMusicAppOpen();
+                        break;
+                    case (byte) 225:
+                        onMusicAppClosed();
+                        break;
+                    default:
+                        LOG.info("unhandled music control event " + value[1]);
+                        return;
+                }
+                evaluateGBDeviceEvent(deviceEventMusicControl);
+                break;
+            case HuamiDeviceEvent.MTU_REQUEST:
+                int mtu = (value[2] & 0xff) << 8 | value[1] & 0xff;
+                LOG.info("device announced MTU of " + mtu);
+                setMtu(mtu);
+                /*
+                 * not really sure if this would make sense, is this event already a proof of a successful MTU
+                 * negotiation initiated by the Huami device, and acknowledged by the phone? do we really have to
+                 * requestMTU() from our side after receiving this?
+                 * /
+                if (mMTU != mtu) {
+                    requestMTU(mtu);
+                }
+                */
+                break;
+            case HuamiDeviceEvent.WORKOUT_STARTING:
+                final HuamiWorkoutTrackActivityType activityType = HuamiWorkoutTrackActivityType.fromCode(value[3]);
+                final ActivityKind activityKind;
+
+                if (activityType == null) {
+                    LOG.warn("Unknown workout activity type {}", String.format("0x%02x", value[3]));
+                    activityKind = ActivityKind.UNKNOWN;
+                } else {
+                    activityKind = activityType.toActivityKind();
+                }
+
+                final boolean needsGps = value[2] == 1;
+
+                LOG.info("Workout starting on band: {}, needs gps = {}", activityType, needsGps);
+
+                onWorkoutOpen(needsGps, activityKind);
+
+                break;
+            default:
+                LOG.warn("unhandled event {}", String.format("0x%02x", value[0]));
+        }
+    }
+
+    /**
+     * Track whether the currently selected workout needs gps (received in {@link #handleDeviceEvent}, so we can start the activity tracking
+     * if needed in {@link #handleDeviceWorkoutEvent}, since in there we don't know what's the current workout.
+     */
+    private boolean workoutNeedsGps = false;
+
+    /**
+     * Track the {@link nodomain.freeyourgadget.gadgetbridge.model.ActivityKind} that was opened, for the same reasons as {@code workoutNeedsGps}.
+     */
+    private ActivityKind workoutActivityKind = ActivityKind.UNKNOWN;
+
+    /**
+     * Track the last time we actually sent a gps location. We need to signal that GPS as re-acquired if the last update was too long ago.
+     */
+    private long lastPhoneGpsSent = 0;
+
+    protected void onWorkoutOpen(final boolean needsGps, final ActivityKind activityKind) {
+        this.workoutNeedsGps = needsGps;
+        this.workoutActivityKind = activityKind;
+
+        final boolean sendGpsToBand = HuamiCoordinator.getWorkoutSendGpsToBand(getDevice().getAddress());
+
+        if (workoutNeedsGps) {
+            if (sendGpsToBand) {
+                lastPhoneGpsSent = 0;
+                sendPhoneGps(HuamiPhoneGpsStatus.SEARCHING, null);
+                GBLocationService.start(getContext(), getDevice(), GBLocationProviderType.GPS, 1000);
+            } else {
+                sendPhoneGps(HuamiPhoneGpsStatus.DISABLED, null);
+            }
+        }
+    }
+
+    protected void onWorkoutStart() {
+        final boolean startOnPhone = HuamiCoordinator.getWorkoutStartOnPhone(getDevice().getAddress());
+
+        if (workoutNeedsGps && startOnPhone) {
+            LOG.info("Starting OpenTracks recording");
+
+            OpenTracksController.startRecording(getContext(), workoutActivityKind);
+        }
+    }
+
+    protected void onWorkoutEnd() {
+        final boolean startOnPhone = HuamiCoordinator.getWorkoutStartOnPhone(getDevice().getAddress());
+
+        GBLocationService.stop(getContext(), getDevice());
+
+        if (startOnPhone) {
+            LOG.info("Stopping OpenTracks recording");
+            OpenTracksController.stopRecording(getContext());
+        }
+    }
+
+    private void handleDeviceWorkoutEvent(byte[] value) {
+        if (value == null || value.length == 0) {
+            return;
+        }
+
+        switch (value[0]) {
+            case 0x11:
+                final HuamiWorkoutStatus status = HuamiWorkoutStatus.fromCode(value[1]);
+
+                LOG.info("Got workout status {}", status);
+
+                switch (status) {
+                    case Start:
+                        onWorkoutStart();
+                        break;
+                    case End:
+                        onWorkoutEnd();
+                        break;
+                    default:
+                        LOG.warn("Unknown workout status {}", String.format("0x%02x", value[1]));
+                        break;
+                }
+
+                break;
+            default:
+                LOG.warn("Unhandled workout event {}", String.format("0x%02x", value[0]));
+        }
+    }
+
+    @Override
+    public void onSetGpsLocation(final Location location) {
+        final boolean sendGpsToBand = HuamiCoordinator.getWorkoutSendGpsToBand(getDevice().getAddress());
+        if (!sendGpsToBand) {
+            LOG.warn("Sending GPS to band is disabled, ignoring location update");
+            return;
+        }
+
+        boolean newGpsLock = System.currentTimeMillis() - lastPhoneGpsSent > 5000;
+        lastPhoneGpsSent = System.currentTimeMillis();
+
+        final HuamiPhoneGpsStatus status = newGpsLock ? HuamiPhoneGpsStatus.ACQUIRED : null;
+
+        sendPhoneGps(status, location);
+    }
+
+    protected void sendPhoneGps(final HuamiPhoneGpsStatus status, final Location location) {
+        if (characteristicChunked == null || location == null) {
+            return;
+        }
+
+        final byte[] locationBytes = encodePhoneGpsPayload(status, location);
+
+        final ByteBuffer buf = ByteBuffer.allocate(1 + locationBytes.length);
+        buf.order(ByteOrder.LITTLE_ENDIAN);
+        buf.put((byte) 0x06);
+        buf.put(locationBytes);
+
+        try {
+            final TransactionBuilder builder = performInitialized("send phone gps location");
+            writeToChunked(builder, 6, buf.array());
+            builder.queue(getQueue());
+        } catch (final IOException e) {
+            LOG.error("Unable to send location", e);
+        }
+    }
+
+    protected byte[] encodePhoneGpsPayload(final HuamiPhoneGpsStatus status, final Location location) {
+        int flags = 0;
+        int length = 4; // Start with just the flag bytes
+
+        if (status != null) {
+            flags |= WORKOUT_GPS_FLAG_STATUS;
+            length += 1;
+        }
+
+        if (location != null) {
+            flags |= WORKOUT_GPS_FLAG_POSITION;
+            length += 31;
+        }
+
+        final ByteBuffer buf = ByteBuffer.allocate(length);
+        buf.order(ByteOrder.LITTLE_ENDIAN);
+
+        buf.putInt(flags);
+
+        if (status != null) {
+            buf.put(status.getCode());
+        }
+
+        if (location != null) {
+            buf.putInt((int) (location.getLongitude() * 3000000.0));
+            buf.putInt((int) (location.getLatitude() * 3000000.0));
+            buf.putInt((int) location.getSpeed() * 10);
+
+            buf.putInt((int) (location.getAltitude() * 100));
+            buf.putLong(location.getTime());
+
+            // Seems to always be ff ?
+            buf.putInt(0xffffffff);
+
+            // Not sure what this is, maybe bearing? It changes while moving, but
+            // doesn't seem to be needed on the Mi Band 5
+            buf.putShort((short) 0x00);
+
+            // Seems to always be 0 ?
+            buf.put((byte) 0x00);
+        }
+
+        return buf.array();
+    }
+
+    protected void requestMTU(int mtu) {
+        new TransactionBuilder("requestMtu")
+                .requestMtu(mtu)
+                .queue(getQueue());
+        mMTU = mtu;
+    }
+
+    @Override
+    public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
+        super.onMtuChanged(gatt, mtu, status);
+
+        LOG.info("MTU changed to {}", mtu);
+        setMtu(mtu);
+    }
+
+    protected void acknowledgeFindPhone() {
+        try {
+            TransactionBuilder builder = performInitialized("acknowledge find phone");
+
+            writeToConfiguration(builder,AmazfitBipService.COMMAND_ACK_FIND_PHONE_IN_PROGRESS);
+            builder.queue(getQueue());
+        } catch (Exception ex) {
+            LOG.error("Error while ending acknowledge find phone", ex);
+        }
+    }
+
+    private void sendPhoneSilentMode(final TransactionBuilder builder) {
+        final boolean silentMode = SilentMode.isPhoneInSilenceMode(getDevice().getAddress());
+
+        sendPhoneSilentMode(builder, silentMode);
+    }
+
+    private void sendPhoneSilentMode(final boolean enabled) {
+        try {
+            final TransactionBuilder builder = performInitialized("send phone silent mode");
+            sendPhoneSilentMode(builder, enabled);
+            builder.queue(getQueue());
+        } catch (final Exception ex) {
+            LOG.error("Error while sending phone silent mode", ex);
+        }
+    }
+
+    private void sendPhoneSilentMode(final TransactionBuilder builder, final boolean enabled) {
+        final byte[] cmd = {ENDPOINT_DISPLAY, 0x19, 0x00, (byte) (enabled ? 0x01 : 0x00)};
+
+        writeToConfiguration(builder, cmd);
+    }
+
+    protected void processDeviceEvent(int deviceEvent){
+        LOG.debug("Handling device event: " + deviceEvent);
+        GBDeviceEvent event;
+        switch (deviceEvent) {
+            case HuamiDeviceEvent.WOKE_UP:
+                event = new GBDeviceEventSleepStateDetection();
+                ((GBDeviceEventSleepStateDetection) event).sleepState = SleepState.AWAKE;
+                break;
+            case HuamiDeviceEvent.FELL_ASLEEP:
+                event = new GBDeviceEventSleepStateDetection();
+                ((GBDeviceEventSleepStateDetection) event).sleepState = SleepState.ASLEEP;
+                break;
+            case HuamiDeviceEvent.START_NONWEAR:
+                event = new GBDeviceEventWearState();
+                ((GBDeviceEventWearState) event).wearingState = WearingState.NOT_WEARING;
+                break;
+            default:
+                LOG.warn("Unhandled device event {}", deviceEvent);
+                return;
+        }
+
+        evaluateGBDeviceEvent(event);
+    }
+
+    private void handleLongButtonEvent(){
+        Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()));
+
+        if (!prefs.getBoolean(HuamiConst.PREF_BUTTON_ACTION_ENABLE, false)) {
+            return;
+        }
+
+        currentButtonActionId = 0;
+        currentButtonPressTime = System.currentTimeMillis();
+        currentButtonTimerActivationTime = currentButtonPressTime;
+        processButtonAction();
+
+    }
+
+    private void handleButtonEvent() {
+
+        // If disabled we return from function immediately
+        Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()));
+        if (!prefs.getBoolean(HuamiConst.PREF_BUTTON_ACTION_ENABLE, false)) {
+            return;
+        }
+
+        int buttonPressMaxDelay = prefs.getInt(HuamiConst.PREF_BUTTON_ACTION_PRESS_MAX_INTERVAL, 2000);
+        int requiredButtonPressCount = prefs.getInt(HuamiConst.PREF_BUTTON_ACTION_PRESS_COUNT, 0);
+
+        if (requiredButtonPressCount > 0) {
+            long timeSinceLastPress = System.currentTimeMillis() - currentButtonPressTime;
+
+            if ((currentButtonPressTime == 0) || (timeSinceLastPress < buttonPressMaxDelay)) {
+                currentButtonPressCount++;
+            } else {
+                currentButtonPressCount = 1;
+                currentButtonActionId = 0;
+            }
+            if (buttonActionTimer != null){
+                buttonActionTimer.cancel();
+            }
+
+            currentButtonPressTime = System.currentTimeMillis();
+            if (currentButtonPressCount == requiredButtonPressCount) {
+                currentButtonTimerActivationTime = currentButtonPressTime;
+                LOG.info("Activating button timer");
+                buttonActionTimer = new Timer("Huami Button Action Timer");
+                buttonActionTimer.scheduleAtFixedRate(new TimerTask() {
+                    @Override
+                    public void run() {
+                        processButtonAction();
+                        buttonActionTimer.cancel();
+                    }
+                }, buttonPressMaxDelay, buttonPressMaxDelay);
+
+                currentButtonActionId++;
+                currentButtonPressCount = 0;
+            }
+        }
+    }
+
+
+    @Override
+    public boolean onCharacteristicChanged(BluetoothGatt gatt,
+                                           BluetoothGattCharacteristic characteristic) {
+        if (super.onCharacteristicChanged(gatt, characteristic)) {
+            // handled upstream
+            return true;
+        }
+
+        final UUID characteristicUUID = characteristic.getUuid();
+        if (HuamiService.UUID_CHARACTERISTIC_6_BATTERY_INFO.equals(characteristicUUID)) {
+            handleBatteryInfo(characteristic.getValue(), BluetoothGatt.GATT_SUCCESS);
+            return true;
+        } else if (MiBandService.UUID_CHARACTERISTIC_REALTIME_STEPS.equals(characteristicUUID)) {
+            handleRealtimeSteps(characteristic.getValue());
+            return true;
+        } else if (GattCharacteristic.UUID_CHARACTERISTIC_HEART_RATE_MEASUREMENT.equals(characteristicUUID)) {
+            handleHeartrate(characteristic.getValue());
+            return true;
+        } else if (HuamiService.UUID_CHARACTERISTIC_AUTH.equals(characteristicUUID)) {
+            LOG.info("AUTHENTICATION?? " + characteristicUUID);
+            logMessageContent(characteristic.getValue());
+            return true;
+        } else if (HuamiService.UUID_CHARACTERISTIC_DEVICEEVENT.equals(characteristicUUID)) {
+            handleDeviceEvent(characteristic.getValue());
+            return true;
+        } else if (HuamiService.UUID_CHARACTERISTIC_WORKOUT.equals(characteristicUUID)) {
+            handleDeviceWorkoutEvent(characteristic.getValue());
+            return true;
+        } else if (HuamiService.UUID_CHARACTERISTIC_7_REALTIME_STEPS.equals(characteristicUUID)) {
+            handleRealtimeSteps(characteristic.getValue());
+            return true;
+        } else if (HuamiService.UUID_CHARACTERISTIC_3_CONFIGURATION.equals(characteristicUUID)) {
+            handleConfigurationInfo(characteristic.getValue());
+            return true;
+        } else if (HuamiService.UUID_CHARACTERISTIC_CHUNKEDTRANSFER_2021_READ.equals(characteristicUUID)) {
+            handleChunked(characteristic.getValue());
+            return true;
+        } else if (HuamiService.UUID_CHARACTERISTIC_RAW_SENSOR_DATA.equals(characteristicUUID)) {
+            handleRawSensorData(characteristic.getValue());
+            return true;
+        } else {
+            LOG.info("Unhandled characteristic changed: " + characteristicUUID);
+            logMessageContent(characteristic.getValue());
+        }
+
+        return false;
+    }
+
+    @Override
+    public boolean onCharacteristicRead(BluetoothGatt gatt,
+                                        BluetoothGattCharacteristic characteristic, int status) {
+        if (super.onCharacteristicRead(gatt, characteristic, status)) {
+            // handled upstream
+            return true;
+        }
+
+        UUID characteristicUUID = characteristic.getUuid();
+        if (GattCharacteristic.UUID_CHARACTERISTIC_DEVICE_NAME.equals(characteristicUUID)) {
+            handleDeviceName(characteristic.getValue(), status);
+            return true;
+        } else if (HuamiService.UUID_CHARACTERISTIC_6_BATTERY_INFO.equals(characteristicUUID)) {
+            handleBatteryInfo(characteristic.getValue(), status);
+            return true;
+        } else if (GattCharacteristic.UUID_CHARACTERISTIC_HEART_RATE_MEASUREMENT.equals(characteristicUUID)) {
+            logHeartrate(characteristic.getValue(), status);
+            return true;
+        } else if (HuamiService.UUID_CHARACTERISTIC_7_REALTIME_STEPS.equals(characteristicUUID)) {
+            handleRealtimeSteps(characteristic.getValue());
+            return true;
+        } else if (HuamiService.UUID_CHARACTERISTIC_DEVICEEVENT.equals(characteristicUUID)) {
+            handleDeviceEvent(characteristic.getValue());
+            return true;
+        } else if (HuamiService.UUID_CHARACTERISTIC_WORKOUT.equals(characteristicUUID)) {
+            handleDeviceWorkoutEvent(characteristic.getValue());
+            return true;
+        } else {
+            LOG.info("Unhandled characteristic read: " + characteristicUUID);
+            logMessageContent(characteristic.getValue());
+        }
+
+        return false;
+    }
+
+    @Override
+    public boolean onCharacteristicWrite(BluetoothGatt gatt,
+                                         BluetoothGattCharacteristic characteristic, int status) {
+        UUID characteristicUUID = characteristic.getUuid();
+        if (HuamiService.UUID_CHARACTERISTIC_AUTH.equals(characteristicUUID)) {
+            LOG.info("KEY AES SEND");
+            logMessageContent(characteristic.getValue());
+            return true;
+        }
+        return false;
+    }
+
+    public void logHeartrate(byte[] value, int status) {
+        if (status == BluetoothGatt.GATT_SUCCESS && value != null) {
+            LOG.info("Got heartrate:");
+            if (value.length == 2 && value[0] == 0) {
+                int hrValue = (value[1] & 0xff);
+                GB.toast(getContext(), "Heart Rate measured: " + hrValue, Toast.LENGTH_LONG, GB.INFO);
+            }
+            return;
+        }
+        logMessageContent(value);
+    }
+
+    protected void handleHeartrate(byte[] value) {
+        if (value.length == 2 && value[0] == 0) {
+            int hrValue = (value[1] & 0xff);
+            if (LOG.isDebugEnabled()) {
+                LOG.debug("heart rate: " + hrValue);
+            }
+            RealtimeSamplesSupport realtimeSamplesSupport = getRealtimeSamplesSupport();
+            realtimeSamplesSupport.setHeartrateBpm(hrValue);
+            if (!realtimeSamplesSupport.isRunning()) {
+                // single shot measurement, manually invoke storage and result publishing
+                realtimeSamplesSupport.triggerCurrentSample();
+            }
+        }
+    }
+
+    protected void handleRealtimeSteps(byte[] value) {
+        if (value == null) {
+            LOG.error("realtime steps: value is null");
+            return;
+        }
+
+        if (value.length == 13) {
+            byte[] stepsValue = new byte[] {value[1], value[2]};
+            int steps = BLETypeConversions.toUint16(stepsValue);
+            if (LOG.isDebugEnabled()) {
+                LOG.debug("realtime steps: " + steps);
+            }
+            getRealtimeSamplesSupport().setSteps(steps);
+        } else {
+            LOG.warn("Unrecognized realtime steps value: " + Logging.formatBytes(value));
+        }
+    }
+
+    byte[] reassemblyBuffer;
+    byte reassemblyType = 0x00;
+
+    private void handleConfigurationInfo(byte[] value) {
+        if (value == null || value.length < 4) {
+            return;
+        }
+        if (value[0] == 0x10 && value[2] == 0x01) {
+            if (value[1] == COMMAND_GPS_VERSION) {
+                String gpsVersion = new String(value, 3, value.length - 3);
+                LOG.info("got gps version = " + gpsVersion);
+                gbDevice.setFirmwareVersion2(gpsVersion);
+            } else if (value[1] == COMMAND_ALARMS) {
+                LOG.info("got alarms from watch");
+                decodeAndUpdateAlarmStatus(value, false);
+            } else {
+                LOG.warn("got configuration info we do not handle yet " + GB.hexdump(value, 3, -1));
+            }
+        } else if (value[0] == ((byte) 0x80) && value[1] == 0x01) {
+            boolean done = false;
+            if (value[2] == 0x00 || value[2] == (byte) 0xc0) { // first chunk or complete data
+                reassemblyBuffer = new byte[value.length - 8];
+                reassemblyType = value[4];
+                System.arraycopy(value, 8, reassemblyBuffer, 0, reassemblyBuffer.length);
+                if (value[2] == (byte) 0xc0) {
+                    done = true;
+                }
+            } else if (reassemblyBuffer != null && (value[2] == 0x40 || value[2] == (byte) 0x80)) {
+                byte[] payload = new byte[value.length - 4];
+                System.arraycopy(value, 4, payload, 0, payload.length);
+                reassemblyBuffer = ArrayUtils.addAll(reassemblyBuffer, payload);
+                if (value[2] == (byte) 0x80) {
+                    done = true;
+                }
+            }
+            if (!done) {
+                LOG.info("got chunk of configuration data for {}", String.format("0x%02x", reassemblyType));
+            } else {
+                LOG.info("got full/reassembled configuration data");
+                switch (reassemblyType) {
+                    case COMMAND_ALARMS_WITH_TIMES:
+                        decodeAndUpdateAlarmStatus(reassemblyBuffer, true);
+                        break;
+                    case COMMAND_WORKOUT_ACTIVITY_TYPES:
+                        LOG.warn("got workout activity types, not handled");
+                        logMessageContent(reassemblyBuffer);
+                        break;
+                    default:
+                        LOG.warn("got unknown chunked configuration response for {}, not handled", String.format("0x%02x", reassemblyType));
+                        break;
+                }
+
+                reassemblyBuffer = null;
+            }
+        } else {
+            LOG.warn("unknown response got from configuration request " + GB.hexdump(value, 0, -1));
+        }
+    }
+
+    private void handleChunked(final byte[] value) {
+        switch (value[0]) {
+            case 0x03:
+                if (huami2021ChunkedDecoder != null) {
+                    final boolean needsAck = huami2021ChunkedDecoder.decode(value);
+                    if (needsAck) {
+                        sendChunkedAck();
+                    }
+                } else {
+                    LOG.warn("Got chunked payload, but decoder is null");
+                }
+                return;
+            case 0x04:
+                final byte handle = value[2];
+                final byte count = value[4];
+                LOG.info("Got chunked ack, handle={}, count={}", handle, count);
+                // TODO: We should probably update the handle and count on the encoder
+                return;
+            default:
+                LOG.warn("Unhandled chunked payload of type {}", value[0]);
+        }
+    }
+
+    public void sendChunkedAck() {
+        if (characteristicChunked2021Read == null) {
+            LOG.error("Chunked read characteristic is null, can't send ack");
+            return;
+        }
+
+        final byte handle = huami2021ChunkedDecoder.getLastHandle();
+        final byte count = huami2021ChunkedDecoder.getLastCount();
+
+        try {
+            final TransactionBuilder builder = createTransactionBuilder("send chunked ack");
+            builder.write(characteristicChunked2021Read, new byte[] {0x04, 0x00, handle, 0x01, count});
+            builder.queue(getQueue());
+        } catch (final Exception e) {
+            LOG.error("Failed to send chunked ack", e);
+        }
+    }
+
+    private void decodeAndUpdateAlarmStatus(byte[] response, boolean withTimes) {
+        List<nodomain.freeyourgadget.gadgetbridge.entities.Alarm> alarms = DBHelper.getAlarms(gbDevice);
+        int maxAlarms = 10;
+
+        //FIXME: we can rather have a full struct here probably
+        boolean[] alarmsInUse = new boolean[maxAlarms];
+        boolean[] alarmsEnabled = new boolean[maxAlarms];
+        byte[] alarmsMinute = new byte[maxAlarms];
+        byte[] alarmsHour = new byte[maxAlarms];
+        byte[] alarmsRepetition = new byte[maxAlarms];
+
+        int nr_alarms;
+        byte enable_flag;
+        if (withTimes) {
+            nr_alarms = (response.length - 1) / 4;
+            enable_flag = (byte) 0x80;
+        } else {
+            nr_alarms = response[8];
+            enable_flag = (byte) 0x10;
+        }
+        for (int i = 0; i < nr_alarms; i++) {
+            int offset;
+            if (withTimes) {
+                offset = i * 4 + 1;
+            } else {
+                offset = 9 + i;
+            }
+            byte alarm_data = response[offset];
+            int index = alarm_data & 0xf;
+            if (index >= maxAlarms) {
+                GB.toast("Unexpected alarm index from device, ignoring: " + index, Toast.LENGTH_SHORT, GB.ERROR);
+                return;
+            }
+            alarmsInUse[index] = true;
+            boolean enabled = (alarm_data & enable_flag) == enable_flag;
+            alarmsEnabled[index] = enabled;
+            if (withTimes) {
+                alarmsHour[index] = response[offset + 1];
+                alarmsMinute[index] = response[offset + 2];
+                alarmsRepetition[index] = response[offset + 3];
+            }
+
+            LOG.info("alarm " + index + " is enabled:" + enabled);
+        }
+        for (nodomain.freeyourgadget.gadgetbridge.entities.Alarm alarm : alarms) {
+            int pos = alarm.getPosition();
+            boolean enabled = alarmsEnabled[pos];
+            boolean unused = !alarmsInUse[pos];
+            if (alarm.getEnabled() != enabled || alarm.getUnused() != unused || (withTimes && !unused && (alarm.getHour() != alarmsHour[pos] || alarm.getMinute() != alarmsMinute[pos] || alarm.getRepetition() != alarmsRepetition[pos]))) {
+                LOG.info("updating alarm index " + pos + " unused=" + unused + ", enabled=" + enabled);
+                alarm.setEnabled(enabled);
+                alarm.setUnused(unused);
+                if (withTimes && !unused) {
+                    alarm.setHour(alarmsHour[pos]);
+                    alarm.setMinute(alarmsMinute[pos]);
+                    alarm.setRepetition(alarmsRepetition[pos]);
+                }
+                DBHelper.store(alarm);
+                Intent intent = new Intent(DeviceService.ACTION_SAVE_ALARMS);
+                LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent);
+            }
+        }
+    }
+
+    protected void enableRealtimeSamplesTimer(boolean enable) {
+        if (enable) {
+            getRealtimeSamplesSupport().start();
+        } else {
+            if (realtimeSamplesSupport != null) {
+                realtimeSamplesSupport.stop();
+            }
+        }
+    }
+
+    private MiBandActivitySample createActivitySample(Device device, User user, int timestampInSeconds, SampleProvider provider) {
+        MiBandActivitySample sample = new MiBandActivitySample();
+        sample.setDevice(device);
+        sample.setUser(user);
+        sample.setTimestamp(timestampInSeconds);
+        sample.setProvider(provider);
+
+        return sample;
+    }
+
+    private RealtimeSamplesSupport getRealtimeSamplesSupport() {
+        if (realtimeSamplesSupport == null) {
+            realtimeSamplesSupport = new RealtimeSamplesSupport(1000, 1000) {
+                @Override
+                public void doCurrentSample() {
+
+                    try (DBHandler handler = GBApplication.acquireDB()) {
+                        DaoSession session = handler.getDaoSession();
+
+                        Device device = DBHelper.getDevice(gbDevice, session);
+                        User user = DBHelper.getUser(session);
+                        int ts = (int) (System.currentTimeMillis() / 1000);
+                        MiBand2SampleProvider provider = new MiBand2SampleProvider(gbDevice, session);
+                        MiBandActivitySample sample = createActivitySample(device, user, ts, provider);
+                        sample.setHeartRate(getHeartrateBpm());
+                        sleepAsAndroidSender.onHrChanged(sample.getHeartRate(), 0);
+
+//                        sample.setSteps(getSteps());
+                        sample.setRawIntensity(ActivitySample.NOT_MEASURED);
+                        sample.setRawKind(HuamiConst.TYPE_ACTIVITY); // to make it visible in the charts TODO: add a MANUAL kind for that?
+
+                        provider.addGBActivitySample(sample);
+
+                        // set the steps only afterwards, since realtime steps are also recorded
+                        // in the regular samples and we must not count them twice
+                        // Note: we know that the DAO sample is never committed again, so we simply
+                        // change the value here in memory.
+                        sample.setSteps(getSteps());
+
+                        if (LOG.isDebugEnabled()) {
+                            LOG.debug("realtime sample: " + sample);
+                        }
+
+                        Intent intent = new Intent(DeviceService.ACTION_REALTIME_SAMPLES)
+                                .putExtra(GBDevice.EXTRA_DEVICE, getDevice())
+                                .putExtra(DeviceService.EXTRA_REALTIME_SAMPLE, sample);
+                        LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent);
+
+                    } catch (Exception e) {
+                        LOG.warn("Unable to acquire db for saving realtime samples", e);
+                    }
+                }
+            };
+        }
+        return realtimeSamplesSupport;
+    }
+
+    private void handleDeviceName(byte[] value, int status) {
+//        if (status == BluetoothGatt.GATT_SUCCESS) {
+//            versionCmd.hwVersion = new String(value);
+//            handleGBDeviceEvent(versionCmd);
+//        }
+    }
+
+    /**
+     * Convert an alarm from the GB internal structure to a Mi Band message and put on the specified
+     * builder queue as a write message for the passed characteristic
+     *
+     * @param alarm
+     * @param builder
+     */
+    protected void queueAlarm(Alarm alarm, TransactionBuilder builder) {
+        DeviceCoordinator coordinator = gbDevice.getDeviceCoordinator();
+
+        Calendar calendar = AlarmUtils.toCalendar(alarm);
+
+        int actionMask = 0;
+        int daysMask = 0;
+        if (alarm.getEnabled() && !alarm.getUnused()) {
+            actionMask = 0x80;
+
+            if (coordinator.supportsAlarmSnoozing() && !alarm.getSnooze()) {
+                actionMask |= 0x40;
+            }
+        }
+        if (!alarm.getUnused()) {
+            daysMask = alarm.getRepetition();
+            if (!alarm.isRepetitive()) {
+                daysMask = 128;
+            }
+        }
+
+        byte[] alarmMessage = new byte[]{
+                (byte) 0x2, // TODO what is this?
+                (byte) (actionMask | alarm.getPosition()), // action mask + alarm slot
+                (byte) calendar.get(Calendar.HOUR_OF_DAY),
+                (byte) calendar.get(Calendar.MINUTE),
+                (byte) daysMask,
+        };
+
+        writeToConfiguration(builder,alarmMessage);
+
+        // TODO: react on 0x10, 0x02, 0x01 on notification (success)
+    }
+
+    protected void handleDeviceInfo(nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo.DeviceInfo info) {
+//        if (getDeviceInfo().supportsHeartrate()) {
+//            getDevice().addDeviceInfo(new GenericItem(
+//                    getContext().getString(R.string.DEVINFO_HR_VER),
+//                    info.getSoftwareRevision()));
+//        }
+
+        LOG.warn("Device info: " + info);
+        versionCmd.hwVersion = info.getHardwareRevision();
+        versionCmd.fwVersion = info.getFirmwareRevision();
+        if (versionCmd.fwVersion == null) {
+            versionCmd.fwVersion = info.getSoftwareRevision();
+        }
+        if (versionCmd.fwVersion != null && versionCmd.fwVersion.length() > 0 && versionCmd.fwVersion.charAt(0) == 'V') {
+            versionCmd.fwVersion = versionCmd.fwVersion.substring(1);
+        }
+        handleGBDeviceEvent(versionCmd);
+    }
+
+    private void handleBatteryInfo(byte[] value, int status) {
+        if (status == BluetoothGatt.GATT_SUCCESS) {
+            HuamiBatteryInfo info = new HuamiBatteryInfo(value);
+            handleGBDeviceEvent(info.toDeviceEvent());
+        }
+    }
+
+    protected HuamiSupport sendCalendarEvents(TransactionBuilder builder) {
+        if (characteristicChunked == null) { // all except Mi Band 2
+            sendCalendarEventsAsAlarms(builder);
+        } else {
+            sendCalendarEventsAsReminders(builder);
+        }
+
+        return this;
+    }
+
+    /**
+     * Fetch the events from the android device calendars and set the alarms on the miband.
+     * @param builder
+     */
+    private HuamiSupport sendCalendarEventsAsAlarms(TransactionBuilder builder) {
+        DeviceCoordinator coordinator = gbDevice.getDeviceCoordinator();
+        Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()));
+        int maxAlarms = coordinator.getAlarmSlotCount(gbDevice);
+        int availableSlots = Math.min(prefs.getInt(PREF_RESERVER_ALARMS_CALENDAR, 0), maxAlarms);
+
+        if (availableSlots <= 0) {
+            return this;
+        }
+
+        CalendarManager upcomingEvents = new CalendarManager(getContext(), getDevice().getAddress());
+        List<CalendarEvent> mEvents = upcomingEvents.getCalendarEventList();
+
+        int iteration = 0;
+
+        for (CalendarEvent mEvt : mEvents) {
+            if (mEvt.isAllDay()) {
+                continue;
+            }
+            if (iteration >= availableSlots) {
+                break;
+            }
+            int slotToUse = 2 - iteration;
+            Calendar calendar = Calendar.getInstance();
+            calendar.setTimeInMillis(mEvt.getBegin());
+            Alarm alarm = AlarmUtils.createSingleShot(slotToUse, false, true, calendar);
+            queueAlarm(alarm, builder);
+            iteration++;
+        }
+
+        return this;
+    }
+
+    private HuamiSupport sendCalendarEventsAsReminders(TransactionBuilder builder) {
+        boolean syncCalendar = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getBoolean(PREF_SYNC_CALENDAR, false);
+        if (!syncCalendar) {
+            return this;
+        }
+        final DeviceCoordinator coordinator = gbDevice.getDeviceCoordinator();
+
+        int availableSlots = GBApplication.getDevicePrefs(gbDevice).getReservedReminderCalendarSlots();
+
+        CalendarManager upcomingEvents = new CalendarManager(getContext(), getDevice().getAddress());
+        List<CalendarEvent> calendarEvents = upcomingEvents.getCalendarEventList();
+        Calendar calendar = Calendar.getInstance();
+
+        int iteration = 0;
+
+        for (CalendarEvent calendarEvent : calendarEvents) {
+            if (calendarEvent.isAllDay()) {
+                continue;
+            }
+
+            if (iteration >= availableSlots) {
+                break;
+            }
+
+            calendar.setTimeInMillis(calendarEvent.getBegin());
+            byte[] title;
+            if (calendarEvent.getTitle() != null) {
+                title = calendarEvent.getTitle().getBytes();
+            } else {
+                title = new byte[]{};
+            }
+
+            int length = 1 + 1 + 4 + 6 + 6 + 1 + title.length + 1;
+
+            ByteBuffer buf = ByteBuffer.allocate(length);
+
+            buf.order(ByteOrder.LITTLE_ENDIAN);
+            buf.put((byte) 0x0b); // always 0x0b?
+            buf.put((byte) iteration); // id
+            buf.putInt(0x08 | 0x04 | 0x01); // flags 0x01 = enable, 0x04 = end date present, 0x08 = has text
+            calendar.setTimeInMillis(calendarEvent.getBegin());
+            buf.put(BLETypeConversions.shortCalendarToRawBytes(calendar));
+            calendar.setTimeInMillis(calendarEvent.getEnd());
+            buf.put(BLETypeConversions.shortCalendarToRawBytes(calendar));
+            buf.put((byte) 0); // 0 Terminated
+            buf.put(title);
+            buf.put((byte) 0); // 0 Terminated
+            writeToChunked(builder, 2, buf.array());
+
+            iteration++;
+        }
+
+        // Continue by deleting the events
+        for(;iteration < availableSlots; iteration++){
+            int length = 1 + 1 + 4 + 6 + 6 + 1 + 0 + 1;
+            ByteBuffer buf = ByteBuffer.allocate(length);
+
+            buf.order(ByteOrder.LITTLE_ENDIAN);
+            buf.put((byte) 0x0b); // always 0x0b?
+            buf.put((byte) iteration); // id
+            buf.putInt(0x08); // flags 0x01 = enable, 0x04 = end date present, 0x08 = has text
+            buf.put(new byte[6 + 6 + 1 + 1]); // default value is 0
+            writeToChunked(builder, 2, buf.array());
+        }
+
+        return this;
+    }
+
+    @Override
+    public void onSendConfiguration(String config) {
+        TransactionBuilder builder;
+        try {
+            builder = performInitialized("Sending configuration for option: " + config);
+            switch (config) {
+                case MiBandConst.PREF_MI2_DATEFORMAT:
+                    setDateDisplay(builder);
+                    break;
+                case PREF_USER_FITNESS_GOAL_NOTIFICATION:
+                    setGoalNotification(builder);
+                    break;
+                case PREF_ACTIVATE_DISPLAY_ON_LIFT:
+                case PREF_DISPLAY_ON_LIFT_START:
+                case PREF_DISPLAY_ON_LIFT_END:
+                    setActivateDisplayOnLiftWrist(builder);
+                    break;
+                case PREF_DISPLAY_ON_LIFT_SENSITIVITY:
+                    setActivateDisplayOnLiftWristSensitivity(builder);
+                    break;
+                case PREF_DISCONNECT_NOTIFICATION:
+                case PREF_DISCONNECT_NOTIFICATION_START:
+                case PREF_DISCONNECT_NOTIFICATION_END:
+                    setDisconnectNotification(builder);
+                    break;
+                case HuamiConst.PREF_DISPLAY_ITEMS:
+                case HuamiConst.PREF_DISPLAY_ITEMS_SORTABLE:
+                    setDisplayItems(builder);
+                    break;
+                case HuamiConst.PREF_SHORTCUTS:
+                case HuamiConst.PREF_SHORTCUTS_SORTABLE:
+                    setShortcuts(builder);
+                    break;
+                case HuamiConst.PREF_WORKOUT_ACTIVITY_TYPES_SORTABLE:
+                    setWorkoutActivityTypes(builder);
+                    break;
+                case MiBandConst.PREF_MI2_ROTATE_WRIST_TO_SWITCH_INFO:
+                    setRotateWristToSwitchInfo(builder);
+                    break;
+                case ActivityUser.PREF_USER_STEPS_GOAL:
+                case ActivityUser.PREF_USER_CALORIES_BURNT:
+                case ActivityUser.PREF_USER_SLEEP_DURATION:
+                case ActivityUser.PREF_USER_GOAL_WEIGHT_KG:
+                case ActivityUser.PREF_USER_GOAL_STANDING_TIME_HOURS:
+                case ActivityUser.PREF_USER_GOAL_FAT_BURN_TIME_MINUTES:
+                    setFitnessGoal(builder);
+                    break;
+                case MiBandConst.PREF_NIGHT_MODE:
+                case MiBandConst.PREF_NIGHT_MODE_START:
+                case MiBandConst.PREF_NIGHT_MODE_END:
+                    setNightMode(builder);
+                    break;
+                case PREF_DO_NOT_DISTURB:
+                case PREF_DO_NOT_DISTURB_START:
+                case PREF_DO_NOT_DISTURB_END:
+                case PREF_DO_NOT_DISTURB_LIFT_WRIST:
+                    setDoNotDisturb(builder);
+                    break;
+                case PREF_INACTIVITY_ENABLE:
+                case PREF_INACTIVITY_THRESHOLD:
+                case PREF_INACTIVITY_START:
+                case PREF_INACTIVITY_END:
+                case PREF_INACTIVITY_DND:
+                case PREF_INACTIVITY_DND_START:
+                case PREF_INACTIVITY_DND_END:
+                    setInactivityWarnings(builder);
+                    break;
+                case PREF_HOURLY_CHIME_ENABLE:
+                case PREF_HOURLY_CHIME_START:
+                case PREF_HOURLY_CHIME_END:
+                    setHourlyChime(builder);
+                    break;
+                case SettingsActivity.PREF_MEASUREMENT_SYSTEM:
+                    setDistanceUnit(builder);
+                    break;
+                case MiBandConst.PREF_SWIPE_UNLOCK:
+                    setBandScreenUnlock(builder);
+                    break;
+                case PREF_TIMEFORMAT:
+                    setTimeFormat(builder);
+                    break;
+                case PREF_DATEFORMAT:
+                    setDateFormat(builder);
+                    break;
+                case PREF_LANGUAGE:
+                    setLanguage(builder);
+                    break;
+                case HuamiConst.PREF_EXPOSE_HR_THIRDPARTY:
+                    setExposeHRThirdParty(builder);
+                    break;
+                case PREF_BT_CONNECTED_ADVERTISEMENT:
+                    setBtConnectedAdvertising(builder);
+                    break;
+                case PREF_WEARLOCATION:
+                    setWearLocation(builder);
+                    break;
+                case PREF_SOUNDS:
+                    setBeepSounds(builder);
+                    break;
+                case PREF_USER_DATE_OF_BIRTH:
+                case PREF_USER_NAME:
+                case PREF_USER_WEIGHT_KG:
+                case PREF_USER_HEIGHT_CM:
+                case PREF_USER_GENDER:
+                    setUserInfo(builder);
+                    break;
+                case PREF_HUAMI_VIBRATION_PROFILE_APP_ALERTS:
+                case PREF_HUAMI_VIBRATION_PROFILE_INCOMING_CALL:
+                case PREF_HUAMI_VIBRATION_PROFILE_INCOMING_SMS:
+                case PREF_HUAMI_VIBRATION_PROFILE_GOAL_NOTIFICATION:
+                case PREF_HUAMI_VIBRATION_PROFILE_ALARM:
+                case PREF_HUAMI_VIBRATION_PROFILE_IDLE_ALERTS:
+                case PREF_HUAMI_VIBRATION_PROFILE_EVENT_REMINDER:
+                case PREF_HUAMI_VIBRATION_PROFILE_FIND_BAND:
+                case PREF_HUAMI_VIBRATION_PROFILE_TODO_LIST:
+                case PREF_HUAMI_VIBRATION_PROFILE_SCHEDULE:
+                case PREF_HUAMI_VIBRATION_COUNT_APP_ALERTS:
+                case PREF_HUAMI_VIBRATION_COUNT_INCOMING_CALL:
+                case PREF_HUAMI_VIBRATION_COUNT_INCOMING_SMS:
+                case PREF_HUAMI_VIBRATION_COUNT_GOAL_NOTIFICATION:
+                case PREF_HUAMI_VIBRATION_COUNT_ALARM:
+                case PREF_HUAMI_VIBRATION_COUNT_IDLE_ALERTS:
+                case PREF_HUAMI_VIBRATION_COUNT_EVENT_REMINDER:
+                case PREF_HUAMI_VIBRATION_COUNT_FIND_BAND:
+                case PREF_HUAMI_VIBRATION_COUNT_TODO_LIST:
+                case PREF_HUAMI_VIBRATION_COUNT_SCHEDULE:
+                case PREF_HUAMI_VIBRATION_TRY_APP_ALERTS:
+                case PREF_HUAMI_VIBRATION_TRY_INCOMING_CALL:
+                case PREF_HUAMI_VIBRATION_TRY_INCOMING_SMS:
+                case PREF_HUAMI_VIBRATION_TRY_GOAL_NOTIFICATION:
+                case PREF_HUAMI_VIBRATION_TRY_ALARM:
+                case PREF_HUAMI_VIBRATION_TRY_IDLE_ALERTS:
+                case PREF_HUAMI_VIBRATION_TRY_EVENT_REMINDER:
+                case PREF_HUAMI_VIBRATION_TRY_FIND_BAND:
+                case PREF_HUAMI_VIBRATION_TRY_TODO_LIST:
+                case PREF_HUAMI_VIBRATION_TRY_SCHEDULE:
+                    setVibrationPattern(builder, config);
+                    break;
+                case PREF_HEARTRATE_ACTIVITY_MONITORING:
+                    setHeartrateActivityMonitoring(builder);
+                    break;
+                case PREF_HEARTRATE_ALERT_ENABLED:
+                case PREF_HEARTRATE_ALERT_HIGH_THRESHOLD:
+                case PREF_HEARTRATE_ALERT_LOW_THRESHOLD:
+                    setHeartrateAlert(builder);
+                    break;
+                case PREF_HEARTRATE_STRESS_MONITORING:
+                    setHeartrateStressMonitoring(builder);
+                    break;
+                case PasswordCapabilityImpl.PREF_PASSWORD:
+                case PasswordCapabilityImpl.PREF_PASSWORD_ENABLED:
+                    setPassword(builder);
+                    break;
+            }
+            builder.queue(getQueue());
+        } catch (IOException e) {
+            GB.toast("Error setting configuration", Toast.LENGTH_LONG, GB.ERROR, e);
+        }
+    }
+
+    @Override
+    public void onTestNewFunction() {
+        //requestMTU(23);
+        try {
+            final TransactionBuilder builder = performInitialized("test request");
+            writeToConfiguration(builder, HuamiService.COMMAND_REQUEST_WORKOUT_ACTIVITY_TYPES);
+            builder.queue(getQueue());
+        } catch (final Exception e) {
+            LOG.error("onTestNewFunction failed", e);
+        }
+    }
+
+    protected HuamiSupport setVibrationPattern(final TransactionBuilder builder, final String preferenceKey) {
+        // The preference key has one of the 3 prefixes
+        final String notificationTypeName = preferenceKey.replace(PREF_HUAMI_VIBRATION_COUNT_PREFIX, "")
+                .replace(PREF_HUAMI_VIBRATION_PROFILE_PREFIX, "")
+                .replace(PREF_HUAMI_VIBRATION_TRY_PREFIX, "")
+                .toUpperCase(Locale.ROOT);
+        final HuamiVibrationPatternNotificationType notificationType = HuamiVibrationPatternNotificationType.valueOf(notificationTypeName);
+        final boolean isTry = preferenceKey.startsWith(PREF_HUAMI_VIBRATION_TRY_PREFIX);
+
+        final VibrationProfile vibrationProfile = HuamiCoordinator.getVibrationProfile(
+                getDevice().getAddress(),
+                notificationType,
+                supportsDeviceDefaultVibrationProfiles()
+        );
+
+        setVibrationPattern(builder, notificationType, isTry, vibrationProfile);
+
+        return this;
+    }
+
+    /**
+     * Whether the device supports built-in default vibration profiles.
+     */
+    protected boolean supportsDeviceDefaultVibrationProfiles() {
+        return false;
+    }
+
+    /**
+     * Test or set a {@link VibrationProfile}.
+     *
+     * @param builder          the {@link TransactionBuilder}
+     * @param notificationType the notification type
+     * @param test             test the pattern (only vibrate the band, do not set it)
+     * @param profile          the {@link VibrationProfile}
+     */
+    protected void setVibrationPattern(final TransactionBuilder builder,
+                                       final HuamiVibrationPatternNotificationType notificationType,
+                                       final boolean test,
+                                       final VibrationProfile profile) {
+        if (profile == null) {
+            LOG.error("Vibration profile is null for {}", notificationType);
+            return;
+        }
+
+        final int MAX_TOTAL_LENGTH_MS = 10_000; // 10 seconds, about as long as Mi Fit allows
+
+        // The on-off sequence, until the max total length is reached
+        final List<Short> onOff = truncateVibrationsOnOff(profile, MAX_TOTAL_LENGTH_MS);
+
+        final ByteBuffer buf = ByteBuffer.allocate(3 + 2 * onOff.size());
+        buf.order(ByteOrder.LITTLE_ENDIAN);
+
+        buf.put((byte) 0x20);
+        buf.put(notificationType.getCode());
+        byte flag = (byte) (onOff.size() / 2);
+        flag |= 0x40;
+        if (test) {
+            flag |= 0x80;
+        }
+        buf.put(flag);
+
+        for (Short time : onOff) {
+            buf.putShort(time);
+        }
+
+        writeToChunked(builder, 2, buf.array());
+    }
+
+    protected List<Short> truncateVibrationsOnOff(final VibrationProfile profile, final int limitMillis) {
+        if (profile == null) {
+            return Collections.emptyList();
+        }
+
+        int totalLengthMs = 0;
+
+        // The on-off sequence, until the max total length is reached
+        final List<Short> onOff = new ArrayList<>(profile.getOnOffSequence().length);
+
+        for (int c = 0; c < profile.getRepeat(); c++) {
+            for (int i = 0; i < profile.getOnOffSequence().length; i += 2) {
+                final short on = (short) profile.getOnOffSequence()[i];
+                final short off = (short) profile.getOnOffSequence()[i + 1];
+
+                if (totalLengthMs + on + off > limitMillis) {
+                    LOG.warn("VibrationProfile {} too long, truncating to {} ms", profile.getId(), limitMillis);
+                    break;
+                }
+
+                onOff.add(on);
+                onOff.add(off);
+                totalLengthMs += on + off;
+            }
+        }
+
+        return onOff;
+    }
+
+    @Override
+    public void onSendWeather(ArrayList<WeatherSpec> weatherSpecs) {
+        final DeviceCoordinator coordinator = gbDevice.getDeviceCoordinator();
+        if (!coordinator.supportsWeather()) {
+            return;
+        }
+
+        if (gbDevice.getFirmwareVersion() == null) {
+            LOG.warn("Device not initialized yet, so not sending weather info");
+            return;
+        }
+        boolean supportsConditionString = true;
+
+        Version version = new Version(gbDevice.getFirmwareVersion());
+        if (gbDevice.getType() == DeviceType.AMAZFITBIP && version.compareTo(new Version("0.0.8.74")) < 0) {
+            supportsConditionString = false;
+        }
+
+        final WeatherSpec weatherSpec = weatherSpecs.get(0);
+
+        MiBandConst.DistanceUnit unit = HuamiCoordinator.getDistanceUnit();
+        int tz_offset_hours = SimpleTimeZone.getDefault().getOffset(weatherSpec.timestamp * 1000L) / (1000 * 60 * 60);
+        try {
+            TransactionBuilder builder;
+            builder = performInitialized("Sending current temp");
+
+            byte condition = HuamiWeatherConditions.mapToAmazfitBipWeatherCode(weatherSpec.currentConditionCode);
+
+            int length = 8;
+            if (supportsConditionString) {
+                length += weatherSpec.currentCondition.getBytes().length + 1;
+            }
+            ByteBuffer buf = ByteBuffer.allocate(length);
+            buf.order(ByteOrder.LITTLE_ENDIAN);
+
+            buf.put((byte) 2);
+            buf.putInt(weatherSpec.timestamp);
+            buf.put((byte) (tz_offset_hours * 4));
+            buf.put(condition);
+
+            int currentTemp = weatherSpec.currentTemp - 273;
+            if (unit == MiBandConst.DistanceUnit.IMPERIAL) {
+                currentTemp = (int) WeatherUtils.celsiusToFahrenheit(currentTemp);
+            }
+            buf.put((byte) currentTemp);
+
+            if (supportsConditionString) {
+                buf.put(weatherSpec.currentCondition.getBytes());
+                buf.put((byte) 0);
+            }
+
+            if (characteristicChunked != null) {
+                writeToChunked(builder, 1, buf.array());
+            } else {
+                builder.write(getCharacteristic(AmazfitBipService.UUID_CHARACTERISTIC_WEATHER), buf.array());
+            }
+
+            builder.queue(getQueue());
+        } catch (Exception ex) {
+            LOG.error("Error sending current weather", ex);
+        }
+
+        try {
+            TransactionBuilder builder;
+            builder = performInitialized("Sending air quality index");
+            int length = 8;
+            String aqiString = "(n/a)";
+            if (supportsConditionString) {
+                length += aqiString.getBytes().length + 1;
+            }
+            ByteBuffer buf = ByteBuffer.allocate(length);
+            buf.order(ByteOrder.LITTLE_ENDIAN);
+            buf.put((byte) 4);
+            buf.putInt(weatherSpec.timestamp);
+            buf.put((byte) (tz_offset_hours * 4));
+            buf.putShort((short) -1);
+            if (supportsConditionString) {
+                buf.put(aqiString.getBytes());
+                buf.put((byte) 0);
+            }
+
+            if (characteristicChunked != null) {
+                writeToChunked(builder, 1, buf.array());
+            } else {
+                builder.write(getCharacteristic(AmazfitBipService.UUID_CHARACTERISTIC_WEATHER), buf.array());
+            }
+
+            builder.queue(getQueue());
+        } catch (IOException ex) {
+            LOG.error("Error sending air quality");
+        }
+
+        try {
+            TransactionBuilder builder = performInitialized("Sending weather forecast");
+            if (weatherSpec.forecasts.size() > 6) { //TDOD: find out the limits for each device
+                weatherSpec.forecasts.subList(6, weatherSpec.forecasts.size()).clear();
+            }
+            final byte NR_DAYS = (byte) (1 + weatherSpec.forecasts.size());
+            int bytesPerDay = 4;
+
+            int conditionsLength = 0;
+            if (supportsConditionString) {
+                bytesPerDay = 5;
+                conditionsLength = weatherSpec.currentCondition.getBytes().length;
+                for (WeatherSpec.Daily forecast : weatherSpec.forecasts) {
+                    conditionsLength += Weather.getConditionString(forecast.conditionCode).getBytes().length;
+                }
+            }
+
+            int length = 7 + bytesPerDay * NR_DAYS + conditionsLength;
+            ByteBuffer buf = ByteBuffer.allocate(length);
+
+            buf.order(ByteOrder.LITTLE_ENDIAN);
+            buf.put((byte) 1);
+            buf.putInt(weatherSpec.timestamp);
+            buf.put((byte) (tz_offset_hours * 4));
+
+            buf.put(NR_DAYS);
+
+            byte condition = HuamiWeatherConditions.mapToAmazfitBipWeatherCode(weatherSpec.currentConditionCode);
+            buf.put(condition);
+            buf.put(condition);
+
+            int todayMaxTemp = weatherSpec.todayMaxTemp - 273;
+            int todayMinTemp = weatherSpec.todayMinTemp - 273;
+            if (unit == MiBandConst.DistanceUnit.IMPERIAL) {
+                todayMaxTemp = (int) WeatherUtils.celsiusToFahrenheit(todayMaxTemp);
+                todayMinTemp = (int) WeatherUtils.celsiusToFahrenheit(todayMinTemp);
+            }
+            buf.put((byte) todayMaxTemp);
+            buf.put((byte) todayMinTemp);
+
+            if (supportsConditionString) {
+                buf.put(weatherSpec.currentCondition.getBytes());
+                buf.put((byte) 0);
+            }
+
+            for (WeatherSpec.Daily forecast : weatherSpec.forecasts) {
+                condition = HuamiWeatherConditions.mapToAmazfitBipWeatherCode(forecast.conditionCode);
+                buf.put(condition);
+                buf.put(condition);
+
+                int forecastMaxTemp = forecast.maxTemp - 273;
+                int forecastMinTemp = forecast.minTemp - 273;
+                if (unit == MiBandConst.DistanceUnit.IMPERIAL) {
+                    forecastMaxTemp = (int) WeatherUtils.celsiusToFahrenheit(forecastMaxTemp);
+                    forecastMinTemp = (int) WeatherUtils.celsiusToFahrenheit(forecastMinTemp);
+                }
+                buf.put((byte) forecastMaxTemp);
+                buf.put((byte) forecastMinTemp);
+
+                if (supportsConditionString) {
+                    buf.put(Weather.getConditionString(forecast.conditionCode).getBytes());
+                    buf.put((byte) 0);
+                }
+            }
+
+            if (characteristicChunked != null) {
+                writeToChunked(builder, 1, buf.array());
+            } else {
+                builder.write(getCharacteristic(AmazfitBipService.UUID_CHARACTERISTIC_WEATHER), buf.array());
+            }
+
+            builder.queue(getQueue());
+        } catch (Exception ex) {
+            LOG.error("Error sending weather forecast", ex);
+        }
+
+        try {
+            TransactionBuilder builder;
+            builder = performInitialized("Sending forecast location");
+
+            int length = 2 + weatherSpec.location.getBytes().length;
+            ByteBuffer buf = ByteBuffer.allocate(length);
+            buf.order(ByteOrder.LITTLE_ENDIAN);
+            buf.put((byte) 8);
+            buf.put(weatherSpec.location.getBytes());
+            buf.put((byte) 0);
+
+
+            if (characteristicChunked != null) {
+                writeToChunked(builder, 1, buf.array());
+            } else {
+                builder.write(getCharacteristic(AmazfitBipService.UUID_CHARACTERISTIC_WEATHER), buf.array());
+            }
+
+            builder.queue(getQueue());
+        } catch (Exception ex) {
+            LOG.error("Error sending current forecast location", ex);
+        }
+
+        if (supportsSunriseSunsetWindHumidity()) {
+            try {
+                TransactionBuilder builder;
+                builder = performInitialized("Sending wind/humidity");
+
+                String windString = this.windSpeedString(weatherSpec);
+                String humidityString = weatherSpec.currentHumidity + "%";
+
+                int length = 8 + windString.getBytes().length + humidityString.getBytes().length;
+
+                ByteBuffer buf = ByteBuffer.allocate(length);
+                buf.order(ByteOrder.LITTLE_ENDIAN);
+                buf.put((byte) 64);
+                buf.putInt(weatherSpec.timestamp);
+                buf.put((byte) (tz_offset_hours * 4));
+                buf.put(windString.getBytes());
+                buf.put((byte) 0);
+                buf.put(humidityString.getBytes());
+                buf.put((byte) 0);
+                writeToChunked(builder, 1, buf.array());
+                builder.queue(getQueue());
+            } catch (Exception ex) {
+                LOG.error("Error sending wind/humidity", ex);
+            }
+
+            float[] longlat = GBApplication.getPrefs().getLongLat(getContext());
+            float longitude = longlat[0];
+            float latitude = longlat[1];
+            if (longitude != 0 && latitude != 0) {
+                final GregorianCalendar dateTimeToday = new GregorianCalendar();
+
+                final SunriseTransitSet sunriseTransitSet = SPA.calculateSunriseTransitSet(
+                        dateTimeToday.toZonedDateTime(),
+                        latitude,
+                        longitude,
+                        DeltaT.estimate(dateTimeToday.toZonedDateTime().toLocalDate())
+                );
+
+                if (sunriseTransitSet.getSunrise() != null && sunriseTransitSet.getSunset() != null) {
+                    try {
+                        TransactionBuilder builder;
+                        builder = performInitialized("Sending sunrise/sunset");
+
+                        ByteBuffer buf = ByteBuffer.allocate(10);
+                        buf.order(ByteOrder.LITTLE_ENDIAN);
+                        buf.put((byte) 16);
+                        buf.putInt(weatherSpec.timestamp);
+                        buf.put((byte) (tz_offset_hours * 4));
+                        buf.put((byte) sunriseTransitSet.getSunrise().getHour());
+                        buf.put((byte) sunriseTransitSet.getSunrise().getMinute());
+                        buf.put((byte) sunriseTransitSet.getSunset().getHour());
+                        buf.put((byte) sunriseTransitSet.getSunset().getMinute());
+
+                        writeToChunked(builder, 1, buf.array());
+                        builder.queue(getQueue());
+                    } catch (Exception ex) {
+                        LOG.error("Error sending sunset/sunrise", ex);
+                    }
+                }
+            }
+        }
+    }
+
+    protected HuamiSupport setDateDisplay(TransactionBuilder builder) {
+        DateTimeDisplay dateTimeDisplay = HuamiCoordinator.getDateDisplay(getContext(), gbDevice.getAddress());
+        LOG.info("Setting date display to " + dateTimeDisplay);
+        switch (dateTimeDisplay) {
+            case TIME:
+                writeToConfiguration(builder,HuamiService.DATEFORMAT_TIME);
+                break;
+            case DATE_TIME:
+                writeToConfiguration(builder,HuamiService.DATEFORMAT_DATE_TIME);
+                break;
+        }
+        return this;
+    }
+
+    protected HuamiSupport setDateFormat(TransactionBuilder builder) {
+        String dateFormat = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getString("dateformat", "MM/dd/yyyy");
+        if (dateFormat == null) {
+            return this;
+        }
+        switch (dateFormat) {
+            case "MM/dd/yyyy":
+            case "dd.MM.yyyy":
+            case "dd/MM/yyyy":
+                byte[] command = HuamiService.DATEFORMAT_DATE_MM_DD_YYYY;
+                System.arraycopy(dateFormat.getBytes(), 0, command, 3, 10);
+                writeToConfiguration(builder,command);
+                break;
+            default:
+                LOG.warn("unsupported date format " + dateFormat);
+        }
+
+        return this;
+    }
+
+    protected HuamiSupport setTimeFormat(TransactionBuilder builder) {
+        String timeFormat = GBApplication.getDevicePrefs(gbDevice).getTimeFormat();
+
+        LOG.info("Setting time format to " + timeFormat);
+        if (timeFormat.equals(DeviceSettingsPreferenceConst.PREF_TIMEFORMAT_24H)) {
+            writeToConfiguration(builder,HuamiService.DATEFORMAT_TIME_24_HOURS);
+        } else {
+            writeToConfiguration(builder,HuamiService.DATEFORMAT_TIME_12_HOURS);
+        }
+        return this;
+    }
+
+    protected HuamiSupport setGoalNotification(TransactionBuilder builder) {
+        boolean enable = HuamiCoordinator.getGoalNotification(gbDevice.getAddress());
+        LOG.info("Setting goal notification to " + enable);
+        if (enable) {
+            writeToConfiguration(builder,HuamiService.COMMAND_ENABLE_GOAL_NOTIFICATION);
+        } else {
+            writeToConfiguration(builder,HuamiService.COMMAND_DISABLE_GOAL_NOTIFICATION);
+        }
+        return this;
+    }
+
+    protected HuamiSupport setActivateDisplayOnLiftWrist(TransactionBuilder builder) {
+        ActivateDisplayOnLift displayOnLift = HuamiCoordinator.getActivateDisplayOnLiftWrist(getContext(), gbDevice.getAddress());
+        LOG.info("Setting activate display on lift wrist to " + displayOnLift);
+
+        switch (displayOnLift) {
+            case ON:
+                writeToConfiguration(builder, HuamiService.COMMAND_ENABLE_DISPLAY_ON_LIFT_WRIST);
+                break;
+            case OFF:
+                writeToConfiguration(builder, HuamiService.COMMAND_DISABLE_DISPLAY_ON_LIFT_WRIST);
+                break;
+            case SCHEDULED:
+                byte[] cmd = HuamiService.COMMAND_SCHEDULE_DISPLAY_ON_LIFT_WRIST.clone();
+
+                Calendar calendar = GregorianCalendar.getInstance();
+
+                Date start = HuamiCoordinator.getDisplayOnLiftStart(gbDevice.getAddress());
+                calendar.setTime(start);
+                cmd[4] = (byte) calendar.get(Calendar.HOUR_OF_DAY);
+                cmd[5] = (byte) calendar.get(Calendar.MINUTE);
+
+                Date end = HuamiCoordinator.getDisplayOnLiftEnd(gbDevice.getAddress());
+                calendar.setTime(end);
+                cmd[6] = (byte) calendar.get(Calendar.HOUR_OF_DAY);
+                cmd[7] = (byte) calendar.get(Calendar.MINUTE);
+
+                writeToConfiguration(builder, cmd);
+                break;
+            default:
+                LOG.warn("Unknown display on lift mode {}", displayOnLift);
+        }
+        return this;
+    }
+
+    protected HuamiSupport setActivateDisplayOnLiftWristSensitivity(TransactionBuilder builder) {
+        final ActivateDisplayOnLiftSensitivity sensitivity = HuamiCoordinator.getDisplayOnLiftSensitivity(gbDevice.getAddress());
+        LOG.info("Setting activate display on lift wrist sensitivity to " + sensitivity);
+
+        switch (sensitivity) {
+            case SENSITIVE:
+                writeToConfiguration(builder, HuamiService.COMMAND_DISPLAY_ON_LIFT_WRIST_SPEED_SENSITIVE);
+                break;
+            case NORMAL:
+            default:
+                writeToConfiguration(builder, HuamiService.COMMAND_DISPLAY_ON_LIFT_WRIST_SPEED_NORMAL);
+                break;
+        }
+
+        return this;
+    }
+
+    protected HuamiSupport setDisplayItems(TransactionBuilder builder) {
+        SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress());
+        Set<String> pages = prefs.getStringSet(HuamiConst.PREF_DISPLAY_ITEMS, new HashSet<>(Arrays.asList(getContext().getResources().getStringArray(R.array.pref_mi2_display_items_default))));
+
+        LOG.info("Setting display items to " + (pages == null ? "none" : pages));
+
+        byte[] data = HuamiService.COMMAND_CHANGE_SCREENS.clone();
+
+        if (pages != null) {
+            if (pages.contains(MiBandConst.PREF_MI2_DISPLAY_ITEM_STEPS)) {
+                data[HuamiService.SCREEN_CHANGE_BYTE] |= HuamiService.DISPLAY_ITEM_BIT_STEPS;
+            }
+            if (pages.contains(MiBandConst.PREF_MI2_DISPLAY_ITEM_DISTANCE)) {
+                data[HuamiService.SCREEN_CHANGE_BYTE] |= HuamiService.DISPLAY_ITEM_BIT_DISTANCE;
+            }
+            if (pages.contains(MiBandConst.PREF_MI2_DISPLAY_ITEM_CALORIES)) {
+                data[HuamiService.SCREEN_CHANGE_BYTE] |= HuamiService.DISPLAY_ITEM_BIT_CALORIES;
+            }
+            if (pages.contains(MiBandConst.PREF_MI2_DISPLAY_ITEM_HEART_RATE)) {
+                data[HuamiService.SCREEN_CHANGE_BYTE] |= HuamiService.DISPLAY_ITEM_BIT_HEART_RATE;
+            }
+            if (pages.contains(MiBandConst.PREF_MI2_DISPLAY_ITEM_BATTERY)) {
+                data[HuamiService.SCREEN_CHANGE_BYTE] |= HuamiService.DISPLAY_ITEM_BIT_BATTERY;
+            }
+            writeToConfiguration(builder,data);
+        }
+
+        return this;
+    }
+
+    protected HuamiSupport setDisplayItemsOld(TransactionBuilder builder, boolean isShortcuts, int defaultSettings, Map<String, Integer> keyPosMap) {
+        SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress());
+        String pages;
+        List<String> enabledList;
+        if (isShortcuts) {
+            pages = prefs.getString(HuamiConst.PREF_SHORTCUTS_SORTABLE, null);
+            LOG.info("Setting shortcuts");
+        } else {
+            pages = prefs.getString(HuamiConst.PREF_DISPLAY_ITEMS_SORTABLE, null);
+            LOG.info("Setting menu items");
+        }
+        if (pages == null) {
+            enabledList = Arrays.asList(getContext().getResources().getStringArray(defaultSettings));
+        } else {
+            enabledList = Arrays.asList(pages.split(","));
+        }
+        LOG.info("enabled items" + enabledList);
+        byte[] command;
+
+        if (isShortcuts) {
+            command = new byte[keyPosMap.size() * 2 + 1];
+            command[0] = 0x10;
+            int pos = 1;
+            int index = 0;
+            for (String key : enabledList) {
+                Integer id = keyPosMap.get(key);
+                if (id != null) {
+                    command[pos++] = (byte) (0x80 | index++);
+                    command[pos++] = id.byteValue();
+                }
+            }
+            for (Map.Entry<String, Integer> entry : keyPosMap.entrySet()) {
+                String key = entry.getKey();
+                int id = entry.getValue();
+
+                if (!enabledList.contains(key)) {
+                    command[pos++] = (byte) index++;
+                    command[pos++] = (byte) id;
+                }
+            }
+        } else {
+            command = new byte[keyPosMap.size() + 4];
+            command[0] = ENDPOINT_DISPLAY_ITEMS;
+            byte index = 1;
+            int enabled_mask = DISPLAY_ITEM_BIT_CLOCK;
+            // it seem that we first have to put all ENABLED items into the array, oder does matter
+            for (String key : enabledList) {
+                Integer id = keyPosMap.get(key);
+                if (id != null) {
+                    enabled_mask |= (1 << id.byteValue());
+                    command[3 + id] = index++;
+                }
+            }
+            // And then all DISABLED ones, order does not matter
+            for (Map.Entry<String, Integer> entry : keyPosMap.entrySet()) {
+                String key = entry.getKey();
+                int id = entry.getValue();
+
+                if (!enabledList.contains(key)) {
+                    command[3 + id] = index++;
+                }
+            }
+
+            command[1] = (byte) (enabled_mask & 0xff);
+            command[2] = (byte) ((enabled_mask >> 8 & 0xff));
+        }
+
+        writeToConfiguration(builder,  command);
+        return this;
+    }
+
+    protected HuamiSupport setDisplayItemsNew(TransactionBuilder builder, boolean isShortcuts, boolean forceWatchface, int defaultSettings) {
+        SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress());
+        String pages;
+        ArrayList<String> enabledList;
+        byte menuType;
+        if (isShortcuts) {
+            menuType = (byte) 0xfd;
+            pages = prefs.getString(HuamiConst.PREF_SHORTCUTS_SORTABLE, null);
+            LOG.info("Setting shortcuts");
+        } else {
+            menuType = (byte) 0xff;
+            pages = prefs.getString(HuamiConst.PREF_DISPLAY_ITEMS_SORTABLE, null);
+            LOG.info("Setting menu items");
+        }
+        final ArrayList<String> defaultPages = new ArrayList<>(Arrays.asList(getContext().getResources().getStringArray(defaultSettings)));
+        if (pages == null) {
+            enabledList = defaultPages;
+        } else {
+            enabledList = new ArrayList<>(Arrays.asList(pages.split(",")));
+        }
+        if (forceWatchface) {
+            enabledList.add(0, "watchface");
+        }
+
+        if (defaultPages.contains("more")) {
+            // If the watch supports a "more" section, enforce a maximum of 16 items in the main screen,
+            // otherwise some items may get cut off
+            final int morePosition = enabledList.indexOf("more");
+            if (morePosition == -1 && enabledList.size() > 16) {
+                enabledList.add(16, "more");
+            } else if (morePosition != -1 && enabledList.size() > 17) {
+                enabledList.remove(morePosition);
+                enabledList.add(16, "more");
+            }
+        }
+
+        LOG.info("enabled items" + enabledList);
+        byte[] command = new byte[enabledList.size() * 4 + 1];
+        command[0] = 0x1e;
+
+        int pos = 1;
+        int index = 0;
+
+        for (String key : enabledList) {
+            Integer id = HuamiMenuType.idLookup.get(key);
+            if (id != null) {
+                command[pos++] = (byte) index++;
+                command[pos++] = 0x00;
+                command[pos++] = menuType;
+                command[pos++] = id.byteValue();
+            }
+        }
+
+        writeToChunked(builder, 2, command);
+
+        return this;
+    }
+
+    protected HuamiSupport setShortcuts(TransactionBuilder builder) {
+        return this;
+    }
+
+    protected HuamiSupport setWorkoutActivityTypes(final TransactionBuilder builder) {
+        final SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress());
+
+        final List<String> defaultActivityTypes = Arrays.asList(HuamiWorkoutScreenActivityType.Freestyle.name().toLowerCase(Locale.ROOT));
+        final String activityTypesPref = prefs.getString(HuamiConst.PREF_WORKOUT_ACTIVITY_TYPES_SORTABLE, null);
+
+        final List<String> enabledActivityTypes;
+        if (activityTypesPref == null || activityTypesPref.equals("") || activityTypesPref.equals("more")) {
+            enabledActivityTypes = defaultActivityTypes;
+        } else {
+            enabledActivityTypes = Arrays.asList(activityTypesPref.split(","));
+        }
+
+        LOG.info("Setting workout types to {}", enabledActivityTypes);
+
+        int workoutCount = enabledActivityTypes.size();
+        if (enabledActivityTypes.contains("more")) {
+            // we shouldn't count the more item when it is present, since it isn't a real
+            // workout type and isn't sent to the device
+            workoutCount--;
+        }
+        final ByteBuffer command = ByteBuffer.allocate(workoutCount * 3 + 2);
+        command.order(ByteOrder.LITTLE_ENDIAN);
+        command.putShort((short) workoutCount);
+
+        // a value of 1 puts items in the main section, a value of 0 puts them in the more section
+        // by default items are put in the main section
+        byte section = 0x01;
+
+        for (final String workoutType : enabledActivityTypes) {
+            if (workoutType.equals("more")) {
+                // all items that follow the More separator are put in the more section
+                section = 0x00;
+                continue;
+            }
+            byte code = HuamiWorkoutScreenActivityType.fromPrefValue(workoutType).getCode();
+            command.putShort(code);
+            command.put(section);
+        }
+
+        writeToChunked(builder, 9, command.array());
+
+        return this;
+    }
+
+    protected HuamiSupport setBeepSounds(TransactionBuilder builder) {
+        SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress());
+        Set<String> sounds = prefs.getStringSet(PREF_SOUNDS, new HashSet<>(Arrays.asList(getContext().getResources().getStringArray(R.array.pref_amazfitneo_sounds_default))));
+
+        LOG.info("Setting sounds to " + (sounds == null ? "none" : sounds));
+
+
+        if (sounds != null) {
+            final String[] soundOrder = new String[]{"button", "calls", "alarm", "notifications", "inactivity_warning", "sms", "email", "goal"};
+            byte[] command = new byte[]{0x3c, 0, 0, 0, 1, 0, 0, 2, 0, 0, 3, 0, 0, 4, 0, 0, 5, 0, 0, 6, 0, 0, 7, 0, 0};
+            int i = 3;
+            for (String sound : soundOrder) {
+                if (sounds.contains(sound)) {
+                    command[i] = 1;
+                }
+                i += 3;
+            }
+            writeToChunked(builder, 2, command);
+        }
+
+        return this;
+    }
+
+    protected HuamiSupport setRotateWristToSwitchInfo(TransactionBuilder builder) {
+        boolean enable = HuamiCoordinator.getRotateWristToSwitchInfo(gbDevice.getAddress());
+        LOG.info("Setting rotate wrist to cycle info to " + enable);
+        if (enable) {
+            writeToConfiguration(builder,  HuamiService.COMMAND_ENABLE_ROTATE_WRIST_TO_SWITCH_INFO);
+        } else {
+            writeToConfiguration(builder,  HuamiService.COMMAND_DISABLE_ROTATE_WRIST_TO_SWITCH_INFO);
+        }
+        return this;
+    }
+
+    protected HuamiSupport setDisplayCaller(TransactionBuilder builder) {
+        writeToConfiguration(builder,  HuamiService.COMMAND_ENABLE_DISPLAY_CALLER);
+        return this;
+    }
+
+    protected HuamiSupport setDoNotDisturb(TransactionBuilder builder) {
+        DoNotDisturb doNotDisturb = HuamiCoordinator.getDoNotDisturb(gbDevice.getAddress());
+        boolean doNotDisturbLiftWrist = HuamiCoordinator.getDoNotDisturbLiftWrist(gbDevice.getAddress());
+        LOG.info("Setting do not disturb to {}, wake on lift wrist {}", doNotDisturb, doNotDisturbLiftWrist);
+        byte[] data = null;
+
+        switch (doNotDisturb) {
+            case OFF:
+                data = HuamiService.COMMAND_DO_NOT_DISTURB_OFF.clone();
+                break;
+            case AUTOMATIC:
+                data = HuamiService.COMMAND_DO_NOT_DISTURB_AUTOMATIC.clone();
+                break;
+            case SCHEDULED:
+                data = HuamiService.COMMAND_DO_NOT_DISTURB_SCHEDULED.clone();
+
+                Calendar calendar = GregorianCalendar.getInstance();
+
+                Date start = HuamiCoordinator.getDoNotDisturbStart(gbDevice.getAddress());
+                calendar.setTime(start);
+                data[HuamiService.DND_BYTE_START_HOURS] = (byte) calendar.get(Calendar.HOUR_OF_DAY);
+                data[HuamiService.DND_BYTE_START_MINUTES] = (byte) calendar.get(Calendar.MINUTE);
+
+                Date end = HuamiCoordinator.getDoNotDisturbEnd(gbDevice.getAddress());
+                calendar.setTime(end);
+                data[HuamiService.DND_BYTE_END_HOURS] = (byte) calendar.get(Calendar.HOUR_OF_DAY);
+                data[HuamiService.DND_BYTE_END_MINUTES] = (byte) calendar.get(Calendar.MINUTE);
+
+                break;
+        }
+
+        if (data != null) {
+            if (doNotDisturbLiftWrist && doNotDisturb != DoNotDisturb.OFF) {
+                data[1] &= ~0x80;
+            }
+
+            writeToConfiguration(builder,  data);
+        }
+
+        return this;
+    }
+
+    protected HuamiSupport setNightMode(TransactionBuilder builder) {
+        String nightMode = MiBand3Coordinator.getNightMode(gbDevice.getAddress());
+        LOG.info("Setting night mode to " + nightMode);
+
+        switch (nightMode) {
+            case MiBandConst.PREF_NIGHT_MODE_SUNSET:
+                writeToConfiguration(builder, MiBand3Service.COMMAND_NIGHT_MODE_SUNSET);
+                break;
+            case MiBandConst.PREF_NIGHT_MODE_OFF:
+                writeToConfiguration(builder, MiBand3Service.COMMAND_NIGHT_MODE_OFF);
+                break;
+            case MiBandConst.PREF_NIGHT_MODE_SCHEDULED:
+                byte[] cmd = MiBand3Service.COMMAND_NIGHT_MODE_SCHEDULED.clone();
+
+                Calendar calendar = GregorianCalendar.getInstance();
+
+                Date start = MiBand3Coordinator.getNightModeStart(gbDevice.getAddress());
+                calendar.setTime(start);
+                cmd[2] = (byte) calendar.get(Calendar.HOUR_OF_DAY);
+                cmd[3] = (byte) calendar.get(Calendar.MINUTE);
+
+                Date end = MiBand3Coordinator.getNightModeEnd(gbDevice.getAddress());
+                calendar.setTime(end);
+                cmd[4] = (byte) calendar.get(Calendar.HOUR_OF_DAY);
+                cmd[5] = (byte) calendar.get(Calendar.MINUTE);
+
+                writeToConfiguration(builder, cmd);
+                break;
+            default:
+                LOG.error("Invalid night mode: " + nightMode);
+                break;
+        }
+
+        return this;
+    }
+
+    protected HuamiSupport setInactivityWarnings(TransactionBuilder builder) {
+        boolean enable = HuamiCoordinator.getInactivityWarnings(gbDevice.getAddress());
+        LOG.info("Setting inactivity warnings to " + enable);
+
+        if (enable) {
+            byte[] data = HuamiService.COMMAND_ENABLE_INACTIVITY_WARNINGS.clone();
+
+            int threshold = HuamiCoordinator.getInactivityWarningsThreshold(gbDevice.getAddress());
+            data[HuamiService.INACTIVITY_WARNINGS_THRESHOLD] = (byte) threshold;
+
+            Calendar calendar = GregorianCalendar.getInstance();
+
+            boolean enableDnd = HuamiCoordinator.getInactivityWarningsDnd(gbDevice.getAddress());
+
+            Date intervalStart = HuamiCoordinator.getInactivityWarningsStart(gbDevice.getAddress());
+            Date intervalEnd = HuamiCoordinator.getInactivityWarningsEnd(gbDevice.getAddress());
+            Date dndStart = HuamiCoordinator.getInactivityWarningsDndStart(gbDevice.getAddress());
+            Date dndEnd = HuamiCoordinator.getInactivityWarningsDndEnd(gbDevice.getAddress());
+
+            // The first interval always starts when the warnings interval starts
+            calendar.setTime(intervalStart);
+            data[HuamiService.INACTIVITY_WARNINGS_INTERVAL_1_START_HOURS] = (byte) calendar.get(Calendar.HOUR_OF_DAY);
+            data[HuamiService.INACTIVITY_WARNINGS_INTERVAL_1_START_MINUTES] = (byte) calendar.get(Calendar.MINUTE);
+
+            if(enableDnd) {
+                // The first interval ends when the dnd interval starts
+                calendar.setTime(dndStart);
+                data[HuamiService.INACTIVITY_WARNINGS_INTERVAL_1_END_HOURS] = (byte) calendar.get(Calendar.HOUR_OF_DAY);
+                data[HuamiService.INACTIVITY_WARNINGS_INTERVAL_1_END_MINUTES] = (byte) calendar.get(Calendar.MINUTE);
+
+                // The second interval starts when the dnd interval ends
+                calendar.setTime(dndEnd);
+                data[HuamiService.INACTIVITY_WARNINGS_INTERVAL_2_START_HOURS] = (byte) calendar.get(Calendar.HOUR_OF_DAY);
+                data[HuamiService.INACTIVITY_WARNINGS_INTERVAL_2_START_MINUTES] = (byte) calendar.get(Calendar.MINUTE);
+
+                // ... and it ends when the warnings interval ends
+                calendar.setTime(intervalEnd);
+                data[HuamiService.INACTIVITY_WARNINGS_INTERVAL_2_END_HOURS] = (byte) calendar.get(Calendar.HOUR_OF_DAY);
+                data[HuamiService.INACTIVITY_WARNINGS_INTERVAL_2_END_MINUTES] = (byte) calendar.get(Calendar.MINUTE);
+            } else {
+                // No Dnd, use the first interval
+                calendar.setTime(intervalEnd);
+                data[HuamiService.INACTIVITY_WARNINGS_INTERVAL_1_END_HOURS] = (byte) calendar.get(Calendar.HOUR_OF_DAY);
+                data[HuamiService.INACTIVITY_WARNINGS_INTERVAL_1_END_MINUTES] = (byte) calendar.get(Calendar.MINUTE);
+            }
+
+            writeToConfiguration(builder,  data);
+        } else {
+            writeToConfiguration(builder,  HuamiService.COMMAND_DISABLE_INACTIVITY_WARNINGS);
+        }
+
+        return this;
+    }
+
+    protected HuamiSupport setHourlyChime(TransactionBuilder builder) {
+        if (!supportsHourlyChime())
+            return this;
+
+        boolean enable = HuamiCoordinator.getHourlyChime(gbDevice.getAddress());
+        LOG.info("Setting hourly chime to " + enable);
+
+        if (enable) {
+            byte[] data = HuamiService.COMMAND_ENABLE_HOURLY_CHIME.clone();
+
+            Calendar calendar = GregorianCalendar.getInstance();
+
+            Date intervalStart = HuamiCoordinator.getHourlyChimeStart(gbDevice.getAddress());
+            Date intervalEnd = HuamiCoordinator.getHourlyChimeEnd(gbDevice.getAddress());
+
+            calendar.setTime(intervalStart);
+            data[HuamiService.INACTIVITY_WARNINGS_INTERVAL_1_START_HOURS] = (byte) calendar.get(Calendar.HOUR_OF_DAY);
+            data[HuamiService.INACTIVITY_WARNINGS_INTERVAL_1_START_MINUTES] = (byte) calendar.get(Calendar.MINUTE);
+
+            calendar.setTime(intervalEnd);
+            data[HuamiService.INACTIVITY_WARNINGS_INTERVAL_1_END_HOURS] = (byte) calendar.get(Calendar.HOUR_OF_DAY);
+            data[HuamiService.INACTIVITY_WARNINGS_INTERVAL_1_END_MINUTES] = (byte) calendar.get(Calendar.MINUTE);
+
+            writeToConfiguration(builder,  data);
+        } else {
+            writeToConfiguration(builder,  HuamiService.COMMAND_DISABLE_HOURLY_CHIME);
+        }
+
+        return this;
+    }
+
+    public boolean supportsHourlyChime() {
+        return false;
+    }
+
+    protected HuamiSupport setDisconnectNotification(TransactionBuilder builder) {
+        DisconnectNotificationSetting disconnectNotificationSetting = HuamiCoordinator.getDisconnectNotificationSetting(getContext(), gbDevice.getAddress());
+        LOG.info("Setting disconnect notification to " + disconnectNotificationSetting);
+
+        switch (disconnectNotificationSetting) {
+            case ON:
+                writeToConfiguration(builder,  HuamiService.COMMAND_ENABLE_DISCONNECT_NOTIFCATION);
+                break;
+            case OFF:
+                writeToConfiguration(builder,  HuamiService.COMMAND_DISABLE_DISCONNECT_NOTIFCATION);
+                break;
+            case SCHEDULED:
+                byte[] cmd = HuamiService.COMMAND_ENABLE_DISCONNECT_NOTIFCATION.clone();
+
+                Calendar calendar = GregorianCalendar.getInstance();
+
+                Date start = HuamiCoordinator.getDisconnectNotificationStart(gbDevice.getAddress());
+                calendar.setTime(start);
+                cmd[4] = (byte) calendar.get(Calendar.HOUR_OF_DAY);
+                cmd[5] = (byte) calendar.get(Calendar.MINUTE);
+
+                Date end = HuamiCoordinator.getDisconnectNotificationEnd(gbDevice.getAddress());
+                calendar.setTime(end);
+                cmd[6] = (byte) calendar.get(Calendar.HOUR_OF_DAY);
+                cmd[7] = (byte) calendar.get(Calendar.MINUTE);
+
+                writeToConfiguration(builder,  cmd);
+        }
+        return this;
+    }
+
+    protected HuamiSupport setDistanceUnit(TransactionBuilder builder) {
+        MiBandConst.DistanceUnit unit = HuamiCoordinator.getDistanceUnit();
+        LOG.info("Setting distance unit to " + unit);
+        if (unit == MiBandConst.DistanceUnit.METRIC) {
+            writeToConfiguration(builder,  HuamiService.COMMAND_DISTANCE_UNIT_METRIC);
+        } else {
+            writeToConfiguration(builder,  HuamiService.COMMAND_DISTANCE_UNIT_IMPERIAL);
+        }
+        return this;
+    }
+
+    protected HuamiSupport setBandScreenUnlock(TransactionBuilder builder) {
+        boolean enable = MiBand3Coordinator.getBandScreenUnlock(gbDevice.getAddress());
+        LOG.info("Setting band screen unlock to " + enable);
+
+        if (enable) {
+            writeToConfiguration(builder,  MiBand3Service.COMMAND_ENABLE_BAND_SCREEN_UNLOCK);
+        } else {
+            writeToConfiguration(builder,  MiBand3Service.COMMAND_DISABLE_BAND_SCREEN_UNLOCK);
+        }
+
+        return this;
+    }
+
+    protected HuamiSupport setLanguage(TransactionBuilder builder) {
+        String localeString = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getString("language", "auto");
+        if (localeString == null || localeString.equals("auto")) {
+            String language = Locale.getDefault().getLanguage();
+            String country = Locale.getDefault().getCountry();
+
+            if (country == null) {
+                // sometimes country is null, no idea why, guess it.
+                country = language;
+            }
+            localeString = language + "_" + country.toUpperCase();
+        }
+        LOG.info("Setting device to locale: " + localeString);
+        final byte[] command_new = HuamiService.COMMAND_SET_LANGUAGE_NEW_TEMPLATE.clone();
+        System.arraycopy(localeString.getBytes(), 0, command_new, 3, localeString.getBytes().length);
+
+        byte[] command_old;
+        switch (localeString.substring(0, 2)) {
+            case "es":
+                command_old = AmazfitBipService.COMMAND_SET_LANGUAGE_SPANISH;
+                break;
+            case "zh":
+                if (localeString.equals("zh_CN")) {
+                    command_old = AmazfitBipService.COMMAND_SET_LANGUAGE_SIMPLIFIED_CHINESE;
+                } else {
+                    command_old = AmazfitBipService.COMMAND_SET_LANGUAGE_TRADITIONAL_CHINESE;
+
+                }
+                break;
+            default:
+                command_old = AmazfitBipService.COMMAND_SET_LANGUAGE_ENGLISH;
+        }
+        if (force2021Protocol()) {
+            writeToConfiguration(builder,command_new);
+        } else {
+            final byte[] finalCommand_old = command_old;
+            builder.add(new ConditionalWriteAction(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_3_CONFIGURATION)) {
+                @Override
+                protected byte[] checkCondition() {
+                    if ((gbDevice.getType() == DeviceType.AMAZFITBIP && new Version(gbDevice.getFirmwareVersion()).compareTo(new Version("0.1.0.77")) < 0) ||
+                            (gbDevice.getType() == DeviceType.AMAZFITCOR && new Version(gbDevice.getFirmwareVersion()).compareTo(new Version("1.0.7.23")) < 0)) {
+                        return finalCommand_old;
+                    } else {
+                        return command_new;
+                    }
+                }
+            });
+        }
+        return this;
+    }
+
+    /*
+        Some newer devices seem to support setting the language by id again instead of a locale string
+        Amazfit Bip U and GTS 2 mini tested so far
+     */
+    protected HuamiSupport setLanguageByIdNew(TransactionBuilder builder) {
+        final byte[] command = new byte[]{0x06, 0x3b, 0x00, getLanguageId(), 0x03};
+        writeToConfiguration(builder, command);
+        return this;
+    }
+
+    protected byte getLanguageId() {
+        byte language_code = 0x02; // english default
+
+        String localeString = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getString("language", "auto");
+        if (localeString == null || localeString.equals("auto")) {
+            String language = Locale.getDefault().getLanguage();
+            String country = Locale.getDefault().getCountry();
+
+            localeString = language + "_" + country.toUpperCase();
+        }
+
+        Integer id = HuamiLanguageType.idLookup.get(localeString);
+        if (id != null) {
+            language_code = id.byteValue();
+        }
+
+        return language_code;
+    }
+
+    protected HuamiSupport setExposeHRThirdParty(TransactionBuilder builder) {
+        boolean enable = HuamiCoordinator.getExposeHRThirdParty(gbDevice.getAddress());
+        LOG.info("Setting exposure of HR to third party apps to: " + enable);
+
+        if (enable) {
+            writeToConfiguration(builder,  HuamiService.COMMAND_ENBALE_HR_CONNECTION);
+        } else {
+            writeToConfiguration(builder,  HuamiService.COMMAND_DISABLE_HR_CONNECTION);
+        }
+
+        return this;
+    }
+
+    protected HuamiSupport setBtConnectedAdvertising(TransactionBuilder builder) {
+        boolean enable = HuamiCoordinator.getBtConnectedAdvertising(gbDevice.getAddress());
+        LOG.info("Setting connected advertisement to: " + enable);
+
+        if (enable) {
+            writeToConfiguration(builder,  HuamiService.COMMAND_ENABLE_BT_CONNECTED_ADVERTISEMENT);
+        } else {
+            writeToConfiguration(builder,  HuamiService.COMMAND_DISABLE_BT_CONNECTED_ADVERTISEMENT);
+        }
+
+        return this;
+    }
+
+    protected void writeToChunked(TransactionBuilder builder, int type, byte[] data) {
+        if (force2021Protocol() && type > 0) {
+            boolean encrypt = true;
+            if (type == 1 && (data[1] == 2)) { // don't encypt current weather
+                encrypt = false;
+            }
+
+            byte[] command = ArrayUtils.addAll(new byte[]{0x00, 0x00, (byte) (0xc0 | type), 0x00}, data);
+            writeToChunked2021(builder, Huami2021Service.CHUNKED2021_ENDPOINT_COMPAT, command, encrypt);
+        } else {
+            writeToChunkedOld(builder, type, data);
+        }
+    }
+
+    protected void writeToChunkedOld(TransactionBuilder builder, int type, byte[] data) {
+        final int MAX_CHUNKLENGTH = mMTU - 6;
+        int remaining = data.length;
+        byte count = 0;
+        while (remaining > 0) {
+            int copybytes = Math.min(remaining, MAX_CHUNKLENGTH);
+            byte[] chunk = new byte[copybytes + 3];
+
+            byte flags = 0;
+            if (remaining <= MAX_CHUNKLENGTH) {
+                flags |= 0x80; // last chunk
+                if (count == 0) {
+                    flags |= 0x40; // weird but true
+                }
+            } else if (count > 0) {
+                flags |= 0x40; // consecutive chunk
+            }
+
+            chunk[0] = 0;
+            chunk[1] = (byte) (flags | type);
+            chunk[2] = (byte) (count & 0xff);
+
+            System.arraycopy(data, count++ * MAX_CHUNKLENGTH, chunk, 3, copybytes);
+            builder.write(characteristicChunked, chunk);
+            remaining -= copybytes;
+        }
+    }
+
+    public void writeToChunked2021(TransactionBuilder builder, short type, byte data, boolean encrypt) {
+        writeToChunked2021(builder, type, new byte[]{data}, encrypt);
+    }
+
+    public void writeToChunked2021(TransactionBuilder builder, short type, byte[] data, boolean encrypt) {
+        huami2021ChunkedEncoder.write(builder, type, data, force2021Protocol(), encrypt);
+    }
+
+    public void writeToChunked2021(final String taskName, short type, byte data, boolean encrypt) {
+        writeToChunked2021(taskName, type, new byte[]{data}, encrypt);
+    }
+
+    public void writeToChunked2021(final String taskName, short type, byte[] data, boolean encrypt) {
+        try {
+            final TransactionBuilder builder = performInitialized(taskName);
+            writeToChunked2021(builder, type, data, encrypt);
+            builder.queue(getQueue());
+        } catch (final Exception e) {
+            LOG.error("Failed to " + taskName, e);
+        }
+    }
+
+    public void writeToConfiguration(TransactionBuilder builder, byte[] data) {
+        if (force2021Protocol()) {
+            data = ArrayUtils.insert(0, data, (byte) 1);
+            writeToChunked2021(builder, Huami2021Service.CHUNKED2021_ENDPOINT_COMPAT, data, true);
+        } else {
+            builder.write(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_3_CONFIGURATION), data);
+        }
+    }
+
+    protected HuamiSupport requestGPSVersion(TransactionBuilder builder) {
+        LOG.info("Requesting GPS version");
+        writeToConfiguration(builder,  HuamiService.COMMAND_REQUEST_GPS_VERSION);
+        return this;
+    }
+
+    protected HuamiSupport requestAlarms(TransactionBuilder builder) {
+        LOG.info("Requesting alarms");
+        //FIXME: on older devices only the first one works, and on newer only the last is sufficient
+        writeToConfiguration(builder, HuamiService.COMMAND_REQUEST_ALARMS);
+        writeToConfiguration(builder, HuamiService.COMMAND_REQUEST_ALARMS_WITH_TIMES);
+        return this;
+    }
+
+    @Override
+    public String customStringFilter(String inputString) {
+        if (HuamiCoordinator.getUseCustomFont(gbDevice.getAddress())) {
+            return convertEmojiToCustomFont(inputString);
+        }
+        return inputString;
+    }
+
+    private String convertEmojiToCustomFont(String str) {
+        StringBuilder sb = new StringBuilder();
+        int i = 0;
+        while (i < str.length()) {
+            char charAt = str.charAt(i);
+            if (Character.isHighSurrogate(charAt)) {
+                int i2 = i + 1;
+                try {
+                    int codePoint = Character.toCodePoint(charAt, str.charAt(i2));
+                    if (codePoint < 127744 || codePoint > 129510) {
+                        sb.append(charAt);
+                    } else {
+                        sb.append((char) (codePoint - 83712));
+                        i = i2;
+                    }
+                } catch (StringIndexOutOfBoundsException e) {
+                    LOG.warn("error while converting emoji to custom font", e);
+                    sb.append(charAt);
+                }
+            } else {
+                sb.append(charAt);
+            }
+            i++;
+        }
+        return sb.toString();
+    }
+
+    public void phase2Initialize(TransactionBuilder builder) {
+        LOG.info("phase2Initialize...");
+
+        if (previousMtu > MIN_MTU) {
+            // We're reconnecting - request the previously set MTU
+            builder.requestMtu(previousMtu);
+            previousMtu = -1;
+        }
+
+        requestBatteryInfo(builder);
+    }
+
+    public void phase3Initialize(TransactionBuilder builder) {
+        final HuamiCoordinator coordinator = getCoordinator();
+
+        LOG.info("phase3Initialize...");
+
+        if (HuamiCoordinator.getOverwriteSettingsOnConnection(getDevice().getAddress())) {
+            setDateDisplay(builder);
+            setTimeFormat(builder);
+            setUserInfo(builder);
+            setDistanceUnit(builder);
+            setWearLocation(builder);
+            setFitnessGoal(builder);
+            setDisplayItems(builder);
+            setDoNotDisturb(builder);
+            setRotateWristToSwitchInfo(builder);
+            setActivateDisplayOnLiftWrist(builder);
+            setDisplayCaller(builder);
+            setGoalNotification(builder);
+            setInactivityWarnings(builder);
+            setHourlyChime(builder);
+            setHeartrateSleepSupport(builder);
+            setHeartrateActivityMonitoring(builder);
+            setHeartrateAlert(builder);
+            setHeartrateStressMonitoring(builder);
+            setDisconnectNotification(builder);
+            setExposeHRThirdParty(builder);
+            setHeartrateMeasurementInterval(builder, HuamiCoordinator.getHeartRateMeasurementInterval(getDevice().getAddress()));
+            sendReminders(builder);
+            setWorldClocks(builder);
+            for (final HuamiVibrationPatternNotificationType type : coordinator.getVibrationPatternNotificationTypes(getDevice())) {
+                final String typeKey = type.name().toLowerCase(Locale.ROOT);
+                setVibrationPattern(builder, HuamiConst.PREF_HUAMI_VIBRATION_PROFILE_PREFIX + typeKey);
+            }
+            if (!PasswordCapabilityImpl.Mode.NONE.equals(coordinator.getPasswordCapability())) {
+                setPassword(builder);
+            }
+        }
+
+        requestAlarms(builder);
+        sendPhoneSilentMode(builder);
+    }
+
+    public abstract HuamiFWHelper createFWHelper(Uri uri, Context context) throws IOException;
+
+    public UpdateFirmwareOperation createUpdateFirmwareOperation(Uri uri) {
+        return new UpdateFirmwareOperation(uri, this);
+    }
+
+    public int getMTU() {
+        return mMTU;
+    }
+
+    protected void setMtu(final int mtu) {
+        if (mtu > MIN_MTU && !allowHighMtu()) {
+            LOG.warn("High MTU is not allowed, ignoring");
+            return;
+        }
+
+        if (mtu < MIN_MTU) {
+            LOG.error("Device announced unreasonable low MTU of {}, ignoring", mtu);
+            return;
+        }
+
+        this.mMTU = mtu;
+        if (huami2021ChunkedEncoder != null) {
+            huami2021ChunkedEncoder.setMTU(mtu);
+        }
+    }
+
+    protected boolean allowHighMtu() {
+        return getDevicePrefs().getBoolean(PREF_ALLOW_HIGH_MTU, true);
+    }
+
+    public int getActivitySampleSize() {
+        return mActivitySampleSize;
+    }
+
+    public TimeUnit getFetchOperationsTimeUnit() {
+        // This is configurable because using seconds was causing issues on Amazfit GTR 3
+        // However, using minutes can cause issues while fetching workouts shorter than 1 minute
+        final Prefs devicePrefs = getDevicePrefs();
+        final boolean truncate = devicePrefs.getBoolean("huami_truncate_fetch_operation_timestamps", true);
+        return truncate ? TimeUnit.MINUTES : TimeUnit.SECONDS;
+    }
+
+    public boolean force2021Protocol() {
+        return GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getBoolean("force_new_protocol", false);
+    }
+
+    protected HuamiCoordinator getCoordinator() {
+        return (HuamiCoordinator) gbDevice.getDeviceCoordinator();
+    }
+
+    @Override
+    public void handle2021Payload(short type, byte[] payload) {
+        if (type == Huami2021Service.CHUNKED2021_ENDPOINT_COMPAT) {
+            LOG.info("got configuration data");
+            type = 0;
+            handleConfigurationInfo(ArrayUtils.remove(payload, 0));
+            return;
+        }
+
+        if (type == ZeppOsCannedMessagesService.ENDPOINT && false) { // unsafe for now, disabled
+            LOG.debug("got command for SMS reply");
+            if (payload[0] == 0x0d) {
+                try {
+                    TransactionBuilder builder = performInitialized("allow sms reply");
+                    writeToChunked2021(builder, ZeppOsCannedMessagesService.ENDPOINT, new byte[]{(byte) ZeppOsCannedMessagesService.CMD_REPLY_SMS_ALLOW, 0x01}, false);
+                    builder.queue(getQueue());
+                } catch (IOException e) {
+                    LOG.error("Unable to allow sms reply");
+                }
+            } else if (payload[0] == ZeppOsCannedMessagesService.CMD_REPLY_SMS) {
+                String phoneNumber = null;
+                String smsReply = null;
+                for (int i = 1; i < payload.length; i++) {
+                    if (payload[i] == 0) {
+                        phoneNumber = new String(payload, 1, i - 1);
+                        // there are four unknown bytes between caller and reply
+                        smsReply = new String(payload, i + 5, payload.length - i - 6);
+                        break;
+                    }
+                }
+                if (phoneNumber != null && !phoneNumber.isEmpty()) {
+                    LOG.debug("will send message '" + smsReply + "' to number '" + phoneNumber + "'");
+                    GBDeviceEventNotificationControl devEvtNotificationControl = new GBDeviceEventNotificationControl();
+                    devEvtNotificationControl.handle = -1;
+                    devEvtNotificationControl.phoneNumber = phoneNumber;
+                    devEvtNotificationControl.reply = smsReply;
+                    devEvtNotificationControl.event = GBDeviceEventNotificationControl.Event.REPLY;
+                    evaluateGBDeviceEvent(devEvtNotificationControl);
+                    try {
+                        TransactionBuilder builder = performInitialized("ack sms reply");
+                        byte[] ackSentCommand = new byte[]{ZeppOsCannedMessagesService.CMD_REPLY_SMS_ACK, 0x01};
+                        writeToChunked2021(builder, ZeppOsCannedMessagesService.ENDPOINT, ackSentCommand, false);
+                        builder.queue(getQueue());
+                    } catch (IOException e) {
+                        LOG.error("Unable to ack sms reply");
+                    }
+                }
+            }
+        }
+    }
+
+    protected void setRawSensor(final boolean enable) {
+        LOG.info("setRawSensor not implemented for HuamiSupport");
+    }
+
+    protected void handleRawSensorData(final byte[] value) {
+        LOG.warn("handleRawSensorData not implemented for HuamiSupport");
+    }
+
+    @Override
+    public boolean getImplicitCallbackModify() {
+        return true;
+    }
+
+    @Override
+    public boolean getSendWriteRequestResponse() {
+        return false;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiVibrationPatternNotificationType.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiVibrationPatternNotificationType.java
new file mode 100644
index 0000000..631003e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiVibrationPatternNotificationType.java
@@ -0,0 +1,45 @@
+/*  Copyright (C) 2022-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami;
+
+/**
+ * The notification types for which vibration patterns are customizable. If these change, the
+ * constants in {@link nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst} need to be updated.
+ */
+public enum HuamiVibrationPatternNotificationType {
+    APP_ALERTS(0x00),
+    INCOMING_CALL(0x01),
+    INCOMING_SMS(0x02),
+    GOAL_NOTIFICATION(0x04),
+    ALARM(0x05),
+    IDLE_ALERTS(0x06),
+    EVENT_REMINDER(0x08),
+    FIND_BAND(0x09),
+    TODO_LIST(0x0a),
+    SCHEDULE(0x0c),
+    ;
+
+    private final byte code;
+
+    HuamiVibrationPatternNotificationType(final int code) {
+        this.code = (byte) code;
+    }
+
+    public byte getCode() {
+        return code;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiWorkoutScreenActivityType.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiWorkoutScreenActivityType.java
new file mode 100644
index 0000000..1ea6bba
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiWorkoutScreenActivityType.java
@@ -0,0 +1,75 @@
+/*  Copyright (C) 2022-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami;
+
+import java.util.Locale;
+
+/**
+ * The workout types, to configure the workouts screen on the band.
+ */
+public enum HuamiWorkoutScreenActivityType {
+    OutdoorRunning(0x01),
+    Walking(0x06),
+    Treadmill(0x08),
+    OutdoorCycling(0x09),
+    IndoorCycling(0x0a),
+    Elliptical(0x0c),
+    PoolSwimming(0x0e),
+    Freestyle(0x10),
+    JumpRope(0x15),
+    RowingMachine(0x17),
+    Yoga(0x3c),
+    Dance(0x4c),
+    IndoorFitness(0x18),
+    Gymnastics(0x3b),
+    HIIT(0x31),
+    CoreTraining(0x32),
+    Stretching(0x35),
+    Stepper(0x3a),
+    Pilates(0x3d),
+    Basketball(0x55),
+    Volleyball(0x58),
+    TableTennis(0x59),
+    Badminton(0x5c),
+    Cricket(0x4e),
+    Bowling(0x50),
+    Boxing(0x61),
+    // TODO: Not working Kickboxing(0x72),
+    StreetDance(0x4a),
+    Zumba(0x4d),
+    IndoorIceSkating(0x2d),
+    ;
+
+    private final byte code;
+
+    HuamiWorkoutScreenActivityType(final int code) {
+        this.code = (byte) code;
+    }
+
+    public byte getCode() {
+        return code;
+    }
+
+    public static HuamiWorkoutScreenActivityType fromPrefValue(final String prefValue) {
+        for (final HuamiWorkoutScreenActivityType type : values()) {
+            if (type.name().toLowerCase(Locale.ROOT).equals(prefValue.replace("_", "").toLowerCase(Locale.ROOT))) {
+                return type;
+            }
+        }
+        throw new RuntimeException("No matching HuamiWorkoutScreenActivityType for pref value: " + prefValue);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiWorkoutStatus.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiWorkoutStatus.java
new file mode 100644
index 0000000..44720b1
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiWorkoutStatus.java
@@ -0,0 +1,45 @@
+/*  Copyright (C) 2022-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami;
+
+public enum HuamiWorkoutStatus {
+    Start(0x02),
+    Pause(0x03),
+    Resume(0x04),
+    End(0x05),
+    ;
+
+    private final byte code;
+
+    HuamiWorkoutStatus(final int code) {
+        this.code = (byte) code;
+    }
+
+    public byte getCode() {
+        return code;
+    }
+
+    public static HuamiWorkoutStatus fromCode(final byte code) {
+        for (final HuamiWorkoutStatus type : values()) {
+            if (type.getCode() == code) {
+                return type;
+            }
+        }
+
+        return null;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiWorkoutTrackActivityType.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiWorkoutTrackActivityType.java
new file mode 100644
index 0000000..25129ff
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiWorkoutTrackActivityType.java
@@ -0,0 +1,85 @@
+/*  Copyright (C) 2022-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami;
+
+import java.util.Locale;
+
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+
+/**
+ * The workout types, used to start / when workout tracking starts on the band.
+ */
+public enum HuamiWorkoutTrackActivityType {
+    OutdoorRunning(0x01),
+    Walking(0x04),
+    Treadmill(0x02),
+    OutdoorCycling(0x03),
+    IndoorCycling(0x09),
+    Elliptical(0x06),
+    PoolSwimming(0x05),
+    Freestyle(0x0b),
+    JumpRope(0x08),
+    RowingMachine(0x07),
+    Yoga(0x0a);
+
+    private final byte code;
+
+    HuamiWorkoutTrackActivityType(final int code) {
+        this.code = (byte) code;
+    }
+
+    public byte getCode() {
+        return code;
+    }
+
+    public ActivityKind toActivityKind() {
+        switch (this) {
+            case Elliptical:
+                return ActivityKind.ELLIPTICAL_TRAINER;
+            case IndoorCycling:
+                return ActivityKind.INDOOR_CYCLING;
+            case JumpRope:
+                return ActivityKind.JUMP_ROPING;
+            case OutdoorCycling:
+                return ActivityKind.CYCLING;
+            case OutdoorRunning:
+                return ActivityKind.RUNNING;
+            case PoolSwimming:
+                return ActivityKind.SWIMMING;
+            case RowingMachine:
+                return ActivityKind.ROWING_MACHINE;
+            case Treadmill:
+                return ActivityKind.TREADMILL;
+            case Walking:
+                return ActivityKind.WALKING;
+            case Yoga:
+                return ActivityKind.YOGA;
+        }
+
+        return ActivityKind.UNKNOWN;
+    }
+
+    public static HuamiWorkoutTrackActivityType fromCode(final byte code) {
+        for (final HuamiWorkoutTrackActivityType type : values()) {
+            if (type.getCode() == code) {
+                return type;
+            }
+        }
+
+        return null;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/UIHHContainer.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/UIHHContainer.java
new file mode 100644
index 0000000..23d51d7
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/UIHHContainer.java
@@ -0,0 +1,234 @@
+/*  Copyright (C) 2022-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami;
+
+import androidx.annotation.Nullable;
+
+import org.apache.commons.lang3.ArrayUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
+import nodomain.freeyourgadget.gadgetbridge.util.CheckSums;
+
+public class UIHHContainer {
+    private static final Logger LOG = LoggerFactory.getLogger(UIHHContainer.class);
+
+    public static final byte[] UIHH_HEADER = new byte[]{
+            'U', 'I', 'H', 'H'
+    };
+
+    public List<FileEntry> files = new ArrayList<>();
+
+    public List<FileEntry> getFiles() {
+        return files;
+    }
+
+    public byte[] getFile(final FileType fileType) {
+        for (final FileEntry file : files) {
+            if (file.getType() == fileType) {
+                return file.getContent();
+            }
+        }
+
+        return null;
+    }
+
+    public void addFile(final FileType type, final byte[] bytes) {
+        files.add(new FileEntry(type, bytes));
+    }
+
+    @Nullable
+    public byte[] toRawBytes() {
+        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+
+        for (final FileEntry file : files) {
+            try {
+                baos.write(file.buildHeader());
+                baos.write(file.getContent());
+            } catch (final IOException e) {
+                LOG.error("Failed to generate UIHH bytes", e);
+                return null;
+            }
+        }
+
+        final byte[] contentBytes = baos.toByteArray();
+        final byte[] headerBytes = buildHeader(contentBytes);
+
+        return ArrayUtils.addAll(headerBytes, contentBytes);
+    }
+
+    @Nullable
+    public static UIHHContainer fromRawBytes(final byte[] bytes) {
+        if (bytes.length < 32) {
+            LOG.error("bytes array too small {}", bytes.length);
+            return null;
+        }
+
+        if (!nodomain.freeyourgadget.gadgetbridge.util.ArrayUtils.startsWith(bytes, UIHH_HEADER)) {
+            LOG.error("UIHH header not found");
+            return null;
+        }
+
+        final int crc32 = BLETypeConversions.toUint32(ArrayUtils.subarray(bytes, 12, 12 + 4));
+        final int length = BLETypeConversions.toUint32(ArrayUtils.subarray(bytes, 22, 22 + 4));
+
+        if (length + 32 != bytes.length) {
+            LOG.error("Length mismatch between header and bytes: {}/{}", length, bytes.length);
+            return null;
+        }
+
+        if (crc32 != CheckSums.getCRC32(ArrayUtils.subarray(bytes, 32, bytes.length))) {
+            LOG.error("CRC mismatch for content");
+            return null;
+        }
+
+        int i = 32;
+
+        final UIHHContainer ret = new UIHHContainer();
+
+        while (i < bytes.length) {
+            if (i + 10 >= bytes.length) {
+                LOG.error("Not enough bytes remaining");
+                return null;
+            }
+
+            if (bytes[i] != 1) {
+                LOG.error("Expected 1 at position {}", i);
+                return null;
+            }
+            i++;
+
+            final FileType type = FileType.fromValue(bytes[i]);
+            if (type == null) {
+                LOG.error("Unknown type byte {} at position {}", String.format("0x%x", bytes[i], i));
+                return null;
+            }
+            i++;
+
+            final int fileLength = BLETypeConversions.toUint32(ArrayUtils.subarray(bytes, i, i + 4));
+            i += 4;
+            final int fileCrc32 = BLETypeConversions.toUint32(ArrayUtils.subarray(bytes, i, i + 4));
+            i += 4;
+
+            if (i + fileLength > bytes.length) {
+                LOG.error("Not enough bytes remaining to read a {} byte file", fileLength);
+                return null;
+            }
+
+            final byte[] fileContent = ArrayUtils.subarray(bytes, i, i + fileLength);
+            if (fileCrc32 != CheckSums.getCRC32(fileContent)) {
+                LOG.error("CRC mismatch for {}", type);
+                return null;
+            }
+            i += fileLength;
+
+            ret.getFiles().add(new FileEntry(type, fileContent));
+        }
+
+        return ret;
+    }
+
+    private static byte[] buildHeader(final byte[] content) {
+        final ByteBuffer buf = ByteBuffer.allocate(32);
+        buf.order(ByteOrder.LITTLE_ENDIAN);
+
+        buf.put(UIHH_HEADER);
+        buf.put(new byte[]{0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01});
+        buf.putInt(CheckSums.getCRC32(content));
+        buf.put(new byte[]{0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
+        buf.putInt(content.length);
+        buf.put(new byte[]{0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
+
+        return buf.array();
+    }
+
+    public enum FileType {
+        FIRMWARE_ZIP(HuamiFirmwareType.FIRMWARE.getValue(), "firmware.zip"),
+        FIRMWARE_CHANGELOG(0x10, "changelog.txt"),
+        GPS_ALM_BIN(HuamiFirmwareType.GPS_ALMANAC.getValue(), "gps_alm.bin"),
+        GLN_ALM_BIN(0x0f, "gln_alm.bin"),
+        LLE_BDS_LLE(0x86, "lle_bds.lle"),
+        LLE_GPS_LLE(0x87, "lle_gps.lle"),
+        LLE_GLO_LLE(0x88, "lle_glo.lle"),
+        LLE_GAL_LLE(0x89, "lle_gal.lle"),
+        LLE_QZSS_LLE(0x8a, "lle_qzss.lle"),
+        AGPS_EPO_GR_3(-116, "EPO_GR_3.DAT"),
+        AGPS_EPO_GAL_7(-115, "EPO_GAL_7.DAT"),
+        AGPS_EPO_BDS_3(-114, "EPO_BDS_3.DAT"),
+        ;
+
+        private final byte value;
+        private final String name;
+
+        FileType(final int value, final String name) {
+            this.value = (byte) value;
+            this.name = name;
+        }
+
+        public byte getValue() {
+            return value;
+        }
+
+        public static FileType fromValue(final byte value) {
+            for (final FileType fileType : values()) {
+                if (fileType.getValue() == value) {
+                    return fileType;
+                }
+            }
+
+            return null;
+        }
+    }
+
+    public static class FileEntry {
+        private final FileType type;
+        private final byte[] content;
+
+        public FileEntry(final FileType type, final byte[] content) {
+            this.type = type;
+            this.content = content;
+        }
+
+        public FileType getType() {
+            return type;
+        }
+
+        public byte[] getContent() {
+            return content;
+        }
+
+        public byte[] buildHeader() {
+            final ByteBuffer buf = ByteBuffer.allocate(10);
+            buf.order(ByteOrder.LITTLE_ENDIAN);
+
+            buf.put((byte) 0x01);
+            buf.put(type.getValue());
+            buf.putInt(content.length);
+            buf.putInt(CheckSums.getCRC32(content));
+
+            return buf.array();
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/actions/StopNotificationAction.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/actions/StopNotificationAction.java
new file mode 100644
index 0000000..a3430f0
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/actions/StopNotificationAction.java
@@ -0,0 +1,44 @@
+/*  Copyright (C) 2018-2024 Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.actions;
+
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.AbortTransactionAction;
+
+public abstract class StopNotificationAction extends AbortTransactionAction {
+
+    private final BluetoothGattCharacteristic alertLevelCharacteristic;
+
+    public StopNotificationAction(BluetoothGattCharacteristic alertLevelCharacteristic) {
+        this.alertLevelCharacteristic = alertLevelCharacteristic;
+    }
+
+    @Override
+    public boolean run(BluetoothGatt gatt) {
+        if (!super.run(gatt)) {
+            // send a signal to stop the vibration
+            alertLevelCharacteristic.setValue(new byte[]{HuamiService.ALERT_LEVEL_NONE});
+            gatt.writeCharacteristic(alertLevelCharacteristic);
+            return false;
+        }
+        return true;
+    }
+};
+
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitband5/AmazfitBand5FirmwareInfo.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitband5/AmazfitBand5FirmwareInfo.java
new file mode 100644
index 0000000..d8e3228
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitband5/AmazfitBand5FirmwareInfo.java
@@ -0,0 +1,82 @@
+/*  Copyright (C) 2020-2024 Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitband5;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareInfo;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareType;
+import nodomain.freeyourgadget.gadgetbridge.util.ArrayUtils;
+
+public class AmazfitBand5FirmwareInfo extends HuamiFirmwareInfo {
+
+    public static final byte[] FW_HEADER = new byte[]{
+            0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, (byte) 0x9c, (byte) 0xe3, 0x7d, 0x5c, 0x00, 0x04
+    };
+
+    public static final int FW_HEADER_OFFSET = 16;
+
+    private static Map<Integer, String> crcToVersion = new HashMap<>();
+
+    static {
+        // no known fw so far
+    }
+
+    public AmazfitBand5FirmwareInfo(byte[] bytes) {
+        super(bytes);
+    }
+
+    @Override
+    protected HuamiFirmwareType determineFirmwareType(byte[] bytes) {
+        if (ArrayUtils.equals(bytes, RES_HEADER, COMPRESSED_RES_HEADER_OFFSET) || ArrayUtils.equals(bytes, NEWRES_HEADER, COMPRESSED_RES_HEADER_OFFSET_NEW) || ArrayUtils.equals(bytes, NEWRES_HEADER, COMPRESSED_RES_HEADER_OFFSET)) {
+            return HuamiFirmwareType.RES_COMPRESSED;
+        }
+        if (ArrayUtils.equals(bytes, FW_HEADER, FW_HEADER_OFFSET)) {
+            if (searchString32BitAligned(bytes, "Amazfit Band 5")) {
+                return HuamiFirmwareType.FIRMWARE;
+            }
+            return HuamiFirmwareType.INVALID;
+        }
+        if (ArrayUtils.startsWith(bytes, UIHH_HEADER) && (bytes[4] == 1 || bytes[4] == 2)) {
+            return HuamiFirmwareType.WATCHFACE;
+        }
+        if (ArrayUtils.startsWith(bytes, NEWFT_HEADER)) {
+            if (bytes[10] == 0x03 || bytes[10] == 0x06) {
+                return HuamiFirmwareType.FONT;
+            }
+        }
+        // somebody might have unpacked the compressed res
+        if (ArrayUtils.startsWith(bytes, RES_HEADER)) {
+            return HuamiFirmwareType.RES;
+        }
+        return HuamiFirmwareType.INVALID;
+    }
+
+
+    @Override
+    public boolean isGenerallyCompatibleWith(GBDevice device) {
+        return isHeaderValid() && device.getType() == DeviceType.AMAZFITBAND5;
+    }
+
+    @Override
+    protected Map<Integer, String> getCrcMap() {
+        return crcToVersion;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitband5/AmazfitBand5Support.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitband5/AmazfitBand5Support.java
new file mode 100644
index 0000000..cf7315f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitband5/AmazfitBand5Support.java
@@ -0,0 +1,47 @@
+/*  Copyright (C) 2020-2024 Andreas Shimokawa, Jos茅 Rebelo, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitband5;
+
+import android.content.Context;
+import android.net.Uri;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitband5.AmazfitBand5FWHelper;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.miband5.MiBand5Support;
+
+public class AmazfitBand5Support extends MiBand5Support {
+    @Override
+    protected AmazfitBand5Support setDisplayItems(TransactionBuilder builder) {
+        setDisplayItemsNew(builder, false, true, R.array.pref_amazfitband5_display_items_default);
+        return this;
+    }
+
+    @Override
+    protected AmazfitBand5Support setShortcuts(TransactionBuilder builder) {
+        setDisplayItemsNew(builder, true, true, R.array.pref_amazfitband5_shortcuts_default);
+        return this;
+    }
+
+    @Override
+    public HuamiFWHelper createFWHelper(Uri uri, Context context) throws IOException {
+        return new AmazfitBand5FWHelper(uri, context);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitbip/AmazfitBipFirmwareInfo.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitbip/AmazfitBipFirmwareInfo.java
new file mode 100644
index 0000000..743da78
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitbip/AmazfitBipFirmwareInfo.java
@@ -0,0 +1,186 @@
+/*  Copyright (C) 2018-2024 Andreas Shimokawa, MyTimeKill
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitbip;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareInfo;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareType;
+import nodomain.freeyourgadget.gadgetbridge.util.ArrayUtils;
+
+public class AmazfitBipFirmwareInfo extends HuamiFirmwareInfo {
+
+    // this is the same as Cor
+    public static final byte[] FW_HEADER = new byte[]{
+            0x00, (byte) 0x98, 0x00, 0x20, (byte) 0xA5, 0x04, 0x00, 0x20, (byte) 0xAD, 0x04, 0x00, 0x20, (byte) 0xC5, 0x04, 0x00, 0x20
+    };
+
+    private static Map<Integer, String> crcToVersion = new HashMap<>();
+    static {
+        // firmware
+        crcToVersion.put(25257, "0.0.8.74");
+        crcToVersion.put(57724, "0.0.8.88");
+        crcToVersion.put(27668, "0.0.8.96");
+        crcToVersion.put(60173, "0.0.8.97");
+        crcToVersion.put(3462,  "0.0.8.98");
+        crcToVersion.put(55420, "0.0.9.14");
+        crcToVersion.put(39465, "0.0.9.26");
+        crcToVersion.put(27394, "0.0.9.40");
+        crcToVersion.put(24736, "0.0.9.49");
+        crcToVersion.put(49555, "0.0.9.59");
+        crcToVersion.put(28586, "0.1.0.08");
+        crcToVersion.put(26714, "0.1.0.11");
+        crcToVersion.put(64160, "0.1.0.17");
+        crcToVersion.put(21992, "0.1.0.26");
+        crcToVersion.put(43028, "0.1.0.27");
+        crcToVersion.put(59462, "0.1.0.33");
+        crcToVersion.put(55277, "0.1.0.39");
+        crcToVersion.put(47685, "0.1.0.43");
+        crcToVersion.put(2839,  "0.1.0.44");
+        crcToVersion.put(30229, "0.1.0.45");
+        crcToVersion.put(24302, "0.1.0.70");
+        crcToVersion.put(1333,  "0.1.0.80");
+        crcToVersion.put(12017, "0.1.0.86");
+        crcToVersion.put(8276,  "0.1.1.14");
+        crcToVersion.put(5914,  "0.1.1.17");
+        crcToVersion.put(6228,  "0.1.1.29");
+        crcToVersion.put(44223, "0.1.1.31");
+        crcToVersion.put(39726, "0.1.1.36");
+        crcToVersion.put(11062, "0.1.1.39");
+        crcToVersion.put(56670, "0.1.1.41");
+        crcToVersion.put(58736, "0.1.1.45");
+        crcToVersion.put(2602,  "1.0.2.00");
+        crcToVersion.put(36157, "1.1.2.05");
+        crcToVersion.put(26444, "1.1.5.02");
+        crcToVersion.put(60002, "1.1.5.04");
+        crcToVersion.put(5229,  "1.1.5.12");
+        crcToVersion.put(32576, "1.1.5.16");
+        crcToVersion.put(28893, "1.1.5.24");
+        crcToVersion.put(61710, "1.1.5.56");
+        crcToVersion.put(23387, "1.1.6.34");
+
+        // Latin Firmware
+        crcToVersion.put(52828, "1.1.5.36 (Latin)");
+        crcToVersion.put(60625, "1.1.6.30 (Latin)");
+        crcToVersion.put(17913, "1.1.6.32 (Latin)");
+
+        // resources
+        crcToVersion.put(12586, "0.0.8.74");
+        crcToVersion.put(34068, "0.0.8.88");
+        crcToVersion.put(59839, "0.0.8.96-98");
+        crcToVersion.put(50401, "0.0.9.14-26");
+        crcToVersion.put(22051, "0.0.9.40");
+        crcToVersion.put(46233, "0.0.9.49-0.1.0.11");
+        crcToVersion.put(12098, "0.1.0.17");
+        crcToVersion.put(28696, "0.1.0.26-27");
+        crcToVersion.put(5650,  "0.1.0.33");
+        crcToVersion.put(16117, "0.1.0.39-45");
+        crcToVersion.put(22506, "0.1.0.66-70");
+        crcToVersion.put(42264, "0.1.0.77-80");
+        crcToVersion.put(55934, "0.1.0.86-89");
+        crcToVersion.put(26587, "0.1.1.14-25");
+        crcToVersion.put(7446,  "0.1.1.29");
+        crcToVersion.put(47887, "0.1.1.31-36");
+        crcToVersion.put(14334, "0.1.1.39");
+        crcToVersion.put(21109, "0.1.1.41");
+        crcToVersion.put(23073, "0.1.1.45");
+        crcToVersion.put(59245, "1.0.2.00");
+        crcToVersion.put(20591, "1.1.2.05");
+        crcToVersion.put(5341, "1.1.5.02-24");
+        crcToVersion.put(22662, "1.1.5.36");
+        crcToVersion.put(24045, "1.1.5.56");
+        crcToVersion.put(37677, "1.1.6.30-32");
+        crcToVersion.put(26735, "1.1.6.34");
+
+        // gps
+        crcToVersion.put(61520, "9367,8f79a91,0,0,");
+        crcToVersion.put(8784,  "9565,dfbd8fa,0,0,");
+        crcToVersion.put(16716, "9565,dfbd8faf42,0");
+        crcToVersion.put(54154, "9567,8b05506,0,0,");
+        crcToVersion.put(15717, "15974,e61dd16,126");
+        crcToVersion.put(62532, "18344,eb2f43f,126");
+
+        // font
+        crcToVersion.put(61054, "8");
+        crcToVersion.put(62291, "9 (old Latin)");
+        crcToVersion.put(59577, "9 (Latin)");
+
+        // BipOS FW
+        crcToVersion.put(28373, "1.1.2.05 (BipOS 0.5)");
+        crcToVersion.put(62977, "1.1.2.05 (BipOS 0.5.1)");
+
+        // BipOS RES
+        crcToVersion.put(16303, "1.1.2.05 (BipOS 0.5)");
+        crcToVersion.put(61135, "1.1.2.05 (BipOS 0.5.1)");
+    }
+
+    public AmazfitBipFirmwareInfo(byte[] bytes) {
+        super(bytes);
+    }
+
+    @Override
+    protected HuamiFirmwareType determineFirmwareType(byte[] bytes) {
+        if (ArrayUtils.startsWith(bytes, RES_HEADER) || ArrayUtils.startsWith(bytes, NEWRES_HEADER)) {
+            if (bytes.length <= 100000) { // dont know how to distinguish from Cor/Mi Band 3 .res
+                return HuamiFirmwareType.INVALID;
+            }
+            return HuamiFirmwareType.RES;
+        }
+        if (ArrayUtils.startsWith(bytes, GPS_ALMANAC_HEADER)) {
+            return HuamiFirmwareType.GPS_ALMANAC;
+        }
+        if (ArrayUtils.startsWith(bytes, GPS_CEP_HEADER)) {
+            return HuamiFirmwareType.GPS_CEP;
+        }
+        if (ArrayUtils.startsWith(bytes, FW_HEADER)) {
+            if (searchString32BitAligned(bytes, "Amazfit Bip Watch")) {
+                return HuamiFirmwareType.FIRMWARE;
+            }
+            return HuamiFirmwareType.INVALID;
+        }
+        if (ArrayUtils.startsWith(bytes, WATCHFACE_HEADER)) {
+            return HuamiFirmwareType.WATCHFACE;
+        }
+        if (ArrayUtils.startsWith(bytes, NEWFT_HEADER)) {
+            if (bytes[10] == 0x01) {
+                return HuamiFirmwareType.FONT;
+            } else if (bytes[10] == 0x02 || bytes[10] == 0x0A) {
+                return HuamiFirmwareType.FONT_LATIN;
+            }
+        }
+        for (byte[] gpsHeader : GPS_HEADERS) {
+            if (ArrayUtils.startsWith(bytes, gpsHeader)) {
+                return HuamiFirmwareType.GPS;
+            }
+        }
+
+        return HuamiFirmwareType.INVALID;
+    }
+
+    @Override
+    public boolean isGenerallyCompatibleWith(GBDevice device) {
+        return isHeaderValid() && device.getType() == DeviceType.AMAZFITBIP;
+    }
+
+    @Override
+    protected Map<Integer, String> getCrcMap() {
+        return crcToVersion;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitbip/AmazfitBipLiteFirmwareInfo.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitbip/AmazfitBipLiteFirmwareInfo.java
new file mode 100644
index 0000000..76d24c0
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitbip/AmazfitBipLiteFirmwareInfo.java
@@ -0,0 +1,107 @@
+/*  Copyright (C) 2019-2024 Andreas Shimokawa, Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitbip;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareInfo;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareType;
+import nodomain.freeyourgadget.gadgetbridge.util.ArrayUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+public class AmazfitBipLiteFirmwareInfo extends HuamiFirmwareInfo {
+
+    // this is the same as Bip and Cor
+    private static final byte[] FW_HEADER = new byte[]{
+            0x00, (byte) 0x98, 0x00, 0x20, (byte) 0xA5, 0x04, 0x00, 0x20, (byte) 0xAD, 0x04, 0x00, 0x20, (byte) 0xC5, 0x04, 0x00, 0x20
+    };
+
+
+    private static Map<Integer, String> crcToVersion = new HashMap<>();
+
+    static {
+        // firmware
+        crcToVersion.put(11059, "1.1.6.02");
+
+        // Latin Firmware
+
+
+        // resources
+        crcToVersion.put(57510, "1.1.6.02");
+
+        // font
+        crcToVersion.put(61054, "8");
+        crcToVersion.put(59577, "9 (Latin)");
+    }
+
+    public AmazfitBipLiteFirmwareInfo(byte[] bytes) {
+        super(bytes);
+    }
+
+    @Override
+    protected HuamiFirmwareType determineFirmwareType(byte[] bytes) {
+        if (ArrayUtils.startsWith(bytes, NEWRES_HEADER)) {
+            if ((bytes.length <= 100000) || (bytes.length > 700000)) { // don't know how to distinguish from Cor/Mi Band 3 .res
+                return HuamiFirmwareType.INVALID;
+            }
+            return HuamiFirmwareType.RES;
+        }
+        if (ArrayUtils.startsWith(bytes, FW_HEADER)) {
+            if (searchString32BitAligned(bytes, "Amazfit Bip Lite")) {
+                return HuamiFirmwareType.FIRMWARE;
+            }
+            List<GBDevice> devices = GBApplication.app().getDeviceManager().getSelectedDevices();
+            for(GBDevice device : devices){
+                Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(device.getAddress()));
+                if (prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_RELAX_FIRMWARE_CHECKS, false)) {
+                    if (searchString32BitAligned(bytes, "Amazfit Bip")) {
+                        return HuamiFirmwareType.FIRMWARE;
+                    }
+                }
+            }
+            return HuamiFirmwareType.INVALID;
+        }
+        if (ArrayUtils.startsWith(bytes, WATCHFACE_HEADER)) {
+            return HuamiFirmwareType.WATCHFACE;
+        }
+        if (ArrayUtils.startsWith(bytes, NEWFT_HEADER)) {
+            if (bytes[10] == 0x01) {
+                return HuamiFirmwareType.FONT;
+            } else if (bytes[10] == 0x02 || bytes[10] == 0x0A) {
+                return HuamiFirmwareType.FONT_LATIN;
+            }
+        }
+
+        return HuamiFirmwareType.INVALID;
+    }
+
+    @Override
+    public boolean isGenerallyCompatibleWith(GBDevice device) {
+        return isHeaderValid() && device.getType() == DeviceType.AMAZFITBIP_LITE;
+    }
+
+    @Override
+    protected Map<Integer, String> getCrcMap() {
+        return crcToVersion;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitbip/AmazfitBipLiteSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitbip/AmazfitBipLiteSupport.java
new file mode 100644
index 0000000..307c4dc
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitbip/AmazfitBipLiteSupport.java
@@ -0,0 +1,43 @@
+/*  Copyright (C) 2019-2024 Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitbip;
+
+import android.content.Context;
+import android.net.Uri;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitbip.AmazfitBipLiteFWHelper;
+
+public class AmazfitBipLiteSupport extends AmazfitBipSupport {
+
+    @Override
+    public byte getCryptFlags() {
+        return (byte) 0x80;
+    }
+    
+    @Override
+    protected byte getAuthFlags() {
+        return 0x00;
+    }
+
+    @Override
+    public HuamiFWHelper createFWHelper(Uri uri, Context context) throws IOException {
+        return new AmazfitBipLiteFWHelper(uri, context);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitbip/AmazfitBipSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitbip/AmazfitBipSupport.java
new file mode 100644
index 0000000..d714f1d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitbip/AmazfitBipSupport.java
@@ -0,0 +1,93 @@
+/*  Copyright (C) 2018-2024 Andreas Shimokawa, DerFetzer, Jos茅 Rebelo,
+    Matthieu Baerts, NekoBox, Oleg Vasilev
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitbip;
+
+import android.content.Context;
+import android.net.Uri;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitbip.AmazfitBipFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.NotificationStrategy;
+
+public class AmazfitBipSupport extends HuamiSupport {
+
+    private static final Logger LOG = LoggerFactory.getLogger(AmazfitBipSupport.class);
+
+    public AmazfitBipSupport() {
+        super(LOG);
+    }
+
+    @Override
+    public NotificationStrategy getNotificationStrategy() {
+        return new AmazfitBipTextNotificationStrategy(this);
+    }
+
+    @Override
+    protected AmazfitBipSupport setDisplayItems(TransactionBuilder builder) {
+        Map<String, Integer> keyPosMap = new LinkedHashMap<>();
+        keyPosMap.put("status", 1);
+        keyPosMap.put("activity", 2);
+        keyPosMap.put("weather", 3);
+        keyPosMap.put("alarm", 4);
+        keyPosMap.put("timer", 5);
+        keyPosMap.put("compass", 6);
+        keyPosMap.put("settings", 7);
+        keyPosMap.put("alipay", 8);
+
+        setDisplayItemsOld(builder, false, R.array.pref_bip_display_items_default, keyPosMap);
+        return this;
+    }
+
+    @Override
+    protected AmazfitBipSupport setShortcuts(TransactionBuilder builder) {
+        Map<String, Integer> keyPosMap = new LinkedHashMap<>();
+        keyPosMap.put("alipay", 1);
+        keyPosMap.put("weather", 2);
+
+        setDisplayItemsOld(builder, true, R.array.pref_bip_shortcuts_default, keyPosMap);
+        return this;
+    }
+
+    @Override
+    public void phase2Initialize(TransactionBuilder builder) {
+        super.phase2Initialize(builder);
+        LOG.info("phase2Initialize...");
+
+        if (HuamiCoordinator.getOverwriteSettingsOnConnection(getDevice().getAddress())) {
+            setLanguage(builder);
+        }
+
+        requestGPSVersion(builder);
+    }
+
+    @Override
+    public HuamiFWHelper createFWHelper(Uri uri, Context context) throws IOException {
+        return new AmazfitBipFWHelper(uri, context);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitbip/AmazfitBipTextNotificationStrategy.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitbip/AmazfitBipTextNotificationStrategy.java
new file mode 100644
index 0000000..45f7374
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitbip/AmazfitBipTextNotificationStrategy.java
@@ -0,0 +1,65 @@
+/*  Copyright (C) 2018-2024 Andreas Shimokawa, Daniele Gobbetti
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitbip;
+
+import androidx.annotation.NonNull;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.VibrationProfile;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEAction;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotification.AlertCategory;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotification.AlertNotificationProfile;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotification.NewAlert;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotification.OverflowStrategy;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.common.SimpleNotification;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.miband2.Mi2TextNotificationStrategy;
+
+
+// This class in no longer in use except for incoming calls
+class AmazfitBipTextNotificationStrategy extends Mi2TextNotificationStrategy {
+
+    AmazfitBipTextNotificationStrategy(HuamiSupport support) {
+        super(support);
+    }
+
+    @Override
+    protected void sendCustomNotification(VibrationProfile vibrationProfile, SimpleNotification simpleNotification, BtLEAction extraAction, TransactionBuilder builder) {
+        if (simpleNotification != null) {
+            sendAlert(simpleNotification, builder);
+        }
+    }
+
+    @Override
+    protected void sendAlert(@NonNull SimpleNotification simpleNotification, TransactionBuilder builder) {
+        AlertNotificationProfile<?> profile = new AlertNotificationProfile<>(getSupport());
+        profile.setMaxLength(255); // TODO: find out real limit, certainly it is more than 18 which is default
+
+        AlertCategory category = simpleNotification.getAlertCategory();
+        switch (simpleNotification.getAlertCategory()) {
+            // only these are confirmed working so far on Amazfit Bip
+            case Email:
+            case IncomingCall:
+            case SMS:
+                break;
+            // default to SMS for non working categories
+            default:
+                category = AlertCategory.SMS;
+        }
+        NewAlert alert = new NewAlert(category, 1, simpleNotification.getMessage());
+        profile.newAlert(builder, alert, OverflowStrategy.TRUNCATE);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitbip3/AmazfitBip3FirmwareInfo.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitbip3/AmazfitBip3FirmwareInfo.java
new file mode 100644
index 0000000..c2f93d8
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitbip3/AmazfitBip3FirmwareInfo.java
@@ -0,0 +1,82 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitbip3;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareInfo;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.miband4.MiBand4FirmwareInfo;
+import nodomain.freeyourgadget.gadgetbridge.util.ArrayUtils;
+
+public class AmazfitBip3FirmwareInfo extends HuamiFirmwareInfo {
+    private static final Map<Integer, String> crcToVersion = new HashMap<>();
+
+    static {
+        // firmware
+
+        // resources
+
+        // gps
+    }
+
+    public AmazfitBip3FirmwareInfo(final byte[] bytes) {
+        super(bytes);
+    }
+
+    @Override
+    protected HuamiFirmwareType determineFirmwareType(final byte[] bytes) {
+        if (ArrayUtils.equals(bytes, MiBand4FirmwareInfo.FW_HEADER, MiBand4FirmwareInfo.FW_HEADER_OFFSET)) {
+            if (searchString32BitAligned(bytes, "\0\0Amazfit Bip 3") && !searchString32BitAligned(bytes, "\0\0Amazfit Bip 3 Pro")) {
+                return HuamiFirmwareType.FIRMWARE;
+            }
+            return HuamiFirmwareType.INVALID;
+        }
+
+        if (ArrayUtils.startsWith(bytes, NEWRES_HEADER)) {
+            return HuamiFirmwareType.RES;
+        }
+
+        if (ArrayUtils.startsWith(bytes, UIHH_HEADER) && (bytes[4] == 1 || bytes[4] == 2)) {
+            return HuamiFirmwareType.WATCHFACE;
+        }
+
+        if (ArrayUtils.startsWith(bytes, AGPS_UIHH_HEADER)) {
+            return HuamiFirmwareType.AGPS_UIHH;
+        }
+
+        for (byte[] gpsHeader : GPS_HEADERS) {
+            if (ArrayUtils.startsWith(bytes, gpsHeader)) {
+                return HuamiFirmwareType.GPS;
+            }
+        }
+        return HuamiFirmwareType.INVALID;
+    }
+
+    @Override
+    public boolean isGenerallyCompatibleWith(final GBDevice device) {
+        return isHeaderValid() && device.getType() == DeviceType.AMAZFITBIP3;
+    }
+
+    @Override
+    protected Map<Integer, String> getCrcMap() {
+        return crcToVersion;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitbip3/AmazfitBip3Support.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitbip3/AmazfitBip3Support.java
new file mode 100644
index 0000000..54666ab
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitbip3/AmazfitBip3Support.java
@@ -0,0 +1,79 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitbip3;
+
+import android.content.Context;
+import android.net.Uri;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitbip3.AmazfitBip3FWHelper;
+import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitbip.AmazfitBipSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.update.UpdateFirmwareOperation;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.update.UpdateFirmwareOperation2020;
+
+public class AmazfitBip3Support extends AmazfitBipSupport {
+    @Override
+    public byte getCryptFlags() {
+        return (byte) 0x80;
+    }
+
+    @Override
+    protected byte getAuthFlags() {
+        return 0x00;
+    }
+
+    @Override
+    protected boolean notificationHasExtraHeader() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsSunriseSunsetWindHumidity() {
+        return true;
+    }
+
+    @Override
+    public HuamiFWHelper createFWHelper(final Uri uri, final Context context) throws IOException {
+        return new AmazfitBip3FWHelper(uri, context);
+    }
+
+    @Override
+    public UpdateFirmwareOperation createUpdateFirmwareOperation(final Uri uri) {
+        return new UpdateFirmwareOperation2020(uri, this);
+    }
+
+    @Override
+    public void onSetCallState(final CallSpec callSpec) {
+        onSetCallStateNew(callSpec);
+    }
+
+    @Override
+    public int getActivitySampleSize() {
+        return 8;
+    }
+
+    @Override
+    protected AmazfitBip3Support setDisplayItems(final TransactionBuilder builder) {
+        setDisplayItemsNew(builder, false, false, R.array.pref_gtsgtr2_display_items_default);
+        return this;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitbip3pro/AmazfitBip3ProFirmwareInfo.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitbip3pro/AmazfitBip3ProFirmwareInfo.java
new file mode 100644
index 0000000..6697995
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitbip3pro/AmazfitBip3ProFirmwareInfo.java
@@ -0,0 +1,82 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitbip3pro;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareInfo;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.miband4.MiBand4FirmwareInfo;
+import nodomain.freeyourgadget.gadgetbridge.util.ArrayUtils;
+
+public class AmazfitBip3ProFirmwareInfo extends HuamiFirmwareInfo {
+    private static final Map<Integer, String> crcToVersion = new HashMap<>();
+
+    static {
+        // firmware
+
+        // resources
+
+        // gps
+    }
+
+    public AmazfitBip3ProFirmwareInfo(final byte[] bytes) {
+        super(bytes);
+    }
+
+    @Override
+    protected HuamiFirmwareType determineFirmwareType(final byte[] bytes) {
+        if (ArrayUtils.equals(bytes, MiBand4FirmwareInfo.FW_HEADER, MiBand4FirmwareInfo.FW_HEADER_OFFSET)) {
+            if (searchString32BitAligned(bytes, "\0\0Amazfit Bip 3 Pro")) {
+                return HuamiFirmwareType.FIRMWARE;
+            }
+            return HuamiFirmwareType.INVALID;
+        }
+
+        if (ArrayUtils.startsWith(bytes, NEWRES_HEADER)) {
+            return HuamiFirmwareType.RES;
+        }
+
+        if (ArrayUtils.startsWith(bytes, UIHH_HEADER) && (bytes[4] == 1 || bytes[4] == 2)) {
+            return HuamiFirmwareType.WATCHFACE;
+        }
+
+        if (ArrayUtils.startsWith(bytes, AGPS_UIHH_HEADER)) {
+            return HuamiFirmwareType.AGPS_UIHH;
+        }
+
+        for (byte[] gpsHeader : GPS_HEADERS) {
+            if (ArrayUtils.startsWith(bytes, gpsHeader)) {
+                return HuamiFirmwareType.GPS;
+            }
+        }
+        return HuamiFirmwareType.INVALID;
+    }
+
+    @Override
+    public boolean isGenerallyCompatibleWith(final GBDevice device) {
+        return isHeaderValid() && device.getType() == DeviceType.AMAZFITBIP3PRO;
+    }
+
+    @Override
+    protected Map<Integer, String> getCrcMap() {
+        return crcToVersion;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitbip3pro/AmazfitBip3ProSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitbip3pro/AmazfitBip3ProSupport.java
new file mode 100644
index 0000000..08f4765
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitbip3pro/AmazfitBip3ProSupport.java
@@ -0,0 +1,86 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitbip3pro;
+
+import android.content.Context;
+import android.net.Uri;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitbip3pro.AmazfitBip3ProFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitbip.AmazfitBipSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.update.UpdateFirmwareOperation;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.update.UpdateFirmwareOperation2020;
+
+public class AmazfitBip3ProSupport extends AmazfitBipSupport {
+    @Override
+    public byte getCryptFlags() {
+        return (byte) 0x80;
+    }
+
+    @Override
+    protected byte getAuthFlags() {
+        return 0x00;
+    }
+
+    @Override
+    protected boolean notificationHasExtraHeader() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsSunriseSunsetWindHumidity() {
+        return true;
+    }
+
+    @Override
+    public HuamiFWHelper createFWHelper(final Uri uri, final Context context) throws IOException {
+        return new AmazfitBip3ProFWHelper(uri, context);
+    }
+
+    @Override
+    public UpdateFirmwareOperation createUpdateFirmwareOperation(final Uri uri) {
+        return new UpdateFirmwareOperation2020(uri, this);
+    }
+
+    @Override
+    public void onSetCallState(final CallSpec callSpec) {
+        onSetCallStateNew(callSpec);
+    }
+
+    @Override
+    public int getActivitySampleSize() {
+        return 8;
+    }
+
+    @Override
+    protected AmazfitBip3ProSupport setDisplayItems(final TransactionBuilder builder) {
+        setDisplayItemsNew(builder, false, false, R.array.pref_gtsgtr2_display_items_default);
+        return this;
+    }
+
+    @Override
+    public String getNotificationBody(NotificationSpec notificationSpec) {
+        // See #4419
+        return getNotificationBodyCheckAcceptsSender(notificationSpec);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitbips/AmazfitBipSFirmwareInfo.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitbips/AmazfitBipSFirmwareInfo.java
new file mode 100644
index 0000000..1d70d8d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitbips/AmazfitBipSFirmwareInfo.java
@@ -0,0 +1,132 @@
+/*  Copyright (C) 2020-2024 Andreas Shimokawa, Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitbips;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareInfo;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitbip.AmazfitBipFirmwareInfo;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.miband4.MiBand4FirmwareInfo;
+import nodomain.freeyourgadget.gadgetbridge.util.ArrayUtils;
+
+public class AmazfitBipSFirmwareInfo extends HuamiFirmwareInfo {
+
+    private static Map<Integer, String> crcToVersion = new HashMap<>();
+
+    static {
+        // fw tonlesap
+        crcToVersion.put(5017, "2.1.1.08");
+        crcToVersion.put(4638, "2.1.1.16");
+        crcToVersion.put(63673, "2.1.1.26");
+        crcToVersion.put(22035, "2.1.1.36");
+        crcToVersion.put(21575, "2.1.1.46");
+        crcToVersion.put(61709, "2.1.1.72");
+        crcToVersion.put(21734, "2.1.1.76");
+
+        // resources
+        crcToVersion.put(61617, "2.1.1.08");
+        crcToVersion.put(5887, "2.1.1.16");
+        crcToVersion.put(15177, "2.1.1.26-36");
+        crcToVersion.put(18775, "2.1.1.46");
+        crcToVersion.put(48133, "2.1.1.72");
+        crcToVersion.put(29102, "2.1.1.76");
+
+        // font
+        crcToVersion.put(62927, "3 (KO/JP)");
+        crcToVersion.put(30818, "3");
+
+        // gps
+        crcToVersion.put(62532, "18344,eb2f43f,126");
+        crcToVersion.put(31510, "19226,f3a8ad3,135");
+        crcToVersion.put(41742, "19439,3aa3392,135");
+    }
+
+    public AmazfitBipSFirmwareInfo(byte[] bytes) {
+        super(bytes);
+    }
+
+    @Override
+    protected HuamiFirmwareType determineFirmwareType(byte[] bytes) {
+        List<GBDevice> devices = GBApplication.app().getDeviceManager().getSelectedDevices();
+        for (GBDevice device : devices) {
+            if (device.getFirmwareVersion().startsWith("2.")) {
+                //For devices on firmware 2.x it is a tonleasp device and needs a header which looks like Mi Band 4
+                if (ArrayUtils.equals(bytes, MiBand4FirmwareInfo.FW_HEADER, MiBand4FirmwareInfo.FW_HEADER_OFFSET)) {
+                    if (searchString32BitAligned(bytes, "Amazfit Bip S")) {
+                        return HuamiFirmwareType.FIRMWARE;
+                    }
+                    return HuamiFirmwareType.INVALID;
+                }
+            } else if (device.getFirmwareVersion().startsWith("4.")) {
+                //For devices on firmware 2.x it is a dth device and needs a header which looks like Bip
+                if (ArrayUtils.startsWith(bytes, AmazfitBipFirmwareInfo.FW_HEADER)) {
+                    if (searchString32BitAligned(bytes, "Amazfit Bip S")) {
+                        return HuamiFirmwareType.FIRMWARE;
+                    }
+                    return HuamiFirmwareType.INVALID;
+                }
+            }
+        }
+
+        if (ArrayUtils.equals(bytes, NEWRES_HEADER, COMPRESSED_RES_HEADER_OFFSET) ||
+                ArrayUtils.equals(bytes, NEWRES_HEADER, COMPRESSED_RES_HEADER_OFFSET_NEW)) {
+            return HuamiFirmwareType.RES_COMPRESSED;
+        }
+
+        if (ArrayUtils.startsWith(bytes, WATCHFACE_HEADER)) {
+            return HuamiFirmwareType.WATCHFACE;
+        }
+        if (ArrayUtils.startsWith(bytes, GPS_ALMANAC_HEADER)) {
+            return HuamiFirmwareType.GPS_ALMANAC;
+        }
+        if (ArrayUtils.startsWith(bytes, GPS_CEP_HEADER)) {
+            return HuamiFirmwareType.GPS_CEP;
+        }
+        if (ArrayUtils.startsWith(bytes, AGPS_UIHH_HEADER)) {
+            return HuamiFirmwareType.AGPS_UIHH;
+        }
+        if (ArrayUtils.startsWith(bytes, NEWFT_HEADER)) {
+            if (bytes[10] == 0x01 || bytes[10] == 0x06 || bytes[10] == 0x03) {
+                return HuamiFirmwareType.FONT;
+            } else if (bytes[10] == 0x02 || bytes[10] == 0x0A) {
+                return HuamiFirmwareType.FONT_LATIN;
+            }
+        }
+        for (byte[] gpsHeader : GPS_HEADERS) {
+            if (ArrayUtils.startsWith(bytes, gpsHeader)) {
+                return HuamiFirmwareType.GPS;
+            }
+        }
+        return HuamiFirmwareType.INVALID;
+    }
+
+    @Override
+    public boolean isGenerallyCompatibleWith(GBDevice device) {
+        return isHeaderValid() && device.getType() == DeviceType.AMAZFITBIPS;
+    }
+
+    @Override
+    protected Map<Integer, String> getCrcMap() {
+        return crcToVersion;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitbips/AmazfitBipSLiteFirmwareInfo.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitbips/AmazfitBipSLiteFirmwareInfo.java
new file mode 100644
index 0000000..c87dd4d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitbips/AmazfitBipSLiteFirmwareInfo.java
@@ -0,0 +1,78 @@
+/*  Copyright (C) 2020-2024 Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitbips;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareInfo;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.miband4.MiBand4FirmwareInfo;
+import nodomain.freeyourgadget.gadgetbridge.util.ArrayUtils;
+
+public class AmazfitBipSLiteFirmwareInfo extends HuamiFirmwareInfo {
+
+    private static Map<Integer, String> crcToVersion = new HashMap<>();
+
+    static {
+
+    }
+
+    public AmazfitBipSLiteFirmwareInfo(byte[] bytes) {
+        super(bytes);
+    }
+
+    @Override
+    protected HuamiFirmwareType determineFirmwareType(byte[] bytes) {
+        if (ArrayUtils.equals(bytes, MiBand4FirmwareInfo.FW_HEADER, MiBand4FirmwareInfo.FW_HEADER_OFFSET)) {
+            if (searchString32BitAligned(bytes, "Bip S Lite")) {
+                return HuamiFirmwareType.FIRMWARE;
+            }
+            return HuamiFirmwareType.INVALID;
+        }
+
+
+        if (ArrayUtils.equals(bytes, NEWRES_HEADER, COMPRESSED_RES_HEADER_OFFSET) ||
+                ArrayUtils.equals(bytes, NEWRES_HEADER, COMPRESSED_RES_HEADER_OFFSET_NEW)) {
+            return HuamiFirmwareType.RES_COMPRESSED;
+        }
+
+        if (ArrayUtils.startsWith(bytes, WATCHFACE_HEADER)) {
+            return HuamiFirmwareType.WATCHFACE;
+        }
+        if (ArrayUtils.startsWith(bytes, NEWFT_HEADER)) {
+            if (bytes[10] == 0x01 || bytes[10] == 0x06 || bytes[10] == 0x03) {
+                return HuamiFirmwareType.FONT;
+            } else if (bytes[10] == 0x02 || bytes[10] == 0x0A) {
+                return HuamiFirmwareType.FONT_LATIN;
+            }
+        }
+        return HuamiFirmwareType.INVALID;
+    }
+
+    @Override
+    public boolean isGenerallyCompatibleWith(GBDevice device) {
+        return isHeaderValid() && device.getType() == DeviceType.AMAZFITBIPS_LITE;
+    }
+
+    @Override
+    protected Map<Integer, String> getCrcMap() {
+        return crcToVersion;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitbips/AmazfitBipSLiteSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitbips/AmazfitBipSLiteSupport.java
new file mode 100644
index 0000000..91bada7
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitbips/AmazfitBipSLiteSupport.java
@@ -0,0 +1,38 @@
+/*  Copyright (C) 2020-2024 Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitbips;
+
+import android.content.Context;
+import android.net.Uri;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitbips.AmazfitBipSLiteFWHelper;
+
+public class AmazfitBipSLiteSupport extends AmazfitBipSSupport {
+
+    private static final Logger LOG = LoggerFactory.getLogger(AmazfitBipSLiteSupport.class);
+
+    @Override
+    public HuamiFWHelper createFWHelper(Uri uri, Context context) throws IOException {
+        return new AmazfitBipSLiteFWHelper(uri, context);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitbips/AmazfitBipSSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitbips/AmazfitBipSSupport.java
new file mode 100644
index 0000000..d23dbb0
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitbips/AmazfitBipSSupport.java
@@ -0,0 +1,110 @@
+/*  Copyright (C) 2020-2024 Andreas Shimokawa, beardhatcode, Jos茅 Rebelo,
+    Zhong Jianxin
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitbips;
+
+import android.content.Context;
+import android.net.Uri;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitbips.AmazfitBipSFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitbip.AmazfitBipSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.update.UpdateFirmwareOperation;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.update.UpdateFirmwareOperation2020;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.update.UpdateFirmwareOperationNew;
+import nodomain.freeyourgadget.gadgetbridge.util.Version;
+
+public class AmazfitBipSSupport extends AmazfitBipSupport {
+
+    private static final Logger LOG = LoggerFactory.getLogger(AmazfitBipSSupport.class);
+
+    @Override
+    public byte getCryptFlags() {
+        return (byte) 0x80;
+    }
+
+    @Override
+    protected boolean notificationHasExtraHeader() {
+        return true;
+    }
+
+    @Override
+    protected int notificationMaxLength() {
+        return 512;
+    }
+
+    @Override
+    protected byte getAuthFlags() {
+        return 0x00;
+    }
+
+    @Override
+    public boolean supportsSunriseSunsetWindHumidity() {
+        Version version = new Version(gbDevice.getFirmwareVersion());
+        return (!isDTH(version) && (version.compareTo(new Version("2.1.1.50")) >= 0) || (version.compareTo(new Version("4.1.5.55")) >= 0));
+    }
+
+    @Override
+    public String windSpeedString(WeatherSpec weatherSpec){
+        return weatherSpec.windSpeed + "km/h";
+    }
+
+    @Override
+    public void onSetCallState(CallSpec callSpec) {
+        onSetCallStateNew(callSpec);
+    }
+
+    @Override
+    public HuamiFWHelper createFWHelper(Uri uri, Context context) throws IOException {
+        return new AmazfitBipSFWHelper(uri, context);
+    }
+
+    @Override
+    public UpdateFirmwareOperation createUpdateFirmwareOperation(Uri uri) {
+        Version version = new Version(gbDevice.getFirmwareVersion());
+        if ((!isDTH(version) && (version.compareTo(new Version("2.1.1.50")) >= 0) || (version.compareTo(new Version("4.1.5.55")) >= 0))) {
+            return new UpdateFirmwareOperation2020(uri, this);
+        }
+
+        return new UpdateFirmwareOperationNew(uri, this);
+    }
+
+    @Override
+    protected AmazfitBipSSupport setDisplayItems(TransactionBuilder builder) {
+        setDisplayItemsNew(builder, false, true, R.array.pref_bips_display_items_default);
+        return this;
+    }
+
+    @Override
+    protected AmazfitBipSSupport setShortcuts(TransactionBuilder builder) {
+        setDisplayItemsNew(builder, true, true, R.array.pref_bips_display_items_default);
+        return this;
+    }
+
+    private boolean isDTH(Version version) {
+        return version.compareTo(new Version("4.0.0.00")) >= 0;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitbipu/AmazfitBipUFirmwareInfo.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitbipu/AmazfitBipUFirmwareInfo.java
new file mode 100644
index 0000000..d4bea21
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitbipu/AmazfitBipUFirmwareInfo.java
@@ -0,0 +1,85 @@
+/*  Copyright (C) 2020-2024 Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitbipu;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareInfo;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.miband4.MiBand4FirmwareInfo;
+import nodomain.freeyourgadget.gadgetbridge.util.ArrayUtils;
+
+public class AmazfitBipUFirmwareInfo extends HuamiFirmwareInfo {
+
+    private static Map<Integer, String> crcToVersion = new HashMap<>();
+
+    static {
+        // fw
+        crcToVersion.put(50652, "1.0.2.95");
+
+        // resources
+        crcToVersion.put(52508, "1.0.2.95");
+
+        // font
+        crcToVersion.put(60101, "8");
+
+    }
+
+    public AmazfitBipUFirmwareInfo(byte[] bytes) {
+        super(bytes);
+    }
+
+    @Override
+    protected HuamiFirmwareType determineFirmwareType(byte[] bytes) {
+
+        if (ArrayUtils.equals(bytes, MiBand4FirmwareInfo.FW_HEADER, MiBand4FirmwareInfo.FW_HEADER_OFFSET)) {
+            if (searchString32BitAligned(bytes, "\0\0Amazfit Bip U")) {
+                return HuamiFirmwareType.FIRMWARE;
+            }
+            return HuamiFirmwareType.INVALID;
+        }
+
+        if (ArrayUtils.startsWith(bytes,NEWRES_HEADER)) {
+            return HuamiFirmwareType.RES;
+        }
+
+        if (ArrayUtils.startsWith(bytes, UIHH_HEADER) && (bytes[4] == 1 || bytes[4] == 2)) {
+            return HuamiFirmwareType.WATCHFACE;
+        }
+
+        if (ArrayUtils.startsWith(bytes, NEWFT_HEADER)) {
+            if (bytes[10] == 0x01 || bytes[10] == 0x06 || bytes[10] == 0x03) {
+                return HuamiFirmwareType.FONT;
+            }
+        }
+
+        return HuamiFirmwareType.INVALID;
+    }
+
+    @Override
+    public boolean isGenerallyCompatibleWith(GBDevice device) {
+        return isHeaderValid() && device.getType() == DeviceType.AMAZFITBIPU;
+    }
+
+    @Override
+    protected Map<Integer, String> getCrcMap() {
+        return crcToVersion;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitbipu/AmazfitBipUSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitbipu/AmazfitBipUSupport.java
new file mode 100644
index 0000000..08d3499
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitbipu/AmazfitBipUSupport.java
@@ -0,0 +1,58 @@
+/*  Copyright (C) 2020-2024 Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitbipu;
+
+import android.content.Context;
+import android.net.Uri;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitbipu.AmazfitBipUFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitbips.AmazfitBipSSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.update.UpdateFirmwareOperation;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.update.UpdateFirmwareOperation2020;
+
+public class AmazfitBipUSupport extends AmazfitBipSSupport {
+
+    @Override
+    protected HuamiSupport setLanguage(TransactionBuilder builder) {
+        return setLanguageByIdNew(builder);
+    }
+
+    @Override
+    public boolean supportsSunriseSunsetWindHumidity() {
+        return true;
+    }
+
+    @Override
+    public HuamiFWHelper createFWHelper(Uri uri, Context context) throws IOException {
+        return new AmazfitBipUFWHelper(uri, context);
+    }
+
+    @Override
+    public UpdateFirmwareOperation createUpdateFirmwareOperation(Uri uri) {
+        return new UpdateFirmwareOperation2020(uri, this);
+    }
+
+    @Override
+    public int getActivitySampleSize() {
+        return 8;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitbipupro/AmazfitBipUProFirmwareInfo.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitbipupro/AmazfitBipUProFirmwareInfo.java
new file mode 100644
index 0000000..de1104f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitbipupro/AmazfitBipUProFirmwareInfo.java
@@ -0,0 +1,93 @@
+/*  Copyright (C) 2021-2024 Andreas Shimokawa, DanialHanif
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitbipupro;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareInfo;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.miband4.MiBand4FirmwareInfo;
+import nodomain.freeyourgadget.gadgetbridge.util.ArrayUtils;
+
+public class AmazfitBipUProFirmwareInfo extends HuamiFirmwareInfo {
+
+    private static Map<Integer, String> crcToVersion = new HashMap<>();
+
+    static {
+        // fw
+        crcToVersion.put(46603, "1.0.2.92");
+
+        // resources
+        crcToVersion.put(33532, "1.0.2.92");
+
+        // font
+        crcToVersion.put(60101, "8");
+    }
+
+    public AmazfitBipUProFirmwareInfo(byte[] bytes) {
+        super(bytes);
+    }
+
+    @Override
+    protected HuamiFirmwareType determineFirmwareType(byte[] bytes) {
+
+        if (ArrayUtils.equals(bytes, MiBand4FirmwareInfo.FW_HEADER, MiBand4FirmwareInfo.FW_HEADER_OFFSET)) {
+            if (searchString32BitAligned(bytes, "\0\0Amazfit Bip U Pro")) {
+                return HuamiFirmwareType.FIRMWARE;
+            }
+            return HuamiFirmwareType.INVALID;
+        }
+
+        if (ArrayUtils.startsWith(bytes, NEWRES_HEADER)) {
+            return HuamiFirmwareType.RES;
+        }
+
+        if (ArrayUtils.startsWith(bytes, UIHH_HEADER) && (bytes[4] == 1 || bytes[4] == 2)) {
+            return HuamiFirmwareType.WATCHFACE;
+        }
+
+        if (ArrayUtils.startsWith(bytes, NEWFT_HEADER)) {
+            if (bytes[10] == 0x01 || bytes[10] == 0x06 || bytes[10] == 0x03) {
+                return HuamiFirmwareType.FONT;
+            }
+        }
+
+        if (ArrayUtils.startsWith(bytes, AGPS_UIHH_HEADER)) {
+            return HuamiFirmwareType.AGPS_UIHH;
+        }
+
+        for (byte[] gpsHeader : GPS_HEADERS) {
+            if (ArrayUtils.startsWith(bytes, gpsHeader)) {
+                return HuamiFirmwareType.GPS;
+            }
+        }
+        return HuamiFirmwareType.INVALID;
+    }
+
+    @Override
+    public boolean isGenerallyCompatibleWith(GBDevice device) {
+        return isHeaderValid() && device.getType() == DeviceType.AMAZFITBIPUPRO;
+    }
+
+    @Override
+    protected Map<Integer, String> getCrcMap() {
+        return crcToVersion;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitbipupro/AmazfitBipUProSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitbipupro/AmazfitBipUProSupport.java
new file mode 100644
index 0000000..e335f48
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitbipupro/AmazfitBipUProSupport.java
@@ -0,0 +1,34 @@
+/*  Copyright (C) 2021-2024 Andreas Shimokawa, DanialHanif
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitbipupro;
+
+import android.content.Context;
+import android.net.Uri;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitbipupro.AmazfitBipUProFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitbipu.AmazfitBipUSupport;
+
+public class AmazfitBipUProSupport extends AmazfitBipUSupport {
+
+    @Override
+    public HuamiFWHelper createFWHelper(Uri uri, Context context) throws IOException {
+        return new AmazfitBipUProFWHelper(uri, context);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitcor/AmazfitCorFirmwareInfo.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitcor/AmazfitCorFirmwareInfo.java
new file mode 100644
index 0000000..6e170ca
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitcor/AmazfitCorFirmwareInfo.java
@@ -0,0 +1,104 @@
+/*  Copyright (C) 2017-2024 Andreas Shimokawa, Daniele Gobbetti
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitcor;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareInfo;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareType;
+import nodomain.freeyourgadget.gadgetbridge.util.ArrayUtils;
+
+public class AmazfitCorFirmwareInfo extends HuamiFirmwareInfo {
+    // this is the same as Bip
+    private static final byte[] FW_HEADER = new byte[]{
+            0x00, (byte) 0x98, 0x00, 0x20, (byte) 0xA5, 0x04, 0x00, 0x20, (byte) 0xAD, 0x04, 0x00, 0x20, (byte) 0xC5, 0x04, 0x00, 0x20
+    };
+
+    private static final int COMPRESSED_RES_HEADER_OFFSET = 0x9;
+
+    private static Map<Integer, String> crcToVersion = new HashMap<>();
+
+    static {
+        // firmware
+        crcToVersion.put(39948, "1.0.5.60");
+        crcToVersion.put(62147, "1.0.5.78");
+        crcToVersion.put(54213, "1.0.6.76");
+        crcToVersion.put(9458,  "1.0.7.52");
+        crcToVersion.put(51575, "1.0.7.88");
+        crcToVersion.put(6346, "1.2.5.00");
+        crcToVersion.put(24277, "1.2.7.20");
+        crcToVersion.put(10078, "1.2.7.32");
+
+        // resources
+        crcToVersion.put(46341, "RES 1.0.5.60");
+        crcToVersion.put(21770, "RES 1.0.5.78");
+        crcToVersion.put(64977, "RES 1.0.6.76");
+        crcToVersion.put(60501, "RES 1.0.7.52-71");
+        crcToVersion.put(31263, "RES 1.0.7.77-91");
+        crcToVersion.put(20920, "RES 1.2.5.00-69");
+        crcToVersion.put(25397, "RES 1.2.7.20");
+        crcToVersion.put(54167, "RES 1.2.7.32");
+
+        // font
+        crcToVersion.put(61054, "8");
+        crcToVersion.put(62291, "9 (Latin)");
+    }
+
+    public AmazfitCorFirmwareInfo(byte[] bytes) {
+        super(bytes);
+    }
+
+    @Override
+    protected HuamiFirmwareType determineFirmwareType(byte[] bytes) {
+        if (ArrayUtils.equals(bytes, RES_HEADER, COMPRESSED_RES_HEADER_OFFSET) || ArrayUtils.equals(bytes, NEWRES_HEADER, COMPRESSED_RES_HEADER_OFFSET)) {
+            return HuamiFirmwareType.RES_COMPRESSED;
+        }
+        if (ArrayUtils.startsWith(bytes, FW_HEADER)) {
+            if (searchString32BitAligned(bytes, "Amazfit Cor")) {
+                return HuamiFirmwareType.FIRMWARE;
+            }
+            return HuamiFirmwareType.INVALID;
+        }
+        if (ArrayUtils.startsWith(bytes, WATCHFACE_HEADER)) {
+            return HuamiFirmwareType.WATCHFACE;
+        }
+        if (ArrayUtils.startsWith(bytes, NEWFT_HEADER)) {
+            if (bytes[10] == 0x01) {
+                return HuamiFirmwareType.FONT;
+            } else if (bytes[10] == 0x02) {
+                return HuamiFirmwareType.FONT_LATIN;
+            }
+        }
+        if (ArrayUtils.startsWith(bytes, RES_HEADER)) {
+            return HuamiFirmwareType.RES;
+        }
+        return HuamiFirmwareType.INVALID;
+    }
+
+    @Override
+    public boolean isGenerallyCompatibleWith(GBDevice device) {
+        return isHeaderValid() && device.getType() == DeviceType.AMAZFITCOR;
+    }
+
+    @Override
+    protected Map<Integer, String> getCrcMap() {
+        return crcToVersion;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitcor/AmazfitCorSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitcor/AmazfitCorSupport.java
new file mode 100644
index 0000000..a654d55
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitcor/AmazfitCorSupport.java
@@ -0,0 +1,63 @@
+/*  Copyright (C) 2017-2024 Andreas Shimokawa, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitcor;
+
+import android.content.Context;
+import android.net.Uri;
+
+import java.io.IOException;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitcor.AmazfitCorFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitbip.AmazfitBipSupport;
+
+public class AmazfitCorSupport extends AmazfitBipSupport {
+
+    @Override
+    protected AmazfitCorSupport setDisplayItems(TransactionBuilder builder) {
+        Map<String, Integer> keyPosMap = new LinkedHashMap<>();
+        keyPosMap.put("status", 1);
+        keyPosMap.put("notifications", 2);
+        keyPosMap.put("activity", 3);
+        keyPosMap.put("weather", 4);
+        keyPosMap.put("alarm", 5);
+        keyPosMap.put("timer", 6);
+        keyPosMap.put("settings", 7);
+        keyPosMap.put("alipay", 8);
+        keyPosMap.put("music", 9);
+
+        setDisplayItemsOld(builder, false, R.array.pref_cor_display_items_default, keyPosMap);
+        return this;
+    }
+
+    @Override
+    public void onSetMusicState(MusicStateSpec stateSpec) {
+        if (mediaManager.onSetMusicState(stateSpec)) {
+            sendMusicStateToDevice(null, mediaManager.getBufferMusicStateSpec());
+        }
+    }
+
+    @Override
+    public HuamiFWHelper createFWHelper(Uri uri, Context context) throws IOException {
+        return new AmazfitCorFWHelper(uri, context);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitcor2/AmazfitCor2FirmwareInfo.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitcor2/AmazfitCor2FirmwareInfo.java
new file mode 100644
index 0000000..3949ae6
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitcor2/AmazfitCor2FirmwareInfo.java
@@ -0,0 +1,87 @@
+/*  Copyright (C) 2019-2024 Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitcor2;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareInfo;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareType;
+import nodomain.freeyourgadget.gadgetbridge.util.ArrayUtils;
+
+public class AmazfitCor2FirmwareInfo extends HuamiFirmwareInfo {
+    // this is the same as Bip
+    private static final byte[] FW_HEADER = new byte[]{
+            0x00, (byte) 0x98, 0x00, 0x20, (byte) 0xA5, 0x04, 0x00, 0x20, (byte) 0xAD, 0x04, 0x00, 0x20, (byte) 0xC5, 0x04, 0x00, 0x20
+    };
+
+    private static Map<Integer, String> crcToVersion = new HashMap<>();
+
+    static {
+        // font
+        crcToVersion.put(61054, "8");
+        crcToVersion.put(62291, "9 (Latin)");
+    }
+
+    public AmazfitCor2FirmwareInfo(byte[] bytes) {
+        super(bytes);
+    }
+
+    @Override
+    protected HuamiFirmwareType determineFirmwareType(byte[] bytes) {
+        if (ArrayUtils.equals(bytes, RES_HEADER, COMPRESSED_RES_HEADER_OFFSET) || ArrayUtils.equals(bytes, NEWRES_HEADER, COMPRESSED_RES_HEADER_OFFSET_NEW) || ArrayUtils.equals(bytes, NEWRES_HEADER, COMPRESSED_RES_HEADER_OFFSET)) {
+            return HuamiFirmwareType.RES_COMPRESSED;
+        }
+        if (ArrayUtils.startsWith(bytes, FW_HEADER)) {
+            // FIXME: It would certainly better if we could check for "Cor 2" when the device name is "Cor 2" and for "Band 2" when it is "Band 2"
+            if (searchString32BitAligned(bytes, "Amazfit Cor 2")) {
+                return HuamiFirmwareType.FIRMWARE;
+            }
+            if (searchString32BitAligned(bytes, "Amazfit Band 2")) {
+                return HuamiFirmwareType.FIRMWARE;
+            }
+            return HuamiFirmwareType.INVALID;
+        }
+        if (ArrayUtils.startsWith(bytes, WATCHFACE_HEADER)) {
+            return HuamiFirmwareType.WATCHFACE;
+        }
+        if (ArrayUtils.startsWith(bytes, NEWFT_HEADER)) {
+            if (bytes[10] == 0x01) {
+                return HuamiFirmwareType.FONT;
+            } else if (bytes[10] == 0x02) {
+                return HuamiFirmwareType.FONT_LATIN;
+            }
+        }
+        // somebody might have unpacked the compressed res
+        if (ArrayUtils.startsWith(bytes, RES_HEADER)) {
+            return HuamiFirmwareType.RES;
+        }
+        return HuamiFirmwareType.INVALID;
+    }
+
+    @Override
+    public boolean isGenerallyCompatibleWith(GBDevice device) {
+        return isHeaderValid() && device.getType() == DeviceType.AMAZFITCOR2;
+    }
+
+    @Override
+    protected Map<Integer, String> getCrcMap() {
+        return crcToVersion;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitcor2/AmazfitCor2Support.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitcor2/AmazfitCor2Support.java
new file mode 100644
index 0000000..59b2c47
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitcor2/AmazfitCor2Support.java
@@ -0,0 +1,40 @@
+/*  Copyright (C) 2019-2024 Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitcor2;
+
+import android.content.Context;
+import android.net.Uri;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitcor2.AmazfitCor2FWHelper;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitcor.AmazfitCorSupport;
+
+public class AmazfitCor2Support extends AmazfitCorSupport {
+
+    private static final Logger LOG = LoggerFactory.getLogger(AmazfitCor2Support.class);
+
+
+    @Override
+    public HuamiFWHelper createFWHelper(Uri uri, Context context) throws IOException {
+        return new AmazfitCor2FWHelper(uri, context);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitgtr/AmazfitGTRFirmwareInfo.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitgtr/AmazfitGTRFirmwareInfo.java
new file mode 100644
index 0000000..18aef6d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitgtr/AmazfitGTRFirmwareInfo.java
@@ -0,0 +1,120 @@
+/*  Copyright (C) 2019-2024 Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitgtr;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareInfo;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareType;
+import nodomain.freeyourgadget.gadgetbridge.util.ArrayUtils;
+
+public class AmazfitGTRFirmwareInfo extends HuamiFirmwareInfo {
+    private static final int FW_OFFSET = 3;
+
+    private static final byte[] FW_HEADER = new byte[]{
+            0x20, (byte) 0x99, 0x12, 0x01, 0x08 // completely nonsense probably
+    };
+
+    private static final byte[] GPS_ALMANAC_HEADER = new byte[]{ // probably wrong
+            (byte) 0xa0, (byte) 0x80, 0x08, 0x00, (byte) 0x8b, 0x07
+    };
+
+    private static final byte[] GPS_CEP_HEADER = new byte[]{ // probably wrong
+            0x2a, 0x12, (byte) 0xa0, 0x02
+    };
+
+    // gps detection is totally bogus, just the first 16 bytes
+    private static final byte[][] GPS_HEADERS = {
+            new byte[]{
+                    0x73, 0x75, 0x68, (byte) 0xd0, 0x70, 0x73, (byte) 0xbb, 0x5a,
+                    0x3e, (byte) 0xc3, (byte) 0xd3, 0x09, (byte) 0x9e, 0x1d, (byte) 0xd3, (byte) 0xc9
+            }
+    };
+    private static Map<Integer, String> crcToVersion = new HashMap<>();
+
+    static {
+        // firmware
+
+        // Latin Firmware
+
+        // resources
+
+        // font
+
+        // gps
+        crcToVersion.put(62532, "18344,eb2f43f,126");
+    }
+
+    public AmazfitGTRFirmwareInfo(byte[] bytes) {
+        super(bytes);
+    }
+
+    @Override
+    protected HuamiFirmwareType determineFirmwareType(byte[] bytes) {
+        if (ArrayUtils.equals(bytes, NEWRES_HEADER, COMPRESSED_RES_HEADER_OFFSET_NEW)) {
+            return HuamiFirmwareType.RES_COMPRESSED;
+        }
+        if (ArrayUtils.equals(bytes, FW_HEADER, FW_OFFSET)) {
+            if (searchString32BitAligned(bytes, "Amazfit GTR")) {
+                return HuamiFirmwareType.FIRMWARE;
+            }
+            return HuamiFirmwareType.INVALID;
+        }
+        if (ArrayUtils.startsWith(bytes, WATCHFACE_HEADER) || ArrayUtils.equals(bytes, WATCHFACE_HEADER, COMPRESSED_RES_HEADER_OFFSET_NEW) || ArrayUtils.equals(bytes, WATCHFACE_HEADER, COMPRESSED_RES_HEADER_OFFSET)) {
+            return HuamiFirmwareType.WATCHFACE;
+        }
+        if (ArrayUtils.startsWith(bytes, NEWFT_HEADER)) {
+            if (bytes[10] == 0x01) {
+                return HuamiFirmwareType.FONT;
+            } else if (bytes[10] == 0x02) {
+                return HuamiFirmwareType.FONT_LATIN;
+            }
+        }
+
+        if (ArrayUtils.startsWith(bytes, GPS_ALMANAC_HEADER)) {
+            return HuamiFirmwareType.GPS_ALMANAC;
+        }
+        if (ArrayUtils.startsWith(bytes, GPS_CEP_HEADER)) {
+            return HuamiFirmwareType.GPS_CEP;
+        }
+
+        if (ArrayUtils.startsWith(bytes, AGPS_UIHH_HEADER)) {
+            return HuamiFirmwareType.AGPS_UIHH;
+        }
+
+        for (byte[] gpsHeader : GPS_HEADERS) {
+            if (ArrayUtils.startsWith(bytes, gpsHeader)) {
+                return HuamiFirmwareType.GPS;
+            }
+        }
+
+        return HuamiFirmwareType.INVALID;
+    }
+
+    @Override
+    public boolean isGenerallyCompatibleWith(GBDevice device) {
+        return isHeaderValid() && device.getType() == DeviceType.AMAZFITGTR;
+    }
+
+    @Override
+    protected Map<Integer, String> getCrcMap() {
+        return crcToVersion;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitgtr/AmazfitGTRLiteFirmwareInfo.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitgtr/AmazfitGTRLiteFirmwareInfo.java
new file mode 100644
index 0000000..2a4cf60
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitgtr/AmazfitGTRLiteFirmwareInfo.java
@@ -0,0 +1,85 @@
+/*  Copyright (C) 2020-2024 Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitgtr;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareInfo;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareType;
+import nodomain.freeyourgadget.gadgetbridge.util.ArrayUtils;
+
+public class AmazfitGTRLiteFirmwareInfo extends HuamiFirmwareInfo {
+    private static final int FW_OFFSET = 3;
+
+    private static final byte[] FW_HEADER = new byte[]{
+            0x20, (byte) 0x99, 0x12, 0x01, 0x08 // completely nonsense probably
+    };
+
+    private static Map<Integer, String> crcToVersion = new HashMap<>();
+
+    static {
+        // firmware
+
+        // Latin Firmware
+
+        // resources
+
+        // font
+    }
+
+    public AmazfitGTRLiteFirmwareInfo(byte[] bytes) {
+        super(bytes);
+    }
+
+    @Override
+    protected HuamiFirmwareType determineFirmwareType(byte[] bytes) {
+        if (ArrayUtils.equals(bytes, NEWRES_HEADER, COMPRESSED_RES_HEADER_OFFSET_NEW)) {
+            return HuamiFirmwareType.RES_COMPRESSED;
+        }
+        if (ArrayUtils.equals(bytes, FW_HEADER, FW_OFFSET)) {
+            if (searchString32BitAligned(bytes, "Amazfit GTR L")) {
+                return HuamiFirmwareType.FIRMWARE;
+            }
+            return HuamiFirmwareType.INVALID;
+        }
+        if (ArrayUtils.startsWith(bytes, WATCHFACE_HEADER) || ArrayUtils.equals(bytes, WATCHFACE_HEADER, COMPRESSED_RES_HEADER_OFFSET_NEW) || ArrayUtils.equals(bytes, WATCHFACE_HEADER, COMPRESSED_RES_HEADER_OFFSET)) {
+            return HuamiFirmwareType.WATCHFACE;
+        }
+        if (ArrayUtils.startsWith(bytes, NEWFT_HEADER)) {
+            if (bytes[10] == 0x01) {
+                return HuamiFirmwareType.FONT;
+            } else if (bytes[10] == 0x02) {
+                return HuamiFirmwareType.FONT_LATIN;
+            }
+        }
+
+        return HuamiFirmwareType.INVALID;
+    }
+
+    @Override
+    public boolean isGenerallyCompatibleWith(GBDevice device) {
+        return isHeaderValid() && device.getType() == DeviceType.AMAZFITGTR_LITE;
+    }
+
+    @Override
+    protected Map<Integer, String> getCrcMap() {
+        return crcToVersion;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitgtr/AmazfitGTRLiteSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitgtr/AmazfitGTRLiteSupport.java
new file mode 100644
index 0000000..cab0d2e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitgtr/AmazfitGTRLiteSupport.java
@@ -0,0 +1,65 @@
+/*  Copyright (C) 2020-2024 Andreas Shimokawa, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitgtr;
+
+import android.content.Context;
+import android.net.Uri;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitgtr.AmazfitGTRLiteFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitgts.AmazfitGTSSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.update.UpdateFirmwareOperation;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.update.UpdateFirmwareOperationNew;
+import nodomain.freeyourgadget.gadgetbridge.util.Version;
+
+public class AmazfitGTRLiteSupport extends AmazfitGTSSupport {
+
+    @Override
+    public HuamiFWHelper createFWHelper(Uri uri, Context context) throws IOException {
+        return new AmazfitGTRLiteFWHelper(uri, context);
+    }
+
+    // override to skip requesting GPS version
+    @Override
+    public void phase2Initialize(TransactionBuilder builder) {
+        super.phase2Initialize(builder);
+
+        if (HuamiCoordinator.getOverwriteSettingsOnConnection(getDevice().getAddress())) {
+            setLanguage(builder);
+        }
+    }
+
+    @Override
+    public UpdateFirmwareOperation createUpdateFirmwareOperation(Uri uri) {
+        return new UpdateFirmwareOperationNew(uri, this);
+    }
+
+    @Override
+    protected void handleDeviceInfo(nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo.DeviceInfo info) {
+        super.handleDeviceInfo(info);
+        if (gbDevice.getFirmwareVersion() != null) {
+            Version version = new Version(gbDevice.getFirmwareVersion());
+            if (version.compareTo(new Version("1.0.0.33")) >= 0) {
+                mActivitySampleSize = 8;
+            }
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitgtr/AmazfitGTRSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitgtr/AmazfitGTRSupport.java
new file mode 100644
index 0000000..23f8dbb
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitgtr/AmazfitGTRSupport.java
@@ -0,0 +1,69 @@
+/*  Copyright (C) 2019-2024 Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitgtr;
+
+import android.content.Context;
+import android.net.Uri;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitgtr.AmazfitGTRFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitgts.AmazfitGTSSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.update.UpdateFirmwareOperation;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.update.UpdateFirmwareOperation2020;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.update.UpdateFirmwareOperationNew;
+import nodomain.freeyourgadget.gadgetbridge.util.Version;
+
+public class AmazfitGTRSupport extends AmazfitGTSSupport {
+
+    @Override
+    public HuamiFWHelper createFWHelper(Uri uri, Context context) throws IOException {
+        return new AmazfitGTRFWHelper(uri, context);
+    }
+
+    @Override
+    public UpdateFirmwareOperation createUpdateFirmwareOperation(Uri uri) {
+        Version version = new Version(gbDevice.getFirmwareVersion());
+        if ((!isCH(version) && (version.compareTo(new Version("1.3.7.16")) >= 0)) || (version.compareTo(new Version("1.7.0.10")) >= 0)) {
+            return new UpdateFirmwareOperation2020(uri, this);
+        }
+
+        return new UpdateFirmwareOperationNew(uri, this);
+    }
+
+    @Override
+    protected void handleDeviceInfo(nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo.DeviceInfo info) {
+        super.handleDeviceInfo(info);
+        if (gbDevice.getFirmwareVersion() != null) {
+            Version version = new Version(gbDevice.getFirmwareVersion());
+            if (version.compareTo(new Version("1.3.5.79")) >= 0 || // For GTR 47mm
+                    (!is47mm(version) && version.compareTo(new Version("0.1.1.15")) >= 0)) { // for GTR 32mm with a different version scheme
+                mActivitySampleSize = 8;
+            }
+        }
+    }
+
+    private boolean is47mm(Version version) {
+        return (version.compareTo(new Version("1.0.0.00")) >= 0) && (version.compareTo(new Version("1.6.0.00")) < 0);
+    }
+
+    private boolean isCH(Version version) { // chinese variant?
+        return version.compareTo(new Version("1.6.0.00")) >= 0;
+    }
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitgtr2/AmazfitGTR2FirmwareInfo.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitgtr2/AmazfitGTR2FirmwareInfo.java
new file mode 100644
index 0000000..f5b4f0f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitgtr2/AmazfitGTR2FirmwareInfo.java
@@ -0,0 +1,120 @@
+/*  Copyright (C) 2020-2024 Andreas Shimokawa, pangwalla
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitgtr2;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareInfo;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareType;
+import nodomain.freeyourgadget.gadgetbridge.util.ArrayUtils;
+
+public class AmazfitGTR2FirmwareInfo extends HuamiFirmwareInfo {
+    private static final int FW_OFFSET = 3;
+
+    private static final byte[] FW_HEADER = new byte[]{
+            0x20, (byte) 0x99, 0x12, 0x01, 0x08 // completely nonsense probably
+    };
+
+    private static final byte[] GPS_ALMANAC_HEADER = new byte[]{ // probably wrong
+            (byte) 0xa0, (byte) 0x80, 0x08, 0x00, (byte) 0x8b, 0x07
+    };
+
+    private static final byte[] GPS_CEP_HEADER = new byte[]{ // probably wrong
+            0x2a, 0x12, (byte) 0xa0, 0x02
+    };
+
+    // gps detection is totally bogus, just the first 16 bytes
+    private static final byte[][] GPS_HEADERS = {
+            new byte[]{
+                    0x73, 0x75, 0x68, (byte) 0xd0, 0x70, 0x73, (byte) 0xbb, 0x5a,
+                    0x3e, (byte) 0xc3, (byte) 0xd3, 0x09, (byte) 0x9e, 0x1d, (byte) 0xd3, (byte) 0xc9
+            }
+    };
+    private static Map<Integer, String> crcToVersion = new HashMap<>();
+
+    static {
+        // firmware
+
+        // Latin Firmware
+
+        // resources
+
+        // font
+
+        // gps
+        crcToVersion.put(62532, "18344,eb2f43f,126");
+    }
+
+    public AmazfitGTR2FirmwareInfo(byte[] bytes) {
+        super(bytes);
+    }
+
+    @Override
+    protected HuamiFirmwareType determineFirmwareType(byte[] bytes) {
+        if (ArrayUtils.equals(bytes, NEWRES_HEADER, COMPRESSED_RES_HEADER_OFFSET_NEW)) {
+            return HuamiFirmwareType.RES_COMPRESSED;
+        }
+        if (ArrayUtils.equals(bytes, FW_HEADER, FW_OFFSET)) {
+            if (searchString32BitAligned(bytes, "Amazfit GTR 2")) {
+                return HuamiFirmwareType.FIRMWARE;
+            }
+            return HuamiFirmwareType.INVALID;
+        }
+        if ((ArrayUtils.startsWith(bytes, UIHH_HEADER) && (bytes[4] == 1 || bytes[4] == 2)) || ArrayUtils.startsWith(bytes, WATCHFACE_HEADER) || ArrayUtils.equals(bytes, WATCHFACE_HEADER, COMPRESSED_RES_HEADER_OFFSET_NEW) || ArrayUtils.equals(bytes, WATCHFACE_HEADER, COMPRESSED_RES_HEADER_OFFSET)) {
+            return HuamiFirmwareType.WATCHFACE;
+        }
+        if (ArrayUtils.startsWith(bytes, NEWFT_HEADER)) {
+            if (bytes[10] == 0x01) {
+                return HuamiFirmwareType.FONT;
+            } else if (bytes[10] == 0x02) {
+                return HuamiFirmwareType.FONT_LATIN;
+            }
+        }
+
+        if (ArrayUtils.startsWith(bytes, GPS_ALMANAC_HEADER)) {
+            return HuamiFirmwareType.GPS_ALMANAC;
+        }
+        if (ArrayUtils.startsWith(bytes, GPS_CEP_HEADER)) {
+            return HuamiFirmwareType.GPS_CEP;
+        }
+
+        if (ArrayUtils.startsWith(bytes, AGPS_UIHH_HEADER)) {
+            return HuamiFirmwareType.AGPS_UIHH;
+        }
+
+        for (byte[] gpsHeader : GPS_HEADERS) {
+            if (ArrayUtils.startsWith(bytes, gpsHeader)) {
+                return HuamiFirmwareType.GPS;
+            }
+        }
+
+        return HuamiFirmwareType.INVALID;
+    }
+
+    @Override
+    public boolean isGenerallyCompatibleWith(GBDevice device) {
+        return isHeaderValid() && device.getType() == DeviceType.AMAZFITGTR2;
+    }
+
+    @Override
+    protected Map<Integer, String> getCrcMap() {
+        return crcToVersion;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitgtr2/AmazfitGTR2Support.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitgtr2/AmazfitGTR2Support.java
new file mode 100644
index 0000000..46f3194
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitgtr2/AmazfitGTR2Support.java
@@ -0,0 +1,66 @@
+/*  Copyright (C) 2020-2024 Andreas Shimokawa, pangwalla, Vianney le Cl茅ment
+    de Saint-Marcq
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitgtr2;
+
+import android.content.Context;
+import android.net.Uri;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitgtr2.AmazfitGTR2FWHelper;
+import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitgtr.AmazfitGTRSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.update.UpdateFirmwareOperation;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.update.UpdateFirmwareOperation2020;
+
+public class AmazfitGTR2Support extends AmazfitGTRSupport {
+
+    @Override
+    public boolean supportsSunriseSunsetWindHumidity() {
+        return true;
+    }
+
+    @Override
+    public HuamiFWHelper createFWHelper(Uri uri, Context context) throws IOException {
+        return new AmazfitGTR2FWHelper(uri, context);
+    }
+
+    @Override
+    public void onSetCallState(CallSpec callSpec) {
+        onSetCallStateNew(callSpec);
+    }
+
+    @Override
+    public UpdateFirmwareOperation createUpdateFirmwareOperation(Uri uri) {
+        return new UpdateFirmwareOperation2020(uri, this);
+    }
+
+    @Override
+    public int getActivitySampleSize() {
+        return 8;
+    }
+
+    @Override
+    protected AmazfitGTR2Support setDisplayItems(TransactionBuilder builder) {
+        setDisplayItemsNew(builder, false, false, R.array.pref_gtsgtr2_display_items_default);
+        return this;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitgtr2/AmazfitGTR2eFirmwareInfo.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitgtr2/AmazfitGTR2eFirmwareInfo.java
new file mode 100644
index 0000000..06662f9
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitgtr2/AmazfitGTR2eFirmwareInfo.java
@@ -0,0 +1,120 @@
+/*  Copyright (C) 2021-2024 Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitgtr2;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareInfo;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareType;
+import nodomain.freeyourgadget.gadgetbridge.util.ArrayUtils;
+
+public class AmazfitGTR2eFirmwareInfo extends HuamiFirmwareInfo {
+    private static final int FW_OFFSET = 3;
+
+    private static final byte[] FW_HEADER = new byte[]{
+            0x20, (byte) 0x99, 0x12, 0x01, 0x08 // completely nonsense probably
+    };
+
+    private static final byte[] GPS_ALMANAC_HEADER = new byte[]{ // probably wrong
+            (byte) 0xa0, (byte) 0x80, 0x08, 0x00, (byte) 0x8b, 0x07
+    };
+
+    private static final byte[] GPS_CEP_HEADER = new byte[]{ // probably wrong
+            0x2a, 0x12, (byte) 0xa0, 0x02
+    };
+
+    // gps detection is totally bogus, just the first 16 bytes
+    private static final byte[][] GPS_HEADERS = {
+            new byte[]{
+                    0x73, 0x75, 0x68, (byte) 0xd0, 0x70, 0x73, (byte) 0xbb, 0x5a,
+                    0x3e, (byte) 0xc3, (byte) 0xd3, 0x09, (byte) 0x9e, 0x1d, (byte) 0xd3, (byte) 0xc9
+            }
+    };
+    private static Map<Integer, String> crcToVersion = new HashMap<>();
+
+    static {
+        // firmware
+
+        // Latin Firmware
+
+        // resources
+
+        // font
+
+        // gps
+        crcToVersion.put(62532, "18344,eb2f43f,126");
+    }
+
+    public AmazfitGTR2eFirmwareInfo(byte[] bytes) {
+        super(bytes);
+    }
+
+    @Override
+    protected HuamiFirmwareType determineFirmwareType(byte[] bytes) {
+        if (ArrayUtils.equals(bytes, NEWRES_HEADER, COMPRESSED_RES_HEADER_OFFSET_NEW)) {
+            return HuamiFirmwareType.RES_COMPRESSED;
+        }
+        if (ArrayUtils.equals(bytes, FW_HEADER, FW_OFFSET)) {
+            if (searchString32BitAligned(bytes, "Amazfit GTR 2e")) {
+                return HuamiFirmwareType.FIRMWARE;
+            }
+            return HuamiFirmwareType.INVALID;
+        }
+        if ((ArrayUtils.startsWith(bytes, UIHH_HEADER) && (bytes[4] == 1 || bytes[4] == 2)) || ArrayUtils.startsWith(bytes, WATCHFACE_HEADER) || ArrayUtils.equals(bytes, WATCHFACE_HEADER, COMPRESSED_RES_HEADER_OFFSET_NEW) || ArrayUtils.equals(bytes, WATCHFACE_HEADER, COMPRESSED_RES_HEADER_OFFSET)) {
+            return HuamiFirmwareType.WATCHFACE;
+        }
+        if (ArrayUtils.startsWith(bytes, NEWFT_HEADER)) {
+            if (bytes[10] == 0x01) {
+                return HuamiFirmwareType.FONT;
+            } else if (bytes[10] == 0x02) {
+                return HuamiFirmwareType.FONT_LATIN;
+            }
+        }
+
+        if (ArrayUtils.startsWith(bytes, GPS_ALMANAC_HEADER)) {
+            return HuamiFirmwareType.GPS_ALMANAC;
+        }
+        if (ArrayUtils.startsWith(bytes, GPS_CEP_HEADER)) {
+            return HuamiFirmwareType.GPS_CEP;
+        }
+
+        if (ArrayUtils.startsWith(bytes, AGPS_UIHH_HEADER)) {
+            return HuamiFirmwareType.AGPS_UIHH;
+        }
+
+        for (byte[] gpsHeader : GPS_HEADERS) {
+            if (ArrayUtils.startsWith(bytes, gpsHeader)) {
+                return HuamiFirmwareType.GPS;
+            }
+        }
+
+        return HuamiFirmwareType.INVALID;
+    }
+
+    @Override
+    public boolean isGenerallyCompatibleWith(GBDevice device) {
+        return isHeaderValid() && device.getType() == DeviceType.AMAZFITGTR2E;
+    }
+
+    @Override
+    protected Map<Integer, String> getCrcMap() {
+        return crcToVersion;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitgtr2/AmazfitGTR2eSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitgtr2/AmazfitGTR2eSupport.java
new file mode 100644
index 0000000..8562503
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitgtr2/AmazfitGTR2eSupport.java
@@ -0,0 +1,33 @@
+/*  Copyright (C) 2021-2024 Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitgtr2;
+
+import android.content.Context;
+import android.net.Uri;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitgtr2.AmazfitGTR2eFWHelper;
+
+public class AmazfitGTR2eSupport extends AmazfitGTR2Support {
+
+    @Override
+    public HuamiFWHelper createFWHelper(Uri uri, Context context) throws IOException {
+        return new AmazfitGTR2eFWHelper(uri, context);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitgts/AmazfitGTSFirmwareInfo.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitgts/AmazfitGTSFirmwareInfo.java
new file mode 100644
index 0000000..f9a0692
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitgts/AmazfitGTSFirmwareInfo.java
@@ -0,0 +1,120 @@
+/*  Copyright (C) 2019-2024 Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitgts;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareInfo;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareType;
+import nodomain.freeyourgadget.gadgetbridge.util.ArrayUtils;
+
+public class AmazfitGTSFirmwareInfo extends HuamiFirmwareInfo {
+    private static final int FW_OFFSET = 3;
+
+    private static final byte[] FW_HEADER = new byte[]{
+            0x20, (byte) 0x99, 0x12, 0x01, 0x08 // completely nonsense probably
+    };
+
+    private static final byte[] GPS_ALMANAC_HEADER = new byte[]{ // probably wrong
+            (byte) 0xa0, (byte) 0x80, 0x08, 0x00, (byte) 0x8b, 0x07
+    };
+
+    private static final byte[] GPS_CEP_HEADER = new byte[]{ // probably wrong
+            0x2a, 0x12, (byte) 0xa0, 0x02
+    };
+
+    // gps detection is totally bogus, just the first 16 bytes
+    private static final byte[][] GPS_HEADERS = {
+            new byte[]{
+                    0x73, 0x75, 0x68, (byte) 0xd0, 0x70, 0x73, (byte) 0xbb, 0x5a,
+                    0x3e, (byte) 0xc3, (byte) 0xd3, 0x09, (byte) 0x9e, 0x1d, (byte) 0xd3, (byte) 0xc9
+            }
+    };
+    private static Map<Integer, String> crcToVersion = new HashMap<>();
+
+    static {
+        // firmware
+
+        // Latin Firmware
+
+        // resources
+
+        // font
+
+        // gps
+        crcToVersion.put(62532, "18344,eb2f43f,126");
+    }
+
+    public AmazfitGTSFirmwareInfo(byte[] bytes) {
+        super(bytes);
+    }
+
+    @Override
+    protected HuamiFirmwareType determineFirmwareType(byte[] bytes) {
+        if (ArrayUtils.equals(bytes, NEWRES_HEADER, COMPRESSED_RES_HEADER_OFFSET_NEW)) {
+            return HuamiFirmwareType.RES_COMPRESSED;
+        }
+        if (ArrayUtils.equals(bytes, FW_HEADER, FW_OFFSET)) {
+            if (searchString32BitAligned(bytes, "Amazfit GTS")) {
+                return HuamiFirmwareType.FIRMWARE;
+            }
+            return HuamiFirmwareType.INVALID;
+        }
+        if (ArrayUtils.startsWith(bytes, WATCHFACE_HEADER) || ArrayUtils.equals(bytes, WATCHFACE_HEADER, COMPRESSED_RES_HEADER_OFFSET_NEW) || ArrayUtils.equals(bytes, WATCHFACE_HEADER, COMPRESSED_RES_HEADER_OFFSET)) {
+            return HuamiFirmwareType.WATCHFACE;
+        }
+        if (ArrayUtils.startsWith(bytes, NEWFT_HEADER)) {
+            if (bytes[10] == 0x01) {
+                return HuamiFirmwareType.FONT;
+            } else if (bytes[10] == 0x02) {
+                return HuamiFirmwareType.FONT_LATIN;
+            }
+        }
+
+        if (ArrayUtils.startsWith(bytes, GPS_ALMANAC_HEADER)) {
+            return HuamiFirmwareType.GPS_ALMANAC;
+        }
+        if (ArrayUtils.startsWith(bytes, GPS_CEP_HEADER)) {
+            return HuamiFirmwareType.GPS_CEP;
+        }
+
+        if (ArrayUtils.startsWith(bytes, AGPS_UIHH_HEADER)) {
+            return HuamiFirmwareType.AGPS_UIHH;
+        }
+
+        for (byte[] gpsHeader : GPS_HEADERS) {
+            if (ArrayUtils.startsWith(bytes, gpsHeader)) {
+                return HuamiFirmwareType.GPS;
+            }
+        }
+
+        return HuamiFirmwareType.INVALID;
+    }
+
+    @Override
+    public boolean isGenerallyCompatibleWith(GBDevice device) {
+        return isHeaderValid() && device.getType() == DeviceType.AMAZFITGTS;
+    }
+
+    @Override
+    protected Map<Integer, String> getCrcMap() {
+        return crcToVersion;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitgts/AmazfitGTSSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitgts/AmazfitGTSSupport.java
new file mode 100644
index 0000000..b001921
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitgts/AmazfitGTSSupport.java
@@ -0,0 +1,86 @@
+/*  Copyright (C) 2019-2024 Andreas Shimokawa, Jos茅 Rebelo, Manuel Ru脽
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitgts;
+
+import android.content.Context;
+import android.net.Uri;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitgts.AmazfitGTSFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitbip.AmazfitBipSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.update.UpdateFirmwareOperation;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.update.UpdateFirmwareOperation2020;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.update.UpdateFirmwareOperationNew;
+import nodomain.freeyourgadget.gadgetbridge.util.Version;
+
+public class AmazfitGTSSupport extends AmazfitBipSupport {
+    private static final Logger LOG = LoggerFactory.getLogger(AmazfitGTSSupport.class);
+
+    @Override
+    public byte getCryptFlags() {
+        return (byte) 0x80;
+    }
+
+    @Override
+    protected byte getAuthFlags() {
+        return 0x00;
+    }
+
+    @Override
+    protected boolean notificationHasExtraHeader() {
+        return true;
+    }
+
+    @Override
+    public HuamiFWHelper createFWHelper(Uri uri, Context context) throws IOException {
+        return new AmazfitGTSFWHelper(uri, context);
+    }
+
+    @Override
+    public UpdateFirmwareOperation createUpdateFirmwareOperation(Uri uri) {
+        Version version = new Version(gbDevice.getFirmwareVersion());
+        if (version.compareTo(new Version("0.1.1.16")) >= 0) {
+            return new UpdateFirmwareOperation2020(uri, this);
+        }
+
+        return new UpdateFirmwareOperationNew(uri, this);
+    }
+
+    @Override
+    protected AmazfitGTSSupport setDisplayItems(TransactionBuilder builder) {
+        setDisplayItemsNew(builder, false, true, R.array.pref_gts_display_items_default);
+        return this;
+    }
+
+    @Override
+    protected void handleDeviceInfo(nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo.DeviceInfo info) {
+        super.handleDeviceInfo(info);
+        if (gbDevice.getFirmwareVersion() != null) {
+            Version version = new Version(gbDevice.getFirmwareVersion());
+            if (version.compareTo(new Version("0.0.9.00")) > 0) {
+                mActivitySampleSize = 8;
+            }
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitgts2/AmazfitGTS2FirmwareInfo.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitgts2/AmazfitGTS2FirmwareInfo.java
new file mode 100644
index 0000000..f6a8847
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitgts2/AmazfitGTS2FirmwareInfo.java
@@ -0,0 +1,120 @@
+/*  Copyright (C) 2020-2024 Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitgts2;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareInfo;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareType;
+import nodomain.freeyourgadget.gadgetbridge.util.ArrayUtils;
+
+public class AmazfitGTS2FirmwareInfo extends HuamiFirmwareInfo {
+    private static final int FW_OFFSET = 3;
+
+    private static final byte[] FW_HEADER = new byte[]{
+            0x20, (byte) 0x99, 0x12, 0x01, 0x08 // completely nonsense probably
+    };
+
+    private static final byte[] GPS_ALMANAC_HEADER = new byte[]{ // probably wrong
+            (byte) 0xa0, (byte) 0x80, 0x08, 0x00, (byte) 0x8b, 0x07
+    };
+
+    private static final byte[] GPS_CEP_HEADER = new byte[]{ // probably wrong
+            0x2a, 0x12, (byte) 0xa0, 0x02
+    };
+
+    // gps detection is totally bogus, just the first 16 bytes
+    private static final byte[][] GPS_HEADERS = {
+            new byte[]{
+                    0x73, 0x75, 0x68, (byte) 0xd0, 0x70, 0x73, (byte) 0xbb, 0x5a,
+                    0x3e, (byte) 0xc3, (byte) 0xd3, 0x09, (byte) 0x9e, 0x1d, (byte) 0xd3, (byte) 0xc9
+            }
+    };
+    private static Map<Integer, String> crcToVersion = new HashMap<>();
+
+    static {
+        // firmware
+
+        // Latin Firmware
+
+        // resources
+
+        // font
+
+        // gps
+        crcToVersion.put(62532, "18344,eb2f43f,126");
+    }
+
+    public AmazfitGTS2FirmwareInfo(byte[] bytes) {
+        super(bytes);
+    }
+
+    @Override
+    protected HuamiFirmwareType determineFirmwareType(byte[] bytes) {
+        if (ArrayUtils.equals(bytes, NEWRES_HEADER, COMPRESSED_RES_HEADER_OFFSET_NEW)) {
+            return HuamiFirmwareType.RES_COMPRESSED;
+        }
+        if (ArrayUtils.equals(bytes, FW_HEADER, FW_OFFSET)) {
+            if (searchString32BitAligned(bytes, "Amazfit GTS 2")) {
+                return HuamiFirmwareType.FIRMWARE;
+            }
+            return HuamiFirmwareType.INVALID;
+        }
+        if ((ArrayUtils.startsWith(bytes, UIHH_HEADER) && (bytes[4] == 1 || bytes[4] == 2)) || ArrayUtils.startsWith(bytes, WATCHFACE_HEADER) || ArrayUtils.equals(bytes, WATCHFACE_HEADER, COMPRESSED_RES_HEADER_OFFSET_NEW) || ArrayUtils.equals(bytes, WATCHFACE_HEADER, COMPRESSED_RES_HEADER_OFFSET)) {
+            return HuamiFirmwareType.WATCHFACE;
+        }
+        if (ArrayUtils.startsWith(bytes, NEWFT_HEADER)) {
+            if (bytes[10] == 0x01) {
+                return HuamiFirmwareType.FONT;
+            } else if (bytes[10] == 0x02) {
+                return HuamiFirmwareType.FONT_LATIN;
+            }
+        }
+
+        if (ArrayUtils.startsWith(bytes, GPS_ALMANAC_HEADER)) {
+            return HuamiFirmwareType.GPS_ALMANAC;
+        }
+        if (ArrayUtils.startsWith(bytes, GPS_CEP_HEADER)) {
+            return HuamiFirmwareType.GPS_CEP;
+        }
+
+        if (ArrayUtils.startsWith(bytes, AGPS_UIHH_HEADER)) {
+            return HuamiFirmwareType.AGPS_UIHH;
+        }
+
+        for (byte[] gpsHeader : GPS_HEADERS) {
+            if (ArrayUtils.startsWith(bytes, gpsHeader)) {
+                return HuamiFirmwareType.GPS;
+            }
+        }
+
+        return HuamiFirmwareType.INVALID;
+    }
+
+    @Override
+    public boolean isGenerallyCompatibleWith(GBDevice device) {
+        return isHeaderValid() && device.getType() == DeviceType.AMAZFITGTS2;
+    }
+
+    @Override
+    protected Map<Integer, String> getCrcMap() {
+        return crcToVersion;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitgts2/AmazfitGTS2MiniFirmwareInfo.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitgts2/AmazfitGTS2MiniFirmwareInfo.java
new file mode 100644
index 0000000..0c06c40
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitgts2/AmazfitGTS2MiniFirmwareInfo.java
@@ -0,0 +1,120 @@
+/*  Copyright (C) 2021-2024 Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitgts2;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareInfo;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareType;
+import nodomain.freeyourgadget.gadgetbridge.util.ArrayUtils;
+
+public class AmazfitGTS2MiniFirmwareInfo extends HuamiFirmwareInfo {
+    private static final int FW_OFFSET = 3;
+
+    private static final byte[] FW_HEADER = new byte[]{
+            0x20, (byte) 0x99, 0x12, 0x01, 0x08 // completely nonsense probably
+    };
+
+    private static final byte[] GPS_ALMANAC_HEADER = new byte[]{ // probably wrong
+            (byte) 0xa0, (byte) 0x80, 0x08, 0x00, (byte) 0x8b, 0x07
+    };
+
+    private static final byte[] GPS_CEP_HEADER = new byte[]{ // probably wrong
+            0x2a, 0x12, (byte) 0xa0, 0x02
+    };
+
+    // gps detection is totally bogus, just the first 16 bytes
+    private static final byte[][] GPS_HEADERS = {
+            new byte[]{
+                    0x73, 0x75, 0x68, (byte) 0xd0, 0x70, 0x73, (byte) 0xbb, 0x5a,
+                    0x3e, (byte) 0xc3, (byte) 0xd3, 0x09, (byte) 0x9e, 0x1d, (byte) 0xd3, (byte) 0xc9
+            }
+    };
+    private static Map<Integer, String> crcToVersion = new HashMap<>();
+
+    static {
+        // firmware
+
+        // Latin Firmware
+
+        // resources
+
+        // font
+
+        // gps
+        crcToVersion.put(62532, "18344,eb2f43f,126");
+    }
+
+    public AmazfitGTS2MiniFirmwareInfo(byte[] bytes) {
+        super(bytes);
+    }
+
+    @Override
+    protected HuamiFirmwareType determineFirmwareType(byte[] bytes) {
+        if (ArrayUtils.equals(bytes, NEWRES_HEADER, COMPRESSED_RES_HEADER_OFFSET_NEW)) {
+            return HuamiFirmwareType.RES_COMPRESSED;
+        }
+        if (ArrayUtils.equals(bytes, FW_HEADER, FW_OFFSET)) {
+            if (searchString32BitAligned(bytes, "Amazfit GTS 2 Mini")) {
+                return HuamiFirmwareType.FIRMWARE;
+            }
+            return HuamiFirmwareType.INVALID;
+        }
+        if ((ArrayUtils.startsWith(bytes, UIHH_HEADER) && (bytes[4] == 1 || bytes[4] == 2)) || ArrayUtils.startsWith(bytes, WATCHFACE_HEADER) || ArrayUtils.equals(bytes, WATCHFACE_HEADER, COMPRESSED_RES_HEADER_OFFSET_NEW) || ArrayUtils.equals(bytes, WATCHFACE_HEADER, COMPRESSED_RES_HEADER_OFFSET)) {
+            return HuamiFirmwareType.WATCHFACE;
+        }
+        if (ArrayUtils.startsWith(bytes, NEWFT_HEADER)) {
+            if (bytes[10] == 0x01) {
+                return HuamiFirmwareType.FONT;
+            } else if (bytes[10] == 0x02) {
+                return HuamiFirmwareType.FONT_LATIN;
+            }
+        }
+
+        if (ArrayUtils.startsWith(bytes, GPS_ALMANAC_HEADER)) {
+            return HuamiFirmwareType.GPS_ALMANAC;
+        }
+        if (ArrayUtils.startsWith(bytes, GPS_CEP_HEADER)) {
+            return HuamiFirmwareType.GPS_CEP;
+        }
+
+        if (ArrayUtils.startsWith(bytes, AGPS_UIHH_HEADER)) {
+            return HuamiFirmwareType.AGPS_UIHH;
+        }
+
+        for (byte[] gpsHeader : GPS_HEADERS) {
+            if (ArrayUtils.startsWith(bytes, gpsHeader)) {
+                return HuamiFirmwareType.GPS;
+            }
+        }
+
+        return HuamiFirmwareType.INVALID;
+    }
+
+    @Override
+    public boolean isGenerallyCompatibleWith(GBDevice device) {
+        return isHeaderValid() && device.getType() == DeviceType.AMAZFITGTS2_MINI;
+    }
+
+    @Override
+    protected Map<Integer, String> getCrcMap() {
+        return crcToVersion;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitgts2/AmazfitGTS2MiniSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitgts2/AmazfitGTS2MiniSupport.java
new file mode 100644
index 0000000..41ce7b5
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitgts2/AmazfitGTS2MiniSupport.java
@@ -0,0 +1,52 @@
+/*  Copyright (C) 2021-2024 Andreas Shimokawa, Jos茅 Rebelo, musover
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitgts2;
+
+import android.content.Context;
+import android.net.Uri;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitgts2.AmazfitGTS2MiniFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport;
+
+public class AmazfitGTS2MiniSupport extends AmazfitGTS2Support {
+
+    private static final Logger LOG = LoggerFactory.getLogger(AmazfitGTS2MiniSupport.class);
+
+    @Override
+    protected HuamiSupport setLanguage(TransactionBuilder builder) {
+        return setLanguageByIdNew(builder);
+    }
+
+    @Override
+    public HuamiFWHelper createFWHelper(Uri uri, Context context) throws IOException {
+        return new AmazfitGTS2MiniFWHelper(uri, context);
+    }
+
+    @Override
+    public String getNotificationBody(NotificationSpec notificationSpec) {
+        // #2987
+        return getNotificationBodyCheckAcceptsSender(notificationSpec);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitgts2/AmazfitGTS2Support.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitgts2/AmazfitGTS2Support.java
new file mode 100644
index 0000000..0cd6fe0
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitgts2/AmazfitGTS2Support.java
@@ -0,0 +1,66 @@
+/*  Copyright (C) 2020-2024 Andreas Shimokawa, Vianney le Cl茅ment de
+    Saint-Marcq
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitgts2;
+
+import android.content.Context;
+import android.net.Uri;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitgts2.AmazfitGTS2FWHelper;
+import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitgts.AmazfitGTSSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.update.UpdateFirmwareOperation;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.update.UpdateFirmwareOperation2020;
+
+public class AmazfitGTS2Support extends AmazfitGTSSupport {
+
+    @Override
+    public boolean supportsSunriseSunsetWindHumidity() {
+        return true;
+    }
+
+    @Override
+    public HuamiFWHelper createFWHelper(Uri uri, Context context) throws IOException {
+        return new AmazfitGTS2FWHelper(uri, context);
+    }
+
+    @Override
+    public void onSetCallState(CallSpec callSpec) {
+        onSetCallStateNew(callSpec);
+    }
+
+    @Override
+    public UpdateFirmwareOperation createUpdateFirmwareOperation(Uri uri) {
+        return new UpdateFirmwareOperation2020(uri, this);
+    }
+
+    @Override
+    public int getActivitySampleSize() {
+        return 8;
+    }
+
+    @Override
+    protected AmazfitGTS2Support setDisplayItems(TransactionBuilder builder) {
+        setDisplayItemsNew(builder, false, false, R.array.pref_gtsgtr2_display_items_default);
+        return this;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitgts2/AmazfitGTS2eFirmwareInfo.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitgts2/AmazfitGTS2eFirmwareInfo.java
new file mode 100644
index 0000000..ec2845a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitgts2/AmazfitGTS2eFirmwareInfo.java
@@ -0,0 +1,120 @@
+/*  Copyright (C) 2021-2024 Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitgts2;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareInfo;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareType;
+import nodomain.freeyourgadget.gadgetbridge.util.ArrayUtils;
+
+public class AmazfitGTS2eFirmwareInfo extends HuamiFirmwareInfo {
+    private static final int FW_OFFSET = 3;
+
+    private static final byte[] FW_HEADER = new byte[]{
+            0x20, (byte) 0x99, 0x12, 0x01, 0x08 // completely nonsense probably
+    };
+
+    private static final byte[] GPS_ALMANAC_HEADER = new byte[]{ // probably wrong
+            (byte) 0xa0, (byte) 0x80, 0x08, 0x00, (byte) 0x8b, 0x07
+    };
+
+    private static final byte[] GPS_CEP_HEADER = new byte[]{ // probably wrong
+            0x2a, 0x12, (byte) 0xa0, 0x02
+    };
+
+    // gps detection is totally bogus, just the first 16 bytes
+    private static final byte[][] GPS_HEADERS = {
+            new byte[]{
+                    0x73, 0x75, 0x68, (byte) 0xd0, 0x70, 0x73, (byte) 0xbb, 0x5a,
+                    0x3e, (byte) 0xc3, (byte) 0xd3, 0x09, (byte) 0x9e, 0x1d, (byte) 0xd3, (byte) 0xc9
+            }
+    };
+    private static Map<Integer, String> crcToVersion = new HashMap<>();
+
+    static {
+        // firmware
+
+        // Latin Firmware
+
+        // resources
+
+        // font
+
+        // gps
+        crcToVersion.put(62532, "18344,eb2f43f,126");
+    }
+
+    public AmazfitGTS2eFirmwareInfo(byte[] bytes) {
+        super(bytes);
+    }
+
+    @Override
+    protected HuamiFirmwareType determineFirmwareType(byte[] bytes) {
+        if (ArrayUtils.equals(bytes, NEWRES_HEADER, COMPRESSED_RES_HEADER_OFFSET_NEW)) {
+            return HuamiFirmwareType.RES_COMPRESSED;
+        }
+        if (ArrayUtils.equals(bytes, FW_HEADER, FW_OFFSET)) {
+            if (searchString32BitAligned(bytes, "Amazfit GTS 2e")) {
+                return HuamiFirmwareType.FIRMWARE;
+            }
+            return HuamiFirmwareType.INVALID;
+        }
+        if ((ArrayUtils.startsWith(bytes, UIHH_HEADER) && (bytes[4] == 1 || bytes[4] == 2)) || ArrayUtils.startsWith(bytes, WATCHFACE_HEADER) || ArrayUtils.equals(bytes, WATCHFACE_HEADER, COMPRESSED_RES_HEADER_OFFSET_NEW) || ArrayUtils.equals(bytes, WATCHFACE_HEADER, COMPRESSED_RES_HEADER_OFFSET)) {
+            return HuamiFirmwareType.WATCHFACE;
+        }
+        if (ArrayUtils.startsWith(bytes, NEWFT_HEADER)) {
+            if (bytes[10] == 0x01) {
+                return HuamiFirmwareType.FONT;
+            } else if (bytes[10] == 0x02) {
+                return HuamiFirmwareType.FONT_LATIN;
+            }
+        }
+
+        if (ArrayUtils.startsWith(bytes, GPS_ALMANAC_HEADER)) {
+            return HuamiFirmwareType.GPS_ALMANAC;
+        }
+        if (ArrayUtils.startsWith(bytes, GPS_CEP_HEADER)) {
+            return HuamiFirmwareType.GPS_CEP;
+        }
+
+        if (ArrayUtils.startsWith(bytes, AGPS_UIHH_HEADER)) {
+            return HuamiFirmwareType.AGPS_UIHH;
+        }
+
+        for (byte[] gpsHeader : GPS_HEADERS) {
+            if (ArrayUtils.startsWith(bytes, gpsHeader)) {
+                return HuamiFirmwareType.GPS;
+            }
+        }
+
+        return HuamiFirmwareType.INVALID;
+    }
+
+    @Override
+    public boolean isGenerallyCompatibleWith(GBDevice device) {
+        return isHeaderValid() && device.getType() == DeviceType.AMAZFITGTS2E;
+    }
+
+    @Override
+    protected Map<Integer, String> getCrcMap() {
+        return crcToVersion;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitgts2/AmazfitGTS2eSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitgts2/AmazfitGTS2eSupport.java
new file mode 100644
index 0000000..7079aef
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitgts2/AmazfitGTS2eSupport.java
@@ -0,0 +1,34 @@
+/*  Copyright (C) 2021-2024 Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitgts2;
+
+import android.content.Context;
+import android.net.Uri;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitgts2.AmazfitGTS2eFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitgts2.AmazfitGTS2Support;
+
+public class AmazfitGTS2eSupport extends AmazfitGTS2Support {
+
+    @Override
+    public HuamiFWHelper createFWHelper(Uri uri, Context context) throws IOException {
+        return new AmazfitGTS2eFWHelper(uri, context);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitneo/AmazfitNeoFirmwareInfo.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitneo/AmazfitNeoFirmwareInfo.java
new file mode 100644
index 0000000..633e8b9
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitneo/AmazfitNeoFirmwareInfo.java
@@ -0,0 +1,79 @@
+/*  Copyright (C) 2021-2024 Andreas Shimokawa, xaos
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitneo;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareInfo;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareType;
+import nodomain.freeyourgadget.gadgetbridge.util.ArrayUtils;
+
+public class AmazfitNeoFirmwareInfo extends HuamiFirmwareInfo {
+
+    public static final byte[] FW_HEADER = new byte[]{
+            // this might be incorrect but found in 1.1.1.18 and 1.1.2.52
+            0x06, 0x68, 0x45, 0x68, (byte) 0x84, 0x68, (byte) 0xC1, 0x68, 0x02, (byte) 0x91, 0x01, 0x69, 0x01, (byte) 0x91, 0x41, 0x69
+    };
+
+    public static final int FW_HEADER_OFFSET = 0x90;
+
+    private static Map<Integer, String> crcToVersion = new HashMap<>();
+
+    static {
+        // no known fw so far
+    }
+
+    public AmazfitNeoFirmwareInfo(byte[] bytes) {
+        super(bytes);
+    }
+
+    @Override
+    protected HuamiFirmwareType determineFirmwareType(byte[] bytes) {
+
+        if (ArrayUtils.equals(bytes, FW_HEADER, FW_HEADER_OFFSET)) {
+            if (searchString32BitAligned(bytes, "Amazfit Neo")) {
+                return HuamiFirmwareType.FIRMWARE;
+            }
+            return HuamiFirmwareType.INVALID;
+        }
+        if (ArrayUtils.startsWith(bytes, FT_HEADER)) {
+            return HuamiFirmwareType.FONT;
+        }
+
+        return HuamiFirmwareType.INVALID;
+    }
+
+    @Override
+    public boolean isGenerallyCompatibleWith(GBDevice device) {
+        return isHeaderValid() && device.getType() == DeviceType.AMAZFITNEO;
+    }
+
+    @Override
+    protected Map<Integer, String> getCrcMap() {
+        return crcToVersion;
+    }
+
+    @Override
+    protected String searchFirmwareVersion(byte[] fwbytes) {
+        // Not implemented
+        return null;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitneo/AmazfitNeoSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitneo/AmazfitNeoSupport.java
new file mode 100644
index 0000000..b75e4bb
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitneo/AmazfitNeoSupport.java
@@ -0,0 +1,184 @@
+/*  Copyright (C) 2021-2024 Andreas Shimokawa, Jos茅 Rebelo, NekoBox, Petr
+    Van臎k, xaos
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitneo;
+
+import android.content.Context;
+import android.net.Uri;
+
+import org.apache.commons.lang3.ArrayUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitneo.AmazfitNeoFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
+import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.miband5.MiBand5Support;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.update.UpdateFirmwareOperation;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.update.UpdateFirmwareOperation2020;
+
+
+public class AmazfitNeoSupport extends MiBand5Support {
+    private static final Logger LOG = LoggerFactory.getLogger(AmazfitNeoSupport.class);
+
+    private boolean heartRateRealtimeStarted = false;
+    private boolean heartRateTestStarted = false;
+    private byte heartRateRealtimeCount = 0;
+
+    @Override
+    protected boolean notificationHasExtraHeader() {
+        return false;
+    }
+
+    @Override
+    protected AmazfitNeoSupport setDisplayItems(TransactionBuilder builder) {
+        setDisplayItemsNew(builder, false, false, R.array.pref_neo_display_items_default);
+        return this;
+    }
+
+    @Override
+    protected AmazfitNeoSupport setFitnessGoal(TransactionBuilder builder) {
+        LOG.info("Attempting to set Fitness Goal...");
+        setNeoFitnessGoal(builder);
+        return this;
+    }
+
+    @Override
+    protected AmazfitNeoSupport setGoalNotification(TransactionBuilder builder) {
+        LOG.info("Attempting to set goal notification...");
+        setNeoFitnessGoal(builder);
+        return this;
+    }
+
+    private void setNeoFitnessGoal(TransactionBuilder builder) {
+        int fitnessGoal = GBApplication.getPrefs().getInt(ActivityUser.PREF_USER_STEPS_GOAL, ActivityUser.defaultUserStepsGoal);
+        boolean fitnessGoalNotification = HuamiCoordinator.getGoalNotification(gbDevice.getAddress());
+        LOG.info("Setting Amazfit Neo fitness goal to: " + fitnessGoal + ", notification: " + fitnessGoalNotification);
+        byte[] bytes = ArrayUtils.addAll(
+                new byte[] { 0x3a, 1, 0, 0, 0, (byte) (fitnessGoalNotification ? 1 : 0 ) },
+                BLETypeConversions.fromUint16(fitnessGoal));
+        bytes = ArrayUtils.addAll(bytes,
+                HuamiService.COMMAND_SET_FITNESS_GOAL_END);
+        writeToChunked(builder, 2, bytes);
+    }
+
+    @Override
+    protected AmazfitNeoSupport requestAlarms(TransactionBuilder builder) {
+        return this; //Neo always returns response array with '03' in it which marks alarms unused on connect
+    }
+
+    @Override
+    public boolean supportsHourlyChime() { return true; }
+
+    @Override
+    protected AmazfitNeoSupport setHeartrateSleepSupport(TransactionBuilder builder) {
+        final boolean enableHrSleepSupport = MiBandCoordinator.getHeartrateSleepSupport(gbDevice.getAddress());
+        LOG.info("Setting Amazfit Neo heartrate sleep support to " + enableHrSleepSupport);
+        writeToConfiguration(builder, new byte[] {0x06, 0x3c, 0x00, (byte) (enableHrSleepSupport ? 1 : 0 )});
+        return this;
+    }
+
+    @Override
+    public void onHeartRateTest() {
+        if (characteristicHRControlPoint == null) {
+            return;
+        }
+        try {
+            TransactionBuilder builder = performInitialized("HeartRateTest");
+            enableNotifyHeartRateMeasurements(true, builder);
+            builder.write(characteristicHRControlPoint, new byte[]{ 0x15, 0x01, 0x01 });
+            builder.queue(getQueue());
+            heartRateTestStarted = true;
+        } catch (IOException ex) {
+            LOG.error("Unable to read heart rate from Huami device", ex);
+        }
+    }
+
+    @Override
+    public void onEnableRealtimeHeartRateMeasurement(boolean enable) {
+        heartRateTestStarted = false;
+        if (characteristicHRControlPoint == null) {
+            return;
+        }
+        try {
+            TransactionBuilder builder = performInitialized("Enable realtime heart rate measurement");
+            enableNotifyHeartRateMeasurements(enable, builder);
+            if (enable) {
+                if (heartRateRealtimeStarted) {
+                    if(heartRateRealtimeCount >= 10) {
+                        builder.write(characteristicHRControlPoint, new byte[]{ 0x16 }); //send continue every 10 seconds
+                        heartRateRealtimeCount = 0;
+                    }
+                    heartRateRealtimeCount++;
+                }
+                else {
+                    builder.write(characteristicHRControlPoint, new byte[]{ 0x15, 0x01, 0x01 });
+                    heartRateRealtimeCount = 10; //sometimes first measurement times out, send first keep alive immediately
+                }
+                heartRateRealtimeStarted = true;
+            } else {
+                builder.write(characteristicHRControlPoint, new byte[] { 0x15, 0x01, 0x00 });
+                heartRateRealtimeStarted = false;
+            }
+            builder.queue(getQueue());
+            enableRealtimeSamplesTimer(enable);
+        } catch (IOException ex) {
+            LOG.error("Unable to enable realtime heart rate measurement", ex);
+        }
+    }
+
+    @Override
+    protected void handleHeartrate(byte[] value) {
+        super.handleHeartrate(value);
+        if (heartRateTestStarted)
+            onEnableRealtimeHeartRateMeasurement(false); //stop test after single measurement, disable HR notify
+    }
+
+    @Override
+    public void onSetCallState(CallSpec callSpec) {
+        if (callSpec.name != null)
+            if (callSpec.name.length() > 7) //if char 0 is number, +, ( or ) and char 1 is a number it renders in bold scrolling font with letters stripped, else it renders in thin font and displays 'incoming call' if the lengh is > 7
+                if (!((Character.isDigit(callSpec.name.charAt(0)) || callSpec.name.charAt(0) == '+' || callSpec.name.charAt(0) == '(' || callSpec.name.charAt(0) == ')') && Character.isDigit(callSpec.name.charAt(1))))
+                    callSpec.name = callSpec.name.substring(0, 7);
+        super.onSetCallState(callSpec);
+    }
+
+    @Override
+    protected int getFindDeviceInterval() {
+        return 10000; //Neo find command lasts 10 seconds
+    }
+
+    @Override
+    public HuamiFWHelper createFWHelper(Uri uri, Context context) throws IOException {
+        return new AmazfitNeoFWHelper(uri, context);
+    }
+
+    @Override
+    public UpdateFirmwareOperation createUpdateFirmwareOperation(Uri uri) {
+        return new UpdateFirmwareOperation2020(uri, this);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitpop/AmazfitPopFirmwareInfo.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitpop/AmazfitPopFirmwareInfo.java
new file mode 100644
index 0000000..8417344
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitpop/AmazfitPopFirmwareInfo.java
@@ -0,0 +1,81 @@
+/*  Copyright (C) 2022-2024 Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitpop;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareInfo;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.miband4.MiBand4FirmwareInfo;
+import nodomain.freeyourgadget.gadgetbridge.util.ArrayUtils;
+
+public class AmazfitPopFirmwareInfo extends HuamiFirmwareInfo {
+
+    private static Map<Integer, String> crcToVersion = new HashMap<>();
+
+    static {
+        // fw
+
+        // resources
+
+        // font
+    }
+
+    public AmazfitPopFirmwareInfo(byte[] bytes) {
+        super(bytes);
+    }
+
+    @Override
+    protected HuamiFirmwareType determineFirmwareType(byte[] bytes) {
+
+        if (ArrayUtils.equals(bytes, MiBand4FirmwareInfo.FW_HEADER, MiBand4FirmwareInfo.FW_HEADER_OFFSET)) {
+            if (searchString32BitAligned(bytes, "\0\0Amazfit Pop")) {
+                return HuamiFirmwareType.FIRMWARE;
+            }
+            return HuamiFirmwareType.INVALID;
+        }
+
+        if (ArrayUtils.startsWith(bytes,NEWRES_HEADER)) {
+            return HuamiFirmwareType.RES;
+        }
+
+        if (ArrayUtils.startsWith(bytes, UIHH_HEADER) && (bytes[4] == 1 || bytes[4] == 2)) {
+            return HuamiFirmwareType.WATCHFACE;
+        }
+
+        if (ArrayUtils.startsWith(bytes, NEWFT_HEADER)) {
+            if (bytes[10] == 0x01 || bytes[10] == 0x06 || bytes[10] == 0x03) {
+                return HuamiFirmwareType.FONT;
+            }
+        }
+
+        return HuamiFirmwareType.INVALID;
+    }
+
+    @Override
+    public boolean isGenerallyCompatibleWith(GBDevice device) {
+        return isHeaderValid() && device.getType() == DeviceType.AMAZFITPOP;
+    }
+
+    @Override
+    protected Map<Integer, String> getCrcMap() {
+        return crcToVersion;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitpop/AmazfitPopSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitpop/AmazfitPopSupport.java
new file mode 100644
index 0000000..5eb8c68
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitpop/AmazfitPopSupport.java
@@ -0,0 +1,33 @@
+/*  Copyright (C) 2022-2024 Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitpop;
+
+import android.content.Context;
+import android.net.Uri;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitpop.AmazfitPopFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitbipu.AmazfitBipUSupport;
+
+public class AmazfitPopSupport extends AmazfitBipUSupport {
+    @Override
+    public HuamiFWHelper createFWHelper(Uri uri, Context context) throws IOException {
+        return new AmazfitPopFWHelper(uri, context);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitpoppro/AmazfitPopProFirmwareInfo.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitpoppro/AmazfitPopProFirmwareInfo.java
new file mode 100644
index 0000000..2d9d4ba
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitpoppro/AmazfitPopProFirmwareInfo.java
@@ -0,0 +1,81 @@
+/*  Copyright (C) 2022-2024 Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitpoppro;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareInfo;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.miband4.MiBand4FirmwareInfo;
+import nodomain.freeyourgadget.gadgetbridge.util.ArrayUtils;
+
+public class AmazfitPopProFirmwareInfo extends HuamiFirmwareInfo {
+
+    private static Map<Integer, String> crcToVersion = new HashMap<>();
+
+    static {
+        // fw
+
+        // resources
+
+        // font
+    }
+
+    public AmazfitPopProFirmwareInfo(byte[] bytes) {
+        super(bytes);
+    }
+
+    @Override
+    protected HuamiFirmwareType determineFirmwareType(byte[] bytes) {
+
+        if (ArrayUtils.equals(bytes, MiBand4FirmwareInfo.FW_HEADER, MiBand4FirmwareInfo.FW_HEADER_OFFSET)) {
+            if (searchString32BitAligned(bytes, "\0\0Amazfit Pop Pro")) {
+                return HuamiFirmwareType.FIRMWARE;
+            }
+            return HuamiFirmwareType.INVALID;
+        }
+
+        if (ArrayUtils.startsWith(bytes,NEWRES_HEADER)) {
+            return HuamiFirmwareType.RES;
+        }
+
+        if (ArrayUtils.startsWith(bytes, UIHH_HEADER) && (bytes[4] == 1 || bytes[4] == 2)) {
+            return HuamiFirmwareType.WATCHFACE;
+        }
+
+        if (ArrayUtils.startsWith(bytes, NEWFT_HEADER)) {
+            if (bytes[10] == 0x01 || bytes[10] == 0x06 || bytes[10] == 0x03) {
+                return HuamiFirmwareType.FONT;
+            }
+        }
+
+        return HuamiFirmwareType.INVALID;
+    }
+
+    @Override
+    public boolean isGenerallyCompatibleWith(GBDevice device) {
+        return isHeaderValid() && device.getType() == DeviceType.AMAZFITPOPPRO;
+    }
+
+    @Override
+    protected Map<Integer, String> getCrcMap() {
+        return crcToVersion;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitpoppro/AmazfitPopProSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitpoppro/AmazfitPopProSupport.java
new file mode 100644
index 0000000..4f1fc1f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitpoppro/AmazfitPopProSupport.java
@@ -0,0 +1,33 @@
+/*  Copyright (C) 2022-2024 Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitpoppro;
+
+import android.content.Context;
+import android.net.Uri;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitpoppro.AmazfitPopProFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitbipupro.AmazfitBipUProSupport;
+
+public class AmazfitPopProSupport extends AmazfitBipUProSupport {
+    @Override
+    public HuamiFWHelper createFWHelper(Uri uri, Context context) throws IOException {
+        return new AmazfitPopProFWHelper(uri, context);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfittrex/AmazfitTRexFirmwareInfo.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfittrex/AmazfitTRexFirmwareInfo.java
new file mode 100644
index 0000000..a7e4ec7
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfittrex/AmazfitTRexFirmwareInfo.java
@@ -0,0 +1,116 @@
+/*  Copyright (C) 2020-2024 Dmytro Bielik
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfittrex;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareInfo;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareType;
+import nodomain.freeyourgadget.gadgetbridge.util.ArrayUtils;
+
+public class AmazfitTRexFirmwareInfo extends HuamiFirmwareInfo {
+    private static final int FW_OFFSET = 3;
+
+    private static final byte[] FW_HEADER = new byte[]{
+            0x20, (byte) 0x99, 0x12, 0x01, 0x08 // completely nonsense probably
+    };
+
+    private static final byte[] GPS_ALMANAC_HEADER = new byte[]{ // probably wrong
+            (byte) 0xa0, (byte) 0x80, 0x08, 0x00, (byte) 0x8b, 0x07
+    };
+
+    private static final byte[] GPS_CEP_HEADER = new byte[]{ // probably wrong
+            0x2a, 0x12, (byte) 0xa0, 0x02
+    };
+
+    // gps detection is totally bogus, just the first 16 bytes
+    private static final byte[][] GPS_HEADERS = {
+            new byte[]{
+                    0x73, 0x75, 0x68, (byte) 0xd0, 0x70, 0x73, (byte) 0xbb, 0x5a,
+                    0x3e, (byte) 0xc3, (byte) 0xd3, 0x09, (byte) 0x9e, 0x1d, (byte) 0xd3, (byte) 0xc9
+            }
+    };
+    private static Map<Integer, String> crcToVersion = new HashMap<>();
+
+    static {
+        // firmware
+
+        // Latin Firmware
+
+        // resources
+
+        // font
+
+        // gps
+        crcToVersion.put(62532, "18344,eb2f43f,126");
+    }
+
+    public AmazfitTRexFirmwareInfo(byte[] bytes) {
+        super(bytes);
+    }
+
+    @Override
+    protected HuamiFirmwareType determineFirmwareType(byte[] bytes) {
+        if (ArrayUtils.equals(bytes, NEWRES_HEADER, COMPRESSED_RES_HEADER_OFFSET_NEW)) {
+            return HuamiFirmwareType.RES_COMPRESSED;
+        }
+        if (ArrayUtils.equals(bytes, FW_HEADER, FW_OFFSET)) {
+            if (searchString32BitAligned(bytes, "Amazfit T-Rex")) {
+                return HuamiFirmwareType.FIRMWARE;
+            }
+            return HuamiFirmwareType.INVALID;
+        }
+        if (ArrayUtils.startsWith(bytes, WATCHFACE_HEADER) || ArrayUtils.equals(bytes, WATCHFACE_HEADER, COMPRESSED_RES_HEADER_OFFSET_NEW) || ArrayUtils.equals(bytes, WATCHFACE_HEADER, COMPRESSED_RES_HEADER_OFFSET)) {
+            return HuamiFirmwareType.WATCHFACE;
+        }
+        if (ArrayUtils.startsWith(bytes, NEWFT_HEADER)) {
+            if (bytes[10] == 0x01) {
+                return HuamiFirmwareType.FONT;
+            } else if (bytes[10] == 0x02) {
+                return HuamiFirmwareType.FONT_LATIN;
+            }
+        }
+
+        if (ArrayUtils.startsWith(bytes, GPS_ALMANAC_HEADER)) {
+            return HuamiFirmwareType.GPS_ALMANAC;
+        }
+        if (ArrayUtils.startsWith(bytes, GPS_CEP_HEADER)) {
+            return HuamiFirmwareType.GPS_CEP;
+        }
+
+        for (byte[] gpsHeader : GPS_HEADERS) {
+            if (ArrayUtils.startsWith(bytes, gpsHeader)) {
+                return HuamiFirmwareType.GPS;
+            }
+        }
+
+        return HuamiFirmwareType.INVALID;
+    }
+
+    @Override
+    public boolean isGenerallyCompatibleWith(GBDevice device) {
+        return isHeaderValid() && device.getType() == DeviceType.AMAZFITTREX;
+    }
+
+    @Override
+    protected Map<Integer, String> getCrcMap() {
+        return crcToVersion;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfittrex/AmazfitTRexSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfittrex/AmazfitTRexSupport.java
new file mode 100644
index 0000000..e9b39e3
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfittrex/AmazfitTRexSupport.java
@@ -0,0 +1,43 @@
+/*  Copyright (C) 2020-2024 Andreas Shimokawa, Dmytro Bielik
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfittrex;
+
+import android.content.Context;
+import android.net.Uri;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfittrex.AmazfitTRexFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitgts.AmazfitGTSSupport;
+
+public class AmazfitTRexSupport extends AmazfitGTSSupport {
+
+    @Override
+    public HuamiFWHelper createFWHelper(Uri uri, Context context) throws IOException {
+        return new AmazfitTRexFWHelper(uri, context);
+    }
+
+    @Override
+    protected AmazfitTRexSupport setDisplayItems(TransactionBuilder builder) {
+        setDisplayItemsNew(builder, false, true, R.array.pref_trex_display_items_default);
+        return this;
+    }
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfittrexpro/AmazfitTRexProFirmwareInfo.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfittrexpro/AmazfitTRexProFirmwareInfo.java
new file mode 100644
index 0000000..1ab6f7a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfittrexpro/AmazfitTRexProFirmwareInfo.java
@@ -0,0 +1,113 @@
+/*  Copyright (C) 2021-2024 Andreas Shimokawa, GeekosaurusR3x
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfittrexpro;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareInfo;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareType;
+import nodomain.freeyourgadget.gadgetbridge.util.ArrayUtils;
+
+public class AmazfitTRexProFirmwareInfo extends HuamiFirmwareInfo {
+    private static final int FW_OFFSET = 3;
+
+    private static final byte[] FW_HEADER = new byte[]{
+            0x20, (byte) 0x99, 0x12, 0x01, 0x08 // completely nonsense probably
+    };
+
+    // gps detection is totally bogus, just the first 16 bytes
+    private static final byte[][] GPS_HEADERS = {
+            new byte[]{
+                    0x73, 0x75, 0x68, (byte) 0xd0, 0x70, 0x73, (byte) 0xbb, 0x5a,
+                    0x3e, (byte) 0xc3, (byte) 0xd3, 0x09, (byte) 0x9e, 0x1d, (byte) 0xd3, (byte) 0xc9
+            }
+    };
+    private static Map<Integer, String> crcToVersion = new HashMap<>();
+
+    static {
+        // firmware
+
+        // Latin Firmware
+
+        // resources
+
+        // font
+
+        // gps
+    }
+
+    public AmazfitTRexProFirmwareInfo(byte[] bytes) {
+        super(bytes);
+    }
+
+    @Override
+    protected HuamiFirmwareType determineFirmwareType(byte[] bytes) {
+        if (ArrayUtils.equals(bytes, NEWRES_HEADER, COMPRESSED_RES_HEADER_OFFSET_NEW)) {
+            return HuamiFirmwareType.RES_COMPRESSED;
+        }
+        if (ArrayUtils.equals(bytes, FW_HEADER, FW_OFFSET)) {
+            if (searchString32BitAligned(bytes, "Amazfit T-Rex Pro")) {
+                return HuamiFirmwareType.FIRMWARE;
+            }
+            return HuamiFirmwareType.INVALID;
+        }
+        if (ArrayUtils.startsWith(bytes, UIHH_HEADER) && (bytes[4] == 1 || bytes[4] == 2)) {
+            return HuamiFirmwareType.WATCHFACE;
+        }
+
+        if (ArrayUtils.startsWith(bytes, NEWFT_HEADER)) {
+            if (bytes[10] == 0x01) {
+                return HuamiFirmwareType.FONT;
+            } else if (bytes[10] == 0x02) {
+                return HuamiFirmwareType.FONT_LATIN;
+            }
+        }
+
+        if (ArrayUtils.startsWith(bytes, GPS_ALMANAC_HEADER)) {
+            return HuamiFirmwareType.GPS_ALMANAC;
+        }
+
+        if (ArrayUtils.startsWith(bytes, GPS_CEP_HEADER)) {
+            return HuamiFirmwareType.GPS_CEP;
+        }
+
+        if (ArrayUtils.startsWith(bytes, AGPS_UIHH_HEADER)) {
+            return HuamiFirmwareType.AGPS_UIHH;
+        }
+
+        for (byte[] gpsHeader : GPS_HEADERS) {
+            if (ArrayUtils.startsWith(bytes, gpsHeader)) {
+                return HuamiFirmwareType.GPS;
+            }
+        }
+
+        return HuamiFirmwareType.INVALID;
+    }
+
+    @Override
+    public boolean isGenerallyCompatibleWith(GBDevice device) {
+        return isHeaderValid() && device.getType() == DeviceType.AMAZFITTREXPRO;
+    }
+
+    @Override
+    protected Map<Integer, String> getCrcMap() {
+        return crcToVersion;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfittrexpro/AmazfitTRexProSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfittrexpro/AmazfitTRexProSupport.java
new file mode 100644
index 0000000..5d78e57
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfittrexpro/AmazfitTRexProSupport.java
@@ -0,0 +1,43 @@
+/*  Copyright (C) 2021-2024 GeekosaurusR3x
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfittrexpro;
+
+import android.content.Context;
+import android.net.Uri;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfittrexpro.AmazfitTRexProFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitgts.AmazfitGTSSupport;
+
+public class AmazfitTRexProSupport extends AmazfitGTSSupport {
+
+    @Override
+    public HuamiFWHelper createFWHelper(Uri uri, Context context) throws IOException {
+        return new AmazfitTRexProFWHelper(uri, context);
+    }
+
+    @Override
+    protected AmazfitTRexProSupport setDisplayItems(TransactionBuilder builder) {
+        setDisplayItemsNew(builder, false, true, R.array.pref_trex_pro_display_items_default);
+        return this;
+    }
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitvergel/AmazfitVergeLFirmwareInfo.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitvergel/AmazfitVergeLFirmwareInfo.java
new file mode 100644
index 0000000..6a9af8b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitvergel/AmazfitVergeLFirmwareInfo.java
@@ -0,0 +1,118 @@
+/*  Copyright (C) 2020-2024 angelpup
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitvergel;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareInfo;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareType;
+import nodomain.freeyourgadget.gadgetbridge.util.ArrayUtils;
+
+//I just copied GTS probably more wrong then gts is wrong
+
+public class AmazfitVergeLFirmwareInfo extends HuamiFirmwareInfo {
+    private static final int FW_OFFSET = 3;
+
+    private static final byte[] FW_HEADER = new byte[]{
+            0x20, (byte) 0x99, 0x12, 0x01, 0x08 // completely nonsense probably
+    };
+
+    private static final byte[] GPS_ALMANAC_HEADER = new byte[]{ // probably wrong
+            (byte) 0xa0, (byte) 0x80, 0x08, 0x00, (byte) 0x8b, 0x07
+    };
+
+    private static final byte[] GPS_CEP_HEADER = new byte[]{ // probably wrong
+            0x2a, 0x12, (byte) 0xa0, 0x02
+    };
+
+    // gps detection is totally bogus, just the first 16 bytes
+    private static final byte[][] GPS_HEADERS = {
+            new byte[]{
+                    0x73, 0x75, 0x68, (byte) 0xd0, 0x70, 0x73, (byte) 0xbb, 0x5a,
+                    0x3e, (byte) 0xc3, (byte) 0xd3, 0x09, (byte) 0x9e, 0x1d, (byte) 0xd3, (byte) 0xc9
+            }
+    };
+    private static Map<Integer, String> crcToVersion = new HashMap<>();
+
+    static {
+        // firmware
+
+        // Latin Firmware
+
+        // resources
+
+        // font
+
+        // gps
+        crcToVersion.put(62532, "18344,eb2f43f,126");
+    }
+
+    public AmazfitVergeLFirmwareInfo(byte[] bytes) {
+        super(bytes);
+    }
+
+    @Override
+    protected HuamiFirmwareType determineFirmwareType(byte[] bytes) {
+        if (ArrayUtils.equals(bytes, NEWRES_HEADER, COMPRESSED_RES_HEADER_OFFSET_NEW)) {
+            return HuamiFirmwareType.RES_COMPRESSED;
+        }
+        if (ArrayUtils.equals(bytes, FW_HEADER, FW_OFFSET)) {
+            if (searchString32BitAligned(bytes, "Amazfit Verge L")) {
+                return HuamiFirmwareType.FIRMWARE;
+            }
+            return HuamiFirmwareType.INVALID;
+        }
+        if (ArrayUtils.startsWith(bytes, WATCHFACE_HEADER) || ArrayUtils.equals(bytes, WATCHFACE_HEADER, COMPRESSED_RES_HEADER_OFFSET_NEW) || ArrayUtils.equals(bytes, WATCHFACE_HEADER, COMPRESSED_RES_HEADER_OFFSET)) {
+            return HuamiFirmwareType.WATCHFACE;
+        }
+        if (ArrayUtils.startsWith(bytes, NEWFT_HEADER)) {
+            if (bytes[10] == 0x01) {
+                return HuamiFirmwareType.FONT;
+            } else if (bytes[10] == 0x02) {
+                return HuamiFirmwareType.FONT_LATIN;
+            }
+        }
+
+        if (ArrayUtils.startsWith(bytes, GPS_ALMANAC_HEADER)) {
+            return HuamiFirmwareType.GPS_ALMANAC;
+        }
+        if (ArrayUtils.startsWith(bytes, GPS_CEP_HEADER)) {
+            return HuamiFirmwareType.GPS_CEP;
+        }
+
+        for (byte[] gpsHeader : GPS_HEADERS) {
+            if (ArrayUtils.startsWith(bytes, gpsHeader)) {
+                return HuamiFirmwareType.GPS;
+            }
+        }
+
+        return HuamiFirmwareType.INVALID;
+    }
+
+    @Override
+    public boolean isGenerallyCompatibleWith(GBDevice device) {
+        return isHeaderValid() && device.getType() == DeviceType.AMAZFITVERGEL;
+    }
+
+    @Override
+    protected Map<Integer, String> getCrcMap() {
+        return crcToVersion;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitvergel/AmazfitVergeLSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitvergel/AmazfitVergeLSupport.java
new file mode 100644
index 0000000..95e24e9
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitvergel/AmazfitVergeLSupport.java
@@ -0,0 +1,68 @@
+/*  Copyright (C) 2020-2024 Andreas Shimokawa, angelpup, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitvergel;
+
+import android.content.Context;
+import android.net.Uri;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitbip.AmazfitBipSupport;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitvergel.AmazfitVergeLFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.update.UpdateFirmwareOperation;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.update.UpdateFirmwareOperation2020;
+
+public class AmazfitVergeLSupport extends AmazfitBipSupport {
+    private static final Logger LOG = LoggerFactory.getLogger(AmazfitVergeLSupport.class);
+
+    @Override
+    public byte getCryptFlags() {
+        return (byte) 0x80;
+    }
+
+    @Override
+    protected byte getAuthFlags() {
+        return 0x00;
+    }
+
+    @Override
+    protected AmazfitVergeLSupport setDisplayItems(TransactionBuilder builder) {
+        setDisplayItemsNew(builder, false, true, R.array.pref_gts_display_items_values);
+        return this;
+    }
+
+    @Override
+    protected boolean notificationHasExtraHeader() {
+        return true;
+    }
+
+    @Override
+    public UpdateFirmwareOperation createUpdateFirmwareOperation(Uri uri) {
+        return new UpdateFirmwareOperation2020(uri, this);
+    }
+
+    @Override
+    public HuamiFWHelper createFWHelper(Uri uri, Context context) throws IOException {
+        return new AmazfitVergeLFWHelper(uri, context);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitx/AmazfitXFirmwareInfo.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitx/AmazfitXFirmwareInfo.java
new file mode 100644
index 0000000..74554a7
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitx/AmazfitXFirmwareInfo.java
@@ -0,0 +1,85 @@
+/*  Copyright (C) 2021-2024 Stefan Bora
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitx;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareInfo;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareType;
+import nodomain.freeyourgadget.gadgetbridge.util.ArrayUtils;
+
+public class AmazfitXFirmwareInfo extends HuamiFirmwareInfo {
+
+    public static final byte[] FW_HEADER = new byte[]{
+            0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, (byte) 0x9c, (byte) 0xe3, 0x7d, 0x5c, 0x00, 0x04
+    };
+
+    public static final int FW_HEADER_OFFSET = 16;
+
+    private static Map<Integer, String> crcToVersion = new HashMap<>();
+
+    static {
+        // no known fw so far
+    }
+
+    public AmazfitXFirmwareInfo(byte[] bytes) {
+        super(bytes);
+    }
+
+    @Override
+    protected HuamiFirmwareType determineFirmwareType(byte[] bytes) {
+        // Currently always return INVALID because it's untested
+        /*
+        if (ArrayUtils.equals(bytes, RES_HEADER, COMPRESSED_RES_HEADER_OFFSET) || ArrayUtils.equals(bytes, NEWRES_HEADER, COMPRESSED_RES_HEADER_OFFSET_NEW) || ArrayUtils.equals(bytes, NEWRES_HEADER, COMPRESSED_RES_HEADER_OFFSET)) {
+            return HuamiFirmwareType.RES_COMPRESSED;
+        }
+        if (ArrayUtils.equals(bytes, FW_HEADER, FW_HEADER_OFFSET)) {
+            if (searchString32BitAligned(bytes, "Amazfit X")) {
+                return HuamiFirmwareType.FIRMWARE;
+            }
+            return HuamiFirmwareType.INVALID;
+        }
+        if (ArrayUtils.startsWith(bytes, WATCHFACE_HEADER_UIHH)) {
+            return HuamiFirmwareType.WATCHFACE;
+        }
+        if (ArrayUtils.startsWith(bytes, NEWFT_HEADER)) {
+            if (bytes[10] == 0x03 || bytes[10] == 0x06) {
+                return HuamiFirmwareType.FONT;
+            }
+        }
+        // somebody might have unpacked the compressed res
+        if (ArrayUtils.startsWith(bytes, RES_HEADER)) {
+            return HuamiFirmwareType.RES;
+        }
+        */
+        return HuamiFirmwareType.INVALID;
+    }
+
+
+    @Override
+    public boolean isGenerallyCompatibleWith(GBDevice device) {
+        return isHeaderValid() && device.getType() == DeviceType.AMAZFITX;
+    }
+
+    @Override
+    protected Map<Integer, String> getCrcMap() {
+        return crcToVersion;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitx/AmazfitXSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitx/AmazfitXSupport.java
new file mode 100644
index 0000000..6697162
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitx/AmazfitXSupport.java
@@ -0,0 +1,52 @@
+/*  Copyright (C) 2021-2024 Andreas Shimokawa, Stefan Bora
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitx;
+
+import android.content.Context;
+import android.net.Uri;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitband5.AmazfitBand5FWHelper;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.miband5.MiBand5Support;
+
+public class AmazfitXSupport extends MiBand5Support {
+    private static final Logger LOG = LoggerFactory.getLogger(AmazfitXSupport.class);
+
+    @Override
+    protected AmazfitXSupport setDisplayItems(TransactionBuilder builder) {
+        setDisplayItemsNew(builder, false, true, R.array.pref_amazfitband5_display_items_default);
+        return this;
+    }
+
+    @Override
+    protected AmazfitXSupport setShortcuts(TransactionBuilder builder) {
+        setDisplayItemsNew(builder, true, true, R.array.pref_amazfitband5_shortcuts_default);
+        return this;
+    }
+
+    @Override
+    public HuamiFWHelper createFWHelper(Uri uri, Context context) throws IOException {
+        return new AmazfitBand5FWHelper(uri, context);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/miband2/Mi2FirmwareInfo.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/miband2/Mi2FirmwareInfo.java
new file mode 100644
index 0000000..e1ba97a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/miband2/Mi2FirmwareInfo.java
@@ -0,0 +1,107 @@
+/*  Copyright (C) 2018-2024 Andreas Shimokawa, Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.miband2;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareInfo;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareType;
+import nodomain.freeyourgadget.gadgetbridge.util.ArrayUtils;
+
+public class Mi2FirmwareInfo extends HuamiFirmwareInfo {
+    private static final byte[] FW_HEADER = new byte[]{
+            (byte) 0xa3,
+            (byte) 0x68,
+            (byte) 0x04,
+            (byte) 0x3b,
+            (byte) 0x02,
+            (byte) 0xdb,
+            (byte) 0xc8,
+            (byte) 0x58,
+            (byte) 0xd0,
+            (byte) 0x50,
+            (byte) 0xfa,
+            (byte) 0xe7,
+            (byte) 0x0c,
+            (byte) 0x34,
+            (byte) 0xf3,
+            (byte) 0xe7,
+    };
+
+    private static final int FW_HEADER_OFFSET = 0x150;
+
+    private static final byte FW_MAGIC = (byte) 0xf8;
+    private static final int FW_MAGIC_OFFSET = 0x17d;
+
+    private static Map<Integer, String> crcToVersion = new HashMap<>();
+
+    static {
+        // firmware
+        crcToVersion.put(41899, "1.0.0.39");
+        crcToVersion.put(49197, "1.0.0.53");
+        crcToVersion.put(32450, "1.0.1.28");
+        crcToVersion.put(51770, "1.0.1.34");
+        crcToVersion.put(3929,  "1.0.1.39");
+        crcToVersion.put(47364, "1.0.1.54");
+        crcToVersion.put(44776, "1.0.1.59");
+        crcToVersion.put(27318, "1.0.1.67");
+        crcToVersion.put(54702, "1.0.1.69");
+        crcToVersion.put(31698, "1.0.1.81");
+        crcToVersion.put(53474, "1.0.1.81 (tph)");
+        crcToVersion.put(46048, "1.0.1.81 (tph as7000)");
+        crcToVersion.put(19930, "1.0.1.81 (tph india)");
+        // fonts
+        crcToVersion.put(45624, "Font");
+        crcToVersion.put(6377,  "Font (En)");
+    }
+
+    public Mi2FirmwareInfo(byte[] bytes) {
+        super(bytes);
+    }
+
+    protected HuamiFirmwareType determineFirmwareType(byte[] bytes) {
+        if (ArrayUtils.startsWith(bytes, FT_HEADER)) {
+            if (bytes[FONT_TYPE_OFFSET] == 0x00 || bytes[FONT_TYPE_OFFSET] == (byte) 0xff) {
+                return HuamiFirmwareType.FONT;
+            }
+            return HuamiFirmwareType.INVALID;
+        }
+        if (ArrayUtils.equals(bytes, FW_HEADER, FW_HEADER_OFFSET)
+                && (bytes[FW_MAGIC_OFFSET] == FW_MAGIC)) {
+            // TODO: this is certainly not a correct validation, but it works for now
+            return HuamiFirmwareType.FIRMWARE;
+        }
+        return HuamiFirmwareType.INVALID;
+    }
+
+    public boolean isGenerallyCompatibleWith(GBDevice device) {
+        return isHeaderValid() && (device.getType() == DeviceType.MIBAND2 || device.getType() == DeviceType.MIBAND2_HRX);
+    }
+
+    protected Map<Integer, String> getCrcMap() {
+        return crcToVersion;
+    }
+
+    @Override
+    protected String searchFirmwareVersion(byte[] fwbytes) {
+        // does not work for Mi Band 2
+        return null;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/miband2/Mi2NotificationStrategy.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/miband2/Mi2NotificationStrategy.java
new file mode 100644
index 0000000..eebe4c9
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/miband2/Mi2NotificationStrategy.java
@@ -0,0 +1,78 @@
+/*  Copyright (C) 2018-2024 Andreas Shimokawa, Daniele Gobbetti, Dmitry Markin
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.miband2;
+
+import android.bluetooth.BluetoothGattCharacteristic;
+
+import androidx.annotation.Nullable;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.VibrationProfile;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEAction;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.GattCharacteristic;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotification.AlertCategory;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.common.SimpleNotification;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.V2NotificationStrategy;
+
+public class Mi2NotificationStrategy extends V2NotificationStrategy<HuamiSupport> {
+
+    private final BluetoothGattCharacteristic alertLevelCharacteristic;
+
+    public Mi2NotificationStrategy(HuamiSupport support) {
+        super(support);
+        alertLevelCharacteristic = support.getCharacteristic(GattCharacteristic.UUID_CHARACTERISTIC_ALERT_LEVEL);
+    }
+
+    @Override
+    protected void sendCustomNotification(VibrationProfile vibrationProfile, SimpleNotification simpleNotification, BtLEAction extraAction, TransactionBuilder builder) {
+        startNotify(builder, vibrationProfile.getAlertLevel(), simpleNotification);
+        BluetoothGattCharacteristic alert = getSupport().getCharacteristic(GattCharacteristic.UUID_CHARACTERISTIC_ALERT_LEVEL);
+        byte repeat = (byte) (vibrationProfile.getRepeat() * (vibrationProfile.getOnOffSequence().length / 2));
+        int waitDuration = 0;
+        if (repeat > 0) {
+            short vibration = (short) vibrationProfile.getOnOffSequence()[0];
+            short pause = (short) vibrationProfile.getOnOffSequence()[1];
+            waitDuration = (vibration + pause) * repeat;
+            builder.write(alert, new byte[]{-1, (byte) (vibration & 255), (byte) (vibration >> 8 & 255), (byte) (pause & 255), (byte) (pause >> 8 & 255), repeat});
+        }
+
+        // Don't wait during an incoming call, otherwise we'll not be able to stop the call notification
+        if (simpleNotification == null || simpleNotification.getAlertCategory() != AlertCategory.IncomingCall) {
+            waitDuration = Math.max(waitDuration, 4000);
+            builder.wait(waitDuration);
+        }
+
+        if (extraAction != null) {
+            builder.add(extraAction);
+        }
+    }
+
+    protected void startNotify(TransactionBuilder builder, int alertLevel, @Nullable SimpleNotification simpleNotification) {
+        builder.write(alertLevelCharacteristic, new byte[] {(byte) alertLevel});
+
+    }
+
+    protected void stopNotify(TransactionBuilder builder) {
+        builder.write(alertLevelCharacteristic, new byte[]{GattCharacteristic.NO_ALERT});
+    }
+
+    @Override
+    public void sendCustomNotification(VibrationProfile vibrationProfile, @Nullable SimpleNotification simpleNotification, int flashTimes, int flashColour, int originalColour, long flashDuration, BtLEAction extraAction, TransactionBuilder builder) {
+        // all other parameters are unfortunately not supported anymore ;-(
+        sendCustomNotification(vibrationProfile, simpleNotification, extraAction, builder);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/miband2/Mi2TextNotificationStrategy.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/miband2/Mi2TextNotificationStrategy.java
new file mode 100644
index 0000000..81ef452
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/miband2/Mi2TextNotificationStrategy.java
@@ -0,0 +1,94 @@
+/*  Copyright (C) 2018-2024 Andreas Shimokawa, Daniele Gobbetti
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.miband2;
+
+import android.bluetooth.BluetoothGattCharacteristic;
+
+import androidx.annotation.NonNull;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.VibrationProfile;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEAction;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.GattCharacteristic;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotification.AlertCategory;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotification.AlertNotificationProfile;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotification.NewAlert;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotification.OverflowStrategy;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.common.SimpleNotification;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiIcon;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport;
+import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
+
+public class Mi2TextNotificationStrategy extends Mi2NotificationStrategy {
+    private final BluetoothGattCharacteristic newAlertCharacteristic;
+
+    public Mi2TextNotificationStrategy(HuamiSupport support) {
+        super(support);
+        newAlertCharacteristic = support.getCharacteristic(GattCharacteristic.UUID_CHARACTERISTIC_NEW_ALERT);
+    }
+
+    @Override
+    protected void sendCustomNotification(VibrationProfile vibrationProfile, SimpleNotification simpleNotification, BtLEAction extraAction, TransactionBuilder builder) {
+        if (simpleNotification != null && simpleNotification.getAlertCategory() == AlertCategory.IncomingCall) {
+            // incoming calls are notified solely via NewAlert including caller ID
+            sendAlert(simpleNotification, builder);
+            return;
+        }
+
+        // announce text messages with configured alerts first
+        super.sendCustomNotification(vibrationProfile, simpleNotification, extraAction, builder);
+        // and finally send the text message, if any
+        if (simpleNotification != null && !StringUtils.isEmpty(simpleNotification.getMessage())) {
+            sendAlert(simpleNotification, builder);
+        }
+    }
+
+    @Override
+    protected void startNotify(TransactionBuilder builder, int alertLevel, SimpleNotification simpleNotification) {
+        builder.write(newAlertCharacteristic, getNotifyMessage(simpleNotification));
+    }
+
+    protected byte[] getNotifyMessage(SimpleNotification simpleNotification) {
+        int numAlerts = 1;
+        if (simpleNotification != null && simpleNotification.getNotificationType() != null && simpleNotification.getAlertCategory() != AlertCategory.SMS) {
+            byte customIconId = HuamiIcon.mapToIconId(simpleNotification.getNotificationType());
+            if (customIconId == HuamiIcon.EMAIL) {
+                // unfortunately. the email icon breaks the notification, fall back to a standard AlertCategory
+                return new byte[]{BLETypeConversions.fromUint8(AlertCategory.Email.getId()), BLETypeConversions.fromUint8(numAlerts)};
+            }
+            return new byte[]{BLETypeConversions.fromUint8(AlertCategory.CustomHuami.getId()), BLETypeConversions.fromUint8(numAlerts), customIconId};
+        }
+        return new byte[] { BLETypeConversions.fromUint8(AlertCategory.SMS.getId()), BLETypeConversions.fromUint8(numAlerts)};
+    }
+
+    protected void sendAlert(@NonNull SimpleNotification simpleNotification, TransactionBuilder builder) {
+        AlertNotificationProfile<?> profile = new AlertNotificationProfile<>(getSupport());
+        // override the alert category,  since only SMS and incoming call support text notification
+        AlertCategory category = AlertCategory.SMS;
+        if (simpleNotification.getAlertCategory() == AlertCategory.IncomingCall) {
+            category = simpleNotification.getAlertCategory();
+        }
+        NewAlert alert = new NewAlert(category, 1, simpleNotification.getMessage());
+        profile.newAlert(builder, alert, OverflowStrategy.MAKE_MULTIPLE);
+    }
+
+    @Override
+    public void stopCurrentNotification(TransactionBuilder builder) {
+        BluetoothGattCharacteristic alert = getSupport().getCharacteristic(GattCharacteristic.UUID_CHARACTERISTIC_NEW_ALERT);
+        builder.write(alert, new byte[]{(byte) AlertCategory.IncomingCall.getId(), 0});
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/miband2/MiBand2Support.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/miband2/MiBand2Support.java
new file mode 100644
index 0000000..78791e0
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/miband2/MiBand2Support.java
@@ -0,0 +1,94 @@
+/*  Copyright (C) 2018-2024 Andreas Shimokawa, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.miband2;
+
+import static nodomain.freeyourgadget.gadgetbridge.service.btle.GattCharacteristic.UUID_CHARACTERISTIC_ALERT_LEVEL;
+
+import android.content.Context;
+import android.net.Uri;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitbip.AmazfitBipFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.miband2.MiBand2FWHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst;
+import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.NotificationType;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.AbortTransactionAction;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotification.AlertCategory;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.common.SimpleNotification;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.actions.StopNotificationAction;
+import nodomain.freeyourgadget.gadgetbridge.util.NotificationUtils;
+
+public class MiBand2Support extends HuamiSupport {
+    private static final Logger LOG = LoggerFactory.getLogger(MiBand2Support.class);
+
+    private boolean alarmClockRinging;
+
+    @Override
+    public HuamiFWHelper createFWHelper(Uri uri, Context context) throws IOException {
+        return new MiBand2FWHelper(uri, context);
+    }
+
+    @Override
+    public void onNotification(NotificationSpec notificationSpec) {
+        if (notificationSpec.type == NotificationType.GENERIC_ALARM_CLOCK) {
+            onAlarmClock(notificationSpec);
+            return;
+        }
+
+        int alertLevel = HuamiService.ALERT_LEVEL_MESSAGE;
+        if (notificationSpec.type == NotificationType.UNKNOWN) {
+            alertLevel = HuamiService.ALERT_LEVEL_VIBRATE_ONLY;
+        }
+        String message = NotificationUtils.getPreferredTextFor(notificationSpec, 40, 40, getContext()).trim();
+        String origin = notificationSpec.type.getGenericType();
+        SimpleNotification simpleNotification = new SimpleNotification(message, BLETypeConversions.toAlertCategory(notificationSpec.type), notificationSpec.type);
+        performPreferredNotification(origin + " received", origin, simpleNotification, alertLevel, null);
+    }
+
+    protected void onAlarmClock(NotificationSpec notificationSpec) {
+        alarmClockRinging = true;
+        AbortTransactionAction abortAction = new StopNotificationAction(getCharacteristic(UUID_CHARACTERISTIC_ALERT_LEVEL)) {
+            @Override
+            protected boolean shouldAbort() {
+                return !isAlarmClockRinging();
+            }
+        };
+        String message = NotificationUtils.getPreferredTextFor(notificationSpec, 40, 40, getContext());
+        SimpleNotification simpleNotification = new SimpleNotification(message, AlertCategory.HighPriorityAlert, notificationSpec.type);
+        performPreferredNotification("alarm clock ringing", MiBandConst.ORIGIN_ALARM_CLOCK, simpleNotification, HuamiService.ALERT_LEVEL_VIBRATE_ONLY, abortAction);
+    }
+
+    @Override
+    public void onDeleteNotification(int id) {
+        alarmClockRinging = false; // we should have the notificationtype at least to check
+    }
+
+    private boolean isAlarmClockRinging() {
+        // don't synchronize, this is not really important
+        return alarmClockRinging;
+    }
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/miband3/MiBand3FirmwareInfo.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/miband3/MiBand3FirmwareInfo.java
new file mode 100644
index 0000000..d30420c
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/miband3/MiBand3FirmwareInfo.java
@@ -0,0 +1,127 @@
+/*  Copyright (C) 2018-2024 Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.miband3;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareInfo;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareType;
+import nodomain.freeyourgadget.gadgetbridge.util.ArrayUtils;
+
+public class MiBand3FirmwareInfo extends HuamiFirmwareInfo {
+    // this is the same as Mi Band 2
+    private static final byte[] FW_HEADER = new byte[]{
+            (byte) 0xa3, (byte) 0x68, (byte) 0x04, (byte) 0x3b, (byte) 0x02, (byte) 0xdb,
+            (byte) 0xc8, (byte) 0x58, (byte) 0xd0, (byte) 0x50, (byte) 0xfa, (byte) 0xe7,
+            (byte) 0x0c, (byte) 0x34, (byte) 0xf3, (byte) 0xe7,
+    };
+
+    private static final int FW_HEADER_OFFSET = 0x150;
+
+    private static final byte FW_MAGIC = (byte) 0xf9;
+    private static final int FW_MAGIC_OFFSET = 0x17d;
+
+    private static Map<Integer, String> crcToVersion = new HashMap<>();
+
+    static {
+        // firmware
+        crcToVersion.put(55852, "1.2.0.8");
+        crcToVersion.put(14899, "1.3.0.4");
+        crcToVersion.put(20651, "1.3.0.8");
+        crcToVersion.put(60781, "1.4.0.12");
+        crcToVersion.put(30045, "1.5.0.2");
+        crcToVersion.put(38254, "1.5.0.7");
+        crcToVersion.put(46985, "1.5.0.11");
+        crcToVersion.put(31330, "1.6.0.16");
+        crcToVersion.put(10930, "1.8.0.0");
+        crcToVersion.put(59800, "2.0.0.4");
+        crcToVersion.put(10023, "2.2.0.12");
+        crcToVersion.put(40344, "2.2.0.14");
+        crcToVersion.put(4467,  "2.2.0.42");
+        crcToVersion.put(61657, "2.3.0.2");
+        crcToVersion.put(62735, "2.3.0.6");
+        crcToVersion.put(40949, "2.3.0.28");
+        crcToVersion.put(59213, "2.4.0.12");
+        crcToVersion.put(10810, "2.4.0.20");
+        crcToVersion.put(18271, "2.4.0.32");
+
+        // firmware (Mi Band 3 NFC)
+        crcToVersion.put(46724, "1.7.0.4");
+
+        // resources
+        crcToVersion.put(54724, "1.2.0.8");
+        crcToVersion.put(52589, "1.3.0.4");
+        crcToVersion.put(34642, "1.3.0.8");
+        crcToVersion.put(25278, "1.4.0.12-1.6.0.16");
+        crcToVersion.put(23249, "1.8.0.0");
+        crcToVersion.put(1815,  "2.0.0.4");
+        crcToVersion.put(7225, "2.2.0.12-2.3.0.6");
+        crcToVersion.put(52754, "2.3.0.28");
+        crcToVersion.put(17930, "2.4.0.12-32");
+
+        // font
+        crcToVersion.put(19775, "1");
+        crcToVersion.put(42959, "2 (old Jap/Kor)");
+        crcToVersion.put(12052, "1 (Jap/Kor)");
+    }
+
+    public MiBand3FirmwareInfo(byte[] bytes) {
+        super(bytes);
+    }
+
+    @Override
+    protected HuamiFirmwareType determineFirmwareType(byte[] bytes) {
+        if (ArrayUtils.startsWith(bytes, FT_HEADER)) {
+            if (bytes[FONT_TYPE_OFFSET] >= 0x03 && bytes[FONT_TYPE_OFFSET] <= 0x05) {
+                return HuamiFirmwareType.FONT;
+            }
+            return HuamiFirmwareType.INVALID;
+        }
+        if (ArrayUtils.startsWith(bytes, RES_HEADER)) {
+            if (bytes.length > 150000) { // don't know how to distinguish from Bip/Cor .res
+                return HuamiFirmwareType.INVALID;
+            }
+            return HuamiFirmwareType.RES;
+        }
+        if (ArrayUtils.equals(bytes, FW_HEADER, FW_HEADER_OFFSET)
+                && (bytes[FW_MAGIC_OFFSET] == FW_MAGIC)) {
+            // TODO: this is certainly not a correct validation, but it works for now
+            return HuamiFirmwareType.FIRMWARE;
+        }
+        return HuamiFirmwareType.INVALID;
+    }
+
+
+    @Override
+    public boolean isGenerallyCompatibleWith(GBDevice device) {
+        return isHeaderValid() && device.getType() == DeviceType.MIBAND3;
+    }
+
+    @Override
+    protected Map<Integer, String> getCrcMap() {
+        return crcToVersion;
+    }
+
+    @Override
+    protected String searchFirmwareVersion(byte[] fwbytes) {
+        // does not work for Mi Band 3
+        return null;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/miband3/MiBand3Support.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/miband3/MiBand3Support.java
new file mode 100644
index 0000000..ab3d98d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/miband3/MiBand3Support.java
@@ -0,0 +1,76 @@
+/*  Copyright (C) 2018-2024 Andreas Shimokawa, Carsten Pfeiffer, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.miband3;
+
+import android.content.Context;
+import android.net.Uri;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.miband3.MiBand3FWHelper;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitbip.AmazfitBipSupport;
+
+public class MiBand3Support extends AmazfitBipSupport {
+    private static final Logger LOG = LoggerFactory.getLogger(MiBand3Support.class);
+
+    @Override
+    protected byte getAuthFlags() {
+        return 0x00;
+    }
+
+    @Override
+    protected MiBand3Support setDisplayItems(TransactionBuilder builder) {
+        Map<String, Integer> keyPosMap = new LinkedHashMap<>();
+        keyPosMap.put("notifications", 1);
+        keyPosMap.put("weather", 2);
+        keyPosMap.put("activity", 3);
+        keyPosMap.put("more", 4);
+        keyPosMap.put("status", 5);
+        keyPosMap.put("heart_rate", 6);
+        keyPosMap.put("timer", 7);
+        keyPosMap.put("nfc", 8);
+
+        setDisplayItemsOld(builder, false, R.array.pref_miband3_display_items_default, keyPosMap);
+        return this;
+    }
+
+    @Override
+    public void phase2Initialize(TransactionBuilder builder) {
+        super.phase2Initialize(builder);
+        LOG.info("phase2Initialize...");
+        if (HuamiCoordinator.getOverwriteSettingsOnConnection(getDevice().getAddress())) {
+            setLanguage(builder);
+            setBandScreenUnlock(builder);
+            setNightMode(builder);
+            setDateFormat(builder);
+        }
+    }
+
+    @Override
+    public HuamiFWHelper createFWHelper(Uri uri, Context context) throws IOException {
+        return new MiBand3FWHelper(uri, context);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/miband4/MiBand4FirmwareInfo.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/miband4/MiBand4FirmwareInfo.java
new file mode 100644
index 0000000..b0b034c
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/miband4/MiBand4FirmwareInfo.java
@@ -0,0 +1,99 @@
+/*  Copyright (C) 2019-2024 Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.miband4;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareInfo;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareType;
+import nodomain.freeyourgadget.gadgetbridge.util.ArrayUtils;
+
+public class MiBand4FirmwareInfo extends HuamiFirmwareInfo {
+
+    public static final byte[] FW_HEADER = new byte[]{
+            0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, (byte) 0x9c, (byte) 0xe3, 0x7d, 0x5c, 0x00, 0x04
+    };
+
+    public static final int FW_HEADER_OFFSET = 16;
+
+    private static Map<Integer, String> crcToVersion = new HashMap<>();
+
+    static {
+        // firmware
+        crcToVersion.put(8969, "1.0.5.22");
+        crcToVersion.put(43437, "1.0.5.66");
+        crcToVersion.put(31632, "1.0.6.00");
+        crcToVersion.put(6856, "1.0.7.14");
+        crcToVersion.put(50145, "1.0.7.60");
+        crcToVersion.put(22570, "1.0.9.48-58");
+
+        // resources
+        crcToVersion.put(27412, "1.0.5.22");
+        crcToVersion.put(5466, "1.0.5.66");
+        crcToVersion.put(20047, "1.0.6.00");
+        crcToVersion.put(62914, "1.0.7.14");
+        crcToVersion.put(17303, "1.0.7.60");
+        crcToVersion.put(57922, "1.0.9.58");
+
+        // font
+        crcToVersion.put(31978, "1");
+    }
+
+    public MiBand4FirmwareInfo(byte[] bytes) {
+        super(bytes);
+    }
+
+    @Override
+    protected HuamiFirmwareType determineFirmwareType(byte[] bytes) {
+        if (ArrayUtils.equals(bytes, RES_HEADER, COMPRESSED_RES_HEADER_OFFSET) || ArrayUtils.equals(bytes, NEWRES_HEADER, COMPRESSED_RES_HEADER_OFFSET_NEW) || ArrayUtils.equals(bytes, NEWRES_HEADER, COMPRESSED_RES_HEADER_OFFSET)) {
+            return HuamiFirmwareType.RES_COMPRESSED;
+        }
+        if (ArrayUtils.equals(bytes, FW_HEADER, FW_HEADER_OFFSET)) {
+            if (searchString32BitAligned(bytes, "Mi Smart Band 4")) {
+                return HuamiFirmwareType.FIRMWARE;
+            }
+            return HuamiFirmwareType.INVALID;
+        }
+        if (ArrayUtils.startsWith(bytes, WATCHFACE_HEADER)) {
+            return HuamiFirmwareType.WATCHFACE;
+        }
+        if (ArrayUtils.startsWith(bytes, NEWFT_HEADER)) {
+            if (bytes[10] == 0x03 || bytes[10] == 0x06) {
+                return HuamiFirmwareType.FONT;
+            }
+        }
+        // somebody might have unpacked the compressed res
+        if (ArrayUtils.startsWith(bytes, RES_HEADER)) {
+            return HuamiFirmwareType.RES;
+        }
+        return HuamiFirmwareType.INVALID;
+    }
+
+
+    @Override
+    public boolean isGenerallyCompatibleWith(GBDevice device) {
+        return isHeaderValid() && device.getType() == DeviceType.MIBAND4;
+    }
+
+    @Override
+    protected Map<Integer, String> getCrcMap() {
+        return crcToVersion;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/miband4/MiBand4Support.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/miband4/MiBand4Support.java
new file mode 100644
index 0000000..c0d7221
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/miband4/MiBand4Support.java
@@ -0,0 +1,66 @@
+/*  Copyright (C) 2019-2024 Andreas Shimokawa, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.miband4;
+
+import android.content.Context;
+import android.net.Uri;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.miband4.MiBand4FWHelper;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.miband3.MiBand3Support;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.update.UpdateFirmwareOperation;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.update.UpdateFirmwareOperationNew;
+
+public class MiBand4Support extends MiBand3Support {
+    private static final Logger LOG = LoggerFactory.getLogger(MiBand4Support.class);
+
+    @Override
+    public byte getCryptFlags() {
+        return (byte) 0x80;
+    }
+
+    @Override
+    protected boolean notificationHasExtraHeader() {
+        return true;
+    }
+
+    @Override
+    public HuamiFWHelper createFWHelper(Uri uri, Context context) throws IOException {
+        return new MiBand4FWHelper(uri, context);
+    }
+
+    @Override
+    public UpdateFirmwareOperation createUpdateFirmwareOperation(Uri uri) {
+        return new UpdateFirmwareOperationNew(uri, this);
+    }
+
+    @Override
+    public void phase3Initialize(TransactionBuilder builder) {
+        super.phase3Initialize(builder);
+        LOG.info("phase3Initialize...");
+        if (HuamiCoordinator.getOverwriteSettingsOnConnection(getDevice().getAddress())) {
+            setActivateDisplayOnLiftWristSensitivity(builder); // TODO? Move this to HuamiSupport?
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/miband5/MiBand5FirmwareInfo.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/miband5/MiBand5FirmwareInfo.java
new file mode 100644
index 0000000..6d47e60
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/miband5/MiBand5FirmwareInfo.java
@@ -0,0 +1,97 @@
+/*  Copyright (C) 2020-2024 Andreas Shimokawa, Cristian Alfano, odavo32nof
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.miband5;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareInfo;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareType;
+import nodomain.freeyourgadget.gadgetbridge.util.ArrayUtils;
+
+public class MiBand5FirmwareInfo extends HuamiFirmwareInfo {
+
+    public static final byte[] FW_HEADER = new byte[]{
+            0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, (byte) 0x9c, (byte) 0xe3, 0x7d, 0x5c, 0x00, 0x04
+    };
+
+    public static final int FW_HEADER_OFFSET = 16;
+
+    private static Map<Integer, String> crcToVersion = new HashMap<>();
+
+    static {
+        // firmware
+        crcToVersion.put(29062, "1.0.0.76");
+        crcToVersion.put(26302, "1.0.1.16");
+        crcToVersion.put(26666, "1.0.1.32");
+        crcToVersion.put(54599, "1.0.2.08");
+        crcToVersion.put(23572, "1.0.2.24");
+
+        // resources
+        crcToVersion.put(8009, "1.0.0.76");
+        crcToVersion.put(47040, "1.0.1.16");
+        crcToVersion.put(49094, "1.0.1.32");
+        crcToVersion.put(18506, "1.0.2.08");
+        crcToVersion.put(22895, "1.0.2.24");
+
+        // font
+        crcToVersion.put(31978, "1");
+    }
+
+    public MiBand5FirmwareInfo(byte[] bytes) {
+        super(bytes);
+    }
+
+    @Override
+    protected HuamiFirmwareType determineFirmwareType(byte[] bytes) {
+        if (ArrayUtils.equals(bytes, RES_HEADER, COMPRESSED_RES_HEADER_OFFSET) || ArrayUtils.equals(bytes, NEWRES_HEADER, COMPRESSED_RES_HEADER_OFFSET_NEW) || ArrayUtils.equals(bytes, NEWRES_HEADER, COMPRESSED_RES_HEADER_OFFSET)) {
+            return HuamiFirmwareType.RES_COMPRESSED;
+        }
+        if (ArrayUtils.equals(bytes, FW_HEADER, FW_HEADER_OFFSET)) {
+            if (searchString32BitAligned(bytes, "Mi Smart Band 5")) {
+                return HuamiFirmwareType.FIRMWARE;
+            }
+            return HuamiFirmwareType.INVALID;
+        }
+        if (ArrayUtils.startsWith(bytes, UIHH_HEADER) && (bytes[4] == 1 || bytes[4] == 2)) {
+            return HuamiFirmwareType.WATCHFACE;
+        }
+        if (ArrayUtils.startsWith(bytes, NEWFT_HEADER)) {
+            if (bytes[10] == 0x03 || bytes[10] == 0x06) {
+                return HuamiFirmwareType.FONT;
+            }
+        }
+        // somebody might have unpacked the compressed res
+        if (ArrayUtils.startsWith(bytes, RES_HEADER)) {
+            return HuamiFirmwareType.RES;
+        }
+        return HuamiFirmwareType.INVALID;
+    }
+
+
+    @Override
+    public boolean isGenerallyCompatibleWith(GBDevice device) {
+        return isHeaderValid() && device.getType() == DeviceType.MIBAND5;
+    }
+
+    @Override
+    protected Map<Integer, String> getCrcMap() {
+        return crcToVersion;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/miband5/MiBand5Support.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/miband5/MiBand5Support.java
new file mode 100644
index 0000000..4108ace
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/miband5/MiBand5Support.java
@@ -0,0 +1,73 @@
+/*  Copyright (C) 2020-2024 Andreas Shimokawa, beardhatcode, Jos茅 Rebelo,
+    odavo32nof
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.miband5;
+
+import android.content.Context;
+import android.net.Uri;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.miband5.MiBand5FWHelper;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.miband4.MiBand4Support;
+
+public class MiBand5Support extends MiBand4Support {
+    private static final Logger LOG = LoggerFactory.getLogger(MiBand5Support.class);
+
+    @Override
+    protected MiBand5Support setDisplayItems(TransactionBuilder builder) {
+        setDisplayItemsNew(builder, false, true, R.array.pref_miband5_display_items_default);
+        return this;
+    }
+
+    @Override
+    protected MiBand5Support setShortcuts(TransactionBuilder builder) {
+        setDisplayItemsNew(builder, true, true, R.array.pref_miband5_shortcuts_default);
+        return this;
+    }
+
+    @Override
+    public HuamiFWHelper createFWHelper(Uri uri, Context context) throws IOException {
+        return new MiBand5FWHelper(uri, context);
+    }
+
+    @Override
+    public boolean supportsSunriseSunsetWindHumidity() {
+        return true;
+    }
+
+    @Override
+    public int getActivitySampleSize() {
+        return 8;
+    }
+
+    @Override
+    public void phase3Initialize(TransactionBuilder builder) {
+        super.phase3Initialize(builder);
+        LOG.info("phase3Initialize...");
+        if (HuamiCoordinator.getOverwriteSettingsOnConnection(getDevice().getAddress())) {
+            setWorkoutActivityTypes(builder);  // TODO: Supported by other bands?
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/miband6/MiBand6FirmwareInfo.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/miband6/MiBand6FirmwareInfo.java
new file mode 100644
index 0000000..b821233
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/miband6/MiBand6FirmwareInfo.java
@@ -0,0 +1,82 @@
+/*  Copyright (C) 2021-2024 Andreas Shimokawa, jhey
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.miband6;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareInfo;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareType;
+import nodomain.freeyourgadget.gadgetbridge.util.ArrayUtils;
+
+public class MiBand6FirmwareInfo extends HuamiFirmwareInfo {
+
+    public static final byte[] FW_HEADER = new byte[]{
+            0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, (byte) 0x9c, (byte) 0xe3, 0x7d, 0x5c, 0x00, 0x04
+    };
+    public static final int FW_HEADER_OFFSET = 16;
+
+    private static final Map<Integer, String> crcToVersion = new HashMap<>();
+
+    static {
+        // firmware
+        crcToVersion.put(47447, "1.0.1.36");
+        crcToVersion.put(41380, "1.0.4.38");
+        crcToVersion.put(8209, "1.0.6.10-16");
+        // resources
+        crcToVersion.put(54803, "1.0.1.36");
+        crcToVersion.put(14596, "1.0.4.38");
+        crcToVersion.put(63397, "1.0.6.10");
+        crcToVersion.put(19391, "1.0.6.16");
+    }
+
+    public MiBand6FirmwareInfo(byte[] bytes) {
+        super(bytes);
+    }
+
+    @Override
+    protected HuamiFirmwareType determineFirmwareType(byte[] bytes) {
+        if (ArrayUtils.equals(bytes, NEWRES_HEADER, COMPRESSED_RES_HEADER_OFFSET_NEW) || ArrayUtils.equals(bytes, NEWRES_HEADER, COMPRESSED_RES_HEADER_OFFSET)) {
+            return HuamiFirmwareType.RES_COMPRESSED;
+        }
+        if (ArrayUtils.equals(bytes, FW_HEADER, FW_HEADER_OFFSET)) {
+            if (searchString32BitAligned(bytes, "Mi Smart Band 6")) {
+                return HuamiFirmwareType.FIRMWARE;
+            }
+            return HuamiFirmwareType.INVALID;
+        }
+
+        if (ArrayUtils.startsWith(bytes, UIHH_HEADER) && (bytes[4] == 1 || bytes[4] == 2)) {
+            return HuamiFirmwareType.WATCHFACE;
+        }
+        return HuamiFirmwareType.INVALID;
+    }
+
+
+
+    @Override
+    public boolean isGenerallyCompatibleWith(GBDevice device) {
+        return isHeaderValid() && device.getType() == DeviceType.MIBAND6;
+    }
+
+    @Override
+    protected Map<Integer, String> getCrcMap() {
+        return crcToVersion;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/miband6/MiBand6Support.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/miband6/MiBand6Support.java
new file mode 100644
index 0000000..0d1c808
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/miband6/MiBand6Support.java
@@ -0,0 +1,79 @@
+/*  Copyright (C) 2021-2024 Andreas Shimokawa, jhey, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.miband6;
+
+import android.content.Context;
+import android.net.Uri;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.miband6.MiBand6FWHelper;
+import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.miband5.MiBand5Support;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.update.UpdateFirmwareOperation;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.update.UpdateFirmwareOperation2020;
+
+public class MiBand6Support extends MiBand5Support {
+    private static final Logger LOG = LoggerFactory.getLogger(MiBand6Support.class);
+
+    @Override
+    public void onFindDevice(boolean start) {
+        final CallSpec callSpec = new CallSpec();
+        callSpec.command = start ? CallSpec.CALL_INCOMING : CallSpec.CALL_END;
+        callSpec.name = "Gadgetbridge";
+        onSetCallState(callSpec);
+    }
+
+    @Override
+    public UpdateFirmwareOperation createUpdateFirmwareOperation(Uri uri) {
+        return new UpdateFirmwareOperation2020(uri, this);
+    }
+
+    @Override
+    protected MiBand6Support setDisplayItems(TransactionBuilder builder) {
+        setDisplayItemsNew(builder, false, true, R.array.pref_miband6_display_items_default);
+        return this;
+    }
+
+    @Override
+    protected MiBand6Support setShortcuts(TransactionBuilder builder) {
+        setDisplayItemsNew(builder, true, true, R.array.pref_miband6_shortcuts_default);
+        return this;
+    }
+
+    @Override
+    public HuamiFWHelper createFWHelper(Uri uri, Context context) throws IOException {
+        return new MiBand6FWHelper(uri, context);
+    }
+
+
+    @Override
+    public boolean supportsSunriseSunsetWindHumidity() {
+        return true;
+    }
+
+    @Override
+    public int getActivitySampleSize() {
+        return 8;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/AbstractFetchOperation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/AbstractFetchOperation.java
new file mode 100644
index 0000000..9a68eed
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/AbstractFetchOperation.java
@@ -0,0 +1,376 @@
+/*  Copyright (C) 2018-2024 Andreas Shimokawa, Carsten Pfeiffer, Daniele
+    Gobbetti, Jos茅 Rebelo, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.fetch;
+
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.content.SharedPreferences;
+import android.widget.Toast;
+
+import androidx.annotation.CallSuper;
+import androidx.annotation.NonNull;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.text.DateFormat;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.Logging;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceBusyAction;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.AbstractHuamiOperation;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsSupport;
+import nodomain.freeyourgadget.gadgetbridge.util.CheckSums;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+/**
+ * An operation that fetches activity data.
+ */
+public abstract class AbstractFetchOperation extends AbstractHuamiOperation {
+    private static final Logger LOG = LoggerFactory.getLogger(AbstractFetchOperation.class);
+
+    protected BluetoothGattCharacteristic characteristicActivityData;
+    protected BluetoothGattCharacteristic characteristicFetch;
+
+    protected Calendar startTimestamp;
+
+    protected int fetchCount;
+    protected byte lastPacketCounter;
+    protected int expectedDataLength = 0;
+    protected final ByteArrayOutputStream buffer = new ByteArrayOutputStream(1024);
+
+    protected boolean operationValid = true; // to mark operation failed midway (eg. out of sync)
+
+    public AbstractFetchOperation(final HuamiSupport support) {
+        super(support);
+    }
+
+    @Override
+    protected void enableNeededNotifications(final TransactionBuilder builder, final boolean enable) {
+        if (!enable) {
+            // dynamically enabled, but always disabled on finish
+            builder.notify(characteristicFetch, false);
+            builder.notify(characteristicActivityData, false);
+        }
+    }
+
+    @Override
+    protected void doPerform() throws IOException {
+        startFetching();
+    }
+
+    protected void startFetching() throws IOException {
+        expectedDataLength = 0;
+        lastPacketCounter = -1;
+
+        final TransactionBuilder builder = performInitialized(getName());
+        if (fetchCount == 0) {
+            builder.add(new SetDeviceBusyAction(getDevice(), taskDescription(), getContext()));
+        }
+        fetchCount++;
+
+        // TODO: this probably returns null when device is not connected/initialized yet!
+        characteristicActivityData = getCharacteristic(HuamiService.UUID_CHARACTERISTIC_5_ACTIVITY_DATA);
+        builder.notify(characteristicActivityData, false);
+
+        characteristicFetch = getCharacteristic(HuamiService.UUID_UNKNOWN_CHARACTERISTIC4);
+        builder.notify(characteristicFetch, true);
+
+        startFetching(builder);
+        builder.queue(getQueue());
+    }
+
+    /**
+     * A task description, to display in notifications and device card.
+     */
+    protected abstract String taskDescription();
+
+    protected abstract void startFetching(TransactionBuilder builder);
+
+    protected abstract String getLastSyncTimeKey();
+
+    @Override
+    public boolean onCharacteristicChanged(final BluetoothGatt gatt,
+                                           final BluetoothGattCharacteristic characteristic) {
+        final UUID characteristicUUID = characteristic.getUuid();
+        if (HuamiService.UUID_CHARACTERISTIC_5_ACTIVITY_DATA.equals(characteristicUUID)) {
+            handleActivityData(characteristic.getValue());
+            return true;
+        } else if (HuamiService.UUID_UNKNOWN_CHARACTERISTIC4.equals(characteristicUUID)) {
+            handleActivityMetadata(characteristic.getValue());
+            return true;
+        } else {
+            return super.onCharacteristicChanged(gatt, characteristic);
+        }
+    }
+
+    /**
+     * Handles the finishing of fetching the activity. This signals the actual end of this operation.
+     */
+    protected final void onOperationFinished() {
+        final AbstractFetchOperation nextFetchOperation = getSupport().getNextFetchOperation();
+        if (nextFetchOperation != null) {
+            LOG.debug("Performing next operation {}", nextFetchOperation.getName());
+            try {
+                nextFetchOperation.perform();
+                return;
+            } catch (final IOException e) {
+                GB.toast(
+                        getContext(),
+                        "Failed to run next fetch operation",
+                        Toast.LENGTH_SHORT,
+                        GB.ERROR, e
+                );
+                return;
+            }
+        }
+
+        LOG.debug("All operations finished");
+
+        GB.updateTransferNotification(null, "", false, 100, getContext());
+        GB.signalActivityDataFinish(getDevice());
+        operationFinished();
+        unsetBusy();
+    }
+
+    /**
+     * Validates that the received data has the expected checksum. Only
+     * relevant for ZeppOsSupport devices.
+     *
+     * @param crc32 the expected checksum
+     * @return whether the checksum was valid
+     */
+    protected boolean validChecksum(int crc32) {
+        return crc32 == CheckSums.getCRC32(buffer.toByteArray());
+    }
+
+    protected abstract boolean processBufferedData();
+
+    protected void handleActivityData(final byte[] value) {
+        LOG.debug("{} data: {}", getName(), Logging.formatBytes(value));
+
+        if (!isOperationRunning()) {
+            LOG.error("ignoring {} notification because operation is not running. Data length: {}", getName(), value.length);
+            getSupport().logMessageContent(value);
+            return;
+        }
+
+        if ((byte) (lastPacketCounter + 1) == value[0]) {
+            // TODO we should handle skipped or repeated bytes more gracefully
+            lastPacketCounter++;
+            bufferActivityData(value);
+        } else {
+            GB.toast("Error " + getName() + ", invalid package counter: " + value[0] + ", last was: " + lastPacketCounter, Toast.LENGTH_LONG, GB.ERROR);
+            operationValid = false;
+        }
+    }
+
+    protected void bufferActivityData(byte[] value) {
+        buffer.write(value, 1, value.length - 1); // skip the counter
+    }
+
+    protected void startFetching(final TransactionBuilder builder, final byte fetchType, final GregorianCalendar sinceWhen) {
+        final HuamiSupport support = getSupport();
+        byte[] fetchBytes = BLETypeConversions.join(new byte[]{
+                        HuamiService.COMMAND_ACTIVITY_DATA_START_DATE,
+                        fetchType},
+                support.getTimeBytes(sinceWhen, support.getFetchOperationsTimeUnit()));
+        builder.write(characteristicFetch, fetchBytes);
+    }
+
+    private void handleActivityMetadata(byte[] value) {
+        if (value.length < 3) {
+            LOG.warn("Activity metadata too short: {}", Logging.formatBytes(value));
+            onOperationFinished();
+            return;
+        }
+
+        if (value[0] != HuamiService.RESPONSE) {
+            LOG.warn("Activity metadata not a response: {}", Logging.formatBytes(value));
+            onOperationFinished();
+            return;
+        }
+
+        switch (value[1]) {
+            case HuamiService.COMMAND_ACTIVITY_DATA_START_DATE:
+                handleStartDateResponse(value);
+                return;
+            case HuamiService.COMMAND_FETCH_DATA:
+                handleFetchDataResponse(value);
+                return;
+            case HuamiService.COMMAND_ACK_ACTIVITY_DATA:
+                LOG.info("Got reply to COMMAND_ACK_ACTIVITY_DATA");
+                onOperationFinished();
+                return;
+            default:
+                LOG.warn("Unexpected activity metadata: {}", Logging.formatBytes(value));
+                onOperationFinished();
+        }
+    }
+
+    private void handleStartDateResponse(final byte[] value) {
+        if (value[2] != HuamiService.SUCCESS) {
+            LOG.warn("Start date unsuccessful response: {}", Logging.formatBytes(value));
+            onOperationFinished();
+            return;
+        }
+
+        // it's 16 on the MB7, with a 0 at the end
+        if (value.length != 15 && (value.length != 16 && value[15] != 0x00)) {
+            LOG.warn("Start date response length: {}", Logging.formatBytes(value));
+            onOperationFinished();
+            return;
+        }
+
+        // the third byte (0x01 on success) = ?
+        // the 4th - 7th bytes represent the number of bytes/packets to expect, excluding the counter bytes
+        expectedDataLength = BLETypeConversions.toUint32(Arrays.copyOfRange(value, 3, 7));
+
+        // last 8 bytes are the start date
+        Calendar startTimestamp = getSupport().fromTimeBytes(Arrays.copyOfRange(value, 7, value.length));
+
+        if (expectedDataLength == 0) {
+            LOG.info("No data to fetch since {}", startTimestamp.getTime());
+            sendAck(true);
+            // do not finish the operation - do it in the ack response
+            return;
+        }
+
+        setStartTimestamp(startTimestamp);
+        LOG.info("Will transfer {} packets since {}", expectedDataLength, startTimestamp.getTime());
+
+        GB.updateTransferNotification(taskDescription(),
+                getContext().getString(R.string.FetchActivityOperation_about_to_transfer_since,
+                        DateFormat.getDateTimeInstance().format(startTimestamp.getTime())), true, 0, getContext());
+
+        // Trigger the actual data fetch
+        final TransactionBuilder step2builder = createTransactionBuilder(getName() + " Step 2");
+        step2builder.notify(characteristicActivityData, true);
+        step2builder.write(characteristicFetch, new byte[]{HuamiService.COMMAND_FETCH_DATA});
+        try {
+            performImmediately(step2builder);
+        } catch (final IOException e) {
+            GB.toast(getContext(), "Error starting fetch step 2: " + e.getMessage(), Toast.LENGTH_LONG, GB.ERROR, e);
+            onOperationFinished();
+        }
+    }
+
+    private void handleFetchDataResponse(final byte[] value) {
+        if (value[2] != HuamiService.SUCCESS) {
+            LOG.warn("Fetch data unsuccessful response: {}", Logging.formatBytes(value));
+            onOperationFinished();
+            return;
+        }
+
+        if (value.length != 3 && value.length != 7) {
+            LOG.warn("Fetch data unexpected metadata length: {}", Logging.formatBytes(value));
+            onOperationFinished();
+            return;
+        }
+
+        if (value.length == 7 && !validChecksum(BLETypeConversions.toUint32(value, 3))) {
+            LOG.warn("Data checksum invalid");
+            // If we're on Zepp OS, ack but keep data on device
+            if (isZeppOs()) {
+                sendAck(true);
+                // do not finish the operation - do it in the ack response
+                return;
+            }
+            onOperationFinished();
+            return;
+        }
+
+        final boolean success = operationValid && processBufferedData();
+
+        final boolean keepActivityDataOnDevice = !success || HuamiCoordinator.getKeepActivityDataOnDevice(getDevice().getAddress());
+        if (isZeppOs() || !keepActivityDataOnDevice) {
+            sendAck(keepActivityDataOnDevice);
+            // do not finish the operation - do it in the ack response
+            return;
+        }
+
+        onOperationFinished();
+    }
+
+    protected void sendAck(final boolean keepDataOnDevice) {
+        final byte[] ackBytes;
+
+        if (isZeppOs()) {
+            LOG.debug("Sending ack, keepDataOnDevice = {}", keepDataOnDevice);
+
+            // 0x01 to ACK, mark as saved on phone (drop from band)
+            // 0x09 to ACK, but keep it marked as not saved
+            // If 0x01 is sent, detailed information seems to be discarded, and is not sent again anymore
+            final byte ackByte = (byte) (keepDataOnDevice ? 0x09 : 0x01);
+            ackBytes = new byte[]{HuamiService.COMMAND_ACK_ACTIVITY_DATA, ackByte};
+        } else {
+            LOG.debug("Sending ack, simple");
+            ackBytes = new byte[]{HuamiService.COMMAND_ACK_ACTIVITY_DATA};
+        }
+
+        try {
+            final TransactionBuilder builder = createTransactionBuilder(getName() + " end");
+            builder.write(characteristicFetch, ackBytes);
+            performImmediately(builder);
+        } catch (final IOException e) {
+            LOG.error("Failed to send ack", e);
+        }
+    }
+
+    private void setStartTimestamp(final Calendar startTimestamp) {
+        this.startTimestamp = startTimestamp;
+    }
+
+    protected Calendar getLastStartTimestamp() {
+        return startTimestamp;
+    }
+
+    protected void saveLastSyncTimestamp(@NonNull final GregorianCalendar timestamp) {
+        final SharedPreferences.Editor editor = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress()).edit();
+        editor.putLong(getLastSyncTimeKey(), timestamp.getTimeInMillis());
+        editor.apply();
+    }
+
+    protected GregorianCalendar getLastSuccessfulSyncTime() {
+        final long timeStampMillis = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress()).getLong(getLastSyncTimeKey(), 0);
+        if (timeStampMillis != 0) {
+            GregorianCalendar calendar = BLETypeConversions.createCalendar();
+            calendar.setTimeInMillis(timeStampMillis);
+            return calendar;
+        }
+        final GregorianCalendar calendar = BLETypeConversions.createCalendar();
+        calendar.add(Calendar.DAY_OF_MONTH, -100);
+        return calendar;
+    }
+
+    protected boolean isZeppOs() {
+        return getSupport() instanceof ZeppOsSupport;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/AbstractRepeatingFetchOperation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/AbstractRepeatingFetchOperation.java
new file mode 100644
index 0000000..9b3c4bc
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/AbstractRepeatingFetchOperation.java
@@ -0,0 +1,140 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.fetch;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.OutputStream;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.Locale;
+
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport;
+import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
+
+/**
+ * A repeating fetch operation. This operation repeats the fetch up to a certain number of times, or
+ * until the fetch timestamp matches the current time. For every fetch, a new operation must
+ * be created, i.e. an operation may not be reused for multiple fetches.
+ */
+public abstract class AbstractRepeatingFetchOperation extends AbstractFetchOperation {
+    private static final Logger LOG = LoggerFactory.getLogger(AbstractRepeatingFetchOperation.class);
+
+    protected final HuamiFetchDataType dataType;
+
+    public AbstractRepeatingFetchOperation(final HuamiSupport support, final HuamiFetchDataType dataType) {
+        super(support);
+        this.dataType = dataType;
+        setName("fetching " + dataType.name());
+    }
+
+    @Override
+    protected void startFetching(final TransactionBuilder builder) {
+        final GregorianCalendar sinceWhen = getLastSuccessfulSyncTime();
+        LOG.info("start {} since {}", getName(), sinceWhen.getTime());
+        startFetching(builder, dataType.getCode(), sinceWhen);
+    }
+
+    /**
+     * Handle the buffered data.
+     *
+     * @param timestamp The timestamp of the first sample. This function should update this to the
+     *                  timestamp of the last processed sample.
+     * @param bytes     the buffered bytes
+     * @return true on success
+     */
+    protected abstract boolean handleActivityData(GregorianCalendar timestamp, byte[] bytes);
+
+    @Override
+    protected boolean processBufferedData() {
+        LOG.info("{} has finished round {}, got {} bytes in buffer", getName(), fetchCount, buffer.size());
+
+        if (buffer.size() == 0) {
+            return true;
+        }
+
+        final byte[] bytes = buffer.toByteArray();
+        final GregorianCalendar timestamp = (GregorianCalendar) this.startTimestamp.clone();
+
+        // Uncomment to dump the bytes to external storage for debugging
+        //dumpBytesToExternalStorage(bytes, timestamp);
+
+        final boolean handleSuccess = handleActivityData(timestamp, bytes);
+
+        if (!handleSuccess) {
+            return false;
+        }
+
+        timestamp.add(Calendar.MINUTE, 1);
+        saveLastSyncTimestamp(timestamp);
+
+        if (needsAnotherFetch(timestamp)) {
+            buffer.reset();
+
+            getSupport().getFetchOperationQueue().add(0, this);
+        }
+
+        return true;
+    }
+
+    private boolean needsAnotherFetch(final GregorianCalendar lastSyncTimestamp) {
+        final long lastFetchRange = lastSyncTimestamp.getTimeInMillis() - startTimestamp.getTimeInMillis();
+        if (lastFetchRange < 1000L) {
+            LOG.warn("Fetch round {} of {} got {} ms of data, stopping to avoid infinite loop", fetchCount, getName(), lastFetchRange);
+            return false;
+        }
+
+        if (fetchCount > 5) {
+            LOG.warn("Already have {} fetch rounds for {}, not doing another one", fetchCount, getName());
+            return false;
+        }
+
+        if (lastSyncTimestamp.getTimeInMillis() >= System.currentTimeMillis()) {
+            LOG.warn("Not doing another fetch since last synced timestamp is in the future: {}", lastSyncTimestamp.getTime());
+            return false;
+        }
+
+        LOG.info("Doing another fetch since last sync timestamp is still too old: {}", lastSyncTimestamp.getTime());
+        return true;
+    }
+
+    protected void dumpBytesToExternalStorage(final byte[] bytes, final GregorianCalendar timestamp) {
+        try {
+            final File externalFilesDir = FileUtils.getExternalFilesDir();
+            final File targetDir = new File(externalFilesDir, "rawFetchOperations");
+            targetDir.mkdirs();
+
+            final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmssSSS", Locale.US);
+            final String filename = getClass().getSimpleName() + "_"
+                    + timestamp.getTime().getTime() + "_"
+                    + dateFormat.format(timestamp.getTime()) + ".bin";
+
+            final File outputFile = new File(targetDir, filename);
+
+            final OutputStream outputStream = new FileOutputStream(outputFile);
+            outputStream.write(bytes);
+            outputStream.close();
+        } catch (final Exception e) {
+            LOG.error("Failed to dump bytes to storage", e);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/FetchActivityOperation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/FetchActivityOperation.java
new file mode 100644
index 0000000..4c2bc4b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/FetchActivityOperation.java
@@ -0,0 +1,194 @@
+/*  Copyright (C) 2018-2024 Andreas Shimokawa, Carsten Pfeiffer, Daniel
+    Dakhno, Daniele Gobbetti, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.fetch;
+
+import android.widget.Toast;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+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 nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.entities.HuamiExtendedActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.entities.MiBandActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.entities.User;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport;
+import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+/**
+ * An operation that fetches activity data. For every fetch, a new operation must
+ * be created, i.e. an operation may not be reused for multiple fetches.
+ */
+public class FetchActivityOperation extends AbstractRepeatingFetchOperation {
+    private static final Logger LOG = LoggerFactory.getLogger(FetchActivityOperation.class);
+
+    private final int sampleSize;
+
+    public FetchActivityOperation(final HuamiSupport support) {
+        super(support, HuamiFetchDataType.ACTIVITY);
+        this.sampleSize = support.getActivitySampleSize();
+    }
+
+    @Override
+    protected String taskDescription() {
+        return getContext().getString(R.string.busy_task_fetch_activity_data);
+    }
+
+    @Override
+    protected boolean handleActivityData(final GregorianCalendar timestamp, final byte[] bytes) {
+        if (bytes.length % sampleSize != 0) {
+            GB.toast(getContext(), "Unexpected " + getName() + " array size: " + bytes.length, Toast.LENGTH_LONG, GB.ERROR);
+            return false;
+        }
+
+        final List<MiBandActivitySample> samples = new ArrayList<>(60 * 24); // 1day per default
+
+        for (int i = 0; i < bytes.length; i += sampleSize) {
+            final MiBandActivitySample sample;
+
+            switch (sampleSize) {
+                case 4:
+                    sample = createSample(bytes, i);
+                    break;
+                case 8:
+                    sample = createExtendedSample(bytes, i);
+                    break;
+                default:
+                    throw new IllegalStateException("Unsupported sample size " + sampleSize);
+            }
+
+            samples.add(sample);
+        }
+
+        if (samples.isEmpty()) {
+            LOG.info("No samples to save");
+            return true;
+        }
+
+        LOG.info("Saving {} samples", samples.size());
+
+        // save all the samples that we got
+        try (DBHandler handler = GBApplication.acquireDB()) {
+            DaoSession session = handler.getDaoSession();
+
+            DeviceCoordinator coordinator = getDevice().getDeviceCoordinator();
+            SampleProvider sampleProvider = coordinator.getSampleProvider(getDevice(), session);
+            Device device = DBHelper.getDevice(getDevice(), session);
+            User user = DBHelper.getUser(session);
+
+            for (MiBandActivitySample sample : samples) {
+                sample.setDevice(device);
+                sample.setUser(user);
+                sample.setTimestamp((int) (timestamp.getTimeInMillis() / 1000));
+                sample.setProvider(sampleProvider);
+
+                //LOG.debug(sampleToString(sample));
+
+                timestamp.add(Calendar.MINUTE, 1);
+            }
+            sampleProvider.addGBActivitySamples(samples.toArray(new MiBandActivitySample[0]));
+
+            timestamp.add(Calendar.MINUTE, -1);
+
+            LOG.info("Huami activity data: last sample timestamp: {}", DateTimeUtils.formatDateTime(timestamp.getTime()));
+            return true;
+        } catch (Exception ex) {
+            GB.toast(getContext(), "Error saving activity samples", Toast.LENGTH_LONG, GB.ERROR);
+            LOG.error("Error saving activity samples", ex);
+            return false;
+        }
+    }
+
+    @Override
+    protected boolean validChecksum(final int crc32) {
+        // TODO actually check it
+        LOG.warn("Checksum not implemented for activity data, assuming it's valid");
+        return true;
+    }
+
+    private MiBandActivitySample createSample(byte[] value, int i) {
+        MiBandActivitySample sample = new MiBandActivitySample();
+        sample.setRawKind(value[i] & 0xff);
+        sample.setRawIntensity(value[i + 1] & 0xff);
+        sample.setSteps(value[i + 2] & 0xff);
+        sample.setHeartRate(value[i + 3] & 0xff);
+
+        return sample;
+    }
+
+    private MiBandActivitySample createExtendedSample(byte[] value, int i) {
+        final HuamiExtendedActivitySample huamiExtendedActivitySample = new HuamiExtendedActivitySample();
+        huamiExtendedActivitySample.setRawKind(value[i] & 0xff);
+        huamiExtendedActivitySample.setRawIntensity(value[i + 1] & 0xff);
+        huamiExtendedActivitySample.setSteps(value[i + 2] & 0xff);
+        huamiExtendedActivitySample.setHeartRate(value[i + 3] & 0xff);
+        huamiExtendedActivitySample.setUnknown1(value[i + 4] & 0xff);
+        huamiExtendedActivitySample.setSleep(value[i + 5] & 0xff);
+        huamiExtendedActivitySample.setDeepSleep(value[i + 6] & 0xff);
+        huamiExtendedActivitySample.setRemSleep(value[i + 7] & 0xff);
+
+        return huamiExtendedActivitySample;
+    }
+
+    private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.ROOT);
+
+    public static String sampleToString(final MiBandActivitySample sample) {
+        final StringBuilder builder = new StringBuilder(160);
+
+        builder.append(sample.getClass().getSimpleName());
+        builder.append("{");
+        builder.append(sdf.format(new Date(sample.getTimestamp() * 1000L)));
+        builder.append(",rawKind=").append(sample.getRawKind());
+        builder.append(",rawIntensity=").append(sample.getRawIntensity());
+        builder.append(",steps=").append(sample.getSteps());
+        builder.append(",heartRate=").append(sample.getHeartRate());
+
+        if (sample instanceof HuamiExtendedActivitySample) {
+            final HuamiExtendedActivitySample huamiExtendedSample = (HuamiExtendedActivitySample) sample;
+
+            builder.append(",unknown1=").append(huamiExtendedSample.getUnknown1());
+            builder.append(",sleep=").append(huamiExtendedSample.getSleep());
+            builder.append(",deepSleep=").append(huamiExtendedSample.getDeepSleep());
+            builder.append(",remSleep=").append(huamiExtendedSample.getRemSleep());
+        }
+
+        builder.append("}");
+
+        return builder.toString();
+    }
+
+    @Override
+    protected String getLastSyncTimeKey() {
+        return "lastSyncTimeMillis";
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/FetchDebugLogsOperation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/FetchDebugLogsOperation.java
new file mode 100644
index 0000000..3d13d6f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/FetchDebugLogsOperation.java
@@ -0,0 +1,109 @@
+/*  Copyright (C) 2019-2024 Andreas Shimokawa, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.fetch;
+
+import androidx.annotation.NonNull;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.Locale;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport;
+import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
+
+public class FetchDebugLogsOperation extends AbstractFetchOperation {
+    private static final Logger LOG = LoggerFactory.getLogger(FetchDebugLogsOperation.class);
+
+    private FileOutputStream logOutputStream;
+
+    public FetchDebugLogsOperation(final HuamiSupport support) {
+        super(support);
+        setName("fetch debug logs");
+    }
+
+    @Override
+    protected String taskDescription() {
+        return getContext().getString(R.string.busy_task_fetch_debug_logs);
+    }
+
+    @Override
+    protected void startFetching(final TransactionBuilder builder) {
+        File dir;
+        try {
+            dir = FileUtils.getExternalFilesDir();
+        } catch (IOException e) {
+            return;
+        }
+
+        final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd-HHmmss", Locale.US);
+        final String filename = "huamidebug_" + dateFormat.format(new Date()) + ".log";
+
+        File outputFile = new File(dir, filename);
+        try {
+            logOutputStream = new FileOutputStream(outputFile);
+        } catch (IOException e) {
+            LOG.warn("could not create file " + outputFile, e);
+            return;
+        }
+        final GregorianCalendar sinceWhen = getLastSuccessfulSyncTime();
+        startFetching(builder, HuamiFetchDataType.DEBUG_LOGS.getCode(), sinceWhen);
+    }
+
+    @Override
+    protected String getLastSyncTimeKey() {
+        return "lastDebugTimeMillis";
+    }
+
+    @Override
+    protected boolean processBufferedData() {
+        LOG.info("{} data has finished", getName());
+        try {
+            logOutputStream.close();
+            logOutputStream = null;
+        } catch (final IOException e) {
+            LOG.error("could not close output stream", e);
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    protected boolean validChecksum(int crc32) {
+        // TODO actually check it?
+        LOG.warn("Checksum not implemented for debug logs, assuming it's valid");
+        return true;
+    }
+
+    @Override
+    protected void bufferActivityData(@NonNull byte[] value) {
+        try {
+            logOutputStream.write(value, 1, value.length - 1);
+        } catch (final IOException e) {
+            LOG.error("could not write to output stream", e);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/FetchHeartRateManualOperation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/FetchHeartRateManualOperation.java
new file mode 100644
index 0000000..d73c024
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/FetchHeartRateManualOperation.java
@@ -0,0 +1,118 @@
+/*  Copyright (C) 2023-2024 Daniel Dakhno, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.fetch;
+
+import android.widget.Toast;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.GregorianCalendar;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiHeartRateManualSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.entities.HuamiHeartRateManualSample;
+import nodomain.freeyourgadget.gadgetbridge.entities.User;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+/**
+ * An operation that fetches manual HR measurement data.
+ */
+public class FetchHeartRateManualOperation extends AbstractRepeatingFetchOperation {
+    private static final Logger LOG = LoggerFactory.getLogger(FetchHeartRateManualOperation.class);
+
+    public FetchHeartRateManualOperation(final HuamiSupport support) {
+        super(support, HuamiFetchDataType.MANUAL_HEART_RATE);
+    }
+
+    @Override
+    protected String taskDescription() {
+        return getContext().getString(R.string.busy_task_fetch_hr_data);
+    }
+
+    @Override
+    protected boolean handleActivityData(final GregorianCalendar timestamp, final byte[] bytes) {
+        if (bytes.length % 6 != 0) {
+            LOG.warn("Unexpected buffered manual heart rate data size {} is not a multiple of 6", bytes.length);
+            return false;
+        }
+
+        final List<HuamiHeartRateManualSample> samples = new ArrayList<>();
+
+        final ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN);
+
+        while (buffer.position() < bytes.length) {
+            final long currentTimestamp = BLETypeConversions.toUnsigned(buffer.getInt()) * 1000;
+            timestamp.setTimeInMillis(currentTimestamp);
+
+            final byte utcOffsetInQuarterHours = buffer.get();
+            final int hr = buffer.get() & 0xff;
+
+            LOG.trace("Manual HR at {} + {}: {}", timestamp.getTime(), utcOffsetInQuarterHours, hr);
+
+            final HuamiHeartRateManualSample sample = new HuamiHeartRateManualSample();
+            sample.setTimestamp(timestamp.getTimeInMillis());
+            sample.setHeartRate(hr);
+            sample.setUtcOffset(utcOffsetInQuarterHours * 900000);
+            samples.add(sample);
+        }
+
+        return persistSamples(samples);
+    }
+
+    protected boolean persistSamples(final List<HuamiHeartRateManualSample> samples) {
+        try (DBHandler handler = GBApplication.acquireDB()) {
+            final DaoSession session = handler.getDaoSession();
+
+            final Device device = DBHelper.getDevice(getDevice(), session);
+            final User user = DBHelper.getUser(session);
+
+            final HuamiCoordinator coordinator = (HuamiCoordinator) getDevice().getDeviceCoordinator();
+            final HuamiHeartRateManualSampleProvider sampleProvider = coordinator.getHeartRateManualSampleProvider(getDevice(), session);
+
+            for (final HuamiHeartRateManualSample sample : samples) {
+                sample.setDevice(device);
+                sample.setUser(user);
+            }
+
+            LOG.debug("Will persist {} heart rate manual samples", samples.size());
+            sampleProvider.addSamples(samples);
+        } catch (final Exception e) {
+            GB.toast(getContext(), "Error saving heart rate manual samples", Toast.LENGTH_LONG, GB.ERROR, e);
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    protected String getLastSyncTimeKey() {
+        return "lastHeartRateManualTimeMillis";
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/FetchHeartRateMaxOperation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/FetchHeartRateMaxOperation.java
new file mode 100644
index 0000000..26189a4
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/FetchHeartRateMaxOperation.java
@@ -0,0 +1,118 @@
+/*  Copyright (C) 2023-2024 Daniel Dakhno, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.fetch;
+
+import android.widget.Toast;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.GregorianCalendar;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiHeartRateMaxSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.entities.HuamiHeartRateMaxSample;
+import nodomain.freeyourgadget.gadgetbridge.entities.User;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+/**
+ * An operation that fetches max HR data.
+ */
+public class FetchHeartRateMaxOperation extends AbstractRepeatingFetchOperation {
+    private static final Logger LOG = LoggerFactory.getLogger(FetchHeartRateMaxOperation.class);
+
+    public FetchHeartRateMaxOperation(final HuamiSupport support) {
+        super(support, HuamiFetchDataType.MAX_HEART_RATE);
+    }
+
+    @Override
+    protected String taskDescription() {
+        return getContext().getString(R.string.busy_task_fetch_hr_data);
+    }
+
+    @Override
+    protected boolean handleActivityData(final GregorianCalendar timestamp, final byte[] bytes) {
+        if (bytes.length % 6 != 0) {
+            LOG.warn("Unexpected buffered max heart rate data size {} is not a multiple of 6", bytes.length);
+            return false;
+        }
+
+        final List<HuamiHeartRateMaxSample> samples = new ArrayList<>();
+
+        final ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN);
+
+        while (buffer.position() < bytes.length) {
+            final long currentTimestamp = BLETypeConversions.toUnsigned(buffer.getInt()) * 1000;
+            timestamp.setTimeInMillis(currentTimestamp);
+
+            final byte utcOffsetInQuarterHours = buffer.get();
+            final int hr = buffer.get() & 0xff;
+
+            LOG.trace("Max HR at {} + {}: {}", timestamp.getTime(), utcOffsetInQuarterHours, hr);
+
+            final HuamiHeartRateMaxSample sample = new HuamiHeartRateMaxSample();
+            sample.setTimestamp(timestamp.getTimeInMillis());
+            sample.setHeartRate(hr);
+            sample.setUtcOffset(utcOffsetInQuarterHours * 900000);
+            samples.add(sample);
+        }
+
+        return persistSamples(samples);
+    }
+
+    protected boolean persistSamples(final List<HuamiHeartRateMaxSample> samples) {
+        try (DBHandler handler = GBApplication.acquireDB()) {
+            final DaoSession session = handler.getDaoSession();
+
+            final Device device = DBHelper.getDevice(getDevice(), session);
+            final User user = DBHelper.getUser(session);
+
+            final HuamiCoordinator coordinator = (HuamiCoordinator) getDevice().getDeviceCoordinator();
+            final HuamiHeartRateMaxSampleProvider sampleProvider = coordinator.getHeartRateMaxSampleProvider(getDevice(), session);
+
+            for (final HuamiHeartRateMaxSample sample : samples) {
+                sample.setDevice(device);
+                sample.setUser(user);
+            }
+
+            LOG.debug("Will persist {} heart rate max samples", samples.size());
+            sampleProvider.addSamples(samples);
+        } catch (final Exception e) {
+            GB.toast(getContext(), "Error saving heart rate max samples", Toast.LENGTH_LONG, GB.ERROR, e);
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    protected String getLastSyncTimeKey() {
+        return "lastHeartRateMaxTimeMillis";
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/FetchHeartRateRestingOperation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/FetchHeartRateRestingOperation.java
new file mode 100644
index 0000000..818b00b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/FetchHeartRateRestingOperation.java
@@ -0,0 +1,119 @@
+/*  Copyright (C) 2023-2024 Daniel Dakhno, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.fetch;
+
+import android.widget.Toast;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.GregorianCalendar;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiHeartRateRestingSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.entities.HuamiHeartRateRestingSample;
+import nodomain.freeyourgadget.gadgetbridge.entities.User;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+/**
+ * An operation that fetches resting heart rate data.
+ */
+public class FetchHeartRateRestingOperation extends AbstractRepeatingFetchOperation {
+    private static final Logger LOG = LoggerFactory.getLogger(FetchHeartRateRestingOperation.class);
+
+    public FetchHeartRateRestingOperation(final HuamiSupport support) {
+        super(support, HuamiFetchDataType.RESTING_HEART_RATE);
+    }
+
+    @Override
+    protected String taskDescription() {
+        return getContext().getString(R.string.busy_task_fetch_hr_data);
+    }
+
+    @Override
+    protected boolean handleActivityData(final GregorianCalendar timestamp, final byte[] bytes) {
+        if (bytes.length % 6 != 0) {
+            LOG.warn("Unexpected buffered rest heart rate data size {} is not a multiple of 6", bytes.length);
+            return false;
+        }
+
+        final List<HuamiHeartRateRestingSample> samples = new ArrayList<>();
+
+        final ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN);
+
+        while (buffer.position() < bytes.length) {
+            final long currentTimestamp = BLETypeConversions.toUnsigned(buffer.getInt()) * 1000;
+            timestamp.setTimeInMillis(currentTimestamp);
+
+            // Official app only shows this at day-level
+            final byte utcOffsetInQuarterHours = buffer.get();
+            final int hr = buffer.get() & 0xff;
+
+            LOG.trace("Resting HR at {} + {}: {}", timestamp.getTime(), utcOffsetInQuarterHours, hr);
+
+            final HuamiHeartRateRestingSample sample = new HuamiHeartRateRestingSample();
+            sample.setTimestamp(timestamp.getTimeInMillis());
+            sample.setHeartRate(hr);
+            sample.setUtcOffset(utcOffsetInQuarterHours * 900000);
+            samples.add(sample);
+        }
+
+        return persistSamples(samples);
+    }
+
+    protected boolean persistSamples(final List<HuamiHeartRateRestingSample> samples) {
+        try (DBHandler handler = GBApplication.acquireDB()) {
+            final DaoSession session = handler.getDaoSession();
+
+            final Device device = DBHelper.getDevice(getDevice(), session);
+            final User user = DBHelper.getUser(session);
+
+            final HuamiCoordinator coordinator = (HuamiCoordinator) getDevice().getDeviceCoordinator();
+            final HuamiHeartRateRestingSampleProvider sampleProvider = coordinator.getHeartRateRestingSampleProvider(getDevice(), session);
+
+            for (final HuamiHeartRateRestingSample sample : samples) {
+                sample.setDevice(device);
+                sample.setUser(user);
+            }
+
+            LOG.debug("Will persist {} heart rate resting samples", samples.size());
+            sampleProvider.addSamples(samples);
+        } catch (final Exception e) {
+            GB.toast(getContext(), "Error saving heart rate resting samples", Toast.LENGTH_LONG, GB.ERROR, e);
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    protected String getLastSyncTimeKey() {
+        return "lastHeartRateRestingTimeMillis";
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/FetchPaiOperation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/FetchPaiOperation.java
new file mode 100644
index 0000000..23fa515
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/FetchPaiOperation.java
@@ -0,0 +1,147 @@
+/*  Copyright (C) 2023-2024 Daniel Dakhno, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.fetch;
+
+import android.widget.Toast;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.GregorianCalendar;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiPaiSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.entities.HuamiPaiSample;
+import nodomain.freeyourgadget.gadgetbridge.entities.User;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+/**
+ * An operation that fetches PAI data.
+ */
+public class FetchPaiOperation extends AbstractRepeatingFetchOperation {
+    private static final Logger LOG = LoggerFactory.getLogger(FetchPaiOperation.class);
+
+    public FetchPaiOperation(final HuamiSupport support) {
+        super(support, HuamiFetchDataType.PAI);
+    }
+
+    @Override
+    protected String taskDescription() {
+        return getContext().getString(R.string.busy_task_fetch_pai_data);
+    }
+
+    @Override
+    protected boolean handleActivityData(final GregorianCalendar timestamp, final byte[] bytes) {
+        final List<HuamiPaiSample> samples = new ArrayList<>();
+
+        final ByteBuffer buf = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN);
+
+        while (buf.position() < bytes.length) {
+            final int type = buf.get() & 0xff;
+
+            if (type != 5) {
+                LOG.error("Unsupported PAI type {}", type);
+                return false;
+            }
+
+            final long timestampSeconds = buf.getInt();
+            timestamp.setTimeInMillis(timestampSeconds * 1000L);
+            final byte utcOffsetInQuarterHours = buf.get();
+
+            byte[] unknown1 = new byte[31];
+            buf.get(unknown1);
+
+            final float paiLow = buf.getFloat();
+            final float paiModerate = buf.getFloat();
+            final float paiHigh = buf.getFloat();
+            final short timeLow = buf.getShort(); // minutes
+            final short timeModerate = buf.getShort(); // minutes
+            final short timeHigh = buf.getShort(); // minutes
+            final float paiToday = buf.getFloat();
+            final float paiTotal = buf.getFloat();
+
+            byte[] unknown2 = new byte[39];
+            buf.get(unknown2);
+
+            LOG.trace(
+                    "PAI at {} + {}: paiLow={} paiModerate={} paiHigh={} timeLow={} timeMid={} timeHigh={} paiToday={} paiTotal={} unknown1={} unknown2={}",
+                    timestamp.getTime(), utcOffsetInQuarterHours,
+                    paiLow, paiModerate, paiHigh,
+                    timeLow, timeModerate, timeHigh,
+                    paiToday, paiTotal,
+                    GB.hexdump(unknown1),
+                    GB.hexdump(unknown2)
+            );
+
+            final HuamiPaiSample sample = new HuamiPaiSample();
+            sample.setTimestamp(timestamp.getTimeInMillis());
+            sample.setUtcOffset(utcOffsetInQuarterHours * 900000);
+            sample.setPaiLow(paiLow);
+            sample.setPaiModerate(paiModerate);
+            sample.setPaiHigh(paiHigh);
+            sample.setTimeLow(timeLow);
+            sample.setTimeModerate(timeModerate);
+            sample.setTimeHigh(timeHigh);
+            sample.setPaiToday(paiToday);
+            sample.setPaiTotal(paiTotal);
+            samples.add(sample);
+        }
+
+        return persistSamples(samples);
+    }
+
+    protected boolean persistSamples(final List<HuamiPaiSample> samples) {
+        try (DBHandler handler = GBApplication.acquireDB()) {
+            final DaoSession session = handler.getDaoSession();
+
+            final Device device = DBHelper.getDevice(getDevice(), session);
+            final User user = DBHelper.getUser(session);
+
+            final HuamiCoordinator coordinator = (HuamiCoordinator) getDevice().getDeviceCoordinator();
+            final HuamiPaiSampleProvider sampleProvider = coordinator.getPaiSampleProvider(getDevice(), session);
+
+            for (final HuamiPaiSample sample : samples) {
+                sample.setDevice(device);
+                sample.setUser(user);
+            }
+
+            LOG.debug("Will persist {} pai samples", samples.size());
+            sampleProvider.addSamples(samples);
+        } catch (final Exception e) {
+            GB.toast(getContext(), "Error saving pai samples", Toast.LENGTH_LONG, GB.ERROR, e);
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    protected String getLastSyncTimeKey() {
+        return "lastPaiTimeMillis";
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/FetchSleepRespiratoryRateOperation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/FetchSleepRespiratoryRateOperation.java
new file mode 100644
index 0000000..6c0ff20
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/FetchSleepRespiratoryRateOperation.java
@@ -0,0 +1,118 @@
+/*  Copyright (C) 2023-2024 Daniel Dakhno, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.fetch;
+
+import android.widget.Toast;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.GregorianCalendar;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiSleepRespiratoryRateSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.entities.HuamiSleepRespiratoryRateSample;
+import nodomain.freeyourgadget.gadgetbridge.entities.User;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+/**
+ * An operation that fetches sleep respiratory rate data.
+ */
+public class FetchSleepRespiratoryRateOperation extends AbstractRepeatingFetchOperation {
+    private static final Logger LOG = LoggerFactory.getLogger(FetchSleepRespiratoryRateOperation.class);
+
+    public FetchSleepRespiratoryRateOperation(final HuamiSupport support) {
+        super(support, HuamiFetchDataType.SLEEP_RESPIRATORY_RATE);
+    }
+
+    @Override
+    protected String taskDescription() {
+        return getContext().getString(R.string.busy_task_fetch_sleep_respiratory_rate_data);
+    }
+
+    @Override
+    protected boolean handleActivityData(final GregorianCalendar timestamp, final byte[] bytes) {
+        if (bytes.length % 8 != 0) {
+            LOG.error("Unexpected length for sleep respiratory rate data {}, not divisible by 8", bytes.length);
+            return false;
+        }
+
+        final List<HuamiSleepRespiratoryRateSample> samples = new ArrayList<>();
+
+        final ByteBuffer buf = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN);
+
+        while (buf.position() < bytes.length) {
+            final long timestampSeconds = buf.getInt();
+            final byte utcOffsetInQuarterHours = buf.get();
+            final int respiratoryRate = buf.get() & 0xff;
+            final byte unknown1 = buf.get(); // always 0?
+            final byte unknown2 = buf.get(); // mostly 1, sometimes 2, 4 when waking up?
+
+            timestamp.setTimeInMillis(timestampSeconds * 1000L);
+
+            LOG.trace("Sleep Respiratory Rate at {} + {}: respiratoryRate={} unknown1={} unknown2={}", timestamp.getTime(), respiratoryRate, utcOffsetInQuarterHours, unknown1, unknown2);
+            final HuamiSleepRespiratoryRateSample sample = new HuamiSleepRespiratoryRateSample();
+            sample.setTimestamp(timestamp.getTimeInMillis());
+            sample.setUtcOffset(utcOffsetInQuarterHours * 900000);
+            sample.setRate(respiratoryRate);
+            samples.add(sample);
+        }
+
+        return persistSamples(samples);
+    }
+
+    protected boolean persistSamples(final List<HuamiSleepRespiratoryRateSample> samples) {
+        try (DBHandler handler = GBApplication.acquireDB()) {
+            final DaoSession session = handler.getDaoSession();
+
+            final Device device = DBHelper.getDevice(getDevice(), session);
+            final User user = DBHelper.getUser(session);
+
+            final HuamiCoordinator coordinator = (HuamiCoordinator) getDevice().getDeviceCoordinator();
+            final HuamiSleepRespiratoryRateSampleProvider sampleProvider = coordinator.getRespiratoryRateSampleProvider(getDevice(), session);
+
+            for (final HuamiSleepRespiratoryRateSample sample : samples) {
+                sample.setDevice(device);
+                sample.setUser(user);
+            }
+
+            LOG.debug("Will persist {} sleep respiratory rate samples", samples.size());
+            sampleProvider.addSamples(samples);
+        } catch (final Exception e) {
+            GB.toast(getContext(), "Error saving sleep respiratory rate samples", Toast.LENGTH_LONG, GB.ERROR, e);
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    protected String getLastSyncTimeKey() {
+        return "lastSleepRespiratoryRateTimeMillis";
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/FetchSpo2NormalOperation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/FetchSpo2NormalOperation.java
new file mode 100644
index 0000000..0583fc9
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/FetchSpo2NormalOperation.java
@@ -0,0 +1,128 @@
+/*  Copyright (C) 2023-2024 Daniel Dakhno, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.fetch;
+
+import android.widget.Toast;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.GregorianCalendar;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiSpo2SampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.entities.HuamiSpo2Sample;
+import nodomain.freeyourgadget.gadgetbridge.entities.User;
+import nodomain.freeyourgadget.gadgetbridge.model.Spo2Sample;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+/**
+ * An operation that fetches SPO2 data for normal (manual and automatic) measurements.
+ */
+public class FetchSpo2NormalOperation extends AbstractRepeatingFetchOperation {
+    private static final Logger LOG = LoggerFactory.getLogger(FetchSpo2NormalOperation.class);
+
+    public FetchSpo2NormalOperation(final HuamiSupport support) {
+        super(support, HuamiFetchDataType.SPO2_NORMAL);
+    }
+
+    @Override
+    protected String taskDescription() {
+        return getContext().getString(R.string.busy_task_fetch_spo2_data);
+    }
+
+    @Override
+    protected boolean handleActivityData(final GregorianCalendar timestamp, final byte[] bytes) {
+        if ((bytes.length - 1) % 65 != 0) {
+            LOG.error("Unexpected length for spo2 data {}, not divisible by 65", bytes.length);
+            return false;
+        }
+
+        final ByteBuffer buf = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN);
+
+        final int version = buf.get() & 0xff;
+        if (version != 2) {
+            LOG.error("Unknown normal spo2 data version {}", version);
+            return false;
+        }
+
+        final List<HuamiSpo2Sample> samples = new ArrayList<>();
+
+        while (buf.position() < bytes.length) {
+            final long timestampSeconds = buf.getInt();
+            final byte spo2raw = buf.get();
+            final boolean autoMeasurement = (spo2raw < 0);
+            final byte spo2 = (byte) (spo2raw < 0 ? spo2raw + 128 : spo2raw);
+
+            final byte[] unknown = new byte[60]; // starts with a few spo2 values, but mostly zeroes after?
+            buf.get(unknown);
+
+            timestamp.setTimeInMillis(timestampSeconds * 1000L);
+
+            LOG.trace("SPO2 at {}: {} auto={}", timestamp.getTime(), spo2, autoMeasurement);
+
+            final HuamiSpo2Sample sample = new HuamiSpo2Sample();
+            sample.setTimestamp(timestamp.getTimeInMillis());
+            sample.setTypeNum((autoMeasurement ? Spo2Sample.Type.AUTOMATIC : Spo2Sample.Type.MANUAL).getNum());
+            sample.setSpo2(spo2);
+            samples.add(sample);
+        }
+
+        return persistSamples(samples);
+    }
+
+    protected boolean persistSamples(final List<HuamiSpo2Sample> samples) {
+        try (DBHandler handler = GBApplication.acquireDB()) {
+            final DaoSession session = handler.getDaoSession();
+
+            final Device device = DBHelper.getDevice(getDevice(), session);
+            final User user = DBHelper.getUser(session);
+
+            final HuamiCoordinator coordinator = (HuamiCoordinator) getDevice().getDeviceCoordinator();
+            final HuamiSpo2SampleProvider sampleProvider = coordinator.getSpo2SampleProvider(getDevice(), session);
+
+            for (final HuamiSpo2Sample sample : samples) {
+                sample.setDevice(device);
+                sample.setUser(user);
+            }
+
+            LOG.debug("Will persist {} normal spo2 samples", samples.size());
+            sampleProvider.addSamples(samples);
+        } catch (final Exception e) {
+            GB.toast(getContext(), "Error saving normal spo2 samples", Toast.LENGTH_LONG, GB.ERROR, e);
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    protected String getLastSyncTimeKey() {
+        return "lastSpo2normalTimeMillis";
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/FetchSpo2SleepOperation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/FetchSpo2SleepOperation.java
new file mode 100644
index 0000000..c013b52
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/FetchSpo2SleepOperation.java
@@ -0,0 +1,96 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.fetch;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.GregorianCalendar;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport;
+
+/**
+ * An operation that fetches SPO2 data for sleep measurements (this requires sleep breathing quality enabled).
+ */
+public class FetchSpo2SleepOperation extends AbstractRepeatingFetchOperation {
+    private static final Logger LOG = LoggerFactory.getLogger(FetchSpo2SleepOperation.class);
+
+    public FetchSpo2SleepOperation(final HuamiSupport support) {
+        super(support, HuamiFetchDataType.SPO2_SLEEP);
+    }
+
+    @Override
+    protected String taskDescription() {
+        return getContext().getString(R.string.busy_task_fetch_spo2_data);
+    }
+
+    @Override
+    protected boolean handleActivityData(final GregorianCalendar timestamp, final byte[] bytes) {
+        if ((bytes.length - 1) % 30 != 0) {
+            LOG.error("Unexpected length for sleep spo2 data {}, not divisible by 30", bytes.length);
+            return false;
+        }
+
+        final ByteBuffer buf = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN);
+
+        final int version = buf.get() & 0xff;
+        if (version != 2) {
+            LOG.error("Unknown sleep spo2 data version {}", version);
+            return false;
+        }
+
+        while (buf.position() < bytes.length) {
+            final long timestampSeconds = buf.getInt();
+            // this doesn't match the spo2 value returned by FetchSpo2NormalOperation.. it's often 100 when the other is 99, but not always
+            final int spo2 = buf.get() & 0xff;
+
+            final int duration = buf.get() & 0xff;
+            final byte[] spo2High = new byte[6];
+            final byte[] spo2Low = new byte[6];
+            final byte[] signalQuality = new byte[8];
+            final byte[] extend = new byte[4];
+            buf.get(spo2High);
+            buf.get(spo2Low);
+            buf.get(signalQuality);
+            buf.get(extend);
+
+            timestamp.setTimeInMillis(timestampSeconds * 1000L);
+
+            LOG.debug(
+                    "SPO2 (sleep) at {}: {} duration={} high={} low={} signalQuality={}, extend={}",
+                    timestamp.getTime(),
+                    spo2,
+                    duration,
+                    spo2High,
+                    spo2Low,
+                    signalQuality,
+                    extend
+            );
+            // TODO save
+        }
+
+        return true;
+    }
+
+    @Override
+    protected String getLastSyncTimeKey() {
+        return "lastSpo2sleepTimeMillis";
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/FetchSportsDetailsOperation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/FetchSportsDetailsOperation.java
new file mode 100644
index 0000000..f4663ff
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/FetchSportsDetailsOperation.java
@@ -0,0 +1,217 @@
+/*  Copyright (C) 2018-2024 Andreas Shimokawa, Carsten Pfeiffer, Daniele
+    Gobbetti, Jos茅 Rebelo, Oleg Vasilev, Sebastian Krey, Your Name
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.fetch;
+
+import android.text.format.DateUtils;
+import android.widget.Toast;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.GregorianCalendar;
+
+import androidx.annotation.NonNull;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary;
+import nodomain.freeyourgadget.gadgetbridge.export.ActivityTrackExporter;
+import nodomain.freeyourgadget.gadgetbridge.export.GPXExporter;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityTrack;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.AbstractHuamiActivityDetailsParser;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiActivityDetailsParser;
+import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+/**
+ * An operation that fetches activity data. For every fetch, a new operation must
+ * be created, i.e. an operation may not be reused for multiple fetches.
+ */
+public class FetchSportsDetailsOperation extends AbstractFetchOperation {
+    private static final Logger LOG = LoggerFactory.getLogger(FetchSportsDetailsOperation.class);
+    private final AbstractHuamiActivityDetailsParser detailsParser;
+    private final BaseActivitySummary summary;
+    private final String lastSyncTimeKey;
+
+    FetchSportsDetailsOperation(@NonNull BaseActivitySummary summary,
+                                @NonNull AbstractHuamiActivityDetailsParser detailsParser,
+                                @NonNull HuamiSupport support,
+                                @NonNull String lastSyncTimeKey,
+                                int fetchCount) {
+        super(support);
+        setName("fetching sport details");
+        this.summary = summary;
+        this.detailsParser = detailsParser;
+        this.lastSyncTimeKey = lastSyncTimeKey;
+        this.fetchCount = fetchCount;
+    }
+
+    @Override
+    protected String taskDescription() {
+        return getContext().getString(R.string.busy_task_fetch_sports_details);
+    }
+
+    @Override
+    protected void startFetching(TransactionBuilder builder) {
+        LOG.info("start " + getName());
+        final GregorianCalendar sinceWhen = getLastSuccessfulSyncTime();
+        startFetching(builder, HuamiFetchDataType.SPORTS_DETAILS.getCode(), sinceWhen);
+    }
+
+    @Override
+    protected boolean processBufferedData() {
+        LOG.info("{} has finished round {}", getName(), fetchCount);
+
+        if (buffer.size() == 0) {
+            LOG.warn("Buffer is empty");
+            return false;
+        }
+
+        if (detailsParser instanceof HuamiActivityDetailsParser) {
+            ((HuamiActivityDetailsParser) detailsParser).setSkipCounterByte(false); // is already stripped
+        }
+
+        try {
+            final ActivityTrack track = detailsParser.parse(buffer.toByteArray());
+            final ActivityTrackExporter exporter = new GPXExporter();
+            final String trackType;
+            switch (ActivityKind.fromCode(summary.getActivityKind())) {
+                case CYCLING:
+                    trackType = getContext().getString(R.string.activity_type_biking);
+                    break;
+                case RUNNING:
+                    trackType = getContext().getString(R.string.activity_type_running);
+                    break;
+                case WALKING:
+                    trackType = getContext().getString(R.string.activity_type_walking);
+                    break;
+                case HIKING:
+                    trackType = getContext().getString(R.string.activity_type_hiking);
+                    break;
+                case CLIMBING:
+                    trackType = getContext().getString(R.string.activity_type_climbing);
+                    break;
+                case SWIMMING:
+                    trackType = getContext().getString(R.string.activity_type_swimming);
+                    break;
+                default:
+                    trackType = "track";
+                    break;
+            }
+
+            final String rawBytesPath = saveRawBytes();
+
+            final String fileName = FileUtils.makeValidFileName("gadgetbridge-" + trackType.toLowerCase() + "-" + DateTimeUtils.formatIso8601(summary.getStartTime()) + ".gpx");
+            final File targetFile = new File(FileUtils.getExternalFilesDir(), fileName);
+
+            boolean exportGpxSuccess = true;
+            try {
+                exporter.performExport(track, targetFile);
+            } catch (final ActivityTrackExporter.GPXTrackEmptyException ex) {
+                exportGpxSuccess = false;
+            }
+
+            try (DBHandler dbHandler = GBApplication.acquireDB()) {
+                if (exportGpxSuccess) {
+                    summary.setGpxTrack(targetFile.getAbsolutePath());
+                }
+                if (rawBytesPath != null) {
+                    summary.setRawDetailsPath(rawBytesPath);
+                }
+                dbHandler.getDaoSession().getBaseActivitySummaryDao().update(summary);
+            }
+        } catch (final Exception e) {
+            GB.toast(getContext(), "Error saving activity details: " + e.getMessage(), Toast.LENGTH_LONG, GB.ERROR, e);
+            return false;
+        }
+
+        // Always increment the sync timestamp on success, even if we did not get data
+        final GregorianCalendar endTime = BLETypeConversions.createCalendar();
+        endTime.setTime(summary.getEndTime());
+        saveLastSyncTimestamp(endTime);
+
+        if (needsAnotherFetch(endTime)) {
+            final FetchSportsSummaryOperation nextOperation = new FetchSportsSummaryOperation(getSupport(), fetchCount);
+            getSupport().getFetchOperationQueue().add(0, nextOperation);
+        }
+
+        return true;
+    }
+
+    private boolean needsAnotherFetch(GregorianCalendar lastSyncTimestamp) {
+        // We have 2 operations per fetch round: summary + details
+        if (fetchCount > 10) {
+            LOG.warn("Already have 5 fetch rounds, not doing another one.");
+            return false;
+        }
+
+        if (DateUtils.isToday(lastSyncTimestamp.getTimeInMillis())) {
+            LOG.info("Hopefully no further fetch needed, last synced timestamp is from today.");
+            return false;
+        }
+
+        if (lastSyncTimestamp.getTimeInMillis() > System.currentTimeMillis()) {
+            LOG.warn("Not doing another fetch since last synced timestamp is in the future: {}", DateTimeUtils.formatDateTime(lastSyncTimestamp.getTime()));
+            return false;
+        }
+
+        LOG.info("Doing another fetch since last sync timestamp is still too old: {}", DateTimeUtils.formatDateTime(lastSyncTimestamp.getTime()));
+        return true;
+    }
+
+    @Override
+    protected String getLastSyncTimeKey() {
+        return lastSyncTimeKey;
+    }
+
+    @Override
+    protected GregorianCalendar getLastSuccessfulSyncTime() {
+        final GregorianCalendar calendar = BLETypeConversions.createCalendar();
+        calendar.setTime(summary.getStartTime());
+        return calendar;
+    }
+
+    private String saveRawBytes() {
+        final String fileName = FileUtils.makeValidFileName(String.format("%s.bin", DateTimeUtils.formatIso8601(summary.getStartTime())));
+        FileOutputStream outputStream = null;
+
+        try {
+            final File targetFolder = new File(FileUtils.getExternalFilesDir(), "rawDetails");
+            //noinspection ResultOfMethodCallIgnored
+            targetFolder.mkdirs();
+            final File targetFile = new File(targetFolder, fileName);
+            outputStream = new FileOutputStream(targetFile);
+            outputStream.write(buffer.toByteArray());
+            outputStream.close();
+            return targetFile.getAbsolutePath();
+        } catch (final IOException e) {
+            LOG.error("Failed to save raw bytes", e);
+        }
+
+        return null;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/FetchSportsSummaryOperation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/FetchSportsSummaryOperation.java
new file mode 100644
index 0000000..4617930
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/FetchSportsSummaryOperation.java
@@ -0,0 +1,119 @@
+/*  Copyright (C) 2018-2024 Andreas Shimokawa, Carsten Pfeiffer, Daniel
+    Dakhno, Daniele Gobbetti, Jos茅 Rebelo, Oleg Vasilev, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.fetch;
+
+import android.widget.Toast;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.GregorianCalendar;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiActivitySummaryParser;
+import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.entities.User;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryParser;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.AbstractHuamiActivityDetailsParser;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+/**
+ * An operation that fetches activity data. For every fetch, a new operation must
+ * be created, i.e. an operation may not be reused for multiple fetches.
+ */
+public class FetchSportsSummaryOperation extends AbstractFetchOperation {
+    private static final Logger LOG = LoggerFactory.getLogger(FetchSportsSummaryOperation.class);
+
+    public FetchSportsSummaryOperation(HuamiSupport support, int fetchCount) {
+        super(support);
+        setName("fetching sport summaries");
+        this.fetchCount = fetchCount;
+    }
+
+    @Override
+    protected String taskDescription() {
+        return getContext().getString(R.string.busy_task_fetch_sports_summaries);
+    }
+
+    @Override
+    protected void startFetching(TransactionBuilder builder) {
+        LOG.info("start" + getName());
+        final GregorianCalendar sinceWhen = getLastSuccessfulSyncTime();
+        startFetching(builder, HuamiFetchDataType.SPORTS_SUMMARIES.getCode(), sinceWhen);
+    }
+
+    @Override
+    protected boolean processBufferedData() {
+        LOG.info("{} has finished round {}", getName(), fetchCount);
+
+        if (buffer.size() < 2) {
+            LOG.warn("Buffer size {} too small for activity summary", buffer.size());
+            return false;
+        }
+
+        final DeviceCoordinator coordinator = getDevice().getDeviceCoordinator();
+        final ActivitySummaryParser summaryParser = coordinator.getActivitySummaryParser(getDevice(), getContext());
+
+        BaseActivitySummary summary = new BaseActivitySummary();
+        summary.setStartTime(getLastStartTimestamp().getTime()); // due to a bug this has to be set
+        summary.setRawSummaryData(buffer.toByteArray());
+        try {
+            summary = summaryParser.parseBinaryData(summary, true);
+        } catch (final Exception e) {
+            GB.toast(getContext(), "Failed to parse activity summary", Toast.LENGTH_LONG, GB.ERROR, e);
+            return false;
+        }
+
+        if (summary == null) {
+            return false;
+        }
+
+        summary.setSummaryData(null); // remove json before saving to database,
+        try (DBHandler dbHandler = GBApplication.acquireDB()) {
+            final DaoSession session = dbHandler.getDaoSession();
+            final Device device = DBHelper.getDevice(getDevice(), session);
+            final User user = DBHelper.getUser(session);
+            summary.setDevice(device);
+            summary.setUser(user);
+            summary.setRawSummaryData(buffer.toByteArray());
+            session.getBaseActivitySummaryDao().insertOrReplace(summary);
+        } catch (final Exception ex) {
+            GB.toast(getContext(), "Error saving activity summary", Toast.LENGTH_LONG, GB.ERROR, ex);
+            return false;
+        }
+
+        final AbstractHuamiActivityDetailsParser detailsParser = ((HuamiActivitySummaryParser) summaryParser).getDetailsParser(summary);
+        final FetchSportsDetailsOperation nextOperation = new FetchSportsDetailsOperation(summary, detailsParser, getSupport(), getLastSyncTimeKey(), fetchCount);
+        getSupport().getFetchOperationQueue().add(0, nextOperation);
+
+        return true;
+    }
+
+    @Override
+    protected String getLastSyncTimeKey() {
+        return "lastSportsActivityTimeMillis";
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/FetchStatisticsOperation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/FetchStatisticsOperation.java
new file mode 100644
index 0000000..9c8cfc4
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/FetchStatisticsOperation.java
@@ -0,0 +1,57 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.fetch;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Date;
+import java.util.GregorianCalendar;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport;
+
+/**
+ * An operation that fetches statistics from /storage/statistics/ (hm_statis_data* files). We do not
+ * know what these are or how to parse them, but syncing them helps free up memory.
+ */
+public class FetchStatisticsOperation extends AbstractRepeatingFetchOperation {
+    private static final Logger LOG = LoggerFactory.getLogger(FetchStatisticsOperation.class);
+
+    public FetchStatisticsOperation(final HuamiSupport support) {
+        super(support, HuamiFetchDataType.STATISTICS);
+    }
+
+    @Override
+    protected String taskDescription() {
+        return getContext().getString(R.string.busy_task_fetch_statistics);
+    }
+
+    @Override
+    protected boolean handleActivityData(final GregorianCalendar timestamp, final byte[] bytes) {
+        LOG.debug("Ignoring {} bytes of statistics", bytes.length);
+
+        timestamp.setTime(new Date());
+
+        return true;
+    }
+
+    @Override
+    protected String getLastSyncTimeKey() {
+        return "lastStatisticsTimeMillis";
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/FetchStressAutoOperation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/FetchStressAutoOperation.java
new file mode 100644
index 0000000..7f7bd1e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/FetchStressAutoOperation.java
@@ -0,0 +1,119 @@
+/*  Copyright (C) 2023-2024 Daniel Dakhno, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.fetch;
+
+import android.widget.Toast;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiStressSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.entities.HuamiStressSample;
+import nodomain.freeyourgadget.gadgetbridge.entities.User;
+import nodomain.freeyourgadget.gadgetbridge.model.StressSample;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+/**
+ * An operation that fetches auto stress data.
+ */
+public class FetchStressAutoOperation extends AbstractRepeatingFetchOperation {
+    private static final Logger LOG = LoggerFactory.getLogger(FetchStressAutoOperation.class);
+
+    public FetchStressAutoOperation(final HuamiSupport support) {
+        super(support, HuamiFetchDataType.STRESS_AUTOMATIC);
+    }
+
+    @Override
+    protected String taskDescription() {
+        return getContext().getString(R.string.busy_task_fetch_stress_data);
+    }
+
+    @Override
+    protected boolean handleActivityData(final GregorianCalendar timestamp, final byte[] bytes) {
+        final List<HuamiStressSample> samples = new ArrayList<>();
+
+        for (byte b : bytes) {
+            if (b == -1) {
+                timestamp.add(Calendar.MINUTE, 1);
+                continue;
+            }
+
+            // 0-39 = relaxed
+            // 40-59 = mild
+            // 60-79 = moderate
+            // 80-100 = high
+            final int stress = b & 0xff;
+
+            LOG.trace("Stress (auto) at {}: {}", timestamp.getTime(), stress);
+
+            final HuamiStressSample sample = new HuamiStressSample();
+            sample.setTimestamp(timestamp.getTimeInMillis());
+            sample.setTypeNum(StressSample.Type.AUTOMATIC.getNum());
+            sample.setStress(stress);
+            samples.add(sample);
+
+            timestamp.add(Calendar.MINUTE, 1);
+        }
+
+        timestamp.add(Calendar.MINUTE, -1);
+
+        return persistSamples(samples);
+    }
+
+    protected boolean persistSamples(final List<HuamiStressSample> samples) {
+        try (DBHandler handler = GBApplication.acquireDB()) {
+            final DaoSession session = handler.getDaoSession();
+
+            final Device device = DBHelper.getDevice(getDevice(), session);
+            final User user = DBHelper.getUser(session);
+
+            final HuamiCoordinator coordinator = (HuamiCoordinator) getDevice().getDeviceCoordinator();
+            final HuamiStressSampleProvider sampleProvider = coordinator.getStressSampleProvider(getDevice(), session);
+
+            for (final HuamiStressSample sample : samples) {
+                sample.setDevice(device);
+                sample.setUser(user);
+            }
+
+            LOG.debug("Will persist {} auto stress samples", samples.size());
+            sampleProvider.addSamples(samples);
+        } catch (final Exception e) {
+            GB.toast(getContext(), "Error saving auto stress samples", Toast.LENGTH_LONG, GB.ERROR, e);
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    protected String getLastSyncTimeKey() {
+        return "lastStressAutoTimeMillis";
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/FetchStressManualOperation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/FetchStressManualOperation.java
new file mode 100644
index 0000000..ba40c9d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/FetchStressManualOperation.java
@@ -0,0 +1,123 @@
+/*  Copyright (C) 2023-2024 Daniel Dakhno, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.fetch;
+
+import android.widget.Toast;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.GregorianCalendar;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiStressSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.entities.HuamiStressSample;
+import nodomain.freeyourgadget.gadgetbridge.entities.User;
+import nodomain.freeyourgadget.gadgetbridge.model.StressSample;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+/**
+ * An operation that fetches manual stress data.
+ */
+public class FetchStressManualOperation extends AbstractRepeatingFetchOperation {
+    private static final Logger LOG = LoggerFactory.getLogger(FetchStressManualOperation.class);
+
+    public FetchStressManualOperation(final HuamiSupport support) {
+        super(support, HuamiFetchDataType.STRESS_MANUAL);
+    }
+
+    @Override
+    protected String taskDescription() {
+        return getContext().getString(R.string.busy_task_fetch_stress_data);
+    }
+
+    @Override
+    protected boolean handleActivityData(final GregorianCalendar timestamp, final byte[] bytes) {
+        if (bytes.length % 5 != 0) {
+            LOG.info("Unexpected buffered stress data size {} is not a multiple of 5", bytes.length);
+            return false;
+        }
+
+        final ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN);
+        final GregorianCalendar lastSyncTimestamp = new GregorianCalendar();
+
+        final List<HuamiStressSample> samples = new ArrayList<>();
+
+        while (buffer.position() < bytes.length) {
+            final long currentTimestamp = BLETypeConversions.toUnsigned(buffer.getInt()) * 1000;
+
+            // 0-39 = relaxed
+            // 40-59 = mild
+            // 60-79 = moderate
+            // 80-100 = high
+            final int stress = buffer.get() & 0xff;
+            timestamp.setTimeInMillis(currentTimestamp);
+
+            LOG.trace("Stress (manual) at {}: {}", lastSyncTimestamp.getTime(), stress);
+
+            final HuamiStressSample sample = new HuamiStressSample();
+            sample.setTimestamp(timestamp.getTimeInMillis());
+            sample.setTypeNum(StressSample.Type.MANUAL.getNum());
+            sample.setStress(stress);
+            samples.add(sample);
+        }
+
+        return persistSamples(samples);
+    }
+
+    protected boolean persistSamples(final List<HuamiStressSample> samples) {
+        try (DBHandler handler = GBApplication.acquireDB()) {
+            final DaoSession session = handler.getDaoSession();
+
+            final Device device = DBHelper.getDevice(getDevice(), session);
+            final User user = DBHelper.getUser(session);
+
+            final HuamiCoordinator coordinator = (HuamiCoordinator) getDevice().getDeviceCoordinator();
+            final HuamiStressSampleProvider sampleProvider = coordinator.getStressSampleProvider(getDevice(), session);
+
+            for (final HuamiStressSample sample : samples) {
+                sample.setDevice(device);
+                sample.setUser(user);
+            }
+
+            LOG.debug("Will persist {} manual stress samples", samples.size());
+            sampleProvider.addSamples(samples);
+        } catch (final Exception e) {
+            GB.toast(getContext(), "Error saving manual stress samples", Toast.LENGTH_LONG, GB.ERROR, e);
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    protected String getLastSyncTimeKey() {
+        return "lastStressManualTimeMillis";
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/FetchTemperatureOperation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/FetchTemperatureOperation.java
new file mode 100644
index 0000000..ae7d984
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/FetchTemperatureOperation.java
@@ -0,0 +1,70 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.fetch;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.GregorianCalendar;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+/**
+ * An operation that fetches temperature data.
+ */
+public class FetchTemperatureOperation extends AbstractRepeatingFetchOperation {
+    private static final Logger LOG = LoggerFactory.getLogger(FetchTemperatureOperation.class);
+
+    public FetchTemperatureOperation(final HuamiSupport support) {
+        super(support, HuamiFetchDataType.TEMPERATURE);
+    }
+
+    @Override
+    protected String taskDescription() {
+        return getContext().getString(R.string.busy_task_fetch_temperature);
+    }
+
+    @Override
+    protected boolean handleActivityData(final GregorianCalendar timestamp, final byte[] bytes) {
+        if (bytes.length % 8 != 0) {
+            LOG.info("Unexpected buffered temperature data size {} is not a multiple of 8", bytes.length);
+            return false;
+        }
+
+        final ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN);
+
+        while (buffer.position() < bytes.length) {
+            final int temperature = buffer.getShort();
+            final byte[] unknown = new byte[6];
+            buffer.get(unknown);
+
+            // TODO persist / parse rest
+            LOG.warn("Temperature: {}, unknown={}", temperature, GB.hexdump(unknown));
+        }
+
+        return false;
+    }
+
+    @Override
+    protected String getLastSyncTimeKey() {
+        return "lastTemperatureTimeMillis";
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/HuamiFetchDataType.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/HuamiFetchDataType.java
new file mode 100644
index 0000000..ed5b269
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/HuamiFetchDataType.java
@@ -0,0 +1,46 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.fetch;
+
+public enum HuamiFetchDataType {
+    ACTIVITY(0x01),
+    MANUAL_HEART_RATE(0x02),
+    SPORTS_SUMMARIES(0x05),
+    SPORTS_DETAILS(0x06),
+    DEBUG_LOGS(0x07),
+    PAI(0x0d),
+    STRESS_MANUAL(0x12),
+    STRESS_AUTOMATIC(0x13),
+    SPO2_NORMAL(0x25),
+    SPO2_SLEEP(0x26),
+    STATISTICS(0x2c),
+    TEMPERATURE(0x2e),
+    SLEEP_RESPIRATORY_RATE(0x38),
+    RESTING_HEART_RATE(0x3a),
+    MAX_HEART_RATE(0x3d),
+    ;
+
+    private final byte code;
+
+    HuamiFetchDataType(final int code) {
+        this.code = (byte) code;
+    }
+
+    public byte getCode() {
+        return code;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/init/InitOperation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/init/InitOperation.java
new file mode 100644
index 0000000..09929f4
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/init/InitOperation.java
@@ -0,0 +1,181 @@
+/*  Copyright (C) 2018-2024 Andreas Shimokawa, Carsten Pfeiffer, Damien
+    Gaignon, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.init;
+
+import android.annotation.SuppressLint;
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.content.SharedPreferences;
+import android.widget.Toast;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
+import java.util.UUID;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.spec.SecretKeySpec;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEOperation;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class InitOperation extends AbstractBTLEOperation<HuamiSupport> {
+    private static final Logger LOG = LoggerFactory.getLogger(InitOperation.class);
+
+    protected final TransactionBuilder builder;
+    private final boolean needsAuth;
+    private final byte authFlags;
+    private final byte cryptFlags;
+    protected final HuamiSupport huamiSupport;
+
+    public InitOperation(boolean needsAuth, byte authFlags, byte cryptFlags, HuamiSupport support, TransactionBuilder builder) {
+        super(support);
+        this.huamiSupport = support;
+        this.needsAuth = needsAuth;
+        this.authFlags = authFlags;
+        this.cryptFlags = cryptFlags;
+        this.builder = builder;
+        builder.setCallback(this);
+    }
+
+    @Override
+    protected void doPerform() {
+        huamiSupport.enableNotifications(builder, true);
+        if (needsAuth) {
+            builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.AUTHENTICATING, getContext()));
+            // write key to device
+            byte[] sendKey = org.apache.commons.lang3.ArrayUtils.addAll(new byte[]{HuamiService.AUTH_SEND_KEY, authFlags}, getSecretKey());
+            builder.write(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_AUTH), sendKey);
+        } else {
+            builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext()));
+            // get random auth number
+            builder.write(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_AUTH), requestAuthNumber());
+        }
+    }
+
+    private byte[] requestAuthNumber() {
+        if (cryptFlags == 0x00) {
+            return new byte[]{HuamiService.AUTH_REQUEST_RANDOM_AUTH_NUMBER, authFlags};
+        } else {
+            return new byte[]{(byte) (cryptFlags | HuamiService.AUTH_REQUEST_RANDOM_AUTH_NUMBER), authFlags, 0x02, 0x01, 0x00};
+        }
+    }
+
+    protected byte[] getSecretKey() {
+        byte[] authKeyBytes = new byte[]{0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45};
+
+        SharedPreferences sharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress());
+
+        String authKey = sharedPrefs.getString("authkey", null);
+        if (authKey != null && !authKey.isEmpty()) {
+            byte[] srcBytes = authKey.trim().getBytes();
+            if (authKey.length() == 34 && authKey.startsWith("0x")) {
+                srcBytes = GB.hexStringToByteArray(authKey.substring(2));
+            }
+            System.arraycopy(srcBytes, 0, authKeyBytes, 0, Math.min(srcBytes.length, 16));
+        }
+
+        return authKeyBytes;
+    }
+
+    @Override
+    public TransactionBuilder performInitialized(String taskName) {
+        throw new UnsupportedOperationException("This IS the initialization class, you cannot call this method");
+    }
+
+    @Override
+    public boolean onCharacteristicChanged(BluetoothGatt gatt,
+                                           BluetoothGattCharacteristic characteristic) {
+        UUID characteristicUUID = characteristic.getUuid();
+        if (!HuamiService.UUID_CHARACTERISTIC_AUTH.equals(characteristicUUID)) {
+            LOG.info("Unhandled characteristic changed: {}", characteristicUUID);
+            return super.onCharacteristicChanged(gatt, characteristic);
+        }
+
+        try {
+            final byte[] value = characteristic.getValue();
+            huamiSupport.logMessageContent(value);
+            if (value[0] != HuamiService.AUTH_RESPONSE) {
+                LOG.warn("Got a non-response: {}", GB.hexdump(value));
+                return super.onCharacteristicChanged(gatt, characteristic);
+            }
+
+            if (value[1] == HuamiService.AUTH_SEND_KEY && value[2] == HuamiService.AUTH_SUCCESS) {
+                TransactionBuilder builder = createTransactionBuilder("Sending the secret key to the device");
+                builder.write(characteristic, requestAuthNumber());
+                huamiSupport.performImmediately(builder);
+            } else if ((value[1] & 0x0f) == HuamiService.AUTH_REQUEST_RANDOM_AUTH_NUMBER && value[2] == HuamiService.AUTH_SUCCESS) {
+                byte[] eValue = handleAESAuth(value, getSecretKey());
+                byte[] responseValue = org.apache.commons.lang3.ArrayUtils.addAll(
+                        new byte[]{(byte) (HuamiService.AUTH_SEND_ENCRYPTED_AUTH_NUMBER | cryptFlags), authFlags}, eValue);
+
+                TransactionBuilder builder = createTransactionBuilder("Sending the encrypted random key to the device");
+                builder.write(characteristic, responseValue);
+                huamiSupport.setCurrentTimeWithService(builder);
+                huamiSupport.performImmediately(builder);
+            } else if ((value[1] & 0x0f) == HuamiService.AUTH_SEND_ENCRYPTED_AUTH_NUMBER) {
+                if (value[2] == HuamiService.AUTH_SUCCESS) {
+                    TransactionBuilder builder = createTransactionBuilder("Authenticated, now initialize phase 2");
+                    builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext()));
+                    builder.setCallback(null); // remove init operation as the callback
+                    huamiSupport.enableFurtherNotifications(builder, true);
+                    huamiSupport.requestDeviceInfo(builder);
+                    huamiSupport.phase2Initialize(builder);
+                    huamiSupport.phase3Initialize(builder);
+                    huamiSupport.setInitialized(builder);
+                    huamiSupport.performImmediately(builder);
+                } else if (value[2] == HuamiService.AUTH_FAIL) {
+                    LOG.error("Authentication failed, disconnecting");
+                    GB.toast(getContext(), R.string.authentication_failed_check_key, Toast.LENGTH_LONG, GB.WARN);
+                    final GBDevice device = getDevice();
+                    if (device != null) {
+                        GBApplication.deviceService(device).disconnect();
+                    }
+                } else {
+                    return super.onCharacteristicChanged(gatt, characteristic);
+                }
+            } else {
+                return super.onCharacteristicChanged(gatt, characteristic);
+            }
+        } catch (Exception e) {
+            GB.toast(getContext(), "Error authenticating Huami device", Toast.LENGTH_LONG, GB.ERROR, e);
+        }
+        return true;
+    }
+
+    private byte[] handleAESAuth(byte[] value, byte[] secretKey) throws InvalidKeyException, NoSuchPaddingException, NoSuchAlgorithmException, BadPaddingException, IllegalBlockSizeException {
+        byte[] mValue = Arrays.copyOfRange(value, 3, 19);
+        @SuppressLint("GetInstance") Cipher ecipher = Cipher.getInstance("AES/ECB/NoPadding");
+        SecretKeySpec newKey = new SecretKeySpec(secretKey, "AES");
+        ecipher.init(Cipher.ENCRYPT_MODE, newKey);
+        return ecipher.doFinal(mValue);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/init/InitOperation2021.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/init/InitOperation2021.java
new file mode 100644
index 0000000..771d0f9
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/init/InitOperation2021.java
@@ -0,0 +1,209 @@
+/*  Copyright (C) 2021-2024 Andreas Shimokawa, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.init;
+
+import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService.RESPONSE;
+import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService.SUCCESS;
+
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.widget.Toast;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Random;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.BuildConfig;
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.Huami2021Service;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.Huami2021ChunkedDecoder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.Huami2021ChunkedEncoder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.Huami2021Handler;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport;
+import nodomain.freeyourgadget.gadgetbridge.util.CryptoUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.ECDH_B163;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class InitOperation2021 extends InitOperation implements Huami2021Handler {
+    private byte[] privateEC = new byte[24];
+    private byte[] publicEC;
+    private byte[] remotePublicEC = new byte[48];
+    private final byte[] remoteRandom = new byte[16];
+    private byte[] sharedEC;
+    private final byte[] finalSharedSessionAES = new byte[16];
+
+    private final Huami2021ChunkedEncoder huami2021ChunkedEncoder;
+    private final Huami2021ChunkedDecoder huami2021ChunkedDecoder;
+
+    private static final Logger LOG = LoggerFactory.getLogger(InitOperation2021.class);
+
+    public InitOperation2021(final boolean needsAuth,
+                             final byte authFlags,
+                             final byte cryptFlags,
+                             final HuamiSupport support,
+                             final TransactionBuilder builder,
+                             final Huami2021ChunkedEncoder huami2021ChunkedEncoder,
+                             final Huami2021ChunkedDecoder huami2021ChunkedDecoder) {
+        super(needsAuth, authFlags, cryptFlags, support, builder);
+        this.huami2021ChunkedEncoder = huami2021ChunkedEncoder;
+        this.huami2021ChunkedDecoder = huami2021ChunkedDecoder;
+        this.huami2021ChunkedDecoder.setHuami2021Handler(this);
+    }
+
+    private void testAuth() {
+        byte[] secretKey = getSecretKey();
+        privateEC = new byte[]{0x0b, 0x42, (byte) 0xb9, (byte) 0xe6, 0x1c, 0x23, 0x34, 0x0e, 0x35, (byte) 0xc1, 0x6e, 0x2e, 0x7d, (byte) 0xe4, 0x33, (byte) 0xf4, (byte) 0xb5, (byte) 0x85, (byte) 0x9a, 0x72, (byte) 0xec, 0x11, 0x40, 0x27};
+        remotePublicEC = new byte[]{(byte) 0xe6, 0x01, 0x6a, (byte) 0xba, 0x1d, (byte) 0xe7, (byte) 0xac, 0x0f, 0x0c, 0x7f, 0x0f, (byte) 0xf7, (byte) 0xe2, 0x24, 0x3e, 0x66, 0x62, (byte) 0xb5, (byte) 0xe0, 0x3b, 0x01, 0x00, 0x00, 0x00, (byte) 0xad, (byte) 0x8a, 0x4b, (byte) 0xed, (byte) 0xc7, 0x6a, 0x1e, (byte) 0xfd, (byte) 0xe7, 0x72, 0x5c, (byte) 0xc6, 0x62, (byte) 0xb5, 0x48, 0x35, 0x51, 0x3e, 0x3d, 0x57, 0x05, 0x00, 0x00, 0x00};
+
+        publicEC = ECDH_B163.ecdh_generate_public(privateEC);
+        sharedEC = ECDH_B163.ecdh_generate_shared(privateEC, remotePublicEC);
+        LOG.warn("publicEC: " + GB.hexdump(publicEC));
+        LOG.warn("privateEC: " + GB.hexdump(privateEC));
+        LOG.warn("remotepubEC: " + GB.hexdump(remotePublicEC));
+        LOG.warn("sharedEC: " + GB.hexdump(sharedEC));
+        for (int i = 0; i < 16; i++) {
+            finalSharedSessionAES[i] = (byte) (sharedEC[i + 8] ^ secretKey[i]);
+        }
+        LOG.warn("finalSharedAES: " + GB.hexdump(finalSharedSessionAES));
+    }
+
+    @Override
+    protected void doPerform() {
+        huamiSupport.enableNotifications(builder, true);
+        builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext()));
+        // get random auth number
+        generateKeyPair();
+        byte[] sendPubkeyCommand = new byte[48 + 4];
+        sendPubkeyCommand[0] = 0x04;
+        sendPubkeyCommand[1] = 0x02;
+        sendPubkeyCommand[2] = 0x00;
+        sendPubkeyCommand[3] = 0x02;
+        System.arraycopy(publicEC, 0, sendPubkeyCommand, 4, 48);
+        //testAuth();
+        huami2021ChunkedEncoder.write(builder, Huami2021Service.CHUNKED2021_ENDPOINT_AUTH, sendPubkeyCommand, true, false);
+    }
+
+    private void generateKeyPair() {
+        Random r = new Random();
+        r.nextBytes(privateEC);
+        publicEC = ECDH_B163.ecdh_generate_public(privateEC);
+    }
+
+    @Override
+    public TransactionBuilder performInitialized(String taskName) {
+        throw new UnsupportedOperationException("This IS the initialization class, you cannot call this method");
+    }
+
+    @Override
+    public boolean onCharacteristicChanged(BluetoothGatt gatt,
+                                           BluetoothGattCharacteristic characteristic) {
+        UUID characteristicUUID = characteristic.getUuid();
+        if (!HuamiService.UUID_CHARACTERISTIC_CHUNKEDTRANSFER_2021_READ.equals(characteristicUUID)) {
+            LOG.info("Unhandled characteristic changed: " + characteristicUUID);
+            return super.onCharacteristicChanged(gatt, characteristic);
+        }
+
+        byte[] value = characteristic.getValue();
+        if (value.length <= 1 || value[0] != 0x03) {
+            // Not chunked
+            return super.onCharacteristicChanged(gatt, characteristic);
+        }
+
+        final boolean needsAck = huami2021ChunkedDecoder.decode(value);
+        if (needsAck) {
+            huamiSupport.sendChunkedAck();
+        }
+
+        return true;
+    }
+
+    @Override
+    public void handle2021Payload(final short type, final byte[] payload) {
+        if (type != Huami2021Service.CHUNKED2021_ENDPOINT_AUTH) {
+            this.huamiSupport.handle2021Payload(type, payload);
+            return;
+        }
+
+        if (payload[0] == RESPONSE && payload[1] == 0x04 && payload[2] == SUCCESS) {
+            LOG.debug("Got remote random + public key");
+            // Received remote random (16 bytes) + public key (48 bytes)
+
+            System.arraycopy(payload, 3, remoteRandom, 0, 16);
+            System.arraycopy(payload, 19, remotePublicEC, 0, 48);
+            sharedEC = ECDH_B163.ecdh_generate_shared(privateEC, remotePublicEC);
+            int encryptedSequenceNumber = (sharedEC[0] & 0xff) | ((sharedEC[1] & 0xff) << 8) | ((sharedEC[2] & 0xff) << 16) | ((sharedEC[3] & 0xff) << 24);
+
+            byte[] secretKey = getSecretKey();
+            for (int i = 0; i < 16; i++) {
+                finalSharedSessionAES[i] = (byte) (sharedEC[i + 8] ^ secretKey[i]);
+            }
+
+            LOG.debug("Shared Session Key: {}", GB.hexdump(finalSharedSessionAES));
+            huami2021ChunkedEncoder.setEncryptionParameters(encryptedSequenceNumber, finalSharedSessionAES);
+            huami2021ChunkedDecoder.setEncryptionParameters(finalSharedSessionAES);
+
+            try {
+                byte[] encryptedRandom1 = CryptoUtils.encryptAES(remoteRandom, secretKey);
+                byte[] encryptedRandom2 = CryptoUtils.encryptAES(remoteRandom, finalSharedSessionAES);
+                if (encryptedRandom1.length == 16 && encryptedRandom2.length == 16) {
+                    byte[] command = new byte[33];
+                    command[0] = 0x05;
+                    System.arraycopy(encryptedRandom1, 0, command, 1, 16);
+                    System.arraycopy(encryptedRandom2, 0, command, 17, 16);
+                    TransactionBuilder builder = createTransactionBuilder("Sending double encryted random to device");
+                    huami2021ChunkedEncoder.write(builder, Huami2021Service.CHUNKED2021_ENDPOINT_AUTH, command, true, false);
+                    huamiSupport.performImmediately(builder);
+                }
+            } catch (Exception e) {
+                LOG.error("AES encryption failed", e);
+            }
+        } else if (payload[0] == RESPONSE && payload[1] == 0x05 && payload[2] == SUCCESS) {
+            LOG.debug("Auth Success");
+
+            try {
+                TransactionBuilder builder = createTransactionBuilder("Authenticated, now initialize phase 2");
+                builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext()));
+                builder.setCallback(null); // remove init operation as the callback
+                huamiSupport.enableFurtherNotifications(builder, true);
+                huamiSupport.setCurrentTimeWithService(builder);
+                huamiSupport.requestDeviceInfo(builder);
+                huamiSupport.phase2Initialize(builder);
+                huamiSupport.phase3Initialize(builder);
+                huamiSupport.setInitialized(builder);
+                huamiSupport.performImmediately(builder);
+            } catch (Exception e) {
+                LOG.error("failed initializing device", e);
+            }
+        } else if (payload[0] == RESPONSE && payload[1] == 0x05 && payload[2] == 0x25) {
+            LOG.error("Authentication failed, disconnecting");
+            GB.toast(getContext(), R.string.authentication_failed_check_key, Toast.LENGTH_LONG, GB.WARN);
+            final GBDevice device = getDevice();
+            if (device != null) {
+                GBApplication.deviceService(device).disconnect();
+            }
+        } else {
+            LOG.warn("Unhandled auth payload: {}", GB.hexdump(payload));
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/update/UpdateFirmwareOperation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/update/UpdateFirmwareOperation.java
new file mode 100644
index 0000000..bad786a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/update/UpdateFirmwareOperation.java
@@ -0,0 +1,293 @@
+/*  Copyright (C) 2018-2024 Andreas Shimokawa, Daniele Gobbetti, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.update;
+
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.content.Context;
+import android.net.Uri;
+import android.widget.Toast;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventDisplayMessage;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceBusyAction;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetProgressAction;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.AbstractHuamiFirmwareInfo;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.AbstractHuamiOperation;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+public class UpdateFirmwareOperation extends AbstractHuamiOperation {
+    private static final Logger LOG = LoggerFactory.getLogger(UpdateFirmwareOperation.class);
+
+    protected final Uri uri;
+    final BluetoothGattCharacteristic fwCControlChar;
+    final BluetoothGattCharacteristic fwCDataChar;
+    protected final Prefs prefs = GBApplication.getPrefs();
+    protected AbstractHuamiFirmwareInfo firmwareInfo;
+
+    public UpdateFirmwareOperation(Uri uri, HuamiSupport support) {
+        super(support);
+        this.uri = uri;
+        fwCControlChar = getCharacteristic(HuamiService.UUID_CHARACTERISTIC_FIRMWARE);
+        fwCDataChar = getCharacteristic(HuamiService.UUID_CHARACTERISTIC_FIRMWARE_DATA);
+    }
+
+    @Override
+    protected void enableNeededNotifications(TransactionBuilder builder, boolean enable) {
+        builder.notify(fwCControlChar, enable);
+    }
+
+    @Override
+    protected void doPerform() throws IOException {
+        firmwareInfo = createFwInfo(uri, getContext());
+        if (!firmwareInfo.isGenerallyCompatibleWith(getDevice())) {
+            throw new IOException("Firmware is not compatible with the given device: " + getDevice().getAddress());
+        }
+
+        if (!sendFwInfo()) {
+            displayMessage(getContext(), "Error sending firmware info, aborting.", Toast.LENGTH_LONG, GB.ERROR);
+            done();
+        }
+        //the firmware will be sent by the notification listener if the band confirms that the metadata are ok.
+    }
+
+    protected AbstractHuamiFirmwareInfo createFwInfo(Uri uri, Context context) throws IOException {
+        HuamiFWHelper fwHelper = getSupport().createFWHelper(uri, context);
+        return fwHelper.getFirmwareInfo();
+    }
+
+    protected void done() {
+        LOG.info("Operation done.");
+        operationFinished();
+        unsetBusy();
+    }
+
+    @Override
+    public boolean onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
+        if (status != BluetoothGatt.GATT_SUCCESS) {
+            operationFailed();
+        }
+        return super.onCharacteristicWrite(gatt, characteristic, status);
+    }
+
+    void operationFailed() {
+        GB.updateInstallNotification(getContext().getString(R.string.updatefirmwareoperation_write_failed), false, 0, getContext());
+    }
+
+    @Override
+    public boolean onCharacteristicChanged(BluetoothGatt gatt,
+                                           BluetoothGattCharacteristic characteristic) {
+        UUID characteristicUUID = characteristic.getUuid();
+        if (fwCControlChar.getUuid().equals(characteristicUUID)) {
+            handleNotificationNotif(characteristic.getValue());
+            return true; // don't let anyone else handle it
+        } else {
+            super.onCharacteristicChanged(gatt, characteristic);
+        }
+        return false;
+    }
+
+    /**
+     * React to messages sent by the Mi Band to the MiBandService.UUID_CHARACTERISTIC_NOTIFICATION
+     * characteristic,
+     * These messages appear to be always 1 byte long, with values that are listed in MiBandService.
+     * It is not excluded that there are further values which are still unknown.
+     * <p/>
+     * Upon receiving known values that request further action by GB, the appropriate method is called.
+     *
+     * @param value
+     */
+    protected void handleNotificationNotif(byte[] value) {
+        if (value.length != 3 && value.length != 11) {
+            LOG.error("Notifications should be 3 or 11 bytes long.");
+            getSupport().logMessageContent(value);
+            return;
+        }
+        boolean success = value[2] == HuamiService.SUCCESS;
+
+        if (value[0] == HuamiService.RESPONSE && success) {
+            try {
+                switch (value[1]) {
+                    case HuamiService.COMMAND_FIRMWARE_INIT: {
+                        sendFirmwareData(getFirmwareInfo());
+                        break;
+                    }
+                    case HuamiService.COMMAND_FIRMWARE_START_DATA: {
+                        sendChecksum(getFirmwareInfo());
+                        break;
+                    }
+                    case HuamiService.COMMAND_FIRMWARE_CHECKSUM: {
+                        if (getFirmwareInfo().getFirmwareType() == HuamiFirmwareType.FIRMWARE) {
+                            TransactionBuilder builder = performInitialized("reboot");
+                            getSupport().sendReboot(builder);
+                            builder.queue(getQueue());
+                        } else {
+                            GB.updateInstallNotification(getContext().getString(R.string.updatefirmwareoperation_update_complete), false, 100, getContext());
+                            done();
+                        }
+                        break;
+                    }
+                    case HuamiService.COMMAND_FIRMWARE_REBOOT: {
+                        LOG.info("Reboot command successfully sent.");
+                        GB.updateInstallNotification(getContext().getString(R.string.updatefirmwareoperation_update_complete), false, 100, getContext());
+                        done();
+                        break;
+                    }
+                    default: {
+                        LOG.error("Unexpected response during firmware update: ");
+                        getSupport().logMessageContent(value);
+                        operationFailed();
+                        displayMessage(getContext(), getContext().getString(R.string.updatefirmwareoperation_updateproblem_do_not_reboot), Toast.LENGTH_LONG, GB.ERROR);
+                        done();
+                    }
+                }
+            } catch (Exception ex) {
+                displayMessage(getContext(), getContext().getString(R.string.updatefirmwareoperation_updateproblem_do_not_reboot), Toast.LENGTH_LONG, GB.ERROR);
+                done();
+            }
+        } else {
+            LOG.error("Unexpected notification during firmware update: ");
+            operationFailed();
+            getSupport().logMessageContent(value);
+            displayMessage(getContext(), getContext().getString(R.string.updatefirmwareoperation_metadata_updateproblem), Toast.LENGTH_LONG, GB.ERROR);
+            done();
+        }
+    }
+
+    void displayMessage(Context context, String message, int duration, int severity) {
+        getSupport().handleGBDeviceEvent(new GBDeviceEventDisplayMessage(message, duration, severity));
+    }
+
+    public boolean sendFwInfo() {
+        try {
+            TransactionBuilder builder = performInitialized("send firmware info");
+//                getSupport().setLowLatency(builder);
+            builder.add(new SetDeviceBusyAction(getDevice(), getContext().getString(R.string.updating_firmware), getContext()));
+            int fwSize = getFirmwareInfo().getSize();
+            byte[] sizeBytes = BLETypeConversions.fromUint24(fwSize);
+            int arraySize = 4;
+            boolean isFirmwareCode = getFirmwareInfo().getFirmwareType() == HuamiFirmwareType.FIRMWARE;
+            if (!isFirmwareCode) {
+                arraySize++;
+            }
+            byte[] bytes = new byte[arraySize];
+            int i = 0;
+            bytes[i++] = HuamiService.COMMAND_FIRMWARE_INIT;
+            bytes[i++] = sizeBytes[0];
+            bytes[i++] = sizeBytes[1];
+            bytes[i++] = sizeBytes[2];
+            if (!isFirmwareCode) {
+                bytes[i] = getFirmwareInfo().getFirmwareType().getValue();
+            }
+
+            builder.write(fwCControlChar, bytes);
+            builder.queue(getQueue());
+            return true;
+        } catch (IOException e) {
+            LOG.error("Error sending firmware info: " + e.getLocalizedMessage(), e);
+            return false;
+        }
+    }
+
+    /**
+     * Method that uploads a firmware (fwbytes) to the Mi Band.
+     * The firmware has to be split into chunks of 20 bytes each, and periodically a COMMAND_SYNC command has to be issued to the Mi Band.
+     * <p/>
+     * The Mi Band will send a notification after receiving this data to confirm if the firmware looks good to it.
+     *
+     * @param info
+     * @return whether the transfer succeeded or not. Only a BT layer exception will cause the transmission to fail.
+     * @see #handleNotificationNotif
+     */
+    private boolean sendFirmwareData(AbstractHuamiFirmwareInfo info) {
+        byte[] fwbytes = info.getBytes();
+        int len = fwbytes.length;
+        final int packetLength = getSupport().getMTU() - 3;
+        int packets = len / packetLength;
+
+        try {
+            // going from 0 to len
+            int firmwareProgress = 0;
+
+            TransactionBuilder builder = performInitialized("send firmware packet");
+            builder.write(fwCControlChar, getFirmwareStartCommand());
+
+            for (int i = 0; i < packets; i++) {
+                byte[] fwChunk = Arrays.copyOfRange(fwbytes, i * packetLength, i * packetLength + packetLength);
+
+                builder.write(fwCDataChar, fwChunk);
+                firmwareProgress += packetLength;
+
+                int progressPercent = (int) ((((float) firmwareProgress) / len) * 100);
+                if ((i > 0) && (i % 100 == 0)) {
+                    builder.write(fwCControlChar, new byte[]{HuamiService.COMMAND_FIRMWARE_UPDATE_SYNC});
+                    builder.add(new SetProgressAction(getContext().getString(R.string.updatefirmwareoperation_update_in_progress), true, progressPercent, getContext()));
+                }
+            }
+
+            if (firmwareProgress < len) {
+                byte[] lastChunk = Arrays.copyOfRange(fwbytes, packets * packetLength, len);
+                builder.write(fwCDataChar, lastChunk);
+            }
+
+            builder.write(fwCControlChar, new byte[]{HuamiService.COMMAND_FIRMWARE_UPDATE_SYNC});
+            builder.queue(getQueue());
+
+        } catch (IOException ex) {
+            LOG.error("Unable to send fw to device", ex);
+            GB.updateInstallNotification(getContext().getString(R.string.updatefirmwareoperation_firmware_not_sent), false, 0, getContext());
+            return false;
+        }
+        return true;
+    }
+
+
+    protected void sendChecksum(AbstractHuamiFirmwareInfo firmwareInfo) throws IOException {
+        TransactionBuilder builder = performInitialized("send firmware checksum");
+        int crc16 = firmwareInfo.getCrc16();
+        byte[] bytes = BLETypeConversions.fromUint16(crc16);
+        builder.write(fwCControlChar, new byte[]{
+                HuamiService.COMMAND_FIRMWARE_CHECKSUM,
+                bytes[0],
+                bytes[1],
+        });
+        builder.queue(getQueue());
+    }
+
+    protected AbstractHuamiFirmwareInfo getFirmwareInfo() {
+        return firmwareInfo;
+    }
+
+    protected byte[] getFirmwareStartCommand() {
+        return new byte[]{HuamiService.COMMAND_FIRMWARE_START_DATA};
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/update/UpdateFirmwareOperation2020.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/update/UpdateFirmwareOperation2020.java
new file mode 100644
index 0000000..35be088
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/update/UpdateFirmwareOperation2020.java
@@ -0,0 +1,303 @@
+/*  Copyright (C) 2020-2024 Andreas Shimokawa, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.update;
+
+import android.net.Uri;
+import android.widget.Toast;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.Arrays;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceBusyAction;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetProgressAction;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.AbstractHuamiFirmwareInfo;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport;
+import nodomain.freeyourgadget.gadgetbridge.util.ArrayUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareInfo.UIHH_HEADER;
+
+public class UpdateFirmwareOperation2020 extends UpdateFirmwareOperation {
+    private static final Logger LOG = LoggerFactory.getLogger(UpdateFirmwareOperation2020.class);
+
+    public UpdateFirmwareOperation2020(Uri uri, HuamiSupport support) {
+        super(uri, support);
+    }
+
+    public static final byte COMMAND_REQUEST_PARAMETERS = (byte) 0xd0;
+    public static final byte COMMAND_UNKNOWN_D1 = (byte) 0xd1;
+    public static final byte COMMAND_SEND_FIRMWARE_INFO = (byte) 0xd2;
+    public static final byte COMMAND_START_TRANSFER = (byte) 0xd3;
+    public static final byte REPLY_UPDATE_PROGRESS = (byte) 0xd4;
+    public static final byte COMMAND_COMPLETE_TRANSFER = (byte) 0xd5;
+    public static final byte COMMAND_FINALIZE_UPDATE = (byte) 0xd6;
+
+    public static final byte REPLY_ERROR_FREE_SPACE = (byte) 0x47;
+    public static final byte REPLY_ERROR_LOW_BATTERY = (byte) 0x22;
+
+    protected int mChunkLength = -1;
+
+    @Override
+    protected void doPerform() throws IOException {
+        firmwareInfo = createFwInfo(uri, getContext());
+        if (!firmwareInfo.isGenerallyCompatibleWith(getDevice())) {
+            throw new IOException("Firmware is not compatible with the given device: " + getDevice().getAddress());
+        }
+
+        if (!requestParameters()) {
+            displayMessage(getContext(), "Error requesting parameters, aborting.", Toast.LENGTH_LONG, GB.ERROR);
+            done();
+        }
+    }
+
+
+    @Override
+    protected void handleNotificationNotif(byte[] value) {
+        boolean success = (value[2] == HuamiService.SUCCESS) || ((value[1] == REPLY_UPDATE_PROGRESS) && value.length >= 6); // ugly
+
+        if (value[0] == HuamiService.RESPONSE && success) {
+            try {
+                switch (value[1]) {
+                    case COMMAND_REQUEST_PARAMETERS: {
+                        mChunkLength = (value[4] & 0xff) | ((value[5] & 0xff) << 8);
+                        LOG.info("got chunk length of " + mChunkLength);
+                        sendFwInfo();
+                        break;
+                    }
+                    case COMMAND_SEND_FIRMWARE_INFO:
+                        sendTransferStart();
+                        break;
+                    case COMMAND_START_TRANSFER:
+                        sendFirmwareDataChunk(getFirmwareInfo(), 0);
+                        break;
+                    case HuamiService.COMMAND_FIRMWARE_START_DATA:
+                        sendChecksum(getFirmwareInfo());
+                        break;
+                    case REPLY_UPDATE_PROGRESS:
+                        int offset = (value[2] & 0xff) | ((value[3] & 0xff) << 8) | ((value[4] & 0xff) << 16) | ((value[5] & 0xff) << 24);
+                        LOG.info("update progress " + offset + " bytes");
+                        sendFirmwareDataChunk(getFirmwareInfo(), offset);
+                        break;
+                    case COMMAND_COMPLETE_TRANSFER:
+                        sendFinalize();
+                        break;
+                    case COMMAND_FINALIZE_UPDATE: {
+                        if (getFirmwareInfo().getFirmwareType() == HuamiFirmwareType.FIRMWARE) {
+                            TransactionBuilder builder = performInitialized("reboot");
+                            getSupport().sendReboot(builder);
+                            builder.queue(getQueue());
+                        } else {
+                            GB.updateInstallNotification(getContext().getString(R.string.updatefirmwareoperation_update_complete), false, 100, getContext());
+                            done();
+                        }
+                        break;
+                    }
+                    case HuamiService.COMMAND_FIRMWARE_REBOOT: {
+                        LOG.info("Reboot command successfully sent.");
+                        GB.updateInstallNotification(getContext().getString(R.string.updatefirmwareoperation_update_complete), false, 100, getContext());
+                        done();
+                        break;
+                    }
+                    default: {
+                        LOG.error("Unexpected response during firmware update: ");
+                        getSupport().logMessageContent(value);
+                        operationFailed();
+                        displayMessage(getContext(), getContext().getString(R.string.updatefirmwareoperation_updateproblem_do_not_reboot), Toast.LENGTH_LONG, GB.ERROR);
+                        done();
+                    }
+                }
+            } catch (Exception ex) {
+                displayMessage(getContext(), getContext().getString(R.string.updatefirmwareoperation_updateproblem_do_not_reboot), Toast.LENGTH_LONG, GB.ERROR);
+                done();
+            }
+        } else {
+            LOG.error("Unexpected notification during firmware update: ");
+            operationFailed();
+            getSupport().logMessageContent(value);
+            int errorMessage = R.string.updatefirmwareoperation_metadata_updateproblem;
+            // Display a more specific error message for known errors
+
+            if (value[0] == HuamiService.RESPONSE && value[1] == COMMAND_START_TRANSFER && value[2] == REPLY_ERROR_FREE_SPACE) {
+                // Not enough free space on the device
+                errorMessage = R.string.updatefirmwareoperation_updateproblem_free_space;
+            } else if (value[0] == HuamiService.RESPONSE && value[1] == COMMAND_SEND_FIRMWARE_INFO && value[2] == REPLY_ERROR_LOW_BATTERY) {
+                // Battery is too low
+                errorMessage = R.string.updatefirmwareoperation_updateproblem_low_battery;
+            }
+            displayMessage(getContext(), getContext().getString(errorMessage), Toast.LENGTH_LONG, GB.ERROR);
+            done();
+        }
+    }
+
+
+    public boolean sendFwInfo() {
+        try {
+            TransactionBuilder builder = performInitialized("send firmware info");
+            builder.add(new SetDeviceBusyAction(getDevice(), getContext().getString(R.string.updating_firmware), getContext()));
+            int fwSize = getFirmwareInfo().getSize();
+            byte[] sizeBytes = BLETypeConversions.fromUint32(fwSize);
+            byte[] bytes = buildFirmwareInfoCommand();
+
+            if (getFirmwareInfo().getFirmwareType() == HuamiFirmwareType.WATCHFACE) {
+                byte[] fwBytes = firmwareInfo.getBytes();
+                if (ArrayUtils.startsWith(fwBytes, UIHH_HEADER)) {
+                    getSupport().writeToConfiguration(builder,
+                            new byte[]{0x39, 0x00,
+                                    sizeBytes[0],
+                                    sizeBytes[1],
+                                    sizeBytes[2],
+                                    sizeBytes[3],
+                                    fwBytes[18],
+                                    fwBytes[19],
+                                    fwBytes[20],
+                                    fwBytes[21]
+                            });
+                }
+            }
+            builder.write(fwCControlChar, bytes);
+            builder.queue(getQueue());
+            return true;
+        } catch (IOException e) {
+            LOG.error("Error sending firmware info: " + e.getLocalizedMessage(), e);
+            return false;
+        }
+    }
+
+    protected byte[] buildFirmwareInfoCommand() {
+        int fwSize = getFirmwareInfo().getSize();
+        byte[] sizeBytes = BLETypeConversions.fromUint32(fwSize);
+        int crc32 = firmwareInfo.getCrc32();
+        byte[] chunkSizeBytes = BLETypeConversions.fromUint16(mChunkLength);
+        byte[] crcBytes = BLETypeConversions.fromUint32(crc32);
+        return new byte[]{
+                COMMAND_SEND_FIRMWARE_INFO,
+                getFirmwareInfo().getFirmwareType().getValue(),
+                sizeBytes[0],
+                sizeBytes[1],
+                sizeBytes[2],
+                sizeBytes[3],
+                crcBytes[0],
+                crcBytes[1],
+                crcBytes[2],
+                crcBytes[3],
+                chunkSizeBytes[0],
+                chunkSizeBytes[1],
+                0, // 0 to update in foreground, 1 for background
+                0, // index
+                1, // count
+                sizeBytes[0], // total size? right now it is equal to the size above
+                sizeBytes[1],
+                sizeBytes[2],
+                sizeBytes[3]
+        };
+    }
+
+    public boolean requestParameters() {
+        try {
+            TransactionBuilder builder = performInitialized("get update capabilities");
+            byte[] bytes = new byte[]{COMMAND_REQUEST_PARAMETERS};
+            builder.write(fwCControlChar, bytes);
+            builder.queue(getQueue());
+            return true;
+        } catch (IOException e) {
+            LOG.error("Error sending firmware info: " + e.getLocalizedMessage(), e);
+            return false;
+        }
+    }
+
+
+    private boolean sendFirmwareDataChunk(AbstractHuamiFirmwareInfo info, int offset) {
+        byte[] fwbytes = info.getBytes();
+        int len = fwbytes.length;
+        int remaining = len - offset;
+        final int packetLength = getSupport().getMTU() - 3;
+
+        int chunkLength = mChunkLength;
+        if (remaining < mChunkLength) {
+            chunkLength = remaining;
+        }
+
+        int packets = chunkLength / packetLength;
+        int chunkProgress = 0;
+
+        try {
+            if (remaining <= 0) {
+                sendTransferComplete();
+                return true;
+            }
+
+            TransactionBuilder builder = performInitialized("send firmware packets");
+
+            for (int i = 0; i < packets; i++) {
+                byte[] fwChunk = Arrays.copyOfRange(fwbytes, offset + i * packetLength, offset + i * packetLength + packetLength);
+
+                builder.write(fwCDataChar, fwChunk);
+                chunkProgress += packetLength;
+            }
+
+            if (chunkProgress < chunkLength) {
+                byte[] lastChunk = Arrays.copyOfRange(fwbytes, offset + packets * packetLength, offset + packets * packetLength + (chunkLength - chunkProgress));
+                builder.write(fwCDataChar, lastChunk);
+            }
+
+            int progressPercent = (int) ((((float) (offset + chunkLength)) / len) * 100);
+
+            builder.add(new SetProgressAction(getContext().getString(R.string.updatefirmwareoperation_update_in_progress), true, progressPercent, getContext()));
+
+            builder.queue(getQueue());
+
+        } catch (IOException ex) {
+            LOG.error("Unable to send fw to device", ex);
+            GB.updateInstallNotification(getContext().getString(R.string.updatefirmwareoperation_firmware_not_sent), false, 0, getContext());
+            return false;
+        }
+        return true;
+    }
+
+
+    protected void sendTransferStart() throws IOException {
+        TransactionBuilder builder = performInitialized("transfer complete");
+        builder.write(fwCControlChar, new byte[]{
+                COMMAND_START_TRANSFER, 1,
+        });
+        builder.queue(getQueue());
+    }
+
+    protected void sendTransferComplete() throws IOException {
+        TransactionBuilder builder = performInitialized("transfer complete");
+        builder.write(fwCControlChar, new byte[]{
+                COMMAND_COMPLETE_TRANSFER,
+        });
+        builder.queue(getQueue());
+    }
+
+    protected void sendFinalize() throws IOException {
+        TransactionBuilder builder = performInitialized("finalize firmware");
+        builder.write(fwCControlChar, new byte[]{
+                COMMAND_FINALIZE_UPDATE,
+        });
+        builder.queue(getQueue());
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/update/UpdateFirmwareOperationNew.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/update/UpdateFirmwareOperationNew.java
new file mode 100644
index 0000000..0a72553
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/update/UpdateFirmwareOperationNew.java
@@ -0,0 +1,100 @@
+/*  Copyright (C) 2019-2024 Andreas Shimokawa, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.update;
+
+import android.net.Uri;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceBusyAction;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.AbstractHuamiFirmwareInfo;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport;
+import nodomain.freeyourgadget.gadgetbridge.util.ArrayUtils;
+
+import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareInfo.UIHH_HEADER;
+
+public class UpdateFirmwareOperationNew extends UpdateFirmwareOperation {
+    private static final Logger LOG = LoggerFactory.getLogger(UpdateFirmwareOperationNew.class);
+
+
+    public UpdateFirmwareOperationNew(Uri uri, HuamiSupport support) {
+        super(uri, support);
+    }
+
+    @Override
+    public boolean sendFwInfo() {
+        try {
+            TransactionBuilder builder = performInitialized("send firmware info");
+            builder.add(new SetDeviceBusyAction(getDevice(), getContext().getString(R.string.updating_firmware), getContext()));
+            int fwSize = getFirmwareInfo().getSize();
+            byte[] sizeBytes = BLETypeConversions.fromUint32(fwSize);
+            byte[] bytes = new byte[10];
+            int i = 0;
+            bytes[i++] = HuamiService.COMMAND_FIRMWARE_INIT;
+            bytes[i++] = getFirmwareInfo().getFirmwareType().getValue();
+            bytes[i++] = sizeBytes[0];
+            bytes[i++] = sizeBytes[1];
+            bytes[i++] = sizeBytes[2];
+            bytes[i++] = sizeBytes[3];
+            int crc32 = firmwareInfo.getCrc32();
+            byte[] crcBytes = BLETypeConversions.fromUint32(crc32);
+            bytes[i++] = crcBytes[0];
+            bytes[i++] = crcBytes[1];
+            bytes[i++] = crcBytes[2];
+            bytes[i] = crcBytes[3];
+
+            if (getFirmwareInfo().getFirmwareType() == HuamiFirmwareType.WATCHFACE) {
+                byte[] fwBytes = firmwareInfo.getBytes();
+                if (ArrayUtils.startsWith(fwBytes, UIHH_HEADER)) {
+                    builder.write(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_3_CONFIGURATION),
+                            new byte[]{0x39, 0x00, 0x00, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                                    fwBytes[18],
+                                    fwBytes[19],
+                                    fwBytes[20],
+                                    fwBytes[21]
+                            });
+                }
+            }
+            builder.write(fwCControlChar, bytes);
+            builder.queue(getQueue());
+            return true;
+        } catch (IOException e) {
+            LOG.error("Error sending firmware info: " + e.getLocalizedMessage(), e);
+            return false;
+        }
+    }
+
+    @Override
+    protected void sendChecksum(AbstractHuamiFirmwareInfo firmwareInfo) throws IOException {
+        TransactionBuilder builder = performInitialized("send firmware upload finished");
+        builder.write(fwCControlChar, new byte[]{HuamiService.COMMAND_FIRMWARE_CHECKSUM});
+        builder.queue(getQueue());
+    }
+
+    @Override
+    protected byte[] getFirmwareStartCommand() {
+        return new byte[]{HuamiService.COMMAND_FIRMWARE_START_DATA, 1};
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppe/ZeppEFirmwareInfo.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppe/ZeppEFirmwareInfo.java
new file mode 100644
index 0000000..c3ca57f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppe/ZeppEFirmwareInfo.java
@@ -0,0 +1,116 @@
+/*  Copyright (C) 2021-2024 Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppe;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareInfo;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareType;
+import nodomain.freeyourgadget.gadgetbridge.util.ArrayUtils;
+
+public class ZeppEFirmwareInfo extends HuamiFirmwareInfo {
+    private static final int FW_OFFSET = 3;
+
+    private static final byte[] FW_HEADER = new byte[]{
+            0x20, (byte) 0x99, 0x12, 0x01, 0x08 // completely nonsense probably
+    };
+
+    private static final byte[] GPS_ALMANAC_HEADER = new byte[]{ // probably wrong
+            (byte) 0xa0, (byte) 0x80, 0x08, 0x00, (byte) 0x8b, 0x07
+    };
+
+    private static final byte[] GPS_CEP_HEADER = new byte[]{ // probably wrong
+            0x2a, 0x12, (byte) 0xa0, 0x02
+    };
+
+    // gps detection is totally bogus, just the first 16 bytes
+    private static final byte[][] GPS_HEADERS = {
+            new byte[]{
+                    0x73, 0x75, 0x68, (byte) 0xd0, 0x70, 0x73, (byte) 0xbb, 0x5a,
+                    0x3e, (byte) 0xc3, (byte) 0xd3, 0x09, (byte) 0x9e, 0x1d, (byte) 0xd3, (byte) 0xc9
+            }
+    };
+    private static Map<Integer, String> crcToVersion = new HashMap<>();
+
+    static {
+        // firmware
+
+        // Latin Firmware
+
+        // resources
+
+        // font
+
+        // gps
+        crcToVersion.put(62532, "18344,eb2f43f,126");
+    }
+
+    public ZeppEFirmwareInfo(byte[] bytes) {
+        super(bytes);
+    }
+
+    @Override
+    protected HuamiFirmwareType determineFirmwareType(byte[] bytes) {
+        if (ArrayUtils.equals(bytes, NEWRES_HEADER, COMPRESSED_RES_HEADER_OFFSET_NEW)) {
+            return HuamiFirmwareType.RES_COMPRESSED;
+        }
+        if (ArrayUtils.equals(bytes, FW_HEADER, FW_OFFSET)) {
+            if (searchString32BitAligned(bytes, "Zepp E")) {
+                return HuamiFirmwareType.FIRMWARE;
+            }
+            return HuamiFirmwareType.INVALID;
+        }
+        if (ArrayUtils.startsWith(bytes, WATCHFACE_HEADER) || ArrayUtils.equals(bytes, WATCHFACE_HEADER, COMPRESSED_RES_HEADER_OFFSET_NEW) || ArrayUtils.equals(bytes, WATCHFACE_HEADER, COMPRESSED_RES_HEADER_OFFSET)) {
+            return HuamiFirmwareType.WATCHFACE;
+        }
+        if (ArrayUtils.startsWith(bytes, NEWFT_HEADER)) {
+            if (bytes[10] == 0x01) {
+                return HuamiFirmwareType.FONT;
+            } else if (bytes[10] == 0x02) {
+                return HuamiFirmwareType.FONT_LATIN;
+            }
+        }
+
+        if (ArrayUtils.startsWith(bytes, GPS_ALMANAC_HEADER)) {
+            return HuamiFirmwareType.GPS_ALMANAC;
+        }
+        if (ArrayUtils.startsWith(bytes, GPS_CEP_HEADER)) {
+            return HuamiFirmwareType.GPS_CEP;
+        }
+
+        for (byte[] gpsHeader : GPS_HEADERS) {
+            if (ArrayUtils.startsWith(bytes, gpsHeader)) {
+                return HuamiFirmwareType.GPS;
+            }
+        }
+
+        return HuamiFirmwareType.INVALID;
+    }
+
+    @Override
+    public boolean isGenerallyCompatibleWith(GBDevice device) {
+        return isHeaderValid() && device.getType() == DeviceType.ZEPP_E;
+    }
+
+    @Override
+    protected Map<Integer, String> getCrcMap() {
+        return crcToVersion;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppe/ZeppESupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppe/ZeppESupport.java
new file mode 100644
index 0000000..0c8e31a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppe/ZeppESupport.java
@@ -0,0 +1,52 @@
+/*  Copyright (C) 2021-2024 Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppe;
+
+import android.content.Context;
+import android.net.Uri;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.zeppe.ZeppEFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitgtr.AmazfitGTRSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.update.UpdateFirmwareOperation;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.update.UpdateFirmwareOperation2020;
+
+public class ZeppESupport extends AmazfitGTRSupport {
+
+    @Override
+    public HuamiFWHelper createFWHelper(Uri uri, Context context) throws IOException {
+        return new ZeppEFWHelper(uri, context);
+    }
+
+    @Override
+    public void onSetCallState(CallSpec callSpec) {
+        onSetCallStateNew(callSpec);
+    }
+
+    @Override
+    public UpdateFirmwareOperation createUpdateFirmwareOperation(Uri uri) {
+        return new UpdateFirmwareOperation2020(uri, this);
+    }
+
+    @Override
+    public int getActivitySampleSize() {
+        return 8;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/AbstractZeppOsService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/AbstractZeppOsService.java
new file mode 100644
index 0000000..b39eeb9
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/AbstractZeppOsService.java
@@ -0,0 +1,130 @@
+/*  Copyright (C) 2022-2024 Daniel Dakhno, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos;
+
+import android.content.Context;
+
+import androidx.annotation.Nullable;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
+import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.zeppos.ZeppOsCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsAlexaService;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+public abstract class AbstractZeppOsService {
+    private static final Logger LOG = LoggerFactory.getLogger(ZeppOsAlexaService.class);
+
+    private final ZeppOsSupport mSupport;
+    private boolean encrypted;
+
+    public AbstractZeppOsService(final ZeppOsSupport support, final boolean encryptedDefault) {
+        this.mSupport = support;
+        this.encrypted = encryptedDefault;
+    }
+
+    public abstract short getEndpoint();
+
+    public final boolean isEncrypted() {
+        return this.encrypted;
+    }
+
+    public final void setEncrypted(final boolean encrypted) {
+        if (encrypted != this.encrypted) {
+            LOG.warn("Replacing encrypted flag for {}, {} -> {}", this.getClass().getSimpleName(), this.encrypted, encrypted);
+        }
+
+        this.encrypted = encrypted;
+    }
+
+    public abstract void handlePayload(final byte[] payload);
+
+    /**
+     * Handle a preference change.
+     * @param config the preference key
+     * @param prefs the device preferences
+     * @return true if the preference was handled, false otherwise
+     */
+    public boolean onSendConfiguration(final String config, final Prefs prefs) {
+        return false;
+    }
+
+    public void initialize(final TransactionBuilder builder) {
+        // Do nothing by default
+        // TODO implement a "quick initialize" that runs for the same firmware + Gb versions, since
+        // we will already know the capabilities
+    }
+
+    protected ZeppOsSupport getSupport() {
+        return mSupport;
+    }
+
+    protected ZeppOsCoordinator getCoordinator() {
+        final DeviceCoordinator coordinator = getSupport().getDevice().getDeviceCoordinator();
+        return (ZeppOsCoordinator) coordinator;
+    }
+
+    protected Prefs getDevicePrefs() {
+        return new Prefs(GBApplication.getDeviceSpecificSharedPrefs(getSupport().getDevice().getAddress()));
+    }
+
+    protected void write(final String taskName, final byte b) {
+        this.write(taskName, new byte[]{b});
+    }
+
+    protected void write(final String taskName, final byte[] data) {
+        this.mSupport.writeToChunked2021(taskName, getEndpoint(), data, isEncrypted());
+    }
+
+    protected void write(final TransactionBuilder builder, final byte b) {
+        this.write(builder, new byte[]{b});
+    }
+
+    protected void write(final TransactionBuilder builder, final byte[] data) {
+        this.mSupport.writeToChunked2021(builder, getEndpoint(), data, isEncrypted());
+    }
+
+    protected void evaluateGBDeviceEvent(final GBDeviceEvent event) {
+        getSupport().evaluateGBDeviceEvent(event);
+    }
+
+    protected Context getContext() {
+        return getSupport().getContext();
+    }
+
+    @Nullable
+    protected static Boolean booleanFromByte(final byte b) {
+        switch (b) {
+            case 0x00:
+                return false;
+            case 0x01:
+                return true;
+            default:
+        }
+
+        return null;
+    }
+
+    protected byte bool(final boolean bool) {
+        return (byte) (bool ? 0x01 : 0x00);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/ZeppOsActivityDetailsParser.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/ZeppOsActivityDetailsParser.java
new file mode 100644
index 0000000..c40ccc0
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/ZeppOsActivityDetailsParser.java
@@ -0,0 +1,357 @@
+/*  Copyright (C) 2022-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos;
+
+import androidx.annotation.Nullable;
+
+import org.apache.commons.lang3.ArrayUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.TimeZone;
+
+import nodomain.freeyourgadget.gadgetbridge.GBException;
+import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityPoint;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityTrack;
+import nodomain.freeyourgadget.gadgetbridge.model.GPSCoordinate;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.AbstractHuamiActivityDetailsParser;
+
+public class ZeppOsActivityDetailsParser extends AbstractHuamiActivityDetailsParser {
+    private static final Logger LOG = LoggerFactory.getLogger(ZeppOsActivityDetailsParser.class);
+
+    private static final SimpleDateFormat SDF = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS", Locale.US);
+
+    static {
+        SDF.setTimeZone(TimeZone.getTimeZone("UTC"));
+    }
+
+    private Date timestamp;
+    private long offset = 0;
+
+    private long longitude;
+    private long latitude;
+    private double altitude;
+
+    private final ActivityTrack activityTrack;
+    private ActivityPoint lastActivityPoint;
+
+    public ZeppOsActivityDetailsParser(final BaseActivitySummary summary) {
+        this.timestamp = summary.getStartTime();
+
+        this.longitude = summary.getBaseLongitude();
+        this.latitude = summary.getBaseLatitude();
+        this.altitude = summary.getBaseAltitude();
+
+        this.activityTrack = new ActivityTrack();
+        this.activityTrack.setUser(summary.getUser());
+        this.activityTrack.setDevice(summary.getDevice());
+        this.activityTrack.setName(createActivityName(summary));
+    }
+
+    @Override
+    public ActivityTrack parse(final byte[] bytes) throws GBException {
+        final ByteBuffer buf = ByteBuffer.wrap(bytes)
+                .order(ByteOrder.LITTLE_ENDIAN);
+
+        // Keep track of unknown type codes so we can print them without spamming the logs
+        final Map<Byte, Integer> unknownTypeCodes = new HashMap<>();
+        
+        while (buf.position() < buf.limit()) {
+            final byte typeCode = buf.get();
+            final byte length = buf.get();
+            final int initialPosition = buf.position();
+
+            final Type type = Type.fromCode(typeCode);
+            if (type == null) {
+                if (!unknownTypeCodes.containsKey(typeCode)) {
+                    unknownTypeCodes.put(typeCode, 0);
+                }
+
+                unknownTypeCodes.put(typeCode, unknownTypeCodes.get(typeCode) + 1);
+                //LOG.warn("Unknown type code {} of length {}", String.format("0x%X", typeCode), length);
+                // Consume the reported length
+                buf.get(new byte[length]);
+                continue;
+            } else if (length != type.getExpectedLength()) {
+                LOG.warn("Unexpected length {} for type {}", length, type);
+                // Consume the reported length
+                buf.get(new byte[length]);
+                continue;
+            }
+
+            // Consume
+            switch (type) {
+                case TIMESTAMP:
+                    consumeTimestamp(buf);
+                    break;
+                case GPS_COORDS:
+                    consumeGpsCoords(buf);
+                    break;
+                case GPS_DELTA:
+                    consumeGpsDelta(buf);
+                    break;
+                case STATUS:
+                    consumeStatus(buf);
+                    break;
+                case SPEED:
+                    consumeSpeed(buf);
+                    break;
+                case ALTITUDE:
+                    consumeAltitude(buf);
+                    break;
+                case HEARTRATE:
+                    consumeHeartRate(buf);
+                    break;
+                case STRENGTH_SET:
+                    // TODO parse strength sets: weight, count, type
+                default:
+                    LOG.warn("No consumer for for type {}", type);
+                    // Consume the reported length
+                    buf.get(new byte[length]);
+                    continue;
+            }
+
+            final int expectedPosition = initialPosition + length;
+            if (buf.position() != expectedPosition) {
+                // Should never happen unless there's a bug in one of the consumers
+                throw new IllegalStateException("Unexpected position " + buf.position() + ", expected " + expectedPosition + ", after consuming " + type);
+            }
+        }
+
+        if (!unknownTypeCodes.isEmpty()) {
+            for (final Map.Entry<Byte, Integer> e : unknownTypeCodes.entrySet()) {
+                LOG.warn("Unknown type code {} seen {} times", String.format("0x%X", e.getKey()), e.getValue());
+            }
+        }
+
+        return this.activityTrack;
+    }
+
+    private void consumeTimestamp(final ByteBuffer buf) {
+        buf.getInt(); // ?
+        this.timestamp = new Date(buf.getLong());
+        this.offset = 0;
+
+        //trace("Consumed timestamp");
+    }
+
+    private void consumeTimestampOffset(final ByteBuffer buf) {
+        this.offset = buf.getShort();
+    }
+
+    private void consumeGpsCoords(final ByteBuffer buf) {
+        buf.get(new byte[6]); // ?
+        this.longitude = buf.getInt();
+        this.latitude = buf.getInt();
+        buf.get(new byte[6]); // ?
+
+        // TODO which one is the time offset? Not sure it is the first
+
+        addNewGpsCoordinates();
+
+        final double longitudeDeg = convertHuamiValueToDecimalDegrees(longitude);
+        final double latitudeDeg = convertHuamiValueToDecimalDegrees(latitude);
+
+        //trace("Consumed GPS coords: {} {}", longitudeDeg, latitudeDeg);
+    }
+
+    private void consumeGpsDelta(final ByteBuffer buf) {
+        consumeTimestampOffset(buf);
+        final short longitudeDelta = buf.getShort();
+        final short latitudeDelta = buf.getShort();
+        buf.getShort(); // ? seems to always be 2
+
+        this.longitude += longitudeDelta;
+        this.latitude += latitudeDelta;
+
+        if (lastActivityPoint == null) {
+            final String timestampStr = SDF.format(new Date(timestamp.getTime() + offset));
+            LOG.warn("{}: Got GPS delta before GPS coords, ignoring", timestampStr);
+            return;
+        }
+
+        addNewGpsCoordinates();
+
+        //trace("Consumed GPS delta: {} {}", longitudeDelta, latitudeDelta);
+    }
+
+    private void consumeStatus(final ByteBuffer buf) {
+        consumeTimestampOffset(buf);
+
+        final int statusCode = buf.getShort();
+        final String status;
+        switch (statusCode) {
+            case 1:
+                status = "start";
+                break;
+            case 4:
+                status = "pause";
+                activityTrack.startNewSegment();
+                break;
+            case 5:
+                status = "resume";
+                activityTrack.startNewSegment();
+                break;
+            case 6:
+                status = "stop";
+                break;
+            default:
+                status = String.format("unknown (0x%X)", statusCode);
+                LOG.warn("Unknown status code {}", String.format("0x%X", statusCode));
+        }
+
+        // TODO split track into multiple segments?
+
+        //trace("Consumed Status: {}", status);
+    }
+
+    private void consumeSpeed(final ByteBuffer buf) {
+        consumeTimestampOffset(buf);
+
+        final short cadence = buf.getShort(); // spm
+        final short stride = buf.getShort(); // cm
+        final short pace = buf.getShort(); // sec/km
+
+        // TODO integrate into gpx
+
+        //trace("Consumed speed: cadence={}, stride={}, ?={}", cadence, stride, );
+    }
+
+    private void consumeAltitude(final ByteBuffer buf) {
+        consumeTimestampOffset(buf);
+        altitude = (int) (buf.getInt() / 100.0f);
+
+        final ActivityPoint ap = getCurrentActivityPoint();
+        if (ap != null) {
+            final GPSCoordinate newCoordinate = new GPSCoordinate(
+                    ap.getLocation().getLongitude(),
+                    ap.getLocation().getLatitude(),
+                    altitude
+            );
+
+            ap.setLocation(newCoordinate);
+        }
+
+        //trace("Consumed altitude: {}", altitude);
+    }
+
+    private void consumeHeartRate(final ByteBuffer buf) {
+        consumeTimestampOffset(buf);
+        final int heartRate = buf.get() & 0xff;
+
+        final ActivityPoint ap = getCurrentActivityPoint();
+        if (ap != null) {
+            ap.setHeartRate(heartRate);
+        }
+
+        //trace("Consumed HeartRate: {}", heartRate);
+    }
+
+    @Nullable
+    private ActivityPoint getCurrentActivityPoint() {
+        if (lastActivityPoint == null) {
+            return null;
+        }
+
+        // Round to the nearest second
+        final long currentTime = timestamp.getTime() + offset;
+        if (currentTime - lastActivityPoint.getTime().getTime() > 500) {
+            addNewGpsCoordinates();
+            return lastActivityPoint;
+        }
+
+        return lastActivityPoint;
+    }
+
+    private void addNewGpsCoordinates() {
+        final GPSCoordinate coordinate = new GPSCoordinate(
+                convertHuamiValueToDecimalDegrees(longitude),
+                convertHuamiValueToDecimalDegrees(latitude),
+                altitude
+        );
+
+        if (lastActivityPoint != null && lastActivityPoint.getLocation() != null && lastActivityPoint.getLocation().equals(coordinate)) {
+            // Ignore repeated location
+            return;
+        }
+
+        final ActivityPoint ap = new ActivityPoint(new Date(timestamp.getTime() + offset));
+        ap.setLocation(coordinate);
+        add(ap);
+    }
+
+    private void add(final ActivityPoint ap) {
+        if (ap == lastActivityPoint) {
+            LOG.debug("skipping point!");
+            return;
+        }
+
+        lastActivityPoint = ap;
+        activityTrack.addTrackPoint(ap);
+    }
+
+    private void trace(final String format, final Object... args) {
+        final Object[] argsWithDate = ArrayUtils.insert(0, args, SDF.format(new Date(timestamp.getTime() + offset)));
+        LOG.debug("{}: " + format, argsWithDate);
+    }
+
+    private enum Type {
+        TIMESTAMP(1, 12),
+        GPS_COORDS(2, 20),
+        GPS_DELTA(3, 8),
+        STATUS(4, 4),
+        SPEED(5, 8),
+        ALTITUDE(7, 6),
+        HEARTRATE(8, 3),
+        STRENGTH_SET(15, 34),
+        ;
+
+        private final byte code;
+        private final int expectedLength;
+
+        Type(final int code, final int expectedLength) {
+            this.code = (byte) code;
+            this.expectedLength = expectedLength;
+        }
+
+        public byte getCode() {
+            return this.code;
+        }
+
+        public int getExpectedLength() {
+            return this.expectedLength;
+        }
+
+        public static Type fromCode(final byte code) {
+            for (final Type type : values()) {
+                if (type.getCode() == code) {
+                    return type;
+                }
+            }
+
+            return null;
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/ZeppOsActivityType.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/ZeppOsActivityType.java
new file mode 100644
index 0000000..9aaa6fa
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/ZeppOsActivityType.java
@@ -0,0 +1,224 @@
+/*  Copyright (C) 2022-2024 Jos茅 Rebelo, Sebastian Reichel
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+
+/**
+ * The workout types, used to start / when workout tracking starts on the band.
+ */
+public enum ZeppOsActivityType {
+    AerobicCombo(0x33, ActivityKind.AEROBIC_COMBO),
+    Aerobics(0x6d, ActivityKind.AEROBICS),
+    AirWalker(0x90, ActivityKind.AIR_WALKER),
+    Archery(0x5d, ActivityKind.ARCHERY),
+    ArtisticSwimming(0x9c, ActivityKind.ARTISTIC_SWIMMING),
+    Badminton(0x5c, ActivityKind.BADMINTON),
+    Ballet(0x47, ActivityKind.BALLET),
+    BallroomDance(0x4b, ActivityKind.BALLROOM_DANCE),
+    Baseball(0x4f, ActivityKind.BASEBALL),
+    Basketball(0x55, ActivityKind.BASKETBALL),
+    BattleRope(0xa7, ActivityKind.BATTLE_ROPE),
+    BeachVolleyball(0x7a, ActivityKind.BEACH_VOLLEYBALL),
+    BellyDance(0x48, ActivityKind.BELLY_DANCE),
+    Billiards(0x97, ActivityKind.BILLIARDS),
+    bmx(0x30, ActivityKind.BMX),
+    BoardGame(0xb1, ActivityKind.BOARD_GAME),
+    Bocce(0xaa, ActivityKind.BOCCE),
+    Bowling(0x50, ActivityKind.BOWLING),
+    Boxing(0x61, ActivityKind.BOXING),
+    Breaking(0xa8, ActivityKind.BREAKING),
+    Bridge(0xb0, ActivityKind.BRIDGE),
+    CardioCombat(0x72, ActivityKind.CARDIO_COMBAT),
+    Checkers(0xae, ActivityKind.CHECKERS),
+    Chess(0xad, ActivityKind.CHESS),
+    CoreTraining(0x32, ActivityKind.CORE_TRAINING),
+    Cricket(0x4e, ActivityKind.CRICKET),
+    CrossTraining(0x82, ActivityKind.CROSS_TRAINING),
+    Curling(0x29, ActivityKind.CURLING),
+    Dance(0x4c, ActivityKind.DANCE),
+    Darts(0x75, ActivityKind.DARTS),
+    Dodgeball(0x99, ActivityKind.DODGEBALL),
+    DragonBoat(0x8a, ActivityKind.DRAGON_BOAT),
+    Driving(0x84, ActivityKind.DRIVING),
+    Elliptical(0x09, ActivityKind.ELLIPTICAL_TRAINER),
+    Esports(0xbd, ActivityKind.ESPORTS),
+    Esquestrian(0x5e, ActivityKind.HORSE_RIDING),
+    Fencing(0x94, ActivityKind.FENCING),
+    Finswimming(0x9b, ActivityKind.FINSWIMMING),
+    Fishing(0x40, ActivityKind.FISHING),
+    Flexibility(0x37, ActivityKind.FLEXIBILITY),
+    Flowriding(0xac, ActivityKind.FLOWRIDING),
+    FolkDance(0x92, ActivityKind.FOLK_DANCE),
+    Freestyle(0x05, ActivityKind.FREE_TRAINING),
+    Frisbee(0x74, ActivityKind.FRISBEE),
+    Futsal(0xa4, ActivityKind.FUTSAL),
+    Gateball(0x57, ActivityKind.GATEBALL),
+    Gymnastics(0x3b, ActivityKind.GYMNASTICS),
+    HackySack(0xa9, ActivityKind.HACKY_SACK),
+    Handball(0x5b, ActivityKind.HANDBALL),
+    HIIT(0x31, ActivityKind.HIIT),
+    HipHop(0xa5, ActivityKind.HIP_HOP),
+    HorizontalBar(0x95, ActivityKind.HORIZONTAL_BAR),
+    HulaHoop(0x73, ActivityKind.HULA_HOOP),
+    IceHockey(0x9e, ActivityKind.ICE_HOCKEY),
+    IceSkating(0x2c, ActivityKind.ICE_SKATING),
+    IndoorCycling(0x08, ActivityKind.INDOOR_CYCLING),
+    IndoorFitness(0x18, ActivityKind.INDOOR_FITNESS),
+    IndoorIceSkating(0x2d, ActivityKind.INDOOR_ICE_SKATING),
+    JaiAlai(0xab, ActivityKind.JAI_ALAI),
+    JazzDance(0x71, ActivityKind.JAZZ_DANCE),
+    Judo(0x62, ActivityKind.JUDO),
+    Jujitsu(0x93, ActivityKind.JUJITSU),
+    JumpRope(0x15, ActivityKind.JUMP_ROPING),
+    Karate(0x60, ActivityKind.KARATE),
+    Kayaking(0x8c, ActivityKind.KAYAKING),
+    Kendo(0x5f, ActivityKind.KENDO),
+    Kickboxing(0x68, ActivityKind.KICKBOXING),
+    KiteFlying(0x76, ActivityKind.KITE_FLYING),
+    LatinDance(0x70, ActivityKind.LATIN_DANCE),
+    MartialArts(0x67, ActivityKind.MARTIAL_ARTS),
+    MassGymnastics(0x6f, ActivityKind.MASS_GYMNASTICS),
+    ModernDance(0xb9, ActivityKind.MODERN_DANCE),
+    MuayThai(0x65, ActivityKind.MUAY_THAI),
+    OutdoorCycling(0x04, ActivityKind.OUTDOOR_CYCLING),
+    OutdoorHiking(0x0f, ActivityKind.HIKING),
+    OutdoorRunning(0x01, ActivityKind.OUTDOOR_RUNNING),
+    OutdoorSwimming(0x07, ActivityKind.SWIMMING_OPENWATER),
+    ParallelBars(0x96, ActivityKind.PARALLEL_BARS),
+    Parkour(0x81, ActivityKind.PARKOUR),
+    Pilates(0x3d, ActivityKind.PILATES),
+    PoleDance(0xa6, ActivityKind.POLE_DANCE),
+    PoolSwimming(0x06, ActivityKind.POOL_SWIM),
+    RaceWalking(0x83, ActivityKind.RACE_WALKING),
+    RockClimbing(0x46, ActivityKind.ROCK_CLIMBING),
+    RollerSkating(0x45, ActivityKind.ROLLER_SKATING),
+    Rowing(0x17, ActivityKind.ROWING),
+    Sailing(0x41, ActivityKind.SAILING),
+    SepakTakraw(0x98, ActivityKind.SEPAK_TAKRAW),
+    Shuffleboard(0xa0, ActivityKind.SHUFFLEBOARD),
+    Shuttlecock(0xa2, ActivityKind.SHUTTLECOCK),
+    Skateboarding(0x43, ActivityKind.SKATEBOARDING),
+    Snorkeling(0x9d, ActivityKind.SNORKELING),
+    Soccer(0xbf, ActivityKind.SOCCER),
+    Softball(0x56, ActivityKind.SOFTBALL),
+    SomatosensoryGame(0xa3, ActivityKind.SOMATOSENSORY_GAME),
+    Spinning(0x8f, ActivityKind.SPINNING),
+    SquareDance(0x49, ActivityKind.SQUARE_DANCE),
+    Squash(0x51, ActivityKind.SQUASH),
+    StairClimber(0x36, ActivityKind.STAIR_CLIMBER),
+    Stepper(0x39, ActivityKind.STEPPER),
+    StreetDance(0x4a, ActivityKind.STREET_DANCE),
+    Strength(0x34, ActivityKind.STRENGTH_TRAINING),
+    Stretching(0x35, ActivityKind.STRETCHING),
+    Swinging(0x9f, ActivityKind.SWING),
+    TableFootball(0xa1, ActivityKind.TABLE_FOOTBALL),
+    TableTennis(0x59, ActivityKind.TABLE_TENNIS),
+    TaiChi(0x64, ActivityKind.TAI_CHI),
+    Taekwondo(0x66, ActivityKind.TAEKWONDO),
+    Tennis(0x11, ActivityKind.TENNIS),
+    Treadmill(0x02, ActivityKind.TREADMILL),
+    TugOfWar(0x77, ActivityKind.TUG_OF_WAR),
+    Volleyball(0x58, ActivityKind.VOLLEYBALL),
+    Walking(0x03, ActivityKind.WALKING),
+    WallBall(0x91, ActivityKind.WALL_BALL),
+    WaterPolo(0x9a, ActivityKind.WATER_POLO),
+    WaterRowing(0x42, ActivityKind.ROWING),
+    Weiqi(0xaf, ActivityKind.WEIQI),
+    Wrestling(0x63, ActivityKind.WRESTLING),
+    Yoga(0x3c, ActivityKind.YOGA),
+    Zumba(0x4d, ActivityKind.ZUMBA),
+    ;
+
+    private static final Logger LOG = LoggerFactory.getLogger(ZeppOsActivityType.class);
+
+    private final byte code;
+    private final ActivityKind activityKind;
+
+    ZeppOsActivityType(final int code, final ActivityKind activityKind) {
+        this.code = (byte) code;
+        this.activityKind = activityKind;
+    }
+
+    public byte getCode() {
+        return code;
+    }
+
+    public ActivityKind toActivityKind() {
+        switch (this) {
+            case Badminton:
+                return ActivityKind.BADMINTON;
+            case Basketball:
+                return ActivityKind.BASKETBALL;
+            case Cricket:
+                return ActivityKind.CRICKET;
+            case Elliptical:
+                return ActivityKind.ELLIPTICAL_TRAINER;
+            case Freestyle:
+            case IndoorFitness:
+                return ActivityKind.EXERCISE;
+            case IndoorCycling:
+                return ActivityKind.INDOOR_CYCLING;
+            case JumpRope:
+                return ActivityKind.JUMP_ROPING;
+            case OutdoorCycling:
+                return ActivityKind.CYCLING;
+            case OutdoorHiking:
+                return ActivityKind.HIKING;
+            case OutdoorRunning:
+                return ActivityKind.RUNNING;
+            case OutdoorSwimming:
+                return ActivityKind.SWIMMING_OPENWATER;
+            case PoolSwimming:
+                return ActivityKind.SWIMMING;
+            case RockClimbing:
+                return ActivityKind.CLIMBING;
+            case Rowing:
+                return ActivityKind.ROWING_MACHINE;
+            case Soccer:
+                return ActivityKind.SOCCER;
+            case TableTennis:
+                return ActivityKind.PINGPONG;
+            case Treadmill:
+                return ActivityKind.TREADMILL;
+            case Walking:
+            case RaceWalking:
+                return ActivityKind.WALKING;
+            case Strength:
+                return ActivityKind.STRENGTH_TRAINING;
+            case Yoga:
+                return ActivityKind.YOGA;
+        }
+
+        LOG.warn("Unmapped workout type {}", this);
+
+        return ActivityKind.UNKNOWN;
+    }
+
+    public static ZeppOsActivityType fromCode(final byte code) {
+        for (final ZeppOsActivityType type : values()) {
+            if (type.getCode() == code) {
+                return type;
+            }
+        }
+
+        return null;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/ZeppOsFwInstallHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/ZeppOsFwInstallHandler.java
new file mode 100644
index 0000000..3a7fa65
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/ZeppOsFwInstallHandler.java
@@ -0,0 +1,177 @@
+/*  Copyright (C) 2022-2024 Daniel Dakhno, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos;
+
+import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareType.AGPS_UIHH;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.net.Uri;
+
+import org.json.JSONObject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.Writer;
+import java.util.Set;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.InstallActivity;
+import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.zeppos.ZeppOsFwHelper;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp;
+import nodomain.freeyourgadget.gadgetbridge.model.GenericItem;
+import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
+
+public class ZeppOsFwInstallHandler implements InstallHandler {
+    private static final Logger LOG = LoggerFactory.getLogger(ZeppOsFwInstallHandler.class);
+
+    private final Uri mUri;
+    private final Context mContext;
+
+    private final ZeppOsFwHelper mHelper;
+
+    public ZeppOsFwInstallHandler(final Uri uri, final Context context, final String deviceName, final Set<Integer> deviceSources) {
+        mUri = uri;
+        mContext = context;
+        mHelper = new ZeppOsFwHelper(uri, context, deviceName, deviceSources);
+    }
+
+    @Override
+    public boolean isValid() {
+        return mHelper.isValid();
+    }
+
+    @Override
+    public void validateInstallation(InstallActivity installActivity, GBDevice device) {
+        if (device.isBusy()) {
+            installActivity.setInfoText(device.getBusyTask());
+            installActivity.setInstallEnabled(false);
+            return;
+        }
+
+        if (!device.isInitialized()) {
+            installActivity.setInfoText(mContext.getString(R.string.fwapp_install_device_not_ready));
+            installActivity.setInstallEnabled(false);
+            return;
+        }
+
+        if (!mHelper.isValid()) {
+            installActivity.setInfoText(mContext.getString(R.string.fwapp_install_device_not_supported));
+            installActivity.setInstallEnabled(false);
+            return;
+        }
+
+        final DeviceCoordinator coordinator = device.getDeviceCoordinator();
+
+        final GenericItem fwItem = new GenericItem(mContext.getString(
+                R.string.installhandler_firmware_name,
+                device.getAliasOrName(),
+                mHelper.getFirmwareType(), // TODO human name
+                mHelper.getFirmwareVersion()
+        ));
+        fwItem.setIcon(coordinator.getDefaultIconResource());
+
+        final StringBuilder sb = new StringBuilder();
+        if (!mHelper.getFirmwareType().isWatchface() && !mHelper.getFirmwareType().isApp() && mHelper.getFirmwareType() != AGPS_UIHH) {
+            sb.append(mContext.getString(
+                    R.string.fw_upgrade_notice,
+                    mContext.getString(R.string.fw_upgrade_notice_zepp_os, mHelper.getFirmwareVersion(), device.getAliasOrName())
+            ));
+
+            // TODO whitelisted firmware
+            //if (mHelper.isFirmwareWhitelisted()) {
+            //    sb.append(" ").append(mContext.getString(R.string.miband_firmware_known));
+            //    fwItem.setDetails(mContext.getString(R.string.miband_fwinstaller_compatible_version));
+            //    // TODO: set a CHECK (OKAY) button
+            //} else {
+                sb.append("  ").append(mContext.getString(R.string.miband_firmware_unknown_warning)).append(" \n\n")
+                        .append(mContext.getString(R.string.miband_firmware_suggest_whitelist, String.valueOf(mHelper.getFirmwareVersion())));
+                fwItem.setDetails(mContext.getString(R.string.miband_fwinstaller_untested_version));
+                // TODO: set a UNKNOWN (question mark) button
+            //}
+        }
+
+        installActivity.setPreview(mHelper.getPreview());
+
+        installActivity.setInfoText(sb.toString());
+        installActivity.setInstallItem(fwItem);
+        installActivity.setInstallEnabled(true);
+    }
+
+    @Override
+    public void onStartInstall(final GBDevice device) {
+        final boolean shouldCache = mHelper.getFirmwareType().isApp() || mHelper.getFirmwareType().isWatchface();
+        if (shouldCache) {
+            saveToCache(device);
+        }
+    }
+
+    private void saveToCache(final GBDevice device) {
+        final DeviceCoordinator coordinator = device.getDeviceCoordinator();
+
+        final File appCacheDir;
+        try {
+            appCacheDir = coordinator.getAppCacheDir();
+        } catch (final IOException e) {
+            LOG.error("Failed to get app cache dir", e);
+            return;
+        }
+
+        final GBDeviceApp app = mHelper.getAppInfo();
+        if (app == null) {
+            LOG.warn("Unable to cache app, appInfo is null");
+            return;
+        }
+
+        // write app zip
+        final File appOutputFile = new File(appCacheDir, app.getUUID().toString() + coordinator.getAppFileExtension());
+        try {
+            appCacheDir.mkdirs();
+            FileUtils.copyURItoFile(mContext, mUri, appOutputFile);
+        } catch (final IOException e) {
+            LOG.error("Failed to save app to cache", e);
+            return;
+        }
+
+        // write app metadata
+        final File metadataOutputFile = new File(appCacheDir, app.getUUID().toString() + ".json");
+        try (Writer writer = new BufferedWriter(new FileWriter(metadataOutputFile))) {
+            final JSONObject appJSON = app.getJSON();
+            writer.write(appJSON.toString());
+        } catch (final IOException e) {
+            LOG.error("Failed to write app metadata", e);
+            return;
+        }
+
+        if (app.getPreviewImage() != null) {
+            final File previewOutputFile = new File(appCacheDir, app.getUUID().toString() + "_preview.png");
+            try (FileOutputStream fos = new FileOutputStream(previewOutputFile)) {
+                app.getPreviewImage().compress(Bitmap.CompressFormat.PNG, 9, fos);
+            } catch (final IOException e) {
+                LOG.error("Failed to write app preview", e);
+            }
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/ZeppOsMenuType.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/ZeppOsMenuType.java
new file mode 100644
index 0000000..f70c451
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/ZeppOsMenuType.java
@@ -0,0 +1,142 @@
+/*  Copyright (C) 2022-2024 Jos茅 Rebelo, Maxime Reyrolle, Reiner Herrmann,
+    Sky233ml
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class ZeppOsMenuType {
+    /**
+     * These somewhat match the ones in HuamiMenuType, but not all. The band sends and
+     * receives those as 8-digit upper case hex strings.
+     */
+    public static final Map<String, String> displayItemNameLookup = new HashMap<String, String>() {{
+        put("00000001", "personal_activity_intelligence");
+        put("00000002", "hr");
+        put("00000003", "workout");
+        put("00000004", "weather");
+        put("00000009", "alarm");
+        put("0000000A", "takephoto");
+        put("0000000B", "music");
+        put("0000000C", "stopwatch");
+        put("0000000D", "countdown");
+        put("0000000E", "findphone");
+        put("0000000F", "mutephone");
+        put("00000010", "cards");
+        put("00000011", "alipay");
+        put("00000013", "settings");
+        put("00000014", "workout_history");
+        put("00000015", "eventreminder");
+        put("00000016", "compass");
+        put("00000019", "pai");
+        put("0000001A", "worldclock");
+        put("0000001C", "stress");
+        put("0000001D", "female_health");
+        put("0000001E", "workout_status");
+        put("00000020", "calendar");
+        put("00000023", "sleep");
+        put("00000024", "spo2");
+        put("00000025", "phone");
+        put("00000026", "events");
+        put("00000031", "wechat_pay");
+        put("00000033", "breathing");
+        put("00000038", "pomodoro");
+        put("00000039", "alexa2");
+        put("0000003B", "thermometer");
+        put("0000003E", "todo");
+        put("0000003F", "mi_ai");
+        put("00000041", "barometer");
+        put("00000042", "voice_memos");
+        put("00000044", "sun_moon");
+        put("00000045", "one_tap_measuring");
+        put("00000046", "zepp_coach");
+        put("00000047", "membership_cards");
+        put("00000049", "body_composition");
+        put("0000004A", "readiness");
+        put("0000004B", "map");
+        put("0000004C", "zepp_pay");
+        put("0000004D", "heart_rate_push");
+        put("00000100", "alexa");
+        put("00000101", "offline_voice");
+        put("00000102", "flashlight");
+    }};
+
+    public static final Map<String, String> shortcutsNameLookup = new HashMap<String, String>() {{
+        put("00000001", "hr");
+        put("00000002", "weather");
+        put("00000003", "pai");
+        put("00000004", "music");
+        put("00000005", "sleep");
+        put("00000006", "alipay");
+        put("00000008", "wechat_pay");
+        put("00000009", "cards");
+        put("0000000A", "workout");
+        put("0000000B", "workout_history");
+        put("0000000C", "workout_status");
+        put("0000000E", "one_tap_measuring");
+        put("0000000F", "stress");
+        put("00000011", "female_health");
+        put("00000012", "breathing");
+        put("00000013", "spo2");
+        put("00000015", "thermometer");
+        put("00000016", "alarm");
+        put("00000017", "calendar");
+        put("00000018", "events");
+        put("00000019", "todo");
+        put("0000001A", "worldclock");
+        put("0000001B", "compass");
+        put("0000001C", "barometer");
+        put("0000001E", "voice_memos");
+        put("00000020", "activity");
+        put("00000021", "eventreminder");
+        put("00000023", "mi_ai");
+        put("00000025", "alexa");
+        put("00000027", "workout_shortcuts");
+        put("00000028", "apps_shortcuts");
+        put("00000029", "body_composition");
+        put("0000002A", "readiness");
+        put("0000002B", "zepp_pay");
+        put("000FFD39", "hrv");
+    }};
+
+    public static final Map<String, String> controlCenterNameLookup = new HashMap<String, String>() {{
+        put("00000000", "flashlight");
+        put("00000001", "brightness");
+        put("00000002", "lockscreen");
+        put("00000003", "dnd");
+        put("00000004", "sleep");
+        put("00000005", "findphone");
+        put("00000006", "volume");
+        put("00000007", "battery");
+        put("00000008", "theater_mode");
+        put("00000009", "screen_always_lit");
+        put("0000000A", "bluetooth");
+        put("0000000B", "wifi");
+        put("0000000D", "calendar");
+        put("00000012", "alarm");
+        put("00000013", "settings");
+        put("00000014", "buzzer_intensity");
+        put("00000015", "barometer");
+        put("00000016", "compass");
+        put("00000017", "countdown");
+        put("00000018", "stopwatch");
+        put("00000019", "eject_water");
+        put("0000001A", "headphone");
+        put("0000001B", "night_display");
+    }};
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/ZeppOsSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/ZeppOsSupport.java
new file mode 100644
index 0000000..426a35f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/ZeppOsSupport.java
@@ -0,0 +1,1700 @@
+/*  Copyright (C) 2022-2024 Daniel Dakhno, Jos茅 Rebelo, Oleg Vasilev
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos;
+
+import static org.apache.commons.lang3.ArrayUtils.subarray;
+import static java.lang.Thread.sleep;
+import static nodomain.freeyourgadget.gadgetbridge.devices.huami.Huami2021Service.*;
+import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService.SUCCESS;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivityUser.PREF_USER_NAME;
+import static nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions.fromUint16;
+import static nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions.fromUint8;
+import static nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions.mapTimeZone;
+import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsConfigService.ConfigArg.FITNESS_GOAL_CALORIES;
+import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsConfigService.ConfigArg.FITNESS_GOAL_FAT_BURN_TIME;
+import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsConfigService.ConfigArg.FITNESS_GOAL_SLEEP;
+import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsConfigService.ConfigArg.FITNESS_GOAL_STANDING_TIME;
+import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsConfigService.ConfigArg.FITNESS_GOAL_STEPS;
+import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsConfigService.ConfigArg.FITNESS_GOAL_WEIGHT;
+import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsConfigService.ConfigArg.HEART_RATE_ALL_DAY_MONITORING;
+import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsConfigService.ConfigArg.LANGUAGE;
+import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsConfigService.ConfigArg.LANGUAGE_FOLLOW_PHONE;
+import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsConfigService.ConfigArg.PASSWORD_ENABLED;
+import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsConfigService.ConfigArg.PASSWORD_TEXT;
+import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsConfigService.ConfigArg.SLEEP_HIGH_ACCURACY_MONITORING;
+import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsConfigService.ConfigArg.TEMPERATURE_UNIT;
+import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsConfigService.ConfigArg.TIME_FORMAT;
+
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.content.Context;
+import android.location.Location;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.widget.Toast;
+
+import androidx.annotation.Nullable;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.StandardCharsets;
+import java.sql.Timestamp;
+import java.time.LocalDate;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventAppInfo;
+import nodomain.freeyourgadget.gadgetbridge.capabilities.loyaltycards.LoyaltyCard;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventDisplayMessage;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventFindPhone;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventScreenshot;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventSilentMode;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdatePreferences;
+import nodomain.freeyourgadget.gadgetbridge.service.SleepAsAndroidSender;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.zeppos.ZeppOsCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.Huami2021Service;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.zeppos.ZeppOsAgpsInstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.zeppos.ZeppOsGpxRouteInstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.VibrationProfile;
+import nodomain.freeyourgadget.gadgetbridge.externalevents.CalendarReceiver;
+import nodomain.freeyourgadget.gadgetbridge.externalevents.sleepasandroid.SleepAsAndroidAction;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+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.CannedMessagesSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.Contact;
+import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.Reminder;
+import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.GattCharacteristic;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiBatteryInfo;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiDeviceEvent;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiPhoneGpsStatus;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiVibrationPatternNotificationType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.update.UpdateFirmwareOperation;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.operations.ZeppOsFirmwareUpdateOperation;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.operations.ZeppOsAgpsUpdateOperation;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.operations.ZeppOsGpxRouteUploadOperation;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsAgpsService;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsAlarmsService;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsAlexaService;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsAppsService;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsCalendarService;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsCannedMessagesService;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsDisplayItemsService;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsHttpService;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsLogsService;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsLoyaltyCardService;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsMusicService;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsNotificationService;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsRemindersService;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsServicesService;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsShortcutCardsService;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsConfigService;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsContactsService;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsFileTransferService;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsFtpServerService;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsMorningUpdatesService;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsPhoneService;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsWatchfaceService;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsWifiService;
+import nodomain.freeyourgadget.gadgetbridge.util.AlarmUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+import nodomain.freeyourgadget.gadgetbridge.util.SilentMode;
+
+public class ZeppOsSupport extends HuamiSupport implements ZeppOsFileTransferService.Callback {
+    private static final Logger LOG = LoggerFactory.getLogger(ZeppOsSupport.class);
+
+    // Tracks whether realtime HR monitoring is already started, so we can just
+    // send CONTINUE commands
+    private boolean heartRateRealtimeStarted;
+    private ScheduledExecutorService heartRateRealtimeScheduler;
+    // Keep track of whether the rawSensor is enabled
+    private boolean rawSensor = false;
+    private ScheduledExecutorService rawSensorScheduler;
+
+    // Services
+    private final ZeppOsServicesService servicesService = new ZeppOsServicesService(this);
+    private final ZeppOsFileTransferService fileTransferService = new ZeppOsFileTransferService(this);
+    private final ZeppOsConfigService configService = new ZeppOsConfigService(this);
+    private final ZeppOsAgpsService agpsService = new ZeppOsAgpsService(this);
+    private final ZeppOsWifiService wifiService = new ZeppOsWifiService(this);
+    private final ZeppOsFtpServerService ftpServerService = new ZeppOsFtpServerService(this);
+    private final ZeppOsContactsService contactsService = new ZeppOsContactsService(this);
+    private final ZeppOsMorningUpdatesService morningUpdatesService = new ZeppOsMorningUpdatesService(this);
+    private final ZeppOsPhoneService phoneService = new ZeppOsPhoneService(this);
+    private final ZeppOsShortcutCardsService shortcutCardsService = new ZeppOsShortcutCardsService(this);
+    private final ZeppOsWatchfaceService watchfaceService = new ZeppOsWatchfaceService(this);
+    private final ZeppOsAlarmsService alarmsService = new ZeppOsAlarmsService(this);
+    private final ZeppOsCalendarService calendarService = new ZeppOsCalendarService(this);
+    private final ZeppOsCannedMessagesService cannedMessagesService = new ZeppOsCannedMessagesService(this);
+    private final ZeppOsNotificationService notificationService = new ZeppOsNotificationService(this, fileTransferService);
+    private final ZeppOsAlexaService alexaService = new ZeppOsAlexaService(this);
+    private final ZeppOsAppsService appsService = new ZeppOsAppsService(this);
+    private final ZeppOsLogsService logsService = new ZeppOsLogsService(this);
+    private final ZeppOsDisplayItemsService displayItemsService = new ZeppOsDisplayItemsService(this);
+    private final ZeppOsHttpService httpService = new ZeppOsHttpService(this);
+    private final ZeppOsRemindersService remindersService = new ZeppOsRemindersService(this);
+    private final ZeppOsLoyaltyCardService loyaltyCardService = new ZeppOsLoyaltyCardService(this);
+    private final ZeppOsMusicService musicService = new ZeppOsMusicService(this);
+
+    private final Set<Short> mSupportedServices = new HashSet<>();
+    // FIXME: We need to keep track of which services are encrypted for now, since not all of them were yet migrated to a service
+    private final Set<Short> mIsEncrypted = new HashSet<>();
+    private final Map<Short, AbstractZeppOsService> mServiceMap = new LinkedHashMap<Short, AbstractZeppOsService>() {{
+        put(servicesService.getEndpoint(), servicesService);
+        put(fileTransferService.getEndpoint(), fileTransferService);
+        put(configService.getEndpoint(), configService);
+        put(agpsService.getEndpoint(), agpsService);
+        put(wifiService.getEndpoint(), wifiService);
+        put(ftpServerService.getEndpoint(), ftpServerService);
+        put(contactsService.getEndpoint(), contactsService);
+        put(morningUpdatesService.getEndpoint(), morningUpdatesService);
+        put(phoneService.getEndpoint(), phoneService);
+        put(shortcutCardsService.getEndpoint(), shortcutCardsService);
+        put(watchfaceService.getEndpoint(), watchfaceService);
+        put(alarmsService.getEndpoint(), alarmsService);
+        put(calendarService.getEndpoint(), calendarService);
+        put(cannedMessagesService.getEndpoint(), cannedMessagesService);
+        put(notificationService.getEndpoint(), notificationService);
+        put(alexaService.getEndpoint(), alexaService);
+        put(appsService.getEndpoint(), appsService);
+        put(logsService.getEndpoint(), logsService);
+        put(displayItemsService.getEndpoint(), displayItemsService);
+        put(httpService.getEndpoint(), httpService);
+        put(remindersService.getEndpoint(), remindersService);
+        put(loyaltyCardService.getEndpoint(), loyaltyCardService);
+        put(musicService.getEndpoint(), musicService);
+    }};
+
+    public ZeppOsSupport() {
+        this(LOG);
+    }
+
+    public ZeppOsSupport(final Logger logger) {
+        super(logger);
+    }
+
+    @Override
+    protected byte getAuthFlags() {
+        return 0x00;
+    }
+
+    @Override
+    public byte getCryptFlags() {
+        return (byte) 0x80;
+    }
+
+    /**
+     * Do not reset the gatt callback implicitly, as that would interrupt operations.
+     * See <a href="https://codeberg.org/Freeyourgadget/Gadgetbridge/pulls/2912">#2912</a> for more
+     * information.
+     */
+    @Override
+    public boolean getImplicitCallbackModify() {
+        return false;
+    }
+
+    @Override
+    public void onSendConfiguration(final String config) {
+        final Prefs prefs = getDevicePrefs();
+
+        // FIXME: This should not be handled here
+        switch (config) {
+            case ActivityUser.PREF_USER_STEPS_GOAL:
+            case ActivityUser.PREF_USER_CALORIES_BURNT:
+            case ActivityUser.PREF_USER_SLEEP_DURATION:
+            case ActivityUser.PREF_USER_GOAL_WEIGHT_KG:
+            case ActivityUser.PREF_USER_GOAL_STANDING_TIME_HOURS:
+            case ActivityUser.PREF_USER_GOAL_FAT_BURN_TIME_MINUTES:
+                final TransactionBuilder builder = createTransactionBuilder("set fitness goal");
+                setFitnessGoal(builder);
+                builder.queue(getQueue());
+                return;
+        }
+
+        // Check if any of the services handles this config
+        for (AbstractZeppOsService service : mServiceMap.values()) {
+            if (service.onSendConfiguration(config, prefs)) {
+                return;
+            }
+        }
+
+        LOG.warn("Unhandled config {}, will pass to HuamiSupport", config);
+
+        super.onSendConfiguration(config);
+    }
+
+    @Override
+    public void onTestNewFunction() {
+        setRawSensor(!rawSensor);
+    }
+
+    @Override
+    protected void acknowledgeFindPhone() {
+        LOG.info("Acknowledging find phone");
+
+        final byte[] cmd = new byte[]{FIND_PHONE_ACK, SUCCESS};
+
+        writeToChunked2021("ack find phone", CHUNKED2021_ENDPOINT_FIND_DEVICE, cmd, true);
+    }
+
+    protected void stopFindPhone() {
+        LOG.info("Stopping find phone");
+
+        writeToChunked2021("found phone", CHUNKED2021_ENDPOINT_FIND_DEVICE, FIND_PHONE_STOP_FROM_PHONE, true);
+    }
+
+    @Override
+    public void onFindDevice(final boolean start) {
+        if (getCoordinator().supportsContinuousFindDevice()) {
+            sendFindDeviceCommand(start);
+        } else {
+            // Vibrate band periodically
+            super.onFindDevice(start);
+        }
+    }
+
+    @Override
+    protected void sendFindDeviceCommand(boolean start) {
+        final byte findBandCommand = start ? FIND_BAND_START : FIND_BAND_STOP_FROM_PHONE;
+
+        LOG.info("Sending find band {}", start);
+
+        try {
+            final TransactionBuilder builder = performInitialized("find huami 2021");
+            writeToChunked2021(builder, CHUNKED2021_ENDPOINT_FIND_DEVICE, findBandCommand, true);
+            builder.queue(getQueue());
+        } catch (IOException e) {
+            LOG.error("error while sending find Huami 2021 device command", e);
+        }
+    }
+
+    @Override
+    public void onFindPhone(final boolean start) {
+        LOG.info("Find phone: {}", start);
+
+        findPhoneStarted = start;
+
+        if (!start) {
+            stopFindPhone();
+        }
+    }
+
+    @Override
+    public void onScreenshotReq() {
+        appsService.requestScreenshot();
+    }
+
+    @Override
+    public void onSetHeartRateMeasurementInterval(final int seconds) {
+        try {
+            int minuteInterval;
+            if (seconds == -1) {
+                // Smart
+                minuteInterval = -1;
+            } else {
+                minuteInterval = seconds / 60;
+                minuteInterval = Math.min(minuteInterval, 120);
+                minuteInterval = Math.max(0, minuteInterval);
+            }
+
+            final TransactionBuilder builder = performInitialized(String.format("set heart rate interval to: %d minutes", minuteInterval));
+            setHeartrateMeasurementInterval(builder, minuteInterval);
+            builder.queue(getQueue());
+        } catch (final IOException e) {
+            GB.toast(getContext(), "Error toggling heart measurement interval: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
+        }
+    }
+
+    @Override
+    protected ZeppOsSupport sendCalendarEvents(final TransactionBuilder builder) {
+        // We have native calendar sync
+        CalendarReceiver.forceSync();
+        return this;
+    }
+
+    @Override
+    public void onAddCalendarEvent(final CalendarEventSpec calendarEventSpec) {
+        calendarService.addEvent(calendarEventSpec);
+    }
+
+    @Override
+    public void onDeleteCalendarEvent(final byte type, final long id) {
+        calendarService.deleteEvent(type, id);
+    }
+
+    @Override
+    public void onHeartRateTest() {
+        // TODO onHeartRateTest - what modes? this only works sometimes
+
+        try {
+            final TransactionBuilder builder = performInitialized("HeartRateTest");
+            enableNotifyHeartRateMeasurements(true, builder);
+            //writeToChunked2021(builder, CHUNKED2021_ENDPOINT_HEARTRATE, new byte[]{HEART_RATE_CMD_REALTIME_SET, HEART_RATE_REALTIME_MODE_START}, false);
+            builder.queue(getQueue());
+        } catch (final IOException e) {
+            LOG.error("Unable to read heart rate from Huami 2021 device", e);
+        }
+    }
+
+    @Override
+    public void onEnableRealtimeHeartRateMeasurement(final boolean enable) {
+        final byte hrcmd;
+        if (!enable) {
+            hrcmd = HEART_RATE_REALTIME_MODE_STOP;
+        } else if (heartRateRealtimeStarted == enable) {
+            hrcmd = HEART_RATE_REALTIME_MODE_CONTINUE;
+        } else {
+            // enable == true, for the first time
+            hrcmd = HEART_RATE_REALTIME_MODE_START;
+        }
+
+        heartRateRealtimeStarted = enable;
+
+        try {
+            final TransactionBuilder builder = performInitialized("Set realtime heart rate measurement = " + enable);
+            enableNotifyHeartRateMeasurements(enable, builder);
+            writeToChunked2021(builder, CHUNKED2021_ENDPOINT_HEARTRATE, new byte[]{HEART_RATE_CMD_REALTIME_SET, hrcmd}, false);
+            builder.queue(getQueue());
+            enableRealtimeSamplesTimer(enable);
+        } catch (final IOException e) {
+            LOG.error("Unable to set realtime heart rate measurement", e);
+        }
+    }
+
+    @Override
+    protected ZeppOsSupport requestBatteryInfo(TransactionBuilder builder) {
+        LOG.debug("Requesting Battery Info");
+
+        writeToChunked2021(builder, CHUNKED2021_ENDPOINT_BATTERY, BATTERY_REQUEST, false);
+
+        return this;
+    }
+
+    @Override
+    protected ZeppOsSupport setFitnessGoal(final TransactionBuilder builder) {
+        final int goalSteps = GBApplication.getPrefs().getInt(ActivityUser.PREF_USER_STEPS_GOAL, ActivityUser.defaultUserStepsGoal);
+        final int goalCalories = GBApplication.getPrefs().getInt(ActivityUser.PREF_USER_CALORIES_BURNT, ActivityUser.defaultUserCaloriesBurntGoal);
+        final int goalSleep = GBApplication.getPrefs().getInt(ActivityUser.PREF_USER_SLEEP_DURATION, ActivityUser.defaultUserSleepDurationGoal);
+        final int goalWeight = GBApplication.getPrefs().getInt(ActivityUser.PREF_USER_GOAL_WEIGHT_KG, ActivityUser.defaultUserGoalWeightKg);
+        final int goalStandingTime = GBApplication.getPrefs().getInt(ActivityUser.PREF_USER_GOAL_STANDING_TIME_HOURS, ActivityUser.defaultUserGoalStandingTimeHours);
+        final int goalFatBurnTime = GBApplication.getPrefs().getInt(ActivityUser.PREF_USER_GOAL_FAT_BURN_TIME_MINUTES, ActivityUser.defaultUserFatBurnTimeMinutes);
+        LOG.info("Setting Fitness Goals to steps={}, calories={}, sleep={}, weight={}, standingTime={}, fatBurn={}", goalSteps, goalCalories, goalSleep, goalWeight, goalStandingTime, goalFatBurnTime);
+
+        configService.newSetter()
+                .setInt(FITNESS_GOAL_STEPS, goalSteps)
+                .setShort(FITNESS_GOAL_CALORIES, (short) goalCalories)
+                .setShort(FITNESS_GOAL_SLEEP, (short) (goalSleep * 60))
+                .setShort(FITNESS_GOAL_WEIGHT, (short) goalWeight)
+                .setShort(FITNESS_GOAL_STANDING_TIME, (short) (goalStandingTime))
+                .setShort(FITNESS_GOAL_FAT_BURN_TIME, (short) goalFatBurnTime)
+                .write(builder);
+
+        return this;
+    }
+
+    @Override
+    protected ZeppOsSupport setUserInfo(final TransactionBuilder builder) {
+        LOG.info("Attempting to set user info...");
+
+        final Prefs prefs = GBApplication.getPrefs();
+        final Prefs devicePrefs = getDevicePrefs();
+
+        final String alias = prefs.getString(PREF_USER_NAME, null);
+        final ActivityUser activityUser = new ActivityUser();
+        final int height = activityUser.getHeightCm();
+        final int weight = activityUser.getWeightKg();
+        final LocalDate dateOfBirth = activityUser.getDateOfBirth();
+        final int birthYear = dateOfBirth.getYear();
+        final byte birthMonth = (byte) dateOfBirth.getMonthValue();
+        final byte birthDay = (byte) dateOfBirth.getDayOfMonth();
+        final String region = devicePrefs.getString(DeviceSettingsPreferenceConst.PREF_DEVICE_REGION, "unknown");
+
+        if (alias == null || weight == 0 || height == 0 || birthYear == 0) {
+            LOG.warn("Unable to set user info, make sure it is set up");
+            return this;
+        }
+
+        byte genderByte = 2; // other
+        switch (activityUser.getGender()) {
+            case ActivityUser.GENDER_MALE:
+                genderByte = 0;
+                break;
+            case ActivityUser.GENDER_FEMALE:
+                genderByte = 1;
+        }
+        final int userid = alias.hashCode();
+
+        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+
+        try {
+            baos.write(USER_INFO_CMD_SET);
+            baos.write(new byte[]{0x4f, 0x07, 0x00, 0x00});
+            baos.write(fromUint16(birthYear));
+            baos.write(birthMonth);
+            baos.write(birthDay);
+            baos.write(genderByte);
+            baos.write(fromUint16(height));
+            baos.write(fromUint16(weight * 200));
+            baos.write(BLETypeConversions.fromUint64(userid));
+            baos.write(region.getBytes(StandardCharsets.UTF_8));
+            baos.write(0);
+            baos.write(0x09); // TODO ?
+            baos.write(alias.getBytes(StandardCharsets.UTF_8));
+            baos.write((byte) 0);
+
+            writeToChunked2021(builder, Huami2021Service.CHUNKED2021_ENDPOINT_USER_INFO, baos.toByteArray(), true);
+        } catch (final Exception e) {
+            LOG.error("Failed to send user info", e);
+        }
+
+        return this;
+    }
+
+    @Override
+    protected ZeppOsSupport setPassword(final TransactionBuilder builder) {
+        final boolean passwordEnabled = HuamiCoordinator.getPasswordEnabled(gbDevice.getAddress());
+        final String password = HuamiCoordinator.getPassword(gbDevice.getAddress());
+
+        LOG.info("Setting password: {}, {}", passwordEnabled, password);
+
+        if (password == null || password.isEmpty()) {
+            LOG.warn("Invalid password: {}", password);
+            return this;
+        }
+
+        configService.newSetter()
+                .setBoolean(PASSWORD_ENABLED, passwordEnabled)
+                .setString(PASSWORD_TEXT, password)
+                .write(builder);
+
+        return this;
+    }
+
+    @Override
+    protected void queueAlarm(final Alarm alarm, final TransactionBuilder builder) {
+        alarmsService.sendAlarm(alarm, builder);
+    }
+
+    @Override
+    public void onSetCallState(final CallSpec callSpec) {
+        notificationService.setCallState(callSpec);
+    }
+
+    @Override
+    public void onNotification(final NotificationSpec notificationSpec) {
+        notificationService.sendNotification(notificationSpec);
+    }
+
+    @Override
+    public void onSetReminders(final ArrayList<? extends Reminder> reminders) {
+        final TransactionBuilder builder;
+        try {
+            builder = performInitialized("onSetReminders");
+            remindersService.sendReminders(builder, reminders);
+            builder.queue(getQueue());
+        } catch (final IOException e) {
+            LOG.error("Unable to send reminders to device", e);
+        }
+    }
+
+    @Override
+    public void onSetLoyaltyCards(final ArrayList<LoyaltyCard> cards) {
+        loyaltyCardService.setCards(cards);
+    }
+
+    @Override
+    public void onSetContacts(ArrayList<? extends Contact> contacts) {
+        //noinspection unchecked
+        contactsService.setContacts((List<Contact>) contacts);
+    }
+
+    @Override
+    protected boolean isWorldClocksEncrypted() {
+        return true;
+    }
+
+    @Override
+    public void onDeleteNotification(final int id) {
+        notificationService.deleteNotification(id);
+    }
+
+    @Override
+    protected void sendPhoneGps(final HuamiPhoneGpsStatus status, final Location location) {
+        final byte[] locationBytes = encodePhoneGpsPayload(status, location);
+
+        final ByteBuffer buf = ByteBuffer.allocate(2 + locationBytes.length);
+        buf.order(ByteOrder.LITTLE_ENDIAN);
+        buf.put(WORKOUT_CMD_GPS_LOCATION);
+        buf.put((byte) 0x00); // ?
+        buf.put(locationBytes);
+
+        writeToChunked2021("send phone gps", CHUNKED2021_ENDPOINT_WORKOUT, buf.array(), true);
+    }
+
+    @Override
+    public void onSetCannedMessages(final CannedMessagesSpec cannedMessagesSpec) {
+        cannedMessagesService.setCannedMessages(cannedMessagesSpec);
+    }
+
+    @Override
+    public void onSetPhoneVolume(final float volume) {
+        musicService.sendVolume(volume);
+    }
+
+    @Override
+    protected void sendMusicStateToDevice(final MusicSpec musicSpec, final MusicStateSpec musicStateSpec) {
+        musicService.sendMusicState(musicSpec, musicStateSpec);
+    }
+
+    @Override
+    public void onEnableRealtimeSteps(final boolean enable) {
+        final byte[] cmd = {STEPS_CMD_ENABLE_REALTIME, bool(enable)};
+
+        writeToChunked2021("toggle realtime steps", CHUNKED2021_ENDPOINT_STEPS, cmd, false);
+    }
+
+    @Override
+    public UpdateFirmwareOperation createUpdateFirmwareOperation(final Uri uri) {
+        throw new UnsupportedOperationException("this method should not be used");
+    }
+
+    @Override
+    public void onInstallApp(final Uri uri) {
+        final ZeppOsAgpsInstallHandler agpsHandler = new ZeppOsAgpsInstallHandler(uri, getContext());
+        if (agpsHandler.isValid()) {
+            try {
+                if (getCoordinator().sendAgpsAsFileTransfer()) {
+                    LOG.info("Sending AGPS as file transfer");
+
+                    if (getMTU() == 23) {
+                        // AGPS updates without high MTU are too slow and eventually get stuck,
+                        // so let's fail right away and inform the user
+                        LOG.warn("MTU of {} is too low for AGPS file transfer", getMTU());
+                        handleGBDeviceEvent(new GBDeviceEventDisplayMessage(
+                                getContext().getString(R.string.updatefirmwareoperation_failed_low_mtu, getMTU()),
+                                Toast.LENGTH_LONG,
+                                GB.WARN
+                        ));
+                        return;
+                    }
+
+                    new ZeppOsAgpsUpdateOperation(
+                            this,
+                            agpsHandler.getFile(),
+                            agpsService,
+                            fileTransferService,
+                            configService
+                    ).perform();
+                } else {
+                    LOG.info("Sending AGPS as firmware update");
+
+                    // Write the agps epo update to a temporary file in cache, so we can reuse the firmware update operation
+                    final File cacheDir = getContext().getCacheDir();
+                    final File agpsCacheDir = new File(cacheDir, "zepp-os-agps");
+                    //noinspection ResultOfMethodCallIgnored
+                    agpsCacheDir.mkdir();
+                    final File uihhFile = new File(agpsCacheDir, "epo-agps.uihh");
+
+                    try (FileOutputStream outputStream = new FileOutputStream(uihhFile)) {
+                        outputStream.write(agpsHandler.getFile().getUihhBytes());
+                    } catch (final IOException e) {
+                        LOG.error("Failed to write agps bytes to temporary uihhFile", e);
+                        return;
+                    }
+
+                    new ZeppOsFirmwareUpdateOperation(
+                            Uri.parse(uihhFile.toURI().toString()),
+                            this
+                    ).perform();
+                }
+            } catch (final Exception e) {
+                GB.toast(getContext(), "AGPS install error: " + e.getMessage(), Toast.LENGTH_LONG, GB.ERROR, e);
+            }
+
+            return;
+        }
+
+        final ZeppOsGpxRouteInstallHandler gpxRouteHandler = new ZeppOsGpxRouteInstallHandler(uri, getContext());
+        if (gpxRouteHandler.isValid()) {
+            try {
+                new ZeppOsGpxRouteUploadOperation(
+                        this,
+                        gpxRouteHandler.getFile(),
+                        fileTransferService
+                ).perform();
+            } catch (final Exception e) {
+                GB.toast(getContext(), "Gpx install error: " + e.getMessage(), Toast.LENGTH_LONG, GB.ERROR, e);
+            }
+
+            return;
+        }
+
+        try {
+            new ZeppOsFirmwareUpdateOperation(uri, this).perform();
+        } catch (final IOException ex) {
+            GB.toast(getContext(), "Firmware install error: " + ex.getMessage(), Toast.LENGTH_LONG, GB.ERROR, ex);
+        }
+    }
+
+    @Override
+    public void onAppInfoReq() {
+        // Merge the data from apps and watchfaces
+        // This is required because the apps service only knows the versions, not the app type,
+        // and the watchface service only knows the app IDs, and not the versions
+
+        final GBDeviceEventAppInfo appInfoCmd = new GBDeviceEventAppInfo();
+        final List<GBDeviceApp> appsFull = new ArrayList<>();
+
+        final Map<UUID, GBDeviceApp> watchfacesById = new HashMap<>();
+        final List<GBDeviceApp> watchfaces = watchfaceService.getWatchfaces();
+        for (final GBDeviceApp watchface : watchfaces) {
+            watchfacesById.put(watchface.getUUID(), watchface);
+        }
+
+        final List<GBDeviceApp> apps = appsService.getApps();
+        for (final GBDeviceApp app : apps) {
+            final GBDeviceApp watchface = watchfacesById.get(app.getUUID());
+            if (watchface != null) {
+                appsFull.add(new GBDeviceApp(
+                        watchface.getUUID(),
+                        watchface.getName(),
+                        watchface.getCreator(),
+                        app.getVersion(),
+                        GBDeviceApp.Type.WATCHFACE
+                ));
+            } else {
+                appsFull.add(new GBDeviceApp(
+                        app.getUUID(),
+                        app.getName(),
+                        app.getCreator(),
+                        app.getVersion(),
+                        GBDeviceApp.Type.APP_GENERIC
+                ));
+            }
+        }
+
+        appInfoCmd.apps = appsFull.toArray(new GBDeviceApp[0]);
+        evaluateGBDeviceEvent(appInfoCmd);
+    }
+
+    @Override
+    public void onAppStart(final UUID uuid, final boolean start) {
+        if (start) {
+            // This actually also starts apps...
+            watchfaceService.setWatchface(uuid);
+        }
+    }
+
+    @Override
+    public void onAppDelete(final UUID uuid) {
+        appsService.deleteApp(uuid);
+    }
+
+    @Override
+    protected ZeppOsSupport setHeartrateSleepSupport(final TransactionBuilder builder) {
+        final boolean enableHrSleepSupport = MiBandCoordinator.getHeartrateSleepSupport(gbDevice.getAddress());
+
+        configService.newSetter()
+                .setBoolean(SLEEP_HIGH_ACCURACY_MONITORING, enableHrSleepSupport)
+                .write(builder);
+
+        return this;
+    }
+
+    @Override
+    public byte[] getTimeBytes(final Calendar calendar, final TimeUnit precision) {
+        final byte[] bytes = BLETypeConversions.shortCalendarToRawBytes(calendar);
+
+        if (precision != TimeUnit.MINUTES && precision != TimeUnit.SECONDS) {
+            throw new IllegalArgumentException("Unsupported precision, only MINUTES and SECONDS are supported");
+        }
+        final byte seconds = precision == TimeUnit.SECONDS ? fromUint8(calendar.get(Calendar.SECOND)) : 0;
+        final byte tz = BLETypeConversions.mapTimeZone(calendar, BLETypeConversions.TZ_FLAG_INCLUDE_DST_IN_TZ);
+        return BLETypeConversions.join(bytes, new byte[]{seconds, tz});
+    }
+
+    @Override
+    public ZeppOsSupport setCurrentTimeWithService(TransactionBuilder builder) {
+        // It seems that the format sent to the Current Time characteristic changed in newer devices
+        // to kind-of match the GATT spec, but it doesn't quite respect it?
+        // - 11 bytes get sent instead of 10 (extra byte at the end for the offset in quarter-hours?)
+        // - Day of week starts at 0
+        // Otherwise, the command gets rejected with an "Out of Range" error and init fails.
+
+        final Calendar timestamp = createCalendar();
+        final byte[] year = fromUint16(timestamp.get(Calendar.YEAR));
+
+        final byte[] cmd = {
+                year[0],
+                year[1],
+                fromUint8(timestamp.get(Calendar.MONTH) + 1),
+                fromUint8(timestamp.get(Calendar.DATE)),
+                fromUint8(timestamp.get(Calendar.HOUR_OF_DAY)),
+                fromUint8(timestamp.get(Calendar.MINUTE)),
+                fromUint8(timestamp.get(Calendar.SECOND)),
+                fromUint8(timestamp.get(Calendar.DAY_OF_WEEK) - 1),
+                0x00, // Fractions256?
+                0x08, // Reason for change?
+                mapTimeZone(timestamp, BLETypeConversions.TZ_FLAG_INCLUDE_DST_IN_TZ), // TODO: Confirm this
+        };
+
+        builder.write(getCharacteristic(GattCharacteristic.UUID_CHARACTERISTIC_CURRENT_TIME), cmd);
+
+        return this;
+    }
+
+    @Override
+    public HuamiSupport enableNotifications(final TransactionBuilder builder, final boolean enable) {
+        builder.notify(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_CHUNKEDTRANSFER_2021_READ), enable);
+        return this;
+    }
+
+    @Override
+    public ZeppOsSupport enableFurtherNotifications(final TransactionBuilder builder,
+                                                    final boolean enable) {
+        // Nothing to do here, they are already enabled from enableNotifications
+        return this;
+    }
+
+    @Override
+    protected HuamiSupport setHeartrateMeasurementInterval(final TransactionBuilder builder, final int minutes) {
+        configService.newSetter()
+                .setByte(HEART_RATE_ALL_DAY_MONITORING, (byte) minutes)
+                .write(builder);
+
+        return this;
+    }
+
+    @Override
+    protected boolean supportsDeviceDefaultVibrationProfiles() {
+        return true;
+    }
+
+    @Override
+    protected void setVibrationPattern(final TransactionBuilder builder,
+                                       final HuamiVibrationPatternNotificationType notificationType,
+                                       final boolean test,
+                                       final VibrationProfile profile) {
+        final int MAX_TOTAL_LENGTH_MS = 10_000; // 10 seconds, about as long as Mi Fit allows
+
+        // The on-off sequence, until the max total length is reached
+        final List<Short> onOff = truncateVibrationsOnOff(profile, MAX_TOTAL_LENGTH_MS);
+
+        final ByteBuffer buf = ByteBuffer.allocate(5 + 2 * onOff.size());
+        buf.order(ByteOrder.LITTLE_ENDIAN);
+
+        buf.put(VIBRATION_PATTERN_SET);
+        buf.put(notificationType.getCode());
+        buf.put((byte) (profile != null ? 1 : 0)); // 1 for custom, 0 for device default
+        buf.put((byte) (test ? 1 : 0));
+        buf.put((byte) (onOff.size() / 2));
+
+        for (Short time : onOff) {
+            buf.putShort(time);
+        }
+
+        writeToChunked2021(builder, Huami2021Service.CHUNKED2021_ENDPOINT_VIBRATION_PATTERNS, buf.array(), true);
+    }
+
+    @Override
+    public void onSendWeather(final ArrayList<WeatherSpec> weatherSpecs) {
+        final WeatherSpec weatherSpec = weatherSpecs.get(0);
+
+        // Weather is not sent directly to the bands, they send HTTP requests for each location.
+        // When we have a weather update, set the default location to that location on the band.
+        // TODO: Support for multiple weather locations
+
+        final String locationKey = "1.234,-5.678,xiaomi_accu:" + System.currentTimeMillis(); // dummy
+        final String locationName = weatherSpec.location;
+
+        try {
+            final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            baos.write(Huami2021Service.WEATHER_CMD_SET_DEFAULT_LOCATION);
+            baos.write((byte) 0x02); // ? 2 for current, 4 for default
+            baos.write((byte) 0x00); // ?
+            baos.write((byte) 0x00); // ?
+            baos.write((byte) 0x00); // ?
+            baos.write(locationKey.getBytes(StandardCharsets.UTF_8));
+            baos.write((byte) 0x00); // ?
+            baos.write(locationName.getBytes(StandardCharsets.UTF_8));
+            baos.write((byte) 0x00); // ?
+
+            final TransactionBuilder builder = performInitialized("set weather location");
+            writeToChunked2021(builder, Huami2021Service.CHUNKED2021_ENDPOINT_WEATHER, baos.toByteArray(), false);
+            builder.queue(getQueue());
+        } catch (final Exception e) {
+            LOG.error("Failed to set weather location", e);
+        }
+    }
+
+    @Override
+    public void onSleepAsAndroidAction(String action, Bundle extras) {
+        // Validate if our device can work with an action
+        try {
+            sleepAsAndroidSender.validateAction(action);
+        } catch (UnsupportedOperationException e) {
+            return;
+        }
+
+        // Consult the SleepAsAndroid documentation for a set of actions and their extra
+        // https://docs.sleep.urbandroid.org/devs/wearable_api.html
+        switch (action) {
+            case SleepAsAndroidAction.CHECK_CONNECTED:
+                sleepAsAndroidSender.confirmConnected();
+                break;
+            // Received when the app starts sleep tracking
+            case SleepAsAndroidAction.START_TRACKING:
+                enableRealtimeHeartRateMeasurement(true);
+                enableRawSensor(true);
+                sleepAsAndroidSender.startTracking();
+                break;
+            // Received when the app stops sleep tracking
+            case SleepAsAndroidAction.STOP_TRACKING:
+                enableRealtimeHeartRateMeasurement(false);
+                enableRawSensor(false);
+                sleepAsAndroidSender.stopTracking();
+                break;
+            // Received when the app pauses sleep tracking
+//            case SleepAsAndroidAction.SET_PAUSE:
+//                long pauseTimestamp = extras.getLong("TIMESTAMP");
+//                long delay = pauseTimestamp > 0 ? pauseTimestamp - System.currentTimeMillis() : 0;
+//                setRawSensor(delay > 0);
+//                enableRealtimeSamplesTimer(delay > 0);
+//                sleepAsAndroidSender.pauseTracking(delay);
+//                break;
+            // Same as above but controlled by a boolean value
+            case SleepAsAndroidAction.SET_SUSPENDED:
+                boolean suspended = extras.getBoolean("SUSPENDED", false);
+                setRawSensor(!suspended);
+                enableRealtimeSamplesTimer(!suspended);
+                sleepAsAndroidSender.pauseTracking(suspended);
+                break;
+            // Received when the app changes the batch size for the movement data
+            case SleepAsAndroidAction.SET_BATCH_SIZE:
+                long batchSize = extras.getLong("SIZE", 12L);
+                sleepAsAndroidSender.setBatchSize(batchSize);
+                break;
+            // Received when the app requests the wearable to vibrate
+            case SleepAsAndroidAction.HINT:
+                int repeat = extras.getInt("REPEAT");
+                for (int i = 0; i < repeat; i++) {
+                    sendFindDeviceCommand(true);
+                    try {
+                        sleep(500);
+                        sendFindDeviceCommand(false);
+                    } catch (InterruptedException e) {
+                        throw new RuntimeException(e);
+                    }
+                }
+                break;
+            // Received when the app sends a notificaation
+            case SleepAsAndroidAction.SHOW_NOTIFICATION:
+                NotificationSpec notificationSpec = new NotificationSpec();
+                notificationSpec.title = extras.getString("TITLE");
+                notificationSpec.body = extras.getString("BODY");
+                notificationService.sendNotification(notificationSpec);
+                break;
+            // Received when the app updates an alarm (Snoozing included too)
+            // It's better to use SleepAsAndroidAction.START_ALARM and .STOP_ALARM where possible to have more control over the alarm.
+            // Using .UPDATE_ALARM will let Gadgetbridge know when an alarm was set but not when it was dismissed.
+            case SleepAsAndroidAction.UPDATE_ALARM:
+                long alarmTimestamp = extras.getLong("TIMESTAMP");
+
+                // Sets the alarm at a giver hour and minute
+                // Snoozing from the app will create a new alarm in the future
+                setSleepAsAndroidAlarm(alarmTimestamp);
+                break;
+            // Received when an app alarm is stopped
+            case SleepAsAndroidAction.STOP_ALARM:
+                // Manually stop an alarm
+                break;
+            // Received when an app alarm starts
+            case SleepAsAndroidAction.START_ALARM:
+                // Manually start an alarm
+                break;
+            default:
+                LOG.warn("Received unsupported " + action);
+                break;
+        }
+    }
+
+    private void setSleepAsAndroidAlarm(long alarmTimestamp) {
+
+        Calendar calendar = Calendar.getInstance();
+        calendar.setTimeInMillis(new Timestamp(alarmTimestamp).getTime());
+        Alarm alarm = AlarmUtils.createSingleShot(SleepAsAndroidSender.getAlarmSlot(), false, false, calendar);
+        ArrayList<Alarm> alarms = new ArrayList<>(1);
+        alarms.add(alarm);
+
+        GBApplication.deviceService(gbDevice).onSetAlarms(alarms);
+    }
+
+    private ScheduledExecutorService startRealtimeHeartRateMeasurement() {
+        ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor();
+        service.scheduleAtFixedRate(new Runnable() {
+            @Override
+            public void run() {
+                if (heartRateRealtimeStarted) {
+                    onEnableRealtimeHeartRateMeasurement(true);
+                }
+            }
+        }, 0, 1000, TimeUnit.MILLISECONDS);
+        return service;
+    }
+
+    private void stopRealtimeHeartRateMeasurement() {
+        if (heartRateRealtimeScheduler != null) {
+            heartRateRealtimeScheduler.shutdown();
+            heartRateRealtimeScheduler = null;
+        }
+    }
+
+    private void enableRealtimeHeartRateMeasurement(boolean enable) {
+        onEnableRealtimeHeartRateMeasurement(enable);
+        if (enable) {
+            heartRateRealtimeScheduler = startRealtimeHeartRateMeasurement();
+        }
+        else {
+            stopRealtimeHeartRateMeasurement();
+        }
+
+    }
+
+    private void stopRawSensors() {
+        if (rawSensorScheduler != null) {
+            rawSensorScheduler.shutdown();
+            rawSensorScheduler = null;
+        }
+    }
+
+    private ScheduledExecutorService startRawSensors() {
+        ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor();
+        service.scheduleAtFixedRate(new Runnable() {
+            @Override
+            public void run() {
+                if (rawSensor) {
+                    setRawSensor(true);
+                }
+            }
+        }, 0, 10000, TimeUnit.MILLISECONDS);
+        return service;
+    }
+
+    private void enableRawSensor(boolean enable) {
+        setRawSensor(enable);
+        if (enable) {
+            rawSensorScheduler = startRawSensors();
+        }
+        else {
+            stopRawSensors();
+        }
+
+    }
+
+    @Override
+    protected ZeppOsSupport setTimeFormat(final TransactionBuilder builder) {
+        final String timeFormat = getDevicePrefs().getTimeFormat();
+
+        // FIXME: This "works", but the band does not update when the setting changes, so we don't do anything
+        //noinspection ConstantValue
+        if (true) {
+            LOG.warn("setDateTime is disabled");
+            return this;
+        }
+
+        LOG.info("Setting time format to {}", timeFormat);
+
+        final byte timeFormatByte;
+        if (timeFormat.equals("24h")) {
+            timeFormatByte = 0x01;
+        } else {
+            timeFormatByte = 0x00;
+        }
+
+        configService.newSetter()
+                .setByte(TIME_FORMAT, timeFormatByte)
+                .write(builder);
+
+        return this;
+    }
+
+    @Override
+    protected ZeppOsSupport setDistanceUnit(final TransactionBuilder builder) {
+        final MiBandConst.DistanceUnit unit = HuamiCoordinator.getDistanceUnit();
+        LOG.info("Setting distance unit to {}", unit);
+
+        final byte unitByte;
+        switch (unit) {
+            case IMPERIAL:
+                unitByte = 0x01;
+                break;
+            case METRIC:
+            default:
+                unitByte = 0x00;
+                break;
+        }
+
+        configService.newSetter()
+                .setByte(TEMPERATURE_UNIT, unitByte)
+                .write(builder);
+
+        return this;
+    }
+
+    @Override
+    protected ZeppOsSupport setLanguage(final TransactionBuilder builder) {
+        final String localeString = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress())
+                .getString("language", "auto");
+
+        LOG.info("Setting device language to {}", localeString);
+
+        configService.newSetter()
+                .setByte(LANGUAGE, getLanguageId())
+                .setBoolean(LANGUAGE_FOLLOW_PHONE, localeString.equals("auto"))
+                .write(builder);
+
+        return this;
+    }
+
+    @Override
+    protected void writeToChunked(final TransactionBuilder builder,
+                                  final int type,
+                                  final byte[] data) {
+        LOG.warn("writeToChunked is not supported");
+    }
+
+    @Override
+    protected void writeToChunkedOld(final TransactionBuilder builder, final int type, final byte[] data) {
+        LOG.warn("writeToChunkedOld is not supported");
+    }
+
+    @Override
+    public void writeToChunked2021(final TransactionBuilder builder, final short endpoint, final byte[] data, final boolean encryptIgnored) {
+        // Ensure communication for all services contains the encrypted flag reported by the service, since not all
+        // watches have the same services encrypted (eg. #3308).
+        huami2021ChunkedEncoder.write(builder, endpoint, data, force2021Protocol(), mIsEncrypted.contains(endpoint));
+    }
+
+    @Override
+    public void writeToConfiguration(final TransactionBuilder builder, final byte[] data) {
+        LOG.warn("writeToConfiguration is not supported");
+    }
+
+    @Override
+    protected ZeppOsSupport requestGPSVersion(final TransactionBuilder builder) {
+        LOG.warn("Request GPS version not implemented");
+        return this;
+    }
+
+    public void requestDisplayItems(final TransactionBuilder builder) {
+        displayItemsService.requestItems(builder, ZeppOsDisplayItemsService.DISPLAY_ITEMS_MENU);
+    }
+
+    public void requestApps(final TransactionBuilder builder) {
+        appsService.requestApps(builder);
+    }
+
+    public void requestWatchfaces(final TransactionBuilder builder) {
+        watchfaceService.requestWatchfaces(builder);
+        watchfaceService.requestCurrentWatchface(builder);
+    }
+
+    protected void requestMTU(final TransactionBuilder builder) {
+        writeToChunked2021(
+                builder,
+                CHUNKED2021_ENDPOINT_CONNECTION,
+                CONNECTION_CMD_MTU_REQUEST,
+                false
+        );
+    }
+
+    @Override
+    public void phase2Initialize(final TransactionBuilder builder) {
+        LOG.info("2021 phase2Initialize...");
+        requestMTU(builder);
+        requestBatteryInfo(builder);
+
+        final GBDeviceEventUpdatePreferences evt = new GBDeviceEventUpdatePreferences()
+                .withPreference(DeviceSettingsPreferenceConst.WIFI_HOTSPOT_STATUS, null)
+                .withPreference(DeviceSettingsPreferenceConst.FTP_SERVER_ADDRESS, null)
+                .withPreference(DeviceSettingsPreferenceConst.FTP_SERVER_USERNAME, null)
+                .withPreference(DeviceSettingsPreferenceConst.FTP_SERVER_STATUS, null);
+        evaluateGBDeviceEvent(evt);
+    }
+
+    @Override
+    public void phase3Initialize(final TransactionBuilder builder) {
+        LOG.info("2021 phase3Initialize...");
+
+        // Make sure that performInitialized is not called accidentally in here
+        // (eg. by creating a new TransactionBuilder).
+        // In those cases, the device will be initialized twice, which will change the shared
+        // session key during these requests and decrypting messages will fail
+
+        // In here, we only request the list of supported services - they will all be initialized in
+        // initializeServices below
+        mSupportedServices.clear();
+        mIsEncrypted.clear();
+        servicesService.requestServices(builder);
+    }
+
+    @Override
+    @Deprecated
+    public HuamiFWHelper createFWHelper(final Uri uri, final Context context) throws IOException {
+        throw new UnsupportedOperationException("This function should not be used for Zepp OS devices");
+    }
+
+    public void addSupportedService(final short endpoint, final boolean encrypted) {
+        mSupportedServices.add(endpoint);
+        if (encrypted) {
+            mIsEncrypted.add(endpoint);
+        }
+    }
+
+    public void initializeServices() {
+        LOG.info("2021 initializeServices...");
+
+        try {
+            final TransactionBuilder builder = createTransactionBuilder("initialize services");
+
+            // At this point we got the service list from phase 3, so we know which
+            // services are supported, and whether they are encrypted or not
+
+            final ZeppOsCoordinator coordinator = getCoordinator();
+
+            // TODO move this to a service
+            setUserInfo(builder);
+
+            // TODO move this to a service
+            for (final HuamiVibrationPatternNotificationType type : coordinator.getVibrationPatternNotificationTypes(gbDevice)) {
+                // FIXME: Can we read these from the band?
+                final String typeKey = type.name().toLowerCase(Locale.ROOT);
+                setVibrationPattern(builder, HuamiConst.PREF_HUAMI_VIBRATION_PROFILE_PREFIX + typeKey);
+            }
+
+            // TODO move these to a service
+            cannedMessagesService.requestCannedMessages(builder);
+            alarmsService.requestAlarms(builder);
+
+            for (AbstractZeppOsService service : mServiceMap.values()) {
+                if (mSupportedServices.contains(service.getEndpoint())) {
+                    // Only initialize supported services
+                    service.initialize(builder);
+                }
+            }
+
+            if (coordinator.supportsBluetoothPhoneCalls(gbDevice)) {
+                phoneService.requestCapabilities(builder);
+                phoneService.requestEnabled(builder);
+            }
+
+            builder.queue(getQueue());
+        } catch (Exception e) {
+            LOG.error("failed initializing device", e);
+        }
+    }
+
+    @Nullable
+    public AbstractZeppOsService getService(final short endpoint) {
+        return mServiceMap.get(endpoint);
+    }
+
+    @Override
+    public int getActivitySampleSize() {
+        return 8;
+    }
+
+    @Override
+    public boolean force2021Protocol() {
+        return true;
+    }
+
+    @Override
+    protected ZeppOsCoordinator getCoordinator() {
+        return (ZeppOsCoordinator) gbDevice.getDeviceCoordinator();
+    }
+
+    @Override
+    protected void setRawSensor(final boolean enable) {
+        LOG.info("Set raw sensor to {}", enable);
+        rawSensor = enable;
+
+        try {
+            final TransactionBuilder builder = performInitialized("set raw sensor");
+            if (enable) {
+                builder.write(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_RAW_SENSOR_CONTROL), Huami2021Service.CMD_RAW_SENSOR_START_1);
+                builder.write(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_RAW_SENSOR_CONTROL), Huami2021Service.CMD_RAW_SENSOR_START_2);
+                builder.write(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_RAW_SENSOR_CONTROL), Huami2021Service.CMD_RAW_SENSOR_START_3);
+            } else {
+                builder.write(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_RAW_SENSOR_CONTROL), Huami2021Service.CMD_RAW_SENSOR_STOP);
+            }
+            builder.notify(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_RAW_SENSOR_DATA), enable);
+            builder.queue(getQueue());
+        } catch (final IOException e) {
+            LOG.error("Unable to set raw sensor", e);
+        }
+    }
+
+    @Override
+    protected void handleRawSensorData(final byte[] value) {
+        // The g values seem to vary between -4100 and 4100, so we scale them
+        final float scaleFactor = 4100f;
+        final float gravity = -9.81f;
+
+        final ByteBuffer buf = ByteBuffer.wrap(value).order(ByteOrder.LITTLE_ENDIAN);
+        final byte type = buf.get();
+        final int index = buf.get() & 0xff; // always incrementing, for each type
+
+        if (type == 0x00) {
+            // g-sensor x y z values, per second
+            if ((value.length - 2) % 6 != 0) {
+                LOG.warn("Raw sensor value for type 0 not divisible by 6");
+                return;
+            }
+
+            for (int i = 2; i < value.length; i += 6) {
+                final int x = (BLETypeConversions.toUint16(value, i) << 16) >> 16;
+                final int y = (BLETypeConversions.toUint16(value, i + 2) << 16) >> 16;
+                final int z = (BLETypeConversions.toUint16(value, i + 4) << 16) >> 16;
+
+                final float gx = (x * gravity) / scaleFactor;
+                final float gy = (y * gravity) / scaleFactor;
+                final float gz = (z * gravity) / scaleFactor;
+                sleepAsAndroidSender.onAccelChanged(gx, gy, gz);
+
+                LOG.info("Raw sensor g: x={} y={} z={}", gx, gy, gz);
+            }
+        } else if (type == 0x01) {
+            // TODO not sure what this is?
+            if ((value.length - 2) % 4 != 0) {
+                LOG.warn("Raw sensor value for type 1 not divisible by 4");
+                return;
+            }
+
+            for (int i = 2; i < value.length; i += 4) {
+                int val = BLETypeConversions.toUint32(value, i);
+                LOG.info("Raw sensor 1: {}", val);
+            }
+        } else if (type == 0x07) {
+            // Timestamp for the targetType, sent in intervals of ~10 seconds
+            final int targetType = buf.get() & 0xff;
+            final long tsMillis = buf.getLong();
+            LOG.debug("Raw sensor timestamp for type={} index={}: {}", targetType, index, new Date(tsMillis));
+        } else {
+            LOG.warn("Unknown raw sensor type: {}", GB.hexdump(value));
+        }
+    }
+
+    @Override
+    public boolean onCharacteristicChanged(final BluetoothGatt gatt,
+                                           final BluetoothGattCharacteristic characteristic) {
+        final UUID characteristicUUID = characteristic.getUuid();
+        if (HuamiService.UUID_CHARACTERISTIC_ZEPP_OS_FILE_TRANSFER_V3.equals(characteristicUUID)) {
+            fileTransferService.onCharacteristicChanged(characteristic.getValue());
+            return true;
+        }
+
+        return super.onCharacteristicChanged(gatt, characteristic);
+    }
+
+    @Override
+    public void handle2021Payload(final short type, final byte[] payload) {
+        if (payload == null || payload.length == 0) {
+            LOG.warn("Empty or null payload for {}", String.format("0x%04x", type));
+            return;
+        }
+
+        final AbstractZeppOsService service = mServiceMap.get(type);
+        if (service != null) {
+            service.handlePayload(payload);
+            return;
+        }
+
+        // TODO: Move these services to dedicated classes, so they get the encryption correctly
+        switch (type) {
+            case CHUNKED2021_ENDPOINT_AUTH:
+                LOG.warn("Unexpected auth payload {}", GB.hexdump(payload));
+                return;
+            case CHUNKED2021_ENDPOINT_COMPAT:
+                LOG.warn("Unexpected compat payload {}", GB.hexdump(payload));
+                return;
+            case CHUNKED2021_ENDPOINT_WEATHER:
+                handle2021Weather(payload);
+                return;
+            case CHUNKED2021_ENDPOINT_WORKOUT:
+                handle2021Workout(payload);
+                return;
+            case CHUNKED2021_ENDPOINT_FIND_DEVICE:
+                handle2021FindDevice(payload);
+                return;
+            case CHUNKED2021_ENDPOINT_HEARTRATE:
+                handle2021HeartRate(payload);
+                return;
+            case CHUNKED2021_ENDPOINT_CONNECTION:
+                handle2021Connection(payload);
+                return;
+            case CHUNKED2021_ENDPOINT_USER_INFO:
+                handle2021UserInfo(payload);
+                return;
+            case CHUNKED2021_ENDPOINT_STEPS:
+                handle2021Steps(payload);
+                return;
+            case CHUNKED2021_ENDPOINT_VIBRATION_PATTERNS:
+                handle2021VibrationPatterns(payload);
+                return;
+            case CHUNKED2021_ENDPOINT_BATTERY:
+                handle2021Battery(payload);
+                return;
+            case CHUNKED2021_ENDPOINT_SILENT_MODE:
+                handle2021SilentMode(payload);
+                return;
+            default:
+                LOG.warn("Unhandled 2021 payload {}", String.format("0x%04x", type));
+        }
+    }
+
+    protected void handle2021Workout(final byte[] payload) {
+        switch (payload[0]) {
+            case WORKOUT_CMD_APP_OPEN:
+                final ZeppOsActivityType activityType = ZeppOsActivityType.fromCode(payload[3]);
+                final boolean workoutNeedsGps = (payload[2] == 1);
+                final ActivityKind activityKind;
+
+                if (activityType == null) {
+                    LOG.warn("Unknown workout activity type {}", String.format("0x%x", payload[3]));
+                    activityKind = ActivityKind.UNKNOWN;
+                } else {
+                    activityKind = activityType.toActivityKind();
+                }
+
+                LOG.info("Workout starting on band: {}, needs gps = {}", activityType, workoutNeedsGps);
+
+                onWorkoutOpen(workoutNeedsGps, activityKind);
+                return;
+            case WORKOUT_CMD_STATUS:
+                switch (payload[1]) {
+                    case WORKOUT_STATUS_START:
+                        LOG.info("Workout Start");
+                        onWorkoutStart();
+                        break;
+                    case WORKOUT_STATUS_END:
+                        LOG.info("Workout End");
+                        onWorkoutEnd();
+                        break;
+                    default:
+                        LOG.warn("Unexpected workout status {}", String.format("0x%02x", payload[1]));
+                        break;
+                }
+                return;
+            default:
+                LOG.warn("Unexpected workout byte {}", String.format("0x%02x", payload[0]));
+        }
+    }
+
+    /**
+     * A handler to schedule the find phone event.
+     */
+    private final Handler findPhoneHandler = new Handler();
+    private boolean findPhoneStarted;
+
+    protected void handle2021FindDevice(final byte[] payload) {
+        final GBDeviceEventFindPhone findPhoneEvent = new GBDeviceEventFindPhone();
+
+        switch (payload[0]) {
+            case FIND_BAND_ACK:
+                LOG.info("Band acknowledged find band command");
+                return;
+            case FIND_PHONE_START:
+                LOG.info("Find Phone Start");
+                acknowledgeFindPhone(); // FIXME: Premature, but the band will only send the mode after we ack
+
+                // Delay the find phone start, because we might get the FIND_PHONE_MODE
+                findPhoneHandler.postDelayed(() -> {
+                    findPhoneEvent.event = GBDeviceEventFindPhone.Event.START;
+                    evaluateGBDeviceEvent(findPhoneEvent);
+                }, 1500);
+
+                break;
+            case FIND_BAND_STOP_FROM_BAND:
+                LOG.info("Find Band Stop from Band");
+                break;
+            case FIND_PHONE_STOP_FROM_BAND:
+                LOG.info("Find Phone Stop");
+                findPhoneEvent.event = GBDeviceEventFindPhone.Event.STOP;
+                evaluateGBDeviceEvent(findPhoneEvent);
+                break;
+            case FIND_PHONE_MODE:
+                findPhoneHandler.removeCallbacksAndMessages(null);
+
+                final int mode = payload[1] & 0xff; // 0 to only vibrate, 1 to ring
+                LOG.info("Find Phone Mode: {}", mode);
+                if (findPhoneStarted) {
+                    // Already started, just change the mode
+                    findPhoneEvent.event = mode == 1 ? GBDeviceEventFindPhone.Event.RING : GBDeviceEventFindPhone.Event.VIBRATE;
+                } else {
+                    findPhoneEvent.event = mode == 1 ? GBDeviceEventFindPhone.Event.START : GBDeviceEventFindPhone.Event.START_VIBRATE;
+                }
+                evaluateGBDeviceEvent(findPhoneEvent);
+                break;
+            default:
+                LOG.warn("Unexpected find phone byte {}", String.format("0x%02x", payload[0]));
+        }
+    }
+
+    protected void handle2021HeartRate(final byte[] payload) {
+        switch (payload[0]) {
+            case HEART_RATE_CMD_REALTIME_ACK:
+                // what does the status mean? Seems to be 0 on success
+                LOG.info("Band acknowledged heart rate command, status = {}", payload[1]);
+                return;
+            case HEART_RATE_CMD_SLEEP:
+                switch (payload[1]) {
+                    case HEART_RATE_FALL_ASLEEP:
+                        LOG.info("Fell asleep");
+                        processDeviceEvent(HuamiDeviceEvent.FELL_ASLEEP);
+                        break;
+                    case HEART_RATE_WAKE_UP:
+                        LOG.info("Woke up");
+                        processDeviceEvent(HuamiDeviceEvent.WOKE_UP);
+                        break;
+                    default:
+                        LOG.warn("Unexpected sleep byte {}", String.format("0x%02x", payload[1]));
+                        break;
+                }
+                return;
+            default:
+                LOG.warn("Unexpected heart rate byte {}", String.format("0x%02x", payload[0]));
+        }
+    }
+
+    protected void handle2021Weather(final byte[] payload) {
+        //noinspection SwitchStatementWithTooFewBranches
+        switch (payload[0]) {
+            case WEATHER_CMD_DEFAULT_LOCATION_ACK:
+                LOG.info("Weather default location ACK, status = {}", payload[1]);
+                return;
+            default:
+                LOG.warn("Unexpected weather byte {}", String.format("0x%02x", payload[0]));
+        }
+    }
+
+    protected void handle2021Connection(final byte[] payload) {
+        switch (payload[0]) {
+            case CONNECTION_CMD_MTU_RESPONSE:
+                final int mtu = BLETypeConversions.toUint16(payload, 1) + 3;
+                LOG.info("Device announced MTU change: {}", mtu);
+                setMtu(mtu);
+                return;
+            case CONNECTION_CMD_UNKNOWN_3:
+                // Some ping? Band sometimes sends 0x03, phone replies with 0x04
+                LOG.info("Got unknown 3, replying with unknown 4");
+                writeToChunked2021("respond connection unknown 4", CHUNKED2021_ENDPOINT_CONNECTION, CONNECTION_CMD_UNKNOWN_4, false);
+                return;
+        }
+
+        LOG.warn("Unexpected connection payload byte {}", String.format("0x%02x", payload[0]));
+    }
+
+    protected void handle2021UserInfo(final byte[] payload) {
+        //noinspection SwitchStatementWithTooFewBranches
+        switch (payload[0]) {
+            case USER_INFO_CMD_SET_ACK:
+                LOG.info("Got user info set ack, status = {}", payload[1]);
+                return;
+        }
+
+        LOG.warn("Unexpected user info payload byte {}", String.format("0x%02x", payload[0]));
+    }
+
+    protected void handle2021Steps(final byte[] payload) {
+        switch (payload[0]) {
+            case STEPS_CMD_REPLY:
+                LOG.info("Got steps reply, status = {}", payload[1]);
+                if (payload.length != 15) {
+                    LOG.error("Unexpected steps reply payload length {}", payload.length);
+                    return;
+                }
+                handleRealtimeSteps(subarray(payload, 2, 15));
+                return;
+            case STEPS_CMD_ENABLE_REALTIME_ACK:
+                LOG.info("Band acknowledged realtime steps, status = {}, enabled = {}", payload[1], payload[2]);
+                return;
+            case STEPS_CMD_REALTIME_NOTIFICATION:
+                LOG.info("Got steps notification");
+                if (payload.length != 14) {
+                    LOG.error("Unexpected realtime notification payload length {}", payload.length);
+                    return;
+                }
+                handleRealtimeSteps(subarray(payload, 1, 14));
+                return;
+            default:
+                LOG.warn("Unexpected steps payload byte {}", String.format("0x%02x", payload[0]));
+        }
+    }
+
+    protected void handle2021VibrationPatterns(final byte[] payload) {
+        //noinspection SwitchStatementWithTooFewBranches
+        switch (payload[0]) {
+            case VIBRATION_PATTERN_ACK:
+                LOG.info("Vibration Patterns ACK, status = {}", payload[1]);
+                return;
+            default:
+                LOG.warn("Unexpected Vibration Patterns payload byte {}", String.format("0x%02x", payload[0]));
+        }
+    }
+
+    protected void handle2021Battery(final byte[] payload) {
+        if (payload[0] != BATTERY_REPLY) {
+            LOG.warn("Unexpected battery payload byte {}", String.format("0x%02x", payload[0]));
+            return;
+        }
+
+        if (payload.length != 21) {
+            LOG.warn("Unexpected battery payload length: {}", payload.length);
+        }
+
+        final HuamiBatteryInfo batteryInfo = new HuamiBatteryInfo(subarray(payload, 1, payload.length));
+        handleGBDeviceEvent(batteryInfo.toDeviceEvent());
+    }
+
+    protected void handle2021SilentMode(final byte[] payload) {
+        switch (payload[0]) {
+            case SILENT_MODE_CMD_NOTIFY_BAND_ACK:
+                LOG.info("Band acknowledged current phone silent mode, status = {}", payload[1]);
+                return;
+            case SILENT_MODE_CMD_QUERY:
+                LOG.info("Got silent mode query from band");
+                sendPhoneSilentMode(SilentMode.isPhoneInSilenceMode(getDevice().getAddress()));
+                return;
+            case SILENT_MODE_CMD_SET:
+                LOG.info("Band setting silent mode = {}", payload[1]);
+                final boolean silentModeEnabled = (payload[1] == 1);
+                ackSilentModeSet();
+                sendPhoneSilentMode(silentModeEnabled);
+                evaluateGBDeviceEvent(new GBDeviceEventSilentMode(silentModeEnabled));
+                return;
+            default:
+                LOG.warn("Unexpected silent mode payload byte {}", String.format("0x%02x", payload[0]));
+        }
+    }
+
+    private void ackSilentModeSet() {
+        writeToChunked2021(
+                "ack silent mode set",
+                CHUNKED2021_ENDPOINT_SILENT_MODE,
+                new byte[]{SILENT_MODE_CMD_ACK, 0x01},
+                false
+        );
+    }
+
+    private void sendPhoneSilentMode(final boolean enabled) {
+        writeToChunked2021(
+                "send phone silent mode to band",
+                CHUNKED2021_ENDPOINT_SILENT_MODE,
+                new byte[]{SILENT_MODE_CMD_NOTIFY_BAND, bool(enabled)},
+                false
+        );
+    }
+
+    @Override
+    public void onFileUploadFinish(final boolean success) {
+        LOG.warn("Unexpected file upload finish: {}", success);
+    }
+
+    @Override
+    public void onFileUploadProgress(final int progress) {
+        LOG.warn("Unexpected file upload progress: {}", progress);
+    }
+
+    @Override
+    public void onFileDownloadFinish(final String url, final String filename, final byte[] data) {
+        LOG.info("File received: url={} filename={} length={}", url, filename, data.length);
+
+        if (filename.startsWith("screenshot-")) {
+            GBDeviceEventScreenshot gbDeviceEventScreenshot = new GBDeviceEventScreenshot(data);
+            evaluateGBDeviceEvent(gbDeviceEventScreenshot);
+            return;
+        }
+
+        final String fileDownloadsDir = "zepp-os-received-files";
+        final File targetFile;
+        try {
+            final String validFilename = FileUtils.makeValidFileName(filename);
+            final File targetFolder = new File(FileUtils.getExternalFilesDir(), fileDownloadsDir);
+            //noinspection ResultOfMethodCallIgnored
+            targetFolder.mkdirs();
+            targetFile = new File(targetFolder, validFilename);
+        } catch (final IOException e) {
+            LOG.error("Failed create folder to save file", e);
+            return;
+        }
+
+        try (FileOutputStream outputStream = new FileOutputStream(targetFile)) {
+            final File targetFolder = new File(FileUtils.getExternalFilesDir(), fileDownloadsDir);
+            //noinspection ResultOfMethodCallIgnored
+            targetFolder.mkdirs();
+            outputStream.write(data);
+        } catch (final IOException e) {
+            LOG.error("Failed to save file bytes", e);
+        }
+    }
+
+    private byte bool(final boolean b) {
+        return (byte) (b ? 1 : 0);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/ZeppOsWeather.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/ZeppOsWeather.java
new file mode 100644
index 0000000..b81f91e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/ZeppOsWeather.java
@@ -0,0 +1,813 @@
+/*  Copyright (C) 2022-2024 Andreas Shimokawa, Arjan Schrijver, Jos茅
+    Rebelo, rany
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos;
+
+import android.location.Location;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonPrimitive;
+import com.google.gson.JsonSerializationContext;
+import com.google.gson.JsonSerializer;
+
+import net.e175.klaus.solarpositioning.DeltaT;
+import net.e175.klaus.solarpositioning.SPA;
+import net.e175.klaus.solarpositioning.SunriseTransitSet;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.lang.reflect.Type;
+import java.text.SimpleDateFormat;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+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.Map;
+import java.util.TimeZone;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiWeatherConditions;
+import nodomain.freeyourgadget.gadgetbridge.model.Weather;
+import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.webview.CurrentPosition;
+
+/**
+ * The weather models that the bands expect as an http response to weather requests. Base URL usually
+ * is https://api-mifit.huami.com.
+ */
+public class ZeppOsWeather {
+    private static final Logger LOG = LoggerFactory.getLogger(ZeppOsWeather.class);
+
+    private static final Gson GSON = new GsonBuilder()
+            .serializeNulls()
+            .setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ") // for pubTimes
+            .registerTypeAdapter(LocalDate.class, new LocalDateSerializer())
+            // These serializers keep the fields in order. Not sure if this is needed, but the debug
+            // logs show some errors when deserializing the json payloads.
+            .registerTypeAdapter(ForecastResponse.class, new ForecastResponse.Serializer())
+            .registerTypeAdapter(MoonRiseSet.class, new MoonRiseSet.Serializer())
+            .registerTypeAdapter(Range.class, new Range.Serializer())
+            .registerTypeAdapter(IndexResponse.class, new IndexResponse.Serializer())
+            .registerTypeAdapter(IndexEntry.class, new IndexEntry.Serializer())
+            .registerTypeAdapter(CurrentResponse.class, new CurrentResponse.Serializer())
+            .registerTypeAdapter(CurrentWeatherModel.class, new CurrentWeatherModel.Serializer())
+            .registerTypeAdapter(AqiModel.class, new AqiModel.Serializer())
+            .registerTypeAdapter(UnitValue.class, new UnitValue.Serializer())
+            .registerTypeAdapter(HourlyResponse.class, new HourlyResponse.Serializer())
+            .registerTypeAdapter(AlertsResponse.class, new AlertsResponse.Serializer())
+            .registerTypeAdapter(TideResponse.class, new TideResponse.Serializer())
+            .registerTypeAdapter(TideDataEntry.class, new TideDataEntry.Serializer())
+            .registerTypeAdapter(TideTableEntry.class, new TideTableEntry.Serializer())
+            .registerTypeAdapter(TideHourlyEntry.class, new TideHourlyEntry.Serializer())
+            .create();
+
+    public static Response handleHttpRequest(final String path, final Map<String, String> query) {
+        final WeatherSpec weatherSpec = Weather.getInstance().getWeatherSpec();
+
+        if (weatherSpec == null) {
+            LOG.error("No weather in weather instance");
+            return new ZeppOsWeather.ErrorResponse(404, -2001, "Not found");
+        }
+
+        switch (path) {
+            case "/weather/v2/forecast":
+                final int forecastDays = getQueryNum(query, "days", 10);
+                return new ForecastResponse(weatherSpec, forecastDays);
+            case "/weather/index":
+                final int indexDays = getQueryNum(query, "days", 3);
+                return new IndexResponse(weatherSpec, indexDays);
+            case "/weather/current":
+                return new CurrentResponse(weatherSpec);
+            case "/weather/forecast/hourly":
+                final int hours = getQueryNum(query, "hours", 72);
+                return new HourlyResponse(weatherSpec, hours);
+            case "/weather/alerts":
+                return new AlertsResponse(weatherSpec);
+            case "/weather/tide":
+                final int tideDays = getQueryNum(query, "days", 10);
+                return new TideResponse(weatherSpec, tideDays);
+        }
+
+        LOG.error("Unknown weather path {}", path);
+        return new ZeppOsWeather.ErrorResponse(404, -2001, "Not found");
+    }
+
+    private static int getQueryNum(final Map<String, String> query, final String key, final int defaultValue) {
+        final String daysStr = query.get(key);
+        if (daysStr != null) {
+            return Integer.parseInt(daysStr);
+        } else {
+            return defaultValue;
+        }
+    }
+
+    private static class RawJsonStringResponse extends Response {
+        private final String content;
+
+        public RawJsonStringResponse(final String content) {
+            this.content = content;
+        }
+
+        public String toJson() {
+            return content;
+        }
+    }
+
+    public static class ErrorResponse extends Response {
+        private final int httpStatusCode;
+        private final int errorCode;
+        private final String message;
+
+        public ErrorResponse(final int httpStatusCode, final int errorCode, final String message) {
+            this.httpStatusCode = httpStatusCode;
+            this.errorCode = errorCode;
+            this.message = message;
+        }
+
+        @Override
+        public int getHttpStatusCode() {
+            return httpStatusCode;
+        }
+
+        public int getErrorCode() {
+            return errorCode;
+        }
+
+        public String getMessage() {
+            return message;
+        }
+    }
+
+    public static abstract class Response {
+        public int getHttpStatusCode() {
+            return 200;
+        }
+
+        public String toJson() {
+            return GSON.toJson(this);
+        }
+    }
+
+    // /weather/v2/forecast
+    //
+    // locale=zh_CN
+    // deviceSource=11
+    // days=10
+    // isGlobal=true
+    // locationKey=00.000,-0.000,xiaomi_accu:000000
+    public static class ForecastResponse extends Response {
+        public Date pubTime;
+        public List<String> humidity = new ArrayList<>(); // int
+        public List<Range> temperature = new ArrayList<>(); // ints
+        public List<Range> weather = new ArrayList<>();
+        public List<Range> windDirection = new ArrayList<>();
+        public List<Range> sunRiseSet = new ArrayList<>();
+        public List<Range> windSpeed = new ArrayList<>();
+        public MoonRiseSet moonRiseSet = new MoonRiseSet();
+        public List<Object> airQualities = new ArrayList<>();
+
+        public ForecastResponse(final WeatherSpec weatherSpec, final int days) {
+            final int actualDays = Math.min(weatherSpec.forecasts.size(), days - 1); // leave one slot for the first day
+
+            pubTime = new Date(weatherSpec.timestamp * 1000L);
+
+            final Calendar calendar = GregorianCalendar.getInstance();
+            calendar.setTime(pubTime);
+
+            final Location lastKnownLocation = new CurrentPosition().getLastKnownLocation();
+            final GregorianCalendar sunriseDate = new GregorianCalendar();
+            sunriseDate.setTime(calendar.getTime());
+
+            // First one is for the current day
+            temperature.add(new Range(weatherSpec.todayMinTemp - 273, weatherSpec.todayMaxTemp - 273));
+            final String currentWeatherCode = String.valueOf(mapToZeppOsWeatherCode(weatherSpec.currentConditionCode));
+            weather.add(new Range(currentWeatherCode, currentWeatherCode));
+            if (weatherSpec.sunRise != 0 && weatherSpec.sunSet != 0) {
+                sunRiseSet.add(getSunriseSunset(new Date(weatherSpec.sunRise * 1000L), new Date(weatherSpec.sunSet * 1000L)));
+            } else if (weatherSpec.getLocation() != null) {
+                sunRiseSet.add(getSunriseSunset(sunriseDate, weatherSpec.getLocation()));
+            } else {
+                sunRiseSet.add(getSunriseSunset(sunriseDate, lastKnownLocation));
+            }
+            sunriseDate.add(Calendar.DAY_OF_MONTH, 1);
+            windDirection.add(new Range(weatherSpec.windDirection, weatherSpec.windDirection));
+            windSpeed.add(new Range(Math.round(weatherSpec.windSpeed), Math.round(weatherSpec.windSpeed)));
+
+            moonRiseSet.add(weatherSpec.moonRise, weatherSpec.moonSet, weatherSpec.moonPhase);
+
+            for (int i = 0; i < actualDays; i++) {
+                final WeatherSpec.Daily forecast = weatherSpec.forecasts.get(i);
+                temperature.add(new Range(forecast.minTemp - 273, forecast.maxTemp - 273));
+                final String weatherCode = String.valueOf(mapToZeppOsWeatherCode(forecast.conditionCode));
+                weather.add(new Range(weatherCode, weatherCode));
+
+                if (forecast.sunRise != 0 && forecast.sunSet != 0) {
+                    sunRiseSet.add(getSunriseSunset(new Date(forecast.sunRise * 1000L), new Date(forecast.sunSet * 1000L)));
+                } else {
+                    sunRiseSet.add(getSunriseSunset(sunriseDate, lastKnownLocation));
+                }
+                sunriseDate.add(Calendar.DAY_OF_MONTH, 1);
+
+                if (forecast.windDirection != -1) {
+                    windDirection.add(new Range(forecast.windDirection, forecast.windDirection));
+                } else {
+                    windDirection.add(new Range(0, 0));
+                }
+
+                if (forecast.windSpeed != -1) {
+                    windSpeed.add(new Range(Math.round(forecast.windSpeed), Math.round(forecast.windSpeed)));
+                } else {
+                    windSpeed.add(new Range(0, 0));
+                }
+
+                moonRiseSet.add(forecast.moonRise, forecast.moonSet, forecast.moonPhase);
+            }
+        }
+
+        private Range getSunriseSunset(final GregorianCalendar date, final Location location) {
+            final SunriseTransitSet sunriseTransitSet = SPA.calculateSunriseTransitSet(
+                    date.toZonedDateTime(),
+                    location.getLatitude(),
+                    location.getLongitude(),
+                    DeltaT.estimate(date.toZonedDateTime().toLocalDate())
+            );
+
+            if (sunriseTransitSet.getSunrise() != null && sunriseTransitSet.getSunset() != null) {
+                return getSunriseSunset(
+                        Date.from(sunriseTransitSet.getSunrise().toInstant()),
+                        Date.from(sunriseTransitSet.getSunset().toInstant())
+                );
+            }
+
+            return getSunriseSunset(new Date(), new Date());
+        }
+
+        private Range getSunriseSunset(final Date sunRise, final Date sunSet) {
+            final SimpleDateFormat sunRiseSetSdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.ROOT);
+            sunRiseSetSdf.setTimeZone(TimeZone.getDefault());
+
+            final String from = sunRiseSetSdf.format(sunRise);
+            final String to = sunRiseSetSdf.format(sunSet);
+
+            return new Range(from, to);
+        }
+
+        public static class Serializer implements JsonSerializer<ForecastResponse> {
+            @Override
+            public JsonElement serialize(final ForecastResponse obj, final Type type, final JsonSerializationContext context) {
+                final JsonObject object = new JsonObject();
+                object.add("pubTime", context.serialize(obj.pubTime));
+                object.add("humidity", context.serialize(obj.humidity));
+                object.add("temperature", context.serialize(obj.temperature));
+                object.add("weather", context.serialize(obj.weather));
+                object.add("windDirection", context.serialize(obj.windDirection));
+                object.add("sunRiseSet", context.serialize(obj.sunRiseSet));
+                object.add("windSpeed", context.serialize(obj.windSpeed));
+                object.add("moonRiseSet", context.serialize(obj.moonRiseSet));
+                object.add("airQualities", context.serialize(obj.airQualities));
+                return object;
+            }
+        }
+    }
+
+    private static class MoonRiseSet {
+        public List<String> moonPhaseValue = new ArrayList<>(); // numbers? 20 21 23...
+        public List<Range> moonRise = new ArrayList<>(); // yyyy-MM-dd HH:mm:ss
+
+        public static class Serializer implements JsonSerializer<MoonRiseSet> {
+            @Override
+            public JsonElement serialize(final MoonRiseSet obj, final Type type, final JsonSerializationContext context) {
+                final JsonObject object = new JsonObject();
+                object.add("moonPhaseValue", context.serialize(obj.moonPhaseValue));
+                object.add("moonRise", context.serialize(obj.moonRise));
+                return object;
+            }
+        }
+
+        public void add(final int rise, final int set, final int phase) {
+            moonPhaseValue.add(String.valueOf(phase));
+
+            final SimpleDateFormat moonRiseSetSdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.ROOT);
+            moonRiseSetSdf.setTimeZone(TimeZone.getDefault());
+
+            final String from = moonRiseSetSdf.format(new Date(rise * 1000L));
+            final String to = moonRiseSetSdf.format(new Date(set * 1000L));
+
+            moonRise.add(new Range(from, to));
+        }
+    }
+
+    private static class Range {
+        public String from;
+        public String to;
+
+        public Range(final String from, final String to) {
+            this.from = from;
+            this.to = to;
+        }
+
+        public Range(final int from, final int to) {
+            this.from = String.valueOf(from);
+            this.to = String.valueOf(to);
+        }
+
+        public static class Serializer implements JsonSerializer<Range> {
+            @Override
+            public JsonElement serialize(final Range obj, final Type type, final JsonSerializationContext context) {
+                final JsonObject object = new JsonObject();
+                object.add("from", context.serialize(obj.from));
+                object.add("to", context.serialize(obj.to));
+                return object;
+            }
+        }
+    }
+
+    // /weather/index
+    //
+    // locale=zh_CN
+    // deviceSource=11
+    // days=3
+    // isGlobal=true
+    // locationKey=00.000,-0.000,xiaomi_accu:000000
+    public static class IndexResponse extends Response {
+        public Date pubTime;
+        public List<IndexEntry> dataList = new ArrayList<>();
+
+        public IndexResponse(final WeatherSpec weatherSpec, final int days) {
+            pubTime = new Date(weatherSpec.timestamp * 1000L);
+        }
+
+        public static class Serializer implements JsonSerializer<IndexResponse> {
+            @Override
+            public JsonElement serialize(final IndexResponse obj, final Type type, final JsonSerializationContext context) {
+                final JsonObject object = new JsonObject();
+                object.add("pubTime", context.serialize(obj.pubTime));
+                object.add("dataList", context.serialize(obj.dataList));
+                return object;
+            }
+        }
+    }
+
+    private static class IndexEntry {
+        public LocalDate date;
+        public String osi; // int
+        public String uvi; // int
+        public Object pai;
+        public String cwi; // int
+        public String fi; // int
+
+        public static class Serializer implements JsonSerializer<IndexEntry> {
+            @Override
+            public JsonElement serialize(final IndexEntry obj, final Type type, final JsonSerializationContext context) {
+                final JsonObject object = new JsonObject();
+                object.add("date", context.serialize(obj.date));
+                object.add("osi", context.serialize(obj.osi));
+                object.add("uvi", context.serialize(obj.uvi));
+                object.add("pai", context.serialize(obj.pai));
+                object.add("cwi", context.serialize(obj.cwi));
+                object.add("fi", context.serialize(obj.fi));
+                return object;
+            }
+        }
+    }
+
+    // /weather/current
+    //
+    // locale=zh_CN
+    // deviceSource=11
+    // isGlobal=true
+    // locationKey=00.000,-0.000,xiaomi_accu:000000
+    public static class CurrentResponse extends Response {
+        public CurrentWeatherModel currentWeatherModel;
+        public AqiModel aqiModel;
+
+        public CurrentResponse(final WeatherSpec weatherSpec) {
+            this.currentWeatherModel = new CurrentWeatherModel(weatherSpec);
+            this.aqiModel = new AqiModel(weatherSpec);
+        }
+
+        public static class Serializer implements JsonSerializer<CurrentResponse> {
+            @Override
+            public JsonElement serialize(final CurrentResponse obj, final Type type, final JsonSerializationContext context) {
+                final JsonObject object = new JsonObject();
+                object.add("currentWeatherModel", context.serialize(obj.currentWeatherModel));
+                object.add("aqiModel", context.serialize(obj.aqiModel));
+                return object;
+            }
+        }
+    }
+
+    private static class CurrentWeatherModel {
+        public UnitValue humidity;
+        public UnitValue pressure;
+        public Date pubTime;
+        public UnitValue temperature;
+        public String uvIndex;
+        public UnitValue visibility;
+        public String weather;
+        public Wind wind;
+
+        public CurrentWeatherModel(final WeatherSpec weatherSpec) {
+            humidity = new UnitValue(Unit.PERCENTAGE, weatherSpec.currentHumidity);
+            pressure = new UnitValue(Unit.PRESSURE_MB, Math.round(weatherSpec.pressure));
+            pubTime = new Date(weatherSpec.timestamp * 1000L);
+            temperature = new UnitValue(Unit.TEMPERATURE_C, weatherSpec.currentTemp - 273);
+            uvIndex = String.valueOf(Math.round(weatherSpec.uvIndex));
+            visibility = new UnitValue(Unit.KM, Math.round(weatherSpec.visibility / 1000));
+            weather = String.valueOf(mapToZeppOsWeatherCode(weatherSpec.currentConditionCode));
+            wind = new Wind(weatherSpec.windDirection, Math.round(weatherSpec.windSpeed));
+        }
+
+        public static class Serializer implements JsonSerializer<CurrentWeatherModel> {
+            @Override
+            public JsonElement serialize(final CurrentWeatherModel obj, final Type type, final JsonSerializationContext context) {
+                final JsonObject object = new JsonObject();
+                object.add("humidity", context.serialize(obj.humidity));
+                object.add("pressure", context.serialize(obj.pressure));
+                object.add("pubTime", context.serialize(obj.pubTime));
+                object.add("temperature", context.serialize(obj.temperature));
+                object.add("uvIndex", context.serialize(obj.uvIndex));
+                object.add("visibility", context.serialize(obj.visibility));
+                object.add("weather", context.serialize(obj.weather));
+                object.add("wind", context.serialize(obj.wind));
+                return object;
+            }
+        }
+    }
+
+    private static class AqiModel {
+        public String aqi; // int
+        public String co; // float
+        public String no2; // float
+        public String o3; // float
+        public String pm10; // int
+        public String pm25; // int
+        public Date pubTime; // 2023-05-14T12:00:00-0400
+        public String so2; // float
+
+        public AqiModel(final WeatherSpec weatherSpec) {
+            if (weatherSpec.airQuality == null) {
+                return;
+            }
+
+            this.aqi = String.valueOf(weatherSpec.airQuality.aqi);
+            this.co = String.format(Locale.ROOT, "%.1f", (float) weatherSpec.airQuality.coAqi);
+            this.no2 = String.format(Locale.ROOT, "%.1f", (float) weatherSpec.airQuality.no2Aqi);
+            this.o3 = String.format(Locale.ROOT, "%.1f", (float) weatherSpec.airQuality.o3Aqi);
+            this.pm10 = String.valueOf(Math.round((float) weatherSpec.airQuality.pm10Aqi));
+            this.pm25 = String.valueOf(Math.round((float) weatherSpec.airQuality.pm25Aqi));
+            this.pubTime = new Date(weatherSpec.timestamp * 1000L);
+            this.so2 = String.format(Locale.ROOT, "%.1f", (float) weatherSpec.airQuality.so2Aqi);
+        }
+
+        public static class Serializer implements JsonSerializer<AqiModel> {
+            @Override
+            public JsonElement serialize(final AqiModel obj, final Type type, final JsonSerializationContext context) {
+                final JsonObject object = new JsonObject();
+                object.add("aqi", context.serialize(obj.aqi));
+                object.add("co", context.serialize(obj.co));
+                object.add("no2", context.serialize(obj.no2));
+                object.add("o3", context.serialize(obj.o3));
+                object.add("pm10", context.serialize(obj.pm10));
+                object.add("pm25", context.serialize(obj.pm25));
+                object.add("pubTime", context.serialize(obj.pubTime));
+                object.add("so2", context.serialize(obj.so2));
+                return object;
+            }
+        }
+    }
+
+    // /weather/tide
+    //
+    // locale=en_US
+    // deviceSource=7930113
+    // days=10
+    // isGlobal=true
+    // latitude=00.000
+    // longitude=-00.000
+    private static class TideResponse extends Response {
+        public Date pubTime;
+        public String poiName; // poi tide station name
+        public String poiKey; // lat,lon,POI_ID
+        public List<TideDataEntry> tideData = new ArrayList<>();
+
+        public TideResponse(final WeatherSpec weatherSpec, int tideDays) {
+            pubTime = new Date(weatherSpec.timestamp * 1000L);
+
+            // Fill all entries, even if without data
+            final Calendar pubTimeDate = Calendar.getInstance();
+            pubTimeDate.setTime(pubTime);
+            LocalDate tideDate = LocalDate.of(pubTimeDate.get(Calendar.YEAR), pubTimeDate.get(Calendar.MONTH) + 1, pubTimeDate.get(Calendar.DAY_OF_MONTH));
+            for (int i = 0; i < tideDays; i++, tideDate = tideDate.plusDays(1)) {
+                final TideDataEntry tideDataEntry = new TideDataEntry();
+                tideDataEntry.date = tideDate;
+                tideData.add(tideDataEntry);
+            }
+        }
+
+        public static class Serializer implements JsonSerializer<TideResponse> {
+            @Override
+            public JsonElement serialize(final TideResponse obj, final Type type, final JsonSerializationContext context) {
+                final JsonObject object = new JsonObject();
+                object.add("pubTime", context.serialize(obj.pubTime));
+                object.add("poiName", context.serialize(obj.poiName));
+                object.add("poiKey", context.serialize(obj.poiKey));
+                object.add("tideData", context.serialize(obj.tideData));
+                return object;
+            }
+        }
+    }
+
+    private static class TideDataEntry {
+        public LocalDate date;
+        public List<TideTableEntry> tideTable = new ArrayList<>();
+        public List<TideHourlyEntry> tideHourly = new ArrayList<>();
+
+        public static class Serializer implements JsonSerializer<TideDataEntry> {
+            @Override
+            public JsonElement serialize(final TideDataEntry obj, final Type type, final JsonSerializationContext context) {
+                final JsonObject object = new JsonObject();
+                object.add("date", context.serialize(obj.date));
+                object.add("tideTable", context.serialize(obj.tideTable));
+                object.add("tideHourly", context.serialize(obj.tideHourly));
+                return object;
+            }
+        }
+    }
+
+    private static class TideTableEntry {
+        public Date fxTime; // pubTime format
+        public String height; // float, x.xx
+        public String type; // H / L
+
+        public static class Serializer implements JsonSerializer<TideTableEntry> {
+            @Override
+            public JsonElement serialize(final TideTableEntry obj, final Type type, final JsonSerializationContext context) {
+                final JsonObject object = new JsonObject();
+                object.add("fxTime", context.serialize(obj.fxTime));
+                object.add("height", context.serialize(obj.height));
+                object.add("type", context.serialize(obj.type));
+                return object;
+            }
+        }
+    }
+
+    private static class TideHourlyEntry {
+        public Date fxTime; // pubTime format
+        public String height; // float, x.xx
+
+        public static class Serializer implements JsonSerializer<TideHourlyEntry> {
+            @Override
+            public JsonElement serialize(final TideHourlyEntry obj, final Type type, final JsonSerializationContext context) {
+                final JsonObject object = new JsonObject();
+                object.add("fxTime", context.serialize(obj.fxTime));
+                object.add("height", context.serialize(obj.height));
+                return object;
+            }
+        }
+    }
+
+    private enum Unit {
+        PRESSURE_MB("mb"),
+        PERCENTAGE("%"),
+        TEMPERATURE_C("鈩�"), // e2 84 83 in UTF-8
+        WIND_DEGREES("掳"), // c2 b0 in UTF-8
+        KM("km"),
+        KPH("km/h"),
+        ;
+
+        private final String value;
+
+        Unit(final String value) {
+            this.value = value;
+        }
+
+        public String getValue() {
+            return value;
+        }
+    }
+
+    private static class UnitValue {
+        public String unit;
+        public String value;
+
+        public UnitValue(final Unit unit, final String value) {
+            this.unit = unit.getValue();
+            this.value = value;
+        }
+
+        public UnitValue(final Unit unit, final int value) {
+            this.unit = unit.getValue();
+            this.value = String.valueOf(value);
+        }
+
+        public static class Serializer implements JsonSerializer<UnitValue> {
+            @Override
+            public JsonElement serialize(final UnitValue obj, final Type type, final JsonSerializationContext context) {
+                final JsonObject object = new JsonObject();
+                object.add("unit", context.serialize(obj.unit));
+                object.add("value", context.serialize(obj.value));
+                return object;
+            }
+        }
+    }
+
+    private static class Wind {
+        public UnitValue direction;
+        public UnitValue speed;
+
+        public Wind(final int direction, final int speed) {
+            this.direction = new UnitValue(Unit.WIND_DEGREES, direction);
+            this.speed = new UnitValue(Unit.KPH, Math.round(speed));
+        }
+    }
+
+    // /weather/forecast/hourly
+    //
+    // locale=zh_CN
+    // deviceSource=11
+    // hourly=72
+    // isGlobal=true
+    // locationKey=00.000,-0.000,xiaomi_accu:000000
+    public static class HourlyResponse extends Response {
+        public Date pubTime;
+        // One entry in each list per hour
+        public List<String> weather = new ArrayList<>();
+        public List<String> temperature = new ArrayList<>();
+        public List<String> humidity = new ArrayList<>();
+        public List<Date> fxTime = new ArrayList<>(); // pubTime format
+        public List<String> windDirection = new ArrayList<>();
+        public List<String> windSpeed = new ArrayList<>();
+        public List<String> windScale = new ArrayList<>(); // each element in the form of 1-2
+
+        public HourlyResponse(final WeatherSpec weatherSpec, final int hours) {
+            pubTime = new Date(weatherSpec.timestamp * 1000L);
+
+            if (weatherSpec.hourly == null || weatherSpec.hourly.isEmpty()) {
+                // We don't have hourly data, but some devices refuse to open the weather app without it
+
+                final Calendar fxTimeCalendar = Calendar.getInstance();
+                fxTimeCalendar.setTime(pubTime);
+                fxTimeCalendar.set(Calendar.MINUTE, 0);
+                fxTimeCalendar.set(Calendar.SECOND, 0);
+                fxTimeCalendar.set(Calendar.MILLISECOND, 0);
+                fxTimeCalendar.add(Calendar.HOUR, 1);
+
+                for (int i = 0; i < hours; i++) {
+                    weather.add(String.valueOf(mapToZeppOsWeatherCode(weatherSpec.currentConditionCode)));
+                    temperature.add(String.valueOf(weatherSpec.currentTemp - 273));
+                    humidity.add(String.valueOf(weatherSpec.currentHumidity));
+                    fxTime.add(fxTimeCalendar.getTime());
+                    windDirection.add(String.valueOf(weatherSpec.windDirection));
+                    windSpeed.add(String.valueOf(Math.round(weatherSpec.windSpeed)));
+                    windScale.add("1-2");
+
+                    fxTimeCalendar.add(Calendar.HOUR, 1);
+                }
+            } else {
+                int i = 0;
+
+                for (final WeatherSpec.Hourly hourly : weatherSpec.hourly) {
+                    if (hourly.timestamp < weatherSpec.timestamp) {
+                        continue;
+                    }
+
+                    weather.add(String.valueOf(mapToZeppOsWeatherCode(hourly.conditionCode)));
+                    temperature.add(String.valueOf(hourly.temp - 273));
+                    humidity.add(String.valueOf(hourly.humidity));
+                    fxTime.add(new Date(hourly.timestamp * 1000L));
+                    windDirection.add(String.valueOf(hourly.windDirection));
+                    windSpeed.add(String.valueOf(Math.round(hourly.windSpeed)));
+                    windScale.add(hourly.windSpeedAsBeaufort() + "-" + hourly.windSpeedAsBeaufort());
+
+                    if (++i >= hours) {
+                        break;
+                    }
+                }
+            }
+        }
+
+        public static class Serializer implements JsonSerializer<HourlyResponse> {
+            @Override
+            public JsonElement serialize(final HourlyResponse obj, final Type type, final JsonSerializationContext context) {
+                final JsonObject object = new JsonObject();
+                object.add("pubTime", context.serialize(obj.pubTime));
+                object.add("weather", context.serialize(obj.weather));
+                object.add("temperature", context.serialize(obj.temperature));
+                object.add("humidity", context.serialize(obj.humidity));
+                object.add("fxTime", context.serialize(obj.fxTime));
+                object.add("windDirection", context.serialize(obj.windDirection));
+                object.add("windSpeed", context.serialize(obj.windSpeed));
+                object.add("windScale", context.serialize(obj.windScale));
+                return object;
+            }
+        }
+    }
+
+    // /weather/alerts
+    //
+    // locale=zh_CN
+    // deviceSource=11
+    // days=3
+    // isGlobal=true
+    // locationKey=00.000,-0.000,xiaomi_accu:000000
+    public static class AlertsResponse extends Response {
+        public List<Object> alerts = new ArrayList<>();
+
+        public AlertsResponse(final WeatherSpec weatherSpec) {
+
+        }
+
+        public static class Serializer implements JsonSerializer<AlertsResponse> {
+            @Override
+            public JsonElement serialize(final AlertsResponse obj, final Type type, final JsonSerializationContext context) {
+                final JsonObject object = new JsonObject();
+                object.add("alerts", context.serialize(obj.alerts));
+                return object;
+            }
+        }
+    }
+
+    private static class LocalDateSerializer implements JsonSerializer<LocalDate> {
+        @Override
+        public JsonElement serialize(final LocalDate src, final Type typeOfSrc, final JsonSerializationContext context) {
+            // Serialize as "YYYY-MM-DD" string
+            return new JsonPrimitive(src.format(DateTimeFormatter.ISO_LOCAL_DATE));
+        }
+    }
+
+    public static final int SUNNY = 0;
+    public static final int SOME_CLOUDS = 1;
+    public static final int CLOUDY = 2;
+    public static final int RAIN_WITH_SUN = 3;
+    public static final int THUNDERSTORM = 4;
+    public static final int HAIL = 5;
+    public static final int SLEET = 6;
+    public static final int LIGHT_RAIN = 7;
+    public static final int MODERATE_RAIN = 8;
+    public static final int HEAVY_RAIN = 9;
+    public static final int RAINSTORM = 10;
+    public static final int HEAVY_RAINSTORM = 11;
+    public static final int EXTRAORDINARY_RAINSTORM = 12;
+    public static final int SNOW_SHOWER_WITH_SOME_SUN = 13;
+    public static final int LIGHT_SNOW = 14;
+    public static final int MODERATE_SNOW = 15;
+    public static final int HEAVY_SNOW = 16;
+    public static final int SNOWSTORM = 17;
+    public static final int FOG = 18;
+    public static final int FREEZING_RAIN = 19;
+    public static final int SANDSTORM = 20;
+    public static final int LIGHT_TO_MODERATE_RAIN = 21;
+    public static final int MODERATE_TO_HEAVY_RAIN = 22;
+    public static final int HEAVY_RAIN_TO_RAINSTORM = 23;
+    public static final int RAINSTORM_TO_HEAVY_RAIN = 24;
+    public static final int HEAVY_TO_SEVERE_STORM = 25;
+    public static final int LIGHT_TO_MODERATE_SNOW = 26;
+    public static final int MODERATE_TO_HEAVY_SNOW = 27;
+    public static final int HEAVY_SNOW_TO_SNOWSTORM = 28;
+    public static final int DUST = 29;
+    public static final int SAND_BLOWING = 30;
+    public static final int STRONG_SANDSTORM = 31;
+    public static final int DENSE_FOG = 32;
+    public static final int SNOW = 33;
+
+    public static int mapToZeppOsWeatherCode(final int openWeatherMapCondition) {
+        // openweathermap.org conditions:
+        // http://openweathermap.org/weather-conditions
+        switch (openWeatherMapCondition) {
+            case 511:  //freezing rain:  //13d
+                return FREEZING_RAIN;
+            case 731:  //sand/dust whirls:  //50d
+            case 751:  //sand:  //50d
+                return SAND_BLOWING;
+            case 761:  //dust:  //50d
+            case 762:  //volcanic ash:  //50d
+                return DUST;
+            case 741:  //fog:  //50d
+                return DENSE_FOG;
+            default:
+                return HuamiWeatherConditions.mapToAmazfitBipWeatherCode(openWeatherMapCondition) & 0xff;
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/operations/ZeppOsAgpsFile.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/operations/ZeppOsAgpsFile.java
new file mode 100644
index 0000000..9b2727a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/operations/ZeppOsAgpsFile.java
@@ -0,0 +1,82 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.operations;
+
+import org.json.JSONObject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.charset.StandardCharsets;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.UIHHContainer;
+import nodomain.freeyourgadget.gadgetbridge.util.GBZipFile;
+import nodomain.freeyourgadget.gadgetbridge.util.ZipFileException;
+
+public class ZeppOsAgpsFile {
+    private static final Logger LOG = LoggerFactory.getLogger(ZeppOsAgpsFile.class);
+
+    private final byte[] zipBytes;
+
+    public ZeppOsAgpsFile(final byte[] zipBytes) {
+        this.zipBytes = zipBytes;
+    }
+
+    public boolean isValid() {
+        if (!GBZipFile.isZipFile(zipBytes)) {
+            return false;
+        }
+
+        final GBZipFile zipFile = new GBZipFile(zipBytes);
+
+        try {
+            final byte[] manifestBin = zipFile.getFileFromZip("META-INF/MANIFEST.MF");
+            if (manifestBin == null) {
+                LOG.warn("Failed to get MANIFEST from zip");
+                return false;
+            }
+
+            final String appJsonString = new String(manifestBin, StandardCharsets.UTF_8)
+                    // Remove UTF-8 BOM if present
+                    .replace("\uFEFF", "");
+            final JSONObject jsonObject = new JSONObject(appJsonString);
+            return jsonObject.getString("manifestVersion").equals("2.0") &&
+                    zipFile.fileExists("EPO_BDS_3.DAT") &&
+                    zipFile.fileExists("EPO_GAL_7.DAT") &&
+                    zipFile.fileExists("EPO_GR_3.DAT");
+        } catch (final Exception e) {
+            LOG.error("Failed to parse read MANIFEST or check file", e);
+        }
+
+        return false;
+    }
+
+    public byte[] getUihhBytes() {
+        final UIHHContainer uihh = new UIHHContainer();
+
+        final GBZipFile zipFile = new GBZipFile(zipBytes);
+
+        try {
+            uihh.addFile(UIHHContainer.FileType.AGPS_EPO_GR_3, zipFile.getFileFromZip("EPO_GR_3.DAT"));
+            uihh.addFile(UIHHContainer.FileType.AGPS_EPO_GAL_7, zipFile.getFileFromZip("EPO_GAL_7.DAT"));
+            uihh.addFile(UIHHContainer.FileType.AGPS_EPO_BDS_3, zipFile.getFileFromZip("EPO_BDS_3.DAT"));
+        } catch (final ZipFileException e) {
+            throw new IllegalStateException("Failed to read file from zip", e);
+        }
+
+        return uihh.toRawBytes();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/operations/ZeppOsAgpsUpdateOperation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/operations/ZeppOsAgpsUpdateOperation.java
new file mode 100644
index 0000000..38ff4fc
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/operations/ZeppOsAgpsUpdateOperation.java
@@ -0,0 +1,162 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.operations;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEOperation;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetProgressAction;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsAgpsService;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsConfigService;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsFileTransferService;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.OperationStatus;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+/**
+ * Updates the AGPS EPO on a Zepp OS device. Update goes as follows:
+ * 1. Request an upload start from {@link ZeppOsAgpsService}
+ * 2. After successful ack from 1, upload the file to agps://upgrade using {@link ZeppOsFileTransferService}
+ * 3. After successful ack from 2, trigger the actual update with {@link ZeppOsAgpsService}
+ * 4. After successful ack from 3, update is finished. Trigger an AGPS config request from {@link ZeppOsConfigService}
+ * to reload the AGPS update and expiration timestamps.
+ */
+public class ZeppOsAgpsUpdateOperation extends AbstractBTLEOperation<ZeppOsSupport>
+        implements ZeppOsFileTransferService.Callback, ZeppOsAgpsService.Callback {
+    private static final Logger LOG = LoggerFactory.getLogger(ZeppOsAgpsUpdateOperation.class);
+
+    private static final String AGPS_UPDATE_URL = "agps://upgrade";
+    private static final String AGPS_UPDATE_FILE = "uih.bin";
+
+    private final ZeppOsAgpsFile file;
+    private final byte[] fileBytes;
+
+    private final ZeppOsAgpsService agpsService;
+    private final ZeppOsFileTransferService fileTransferService;
+    private final ZeppOsConfigService configService;
+
+    public ZeppOsAgpsUpdateOperation(final ZeppOsSupport support,
+                                     final ZeppOsAgpsFile file,
+                                     final ZeppOsAgpsService agpsService,
+                                     final ZeppOsFileTransferService fileTransferService,
+                                     final ZeppOsConfigService configService) {
+        super(support);
+        this.file = file;
+        this.fileBytes = file.getUihhBytes();
+        this.agpsService = agpsService;
+        this.fileTransferService = fileTransferService;
+        this.configService = configService;
+    }
+
+    @Override
+    protected void doPerform() throws IOException {
+        agpsService.setCallback(this);
+        agpsService.startUpload(file.getUihhBytes().length);
+    }
+
+    @Override
+    protected void operationFinished() {
+        operationStatus = OperationStatus.FINISHED;
+        if (getDevice() != null && getDevice().isConnected()) {
+            unsetBusy();
+            getDevice().sendDeviceUpdateIntent(getContext());
+        }
+    }
+
+    @Override
+    public void onFileUploadFinish(final boolean success) {
+        LOG.info("Finished file upload operation, success={}", success);
+
+        agpsService.startUpdate();
+    }
+
+    @Override
+    public void onFileUploadProgress(final int progress) {
+        LOG.trace("File upload operation progress: {}", progress);
+
+        // This makes the progress go from 0% to 50%, during file upload the other 50% are incremented
+        // by the update process on the watch
+        final int progressPercent = (int) ((((float) (progress)) / (fileBytes.length * 2)) * 100);
+        updateProgress(progressPercent);
+    }
+
+    @Override
+    public void onFileDownloadFinish(final String url, final String filename, final byte[] data) {
+        LOG.warn("Received unexpected file: url={} filename={} length={}", url, filename, data.length);
+    }
+
+    @Override
+    public void onAgpsUploadStartResponse(final boolean success) {
+        if (!success) {
+            onFinish(false);
+            return;
+        }
+
+        fileTransferService.sendFile(AGPS_UPDATE_URL, AGPS_UPDATE_FILE, fileBytes, false, this);
+    }
+
+    @Override
+    public void onAgpsProgressResponse(final int size, final int progress) {
+        // First 50% are from file upload, so this one starts at 50%
+        final int progressPercent = (int) ((((float) (size + progress)) / (size * 2)) * 100);
+        updateProgress(progressPercent);
+    }
+
+    @Override
+    public void onAgpsUpdateFinishResponse(final boolean success) {
+        if (success) {
+            try {
+                final TransactionBuilder builder = performInitialized("request agps config");
+                configService.requestConfig(builder, ZeppOsConfigService.ConfigGroup.AGPS);
+                builder.queue(getQueue());
+            } catch (final Exception e) {
+                LOG.error("Failed to request agps config", e);
+            }
+        }
+
+        onFinish(success);
+    }
+
+    private void updateProgress(final int progressPercent) {
+        try {
+            final TransactionBuilder builder = performInitialized("send agps update progress");
+            builder.add(new SetProgressAction(getContext().getString(R.string.updatefirmwareoperation_update_in_progress), true, progressPercent, getContext()));
+            builder.queue(getQueue());
+        } catch (final Exception e) {
+            LOG.error("Failed to update progress notification", e);
+        }
+    }
+
+    private void onFinish(final boolean success) {
+        LOG.info("Finished agps update operation, success={}", success);
+
+        agpsService.setCallback(null);
+
+        final String notificationMessage = success ?
+                getContext().getString(R.string.updatefirmwareoperation_update_complete) :
+                getContext().getString(R.string.updatefirmwareoperation_write_failed);
+
+        GB.updateInstallNotification(notificationMessage, false, 100, getContext());
+
+        operationFinished();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/operations/ZeppOsFirmwareUpdateOperation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/operations/ZeppOsFirmwareUpdateOperation.java
new file mode 100644
index 0000000..8c59ce0
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/operations/ZeppOsFirmwareUpdateOperation.java
@@ -0,0 +1,376 @@
+/*  Copyright (C) 2022-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.operations;
+
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.net.Uri;
+import android.widget.Toast;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventDisplayMessage;
+import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.zeppos.ZeppOsCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.zeppos.ZeppOsFwHelper;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceBusyAction;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetProgressAction;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.update.UpdateFirmwareOperation2020;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.AbstractMiBandOperation;
+import nodomain.freeyourgadget.gadgetbridge.util.ArrayUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+/**
+ * This class is basically UpdateFirmwareOperation2020. However, Zepp OS firmwares are growing huge (200+MB),
+ * which crash Gadgetbridge. In order to refactor the upgrade process for Zepp OS devices without having to
+ * refactor all 52 devices, the code was moved to this class so it can be refactored with a smaller impact.
+ * <p>
+ * The protocol for Zepp OS devices differs from UpdateFirmwareOperation2020 in the following points:
+ * - no crc16 support (not needed for zepp os)
+ * - buildFirmwareInfoCommand is slightly different
+ * - at the end of the update, we request display items / watchfaces depending on what was installed, to refresh the
+ * preferences in the UI
+ */
+public class ZeppOsFirmwareUpdateOperation extends AbstractMiBandOperation<ZeppOsSupport> {
+    private static final Logger LOG = LoggerFactory.getLogger(ZeppOsFirmwareUpdateOperation.class);
+
+    private final Uri uri;
+    private final BluetoothGattCharacteristic fwCControlChar;
+    private final BluetoothGattCharacteristic fwCDataChar;
+    private ZeppOsFwHelper fwHelper;
+    private RandomAccessFile raf;
+
+    protected int mChunkLength = -1;
+
+    public ZeppOsFirmwareUpdateOperation(final Uri uri, final ZeppOsSupport support) {
+        super(support);
+        this.uri = uri;
+        this.fwCControlChar = getCharacteristic(HuamiService.UUID_CHARACTERISTIC_FIRMWARE);
+        this.fwCDataChar = getCharacteristic(HuamiService.UUID_CHARACTERISTIC_FIRMWARE_DATA);
+    }
+
+    @Override
+    protected void enableNeededNotifications(TransactionBuilder builder, boolean enable) {
+        builder.notify(fwCControlChar, enable);
+    }
+
+    @Override
+    protected void enableOtherNotifications(final TransactionBuilder builder, final boolean enable) {
+        // Disable 2021 chunked reads, otherwise firmware upgrades and activity sync get interrupted
+        // FIXME is this still needed?
+        builder.notify(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_CHUNKEDTRANSFER_2021_READ), enable);
+    }
+
+    @Override
+    protected void doPerform() throws IOException {
+        final GBDevice device = getDevice();
+        final DeviceCoordinator coordinator = device.getDeviceCoordinator();
+        if (!(coordinator instanceof ZeppOsCoordinator)) {
+            throw new IOException("Not a Zepp OS coordinator for " + getDevice().getAddress());
+        }
+
+        fwHelper = new ZeppOsFwHelper(
+                uri,
+                getContext(),
+                ((ZeppOsCoordinator) coordinator).getDeviceBluetoothName(),
+                ((ZeppOsCoordinator) coordinator).getDeviceSources()
+        );
+
+        if (!fwHelper.isValid()) {
+            throw new IOException("Firmware is not valid for: " + getDevice().getAddress());
+        }
+
+        raf = new RandomAccessFile(fwHelper.getFile(), "r");
+
+        if (!requestParameters()) {
+            displayErrorMessage("Error requesting parameters, aborting.");
+            done();
+        }
+    }
+
+    protected void done() {
+        LOG.info("Operation done.");
+        operationFinished();
+        unsetBusy();
+    }
+
+    @Override
+    public boolean onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
+        if (status != BluetoothGatt.GATT_SUCCESS) {
+            operationFailed();
+        }
+        return super.onCharacteristicWrite(gatt, characteristic, status);
+    }
+
+    void operationFailed() {
+        GB.updateInstallNotification(getContext().getString(R.string.updatefirmwareoperation_write_failed), false, 0, getContext());
+    }
+
+    @Override
+    public boolean onCharacteristicChanged(BluetoothGatt gatt,
+                                           BluetoothGattCharacteristic characteristic) {
+        UUID characteristicUUID = characteristic.getUuid();
+        if (fwCControlChar.getUuid().equals(characteristicUUID)) {
+            handleNotificationNotif(characteristic.getValue());
+            return true; // don't let anyone else handle it
+        } else {
+            super.onCharacteristicChanged(gatt, characteristic);
+        }
+        return false;
+    }
+
+    protected void handleNotificationNotif(byte[] value) {
+        boolean success = (value[2] == HuamiService.SUCCESS) || ((value[1] == UpdateFirmwareOperation2020.REPLY_UPDATE_PROGRESS) && value.length >= 6); // ugly
+
+        if (value[0] == HuamiService.RESPONSE && success) {
+            try {
+                switch (value[1]) {
+                    case UpdateFirmwareOperation2020.COMMAND_REQUEST_PARAMETERS: {
+                        mChunkLength = (value[4] & 0xff) | ((value[5] & 0xff) << 8);
+                        LOG.info("got chunk length of " + mChunkLength);
+                        sendFwInfo();
+                        break;
+                    }
+                    case UpdateFirmwareOperation2020.COMMAND_SEND_FIRMWARE_INFO:
+                        sendTransferStart();
+                        break;
+                    case UpdateFirmwareOperation2020.COMMAND_START_TRANSFER:
+                        sendFirmwareDataChunk(0);
+                        break;
+                    // not used in zepp os
+                    //case HuamiService.COMMAND_FIRMWARE_START_DATA:
+                    //    sendChecksum();
+                    //    break;
+                    case UpdateFirmwareOperation2020.REPLY_UPDATE_PROGRESS:
+                        int offset = (value[2] & 0xff) | ((value[3] & 0xff) << 8) | ((value[4] & 0xff) << 16) | ((value[5] & 0xff) << 24);
+                        LOG.info("update progress " + offset + " bytes");
+                        sendFirmwareDataChunk(offset);
+                        break;
+                    case UpdateFirmwareOperation2020.COMMAND_COMPLETE_TRANSFER:
+                        sendFinalize();
+                        break;
+                    case UpdateFirmwareOperation2020.COMMAND_FINALIZE_UPDATE: {
+                        if (fwHelper.getFirmwareType() == HuamiFirmwareType.FIRMWARE) {
+                            TransactionBuilder builder = performInitialized("reboot");
+                            getSupport().sendReboot(builder);
+                            builder.queue(getQueue());
+                        } else {
+                            GB.updateInstallNotification(getContext().getString(R.string.updatefirmwareoperation_update_complete), false, 100, getContext());
+                            done();
+                        }
+                        break;
+                    }
+                    case HuamiService.COMMAND_FIRMWARE_REBOOT: {
+                        LOG.info("Reboot command successfully sent.");
+                        GB.updateInstallNotification(getContext().getString(R.string.updatefirmwareoperation_update_complete), false, 100, getContext());
+                        done();
+                        break;
+                    }
+                    default: {
+                        LOG.error("Unexpected response during firmware update: ");
+                        getSupport().logMessageContent(value);
+                        operationFailed();
+                        displayErrorMessage(getContext().getString(R.string.updatefirmwareoperation_updateproblem_do_not_reboot));
+                        done();
+                    }
+                }
+            } catch (Exception ex) {
+                displayErrorMessage(getContext().getString(R.string.updatefirmwareoperation_updateproblem_do_not_reboot));
+                done();
+            }
+        } else {
+            LOG.error("Unexpected notification during firmware update: ");
+            operationFailed();
+            getSupport().logMessageContent(value);
+            int errorMessage = R.string.updatefirmwareoperation_metadata_updateproblem;
+            // Display a more specific error message for known errors
+
+            if (value[0] == HuamiService.RESPONSE && value[1] == UpdateFirmwareOperation2020.COMMAND_START_TRANSFER && value[2] == UpdateFirmwareOperation2020.REPLY_ERROR_FREE_SPACE) {
+                // Not enough free space on the device
+                errorMessage = R.string.updatefirmwareoperation_updateproblem_free_space;
+            } else if (value[0] == HuamiService.RESPONSE && value[1] == UpdateFirmwareOperation2020.COMMAND_SEND_FIRMWARE_INFO && value[2] == UpdateFirmwareOperation2020.REPLY_ERROR_LOW_BATTERY) {
+                // Battery is too low
+                errorMessage = R.string.updatefirmwareoperation_updateproblem_low_battery;
+            }
+            displayErrorMessage(getContext().getString(errorMessage));
+            done();
+        }
+
+        if (ArrayUtils.startsWith(value, new byte[]{HuamiService.RESPONSE, UpdateFirmwareOperation2020.COMMAND_FINALIZE_UPDATE, HuamiService.SUCCESS})) {
+            if (fwHelper.getFirmwareType() == HuamiFirmwareType.APP) {
+                // After an app is installed, request the display items from the band (new app will be at the end)
+                try {
+                    TransactionBuilder builder = performInitialized("request display items and apps");
+                    getSupport().requestDisplayItems(builder);
+                    getSupport().requestApps(builder);
+                    builder.queue(getQueue());
+                } catch (final IOException e) {
+                    LOG.error("Failed to request display items after app install", e);
+                }
+            } else if (fwHelper.getFirmwareType() == HuamiFirmwareType.WATCHFACE) {
+                // After a watchface is installed, request the watchfaces from the band (new watchface will be at the end)
+                try {
+                    TransactionBuilder builder = performInitialized("request watchfaces and apps");
+                    getSupport().requestWatchfaces(builder);
+                    getSupport().requestApps(builder);
+                    builder.queue(getQueue());
+                } catch (final IOException e) {
+                    LOG.error("Failed to request watchfaces after watchface install", e);
+                }
+            }
+        }
+    }
+
+    private void displayErrorMessage(String message) {
+        getSupport().handleGBDeviceEvent(new GBDeviceEventDisplayMessage(message, Toast.LENGTH_LONG, GB.ERROR));
+    }
+
+    public void sendFwInfo() {
+        try {
+            TransactionBuilder builder = performInitialized("send firmware info");
+            builder.add(new SetDeviceBusyAction(getDevice(), getContext().getString(R.string.updating_firmware), getContext()));
+            builder.write(fwCControlChar, buildFirmwareInfoCommand());
+            builder.queue(getQueue());
+        } catch (IOException e) {
+            LOG.error("Error sending firmware info: " + e.getLocalizedMessage(), e);
+        }
+    }
+
+    protected byte[] buildFirmwareInfoCommand() {
+        final int fwSize = fwHelper.getSize();
+        final int crc32 = fwHelper.getCrc32();
+
+        final byte[] sizeBytes = BLETypeConversions.fromUint32(fwSize);
+        final byte[] chunkSizeBytes = BLETypeConversions.fromUint16(mChunkLength);
+        final byte[] crcBytes = BLETypeConversions.fromUint32(crc32);
+
+        return new byte[]{
+                UpdateFirmwareOperation2020.COMMAND_SEND_FIRMWARE_INFO,
+                fwHelper.getFirmwareType().getValue(),
+                sizeBytes[0],
+                sizeBytes[1],
+                sizeBytes[2],
+                sizeBytes[3],
+                crcBytes[0],
+                crcBytes[1],
+                crcBytes[2],
+                crcBytes[3],
+                chunkSizeBytes[0],
+                chunkSizeBytes[1],
+                0, // 0 to update in foreground, 1 for background
+                (byte) 0xff, // ??
+        };
+    }
+
+    public boolean requestParameters() {
+        try {
+            TransactionBuilder builder = performInitialized("get update capabilities");
+            byte[] bytes = new byte[]{UpdateFirmwareOperation2020.COMMAND_REQUEST_PARAMETERS};
+            builder.write(fwCControlChar, bytes);
+            builder.queue(getQueue());
+            return true;
+        } catch (IOException e) {
+            LOG.error("Error sending firmware info: " + e.getLocalizedMessage(), e);
+            return false;
+        }
+    }
+
+    private void sendFirmwareDataChunk(int offset) {
+        int len = fwHelper.getSize();
+        int remaining = len - offset;
+        final int packetLength = getSupport().getMTU() - 3;
+
+        int chunkLength = mChunkLength;
+        if (remaining < mChunkLength) {
+            chunkLength = remaining;
+        }
+
+        int packets = chunkLength / packetLength;
+        int chunkProgress = 0;
+
+        try {
+            if (remaining <= 0) {
+                sendTransferComplete();
+                return;
+            }
+
+            TransactionBuilder builder = performInitialized("send firmware packets");
+
+            for (int i = 0; i < packets; i++) {
+                raf.seek(offset + (long) i * packetLength);
+                byte[] fwChunk = new byte[packetLength];
+                raf.read(fwChunk);
+
+                builder.write(fwCDataChar, fwChunk);
+                chunkProgress += packetLength;
+            }
+
+            if (chunkProgress < chunkLength) {
+                raf.seek(offset + (long) packets * packetLength);
+                byte[] lastChunk = new byte[chunkLength - chunkProgress];
+                raf.read(lastChunk);
+                builder.write(fwCDataChar, lastChunk);
+            }
+
+            int progressPercent = (int) ((((float) (offset + chunkLength)) / len) * 100);
+
+            builder.add(new SetProgressAction(getContext().getString(R.string.updatefirmwareoperation_update_in_progress), true, progressPercent, getContext()));
+
+            builder.queue(getQueue());
+
+        } catch (final IOException e) {
+            LOG.error("Unable to send fw to device", e);
+            GB.updateInstallNotification(getContext().getString(R.string.updatefirmwareoperation_firmware_not_sent), false, 0, getContext());
+        }
+    }
+
+    protected void sendTransferStart() throws IOException {
+        final TransactionBuilder builder = performInitialized("transfer complete");
+        builder.write(fwCControlChar, new byte[]{
+                UpdateFirmwareOperation2020.COMMAND_START_TRANSFER, 1,
+        });
+        builder.queue(getQueue());
+    }
+
+    protected void sendTransferComplete() throws IOException {
+        final TransactionBuilder builder = performInitialized("transfer complete");
+        builder.write(fwCControlChar, new byte[]{
+                UpdateFirmwareOperation2020.COMMAND_COMPLETE_TRANSFER,
+        });
+        builder.queue(getQueue());
+    }
+
+    protected void sendFinalize() throws IOException {
+        final TransactionBuilder builder = performInitialized("finalize firmware");
+        builder.write(fwCControlChar, new byte[]{
+                UpdateFirmwareOperation2020.COMMAND_FINALIZE_UPDATE,
+        });
+        builder.queue(getQueue());
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/operations/ZeppOsGpxRouteFile.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/operations/ZeppOsGpxRouteFile.java
new file mode 100644
index 0000000..d5d0e40
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/operations/ZeppOsGpxRouteFile.java
@@ -0,0 +1,170 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.operations;
+
+import android.location.Location;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.model.GPSCoordinate;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
+import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.gpx.GpxParser;
+import nodomain.freeyourgadget.gadgetbridge.util.gpx.model.GpxFile;
+import nodomain.freeyourgadget.gadgetbridge.util.gpx.model.GpxTrackPoint;
+import nodomain.freeyourgadget.gadgetbridge.util.gpx.model.GpxWaypoint;
+
+public class ZeppOsGpxRouteFile {
+    private static final Logger LOG = LoggerFactory.getLogger(ZeppOsGpxRouteFile.class);
+
+    private static final double COORD_MULTIPLIER = 3000000.0;
+
+    private final byte[] xmlBytes;
+    private final long timestamp;
+    private final GpxFile gpxFile;
+
+    public ZeppOsGpxRouteFile(final byte[] xmlBytes) {
+        this.xmlBytes = xmlBytes;
+        this.timestamp = System.currentTimeMillis() / 1000;
+        this.gpxFile = GpxParser.parseGpx(xmlBytes);
+    }
+
+    public boolean isValid() {
+        return this.gpxFile != null;
+    }
+
+    public long getTimestamp() {
+        return timestamp;
+    }
+
+    public String getName() {
+        if (gpxFile == null) {
+            return "";
+        }
+
+        if (!StringUtils.isNullOrEmpty(gpxFile.getName())) {
+            return gpxFile.getName();
+        } else {
+            return String.valueOf(getTimestamp());
+        }
+    }
+
+    public byte[] getEncodedBytes() {
+        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        if (gpxFile == null) {
+            LOG.error("Failed to read gpx file - this should never happen");
+            return null;
+        }
+
+        final List<GpxTrackPoint> trackPoints = gpxFile.getPoints();
+        final List<GpxWaypoint> waypoints = gpxFile.getWaypoints();
+
+        double minLatitude = 180;
+        double maxLatitude = -180;
+        double minLongitude = 180;
+        double maxLongitude = -180;
+        double minAltitude = 10000;
+        double maxAltitude = -10000;
+
+        for (final GPSCoordinate coord : trackPoints) {
+            minLatitude = Math.min(minLatitude, coord.getLatitude());
+            maxLatitude = Math.max(maxLatitude, coord.getLatitude());
+            minLongitude = Math.min(minLongitude, coord.getLongitude());
+            maxLongitude = Math.max(maxLongitude, coord.getLongitude());
+            minAltitude = Math.min(minAltitude, coord.getAltitude());
+            maxAltitude = Math.max(maxAltitude, coord.getAltitude());
+        }
+
+        try {
+            baos.write(BLETypeConversions.fromUint32(0)); // ?
+            baos.write(BLETypeConversions.fromUint32(0x54)); // ?
+            baos.write(BLETypeConversions.fromUint32(0x01)); // ?
+            baos.write(BLETypeConversions.fromUint32((int) timestamp));
+            baos.write(BLETypeConversions.fromUint32((int) (minLatitude * COORD_MULTIPLIER)));
+            baos.write(BLETypeConversions.fromUint32((int) (maxLatitude * COORD_MULTIPLIER)));
+            baos.write(BLETypeConversions.fromUint32((int) (minLongitude * COORD_MULTIPLIER)));
+            baos.write(BLETypeConversions.fromUint32((int) (maxLongitude * COORD_MULTIPLIER)));
+            baos.write(BLETypeConversions.fromUint32((int) minAltitude));
+            baos.write(BLETypeConversions.fromUint32((int) maxAltitude));
+            baos.write(truncatePadString(getName()));
+            baos.write(BLETypeConversions.fromUint32(0)); // ?
+
+            if (!waypoints.isEmpty()) {
+                baos.write(BLETypeConversions.fromUint32(2));
+                baos.write(BLETypeConversions.fromUint32(waypoints.size() * 68));
+                for (final GpxWaypoint waypoint : waypoints) {
+                    baos.write(BLETypeConversions.fromUint32(0x1a)); // ?
+                    baos.write(BLETypeConversions.fromUint32((int) (waypoint.getLatitude() * COORD_MULTIPLIER)));
+                    baos.write(BLETypeConversions.fromUint32((int) (waypoint.getLongitude() * COORD_MULTIPLIER)));
+                    baos.write(BLETypeConversions.fromUint32((int) waypoint.getAltitude()));
+                    baos.write(truncatePadString(waypoint.getName()));
+                    baos.write(BLETypeConversions.fromUint32(0)); // ?
+                }
+            }
+
+            baos.write(BLETypeConversions.fromUint32(1)); // ?
+            baos.write(BLETypeConversions.fromUint32(trackPoints.size() * 14));
+
+            // Keep track of the total distance
+            double totalDist = 0;
+            GPSCoordinate prevPoint = trackPoints.isEmpty() ? null : trackPoints.get(0);
+
+            for (final GPSCoordinate point : trackPoints) {
+                totalDist += distanceBetween(prevPoint, point);
+
+                baos.write(BLETypeConversions.fromUint32((int) totalDist));
+                baos.write(BLETypeConversions.fromUint32((int) (point.getLatitude() * COORD_MULTIPLIER)));
+                baos.write(BLETypeConversions.fromUint32((int) (point.getLongitude() * COORD_MULTIPLIER)));
+                baos.write(BLETypeConversions.fromUint16((int) point.getAltitude()));
+
+                prevPoint = point;
+            }
+        } catch (final IOException e) {
+            LOG.error("Failed to encode gpx file", e);
+            return null;
+        }
+
+        return baos.toByteArray();
+    }
+
+    public static double distanceBetween(final GPSCoordinate a, final GPSCoordinate b) {
+        final Location start = new Location("start");
+        start.setLatitude(a.getLatitude());
+        start.setLongitude(a.getLongitude());
+
+        final Location end = new Location("end");
+        end.setLatitude(b.getLatitude());
+        end.setLongitude(b.getLongitude());
+
+        return end.distanceTo(start);
+    }
+
+    /**
+     * Truncates / pads a string to 48 bytes (including null terminator).
+     */
+    public static byte[] truncatePadString(final String s) {
+        final ByteBuffer buf = ByteBuffer.allocate(48);
+        buf.put(StringUtils.truncateToBytes(s, 47));
+        return buf.array();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/operations/ZeppOsGpxRouteUploadOperation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/operations/ZeppOsGpxRouteUploadOperation.java
new file mode 100644
index 0000000..780856b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/operations/ZeppOsGpxRouteUploadOperation.java
@@ -0,0 +1,106 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.operations;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEOperation;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetProgressAction;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsFileTransferService;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.OperationStatus;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class ZeppOsGpxRouteUploadOperation extends AbstractBTLEOperation<ZeppOsSupport>
+        implements ZeppOsFileTransferService.Callback {
+    private static final Logger LOG = LoggerFactory.getLogger(ZeppOsGpxRouteUploadOperation.class);
+
+    private final ZeppOsGpxRouteFile file;
+    private final byte[] fileBytes;
+
+    private final ZeppOsFileTransferService fileTransferService;
+
+    public ZeppOsGpxRouteUploadOperation(final ZeppOsSupport support,
+                                         final ZeppOsGpxRouteFile file,
+                                         final ZeppOsFileTransferService fileTransferService) {
+        super(support);
+        this.file = file;
+        this.fileBytes = file.getEncodedBytes();
+        this.fileTransferService = fileTransferService;
+    }
+
+    @Override
+    protected void doPerform() throws IOException {
+        fileTransferService.sendFile(
+                "sport://file_transfer?appId=7073283073&params={}",
+                "track_" + file.getTimestamp() + ".dat",
+                fileBytes,
+                false,
+                this
+        );
+    }
+
+    @Override
+    protected void operationFinished() {
+        operationStatus = OperationStatus.FINISHED;
+        if (getDevice() != null && getDevice().isConnected()) {
+            unsetBusy();
+            getDevice().sendDeviceUpdateIntent(getContext());
+        }
+    }
+
+    @Override
+    public void onFileUploadFinish(final boolean success) {
+        LOG.info("Finished gpx route upload operation, success={}", success);
+
+        final String notificationMessage = success ?
+                getContext().getString(R.string.gpx_route_upload_complete) :
+                getContext().getString(R.string.gpx_route_upload_failed);
+
+        GB.updateInstallNotification(notificationMessage, false, 100, getContext());
+
+        operationFinished();
+    }
+
+    @Override
+    public void onFileUploadProgress(final int progress) {
+        LOG.trace("Gpx route upload operation progress: {}", progress);
+
+        final int progressPercent = (int) ((((float) (progress)) / fileBytes.length) * 100);
+        updateProgress(progressPercent);
+    }
+
+    @Override
+    public void onFileDownloadFinish(final String url, final String filename, final byte[] data) {
+        LOG.warn("Received unexpected file: url={} filename={} length={}", url, filename, data.length);
+    }
+
+    private void updateProgress(final int progressPercent) {
+        try {
+            final TransactionBuilder builder = performInitialized("send gpx route upload progress");
+            builder.add(new SetProgressAction(getContext().getString(R.string.gpx_route_upload_in_progress), true, progressPercent, getContext()));
+            builder.queue(getQueue());
+        } catch (final Exception e) {
+            LOG.error("Failed to update progress notification", e);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsAgpsService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsAgpsService.java
new file mode 100644
index 0000000..75e2422
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsAgpsService.java
@@ -0,0 +1,108 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services;
+
+import androidx.annotation.Nullable;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.AbstractZeppOsService;
+
+public class ZeppOsAgpsService extends AbstractZeppOsService {
+    private static final Logger LOG = LoggerFactory.getLogger(ZeppOsAgpsService.class);
+
+    private static final short ENDPOINT = 0x0042;
+
+    private static final byte CMD_UPDATE_START_UPLOAD_REQUEST = 0x03;
+    private static final byte CMD_UPDATE_START_UPLOAD_RESPONSE = 0x04;
+    private static final byte CMD_UPDATE_START_REQUEST = 0x05;
+    private static final byte CMD_UPDATE_PROGRESS_RESPONSE = 0x06;
+    private static final byte CMD_UPDATE_FINISH_RESPONSE = 0x07;
+
+    private Callback mCallback = null;
+
+    public ZeppOsAgpsService(final ZeppOsSupport support) {
+        super(support, false);
+    }
+
+    @Override
+    public short getEndpoint() {
+        return ENDPOINT;
+    }
+
+    @Override
+    public void handlePayload(final byte[] payload) {
+        switch (payload[0]) {
+            case CMD_UPDATE_START_UPLOAD_RESPONSE:
+                final byte uploadStatus = payload[1];
+                LOG.info("Got agps start upload status = {}", uploadStatus);
+                if (mCallback != null) {
+                    mCallback.onAgpsUploadStartResponse(uploadStatus == 0x01);
+                }
+                return;
+            case CMD_UPDATE_PROGRESS_RESPONSE:
+                final int size = BLETypeConversions.toUint32(payload, 1);
+                final int progress = BLETypeConversions.toUint32(payload, 5);
+
+                LOG.info("Got agps progress = {}/{}", progress, size);
+                if (mCallback != null) {
+                    mCallback.onAgpsProgressResponse(size, progress);
+                }
+                return;
+            case CMD_UPDATE_FINISH_RESPONSE:
+                final byte finishStatus = payload[1];
+                LOG.info("Got agps update finish status = {}", finishStatus);
+                if (mCallback != null) {
+                    mCallback.onAgpsUpdateFinishResponse(finishStatus == 0x01);
+                }
+                return;
+            default:
+                LOG.warn("Unexpected agps byte {}", String.format("0x%02x", payload[0]));
+        }
+    }
+
+    public void startUpload(final int size) {
+        final ByteBuffer buf = ByteBuffer.allocate(5)
+                .order(ByteOrder.LITTLE_ENDIAN)
+                .put(CMD_UPDATE_START_UPLOAD_REQUEST)
+                .putInt(size);
+
+        write("start upload request", buf.array());
+    }
+
+    public void startUpdate() {
+        write("start update request", CMD_UPDATE_START_REQUEST);
+    }
+
+    public void setCallback(@Nullable final Callback callback) {
+        this.mCallback = callback;
+    }
+
+    public interface Callback {
+        void onAgpsUploadStartResponse(boolean success);
+
+        void onAgpsProgressResponse(int size, int progress);
+
+        void onAgpsUpdateFinishResponse(boolean success);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsAlarmsService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsAlarmsService.java
new file mode 100644
index 0000000..9ea52aa
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsAlarmsService.java
@@ -0,0 +1,220 @@
+/*  Copyright (C) 2023-2024 Daniel Dakhno, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services;
+
+import android.content.Intent;
+
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Calendar;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.AbstractZeppOsService;
+import nodomain.freeyourgadget.gadgetbridge.util.AlarmUtils;
+
+public class ZeppOsAlarmsService extends AbstractZeppOsService {
+    private static final Logger LOG = LoggerFactory.getLogger(ZeppOsAlarmsService.class);
+
+    public static final short ENDPOINT = 0x000f;
+
+    public static final byte CMD_CAPABILITIES_REQUEST = 0x01;
+    public static final byte CMD_CAPABILITIES_RESPONSE = 0x02;
+    public static final byte CMD_CREATE = 0x03;
+    public static final byte CMD_CREATE_ACK = 0x04;
+    public static final byte CMD_DELETE = 0x05;
+    public static final byte CMD_DELETE_ACK = 0x06;
+    public static final byte CMD_UPDATE = 0x07;
+    public static final byte CMD_UPDATE_ACK = 0x08;
+    public static final byte CMD_REQUEST = 0x09;
+    public static final byte CMD_RESPONSE = 0x0a;
+    public static final byte CMD_NOTIFY_CHANGE = 0x0f;
+
+    public static final int IDX_FLAGS = 0;
+    public static final int IDX_POSITION = 1;
+    public static final int IDX_HOUR = 2;
+    public static final int IDX_MINUTE = 3;
+    public static final int IDX_REPETITION = 4;
+
+    public static final int FLAG_SMART = 0x01;
+    public static final int FLAG_UNKNOWN_2 = 0x02;
+    public static final int FLAG_ENABLED = 0x04;
+
+    public ZeppOsAlarmsService(final ZeppOsSupport support) {
+        super(support, false);
+    }
+
+    @Override
+    public short getEndpoint() {
+        return ENDPOINT;
+    }
+
+    @Override
+    public void handlePayload(final byte[] payload) {
+        switch (payload[0]) {
+            case CMD_CREATE_ACK:
+                LOG.info("Alarm create ACK, status = {}", payload[1]);
+                return;
+            case CMD_DELETE_ACK:
+                LOG.info("Alarm delete ACK, status = {}", payload[1]);
+                return;
+            case CMD_UPDATE_ACK:
+                LOG.info("Alarm update ACK, status = {}", payload[1]);
+                return;
+            case CMD_NOTIFY_CHANGE:
+                LOG.info("Alarms changed on band");
+                requestAlarms();
+                return;
+            case CMD_RESPONSE:
+                LOG.info("Got alarms from band");
+                decodeAndUpdateAlarms(payload);
+                return;
+            default:
+                LOG.warn("Unexpected alarms payload byte {}", String.format("0x%02x", payload[0]));
+        }
+    }
+
+    private void requestAlarms() {
+        try {
+            final TransactionBuilder builder = new TransactionBuilder("request alarms");
+            requestAlarms(builder);
+            builder.queue(getSupport().getQueue());
+        } catch (final Exception e) {
+            LOG.error("Failed to request alarms", e);
+        }
+    }
+
+    public void requestAlarms(final TransactionBuilder builder) {
+        LOG.info("Requesting alarms");
+
+        write(builder, new byte[]{CMD_REQUEST});
+    }
+
+    public void sendAlarm(final Alarm alarm, final TransactionBuilder builder) {
+        final DeviceCoordinator coordinator = getSupport().getDevice().getDeviceCoordinator();
+
+        final Calendar calendar = AlarmUtils.toCalendar(alarm);
+
+        final byte[] alarmMessage;
+        if (!alarm.getUnused()) {
+            int alarmFlags = 0;
+            if (alarm.getEnabled()) {
+                alarmFlags = FLAG_ENABLED;
+            }
+            if (coordinator.supportsSmartWakeup(getSupport().getDevice(), alarm.getPosition()) && alarm.getSmartWakeup()) {
+                alarmFlags |= FLAG_SMART;
+            }
+            alarmMessage = new byte[]{
+                    CMD_CREATE,
+                    (byte) 0x01, // ?
+                    (byte) alarmFlags,
+                    (byte) alarm.getPosition(),
+                    (byte) calendar.get(Calendar.HOUR_OF_DAY),
+                    (byte) calendar.get(Calendar.MINUTE),
+                    (byte) alarm.getRepetition(),
+                    (byte) 0x00, // ?
+                    (byte) 0x00, // ?
+                    (byte) 0x00, // ?
+                    (byte) 0x00, // ?, this is usually 0 in the create command, 1 in the watch response
+                    (byte) 0x00, // ?
+            };
+        } else {
+            // Delete it from the band
+            alarmMessage = new byte[]{
+                    CMD_DELETE,
+                    (byte) 0x01, // ?
+                    (byte) alarm.getPosition()
+            };
+        }
+
+        write(builder, alarmMessage);
+    }
+
+    private void decodeAndUpdateAlarms(final byte[] payload) {
+        final int numAlarms = payload[1];
+
+        if (payload.length != 2 + numAlarms * 10) {
+            LOG.warn("Unexpected payload length of {} for {} alarms", payload.length, numAlarms);
+            return;
+        }
+
+        // Map of alarm position to Alarm, as returned by the band
+        final Map<Integer, Alarm> payloadAlarms = new HashMap<>();
+        for (int i = 0; i < numAlarms; i++) {
+            final Alarm alarm = parseAlarm(payload, 2 + i * 10);
+            payloadAlarms.put(alarm.getPosition(), alarm);
+        }
+
+        final List<nodomain.freeyourgadget.gadgetbridge.entities.Alarm> dbAlarms = DBHelper.getAlarms(getSupport().getDevice());
+        int numUpdatedAlarms = 0;
+
+        for (nodomain.freeyourgadget.gadgetbridge.entities.Alarm alarm : dbAlarms) {
+            final int pos = alarm.getPosition();
+            final Alarm updatedAlarm = payloadAlarms.get(pos);
+            final boolean alarmNeedsUpdate = updatedAlarm == null ||
+                    alarm.getUnused() != updatedAlarm.getUnused() ||
+                    alarm.getEnabled() != updatedAlarm.getEnabled() ||
+                    alarm.getSmartWakeup() != updatedAlarm.getSmartWakeup() ||
+                    alarm.getHour() != updatedAlarm.getHour() ||
+                    alarm.getMinute() != updatedAlarm.getMinute() ||
+                    alarm.getRepetition() != updatedAlarm.getRepetition();
+
+            if (alarmNeedsUpdate) {
+                numUpdatedAlarms++;
+                LOG.info("Updating alarm index={}, unused={}", pos, updatedAlarm == null);
+                alarm.setUnused(updatedAlarm == null);
+                if (updatedAlarm != null) {
+                    alarm.setEnabled(updatedAlarm.getEnabled());
+                    alarm.setSmartWakeup(updatedAlarm.getSmartWakeup());
+                    alarm.setHour(updatedAlarm.getHour());
+                    alarm.setMinute(updatedAlarm.getMinute());
+                    alarm.setRepetition(updatedAlarm.getRepetition());
+                }
+                DBHelper.store(alarm);
+            }
+        }
+
+        if (numUpdatedAlarms > 0) {
+            final Intent intent = new Intent(DeviceService.ACTION_SAVE_ALARMS);
+            LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent);
+        }
+    }
+
+    private Alarm parseAlarm(final byte[] payload, final int offset) {
+        final nodomain.freeyourgadget.gadgetbridge.entities.Alarm alarm = new nodomain.freeyourgadget.gadgetbridge.entities.Alarm();
+
+        alarm.setUnused(false); // If the band sent it, it's not unused
+        alarm.setPosition(payload[offset + IDX_POSITION]);
+        alarm.setEnabled((payload[offset + IDX_FLAGS] & FLAG_ENABLED) > 0);
+        alarm.setSmartWakeup((payload[offset + IDX_FLAGS] & FLAG_SMART) > 0);
+        alarm.setHour(payload[offset + IDX_HOUR]);
+        alarm.setMinute(payload[offset + IDX_MINUTE]);
+        alarm.setRepetition(payload[offset + IDX_REPETITION]);
+
+        return alarm;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsAlexaService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsAlexaService.java
new file mode 100644
index 0000000..4c30a9a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsAlexaService.java
@@ -0,0 +1,391 @@
+/*  Copyright (C) 2023-2024 Andreas Shimokawa, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services;
+
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_VOICE_SERVICE_LANGUAGE;
+
+import android.os.Handler;
+import android.text.TextUtils;
+import android.widget.Toast;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsUtils;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdatePreferences;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.zeppos.ZeppOsCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.AbstractZeppOsService;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
+
+public class ZeppOsAlexaService extends AbstractZeppOsService {
+    private static final Logger LOG = LoggerFactory.getLogger(ZeppOsAlexaService.class);
+
+    private static final short ENDPOINT = 0x0011;
+
+    private static final byte CMD_START = 0x01;
+    private static final byte CMD_END = 0x02;
+    private static final byte CMD_START_ACK = 0x03;
+    private static final byte CMD_VOICE_DATA = 0x05;
+    private static final byte CMD_TRIGGERED = 0x06;
+    private static final byte CMD_REPLY_COMPLEX = 0x08;
+    private static final byte CMD_REPLY_SIMPLE = 0x09;
+    private static final byte CMD_REPLY_VOICE = 0x0a;
+    private static final byte CMD_REPLY_VOICE_MORE = 0x0b;
+    private static final byte CMD_ERROR = 0x0f;
+    private static final byte CMD_LANGUAGES_REQUEST = 0x10;
+    private static final byte CMD_LANGUAGES_RESPONSE = 0x11;
+    private static final byte CMD_SET_LANGUAGE = 0x12;
+    private static final byte CMD_SET_LANGUAGE_ACK = 0x13;
+    private static final byte CMD_CAPABILITIES_REQUEST = 0x20;
+    private static final byte CMD_CAPABILITIES_RESPONSE = 0x21;
+
+    private static final byte COMPLEX_REPLY_WEATHER = 0x01;
+    private static final byte COMPLEX_REPLY_REMINDER = 0x02;
+    private static final byte COMPLEX_REPLY_RICH_TEXT = 0x06;
+
+    private static final byte ERROR_NO_INTERNET = 0x03;
+    private static final byte ERROR_UNAUTHORIZED = 0x06;
+
+    public static final String PREF_VERSION = "zepp_os_alexa_version";
+
+    private final Handler handler = new Handler();
+
+    final ByteArrayOutputStream voiceBuffer = new ByteArrayOutputStream();
+
+    public ZeppOsAlexaService(final ZeppOsSupport support) {
+        super(support, true);
+    }
+
+    @Override
+    public short getEndpoint() {
+        return ENDPOINT;
+    }
+
+    @Override
+    public void handlePayload(final byte[] payload) {
+        switch (payload[0]) {
+            case CMD_START:
+                handleStart(payload);
+                break;
+            case CMD_END:
+                handleEnd(payload);
+                break;
+            case CMD_VOICE_DATA:
+                handleVoiceData(payload);
+                break;
+            case CMD_LANGUAGES_RESPONSE:
+                handleLanguagesResponse(payload);
+                break;
+            case CMD_SET_LANGUAGE_ACK:
+                LOG.info("Alexa set language ack, status = {}", payload[1]);
+                break;
+            case CMD_CAPABILITIES_RESPONSE:
+                handleCapabilitiesResponse(payload);
+                break;
+            default:
+                LOG.warn("Unexpected alexa byte {}", String.format("0x%02x", payload[0]));
+        }
+    }
+
+    @Override
+    public boolean onSendConfiguration(final String config, final Prefs prefs) {
+        switch (config) {
+            case DeviceSettingsPreferenceConst.PREF_VOICE_SERVICE_LANGUAGE:
+                final String alexaLanguage = prefs.getString(DeviceSettingsPreferenceConst.PREF_VOICE_SERVICE_LANGUAGE, null);
+                LOG.info("Setting alexa language to {}", alexaLanguage);
+                setLanguage(alexaLanguage);
+                return true;
+            case "zepp_os_alexa_btn_trigger":
+                GB.toast("Alexa cmd trigger", Toast.LENGTH_SHORT, GB.INFO);
+                sendCmdTriggered();
+                return true;
+            case "zepp_os_alexa_btn_send_simple":
+                GB.toast("Alexa simple reply", Toast.LENGTH_SHORT, GB.INFO);
+                final String simpleText = prefs.getString("zepp_os_alexa_reply_text", null);
+                sendReply(simpleText);
+                return true;
+            case "zepp_os_alexa_btn_send_complex":
+                GB.toast("Alexa complex reply", Toast.LENGTH_SHORT, GB.INFO);
+                final String title = prefs.getString("zepp_os_alexa_reply_title", null);
+                final String subtitle = prefs.getString("zepp_os_alexa_reply_subtitle", null);
+                final String text = prefs.getString("zepp_os_alexa_reply_text", null);
+                sendReply(title, subtitle, text);
+                return true;
+        }
+
+        return false;
+    }
+
+    @Override
+    public void initialize(final TransactionBuilder builder) {
+        if (ZeppOsCoordinator.experimentalFeatures(getSupport().getDevice())) {
+            requestCapabilities(builder);
+            requestLanguages(builder);
+        }
+    }
+
+    public void requestCapabilities(final TransactionBuilder builder) {
+        write(builder, CMD_CAPABILITIES_REQUEST);
+    }
+
+    public void requestLanguages(final TransactionBuilder builder) {
+        write(builder, CMD_LANGUAGES_REQUEST);
+    }
+
+    public void sendReply(final String text) {
+        LOG.debug("Sending alexa simple text reply '{}'", text);
+
+        final byte[] textBytes = StringUtils.ensureNotNull(text).getBytes(StandardCharsets.UTF_8);
+
+        final ByteBuffer buf = ByteBuffer.allocate(textBytes.length + 2)
+                .order(ByteOrder.LITTLE_ENDIAN);
+
+        buf.put(CMD_REPLY_SIMPLE);
+        buf.put(textBytes);
+        buf.put((byte) 0);
+
+        write("send simple text reply", buf.array());
+    }
+
+    public void sendReply(final String title, final String subtitle, final String text) {
+        LOG.debug("Sending alexa complex text reply '{}', '{}', '{}'", title, subtitle, text);
+
+        final byte[] titleBytes = StringUtils.ensureNotNull(title).getBytes(StandardCharsets.UTF_8);
+        final byte[] subtitleBytes = StringUtils.ensureNotNull(subtitle).getBytes(StandardCharsets.UTF_8);
+        final byte[] textBytes = StringUtils.ensureNotNull(text).getBytes(StandardCharsets.UTF_8);
+
+        final int messageLength = titleBytes.length + subtitleBytes.length + textBytes.length + 3;
+
+        final ByteBuffer buf = ByteBuffer.allocate(1 + 2 + 4 + messageLength)
+                .order(ByteOrder.LITTLE_ENDIAN);
+
+        buf.put(CMD_REPLY_COMPLEX);
+        buf.putShort(COMPLEX_REPLY_RICH_TEXT);
+        buf.putInt(messageLength);
+        buf.put(titleBytes);
+        buf.put((byte) 0);
+        buf.put(subtitleBytes);
+        buf.put((byte) 0);
+        buf.put(textBytes);
+        buf.put((byte) 0);
+
+        write("send complex text reply", buf.array());
+    }
+
+    public void sendReply(final WeatherSpec weather) {
+        // TODO finish this
+        if (true) {
+            LOG.warn("Reply with weather not fully implemented");
+            return;
+        }
+
+        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+
+        try {
+            baos.write(0xfd); // ?
+            baos.write(0x03); // ?
+            baos.write(0x00); // ?
+            baos.write(0x00); // ?
+
+            baos.write(BLETypeConversions.fromUint32(weather.timestamp));
+
+            baos.write(StringUtils.ensureNotNull(weather.location).getBytes(StandardCharsets.UTF_8));
+            baos.write(0);
+
+            // FIXME long date string
+            baos.write(0);
+
+            baos.write(StringUtils.ensureNotNull(weather.currentCondition).getBytes(StandardCharsets.UTF_8));
+            baos.write(0);
+
+            // FIXME Second line for the condition
+            baos.write(0);
+
+            // FIXME
+
+            baos.write(weather.forecasts.size());
+            for (final WeatherSpec.Daily forecast : weather.forecasts) {
+                // FIXME
+            }
+        } catch (final IOException e) {
+            LOG.error("Failed to encode weather payload", e);
+            return;
+        }
+
+        final ByteBuffer buf = ByteBuffer.allocate(1 + 2 + 4 + baos.size())
+                .order(ByteOrder.LITTLE_ENDIAN);
+
+        buf.put(CMD_REPLY_COMPLEX);
+        buf.putShort(COMPLEX_REPLY_WEATHER);
+        buf.putInt(baos.size());
+        buf.put(baos.toByteArray());
+
+        write("send weather reply", buf.array());
+    }
+
+    public void sendReplyReminder() {
+        // TODO implement
+    }
+
+    public void sendReplyAlarm() {
+        // TODO implement
+    }
+
+    public void sendVoiceReply(final List<byte[]> voiceFrames) {
+        try {
+            final TransactionBuilder builder = getSupport().performInitialized("send voice reply");
+
+            for (final byte[] voiceFrame : voiceFrames) {
+                // TODO encode
+            }
+
+            builder.queue(getSupport().getQueue());
+        } catch (final Exception e) {
+            LOG.error("Failed to send voice reply", e);
+        }
+    }
+
+    public void setLanguage(final String language) {
+        if (language == null) {
+            LOG.warn("Alexa language is null");
+            return;
+        }
+
+        final byte[] languageBytes = language.replace("_", "-").getBytes(StandardCharsets.UTF_8);
+
+        final ByteBuffer buf = ByteBuffer.allocate(languageBytes.length + 2)
+                .order(ByteOrder.LITTLE_ENDIAN);
+
+        buf.put(CMD_SET_LANGUAGE);
+        buf.put(languageBytes);
+        buf.put((byte) 0);
+
+        write("set alexa language", buf.array());
+    }
+
+    public void sendError(final byte errorCode, final String errorMessage) {
+        final byte[] messageBytes = StringUtils.ensureNotNull(errorMessage).getBytes(StandardCharsets.UTF_8);
+
+        final ByteBuffer buf = ByteBuffer.allocate(messageBytes.length + 3)
+                .order(ByteOrder.LITTLE_ENDIAN);
+
+        buf.put(CMD_ERROR);
+        buf.put(errorCode);
+        buf.put(messageBytes);
+        buf.put((byte) 0);
+
+        write("send alexa error", buf.array());
+    }
+
+    public void sendStartAck() {
+        write("send alexa start ack", new byte[]{CMD_START_ACK, 0x00});
+    }
+
+    public void sendCmdTriggered() {
+        write("alexa cmd triggered", CMD_TRIGGERED);
+    }
+
+    public void sendVoiceMore() {
+        write("alexa request more voice", CMD_REPLY_VOICE_MORE);
+    }
+
+    private void handleCapabilitiesResponse(final byte[] payload) {
+        final int version = payload[1] & 0xFF;
+        if (version != 3) {
+            LOG.warn("Unsupported alexa service version {}", version);
+            return;
+        }
+        final byte var1 = payload[2];
+        if (var1 != 1) {
+            LOG.warn("Unexpected value for var1 '{}'", var1);
+        }
+        final byte var2 = payload[3];
+        if (var1 != 1) {
+            LOG.warn("Unexpected value for var2 '{}'", var2);
+        }
+
+        getSupport().evaluateGBDeviceEvent(new GBDeviceEventUpdatePreferences(PREF_VERSION, version));
+
+        LOG.info("Alexa version={}, var1={}, var2={}", version, var1, var2);
+    }
+
+    private void handleStart(final byte[] payload) {
+        final byte var1 = payload[1];
+        final byte var2 = payload[2];
+        final byte var3 = payload[3];
+        final byte var4 = payload[4];
+        final String params = StringUtils.untilNullTerminator(payload, 5);
+
+        LOG.info("Alexa starting: var1={}, var2={}, var3={}, var4={}, params={}", var1, var2, var3, var4, params);
+
+        // Send the start ack with a slight delay, to give enough time for the connection to switch to fast mode
+        // I can't seem to get the callback for onConnectionUpdated working, and if we reply too soon the watch
+        // will just stay stuck "Connecting...". It seems like it takes ~350ms to switch to fast connection.
+        handler.postDelayed(this::sendStartAck, 700);
+    }
+
+    private void handleEnd(final byte[] payload) {
+        voiceBuffer.reset();
+        // TODO do something else?
+    }
+
+    private void handleVoiceData(final byte[] payload) {
+        LOG.info("Got {} bytes of voice data", payload.length);
+        // TODO
+    }
+
+    private void handleLanguagesResponse(final byte[] payload) {
+        int pos = 2;
+        final String currentLanguage = StringUtils.untilNullTerminator(payload, pos);
+        pos = pos + currentLanguage.length() + 1;
+
+        final int numLanguages = payload[pos++] & 0xFF;
+        final List<String> allLanguages = new ArrayList<>();
+
+        for (int i = 0; i < numLanguages; i++) {
+            final String language = StringUtils.untilNullTerminator(payload, pos);
+            allLanguages.add(language);
+            pos = pos + language.length() + 1;
+        }
+
+        LOG.info("Got alexa language = {}, supported languages = {}", currentLanguage, allLanguages);
+
+        final GBDeviceEventUpdatePreferences evt = new GBDeviceEventUpdatePreferences()
+                .withPreference(PREF_VOICE_SERVICE_LANGUAGE, currentLanguage.replace("-", "_"))
+                .withPreference(DeviceSettingsUtils.getPrefPossibleValuesKey(PREF_VOICE_SERVICE_LANGUAGE), TextUtils.join(",", allLanguages).replace("-", "_"));
+        getSupport().evaluateGBDeviceEvent(evt);
+    }
+
+    public static boolean isSupported(final Prefs devicePrefs) {
+        return devicePrefs.getInt(PREF_VERSION, 0) == 3;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsAppsService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsAppsService.java
new file mode 100644
index 0000000..96ee65a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsAppsService.java
@@ -0,0 +1,225 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services;
+
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.AbstractZeppOsService;
+
+public class ZeppOsAppsService extends AbstractZeppOsService {
+    private static final Logger LOG = LoggerFactory.getLogger(ZeppOsAppsService.class);
+
+    private static final short ENDPOINT = 0x00a0;
+
+    private static final byte CMD_JS = 0x01;
+    private static final byte CMD_APPS = 0x02;
+    private static final byte CMD_SCREENSHOT = 0x03;
+
+    private static final byte CMD_INCOMING = 0x00;
+    private static final byte CMD_OUTGOING = 0x01;
+
+    private static final byte CMD_APPS_LIST = 0x01;
+    private static final byte CMD_APPS_DELETE = 0x03;
+    private static final byte CMD_APPS_DELETING = 0x04;
+    private static final byte CMD_APPS_API_LEVEL = 0x05;
+    private static final byte CMD_SCREENSHOT_REQUEST = 0x01;
+
+    private final List<GBDeviceApp> apps = new ArrayList<>();
+
+    public ZeppOsAppsService(final ZeppOsSupport support) {
+        super(support, false);
+    }
+
+    @Override
+    public short getEndpoint() {
+        return ENDPOINT;
+    }
+
+    @Override
+    public void handlePayload(final byte[] payload) {
+        switch (payload[0]) {
+            case CMD_JS:
+                handleJsPayload(payload);
+                return;
+            case CMD_APPS:
+                handleAppsPayload(payload);
+                return;
+            case CMD_SCREENSHOT:
+                handleScreenshotPayload(payload);
+                return;
+            default:
+                LOG.warn("Unexpected apps byte {}", String.format("0x%02x", payload[0]));
+        }
+    }
+
+    public void initialize(final TransactionBuilder builder) {
+        requestApps(builder);
+    }
+
+    public List<GBDeviceApp> getApps() {
+        return apps;
+    }
+
+    private void handleJsPayload(final byte[] payload) {
+        LOG.warn("Handling js payloads not implemented");
+    }
+
+    private void handleAppsPayload(final byte[] payload) {
+        if (payload[1] != CMD_INCOMING) {
+            LOG.warn("Unexpected non-incoming payload ({})", String.format("0x%02x", payload[1]));
+            return;
+        }
+
+        switch (payload[2]) {
+            case CMD_APPS_LIST:
+                parseAppList(payload);
+                return;
+            case CMD_APPS_DELETE:
+                LOG.info("Got app delete");
+                return;
+            case CMD_APPS_DELETING:
+                LOG.info("Got app deleting");
+                return;
+            case CMD_APPS_API_LEVEL:
+                final int apiLevel = payload[17] & 0xff;
+                LOG.info("Got API level: {}", apiLevel); // 200 = 2.0
+                return;
+            default:
+                LOG.warn("Unexpected apps payload byte {}", payload[2]);
+        }
+    }
+
+    private void handleScreenshotPayload(final byte[] payload) {
+        if (payload[1] != CMD_INCOMING) {
+            LOG.warn("Unexpected non-incoming payload ({})", String.format("0x%02x", payload[1]));
+            return;
+        }
+
+        switch (payload[2]) {
+            case CMD_SCREENSHOT_REQUEST:
+                LOG.info("Got screenshot request ack, status={}", payload[16]);  // 0 for success
+                return;
+            default:
+                LOG.warn("Unexpected screenshot payload byte {}", payload[2]);
+        }
+    }
+
+    private void parseAppList(final byte[] payload) {
+        apps.clear();
+
+        final byte[] appListStringBytes = ArrayUtils.subarray(payload, 16, payload.length);
+        final String appListString = new String(appListStringBytes);
+        final String[] appListSplit = appListString.split(";");
+        for (final String appString : appListSplit) {
+            if (StringUtils.isBlank(appString)) {
+                continue;
+            }
+
+            final String[] appSplit = appString.split("-", 2);
+            if (appSplit.length != 2) {
+                LOG.warn("Failed to parse {}", appString);
+                continue;
+            }
+
+            final int appId = Integer.parseInt(appSplit[0], 16);
+            final String appVersion = appSplit[1];
+
+            LOG.debug("Got app: '{}'", appString);
+
+            apps.add(new GBDeviceApp(
+                    UUID.fromString(String.format("%08x-0000-0000-0000-000000000000", appId)),
+                    "",
+                    "",
+                    appVersion,
+                    GBDeviceApp.Type.UNKNOWN // it might be an app or watchface
+            ));
+        }
+
+        // TODO broadcast something to update app manager
+    }
+
+    public void requestApps(final TransactionBuilder builder) {
+        LOG.info("Request apps");
+
+        final ByteBuffer buf = ByteBuffer.allocate(16).order(ByteOrder.LITTLE_ENDIAN);
+
+        buf.put(CMD_APPS);
+        buf.put(CMD_OUTGOING);
+        buf.put(CMD_APPS_LIST);
+        buf.put((byte) 0x00);
+
+        write(builder, buf.array());
+    }
+
+    public void requestApilevel() {
+        LOG.info("Request api level");
+
+        final ByteBuffer buf = ByteBuffer.allocate(16).order(ByteOrder.LITTLE_ENDIAN);
+
+        buf.put(CMD_APPS);
+        buf.put(CMD_OUTGOING);
+        buf.put(CMD_APPS_API_LEVEL);
+
+        write("request api level", buf.array());
+    }
+
+    public void deleteApp(final UUID uuid) {
+        deleteApp(Integer.parseInt(uuid.toString().split("-")[0], 16));
+    }
+
+    public void deleteApp(final int appId) {
+        LOG.info("Delete app {}", String.format("0x%08x", appId));
+
+        final ByteBuffer buf = ByteBuffer.allocate(20).order(ByteOrder.LITTLE_ENDIAN);
+
+        buf.put(CMD_APPS);
+        buf.put(CMD_OUTGOING);
+        buf.put(CMD_APPS_DELETE);
+        buf.put((byte) 0x00);
+        buf.putInt(0x00);
+        buf.putInt(0x00);
+        buf.putInt(0x00);
+        buf.putInt(appId);
+
+        write("delete app", buf.array());
+    }
+
+    public void requestScreenshot() {
+        LOG.info("Requesting screenshot");
+
+        final ByteBuffer buf = ByteBuffer.allocate(20).order(ByteOrder.LITTLE_ENDIAN);
+
+        buf.put(CMD_SCREENSHOT);
+        buf.put(CMD_OUTGOING);
+        buf.put(CMD_SCREENSHOT_REQUEST);
+        buf.put((byte) 0x00);
+
+        write("request screenshot", buf.array());
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsCalendarService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsCalendarService.java
new file mode 100644
index 0000000..84d104a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsCalendarService.java
@@ -0,0 +1,258 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.StandardCharsets;
+
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdatePreferences;
+import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.AbstractZeppOsService;
+import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
+
+public class ZeppOsCalendarService extends AbstractZeppOsService {
+    private static final Logger LOG = LoggerFactory.getLogger(ZeppOsCalendarService.class);
+
+    public static final short ENDPOINT = 0x0007;
+
+    public static final byte CMD_CAPABILITIES_REQUEST = 0x01;
+    public static final byte CMD_CAPABILITIES_RESPONSE = 0x02;
+    public static final byte CMD_EVENTS_REQUEST = 0x05;
+    public static final byte CMD_EVENTS_RESPONSE = 0x06;
+    public static final byte CMD_CREATE_EVENT = 0x07;
+    public static final byte CMD_CREATE_EVENT_ACK = 0x08;
+    public static final byte CMD_DELETE_EVENT = 0x09;
+    public static final byte CMD_DELETE_EVENT_ACK = 0x0a;
+
+    public static final String PREF_VERSION = "zepp_os_calendar_version";
+
+    private int version = -1;
+
+    public ZeppOsCalendarService(final ZeppOsSupport support) {
+        super(support, false);
+    }
+
+    @Override
+    public short getEndpoint() {
+        return ENDPOINT;
+    }
+
+    @Override
+    public void initialize(final TransactionBuilder builder) {
+        requestCapabilities(builder);
+    }
+
+    public void requestCapabilities(final TransactionBuilder builder) {
+        write(builder, CMD_CAPABILITIES_REQUEST);
+    }
+
+    @Override
+    public void handlePayload(final byte[] payload) {
+        switch (payload[0]) {
+            case CMD_CAPABILITIES_RESPONSE:
+                version = payload[1];
+                getSupport().evaluateGBDeviceEvent(new GBDeviceEventUpdatePreferences(PREF_VERSION, version));
+                if (version != 1 && version != 3) {
+                    LOG.warn("Unsupported calendar service version {}", version);
+                    return;
+                }
+                LOG.info("Calendar service version={}", version);
+                break;
+            case CMD_EVENTS_RESPONSE:
+                LOG.info("Got calendar events from band");
+                decodeAndUpdateCalendarEvents(payload);
+                return;
+            case CMD_CREATE_EVENT_ACK:
+                LOG.info("Calendar create event ACK, status = {}", payload[1]);
+                return;
+            case CMD_DELETE_EVENT_ACK:
+                LOG.info("Calendar delete event ACK, status = {}", payload[1]);
+                return;
+            default:
+                LOG.warn("Unexpected calendar payload byte {}", String.format("0x%02x", payload[0]));
+        }
+    }
+
+    public void requestCalendarEvents() {
+        LOG.info("Requesting calendar events from band");
+
+        write("request calendar events", new byte[]{CMD_EVENTS_REQUEST, 0x00, 0x00});
+    }
+
+    public void addEvent(final CalendarEventSpec calendarEventSpec) {
+        if (calendarEventSpec.type != CalendarEventSpec.TYPE_UNKNOWN) {
+            LOG.warn("Unsupported calendar event type {}", calendarEventSpec.type);
+            return;
+        }
+
+        LOG.info("Sending calendar event {} to band", calendarEventSpec.id);
+
+        int length = 34;
+        if (calendarEventSpec.title != null) {
+            length += calendarEventSpec.title.getBytes(StandardCharsets.UTF_8).length;
+        }
+        if (calendarEventSpec.description != null) {
+            length += calendarEventSpec.description.getBytes(StandardCharsets.UTF_8).length;
+        }
+
+        if (version == 3) {
+            if (calendarEventSpec.location != null) {
+                length += calendarEventSpec.location.getBytes(StandardCharsets.UTF_8).length;
+            }
+            // Extra null byte at the end
+            length++;
+        }
+
+        final ByteBuffer buf = ByteBuffer.allocate(length);
+        buf.order(ByteOrder.LITTLE_ENDIAN);
+        buf.put(CMD_CREATE_EVENT);
+        buf.putInt((int) calendarEventSpec.id);
+
+        if (calendarEventSpec.title != null) {
+            buf.put(calendarEventSpec.title.getBytes(StandardCharsets.UTF_8));
+        }
+        buf.put((byte) 0x00);
+
+        if (calendarEventSpec.description != null) {
+            buf.put(calendarEventSpec.description.getBytes(StandardCharsets.UTF_8));
+        }
+        buf.put((byte) 0x00);
+
+        buf.putInt(calendarEventSpec.timestamp);
+        buf.putInt(calendarEventSpec.timestamp + calendarEventSpec.durationInSeconds);
+
+        // Remind
+        if (calendarEventSpec.reminders != null && !calendarEventSpec.reminders.isEmpty()) {
+            buf.putInt((int) (calendarEventSpec.reminders.get(0) / 1000L));
+        } else {
+            buf.putInt(0);
+        }
+
+        // Repeat
+        buf.put((byte) 0x00); // ?
+        buf.put((byte) 0x00); // ?
+        buf.put((byte) 0x00); // ?
+        buf.put((byte) 0x00); // ?
+        // ?
+        buf.put((byte) 0xff); // ?
+        buf.put((byte) 0xff); // ?
+        buf.put((byte) 0xff); // ?
+        buf.put((byte) 0xff); // ?
+        buf.put(bool(calendarEventSpec.allDay));
+        buf.put((byte) 0x00); // ?
+        buf.put((byte) 130); // ?
+        buf.put((byte) 0x00); // ?
+        buf.put((byte) 0x00); // ?
+        buf.put((byte) 0x00); // ?
+        buf.put((byte) 0x00); // ?
+        // TODO: Description here
+
+        if (version == 3) {
+            if (calendarEventSpec.location != null) {
+                buf.put(calendarEventSpec.location.getBytes(StandardCharsets.UTF_8));
+            }
+            buf.put((byte) 0x00);
+        }
+
+        write("add calendar event", buf.array());
+    }
+
+    public void deleteEvent(final byte type, final long id) {
+        if (type != CalendarEventSpec.TYPE_UNKNOWN) {
+            LOG.warn("Unsupported calendar event type {}", type);
+            return;
+        }
+
+        LOG.info("Deleting calendar event {} from band", id);
+
+        final ByteBuffer buf = ByteBuffer.allocate(5);
+        buf.order(ByteOrder.LITTLE_ENDIAN);
+        buf.put(CMD_DELETE_EVENT);
+        buf.putInt((int) id);
+
+        write("delete calendar event", buf.array());
+    }
+
+    private void decodeAndUpdateCalendarEvents(final byte[] payload) {
+        final int numEvents = payload[1];
+        // FIXME there's a 0 after this, is it actually a 2-byte short?
+
+        if (payload.length < 1 + numEvents * 34) {
+            LOG.warn("Unexpected payload length of {} for {} calendar events", payload.length, numEvents);
+            return;
+        }
+
+        int i = 3;
+        while (i < payload.length) {
+            if (payload.length - i < 34) {
+                LOG.error("Not enough bytes remaining to parse a calendar event ({})", payload.length - i);
+                return;
+            }
+
+            final int eventId = BLETypeConversions.toUint32(payload, i);
+            i += 4;
+
+            final String title = StringUtils.untilNullTerminator(payload, i);
+            if (title == null) {
+                LOG.error("Failed to decode title");
+                return;
+            }
+            i += title.length() + 1;
+
+            final String description = StringUtils.untilNullTerminator(payload, i);
+            if (description == null) {
+                LOG.error("Failed to decode description");
+                return;
+            }
+            i += description.length() + 1;
+
+            final int startTime = BLETypeConversions.toUint32(payload, i);
+            i += 4;
+
+            final int endTime = BLETypeConversions.toUint32(payload, i);
+            i += 4;
+
+            final int reminderTime = BLETypeConversions.toUint32(payload, i);
+            i += 4;
+
+            // ? 00 00 00 00 ff ff ff ff
+            i += 12;
+
+            boolean allDay = (payload[i] == 0x01);
+            i++;
+
+            // ? 00 82 00 00 00 00
+            i += 6;
+
+            LOG.info("Calendar Event {}: {}", eventId, title);
+        }
+
+        if (i != payload.length) {
+            LOG.error("Unexpected calendar events payload trailer, {} bytes were not consumed", payload.length - i);
+            return;
+        }
+
+        // TODO update database?
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsCannedMessagesService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsCannedMessagesService.java
new file mode 100644
index 0000000..877d434
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsCannedMessagesService.java
@@ -0,0 +1,240 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services;
+
+import static org.apache.commons.lang3.ArrayUtils.subarray;
+
+import android.Manifest;
+import android.content.pm.PackageManager;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Map;
+
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventCallControl;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventNotificationControl;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdatePreferences;
+import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.AbstractZeppOsService;
+import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
+
+public class ZeppOsCannedMessagesService extends AbstractZeppOsService {
+    private static final Logger LOG = LoggerFactory.getLogger(ZeppOsCannedMessagesService.class);
+
+    public static final short ENDPOINT = 0x0013;
+
+    public static final byte CMD_CAPABILITIES_REQUEST = 0x01;
+    public static final byte CMD_CAPABILITIES_RESPONSE = 0x02;
+    public static final byte CMD_REQUEST = 0x03;
+    public static final byte CMD_RESPONSE = 0x04;
+    public static final byte CMD_SET = 0x05;
+    public static final byte CMD_SET_ACK = 0x06;
+    public static final byte CMD_DELETE = 0x07;
+    public static final byte CMD_DELETE_ACK = 0x08;
+    public static final byte CMD_REPLY_SMS = 0x0b;
+    public static final byte CMD_REPLY_SMS_ACK = 0x0c;
+    public static final byte CMD_REPLY_SMS_CHECK = 0x0d;
+    public static final byte CMD_REPLY_SMS_ALLOW = 0x0e;
+
+    public ZeppOsCannedMessagesService(final ZeppOsSupport support) {
+        super(support, false);
+    }
+
+    @Override
+    public short getEndpoint() {
+        return ENDPOINT;
+    }
+
+    @Override
+    public void handlePayload(final byte[] payload) {
+        switch (payload[0]) {
+            case CMD_RESPONSE:
+                decodeAndUpdateCannedMessagesResponse(payload);
+                return;
+            case CMD_SET_ACK:
+                LOG.info("Canned Message set ACK, status = {}", payload[1]);
+                return;
+            case CMD_DELETE_ACK:
+                LOG.info("Canned Message delete ACK, status = {}", payload[1]);
+                return;
+            case CMD_REPLY_SMS:
+                handleCannedSmsReply(payload);
+                return;
+            case CMD_REPLY_SMS_CHECK:
+                LOG.info("Canned Message reply SMS check");
+                final boolean canSendSms;
+                // TODO place this behind a setting as well?
+                if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
+                    canSendSms = getContext().checkSelfPermission(Manifest.permission.SEND_SMS) == PackageManager.PERMISSION_GRANTED;
+                } else {
+                    canSendSms = true;
+                }
+                sendCannedSmsReplyAllow(canSendSms);
+                return;
+            default:
+                LOG.warn("Unexpected canned messages payload byte {}", String.format("0x%02x", payload[0]));
+        }
+    }
+
+    public void setCannedMessages(final CannedMessagesSpec cannedMessagesSpec) {
+        if (cannedMessagesSpec.type != CannedMessagesSpec.TYPE_GENERIC) {
+            LOG.warn("Got unsupported canned messages type: {}", cannedMessagesSpec.type);
+            return;
+        }
+
+        final TransactionBuilder builder = new TransactionBuilder("set canned messages");
+
+        for (int i = 0; i < 16; i++) {
+            LOG.debug("Deleting canned message {}", i);
+            final ByteBuffer buf = ByteBuffer.allocate(5);
+            buf.order(ByteOrder.LITTLE_ENDIAN);
+            buf.put(CMD_DELETE);
+            buf.putInt(i);
+            write(builder, buf.array());
+        }
+
+        int i = 0;
+        for (String cannedMessage : cannedMessagesSpec.cannedMessages) {
+            cannedMessage = StringUtils.truncate(cannedMessage, 140);
+            LOG.debug("Setting canned message {} = '{}'", i, cannedMessage);
+
+            final int length = cannedMessage.getBytes(StandardCharsets.UTF_8).length + 7;
+            final ByteBuffer buf = ByteBuffer.allocate(length);
+            buf.order(ByteOrder.LITTLE_ENDIAN);
+            buf.put(CMD_SET);
+            buf.putInt(i++);
+            buf.put((byte) cannedMessage.getBytes(StandardCharsets.UTF_8).length);
+            buf.put((byte) 0x00);
+            buf.put(cannedMessage.getBytes(StandardCharsets.UTF_8));
+            write(builder, buf.array());
+        }
+        builder.queue(getSupport().getQueue());
+    }
+
+    public void requestCannedMessages(final TransactionBuilder builder) {
+        LOG.info("Requesting canned messages");
+
+        write(builder, new byte[]{CMD_REQUEST});
+    }
+
+    private void sendCannedSmsReplyAllow(final boolean allowed) {
+        LOG.info("Sending SMS reply allowed = {}", allowed);
+
+        write("allow sms reply", new byte[]{CMD_REPLY_SMS_ALLOW, bool(allowed)});
+    }
+
+    private void handleCannedSmsReply(final byte[] payload) {
+        LOG.info("Canned Message SMS reply");
+
+        final String phoneNumber = StringUtils.untilNullTerminator(payload, 1);
+        if (phoneNumber == null || phoneNumber.isEmpty()) {
+            LOG.warn("No phone number for SMS reply");
+            ackCannedSmsReply(false);
+            return;
+        }
+
+        final int messageLength = payload[phoneNumber.length() + 6] & 0xff;
+        if (phoneNumber.length() + 8 + messageLength != payload.length) {
+            LOG.warn("Unexpected message or payload lengths ({} / {})", messageLength, payload.length);
+            ackCannedSmsReply(false);
+            return;
+        }
+
+        final String message = new String(payload, phoneNumber.length() + 8, messageLength);
+        if (StringUtils.isNullOrEmpty(message)) {
+            LOG.warn("No message for SMS reply");
+            ackCannedSmsReply(false);
+            return;
+        }
+
+        LOG.debug("Sending SMS message '{}' to number '{}' and rejecting call", message, phoneNumber);
+        final GBDeviceEventNotificationControl devEvtNotificationControl = new GBDeviceEventNotificationControl();
+        devEvtNotificationControl.handle = -1;
+        devEvtNotificationControl.phoneNumber = phoneNumber;
+        devEvtNotificationControl.reply = message;
+        devEvtNotificationControl.event = GBDeviceEventNotificationControl.Event.REPLY;
+        evaluateGBDeviceEvent(devEvtNotificationControl);
+
+        final GBDeviceEventCallControl rejectCallCmd = new GBDeviceEventCallControl(GBDeviceEventCallControl.Event.REJECT);
+        evaluateGBDeviceEvent(rejectCallCmd);
+
+        ackCannedSmsReply(true); // FIXME probably premature
+    }
+
+    private void ackCannedSmsReply(final boolean success) {
+        LOG.info("Acknowledging SMS reply, success = {}", success);
+
+        write("ack sms reply", new byte[]{CMD_REPLY_SMS_ACK, bool(success)});
+    }
+
+    private void decodeAndUpdateCannedMessagesResponse(final byte[] payload) {
+        final int numberMessages = payload[1] & 0xff;
+
+        LOG.info("Got {} canned messages", numberMessages);
+
+        final GBDeviceEventUpdatePreferences gbDeviceEventUpdatePreferences = new GBDeviceEventUpdatePreferences();
+        final Map<Integer, String> cannedMessages = new HashMap<>();
+
+        int pos = 3;
+        for (int i = 0; i < numberMessages; i++) {
+            if (pos + 4 >= payload.length) {
+                LOG.warn("Unexpected end of payload while parsing message {} at pos {}", i, pos);
+                return;
+            }
+
+            final int messageId = BLETypeConversions.toUint32(subarray(payload, pos, pos + 4));
+            final int messageLength = payload[pos + 4] & 0xff;
+
+            if (pos + 6 + messageLength > payload.length) {
+                LOG.warn("Unexpected end of payload for message of length {} while parsing message {} at pos {}", messageLength, i, pos);
+                return;
+            }
+
+            final String messageText = new String(subarray(payload, pos + 6, pos + 6 + messageLength));
+
+            LOG.debug("Canned message {}: {}", String.format("0x%x", messageId), messageText);
+
+            final int cannedMessagePrefId = i + 1;
+            if (cannedMessagePrefId > 16) {
+                LOG.warn("Canned message ID {} is out of range", cannedMessagePrefId);
+            } else {
+                cannedMessages.put(cannedMessagePrefId, messageText);
+            }
+
+            pos += messageLength + 6;
+        }
+
+        for (int i = 1; i <= 16; i++) {
+            String message = cannedMessages.get(i);
+            if (StringUtils.isEmpty(message)) {
+                message = null;
+            }
+
+            gbDeviceEventUpdatePreferences.withPreference("canned_reply_" + i, message);
+        }
+
+        evaluateGBDeviceEvent(gbDeviceEventUpdatePreferences);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsConfigService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsConfigService.java
new file mode 100644
index 0000000..407d416
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsConfigService.java
@@ -0,0 +1,1850 @@
+/*  Copyright (C) 2022-2024 Andreas Shimokawa, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services;
+
+import static org.apache.commons.lang3.ArrayUtils.subarray;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.*;
+import static nodomain.freeyourgadget.gadgetbridge.capabilities.password.PasswordCapabilityImpl.PREF_PASSWORD;
+import static nodomain.freeyourgadget.gadgetbridge.capabilities.password.PasswordCapabilityImpl.PREF_PASSWORD_ENABLED;
+import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_EXPOSE_HR_THIRDPARTY;
+import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_NIGHT_MODE;
+import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_NIGHT_MODE_END;
+import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_NIGHT_MODE_START;
+
+import android.text.TextUtils;
+import android.widget.Toast;
+
+import androidx.annotation.Nullable;
+
+import org.apache.commons.lang3.ArrayUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.StandardCharsets;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.BuildConfig;
+import nodomain.freeyourgadget.gadgetbridge.activities.SettingsActivity;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsUtils;
+import nodomain.freeyourgadget.gadgetbridge.capabilities.GpsCapability;
+import nodomain.freeyourgadget.gadgetbridge.capabilities.WorkoutDetectionCapability;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdatePreferences;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.ActivateDisplayOnLift;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.ActivateDisplayOnLiftSensitivity;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.AlwaysOnDisplay;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.DoNotDisturb;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsMenuType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiLanguageType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.AbstractZeppOsService;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+import nodomain.freeyourgadget.gadgetbridge.util.MapUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
+
+public class ZeppOsConfigService extends AbstractZeppOsService {
+    private static final Logger LOG = LoggerFactory.getLogger(ZeppOsConfigService.class);
+
+    private static final short ENDPOINT = 0x000a;
+
+    private static final byte CMD_CAPABILITIES_REQUEST = 0x01;
+    private static final byte CMD_CAPABILITIES_RESPONSE = 0x02;
+    private static final byte CMD_REQUEST = 0x03;
+    private static final byte CMD_RESPONSE = 0x04;
+    private static final byte CMD_SET = 0x05;
+    private static final byte CMD_ACK = 0x06;
+
+    private final Map<ConfigGroup, Byte> mGroupVersions = new HashMap<>();
+
+    public ZeppOsConfigService(final ZeppOsSupport support) {
+        super(support, true);
+    }
+
+    @Override
+    public short getEndpoint() {
+        return ENDPOINT;
+    }
+
+    @Override
+    public void handlePayload(final byte[] payload) {
+        switch (payload[0]) {
+            case CMD_CAPABILITIES_RESPONSE:
+                handleCapabilitiesResponse(payload);
+                return;
+
+            case CMD_ACK:
+                LOG.info("Configuration ACK, status = {}", payload[1]);
+                return;
+
+            case CMD_RESPONSE:
+                if (payload[1] != 1) {
+                    LOG.warn("Configuration response not success: {}", payload[1]);
+                    return;
+                }
+
+                handle2021ConfigResponse(payload);
+                return;
+            default:
+                LOG.warn("Unexpected configuration payload byte {}", String.format("0x%02x", payload[0]));
+        }
+    }
+
+    @Override
+    public void initialize(final TransactionBuilder builder) {
+        write(builder, CMD_CAPABILITIES_REQUEST);
+        requestAllConfigs(builder);
+    }
+
+    @Override
+    public boolean onSendConfiguration(final String prefKey, Prefs prefs) {
+        if (!PREF_TO_CONFIG.containsKey(prefKey)) {
+            return false;
+        }
+
+        final ConfigSetter configSetter = new ConfigSetter();
+        if (setConfig(prefs, prefKey, configSetter)) {
+            try {
+                // If the ConfigSetter was able to set the config, just write it and return
+                final TransactionBuilder builder = new TransactionBuilder("send config " + prefKey);
+                configSetter.write(builder);
+                builder.queue(getSupport().getQueue());
+            } catch (final Exception e) {
+                GB.toast("Error setting configuration", Toast.LENGTH_LONG, GB.ERROR, e);
+            }
+
+            return true;
+        }
+
+        return false;
+    }
+
+    private void handleCapabilitiesResponse(final byte[] payload) {
+        final int version = payload[1] & 0xFF;
+        LOG.info("Got config service version={}", version);
+        if (version > 3) {
+            LOG.error("Unsupported config service version {}", version);
+            return;
+        }
+        final int numGroups = payload[2] & 0xFF;
+        if (payload.length != numGroups + 3) {
+            LOG.error("Unexpected config capabilities response length {} for {} groups", payload.length, numGroups);
+            return;
+        }
+
+        for (int i = 0; i < numGroups; i++) {
+            final ConfigGroup configGroup = ConfigGroup.fromValue(payload[3 + i]);
+            LOG.debug("Got supported config group {}: {}", String.format("0x%02x", payload[3 + i]), configGroup);
+        }
+
+        // TODO: We should only request supported config groups
+    }
+
+    private boolean sentFitnessGoal = false;
+
+    private void handle2021ConfigResponse(final byte[] payload) {
+        final ConfigGroup configGroup = ConfigGroup.fromValue(payload[2]);
+        if (configGroup == null) {
+            LOG.warn("Unknown config type {}", String.format("0x%02x", payload[2]));
+            return;
+        }
+
+        final byte version = payload[3];
+        if (configGroup.getVersion() != version) {
+            // Special case for HEALTH, where we actually support version 1 as well
+            // TODO: Support multiple versions in a cleaner way...
+            if (!(configGroup == ConfigGroup.HEALTH && version == 1)) {
+                LOG.warn("Unexpected version {} for {}", String.format("0x%02x", version), configGroup);
+                return;
+            }
+        }
+        mGroupVersions.put(configGroup, version);
+
+        final boolean includesConstraints = payload[4] == 0x01;
+
+        int numConfigs = payload[5] & 0xff;
+
+        LOG.info("Got {} configs for {} version {}", numConfigs, configGroup, version);
+
+        final Map<String, Object> prefs = new ZeppOsConfigService.ConfigParser(configGroup, includesConstraints)
+                .parse(numConfigs, subarray(payload, 6, payload.length));
+
+        if (prefs == null) {
+            return;
+        }
+
+        final GBDeviceEventUpdatePreferences eventUpdatePreferences = new GBDeviceEventUpdatePreferences(prefs);
+        getSupport().evaluateGBDeviceEvent(eventUpdatePreferences);
+
+        if (getSupport().getDevice().isInitialized()) {
+            if (prefs.containsKey(PREF_LANGUAGE) && prefs.get(PREF_LANGUAGE).equals(PREF_LANGUAGE_AUTO)) {
+                // Band is reporting automatic language, we need to send the actual language
+                getSupport().onSendConfiguration(PREF_LANGUAGE);
+            }
+            if (prefs.containsKey(PREF_TIMEFORMAT) && prefs.get(PREF_TIMEFORMAT).equals(PREF_TIMEFORMAT_AUTO)) {
+                // Band is reporting automatic time format, we need to send the actual time format
+                getSupport().onSendConfiguration(PREF_TIMEFORMAT);
+            }
+        }
+
+        if (configGroup == ConfigGroup.HEALTH && !sentFitnessGoal) {
+            // We need to send the fitness goal after we got the protocol version
+            getSupport().onSendConfiguration(PREF_USER_FITNESS_GOAL);
+            sentFitnessGoal = true;
+        }
+    }
+
+    public void requestAllConfigs(final TransactionBuilder builder) {
+        for (final ConfigGroup configGroup : ConfigGroup.values()) {
+            requestConfig(builder, configGroup);
+        }
+    }
+
+    public void requestConfig(final TransactionBuilder builder, final ConfigGroup config) {
+        requestConfig(builder, config, true, ZeppOsConfigService.ConfigArg.getAllArgsForConfigGroup(config));
+    }
+
+    public void requestConfig(final TransactionBuilder builder,
+                              final ConfigGroup config,
+                              final boolean includeConstraints,
+                              final List<ZeppOsConfigService.ConfigArg> args) {
+        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+
+        baos.write(CMD_REQUEST);
+        baos.write((byte) (includeConstraints ? 1 : 0));
+        baos.write(config.getValue());
+        baos.write(args.size());
+        for (final ZeppOsConfigService.ConfigArg arg : args) {
+            baos.write(arg.getCode());
+        }
+
+        write(builder, baos.toByteArray());
+    }
+
+    public enum ConfigGroup {
+        AGPS(0x00, 0x01),
+        DISPLAY(0x01, 0x02),
+        // TODO 0x02
+        SOUND_AND_VIBRATION(0x03, 0x02),
+        LOCKSCREEN(0x04, 0x01),
+        WEARING_DIRECTION(0x05, 0x02),
+        OFFLINE_VOICE(0x06, 0x02),
+        LANGUAGE(0x07, 0x02),
+        HEALTH(0x08, 0x02),
+        WORKOUT(0x09, 0x01),
+        SYSTEM(0x0a, 0x01),
+        BLUETOOTH(0x0b, 0x01),
+        ;
+
+        private final byte value;
+        private final byte version;
+
+        ConfigGroup(int value, int version) {
+            this.value = (byte) value;
+            this.version = (byte) version;
+        }
+
+        public byte getValue() {
+            return value;
+        }
+
+        public byte getVersion() {
+            return version;
+        }
+
+        public static ConfigGroup fromValue(final byte value) {
+            for (final ConfigGroup configGroup : values()) {
+                if (configGroup.getValue() == value) {
+                    return configGroup;
+                }
+            }
+
+            return null;
+        }
+    }
+
+    public enum ConfigType {
+        BOOL(0x0b),
+        STRING(0x20),
+        STRING_LIST(0x21),
+        SHORT(0x01),
+        INT(0x03),
+        BYTE(0x10),
+        BYTE_LIST(0x11),
+        DATETIME_HH_MM(0x30),
+        TIMESTAMP_MILLIS(0x40),
+        ;
+
+        private final byte value;
+
+        ConfigType(int value) {
+            this.value = (byte) value;
+        }
+
+        public byte getValue() {
+            return value;
+        }
+
+        public static ConfigType fromValue(final byte value) {
+            for (final ConfigType configType : values()) {
+                if (configType.getValue() == value) {
+                    return configType;
+                }
+            }
+
+            return null;
+        }
+    }
+
+    public enum ConfigArg {
+        // AGPS
+        AGPS_UPDATE_TIME(ConfigGroup.AGPS, ConfigType.TIMESTAMP_MILLIS, 0x09, PREF_AGPS_UPDATE_TIME),
+        AGPS_EXPIRE_TIME(ConfigGroup.AGPS, ConfigType.TIMESTAMP_MILLIS, 0x0a, PREF_AGPS_EXPIRE_TIME),
+
+        // Display
+        SCREEN_AUTO_BRIGHTNESS(ConfigGroup.DISPLAY, ConfigType.BOOL, 0x01, PREF_SCREEN_AUTO_BRIGHTNESS),
+        SCREEN_BRIGHTNESS(ConfigGroup.DISPLAY, ConfigType.SHORT, 0x02, PREF_SCREEN_BRIGHTNESS),
+        SCREEN_TIMEOUT(ConfigGroup.DISPLAY, ConfigType.BYTE, 0x03, PREF_SCREEN_TIMEOUT),
+        ALWAYS_ON_DISPLAY_MODE(ConfigGroup.DISPLAY, ConfigType.BYTE, 0x04, PREF_ALWAYS_ON_DISPLAY_MODE),
+        ALWAYS_ON_DISPLAY_SCHEDULED_START(ConfigGroup.DISPLAY, ConfigType.DATETIME_HH_MM, 0x05, PREF_ALWAYS_ON_DISPLAY_START),
+        ALWAYS_ON_DISPLAY_SCHEDULED_END(ConfigGroup.DISPLAY, ConfigType.DATETIME_HH_MM, 0x06, PREF_ALWAYS_ON_DISPLAY_END),
+        LIFT_WRIST_MODE(ConfigGroup.DISPLAY, ConfigType.BYTE, 0x08, PREF_ACTIVATE_DISPLAY_ON_LIFT),
+        LIFT_WRIST_SCHEDULED_START(ConfigGroup.DISPLAY, ConfigType.DATETIME_HH_MM, 0x09, PREF_DISPLAY_ON_LIFT_START),
+        LIFT_WRIST_SCHEDULED_END(ConfigGroup.DISPLAY, ConfigType.DATETIME_HH_MM, 0x0a, PREF_DISPLAY_ON_LIFT_END),
+        LIFT_WRIST_RESPONSE_SENSITIVITY(ConfigGroup.DISPLAY, ConfigType.BYTE, 0x0b, PREF_DISPLAY_ON_LIFT_SENSITIVITY),
+        SCREEN_ON_ON_NOTIFICATIONS(ConfigGroup.DISPLAY, ConfigType.BOOL, 0x0c, PREF_SCREEN_ON_ON_NOTIFICATIONS),
+        WORKOUT_KEEP_SCREEN_ON(ConfigGroup.DISPLAY, ConfigType.BOOL, 0x0d, PREF_WORKOUT_KEEP_SCREEN_ON),
+        ALWAYS_ON_DISPLAY_FOLLOW_WATCHFACE(ConfigGroup.DISPLAY, ConfigType.BOOL, 0x0e, PREF_ALWAYS_ON_DISPLAY_FOLLOW_WATCHFACE),
+        ALWAYS_ON_DISPLAY_STYLE(ConfigGroup.DISPLAY, ConfigType.STRING_LIST, 0x0f, PREF_ALWAYS_ON_DISPLAY_STYLE),
+
+        // Sound and Vibration
+        VOLUME(ConfigGroup.SOUND_AND_VIBRATION, ConfigType.SHORT, 0x02, PREF_VOLUME),
+        CROWN_VIBRATION(ConfigGroup.SOUND_AND_VIBRATION, ConfigType.BOOL, 0x06, PREF_CROWN_VIBRATION),
+        ALERT_TONE(ConfigGroup.SOUND_AND_VIBRATION, ConfigType.BOOL, 0x07, PREF_ALERT_TONE),
+        COVER_TO_MUTE(ConfigGroup.SOUND_AND_VIBRATION, ConfigType.BOOL, 0x08, PREF_COVER_TO_MUTE),
+        VIBRATE_FOR_ALERT(ConfigGroup.SOUND_AND_VIBRATION, ConfigType.BOOL, 0x09, PREF_VIBRATE_FOR_ALERT),
+        TEXT_TO_SPEECH(ConfigGroup.SOUND_AND_VIBRATION, ConfigType.BOOL, 0x0a, PREF_TEXT_TO_SPEECH),
+
+        // Wearing Direction
+        WEARING_DIRECTION_BUTTONS(ConfigGroup.WEARING_DIRECTION, ConfigType.BYTE, 0x02, PREF_WEARDIRECTION),
+
+        // Offline Voice
+        OFFLINE_VOICE_RESPOND_TURN_WRIST(ConfigGroup.OFFLINE_VOICE, ConfigType.BOOL, 0x01, PREF_OFFLINE_VOICE_RESPOND_TURN_WRIST),
+        OFFLINE_VOICE_RESPOND_SCREEN_ON(ConfigGroup.OFFLINE_VOICE, ConfigType.BOOL, 0x02, PREF_OFFLINE_VOICE_RESPOND_SCREEN_ON),
+        OFFLINE_VOICE_RESPONSE_DURING_SCREEN_LIGHTING(ConfigGroup.OFFLINE_VOICE, ConfigType.BOOL, 0x03, PREF_OFFLINE_VOICE_RESPONSE_DURING_SCREEN_LIGHTING),
+        OFFLINE_VOICE_LANGUAGE(ConfigGroup.OFFLINE_VOICE, ConfigType.BYTE, 0x04, PREF_OFFLINE_VOICE_LANGUAGE),
+
+        // Lock Screen
+        PASSWORD_ENABLED(ConfigGroup.LOCKSCREEN, ConfigType.BOOL, 0x01, PREF_PASSWORD_ENABLED),
+        PASSWORD_TEXT(ConfigGroup.LOCKSCREEN, ConfigType.STRING, 0x02, PREF_PASSWORD),
+
+        // Language
+        LANGUAGE(ConfigGroup.LANGUAGE, ConfigType.BYTE, 0x01, PREF_LANGUAGE),
+        LANGUAGE_FOLLOW_PHONE(ConfigGroup.LANGUAGE, ConfigType.BOOL, 0x02, null /* special case, handled below */),
+
+        // Health
+        HEART_RATE_ALL_DAY_MONITORING(ConfigGroup.HEALTH, ConfigType.BYTE, 0x01, PREF_HEARTRATE_MEASUREMENT_INTERVAL),
+        HEART_RATE_HIGH_ALERTS(ConfigGroup.HEALTH, ConfigType.BYTE, 0x02, PREF_HEARTRATE_ALERT_HIGH_THRESHOLD),
+        HEART_RATE_LOW_ALERTS(ConfigGroup.HEALTH, ConfigType.BYTE, 0x03, PREF_HEARTRATE_ALERT_LOW_THRESHOLD),
+        HEART_RATE_ACTIVITY_MONITORING(ConfigGroup.HEALTH, ConfigType.BOOL, 0x04, PREF_HEARTRATE_ACTIVITY_MONITORING),
+        THIRD_PARTY_HR_SHARING(ConfigGroup.HEALTH, ConfigType.BOOL, 0x05, PREF_EXPOSE_HR_THIRDPARTY),
+        SLEEP_HIGH_ACCURACY_MONITORING(ConfigGroup.HEALTH, ConfigType.BOOL, 0x11, PREF_HEARTRATE_USE_FOR_SLEEP_DETECTION),
+        SLEEP_BREATHING_QUALITY_MONITORING(ConfigGroup.HEALTH, ConfigType.BOOL, 0x12, PREF_HEARTRATE_SLEEP_BREATHING_QUALITY_MONITORING),
+        STRESS_MONITORING(ConfigGroup.HEALTH, ConfigType.BOOL, 0x13, PREF_HEARTRATE_STRESS_MONITORING),
+        STRESS_RELAXATION_REMINDER(ConfigGroup.HEALTH, ConfigType.BOOL, 0x14, PREF_HEARTRATE_STRESS_RELAXATION_REMINDER),
+        SPO2_ALL_DAY_MONITORING(ConfigGroup.HEALTH, ConfigType.BOOL, 0x31, PREF_SPO2_ALL_DAY_MONITORING),
+        SPO2_LOW_ALERT(ConfigGroup.HEALTH, ConfigType.BYTE, 0x32, PREF_SPO2_LOW_ALERT_THRESHOLD),
+        FITNESS_GOAL_NOTIFICATION(ConfigGroup.HEALTH, ConfigType.BOOL, 0x51, PREF_USER_FITNESS_GOAL_NOTIFICATION),
+        FITNESS_GOAL_STEPS(ConfigGroup.HEALTH, null /* Special case, handled below */, 0x52, null), // TODO needs to be handled globally
+        FITNESS_GOAL_CALORIES(ConfigGroup.HEALTH, ConfigType.SHORT, 0x53, null), // TODO needs to be handled globally
+        FITNESS_GOAL_WEIGHT(ConfigGroup.HEALTH, ConfigType.SHORT, 0x54, null), // TODO needs to be handled globally
+        FITNESS_GOAL_SLEEP(ConfigGroup.HEALTH, ConfigType.SHORT, 0x55, null), // TODO needs to be handled globally
+        FITNESS_GOAL_STANDING_TIME(ConfigGroup.HEALTH, ConfigType.SHORT, 0x56, null), // TODO needs to be handled globally
+        FITNESS_GOAL_FAT_BURN_TIME(ConfigGroup.HEALTH, ConfigType.SHORT, 0x57, null), // TODO needs to be handled globally
+        INACTIVITY_WARNINGS_ENABLED(ConfigGroup.HEALTH, ConfigType.BOOL, 0x41, PREF_INACTIVITY_ENABLE),
+        INACTIVITY_WARNINGS_SCHEDULED_START(ConfigGroup.HEALTH, ConfigType.DATETIME_HH_MM, 0x42, PREF_INACTIVITY_START),
+        INACTIVITY_WARNINGS_SCHEDULED_END(ConfigGroup.HEALTH, ConfigType.DATETIME_HH_MM, 0x43, PREF_INACTIVITY_END),
+        INACTIVITY_WARNINGS_DND_ENABLED(ConfigGroup.HEALTH, ConfigType.BOOL, 0x44, PREF_INACTIVITY_DND),
+        INACTIVITY_WARNINGS_DND_SCHEDULED_START(ConfigGroup.HEALTH, ConfigType.DATETIME_HH_MM, 0x45, PREF_INACTIVITY_DND_START),
+        INACTIVITY_WARNINGS_DND_SCHEDULED_END(ConfigGroup.HEALTH, ConfigType.DATETIME_HH_MM, 0x46, PREF_INACTIVITY_DND_END),
+
+        // Workout
+        WORKOUT_GPS_PRESET(ConfigGroup.WORKOUT, ConfigType.BYTE, 0x20, PREF_GPS_MODE_PRESET),
+        WORKOUT_GPS_BAND(ConfigGroup.WORKOUT, ConfigType.BYTE, 0x21, PREF_GPS_BAND),
+        WORKOUT_GPS_COMBINATION(ConfigGroup.WORKOUT, ConfigType.BYTE, 0x22, PREF_GPS_COMBINATION),
+        WORKOUT_GPS_SATELLITE_SEARCH(ConfigGroup.WORKOUT, ConfigType.BYTE, 0x23, PREF_GPS_SATELLITE_SEARCH),
+        WORKOUT_AGPS_EXPIRY_REMINDER_ENABLED(ConfigGroup.WORKOUT, ConfigType.BOOL, 0x30, PREF_AGPS_EXPIRY_REMINDER_ENABLED),
+        WORKOUT_AGPS_EXPIRY_REMINDER_TIME(ConfigGroup.WORKOUT, ConfigType.DATETIME_HH_MM, 0x31, PREF_AGPS_EXPIRY_REMINDER_TIME),
+        WORKOUT_DETECTION_CATEGORY(ConfigGroup.WORKOUT, ConfigType.BYTE_LIST, 0x40, PREF_WORKOUT_DETECTION_CATEGORIES),
+        WORKOUT_DETECTION_ALERT(ConfigGroup.WORKOUT, ConfigType.BOOL, 0x41, PREF_WORKOUT_DETECTION_ALERT),
+        WORKOUT_DETECTION_SENSITIVITY(ConfigGroup.WORKOUT, ConfigType.BYTE, 0x42, PREF_WORKOUT_DETECTION_SENSITIVITY),
+        WORKOUT_POOL_SWIMMING_SIZE(ConfigGroup.WORKOUT, ConfigType.BYTE, 0x51, null), // TODO ?
+
+        // System
+        TIME_FORMAT(ConfigGroup.SYSTEM, ConfigType.BYTE, 0x01, PREF_TIMEFORMAT),
+        DATE_FORMAT(ConfigGroup.SYSTEM, ConfigType.STRING, 0x02, PREF_DATEFORMAT),
+        DND_MODE(ConfigGroup.SYSTEM, ConfigType.BYTE, 0x0a, PREF_DO_NOT_DISTURB),
+        DND_SCHEDULED_START(ConfigGroup.SYSTEM, ConfigType.DATETIME_HH_MM, 0x0b, PREF_DO_NOT_DISTURB_START),
+        DND_SCHEDULED_END(ConfigGroup.SYSTEM, ConfigType.DATETIME_HH_MM, 0x0c, PREF_DO_NOT_DISTURB_END),
+        CALL_DELAY(ConfigGroup.SYSTEM, ConfigType.SHORT, 0x11, PREF_NOTIFICATION_DELAY_CALLS),
+        TEMPERATURE_UNIT(ConfigGroup.SYSTEM, ConfigType.BYTE, 0x12, SettingsActivity.PREF_MEASUREMENT_SYSTEM),
+        TIME_FORMAT_FOLLOWS_PHONE(ConfigGroup.SYSTEM, ConfigType.BOOL, 0x13, null /* special case, handled below */),
+        UPPER_BUTTON_LONG_PRESS(ConfigGroup.SYSTEM, ConfigType.STRING_LIST, 0x15, PREF_UPPER_BUTTON_LONG_PRESS),
+        LOWER_BUTTON_PRESS(ConfigGroup.SYSTEM, ConfigType.STRING_LIST, 0x16, PREF_LOWER_BUTTON_SHORT_PRESS),
+        DISPLAY_CALLER(ConfigGroup.SYSTEM, ConfigType.BOOL, 0x18, PREF_DISPLAY_CALLER),
+        NIGHT_MODE_MODE(ConfigGroup.SYSTEM, ConfigType.BYTE, 0x1b, PREF_NIGHT_MODE),
+        NIGHT_MODE_SCHEDULED_START(ConfigGroup.SYSTEM, ConfigType.DATETIME_HH_MM, 0x1c, PREF_NIGHT_MODE_START),
+        NIGHT_MODE_SCHEDULED_END(ConfigGroup.SYSTEM, ConfigType.DATETIME_HH_MM, 0x1d, PREF_NIGHT_MODE_END),
+        SLEEP_MODE_SLEEP_SCREEN(ConfigGroup.SYSTEM, ConfigType.BOOL, 0x21, PREF_SLEEP_MODE_SLEEP_SCREEN),
+        SLEEP_MODE_SMART_ENABLE(ConfigGroup.SYSTEM, ConfigType.BOOL, 0x22, PREF_SLEEP_MODE_SMART_ENABLE),
+        CAMERA_REMOTE(ConfigGroup.SYSTEM, ConfigType.BOOL, 0x23, PREF_CAMERA_REMOTE),
+
+        // Bluetooth
+        BLUETOOTH_CONNECTED_ADVERTISING(ConfigGroup.BLUETOOTH, ConfigType.BOOL, 0x02, PREF_BT_CONNECTED_ADVERTISEMENT),
+        ;
+
+        private final ConfigGroup configGroup;
+        private final ConfigType configType;
+        private final byte code;
+        private final String prefKey;
+
+        ConfigArg(final ConfigGroup configGroup, final ConfigType configType, final int code, final String prefKey) {
+            this.configGroup = configGroup;
+            this.configType = configType;
+            this.code = (byte) code;
+            this.prefKey = prefKey;
+        }
+
+        public ConfigGroup getConfigGroup() {
+            return configGroup;
+        }
+
+        public ConfigType getConfigType(@Nullable final Map<ConfigGroup, Byte> groupVersions) {
+            if (this == FITNESS_GOAL_STEPS) {
+                if (groupVersions == null) {
+                    return ConfigType.INT;
+                }
+
+                final Byte groupVersion = groupVersions.get(getConfigGroup());
+                if (groupVersion == null) {
+                    LOG.error("Version for {} is not known", getConfigGroup());
+                    return null;
+                }
+
+                switch (groupVersion) {
+                    case 0x01:
+                        return ConfigType.SHORT;
+                    case 0x02:
+                    default:
+                        return ConfigType.INT;
+                }
+            }
+
+            return configType;
+        }
+
+        public byte getCode() {
+            return code;
+        }
+
+        public String getPrefKey() {
+            return prefKey;
+        }
+
+        public static ConfigArg fromCode(final ConfigGroup configGroup, final byte code) {
+            for (final ZeppOsConfigService.ConfigArg arg : values()) {
+                if (arg.getConfigGroup().equals(configGroup) && arg.getCode() == code) {
+                    return arg;
+                }
+            }
+            return null;
+        }
+
+        public static List<ConfigArg> getAllArgsForConfigGroup(final ConfigGroup configGroup) {
+            final List<ZeppOsConfigService.ConfigArg> configArgs = new ArrayList<>();
+            for (final ZeppOsConfigService.ConfigArg arg : values()) {
+                if (arg.getConfigGroup().equals(configGroup)) {
+                    configArgs.add(arg);
+                }
+            }
+            return configArgs;
+        }
+    }
+
+    /**
+     * Map of pref key to config.
+     */
+    private static final Map<String, ConfigArg> PREF_TO_CONFIG = new HashMap<String, ConfigArg>() {{
+        for (final ConfigArg arg : ConfigArg.values()) {
+            if (arg.getPrefKey() != null) {
+                if (containsKey(arg.getPrefKey())) {
+                    LOG.error("Duplicate config preference key: {}", arg);
+                    continue;
+                }
+                put(arg.getPrefKey(), arg);
+            }
+        }
+    }};
+
+    /**
+     * Updates a {@link ConfigSetter} with a preference. Default values don't really matter - if we're
+     * setting this preference, it's because the device reported it, along with the current value, so we
+     * shouldn't need a default unless there's a bug.
+     *
+     * @return true if the {@link ConfigSetter} was updated for this preference key
+     */
+    public boolean setConfig(final Prefs prefs, final String key, final ConfigSetter setter) {
+        final ConfigArg configArg = PREF_TO_CONFIG.get(key);
+        if (configArg == null) {
+            LOG.error("Unknown pref key {}", key);
+            return false;
+        }
+
+        switch (configArg.getConfigType(mGroupVersions)) {
+            case BOOL:
+                setter.setBoolean(configArg, prefs.getBoolean(key, false));
+                return true;
+            case STRING:
+                final String encodedString = encodeString(configArg, prefs.getString(key, null));
+                if (encodedString != null) {
+                    setter.setString(configArg, encodedString);
+                    return true;
+                }
+                break;
+            case STRING_LIST:
+                final String encodedStringList = encodeString(configArg, prefs.getString(key, null));
+                if (encodedStringList != null) {
+                    setter.setStringList(configArg, encodedStringList);
+                    return true;
+                }
+                break;
+            case SHORT:
+                setter.setShort(configArg, (short) prefs.getInt(key, 0));
+                return true;
+            case INT:
+                setter.setInt(configArg, prefs.getInt(key, 0));
+                return true;
+            case BYTE:
+                final Byte encodedByte = encodeByte(configArg, prefs.getString(key, null));
+                if (encodedByte != null) {
+                    setter.setByte(configArg, encodedByte);
+                    return true;
+                }
+                break;
+            case BYTE_LIST:
+                final Set<String> byteListString = prefs.getStringSet(key, Collections.emptySet());
+                final byte[] encodedByteList = new byte[byteListString.size()];
+                int i = 0;
+                for (final String s : byteListString) {
+                    encodedByteList[i++] = encodeByte(configArg, s);
+                }
+                setter.setByteList(configArg, encodedByteList);
+                return true;
+            case DATETIME_HH_MM:
+                setter.setHourMinute(configArg, prefs.getTimePreference(key, "00:00"));
+                return true;
+        }
+
+        LOG.warn("Failed to set {}", configArg);
+
+        return false;
+    }
+
+    private static String encodeString(final ConfigArg configArg, final String value) {
+        if (value == null) {
+            return null;
+        }
+
+        switch (configArg) {
+            case UPPER_BUTTON_LONG_PRESS:
+            case LOWER_BUTTON_PRESS:
+                final String itemHex = MapUtils.reverse(ZeppOsMenuType.displayItemNameLookup).get(value);
+                if (itemHex != null) {
+                    return itemHex;
+                }
+
+                // Unknown button press value - attempt to parse it as hex
+                final Matcher matcher = Pattern.compile("^([0-9A-F]{8})$").matcher(value);
+                if (matcher.matches()) {
+                    LOG.debug("Sending unknown button press item {} as hex", value);
+                    return value;
+                }
+
+                LOG.warn("Failed to map button press value {}", value);
+
+                return null;
+            case DATE_FORMAT:
+                return value.replace("/", ".");
+        }
+
+        return value; // passthrough
+    }
+
+    private static Byte encodeByte(final ConfigArg configArg, final String value) {
+        if (value == null) {
+            return null;
+        }
+
+        switch (configArg) {
+            case ALWAYS_ON_DISPLAY_MODE:
+                return encodeEnum(ALWAYS_ON_DISPLAY_MAP, value);
+            case LIFT_WRIST_MODE:
+                return encodeEnum(LIFT_WRIST_MAP, value);
+            case LIFT_WRIST_RESPONSE_SENSITIVITY:
+                return encodeEnum(LIFT_WRIST_SENSITIVITY_MAP, value);
+            case LANGUAGE:
+                return languageLocaleToByte(value);
+            case HEART_RATE_ALL_DAY_MONITORING:
+                return encodeHeartRateAllDayMonitoring(value);
+            case SCREEN_TIMEOUT:
+            case HEART_RATE_HIGH_ALERTS:
+            case HEART_RATE_LOW_ALERTS:
+            case SPO2_LOW_ALERT:
+                return (byte) Integer.parseInt(value);
+            case TIME_FORMAT:
+                return encodeString(TIME_FORMAT_MAP, value);
+            case DND_MODE:
+                return encodeEnum(DND_MODE_MAP, value);
+            case TEMPERATURE_UNIT:
+                return encodeEnum(TEMPERATURE_UNIT_MAP, value);
+            case NIGHT_MODE_MODE:
+                return encodeString(NIGHT_MODE_MAP, value);
+            case WEARING_DIRECTION_BUTTONS:
+                return encodeString(WEARING_DIRECTION_MAP, value);
+            case OFFLINE_VOICE_LANGUAGE:
+                return encodeString(OFFLINE_VOICE_LANGUAGE_MAP, value);
+            case WORKOUT_GPS_PRESET:
+                return encodeEnum(GPS_PRESET_MAP, value);
+            case WORKOUT_GPS_BAND:
+                return encodeEnum(GPS_BAND_MAP, value);
+            case WORKOUT_GPS_COMBINATION:
+                return encodeEnum(GPS_COMBINATION_MAP, value);
+            case WORKOUT_GPS_SATELLITE_SEARCH:
+                return encodeEnum(GPS_SATELLITE_SEARCH_MAP, value);
+            case WORKOUT_DETECTION_CATEGORY:
+                return encodeEnum(WORKOUT_DETECTION_CATEGORY_MAP, value);
+            case WORKOUT_DETECTION_SENSITIVITY:
+                return encodeEnum(WORKOUT_DETECTION_SENSITIVITY_MAP, value);
+        }
+
+        LOG.error("No encoder for {}", configArg);
+
+        return null;
+    }
+
+    /**
+     * Returns the preference key where to save the minimum possible value for a preference.
+     */
+    public static String getPrefMinKey(final String key) {
+        return String.format(Locale.ROOT, "%s_huami_2021_min", key);
+    }
+
+    /**
+     * Returns the preference key where to save the maximum possible value for a preference.
+     */
+    public static String getPrefMaxKey(final String key) {
+        return String.format(Locale.ROOT, "%s_huami_2021_max", key);
+    }
+
+    public static boolean deviceHasConfig(final Prefs devicePrefs, final ZeppOsConfigService.ConfigArg config) {
+        return devicePrefs.getBoolean(DeviceSettingsUtils.getPrefKnownConfig(config.name()), false);
+    }
+
+    public ConfigSetter newSetter() {
+        return new ConfigSetter();
+    }
+
+    public class ConfigSetter {
+        private final Map<ConfigGroup, Map<ConfigArg, byte[]>> arguments = new LinkedHashMap<>();
+
+        public ConfigSetter() {
+        }
+
+        public ConfigSetter setBoolean(final ConfigArg arg, final boolean value) {
+            checkArg(arg, ConfigType.BOOL);
+
+            putArgument(arg, new byte[]{(byte) (value ? 0x01 : 0x00)});
+
+            return this;
+        }
+
+        public ConfigSetter setString(final ConfigArg arg, final String value) {
+            checkArg(arg, ConfigType.STRING);
+
+            putArgument(arg, (value + "\0").getBytes(StandardCharsets.UTF_8));
+
+            return this;
+        }
+
+        public ConfigSetter setStringList(final ConfigArg arg, final String value) {
+            checkArg(arg, ConfigType.STRING_LIST);
+
+            putArgument(arg, (value + "\0").getBytes(StandardCharsets.UTF_8));
+
+            return this;
+        }
+
+        public ConfigSetter setShort(final ConfigArg arg, final short value) {
+            checkArg(arg, ConfigType.SHORT);
+
+            putArgument(arg, BLETypeConversions.fromUint16(value));
+
+            return this;
+        }
+
+        public ConfigSetter setInt(final ConfigArg arg, final int value) {
+            checkArg(arg, ConfigType.INT);
+
+            putArgument(arg, BLETypeConversions.fromUint32(value));
+
+            return this;
+        }
+
+        public ConfigSetter setByte(final ConfigArg arg, final byte value) {
+            checkArg(arg, ConfigType.BYTE);
+
+            putArgument(arg, new byte[]{value});
+
+            return this;
+        }
+
+        public ConfigSetter setByteList(final ConfigArg arg, final byte[] values) {
+            checkArg(arg, ConfigType.BYTE_LIST);
+
+            putArgument(arg, ArrayUtils.addAll(new byte[]{(byte) values.length}, values));
+
+            return this;
+        }
+
+        public ConfigSetter setHourMinute(final ConfigArg arg, final Date date) {
+            checkArg(arg, ConfigType.DATETIME_HH_MM);
+
+            final Calendar calendar = GregorianCalendar.getInstance();
+            calendar.setTime(date);
+
+            putArgument(arg, new byte[]{
+                    (byte) calendar.get(Calendar.HOUR_OF_DAY),
+                    (byte) calendar.get(Calendar.MINUTE)
+            });
+
+            return this;
+        }
+
+        private void putArgument(final ConfigArg arg, final byte[] encodedValue) {
+            final Map<ConfigArg, byte[]> groupMap;
+            if (arguments.containsKey(arg.getConfigGroup())) {
+                groupMap = arguments.get(arg.getConfigGroup());
+            } else {
+                groupMap = new LinkedHashMap<>();
+                arguments.put(arg.getConfigGroup(), groupMap);
+            }
+
+            groupMap.put(arg, encodedValue);
+        }
+
+        public byte[] encode(final ConfigGroup configGroup) {
+            final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+
+            final Map<ConfigArg, byte[]> configArgMap = arguments.get(configGroup);
+
+            try {
+                baos.write(CMD_SET);
+                baos.write(configGroup.getValue());
+                baos.write(configGroup.getVersion());
+                baos.write(0x00); // ?
+                baos.write(configArgMap.size());
+                for (final Map.Entry<ConfigArg, byte[]> arg : configArgMap.entrySet()) {
+                    final ConfigType configType = arg.getKey().getConfigType(mGroupVersions);
+                    baos.write(arg.getKey().getCode());
+                    baos.write(configType.getValue());
+                    baos.write(arg.getValue());
+                }
+            } catch (final IOException e) {
+                LOG.error("Failed to encode command", e);
+            }
+
+            return baos.toByteArray();
+        }
+
+        public void write(final TransactionBuilder builder) {
+            // Write one command per config group
+            for (final ConfigGroup configGroup : arguments.keySet()) {
+                ZeppOsConfigService.this.write(builder, encode(configGroup));
+            }
+        }
+
+        private void checkArg(final ConfigArg arg, final ConfigType expectedConfigType) {
+            if (arg.getConfigType(mGroupVersions) == null) {
+                // Some special cases (STEPS goal) do not have a config type
+                return;
+            }
+
+            try {
+                if (!expectedConfigType.equals(arg.getConfigType(mGroupVersions))) {
+                    throw new IllegalArgumentException(
+                            String.format(
+                                    "Invalid arg type %s for %s, expected %s",
+                                    expectedConfigType,
+                                    arg,
+                                    arg.getConfigType(mGroupVersions)
+                            )
+                    );
+                }
+            } catch (final IllegalArgumentException e) {
+                if (!BuildConfig.DEBUG) {
+                    // Crash
+                    throw e;
+                } else {
+                    LOG.error(e.getMessage());
+                }
+            }
+        }
+    }
+
+    public class ConfigParser {
+        private final ConfigGroup configGroup;
+        private final boolean includesConstraints;
+
+        public ConfigParser(final ConfigGroup configGroup, final boolean includesConstraints) {
+            this.configGroup = configGroup;
+            this.includesConstraints = includesConstraints;
+        }
+
+        public Map<String, Object> parse(final int expectedNumConfigs, final byte[] bytes) {
+            final ByteBuffer buf = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN);
+            final Map<String, Object> prefs = new HashMap<>();
+
+            int configCount = 0;
+
+            while (buf.position() < buf.limit()) {
+                if (configCount > expectedNumConfigs) {
+                    LOG.error("Got more configs than {}", expectedNumConfigs);
+                    return null;
+                }
+
+                final byte configArgByte = buf.get();
+                final ZeppOsConfigService.ConfigArg configArg = ZeppOsConfigService.ConfigArg.fromCode(configGroup, configArgByte);
+                if (configArg == null) {
+                    LOG.error("Unknown config {} for {}", String.format("0x%02x", configArgByte), configGroup);
+                }
+
+                final byte configTypeByte = buf.get();
+                final ConfigType configType = ConfigType.fromValue(configTypeByte);
+                if (configType == null) {
+                    LOG.error("Unknown type {} for {}", String.format("0x%02x", configTypeByte), configArg);
+                    // Abort, since we don't know how to parse this type or how many bytes it is
+                    // Return whatever we parsed so far, since that's still valid
+                    return prefs;
+                }
+                if (configArg != null) {
+                    if (configType != configArg.getConfigType(mGroupVersions)) {
+                        LOG.warn("Unexpected arg type {} for {}, expected {}", configType, configArg, configArg.getConfigType(mGroupVersions));
+                    }
+                }
+
+                Map<String, Object> argPrefs = null;
+
+                // FIXME this switch has a lot of repeated code that could be generalized...
+                switch (configType) {
+                    case BOOL:
+                        final ConfigBoolean valBoolean = ConfigBoolean.consume(buf);
+                        if (valBoolean == null) {
+                            LOG.error("Failed to parse {} for {}", configType, configArg);
+                            return prefs;
+                        }
+                        LOG.info("Got {} ({}) = {}", configArg, String.format("0x%02x", configArgByte), valBoolean);
+                        if (configArg != null) {
+                            argPrefs = convertBooleanToPrefs(configArg, valBoolean);
+                        }
+                        break;
+                    case STRING:
+                        final ConfigString valString = ConfigString.consume(buf, includesConstraints);
+                        if (valString == null) {
+                            LOG.error("Failed to parse {} for {}", configType, configArg);
+                            return prefs;
+                        }
+                        LOG.info("Got {} ({}) = {}", configArg, String.format("0x%02x", configArgByte), valString);
+                        if (configArg != null) {
+                            argPrefs = convertStringToPrefs(configArg, valString);
+                        }
+                        break;
+                    case STRING_LIST:
+                        final ConfigStringList valStringList = ConfigStringList.consume(buf, includesConstraints);
+                        if (valStringList == null) {
+                            LOG.error("Failed to parse {} for {}", configType, configArg);
+                            return prefs;
+                        }
+                        LOG.info("Got {} ({}) = {}", configArg, String.format("0x%02x", configArgByte), valStringList);
+                        if (configArg != null) {
+                            argPrefs = convertStringListToPrefs(configArg, valStringList);
+                        }
+                        break;
+                    case SHORT:
+                        final ConfigShort valShort = ConfigShort.consume(buf, includesConstraints);
+                        if (valShort == null) {
+                            LOG.error("Failed to parse {} for {}", configType, configArg);
+                            return prefs;
+                        }
+                        LOG.info("Got {} ({}) = {}", configArg, String.format("0x%02x", configArgByte), valShort);
+                        if (configArg != null) {
+                            argPrefs = convertShortToPrefs(configArg, valShort);
+                        }
+                        break;
+                    case INT:
+                        final ConfigInt valInt = ConfigInt.consume(buf, includesConstraints);
+                        if (valInt == null) {
+                            LOG.error("Failed to parse {} for {}", configType, configArg);
+                            return prefs;
+                        }
+                        LOG.info("Got {} ({}) = {}", configArg, String.format("0x%02x", configArgByte), valInt);
+                        if (configArg != null) {
+                            argPrefs = convertIntToPrefs(configArg, valInt);
+                        }
+                        break;
+                    case BYTE:
+                        final ConfigByte valByte = ConfigByte.consume(buf, includesConstraints);
+                        if (valByte == null) {
+                            LOG.error("Failed to parse {} for {}", configType, configArg);
+                            return prefs;
+                        }
+                        LOG.info("Got {} ({}) = {}", configArg, String.format("0x%02x", configArgByte), valByte);
+                        if (configArg != null) {
+                            argPrefs = convertByteToPrefs(configArg, valByte);
+                        }
+                        break;
+                    case BYTE_LIST:
+                        final ConfigByteList valByteList = ConfigByteList.consume(buf, includesConstraints);
+                        if (valByteList == null) {
+                            LOG.error("Failed to parse {} for {}", configType, configArg);
+                            return prefs;
+                        }
+                        LOG.info("Got {} ({}) = {}", configArg, String.format("0x%02x", configArgByte), valByteList);
+                        if (configArg != null) {
+                            argPrefs = convertByteListToPrefs(configArg, valByteList);
+                        }
+                        break;
+                    case DATETIME_HH_MM:
+                        final ConfigDatetimeHhMm valHhMm = ConfigDatetimeHhMm.consume(buf);
+                        if (valHhMm == null) {
+                            LOG.error("Failed to parse {} for {}", configType, configArg);
+                            return prefs;
+                        }
+                        LOG.info("Got {} ({}) = {}", configArg, String.format("0x%02x", configArgByte), valHhMm);
+                        if (configArg != null) {
+                            argPrefs = convertDatetimeHhMmToPrefs(configArg, valHhMm);
+                        }
+                        break;
+                    case TIMESTAMP_MILLIS:
+                        final ConfigTimestamp valTimestamp = ConfigTimestamp.consume(buf);
+                        if (valTimestamp == null) {
+                            LOG.error("Failed to parse {} for {}", configType, configArg);
+                            return prefs;
+                        }
+                        LOG.info("Got {} ({}) = {}", configArg, String.format("0x%02x", configArgByte), valTimestamp);
+                        if (configArg != null) {
+                            argPrefs = convertTimestampToPrefs(configArg, valTimestamp);
+                        }
+                        break;
+                    default:
+                        LOG.error("No parser for {}", configArg);
+                        // Abort, since we don't know how to parse this type or how many bytes it is
+                        // Return whatever we parsed so far, since that's still valid
+                        return prefs;
+                }
+
+                if (argPrefs == null) {
+                    LOG.warn("Unhandled {} pref of type {}", configType, configArg);
+                }
+
+                if (configArg != null && argPrefs != null && configType == configArg.getConfigType(mGroupVersions)) {
+                    prefs.put(DeviceSettingsUtils.getPrefKnownConfig(configArg.name()), true);
+
+                    // Special cases for "follow phone" preferences. We need to ensure that "auto"
+                    // always has precedence
+                    if (argPrefs.containsKey(PREF_LANGUAGE) && prefs.containsKey(PREF_LANGUAGE)) {
+                        if (Objects.equals(prefs.get(PREF_LANGUAGE), DeviceSettingsPreferenceConst.PREF_LANGUAGE_AUTO)) {
+                            argPrefs.remove(PREF_LANGUAGE);
+                        }
+                    }
+                    if (argPrefs.containsKey(PREF_TIMEFORMAT) && prefs.containsKey(PREF_TIMEFORMAT)) {
+                        if (Objects.equals(prefs.get(PREF_TIMEFORMAT), DeviceSettingsPreferenceConst.PREF_TIMEFORMAT_AUTO)) {
+                            argPrefs.remove(PREF_TIMEFORMAT);
+                        }
+                    }
+
+                    prefs.putAll(argPrefs);
+                }
+
+                configCount++;
+            }
+
+            return prefs;
+        }
+
+        private Map<String, Object> convertBooleanToPrefs(final ConfigArg configArg, final ConfigBoolean value) {
+            // Special cases
+            switch (configArg) {
+                case LANGUAGE_FOLLOW_PHONE:
+                    if (value.getValue()) {
+                        return singletonMap(PREF_LANGUAGE, DeviceSettingsPreferenceConst.PREF_LANGUAGE_AUTO);
+                    } else {
+                        // If not following phone, we'll receive the actual value in LANGUAGE
+                        return Collections.emptyMap();
+                    }
+                case TIME_FORMAT_FOLLOWS_PHONE:
+                    if (value.getValue()) {
+                        return singletonMap(PREF_TIMEFORMAT, DeviceSettingsPreferenceConst.PREF_TIMEFORMAT_AUTO);
+                    } else {
+                        // If not following phone, we'll receive the actual value in TIME_FORMAT
+                        return Collections.emptyMap();
+                    }
+                default:
+                    break;
+            }
+
+            if (configArg.getPrefKey() != null) {
+                // The arg maps to a boolean pref directly
+                return singletonMap(configArg.getPrefKey(), value.getValue());
+            }
+
+            return null;
+        }
+
+        private Map<String, Object> convertStringToPrefs(final ConfigArg configArg, final ConfigString str) {
+            // Special cases
+            switch (configArg) {
+                case DATE_FORMAT:
+                    return singletonMap(PREF_DATEFORMAT, str.getValue().replace(".", "/").toUpperCase(Locale.ROOT));
+                default:
+                    break;
+            }
+
+            if (configArg.getPrefKey() != null) {
+                // The arg maps to a string pref directly
+                return singletonMap(configArg.getPrefKey(), str.getValue());
+            }
+
+            return null;
+        }
+
+        private Map<String, Object> convertStringListToPrefs(final ConfigArg configArg, final ConfigStringList str) {
+            final List<String> possibleValues = str.getPossibleValues();
+            final boolean includesConstraints = !possibleValues.isEmpty();
+            Map<String, Object> prefs = null;
+            final ValueDecoder<String> decoder;
+
+            switch (configArg) {
+                case UPPER_BUTTON_LONG_PRESS:
+                case LOWER_BUTTON_PRESS:
+                    decoder = ZeppOsMenuType.displayItemNameLookup::get;
+                    break;
+                default:
+                    decoder = a -> a; // passthrough
+            }
+
+            if (configArg.getPrefKey() != null) {
+                prefs = singletonMap(configArg.getPrefKey(), decoder.decode(str.getValue()));
+                if (includesConstraints) {
+                    prefs.put(
+                            DeviceSettingsUtils.getPrefPossibleValuesKey(configArg.getPrefKey()),
+                            decodeStringValues(possibleValues, decoder)
+                    );
+                }
+            }
+
+            return prefs;
+        }
+
+        private Map<String, Object> convertShortToPrefs(final ConfigArg configArg, final ConfigShort value) {
+            if (configArg.getPrefKey() != null) {
+                final Map<String, Object> prefs;
+                if (configArg == ConfigArg.CALL_DELAY) {
+                    // Persist as string, otherwise the EditText crashes
+                    prefs = singletonMap(configArg.getPrefKey(), String.valueOf(value.getValue()));
+                } else {
+                    // The arg maps to a number pref directly
+                    prefs = singletonMap(configArg.getPrefKey(), value.getValue());
+                }
+
+                if (value.isMinMaxKnown()) {
+                    prefs.put(getPrefMinKey(configArg.getPrefKey()), value.getMin());
+                    prefs.put(getPrefMaxKey(configArg.getPrefKey()), value.getMax());
+                }
+
+                return prefs;
+            }
+
+            return null;
+        }
+
+        private Map<String, Object> convertIntToPrefs(final ConfigArg configArg, final ConfigInt value) {
+            if (configArg.getPrefKey() != null) {
+                // The arg maps to a number pref directly
+                final Map<String, Object> prefs = singletonMap(configArg.getPrefKey(), value.getValue());
+
+                if (value.isMinMaxKnown()) {
+                    prefs.put(getPrefMinKey(configArg.getPrefKey()), value.getMin());
+                    prefs.put(getPrefMaxKey(configArg.getPrefKey()), value.getMax());
+                }
+
+                return prefs;
+            }
+
+            return null;
+        }
+
+        private Map<String, Object> convertTimestampToPrefs(final ConfigArg configArg, final ConfigTimestamp value) {
+            if (configArg.getPrefKey() != null) {
+                // The arg maps to a number pref directly
+                return singletonMap(configArg.getPrefKey(), value.getValue());
+            }
+
+            return null;
+        }
+
+        private Map<String, Object> convertDatetimeHhMmToPrefs(final ConfigArg configArg, final ConfigDatetimeHhMm hhmm) {
+            if (configArg.getPrefKey() != null) {
+                // The arg maps to a hhmm pref directly
+                return singletonMap(configArg.getPrefKey(), hhmm.getValue());
+            }
+
+            return null;
+        }
+
+        private Map<String, Object> convertByteListToPrefs(final ConfigArg configArg, final ConfigByteList value) {
+            final byte[] possibleValues = value.getPossibleValues();
+            final boolean includesConstraints = possibleValues != null && possibleValues.length > 0;
+            Map<String, Object> prefs = null;
+            final ValueDecoder<Byte> decoder;
+
+            switch (configArg) {
+                case WORKOUT_DETECTION_CATEGORY:
+                    decoder = b -> decodeEnum(WORKOUT_DETECTION_CATEGORY_MAP, b);
+                    break;
+                default:
+                    LOG.warn("No decoder for {}", configArg);
+                    return null;
+            }
+
+            if (configArg.getPrefKey() != null) {
+                final List<String> valuesList = decodeByteValues(value.getValues(), decoder);
+                prefs = singletonMap(configArg.getPrefKey(), new HashSet<>(valuesList));
+                if (includesConstraints) {
+                    prefs.put(
+                            DeviceSettingsUtils.getPrefPossibleValuesKey(configArg.getPrefKey()),
+                            TextUtils.join(",", decodeByteValues(possibleValues, decoder))
+                    );
+                }
+            }
+
+            return prefs;
+        }
+
+        private Map<String, Object> convertByteToPrefs(final ConfigArg configArg, final ConfigByte value) {
+            final byte[] possibleValues = value.getPossibleValues();
+            final boolean includesConstraints = value.getPossibleValues().length > 0;
+            Map<String, Object> prefs = null;
+            final ValueDecoder<Byte> decoder;
+
+            switch (configArg) {
+                case ALWAYS_ON_DISPLAY_MODE:
+                    decoder = b -> decodeEnum(ALWAYS_ON_DISPLAY_MAP, b);
+                    break;
+                case LIFT_WRIST_MODE:
+                    decoder = b -> decodeEnum(LIFT_WRIST_MAP, b);
+                    break;
+                case LIFT_WRIST_RESPONSE_SENSITIVITY:
+                    decoder = b -> decodeEnum(LIFT_WRIST_SENSITIVITY_MAP, b);
+                    break;
+                case LANGUAGE:
+                    final String language = languageByteToLocale(value.getValue());
+                    if (language != null) {
+                        prefs = singletonMap(configArg.getPrefKey(), language);
+                        if (includesConstraints) {
+                            final List<String> possibleLanguages = new ArrayList<>();
+                            possibleLanguages.add("auto");
+                            for (final byte possibleValue : value.getPossibleValues()) {
+                                possibleLanguages.add(languageByteToLocale(possibleValue));
+                            }
+                            possibleLanguages.removeAll(Collections.singleton(null));
+                            prefs.put(DeviceSettingsUtils.getPrefPossibleValuesKey(configArg.getPrefKey()), TextUtils.join(",", possibleLanguages));
+                        }
+                    }
+                    decoder = null;
+                    break;
+                case HEART_RATE_ALL_DAY_MONITORING:
+                    decoder = ZeppOsConfigService::decodeHeartRateAllDayMonitoring;
+                    break;
+                case SCREEN_TIMEOUT:
+                case HEART_RATE_HIGH_ALERTS:
+                case HEART_RATE_LOW_ALERTS:
+                case SPO2_LOW_ALERT:
+                    decoder = a -> String.format(Locale.ROOT, "%d", a & 0xff);
+                    break;
+                case TIME_FORMAT:
+                    decoder = b -> decodeString(TIME_FORMAT_MAP, b);
+                    break;
+                case DND_MODE:
+                    decoder = b -> decodeEnum(DND_MODE_MAP, b);
+                    break;
+                case TEMPERATURE_UNIT:
+                    // TODO: This should be per device...
+                    decoder = b -> decodeEnum(TEMPERATURE_UNIT_MAP, b);
+                    break;
+                case NIGHT_MODE_MODE:
+                    decoder = b -> decodeString(NIGHT_MODE_MAP, b);
+                    break;
+                case WEARING_DIRECTION_BUTTONS:
+                    decoder = b -> decodeString(WEARING_DIRECTION_MAP, b);
+                    break;
+                case OFFLINE_VOICE_LANGUAGE:
+                    decoder = b -> decodeString(OFFLINE_VOICE_LANGUAGE_MAP, b);
+                    break;
+                case WORKOUT_GPS_PRESET:
+                    decoder = b -> decodeEnum(GPS_PRESET_MAP, b);
+                    break;
+                case WORKOUT_GPS_BAND:
+                    decoder = b -> decodeEnum(GPS_BAND_MAP, b);
+                    break;
+                case WORKOUT_GPS_COMBINATION:
+                    decoder = b -> decodeEnum(GPS_COMBINATION_MAP, b);
+                    break;
+                case WORKOUT_GPS_SATELLITE_SEARCH:
+                    decoder = b -> decodeEnum(GPS_SATELLITE_SEARCH_MAP, b);
+                    break;
+                case WORKOUT_DETECTION_SENSITIVITY:
+                    decoder = b -> decodeEnum(WORKOUT_DETECTION_SENSITIVITY_MAP, b);
+                    break;
+                default:
+                    decoder = null;
+            }
+
+            if (decoder != null) {
+                prefs = singletonMap(configArg.getPrefKey(), decoder.decode(value.getValue()));
+                if (includesConstraints) {
+                    prefs.put(
+                            DeviceSettingsUtils.getPrefPossibleValuesKey(configArg.getPrefKey()),
+                            TextUtils.join(",", decodeByteValues(possibleValues, decoder))
+                    );
+                }
+            }
+
+            return prefs;
+        }
+
+        private List<String> decodeByteValues(final byte[] values, final ValueDecoder<Byte> decoder) {
+            final List<String> decoded = new ArrayList<>(values.length);
+            for (final byte b : values) {
+                final String decodedByte = decoder.decode(b);
+                if (decodedByte != null) {
+                    decoded.add(decodedByte);
+                } else {
+                    decoded.add(String.format("0x%x", b));
+                }
+            }
+            decoded.removeAll(Collections.singleton(null));
+            return decoded;
+        }
+
+        private String decodeStringValues(final List<String> values, final ValueDecoder<String> decoder) {
+            final List<String> decoded = new ArrayList<>(values.size());
+            for (final String str : values) {
+                final String decodedStr = decoder.decode(str);
+                if (decodedStr != null) {
+                    decoded.add(decodedStr);
+                } else {
+                    decoded.add(str);
+                }
+            }
+            if (decoded.isEmpty()) {
+                return null;
+            }
+            return TextUtils.join(",", decoded);
+        }
+
+        private Map<String, Object> singletonMap(final String key, final Object value) {
+            if (key == null) {
+                LOG.error("Null key in prefs update");
+                if (BuildConfig.DEBUG) {
+                    // Crash
+                    throw new IllegalStateException("Null key in prefs update");
+                }
+                return Collections.emptyMap();
+            }
+
+            return new HashMap<String, Object>() {{
+                put(key, value);
+            }};
+        }
+    }
+
+    private static class ConfigBoolean {
+        private final boolean value;
+
+        public ConfigBoolean(final boolean value) {
+            this.value = value;
+        }
+
+        public boolean getValue() {
+            return value;
+        }
+
+        private static ConfigBoolean consume(final ByteBuffer buf) {
+            return new ConfigBoolean(buf.get() == 1);
+        }
+
+        @Override
+        public String toString() {
+            return String.format("ConfigBoolean{value=%s}", value);
+        }
+    }
+
+    private static class ConfigString {
+        private final String value;
+        private final int maxLength;
+
+        public ConfigString(final String value, final int maxLength) {
+            this.value = value;
+            this.maxLength = maxLength;
+        }
+
+        public String getValue() {
+            return value;
+        }
+
+        public int getMaxLength() {
+            return maxLength;
+        }
+
+        private static ConfigString consume(final ByteBuffer buf, final boolean includesConstraints) {
+            final String value = StringUtils.untilNullTerminator(buf);
+            if (value == null) {
+                LOG.error("Null terminator not found in buffer");
+                return null;
+            }
+
+            if (!includesConstraints) {
+                return new ConfigString(value, -1);
+            }
+
+            final int maxLength = buf.get() & 0xff;
+
+            return new ConfigString(value, maxLength);
+        }
+
+        @Override
+        public String toString() {
+            return String.format("ConfigString{value=%s}", value);
+        }
+    }
+
+    private static class ConfigStringList {
+        private final String value;
+        private final List<String> possibleValues;
+
+        public ConfigStringList(final String value, final List<String> possibleValues) {
+            this.value = value;
+            this.possibleValues = possibleValues;
+        }
+
+        public String getValue() {
+            return value;
+        }
+
+        public List<String> getPossibleValues() {
+            return possibleValues;
+        }
+
+        private static ConfigStringList consume(final ByteBuffer buf, final boolean includesConstraints) {
+            final String value = StringUtils.untilNullTerminator(buf);
+            if (value == null) {
+                LOG.error("Null terminator not found in buffer");
+                return null;
+            }
+
+            final List<String> possibleValues = new ArrayList<>();
+            if (includesConstraints) {
+                final int unknown1 = buf.get() & 0xff; // ?
+                final int numPossibleValues = buf.get() & 0xff;
+
+                for (int i = 0; i < numPossibleValues; i++) {
+                    final String possibleValue = StringUtils.untilNullTerminator(buf);
+                    possibleValues.add(possibleValue);
+                }
+            }
+
+            return new ConfigStringList(value, possibleValues);
+        }
+
+        @Override
+        public String toString() {
+            return String.format("ConfigStringList{value=%s, possibleValues=%s}", value, possibleValues);
+        }
+    }
+
+    private static class ConfigShort {
+        private final short value;
+        private final short min;
+        private final short max;
+        private final boolean minMaxKnown;
+
+        public ConfigShort(final short value) {
+            this.value = value;
+            this.min = this.max = 0;
+            minMaxKnown = false;
+        }
+
+        public ConfigShort(final short value, final short min, final short max) {
+            this.value = value;
+            this.min = min;
+            this.max = max;
+            this.minMaxKnown = true;
+        }
+
+        public short getValue() {
+            return value;
+        }
+
+        public short getMin() {
+            return min;
+        }
+
+        public short getMax() {
+            return max;
+        }
+
+        public boolean isMinMaxKnown() {
+            return minMaxKnown;
+        }
+
+        private static ConfigShort consume(final ByteBuffer buf, final boolean includesConstraints) {
+            final short value = buf.getShort();
+
+            if (!includesConstraints) {
+                return new ConfigShort(value);
+            }
+
+            final short min = buf.getShort();
+            final short max = buf.getShort();
+
+            return new ConfigShort(value, min, max);
+        }
+
+        @Override
+        public String toString() {
+            if (isMinMaxKnown()) {
+                return String.format(Locale.ROOT, "ConfigShort{value=%d, min=%d, max=%d}", value, min, max);
+            } else {
+                return String.format(Locale.ROOT, "ConfigShort{value=%d}", value);
+            }
+        }
+    }
+
+    private static class ConfigInt {
+        private final int value;
+        private final int min;
+        private final int max;
+        private final boolean minMaxKnown;
+
+        public ConfigInt(final int value) {
+            this.value = value;
+            this.min = this.max = 0;
+            minMaxKnown = false;
+        }
+
+        public ConfigInt(final int value, final int min, final int max) {
+            this.value = value;
+            this.min = min;
+            this.max = max;
+            this.minMaxKnown = true;
+        }
+
+        public int getValue() {
+            return value;
+        }
+
+        public int getMin() {
+            return min;
+        }
+
+        public int getMax() {
+            return max;
+        }
+
+        public boolean isMinMaxKnown() {
+            return minMaxKnown;
+        }
+
+        private static ConfigInt consume(final ByteBuffer buf, final boolean includesConstraints) {
+            final int value = buf.getInt();
+
+            if (!includesConstraints) {
+                return new ConfigInt(value);
+            }
+
+            final int min = buf.getInt();
+            final int max = buf.getInt();
+
+            return new ConfigInt(value, min, max);
+        }
+
+        @Override
+        public String toString() {
+            if (isMinMaxKnown()) {
+                return String.format(Locale.ROOT, "ConfigInt{value=%d, min=%d, max=%d}", value, min, max);
+            } else {
+                return String.format(Locale.ROOT, "ConfigInt{value=%d}", value);
+            }
+        }
+    }
+
+    private static class ConfigTimestamp {
+        private final long value;
+
+        public ConfigTimestamp(final long value) {
+            this.value = value;
+        }
+
+        public long getValue() {
+            return value;
+        }
+
+        private static ConfigTimestamp consume(final ByteBuffer buf) {
+            final long value = buf.getLong();
+
+            return new ConfigTimestamp(value);
+        }
+
+        @Override
+        public String toString() {
+            return String.format(Locale.ROOT, "ConfigTimestamp{value=%s}", new Date(value));
+        }
+    }
+
+    private static class ConfigByte {
+        private final byte value;
+        private final byte[] possibleValues;
+
+        public ConfigByte(final byte value, final byte[] possibleValues) {
+            this.value = value;
+            this.possibleValues = possibleValues;
+        }
+
+        public byte getValue() {
+            return value;
+        }
+
+        public byte[] getPossibleValues() {
+            return possibleValues;
+        }
+
+        private static ConfigByte consume(final ByteBuffer buf, final boolean includesConstraints) {
+            final byte value = buf.get();
+
+            if (includesConstraints) {
+                final int numPossibleValues = buf.get() & 0xff;
+                final byte[] possibleValues = new byte[numPossibleValues];
+
+                for (int i = 0; i < numPossibleValues; i++) {
+                    possibleValues[i] = buf.get();
+                }
+
+                return new ConfigByte(value, possibleValues);
+            }
+
+            return new ConfigByte(value, new byte[0]);
+        }
+
+        @Override
+        public String toString() {
+            return String.format("ConfigByte{value=0x%02x, possibleValues=%s}", value, GB.hexdump(possibleValues));
+        }
+    }
+
+    private static class ConfigByteList {
+        private final byte[] values;
+        private final byte[] possibleValues;
+
+        public ConfigByteList(final byte[] values, final byte[] possibleValues) {
+            this.values = values;
+            this.possibleValues = possibleValues;
+        }
+
+        public byte[] getValues() {
+            return values;
+        }
+
+        @Nullable
+        public byte[] getPossibleValues() {
+            return possibleValues;
+        }
+
+        private static ConfigByteList consume(final ByteBuffer buf, final boolean includesConstraints) {
+            final int numValues = buf.get() & 0xff;
+            final byte[] values = new byte[numValues];
+            for (int i = 0; i < numValues; i++) {
+                values[i] = buf.get();
+            }
+
+            if (includesConstraints) {
+                final int numPossibleValues = buf.get() & 0xff;
+                final byte[] possibleValues = new byte[numPossibleValues];
+
+                for (int i = 0; i < numPossibleValues; i++) {
+                    possibleValues[i] = buf.get();
+                }
+
+                return new ConfigByteList(values, possibleValues);
+            }
+
+            return new ConfigByteList(values, null);
+        }
+
+        @Override
+        public String toString() {
+            if (possibleValues != null) {
+                return String.format("ConfigByteList{values=%s, possibleValues=%s}", GB.hexdump(values), GB.hexdump(possibleValues));
+            } else {
+                return String.format("ConfigByteList{values=%s}", GB.hexdump(values));
+            }
+        }
+    }
+
+    private static class ConfigDatetimeHhMm {
+        final String value;
+
+        public ConfigDatetimeHhMm(final String value) {
+            this.value = value;
+        }
+
+        public String getValue() {
+            return value;
+        }
+
+        private static ConfigDatetimeHhMm consume(final ByteBuffer buf) {
+            final DateFormat df = new SimpleDateFormat("HH:mm", Locale.getDefault());
+            final String hhmm = String.format(Locale.ROOT, "%02d:%02d", buf.get(), buf.get());
+            try {
+                df.parse(hhmm);
+            } catch (final ParseException e) {
+                LOG.error("Failed to parse HH:mm from {}", hhmm);
+                return null;
+            }
+            return new ConfigDatetimeHhMm(hhmm);
+        }
+
+        @Override
+        public String toString() {
+            return String.format("ConfigDatetimeHhMm{value=%s}", value);
+        }
+    }
+
+    private interface ValueDecoder<T> {
+        String decode(T val);
+    }
+
+    public static String languageByteToLocale(final byte code) {
+        final Map<Integer, String> localeLookup = MapUtils.reverse(HuamiLanguageType.idLookup);
+        return localeLookup.get((int) code);
+    }
+
+    public static Byte languageLocaleToByte(final String locale) {
+        if (HuamiLanguageType.idLookup.containsKey(locale)) {
+            return (byte) (int) HuamiLanguageType.idLookup.get(locale);
+        }
+
+        return null;
+    }
+
+    public static String decodeHeartRateAllDayMonitoring(final byte b) {
+        if (b > 0) {
+            return String.format(Locale.ROOT, "%d", (b & 0xff) * 60);
+        } else {
+            return String.format(Locale.ROOT, "%d", b);
+        }
+    }
+
+    public static byte encodeHeartRateAllDayMonitoring(final String val) {
+        final int intVal = Integer.parseInt(val);
+        if (intVal < 0) {
+            return (byte) intVal;
+        } else {
+            return (byte) (intVal / 60);
+        }
+    }
+
+    private static final Map<Byte, Enum<?>> ALWAYS_ON_DISPLAY_MAP = new HashMap<Byte, Enum<?>>() {{
+        put((byte) 0x00, AlwaysOnDisplay.OFF);
+        put((byte) 0x01, AlwaysOnDisplay.AUTOMATIC);
+        put((byte) 0x02, AlwaysOnDisplay.SCHEDULED);
+        put((byte) 0x03, AlwaysOnDisplay.ALWAYS);
+    }};
+
+    private static final Map<Byte, String> NIGHT_MODE_MAP = new HashMap<Byte, String>() {{
+        put((byte) 0x00, MiBandConst.PREF_NIGHT_MODE_OFF);
+        put((byte) 0x01, MiBandConst.PREF_NIGHT_MODE_SUNSET);
+        put((byte) 0x02, MiBandConst.PREF_NIGHT_MODE_SCHEDULED);
+    }};
+
+    private static final Map<Byte, Enum<?>> DND_MODE_MAP = new HashMap<Byte, Enum<?>>() {{
+        put((byte) 0x00, DoNotDisturb.OFF);
+        put((byte) 0x01, DoNotDisturb.SCHEDULED);
+        put((byte) 0x02, DoNotDisturb.AUTOMATIC);
+        put((byte) 0x03, DoNotDisturb.ALWAYS);
+    }};
+
+    private static final Map<Byte, Enum<?>> TEMPERATURE_UNIT_MAP = new HashMap<Byte, Enum<?>>() {{
+        put((byte) 0x00, MiBandConst.DistanceUnit.METRIC);
+        put((byte) 0x01, MiBandConst.DistanceUnit.IMPERIAL);
+    }};
+
+    private static final Map<Byte, String> TIME_FORMAT_MAP = new HashMap<Byte, String>() {{
+        put((byte) 0x00, DeviceSettingsPreferenceConst.PREF_TIMEFORMAT_24H);
+        put((byte) 0x01, DeviceSettingsPreferenceConst.PREF_TIMEFORMAT_12H);
+    }};
+
+    private static final Map<Byte, Enum<?>> LIFT_WRIST_MAP = new HashMap<Byte, Enum<?>>() {{
+        put((byte) 0x00, ActivateDisplayOnLift.OFF);
+        put((byte) 0x01, ActivateDisplayOnLift.SCHEDULED);
+        put((byte) 0x02, ActivateDisplayOnLift.ON);
+        put((byte) 0x03, ActivateDisplayOnLift.SMART);
+    }};
+
+    private static final Map<Byte, Enum<?>> LIFT_WRIST_SENSITIVITY_MAP = new HashMap<Byte, Enum<?>>() {{
+        put((byte) 0x00, ActivateDisplayOnLiftSensitivity.NORMAL);
+        put((byte) 0x01, ActivateDisplayOnLiftSensitivity.SENSITIVE);
+    }};
+
+    private static final Map<Byte, String> WEARING_DIRECTION_MAP = new HashMap<Byte, String>() {{
+        put((byte) 0x00, "buttons_on_left");
+        put((byte) 0x01, "buttons_on_right");
+    }};
+
+    private static final Map<Byte, String> OFFLINE_VOICE_LANGUAGE_MAP = new HashMap<Byte, String>() {{
+        put((byte) 0x01, "zh_CN"); // TODO confirm
+        put((byte) 0x02, "en_US");
+        put((byte) 0x03, "de_DE");
+        put((byte) 0x04, "es_ES");
+    }};
+
+    private static final Map<Byte, Enum<?>> GPS_PRESET_MAP = new HashMap<Byte, Enum<?>>() {{
+        put((byte) 0x00, GpsCapability.Preset.ACCURACY);
+        put((byte) 0x01, GpsCapability.Preset.BALANCED);
+        put((byte) 0x02, GpsCapability.Preset.POWER_SAVING);
+        put((byte) 0x04, GpsCapability.Preset.CUSTOM);
+    }};
+
+    private static final Map<Byte, Enum<?>> GPS_BAND_MAP = new HashMap<Byte, Enum<?>>() {{
+        put((byte) 0x00, GpsCapability.Band.SINGLE_BAND);
+        put((byte) 0x01, GpsCapability.Band.DUAL_BAND);
+    }};
+
+    private static final Map<Byte, Enum<?>> GPS_COMBINATION_MAP = new HashMap<Byte, Enum<?>>() {{
+        put((byte) 0x00, GpsCapability.Combination.LOW_POWER_GPS);
+        put((byte) 0x01, GpsCapability.Combination.GPS);
+        put((byte) 0x02, GpsCapability.Combination.GPS_BDS);
+        put((byte) 0x03, GpsCapability.Combination.GPS_GLONASS);
+        put((byte) 0x04, GpsCapability.Combination.GPS_GALILEO);
+        put((byte) 0x05, GpsCapability.Combination.ALL_SATELLITES);
+    }};
+
+    private static final Map<Byte, Enum<?>> GPS_SATELLITE_SEARCH_MAP = new HashMap<Byte, Enum<?>>() {{
+        put((byte) 0x00, GpsCapability.SatelliteSearch.SPEED_FIRST);
+        put((byte) 0x01, GpsCapability.SatelliteSearch.ACCURACY_FIRST);
+    }};
+
+    private static final Map<Byte, Enum<?>> WORKOUT_DETECTION_CATEGORY_MAP = new HashMap<Byte, Enum<?>>() {{
+        put((byte) 0x03, WorkoutDetectionCapability.Category.WALKING);
+        put((byte) 0x28, WorkoutDetectionCapability.Category.INDOOR_WALKING);
+        put((byte) 0x01, WorkoutDetectionCapability.Category.OUTDOOR_RUNNING);
+        put((byte) 0x02, WorkoutDetectionCapability.Category.TREADMILL);
+        put((byte) 0x04, WorkoutDetectionCapability.Category.OUTDOOR_CYCLING);
+        put((byte) 0x06, WorkoutDetectionCapability.Category.POOL_SWIMMING);
+        put((byte) 0x09, WorkoutDetectionCapability.Category.ELLIPTICAL);
+        put((byte) 0x17, WorkoutDetectionCapability.Category.ROWING_MACHINE);
+    }};
+
+    private static final Map<Byte, Enum<?>> WORKOUT_DETECTION_SENSITIVITY_MAP = new HashMap<Byte, Enum<?>>() {{
+        put((byte) 0x00, WorkoutDetectionCapability.Sensitivity.HIGH);
+        put((byte) 0x01, WorkoutDetectionCapability.Sensitivity.STANDARD);
+        put((byte) 0x02, WorkoutDetectionCapability.Sensitivity.LOW);
+    }};
+
+    public static String decodeEnum(final Map<Byte, Enum<?>> map, final byte b) {
+        if (map.containsKey(b)) {
+            return map.get(b).name().toLowerCase(Locale.ROOT);
+        }
+
+        return null;
+    }
+
+    public static String decodeString(final Map<Byte, String> map, final byte b) {
+        return map.get(b);
+    }
+
+    public static Byte encodeEnum(final Map<Byte, Enum<?>> map, final String val) {
+        final Map<Enum<?>, Byte> reverse = MapUtils.reverse(map);
+        for (final Enum<?> anEnum : reverse.keySet()) {
+            if (anEnum.name().toLowerCase(Locale.ROOT).equals(val)) {
+                return reverse.get(anEnum);
+            }
+        }
+
+        // Byte doesn't match a known enum value, attempt to parse it as hex
+        final Matcher matcher = Pattern.compile("^0[xX]([0-9a-fA-F]{1,2})$").matcher(val);
+        if (matcher.find()) {
+            return (byte) Integer.parseInt(matcher.group(1), 16);
+        }
+
+        return null;
+    }
+
+    public static Byte encodeString(final Map<Byte, String> map, final String val) {
+        final Map<String, Byte> reverse = MapUtils.reverse(map);
+        for (final String aString : reverse.keySet()) {
+            if (aString.equals(val)) {
+                return reverse.get(aString);
+            }
+        }
+
+        return null;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsContactsService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsContactsService.java
new file mode 100644
index 0000000..da40d12
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsContactsService.java
@@ -0,0 +1,118 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.ByteArrayOutputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdatePreferences;
+import nodomain.freeyourgadget.gadgetbridge.model.Contact;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.AbstractZeppOsService;
+import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
+
+public class ZeppOsContactsService extends AbstractZeppOsService {
+    private static final Logger LOG = LoggerFactory.getLogger(ZeppOsContactsService.class);
+
+    private static final short ENDPOINT = 0x0014;
+
+    public static final byte CMD_CAPABILITIES_REQUEST = 0x01;
+    public static final byte CMD_CAPABILITIES_RESPONSE = 0x02;
+    public static final byte CMD_SET_LIST = 0x07;
+    public static final byte CMD_SET_LIST_ACK = 0x08;
+
+    private int version = 0;
+    private int maxContacts = 0;
+
+    public static final String PREF_CONTACTS_SLOT_COUNT = "zepp_os_contacts_slot_count";
+
+    public ZeppOsContactsService(final ZeppOsSupport support) {
+        super(support, true);
+    }
+
+    @Override
+    public short getEndpoint() {
+        return ENDPOINT;
+    }
+
+    @Override
+    public void handlePayload(final byte[] payload) {
+        switch (payload[0]) {
+            case CMD_CAPABILITIES_RESPONSE:
+                version = payload[1];
+                if (version != 1) {
+                    LOG.warn("Unsupported contacts service version {}", version);
+                    return;
+                }
+                maxContacts = BLETypeConversions.toUint16(payload, 2);
+                LOG.info("Contacts version={}, maxContacts={}", version, maxContacts);
+                getSupport().evaluateGBDeviceEvent(new GBDeviceEventUpdatePreferences(PREF_CONTACTS_SLOT_COUNT, maxContacts));
+                break;
+            case CMD_SET_LIST_ACK:
+                LOG.info("Got contacts set list ack, status = {}", payload[1]);
+                break;
+            default:
+                LOG.warn("Unexpected contacts byte {}", String.format("0x%02x", payload[0]));
+        }
+    }
+
+    @Override
+    public void initialize(final TransactionBuilder builder) {
+        requestCapabilities(builder);
+    }
+
+    public void requestCapabilities(final TransactionBuilder builder) {
+        write(builder, CMD_CAPABILITIES_REQUEST);
+    }
+
+    public void setContacts(final List<Contact> contacts) {
+        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+
+        if (contacts.size() > maxContacts) {
+            LOG.warn("Number of contacts {} larger than max contacts {}, list will be truncated", contacts.size(), maxContacts);
+        }
+
+        int numContacts = Math.min(contacts.size(), maxContacts);
+
+        try {
+            baos.write(CMD_SET_LIST);
+            baos.write(BLETypeConversions.fromUint16(numContacts));
+            for (int i = 0; i < numContacts; i++) {
+                final Contact contact = contacts.get(i);
+                if (!StringUtils.isNullOrEmpty(contact.getName())) {
+                    baos.write(contact.getName().getBytes(StandardCharsets.UTF_8));
+                }
+                baos.write(0);
+                if (!StringUtils.isNullOrEmpty(contact.getNumber())) {
+                    baos.write(contact.getNumber().getBytes(StandardCharsets.UTF_8));
+                }
+                baos.write(0);
+            }
+        } catch (final Exception e) {
+            LOG.error("Failed to create command", e);
+            return;
+        }
+
+        write("set contacts", baos.toByteArray());
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsDisplayItemsService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsDisplayItemsService.java
new file mode 100644
index 0000000..aff09ab
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsDisplayItemsService.java
@@ -0,0 +1,510 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services;
+
+import androidx.annotation.Nullable;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsUtils;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdatePreferences;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsMenuType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.AbstractZeppOsService;
+import nodomain.freeyourgadget.gadgetbridge.util.MapUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
+
+public class ZeppOsDisplayItemsService extends AbstractZeppOsService {
+    private static final Logger LOG = LoggerFactory.getLogger(ZeppOsDisplayItemsService.class);
+
+    private static final short ENDPOINT = 0x0026;
+
+    public static final byte CMD_CAPABILITIES_REQUEST = 0x01;
+    public static final byte CMD_CAPABILITIES_RESPONSE = 0x02;
+    public static final byte CMD_REQUEST = 0x03;
+    public static final byte CMD_RESPONSE = 0x04;
+    public static final byte CMD_CREATE = 0x05;
+    public static final byte CMD_CREATE_ACK = 0x06;
+
+    public static final byte DISPLAY_ITEMS_MENU = 0x01;
+    public static final byte DISPLAY_ITEMS_SHORTCUTS = 0x02;
+    public static final byte DISPLAY_ITEMS_CONTROL_CENTER = 0x03;
+
+    public static final byte DISPLAY_ITEMS_SECTION_MAIN = 0x01;
+    public static final byte DISPLAY_ITEMS_SECTION_MORE = 0x02;
+    public static final byte DISPLAY_ITEMS_SECTION_DISABLED = 0x03;
+
+    public ZeppOsDisplayItemsService(final ZeppOsSupport support) {
+        super(support, true);
+    }
+
+    @Override
+    public short getEndpoint() {
+        return ENDPOINT;
+    }
+
+    @Override
+    public void handlePayload(final byte[] payload) {
+        switch (payload[0]) {
+            case CMD_RESPONSE:
+                decodeAndUpdateDisplayItems(payload);
+                break;
+            case CMD_CREATE_ACK:
+                LOG.info("Display items set ACK, type = {}, status = {}", payload[1], payload[2]);
+                break;
+            default:
+                LOG.warn("Unexpected display items payload byte {}", String.format("0x%02x", payload[0]));
+        }
+    }
+
+    @Override
+    public boolean onSendConfiguration(final String config, final Prefs prefs) {
+        final byte menuType;
+
+        switch (config) {
+            case HuamiConst.PREF_DISPLAY_ITEMS:
+            case HuamiConst.PREF_DISPLAY_ITEMS_SORTABLE:
+                menuType = DISPLAY_ITEMS_MENU;
+                break;
+            case HuamiConst.PREF_SHORTCUTS:
+            case HuamiConst.PREF_SHORTCUTS_SORTABLE:
+                menuType = DISPLAY_ITEMS_SHORTCUTS;
+                break;
+            case HuamiConst.PREF_CONTROL_CENTER_SORTABLE:
+                menuType = DISPLAY_ITEMS_CONTROL_CENTER;
+                break;
+            default:
+                return false;
+        }
+
+        setDisplayItems(
+                menuType,
+                new ArrayList<>(prefs.getList(DeviceSettingsUtils.getPrefPossibleValuesKey(config), Collections.emptyList())),
+                new ArrayList<>(prefs.getList(config, Collections.emptyList())),
+                FlagsMap.fromPrefValue(prefs.getString(config + "_flags", "{}"))
+        );
+
+
+        return true;
+    }
+
+    @Override
+    public void initialize(final TransactionBuilder builder) {
+        requestItems(builder, DISPLAY_ITEMS_MENU);
+        requestItems(builder, DISPLAY_ITEMS_SHORTCUTS);
+        if (getCoordinator().supportsControlCenter()) {
+            requestItems(builder, DISPLAY_ITEMS_CONTROL_CENTER);
+        }
+    }
+
+    public void requestItems(final TransactionBuilder builder, final byte type) {
+        LOG.info("Requesting display items type={}", type);
+
+        write(builder, new byte[]{CMD_REQUEST, type});
+    }
+
+    private void decodeAndUpdateDisplayItems(final byte[] payloadd) {
+        final ByteBuffer buf = ByteBuffer.wrap(payloadd).order(ByteOrder.LITTLE_ENDIAN);
+        buf.get(); // discard the command byte
+
+        final int type = buf.get();
+        final int numberScreens = buf.getShort();
+
+        LOG.info("Got {} display items of type {}", numberScreens, type);
+
+        final String prefKey;
+        final Map<String, String> idMap;
+        switch (type) {
+            case DISPLAY_ITEMS_MENU:
+                prefKey = HuamiConst.PREF_DISPLAY_ITEMS_SORTABLE;
+                idMap = ZeppOsMenuType.displayItemNameLookup;
+                break;
+            case DISPLAY_ITEMS_SHORTCUTS:
+                prefKey = HuamiConst.PREF_SHORTCUTS_SORTABLE;
+                idMap = ZeppOsMenuType.shortcutsNameLookup;
+                break;
+            case DISPLAY_ITEMS_CONTROL_CENTER:
+                prefKey = HuamiConst.PREF_CONTROL_CENTER_SORTABLE;
+                idMap = ZeppOsMenuType.controlCenterNameLookup;
+                break;
+            default:
+                LOG.error("Unknown display items type {}", String.format("0x%x", type));
+                return;
+        }
+        final String allScreensPrefKey = DeviceSettingsUtils.getPrefPossibleValuesKey(prefKey);
+
+        final boolean menuHasMoreSection;
+
+        if (type == DISPLAY_ITEMS_MENU) {
+            menuHasMoreSection = getCoordinator().mainMenuHasMoreSection();
+        } else {
+            menuHasMoreSection = false;
+        }
+
+        final String[] mainScreensArr = new String[numberScreens];
+        final String[] moreScreensArr = new String[numberScreens];
+        final List<String> allScreens = new LinkedList<>();
+
+        // we need to save the flags so that we can send them when setting the screens
+        final FlagsMap flagsMap = new FlagsMap();
+
+        if (menuHasMoreSection) {
+            // The band doesn't report the "more" screen, so we add it
+            allScreens.add("more");
+        }
+
+        for (int i = 0; i < numberScreens; i++) {
+            // Screen IDs are sent as literal hex strings
+            final String screenId = StringUtils.untilNullTerminator(buf);
+            final String screenNameOrId = idMap.containsKey(screenId) ? idMap.get(screenId) : screenId;
+            allScreens.add(screenNameOrId);
+
+            final int screenSectionVal = buf.get();
+            final int screenPosition = buf.get();
+            final int flags = buf.get();
+            if (flags != 0) {
+                final Flags flagsObj = new Flags();
+
+                flagsObj.value = flags;
+
+                // 1 = settings / can't be removed
+
+                if ((flags & 2) != 0 || (flags & 4) != 0) {
+                    // unsure which flag indicates the version and which indicates the unknown string
+                    flagsObj.version = StringUtils.untilNullTerminator(buf);
+
+                    if ((flags & 2) != 0 && (flags & 4) != 0) {
+                        // but if both are set, we have a 2nd string
+                        flagsObj.unk = StringUtils.untilNullTerminator(buf);
+                    }
+                }
+
+                flagsMap.put(screenId, flagsObj);
+            }
+
+            if (screenPosition >= numberScreens) {
+                LOG.warn("Invalid screen position {}, ignoring", screenPosition);
+                continue;
+            }
+
+            switch (screenSectionVal) {
+                case DISPLAY_ITEMS_SECTION_MAIN:
+                    if (mainScreensArr[screenPosition] != null) {
+                        LOG.warn("Duplicate position {} for main section", screenPosition);
+                    }
+                    //LOG.debug("mainScreensArr[{}] = {}", screenPosition, screenKey);
+                    mainScreensArr[screenPosition] = screenNameOrId;
+                    break;
+                case DISPLAY_ITEMS_SECTION_MORE:
+                    if (moreScreensArr[screenPosition] != null) {
+                        LOG.warn("Duplicate position {} for more section", screenPosition);
+                    }
+                    //LOG.debug("moreScreensArr[{}] = {}", screenPosition, screenKey);
+                    moreScreensArr[screenPosition] = screenNameOrId;
+                    break;
+                case DISPLAY_ITEMS_SECTION_DISABLED:
+                    // Ignore disabled screens
+                    //LOG.debug("Ignoring disabled screen {} {}", screenPosition, screenKey);
+                    break;
+                default:
+                    LOG.warn("Unknown screen section {}, ignoring", String.format("0x%02x", screenSectionVal));
+            }
+        }
+
+        final List<String> screens = new ArrayList<>(Arrays.asList(mainScreensArr));
+        if (menuHasMoreSection) {
+            screens.add("more");
+            screens.addAll(Arrays.asList(moreScreensArr));
+        }
+        screens.removeAll(Collections.singleton(null));
+
+        final String allScreensPrefValue = StringUtils.join(",", allScreens.toArray(new String[0])).toString();
+        final String prefValue = StringUtils.join(",", screens.toArray(new String[0])).toString();
+        final GBDeviceEventUpdatePreferences eventUpdatePreferences = new GBDeviceEventUpdatePreferences()
+                .withPreference(prefKey + "_flags", flagsMap.toPrefValue())
+                .withPreference(allScreensPrefKey, allScreensPrefValue)
+                .withPreference(prefKey, prefValue);
+
+        evaluateGBDeviceEvent(eventUpdatePreferences);
+    }
+
+    private void setDisplayItems(final byte menuType,
+                                 final List<String> allSettings,
+                                 List<String> enabledList,
+                                 final FlagsMap flags) {
+        try {
+            final TransactionBuilder builder = new TransactionBuilder("set display items type " + menuType);
+            setDisplayItems(builder, menuType, allSettings, enabledList, flags);
+            builder.queue(getSupport().getQueue());
+        } catch (final Exception e) {
+            LOG.error("Failed to set display items", e);
+        }
+    }
+
+    private void setDisplayItems(final TransactionBuilder builder,
+                                 final byte menuType,
+                                 final List<String> allSettings,
+                                 List<String> enabledList,
+                                 final FlagsMap flagsMap) {
+        final boolean isMainMenu = menuType == DISPLAY_ITEMS_MENU;
+        final boolean isShortcuts = menuType == DISPLAY_ITEMS_SHORTCUTS;
+        final boolean hasMoreSection;
+        final Map<String, String> idMap;
+
+        switch (menuType) {
+            case DISPLAY_ITEMS_MENU:
+                LOG.info("Setting menu items");
+                hasMoreSection = getCoordinator().mainMenuHasMoreSection();
+                idMap = MapUtils.reverse(ZeppOsMenuType.displayItemNameLookup);
+                break;
+            case DISPLAY_ITEMS_SHORTCUTS:
+                LOG.info("Setting shortcuts");
+                hasMoreSection = false;
+                idMap = MapUtils.reverse(ZeppOsMenuType.shortcutsNameLookup);
+                break;
+            case DISPLAY_ITEMS_CONTROL_CENTER:
+                LOG.info("Setting control center");
+                hasMoreSection = false;
+                idMap = MapUtils.reverse(ZeppOsMenuType.controlCenterNameLookup);
+                break;
+            default:
+                LOG.warn("Unknown menu type {}", menuType);
+                return;
+        }
+
+        if (allSettings.isEmpty()) {
+            LOG.warn("List of all display items is missing");
+            return;
+        }
+
+        if (flagsMap == null) {
+            LOG.error("Flags map is missing");
+            return;
+        }
+
+        if (isMainMenu && !enabledList.contains("settings")) {
+            // Settings can't be disabled
+            enabledList.add("settings");
+        }
+
+        if (isShortcuts && enabledList.size() > 10) {
+            // Enforced by official app
+            LOG.warn("Truncating shortcuts list to 10");
+            enabledList = enabledList.subList(0, 10);
+        }
+
+        LOG.info("Setting display items (shortcuts={}): {}", isShortcuts, enabledList);
+
+        int numItems = allSettings.size();
+        if (hasMoreSection) {
+            // Exclude the "more" item from the main menu, since it's not a real item
+            numItems--;
+        }
+
+        int flagsOverhead = 0;
+        for (final Map.Entry<String, Flags> e : flagsMap.entrySet()) {
+            if (e.getValue().version != null) {
+                flagsOverhead += e.getValue().version.getBytes(StandardCharsets.UTF_8).length + 1;
+            }
+            if (e.getValue().unk != null) {
+                flagsOverhead += e.getValue().unk.getBytes(StandardCharsets.UTF_8).length + 1;
+            }
+        }
+
+        final ByteBuffer buf = ByteBuffer.allocate(4 + numItems * 12 + flagsOverhead);
+        buf.order(ByteOrder.LITTLE_ENDIAN);
+
+        buf.put(CMD_CREATE);
+        buf.put(menuType);
+        buf.putShort((short) numItems);
+
+        byte pos = 0;
+        boolean inMoreSection = false;
+
+        // IDs are 8-char hex strings, in upper case
+        final Pattern ID_REGEX = Pattern.compile("^[0-9A-F]{8}$");
+
+        for (final String name : enabledList) {
+            if (name.equals("more")) {
+                inMoreSection = true;
+                pos = 0;
+                continue;
+            }
+
+            final String id = idMap.containsKey(name) ? Objects.requireNonNull(idMap.get(name)) : name;
+            if (!ID_REGEX.matcher(id).find()) {
+                LOG.error("Screen item id '{}' is not 8-char hex string", id);
+                continue;
+            }
+
+            final byte sectionKey;
+            if (inMoreSection) {
+                // In more section
+                sectionKey = DISPLAY_ITEMS_SECTION_MORE;
+            } else {
+                // In main section
+                sectionKey = DISPLAY_ITEMS_SECTION_MAIN;
+            }
+
+            // Screen IDs are sent as literal hex strings
+            buf.put(id.getBytes(StandardCharsets.UTF_8));
+            buf.put((byte) 0);
+            buf.put(sectionKey);
+            buf.put(pos++);
+            final Flags flags = flagsMap.get(id);
+            if (flags != null && flags.value != 0) {
+                buf.put((byte) (flags.value));
+                if (flags.version != null) {
+                    buf.put(flags.version.getBytes(StandardCharsets.UTF_8));
+                    buf.put((byte) 0);
+                }
+                if (flags.unk != null) {
+                    buf.put(flags.unk.getBytes(StandardCharsets.UTF_8));
+                    buf.put((byte) 0);
+                }
+            } else {
+                buf.put((byte) 0);
+            }
+        }
+
+        // Set all disabled items
+        pos = 0;
+        for (final String name : allSettings) {
+            if (enabledList.contains(name) || name.equals("more")) {
+                continue;
+            }
+
+            final String id = idMap.containsKey(name) ? Objects.requireNonNull(idMap.get(name)) : name;
+            if (!ID_REGEX.matcher(id).find()) {
+                LOG.error("Screen item id '{}' is not 8-char hex string", id);
+                continue;
+            }
+
+            // Screen IDs are sent as literal hex strings
+            buf.put(id.getBytes(StandardCharsets.UTF_8));
+            buf.put((byte) 0);
+            buf.put(DISPLAY_ITEMS_SECTION_DISABLED);
+            buf.put(pos++);
+            final Flags flags = flagsMap.get(id);
+            if (flags != null && flags.value != 0) {
+                buf.put((byte) (flags.value));
+                if (flags.version != null) {
+                    buf.put(flags.version.getBytes(StandardCharsets.UTF_8));
+                    buf.put((byte) 0);
+                }
+                if (flags.unk != null) {
+                    buf.put(flags.unk.getBytes(StandardCharsets.UTF_8));
+                    buf.put((byte) 0);
+                }
+            } else {
+                buf.put((byte) 0);
+            }
+        }
+
+        write(builder, buf.array());
+    }
+
+    private static class Flags {
+        private int value = 0;
+        private String version = null;
+        private String unk = null;
+    }
+
+    /**
+     * When requesting the items from the watch, it sends a bitmask with flags, and a version / unknown
+     * string. We need to persist this so that we can send it back when setting the display items.
+     */
+    private static class FlagsMap extends HashMap<String, Flags> {
+        @Nullable
+        private String toPrefValue() {
+            final JSONObject jsonObject = new JSONObject();
+
+            for (final Map.Entry<String, Flags> e : entrySet()) {
+                final JSONObject entryObj = new JSONObject();
+
+                try {
+                    entryObj.put("value", e.getValue().value);
+                    if (e.getValue().version != null) {
+                        entryObj.put("version", e.getValue().version);
+                    }
+                    if (e.getValue().unk != null) {
+                        entryObj.put("unk", e.getValue().unk);
+                    }
+                    jsonObject.put(e.getKey(), entryObj);
+                } catch (final JSONException ex) {
+                    LOG.error("This should never happen", ex);
+                    return null;
+                }
+            }
+
+            return jsonObject.toString();
+        }
+
+        @Nullable
+        private static FlagsMap fromPrefValue(final String value) {
+            final FlagsMap flagsMap = new FlagsMap();
+
+            final JSONObject flagsMapObj;
+            try {
+                flagsMapObj = new JSONObject(value);
+
+                final Iterator<String> keys = flagsMapObj.keys();
+                while (keys.hasNext()) {
+                    final String id = keys.next();
+                    final JSONObject flagsObj = flagsMapObj.getJSONObject(id);
+
+                    final Flags flags = new Flags();
+                    if (flagsObj.has("value")) {
+                        flags.value = flagsObj.getInt("value");
+                    }
+                    if (flagsObj.has("version")) {
+                        flags.version = flagsObj.getString("version");
+                    }
+                    if (flagsObj.has("unk")) {
+                        flags.unk = flagsObj.getString("unk");
+                    }
+                    flagsMap.put(id, flags);
+                }
+
+                return flagsMap;
+            } catch (final JSONException ex) {
+                LOG.error("This should never happen", ex);
+                return null;
+            }
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsFileTransferService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsFileTransferService.java
new file mode 100644
index 0000000..5b0bd8d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsFileTransferService.java
@@ -0,0 +1,615 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services;
+
+import org.apache.commons.lang3.ArrayUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.ByteArrayOutputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.zip.DataFormatException;
+import java.util.zip.Deflater;
+import java.util.zip.Inflater;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.AbstractZeppOsService;
+import nodomain.freeyourgadget.gadgetbridge.util.CheckSums;
+import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
+
+public class ZeppOsFileTransferService extends AbstractZeppOsService {
+    private static final Logger LOG = LoggerFactory.getLogger(ZeppOsFileTransferService.class);
+
+    private static final short ENDPOINT = 0x000d;
+
+    private static final byte CMD_CAPABILITIES_REQUEST = 0x01;
+    private static final byte CMD_CAPABILITIES_RESPONSE = 0x02;
+    private static final byte CMD_TRANSFER_REQUEST = 0x03;
+    private static final byte CMD_TRANSFER_RESPONSE = 0x04;
+    private static final byte CMD_DATA_SEND = 0x10;
+    private static final byte CMD_DATA_ACK = 0x11;
+    private static final byte CMD_DATA_V3_SEND = 0x12;
+    private static final byte CMD_DATA_V3_ACK = 0x13;
+
+    private static final byte FLAG_FIRST_CHUNK = 0x01;
+    private static final byte FLAG_LAST_CHUNK = 0x02;
+    private static final byte FLAG_CRC = 0x04;
+
+    private final Map<Byte, FileTransferRequest> mSessionRequests = new HashMap<>();
+
+    private int mVersion = -1;
+    private int mChunkSize = -1;
+    private int mCompressedChunkSize = -1;
+
+    public ZeppOsFileTransferService(final ZeppOsSupport support) {
+        super(support, false);
+    }
+
+    @Override
+    public short getEndpoint() {
+        return ENDPOINT;
+    }
+
+    @Override
+    public void handlePayload(final byte[] payload) {
+        byte session;
+        byte status;
+
+        switch (payload[0]) {
+            case CMD_CAPABILITIES_RESPONSE:
+                mVersion = payload[1] & 0xff;
+                if (mVersion != 1 && mVersion != 2 && mVersion != 3) {
+                    LOG.error("Unsupported file transfer service version: {}", mVersion);
+                    return;
+                }
+                mChunkSize = BLETypeConversions.toUint16(payload, 2);
+                if (mVersion == 3) {
+                    // TODO parse the rest for v3
+                    mCompressedChunkSize = BLETypeConversions.toUint32(payload, 4);
+                    final TransactionBuilder builder = getSupport().createTransactionBuilder("enable file transfer v3 notifications");
+                    builder.notify(getSupport().getCharacteristic(HuamiService.UUID_CHARACTERISTIC_ZEPP_OS_FILE_TRANSFER_V3), true);
+                    builder.queue(getSupport().getQueue());
+                }
+                LOG.info(
+                    "Got file transfer service: version={}, chunkSize={}, compressedChunkSize={}",
+                    mVersion,
+                    mChunkSize,
+                    mCompressedChunkSize
+                );
+                return;
+            case CMD_TRANSFER_REQUEST:
+                handleFileTransferRequest(payload);
+                return;
+            case CMD_TRANSFER_RESPONSE:
+                session = payload[1];
+                status = payload[2];
+                final int existingProgress = BLETypeConversions.toUint32(payload, 3);
+                LOG.info("Band acknowledged file transfer request: session={}, status={}, existingProgress={}", session, status, existingProgress);
+                if (status != 0) {
+                    LOG.error("Unexpected status from band for session {}, aborting", session);
+                    onUploadFinish(session, false);
+                    return;
+                }
+                if (existingProgress != 0) {
+                    LOG.info("Updating existing progress for session {} to {}", session, existingProgress);
+                    final FileTransferRequest request = mSessionRequests.get(session);
+                    if (request == null) {
+                        LOG.error("No request found for session {}", session);
+                        return;
+                    }
+                    request.setProgress(existingProgress);
+                }
+                sendNextQueuedData(session);
+                return;
+            case CMD_DATA_SEND:
+                handleFileTransferData(payload);
+                return;
+            case CMD_DATA_ACK:
+                session = payload[1];
+                status = payload[2];
+                LOG.info("Band acknowledged file transfer data: session={}, status={}", session, status);
+                if (status != 0) {
+                    LOG.error("Unexpected status from band, aborting session {}", session);
+                    onUploadFinish(session, false);
+                    return;
+                }
+                sendNextQueuedData(session);
+                return;
+            default:
+                LOG.warn("Unexpected file transfer byte {}", String.format("0x%02x", payload[0]));
+        }
+    }
+
+    @Override
+    public void initialize(final TransactionBuilder builder) {
+        requestCapability(builder);
+    }
+
+    public void requestCapability(final TransactionBuilder builder) {
+        write(builder, new byte[]{CMD_CAPABILITIES_REQUEST});
+    }
+
+    private void handleFileTransferRequest(final byte[] payload) {
+        // File transfer request initialized from watch
+        int pos = 1;
+        final byte session = payload[pos++];
+        final String url = StringUtils.untilNullTerminator(payload, pos);
+        if (url == null) {
+            LOG.error("Unable to parse url from transfer request");
+            return;
+        }
+        pos += url.length() + 1;
+        final String filename = StringUtils.untilNullTerminator(payload, pos);
+        if (filename == null) {
+            LOG.error("Unable to parse filename from transfer request");
+            return;
+        }
+        pos += filename.length() + 1;
+        final int length = BLETypeConversions.toUint32(payload, pos);
+        pos += 4;
+        final int crc32 = BLETypeConversions.toUint32(payload, pos);
+        pos += 4;
+
+        final boolean compressed;
+        if (pos < payload.length) {
+            final Boolean compressedBoolean = booleanFromByte(payload[pos]);
+            if (compressedBoolean == null) {
+                LOG.warn("Unknown compression type {}", payload[pos]);
+                return;
+            }
+            compressed = compressedBoolean;
+        } else {
+            compressed = false;
+        }
+
+        LOG.info("Got transfer request: session={}, url={}, filename={}, length={}, compressed={}", session, url, filename, length, compressed);
+
+        final FileTransferRequest request = new FileTransferRequest(
+                url,
+                filename,
+                new byte[length],
+                compressed,
+                compressed ? mCompressedChunkSize : mChunkSize,
+                getSupport()
+        );
+        request.setCrc32(crc32);
+
+        if (mVersion < 3) {
+            final ByteBuffer buf = ByteBuffer.allocate(7).order(ByteOrder.LITTLE_ENDIAN);
+            buf.order(ByteOrder.LITTLE_ENDIAN);
+            buf.put(CMD_TRANSFER_RESPONSE);
+            buf.put(session);
+            buf.put((byte) 0x00);
+            buf.putInt(0);
+
+            write("send file transfer response", buf.array());
+        } else {
+            // FIXME: Receive files on v3
+            LOG.error("Receiving files on V3 is not implemented");
+            return;
+        }
+
+        mSessionRequests.put(session, request);
+    }
+
+    private void handleFileTransferData(final byte[] payload) {
+        final ByteBuffer buf = ByteBuffer.wrap(payload).order(ByteOrder.LITTLE_ENDIAN);
+        buf.get(); // Discard first byte
+        final byte flags = buf.get();
+        final boolean firstPacket = (flags == 1);
+        final boolean lastPacket = (flags == 2);
+        final byte session = buf.get();
+        final byte index = buf.get();
+
+        if ((flags & 0x01) != 0) {
+            buf.getInt(); // ?
+        }
+
+        final short size = buf.getShort();
+
+        final FileTransferRequest request = mSessionRequests.get(session);
+        if (request == null) {
+            LOG.error("No request found for session {}", session);
+            return;
+        }
+
+        if (index != request.index) {
+            LOG.warn("Unexpected index {}, expected {}", index, request.index);
+            return;
+        }
+
+        if (firstPacket && request.getProgress() != 0) {
+            LOG.warn("Got first packet, but progress is {}", request.getProgress());
+            return;
+        }
+
+        buf.get(request.getBytes(), request.getProgress(), size);
+        request.setIndex((byte) (index + 1));
+        request.setProgress(request.getProgress() + size);
+
+        LOG.debug("Got data for session={}, progress={}/{}", session, request.getProgress(), request.getSize());
+
+        write("ack file data", new byte[]{CMD_DATA_ACK, session, 0x00});
+
+        if (lastPacket) {
+            mSessionRequests.remove(session);
+
+            final byte[] data;
+            if (request.isCompressed()) {
+                data = decompress(request.getBytes());
+                if (data == null) {
+                    LOG.error("Failed to decompress bytes for session={}", session);
+                    return;
+                }
+            } else {
+                data = request.getBytes();
+            }
+
+            final int checksum = CheckSums.getCRC32(data);
+            if (checksum != request.getCrc32()) {
+                LOG.warn("Checksum mismatch: expected {}, got {}", request.getCrc32(), checksum);
+                return;
+            }
+
+            request.getCallback().onFileDownloadFinish(request.getUrl(), request.getFilename(), data);
+        }
+    }
+
+    public static byte[] compress(final byte[] data) {
+        final Deflater deflater = new Deflater();
+        deflater.setInput(data);
+        deflater.finish();
+        final ByteArrayOutputStream baos = new ByteArrayOutputStream(data.length);
+        final byte[] buf = new byte[8096];
+        int read;
+        while ((read = deflater.deflate(buf)) > 0) {
+            baos.write(buf, 0, read);
+        }
+
+        return baos.toByteArray();
+    }
+
+    public static byte[] decompress(final byte[] data) {
+        final Inflater inflater = new Inflater();
+        final byte[] output = new byte[data.length];
+        inflater.setInput(data);
+        try {
+            inflater.inflate(output);
+        } catch (final DataFormatException e) {
+            LOG.error("Failed to decompress data", e);
+            return null;
+        } finally {
+            inflater.end();
+        }
+
+        return output;
+    }
+
+    public void sendFile(final String url, final String filename, final byte[] bytes, final boolean compress, final Callback callback) {
+        if (mChunkSize < 0) {
+            LOG.error("Service not initialized, refusing to send {}", url);
+            callback.onFileUploadFinish(false);
+            return;
+        }
+
+        LOG.info("Sending {} bytes to {} in {}", bytes.length, filename, url);
+
+        final FileTransferRequest request = new FileTransferRequest(
+                url,
+                filename,
+                bytes,
+                compress && mCompressedChunkSize > 0,
+                compress && mCompressedChunkSize > 0 ? mCompressedChunkSize : mChunkSize,
+                callback
+        );
+
+        if (mVersion == 3 && !mSessionRequests.isEmpty()) {
+            // FIXME non-zero session on v3
+            LOG.error("File transfer v3 only supports single session, not sending file");
+            callback.onFileUploadFinish(false);
+            return;
+        }
+
+        byte session = (byte) mSessionRequests.size();
+        while (mSessionRequests.containsKey(session)) {
+            session++;
+        }
+
+        int payloadSize = 2 + url.length() + 1 + filename.length() + 1 + 4 + 4;
+        if (mVersion == 3) {
+            payloadSize += 2;
+            if (compress) {
+                payloadSize += 4;
+            }
+        }
+
+        final ByteBuffer buf = ByteBuffer.allocate(payloadSize);
+        buf.order(ByteOrder.LITTLE_ENDIAN);
+        buf.put(CMD_TRANSFER_REQUEST);
+        buf.put(session);
+        buf.put(url.getBytes(StandardCharsets.UTF_8));
+        buf.put((byte) 0x00);
+        buf.put(filename.getBytes(StandardCharsets.UTF_8));
+        buf.put((byte) 0x00);
+        buf.putInt(bytes.length);
+        buf.putInt(request.getCrc32());
+        if (mVersion == 3) {
+            buf.put((byte) (compress ? 1 : 0));
+            if (compress) {
+                buf.putInt(mCompressedChunkSize);
+            }
+            buf.put((byte) 0);
+        }
+
+        write("send file upload request", buf.array());
+
+        mSessionRequests.put(session, request);
+    }
+
+    private void sendNextQueuedData(final byte session) {
+        final FileTransferRequest request = mSessionRequests.get(session);
+        if (request == null) {
+            LOG.error("No request found for session {}", session);
+            return;
+        }
+
+        if (request.getProgress() >= request.getSize()) {
+            LOG.info("Finished sending {}", request.getUrl());
+            onUploadFinish(session, true);
+            return;
+        }
+
+        LOG.debug("Sending file data for session={}, progress={}, index={}", session, request.getProgress(), request.getIndex());
+
+        if (mVersion < 3) {
+            writeChunkV1(request, session);
+        } else {
+            if (session != 0) {
+                // FIXME non-zero session on v3
+                LOG.error("Sending non-zero session on v3 is not supported, got session={}", session);
+                mSessionRequests.remove(session);
+                return;
+            }
+            writeChunkV3(request);
+        }
+    }
+
+    private void writeChunkV1(final FileTransferRequest request, final byte session) {
+        final ByteBuffer buf = ByteBuffer.allocate(10 + request.getChunkSize());
+        buf.order(ByteOrder.LITTLE_ENDIAN);
+        buf.put(CMD_DATA_SEND);
+
+        byte flags = 0;
+        if (request.getProgress() == 0) {
+            flags |= FLAG_FIRST_CHUNK;
+        }
+        if (request.getProgress() + request.getChunkSize() >= request.getSize()) {
+            flags |= FLAG_LAST_CHUNK;
+        }
+
+        buf.put(flags);
+        buf.put(session);
+        buf.put(request.getIndex());
+        if ((flags & FLAG_FIRST_CHUNK) > 0) {
+            buf.put((byte) 0x00); // ?
+            buf.put((byte) 0x00); // ?
+            buf.put((byte) 0x00); // ?
+            buf.put((byte) 0x00); // ?
+        }
+
+        final byte[] payload = ArrayUtils.subarray(
+                request.getBytes(),
+                request.getProgress(),
+                request.getProgress() + request.getChunkSize()
+        );
+
+        buf.putShort((short) payload.length);
+        buf.put(payload);
+
+        request.setProgress(request.getProgress() + payload.length);
+        request.setIndex((byte) (request.getIndex() + 1));
+        request.getCallback().onFileUploadProgress(request.getProgress());
+
+        write("send file data", buf.array());
+    }
+
+    private void writeChunkV3(final FileTransferRequest request) {
+        final byte[] chunk = ArrayUtils.subarray(
+                request.getBytes(),
+                request.getProgress(),
+                request.getProgress() + request.getChunkSize()
+        );
+
+        byte flags = 0;
+        if (request.getProgress() == 0) {
+            flags |= FLAG_FIRST_CHUNK;
+        }
+        if (request.getProgress() + request.getChunkSize() >= request.getSize()) {
+            flags |= FLAG_LAST_CHUNK;
+        }
+
+        final int partSize = getSupport().getMTU() - 3;
+
+        final ByteBuffer buf = ByteBuffer.allocate(chunk.length + 5);
+        buf.order(ByteOrder.LITTLE_ENDIAN);
+        buf.put(CMD_DATA_V3_SEND);
+        buf.put(flags);
+        buf.put(request.getIndex());
+        buf.putShort((short) chunk.length);
+        buf.put(chunk);
+
+        final byte[] payload = buf.array();
+
+        final TransactionBuilder builder = getSupport().createTransactionBuilder("send chunk v3");
+        for (int i = 0; i < payload.length; i += partSize) {
+            final byte[] part = ArrayUtils.subarray(payload, i, i + partSize);
+            builder.write(
+                    getSupport().getCharacteristic(HuamiService.UUID_CHARACTERISTIC_ZEPP_OS_FILE_TRANSFER_V3),
+                    part
+            );
+        }
+        builder.queue(getSupport().getQueue());
+
+        request.setProgress(request.getProgress() + chunk.length);
+        request.setIndex((byte) (request.getIndex() + 1));
+        request.getCallback().onFileUploadProgress(request.getProgress());
+    }
+
+    private void onUploadFinish(final byte session, final boolean success) {
+        final FileTransferRequest request = mSessionRequests.get(session);
+        if (request == null) {
+            LOG.error("No request found for session {}", session);
+            return;
+        }
+
+        mSessionRequests.remove(session);
+
+        request.getCallback().onFileUploadFinish(success);
+    }
+
+    public void onCharacteristicChanged(final byte[] value) {
+        if (value[0] != CMD_DATA_V3_ACK) {
+            LOG.error("Got non-ack on file transfer characteristic");
+            return;
+        }
+
+        final byte session = (byte) 0; // FIXME non-zero session on v3
+        final byte status = value[1];
+        final byte chunkIndex = value[2];
+        final byte unk1 = value[3]; // 1/2?
+
+        LOG.info(
+                "Band acknowledged file transfer data: session={}, status={}, chunkIndex={}, unk1={}",
+                session,
+                status,
+                chunkIndex,
+                unk1
+        );
+
+        final FileTransferRequest request = mSessionRequests.get(session);
+        if (request == null) {
+            LOG.error("No request found for v3 session {}", session);
+            return;
+        }
+
+        if (status != 0) {
+            LOG.error("Unexpected status from band, aborting session {}", session);
+            onUploadFinish(session, false);
+            return;
+        }
+
+        if (request.getIndex() - 1 != chunkIndex) {
+            LOG.error("Got ack for unexpected chunk index {}, expected {}", chunkIndex, request.getIndex() - 1);
+            onUploadFinish(session, false);
+            return;
+        }
+
+        sendNextQueuedData(session);
+    }
+
+    /**
+     * Wrapper class to keep track of ongoing file send requests and their progress.
+     */
+    public static class FileTransferRequest {
+        private final String url;
+        private final String filename;
+        private final byte[] bytes;
+        private final boolean compressed;
+        private final int chunkSize;
+        private final Callback callback;
+        private int progress = 0;
+        private byte index = 0;
+        private int crc32;
+
+        public FileTransferRequest(final String url, final String filename, final byte[] bytes, boolean compressed, int chunkSize, final Callback callback) {
+            this.url = url;
+            this.filename = filename;
+            this.bytes = compressed ? compress(bytes) : bytes;
+            this.compressed = compressed;
+            this.chunkSize = chunkSize;
+            this.callback = callback;
+            this.crc32 = CheckSums.getCRC32(bytes);
+        }
+
+        public String getUrl() {
+            return url;
+        }
+
+        public String getFilename() {
+            return filename;
+        }
+
+        public byte[] getBytes() {
+            return bytes;
+        }
+
+        public int getSize() {
+            return bytes.length;
+        }
+
+        public boolean isCompressed() {
+            return compressed;
+        }
+
+        public int getChunkSize() {
+            return chunkSize;
+        }
+
+        public Callback getCallback() {
+            return callback;
+        }
+
+        public int getProgress() {
+            return progress;
+        }
+
+        public void setProgress(final int progress) {
+            this.progress = progress;
+        }
+
+        public byte getIndex() {
+            return index;
+        }
+
+        public void setIndex(final byte index) {
+            this.index = index;
+        }
+
+        public int getCrc32() {
+            return crc32;
+        }
+
+        public void setCrc32(final int crc32) {
+            this.crc32 = crc32;
+        }
+    }
+
+    public interface Callback {
+        void onFileUploadFinish(boolean success);
+
+        void onFileUploadProgress(int progress);
+
+        void onFileDownloadFinish(String url, String filename, byte[] data);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsFtpServerService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsFtpServerService.java
new file mode 100644
index 0000000..3033161
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsFtpServerService.java
@@ -0,0 +1,177 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services;
+
+import android.widget.Toast;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.ByteArrayOutputStream;
+import java.nio.charset.StandardCharsets;
+
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdatePreferences;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.AbstractZeppOsService;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
+
+public class ZeppOsFtpServerService extends AbstractZeppOsService {
+    private static final Logger LOG = LoggerFactory.getLogger(ZeppOsFtpServerService.class);
+
+    private static final short ENDPOINT = 0x0006;
+
+    public static final byte FTP_CMD_START = 0x01;
+    public static final byte FTP_CMD_STOP = 0x02;
+    public static final byte FTP_CMD_INFO = 0x03;
+    public static final byte FTP_INFO_STOP = 0x00;
+    public static final byte FTP_INFO_STARTED = 0x02;
+
+    private Callback mCallback = null;
+
+    public ZeppOsFtpServerService(final ZeppOsSupport support) {
+        super(support, true);
+    }
+
+    @Override
+    public short getEndpoint() {
+        return ENDPOINT;
+    }
+
+    @Override
+    public void handlePayload(final byte[] payload) {
+        switch (payload[0]) {
+            case FTP_CMD_INFO:
+                switch (payload[1]) {
+                    case FTP_INFO_STOP:
+                        LOG.info("FTP Server stopped");
+
+                        if (mCallback != null) {
+                            mCallback.onFtpServerStop();
+                        } else {
+                            // If there's no callback, show a toast (eg. used from developer options)
+                            GB.toast("FTP Server stopped", Toast.LENGTH_SHORT, GB.INFO);
+                        }
+
+                        // TODO: The toast + preference update should not be here
+                        final GBDeviceEventUpdatePreferences wifiStoppedEvent = new GBDeviceEventUpdatePreferences()
+                                .withPreference(DeviceSettingsPreferenceConst.FTP_SERVER_STATUS, "Stopped");
+                        getSupport().evaluateGBDeviceEvent(wifiStoppedEvent);
+                        return;
+                    case FTP_INFO_STARTED:
+                        final String address = StringUtils.untilNullTerminator(payload, 2);
+                        if (address == null) {
+                            LOG.error("Unable to parse address from FTP info payload");
+                            return;
+                        }
+                        final String username = StringUtils.untilNullTerminator(payload, 2 + address.length() + 1);
+                        if (username == null) {
+                            LOG.error("Unable to parse username from FTP info payload");
+                            return;
+                        }
+                        LOG.info("FTP Server started, address = {}, username = {}", address, username);
+
+                        if (mCallback != null) {
+                            mCallback.onFtpServerStart(address, username);
+                        } else {
+                            // If there's no callback, show a toast (eg. used from developer options)
+                            GB.toast("FTP Server started", Toast.LENGTH_SHORT, GB.INFO);
+                        }
+
+                        // TODO: The toast + preference update should not be here
+                        final GBDeviceEventUpdatePreferences wifiStartedEvent = new GBDeviceEventUpdatePreferences()
+                                .withPreference(DeviceSettingsPreferenceConst.FTP_SERVER_ADDRESS, address)
+                                .withPreference(DeviceSettingsPreferenceConst.FTP_SERVER_USERNAME, username)
+                                .withPreference(DeviceSettingsPreferenceConst.FTP_SERVER_STATUS, "Started");
+                        getSupport().evaluateGBDeviceEvent(wifiStartedEvent);
+                        return;
+                    default:
+                        LOG.warn("Unexpected FTP info byte {}", String.format("0x%02x", payload[1]));
+                        break;
+                }
+                return;
+            default:
+                LOG.warn("Unexpected FTP byte {}", String.format("0x%02x", payload[0]));
+        }
+    }
+
+    @Override
+    public boolean onSendConfiguration(final String config, final Prefs prefs) {
+        switch (config) {
+            case DeviceSettingsPreferenceConst.FTP_SERVER_START:
+                startFtpServer(prefs.getString(DeviceSettingsPreferenceConst.FTP_SERVER_ROOT_DIR, ""));
+                return true;
+            case DeviceSettingsPreferenceConst.FTP_SERVER_STOP:
+                stopFtpServer();
+                return true;
+            case DeviceSettingsPreferenceConst.FTP_SERVER_ROOT_DIR:
+            case DeviceSettingsPreferenceConst.FTP_SERVER_ADDRESS:
+            case DeviceSettingsPreferenceConst.FTP_SERVER_USERNAME:
+            case DeviceSettingsPreferenceConst.FTP_SERVER_STATUS:
+                // Ignore preferences that are not reloadable
+                return true;
+        }
+
+        return false;
+    }
+
+    @Override
+    public void initialize(final TransactionBuilder builder) {
+    }
+
+    public void setCallback(final Callback callback) {
+        mCallback = callback;
+    }
+
+    public void removeCallback() {
+        mCallback = null;
+    }
+
+    public void startFtpServer(final String rootDir) {
+        LOG.info("Starting FTP Server, rootDir={}", rootDir);
+
+        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+
+        try {
+            baos.write(FTP_CMD_START);
+            if (!StringUtils.isNullOrEmpty(rootDir)) {
+                baos.write(rootDir.getBytes(StandardCharsets.UTF_8));
+                baos.write(0);
+            }
+        } catch (final Exception e) {
+            LOG.error("Failed to create command", e);
+            return;
+        }
+
+        write("start ftp server", baos.toByteArray());
+    }
+
+    public void stopFtpServer() {
+        LOG.info("Stopping FTP Server");
+
+        write("stop ftp server", FTP_CMD_STOP);
+    }
+
+    public interface Callback {
+        void onFtpServerStart(String address, String username);
+
+        void onFtpServerStop();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsHttpService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsHttpService.java
new file mode 100644
index 0000000..96581fb
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsHttpService.java
@@ -0,0 +1,139 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLDecoder;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Map;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsWeather;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.AbstractZeppOsService;
+import nodomain.freeyourgadget.gadgetbridge.util.HttpUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
+
+public class ZeppOsHttpService extends AbstractZeppOsService {
+    private static final Logger LOG = LoggerFactory.getLogger(ZeppOsHttpService.class);
+
+    private static final short ENDPOINT = 0x0001;
+
+    public static final byte CMD_REQUEST = 0x01;
+    public static final byte CMD_RESPONSE = 0x02;
+
+    public static final byte RESPONSE_SUCCESS = 0x01;
+    public static final byte RESPONSE_NO_INTERNET = 0x02;
+
+    public ZeppOsHttpService(final ZeppOsSupport support) {
+        super(support, true);
+    }
+
+    @Override
+    public short getEndpoint() {
+        return ENDPOINT;
+    }
+
+    @Override
+    public void handlePayload(final byte[] payload) {
+        switch (payload[0]) {
+            case CMD_REQUEST:
+                int pos = 1;
+                final byte requestId = payload[pos++];
+                final String method = StringUtils.untilNullTerminator(payload, pos);
+                if (method == null) {
+                    LOG.error("Failed to decode method from payload");
+                    return;
+                }
+                pos += method.length() + 1;
+                final String url = StringUtils.untilNullTerminator(payload, pos);
+                if (url == null) {
+                    LOG.error("Failed to decode method from payload");
+                    return;
+                }
+                // headers after pos += url.length() + 1;
+
+                LOG.info("Got HTTP {} request: {}", method, url);
+
+                handleUrlRequest(requestId, method, url);
+                return;
+            default:
+                LOG.warn("Unexpected HTTP payload byte {}", String.format("0x%02x", payload[0]));
+        }
+    }
+
+    private void handleUrlRequest(final byte requestId, final String method, final String urlString) {
+        if (!"GET".equals(method)) {
+            LOG.error("Unable to handle HTTP method {}", method);
+            // TODO: There's probably a "BAD REQUEST" response or similar
+            replyHttpNoInternet(requestId);
+            return;
+        }
+
+        final URL url;
+        try {
+            url = new URL(urlString);
+        } catch (final MalformedURLException e) {
+            LOG.error("Failed to parse url", e);
+            replyHttpNoInternet(requestId);
+            return;
+        }
+
+        final String path = url.getPath();
+        final Map<String, String> query = HttpUtils.urlQueryParameters(url);
+
+        if (path.startsWith("/weather/")) {
+            final ZeppOsWeather.Response response = ZeppOsWeather.handleHttpRequest(path, query);
+            replyHttpSuccess(requestId, response.getHttpStatusCode(), response.toJson());
+            return;
+        }
+
+        LOG.error("Unhandled URL {}", url);
+        replyHttpNoInternet(requestId);
+    }
+
+    private void replyHttpNoInternet(final byte requestId) {
+        LOG.info("Replying with no internet to http request {}", requestId);
+
+        final byte[] cmd = new byte[]{CMD_RESPONSE, requestId, RESPONSE_NO_INTERNET, 0x00, 0x00, 0x00, 0x00};
+
+        write("http reply no internet", cmd);
+    }
+
+    private void replyHttpSuccess(final byte requestId, final int status, final String content) {
+        LOG.debug("Replying with http {} request {} with {}", status, requestId, content);
+
+        final byte[] contentBytes = content.getBytes(StandardCharsets.UTF_8);
+        final ByteBuffer buf = ByteBuffer.allocate(8 + contentBytes.length);
+        buf.order(ByteOrder.LITTLE_ENDIAN);
+
+        buf.put(CMD_RESPONSE);
+        buf.put(requestId);
+        buf.put(RESPONSE_SUCCESS);
+        buf.put((byte) status);
+        buf.putInt(contentBytes.length);
+        buf.put(contentBytes);
+
+        write("http reply success", buf.array());
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsLogsService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsLogsService.java
new file mode 100644
index 0000000..ae286be
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsLogsService.java
@@ -0,0 +1,224 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services;
+
+import android.annotation.SuppressLint;
+import android.widget.Toast;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.StandardCharsets;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Set;
+
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdatePreferences;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.AbstractZeppOsService;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
+
+public class ZeppOsLogsService extends AbstractZeppOsService {
+    private static final Logger LOG = LoggerFactory.getLogger(ZeppOsLogsService.class);
+
+    private static final short ENDPOINT = 0x003a;
+
+    public static final byte CMD_CAPABILITIES_REQUEST = 0x01;
+    public static final byte CMD_CAPABILITIES_RESPONSE = 0x02;
+    public static final byte CMD_LOGS_START = 0x03;
+    public static final byte CMD_LOGS_START_ACK = 0x04;
+    public static final byte CMD_LOGS_STOP = 0x05;
+    public static final byte CMD_LOGS_STOP_ACK = 0x06;
+    public static final byte CMD_LOGS_DATA = 0x07;
+    public static final byte CMD_UNKNOWN_8 = 0x08;
+    public static final byte CMD_UNKNOWN_9 = 0x09;
+
+    public static final String PREF_VERSION = "zepp_os_logs_version";
+
+    private String logsType;
+    private final Set<Byte> sessions = new HashSet<>();
+
+    @SuppressLint("SimpleDateFormat")
+    private final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
+
+    public ZeppOsLogsService(final ZeppOsSupport support) {
+        super(support, false);
+    }
+
+    @Override
+    public short getEndpoint() {
+        return ENDPOINT;
+    }
+
+    @Override
+    public void handlePayload(final byte[] payload) {
+        switch (payload[0]) {
+            case CMD_CAPABILITIES_RESPONSE:
+                handleCapabilitiesResponse(payload);
+                break;
+            case CMD_LOGS_START_ACK:
+                if (payload[1] != 1) {
+                    LOG.warn("Failed to start logs, status = {}", payload[1]);
+                    GB.toast(getContext(), "Failed to start logs", Toast.LENGTH_SHORT, GB.WARN);
+                    return;
+                }
+                final byte sessionId = payload[2];
+                LOG.info("Got logs start ack, sessionId = {}", sessionId);
+                GB.toast(getContext(), "App logs started", Toast.LENGTH_SHORT, GB.INFO);
+                sessions.add(sessionId);
+                break;
+            case CMD_LOGS_STOP_ACK:
+                LOG.info("Got logs stop ack, status = {}", payload[1]);
+                GB.toast(getContext(), "App logs stopped", Toast.LENGTH_SHORT, GB.INFO);
+                break;
+            case CMD_LOGS_DATA:
+                handleLogsData(payload);
+                break;
+            case CMD_UNKNOWN_8:
+                LOG.info("Got unknown 8, replying with unknown 9");
+                write("reply logs unknown 9", CMD_UNKNOWN_9);
+                break;
+            default:
+                LOG.warn("Unexpected logs payload byte {}", String.format("0x%02x", payload[0]));
+        }
+    }
+
+    @Override
+    public boolean onSendConfiguration(final String config, final Prefs prefs) {
+        switch (config) {
+            case DeviceSettingsPreferenceConst.PREF_APP_LOGS_START:
+                start();
+                return true;
+            case DeviceSettingsPreferenceConst.PREF_APP_LOGS_STOP:
+                stop();
+                return true;
+        }
+
+        return false;
+    }
+
+    @Override
+    public void initialize(final TransactionBuilder builder) {
+        requestCapabilities(builder);
+    }
+
+    public void requestCapabilities(final TransactionBuilder builder) {
+        write(builder, CMD_CAPABILITIES_REQUEST);
+    }
+
+    public void start() {
+        if (logsType == null) {
+            LOG.error("logsType is null");
+            return;
+        }
+
+        LOG.info("Starting logs");
+
+        final byte[] logsTypeBytes = logsType.getBytes(StandardCharsets.UTF_8);
+        final ByteBuffer buf = ByteBuffer.allocate(1 + logsTypeBytes.length + 1)
+                .order(ByteOrder.LITTLE_ENDIAN);
+
+        buf.put(CMD_LOGS_START);
+        buf.put(logsTypeBytes);
+        buf.put((byte) 0);
+
+        write("start logs", buf.array());
+    }
+
+    public void stop() {
+        LOG.info("Stopping {} log sessions", sessions.size());
+
+        for (final Byte session : sessions) {
+            stop(session);
+        }
+    }
+
+    private void stop(final byte sessionId) {
+        final ByteBuffer buf = ByteBuffer.allocate(3)
+                .order(ByteOrder.LITTLE_ENDIAN);
+
+        buf.put(CMD_LOGS_STOP);
+        buf.put((byte) 0);
+        buf.put(sessionId);
+
+        write("stop logs session " + sessionId, buf.array());
+    }
+
+    private void handleCapabilitiesResponse(final byte[] payload) {
+        final int version = payload[1] & 0xFF;
+        if (version != 1) {
+            LOG.warn("Unsupported logs service version {}", version);
+            return;
+        }
+        final byte var1 = payload[2];
+        if (var1 != 1) {
+            LOG.warn("Unexpected value for var1 '{}'", var1);
+        }
+        final byte var2 = payload[3];
+        if (var2 != 0) {
+            LOG.warn("Unexpected value for var2 '{}'", var2);
+        }
+
+        logsType = StringUtils.untilNullTerminator(payload, 4);
+
+        LOG.info("Logs version={}, var1={}, var2={}, logsType={}", version, var1, var2, logsType);
+
+        getSupport().evaluateGBDeviceEvent(new GBDeviceEventUpdatePreferences(PREF_VERSION, version));
+    }
+
+    private void handleLogsData(final byte[] payload) {
+        final ByteBuffer buf = ByteBuffer.wrap(payload).order(ByteOrder.LITTLE_ENDIAN);
+        buf.get(); // discard first byte
+        final byte index = buf.get();
+        final byte sessionId = buf.get();
+        if (!sessions.contains(sessionId)) {
+            LOG.warn("Got log data for unknown session {}", sessionId);
+        }
+        final String appIdDecimal = StringUtils.untilNullTerminator(buf);
+        final byte unknown1 = buf.get();
+        if (unknown1 != 0) {
+            LOG.warn("Unexpected value for unknown1 = {}", unknown1);
+        }
+        final long timestampMillis = buf.getLong();
+        final byte unknown2 = buf.get();
+        if (unknown2 != 2) {
+            LOG.warn("Unexpected value for unknown2 = {}", unknown2);
+        }
+        final String message = StringUtils.untilNullTerminator(buf);
+        if (buf.position() < buf.limit()) {
+            LOG.warn("There are {} log data bytes still in the buffer", (buf.limit() - buf.position()));
+        }
+
+        LOG.info(
+                "Log entry - {} [{}] - {}",
+                sdf.format(new Date(timestampMillis)),
+                appIdDecimal,
+                message
+        );
+    }
+
+    public static boolean isSupported(final Prefs devicePrefs) {
+        return devicePrefs.getInt(PREF_VERSION, 0) == 1;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsLoyaltyCardService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsLoyaltyCardService.java
new file mode 100644
index 0000000..519b97f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsLoyaltyCardService.java
@@ -0,0 +1,264 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.ByteArrayOutputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+import nodomain.freeyourgadget.gadgetbridge.capabilities.loyaltycards.BarcodeFormat;
+import nodomain.freeyourgadget.gadgetbridge.capabilities.loyaltycards.LoyaltyCard;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdatePreferences;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.AbstractZeppOsService;
+import nodomain.freeyourgadget.gadgetbridge.util.MapUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+public class ZeppOsLoyaltyCardService extends AbstractZeppOsService {
+    private static final Logger LOG = LoggerFactory.getLogger(ZeppOsLoyaltyCardService.class);
+
+    private static final short ENDPOINT = 0x003c;
+
+    private static final byte CMD_CAPABILITIES_REQUEST = 0x01;
+    private static final byte CMD_CAPABILITIES_RESPONSE = 0x02;
+    private static final byte CMD_REQUEST = 0x05;
+    private static final byte CMD_RESPONSE = 0x06;
+    private static final byte CMD_SET = 0x03;
+    private static final byte CMD_SET_ACK = 0x04;
+    private static final byte CMD_UPDATE = 0x07;
+    private static final byte CMD_UPDATE_ACK = 0x08;
+    private static final byte CMD_ADD = 0x09;
+    private static final byte CMD_ADD_ACK = 0x0a;
+
+    private final List<BarcodeFormat> supportedFormats = new ArrayList<>();
+    private final List<Integer> supportedColors = new ArrayList<>();
+
+    public static final String PREF_VERSION = "zepp_os_loyalty_cards_version";
+
+    public ZeppOsLoyaltyCardService(final ZeppOsSupport support) {
+        super(support, false);
+    }
+
+    @Override
+    public short getEndpoint() {
+        return ENDPOINT;
+    }
+
+    @Override
+    public void handlePayload(final byte[] payload) {
+        switch (payload[0]) {
+            case CMD_CAPABILITIES_RESPONSE:
+                LOG.info("Loyalty cards capabilities, version1={}, version2={}", payload[1], payload[2]);
+
+                supportedFormats.clear();
+                supportedColors.clear();
+                int version = payload[1];
+
+                if (version != 1 || payload[2] != 1) {
+                    LOG.warn("Unexpected loyalty cards service version");
+                    return;
+                }
+
+                int pos = 3;
+
+                final byte numSupportedCardTypes = payload[pos++];
+                final Map<Byte, BarcodeFormat> barcodeFormatCodes = MapUtils.reverse(BARCODE_FORMAT_CODES);
+                for (int i = 0; i < numSupportedCardTypes; i++, pos++) {
+                    final BarcodeFormat barcodeFormat = barcodeFormatCodes.get(payload[pos]);
+                    if (barcodeFormat == null) {
+                        LOG.warn("Unknown barcode format {}", String.format("0x%02x", payload[pos]));
+                        continue;
+                    }
+                    supportedFormats.add(barcodeFormat);
+                }
+
+                final byte numSupportedColors = payload[pos++];
+                final Map<Byte, Integer> colorCodes = MapUtils.reverse(COLOR_CODES);
+                for (int i = 0; i < numSupportedColors; i++) {
+                    final Integer color = colorCodes.get(payload[pos]);
+                    if (color == null) {
+                        LOG.warn("Unknown color {}", String.format("0x%02x", payload[pos]));
+                        continue;
+                    }
+                    supportedColors.add(color);
+                }
+
+                getSupport().evaluateGBDeviceEvent(new GBDeviceEventUpdatePreferences(PREF_VERSION, version));
+                return;
+            case CMD_SET_ACK:
+                LOG.info("Loyalty cards set ACK, status = {}", payload[1]);
+                return;
+        }
+
+        LOG.warn("Unexpected loyalty cards byte {}", String.format("0x%02x", payload[0]));
+    }
+
+    @Override
+    public void initialize(final TransactionBuilder builder) {
+        requestCapabilities(builder);
+    }
+
+    public boolean isSupported() {
+        return !supportedFormats.isEmpty() && !supportedColors.isEmpty();
+    }
+
+    public List<BarcodeFormat> getSupportedFormats() {
+        return supportedFormats;
+    }
+
+    public void requestCapabilities(final TransactionBuilder builder) {
+        write(builder, CMD_CAPABILITIES_REQUEST);
+    }
+
+    public void setCards(final List<LoyaltyCard> cards) {
+        LOG.info("Setting {} loyalty cards", cards.size());
+
+        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+
+        final List<LoyaltyCard> supportedCards = filterSupportedCards(cards);
+
+        baos.write(CMD_SET);
+        baos.write(supportedCards.size());
+
+        for (final LoyaltyCard card : supportedCards) {
+            try {
+                baos.write(encodeCard(card));
+            } catch (final Exception e) {
+                LOG.error("Failed to encode card", e);
+                return;
+            }
+        }
+
+        write("set loyalty cards", baos.toByteArray());
+    }
+
+    private List<LoyaltyCard> filterSupportedCards(final List<LoyaltyCard> cards) {
+        final List<LoyaltyCard> ret = new ArrayList<>();
+
+        for (final LoyaltyCard card : cards) {
+            if (supportedFormats.contains(card.getBarcodeFormat())) {
+                ret.add(card);
+            }
+        }
+
+        return ret;
+    }
+
+    private byte[] encodeCard(final LoyaltyCard card) {
+        final Byte barcodeFormatCode = BARCODE_FORMAT_CODES.get(card.getBarcodeFormat());
+        if (barcodeFormatCode == null) {
+            LOG.error("Unsupported barcode format {}", card.getBarcodeFormat());
+            return null;
+        }
+
+        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+
+        try {
+            baos.write(card.getName().getBytes(StandardCharsets.UTF_8));
+            baos.write(0);
+
+            // This is optional
+            baos.write(card.getCardId().getBytes(StandardCharsets.UTF_8));
+            baos.write(0);
+
+            if (card.getBarcodeId() != null) {
+                baos.write(card.getBarcodeId().getBytes(StandardCharsets.UTF_8));
+            } else {
+                baos.write(card.getCardId().getBytes(StandardCharsets.UTF_8));
+            }
+            baos.write(0);
+
+            baos.write(barcodeFormatCode);
+            if (card.getColor() != null) {
+                baos.write(findNearestColorCode(card.getColor()));
+            } else {
+                baos.write(0x00);
+            }
+        } catch (final Exception e) {
+            LOG.error("Failed to encode card", e);
+            return null;
+        }
+
+        return baos.toByteArray();
+    }
+
+    private byte findNearestColorCode(final int color) {
+        final double r = ((color >> 16) & 0xff) / 255f;
+        final double g = ((color >> 8) & 0xff) / 255f;
+        final double b = (color & 0xff) / 255f;
+
+        int nearestColor = 0x66c6ea;
+        double minDistance = Float.MAX_VALUE;
+
+        // TODO better color distance algorithm?
+        for (final Integer colorPreset : COLOR_CODES.keySet()) {
+            final double rPreset = ((colorPreset >> 16) & 0xff) / 255f;
+            final double gPreset = ((colorPreset >> 8) & 0xff) / 255f;
+            final double bPreset = (colorPreset & 0xff) / 255f;
+
+            final double distance = Math.sqrt(Math.pow(rPreset - r, 2) + Math.pow(gPreset - g, 2) + Math.pow(bPreset - b, 2));
+            if (distance < minDistance) {
+                nearestColor = colorPreset;
+                minDistance = distance;
+            }
+        }
+
+        return Objects.requireNonNull(COLOR_CODES.get(nearestColor));
+    }
+
+    private static final Map<BarcodeFormat, Byte> BARCODE_FORMAT_CODES = new HashMap<BarcodeFormat, Byte>() {{
+        put(BarcodeFormat.CODE_128, (byte) 0x00);
+        put(BarcodeFormat.CODE_39, (byte) 0x01);
+        put(BarcodeFormat.ITF, (byte) 0x02);
+        put(BarcodeFormat.QR_CODE, (byte) 0x03);
+        put(BarcodeFormat.PDF_417, (byte) 0x04);
+        put(BarcodeFormat.DATA_MATRIX, (byte) 0x05);
+        put(BarcodeFormat.UPC_A, (byte) 0x06);
+        put(BarcodeFormat.EAN_13, (byte) 0x07);
+        put(BarcodeFormat.EAN_8, (byte) 0x08);
+    }};
+
+    /**
+     * Map or RGB color to color byte - the watches only support color presets.
+     */
+    private static final Map<Integer, Byte> COLOR_CODES = new HashMap<Integer, Byte>() {{
+        put(0x66c6ea, (byte) 0x00); // Light blue
+        put(0x008fc5, (byte) 0x01); // Blue
+        put(0xc19ffd, (byte) 0x02); // Light purple
+        put(0x8855e2, (byte) 0x03); // Purple
+        put(0xfb8e89, (byte) 0x04); // Light red
+        put(0xdf3b34, (byte) 0x05); // Red
+        put(0xffab03, (byte) 0x06); // Orange
+        put(0xffaa77, (byte) 0x07); // Light Orange
+        put(0xe75800, (byte) 0x08); // Dark Orange
+        put(0x66d0b8, (byte) 0x09); // Light green
+        put(0x009e7a, (byte) 0x0a); // Green
+        put(0xffcd68, (byte) 0x0b); // Yellow-ish
+    }};
+
+    public static boolean isSupported(final Prefs devicePrefs) {
+        return devicePrefs.getInt(PREF_VERSION, 0) == 1;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsMorningUpdatesService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsMorningUpdatesService.java
new file mode 100644
index 0000000..9a255d4
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsMorningUpdatesService.java
@@ -0,0 +1,231 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo, Raghd Hamzeh
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsUtils;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdatePreferences;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.AbstractZeppOsService;
+import nodomain.freeyourgadget.gadgetbridge.util.MapUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
+
+public class ZeppOsMorningUpdatesService extends AbstractZeppOsService {
+    private static final Logger LOG = LoggerFactory.getLogger(ZeppOsMorningUpdatesService.class);
+
+    private static final short ENDPOINT = 0x003f;
+
+    private static final byte CMD_ENABLED_GET = 0x03;
+    private static final byte CMD_ENABLED_RET = 0x04;
+    private static final byte CMD_ENABLED_SET = 0x07;
+    private static final byte CMD_ENABLED_SET_ACK = 0x08;
+    private static final byte CMD_CATEGORIES_REQUEST = 0x05;
+    private static final byte CMD_CATEGORIES_RESPONSE = 0x06;
+    private static final byte CMD_CATEGORIES_SET = 0x09;
+    private static final byte CMD_CATEGORIES_SET_ACK = 0x0a;
+
+    private static final Map<Byte, String> MORNING_UPDATES_MAP = new HashMap<Byte, String>() {{
+         put((byte) 0x02, "weather");
+         put((byte) 0x03, "battery");
+         put((byte) 0x04, "sleep");
+         put((byte) 0x06, "event");
+         put((byte) 0x07, "pai");
+         put((byte) 0x08, "yesterdays_activity");
+         put((byte) 0x09, "zepp_coach");
+         put((byte) 0x0a, "cycle_tracking");
+    }};
+
+    public ZeppOsMorningUpdatesService(ZeppOsSupport support) {
+        super(support, false);
+    }
+
+    @Override
+    public short getEndpoint() {
+        return ENDPOINT;
+    }
+
+    @Override
+    public void handlePayload(byte[] payload) {
+        switch (payload[0]) {
+            case CMD_ENABLED_RET:
+                if (payload[1] != 0x01) {
+                    LOG.warn("Unexpected morning updates enabled byte {}", payload[1]);
+                    return;
+                }
+                final Boolean enabled = booleanFromByte(payload[2]);
+                if (enabled == null) {
+                    LOG.error("Unexpected morning updates enabled byte");
+                    return;
+                }
+                final GBDeviceEventUpdatePreferences gbDeviceEventUpdatePreferences = new GBDeviceEventUpdatePreferences()
+                        .withPreference(DeviceSettingsUtils.getPrefKnownConfig(DeviceSettingsPreferenceConst.MORNING_UPDATES_ENABLED), true)
+                        .withPreference(DeviceSettingsPreferenceConst.MORNING_UPDATES_ENABLED, enabled);
+                getSupport().evaluateGBDeviceEvent(gbDeviceEventUpdatePreferences);
+                LOG.info("Morning updates enabled = {}", enabled);
+                return;
+            case CMD_ENABLED_SET_ACK:
+                LOG.info("Morning updates enabled set ack ACK, status = {}", payload[1]);
+                return;
+            case CMD_CATEGORIES_RESPONSE:
+                LOG.info("Got morning update items from watch");
+                decodeAndUpdateCategories(payload);
+                return;
+            case CMD_CATEGORIES_SET_ACK:
+                LOG.info("Morning updates categories set ack ACK, status = {}", payload[1]);
+                return;
+            default:
+                LOG.warn("Unexpected morning updates byte {}", String.format("0x%02x", payload[0]));
+        }
+    }
+
+    @Override
+    public boolean onSendConfiguration(final String config, final Prefs prefs) {
+        switch (config) {
+            case DeviceSettingsPreferenceConst.MORNING_UPDATES_ENABLED:
+                final boolean morningUpdatesEnabled = prefs.getBoolean(config, false);
+                LOG.info("Setting morning updates enabled = {}", morningUpdatesEnabled);
+                setEnabled(morningUpdatesEnabled);
+                return true;
+            case DeviceSettingsPreferenceConst.MORNING_UPDATES_CATEGORIES_SORTABLE:
+                final List<String> categories = new ArrayList<>(prefs.getList(config, Collections.emptyList()));
+                final List<String> allCategories = new ArrayList<>(prefs.getList(DeviceSettingsUtils.getPrefPossibleValuesKey(config), Collections.emptyList()));
+                LOG.info("Setting morning updates categories = {}", categories);
+                setCategories(categories, allCategories);
+                return true;
+        }
+
+        return false;
+    }
+
+    @Override
+    public void initialize(final TransactionBuilder builder) {
+        getEnabled(builder);
+        getCategories(builder);
+    }
+
+    public void getEnabled(final TransactionBuilder builder) {
+        write(builder, CMD_ENABLED_GET);
+    }
+
+    public void setEnabled(final boolean enabled) {
+        write("set morning updates enabled", new byte[] {CMD_ENABLED_SET, bool(enabled)});
+    }
+
+    public void getCategories(final TransactionBuilder builder) {
+        write(builder, CMD_CATEGORIES_REQUEST);
+    }
+
+    public void setCategories(final List<String> categories, final List<String> allCategories) {
+        // Build a sorted list, with the enabled at the start
+        final List<String> categoriesSorted = new ArrayList<>(categories);
+        for (String category : allCategories) {
+            if (!categories.contains(category)) {
+                categoriesSorted.add(category);
+            }
+        }
+
+        final Map<String, Byte> idMap = MapUtils.reverse(MORNING_UPDATES_MAP);
+
+        final ByteBuffer buf = ByteBuffer.allocate(2 + categoriesSorted.size() * 3)
+                .order(ByteOrder.LITTLE_ENDIAN);
+
+        buf.put(CMD_CATEGORIES_SET);
+        buf.put((byte) categoriesSorted.size());
+
+        for (final String category : categoriesSorted) {
+            final byte id;
+            if (idMap.containsKey(category)) {
+                id = idMap.get(category);
+            } else {
+                // name doesn't match a known value, attempt to parse it as hex
+                final Matcher matcher = Pattern.compile("^0[xX]([0-9a-fA-F]{1,2})$").matcher(category);
+                if (matcher.find()) {
+                    id = (byte) Integer.parseInt(matcher.group(1), 16);
+                } else {
+                    LOG.warn("Unknown category {}, and failed to parse as hex, not setting", category);
+                    return;
+                }
+            }
+
+            buf.put(id);
+            buf.put((byte) 0x00);
+            buf.put(bool(categories.contains(category)));
+        }
+
+        write("set morning updates categories", buf.array());
+    }
+
+    private void decodeAndUpdateCategories(final byte[] payload) {
+        if (payload[1] != 0x01) {
+            LOG.warn("Unexpected morning update items byte {}", payload[1]);
+            return;
+        }
+
+        final int numCategories = payload[2];
+        final int expectedLength = 3 + numCategories * 3;
+        if (payload.length != expectedLength) {
+            LOG.warn("Unexpected morning updates categories payload size {}, expected {}", payload.length, expectedLength);
+            return;
+        }
+
+        final List<String> enabledCategories = new ArrayList<>(numCategories);
+        final List<String> allCategories = new ArrayList<>(numCategories);
+
+        for (int i = 3; i < expectedLength; i += 3) {
+            final String itemName = MORNING_UPDATES_MAP.containsKey(payload[i])
+                    ? MORNING_UPDATES_MAP.get(payload[i])
+                    : String.format("0x%x", payload[i]);
+
+            allCategories.add(itemName);
+
+            // i + 1 is always 0
+            final Boolean itemEnabled = booleanFromByte(payload[i + 2]);
+            if (itemEnabled == null) {
+                LOG.warn("Unexpected enabled byte {} for item at i={}", payload[i + 2], i);
+            } else if(itemEnabled) {
+                enabledCategories.add(itemName);
+            }
+        }
+
+        final String prefKey = DeviceSettingsPreferenceConst.MORNING_UPDATES_CATEGORIES_SORTABLE;
+        final String allCategoriesPrefKey = DeviceSettingsUtils.getPrefPossibleValuesKey(prefKey);
+
+        final String allCategoriesPrefValue = StringUtils.join(",", allCategories.toArray(new String[0])).toString();
+        final String prefValue = StringUtils.join(",", enabledCategories.toArray(new String[0])).toString();
+        final GBDeviceEventUpdatePreferences eventUpdatePreferences = new GBDeviceEventUpdatePreferences()
+                .withPreference(DeviceSettingsUtils.getPrefKnownConfig(prefKey), true)
+                .withPreference(allCategoriesPrefKey, allCategoriesPrefValue)
+                .withPreference(prefKey, prefValue);
+
+        getSupport().evaluateGBDeviceEvent(eventUpdatePreferences);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsMusicService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsMusicService.java
new file mode 100644
index 0000000..ae87bea
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsMusicService.java
@@ -0,0 +1,137 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services;
+
+import org.apache.commons.lang3.ArrayUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventMusicControl;
+import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.AbstractZeppOsService;
+
+public class ZeppOsMusicService extends AbstractZeppOsService {
+    private static final Logger LOG = LoggerFactory.getLogger(ZeppOsMusicService.class);
+
+    private static final short ENDPOINT = 0x001b;
+
+    private static final byte CMD_MEDIA_INFO = 0x03;
+    private static final byte CMD_APP_STATE = 0x04;
+    private static final byte CMD_BUTTON_PRESS = 0x05;
+    private static final byte MUSIC_APP_OPEN = 0x01;
+    private static final byte MUSIC_APP_CLOSE = 0x02;
+    private static final byte BUTTON_PLAY = 0x00;
+    private static final byte BUTTON_PAUSE = 0x01;
+    private static final byte BUTTON_NEXT = 0x03;
+    private static final byte BUTTON_PREVIOUS = 0x04;
+    private static final byte BUTTON_VOLUME_UP = 0x05;
+    private static final byte BUTTON_VOLUME_DOWN = 0x06;
+
+    public ZeppOsMusicService(final ZeppOsSupport support) {
+        super(support, false);
+    }
+
+    @Override
+    public short getEndpoint() {
+        return ENDPOINT;
+    }
+
+    @Override
+    public void handlePayload(final byte[] payload) {
+        switch (payload[0]) {
+            case CMD_APP_STATE:
+                switch (payload[1]) {
+                    case MUSIC_APP_OPEN:
+                        onMusicAppOpen();
+                        break;
+                    case MUSIC_APP_CLOSE:
+                        onMusicAppClosed();
+                        break;
+                    default:
+                        LOG.warn("Unexpected music app state {}", String.format("0x%02x", payload[1]));
+                        break;
+                }
+                return;
+
+            case CMD_BUTTON_PRESS:
+                LOG.info("Got music button press");
+                final GBDeviceEventMusicControl deviceEventMusicControl = new GBDeviceEventMusicControl();
+                switch (payload[1]) {
+                    case BUTTON_PLAY:
+                        deviceEventMusicControl.event = GBDeviceEventMusicControl.Event.PLAY;
+                        break;
+                    case BUTTON_PAUSE:
+                        deviceEventMusicControl.event = GBDeviceEventMusicControl.Event.PAUSE;
+                        break;
+                    case BUTTON_NEXT:
+                        deviceEventMusicControl.event = GBDeviceEventMusicControl.Event.NEXT;
+                        break;
+                    case BUTTON_PREVIOUS:
+                        deviceEventMusicControl.event = GBDeviceEventMusicControl.Event.PREVIOUS;
+                        break;
+                    case BUTTON_VOLUME_UP:
+                        deviceEventMusicControl.event = GBDeviceEventMusicControl.Event.VOLUMEUP;
+                        break;
+                    case BUTTON_VOLUME_DOWN:
+                        deviceEventMusicControl.event = GBDeviceEventMusicControl.Event.VOLUMEDOWN;
+                        break;
+                    default:
+                        LOG.warn("Unexpected music button {}", String.format("0x%02x", payload[1]));
+                        return;
+                }
+                evaluateGBDeviceEvent(deviceEventMusicControl);
+                return;
+            default:
+                LOG.warn("Unexpected music byte {}", String.format("0x%02x", payload[0]));
+        }
+    }
+
+    private void onMusicAppOpen() {
+        getSupport().onMusicAppOpen();
+    }
+
+    private void onMusicAppClosed() {
+        getSupport().onMusicAppClosed();
+    }
+
+    public void sendMusicState(final MusicSpec musicSpec,
+                               final MusicStateSpec musicStateSpec) {
+        LOG.info("Sending music: {}, {}", musicSpec, musicStateSpec);
+
+        // TODO: Encode not playing state (flag 0x20, single 0x01 byte before volume)
+        final byte[] cmd = ArrayUtils.addAll(
+                new byte[]{CMD_MEDIA_INFO},
+                HuamiSupport.encodeMusicState(getContext(), musicSpec, musicStateSpec, false)
+        );
+
+        write("send music state", cmd);
+    }
+
+    public void sendVolume(final float volume) {
+        LOG.info("Sending volume: {}", volume);
+
+        final byte[] cmd = ArrayUtils.addAll(
+                new byte[]{CMD_MEDIA_INFO},
+                HuamiSupport.encodeMusicState(getContext(), null, null, true)
+        );
+
+        write("send volume", cmd);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsNotificationService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsNotificationService.java
new file mode 100644
index 0000000..15d658f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsNotificationService.java
@@ -0,0 +1,600 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services;
+
+import static org.apache.commons.lang3.ArrayUtils.subarray;
+
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.drawable.Drawable;
+
+import androidx.annotation.Nullable;
+
+import org.slf4j.Logger;
+
+import org.slf4j.LoggerFactory;
+
+import java.io.ByteArrayOutputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.StandardCharsets;
+import java.util.Locale;
+import java.util.function.Consumer;
+
+import nodomain.freeyourgadget.gadgetbridge.BuildConfig;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventCallControl;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventNotificationControl;
+import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.NotificationType;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.AbstractZeppOsService;
+import nodomain.freeyourgadget.gadgetbridge.util.BitmapUtil;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+import nodomain.freeyourgadget.gadgetbridge.util.LimitedQueue;
+import nodomain.freeyourgadget.gadgetbridge.util.NotificationUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
+
+public class ZeppOsNotificationService extends AbstractZeppOsService {
+    private static final Logger LOG = LoggerFactory.getLogger(ZeppOsNotificationService.class);
+
+    public static final short ENDPOINT = 0x001e;
+
+    public static final byte NOTIFICATION_CMD_CAPABILITIES_REQUEST = 0x01;
+    public static final byte NOTIFICATION_CMD_CAPABILITIES_RESPONSE = 0x02;
+    public static final byte NOTIFICATION_CMD_SEND = 0x03;
+    public static final byte NOTIFICATION_CMD_REPLY = 0x04;
+    public static final byte NOTIFICATION_CMD_DISMISS = 0x05;
+    public static final byte NOTIFICATION_CMD_REPLY_ACK = 0x06;
+    public static final byte NOTIFICATION_CMD_ICON_REQUEST = 0x10;
+    public static final byte NOTIFICATION_CMD_ICON_REQUEST_ACK = 0x11;
+    public static final byte NOTIFICATION_CMD_PICTURE_REQUEST = 0x19;
+    public static final byte NOTIFICATION_CMD_PICTURE_REQUEST_ACK = 0x1a;
+    public static final byte NOTIFICATION_TYPE_NORMAL = (byte) 0xfa;
+    public static final byte NOTIFICATION_TYPE_CALL = 0x03;
+    public static final byte NOTIFICATION_TYPE_SMS = (byte) 0x05;
+    public static final byte NOTIFICATION_SUBCMD_SHOW = 0x00;
+    public static final byte NOTIFICATION_SUBCMD_DISMISS_FROM_PHONE = 0x02;
+    public static final byte NOTIFICATION_DISMISS_NOTIFICATION = 0x03;
+    public static final byte NOTIFICATION_DISMISS_MUTE_CALL = 0x02;
+    public static final byte NOTIFICATION_DISMISS_REJECT_CALL = 0x01;
+    public static final byte NOTIFICATION_CALL_STATE_START = 0x00;
+    public static final byte NOTIFICATION_CALL_STATE_END = 0x02;
+
+    private int version = -1;
+    private boolean supportsPictures = false;
+    private boolean supportsNotificationKey = false;
+
+    // Keep track of Notification ID -> action handle, as BangleJSDeviceSupport.
+    // This needs to be simplified.
+    private final LimitedQueue<Integer, Long> mNotificationReplyAction = new LimitedQueue<>(16);
+
+    // Keep track of notification pictures
+    private final LimitedQueue<Integer, String> mNotificationPictures = new LimitedQueue<>(16);
+
+    private final ZeppOsFileTransferService fileTransferService;
+
+    public ZeppOsNotificationService(final ZeppOsSupport support, final ZeppOsFileTransferService fileTransferService) {
+        super(support, true);
+        this.fileTransferService = fileTransferService;
+    }
+
+    @Override
+    public short getEndpoint() {
+        return ENDPOINT;
+    }
+
+    @Override
+    public void initialize(final TransactionBuilder builder) {
+        requestCapabilities(builder);
+    }
+
+    public void requestCapabilities(final TransactionBuilder builder) {
+        write(builder, NOTIFICATION_CMD_CAPABILITIES_REQUEST);
+    }
+
+    @Override
+    public void handlePayload(final byte[] payload) {
+        final ByteBuffer buf = ByteBuffer.wrap(payload).order(ByteOrder.LITTLE_ENDIAN);
+        final byte cmd = buf.get();
+
+        final GBDeviceEventNotificationControl deviceEvtNotificationControl = new GBDeviceEventNotificationControl();
+        final GBDeviceEventCallControl deviceEvtCallControl = new GBDeviceEventCallControl();
+
+        switch (cmd) {
+            case NOTIFICATION_CMD_CAPABILITIES_RESPONSE: {
+                version = buf.get() & 0xff;
+                if (version < 4 || version > 5) {
+                    // Untested, might work, might not..
+                    LOG.warn("Unsupported notification service version {}", version);
+                }
+                if (version >= 4) {
+                    final short unk1 = buf.getShort(); // 100
+                    final byte unk2 = buf.get(); // 1
+                    final byte unk3 = buf.get(); // 1
+                    final short unk4count = buf.getShort();
+                    buf.get(new byte[unk4count]);
+                }
+                if (version >= 5) {
+                    supportsPictures = buf.get() != 0;
+                    supportsNotificationKey = buf.get() != 0;
+                }
+                LOG.info("Notification service version={}, supportsPictures={}", version, supportsPictures);
+                break;
+            }
+            case NOTIFICATION_CMD_REPLY: {
+                // TODO make this configurable?
+                final int notificationId = BLETypeConversions.toUint32(subarray(payload, 1, 5));
+                final Long replyHandle = mNotificationReplyAction.lookup(notificationId);
+                if (replyHandle == null) {
+                    LOG.warn("Failed to find reply handle for notification ID {}", notificationId);
+                    return;
+                }
+                final String replyMessage = StringUtils.untilNullTerminator(payload, 5);
+                if (replyMessage == null) {
+                    LOG.warn("Failed to parse reply message for notification ID {}", notificationId);
+                    return;
+                }
+
+                LOG.info("Got reply to notification {} with '{}'", notificationId, replyMessage);
+
+                deviceEvtNotificationControl.handle = replyHandle;
+                deviceEvtNotificationControl.event = GBDeviceEventNotificationControl.Event.REPLY;
+                deviceEvtNotificationControl.reply = replyMessage;
+                evaluateGBDeviceEvent(deviceEvtNotificationControl);
+
+                ackNotificationReply(notificationId); // FIXME: premature?
+                deleteNotification(notificationId); // FIXME: premature?
+                return;
+            }
+            case NOTIFICATION_CMD_DISMISS:
+                switch (payload[1]) {
+                    case NOTIFICATION_DISMISS_NOTIFICATION:
+                        // TODO make this configurable?
+                        final int dismissNotificationId = BLETypeConversions.toUint32(subarray(payload, 2, 6));
+                        LOG.info("Dismiss notification {}", dismissNotificationId);
+                        deviceEvtNotificationControl.handle = dismissNotificationId;
+                        deviceEvtNotificationControl.event = GBDeviceEventNotificationControl.Event.DISMISS;
+                        evaluateGBDeviceEvent(deviceEvtNotificationControl);
+                        return;
+                    case NOTIFICATION_DISMISS_MUTE_CALL:
+                        LOG.info("Mute call");
+                        deviceEvtCallControl.event = GBDeviceEventCallControl.Event.IGNORE;
+                        evaluateGBDeviceEvent(deviceEvtCallControl);
+                        return;
+                    case NOTIFICATION_DISMISS_REJECT_CALL:
+                        LOG.info("Reject call");
+                        deviceEvtCallControl.event = GBDeviceEventCallControl.Event.REJECT;
+                        evaluateGBDeviceEvent(deviceEvtCallControl);
+                        return;
+                    default:
+                        LOG.warn("Unexpected notification dismiss byte {}", String.format("0x%02x", payload[1]));
+                        return;
+                }
+            case NOTIFICATION_CMD_ICON_REQUEST: {
+                final String packageName = StringUtils.untilNullTerminator(payload, 1);
+                if (packageName == null) {
+                    LOG.error("Failed to decode package name from payload");
+                    return;
+                }
+                LOG.info("Got notification icon request for {}", packageName);
+
+                final int expectedLength = packageName.length() + 7;
+                if (payload.length != expectedLength) {
+                    LOG.error("Unexpected icon request payload length {}, expected {}", payload.length, expectedLength);
+                    return;
+                }
+                int pos = 1 + packageName.length() + 1;
+                final byte iconFormat = payload[pos];
+                pos++;
+                int width = BLETypeConversions.toUint16(subarray(payload, pos, pos + 2));
+                pos += 2;
+                int height = BLETypeConversions.toUint16(subarray(payload, pos, pos + 2));
+                sendIconForPackage(packageName, iconFormat, width, height);
+                return;
+            }
+            case NOTIFICATION_CMD_PICTURE_REQUEST: {
+                final String packageName = StringUtils.untilNullTerminator(buf);
+                if (packageName == null) {
+                    LOG.error("Failed to decode package name for picture from payload");
+                    return;
+                }
+
+                final int notificationId = buf.getInt();
+                final byte pictureFormat = buf.get();
+                final int width = buf.getShort();
+                final int height = buf.getShort();
+
+                LOG.info(
+                        "Got notification picture request for {}, {}, {}, {}x{}",
+                        packageName,
+                        notificationId,
+                        pictureFormat,
+                        width,
+                        height
+                );
+
+                sendNotificationPicture(packageName, notificationId, pictureFormat, width);
+                return;
+            }
+            default:
+                LOG.warn("Unexpected notification byte {}", String.format("0x%02x", payload[0]));
+        }
+    }
+
+    public int maxLength() {
+        return 512;
+    }
+
+    public void setCallState(final CallSpec callSpec) {
+        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+
+        try {
+            final TransactionBuilder builder = new TransactionBuilder("send notification");
+
+            baos.write(NOTIFICATION_CMD_SEND);
+
+            // ID
+            baos.write(BLETypeConversions.fromUint32(0));
+
+            baos.write(NOTIFICATION_TYPE_CALL);
+            if (callSpec.command == CallSpec.CALL_INCOMING) {
+                baos.write(NOTIFICATION_CALL_STATE_START);
+            } else if ((callSpec.command == CallSpec.CALL_START) || (callSpec.command == CallSpec.CALL_END)) {
+                baos.write(NOTIFICATION_CALL_STATE_END);
+            }
+
+            baos.write(0x00); // ?
+            if (callSpec.name != null) {
+                baos.write(callSpec.name.getBytes(StandardCharsets.UTF_8));
+            }
+            baos.write(0x00);
+
+            baos.write(0x00); // ?
+            baos.write(0x00); // ?
+
+            if (callSpec.number != null) {
+                baos.write(callSpec.number.getBytes(StandardCharsets.UTF_8));
+            }
+            baos.write(0x00);
+
+            // TODO put this behind a setting?
+            baos.write(callSpec.number != null ? 0x01 : 0x00); // reply from watch
+
+            write(builder, baos.toByteArray());
+            builder.queue(getSupport().getQueue());
+        } catch (final Exception e) {
+            LOG.error("Failed to send call", e);
+        }
+    }
+
+    public void sendNotification(final NotificationSpec notificationSpec) {
+        if (!getDevicePrefs().getBoolean(DeviceSettingsPreferenceConst.PREF_SEND_APP_NOTIFICATIONS, true)) {
+            LOG.debug("App notifications disabled - ignoring");
+            return;
+        }
+
+        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+
+        final String senderOrTitle = StringUtils.getFirstOf(notificationSpec.sender, notificationSpec.title);
+
+        // TODO Check real limit for notificationMaxLength / respect across all fields
+
+        try {
+            final TransactionBuilder builder = new TransactionBuilder("send notification");
+
+            baos.write(NOTIFICATION_CMD_SEND);
+            baos.write(BLETypeConversions.fromUint32(notificationSpec.getId()));
+            if (notificationSpec.type == NotificationType.GENERIC_SMS) {
+                baos.write(NOTIFICATION_TYPE_SMS);
+            } else {
+                baos.write(NOTIFICATION_TYPE_NORMAL);
+            }
+            baos.write(NOTIFICATION_SUBCMD_SHOW);
+
+            // app package
+            if (notificationSpec.sourceAppId != null) {
+                baos.write(notificationSpec.sourceAppId.getBytes(StandardCharsets.UTF_8));
+            } else {
+                // Send the GB package name, otherwise the last notification icon will
+                // be used wrongly (eg. when receiving an SMS)
+                baos.write(BuildConfig.APPLICATION_ID.getBytes(StandardCharsets.UTF_8));
+            }
+            baos.write(0);
+
+            // sender/title
+            if (!senderOrTitle.isEmpty()) {
+                baos.write(senderOrTitle.getBytes(StandardCharsets.UTF_8));
+            }
+            baos.write(0);
+
+            // body
+            if (notificationSpec.body != null) {
+                baos.write(StringUtils.truncate(notificationSpec.body, maxLength()).getBytes(StandardCharsets.UTF_8));
+            }
+            baos.write(0);
+
+            // app name
+            if (notificationSpec.sourceName != null) {
+                baos.write(notificationSpec.sourceName.getBytes(StandardCharsets.UTF_8));
+            }
+            baos.write(0);
+
+            // reply
+            boolean hasReply = false;
+            if (notificationSpec.attachedActions != null && !notificationSpec.attachedActions.isEmpty()) {
+                for (int i = 0; i < notificationSpec.attachedActions.size(); i++) {
+                    final NotificationSpec.Action action = notificationSpec.attachedActions.get(i);
+
+                    switch (action.type) {
+                        case NotificationSpec.Action.TYPE_WEARABLE_REPLY:
+                        case NotificationSpec.Action.TYPE_SYNTECTIC_REPLY_PHONENR:
+                            hasReply = true;
+                            mNotificationReplyAction.add(notificationSpec.getId(), action.handle);
+                            break;
+                        default:
+                            break;
+                    }
+                }
+            }
+
+            baos.write((byte) (hasReply ? 1 : 0));
+            if (version >= 5) {
+                baos.write(0); // 1 for silent
+            }
+            if (supportsPictures) {
+                baos.write((byte) (notificationSpec.picturePath != null ? 1 : 0));
+                if (notificationSpec.picturePath != null) {
+                    mNotificationPictures.add(notificationSpec.getId(), notificationSpec.picturePath);
+                }
+            }
+            if (supportsNotificationKey) {
+                if (notificationSpec.key != null) {
+                    baos.write(notificationSpec.key.getBytes(StandardCharsets.UTF_8));
+                }
+                baos.write(0);
+            }
+
+            write(builder, baos.toByteArray());
+            builder.queue(getSupport().getQueue());
+        } catch (final Exception e) {
+            LOG.error("Failed to send notification", e);
+        }
+    }
+
+    public void deleteNotification(final int id) {
+        if (!getDevicePrefs().getBoolean(DeviceSettingsPreferenceConst.PREF_SEND_APP_NOTIFICATIONS, true)) {
+            LOG.debug("App notifications disabled - ignoring delete");
+            return;
+        }
+
+        mNotificationPictures.remove(id);
+
+        LOG.info("Deleting notification {} from band", id);
+
+        final ByteBuffer buf = ByteBuffer.allocate(12);
+        buf.order(ByteOrder.LITTLE_ENDIAN);
+        buf.put(NOTIFICATION_CMD_SEND);
+        buf.putInt(id);
+        buf.put(NOTIFICATION_TYPE_NORMAL);
+        buf.put(NOTIFICATION_SUBCMD_DISMISS_FROM_PHONE);
+        buf.put((byte) 0x00); // ?
+        buf.put((byte) 0x00); // ?
+        buf.put((byte) 0x00); // ?
+        buf.put((byte) 0x00); // ?
+        buf.put((byte) 0x00); // ?
+
+        write("delete notification", buf.array());
+    }
+
+    private void ackNotificationReply(final int notificationId) {
+        final ByteBuffer buf = ByteBuffer.allocate(9);
+        buf.order(ByteOrder.LITTLE_ENDIAN);
+        buf.put(NOTIFICATION_CMD_REPLY_ACK);
+        buf.putInt(notificationId);
+        buf.put((byte) 0x00); // ?
+        buf.put((byte) 0x00); // ?
+        buf.put((byte) 0x00); // ?
+        buf.put((byte) 0x00); // ?
+
+        write("ack notification reply", buf.array());
+    }
+
+    private void ackNotificationAfterIconSent(final String queuedIconPackage, final boolean success) {
+        if (!success) {
+            return;
+        }
+
+        LOG.info("Acknowledging icon send for {}", queuedIconPackage);
+
+        final ByteBuffer buf = ByteBuffer.allocate(1 + queuedIconPackage.length() + 1 + 1);
+        buf.order(ByteOrder.LITTLE_ENDIAN);
+        buf.put(NOTIFICATION_CMD_ICON_REQUEST_ACK);
+        buf.put(queuedIconPackage.getBytes(StandardCharsets.UTF_8));
+        buf.put((byte) 0x00);
+        buf.put((byte) 0x01); // TODO !success?
+
+        write("ack icon send", buf.array());
+    }
+
+    private void ackNotificationAfterPictureSent(final String packageName, final int notificationId, final boolean success) {
+        LOG.info("Acknowledging picture send for {}", packageName);
+
+        final ByteBuffer buf = ByteBuffer.allocate(1 + packageName.length() + 1 + 4 + 1).order(ByteOrder.LITTLE_ENDIAN);
+        buf.put(NOTIFICATION_CMD_PICTURE_REQUEST_ACK);
+        buf.put(packageName.getBytes(StandardCharsets.UTF_8));
+        buf.put((byte) 0x00);
+        buf.putInt(notificationId);
+        buf.put((byte) (success ? 0x01 : 0x02));
+
+        write("ack picture send", buf.array());
+    }
+
+    private void sendIconForPackage(final String packageName, final byte iconFormat, final int width, final int height) {
+        final BitmapFormat format = BitmapFormat.fromCode(iconFormat);
+        if (format == null) {
+            LOG.error("Unknown icon bitmap format code {}", String.format("0x%02x", iconFormat));
+            return;
+        }
+
+        final Drawable icon = NotificationUtils.getAppIcon(getContext(), packageName);
+        if (icon == null) {
+            LOG.warn("Failed to get icon for {}", packageName);
+            return;
+        }
+
+        final Bitmap bmp = BitmapUtil.toBitmap(icon);
+        final byte[] tga = encodeBitmap(bmp, format, width, height);
+
+        final String url = String.format(
+                Locale.ROOT,
+                "notification://logo?app_id=%s&width=%d&height=%d&format=%s",
+                packageName,
+                width,
+                height,
+                format
+        );
+        final String filename = String.format("logo_%s.tga", packageName.replace(".", "_"));
+
+        sendFile(url, filename, tga, false, success -> ackNotificationAfterIconSent(packageName, success));
+    }
+
+    private void sendNotificationPicture(final String packageName, final int notificationId, final byte pictureFormat, final int width) {
+        final BitmapFormat format = BitmapFormat.fromCode(pictureFormat);
+        if (format == null) {
+            LOG.error("Unknown picture bitmap format code {}", String.format("0x%02x", pictureFormat));
+            ackNotificationAfterPictureSent(packageName, notificationId, false);
+            return;
+        }
+
+        final String picturePath = mNotificationPictures.lookup(notificationId);
+        if (picturePath == null) {
+            LOG.warn("Failed to find picture path for {}", notificationId);
+            ackNotificationAfterPictureSent(packageName, notificationId, false);
+            return;
+        }
+
+        final Bitmap bmp = BitmapFactory.decodeFile(picturePath);
+        if (bmp == null) {
+            LOG.warn("Failed to decode bitmap from {}", picturePath);
+            ackNotificationAfterPictureSent(packageName, notificationId, false);
+            return;
+        }
+
+        // FIXME: On the GTR 4, the band sends 358 on the url, but the actual image has 368 width
+        //  if sent as requested, it gets all corrupted...
+        final int targetWidth = width + 10;
+        final int targetHeight = (int) Math.round(bmp.getHeight() * ((double) targetWidth / bmp.getWidth()));
+        final byte[] tga = encodeBitmap(bmp, format, targetWidth, targetHeight);
+
+        final String url = String.format(
+                Locale.ROOT,
+                "notification://content_image?app_id=%s&uid=%d&width=%d&height=%d&format=%s",
+                packageName,
+                notificationId,
+                width,
+                targetHeight,
+                format
+        );
+        final String filename = String.format(Locale.ROOT, "picture_%d.tga", notificationId);
+
+        sendFile(url, filename, tga, true, success -> ackNotificationAfterPictureSent(packageName, notificationId, success));
+    }
+
+    private void sendFile(final String url,
+                          final String filename,
+                          final byte[] bytes,
+                          final boolean compress,
+                          final Consumer<Boolean> uploadFinishCallback) {
+        if (getSupport().getMTU() < 247) {
+            LOG.warn("Sending files requires high MTU, current MTU is {}", getSupport().getMTU());
+            return;
+        }
+
+        fileTransferService.sendFile(
+                url,
+                filename,
+                bytes,
+                true,
+                new ZeppOsFileTransferService.Callback() {
+                    @Override
+                    public void onFileUploadFinish(final boolean success) {
+                        LOG.info("Finished sending '{}' to '{}', success={}", filename, url, success);
+                        uploadFinishCallback.accept(success);
+                    }
+
+                    @Override
+                    public void onFileUploadProgress(final int progress) {
+                        LOG.trace("File send progress: {}", progress);
+                    }
+
+                    @Override
+                    public void onFileDownloadFinish(final String url, final String filename, final byte[] data) {
+                        LOG.warn("Receiver unexpected file: url={} filename={} length={}", url, filename, data.length);
+                    }
+                }
+        );
+
+        LOG.info("Queueing file send '{}' to '{}'", filename, url);
+    }
+
+    private static byte[] encodeBitmap(final Bitmap bmp, final BitmapFormat format, final int width, final int height) {
+        // Without the expected tga id and format string they seem to get corrupted,
+        // but the encoding seems to actually be the same...?
+        // The TGA needs to have this ID, or the band does not accept it
+        final byte[] tgaIdBytes = new byte[46];
+        System.arraycopy(format.getTgaId().getBytes(StandardCharsets.UTF_8), 0, tgaIdBytes, 0, 5);
+
+        return BitmapUtil.convertToTgaRGB565(bmp, width, height, tgaIdBytes);
+    }
+
+    private enum BitmapFormat {
+        TGA_RGB565_GCNANOLITE(0x04, "SOMHP"),
+        TGA_RGB565_DAVE2D(0x08, "SOMH6"),
+        ;
+
+        private final byte code;
+        private final String tgaId;
+
+        BitmapFormat(final int code, final String tgaId) {
+            this.code = (byte) code;
+            this.tgaId = tgaId;
+        }
+
+        public byte getCode() {
+            return code;
+        }
+
+        public String getTgaId() {
+            return tgaId;
+        }
+
+        @Nullable
+        public static BitmapFormat fromCode(final byte code) {
+            for (final BitmapFormat format : BitmapFormat.values()) {
+                if (format.code == code) {
+                    return format;
+                }
+            }
+
+            return null;
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsPhoneService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsPhoneService.java
new file mode 100644
index 0000000..d481ef1
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsPhoneService.java
@@ -0,0 +1,211 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services;
+
+import android.bluetooth.BluetoothAdapter;
+
+import androidx.annotation.Nullable;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.StandardCharsets;
+
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdatePreferences;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.AbstractZeppOsService;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+public class ZeppOsPhoneService extends AbstractZeppOsService {
+    private static final Logger LOG = LoggerFactory.getLogger(ZeppOsPhoneService.class);
+
+    private static final short ENDPOINT = 0x000b;
+
+    public static final byte CMD_CAPABILITIES_REQUEST = 0x01;
+    public static final byte CMD_CAPABILITIES_RESPONSE = 0x02;
+    public static final byte CMD_PAIRED_GET = 0x03;
+    public static final byte CMD_PAIRED_RET = 0x04;
+    public static final byte CMD_START_PAIR = 0x05;
+    public static final byte CMD_ENABLED_REQUEST = 0x06;
+    public static final byte CMD_ENABLED_RESPONSE = 0x07;
+    public static final byte CMD_ENABLED_SET = 0x08;
+    public static final byte CMD_ENABLED_SET_ACK = 0x09;
+
+    public static final String PREF_VERSION = "zepp_os_phone_service_version";
+
+    private int version = 0;
+
+    public ZeppOsPhoneService(final ZeppOsSupport support) {
+        super(support, true);
+    }
+
+    @Override
+    public short getEndpoint() {
+        return ENDPOINT;
+    }
+
+    @Override
+    public void handlePayload(final byte[] payload) {
+        switch (payload[0]) {
+            case CMD_CAPABILITIES_RESPONSE:
+                version = payload[1];
+                getSupport().evaluateGBDeviceEvent(new GBDeviceEventUpdatePreferences(PREF_VERSION, version));
+                if (version != 1) {
+                    LOG.warn("Unsupported phone service version {}", version);
+                    return;
+                }
+                LOG.info("Phone version={}", version);
+                break;
+            case CMD_PAIRED_RET:
+                final byte pairedStatus = payload[1];
+                // 0 = unpaired
+                // 1 = paired
+                // 4 = ?
+                LOG.info("Got phone pair status = {}", pairedStatus);
+                break;
+            case CMD_ENABLED_RESPONSE:
+                if (payload.length != 4) {
+                    LOG.error("Unexpected phone enabled payload size {}", payload.length);
+                    return;
+                }
+
+                if (payload[1] != 0x01 || payload[2] != 0x01) {
+                    LOG.error("Unexpected phone enabled bytes");
+                    return;
+                }
+
+                final Boolean phoneEnabled = booleanFromByte(payload[3]);
+                if (phoneEnabled == null) {
+                    LOG.error("Unexpected phone enabled byte");
+                    return;
+                }
+                LOG.info("Got phone enabled = {}", phoneEnabled);
+                getSupport().evaluateGBDeviceEvent(new GBDeviceEventUpdatePreferences(DeviceSettingsPreferenceConst.PREF_BLUETOOTH_CALLS_ENABLED, phoneEnabled));
+                break;
+            case CMD_ENABLED_SET_ACK:
+                LOG.info("Got phone enabled set ack, status = {}", payload[1]);
+                break;
+            default:
+                LOG.warn("Unexpected phone byte {}", String.format("0x%02x", payload[0]));
+        }
+    }
+
+    @Override
+    public boolean onSendConfiguration(final String config, final Prefs prefs) {
+        switch (config) {
+            case DeviceSettingsPreferenceConst.PREF_BLUETOOTH_CALLS_PAIR:
+                if (!isSupported()) {
+                    LOG.warn("Phone service is not supported.");
+                    return false;
+                }
+
+                startPairing();
+                return true;
+            case DeviceSettingsPreferenceConst.PREF_BLUETOOTH_CALLS_ENABLED:
+                final boolean bluetoothCallsEnabled = prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_BLUETOOTH_CALLS_ENABLED, false);
+                LOG.info("Setting bluetooth calls enabled = {}", bluetoothCallsEnabled);
+                setEnabled(bluetoothCallsEnabled);
+                return true;
+        }
+
+        return false;
+    }
+
+    @Override
+    public void initialize(final TransactionBuilder builder) {
+    }
+
+    public boolean isSupported() {
+        return version == 1;
+    }
+
+    public void requestCapabilities(final TransactionBuilder builder) {
+        write(builder, CMD_CAPABILITIES_REQUEST);
+    }
+
+    public void getPairedStatus() {
+        final String bluetoothName = getBluetoothName();
+        if (bluetoothName == null) {
+            LOG.error("bluetoothName is null");
+            return;
+        }
+
+        final byte[] nameBytes = bluetoothName.getBytes(StandardCharsets.UTF_8);
+
+        final ByteBuffer buf = ByteBuffer.allocate(2 + nameBytes.length)
+                .order(ByteOrder.LITTLE_ENDIAN);
+
+        buf.put(CMD_PAIRED_GET);
+        buf.put(nameBytes);
+        buf.put((byte) 0);
+
+        write("get paired status", buf.array());
+    }
+
+    public void startPairing() {
+        final String bluetoothName = getBluetoothName();
+        if (bluetoothName == null) {
+            LOG.error("bluetoothName is null");
+            return;
+        }
+
+        final byte[] nameBytes = bluetoothName.getBytes(StandardCharsets.UTF_8);
+
+        final ByteBuffer buf = ByteBuffer.allocate(2 + nameBytes.length)
+                .order(ByteOrder.LITTLE_ENDIAN);
+
+        buf.put(CMD_START_PAIR);
+        buf.put(nameBytes);
+        buf.put((byte) 0);
+
+        write("start phone pairing", buf.array());
+    }
+
+    public void requestEnabled(final TransactionBuilder builder) {
+        write(builder, CMD_ENABLED_REQUEST);
+    }
+
+    public void setEnabled(final boolean enabled) {
+        final byte[] cmd = new byte[]{
+                CMD_ENABLED_SET,
+                0x01,
+                0x01,
+                (byte) (enabled ? 0x01 : 0x00)
+        };
+
+        write("set phone enabled", cmd);
+    }
+
+    @Nullable
+    public String getBluetoothName() {
+        final BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
+        if (bluetoothAdapter == null) {
+            LOG.error("bluetoothAdapter is null");
+            return null;
+        }
+
+        return bluetoothAdapter.getName();
+    }
+
+    public static boolean isSupported(final Prefs devicePrefs) {
+        return devicePrefs.getInt(PREF_VERSION, 0) == 1;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsRemindersService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsRemindersService.java
new file mode 100644
index 0000000..20d7636
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsRemindersService.java
@@ -0,0 +1,379 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.StandardCharsets;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdatePreferences;
+import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.model.Reminder;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.AbstractZeppOsService;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
+
+public class ZeppOsRemindersService extends AbstractZeppOsService {
+    private static final Logger LOG = LoggerFactory.getLogger(ZeppOsRemindersService.class);
+
+    private static final short ENDPOINT = 0x0038;
+
+    private static final byte CMD_CAPABILITIES_REQUEST = 0x01;
+    private static final byte CMD_CAPABILITIES_RESPONSE = 0x02;
+    private static final byte CMD_REQUEST = 0x03;
+    private static final byte CMD_RESPONSE = 0x04;
+    private static final byte CMD_CREATE = 0x05;
+    private static final byte CMD_CREATE_ACK = 0x06;
+    private static final byte CMD_UPDATE = 0x07;
+    private static final byte CMD_UPDATE_ACK = 0x08;
+    private static final byte CMD_DELETE = 0x09;
+    private static final byte CMD_DELETE_ACK = 0x0a;
+
+    private static final int FLAG_ENABLED = 0x0001;
+    private static final int FLAG_TEXT = 0x0008;
+    private static final int FLAG_REPEAT_MONTH = 0x1000;
+    private static final int FLAG_REPEAT_YEAR = 0x2000;
+
+    private static final String PREF_CAPABILITY = "huami_2021_capability_reminders";
+
+    private final Map<ZeppOsReminder, Integer> deviceReminders = new HashMap<>();
+
+    public ZeppOsRemindersService(final ZeppOsSupport support) {
+        super(support, false);
+    }
+
+    @Override
+    public short getEndpoint() {
+        return ENDPOINT;
+    }
+
+    @Override
+    public void handlePayload(final byte[] payload) {
+        switch (payload[0]) {
+            case CMD_CAPABILITIES_RESPONSE:
+                LOG.info("Reminder capability, version = {}", payload[1]);
+                if (payload[1] != 1) {
+                    LOG.warn("Reminder unsupported version {}", payload[1]);
+                    return;
+                }
+                final int numReminders = payload[2] & 0xff;
+                final GBDeviceEventUpdatePreferences eventUpdatePreferences = new GBDeviceEventUpdatePreferences(
+                        PREF_CAPABILITY,
+                        numReminders
+                );
+                evaluateGBDeviceEvent(eventUpdatePreferences);
+                return;
+            case CMD_CREATE_ACK:
+                LOG.info("Reminder create ACK, status = {}", payload[1]);
+                return;
+            case CMD_DELETE_ACK:
+                LOG.info("Reminder delete ACK, status = {}", payload[1]);
+                // status 1 = success
+                // status 2 = reminder not found
+                return;
+            case CMD_UPDATE_ACK:
+                LOG.info("Reminder update ACK, status = {}", payload[1]);
+                return;
+            case CMD_RESPONSE:
+                LOG.info("Got reminders from band");
+                decodeAndUpdateReminders(payload);
+                final TransactionBuilder builder = getSupport().createTransactionBuilder("send reminders");
+                sendReminders(builder);
+                builder.queue(getSupport().getQueue());
+                return;
+            default:
+                LOG.warn("Unexpected reminders payload byte {}", String.format("0x%02x", payload[0]));
+        }
+    }
+
+    @Override
+    public void initialize(final TransactionBuilder builder) {
+        requestCapabilities(builder);
+        requestReminders(builder);
+    }
+
+    private void requestCapabilities(final TransactionBuilder builder) {
+        write(builder, CMD_CAPABILITIES_REQUEST);
+    }
+
+    private void requestReminders(final TransactionBuilder builder) {
+        write(builder, CMD_REQUEST);
+    }
+
+    public void sendReminders(final TransactionBuilder builder) {
+        final List<? extends Reminder> reminders = DBHelper.getReminders(getSupport().getDevice());
+        sendReminders(builder, reminders);
+    }
+
+    public void sendReminders(final TransactionBuilder builder, final List<? extends Reminder> reminders) {
+        LOG.info("On Set Reminders: {}", reminders.size());
+
+        final int reminderSlotCount = getCoordinator().getReminderSlotCount(getSupport().getDevice());
+        if (reminderSlotCount <= 0) {
+            LOG.warn("Reminders not yet initialized");
+            return;
+        }
+
+        final Date currentDate = new Date();
+        final Set<ZeppOsReminder> allReminders = new HashSet<>();
+        final LinkedList<ZeppOsReminder> toDelete = new LinkedList<>();
+        final LinkedList<ZeppOsReminder> toSend = new LinkedList<>();
+
+        for (final Reminder reminder : reminders) {
+            if (currentDate.after(reminder.getDate())) {
+                // Disregard reminders from the past, device ignores them anyway (does not even send
+                // them back when requesting).
+                continue;
+            }
+
+            final ZeppOsReminder newReminder = new ZeppOsReminder(reminder);
+            allReminders.add(newReminder);
+
+            if (deviceReminders.containsKey(newReminder)) {
+                // Reminder exists and is up-to-date
+                continue;
+            }
+            toSend.push(newReminder);
+        }
+
+        for (final ZeppOsReminder reminder : deviceReminders.keySet()) {
+            if (!allReminders.contains(reminder)) {
+                toDelete.add(reminder);
+            }
+        }
+
+        for (final ZeppOsReminder reminder : toSend) {
+            if (!toDelete.isEmpty()) {
+                // If we have reminders to delete, replace them with the ones we want to send
+                final ZeppOsReminder reminderToReplace = toDelete.pop();
+                final Integer position = deviceReminders.get(reminderToReplace);
+                if (position == null) {
+                    LOG.error("Failed to find position for {} - this should never happen", reminderToReplace);
+                    // We somehow got out of sync - request all reminders again
+                    requestReminders(builder);
+                    return;
+                }
+                LOG.debug("Updating reminder at position {}", position);
+                sendReminderToDevice(builder, position, true, reminder);
+                deviceReminders.remove(reminderToReplace);
+                deviceReminders.put(reminder, position);
+            } else {
+                // Find the next available position
+                for (int position = 0; position < reminderSlotCount; position++) {
+                    if (!deviceReminders.containsValue(position)) {
+                        LOG.debug("Creating reminder at position {}", position);
+                        sendReminderToDevice(builder, position, false, reminder);
+                        deviceReminders.put(reminder, position);
+                        break;
+                    }
+                }
+            }
+        }
+
+        for (final ZeppOsReminder reminder : toDelete) {
+            final Integer position = deviceReminders.remove(reminder);
+            if (position != null) {
+                LOG.debug("Deleting reminder at position {}", position);
+                sendReminderToDevice(builder, position, true, null);
+            }
+        }
+    }
+
+    private void sendReminderToDevice(final TransactionBuilder builder, int position, final boolean update, final ZeppOsReminder reminder) {
+        final DeviceCoordinator coordinator = getCoordinator();
+        final int reminderSlotCount = coordinator.getReminderSlotCount(getSupport().getDevice());
+        if (position + 1 > reminderSlotCount) {
+            LOG.error("Reminder for position {} is over the limit of {} reminders", position, reminderSlotCount);
+            return;
+        }
+
+        if (reminder == null) {
+            // Delete reminder
+            write(builder, new byte[]{CMD_DELETE, (byte) (position & 0xFF)});
+
+            return;
+        }
+
+        final ByteBuffer buf = ByteBuffer.allocate(1 + 10 + reminder.getText().getBytes(StandardCharsets.UTF_8).length + 1);
+        buf.order(ByteOrder.LITTLE_ENDIAN);
+
+        // Update does an upsert, so let's use it. If we call create twice on the same ID, it becomes weird
+        buf.put(update ? CMD_UPDATE : CMD_CREATE);
+        buf.put((byte) (position & 0xFF));
+
+        buf.putInt(reminder.getFlags());
+        buf.putInt(reminder.getTimestamp());
+        buf.put((byte) 0x00);
+
+        buf.put(reminder.getText().getBytes(StandardCharsets.UTF_8));
+        buf.put((byte) 0x00);
+
+        write(builder, buf.array());
+    }
+
+    private void decodeAndUpdateReminders(final byte[] payload) {
+        final int numReminders = payload[1] & 0xff;
+
+        if (payload.length < 3 + numReminders * 11) {
+            LOG.warn("Unexpected payload length of {} for {} reminders", payload.length, numReminders);
+            return;
+        }
+
+        LOG.debug("Got {} reminders from band", numReminders);
+
+        deviceReminders.clear();
+
+        int i = 3;
+        while (i < payload.length) {
+            if (payload.length - i < 11) {
+                LOG.error("Not enough bytes remaining to parse a reminder ({})", payload.length - i);
+                return;
+            }
+
+            final int reminderPosition = payload[i++] & 0xff;
+            final int reminderFlags = BLETypeConversions.toUint32(payload, i);
+            i += 4;
+            final int reminderTimestamp = BLETypeConversions.toUint32(payload, i);
+            i += 4;
+            i++; // 0 ?
+            final Date reminderDate = new Date(reminderTimestamp * 1000L);
+            final String reminderText = StringUtils.untilNullTerminator(payload, i);
+            if (reminderText == null) {
+                LOG.error("Failed to parse reminder text at pos {}", i);
+                return;
+            }
+
+            final ZeppOsReminder zeppOsReminder = new ZeppOsReminder(
+                    reminderFlags,
+                    reminderTimestamp,
+                    reminderText
+            );
+
+            deviceReminders.put(zeppOsReminder, reminderPosition);
+
+            i += reminderText.length() + 1;
+
+            LOG.info("Reminder[{}]: {}, {}, {}", reminderPosition, String.format("0x%04x", reminderFlags), reminderDate, reminderText);
+        }
+        if (i != payload.length) {
+            LOG.error("Unexpected reminders payload trailer, {} bytes were not consumed", payload.length - i);
+        }
+
+        // TODO persist in database. Probably not trivial, because reminderPosition != reminderId
+    }
+
+    public static int getSlotCount(final Prefs devicePrefs) {
+        return devicePrefs.getInt(PREF_CAPABILITY, 0);
+    }
+
+    private class ZeppOsReminder {
+        final int flags;
+        final int timestamp;
+        final String text;
+
+        private ZeppOsReminder(final int flags, final int timestamp, final String text) {
+            this.flags = flags;
+            this.timestamp = timestamp;
+            this.text = text;
+        }
+
+        private ZeppOsReminder(final Reminder reminder) {
+            this.flags = getReminderFlags(reminder);
+            this.timestamp = (int) (reminder.getDate().getTime() / 1000L);
+            if (reminder.getMessage().length() > getCoordinator().getMaximumReminderMessageLength()) {
+                LOG.warn("The reminder message length {} is longer than {}, will be truncated",
+                        reminder.getMessage().length(),
+                        getCoordinator().getMaximumReminderMessageLength()
+                );
+                text = StringUtils.truncate(reminder.getMessage(), getCoordinator().getMaximumReminderMessageLength());
+            } else {
+                text = reminder.getMessage();
+            }
+        }
+
+        public int getFlags() {
+            return flags;
+        }
+
+        public int getTimestamp() {
+            return timestamp;
+        }
+
+        public String getText() {
+            return text;
+        }
+
+        private int getReminderFlags(final Reminder reminder) {
+            final Calendar cal = BLETypeConversions.createCalendar();
+            cal.setTime(reminder.getDate());
+
+            int reminderFlags = FLAG_ENABLED | FLAG_TEXT;
+
+            switch (reminder.getRepetition()) {
+                case Reminder.ONCE:
+                    // Default is once, nothing to do
+                    break;
+                case Reminder.EVERY_DAY:
+                    reminderFlags |= 0x0fe0; // all week day bits set
+                    break;
+                case Reminder.EVERY_WEEK:
+                    int dayOfWeek = BLETypeConversions.dayOfWeekToRawBytes(cal) - 1; // Monday = 0
+                    reminderFlags |= 0x20 << dayOfWeek;
+                    break;
+                case Reminder.EVERY_MONTH:
+                    reminderFlags |= FLAG_REPEAT_MONTH;
+                    break;
+                case Reminder.EVERY_YEAR:
+                    reminderFlags |= FLAG_REPEAT_YEAR;
+                    break;
+                default:
+                    LOG.warn("Unknown repetition for reminder {}, defaulting to once", reminder.getReminderId());
+            }
+
+            return reminderFlags;
+        }
+
+        @Override
+        public boolean equals(final Object o) {
+            if (this == o) return true;
+            if (!(o instanceof ZeppOsReminder)) return false;
+            final ZeppOsReminder that = (ZeppOsReminder) o;
+            return flags == that.flags && timestamp == that.timestamp && Objects.equals(text, that.text);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(flags, timestamp, text);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsServicesService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsServicesService.java
new file mode 100644
index 0000000..4e0b19d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsServicesService.java
@@ -0,0 +1,96 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.AbstractZeppOsService;
+
+public class ZeppOsServicesService extends AbstractZeppOsService {
+    private static final Logger LOG = LoggerFactory.getLogger(ZeppOsServicesService.class);
+
+    private static final short ENDPOINT = 0x0000;
+
+    public static final byte CMD_GET_LIST = 0x03;
+    public static final byte CMD_RET_LIST = 0x04;
+
+    public ZeppOsServicesService(final ZeppOsSupport support) {
+        super(support, false);
+    }
+
+    @Override
+    public short getEndpoint() {
+        return ENDPOINT;
+    }
+
+    @Override
+    public void handlePayload(final byte[] payload) {
+        switch (payload[0]) {
+            case CMD_RET_LIST:
+                handleSupportedServices(payload);
+                break;
+            default:
+                LOG.warn("Unexpected services payload byte {}", String.format("0x%02x", payload[0]));
+        }
+    }
+
+    public void requestServices(final TransactionBuilder builder) {
+        write(builder, CMD_GET_LIST);
+    }
+
+    private void handleSupportedServices(final byte[] payload) {
+        final ByteBuffer buf = ByteBuffer.wrap(payload).order(ByteOrder.LITTLE_ENDIAN);
+        buf.get(); // discard first byte
+        final short numServices = buf.getShort();
+
+        LOG.info("Number of services: {}", numServices);
+
+        for (int i = 0; i < numServices; i++) {
+            final short endpoint = buf.getShort();
+            final byte encryptedByte = buf.get();
+            final Boolean encrypted = booleanFromByte(encryptedByte);
+
+            final AbstractZeppOsService service = getSupport().getService(endpoint);
+
+            LOG.debug(
+                    "Zepp OS Service: endpoint={} encrypted={} name={}",
+                    String.format("%04x", endpoint),
+                    encrypted,
+                    service != null ? service.getClass().getSimpleName() : "unknown"
+            );
+
+            if (service != null && encrypted != null) {
+                service.setEncrypted(encrypted);
+            }
+
+            getSupport().addSupportedService(endpoint, encrypted != null && encrypted);
+        }
+
+        getSupport().initializeServices();
+
+        final int remainingBytes = buf.limit() - buf.position();
+        if (remainingBytes != 0) {
+            LOG.warn("There are {} bytes remaining in the buffer", remainingBytes);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsShortcutCardsService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsShortcutCardsService.java
new file mode 100644
index 0000000..8ff7d05
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsShortcutCardsService.java
@@ -0,0 +1,275 @@
+/*  Copyright (C) 2023-2024 Andreas Shimokawa, Jos茅 Rebelo, Maxime Reyrolle
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services;
+
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.SHORTCUT_CARDS_SORTABLE;
+
+import android.text.TextUtils;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsUtils;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdatePreferences;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.AbstractZeppOsService;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
+
+public class ZeppOsShortcutCardsService extends AbstractZeppOsService {
+    private static final Logger LOG = LoggerFactory.getLogger(ZeppOsShortcutCardsService.class);
+
+    private static final short ENDPOINT = 0x0009;
+
+    public static final byte CMD_CAPABILITIES_REQUEST = 0x01;
+    public static final byte CMD_CAPABILITIES_RESPONSE = 0x02;
+    public static final byte CMD_LIST_GET = 0x07;
+    public static final byte CMD_LIST_RET = 0x08;
+    public static final byte CMD_ENABLED_SET = 0x09;
+    public static final byte CMD_ENABLED_SET_ACK = 0x0a;
+
+    public static final String PREF_VERSION = "zepp_os_shortcut_cards_version";
+
+    public enum ShortcutCard {
+        WEATHER("2", "1"),
+        AQI("2", "2"),
+        FORECAST("2", "3"),
+        PAI("3", "1"),
+        ALARM("4", "1"),
+        SLEEP("5", "1"),
+        HEARTRATE("6", "1"),
+        ACTIVITY("7", "1"),
+        SPO2("8", "1"),
+        PHONE("9", "1"),
+        EVENTS("10", "1"),
+        STRESS("11", "1"),
+        THERMOMETER("12", "1"),
+        WORLDCLOCK("13", "1"),
+        TODO("17", "1"),
+        COUNTDOWN("18", "1"),
+        LAST_WORKOUT("19", "1"),
+        TOTAL_WORKOUT("19", "2"),
+        WORKOUT_STATUS("19", "3"),
+        VO2_MAX("19", "4"),
+        MUSIC("20", "1"),
+        CYCLE_TRACKING("21", "1"),
+        ONE_TAP_MEASURING("22", "1"),
+        BREATHING("24", "1"),
+        STOPWATCH("25", "1"),
+        ZEPP_COACH("27", "1"),
+        RECOMMENDATION("28", "1"),
+        BODY_COMPOSITION("33", "1"),
+        READINESS("34", "1"),
+        ALEXA("35", "1"),
+        ZEPP_PAY("37", "1"),
+        CALORIES("38", "1"),
+        HRV("1047865", "1"),
+        ;
+
+        private final String appNum;
+        private final String cardNum;
+
+        ShortcutCard(final String appNum, final String cardNum) {
+            this.appNum = appNum;
+            this.cardNum = cardNum;
+        }
+
+        public String getAppNum() {
+            return appNum;
+        }
+
+        public String getCardNum() {
+            return cardNum;
+        }
+
+        public static ShortcutCard fromCodes(final String appNum, final String cardNum) {
+            for (ShortcutCard value : ShortcutCard.values()) {
+                if (value.getAppNum().equals(appNum) && value.getCardNum().equals(cardNum)) {
+                    return value;
+                }
+            }
+
+            return null;
+        }
+    }
+
+    private int version = 0;
+    private int maxCards = 0;
+
+    public ZeppOsShortcutCardsService(final ZeppOsSupport support) {
+        super(support, true);
+    }
+
+    @Override
+    public short getEndpoint() {
+        return ENDPOINT;
+    }
+
+    @Override
+    public void handlePayload(final byte[] payload) {
+        switch (payload[0]) {
+            case CMD_CAPABILITIES_RESPONSE:
+                version = payload[1];
+                getSupport().evaluateGBDeviceEvent(new GBDeviceEventUpdatePreferences(PREF_VERSION, version));
+                if (version != 3 && version != 4) {
+                    LOG.warn("Unsupported shortcut cards service version {}", version);
+                    return;
+                }
+                maxCards = payload[2] & 0xFF;
+                LOG.info("Shortcut cards version={}, maxCards={}", version, maxCards);
+                break;
+            case CMD_LIST_RET:
+                LOG.info("Got shortcut cards list");
+                parseShortcutCards(payload);
+                break;
+            case CMD_ENABLED_SET_ACK:
+                LOG.info("Got enabled shortcut cards ack, status = {}", payload[1]);
+                break;
+            default:
+                LOG.warn("Unexpected shortcut cards byte {}", String.format("0x%02x", payload[0]));
+        }
+    }
+
+    @Override
+    public boolean onSendConfiguration(final String config, final Prefs prefs) {
+        switch (config) {
+            case DeviceSettingsPreferenceConst.SHORTCUT_CARDS_SORTABLE:
+                final List<String> shortcutCards = prefs.getList(SHORTCUT_CARDS_SORTABLE, Collections.emptyList());
+                LOG.info("Setting shortcut cards to {}", shortcutCards);
+                setShortcutCards(shortcutCards);
+                return true;
+        }
+
+        return false;
+    }
+
+    @Override
+    public void initialize(final TransactionBuilder builder) {
+        requestCapabilities(builder);
+        requestShortcutCards(builder);
+    }
+
+    public void requestCapabilities(final TransactionBuilder builder) {
+        write(builder, CMD_CAPABILITIES_REQUEST);
+    }
+
+    public void requestShortcutCards(final TransactionBuilder builder) {
+        write(builder, CMD_LIST_GET);
+    }
+
+    public void parseShortcutCards(final byte[] payload) {
+        final ByteBuffer buf = ByteBuffer.wrap(payload).order(ByteOrder.LITTLE_ENDIAN);
+        buf.get(); // discard the command byte
+        final int numCards = buf.get() & 0xFF;
+        final List<String> allCards = new ArrayList<>();
+        final List<String> enabledCards = new ArrayList<>();
+
+        for (int i = 0; i < numCards; i++) {
+            final String appNum = StringUtils.untilNullTerminator(buf);
+            final String cardNum = StringUtils.untilNullTerminator(buf);
+            final boolean enabled = buf.get() == 0x01;
+            final String version = StringUtils.untilNullTerminator(buf);
+            final ShortcutCard card = ShortcutCard.fromCodes(appNum, cardNum);
+            final String cardPrefValue;
+            if (card != null) {
+                cardPrefValue = card.name().toLowerCase(Locale.ROOT);
+            } else {
+                LOG.warn("Unknown shortcut card [{}, {}]", appNum, cardNum);
+                cardPrefValue = appNum + "/" + cardNum;
+            }
+            allCards.add(cardPrefValue);
+            if (enabled) {
+                enabledCards.add(cardPrefValue);
+            }
+        }
+
+        final GBDeviceEventUpdatePreferences evt = new GBDeviceEventUpdatePreferences()
+                .withPreference(SHORTCUT_CARDS_SORTABLE, TextUtils.join(",", enabledCards))
+                .withPreference(DeviceSettingsUtils.getPrefPossibleValuesKey(SHORTCUT_CARDS_SORTABLE), TextUtils.join(",", allCards));
+        getSupport().evaluateGBDeviceEvent(evt);
+    }
+
+    public void setShortcutCards(final List<String> cardsPrefValue) {
+        if (maxCards == 0) {
+            LOG.warn("maxCards == 0, refusing");
+            return;
+        }
+
+        final List<String> cards = new ArrayList<>(cardsPrefValue);
+        if (cards.size() > maxCards) {
+            LOG.warn("Number of cards {} > maxCards {}, truncating", cards.size(), maxCards);
+            cards.subList(maxCards, cards.size()).clear();
+        }
+
+        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+
+        baos.write(CMD_ENABLED_SET);
+        baos.write(cards.size());
+
+        for (final String cardPrefValue : cards) {
+            String appNum;
+            String cardNum;
+            try {
+                final ShortcutCard card = ShortcutCard.valueOf(cardPrefValue.toUpperCase(Locale.ROOT));
+                appNum = card.getAppNum();
+                cardNum = card.getCardNum();
+            } catch (final IllegalArgumentException e) {
+                // attempt to parse as appNum/cardNum
+                final Matcher matcher = Pattern.compile("^([0-9a-fA-F]+)/([0-9a-fA-F]+)$").matcher(cardPrefValue);
+                if (matcher.find()) {
+                    appNum = matcher.group(1);
+                    cardNum = matcher.group(2);
+                } else {
+                    LOG.warn("Unexpected format for shortcut cards pref value {}", cardPrefValue);
+                    return;
+                }
+            }
+
+            try {
+                baos.write(appNum.getBytes(StandardCharsets.UTF_8));
+                baos.write(0);
+                baos.write(cardNum.getBytes(StandardCharsets.UTF_8));
+                baos.write(0);
+                baos.write(1); // enabled
+                baos.write(0); // ?
+            } catch (final IOException e) {
+                throw new RuntimeException(e);
+            }
+        }
+
+        write("set enabled shortcut cards", baos.toByteArray());
+    }
+
+    public static boolean isSupported(final Prefs devicePrefs) {
+        final int version = devicePrefs.getInt(PREF_VERSION, 0);
+        return version == 3 || version == 4;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsWatchfaceService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsWatchfaceService.java
new file mode 100644
index 0000000..c19679b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsWatchfaceService.java
@@ -0,0 +1,266 @@
+/*  Copyright (C) 2023-2024 Andreas Shimokawa, Jos茅 Rebelo, Maxime Reyrolle
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services;
+
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_WATCHFACE;
+
+import android.content.Context;
+import android.text.TextUtils;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.UUID;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsUtils;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdatePreferences;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.AbstractZeppOsService;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+public class ZeppOsWatchfaceService extends AbstractZeppOsService {
+    private static final Logger LOG = LoggerFactory.getLogger(ZeppOsWatchfaceService.class);
+
+    private static final short ENDPOINT = 0x0023;
+
+    public static final byte CMD_LIST_GET = 0x05;
+    public static final byte CMD_LIST_RET = 0x06;
+    public static final byte CMD_SET = 0x07;
+    public static final byte CMD_SET_ACK = 0x08;
+    public static final byte CMD_CURRENT_GET = 0x09;
+    public static final byte CMD_CURRENT_RET = 0x0a;
+    public static final byte CMD_WATCHFACE_CHANGED = (byte) 0xff;
+
+    public enum Watchface {
+        // Codes are from GTR 4, not sure if they match on other watches
+        RED_FANTASY(0x00002D38, R.string.zepp_os_watchface_red_fantasy),
+        MULTIPLE_DATA(0x00002D10, R.string.zepp_os_watchface_multiple_data),
+        RUSH(0x00002D37, R.string.zepp_os_watchface_rush),
+        MINIMALIST(0x00002D0E, R.string.zepp_os_watchface_minimalist),
+        SIMPLICITY_DATA(0x00002D08, R.string.zepp_os_watchface_simplicity_data),
+        VIBRANT(0x00002D09, R.string.zepp_os_watchface_vibrant),
+        BUSINESS_STYLE(0x00002D0D, R.string.zepp_os_watchface_business_style),
+        EMERALD_MOONLIGHT(0x00002D0A, R.string.zepp_os_watchface_emerald_moonlight),
+        ROTATING_EARTH(0x00002D0F, R.string.zepp_os_watchface_rotating_earth),
+        SUPERPOSITION(0x00002D0C, R.string.zepp_os_watchface_superposition),
+
+        // Codes are from Balance, not sure if they match on other watches
+        VAST_SKY(0x00002DB6, R.string.zepp_os_watchface_vast_sky),
+        LIGHTNING_FLASH(0x00002DB7, R.string.zepp_os_watchface_lightning_flash),
+        FREE_COMBINATION(0x00002DB9, R.string.zepp_os_watchface_free_combination),
+        PURE_WHITE(0x00002DBA, R.string.zepp_os_watchface_pure_white),
+        GUIDER(0x00002DBB, R.string.zepp_os_watchface_guider),
+        CITY_OF_SPEED(0x00002DBC, R.string.zepp_os_watchface_city_of_speed),
+        STARRY_SKY(0x00002DBD, R.string.zepp_os_watchface_starry_sky),
+        THE_ULTIMA(0x00002DBE, R.string.zepp_os_watchface_the_ultima),
+        ;
+
+        private final int code;
+        private final int nameResId;
+
+        Watchface(final int code, final int nameResId) {
+            this.code = code;
+            this.nameResId = nameResId;
+        }
+
+        public int getCode() {
+            return code;
+        }
+
+        public String getName(final Context context) {
+            return context.getString(nameResId);
+        }
+
+        public static Watchface fromCode(final int code) {
+            for (final Watchface watchface : values()) {
+                if (watchface.getCode() == code) {
+                    return watchface;
+                }
+            }
+
+            return null;
+        }
+    }
+
+    final List<GBDeviceApp> watchfaces = new ArrayList<>();
+
+    public ZeppOsWatchfaceService(final ZeppOsSupport support) {
+        super(support, true);
+    }
+
+    @Override
+    public short getEndpoint() {
+        return ENDPOINT;
+    }
+
+    @Override
+    public void handlePayload(final byte[] payload) {
+        switch (payload[0]) {
+            case CMD_LIST_RET:
+                LOG.info("Got watchface list, status = {}", payload[1]);
+                if (payload[1] != 1) {
+                    LOG.warn("Unexpected status byte {}", payload[1]);
+                    return;
+                }
+                parseWatchfaceList(payload);
+                break;
+            case CMD_SET_ACK:
+                LOG.info("Got watchface set ack, status = {}", payload[1]);
+                break;
+            case CMD_CURRENT_RET:
+                final int watchface = BLETypeConversions.toUint32(payload, 1);
+                final String watchfaceHex = String.format(Locale.ROOT, "0x%08X", watchface);
+                LOG.info("Got current watchface = {}", watchfaceHex);
+                getSupport().evaluateGBDeviceEvent(new GBDeviceEventUpdatePreferences(PREF_WATCHFACE, watchfaceHex));
+                break;
+            case CMD_WATCHFACE_CHANGED:
+                LOG.info("Watchface changed");
+                requestCurrentWatchface();
+                break;
+            default:
+                LOG.warn("Unexpected watchface byte {}", String.format("0x%02x", payload[0]));
+        }
+    }
+
+    @Override
+    public boolean onSendConfiguration(final String config, final Prefs prefs) {
+        switch (config) {
+            case DeviceSettingsPreferenceConst.PREF_WATCHFACE:
+                final String watchface = prefs.getString(DeviceSettingsPreferenceConst.PREF_WATCHFACE, null);
+                LOG.info("Setting watchface to {}", watchface);
+                setWatchface(watchface);
+                return true;
+        }
+
+        return false;
+    }
+
+    @Override
+    public void initialize(final TransactionBuilder builder) {
+        requestWatchfaces(builder);
+        requestCurrentWatchface(builder);
+    }
+
+    public List<GBDeviceApp> getWatchfaces() {
+        return watchfaces;
+    }
+
+    public void requestWatchfaces(final TransactionBuilder builder) {
+        write(builder, CMD_LIST_GET);
+    }
+
+    public void requestCurrentWatchface() {
+        try {
+            final TransactionBuilder builder = new TransactionBuilder("request current watchface");
+            requestCurrentWatchface(builder);
+            builder.queue(getSupport().getQueue());
+        } catch (final Exception e) {
+            LOG.error("Failed to request current watchface", e);
+        }
+    }
+
+    public void requestCurrentWatchface(final TransactionBuilder builder) {
+        write(builder, CMD_CURRENT_GET);
+    }
+
+    private void parseWatchfaceList(final byte[] payload) {
+        watchfaces.clear();
+
+        final ByteBuffer buf = ByteBuffer.wrap(payload).order(ByteOrder.LITTLE_ENDIAN);
+        buf.get(); // discard the command byte
+        buf.get(); // discard the status byte
+        final int numWatchfaces = buf.get() & 0xFF;
+
+        final List<String> watchfacePrefValues = new ArrayList<>();
+
+        for (int i = 0; i < numWatchfaces; i++) {
+            final int watchfaceCode = buf.getInt();
+            final Watchface watchface = Watchface.fromCode(watchfaceCode);
+            final String watchfaceName;
+            if (watchface != null) {
+                watchfaceName = watchface.getName(getContext());
+                watchfacePrefValues.add(watchface.name().toLowerCase(Locale.ROOT));
+            } else {
+                watchfaceName = "";
+                final String watchfaceHex = String.format(Locale.ROOT, "0x%08X", watchfaceCode);
+                watchfacePrefValues.add(watchfaceHex);
+                LOG.warn("Unknown watchface code {}", watchfaceHex);
+            }
+
+            watchfaces.add(new GBDeviceApp(
+                    UUID.fromString(String.format("%08x-0000-0000-0000-000000000000", watchfaceCode)),
+                    watchfaceName,
+                    "",
+                    "",
+                    GBDeviceApp.Type.WATCHFACE
+            ));
+        }
+
+        // TODO broadcast something to update app manager
+        final GBDeviceEventUpdatePreferences evt = new GBDeviceEventUpdatePreferences()
+                .withPreference(DeviceSettingsUtils.getPrefPossibleValuesKey(PREF_WATCHFACE), TextUtils.join(",", watchfacePrefValues));
+        getSupport().evaluateGBDeviceEvent(evt);
+    }
+
+    public void setWatchface(final UUID uuid) {
+        setWatchface(Integer.parseInt(uuid.toString().split("-")[0], 16));
+    }
+
+    public void setWatchface(final String watchfacePrefValue) {
+        if (watchfacePrefValue == null) {
+            LOG.warn("watchface is null");
+            return;
+        }
+
+        int watchfaceInt;
+
+        try {
+            final Watchface watchfaceEnum = Watchface.valueOf(watchfacePrefValue.toUpperCase(Locale.ROOT));
+            watchfaceInt = watchfaceEnum.getCode();
+        } catch (final IllegalArgumentException e) {
+            // attempt to parse as hex
+            final Matcher matcher = Pattern.compile("^0[xX]([0-9a-fA-F]+)$").matcher(watchfacePrefValue);
+            if (!matcher.find()) {
+                LOG.warn("Failed to parse watchface '{}' as hex", watchfacePrefValue);
+                return;
+            }
+            watchfaceInt = Integer.parseInt(matcher.group(1), 16);
+        }
+
+        setWatchface(watchfaceInt);
+    }
+
+    public void setWatchface(final int watchfaceId) {
+        final ByteBuffer buf = ByteBuffer.allocate(5).order(ByteOrder.LITTLE_ENDIAN);
+        buf.put(CMD_SET);
+        buf.putInt(watchfaceId);
+
+        write("set watchface", buf.array());
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsWifiService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsWifiService.java
new file mode 100644
index 0000000..b2b2b06
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsWifiService.java
@@ -0,0 +1,174 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services;
+
+import android.widget.Toast;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.Locale;
+
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdatePreferences;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.AbstractZeppOsService;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
+
+public class ZeppOsWifiService extends AbstractZeppOsService {
+    private static final Logger LOG = LoggerFactory.getLogger(ZeppOsWifiService.class);
+
+    private static final short ENDPOINT = 0x0003;
+
+    public static final byte WIFI_CMD_HOTSPOT_START = 0x11;
+    public static final byte WIFI_CMD_HOTSPOT_STOP = 0x12;
+    public static final byte WIFI_CMD_HOTSPOT_STATE = 0x13;
+
+    private Callback mCallback = null;
+
+    public ZeppOsWifiService(final ZeppOsSupport support) {
+        super(support, true);
+    }
+
+    @Override
+    public short getEndpoint() {
+        return ENDPOINT;
+    }
+
+    @Override
+    public void handlePayload(final byte[] payload) {
+        switch (payload[0]) {
+            case WIFI_CMD_HOTSPOT_STATE:
+                LOG.info("Wi-Fi hotspot state = {}", payload[1]);
+
+                final String stateHex = String.format(Locale.ROOT, "0x%02x", payload[1]);
+                final String stateStr;
+                switch (payload[1]) {
+                    case 0x00:
+                        stateStr = "Stopped (" + stateHex + ")";
+                        if (mCallback != null) {
+                            mCallback.onWifiHotspotStop();
+                        }
+                        break;
+                    case 0x02:
+                        stateStr = "Started (" + stateHex + ")";
+                        if (mCallback != null) {
+                            mCallback.onWifiHotspotStart();
+                        }
+                        break;
+                    default:
+                        stateStr = "Unknown (" + stateHex + ")";
+                        break;
+                }
+
+                // TODO: This toast + preference update should not be here
+                if (mCallback == null) {
+                    // If there's no callback, show a toast (eg. used from developer options)
+                    GB.toast("Wi-Fi hotspot state: " + stateStr, Toast.LENGTH_SHORT, GB.INFO);
+                }
+
+                final GBDeviceEventUpdatePreferences evt = new GBDeviceEventUpdatePreferences()
+                        .withPreference(DeviceSettingsPreferenceConst.WIFI_HOTSPOT_STATUS, stateStr);
+                getSupport().evaluateGBDeviceEvent(evt);
+                return;
+            default:
+                LOG.warn("Unexpected Wi-Fi byte {}", String.format("0x%02x", payload[0]));
+        }
+    }
+
+    @Override
+    public boolean onSendConfiguration(final String config, final Prefs prefs) {
+        switch (config) {
+            case DeviceSettingsPreferenceConst.WIFI_HOTSPOT_START:
+                final String ssid = prefs.getString(DeviceSettingsPreferenceConst.WIFI_HOTSPOT_SSID, "");
+                if (StringUtils.isNullOrEmpty(ssid)) {
+                    LOG.error("Wi-Fi hotspot SSID not specified");
+                    return true;
+                }
+
+                final String password = prefs.getString(DeviceSettingsPreferenceConst.WIFI_HOTSPOT_PASSWORD, "");
+                if (StringUtils.isNullOrEmpty(password) || password.length() < 8) {
+                    LOG.error("Wi-Fi hotspot password is not valid");
+                    return true;
+                }
+                startWifiHotspot(ssid, password);
+                return true;
+            case DeviceSettingsPreferenceConst.WIFI_HOTSPOT_STOP:
+                stopWifiHotspot();
+                return true;
+            case DeviceSettingsPreferenceConst.WIFI_HOTSPOT_SSID:
+            case DeviceSettingsPreferenceConst.WIFI_HOTSPOT_PASSWORD:
+            case DeviceSettingsPreferenceConst.WIFI_HOTSPOT_STATUS:
+                // Ignore preferences that are not reloadable
+                return true;
+        }
+
+        return false;
+    }
+
+    @Override
+    public void initialize(final TransactionBuilder builder) {
+
+    }
+
+    public void setCallback(final Callback callback) {
+        mCallback = callback;
+    }
+
+    public void removeCallback() {
+        mCallback = null;
+    }
+
+    public void startWifiHotspot(final String ssid, final String password) {
+        LOG.info("Starting Wi-Fi hotspot SSID={}", ssid);
+
+        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+
+        try {
+            baos.write(WIFI_CMD_HOTSPOT_START);
+            baos.write(ssid.getBytes(StandardCharsets.UTF_8));
+            baos.write(0);
+            baos.write(password.getBytes(StandardCharsets.UTF_8));
+            baos.write(0);
+            baos.write("gadgetbridge".getBytes(StandardCharsets.UTF_8));
+            baos.write(0);
+        } catch (final IOException e) {
+            LOG.error("Failed to create command", e);
+            return;
+        }
+
+        write("start wifi hotspot", baos.toByteArray());
+    }
+
+    public void stopWifiHotspot() {
+        LOG.info("Stopping Wi-Fi hotspot");
+
+        write("start wifi hotspot", WIFI_CMD_HOTSPOT_STOP);
+    }
+
+    public interface Callback {
+        void onWifiHotspotStart();
+
+        void onWifiHotspotStop();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/AsynchronousResponse.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/AsynchronousResponse.java
new file mode 100644
index 0000000..8847df0
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/AsynchronousResponse.java
@@ -0,0 +1,728 @@
+/*  Copyright (C) 2024 Damien Gaignon, Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.media.AudioManager;
+import android.os.Build;
+import android.os.Handler;
+import android.os.Looper;
+import android.widget.Toast;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.time.LocalTime;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.HashMap;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.CameraActivity;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventCallControl;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventCameraRemote;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventFindPhone;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventMusicControl;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.App;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Calls;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.CameraRemote;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.DeviceConfig;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Ephemeris;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.EphemerisFileUpload;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FindPhone;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.GpsAndTime;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Menstrual;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.MusicControl;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FileUpload;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.P2P;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Watchface;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Weather;
+import nodomain.freeyourgadget.gadgetbridge.model.BatteryState;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetBatteryLevelRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.Request;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetPhoneInfoRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendFileUploadComplete;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendGpsStatusRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendMenstrualModifyTimeRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendFileUploadAck;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendFileUploadChunk;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendFileUploadHash;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendWatchfaceConfirm;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendWatchfaceOperation;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SetMusicStatusRequest;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+/**
+ * Handles responses that are not a reply to a request
+ */
+public class AsynchronousResponse {
+    private static final Logger LOG = LoggerFactory.getLogger(AsynchronousResponse.class);
+
+    private final HuaweiSupportProvider support;
+    private final Handler mFindPhoneHandler = new Handler();
+    private final static HashMap<Integer, String> dayOfWeekMap = new HashMap<>();
+
+    static {
+        dayOfWeekMap.put(Calendar.MONDAY, DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_MO);
+        dayOfWeekMap.put(Calendar.TUESDAY, DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_TU);
+        dayOfWeekMap.put(Calendar.WEDNESDAY, DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_WE);
+        dayOfWeekMap.put(Calendar.THURSDAY, DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_TH);
+        dayOfWeekMap.put(Calendar.FRIDAY, DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_FR);
+        dayOfWeekMap.put(Calendar.SATURDAY, DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_SA);
+        dayOfWeekMap.put(Calendar.SUNDAY, DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_SU);
+    }
+
+    public AsynchronousResponse(HuaweiSupportProvider support) {
+        this.support = support;
+    }
+
+    public void handleResponse(HuaweiPacket response) {
+        // Ignore messages if the key isn't set yet
+        if (support.getParamsProvider().getSecretKey() == null)
+            return;
+
+        try {
+            response.parseTlv();
+        } catch (HuaweiPacket.ParseException e) {
+            LOG.error("Parse TLV exception", e);
+            return;
+        }
+
+        try {
+            handleFindPhone(response);
+            handleMusicControls(response);
+            handleCallControls(response);
+            handlePhoneInfo(response);
+            handleMenstrualModifyTime(response);
+            handleWeatherCheck(response);
+            handleGpsRequest(response);
+            handleFileUpload(response);
+            handleWatchface(response);
+            handleCameraRemote(response);
+            handleApp(response);
+            handleP2p(response);
+            handleEphemeris(response);
+            handleEphemerisUploadService(response);
+            handleAsyncBattery(response);
+        } catch (Request.ResponseParseException e) {
+            LOG.error("Response parse exception", e);
+        }
+    }
+
+    private void handleFindPhone(HuaweiPacket response) throws Request.ResponseParseException {
+        if (response.serviceId == FindPhone.id && response.commandId == FindPhone.Response.id) {
+            if (!(response instanceof FindPhone.Response))
+                throw new Request.ResponseTypeMismatchException(response, FindPhone.Response.class);
+
+            SharedPreferences sharedPreferences = GBApplication.getDeviceSpecificSharedPrefs(support.getDeviceMac());
+
+            String findPhone = sharedPreferences.getString(DeviceSettingsPreferenceConst.PREF_FIND_PHONE, support.getContext().getString(R.string.p_off));
+
+            if (findPhone.equals(support.getContext().getString(R.string.p_off))) {
+                LOG.debug("Find phone command received, but it is disabled");
+                // TODO: hide applet on device
+                return;
+            }
+
+            if (sharedPreferences.getBoolean("disable_find_phone_with_dnd", false) && dndActive()) {
+                LOG.debug("Find phone command received, ringing prevented because of DND");
+                // TODO: stop the band from showing as ringing
+                return;
+            }
+
+            if (!findPhone.equals(support.getContext().getString(R.string.p_on))) {
+                // Duration set, stop after specified time
+                String strDuration = sharedPreferences.getString(DeviceSettingsPreferenceConst.PREF_FIND_PHONE_DURATION, "0");
+
+                int duration = Integer.parseInt(strDuration);
+                if (duration > 0) {
+                    mFindPhoneHandler.postDelayed(new Runnable() {
+                        @Override
+                        public void run() {
+                            GBDeviceEventFindPhone findPhoneEvent = new GBDeviceEventFindPhone();
+                            findPhoneEvent.event = GBDeviceEventFindPhone.Event.STOP;
+                            support.evaluateGBDeviceEvent(findPhoneEvent);
+
+                            // TODO: stop the band from showing as ringing
+                        }
+                    }, duration * 1000L);
+                }
+            }
+
+            GBDeviceEventFindPhone findPhoneEvent = new GBDeviceEventFindPhone();
+            if (((FindPhone.Response) response).start)
+                findPhoneEvent.event = GBDeviceEventFindPhone.Event.START;
+            else
+                findPhoneEvent.event = GBDeviceEventFindPhone.Event.STOP;
+            support.evaluateGBDeviceEvent(findPhoneEvent);
+        }
+    }
+
+    private boolean dndActive() {
+        SharedPreferences sharedPreferences = GBApplication.getDeviceSpecificSharedPrefs(support.getDeviceMac());
+
+        String dndSwitch = sharedPreferences.getString(DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB, "off");
+        if (dndSwitch.equals(DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_OFF))
+            return false;
+
+        String startStr = sharedPreferences.getString(DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_START, "00:00");
+        if (dndSwitch.equals("automatic")) startStr = "00:00";
+        String endStr = sharedPreferences.getString(DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_END, "23:59");
+        if (dndSwitch.equals("automatic")) endStr = "23:59";
+
+        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
+            LocalTime currentTime = LocalTime.now();
+            LocalTime start = LocalTime.parse(startStr);
+            LocalTime end = LocalTime.parse(endStr);
+
+            if (start.isAfter(currentTime))
+                return false;
+            if (end.isBefore(currentTime))
+                return false;
+        } else {
+            @SuppressLint("SimpleDateFormat") SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm");
+            try {
+                Date currentTime = dateFormat.parse(String.format(GBApplication.getLanguage(), "%d:%d",
+                        Calendar.getInstance().get(Calendar.HOUR_OF_DAY),
+                        Calendar.getInstance().get(Calendar.MINUTE)));
+                Date start = dateFormat.parse(startStr);
+                Date end = dateFormat.parse(endStr);
+
+                assert start != null;
+                if (start.after(currentTime))
+                    return false;
+                assert end != null;
+                if (end.before(currentTime))
+                    return false;
+            } catch (ParseException e) {
+                LOG.error("Parse exception for DnD", e);
+            }
+        }
+
+        Calendar date = Calendar.getInstance();
+        String preferenceString = dayOfWeekMap.get(date.get(Calendar.DAY_OF_WEEK));
+
+        return sharedPreferences.getBoolean(preferenceString, true);
+    }
+
+    /**
+     * Handles asynchronous music packet, for the following events:
+     * - The app is opened on the band (sends back music info)
+     * - A button is clicked
+     * - Play
+     * - Pause
+     * - Previous
+     * - Next
+     * - The volume is adjusted
+     *
+     * @param response Packet to be handled
+     */
+    private void handleMusicControls(HuaweiPacket response) throws Request.ResponseParseException {
+        if (response.serviceId == MusicControl.id) {
+            AudioManager audioManager = (AudioManager) this.support.getContext().getSystemService(Context.AUDIO_SERVICE);
+
+            if (response.commandId == MusicControl.MusicStatusResponse.id) {
+                if (!(response instanceof MusicControl.MusicStatusResponse))
+                    throw new Request.ResponseTypeMismatchException(response, MusicControl.MusicStatusResponse.class);
+
+                MusicControl.MusicStatusResponse resp = (MusicControl.MusicStatusResponse) response;
+                if (resp.status != -1 && resp.status != 0x000186A0) {
+                    LOG.warn("Music information error, will stop here: " + Integer.toHexString(resp.status));
+                    return;
+                }
+
+                LOG.debug("Music information requested, sending acknowledgement and music info.");
+                SetMusicStatusRequest setMusicStatusRequest = new SetMusicStatusRequest(this.support, MusicControl.MusicStatusResponse.id, MusicControl.successValue);
+                try {
+                    setMusicStatusRequest.doPerform();
+                } catch (IOException e) {
+                    GB.toast("Failed to send music status request", Toast.LENGTH_SHORT, GB.ERROR, e);
+                    LOG.error("Failed to send music status request (1)", e);
+                }
+
+                // Update and send Music Info
+                this.support.refreshMediaManager();
+                this.support.sendSetMusic();
+            } else if (response.commandId == MusicControl.Control.id) {
+                if (!(response instanceof MusicControl.Control.Response))
+                    throw new Request.ResponseTypeMismatchException(response, MusicControl.Control.Response.class);
+
+                MusicControl.Control.Response resp = (MusicControl.Control.Response) response;
+
+                if (resp.buttonPresent) {
+                    if (resp.button != MusicControl.Control.Response.Button.Unknown) {
+                        GBDeviceEventMusicControl musicControl = new GBDeviceEventMusicControl();
+                        switch (resp.button) {
+                            case Play:
+                                LOG.debug("Music - Play button event received");
+                                musicControl.event = GBDeviceEventMusicControl.Event.PLAY;
+                                break;
+                            case Pause:
+                                LOG.debug("Music - Pause button event received");
+                                musicControl.event = GBDeviceEventMusicControl.Event.PAUSE;
+                                break;
+                            case Previous:
+                                LOG.debug("Music - Previous button event received");
+                                musicControl.event = GBDeviceEventMusicControl.Event.PREVIOUS;
+                                break;
+                            case Next:
+                                LOG.debug("Music - Next button event received");
+                                musicControl.event = GBDeviceEventMusicControl.Event.NEXT;
+                                break;
+                            case Volume_up:
+                                LOG.debug("Music - Volume up button event received");
+                                musicControl.event = GBDeviceEventMusicControl.Event.VOLUMEUP;
+                                break;
+                            case Volume_down:
+                                LOG.debug("Music - Volume down button event received");
+                                musicControl.event = GBDeviceEventMusicControl.Event.VOLUMEDOWN;
+                                break;
+                            default:
+                        }
+                        this.support.evaluateGBDeviceEvent(musicControl);
+                    }
+                }
+                if (resp.volumePresent) {
+                    byte volume = resp.volume;
+                    if (volume > audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC)) {
+                        LOG.warn("Music - Received volume is too high: 0x"
+                                + Integer.toHexString(volume)
+                                + " > 0x"
+                                + Integer.toHexString(audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC))
+                        );
+                        // TODO: probably best to send back an error code, though I wouldn't know which
+                        return;
+                    }
+                    if (Build.VERSION.SDK_INT > 28) {
+                        if (volume < audioManager.getStreamMinVolume(AudioManager.STREAM_MUSIC)) {
+                            LOG.warn("Music - Received volume is too low: 0x"
+                                    + Integer.toHexString(volume)
+                                    + " < 0x"
+                                    + audioManager.getStreamMinVolume(AudioManager.STREAM_MUSIC)
+                            );
+                            // TODO: probably best to send back an error code, though I wouldn't know which
+                            return;
+                        }
+                    }
+                    LOG.debug("Music - Setting volume to: 0x" + Integer.toHexString(volume));
+                    audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume, 0);
+                }
+
+                if (resp.buttonPresent || resp.volumePresent) {
+                    SetMusicStatusRequest setMusicStatusRequest = new SetMusicStatusRequest(this.support, MusicControl.Control.id, MusicControl.successValue);
+                    try {
+                        setMusicStatusRequest.doPerform();
+                    } catch (IOException e) {
+                        GB.toast("Failed to send music status request", Toast.LENGTH_SHORT, GB.ERROR, e);
+                        LOG.error("Failed to send music status request (2)", e);
+                    }
+
+                    // Delay so the media app has a moment to change state
+                    new Handler(Looper.getMainLooper()).postDelayed(() -> {
+                        // Update and send Music Info
+                        this.support.refreshMediaManager();
+                        this.support.sendSetMusic();
+                    }, 100);
+                }
+            } else if (response.commandId == MusicControl.UploadMusicFileInfo.id) {
+                if (!(response instanceof MusicControl.UploadMusicFileInfo.UploadMusicFileInfoRequest))
+                    throw new Request.ResponseTypeMismatchException(response, MusicControl.UploadMusicFileInfo.UploadMusicFileInfoRequest.class);
+
+                MusicControl.UploadMusicFileInfo.UploadMusicFileInfoRequest resp = (MusicControl.UploadMusicFileInfo.UploadMusicFileInfoRequest) response;
+                support.getHuaweiMusicManager().uploadMusicInfo(resp.songIndex, resp.songFileName);
+            }
+        }
+    }
+
+    private void handleCallControls(HuaweiPacket response) throws Request.ResponseParseException {
+        if (response.serviceId == Calls.id && response.commandId == Calls.AnswerCallResponse.id) {
+            if (!(response instanceof Calls.AnswerCallResponse))
+                throw new Request.ResponseTypeMismatchException(response, Calls.AnswerCallResponse.class);
+
+            SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(support.getDevice().getAddress());
+
+            GBDeviceEventCallControl callControlEvent = new GBDeviceEventCallControl();
+            switch (((Calls.AnswerCallResponse) response).action) {
+                case UNKNOWN:
+                    LOG.info("Unknown action for call");
+                    return;
+                case CALL_ACCEPT:
+                    callControlEvent.event = GBDeviceEventCallControl.Event.ACCEPT;
+                    LOG.info("Accepted call");
+
+                    if (!prefs.getBoolean("enable_call_accept", true)) {
+                        LOG.info("Disabled accepting calls, ignoring");
+                        return;
+                    }
+
+                    break;
+                case CALL_REJECT:
+                    callControlEvent.event = GBDeviceEventCallControl.Event.REJECT;
+                    LOG.info("Rejected call");
+
+                    if (!prefs.getBoolean("enable_call_reject", true)) {
+                        LOG.info("Disabled rejecting calls, ignoring");
+                        return;
+                    }
+
+                    break;
+            }
+            support.evaluateGBDeviceEvent(callControlEvent);
+        }
+    }
+
+    private void handlePhoneInfo(HuaweiPacket response) {
+        if (response.serviceId == DeviceConfig.id && response.commandId == DeviceConfig.PhoneInfo.id) {
+            if (!(response instanceof DeviceConfig.PhoneInfo.Response)) {
+                // TODO: exception
+                return;
+            }
+            DeviceConfig.PhoneInfo.Response phoneInfoResp = (DeviceConfig.PhoneInfo.Response) response;
+            if (phoneInfoResp.isAck) {
+                LOG.info("Not responding to ack for PhoneInfo");
+                return;
+            }
+            GetPhoneInfoRequest getPhoneInfoReq = new GetPhoneInfoRequest(this.support, phoneInfoResp.info);
+            try {
+                getPhoneInfoReq.doPerform();
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
+    private void handleMenstrualModifyTime(HuaweiPacket response) {
+        if (response.serviceId == Menstrual.id && response.commandId == Menstrual.ModifyTime.id) {
+            if (!(response instanceof Menstrual.ModifyTime.Response)) {
+                // TODO: exception
+                return;
+            }
+            //Menstrual.ModifyTime.Response menstrualModifyTimeResp = (Menstrual.ModifyTime.Response) response;
+            SendMenstrualModifyTimeRequest sendMenstrualModifyTimeReq = new SendMenstrualModifyTimeRequest(this.support);
+            try {
+                sendMenstrualModifyTimeReq.doPerform();
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+
+        }
+    }
+
+    private void handleFileUpload(HuaweiPacket response) throws Request.ResponseParseException {
+        if (response.serviceId == FileUpload.id) {
+            if (response.commandId == FileUpload.FileInfoSend.id) {
+                if (!(response instanceof FileUpload.FileInfoSend.Response))
+                    throw new Request.ResponseTypeMismatchException(response, FileUpload.FileInfoSend.Response.class);
+                if (support.huaweiUploadManager.getFileUploadInfo() == null) {
+                    LOG.error("Upload file info received but no file to upload");
+                } else {
+                    FileUpload.FileInfoSend.Response resp = (FileUpload.FileInfoSend.Response) response;
+                    if (resp.result != 100000) {
+                        if (support.huaweiUploadManager.getFileUploadInfo().getFileUploadCallback() != null) {
+                            support.huaweiUploadManager.getFileUploadInfo().getFileUploadCallback().onError(resp.result);
+                        } else {
+                            LOG.error("Upload file info error without callback: {}", resp.result);
+                        }
+                        //Cleanup
+                        support.huaweiUploadManager.setFileUploadInfo(null);
+                    }
+                }
+            } else if (response.commandId == FileUpload.FileHashSend.id) {
+                if (!(response instanceof FileUpload.FileHashSend.Response))
+                    throw new Request.ResponseTypeMismatchException(response, FileUpload.FileHashSend.Response.class);
+                if (support.huaweiUploadManager.getFileUploadInfo() == null) {
+                    LOG.error("Upload file hash requested but no file to upload");
+                } else {
+                    FileUpload.FileHashSend.Response resp = (FileUpload.FileHashSend.Response) response;
+                    support.huaweiUploadManager.getFileUploadInfo().setFileId(resp.fileId);
+                    try {
+                        SendFileUploadHash sendFileUploadHash = new SendFileUploadHash(support, support.huaweiUploadManager);
+                        sendFileUploadHash.doPerform();
+                    } catch (IOException e) {
+                        LOG.error("Could not send file upload hash request", e);
+                    }
+                }
+            } else if (response.commandId == FileUpload.FileUploadConsultAck.id) {
+                if (!(response instanceof FileUpload.FileUploadConsultAck.Response))
+                    throw new Request.ResponseTypeMismatchException(response, FileUpload.FileUploadConsultAck.Response.class);
+                if (support.huaweiUploadManager.getFileUploadInfo() == null) {
+                    LOG.error("Upload file ask requested but no file to upload");
+                } else {
+                    FileUpload.FileUploadConsultAck.Response resp = (FileUpload.FileUploadConsultAck.Response) response;
+                    support.huaweiUploadManager.getFileUploadInfo().setFileUploadParams(resp.fileUploadParams);
+                    try {
+                        if (support.huaweiUploadManager.getFileUploadInfo().getFileUploadCallback() != null) {
+                            support.huaweiUploadManager.getFileUploadInfo().getFileUploadCallback().onUploadStart();
+                        }
+                        SendFileUploadAck sendFileUploadAck = new SendFileUploadAck(support,
+                                resp.fileUploadParams.no_encrypt, support.huaweiUploadManager.getFileUploadInfo().getFileId());
+                        sendFileUploadAck.doPerform();
+                    } catch (IOException e) {
+                        LOG.error("Could not send file upload ack request", e);
+                    }
+                }
+            } else if (response.commandId == FileUpload.FileNextChunkParams.id) {
+                if (!(response instanceof FileUpload.FileNextChunkParams))
+                    throw new Request.ResponseTypeMismatchException(response, FileUpload.FileNextChunkParams.class);
+                if (support.huaweiUploadManager.getFileUploadInfo() == null) {
+                    LOG.error("Upload file next chunk requested but no file to upload");
+                } else {
+                    FileUpload.FileNextChunkParams resp = (FileUpload.FileNextChunkParams) response;
+                    support.huaweiUploadManager.getFileUploadInfo().setUploadChunkSize(resp.nextchunkSize);
+                    support.huaweiUploadManager.getFileUploadInfo().setCurrentUploadPosition(resp.bytesUploaded);
+                    int progress = Math.round(((float) resp.bytesUploaded / (float) support.huaweiUploadManager.getFileUploadInfo().getFileSize()) * 100);
+                    try {
+                        if (support.huaweiUploadManager.getFileUploadInfo().getFileUploadCallback() != null) {
+                            support.huaweiUploadManager.getFileUploadInfo().getFileUploadCallback().onUploadProgress(progress);
+                        }
+                        SendFileUploadChunk sendFileUploadChunk = new SendFileUploadChunk(support, support.huaweiUploadManager);
+                        sendFileUploadChunk.doPerform();
+                    } catch (IOException e) {
+                        LOG.error("Could not send fileupload next chunk request", e);
+                    }
+                }
+            } else if (response.commandId == FileUpload.FileUploadResult.id) {
+                if (support.huaweiUploadManager.getFileUploadInfo() == null) {
+                    LOG.error("Upload file result requested but no file to upload");
+                } else {
+                    try {
+                        byte fileId = support.huaweiUploadManager.getFileUploadInfo().getFileId();
+                        if (support.huaweiUploadManager.getFileUploadInfo().getFileUploadCallback() != null) {
+                            support.huaweiUploadManager.getFileUploadInfo().getFileUploadCallback().onUploadComplete();
+                        }
+                        //Cleanup
+                        support.huaweiUploadManager.setFileUploadInfo(null);
+                        SendFileUploadComplete sendFileUploadComplete = new SendFileUploadComplete(this.support, fileId);
+                        sendFileUploadComplete.doPerform();
+                    } catch (IOException e) {
+                        LOG.error("Could not send file upload result request", e);
+                    }
+                }
+            }
+        }
+    }
+
+    private void handleWatchface(HuaweiPacket response) throws Request.ResponseParseException {
+        if (response.serviceId == Watchface.id) {
+            if (response.commandId == Watchface.WatchfaceConfirm.id) {
+                try {
+                    if (!(response instanceof Watchface.WatchfaceConfirm.Response))
+                        throw new Request.ResponseTypeMismatchException(response, Watchface.WatchfaceConfirm.class);
+                    Watchface.WatchfaceConfirm.Response resp = (Watchface.WatchfaceConfirm.Response) response;
+                    SendWatchfaceConfirm sendWatchfaceConfirm = new SendWatchfaceConfirm(this.support, resp.fileName);
+                    sendWatchfaceConfirm.doPerform();
+                    if (resp.reportType == 0x02) {
+                        //make uploaded watchface active
+                        SendWatchfaceOperation sendWatchfaceOperation = new SendWatchfaceOperation(this.support, resp.fileName, Watchface.WatchfaceOperation.operationActive);
+                        sendWatchfaceOperation.doPerform();
+                    }
+                } catch (IOException e) {
+                    LOG.error("Could not send watchface confirm request", e);
+                }
+
+            }
+        }
+    }
+
+    private void handleApp(HuaweiPacket response) throws Request.ResponseParseException {
+        if (response.serviceId == App.id) {
+            if (response.commandId == 0x2) {
+                try {
+                    byte status = response.getTlv().getByte(0x1);
+                    if (status == (byte) 0x66 || status == (byte) 0x69) {
+                        this.support.getHuaweiAppManager().requestAppList();
+                    }
+                } catch (HuaweiPacket.MissingTagException e) {
+                    LOG.error("Could not send watchface confirm request", e);
+                }
+
+            }
+        }
+    }
+
+    private void handleP2p(HuaweiPacket response) throws Request.ResponseParseException {
+        if (response.serviceId == P2P.id && response.commandId == P2P.P2PCommand.id) {
+            if (!(response instanceof P2P.P2PCommand.Response))
+                throw new Request.ResponseTypeMismatchException(response, P2P.P2PCommand.class);
+            try {
+                this.support.getHuaweiP2PManager().handlePacket((P2P.P2PCommand.Response) response);
+            } catch (Exception e) {
+                LOG.error("Error in P2P service", e);
+            }
+        }
+    }
+
+    private void handleWeatherCheck(HuaweiPacket response) {
+        if (response.serviceId == Weather.id && response.commandId == 0x04) {
+            support.huaweiWeatherManager.handleAsyncMessage(response);
+        }
+    }
+
+    private void handleGpsRequest(HuaweiPacket response) {
+        if (response.serviceId == GpsAndTime.id && response.commandId == GpsAndTime.GpsStatus.id) {
+            if (!(response instanceof GpsAndTime.GpsStatus.Response)) {
+                // TODO: exception?
+                return;
+            }
+
+            // Send back OK
+            try {
+                SendGpsStatusRequest sendGpsStatusRequest = new SendGpsStatusRequest(this.support);
+                sendGpsStatusRequest.doPerform();
+            } catch (IOException e) {
+                LOG.error("Could not send back OK");
+            }
+
+            support.setGps(((GpsAndTime.GpsStatus.Response) response).enableGps);
+        }
+    }
+
+    private void handleEphemeris(HuaweiPacket response) {
+        if (response.serviceId == Ephemeris.id && response.commandId == Ephemeris.OperatorData.id) {
+            if (!(response instanceof Ephemeris.OperatorData.OperatorIncomingRequest)) {
+                return;
+            }
+            byte operationInfo = ((Ephemeris.OperatorData.OperatorIncomingRequest) response).operationInfo;
+            int operationTime = ((Ephemeris.OperatorData.OperatorIncomingRequest) response).operationTime;
+            LOG.info("Ephemeris: operation: {} time: {}", operationInfo, operationTime);
+            support.getHuaweiEphemerisManager().handleOperatorRequest(operationInfo, operationTime);
+        }
+    }
+
+    private void handleEphemerisUploadService(HuaweiPacket response) {
+        if (response.serviceId == EphemerisFileUpload.id) {
+            if (response.commandId == EphemerisFileUpload.FileList.id) {
+                if (!(response instanceof EphemerisFileUpload.FileList.FileListIncomingRequest)) {
+                    return;
+                }
+                support.getHuaweiEphemerisManager().handleFileSendRequest(((EphemerisFileUpload.FileList.FileListIncomingRequest) response).fileType, ((EphemerisFileUpload.FileList.FileListIncomingRequest) response).productId);
+            } else if (response.commandId == EphemerisFileUpload.FileConsult.id) {
+                if (!(response instanceof EphemerisFileUpload.FileConsult.FileConsultIncomingRequest)) {
+                    return;
+                }
+                EphemerisFileUpload.FileConsult.FileConsultIncomingRequest res = (EphemerisFileUpload.FileConsult.FileConsultIncomingRequest) response;
+                support.getHuaweiEphemerisManager().handleFileConsultIncomingRequest(res.responseCode, res.protocolVersion, res.bitmapEnable, res.transferSize, res.maxDataSize, res.timeOut, res.fileType);
+            } else if (response.commandId == EphemerisFileUpload.QuerySingleFileInfo.id) {
+                if (!(response instanceof EphemerisFileUpload.QuerySingleFileInfo.QuerySingleFileInfoIncomingRequest)) {
+                    return;
+                }
+                support.getHuaweiEphemerisManager().handleSingleFileIncomingRequest(((EphemerisFileUpload.QuerySingleFileInfo.QuerySingleFileInfoIncomingRequest) response).fileName);
+            } else if (response.commandId == EphemerisFileUpload.DataRequest.id) {
+                if (!(response instanceof EphemerisFileUpload.DataRequest.DataRequestIncomingRequest)) {
+                    return;
+                }
+                EphemerisFileUpload.DataRequest.DataRequestIncomingRequest res = (EphemerisFileUpload.DataRequest.DataRequestIncomingRequest) response;
+                support.getHuaweiEphemerisManager().handleDataRequestIncomingRequest(res.responseCode, res.fileName, res.offset, res.len, res.bitmap);
+            } else if (response.commandId == EphemerisFileUpload.UploadData.id) {
+                if (!(response instanceof EphemerisFileUpload.UploadData.UploadDataResponse)) {
+                    return;
+                }
+                support.getHuaweiEphemerisManager().handleFileUploadResponse(((EphemerisFileUpload.UploadData.UploadDataResponse) response).responseCode);
+            } else if (response.commandId == EphemerisFileUpload.UploadDone.id) {
+                if (!(response instanceof EphemerisFileUpload.UploadDone.UploadDoneIncomingRequest)) {
+                    return;
+                }
+                support.getHuaweiEphemerisManager().handleFileDoneRequest(((EphemerisFileUpload.UploadDone.UploadDoneIncomingRequest) response).uploadResult);
+            }
+
+        }
+    }
+
+    private void handleCameraRemote(HuaweiPacket response) {
+        if (response.serviceId == CameraRemote.id && response.commandId == CameraRemote.CameraRemoteStatus.id) {
+            if (!(response instanceof CameraRemote.CameraRemoteStatus.Response)) {
+                // TODO: exception?
+                return;
+            }
+
+            if (!CameraActivity.supportsCamera()) {
+                LOG.error("No camera present");
+                // TODO: Toast?
+                return;
+            }
+
+            switch (((CameraRemote.CameraRemoteStatus.Response) response).event) {
+                case OPEN_CAMERA:
+                    GBDeviceEventCameraRemote openCameraEvent = new GBDeviceEventCameraRemote();
+                    openCameraEvent.event = GBDeviceEventCameraRemote.Event.OPEN_CAMERA;
+                    support.evaluateGBDeviceEvent(openCameraEvent);
+                    break;
+                case TAKE_PICTURE:
+                    GBDeviceEventCameraRemote takePictureEvent = new GBDeviceEventCameraRemote();
+                    takePictureEvent.event = GBDeviceEventCameraRemote.Event.TAKE_PICTURE;
+                    support.evaluateGBDeviceEvent(takePictureEvent);
+                    break;
+                case CLOSE_CAMERA:
+                    GBDeviceEventCameraRemote closeCameraEvent = new GBDeviceEventCameraRemote();
+                    closeCameraEvent.event = GBDeviceEventCameraRemote.Event.CLOSE_CAMERA;
+                    support.evaluateGBDeviceEvent(closeCameraEvent);
+                    break;
+            }
+        }
+    }
+
+    private void handleAsyncBattery(HuaweiPacket response) {
+        if (response.serviceId == DeviceConfig.id && response.commandId == DeviceConfig.BatteryLevel.id_change) {
+            if (!(response instanceof DeviceConfig.BatteryLevel.Response)) {
+                // TODO: exception?
+                return;
+            }
+
+            DeviceConfig.BatteryLevel.Response resp = (DeviceConfig.BatteryLevel.Response) response;
+
+            if (resp.multi_level == null) {
+                byte batteryLevel = resp.level;
+                this.support.getDevice().setBatteryLevel(batteryLevel);
+
+                GBDeviceEventBatteryInfo batteryInfo = new GBDeviceEventBatteryInfo();
+                batteryInfo.state = BatteryState.BATTERY_NORMAL;
+                batteryInfo.level = (int) batteryLevel & 0xff;
+                this.support.evaluateGBDeviceEvent(batteryInfo);
+            } else {
+                // Handle multiple batteries
+                for (int i = 0; i < resp.multi_level.length; i++) {
+                    int level = (int) resp.multi_level[i] & 0xff;
+                    this.support.getDevice().setBatteryLevel(level, i);
+
+                    GBDeviceEventBatteryInfo batteryInfo = new GBDeviceEventBatteryInfo();
+                    batteryInfo.batteryIndex = i;
+                    batteryInfo.state = resp.status != null && resp.status.length > i ?
+                            GetBatteryLevelRequest.byteToBatteryState(resp.status[i]) :
+                            BatteryState.BATTERY_NORMAL;
+                    batteryInfo.level = level;
+                    this.support.evaluateGBDeviceEvent(batteryInfo);
+                }
+            }
+
+            if (GBApplication.getDevicePrefs(this.support.getDevice()).getBatteryPollingEnabled()) {
+                if (!this.support.startBatteryRunnerDelayed()) {
+                    GB.toast(this.support.getContext(), R.string.battery_polling_failed_start, Toast.LENGTH_SHORT, GB.ERROR);
+                    LOG.error("Failed to start the battery polling");
+                }
+            }
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiAppManager.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiAppManager.java
new file mode 100644
index 0000000..5d14397
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiAppManager.java
@@ -0,0 +1,118 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei;
+
+import org.json.JSONObject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.App;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetAppNames;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendAppDelete;
+
+public class HuaweiAppManager {
+
+    static Logger LOG = LoggerFactory.getLogger(HuaweiAppManager.class);
+
+    public static class AppConfig {
+
+        public String bundleName;
+        public String vendor;
+        public String version;
+
+
+        public AppConfig(String jsonStr) {
+
+            try {
+                JSONObject config = new JSONObject(jsonStr);
+                this.bundleName = config.getJSONObject("app").getString("bundleName");
+                this.vendor = config.getJSONObject("app").getString("vendor");
+                this.version = config.getJSONObject("app").getJSONObject("version").getString("name");
+
+            } catch (Exception e) {
+                LOG.error("Error decode app config", e);
+            }
+        }
+
+    }
+
+    private final HuaweiSupportProvider support;
+
+    private List<App.InstalledAppInfo> installedAppList = null;
+
+    public HuaweiAppManager(HuaweiSupportProvider support) {
+        this.support = support;
+    }
+
+    public void setInstalledAppList(List<App.InstalledAppInfo> installedAppList) {
+        this.installedAppList = installedAppList;
+        handleAppList();
+    }
+
+    public void handleAppList() {
+        if (this.installedAppList == null) {
+            return;
+        }
+
+        final List<GBDeviceApp> gbDeviceApps = new ArrayList<>();
+
+        for (final App.InstalledAppInfo appInfo : installedAppList) {
+            final UUID uuid = UUID.nameUUIDFromBytes(appInfo.packageName.getBytes());
+            GBDeviceApp gbDeviceApp = new GBDeviceApp(
+                    uuid,
+                    appInfo.appName,
+                    "",
+                    appInfo.version,
+                    GBDeviceApp.Type.APP_GENERIC
+            );
+            gbDeviceApps.add(gbDeviceApp);
+        }
+        support.setGbWatchApps(gbDeviceApps);
+    }
+
+    public void requestAppList() {
+        if (!this.support.getHuaweiCoordinator().supportsAppParams())
+            return;
+        try {
+            GetAppNames getAppNames = new GetAppNames(support);
+            getAppNames.doPerform();
+        } catch (IOException e) {
+            LOG.error("Error request applications list", e);
+        }
+    }
+
+    public boolean startApp(UUID uuid) {
+        if (this.installedAppList == null)
+            return false;
+
+        for (final App.InstalledAppInfo appInfo : installedAppList) {
+            final UUID appUuid = UUID.nameUUIDFromBytes(appInfo.packageName.getBytes());
+            if (appUuid.equals(uuid))
+                return true;
+        }
+        return false;
+    }
+
+    public boolean deleteApp(UUID uuid) {
+        if (this.installedAppList == null)
+            return false;
+        
+        for (final App.InstalledAppInfo appInfo : installedAppList) {
+            final UUID appUuid = UUID.nameUUIDFromBytes(appInfo.packageName.getBytes());
+            if (appUuid.equals(uuid)) {
+                try {
+                    SendAppDelete sendAppDelete = new SendAppDelete(support, appInfo.packageName);
+                    sendAppDelete.doPerform();
+                } catch (IOException e) {
+                    LOG.error("Could not delete app: " + appInfo.packageName, e);
+                }
+                return true;
+            }
+        }
+        return false;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiBRSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiBRSupport.java
new file mode 100644
index 0000000..685eb61
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiBRSupport.java
@@ -0,0 +1,215 @@
+/*  Copyright (C) 2024 Damien Gaignon, Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei;
+
+import android.bluetooth.BluetoothAdapter;
+import android.content.Context;
+import android.location.Location;
+import android.net.Uri;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventCameraRemote;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiConstants;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceMusic;
+import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.Contact;
+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.btbr.AbstractBTBRDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.btbr.TransactionBuilder;
+
+public class HuaweiBRSupport extends AbstractBTBRDeviceSupport {
+    private static final Logger LOG = LoggerFactory.getLogger(HuaweiBRSupport.class);
+
+    private final HuaweiSupportProvider supportProvider;
+
+    public HuaweiBRSupport() {
+        super(LOG);
+        addSupportedService(HuaweiConstants.UUID_SERVICE_HUAWEI_SDP);
+        setBufferSize(1032);
+        supportProvider = new HuaweiSupportProvider(this);
+    }
+
+    protected HuaweiSupportProvider getSupportProvider() {
+        return supportProvider;
+    }
+
+    @Override
+    public void setContext(GBDevice gbDevice, BluetoothAdapter btAdapter, Context context) {
+        super.setContext(gbDevice, btAdapter, context);
+        supportProvider.setContext(context);
+    }
+
+    @Override
+    protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
+        return supportProvider.initializeDevice(builder);
+    }
+
+    @Override
+    public boolean connectFirstTime() {
+        supportProvider.setFirstConnection(true);
+        return connect();
+    }
+
+    @Override
+    public void onSocketRead(byte[] data) {
+        supportProvider.onSocketRead(data);
+    }
+
+    @Override
+    public boolean useAutoConnect() {
+        return true;
+    }
+
+    @Override
+    public void onSendConfiguration(String config) {
+        supportProvider.onSendConfiguration(config);
+    }
+
+    @Override
+    public void onFetchRecordedData(int dataTypes) {
+        supportProvider.onFetchRecordedData(dataTypes);
+    }
+
+    @Override
+    public void onReset(int flags) {
+        supportProvider.onReset(flags);
+    }
+
+    @Override
+    public void onNotification(NotificationSpec notificationSpec) {
+        supportProvider.onNotification(notificationSpec);
+    }
+
+    @Override
+    public void onSetTime() {
+        supportProvider.onSetTime();
+    }
+
+    @Override
+    public void onSetAlarms(ArrayList<? extends nodomain.freeyourgadget.gadgetbridge.model.Alarm> alarms) {
+        supportProvider.onSetAlarms(alarms);
+    }
+
+    @Override
+    public void onSetCallState(CallSpec callSpec) {
+        supportProvider.onSetCallState(callSpec);
+    }
+
+    @Override
+    public void onSetMusicState(MusicStateSpec stateSpec) {
+        supportProvider.onSetMusicState(stateSpec);
+    }
+
+    @Override
+    public void onSetMusicInfo(MusicSpec musicSpec) {
+        supportProvider.onSetMusicInfo(musicSpec);
+    }
+
+    @Override
+    public void onSetPhoneVolume(float volume) {
+        supportProvider.onSetPhoneVolume();
+    }
+
+    @Override
+    public void onFindPhone(boolean start) {
+        if (!start)
+            supportProvider.onStopFindPhone();
+    }
+
+    @Override
+    public void onSendWeather(ArrayList<WeatherSpec> weatherSpecs) {
+        supportProvider.onSendWeather(weatherSpecs);
+    }
+
+    @Override
+    public void onSetGpsLocation(Location location) {
+        supportProvider.onSetGpsLocation(location);
+    }
+
+    @Override
+    public void onInstallApp(Uri uri) {
+        supportProvider.onInstallApp(uri);
+    }
+
+    @Override
+    public void onAppInfoReq() {
+        supportProvider.onAppInfoReq();
+    }
+
+    @Override
+    public void onAppStart(final UUID uuid, boolean start) {
+        if (start) {
+            supportProvider.onAppStart(uuid, start);
+        }
+    }
+
+    @Override
+    public void onAppDelete(final UUID uuid) {
+        supportProvider.onAppDelete(uuid);
+    }
+
+    @Override
+    public void onCameraStatusChange(GBDeviceEventCameraRemote.Event event, String filename) {
+        supportProvider.onCameraStatusChange(event, filename);
+    }
+
+    @Override
+    public void onSetContacts(ArrayList<? extends Contact> contacts) {
+        supportProvider.onSetContacts(contacts);
+    }
+
+    @Override
+    public void onAddCalendarEvent(final CalendarEventSpec calendarEventSpec) {
+        supportProvider.onAddCalendarEvent(calendarEventSpec);
+    }
+
+    @Override
+    public void onDeleteCalendarEvent(final byte type, long id) {
+        supportProvider.onDeleteCalendarEvent(type, id);
+    }
+
+
+    @Override
+    public void dispose() {
+        supportProvider.dispose();
+        super.dispose();
+    }
+
+    public void onTestNewFunction() {
+        supportProvider.onTestNewFunction();
+    }
+
+    @Override
+    public void onMusicListReq() {
+        supportProvider.onMusicListReq();
+    }
+
+    @Override
+    public void onMusicOperation(int operation, int playlistIndex, String playlistName, ArrayList<Integer> musicIds) {
+        supportProvider.onMusicOperation(operation, playlistIndex, playlistName, musicIds);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiEphemerisManager.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiEphemerisManager.java
new file mode 100644
index 0000000..f977121
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiEphemerisManager.java
@@ -0,0 +1,563 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei;
+
+import android.text.TextUtils;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.List;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipException;
+import java.util.zip.ZipFile;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendEphemerisDataRequestResponse;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendEphemerisFileConsultResponse;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendEphemerisFileListResponse;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendEphemerisFileStatusRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendEphemerisFileUploadChunk;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendEphemerisFileUploadDoneResponse;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendEphemerisOperatorResponse;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendEphemerisParameterConsultRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendEphemerisSingleFileInfoResponse;
+import nodomain.freeyourgadget.gadgetbridge.util.CheckSums;
+import nodomain.freeyourgadget.gadgetbridge.util.CryptoUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
+
+public class HuaweiEphemerisManager {
+    private static final Logger LOG = LoggerFactory.getLogger(HuaweiEphemerisManager.class);
+
+    public static class UploadParameters {
+        //upload file related data
+        private final String protocolVersion;
+        private final byte bitmapEnable;
+        private final short transferSize;
+        private final int maxDataSize;
+        private final short timeOut;
+        private final byte fileType;
+
+        public UploadParameters(String protocolVersion, byte bitmapEnable, short transferSize, int maxDataSize, short timeOut, byte fileType) {
+            this.protocolVersion = protocolVersion;
+            this.bitmapEnable = bitmapEnable;
+            this.transferSize = transferSize;
+            this.maxDataSize = maxDataSize;
+            this.timeOut = timeOut;
+            this.fileType = fileType;
+        }
+
+        public String getProtocolVersion() {
+            return protocolVersion;
+        }
+
+        public byte getBitmapEnable() {
+            return bitmapEnable;
+        }
+
+        public short getTransferSize() {
+            return transferSize;
+        }
+
+        public int getMaxDataSize() {
+            return maxDataSize;
+        }
+
+        public short getTimeOut() {
+            return timeOut;
+        }
+
+        public byte getFileType() {
+            return fileType;
+        }
+    }
+
+    public static class RequestInfo {
+        private int tagVersion = -1;
+        private String tagUUID = null;
+        private List<String> tagFiles = null;
+
+        private byte[] currentFileData = null;
+        private String currentFileName = null;
+
+        UploadParameters uploadParameters = null;
+
+        private List<String> processedFiles = new ArrayList<>();
+
+        public RequestInfo(int tagVersion, String tagUUID, List<String> tagFiles) {
+            this.tagVersion = tagVersion;
+            this.tagUUID = tagUUID;
+            this.tagFiles = tagFiles;
+        }
+
+        public int getTagVersion() {
+            return tagVersion;
+        }
+
+        public String getTagUUID() {
+            return tagUUID;
+        }
+
+        public List<String> getTagFiles() {
+            return tagFiles;
+        }
+
+        public byte[] getCurrentFileData() {
+            return currentFileData;
+        }
+
+        public String getCurrentFileName() {
+            return currentFileName;
+        }
+
+        public UploadParameters getUploadParameters() {
+            return uploadParameters;
+        }
+
+        public void setCurrentFileData(byte[] currentFileData) {
+            this.currentFileData = currentFileData;
+        }
+
+        public void setCurrentFileName(String currentFileName) {
+            this.currentFileName = currentFileName;
+        }
+
+        public void setUploadParameters(UploadParameters uploadParameters) {
+            this.uploadParameters = uploadParameters;
+        }
+
+        public void addProcessedFile(String name) {
+            processedFiles.add(name);
+        }
+
+        public boolean isAllProcessed() {
+            LOG.info("Ephemeris tagFiles: {}", tagFiles.toString());
+            LOG.info("Ephemeris processed: {}", processedFiles.toString());
+            return processedFiles.size() == tagFiles.size() && new HashSet<>(processedFiles).containsAll(tagFiles);
+        }
+    }
+
+    private final HuaweiSupportProvider support;
+
+
+    private JsonObject availableDataConfig = null;
+
+
+    private RequestInfo currentRequest = null;
+
+
+    public HuaweiEphemerisManager(HuaweiSupportProvider support) {
+        this.support = support;
+    }
+
+    private byte[] getZIPFileContent(File file, String name) {
+        byte[] ret = null;
+        try {
+            ZipFile zipFile = new ZipFile(file);
+            Enumeration<? extends ZipEntry> entries = zipFile.entries();
+            while (entries.hasMoreElements()) {
+                ZipEntry entry = entries.nextElement();
+                if (entry.getName().equals(name)) {
+                    InputStream inputStream = zipFile.getInputStream(entry);
+                    ret = new byte[inputStream.available()];
+                    inputStream.read(ret);
+                    inputStream.close();
+                }
+            }
+            zipFile.close();
+        } catch (ZipException e) {
+            LOG.error("zip exception", e);
+        } catch (IOException e) {
+            LOG.error("zip IO exception", e);
+        }
+        return ret;
+    }
+
+    private boolean checkZIPFileExists(File file, String name) {
+        try {
+            ZipFile zipFile = new ZipFile(file);
+            Enumeration<? extends ZipEntry> entries = zipFile.entries();
+            while (entries.hasMoreElements()) {
+                ZipEntry entry = entries.nextElement();
+                if (!entry.isDirectory() && entry.getName().equals(name)) {
+                    return true;
+                }
+            }
+            zipFile.close();
+        } catch (ZipException e) {
+            LOG.error("zip exception", e);
+        } catch (IOException e) {
+            LOG.error("zip IO exception", e);
+        }
+        return false;
+    }
+
+    public void handleOperatorRequest(byte operationInfo, int operationTime) {
+        //TODO:
+        // 100007 - no network connection
+        if (operationInfo == 1) {
+            // NOTE: clear data on new request
+            currentRequest = null;
+
+            int responseCode = 100000;
+            try {
+                File filesDir = support.getContext().getExternalFilesDir(null);
+                File file = new File(filesDir, "ephemeris.zip");
+                if (!file.exists()) {
+                    throw new Exception("Ephemeris file does not exists");
+                }
+                byte[] time = getZIPFileContent(file, "time");
+                byte[] config = getZIPFileContent(file, "ephemeris_config.json");
+
+                if (time == null || config == null) {
+                    throw new Exception("Ephemeris no time or config in file");
+                }
+
+                long fileTime = Long.parseLong(new String(time));
+                if (fileTime < (System.currentTimeMillis() - (7200 * 1000))) { // 2 hours. Maybe should be decreased.
+                    throw new Exception("Ephemeris file old");
+                }
+
+                availableDataConfig = JsonParser.parseString(new String(config)).getAsJsonObject();
+
+                LOG.info("Ephemeris Time: {} ConfigData: {}", fileTime, availableDataConfig.toString());
+
+            } catch (Exception e) {
+                LOG.info("Ephemeris exception file or config processing: {}", e.getMessage());
+                availableDataConfig = null;
+                //responseCode = 100007; //no network connection
+                return; // NOTE: just ignore request if something wrong with data.
+            }
+
+            try {
+                SendEphemerisOperatorResponse sendEphemerisOperatorResponse = new SendEphemerisOperatorResponse(this.support, responseCode);
+                sendEphemerisOperatorResponse.doPerform();
+
+                if (responseCode == 100000) {
+                    SendEphemerisParameterConsultRequest sendEphemerisParameterConsultRequest = new SendEphemerisParameterConsultRequest(this.support);
+                    sendEphemerisParameterConsultRequest.doPerform();
+                }
+
+            } catch (IOException e) {
+                LOG.error("Error send Ephemeris data");
+            }
+        }
+
+        // TODO: operation code == 2 send file download progress
+        //   Currently we do not need this code because everything is downloaded already. Maybe needed in the future
+        //   0x2: 02
+        //   0x3: operationTime
+        //   0x4:  from 01 to 06 status
+        //   in the container 0x81
+    }
+
+    public void handleParameterConsultResponse(int consultDeviceTime, byte downloadVersion, String downloadTag) {
+        LOG.info("consultDeviceTime: {}, downloadVersion: {}, downloadTag: {}", consultDeviceTime, downloadVersion, downloadTag);
+        byte status = 0x3; // ready to download
+
+        try {
+            File filesDir = support.getContext().getExternalFilesDir(null);
+            File file = new File(filesDir, "ephemeris.zip");
+            if (!file.exists()) {
+                throw new Exception("Ephemeris file does not exists");
+            }
+
+            if (availableDataConfig == null) {
+                throw new Exception("Ephemeris no config data");
+            }
+
+            JsonObject conf = availableDataConfig.getAsJsonObject(downloadTag);
+
+            int version = conf.get("ver").getAsInt();
+            if(version != downloadVersion) {
+                throw new Exception("Ephemeris version mismatch");
+            }
+            String uuid = conf.get("uuid").getAsString();
+
+            if(uuid.isEmpty())
+                throw new Exception("Ephemeris uuid is empty");
+
+            JsonArray filesJs = conf.get("files").getAsJsonArray();
+
+            List<String> files = new ArrayList<>();
+            for (int i = 0; i < filesJs.size(); i++) {
+                files.add(filesJs.get(i).getAsString());
+            }
+
+            if(files.isEmpty())
+                throw new Exception("Ephemeris file list is empty");
+
+            for(String fl: files) {
+                if(!checkZIPFileExists(file, uuid + File.separator + fl)) {
+                    throw new Exception("Ephemeris file does not exist in ZIP");
+                }
+            }
+
+            currentRequest = new RequestInfo(downloadVersion, uuid, files);
+
+        } catch (Exception e) {
+            LOG.error("Ephemeris exception file or config processing", e);
+            availableDataConfig = null;
+            status = 0x5; //error or timeout
+        }
+
+        try {
+            SendEphemerisFileStatusRequest sendEphemerisFileStatusRequest = new SendEphemerisFileStatusRequest(this.support, status);
+            sendEphemerisFileStatusRequest.doPerform();
+        } catch (IOException e) {
+            LOG.error("Error to send SendEphemerisFileStatusRequest");
+        }
+    }
+
+    //File transfer related
+    public void handleFileSendRequest(byte fileType, String productId) {
+        if(currentRequest == null) {
+            return;
+        }
+
+        String fileList = "";
+        int responseCode = 0;
+
+        if(fileType == 0) {
+            //TODO: find all files that name contain productId and send
+            LOG.error("Currently not supported. File type: 0");
+        } else if(fileType == 1){
+            if(currentRequest.getTagVersion() == 0) {
+                //TODO: implement this type
+                //fileList = "gpslocation.dat";
+                LOG.error("Currently not supported. File type 1. Tag version 0");
+            } else if (currentRequest.getTagVersion() == 1 || currentRequest.getTagVersion() == 2 || currentRequest.getTagVersion() == 3){
+                int i = 0;
+                while (i < currentRequest.getTagFiles().size()) {
+                    fileList += currentRequest.getTagFiles().get(i);
+                    if (i == currentRequest.getTagFiles().size() - 1) {
+                        break;
+                    }
+                    fileList += ";";
+                    i++;
+                }
+            } else {
+                LOG.error("Unknown version");
+            }
+        } else {
+            LOG.error("Unknown file id");
+        }
+
+        if(TextUtils.isEmpty(fileList)) {
+            responseCode = 100001;
+            cleanupUpload(true);
+        }
+
+        try {
+            SendEphemerisFileListResponse sendEphemerisFileListResponse = new SendEphemerisFileListResponse(this.support, responseCode, fileList);
+            sendEphemerisFileListResponse.doPerform();
+        } catch (IOException e) {
+            LOG.error("Error to send SendEphemerisFileListResponse");
+        }
+    }
+
+    void handleFileConsultIncomingRequest(int responseCode, String protocolVersion, byte bitmapEnable, short transferSize, int maxDataSize, short timeOut, byte fileType) {
+        if(currentRequest == null) {
+            return;
+        }
+
+        if (responseCode != 0) {
+            LOG.error("Error on handleFileConsultIncomingRequest: {}", responseCode);
+            cleanupUpload(true);
+            return;
+        }
+
+        if (transferSize == 0) {
+            LOG.error("transfer size is 0");
+            cleanupUpload(true);
+            return;
+        }
+
+        currentRequest.setUploadParameters(new UploadParameters(protocolVersion, bitmapEnable, transferSize, maxDataSize, timeOut, fileType));
+
+        if((!TextUtils.isEmpty(protocolVersion) && fileType == 0) || fileType == 1) {
+            try {
+                SendEphemerisFileConsultResponse sendEphemerisFileConsultResponse = new SendEphemerisFileConsultResponse(this.support, 100000);
+                sendEphemerisFileConsultResponse.doPerform();
+            } catch (IOException e) {
+                LOG.error("Error to send SendEphemerisFileConsultResponse");
+            }
+        } else {
+            // I don't know how to  properly process error in this case. Just cleanup.
+            cleanupUpload(true);
+        }
+    }
+
+    void handleSingleFileIncomingRequest(String filename) {
+        if(currentRequest == null || currentRequest.getUploadParameters() == null) {
+            cleanupUpload(true);
+            return;
+        }
+
+        if(TextUtils.isEmpty(filename) || !currentRequest.getTagFiles().contains(filename)) {
+            cleanupUpload(true);
+            try {
+                SendEphemerisSingleFileInfoResponse sendEphemerisSingleFileInfoResponse = new SendEphemerisSingleFileInfoResponse(this.support, 100001, 0, (short) 0);
+                sendEphemerisSingleFileInfoResponse.doPerform();
+            } catch (IOException e) {
+                LOG.error("Error to send sendEphemerisSingleFileInfoResponse");
+            }
+            return;
+        }
+
+        currentRequest.addProcessedFile(filename);
+
+        int responseCode = 0;
+        try {
+
+            File filesDir = support.getContext().getExternalFilesDir(null);
+            File file = new File(filesDir, "ephemeris.zip");
+            if (!file.exists()) {
+                throw new Exception("Ephemeris handleSingleFileIncomingRequest file does not exists");
+            }
+
+            byte[] currentFileData = getZIPFileContent(file, currentRequest.getTagUUID() + File.separator + filename);
+            if (currentFileData == null || currentFileData.length == 0) {
+                throw new Exception("Ephemeris handleSingleFileIncomingRequest file is empty");
+            }
+            currentRequest.setCurrentFileData(currentFileData);
+            currentRequest.setCurrentFileName(filename);
+        } catch (Exception e) {
+            LOG.error("Ephemeris exception handleSingleFileIncomingRequest processing", e);
+            cleanupUpload(true);
+            responseCode = 100001;
+        }
+
+        short crc = 0;
+        if(currentRequest.getUploadParameters().getFileType() == 0) {
+            //TODO: implement this type
+            responseCode = 100001;
+        } else if(currentRequest.getUploadParameters().getFileType()  == 1) {
+            crc = (short)CheckSums.getCRC16(currentRequest.getCurrentFileData(), 0x0000);
+        }
+
+        int size = 0;
+        if(currentRequest.getCurrentFileData() == null || crc == 0) {
+            responseCode = 100001;
+        } else {
+            size = currentRequest.getCurrentFileData().length;
+        }
+
+        try {
+            SendEphemerisSingleFileInfoResponse sendEphemerisSingleFileInfoResponse = new SendEphemerisSingleFileInfoResponse(this.support, responseCode, size, crc);
+            sendEphemerisSingleFileInfoResponse.doPerform();
+        } catch (IOException e) {
+            LOG.error("Error to send SendEphemerisSingleFileInfoResponse");
+        }
+    }
+
+    public static String getHexDigest(String str) {
+        if (TextUtils.isEmpty(str)) {
+            return "";
+        }
+        if (str.contains(":")) {
+            str = str.replace(":", "");
+        }
+        try {
+            byte[] data = CryptoUtils.digest(str.getBytes(StandardCharsets.UTF_8));
+            return StringUtils.bytesToHex(data);
+        } catch (NoSuchAlgorithmException e) {
+            LOG.error("EncryptUtil getEncryption :{} ", e.getMessage());
+            return "";
+        }
+    }
+
+    void handleDataRequestIncomingRequest(int responseCode, String fileName, int offset, int len, byte bitmap) {
+        if(currentRequest == null || currentRequest.getUploadParameters() == null) {
+            cleanupUpload(true);
+            return;
+        }
+
+        if(offset == -1 || fileName == null || fileName.isEmpty()) {
+            cleanupUpload(true);
+            return;
+        }
+
+        if(!currentRequest.getCurrentFileName().equals(fileName)) {
+            cleanupUpload(true);
+            return;
+        }
+
+        int localResponseCode = 100000;
+
+        String localFilename = "";
+
+        if(currentRequest.getUploadParameters().getFileType() == 0) {
+            //TODO: implement this type
+            localResponseCode = 100001;
+        } else if(currentRequest.getUploadParameters().getFileType() == 1) {
+            localFilename = getHexDigest(support.deviceMac) + fileName;
+        } else {
+            LOG.error("Unsupported type: {}", currentRequest.getUploadParameters().getFileType());
+            localResponseCode = 100001;
+        }
+
+        try {
+            SendEphemerisDataRequestResponse sendEphemerisDataRequestResponse = new SendEphemerisDataRequestResponse(this.support, localResponseCode, localFilename, offset);
+            sendEphemerisDataRequestResponse.doPerform();
+        } catch (IOException e) {
+            LOG.error("Error to send SendEphemerisDataRequestResponse");
+        }
+
+        if(localResponseCode == 100000) {
+            int dataSize = Math.min(currentRequest.getCurrentFileData().length - offset,currentRequest.getUploadParameters().getMaxDataSize());
+            int packetsCount = (int) Math.ceil((double) dataSize / currentRequest.getUploadParameters().getTransferSize());
+            byte[] chunk = new byte[dataSize];
+            System.arraycopy(currentRequest.getCurrentFileData(), offset, chunk, 0, dataSize);
+
+            try {
+                SendEphemerisFileUploadChunk sendEphemerisFileUploadChunk = new SendEphemerisFileUploadChunk(this.support, chunk, currentRequest.getUploadParameters().getTransferSize(), packetsCount);
+                sendEphemerisFileUploadChunk.doPerform();
+            } catch (IOException e) {
+                LOG.error("Error to send SendEphemerisFileUploadChunk");
+            }
+        }
+    }
+
+    void handleFileUploadResponse(int responseCode) {
+        LOG.info("handleFileUploadResponse {}", responseCode);
+        if(responseCode != 100000) {
+            cleanupUpload(true);
+        }
+    }
+
+    void handleFileDoneRequest(byte uploadResult) {
+        LOG.info("handleFileDoneRequest");
+        cleanupUpload(false);
+        try {
+            SendEphemerisFileUploadDoneResponse sendEphemerisFileUploadDoneResponse = new SendEphemerisFileUploadDoneResponse(this.support, 100000);
+            sendEphemerisFileUploadDoneResponse.doPerform();
+        } catch (IOException e) {
+            LOG.error("Error to send SendEphemerisFileUploadDoneResponse");
+        }
+    }
+
+    void cleanupUpload(boolean force) {
+        if(currentRequest != null) {
+            currentRequest.setCurrentFileName(null);
+            currentRequest.setCurrentFileData(null);
+            currentRequest.setUploadParameters(null);
+            boolean isAllProcessed = currentRequest.isAllProcessed();
+            LOG.info("Ephemeris is Done: {}", isAllProcessed);
+            if(isAllProcessed || force) {
+                currentRequest = null;
+                LOG.info("Ephemeris All files uploaded. {} Cleanup...", force?"Force":"");
+            }
+        }
+    }
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiFileDownloadManager.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiFileDownloadManager.java
new file mode 100644
index 0000000..8c855ec
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiFileDownloadManager.java
@@ -0,0 +1,686 @@
+/*  Copyright (C) 2024 Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.widget.Toast;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Locale;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FileDownloadService0A;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FileDownloadService2C;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetFileBlockRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetFileDownloadCompleteRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetFileDownloadInitRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetFileInfoRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetFileParametersRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.Request;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class HuaweiFileDownloadManager {
+    private static final Logger LOG = LoggerFactory.getLogger(HuaweiFileDownloadManager.class);
+
+    public static class HuaweiFileDownloadException extends Exception {
+        @Nullable
+        public final FileRequest fileRequest;
+
+        HuaweiFileDownloadException(@Nullable FileRequest fileRequest, String message) {
+            super(message);
+            this.fileRequest = fileRequest;
+        }
+
+        HuaweiFileDownloadException(@Nullable FileRequest fileRequest, String message, Exception e) {
+            super(message, e);
+            this.fileRequest = fileRequest;
+        }
+    }
+
+    public static class HuaweiFileDownloadSendException extends HuaweiFileDownloadException {
+        HuaweiFileDownloadSendException(@Nullable FileRequest fileRequest, Request request, Exception e) {
+            super(fileRequest, "Error sending request " + request.getName(), e);
+        }
+    }
+
+    public static class HuaweiFileDownloadRequestException extends HuaweiFileDownloadException {
+        HuaweiFileDownloadRequestException(@Nullable FileRequest fileRequest, Class<?> requestClass, Exception e) {
+            super(fileRequest, "Error in request of class " + requestClass, e);
+        }
+    }
+
+    public static class HuaweiFileDownloadTimeoutException extends HuaweiFileDownloadException {
+        HuaweiFileDownloadTimeoutException(@Nullable FileRequest fileRequest) {
+            super(fileRequest, "Timeout was hit!");
+        }
+    }
+
+    public static class HuaweiFileDownloadFileMismatchException extends HuaweiFileDownloadException {
+        HuaweiFileDownloadFileMismatchException(@NonNull FileRequest fileRequest, String filename) {
+            super(fileRequest, "Data for wrong file received. Expected name " + fileRequest.filename + ", got name " + filename);
+        }
+
+        HuaweiFileDownloadFileMismatchException(@NonNull FileRequest fileRequest, FileType fileType) {
+            super(fileRequest, "Data for wrong file received. Expected type " + fileRequest.fileType + ", got type " + fileType);
+        }
+
+        HuaweiFileDownloadFileMismatchException(@NonNull FileRequest fileRequest, String[] filenames) {
+            super(fileRequest, "File " + fileRequest.filename + " cannot be downloaded using this method. Only the following files are supported: " + Arrays.toString(filenames));
+        }
+
+        HuaweiFileDownloadFileMismatchException(@NonNull FileRequest fileRequest, int number, boolean newSync) {
+            super(
+                    fileRequest,
+                    "Data for wrong file received. Expected " +
+                        (newSync ?
+                            "id " + fileRequest.fileId + ", got id " + number :
+                            "packet number " + (fileRequest.lastPacketNumber + 1) + ", got " + number)
+            );
+        }
+    }
+
+    public enum FileType {
+        DEBUG,
+        SLEEP_STATE,
+        SLEEP_DATA,
+        GPS,
+        UNKNOWN // Never for input!
+    }
+
+    public static class FileDownloadCallback {
+        public void downloadComplete(FileRequest fileRequest) {  }
+
+        public void downloadException(HuaweiFileDownloadException e) {
+            if (e.fileRequest != null)
+                LOG.error("Error downloading file: {}{}", e.fileRequest.getFilename(), e.fileRequest.isNewSync() ? " (newsync)" : "", e);
+            else
+                LOG.error("Error in file download", e);
+        }
+    }
+
+    public static class FileRequest {
+        // Inputs
+
+        private final String filename;
+        private final FileType fileType;
+        private final boolean newSync;
+
+        FileDownloadCallback fileDownloadCallback = null;
+
+        // Sleep type only - for 2C GPS they are set to zero
+        private int startTime = 0;
+        private int endTime = 0;
+
+        // GPS type only
+        private short workoutId;
+        private Long databaseId;
+
+        private FileRequest(String filename, FileType fileType, boolean newSync, int startTime, int endTime, FileDownloadCallback fileDownloadCallback) {
+            this.filename = filename;
+            this.fileType = fileType;
+            this.newSync = newSync;
+            this.fileDownloadCallback = fileDownloadCallback;
+            this.startTime = startTime;
+            this.endTime = endTime;
+        }
+
+        public static FileRequest sleepStateFileRequest(boolean supportsTruSleepNewSync, int startTime, int endTime, FileDownloadCallback fileDownloadCallback) {
+            return new FileRequest("sleep_state.bin", FileType.SLEEP_STATE, supportsTruSleepNewSync, startTime, endTime, fileDownloadCallback);
+        }
+
+        public static FileRequest sleepDataFileRequest(boolean supportsTruSleepNewSync, int startTime, int endTime, FileDownloadCallback fileDownloadCallback) {
+            return new FileRequest("sleep_data.bin", FileType.SLEEP_DATA, supportsTruSleepNewSync, startTime, endTime, fileDownloadCallback);
+        }
+
+        private FileRequest(String filename, FileType fileType, boolean newSync, FileDownloadCallback fileDownloadCallback) {
+            this.filename = filename;
+            this.fileType = fileType;
+            this.newSync = newSync;
+            this.fileDownloadCallback = fileDownloadCallback;
+        }
+
+        public static FileRequest debugFileRequest(String filename, FileDownloadCallback fileDownloadCallback) {
+            return new FileRequest(filename, FileType.DEBUG, false, fileDownloadCallback);
+        }
+
+        private FileRequest(@Nullable String filename, FileType fileType, boolean newSync, short workoutId, Long databaseId, FileDownloadCallback fileDownloadCallback) {
+            this.filename = filename;
+            this.fileType = fileType;
+            this.newSync = newSync;
+            this.fileDownloadCallback = fileDownloadCallback;
+            this.workoutId = workoutId;
+            this.databaseId = databaseId;
+        }
+
+        public static FileRequest workoutGpsFileRequest(boolean newSync, short workoutId, Long databaseId, FileDownloadCallback fileDownloadCallback) {
+            if (newSync)
+                return new FileRequest(String.format(Locale.getDefault(), "%d_gps.bin", workoutId), FileType.GPS, true, workoutId, databaseId, fileDownloadCallback);
+            else
+                return new FileRequest(null, FileType.GPS, false, workoutId, databaseId, fileDownloadCallback);
+        }
+
+        // Retrieved
+
+        private int fileSize;
+        private int maxBlockSize;
+        private int timeout; // TODO: unit?
+        private ByteBuffer buffer;
+
+        private int startOfBlockOffset;
+        private int currentBlockSize;
+
+        // Old sync only
+        private String[] filenames;
+        private byte lastPacketNumber;
+
+        // New sync only
+        private byte fileId;
+        private boolean noEncrypt;
+
+        public byte getFileId() {
+            return fileId;
+        }
+
+        public String getFilename() {
+            return filename;
+        }
+
+        public FileType getFileType() {
+            return fileType;
+        }
+
+        public byte[] getData() {
+            if (buffer == null)
+                return new byte[] {};
+            return buffer.array();
+        }
+
+        public int getCurrentOffset() {
+            return buffer.position();
+        }
+
+        public boolean isNewSync() {
+            return newSync;
+        }
+
+        public int getStartTime() {
+            return startTime;
+        }
+
+        public int getEndTime() {
+            return endTime;
+        }
+
+        public short getWorkoutId() {
+            return workoutId;
+        }
+
+        public Long getDatabaseId() {
+            return databaseId;
+        }
+
+        public int getCurrentBlockSize() {
+            return currentBlockSize;
+        }
+
+        public boolean isNoEncrypt() {
+            return noEncrypt;
+        }
+    }
+
+    /**
+     * Actually receives the file data
+     */
+    private static class FileDataReceiver extends Request {
+
+        public boolean newSync;
+        public byte[] data;
+
+        // Packet number for old sync
+        // fileId for new sync
+        public byte number;
+
+        public FileDataReceiver(HuaweiSupportProvider supportProvider) {
+            super(supportProvider);
+        }
+
+        @Override
+        public boolean handleResponse(HuaweiPacket response) {
+            if (
+                    (response.serviceId == FileDownloadService0A.id &&
+                        response.commandId == FileDownloadService0A.BlockResponse.id) ||
+                    (response.serviceId == FileDownloadService2C.id &&
+                        response.commandId == FileDownloadService2C.BlockResponse.id)
+            ) {
+                receivedPacket = response;
+                return true;
+            }
+            return false;
+        }
+
+        @Override
+        public boolean autoRemoveFromResponseHandler() {
+            // This needs to be removed manually
+            return false;
+        }
+
+        @Override
+        protected void processResponse() throws ResponseParseException {
+            if (this.receivedPacket instanceof FileDownloadService0A.BlockResponse) {
+                FileDownloadService0A.BlockResponse response = (FileDownloadService0A.BlockResponse) this.receivedPacket;
+                this.newSync = false;
+                this.number = response.number;
+                this.data = response.data;
+
+                if (response.e instanceof HuaweiPacket.CryptoException) {
+                    LOG.warn("Data could be decoded as TLV, but not decrypted.", response.e);
+                }
+            } else if (this.receivedPacket instanceof FileDownloadService2C.BlockResponse) {
+                this.newSync = true;
+                this.number = ((FileDownloadService2C.BlockResponse) this.receivedPacket).fileId;
+                this.data = ((FileDownloadService2C.BlockResponse) this.receivedPacket).data;
+            } else {
+                throw new ResponseTypeMismatchException(
+                        this.receivedPacket,
+                        FileDownloadService0A.BlockResponse.class,
+                        FileDownloadService2C.BlockResponse.class
+                );
+            }
+        }
+    }
+
+    private final HuaweiSupportProvider supportProvider;
+    private final Handler handler;
+    private final Runnable timeout;
+
+    // Cannot be final as we need the device to be connected before creating this
+    private FileDataReceiver fileDataReceiver;
+
+    // For old sync we cannot download multiple files at the same time, for new sync we don't want
+    // to, so we limit that. We also do not allow old and new sync at the same time.
+    // Note that old and new sync are already split by the serviceID and commandID that are used for
+    // all the requests.
+    // Note that the timeout is not ready for concurrent downloads
+    private boolean isBusy;
+
+    private final ArrayList<FileRequest> fileRequests;
+    private FileRequest currentFileRequest;
+
+    // If the GB interface needs to be updated at the end of the queue of downloads
+    private boolean needSync = false;
+
+    public HuaweiFileDownloadManager(HuaweiSupportProvider supportProvider) {
+        this.supportProvider = supportProvider;
+        handler = new Handler(Looper.getMainLooper());
+        isBusy = false;
+        fileRequests = new ArrayList<>();
+        timeout = () -> {
+            this.currentFileRequest.fileDownloadCallback.downloadException(new HuaweiFileDownloadTimeoutException(currentFileRequest));
+            reset();
+        };
+    }
+
+    public void addToQueue(FileRequest fileRequest, boolean needSync) {
+        synchronized (supportProvider) {
+            fileRequests.add(fileRequest);
+            if (needSync)
+                this.needSync = true;
+        }
+        startDownload();
+    }
+
+    private boolean arrayContains(String[] haystack, String needle) {
+        return Arrays.stream(haystack).anyMatch(s -> s.equals(needle));
+    }
+
+    private void initFileDataReceiver() {
+        if (fileDataReceiver == null) {
+            // We can only init fileDataReceiver if the device is already connected
+            fileDataReceiver = new FileDataReceiver(supportProvider);
+            fileDataReceiver.setFinalizeReq(new Request.RequestCallback() {
+                @Override
+                public void call() {
+                    // Reset timeout
+                    handler.removeCallbacks(HuaweiFileDownloadManager.this.timeout);
+                    handler.postDelayed(HuaweiFileDownloadManager.this.timeout, currentFileRequest.timeout * 1000L);
+
+                    // Handle data
+                    handleFileData(fileDataReceiver.newSync, fileDataReceiver.number, fileDataReceiver.data);
+                }
+
+                @Override
+                public void handleException(Request.ResponseParseException e) {
+                    currentFileRequest.fileDownloadCallback.downloadException(new HuaweiFileDownloadRequestException(null, this.getClass(), e));
+                }
+            });
+        }
+    }
+
+    public void startDownload() {
+        initFileDataReceiver(); // Make sure the fileDataReceiver is ready
+
+        synchronized (this.supportProvider) {
+            if (this.isBusy) {
+                LOG.debug("A new download is started while a previous is in progress.");
+                return; // Already downloading, this file will come eventually
+            }
+            if (this.fileRequests.isEmpty()) {
+                // No more files to download
+                supportProvider.downloadQueueEmpty(this.needSync);
+                // Don't need sync after this anymore
+                this.needSync = false;
+                return;
+            }
+            this.isBusy = true;
+        }
+
+        this.currentFileRequest = this.fileRequests.remove(0);
+
+        GetFileDownloadInitRequest getFileDownloadInitRequest = new GetFileDownloadInitRequest(supportProvider, currentFileRequest);
+        getFileDownloadInitRequest.setFinalizeReq(new Request.RequestCallback() {
+            @Override
+            public void call(Request request) {
+                // For multi-download, match to file instead of assuming current
+                GetFileDownloadInitRequest r = (GetFileDownloadInitRequest) request;
+                if (r.newSync) {
+                    if (!currentFileRequest.filename.equals(r.filename)) {
+                        currentFileRequest.fileDownloadCallback.downloadException(new HuaweiFileDownloadFileMismatchException(
+                                currentFileRequest,
+                                r.filename
+                        ));
+                        reset();
+                        return;
+                    }
+                    if (currentFileRequest.fileType != r.fileType) {
+                        currentFileRequest.fileDownloadCallback.downloadException(new HuaweiFileDownloadFileMismatchException(
+                                currentFileRequest,
+                                r.fileType
+                        ));
+                        reset();
+                        return;
+                    }
+                    currentFileRequest.fileId = r.fileId;
+                    currentFileRequest.fileSize = r.fileSize;
+                    if (r.fileSize == 0) {
+                        // Nothing to download, go to end
+                        fileComplete();
+                        return;
+                    }
+                    getFileInfo();
+                } else {
+                    if (currentFileRequest.fileType == FileType.GPS) {
+                        if (r.filenames.length == 0) {
+                            reset();
+                            return;
+                        }
+                        for (String filename : r.filenames) {
+                            // We only download the gps file itself right now
+                            if (!filename.contains("_gps.bin"))
+                                continue;
+
+                            // Add download request with the filename at the start of the queue
+                            FileRequest fileRequest = new FileRequest(
+                                    filename,
+                                    FileType.GPS,
+                                    currentFileRequest.newSync,
+                                    currentFileRequest.workoutId,
+                                    currentFileRequest.databaseId,
+                                    currentFileRequest.fileDownloadCallback
+                            );
+                            synchronized (supportProvider) {
+                                fileRequests.add(0, fileRequest);
+                            }
+                        }
+                        currentFileRequest = fileRequests.remove(0); // Replace with the file to download
+                    } else {
+                        if (!arrayContains(r.filenames, currentFileRequest.filename)) {
+                            currentFileRequest.fileDownloadCallback.downloadException(new HuaweiFileDownloadFileMismatchException(
+                                    currentFileRequest,
+                                    r.filenames
+                            ));
+                            reset();
+                            return;
+                        }
+                    }
+                    currentFileRequest.filenames = r.filenames;
+                    getDownloadParameters();
+                }
+            }
+
+            @Override
+            public void handleException(Request.ResponseParseException e) {
+                currentFileRequest.fileDownloadCallback.downloadException(new HuaweiFileDownloadRequestException(currentFileRequest, this.getClass(), e));
+            }
+        });
+        try {
+            getFileDownloadInitRequest.doPerform();
+        } catch (IOException e) {
+            currentFileRequest.fileDownloadCallback.downloadException(new HuaweiFileDownloadSendException(currentFileRequest, getFileDownloadInitRequest, e));
+            reset();
+        }
+    }
+
+    private void getDownloadParameters() {
+        // Old sync only, can never be multiple at the same time
+        // Assuming currentRequest is the correct one the entire time
+        // Which may no longer be the case when we implement multi-download for new sync
+        GetFileParametersRequest getFileParametersRequest = new GetFileParametersRequest(supportProvider,
+                currentFileRequest.fileType == FileType.SLEEP_STATE ||
+                        currentFileRequest.fileType == FileType.SLEEP_DATA
+        );
+        getFileParametersRequest.setFinalizeReq(new Request.RequestCallback() {
+            @Override
+            public void call() {
+                currentFileRequest.maxBlockSize = getFileParametersRequest.getMaxBlockSize();
+                currentFileRequest.timeout = getFileParametersRequest.getTimeout();
+                getFileInfo();
+            }
+
+            @Override
+            public void handleException(Request.ResponseParseException e) {
+                currentFileRequest.fileDownloadCallback.downloadException(new HuaweiFileDownloadRequestException(null, this.getClass(), e));
+                reset();
+            }
+        });
+        try {
+            getFileParametersRequest.doPerform();
+        } catch (IOException e) {
+            currentFileRequest.fileDownloadCallback.downloadException(new HuaweiFileDownloadSendException(currentFileRequest, getFileParametersRequest, e));
+            reset();
+        }
+    }
+
+    private void getFileInfo() {
+        GetFileInfoRequest getFileInfoRequest = new GetFileInfoRequest(supportProvider, currentFileRequest);
+        getFileInfoRequest.setFinalizeReq(new Request.RequestCallback() {
+            @Override
+            public void call(Request request) {
+                GetFileInfoRequest r = (GetFileInfoRequest) request;
+                if (r.newSync) {
+                    if (currentFileRequest.fileId != r.fileId) {
+                        currentFileRequest.fileDownloadCallback.downloadException(new HuaweiFileDownloadFileMismatchException(currentFileRequest, r.fileId, true));
+                        reset();
+                        return;
+                    }
+                    currentFileRequest.timeout = r.timeout;
+                    currentFileRequest.maxBlockSize = r.maxBlockSize;
+                    currentFileRequest.noEncrypt = r.noEncrypt;
+                } else {
+                    // currentFileRequest MUST BE correct here
+                    currentFileRequest.fileSize = r.fileLength;
+
+                    if (currentFileRequest.fileSize == 0) {
+                        // Nothing to download, go to complete
+                        fileComplete();
+                        return;
+                    }
+                }
+                downloadNextFileBlock();
+            }
+
+            @Override
+            public void handleException(Request.ResponseParseException e) {
+                currentFileRequest.fileDownloadCallback.downloadException(new HuaweiFileDownloadRequestException(null, this.getClass(), e));
+                reset();
+            }
+        });
+        try {
+            getFileInfoRequest.doPerform();
+        } catch (IOException e) {
+            currentFileRequest.fileDownloadCallback.downloadException(new HuaweiFileDownloadSendException(currentFileRequest, getFileInfoRequest, e));
+            reset();
+        }
+    }
+
+    private void downloadNextFileBlock() {
+        if (currentFileRequest.buffer == null) // New file
+            currentFileRequest.buffer = ByteBuffer.allocate(currentFileRequest.fileSize);
+        currentFileRequest.lastPacketNumber = -1; // Counts per block
+        currentFileRequest.startOfBlockOffset = currentFileRequest.buffer.position();
+        currentFileRequest.currentBlockSize = Math.min(
+                currentFileRequest.fileSize - currentFileRequest.buffer.position(), // Remaining file size
+                currentFileRequest.maxBlockSize // Max we can ask for
+        );
+
+        // Start listening for file data
+        this.supportProvider.addInProgressRequest(fileDataReceiver);
+
+        handler.removeCallbacks(this.timeout);
+        handler.postDelayed(HuaweiFileDownloadManager.this.timeout, currentFileRequest.timeout * 1000L);
+
+        GetFileBlockRequest getFileBlockRequest = new GetFileBlockRequest(supportProvider, currentFileRequest);
+        getFileBlockRequest.setFinalizeReq(new Request.RequestCallback() {
+            @Override
+            public void call() {
+                // Reset timeout
+                handler.removeCallbacks(HuaweiFileDownloadManager.this.timeout);
+                handler.postDelayed(HuaweiFileDownloadManager.this.timeout, currentFileRequest.timeout * 1000L);
+            }
+        });
+        try {
+            getFileBlockRequest.doPerform();
+        } catch (IOException e) {
+            currentFileRequest.fileDownloadCallback.downloadException(new HuaweiFileDownloadSendException(currentFileRequest, getFileBlockRequest, e));
+            reset();
+        }
+    }
+
+    private void handleFileData(boolean newSync, byte number, byte[] data) {
+        if (newSync && currentFileRequest.fileId != number) {
+            currentFileRequest.fileDownloadCallback.downloadException(new HuaweiFileDownloadFileMismatchException(currentFileRequest, number, true));
+            reset();
+            return;
+        }
+        if (!newSync) {
+            if (currentFileRequest.lastPacketNumber != number - 1) {
+                currentFileRequest.fileDownloadCallback.downloadException(new HuaweiFileDownloadFileMismatchException(currentFileRequest, number, false));
+                reset();
+                return;
+            }
+            currentFileRequest.lastPacketNumber = number;
+        }
+
+        currentFileRequest.buffer.put(data);
+
+        if (currentFileRequest.buffer.position() >= currentFileRequest.fileSize) {
+            // File complete
+            LOG.info("Download complete for file {}", currentFileRequest.filename);
+            if (currentFileRequest.buffer.position() != currentFileRequest.fileSize) {
+                GB.toast("Downloaded file is larger than expected", Toast.LENGTH_SHORT, GB.ERROR);
+                LOG.error("Downloaded file is larger than expected: {}", currentFileRequest.filename);
+            }
+            fileComplete();
+        } else if (currentFileRequest.buffer.position() - currentFileRequest.startOfBlockOffset >= currentFileRequest.maxBlockSize) {
+            // Block complete, need to request a new file block
+            downloadNextFileBlock();
+        } // Else we're expecting more data to arrive automatically
+    }
+
+    private void fileComplete() {
+        // Stop timeout from hitting
+        this.handler.removeCallbacks(this.timeout);
+
+        // File complete request
+        GetFileDownloadCompleteRequest getFileDownloadCompleteRequest = new GetFileDownloadCompleteRequest(supportProvider, currentFileRequest);
+        try {
+            getFileDownloadCompleteRequest.doPerform();
+        } catch (IOException e) {
+            currentFileRequest.fileDownloadCallback.downloadException(new HuaweiFileDownloadSendException(currentFileRequest, getFileDownloadCompleteRequest, e));
+            reset();
+        }
+
+        // Handle file data
+        try {
+            currentFileRequest.fileDownloadCallback.downloadComplete(currentFileRequest);
+        } catch (Exception e) {
+            LOG.error("Download complete callback exception.", e);
+            LOG.warn("File contents: {}", GB.hexdump(currentFileRequest.getData()));
+            GB.toast("Workout GPX file could not be parsed.",Toast.LENGTH_SHORT, GB.ERROR, e);
+        }
+
+        if (!this.currentFileRequest.newSync && !this.fileRequests.isEmpty() && !this.fileRequests.get(0).newSync) {
+            // Old sync can potentially take a shortcut
+            if (arrayContains(this.currentFileRequest.filenames, this.fileRequests.get(0).filename)) {
+                // Shortcut to next download
+                // - No init
+                // - No getting parameters
+                // Just copy over the info and go directly to GetFileInfo
+                FileRequest nextRequest = this.fileRequests.remove(0);
+
+                nextRequest.filenames = this.currentFileRequest.filenames;
+                nextRequest.maxBlockSize = this.currentFileRequest.maxBlockSize;
+                nextRequest.timeout = this.currentFileRequest.timeout;
+
+                this.currentFileRequest = nextRequest;
+                getFileInfo();
+                return;
+            }
+        }
+
+        reset();
+    }
+
+    private void reset() {
+        // Stop listening for file data, if we were doing that
+        this.supportProvider.removeInProgressRequests(this.fileDataReceiver);
+
+        // Reset current request
+        this.currentFileRequest = null;
+
+        // Unset busy, otherwise the next download will never start
+        synchronized (this.supportProvider) {
+            this.isBusy = false;
+        }
+        // Try to download next file
+        startDownload();
+    }
+
+    public void dispose() {
+        // Stop timeout from hitting, nothing else to really do
+        this.handler.removeCallbacks(this.timeout);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiFreebudsSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiFreebudsSupport.java
new file mode 100644
index 0000000..e23d005
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiFreebudsSupport.java
@@ -0,0 +1,135 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei;
+
+import android.bluetooth.BluetoothAdapter;
+import android.content.Context;
+import android.widget.Toast;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Earphones;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
+import nodomain.freeyourgadget.gadgetbridge.service.AbstractHeadphoneDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.HeadphoneHelper;
+import nodomain.freeyourgadget.gadgetbridge.service.btbr.AbstractBTBRDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.btbr.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetProductInformationRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.Request;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SetAudioModeRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SetPauseWhenRemovedFromEarRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceIoThread;
+import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+// TODO: Move from HuaweiBRSupport to AbstractBTBRDeviceSupport
+public class HuaweiFreebudsSupport extends HuaweiBRSupport implements HeadphoneHelper.Callback {
+    private static final Logger LOG = LoggerFactory.getLogger(HuaweiFreebudsSupport.class);
+
+    private HeadphoneHelper headphoneHelper;
+
+    public HuaweiFreebudsSupport() {
+        super();
+        addSupportedService(UUID.fromString("00001101-0000-1000-8000-00805f9b34fb"));
+        setBufferSize(1032);
+    }
+
+    @Override
+    public void setContext(GBDevice gbDevice, BluetoothAdapter btAdapter, Context context) {
+        super.setContext(gbDevice, btAdapter, context);
+        headphoneHelper = new HeadphoneHelper(getContext(), getDevice(), this);
+    }
+
+    @Override
+    protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
+        LOG.info("Huawei Freebuds init" );
+
+        super.getSupportProvider().setup(getDevice(), getContext());
+
+        builder.add(new nodomain.freeyourgadget.gadgetbridge.service.btbr.actions.SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext()));
+        try {
+            builder.setCallback(this);
+            final GetProductInformationRequest deviceProductReq = new GetProductInformationRequest(super.getSupportProvider());
+            deviceProductReq.setFinalizeReq(new Request.RequestCallback(getSupportProvider()) {
+                @Override
+                public void call() {
+                    // This also (optionally) starts the battery polling
+                    getSupportProvider().getBatteryLevel();
+                }
+            });
+            deviceProductReq.doPerform();
+        } catch (IOException e) {
+            LOG.error("Connection failed", e);
+            GB.toast("Connection failed", Toast.LENGTH_SHORT, GB.ERROR, e);
+        }
+        builder.add(new nodomain.freeyourgadget.gadgetbridge.service.btbr.actions.SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZED, getContext()));
+        return builder;
+    }
+
+    @Override
+    public void dispose() {
+        if (headphoneHelper != null)
+            headphoneHelper.dispose();
+        super.dispose();
+    }
+
+    @Override
+    public void onSocketRead(byte[] data) {
+        super.getSupportProvider().onSocketRead(data);
+    }
+
+    @Override
+    public void onSetCallState(CallSpec callSpec) {
+        headphoneHelper.onSetCallState(callSpec);
+    }
+
+    @Override
+    public void onNotification(NotificationSpec notificationSpec) {
+        headphoneHelper.onNotification(notificationSpec);
+    }
+
+    @Override
+    public void onFetchRecordedData(int dataTypes) {
+        // Do nothing.
+    }
+
+    @Override
+    public void onSendConfiguration(String config) {
+        if (headphoneHelper.onSendConfiguration(config))
+            return;
+
+        try {
+            switch (config) {
+                case DeviceSettingsPreferenceConst.PREF_HUAWEI_FREEBUDS_INEAR:
+                    new SetPauseWhenRemovedFromEarRequest(getSupportProvider()).doPerform();
+                    break;
+                case DeviceSettingsPreferenceConst.PREF_HUAWEI_FREEBUDS_AUDIOMODE:
+                    new SetAudioModeRequest(getSupportProvider()).doPerform();
+                    break;
+                case DeviceSettingsPreferenceConst.PREF_BATTERY_POLLING_ENABLE:
+                    if (!GBApplication.getDevicePrefs(gbDevice).getBatteryPollingEnabled()) {
+                        getSupportProvider().stopBatteryRunnerDelayed();
+                        break;
+                    }
+                    // Fall through if enabled
+                case DeviceSettingsPreferenceConst.PREF_BATTERY_POLLING_INTERVAL:
+                    if (!getSupportProvider().startBatteryRunnerDelayed()) {
+                        GB.toast(getContext(), R.string.battery_polling_failed_start, Toast.LENGTH_SHORT, GB.ERROR);
+                        LOG.error("Failed to start the battery polling");
+                    }
+                    break;
+
+            }
+        } catch (IOException e) {
+            GB.toast(getContext(), "Configuration of Huawei device failed", Toast.LENGTH_SHORT, GB.ERROR, e);
+            LOG.error("Configuration of Huawei device failed", e);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiFwHelper.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiFwHelper.java
new file mode 100644
index 0000000..1529f6b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiFwHelper.java
@@ -0,0 +1,354 @@
+/*  Copyright (C) 2024 Vitalii Tomin
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.media.MediaExtractor;
+import android.media.MediaMetadataRetriever;
+import  	android.media.MediaFormat;
+import android.net.Uri;
+import android.provider.OpenableColumns;
+import android.text.TextUtils;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.ByteArrayOutputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiBinAppParser;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FileUpload;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+import nodomain.freeyourgadget.gadgetbridge.util.GBZipFile;
+import nodomain.freeyourgadget.gadgetbridge.util.UriHelper;
+import nodomain.freeyourgadget.gadgetbridge.util.ZipFileException;
+
+public class HuaweiFwHelper {
+    private static final Logger LOG = LoggerFactory.getLogger(HuaweiFwHelper.class);
+
+    private final Uri uri;
+
+    private byte[] fw;
+    private int fileSize = 0;
+    private byte fileType = 0;
+    String fileName = "";
+
+    Bitmap previewBitmap;
+    HuaweiWatchfaceManager.WatchfaceDescription watchfaceDescription;
+    HuaweiAppManager.AppConfig appConfig;
+    HuaweiMusicManager.AudioInfo musicInfo;
+    Context mContext;
+
+
+
+    public HuaweiFwHelper(final Uri uri, final Context context) {
+
+        this.uri = uri;
+        final UriHelper uriHelper;
+        this.mContext = context;
+        try {
+            uriHelper = UriHelper.get(uri, context);
+        } catch (final IOException e) {
+            LOG.error("Failed to get uri helper for {}", uri, e);
+            return;
+        }
+
+        parseFile();
+    }
+
+    private void parseFile() {
+        if (parseAsMusic()) {
+            fileType = FileUpload.Filetype.music;
+        } else if (parseAsApp()) {
+            assert appConfig.bundleName != null;
+            fileType = FileUpload.Filetype.app;
+        } else if (parseAsWatchFace()) {
+            assert watchfaceDescription.screen != null;
+            assert watchfaceDescription.title != null;
+            fileType = FileUpload.Filetype.watchface;
+        }
+    }
+
+    private String getNameWithoutExtension(String fileName) {
+        return fileName.indexOf(".") > 0?fileName.substring(0, fileName.lastIndexOf(".")): fileName;
+    }
+
+    private String getExtension(String fileName) {
+        if (TextUtils.isEmpty(fileName)) {
+            return null;
+        }
+        int lastIndexOf = fileName.lastIndexOf(".");
+        if (lastIndexOf >= 0 && lastIndexOf + 1 < fileName.length()) {
+            return fileName.substring(lastIndexOf + 1);
+        }
+        return null;
+    }
+
+    private HuaweiMusicManager.AudioInfo getAudioInfo(Uri selectedUri) throws IOException {
+        ContentResolver contentResolver = mContext.getContentResolver();
+        String[] filePathColumn = {OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE};
+
+        Cursor cursor = contentResolver.query(selectedUri, filePathColumn, null, null, null);
+        if(cursor == null)
+            return null;
+        cursor.moveToFirst();
+
+        int fileNameIndex = cursor.getColumnIndex(filePathColumn[0]);
+        String fileName = cursor.getString(fileNameIndex);
+        int fileSizeIndex = cursor.getColumnIndex(filePathColumn[1]);
+        long fileSize = cursor.getLong(fileSizeIndex);
+        cursor.close();
+
+        MediaMetadataRetriever mmr = new MediaMetadataRetriever();
+        mmr.setDataSource(mContext, selectedUri);
+
+        String title = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_TITLE);
+        String artist = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ARTIST);
+
+        if(TextUtils.isEmpty(title)) {
+            title = getNameWithoutExtension(fileName);
+        }
+        if(TextUtils.isEmpty(artist)) {
+            artist = "Unknown";
+        }
+
+        MediaExtractor mex = new MediaExtractor();
+        mex.setDataSource(mContext, selectedUri, null);
+
+
+        MediaFormat mf = mex.getTrackFormat(0);
+
+        int bitrate = -1; // TODO: calculate or get bitrate
+        int sampleRate = mf.getInteger(MediaFormat.KEY_SAMPLE_RATE);
+        int channels = mf.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
+        long duration = mf.getLong(MediaFormat.KEY_DURATION);
+
+        LOG.info("bitRate: " + bitrate);
+        LOG.info("sampleRate: " + sampleRate);
+        LOG.info("channelCount: " + channels);
+        LOG.info("duration: " + duration);
+
+        String extension = getExtension(fileName);
+        if(!TextUtils.isEmpty(extension)) {
+            extension = extension.toLowerCase();
+        }
+
+        HuaweiMusicManager.AudioInfo audioInfo = new HuaweiMusicManager.AudioInfo(fileName, fileSize, title, artist, extension);
+        audioInfo.setCharacteristics(duration, sampleRate, bitrate, (byte) channels);
+
+        return audioInfo;
+    }
+
+    private byte[] getFileData(UriHelper uriHelper) throws IOException {
+        InputStream inputStream = uriHelper.openInputStream();
+        ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+
+        int nRead;
+        byte[] data = new byte[1000];
+        while ((nRead = inputStream.read(data, 0, data.length)) != -1) {
+            buffer.write(data, 0, nRead);
+        }
+        buffer.flush();
+        inputStream.close();
+        return buffer.toByteArray();
+    }
+
+    boolean parseAsMusic() {
+        try {
+            final UriHelper uriHelper = UriHelper.get(uri, this.mContext);
+
+            String mimeType = mContext.getContentResolver().getType(uri);
+            LOG.info("File mime type: {}", mimeType);
+
+            if(mimeType == null || !mimeType.startsWith("audio/"))
+                return false;
+
+            musicInfo = getAudioInfo(uri);
+            if(musicInfo == null)
+                return false;
+
+            musicInfo.setMimeType(mimeType);
+
+            byte[] musicData = getFileData(uriHelper);
+
+            fileName = musicInfo.getFileName();
+            fw = musicData;
+            fileSize = fw.length;
+
+            return true;
+        } catch (FileNotFoundException e) {
+            LOG.error("Music: File was not found {}", e.getMessage());
+        } catch (IOException e) {
+            LOG.error("Music: General IO error occurred {}", e.getMessage());
+        } catch (Exception e) {
+            LOG.error("Music: Unknown error occurred", e);
+        }
+        return false;
+    }
+
+
+    boolean parseAsApp() {
+
+        try {
+            final UriHelper uriHelper = UriHelper.get(uri, this.mContext);
+
+            byte[] appData = getFileData(uriHelper);
+
+            HuaweiBinAppParser app = new HuaweiBinAppParser();
+            app.parseData(appData);
+
+            byte[] config = app.getEntryContent("config.json");
+            if (config == null)
+                return false;
+            appConfig = new HuaweiAppManager.AppConfig(new String(config));
+            fileName = app.getPackageName() + "_INSTALL"; //TODO: INSTALL or UPDATE suffix
+
+            fw = appData;
+            fileSize = fw.length;
+
+            byte[] icon = app.getEntryContent("icon_small.png");
+            if (icon != null) {
+                previewBitmap = BitmapFactory.decodeByteArray(icon, 0, icon.length);
+            }
+
+            return true;
+
+        } catch (FileNotFoundException e) {
+            LOG.error("App: File was not found{}", e.getMessage());
+        } catch (IOException e) {
+            LOG.error("App: General IO error occurred {}", e.getMessage());
+        } catch (HuaweiBinAppParser.HuaweiBinAppParseError e) {
+            LOG.error("App: Error parsing app File {}", e.getMessage());
+        } catch (Exception e) {
+            LOG.error("App: Unknown error occurred", e);
+        }
+        return false;
+    }
+
+
+    public byte[] getBytes() {
+        return fw;
+    }
+
+    public void unsetFwBytes() {
+        this.fw = null;
+    }
+
+    boolean parseAsWatchFace() {
+        boolean isWatchface = false;
+
+        try {
+            final UriHelper uriHelper = UriHelper.get(uri, this.mContext);
+
+            GBZipFile watchfacePackage = new GBZipFile(uriHelper.openInputStream());
+            byte[] bytesDescription = watchfacePackage.getFileFromZip("description.xml");
+
+            // check if description file contents BOM
+            ByteBuffer bb = ByteBuffer.wrap(bytesDescription);
+            byte[] bom = new byte[3];
+            // get the first 3 bytes
+            bb.get(bom, 0, bom.length);
+            String content = new String(GB.hexdump(bom));
+            String xmlDescription = null;
+            if ("efbbbf".equalsIgnoreCase(content)) {
+                byte[] contentAfterFirst3Bytes = new byte[bytesDescription.length - 3];
+                bb.get(contentAfterFirst3Bytes, 0, contentAfterFirst3Bytes.length);
+                xmlDescription = new String(contentAfterFirst3Bytes);
+            } else {
+                xmlDescription = new String(bytesDescription);
+            }
+
+            watchfaceDescription = new HuaweiWatchfaceManager.WatchfaceDescription(xmlDescription);
+            if (watchfacePackage.fileExists("preview/cover.jpg")) {
+                final byte[] preview = watchfacePackage.getFileFromZip("preview/cover.jpg");
+                previewBitmap = BitmapFactory.decodeByteArray(preview, 0, preview.length);
+            }
+
+            byte[] watchfaceZip = watchfacePackage.getFileFromZip("com.huawei.watchface");
+            try {
+                GBZipFile watchfaceBinZip = new GBZipFile(watchfaceZip);
+                fw = watchfaceBinZip.getFileFromZip("watchface.bin");
+            } catch (ZipFileException e) {
+                LOG.error("Unable to get watchfaceZip,  it seems older already watchface.bin");
+                fw = watchfaceZip;
+            }
+            fileSize = fw.length;
+            isWatchface = true;
+
+        } catch (ZipFileException e) {
+            LOG.error("Watchface: Unable to read file {}", e.getMessage());
+        } catch (FileNotFoundException e) {
+            LOG.error("Watchface: File was not found {}", e.getMessage());
+        } catch (IOException e) {
+            LOG.error("Watchface: General IO error occurred {}", e.getMessage());
+        } catch (Exception e) {
+            LOG.error("Watchface: Unknown error occurred", e);
+        }
+
+        return isWatchface;
+    }
+
+    public boolean isWatchface() {
+        return fileType == FileUpload.Filetype.watchface;
+    }
+
+    public boolean isAPP() {
+        return fileType == FileUpload.Filetype.app;
+    }
+
+    public boolean isMusic() {
+        return fileType == FileUpload.Filetype.music;
+    }
+
+    public boolean isValid() {
+        return isWatchface() || isAPP() || isMusic();
+    }
+
+    public Bitmap getPreviewBitmap() {
+        return previewBitmap;
+    }
+
+    public HuaweiWatchfaceManager.WatchfaceDescription getWatchfaceDescription() {
+        return watchfaceDescription;
+    }
+
+    public HuaweiAppManager.AppConfig getAppConfig() {
+        return appConfig;
+    }
+
+    public HuaweiMusicManager.AudioInfo getMusicInfo() {
+        return musicInfo;
+    }
+
+    public byte getFileType() {
+        return fileType;
+    }
+
+    public String getFileName() {
+        return fileName;
+    }
+
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiLESupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiLESupport.java
new file mode 100644
index 0000000..1586816
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiLESupport.java
@@ -0,0 +1,228 @@
+/*  Copyright (C) 2024 Damien Gaignon, Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.content.Context;
+import android.location.Location;
+import android.net.Uri;
+
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventCameraRemote;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiConstants;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceMusic;
+import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.Contact;
+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.GattService;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+
+public class HuaweiLESupport extends AbstractBTLEDeviceSupport {
+    private static final Logger LOG = LoggerFactory.getLogger(HuaweiLESupport.class);
+
+    private final HuaweiSupportProvider supportProvider;
+
+    public HuaweiLESupport() {
+        super(LOG);
+        addSupportedService(GattService.UUID_SERVICE_GENERIC_ACCESS);
+        addSupportedService(GattService.UUID_SERVICE_GENERIC_ATTRIBUTE);
+        addSupportedService(GattService.UUID_SERVICE_DEVICE_INFORMATION);
+        addSupportedService(GattService.UUID_SERVICE_HUMAN_INTERFACE_DEVICE);
+        addSupportedService(HuaweiConstants.UUID_SERVICE_HUAWEI_SERVICE);
+        supportProvider = new HuaweiSupportProvider(this);
+    }
+
+    @Override
+    public void setContext(GBDevice gbDevice, BluetoothAdapter btAdapter, Context context) {
+        super.setContext(gbDevice, btAdapter, context);
+        supportProvider.setContext(context);
+    }
+
+    @Override
+    protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
+        return supportProvider.initializeDevice(builder);
+    }
+
+    @Override
+    public boolean connectFirstTime() {
+        supportProvider.setFirstConnection(true);
+        return connect();
+    }
+
+    @Override
+    public boolean onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
+        supportProvider.onCharacteristicChanged(characteristic);
+        return true;
+    }
+
+    @Override
+    public boolean useAutoConnect() {
+        return true;
+    }
+
+    @Override
+    public void onSendConfiguration(String config) {
+        supportProvider.onSendConfiguration(config);
+    }
+
+    @Override
+    public void onFetchRecordedData(int dataTypes) {
+        supportProvider.onFetchRecordedData(dataTypes);
+    }
+
+    @Override
+    public void onReset(int flags) {
+        supportProvider.onReset(flags);
+    }
+
+    @Override
+    public void onNotification(NotificationSpec notificationSpec) {
+        supportProvider.onNotification(notificationSpec);
+    }
+
+    @Override
+    public void onSetTime() {
+        supportProvider.onSetTime();
+    }
+
+    @Override
+    public void onSetAlarms(ArrayList<? extends nodomain.freeyourgadget.gadgetbridge.model.Alarm> alarms) {
+        supportProvider.onSetAlarms(alarms);
+    }
+
+    @Override
+    public void onSetCallState(CallSpec callSpec) {
+        supportProvider.onSetCallState(callSpec);
+    }
+
+    @Override
+    public void onSetMusicState(MusicStateSpec stateSpec) {
+        supportProvider.onSetMusicState(stateSpec);
+    }
+
+    @Override
+    public void onSetMusicInfo(MusicSpec musicSpec) {
+        supportProvider.onSetMusicInfo(musicSpec);
+    }
+
+    @Override
+    public void onSetPhoneVolume(float volume) {
+        supportProvider.onSetPhoneVolume();
+    }
+
+    @Override
+    public void onFindPhone(boolean start) {
+        if (!start)
+            supportProvider.onStopFindPhone();
+    }
+
+    @Override
+    public void onSendWeather(ArrayList<WeatherSpec> weatherSpecs) {
+        supportProvider.onSendWeather(weatherSpecs);
+    }
+
+    @Override
+    public void onSetGpsLocation(Location location) {
+        supportProvider.onSetGpsLocation(location);
+    }
+
+    @Override
+    public void onInstallApp(Uri uri) {
+        supportProvider.onInstallApp(uri);
+    }
+
+    @Override
+    public void onAppInfoReq() {
+        supportProvider.onAppInfoReq();
+    }
+
+    @Override
+    public void onAppStart(final UUID uuid, boolean start) {
+        if (start) {
+            supportProvider.onAppStart(uuid, start);
+        }
+    }
+
+    @Override
+    public void onAppDelete(final UUID uuid) {
+        supportProvider.onAppDelete(uuid);
+    }
+
+    @Override
+    public void onCameraStatusChange(GBDeviceEventCameraRemote.Event event, String filename) {
+        supportProvider.onCameraStatusChange(event, filename);
+    }
+
+    @Override
+    public void onSetContacts(ArrayList<? extends Contact> contacts) {
+        supportProvider.onSetContacts(contacts);
+    }
+
+    @Override
+    public void onAddCalendarEvent(final CalendarEventSpec calendarEventSpec) {
+        supportProvider.onAddCalendarEvent(calendarEventSpec);
+    }
+
+    @Override
+    public void onDeleteCalendarEvent(final byte type, long id) {
+        supportProvider.onDeleteCalendarEvent(type, id);
+    }
+
+    @Override
+    public void dispose() {
+        supportProvider.dispose();
+        super.dispose();
+    }
+
+    public void onTestNewFunction() {
+        supportProvider.onTestNewFunction();
+    }
+
+    @Override
+    public boolean getImplicitCallbackModify() {
+        return true;
+    }
+
+    @Override
+    public boolean getSendWriteRequestResponse() {
+        return false;
+    }
+
+    @Override
+    public void onMusicListReq() {
+        supportProvider.onMusicListReq();
+    }
+
+    @Override
+    public void onMusicOperation(int operation, int playlistIndex, String playlistName, ArrayList<Integer> musicIds) {
+        supportProvider.onMusicOperation(operation, playlistIndex, playlistName, musicIds);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiMusicManager.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiMusicManager.java
new file mode 100644
index 0000000..c948dfb
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiMusicManager.java
@@ -0,0 +1,377 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei;
+
+import android.widget.Toast;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceMusicData;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceMusicUpdate;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiMusicUtils;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.MusicControl;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceMusic;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceMusicPlaylist;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetMusicInfoParams;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetMusicList;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetMusicPlaylist;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetMusicPlaylistMusics;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendMusicOperation;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendUploadMusicFileInfoResponse;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class HuaweiMusicManager {
+    static Logger LOG = LoggerFactory.getLogger(HuaweiMusicManager.class);
+
+    public static class AudioInfo {
+        private final String fileName;
+        private final long fileSize;
+        private final String title;
+        private final String artist;
+        private final String extension;
+
+        private String mimeType;
+
+        private long duration;
+        private int sampleRate;
+        private int bitrate;
+        private byte channels;
+
+        //public byte musicEncode = -1;  // TODO: not sure
+        //public short unknownBitrate = -1;   // TODO: not sure
+
+        public AudioInfo(String fileName, long fileSize, String title, String artist, String extension) {
+            this.fileName = fileName;
+            this.fileSize = fileSize;
+            this.title = title;
+            this.artist = artist;
+            this.extension = extension;
+        }
+
+        public String getFileName() {
+            return fileName;
+        }
+
+        public long getFileSize() { return fileSize;}
+
+        public String getTitle() {
+            return title;
+        }
+
+        public String getArtist() {
+            return artist;
+        }
+
+        public String getExtension() {
+            return extension;
+        }
+
+        public String getMimeType() {
+            return mimeType;
+        }
+
+        public void setMimeType(String mimeType) {
+            this.mimeType = mimeType;
+        }
+
+        public void setCharacteristics(long duration, int sampleRate, int bitrate, byte channels) {
+            this.duration = duration;
+            this.sampleRate = sampleRate;
+            this.bitrate = bitrate;
+            this.channels = channels;
+        }
+
+        public long getDuration() {
+            return duration;
+        }
+
+        public int getSampleRate() {
+            return sampleRate;
+        }
+
+        public int getBitrate() {
+            return bitrate;
+        }
+
+        public byte getChannels() {
+            return channels;
+        }
+
+        @Override
+        public String toString() {
+            final StringBuffer sb = new StringBuffer("AudioInfo{");
+            sb.append("fileName='").append(fileName).append('\'');
+            sb.append("fileSize='").append(fileSize).append('\'');
+            sb.append(", title='").append(title).append('\'');
+            sb.append(", artist='").append(artist).append('\'');
+            sb.append(", mimeType='").append(mimeType).append('\'');
+            sb.append('}');
+            return sb.toString();
+        }
+    }
+
+    private final HuaweiSupportProvider support;
+
+    private AudioInfo currentMusicInfo;
+
+
+    public HuaweiMusicManager(HuaweiSupportProvider support) {
+        this.support = support;
+    }
+
+
+    public void addUploadMusic(AudioInfo audioInfo) {
+        currentMusicInfo = audioInfo;
+    }
+
+    public void uploadMusicInfo(short songIndex, String fileName) {
+        AudioInfo current = currentMusicInfo;
+        if(current == null || (!current.getFileName().equals(fileName))) {
+            LOG.error("Upload file info does not exist.");
+            return;
+        }
+        try {
+            SendUploadMusicFileInfoResponse sendUploadMusicFileInfoResponse = new SendUploadMusicFileInfoResponse(support,
+                    songIndex, current.getTitle(), current.getArtist());
+            sendUploadMusicFileInfoResponse.doPerform();
+        } catch (IOException e) {
+            LOG.error("Could not send sendUploadMusicFileInfoResponse", e);
+        }
+    }
+
+    private boolean syncMusicData = false;
+    private int frameCount = 0;
+    private int endFrame = 65535;
+    private int currentFrame = 0;
+
+
+    public void startSyncMusicData() {
+        syncMusicData = true;
+        try {
+            GetMusicInfoParams getMusicInfoParams = new GetMusicInfoParams(this.support);
+            getMusicInfoParams.doPerform();
+        } catch (IOException e) {
+            LOG.error("Get music info: {}", e.getMessage());
+            syncMusicData = false;
+        }
+    }
+
+
+    private void syncMusicList() {
+        if (!syncMusicData) {
+            this.currentFrame = 0;
+            return;
+        }
+        int count = this.frameCount;
+        if (support.getHuaweiCoordinator().supportsMoreMusic()) {
+            count = Math.min(this.frameCount, 250);
+        }
+        if (this.currentFrame < count) {
+            try {
+                GetMusicList getMusicList = new GetMusicList(this.support, this.currentFrame, this.endFrame);
+                getMusicList.doPerform();
+            } catch (IOException e) {
+                LOG.error("Get music list: {}", e.getMessage());
+                endMusicListSync();
+            }
+        } else {
+            endMusicListSync();
+        }
+    }
+
+    private void endMusicListSync() {
+        this.currentFrame = 0;
+        try {
+            GetMusicPlaylist getMusicPlaylist = new GetMusicPlaylist(this.support);
+            getMusicPlaylist.doPerform();
+        } catch (IOException e) {
+            LOG.error("Get music playlist: {}", e.getMessage());
+            endMusicPlaylistSync();
+        }
+    }
+
+    private void endMusicPlaylistSync() {
+        this.currentPlaylistIndex = 0;
+        this.currentPlaylistFrame = 0;
+        tempPlaylistMusic.clear();
+
+        musicPlaylistMusicSync();
+    }
+
+    private final List<MusicControl.MusicPlaylists.Response.PlaylistData> devicePlaylists = new ArrayList<>();
+
+    private int currentPlaylistIndex = 0;
+    private int currentPlaylistFrame = 0;
+    private final List<List<Integer>> tempPlaylistMusic = new ArrayList<>();
+
+    private void musicPlaylistMusicSync() {
+        if (this.currentPlaylistIndex < devicePlaylists.size()) {
+            MusicControl.MusicPlaylists.Response.PlaylistData playlist = devicePlaylists.get(this.currentPlaylistIndex);
+            syncPlaylistMusicsOne(playlist.id, playlist.frameCount);
+        } else {
+            musicPlaylistMusicDone();
+        }
+    }
+
+    private void syncPlaylistMusicsOne(int id, int frameCount) {
+        if (this.currentPlaylistFrame < frameCount) {
+            try {
+                GetMusicPlaylistMusics getMusicPlaylistMusics = new GetMusicPlaylistMusics(this.support, id, this.currentPlaylistFrame);
+                getMusicPlaylistMusics.doPerform();
+            } catch (IOException e) {
+                LOG.error("Get music playlist musics: {}", e.getMessage());
+                musicPlaylistMusicDone();
+            }
+        } else {
+            syncPlayListMusicIndexDone(id, frameCount);
+        }
+    }
+
+    public void syncNextPlaylistMusicIndex() {
+        this.currentPlaylistFrame++;
+        MusicControl.MusicPlaylists.Response.PlaylistData playlist = devicePlaylists.get(this.currentPlaylistIndex);
+        syncPlaylistMusicsOne(playlist.id, playlist.frameCount);
+    }
+
+    private void syncPlayListMusicIndexDone(int id, int frameCount) {
+        MusicControl.MusicPlaylists.Response.PlaylistData playlist = devicePlaylists.get(this.currentPlaylistIndex);
+
+        ArrayList<Integer> musics = new ArrayList<>();
+        if (this.tempPlaylistMusic.size() == frameCount) {
+            for (int i = 0; i < frameCount; i++) {
+                musics.addAll(this.tempPlaylistMusic.get(i));
+            }
+        }
+
+        GBDeviceMusicPlaylist pl = new GBDeviceMusicPlaylist(playlist.id, playlist.name, musics);
+        List<GBDeviceMusicPlaylist> list = new ArrayList<>();
+        list.add(pl);
+        sendMusicPlaylist(list);
+        this.currentPlaylistIndex++;
+        this.currentPlaylistFrame = 0;
+        this.tempPlaylistMusic.clear();
+        musicPlaylistMusicSync();
+    }
+
+    private void musicPlaylistMusicDone() {
+        this.currentPlaylistIndex = 0;
+        this.currentPlaylistFrame = 0;
+        this.tempPlaylistMusic.clear();
+
+        this.syncMusicData = false;
+        sendMusicSyncDone();
+    }
+
+    public void onMusicMusicInfoParams(HuaweiMusicUtils.MusicCapabilities capabilities, int frameCount, List<HuaweiMusicUtils.PageStruct> pageStruct) {
+        //TODO: research and use pageStruct. It may/should be used to retrieve music data from devices by pages.
+        // without it list can be incomplete, but I can't confirm this.
+        LOG.info("FrameCount: {}, pageStruct: {}", frameCount, pageStruct);
+        support.getHuaweiCoordinator().setMusicInfoParams(capabilities);
+        if(syncMusicData) {
+            this.frameCount = frameCount;
+            this.currentFrame = 0;
+            this.endFrame = 65535;
+            String formats = null;
+            if(capabilities.supportedFormats != null) {
+                formats = String.join(",", capabilities.supportedFormats);
+            }
+            int maxPlaylistCount = 0;
+            if(support.getCoordinator().getHuaweiCoordinator().getExtendedMusicInfoParams() != null) {
+                maxPlaylistCount = support.getCoordinator().getHuaweiCoordinator().getExtendedMusicInfoParams().maxPlaylistCount;
+            }
+            sendMusicSyncStart(support.getContext().getString(R.string.music_huawei_device_info, formats, capabilities.availableSpace), capabilities.maxMusicCount, maxPlaylistCount);
+            syncMusicList();
+        }
+    }
+
+    private void sendMusicSyncStart(final String info, int maxMusicCount, int maxPlaylistCount) {
+        final GBDeviceMusicData musicListCmd = new GBDeviceMusicData();
+        musicListCmd.type = 1;
+        musicListCmd.deviceInfo = info;
+        musicListCmd.maxMusicCount = maxMusicCount;
+        musicListCmd.maxPlaylistCount = maxPlaylistCount;
+        support.evaluateGBDeviceEvent(musicListCmd);
+    }
+
+
+    private void sendMusicList(List<GBDeviceMusic> list) {
+        final GBDeviceMusicData musicListCmd = new GBDeviceMusicData();
+        musicListCmd.type = 2;
+        musicListCmd.list = list;
+        support.evaluateGBDeviceEvent(musicListCmd);
+    }
+
+    private void sendMusicPlaylist(List<GBDeviceMusicPlaylist> list) {
+        final GBDeviceMusicData musicListCmd = new GBDeviceMusicData();
+        musicListCmd.type = 2;
+        musicListCmd.playlists = list;
+        support.evaluateGBDeviceEvent(musicListCmd);
+    }
+
+    private void sendMusicSyncDone() {
+        final GBDeviceMusicData musicListCmd = new GBDeviceMusicData();
+        musicListCmd.type = 10;
+        support.evaluateGBDeviceEvent(musicListCmd);
+    }
+
+    public void onMusicListResponse(int startFrame, int endFrame, List<GBDeviceMusic> list) {
+        sendMusicList(list);
+        if (support.getHuaweiCoordinator().supportsMoreMusic() || !(endFrame == this.endFrame || list.size() == 1)) {
+            if (list.size() == 2) {
+                this.endFrame = list.get(1).getId();
+            }
+            this.currentFrame++;
+            syncMusicList();
+            return;
+        }
+        endMusicListSync();
+    }
+
+    public void onMusicPlaylistResponse(List<MusicControl.MusicPlaylists.Response.PlaylistData> playlists) {
+        this.devicePlaylists.clear();
+        for(MusicControl.MusicPlaylists.Response.PlaylistData pl: playlists) {
+            if(pl.id != 0) {
+                this.devicePlaylists.add(pl);
+            }
+        }
+        endMusicPlaylistSync();
+    }
+
+    public void onMusicPlaylistMusics(int id, int index, List<Integer> musicIds) {
+        this.tempPlaylistMusic.add(musicIds);
+        syncNextPlaylistMusicIndex();
+    }
+
+    public void onMusicOperation(int operation, int playlistIndex, String playlistName, ArrayList<Integer> musicIds) {
+        LOG.info("music operation: {}", operation);
+        try {
+            SendMusicOperation sendMusicOperation = new SendMusicOperation(this.support, operation, playlistIndex, playlistName, musicIds);
+            sendMusicOperation.doPerform();
+        } catch (IOException e) {
+            LOG.error("SendMusicOperation: {}", e.getMessage());
+        }
+    }
+
+    public void onMusicOperationResponse(int resultCode, int operation, int playlistIndex, String playlistName, ArrayList<Integer> musicIds) {
+
+        boolean success = true;
+        if (resultCode != 0x000186A0) {
+            GB.toast(support.getContext(), support.getContext().getString(R.string.music_error), Toast.LENGTH_SHORT, GB.ERROR);
+            success = false;
+        }
+
+        LOG.info("music operation response: {} {}", operation, success);
+        final GBDeviceMusicUpdate updateCmd = new GBDeviceMusicUpdate();
+        updateCmd.success = success;
+        updateCmd.operation = operation;
+        updateCmd.playlistIndex = playlistIndex;
+        updateCmd.playlistName = playlistName;
+        updateCmd.musicIds = musicIds;
+
+        support.evaluateGBDeviceEvent(updateCmd);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiP2PManager.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiP2PManager.java
new file mode 100644
index 0000000..3710e29
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiP2PManager.java
@@ -0,0 +1,71 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.P2P;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.p2p.HuaweiBaseP2PService;
+
+public class HuaweiP2PManager {
+    private final Logger LOG = LoggerFactory.getLogger(HuaweiP2PManager.class);
+
+    private final HuaweiSupportProvider support;
+
+    private final List<HuaweiBaseP2PService> registeredServices;
+
+    private Short sequence = 1;
+
+    public synchronized Short getNextSequence() {
+            return sequence++;
+    }
+
+    public HuaweiP2PManager(HuaweiSupportProvider support) {
+        this.support = support;
+        this.registeredServices = new ArrayList<>();
+    }
+
+    public HuaweiSupportProvider getSupportProvider() {
+        return support;
+    }
+
+    public void registerService(HuaweiBaseP2PService service) {
+        for (HuaweiBaseP2PService svr : registeredServices) {
+            if (svr.getModule().equals(service.getModule())) {
+                LOG.error("P2P Service already registered, unregister: {}", service.getModule());
+                svr.unregister();
+                registeredServices.remove(svr);
+            }
+        }
+        registeredServices.add(service);
+        service.registered();
+    }
+
+    public HuaweiBaseP2PService getRegisteredService(String module) {
+        for (HuaweiBaseP2PService svr : registeredServices) {
+            if (svr.getModule().equals(module)) {
+                return svr;
+            }
+        }
+        return null;
+    }
+
+    public void unregisterAllService() {
+        for (HuaweiBaseP2PService svr : registeredServices) {
+            svr.unregister();
+        }
+        registeredServices.clear();
+    }
+
+
+    public void handlePacket(P2P.P2PCommand.Response packet) {
+        LOG.info("P2P Service message: Src: {} Dst: {} Seq: {}", packet.srcPackage, packet.dstPackage, packet.sequenceId);
+        for (HuaweiBaseP2PService service : registeredServices) {
+            if (service.getPackage().equals(packet.srcPackage)) {
+                service.handlePacket(packet);
+            }
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiSupportProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiSupportProvider.java
new file mode 100644
index 0000000..015d9a8
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiSupportProvider.java
@@ -0,0 +1,2514 @@
+/*  Copyright (C) 2024 Damien Gaignon, Martin.JM, Vitalii Tomin
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei;
+
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.location.Location;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+import android.widget.Toast;
+
+import androidx.annotation.NonNull;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.nio.charset.StandardCharsets;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Random;
+import java.util.UUID;
+
+import de.greenrobot.dao.query.DeleteQuery;
+import de.greenrobot.dao.query.QueryBuilder;
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.SettingsActivity;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventAppInfo;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventCameraRemote;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventDisplayMessage;
+import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiConstants;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiCoordinatorSupplier;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiCoordinatorSupplier.HuaweiDeviceType;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiCrypto;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiDictTypes;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiGpsParser;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiTruSleepParser;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.CameraRemote;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.GpsAndTime;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Weather;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Workout;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst;
+import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary;
+import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummaryDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiDictData;
+import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiDictDataDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiDictDataValues;
+import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiDictDataValuesDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutDataSample;
+import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutDataSampleDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutPaceSample;
+import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutPaceSampleDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutSummarySample;
+import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutSummarySampleDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutSwimSegmentsSample;
+import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutSwimSegmentsSampleDao;
+import nodomain.freeyourgadget.gadgetbridge.export.ActivityTrackExporter;
+import nodomain.freeyourgadget.gadgetbridge.export.GPXExporter;
+import nodomain.freeyourgadget.gadgetbridge.externalevents.gps.GBLocationProviderType;
+import nodomain.freeyourgadget.gadgetbridge.externalevents.gps.GBLocationService;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.entities.Alarm;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.entities.User;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityPoint;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityTrack;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
+import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.Contact;
+import nodomain.freeyourgadget.gadgetbridge.model.GPSCoordinate;
+import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.RecordedDataTypes;
+import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
+
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.p2p.HuaweiP2PCalendarService;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.p2p.HuaweiP2PTrackService;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.p2p.HuaweiP2PDataDictionarySyncService;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.AcceptAgreementsRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetAppInfoParams;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetContactsCount;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetEventAlarmList;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetGpsParameterRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetExtendedMusicInfoParams;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetMusicInfoParams;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetNotificationConstraintsRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetSmartAlarmList;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetWatchfaceParams;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendCameraRemoteSetupEvent;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendDeviceReportThreshold;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendExtendedAccountRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendFitnessUserInfoRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendGpsDataRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendFileUploadInfo;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendHeartRateZonesConfig;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendRunPaceConfigRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendSetContactsRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendNotifyHeartRateCapabilityRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendNotifyRestHeartRateCapabilityRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SetAutomaticHeartrateRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SetAutomaticSpoRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SetDisconnectNotification;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SetMediumToStrengthThresholdRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SetSkinTemperatureMeasurement;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SetTemperatureUnitSetting;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.StopFindPhoneRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.StopNotificationRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetFitnessTotalsRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetHiChainRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetSleepDataCountRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetStepDataCountRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetWorkoutCountRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendNotificationRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SetMusicRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.AlarmsRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.DebugRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetActivityTypeRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.Request;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.Request.RequestCallback;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetAuthRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetBatteryLevelRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetBondParamsRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetBondRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetConnectStatusRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetDeviceStatusRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetDndLiftWristTypeRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetExpandCapabilityRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetLinkParamsRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetPincodeRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetProductInformationRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetSecurityNegotiationRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetSettingRelatedRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetSupportedServicesRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetWearStatusRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendDndAddRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendFactoryResetRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendFitnessGoalRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendMenstrualCapabilityRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendDndDeleteRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendSetUpDeviceStatusRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SetActivateOnLiftRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SetActivityReminderRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SetDateFormatRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SetLanguageSettingRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SetNotificationRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SetNavigateOnRotateRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SetTimeRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SetTimeZoneIdRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SetTruSleepRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SetWearLocationRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SetWearMessagePushRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetNotificationCapabilitiesRequest;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FitnessData;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SetWorkModeRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol;
+import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+import nodomain.freeyourgadget.gadgetbridge.util.MediaManager;
+import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
+
+public class HuaweiSupportProvider {
+    private static final Logger LOG = LoggerFactory.getLogger(HuaweiSupportProvider.class);
+
+    // TODO: Potentially use translatable messages for the toast messages
+
+    private final HuaweiSyncState syncState = new HuaweiSyncState(this);
+
+    private final int initTimeout = 2000;
+
+    private HuaweiBRSupport brSupport;
+    private HuaweiLESupport leSupport;
+
+    private GBDevice gbDevice;
+    private Context context;
+    private HuaweiCoordinatorSupplier.HuaweiDeviceType huaweiType;
+
+    private final Handler handler = new Handler(Looper.getMainLooper());
+    private final Runnable batteryRunner = () -> {
+        LOG.info("Running retrieving battery through runner.");
+        getBatteryLevel();
+    };
+
+    private boolean firstConnection = false;
+    protected byte protocolVersion;
+    public String deviceMac; //get it from GB
+    protected String macAddress;
+    protected String androidID;
+    protected short msgId = 0;
+
+    private MediaManager mediaManager = null;
+
+    private GpsAndTime.GpsParameters.Response gpsParametersResponse = null;
+    private boolean gpsEnabled = false;
+    private Location gpsLastLocation;
+
+    private final HuaweiPacket.ParamsProvider paramsProvider = new HuaweiPacket.ParamsProvider();
+
+    protected ResponseManager responseManager = new ResponseManager(this);
+    protected HuaweiUploadManager huaweiUploadManager = new HuaweiUploadManager(this);
+
+    protected HuaweiWatchfaceManager huaweiWatchfaceManager = new HuaweiWatchfaceManager(this);
+
+    protected HuaweiFileDownloadManager huaweiFileDownloadManager = new HuaweiFileDownloadManager(this);
+
+    protected HuaweiAppManager huaweiAppManager = new HuaweiAppManager(this);
+
+    protected HuaweiWeatherManager huaweiWeatherManager = new HuaweiWeatherManager(this);
+
+    protected HuaweiEphemerisManager huaweiEphemerisManager = new HuaweiEphemerisManager(this);
+
+    protected HuaweiMusicManager huaweiMusicManager = new HuaweiMusicManager(this);
+
+    //TODO: we need only one instance of manager and all it services.
+    protected HuaweiP2PManager huaweiP2PManager = new HuaweiP2PManager(this);
+
+    public HuaweiCoordinatorSupplier getCoordinator() {
+        return ((HuaweiCoordinatorSupplier) this.gbDevice.getDeviceCoordinator());
+    }
+
+    public HuaweiCoordinator getHuaweiCoordinator() {
+        return getCoordinator().getHuaweiCoordinator();
+    }
+
+    public HuaweiUploadManager getUploadManager() {
+        return huaweiUploadManager;
+    }
+
+    public HuaweiWatchfaceManager getHuaweiWatchfaceManager() {
+        return huaweiWatchfaceManager;
+    }
+
+    public HuaweiAppManager getHuaweiAppManager() {
+        return huaweiAppManager;
+    }
+
+    public HuaweiP2PManager getHuaweiP2PManager() {
+        return huaweiP2PManager;
+    }
+
+    public HuaweiEphemerisManager getHuaweiEphemerisManager() {
+        return huaweiEphemerisManager;
+    }
+
+    public HuaweiMusicManager getHuaweiMusicManager() {
+        return huaweiMusicManager;
+    }
+
+    public HuaweiSupportProvider(HuaweiBRSupport support) {
+        this.brSupport = support;
+    }
+
+    public HuaweiSupportProvider(HuaweiLESupport support) {
+        this.leSupport = support;
+    }
+
+    public void setContext(Context context) {
+        mediaManager = new MediaManager(context);
+    }
+
+    public boolean isBLE() {
+        return huaweiType == HuaweiDeviceType.AW || huaweiType == HuaweiDeviceType.BLE || huaweiType == HuaweiDeviceType.SMART;
+    }
+
+    public nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder createLeTransactionBuilder(String taskName) {
+        return leSupport.createTransactionBuilder(taskName);
+    }
+
+    public nodomain.freeyourgadget.gadgetbridge.service.btbr.TransactionBuilder createBrTransactionBuilder(String taskName) {
+        return brSupport.createTransactionBuilder(taskName);
+    }
+
+    public BluetoothGattCharacteristic getLeCharacteristic(UUID uuid) {
+        return leSupport.getCharacteristic(uuid);
+    }
+
+    public void performConnected(nodomain.freeyourgadget.gadgetbridge.service.btle.Transaction transaction) throws IOException {
+        leSupport.performConnected(transaction);
+    }
+
+    public void performConnected(nodomain.freeyourgadget.gadgetbridge.service.btbr.Transaction transaction) throws IOException {
+        brSupport.performConnected(transaction);
+    }
+
+    public void evaluateGBDeviceEvent(GBDeviceEvent deviceEvent) {
+        if (isBLE()) {
+            leSupport.evaluateGBDeviceEvent(deviceEvent);
+        } else {
+            brSupport.evaluateGBDeviceEvent(deviceEvent);
+        }
+    }
+
+    public void handleGBDeviceEvent(GBDeviceEventDisplayMessage message) {
+        if (isBLE()) {
+            leSupport.handleGBDeviceEvent(message);
+        } else {
+            brSupport.handleGBDeviceEvent(message);
+        }
+    }
+
+    public void setGps(boolean start) {
+        if (start) {
+            if (!GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress()).getBoolean(DeviceSettingsPreferenceConst.PREF_WORKOUT_SEND_GPS_TO_BAND, false))
+                return;
+            if (gpsParametersResponse == null) {
+                GetGpsParameterRequest gpsParameterRequest = new GetGpsParameterRequest(this);
+                gpsParameterRequest.setFinalizeReq(new RequestCallback() {
+                    @Override
+                    public void call() {
+                        if (gpsEnabled) {
+                            // Prevent adding multiple GPS providers
+                            LOG.info("GPS is already enabled.");
+                            return;
+                        }
+                        gpsEnabled = true;
+                        GBLocationService.start(getContext(), getDevice(), GBLocationProviderType.GPS, 1000);
+                    }
+                });
+                try {
+                    gpsParameterRequest.doPerform();
+                } catch (IOException e) {
+                    GB.toast(context, "Failed to get GPS parameters", Toast.LENGTH_SHORT, GB.ERROR, e);
+                    LOG.error("Failed to get GPS parameters", e);
+                }
+            } else {
+                if (gpsEnabled) {
+                    // Prevent adding multiple GPS providers
+                    LOG.info("GPS is already enabled.");
+                    return;
+                }
+                gpsEnabled = true;
+                GBLocationService.start(getContext(), getDevice(), GBLocationProviderType.GPS, 1000);
+            }
+        } else {
+            gpsEnabled = false;
+            GBLocationService.stop(getContext(), getDevice());
+            gpsLastLocation = null;
+        }
+    }
+
+    public void setGpsParametersResponse(GpsAndTime.GpsParameters.Response response) {
+        this.gpsParametersResponse = response;
+    }
+
+    public void setup(GBDevice device, Context context) {
+        this.gbDevice = device;
+        this.context = context;
+        this.huaweiType = getCoordinator().getHuaweiType();
+        this.paramsProvider.setTransactionsCrypted(this.getHuaweiCoordinator().isTransactionCrypted());
+    }
+
+    protected nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder initializeDevice(nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder builder) {
+        setup(leSupport.getDevice(), leSupport.getContext());
+        builder.setCallback(leSupport);
+        final BluetoothGattCharacteristic characteristicRead = leSupport.getCharacteristic(HuaweiConstants.UUID_CHARACTERISTIC_HUAWEI_READ);
+        if (characteristicRead == null) {
+            LOG.warn("Read characteristic is null, will attempt to reconnect");
+            builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.WAITING_FOR_RECONNECT, getContext()));
+            return builder;
+        }
+        builder.notify(characteristicRead, true);
+        builder.add(new nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction(getDevice(), GBDevice.State.AUTHENTICATING, getContext()));
+        final GetLinkParamsRequest linkParamsReq = new GetLinkParamsRequest(this, builder);
+        initializeDevice(linkParamsReq);
+        getCoordinator().setDevice(this.gbDevice);
+        return builder;
+    }
+
+    protected nodomain.freeyourgadget.gadgetbridge.service.btbr.TransactionBuilder initializeDevice(nodomain.freeyourgadget.gadgetbridge.service.btbr.TransactionBuilder builder) {
+        setup(brSupport.getDevice(), brSupport.getContext());
+        builder.setCallback(brSupport);
+        builder.add(new nodomain.freeyourgadget.gadgetbridge.service.btbr.actions.SetDeviceStateAction(getDevice(), GBDevice.State.AUTHENTICATING, getContext()));
+        final GetLinkParamsRequest linkParamsReq = new GetLinkParamsRequest(this, builder);
+        initializeDevice(linkParamsReq);
+        getCoordinator().setDevice(this.gbDevice);
+        return builder;
+    }
+
+    protected void initializeDevice(final Request linkParamsReq) {
+        deviceMac = this.gbDevice.getAddress();
+        createRandomMacAddress();
+        createAndroidID();
+        try {
+            RequestCallback finalizeReq = new RequestCallback() {
+                @Override
+                public void call() {
+                    initializeDeviceCheckStatus(linkParamsReq);
+                }
+
+                @Override
+                public void handleException(Request.ResponseParseException e) {
+                    LOG.error("Link params TLV exception", e);
+                }
+            };
+            linkParamsReq.setFinalizeReq(finalizeReq);
+            linkParamsReq.doPerform();
+        } catch (IOException e) {
+            GB.toast(context, "Initialization of authenticating to Huawei device failed", Toast.LENGTH_SHORT, GB.ERROR, e);
+            LOG.error("Initialization of authenticating to Huawei device failed", e);
+        }
+
+        /* This is to have the setting match the default Huawei behaviour */
+        SharedPreferences sharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(getDeviceMac());
+        if (!sharedPrefs.contains(DeviceSettingsPreferenceConst.PREF_DISCONNECTNOTIF_NOSHED)) {
+            sharedPrefs.edit().putBoolean(DeviceSettingsPreferenceConst.PREF_DISCONNECTNOTIF_NOSHED, true).apply();
+        }
+    }
+
+    protected void initializeDeviceCheckStatus(final Request linkParamsReq) {
+        try {
+            final GetDeviceStatusRequest deviceStatusReq = new GetDeviceStatusRequest(this, true);
+            RequestCallback finalizeReq = new RequestCallback() {
+                @Override
+                public void call() {
+                    int status = deviceStatusReq.status;
+                    if (status == -0x01 || status == 0x00 || status == 0x01) {
+                        initializeDeviceDealHiChain(linkParamsReq);
+                    } else {
+                        initializeDeviceNotify();
+                    }
+                }
+
+                @Override
+                public void handleException(Request.ResponseParseException e) {
+                    LOG.error("Status TLV exception", e);
+                }
+            };
+            if (huaweiType == HuaweiDeviceType.BLE) { //Only BLE known, check later for AW and SMART
+                initializeDeviceDealHiChain(linkParamsReq);
+            } else {
+                deviceStatusReq.setFinalizeReq(finalizeReq);
+                deviceStatusReq.doPerform();
+            }
+        } catch (IOException e) {
+            GB.toast(context, "Status of authenticating to Huawei device failed", Toast.LENGTH_SHORT, GB.ERROR, e);
+            LOG.error("Status of authenticating to Huawei device failed", e);
+        }
+    }
+
+    protected boolean isHiChain() {
+        // In HH
+        // HiChain : 1 || 3
+        // HiChainLite : 2 || 3 || 8
+        // HiChain3 : 4 & API>=23 - API is always >=23
+        // For GB we will consider for authMode
+        // 0 : No HiChain
+        // 1 or 3 : HiChain
+        // 2 or 8 : HiChainLite -> normal mode
+        // 4 : HiChain3
+        byte authMode = paramsProvider.getDeviceSupportType();
+        return authMode == 0x01 || authMode == 0x03 || authMode == 0x04 || isHiChainLite();
+    }
+
+    protected boolean isHiChainLite() {
+        byte authMode = paramsProvider.getDeviceSupportType();
+        return authMode == 0x02;
+    }
+
+    protected boolean isHiChain3(int authType) {
+        return (authType ^ 0x01) == 0x04 || (authType ^ 0x02) == 0x04;
+    }
+
+    protected void initializeDeviceDealHiChain(final Request linkParamsReq) {
+        try {
+            if (isHiChain()) {
+
+                if (paramsProvider.getDeviceSupportType() == 4)
+                    paramsProvider.setAuthMode((byte) 4);
+                else
+                    paramsProvider.setAuthMode((byte) 2);
+                final GetSecurityNegotiationRequest securityNegoReq = new GetSecurityNegotiationRequest(this);
+                RequestCallback securityFinalizeReq = new RequestCallback(this) {
+                    @Override
+                    public void call() {
+                        if (securityNegoReq.authType == 0x0186A0 || isHiChain3(securityNegoReq.authType)) {
+                            LOG.debug("HiChain mode");
+                            initializeDeviceHiChainMode();
+                        } else if (securityNegoReq.authType == 0x01 || securityNegoReq.authType == 0x02) {
+                            LOG.debug("HiChain Lite mode");
+                            // Keep track the gadget is connected
+                            initializeDeviceHiChainLiteMode(linkParamsReq);
+                        }
+                    }
+                };
+                securityNegoReq.setFinalizeReq(securityFinalizeReq);
+                securityNegoReq.doPerform();
+            } else {
+                LOG.debug("Normal mode");
+                initializeDeviceNormalMode(linkParamsReq);
+            }
+        } catch (IOException e) {
+            GB.toast(context, "Init deal with HiChain of Huawei device failed", Toast.LENGTH_SHORT, GB.ERROR, e);
+            LOG.error("Init deal with HiChain of Huawei device failed", e);
+        }
+    }
+
+    protected void initializeDeviceNotify() {
+        // TODO: Implement
+    }
+
+    RequestCallback configureReq = new RequestCallback() {
+        @Override
+        public void call() {
+            initializeDeviceConfigure();
+        }
+
+        @Override
+        public void timeout(Request request) {
+            LOG.error("Authentication timed out");
+            GB.toast(context, R.string.authentication_failed_negotiation, Toast.LENGTH_LONG, GB.ERROR);
+            // Disconnect as no communication can succeed after this point
+            final GBDevice device = getDevice();
+            if (device != null) {
+                GBApplication.deviceService(device).disconnect();
+            }
+        }
+
+        @Override
+        public void handleException(Request.ResponseParseException e) {
+            LOG.error("Authentication exception", e);
+            GB.toast(context, R.string.authentication_failed_negotiation, Toast.LENGTH_LONG, GB.ERROR);
+            // Disconnect as no communication can succeed after this point
+            final GBDevice device = getDevice();
+            if (device != null) {
+                GBApplication.deviceService(device).disconnect();
+            }
+        }
+    };
+
+    protected void initializeDeviceHiChainMode() {
+        try {
+            GetHiChainRequest hiChainReq = new GetHiChainRequest(this, firstConnection);
+            hiChainReq.setFinalizeReq(configureReq);
+            if (firstConnection) {
+                GetPincodeRequest pincodeReq = new GetPincodeRequest(this);
+                pincodeReq.nextRequest(hiChainReq);
+                pincodeReq.doPerform();
+            } else
+                hiChainReq.doPerform();
+        } catch (IOException e) {
+            GB.toast(context, "HiChain Mode init of Huawei device failed", Toast.LENGTH_SHORT, GB.ERROR, e);
+            LOG.error("HiChain Mode init of Huawei device failed", e);
+        }
+    }
+
+    protected void initializeDeviceHiChainLiteMode(Request linkParamsReq) {
+        try {
+            createSecretKey();
+            GetAuthRequest authReq = new GetAuthRequest(this, linkParamsReq);
+            GetBondParamsRequest bondParamsReq = new GetBondParamsRequest(this);
+            GetBondRequest bondReq = new GetBondRequest(this);
+            authReq.nextRequest(bondParamsReq);
+            bondParamsReq.nextRequest(bondReq);
+            bondParamsReq.setFinalizeReq(configureReq);
+            bondReq.setFinalizeReq(configureReq);
+            if (paramsProvider.getPinCode() == null & paramsProvider.getAuthVersion() != 0x02) {
+                GetPincodeRequest pinCodeReq = new GetPincodeRequest(this);
+                pinCodeReq.nextRequest(authReq);
+                pinCodeReq.doPerform();
+            } else {
+                authReq.doPerform();
+            }
+        } catch (IOException e) {
+            GB.toast(context, "HiChainLite mode init of Huawei device failed", Toast.LENGTH_SHORT, GB.ERROR, e);
+            LOG.error("HiChainLite mode init of Huawei device failed", e);
+        }
+    }
+
+    protected void initializeDeviceNormalMode(Request linkParamsReq) {
+        try {
+            createSecretKey();
+            GetAuthRequest authReq = new GetAuthRequest(this, linkParamsReq);
+            if (getHuaweiType() == HuaweiDeviceType.BLE || getHuaweiType() == HuaweiDeviceType.AW) {
+                GetBondParamsRequest bondParamsReq = new GetBondParamsRequest(this);
+                GetBondRequest bondReq = new GetBondRequest(this);
+                authReq.nextRequest(bondParamsReq);
+                bondParamsReq.nextRequest(bondReq);
+                bondParamsReq.setFinalizeReq(configureReq);
+                bondReq.setFinalizeReq(configureReq);
+            } else {
+                authReq.setFinalizeReq(configureReq);
+            }
+            authReq.doPerform();
+        } catch (IOException e) {
+            GB.toast(context, "Normal mode init of Huawei device failed", Toast.LENGTH_SHORT, GB.ERROR, e);
+            LOG.error("Normal mode init of Huawei device failed", e);
+        }
+    }
+
+    protected void initializeDeviceConfigure() {
+        if (isBLE()) {
+            nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder leBuilder = createLeTransactionBuilder("Initializing");
+            leBuilder.setCallback(leSupport);
+            if (!GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getBoolean("force_new_protocol", false))
+                leBuilder.notify(leSupport.getCharacteristic(HuaweiConstants.UUID_CHARACTERISTIC_HUAWEI_READ), true);
+            leBuilder.add(new nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction(gbDevice, GBDevice.State.INITIALIZING, context));
+        } else {
+            nodomain.freeyourgadget.gadgetbridge.service.btbr.TransactionBuilder brBuilder = createBrTransactionBuilder("Initializing");
+            brBuilder.setCallback(brSupport);
+            brBuilder.add(new nodomain.freeyourgadget.gadgetbridge.service.btbr.actions.SetDeviceStateAction(gbDevice, GBDevice.State.INITIALIZING, context));
+        }
+        try {
+            if (firstConnection) {
+                // Workaround to enable PREF_HUAWEI_ROTATE_WRIST_TO_SWITCH_INFO preference
+                SharedPreferences sharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(deviceMac);
+                SharedPreferences.Editor editor = sharedPrefs.edit();
+                editor.putString(DeviceSettingsPreferenceConst.PREF_ACTIVATE_DISPLAY_ON_LIFT, "p_on");
+                editor.apply();
+            }
+
+            huaweiP2PManager.unregisterAllService();
+            stopBatteryRunnerDelayed();
+            GetBatteryLevelRequest batteryLevelReq = new GetBatteryLevelRequest(this);
+            batteryLevelReq.setFinalizeReq(new RequestCallback() {
+                @Override
+                public void timeout(Request request) {
+                    request.handleNext();
+                    // Start the battery runner again so it keeps running even if the timeout is hit
+                    startBatteryRunnerDelayed();
+                }
+            });
+
+            final List<Request> initRequestQueue = new ArrayList<>();
+            initRequestQueue.add(new GetProductInformationRequest(this));
+            initRequestQueue.add(new SetTimeRequest(this, true));
+            initRequestQueue.add(batteryLevelReq);
+            initRequestQueue.add(new GetSupportedServicesRequest(this)); // MUST BE LAST - it indirectly kicks off initializeDynamicServices
+
+            // Queue all the requests
+            for (int i = 1; i < initRequestQueue.size(); i++) {
+                initRequestQueue.get(i - 1).setupTimeoutUntilNext(initTimeout);
+                initRequestQueue.get(i - 1).nextRequest(initRequestQueue.get(i));
+            }
+            initRequestQueue.get(initRequestQueue.size() - 1).setupTimeoutUntilNext(initTimeout);
+
+            initRequestQueue.get(0).doPerform();
+        } catch (IOException e) {
+            GB.toast(context, "Final initialization of Huawei device failed", Toast.LENGTH_SHORT, GB.ERROR, e);
+            LOG.error("Final initialization of Huawei device failed", e);
+        }
+    }
+
+    public void createSecretKey() {
+        SharedPreferences sharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(deviceMac);
+
+        String authKey = sharedPrefs.getString("authkey", null);
+        if (authKey == null || authKey.isEmpty()) {
+            SharedPreferences.Editor editor = sharedPrefs.edit();
+
+            authKey = StringUtils.bytesToHex(HuaweiCrypto.generateNonce());
+            editor.putString("authkey", authKey);
+            editor.apply();
+        }
+        paramsProvider.setSecretKey(GB.hexStringToByteArray(authKey));
+    }
+
+    public byte[] getSecretKey() {
+        SharedPreferences sharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(deviceMac);
+
+        String authKey = sharedPrefs.getString("authkey", null);
+
+        // TODO: Handle null key - maybe error out of the entire connection?
+
+        return GB.hexStringToByteArray(authKey);
+    }
+
+    public void setSecretKey(byte[] authKey) {
+        SharedPreferences sharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(deviceMac);
+
+        SharedPreferences.Editor editor = sharedPrefs.edit();
+
+        editor.putString("authkey", StringUtils.bytesToHex(authKey));
+        editor.apply();
+        paramsProvider.setSecretKey(authKey);
+    }
+
+    public HuaweiCoordinatorSupplier.HuaweiDeviceType getHuaweiType() {
+        return this.huaweiType;
+    }
+
+    public HuaweiPacket.ParamsProvider getParamsProvider() {
+        return paramsProvider;
+    }
+
+    public void setFirstConnection(boolean firstConnection) {
+        this.firstConnection = firstConnection;
+    }
+
+    protected void createRandomMacAddress() {
+        SharedPreferences sharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(deviceMac);
+
+        macAddress = sharedPrefs.getString(HuaweiConstants.PREF_HUAWEI_ADDRESS, null);
+        if (macAddress == null || macAddress.isEmpty()) {
+            StringBuilder mac = new StringBuilder("FF:FF:FF");
+            Random r = new Random();
+            for (int i = 0; i < 3; i++) {
+                int n = r.nextInt(255);
+                mac.append(String.format(":%02x", n));
+            }
+            macAddress = mac.toString().toUpperCase();
+            SharedPreferences.Editor editor = sharedPrefs.edit();
+            editor.putString(HuaweiConstants.PREF_HUAWEI_ADDRESS, macAddress);
+            editor.apply();
+        }
+    }
+
+    public byte[] getMacAddress() {
+        return macAddress.getBytes(StandardCharsets.UTF_8);
+    }
+
+    public byte[] getSerial() {
+        return macAddress.replace(":", "").substring(6, 12).getBytes(StandardCharsets.UTF_8);
+    }
+
+    public String getDeviceMac() {
+        return deviceMac;
+    }
+
+    protected void createAndroidID() {
+        SharedPreferences sharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(deviceMac);
+
+        androidID = sharedPrefs.getString(DeviceSettingsPreferenceConst.PREF_FAKE_ANDROID_ID, null);
+        if (androidID == null || androidID.isEmpty()) {
+            androidID = StringUtils.bytesToHex(HuaweiCrypto.generateNonce());
+            LOG.debug("Created androidID: {}", androidID);
+            SharedPreferences.Editor editor = sharedPrefs.edit();
+            editor.putString(DeviceSettingsPreferenceConst.PREF_FAKE_ANDROID_ID, androidID);
+            editor.apply();
+        }
+    }
+
+    public byte[] getAndroidId() {
+        return androidID.getBytes(StandardCharsets.UTF_8);
+    }
+
+    public Context getContext() {
+        return context;
+    }
+
+    public GBDevice getDevice() {
+        return gbDevice;
+    }
+
+    /**
+     * Initialize the services that may or may not be supported on the device
+     * To be called after the commandsPerService is filled in the coordinator
+     */
+    public void initializeDynamicServices() {
+        try {
+            // All of the below check that they are supported and otherwise they skip themselves
+            final List<Request> initRequestQueue = new ArrayList<>();
+            initRequestQueue.add(new GetExpandCapabilityRequest(this));
+            initRequestQueue.add(new SendExtendedAccountRequest(this));
+            initRequestQueue.add(new GetSettingRelatedRequest(this));
+            initRequestQueue.add(new AcceptAgreementsRequest(this));
+            initRequestQueue.add(new GetActivityTypeRequest(this));
+            initRequestQueue.add(new GetConnectStatusRequest(this));
+            initRequestQueue.add(new GetDndLiftWristTypeRequest(this));
+            initRequestQueue.add(new SendDndDeleteRequest(this));
+            initRequestQueue.add(new SendDndAddRequest(this));
+            initRequestQueue.add(new SendSetUpDeviceStatusRequest(this));
+            initRequestQueue.add(new GetWearStatusRequest(this));
+            initRequestQueue.add(new SendMenstrualCapabilityRequest(this));
+            initRequestQueue.add(new SendNotifyHeartRateCapabilityRequest(this));
+            initRequestQueue.add(new SendNotifyRestHeartRateCapabilityRequest(this));
+            initRequestQueue.add(new SendFitnessUserInfoRequest(this));
+            initRequestQueue.add(new SendRunPaceConfigRequest(this));
+            initRequestQueue.add(new SendDeviceReportThreshold(this));
+            initRequestQueue.add(new SendHeartRateZonesConfig(this));
+            initRequestQueue.add(new SetMediumToStrengthThresholdRequest(this));
+            initRequestQueue.add(new SendFitnessGoalRequest(this));
+            initRequestQueue.add(new GetNotificationCapabilitiesRequest(this));
+            initRequestQueue.add(new GetNotificationConstraintsRequest(this));
+            initRequestQueue.add(new GetWatchfaceParams(this));
+            initRequestQueue.add(new SendCameraRemoteSetupEvent(this, CameraRemote.CameraRemoteSetup.Request.Event.ENABLE_CAMERA));
+            initRequestQueue.add(new GetAppInfoParams(this));
+            initRequestQueue.add(new GetMusicInfoParams(this));
+            initRequestQueue.add(new GetExtendedMusicInfoParams(this));
+            initRequestQueue.add(new SetActivateOnLiftRequest(this));
+            initRequestQueue.add(new SetWearLocationRequest(this));
+            initRequestQueue.add(new SetNavigateOnRotateRequest(this));
+            initRequestQueue.add(new SetNotificationRequest(this));
+            initRequestQueue.add(new SetWearMessagePushRequest(this));
+            initRequestQueue.add(new SetTimeZoneIdRequest(this));
+            initRequestQueue.add(new SetLanguageSettingRequest(this));
+            initRequestQueue.add(new SetDateFormatRequest(this));
+            initRequestQueue.add(new SetActivityReminderRequest(this));
+            initRequestQueue.add(new SetTruSleepRequest(this));
+            initRequestQueue.add(new GetContactsCount(this));
+            initRequestQueue.add(new GetEventAlarmList(this));
+            initRequestQueue.add(new GetSmartAlarmList(this));
+
+            // Setup the alarms if necessary
+            if (!getHuaweiCoordinator().supportsChangingAlarm() && firstConnection)
+                initializeAlarms();
+
+            // Queue all the requests
+            for (int i = 1; i < initRequestQueue.size(); i++) {
+                initRequestQueue.get(i - 1).setupTimeoutUntilNext(initTimeout);
+                if (initRequestQueue.get(i - 1) instanceof SendSetUpDeviceStatusRequest) {
+                    // NOTE: The watch is never answer to this command. To decrease init time timeout for it is 50 ms
+                    initRequestQueue.get(i - 1).setupTimeoutUntilNext(50);
+                }
+                initRequestQueue.get(i - 1).nextRequest(initRequestQueue.get(i));
+            }
+
+            initRequestQueue.get(initRequestQueue.size() - 1).setupTimeoutUntilNext(initTimeout);
+            initRequestQueue.get(initRequestQueue.size() - 1).setFinalizeReq(new RequestCallback() {
+                @Override
+                public void call() {
+                    gbDevice.setState(GBDevice.State.INITIALIZED);
+                    gbDevice.sendDeviceUpdateIntent(getContext(), GBDevice.DeviceUpdateSubject.DEVICE_STATE);
+
+                    if (getHuaweiCoordinator().supportsP2PService()) {
+                        if (getHuaweiCoordinator().supportsCalendar()) {
+                            if (HuaweiP2PCalendarService.getRegisteredInstance(huaweiP2PManager) == null) {
+                                HuaweiP2PCalendarService calendarService = new HuaweiP2PCalendarService(huaweiP2PManager);
+                                calendarService.register();
+                            }
+                        }
+                        if (getHuaweiCoordinator().supportsTrack()) {
+                            if (HuaweiP2PTrackService.getRegisteredInstance(huaweiP2PManager) == null) {
+                                HuaweiP2PTrackService trackService = new HuaweiP2PTrackService(huaweiP2PManager);
+                                trackService.register();
+                            }
+                        }
+                        if (HuaweiP2PDataDictionarySyncService.getRegisteredInstance(huaweiP2PManager) == null) {
+                            HuaweiP2PDataDictionarySyncService trackService = new HuaweiP2PDataDictionarySyncService(huaweiP2PManager);
+                            trackService.register();
+                        }
+
+                    }
+                }
+            });
+
+            initRequestQueue.get(0).doPerform();
+        } catch (IOException e) {
+            GB.toast("Initialize dynamic services of Huawei device failed", Toast.LENGTH_SHORT, GB.ERROR, e);
+            LOG.error("Initializing dynamic services of Huawei device failed", e);
+        }
+    }
+
+    public void setProtocolVersion(byte protocolVersion) {
+        this.protocolVersion = protocolVersion;
+    }
+
+    public byte getProtocolVersion() {
+        return this.protocolVersion;
+    }
+
+    private void initializeAlarms() {
+        // TODO: check for smart alarm && overwrite for smart alarm
+        //       note that lowering the alarm count shouldn't delete the alarm of course...
+
+        // Populate alarms in order to specify important data
+        List<Alarm> alarms = DBHelper.getAlarms(gbDevice);
+        DeviceCoordinator coordinator = this.gbDevice.getDeviceCoordinator();
+        int supportedNumAlarms = coordinator.getAlarmSlotCount(gbDevice);
+        if (alarms.isEmpty()) {
+            try (DBHandler db = GBApplication.acquireDB()) {
+                DaoSession daoSession = db.getDaoSession();
+                Device device = DBHelper.getDevice(gbDevice, daoSession);
+                User user = DBHelper.getUser(daoSession);
+                for (int position = 0; position < supportedNumAlarms; position++) {
+                    LOG.info("Adding missing alarm at position {}", position);
+                    DBHelper.store(createDefaultAlarm(device, user, position));
+                }
+            } catch (Exception e) {
+                // TODO: show user?
+                // TODO: What exceptions can happen here?
+                LOG.error("Error accessing database", e);
+            }
+        }
+    }
+
+    private Alarm createDefaultAlarm(@NonNull Device device, @NonNull User user, int position) {
+        boolean smartWakeup = false;
+        String title = context.getString(R.string.menuitem_alarm);
+        String description = context.getString(R.string.huawei_alarm_event_description);
+        if (position == 0) {
+            smartWakeup = true;
+            title = context.getString(R.string.alarm_smart_wakeup);
+            description = context.getString(R.string.huawei_alarm_smart_description);
+        }
+        return new Alarm(device.getId(), user.getId(), position, false, smartWakeup, null, false, 0, 6, 30, true, title, description);
+    }
+
+    private void getAlarms() {
+        if (!getHuaweiCoordinator().supportsChangingAlarm())
+            return;
+
+        GetEventAlarmList getEventAlarmList = new GetEventAlarmList(this);
+        getEventAlarmList.setFinalizeReq(new RequestCallback() {
+            @Override
+            public void call() {
+                if (!getHuaweiCoordinator().supportsSmartAlarm(getDevice()))
+                    return; // Don't get smart alarms when not supported
+
+                GetSmartAlarmList getSmartAlarmList = new GetSmartAlarmList(HuaweiSupportProvider.this);
+                try {
+                    getSmartAlarmList.doPerform();
+                } catch (IOException e) {
+                    GB.toast(context, "Error sending smart alarm list request", Toast.LENGTH_SHORT, GB.ERROR, e);
+                    LOG.error("Error sending smart alarm list request", e);
+                }
+            }
+
+            @Override
+            public void handleException(Request.ResponseParseException e) {
+                GB.toast(context, "Error parsing event list", Toast.LENGTH_SHORT, GB.ERROR, e);
+                LOG.error("Error parsing event list", e);
+            }
+        });
+        try {
+            getEventAlarmList.doPerform();
+        } catch (IOException e) {
+            GB.toast(context, "Error sending event alarm list request", Toast.LENGTH_SHORT, GB.ERROR, e);
+            LOG.error("Error sending event alarm list request", e);
+        }
+    }
+
+    public void saveAlarms(Alarm[] alarms) {
+        try (DBHandler db = GBApplication.acquireDB()) {
+            DaoSession daoSession = db.getDaoSession();
+            Device device = DBHelper.getDevice(gbDevice, daoSession);
+            User user = DBHelper.getUser(daoSession);
+            for (Alarm alarm : alarms) {
+                alarm.setDeviceId(device.getId());
+                alarm.setUserId(user.getId());
+                DBHelper.store(alarm);
+            }
+        } catch (Exception e) {
+            GB.toast(context, "Error saving alarms", Toast.LENGTH_SHORT, GB.ERROR, e);
+            LOG.error("Error saving alarms", e);
+        }
+    }
+
+    public boolean onCharacteristicChanged(BluetoothGattCharacteristic characteristic) {
+        byte[] data = characteristic.getValue();
+        responseManager.handleData(data);
+        return true;
+    }
+
+    public void onSocketRead(byte[] data) {
+        responseManager.handleData(data);
+    }
+
+    public void removeInProgressRequests(Request req) {
+        responseManager.removeHandler(req);
+    }
+
+    public void onSendConfiguration(String config) {
+        try {
+            switch (config) {
+                case DeviceSettingsPreferenceConst.PREF_DATEFORMAT:
+                case DeviceSettingsPreferenceConst.PREF_TIMEFORMAT: {
+                    setDateFormat();
+                    break;
+                }
+                case SettingsActivity.PREF_MEASUREMENT_SYSTEM:
+                case DeviceSettingsPreferenceConst.PREF_LANGUAGE: {
+                    setLanguageSetting();
+                    break;
+                }
+                case DeviceSettingsPreferenceConst.PREF_WEARLOCATION: {
+                    setWearLocation();
+                    break;
+                }
+                case DeviceSettingsPreferenceConst.PREF_LIFTWRIST_NOSHED: {
+                    setActivateOnLift();
+                    break;
+                }
+                case MiBandConst.PREF_MI2_ROTATE_WRIST_TO_SWITCH_INFO: {
+                    setNavigateOnRotate();
+                    break;
+                }
+                case DeviceSettingsPreferenceConst.PREF_INACTIVITY_ENABLE:
+                case DeviceSettingsPreferenceConst.PREF_INACTIVITY_THRESHOLD:
+                case DeviceSettingsPreferenceConst.PREF_INACTIVITY_START:
+                case DeviceSettingsPreferenceConst.PREF_INACTIVITY_END:
+                case DeviceSettingsPreferenceConst.PREF_INACTIVITY_MO:
+                case DeviceSettingsPreferenceConst.PREF_INACTIVITY_TU:
+                case DeviceSettingsPreferenceConst.PREF_INACTIVITY_WE:
+                case DeviceSettingsPreferenceConst.PREF_INACTIVITY_TH:
+                case DeviceSettingsPreferenceConst.PREF_INACTIVITY_FR:
+                case DeviceSettingsPreferenceConst.PREF_INACTIVITY_SA:
+                case DeviceSettingsPreferenceConst.PREF_INACTIVITY_SU: {
+                    setActivityReminder();
+                    break;
+                }
+                case HuaweiConstants.PREF_HUAWEI_TRUSLEEP: {
+                    setTrusleep();
+                    break;
+                }
+                case HuaweiConstants.PREF_HUAWEI_CONTINUOUS_SKIN_TEMPERATURE_MEASUREMENT: {
+                    setContinuousSkinTemperatureMeasurement();
+                    break;
+                }
+                case DeviceSettingsPreferenceConst.PREF_TEMPERATURE_SCALE_CF: {
+                    setTemperatureUnit();
+                    break;
+                }
+                case DeviceSettingsPreferenceConst.PREF_NOTIFICATION_ENABLE: {
+                    setNotificationStatus();
+                    break;
+                }
+                case HuaweiConstants.PREF_HUAWEI_WORKMODE:
+                    SetWorkModeRequest setWorkModeReq = new SetWorkModeRequest(this);
+                    setWorkModeReq.doPerform();
+                    break;
+                case DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB:
+                case DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_START:
+                case DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_END:
+                case DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_LIFT_WRIST:
+                case DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_MO:
+                case DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_TU:
+                case DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_WE:
+                case DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_TH:
+                case DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_FR:
+                case DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_SA:
+                case DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_SU: {
+                    setDnd();
+                    break;
+                }
+                case DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_NOT_WEAR:
+                    setDndNotWear();
+                    break;
+                case DeviceSettingsPreferenceConst.PREF_FIND_PHONE:
+                case DeviceSettingsPreferenceConst.PREF_FIND_PHONE_DURATION:
+                    // TODO: enable/disable the find phone applet on band
+                    break;
+                case DeviceSettingsPreferenceConst.PREF_DISCONNECTNOTIF_NOSHED:
+                    setDisconnectNotification();
+                    break;
+                case DeviceSettingsPreferenceConst.PREF_HEARTRATE_AUTOMATIC_ENABLE:
+                    setHeartrateAutomatic();
+                    break;
+                case DeviceSettingsPreferenceConst.PREF_SPO_AUTOMATIC_ENABLE:
+                    setSpoAutomatic();
+                    break;
+                case DeviceSettingsPreferenceConst.PREF_FORCE_ENABLE_SMART_ALARM:
+                    getAlarms();
+                    break;
+                case HuaweiConstants.PREF_HUAWEI_DEBUG_REQUEST:
+                    sendDebugRequest();
+                    break;
+                case ActivityUser.PREF_USER_STEPS_GOAL:
+                    new SendFitnessGoalRequest(this).doPerform();
+                    break;
+                case DeviceSettingsPreferenceConst.PREF_CAMERA_REMOTE:
+                    if (GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getBoolean(DeviceSettingsPreferenceConst.PREF_CAMERA_REMOTE, false)) {
+                        SendCameraRemoteSetupEvent sendCameraRemoteSetupEvent = new SendCameraRemoteSetupEvent(this, CameraRemote.CameraRemoteSetup.Request.Event.ENABLE_CAMERA);
+                        sendCameraRemoteSetupEvent.doPerform();
+                    } else {
+                        // Somehow it is impossible to disable the camera remote
+                        // But it will disappear after reconnection - until it is enabled again
+                        GB.toast(context, context.getString(R.string.toast_setting_requires_reconnect), Toast.LENGTH_SHORT, GB.INFO);
+                    }
+                case DeviceSettingsPreferenceConst.PREF_BATTERY_POLLING_ENABLE:
+                    if (!GBApplication.getDevicePrefs(gbDevice).getBatteryPollingEnabled()) {
+                        stopBatteryRunnerDelayed();
+                        break;
+                    }
+                    // Fall through if enabled
+                case DeviceSettingsPreferenceConst.PREF_BATTERY_POLLING_INTERVAL:
+                    if (!startBatteryRunnerDelayed()) {
+                        GB.toast(context, R.string.battery_polling_failed_start, Toast.LENGTH_SHORT, GB.ERROR);
+                        LOG.error("Failed to start the battery polling");
+                    }
+                    break;
+                case ActivityUser.PREF_USER_WEIGHT_KG:
+                case ActivityUser.PREF_USER_HEIGHT_CM:
+                case ActivityUser.PREF_USER_GENDER:
+                case ActivityUser.PREF_USER_DATE_OF_BIRTH:
+                    sendUserInfo();
+                    break;
+                case DeviceSettingsPreferenceConst.PREF_SYNC_CALENDAR:
+                case DeviceSettingsPreferenceConst.PREF_CALENDAR_LOOKAHEAD_DAYS:
+                    HuaweiP2PCalendarService.getRegisteredInstance(huaweiP2PManager).restartSynchronization();
+                    break;
+            }
+        } catch (IOException e) {
+            GB.toast(context, "Configuration of Huawei device failed", Toast.LENGTH_SHORT, GB.ERROR, e);
+            LOG.error("Configuration of Huawei device failed", e);
+        }
+    }
+
+    public void onFetchRecordedData(int dataTypes) {
+        for (int i = 1; i > -1; i <<= 1) {
+            if ((dataTypes & i) != 0) {
+                switch (i) {
+                    case RecordedDataTypes.TYPE_ACTIVITY:
+                        this.syncState.addActivitySyncToQueue();
+                        break;
+                    case RecordedDataTypes.TYPE_GPS_TRACKS:
+                        this.syncState.addWorkoutSyncToQueue();
+                        break;
+                    // Ignore the following because we know/they are included in the others
+                    case RecordedDataTypes.TYPE_SPO2:
+                    case RecordedDataTypes.TYPE_STRESS:
+                    case RecordedDataTypes.TYPE_HEART_RATE:
+                    case RecordedDataTypes.TYPE_PAI:
+                    case RecordedDataTypes.TYPE_SLEEP_RESPIRATORY_RATE:
+                        break;
+                    default:
+                        LOG.warn("Recorded data type {} not implemented yet.", i);
+                }
+            }
+        }
+        if (gbDevice.isBusy())
+            LOG.warn("Device is already busy with {}, so won't fetch data now.", gbDevice.getBusyTask());
+        else
+            fetchRecodedDataFromQueue();
+
+        // Get the battery level as well
+        getBatteryLevel();
+
+        // Get the alarms as they cannot be retrieved on opening the alarm window
+        // TODO: get the alarms if the alarm settings are opened instead of here
+        getAlarms();
+    }
+
+    protected void fetchRecodedDataFromQueue() {
+        int dataType = this.syncState.getCurrentSyncType();
+        if (dataType == -1)
+            return; // Empty queue
+
+        if (dataType == RecordedDataTypes.TYPE_ACTIVITY) {
+            fetchActivityData();
+        } else if (dataType == RecordedDataTypes.TYPE_GPS_TRACKS) {
+            fetchWorkoutData();
+        }
+    }
+
+    private void fetchActivityData() {
+        syncState.setActivitySync(true);
+        fetchActivityDataP2P();
+
+        int sleepStart = 0;
+        int stepStart = 0;
+        final int end = (int) (System.currentTimeMillis() / 1000);
+
+        SharedPreferences sharedPreferences = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress());
+        long prefLastSyncTime = sharedPreferences.getLong("lastSyncTimeMillis", 0);
+        if (prefLastSyncTime != 0) {
+            sleepStart = (int) (prefLastSyncTime / 1000);
+            stepStart = (int) (prefLastSyncTime / 1000);
+
+            // Reset for next calls
+            sharedPreferences.edit().putLong("lastSyncTimeMillis", 0).apply();
+        } else {
+            try (DBHandler db = GBApplication.acquireDB()) {
+                HuaweiSampleProvider sampleProvider = new HuaweiSampleProvider(gbDevice, db.getDaoSession());
+                sleepStart = sampleProvider.getLastSleepFetchTimestamp();
+                stepStart = sampleProvider.getLastStepFetchTimestamp();
+            } catch (Exception e) {
+                LOG.warn("Exception for getting start times, using 01/01/2000 - 00:00:00.");
+            }
+
+            // Some bands don't work with zero timestamp, so starting later
+            if (sleepStart == 0)
+                sleepStart = 946684800;
+            if (stepStart == 0)
+                stepStart = 946684800;
+        }
+        final GetSleepDataCountRequest getSleepDataCountRequest;
+        if (isBLE()) {
+            nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder leBuilder = createLeTransactionBuilder("FetchRecordedData");
+            leBuilder.add(new nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceBusyAction(gbDevice, context.getString(R.string.busy_task_fetch_activity_data), context));
+            getSleepDataCountRequest = new GetSleepDataCountRequest(this, leBuilder, sleepStart, end);
+        } else {
+            nodomain.freeyourgadget.gadgetbridge.service.btbr.TransactionBuilder brBuilder = createBrTransactionBuilder("FetchRecordedData");
+            brBuilder.add(new nodomain.freeyourgadget.gadgetbridge.service.btbr.actions.SetDeviceBusyAction(gbDevice, context.getString(R.string.busy_task_fetch_activity_data), context));
+            getSleepDataCountRequest = new GetSleepDataCountRequest(this, brBuilder, sleepStart, end);
+        }
+
+        final GetStepDataCountRequest getStepDataCountRequest = new GetStepDataCountRequest(this, stepStart, end);
+        //noinspection ExtractMethodRecommender
+        final GetFitnessTotalsRequest getFitnessTotalsRequest = new GetFitnessTotalsRequest(this);
+
+        final int start = sleepStart;
+        getFitnessTotalsRequest.setFinalizeReq(new RequestCallback() {
+            @Override
+            public void call() {
+                if (!downloadTruSleepData(start, end))
+                    syncState.setActivitySync(false);
+            }
+
+            @Override
+            public void handleException(Request.ResponseParseException e) {
+                LOG.error("Fitness totals exception", e);
+                syncState.setActivitySync(false);
+            }
+        });
+
+
+        getStepDataCountRequest.setFinalizeReq(new RequestCallback() {
+            @Override
+            public void call() {
+                try {
+                    getFitnessTotalsRequest.doPerform();
+                } catch (IOException e) {
+                    LOG.error("Exception on starting fitness totals request", e);
+                    syncState.setActivitySync(false);
+                }
+            }
+
+            @Override
+            public void handleException(Request.ResponseParseException e) {
+                LOG.error("Step data count exception", e);
+                syncState.setActivitySync(false);
+            }
+        });
+
+        getSleepDataCountRequest.setFinalizeReq(new RequestCallback() {
+            @Override
+            public void call() {
+                try {
+                    getStepDataCountRequest.doPerform();
+                } catch (IOException e) {
+                    LOG.error("Exception on starting step data count request", e);
+                    syncState.setActivitySync(false);
+                }
+            }
+
+            @Override
+            public void handleException(Request.ResponseParseException e) {
+                LOG.error("Sleep data count exception", e);
+                syncState.setActivitySync(false);
+            }
+        });
+
+        try {
+            getSleepDataCountRequest.doPerform();
+        } catch (IOException e) {
+            LOG.error("Exception on starting sleep data count request", e);
+            syncState.setActivitySync(false);
+        }
+    }
+
+    private void fetchActivityDataP2P() {
+        HuaweiP2PDataDictionarySyncService P2PSyncService = HuaweiP2PDataDictionarySyncService.getRegisteredInstance(huaweiP2PManager);
+
+        if (P2PSyncService != null) {
+            List<Integer> list = P2PSyncService.checkSupported(this.getHuaweiCoordinator(), Arrays.asList(HuaweiDictTypes.SKIN_TEMPERATURE_CLASS, HuaweiDictTypes.BLOOD_PRESSURE_CLASS));
+            if(!list.isEmpty()) {
+                syncState.setP2pSync(true);
+                P2PSyncService.startSync(list, new HuaweiP2PDataDictionarySyncService.DictionarySyncCallback() {
+                    @Override
+                    public void onComplete(boolean complete) {
+                        LOG.info("Sync P2P Data complete");
+                        syncState.setP2pSync(false);
+                    }
+                });
+            }
+        }
+    }
+
+    private void fetchWorkoutData() {
+        syncState.setWorkoutSync(true);
+
+        int start = 0;
+        int end = (int) (System.currentTimeMillis() / 1000);
+
+        SharedPreferences sharedPreferences = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress());
+        long prefLastSyncTime = sharedPreferences.getLong("lastSportsActivityTimeMillis", 0);
+        if (prefLastSyncTime != 0) {
+            start = (int) (prefLastSyncTime / 1000);
+
+            // Reset for next calls
+            sharedPreferences.edit().putLong("lastSportsActivityTimeMillis", 0).apply();
+        } else {
+            try (DBHandler db = GBApplication.acquireDB()) {
+                Long userId = DBHelper.getUser(db.getDaoSession()).getId();
+                Long deviceId = DBHelper.getDevice(gbDevice, db.getDaoSession()).getId();
+
+                QueryBuilder<HuaweiWorkoutSummarySample> qb1 = db.getDaoSession().getHuaweiWorkoutSummarySampleDao().queryBuilder().where(
+                        HuaweiWorkoutSummarySampleDao.Properties.DeviceId.eq(deviceId),
+                        HuaweiWorkoutSummarySampleDao.Properties.UserId.eq(userId)
+                ).orderDesc(
+                        HuaweiWorkoutSummarySampleDao.Properties.StartTimestamp
+                ).limit(1);
+
+                List<HuaweiWorkoutSummarySample> samples1 = qb1.list();
+                if (!samples1.isEmpty())
+                    start = samples1.get(0).getEndTimestamp();
+
+                QueryBuilder<BaseActivitySummary> qb2 = db.getDaoSession().getBaseActivitySummaryDao().queryBuilder().where(
+                        BaseActivitySummaryDao.Properties.DeviceId.eq(deviceId),
+                        BaseActivitySummaryDao.Properties.UserId.eq(userId)
+                ).orderDesc(
+                        BaseActivitySummaryDao.Properties.StartTime
+                ).limit(1);
+
+                List<BaseActivitySummary> samples2 = qb2.list();
+                if (!samples2.isEmpty())
+                    start = Math.min(start, (int) (samples2.get(0).getEndTime().getTime() / 1000L));
+
+                start = start + 1;
+            } catch (Exception e) {
+                LOG.warn("Exception for getting start time, using 10/06/2022 - 00:00:00.");
+            }
+
+            if (start == 0 || start == 1)
+                start = 1654819200;
+        }
+
+        final GetWorkoutCountRequest getWorkoutCountRequest;
+        if (isBLE()) {
+            nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder leBuilder = createLeTransactionBuilder("FetchWorkoutData");
+            leBuilder.add(new nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceBusyAction(gbDevice, context.getString(R.string.busy_task_fetch_activity_data), context));
+            getWorkoutCountRequest = new GetWorkoutCountRequest(this, leBuilder, start, end);
+        } else {
+            nodomain.freeyourgadget.gadgetbridge.service.btbr.TransactionBuilder brBuilder = createBrTransactionBuilder("FetchWorkoutData");
+            brBuilder.add(new nodomain.freeyourgadget.gadgetbridge.service.btbr.actions.SetDeviceBusyAction(gbDevice, context.getString(R.string.busy_task_fetch_activity_data), context));
+            getWorkoutCountRequest = new GetWorkoutCountRequest(this, brBuilder, start, end);
+        }
+
+        getWorkoutCountRequest.setFinalizeReq(new RequestCallback() {
+            @Override
+            public void handleException(Request.ResponseParseException e) {
+                // This is propagated through the workout requests, hence the slightly generic error message
+                GB.toast(context, "Exception synchronizing workout", Toast.LENGTH_SHORT, GB.ERROR);
+                LOG.error("Exception synchronizing workout", e);
+                endOfWorkoutSync();
+            }
+        });
+
+        try {
+            getWorkoutCountRequest.doPerform();
+        } catch (IOException e) {
+            GB.toast(context, "Exception synchronizing workout", Toast.LENGTH_SHORT, GB.ERROR);
+            LOG.error("Error sending workout count - showing user that the workout sync failed", e);
+            endOfWorkoutSync();
+        }
+    }
+
+    public void onReset(int flags) {
+        try {
+            if (flags == GBDeviceProtocol.RESET_FLAGS_FACTORY_RESET) {
+                SendFactoryResetRequest sendFactoryResetReq = new SendFactoryResetRequest(this);
+                sendFactoryResetReq.doPerform();
+            }
+        } catch (IOException e) {
+            GB.toast(context, "Factory resetting Huawei device failed", Toast.LENGTH_SHORT, GB.ERROR, e);
+            LOG.error("Factory resetting Huawei device failed", e);
+        }
+    }
+
+    public void setNotificationStatus() {
+        /*
+         * TODO: this doesn't work as expected
+         *       We thought it would disable(/enable) the notifications on the device side,
+         *       but at least the disabling doesn't work - so we don't send notifications to the
+         *       device at all if the setting is disabled now.
+         *      TRYING to debug this as it should really be handled on device side...
+         */
+        try {
+            SetNotificationRequest setNotificationReq = new SetNotificationRequest(this);
+            setNotificationReq.doPerform();
+//            SetWearMessagePushRequest setWearMessagePushReq = new SetWearMessagePushRequest(this);
+//            setWearMessagePushReq.doPerform();
+        } catch (IOException e) {
+            GB.toast(context, "Setting notification failed", Toast.LENGTH_SHORT, GB.ERROR, e);
+            LOG.error("Setting notification failed", e);
+        }
+    }
+
+    public short getNotificationId() {
+        if (msgId < 256) {
+            msgId += 1;
+        } else {
+            msgId = 0;
+        }
+        return msgId;
+    }
+
+    public void onNotification(NotificationSpec notificationSpec) {
+        if (!GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress()).getBoolean(DeviceSettingsPreferenceConst.PREF_NOTIFICATION_ENABLE, false)) {
+            // Don't send notifications when they are disabled
+            LOG.info("Stopped notification as they are disabled.");
+            return;
+        }
+
+        SendNotificationRequest sendNotificationReq = new SendNotificationRequest(this);
+        try {
+            sendNotificationReq.buildNotificationTLVFromNotificationSpec(notificationSpec);
+            sendNotificationReq.doPerform();
+        } catch (IOException e) {
+            LOG.error("Sending notification failed", e);
+        }
+    }
+
+    public void setDateFormat() {
+        try {
+            SetDateFormatRequest setDateFormatReq = new SetDateFormatRequest(this);
+            setDateFormatReq.doPerform();
+        } catch (IOException e) {
+            GB.toast(context, "Failed to configure date format", Toast.LENGTH_SHORT, GB.ERROR, e);
+            LOG.error("Failed to configure date format", e);
+        }
+    }
+
+    public void onSetTime() {
+        try {
+            new SetTimeRequest(this, true).doPerform();
+        } catch (IOException e) {
+            GB.toast(context, "Failed to configure time", Toast.LENGTH_SHORT, GB.ERROR, e);
+            LOG.error("Failed to configure time", e);
+        }
+    }
+
+    public void onSetAlarms(ArrayList<? extends nodomain.freeyourgadget.gadgetbridge.model.Alarm> alarms) {
+        boolean smartAlarmEnabled = getHuaweiCoordinator().supportsSmartAlarm(getDevice());
+
+        AlarmsRequest smartAlarmReq = new AlarmsRequest(this, true);
+        AlarmsRequest eventAlarmReq = new AlarmsRequest(this, false);
+        for (nodomain.freeyourgadget.gadgetbridge.model.Alarm alarm : alarms) {
+            if (alarm.getPosition() == 0 && smartAlarmEnabled) {
+                smartAlarmReq.buildSmartAlarm(alarm);
+            } else {
+                eventAlarmReq.addEventAlarm(alarm, !smartAlarmEnabled);
+            }
+        }
+        try {
+            if (smartAlarmEnabled)
+                smartAlarmReq.doPerform();
+            eventAlarmReq.doPerform();
+        } catch (IOException e) {
+            GB.toast(context, "Failed to configure alarms", Toast.LENGTH_SHORT, GB.ERROR, e);
+            LOG.error("Failed to configure alarms", e);
+        }
+    }
+
+    public void onSetCallState(CallSpec callSpec) {
+        if (callSpec.command == CallSpec.CALL_INCOMING) {
+            SendNotificationRequest sendNotificationReq = new SendNotificationRequest(this);
+            try {
+                sendNotificationReq.buildNotificationTLVFromCallSpec(callSpec);
+                sendNotificationReq.doPerform();
+            } catch (IOException e) {
+                LOG.error("Failed to send start call notification", e);
+            }
+        } else if (
+                callSpec.command == CallSpec.CALL_ACCEPT ||
+                        callSpec.command == CallSpec.CALL_START ||
+                        callSpec.command == CallSpec.CALL_REJECT ||
+                        callSpec.command == CallSpec.CALL_END
+        ) {
+            StopNotificationRequest stopNotificationRequest = new StopNotificationRequest(this);
+            try {
+                stopNotificationRequest.doPerform();
+            } catch (IOException e) {
+                LOG.error("Failed to send stop call notification", e);
+            }
+        }
+    }
+
+    public void onSetMusicState(MusicStateSpec stateSpec) {
+        if (mediaManager.onSetMusicState(stateSpec))
+            sendSetMusic();
+    }
+
+    public void onSetMusicInfo(MusicSpec musicSpec) {
+        if (mediaManager.onSetMusicInfo(musicSpec))
+            sendSetMusic();
+    }
+
+    public void onSetPhoneVolume() {
+        sendSetMusic();
+    }
+
+    public void refreshMediaManager() {
+        mediaManager.refresh();
+    }
+
+    public void sendSetMusic() {
+        // This often gets called twice in a row because of onSetMusicState and onSetMusicInfo
+        // Maybe we can consolidate that into just one request?
+        SetMusicRequest setMusicRequest = new SetMusicRequest(
+                this,
+                mediaManager.getBufferMusicStateSpec(),
+                mediaManager.getBufferMusicSpec()
+        );
+        try {
+            setMusicRequest.doPerform();
+        } catch (IOException e) {
+            LOG.error("Failed to send set music request", e);
+        }
+    }
+
+    public void addInProgressRequest(Request request) {
+        responseManager.addHandler(request);
+    }
+
+    public void addSleepActivity(int timestamp_start, int timestamp_end, byte type, byte source) {
+        LOG.debug("Adding sleep activity between {} and {}", timestamp_start, timestamp_end);
+
+        try (DBHandler db = GBApplication.acquireDB()) {
+            Long userId = DBHelper.getUser(db.getDaoSession()).getId();
+            Long deviceId = DBHelper.getDevice(gbDevice, db.getDaoSession()).getId();
+            HuaweiSampleProvider sampleProvider = new HuaweiSampleProvider(gbDevice, db.getDaoSession());
+
+            HuaweiActivitySample activitySample = new HuaweiActivitySample(
+                    timestamp_start,
+                    deviceId,
+                    userId,
+                    timestamp_end,
+                    source,
+                    type,
+                    1,
+                    ActivitySample.NOT_MEASURED,
+                    ActivitySample.NOT_MEASURED,
+                    ActivitySample.NOT_MEASURED,
+                    ActivitySample.NOT_MEASURED,
+                    ActivitySample.NOT_MEASURED
+            );
+            activitySample.setProvider(sampleProvider);
+
+            sampleProvider.addGBActivitySample(activitySample);
+        } catch (Exception e) {
+            LOG.error("Failed to add sleep activity to database", e);
+        }
+    }
+
+    public void addStepData(int timestamp, short steps, short calories, short distance, byte spo, byte heartrate) {
+        try (DBHandler db = GBApplication.acquireDB()) {
+            Long userId = DBHelper.getUser(db.getDaoSession()).getId();
+            Long deviceId = DBHelper.getDevice(gbDevice, db.getDaoSession()).getId();
+            HuaweiSampleProvider sampleProvider = new HuaweiSampleProvider(gbDevice, db.getDaoSession());
+
+            HuaweiActivitySample activitySample = new HuaweiActivitySample(
+                    timestamp,
+                    deviceId,
+                    userId,
+                    timestamp + 60,
+                    FitnessData.MessageData.stepId,
+                    ActivitySample.NOT_MEASURED,
+                    1,
+                    steps,
+                    calories,
+                    distance,
+                    spo,
+                    heartrate
+            );
+            activitySample.setProvider(sampleProvider);
+
+            sampleProvider.addGBActivitySample(activitySample);
+        } catch (Exception e) {
+            LOG.error("Failed to add step data to database", e);
+        }
+    }
+
+    public void addTotalFitnessData(int steps, int calories, int distance) {
+        LOG.debug("FITNESS total steps: {}", steps);
+        LOG.debug("FITNESS total calories: {}", calories); // TODO: May actually be kilocalories
+        LOG.debug("FITNESS total distance: {} m", distance);
+
+        // TODO: potentially do more with this, maybe through realtime data?
+    }
+
+    public Long addWorkoutTotalsData(Workout.WorkoutTotals.Response packet) {
+        try (DBHandler db = GBApplication.acquireDB()) {
+            Long userId = DBHelper.getUser(db.getDaoSession()).getId();
+            Long deviceId = DBHelper.getDevice(gbDevice, db.getDaoSession()).getId();
+
+            // Avoid duplicates
+            QueryBuilder<HuaweiWorkoutSummarySample> qb = db.getDaoSession().getHuaweiWorkoutSummarySampleDao().queryBuilder().where(
+                    HuaweiWorkoutSummarySampleDao.Properties.UserId.eq(userId),
+                    HuaweiWorkoutSummarySampleDao.Properties.DeviceId.eq(deviceId),
+                    HuaweiWorkoutSummarySampleDao.Properties.WorkoutNumber.eq(packet.number),
+                    HuaweiWorkoutSummarySampleDao.Properties.StartTimestamp.eq(packet.startTime),
+                    HuaweiWorkoutSummarySampleDao.Properties.EndTimestamp.eq(packet.endTime)
+            );
+            List<HuaweiWorkoutSummarySample> results = qb.build().list();
+            Long workoutId = null;
+            if (!results.isEmpty())
+                workoutId = results.get(0).getWorkoutId();
+
+            byte[] raw;
+            if (packet.rawData == null)
+                raw = null;
+            else
+                raw = StringUtils.bytesToHex(packet.rawData).getBytes(StandardCharsets.UTF_8);
+
+
+            byte[] recoveryHeartRates;
+            if (packet.recoveryHeartRates == null)
+                recoveryHeartRates = null;
+            else
+                recoveryHeartRates = StringUtils.bytesToHex(packet.recoveryHeartRates).getBytes(StandardCharsets.UTF_8);
+
+            HuaweiWorkoutSummarySample summarySample = new HuaweiWorkoutSummarySample(
+                    workoutId,
+                    deviceId,
+                    userId,
+                    packet.number,
+                    packet.status,
+                    packet.startTime,
+                    packet.endTime,
+                    packet.calories,
+                    packet.distance,
+                    packet.stepCount,
+                    packet.totalTime,
+                    packet.duration,
+                    packet.type,
+                    packet.strokes,
+                    packet.avgStrokeRate,
+                    packet.poolLength,
+                    packet.laps,
+                    packet.avgSwolf,
+                    raw,
+                    null,
+                    packet.maxAltitude,
+                    packet.minAltitude,
+                    packet.elevationGain,
+                    packet.elevationLoss,
+                    packet.workoutLoad,
+                    packet.workoutAerobicEffect,
+                    packet.workoutAnaerobicEffect,
+                    packet.recoveryTime,
+                    packet.minHeartRatePeak,
+                    packet.maxHeartRatePeak,
+                    recoveryHeartRates,
+                    packet.swimType,
+                    packet.maxMET,
+                    packet.hrZoneType,
+                    packet.runPaceZone1Min,
+                    packet.runPaceZone2Min,
+                    packet.runPaceZone3Min,
+                    packet.runPaceZone4Min,
+                    packet.runPaceZone5Min,
+                    packet.runPaceZone5Max,
+                    packet.runPaceZone1Time,
+                    packet.runPaceZone2Time,
+                    packet.runPaceZone3Time,
+                    packet.runPaceZone4Time,
+                    packet.runPaceZone5Time,
+                    packet.algType,
+                    packet.trainingPoints
+            );
+
+
+            db.getDaoSession().getHuaweiWorkoutSummarySampleDao().insertOrReplace(summarySample);
+
+            return summarySample.getWorkoutId();
+        } catch (Exception e) {
+            LOG.error("Failed to add workout totals data to database", e);
+            return null;
+        }
+    }
+
+    public void addWorkoutSampleData(Long workoutId, List<Workout.WorkoutData.Response.Data> dataList) {
+        if (workoutId == null)
+            return;
+
+        try (DBHandler db = GBApplication.acquireDB()) {
+            HuaweiWorkoutDataSampleDao dao = db.getDaoSession().getHuaweiWorkoutDataSampleDao();
+
+            for (Workout.WorkoutData.Response.Data data : dataList) {
+                byte[] unknown;
+                if (data.unknownData == null)
+                    unknown = null;
+                else
+                    unknown = StringUtils.bytesToHex(data.unknownData).getBytes(StandardCharsets.UTF_8);
+
+                HuaweiWorkoutDataSample dataSample = new HuaweiWorkoutDataSample(
+                        workoutId,
+                        data.timestamp,
+                        data.heartRate,
+                        data.speed,
+                        data.stepRate,
+                        data.cadence,
+                        data.stepLength,
+                        data.groundContactTime,
+                        data.impact,
+                        data.swingAngle,
+                        data.foreFootLanding,
+                        data.midFootLanding,
+                        data.backFootLanding,
+                        data.eversionAngle,
+                        data.swolf,
+                        data.strokeRate,
+                        unknown,
+                        data.calories,
+                        data.cyclingPower,
+                        data.frequency,
+                        data.altitude
+                );
+                dao.insertOrReplace(dataSample);
+            }
+        } catch (Exception e) {
+            LOG.error("Failed to add workout data to database", e);
+        }
+    }
+
+    public void addWorkoutPaceData(Long workoutId, List<Workout.WorkoutPace.Response.Block> paceList, short number) {
+        if (workoutId == null)
+            return;
+
+        try (DBHandler db = GBApplication.acquireDB()) {
+            HuaweiWorkoutPaceSampleDao dao = db.getDaoSession().getHuaweiWorkoutPaceSampleDao();
+
+            if (number == 0) {
+                final DeleteQuery<HuaweiWorkoutPaceSample> tableDeleteQuery = dao.queryBuilder()
+                        .where(HuaweiWorkoutPaceSampleDao.Properties.WorkoutId.eq(workoutId))
+                        .buildDelete();
+                tableDeleteQuery.executeDeleteWithoutDetachingEntities();
+            }
+
+            int paceIndex = (int) dao.queryBuilder().where(HuaweiWorkoutPaceSampleDao.Properties.WorkoutId.eq(workoutId)).count();
+            for (Workout.WorkoutPace.Response.Block block : paceList) {
+
+                Integer correction = block.hasCorrection ? (int) block.correction : null;
+                HuaweiWorkoutPaceSample paceSample = new HuaweiWorkoutPaceSample(
+                        workoutId,
+                        paceIndex++,
+                        block.distance,
+                        block.type,
+                        block.pace,
+                        block.pointIndex,
+                        correction
+                );
+                dao.insertOrReplace(paceSample);
+            }
+        } catch (Exception e) {
+            LOG.error("Failed to add workout pace data to database", e);
+        }
+    }
+
+
+    public void addWorkoutSwimSegmentsData(Long workoutId, List<Workout.WorkoutSwimSegments.Response.Block> paceList, short number) {
+        if (workoutId == null)
+            return;
+
+        try (DBHandler db = GBApplication.acquireDB()) {
+            HuaweiWorkoutSwimSegmentsSampleDao dao = db.getDaoSession().getHuaweiWorkoutSwimSegmentsSampleDao();
+
+            if (number == 0) {
+                final DeleteQuery<HuaweiWorkoutSwimSegmentsSample> tableDeleteQuery = dao.queryBuilder()
+                        .where(HuaweiWorkoutSwimSegmentsSampleDao.Properties.WorkoutId.eq(workoutId))
+                        .buildDelete();
+                tableDeleteQuery.executeDeleteWithoutDetachingEntities();
+            }
+
+            int paceIndex = (int) dao.queryBuilder().where(HuaweiWorkoutSwimSegmentsSampleDao.Properties.WorkoutId.eq(workoutId)).count();
+            for (Workout.WorkoutSwimSegments.Response.Block block : paceList) {
+                HuaweiWorkoutSwimSegmentsSample swimSectionSample = new HuaweiWorkoutSwimSegmentsSample(
+                        workoutId,
+                        paceIndex++,
+                        block.distance,
+                        block.type,
+                        block.pace,
+                        block.pointIndex,
+                        block.segment,
+                        block.swimType,
+                        block.strokes,
+                        block.avgSwolf,
+                        block.time
+                );
+                dao.insertOrReplace(swimSectionSample);
+            }
+        } catch (Exception e) {
+            LOG.error("Failed to add workout swim section data to database", e);
+        }
+    }
+
+    public void addDictData(List<HuaweiP2PDataDictionarySyncService.DictData> dictData) {
+        try (DBHandler db = GBApplication.acquireDB()) {
+            Long userId = DBHelper.getUser(db.getDaoSession()).getId();
+            Long deviceId = DBHelper.getDevice(gbDevice, db.getDaoSession()).getId();
+
+            for (HuaweiP2PDataDictionarySyncService.DictData data : dictData) {
+                // Avoid duplicates
+                QueryBuilder<HuaweiDictData> qb = db.getDaoSession().getHuaweiDictDataDao().queryBuilder().where(
+                        HuaweiDictDataDao.Properties.UserId.eq(userId),
+                        HuaweiDictDataDao.Properties.DeviceId.eq(deviceId),
+                        HuaweiDictDataDao.Properties.DictClass.eq(data.getDictClass()),
+                        HuaweiDictDataDao.Properties.StartTimestamp.eq(data.getStartTimestamp())
+                );
+                List<HuaweiDictData> results = qb.build().list();
+                Long dictId = null;
+                if (!results.isEmpty())
+                    dictId = results.get(0).getDictId();
+
+                HuaweiDictData dictSample = new HuaweiDictData(
+                        dictId,
+                        deviceId,
+                        userId,
+                        data.getDictClass(),
+                        data.getStartTimestamp(),
+                        data.getEndTimestamp(),
+                        data.getModifyTimestamp()
+                );
+                db.getDaoSession().getHuaweiDictDataDao().insertOrReplace(dictSample);
+                addDictDataValue(dictSample.getDictId(), data.getData());
+            }
+        } catch (Exception e) {
+            LOG.error("Failed to add dict data", e);
+        }
+    }
+
+    public void addDictDataValue(Long dictId, List<HuaweiP2PDataDictionarySyncService.DictData.DictDataValue> dictDataValues) {
+        if (dictId == null)
+            return;
+
+        try (DBHandler db = GBApplication.acquireDB()) {
+            HuaweiDictDataValuesDao dao = db.getDaoSession().getHuaweiDictDataValuesDao();
+
+            for (HuaweiP2PDataDictionarySyncService.DictData.DictDataValue dataValues : dictDataValues) {
+
+                HuaweiDictDataValues dictValue = new HuaweiDictDataValues(
+                        dictId,
+                        dataValues.getDataType(),
+                        dataValues.getTag(),
+                        dataValues.getValue()
+                );
+                dao.insertOrReplace(dictValue);
+            }
+        } catch (Exception e) {
+            LOG.error("Failed to add dict value to database", e);
+        }
+    }
+
+    public long getLastDataDictLastTimestamp(int dictClass) {
+        long lastTimestamp = 0;
+        if (dictClass == 0)
+            return lastTimestamp;
+
+        try (DBHandler db = GBApplication.acquireDB()) {
+            Long userId = DBHelper.getUser(db.getDaoSession()).getId();
+            Long deviceId = DBHelper.getDevice(gbDevice, db.getDaoSession()).getId();
+
+            QueryBuilder<HuaweiDictData> qb = db.getDaoSession().getHuaweiDictDataDao().queryBuilder().where(
+                    HuaweiDictDataDao.Properties.UserId.eq(userId),
+                    HuaweiDictDataDao.Properties.DeviceId.eq(deviceId),
+                    HuaweiDictDataDao.Properties.DictClass.eq(dictClass)
+            );
+            List<HuaweiDictData> results = qb.build().list();
+            for (HuaweiDictData data : results) {
+                if (data.getModifyTimestamp() != null) {
+                    lastTimestamp = Math.max(lastTimestamp, data.getModifyTimestamp());
+                }
+                if (data.getEndTimestamp() != null) {
+                    lastTimestamp = Math.max(lastTimestamp, data.getEndTimestamp());
+                }
+            }
+        } catch (Exception e) {
+            LOG.error("Failed to select last timestamp value to database", e);
+        }
+        return lastTimestamp;
+    }
+
+
+    public void setWearLocation() {
+        try {
+            SetWearLocationRequest setWearLocationReq = new SetWearLocationRequest(this);
+            setWearLocationReq.doPerform();
+        } catch (IOException e) {
+            GB.toast(context, "Failed to configure Wear Location", Toast.LENGTH_SHORT, GB.ERROR, e);
+            LOG.error("Failed to configure Wear Location", e);
+        }
+    }
+
+    public void getBatteryLevel() {
+        try {
+            stopBatteryRunnerDelayed();
+            GetBatteryLevelRequest batteryLevelReq = new GetBatteryLevelRequest(this);
+            batteryLevelReq.doPerform();
+        } catch (IOException e) {
+            GB.toast(context, "Failed to get battery Level", Toast.LENGTH_SHORT, GB.ERROR, e);
+            LOG.error("Failed to get battery Level", e);
+        }
+    }
+
+    public void sendUserInfo() {
+        try {
+            SendFitnessUserInfoRequest sendFitnessUserInfoRequest = new SendFitnessUserInfoRequest(this);
+            sendFitnessUserInfoRequest.doPerform();
+        } catch (IOException e) {
+            GB.toast(context, "Failed to set user info", Toast.LENGTH_SHORT, GB.ERROR, e);
+            LOG.error("Failed to set user info", e);
+        }
+    }
+
+    public void setActivateOnLift() {
+        try {
+            SetActivateOnLiftRequest setActivateOnLiftReq = new SetActivateOnLiftRequest(this);
+            setActivateOnLiftReq.doPerform();
+            SharedPreferences sharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(deviceMac);
+            boolean statusDndLiftWrist = sharedPrefs.getBoolean(DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_LIFT_WRIST, false);
+            if (statusDndLiftWrist) {
+                setDnd();
+            }
+        } catch (IOException e) {
+            GB.toast(context, "Failed to configure Activate on Rotate", Toast.LENGTH_SHORT, GB.ERROR, e);
+            LOG.error("Failed to configure Activate on Rotate", e);
+        }
+    }
+
+    public void setNavigateOnRotate() {
+        try {
+            SetNavigateOnRotateRequest setNavigateOnRotateReq = new SetNavigateOnRotateRequest(this);
+            setNavigateOnRotateReq.doPerform();
+        } catch (IOException e) {
+            GB.toast(context, "Failed to configure Navigate on Rotate", Toast.LENGTH_SHORT, GB.ERROR, e);
+            LOG.error("Failed to configure Navigate on Rotate", e);
+        }
+    }
+
+    public void setActivityReminder() {
+        try {
+            SetActivityReminderRequest setActivityReminderReq = new SetActivityReminderRequest(this);
+            setActivityReminderReq.doPerform();
+        } catch (IOException e) {
+            GB.toast(context, "Failed to configure Activity reminder", Toast.LENGTH_SHORT, GB.ERROR, e);
+            LOG.error("Failed to configure Activity reminder", e);
+        }
+    }
+
+    public void setTrusleep() {
+        try {
+            SetTruSleepRequest setTruSleepReq = new SetTruSleepRequest(this);
+            setTruSleepReq.doPerform();
+        } catch (IOException e) {
+            GB.toast(context, "Failed to configure truSleep", Toast.LENGTH_SHORT, GB.ERROR, e);
+            LOG.error("Failed to configure truSleep", e);
+        }
+    }
+
+    public void setTemperatureUnit() {
+        try {
+            SetTemperatureUnitSetting setTemperatureUnitSetting = new SetTemperatureUnitSetting(this);
+            setTemperatureUnitSetting.doPerform();
+        } catch (IOException e) {
+            GB.toast(context, "Failed to set temperature unit", Toast.LENGTH_SHORT, GB.ERROR, e);
+            LOG.error("Failed to configure TemperatureUnitSetting", e);
+        }
+    }
+
+    public void setContinuousSkinTemperatureMeasurement() {
+        try {
+            SetSkinTemperatureMeasurement skinTemperatureMeasurement = new SetSkinTemperatureMeasurement(this);
+            skinTemperatureMeasurement.doPerform();
+        } catch (IOException e) {
+            GB.toast(context, "Failed to configure continuous skin temperature measurement", Toast.LENGTH_SHORT, GB.ERROR, e);
+            LOG.error("Failed to configure SkinTemperatureMeasurement", e);
+        }
+    }
+
+    public void setDnd() {
+        try {
+            SendDndDeleteRequest sendDndDeleteReq = new SendDndDeleteRequest(this);
+            SendDndAddRequest sendDndAddReq = new SendDndAddRequest(this);
+            sendDndDeleteReq.nextRequest(sendDndAddReq);
+            sendDndDeleteReq.doPerform();
+        } catch (IOException e) {
+            GB.toast(context, "Failed to set DND", Toast.LENGTH_SHORT, GB.ERROR, e);
+            LOG.error("Failed to set DND", e);
+        }
+    }
+
+    public void setDndNotWear() {
+        try {
+            SetWearMessagePushRequest setWearMessagePushReq = new SetWearMessagePushRequest(this);
+            setWearMessagePushReq.doPerform();
+        } catch (IOException e) {
+            GB.toast(context, "Setting DND not wear failed", Toast.LENGTH_SHORT, GB.ERROR, e);
+            LOG.error("Setting DND not wear failed", e);
+        }
+
+    }
+
+    private void setDisconnectNotification() {
+        try {
+            SetDisconnectNotification req = new SetDisconnectNotification(this);
+            req.doPerform();
+        } catch (IOException e) {
+            GB.toast(context, "Failed to set disconnect notification", Toast.LENGTH_SHORT, GB.ERROR, e);
+            LOG.error("Failed to set disconnect notification", e);
+        }
+    }
+
+    private void setHeartrateAutomatic() {
+        try {
+            SetAutomaticHeartrateRequest req = new SetAutomaticHeartrateRequest(this);
+            req.doPerform();
+        } catch (IOException e) {
+            GB.toast(context, "Failed to set automatic heart rate", Toast.LENGTH_SHORT, GB.ERROR, e);
+            LOG.error("Failed to set automatic heart rate", e);
+        }
+    }
+
+    private void setSpoAutomatic() {
+        try {
+            SetAutomaticSpoRequest req = new SetAutomaticSpoRequest(this);
+            req.doPerform();
+        } catch (IOException e) {
+            GB.toast(context, "Failed to set automatic SpO", Toast.LENGTH_SHORT, GB.ERROR, e);
+            LOG.error("Failed to set automatic SpO", e);
+        }
+    }
+
+    public void sendDebugRequest() {
+        try {
+            LOG.debug("Send debug request");
+            DebugRequest req = new DebugRequest(this);
+            req.doPerform();
+        } catch (IOException e) {
+            GB.toast(context, "Failed to send debug request", Toast.LENGTH_SHORT, GB.ERROR, e);
+            LOG.error("Failed to send debug request", e);
+        }
+    }
+
+    public void onStopFindPhone() {
+        try {
+            LOG.debug("Send stop find phone request");
+            StopFindPhoneRequest stopFindPhoneRequest = new StopFindPhoneRequest(this);
+            stopFindPhoneRequest.doPerform();
+        } catch (IOException e) {
+            GB.toast(context, "Failed to send stop find phone request", Toast.LENGTH_SHORT, GB.ERROR, e);
+            LOG.error("Failed to send stop find phone request", e);
+        }
+    }
+
+    public void setLanguageSetting() {
+        try {
+            SetLanguageSettingRequest setLocaleReq = new SetLanguageSettingRequest(this);
+            setLocaleReq.doPerform();
+        } catch (IOException e) {
+            GB.toast(context, "Failed to set language settings request", Toast.LENGTH_SHORT, GB.ERROR, e);
+            LOG.error("Failed to set language settings request", e);
+        }
+    }
+
+    public Weather.WeatherIcon openWeatherMapConditionCodeToHuaweiIcon(int conditionCode) {
+        return huaweiWeatherManager.openWeatherMapConditionCodeToHuaweiIcon(conditionCode);
+    }
+
+    public void onSendWeather(ArrayList<WeatherSpec> weatherSpecs) {
+        huaweiWeatherManager.sendWeather(weatherSpecs.get(0));
+    }
+
+    public void onSetGpsLocation(Location location) {
+        if (gpsParametersResponse == null) {
+            GB.toast(context, "Received location without knowing supported parameters", Toast.LENGTH_SHORT, GB.ERROR);
+            LOG.error("Received location without knowing supported parameters");
+            return;
+        }
+
+        if (!gpsEnabled) {
+            LOG.warn("Received GPS data without GPS being enabled! Attempting to stop again.");
+            GBLocationService.stop(getContext(), getDevice());
+            return;
+        }
+
+        SendGpsDataRequest sendGpsDataRequest = new SendGpsDataRequest(this, location, gpsLastLocation, gpsParametersResponse);
+        try {
+            sendGpsDataRequest.doPerform();
+        } catch (IOException e) {
+            GB.toast(context, "Failed to send GPS data", Toast.LENGTH_SHORT, GB.ERROR, e);
+            LOG.error("Failed to send GPS data", e);
+        }
+        gpsLastLocation = location;
+    }
+
+    public void onInstallApp(Uri uri) {
+        LOG.info("enter onAppInstall uri: {}", uri);
+        HuaweiFwHelper huaweiFwHelper = new HuaweiFwHelper(uri, getContext());
+
+        HuaweiUploadManager.FileUploadInfo fileInfo = new HuaweiUploadManager.FileUploadInfo();
+
+        if (huaweiFwHelper.isMusic()) {
+            getHuaweiMusicManager().addUploadMusic(huaweiFwHelper.getMusicInfo());
+        }
+
+        fileInfo.setFileType(huaweiFwHelper.getFileType());
+        if (huaweiFwHelper.isWatchface()) {
+            fileInfo.setFileName(huaweiWatchfaceManager.getRandomName());
+        } else {
+            fileInfo.setFileName(huaweiFwHelper.getFileName());
+        }
+        fileInfo.setBytes(huaweiFwHelper.getBytes());
+
+        fileInfo.setFileUploadCallback(new HuaweiUploadManager.FileUploadCallback() {
+            @Override
+            public void onUploadStart() {
+                HuaweiSupportProvider.this.huaweiUploadManager.setDeviceBusy();
+            }
+
+            @Override
+            public void onUploadProgress(int progress) {
+                HuaweiSupportProvider.this.onUploadProgress(R.string.updatefirmwareoperation_update_in_progress, progress, true);
+            }
+
+            @Override
+            public void onUploadComplete() {
+                HuaweiSupportProvider.this.huaweiUploadManager.unsetDeviceBusy();
+                HuaweiSupportProvider.this.onUploadProgress(R.string.updatefirmwareoperation_update_complete, 100, false);
+            }
+
+            @Override
+            public void onError(int code) {
+                if (code == 140004) {
+                    LOG.error("Too many watchfaces installed or musics uploaded");
+                    HuaweiSupportProvider.this.handleGBDeviceEvent(new GBDeviceEventDisplayMessage(HuaweiSupportProvider.this.getContext().getString(R.string.cannot_upload_watchface_too_many_watchfaces_installed), Toast.LENGTH_LONG, GB.ERROR));
+                } else if (code == 140008) {
+                    LOG.error("File already exists");
+                    HuaweiSupportProvider.this.handleGBDeviceEvent(new GBDeviceEventDisplayMessage(HuaweiSupportProvider.this.getContext().getString(R.string.file_already_exists), Toast.LENGTH_LONG, GB.ERROR));
+                } else if (code == 140009) {
+                    LOG.error("Insufficient space for upload");
+                    HuaweiSupportProvider.this.handleGBDeviceEvent(new GBDeviceEventDisplayMessage(HuaweiSupportProvider.this.getContext().getString(R.string.insufficient_space_for_upload), Toast.LENGTH_LONG, GB.ERROR));
+                }
+            }
+        });
+
+        huaweiUploadManager.setFileUploadInfo(fileInfo);
+
+        try {
+            SendFileUploadInfo sendFileUploadInfo = new SendFileUploadInfo(this, huaweiUploadManager);
+            sendFileUploadInfo.doPerform();
+        } catch (IOException e) {
+            GB.toast(context, "Failed to send file upload info", Toast.LENGTH_SHORT, GB.ERROR, e);
+            LOG.error("Failed to send file upload info", e);
+        }
+    }
+
+    public void onUploadProgress(int textRsrc, int progressPercent, boolean ongoing) {
+        try {
+            if (isBLE()) {
+                nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder leBuilder = createLeTransactionBuilder("FetchRecordedData");
+                leBuilder.add(new nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetProgressAction(
+                        context.getString(textRsrc),
+                        ongoing,
+                        progressPercent,
+                        context
+                ));
+                leBuilder.queue(leSupport.getQueue());
+            } else {
+                nodomain.freeyourgadget.gadgetbridge.service.btbr.TransactionBuilder brBuilder = createBrTransactionBuilder("FetchRecordedData");
+                brBuilder.add(new nodomain.freeyourgadget.gadgetbridge.service.btbr.actions.SetProgressAction(
+                        context.getString(textRsrc),
+                        ongoing,
+                        progressPercent,
+                        context));
+                brBuilder.queue(brSupport.getQueue());
+
+            }
+
+        } catch (final Exception e) {
+            LOG.error("Failed to update progress notification", e);
+        }
+    }
+
+    private List<GBDeviceApp> gbWatchFaces = null;
+    private List<GBDeviceApp> gbWatchApps = null;
+
+    public void setGbWatchFaces(List<GBDeviceApp> gbWatchFaces) {
+        this.gbWatchFaces = gbWatchFaces;
+        updateAppList();
+    }
+
+    public void setGbWatchApps(List<GBDeviceApp> gbWatchApps) {
+        this.gbWatchApps = gbWatchApps;
+        updateAppList();
+    }
+
+    private void updateAppList() {
+        ArrayList<GBDeviceApp> gbDeviceApps = new ArrayList<>();
+        if (this.gbWatchFaces != null)
+            gbDeviceApps.addAll(this.gbWatchFaces);
+        if (this.gbWatchApps != null)
+            gbDeviceApps.addAll(this.gbWatchApps);
+        final GBDeviceEventAppInfo appInfoCmd = new GBDeviceEventAppInfo();
+        appInfoCmd.apps = gbDeviceApps.toArray(new GBDeviceApp[0]);
+        evaluateGBDeviceEvent(appInfoCmd);
+    }
+
+    public void onAppInfoReq() {
+        this.gbWatchFaces = null;
+        this.gbWatchApps = null;
+        huaweiWatchfaceManager.requestWatchfaceList();
+        huaweiAppManager.requestAppList();
+    }
+
+    public void onAppStart(final UUID uuid, boolean start) {
+        if (start) {
+            //NOTE: to prevent exception in watchfaces code
+            if (!huaweiAppManager.startApp(uuid)) {
+                huaweiWatchfaceManager.setWatchface(uuid);
+            }
+        }
+    }
+
+    public void onAppDelete(final UUID uuid) {
+        //NOTE: to prevent exception in watchfaces code
+        if (!huaweiAppManager.deleteApp(uuid)) {
+            huaweiWatchfaceManager.deleteWatchface(uuid);
+        }
+    }
+
+    public void onCameraStatusChange(GBDeviceEventCameraRemote.Event event, String filename) {
+        if (event == GBDeviceEventCameraRemote.Event.OPEN_CAMERA) {
+            // Somehow a delay is necessary for the watch
+            new Handler(GBApplication.getContext().getMainLooper()).postDelayed(
+                    () -> {
+                        SendCameraRemoteSetupEvent sendCameraRemoteSetupEvent = new SendCameraRemoteSetupEvent(HuaweiSupportProvider.this, CameraRemote.CameraRemoteSetup.Request.Event.CAMERA_STARTED);
+                        try {
+                            sendCameraRemoteSetupEvent.doPerform();
+                        } catch (IOException e) {
+                            GB.toast("Failed to send open camera request", Toast.LENGTH_SHORT, GB.ERROR, e);
+                            LOG.error("Failed to send open camera request", e);
+                        }
+                    },
+                    3000
+            );
+        } else if (event == GBDeviceEventCameraRemote.Event.CLOSE_CAMERA) {
+            SendCameraRemoteSetupEvent sendCameraRemoteSetupEvent2 = new SendCameraRemoteSetupEvent(this, CameraRemote.CameraRemoteSetup.Request.Event.CAMERA_STOPPED);
+            try {
+                sendCameraRemoteSetupEvent2.doPerform();
+            } catch (IOException e) {
+                GB.toast("Failed to send open camera request", Toast.LENGTH_SHORT, GB.ERROR, e);
+                LOG.error("Failed to send open camera request", e);
+            }
+        }
+    }
+
+    public void onSetContacts(ArrayList<? extends Contact> contacts) {
+        SendSetContactsRequest sendSetContactsRequest = new SendSetContactsRequest(
+                this,
+                contacts,
+                this.getHuaweiCoordinator().getContactsSlotCount(getDevice())
+        );
+        try {
+            sendSetContactsRequest.doPerform();
+        } catch (IOException e) {
+            GB.toast(context, "Failed to set contacts", Toast.LENGTH_SHORT, GB.ERROR, e);
+            LOG.error("Failed to send set contacts request", e);
+        }
+
+    }
+
+    public void onAddCalendarEvent(final CalendarEventSpec calendarEventSpec) {
+        HuaweiP2PCalendarService service = HuaweiP2PCalendarService.getRegisteredInstance(huaweiP2PManager);
+        if (service != null) {
+            service.onAddCalendarEvent(calendarEventSpec);
+        }
+    }
+
+    public void onDeleteCalendarEvent(final byte type, long id) {
+        HuaweiP2PCalendarService service = HuaweiP2PCalendarService.getRegisteredInstance(huaweiP2PManager);
+        if (service != null) {
+            service.onDeleteCalendarEvent(type, id);
+        }
+    }
+
+    public boolean startBatteryRunnerDelayed() {
+        int interval_minutes = GBApplication.getDevicePrefs(gbDevice).getBatteryPollingIntervalMinutes();
+        int interval = interval_minutes * 60 * 1000;
+        LOG.debug("Starting battery runner delayed by {} ({} minutes)", interval, interval_minutes);
+        handler.removeCallbacks(batteryRunner);
+        return handler.postDelayed(batteryRunner, interval);
+    }
+
+    public void stopBatteryRunnerDelayed() {
+        LOG.debug("Stopping battery runner delayed");
+        handler.removeCallbacks(batteryRunner);
+    }
+
+    public void dispose() {
+        stopBatteryRunnerDelayed();
+        huaweiFileDownloadManager.dispose();
+        huaweiP2PManager.unregisterAllService();
+    }
+
+    public boolean downloadTruSleepData(int start, int end) {
+        // We only get the data if TruSleep is supported
+        if (!getHuaweiCoordinator().supportsTruSleep())
+            return false;
+
+        huaweiFileDownloadManager.addToQueue(HuaweiFileDownloadManager.FileRequest.sleepStateFileRequest(
+                getHuaweiCoordinator().getSupportsTruSleepNewSync(),
+                start,
+                end,
+                new HuaweiFileDownloadManager.FileDownloadCallback() {
+                    @Override
+                    public void downloadComplete(HuaweiFileDownloadManager.FileRequest fileRequest) {
+                        if (fileRequest.getData().length != 0) {
+                            LOG.debug("Parsing sleep state file");
+                            HuaweiTruSleepParser.TruSleepStatus[] results = HuaweiTruSleepParser.parseState(fileRequest.getData());
+                            for (HuaweiTruSleepParser.TruSleepStatus status : results)
+                                addSleepActivity(status.startTime, status.endTime, (byte) 0x06, (byte) 0x0a);
+                        } else
+                            LOG.debug("Sleep state file empty");
+                        syncState.setActivitySync(false);
+                    }
+
+                    @Override
+                    public void downloadException(HuaweiFileDownloadManager.HuaweiFileDownloadException e) {
+                        super.downloadException(e);
+                        syncState.setActivitySync(false);
+                    }
+                }
+        ), true);
+        return true;
+    }
+
+    public void endOfWorkoutSync() {
+        this.syncState.setWorkoutSync(false);
+    }
+
+    public void downloadWorkoutGpsFiles(short workoutId, Long databaseId, Runnable extraCallbackAction) {
+        syncState.startWorkoutGpsDownload();
+
+        huaweiFileDownloadManager.addToQueue(HuaweiFileDownloadManager.FileRequest.workoutGpsFileRequest(
+                getHuaweiCoordinator().isSupportsGpsNewSync(),
+                workoutId,
+                databaseId,
+                new HuaweiFileDownloadManager.FileDownloadCallback() {
+                    @Override
+                    public void downloadComplete(HuaweiFileDownloadManager.FileRequest fileRequest) {
+                        extraCallbackAction.run();
+
+                        if (fileRequest.getData().length == 0) {
+                            LOG.debug("GPS file empty");
+                            syncState.stopWorkoutGpsDownload();
+                            return;
+                        }
+
+                        LOG.debug("Parsing GPS file");
+
+                        HuaweiGpsParser.GpsPoint[] points = HuaweiGpsParser.parseHuaweiGps(fileRequest.getData());
+
+                        LOG.debug("Received {} GPS points", points.length);
+
+                        if (points.length == 0) {
+                            LOG.debug("No GPS points returned");
+                            syncState.stopWorkoutGpsDownload();
+                            return;
+                        }
+
+                        ActivityTrack track = new ActivityTrack();
+
+                        track.setName("Workout " + fileRequest.getWorkoutId());
+                        track.setBaseTime(DateTimeUtils.parseTimeStamp(points[0].timestamp));
+
+                        try (DBHandler db = GBApplication.acquireDB()) {
+                            track.setUser(DBHelper.getUser(db.getDaoSession()));
+                            track.setDevice(DBHelper.getDevice(gbDevice, db.getDaoSession()));
+                        } catch (Exception e) {
+                            LOG.error("Cannot acquire DB, set user, or set device for Activity track, continuing anyway");
+                        }
+
+                        for (HuaweiGpsParser.GpsPoint point : points) {
+                            GPSCoordinate coordinate;
+                            if (point.altitudeSupported)
+                                coordinate = new GPSCoordinate(point.longitude, point.latitude, point.altitude);
+                            else
+                                coordinate = new GPSCoordinate(point.longitude, point.latitude);
+
+                            ActivityPoint activityPoint = new ActivityPoint();
+                            activityPoint.setTime(DateTimeUtils.parseTimeStamp(point.timestamp));
+                            activityPoint.setLocation(coordinate);
+
+                            track.addTrackPoint(activityPoint);
+                        }
+
+                        String filename = FileUtils.makeValidFileName("workout_" + fileRequest.getWorkoutId() + "_" + points[0].timestamp + ".gpx");
+                        File targetFile;
+                        try {
+                            targetFile = new File(
+                                    getDevice().getDeviceCoordinator().getWritableExportDirectory(getDevice()),
+                                    filename
+                            );
+                        } catch (IOException e) {
+                            GB.toast(context, "Could not open Workout GPS file to write to", Toast.LENGTH_SHORT, GB.ERROR, e);
+                            LOG.error("Could not open Workout GPS file to write to", e);
+                            syncState.stopWorkoutGpsDownload();
+                            return;
+                        }
+
+                        GPXExporter exporter = new GPXExporter();
+                        exporter.setCreator(GBApplication.app().getNameAndVersion());
+                        try {
+                            exporter.performExport(track, targetFile);
+                        } catch (IOException | ActivityTrackExporter.GPXTrackEmptyException e) {
+                            GB.toast(context, "Failed to export Workout GPX file", Toast.LENGTH_SHORT, GB.ERROR, e);
+                            LOG.error("Failed to export Workout GPX file", e);
+                            syncState.stopWorkoutGpsDownload();
+                            return;
+                        }
+
+                        Long databaseId = fileRequest.getDatabaseId();
+                        if (databaseId == null) {
+                            GB.toast(context, "Cannot link GPX to workout", Toast.LENGTH_SHORT, GB.ERROR);
+                            LOG.error("Cannot link GPX to workout");
+                            syncState.stopWorkoutGpsDownload();
+                            return;
+                        }
+
+                        try (DBHandler db = GBApplication.acquireDB()) {
+                            DaoSession daoSession = db.getDaoSession();
+                            HuaweiWorkoutSummarySample sample = daoSession.getHuaweiWorkoutSummarySampleDao().load(databaseId);
+                            sample.setGpxFileLocation(targetFile.getAbsolutePath());
+                            sample.update();
+                        } catch (Exception e) {
+                            GB.toast(context, "Failed to save Workout GPX file location", Toast.LENGTH_SHORT, GB.ERROR, e);
+                            LOG.error("Failed to save Workout GPX file location", e);
+                            syncState.stopWorkoutGpsDownload();
+                            return;
+                        }
+
+                        new HuaweiWorkoutGbParser(getDevice(), getContext()).parseWorkout(databaseId);
+
+                        LOG.debug("Completed workout GPS parsing and inserting");
+                        syncState.stopWorkoutGpsDownload();
+                    }
+
+                    @Override
+                    public void downloadException(HuaweiFileDownloadManager.HuaweiFileDownloadException e) {
+                        super.downloadException(e);
+                        syncState.stopWorkoutGpsDownload();
+
+                        extraCallbackAction.run();
+                    }
+                }
+        ), true);
+    }
+
+    /**
+     * Called when there are no more files left to download
+     */
+    public void downloadQueueEmpty(boolean needSync) {
+        syncState.updateState(needSync);
+    }
+
+    public void onTestNewFunction() {
+        // Show to user
+        gbDevice.setBusyTask("Downloading files...");
+        gbDevice.sendDeviceUpdateIntent(getContext());
+
+        HuaweiTruSleepParser.SleepFileDownloadCallback callback = new HuaweiTruSleepParser.SleepFileDownloadCallback(this) {
+            @Override
+            public void syncComplete(byte[] statusData, byte[] sleepData) {
+                LOG.debug("Sync of TruSleep status and data finished");
+
+                if (statusData == null || statusData.length == 0)
+                    return;
+
+                HuaweiTruSleepParser.TruSleepStatus[] results = HuaweiTruSleepParser.parseState(statusData);
+                if (results.length == 0)
+                    return;
+
+                HuaweiTruSleepParser.TruSleepData data = HuaweiTruSleepParser.parseData(sleepData);
+                HuaweiTruSleepParser.analyze(this.provider, results, data);
+            }
+        };
+
+        huaweiFileDownloadManager.addToQueue(HuaweiFileDownloadManager.FileRequest.sleepStateFileRequest(
+                getHuaweiCoordinator().getSupportsTruSleepNewSync(),
+                0,
+                (int) (System.currentTimeMillis() / 1000),
+                callback
+        ), true);
+        huaweiFileDownloadManager.addToQueue(HuaweiFileDownloadManager.FileRequest.sleepDataFileRequest(
+                getHuaweiCoordinator().getSupportsTruSleepNewSync(),
+                0,
+                (int) (System.currentTimeMillis() / 1000),
+                callback
+        ), true);
+    }
+
+    public void onMusicListReq() {
+        getHuaweiMusicManager().startSyncMusicData();
+    }
+
+    public void onMusicOperation(int operation, int playlistIndex, String playlistName, ArrayList<Integer> musicIds) {
+        getHuaweiMusicManager().onMusicOperation(operation, playlistIndex, playlistName,  musicIds);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiSyncState.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiSyncState.java
new file mode 100644
index 0000000..f03fb70
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiSyncState.java
@@ -0,0 +1,106 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.model.RecordedDataTypes;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+class HuaweiSyncState {
+    private static final Logger LOG = LoggerFactory.getLogger(HuaweiSyncState.class);
+
+    private final HuaweiSupportProvider supportProvider;
+    private final List<Integer> syncQueue = new ArrayList<>(2);
+
+    private boolean activitySync = false;
+    private boolean p2pSync = false;
+    private boolean workoutSync = false;
+    private int workoutGpsDownload = 0;
+
+    public HuaweiSyncState(HuaweiSupportProvider supportProvider) {
+        this.supportProvider = supportProvider;
+    }
+
+    public void addActivitySyncToQueue() {
+        if (syncQueue.contains(RecordedDataTypes.TYPE_ACTIVITY))
+            LOG.info("Activity type sync already queued, ignoring");
+        else
+            syncQueue.add(RecordedDataTypes.TYPE_ACTIVITY);
+    }
+
+    public void addWorkoutSyncToQueue() {
+        if (syncQueue.contains(RecordedDataTypes.TYPE_GPS_TRACKS))
+            LOG.info("Workout type sync already queued, ignoring");
+        else
+            syncQueue.add(RecordedDataTypes.TYPE_GPS_TRACKS);
+    }
+
+    public int getCurrentSyncType() {
+        if (syncQueue.isEmpty())
+            return -1;
+        return syncQueue.get(0);
+    }
+
+    public void setActivitySync(boolean state) {
+        LOG.debug("Set activity sync state to {}", state);
+        this.activitySync = state;
+        if (!state && !this.p2pSync) {
+            this.syncQueue.remove((Integer) RecordedDataTypes.TYPE_ACTIVITY);
+            supportProvider.fetchRecodedDataFromQueue();
+        }
+        updateState();
+    }
+
+    public void setP2pSync(boolean state) {
+        LOG.debug("Set p2p sync state to {}", state);
+        this.p2pSync = state;
+        if (!state && !this.activitySync) {
+            this.syncQueue.remove((Integer) RecordedDataTypes.TYPE_ACTIVITY);
+            supportProvider.fetchRecodedDataFromQueue();
+        }
+        updateState();
+    }
+
+    public void setWorkoutSync(boolean state) {
+        LOG.debug("Set workout sync state to {}", state);
+        this.workoutSync = state;
+        if (!state && this.workoutGpsDownload == 0) {
+            this.syncQueue.remove((Integer) RecordedDataTypes.TYPE_GPS_TRACKS);
+            supportProvider.fetchRecodedDataFromQueue();
+        }
+        updateState();
+    }
+
+    public void startWorkoutGpsDownload() {
+        this.workoutGpsDownload += 1;
+        LOG.debug("Add GPS download: {}", this.workoutGpsDownload);
+    }
+
+    public void stopWorkoutGpsDownload() {
+        this.workoutGpsDownload -= 1;
+        LOG.debug("Subtract GPS download: {}", this.workoutGpsDownload);
+        if (this.workoutGpsDownload == 0 && !this.workoutSync) {
+            this.syncQueue.remove((Integer) RecordedDataTypes.TYPE_GPS_TRACKS);
+            supportProvider.fetchRecodedDataFromQueue();
+        }
+        updateState();
+    }
+
+    public void updateState() {
+        updateState(true);
+    }
+
+    public void updateState(boolean needSync) {
+        if (!activitySync && !p2pSync && !workoutSync && workoutGpsDownload == 0) {
+            if (supportProvider.getDevice().isBusy()) {
+                supportProvider.getDevice().unsetBusyTask();
+                supportProvider.getDevice().sendDeviceUpdateIntent(supportProvider.getContext());
+            }
+            if (needSync)
+                GB.signalActivityDataFinish(supportProvider.getDevice());
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiUploadManager.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiUploadManager.java
new file mode 100644
index 0000000..cf00965
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiUploadManager.java
@@ -0,0 +1,211 @@
+/*  Copyright (C) 2024 Vitalii Tomin
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FileUpload;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FileUpload.FileUploadParams;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class HuaweiUploadManager {
+    private static final Logger LOG = LoggerFactory.getLogger(HuaweiUploadManager.class);
+
+    public interface FileUploadCallback {
+        void onUploadStart();
+        void onUploadProgress(int progress);
+        void onUploadComplete();
+        void onError(int code);
+    }
+
+    public static class FileUploadInfo {
+        private byte[] fileBin;
+        private byte[] fileSHA256;
+        private byte fileType = 1; // 1 - watchface, 2 - music, 3 - png for background , 7 - app
+        private int fileSize = 0;
+        private byte fileId = 0; // get on incoming (2803)
+
+        private String fileName = ""; //FIXME generate random name
+
+        private String srcPackage = null;
+        private String dstPackage = null;
+        private String srcFingerprint = null;
+        private String dstFingerprint = null;
+
+        private int currentUploadPosition = 0;
+        private int uploadChunkSize = 0;
+
+        private FileUploadCallback fileUploadCallback = null;
+
+        //ack values set from 28 4 response
+        private FileUploadParams fileUploadParams = null;
+
+
+        public FileUploadCallback getFileUploadCallback() {
+            return fileUploadCallback;
+        }
+
+        public void setFileUploadCallback(FileUploadCallback fileUploadCallback) {
+            this.fileUploadCallback = fileUploadCallback;
+        }
+
+        public void setFileUploadParams(FileUploadParams params) {
+            this.fileUploadParams = params;
+        }
+
+        public int getUnitSize() {
+            return fileUploadParams.unit_size;
+        }
+
+        public boolean getEncrypt() {
+            return fileUploadParams.no_encrypt == 0;
+        }
+
+        public void setBytes(byte[] uploadArray) {
+
+            this.fileSize = uploadArray.length;
+            this.fileBin = uploadArray;
+
+            try {
+                MessageDigest m = MessageDigest.getInstance("SHA256");
+                m.update(fileBin, 0, fileBin.length);
+                fileSHA256 =  m.digest();
+            } catch (NoSuchAlgorithmException e) {
+                LOG.error("Digest algorithm not found.", e);
+                return;
+            }
+
+            currentUploadPosition = 0;
+            uploadChunkSize = 0;
+
+            LOG.info("File ready for upload, SHA256: "+ GB.hexdump(fileSHA256) + " fileName: " + fileName + " filetype: ", fileType);
+
+        }
+
+        public int getFileSize() {
+            return fileSize;
+        }
+
+        public String getFileName() {
+            return this.fileName;
+        }
+
+        public void setFileName(String fileName) {
+            this.fileName = fileName;
+        }
+
+        public byte getFileType() {
+            return this.fileType;
+        }
+
+        public void setFileType(byte fileType) {
+            this.fileType = fileType;
+        }
+
+        public byte getFileId() {
+            return fileId;
+        }
+
+        public void setFileId(byte fileId) {
+            this.fileId = fileId;
+        }
+
+        public String getSrcPackage() { return srcPackage; }
+
+        public void setSrcPackage(String srcPackage) { this.srcPackage = srcPackage; }
+
+        public String getDstPackage() { return dstPackage; }
+
+        public void setDstPackage(String dstPackage) { this.dstPackage = dstPackage; }
+
+        public String getSrcFingerprint() { return srcFingerprint; }
+
+        public void setSrcFingerprint(String srcFingerprint) { this.srcFingerprint = srcFingerprint; }
+
+        public String getDstFingerprint() { return dstFingerprint;}
+
+        public void setDstFingerprint(String dstFingerprint) { this.dstFingerprint = dstFingerprint;}
+
+        public byte[] getFileSHA256() {
+            return fileSHA256;
+        }
+
+        public void setUploadChunkSize(int chunkSize) {
+            uploadChunkSize = chunkSize;
+        }
+
+        public void setCurrentUploadPosition (int pos) {
+            currentUploadPosition = pos;
+        }
+
+        public int getCurrentUploadPosition() {
+            return currentUploadPosition;
+        }
+
+        public byte[] getCurrentChunk() {
+            byte[] ret = new byte[uploadChunkSize];
+            System.arraycopy(fileBin, currentUploadPosition, ret, 0, uploadChunkSize);
+            return ret;
+        }
+    }
+
+    private final HuaweiSupportProvider support;
+
+    FileUploadInfo fileUploadInfo = null;
+
+    public HuaweiUploadManager(HuaweiSupportProvider support) {
+        this.support=support;
+    }
+
+    public FileUploadInfo getFileUploadInfo() {
+        return fileUploadInfo;
+    }
+
+    public void setFileUploadInfo(FileUploadInfo fileUploadInfo) {
+        this.fileUploadInfo = fileUploadInfo;
+    }
+
+    public void setDeviceBusy() {
+        final GBDevice device = support.getDevice();
+        if(fileUploadInfo != null && fileUploadInfo.fileType == FileUpload.Filetype.watchface) {
+            device.setBusyTask(support.getContext().getString(R.string.uploading_watchface));
+        } else {
+            device.setBusyTask(support.getContext().getString(R.string.updating_firmware));
+        }
+        device.sendDeviceUpdateIntent(support.getContext());
+    }
+
+    public void unsetDeviceBusy() {
+        final GBDevice device = support.getDevice();
+        if (device != null && device.isConnected()) {
+            if (device.isBusy()) {
+                device.unsetBusyTask();
+                device.sendDeviceUpdateIntent(support.getContext());
+            }
+            device.sendDeviceUpdateIntent(support.getContext());
+        }
+    }
+
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiWatchfaceManager.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiWatchfaceManager.java
new file mode 100644
index 0000000..8e39834
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiWatchfaceManager.java
@@ -0,0 +1,304 @@
+/*  Copyright (C) 2024 Vitalii Tomin
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.w3c.dom.Document;
+import org.xml.sax.InputSource;
+
+import java.io.IOException;
+import java.io.StringReader;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.UUID;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Watchface;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetWatchfacesList;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetWatchfacesNames;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.Request;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendWatchfaceOperation;
+
+public class HuaweiWatchfaceManager
+{
+    Logger LOG = LoggerFactory.getLogger(HuaweiCoordinator.class);
+
+    public static class Resolution {
+
+        Map<String, Object> map = new HashMap<>();
+        public Resolution() {
+            //             "height*width"
+            map.put("HWHD01", "390*390");
+            map.put("HWHD02", "454*454");
+            map.put("HWHD03", "240*120");
+            map.put("HWHD04", "160*80");
+            map.put("HWHD05", "460*188");
+            map.put("HWHD06", "456*280");
+            map.put("HWHD07", "368*194");
+            map.put("HWHD08", "320*320");
+            map.put("HWHD09", "466*466");
+            map.put("HWHD10", "360*320");
+            map.put("HWHD11", "480*336");
+            map.put("HWHD12", "240*240");
+            map.put("HWHD13", "480*408");
+        }
+
+        public boolean  isValid(String themeVersion, String screenResolution) {
+            if(!map.containsKey(themeVersion))
+                return false;
+            String screen = map.get(themeVersion).toString();
+            if (screenResolution.equals(screen)) {
+                return true;
+            } else {
+                return false;
+            }
+        }
+
+        public String screenByThemeVersion(String themeVersion) {
+            if(!map.containsKey(themeVersion))
+                return "0x0";
+            String screen = map.get(themeVersion).toString();
+            return screen;
+        }
+
+    }
+
+    public static class WatchfaceDescription {
+
+        public String title;
+        public String title_cn;
+        public String author;
+        public String designer;
+        public String screen;
+        public String version;
+        public String font;
+        public String font_cn;
+
+        public WatchfaceDescription(String xmlStr) {
+            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+            DocumentBuilder builder;
+            try {
+                builder = factory.newDocumentBuilder();
+                Document doc = builder.parse(new InputSource(new StringReader(
+                        xmlStr)));
+
+                this.title = doc.getElementsByTagName("title").item(0).getTextContent();
+                this.title_cn = doc.getElementsByTagName("title-cn").item(0).getTextContent();
+                this.author = doc.getElementsByTagName("author").item(0).getTextContent();
+                this.designer = doc.getElementsByTagName("designer").item(0).getTextContent();
+                this.screen = doc.getElementsByTagName("screen").item(0).getTextContent();
+                this.version = doc.getElementsByTagName("version").item(0).getTextContent();
+                this.font = doc.getElementsByTagName("font").item(0).getTextContent();
+                this.font_cn = doc.getElementsByTagName("font-cn").item(0).getTextContent();
+
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
+    private List<Watchface.InstalledWatchfaceInfo> installedWatchfaceInfoList;
+    private HashMap<String, String> watchfacesNames;
+
+    private HuaweiSupportProvider support;
+
+    public HuaweiWatchfaceManager(HuaweiSupportProvider support) {
+        this.support = support;
+    }
+
+    public void setInstalledWatchfaceInfoList(List<Watchface.InstalledWatchfaceInfo> list) {
+        this.installedWatchfaceInfoList = list;
+    }
+
+    public List<Watchface.InstalledWatchfaceInfo> getInstalledWatchfaceInfoList()
+    {
+        return installedWatchfaceInfoList;
+    }
+
+    public void  setWatchfacesNames(HashMap<String, String> map) {
+        this.watchfacesNames = map;
+    }
+
+
+    public String getRandomName() {
+        Random random = new Random();
+
+        String res="";
+        for (int i = 0; i < 9; i++) {
+            int ran = random.nextInt(9);
+            res += String.valueOf(ran);
+        }
+
+        res += "_1.0.0";
+        return res;
+    }
+
+    public static UUID toWatchfaceUUID(final String id) {
+        // Watchface IDs are numbers as strings - pad them to the right with F
+        // and encode as UUID
+        final String padded = String.format("%-32s", id).replace(' ', 'F');
+        return UUID.fromString(
+                padded.substring(0, 8) + "-" +
+                        padded.substring(8, 12) + "-" +
+                        padded.substring(12, 16) + "-" +
+                        padded.substring(16, 20) + "-" +
+                        padded.substring(20, 32)
+        );
+    }
+
+    public static String toWatchfaceId(final UUID uuid) {
+        return uuid.toString()
+                .replaceAll("-", "")
+                .replaceAll("f", "")
+                .replaceAll("F", "");
+    }
+
+    public void handleWatchfaceList() {
+
+        final List<GBDeviceApp> gbDeviceApps = new ArrayList<>();
+
+        for (final Watchface.InstalledWatchfaceInfo watchfaceInfo : installedWatchfaceInfoList) {
+            final UUID uuid = toWatchfaceUUID(watchfaceInfo.fileName);
+            GBDeviceApp gbDeviceApp = new GBDeviceApp(
+                    uuid,
+                    watchfacesNames.get(watchfaceInfo.fileName),
+                    "",
+                    "",
+                    GBDeviceApp.Type.WATCHFACE
+            );
+            gbDeviceApps.add(gbDeviceApp);
+        }
+        support.setGbWatchFaces(gbDeviceApps);
+    }
+
+    public void updateWatchfaceNames() {
+        Request.RequestCallback finalizeReq = new Request.RequestCallback() {
+            @Override
+            public void call() {
+                handleWatchfaceList();
+            }
+
+            @Override
+            public void handleException(Request.ResponseParseException e) {
+                LOG.error("Watchface update list exception", e);
+            }
+        };
+
+        try {
+            GetWatchfacesNames getWatchfacesNames = new GetWatchfacesNames(support, installedWatchfaceInfoList);
+            getWatchfacesNames.setFinalizeReq(finalizeReq);
+            getWatchfacesNames.doPerform();
+        } catch (IOException e) {
+            LOG.error("Could not get watchface names", e);
+        }
+
+    }
+
+    public void requestWatchfaceList() {
+        Request.RequestCallback finalizeReq = new Request.RequestCallback() {
+            @Override
+            public void call() {
+                updateWatchfaceNames();
+            }
+
+            @Override
+            public void handleException(Request.ResponseParseException e) {
+                LOG.error("Watchface update list exception", e);
+            }
+        };
+
+
+        try {
+            GetWatchfacesList getWatchfacesList = new GetWatchfacesList(support);
+            getWatchfacesList.setFinalizeReq(finalizeReq);
+            getWatchfacesList.doPerform();
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+
+    };
+
+    public void setWatchface(UUID uuid) {
+        Request.RequestCallback finalizeReq = new Request.RequestCallback() {
+            @Override
+            public void call() {
+                requestWatchfaceList();
+            }
+
+            @Override
+            public void handleException(Request.ResponseParseException e) {
+                LOG.error("Watchface update list exception", e);
+            }
+        };
+
+        try {
+            SendWatchfaceOperation sendWatchfaceOperation = new SendWatchfaceOperation(support,
+                    getFullFileName(uuid),
+                    Watchface.WatchfaceOperation.operationActive);
+            sendWatchfaceOperation.setFinalizeReq(finalizeReq);
+            sendWatchfaceOperation.doPerform();
+        } catch (IOException e) {
+            LOG.error("Could not set watchface ", getFullFileName(uuid), e );
+        }
+    }
+
+    public void deleteWatchface(UUID uuid) {
+        Request.RequestCallback finalizeReq = new Request.RequestCallback() {
+            @Override
+            public void call() {
+                requestWatchfaceList();
+            }
+
+            @Override
+            public void handleException(Request.ResponseParseException e) {
+                LOG.error("Watchface update list exception", e);
+            }
+        };
+
+        try {
+            SendWatchfaceOperation sendWatchfaceOperation = new SendWatchfaceOperation(support,
+                    getFullFileName(uuid),
+                    Watchface.WatchfaceOperation.operationDelete);
+            sendWatchfaceOperation.setFinalizeReq(finalizeReq);
+            sendWatchfaceOperation.doPerform();
+        } catch (IOException e) {
+            LOG.error("Could not delete watchface", getFullFileName(uuid), e);
+        }
+    }
+
+    private String getFullFileName(UUID uuid) {
+        String name = toWatchfaceId(uuid);
+        String version = "";
+        for (final Watchface.InstalledWatchfaceInfo watchfaceInfo : installedWatchfaceInfoList) {
+            if (watchfaceInfo.fileName.equals(name)) {
+                version = watchfaceInfo.version;
+                break;
+            }
+        }
+        String filename = name + "_" + version;
+        return filename;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiWeatherManager.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiWeatherManager.java
new file mode 100644
index 0000000..ecf258a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiWeatherManager.java
@@ -0,0 +1,302 @@
+/*  Copyright (C) 2024 Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei;
+
+import android.widget.Toast;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Date;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Weather;
+import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.Request;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendGpsAndTimeToDeviceRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendWeatherCurrentRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendWeatherDeviceRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendWeatherErrorRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendWeatherExtendedSupportRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendWeatherForecastRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendWeatherStartRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendWeatherSunMoonSupportRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendWeatherSupportRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendWeatherUnitRequest;
+import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class HuaweiWeatherManager {
+    private static final Logger LOG = LoggerFactory.getLogger(HuaweiWeatherManager.class);
+
+    private final HuaweiSupportProvider supportProvider;
+    private boolean syncInProgress;
+
+    public HuaweiWeatherManager(HuaweiSupportProvider supportProvider) {
+        this.supportProvider = supportProvider;
+        this.syncInProgress = false;
+    }
+
+    public Weather.WeatherIcon openWeatherMapConditionCodeToHuaweiIcon(int conditionCode) {
+        // More exact first, groups after
+        switch (conditionCode) {
+            case 500:
+                return Weather.WeatherIcon.LIGHT_RAIN;
+            case 501:
+                return Weather.WeatherIcon.RAIN;
+            case 502:
+                return Weather.WeatherIcon.HEAVY_RAIN;
+            case 503:
+                return Weather.WeatherIcon.RAIN_STORM;
+            case 504:
+                return Weather.WeatherIcon.SEVERE_RAIN_STORMS;
+            case 511:
+                return Weather.WeatherIcon.FREEZING_RAIN;
+            case 600:
+                return Weather.WeatherIcon.LIGHT_SNOW;
+            case 601:
+                return Weather.WeatherIcon.SNOW;
+            case 602:
+                return Weather.WeatherIcon.HEAVY_SNOW;
+            case 611:
+                return Weather.WeatherIcon.SLEET;
+            case 701:
+            case 741:
+                return Weather.WeatherIcon.FOG;
+            case 721:
+                return Weather.WeatherIcon.HAZY;
+            case 751:
+                return Weather.WeatherIcon.SAND;
+            case 761:
+                return Weather.WeatherIcon.DUST;
+            case 800:
+                return Weather.WeatherIcon.SUNNY;
+            case 801:
+            case 802:
+                return Weather.WeatherIcon.CLOUDY;
+            case 803:
+            case 804:
+                return Weather.WeatherIcon.OVERCAST;
+        }
+        if (conditionCode >= 200 && conditionCode < 300)
+            return Weather.WeatherIcon.THUNDERSTORMS;
+        if (conditionCode >= 300 && conditionCode < 400)
+            return Weather.WeatherIcon.LIGHT_RAIN;
+        if (conditionCode >= 500 && conditionCode < 600)
+            return Weather.WeatherIcon.RAIN;
+        if (conditionCode >= 600 && conditionCode < 700)
+            return Weather.WeatherIcon.SNOW;
+        return Weather.WeatherIcon.UNKNOWN;
+    }
+
+    private void handleException(Request.ResponseParseException e) {
+        LOG.error("Error in weather sending", e);
+
+        synchronized (this) {
+            // Allow a new sync to start again
+            this.syncInProgress = false;
+        }
+    }
+
+    // This is to reset the syncInProgress flag when an error occurs
+    private final Request.RequestCallback errorHandler = new Request.RequestCallback() {
+        @Override
+        public void handleException(Request.ResponseParseException e) {
+            HuaweiWeatherManager.this.handleException(e);
+        }
+    };
+
+    // This is to reset the syncInProgress flag when everything goes well, or an error occurs
+    // Only for the last request in the chain
+    private final Request.RequestCallback lastHandler = new Request.RequestCallback() {
+        @Override
+        public void call() {
+            synchronized (this) {
+                HuaweiWeatherManager.this.syncInProgress = false;
+            }
+        }
+
+        @Override
+        public void handleException(Request.ResponseParseException e) {
+            HuaweiWeatherManager.this.handleException(e);
+        }
+    };
+
+    private boolean timeOutOfDateInterval(int check, Date start, Date end) {
+        return check * 1000L <= start.getTime() || check * 1000L > end.getTime();
+    }
+
+    /**
+     * Some erroneous data will stop the watch from showing any weather data at all, this attempts
+     * to remove such data.
+     */
+    private void fixupWeather(WeatherSpec weatherSpec) {
+        // Ignore sun/moon data if they are not in the correct day
+        // If this is wrong the watch does not display any weather data at all
+        Date currentDay = DateTimeUtils.dayStart(DateTimeUtils.todayUTC());
+        Date nextDay = DateTimeUtils.shiftByDays(currentDay, 1);
+
+        if (timeOutOfDateInterval(weatherSpec.sunRise, currentDay, nextDay)) {
+            LOG.warn("Sun rise for today out of bounds: {}", DateTimeUtils.parseTimeStamp(weatherSpec.sunRise));
+            weatherSpec.sunRise = 0;
+        }
+        if (timeOutOfDateInterval(weatherSpec.sunSet, currentDay, nextDay)) {
+            LOG.warn("Sun set for today out of bounds: {}", DateTimeUtils.parseTimeStamp(weatherSpec.sunSet));
+            weatherSpec.sunSet = 0;
+        }
+        if (timeOutOfDateInterval(weatherSpec.moonRise, currentDay, nextDay)) {
+            LOG.warn("Moon rise for today out of bounds: {}", DateTimeUtils.parseTimeStamp(weatherSpec.moonRise));
+            weatherSpec.moonRise = 0;
+        }
+        if (timeOutOfDateInterval(weatherSpec.moonSet, currentDay, nextDay)) {
+            LOG.warn("Moon set for today out of bounds: {}", DateTimeUtils.parseTimeStamp(weatherSpec.moonSet));
+            weatherSpec.moonSet = 0;
+        }
+
+        for (WeatherSpec.Daily point : weatherSpec.forecasts) {
+            currentDay = nextDay;
+            nextDay = DateTimeUtils.shiftByDays(currentDay, 1);
+
+            if (timeOutOfDateInterval(point.sunRise, currentDay, nextDay)) {
+                LOG.warn("Sun rise for {} out of bounds: {}", currentDay, DateTimeUtils.parseTimeStamp(point.sunRise));
+                point.sunRise = 0;
+            }
+            if (timeOutOfDateInterval(point.sunSet, currentDay, nextDay)) {
+                LOG.warn("Sun set for {} out of bounds: {}", currentDay, DateTimeUtils.parseTimeStamp(point.sunSet));
+                point.sunSet = 0;
+            }
+            if (timeOutOfDateInterval(point.moonRise, currentDay, nextDay)) {
+                LOG.warn("Moon rise for {} out of bounds: {}", currentDay, DateTimeUtils.parseTimeStamp(point.moonRise));
+                point.moonRise = 0;
+            }
+            if (timeOutOfDateInterval(point.moonSet, currentDay, nextDay)) {
+                LOG.warn("Moon set for {} out of bounds: {}", currentDay, DateTimeUtils.parseTimeStamp(point.moonSet));
+                point.moonSet = 0;
+            }
+        }
+    }
+
+    public void sendWeather(WeatherSpec weatherSpec) {
+        // Initialize weather settings and send weather
+        if (!supportProvider.getHuaweiCoordinator().supportsWeather()) {
+            LOG.error("onSendWeather called while weather is not supported.");
+            return;
+        }
+
+        // Do not allow sending weather if it's still going
+        synchronized (this) {
+            if (syncInProgress) {
+                LOG.warn("Weather sync already in progress, ignoring next one");
+                return;
+            }
+            syncInProgress = true;
+        }
+
+        fixupWeather(weatherSpec);
+
+        Weather.Settings weatherSettings = new Weather.Settings();
+        weatherSettings.uvIndexSupported = supportProvider.getHuaweiCoordinator().supportsWeatherUvIndex();
+
+        SendWeatherStartRequest weatherStartRequest = new SendWeatherStartRequest(supportProvider, weatherSettings);
+        weatherStartRequest.setFinalizeReq(errorHandler);
+        weatherStartRequest.setupTimeoutUntilNext(1000);
+        Request lastRequest = weatherStartRequest;
+
+        if (supportProvider.getHuaweiCoordinator().supportsWeatherUnit()) {
+            SendWeatherUnitRequest weatherUnitRequest = new SendWeatherUnitRequest(supportProvider);
+            weatherUnitRequest.setFinalizeReq(errorHandler);
+            lastRequest.nextRequest(weatherUnitRequest);
+            lastRequest = weatherUnitRequest;
+        }
+
+        SendWeatherSupportRequest weatherSupportRequest = new SendWeatherSupportRequest(supportProvider, weatherSettings);
+        weatherSupportRequest.setFinalizeReq(errorHandler);
+        lastRequest.nextRequest(weatherSupportRequest);
+        lastRequest = weatherSupportRequest;
+
+        if (supportProvider.getHuaweiCoordinator().supportsWeatherExtended()) {
+            SendWeatherExtendedSupportRequest weatherExtendedSupportRequest = new SendWeatherExtendedSupportRequest(supportProvider, weatherSettings);
+            weatherExtendedSupportRequest.setFinalizeReq(errorHandler);
+            lastRequest.nextRequest(weatherExtendedSupportRequest);
+            lastRequest = weatherExtendedSupportRequest;
+        }
+
+        if (supportProvider.getHuaweiCoordinator().supportsWeatherMoonRiseSet()) {
+            SendWeatherSunMoonSupportRequest weatherSunMoonSupportRequest = new SendWeatherSunMoonSupportRequest(supportProvider, weatherSettings);
+            weatherSunMoonSupportRequest.setFinalizeReq(errorHandler);
+            lastRequest.nextRequest(weatherSunMoonSupportRequest);
+            lastRequest = weatherSunMoonSupportRequest;
+        }
+
+        // End of initialization and start of actually sending weather
+
+        SendWeatherCurrentRequest sendWeatherCurrentRequest = new SendWeatherCurrentRequest(supportProvider, weatherSettings, weatherSpec);
+        sendWeatherCurrentRequest.setFinalizeReq(errorHandler);
+        lastRequest.nextRequest(sendWeatherCurrentRequest);
+        lastRequest = sendWeatherCurrentRequest;
+
+        SendGpsAndTimeToDeviceRequest sendGpsAndTimeToDeviceRequest = new SendGpsAndTimeToDeviceRequest(supportProvider);
+        sendGpsAndTimeToDeviceRequest.setFinalizeReq(errorHandler);
+        lastRequest.nextRequest(sendGpsAndTimeToDeviceRequest);
+        lastRequest = sendGpsAndTimeToDeviceRequest;
+
+        if (supportProvider.getHuaweiCoordinator().supportsWeatherForecasts()) {
+            SendWeatherForecastRequest sendWeatherForecastRequest = new SendWeatherForecastRequest(supportProvider, weatherSettings, weatherSpec);
+            sendWeatherForecastRequest.setFinalizeReq(errorHandler);
+            lastRequest.nextRequest(sendWeatherForecastRequest);
+            lastRequest = sendWeatherForecastRequest;
+        }
+
+        lastRequest.setFinalizeReq(lastHandler);
+
+        try {
+            weatherStartRequest.doPerform();
+        } catch (IOException e) {
+            // TODO: Use translatable string
+            GB.toast(supportProvider.getContext(), "Failed to send weather", Toast.LENGTH_SHORT, GB.ERROR, e);
+            LOG.error("Failed to send weather", e);
+        }
+    }
+
+    public void handleAsyncMessage(HuaweiPacket response) {
+        if (response.getTlv().getInteger(0x7f, -1) == 0x000186AA) {
+            // Send weather
+            final ArrayList<WeatherSpec> specs = new ArrayList<>(nodomain.freeyourgadget.gadgetbridge.model.Weather.getInstance().getWeatherSpecs());
+            if (specs.isEmpty()) {
+                LOG.debug("Weather specs empty, returning that weather is disabled.");
+                try {
+                    new SendWeatherErrorRequest(supportProvider, Weather.ErrorCode.WEATHER_DISABLED).doPerform();
+                } catch (IOException e) {
+                    LOG.error("Could not send weather error request.", e);
+                }
+                return;
+            }
+            this.sendWeather(specs.get(0));
+            return;
+        }
+
+        // Send back ok
+        try {
+            SendWeatherDeviceRequest sendWeatherDeviceRequest = new SendWeatherDeviceRequest(supportProvider);
+            sendWeatherDeviceRequest.doPerform();
+        } catch (IOException e) {
+            LOG.error("Could not send weather device request", e);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiWorkoutGbParser.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiWorkoutGbParser.java
new file mode 100644
index 0000000..ffe48ce
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiWorkoutGbParser.java
@@ -0,0 +1,1004 @@
+/*  Copyright (C) 2024 Damien Gaignon, Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei;
+
+import android.content.Context;
+import android.widget.Toast;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+import de.greenrobot.dao.query.CloseableListIterator;
+import de.greenrobot.dao.query.QueryBuilder;
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.SettingsActivity;
+import nodomain.freeyourgadget.gadgetbridge.activities.workouts.entries.ActivitySummaryProgressEntry;
+import nodomain.freeyourgadget.gadgetbridge.activities.workouts.entries.ActivitySummaryTableRowEntry;
+import nodomain.freeyourgadget.gadgetbridge.activities.workouts.entries.ActivitySummaryValue;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HeartRateZonesConfig;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiSportHRZones;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Workout;
+import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutDataSample;
+import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutDataSampleDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutPaceSample;
+import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutPaceSampleDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutSummarySample;
+import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutSummarySampleDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutSwimSegmentsSample;
+import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutSwimSegmentsSampleDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.User;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryData;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryParser;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
+
+/**
+ * This class parses the Huawei workouts into the table GB uses to show the workouts
+ * It also re-parses the unknown data from the workout tables
+ * It is a separate class so it can easily be used to re-parse the data without database migrations
+ */
+public class HuaweiWorkoutGbParser implements ActivitySummaryParser {
+    private static final Logger LOG = LoggerFactory.getLogger(HuaweiWorkoutGbParser.class);
+
+    // TODO: Might be nicer to propagate the exceptions, so they can be handled upstream
+
+    private final GBDevice gbDevice;
+    private final Context context;
+
+    public HuaweiWorkoutGbParser(final GBDevice gbDevice, final Context context) {
+        this.gbDevice = gbDevice;
+        this.context = context;
+    }
+
+    @Override
+    public BaseActivitySummary parseBinaryData(final BaseActivitySummary summary, final boolean forDetails) {
+        if (!forDetails) {
+            // Our parsing is too slow, especially without a RecyclerView
+            return summary;
+        }
+
+        // Find the existing HuaweiWorkoutSummarySample
+        try (DBHandler db = GBApplication.acquireDB()) {
+            final DaoSession session = db.getDaoSession();
+            final Device device = DBHelper.getDevice(gbDevice, session);
+            final User user = DBHelper.getUser(session);
+
+            QueryBuilder<HuaweiWorkoutSummarySample> qb = session.getHuaweiWorkoutSummarySampleDao().queryBuilder();
+            qb.where(HuaweiWorkoutSummarySampleDao.Properties.StartTimestamp.eq(summary.getStartTime().getTime() / 1000));
+            qb.where(HuaweiWorkoutSummarySampleDao.Properties.DeviceId.eq(device.getId()));
+            qb.where(HuaweiWorkoutSummarySampleDao.Properties.UserId.eq(user.getId()));
+            final List<HuaweiWorkoutSummarySample> huaweiSummaries = qb.build().list();
+            if (huaweiSummaries.isEmpty()) {
+                LOG.warn("Failed to find huawei summary for {}", summary.getStartTime());
+                return summary;
+            }
+            updateBaseSummary(session, huaweiSummaries.get(0), summary);
+        } catch (Exception e) {
+            LOG.error("Failed to update summary");
+        }
+
+        return summary;
+    }
+
+    public enum HuaweiActivityType {
+        // Based on nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.enums.GarminSport
+
+        RUNNING(1, ActivityKind.RUNNING),
+        WALKING(2, ActivityKind.WALKING),
+        CYCLING(3, ActivityKind.CYCLING),
+        MOUNTAIN_HIKE(4, ActivityKind.MOUNTAIN_HIKE),
+        INDOOR_RUN(5, ActivityKind.INDOOR_RUNNING),
+        POOL_SWIM(6, ActivityKind.POOL_SWIM),
+        INDOOR_CYCLE(7, ActivityKind.INDOOR_CYCLING),
+        OPEN_WATER_SWIM(8, ActivityKind.SWIMMING_OPENWATER),
+        INDOOR_WALK(13, ActivityKind.INDOOR_WALKING),
+        HIKING(14, ActivityKind.HIKING),
+        JUMP_ROPING(21, ActivityKind.JUMP_ROPING),
+        PING_PONG(128, ActivityKind.PINGPONG),
+        BADMINTON(129, ActivityKind.BADMINTON),
+        TENNIS(130, ActivityKind.TENNIS),
+        SOCCER(131, ActivityKind.SOCCER),
+        BASKETBALL(132, ActivityKind.BASKETBALL),
+        VOLLEYBALL(133, ActivityKind.VOLLEYBALL),
+        ELLIPTICAL_TRAINER(134, ActivityKind.ELLIPTICAL_TRAINER),
+        ROWING_MACHINE(135, ActivityKind.ROWING_MACHINE),
+        STEPPER(136, ActivityKind.STEPPER),
+        YOGA(137, ActivityKind.YOGA),
+        PILATES(138, ActivityKind.PILATES),
+        AEROBICS(139, ActivityKind.AEROBICS),
+        STRENGTH_TRAINING(140, ActivityKind.STRENGTH_TRAINING),
+        SPINNING(141, ActivityKind.SPINNING),
+        AIR_WALKER(142, ActivityKind.AIR_WALKER),
+        HIIT(143, ActivityKind.HIIT),
+        CROSSFIT(145, ActivityKind.CROSSFIT),
+        FUNCTIONAL_TRAINING(146, ActivityKind.FUNCTIONAL_TRAINING),
+        PHYSICAL_TRAINING(147, ActivityKind.PHYSICAL_TRAINING),
+        TAEKWONDO(148, ActivityKind.TAEKWONDO),
+        BOXING(149, ActivityKind.BOXING),
+        FREE_SPARRING(150, ActivityKind.FREE_SPARRING),
+        KARATE(151, ActivityKind.KARATE),
+        FENCING(152, ActivityKind.FENCING),
+        BELLY_DANCE(153, ActivityKind.BELLY_DANCE),
+        JAZZ_DANCE(154, ActivityKind.JAZZ_DANCE),
+        LATIN_DANCE(155, ActivityKind.LATIN_DANCE),
+        BALLET(156, ActivityKind.BALLET),
+        CORE_TRAINING(157, ActivityKind.CORE_TRAINING),
+        BODY_COMBAT(158, ActivityKind.BODY_COMBAT),
+        KENDO(159, ActivityKind.KENDO),
+        SINGLE_BAR(160, ActivityKind.HORIZONTAL_BAR),
+        PARALLEL_BARS(161, ActivityKind.PARALLEL_BARS),
+        STREET_DANCE(162, ActivityKind.STREET_DANCE),
+        ROLLER_SKATING(163, ActivityKind.ROLLER_SKATING),
+        MARTIAL_ARTS(164, ActivityKind.MARTIAL_ARTS),
+        PLAZA_DANCING(165, ActivityKind.PLAZA_DANCING),
+        TAI_CHI(166, ActivityKind.TAI_CHI),
+        DANCE(167, ActivityKind.DANCE),
+        HULA_HOOP(168, ActivityKind.HULA_HOOP),
+        FRISBEE(169, ActivityKind.FRISBEE),
+        DARTS(170, ActivityKind.DARTS),
+        ARCHERY(171, ActivityKind.ARCHERY),
+        HORSE_RIDING(172, ActivityKind.HORSE_RIDING),
+        LASER_TAG(173, ActivityKind.LASER_TAG),
+        KITE_FLYING(174, ActivityKind.KITE_FLYING),
+        TUG_OF_WAR(175, ActivityKind.TUG_OF_WAR),
+        SWINGING(176, ActivityKind.SWING),
+        STAIR_CLIMBING(177, ActivityKind.STAIRS),
+        OBSTACLE_RACE(178, ActivityKind.OBSTACLE_RACE),
+        BILLIARD_POOL(179, ActivityKind.BILLIARD_POOL),
+        BOWLING(180, ActivityKind.BOWLING),
+        SHUTTLECOCK(181, ActivityKind.SHUTTLECOCK),
+        HANDBALL(182, ActivityKind.HANDBALL),
+        BASEBALL(183, ActivityKind.BASEBALL),
+        SOFTBALL(184, ActivityKind.SOFTBALL),
+        CRICKET(185, ActivityKind.CRICKET),
+        RUGBY(186, ActivityKind.RUGBY),
+        BEACH_SOCCER(187, ActivityKind.BEACH_SOCCER),
+        BEACH_VOLLEYBALL(188, ActivityKind.BEACH_VOLLEYBALL),
+        GATEBALL(189, ActivityKind.GATEBALL),
+        HOCKEY(190, ActivityKind.HOCKEY),
+        SQUASH(191, ActivityKind.SQUASH),
+        SEPAK_TAKRAW(192, ActivityKind.SEPAK_TAKRAW),
+        DODGEBALL(193, ActivityKind.DODGEBALL),
+        SAILING(194, ActivityKind.SAILING),
+        SURFING(195, ActivityKind.SURFING),
+        FISHING(196, ActivityKind.FISHING),
+        RAFTING(197, ActivityKind.RAFTING),
+        DRAGON_BOATING(198, ActivityKind.DRAGON_BOAT),
+        CANOEING(199, ActivityKind.CANOEING),
+        ROWING(200, ActivityKind.ROWING),
+        WATER_SCOOTER(201, ActivityKind.WATER_SCOOTER),
+        STAND_UP_PADDLEBOARDING(202, ActivityKind.STAND_UP_PADDLEBOARDING),
+        ICE_SKATING(203, ActivityKind.ICE_SKATING),
+        ICE_HOCKEY(204, ActivityKind.ICE_HOCKEY),
+        CURLING(205, ActivityKind.CURLING),
+        BOBSLEIGH(206, ActivityKind.BOBSLEIGH),
+        SLEDDING(207, ActivityKind.SLEDDING),
+        BIATHLON(208, ActivityKind.BIATHLON),
+        SKATEBOARDING(209, ActivityKind.SKATEBOARDING),
+        ROCK_CLIMBING(210, ActivityKind.ROCK_CLIMBING),
+        BUNGEE_JUMPING(211, ActivityKind.BUNGEE_JUMPING),
+        PARKOUR(212, ActivityKind.PARKOUR),
+        BMX(213, ActivityKind.BMX),
+        ORIENTEERING(214, ActivityKind.ORIENTEERING),
+        PARACHUTING(215, ActivityKind.PARACHUTING),
+        MOTOR_AUTO_RACING(216, ActivityKind.AUTO_RACING),
+        ESPORTS(223, ActivityKind.ESPORTS),
+        PADEL(224, ActivityKind.PADEL),
+        OTHER(255, ActivityKind.EXERCISE),
+        ;
+
+        private final byte type;
+        private final ActivityKind activityKind;
+
+        HuaweiActivityType(final int type, final ActivityKind activityKind) {
+            this.type = (byte) type;
+            this.activityKind = activityKind;
+        }
+
+        public ActivityKind getActivityKind() {
+            return activityKind;
+        }
+
+        public byte getType() {
+            return type;
+        }
+
+        public static Optional<HuaweiActivityType> fromByte(final byte type) {
+            for (final HuaweiActivityType value : HuaweiActivityType.values()) {
+                if (value.getType() == type)
+                    return Optional.of(value);
+            }
+            return Optional.empty();
+        }
+    }
+
+    public void parseAllWorkouts() {
+        parseUnknownWorkoutData();
+
+        try (DBHandler db = GBApplication.acquireDB()) {
+            final DaoSession session = db.getDaoSession();
+            QueryBuilder<HuaweiWorkoutSummarySample> qb = session.getHuaweiWorkoutSummarySampleDao().queryBuilder();
+            for (HuaweiWorkoutSummarySample summary : qb.listLazy()) {
+                parseWorkout(session, summary.getWorkoutId(), summary.getDeviceId());
+            }
+        } catch (Exception e) {
+            GB.toast("Exception parsing workouts", Toast.LENGTH_SHORT, GB.ERROR, e);
+            LOG.error("Exception parsing workouts", e);
+        }
+    }
+
+    /**
+     * Parses the unknown data from the workout data table
+     */
+    private static void parseUnknownWorkoutData() {
+        try (DBHandler dbHandler = GBApplication.acquireDB()) {
+            QueryBuilder<HuaweiWorkoutDataSample> qb = dbHandler.getDaoSession().getHuaweiWorkoutDataSampleDao().queryBuilder().where(
+                    HuaweiWorkoutDataSampleDao.Properties.DataErrorHex.notEq("")
+            );
+            for (HuaweiWorkoutDataSample sample : qb.build().listLazy()) {
+                byte[] data = GB.hexStringToByteArray(new String(sample.getDataErrorHex()));
+                Workout.WorkoutData.Response response = new Workout.WorkoutData.Response(data);
+
+                for (Workout.WorkoutData.Response.Data responseData : response.dataList) {
+                    byte[] dataErrorHex;
+                    if (responseData.unknownData == null)
+                        dataErrorHex = null;
+                    else
+                        dataErrorHex = StringUtils.bytesToHex(responseData.unknownData).getBytes(StandardCharsets.UTF_8);
+
+                    HuaweiWorkoutDataSample dataSample = new HuaweiWorkoutDataSample(
+                            sample.getWorkoutId(),
+                            responseData.timestamp,
+                            responseData.heartRate,
+                            responseData.speed,
+                            responseData.stepRate,
+                            responseData.cadence,
+                            responseData.stepLength,
+                            responseData.groundContactTime,
+                            responseData.impact,
+                            responseData.swingAngle,
+                            responseData.foreFootLanding,
+                            responseData.midFootLanding,
+                            responseData.backFootLanding,
+                            responseData.eversionAngle,
+                            responseData.swolf,
+                            responseData.strokeRate,
+                            dataErrorHex,
+                            responseData.calories,
+                            responseData.cyclingPower,
+                            responseData.frequency,
+                            responseData.altitude
+                    );
+
+                    dbHandler.getDaoSession().getHuaweiWorkoutDataSampleDao().insertOrReplace(dataSample);
+                }
+            }
+        } catch (Exception e) {
+            GB.toast("Exception parsing unknown workout data", Toast.LENGTH_SHORT, GB.ERROR, e);
+            LOG.error("Exception parsing unknown workout data", e);
+        }
+    }
+
+    public static ActivityKind huaweiTypeToGbType(byte huaweiType) {
+        final Optional<HuaweiActivityType> type = HuaweiActivityType.fromByte(huaweiType);
+        if (type.isPresent())
+            return type.get().getActivityKind();
+        else
+            return ActivityKind.UNKNOWN;
+    }
+
+    private String getSwimStyle(byte swimType) {
+        switch (swimType) {
+            case 1:
+                return "breaststroke";
+            case 3:
+                return "butterfly";
+            case 4:
+                return "backstroke";
+            case 5:
+                return "medley";
+        }
+        return "freestyle";
+    }
+
+    public void parseWorkout(Long workoutId) {
+        LOG.debug("Parsing workout ID {}", workoutId);
+        if (workoutId == null)
+            return;
+
+        try (DBHandler db = GBApplication.acquireDB()) {
+            final DaoSession session = db.getDaoSession();
+            final Device device = DBHelper.getDevice(gbDevice, session);
+            parseWorkout(session, workoutId, device.getId());
+        } catch (Exception e) {
+            GB.toast("Exception parsing workout data", Toast.LENGTH_SHORT, GB.ERROR, e);
+            LOG.error("Exception parsing workout data", e);
+        }
+    }
+
+    public void parseWorkout(final DaoSession session, final Long workoutId, final long deviceId) {
+        if (workoutId == null)
+            return;
+
+        QueryBuilder<HuaweiWorkoutSummarySample> qbSummary = session.getHuaweiWorkoutSummarySampleDao().queryBuilder().where(
+                HuaweiWorkoutSummarySampleDao.Properties.WorkoutId.eq(workoutId)
+        );
+        List<HuaweiWorkoutSummarySample> summarySamples = qbSummary.build().list();
+        if (summarySamples.size() != 1)
+            return;
+        HuaweiWorkoutSummarySample summary = summarySamples.get(0);
+
+        final BaseActivitySummary baseSummary = ActivitySummaryParser.findOrCreateBaseActivitySummary(
+                session,
+                deviceId,
+                summary.getStartTimestamp()
+        );
+
+        updateBaseSummary(session, summary, baseSummary);
+
+        session.getBaseActivitySummaryDao().insertOrReplace(baseSummary);
+    }
+
+    public void updateBaseSummary(final DaoSession session,
+                                  final HuaweiWorkoutSummarySample summary,
+                                  final BaseActivitySummary baseSummary) {
+        try {
+            QueryBuilder<HuaweiWorkoutDataSample> qbData = session.getHuaweiWorkoutDataSampleDao().queryBuilder().where(
+                    HuaweiWorkoutDataSampleDao.Properties.WorkoutId.eq(summary.getWorkoutId())
+            );
+            List<HuaweiWorkoutDataSample> dataSamples = qbData.build().list();
+
+            QueryBuilder<HuaweiWorkoutPaceSample> qbPace = session.getHuaweiWorkoutPaceSampleDao().queryBuilder().orderAsc(HuaweiWorkoutPaceSampleDao.Properties.PaceIndex).where(
+                    HuaweiWorkoutPaceSampleDao.Properties.WorkoutId.eq(summary.getWorkoutId())
+            );
+
+            QueryBuilder<HuaweiWorkoutSwimSegmentsSample> qbSegments = session.getHuaweiWorkoutSwimSegmentsSampleDao().queryBuilder().orderAsc(HuaweiWorkoutSwimSegmentsSampleDao.Properties.SegmentIndex).where(
+                    HuaweiWorkoutSwimSegmentsSampleDao.Properties.WorkoutId.eq(summary.getWorkoutId())
+            );
+
+            ActivityKind type = huaweiTypeToGbType(summary.getType());
+
+            ActivitySummaryData summaryData = new ActivitySummaryData();
+
+            summaryData.add(ActivitySummaryEntries.CALORIES_BURNT, summary.getCalories(), ActivitySummaryEntries.UNIT_KCAL);
+            summaryData.add(ActivitySummaryEntries.DISTANCE_METERS, summary.getDistance(), ActivitySummaryEntries.UNIT_METERS);
+            summaryData.add(ActivitySummaryEntries.STEPS, summary.getStepCount(), ActivitySummaryEntries.UNIT_STEPS);
+            summaryData.add(ActivitySummaryEntries.ACTIVE_SECONDS, summary.getDuration(), ActivitySummaryEntries.UNIT_SECONDS);
+            //summaryData.add(ActivitySummaryEntries.STATUS, summary.getStatus() & 0xFF, ActivitySummaryEntries.UNIT_NONE);
+            summaryData.add(ActivitySummaryEntries.TYPE, summary.getType() & 0xFF, ActivitySummaryEntries.UNIT_NONE);
+
+            if (summary.getStrokes() != -1) {
+                summaryData.add(ActivitySummaryEntries.STROKES, summary.getStrokes(), ActivitySummaryEntries.UNIT_STROKES);
+            }
+
+            if (summary.getPoolLength() != -1) {
+                summaryData.add(ActivitySummaryEntries.LANE_LENGTH, summary.getPoolLength(), ActivitySummaryEntries.UNIT_CM);
+            }
+
+            if (summary.getLaps() != -1) {
+                summaryData.add(ActivitySummaryEntries.LAPS, summary.getLaps(), ActivitySummaryEntries.UNIT_LAPS);
+            }
+
+            if(summary.getWorkoutLoad() > 0) {
+                summaryData.add(ActivitySummaryEntries.WORKOUT_LOAD, summary.getWorkoutLoad(), ActivitySummaryEntries.UNIT_NONE);
+            }
+
+            if(summary.getWorkoutAerobicEffect() > 0) {
+                summaryData.add(ActivitySummaryEntries.TRAINING_EFFECT_AEROBIC, summary.getWorkoutAerobicEffect() / 10.0, ActivitySummaryEntries.UNIT_NONE);
+            }
+
+            if(summary.getWorkoutAnaerobicEffect() >= 0) {
+                summaryData.add(ActivitySummaryEntries.TRAINING_EFFECT_ANAEROBIC, summary.getWorkoutAnaerobicEffect() / 10.0, ActivitySummaryEntries.UNIT_NONE);
+            }
+
+            if(summary.getRecoveryTime() > 0) {
+                summaryData.add(ActivitySummaryEntries.RECOVERY_TIME, summary.getRecoveryTime() / 60.0, ActivitySummaryEntries.UNIT_HOURS);
+            }
+
+            if(summary.getSwimType() != -1) {
+                summaryData.add(ActivitySummaryEntries.SWIM_STYLE, getSwimStyle(summary.getSwimType()));
+            }
+
+            if(summary.getMaxMET() > 0) {
+                int value = (int) (((float) summary.getMaxMET() * 3.5f)) / 65536;
+                summaryData.add(ActivitySummaryEntries.MAXIMUM_OXYGEN_UPTAKE, value, ActivitySummaryEntries.UNIT_ML_KG_MIN);
+            }
+
+
+            Integer summaryMinAltitude = summary.getMinAltitude();
+            Integer summaryMaxAltitude = summary.getMaxAltitude();
+            Integer elevationGain = summary.getElevationGain();
+            Integer elevationLoss = summary.getElevationLoss();
+
+            int minHeartRatePeak = summary.getMinHeartRatePeak() & 0xff;
+            int maxHeartRatePeak = summary.getMaxHeartRatePeak() & 0xff;
+
+            int avgStrokeRate = summary.getAvgStrokeRate();
+
+            int avgSwolf = summary.getAvgSwolf();
+
+            boolean unknownData = false;
+            if (!dataSamples.isEmpty()) {
+                int speed = 0;
+                int speedCount = 0;
+                boolean stepRatePresent = false;
+                int stepRate = 0;
+                int avgStepRate = 0;
+                int cadence = 0;
+                int cadenceCount = 0;
+                int stepLength = 0;
+                int stepLengthCount = 0;
+                int groundContactTime = 0;
+                int groundContactTimeCount = 0;
+                int impact = 0;
+                int impactCount = 0;
+                int maxImpact = 0;
+                int swingAngle = 0;
+                int swingAngleCount = 0;
+                boolean footLandingPresent = false;
+                int foreFootLanding = 0;
+                int midFootLanding = 0;
+                int backFootLanding = 0;
+                int eversionAngle = 0;
+                int eversionAngleCount = 0;
+                int maxEversionAngle = 0;
+                int swolf = 0;
+                int swolfCount = 0;
+                int maxSwolf = 0;
+                int minSwolf = 0;
+                int strokeRate = 0;
+                int strokeRateCount = 0;
+                int maxStrokeRate = 0;
+                int heartRate = 0;
+                int heartRateCount = 0;
+                int maxHeartRate = 0;
+                int minHeartRate = Integer.MAX_VALUE;
+                int sumCalories = 0;
+                int minCyclingPower = Integer.MAX_VALUE;
+                int maxCyclingPower = 0;
+                int cyclingPower = 0;
+                int cyclingPowerCount = 0;
+                int avgAltitude = 0;
+                int altitudeCount = 0;
+                int minAltitude = Integer.MAX_VALUE;
+                int maxAltitude = Integer.MIN_VALUE;
+                Integer previousAlt = null;
+                int sumAltitudeUp = 0;
+                int sumAltitudeDown = 0;
+
+                //NOTE: The method of retrieving HR zones from the Huawei watch is not discovered. It may not return zones.
+                // So they are calculated based on config.
+                HeartRateZonesConfig HRZonesCfg = null;
+                Integer zoneType = HuaweiWorkoutUtils.getHRZoneTypeByActivity(type);
+                int zoneCalculateMethod = summary.getHrZoneType();
+                LOG.info("Workout HR Zone Calculate Type: {}", zoneCalculateMethod);
+                if(zoneType != null && HeartRateZonesConfig.isCalculateMethodValidFroType(zoneType, zoneCalculateMethod)) {
+                    ActivityUser activityUser = new ActivityUser();
+                    HuaweiSportHRZones hrSportZones = new HuaweiSportHRZones(activityUser.getAge());
+                    HRZonesCfg = hrSportZones.getHRZonesConfigByType(zoneType);
+                }
+
+                int dataDelta = 5;
+                if (dataSamples.size() >= 2 && dataSamples.get(1).getTimestamp() - dataSamples.get(0).getTimestamp() >= 40) {
+                    dataDelta = 60;
+                }
+
+                int[] HRZones = new int[5];
+
+                int dataIdx = 0;
+                for (HuaweiWorkoutDataSample dataSample : dataSamples) {
+
+                    if(HRZonesCfg != null) {
+                        int zoneIdx = HRZonesCfg.getZoneByMethod(dataSample.getHeartRate() & 0xFF, zoneCalculateMethod);
+                        if (zoneIdx != -1 && dataIdx < (dataSamples.size() - 1)) {
+                                 HRZones[zoneIdx] += dataDelta;
+                        }
+                        dataIdx++;
+                    }
+
+                    if (dataSample.getSpeed() != -1) {
+                        speed += dataSample.getSpeed();
+                        speedCount += 1;
+                    }
+                    if (dataSample.getStepRate() != -1) {
+                        stepRate += dataSample.getStepRate();
+                        stepRatePresent = true;
+                    }
+                    if (dataSample.getCadence() != -1) {
+                        cadence += dataSample.getCadence();
+                        cadenceCount += 1;
+                    }
+                    if (dataSample.getStepLength() != -1) {
+                        stepLength += dataSample.getStepLength();
+                        stepLengthCount += 1;
+                    }
+                    if (dataSample.getGroundContactTime() != -1) {
+                        groundContactTime += dataSample.getGroundContactTime();
+                        groundContactTimeCount += 1;
+                    }
+                    if (dataSample.getImpact() != -1) {
+                        impact += dataSample.getImpact();
+                        impactCount += 1;
+                        if (dataSample.getImpact() > maxImpact)
+                            maxImpact = dataSample.getImpact();
+                    }
+                    if (dataSample.getSwingAngle() != -1) {
+                        swingAngle += dataSample.getSwingAngle();
+                        swingAngleCount += 1;
+                    }
+                    if (dataSample.getForeFootLanding() != -1) {
+                        foreFootLanding += dataSample.getForeFootLanding();
+                        footLandingPresent = true;
+                    }
+                    if (dataSample.getMidFootLanding() != -1) {
+                        midFootLanding += dataSample.getMidFootLanding();
+                        footLandingPresent = true;
+                    }
+                    if (dataSample.getBackFootLanding() != -1) {
+                        backFootLanding += dataSample.getBackFootLanding();
+                        footLandingPresent = true;
+                    }
+                    if (dataSample.getEversionAngle() != -1) {
+                        eversionAngle += dataSample.getEversionAngle();
+                        eversionAngleCount += 1;
+                        if (dataSample.getEversionAngle() > maxEversionAngle)
+                            maxEversionAngle = dataSample.getEversionAngle();
+                    }
+                    if (dataSample.getSwolf() != -1) {
+                        swolf += dataSample.getSwolf();
+                        swolfCount += 1;
+                        if (dataSample.getSwolf() > maxSwolf)
+                            maxSwolf = dataSample.getSwolf();
+                        if (dataSample.getSwolf() < minSwolf)
+                            minSwolf = dataSample.getSwolf();
+                    }
+                    if (dataSample.getStrokeRate() != -1) {
+                        strokeRate += dataSample.getStrokeRate();
+                        strokeRateCount += 1;
+                        if (dataSample.getStrokeRate() > maxStrokeRate)
+                            maxStrokeRate = dataSample.getStrokeRate();
+                    }
+                    if (dataSample.getHeartRate() != -1 && dataSample.getHeartRate() != 0) {
+                        int hr = dataSample.getHeartRate() & 0xff;
+                        heartRate += hr;
+                        heartRateCount += 1;
+                        if (hr > maxHeartRate)
+                            maxHeartRate = hr;
+                        if (hr < minHeartRate)
+                            minHeartRate = hr;
+                    }
+                    if (dataSample.getCalories() != -1)
+                        sumCalories += dataSample.getCalories();
+                    if (dataSample.getCyclingPower() != -1) {
+                        int cp = dataSample.getCyclingPower();
+                        cyclingPower += cp;
+                        cyclingPowerCount += 1;
+                        if (cp > maxCyclingPower)
+                            maxCyclingPower = cp;
+                        if (cp < minCyclingPower)
+                            minCyclingPower = cp;
+                    }
+                    if (dataSample.getAltitude() != null) {
+                        int alt = dataSample.getAltitude();
+                        avgAltitude += alt;
+                        altitudeCount += 1;
+                        if (alt > maxAltitude)
+                            maxAltitude = alt;
+                        if (alt < minAltitude)
+                            minAltitude = alt;
+                        if (previousAlt != null) {
+                            if (alt > previousAlt)
+                                sumAltitudeUp += alt - previousAlt;
+                            else if (alt < previousAlt)
+                                sumAltitudeDown += previousAlt - alt;
+                        }
+                        previousAlt = alt;
+                    }
+                    if (dataSample.getDataErrorHex() != null)
+                        unknownData = true;
+                }
+
+
+                if(HRZonesCfg != null) {
+                    final double totalTime = Arrays.stream(HRZones).sum();
+                    final List<String> zoneOrder = Arrays.asList(ActivitySummaryEntries.HR_ZONE_WARM_UP, ActivitySummaryEntries.HR_ZONE_FAT_BURN, ActivitySummaryEntries.HR_ZONE_AEROBIC, ActivitySummaryEntries.HR_ZONE_ANAEROBIC, ActivitySummaryEntries.HR_ZONE_EXTREME);
+                    final int[] zoneColors = new int[]{
+                            context.getResources().getColor(R.color.hr_zone_warm_up_color),
+                            context.getResources().getColor(R.color.hr_zone_easy_color),
+                            context.getResources().getColor(R.color.hr_zone_aerobic_color),
+                            context.getResources().getColor(R.color.hr_zone_threshold_color),
+                            context.getResources().getColor(R.color.hr_zone_maximum_color),
+                    };
+                    for (int i = zoneOrder.size() - 1; i >= 0; i--) {
+                        double timeInZone = HRZones[i];
+                        LOG.info("Zone: {} {}", zoneOrder.get(i), timeInZone);
+                        summaryData.add(
+                                zoneOrder.get(i),
+                                new ActivitySummaryProgressEntry(
+                                        timeInZone,
+                                        ActivitySummaryEntries.UNIT_SECONDS,
+                                        (int) (100 * timeInZone / totalTime),
+                                        zoneColors[i]
+                                )
+                        );
+                    }
+                }
+
+
+                // Average the things that should be averaged
+                if (speedCount > 0)
+                    speed = speed / speedCount;
+                if (cadenceCount > 0)
+                    cadence = cadence / cadenceCount;
+                if (summary.getDuration() > 60)
+                    avgStepRate = stepRate / (summary.getDuration() / 60); // steps per minute
+                if (stepLengthCount > 0)
+                    stepLength = stepLength / stepLengthCount;
+                if (groundContactTimeCount > 0)
+                    groundContactTime = groundContactTime / groundContactTimeCount;
+                if (impactCount > 0)
+                    impact = impact / impactCount;
+                if (swingAngleCount > 0)
+                    swingAngle = swingAngle / swingAngleCount;
+                if (eversionAngleCount > 0)
+                    eversionAngle = eversionAngle / eversionAngleCount;
+                if (swolfCount > 0)
+                    swolf = swolf / swolfCount;
+                if (strokeRateCount > 0)
+                    strokeRate = strokeRate / strokeRateCount;
+                if (heartRateCount > 0)
+                    heartRate = heartRate / heartRateCount;
+                if (cyclingPowerCount > 0)
+                    cyclingPower = cyclingPower / cyclingPowerCount;
+                if (altitudeCount > 0)
+                    avgAltitude = avgAltitude / altitudeCount;
+
+                if (speedCount > 0) {
+                    summaryData.add(ActivitySummaryEntries.SPEED_AVG, speed / 10f, ActivitySummaryEntries.UNIT_METERS_PER_SECOND);
+                }
+
+                if (stepRatePresent) {
+                    summaryData.add(ActivitySummaryEntries.STEP_RATE_AVG, avgStepRate, ActivitySummaryEntries.UNIT_SPM);
+                }
+
+                if (cadenceCount > 0) {
+                    summaryData.add(ActivitySummaryEntries.CADENCE_AVG, cadence, ActivitySummaryEntries.UNIT_SPM);
+                }
+
+                if (stepLengthCount > 0) {
+                    summaryData.add(ActivitySummaryEntries.STEP_LENGTH_AVG, stepLength, ActivitySummaryEntries.UNIT_CM);
+                }
+
+                if (groundContactTimeCount > 0) {
+                    summaryData.add(ActivitySummaryEntries.GROUND_CONTACT_TIME_AVG, groundContactTime, ActivitySummaryEntries.UNIT_MILLISECONDS);
+                }
+
+                if (impactCount > 0) {
+                    summaryData.add(ActivitySummaryEntries.IMPACT_AVG, impact, "g");
+                    summaryData.add(ActivitySummaryEntries.IMPACT_MAX, maxImpact, "g");
+                }
+
+                if (swingAngleCount > 0) {
+                    summaryData.add(ActivitySummaryEntries.SWING_ANGLE_AVG, swingAngle, ActivitySummaryEntries.UNIT_DEGREES);
+                }
+
+                if (footLandingPresent) {
+                    summaryData.add(ActivitySummaryEntries.FORE_FOOT_LANDINGS, foreFootLanding, ActivitySummaryEntries.UNIT_NONE);
+                    summaryData.add(ActivitySummaryEntries.MID_FOOT_LANDINGS, midFootLanding, ActivitySummaryEntries.UNIT_NONE);
+                    summaryData.add(ActivitySummaryEntries.BACK_FOOT_LANDINGS, backFootLanding, ActivitySummaryEntries.UNIT_NONE);
+                }
+
+                if (eversionAngleCount > 0) {
+                    summaryData.add(ActivitySummaryEntries.EVERSION_ANGLE_AVG, eversionAngle, ActivitySummaryEntries.UNIT_DEGREES);
+                    summaryData.add(ActivitySummaryEntries.EVERSION_ANGLE_MAX, maxEversionAngle, ActivitySummaryEntries.UNIT_DEGREES);
+                }
+
+                if (swolfCount > 0) {
+                    if(avgSwolf == -1) {
+                        avgSwolf = swolf;
+                    }
+                    summaryData.add(ActivitySummaryEntries.SWOLF_MAX, maxSwolf, ActivitySummaryEntries.UNIT_NONE);
+                    summaryData.add(ActivitySummaryEntries.SWOLF_MIN, minSwolf, ActivitySummaryEntries.UNIT_NONE);
+                }
+
+                if (strokeRateCount > 0) {
+                    if(avgStrokeRate == -1) {
+                        avgStrokeRate = strokeRate;
+                    }
+                    summaryData.add(ActivitySummaryEntries.STROKE_RATE_MAX, maxStrokeRate, ActivitySummaryEntries.UNIT_STROKES_PER_MINUTE);
+                }
+
+                if (heartRateCount > 0) {
+                    summaryData.add(ActivitySummaryEntries.HR_AVG, heartRate, ActivitySummaryEntries.UNIT_BPM);
+                    if(minHeartRatePeak == 0) {
+                        minHeartRatePeak = minHeartRate;
+                    }
+
+                    if(maxHeartRatePeak == 0) {
+                        maxHeartRatePeak = maxHeartRate;
+                    }
+                }
+
+                if (sumCalories > 0) {
+                    summaryData.add(ActivitySummaryEntries.CALORIES_BURNT, sumCalories, ActivitySummaryEntries.UNIT_KCAL);
+                }
+
+                if (cyclingPowerCount > 0) {
+                    summaryData.add(ActivitySummaryEntries.CYCLING_POWER_AVERAGE, cyclingPower, ActivitySummaryEntries.UNIT_NONE);
+                    summaryData.add(ActivitySummaryEntries.CYCLING_POWER_MIN, minCyclingPower, ActivitySummaryEntries.UNIT_NONE);
+                    summaryData.add(ActivitySummaryEntries.CYCLING_POWER_MAX, maxCyclingPower, ActivitySummaryEntries.UNIT_NONE);
+                }
+
+                if (altitudeCount > 0) {
+                    summaryData.add(ActivitySummaryEntries.ALTITUDE_AVG, avgAltitude / 10.0f, ActivitySummaryEntries.UNIT_METERS);
+
+                    if(summaryMinAltitude == null) {
+                        summaryMinAltitude = minAltitude;
+                    }
+
+                    if(summaryMaxAltitude == null) {
+                        summaryMaxAltitude = maxAltitude;
+                    }
+
+                    if(elevationGain == null) {
+                        elevationGain = sumAltitudeUp;
+                    }
+
+                    if(elevationLoss == null) {
+                        elevationLoss = sumAltitudeDown;
+                    }
+                }
+            }
+
+            if(avgSwolf > 0) {
+                summaryData.add(ActivitySummaryEntries.SWOLF_AVG, avgSwolf, ActivitySummaryEntries.UNIT_NONE);
+            }
+
+            if (avgStrokeRate > 0) {
+                summaryData.add(ActivitySummaryEntries.STROKE_RATE_AVG, avgStrokeRate, ActivitySummaryEntries.UNIT_STROKES_PER_MINUTE);
+            }
+
+            if (minHeartRatePeak > 0) {
+                summaryData.add(ActivitySummaryEntries.HR_MIN, minHeartRatePeak, ActivitySummaryEntries.UNIT_BPM);
+            }
+            if (maxHeartRatePeak > 0) {
+                summaryData.add(ActivitySummaryEntries.HR_MAX, maxHeartRatePeak, ActivitySummaryEntries.UNIT_BPM);
+            }
+
+            if(summaryMinAltitude != null) {
+                summaryData.add(ActivitySummaryEntries.ALTITUDE_MIN, summaryMinAltitude / 10.0f, ActivitySummaryEntries.UNIT_METERS);
+            }
+
+            if(summaryMaxAltitude != null) {
+                summaryData.add(ActivitySummaryEntries.ALTITUDE_MAX, summaryMaxAltitude / 10.0f, ActivitySummaryEntries.UNIT_METERS);
+            }
+            if(elevationGain != null) {
+                summaryData.add(ActivitySummaryEntries.ELEVATION_GAIN, elevationGain / 10.0f, ActivitySummaryEntries.UNIT_METERS);
+            }
+            if(elevationLoss != null) {
+                summaryData.add(ActivitySummaryEntries.ELEVATION_LOSS, elevationLoss / 10.0f, ActivitySummaryEntries.UNIT_METERS);
+            }
+
+
+            final LinkedHashMap<String, ActivitySummaryTableRowEntry> pacesTable = new LinkedHashMap<>();
+
+            pacesTable.put("paces_table",
+                    new ActivitySummaryTableRowEntry(
+                            ActivitySummaryEntries.GROUP_PACE,
+                            Arrays.asList(
+                                    new ActivitySummaryValue("#", ActivitySummaryEntries.UNIT_RAW_STRING),
+                                    new ActivitySummaryValue("distance"),
+                                    new ActivitySummaryValue("Pace")
+                            ),
+                            true,
+                            true
+                    )
+            );
+
+            String measurementSystem = GBApplication.getPrefs().getString(SettingsActivity.PREF_MEASUREMENT_SYSTEM, "metric");
+
+            byte unitType = (byte) (measurementSystem.equals("metric") ? 0 : 1);
+            try (CloseableListIterator<HuaweiWorkoutPaceSample> it = qbPace.build().listIterator()) {
+
+                int paceCount = 0;
+                int paceSum = 0;
+                int paceFastest = Integer.MAX_VALUE;
+                int paceSlowest = 0;
+
+                int currentIndex = 1;
+                while (it.hasNext()) {
+                    int index = it.nextIndex();
+                    HuaweiWorkoutPaceSample sample = it.next();
+
+                    if (sample.getType() != unitType)
+                        continue;
+
+                    int pace = sample.getPace();
+
+                    paceCount++;
+                    paceSum += pace;
+
+                    if (pace < paceFastest)
+                        paceFastest = pace;
+
+                    if (pace > paceSlowest)
+                        paceSlowest = pace;
+
+                    double distance = sample.getDistance();
+
+                    if (sample.getCorrection() != null) {
+                        distance += sample.getCorrection() / 10000d;
+                    }
+
+                    final List<ActivitySummaryValue> columns = new LinkedList<>();
+                    // TODO: add proper units for type == 1. MILES and SECONDS PER MILE
+                    columns.add(new ActivitySummaryValue(currentIndex++, ActivitySummaryEntries.UNIT_NONE));
+                    columns.add(new ActivitySummaryValue(distance, ActivitySummaryEntries.UNIT_KILOMETERS));
+                    columns.add(new ActivitySummaryValue(sample.getPace(), ActivitySummaryEntries.UNIT_SECONDS_PER_KM));
+
+                    pacesTable.put("paces_table_" + index,
+                            new ActivitySummaryTableRowEntry(
+                                    ActivitySummaryEntries.GROUP_PACE,
+                                    columns,
+                                    false,
+                                    true
+                            )
+                    );
+                }
+
+                if (paceCount != 0 && paceSum != 0) {
+                    summaryData.add(
+                            ActivitySummaryEntries.GROUP_PACE,
+                            GBApplication.getContext().getString(R.string.fmtPaceAverage),
+                            paceSum / (float) paceCount,
+                            ActivitySummaryEntries.UNIT_SECONDS_PER_KM
+                    );
+                }
+
+                if (paceFastest != Integer.MAX_VALUE) {
+                    summaryData.add(
+                            ActivitySummaryEntries.GROUP_PACE,
+                            GBApplication.getContext().getString(R.string.maxPace),
+                            paceFastest,
+                            ActivitySummaryEntries.UNIT_SECONDS_PER_KM
+                    );
+                }
+
+                if (paceSlowest != 0) {
+                    summaryData.add(
+                            ActivitySummaryEntries.GROUP_PACE,
+                            GBApplication.getContext().getString(R.string.minPace),
+                            paceSlowest,
+                            ActivitySummaryEntries.UNIT_SECONDS_PER_KM
+                    );
+                }
+
+                if (pacesTable.size() > 1) {
+                    for (final Map.Entry<String, ActivitySummaryTableRowEntry> e : pacesTable.entrySet()) {
+                        summaryData.add(e.getKey(), e.getValue());
+                    }
+                }
+            }
+
+            final LinkedHashMap<String, ActivitySummaryTableRowEntry> segmentsTable = new LinkedHashMap<>();
+
+            try (CloseableListIterator<HuaweiWorkoutSwimSegmentsSample> it = qbSegments.build().listIterator()) {
+
+                int currentIndex = 1;
+                int tableIndex = 1;
+                while (it.hasNext()) {
+                    HuaweiWorkoutSwimSegmentsSample sample = it.next();
+
+                    if (sample.getType() != unitType)
+                        continue;
+
+                    final List<ActivitySummaryValue> columns = new LinkedList<>();
+                    // TODO: add proper units for type == 1. MILES
+                    columns.add(new ActivitySummaryValue(currentIndex++, ActivitySummaryEntries.UNIT_NONE));
+                    columns.add(new ActivitySummaryValue(getSwimStyle(sample.getSwimType()), ActivitySummaryEntries.UNIT_NONE));
+                    columns.add(new ActivitySummaryValue(sample.getDistance(), ActivitySummaryEntries.UNIT_METERS));
+                    columns.add(new ActivitySummaryValue(sample.getTime(), ActivitySummaryEntries.UNIT_SECONDS));
+
+
+                    segmentsTable.put("segments_table_" + tableIndex++,
+                            new ActivitySummaryTableRowEntry(
+                                    ActivitySummaryEntries.GROUP_PACE,
+                                    columns,
+                                    true,
+                                    true
+                            )
+                    );
+
+                    final List<ActivitySummaryValue> columns2 = new LinkedList<>();
+                    // TODO: add proper units for type == 1. MILES and SECONDS PER MILE
+                    columns2.add(new ActivitySummaryValue("", ActivitySummaryEntries.UNIT_NONE));
+                    columns2.add(new ActivitySummaryValue(sample.getStrokes(), ActivitySummaryEntries.UNIT_STROKES));
+                    columns2.add(new ActivitySummaryValue(sample.getAvgSwolf(), ActivitySummaryEntries.UNIT_NONE));
+                    columns2.add(new ActivitySummaryValue(sample.getPace(), ActivitySummaryEntries.UNIT_NONE)); //TODO: seconds / 100 meters
+
+
+
+                    segmentsTable.put("segments_table_" + tableIndex++,
+                            new ActivitySummaryTableRowEntry(
+                                    ActivitySummaryEntries.GROUP_PACE,
+                                    columns2,
+                                    false,
+                                    false
+                            )
+                    );
+                    segmentsTable.put("segments_table_" + tableIndex++,
+                            new ActivitySummaryTableRowEntry(
+                                    ActivitySummaryEntries.GROUP_PACE,
+                                    new ArrayList<>(),
+                                    false,
+                                    false
+                            )
+                    );
+                }
+
+                if (!segmentsTable.isEmpty()) {
+                    for (final Map.Entry<String, ActivitySummaryTableRowEntry> e : segmentsTable.entrySet()) {
+                        summaryData.add(e.getKey(), e.getValue());
+                    }
+                }
+            }
+
+            if (unknownData) {
+                summaryData.add(
+                        GBApplication.getContext().getString(R.string.unknownDataEncountered),
+                        GBApplication.getContext().getString(R.string.yes).toUpperCase()
+                );
+            }
+
+            if (baseSummary.getName() == null) {
+                baseSummary.setName("Workout " + summary.getWorkoutNumber());
+            }
+
+            if (baseSummary.getGpxTrack() == null) {
+                baseSummary.setGpxTrack(summary.getGpxFileLocation());
+            }
+
+            // start time never changes
+            baseSummary.setEndTime(new Date(summary.getEndTimestamp() * 1000L));
+            baseSummary.setActivityKind(type.getCode());
+            baseSummary.setSummaryData(summaryData.toString());
+        } catch (Exception e) {
+            GB.toast("Exception parsing workout data", Toast.LENGTH_SHORT, GB.ERROR, e);
+            LOG.error("Exception parsing workout data", e);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiWorkoutUtils.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiWorkoutUtils.java
new file mode 100644
index 0000000..6073c29
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiWorkoutUtils.java
@@ -0,0 +1,48 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HeartRateZonesConfig;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+
+public class HuaweiWorkoutUtils {
+    private static final Map<ActivityKind, Integer> activityHRZoneType = createActivityHRZoneType();
+
+
+    //TODO: discover and add more activity types. Should be same as in the watch.
+    private static Map<ActivityKind, Integer> createActivityHRZoneType() {
+        Map<ActivityKind, Integer>  result = new HashMap<>();
+        result.put(ActivityKind.RUNNING, HeartRateZonesConfig.TYPE_UPRIGHT);
+        result.put(ActivityKind.WALKING, HeartRateZonesConfig.TYPE_UPRIGHT);
+        result.put(ActivityKind.CYCLING, HeartRateZonesConfig.TYPE_SITTING);
+        result.put(ActivityKind.MOUNTAIN_HIKE, HeartRateZonesConfig.TYPE_UPRIGHT);
+        result.put(ActivityKind.INDOOR_RUNNING, HeartRateZonesConfig.TYPE_UPRIGHT);
+        result.put(ActivityKind.POOL_SWIM, HeartRateZonesConfig.TYPE_SWIMMING);
+        result.put(ActivityKind.INDOOR_CYCLING, HeartRateZonesConfig.TYPE_SITTING);
+        result.put(ActivityKind.SWIMMING_OPENWATER, HeartRateZonesConfig.TYPE_SWIMMING);
+        result.put(ActivityKind.INDOOR_WALKING, HeartRateZonesConfig.TYPE_UPRIGHT);
+        result.put(ActivityKind.HIKING, HeartRateZonesConfig.TYPE_UPRIGHT);
+        result.put(ActivityKind.JUMP_ROPING, HeartRateZonesConfig.TYPE_UPRIGHT);
+        result.put(ActivityKind.PINGPONG, HeartRateZonesConfig.TYPE_UPRIGHT);
+        result.put(ActivityKind.BADMINTON, HeartRateZonesConfig.TYPE_UPRIGHT);
+        result.put(ActivityKind.TENNIS, HeartRateZonesConfig.TYPE_UPRIGHT);
+        result.put(ActivityKind.SOCCER, HeartRateZonesConfig.TYPE_UPRIGHT);
+        result.put(ActivityKind.BASKETBALL, HeartRateZonesConfig.TYPE_UPRIGHT);
+        result.put(ActivityKind.VOLLEYBALL, HeartRateZonesConfig.TYPE_UPRIGHT);
+        result.put(ActivityKind.ELLIPTICAL_TRAINER, HeartRateZonesConfig.TYPE_UPRIGHT);
+        result.put(ActivityKind.ROWING_MACHINE, HeartRateZonesConfig.TYPE_SITTING);
+        result.put(ActivityKind.STEPPER, HeartRateZonesConfig.TYPE_UPRIGHT);
+        result.put(ActivityKind.YOGA, HeartRateZonesConfig.TYPE_OTHER);
+        return Collections.unmodifiableMap(result);
+    }
+
+    public static Integer getHRZoneTypeByActivity(ActivityKind type) {
+        if(activityHRZoneType.containsKey(type)) {
+            return activityHRZoneType.get(type);
+        }
+        return null;
+    }
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/ResponseManager.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/ResponseManager.java
new file mode 100644
index 0000000..16c94d5
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/ResponseManager.java
@@ -0,0 +1,137 @@
+/*  Copyright (C) 2024 Damien Gaignon, Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.Request;
+
+/**
+ * Manages all response data.
+ */
+public class ResponseManager {
+    private static final Logger LOG = LoggerFactory.getLogger(ResponseManager.class);
+
+    private final List<Request> handlers = Collections.synchronizedList(new ArrayList<>());
+    private HuaweiPacket receivedPacket;
+    private final AsynchronousResponse asynchronousResponse;
+    private final HuaweiSupportProvider support;
+
+    public ResponseManager(HuaweiSupportProvider support) {
+        this.asynchronousResponse = new AsynchronousResponse(support);
+        this.support = support;
+    }
+
+    /**
+     * Add a request to the response handler list
+     * @param handler The request to handle responses
+     */
+    public void addHandler(Request handler) {
+        synchronized (handlers) {
+            handlers.add(handler);
+        }
+    }
+
+    /**
+     * Remove a request from the response handler list
+     * @param handler The request to remove
+     */
+    public void removeHandler(Request handler) {
+        synchronized (handlers) {
+            handlers.remove(handler);
+        }
+    }
+
+    /**
+     * Remove all requests with specified class from the response handler list
+     * @param handlerClass The class of which the requests are removed
+     */
+    public void removeHandler(Class<?> handlerClass) {
+        synchronized (handlers) {
+            handlers.removeIf(request -> request.getClass() == handlerClass);
+        }
+    }
+
+    /**
+     * Parses the data into a Huawei Packet.
+     * If the packet is complete, it will be handled by the first request that accepts it,
+     * or as an asynchronous request otherwise.
+     *
+     * @param data The received data
+     */
+    public void handleData(byte[] data) {
+        //NOTE: This is a quick fix issue with concatenated packets.
+        //TODO: Extract transport related code from packet.
+        int left = 0;
+        do {
+            if(left > 0)
+                data = Arrays.copyOfRange(data, data.length - left, data.length);
+
+            try {
+                if (receivedPacket == null)
+                    receivedPacket = new HuaweiPacket(support.getParamsProvider()).parse(data);
+                else
+                    receivedPacket = receivedPacket.parse(data);
+
+                left = receivedPacket.getLeft();
+            } catch (HuaweiPacket.ParseException e) {
+                LOG.error("Packet parse exception", e);
+
+                // Clean up so the next message may be parsed correctly
+                this.receivedPacket = null;
+                return;
+            }
+
+            if (receivedPacket.complete) {
+                Request handler = null;
+                synchronized (handlers) {
+                    for (Request req : handlers) {
+                        if (req.handleResponse(receivedPacket)) {
+                            handler = req;
+                            break;
+                        }
+                    }
+                }
+
+                if (handler == null) {
+                    LOG.debug("Service: " + Integer.toHexString(receivedPacket.serviceId & 0xff) + ", command: " + Integer.toHexString(receivedPacket.commandId & 0xff) + ", asynchronous response.");
+
+                    // Asynchronous response
+                    asynchronousResponse.handleResponse(receivedPacket);
+                } else {
+                    LOG.debug("Service: " + Integer.toHexString(receivedPacket.serviceId & 0xff) + ", command: " + Integer.toHexString(receivedPacket.commandId & 0xff) + ", handled by: " + handler.getClass());
+
+                    if (handler.autoRemoveFromResponseHandler()) {
+                        synchronized (handlers) {
+                            handlers.remove(handler);
+                        }
+                    }
+
+                    handler.handleResponse();
+                }
+                receivedPacket = null;
+            }
+        } while (left > 0);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/p2p/HuaweiBaseP2PService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/p2p/HuaweiBaseP2PService.java
new file mode 100644
index 0000000..a4b40f8
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/p2p/HuaweiBaseP2PService.java
@@ -0,0 +1,114 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.p2p;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.P2P;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiP2PManager;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendP2PCommand;
+
+public abstract class HuaweiBaseP2PService {
+    private final Logger LOG = LoggerFactory.getLogger(HuaweiBaseP2PService.class);
+
+
+    public interface HuaweiP2PCallback {
+        void onResponse(int code, byte[] data);
+    }
+
+    protected final HuaweiP2PManager manager;
+
+    protected HuaweiBaseP2PService(HuaweiP2PManager manager) {
+        this.manager = manager;
+    }
+
+    public void register() {
+        manager.registerService(this);
+    }
+
+    public abstract String getModule();
+
+    public abstract String getPackage();
+
+    public abstract String getFingerprint();
+
+    public abstract void registered();
+
+    public abstract void unregister();
+
+    public abstract void handleData(byte[] data);
+
+    public String getLocalFingerprint() {
+        return "UniteDeviceManagement";
+    }
+
+    public String getPingPackage() {
+        return "com.huawei.health";
+    }
+
+    private final Map<Short, HuaweiP2PCallback> waitPackets = new ConcurrentHashMap<>();
+
+    private Short getNextSequence() {
+        return manager.getNextSequence();
+    }
+
+    public void sendCommand(byte[] sendData, HuaweiP2PCallback callback) {
+        try {
+            short seq = this.getNextSequence();
+            SendP2PCommand test = new SendP2PCommand(this.manager.getSupportProvider(), (byte) 2, seq, this.getModule(), this.getPackage(), this.getLocalFingerprint(), this.getFingerprint(), sendData, 0);
+            if (callback != null) {
+                this.waitPackets.put(seq, callback);
+            }
+            test.doPerform();
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public void sendPing(HuaweiP2PCallback callback) {
+        try {
+            short seq = this.getNextSequence();
+            SendP2PCommand test = new SendP2PCommand(this.manager.getSupportProvider(), (byte) 1, seq, this.getPingPackage(), this.getPackage(), null, null, null, 0);
+            if (callback != null) {
+                this.waitPackets.put(seq, callback);
+            }
+            test.doPerform();
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public void sendAck(short sequence, String srcPackage, String dstPackage, int code) {
+        try {
+            SendP2PCommand test = new SendP2PCommand(this.manager.getSupportProvider(), (byte) 3, sequence, srcPackage, dstPackage, null, null, null, code);
+            test.doPerform();
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public void handlePacket(P2P.P2PCommand.Response packet) {
+        LOG.info("HuaweiP2PCalendarService handlePacket: {} Code: {}", packet.cmdId, packet.respCode);
+        if (waitPackets.containsKey(packet.sequenceId)) {
+            LOG.info("HuaweiP2PCalendarService handlePacket find handler");
+            HuaweiP2PCallback handle = waitPackets.remove(packet.sequenceId);
+            if(handle != null) {
+                handle.onResponse(packet.respCode, packet.respData);
+            } else {
+                LOG.error("HuaweiP2PCalendarService handler is null");
+            }
+        } else {
+
+            if (packet.cmdId == 1) { //Ping
+                sendAck(packet.sequenceId, packet.dstPackage, packet.srcPackage, 0xca);
+            } else if (packet.cmdId == 2) {
+                handleData(packet.respData);
+                sendAck(packet.sequenceId, packet.dstPackage, packet.srcPackage, 0xca);
+            }
+        }
+    }
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/p2p/HuaweiP2PCalendarService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/p2p/HuaweiP2PCalendarService.java
new file mode 100644
index 0000000..7a15142
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/p2p/HuaweiP2PCalendarService.java
@@ -0,0 +1,491 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.p2p;
+
+
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SYNC_CALENDAR;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.text.TextUtils;
+import android.util.Pair;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonObject;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.CharsetDecoder;
+import java.nio.charset.CodingErrorAction;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiTLV;
+import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiP2PManager;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiUploadManager;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendFileUploadInfo;
+import nodomain.freeyourgadget.gadgetbridge.util.calendar.CalendarEvent;
+import nodomain.freeyourgadget.gadgetbridge.util.calendar.CalendarManager;
+
+public class HuaweiP2PCalendarService extends HuaweiBaseP2PService {
+    private final Logger LOG = LoggerFactory.getLogger(HuaweiP2PCalendarService.class);
+
+    public final int OPERATION_ADD = 1;
+    public final int OPERATION_DELETE = 2;
+    public final int OPERATION_UPDATE = 2;
+    
+    public static final String MODULE = "hw.unitedevice.calendarapp";
+
+    private final AtomicBoolean isRegistered = new AtomicBoolean(false);
+
+    private final Handler handler = new Handler(Looper.getMainLooper());
+    private final Runnable syncCallback = new Runnable() {
+        @Override
+        public void run() {
+            sendCalendarCmd((byte) 0x01, (byte) 0x01, null);
+        }
+    };
+
+    private List<CalendarEvent> lastCalendarEvents = null;
+
+    public HuaweiP2PCalendarService(HuaweiP2PManager manager) {
+        super(manager);
+        LOG.info("HuaweiP2PCalendarService");
+    }
+
+    public static HuaweiP2PCalendarService getRegisteredInstance(HuaweiP2PManager manager) {
+        return (HuaweiP2PCalendarService) manager.getRegisteredService(HuaweiP2PCalendarService.MODULE);
+    }
+
+    public String getModule() {
+        return HuaweiP2PCalendarService.MODULE;
+    }
+
+    @Override
+    public String getPackage() {
+        if (manager.getSupportProvider().getHuaweiCoordinator().supportsExternalCalendarService())
+            return "com.huawei.ohos.calendar";
+        return "in.huawei.calendar";
+    }
+
+    @Override
+    public String getFingerprint() {
+        if (manager.getSupportProvider().getHuaweiCoordinator().supportsExternalCalendarService())
+            return "com.huawei.ohos.calendar_BCgpfcWNSKWgvxsSILxooQZyAmKYsFQnMTibnfrKQqK9M0ABtXH+GbsOscsnVvVc5qIDiFEyEOYMSF7gJ7Vb5Mc=";
+        return "SystemApp";
+    }
+
+    @Override
+    public void registered() {
+        isRegistered.set(true);
+        startSynchronization();
+    }
+
+    @Override
+    public void unregister() {
+        isRegistered.set(false);
+        handler.removeCallbacks(syncCallback);
+    }
+
+    private void startSynchronization() {
+        sendCalendarCmd((byte) 0x02, (byte) 0x01, null); // download calendar request but it does not work on my device
+        sendCalendarCmd((byte) 0x01, (byte) 0x01, null); // send sync upload request
+    }
+
+    public void restartSynchronization() {
+        if (isRegistered.get()) {
+            scheduleUpdate(2000);
+        }
+    }
+
+    public void scheduleUpdate(long delay) {
+        handler.removeCallbacks(syncCallback);
+        if (isRegistered.get()) {
+            handler.postDelayed(syncCallback, delay);
+        }
+    }
+
+    private void sendCalendarCmd(byte command, byte commandData, HuaweiP2PCallback callback) {
+        sendPing(new HuaweiP2PCallback() {
+            @Override
+            public void onResponse(int code, byte[] data) {
+                if ((byte) code != (byte) 0xca)
+                    return;
+                // NOTE: basically this is TLV with one tag 0x1, But I have no reasons to create additional classes for this.
+                sendCommand(new byte[]{command, 0x01, 0x01, commandData}, callback);
+            }
+        });
+    }
+
+    public void onAddCalendarEvent(final CalendarEventSpec calendarEventSpec) {
+        LOG.info("onAddCalendarEvent {}", calendarEventSpec.id);
+        scheduleUpdate(2000);
+    }
+
+    public void onDeleteCalendarEvent(final byte type, long id) {
+        LOG.info("onDeleteCalendarEvent {} {}", type, id);
+        scheduleUpdate(2000);
+    }
+
+    private String truncateToHexBytes(String str, int count) {
+        if(str == null)
+            return "";
+        byte[] bytes = str.getBytes(StandardCharsets.UTF_8);
+        if (bytes.length > count) {
+            int len = (int)Math.ceil(count/2.0) + 1;
+            ByteBuffer buffer = ByteBuffer.wrap(bytes, 0, len - 3);
+            CharBuffer cb = CharBuffer.allocate(len);
+            CharsetDecoder cd = StandardCharsets.UTF_8.newDecoder();
+            cd.onMalformedInput(CodingErrorAction.IGNORE);
+            cd.decode(buffer, cb, true);
+            cd.flush(cb);
+            return new String(cb.array(), 0, cb.position()) + "...";
+        }
+        return str;
+    }
+
+
+    private String getFileCalendarName() {
+        long millis = System.currentTimeMillis();
+        return String.format(Locale.ROOT, "calendar_data_%d.json", millis);
+    }
+
+    private String prepareEventUUID(long id, long begin) {
+        return String.valueOf(id) + "_" + begin;
+    }
+
+    static <T> T valueOrEmpty(T val, T def) {
+        return val != null ? val : def;
+    }
+
+    private JsonObject calendarEventToJson(CalendarEvent calendarEvent, int operation) {
+        JsonObject ret = new JsonObject();
+
+        // NOTE: Calendar contain reminders already in required format. But GB reformat them.
+        // So we need to reformat them back.
+        StringBuilder reminders = new StringBuilder();
+        for (long rem : calendarEvent.getRemindersAbsoluteTs()) {
+            reminders.append(String.valueOf((calendarEvent.getBegin() - rem) / 60 / 1000L)).append(",");
+        }
+
+        String eventUUID = prepareEventUUID(calendarEvent.getId(), calendarEvent.getBegin());
+
+        ret.addProperty("account_name", truncateToHexBytes(calendarEvent.getCalAccountName(), 64));
+        ret.addProperty("account_type", truncateToHexBytes(calendarEvent.getCalAccountType(), 64));
+        ret.addProperty("all_day", calendarEvent.isAllDay() ? 1 : 0);
+        ret.addProperty("calendar_color", calendarEvent.getColor());
+        ret.addProperty("calendar_displayName", truncateToHexBytes(calendarEvent.getCalName(), 64));
+        ret.addProperty("calendar_id", valueOrEmpty(calendarEvent.getCalendarId(), "0"));
+        ret.addProperty("description", truncateToHexBytes(calendarEvent.getDescription(), 512));
+        ret.addProperty("dtend", calendarEvent.getEnd());
+        ret.addProperty("dtstart", calendarEvent.getBegin());
+        ret.addProperty("event_id", String.valueOf(calendarEvent.getId()));
+        ret.addProperty("event_location", truncateToHexBytes(calendarEvent.getLocation(), 256));
+        ret.addProperty("event_uuid", eventUUID);
+        ret.addProperty("has_alarm", (reminders.length() == 0) ? 0 : 1);
+        ret.addProperty("minutes", reminders.toString());
+        ret.addProperty("operation", operation);
+        ret.addProperty("rrule", valueOrEmpty(calendarEvent.getRrule(),""));
+        // TODO: Retrieve from CalendarContract.CalendarAlerts, field state
+        // TODO: see handleData function command ID 3 for details
+        ret.addProperty("state", -1);
+        ret.addProperty("title", truncateToHexBytes(calendarEvent.getTitle(), 500));
+        return ret;
+    }
+
+    private JsonObject calendarDeletedEventToJson(CalendarEvent calendarEvent) {
+        JsonObject ret = new JsonObject();
+
+        String eventUUID = prepareEventUUID(calendarEvent.getId(), calendarEvent.getBegin());
+
+        ret.addProperty("account_name", "");
+        ret.addProperty("account_type", "");
+        ret.addProperty("all_day", 0);
+        ret.addProperty("calendar_color", 0);
+        ret.addProperty("calendar_displayName", "");
+        ret.addProperty("calendar_id", "");
+        ret.addProperty("description", "");
+        ret.addProperty("dtend", 0);
+        ret.addProperty("dtstart", 0);
+        ret.addProperty("event_id", String.valueOf(calendarEvent.getId()));
+        ret.addProperty("event_location", "");
+        ret.addProperty("event_uuid", eventUUID);
+        ret.addProperty("has_alarm", 0);
+        ret.addProperty("minutes", "");
+        ret.addProperty("operation", OPERATION_DELETE);
+        ret.addProperty("rrule", "");
+        ret.addProperty("state", 0);
+        ret.addProperty("title", "");
+        return ret;
+    }
+
+    private JsonArray getFullCalendarData() {
+        final CalendarManager upcomingEvents = new CalendarManager(manager.getSupportProvider().getContext(), manager.getSupportProvider().getDevice().getAddress());
+        final List<CalendarEvent> calendarEvents = upcomingEvents.getCalendarEventList();
+
+        JsonArray events = new JsonArray();
+        for (final CalendarEvent calendarEvent : calendarEvents) {
+            events.add(calendarEventToJson(calendarEvent, OPERATION_ADD));
+        }
+
+        lastCalendarEvents = calendarEvents;
+        return events;
+    }
+
+    private JsonArray getUpdateCalendarData() {
+        final CalendarManager upcomingEvents = new CalendarManager(manager.getSupportProvider().getContext(), manager.getSupportProvider().getDevice().getAddress());
+        final List<CalendarEvent> calendarEvents = upcomingEvents.getCalendarEventList();
+
+        List<CalendarEvent> newEvents = new ArrayList<>();
+        List<CalendarEvent> updatedEvents = new ArrayList<>();
+
+        Map<Pair<Long, Long>, CalendarEvent> lastEventsIds = new HashMap<>();
+        for (CalendarEvent evt : lastCalendarEvents) {
+            lastEventsIds.put(new Pair<>(evt.getId(),  evt.getBegin()), evt);
+        }
+
+        for (CalendarEvent evt : calendarEvents) {
+            CalendarEvent lastEvt = lastEventsIds.remove((new Pair<>(evt.getId(),  evt.getBegin())));
+            if (lastEvt == null) {
+                newEvents.add(evt);
+            } else {
+                if(!lastEvt.equals(evt)) {
+                    updatedEvents.add(evt);
+                }
+            }
+        }
+
+        List<CalendarEvent> removedEvents = new ArrayList<>(lastEventsIds.values());
+
+
+        JsonArray events = new JsonArray();
+
+        for (final CalendarEvent calendarEvent : updatedEvents) {
+            LOG.info("Update: {}", prepareEventUUID(calendarEvent.getId(), calendarEvent.getBegin()));
+            events.add(calendarEventToJson(calendarEvent, OPERATION_UPDATE));
+        }
+
+        for (final CalendarEvent calendarEvent : newEvents) {
+            LOG.info("New: {}", prepareEventUUID(calendarEvent.getId(), calendarEvent.getBegin()));
+            events.add(calendarEventToJson(calendarEvent, OPERATION_ADD));
+        }
+
+        for (final CalendarEvent calendarEvent : removedEvents) {
+            LOG.info("Remove: {}", prepareEventUUID(calendarEvent.getId(), calendarEvent.getBegin()));
+            events.add(calendarDeletedEventToJson(calendarEvent));
+        }
+
+        lastCalendarEvents = calendarEvents;
+        return events;
+    }
+
+    private byte[] getCalendarFileContent(String majorVersion, short minorVersion, JsonArray scheduleList) {
+        JsonObject syncData = new JsonObject();
+        syncData.addProperty("major", majorVersion);
+        syncData.addProperty("minor", minorVersion);
+        syncData.add("scheduleList", scheduleList);
+
+        String data = new Gson().toJson(syncData);
+        LOG.info(data);
+
+        byte[] dataBytes = data.getBytes(StandardCharsets.UTF_8);
+        ByteBuffer sendData = ByteBuffer.allocate(dataBytes.length + 8); // 8 is data header
+        //NOTE: minor version is short in response but in this case it writes as integer
+        sendData.putInt(minorVersion);
+        sendData.putInt(dataBytes.length);
+        sendData.put(dataBytes);
+
+        return sendData.array();
+    }
+
+    private boolean sendCalendarFile(String majorVersion, short minorVersion, JsonArray calendarData) {
+        LOG.info("Send calendar file upload info");
+
+        if (majorVersion == null || majorVersion.isEmpty()) {
+            majorVersion = new String(this.manager.getSupportProvider().getAndroidId(), StandardCharsets.UTF_8);
+        }
+
+        byte[] data = getCalendarFileContent(majorVersion, minorVersion, calendarData);
+
+        HuaweiUploadManager.FileUploadInfo fileInfo = new HuaweiUploadManager.FileUploadInfo();
+
+        fileInfo.setFileType((byte) 7);
+        fileInfo.setFileName(getFileCalendarName());
+        fileInfo.setBytes(data);
+        fileInfo.setSrcPackage(this.getModule());
+        fileInfo.setDstPackage(this.getPackage());
+        fileInfo.setSrcFingerprint(this.getLocalFingerprint());
+        fileInfo.setDstFingerprint(this.getFingerprint());
+
+        fileInfo.setFileUploadCallback(new HuaweiUploadManager.FileUploadCallback() {
+            @Override
+            public void onUploadStart() {
+                // TODO: set device as busy in this case. But maybe exists another way to do this. Currently user see text on device card.
+                // Also text should be changed
+                manager.getSupportProvider().getDevice().setBusyTask(manager.getSupportProvider().getContext().getString(R.string.updating_firmware));
+                manager.getSupportProvider().getDevice().sendDeviceUpdateIntent(manager.getSupportProvider().getContext());
+            }
+
+            @Override
+            public void onUploadProgress(int progress) {
+            }
+
+            @Override
+            public void onUploadComplete() {
+                if (manager.getSupportProvider().getDevice().isBusy()) {
+                    manager.getSupportProvider().getDevice().unsetBusyTask();
+                    manager.getSupportProvider().getDevice().sendDeviceUpdateIntent(manager.getSupportProvider().getContext());
+                }
+                if(lastCalendarEvents == null) {
+                    scheduleUpdate(100);
+                }
+            }
+
+            @Override
+            public void onError(int code) {
+                // TODO: maybe we should retry resend on error, at least 3 times.
+                // currently I don't understand the mandatory of this action because file sends always successfully,
+            }
+        });
+
+        HuaweiUploadManager huaweiUploadManager = this.manager.getSupportProvider().getUploadManager();
+
+        huaweiUploadManager.setFileUploadInfo(fileInfo);
+
+        try {
+            SendFileUploadInfo sendFileUploadInfo = new SendFileUploadInfo(this.manager.getSupportProvider(), huaweiUploadManager);
+            sendFileUploadInfo.doPerform();
+        } catch (IOException e) {
+            LOG.error("Failed to send file upload info", e);
+        }
+        return true;
+    }
+
+
+    private boolean syncCalendarEvents(String majorVersion, short minorVersion, short scheduleCount) {
+        LOG.info("Sync calendar file upload info");
+
+        JsonArray calendarData;
+        if (TextUtils.isEmpty(majorVersion) || lastCalendarEvents == null || minorVersion == 0) {
+            if(lastCalendarEvents == null && minorVersion != 0) {
+                minorVersion = 0;
+                calendarData = new JsonArray();
+            } else {
+                calendarData = getFullCalendarData();
+                if (calendarData.isEmpty()) {
+                    if (minorVersion == 0 && !TextUtils.isEmpty(majorVersion)) {
+                        return false;
+                    }
+                    minorVersion = 0;
+                } else {
+                    minorVersion++;
+                }
+            }
+        } else {
+            calendarData = getUpdateCalendarData();
+            if (calendarData.isEmpty())
+                return false;
+        }
+
+        return sendCalendarFile(majorVersion, minorVersion, calendarData);
+    }
+
+    @Override
+    public void handleData(byte[] data) {
+        try {
+            byte commandId = data[0];
+            if (commandId == 1) {
+                HuaweiTLV tlv = new HuaweiTLV();
+                tlv.parse(data, 1, data.length - 1);
+                byte operateMode = -1;
+                String majorVersion = null;
+                short minorVersion = -1;
+                short scheduleCount = -1;
+
+                if (tlv.contains(0x1))
+                    operateMode = tlv.getByte(0x1);
+                if (tlv.contains(0x2))
+                    majorVersion = tlv.getString(0x2).trim();
+                if (tlv.contains(0x3))
+                    minorVersion = tlv.getShort(0x3);
+                if (tlv.contains(0x4))
+                    scheduleCount = tlv.getShort(0x4);
+
+                LOG.info("Operate mode: {} Major: {} Minor: {} Schedule Count: {}", operateMode, majorVersion, minorVersion, scheduleCount);
+
+                // TODO: scheduleCount can be a max number of events to send, but I am not sure. Ignore it for now.
+                // NOTE: device can initiate calendar sync. So we need to check and answer properly.
+                final boolean syncEnabled = GBApplication.getDeviceSpecificSharedPrefs(manager.getSupportProvider().getDevice().getAddress()).getBoolean(PREF_SYNC_CALENDAR, false);
+                if(!syncEnabled) {
+                    sendCalendarCmd((byte) 0x01, (byte) 0x07, null);  //sync disabled
+                    return;
+                }
+
+                if (operateMode != -1 && majorVersion != null && minorVersion != -1 && scheduleCount != -1) {
+                    if (operateMode == 3) {
+                        sendCalendarCmd((byte) 0x01, (byte) 0x03, null);
+                    }
+                    if (operateMode == 2 || operateMode == 3) {
+
+                        //TODO:
+                        //sendCalendarCmd((byte) 0x01, (byte) 0x06, null);  //no permissions
+
+                        //external calendar synchronization only supported on Harmony devices. I don't know how to deal with this.
+                        if (!manager.getSupportProvider().getHuaweiCoordinator().supportsExternalCalendarService()) {
+                            if (!syncCalendarEvents(majorVersion, minorVersion, scheduleCount)) {
+                                sendCalendarCmd((byte) 0x01, (byte) 0x04, null);  //No sync required
+                            }
+                        }
+                    }
+                }
+
+            } else if (commandId == 3) {
+                HuaweiTLV tlv = new HuaweiTLV();
+                tlv.parse(data, 1, data.length - 1);
+                //TODO: wearable sends this command if calendar event status changed.
+                // For example one of the quick action buttons on the reminders notification wes pressed.
+                // There are two buttons:
+                //   Snooze - status 0
+                //   Close - status 2
+                // It should be send to Android or stored to local GB database. And should be used during next synchronization.
+                // Should be saved to CalendarContract.CalendarAlerts, column "state".
+                // CalendarContract.CalendarAlertsColumns.STATE
+                // Values:
+                //    STATE_DISMISSED = 2
+                //    STATE_FIRED = 1
+                //    STATE_SCHEDULED = 0
+                // Currently GB does not support CALENDAR_WRITE permission so it is not possible.
+                // Additional research required.
+                LOG.info("calendarRequest");
+                if (tlv.contains(0x1))
+                    LOG.info("eventId: {}", tlv.getString(0x1).trim());
+                if (tlv.contains(0x2))
+                    LOG.info("calendarId: {}", tlv.getString(0x2).trim());
+                if (tlv.contains(0x3))
+                    LOG.info("accountName: {}", tlv.getString(0x3).trim());
+                if (tlv.contains(0x4))
+                    LOG.info("accountType: {}", tlv.getString(0x4).trim());
+                if (tlv.contains(0x5))
+                    LOG.info("state: {}", tlv.getByte(0x5)); //state: 0 - snooze, 2 - close. Quick actions on watch
+                if (tlv.contains(0x6))
+                    LOG.info("eventUuid: {}", tlv.getString(0x6).trim());
+            } else {
+                LOG.info("Unknown command");
+            }
+        } catch (HuaweiPacket.MissingTagException e) {
+            LOG.error("P2P handle packet: tag is missing");
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/p2p/HuaweiP2PDataDictionarySyncService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/p2p/HuaweiP2PDataDictionarySyncService.java
new file mode 100644
index 0000000..49e276a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/p2p/HuaweiP2PDataDictionarySyncService.java
@@ -0,0 +1,312 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.p2p;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiDictTypes;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiTLV;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiP2PManager;
+
+public class HuaweiP2PDataDictionarySyncService extends HuaweiBaseP2PService {
+    private final Logger LOG = LoggerFactory.getLogger(HuaweiP2PDataDictionarySyncService.class);
+
+    public static final String MODULE = "hw.unitedevice.datadictionarysync";
+
+    private AtomicBoolean serviceAvailable = new AtomicBoolean(false);
+
+
+    private List<Integer> classesToSync = null;
+
+    public interface DictionarySyncCallback {
+        void onComplete(boolean complete);
+    }
+
+    private final Map<Integer, DictionarySyncCallback> currentRequests = new HashMap<>();
+
+    public HuaweiP2PDataDictionarySyncService(HuaweiP2PManager manager) {
+        super(manager);
+        LOG.info("P2PDataDictionarySyncService");
+    }
+
+    @Override
+    public String getModule() {
+        return HuaweiP2PDataDictionarySyncService.MODULE;
+    }
+
+    @Override
+    public String getPackage() {
+        return "hw.watch.health.filesync";
+    }
+
+    @Override
+    public String getFingerprint() {
+        return "SystemApp";
+    }
+
+    public static byte[] dictToBytes(int value) {
+        return new byte[]{
+                (byte) (value >>> 16),
+                (byte) (value >>> 8),
+                (byte) value};
+    }
+
+    public List<Integer> checkSupported(HuaweiCoordinator coordinator, List<Integer> list) {
+        List<Integer> result = new ArrayList<>();
+
+        for(Integer cl: list) {
+            if((cl == HuaweiDictTypes.BODY_TEMPERATURE_CLASS || cl == HuaweiDictTypes.SKIN_TEMPERATURE_CLASS) && coordinator.supportsTemperature()) {
+                result.add(cl);
+            } else if(cl == HuaweiDictTypes.BLOOD_PRESSURE_CLASS && coordinator.supportsBloodPressure()) {
+                result.add(cl);
+            }
+        }
+        return result;
+    }
+
+    public void startSync(List<Integer> dictClasses, DictionarySyncCallback callback) {
+        LOG.info("P2PDataDictionarySyncService startSync {}", dictClasses);
+        classesToSync = dictClasses;
+        if(classesToSync.isEmpty()) {
+            callback.onComplete(false);
+            return;
+        }
+        sendSyncRequest(classesToSync.remove(0), callback);
+    }
+
+    private void sendSyncRequest(int dictClass, DictionarySyncCallback callback) {
+
+        LOG.info("P2PDataDictionarySyncService class {}", dictClass);
+
+        if (!serviceAvailable.get()) {
+            LOG.info("P2PDataDictionarySyncService not available");
+            callback.onComplete(false);
+            return;
+        }
+
+        if(currentRequests.containsKey(dictClass)) {
+            LOG.info("P2PDataDictionarySyncService current class in progress");
+            callback.onComplete(false);
+            return;
+        }
+
+        long startTime =  manager.getSupportProvider().getLastDataDictLastTimestamp(dictClass);
+        if(startTime > 0) {
+            startTime += 1000;
+        }
+
+        HuaweiTLV tlv = new HuaweiTLV()
+                .put(0x1, (byte) 1)
+                .put(0x2, dictToBytes(dictClass)) //-- skin temperature
+                .put(0x5, startTime)
+                .put(0x6, System.currentTimeMillis())
+                .put(0x0d, (byte) 1);
+        byte[] data = tlv.serialize();
+        if (data == null) {
+            LOG.error("Incorrect data");
+            callback.onComplete(false);
+            return;
+        }
+
+
+        ByteBuffer packet = ByteBuffer.allocate(1 + data.length);
+        packet.put((byte) 0x1); // type tlv
+        packet.put(data);
+        packet.flip();
+
+        LOG.info("P2PDataDictionarySyncService send command");
+        currentRequests.put(dictClass, callback);
+
+        sendCommand(packet.array(), null);
+    }
+
+    @Override
+    public void registered() {
+        sendPing(new HuaweiP2PCallback() {
+            @Override
+            public void onResponse(int code, byte[] data) {
+                if ((byte) code != (byte) 0xca)
+                    return;
+                serviceAvailable.set(true);
+            }
+        });
+
+    }
+
+    @Override
+    public void unregister() {
+        serviceAvailable.set(false);
+    }
+
+    public static class DictData {
+        public static class DictDataValue {
+            private final int dataType;
+            private final byte tag;
+            private final byte[] value;
+
+            public DictDataValue(int dataType, byte tag, byte[] value) {
+                this.dataType = dataType;
+                this.tag = tag;
+                this.value = value;
+            }
+
+            public int getDataType() {
+                return dataType;
+            }
+
+            public byte getTag() {
+                return tag;
+            }
+
+            public byte[] getValue() {
+                return value;
+            }
+
+            @Override
+            public String toString() {
+                final StringBuffer sb = new StringBuffer("HuaweiDictDataValue{");
+                sb.append("dataType=").append(dataType);
+                sb.append(", tag=").append(tag);
+                sb.append(", value=");
+                if (value == null) sb.append("null");
+                else {
+                    sb.append('[');
+                    for (int i = 0; i < value.length; ++i)
+                        sb.append(i == 0 ? "" : ", ").append(value[i]);
+                    sb.append(']');
+                }
+                sb.append('}');
+                return sb.toString();
+            }
+        }
+
+
+        private final int dictClass;
+        private final long startTimestamp;
+        private final long endTimestamp;
+        private final long modifyTimestamp;
+        private final List<DictDataValue> data;
+
+        public DictData(int dictClass, long startTimestamp, long endTimestamp, long modifyTimestamp, List<DictDataValue> data) {
+            this.dictClass = dictClass;
+            this.startTimestamp = startTimestamp;
+            this.endTimestamp = endTimestamp;
+            this.modifyTimestamp = modifyTimestamp;
+            this.data = data;
+        }
+
+        public int getDictClass() { return dictClass; }
+
+        public long getStartTimestamp() {
+            return startTimestamp;
+        }
+
+        public long getEndTimestamp() {
+            return endTimestamp;
+        }
+
+        public long getModifyTimestamp() {
+            return modifyTimestamp;
+        }
+
+        public List<DictDataValue> getData() {
+            return data;
+        }
+
+        @Override
+        public String toString() {
+            final StringBuffer sb = new StringBuffer("HuaweiDictSample{");
+            sb.append("startTime=").append(startTimestamp);
+            sb.append(", endTime=").append(endTimestamp);
+            sb.append(", modifyTime=").append(modifyTimestamp);
+            sb.append(", data=").append(data);
+            sb.append('}');
+            return sb.toString();
+        }
+    }
+
+    @Override
+    public void handleData(byte[] data) {
+        LOG.info("P2PDataDictionarySyncService handleData: {}", data.length);
+        if (data[0] == 1) {
+            DictionarySyncCallback callback = null;
+            try {
+
+
+                HuaweiTLV tlv = new HuaweiTLV();
+                tlv.parse(data, 1, data.length - 1);
+
+                int operation = tlv.getInteger(0x01); ///???
+                int dictClass = tlv.getInteger(0x02);
+
+                if(!currentRequests.containsKey(dictClass)) {
+                    return;
+                }
+                callback = currentRequests.remove(dictClass);
+
+                if(callback == null) {
+                    return;
+                }
+
+                //NOTE: all tags with high bit set should be parsed as container
+
+                List<DictData> result =  new ArrayList<>();
+
+                for (HuaweiTLV blockTlv : tlv.getObjects(0x83)) {
+                    for (HuaweiTLV l : blockTlv.getObjects(0x84)) {
+                        //5 - start time, 6 - end time, 0xc - modify time
+                        long startTimestamp = l.getLong(0x5);
+                        long endTimestamp = 0;
+                        long modifyTimestamp = 0;
+                        if (l.contains(0x6))
+                            endTimestamp = l.getLong(0x6);
+                        if (l.contains(0xc))
+                            modifyTimestamp = l.getLong(0xc);
+                        List<DictData.DictDataValue> dataValues = new ArrayList<>();
+                        for (HuaweiTLV l1 : l.getObjects(0x87)) {
+                            for (HuaweiTLV ll : l1.getObjects(0x88)) {
+                                int type = ll.getInteger(0x9);
+                                if (ll.contains(0xa))
+                                    dataValues.add(new DictData.DictDataValue(type, (byte) 0xa, ll.getBytes(0xa)));
+                                if (ll.contains(0xb))
+                                    dataValues.add(new DictData.DictDataValue(type, (byte) 0xb, ll.getBytes(0xb)));
+                            }
+                        }
+                        result.add(new DictData(dictClass, startTimestamp, endTimestamp, modifyTimestamp, dataValues));
+                    }
+                }
+
+                manager.getSupportProvider().addDictData(result);
+
+                if (!result.isEmpty()) {
+                    sendSyncRequest(dictClass, callback);
+                } else {
+                    if(classesToSync.isEmpty()) {
+                        classesToSync = null;
+                        callback.onComplete(true);
+                    } else {
+                        sendSyncRequest(classesToSync.remove(0), callback);
+                    }
+                }
+            } catch (HuaweiPacket.MissingTagException e) {
+                LOG.error("P2PDataDictionarySyncService parse error", e);
+                if(callback != null) {
+                    callback.onComplete(false);
+                }
+            }
+        }
+    }
+
+    public static HuaweiP2PDataDictionarySyncService getRegisteredInstance(HuaweiP2PManager manager) {
+        return (HuaweiP2PDataDictionarySyncService) manager.getRegisteredService(HuaweiP2PDataDictionarySyncService.MODULE);
+    }
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/p2p/HuaweiP2PTrackService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/p2p/HuaweiP2PTrackService.java
new file mode 100644
index 0000000..da92fec
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/p2p/HuaweiP2PTrackService.java
@@ -0,0 +1,106 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.p2p;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiSportHRZones;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiP2PManager;
+
+public class HuaweiP2PTrackService extends HuaweiBaseP2PService {
+    private final Logger LOG = LoggerFactory.getLogger(HuaweiP2PTrackService.class);
+
+    public static final String MODULE = "hw.unitedevice.track";
+
+    private static final int HEADER_LENGTH = 36;
+
+    static class Sequence {
+        int counter;
+
+        private Sequence() {
+            this.counter = 0;
+        }
+
+        public int getNext() {
+            synchronized (this) {
+                this.counter = (this.counter + 1) % 10000;
+                return this.counter;
+            }
+        }
+    }
+
+    private final Sequence counter = new Sequence();
+
+    public HuaweiP2PTrackService(HuaweiP2PManager manager) {
+        super(manager);
+        LOG.info("HuaweiP2PTrackService");
+    }
+
+    @Override
+    public String getModule() {
+        return HuaweiP2PTrackService.MODULE;
+    }
+
+    @Override
+    public String getPackage() {
+        return "hw.watch.health.p2p";
+    }
+
+    @Override
+    public String getFingerprint() {
+        return "SystemApp";
+    }
+
+    public void sendHeartZoneConfig() {
+
+        ActivityUser activityUser = new ActivityUser();
+
+        HuaweiSportHRZones hrZones = new HuaweiSportHRZones(activityUser.getAge());
+
+        byte[] data = hrZones.getHRZonesData();
+        if (data == null) {
+            LOG.error("Incorrect Heart Rate config");
+            return;
+        }
+
+        ByteBuffer header = ByteBuffer.allocate(HEADER_LENGTH);
+        header.order(ByteOrder.LITTLE_ENDIAN);
+        header.putInt(2); // session id ??
+        header.putInt(1); // version
+        header.putInt(HEADER_LENGTH + data.length); // total length
+        header.putInt(0); // unknown, sub header length??
+        header.putInt(counter.getNext()); // message id
+        header.flip();
+
+        ByteBuffer packet = ByteBuffer.allocate(HEADER_LENGTH + data.length);
+        packet.put(header.array());
+        packet.put(data);
+        packet.flip();
+
+        LOG.info("HuaweiP2PTrackService sendHeartZoneConfig");
+
+        sendCommand(packet.array(), null);
+    }
+
+    @Override
+    public void registered() {
+        sendHeartZoneConfig();
+    }
+
+    @Override
+    public void unregister() {
+
+    }
+
+    @Override
+    public void handleData(byte[] data) {
+        LOG.info("HuaweiP2PTrackService handleData: {}", data.length);
+    }
+
+    public static HuaweiP2PTrackService getRegisteredInstance(HuaweiP2PManager manager) {
+        return (HuaweiP2PTrackService) manager.getRegisteredService(HuaweiP2PTrackService.MODULE);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/AcceptAgreementsRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/AcceptAgreementsRequest.java
new file mode 100644
index 0000000..c189ecc
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/AcceptAgreementsRequest.java
@@ -0,0 +1,53 @@
+/*  Copyright (C) 2024 Vitalii Tomin
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.DeviceConfig;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class AcceptAgreementsRequest extends Request {
+    private static final Logger LOG = LoggerFactory.getLogger(AcceptAgreementsRequest.class);
+
+    public AcceptAgreementsRequest(HuaweiSupportProvider support) {
+        super(support);
+
+        this.serviceId = DeviceConfig.id;
+        this.commandId = DeviceConfig.AcceptAgreement.id;
+    }
+
+    @Override
+    protected boolean requestSupported() {
+        return supportProvider.getHuaweiCoordinator().supportsAcceptAgreement();
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        try {
+            return new DeviceConfig.AcceptAgreement.Request(paramsProvider).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/AlarmsRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/AlarmsRequest.java
new file mode 100644
index 0000000..a3737fc
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/AlarmsRequest.java
@@ -0,0 +1,95 @@
+/*  Copyright (C) 2024 Damien Gaignon, Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import static nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Alarms.EventAlarmsRequest;
+import static nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Alarms.SmartAlarmRequest;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Alarms;
+import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class AlarmsRequest extends Request {
+    private static final Logger LOG = LoggerFactory.getLogger(AlarmsRequest.class);
+
+    private EventAlarmsRequest eventAlarmsRequest = null;
+    private SmartAlarmRequest smartAlarmRequest = null;
+
+    public AlarmsRequest(HuaweiSupportProvider support, boolean smart) {
+        super(support);
+        this.serviceId = Alarms.id;
+        this.commandId = smart ? SmartAlarmRequest.id : EventAlarmsRequest.id;
+        if (!smart)
+            eventAlarmsRequest = new EventAlarmsRequest(support.getParamsProvider());
+    }
+
+    public void addEventAlarm(Alarm alarm, boolean increasePosition) {
+        if (!alarm.getUnused()) {
+            byte position = (byte) alarm.getPosition();
+            if (increasePosition)
+                position += 1;
+            eventAlarmsRequest.addEventAlarm(new Alarms.EventAlarm(
+                    position,
+                    alarm.getEnabled(),
+                    (byte) alarm.getHour(),
+                    (byte) alarm.getMinute(),
+                    (byte) alarm.getRepetition(),
+                    alarm.getTitle()
+            ));
+        }
+    }
+
+    public void buildSmartAlarm(Alarm alarm) {
+        Integer smartWakeupInterval = alarm.getSmartWakeupInterval();
+        this.smartAlarmRequest = new SmartAlarmRequest(
+                paramsProvider,
+                new Alarms.SmartAlarm(
+                        alarm.getEnabled() && !alarm.getUnused(),
+                        (byte) alarm.getHour(),
+                        (byte) alarm.getMinute(),
+                        (byte) alarm.getRepetition(),
+                        smartWakeupInterval == null ? 5 : smartWakeupInterval.byteValue()
+                )
+        );
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        try {
+            if (eventAlarmsRequest != null) {
+                return eventAlarmsRequest.serialize();
+            } else if (smartAlarmRequest != null) {
+                return smartAlarmRequest.serialize();
+            } else {
+                throw new RequestCreationException("No alarms set");
+            }
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+
+    @Override
+    protected void processResponse() {
+        LOG.debug("handle Alarm");
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/DebugRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/DebugRequest.java
new file mode 100644
index 0000000..fb90dc0
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/DebugRequest.java
@@ -0,0 +1,241 @@
+/*  Copyright (C) 2024 Damien Gaignon, Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiConstants;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiTLV;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class DebugRequest extends Request {
+
+    public DebugRequest(HuaweiSupportProvider support) {
+        super(support);
+        this.serviceId = 0;
+        this.commandId = 0;
+        this.addToResponse = false;
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        String debugString = GBApplication
+            .getDeviceSpecificSharedPrefs(supportProvider.getDevice().getAddress())
+            .getString(HuaweiConstants.PREF_HUAWEI_DEBUG_REQUEST, "1,1,false,(1,/),(2,/),(3,/),(4,/)");
+        HuaweiPacket packet = parseDebugString(debugString);
+        try {
+            return packet.serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+
+    /*
+        DebugString		:= [service_id] "," [command id] "," [encryptflag] "," [sliceflag] ("," [tlv])*
+        service_id		:= int
+                         | "0x" hex
+        command_id		:= int
+                         | "0x" hex
+        encryptflag     := [boolean]
+        sliceflag       := [boolean]
+        boolean 		:= "true"
+                         | "t"
+                         | "false"
+                         | "f"
+        tlv				:= "(" [tag] "," [typevalue] ")" ("," [tlv])*
+        tag				:= int
+                         | "0x" hex
+        typevalue		:= [type] [value]
+                         | [tlv]
+        type			:= "/"                                  # Empty tag
+                         | "B"									# Byte (1 byte)
+                         | "S"									# Short (2 bytes)
+                         | "I"									# Integer (4 bytes)
+                         | "b"                                  # Boolean
+                         | "a"									# Array of bytes (in hex)
+                         | "-"									# String
+        value			:= [any]
+     */
+
+    public HuaweiPacket parseDebugString(String debugString) throws RequestCreationException {
+        HuaweiPacket packet = new HuaweiPacket(paramsProvider);
+
+        int current = 0;
+        int nextComma = debugString.indexOf(',');
+
+        if (nextComma < 1 || debugString.length() - current < 2)
+            throw new RequestCreationException("Invalid debug command");
+
+        if (debugString.charAt(current+1) == 'x')
+            packet.serviceId = Short.valueOf(debugString.substring(current+2, nextComma), 16).byteValue();
+        else
+            packet.serviceId = Short.valueOf(debugString.substring(current, nextComma)).byteValue();
+
+        current = nextComma + 1;
+        nextComma = debugString.indexOf(',', current);
+
+        if (nextComma < 1 || debugString.length() - current < 2)
+            throw new RequestCreationException("Invalid debug command");
+
+        if (debugString.charAt(current+1) == 'x')
+            packet.commandId = Short.valueOf(debugString.substring(current+2, nextComma), 16).byteValue();
+        else
+            packet.commandId = Short.valueOf(debugString.substring(current, nextComma)).byteValue();
+
+        current = nextComma + 1;
+        nextComma = debugString.indexOf(',', current);
+
+        if (nextComma < 1 || debugString.length() - current < 2)
+            throw new RequestCreationException("Invalid debug command");
+
+        switch (debugString.substring(current, nextComma)) {
+            case "true":
+            case "t":
+                packet.setEncryption(true);
+                break;
+            case "false":
+            case "f":
+                packet.setEncryption(false);
+                break;
+            default:
+                throw new RequestCreationException("Boolean is not a boolean");
+        }
+
+        current = nextComma + 1;
+        nextComma = debugString.indexOf(',', current);
+        if (debugString.length() - current < 1)
+            throw new RequestCreationException("Invalid debug command");
+        if (nextComma < 0)
+            nextComma = debugString.length(); // For no TLVs
+
+        switch (debugString.substring(current, nextComma)) {
+            case "true":
+            case "t":
+                packet.setSliced(true);
+                break;
+            case "false":
+            case "f":
+                packet.setSliced(false);
+                break;
+            default:
+                throw new RequestCreationException("Boolean is not a boolean");
+        }
+
+        current = nextComma + 1;
+
+        if (current < debugString.length()) {
+            HuaweiTlvParseReturn retv = parseTlv(debugString.substring(current));
+            if (current + retv.parsedCount != debugString.length())
+                throw new RequestCreationException("Invalid debug command");
+            packet.setTlv(retv.tlv);
+        }
+
+        packet.complete = true;
+        return packet;
+    }
+
+    private HuaweiTlvParseReturn parseTlv(String tlvString) throws RequestCreationException {
+        HuaweiTLV tlv = new HuaweiTLV();
+        int current = 0;
+        int nextDelim;
+
+        while (current < tlvString.length()) {
+            if (tlvString.charAt(current) != '(')
+                throw new RequestCreationException("Invalid debug command");
+
+            current += 1;
+            nextDelim = tlvString.indexOf(',', current);
+
+            if (nextDelim < 1 || tlvString.length() - current < 2)
+                throw new RequestCreationException("Invalid debug command");
+
+            byte tag;
+            // Short in between is because Java doesn't like unsigned numbers
+            if (tlvString.charAt(current+1) == 'x')
+                tag = Short.valueOf(tlvString.substring(current+2, nextDelim), 16).byteValue();
+            else
+                tag = Short.valueOf(tlvString.substring(current, nextDelim)).byteValue();
+
+            current = nextDelim + 1;
+            nextDelim = tlvString.indexOf(')', current);
+
+            if (nextDelim < 1)
+                throw new RequestCreationException("Invalid debug command");
+
+            if (tlvString.charAt(current) != '(') {
+                char type = tlvString.charAt(current);
+                String value = tlvString.substring(current + 1, nextDelim);
+
+                switch (type) {
+                    case '/':
+                        tlv.put(tag);
+                        break;
+                    case 'B':
+                        tlv.put(tag, Byte.parseByte(value));
+                        break;
+                    case 'S':
+                        tlv.put(tag, Short.parseShort(value));
+                        break;
+                    case 'I':
+                        tlv.put(tag, Integer.parseInt(value));
+                        break;
+                    case 'b':
+                        tlv.put(tag, value.equals("1"));
+                        break;
+                    case 'a':
+                        tlv.put(tag, GB.hexStringToByteArray(value));
+                        break;
+                    case '-':
+                        tlv.put(tag, value);
+                        break;
+                    default:
+                        throw new RequestCreationException("Invalid tag type");
+                }
+
+                current = nextDelim + 1;
+            } else {
+                HuaweiTlvParseReturn retv = parseTlv(tlvString.substring(current));
+                tlv.put(tag, retv.tlv);
+                current += retv.parsedCount + 1;
+            }
+
+            if (current == tlvString.length())
+                break;
+            if (tlvString.charAt(current) == ')')
+                break;
+            if (tlvString.charAt(current) != ',')
+                throw new RequestCreationException("Invalid debug command");
+
+            current += 1;
+        }
+
+        return new HuaweiTlvParseReturn(tlv, current);
+    }
+
+    private static class HuaweiTlvParseReturn {
+        public HuaweiTLV tlv;
+        public Integer parsedCount;
+
+        HuaweiTlvParseReturn(HuaweiTLV tlv, Integer parsedCount) {
+            this.tlv = tlv;
+            this.parsedCount = parsedCount;
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetActivityTypeRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetActivityTypeRequest.java
new file mode 100644
index 0000000..6e75c86
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetActivityTypeRequest.java
@@ -0,0 +1,62 @@
+/*  Copyright (C) 2024 Damien Gaignon
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+
+/* In order to be compatible with all devices, request send all possible commands
+to all possible services. This implies long packet which is not handled on the device.
+Thus, this request could be sliced in 3 packets. But this command does not support slicing.
+Thus, one need to send multiple requests and concat the response.
+Packets should be 240 bytes max */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.DeviceConfig;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class GetActivityTypeRequest extends Request {
+    private static final Logger LOG = LoggerFactory.getLogger(GetActivityTypeRequest.class);
+
+    public GetActivityTypeRequest(HuaweiSupportProvider support) {
+        super(support);
+        this.serviceId = DeviceConfig.id;
+        this.commandId = DeviceConfig.ActivityType.id;
+    }
+
+    @Override
+    protected boolean requestSupported() {
+        return supportProvider.getHuaweiCoordinator().supportsActivityType();
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        DeviceConfig.ActivityType.Request activityRequest = new DeviceConfig.ActivityType.Request(paramsProvider);
+        try {
+            return activityRequest.serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+
+    @Override
+    protected void processResponse() throws ResponseParseException {
+        LOG.debug("handle Activity Type");
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetAppInfoParams.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetAppInfoParams.java
new file mode 100644
index 0000000..15724b4
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetAppInfoParams.java
@@ -0,0 +1,40 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.App;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class GetAppInfoParams extends Request{
+
+    public GetAppInfoParams(HuaweiSupportProvider support) {
+        super(support);
+        this.serviceId = App.id;
+        this.commandId = App.AppInfoParams.id;
+
+    }
+
+    @Override
+    protected boolean requestSupported() {
+        return supportProvider.getHuaweiCoordinator().supportsAppParams();
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        try {
+            return new App.AppInfoParams.Request(paramsProvider).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+
+    @Override
+    protected void processResponse() throws ResponseParseException {
+        if (!(receivedPacket instanceof App.AppInfoParams.Response))
+            throw new ResponseTypeMismatchException(receivedPacket, App.AppInfoParams.Response.class);
+
+        App.AppInfoParams.Response resp = (App.AppInfoParams.Response)(receivedPacket);
+        supportProvider.getHuaweiCoordinator().setAppDeviceParams(resp.params);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetAppNames.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetAppNames.java
new file mode 100644
index 0000000..5a7cd54
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetAppNames.java
@@ -0,0 +1,33 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.App;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class GetAppNames extends Request{
+    public GetAppNames(HuaweiSupportProvider support) {
+        super(support);
+        this.serviceId = App.id;
+        this.commandId = App.AppNames.id;
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        try {
+            return new App.AppNames.Request(paramsProvider).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+
+    @Override
+    protected void processResponse() throws ResponseParseException {
+        if (!(receivedPacket instanceof App.AppNames.Response))
+            throw new ResponseTypeMismatchException(receivedPacket, App.AppNames.Response.class);
+
+        App.AppNames.Response resp = (App.AppNames.Response)(receivedPacket);
+        supportProvider.getHuaweiAppManager().setInstalledAppList(resp.appInfoList);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetAuthRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetAuthRequest.java
new file mode 100644
index 0000000..9b7b951
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetAuthRequest.java
@@ -0,0 +1,122 @@
+/*  Copyright (C) 2024 Damien Gaignon, Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.UnsupportedEncodingException;
+import java.nio.ByteBuffer;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.spec.InvalidKeySpecException;
+import java.util.Arrays;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiCrypto;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.DeviceConfig;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
+
+public class GetAuthRequest extends Request {
+    private static final Logger LOG = LoggerFactory.getLogger(GetAuthRequest.class);
+
+    protected final byte[] clientNonce;
+    protected short authVersion;
+    protected byte authAlgo;
+    protected byte[] doubleNonce;
+    protected byte[] key = null;
+    protected byte deviceSupportType;
+    protected byte authMode;
+
+    public GetAuthRequest(HuaweiSupportProvider support,
+            Request linkParamsReq) {
+        super(support);
+        this.serviceId = DeviceConfig.id;
+        this.commandId = DeviceConfig.Auth.id;
+        this.clientNonce = HuaweiCrypto.generateNonce();
+        doubleNonce = ByteBuffer.allocate(32)
+                .put(((GetLinkParamsRequest)linkParamsReq).serverNonce)
+                .put(clientNonce)
+                .array();
+        this.authVersion = paramsProvider.getAuthVersion();
+        this.authAlgo = paramsProvider.getAuthAlgo();
+        this.deviceSupportType = paramsProvider.getDeviceSupportType();
+        this.authMode = paramsProvider.getAuthMode();
+        this.huaweiCrypto = new HuaweiCrypto(authVersion, authAlgo, deviceSupportType, authMode);
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        byte[] nonce;
+
+        try {
+            if (authMode == 0x02) {
+                key = paramsProvider.getPinCode();
+                if (authVersion == 0x02)
+                    key = paramsProvider.getSecretKey();
+            }
+            nonce = ByteBuffer.allocate(18)
+                    .putShort(authVersion)
+                    .put(clientNonce)
+                    .array();
+            ByteBuffer digestedChallenge = ByteBuffer.wrap(huaweiCrypto.digestChallenge(key, doubleNonce));
+            byte[] challenge = new byte[0x20];
+            digestedChallenge.get(challenge, 0x00, 0x20);
+            LOG.debug("challenge: " + GB.hexdump(challenge));
+            byte[] firstKey = new byte[0x10];
+            digestedChallenge.get(firstKey, 0x00, 0x10);
+            paramsProvider.setFirstKey(firstKey);
+            if (challenge == null)
+                throw new RequestCreationException("Challenge null");
+            return new DeviceConfig.Auth.Request(paramsProvider, challenge, nonce).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        } catch (NoSuchAlgorithmException | InvalidKeyException | InvalidKeySpecException | UnsupportedEncodingException e) {
+            throw new RequestCreationException("Digest exception", e);
+        }
+    }
+
+    @Override
+    protected void processResponse() throws ResponseParseException {
+        LOG.debug("handle Auth");
+
+        if (!(receivedPacket instanceof DeviceConfig.Auth.Response))
+            throw new ResponseTypeMismatchException(receivedPacket, DeviceConfig.Auth.Response.class);
+
+        try {
+            ByteBuffer digestedChallenge = ByteBuffer.wrap(huaweiCrypto.digestResponse(key, doubleNonce));
+            byte[] expectedAnswer = new byte[0x20];
+            digestedChallenge.get(expectedAnswer, 0x00, 0x20);
+            LOG.debug("challenge: " + GB.hexdump(expectedAnswer));
+            if (expectedAnswer == null)
+                throw new ResponseParseException("Challenge null");
+            byte[] actualAnswer = ((DeviceConfig.Auth.Response) receivedPacket).challengeResponse;
+            if (!Arrays.equals(expectedAnswer, actualAnswer)) {
+                throw new ResponseParseException("Challenge answer mismatch : "
+                        + StringUtils.bytesToHex(actualAnswer)
+                        + " != "
+                        + StringUtils.bytesToHex(expectedAnswer)
+                );
+            }
+        } catch (NoSuchAlgorithmException | InvalidKeyException | InvalidKeySpecException | UnsupportedEncodingException e) {
+            throw new ResponseParseException("Challenge response digest exception");
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetBatteryLevelRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetBatteryLevelRequest.java
new file mode 100644
index 0000000..5b02312
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetBatteryLevelRequest.java
@@ -0,0 +1,99 @@
+/*  Copyright (C) 2024 Damien Gaignon, Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import android.widget.Toast;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.DeviceConfig;
+import nodomain.freeyourgadget.gadgetbridge.model.BatteryState;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class GetBatteryLevelRequest extends Request {
+    private static final Logger LOG = LoggerFactory.getLogger(GetBatteryLevelRequest.class);
+
+    public GetBatteryLevelRequest(HuaweiSupportProvider support) {
+        super(support);
+        this.serviceId = DeviceConfig.id;
+        this.commandId = DeviceConfig.BatteryLevel.id;
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        try {
+            return new DeviceConfig.BatteryLevel.Request(paramsProvider).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+
+    public static BatteryState byteToBatteryState(byte state) {
+        if (state == 1)
+            return BatteryState.BATTERY_CHARGING;
+        return BatteryState.BATTERY_NORMAL;
+    }
+
+    @Override
+    protected void processResponse() throws ResponseParseException {
+        LOG.debug("handle Battery Level");
+
+        if (!(receivedPacket instanceof DeviceConfig.BatteryLevel.Response))
+            throw new ResponseTypeMismatchException(receivedPacket, DeviceConfig.BatteryLevel.Response.class);
+
+        DeviceConfig.BatteryLevel.Response response = (DeviceConfig.BatteryLevel.Response) receivedPacket;
+
+        if (response.multi_level == null) {
+            byte batteryLevel = response.level;
+            getDevice().setBatteryLevel(batteryLevel);
+
+            GBDeviceEventBatteryInfo batteryInfo = new GBDeviceEventBatteryInfo();
+            batteryInfo.state = BatteryState.BATTERY_NORMAL;
+            batteryInfo.level = (int) batteryLevel & 0xff;
+            this.supportProvider.evaluateGBDeviceEvent(batteryInfo);
+        } else {
+            // Handle multiple batteries
+            for (int i = 0; i < response.multi_level.length; i++) {
+                int level = (int) response.multi_level[i] & 0xff;
+                getDevice().setBatteryLevel(level, i);
+
+                GBDeviceEventBatteryInfo batteryInfo = new GBDeviceEventBatteryInfo();
+                batteryInfo.batteryIndex = i;
+                batteryInfo.state = response.status != null && response.status.length > i ?
+                        byteToBatteryState(response.status[i]) :
+                        BatteryState.BATTERY_NORMAL;
+                batteryInfo.level = level;
+                this.supportProvider.evaluateGBDeviceEvent(batteryInfo);
+            }
+        }
+
+        if (GBApplication.getDevicePrefs(getDevice()).getBatteryPollingEnabled()) {
+            if (!this.supportProvider.startBatteryRunnerDelayed()) {
+                GB.toast(getContext(), R.string.battery_polling_failed_start, Toast.LENGTH_SHORT, GB.ERROR);
+                LOG.error("Failed to start the battery polling");
+            }
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetBondParamsRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetBondParamsRequest.java
new file mode 100644
index 0000000..bcd6ff7
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetBondParamsRequest.java
@@ -0,0 +1,64 @@
+/*  Copyright (C) 2024 Damien Gaignon, Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.DeviceConfig;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class GetBondParamsRequest extends Request {
+    private static final Logger LOG = LoggerFactory.getLogger(GetBondParamsRequest.class);
+
+    public GetBondParamsRequest(HuaweiSupportProvider support) {
+        super(support);
+        this.serviceId = DeviceConfig.id;
+        this.commandId = DeviceConfig.BondParams.id;
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        try {
+            return new DeviceConfig.BondParams.Request(
+                    paramsProvider,
+                    supportProvider.getSerial(),
+                    supportProvider.getMacAddress()
+            ).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+
+    @Override
+    protected void processResponse() throws ResponseParseException {
+        LOG.debug("handle BondParams");
+
+        if (!(receivedPacket instanceof DeviceConfig.BondParams.Response))
+            throw new ResponseTypeMismatchException(receivedPacket, DeviceConfig.BondParams.Response.class);
+
+        paramsProvider.setEncryptionCounter(((DeviceConfig.BondParams.Response) receivedPacket).encryptionCounter);
+        if (paramsProvider.getAuthMode() != 0x02) {
+            if (((DeviceConfig.BondParams.Response) receivedPacket).status == 1) {
+                stopChain(this);
+            }
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetBondRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetBondRequest.java
new file mode 100644
index 0000000..fcfc699
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetBondRequest.java
@@ -0,0 +1,77 @@
+/*  Copyright (C) 2024 Damien Gaignon, Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.util.List;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.NoSuchPaddingException;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiCrypto;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.DeviceConfig;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class GetBondRequest extends Request {
+    private static final Logger LOG = LoggerFactory.getLogger(GetBondRequest.class);
+
+    protected String macAddress;
+
+    public GetBondRequest(HuaweiSupportProvider support) {
+        super(support);
+        this.serviceId = DeviceConfig.id;
+        this.commandId = DeviceConfig.Bond.id;
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        try {
+            byte[] iv = paramsProvider.getIv();
+            huaweiCrypto = new HuaweiCrypto(paramsProvider.getAuthVersion(), paramsProvider.getAuthAlgo(), paramsProvider.getDeviceSupportType(), paramsProvider.getAuthMode());
+            byte[] encryptionKey;
+            if (paramsProvider.getAuthMode() == 0x02) { //HiChainLite
+                encryptionKey = paramsProvider.getFirstKey();
+            } else {
+                encryptionKey = huaweiCrypto.createSecretKey(supportProvider.getDeviceMac());
+            }
+            byte[] key = huaweiCrypto.encryptBondingKey(paramsProvider.getEncryptMethod(), paramsProvider.getSecretKey(), encryptionKey, iv);
+            LOG.debug("key: " + GB.hexdump(key));
+            return new DeviceConfig.Bond.Request(
+                    paramsProvider,
+                    supportProvider.getSerial(),
+                    key,
+                    iv
+            ).serialize();
+        } catch (HuaweiPacket.CryptoException | NoSuchAlgorithmException | NoSuchPaddingException | InvalidAlgorithmParameterException | InvalidKeyException | IllegalBlockSizeException |
+                 BadPaddingException e) {
+            throw new RequestCreationException(e.toString());
+        }
+    }
+
+    @Override
+    protected void processResponse() {
+        LOG.debug("handle Bond");
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetConnectStatusRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetConnectStatusRequest.java
new file mode 100644
index 0000000..efa40cf
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetConnectStatusRequest.java
@@ -0,0 +1,55 @@
+/*  Copyright (C) 2024 Damien Gaignon
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket.CryptoException;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.DeviceConfig;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class GetConnectStatusRequest extends Request {
+    private static final Logger LOG = LoggerFactory.getLogger(GetConnectStatusRequest.class);
+
+    public GetConnectStatusRequest(HuaweiSupportProvider support) {
+        super(support);
+        this.serviceId = DeviceConfig.id;
+        this.commandId = DeviceConfig.ConnectStatusRequest.id;
+    }
+
+    @Override
+    protected boolean requestSupported() {
+        return supportProvider.getHuaweiCoordinator().supportsConnectStatus();
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        try {
+            return new DeviceConfig.ConnectStatusRequest(paramsProvider).serialize();
+        } catch (CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+
+    @Override
+    protected void processResponse() throws ResponseParseException {
+        LOG.debug("handle Connect Status");
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetContactsCount.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetContactsCount.java
new file mode 100644
index 0000000..e45d118
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetContactsCount.java
@@ -0,0 +1,45 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Contacts;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class GetContactsCount extends Request {
+    private static final Logger LOG = LoggerFactory.getLogger(GetContactsCount.class);
+
+    public GetContactsCount(HuaweiSupportProvider support) {
+        super(support);
+        this.serviceId = Contacts.id;
+        this.commandId = Contacts.ContactsCount.id;
+    }
+
+    @Override
+    protected boolean requestSupported() {
+        return supportProvider.getHuaweiCoordinator().supportsContacts();
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        try {
+            return new Contacts.ContactsCount.Request(paramsProvider).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+
+    @Override
+    protected void processResponse() throws ResponseParseException {
+        LOG.debug("handle contacts count");
+
+        if (!(receivedPacket instanceof Contacts.ContactsCount.Response))
+            throw new ResponseTypeMismatchException(receivedPacket, Contacts.ContactsCount.Response.class);
+
+        int count = ((Contacts.ContactsCount.Response) receivedPacket).maxCount;
+        this.supportProvider.getHuaweiCoordinator().saveMaxContactsCount(count);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetDeviceStatusRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetDeviceStatusRequest.java
new file mode 100644
index 0000000..d5f9cc4
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetDeviceStatusRequest.java
@@ -0,0 +1,59 @@
+/*  Copyright (C) 2024 Damien Gaignon, Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.DeviceConfig;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class GetDeviceStatusRequest extends Request {
+    private static final Logger LOG = LoggerFactory.getLogger(GetDeviceStatusRequest.class);
+
+    public byte status;
+    private boolean askStatus;
+
+    public GetDeviceStatusRequest(HuaweiSupportProvider support, boolean askStatus) {
+        super(support);
+        this.serviceId = DeviceConfig.id;
+        this.commandId = DeviceConfig.DeviceStatus.id;
+        this.askStatus = askStatus;
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        try {
+            return new DeviceConfig.DeviceStatus.Request(paramsProvider, askStatus).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+
+    @Override
+    protected void processResponse() throws ResponseParseException {
+        LOG.debug("handle Device Status");
+
+        if (!(receivedPacket instanceof DeviceConfig.DeviceStatus.Response))
+            throw new ResponseTypeMismatchException(receivedPacket, DeviceConfig.DeviceStatus.Response.class);
+
+        this.status = ((DeviceConfig.DeviceStatus.Response) receivedPacket).status;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetDndLiftWristTypeRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetDndLiftWristTypeRequest.java
new file mode 100644
index 0000000..266b030
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetDndLiftWristTypeRequest.java
@@ -0,0 +1,68 @@
+/*  Copyright (C) 2024 Damien Gaignon, Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import android.content.SharedPreferences;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiConstants;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.DeviceConfig;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class GetDndLiftWristTypeRequest extends Request {
+    private static final Logger LOG = LoggerFactory.getLogger(GetDndLiftWristTypeRequest.class);
+
+    public GetDndLiftWristTypeRequest(HuaweiSupportProvider support) {
+        super(support);
+        this.serviceId = DeviceConfig.id;
+        this.commandId = DeviceConfig.DndLiftWristType.id;
+    }
+
+    @Override
+    protected boolean requestSupported() {
+        return supportProvider.getHuaweiCoordinator().supportsQueryDndLiftWristDisturbType();
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        try {
+            return new DeviceConfig.DndLiftWristType.Request(
+                    paramsProvider
+            ).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+
+    @Override
+    protected void processResponse() throws ResponseParseException {
+        LOG.debug("handle DND Allow Content");
+        if (!(receivedPacket instanceof DeviceConfig.DndLiftWristType.Response))
+            throw new ResponseTypeMismatchException(receivedPacket, DeviceConfig.DndLiftWristType.Response.class);
+        SharedPreferences sharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(supportProvider.getDeviceMac());
+        SharedPreferences.Editor editor = sharedPrefs.edit();
+        editor.putInt(HuaweiConstants.PREF_HUAWEI_DND_LIFT_WRIST_TYPE,
+                    ((DeviceConfig.DndLiftWristType.Response) receivedPacket).dndLiftWristType);
+        editor.apply();
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetEventAlarmList.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetEventAlarmList.java
new file mode 100644
index 0000000..a7fa49b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetEventAlarmList.java
@@ -0,0 +1,109 @@
+/*  Copyright (C) 2024 Damien Gaignon, Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Alarms;
+import nodomain.freeyourgadget.gadgetbridge.entities.Alarm;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class GetEventAlarmList extends Request {
+
+    public GetEventAlarmList(HuaweiSupportProvider support) {
+        super(support);
+
+        this.serviceId = Alarms.id;
+        this.commandId = Alarms.EventAlarmsList.id;
+    }
+
+    @Override
+    protected boolean requestSupported() {
+        return supportProvider.getHuaweiCoordinator().supportsChangingAlarm();
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        try {
+            return new Alarms.EventAlarmsList.Request(supportProvider.getParamsProvider()).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+
+    @Override
+    protected void processResponse() throws ResponseParseException {
+        if (!(receivedPacket instanceof Alarms.EventAlarmsList.Response))
+            throw new ResponseTypeMismatchException(receivedPacket, Alarms.EventAlarmsList.Response.class);
+
+        List<Alarm> alarms = new ArrayList<>();
+
+        // Correct for position of smart alarm
+        // Note that the band uses 1 as the first index for event alarms
+        int positionOffset;
+        if (supportProvider.getCoordinator().getHuaweiCoordinator().supportsSmartAlarm(supportProvider.getDevice()))
+            positionOffset = 0;
+        else
+            positionOffset = -1;
+
+        byte usedBitmap = 0;
+
+        for (Alarms.EventAlarm eventAlarm : ((Alarms.EventAlarmsList.Response) receivedPacket).eventAlarms) {
+            alarms.add(new Alarm(
+                    0,
+                    0,
+                    eventAlarm.index + positionOffset,
+                    eventAlarm.status,
+                    false,
+                    null,
+                    false,
+                    eventAlarm.repeat,
+                    eventAlarm.startHour,
+                    eventAlarm.startMinute,
+                    false,
+                    eventAlarm.name,
+                    ""
+            ));
+            usedBitmap |= 1 << eventAlarm.index;
+        }
+
+        // Add all unused alarms as unused
+        for (int i = 1; i < 6; i++) {
+            if ((usedBitmap & (1 << i)) == 0) {
+                alarms.add(new Alarm(
+                        0,
+                        0,
+                        i + positionOffset,
+                        false,
+                        false,
+                        null,
+                        false,
+                        0,
+                        0,
+                        0,
+                        true,
+                        "",
+                        ""
+                ));
+            }
+        }
+
+        supportProvider.saveAlarms(alarms.toArray(new Alarm[]{}));
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetExpandCapabilityRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetExpandCapabilityRequest.java
new file mode 100644
index 0000000..993646e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetExpandCapabilityRequest.java
@@ -0,0 +1,67 @@
+/*  Copyright (C) 2024 Damien Gaignon
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+
+/* In order to be compatible with all devices, request send all possible commands
+to all possible services. This implies long packet which is not handled on the device.
+Thus, this request could be sliced in 3 packets. But this command does not support slicing.
+Thus, one need to send multiple requests and concat the response.
+Packets should be 240 bytes max */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.DeviceConfig;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class GetExpandCapabilityRequest extends Request {
+    private static final Logger LOG = LoggerFactory.getLogger(GetExpandCapabilityRequest.class);
+
+    public GetExpandCapabilityRequest(HuaweiSupportProvider support) {
+        super(support);
+        this.serviceId = DeviceConfig.id;
+        this.commandId = DeviceConfig.ExpandCapability.id;
+    }
+
+    @Override
+    protected boolean requestSupported() {
+        return supportProvider.getHuaweiCoordinator().supportsExpandCapability();
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        DeviceConfig.ExpandCapability.Request expandRequest = new DeviceConfig.ExpandCapability.Request(paramsProvider);
+        try {
+            return expandRequest.serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+
+    @Override
+    protected void processResponse() throws ResponseParseException {
+        LOG.debug("handle Expand Capability");
+
+        if (!(receivedPacket instanceof DeviceConfig.ExpandCapability.Response))
+            throw new ResponseTypeMismatchException(receivedPacket, DeviceConfig.ExpandCapability.Response.class);
+
+        supportProvider.getHuaweiCoordinator().saveExpandCapabilities(((DeviceConfig.ExpandCapability.Response) receivedPacket).expandCapabilities);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetExtendedMusicInfoParams.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetExtendedMusicInfoParams.java
new file mode 100644
index 0000000..44f8dda
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetExtendedMusicInfoParams.java
@@ -0,0 +1,44 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.MusicControl;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class GetExtendedMusicInfoParams extends Request {
+    private final Logger LOG = LoggerFactory.getLogger(GetExtendedMusicInfoParams.class);
+    public GetExtendedMusicInfoParams(HuaweiSupportProvider support) {
+        super(support);
+        this.serviceId = MusicControl.id;
+        this.commandId = MusicControl.ExtendedMusicInfoParams.id;
+
+    }
+
+    @Override
+    protected boolean requestSupported() {
+        return supportProvider.getHuaweiCoordinator().supportsMusicUploading();
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws Request.RequestCreationException {
+        try {
+            return new MusicControl.ExtendedMusicInfoParams.Request(paramsProvider).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new Request.RequestCreationException(e);
+        }
+    }
+
+    @Override
+    protected void processResponse() throws Request.ResponseParseException {
+        LOG.info("MusicControl.ExtendedMusicInfoParams processResponse");
+        if (!(receivedPacket instanceof MusicControl.ExtendedMusicInfoParams.Response))
+            throw new Request.ResponseTypeMismatchException(receivedPacket, MusicControl.ExtendedMusicInfoParams.Response.class);
+
+        MusicControl.ExtendedMusicInfoParams.Response resp = (MusicControl.ExtendedMusicInfoParams.Response)(receivedPacket);
+        supportProvider.getHuaweiCoordinator().setExtendedMusicInfoParams(resp.params);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetFileBlockRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetFileBlockRequest.java
new file mode 100644
index 0000000..1a1beb3
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetFileBlockRequest.java
@@ -0,0 +1,69 @@
+/*  Copyright (C) 2024 Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FileDownloadService0A;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FileDownloadService2C;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiFileDownloadManager;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class GetFileBlockRequest extends Request {
+    private final HuaweiFileDownloadManager.FileRequest request;
+
+    public GetFileBlockRequest(HuaweiSupportProvider support, HuaweiFileDownloadManager.FileRequest request) {
+        super(support);
+        if (request.isNewSync()) {
+            this.serviceId = FileDownloadService2C.id;
+            this.commandId = FileDownloadService2C.RequestBlock.id;
+        } else {
+            this.serviceId = FileDownloadService0A.id;
+            this.commandId = FileDownloadService0A.RequestBlock.id;
+        }
+        this.request = request;
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws Request.RequestCreationException {
+        try {
+            if (this.request.isNewSync())
+                return new FileDownloadService2C.RequestBlock(
+                        paramsProvider,
+                        this.request.getFileId(),
+                        this.request.getCurrentOffset(),
+                        this.request.getCurrentBlockSize(),
+                        this.request.isNoEncrypt()
+                ).serialize();
+            else
+                return new FileDownloadService0A.RequestBlock.Request(
+                        paramsProvider,
+                        this.request.getFilename(),
+                        this.request.getCurrentOffset(),
+                        this.request.getCurrentBlockSize()
+                ).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new Request.RequestCreationException(e);
+        }
+    }
+
+    @Override
+    protected void processResponse() throws Request.ResponseParseException {
+        // TODO: handle data?
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetFileDownloadCompleteRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetFileDownloadCompleteRequest.java
new file mode 100644
index 0000000..a016886
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetFileDownloadCompleteRequest.java
@@ -0,0 +1,54 @@
+/*  Copyright (C) 2024 Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FileDownloadService0A;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FileDownloadService2C;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiFileDownloadManager;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class GetFileDownloadCompleteRequest extends Request {
+
+    private final HuaweiFileDownloadManager.FileRequest request;
+
+    public GetFileDownloadCompleteRequest(HuaweiSupportProvider support, HuaweiFileDownloadManager.FileRequest request) {
+        super(support);
+        if (request.isNewSync()) {
+            this.serviceId = FileDownloadService2C.id;
+            this.commandId = FileDownloadService2C.FileDownloadCompleteRequest.id;
+        } else {
+            this.serviceId = FileDownloadService0A.id;
+            this.commandId = FileDownloadService0A.FileDownloadCompleteRequest.id;
+        }
+        this.request = request;
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        try {
+            if (request.isNewSync())
+                return new FileDownloadService2C.FileDownloadCompleteRequest(paramsProvider, this.request.getFileId()).serialize();
+            else
+                return new FileDownloadService0A.FileDownloadCompleteRequest(paramsProvider).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetFileDownloadInitRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetFileDownloadInitRequest.java
new file mode 100644
index 0000000..2e89510
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetFileDownloadInitRequest.java
@@ -0,0 +1,122 @@
+/*  Copyright (C) 2024 Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FileDownloadService0A;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FileDownloadService2C;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiFileDownloadManager;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class GetFileDownloadInitRequest extends Request {
+
+    final HuaweiFileDownloadManager.FileRequest request;
+
+    public boolean newSync;
+
+    // Old sync
+    public String[] filenames;
+
+    // New sync
+    public String filename;
+    public HuaweiFileDownloadManager.FileType fileType;
+    public byte fileId;
+    public int fileSize;
+
+    public GetFileDownloadInitRequest(HuaweiSupportProvider support, HuaweiFileDownloadManager.FileRequest request) {
+        super(support);
+        if (request.isNewSync()) {
+            this.serviceId = FileDownloadService2C.id;
+            this.commandId = FileDownloadService2C.FileDownloadInit.id;
+        } else {
+            this.serviceId = FileDownloadService0A.id;
+            this.commandId = FileDownloadService0A.FileDownloadInit.id;
+        }
+        this.request = request;
+    }
+
+    private FileDownloadService2C.FileType convertFileTypeTo2C(HuaweiFileDownloadManager.FileType type) {
+        switch (type) {
+            case SLEEP_STATE:
+                return FileDownloadService2C.FileType.SLEEP_STATE;
+            case SLEEP_DATA:
+                return FileDownloadService2C.FileType.SLEEP_DATA;
+            case GPS:
+                return FileDownloadService2C.FileType.GPS;
+            default:
+                return FileDownloadService2C.FileType.UNKNOWN;
+        }
+    }
+
+    private HuaweiFileDownloadManager.FileType convertFileTypeFrom2C(FileDownloadService2C.FileType type) {
+        switch (type) {
+            case SLEEP_STATE:
+                return HuaweiFileDownloadManager.FileType.SLEEP_STATE;
+            case SLEEP_DATA:
+                return HuaweiFileDownloadManager.FileType.SLEEP_DATA;
+            case GPS:
+                return HuaweiFileDownloadManager.FileType.GPS;
+            default:
+                return HuaweiFileDownloadManager.FileType.UNKNOWN;
+        }
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        try {
+            if (this.request.isNewSync()) {
+                FileDownloadService2C.FileType type = convertFileTypeTo2C(request.getFileType());
+                if (type == FileDownloadService2C.FileType.UNKNOWN)
+                    throw new RequestCreationException("Cannot convert type " + request.getFileType());
+                return new FileDownloadService2C.FileDownloadInit.Request(paramsProvider, request.getFilename(), type, request.getStartTime(), request.getEndTime()).serialize();
+            } else {
+                if (this.request.getFileType() == HuaweiFileDownloadManager.FileType.DEBUG)
+                    return new FileDownloadService0A.FileDownloadInit.DebugFilesRequest(paramsProvider).serialize();
+                else if (this.request.getFileType() == HuaweiFileDownloadManager.FileType.SLEEP_STATE)
+                    return new FileDownloadService0A.FileDownloadInit.SleepFilesRequest(paramsProvider, request.getStartTime(), request.getEndTime()).serialize();
+                else if (this.request.getFileType() == HuaweiFileDownloadManager.FileType.GPS)
+                    return new FileDownloadService0A.FileDownloadInit.GpsFileRequest(paramsProvider, request.getWorkoutId()).serialize();
+                else
+                    throw new RequestCreationException("Unknown file type");
+            }
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+
+    @Override
+    protected void processResponse() throws ResponseParseException {
+        // In case of multiple downloads, the response here does not need to match the original request
+        // So we cannot use the request at all!
+
+        if (this.receivedPacket instanceof FileDownloadService0A.FileDownloadInit.Response) {
+            this.newSync = false;
+            this.filenames = ((FileDownloadService0A.FileDownloadInit.Response) this.receivedPacket).fileNames;
+        } else if (this.receivedPacket instanceof FileDownloadService2C.FileDownloadInit.Response) {
+            this.newSync = true;
+            FileDownloadService2C.FileDownloadInit.Response packet = (FileDownloadService2C.FileDownloadInit.Response) this.receivedPacket;
+            this.filename = packet.fileName;
+            this.fileType = convertFileTypeFrom2C(packet.fileType);
+            this.fileId = packet.fileId;
+            this.fileSize = packet.fileSize;
+        } else {
+            throw new ResponseTypeMismatchException(receivedPacket, FileDownloadService2C.FileDownloadInit.Response.class);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetFileInfoRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetFileInfoRequest.java
new file mode 100644
index 0000000..b55d402
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetFileInfoRequest.java
@@ -0,0 +1,81 @@
+/*  Copyright (C) 2024 Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FileDownloadService0A;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FileDownloadService2C;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiFileDownloadManager;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class GetFileInfoRequest extends Request {
+    private final HuaweiFileDownloadManager.FileRequest request;
+
+    public boolean newSync;
+
+    // Old sync
+    public int fileLength;
+
+    // New sync
+    public byte fileId;
+    public byte timeout;
+    public int maxBlockSize;
+    public boolean noEncrypt;
+
+    public GetFileInfoRequest(HuaweiSupportProvider support, HuaweiFileDownloadManager.FileRequest request) {
+        super(support);
+        if (request.isNewSync()) {
+            this.serviceId = FileDownloadService2C.id;
+            this.commandId = FileDownloadService2C.FileInfo.id;
+        } else {
+            this.serviceId = FileDownloadService0A.id;
+            this.commandId = FileDownloadService0A.FileInfo.id;
+        }
+        this.request = request;
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        try {
+            if (this.request.isNewSync())
+                return new FileDownloadService2C.FileInfo.Request(paramsProvider, this.request.getFileId()).serialize();
+            else
+                return new FileDownloadService0A.FileInfo.Request(paramsProvider, this.request.getFilename()).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+
+    @Override
+    protected void processResponse() throws ResponseParseException {
+        if (this.receivedPacket instanceof FileDownloadService0A.FileInfo.Response) {
+            this.newSync = false;
+            this.fileLength = ((FileDownloadService0A.FileInfo.Response) this.receivedPacket).fileLength;
+        } else if (this.receivedPacket instanceof FileDownloadService2C.FileInfo.Response) {
+            this.newSync = true;
+            FileDownloadService2C.FileInfo.Response packet = (FileDownloadService2C.FileInfo.Response) this.receivedPacket;
+            this.fileId = packet.fileId;
+            this.timeout = packet.timeout;
+            this.maxBlockSize = packet.maxBlockSize;
+            this.noEncrypt = packet.noEncrypt;
+        } else {
+            throw new ResponseTypeMismatchException(this.receivedPacket, FileDownloadService2C.FileInfo.Response.class);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetFileParametersRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetFileParametersRequest.java
new file mode 100644
index 0000000..0251c08
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetFileParametersRequest.java
@@ -0,0 +1,63 @@
+/*  Copyright (C) 2024 Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FileDownloadService0A;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class GetFileParametersRequest extends Request {
+
+    private final boolean truSleep;
+
+    private int maxBlockSize;
+    private int timeout;
+
+    public GetFileParametersRequest(HuaweiSupportProvider support, boolean truSleep) {
+        super(support);
+        this.serviceId = FileDownloadService0A.id;
+        this.commandId = FileDownloadService0A.FileParameters.id;
+        this.truSleep = truSleep;
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        try {
+            return new FileDownloadService0A.FileParameters.Request(paramsProvider, truSleep).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+
+    @Override
+    protected void processResponse() throws ResponseParseException {
+        if (!(this.receivedPacket instanceof FileDownloadService0A.FileParameters.Response))
+            throw new ResponseTypeMismatchException(this.receivedPacket, FileDownloadService0A.FileParameters.Response.class);
+        this.maxBlockSize = ((FileDownloadService0A.FileParameters.Response) this.receivedPacket).maxBlockSize;
+        this.timeout = ((FileDownloadService0A.FileParameters.Response) this.receivedPacket).timeout;
+    }
+
+    public int getMaxBlockSize() {
+        return maxBlockSize;
+    }
+
+    public int getTimeout() {
+        return timeout;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetFitnessTotalsRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetFitnessTotalsRequest.java
new file mode 100644
index 0000000..18fa0f3
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetFitnessTotalsRequest.java
@@ -0,0 +1,56 @@
+/*  Copyright (C) 2024 Damien Gaignon, Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FitnessData;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class GetFitnessTotalsRequest extends Request {
+
+    public GetFitnessTotalsRequest(HuaweiSupportProvider support) {
+        super(support);
+
+        this.serviceId = FitnessData.id;
+        this.commandId = FitnessData.FitnessTotals.id;
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        try {
+            return new FitnessData.FitnessTotals.Request(
+                    paramsProvider
+            ).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+
+    @Override
+    protected void processResponse() throws ResponseParseException {
+        if (!(receivedPacket instanceof FitnessData.FitnessTotals.Response))
+            throw new ResponseTypeMismatchException(receivedPacket, FitnessData.FitnessTotals.Response.class);
+
+        int totalSteps = ((FitnessData.FitnessTotals.Response) receivedPacket).totalSteps;
+        int totalCalories = ((FitnessData.FitnessTotals.Response) receivedPacket).totalCalories;
+        int totalDistance = ((FitnessData.FitnessTotals.Response) receivedPacket).totalDistance;
+
+        supportProvider.addTotalFitnessData(totalSteps, totalCalories, totalDistance);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetGpsParameterRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetGpsParameterRequest.java
new file mode 100644
index 0000000..2f02215
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetGpsParameterRequest.java
@@ -0,0 +1,48 @@
+/*  Copyright (C) 2024 Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.GpsAndTime;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class GetGpsParameterRequest extends Request {
+
+    public GetGpsParameterRequest(HuaweiSupportProvider support) {
+        super(support);
+        this.serviceId = GpsAndTime.id;
+        this.commandId = GpsAndTime.GpsParameters.id;
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        try {
+            return new GpsAndTime.GpsParameters.Request(this.paramsProvider).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+
+    @Override
+    protected void processResponse() throws ResponseParseException {
+        if (!(receivedPacket instanceof GpsAndTime.GpsParameters.Response))
+            throw new ResponseTypeMismatchException(receivedPacket, GpsAndTime.GpsParameters.Response.class);
+        supportProvider.setGpsParametersResponse((GpsAndTime.GpsParameters.Response) receivedPacket);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetHiChainRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetHiChainRequest.java
new file mode 100644
index 0000000..fd9ff20
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetHiChainRequest.java
@@ -0,0 +1,256 @@
+/*  Copyright (C) 2024 Damien Gaignon, Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import org.json.JSONObject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Random;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiConstants;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.DeviceConfig;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.DeviceConfig.HiChain;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+import nodomain.freeyourgadget.gadgetbridge.util.CryptoUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
+
+public class GetHiChainRequest extends Request {
+    private static final Logger LOG = LoggerFactory.getLogger(GetHiChainRequest.class);
+    // Attributs used along all operation
+    private HiChain.Request req = null;
+    private byte operationCode = 0x02;
+    private byte step;
+    private byte[] authIdSelf = null;
+    private byte[] authIdPeer = null;
+    private byte[] randSelf = null;
+    private byte[] randPeer = null;
+    private long requestId = 0x00;
+    private JSONObject json = null;
+    private byte[] sessionKey = null;
+    // Attributs used once
+    private byte[] seed = null;
+    private byte[] challenge = null;
+    private byte[] psk = null;
+
+    // The user needs to confirm the pairing request on the device itself.
+    private final int firstAuthenticateTimeout = 30 * 1000;
+    private final int authenticateTimeout = 5000;
+
+    public GetHiChainRequest(HuaweiSupportProvider support, boolean firstConnection) {
+        super(support);
+        this.serviceId = DeviceConfig.id;
+        this.commandId = HiChain.id;
+        if (firstConnection) {
+            setupTimeoutUntilNext(firstAuthenticateTimeout);
+            operationCode = 0x01;
+        } else {
+            setupTimeoutUntilNext(authenticateTimeout);
+        }
+        this.step = 0x01;
+    }
+
+    public GetHiChainRequest(Request prevReq) {
+        super(prevReq.supportProvider);
+        this.serviceId = DeviceConfig.id;
+        this.commandId = HiChain.id;
+        GetHiChainRequest hcReq = (GetHiChainRequest)prevReq;
+        this.req = hcReq.req;
+        this.requestId = (Long)hcReq.requestId;
+        this.operationCode = (byte)hcReq.operationCode;
+        this.step = (byte)hcReq.step;
+        this.authIdSelf = hcReq.authIdSelf;
+        this.authIdPeer = hcReq.authIdPeer;
+        this.randSelf = hcReq.randSelf;
+        this.randPeer = hcReq.randPeer;
+        this.psk = hcReq.psk;
+        this.json = hcReq.json;
+        this.sessionKey = hcReq.sessionKey;
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        if (requestId == 0x00) {
+            requestId = System.currentTimeMillis();
+        }
+
+        LOG.debug("Request operationCode: " + operationCode + " - step: " + step);
+        if (req == null) req = new HiChain.Request(
+                operationCode,
+                requestId,
+                supportProvider.getAndroidId(),
+                HuaweiConstants.GROUP_ID
+        );
+        HuaweiPacket packet = null;
+        int messageId = step;
+        try {
+            if (step == 0x01) {
+                seed = new byte[32];
+                new Random().nextBytes(seed);
+                randSelf = new byte[16];
+                new Random().nextBytes(randSelf);
+                HiChain.Request.StepOne stepOne = req.new StepOne(paramsProvider, messageId, randSelf, seed );
+                packet = stepOne;
+            } else if (step == 0x02) {
+                byte[] message = ByteBuffer
+                        .allocate(randPeer.length + randSelf.length + authIdSelf.length + authIdPeer.length)
+                        .put(randSelf)
+                        .put(randPeer)
+                        .put(authIdPeer)
+                        .put(authIdSelf)
+                        .array();
+                byte[] selfToken = CryptoUtils.calcHmacSha256(psk, message);
+                HiChain.Request.StepTwo stepTwo = req.new StepTwo(paramsProvider, messageId, selfToken);
+                packet = stepTwo;
+            } else if (step == 0x03) {
+                byte[] salt = ByteBuffer
+                    .allocate( randSelf.length + randPeer.length)
+                    .put(randSelf)
+                    .put(randPeer)
+                    .array();
+                byte[] info = "hichain_iso_session_key".getBytes(StandardCharsets.UTF_8);
+                sessionKey = CryptoUtils.hkdfSha256(psk, salt, info, 32);
+                LOG.debug("sessionKey: " + GB.hexdump(sessionKey));
+                if (operationCode == 0x01) {
+                    byte[] nonce = new byte[12];
+                    new Random().nextBytes(nonce);
+                    challenge = new byte[16];
+                    new Random().nextBytes(challenge);
+                    byte[] aad = "hichain_iso_exchange".getBytes(StandardCharsets.UTF_8);
+                    byte[] encData = CryptoUtils.encryptAES_GCM_NoPad(challenge, sessionKey, nonce, aad); //aesGCMNoPadding encrypt(sessionKey as key, challenge to encrypt, nonce as iv)
+                    HiChain.Request.StepThree stepThree = req.new StepThree(paramsProvider, messageId, nonce, encData);
+                    packet = stepThree;
+                } else {
+                    step += 0x01;
+                }
+            }
+            if (step == 0x04) {
+                LOG.debug("Step " + step);
+                byte[] nonce = new byte[12];
+                new Random().nextBytes(nonce);
+                byte[] input = new byte[]{0x00, 0x00, 0x00, 0x00};
+                byte[] aad = "hichain_iso_result".getBytes(StandardCharsets.UTF_8);
+                byte[] encResult = CryptoUtils.encryptAES_GCM_NoPad(input, sessionKey, nonce, aad);
+                HiChain.Request.StepFour stepFour = req.new StepFour(paramsProvider, messageId, nonce, encResult);
+                packet = stepFour;
+
+            }
+            LOG.debug("JSONObject on creation:" + (new JSONObject(packet.getTlv().getString(1))).getJSONObject("payload").toString());
+            return packet.serialize();
+        } catch (Exception e) {
+            // TODO: Make exception explicit
+            throw new RequestCreationException("HiChain exception", e);
+        }
+        //return null;
+    }
+
+    @Override
+    protected void processResponse() throws ResponseParseException {
+        if (!(receivedPacket instanceof HiChain.Response))
+            throw new ResponseTypeMismatchException(receivedPacket, HiChain.Response.class);
+
+        HiChain.Response response = (HiChain.Response)receivedPacket;
+        if (response.errorCode != 0) {
+            throw new ResponseParseException("Got errorCode " +  response.errorCode);
+        }
+        step = response.step;
+
+        LOG.debug("Response operationCode: " + operationCode + " - step: " + step);
+        try {
+            if (step == 0x04) {
+                if (operationCode == 0x01) {
+                    LOG.debug("Finished auth operation, go to bind");
+                    GetHiChainRequest nextRequest = new GetHiChainRequest(supportProvider, false);
+                    nextRequest.setFinalizeReq(this.finalizeReq);
+                    this.nextRequest(nextRequest);
+                } else {
+                    LOG.debug("Finished bind operation");
+                    byte[] salt = ByteBuffer
+                        .allocate( randSelf.length + randPeer.length)
+                        .put(randSelf)
+                        .put(randPeer)
+                        .array();
+                    byte[] info = "hichain_return_key".getBytes(StandardCharsets.UTF_8);
+                    byte[] key = CryptoUtils.hkdfSha256(sessionKey, salt, info, 32);
+                    LOG.debug("Final sessionKey:" + GB.hexdump(key));
+                    paramsProvider.setSecretKey(key);
+                }
+            } else {
+                if (step == 0x01) {
+                    byte[] key = null;
+                    authIdSelf = supportProvider.getAndroidId();
+                    authIdPeer = response.step1Data.peerAuthId;
+                    randPeer = response.step1Data.isoSalt;
+                    byte[] peerToken = response.step1Data.token;
+                    // GeneratePsk
+                    if (operationCode == 0x01) {
+                        String pinCodeHexStr = StringUtils.bytesToHex(paramsProvider.getPinCode());
+                        byte[] pinCode = pinCodeHexStr.getBytes(StandardCharsets.UTF_8);
+                        key = CryptoUtils.digest(pinCode);
+                    } else {
+                        key = supportProvider.getSecretKey();
+                    }
+                    psk = CryptoUtils.calcHmacSha256(key, seed);
+                    byte[] message = ByteBuffer
+                        .allocate(randPeer.length + randSelf.length + authIdSelf.length + authIdPeer.length)
+                        .put(randPeer)
+                        .put(randSelf)
+                        .put(authIdSelf)
+                        .put(authIdPeer)
+                        .array();
+                    byte[] tokenCheck = CryptoUtils.calcHmacSha256(psk, message);
+                    if (!Arrays.equals(peerToken, tokenCheck)) {
+                        LOG.debug("tokenCheck: " + GB.hexdump(tokenCheck) + " is different than " + GB.hexdump(peerToken));
+                        throw new RequestCreationException("tokenCheck: " + GB.hexdump(tokenCheck) + " is different than " + GB.hexdump(peerToken));
+                    } else {
+                        LOG.debug("Token check passes");
+                    }
+                } else if (step == 0x02) {
+                    byte[] returnCodeMac = response.step2Data.returnCodeMac;
+                    byte[] returnCodeMacCheck = CryptoUtils.calcHmacSha256(psk, new byte[]{0x00, 0x00, 0x00, 0x00});
+                    if (!Arrays.equals(returnCodeMacCheck, returnCodeMac)) {
+                        LOG.debug("returnCodeMacCheck: " + GB.hexdump(returnCodeMacCheck) + " is different than " + GB.hexdump(returnCodeMac));
+                        throw new RequestCreationException("returnCodeMacCheck: " + GB.hexdump(returnCodeMacCheck) + " is different than " + GB.hexdump(returnCodeMac));
+                    } else {
+                        LOG.debug("returnCodeMac check passes");
+                    }
+                } else if (step == 0x03) {
+                    if (operationCode == 0x01) {
+                        byte[] nonce = response.step3Data.nonce;
+                        byte[] encAuthToken = response.step3Data.encAuthToken;
+                        byte[] authToken = CryptoUtils.decryptAES_GCM_NoPad(encAuthToken, sessionKey, nonce, challenge);
+                        supportProvider.setSecretKey(authToken);
+                        LOG.debug("Set secret key");
+                    }
+                }
+                this.step += 0x01;
+                GetHiChainRequest nextRequest = new GetHiChainRequest(this);
+                nextRequest.setFinalizeReq(this.finalizeReq);
+                this.nextRequest(nextRequest);
+            }
+        } catch (Exception e) {
+            // TODO: Specify exceptions
+            throw new ResponseParseException(e);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetLinkParamsRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetLinkParamsRequest.java
new file mode 100644
index 0000000..1be952a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetLinkParamsRequest.java
@@ -0,0 +1,89 @@
+/*  Copyright (C) 2024 Damien Gaignon, Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import static nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.DeviceConfig.LinkParams;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.DeviceConfig;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+// GetLinkParamsRequest<HuaweiBtLESupport, BtLERequest>
+public class GetLinkParamsRequest extends Request {
+    private static final Logger LOG = LoggerFactory.getLogger(GetLinkParamsRequest.class);
+
+    public byte[] serverNonce;
+    public byte bondState;
+
+    public GetLinkParamsRequest(
+            HuaweiSupportProvider support,
+            nodomain.freeyourgadget.gadgetbridge.service.btbr.TransactionBuilder builder
+    ) {
+        super(support, builder);
+        this.serviceId = DeviceConfig.id;
+        this.commandId = LinkParams.id;
+        this.serverNonce = new byte[18];
+        isSelfQueue = false;
+    }
+
+    public GetLinkParamsRequest(
+            HuaweiSupportProvider support,
+            nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder builder
+    ) {
+        super(support, builder);
+        this.serviceId = DeviceConfig.id;
+        this.commandId = LinkParams.id;
+        this.serverNonce = new byte[18];
+        isSelfQueue = false;
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        try {
+            return new LinkParams.Request(paramsProvider, supportProvider.getHuaweiType()).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+
+    @Override
+    protected void processResponse() throws ResponseParseException {
+        LOG.debug("handle LinkParams");
+
+        if (!(receivedPacket instanceof LinkParams.Response))
+            throw new ResponseTypeMismatchException(receivedPacket, LinkParams.Response.class);
+
+        supportProvider.setProtocolVersion(((LinkParams.Response) receivedPacket).protocolVersion);
+        paramsProvider.setDeviceSupportType(((LinkParams.Response) receivedPacket).deviceSupportType);
+
+        paramsProvider.setSliceSize(((LinkParams.Response) receivedPacket).sliceSize);
+        paramsProvider.setMtu(((LinkParams.Response) receivedPacket).mtu);
+
+        this.serverNonce = ((LinkParams.Response) receivedPacket).serverNonce;
+        paramsProvider.setAuthVersion(((LinkParams.Response) receivedPacket).authVersion);
+
+        this.bondState = ((LinkParams.Response) receivedPacket).bondState;
+
+        paramsProvider.setAuthAlgo(((LinkParams.Response) receivedPacket).authAlgo);
+        paramsProvider.setEncryptMethod(((LinkParams.Response) receivedPacket).encryptMethod);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetMusicInfoParams.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetMusicInfoParams.java
new file mode 100644
index 0000000..2a1a6eb
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetMusicInfoParams.java
@@ -0,0 +1,44 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.MusicControl;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class GetMusicInfoParams extends Request {
+    private final Logger LOG = LoggerFactory.getLogger(GetMusicInfoParams.class);
+    public GetMusicInfoParams(HuaweiSupportProvider support) {
+        super(support);
+        this.serviceId = MusicControl.id;
+        this.commandId = MusicControl.MusicInfoParams.id;
+
+    }
+
+    @Override
+    protected boolean requestSupported() {
+        return supportProvider.getHuaweiCoordinator().supportsMusicUploading();
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws Request.RequestCreationException {
+        try {
+            return new MusicControl.MusicInfoParams.Request(paramsProvider).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new Request.RequestCreationException(e);
+        }
+    }
+
+    @Override
+    protected void processResponse() throws Request.ResponseParseException {
+        LOG.info("MusicControl.MusicInfoParams processResponse");
+        if (!(receivedPacket instanceof MusicControl.MusicInfoParams.Response))
+            throw new Request.ResponseTypeMismatchException(receivedPacket, MusicControl.MusicInfoParams.Response.class);
+
+        MusicControl.MusicInfoParams.Response resp = (MusicControl.MusicInfoParams.Response)(receivedPacket);
+        supportProvider.getHuaweiMusicManager().onMusicMusicInfoParams(resp.params, resp.frameCount, resp.pageStruct);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetMusicList.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetMusicList.java
new file mode 100644
index 0000000..46281b0
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetMusicList.java
@@ -0,0 +1,45 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.MusicControl;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class GetMusicList extends Request {
+    private final Logger LOG = LoggerFactory.getLogger(GetMusicList.class);
+
+    private final int startFrame;
+    private final int endFrame;
+
+    public GetMusicList(HuaweiSupportProvider support, int startFrame, int endFrame) {
+        super(support);
+        this.serviceId = MusicControl.id;
+        this.commandId = MusicControl.MusicList.id;
+        this.startFrame = startFrame;
+        this.endFrame = endFrame;
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws Request.RequestCreationException {
+        try {
+            return new MusicControl.MusicList.Request(paramsProvider, (short) this.startFrame, (short) this.endFrame).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new Request.RequestCreationException(e);
+        }
+    }
+
+    @Override
+    protected void processResponse() throws Request.ResponseParseException {
+        LOG.info("MusicControl.MusicList processResponse");
+        if (!(receivedPacket instanceof MusicControl.MusicList.Response))
+            throw new Request.ResponseTypeMismatchException(receivedPacket, MusicControl.MusicList.Response.class);
+
+        MusicControl.MusicList.Response resp = (MusicControl.MusicList.Response) (receivedPacket);
+        supportProvider.getHuaweiMusicManager().onMusicListResponse(resp.startFrame, resp.endIndex, resp.musicList);
+
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetMusicPlaylist.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetMusicPlaylist.java
new file mode 100644
index 0000000..9099a5d6
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetMusicPlaylist.java
@@ -0,0 +1,39 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.MusicControl;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class GetMusicPlaylist extends Request {
+    private final Logger LOG = LoggerFactory.getLogger(GetMusicPlaylist.class);
+
+    public GetMusicPlaylist(HuaweiSupportProvider support) {
+        super(support);
+        this.serviceId = MusicControl.id;
+        this.commandId = MusicControl.MusicPlaylists.id;
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws Request.RequestCreationException {
+        try {
+            return new MusicControl.MusicPlaylists.Request(paramsProvider).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new Request.RequestCreationException(e);
+        }
+    }
+
+    @Override
+    protected void processResponse() throws Request.ResponseParseException {
+        LOG.info("MusicControl.MusicPlaylists processResponse");
+        if (!(receivedPacket instanceof MusicControl.MusicPlaylists.Response))
+            throw new Request.ResponseTypeMismatchException(receivedPacket, MusicControl.MusicPlaylists.Response.class);
+
+        MusicControl.MusicPlaylists.Response resp = (MusicControl.MusicPlaylists.Response) (receivedPacket);
+        supportProvider.getHuaweiMusicManager().onMusicPlaylistResponse(resp.playlists);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetMusicPlaylistMusics.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetMusicPlaylistMusics.java
new file mode 100644
index 0000000..c557b90
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetMusicPlaylistMusics.java
@@ -0,0 +1,44 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.MusicControl;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class GetMusicPlaylistMusics extends Request {
+    private final Logger LOG = LoggerFactory.getLogger(GetMusicPlaylistMusics.class);
+
+    private final int playlist;
+    private final int index;
+
+    public GetMusicPlaylistMusics(HuaweiSupportProvider support, int playlist, int index) {
+        super(support);
+        this.serviceId = MusicControl.id;
+        this.commandId = MusicControl.MusicPlaylistMusics.id;
+        this.playlist = playlist;
+        this.index = index;
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws Request.RequestCreationException {
+        try {
+            return new MusicControl.MusicPlaylistMusics.Request(paramsProvider, (short) playlist, (short) index).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new Request.RequestCreationException(e);
+        }
+    }
+
+    @Override
+    protected void processResponse() throws Request.ResponseParseException {
+        LOG.info("MusicControl.GetMusicPlaylistMusics processResponse");
+        if (!(receivedPacket instanceof MusicControl.MusicPlaylistMusics.Response))
+            throw new Request.ResponseTypeMismatchException(receivedPacket, MusicControl.MusicPlaylistMusics.Response.class);
+
+        MusicControl.MusicPlaylistMusics.Response resp = (MusicControl.MusicPlaylistMusics.Response) (receivedPacket);
+        supportProvider.getHuaweiMusicManager().onMusicPlaylistMusics(resp.id, resp.index, resp.musicIds);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetNotificationCapabilitiesRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetNotificationCapabilitiesRequest.java
new file mode 100644
index 0000000..2d2f3f8
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetNotificationCapabilitiesRequest.java
@@ -0,0 +1,61 @@
+/*  Copyright (C) 2024 Damien Gaignon
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Notifications;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Notifications.NotificationCapabilities;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class GetNotificationCapabilitiesRequest extends Request {
+    private static final Logger LOG = LoggerFactory.getLogger(GetNotificationCapabilitiesRequest.class);
+
+    public GetNotificationCapabilitiesRequest(HuaweiSupportProvider support) {
+        super(support);
+        this.serviceId = Notifications.id;
+        this.commandId = NotificationCapabilities.id;
+    }
+
+    @Override
+    protected boolean requestSupported() {
+        return supportProvider.getHuaweiCoordinator().supportsPromptPushMessage() && supportProvider.getProtocolVersion() == 2;
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        try {
+            return new NotificationCapabilities.Request(paramsProvider).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+
+    @Override
+    protected void processResponse() throws ResponseParseException {
+        LOG.debug("handle Get Notification Capabilities");
+
+        if (!(receivedPacket instanceof NotificationCapabilities.Response))
+            throw new ResponseTypeMismatchException(receivedPacket, NotificationCapabilities.Response.class);
+
+        supportProvider.getHuaweiCoordinator().saveNotificationCapabilities(((NotificationCapabilities.Response) receivedPacket).capabilities);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetNotificationConstraintsRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetNotificationConstraintsRequest.java
new file mode 100644
index 0000000..a9eb3bb
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetNotificationConstraintsRequest.java
@@ -0,0 +1,63 @@
+/*  Copyright (C) 2024 Damien Gaignon
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Notifications;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Notifications.NotificationConstraints;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class GetNotificationConstraintsRequest extends Request {
+    private static final Logger LOG = LoggerFactory.getLogger(GetNotificationConstraintsRequest.class);
+
+    public GetNotificationConstraintsRequest(HuaweiSupportProvider support) {
+        super(support);
+        this.serviceId = Notifications.id;
+        this.commandId = NotificationConstraints.id;
+    }
+
+    @Override
+    protected boolean requestSupported() {
+        return supportProvider.getHuaweiCoordinator().supportsNotificationAlert() && supportProvider.getProtocolVersion() == 2
+                && supportProvider.getCoordinator().getDeviceType() != DeviceType.HUAWEIBANDAW70; // Bit of a workaround, there is probably some capabilities this actually depends on
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        try {
+            return new NotificationConstraints.Request(paramsProvider).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+
+    @Override
+    protected void processResponse() throws ResponseParseException {
+        LOG.debug("handle Get Notification Constraint");
+
+        if (!(receivedPacket instanceof NotificationConstraints.Response))
+            throw new ResponseTypeMismatchException(receivedPacket, NotificationConstraints.Response.class);
+
+        supportProvider.getHuaweiCoordinator().saveNotificationConstraints(((NotificationConstraints.Response) receivedPacket).constraints);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetPhoneInfoRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetPhoneInfoRequest.java
new file mode 100644
index 0000000..a4f9c5b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetPhoneInfoRequest.java
@@ -0,0 +1,55 @@
+/*  Copyright (C) 2024 Damien Gaignon
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.GBException;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket.CryptoException;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.DeviceConfig;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class GetPhoneInfoRequest extends Request {
+    private static final Logger LOG = LoggerFactory.getLogger(GetPhoneInfoRequest.class);
+
+    private byte[] phoneInfo;
+
+    public GetPhoneInfoRequest(HuaweiSupportProvider support, byte[] phoneInfo) {
+        super(support);
+        this.serviceId = DeviceConfig.id;
+        this.commandId = DeviceConfig.PhoneInfo.id;
+        this.phoneInfo = phoneInfo;
+        this.addToResponse = false;
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        try {
+            return new DeviceConfig.PhoneInfo.Request(paramsProvider, this.phoneInfo).serialize();
+        } catch (CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+
+    @Override
+    protected void processResponse() throws ResponseParseException {
+        LOG.debug("handle Phone Info");
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetPincodeRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetPincodeRequest.java
new file mode 100644
index 0000000..11a3c6d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetPincodeRequest.java
@@ -0,0 +1,56 @@
+/*  Copyright (C) 2024 Damien Gaignon, Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiCrypto;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket.CryptoException;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.DeviceConfig;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class GetPincodeRequest extends Request {
+    private static final Logger LOG = LoggerFactory.getLogger(GetPincodeRequest.class);
+
+    public GetPincodeRequest(HuaweiSupportProvider support) {
+        super(support);
+        this.serviceId = DeviceConfig.id;
+        this.commandId = DeviceConfig.PinCode.id;
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        try {
+            return new DeviceConfig.PinCode.Request(paramsProvider).serialize();
+        } catch (CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+
+    @Override
+    protected void processResponse() throws ResponseParseException {
+        LOG.debug("handle Pincode");
+
+        if (!(receivedPacket instanceof DeviceConfig.PinCode.Response))
+            throw new ResponseTypeMismatchException(receivedPacket, DeviceConfig.PinCode.Response.class);
+
+        paramsProvider.setPinCode(((DeviceConfig.PinCode.Response) receivedPacket).pinCode);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetProductInformationRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetProductInformationRequest.java
new file mode 100644
index 0000000..a6e5d52
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetProductInformationRequest.java
@@ -0,0 +1,57 @@
+/*  Copyright (C) 2024 Damien Gaignon, Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.DeviceConfig;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class GetProductInformationRequest extends Request {
+    private static final Logger LOG = LoggerFactory.getLogger(GetProductInformationRequest.class);
+
+    public GetProductInformationRequest(HuaweiSupportProvider support) {
+        super(support);
+        this.serviceId = DeviceConfig.id;
+        this.commandId = DeviceConfig.ProductInfo.id;
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        try {
+            return new DeviceConfig.ProductInfo.Request(paramsProvider, supportProvider.getHuaweiType()).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+
+    @Override
+    protected void processResponse() throws ResponseParseException {
+        LOG.debug("handle Product Information");
+
+        if (!(receivedPacket instanceof DeviceConfig.ProductInfo.Response))
+            throw new ResponseTypeMismatchException(receivedPacket, DeviceConfig.ProductInfo.Response.class);
+
+        getDevice().setFirmwareVersion(((DeviceConfig.ProductInfo.Response) receivedPacket).softwareVersion);
+        getDevice().setFirmwareVersion2(((DeviceConfig.ProductInfo.Response) receivedPacket).hardwareVersion);
+        getDevice().setModel(((DeviceConfig.ProductInfo.Response) receivedPacket).productModel);
+   }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetSecurityNegotiationRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetSecurityNegotiationRequest.java
new file mode 100644
index 0000000..f4bf41b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetSecurityNegotiationRequest.java
@@ -0,0 +1,65 @@
+/*  Copyright (C) 2024 Damien Gaignon, Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import android.os.Build;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.DeviceConfig;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class GetSecurityNegotiationRequest extends Request {
+    private static final Logger LOG = LoggerFactory.getLogger(GetSecurityNegotiationRequest.class);
+    public int authType = 0x00;
+
+    public GetSecurityNegotiationRequest(HuaweiSupportProvider support) {
+        super(support);
+        this.serviceId = DeviceConfig.id;
+        this.commandId = DeviceConfig.SecurityNegotiation.id;
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        try {
+            return new DeviceConfig.SecurityNegotiation.Request(
+                    paramsProvider,
+                    paramsProvider.getAuthMode(),
+                    supportProvider.getAndroidId(),
+                    Build.MODEL
+                ).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+
+    @Override
+    protected void processResponse() {
+        LOG.debug("handle Security and Negotiation");
+
+        if (!(receivedPacket instanceof DeviceConfig.SecurityNegotiation.Response)) {
+            // TODO: exception
+            return;
+        }
+
+        this.authType = ((DeviceConfig.SecurityNegotiation.Response) receivedPacket).authType;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetSettingRelatedRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetSettingRelatedRequest.java
new file mode 100644
index 0000000..e113548
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetSettingRelatedRequest.java
@@ -0,0 +1,61 @@
+/*  Copyright (C) 2024 Damien Gaignon
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket.CryptoException;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.DeviceConfig;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class GetSettingRelatedRequest extends Request {
+    private static final Logger LOG = LoggerFactory.getLogger(GetSettingRelatedRequest.class);
+
+    public GetSettingRelatedRequest(HuaweiSupportProvider support) {
+        super(support);
+        this.serviceId = DeviceConfig.id;
+        this.commandId = DeviceConfig.SettingRelated.id;
+    }
+
+    @Override
+    protected boolean requestSupported() {
+        return supportProvider.getHuaweiCoordinator().supportsSettingRelated();
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        try {
+            return new DeviceConfig.SettingRelated.Request(paramsProvider).serialize();
+        } catch (CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+
+    @Override
+    protected void processResponse() throws ResponseParseException {
+        if (!(receivedPacket instanceof DeviceConfig.SettingRelated.Response))
+            throw new ResponseTypeMismatchException(receivedPacket, DeviceConfig.SettingRelated.Response.class);
+
+        LOG.debug("handle Setting Related");
+
+        supportProvider.getHuaweiCoordinator().setSupportsTruSleepNewSync(((DeviceConfig.SettingRelated.Response) receivedPacket).truSleepNewSync);
+        supportProvider.getHuaweiCoordinator().setSupportsGpsNewSync(((DeviceConfig.SettingRelated.Response) receivedPacket).gpsNewSync);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetSleepDataCountRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetSleepDataCountRequest.java
new file mode 100644
index 0000000..53a339f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetSleepDataCountRequest.java
@@ -0,0 +1,84 @@
+/*  Copyright (C) 2024 Damien Gaignon, Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FitnessData;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class GetSleepDataCountRequest extends Request {
+    private final int start;
+    private final int end;
+
+    public GetSleepDataCountRequest(
+            HuaweiSupportProvider support,
+            nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder builder,
+            int start,
+            int end
+    ) {
+        super(support, builder);
+        this.serviceId = FitnessData.id;
+        this.commandId = FitnessData.MessageCount.sleepId;
+
+        this.start = start;
+        this.end = end;
+    }
+
+    public GetSleepDataCountRequest(
+            HuaweiSupportProvider support,
+            nodomain.freeyourgadget.gadgetbridge.service.btbr.TransactionBuilder builder,
+            int start,
+            int end
+    ) {
+        super(support, builder);
+        this.serviceId = FitnessData.id;
+        this.commandId = FitnessData.MessageCount.sleepId;
+
+        this.start = start;
+        this.end = end;
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        try {
+            return new FitnessData.MessageCount.Request(
+                    paramsProvider,
+                    this.commandId,
+                    this.start,
+                    this.end
+            ).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+
+    @Override
+    protected void processResponse() throws ResponseParseException {
+        if (!(receivedPacket instanceof FitnessData.MessageCount.Response))
+            throw new ResponseTypeMismatchException(receivedPacket, FitnessData.MessageCount.Response.class);
+
+        short count = ((FitnessData.MessageCount.Response) receivedPacket).count;
+
+        if (count > 0) {
+            GetSleepDataRequest nextRequest = new GetSleepDataRequest(supportProvider, count, (short) 0);
+            nextRequest.setFinalizeReq(this.finalizeReq);
+            this.nextRequest(nextRequest);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetSleepDataRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetSleepDataRequest.java
new file mode 100644
index 0000000..0779a56
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetSleepDataRequest.java
@@ -0,0 +1,97 @@
+/*  Copyright (C) 2024 Damien Gaignon, Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FitnessData;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class GetSleepDataRequest extends Request {
+    private static final Logger LOG = LoggerFactory.getLogger(GetSleepDataRequest.class);
+
+    private final short maxCount;
+    private final short count;
+
+    public GetSleepDataRequest(HuaweiSupportProvider support, short maxCount, short count) {
+        super(support);
+        this.serviceId = FitnessData.id;
+        this.commandId = FitnessData.MessageData.sleepId;
+
+        this.maxCount = maxCount;
+        this.count = count;
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        try {
+            return new FitnessData.MessageData.Request(paramsProvider, this.commandId, this.count).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+
+    @Override
+    protected void processResponse() throws ResponseParseException {
+        // FitnessData.MessageData.SleepResponse response = FitnessData.MessageData.SleepResponse.fromTlv(receivedPacket.tlv);
+        if (!(receivedPacket instanceof FitnessData.MessageData.SleepResponse))
+            throw new ResponseTypeMismatchException(receivedPacket, FitnessData.MessageData.SleepResponse.class);
+
+        FitnessData.MessageData.SleepResponse response = (FitnessData.MessageData.SleepResponse) receivedPacket;
+
+        short receivedCount = response.number;
+
+        if (receivedCount != this.count) {
+            LOG.warn("Counts do not match");
+        }
+
+        for (FitnessData.MessageData.SleepResponse.SubContainer subContainer : response.containers) {
+            // TODO: it might make more sense to convert the timestamp in the FitnessData class
+            int[] timestampInts = new int[6];
+
+            for (int i = 0; i < 6; i++) {
+                if (subContainer.timestamp[i] >= 0)
+                    timestampInts[i] = subContainer.timestamp[i];
+                else
+                    timestampInts[i] = subContainer.timestamp[i] & 0xFF;
+            }
+
+            int timestamp =
+                    (timestampInts[0] << 24) +
+                            (timestampInts[1] << 16) +
+                            (timestampInts[2] << 8) +
+                            (timestampInts[3]);
+
+            int durationInt =
+                    (timestampInts[4] << 8L) +
+                            (timestampInts[5]);
+            short duration = (short) (durationInt * 60);
+
+            this.supportProvider.addSleepActivity(timestamp, timestamp + duration, subContainer.type, FitnessData.MessageData.sleepId);
+        }
+
+        if (count + 1 < maxCount) {
+            GetSleepDataRequest nextRequest = new GetSleepDataRequest(supportProvider, this.maxCount, (short) (this.count + 1));
+            nextRequest.setFinalizeReq(this.finalizeReq);
+            this.nextRequest(nextRequest);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetSmartAlarmList.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetSmartAlarmList.java
new file mode 100644
index 0000000..007c358
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetSmartAlarmList.java
@@ -0,0 +1,96 @@
+/*  Copyright (C) 2024 Damien Gaignon, Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Alarms;
+import nodomain.freeyourgadget.gadgetbridge.entities.Alarm;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class GetSmartAlarmList extends Request {
+
+    public GetSmartAlarmList(HuaweiSupportProvider support) {
+        super(support);
+
+        this.serviceId = Alarms.id;
+        this.commandId = Alarms.SmartAlarmList.id;
+    }
+
+    @Override
+    protected boolean requestSupported() {
+        return supportProvider.getHuaweiCoordinator().supportsChangingAlarm() &&
+                supportProvider.getHuaweiCoordinator().supportsSmartAlarm(supportProvider.getDevice());
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        try {
+            return new Alarms.SmartAlarmList.Request(supportProvider.getParamsProvider()).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+
+    @Override
+    protected void processResponse() throws ResponseParseException {
+        if (!(receivedPacket instanceof Alarms.SmartAlarmList.Response))
+            throw new ResponseTypeMismatchException(receivedPacket, Alarms.SmartAlarmList.Response.class);
+
+        Alarms.SmartAlarm smartAlarm = ((Alarms.SmartAlarmList.Response) receivedPacket).smartAlarm;
+
+        if (smartAlarm != null) {
+            supportProvider.saveAlarms(new Alarm[] {
+                    new Alarm(
+                            0,
+                            0,
+                            0,
+                            smartAlarm.status,
+                            true,
+                            (int) smartAlarm.aheadTime,
+                            false,
+                            smartAlarm.repeat,
+                            smartAlarm.startHour,
+                            smartAlarm.startMinute,
+                            false,
+                            "Smart alarm",
+                            ""
+                    )
+            });
+        } else {
+            // Set empty smart alarm so index zero is always smart alarm
+            supportProvider.saveAlarms(new Alarm[] {
+                    new Alarm(
+                            0,
+                            0,
+                            0,
+                            false,
+                            true,
+                            null,
+                            false,
+                            0,
+                            0,
+                            0,
+                            true,
+                            "Smart alarm",
+                            ""
+                    )
+            });
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetStepDataCountRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetStepDataCountRequest.java
new file mode 100644
index 0000000..3b6b901
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetStepDataCountRequest.java
@@ -0,0 +1,61 @@
+/*  Copyright (C) 2024 Damien Gaignon, Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FitnessData;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class GetStepDataCountRequest extends Request {
+    private int start = 0;
+    private int end = 0;
+
+    public GetStepDataCountRequest(HuaweiSupportProvider support, int start, int end) {
+        super(support);
+
+        this.serviceId = FitnessData.id;
+        this.commandId = FitnessData.MessageCount.stepId;
+
+        this.start = start;
+        this.end = end;
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        try {
+            return new FitnessData.MessageCount.Request(paramsProvider, this.commandId, this.start, this.end).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+
+    @Override
+    protected void processResponse() throws ResponseParseException {
+        if (!(receivedPacket instanceof FitnessData.MessageCount.Response))
+            throw new ResponseTypeMismatchException(receivedPacket, FitnessData.MessageCount.Response.class);
+
+        short count = ((FitnessData.MessageCount.Response) receivedPacket).count;
+
+        if (count > 0) {
+            GetStepDataRequest nextRequest = new GetStepDataRequest(supportProvider, count, (short) 0);
+            nextRequest.setFinalizeReq(this.finalizeReq);
+            this.nextRequest(nextRequest);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetStepDataRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetStepDataRequest.java
new file mode 100644
index 0000000..3f59b3c
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetStepDataRequest.java
@@ -0,0 +1,96 @@
+/*  Copyright (C) 2024 Damien Gaignon, Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FitnessData;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class GetStepDataRequest extends Request {
+    private static final Logger LOG = LoggerFactory.getLogger(GetStepDataRequest.class);
+
+    short maxCount;
+    short count;
+
+    public GetStepDataRequest(HuaweiSupportProvider support, short maxCount, short count) {
+        super(support);
+        this.serviceId = FitnessData.id;
+        this.commandId = FitnessData.MessageData.stepId;
+        this.maxCount = maxCount;
+        this.count = count;
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        try {
+            return new FitnessData.MessageData.Request(paramsProvider, this.commandId, this.count).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+
+    @Override
+    protected void processResponse() throws ResponseParseException {
+        if (!(receivedPacket instanceof FitnessData.MessageData.StepResponse))
+            throw new ResponseTypeMismatchException(receivedPacket, FitnessData.MessageData.StepResponse.class);
+
+        FitnessData.MessageData.StepResponse response = (FitnessData.MessageData.StepResponse) receivedPacket;
+
+        if (response.number != this.count) {
+            LOG.warn("Counts do not match! Received: " + response.number + ", expected: " + this.count);
+            this.count = response.number; // This stops it from going into a loop
+        }
+
+        for (FitnessData.MessageData.StepResponse.SubContainer subContainer : response.containers) {
+            int dataTimestamp = subContainer.timestamp;
+
+            if (subContainer.parsedData != null) {
+                short steps = (short) subContainer.steps;
+                short calories = (short) subContainer.calories;
+                short distance = (short) subContainer.distance;
+                byte heartrate = (byte) subContainer.heartrate;
+                byte spo = (byte) subContainer.spo;
+
+                if (steps == -1)
+                    steps = 0;
+                if (calories == -1)
+                    calories = 0;
+                if (distance == -1)
+                    distance = 0;
+
+                for (FitnessData.MessageData.StepResponse.SubContainer.TV tv : subContainer.unknownTVs) {
+                    LOG.warn("Unknown tag in step data: " + tv);
+                }
+
+                this.supportProvider.addStepData(dataTimestamp, steps, calories, distance, spo, heartrate);
+            } else {
+                LOG.error(subContainer.parsedDataError);
+            }
+        }
+
+        if (count + 1 < maxCount) {
+            GetStepDataRequest nextRequest = new GetStepDataRequest(supportProvider, this.maxCount, (short) (this.count + 1));
+            nextRequest.setFinalizeReq(this.finalizeReq);
+            this.nextRequest(nextRequest);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetSupportedCommandsRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetSupportedCommandsRequest.java
new file mode 100644
index 0000000..93061bb
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetSupportedCommandsRequest.java
@@ -0,0 +1,105 @@
+/*  Copyright (C) 2024 Damien Gaignon, Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+
+/* In order to be compatible with all devices, request send all possible commands
+to all possible services. This implies long packet which is not handled on the device.
+Thus, this request could be sliced in 3 packets. But this command does not support slicing.
+Thus, one need to send multiple requests and concat the response.
+Packets should be 240 bytes max */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+import java.util.Map;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.DeviceConfig;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class GetSupportedCommandsRequest extends Request {
+    private static final Logger LOG = LoggerFactory.getLogger(GetSupportedCommandsRequest.class);
+
+    private final Map<Integer, byte[]> commandsPerService;
+    private final List<Byte> activatedServices;
+
+    public GetSupportedCommandsRequest(
+            HuaweiSupportProvider support,
+            List<Byte> activatedServices
+    ) {
+        super(support);
+        this.serviceId = DeviceConfig.id;
+        this.commandId = DeviceConfig.SupportedCommands.id;
+        this.commandsPerService = DeviceConfig.SupportedCommands.commandsPerService;
+        this.activatedServices = activatedServices;
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        DeviceConfig.SupportedCommands.Request commandsRequest = new DeviceConfig.SupportedCommands.Request(paramsProvider);
+        byte nextService = activatedServices.remove(0);
+        boolean fits = commandsRequest.addCommandsForService(nextService, this.commandsPerService.get((int) nextService));
+        while (fits && activatedServices.size() > 0) {
+            nextService = activatedServices.remove(0);
+            fits = commandsRequest.addCommandsForService(nextService, this.commandsPerService.get((int) nextService));
+        }
+        if (!fits)
+            activatedServices.add(0, nextService); // Put the extra back
+        try {
+            return commandsRequest.serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+
+    RequestCallback dynamicServicesReq = new RequestCallback() {
+        @Override
+        public void call() {
+            supportProvider.initializeDynamicServices();
+        }
+    };
+
+    @Override
+    protected void processResponse() throws ResponseParseException {
+        LOG.debug("handle Supported Commands");
+
+        if (!(receivedPacket instanceof DeviceConfig.SupportedCommands.Response))
+            throw new ResponseTypeMismatchException(receivedPacket, DeviceConfig.SupportedCommands.Response.class);
+
+        for (DeviceConfig.SupportedCommands.Response.CommandsList commandsList : ((DeviceConfig.SupportedCommands.Response) receivedPacket).commandsLists) {
+            supportProvider.getHuaweiCoordinator().addCommandsForService(
+                    commandsList.service,
+                    commandsList.commands
+            );
+        }
+
+        if (activatedServices.size() > 0) {
+            GetSupportedCommandsRequest nextRequest = new GetSupportedCommandsRequest(supportProvider, activatedServices);
+            this.nextRequest(nextRequest);
+        } else {
+            supportProvider.getHuaweiCoordinator().printCommandsPerService();
+            if (supportProvider.getHuaweiCoordinator().supportsExpandCapability()) {
+                GetExpandCapabilityRequest nextRequest = new GetExpandCapabilityRequest(supportProvider);
+                nextRequest.setFinalizeReq(dynamicServicesReq);
+                this.nextRequest(nextRequest);
+            } else {
+                dynamicServicesReq.call();
+            }
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetSupportedServicesRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetSupportedServicesRequest.java
new file mode 100644
index 0000000..f7c9677
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetSupportedServicesRequest.java
@@ -0,0 +1,68 @@
+/*  Copyright (C) 2024 Damien Gaignon, Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.DeviceConfig;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class GetSupportedServicesRequest extends Request {
+    private static final Logger LOG = LoggerFactory.getLogger(GetSupportedServicesRequest.class);
+
+    private final byte[] knownSupportedServices;
+
+    public GetSupportedServicesRequest(HuaweiSupportProvider support) {
+        super(support);
+        this.serviceId = DeviceConfig.id;
+        this.commandId = DeviceConfig.SupportedServices.id;
+        this.knownSupportedServices = DeviceConfig.SupportedServices.knownSupportedServices;
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        try {
+            return new DeviceConfig.SupportedServices.Request(paramsProvider, this.knownSupportedServices).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+
+    @Override
+    protected void processResponse() throws ResponseParseException {
+        LOG.debug("handle Supported Services");
+
+        if (!(receivedPacket instanceof DeviceConfig.SupportedServices.Response))
+            throw new ResponseTypeMismatchException(receivedPacket, DeviceConfig.SupportedServices.Response.class);
+
+        byte[] supportedServices = ((DeviceConfig.SupportedServices.Response) receivedPacket).supportedServices;
+        List<Byte> activatedServices = new ArrayList<>();
+        for (int i = 0; i < supportedServices.length; i++) {
+            if (supportedServices[i] == 1) {
+                activatedServices.add(knownSupportedServices[i]);
+            }
+        }
+
+        GetSupportedCommandsRequest supportedCommandsReq = new GetSupportedCommandsRequest(supportProvider, activatedServices);
+        nextRequest(supportedCommandsReq);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetWatchfaceParams.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetWatchfaceParams.java
new file mode 100644
index 0000000..6c65c11
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetWatchfaceParams.java
@@ -0,0 +1,57 @@
+/*  Copyright (C) 2024 Vitalii Tomin
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Watchface;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class GetWatchfaceParams extends Request{
+
+    public GetWatchfaceParams(HuaweiSupportProvider support) {
+        super(support);
+        this.serviceId = Watchface.id;
+        this.commandId = Watchface.WatchfaceParams.id;
+
+    }
+
+    @Override
+    protected boolean requestSupported() {
+        return supportProvider.getHuaweiCoordinator().supportsWatchfaceParams();
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        try {
+            return new Watchface.WatchfaceParams.Request(paramsProvider).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+
+    @Override
+    protected void processResponse() throws ResponseParseException {
+        if (!(receivedPacket instanceof Watchface.WatchfaceParams.Response))
+            throw new ResponseTypeMismatchException(receivedPacket, Watchface.WatchfaceParams.Response.class);
+
+        Watchface.WatchfaceParams.Response resp = (Watchface.WatchfaceParams.Response)(receivedPacket);
+        supportProvider.getHuaweiCoordinator().setWatchfaceDeviceParams(resp.params);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetWatchfacesList.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetWatchfacesList.java
new file mode 100644
index 0000000..644508d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetWatchfacesList.java
@@ -0,0 +1,57 @@
+/*  Copyright (C) 2024 Vitalii Tomin
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Watchface;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class GetWatchfacesList extends Request{
+
+    private static final Logger LOG = LoggerFactory.getLogger(GetWatchfacesList.class);
+
+    public GetWatchfacesList(HuaweiSupportProvider support) {
+        super(support);
+        this.serviceId = Watchface.id;
+        this.commandId = Watchface.DeviceWatchInfo.id;
+
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        try {
+            return new Watchface.DeviceWatchInfo.Request(paramsProvider).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+
+    @Override
+    protected void processResponse() throws ResponseParseException {
+        if (!(receivedPacket instanceof Watchface.DeviceWatchInfo.Response))
+            throw new ResponseTypeMismatchException(receivedPacket, Watchface.DeviceWatchInfo.Response.class);
+
+        Watchface.DeviceWatchInfo.Response resp = (Watchface.DeviceWatchInfo.Response)(receivedPacket);
+        supportProvider.getHuaweiWatchfaceManager().setInstalledWatchfaceInfoList(resp.watchfaceInfoList);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetWatchfacesNames.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetWatchfacesNames.java
new file mode 100644
index 0000000..dd9cc0e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetWatchfacesNames.java
@@ -0,0 +1,60 @@
+/*  Copyright (C) 2024 Vitalii Tomin
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Watchface;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class GetWatchfacesNames extends Request{
+
+    private static final Logger LOG = LoggerFactory.getLogger(GetWatchfacesNames.class);
+
+    List<Watchface.InstalledWatchfaceInfo> watchfaceInfoList;
+    public GetWatchfacesNames(HuaweiSupportProvider support, List<Watchface.InstalledWatchfaceInfo> list) {
+        super(support);
+        this.serviceId = Watchface.id;
+        this.commandId = Watchface.WatchfaceNameInfo.id;
+        this.watchfaceInfoList = list;
+
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        try {
+            return new Watchface.WatchfaceNameInfo.Request(paramsProvider, this.watchfaceInfoList).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+
+    @Override
+    protected void processResponse() throws ResponseParseException {
+        if (!(receivedPacket instanceof Watchface.WatchfaceNameInfo.Response))
+            throw new ResponseTypeMismatchException(receivedPacket, Watchface.WatchfaceNameInfo.Response.class);
+
+        Watchface.WatchfaceNameInfo.Response resp = (Watchface.WatchfaceNameInfo.Response)(receivedPacket);
+        supportProvider.getHuaweiWatchfaceManager().setWatchfacesNames(resp.watchFaceNames);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetWearStatusRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetWearStatusRequest.java
new file mode 100644
index 0000000..008c360
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetWearStatusRequest.java
@@ -0,0 +1,56 @@
+/*  Copyright (C) 2024 Damien Gaignon
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.DeviceConfig;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.DeviceConfig.WearStatus;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class GetWearStatusRequest extends Request {
+    private static final Logger LOG = LoggerFactory.getLogger(GetWearStatusRequest.class);
+
+    public GetWearStatusRequest(HuaweiSupportProvider support) {
+        super(support);
+        this.serviceId = DeviceConfig.id;
+        this.commandId = WearStatus.id;
+    }
+
+    @Override
+    protected boolean requestSupported() {
+        return supportProvider.getHuaweiCoordinator().supportsMultiDevice();
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        try {
+            return new WearStatus.Request(paramsProvider).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+
+    @Override
+    protected void processResponse() {
+        LOG.debug("handle Get Wear Status");
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetWorkoutCountRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetWorkoutCountRequest.java
new file mode 100644
index 0000000..02398b5
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetWorkoutCountRequest.java
@@ -0,0 +1,90 @@
+/*  Copyright (C) 2024 Damien Gaignon, Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Workout;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class GetWorkoutCountRequest extends Request {
+    private final int start;
+    private final int end;
+
+    public GetWorkoutCountRequest(
+            HuaweiSupportProvider support,
+            nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder builder,
+            int start,
+            int end
+    ) {
+        super(support, builder);
+
+        this.serviceId = Workout.id;
+        this.commandId = Workout.WorkoutCount.id;
+
+        this.start = start;
+        this.end = end;
+    }
+
+    public GetWorkoutCountRequest(
+            HuaweiSupportProvider support,
+            nodomain.freeyourgadget.gadgetbridge.service.btbr.TransactionBuilder builder,
+            int start,
+            int end
+    ) {
+        super(support, builder);
+
+        this.serviceId = Workout.id;
+        this.commandId = Workout.WorkoutCount.id;
+
+        this.start = start;
+        this.end = end;
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        try {
+            return new Workout.WorkoutCount.Request(paramsProvider, this.start, this.end).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+
+    @Override
+    protected void processResponse() throws ResponseParseException {
+        if (!(receivedPacket instanceof Workout.WorkoutCount.Response))
+            throw new ResponseTypeMismatchException(receivedPacket, Workout.WorkoutCount.Response.class);
+
+        Workout.WorkoutCount.Response packet = (Workout.WorkoutCount.Response) receivedPacket;
+
+        if (packet.count != packet.workoutNumbers.size())
+            throw new WorkoutParseException("Packet count and workout numbers size do not match.");
+
+        if (packet.count > 0) {
+            GetWorkoutTotalsRequest nextRequest = new GetWorkoutTotalsRequest(
+                    this.supportProvider,
+                    packet.workoutNumbers.remove(0),
+                    packet.workoutNumbers
+            );
+            nextRequest.setFinalizeReq(this.finalizeReq);
+            this.nextRequest(nextRequest);
+        } else {
+            this.supportProvider.endOfWorkoutSync();
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetWorkoutDataRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetWorkoutDataRequest.java
new file mode 100644
index 0000000..98756ab
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetWorkoutDataRequest.java
@@ -0,0 +1,150 @@
+/*  Copyright (C) 2024 Damien Gaignon, Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Workout;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiWorkoutGbParser;
+
+public class GetWorkoutDataRequest extends Request {
+    private static final Logger LOG = LoggerFactory.getLogger(GetWorkoutDataRequest.class);
+
+    Workout.WorkoutCount.Response.WorkoutNumbers workoutNumbers;
+    List<Workout.WorkoutCount.Response.WorkoutNumbers> remainder;
+    short number;
+    Long databaseId;
+
+    /**
+     * Request to get workout totals
+     * @param support The support
+     * @param workoutNumbers The numbers of the current workout
+     * @param remainder The numbers of the remainder if the workouts to get
+     * @param number The number of this data request
+     */
+    public GetWorkoutDataRequest(HuaweiSupportProvider support, Workout.WorkoutCount.Response.WorkoutNumbers workoutNumbers, List<Workout.WorkoutCount.Response.WorkoutNumbers> remainder, short number, Long databaseId) {
+        super(support);
+
+        this.serviceId = Workout.id;
+        this.commandId = Workout.WorkoutData.id;
+
+        this.workoutNumbers = workoutNumbers;
+        this.remainder = remainder;
+        this.number = number;
+
+        this.databaseId = databaseId;
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        try {
+            return new Workout.WorkoutData.Request(paramsProvider, workoutNumbers.workoutNumber, this.number).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+
+    @Override
+    protected void processResponse() throws ResponseParseException {
+        if (!(receivedPacket instanceof Workout.WorkoutData.Response))
+            throw new ResponseTypeMismatchException(receivedPacket, Workout.WorkoutData.Response.class);
+
+        Workout.WorkoutData.Response packet = (Workout.WorkoutData.Response) receivedPacket;
+
+        if (packet.workoutNumber != this.workoutNumbers.workoutNumber)
+            throw new WorkoutParseException("Incorrect workout number!");
+
+        if (packet.dataNumber != this.number)
+            throw new WorkoutParseException("Incorrect data number!");
+
+        LOG.info("Workout {} data {}:", this.workoutNumbers.workoutNumber, this.number);
+        LOG.info("Workout : " + packet.workoutNumber);
+        LOG.info("Data num: " + packet.dataNumber);
+        LOG.info("Header  : " + Arrays.toString(packet.rawHeader));
+        LOG.info("Header  : " + packet.header);
+        LOG.info("Data    : " + Arrays.toString(packet.rawData));
+        LOG.info("Data    : " + Arrays.toString(packet.dataList.toArray()));
+        LOG.info("Bitmap  : " + packet.innerBitmap);
+
+        this.supportProvider.addWorkoutSampleData(
+                this.databaseId,
+                packet.dataList
+        );
+
+        if (this.workoutNumbers.dataCount > this.number + 1) {
+            GetWorkoutDataRequest nextRequest = new GetWorkoutDataRequest(
+                    this.supportProvider,
+                    this.workoutNumbers,
+                    this.remainder,
+                    (short) (this.number + 1),
+                    databaseId
+            );
+            nextRequest.setFinalizeReq(this.finalizeReq);
+            this.nextRequest(nextRequest);
+        } else if (this.workoutNumbers.paceCount > 0) {
+            GetWorkoutPaceRequest nextRequest = new GetWorkoutPaceRequest(
+                    this.supportProvider,
+                    this.workoutNumbers,
+                    this.remainder,
+                    (short) 0,
+                    this.databaseId
+            );
+            nextRequest.setFinalizeReq(this.finalizeReq);
+            this.nextRequest(nextRequest);
+        } else if (this.workoutNumbers.segmentsCount > 0) {
+            GetWorkoutSwimSegmentsRequest nextRequest = new GetWorkoutSwimSegmentsRequest(
+                    this.supportProvider,
+                    this.workoutNumbers,
+                    this.remainder,
+                    (short) 0,
+                    this.databaseId
+            );
+            nextRequest.setFinalizeReq(this.finalizeReq);
+            this.nextRequest(nextRequest);
+        } else {
+            new HuaweiWorkoutGbParser(getDevice(), getContext()).parseWorkout(this.databaseId);
+            supportProvider.downloadWorkoutGpsFiles(this.workoutNumbers.workoutNumber, this.databaseId, new Runnable() {
+                @Override
+                public void run() {
+                    if (!remainder.isEmpty()) {
+                        GetWorkoutTotalsRequest nextRequest = new GetWorkoutTotalsRequest(
+                                GetWorkoutDataRequest.this.supportProvider,
+                                remainder.remove(0),
+                                remainder
+                        );
+                        nextRequest.setFinalizeReq(GetWorkoutDataRequest.this.finalizeReq);
+                        // Cannot do this with nextRequest because it's in a callback
+                        try {
+                            nextRequest.doPerform();
+                        } catch (IOException e) {
+                            finalizeReq.handleException(new ResponseParseException("Cannot send next request", e));
+                        }
+                    } else {
+                        supportProvider.endOfWorkoutSync();
+                    }
+                }
+            });
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetWorkoutPaceRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetWorkoutPaceRequest.java
new file mode 100644
index 0000000..b4261b3
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetWorkoutPaceRequest.java
@@ -0,0 +1,127 @@
+/*  Copyright (C) 2024 Damien Gaignon, Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Workout;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiWorkoutGbParser;
+
+public class GetWorkoutPaceRequest extends Request {
+    private static final Logger LOG = LoggerFactory.getLogger(GetWorkoutPaceRequest.class);
+
+    Workout.WorkoutCount.Response.WorkoutNumbers workoutNumbers;
+    List<Workout.WorkoutCount.Response.WorkoutNumbers> remainder;
+    short number;
+    Long databaseId;
+
+    public GetWorkoutPaceRequest(HuaweiSupportProvider support, Workout.WorkoutCount.Response.WorkoutNumbers workoutNumbers, List<Workout.WorkoutCount.Response.WorkoutNumbers> remainder, short number, Long databaseId) {
+        super(support);
+
+        this.serviceId = Workout.id;
+        this.commandId = Workout.WorkoutPace.id;
+
+        this.workoutNumbers = workoutNumbers;
+        this.remainder = remainder;
+        this.number = number;
+
+        this.databaseId = databaseId;
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        try {
+            return new Workout.WorkoutPace.Request(paramsProvider,this.workoutNumbers.workoutNumber, this.number).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+
+    @Override
+    protected void processResponse() throws ResponseParseException {
+        if (!(receivedPacket instanceof Workout.WorkoutPace.Response))
+            throw new ResponseTypeMismatchException(receivedPacket, Workout.WorkoutPace.Response.class);
+
+        Workout.WorkoutPace.Response packet = (Workout.WorkoutPace.Response) receivedPacket;
+
+        if (packet.workoutNumber != this.workoutNumbers.workoutNumber)
+            throw new WorkoutParseException("Incorrect workout number!");
+
+        if (packet.paceNumber != this.number)
+            throw new WorkoutParseException("Incorrect pace number!");
+
+        LOG.info("Workout {} pace {}:", this.workoutNumbers.workoutNumber, this.number);
+        LOG.info("Workout  : " + packet.workoutNumber);
+        LOG.info("Pace     : " + packet.paceNumber);
+        LOG.info("Block num: " + packet.blocks.size());
+        LOG.info("Blocks   : " + Arrays.toString(packet.blocks.toArray()));
+
+        supportProvider.addWorkoutPaceData(this.databaseId, packet.blocks, packet.paceNumber);
+
+        if (this.workoutNumbers.paceCount > this.number + 1) {
+            GetWorkoutPaceRequest nextRequest = new GetWorkoutPaceRequest(
+                    this.supportProvider,
+                    this.workoutNumbers,
+                    this.remainder,
+                    (short) (this.number + 1),
+                    this.databaseId
+            );
+            nextRequest.setFinalizeReq(this.finalizeReq);
+            this.nextRequest(nextRequest);
+        } else if (this.workoutNumbers.segmentsCount > 0) {
+            GetWorkoutSwimSegmentsRequest nextRequest = new GetWorkoutSwimSegmentsRequest(
+                    this.supportProvider,
+                    this.workoutNumbers,
+                    this.remainder,
+                    (short) 0,
+                    this.databaseId
+            );
+            nextRequest.setFinalizeReq(this.finalizeReq);
+            this.nextRequest(nextRequest);
+        }  else {
+            new HuaweiWorkoutGbParser(getDevice(), getContext()).parseWorkout(this.databaseId);
+            supportProvider.downloadWorkoutGpsFiles(this.workoutNumbers.workoutNumber, this.databaseId, new Runnable() {
+                @Override
+                public void run() {
+                    if (!remainder.isEmpty()) {
+                        GetWorkoutTotalsRequest nextRequest = new GetWorkoutTotalsRequest(
+                                GetWorkoutPaceRequest.this.supportProvider,
+                                remainder.remove(0),
+                                remainder
+                        );
+                        nextRequest.setFinalizeReq(GetWorkoutPaceRequest.this.finalizeReq);
+                        // Cannot do this with nextRequest because it's in a callback
+                        try {
+                            nextRequest.doPerform();
+                        } catch (IOException e) {
+                            finalizeReq.handleException(new ResponseParseException("Cannot send next request", e));
+                        }
+                    } else {
+                        supportProvider.endOfWorkoutSync();
+                    }
+                }
+            });
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetWorkoutSwimSegmentsRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetWorkoutSwimSegmentsRequest.java
new file mode 100644
index 0000000..d636870
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetWorkoutSwimSegmentsRequest.java
@@ -0,0 +1,102 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Workout;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiWorkoutGbParser;
+
+public class GetWorkoutSwimSegmentsRequest extends Request {
+    private static final Logger LOG = LoggerFactory.getLogger(GetWorkoutSwimSegmentsRequest.class);
+
+    Workout.WorkoutCount.Response.WorkoutNumbers workoutNumbers;
+    List<Workout.WorkoutCount.Response.WorkoutNumbers> remainder;
+    short number;
+    Long databaseId;
+
+    public GetWorkoutSwimSegmentsRequest(HuaweiSupportProvider support, Workout.WorkoutCount.Response.WorkoutNumbers workoutNumbers, List<Workout.WorkoutCount.Response.WorkoutNumbers> remainder, short number, Long databaseId) {
+        super(support);
+
+        this.serviceId = Workout.id;
+        this.commandId = Workout.WorkoutSwimSegments.id;
+
+        this.workoutNumbers = workoutNumbers;
+        this.remainder = remainder;
+        this.number = number;
+
+        this.databaseId = databaseId;
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        try {
+            return new Workout.WorkoutSwimSegments.Request(paramsProvider,this.workoutNumbers.workoutNumber, this.number).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+
+    @Override
+    protected void processResponse() throws ResponseParseException {
+        if (!(receivedPacket instanceof Workout.WorkoutSwimSegments.Response))
+            throw new ResponseTypeMismatchException(receivedPacket, Workout.WorkoutSwimSegments.Response.class);
+
+        Workout.WorkoutSwimSegments.Response packet = (Workout.WorkoutSwimSegments.Response) receivedPacket;
+
+        if (packet.workoutNumber != this.workoutNumbers.workoutNumber)
+            throw new WorkoutParseException("Incorrect workout number!");
+
+        if (packet.segmentNumber != this.number)
+            throw new WorkoutParseException("Incorrect pace number!");
+
+        LOG.info("Workout {} segment {}:", this.workoutNumbers.workoutNumber, this.number);
+        LOG.info("Workout  : " + packet.workoutNumber);
+        LOG.info("Segments  : " + packet.segmentNumber);
+        LOG.info("Block num: " + packet.blocks.size());
+        LOG.info("Blocks   : " + Arrays.toString(packet.blocks.toArray()));
+
+        supportProvider.addWorkoutSwimSegmentsData(this.databaseId, packet.blocks, packet.segmentNumber);
+
+        if (this.workoutNumbers.segmentsCount > this.number + 1) {
+            GetWorkoutSwimSegmentsRequest nextRequest = new GetWorkoutSwimSegmentsRequest(
+                    this.supportProvider,
+                    this.workoutNumbers,
+                    this.remainder,
+                    (short) (this.number + 1),
+                    this.databaseId
+            );
+            nextRequest.setFinalizeReq(this.finalizeReq);
+            this.nextRequest(nextRequest);
+        } else {
+            new HuaweiWorkoutGbParser(getDevice(), getContext()).parseWorkout(this.databaseId);
+            supportProvider.downloadWorkoutGpsFiles(this.workoutNumbers.workoutNumber, this.databaseId, new Runnable() {
+                @Override
+                public void run() {
+                    if (!remainder.isEmpty()) {
+                        GetWorkoutTotalsRequest nextRequest = new GetWorkoutTotalsRequest(
+                                GetWorkoutSwimSegmentsRequest.this.supportProvider,
+                                remainder.remove(0),
+                                remainder
+                        );
+                        nextRequest.setFinalizeReq(GetWorkoutSwimSegmentsRequest.this.finalizeReq);
+                        // Cannot do this with nextRequest because it's in a callback
+                        try {
+                            nextRequest.doPerform();
+                        } catch (IOException e) {
+                            finalizeReq.handleException(new ResponseParseException("Cannot send next request", e));
+                        }
+                    } else {
+                        supportProvider.endOfWorkoutSync();
+                    }
+                }
+            });
+        }
+
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetWorkoutTotalsRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetWorkoutTotalsRequest.java
new file mode 100644
index 0000000..ff52097
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetWorkoutTotalsRequest.java
@@ -0,0 +1,141 @@
+/*  Copyright (C) 2024 Damien Gaignon, Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Workout;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiWorkoutGbParser;
+
+public class GetWorkoutTotalsRequest extends Request {
+    private static final Logger LOG = LoggerFactory.getLogger(GetWorkoutTotalsRequest.class);
+
+    Workout.WorkoutCount.Response.WorkoutNumbers workoutNumbers;
+    List<Workout.WorkoutCount.Response.WorkoutNumbers> remainder;
+
+    /**
+     * Request to get workout totals
+     * @param support The support
+     * @param workoutNumbers The numbers of the current workout
+     * @param remainder The numbers of the remainder of the workouts to get
+     */
+    public GetWorkoutTotalsRequest(HuaweiSupportProvider support, Workout.WorkoutCount.Response.WorkoutNumbers workoutNumbers, List<Workout.WorkoutCount.Response.WorkoutNumbers> remainder) {
+        super(support);
+
+        this.serviceId = Workout.id;
+        this.commandId = Workout.WorkoutTotals.id;
+
+        this.workoutNumbers = workoutNumbers;
+        this.remainder = remainder;
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        try {
+            return new Workout.WorkoutTotals.Request(paramsProvider, workoutNumbers.workoutNumber).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+
+    @Override
+    protected void processResponse() throws ResponseParseException {
+        if (!(receivedPacket instanceof Workout.WorkoutTotals.Response))
+            throw new ResponseTypeMismatchException(receivedPacket, Workout.WorkoutTotals.Response.class);
+
+        Workout.WorkoutTotals.Response packet = (Workout.WorkoutTotals.Response) receivedPacket;
+
+        if (packet.number != this.workoutNumbers.workoutNumber)
+            throw new WorkoutParseException("Incorrect workout number!");
+
+        LOG.info("Workout {} totals:", this.workoutNumbers.workoutNumber);
+        LOG.info("Number  : " + packet.number);
+        LOG.info("Status  : " + packet.status);
+        LOG.info("Start   : " + packet.startTime);
+        LOG.info("End     : " + packet.endTime);
+        LOG.info("Calories: " + packet.calories);
+        LOG.info("Distance: " + packet.distance);
+        LOG.info("Steps   : " + packet.stepCount);
+        LOG.info("Time    : " + packet.totalTime);
+        LOG.info("Duration: " + packet.duration);
+        LOG.info("Type    : " + packet.type);
+
+        Long databaseId = this.supportProvider.addWorkoutTotalsData(packet);
+
+        // Create the next request
+        if (this.workoutNumbers.dataCount > 0) {
+            GetWorkoutDataRequest nextRequest = new GetWorkoutDataRequest(
+                    this.supportProvider,
+                    this.workoutNumbers,
+                    this.remainder,
+                    (short) 0,
+                    databaseId
+            );
+            nextRequest.setFinalizeReq(this.finalizeReq);
+            this.nextRequest(nextRequest);
+        } else if (this.workoutNumbers.paceCount > 0) {
+            GetWorkoutPaceRequest nextRequest = new GetWorkoutPaceRequest(
+                    this.supportProvider,
+                    this.workoutNumbers,
+                    this.remainder,
+                    (short) 0,
+                    databaseId
+            );
+            nextRequest.setFinalizeReq(this.finalizeReq);
+            this.nextRequest(nextRequest);
+        } else if (this.workoutNumbers.segmentsCount > 0) {
+            GetWorkoutSwimSegmentsRequest nextRequest = new GetWorkoutSwimSegmentsRequest(
+                    this.supportProvider,
+                    this.workoutNumbers,
+                    this.remainder,
+                    (short) 0,
+                    databaseId
+            );
+            nextRequest.setFinalizeReq(this.finalizeReq);
+            this.nextRequest(nextRequest);
+        }  else {
+            new HuaweiWorkoutGbParser(getDevice(), getContext()).parseWorkout(databaseId);
+            supportProvider.downloadWorkoutGpsFiles(this.workoutNumbers.workoutNumber, databaseId, new Runnable() {
+                @Override
+                public void run() {
+                    if (!remainder.isEmpty()) {
+                        GetWorkoutTotalsRequest nextRequest = new GetWorkoutTotalsRequest(
+                                GetWorkoutTotalsRequest.this.supportProvider,
+                                remainder.remove(0),
+                                remainder
+                        );
+                        nextRequest.setFinalizeReq(GetWorkoutTotalsRequest.this.finalizeReq);
+                        // Cannot do this with nextRequest because it's in a callback
+                        try {
+                            nextRequest.doPerform();
+                        } catch (IOException e) {
+                            finalizeReq.handleException(new ResponseParseException("Cannot send next request", e));
+                        }
+                    } else {
+                        supportProvider.endOfWorkoutSync();
+                    }
+                }
+            });
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/Request.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/Request.java
new file mode 100644
index 0000000..aa14e3a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/Request.java
@@ -0,0 +1,377 @@
+/*  Copyright (C) 2024 Damien Gaignon, Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.widget.Toast;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiConstants;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiCrypto;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.OperationStatus;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+// Based on nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.Request
+
+/**
+ * Add capacity to :
+ *     - chain requests;
+ *     - use data from a past request;
+ *     - call a function after last request.
+ */
+
+public class Request {
+    private static final Logger LOG = LoggerFactory.getLogger(Request.class);
+
+    public static class RequestCreationException extends Exception {
+        public RequestCreationException(String message) {
+            super(message);
+        }
+
+        public RequestCreationException(HuaweiPacket.CryptoException e) {
+            super(e);
+        }
+
+        public RequestCreationException(String message, Exception e) {
+            super(message, e);
+        }
+    }
+
+    public static class ResponseParseException extends Exception {
+        public ResponseParseException(String message) {
+            super(message);
+        }
+
+        public ResponseParseException(Exception e) {
+            super(e);
+        }
+
+        public ResponseParseException(String message, Exception e) {
+            super(message, e);
+        }
+    }
+
+    public static class ResponseTypeMismatchException extends ResponseParseException {
+        public ResponseTypeMismatchException(HuaweiPacket a, Class<?> b) {
+            super("Response type mismatch, packet is of type " + a.getClass() + " but expected " + b);
+        }
+
+        public ResponseTypeMismatchException(HuaweiPacket a, Class<?> b, Class<?> c) {
+            super("Response type mismatch, packet is of type " + a.getClass() + " but expected " + b + " or " + c);
+        }
+    }
+
+    public static class WorkoutParseException extends ResponseParseException {
+        public WorkoutParseException(String message) {
+            super(message);
+        }
+    }
+
+    protected OperationStatus operationStatus = OperationStatus.INITIAL;
+    protected byte serviceId;
+    protected byte commandId;
+    protected HuaweiPacket receivedPacket = null;
+    protected HuaweiSupportProvider supportProvider;
+    protected HuaweiPacket.ParamsProvider paramsProvider;
+    private nodomain.freeyourgadget.gadgetbridge.service.btbr.TransactionBuilder builderBr;
+    private nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder builderLe;
+    // Be able to autostart a request after this one
+    protected Request nextRequest = null;
+    protected boolean isSelfQueue = false;
+    // Callback function to start after the request
+    protected RequestCallback finalizeReq = null;
+    // Stop chaining requests and clean support.inProgressRequests from these requests
+    protected boolean stopChain = false;
+    protected HuaweiCrypto huaweiCrypto = null;
+    protected boolean addToResponse = true;
+
+    private final Handler handler;
+    private final Runnable timeoutRunner;
+    private Integer timeout = null;
+
+    public static class RequestCallback {
+        protected HuaweiSupportProvider support = null;
+        public RequestCallback() {}
+        public RequestCallback(HuaweiSupportProvider supportProvider) {
+            support = supportProvider;
+        }
+        public void call() {}
+        public void call(Request request) {
+            call(); // To keep everything working as it was as well
+        }
+        public void handleException(ResponseParseException e) {
+            LOG.error("Callback request exception", e);
+        }
+        public void timeout(Request request) {
+            request.handleNext();
+        }
+    }
+
+    private Runnable getTimeoutRunnable() {
+        return () -> {
+            LOG.debug("Timeout on Service {} command {}", Integer.toHexString(this.serviceId & 0xff), Integer.toHexString(this.commandId & 0xff));
+            if (finalizeReq != null)
+                finalizeReq.timeout(this);
+            else
+                this.handleNext();
+        };
+    }
+
+    public Request(HuaweiSupportProvider supportProvider, nodomain.freeyourgadget.gadgetbridge.service.btbr.TransactionBuilder builder) {
+        this.supportProvider = supportProvider;
+        this.paramsProvider = supportProvider.getParamsProvider();
+        assert !supportProvider.isBLE();
+        this.builderBr = builder;
+
+        this.isSelfQueue = true;
+
+        this.handler = new Handler(Looper.getMainLooper());
+        this.timeoutRunner = getTimeoutRunnable();
+    }
+
+    public Request(HuaweiSupportProvider supportProvider, nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder builder) {
+        this.supportProvider = supportProvider;
+        this.paramsProvider = supportProvider.getParamsProvider();
+        assert supportProvider.isBLE();
+        this.builderLe = builder;
+
+        this.isSelfQueue = true;
+
+        this.handler = new Handler(Looper.getMainLooper());
+        this.timeoutRunner = getTimeoutRunnable();
+    }
+
+    public Request(HuaweiSupportProvider supportProvider) {
+        this.supportProvider = supportProvider;
+        this.paramsProvider = supportProvider.getParamsProvider();
+
+        if (!supportProvider.isBLE())
+            this.builderBr = supportProvider.createBrTransactionBuilder(getName());
+        else
+            this.builderLe = supportProvider.createLeTransactionBuilder(getName());
+
+        this.isSelfQueue = true;
+
+        this.handler = new Handler(Looper.getMainLooper());
+        this.timeoutRunner = getTimeoutRunnable();
+    }
+
+    protected boolean requestSupported() {
+        return true;
+    }
+
+    public void doPerform() throws IOException {
+        if (!requestSupported()) {
+            this.handleNext();
+            return;
+        }
+
+        if (this.addToResponse) {
+            supportProvider.addInProgressRequest(this);
+        }
+        try {
+            for (byte[] request : createRequest()) {
+                int mtu = paramsProvider.getMtu();
+                if (request.length >= mtu) {
+                    ByteBuffer buffer = ByteBuffer.wrap(request);
+                    byte[] data;
+                    while (buffer.hasRemaining()) {
+                        int delta = Math.min(mtu, buffer.remaining());
+                        data = new byte[delta];
+                        buffer.get(data, 0, delta);
+                        builderWrite(data);
+                    }
+                } else {
+                    builderWrite(request);
+                }
+            }
+            builderWait(paramsProvider.getInterval()); // Need to wait a little to let some requests end correctly i.e. Battery Level on reconnection to not print correctly
+            if (isSelfQueue) {
+                performConnected();
+            }
+        } catch (RequestCreationException e) {
+            // We cannot throw the RequestCreationException, so we throw an IOException
+            throw new IOException("Request could not be created", e);
+        }
+    }
+
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        return null;
+    }
+
+    protected void processResponse() throws ResponseParseException {}
+
+    public void handleResponse() {
+        // Stop timeout timer
+        this.handler.removeCallbacks(this.timeoutRunner);
+
+        try {
+            this.receivedPacket.parseTlv();
+        } catch (HuaweiPacket.ParseException e) {
+            LOG.error("Parse TLV exception", e);
+            if (finalizeReq != null)
+                finalizeReq.handleException(new ResponseParseException("Parse TLV exception", e));
+            return;
+        }
+        try {
+            processResponse();
+        } catch (ResponseParseException e) {
+            if (finalizeReq != null)
+                finalizeReq.handleException(e);
+            return;
+        }
+        handleNext();
+    }
+
+    public void handleNext() {
+        if (nextRequest != null && !stopChain) {
+            try {
+                nextRequest.doPerform();
+            } catch (IOException e) {
+                GB.toast(supportProvider.getContext(), "nextRequest failed", Toast.LENGTH_SHORT, GB.ERROR, e);
+                LOG.error("Next request failed", e);
+                if (finalizeReq != null)
+                    finalizeReq.handleException(new ResponseParseException("Next request failed", e));
+                return;
+            }
+        }
+        if (nextRequest == null || stopChain) {
+            operationStatus = OperationStatus.FINISHED;
+            if (finalizeReq != null) {
+                finalizeReq.call(this);
+            }
+        }
+        nextRequest = null;
+    }
+
+    public void setSelfQueue() {
+        isSelfQueue = true;
+    }
+
+    public Request nextRequest(Request req) {
+        if (req != null) {
+            nextRequest = req;
+            nextRequest.setSelfQueue();
+        }
+        return this;
+    }
+
+    public void stopChain(Request req) {
+        req.stopChain();
+        Request next = req.nextRequest;
+        if (next != null) {
+            next.stopChain(next);
+            supportProvider.removeInProgressRequests(next);
+        }
+    }
+
+    public void stopChain() {
+        stopChain = true;
+    }
+
+    /**
+     * Handler for responses from the device
+     * @param response The response packet
+     * @return True if this request handles this response, false otherwise
+     */
+    public boolean handleResponse(HuaweiPacket response) {
+        if (response.serviceId == serviceId && response.commandId == commandId) {
+            receivedPacket = response;
+            return true;
+        }
+        return false;
+    }
+
+    protected Context getContext() {
+        return supportProvider.getContext();
+    }
+
+    protected GBDevice getDevice() {
+        return supportProvider.getDevice();
+    }
+
+    public String getName() {
+        Class<?> thisClass = getClass();
+        while (thisClass.isAnonymousClass()) thisClass = thisClass.getSuperclass();
+        return thisClass.getSimpleName();
+    }
+
+    public void setFinalizeReq(RequestCallback finalizeReq) {
+        this.finalizeReq = finalizeReq;
+    }
+
+    private void builderWrite(byte[] data) {
+        if (!this.supportProvider.isBLE()) {
+            this.builderBr.write(data);
+        } else {
+            BluetoothGattCharacteristic characteristic = supportProvider
+                    .getLeCharacteristic(HuaweiConstants.UUID_CHARACTERISTIC_HUAWEI_WRITE);
+            this.builderLe.write(characteristic, data);
+        }
+    }
+
+    private void builderWait(int millis) {
+        if (!this.supportProvider.isBLE())
+            this.builderBr.wait(millis);
+        else
+            this.builderLe.wait(millis);
+    }
+
+    private void performConnected() throws IOException {
+        LOG.debug("Perform connected");
+
+        // Start the timeout timer
+        if (this.timeout != null)
+            handler.postDelayed(this.timeoutRunner, this.timeout);
+
+        if (!this.supportProvider.isBLE()) {
+            nodomain.freeyourgadget.gadgetbridge.service.btbr.Transaction transaction = this.builderBr.getTransaction();
+            this.supportProvider.performConnected(transaction);
+        } else {
+            nodomain.freeyourgadget.gadgetbridge.service.btle.Transaction transaction = this.builderLe.getTransaction();
+            this.supportProvider.performConnected(transaction);
+        }
+    }
+
+    public boolean autoRemoveFromResponseHandler() {
+        return true;
+    }
+
+    public void setupTimeoutUntilNext(int timeout) {
+        this.timeout = timeout;
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        // Stop timeout timer
+        this.handler.removeCallbacks(this.timeoutRunner);
+
+        super.finalize();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendAccountRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendAccountRequest.java
new file mode 100644
index 0000000..8ca704b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendAccountRequest.java
@@ -0,0 +1,56 @@
+/*  Copyright (C) 2024 Damien Gaignon
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.GBException;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiConstants;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket.CryptoException;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.AccountRelated;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class SendAccountRequest extends Request {
+    private static final Logger LOG = LoggerFactory.getLogger(SendAccountRequest.class);
+
+    public SendAccountRequest(HuaweiSupportProvider support) {
+        super(support);
+        this.serviceId = AccountRelated.id;
+        this.commandId = AccountRelated.SendAccountToDevice.id;
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        String account = GBApplication
+                .getDeviceSpecificSharedPrefs(supportProvider.getDevice().getAddress())
+                .getString(HuaweiConstants.PREF_HUAWEI_ACCOUNT, "").trim();
+        try {
+            return new AccountRelated.SendAccountToDevice.Request(paramsProvider, account).serialize();
+        } catch (CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+
+    @Override
+    protected void processResponse() throws ResponseParseException {
+        LOG.debug("handle Send Account to Device");
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendAppDelete.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendAppDelete.java
new file mode 100644
index 0000000..01bb819
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendAppDelete.java
@@ -0,0 +1,28 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.App;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class SendAppDelete extends Request {
+    private final String packageName;
+    public SendAppDelete(HuaweiSupportProvider support, String packageName) {
+        super(support);
+        this.serviceId = App.id;
+        this.commandId = App.AppDelete.id;
+        this.packageName = packageName;
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        try {
+            return new App.AppDelete.Request(this.paramsProvider, this.packageName).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+
+}
+
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendCameraRemoteSetupEvent.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendCameraRemoteSetupEvent.java
new file mode 100644
index 0000000..b06ab2a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendCameraRemoteSetupEvent.java
@@ -0,0 +1,54 @@
+/*  Copyright (C) 2024 Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.CameraRemote;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class SendCameraRemoteSetupEvent extends Request {
+
+    CameraRemote.CameraRemoteSetup.Request.Event event;
+
+    public SendCameraRemoteSetupEvent(HuaweiSupportProvider support, CameraRemote.CameraRemoteSetup.Request.Event event) {
+        super(support);
+        this.serviceId = CameraRemote.id;
+        this.commandId = CameraRemote.CameraRemoteSetup.id;
+        this.event = event;
+    }
+
+    @Override
+    protected boolean requestSupported() {
+        return supportProvider.getHuaweiCoordinator().supportsCameraRemote() && GBApplication.getDeviceSpecificSharedPrefs(supportProvider.getDevice().getAddress()).getBoolean(DeviceSettingsPreferenceConst.PREF_CAMERA_REMOTE, false);
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        try {
+            return new CameraRemote.CameraRemoteSetup.Request(
+                    supportProvider.getParamsProvider(),
+                    this.event
+            ).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendDeviceReportThreshold.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendDeviceReportThreshold.java
new file mode 100644
index 0000000..5ea2587
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendDeviceReportThreshold.java
@@ -0,0 +1,41 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiReportThreshold;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FitnessData;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class SendDeviceReportThreshold extends Request {
+    private static final Logger LOG = LoggerFactory.getLogger(SendDeviceReportThreshold.class);
+
+    public SendDeviceReportThreshold(HuaweiSupportProvider support) {
+        super(support);
+        this.serviceId = FitnessData.id;
+        this.commandId = FitnessData.DeviceReportThreshold.id;
+    }
+
+    @Override
+    protected boolean requestSupported() {
+        return supportProvider.getHuaweiCoordinator().supportsDeviceReportThreshold();
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        try {
+
+            return new FitnessData.DeviceReportThreshold.Request(paramsProvider, HuaweiReportThreshold.getReportThresholds()).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+
+    @Override
+    protected void processResponse() {
+        LOG.debug("handle Send Device Report Threshold");
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendDndAddRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendDndAddRequest.java
new file mode 100644
index 0000000..7a39439
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendDndAddRequest.java
@@ -0,0 +1,92 @@
+/*  Copyright (C) 2024 Damien Gaignon, Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import android.content.SharedPreferences;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiConstants;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiUtil;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.DeviceConfig;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+import nodomain.freeyourgadget.gadgetbridge.util.AlarmUtils;
+
+public class SendDndAddRequest extends Request {
+    private static final Logger LOG = LoggerFactory.getLogger(SendDndAddRequest.class);
+
+    public SendDndAddRequest(HuaweiSupportProvider support) {
+        super(support);
+        this.serviceId = DeviceConfig.id;
+        this.commandId = DeviceConfig.DndAddRequest.id;
+    }
+
+    @Override
+    protected boolean requestSupported() {
+        return supportProvider.getHuaweiCoordinator().supportsDoNotDisturb(supportProvider.getDevice());
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        SharedPreferences sharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(supportProvider.getDeviceMac());
+
+        int dndLiftWristType = sharedPrefs.getInt(HuaweiConstants.PREF_HUAWEI_DND_LIFT_WRIST_TYPE, 0x00); //Device allow content - accept activation
+        boolean statusDndLiftWrist = sharedPrefs.getBoolean(DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_LIFT_WRIST, false); //Activate on wrist lift with DND
+        String dndSwitch = sharedPrefs.getString(DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB, "off");
+        boolean dndEnable = !dndSwitch.equals("off");
+        String startStr = sharedPrefs.getString(DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_START, "00:00");
+        if (dndSwitch.equals("automatic")) startStr = "00:00";
+        byte[] start = HuaweiUtil.timeToByte(startStr);
+        String endStr = sharedPrefs.getString(DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_END, "23:59");
+        if (dndSwitch.equals("automatic")) endStr = "23:59";
+        byte[] end = HuaweiUtil.timeToByte(endStr);
+        int cycle = AlarmUtils.createRepetitionMask(
+                sharedPrefs.getBoolean(DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_MO, true),
+                sharedPrefs.getBoolean(DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_TU, true),
+                sharedPrefs.getBoolean(DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_WE, true),
+                sharedPrefs.getBoolean(DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_TH, true),
+                sharedPrefs.getBoolean(DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_FR, true),
+                sharedPrefs.getBoolean(DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_SA, true),
+                sharedPrefs.getBoolean(DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_SU, true)
+        );
+
+        try {
+            return new DeviceConfig.DndAddRequest(
+                    paramsProvider,
+                    dndEnable,
+                    start,
+                    end,
+                    cycle,
+                    statusDndLiftWrist ? dndLiftWristType : 0x00,
+                    supportProvider.getHuaweiCoordinator().supportsQueryDndLiftWristDisturbType()
+            ).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+
+    @Override
+    protected void processResponse() {
+        LOG.debug("handle DND Add");
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendDndDeleteRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendDndDeleteRequest.java
new file mode 100644
index 0000000..8e8accc
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendDndDeleteRequest.java
@@ -0,0 +1,55 @@
+/*  Copyright (C) 2024 Damien Gaignon, Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.DeviceConfig;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class SendDndDeleteRequest extends Request {
+    private static final Logger LOG = LoggerFactory.getLogger(SendDndDeleteRequest.class);
+
+    public SendDndDeleteRequest(HuaweiSupportProvider support) {
+        super(support);
+        this.serviceId = DeviceConfig.id;
+        this.commandId = DeviceConfig.DndDeleteRequest.id;
+    }
+
+    @Override
+    protected boolean requestSupported() {
+        return supportProvider.getHuaweiCoordinator().supportsDoNotDisturb(supportProvider.getDevice());
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        try {
+            return new DeviceConfig.DndDeleteRequest(paramsProvider).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+
+    @Override
+    protected void processResponse() {
+        LOG.debug("handle DND Delete");
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendEphemerisDataRequestResponse.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendEphemerisDataRequestResponse.java
new file mode 100644
index 0000000..1b39135
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendEphemerisDataRequestResponse.java
@@ -0,0 +1,33 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.EphemerisFileUpload;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class SendEphemerisDataRequestResponse extends Request {
+    final int responseCode;
+    final String fileName;
+    final int offset;
+
+
+    public SendEphemerisDataRequestResponse(HuaweiSupportProvider support, int responseCode, String fileName, int offset) {
+        super(support);
+        this.serviceId = EphemerisFileUpload.id;
+        this.commandId = EphemerisFileUpload.FileList.id;
+        this.responseCode = responseCode;
+        this.fileName = fileName;
+        this.offset = offset;
+        this.addToResponse = false;
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws Request.RequestCreationException {
+        try {
+            return new EphemerisFileUpload.DataRequest.DataRequestResponse(this.paramsProvider, this.responseCode, this.fileName, this.offset).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new Request.RequestCreationException(e);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendEphemerisFileConsultResponse.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendEphemerisFileConsultResponse.java
new file mode 100644
index 0000000..61015aa
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendEphemerisFileConsultResponse.java
@@ -0,0 +1,28 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.EphemerisFileUpload;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class SendEphemerisFileConsultResponse extends Request {
+    final int responseCode;
+
+    public SendEphemerisFileConsultResponse(HuaweiSupportProvider support, int responseCode) {
+        super(support);
+        this.serviceId = EphemerisFileUpload.id;
+        this.commandId = EphemerisFileUpload.FileConsult.id;
+        this.responseCode = responseCode;
+        this.addToResponse = false;
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        try {
+            return new EphemerisFileUpload.FileConsult.FileConsultResponse(this.paramsProvider, this.responseCode).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendEphemerisFileListResponse.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendEphemerisFileListResponse.java
new file mode 100644
index 0000000..7b06e12
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendEphemerisFileListResponse.java
@@ -0,0 +1,30 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.EphemerisFileUpload;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class SendEphemerisFileListResponse extends Request {
+    final int responseCode;
+    final String files;
+
+    public SendEphemerisFileListResponse(HuaweiSupportProvider support, int responseCode, String files) {
+        super(support);
+        this.serviceId = EphemerisFileUpload.id;
+        this.commandId = EphemerisFileUpload.FileList.id;
+        this.responseCode = responseCode;
+        this.files = files;
+        this.addToResponse = false;
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        try {
+            return new EphemerisFileUpload.FileList.FileListResponse(this.paramsProvider, this.responseCode, this.files).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendEphemerisFileStatusRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendEphemerisFileStatusRequest.java
new file mode 100644
index 0000000..c00e8ad
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendEphemerisFileStatusRequest.java
@@ -0,0 +1,38 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Ephemeris;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class SendEphemerisFileStatusRequest extends Request  {
+    private final Logger LOG = LoggerFactory.getLogger(SendEphemerisFileStatusRequest.class);
+
+    private final byte status;
+    public SendEphemerisFileStatusRequest(HuaweiSupportProvider support, byte status) {
+        super(support);
+        this.serviceId = Ephemeris.id;
+        this.commandId = Ephemeris.FileStatus.id;
+        this.status = status;
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws Request.RequestCreationException {
+        try {
+            return new Ephemeris.FileStatus.Request(this.paramsProvider, status).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new Request.RequestCreationException(e);
+        }
+    }
+
+    @Override
+    protected void processResponse() throws ResponseParseException {
+        if (!(receivedPacket instanceof Ephemeris.FileStatus.Response))
+            throw new ResponseTypeMismatchException(receivedPacket, Ephemeris.FileStatus.Response.class);
+        LOG.info("Ephemeris FileStatus Response code: {}", ((Ephemeris.FileStatus.Response) receivedPacket).responseCode);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendEphemerisFileUploadChunk.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendEphemerisFileUploadChunk.java
new file mode 100644
index 0000000..70081ad
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendEphemerisFileUploadChunk.java
@@ -0,0 +1,36 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.EphemerisFileUpload;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+
+public class SendEphemerisFileUploadChunk extends Request {
+    byte[] fileChunk;
+    short transferSize;
+    int packetCount;
+    public SendEphemerisFileUploadChunk(HuaweiSupportProvider support, byte[] fileChunk, short transferSize, int packetCount) {
+        super(support);
+        this.serviceId = EphemerisFileUpload.id;
+        this.commandId = EphemerisFileUpload.UploadData.id;
+        this.fileChunk = fileChunk;
+        this.transferSize = transferSize;
+        this.packetCount = packetCount;
+        this.addToResponse = false;
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        try {
+            return new EphemerisFileUpload.UploadData.FileNextChunkSend(this.paramsProvider).serializeFileChunk1c(
+                    fileChunk,
+                    transferSize,
+                    packetCount
+            );
+        } catch(HuaweiPacket.SerializeException e) {
+            throw new RequestCreationException(e.getMessage());
+        }
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendEphemerisFileUploadDoneResponse.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendEphemerisFileUploadDoneResponse.java
new file mode 100644
index 0000000..20a0400
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendEphemerisFileUploadDoneResponse.java
@@ -0,0 +1,28 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.EphemerisFileUpload;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class SendEphemerisFileUploadDoneResponse extends Request {
+    final int responseCode;
+
+    public SendEphemerisFileUploadDoneResponse(HuaweiSupportProvider support, int responseCode) {
+        super(support);
+        this.serviceId = EphemerisFileUpload.id;
+        this.commandId = EphemerisFileUpload.UploadDone.id;
+        this.responseCode = responseCode;
+        this.addToResponse = false;
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        try {
+            return new EphemerisFileUpload.UploadDone.UploadDoneResponse(this.paramsProvider, this.responseCode).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendEphemerisOperatorResponse.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendEphemerisOperatorResponse.java
new file mode 100644
index 0000000..1ba3b00
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendEphemerisOperatorResponse.java
@@ -0,0 +1,27 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Ephemeris;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class SendEphemerisOperatorResponse extends Request {
+    final int responseCode;
+    public SendEphemerisOperatorResponse(HuaweiSupportProvider support, int responseCode) {
+        super(support);
+        this.serviceId = Ephemeris.id;
+        this.commandId = Ephemeris.OperatorData.id;
+        this.responseCode = responseCode;
+        this.addToResponse = false;
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        try {
+            return new Ephemeris.OperatorData.OperatorResponse(this.paramsProvider, this.responseCode).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendEphemerisParameterConsultRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendEphemerisParameterConsultRequest.java
new file mode 100644
index 0000000..fe6855a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendEphemerisParameterConsultRequest.java
@@ -0,0 +1,38 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Ephemeris;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class SendEphemerisParameterConsultRequest extends Request  {
+    private Logger LOG = LoggerFactory.getLogger(SendEphemerisParameterConsultRequest.class);
+
+    public SendEphemerisParameterConsultRequest(HuaweiSupportProvider support) {
+        super(support);
+        this.serviceId = Ephemeris.id;
+        this.commandId = Ephemeris.ParameterConsult.id;
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws Request.RequestCreationException {
+        try {
+            return new Ephemeris.ParameterConsult.Request(this.paramsProvider).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new Request.RequestCreationException(e);
+        }
+    }
+
+    @Override
+    protected void processResponse() throws ResponseParseException {
+        if (!(receivedPacket instanceof Ephemeris.ParameterConsult.Response))
+            throw new ResponseTypeMismatchException(receivedPacket, Ephemeris.ParameterConsult.Response.class);
+
+        Ephemeris.ParameterConsult.Response resp = (Ephemeris.ParameterConsult.Response)receivedPacket;
+        supportProvider.getHuaweiEphemerisManager().handleParameterConsultResponse(resp.consultDeviceTime, resp.downloadVersion, resp.downloadTag);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendEphemerisSingleFileInfoResponse.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendEphemerisSingleFileInfoResponse.java
new file mode 100644
index 0000000..a1a8a11
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendEphemerisSingleFileInfoResponse.java
@@ -0,0 +1,32 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.EphemerisFileUpload;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class SendEphemerisSingleFileInfoResponse extends Request {
+    final int responseCode;
+    final int fileSize;
+    final short crc;
+
+    public SendEphemerisSingleFileInfoResponse(HuaweiSupportProvider support, int responseCode, int fileSize, short crc) {
+        super(support);
+        this.serviceId = EphemerisFileUpload.id;
+        this.commandId = EphemerisFileUpload.FileList.id;
+        this.responseCode = responseCode;
+        this.fileSize = fileSize;
+        this.crc = crc;
+        this.addToResponse = false;
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws Request.RequestCreationException {
+        try {
+            return new EphemerisFileUpload.QuerySingleFileInfo.QuerySingleFileInfoResponse(this.paramsProvider, this.responseCode, this.fileSize, this.crc).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new Request.RequestCreationException(e);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendExtendedAccountRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendExtendedAccountRequest.java
new file mode 100644
index 0000000..8068f59
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendExtendedAccountRequest.java
@@ -0,0 +1,65 @@
+/*  Copyright (C) 2024 Vitalii Tomin
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiConstants;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket.CryptoException;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.AccountRelated;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class SendExtendedAccountRequest extends Request {
+    private static final Logger LOG = LoggerFactory.getLogger(SendExtendedAccountRequest.class);
+
+    public SendExtendedAccountRequest(HuaweiSupportProvider support) {
+        super(support);
+        this.serviceId = AccountRelated.id;
+        this.commandId = AccountRelated.SendExtendedAccountToDevice.id;
+    }
+
+    @Override
+    protected boolean requestSupported() {
+        return supportProvider.getHuaweiCoordinator().supportsAccountJudgment() && supportProvider.getHuaweiCoordinator().supportsAccountSwitch();
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws Request.RequestCreationException {
+        String account = GBApplication
+                .getDeviceSpecificSharedPrefs(supportProvider.getDevice().getAddress())
+                .getString(HuaweiConstants.PREF_HUAWEI_ACCOUNT, "").trim();
+        try {
+            return new AccountRelated.SendExtendedAccountToDevice.Request(
+                    paramsProvider,
+                    supportProvider.getHuaweiCoordinator().supportsDiffAccountPairingOptimization(),
+                    account)
+                    .serialize();
+        } catch (CryptoException e) {
+            throw new Request.RequestCreationException(e);
+        }
+    }
+
+    @Override
+    protected void processResponse() throws Request.ResponseParseException {
+        LOG.debug("handle Send Extended Account to Device");
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendFactoryResetRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendFactoryResetRequest.java
new file mode 100644
index 0000000..7ff97b8
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendFactoryResetRequest.java
@@ -0,0 +1,50 @@
+/*  Copyright (C) 2024 Damien Gaignon, Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.DeviceConfig;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class SendFactoryResetRequest extends Request {
+    private static final Logger LOG = LoggerFactory.getLogger(SendFactoryResetRequest.class);
+
+    public SendFactoryResetRequest(HuaweiSupportProvider support) {
+        super(support);
+        this.serviceId = DeviceConfig.id;
+        this.commandId = DeviceConfig.FactoryResetRequest.id;
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        try {
+            return new DeviceConfig.FactoryResetRequest(paramsProvider).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+
+    @Override
+    protected void processResponse() {
+        LOG.debug("handle Factory Reset");
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendFileUploadAck.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendFileUploadAck.java
new file mode 100644
index 0000000..bb48ebd
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendFileUploadAck.java
@@ -0,0 +1,46 @@
+/*  Copyright (C) 2024 Vitalii Tomin
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FileUpload;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class SendFileUploadAck extends Request {
+    byte noEncryption = 0;
+    byte fileType = 1;
+    public SendFileUploadAck(HuaweiSupportProvider support, byte noEncryption, byte fileType) {
+        super(support);
+        this.serviceId = FileUpload.id;
+        this.commandId = FileUpload.FileUploadConsultAck.id;
+        this.noEncryption = noEncryption;
+        this.fileType = fileType;
+        this.addToResponse = false;
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        try {
+            return new FileUpload.FileUploadConsultAck.Request(this.paramsProvider, this.noEncryption, this.fileType).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendFileUploadChunk.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendFileUploadChunk.java
new file mode 100644
index 0000000..4f501fc
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendFileUploadChunk.java
@@ -0,0 +1,52 @@
+/*  Copyright (C) 2024 Vitalii Tomin
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FileUpload;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiUploadManager;
+
+public class SendFileUploadChunk extends Request {
+    HuaweiUploadManager huaweiUploadManager;
+    public SendFileUploadChunk(HuaweiSupportProvider support,
+                               HuaweiUploadManager watchfaceManager) {
+        super(support);
+        this.huaweiUploadManager = watchfaceManager;
+        this.serviceId = FileUpload.id;
+        this.commandId = FileUpload.FileNextChunkSend.id;
+        this.addToResponse = false;
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        try {
+            return new FileUpload.FileNextChunkSend(this.paramsProvider).serializeFileChunk(
+                    huaweiUploadManager.getFileUploadInfo().getCurrentChunk(),
+                    huaweiUploadManager.getFileUploadInfo().getCurrentUploadPosition(),
+                    huaweiUploadManager.getFileUploadInfo().getUnitSize(),
+                    huaweiUploadManager.getFileUploadInfo().getFileId(),
+                    huaweiUploadManager.getFileUploadInfo().getEncrypt()
+            );
+        } catch(HuaweiPacket.SerializeException e) {
+            throw new RequestCreationException(e.getMessage());
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendFileUploadComplete.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendFileUploadComplete.java
new file mode 100644
index 0000000..93d4876
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendFileUploadComplete.java
@@ -0,0 +1,47 @@
+/*  Copyright (C) 2024 Vitalii Tomin
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FileUpload;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class SendFileUploadComplete extends Request {
+
+    byte fileId = 0;
+    public SendFileUploadComplete(HuaweiSupportProvider support, byte fileId) {
+        super(support);
+
+        this.serviceId = FileUpload.id;
+        this.commandId = FileUpload.FileUploadResult.id;
+        this.fileId = fileId;
+        this.addToResponse = false;
+    }
+
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        try {
+            return new FileUpload.FileUploadResult.Request(this.paramsProvider, this.fileId).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendFileUploadHash.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendFileUploadHash.java
new file mode 100644
index 0000000..5a90af6
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendFileUploadHash.java
@@ -0,0 +1,50 @@
+/*  Copyright (C) 2024 Vitalii Tomin
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FileUpload;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiUploadManager;
+
+public class SendFileUploadHash extends Request{
+    HuaweiUploadManager huaweiUploadManager;
+    public SendFileUploadHash(HuaweiSupportProvider support,
+                              HuaweiUploadManager huaweiUploadManager) {
+        super(support);
+        this.huaweiUploadManager = huaweiUploadManager;
+        this.serviceId = FileUpload.id;
+        this.commandId = FileUpload.FileHashSend.id;
+        this.addToResponse = false;
+    }
+
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        try {
+            return new FileUpload.FileHashSend.Request(this.paramsProvider,
+                    huaweiUploadManager.getFileUploadInfo().getFileSHA256(),
+                    huaweiUploadManager.getFileUploadInfo().getFileId()
+            ).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendFileUploadInfo.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendFileUploadInfo.java
new file mode 100644
index 0000000..c5417e1
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendFileUploadInfo.java
@@ -0,0 +1,55 @@
+/*  Copyright (C) 2024 Vitalii Tomin
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FileUpload;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiUploadManager;
+
+public class SendFileUploadInfo extends Request{
+    HuaweiUploadManager huaweiUploadManager;
+    public SendFileUploadInfo(HuaweiSupportProvider support,
+                              HuaweiUploadManager huaweiUploadManager) {
+        super(support);
+        this.huaweiUploadManager = huaweiUploadManager;
+        this.serviceId = FileUpload.id;
+        this.commandId = FileUpload.FileInfoSend.id;
+        this.addToResponse = false;
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        try {
+            return new FileUpload.FileInfoSend.Request(this.paramsProvider,
+                    huaweiUploadManager.getFileUploadInfo().getFileSize(),
+                    huaweiUploadManager.getFileUploadInfo().getFileName(),
+                    huaweiUploadManager.getFileUploadInfo().getFileType(),
+                    huaweiUploadManager.getFileUploadInfo().getSrcPackage(),
+                    huaweiUploadManager.getFileUploadInfo().getDstPackage(),
+                    huaweiUploadManager.getFileUploadInfo().getSrcFingerprint(),
+                    huaweiUploadManager.getFileUploadInfo().getDstFingerprint()
+            ).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendFitnessGoalRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendFitnessGoalRequest.java
new file mode 100644
index 0000000..4121b15
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendFitnessGoalRequest.java
@@ -0,0 +1,68 @@
+/*  Copyright (C) 2024 Damien Gaignon
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FitnessData;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FitnessData.MotionGoal;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class SendFitnessGoalRequest extends Request {
+    private static final Logger LOG = LoggerFactory.getLogger(SendFitnessGoalRequest.class);
+
+    public SendFitnessGoalRequest(HuaweiSupportProvider support) {
+        super(support);
+        this.serviceId = FitnessData.id;
+        this.commandId = MotionGoal.id;
+    }
+
+    @Override
+    protected boolean requestSupported() {
+        return supportProvider.getHuaweiCoordinator().supportsMotionGoal();
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        try {
+            // Hardcoded values till interface for goal
+            int stepGoal = GBApplication.getPrefs().getInt(ActivityUser.PREF_USER_STEPS_GOAL, ActivityUser.defaultUserStepsGoal);
+            int calorieGoal = 0;
+            short durationGoal = 0;
+            return new MotionGoal.Request(paramsProvider,
+                    (byte)0x01,
+                    (byte)0x00,
+                    stepGoal,
+                    calorieGoal,
+                    durationGoal
+            ).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+
+    @Override
+    protected void processResponse() {
+        LOG.debug("handle Send Fitness Goal Request");
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendFitnessUserInfoRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendFitnessUserInfoRequest.java
new file mode 100644
index 0000000..bfbc87d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendFitnessUserInfoRequest.java
@@ -0,0 +1,86 @@
+/*  Copyright (C) 2024 Vitalii Tomin
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.time.LocalDate;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FitnessData;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FitnessData.UserInfo;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class SendFitnessUserInfoRequest extends Request {
+    private static final Logger LOG = LoggerFactory.getLogger(SendFitnessUserInfoRequest.class);
+
+    public SendFitnessUserInfoRequest(HuaweiSupportProvider support) {
+        super(support);
+        this.serviceId = FitnessData.id;
+        this.commandId = UserInfo.id;
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        try {
+            // Hardcoded values till interface for goal
+            ActivityUser activityUser = new ActivityUser();
+            byte gender = UserInfo.UserInfoData.GENDER_UNKNOWN;
+            if (activityUser.getGender() == ActivityUser.GENDER_FEMALE) {
+                gender = UserInfo.UserInfoData.GENDER_FEMALE;
+            } else if (activityUser.getGender() == ActivityUser.GENDER_MALE) {
+                gender = UserInfo.UserInfoData.GENDER_MALE;
+            }
+
+            LocalDate dateOfBirth = activityUser.getDateOfBirth();
+            int birthdayEncoded = dateOfBirth.getYear() << 16;
+            birthdayEncoded += dateOfBirth.getMonthValue() << 8;
+            birthdayEncoded += dateOfBirth.getDayOfMonth();
+
+            boolean unknownGenderSupported = supportProvider.getHuaweiCoordinator().supportsUnknownGender();
+            //TODO: discover Vo2Max capability and retrieve calculate and set proper values. Currently disabled.
+            boolean vo2Supported = false;
+            int vo2Max = 0;
+            int vo2Time = 0;
+            boolean precisionWeightSupported = supportProvider.getHuaweiCoordinator().supportsPrecisionWeight();
+
+            UserInfo.UserInfoData userInfoData = new UserInfo.UserInfoData(
+                    activityUser.getHeightCm(),
+                    activityUser.getWeightKg(),
+                    activityUser.getAge(),
+                    birthdayEncoded,
+                    gender,
+                    unknownGenderSupported,
+                    vo2Supported,
+                    vo2Max,
+                    vo2Time,
+                    precisionWeightSupported);
+
+            return new UserInfo.Request(paramsProvider, userInfoData).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+
+    @Override
+    protected void processResponse() {
+        LOG.debug("handle Send Fitness UserInfo Request");
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendGpsAndTimeToDeviceRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendGpsAndTimeToDeviceRequest.java
new file mode 100644
index 0000000..271299d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendGpsAndTimeToDeviceRequest.java
@@ -0,0 +1,52 @@
+/*  Copyright (C) 2024 Vitalii Tomin, Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import android.location.Location;
+
+import java.util.Calendar;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.GpsAndTime;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.webview.CurrentPosition;
+
+public class SendGpsAndTimeToDeviceRequest extends Request {
+
+    public SendGpsAndTimeToDeviceRequest(HuaweiSupportProvider support) {
+        super(support);
+        this.serviceId = GpsAndTime.id;
+        this.commandId = GpsAndTime.CurrentGPSRequest.id;
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        try {
+            Location location = new CurrentPosition().getLastKnownLocation();
+            return new GpsAndTime.CurrentGPSRequest(
+                    this.paramsProvider,
+                    (int) (Calendar.getInstance().getTime().getTime() / 1000L) - 60, // Backdating a bit seems to work better
+                    location.getLatitude(),
+                    location.getLongitude()
+            ).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendGpsDataRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendGpsDataRequest.java
new file mode 100644
index 0000000..fc8ff60
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendGpsDataRequest.java
@@ -0,0 +1,94 @@
+/*  Copyright (C) 2024 Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import android.location.Location;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.GpsAndTime;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class SendGpsDataRequest extends Request {
+    Location location;
+    Location lastLocation;
+    GpsAndTime.GpsParameters.Response gpsParametersResponse;
+
+    public SendGpsDataRequest(HuaweiSupportProvider support, Location location, Location lastLocation, GpsAndTime.GpsParameters.Response gpsParametersResponse) {
+        super(support);
+        this.serviceId = GpsAndTime.id;
+        this.commandId = GpsAndTime.GpsData.id;
+        this.location = location;
+        this.lastLocation = lastLocation;
+        this.gpsParametersResponse = gpsParametersResponse;
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        ArrayList<GpsAndTime.GpsData.Request.GpsDataContainer> gpsList = new ArrayList<>();
+        GpsAndTime.GpsData.Request.GpsDataContainer gpsData = new GpsAndTime.GpsData.Request.GpsDataContainer();
+
+        long timeOffset = 1000; // Default 1 second sample time
+        float distance = 0.0f;
+        if (lastLocation != null) {
+            timeOffset = location.getTime() - lastLocation.getTime();
+            distance = location.distanceTo(lastLocation);
+        }
+        if (this.gpsParametersResponse.supportsSpeed && location.hasSpeed()) {
+            gpsData.hasSpeed = true;
+            gpsData.speed = (short) location.getSpeed();
+        }
+        if (this.gpsParametersResponse.supportsAltitude && location.hasAltitude()) {
+            gpsData.hasAltitude = true;
+            gpsData.altitude = (short) location.getAltitude();
+        }
+        if (this.gpsParametersResponse.supportsLatLon) {
+            gpsData.hasLatLon = true;
+            gpsData.lat = location.getLatitude();
+            gpsData.lon = location.getLongitude();
+        }
+        if (this.gpsParametersResponse.supportsDirection && location.hasBearing()) {
+            gpsData.hasBearing = true;
+            gpsData.bearing = location.getBearing();
+        }
+        if (this.gpsParametersResponse.supportsPrecision && location.hasAccuracy()) {
+            gpsData.hasAccuracy = true;
+            gpsData.accuracy = location.getAccuracy();
+        }
+        if (this.gpsParametersResponse.supportsDistance) {
+            gpsData.hasDistance = true;
+            gpsData.distance = (int)distance;
+        }
+        long currentTime = System.currentTimeMillis();
+        gpsData.hasStartTime = true;
+        gpsData.startTime = (int)((currentTime - timeOffset) / 1000);
+        gpsData.hasEndTime = true;
+        gpsData.endTime = (int)(currentTime / 1000);
+
+        gpsList.add(gpsData);
+        try {
+            return new GpsAndTime.GpsData.Request(
+                    this.paramsProvider,
+                    gpsList
+            ).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendGpsStatusRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendGpsStatusRequest.java
new file mode 100644
index 0000000..8c0f9bb
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendGpsStatusRequest.java
@@ -0,0 +1,42 @@
+/*  Copyright (C) 2024 Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.GpsAndTime;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class SendGpsStatusRequest extends Request {
+
+    public SendGpsStatusRequest(HuaweiSupportProvider support) {
+        super(support);
+        this.serviceId = GpsAndTime.id;
+        this.commandId = GpsAndTime.GpsStatus.id;
+        this.addToResponse = false;
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        try {
+            return new GpsAndTime.GpsStatus.Request(this.paramsProvider).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendHeartRateZonesConfig.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendHeartRateZonesConfig.java
new file mode 100644
index 0000000..646ddeb
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendHeartRateZonesConfig.java
@@ -0,0 +1,57 @@
+/*  Copyright (C) 2024 Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HeartRateZonesConfig;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FitnessData;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class SendHeartRateZonesConfig extends Request {
+
+    public SendHeartRateZonesConfig(HuaweiSupportProvider support) {
+        super(support);
+        this.serviceId = FitnessData.id;
+        this.commandId = supportProvider.getHuaweiCoordinator().supportsExtendedHeartRateZones() ?
+                FitnessData.HeartRateZoneConfigPacket.id_extended :
+                FitnessData.HeartRateZoneConfigPacket.id_simple;
+    }
+
+    @Override
+    protected boolean requestSupported() {
+        return
+                !supportProvider.getHuaweiCoordinator().supportsTrack() && // In this case it uses P2P
+                supportProvider.getHuaweiCoordinator().supportsHeartRateZones();
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        try {
+            HeartRateZonesConfig heartRateZonesConfig = new HeartRateZonesConfig(HeartRateZonesConfig.TYPE_UPRIGHT, new ActivityUser().getAge());
+            if (supportProvider.getHuaweiCoordinator().supportsExtendedHeartRateZones()) {
+                return FitnessData.HeartRateZoneConfigPacket.Request.requestExtended(paramsProvider, heartRateZonesConfig).serialize();
+            } else {
+                return FitnessData.HeartRateZoneConfigPacket.Request.requestSimple(paramsProvider, heartRateZonesConfig).serialize();
+            }
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendMenstrualCapabilityRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendMenstrualCapabilityRequest.java
new file mode 100644
index 0000000..7565145
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendMenstrualCapabilityRequest.java
@@ -0,0 +1,56 @@
+/*  Copyright (C) 2024 Damien Gaignon
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Menstrual;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Menstrual.CapabilityRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class SendMenstrualCapabilityRequest extends Request {
+    private static final Logger LOG = LoggerFactory.getLogger(SendMenstrualCapabilityRequest.class);
+
+    public SendMenstrualCapabilityRequest(HuaweiSupportProvider support) {
+        super(support);
+        this.serviceId = Menstrual.id;
+        this.commandId = CapabilityRequest.id;
+    }
+
+    @Override
+    protected boolean requestSupported() {
+        return supportProvider.getHuaweiCoordinator().supportsMenstrual();
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        try {
+            return new CapabilityRequest(paramsProvider).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+
+    @Override
+    protected void processResponse() {
+        LOG.debug("handle Send Menstrual Capability");
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendMenstrualModifyTimeRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendMenstrualModifyTimeRequest.java
new file mode 100644
index 0000000..46197d3
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendMenstrualModifyTimeRequest.java
@@ -0,0 +1,51 @@
+/*  Copyright (C) 2024 Damien Gaignon
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Menstrual;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Menstrual.ModifyTime;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class SendMenstrualModifyTimeRequest extends Request {
+    private static final Logger LOG = LoggerFactory.getLogger(SendMenstrualModifyTimeRequest.class);
+
+    public SendMenstrualModifyTimeRequest(HuaweiSupportProvider support) {
+        super(support);
+        this.serviceId = Menstrual.id;
+        this.commandId = ModifyTime.id;
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        try {
+            return new ModifyTime.Request(paramsProvider, -1, 0).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+
+    @Override
+    protected void processResponse() {
+        LOG.debug("handle Send Menstrual Capability");
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendMusicOperation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendMusicOperation.java
new file mode 100644
index 0000000..1dca7ad
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendMusicOperation.java
@@ -0,0 +1,51 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.MusicControl;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class SendMusicOperation extends Request {
+    private static final Logger LOG = LoggerFactory.getLogger(SendMusicOperation.class);
+
+    private final int operation;
+    private final int playlistIndex;
+    private final String playlistName;
+    private final ArrayList<Integer> musicIds;
+
+
+    public SendMusicOperation(HuaweiSupportProvider support, int operation, int playlistIndex, String playlistName, ArrayList<Integer> musicIds) {
+        super(support);
+        this.serviceId = MusicControl.id;
+        this.commandId = MusicControl.MusicOperation.id;
+        this.operation = operation;
+        this.playlistIndex = playlistIndex;
+        this.playlistName = playlistName;
+        this.musicIds = musicIds;
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws Request.RequestCreationException {
+        try {
+            return new MusicControl.MusicOperation.Request(paramsProvider, operation, playlistIndex, playlistName, musicIds).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new Request.RequestCreationException(e);
+        }
+    }
+
+    @Override
+    protected void processResponse() throws ResponseTypeMismatchException {
+        LOG.debug("handle Music Operation");
+        if (!(receivedPacket instanceof MusicControl.MusicOperation.Response))
+            throw new Request.ResponseTypeMismatchException(receivedPacket, MusicControl.MusicOperation.Response.class);
+
+        MusicControl.MusicOperation.Response resp = (MusicControl.MusicOperation.Response) (receivedPacket);
+        supportProvider.getHuaweiMusicManager().onMusicOperationResponse(resp.resultCode, resp.operation, resp.playlistIndex, resp.playlistName, resp.musicIds);
+    }
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendNotificationRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendNotificationRequest.java
new file mode 100644
index 0000000..1eb3040
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendNotificationRequest.java
@@ -0,0 +1,108 @@
+/*  Copyright (C) 2024 Damien Gaignon, Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Notifications;
+import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.NotificationType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class SendNotificationRequest extends Request {
+
+    private static final Logger LOG = LoggerFactory.getLogger(SendNotificationRequest.class);
+
+    private HuaweiPacket packet;
+
+    public SendNotificationRequest(HuaweiSupportProvider support) {
+        super(support);
+        this.serviceId = Notifications.id;
+        this.commandId = Notifications.NotificationActionRequest.id;
+    }
+
+    public static byte getNotificationType(NotificationType type) {
+        switch (type.getGenericType()) {
+            case "generic":
+            case "generic_social":
+            case "generic_chat":
+                return Notifications.NotificationType.generic;
+            case "generic_email":
+                return Notifications.NotificationType.email;
+            default:
+                return Notifications.NotificationType.sms;
+        }
+    }
+
+
+    public void buildNotificationTLVFromNotificationSpec(NotificationSpec notificationSpec) {
+        String title;
+        if (notificationSpec.title != null)
+            title = notificationSpec.title;
+        else
+            title = notificationSpec.sourceName;
+
+        String body = notificationSpec.body;
+        if (body != null && body.length() > supportProvider.getHuaweiCoordinator().getContentLength()) {
+            body = notificationSpec.body.substring(0x0, supportProvider.getHuaweiCoordinator().getContentLength() - 0xD);
+            body += "...";
+        }
+        this.packet = new Notifications.NotificationActionRequest(
+                paramsProvider,
+                supportProvider.getNotificationId(),
+                getNotificationType(notificationSpec.type),
+                supportProvider.getHuaweiCoordinator().getContentFormat(),
+                title,
+                notificationSpec.sender,
+                body,
+                notificationSpec.sourceAppId
+        );
+    }
+
+    public void buildNotificationTLVFromCallSpec(CallSpec callSpec) {
+
+        this.packet = new Notifications.NotificationActionRequest(
+                paramsProvider,
+                supportProvider.getNotificationId(),
+                Notifications.NotificationType.call,
+                supportProvider.getHuaweiCoordinator().getContentFormat(),
+                callSpec.name,
+                callSpec.name,
+                callSpec.name,
+                null
+        );
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        try {
+            return this.packet.serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+
+    @Override
+    protected void processResponse() {
+        LOG.debug("handle Notification");
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendNotifyHeartRateCapabilityRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendNotifyHeartRateCapabilityRequest.java
new file mode 100644
index 0000000..8e05c1a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendNotifyHeartRateCapabilityRequest.java
@@ -0,0 +1,56 @@
+/*  Copyright (C) 2024 Damien Gaignon
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Workout;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Workout.NotifyHeartRate;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class SendNotifyHeartRateCapabilityRequest extends Request {
+    private static final Logger LOG = LoggerFactory.getLogger(SendNotifyHeartRateCapabilityRequest.class);
+
+    public SendNotifyHeartRateCapabilityRequest(HuaweiSupportProvider support) {
+        super(support);
+        this.serviceId = Workout.id;
+        this.commandId = NotifyHeartRate.id;
+    }
+
+    @Override
+    protected boolean requestSupported() {
+        return supportProvider.getHuaweiCoordinator().supportsWorkoutsTrustHeartRate();
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        try {
+            return new NotifyHeartRate.Request(paramsProvider).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+
+    @Override
+    protected void processResponse() {
+        LOG.debug("handle Send Workout HeartRate Capability Request");
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendNotifyRestHeartRateCapabilityRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendNotifyRestHeartRateCapabilityRequest.java
new file mode 100644
index 0000000..c0f99a1
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendNotifyRestHeartRateCapabilityRequest.java
@@ -0,0 +1,56 @@
+/*  Copyright (C) 2024 Damien Gaignon
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FitnessData;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FitnessData.NotifyRestHeartRate;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class SendNotifyRestHeartRateCapabilityRequest extends Request {
+    private static final Logger LOG = LoggerFactory.getLogger(SendNotifyRestHeartRateCapabilityRequest.class);
+
+    public SendNotifyRestHeartRateCapabilityRequest(HuaweiSupportProvider support) {
+        super(support);
+        this.serviceId = FitnessData.id;
+        this.commandId = NotifyRestHeartRate.id;
+    }
+
+    @Override
+    protected boolean requestSupported() {
+        return supportProvider.getHuaweiCoordinator().supportsFitnessRestHeartRate();
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        try {
+            return new NotifyRestHeartRate.Request(paramsProvider).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+
+    @Override
+    protected void processResponse() {
+        LOG.debug("handle Send Workout HeartRate Capability Request");
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendP2PCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendP2PCommand.java
new file mode 100644
index 0000000..f736584
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendP2PCommand.java
@@ -0,0 +1,59 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiConstants;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.AccountRelated;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.P2P;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class SendP2PCommand extends Request {
+    private static final Logger LOG = LoggerFactory.getLogger(SendAccountRequest.class);
+
+    private byte cmdId;
+    private short sequenceId;
+    private String srcPackage;
+    private String dstPackage;
+    private String srcFingerprint = null;
+    private String dstFingerprint = null;
+    private byte[] sendData = null;
+    private int sendCode = 0;
+
+    public SendP2PCommand(HuaweiSupportProvider support,
+                          byte cmdId,
+                          short sequenceId,
+                          String srcPackage,
+                          String dstPackage,
+                          String srcFingerprint,
+                          String dstFingerprint,
+                          byte[] sendData,
+                          int sendCode) {
+        super(support);
+        this.serviceId = P2P.id;
+        this.commandId = P2P.P2PCommand.id;
+
+        this.cmdId = cmdId;
+        this.sequenceId = sequenceId;
+        this.srcPackage = srcPackage;
+        this.dstPackage = dstPackage;
+        this.srcFingerprint = srcFingerprint;
+        this.dstFingerprint = dstFingerprint;
+        this.sendData = sendData;
+        this.sendCode = sendCode;
+        this.addToResponse = false;
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        try {
+            return new P2P.P2PCommand.Request(paramsProvider, this.cmdId, this.sequenceId, this.srcPackage, this.dstPackage, this.srcFingerprint, this.dstFingerprint, this.sendData, this.sendCode).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendRunPaceConfigRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendRunPaceConfigRequest.java
new file mode 100644
index 0000000..68d0329
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendRunPaceConfigRequest.java
@@ -0,0 +1,48 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiRunPaceConfig;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FitnessData;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class SendRunPaceConfigRequest extends Request {
+    private static final Logger LOG = LoggerFactory.getLogger(SendRunPaceConfigRequest.class);
+
+    public SendRunPaceConfigRequest(HuaweiSupportProvider support) {
+        super(support);
+        this.serviceId = FitnessData.id;
+        this.commandId = FitnessData.RunPaceConfig.id;
+    }
+
+    @Override
+    protected boolean requestSupported() {
+        return supportProvider.getHuaweiCoordinator().supportsRunPaceConfig();
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws Request.RequestCreationException {
+        try {
+            return new FitnessData.RunPaceConfig.Request(paramsProvider, new HuaweiRunPaceConfig()).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new Request.RequestCreationException(e);
+        }
+    }
+
+    @Override
+    protected void processResponse() {
+        if (receivedPacket instanceof FitnessData.RunPaceConfig.Response) {
+            if (((FitnessData.RunPaceConfig.Response) receivedPacket).isOk) {
+                LOG.debug("RunPace set");
+            } else {
+                LOG.warn("Error set RunPace");
+            }
+        } else {
+            LOG.error("Set RunPace response is not of type RunPaceConfig response");
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendSetContactsRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendSetContactsRequest.java
new file mode 100644
index 0000000..a56a23f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendSetContactsRequest.java
@@ -0,0 +1,49 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Contacts;
+import nodomain.freeyourgadget.gadgetbridge.model.Contact;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class SendSetContactsRequest extends Request {
+    private static final Logger LOG = LoggerFactory.getLogger(SendSetContactsRequest.class);
+
+    private final ArrayList<? extends Contact> contacts;
+    int maxCount;
+
+    public SendSetContactsRequest(HuaweiSupportProvider support,ArrayList<? extends Contact> contacts, int maxCount) {
+        super(support);
+        this.serviceId = Contacts.id;
+        this.commandId = Contacts.ContactsSet.id;
+        this.contacts = contacts;
+        this.maxCount = maxCount;
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        try {
+            return new Contacts.ContactsSet.Request(paramsProvider, this.contacts, this.maxCount).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+
+    @Override
+    protected void processResponse() {
+        if (receivedPacket instanceof Contacts.ContactsSet.Response) {
+            if (((Contacts.ContactsSet.Response) receivedPacket).isOk) {
+                LOG.debug("Contacts set");
+            } else {
+                LOG.warn("Error set contacts");
+            }
+        } else {
+            LOG.error("Set Contacts response is not of type ContactsSet response");
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendSetUpDeviceStatusRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendSetUpDeviceStatusRequest.java
new file mode 100644
index 0000000..6bcd397
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendSetUpDeviceStatusRequest.java
@@ -0,0 +1,58 @@
+/*  Copyright (C) 2024 Damien Gaignon
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.DeviceConfig;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.DeviceConfig.SetUpDeviceStatusRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class SendSetUpDeviceStatusRequest extends Request {
+    private static final Logger LOG = LoggerFactory.getLogger(SetUpDeviceStatusRequest.class);
+
+    public SendSetUpDeviceStatusRequest(HuaweiSupportProvider support) {
+        super(support);
+        this.serviceId = DeviceConfig.id;
+        this.commandId = SetUpDeviceStatusRequest.id;
+    }
+
+    @Override
+    protected boolean requestSupported() {
+        return supportProvider.getHuaweiCoordinator().supportsMultiDevice();
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        int relationShip = 1; // Hardcoded value for now - 1 = mainDevice
+        String deviceName = supportProvider.getDevice().getName(); 
+        try {
+            return new SetUpDeviceStatusRequest(paramsProvider, relationShip, deviceName).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+
+    @Override
+    protected void processResponse() {
+        LOG.debug("handle Set Up Device Status");
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendUploadMusicFileInfoResponse.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendUploadMusicFileInfoResponse.java
new file mode 100644
index 0000000..a6853d9
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendUploadMusicFileInfoResponse.java
@@ -0,0 +1,33 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.MusicControl;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class SendUploadMusicFileInfoResponse extends Request {
+    short songIndex;
+    String songName;
+    String songArtist;
+
+
+    public SendUploadMusicFileInfoResponse(HuaweiSupportProvider support, short songIndex, String songName, String songArtist) {
+        super(support);
+        this.serviceId = MusicControl.id;
+        this.commandId = MusicControl.UploadMusicFileInfo.id;
+        this.songIndex = songIndex;
+        this.songName = songName;
+        this.songArtist = songArtist;
+        this.addToResponse = false;
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws Request.RequestCreationException {
+        try {
+            return new MusicControl.UploadMusicFileInfo.UploadMusicFileInfoResponse(this.paramsProvider,  this.songIndex, this.songName, this.songArtist).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new Request.RequestCreationException(e);
+        }
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendWatchfaceConfirm.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendWatchfaceConfirm.java
new file mode 100644
index 0000000..8d8b08f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendWatchfaceConfirm.java
@@ -0,0 +1,46 @@
+/*  Copyright (C) 2024 Vitalii Tomin
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Watchface;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class SendWatchfaceConfirm extends Request {
+    private String fileName;
+    public SendWatchfaceConfirm(HuaweiSupportProvider support, String filename) {
+        super(support);
+        this.serviceId = Watchface.id;
+        this.commandId = Watchface.WatchfaceConfirm.id;
+        this.fileName = filename;
+        this.addToResponse = false;
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        try {
+            return new Watchface.WatchfaceConfirm.Request(this.paramsProvider, this.fileName ).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendWatchfaceOperation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendWatchfaceOperation.java
new file mode 100644
index 0000000..7fd9ff5
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendWatchfaceOperation.java
@@ -0,0 +1,46 @@
+/*  Copyright (C) 2024 Vitalii Tomin
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Watchface;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class SendWatchfaceOperation extends Request {
+    private String fileName;
+    private byte operation;
+    public SendWatchfaceOperation(HuaweiSupportProvider support, String filename, byte operation) {
+        super(support);
+        this.serviceId = Watchface.id;
+        this.commandId = Watchface.WatchfaceOperation.id;
+        this.fileName = filename;
+        this.operation = operation;
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        try {
+            return new Watchface.WatchfaceOperation.Request(this.paramsProvider, this.fileName, this.operation ).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendWeatherCurrentRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendWeatherCurrentRequest.java
new file mode 100644
index 0000000..9efc52c
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendWeatherCurrentRequest.java
@@ -0,0 +1,76 @@
+/*  Copyright (C) 2024 Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.SettingsActivity;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Weather;
+import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
+
+public class SendWeatherCurrentRequest extends Request {
+    Weather.Settings settings;
+    WeatherSpec weatherSpec;
+
+    public SendWeatherCurrentRequest(HuaweiSupportProvider support, Weather.Settings settings, WeatherSpec weatherSpec) {
+        super(support);
+        this.serviceId = Weather.id;
+        this.commandId = Weather.CurrentWeatherRequest.id;
+        this.settings = settings;
+        this.weatherSpec = weatherSpec;
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        Weather.HuaweiTemperatureFormat temperatureFormat = Weather.HuaweiTemperatureFormat.CELSIUS;
+        String unit = GBApplication.getPrefs().getString(SettingsActivity.PREF_MEASUREMENT_SYSTEM, GBApplication.getContext().getString(R.string.p_unit_metric));
+        if (unit.equals(GBApplication.getContext().getString(R.string.p_unit_imperial)))
+            temperatureFormat = Weather.HuaweiTemperatureFormat.FAHRENHEIT;
+        try {
+            Short pm25 = null;
+            Short aqi = null;
+            if (weatherSpec.airQuality != null) {
+                pm25 = (short) weatherSpec.airQuality.pm25; // TODO: does this work?
+                aqi = (short) weatherSpec.airQuality.aqi;
+            }
+            return new Weather.CurrentWeatherRequest(
+                    this.paramsProvider,
+                    settings,
+                    supportProvider.openWeatherMapConditionCodeToHuaweiIcon(weatherSpec.currentConditionCode),
+                    (byte) weatherSpec.windDirection,
+                    (byte) weatherSpec.windSpeedAsBeaufort(),
+                    (byte) (weatherSpec.todayMinTemp - 273),
+                    (byte) (weatherSpec.todayMaxTemp - 273),
+                    pm25,
+                    weatherSpec.location,
+                    (byte) (weatherSpec.currentTemp - 273),
+                    temperatureFormat,
+                    aqi,
+                    weatherSpec.timestamp,
+                    weatherSpec.uvIndex,
+                    "Gadgetbridge"
+                ).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendWeatherDeviceRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendWeatherDeviceRequest.java
new file mode 100644
index 0000000..acb6d35
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendWeatherDeviceRequest.java
@@ -0,0 +1,43 @@
+/*  Copyright (C) 2024 Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiConstants;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Weather;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class SendWeatherDeviceRequest extends Request {
+
+    public SendWeatherDeviceRequest(HuaweiSupportProvider support) {
+        super(support);
+        this.serviceId = Weather.id;
+        this.commandId = 0x04;
+        this.addToResponse = false; // Handled in async response
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        try {
+            return new Weather.WeatherDeviceRequest(paramsProvider, HuaweiConstants.RESULT_SUCCESS_INT).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendWeatherErrorRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendWeatherErrorRequest.java
new file mode 100644
index 0000000..9537138
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendWeatherErrorRequest.java
@@ -0,0 +1,62 @@
+/*  Copyright (C) 2024 Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Weather;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class SendWeatherErrorRequest extends Request {
+    private static final Logger LOG = LoggerFactory.getLogger(SendWeatherErrorRequest.class);
+
+    private final Weather.ErrorCode errorCode;
+
+    @Override
+    protected boolean requestSupported() {
+        return supportProvider.getHuaweiCoordinator().supportsWeatherErrorSimple() || supportProvider.getHuaweiCoordinator().supportsWeatherErrorExtended();
+    }
+
+    public SendWeatherErrorRequest(HuaweiSupportProvider support, Weather.ErrorCode errorCode) {
+        super(support);
+        this.serviceId = Weather.id;
+        if (supportProvider.getHuaweiCoordinator().supportsWeatherErrorExtended())
+            this.commandId = Weather.WeatherErrorExtended.id;
+        else
+            this.commandId = Weather.WeatherErrorSimple.id;
+        this.errorCode = errorCode;
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        try {
+            if (supportProvider.getHuaweiCoordinator().supportsWeatherErrorExtended()) {
+                Weather.WeatherErrorExtended.Request request = new Weather.WeatherErrorExtended.Request(paramsProvider, errorCode, false);
+                return request.serialize();
+            } else {
+                Weather.WeatherErrorSimple.Request request = new Weather.WeatherErrorSimple.Request(paramsProvider, errorCode);
+                return request.serialize();
+            }
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendWeatherExtendedSupportRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendWeatherExtendedSupportRequest.java
new file mode 100644
index 0000000..cd75090
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendWeatherExtendedSupportRequest.java
@@ -0,0 +1,59 @@
+/*  Copyright (C) 2024 Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Weather;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class SendWeatherExtendedSupportRequest extends Request {
+    private static final Logger LOG = LoggerFactory.getLogger(SendWeatherExtendedSupportRequest.class);
+
+    private Weather.Settings settings;
+
+    public SendWeatherExtendedSupportRequest(HuaweiSupportProvider support, Weather.Settings settings) {
+        super(support);
+        this.serviceId = Weather.id;
+        this.commandId = Weather.WeatherExtendedSupport.id;
+        this.settings = settings;
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        try {
+            return new Weather.WeatherExtendedSupport.Request(this.paramsProvider).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+
+    @Override
+    protected void processResponse() throws ResponseParseException {
+        if (receivedPacket instanceof Weather.WeatherExtendedSupport.Response) {
+            this.settings.timeSupported = ((Weather.WeatherExtendedSupport.Response) receivedPacket).timeSupported;
+            this.settings.sourceSupported = ((Weather.WeatherExtendedSupport.Response) receivedPacket).sourceSupported;
+            this.settings.weatherIconSupported = ((Weather.WeatherExtendedSupport.Response) receivedPacket).weatherIconSupported;
+        } else {
+            LOG.error("WeatherExtendedSupport response is not of type WeatherExtendedSupport response");
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendWeatherForecastRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendWeatherForecastRequest.java
new file mode 100644
index 0000000..11b2253
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendWeatherForecastRequest.java
@@ -0,0 +1,94 @@
+/*  Copyright (C) 2024 Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Weather;
+import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Weather.WeatherForecastData;
+
+public class SendWeatherForecastRequest extends Request {
+    Weather.Settings weatherSettings;
+    WeatherSpec weatherSpec;
+
+    public SendWeatherForecastRequest(HuaweiSupportProvider support, Weather.Settings weatherSettings, WeatherSpec weatherSpec) {
+        super(support);
+        this.serviceId = Weather.id;
+        this.commandId = Weather.WeatherForecastData.id;
+        this.weatherSettings = weatherSettings;
+        this.weatherSpec = weatherSpec;
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        int hourlyCount = Math.min(weatherSpec.hourly.size(), 24);
+        int dayCount = Math.min(weatherSpec.forecasts.size() + 1, 8); // We add today as well
+
+        ArrayList<WeatherForecastData.TimeData> timeDataArrayList = new ArrayList<>(hourlyCount);
+        ArrayList<WeatherForecastData.DayData> dayDataArrayList = new ArrayList<>(dayCount);
+        for (int i = 0; i < hourlyCount; i++) {
+            WeatherSpec.Hourly hourly = weatherSpec.hourly.get(i);
+            WeatherForecastData.TimeData timeData = new WeatherForecastData.TimeData();
+            timeData.timestamp = hourly.timestamp;
+            timeData.icon = supportProvider.openWeatherMapConditionCodeToHuaweiIcon(hourly.conditionCode);
+            timeData.temperature = (byte) (hourly.temp - 273);
+            timeDataArrayList.add(timeData);
+        }
+
+        // Add today as well
+        WeatherForecastData.DayData today = new WeatherForecastData.DayData();
+        today.timestamp = weatherSpec.timestamp;
+        today.icon = supportProvider.openWeatherMapConditionCodeToHuaweiIcon(weatherSpec.currentConditionCode);
+        today.highTemperature = (byte) (weatherSpec.todayMaxTemp - 273);
+        today.lowTemperature = (byte) (weatherSpec.todayMinTemp - 273);
+        today.sunriseTime = weatherSpec.sunRise;
+        today.sunsetTime = weatherSpec.sunSet;
+        today.moonRiseTime = weatherSpec.moonRise;
+        today.moonSetTime = weatherSpec.moonSet;
+        today.moonPhase = Weather.degreesToMoonPhase(weatherSpec.moonPhase);
+        dayDataArrayList.add(today);
+
+        for (int i = 0; i < dayCount - 1; i++) {
+            WeatherSpec.Daily daily = weatherSpec.forecasts.get(i);
+            WeatherForecastData.DayData dayData = new WeatherForecastData.DayData();
+            dayData.timestamp = weatherSpec.timestamp + (60*60*24 * (i + 1));
+            dayData.icon = supportProvider.openWeatherMapConditionCodeToHuaweiIcon(daily.conditionCode);
+            dayData.highTemperature = (byte) (daily.maxTemp - 273);
+            dayData.lowTemperature = (byte) (daily.minTemp - 273);
+            dayData.sunriseTime = daily.sunRise;
+            dayData.sunsetTime = daily.sunSet;
+            dayData.moonRiseTime = daily.moonRise;
+            dayData.moonSetTime = daily.moonSet;
+            dayData.moonPhase = Weather.degreesToMoonPhase(daily.moonPhase);
+            dayDataArrayList.add(dayData);
+        }
+        try {
+            return new WeatherForecastData.Request(
+                    this.paramsProvider,
+                    this.weatherSettings,
+                    timeDataArrayList,
+                    dayDataArrayList
+            ).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendWeatherStartRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendWeatherStartRequest.java
new file mode 100644
index 0000000..87ba606
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendWeatherStartRequest.java
@@ -0,0 +1,69 @@
+/*  Copyright (C) 2024 Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import android.widget.Toast;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Weather;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class SendWeatherStartRequest extends Request {
+    private static final Logger LOG = LoggerFactory.getLogger(SendWeatherStartRequest.class);
+
+    public int response = -1;
+    private Weather.Settings weatherSettings;
+
+    public SendWeatherStartRequest(HuaweiSupportProvider support, Weather.Settings weatherSettings) {
+        super(support);
+        this.serviceId = Weather.id;
+        this.commandId = Weather.WeatherStart.id;
+        this.weatherSettings = weatherSettings;
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        try {
+            return new Weather.WeatherStart.Request(this.paramsProvider).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+
+    @Override
+    protected void processResponse() throws ResponseParseException {
+        if (receivedPacket instanceof Weather.WeatherStart.Response) {
+            if (!((Weather.WeatherStart.Response) receivedPacket).success) {
+                this.stopChain();
+                GB.toast(supportProvider.getContext(), "Received non-ok status for WeatherStart response", Toast.LENGTH_SHORT, GB.INFO);
+                LOG.info("Received non-ok status for WeatherStart response");
+            } else {
+                weatherSettings.weatherSupported = true;
+            }
+        } else {
+            this.stopChain();
+            GB.toast(supportProvider.getContext(), "WeatherStart response is not of type WeatherStart response", Toast.LENGTH_SHORT, GB.ERROR);
+            LOG.error("WeatherStart response is not of type WeatherStart response");
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendWeatherSunMoonSupportRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendWeatherSunMoonSupportRequest.java
new file mode 100644
index 0000000..ee68842
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendWeatherSunMoonSupportRequest.java
@@ -0,0 +1,58 @@
+/*  Copyright (C) 2024 Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Weather;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class SendWeatherSunMoonSupportRequest extends Request {
+    private static final Logger LOG = LoggerFactory.getLogger(SendWeatherSunMoonSupportRequest.class);
+
+    private Weather.Settings settings;
+
+    public SendWeatherSunMoonSupportRequest(HuaweiSupportProvider support, Weather.Settings settings) {
+        super(support);
+        this.serviceId = Weather.id;
+        this.commandId = Weather.WeatherSunMoonSupport.id;
+        this.settings = settings;
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        try {
+            return new Weather.WeatherSunMoonSupport.Request(this.paramsProvider).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+
+    @Override
+    protected void processResponse() throws ResponseParseException {
+        if (receivedPacket instanceof Weather.WeatherSunMoonSupport.Response) {
+            this.settings.sunRiseSetSupported = ((Weather.WeatherSunMoonSupport.Response) receivedPacket).sunRiseSetSupported;
+            this.settings.moonPhaseSupported = ((Weather.WeatherSunMoonSupport.Response) receivedPacket).moonPhaseSupported;
+        } else {
+            LOG.error("WeatherExtendedSupport response is not of type WeatherExtendedSupport response");
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendWeatherSupportRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendWeatherSupportRequest.java
new file mode 100644
index 0000000..f06ca92
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendWeatherSupportRequest.java
@@ -0,0 +1,64 @@
+/*  Copyright (C) 2024 Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Weather;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class SendWeatherSupportRequest extends Request {
+    private static final Logger LOG = LoggerFactory.getLogger(SendWeatherSupportRequest.class);
+
+    private Weather.Settings settings;
+
+    public SendWeatherSupportRequest(HuaweiSupportProvider support, Weather.Settings settings) {
+        super(support);
+        this.serviceId = Weather.id;
+        this.commandId = Weather.WeatherSupport.id;
+        this.settings = settings;
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        try {
+            return new Weather.WeatherSupport.Request(this.paramsProvider).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+
+    @Override
+    protected void processResponse() throws ResponseParseException {
+        if (receivedPacket instanceof Weather.WeatherSupport.Response) {
+            this.settings.weatherSupported = ((Weather.WeatherSupport.Response) receivedPacket).weatherSupported;
+            this.settings.windSupported = ((Weather.WeatherSupport.Response) receivedPacket).windSupported;
+            this.settings.pm25Supported = ((Weather.WeatherSupport.Response) receivedPacket).pm25Supported;
+            this.settings.temperatureSupported = ((Weather.WeatherSupport.Response) receivedPacket).temperatureSupported;
+            this.settings.locationNameSupported = ((Weather.WeatherSupport.Response) receivedPacket).locationNameSupported;
+            this.settings.currentTemperatureSupported = ((Weather.WeatherSupport.Response) receivedPacket).currentTemperatureSupported;
+            this.settings.unitSupported = ((Weather.WeatherSupport.Response) receivedPacket).unitSupported;
+            this.settings.airQualityIndexSupported = ((Weather.WeatherSupport.Response) receivedPacket).airQualityIndexSupported;
+        } else {
+            LOG.error("WeatherSupport response is not of type WeatherSupport response");
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendWeatherUnitRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendWeatherUnitRequest.java
new file mode 100644
index 0000000..fec3962
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendWeatherUnitRequest.java
@@ -0,0 +1,49 @@
+/*  Copyright (C) 2024 Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.SettingsActivity;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Weather;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class SendWeatherUnitRequest extends Request {
+
+    public SendWeatherUnitRequest(HuaweiSupportProvider support) {
+        super(support);
+        this.serviceId = Weather.id;
+        this.commandId = Weather.WeatherUnitRequest.id;
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        Weather.HuaweiTemperatureFormat temperatureFormat = Weather.HuaweiTemperatureFormat.CELSIUS;
+        String unit = GBApplication.getPrefs().getString(SettingsActivity.PREF_MEASUREMENT_SYSTEM, GBApplication.getContext().getString(R.string.p_unit_metric));
+        if (unit.equals(GBApplication.getContext().getString(R.string.p_unit_imperial)))
+            temperatureFormat = Weather.HuaweiTemperatureFormat.FAHRENHEIT;
+        try {
+            return new Weather.WeatherUnitRequest(this.paramsProvider, temperatureFormat).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SetActivateOnLiftRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SetActivateOnLiftRequest.java
new file mode 100644
index 0000000..6b3156e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SetActivateOnLiftRequest.java
@@ -0,0 +1,60 @@
+/*  Copyright (C) 2024 Damien Gaignon, Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.DeviceConfig;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class SetActivateOnLiftRequest extends Request {
+    private static final Logger LOG = LoggerFactory.getLogger(SetActivateOnLiftRequest.class);
+
+    public SetActivateOnLiftRequest(HuaweiSupportProvider support) {
+        super(support);
+        this.serviceId = DeviceConfig.id;
+        this.commandId = DeviceConfig.ActivateOnLiftRequest.id;
+    }
+
+    @Override
+    protected boolean requestSupported() {
+        return supportProvider.getHuaweiCoordinator().supportsActivateOnLift();
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        boolean activate = GBApplication
+            .getDeviceSpecificSharedPrefs(supportProvider.getDevice().getAddress())
+            .getBoolean(DeviceSettingsPreferenceConst.PREF_LIFTWRIST_NOSHED, false);
+        try {
+            return new DeviceConfig.ActivateOnLiftRequest(paramsProvider, activate).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+
+    @Override
+    protected void processResponse() {
+        LOG.debug("handle Set Activate On Rotate");
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SetActivityReminderRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SetActivityReminderRequest.java
new file mode 100644
index 0000000..45f13fb
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SetActivityReminderRequest.java
@@ -0,0 +1,86 @@
+/*  Copyright (C) 2024 Damien Gaignon, Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import android.content.SharedPreferences;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiUtil;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FitnessData;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+import nodomain.freeyourgadget.gadgetbridge.util.AlarmUtils;
+
+public class SetActivityReminderRequest extends Request {
+    private static final Logger LOG = LoggerFactory.getLogger(SetActivityReminderRequest.class);
+
+    public SetActivityReminderRequest(HuaweiSupportProvider support) {
+        super(support);
+        this.serviceId = FitnessData.id;
+        this.commandId = FitnessData.ActivityReminder.id;
+    }
+
+    @Override
+    protected boolean requestSupported() {
+        return supportProvider.getHuaweiCoordinator().supportsActivityReminder();
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        SharedPreferences sharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(supportProvider.getDeviceMac());
+
+        boolean longsitSwitch = sharedPrefs.getBoolean(DeviceSettingsPreferenceConst.PREF_INACTIVITY_ENABLE, false);
+        String longsitInterval = sharedPrefs.getString(DeviceSettingsPreferenceConst.PREF_INACTIVITY_THRESHOLD, "60");
+        String longsitStart = sharedPrefs.getString(DeviceSettingsPreferenceConst.PREF_INACTIVITY_START, "06:00");
+        String longsitEnd = sharedPrefs.getString(DeviceSettingsPreferenceConst.PREF_INACTIVITY_END, "23:00");
+        byte[] start = HuaweiUtil.timeToByte(longsitStart);
+        byte[] end = HuaweiUtil.timeToByte(longsitEnd);
+        int cycle = AlarmUtils.createRepetitionMask(
+                sharedPrefs.getBoolean(DeviceSettingsPreferenceConst.PREF_INACTIVITY_MO, false),
+                sharedPrefs.getBoolean(DeviceSettingsPreferenceConst.PREF_INACTIVITY_TU, false),
+                sharedPrefs.getBoolean(DeviceSettingsPreferenceConst.PREF_INACTIVITY_WE, false),
+                sharedPrefs.getBoolean(DeviceSettingsPreferenceConst.PREF_INACTIVITY_TH, false),
+                sharedPrefs.getBoolean(DeviceSettingsPreferenceConst.PREF_INACTIVITY_FR, false),
+                sharedPrefs.getBoolean(DeviceSettingsPreferenceConst.PREF_INACTIVITY_SA, false),
+                sharedPrefs.getBoolean(DeviceSettingsPreferenceConst.PREF_INACTIVITY_SU, false)
+        );
+
+        try {
+            return new FitnessData.ActivityReminder.Request(
+                    paramsProvider,
+                    longsitSwitch,
+                    (byte) Integer.parseInt(longsitInterval),
+                    start,
+                    end,
+                    (byte) cycle
+            ).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+
+    @Override
+    protected void processResponse() {
+        LOG.debug("handle Set Activity Reminder");
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SetAudioModeRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SetAudioModeRequest.java
new file mode 100644
index 0000000..4f21025
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SetAudioModeRequest.java
@@ -0,0 +1,41 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Earphones;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class SetAudioModeRequest extends Request {
+
+    public SetAudioModeRequest(HuaweiSupportProvider supportProvider) {
+        super(supportProvider);
+        this.serviceId = Earphones.id;
+        this.commandId = Earphones.SetAudioModeRequest.id;
+        this.addToResponse = false; // Response with different command ID
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        try {
+            String audioMode = GBApplication
+                    .getDeviceSpecificSharedPrefs(this.getDevice().getAddress())
+                    .getString(DeviceSettingsPreferenceConst.PREF_HUAWEI_FREEBUDS_AUDIOMODE, "off");
+            byte mode = 0; // Off by default
+            switch (audioMode) {
+                case "anc":
+                    mode = 1;
+                    break;
+                case "transparency":
+                    mode = 2;
+                    break;
+                default:
+            }
+            return new Earphones.SetAudioModeRequest(this.paramsProvider, mode).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SetAutomaticHeartrateRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SetAutomaticHeartrateRequest.java
new file mode 100644
index 0000000..39f6fd3
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SetAutomaticHeartrateRequest.java
@@ -0,0 +1,55 @@
+/*  Copyright (C) 2024 Damien Gaignon, Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FitnessData;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class SetAutomaticHeartrateRequest extends Request {
+    private static final Logger LOG = LoggerFactory.getLogger(SetAutomaticHeartrateRequest.class);
+
+    public SetAutomaticHeartrateRequest(HuaweiSupportProvider support) {
+        super(support);
+        this.serviceId = FitnessData.id;
+        this.commandId = FitnessData.EnableAutomaticHeartrate.id;
+        this.addToResponse = false;
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        boolean automaticHeartrateEnabled = GBApplication
+                .getDeviceSpecificSharedPrefs(supportProvider.getDevice().getAddress())
+                .getBoolean(DeviceSettingsPreferenceConst.PREF_HEARTRATE_AUTOMATIC_ENABLE, false);
+        if (automaticHeartrateEnabled)
+            LOG.info("Attempting to enable automatic heartrate");
+        else
+            LOG.info("Attempting to disable automatic heartrate");
+        try {
+            return new FitnessData.EnableAutomaticHeartrate.Request(paramsProvider, automaticHeartrateEnabled).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SetAutomaticSpoRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SetAutomaticSpoRequest.java
new file mode 100644
index 0000000..1bea25d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SetAutomaticSpoRequest.java
@@ -0,0 +1,55 @@
+/*  Copyright (C) 2024 Damien Gaignon, Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FitnessData;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class SetAutomaticSpoRequest extends Request {
+    private static final Logger LOG = LoggerFactory.getLogger(SetAutomaticSpoRequest.class);
+
+    public SetAutomaticSpoRequest(HuaweiSupportProvider support) {
+        super(support);
+        this.serviceId = FitnessData.id;
+        this.commandId = FitnessData.EnableAutomaticSpo.id;
+        this.addToResponse = false;
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        boolean automaticSpoEnabled = GBApplication
+                .getDeviceSpecificSharedPrefs(supportProvider.getDevice().getAddress())
+                .getBoolean(DeviceSettingsPreferenceConst.PREF_SPO_AUTOMATIC_ENABLE, false);
+        if (automaticSpoEnabled)
+            LOG.info("Attempting to enable automatic SpO");
+        else
+            LOG.info("Attempting to disable automatic SpO");
+        try {
+            return new FitnessData.EnableAutomaticSpo.Request(paramsProvider, automaticSpoEnabled).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SetDateFormatRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SetDateFormatRequest.java
new file mode 100644
index 0000000..8487a4b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SetDateFormatRequest.java
@@ -0,0 +1,83 @@
+/*  Copyright (C) 2024 Damien Gaignon, Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.DeviceConfig;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.DeviceConfig.DateFormat;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class SetDateFormatRequest extends Request {
+    private static final Logger LOG = LoggerFactory.getLogger(SetDateFormatRequest.class);
+
+    public SetDateFormatRequest(HuaweiSupportProvider support) {
+        super(support);
+        this.serviceId = DeviceConfig.id;
+        this.commandId = DateFormat.id;
+    }
+
+    @Override
+    protected boolean requestSupported() {
+        return supportProvider.getHuaweiCoordinator().supportsDateFormat();
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        int time = DeviceConfig.Time.hours12;
+        int date;
+        String timeFormat = GBApplication
+            .getDeviceSpecificSharedPrefs(supportProvider.getDevice().getAddress())
+            .getString(DeviceSettingsPreferenceConst.PREF_TIMEFORMAT, "auto");
+        if (timeFormat.equals("auto")) {
+            if (android.text.format.DateFormat.is24HourFormat(GBApplication.getContext()))
+                time = DeviceConfig.Time.hours24;
+        } else if (timeFormat.equals("24h")) {
+            time = DeviceConfig.Time.hours24;
+        }
+        String dateFormat = GBApplication
+            .getDeviceSpecificSharedPrefs(supportProvider.getDevice().getAddress())
+            .getString(DeviceSettingsPreferenceConst.PREF_DATEFORMAT, "MM/dd/yyyy");
+        switch (dateFormat) {
+            case "MM/dd/yyyy":
+                date = DeviceConfig.Date.monthFirst;
+                break;
+            case "dd.MM.yyyy":
+            case "dd/MM/yyyy":
+                date = DeviceConfig.Date.dayFirst;
+                break;
+            default:
+                date = DeviceConfig.Date.yearFirst;
+        }
+        try {
+            return new DateFormat.Request(paramsProvider, (byte) date, (byte) time).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+
+    @Override
+    protected void processResponse() {
+        LOG.debug("handle Set Date Format");
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SetDisconnectNotification.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SetDisconnectNotification.java
new file mode 100644
index 0000000..198fd72
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SetDisconnectNotification.java
@@ -0,0 +1,60 @@
+/*  Copyright (C) 2024 Damien Gaignon, Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import android.content.SharedPreferences;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.DisconnectNotification;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class SetDisconnectNotification extends Request {
+    private static final Logger LOG = LoggerFactory.getLogger(SetDisconnectNotification.class);
+
+    public SetDisconnectNotification(HuaweiSupportProvider support) {
+        super(support);
+        this.serviceId = DisconnectNotification.id;
+        this.commandId = DisconnectNotification.DisconnectNotificationSetting.id;
+        this.addToResponse = false;
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        SharedPreferences sharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(supportProvider.getDeviceMac());
+        boolean notificationEnable = sharedPrefs.getBoolean(DeviceSettingsPreferenceConst.PREF_DISCONNECTNOTIF_NOSHED, true);
+        if (notificationEnable) {
+            LOG.info("Attempting to enable disconnect notification");
+        } else {
+            LOG.info("Attempting to disable disconnect notification");
+        }
+        try {
+            return new DisconnectNotification.DisconnectNotificationSetting.Request(
+                    paramsProvider,
+                    notificationEnable
+            ).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SetLanguageSettingRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SetLanguageSettingRequest.java
new file mode 100644
index 0000000..125b4db
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SetLanguageSettingRequest.java
@@ -0,0 +1,81 @@
+/*  Copyright (C) 2024 Damien Gaignon, Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import java.util.Locale;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.SettingsActivity;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.LocaleConfig;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.LocaleConfig.SetLanguageSetting;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class SetLanguageSettingRequest extends Request {
+    private static final Logger LOG = LoggerFactory.getLogger(SetLanguageSettingRequest.class);
+
+    public SetLanguageSettingRequest(HuaweiSupportProvider support) {
+        super(support);
+        this.serviceId = LocaleConfig.id;
+        this.commandId = SetLanguageSetting.id;
+    }
+
+    @Override
+    protected boolean requestSupported() {
+        return supportProvider.getHuaweiCoordinator().supportsLanguageSetting();
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        String localeString = GBApplication
+            .getDeviceSpecificSharedPrefs(supportProvider.getDevice().getAddress())
+            .getString(DeviceSettingsPreferenceConst.PREF_LANGUAGE, "auto");
+        if (localeString == null || localeString.equals("auto")) {
+            String language = Locale.getDefault().getLanguage();
+            String country = Locale.getDefault().getCountry();
+            if (country.equals("")) {
+                country = language;
+            }
+            localeString = language + "-" + country.toUpperCase();
+        } else {
+            localeString = localeString.replace("_", "-");
+        }
+        LOG.debug("localeString: " + localeString);
+        String measurementString = GBApplication
+            .getPrefs()
+            .getString(SettingsActivity.PREF_MEASUREMENT_SYSTEM, getContext().getString(R.string.p_unit_metric));
+        LOG.debug("measurementString: " + measurementString);
+        byte measurement = measurementString.equals("metric") ? LocaleConfig.MeasurementSystem.metric : LocaleConfig.MeasurementSystem.imperial;
+        try {
+            return new SetLanguageSetting(paramsProvider, localeString.getBytes(StandardCharsets.UTF_8), measurement).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+
+    @Override
+    protected void processResponse() {
+        LOG.debug("handle Set Locale");
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SetMediumToStrengthThresholdRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SetMediumToStrengthThresholdRequest.java
new file mode 100644
index 0000000..a35bc47
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SetMediumToStrengthThresholdRequest.java
@@ -0,0 +1,79 @@
+/*  Copyright (C) 2024 Damien Gaignon, Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FitnessData;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FitnessData.MediumToStrengthThreshold;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class SetMediumToStrengthThresholdRequest extends Request {
+    private static final Logger LOG = LoggerFactory.getLogger(SetMediumToStrengthThresholdRequest.class);
+
+    public SetMediumToStrengthThresholdRequest(HuaweiSupportProvider support) {
+        super(support);
+        this.serviceId = FitnessData.id;
+        this.commandId = MediumToStrengthThreshold.id;
+    }
+
+    @Override
+    protected boolean requestSupported() {
+        return supportProvider.getHuaweiCoordinator().supportsFitnessThresholdValue();
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        try {
+            //Hardcoded value till interface enable threshold values
+            if(supportProvider.getHuaweiCoordinator().supportsFitnessThresholdValueV2()) {
+                return new MediumToStrengthThreshold.Request(paramsProvider,
+                        (byte) 0x6E,
+                        (byte) 0x3C,
+                        (byte) 0x19,
+                        (byte) 0x58,
+                        (byte) 0x01,
+                        (byte) 0x01,
+                        0x28,
+                        0x28
+                ).serialize();
+            } else {
+                return new MediumToStrengthThreshold.Request(paramsProvider,
+                        (byte) 0x6E,
+                        (byte) 0x3C,
+                        (byte) 0x05,
+                        (byte) 0x40,
+                        (byte) 0x50,
+                        (byte) 0x03,
+                        -1,
+                        -1
+                ).serialize();
+            }
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+
+    @Override
+    protected void processResponse() {
+        LOG.debug("handle Set Activate On Rotate");
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SetMusicRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SetMusicRequest.java
new file mode 100644
index 0000000..30a7ceb
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SetMusicRequest.java
@@ -0,0 +1,103 @@
+/*  Copyright (C) 2024 Damien Gaignon, Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import android.content.Context;
+import android.media.AudioManager;
+import android.media.session.PlaybackState;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.MusicControl;
+import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class SetMusicRequest extends Request {
+    private static final Logger LOG = LoggerFactory.getLogger(SetMusicRequest.class);
+
+    private final MusicStateSpec musicStateSpec;
+    private final MusicSpec musicSpec;
+
+    public SetMusicRequest(HuaweiSupportProvider support, MusicStateSpec musicStateSpec, MusicSpec musicSpec) {
+        super(support);
+        this.serviceId = MusicControl.id;
+        this.commandId = MusicControl.MusicInfo.id;
+        this.musicStateSpec = musicStateSpec;
+        this.musicSpec = musicSpec;
+    }
+
+    private byte convertMusicState(int in) {
+        switch (in) {
+            case MusicStateSpec.STATE_PLAYING:
+                return PlaybackState.STATE_PLAYING;
+            case MusicStateSpec.STATE_PAUSED:
+                return PlaybackState.STATE_PAUSED;
+            case MusicStateSpec.STATE_STOPPED:
+                return PlaybackState.STATE_STOPPED;
+            case MusicStateSpec.STATE_UNKNOWN:
+            default:
+                return PlaybackState.STATE_NONE;
+        }
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        String artistName = "";
+        String songName = "";
+        byte playState = convertMusicState(MusicStateSpec.STATE_UNKNOWN);
+        if (this.musicSpec != null) {
+            artistName = this.musicSpec.artist;
+            songName = this.musicSpec.track;
+        }
+        if (this.musicStateSpec != null)
+            playState = convertMusicState(this.musicStateSpec.state);
+        AudioManager audioManager = (AudioManager) this.supportProvider.getContext().getSystemService(Context.AUDIO_SERVICE);
+        byte maxVolume = (byte) audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
+        byte currentVolume = (byte) audioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
+
+        try {
+            return new MusicControl.MusicInfo.Request(
+                    paramsProvider,
+                    artistName,
+                    songName,
+                    playState,
+                    maxVolume,
+                    currentVolume
+            ).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+
+    @Override
+    protected void processResponse() {
+        if (receivedPacket instanceof MusicControl.MusicInfo.Response) {
+            if (((MusicControl.MusicInfo.Response) receivedPacket).ok) {
+                LOG.debug("Music information acknowledged by band");
+            } else {
+                LOG.warn(((MusicControl.MusicInfo.Response) receivedPacket).error);
+            }
+        } else {
+            LOG.error("MusicInfo response is not of type MusicInfo response");
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SetMusicStatusRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SetMusicStatusRequest.java
new file mode 100644
index 0000000..5235058
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SetMusicStatusRequest.java
@@ -0,0 +1,44 @@
+/*  Copyright (C) 2024 Damien Gaignon, Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.MusicControl;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class SetMusicStatusRequest extends Request {
+    private final int returnValue;
+
+    public SetMusicStatusRequest(HuaweiSupportProvider support, byte commandId, int returnValue) {
+        super(support);
+        this.serviceId = MusicControl.id;
+        this.commandId = commandId;
+        this.returnValue = returnValue;
+        this.addToResponse = false;
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        try {
+            return new MusicControl.MusicStatusRequest(paramsProvider, (byte) commandId, returnValue).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SetNavigateOnRotateRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SetNavigateOnRotateRequest.java
new file mode 100644
index 0000000..c445ce7
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SetNavigateOnRotateRequest.java
@@ -0,0 +1,60 @@
+/*  Copyright (C) 2024 Damien Gaignon, Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.DeviceConfig;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class SetNavigateOnRotateRequest extends Request {
+    private static final Logger LOG = LoggerFactory.getLogger(SetNavigateOnRotateRequest.class);
+
+    public SetNavigateOnRotateRequest(HuaweiSupportProvider support) {
+        super(support);
+        this.serviceId = DeviceConfig.id;
+        this.commandId = DeviceConfig.NavigateOnRotateRequest.id;
+    }
+
+    @Override
+    protected boolean requestSupported() {
+        return supportProvider.getHuaweiCoordinator().supportsRotateToCycleInfo();
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        boolean navigate = GBApplication
+            .getDeviceSpecificSharedPrefs(supportProvider.getDevice().getAddress())
+            .getBoolean(MiBandConst.PREF_MI2_ROTATE_WRIST_TO_SWITCH_INFO, false);
+        try {
+            return new DeviceConfig.NavigateOnRotateRequest(paramsProvider, navigate).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+
+    @Override
+    protected void processResponse() {
+        LOG.debug("handle Set Navigate On Rotate");
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SetNotificationRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SetNotificationRequest.java
new file mode 100644
index 0000000..24da95a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SetNotificationRequest.java
@@ -0,0 +1,60 @@
+/*  Copyright (C) 2024 Damien Gaignon, Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Notifications;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class SetNotificationRequest extends Request {
+    private static final Logger LOG = LoggerFactory.getLogger(SetNotificationRequest.class);
+
+    public SetNotificationRequest(HuaweiSupportProvider support) {
+        super(support);
+        this.serviceId = Notifications.id;
+        this.commandId = Notifications.NotificationStateRequest.id;
+    }
+
+    @Override
+    protected boolean requestSupported() {
+        return supportProvider.getHuaweiCoordinator().supportsNotification();
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        boolean status = GBApplication
+            .getDeviceSpecificSharedPrefs(supportProvider.getDevice().getAddress())
+            .getBoolean(DeviceSettingsPreferenceConst.PREF_NOTIFICATION_ENABLE, false);
+        try {
+            return new Notifications.NotificationStateRequest(paramsProvider, status).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+
+    @Override
+    protected void processResponse() {
+        LOG.debug("handle Set Notification");
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SetPauseWhenRemovedFromEarRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SetPauseWhenRemovedFromEarRequest.java
new file mode 100644
index 0000000..5b6b065
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SetPauseWhenRemovedFromEarRequest.java
@@ -0,0 +1,31 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Earphones;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class SetPauseWhenRemovedFromEarRequest extends Request {
+
+    public SetPauseWhenRemovedFromEarRequest(HuaweiSupportProvider supportProvider) {
+        super(supportProvider);
+        this.serviceId = Earphones.id;
+        this.commandId = Earphones.SetAudioModeRequest.id;
+        this.addToResponse = false; // Response with different command ID
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        try {
+            boolean newState = GBApplication
+                    .getDeviceSpecificSharedPrefs(this.getDevice().getAddress())
+                    .getBoolean(DeviceSettingsPreferenceConst.PREF_HUAWEI_FREEBUDS_INEAR, false);
+            return new Earphones.SetPauseWhenRemovedFromEar(this.paramsProvider, newState).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SetSkinTemperatureMeasurement.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SetSkinTemperatureMeasurement.java
new file mode 100644
index 0000000..50c2ec8
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SetSkinTemperatureMeasurement.java
@@ -0,0 +1,39 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiConstants;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FitnessData;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class SetSkinTemperatureMeasurement extends Request {
+    private static final Logger LOG = LoggerFactory.getLogger(SetSkinTemperatureMeasurement.class);
+
+    public SetSkinTemperatureMeasurement(HuaweiSupportProvider support) {
+        super(support);
+        this.serviceId = FitnessData.id;
+        this.commandId = FitnessData.SkinTemperatureMeasurement.id;
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        boolean temperatureSwitch = GBApplication
+                .getDeviceSpecificSharedPrefs(this.getDevice().getAddress())
+                .getBoolean(HuaweiConstants.PREF_HUAWEI_CONTINUOUS_SKIN_TEMPERATURE_MEASUREMENT, false);
+        try {
+            return new FitnessData.SkinTemperatureMeasurement.Request(paramsProvider, temperatureSwitch).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+
+    @Override
+    protected void processResponse() {
+        LOG.debug("handle Set SkinTemperatureMeasurement");
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SetTemperatureUnitSetting.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SetTemperatureUnitSetting.java
new file mode 100644
index 0000000..471692c
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SetTemperatureUnitSetting.java
@@ -0,0 +1,38 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.LocaleConfig;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class SetTemperatureUnitSetting extends Request {
+    private static final Logger LOG = LoggerFactory.getLogger(SetLanguageSettingRequest.class);
+
+    public SetTemperatureUnitSetting(HuaweiSupportProvider support) {
+        super(support);
+        this.serviceId = LocaleConfig.id;
+        this.commandId = LocaleConfig.SetTemperatureUnitSetting.id;
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        String temperatureScale = GBApplication.getDeviceSpecificSharedPrefs(this.getDevice().getAddress()).getString(DeviceSettingsPreferenceConst.PREF_TEMPERATURE_SCALE_CF, "");
+        byte isFahrenheit = (byte) ((temperatureScale.equals("f")) ? 1 : 0);
+        try {
+            return new LocaleConfig.SetTemperatureUnitSetting(paramsProvider, isFahrenheit).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+
+    @Override
+    protected void processResponse() {
+        LOG.debug("handle Set Temperature unit");
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SetTimeRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SetTimeRequest.java
new file mode 100644
index 0000000..e33ccd9
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SetTimeRequest.java
@@ -0,0 +1,64 @@
+/*  Copyright (C) 2024 Damien Gaignon, Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.DeviceConfig;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class SetTimeRequest extends Request {
+    private static final Logger LOG = LoggerFactory.getLogger(SetTimeRequest.class);
+    private boolean checkTime;
+
+    public SetTimeRequest(HuaweiSupportProvider support, boolean syncTime) {
+        super(support);
+        this.serviceId = DeviceConfig.id;
+        this.commandId = DeviceConfig.TimeRequest.id;
+        this.checkTime = syncTime;
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        try {
+            return new DeviceConfig.TimeRequest(paramsProvider).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+
+    @Override
+    protected void processResponse() throws ResponseTypeMismatchException {
+        LOG.debug("handle Set Time");
+        if (!(receivedPacket instanceof DeviceConfig.TimeRequest.Response))
+            throw new ResponseTypeMismatchException(receivedPacket, DeviceConfig.TimeRequest.Response.class);
+
+        int deviceTime = ((DeviceConfig.TimeRequest.Response) receivedPacket).deviceTime;
+        int systemTime = (int) (System.currentTimeMillis() / 1000);
+        int diff = Math.abs(systemTime - deviceTime);
+        LOG.debug("systemTime: {} deviceTime: {} diff: {}",systemTime, deviceTime, diff);
+        if (this.checkTime && diff > 0) {
+            SetTimeRequest setTimeReq = new SetTimeRequest(supportProvider, false);
+            setTimeReq.nextRequest(this.nextRequest);
+            nextRequest(setTimeReq);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SetTimeZoneIdRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SetTimeZoneIdRequest.java
new file mode 100644
index 0000000..5f1fd5d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SetTimeZoneIdRequest.java
@@ -0,0 +1,55 @@
+/*  Copyright (C) 2024 Damien Gaignon, Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.DeviceConfig;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class SetTimeZoneIdRequest extends Request {
+    private static final Logger LOG = LoggerFactory.getLogger(SetTimeZoneIdRequest.class);
+
+    public SetTimeZoneIdRequest(HuaweiSupportProvider support) {
+        super(support);
+        this.serviceId = DeviceConfig.id;
+        this.commandId = DeviceConfig.TimeZoneIdRequest.id;
+    }
+
+    @Override
+    protected boolean requestSupported() {
+        return supportProvider.getHuaweiCoordinator().supportsTimeAndZoneId();
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        try {
+            return new DeviceConfig.TimeZoneIdRequest(paramsProvider).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+
+    @Override
+    protected void processResponse() {
+        LOG.debug("handle Set Time ZoneId");
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SetTruSleepRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SetTruSleepRequest.java
new file mode 100644
index 0000000..3671429
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SetTruSleepRequest.java
@@ -0,0 +1,60 @@
+/*  Copyright (C) 2024 Damien Gaignon, Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiConstants;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FitnessData;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class SetTruSleepRequest extends Request {
+    private static final Logger LOG = LoggerFactory.getLogger(SetTruSleepRequest.class);
+
+    public SetTruSleepRequest(HuaweiSupportProvider support) {
+        super(support);
+        this.serviceId = FitnessData.id;
+        this.commandId = FitnessData.TruSleep.id;
+    }
+
+    @Override
+    protected boolean requestSupported() {
+        return supportProvider.getHuaweiCoordinator().supportsTruSleep();
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        boolean truSleepSwitch = GBApplication
+                .getDeviceSpecificSharedPrefs(this.getDevice().getAddress())
+                .getBoolean(HuaweiConstants.PREF_HUAWEI_TRUSLEEP, false);
+        try {
+            return new FitnessData.TruSleep.Request(paramsProvider, truSleepSwitch).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+
+    @Override
+    protected void processResponse() {
+        LOG.debug("handle Set TruSleep");
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SetWearLocationRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SetWearLocationRequest.java
new file mode 100644
index 0000000..6150586
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SetWearLocationRequest.java
@@ -0,0 +1,61 @@
+/*  Copyright (C) 2024 Damien Gaignon, Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.DeviceConfig;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class SetWearLocationRequest extends Request {
+    private static final Logger LOG = LoggerFactory.getLogger(SetWearLocationRequest.class);
+
+    public SetWearLocationRequest(HuaweiSupportProvider support) {
+        super(support);
+        this.serviceId = DeviceConfig.id;
+        this.commandId = DeviceConfig.WearLocationRequest.id;
+    }
+
+    @Override
+    protected boolean requestSupported() {
+        return supportProvider.getHuaweiCoordinator().supportsWearLocation(supportProvider.getDevice());
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        String locationString = GBApplication
+            .getDeviceSpecificSharedPrefs(supportProvider.getDevice().getAddress())
+            .getString(DeviceSettingsPreferenceConst.PREF_WEARLOCATION, "left");
+        byte location = (byte) (locationString.equals("left") ? 1 : 0);
+        try {
+            return new DeviceConfig.WearLocationRequest(paramsProvider, location).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+
+    @Override
+    protected void processResponse() {
+        LOG.debug("handle Set Wear Location");
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SetWearMessagePushRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SetWearMessagePushRequest.java
new file mode 100644
index 0000000..f2861bd
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SetWearMessagePushRequest.java
@@ -0,0 +1,60 @@
+/*  Copyright (C) 2024 Damien Gaignon, Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Notifications;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class SetWearMessagePushRequest extends Request {
+    private static final Logger LOG = LoggerFactory.getLogger(SetWearMessagePushRequest.class);
+
+    public SetWearMessagePushRequest(HuaweiSupportProvider support) {
+        super(support);
+        this.serviceId = Notifications.id;
+        this.commandId = Notifications.WearMessagePushRequest.id;
+    }
+
+    @Override
+    protected boolean requestSupported() {
+        return supportProvider.getHuaweiCoordinator().supportsDoNotDisturb(supportProvider.getDevice()) && supportProvider.getHuaweiCoordinator().supportsWearMessagePush();
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        boolean status = GBApplication
+            .getDeviceSpecificSharedPrefs(supportProvider.getDevice().getAddress())
+            .getBoolean(DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_NOT_WEAR, false);
+        try {
+            return new Notifications.WearMessagePushRequest(paramsProvider, status).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+
+    @Override
+    protected void processResponse() {
+        LOG.debug("handle Set WearMessage Push ");
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SetWorkModeRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SetWorkModeRequest.java
new file mode 100644
index 0000000..86280c8
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SetWorkModeRequest.java
@@ -0,0 +1,56 @@
+/*  Copyright (C) 2024 Damien Gaignon, Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiConstants;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.WorkMode;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class SetWorkModeRequest extends Request {
+    private static final Logger LOG = LoggerFactory.getLogger(SetWorkModeRequest.class);
+
+    public SetWorkModeRequest(HuaweiSupportProvider support) {
+        super(support);
+        this.serviceId = WorkMode.id;
+        this.commandId = WorkMode.SwitchStatusRequest.id;
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        String workModeString = GBApplication
+            .getDeviceSpecificSharedPrefs(supportProvider.getDevice().getAddress())
+            .getString(HuaweiConstants.PREF_HUAWEI_WORKMODE, "auto");
+        boolean workMode = workModeString.equals("auto");
+        try {
+            return new WorkMode.SwitchStatusRequest(paramsProvider, workMode).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+
+    @Override
+    protected void processResponse() {
+        LOG.debug("handle Set WorkMode");
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/StopFindPhoneRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/StopFindPhoneRequest.java
new file mode 100644
index 0000000..3aee8cf
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/StopFindPhoneRequest.java
@@ -0,0 +1,40 @@
+/*  Copyright (C) 2024 Damien Gaignon, Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FindPhone;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class StopFindPhoneRequest extends Request {
+    public StopFindPhoneRequest(HuaweiSupportProvider support) {
+        super(support);
+        this.serviceId = FindPhone.id;
+        this.commandId = FindPhone.StopRequest.id;
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        try {
+            return new FindPhone.StopRequest(paramsProvider).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/StopNotificationRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/StopNotificationRequest.java
new file mode 100644
index 0000000..a153c7f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/StopNotificationRequest.java
@@ -0,0 +1,50 @@
+/*  Copyright (C) 2024 Damien Gaignon, Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Notifications;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class StopNotificationRequest extends Request {
+    public StopNotificationRequest(HuaweiSupportProvider support) {
+        super(support);
+        this.serviceId = Notifications.id;
+        this.commandId = Notifications.NotificationActionRequest.id;
+        this.addToResponse = false;
+    }
+
+    @Override
+    protected List<byte[]> createRequest() throws RequestCreationException {
+        try {
+            return new Notifications.NotificationActionRequest(
+                    paramsProvider,
+                    supportProvider.getNotificationId(),
+                    Notifications.NotificationType.stopNotification,
+                    supportProvider.getHuaweiCoordinator().getContentFormat(),
+                    null,
+                    null,
+                    null,
+                    null
+            ).serialize();
+        } catch (HuaweiPacket.CryptoException e) {
+            throw new RequestCreationException(e);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/id115/AbstractID115Operation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/id115/AbstractID115Operation.java
new file mode 100644
index 0000000..a124760
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/id115/AbstractID115Operation.java
@@ -0,0 +1,96 @@
+/*  Copyright (C) 2018-2024 Damien Gaignon, Taavi Eom盲e, Vadim Kaushan
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.id115;
+
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.widget.Toast;
+
+import java.io.IOException;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.id115.ID115Constants;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEOperation;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.OperationStatus;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public abstract class AbstractID115Operation extends AbstractBTLEOperation<ID115Support> {
+    protected BluetoothGattCharacteristic controlCharacteristic;
+    protected BluetoothGattCharacteristic notifyCharacteristic;
+
+    protected AbstractID115Operation(ID115Support support) {
+        super(support);
+
+        if (isHealthOperation()) {
+            controlCharacteristic = getCharacteristic(ID115Constants.UUID_CHARACTERISTIC_WRITE_HEALTH);
+            notifyCharacteristic = getCharacteristic(ID115Constants.UUID_CHARACTERISTIC_NOTIFY_HEALTH);
+        } else {
+            controlCharacteristic = getCharacteristic(ID115Constants.UUID_CHARACTERISTIC_WRITE_NORMAL);
+            notifyCharacteristic = getCharacteristic(ID115Constants.UUID_CHARACTERISTIC_NOTIFY_NORMAL);
+        }
+    }
+
+    @Override
+    protected void prePerform() throws IOException {
+        super.prePerform();
+        getDevice().setBusyTask("AbstractID115Operation starting..."); // mark as busy quickly to avoid interruptions from the outside
+        TransactionBuilder builder = performInitialized("disabling some notifications");
+        enableNotifications(builder, true);
+        builder.queue(getQueue());
+    }
+
+    @Override
+    protected void operationFinished() {
+        operationStatus = OperationStatus.FINISHED;
+        if (getDevice() != null && getDevice().isConnected()) {
+            unsetBusy();
+            try {
+                TransactionBuilder builder = performInitialized("reenabling disabled notifications");
+                enableNotifications(builder, false);
+                builder.setCallback(null); // unset ourselves from being the queue's gatt callback
+                builder.queue(getQueue());
+            } catch (IOException ex) {
+                GB.toast(getContext(), "Error enabling ID115 notifications, you may need to connect and disconnect", Toast.LENGTH_LONG, GB.ERROR, ex);
+            }
+        }
+    }
+
+    @Override
+    public boolean onCharacteristicChanged(BluetoothGatt gatt,
+                                           BluetoothGattCharacteristic characteristic) {
+        UUID characteristicUUID = characteristic.getUuid();
+        if (notifyCharacteristic.getUuid().equals(characteristicUUID)) {
+            handleResponse(characteristic.getValue());
+            return true;
+        } else {
+            return super.onCharacteristicChanged(gatt, characteristic);
+        }
+    }
+
+    void enableNotifications(TransactionBuilder builder, boolean enable) {
+        if (isHealthOperation()) {
+            builder.notify(getCharacteristic(ID115Constants.UUID_CHARACTERISTIC_NOTIFY_HEALTH), enable);
+        } else {
+            builder.notify(getCharacteristic(ID115Constants.UUID_CHARACTERISTIC_NOTIFY_NORMAL), enable);
+        }
+    }
+
+    abstract boolean isHealthOperation();
+
+    abstract void handleResponse(byte[] data);
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/id115/FetchActivityOperation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/id115/FetchActivityOperation.java
new file mode 100644
index 0000000..c5a7564
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/id115/FetchActivityOperation.java
@@ -0,0 +1,176 @@
+/*  Copyright (C) 2018-2024 Vadim Kaushan
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.id115;
+
+import android.widget.Toast;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.id115.ID115Constants;
+import nodomain.freeyourgadget.gadgetbridge.devices.id115.ID115SampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.ID115ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class FetchActivityOperation extends AbstractID115Operation {
+    private static final Logger LOG = LoggerFactory.getLogger(FetchActivityOperation.class);
+    private byte expectedCmd;
+    private byte expectedSeq;
+    private ArrayList<byte[]> packets;
+
+    protected FetchActivityOperation(ID115Support support) {
+        super(support);
+    }
+
+    @Override
+    boolean isHealthOperation() {
+        return true;
+    }
+
+    @Override
+    protected void doPerform() throws IOException {
+        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        outputStream.write(ID115Constants.CMD_ID_HEALTH_DATA);
+        outputStream.write(ID115Constants.CMD_KEY_FETCH_ACTIVITY_TODAY);
+        outputStream.write(0x01);
+        outputStream.write(0x00);
+        outputStream.write(0x00);
+        byte[] cmd = outputStream.toByteArray();
+
+        expectedCmd = ID115Constants.CMD_KEY_FETCH_ACTIVITY_TODAY;
+        expectedSeq = 1;
+        packets = new ArrayList<>();
+
+        TransactionBuilder builder = performInitialized("send activity fetch request");
+        builder.write(controlCharacteristic, cmd);
+        builder.queue(getQueue());
+    }
+
+    @Override
+    void handleResponse(byte[] data) {
+        if (!isOperationRunning()) {
+            LOG.error("ignoring notification because operation is not running. Data length: {}", data.length);
+            getSupport().logMessageContent(data);
+            return;
+        }
+
+        if (data.length < 4) {
+            LOG.warn("short GATT response");
+            return;
+        }
+
+        if (data[0] == ID115Constants.CMD_ID_HEALTH_DATA) {
+            if (data[1] == (byte)0xEE) {
+                LOG.info("Activity data transfer has finished.");
+                parseAndStore();
+                operationFinished();
+            } else {
+                if ((data[1] != expectedCmd) || (data[2] != expectedSeq)) {
+                    GB.toast(getContext(), "Error fetching ID115 activity data, you may need to connect and disconnect", Toast.LENGTH_LONG, GB.ERROR);
+                    operationFinished();
+                    return;
+                }
+                expectedSeq += 1;
+
+                byte[] payload = new byte[data.length - 4];
+                System.arraycopy(data, 4, payload, 0, payload.length);
+                packets.add(payload);
+            }
+        }
+    }
+
+    void parseAndStore() {
+        if (packets.size() <= 1) {
+            return;
+        }
+
+        byte[] header = packets.get(0);
+        int year = ((header[1] & 0xFF) << 8) | (header[0] & 0xFF);
+        int month = header[2] & 0xFF;
+        int day = header[3] & 0xFF;
+        int sampleDurationMinutes = header[6] & 0xFF;
+        Calendar calendar = new GregorianCalendar(year, month - 1, day);
+        int ts = (int)(calendar.getTimeInMillis() / 1000);
+        int dt = sampleDurationMinutes * 60;
+
+        ArrayList<ID115ActivitySample> samples = new ArrayList<>();
+
+        for (int i = 2; i < packets.size(); i++) {
+            byte[] packet = packets.get(i);
+            for (int j = 0; j <= packet.length - 5; j += 5) {
+                byte[] sampleData = new byte[5];
+                System.arraycopy(packet, j, sampleData, 0, sampleData.length);
+
+                ID115ActivitySample sample = parseSample(sampleData);
+                if (sample != null) {
+                    sample.setTimestamp(ts);
+                    sample.setRawKind(ActivityKind.ACTIVITY.getCode());
+                    samples.add(sample);
+                }
+                ts += dt;
+            }
+        }
+
+        try (DBHandler dbHandler = GBApplication.acquireDB()) {
+            ID115ActivitySample[] sampleArray = samples.toArray(new ID115ActivitySample[0]);
+            long userId = DBHelper.getUser(dbHandler.getDaoSession()).getId();
+            long deviceId = DBHelper.getDevice(getDevice(), dbHandler.getDaoSession()).getId();
+            for (ID115ActivitySample sample: sampleArray) {
+                sample.setUserId(userId);
+                sample.setDeviceId(deviceId);
+            }
+
+            ID115SampleProvider provider = new ID115SampleProvider(getDevice(), dbHandler.getDaoSession());
+            provider.addGBActivitySamples(sampleArray);
+        } catch (Exception ex) {
+            GB.toast(getContext(), "Error saving activity data: " + ex.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
+        }
+    }
+
+    ID115ActivitySample parseSample(byte[] data) {
+        int d01 = ((data[1] & 0xFF) << 8) | (data[0] & 0xFF);
+        int d12 = ((data[2] & 0xFF) << 8) | (data[1] & 0xFF);
+        int d23 = ((data[3] & 0xFF) << 8) | (data[2] & 0xFF);
+        int d34 = ((data[4] & 0xFF) << 8) | (data[3] & 0xFF);
+        int stepCount = (d01 >> 2) & 0xFFF;
+        int activeTime = (d12 >> 6) & 0xF;
+        int calories = (d23 >> 2) & 0x3FF;
+        int distance = (d34 >> 4);
+
+        if (stepCount == 0) {
+            return null;
+        }
+
+        ID115ActivitySample sample = new ID115ActivitySample();
+        sample.setSteps(stepCount);
+        sample.setActiveTimeMinutes(activeTime);
+        sample.setCaloriesBurnt(calories);
+        sample.setDistanceMeters(distance);
+        return sample;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/id115/ID115Support.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/id115/ID115Support.java
new file mode 100644
index 0000000..753ebb1
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/id115/ID115Support.java
@@ -0,0 +1,241 @@
+/*  Copyright (C) 2018-2024 Andreas Shimokawa, Arjan Schrijver, Carsten
+    Pfeiffer, Sebastian Kranz, Vadim Kaushan
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.id115;
+
+import android.bluetooth.BluetoothGattCharacteristic;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.Calendar;
+import java.util.TimeZone;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+import nodomain.freeyourgadget.gadgetbridge.devices.id115.ID115Constants;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
+import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.GattService;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
+
+public class ID115Support extends AbstractBTLEDeviceSupport {
+    private static final Logger LOG = LoggerFactory.getLogger(ID115Support.class);
+
+    public BluetoothGattCharacteristic normalWriteCharacteristic = null;
+    public BluetoothGattCharacteristic healthWriteCharacteristic = null;
+
+    public ID115Support() {
+        super(LOG);
+        addSupportedService(GattService.UUID_SERVICE_GENERIC_ACCESS);
+        addSupportedService(GattService.UUID_SERVICE_GENERIC_ATTRIBUTE);
+        addSupportedService(ID115Constants.UUID_SERVICE_ID115);
+    }
+
+    @Override
+    protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
+        normalWriteCharacteristic = getCharacteristic(ID115Constants.UUID_CHARACTERISTIC_WRITE_NORMAL);
+        healthWriteCharacteristic = getCharacteristic(ID115Constants.UUID_CHARACTERISTIC_WRITE_HEALTH);
+
+        builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext()));
+
+        setTime(builder)
+                .setWrist(builder)
+                .setScreenOrientation(builder)
+                .setGoal(builder)
+                .setInitialized(builder);
+
+        getDevice().setFirmwareVersion("N/A");
+        getDevice().setFirmwareVersion2("N/A");
+
+        return builder;
+    }
+
+    @Override
+    public boolean useAutoConnect() {
+        return true;
+    }
+
+    @Override
+    public void onNotification(NotificationSpec notificationSpec) {
+        try {
+            new SendNotificationOperation(this, notificationSpec).perform();
+        } catch (IOException ex) {
+            LOG.error("Unable to send ID115 notification", ex);
+        }
+    }
+
+    @Override
+    public void onSetTime() {
+        try {
+            TransactionBuilder builder = performInitialized("time");
+            setTime(builder);
+            builder.queue(getQueue());
+        } catch(IOException e) {
+            LOG.warn("Unable to send current time", e);
+        }
+    }
+
+    @Override
+    public void onSetCallState(CallSpec callSpec) {
+        if (callSpec.command == CallSpec.CALL_INCOMING) {
+            try {
+                new SendNotificationOperation(this, callSpec).perform();
+            } catch (IOException ex) {
+                LOG.error("Unable to send ID115 notification", ex);
+            }
+        } else {
+            sendStopCallNotification();
+        }
+    }
+
+    @Override
+    public void onFetchRecordedData(int dataTypes) {
+        try {
+            new FetchActivityOperation(this).perform();
+        } catch (IOException ex) {
+            LOG.error("Unable to fetch ID115 activity data", ex);
+        }
+    }
+
+    @Override
+    public void onReset(int flags) {
+        try {
+            getQueue().clear();
+
+            TransactionBuilder builder = performInitialized("reboot");
+            builder.write(normalWriteCharacteristic, new byte[] {
+                    ID115Constants.CMD_ID_DEVICE_RESTART, ID115Constants.CMD_KEY_REBOOT
+            });
+            builder.queue(getQueue());
+        } catch(Exception e) {
+        }
+    }
+
+    private void setInitialized(TransactionBuilder builder) {
+        builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZED, getContext()));
+    }
+
+    ID115Support setTime(TransactionBuilder builder) {
+        Calendar c = Calendar.getInstance(TimeZone.getDefault());
+
+        int day = c.get(Calendar.DAY_OF_WEEK);
+
+        byte dayOfWeek;
+        if (day == Calendar.SUNDAY) {
+            dayOfWeek = 6;
+        } else {
+            dayOfWeek = (byte)(day - 2);
+        }
+
+        int year = c.get(Calendar.YEAR);
+        builder.write(normalWriteCharacteristic, new byte[] {
+                ID115Constants.CMD_ID_SETTINGS, ID115Constants.CMD_KEY_SET_TIME,
+                (byte)(year & 0xff),
+                (byte)(year >> 8),
+                (byte)(1 + c.get(Calendar.MONTH)),
+                (byte)c.get(Calendar.DAY_OF_MONTH),
+                (byte)c.get(Calendar.HOUR_OF_DAY),
+                (byte)c.get(Calendar.MINUTE),
+                (byte)c.get(Calendar.SECOND),
+                dayOfWeek
+        });
+        return this;
+    }
+
+    ID115Support setWrist(TransactionBuilder builder) {
+        String value = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getString(DeviceSettingsPreferenceConst.PREF_WEARLOCATION,
+                "left");
+
+        byte wrist;
+        if ("left".equals(value)) {
+            wrist = ID115Constants.CMD_ARG_LEFT;
+        } else {
+            wrist = ID115Constants.CMD_ARG_RIGHT;
+        }
+
+        builder.write(normalWriteCharacteristic, new byte[] {
+                ID115Constants.CMD_ID_SETTINGS, ID115Constants.CMD_KEY_SET_HAND,
+                wrist
+        });
+        return this;
+    }
+
+    ID115Support setScreenOrientation(TransactionBuilder builder) {
+        String value = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getString(DeviceSettingsPreferenceConst.PREF_SCREEN_ORIENTATION,
+                "horizontal");
+
+        byte orientation;
+        if ("horizontal".equals(value)) {
+            orientation = ID115Constants.CMD_ARG_HORIZONTAL;
+        } else {
+            orientation = ID115Constants.CMD_ARG_VERTICAL;
+        }
+
+        builder.write(normalWriteCharacteristic, new byte[] {
+                ID115Constants.CMD_ID_SETTINGS, ID115Constants.CMD_KEY_SET_DISPLAY_MODE,
+                orientation
+        });
+        return this;
+    }
+
+    private ID115Support setGoal(TransactionBuilder transaction) {
+        ActivityUser activityUser = new ActivityUser();
+        int value = activityUser.getStepsGoal();
+
+        transaction.write(normalWriteCharacteristic, new byte[]{
+                ID115Constants.CMD_ID_SETTINGS,
+                ID115Constants.CMD_KEY_SET_GOAL,
+                0,
+                (byte) (value & 0xff),
+                (byte) ((value >> 8) & 0xff),
+                (byte) ((value >> 16) & 0xff),
+                (byte) ((value >> 24) & 0xff),
+                0, 0
+        });
+        return this;
+    }
+
+    void sendStopCallNotification() {
+        try {
+            TransactionBuilder builder = performInitialized("stop_call_notification");
+            builder.write(normalWriteCharacteristic, new byte[] {
+                    ID115Constants.CMD_ID_NOTIFY,
+                    ID115Constants.CMD_KEY_NOTIFY_STOP,
+                    1
+            });
+            builder.queue(getQueue());
+        } catch(IOException e) {
+            LOG.warn("Unable to stop call notification", e);
+        }
+    }
+
+    @Override
+    public boolean getImplicitCallbackModify() {
+        return true;
+    }
+
+    @Override
+    public boolean getSendWriteRequestResponse() {
+        return false;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/id115/SendNotificationOperation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/id115/SendNotificationOperation.java
new file mode 100644
index 0000000..2418b7b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/id115/SendNotificationOperation.java
@@ -0,0 +1,209 @@
+/*  Copyright (C) 2018-2024 Vadim Kaushan
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.id115;
+
+import android.widget.Toast;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.id115.ID115Constants;
+import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.NotificationType;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class SendNotificationOperation extends AbstractID115Operation {
+    private static final Logger LOG = LoggerFactory.getLogger(SendNotificationOperation.class);
+
+    byte[] currentNotificationBuffer;
+    int currentNotificationSize;
+    int currentNotificationIndex;
+    byte currentNotificationType;
+
+    SendNotificationOperation(ID115Support support, NotificationSpec notificationSpec)
+    {
+        super(support);
+
+        String phone = "";
+        if (notificationSpec.phoneNumber != null) {
+            phone = notificationSpec.phoneNumber;
+        }
+
+        String title = "";
+        if (notificationSpec.sender != null) {
+            title = notificationSpec.sender;
+        } else if (notificationSpec.title != null) {
+            title = notificationSpec.title;
+        } else if (notificationSpec.subject != null) {
+            title = notificationSpec.subject;
+        }
+
+        String text = "";
+        if (notificationSpec.body != null) {
+            text = notificationSpec.body;
+        }
+
+        currentNotificationBuffer = encodeMessageNotification(notificationSpec.type, title, phone, text);
+        currentNotificationSize = (currentNotificationBuffer.length + 15) / 16;
+        currentNotificationType = ID115Constants.CMD_KEY_NOTIFY_MSG;
+    }
+
+    SendNotificationOperation(ID115Support support, CallSpec callSpec)
+    {
+        super(support);
+
+        String number = "";
+        if (callSpec.number != null) {
+            number = callSpec.number;
+        }
+
+        String name = "";
+        if (callSpec.name != null) {
+            name = callSpec.name;
+        }
+
+        currentNotificationBuffer = encodeCallNotification(name, number);
+        currentNotificationSize = (currentNotificationBuffer.length + 15) / 16;
+        currentNotificationType = ID115Constants.CMD_KEY_NOTIFY_CALL;
+    }
+
+    @Override
+    boolean isHealthOperation() {
+        return false;
+    }
+
+    @Override
+    protected void doPerform() throws IOException {
+        sendNotificationChunk(1);
+    }
+
+    void sendNotificationChunk(int chunkIndex) throws IOException {
+        currentNotificationIndex = chunkIndex;
+
+        int offset = (chunkIndex - 1) * 16;
+        int tailSize = currentNotificationBuffer.length - offset;
+        int chunkSize = (tailSize > 16)? 16 : tailSize;
+
+        byte raw[] = new byte[16];
+        System.arraycopy(currentNotificationBuffer, offset, raw, 0, chunkSize);
+
+        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        outputStream.write(ID115Constants.CMD_ID_NOTIFY);
+        outputStream.write(currentNotificationType);
+        outputStream.write((byte)currentNotificationSize);
+        outputStream.write((byte)currentNotificationIndex);
+        outputStream.write(raw);
+        byte cmd[] = outputStream.toByteArray();
+
+        TransactionBuilder builder = performInitialized("send notification chunk");
+        builder.write(controlCharacteristic, cmd);
+        builder.queue(getQueue());
+    }
+
+    void handleResponse(byte[] data) {
+        if (!isOperationRunning()) {
+            LOG.error("ignoring notification because operation is not running. Data length: " + data.length);
+            getSupport().logMessageContent(data);
+            return;
+        }
+
+        if (data.length < 2) {
+            LOG.warn("short GATT response");
+            return;
+        }
+        if (data[0] == ID115Constants.CMD_ID_NOTIFY) {
+            if (data.length < 4) {
+                LOG.warn("short GATT response for NOTIFY");
+                return;
+            }
+            if (data[1] == currentNotificationType) {
+                if (data[3] == currentNotificationIndex) {
+                    if (currentNotificationIndex != currentNotificationSize) {
+                        try {
+                            sendNotificationChunk(currentNotificationIndex + 1);
+                        } catch (IOException ex) {
+                            GB.toast(getContext(), "Error sending ID115 notification, you may need to connect and disconnect", Toast.LENGTH_LONG, GB.ERROR, ex);
+                        }
+                    } else {
+                        LOG.info("Notification transfer has finished.");
+                        operationFinished();
+                    }
+                }
+            }
+        }
+    }
+
+    byte[] encodeCallNotification(String name, String phone) {
+        if (name.length() > 20) {
+            name = name.substring(0, 20);
+        }
+        if (phone.length() > 20) {
+            phone = phone.substring(0, 20);
+        }
+
+        byte[] name_bytes = name.getBytes(StandardCharsets.UTF_8);
+        byte[] phone_bytes = phone.getBytes(StandardCharsets.UTF_8);
+
+        try {
+            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+            outputStream.write((byte) phone_bytes.length);
+            outputStream.write((byte) name_bytes.length);
+            outputStream.write(phone_bytes);
+            outputStream.write(name_bytes);
+            return outputStream.toByteArray();
+        } catch (IOException e) {
+            return null;
+        }
+    }
+
+    byte[] encodeMessageNotification(NotificationType type, String title, String phone, String text) {
+        if (title.length() > 20) {
+            title = title.substring(0, 20);
+        }
+        if (phone.length() > 20) {
+            phone = phone.substring(0, 20);
+        }
+        if (text.length() > 20) {
+            text = text.substring(0, 20);
+        }
+        byte[] title_bytes = title.getBytes(StandardCharsets.UTF_8);
+        byte[] phone_bytes = phone.getBytes(StandardCharsets.UTF_8);
+        byte[] text_bytes = text.getBytes(StandardCharsets.UTF_8);
+
+        byte nativeType = ID115Constants.getNotificationType(type);
+
+        try {
+            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+            outputStream.write(nativeType);
+            outputStream.write((byte) text_bytes.length);
+            outputStream.write((byte) phone_bytes.length);
+            outputStream.write((byte) title_bytes.length);
+            outputStream.write(phone_bytes);
+            outputStream.write(title_bytes);
+            outputStream.write(text_bytes);
+            return outputStream.toByteArray();
+        } catch (IOException e) {
+            return null;
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/idasen/IdasenDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/idasen/IdasenDeviceSupport.java
new file mode 100644
index 0000000..47ead98
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/idasen/IdasenDeviceSupport.java
@@ -0,0 +1,208 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.idasen;
+
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.SharedPreferences;
+
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.HashMap;
+import java.util.Objects;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+import nodomain.freeyourgadget.gadgetbridge.devices.idasen.IdasenConstants;
+
+public class IdasenDeviceSupport extends AbstractBTLEDeviceSupport {
+    private static final Logger LOG = LoggerFactory.getLogger(IdasenDeviceSupport.class);
+
+    public static final String COMMAND_UP = "nodomain.freeyourgadget.gadgetbridge.idasen.command.UP";
+    public static final String COMMAND_DOWN = "nodomain.freeyourgadget.gadgetbridge.idasen.command.DOWN";
+    public static final String COMMAND_SET_HEIGHT = "nodomain.freeyourgadget.gadgetbridge.idasen.command.SET_HEIGHT";
+    public static final String COMMAND_GET_DESK_VALUES = "nodomain.freeyourgadget.gadgetbridge.idasen.command.GET_DESK_VALUES";
+    public static final String TARGET_HEIGHT = "TARGET_HEIGHT";
+    public static final String TARGET_POS_SIT = "TARGET_POS_SIT";
+    public static final String TARGET_POS_STAND = "TARGET_POS_STAND";
+    public static final String TARGET_POS_MID = "TARGET_POS_MID";
+    public float sit_height, stand_height, mid_height;
+    public float deskSpeed, deskHeight;
+
+    public IdasenDeviceSupport() {
+        super(LOG);
+        addSupportedService(IdasenConstants.CHARACTERISTIC_SVC_HEIGHT);
+        addSupportedService(IdasenConstants.CHARACTERISTIC_SVC_COMMAND);
+        addSupportedService(IdasenConstants.CHARACTERISTIC_SVC_REF_HEIGHT);
+        addSupportedService(IdasenConstants.CHARACTERISTIC_SVC_DPG);
+    }
+
+    BroadcastReceiver commandReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String act = intent.getAction();
+
+            switch(Objects.requireNonNull(act)){
+                case COMMAND_UP:
+                    sendCommand("cmd", IdasenConstants.CHARACTERISTIC_COMMAND, IdasenConstants.CMD_UP);
+                    break;
+                case COMMAND_DOWN:
+                    sendCommand("cmd", IdasenConstants.CHARACTERISTIC_COMMAND, IdasenConstants.CMD_DOWN);
+                    break;
+                case COMMAND_GET_DESK_VALUES:
+                    readCharacteristic("IdasenGetHeight", IdasenConstants.CHARACTERISTIC_HEIGHT);
+                    break;
+                case COMMAND_SET_HEIGHT:
+                    HashMap<String, Float> mPositionsMap = new HashMap<>();
+
+                    SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress());
+                    stand_height = Float.parseFloat(prefs.getString(DeviceSettingsPreferenceConst.PREF_IDASEN_STAND_HEIGHT, "0.0")) / 100F;
+                    mid_height = Float.parseFloat(prefs.getString(DeviceSettingsPreferenceConst.PREF_IDASEN_MID_HEIGHT, "0.0")) / 100F;
+                    sit_height = Float.parseFloat(prefs.getString(DeviceSettingsPreferenceConst.PREF_IDASEN_SIT_HEIGHT, "0.0")) / 100F;
+                    mPositionsMap.put(TARGET_POS_SIT, sit_height);
+                    mPositionsMap.put(TARGET_POS_STAND, stand_height);
+                    mPositionsMap.put(TARGET_POS_MID, mid_height);
+
+                    sendCommand("cmd", IdasenConstants.CHARACTERISTIC_COMMAND, IdasenConstants.CMD_WAKEUP);
+                    sendCommand("cmd", IdasenConstants.CHARACTERISTIC_COMMAND, IdasenConstants.CMD_STOP);
+
+                    BluetoothGattCharacteristic characteristic = getCharacteristic(IdasenConstants.CHARACTERISTIC_REF_HEIGHT);
+                    String targetHeightKey = intent.getStringExtra(TARGET_HEIGHT);
+                    float targetHeight = mPositionsMap.get(targetHeightKey);
+
+                    ByteBuffer setHeightBB = ByteBuffer.allocate(2);
+                    setHeightBB.order(ByteOrder.LITTLE_ENDIAN);
+                    setHeightBB.putShort(0, (short) ((targetHeight - IdasenConstants.MIN_HEIGHT) * 10000F));
+                    byte[] setHeightRequest = setHeightBB.array();
+                    new Thread(() -> {
+                        // This acts as a fail-safe, in case deskSpeed never goes to 0
+                        // for whatever reason. It's based on the time the desk controller
+                        // needs to get from the lowest to the highest point.
+                        int cutOff = 100;
+                        do {
+                            TransactionBuilder builder = new TransactionBuilder("height");
+
+                            builder.write(characteristic, setHeightRequest);
+                            builder.queue(getQueue());
+                            try {
+                                Thread.sleep(300);
+                            } catch (InterruptedException e) {
+                                GB.log("error", GB.ERROR, e);
+                            }
+                            cutOff--;
+                        } while (deskSpeed != 0F && cutOff != 0);
+
+                        if (cutOff == 0) {
+                            LOG.warn("desk controller did not reach the desired height in time");
+                        }
+                    }).start();
+                    break;
+            }
+        }
+    };
+
+    @Override
+    public boolean useAutoConnect() {
+        return true;
+    }
+
+    @Override
+    public void dispose() {
+        super.dispose();
+        LocalBroadcastManager.getInstance(getContext()).unregisterReceiver(commandReceiver);
+    }
+
+    @Override
+    protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
+        builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext()));
+        builder.notify(getCharacteristic(IdasenConstants.CHARACTERISTIC_HEIGHT), true);
+        sendCommand("dpg", IdasenConstants.CHARACTERISTIC_DPG, IdasenConstants.CMD_DPG_WAKEUP_PREP);
+        sendCommand("dpg", IdasenConstants.CHARACTERISTIC_DPG, IdasenConstants.CMD_DPG_WAKEUP);
+        sendCommand("dpg", IdasenConstants.CHARACTERISTIC_COMMAND, IdasenConstants.CMD_WAKEUP);
+        initBroadcast();
+        builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZED, getContext()));
+        return builder;
+    }
+
+    @Override
+    public boolean onCharacteristicRead(BluetoothGatt gatt,
+                                        BluetoothGattCharacteristic characteristic,
+                                        int status) {
+
+        if (characteristic.getUuid().equals(IdasenConstants.CHARACTERISTIC_HEIGHT)) {
+            getDeskValues(characteristic);
+            announceDeskValues();
+            return true;
+        }
+        return super.onCharacteristicRead(gatt, characteristic, status);
+    }
+
+    @Override
+    public boolean onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
+
+        if (characteristic.getUuid().equals(IdasenConstants.CHARACTERISTIC_HEIGHT)) {
+            getDeskValues(characteristic);
+            announceDeskValues();
+            return true;
+        }
+        return super.onCharacteristicChanged(gatt, characteristic);
+    }
+
+    private void readCharacteristic(String taskName, UUID charac) {
+        BluetoothGattCharacteristic characteristic = getCharacteristic(charac);
+
+        TransactionBuilder builder = new TransactionBuilder(taskName);
+        builder.read(characteristic);
+        builder.queue(getQueue());
+    }
+
+    private void initBroadcast() {
+        LocalBroadcastManager broadcastManager = LocalBroadcastManager.getInstance(getContext());
+
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(COMMAND_UP);
+        filter.addAction(COMMAND_DOWN);
+        filter.addAction(COMMAND_SET_HEIGHT);
+        filter.addAction(COMMAND_GET_DESK_VALUES);
+
+        broadcastManager.registerReceiver(commandReceiver, filter);
+    }
+
+    private void getDeskValues(BluetoothGattCharacteristic characteristic) {
+        byte[] value = characteristic.getValue();
+        final ByteBuffer buf = ByteBuffer.wrap(value).order(ByteOrder.LITTLE_ENDIAN);
+        int hh = BLETypeConversions.toUnsigned(buf.getShort());
+        deskHeight = (float) IdasenConstants.MIN_HEIGHT + hh / 10000F;
+        deskSpeed = buf.getShort() / 10000F;
+    }
+
+    private void announceDeskValues() {
+        final Intent intent = new Intent(IdasenConstants.ACTION_REALTIME_DESK_VALUES)
+                .putExtra(IdasenConstants.EXTRA_DESK_HEIGHT, deskHeight)
+                .putExtra(IdasenConstants.EXTRA_DESK_SPEED, deskSpeed);
+        LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent);
+    }
+
+    private void sendCommand(String taskName, UUID charac,  byte[] contents) {
+        TransactionBuilder builder = new TransactionBuilder(taskName);
+        BluetoothGattCharacteristic characteristic = getCharacteristic(charac);
+        if (characteristic != null) {
+            builder.write(characteristic, contents);
+            builder.queue(getQueue());
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/itag/ITagSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/itag/ITagSupport.java
new file mode 100644
index 0000000..b7f9eea
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/itag/ITagSupport.java
@@ -0,0 +1,158 @@
+/*  Copyright (C) 2020-2024 Arjan Schrijver, Taavi Eom盲e
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.itag;
+
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.content.Intent;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
+import nodomain.freeyourgadget.gadgetbridge.devices.itag.ITagConstants;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.GattService;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.IntentListener;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.battery.BatteryInfoProfile;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo.DeviceInfoProfile;
+
+public class ITagSupport extends AbstractBTLEDeviceSupport {
+
+    private static final Logger LOG = LoggerFactory.getLogger(ITagSupport.class);
+    private final GBDeviceEventBatteryInfo batteryCmd = new GBDeviceEventBatteryInfo();
+    private final DeviceInfoProfile<ITagSupport> deviceInfoProfile;
+    private final BatteryInfoProfile<ITagSupport> batteryInfoProfile;
+
+    private final IntentListener mListener = new IntentListener() {
+        @Override
+        public void notify(Intent intent) {
+            String s = intent.getAction();
+            if (s.equals(DeviceInfoProfile.ACTION_DEVICE_INFO)) {
+                handleDeviceInfo((nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo.DeviceInfo) intent.getParcelableExtra(DeviceInfoProfile.EXTRA_DEVICE_INFO));
+            } else if (s.equals(BatteryInfoProfile.ACTION_BATTERY_INFO)) {
+                handleBatteryInfo((nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.battery.BatteryInfo) intent.getParcelableExtra(BatteryInfoProfile.EXTRA_BATTERY_INFO));
+            }
+        }
+    };
+
+    public ITagSupport() {
+        super(LOG);
+        addSupportedService(GattService.UUID_SERVICE_GENERIC_ACCESS);
+        addSupportedService(GattService.UUID_SERVICE_GENERIC_ATTRIBUTE);
+        addSupportedService(GattService.UUID_SERVICE_BATTERY_SERVICE);
+
+        addSupportedService(GattService.UUID_SERVICE_IMMEDIATE_ALERT);
+        addSupportedService(ITagConstants.UUID_SERVICE_BUTTON);
+
+
+        deviceInfoProfile = new DeviceInfoProfile<>(this);
+        deviceInfoProfile.addListener(mListener);
+        batteryInfoProfile = new BatteryInfoProfile<>(this);
+        batteryInfoProfile.addListener(mListener);
+
+        addSupportedProfile(deviceInfoProfile);
+        addSupportedProfile(batteryInfoProfile);
+    }
+
+    private void handleBatteryInfo(nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.battery.BatteryInfo info) {
+        batteryCmd.level = (short) info.getPercentCharged();
+        handleGBDeviceEvent(batteryCmd);
+    }
+
+    @Override
+    protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
+        builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext()));
+        requestDeviceInfo(builder);
+        setInitialized(builder);
+        batteryInfoProfile.requestBatteryInfo(builder);
+        return builder;
+    }
+
+    private void requestDeviceInfo(TransactionBuilder builder) {
+        LOG.debug("Requesting device info!");
+        deviceInfoProfile.requestDeviceInfo(builder);
+    }
+
+    private void setInitialized(TransactionBuilder builder) {
+        builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZED, getContext()));
+    }
+
+
+    @Override
+    public boolean useAutoConnect() {
+        return true;
+    }
+
+    private void handleDeviceInfo(nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo.DeviceInfo info) {
+
+    }
+
+    @Override
+    public void onFindDevice(boolean start) {
+        onSetConstantVibration(start ? 0x02 : 0x00);
+    }
+
+    @Override
+    public void onSetConstantVibration(int intensity) {
+        getQueue().clear();
+        BluetoothGattCharacteristic characteristic = getCharacteristic(ITagConstants.UUID_LINK_LOSS_ALERT_LEVEL);
+
+        TransactionBuilder builder = new TransactionBuilder("beeping");
+        builder.write(characteristic, new byte[]{(byte) intensity});
+        builder.queue(getQueue());
+    }
+
+    @Override
+    public boolean onCharacteristicChanged(BluetoothGatt gatt,
+                                           BluetoothGattCharacteristic characteristic) {
+        if (super.onCharacteristicChanged(gatt, characteristic)) {
+            return true;
+        }
+
+        UUID characteristicUUID = characteristic.getUuid();
+        LOG.info("Unhandled characteristic changed: " + characteristicUUID);
+        return false;
+    }
+
+    @Override
+    public boolean onCharacteristicRead(BluetoothGatt gatt,
+                                        BluetoothGattCharacteristic characteristic, int status) {
+        if (super.onCharacteristicRead(gatt, characteristic, status)) {
+            return true;
+        }
+        UUID characteristicUUID = characteristic.getUuid();
+
+        LOG.info("Unhandled characteristic read: " + characteristicUUID);
+        return false;
+    }
+
+    @Override
+    public boolean getImplicitCallbackModify() {
+        return true;
+    }
+
+    @Override
+    public boolean getSendWriteRequestResponse() {
+        return false;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/jyou/BFH16DeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/jyou/BFH16DeviceSupport.java
new file mode 100644
index 0000000..d85d737
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/jyou/BFH16DeviceSupport.java
@@ -0,0 +1,552 @@
+/*  Copyright (C) 2019-2024 Arjan Schrijver, Damien Gaignon, Sophanimus
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+
+/*
+    Features:
+                                            Working
+                                                    work in progress
+                                                            possible
+                                                                    not supported
+    Get firmware version                    (X)     ()      (X)     ()
+    Get battery level                       (X)     ()      (X)     ()
+
+    Set alarms (1-3)                        (X)     ()      (X)     ()
+    Sync date and time                      (X)     ()      (X)     ()
+    Find device                             (X)     ()      (X)     ()
+
+    Switch 12/24 hour mode                  ()      ()      (X)     ()
+    Set step goal                           ()      ()      (X)     ()
+    Set sitting reminder                    ()      ()      (X)     ()
+    Trigger a photo                         ()      ()      (X)     ()
+
+    Switch automated heartbeat detection    (X)     ()      (X)     ()
+    Switch display illumination             ()      ()      (X)     ()
+    Switch vibration                        ()      ()      (X)     ()
+    Switch notifications                    ()      ()      (X)     ()
+    Set do not distract time                ()      ()      (X)     ()
+
+    Get Steps                               ()      ()      (X)     ()
+    Get Heart Rate                          ()      ()      (X)     ()
+    Get Blood Pressure                      ()      ()      (x)     ()
+    Get Blood Satiation                     ()      ()      (X)     ()
+
+    Send Notification                       ()      ()      (X)     ()
+
+ */
+
+
+package nodomain.freeyourgadget.gadgetbridge.service.devices.jyou;
+
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.widget.Toast;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo;
+import nodomain.freeyourgadget.gadgetbridge.devices.jyou.BFH16Constants;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
+import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.util.AlarmUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
+
+public class BFH16DeviceSupport extends AbstractBTLEDeviceSupport {
+    private static final Logger LOG = LoggerFactory.getLogger(BFH16DeviceSupport.class);
+
+    public BluetoothGattCharacteristic ctrlCharacteristic = null;
+    public BluetoothGattCharacteristic measureCharacteristic = null;
+
+    private final GBDeviceEventVersionInfo versionCmd = new GBDeviceEventVersionInfo();
+    private final GBDeviceEventBatteryInfo batteryCmd = new GBDeviceEventBatteryInfo();
+
+    public BFH16DeviceSupport() {
+        super(LOG);
+        addSupportedService(BFH16Constants.BFH16_SERVICE1);
+        addSupportedService(BFH16Constants.BFH16_SERVICE2);
+    }
+
+    @Override
+    public boolean useAutoConnect() {
+        return true;
+    }
+
+    @Override
+    protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
+        LOG.info("Initializing BFH16");
+
+        gbDevice.setState(GBDevice.State.INITIALIZING);
+        gbDevice.sendDeviceUpdateIntent(getContext());
+
+        measureCharacteristic = getCharacteristic(BFH16Constants.BFH16_SERVICE1_NOTIFY);
+        ctrlCharacteristic = getCharacteristic(BFH16Constants.BFH16_SERVICE1_WRITE);
+
+        builder.setCallback(this);
+        builder.notify(measureCharacteristic, true);
+
+        syncSettings(builder);
+
+        gbDevice.setState(GBDevice.State.INITIALIZED);
+        gbDevice.sendDeviceUpdateIntent(getContext());
+
+        LOG.info("Initialization BFH16 Done");
+
+        return builder;
+    }
+
+    //onXYZ
+    //______________________________________________________________________________________________
+
+    //TODO check TODOs in method
+    @Override
+    public boolean onCharacteristicChanged(BluetoothGatt gatt,
+                                           BluetoothGattCharacteristic characteristic) {
+        if (super.onCharacteristicChanged(gatt, characteristic)) {
+            return true;
+        }
+
+        UUID characteristicUUID = characteristic.getUuid();
+        byte[] data = characteristic.getValue();
+        if (data.length == 0)
+            return true;
+
+        switch (data[0]) {
+            case BFH16Constants.RECEIVE_DEVICE_INFO:
+                int fwVerNum = data[4] & 0xFF;
+                versionCmd.fwVersion = (fwVerNum / 100) + "." + ((fwVerNum % 100) / 10) + "." + ((fwVerNum % 100) % 10);
+                handleGBDeviceEvent(versionCmd);
+                LOG.info("Firmware version is: " + versionCmd.fwVersion);
+                return true;
+            case BFH16Constants.RECEIVE_BATTERY_LEVEL:
+                batteryCmd.level = data[8];
+                handleGBDeviceEvent(batteryCmd);
+                LOG.info("Battery level is: " + batteryCmd.level);
+                return true;
+            case BFH16Constants.RECEIVE_STEPS_DATA:
+                int steps = ByteBuffer.wrap(data, 5, 4).getInt();
+                //TODO handle step data
+                LOG.info("Number of walked steps: " + steps);
+                return true;
+            case BFH16Constants.RECEIVE_HEART_DATA:
+                //TODO handle heart data
+                LOG.info("Current heart rate: "     + data[1]);
+                LOG.info("Current blood pressure: " + data[2] + "/" + data[3]);
+                LOG.info("Current satiation: "      + data[4]);
+                return true;
+            case BFH16Constants.RECEIVE_PHOTO_TRIGGER:
+                //TODO handle photo trigger
+                LOG.info("Received photo trigger: " + data[8]);
+                return true;
+            default:
+                LOG.info("Unhandled characteristic change: " + characteristicUUID + " code: " + String.format("0x%1x ...", data[0]));
+                LOG.info("Unhandled characteristic data: "+ data[0]+" "+data[1]+" "+data[2]+" "+data[3]+" "+data[4]+" "+data[5]+" "+data[6]+" "+data[7]+" "+data[8]);
+                return true;
+        }
+    }
+
+
+    //working
+    @Override
+    public void onSetAlarms(ArrayList<? extends Alarm> alarms) {
+        try {
+            TransactionBuilder builder = performInitialized("SetAlarms");
+
+            for (int i = 0; i < alarms.size(); i++)
+            {
+                byte cmd;
+                switch (i) {
+                    case 0:
+                        cmd = BFH16Constants.CMD_SET_ALARM_1;
+                        break;
+                    case 1:
+                        cmd = BFH16Constants.CMD_SET_ALARM_2;
+                        break;
+                    case 2:
+                        cmd = BFH16Constants.CMD_SET_ALARM_3;
+                        break;
+                    default:
+                        return;
+                }
+                Calendar cal = AlarmUtils.toCalendar(alarms.get(i));
+                builder.write(ctrlCharacteristic, commandWithChecksum(
+                        cmd,
+                        alarms.get(i).getEnabled() ? cal.get(Calendar.HOUR_OF_DAY) : -1,
+                        alarms.get(i).getEnabled() ? cal.get(Calendar.MINUTE) : -1
+                ));
+            }
+            builder.queue(getQueue());
+            GB.toast(getContext(), "Alarm settings applied - do note that the current device does not support day specification", Toast.LENGTH_LONG, GB.INFO);
+        } catch(IOException e) {
+            LOG.warn(e.getMessage());
+        }
+    }
+
+    //working
+    @Override
+    public void onSetTime() {
+        try {
+            TransactionBuilder builder = performInitialized("SetTime");
+            syncDateAndTime(builder);
+            builder.queue(getQueue());
+        } catch(IOException e) {
+            LOG.warn(e.getMessage());
+        }
+    }
+
+    //working
+    @Override
+    public void onFindDevice(boolean start) {
+        try {
+            TransactionBuilder builder = performInitialized("FindDevice");
+            builder.write(ctrlCharacteristic, commandWithChecksum(
+                    BFH16Constants.CMD_VIBRATE, 0, start ? 1 : 0
+            ));
+            builder.queue(getQueue());
+        } catch(Exception e) {
+            LOG.warn(e.getMessage());
+        }
+        GB.toast(getContext(), "Your device will vibrate 3 times!", Toast.LENGTH_LONG, GB.INFO);
+    }
+
+
+    //TODO: checked + rework
+    @Override
+    public void onNotification(NotificationSpec notificationSpec) {
+        String notificationTitle = StringUtils.getFirstOf(notificationSpec.sender, notificationSpec.title);
+        byte icon;
+        switch (notificationSpec.type) {
+            case GENERIC_SMS:
+                icon = BFH16Constants.ICON_SMS;
+                break;
+            case FACEBOOK:
+            case FACEBOOK_MESSENGER:
+                icon = BFH16Constants.ICON_FACEBOOK;
+                break;
+            case TWITTER:
+                icon = BFH16Constants.ICON_TWITTER;
+                break;
+            case WHATSAPP:
+                icon = BFH16Constants.ICON_WHATSAPP;
+                break;
+            default:
+                icon = BFH16Constants.ICON_LINE;
+                break;
+        }
+        showNotification(icon, notificationTitle, notificationSpec.body);
+    }
+
+    //TODO: check
+    @Override
+    public void onSetCallState(CallSpec callSpec) {
+        switch (callSpec.command) {
+            case CallSpec.CALL_INCOMING:
+                showNotification(BFH16Constants.ICON_CALL, callSpec.name, callSpec.number);
+                break;
+        }
+    }
+
+    //TODO: check
+    @Override
+    public void onEnableRealtimeSteps(boolean enable) {
+        onEnableRealtimeHeartRateMeasurement(enable);
+    }
+
+    //TODO: check
+    @Override
+    public void onReset(int flags) {
+        try {
+            TransactionBuilder builder = performInitialized("Reboot");
+            builder.write(ctrlCharacteristic, commandWithChecksum(
+                    BFH16Constants.CMD_ACTION_REBOOT_DEVICE, 0, 0
+            ));
+            builder.queue(getQueue());
+        } catch(Exception e) {
+            LOG.warn(e.getMessage());
+        }
+    }
+
+    //TODO: check
+    @Override
+    public void onHeartRateTest() {
+        try {
+            TransactionBuilder builder = performInitialized("HeartRateTest");
+            builder.write(ctrlCharacteristic, commandWithChecksum(
+                    BFH16Constants.CMD_MEASURE_HEART, 0, 1
+            ));
+            builder.queue(getQueue());
+        } catch(Exception e) {
+            LOG.warn(e.getMessage());
+        }
+    }
+
+    //TODO: check
+    @Override
+    public void onEnableRealtimeHeartRateMeasurement(boolean enable) {
+        // TODO: test
+        try {
+            TransactionBuilder builder = performInitialized("RealTimeHeartMeasurement");
+            builder.write(ctrlCharacteristic, commandWithChecksum(
+                    BFH16Constants.CMD_MEASURE_HEART, 0, enable ? 1 : 0
+            ));
+            builder.queue(getQueue());
+        } catch(Exception e) {
+            LOG.warn(e.getMessage());
+        }
+    }
+
+    //TODO: check
+    @Override
+    public void onSetConstantVibration(int integer) {
+        try {
+            TransactionBuilder builder = performInitialized("Vibrate");
+            builder.write(ctrlCharacteristic, commandWithChecksum(
+                    BFH16Constants.CMD_VIBRATE, 0, 1
+            ));
+            builder.queue(getQueue());
+        } catch(Exception e) {
+            LOG.warn(e.getMessage());
+        }
+    }
+
+    @Override
+    public void onTestNewFunction() {
+
+        showNotification((byte)0xFF, "", "");
+
+//        try {
+//            TransactionBuilder builder = performInitialized("TestNewFunction");
+//
+//            //Test new command
+//            builder.write(ctrlCharacteristic, commandWithChecksum( (byte)0x32, 0, 0));
+//            builder.queue(getQueue());
+//
+//            GB.toast(getContext(), "TestNewFunction executed!", Toast.LENGTH_LONG, GB.INFO);
+//
+//        } catch(IOException e) {
+//            LOG.warn(e.getMessage());
+//        }
+
+    }
+
+    
+
+    //FUNCTIONS
+    //______________________________________________________________________________________________
+
+    //TODO: check
+    private void showNotification(byte icon, String title, String message) {
+        try {
+            TransactionBuilder builder = performInitialized("ShowNotification");
+
+            byte[] titleBytes = stringToUTF8Bytes(title, 16);
+            byte[] messageBytes = stringToUTF8Bytes(message, 80);
+
+            for (int i = 1; i <= 7; i++)
+            {
+                byte[] currentPacket = new byte[20];
+                currentPacket[0] = BFH16Constants.CMD_ACTION_SHOW_NOTIFICATION;
+                currentPacket[1] = 7;
+                currentPacket[2] = (byte)i;
+                switch(i) {
+                    case 1:
+                        currentPacket[4] = icon;
+                        break;
+                    case 2:
+                        if (titleBytes != null) {
+                            System.arraycopy(titleBytes, 0, currentPacket, 3, 6);
+                            System.arraycopy(titleBytes, 6, currentPacket, 10, 10);
+                        }
+                        break;
+                    default:
+                        if (messageBytes != null) {
+                            System.arraycopy(messageBytes, 16 * (i - 3), currentPacket, 3, 6);
+                            System.arraycopy(messageBytes, 6 + 16 * (i - 3), currentPacket, 10, 10);
+                        }
+                        break;
+                }
+                builder.write(ctrlCharacteristic, currentPacket);
+            }
+            builder.queue(getQueue());
+        } catch (IOException e) {
+            LOG.warn(e.getMessage());
+        }
+    }
+
+
+    //TODO: check
+    private void syncSettings(TransactionBuilder builder) {
+        syncDateAndTime(builder);
+
+        // TODO: unhardcode and separate stuff
+        builder.write(ctrlCharacteristic, commandWithChecksum(
+                BFH16Constants.CMD_SET_HEARTRATE_WARNING_VALUE, 0, 152
+        ));
+        builder.write(ctrlCharacteristic, commandWithChecksum(
+                BFH16Constants.CMD_SET_TARGET_STEPS, 0, 10000
+        ));
+        builder.write(ctrlCharacteristic, commandWithChecksum(
+                BFH16Constants.CMD_SWITCH_METRIC_IMPERIAL, 0, 0
+        ));
+        builder.write(ctrlCharacteristic, commandWithChecksum(
+                BFH16Constants.CMD_GET_SLEEP_TIME, 0, 0
+        ));
+        builder.write(ctrlCharacteristic, commandWithChecksum(
+                BFH16Constants.CMD_SET_NOON_TIME, 12 * 60 * 60, 14 * 60 * 60 // 12:00 - 14:00
+        ));
+        builder.write(ctrlCharacteristic, commandWithChecksum(
+                BFH16Constants.CMD_SET_SLEEP_TIME, 21 * 60 * 60, 8 * 60 * 60 // 21:00 - 08:00
+        ));
+        builder.write(ctrlCharacteristic, commandWithChecksum(
+                BFH16Constants.CMD_SET_INACTIVITY_WARNING_TIME, 0, 0
+        ));
+
+        // do not disturb and a couple more features
+        byte dndStartHour = 22;
+        byte dndStartMin = 0;
+        byte dndEndHour = 8;
+        byte dndEndMin = 0;
+        boolean dndToggle = false;
+        boolean vibrationToggle = true;
+        boolean wakeOnRaiseToggle = true;
+        builder.write(ctrlCharacteristic, commandWithChecksum(
+                BFH16Constants.CMD_SET_DND_SETTINGS,
+                (dndStartHour << 24) | (dndStartMin << 16) | (dndEndHour << 8) | dndEndMin,
+                ((dndToggle ? 0 : 1) << 2) | ((vibrationToggle ? 1 : 0) << 1) | (wakeOnRaiseToggle ? 1 : 0)
+        ));
+    }
+
+
+    //working
+    private void syncDateAndTime(TransactionBuilder builder) {
+        Calendar cal = Calendar.getInstance();
+        String strYear = String.valueOf(cal.get(Calendar.YEAR));
+        byte year1 = (byte)Integer.parseInt(strYear.substring(0, 2));
+        byte year2 = (byte)Integer.parseInt(strYear.substring(2, 4));
+        byte month = (byte)cal.get(Calendar.MONTH);
+        byte day = (byte)cal.get(Calendar.DAY_OF_MONTH);
+        byte hour = (byte)cal.get(Calendar.HOUR_OF_DAY);
+        byte minute = (byte)cal.get(Calendar.MINUTE);
+        byte second = (byte)cal.get(Calendar.SECOND);
+        byte weekDay = (byte)cal.get(Calendar.DAY_OF_WEEK);
+
+        builder.write(ctrlCharacteristic, commandWithChecksum(
+                BFH16Constants.CMD_SET_DATE_AND_TIME,
+                (year1 << 24) | (year2 << 16) | (month << 8) | day,
+                (hour << 24) | (minute << 16) | (second << 8) | weekDay
+        ));
+    }
+
+    //working
+    private byte[] commandWithChecksum(byte cmd, int argSlot1, int argSlot2)
+    {
+        ByteBuffer buf = ByteBuffer.allocate(10);
+        buf.put(cmd);
+        buf.putInt(argSlot1);
+        buf.putInt(argSlot2);
+
+        byte[] bytesToWrite = buf.array();
+
+        byte checksum = 0;
+        for (byte b : bytesToWrite) {
+            checksum += b;
+        }
+
+        bytesToWrite[9] = checksum;
+
+        return bytesToWrite;
+    }
+
+    /**
+     * Checksum is calculated by the sum of bytes 0 to 8 and send as byte 9
+     */
+    private byte[] commandWithChecksum(byte s0, byte s1, byte s2, byte s3, byte s4, byte s5, byte s6, byte s7, byte s8)
+    {
+        ByteBuffer buf = ByteBuffer.allocate(10);
+        buf.put(s0);
+        buf.put(s1);
+        buf.put(s2);
+        buf.put(s3);
+        buf.put(s4);
+        buf.put(s5);
+        buf.put(s6);
+        buf.put(s7);
+        buf.put(s8);
+
+        byte[] bytesToWrite = buf.array();
+
+        byte checksum = 0;
+        for (byte b : bytesToWrite) {
+            checksum += b;
+        }
+
+        //checksum = (byte) ((byte) checksum & (byte) 0xFF);  //TODO EXPERIMENTAL
+
+        LOG.debug("Checksum = " + checksum);
+
+        bytesToWrite[9] = checksum;
+
+        return bytesToWrite;
+    }
+
+    private byte[] stringToUTF8Bytes(String src, int byteCount) {
+        try {
+            if (src == null)
+                return null;
+
+            for (int i = src.length(); i > 0; i--) {
+                String sub = src.substring(0, i);
+                byte[] subUTF8 = sub.getBytes("UTF-8");
+
+                if (subUTF8.length == byteCount) {
+                    return subUTF8;
+                }
+
+                if (subUTF8.length < byteCount) {
+                    byte[] largerSubUTF8 = new byte[byteCount];
+                    System.arraycopy(subUTF8, 0, largerSubUTF8, 0, subUTF8.length);
+                    return largerSubUTF8;
+                }
+            }
+        } catch (UnsupportedEncodingException e) {
+            LOG.warn(e.getMessage());
+        }
+        return null;
+    }
+
+    @Override
+    public boolean getImplicitCallbackModify() {
+        return true;
+    }
+
+    @Override
+    public boolean getSendWriteRequestResponse() {
+        return false;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/jyou/JYouSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/jyou/JYouSupport.java
new file mode 100644
index 0000000..a70f20f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/jyou/JYouSupport.java
@@ -0,0 +1,339 @@
+/*  Copyright (C) 2020-2024 Arjan Schrijver, Damien Gaignon, Da Pa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.jyou;
+
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.widget.Toast;
+
+import org.slf4j.Logger;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Calendar;
+
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo;
+import nodomain.freeyourgadget.gadgetbridge.devices.jyou.JYouConstants;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
+import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.util.AlarmUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
+
+public class JYouSupport extends AbstractBTLEDeviceSupport {
+
+    private Logger logger;
+
+    protected BluetoothGattCharacteristic ctrlCharacteristic = null;
+
+    protected final GBDeviceEventVersionInfo versionCmd = new GBDeviceEventVersionInfo();
+    protected final GBDeviceEventBatteryInfo batteryCmd = new GBDeviceEventBatteryInfo();
+
+    public JYouSupport(Logger logger) {
+        super(logger);
+        this.logger = logger;
+        if (logger == null) {
+            throw new IllegalArgumentException("logger must not be null");
+        }
+        addSupportedService(JYouConstants.UUID_SERVICE_JYOU);
+    }
+
+    @Override
+    protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
+        logger.info("Initializing");
+
+        gbDevice.setState(GBDevice.State.INITIALIZING);
+        gbDevice.sendDeviceUpdateIntent(getContext());
+
+        BluetoothGattCharacteristic measureCharacteristic = getCharacteristic(JYouConstants.UUID_CHARACTERISTIC_MEASURE);
+        ctrlCharacteristic = getCharacteristic(JYouConstants.UUID_CHARACTERISTIC_CONTROL);
+
+        builder.setCallback(this);
+        builder.notify(measureCharacteristic, true);
+
+        syncSettings(builder);
+
+        gbDevice.setState(GBDevice.State.INITIALIZED);
+        gbDevice.sendDeviceUpdateIntent(getContext());
+
+        logger.info("Initialization Done");
+
+        return builder;
+    }
+
+    @Override
+    public boolean onCharacteristicChanged(BluetoothGatt gatt,
+                                           BluetoothGattCharacteristic characteristic) {
+        return super.onCharacteristicChanged(gatt, characteristic);
+    }
+
+    protected void syncDateAndTime(TransactionBuilder builder) {
+        Calendar cal = Calendar.getInstance();
+        String strYear = String.valueOf(cal.get(Calendar.YEAR));
+        byte year1 = (byte)Integer.parseInt(strYear.substring(0, 2));
+        byte year2 = (byte)Integer.parseInt(strYear.substring(2, 4));
+        byte month = (byte)cal.get(Calendar.MONTH);
+        byte day = (byte)cal.get(Calendar.DAY_OF_MONTH);
+        byte hour = (byte)cal.get(Calendar.HOUR_OF_DAY);
+        byte minute = (byte)cal.get(Calendar.MINUTE);
+        byte second = (byte)cal.get(Calendar.SECOND);
+        byte weekDay = (byte)cal.get(Calendar.DAY_OF_WEEK);
+
+        builder.write(ctrlCharacteristic, commandWithChecksum(
+                JYouConstants.CMD_SET_DATE_AND_TIME,
+                (year1 << 24) | (year2 << 16) | (month << 8) | day,
+                (hour << 24) | (minute << 16) | (second << 8) | weekDay
+        ));
+    }
+
+    protected void syncSettings(TransactionBuilder builder) {
+    }
+
+    private void showNotification(byte icon, String title, String message) {
+        try {
+            TransactionBuilder builder = performInitialized("ShowNotification");
+
+            byte[] titleBytes = stringToUTF8Bytes(title, 16);
+            byte[] messageBytes = stringToUTF8Bytes(message, 80);
+
+            for (int i = 1; i <= 7; i++)
+            {
+                byte[] currentPacket = new byte[20];
+                currentPacket[0] = JYouConstants.CMD_ACTION_SHOW_NOTIFICATION;
+                currentPacket[1] = 7;
+                currentPacket[2] = (byte)i;
+                switch(i) {
+                    case 1:
+                        currentPacket[4] = icon;
+                        break;
+                    case 2:
+                        if (titleBytes != null) {
+                            System.arraycopy(titleBytes, 0, currentPacket, 3, 6);
+                            System.arraycopy(titleBytes, 6, currentPacket, 10, 10);
+                        }
+                        break;
+                    default:
+                        if (messageBytes != null) {
+                            System.arraycopy(messageBytes, 16 * (i - 3), currentPacket, 3, 6);
+                            System.arraycopy(messageBytes, 6 + 16 * (i - 3), currentPacket, 10, 10);
+                        }
+                        break;
+                }
+                builder.write(ctrlCharacteristic, currentPacket);
+            }
+            performConnected(builder.getTransaction());
+        } catch (IOException e) {
+            logger.warn(e.getMessage());
+        }
+    }
+
+    @Override
+    public boolean useAutoConnect() {
+        return true;
+    }
+
+    @Override
+    public void onNotification(NotificationSpec notificationSpec) {
+        String notificationTitle = StringUtils.getFirstOf(notificationSpec.sender, notificationSpec.title);
+        byte icon;
+        switch (notificationSpec.type) {
+            case GENERIC_SMS:
+                icon = JYouConstants.ICON_SMS;
+                break;
+            case FACEBOOK:
+            case FACEBOOK_MESSENGER:
+                icon = JYouConstants.ICON_FACEBOOK;
+                break;
+            case TWITTER:
+                icon = JYouConstants.ICON_TWITTER;
+                break;
+            case WHATSAPP:
+                icon = JYouConstants.ICON_WHATSAPP;
+                break;
+            default:
+                icon = JYouConstants.ICON_LINE;
+                break;
+        }
+        showNotification(icon, notificationTitle, notificationSpec.body);
+    }
+
+    @Override
+    public void onSetAlarms(ArrayList<? extends Alarm> alarms) {
+        try {
+            TransactionBuilder builder = performInitialized("SetAlarms");
+
+            for (int i = 0; i < alarms.size(); i++)
+            {
+                byte cmd;
+                switch (i) {
+                    case 0:
+                        cmd = JYouConstants.CMD_SET_ALARM_1;
+                        break;
+                    case 1:
+                        cmd = JYouConstants.CMD_SET_ALARM_2;
+                        break;
+                    case 2:
+                        cmd = JYouConstants.CMD_SET_ALARM_3;
+                        break;
+                    default:
+                        return;
+                }
+                Calendar cal = AlarmUtils.toCalendar(alarms.get(i));
+                builder.write(ctrlCharacteristic, commandWithChecksum(
+                        cmd,
+                        alarms.get(i).getEnabled() ? cal.get(Calendar.HOUR_OF_DAY) : -1,
+                        alarms.get(i).getEnabled() ? cal.get(Calendar.MINUTE) : -1
+                ));
+            }
+            performConnected(builder.getTransaction());
+            GB.toast(getContext(), "Alarm settings applied - do note that the current device does not support day specification", Toast.LENGTH_LONG, GB.INFO);
+        } catch(IOException e) {
+            logger.warn(e.getMessage());
+        }
+    }
+
+    @Override
+    public void onSetTime() {
+        try {
+            TransactionBuilder builder = performInitialized("SetTime");
+            syncDateAndTime(builder);
+            performConnected(builder.getTransaction());
+        } catch(IOException e) {
+            logger.warn(e.getMessage());
+        }
+    }
+
+    @Override
+    public void onSetCallState(CallSpec callSpec) {
+        if(callSpec.command == CallSpec.CALL_INCOMING) {
+            showNotification(JYouConstants.ICON_CALL, callSpec.name, callSpec.number);
+        }
+    }
+
+    @Override
+    public void onEnableRealtimeSteps(boolean enable) {
+        onEnableRealtimeHeartRateMeasurement(enable);
+    }
+
+    @Override
+    public void onReset(int flags) {
+        try {
+            TransactionBuilder builder = performInitialized("Reboot");
+            builder.write(ctrlCharacteristic, commandWithChecksum(
+                    JYouConstants.CMD_ACTION_REBOOT_DEVICE, 0, 0
+            ));
+            performConnected(builder.getTransaction());
+        } catch(Exception e) {
+            logger.warn(e.getMessage());
+        }
+    }
+
+    @Override
+    public void onHeartRateTest() {
+        try {
+            TransactionBuilder builder = performInitialized("HeartRateTest");
+            builder.write(ctrlCharacteristic, commandWithChecksum(
+                    JYouConstants.CMD_ACTION_HEARTRATE_SWITCH, 0, 1
+            ));
+            performConnected(builder.getTransaction());
+        } catch(Exception e) {
+            logger.warn(e.getMessage());
+        }
+    }
+
+    @Override
+    public void onEnableRealtimeHeartRateMeasurement(boolean enable) {
+        try {
+            TransactionBuilder builder = performInitialized("RealTimeHeartMeasurement");
+            builder.write(ctrlCharacteristic, commandWithChecksum(
+                    JYouConstants.CMD_SET_HEARTRATE_AUTO, 0, enable ? 1 : 0
+            ));
+            builder.queue(getQueue());
+        } catch(Exception e) {
+            logger.warn(e.getMessage());
+        }
+    }
+
+    @Override
+    public void onFindDevice(boolean start) {
+        if (start) {
+            showNotification(JYouConstants.ICON_QQ, "Gadgetbridge", "Bzzt! Bzzt!");
+            GB.toast(getContext(), "As your device doesn't have sound, it will only vibrate 3 times consecutively", Toast.LENGTH_LONG, GB.INFO);
+        }
+    }
+
+    protected byte[] commandWithChecksum(byte cmd, int argSlot1, int argSlot2)
+    {
+        ByteBuffer buf = ByteBuffer.allocate(10);
+        buf.put(cmd);
+        buf.putInt(argSlot1);
+        buf.putInt(argSlot2);
+
+        byte[] bytesToWrite = buf.array();
+
+        byte checksum = 0;
+        for (byte b : bytesToWrite) {
+            checksum += b;
+        }
+
+        bytesToWrite[9] = checksum;
+
+        return bytesToWrite;
+    }
+
+    private byte[] stringToUTF8Bytes(String src, int byteCount) {
+        try {
+            if (src == null)
+                return null;
+
+            for (int i = src.length(); i > 0; i--) {
+                String sub = src.substring(0, i);
+                byte[] subUTF8 = sub.getBytes("UTF-8");
+
+                if (subUTF8.length == byteCount) {
+                    return subUTF8;
+                }
+
+                if (subUTF8.length < byteCount) {
+                    byte[] largerSubUTF8 = new byte[byteCount];
+                    System.arraycopy(subUTF8, 0, largerSubUTF8, 0, subUTF8.length);
+                    return largerSubUTF8;
+                }
+            }
+        } catch (UnsupportedEncodingException e) {
+            logger.warn(e.getMessage());
+        }
+        return null;
+    }
+
+    @Override
+    public boolean getImplicitCallbackModify() {
+        return true;
+    }
+
+    @Override
+    public boolean getSendWriteRequestResponse() {
+        return false;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/jyou/RealtimeSamplesSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/jyou/RealtimeSamplesSupport.java
new file mode 100644
index 0000000..ce366d4
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/jyou/RealtimeSamplesSupport.java
@@ -0,0 +1,111 @@
+/*  Copyright (C) 2018-2024 Pavel Elagin
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.jyou;
+
+import java.util.Timer;
+import java.util.TimerTask;
+
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
+
+public abstract class RealtimeSamplesSupport {
+    private final long delay;
+    private final long period;
+
+    protected int steps;
+    protected int heartrateBpm;
+    private int lastSteps;
+    // subclasses may add more
+
+    private Timer realtimeStorageTimer;
+
+    public RealtimeSamplesSupport(long delay, long period) {
+        this.delay = delay;
+        this.period = period;
+    }
+
+    public synchronized void start() {
+        if (isRunning()) {
+            return; // already running
+        }
+        realtimeStorageTimer = new Timer("JYou Realtime Storage Timer");
+        realtimeStorageTimer.scheduleAtFixedRate(new TimerTask() {
+            @Override
+            public void run() {
+                triggerCurrentSample();
+            }
+        }, delay, period);
+    }
+
+    public synchronized void stop() {
+        if (realtimeStorageTimer != null) {
+            realtimeStorageTimer.cancel();
+            realtimeStorageTimer.purge();
+            realtimeStorageTimer = null;
+        }
+    }
+
+    public synchronized boolean isRunning() {
+        return realtimeStorageTimer != null;
+    }
+
+    public synchronized void setSteps(int stepsPerMinute) {
+        this.steps = stepsPerMinute;
+    }
+
+    /**
+     * Returns the number of steps recorded since the last measurements. If no
+     * steps are available yet, ActivitySample.NOT_MEASURED is returned.
+     * @return
+     */
+    public synchronized int getSteps() {
+        if (steps == ActivitySample.NOT_MEASURED) {
+            return ActivitySample.NOT_MEASURED;
+        }
+        if (lastSteps == 0)  {
+            return ActivitySample.NOT_MEASURED; // wait until we have a delta between two samples
+        }
+        int delta = steps - lastSteps;
+        if (delta < 0) {
+            return 0;
+        }
+        return delta;
+    }
+
+    public void setHeartrateBpm(int hrBpm) {
+        this.heartrateBpm = hrBpm;
+    }
+
+    public int getHeartrateBpm() {
+        return heartrateBpm;
+    }
+
+    public void triggerCurrentSample() {
+        doCurrentSample();
+        resetCurrentValues();
+    }
+
+    protected synchronized void resetCurrentValues() {
+        if (steps >= lastSteps) {
+            lastSteps = steps;
+        }
+        steps = ActivitySample.NOT_MEASURED;
+        heartrateBpm = ActivitySample.NOT_MEASURED;
+    }
+
+    protected abstract void doCurrentSample();
+}
+
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/jyou/TeclastH30/TeclastH30Support.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/jyou/TeclastH30/TeclastH30Support.java
new file mode 100644
index 0000000..6793426
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/jyou/TeclastH30/TeclastH30Support.java
@@ -0,0 +1,120 @@
+/*  Copyright (C) 2018-2024 Da Pa, Pavel Elagin
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.jyou.TeclastH30;
+
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.jyou.JYouConstants;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.jyou.JYouSupport;
+
+public class TeclastH30Support extends JYouSupport {
+
+    private static final Logger LOG = LoggerFactory.getLogger(TeclastH30Support.class);
+
+    public TeclastH30Support() {
+        super(LOG);
+        addSupportedService(JYouConstants.UUID_SERVICE_JYOU);
+    }
+
+    @Override
+    public boolean onCharacteristicChanged(BluetoothGatt gatt,
+                                           BluetoothGattCharacteristic characteristic) {
+        if (super.onCharacteristicChanged(gatt, characteristic)) {
+            return true;
+        }
+
+        UUID characteristicUUID = characteristic.getUuid();
+        byte[] data = characteristic.getValue();
+        if (data.length == 0)
+            return true;
+
+        switch (data[0]) {
+            case JYouConstants.RECEIVE_DEVICE_INFO:
+                int fwVerNum = data[4] & 0xFF;
+                versionCmd.fwVersion = (fwVerNum / 100) + "." + ((fwVerNum % 100) / 10) + "." + ((fwVerNum % 100) % 10);
+                handleGBDeviceEvent(versionCmd);
+                LOG.info("Firmware version is: " + versionCmd.fwVersion);
+                return true;
+            case JYouConstants.RECEIVE_BATTERY_LEVEL:
+                batteryCmd.level = data[8];
+                handleGBDeviceEvent(batteryCmd);
+                LOG.info("Battery level is: " + batteryCmd.level);
+                return true;
+            case JYouConstants.RECEIVE_STEPS_DATA:
+                int steps = ByteBuffer.wrap(data, 5, 4).getInt();
+                LOG.info("Number of walked steps: " + steps);
+                return true;
+            case JYouConstants.RECEIVE_HEARTRATE:
+                LOG.info("Current heart rate: " + data[8]);
+                return true;
+            default:
+                LOG.info("Unhandled characteristic change: " + characteristicUUID + " code: " + String.format("0x%1x ...", data[0]));
+                return true;
+        }
+    }
+
+    @Override
+    protected void syncSettings(TransactionBuilder builder) {
+        syncDateAndTime(builder);
+
+        // TODO: unhardcode and separate stuff
+        builder.write(ctrlCharacteristic, commandWithChecksum(
+                JYouConstants.CMD_SET_HEARTRATE_WARNING_VALUE, 0, 152
+        ));
+        builder.write(ctrlCharacteristic, commandWithChecksum(
+                JYouConstants.CMD_SET_TARGET_STEPS, 0, 10000
+        ));
+        builder.write(ctrlCharacteristic, commandWithChecksum(
+                JYouConstants.CMD_GET_STEP_COUNT, 0, 0
+        ));
+        builder.write(ctrlCharacteristic, commandWithChecksum(
+                JYouConstants.CMD_GET_SLEEP_TIME, 0, 0
+        ));
+        builder.write(ctrlCharacteristic, commandWithChecksum(
+                JYouConstants.CMD_SET_NOON_TIME, 12 * 60 * 60, 14 * 60 * 60 // 12:00 - 14:00
+        ));
+        builder.write(ctrlCharacteristic, commandWithChecksum(
+                JYouConstants.CMD_SET_SLEEP_TIME, 21 * 60 * 60, 8 * 60 * 60 // 21:00 - 08:00
+        ));
+        builder.write(ctrlCharacteristic, commandWithChecksum(
+                JYouConstants.CMD_SET_INACTIVITY_WARNING_TIME, 0, 0
+        ));
+
+        // do not disturb and a couple more features
+        byte dndStartHour = 22;
+        byte dndStartMin = 0;
+        byte dndEndHour = 8;
+        byte dndEndMin = 0;
+        boolean dndToggle = false;
+        boolean vibrationToggle = true;
+        boolean wakeOnRaiseToggle = true;
+        builder.write(ctrlCharacteristic, commandWithChecksum(
+                JYouConstants.CMD_SET_DND_SETTINGS,
+                (dndStartHour << 24) | (dndStartMin << 16) | (dndEndHour << 8) | dndEndMin,
+                ((dndToggle ? 0 : 1) << 2) | ((vibrationToggle ? 1 : 0) << 1) | (wakeOnRaiseToggle ? 1 : 0)
+        ));
+    }
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/jyou/y5/Y5Support.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/jyou/y5/Y5Support.java
new file mode 100644
index 0000000..96d881f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/jyou/y5/Y5Support.java
@@ -0,0 +1,234 @@
+/*  Copyright (C) 2018-2024 Da Pa, Pavel Elagin
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.jyou.y5;
+
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.content.Intent;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.jyou.JYouConstants;
+import nodomain.freeyourgadget.gadgetbridge.devices.jyou.JYouSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.entities.JYouActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.entities.User;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.jyou.JYouSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.jyou.RealtimeSamplesSupport;
+
+public class Y5Support extends JYouSupport {
+    private static final Logger LOG = LoggerFactory.getLogger(Y5Support.class);
+
+    private RealtimeSamplesSupport realtimeSamplesSupport;
+
+
+    public Y5Support() {
+        super(LOG);
+        addSupportedService(JYouConstants.UUID_SERVICE_JYOU);
+    }
+
+    @Override
+    public boolean onCharacteristicChanged(BluetoothGatt gatt,
+                                           BluetoothGattCharacteristic characteristic) {
+        if (super.onCharacteristicChanged(gatt, characteristic)) {
+            return true;
+        }
+
+        UUID characteristicUUID = characteristic.getUuid();
+        byte[] data = characteristic.getValue();
+        if (data.length == 0)
+            return true;
+
+        switch (data[0]) {
+            case JYouConstants.RECEIVE_HISTORY_SLEEP_COUNT:
+                LOG.info("onCharacteristicChanged: " + data[0]);
+                return true;
+            case JYouConstants.RECEIVE_BLOOD_PRESSURE:
+                int heartRate = data[2];
+                int bloodPressureHigh = data[3];
+                int bloodPressureLow = data[4];
+                int bloodOxygen = data[5];
+                int Fatigue = data[6];
+                LOG.info("RECEIVE_BLOOD_PRESSURE: Heart rate: " + heartRate + " Pressure high: " + bloodPressureHigh+ " pressure low: " + bloodPressureLow);
+                return true;
+            case JYouConstants.RECEIVE_DEVICE_INFO:
+                int model = data[7];
+                int fwVerNum = data[4] & 0xFF;
+                versionCmd.fwVersion = (fwVerNum / 100) + "." + ((fwVerNum % 100) / 10) + "." + ((fwVerNum % 100) % 10);
+                handleGBDeviceEvent(versionCmd);
+                LOG.info("Firmware version is: " + versionCmd.fwVersion);
+                return true;
+            case JYouConstants.RECEIVE_BATTERY_LEVEL:
+                batteryCmd.level = data[8];
+                handleGBDeviceEvent(batteryCmd);
+                LOG.info("Battery level is: " + batteryCmd.level);
+                return true;
+            case JYouConstants.RECEIVE_STEPS_DATA:
+                int steps = ByteBuffer.wrap(data, 5, 4).getInt();
+                LOG.info("Number of walked steps: " + steps);
+                handleRealtimeSteps(steps);
+                return true;
+            case JYouConstants.RECEIVE_HEARTRATE:
+                handleHeartrate(data[8]);
+                return true;
+            case JYouConstants.RECEIVE_WATCH_MAC:
+                return true;
+            case JYouConstants.RECEIVE_GET_PHOTO:
+                return true;
+            default:
+                LOG.info("Unhandled characteristic change: " + characteristicUUID + " code: " + String.format("0x%1x ...", data[0]));
+                return true;
+        }
+    }
+
+    private void handleRealtimeSteps(int value) {
+        //todo Call on connect the device
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("realtime steps: " + value);
+        }
+        getRealtimeSamplesSupport().setSteps(value);
+    }
+
+    private void handleHeartrate(int value) {
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("heart rate: " + value);
+        }
+        RealtimeSamplesSupport realtimeSamplesSupport = getRealtimeSamplesSupport();
+        realtimeSamplesSupport.setHeartrateBpm(value);
+        if (!realtimeSamplesSupport.isRunning()) {
+            // single shot measurement, manually invoke storage and result publishing
+            realtimeSamplesSupport.triggerCurrentSample();
+        }
+    }
+
+    public JYouActivitySample createActivitySample(Device device, User user, int timestampInSeconds, SampleProvider provider) {
+        JYouActivitySample sample = new JYouActivitySample();
+        sample.setDevice(device);
+        sample.setUser(user);
+        sample.setTimestamp(timestampInSeconds);
+        sample.setProvider(provider);
+        return sample;
+    }
+
+    private void enableRealtimeSamplesTimer(boolean enable) {
+        if (enable) {
+            getRealtimeSamplesSupport().start();
+        } else {
+            if (realtimeSamplesSupport != null) {
+                realtimeSamplesSupport.stop();
+            }
+        }
+    }
+
+    private RealtimeSamplesSupport getRealtimeSamplesSupport() {
+        if (realtimeSamplesSupport == null) {
+            realtimeSamplesSupport = new RealtimeSamplesSupport(1000, 1000) {
+                @Override
+                public void doCurrentSample() {
+
+                    try (DBHandler handler = GBApplication.acquireDB()) {
+                        DaoSession session = handler.getDaoSession();
+                        int ts = (int) (System.currentTimeMillis() / 1000);
+                        JYouSampleProvider provider = new JYouSampleProvider(gbDevice, session);
+                        JYouActivitySample sample = createActivitySample(DBHelper.getDevice(getDevice(), session), DBHelper.getUser(session), ts, provider);
+                        sample.setHeartRate(getHeartrateBpm());
+                        sample.setRawIntensity(ActivitySample.NOT_MEASURED);
+                        sample.setRawKind(JYouSampleProvider.TYPE_ACTIVITY); // to make it visible in the charts TODO: add a MANUAL kind for that?
+
+                        provider.addGBActivitySample(sample);
+
+                        // set the steps only afterwards, since realtime steps are also recorded
+                        // in the regular samples and we must not count them twice
+                        // Note: we know that the DAO sample is never committed again, so we simply
+                        // change the value here in memory.
+                        sample.setSteps(getSteps());
+                        if(steps > 1){
+                            LOG.debug("Have steps: " + getSteps());
+                        }
+
+                        if (LOG.isDebugEnabled()) {
+                            LOG.debug("realtime sample: " + sample);
+                        }
+
+                        Intent intent = new Intent(DeviceService.ACTION_REALTIME_SAMPLES)
+                                .putExtra(GBDevice.EXTRA_DEVICE, getDevice())
+                                .putExtra(DeviceService.EXTRA_REALTIME_SAMPLE, sample);
+                        LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent);
+
+                    } catch (Exception e) {
+                        LOG.warn("Unable to acquire db for saving realtime samples", e);
+                    }
+                }
+            };
+        }
+        return realtimeSamplesSupport;
+    }
+
+    @Override
+    protected void syncSettings(TransactionBuilder builder) {
+        syncDateAndTime(builder);
+    }
+
+    @Override
+    public void dispose() {
+        LOG.info("Dispose");
+        super.dispose();
+    }
+
+    @Override
+    public void onHeartRateTest() {
+        try {
+            TransactionBuilder builder = performInitialized("HeartRateTest");
+            builder.write(ctrlCharacteristic, commandWithChecksum(
+                    JYouConstants.CMD_SET_HEARTRATE_AUTO, 0, 0
+
+            ));
+            performConnected(builder.getTransaction());
+        } catch(Exception e) {
+            LOG.warn(e.getMessage());
+        }
+    }
+
+    @Override
+    public void onEnableRealtimeHeartRateMeasurement(boolean enable) {
+        try {
+            TransactionBuilder builder = performInitialized("RealTimeHeartMeasurement");
+            builder.write(ctrlCharacteristic, commandWithChecksum(
+                    JYouConstants.CMD_ACTION_HEARTRATE_SWITCH, 0, enable ? 1 : 0
+            ));
+            performConnected(builder.getTransaction());
+            enableRealtimeSamplesTimer(enable);
+        } catch (Exception e) {
+            LOG.warn(e.getMessage());
+        }
+    }
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/LefunDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/LefunDeviceSupport.java
new file mode 100644
index 0000000..060bf54
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/LefunDeviceSupport.java
@@ -0,0 +1,1036 @@
+/*  Copyright (C) 2020-2024 Arjan Schrijver, Damien Gaignon, Jos茅 Rebelo,
+    Yukai Li
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.lefun;
+
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.text.format.DateFormat;
+import android.widget.Toast;
+
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.List;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+import de.greenrobot.dao.query.Query;
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.SettingsActivity;
+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.lefun.LefunConstants;
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.FeaturesCommand;
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.FindPhoneCommand;
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.GetActivityDataCommand;
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.GetPpgDataCommand;
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.GetSleepDataCommand;
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.GetStepsDataCommand;
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.PpgResultCommand;
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.SettingsCommand;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.LefunActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.entities.LefunActivitySampleDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.LefunBiometricSample;
+import nodomain.freeyourgadget.gadgetbridge.entities.LefunSleepSample;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
+import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
+import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
+import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.RecordedDataTypes;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.GattService;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.FindDeviceRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.GetActivityDataRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.GetBatteryLevelRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.GetEnabledFeaturesRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.GetFirmwareInfoRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.GetGeneralSettingsRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.GetHydrationReminderIntervalRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.GetPpgDataRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.GetSedentaryReminderIntervalRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.GetSleepDataRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.Request;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.SendCallNotificationRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.SendNotificationRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.SetAlarmRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.SetEnabledFeaturesRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.SetGeneralSettingsRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.SetHydrationReminderIntervalRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.SetLanguageRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.SetProfileRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.SetSedentaryReminderIntervalRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.SetTimeRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.StartPpgRequest;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+/**
+ * Device support class for Lefun devices
+ */
+public class LefunDeviceSupport extends AbstractBTLEDeviceSupport {
+    private static final Logger LOG = LoggerFactory.getLogger(LefunDeviceSupport.class);
+
+    private final List<Request> inProgressRequests = Collections.synchronizedList(new ArrayList<Request>());
+    private final Queue<Request> queuedRequests = new ConcurrentLinkedQueue<>();
+
+    private int lastStepsCount = -1;
+    private int lastStepsTimestamp;
+
+    /**
+     * Instantiates a new instance of LefunDeviceSupport
+     */
+    public LefunDeviceSupport() {
+        super(LOG);
+        addSupportedService(GattService.UUID_SERVICE_GENERIC_ACCESS);
+        addSupportedService(GattService.UUID_SERVICE_GENERIC_ATTRIBUTE);
+        addSupportedService(GattService.UUID_SERVICE_DEVICE_INFORMATION);
+        addSupportedService(LefunConstants.UUID_SERVICE_LEFUN);
+    }
+
+    @Override
+    protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
+        builder.setCallback(this);
+        builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext()));
+
+        // Enable notification
+        builder.notify(getCharacteristic(LefunConstants.UUID_CHARACTERISTIC_LEFUN_NOTIFY), true);
+
+        // Init device (get version info, battery level, and set time)
+        try {
+            GetFirmwareInfoRequest firmwareReq = new GetFirmwareInfoRequest(this, builder);
+            firmwareReq.perform();
+            inProgressRequests.add(firmwareReq);
+
+            SetTimeRequest timeReq = new SetTimeRequest(this, builder);
+            timeReq.perform();
+            inProgressRequests.add(timeReq);
+
+            GetBatteryLevelRequest batReq = new GetBatteryLevelRequest(this, builder);
+            batReq.perform();
+            inProgressRequests.add(batReq);
+
+            sendAmPmSettingIfNecessary(builder);
+            sendUnitsSetting(builder);
+            sendUserProfile(builder);
+        } catch (IOException e) {
+            GB.toast(getContext(), "Failed to initialize Lefun device", Toast.LENGTH_SHORT,
+                    GB.ERROR, e);
+        }
+
+        return builder;
+    }
+
+    @Override
+    public boolean useAutoConnect() {
+        return true;
+    }
+
+    @Override
+    public void onNotification(NotificationSpec notificationSpec) {
+        try {
+            TransactionBuilder builder = performInitialized(SetTimeRequest.class.getSimpleName());
+            SendNotificationRequest request = new SendNotificationRequest(this, builder);
+            request.setNotification(notificationSpec);
+            request.perform();
+            performConnected(builder.getTransaction());
+        } catch (IOException e) {
+            GB.toast(getContext(), "Failed to send notification", Toast.LENGTH_SHORT,
+                    GB.ERROR, e);
+        }
+    }
+
+    @Override
+    public void onSetTime() {
+        try {
+            TransactionBuilder builder = performInitialized(SetTimeRequest.class.getSimpleName());
+            SetTimeRequest request = new SetTimeRequest(this, builder);
+            request.perform();
+            inProgressRequests.add(request);
+            performConnected(builder.getTransaction());
+        } catch (IOException e) {
+            GB.toast(getContext(), "Failed to set time", Toast.LENGTH_SHORT,
+                    GB.ERROR, e);
+        }
+    }
+
+    @Override
+    public void onSetAlarms(ArrayList<? extends Alarm> alarms) {
+        int i = 0;
+        for (Alarm alarm : alarms) {
+            try {
+                TransactionBuilder builder = performInitialized(SetAlarmRequest.class.getSimpleName());
+                SetAlarmRequest request = new SetAlarmRequest(this, builder);
+                request.setIndex(i);
+                request.setEnabled(alarm.getEnabled());
+                request.setDayOfWeek(alarm.getRepetition());
+                request.setHour(alarm.getHour());
+                request.setMinute(alarm.getMinute());
+                request.perform();
+                inProgressRequests.add(request);
+                performConnected(builder.getTransaction());
+            } catch (IOException e) {
+                GB.toast(getContext(), "Failed to set alarm", Toast.LENGTH_SHORT,
+                        GB.ERROR, e);
+            }
+            ++i;
+        }
+    }
+
+    @Override
+    public void onSetCallState(CallSpec callSpec) {
+        switch (callSpec.command) {
+            case CallSpec.CALL_INCOMING:
+                try {
+                    TransactionBuilder builder = performInitialized(SetTimeRequest.class.getSimpleName());
+                    SendCallNotificationRequest request = new SendCallNotificationRequest(this, builder);
+                    request.setCallNotification(callSpec);
+                    request.perform();
+                    performConnected(builder.getTransaction());
+                } catch (IOException e) {
+                    GB.toast(getContext(), "Failed to send call notification", Toast.LENGTH_SHORT,
+                            GB.ERROR, e);
+                }
+                break;
+        }
+    }
+
+    @Override
+    public void onFetchRecordedData(int dataTypes) {
+        if ((dataTypes & RecordedDataTypes.TYPE_ACTIVITY) != 0) {
+            for (int i = 0; i < 7; ++i) {
+                GetActivityDataRequest req = new GetActivityDataRequest(this);
+                req.setDaysAgo(i);
+                queuedRequests.add(req);
+            }
+
+            for (int i = 0; i < LefunConstants.PPG_TYPE_COUNT; ++i) {
+                GetPpgDataRequest req = new GetPpgDataRequest(this);
+                req.setPpgType(i);
+                queuedRequests.add(req);
+            }
+
+            for (int i = 0; i < 7; ++i) {
+                GetSleepDataRequest req = new GetSleepDataRequest(this);
+                req.setDaysAgo(i);
+                queuedRequests.add(req);
+            }
+
+            runNextQueuedRequest();
+        }
+    }
+
+    @Override
+    public void onHeartRateTest() {
+        try {
+            TransactionBuilder builder = performInitialized(StartPpgRequest.class.getSimpleName());
+            StartPpgRequest request = new StartPpgRequest(this, builder);
+            request.setPpgType(LefunConstants.PPG_TYPE_HEART_RATE);
+            request.perform();
+            inProgressRequests.add(request);
+            performConnected(builder.getTransaction());
+        } catch (IOException e) {
+            GB.toast(getContext(), "Failed to start heart rate test", Toast.LENGTH_SHORT,
+                    GB.ERROR, e);
+        }
+    }
+
+    @Override
+    public void onFindDevice(boolean start) {
+        if (start) {
+            try {
+                TransactionBuilder builder = performInitialized(FindDeviceRequest.class.getSimpleName());
+                FindDeviceRequest request = new FindDeviceRequest(this, builder);
+                request.perform();
+                inProgressRequests.add(request);
+                performConnected(builder.getTransaction());
+            } catch (IOException e) {
+                GB.toast(getContext(), "Failed to initiate find device", Toast.LENGTH_SHORT,
+                        GB.ERROR, e);
+            }
+        }
+    }
+
+    @Override
+    public void onSendConfiguration(String config) {
+        SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress());
+        switch (config) {
+            case DeviceSettingsPreferenceConst.PREF_TIMEFORMAT: {
+                sendAmPmSetting(null);
+                break;
+            }
+            case DeviceSettingsPreferenceConst.PREF_LIFTWRIST_NOSHED: {
+                boolean enabled = prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_LIFTWRIST_NOSHED, true);
+                FeaturesCommand features = getCurrentEnabledFeatures();
+                features.setFeature(FeaturesCommand.FEATURE_RAISE_TO_WAKE, enabled);
+                sendEnabledFeaturesSetting(features);
+                break;
+            }
+            case DeviceSettingsPreferenceConst.PREF_ANTILOST_ENABLED: {
+                boolean enabled = prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_ANTILOST_ENABLED, true);
+                FeaturesCommand features = getCurrentEnabledFeatures();
+                features.setFeature(FeaturesCommand.FEATURE_ANTI_LOST, enabled);
+                sendEnabledFeaturesSetting(features);
+                break;
+            }
+            case DeviceSettingsPreferenceConst.PREF_INACTIVITY_ENABLE: {
+                boolean enabled = prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_INACTIVITY_ENABLE, false);
+                FeaturesCommand features = getCurrentEnabledFeatures();
+                features.setFeature(FeaturesCommand.FEATURE_SEDENTARY_REMINDER, enabled);
+                sendEnabledFeaturesSetting(features);
+                break;
+            }
+            case DeviceSettingsPreferenceConst.PREF_INACTIVITY_THRESHOLD: {
+                String periodStr = prefs.getString(DeviceSettingsPreferenceConst.PREF_INACTIVITY_THRESHOLD, "60");
+                try {
+                    int period = Integer.parseInt(periodStr);
+                    sendSedentaryReminderIntervalSetting(period);
+                } catch (NumberFormatException e) {
+                    GB.toast(getContext(), "Invalid sedentary reminder interval value", Toast.LENGTH_SHORT,
+                            GB.ERROR, e);
+                }
+                break;
+            }
+            case DeviceSettingsPreferenceConst.PREF_HYDRATION_SWITCH: {
+                boolean enabled = prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_HYDRATION_SWITCH, false);
+                FeaturesCommand features = getCurrentEnabledFeatures();
+                features.setFeature(FeaturesCommand.FEATURE_HYDRATION_REMINDER, enabled);
+                sendEnabledFeaturesSetting(features);
+                break;
+            }
+            case DeviceSettingsPreferenceConst.PREF_HYDRATION_PERIOD: {
+                String periodStr = prefs.getString(DeviceSettingsPreferenceConst.PREF_HYDRATION_PERIOD, "60");
+                try {
+                    int period = Integer.parseInt(periodStr);
+                    sendHydrationReminderIntervalSetting(period);
+                } catch (NumberFormatException e) {
+                    GB.toast(getContext(), "Invalid sedentary reminder interval value", Toast.LENGTH_SHORT,
+                            GB.ERROR, e);
+                }
+                break;
+            }
+            case SettingsActivity.PREF_MEASUREMENT_SYSTEM: {
+                sendUnitsSetting(null);
+                break;
+            }
+            case DeviceSettingsPreferenceConst.PREF_LANGUAGE: {
+                String value = prefs.getString(DeviceSettingsPreferenceConst.PREF_LANGUAGE, "0");
+                int intValue = Integer.parseInt(value);
+                sendLanguageSetting((byte) intValue);
+                break;
+            }
+        }
+    }
+
+    /**
+     * Sends unit of measurement to the device
+     *
+     * @param builder the transaction builder to append to
+     */
+    private void sendUnitsSetting(TransactionBuilder builder) {
+        Prefs prefs = GBApplication.getPrefs();
+        String units = prefs.getString(SettingsActivity.PREF_MEASUREMENT_SYSTEM,
+                getContext().getString(R.string.p_unit_metric));
+
+        byte lefunUnits;
+        if (getContext().getString(R.string.p_unit_metric).equals(units)) {
+            lefunUnits = SettingsCommand.MEASUREMENT_UNIT_METRIC;
+        } else {
+            lefunUnits = SettingsCommand.MEASUREMENT_UNIT_IMPERIAL;
+        }
+        sendGeneralSettings(builder, (byte) 0xff, lefunUnits);
+    }
+
+    /**
+     * Send AM/PM indicator setting based on time format pref
+     *
+     * @param builder the transaction builder to append to
+     */
+    private void sendAmPmSetting(TransactionBuilder builder) {
+        SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress());
+        String ampmSetting = prefs.getString(DeviceSettingsPreferenceConst.PREF_TIMEFORMAT,
+                getContext().getString(R.string.p_timeformat_auto));
+
+        byte ampmDeviceSetting = (byte) 0xff;
+        if (getContext().getString(R.string.p_timeformat_auto).equals(ampmSetting)) {
+            if (DateFormat.is24HourFormat(getContext())) {
+                ampmDeviceSetting = SettingsCommand.AM_PM_24_HOUR;
+            } else {
+                ampmDeviceSetting = SettingsCommand.AM_PM_12_HOUR;
+            }
+        } else if (getContext().getString(R.string.p_timeformat_24h).equals(ampmSetting)) {
+            ampmDeviceSetting = SettingsCommand.AM_PM_24_HOUR;
+        } else if (getContext().getString(R.string.p_timeformat_am_pm).equals(ampmSetting)) {
+            ampmDeviceSetting = SettingsCommand.AM_PM_12_HOUR;
+        }
+
+        sendGeneralSettings(builder, ampmDeviceSetting, (byte) 0xff);
+    }
+
+    /**
+     * Send AM/PM indicator setting only if time format pref is set to auto
+     *
+     * @param builder the transaction builder to append to
+     */
+    private void sendAmPmSettingIfNecessary(TransactionBuilder builder) {
+        SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress());
+        String ampmSetting = prefs.getString(DeviceSettingsPreferenceConst.PREF_TIMEFORMAT,
+                getContext().getString(R.string.p_timeformat_auto));
+
+        if (getContext().getString(R.string.p_timeformat_auto).equals(ampmSetting)) {
+            sendAmPmSetting(builder);
+        }
+    }
+
+    /**
+     * Gets a features command with the currently enabled features set
+     *
+     * @return the features command
+     */
+    private FeaturesCommand getCurrentEnabledFeatures() {
+        SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress());
+        boolean raiseToWakeEnabled = prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_LIFTWRIST_NOSHED, true);
+        boolean antilostEnabled = prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_ANTILOST_ENABLED, true);
+        boolean sedentaryEnabled = prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_INACTIVITY_ENABLE, false);
+        boolean hydrationEnabled = prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_HYDRATION_SWITCH, false);
+
+        FeaturesCommand cmd = new FeaturesCommand();
+        cmd.setFeature(FeaturesCommand.FEATURE_RAISE_TO_WAKE, raiseToWakeEnabled);
+        cmd.setFeature(FeaturesCommand.FEATURE_ANTI_LOST, antilostEnabled);
+        cmd.setFeature(FeaturesCommand.FEATURE_SEDENTARY_REMINDER, sedentaryEnabled);
+        cmd.setFeature(FeaturesCommand.FEATURE_HYDRATION_REMINDER, hydrationEnabled);
+
+        return cmd;
+    }
+
+    /**
+     * Sends general settings to the device
+     *
+     * @param builder the transaction builder to append to
+     * @param amPm    AM/PM indicator setting
+     * @param units   units of measurement setting
+     */
+    private void sendGeneralSettings(TransactionBuilder builder, byte amPm, byte units) {
+        boolean givenBuilder = builder != null;
+        try {
+            if (!givenBuilder)
+                builder = performInitialized(SetGeneralSettingsRequest.class.getSimpleName());
+            SetGeneralSettingsRequest request = new SetGeneralSettingsRequest(this, builder);
+            request.setAmPm(amPm);
+            request.setUnits(units);
+            request.perform();
+            inProgressRequests.add(request);
+            if (!givenBuilder)
+                performConnected(builder.getTransaction());
+        } catch (IOException e) {
+            GB.toast(getContext(), "Failed to set settings", Toast.LENGTH_SHORT,
+                    GB.ERROR, e);
+        }
+    }
+
+    /**
+     * Sends the user profile to the device
+     *
+     * @param builder the transaction builder to append to
+     */
+    private void sendUserProfile(TransactionBuilder builder) {
+        boolean givenBuilder = builder != null;
+        try {
+            if (!givenBuilder)
+                builder = performInitialized(SetProfileRequest.class.getSimpleName());
+            SetProfileRequest request = new SetProfileRequest(this, builder);
+            ActivityUser user = new ActivityUser();
+            request.setUser(user);
+            request.perform();
+            inProgressRequests.add(request);
+            if (!givenBuilder)
+                performConnected(builder.getTransaction());
+        } catch (IOException e) {
+            GB.toast(getContext(), "Failed to send profile", Toast.LENGTH_SHORT,
+                    GB.ERROR, e);
+        }
+    }
+
+    /**
+     * Sends enabled features settings to the device
+     *
+     * @param cmd the features command to send
+     */
+    private void sendEnabledFeaturesSetting(FeaturesCommand cmd) {
+        try {
+            TransactionBuilder builder = performInitialized(SetEnabledFeaturesRequest.class.getSimpleName());
+            SetEnabledFeaturesRequest request = new SetEnabledFeaturesRequest(this, builder);
+            request.setCmd(cmd);
+            request.perform();
+            inProgressRequests.add(request);
+            performConnected(builder.getTransaction());
+        } catch (IOException e) {
+            GB.toast(getContext(), "Failed to set enabled features", Toast.LENGTH_SHORT,
+                    GB.ERROR, e);
+        }
+    }
+
+    /**
+     * Sends the sedentary reminder interval setting to the device
+     *
+     * @param period the reminder interval
+     */
+    private void sendSedentaryReminderIntervalSetting(int period) {
+        try {
+            TransactionBuilder builder = performInitialized(SetSedentaryReminderIntervalRequest.class.getSimpleName());
+            SetSedentaryReminderIntervalRequest request = new SetSedentaryReminderIntervalRequest(this, builder);
+            request.setInterval(period);
+            request.perform();
+            inProgressRequests.add(request);
+            performConnected(builder.getTransaction());
+        } catch (IOException e) {
+            GB.toast(getContext(), "Failed to set sedentary reminder interval", Toast.LENGTH_SHORT,
+                    GB.ERROR, e);
+        }
+    }
+
+    /**
+     * Sends the hydration reminder interval setting to the device
+     *
+     * @param period the reminder interval
+     */
+    private void sendHydrationReminderIntervalSetting(int period) {
+        try {
+            TransactionBuilder builder = performInitialized(SetHydrationReminderIntervalRequest.class.getSimpleName());
+            SetHydrationReminderIntervalRequest request = new SetHydrationReminderIntervalRequest(this, builder);
+            request.setInterval(period);
+            request.perform();
+            inProgressRequests.add(request);
+            performConnected(builder.getTransaction());
+        } catch (IOException e) {
+            GB.toast(getContext(), "Failed to set hydration reminder interval", Toast.LENGTH_SHORT,
+                    GB.ERROR, e);
+        }
+    }
+
+    /**
+     * Sends the language selection to the device
+     *
+     * @param language the language selection
+     */
+    private void sendLanguageSetting(byte language) {
+        try {
+            TransactionBuilder builder = performInitialized(SetLanguageRequest.class.getSimpleName());
+            SetLanguageRequest request = new SetLanguageRequest(this, builder);
+            request.setLanguage(language);
+            request.perform();
+            inProgressRequests.add(request);
+            performConnected(builder.getTransaction());
+        } catch (IOException e) {
+            GB.toast(getContext(), "Failed to set language", Toast.LENGTH_SHORT,
+                    GB.ERROR, e);
+        }
+    }
+
+    /**
+     * Stores received general settings to prefs
+     *
+     * @param amPm  AM/PM indicator setting
+     * @param units units of measurement setting
+     */
+    public void receiveGeneralSettings(int amPm, int units) {
+        SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress());
+        boolean ampmEnabled = amPm == SettingsCommand.AM_PM_12_HOUR;
+        String currAmpmSetting = prefs.getString(DeviceSettingsPreferenceConst.PREF_TIMEFORMAT,
+                getContext().getString(R.string.p_timeformat_auto));
+
+        SharedPreferences.Editor editor = prefs.edit();
+
+        // Only update AM/PM indicator setting if it is not currently set to auto
+        if (!getContext().getString(R.string.p_timeformat_auto).equals(currAmpmSetting)) {
+            String ampmValue = getContext().getString(ampmEnabled ? R.string.p_timeformat_am_pm
+                    : R.string.p_timeformat_24h);
+            editor.putString(DeviceSettingsPreferenceConst.PREF_TIMEFORMAT, ampmValue);
+        }
+
+        editor.apply();
+    }
+
+    /**
+     * Stores received enabled features settings to prefs
+     *
+     * @param cmd the features command
+     */
+    public void receiveEnabledFeaturesSetting(FeaturesCommand cmd) {
+        SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress());
+        prefs.edit()
+                .putBoolean(DeviceSettingsPreferenceConst.PREF_LIFTWRIST_NOSHED,
+                        cmd.getFeature(FeaturesCommand.FEATURE_RAISE_TO_WAKE))
+                .putBoolean(DeviceSettingsPreferenceConst.PREF_INACTIVITY_ENABLE,
+                        cmd.getFeature(FeaturesCommand.FEATURE_SEDENTARY_REMINDER))
+                .putBoolean(DeviceSettingsPreferenceConst.PREF_HYDRATION_SWITCH,
+                        cmd.getFeature(FeaturesCommand.FEATURE_HYDRATION_REMINDER))
+                .putBoolean(DeviceSettingsPreferenceConst.PREF_ANTILOST_ENABLED,
+                        cmd.getFeature(FeaturesCommand.FEATURE_ANTI_LOST))
+                .apply();
+    }
+
+    /**
+     * Stores received sedentary reminder interval setting to prefs
+     *
+     * @param period the interval
+     */
+    public void receiveSedentaryReminderIntervalSetting(int period) {
+        SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress());
+        prefs.edit()
+                .putString(DeviceSettingsPreferenceConst.PREF_INACTIVITY_THRESHOLD, String.valueOf(period))
+                .apply();
+    }
+
+    /**
+     * Stores received hydration reminder interval setting to prefs
+     *
+     * @param period the interval
+     */
+    public void receiveHydrationReminderIntervalSetting(int period) {
+        SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress());
+        prefs.edit()
+                .putString(DeviceSettingsPreferenceConst.PREF_HYDRATION_PERIOD, String.valueOf(period))
+                .apply();
+    }
+
+    @Override
+    public void onReadConfiguration(String config) {
+        // Just going to read all the settings
+        try {
+            TransactionBuilder builder = performInitialized("Read settings");
+
+            GetGeneralSettingsRequest getGeneralSettingsRequest
+                    = new GetGeneralSettingsRequest(this, builder);
+            getGeneralSettingsRequest.perform();
+            inProgressRequests.add(getGeneralSettingsRequest);
+
+            GetEnabledFeaturesRequest getEnabledFeaturesRequest
+                    = new GetEnabledFeaturesRequest(this, builder);
+            getEnabledFeaturesRequest.perform();
+            inProgressRequests.add(getEnabledFeaturesRequest);
+
+            GetSedentaryReminderIntervalRequest getSedentaryReminderIntervalRequest
+                    = new GetSedentaryReminderIntervalRequest(this, builder);
+            getSedentaryReminderIntervalRequest.perform();
+            inProgressRequests.add(getSedentaryReminderIntervalRequest);
+
+            GetHydrationReminderIntervalRequest getHydrationReminderIntervalRequest
+                    = new GetHydrationReminderIntervalRequest(this, builder);
+            getHydrationReminderIntervalRequest.perform();
+            inProgressRequests.add(getHydrationReminderIntervalRequest);
+
+            performConnected(builder.getTransaction());
+        } catch (IOException e) {
+            GB.toast(getContext(), "Failed to retrieve settings", Toast.LENGTH_SHORT,
+                    GB.ERROR, e);
+        }
+    }
+
+    @Override
+    public boolean onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
+        if (characteristic.getUuid().equals(LefunConstants.UUID_CHARACTERISTIC_LEFUN_NOTIFY)) {
+            byte[] data = characteristic.getValue();
+            // Parse response
+            if (data.length >= LefunConstants.CMD_HEADER_LENGTH && data[0] == LefunConstants.CMD_RESPONSE_ID) {
+                // Note: full validation is done within the request
+                byte commandId = data[2];
+                synchronized (inProgressRequests) {
+                    for (Request req : inProgressRequests) {
+                        if (req.expectsResponse() && req.getCommandId() == commandId) {
+                            try {
+                                req.handleResponse(data);
+                                if (req.shouldRemoveAfterHandling())
+                                    inProgressRequests.remove(req);
+                                return true;
+                            } catch (IllegalArgumentException e) {
+                                LOG.error("Failed to handle response", e);
+                            }
+                        }
+                    }
+                }
+
+                if (handleAsynchronousResponse(commandId, data))
+                    return true;
+
+                logMessageContent(data);
+                LOG.error(String.format("No handler for response 0x%02x", commandId));
+                return false;
+            }
+
+            logMessageContent(data);
+            LOG.error("Invalid response received");
+            return false;
+        }
+
+        return super.onCharacteristicChanged(gatt, characteristic);
+    }
+
+    /**
+     * Handles commands from the device that are not typically associated with a request
+     *
+     * @param commandId the command ID
+     * @param data      the entire response
+     * @return whether the response has been handled
+     */
+    private boolean handleAsynchronousResponse(byte commandId, byte[] data) {
+        // Assume data already checked for correct response code and length
+        switch (commandId) {
+            case LefunConstants.CMD_PPG_RESULT:
+                return handleAsynchronousPpgResult(data);
+            case LefunConstants.CMD_FIND_PHONE:
+                return handleAntiLoss(data);
+            case LefunConstants.CMD_STEPS_DATA:
+                return handleAsynchronousActivity(data);
+        }
+        return false;
+    }
+
+    /**
+     * Handles live steps data
+     *
+     * @param data the response
+     * @return whether the response has been handled
+     */
+    private boolean handleAsynchronousActivity(byte[] data) {
+        try {
+            GetStepsDataCommand cmd = new GetStepsDataCommand();
+            cmd.deserialize(data);
+            broadcastSample(cmd);
+            return true;
+        } catch (IllegalArgumentException e) {
+            LOG.error("Failed to handle live activity update", e);
+            return false;
+        }
+    }
+
+    // Adapted from nodomain.freeyourgadget.gadgetbridge.service.devices.makibeshr3.MakibesHR3DeviceSupport.broadcastSample
+
+    /**
+     * Broadcasts live sample
+     *
+     * @param command the steps data
+     */
+    private void broadcastSample(GetStepsDataCommand command) {
+        Calendar now = Calendar.getInstance();
+        int timestamp = (int) (now.getTimeInMillis() / 1000);
+        // Workaround for a world where sub-second time resolution is not a thing
+        if (lastStepsTimestamp == timestamp) return;
+        lastStepsTimestamp = timestamp;
+        LefunActivitySample sample = new LefunActivitySample();
+        sample.setTimestamp(timestamp);
+        if (lastStepsCount == -1 || command.getSteps() < lastStepsCount) {
+            lastStepsCount = command.getSteps();
+        }
+        int diff = command.getSteps() - lastStepsCount;
+        sample.setSteps(diff);
+        lastStepsCount = command.getSteps();
+        Intent intent = new Intent(DeviceService.ACTION_REALTIME_SAMPLES)
+                .putExtra(GBDevice.EXTRA_DEVICE, getDevice())
+                .putExtra(DeviceService.EXTRA_REALTIME_SAMPLE, sample)
+                .putExtra(DeviceService.EXTRA_TIMESTAMP, sample.getTimestamp());
+        LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent);
+    }
+
+    /**
+     * Handles PPG result from earlier request
+     *
+     * @param data the response
+     * @return whether the response has been handled
+     */
+    private boolean handleAsynchronousPpgResult(byte[] data) {
+        try {
+            PpgResultCommand cmd = new PpgResultCommand();
+            cmd.deserialize(data);
+            handlePpgData(cmd);
+            return true;
+        } catch (IllegalArgumentException e) {
+            LOG.error("Failed to PPG result", e);
+            return false;
+        }
+    }
+
+    /**
+     * Handles find phone request
+     *
+     * @param data the response
+     * @return whether the response has been handled
+     */
+    private boolean handleAntiLoss(byte[] data) {
+        try {
+            FindPhoneCommand cmd = new FindPhoneCommand();
+            cmd.deserialize(data);
+            GBDeviceEventFindPhone event = new GBDeviceEventFindPhone();
+            event.event = GBDeviceEventFindPhone.Event.START;
+            evaluateGBDeviceEvent(event);
+            return true;
+        } catch (IllegalArgumentException e) {
+            LOG.error("Failed to handle anti-loss", e);
+            return false;
+        }
+    }
+
+    /**
+     * Callback when device info has been obtained
+     */
+    public void completeInitialization() {
+        gbDevice.setState(GBDevice.State.INITIALIZED);
+        gbDevice.sendDeviceUpdateIntent(getContext());
+        onReadConfiguration("");
+    }
+
+    /**
+     * Converts Lefun datetime format to Unix timestamp
+     *
+     * @param year   the year (2 digits based on 2000)
+     * @param month  the month
+     * @param day    the day
+     * @param hour   the hour
+     * @param minute the minute
+     * @param second the second
+     * @return Unix timestamp of the datetime
+     */
+    private int dateToTimestamp(byte year, byte month, byte day, byte hour, byte minute, byte second) {
+        Calendar calendar = Calendar.getInstance();
+        calendar.set(
+                ((int) year & 0xff) + 2000,
+                ((int) month & 0xff) - 1,
+                (int) day,
+                (int) hour,
+                (int) minute,
+                (int) second
+        );
+        return (int) (calendar.getTimeInMillis() / 1000);
+    }
+
+    /**
+     * Fetches an activity sample given the timestamp
+     *
+     * @param session   DAO session
+     * @param timestamp the timestamp
+     * @return fetched activity or null if none exists
+     */
+    private LefunActivitySample getActivitySample(DaoSession session, int timestamp) {
+        LefunActivitySampleDao dao = session.getLefunActivitySampleDao();
+        Long userId = DBHelper.getUser(session).getId();
+        Long deviceId = DBHelper.getDevice(getDevice(), session).getId();
+        Query<LefunActivitySample> q = dao.queryBuilder()
+                .where(LefunActivitySampleDao.Properties.Timestamp.eq(timestamp))
+                .where(LefunActivitySampleDao.Properties.DeviceId.eq(deviceId))
+                .where(LefunActivitySampleDao.Properties.UserId.eq(userId))
+                .build();
+        return q.unique();
+    }
+
+    /**
+     * Processes activity data and stores it
+     *
+     * @param command the activity data
+     */
+    public void handleActivityData(GetActivityDataCommand command) {
+        try (DBHandler handler = GBApplication.acquireDB()) {
+            DaoSession session = handler.getDaoSession();
+            int timestamp = dateToTimestamp(command.getYear(), command.getMonth(), command.getDay(),
+                    command.getHour(), command.getMinute(), (byte) 0);
+            // For the most part I'm ignoring the sample provider, because it doesn't really help
+            // when I need to combine sample data instead of replacing
+            LefunActivitySample sample = getActivitySample(session, timestamp);
+            if (sample == null) {
+                sample = new LefunActivitySample(timestamp,
+                        DBHelper.getDevice(getDevice(), session).getId());
+                sample.setUserId(DBHelper.getUser(session).getId());
+                sample.setRawKind(LefunConstants.DB_ACTIVITY_KIND_ACTIVITY);
+            }
+
+            sample.setSteps(command.getSteps());
+            sample.setDistance(command.getDistance());
+            sample.setCalories(command.getCalories());
+            sample.setRawIntensity(LefunConstants.INTENSITY_AWAKE);
+
+            session.getLefunActivitySampleDao().insertOrReplace(sample);
+        } catch (Exception e) {
+            LOG.error("Error handling activity data", e);
+        }
+    }
+
+    /**
+     * Processes PPG data and stores it
+     *
+     * @param timestamp the timestamp
+     * @param ppgType   the PPG type
+     * @param ppgData   the data from the PPG operation
+     */
+    private void handlePpgData(int timestamp, int ppgType, byte[] ppgData) {
+        int ppgData0 = ppgData[0] & 0xff;
+        int ppgData1 = ppgData.length > 1 ? ppgData[1] & 0xff : 0;
+
+        try (DBHandler handler = GBApplication.acquireDB()) {
+            DaoSession session = handler.getDaoSession();
+
+            if (ppgType == LefunConstants.PPG_TYPE_HEART_RATE) {
+                LefunActivitySample sample = getActivitySample(session, timestamp);
+                if (sample == null) {
+                    sample = new LefunActivitySample(timestamp,
+                            DBHelper.getDevice(getDevice(), session).getId());
+                    sample.setUserId(DBHelper.getUser(session).getId());
+                    sample.setRawKind(LefunConstants.DB_ACTIVITY_KIND_HEART_RATE);
+                }
+
+                sample.setHeartRate(ppgData0);
+
+                session.getLefunActivitySampleDao().insertOrReplace(sample);
+
+                final Intent intent = new Intent(DeviceService.ACTION_REALTIME_SAMPLES)
+                        .putExtra(GBDevice.EXTRA_DEVICE, getDevice())
+                        .putExtra(DeviceService.EXTRA_REALTIME_SAMPLE, sample);
+                LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent);
+            }
+
+            LefunBiometricSample bioSample = new LefunBiometricSample(timestamp,
+                    DBHelper.getDevice(getDevice(), session).getId());
+            bioSample.setUserId(DBHelper.getUser(session).getId());
+            bioSample.setType(ppgType);
+            bioSample.setValue1(ppgData0);
+            bioSample.setValue2(ppgData1);
+            session.getLefunBiometricSampleDao().insertOrReplace(bioSample);
+        } catch (Exception e) {
+            LOG.error("Error handling PPG data", e);
+        }
+    }
+
+    /**
+     * Processes PPG data from bulk get operation
+     *
+     * @param command the PPG data
+     */
+    public void handlePpgData(GetPpgDataCommand command) {
+        int timestamp = dateToTimestamp(command.getYear(), command.getMonth(), command.getDay(),
+                command.getHour(), command.getMinute(), command.getSecond());
+        int ppgType = command.getPpgType();
+        byte[] ppgData = command.getPpgData();
+        handlePpgData(timestamp, ppgType, ppgData);
+    }
+
+    /**
+     * Processes PPG result received as a result of requesting PPG operation
+     *
+     * @param command the PPG result
+     */
+    public void handlePpgData(PpgResultCommand command) {
+        int timestamp = (int) (Calendar.getInstance().getTimeInMillis() / 1000);
+        int ppgType = command.getPpgType();
+        byte[] ppgData = command.getPpgData();
+        handlePpgData(timestamp, ppgType, ppgData);
+    }
+
+    /**
+     * Processes bulk sleep data
+     *
+     * @param command the sleep data
+     */
+    public void handleSleepData(GetSleepDataCommand command) {
+        try (DBHandler handler = GBApplication.acquireDB()) {
+            DaoSession session = handler.getDaoSession();
+            int timestamp = dateToTimestamp(command.getYear(), command.getMonth(), command.getDay(),
+                    command.getHour(), command.getMinute(), (byte) 0);
+
+            LefunActivitySample sample = getActivitySample(session, timestamp);
+            if (sample == null) {
+                sample = new LefunActivitySample(timestamp,
+                        DBHelper.getDevice(getDevice(), session).getId());
+                sample.setUserId(DBHelper.getUser(session).getId());
+            }
+
+            int rawKind;
+            int intensity;
+            switch (command.getSleepType()) {
+                case GetSleepDataCommand.SLEEP_TYPE_AWAKE:
+                    rawKind = LefunConstants.DB_ACTIVITY_KIND_ACTIVITY;
+                    intensity = LefunConstants.INTENSITY_AWAKE;
+                    break;
+                case GetSleepDataCommand.SLEEP_TYPE_LIGHT_SLEEP:
+                    rawKind = LefunConstants.DB_ACTIVITY_KIND_LIGHT_SLEEP;
+                    intensity = LefunConstants.INTENSITY_LIGHT_SLEEP;
+                    break;
+                case GetSleepDataCommand.SLEEP_TYPE_DEEP_SLEEP:
+                    rawKind = LefunConstants.DB_ACTIVITY_KIND_DEEP_SLEEP;
+                    intensity = LefunConstants.INTENSITY_DEEP_SLEEP;
+                    break;
+                default:
+                    rawKind = LefunConstants.DB_ACTIVITY_KIND_UNKNOWN;
+                    intensity = LefunConstants.INTENSITY_AWAKE;
+                    break;
+            }
+
+            sample.setRawKind(rawKind);
+            sample.setRawIntensity(intensity);
+
+            session.getLefunActivitySampleDao().insertOrReplace(sample);
+
+            LefunSleepSample sleepSample = new LefunSleepSample(timestamp,
+                    DBHelper.getDevice(getDevice(), session).getId());
+            sleepSample.setUserId(DBHelper.getUser(session).getId());
+            sleepSample.setType(command.getSleepType());
+            session.getLefunSleepSampleDao().insertOrReplace(sleepSample);
+        } catch (Exception e) {
+            LOG.error("Error handling sleep data", e);
+        }
+    }
+
+    /**
+     * Runs the next queued request
+     */
+    public void runNextQueuedRequest() {
+        Request request = queuedRequests.poll();
+        if (request != null) {
+            try {
+                request.perform();
+                if (!request.isSelfQueue())
+                    performConnected(request.getTransactionBuilder().getTransaction());
+            } catch (IOException e) {
+                GB.toast(getContext(), "Failed to run next queued request", Toast.LENGTH_SHORT,
+                        GB.ERROR, e);
+            }
+        }
+    }
+
+    @Override
+    public boolean getImplicitCallbackModify() {
+        return true;
+    }
+
+    @Override
+    public boolean getSendWriteRequestResponse() {
+        return false;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/AbstractSendNotificationRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/AbstractSendNotificationRequest.java
new file mode 100644
index 0000000..c596b21
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/AbstractSendNotificationRequest.java
@@ -0,0 +1,99 @@
+/*  Copyright (C) 2020-2024 Yukai Li
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests;
+
+import android.bluetooth.BluetoothGattCharacteristic;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.NotificationCommand;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.LefunDeviceSupport;
+
+public abstract class AbstractSendNotificationRequest extends Request {
+    protected AbstractSendNotificationRequest(LefunDeviceSupport support, TransactionBuilder builder) {
+        super(support, builder);
+    }
+
+    protected abstract String getMessage();
+
+    protected abstract byte getNotificationType();
+
+    protected abstract byte getExtendedNotificationType();
+
+    @Override
+    public byte[] createRequest() {
+        return new byte[0];
+    }
+
+    @Override
+    protected void doPerform() throws IOException {
+        byte notificationType = getNotificationType();
+        byte extendedNotificationType = getExtendedNotificationType();
+        boolean reserveSpaceForExtended = notificationType == NotificationCommand.SERVICE_TYPE_EXTENDED;
+        byte[] encoded = getMessage().getBytes(StandardCharsets.UTF_8);
+        ByteBuffer buffer = ByteBuffer.wrap(encoded);
+
+        BluetoothGattCharacteristic characteristic = getSupport()
+                .getCharacteristic(LefunConstants.UUID_CHARACTERISTIC_LEFUN_WRITE);
+
+        List<NotificationCommand> commandList = new ArrayList<>();
+        int charsWritten = 0;
+        for (int i = 0; i < 0xff; ++i) {
+            int maxPayloadLength = NotificationCommand.MAX_PAYLOAD_LENGTH;
+            if (reserveSpaceForExtended) maxPayloadLength -= 1;
+            maxPayloadLength = Math.min(maxPayloadLength, buffer.limit() - buffer.position());
+            maxPayloadLength = Math.min(maxPayloadLength, NotificationCommand.MAX_MESSAGE_LENGTH - charsWritten);
+            if (maxPayloadLength == 0 && i != 0) break;
+
+            byte[] payload = new byte[maxPayloadLength];
+            buffer.get(payload);
+
+            NotificationCommand cmd = new NotificationCommand();
+            cmd.setServiceType(notificationType);
+            cmd.setExtendedServiceType(extendedNotificationType);
+            cmd.setCurrentPiece((byte) (i + 1));
+            cmd.setPayload(payload);
+            charsWritten += maxPayloadLength;
+
+            commandList.add(cmd);
+        }
+
+        for (NotificationCommand cmd : commandList) {
+            cmd.setTotalPieces((byte) commandList.size());
+            builder.write(characteristic, cmd.serialize());
+        }
+
+        if (isSelfQueue())
+            getSupport().performConnected(builder.getTransaction());
+    }
+
+    @Override
+    public int getCommandId() {
+        return LefunConstants.CMD_NOTIFICATION;
+    }
+
+    @Override
+    public boolean expectsResponse() {
+        return false;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/FindDeviceRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/FindDeviceRequest.java
new file mode 100644
index 0000000..eca869a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/FindDeviceRequest.java
@@ -0,0 +1,51 @@
+/*  Copyright (C) 2020-2024 Yukai Li
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.FindDeviceCommand;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.LefunDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.OperationStatus;
+
+public class FindDeviceRequest extends Request {
+    public FindDeviceRequest(LefunDeviceSupport support, TransactionBuilder builder) {
+        super(support, builder);
+    }
+
+    @Override
+    public byte[] createRequest() {
+        FindDeviceCommand cmd = new FindDeviceCommand();
+        return cmd.serialize();
+    }
+
+    @Override
+    public int getCommandId() {
+        return LefunConstants.CMD_FIND_DEVICE;
+    }
+
+    @Override
+    public void handleResponse(byte[] data) {
+        FindDeviceCommand cmd = new FindDeviceCommand();
+        cmd.deserialize(data);
+
+        if (!cmd.isSuccess())
+            reportFailure("Could not initiate find device");
+
+        operationStatus = OperationStatus.FINISHED;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/GetActivityDataRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/GetActivityDataRequest.java
new file mode 100644
index 0000000..ace1c79
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/GetActivityDataRequest.java
@@ -0,0 +1,85 @@
+/*  Copyright (C) 2020-2024 Yukai Li
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.GetActivityDataCommand;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.LefunDeviceSupport;
+
+public class GetActivityDataRequest extends MultiFetchRequest {
+    private int daysAgo;
+
+    public GetActivityDataRequest(LefunDeviceSupport support) {
+        super(support);
+    }
+
+    public int getDaysAgo() {
+        return daysAgo;
+    }
+
+    public void setDaysAgo(int daysAgo) {
+        this.daysAgo = daysAgo;
+    }
+
+    @Override
+    public byte[] createRequest() {
+        GetActivityDataCommand cmd = new GetActivityDataCommand();
+        cmd.setDaysAgo((byte) daysAgo);
+        return cmd.serialize();
+    }
+
+    @Override
+    public void handleResponse(byte[] data) {
+        GetActivityDataCommand cmd = new GetActivityDataCommand();
+        cmd.deserialize(data);
+
+        if (daysAgo != (cmd.getDaysAgo() & 0xff)) {
+            throw new IllegalArgumentException("Mismatching days ago");
+        }
+
+        if (totalRecords == -1) {
+            totalRecords = cmd.getTotalRecords() & 0xff;
+        } else if (totalRecords != (cmd.getTotalRecords() & 0xff)) {
+            throw new IllegalArgumentException("Total records mismatch");
+        }
+
+        if (totalRecords != 0) {
+            int currentRecord = cmd.getCurrentRecord() & 0xff;
+            if (lastRecord + 1 != currentRecord) {
+                throw new IllegalArgumentException("Records received out of sequence");
+            }
+            lastRecord = currentRecord;
+
+            getSupport().handleActivityData(cmd);
+        } else {
+            lastRecord = totalRecords;
+        }
+
+        if (lastRecord == totalRecords)
+            operationFinished();
+    }
+
+    @Override
+    public int getCommandId() {
+        return LefunConstants.CMD_ACTIVITY_DATA;
+    }
+
+    @Override
+    protected String getOperationName() {
+        return "Getting activity data";
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/GetBatteryLevelRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/GetBatteryLevelRequest.java
new file mode 100644
index 0000000..df0bc08
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/GetBatteryLevelRequest.java
@@ -0,0 +1,53 @@
+/*  Copyright (C) 2020-2024 Yukai Li
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests;
+
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.GetBatteryLevelCommand;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.LefunDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.OperationStatus;
+
+public class GetBatteryLevelRequest extends Request {
+    public GetBatteryLevelRequest(LefunDeviceSupport support, TransactionBuilder builder) {
+        super(support, builder);
+    }
+
+    @Override
+    public byte[] createRequest() {
+        GetBatteryLevelCommand cmd = new GetBatteryLevelCommand();
+        return cmd.serialize();
+    }
+
+    @Override
+    public void handleResponse(byte[] data) {
+        GetBatteryLevelCommand cmd = new GetBatteryLevelCommand();
+        cmd.deserialize(data);
+
+        GBDeviceEventBatteryInfo batteryInfo = new GBDeviceEventBatteryInfo();
+        batteryInfo.level = (short)((int)cmd.getBatteryLevel() & 0xff);
+        getSupport().evaluateGBDeviceEvent(batteryInfo);
+
+        operationStatus = OperationStatus.FINISHED;
+    }
+
+    @Override
+    public int getCommandId() {
+        return LefunConstants.CMD_BATTERY_LEVEL;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/GetEnabledFeaturesRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/GetEnabledFeaturesRequest.java
new file mode 100644
index 0000000..b42afa0
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/GetEnabledFeaturesRequest.java
@@ -0,0 +1,55 @@
+/*  Copyright (C) 2020-2024 Yukai Li
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.BaseCommand;
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.FeaturesCommand;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.LefunDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.OperationStatus;
+
+public class GetEnabledFeaturesRequest extends Request {
+    public GetEnabledFeaturesRequest(LefunDeviceSupport support, TransactionBuilder builder) {
+        super(support, builder);
+    }
+
+    @Override
+    public byte[] createRequest() {
+        FeaturesCommand cmd = new FeaturesCommand();
+
+        cmd.setOp(BaseCommand.OP_GET);
+
+        return cmd.serialize();
+    }
+
+    @Override
+    public void handleResponse(byte[] data) {
+        FeaturesCommand cmd = new FeaturesCommand();
+        cmd.deserialize(data);
+        if (cmd.getOp() == BaseCommand.OP_GET) {
+            getSupport().receiveEnabledFeaturesSetting(cmd);
+        }
+
+        operationStatus = OperationStatus.FINISHED;
+    }
+
+    @Override
+    public int getCommandId() {
+        return LefunConstants.CMD_FEATURES;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/GetFirmwareInfoRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/GetFirmwareInfoRequest.java
new file mode 100644
index 0000000..002f63d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/GetFirmwareInfoRequest.java
@@ -0,0 +1,64 @@
+/*  Copyright (C) 2020-2024 Yukai Li
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests;
+
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo;
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.GetFirmwareInfoCommand;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.LefunDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.OperationStatus;
+
+public class GetFirmwareInfoRequest extends Request {
+    public GetFirmwareInfoRequest(LefunDeviceSupport support, TransactionBuilder builder) {
+        super(support, builder);
+    }
+
+    @Override
+    public byte[] createRequest() {
+        GetFirmwareInfoCommand cmd = new GetFirmwareInfoCommand();
+        return cmd.serialize();
+    }
+
+    @Override
+    public void handleResponse(byte[] data) {
+        GetFirmwareInfoCommand cmd = new GetFirmwareInfoCommand();
+        cmd.deserialize(data);
+
+        int hardwareVersion = cmd.getHardwareVersion() & 0xffff;
+        int softwareVersion = cmd.getSoftwareVersion() & 0xffff;
+
+        GBDeviceEventVersionInfo versionInfo = new GBDeviceEventVersionInfo();
+        versionInfo.fwVersion = String.format("%d.%d", softwareVersion >> 8, softwareVersion & 0xff);
+        // Last character is a \x1f? Not printable either way.
+        versionInfo.hwVersion = cmd.getTypeCode().substring(0, 3);
+        getSupport().evaluateGBDeviceEvent(versionInfo);
+
+        GBDevice device = getSupport().getDevice();
+        device.setFirmwareVersion2(String.format("%d.%d", hardwareVersion >> 8, hardwareVersion & 0xff));
+
+        getSupport().completeInitialization();
+
+        operationStatus = OperationStatus.FINISHED;
+    }
+
+    @Override
+    public int getCommandId() {
+        return LefunConstants.CMD_FIRMWARE_INFO;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/GetGeneralSettingsRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/GetGeneralSettingsRequest.java
new file mode 100644
index 0000000..11115c2
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/GetGeneralSettingsRequest.java
@@ -0,0 +1,58 @@
+/*  Copyright (C) 2020-2024 Yukai Li
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.BaseCommand;
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.SettingsCommand;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.LefunDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.OperationStatus;
+
+public class GetGeneralSettingsRequest extends Request {
+    public GetGeneralSettingsRequest(LefunDeviceSupport support, TransactionBuilder builder) {
+        super(support, builder);
+    }
+
+    @Override
+    public byte[] createRequest() {
+        SettingsCommand cmd = new SettingsCommand();
+
+        cmd.setOp(BaseCommand.OP_GET);
+
+        return cmd.serialize();
+    }
+
+    @Override
+    public void handleResponse(byte[] data) {
+        SettingsCommand cmd = new SettingsCommand();
+        cmd.deserialize(data);
+        if (cmd.getOp() == BaseCommand.OP_GET) {
+            getSupport().receiveGeneralSettings(
+                    (int) cmd.getAmPmIndicator() & 0xff,
+                    (int) cmd.getMeasurementUnit() & 0xff
+            );
+        }
+
+        operationStatus = OperationStatus.FINISHED;
+    }
+
+    @Override
+    public int getCommandId() {
+        return LefunConstants.CMD_SETTINGS;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/GetHydrationReminderIntervalRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/GetHydrationReminderIntervalRequest.java
new file mode 100644
index 0000000..afbee45
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/GetHydrationReminderIntervalRequest.java
@@ -0,0 +1,55 @@
+/*  Copyright (C) 2020-2024 Yukai Li
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.BaseCommand;
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.HydrationReminderIntervalCommand;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.LefunDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.OperationStatus;
+
+public class GetHydrationReminderIntervalRequest extends Request {
+    public GetHydrationReminderIntervalRequest(LefunDeviceSupport support, TransactionBuilder builder) {
+        super(support, builder);
+    }
+
+    @Override
+    public byte[] createRequest() {
+        HydrationReminderIntervalCommand cmd = new HydrationReminderIntervalCommand();
+
+        cmd.setOp(BaseCommand.OP_GET);
+
+        return cmd.serialize();
+    }
+
+    @Override
+    public void handleResponse(byte[] data) {
+        HydrationReminderIntervalCommand cmd = new HydrationReminderIntervalCommand();
+        cmd.deserialize(data);
+        if (cmd.getOp() == BaseCommand.OP_GET) {
+            getSupport().receiveHydrationReminderIntervalSetting((int) cmd.getHydrationReminderInterval() & 0xff);
+        }
+
+        operationStatus = OperationStatus.FINISHED;
+    }
+
+    @Override
+    public int getCommandId() {
+        return LefunConstants.CMD_HYDRATION_REMINDER_INTERVAL;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/GetPpgDataRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/GetPpgDataRequest.java
new file mode 100644
index 0000000..e00e7b8
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/GetPpgDataRequest.java
@@ -0,0 +1,85 @@
+/*  Copyright (C) 2020-2024 Yukai Li
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.GetPpgDataCommand;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.LefunDeviceSupport;
+
+public class GetPpgDataRequest extends MultiFetchRequest {
+    private int ppgType;
+
+    public GetPpgDataRequest(LefunDeviceSupport support) {
+        super(support);
+    }
+
+    public int getPpgType() {
+        return ppgType;
+    }
+
+    public void setPpgType(int ppgType) {
+        this.ppgType = ppgType;
+    }
+
+    @Override
+    public byte[] createRequest() {
+        GetPpgDataCommand cmd = new GetPpgDataCommand();
+        cmd.setPpgType(ppgType);
+        return cmd.serialize();
+    }
+
+    @Override
+    public void handleResponse(byte[] data) {
+        GetPpgDataCommand cmd = new GetPpgDataCommand();
+        cmd.deserialize(data);
+
+        if (cmd.getPpgType() != ppgType) {
+            throw new IllegalArgumentException("Mismatching PPG type");
+        }
+
+        if (totalRecords == -1) {
+            totalRecords = cmd.getTotalRecords() & 0xffff;
+        } else if (totalRecords != (cmd.getTotalRecords() & 0xffff)) {
+            throw new IllegalArgumentException("Total records mismatch");
+        }
+
+        if (totalRecords != 0) {
+            int currentRecord = cmd.getCurrentRecord() & 0xffff;
+            if (lastRecord + 1 != currentRecord) {
+                throw new IllegalArgumentException("Records received out of sequence");
+            }
+            lastRecord = currentRecord;
+
+            getSupport().handlePpgData(cmd);
+        } else {
+            lastRecord = totalRecords;
+        }
+
+        if (lastRecord == totalRecords)
+            operationFinished();
+    }
+
+    @Override
+    public int getCommandId() {
+        return LefunConstants.CMD_PPG_DATA;
+    }
+
+    @Override
+    protected String getOperationName() {
+        return "Getting PPG data";
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/GetSedentaryReminderIntervalRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/GetSedentaryReminderIntervalRequest.java
new file mode 100644
index 0000000..421a4ea
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/GetSedentaryReminderIntervalRequest.java
@@ -0,0 +1,55 @@
+/*  Copyright (C) 2020-2024 Yukai Li
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.BaseCommand;
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.SedentaryReminderIntervalCommand;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.LefunDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.OperationStatus;
+
+public class GetSedentaryReminderIntervalRequest extends Request {
+    public GetSedentaryReminderIntervalRequest(LefunDeviceSupport support, TransactionBuilder builder) {
+        super(support, builder);
+    }
+
+    @Override
+    public byte[] createRequest() {
+        SedentaryReminderIntervalCommand cmd = new SedentaryReminderIntervalCommand();
+
+        cmd.setOp(BaseCommand.OP_GET);
+
+        return cmd.serialize();
+    }
+
+    @Override
+    public void handleResponse(byte[] data) {
+        SedentaryReminderIntervalCommand cmd = new SedentaryReminderIntervalCommand();
+        cmd.deserialize(data);
+        if (cmd.getOp() == BaseCommand.OP_GET) {
+            getSupport().receiveSedentaryReminderIntervalSetting((int) cmd.getSedentaryReminderInterval() & 0xff);
+        }
+
+        operationStatus = OperationStatus.FINISHED;
+    }
+
+    @Override
+    public int getCommandId() {
+        return LefunConstants.CMD_SEDENTARY_REMINDER_INTERVAL;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/GetSleepDataRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/GetSleepDataRequest.java
new file mode 100644
index 0000000..6529e5f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/GetSleepDataRequest.java
@@ -0,0 +1,85 @@
+/*  Copyright (C) 2020-2024 Yukai Li
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.GetSleepDataCommand;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.LefunDeviceSupport;
+
+public class GetSleepDataRequest extends MultiFetchRequest {
+    private int daysAgo;
+
+    public GetSleepDataRequest(LefunDeviceSupport support) {
+        super(support);
+    }
+
+    public int getDaysAgo() {
+        return daysAgo;
+    }
+
+    public void setDaysAgo(int daysAgo) {
+        this.daysAgo = daysAgo;
+    }
+
+    @Override
+    public byte[] createRequest() {
+        GetSleepDataCommand cmd = new GetSleepDataCommand();
+        cmd.setDaysAgo((byte) daysAgo);
+        return cmd.serialize();
+    }
+
+    @Override
+    public void handleResponse(byte[] data) {
+        GetSleepDataCommand cmd = new GetSleepDataCommand();
+        cmd.deserialize(data);
+
+        if (daysAgo != (cmd.getDaysAgo() & 0xff)) {
+            throw new IllegalArgumentException("Mismatching days ago");
+        }
+
+        if (totalRecords == -1) {
+            totalRecords = cmd.getTotalRecords() & 0xff;
+        } else if (totalRecords != (cmd.getTotalRecords() & 0xff)) {
+            throw new IllegalArgumentException("Total records mismatch");
+        }
+
+        if (totalRecords != 0) {
+            int currentRecord = cmd.getCurrentRecord() & 0xff;
+            if (lastRecord + 1 != currentRecord) {
+                throw new IllegalArgumentException("Records received out of sequence");
+            }
+            lastRecord = currentRecord;
+
+            getSupport().handleSleepData(cmd);
+        } else {
+            lastRecord = totalRecords;
+        }
+
+        if (lastRecord == totalRecords)
+            operationFinished();
+    }
+
+    @Override
+    public int getCommandId() {
+        return LefunConstants.CMD_SLEEP_DATA;
+    }
+
+    @Override
+    protected String getOperationName() {
+        return "Getting sleep data";
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/MultiFetchRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/MultiFetchRequest.java
new file mode 100644
index 0000000..2011afd
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/MultiFetchRequest.java
@@ -0,0 +1,110 @@
+/*  Copyright (C) 2020-2024 Damien Gaignon, Yukai Li
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests;
+
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.widget.Toast;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceBusyAction;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.LefunDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.OperationStatus;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+/**
+ * Represents a request that receives several responses
+ */
+public abstract class MultiFetchRequest extends Request {
+    /**
+     * Instantiates a new MultiFetchRequest
+     * @param support the device support
+     */
+    protected MultiFetchRequest(LefunDeviceSupport support) {
+        super(support, null);
+        removeAfterHandling = false;
+    }
+
+    protected int lastRecord = 0;
+    protected int totalRecords = -1;
+
+    @Override
+    protected void prePerform() throws IOException {
+        super.prePerform();
+        builder = performInitialized(getClass().getSimpleName());
+        if (getDevice().isBusy()) {
+            throw new IllegalStateException("Device is busy");
+        }
+        builder.add(new SetDeviceBusyAction(getDevice(), getOperationName(), getContext()));
+        builder.wait(1000); // Wait a bit (after previous operation), or device sometimes won't respond
+    }
+
+    @Override
+    protected void operationFinished() {
+        if (lastRecord == totalRecords)
+            removeAfterHandling = true;
+        try {
+            super.operationFinished();
+            TransactionBuilder builder = performInitialized("Finishing operation");
+            builder.setCallback(null);
+            builder.queue(getQueue());
+        } catch (IOException e) {
+            GB.toast(getContext(), "Failed to reset callback", Toast.LENGTH_SHORT,
+                    GB.ERROR, e);
+        }
+        unsetBusy();
+        operationStatus = OperationStatus.FINISHED;
+        getSupport().runNextQueuedRequest();
+    }
+
+    @Override
+    public boolean onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
+        if (characteristic.getUuid().equals(LefunConstants.UUID_CHARACTERISTIC_LEFUN_NOTIFY)) {
+            byte[] data = characteristic.getValue();
+            // Parse response
+            if (data.length >= LefunConstants.CMD_HEADER_LENGTH && data[0] == LefunConstants.CMD_RESPONSE_ID) {
+                try {
+                    handleResponse(data);
+                    return true;
+                } catch (IllegalArgumentException e) {
+                    log("Failed to handle response");
+                    operationFinished();
+                }
+            }
+
+            getSupport().logMessageContent(data);
+            log("Invalid response received");
+            return false;
+        }
+
+        return super.onCharacteristicChanged(gatt, characteristic);
+    }
+
+    @Override
+    public boolean isSelfQueue() {
+        return true;
+    }
+
+    /**
+     * Gets the display operation name
+     * @return the operation name
+     */
+    protected abstract String getOperationName();
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/Request.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/Request.java
new file mode 100644
index 0000000..fc0d512
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/Request.java
@@ -0,0 +1,151 @@
+/*  Copyright (C) 2020-2024 Yukai Li
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests;
+
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.widget.Toast;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEOperation;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.LefunDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.OperationStatus;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+// Ripped from nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.Request
+
+/**
+ * Basic request for operations with Lefun devices
+ */
+public abstract class Request extends AbstractBTLEOperation<LefunDeviceSupport> {
+    protected TransactionBuilder builder;
+    protected boolean removeAfterHandling = true;
+    private Logger logger = (Logger) LoggerFactory.getLogger(getName());
+
+    /**
+     * Instantiates Request
+     *
+     * @param support the device support
+     * @param builder the transaction builder to use
+     */
+    protected Request(LefunDeviceSupport support, TransactionBuilder builder) {
+        super(support);
+        this.builder = builder;
+    }
+
+    /**
+     * Gets the transaction builder
+     *
+     * @return the transaction builder
+     */
+    public TransactionBuilder getTransactionBuilder() {
+        return builder;
+    }
+
+    @Override
+    protected void doPerform() throws IOException {
+        BluetoothGattCharacteristic characteristic = getSupport()
+                .getCharacteristic(LefunConstants.UUID_CHARACTERISTIC_LEFUN_WRITE);
+        builder.write(characteristic, createRequest());
+        if (isSelfQueue())
+            getSupport().performConnected(builder.getTransaction());
+    }
+
+    /**
+     * When implemented in a subclass, provides the request bytes to send to the device
+     *
+     * @return the request bytes
+     */
+    public abstract byte[] createRequest();
+
+    /**
+     * When overridden in a subclass, handles the response to the current command
+     *
+     * @param data the response data
+     */
+    public void handleResponse(byte[] data) {
+        operationStatus = OperationStatus.FINISHED;
+    }
+
+    /**
+     * Gets the class name of this instance
+     *
+     * @return the class name
+     */
+    public String getName() {
+        Class thisClass = getClass();
+        while (thisClass.isAnonymousClass()) thisClass = thisClass.getSuperclass();
+        return thisClass.getSimpleName();
+    }
+
+    /**
+     * Logs a debug message
+     *
+     * @param message the message to log
+     */
+    protected void log(String message) {
+        logger.debug(message);
+    }
+
+    /**
+     * When implemented in a subclass, returns the command ID associated with the current request
+     *
+     * @return the command ID
+     */
+    public abstract int getCommandId();
+
+    /**
+     * Gets whether the request will queue itself
+     *
+     * @return whether the request is self-queuing
+     */
+    public boolean isSelfQueue() {
+        return false;
+    }
+
+    /**
+     * Gets whether the request expects a response
+     *
+     * @return whether the request expects a response
+     */
+    public boolean expectsResponse() {
+        return true;
+    }
+
+    /**
+     * Gets whether the response should be removed from in progress requests list after handling
+     *
+     * @return whether the response should be removed after handling
+     */
+    public boolean shouldRemoveAfterHandling() {
+        return removeAfterHandling;
+    }
+
+    /**
+     * Reports an error to the user
+     *
+     * @param message the message to show
+     */
+    protected void reportFailure(String message) {
+        GB.toast(getContext(), message, Toast.LENGTH_SHORT, GB.ERROR);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SendCallNotificationRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SendCallNotificationRequest.java
new file mode 100644
index 0000000..18d7473
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SendCallNotificationRequest.java
@@ -0,0 +1,65 @@
+/*  Copyright (C) 2020-2024 Yukai Li
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.NotificationCommand;
+import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.LefunDeviceSupport;
+
+public class SendCallNotificationRequest extends AbstractSendNotificationRequest {
+    public SendCallNotificationRequest(LefunDeviceSupport support, TransactionBuilder builder) {
+        super(support, builder);
+    }
+
+    private CallSpec callNotification;
+
+    public CallSpec getCallNotification() {
+        return callNotification;
+    }
+
+    public void setCallNotification(CallSpec callNotification) {
+        this.callNotification = callNotification;
+    }
+
+    @Override
+    protected String getMessage() {
+        String message = "";
+        if (callNotification.number != null &&!callNotification.number.isEmpty()) {
+            message = callNotification.number;
+        }
+
+        if (callNotification.name != null && !callNotification.name.isEmpty()) {
+            if (message.length() > 0) {
+                message += " - ";
+            }
+            message += callNotification.name;
+        }
+
+        return message;
+    }
+
+    @Override
+    protected byte getNotificationType() {
+        return NotificationCommand.SERVICE_TYPE_CALL;
+    }
+
+    @Override
+    protected byte getExtendedNotificationType() {
+        return 0;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SendNotificationRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SendNotificationRequest.java
new file mode 100644
index 0000000..2d0eca2
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SendNotificationRequest.java
@@ -0,0 +1,109 @@
+/*  Copyright (C) 2020-2024 Yukai Li
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.NotificationCommand;
+import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.LefunDeviceSupport;
+
+public class SendNotificationRequest extends AbstractSendNotificationRequest {
+    NotificationSpec notification;
+
+    public SendNotificationRequest(LefunDeviceSupport support, TransactionBuilder builder) {
+        super(support, builder);
+    }
+
+    @Override
+    protected byte getNotificationType() {
+        switch (notification.type) {
+            case GENERIC_PHONE:
+                return NotificationCommand.SERVICE_TYPE_CALL;
+            case GENERIC_SMS:
+            case GENERIC_EMAIL:
+            default:
+                return NotificationCommand.SERVICE_TYPE_TEXT;
+            case WECHAT:
+                return NotificationCommand.SERVICE_TYPE_WECHAT;
+            case FACEBOOK:
+            case FACEBOOK_MESSENGER:
+            case TWITTER:
+            case LINKEDIN:
+            case WHATSAPP:
+            case LINE:
+            case KAKAO_TALK:
+                return NotificationCommand.SERVICE_TYPE_EXTENDED;
+        }
+    }
+
+    @Override
+    protected byte getExtendedNotificationType() {
+        switch (notification.type) {
+            case GENERIC_PHONE:
+            case GENERIC_SMS:
+            case GENERIC_EMAIL:
+            default:
+            case WECHAT:
+                return 0;
+            case FACEBOOK:
+            case FACEBOOK_MESSENGER:
+                return NotificationCommand.EXTENDED_SERVICE_TYPE_FACEBOOK;
+            case TWITTER:
+                return NotificationCommand.EXTENDED_SERVICE_TYPE_TWITTER;
+            case LINKEDIN:
+                return NotificationCommand.EXTENDED_SERVICE_TYPE_LINKEDIN;
+            case WHATSAPP:
+                return NotificationCommand.EXTENDED_SERVICE_TYPE_WHATSAPP;
+            case LINE:
+                return NotificationCommand.EXTENDED_SERVICE_TYPE_LINE;
+            case KAKAO_TALK:
+                return NotificationCommand.EXTENDED_SERVICE_TYPE_KAKAOTALK;
+        }
+    }
+
+    public NotificationSpec getNotification() {
+        return notification;
+    }
+
+    public void setNotification(NotificationSpec notification) {
+        this.notification = notification;
+    }
+
+    @Override
+    protected String getMessage() {
+        // Based on nodomain.freeyourgadget.gadgetbridge.service.devices.id115.SendNotificationOperation
+        String message = "";
+
+        if (notification.phoneNumber != null && !notification.phoneNumber.isEmpty()) {
+            message += notification.phoneNumber + ": ";
+        }
+
+        if (notification.sender != null && !notification.sender.isEmpty()) {
+            message += notification.sender + " - ";
+        } else if (notification.title != null && !notification.title.isEmpty()) {
+            message += notification.title + " - ";
+        } else if (notification.subject != null && !notification.subject.isEmpty()) {
+            message += notification.subject + " - ";
+        }
+
+        if (notification.body != null && !notification.body.isEmpty()) {
+            message += notification.body;
+        }
+
+        return message;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SetAlarmRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SetAlarmRequest.java
new file mode 100644
index 0000000..3cb7271
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SetAlarmRequest.java
@@ -0,0 +1,116 @@
+/*  Copyright (C) 2020-2024 Yukai Li
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.AlarmCommand;
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.BaseCommand;
+import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.LefunDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.OperationStatus;
+
+public class SetAlarmRequest extends Request {
+    private int index;
+    private boolean enabled;
+    private int dayOfWeek;
+    private int hour;
+    private int minute;
+    public SetAlarmRequest(LefunDeviceSupport support, TransactionBuilder builder) {
+        super(support, builder);
+    }
+
+    public int getIndex() {
+        return index;
+    }
+
+    public void setIndex(int index) {
+        this.index = index;
+    }
+
+    public boolean isEnabled() {
+        return enabled;
+    }
+
+    public void setEnabled(boolean enabled) {
+        this.enabled = enabled;
+    }
+
+    public int getDayOfWeek() {
+        return dayOfWeek;
+    }
+
+    public void setDayOfWeek(int dayOfWeek) {
+        this.dayOfWeek = dayOfWeek;
+    }
+
+    public int getHour() {
+        return hour;
+    }
+
+    public void setHour(int hour) {
+        this.hour = hour;
+    }
+
+    public int getMinute() {
+        return minute;
+    }
+
+    public void setMinute(int minute) {
+        this.minute = minute;
+    }
+
+    @Override
+    public byte[] createRequest() {
+        AlarmCommand cmd = new AlarmCommand();
+        cmd.setOp(BaseCommand.OP_SET);
+        cmd.setIndex((byte) index);
+        cmd.setEnabled(enabled);
+        cmd.setNumOfSnoozes((byte) 0);
+        cmd.setHour((byte) hour);
+        cmd.setMinute((byte) minute);
+
+        // Translate GB alarm day of week to Lefun day of week
+        // GB starts on Monday, Lefun starts on Sunday
+        for (int i = 0; i < 6; ++i) {
+            if ((dayOfWeek & (1 << i)) != 0) {
+                cmd.setDayOfWeek(i + 1, true);
+            }
+        }
+        if ((dayOfWeek & Alarm.ALARM_SUN) != 0) {
+            cmd.setDayOfWeek(AlarmCommand.DOW_SUNDAY, true);
+        }
+
+        return cmd.serialize();
+    }
+
+    @Override
+    public void handleResponse(byte[] data) {
+        AlarmCommand cmd = new AlarmCommand();
+        cmd.deserialize(data);
+
+        if (cmd.getOp() != BaseCommand.OP_SET || cmd.getIndex() != index || !cmd.isSetSuccess())
+            reportFailure("Could not set alarm");
+
+        operationStatus = OperationStatus.FINISHED;
+    }
+
+    @Override
+    public int getCommandId() {
+        return LefunConstants.CMD_ALARM;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SetEnabledFeaturesRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SetEnabledFeaturesRequest.java
new file mode 100644
index 0000000..102f9c6
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SetEnabledFeaturesRequest.java
@@ -0,0 +1,61 @@
+/*  Copyright (C) 2020-2024 Yukai Li
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.BaseCommand;
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.FeaturesCommand;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.LefunDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.OperationStatus;
+
+public class SetEnabledFeaturesRequest extends Request {
+    private FeaturesCommand cmd;
+
+    public SetEnabledFeaturesRequest(LefunDeviceSupport support, TransactionBuilder builder) {
+        super(support, builder);
+    }
+
+    public FeaturesCommand getCmd() {
+        return cmd;
+    }
+
+    public void setCmd(FeaturesCommand cmd) {
+        this.cmd = cmd;
+    }
+
+    @Override
+    public byte[] createRequest() {
+        cmd.setOp(BaseCommand.OP_SET);
+        return cmd.serialize();
+    }
+
+    @Override
+    public void handleResponse(byte[] data) {
+        FeaturesCommand cmd = new FeaturesCommand();
+        cmd.deserialize(data);
+        if (cmd.getOp() == BaseCommand.OP_SET && !cmd.isSetSuccess())
+            reportFailure("Could not set enabled features");
+
+        operationStatus = OperationStatus.FINISHED;
+    }
+
+    @Override
+    public int getCommandId() {
+        return LefunConstants.CMD_FEATURES;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SetGeneralSettingsRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SetGeneralSettingsRequest.java
new file mode 100644
index 0000000..82b99f7
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SetGeneralSettingsRequest.java
@@ -0,0 +1,76 @@
+/*  Copyright (C) 2020-2024 Yukai Li
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.BaseCommand;
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.SettingsCommand;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.LefunDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.OperationStatus;
+
+public class SetGeneralSettingsRequest extends Request {
+    private byte amPm;
+    private byte units;
+
+    public SetGeneralSettingsRequest(LefunDeviceSupport support, TransactionBuilder builder) {
+        super(support, builder);
+    }
+
+    public byte getAmPm() {
+        return amPm;
+    }
+
+    public void setAmPm(byte amPm) {
+        this.amPm = amPm;
+    }
+
+    public byte getUnits() {
+        return units;
+    }
+
+    public void setUnits(byte units) {
+        this.units = units;
+    }
+
+    @Override
+    public byte[] createRequest() {
+        SettingsCommand cmd = new SettingsCommand();
+
+        cmd.setOp(BaseCommand.OP_SET);
+        cmd.setOption1((byte) 0xff); // Don't set
+        cmd.setAmPmIndicator(amPm);
+        cmd.setMeasurementUnit(units);
+
+        return cmd.serialize();
+    }
+
+    @Override
+    public void handleResponse(byte[] data) {
+        SettingsCommand cmd = new SettingsCommand();
+        cmd.deserialize(data);
+        if (cmd.getOp() == BaseCommand.OP_SET && !cmd.isSetSuccess())
+            reportFailure("Could not set settings");
+
+        operationStatus = OperationStatus.FINISHED;
+    }
+
+    @Override
+    public int getCommandId() {
+        return LefunConstants.CMD_SETTINGS;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SetHydrationReminderIntervalRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SetHydrationReminderIntervalRequest.java
new file mode 100644
index 0000000..2d99627
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SetHydrationReminderIntervalRequest.java
@@ -0,0 +1,65 @@
+/*  Copyright (C) 2020-2024 Yukai Li
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.BaseCommand;
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.HydrationReminderIntervalCommand;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.LefunDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.OperationStatus;
+
+public class SetHydrationReminderIntervalRequest extends Request {
+    private int interval;
+
+    public SetHydrationReminderIntervalRequest(LefunDeviceSupport support, TransactionBuilder builder) {
+        super(support, builder);
+    }
+
+    public int getInterval() {
+        return interval;
+    }
+
+    public void setInterval(int interval) {
+        this.interval = interval;
+    }
+
+    @Override
+    public byte[] createRequest() {
+        HydrationReminderIntervalCommand cmd = new HydrationReminderIntervalCommand();
+
+        cmd.setOp(BaseCommand.OP_SET);
+        cmd.setHydrationReminderInterval((byte) interval);
+
+        return cmd.serialize();
+    }
+
+    @Override
+    public void handleResponse(byte[] data) {
+        HydrationReminderIntervalCommand cmd = new HydrationReminderIntervalCommand();
+        cmd.deserialize(data);
+        if (cmd.getOp() == BaseCommand.OP_SET && !cmd.isSetSuccess())
+            reportFailure("Could not set hydration reminder interval");
+
+        operationStatus = OperationStatus.FINISHED;
+    }
+
+    @Override
+    public int getCommandId() {
+        return LefunConstants.CMD_HYDRATION_REMINDER_INTERVAL;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SetLanguageRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SetLanguageRequest.java
new file mode 100644
index 0000000..dcff740
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SetLanguageRequest.java
@@ -0,0 +1,63 @@
+/*  Copyright (C) 2020-2024 Yukai Li
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.SetLanguageCommand;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.LefunDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.OperationStatus;
+
+public class SetLanguageRequest extends Request {
+    private byte language;
+
+    public SetLanguageRequest(LefunDeviceSupport support, TransactionBuilder builder) {
+        super(support, builder);
+    }
+
+    public byte getLanguage() {
+        return language;
+    }
+
+    public void setLanguage(byte language) {
+        this.language = language;
+    }
+
+    @Override
+    public byte[] createRequest() {
+        SetLanguageCommand cmd = new SetLanguageCommand();
+
+        cmd.setLanguage(language);
+
+        return cmd.serialize();
+    }
+
+    @Override
+    public void handleResponse(byte[] data) {
+        SetLanguageCommand cmd = new SetLanguageCommand();
+        cmd.deserialize(data);
+        if (!cmd.isSetSuccess())
+            reportFailure("Could not set language");
+
+        operationStatus = OperationStatus.FINISHED;
+    }
+
+    @Override
+    public int getCommandId() {
+        return LefunConstants.CMD_LANGUAGE;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SetProfileRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SetProfileRequest.java
new file mode 100644
index 0000000..565c9fd
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SetProfileRequest.java
@@ -0,0 +1,72 @@
+/*  Copyright (C) 2020-2024 Yukai Li
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.BaseCommand;
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.ProfileCommand;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.LefunDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.OperationStatus;
+
+public class SetProfileRequest extends Request {
+    private ActivityUser user;
+
+    public SetProfileRequest(LefunDeviceSupport support, TransactionBuilder builder) {
+        super(support, builder);
+    }
+
+    public ActivityUser getUser() {
+        return user;
+    }
+
+    public void setUser(ActivityUser user) {
+        this.user = user;
+    }
+
+    @Override
+    public byte[] createRequest() {
+        ProfileCommand cmd = new ProfileCommand();
+
+        cmd.setOp(BaseCommand.OP_SET);
+        // No "other" option available, only male or female
+        cmd.setGender(user.getGender() == ActivityUser.GENDER_FEMALE
+                ? ProfileCommand.GENDER_FEMALE
+                : ProfileCommand.GENDER_MALE);
+        cmd.setHeight((byte) user.getHeightCm());
+        cmd.setWeight((byte) user.getWeightKg());
+        cmd.setAge((byte) user.getAge());
+
+        return cmd.serialize();
+    }
+
+    @Override
+    public void handleResponse(byte[] data) {
+        ProfileCommand cmd = new ProfileCommand();
+        cmd.deserialize(data);
+        if (cmd.getOp() == BaseCommand.OP_SET && !cmd.isSetSuccess())
+            reportFailure("Could not set profile");
+
+        operationStatus = OperationStatus.FINISHED;
+    }
+
+    @Override
+    public int getCommandId() {
+        return LefunConstants.CMD_PROFILE;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SetSedentaryReminderIntervalRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SetSedentaryReminderIntervalRequest.java
new file mode 100644
index 0000000..fa018a9
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SetSedentaryReminderIntervalRequest.java
@@ -0,0 +1,65 @@
+/*  Copyright (C) 2020-2024 Yukai Li
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.BaseCommand;
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.SedentaryReminderIntervalCommand;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.LefunDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.OperationStatus;
+
+public class SetSedentaryReminderIntervalRequest extends Request {
+    private int interval;
+
+    public SetSedentaryReminderIntervalRequest(LefunDeviceSupport support, TransactionBuilder builder) {
+        super(support, builder);
+    }
+
+    public int getInterval() {
+        return interval;
+    }
+
+    public void setInterval(int interval) {
+        this.interval = interval;
+    }
+
+    @Override
+    public byte[] createRequest() {
+        SedentaryReminderIntervalCommand cmd = new SedentaryReminderIntervalCommand();
+
+        cmd.setOp(BaseCommand.OP_SET);
+        cmd.setSedentaryReminderInterval((byte) interval);
+
+        return cmd.serialize();
+    }
+
+    @Override
+    public void handleResponse(byte[] data) {
+        SedentaryReminderIntervalCommand cmd = new SedentaryReminderIntervalCommand();
+        cmd.deserialize(data);
+        if (cmd.getOp() == BaseCommand.OP_SET && !cmd.isSetSuccess())
+            reportFailure("Could not set sedentary reminder interval");
+
+        operationStatus = OperationStatus.FINISHED;
+    }
+
+    @Override
+    public int getCommandId() {
+        return LefunConstants.CMD_SEDENTARY_REMINDER_INTERVAL;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SetTimeRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SetTimeRequest.java
new file mode 100644
index 0000000..25222be
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SetTimeRequest.java
@@ -0,0 +1,64 @@
+/*  Copyright (C) 2020-2024 Yukai Li
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests;
+
+import java.util.Calendar;
+import java.util.TimeZone;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.BaseCommand;
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.TimeCommand;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.LefunDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.OperationStatus;
+
+public class SetTimeRequest extends Request {
+    public SetTimeRequest(LefunDeviceSupport support, TransactionBuilder builder) {
+        super(support, builder);
+    }
+
+    @Override
+    public byte[] createRequest() {
+        TimeCommand cmd = new TimeCommand();
+        Calendar c = Calendar.getInstance();
+
+        cmd.setOp(BaseCommand.OP_SET);
+        cmd.setYear((byte)(c.get(Calendar.YEAR) - 2000));
+        cmd.setMonth((byte)(c.get(Calendar.MONTH) + 1));
+        cmd.setDay((byte)c.get(Calendar.DAY_OF_MONTH));
+        cmd.setHour((byte)c.get(Calendar.HOUR_OF_DAY));
+        cmd.setMinute((byte)c.get(Calendar.MINUTE));
+        cmd.setSecond((byte)c.get(Calendar.SECOND));
+
+        return cmd.serialize();
+    }
+
+    @Override
+    public void handleResponse(byte[] data) {
+        TimeCommand cmd = new TimeCommand();
+        cmd.deserialize(data);
+        if (cmd.getOp() == BaseCommand.OP_SET && !cmd.isSetSuccess())
+            reportFailure("Could not set time");
+
+        operationStatus = OperationStatus.FINISHED;
+    }
+
+    @Override
+    public int getCommandId() {
+        return LefunConstants.CMD_TIME;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/StartPpgRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/StartPpgRequest.java
new file mode 100644
index 0000000..5a4f80c
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/StartPpgRequest.java
@@ -0,0 +1,62 @@
+/*  Copyright (C) 2020-2024 Yukai Li
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
+import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.StartPpgSensingCommand;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.LefunDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.OperationStatus;
+
+public class StartPpgRequest extends Request {
+    public StartPpgRequest(LefunDeviceSupport support, TransactionBuilder builder) {
+        super(support, builder);
+    }
+
+    int ppgType;
+
+    public int getPpgType() {
+        return ppgType;
+    }
+
+    public void setPpgType(int ppgType) {
+        this.ppgType = ppgType;
+    }
+
+    @Override
+    public byte[] createRequest() {
+        StartPpgSensingCommand cmd = new StartPpgSensingCommand();
+        cmd.setPpgType(ppgType);
+        return cmd.serialize();
+    }
+
+    @Override
+    public int getCommandId() {
+        return LefunConstants.CMD_PPG_START;
+    }
+
+    @Override
+    public void handleResponse(byte[] data) {
+        StartPpgSensingCommand cmd = new StartPpgSensingCommand();
+        cmd.deserialize(data);
+
+        if (!cmd.isSetSuccess() || cmd.getPpgType() != ppgType)
+            reportFailure("Could not start PPG sensing");
+
+        operationStatus = OperationStatus.FINISHED;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lenovo/operations/InitOperation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lenovo/operations/InitOperation.java
new file mode 100644
index 0000000..5982e82
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lenovo/operations/InitOperation.java
@@ -0,0 +1,92 @@
+/*  Copyright (C) 2019-2024 Damien Gaignon, mamucho, mkusnierz
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.lenovo.operations;
+
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.widget.Toast;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.lenovo.watchxplus.WatchXPlusConstants;
+import nodomain.freeyourgadget.gadgetbridge.devices.watch9.Watch9Constants;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEOperation;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.lenovo.watchxplus.WatchXPlusDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.util.ArrayUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class InitOperation extends AbstractBTLEOperation<WatchXPlusDeviceSupport>{
+
+    private static final Logger LOG = LoggerFactory.getLogger(InitOperation.class);
+
+    private final TransactionBuilder builder;
+    private final boolean needsAuth;
+    private final BluetoothGattCharacteristic cmdCharacteristic = getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE);
+    private final BluetoothGattCharacteristic dbCharacteristic = getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_DATABASE_READ);
+
+    public InitOperation(boolean needsAuth, WatchXPlusDeviceSupport support, TransactionBuilder builder) {
+        super(support);
+        this.needsAuth = needsAuth;
+        this.builder = builder;
+        builder.setCallback(this);
+    }
+
+    @Override
+    protected void doPerform() throws IOException {
+        builder.notify(cmdCharacteristic, true).notify(dbCharacteristic, true);
+            builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.AUTHENTICATING, getContext()));
+            getSupport().authorizationRequest(builder, needsAuth);
+            builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext()));
+            getSupport().initialize(builder);
+            getSupport().performImmediately(builder);
+    }
+
+    @Override
+    public boolean onCharacteristicChanged(BluetoothGatt gatt,
+                                           BluetoothGattCharacteristic characteristic) {
+        UUID characteristicUUID = characteristic.getUuid();
+        if (Watch9Constants.UUID_CHARACTERISTIC_WRITE.equals(characteristicUUID) && needsAuth) {
+            try {
+                byte[] value = characteristic.getValue();
+                getSupport().logMessageContent(value);
+                if (ArrayUtils.equals(value, Watch9Constants.RESP_AUTHORIZATION_TASK, 5) && value[8] == 0x01) {
+                    TransactionBuilder builder = getSupport().createTransactionBuilder("authInit");
+                    builder.setCallback(this);
+                    builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext()));
+                    getSupport().initialize(builder).performImmediately(builder);
+                } else {
+                    return super.onCharacteristicChanged(gatt, characteristic);
+                }
+            } catch (Exception e) {
+                GB.toast(getContext(), "Error authenticating Watch X Plus", Toast.LENGTH_LONG, GB.ERROR, e);
+            }
+            return true;
+        } else {
+            LOG.info("Unhandled characteristic changed: " + characteristicUUID);
+            return super.onCharacteristicChanged(gatt, characteristic);
+        }
+    }
+
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lenovo/watchxplus/WatchXPlusDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lenovo/watchxplus/WatchXPlusDeviceSupport.java
new file mode 100644
index 0000000..b0f82db
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lenovo/watchxplus/WatchXPlusDeviceSupport.java
@@ -0,0 +1,2135 @@
+/*  Copyright (C) 2019-2024 Andreas B枚hler, Andreas Shimokawa, Arjan
+    Schrijver, Damien Gaignon, mamucho, mkusnierz, Taavi Eom盲e
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.lenovo.watchxplus;
+
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.SharedPreferences;
+import android.os.Handler;
+import android.widget.Toast;
+
+import androidx.annotation.IntRange;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.TimeZone;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.SettingsActivity;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventCallControl;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventFindPhone;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo;
+import nodomain.freeyourgadget.gadgetbridge.devices.lenovo.DataType;
+import nodomain.freeyourgadget.gadgetbridge.devices.lenovo.watchxplus.WatchXPlusConstants;
+import nodomain.freeyourgadget.gadgetbridge.devices.lenovo.watchxplus.WatchXPlusDeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.lenovo.watchxplus.WatchXPlusSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.WatchXPlusActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.entities.WatchXPlusHealthActivityOverlay;
+import nodomain.freeyourgadget.gadgetbridge.entities.WatchXPlusHealthActivityOverlayDao;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
+import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
+import nodomain.freeyourgadget.gadgetbridge.model.BatteryState;
+import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
+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.BLETypeConversions;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.GattService;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.lenovo.operations.InitOperation;
+import nodomain.freeyourgadget.gadgetbridge.util.AlarmUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.ArrayUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.BcdUtil;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
+
+public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport {
+    private boolean needsAuth;
+    private int sequenceNumber = 0;
+    private boolean isCalibrationActive = false;
+
+    private final Map<Integer, Integer> dataToFetch = new LinkedHashMap<>();
+    private int requestedDataTimestamp;
+    private int dataSlots = 0;
+    private DataType currentDataType;
+
+    private byte ACK_CALIBRATION = 0;
+
+    private final GBDeviceEventVersionInfo versionInfo = new GBDeviceEventVersionInfo();
+    private final GBDeviceEventBatteryInfo batteryInfo = new GBDeviceEventBatteryInfo();
+
+    private static final Logger LOG = LoggerFactory.getLogger(WatchXPlusDeviceSupport.class);
+
+    private final BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String broadcastAction = intent.getAction();
+            assert broadcastAction != null;
+            switch (broadcastAction) {
+                case WatchXPlusConstants.ACTION_CALIBRATION:
+                    enableCalibration(intent.getBooleanExtra(WatchXPlusConstants.ACTION_ENABLE, false));
+                    break;
+                case WatchXPlusConstants.ACTION_CALIBRATION_SEND:
+                    int hour = intent.getIntExtra(WatchXPlusConstants.VALUE_CALIBRATION_HOUR, -1);
+                    int minute = intent.getIntExtra(WatchXPlusConstants.VALUE_CALIBRATION_MINUTE, -1);
+                    int second = intent.getIntExtra(WatchXPlusConstants.VALUE_CALIBRATION_SECOND, -1);
+                    if (hour != -1 && minute != -1 && second != -1) {
+                        sendCalibrationData(hour, minute, second);
+                    }
+                    break;
+                case WatchXPlusConstants.ACTION_CALIBRATION_HOLD:
+                    holdCalibration();
+                    break;
+            }
+        }
+    };
+
+    public WatchXPlusDeviceSupport() {
+        super(LOG);
+        addSupportedService(GattService.UUID_SERVICE_GENERIC_ACCESS);
+        addSupportedService(GattService.UUID_SERVICE_GENERIC_ATTRIBUTE);
+        addSupportedService(WatchXPlusConstants.UUID_SERVICE_WATCHXPLUS);
+
+        LocalBroadcastManager broadcastManager = LocalBroadcastManager.getInstance(getContext());
+        IntentFilter intentFilter = new IntentFilter();
+        intentFilter.addAction(WatchXPlusConstants.ACTION_CALIBRATION);
+        intentFilter.addAction(WatchXPlusConstants.ACTION_CALIBRATION_SEND);
+        intentFilter.addAction(WatchXPlusConstants.ACTION_CALIBRATION_HOLD);
+        broadcastManager.registerReceiver(broadcastReceiver, intentFilter);
+    }
+
+    @Override
+    protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
+        try {
+            boolean auth = needsAuth;
+            needsAuth = false;
+            new InitOperation(auth, this, builder).perform();
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        return builder;
+    }
+
+    @Override
+    public boolean connectFirstTime() {
+        needsAuth = true;
+        return super.connect();
+    }
+
+    @Override
+    public boolean useAutoConnect() {
+        return true;
+    }
+
+    @Override
+    public void onNotification(NotificationSpec notificationSpec) {
+        String senderOrTitle = StringUtils.getFirstOf(notificationSpec.sender, notificationSpec.title);
+
+        String message = StringUtils.truncate(senderOrTitle, 14) + "\0";
+        if (notificationSpec.subject != null) {
+            message += StringUtils.truncate(notificationSpec.subject, 20) + ": ";
+        }
+        if (notificationSpec.body != null) {
+            message += StringUtils.truncate(notificationSpec.body, 64);
+        }
+
+        sendNotification(WatchXPlusConstants.NOTIFICATION_CHANNEL_DEFAULT, message);
+    }
+
+    /** Cancel notification
+     * cancel watch notification - stop vibration and turn off screen
+     * on watch - clear phone icon near bluetooth
+    */
+    private void cancelNotification() {
+        try {
+            if (getQueue() == null) {
+                LOG.warn("Unable to cancel notification, queue is null");
+                return;
+            }
+            getQueue().clear();
+            TransactionBuilder builder = performInitialized("cancelNotification");
+            int mPosition = 1024;   // all positions
+            int mMessageId = 0xFF;  // all messages
+            byte[] bArr = new byte[6];
+            bArr[0] = (byte) ((int) (mPosition >> 24));
+            bArr[1] = (byte) ((int) (mPosition >> 16));
+            bArr[2] = (byte) ((int) (mPosition >> 8));
+            bArr[3] = (byte) ((int) mPosition);
+            bArr[4] = (byte) (mMessageId >> 8);
+            bArr[5] = (byte) mMessageId;
+            builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE),
+                    buildCommand(WatchXPlusConstants.CMD_NOTIFICATION_CANCEL,
+                            WatchXPlusConstants.WRITE_VALUE,
+                            bArr));
+            builder.queue(getQueue());
+        } catch (IOException e) {
+            LOG.warn(" Unable to cancel notification ", e);
+        }
+    }
+
+
+    private String transliterate(String inputText) {
+        String outText = "";
+        String returnText = "";
+        for (int messageIndex = 0; messageIndex < inputText.length(); messageIndex++) {
+            String checkLetter = inputText.substring(messageIndex, messageIndex + 1);
+            returnText = checkLetter;
+            switch (checkLetter) {
+                case "邪":
+                    returnText = "袗";
+                    break;
+                case "斜":
+                    returnText = "袘";
+                    break;
+                case "胁":
+                    returnText = "袙";
+                    break;
+                case "谐":
+                    returnText = "袚";
+                    break;
+                case "写":
+                    returnText = "袛";
+                    break;
+                case "械":
+                    returnText = "袝";
+                    break;
+                case "卸":
+                    returnText = "袞";
+                    break;
+                case "蟹":
+                    returnText = "袟";
+                    break;
+                case "懈":
+                    returnText = "袠";
+                    break;
+                case "泄":
+                    returnText = "袡";
+                    break;
+                case "泻":
+                    returnText = "袣";
+                    break;
+                case "谢":
+                    returnText = "袥";
+                    break;
+                case "屑":
+                    returnText = "袦";
+                    break;
+                case "薪":
+                    returnText = "袧";
+                    break;
+                case "芯":
+                    returnText = "袨";
+                    break;
+                case "锌":
+                    returnText = "袩";
+                    break;
+                case "袪":
+                    returnText = "褉";
+                    break;
+                case "小":
+                    returnText = "褋";
+                    break;
+                case "孝":
+                    returnText = "褌";
+                    break;
+                case "校":
+                    returnText = "褍";
+                    break;
+                case "肖":
+                    returnText = "褎";
+                    break;
+                case "啸":
+                    returnText = "褏";
+                    break;
+                case "笑":
+                    returnText = "褑";
+                    break;
+                case "效":
+                    returnText = "褔";
+                    break;
+                case "楔":
+                    returnText = "褕";
+                    break;
+                case "些":
+                    returnText = "褖";
+                    break;
+                case "歇":
+                    returnText = "褗";
+                    break;
+                case "鞋":
+                    returnText = "褜";
+                    break;
+                case "挟":
+                    returnText = "褞";
+                    break;
+                case "携":
+                    returnText = "褟";
+                    break;
+                case "蝎":
+                    returnText = "褘";
+                    break;
+                case "协":
+                    returnText = "褝";
+                    break;
+            }
+            outText = outText + returnText;
+        }
+        return outText;
+    }
+
+    /** Format text and send it to watch
+     * @param notificationChannel - text or call
+     * @param notificationText - text to show
+     */
+    private void sendNotification(int notificationChannel, String notificationText) {
+        try {
+            TransactionBuilder builder = performInitialized("showNotification");
+            byte[] command = WatchXPlusConstants.CMD_NOTIFICATION_TEXT_TASK;
+
+            //byte[] text = notificationText.getBytes(StandardCharsets.UTF_8);
+            byte[] text = transliterate(notificationText).getBytes(StandardCharsets.UTF_8);
+
+            byte[] messagePart;
+
+            int messageLength = text.length;
+            int parts = messageLength / 9;
+            int remainder = messageLength % 9;
+//            Increment parts quantity if message length is not multiple of 9
+            if (remainder != 0) {
+                parts++;
+            }
+            for (int messageIndex = 0; messageIndex < parts; messageIndex++) {
+                if (messageIndex + 1 != parts || remainder == 0) {
+                    messagePart = new byte[11];
+                } else {
+                    messagePart = new byte[remainder + 2];
+                }
+
+                System.arraycopy(text, messageIndex * 9, messagePart, 2, messagePart.length - 2);
+
+                if (messageIndex + 1 == parts) {
+                    messageIndex = 0xFF;
+                }
+                messagePart[0] = (byte) notificationChannel;
+                messagePart[1] = (byte) messageIndex;
+                builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE),
+                    buildCommand(command,
+                          WatchXPlusConstants.KEEP_ALIVE,
+                          messagePart));
+            }
+            builder.queue(getQueue());
+        } catch (IOException e) {
+            LOG.warn(" Unable to send notification ", e);
+        }
+    }
+
+    /** enable notification channels on watch
+     * @param builder
+     * enable all notification channels
+     * TODO add settings to choose notification channels
+     */
+    private WatchXPlusDeviceSupport enableNotificationChannels(TransactionBuilder builder) {
+        builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE),
+                buildCommand(WatchXPlusConstants.CMD_NOTIFICATION_SETTINGS,
+                        WatchXPlusConstants.WRITE_VALUE,
+                        new byte[]{(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF}));
+
+        return this;
+    }
+
+    public void authorizationRequest(TransactionBuilder builder, boolean firstConnect) {
+        builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE),
+                buildCommand(WatchXPlusConstants.CMD_AUTHORIZATION_TASK,
+                        WatchXPlusConstants.TASK,
+                        new byte[]{(byte) (firstConnect ? 0x00 : 0x01)})); //possibly not the correct meaning
+
+    }
+
+    private void enableCalibration(boolean enable) {
+        try {
+            TransactionBuilder builder = performInitialized("enableCalibration");
+            builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE),
+                    buildCommand(WatchXPlusConstants.CMD_CALIBRATION_INIT_TASK,
+                            WatchXPlusConstants.TASK,
+                            new byte[]{(byte) (enable ? 0x01 : 0x00)}));
+            performImmediately(builder);
+        } catch (IOException e) {
+            LOG.warn(" Unable to start/stop calibration mode ", e);
+        }
+    }
+
+    private void holdCalibration() {
+        try {
+            TransactionBuilder builder = performInitialized("holdCalibration");
+            builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE),
+                    buildCommand(WatchXPlusConstants.CMD_CALIBRATION_KEEP_ALIVE,
+                            WatchXPlusConstants.KEEP_ALIVE));
+            performImmediately(builder);
+        } catch (IOException e) {
+            LOG.warn(" Unable to keep calibration mode alive ", e);
+        }
+    }
+
+    private void sendCalibrationData(@IntRange(from = 0, to = 23) int hour, @IntRange(from = 0, to = 59) int minute, @IntRange(from = 0, to = 59) int second) {
+        try {
+            isCalibrationActive = true;
+            TransactionBuilder builder = performInitialized("calibrate");
+            int handsPosition = ((hour % 12) * 60 + minute) * 60 + second;
+            builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE),
+                    buildCommand(WatchXPlusConstants.CMD_CALIBRATION_TASK,
+                            WatchXPlusConstants.TASK,
+                            Conversion.toByteArr16(handsPosition)));
+            performImmediately(builder);
+        } catch (IOException e) {
+            isCalibrationActive = false;
+            LOG.warn(" Unable to send calibration data ", e);
+        }
+    }
+
+    private WatchXPlusDeviceSupport checkInitTime(TransactionBuilder builder) {
+        LOG.info(" Check init time ");
+        builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE),
+                buildCommand(WatchXPlusConstants.CMD_TIME_SETTINGS,
+                        WatchXPlusConstants.READ_VALUE));
+        return this;
+    }
+
+    private void getTime() {
+        try {
+            TransactionBuilder builder = performInitialized("getTime");
+            builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE),
+                    buildCommand(WatchXPlusConstants.CMD_TIME_SETTINGS,
+                            WatchXPlusConstants.READ_VALUE));
+            builder.queue(getQueue());
+        } catch (IOException e) {
+            LOG.warn(" Unable to get device time ", e);
+        }
+    }
+
+    private void handleTime(byte[] time) {
+        GregorianCalendar now = BLETypeConversions.createCalendar();
+        GregorianCalendar nowDevice = BLETypeConversions.createCalendar();
+        int year = (nowDevice.get(Calendar.YEAR) / 100) * 100 + BcdUtil.fromBcd8(time[8]);
+        nowDevice.set(year,
+                BcdUtil.fromBcd8(time[9]) - 1,
+                BcdUtil.fromBcd8(time[10]),
+                BcdUtil.fromBcd8(time[11]),
+                BcdUtil.fromBcd8(time[12]),
+                BcdUtil.fromBcd8(time[13]));
+        nowDevice.set(Calendar.DAY_OF_WEEK, BcdUtil.fromBcd8(time[16]) + 1);
+
+        long timeDiff = (Math.abs(now.getTimeInMillis() - nowDevice.getTimeInMillis())) / 1000;
+
+        LOG.info(" Time diff: " + timeDiff);
+        if (10 < timeDiff && timeDiff < 120) {
+            LOG.info(" Auto set time ");
+            enableCalibration(true);
+            setTime(BLETypeConversions.createCalendar());
+            enableCalibration(false);
+        } else if (timeDiff > 120) {
+            LOG.info(" Time diff is too big ");
+            GB.toast("Manual time calibration needed!", Toast.LENGTH_LONG, GB.WARN);
+            sendNotification(WatchXPlusConstants.NOTIFICATION_CHANNEL_DEFAULT, "Calibrate time");
+            boolean forceTime = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getBoolean(WatchXPlusConstants.PREF_FORCE_TIME, false);
+            if (forceTime) {
+                LOG.info(" Force set time ");
+                enableCalibration(true);
+                setTime(BLETypeConversions.createCalendar());
+                enableCalibration(false);
+                GB.toast("Check analog time!", Toast.LENGTH_LONG, GB.WARN);
+                sendNotification(WatchXPlusConstants.NOTIFICATION_CHANNEL_DEFAULT, "Check analog time");
+            }
+        }
+    }
+
+    // set only digital time
+    private void setTime(Calendar calendar) {
+        try {
+            TransactionBuilder builder = performInitialized("setTime");
+            int timezoneOffsetMinutes = (calendar.get(Calendar.ZONE_OFFSET) + calendar.get(Calendar.DST_OFFSET)) / (60 * 1000);
+            int timezoneOffsetIndustrialMinutes = Math.round((Math.abs(timezoneOffsetMinutes) % 60) * 100f / 60f);
+            byte[] time = new byte[]{BcdUtil.toBcd8(calendar.get(Calendar.YEAR) % 100),
+                    BcdUtil.toBcd8(calendar.get(Calendar.MONTH) + 1),
+                    BcdUtil.toBcd8(calendar.get(Calendar.DAY_OF_MONTH)),
+                    BcdUtil.toBcd8(calendar.get(Calendar.HOUR_OF_DAY)),
+                    BcdUtil.toBcd8(calendar.get(Calendar.MINUTE)),
+                    BcdUtil.toBcd8(calendar.get(Calendar.SECOND)),
+                    (byte) (timezoneOffsetMinutes / 60),
+                    (byte) timezoneOffsetIndustrialMinutes,
+                    (byte) (calendar.get(Calendar.DAY_OF_WEEK) - 1)
+            };
+            builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE),
+                    buildCommand(WatchXPlusConstants.CMD_TIME_SETTINGS,
+                            WatchXPlusConstants.WRITE_VALUE,
+                            time));
+            builder.queue(getQueue());
+        } catch (IOException e) {
+            LOG.warn(" Unable to set time ", e);
+        }
+    }
+
+    /** send command to request watch firmware version
+     * @param builder - transaction builder
+     */
+    private WatchXPlusDeviceSupport getFirmwareVersion(TransactionBuilder builder) {
+        builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE),
+                buildCommand(WatchXPlusConstants.CMD_FIRMWARE_INFO,
+                        WatchXPlusConstants.READ_VALUE));
+
+        return this;
+    }
+
+    /** send command to request watch battery state
+     * @param builder - transaction builder
+     */
+    private WatchXPlusDeviceSupport getBatteryState(TransactionBuilder builder) {
+        builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE),
+                buildCommand(WatchXPlusConstants.CMD_BATTERY_INFO,
+                        WatchXPlusConstants.READ_VALUE));
+
+        return this;
+    }
+
+    /** initialize device on connect
+     * @param builder - transaction builder
+     */
+    public WatchXPlusDeviceSupport initialize(TransactionBuilder builder) {
+        getFirmwareVersion(builder)
+                .getBatteryState(builder)
+                .enableNotificationChannels(builder)
+                .setFitnessGoal(builder)                        // set steps per day
+                .getBloodPressureCalibrationStatus(builder)     // request blood pressure calibration
+                //.setUnitsSettings()                             // set metric/imperial units
+                .checkInitTime(builder)
+                .syncPreferences(builder);                      // read preferences from app and set them to watch
+        builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZED, getContext()));
+        builder.setCallback(this);
+        return this;
+    }
+
+    @Override
+    public void onDeleteNotification(int id) {
+        isMissedCall = false;
+        cancelNotification();
+    }
+
+    @Override
+    public void onSetTime() {
+        LOG.info(" Get time ");
+        getTime();
+    }
+
+    @Override
+    public void onSetAlarms(ArrayList<? extends Alarm> alarms) {
+        try {
+            TransactionBuilder builder = performInitialized("setAlarms");
+            for (Alarm alarm : alarms) {
+                setAlarm(alarm, alarm.getPosition() + 1, builder);
+            }
+            builder.queue(getQueue());
+        } catch (IOException e) {
+            LOG.warn(" Unable to set alarms ", e);
+        }
+    }
+
+    // No useful use case at the moment, used to clear alarm slots for testing.
+    private void deleteAlarm(TransactionBuilder builder, int index) {
+        if (0 < index && index < 4) {
+            byte[] alarmValue = new byte[]{(byte) index, 0x00, 0x00, 0x00, 0x00, 0x00};
+            builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE),
+                    buildCommand(WatchXPlusConstants.CMD_ALARM_SETTINGS,
+                            WatchXPlusConstants.WRITE_VALUE,
+                            alarmValue));
+        }
+    }
+
+    private void setAlarm(Alarm alarm, int index, TransactionBuilder builder) {
+        // Shift the GB internal repetition mask to match the device specific one.
+        byte repetitionMask = (byte) ((alarm.getRepetition() << 1) | (alarm.isRepetitive() ? 0x80 : 0x00));
+        repetitionMask |= (alarm.getRepetition(Alarm.ALARM_SUN) ? 0x01 : 0x00);
+        if (0 < index && index < 4) {
+            byte[] alarmValue = new byte[]{(byte) index,
+                    BcdUtil.toBcd8(AlarmUtils.toCalendar(alarm).get(Calendar.HOUR_OF_DAY)),
+                    BcdUtil.toBcd8(AlarmUtils.toCalendar(alarm).get(Calendar.MINUTE)),
+                    repetitionMask,
+                    (byte) (alarm.getEnabled() ? 0x01 : 0x00),
+                    0x00 // TODO: Unknown
+            };
+            builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE),
+                    buildCommand(WatchXPlusConstants.CMD_ALARM_SETTINGS,
+                            WatchXPlusConstants.WRITE_VALUE,
+                            alarmValue));
+        }
+    }
+
+    private boolean isRinging = false;  // store ringing state
+    private boolean isMissedCall = false;    // missed call state
+    private int remainingRepeats = 0;   // initialize call notification reminds
+    private int remainingMissedRepeats = 0;   // initialize missed call notification reminds
+
+    /** send notification on watch when phone rings
+     * @param callSpec - phone state
+     * send notification on incoming call, cancel notification when call is answered, ignored or rejected
+     * send missed call notification (if enabled from settings) when phone state changed from ringing to end call
+     * TODO add missed call reminder (send notification to watch at desired period)
+     */
+    // variables to handle ring notifications
+
+    @Override
+    public void onSetCallState(final CallSpec callSpec) {
+        final int repeatDelay = 5000;       // repeat delay of 5 sec (watch show call notifications for about 5 sec.)
+        final int repeatMissedDelay = 60000;       // repeat missed call delay of 60 sec
+        // set settings for missed call
+        //int repeatCount = WatchXPlusDeviceCoordinator.getRepeatOnCall();
+
+        final boolean continuousRing = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getBoolean(WatchXPlusConstants.PREF_CONTINIOUS_RING, false);
+        int repeatCount = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getInt(WatchXPlusConstants.PREF_REPEAT_RING, 0);
+        final boolean enableMissedCall = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getBoolean(WatchXPlusConstants.PREF_MISSED_CALL_ENABLE, false);
+        int repeatCountMissed = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getInt(WatchXPlusConstants.PREF_MISSED_CALL_REPEAT, 0);
+
+        switch (callSpec.command) {
+            case CallSpec.CALL_INCOMING:
+                isRinging = true;
+                remainingRepeats = repeatCount;
+                LOG.info(" Incoming call ");
+                if (("Phone".equals(callSpec.name)) || (callSpec.name.contains("ropusn")) || (callSpec.name.contains("issed"))) {
+                    // do nothing for notifications without caller name, e.g. system call event
+                } else {
+                    // possible missed call
+                    isMissedCall = true;
+                    // send first notification
+                    sendNotification(WatchXPlusConstants.NOTIFICATION_CHANNEL_PHONE_CALL, callSpec.name);
+                    // init repeat handler
+                    final Handler handler = new Handler();
+                    handler.postDelayed(new Runnable() {
+                        public void run() {
+                            // Actions to do after repeatDelay seconds
+                            if (((isRinging) && (remainingRepeats > 0)) || ((isRinging) && (continuousRing))) {
+                                remainingRepeats = remainingRepeats - 1;
+                                sendNotification(WatchXPlusConstants.NOTIFICATION_CHANNEL_PHONE_CALL, callSpec.name);
+                                // re-run handler
+                                handler.postDelayed(this, repeatDelay);
+                            } else {
+                                remainingRepeats = 0;
+                                // stop handler
+                                handler.removeCallbacks(this);
+                                cancelNotification();
+                            }
+                        }
+                    }, repeatDelay);
+                }
+                break;
+            case CallSpec.CALL_START:
+                isRinging = false;
+                isMissedCall = false;
+                cancelNotification();
+                LOG.info(" Call start ");
+                break;
+            case CallSpec.CALL_REJECT:
+                isRinging = false;
+                isMissedCall = false;
+                cancelNotification();
+                LOG.info(" Call reject ");
+                break;
+            case CallSpec.CALL_ACCEPT:
+                isRinging = false;
+                isMissedCall = false;
+                cancelNotification();
+                LOG.info(" Call accept ");
+                break;
+            case CallSpec.CALL_OUTGOING:
+                isRinging = false;
+                isMissedCall = false;
+                cancelNotification();
+                LOG.info(" Outgoing call ");
+                break;
+            case CallSpec.CALL_END:
+                LOG.info(" End call ");
+                isRinging = false;
+                // it's a missed call, don't clear notification to preserve small icon near bluetooth
+                if (isMissedCall) {
+                    remainingMissedRepeats = repeatCountMissed;
+                    // send missed call notification if enabled in settings
+                    if (enableMissedCall) {
+                        LOG.info(" Missed call reminder ");
+                        sendNotification(WatchXPlusConstants.NOTIFICATION_CHANNEL_PHONE_CALL, "Missed call");
+                        // repeat missed call notification
+                        final Handler handler = new Handler();
+                        handler.postDelayed(new Runnable() {
+                            public void run() {
+                                // Actions to do after repeatDelay seconds
+                                if ((isMissedCall) && (remainingMissedRepeats > 0)) {
+                                    remainingMissedRepeats = remainingMissedRepeats - 1;
+                                    sendNotification(WatchXPlusConstants.NOTIFICATION_CHANNEL_PHONE_CALL, "Missed call");
+                                    LOG.info(" Missed call reminder repeats to go: " + remainingMissedRepeats);
+                                    // re-run handler
+                                    handler.postDelayed(this, repeatMissedDelay);
+                                } else {
+                                    remainingMissedRepeats = 0;
+                                    LOG.info(" Missed call reminder repeats to go: " + remainingMissedRepeats);
+                                    isMissedCall = false;
+                                    // stop handler
+                                    handler.removeCallbacks(this);
+                                    cancelNotification();
+                                }
+                            }
+                        }, repeatMissedDelay);
+                    } else {
+                        remainingMissedRepeats = 0;
+                        isMissedCall = false;
+                        cancelNotification();
+                    }
+                } else {
+                    isRinging = false;
+                    isMissedCall = false;
+                    cancelNotification();
+                    LOG.info(" Outgoing call end ");
+                }
+                break;
+            default:
+                isRinging = false;
+                isMissedCall = false;
+                cancelNotification();
+                LOG.info(" Call default ");
+                break;
+        }
+    }
+
+    /** handle button press while ringing
+     */
+    private void handleButtonWhenRing() {
+        GBDeviceEventCallControl callCmd = new GBDeviceEventCallControl();
+        // get saved settings if true - reject call, otherwise ignore call
+        boolean buttonReject = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getBoolean(WatchXPlusConstants.PREF_BUTTON_REJECT, false);
+        if (buttonReject) {
+            LOG.info(" call rejected ");
+            isRinging = false;
+            remainingRepeats = 0;
+            isMissedCall = false;
+            callCmd.event = GBDeviceEventCallControl.Event.REJECT;
+            evaluateGBDeviceEvent(callCmd);
+            cancelNotification();
+        } else {
+            LOG.info(" call ignored ");
+            isRinging = false;
+            remainingRepeats = 0;
+            isMissedCall = false;
+            callCmd.event = GBDeviceEventCallControl.Event.IGNORE;
+            evaluateGBDeviceEvent(callCmd);
+            cancelNotification();
+        }
+    }
+
+    private WatchXPlusDeviceSupport setFitnessGoal(TransactionBuilder builder) {
+        int fitnessGoal = new ActivityUser().getStepsGoal();
+        builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE),
+                buildCommand(WatchXPlusConstants.CMD_FITNESS_GOAL_SETTINGS,
+                        WatchXPlusConstants.WRITE_VALUE,
+                        Conversion.toByteArr16(fitnessGoal)));
+        return this;
+    }
+
+    /** set personal info - read it from About me
+     * @param builder - transaction builder
+     * @param height - user height in meters
+     * @param weight - user weight in kg
+     * @param age    - user age
+     * @param gender - user age
+     */
+    private void setPersonalInformation(TransactionBuilder builder, int height, int weight, int age, int gender) {
+        LOG.info(" Setting Personal Information... height:"+height+" weight:"+weight+" age:"+age+" gender:"+gender);
+        byte[] command = WatchXPlusConstants.CMD_SET_PERSONAL_INFO;
+
+        byte[] bArr = new byte[4];
+        bArr[0] = (byte) height;        // byte[08]
+        bArr[1] = (byte) weight;        // byte[09]
+        bArr[2] = (byte) age;           // byte[10]
+        bArr[3] = (byte) gender;        // byte[11]
+
+        builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE),
+                buildCommand(command,
+                        WatchXPlusConstants.WRITE_VALUE,
+                        bArr));
+    }
+
+    /** handle get/set personal info
+     * @param value - reply from watch
+     * actual do nothing (for test purposes only)
+     */
+    private void handlePersonalInfo(byte[] value) {
+        int height = Conversion.fromByteArr16(value[8]);
+        int weight = Conversion.fromByteArr16(value[9]);
+        int age = Conversion.fromByteArr16(value[10]);
+        int gender = Conversion.fromByteArr16(value[11]);
+        LOG.info(" Personal info - height:" + height + ", weight:" + weight + ", age:" + age + ", gender:" + gender);
+    }
+
+    @Override
+    public void onFetchRecordedData(int dataTypes) {
+        TransactionBuilder builder;
+        // get battery state
+        try {
+            builder = performInitialized("getBatteryInfo");
+            builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE),
+                buildCommand(WatchXPlusConstants.CMD_BATTERY_INFO,
+                        WatchXPlusConstants.READ_VALUE));
+            builder.queue(getQueue());
+        } catch (IOException e) {
+            LOG.warn(" Unable to retrieve battery data ", e);
+        }
+
+        try {
+            builder = performInitialized("fetchData");
+
+            builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE),
+                    buildCommand(WatchXPlusConstants.CMD_DAY_STEPS_INFO,
+                            WatchXPlusConstants.READ_VALUE));
+
+//            Fetch heart rate data samples count
+            requestDataCount(DataType.HEART_RATE);
+
+            builder.queue(getQueue());
+        } catch (IOException e) {
+            LOG.warn(" Unable to retrieve recorded data ", e);
+        }
+    }
+
+    @Override
+    public void onReset(int flags) {
+       // testNewCommands();
+    }
+
+    @Override
+    public void onHeartRateTest() {
+        //requestHeartRateMeasurement();
+    }
+
+    @Override
+    public void onScreenshotReq() {
+        sendBloodPressureCalibration();
+    }
+
+    @Override
+    public void onSendConfiguration(String config) {
+        TransactionBuilder builder;
+        SharedPreferences sharedPreferences = GBApplication.getDeviceSpecificSharedPrefs(this.getDevice().getAddress());
+        LOG.info(" onSendConfiguration: " + config);
+        try {
+            builder = performInitialized("sendConfig: " + config);
+            switch (config) {
+                // settings from App Settings
+                case SettingsActivity.PREF_MEASUREMENT_SYSTEM:
+                    setUnitsSettings();
+                    break;
+                case ActivityUser.PREF_USER_STEPS_GOAL:
+                    setFitnessGoal(builder);
+                    break;
+                // settings from App Settings -> WatchXPlus settings
+                case DeviceSettingsPreferenceConst.PREF_POWER_MODE:
+                    setPowerMode();
+                    break;
+                case DeviceSettingsPreferenceConst.PREF_LANGUAGE:
+                    setLanguageAndTimeFormat(builder);
+                    break;
+
+                case DeviceSettingsPreferenceConst.PREF_INACTIVITY_THRESHOLD:
+                case DeviceSettingsPreferenceConst.PREF_INACTIVITY_ENABLE:
+                    setLongSitHours(builder);
+                    break;
+                // calibrations
+                case DeviceSettingsPreferenceConst.PREF_ALTITUDE_CALIBRATE:
+                    setAltitude(builder);
+                    break;
+                case DeviceSettingsPreferenceConst.PREF_BUTTON_BP_CALIBRATE:
+                    sendBloodPressureCalibration();
+                    break;
+                // settings from device card
+                case DeviceSettingsPreferenceConst.PREF_LIFTWRIST_NOSHED:
+                    setHeadsUpScreen(builder);
+                    getShakeStatus(builder);
+                    break;
+                case DeviceSettingsPreferenceConst.PREF_DISCONNECTNOTIF_NOSHED:
+                    setDisconnectReminder(builder);
+                    break;
+                case DeviceSettingsPreferenceConst.PREF_TIMEFORMAT:
+                    setLanguageAndTimeFormat(builder);
+                    break;
+                case DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_NOAUTO:
+                    setDNDHours(builder);
+                    break;
+            }
+            builder.queue(getQueue());
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+
+    @Override
+    public void onReadConfiguration(String config) {
+        LOG.info(" onReadConfiguration : " + config);
+    }
+
+    @Override
+    public void onTestNewFunction() {
+        requestBloodPressureMeasurement();
+    }
+
+
+    /** set long sit reminder time
+     * @param builder - transaction builder
+     * @param enable        - state (true - enabled or false - disabled)
+     * @param hourStart     - begin hour
+     * @param minuteStart   - begin minute
+     * @param hourEnd       - end hour
+     * @param minuteEnd     - end minute
+     */
+    private void setLongSitHours(TransactionBuilder builder, boolean enable, int hourStart, int minuteStart, int hourEnd, int minuteEnd, int period) {
+        LOG.info(" Setting Long sit reminder... Enabled:"+enable+" Period:"+period);
+        LOG.info(" Setting Long sit time... Hs:"+hourStart+" Ms:"+minuteStart+" He:"+hourEnd+" Me:"+minuteEnd);
+        // set Long Sit reminder time
+        byte[] command = WatchXPlusConstants.CMD_INACTIVITY_REMINDER_SET;
+
+        byte[] bArr = new byte[10];
+        // do not remind
+        bArr[0] = (byte) hourEnd;            // byte[08]
+        bArr[1] = (byte) minuteEnd;          // byte[09]
+        bArr[2] = (byte) hourStart;          // byte[10]
+        bArr[3] = (byte) minuteStart;        // byte[11]
+        // remind
+        bArr[4] = (byte) hourStart;          // byte[12]
+        bArr[5] = (byte) minuteStart;        // byte[13]
+        bArr[6] = (byte) hourEnd;            // byte[14]
+        bArr[7] = (byte) minuteEnd;          // byte[15]
+        bArr[8] = (byte) (period >> 8);      // byte[16]
+        bArr[9] = (byte) period;             // byte[17]
+
+        builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE),
+                buildCommand(command, WatchXPlusConstants.WRITE_VALUE, bArr));
+        // set long sit reminder state (enabled, disabled)
+        setLongSitSwitch(builder, enable);
+    }
+
+    /** get Long sit settings from app, and send it to watch
+     * @param builder  - transaction builder
+     */
+    private void setLongSitHours(TransactionBuilder builder) {
+        Calendar start = new GregorianCalendar();
+        Calendar end = new GregorianCalendar();
+        boolean enable = WatchXPlusDeviceCoordinator.getLongSitHours(gbDevice.getAddress(), start, end);
+        if (enable) {
+            String periodString = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getString(DeviceSettingsPreferenceConst.PREF_INACTIVITY_THRESHOLD, "60");
+            int period = Integer.parseInt(periodString);
+
+            this.setLongSitHours(builder, enable,
+                    start.get(Calendar.HOUR_OF_DAY), start.get(Calendar.MINUTE),
+                    end.get(Calendar.HOUR_OF_DAY), end.get(Calendar.MINUTE),
+                    period);
+        } else {
+            // disable Long sit reminder
+            LOG.info(" Long sit reminder are disabled ");
+            this.setLongSitSwitch(builder, enable);
+        }
+    }
+
+    /** set long sit reminder switch
+     * @param tbuilder - transaction builder
+     * @param enable - true or false
+     */
+    private void setLongSitSwitch(TransactionBuilder tbuilder, boolean enable) {
+        LOG.info(" Setting Long sit reminder switch to: " + enable);
+        tbuilder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE),
+                buildCommand(WatchXPlusConstants.CMD_INACTIVITY_REMINDER_SWITCH,
+                        WatchXPlusConstants.WRITE_VALUE,
+                        new byte[]{(byte) (enable ? 0x01 : 0x00)}));
+    }
+
+
+
+    /** set do not disturb time
+     * @param builder - transaction builder
+     * @param enable        - state (true - enabled or false - disabled)
+     * @param hourStart     - begin hour
+     * @param minuteStart   - begin minute
+     * @param hourEnd       - end hour
+     * @param minuteEnd     - end minute
+     */
+    private void setDNDHours(TransactionBuilder builder, boolean enable, int hourStart, int minuteStart, int hourEnd, int minuteEnd) {
+         LOG.info(" Setting DND time... Hs:"+hourStart+" Ms:"+minuteStart+" He:"+hourEnd+" Me:"+minuteEnd);
+         // set DND time
+         byte[] command = WatchXPlusConstants.CMD_SET_DND_HOURS_TIME;
+
+         byte[] bArr = new byte[4];
+         bArr[0] = (byte) hourStart;        // byte[08]
+         bArr[1] = (byte) minuteStart;      // byte[09]
+         bArr[2] = (byte) hourEnd;          // byte[10]
+         bArr[3] = (byte) minuteEnd;        // byte[11]
+         builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE),
+              buildCommand(command, WatchXPlusConstants.WRITE_VALUE, bArr));
+         // set DND state (enabled, disabled)
+         setDNDHoursSwitch(builder, enable);
+    }
+
+    /** set do not disturb switch
+     * @param tbuilder - transaction builder
+     * @param enable - true or false
+     */
+    private void setDNDHoursSwitch(TransactionBuilder tbuilder, boolean enable) {
+            LOG.info(" Setting DND switch to: " + enable);
+            tbuilder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE),
+                buildCommand(WatchXPlusConstants.CMD_SET_DND_HOURS_SWITCH,
+                        WatchXPlusConstants.WRITE_VALUE,
+                        new byte[]{(byte) (enable ? 0x01 : 0x00)}));
+    }
+
+    /** get DND settings from app, and send it to watch
+     * @param builder - transaction builder
+     */
+    private void setDNDHours(TransactionBuilder builder) {
+        Calendar start = new GregorianCalendar();
+        Calendar end = new GregorianCalendar();
+        boolean enable = WatchXPlusDeviceCoordinator.getDNDHours(gbDevice.getAddress(), start, end);
+        if (enable) {
+            this.setDNDHours(builder, enable,
+                    start.get(Calendar.HOUR_OF_DAY), start.get(Calendar.MINUTE),
+                    end.get(Calendar.HOUR_OF_DAY), end.get(Calendar.MINUTE));
+        } else {
+            // disable DND
+            LOG.info(" Quiet hours are disabled ");
+            this.setDNDHoursSwitch(builder, enable);
+        }
+    }
+
+    /** set watch power
+     * switch watch power mode
+     * modes (0- normal, 1- energysaving, 2- only watch)
+     */
+    private void setPowerMode() {
+        byte setWatchPowerMode = 0x00;
+        String powermodeStr = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getString(DeviceSettingsPreferenceConst.PREF_POWER_MODE, "0");
+        if (powermodeStr.equals("0")) {
+            setWatchPowerMode = 0x00;
+        } else if (powermodeStr.equals("1")) {
+            setWatchPowerMode = 0x01;
+        } else if (powermodeStr.equals("2")) {
+            setWatchPowerMode = 0x02;
+        }
+
+        byte[] bArr = new byte[1];
+        bArr[0] = (byte) setWatchPowerMode;
+        LOG.info(" setting power mode to: " + setWatchPowerMode);
+        try {
+            TransactionBuilder builder = performInitialized("setWatchPowerMode");
+            builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE),
+                    buildCommand(WatchXPlusConstants.CMD_POWER_MODE,
+                            WatchXPlusConstants.TASK,
+                            bArr));
+            builder.queue(getQueue());
+        }   catch (IOException e) {
+            LOG.warn(" Unable to set power mode ", e);
+        }
+        //prefs.getPreferences().edit().putInt("PREF_POWER_MODE", 0).apply();
+    }
+
+     /** request watch units
+     * for testing purposes only
+     */
+    private WatchXPlusDeviceSupport getUnitsSettings() {
+        LOG.info(" Get units from watch... ");
+        try {
+            TransactionBuilder builder = performInitialized("getUnits");
+            builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE),
+                    buildCommand(WatchXPlusConstants.CMD_SET_UNITS,
+                            WatchXPlusConstants.READ_VALUE));
+            builder.queue(getQueue());
+        } catch (IOException e) {
+            LOG.warn(" Unable to get units ", e);
+        }
+        return this;
+    }
+
+    /**
+     * Set watch units
+     */
+    private void setUnitsSettings() {
+        int units = 0;
+        String unitsPref = GBApplication.getPrefs().getString(SettingsActivity.PREF_MEASUREMENT_SYSTEM, GBApplication.getContext().getString(R.string.p_unit_metric));
+
+        if (unitsPref.equals(GBApplication.getContext().getString(R.string.p_unit_imperial))) {
+            units = 1;
+            LOG.info(" Changed units: imperial ");
+        } else {
+            LOG.info(" Changed units: metric ");
+        }
+
+        byte[] bArr = new byte[3];
+        bArr[0] = (byte) units; // metric - 0/imperial - 1
+        bArr[1] = (byte) 0x00;  // time unit 12/24h (there is a separate command for this)
+        bArr[2] = (byte) 0x00;  // temperature unit (do nothing)
+        try {
+            TransactionBuilder builder = performInitialized("setUnits");
+            builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE),
+                    buildCommand(WatchXPlusConstants.CMD_SET_UNITS,
+                            WatchXPlusConstants.WRITE_VALUE,
+                            bArr));
+            builder.queue(getQueue());
+        }   catch (IOException e) {
+            LOG.warn(" Unable to set units ", e);
+        }
+    }
+
+    /** request status of blood pressure calibration
+     * @param builder - transaction builder
+     */
+    private WatchXPlusDeviceSupport getBloodPressureCalibrationStatus(TransactionBuilder builder) {
+        builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE),
+                buildCommand(WatchXPlusConstants.CMD_IS_BP_CALIBRATED,
+                        WatchXPlusConstants.READ_VALUE));
+
+        return this;
+    }
+
+    /** send blood pressure calibration to watch
+     * TODO add better error handling if blood pressure calibration is failed
+     */
+    private void sendBloodPressureCalibration() {
+        try {
+            String beginCalibration = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getString(DeviceSettingsPreferenceConst.PREF_BUTTON_BP_CALIBRATE, "0");
+            if (beginCalibration.equals("1")) {
+                LOG.info(" Calibrating BP - cancel " + beginCalibration);
+                return;
+            }
+            String mLowPString = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getString(WatchXPlusConstants.PREF_BP_CAL_LOW, "80");
+            int mLowP = Integer.parseInt(mLowPString);
+            String mHighPString = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getString(WatchXPlusConstants.PREF_BP_CAL_HIGH, "130");
+            int mHighP = Integer.parseInt(mHighPString);
+            LOG.warn(" Calibrating BP ... LowP=" + mLowP + " HighP="+mHighP);
+            GB.toast("Calibrating BP...", Toast.LENGTH_LONG, GB.INFO);
+           TransactionBuilder builder = performInitialized("bpCalibrate");
+            byte[] command = WatchXPlusConstants.CMD_BP_CALIBRATION;
+            byte mStart = 0x01; // initiate calibration
+
+            byte[] bArr = new byte[5];
+            bArr[0] = mStart;                      // byte[08]
+            bArr[1] = (byte) (mHighP >> 8);        // byte[09]
+            bArr[2] = (byte) mHighP;              // byte[10]
+            bArr[3] = (byte) (mLowP >> 8);        // byte[11]
+            bArr[4] = (byte) mLowP;               // byte[12]
+
+            builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE),
+                    buildCommand(command,
+                            WatchXPlusConstants.TASK,
+                            bArr));
+            builder.queue(getQueue());
+        } catch (IOException e) {
+            LOG.warn(" Unable to send BP Calibration ", e);
+        }
+    }
+
+    /** handle watch response if blood pressure is calibrated
+     * @param value - watch response
+     * save result to global variable (uses for BP measurement)
+     */
+    private void handleBloodPressureCalibrationStatus(byte[] value) {
+        WatchXPlusDeviceCoordinator.isBPCalibrated = Conversion.fromByteArr16(value[8]) == 0;
+    }
+
+    /** handle watch response for result of blood pressure calibration
+     * @param value - watch response
+     */
+    private void handleBloodPressureCalibrationResult(byte[] value) {
+        if (Conversion.fromByteArr16(value[8]) != 0x00) {
+            WatchXPlusDeviceCoordinator.isBPCalibrated = false;
+            GB.toast(" Calibrating BP fail ", Toast.LENGTH_LONG, GB.ERROR);
+        } else {
+            WatchXPlusDeviceCoordinator.isBPCalibrated = true;
+            int high = Conversion.fromByteArr16(value[9], value[10]);
+            int low = Conversion.fromByteArr16(value[11], value[12]);
+            GB.toast("OK. Measured Low:"+low+" high:"+high, Toast.LENGTH_LONG, GB.INFO);
+        }
+    }
+
+    /** request blood pressure measurement
+     * first check if blood pressure is calibrated
+     */
+    private void requestBloodPressureMeasurement() {
+        if (!WatchXPlusDeviceCoordinator.isBPCalibrated) {
+            LOG.info(" BP is NOT calibrated ");
+            GB.toast("BP is not calibrated", Toast.LENGTH_LONG, GB.WARN);
+            return;
+        }
+        try {
+            TransactionBuilder builder = performInitialized("bpMeasure");
+
+            byte[] command = WatchXPlusConstants.CMD_BLOOD_PRESSURE_MEASURE;
+
+            builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE),
+                    buildCommand(command,
+                            WatchXPlusConstants.TASK, new byte[]{0x01}));
+            builder.queue(getQueue());
+        } catch (IOException e) {
+            LOG.warn(" Unable to request BP Measure ", e);
+        }
+    }
+
+    @Override
+    public void onSendWeather(ArrayList<WeatherSpec> weatherSpecs) {
+        WeatherSpec weatherSpec = weatherSpecs.get(0);
+        try {
+            TransactionBuilder builder = performInitialized("setWeather");
+            int currentTemp;
+            int todayMinTemp;
+            int todayMaxTemp;
+            byte[] command = WatchXPlusConstants.CMD_WEATHER_SET;
+            byte[] weatherInfo = new byte[5];
+            int currentCondition = weatherSpec.currentConditionCode;
+// set weather icon
+            int currentConditionCode = 0; // 0 is sunny
+            switch (currentCondition) {
+//Group 2xx: Thunderstorm
+                case 200:  //thunderstorm with light rain:  //11d
+                case 201:  //thunderstorm with rain:  //11d
+                case 202:  //thunderstorm with heavy rain:  //11d
+                    currentConditionCode = 1024;
+                    break;
+                case 210:  //light thunderstorm::  //11d
+                case 211:  //thunderstorm:  //11d
+                case 212:  //heavy thunderstorm:  //11d
+                case 221:  //ragged thunderstorm:  //11d
+                case 230:  //thunderstorm with light drizzle:  //11d
+                case 231:  //thunderstorm with drizzle:  //11d
+                case 232:  //thunderstorm with heavy drizzle:  //11d
+                    currentConditionCode = 1025;
+                    break;
+//Group 3xx: Drizzle
+                case 300:  //light intensity drizzle:  //09d
+                case 301:  //drizzle:  //09d
+                case 302:  //heavy intensity drizzle:  //09d
+                case 310:  //light intensity drizzle rain:  //09d
+                case 500:  //light rain:  //10d
+                    currentConditionCode = 256;
+                    break;
+                case 311:  //drizzle rain:  //09d
+                case 312:  //heavy intensity drizzle rain:  //09d
+                case 313:  //shower rain and drizzle:  //09d
+                case 314:  //heavy shower rain and drizzle:  //09d
+                case 321:  //shower drizzle:  //09d
+                case 501:  //moderate rain:  //10d
+                    currentConditionCode = 1280;
+                    break;
+//Group 5xx: Rain
+                case 511:  //freezing rain:  //13d
+                case 520:  //light intensity shower rain:  //09d
+                case 521:  //shower rain:  //09d
+                case 502:  //heavy intensity rain:  //10d
+                case 503:  //very heavy rain:  //10d
+                case 504:  //extreme rain:  //10d
+                case 522:  //heavy intensity shower rain:  //09d
+                case 531:  //ragged shower rain:  //09d
+                    currentConditionCode = 258;
+                    break;
+//Group 6xx: Snow
+                case 600:  //light snow:
+                case 601:  //snow:  //[[file:13d.png]]
+                    currentConditionCode = 513;
+                    break;
+                case 620:  //light shower snow:  //[[file:13d.png]]
+                    currentConditionCode = 514;
+                    break;
+                case 602:  //heavy snow:  //[[file:13d.png]]
+                case 621:  //shower snow:  //[[file:13d.png]]
+                case 622:  //heavy shower snow:  //[[file:13d.png]]
+                    currentConditionCode = 515;
+                    break;
+                case 611:  //sleet:  //[[file:13d.png]]
+                case 612:  //shower sleet:  //[[file:13d.png]]
+                    currentConditionCode = 1026;
+                    break;
+                case 615:  //light rain and snow:  //[[file:13d.png]]
+                case 616:  //rain and snow:  //[[file:13d.png]]
+                    currentConditionCode = 4;
+                    break;
+//Group 7xx: Atmosphere
+                case 741:  //fog:  //[[file:50d.png]]
+                case 701:  //mist:  //[[file:50d.png]]
+                case 711:  //smoke:  //[[file:50d.png]]
+                    currentConditionCode = 5;
+                    break;
+                case 721:  //haze:  //[[file:50d.png]]
+                    currentConditionCode = 3;
+                    break;
+                case 731:  //sandcase  dust whirls:  //[[file:50d.png]]
+                    currentConditionCode = 771;
+                    break;
+                case 751:  //sand:  //[[file:50d.png]]
+                case 761:  //dust:  //[[file:50d.png]]
+                case 762:  //volcanic ash:  //[[file:50d.png]]
+                case 771:  //squalls:  //[[file:50d.png]]
+                    currentConditionCode = 769;
+                    break;
+                case 781:  //tornado:  //[[file:50d.png]]
+                case 900:  //tornado
+                    currentConditionCode = 1283;
+                    break;
+//Group 800: Clear
+                case 800:  //clear sky
+                    currentConditionCode = 0;
+                    break;
+//Group 80x: Clouds
+                case 801:  //few clouds:  //[[file:02d.png]] [[file:02n.png]]
+                case 802:  //scattered clouds:  //[[file:03d.png]] [[file:03d.png]]
+                case 803:  //broken clouds:  //[[file:04d.png]] [[file:03d.png]]
+                    currentConditionCode = 1;
+                    break;
+                case 804:  //overcast clouds:  //[[file:04d.png]] [[file:04d.png]]
+                    currentConditionCode = 2;
+                    break;
+//Group 90x: Extreme
+                case 901:  //tropical storm
+                case 903:  //cold
+                case 904:  //hot
+                case 905:  //windy
+                case 906:  //hail
+                    currentConditionCode = 1027;
+                    break;
+//Group 9xx: Additional
+                case 951:  //calm
+                case 952:  //light breeze
+                case 953:  //gentle breeze
+                case 954:  //moderate breeze
+                case 955:  //fresh breeze
+                case 956:  //strong breeze
+                case 957:  //high windcase  near gale
+                case 958:  //gale
+                case 959:  //severe gale
+                case 960:  //storm
+                case 961:  //violent storm
+                case 902:  //hurricane
+                case 962:  //hurricane
+                    currentConditionCode = 261;
+                    break;
+            }
+            LOG.info( " Weather cond: " + currentCondition + " icon: " + currentConditionCode);
+// calculate for temps under 0
+            currentTemp = (Math.abs(weatherSpec.currentTemp)) - 273;
+            if (currentTemp < 0) {
+                currentTemp = (Math.abs(currentTemp) ^ 255) + 1;
+            }
+            todayMinTemp = (Math.abs(weatherSpec.todayMinTemp)) - 273;
+            if (todayMinTemp < 0) {
+                todayMinTemp = (Math.abs(todayMinTemp) ^ 255) + 1;
+            }
+            todayMaxTemp = (Math.abs(weatherSpec.todayMaxTemp)) - 273;
+            if (todayMaxTemp < 0) {
+                todayMaxTemp = (Math.abs(todayMaxTemp) ^ 255) + 1;
+            }
+            LOG.info(" Set weather min: " + todayMinTemp + " max: " + todayMaxTemp + " current: " + currentTemp + " icon: " + currentCondition);
+//            First two bytes are controlling the icon
+            weatherInfo[0] = (byte )(currentConditionCode >> 8);
+            weatherInfo[1] = (byte )currentConditionCode;
+            weatherInfo[2] = (byte) todayMinTemp;
+            weatherInfo[3] = (byte) todayMaxTemp;
+            weatherInfo[4] = (byte) currentTemp;
+            builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE),
+                    buildCommand(command,
+                            WatchXPlusConstants.KEEP_ALIVE,
+                            weatherInfo));
+            builder.queue(getQueue());
+        } catch (IOException e) {
+            LOG.warn(" Unable to set weather ", e);
+        }
+    }
+
+    @Override
+    public boolean onCharacteristicChanged(BluetoothGatt gatt,
+                                           BluetoothGattCharacteristic characteristic) {
+        super.onCharacteristicChanged(gatt, characteristic);
+
+        UUID characteristicUUID = characteristic.getUuid();
+        byte[] value = characteristic.getValue();
+        if (WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE.equals(characteristicUUID)) {
+            if (ArrayUtils.equals(value, WatchXPlusConstants.RESP_FIRMWARE_INFO, 5)) {
+                handleFirmwareInfo(value);
+            } else if (ArrayUtils.equals(value, WatchXPlusConstants.RESP_SHAKE_SWITCH, 5)) {
+                handleShakeState(value);
+            } else if (ArrayUtils.equals(value, WatchXPlusConstants.RESP_SET_PERSONAL_INFO, 5)) {
+                handlePersonalInfo(value);
+            } else if (ArrayUtils.equals(value, WatchXPlusConstants.RESP_BUTTON_WHILE_RING, 5)) {
+                handleButtonWhenRing();
+            } else if (ArrayUtils.equals(value, WatchXPlusConstants.RESP_DISCONNECT_REMIND, 5)) {
+                handleDisconnectReminderState(value);
+            } else if (ArrayUtils.equals(value, WatchXPlusConstants.RESP_BATTERY_INFO, 5)) {
+                handleBatteryState(value);
+            } else if (ArrayUtils.equals(value, WatchXPlusConstants.RESP_GOAL_AIM_STATUS, 5)) {
+                handleSportAimStatus(value);
+            } else if (ArrayUtils.equals(value, WatchXPlusConstants.RESP_TIME_SETTINGS, 5)) {
+                handleTime(value);
+            } else if (ArrayUtils.equals(value, WatchXPlusConstants.RESP_IS_BP_CALIBRATED, 5)) {
+                handleBloodPressureCalibrationStatus(value);
+            } else if (ArrayUtils.equals(value, WatchXPlusConstants.RESP_BP_CALIBRATION, 5)) {
+                handleBloodPressureCalibrationResult(value);
+            } else if (ArrayUtils.equals(value, WatchXPlusConstants.RESP_BUTTON_INDICATOR, 5)) {
+                this.onReverseFindDevice(true);
+//                It looks like WatchXPlus doesn't send this action
+//                WRONG: WatchXPlus send this on find phone
+                LOG.info(" Unhandled action: Button pressed ");
+            } else if (ArrayUtils.equals(value, WatchXPlusConstants.RESP_ALARM_INDICATOR, 5)) {
+                LOG.info(" Alarm active: id=" + value[8]);
+            } else if (isCalibrationActive && value.length == 7 && value[4] == ACK_CALIBRATION) {
+                setTime(BLETypeConversions.createCalendar());
+                isCalibrationActive = false;
+            } else if (ArrayUtils.equals(value, WatchXPlusConstants.RESP_DAY_STEPS_INDICATOR, 5)) {
+                handleStepsInfo(value);
+            } else if (ArrayUtils.equals(value, WatchXPlusConstants.RESP_DATA_COUNT, 5)) {
+                LOG.info(" Received data count: " + value);
+                handleDataCount(value);
+            } else if (ArrayUtils.equals(value, WatchXPlusConstants.RESP_DATA_DETAILS, 5)) {
+                LOG.info(" Received data details: " + value);
+                handleDataDetails(value);
+            } else if (ArrayUtils.equals(value, WatchXPlusConstants.RESP_DATA_CONTENT, 5)) {
+                LOG.info(" Received data content: " + value);
+                handleDataContentAck(value);
+            } else if (ArrayUtils.equals(value, WatchXPlusConstants.RESP_BP_MEASURE_STARTED, 5)) {
+                handleBpMeasureResult(value);
+            } else if (ArrayUtils.equals(value, WatchXPlusConstants.RESP_DATA_CONTENT_REMOVE, 5)) {
+                handleDataContentRemove(value);
+            } else if (value.length == 7 && value[5] == 0) {
+                LOG.info(" Received ACK ");
+//                Not sure if that's necessary. There is no response for ACK in original app logs
+//                handleAck();
+            } else if (ArrayUtils.equals(value, WatchXPlusConstants.RESP_NOTIFICATION_SETTINGS, 5)) {
+                LOG.info(" Received notification settings status ");
+            } else {
+                LOG.info(" Unhandled value change for characteristic: " + characteristicUUID);
+                logMessageContent(characteristic.getValue());
+            }
+
+            return true;
+        } else if (WatchXPlusConstants.UUID_CHARACTERISTIC_DATABASE_READ.equals(characteristicUUID)) {
+            LOG.info(" Value change for characteristic DATABASE: " + characteristicUUID + " value " + Arrays.toString(value));
+            handleContentDataChunk(value);
+            return true;
+        } else {
+            LOG.info(" Unhandled characteristic changed: " + characteristicUUID + " value " + Arrays.toString(value));
+            logMessageContent(characteristic.getValue());
+        }
+
+        return false;
+    }
+
+    private void handleDataContentRemove(byte[] value) {
+        int dataType = Conversion.fromByteArr16(value[8], value[9]);
+        int timestamp = Conversion.fromByteArr16(value[10], value[11], value[12], value[13]);
+        int removed = value[14];
+        DataType type = DataType.getType(dataType);
+        if( removed == 0) {
+            LOG.info(" Removed " + type + " data for timestamp " + timestamp);
+        } else {
+            LOG.info(" Unsuccessful removal of " + type + " data for timestamp " + timestamp);
+        }
+    }
+
+    /**
+     * Heart rate history retrieve flow:
+     * 1. Request for heart rate data slots count. CMD_RETRIEVE_DATA_COUNT, {@link WatchXPlusDeviceSupport#requestDataCount}
+     * 2. Extract data count from response. RESP_DATA_COUNT, {@link WatchXPlusDeviceSupport#handleDataCount}
+     * 3. Request for N data slot details. CMD_RETRIEVE_DATA_DETAILS, {@link WatchXPlusDeviceSupport#requestDataDetails}
+     * 4. Timestamp of slot is returned, save it for later use. RESP_DATA_DETAILS, {@link WatchXPlusDeviceSupport#handleDataDetails}
+     * 5. Repeat step 3-4 until all slots details retrieved.
+     * 6. Request for M data content by timestamp. CMD_RETRIEVE_DATA_CONTENT, {@link WatchXPlusDeviceSupport#requestDataContentForTimestamp}
+     * 7. Receive kind of pre-flight response. RESP_DATA_CONTENT, {@link WatchXPlusDeviceSupport#handleDataContentAck}
+     * 8. Receive frames with content. They are different than other frames, {@link WatchXPlusDeviceSupport#handleContentDataChunk}
+     *      ie. 0000000255-4F4C48-434241434444454648474747, 0001000247-474645-434240FFFFFFFFFFFFFFFFFF
+     */
+    private void requestDataCount(DataType dataType) {
+
+        TransactionBuilder builder;
+        try {
+            builder = performInitialized("requestDataCount");
+
+            builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE),
+                    buildCommand(WatchXPlusConstants.CMD_RETRIEVE_DATA_COUNT,
+                            WatchXPlusConstants.READ_VALUE,
+                            dataType.getValue()));
+
+            builder.queue(getQueue());
+        } catch (IOException e) {
+            LOG.warn(" Unable to send request to retrieve recorded data ", e);
+        }
+    }
+
+    private void handleDataCount(byte[] value) {
+
+        int dataType = Conversion.fromByteArr16(value[8], value[9]);
+        int dataCount = Conversion.fromByteArr16(value[10], value[11]);
+
+        DataType type = DataType.getType(dataType);
+        LOG.info(" Watch contains " + dataCount + " " + type + " entries");
+        dataSlots = dataCount;
+        dataToFetch.clear();
+        if (dataCount != 0) {
+            requestDataDetails(dataToFetch.size(), type);
+        }
+    }
+
+    private void requestDataDetails(int i, DataType dataType) {
+        LOG.info(" Requesting " + dataType + " details");
+        try {
+            TransactionBuilder builder = performInitialized("requestDataDetails");
+
+            byte[] index = Conversion.toByteArr16(i);
+            byte[] req = BLETypeConversions.join(dataType.getValue(), index);
+            currentDataType = dataType;
+            builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE),
+                    buildCommand(WatchXPlusConstants.CMD_RETRIEVE_DATA_DETAILS,
+                            WatchXPlusConstants.READ_VALUE,
+                            req));
+
+            builder.queue(getQueue());
+        } catch (IOException e) {
+            LOG.warn( "Unable to request data details ", e);
+        }
+    }
+
+    private void handleDataDetails(byte[] value) {
+        LOG.info(" Got data details ");
+        int timestamp = Conversion.fromByteArr16(value[8], value[9], value[10], value[11]);
+        int dataLength = Conversion.fromByteArr16(value[12], value[13]);
+        int samplingInterval = (int) onSamplingInterval(value[14] >> 4, Conversion.fromByteArr16((byte) (value[14] & 15), value[15]));
+        int mtu = Conversion.fromByteArr16(value[16]);
+        int parts = dataLength / 16;
+        if (dataLength % 16 > 0) {
+            parts++;
+        }
+
+        LOG.info(" timestamp (UTC): " + timestamp);
+        LOG.info(" timestamp (UTC): " + new Date((long) timestamp * 1000));
+        LOG.info(" dataLength (data length): " + dataLength);
+        LOG.info(" samplingInterval (per time): " + samplingInterval);
+        LOG.info(" mtu (mtu): " + mtu);
+        LOG.info(" parts: " + parts);
+
+        dataToFetch.put(timestamp, parts);
+
+        if (dataToFetch.size() == dataSlots) {
+            Map.Entry<Integer, Integer> currentValue = dataToFetch.entrySet().iterator().next();
+            requestedDataTimestamp = currentValue.getKey();
+            requestDataContentForTimestamp(requestedDataTimestamp, currentDataType);
+        } else {
+            requestDataDetails(dataToFetch.size(), currentDataType);
+        }
+    }
+
+    private void requestDataContentForTimestamp(int timestamp, DataType dataType) {
+        byte[] command = WatchXPlusConstants.CMD_RETRIEVE_DATA_CONTENT;
+
+        try {
+            TransactionBuilder builder = performInitialized("requestDataContentForTimestamp");
+            byte[] ts = Conversion.toByteArr32(timestamp);
+            byte[] req = BLETypeConversions.join(dataType.getValue(), ts);
+            req = BLETypeConversions.join(req, Conversion.toByteArr16(0));
+            requestedDataTimestamp = timestamp;
+            builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE),
+                    buildCommand(command,
+                            WatchXPlusConstants.READ_VALUE,
+                            req));
+            builder.queue(getQueue());
+        } catch (IOException e) {
+            LOG.warn(" Unable to request data content ", e);
+        }
+    }
+
+    private void removeDataContentForTimestamp(int timestamp, DataType dataType) {
+        byte[] command = WatchXPlusConstants.CMD_REMOVE_DATA_CONTENT;
+
+        try {
+            TransactionBuilder builder = performInitialized("removeDataContentForTimestamp");
+            byte[] ts = Conversion.toByteArr32(timestamp);
+            byte[] req = BLETypeConversions.join(dataType.getValue(), ts);
+            builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE),
+                    buildCommand(command,
+                            WatchXPlusConstants.TASK,
+                            req));
+            builder.queue(getQueue());
+        } catch (IOException e) {
+            LOG.warn(" Unable to remove data content ", e);
+        }
+    }
+
+    private void handleDataContentAck(byte[] value) {
+        LOG.info(" Received data content start ");
+//        To verify: Chunks are sent if value[8] == 0, if value[8] == 1 they are not sent by watch
+    }
+
+    private void handleContentDataChunk(byte[] value) {
+        int chunkNo = Conversion.fromByteArr16(value[0], value[1]);
+        int dataType = Conversion.fromByteArr16(value[2], value[3]);
+        int timezoneOffset = TimeZone.getDefault().getOffset(System.currentTimeMillis())/1000;
+        DataType type = DataType.getType(dataType);
+        try (DBHandler dbHandler = GBApplication.acquireDB()) {
+            WatchXPlusSampleProvider provider = new WatchXPlusSampleProvider(getDevice(), dbHandler.getDaoSession());
+            List<WatchXPlusActivitySample> samples = new ArrayList<>();
+
+            if (DataType.SLEEP.equals(type)) {
+                WatchXPlusHealthActivityOverlayDao overlayDao = dbHandler.getDaoSession().getWatchXPlusHealthActivityOverlayDao();
+                List<WatchXPlusHealthActivityOverlay> overlayList = new ArrayList<>();
+
+                for (int i = 4; i < value.length; i+= 2) {
+
+                    int val = Conversion.fromByteArr16(value[i], value[i+1]);
+                    if (65535 == val) {
+                        break;
+                    }
+
+                    int tsWithOffset = requestedDataTimestamp + (((((chunkNo * 16) / 2) + ((i - 4) / 2)) *5) * 60) - timezoneOffset;
+                    LOG.debug(" SLEEP requested timestamp " + requestedDataTimestamp + " chunkNo " + chunkNo + " Got data: " + new Date((long) tsWithOffset * 1000) + ", rawIntensity: " + val);
+                    WatchXPlusActivitySample sample = createSample(dbHandler, tsWithOffset);
+                    sample.setTimestamp(tsWithOffset);
+                    sample.setProvider(provider);
+                    sample.setRawIntensity(val);
+                    sample.setRawKind((val == 0 ? ActivityKind.DEEP_SLEEP : ActivityKind.LIGHT_SLEEP).getCode());
+                    samples.add(sample);
+                    overlayList.add(new WatchXPlusHealthActivityOverlay(sample.getTimestamp(), sample.getTimestamp()+300, sample.getRawKind(), sample.getDeviceId(), sample.getUserId(), sample.getRawWatchXPlusHealthData()));
+                }
+                overlayDao.insertOrReplaceInTx(overlayList);
+                provider.addGBActivitySamples(samples.toArray(new WatchXPlusActivitySample[0]));
+
+                handleEndOfDataChunks(chunkNo, type);
+            } else if (DataType.HEART_RATE.equals(type)) {
+
+                for (int i = 4; i < value.length; i++) {
+
+                    int val = Conversion.fromByteArr16(value[i]);
+                    if (255 == val) {
+                        break;
+                    }
+                    int tsWithOffset = requestedDataTimestamp + (((((chunkNo * 16) + i) - 4) * 2) * 60) - timezoneOffset;
+                    LOG.debug(" HEART RATE requested timestamp " + requestedDataTimestamp + " chunkNo " + chunkNo + " Got data: " + new Date((long) tsWithOffset * 1000) + ", value: " + val);
+                    WatchXPlusActivitySample sample = createSample(dbHandler, tsWithOffset);
+                    sample.setTimestamp(tsWithOffset);
+                    sample.setHeartRate(val);
+                    sample.setProvider(provider);
+                    sample.setRawKind(ActivityKind.ACTIVITY.getCode());
+                    samples.add(sample);
+                }
+                provider.addGBActivitySamples(samples.toArray(new WatchXPlusActivitySample[0]));
+
+                handleEndOfDataChunks(chunkNo, type);
+            } else {
+                LOG.warn(" Got unsupported data package type: " + type);
+
+                for (int i = 4; i < value.length; i++) {
+                    int val = Conversion.fromByteArr16(value[i], value[i+1]);
+                    if (65535 == val) {
+                        break;
+                    }
+                    int tsWithOffset = requestedDataTimestamp + (((((chunkNo * 16) / 2) + ((i - 4) / 2)) *5) * 60) - timezoneOffset;
+                    LOG.debug(" UNSUPPORTED requested timestamp for type: " + type + " " + requestedDataTimestamp + " chunkNo " + chunkNo + " Got data: " + new Date((long) tsWithOffset * 1000) + ", rawIntensity: " + val);
+                }
+
+            }
+        } catch (Exception ex) {
+            LOG.warn((ex.getMessage()));
+        }
+
+    }
+
+    private void handleEndOfDataChunks(int chunkNo, DataType type) {
+        if(!dataToFetch.isEmpty() && chunkNo == dataToFetch.get(requestedDataTimestamp) - 1) {
+            dataToFetch.remove(requestedDataTimestamp);
+            removeDataContentForTimestamp(requestedDataTimestamp, currentDataType);
+            if (!dataToFetch.isEmpty()) {
+                Map.Entry<Integer, Integer> currentValue = dataToFetch.entrySet().iterator().next();
+                requestedDataTimestamp = currentValue.getKey();
+                requestDataContentForTimestamp(requestedDataTimestamp, type);
+            } else {
+                dataSlots = 0;
+                if(type.equals(DataType.HEART_RATE)) {
+                    currentDataType = DataType.SLEEP;
+                    requestDataCount(currentDataType);
+                }
+            }
+        } else if (dataToFetch.isEmpty()) {
+            dataSlots = 0;
+            if(type.equals(DataType.HEART_RATE)) {
+                currentDataType = DataType.SLEEP;
+                requestDataCount(currentDataType);
+            }
+        }
+    }
+
+
+    private void handleBpMeasureResult(byte[] value) {
+
+        if (value.length < 11) {
+            LOG.info(" BP Measure started. Waiting for result ");
+            GB.toast("BP Measure started. Waiting for result...", Toast.LENGTH_LONG, GB.INFO);
+        } else {
+            LOG.info(" Received BP live data ");
+            int high = Conversion.fromByteArr16(value[8], value[9]);
+            int low = Conversion.fromByteArr16(value[10], value[11]);
+            int timestamp = Conversion.fromByteArr16(value[12], value[13], value[14], value[15]);
+            GB.toast("Calculated BP data: low: " + low + ", high: " + high, Toast.LENGTH_LONG, GB.INFO);
+            LOG.info(" Calculated BP data: timestamp: " + timestamp + ", high: " + high + ", low: " + low);
+        }
+    }
+
+    private void handleAck() {
+        try {
+            TransactionBuilder builder = performInitialized("handleAck");
+
+            builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE),
+                    buildCommand());
+            builder.queue(getQueue());
+        } catch (IOException e) {
+            LOG.warn(" Unable to response to ACK ", e);
+        }
+    }
+
+    //    This is only for ACK response
+    private byte[] buildCommand() {
+        byte[] result = new byte[7];
+        System.arraycopy(WatchXPlusConstants.CMD_HEADER, 0, result, 0, 5);
+
+        result[2] = (byte) (result.length - 6);
+        result[3] = WatchXPlusConstants.REQUEST;
+        result[4] = (byte) sequenceNumber++;
+        result[5] = (byte) 0;
+        result[result.length - 1] = calculateChecksum(result);
+
+        return result;
+    }
+
+    /** handle watch response for steps goal (show steps setting)
+     * @param value - watch reply
+     * for test purposes only
+     */
+    private void handleSportAimStatus(byte[] value) {
+        int stepsAim = Conversion.fromByteArr16(value[8], value[9]);
+        LOG.info(" Received goal stepsAim: " + stepsAim);
+    }
+
+    private void handleStepsInfo(byte[] value) {
+        int steps = Conversion.fromByteArr16(value[8], value[9]);
+        LOG.info(" Received steps count: " + steps);
+
+        // This code is from MakibesHR3DeviceSupport
+        Calendar date = GregorianCalendar.getInstance();
+        int timestamp = (int) (date.getTimeInMillis() / 1000);
+
+        // We need to subtract the day's total step count thus far.
+        int dayStepCount = this.getStepsOnDay(timestamp);
+
+        int newSteps = (steps - dayStepCount);
+
+        if (newSteps > 0) {
+            LOG.info("adding " + newSteps + " steps");
+
+            try (DBHandler dbHandler = GBApplication.acquireDB()) {
+                WatchXPlusSampleProvider provider = new WatchXPlusSampleProvider(getDevice(), dbHandler.getDaoSession());
+
+                WatchXPlusActivitySample sample = createSample(dbHandler, timestamp);
+                sample.setTimestamp(timestamp);
+//            sample.setRawKind(record.type);
+                sample.setRawKind(ActivityKind.ACTIVITY.getCode());
+                sample.setSteps(newSteps);
+//            sample.setDistance(record.distance);
+//            sample.setCalories(record.calories);
+//            sample.setDistance(record.distance);
+//            sample.setHeartRate((record.maxHeartRate - record.minHeartRate) / 2); //TODO: Find an alternative approach for Day Summary Heart Rate
+//            sample.setRawHPlusHealthData(record.getRawData());
+
+                sample.setProvider(provider);
+                provider.addGBActivitySample(sample);
+            } catch (Exception ex) {
+                LOG.warn(ex.getMessage());
+            }
+        }
+    }
+
+    /**
+     * @param timeStamp Time stamp at some point during the requested day.
+     */
+    private int getStepsOnDay(int timeStamp) {
+        try (DBHandler dbHandler = GBApplication.acquireDB()) {
+
+            Calendar dayStart = new GregorianCalendar();
+            Calendar dayEnd = new GregorianCalendar();
+
+            this.getDayStartEnd(timeStamp, dayStart, dayEnd);
+
+            WatchXPlusSampleProvider provider = new WatchXPlusSampleProvider(this.getDevice(), dbHandler.getDaoSession());
+
+            List<WatchXPlusActivitySample> samples = provider.getAllActivitySamples(
+                    (int) (dayStart.getTimeInMillis() / 1000L),
+                    (int) (dayEnd.getTimeInMillis() / 1000L));
+
+            int totalSteps = 0;
+
+            for (WatchXPlusActivitySample sample : samples) {
+                totalSteps += sample.getSteps();
+            }
+
+            return totalSteps;
+
+        } catch (Exception ex) {
+            LOG.warn(ex.getMessage());
+
+            return 0;
+        }
+    }
+
+    /**
+     * @param timeStamp seconds
+     */
+    private void getDayStartEnd(int timeStamp, Calendar start, Calendar end) {
+        final int DAY = (24 * 60 * 60);
+
+        int timeStampStart = ((timeStamp / DAY) * DAY);
+        int timeStampEnd = (timeStampStart + DAY);
+
+        start.setTimeInMillis(timeStampStart * 1000L);
+        end.setTimeInMillis(timeStampEnd * 1000L);
+    }
+
+    private WatchXPlusActivitySample createSample(DBHandler dbHandler, int timestamp) {
+        Long userId = DBHelper.getUser(dbHandler.getDaoSession()).getId();
+        Long deviceId = DBHelper.getDevice(getDevice(), dbHandler.getDaoSession()).getId();
+
+        return new WatchXPlusActivitySample(
+                timestamp,                      // ts
+                deviceId, userId,               // User id
+                null,            // Raw Data
+                ActivityKind.UNKNOWN.getCode(),      // rawKind
+                ActivitySample.NOT_MEASURED,      // rawIntensity
+                ActivitySample.NOT_MEASURED,     // Steps
+                ActivitySample.NOT_MEASURED,    // HR
+                ActivitySample.NOT_MEASURED,  // Distance
+                ActivitySample.NOT_MEASURED     // Calories
+        );
+    }
+
+    private byte[] buildCommand(byte[] command, byte action) {
+        return buildCommand(command, action, null);
+    }
+
+    private byte[] buildCommand(byte[] command, byte action, byte[] value) {
+        if (Arrays.equals(command, WatchXPlusConstants.CMD_CALIBRATION_TASK)) {
+            ACK_CALIBRATION = (byte) sequenceNumber;
+        }
+        command = BLETypeConversions.join(command, value);
+        byte[] result = new byte[7 + command.length];
+        System.arraycopy(WatchXPlusConstants.CMD_HEADER, 0, result, 0, 5);
+        System.arraycopy(command, 0, result, 6, command.length);
+        result[2] = (byte) (command.length + 1);
+        result[3] = WatchXPlusConstants.REQUEST;
+        result[4] = (byte) sequenceNumber++;
+        result[5] = action;
+        result[result.length - 1] = calculateChecksum(result);
+
+        return result;
+    }
+
+    private byte calculateChecksum(byte[] bytes) {
+        byte checksum = 0x00;
+        for (int i = 0; i < bytes.length - 1; i++) {
+            checksum += (bytes[i] ^ i) & 0xFF;
+        }
+        return (byte) (checksum & 0xFF);
+    }
+
+    /** handle watch response for firmware version
+     * @param value - watch response
+     */
+    private void handleFirmwareInfo(byte[] value) {
+        versionInfo.fwVersion = String.format(Locale.US, "%d.%d.%d", value[8], value[9], value[10]);
+        handleGBDeviceEvent(versionInfo);
+    }
+
+    /** handle watch response for battery level
+     * @param value - returned value
+     */
+    private void handleBatteryState(byte[] value) {
+        batteryInfo.state = value[8] == 1 ? BatteryState.BATTERY_NORMAL : BatteryState.BATTERY_LOW;
+        batteryInfo.level = value[9];
+        handleGBDeviceEvent(batteryInfo);
+    }
+
+    /** handle watch response for lift wrist, and shake to refuse/ignore call
+     * @param value - watch response
+     * for test purposes only
+     */
+    private void handleShakeState(byte[] value) {
+        String light = "lightScreen";
+        if ((value[11] & 1) == 1) {
+            light = light + " on";
+        } else {
+            light = light + " off";
+        }
+        String refuse = "refuseCall";
+        if ((((value[11] & 2) >> 1) & 1) != 1) {
+           //z = false;
+            refuse = refuse + " off";
+        } else {
+            refuse = refuse + " on";
+        }
+        LOG.info(" handleShakeState: " + light + " " + refuse);
+    }
+
+    /** handle disconnect reminder (lost device) status
+     * @param value - watch response
+     * for test purposes only
+     */
+    private void handleDisconnectReminderState(byte[] value) {
+        boolean z = true;
+        if (1 != value[8]) {
+            z = false;
+        }
+        LOG.info(" disconnectReminder: " + z + " val: " + value[8]);
+    }
+
+// read preferences
+    private void syncPreferences(TransactionBuilder transaction) {
+        this.setHeadsUpScreen(transaction);              // lift wirst to screen on
+        this.setDNDHours(transaction);                 // DND
+        this.setDisconnectReminder(transaction);         // disconnect reminder
+        this.setLanguageAndTimeFormat(transaction);      // set time mode 12/24h
+        this.setAltitude(transaction);                                      // set altitude calibration
+        this.setLongSitHours(transaction);               // set Long sit reminder
+        ActivityUser activityUser = new ActivityUser();
+        this.setPersonalInformation(transaction, activityUser.getHeightCm(), activityUser.getWeightKg(),
+                activityUser.getAge(),activityUser.getGender());
+    }
+
+    private final Handler mFindPhoneHandler = new Handler();
+
+    private void onReverseFindDevice(boolean start) {
+        if (start) {
+            SharedPreferences sharedPreferences = GBApplication.getDeviceSpecificSharedPrefs(
+                    this.getDevice().getAddress());
+
+            int findPhone = WatchXPlusDeviceCoordinator.getFindPhone(sharedPreferences);
+
+            if (findPhone != WatchXPlusDeviceCoordinator.FindPhone_OFF) {
+                GBDeviceEventFindPhone findPhoneEvent = new GBDeviceEventFindPhone();
+
+                findPhoneEvent.event = GBDeviceEventFindPhone.Event.START;
+
+                evaluateGBDeviceEvent(findPhoneEvent);
+
+                if (findPhone > 0) {
+                    this.mFindPhoneHandler.postDelayed(new Runnable() {
+                        @Override
+                        public void run() {
+                            onReverseFindDevice(false);
+                        }
+                    }, findPhone * 1000);
+                }
+            }
+        } else {
+            // Always send stop, ignore preferences.
+            GBDeviceEventFindPhone findPhoneEvent = new GBDeviceEventFindPhone();
+
+            findPhoneEvent.event = GBDeviceEventFindPhone.Event.STOP;
+
+            evaluateGBDeviceEvent(findPhoneEvent);
+        }
+    }
+
+    // Command to toggle Lift Wrist to Light Screen, and shake to ignore/reject call
+    private void setHeadsUpScreen(TransactionBuilder transactionBuilder) {
+        boolean enable = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getBoolean(DeviceSettingsPreferenceConst.PREF_LIFTWRIST_NOSHED, false);
+        boolean shakeReject = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getBoolean(WatchXPlusConstants.PREF_SHAKE_REJECT, false);
+        byte refuseCall = 0x00; // force shake wrist to ignore/reject call to OFF
+                                // returned characteristic is equal with button press while ringing
+        if (shakeReject) refuseCall = 0x01;
+        byte lightScreen = 0x00;
+        if (enable) {
+            lightScreen = 0x01;
+        }
+        byte b = (byte) (lightScreen + (refuseCall << 1));
+        byte[] liftScreen = new byte[4];
+        liftScreen[0] = 0x00;
+        liftScreen[1] = 0x00;
+        liftScreen[2] = 0x00;
+        liftScreen[3] = b;              //byte[11]
+        transactionBuilder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE),
+                buildCommand(WatchXPlusConstants.CMD_SHAKE_SWITCH,
+                             WatchXPlusConstants.WRITE_VALUE,
+                             liftScreen));
+    }
+
+    // command to set disconnect reminder
+    private void setDisconnectReminder(TransactionBuilder transactionBuilder) {
+        boolean enable = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getBoolean(DeviceSettingsPreferenceConst.PREF_DISCONNECTNOTIF_NOSHED, false);
+        transactionBuilder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE),
+                buildCommand(WatchXPlusConstants.CMD_DISCONNECT_REMIND,
+                        WatchXPlusConstants.WRITE_VALUE,
+                        new byte[]{(byte) (enable ? 0x01 : 0x00)}));
+    }
+
+// Request status of Lift Wrist to Light Screen, and Shake to Ignore/Reject Call
+    private void getShakeStatus(TransactionBuilder transactionBuilder) {
+        transactionBuilder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE),
+                buildCommand(WatchXPlusConstants.CMD_SHAKE_SWITCH,
+                        WatchXPlusConstants.READ_VALUE));
+    }
+
+// calibrate altitude
+    private void setAltitude(TransactionBuilder transactionBuilder) {
+        String altitudeString = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getString(DeviceSettingsPreferenceConst.PREF_ALTITUDE_CALIBRATE, "200");
+        int mAltitude = Integer.parseInt(altitudeString);
+        if (mAltitude < 0) {
+            mAltitude = (Math.abs(mAltitude) ^ 65535) + 1;
+        }
+        int mAirPressure = Math.abs(0); // air pressure 0 ???
+        byte[] bArr = new byte[4];
+        bArr[0] = (byte) (mAltitude >> 8);      // bytr[8]
+        bArr[1] = (byte) mAltitude;             // bytr[9]
+        bArr[2] = (byte) (mAirPressure >> 8);   // bytr[10]
+        bArr[3] = (byte) mAirPressure;          // bytr[11]
+        transactionBuilder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE),
+                buildCommand(WatchXPlusConstants.CMD_ALTITUDE,
+                        WatchXPlusConstants.WRITE_VALUE,
+                        bArr));
+        LOG.info(" setAltitude: " + mAltitude);
+    }
+
+    // set time format
+    private void setLanguageAndTimeFormat(TransactionBuilder transactionBuilder) {
+        byte setLanguage, setTimeMode;
+        String languageString = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getString(DeviceSettingsPreferenceConst.PREF_LANGUAGE, "1");
+        if (languageString == null || languageString.equals("1")) {
+             setLanguage = 0x01;
+        } else {
+            setLanguage = 0x00;
+        }
+
+        String timeformatString = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getString(DeviceSettingsPreferenceConst.PREF_TIMEFORMAT, "1");
+        assert timeformatString != null;
+        if (timeformatString.equals(getContext().getString(R.string.p_timeformat_24h))) {
+            setTimeMode = WatchXPlusConstants.ARG_SET_TIMEMODE_24H;
+        } else {
+            setTimeMode = WatchXPlusConstants.ARG_SET_TIMEMODE_12H;
+        }
+
+        byte[] bArr = new byte[2];
+        bArr[0] = setLanguage;           //byte[08] language
+        bArr[1] = setTimeMode;           //byte[09] time
+        transactionBuilder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE),
+                buildCommand(WatchXPlusConstants.CMD_TIME_LANGUAGE,
+                        WatchXPlusConstants.WRITE_VALUE,
+                        bArr));
+    }
+
+    @Override
+    public void dispose() {
+        LocalBroadcastManager broadcastManager = LocalBroadcastManager.getInstance(getContext());
+        broadcastManager.unregisterReceiver(broadcastReceiver);
+        super.dispose();
+    }
+
+    private static double onSamplingInterval(int i, int i2) {
+        switch (i) {
+            case 1:
+                return 1.0d * Math.pow(10.0d, -6.0d) * ((double) i2);
+            case 2:
+                return 1.0d * Math.pow(10.0d, -3.0d) * ((double) i2);
+            case 3:
+                return (double) (i2);
+            case 4:
+                return 10.0d * Math.pow(10.0d, -6.0d) * ((double) i2);
+            case 5:
+                return 10.0d * Math.pow(10.0d, -3.0d) * ((double) i2);
+            case 6:
+                return (double) (10 * i2);
+            default:
+                return (double) (10 * i2);
+        }
+    }
+
+    @Override
+    public boolean getImplicitCallbackModify() {
+        return true;
+    }
+
+    @Override
+    public boolean getSendWriteRequestResponse() {
+        return false;
+    }
+
+
+    private static class Conversion {
+        static byte[] toByteArr16(int value) {
+            return new byte[]{(byte) (value >> 8), (byte) value};
+        }
+
+        static int fromByteArr16(byte... value) { // equals calculateHigh
+            int intValue = 0;
+            for (int i2 = 0; i2 < value.length; i2++) {
+                intValue += (value[i2] & 255) << (((value.length - 1) - i2) * 8);
+            }
+            return intValue;
+        }
+
+        static byte[] toByteArr32(int value) {
+            return new byte[]{(byte) (value >> 24),
+                    (byte) (value >> 16),
+                    (byte) (value >> 8),
+                    (byte) value};
+        }
+
+// --Commented out by Inspection START (13.12.2019 谐. 23:38):
+//        static int calculateLow(byte... bArr) {
+//            int i = 0;
+//            int i2 = 0;
+//            while (i < bArr.length) {
+//                i2 += (bArr[i] & 255) << (i * 8);
+//                i++;
+//            }
+//            return i2;
+//        }
+// --Commented out by Inspection STOP (13.12.2019 谐. 23:38)
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/liveview/LiveviewIoThread.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/liveview/LiveviewIoThread.java
new file mode 100644
index 0000000..bb79836
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/liveview/LiveviewIoThread.java
@@ -0,0 +1,98 @@
+/*  Copyright (C) 2016-2024 Carsten Pfeiffer, Daniele Gobbetti
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.liveview;
+
+import android.bluetooth.BluetoothAdapter;
+import android.content.Context;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.liveview.LiveviewConstants;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.btclassic.BtClassicIoThread;
+import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class LiveviewIoThread extends BtClassicIoThread {
+    private static final Logger LOG = LoggerFactory.getLogger(LiveviewIoThread.class);
+
+    private static final UUID SERIAL = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
+
+    public LiveviewIoThread(GBDevice gbDevice, Context context, GBDeviceProtocol lvProtocol, LiveviewSupport lvSupport, BluetoothAdapter lvBtAdapter) {
+        super(gbDevice, context, lvProtocol, lvSupport, lvBtAdapter);
+    }
+
+    protected byte[] parseIncoming(InputStream inputStream) throws IOException {
+        ByteArrayOutputStream msgStream = new ByteArrayOutputStream();
+
+        boolean finished = false;
+        ReaderState state = ReaderState.ID;
+        byte[] incoming = new byte[1];
+
+        while (!finished) {
+            inputStream.read(incoming);
+            msgStream.write(incoming);
+
+            switch (state) {
+                case ID:
+                    state = ReaderState.HEADER_LEN;
+                    incoming = new byte[1];
+                    break;
+                case HEADER_LEN:
+                    int headerSize = 0xff & incoming[0];
+                    state = ReaderState.HEADER;
+                    incoming = new byte[headerSize];
+                    break;
+                case HEADER:
+                    int payloadSize = getLastInt(msgStream);
+                    if (payloadSize < 0 || payloadSize > 8000) //this will possibly be changed in the future
+                        throw new IOException();
+                    state = ReaderState.PAYLOAD;
+                    incoming = new byte[payloadSize];
+                    break;
+                case PAYLOAD: //read is blocking, if we are here we have all the data
+                    finished = true;
+                    break;
+            }
+        }
+        byte[] msgArray = msgStream.toByteArray();
+        LOG.debug("received: " + GB.hexdump(msgArray, 0, msgArray.length));
+        return msgArray;
+    }
+
+
+    /**
+     * Enumeration containing the possible internal status of the reader.
+     */
+    private enum ReaderState {
+        ID, HEADER_LEN, HEADER, PAYLOAD;
+    }
+
+    private int getLastInt(ByteArrayOutputStream stream) {
+        byte[] array = stream.toByteArray();
+        ByteBuffer buffer = ByteBuffer.wrap(array, array.length - 4, 4);
+        buffer.order(LiveviewConstants.BYTE_ORDER);
+        return buffer.getInt();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/liveview/LiveviewProtocol.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/liveview/LiveviewProtocol.java
new file mode 100644
index 0000000..e6ca903
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/liveview/LiveviewProtocol.java
@@ -0,0 +1,148 @@
+/*  Copyright (C) 2016-2024 Daniele Gobbetti
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.liveview;
+
+import java.nio.ByteBuffer;
+import java.util.Calendar;
+
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventSendBytes;
+import nodomain.freeyourgadget.gadgetbridge.devices.liveview.LiveviewConstants;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
+import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol;
+
+public class LiveviewProtocol extends GBDeviceProtocol {
+
+    @Override
+    public byte[] encodeFindDevice(boolean start) {
+        return encodeVibrateRequest((short) 100, (short) 200);
+    }
+
+    protected LiveviewProtocol(GBDevice device) {
+        super(device);
+    }
+
+    @Override
+    public GBDeviceEvent[] decodeResponse(byte[] responseData) {
+        int length = responseData.length;
+        if (length < 4) {
+            //empty message
+            return null;
+        } else {
+            ByteBuffer buffer = ByteBuffer.wrap(responseData, 0, length);
+            byte msgId = buffer.get();
+            buffer.get();
+            int payloadLen = buffer.getInt();
+            GBDeviceEventSendBytes reply = new GBDeviceEventSendBytes();
+            if (payloadLen + 6 == length) {
+                switch (msgId) {
+                    case LiveviewConstants.MSG_DEVICESTATUS:
+                        reply.encodedBytes = constructMessage(LiveviewConstants.MSG_DEVICESTATUS_ACK, new byte[]{LiveviewConstants.RESULT_OK});
+                        break;
+                    case LiveviewConstants.MSG_DISPLAYPANEL_ACK:
+                        reply.encodedBytes = encodeVibrateRequest((short) 100, (short) 200); //hack to make the notifications vibrate!
+                        break;
+                    default:
+                }
+                GBDeviceEventSendBytes ack = new GBDeviceEventSendBytes();
+                ack.encodedBytes = constructMessage(LiveviewConstants.MSG_ACK, new byte[]{msgId});
+
+                return new GBDeviceEvent[]{ack, reply};
+            }
+        }
+
+
+        return super.decodeResponse(responseData);
+    }
+
+    @Override
+    public byte[] encodeSetTime() {
+        int time = (int) (Calendar.getInstance().getTimeInMillis() / 1000);
+        time += Calendar.getInstance().get(Calendar.ZONE_OFFSET) / 1000;
+        time += Calendar.getInstance().get(Calendar.DST_OFFSET) / 1000;
+        ByteBuffer buffer = ByteBuffer.allocate(5);
+        buffer.order(LiveviewConstants.BYTE_ORDER);
+        buffer.putInt(time);
+        buffer.put(LiveviewConstants.CLOCK_24H);
+        return constructMessage(LiveviewConstants.MSG_GETTIME_RESP, buffer.array());
+    }
+
+    @Override
+    public byte[] encodeNotification(NotificationSpec notificationSpec) {
+        String headerText;
+        // for SMS and EMAIL that came in though SMS or K9 receiver
+        if (notificationSpec.sender != null) {
+            headerText = notificationSpec.sender;
+        } else {
+            headerText = notificationSpec.title;
+        }
+
+        String footerText = (null != notificationSpec.sourceName) ? notificationSpec.sourceName : "";
+        String bodyText = (null != notificationSpec.body) ? notificationSpec.body : "";
+
+        byte[] headerTextArray = headerText.getBytes(LiveviewConstants.ENCODING);
+        byte[] footerTextArray = footerText.getBytes(LiveviewConstants.ENCODING);
+        byte[] bodyTextArray = bodyText.getBytes(LiveviewConstants.ENCODING);
+        int size = 15 + headerTextArray.length + bodyTextArray.length + footerTextArray.length;
+        ByteBuffer buffer = ByteBuffer.allocate(size);
+        buffer.put((byte) 1);
+        buffer.putShort((short) 0);
+        buffer.putShort((short) 0);
+        buffer.putShort((short) 0);
+        buffer.put((byte) 80); //should alert but it doesn't make the liveview vibrate
+
+        buffer.put((byte) 0); //0 is for plaintext vs bitmapimage (1) strings
+        buffer.putShort((short) headerTextArray.length);
+        buffer.put(headerTextArray);
+        buffer.putShort((short) bodyTextArray.length);
+        buffer.put(bodyTextArray);
+        buffer.putShort((short) footerTextArray.length);
+        buffer.put(footerTextArray);
+        return constructMessage(LiveviewConstants.MSG_DISPLAYPANEL, buffer.array());
+    }
+
+
+    //specific messages
+
+    public static byte[] constructMessage(byte messageType, byte[] payload) {
+        ByteBuffer msgBuffer = ByteBuffer.allocate(payload.length + 6);
+        msgBuffer.order(LiveviewConstants.BYTE_ORDER);
+        msgBuffer.put(messageType);
+        msgBuffer.put((byte) 4);
+        msgBuffer.putInt(payload.length);
+        msgBuffer.put(payload);
+        return msgBuffer.array();
+    }
+
+    public byte[] encodeVibrateRequest(short delay, short time) {
+        ByteBuffer buffer = ByteBuffer.allocate(4);
+        buffer.order(LiveviewConstants.BYTE_ORDER);
+        buffer.putShort(delay);
+        buffer.putShort(time);
+        return constructMessage(LiveviewConstants.MSG_SETVIBRATE, buffer.array());
+    }
+
+    public byte[] encodeCapabilitiesRequest() {
+        byte[] version = LiveviewConstants.CLIENT_SOFTWARE_VERSION.getBytes(LiveviewConstants.ENCODING);
+        ByteBuffer buffer = ByteBuffer.allocate(version.length + 1);
+        buffer.order(LiveviewConstants.BYTE_ORDER);
+        buffer.put((byte) version.length);
+        buffer.put(version);
+        return constructMessage(LiveviewConstants.MSG_GETCAPS, buffer.array());
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/liveview/LiveviewSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/liveview/LiveviewSupport.java
new file mode 100644
index 0000000..3eae490
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/liveview/LiveviewSupport.java
@@ -0,0 +1,67 @@
+/*  Copyright (C) 2016-2024 Andreas Shimokawa, Arjan Schrijver, Daniele
+    Gobbetti, Sebastian Kranz
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.liveview;
+
+import android.net.Uri;
+
+import java.util.ArrayList;
+import java.util.UUID;
+
+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.service.serial.AbstractSerialDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceIoThread;
+import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol;
+
+public class LiveviewSupport extends AbstractSerialDeviceSupport {
+
+    @Override
+    public boolean connect() {
+        getDeviceIOThread().start();
+        return true;
+    }
+
+    @Override
+    protected GBDeviceProtocol createDeviceProtocol() {
+        return new LiveviewProtocol(getDevice());
+    }
+
+    @Override
+    protected GBDeviceIoThread createDeviceIOThread() {
+        return new LiveviewIoThread(getDevice(), getContext(), getDeviceProtocol(), LiveviewSupport.this, getBluetoothAdapter());
+    }
+
+    @Override
+    public boolean useAutoConnect() {
+        return false;
+    }
+
+    @Override
+    public synchronized LiveviewIoThread getDeviceIOThread() {
+        return (LiveviewIoThread) super.getDeviceIOThread();
+    }
+
+    @Override
+    public void onNotification(NotificationSpec notificationSpec) {
+        super.onNotification(notificationSpec);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/makibeshr3/MakibesHR3DeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/makibeshr3/MakibesHR3DeviceSupport.java
new file mode 100644
index 0000000..9b4fdf3
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/makibeshr3/MakibesHR3DeviceSupport.java
@@ -0,0 +1,1083 @@
+/*  Copyright (C) 2019-2024 Andreas Shimokawa, Arjan Schrijver, Cre3per,
+    Damien Gaignon, Taavi Eom盲e
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+// TODO: All the commands that aren't supported by GB should be added to device specific settings.
+
+// TODO: It'd be cool if we could change the language. There's no official way to do so, but the
+// TODO: watch is sold as chinese/english. Screen-on-time would be nice too.
+
+// TODO: Firmware upgrades. WearFit tries to connect to Wake up Technology at
+// TODO: http://47.112.119.52/app.php/Api/hardUpdate/type/55
+// TODO: But that server resets the connection.
+// TODO: The host is supposed to be www.iwhop.com, but that domain no longer exists.
+// TODO: I think /app.php is missing a closing php tag.
+
+package nodomain.freeyourgadget.gadgetbridge.service.devices.makibeshr3;
+
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.os.CountDownTimer;
+import android.os.Handler;
+import android.widget.Toast;
+
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.List;
+import java.util.UUID;
+
+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.GBDeviceEventBatteryInfo;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventFindPhone;
+import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.makibeshr3.MakibesHR3Constants;
+import nodomain.freeyourgadget.gadgetbridge.devices.makibeshr3.MakibesHR3Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.makibeshr3.MakibesHR3SampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.entities.MakibesHR3ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.entities.User;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
+import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
+import nodomain.freeyourgadget.gadgetbridge.model.BatteryState;
+import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
+import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+import nodomain.freeyourgadget.gadgetbridge.util.preferences.DevicePrefs;
+
+public class MakibesHR3DeviceSupport extends AbstractBTLEDeviceSupport implements SharedPreferences.OnSharedPreferenceChangeListener {
+
+    private static final Logger LOG = LoggerFactory.getLogger(MakibesHR3DeviceSupport.class);
+
+    // The delay must be at least as long as it takes the watch to respond.
+    // Reordering the requests could maybe reduce the delay, but this works fine too.
+    private final CountDownTimer mFetchCountDown = new CountDownTimer(2000, 2000) {
+        @Override
+        public void onTick(long millisUntilFinished) {
+
+        }
+
+        @Override
+        public void onFinish() {
+            LOG.debug("download finished");
+            GB.updateTransferNotification(null, "", false, 100, getContext());
+        }
+    };
+
+    private final Handler mFindPhoneHandler = new Handler();
+
+    private BluetoothGattCharacteristic mControlCharacteristic = null;
+    private BluetoothGattCharacteristic mReportCharacteristic = null;
+
+
+    public MakibesHR3DeviceSupport() {
+        super(LOG);
+
+        addSupportedService(MakibesHR3Constants.UUID_SERVICE);
+    }
+
+    /**
+     * Called whenever data is received to postpone the removing of the progress notification.
+     *
+     * @param start Start showing the notification
+     */
+    private void fetch(boolean start) {
+        if (start) {
+            // We don't know how long the watch is going to take to reply. Keep progress at 0.
+            GB.updateTransferNotification(null, getContext().getString(R.string.busy_task_fetch_activity_data), true, 0, getContext());
+        }
+
+        this.mFetchCountDown.cancel();
+        this.mFetchCountDown.start();
+    }
+
+    @Override
+    public boolean useAutoConnect() {
+        return false;
+    }
+
+    /**
+     * @param timeStamp seconds
+     */
+    private void getDayStartEnd(int timeStamp, Calendar start, Calendar end) {
+        final int DAY = (24 * 60 * 60);
+
+        int timeStampStart = ((timeStamp / DAY) * DAY);
+        int timeStampEnd = (timeStampStart + DAY);
+
+        start.setTimeInMillis(timeStampStart * 1000L);
+        end.setTimeInMillis(timeStampEnd * 1000L);
+    }
+
+    /**
+     * @param timeStamp Time stamp at some point during the requested day.
+     */
+    private int getStepsOnDay(int timeStamp) {
+        try (DBHandler dbHandler = GBApplication.acquireDB()) {
+
+            Calendar dayStart = new GregorianCalendar();
+            Calendar dayEnd = new GregorianCalendar();
+
+            this.getDayStartEnd(timeStamp, dayStart, dayEnd);
+
+            MakibesHR3SampleProvider provider = new MakibesHR3SampleProvider(this.getDevice(), dbHandler.getDaoSession());
+
+            List<MakibesHR3ActivitySample> samples = provider.getAllActivitySamples(
+                    (int) (dayStart.getTimeInMillis() / 1000L),
+                    (int) (dayEnd.getTimeInMillis() / 1000L));
+
+            int totalSteps = 0;
+
+            for (MakibesHR3ActivitySample sample : samples) {
+                totalSteps += sample.getSteps();
+            }
+
+            return totalSteps;
+
+        } catch (Exception ex) {
+            LOG.error(ex.getMessage());
+
+            return 0;
+        }
+    }
+
+    public MakibesHR3ActivitySample createActivitySample(Device device, User user, int timestampInSeconds, SampleProvider provider) {
+        MakibesHR3ActivitySample sample = new MakibesHR3ActivitySample();
+        sample.setDevice(device);
+        sample.setUser(user);
+        sample.setTimestamp(timestampInSeconds);
+        sample.setProvider(provider);
+
+        return sample;
+    }
+
+
+    @Override
+    public void onNotification(NotificationSpec notificationSpec) {
+        TransactionBuilder transactionBuilder = this.createTransactionBuilder("onnotificaiton");
+
+        byte sender;
+
+        switch (notificationSpec.type) {
+            case FACEBOOK:
+            case FACEBOOK_MESSENGER:
+                sender = MakibesHR3Constants.ARG_SEND_NOTIFICATION_SOURCE_FACEBOOK;
+                break;
+            case LINE:
+                sender = MakibesHR3Constants.ARG_SEND_NOTIFICATION_SOURCE_LINE;
+                break;
+            case TELEGRAM:
+                sender = MakibesHR3Constants.ARG_SEND_NOTIFICATION_SOURCE_MESSAGE;
+                break;
+            case TWITTER:
+                sender = MakibesHR3Constants.ARG_SEND_NOTIFICATION_SOURCE_TWITTER;
+                break;
+            case WECHAT:
+                sender = MakibesHR3Constants.ARG_SEND_NOTIFICATION_SOURCE_WECHAT;
+                break;
+            case WHATSAPP:
+                sender = MakibesHR3Constants.ARG_SEND_NOTIFICATION_SOURCE_WHATSAPP;
+                break;
+            case KAKAO_TALK:
+                sender = MakibesHR3Constants.ARG_SEND_NOTIFICATION_SOURCE_KAKOTALK;
+                break;
+
+            default:
+                sender = MakibesHR3Constants.ARG_SEND_NOTIFICATION_SOURCE_MESSAGE;
+                break;
+        }
+
+        String message = "";
+
+        if (notificationSpec.title != null) {
+            message += (notificationSpec.title + ": ");
+        }
+
+        message += notificationSpec.body;
+
+        this.sendNotification(transactionBuilder,
+                sender, message);
+
+        try {
+            this.performConnected(transactionBuilder.getTransaction());
+        } catch (Exception ex) {
+            LoggerFactory.getLogger(this.getClass()).error("notification failed");
+        }
+    }
+
+    @Override
+    public void onSetTime() {
+        TransactionBuilder transactionBuilder = this.createTransactionBuilder("settime");
+
+        this.setDateTime(transactionBuilder);
+
+        try {
+            this.performConnected(transactionBuilder.getTransaction());
+        } catch (Exception ex) {
+            LoggerFactory.getLogger(this.getClass()).error("factory reset failed");
+        }
+    }
+
+    @Override
+    public void onSetAlarms(ArrayList<? extends Alarm> alarms) {
+
+        TransactionBuilder transactionBuilder = this.createTransactionBuilder("setalarms");
+
+        for (int i = 0; i < alarms.size(); ++i) {
+            Alarm alarm = alarms.get(i);
+
+            byte repetition = 0x00;
+
+            switch (alarm.getRepetition()) {
+                case Alarm.ALARM_ONCE:
+                    repetition = MakibesHR3Constants.ARG_SET_ALARM_REMINDER_REPEAT_ONE_TIME;
+                    break;
+
+                case Alarm.ALARM_MON:
+                    repetition |= MakibesHR3Constants.ARG_SET_ALARM_REMINDER_REPEAT_MONDAY;
+                case Alarm.ALARM_TUE:
+                    repetition |= MakibesHR3Constants.ARG_SET_ALARM_REMINDER_REPEAT_TUESDAY;
+                case Alarm.ALARM_WED:
+                    repetition |= MakibesHR3Constants.ARG_SET_ALARM_REMINDER_REPEAT_WEDNESDAY;
+                case Alarm.ALARM_THU:
+                    repetition |= MakibesHR3Constants.ARG_SET_ALARM_REMINDER_REPEAT_THURSDAY;
+                case Alarm.ALARM_FRI:
+                    repetition |= MakibesHR3Constants.ARG_SET_ALARM_REMINDER_REPEAT_FRIDAY;
+                case Alarm.ALARM_SAT:
+                    repetition |= MakibesHR3Constants.ARG_SET_ALARM_REMINDER_REPEAT_SATURDAY;
+                case Alarm.ALARM_SUN:
+                    repetition |= MakibesHR3Constants.ARG_SET_ALARM_REMINDER_REPEAT_SUNDAY;
+                    break;
+
+                default:
+                    LOG.warn("invalid alarm repetition " + alarm.getRepetition());
+                    break;
+            }
+
+            // Should we use @alarm.getPosition() rather than @i?
+            this.setAlarmReminder(
+                    transactionBuilder,
+                    i,
+                    alarm.getEnabled(),
+                    alarm.getHour(),
+                    alarm.getMinute(),
+                    repetition);
+        }
+
+        try {
+            this.performConnected(transactionBuilder.getTransaction());
+        } catch (Exception ex) {
+            LoggerFactory.getLogger(this.getClass()).error("setalarms failed");
+        }
+    }
+
+    @Override
+    public void onSetCallState(CallSpec callSpec) {
+        TransactionBuilder transactionBuilder = this.createTransactionBuilder("callstate");
+        LOG.debug("callSpec " + callSpec.command);
+        if (callSpec.command == CallSpec.CALL_INCOMING) {
+            this.sendNotification(transactionBuilder, MakibesHR3Constants.ARG_SEND_NOTIFICATION_SOURCE_CALL, callSpec.name);
+        } else {
+            this.sendNotification(transactionBuilder, MakibesHR3Constants.ARG_SEND_NOTIFICATION_SOURCE_STOP_CALL, "");
+        }
+
+        try {
+            this.performConnected(transactionBuilder.getTransaction());
+        } catch (Exception ex) {
+            LoggerFactory.getLogger(this.getClass()).error("call state failed");
+        }
+    }
+
+    @Override
+    public void onReset(int flags) {
+
+        if ((flags & GBDeviceProtocol.RESET_FLAGS_FACTORY_RESET) != 0) {
+            TransactionBuilder transactionBuilder = this.createTransactionBuilder("reset");
+            this.factoryReset(transactionBuilder);
+
+            try {
+                this.performConnected(transactionBuilder.getTransaction());
+            } catch (Exception ex) {
+                LoggerFactory.getLogger(this.getClass()).error("factory reset failed");
+            }
+        } else if ((flags & GBDeviceProtocol.RESET_FLAGS_REBOOT) != 0) {
+            TransactionBuilder transactionBuilder = this.createTransactionBuilder("reboot");
+            this.reboot(transactionBuilder);
+
+            try {
+                this.performConnected(transactionBuilder.getTransaction());
+            } catch (Exception ex) {
+                LoggerFactory.getLogger(this.getClass()).error("factory reset failed");
+            }
+        }
+    }
+
+    @Override
+    public void onEnableRealtimeHeartRateMeasurement(boolean enable) {
+        TransactionBuilder transactionBuilder = this.createTransactionBuilder("finddevice");
+
+        this.setEnableRealTimeHeartRate(transactionBuilder, enable);
+
+        try {
+            this.performConnected(transactionBuilder.getTransaction());
+        } catch (Exception e) {
+            LOG.debug("ERROR");
+        }
+    }
+
+    private void onReverseFindDevice(boolean start) {
+        if (start) {
+            SharedPreferences sharedPreferences = GBApplication.getDeviceSpecificSharedPrefs(
+                    this.getDevice().getAddress());
+
+            int findPhone = MakibesHR3Coordinator.getFindPhone(sharedPreferences);
+
+            if (findPhone != MakibesHR3Coordinator.FindPhone_OFF) {
+                GBDeviceEventFindPhone findPhoneEvent = new GBDeviceEventFindPhone();
+
+                findPhoneEvent.event = GBDeviceEventFindPhone.Event.START;
+
+                evaluateGBDeviceEvent(findPhoneEvent);
+
+                if (findPhone > 0) {
+                    this.mFindPhoneHandler.postDelayed(new Runnable() {
+                        @Override
+                        public void run() {
+                            onReverseFindDevice(false);
+                        }
+                    }, findPhone * 1000);
+                }
+            }
+        } else {
+            // Always send stop, ignore preferences.
+            GBDeviceEventFindPhone findPhoneEvent = new GBDeviceEventFindPhone();
+
+            findPhoneEvent.event = GBDeviceEventFindPhone.Event.STOP;
+
+            evaluateGBDeviceEvent(findPhoneEvent);
+        }
+    }
+
+    @Override
+    public void onFindDevice(boolean start) {
+        if (!start) {
+            return;
+        }
+
+        TransactionBuilder transactionBuilder = this.createTransactionBuilder("finddevice");
+
+        this.findDevice(transactionBuilder);
+
+        try {
+            this.performConnected(transactionBuilder.getTransaction());
+        } catch (Exception e) {
+            LOG.debug("ERROR");
+        }
+    }
+
+    private void syncPreferences(TransactionBuilder transaction) {
+
+        SharedPreferences sharedPreferences = GBApplication.getDeviceSpecificSharedPrefs(this.getDevice().getAddress());
+
+        this.setTimeMode(transaction, sharedPreferences);
+        this.setDateTime(transaction);
+        this.setQuiteHours(transaction, sharedPreferences);
+
+        this.setHeadsUpScreen(transaction, sharedPreferences);
+        this.setLostReminder(transaction, sharedPreferences);
+
+        ActivityUser activityUser = new ActivityUser();
+
+        this.setPersonalInformation(transaction,
+                (byte) Math.round(activityUser.getHeightCm() * 0.43), // Thanks no1f1
+                activityUser.getAge(),
+                activityUser.getHeightCm(),
+                activityUser.getWeightKg(),
+                activityUser.getStepsGoal() / 1000);
+
+        this.fetch(true);
+    }
+
+    @Override
+    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
+        LOG.debug(key + " changed");
+
+        if (!this.isConnected()) {
+            LOG.debug("ignoring change, we're disconnected");
+            return;
+        }
+
+        TransactionBuilder transactionBuilder = this.createTransactionBuilder("onSharedPreferenceChanged");
+
+        if (key.equals(DeviceSettingsPreferenceConst.PREF_TIMEFORMAT)) {
+            this.setTimeMode(transactionBuilder, sharedPreferences);
+        } else if (key.equals(DeviceSettingsPreferenceConst.PREF_ACTIVATE_DISPLAY_ON_LIFT)) {
+            this.setHeadsUpScreen(transactionBuilder, sharedPreferences);
+        } else if (key.equals(DeviceSettingsPreferenceConst.PREF_DISCONNECT_NOTIFICATION)) {
+            this.setLostReminder(transactionBuilder, sharedPreferences);
+        } else if (key.equals(DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_NOAUTO) ||
+                key.equals(DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_NOAUTO_START) ||
+                key.equals(DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_NOAUTO_END)) {
+            this.setQuiteHours(transactionBuilder, sharedPreferences);
+        } else if (key.equals(DeviceSettingsPreferenceConst.PREF_FIND_PHONE) ||
+                key.equals(DeviceSettingsPreferenceConst.PREF_FIND_PHONE_DURATION)) {
+            // No action, we check the shared preferences when the device tries to ring the phone.
+        } else {
+            return;
+        }
+
+        try {
+            this.performConnected(transactionBuilder.getTransaction());
+        } catch (Exception ex) {
+            LOG.warn(ex.getMessage());
+        }
+    }
+
+    /**
+     * Use to show the battery icon in the device card.
+     * If the icon shows up later, the user might be trying to tap one thing but the battery icon
+     * will shift everything.
+     * This is hacky. There should be a "supportsBattery" function in the coordinator that displays
+     * the battery icon before the battery level is received.
+     */
+    private void fakeBattery() {
+        GBDeviceEventBatteryInfo batteryInfo = new GBDeviceEventBatteryInfo();
+
+        batteryInfo.level = 100;
+        batteryInfo.state = BatteryState.UNKNOWN;
+
+        this.handleGBDeviceEvent(batteryInfo);
+    }
+
+    @Override
+    protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
+        this.fakeBattery();
+
+        GB.updateTransferNotification(null, getContext().getString(R.string.busy_task_fetch_activity_data), true, 0, getContext());
+
+        gbDevice.setState(GBDevice.State.INITIALIZING);
+        gbDevice.sendDeviceUpdateIntent(getContext());
+
+        this.mControlCharacteristic = getCharacteristic(MakibesHR3Constants.UUID_CHARACTERISTIC_CONTROL);
+        this.mReportCharacteristic = getCharacteristic(MakibesHR3Constants.UUID_CHARACTERISTIC_REPORT);
+
+        builder.notify(this.mReportCharacteristic, true);
+        builder.setCallback(this);
+
+
+        // Allow modifications
+        builder.write(this.mControlCharacteristic, new byte[]{0x01, 0x00});
+
+        // Initialize device
+        this.syncPreferences(builder);
+
+        this.requestFitness(builder);
+
+        gbDevice.setState(GBDevice.State.INITIALIZED);
+        gbDevice.sendDeviceUpdateIntent(getContext());
+
+        getDevice().setFirmwareVersion("N/A");
+        getDevice().setFirmwareVersion2("N/A");
+
+        SharedPreferences preferences = GBApplication.getDeviceSpecificSharedPrefs(this.getDevice().getAddress());
+
+        preferences.registerOnSharedPreferenceChangeListener(this);
+
+        return builder;
+    }
+
+    private void addGBActivitySamples(MakibesHR3ActivitySample[] samples) {
+        try (DBHandler dbHandler = GBApplication.acquireDB()) {
+
+            User user = DBHelper.getUser(dbHandler.getDaoSession());
+            Device device = DBHelper.getDevice(this.getDevice(), dbHandler.getDaoSession());
+
+            MakibesHR3SampleProvider provider = new MakibesHR3SampleProvider(this.getDevice(), dbHandler.getDaoSession());
+
+            for (MakibesHR3ActivitySample sample : samples) {
+                sample.setDevice(device);
+                sample.setUser(user);
+                sample.setProvider(provider);
+
+                sample.setRawIntensity(ActivitySample.NOT_MEASURED);
+
+                provider.addGBActivitySample(sample);
+            }
+
+        } catch (Exception ex) {
+            // Why is this a toast? The user doesn't care about the error.
+            GB.toast(getContext(), "Error saving samples: " + ex.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
+            GB.updateTransferNotification(null, "Data transfer failed", false, 0, getContext());
+
+            LOG.error(ex.getMessage());
+        }
+    }
+
+    private void addGBActivitySample(MakibesHR3ActivitySample sample) {
+        this.addGBActivitySamples(new MakibesHR3ActivitySample[]{sample});
+    }
+
+    /**
+     * Should only be called after the sample has been populated by
+     * {@link MakibesHR3DeviceSupport#addGBActivitySample} or
+     * {@link MakibesHR3DeviceSupport#addGBActivitySamples}
+     */
+    private void broadcastSample(MakibesHR3ActivitySample sample) {
+        Intent intent = new Intent(DeviceService.ACTION_REALTIME_SAMPLES)
+                .putExtra(GBDevice.EXTRA_DEVICE, getDevice())
+                .putExtra(DeviceService.EXTRA_REALTIME_SAMPLE, sample)
+                .putExtra(DeviceService.EXTRA_TIMESTAMP, sample.getTimestamp());
+        LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent);
+    }
+
+    private void onReceiveFitness(int steps) {
+        LOG.info("steps: " + steps);
+
+        this.onReceiveStepsSample(steps);
+    }
+
+    private void onReceiveHeartRate(int heartRate) {
+        LOG.info("heart rate: " + heartRate);
+
+        MakibesHR3ActivitySample sample = new MakibesHR3ActivitySample();
+
+        if (heartRate > 0) {
+            sample.setHeartRate(heartRate);
+            sample.setTimestamp((int) (System.currentTimeMillis() / 1000));
+            sample.setRawKind(ActivityKind.ACTIVITY.getCode());
+        } else {
+            if (heartRate == MakibesHR3Constants.ARG_HEARTRATE_NO_TARGET) {
+                sample.setRawKind(ActivityKind.NOT_WORN.getCode());
+            } else if (heartRate == MakibesHR3Constants.ARG_HEARTRATE_NO_READING) {
+                sample.setRawKind(ActivityKind.NOT_MEASURED.getCode());
+            } else {
+                LOG.warn("invalid heart rate reading: " + heartRate);
+                return;
+            }
+        }
+
+        this.addGBActivitySample(sample);
+        this.broadcastSample(sample);
+    }
+
+    private void onReceiveHeartRateSample(int year, int month, int day, int hour, int minute, int heartRate) {
+        LOG.debug("received heart rate sample " + year + "-" + month + "-" + day + " " + hour + ":" + minute + " " + heartRate);
+
+        MakibesHR3ActivitySample sample = new MakibesHR3ActivitySample();
+
+        Calendar calendar = new GregorianCalendar(year, month - 1, day, hour, minute);
+
+        int timeStamp = (int) (calendar.getTimeInMillis() / 1000);
+
+        sample.setHeartRate(heartRate);
+        sample.setTimestamp(timeStamp);
+
+        sample.setRawKind(ActivityKind.ACTIVITY.getCode());
+
+        this.addGBActivitySample(sample);
+    }
+
+    private void onReceiveStepsSample(int timeStamp, int steps) {
+        MakibesHR3ActivitySample sample = new MakibesHR3ActivitySample();
+
+        // We need to subtract the day's total step count thus far.
+        int dayStepCount = this.getStepsOnDay(timeStamp);
+
+        int newSteps = (steps - dayStepCount);
+
+        if (newSteps > 0) {
+            LOG.debug("adding {} steps", newSteps);
+
+            sample.setSteps(steps - dayStepCount);
+            sample.setTimestamp(timeStamp);
+
+            sample.setRawKind(ActivityKind.ACTIVITY.getCode());
+
+            this.addGBActivitySample(sample);
+        }
+    }
+
+    /**
+     * The time is the start of the measurement. Each measurement lasts 1h.
+     */
+    private void onReceiveStepsSample(int year, int month, int day, int hour, int minute, int steps) {
+        LOG.debug("received steps sample {}-{}-{} {}:{} {}", year, month, day, hour, minute, steps);
+
+        Calendar calendar = new GregorianCalendar(year, month - 1, day, hour + 1, minute);
+
+        int timeStamp = (int) (calendar.getTimeInMillis() / 1000);
+
+        this.onReceiveStepsSample(timeStamp, steps);
+    }
+
+    private void onReceiveStepsSample(int steps) {
+        this.onReceiveStepsSample((int) (Calendar.getInstance().getTimeInMillis() / 1000l), steps);
+    }
+
+    @Override
+    public boolean onCharacteristicChanged(BluetoothGatt gatt,
+                                           BluetoothGattCharacteristic characteristic) {
+        if (super.onCharacteristicChanged(gatt, characteristic)) {
+            return true;
+        }
+
+        byte[] data = characteristic.getValue();
+        if (data.length < 6)
+            return true;
+
+        this.fetch(false);
+
+        UUID characteristicUuid = characteristic.getUuid();
+
+        if (characteristicUuid.equals(mReportCharacteristic.getUuid())) {
+            byte[] value = characteristic.getValue();
+            byte[] arguments = new byte[value.length - 6];
+
+            if (arguments.length >= 0) {
+                System.arraycopy(value, 6, arguments, 0, arguments.length);
+            }
+
+            byte[] report = new byte[]{value[4], value[5]};
+
+            switch (report[0]) {
+                case MakibesHR3Constants.RPRT_REVERSE_FIND_DEVICE:
+                    this.onReverseFindDevice(arguments[0] == 0x01);
+                    break;
+                case MakibesHR3Constants.RPRT_HEARTRATE:
+                    if (value.length == 7) {
+                        this.onReceiveHeartRate((int) arguments[0]);
+                    }
+                    break;
+                case MakibesHR3Constants.RPRT_BATTERY:
+                    if (arguments.length == 2) {
+                        GBDeviceEventBatteryInfo batteryInfo = new GBDeviceEventBatteryInfo();
+
+                        batteryInfo.level = (short) (arguments[1] & 0xff);
+                        batteryInfo.state = ((arguments[0] == 0x01) ? BatteryState.BATTERY_CHARGING : BatteryState.BATTERY_NORMAL);
+
+                        this.handleGBDeviceEvent(batteryInfo);
+                    }
+                    break;
+                case MakibesHR3Constants.RPRT_SOFTWARE:
+                    if (arguments.length == 11) {
+                        this.getDevice().setFirmwareVersion(((int) (arguments[0] & 0xff)) + "." + (arguments[1] & 0xff));
+                    }
+                    break;
+                default: // Non-80 reports
+                    if (Arrays.equals(report, MakibesHR3Constants.RPRT_FITNESS)) {
+                        int steps = (arguments[1] & 0xff) * 0x100 + (arguments[2] & 0xff);
+                        this.onReceiveFitness(
+                                steps
+                        );
+                    } else if (Arrays.equals(report, MakibesHR3Constants.RPRT_HEART_RATE_SAMPLE)) {
+                        this.onReceiveHeartRateSample(
+                                (arguments[0] & 0xff) + 2000, (arguments[1] & 0xff), (arguments[2] & 0xff),
+                                (arguments[3] & 0xff), (arguments[4] & 0xff),
+                                (arguments[5] & 0xff));
+                    } else if (Arrays.equals(report, MakibesHR3Constants.RPRT_STEPS_SAMPLE)) {
+                        this.onReceiveStepsSample(
+                                (arguments[0] & 0xff) + 2000, (arguments[1] & 0xff), (arguments[2] & 0xff),
+                                (arguments[3] & 0xff), 0,
+                                ((arguments[5] & 0xff) * 0x100) + (arguments[6] & 0xff));
+                    }
+                    break;
+            }
+        }
+
+        return false;
+    }
+
+    private byte[] craftData(byte[] command, byte[] data) {
+        byte[] result = new byte[MakibesHR3Constants.DATA_TEMPLATE.length + data.length];
+
+        System.arraycopy(MakibesHR3Constants.DATA_TEMPLATE, 0, result, 0, MakibesHR3Constants.DATA_TEMPLATE.length);
+
+        result[MakibesHR3Constants.DATA_ARGUMENT_COUNT_INDEX] = (byte) (data.length + 3);
+
+        System.arraycopy(command, 0, result, 4, command.length);
+
+        System.arraycopy(data, 0, result, 6, data.length);
+
+        return result;
+    }
+
+    private byte[] craftData(byte command, byte[] data) {
+        return this.craftData(new byte[]{command}, data);
+    }
+
+
+    private byte[] craftData(byte command) {
+        return this.craftData(command, new byte[]{});
+    }
+
+    private void writeSafe(BluetoothGattCharacteristic characteristic, TransactionBuilder builder, byte[] data) {
+        final int maxMessageLength = 20;
+
+        // For every split, we need 1 byte extra.
+        int extraBytes = 0;
+
+        if (data.length > 20) {
+            extraBytes = (((data.length - maxMessageLength) / maxMessageLength) + 1);
+        }
+
+        int totalDataLength = (data.length + extraBytes);
+
+        int segmentCount = (((totalDataLength - 1) / maxMessageLength) + 1);
+
+        byte[] indexedData = new byte[totalDataLength];
+
+        int it = 0;
+        int segmentIndex = 0;
+        for (int i = 0; i < data.length; ++i) {
+            if ((i != 0) && ((it % maxMessageLength) == 0)) {
+                indexedData[it++] = (byte) segmentIndex++;
+            }
+
+            indexedData[it++] = data[i];
+        }
+
+        for (int i = 0; i < segmentCount; ++i) {
+            int segmentStart = (i * maxMessageLength);
+            int segmentLength;
+
+            if (i == (segmentCount - 1)) {
+                segmentLength = (indexedData.length - segmentStart);
+            } else {
+                segmentLength = maxMessageLength;
+            }
+
+            byte[] segment = new byte[segmentLength];
+
+            System.arraycopy(indexedData, segmentStart, segment, 0, segmentLength);
+
+            builder.write(characteristic, segment);
+        }
+    }
+
+    private MakibesHR3DeviceSupport factoryReset(TransactionBuilder transaction) {
+        transaction.write(this.mControlCharacteristic, this.craftData(MakibesHR3Constants.CMD_FACTORY_RESET));
+
+        return this.reboot(transaction);
+    }
+
+    /**
+     * Ugly because I don't like Date.
+     * All non-zero records after the given times will be returned via
+     * {@link MakibesHR3Constants#RPRT_HEART_RATE_SAMPLE},
+     * {@link MakibesHR3Constants#RPRT_STEPS_SAMPLE},
+     * {@link MakibesHR3Constants#RPRT_FITNESS}
+     */
+    private MakibesHR3DeviceSupport requestFitness(TransactionBuilder transaction,
+                                                   int yearStepsAfter, int monthStepsAfter, int dayStepsAfter,
+                                                   int hourStepsAfter, int minuteStepsAfter,
+                                                   int yearHeartRateAfter, int monthHeartRateAfter, int dayHeartRateAfter,
+                                                   int hourHeartRateAfter, int minuteHeartRateAfter) {
+
+        byte[] data = this.craftData(MakibesHR3Constants.CMD_REQUEST_FITNESS,
+                new byte[]{
+                        (byte) 0x00,
+                        (byte) (yearStepsAfter - 2000),
+                        (byte) monthStepsAfter,
+                        (byte) dayStepsAfter,
+                        (byte) hourStepsAfter,
+                        (byte) minuteStepsAfter,
+                        (byte) (yearHeartRateAfter - 2000),
+                        (byte) monthHeartRateAfter,
+                        (byte) dayHeartRateAfter,
+                        (byte) hourHeartRateAfter,
+                        (byte) minuteHeartRateAfter
+                });
+
+        transaction.write(this.mControlCharacteristic, data);
+
+        this.fetch(true);
+
+        return this;
+    }
+
+    /**
+     * Ugly because I don't like Date.
+     * All non-zero records after the given times will be returned via
+     * {@link MakibesHR3Constants#RPRT_HEART_RATE_SAMPLE},
+     * {@link MakibesHR3Constants#RPRT_STEPS_SAMPLE},
+     * {@link MakibesHR3Constants#RPRT_FITNESS}
+     */
+    private MakibesHR3DeviceSupport requestFitness(TransactionBuilder transaction) {
+        try (DBHandler dbHandler = GBApplication.acquireDB()) {
+
+            MakibesHR3SampleProvider provider = new MakibesHR3SampleProvider(this.getDevice(), dbHandler.getDaoSession());
+
+            MakibesHR3ActivitySample latestSample = provider.getLatestActivitySample();
+
+            if (latestSample == null) {
+                this.requestFitness(transaction,
+                        2000, 0, 0, 0, 0,
+                        2000, 0, 0, 0, 0);
+            } else {
+                Calendar calendar = new GregorianCalendar();
+                calendar.setTime(new Date(latestSample.getTimestamp() * 1000l));
+
+                int year = calendar.get(Calendar.YEAR);
+                int month = (calendar.get(Calendar.MONTH) + 1);
+                int day = calendar.get(Calendar.DAY_OF_MONTH);
+                int hour = calendar.get(Calendar.HOUR_OF_DAY);
+                int minute = calendar.get(Calendar.MINUTE);
+
+                this.requestFitness(transaction,
+                        year, month, day, hour, minute,
+                        year, month, day, hour, minute);
+            }
+
+        } catch (Exception ex) {
+            LOG.error(ex.getMessage());
+        }
+
+        return this;
+    }
+
+    private MakibesHR3DeviceSupport findDevice(TransactionBuilder transaction) {
+        transaction.write(this.mControlCharacteristic, this.craftData(MakibesHR3Constants.CMD_FIND_DEVICE));
+
+        return this;
+    }
+
+    private MakibesHR3DeviceSupport sendNotification(TransactionBuilder transaction,
+                                                     byte source, String message) {
+        byte[] data = new byte[message.length() + 2];
+        data[0] = source;
+        data[1] = (byte) 0x02;
+
+        for (int i = 0; i < message.length(); ++i) {
+            data[i + 2] = (byte) message.charAt(i);
+        }
+
+        this.writeSafe(
+                this.mControlCharacteristic,
+                transaction,
+                this.craftData(MakibesHR3Constants.CMD_SEND_NOTIFICATION, data));
+
+        return this;
+    }
+
+    private MakibesHR3DeviceSupport setAlarmReminder(TransactionBuilder transaction,
+                                                     int id, boolean enable, int hour, int minute, byte repeat) {
+        transaction.write(this.mControlCharacteristic,
+                this.craftData(MakibesHR3Constants.CMD_SET_ALARM_REMINDER, new byte[]{
+                        (byte) id,
+                        (byte) (enable ? 0x01 : 0x00),
+                        (byte) hour,
+                        (byte) minute,
+                        repeat
+                }));
+
+        return this;
+    }
+
+    /**
+     * @param transactionBuilder
+     * @param stepLength         cm
+     * @param age                years
+     * @param height             cm
+     * @param weight             kg
+     * @param stepGoal           kilo
+     */
+    private MakibesHR3DeviceSupport setPersonalInformation(TransactionBuilder transactionBuilder,
+                                                           int stepLength, int age, int height, int weight, int stepGoal) {
+        byte[] data = this.craftData(MakibesHR3Constants.CMD_SET_PERSONAL_INFORMATION,
+                new byte[]{
+                        (byte) stepLength,
+                        (byte) age,
+                        (byte) height,
+                        (byte) weight,
+                        MakibesHR3Constants.ARG_SET_PERSONAL_INFORMATION_UNIT_DISTANCE_KILOMETERS,
+                        (byte) stepGoal,
+                        (byte) 0x5a,
+                        (byte) 0x82,
+                        (byte) 0x3c,
+                        (byte) 0x5a,
+                        (byte) 0x28,
+                        (byte) 0xb4,
+                        (byte) 0x5d,
+                        (byte) 0x64,
+
+                });
+
+        transactionBuilder.write(this.mControlCharacteristic, data);
+
+        return this;
+    }
+
+    private MakibesHR3DeviceSupport setHeadsUpScreen(TransactionBuilder transactionBuilder, boolean enable) {
+        byte[] data = this.craftData(MakibesHR3Constants.CMD_SET_HEADS_UP_SCREEN,
+                new byte[]{(byte) (enable ? 0x01 : 0x00)});
+
+        transactionBuilder.write(this.mControlCharacteristic, data);
+
+        return this;
+    }
+
+    private MakibesHR3DeviceSupport setQuiteHours(TransactionBuilder transactionBuilder,
+                                                  boolean enable,
+                                                  int hourStart, int minuteStart,
+                                                  int hourEnd, int minuteEnd) {
+        byte[] data = this.craftData(MakibesHR3Constants.CMD_SET_QUITE_HOURS, new byte[]{
+                (byte) (enable ? 0x01 : 0x00),
+                (byte) hourStart, (byte) minuteStart,
+                (byte) hourEnd, (byte) minuteEnd
+        });
+
+        transactionBuilder.write(this.mControlCharacteristic, data);
+
+        return this;
+    }
+
+    private MakibesHR3DeviceSupport setQuiteHours(TransactionBuilder transactionBuilder,
+                                                  SharedPreferences sharedPreferences) {
+
+        Calendar start = new GregorianCalendar();
+        Calendar end = new GregorianCalendar();
+        boolean enable = MakibesHR3Coordinator.getQuiteHours(sharedPreferences, start, end);
+
+        return this.setQuiteHours(transactionBuilder, enable,
+                start.get(Calendar.HOUR_OF_DAY), start.get(Calendar.MINUTE),
+                end.get(Calendar.HOUR_OF_DAY), end.get(Calendar.MINUTE));
+    }
+
+    private MakibesHR3DeviceSupport setHeadsUpScreen(TransactionBuilder transactionBuilder, SharedPreferences sharedPreferences) {
+        return this.setHeadsUpScreen(transactionBuilder,
+                MakibesHR3Coordinator.shouldEnableHeadsUpScreen(sharedPreferences));
+    }
+
+    private MakibesHR3DeviceSupport setLostReminder(TransactionBuilder transactionBuilder, boolean enable) {
+        byte[] data = this.craftData(MakibesHR3Constants.CMD_SET_LOST_REMINDER,
+                new byte[]{(byte) (enable ? 0x01 : 0x00)});
+
+        transactionBuilder.write(this.mControlCharacteristic, data);
+
+        return this;
+    }
+
+    private MakibesHR3DeviceSupport setLostReminder(TransactionBuilder transactionBuilder, SharedPreferences sharedPreferences) {
+        return this.setLostReminder(transactionBuilder,
+                MakibesHR3Coordinator.shouldEnableLostReminder(sharedPreferences));
+    }
+
+    private MakibesHR3DeviceSupport setTimeMode(TransactionBuilder transactionBuilder, byte timeMode) {
+        byte[] data = this.craftData(MakibesHR3Constants.CMD_SET_TIMEMODE, new byte[]{timeMode});
+
+        transactionBuilder.write(this.mControlCharacteristic, data);
+
+        return this;
+    }
+
+    public byte getTimeMode(SharedPreferences sharedPrefs) {
+        String timeMode = new DevicePrefs(sharedPrefs, gbDevice).getTimeFormat();
+
+        if (timeMode.equals(getContext().getString(R.string.p_timeformat_24h))) {
+            return MakibesHR3Constants.ARG_SET_TIMEMODE_24H;
+        } else {
+            return MakibesHR3Constants.ARG_SET_TIMEMODE_12H;
+        }
+    }
+
+    private MakibesHR3DeviceSupport setTimeMode(TransactionBuilder transactionBuilder, SharedPreferences sharedPreferences) {
+        return this.setTimeMode(transactionBuilder,
+                getTimeMode(sharedPreferences));
+    }
+
+    private MakibesHR3DeviceSupport setEnableRealTimeHeartRate(TransactionBuilder transaction, boolean enable) {
+        byte[] data = this.craftData(MakibesHR3Constants.CMD_SET_REAL_TIME_HEART_RATE, new byte[]{(byte) (enable ? 0x01 : 0x00)});
+
+        transaction.write(this.mControlCharacteristic, data);
+
+        return this;
+    }
+
+    private MakibesHR3DeviceSupport setDateTime(TransactionBuilder transaction,
+                                                int year,
+                                                int month,
+                                                int day,
+                                                int hour,
+                                                int minute,
+                                                int second) {
+
+        byte[] data = this.craftData(MakibesHR3Constants.CMD_SET_DATE_TIME,
+                new byte[]{
+                        (byte) 0x00,
+                        (byte) ((year & 0xff00) >> 8),
+                        (byte) (year & 0x00ff),
+                        (byte) month,
+                        (byte) day,
+                        (byte) hour,
+                        (byte) minute,
+                        (byte) second
+                });
+
+        transaction.write(this.mControlCharacteristic, data);
+
+        return this;
+    }
+
+    private MakibesHR3DeviceSupport setDateTime(TransactionBuilder transaction) {
+
+        Calendar calendar = Calendar.getInstance();
+
+        return this.setDateTime(transaction,
+                calendar.get(Calendar.YEAR),
+                calendar.get(Calendar.MONTH) + 1,
+                calendar.get(Calendar.DAY_OF_MONTH),
+                calendar.get(Calendar.HOUR_OF_DAY),
+                calendar.get(Calendar.MINUTE),
+                calendar.get(Calendar.SECOND)
+        );
+    }
+
+    private MakibesHR3DeviceSupport reboot(TransactionBuilder transaction) {
+        transaction.write(this.mControlCharacteristic, this.craftData(MakibesHR3Constants.CMD_REBOOT));
+
+        return this;
+    }
+
+    @Override
+    public boolean getImplicitCallbackModify() {
+        return true;
+    }
+
+    @Override
+    public boolean getSendWriteRequestResponse() {
+        return false;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/marstek/MarstekB2500DeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/marstek/MarstekB2500DeviceSupport.java
new file mode 100644
index 0000000..8dac2d8
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/marstek/MarstekB2500DeviceSupport.java
@@ -0,0 +1,346 @@
+/*  Copyright (C) 2024 Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.marstek;
+
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_BATTERY_ALLOW_PASS_THOUGH;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_BATTERY_DISCHARGE_INTERVALS_SET;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_BATTERY_DISCHARGE_MANAUAL;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_BATTERY_MINIMUM_CHARGE;
+
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.content.Intent;
+import android.content.SharedPreferences;
+
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.time.LocalTime;
+import java.util.Calendar;
+import java.util.SimpleTimeZone;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.devices.marstek.SolarEquipmentStatusActivity;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
+import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol;
+import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+
+public class MarstekB2500DeviceSupport extends AbstractBTLEDeviceSupport {
+    public static final UUID UUID_CHARACTERISTIC_MAIN = UUID.fromString("0000ff02-0000-1000-8000-00805f9b34fb");
+    public static final UUID UUID_SERVICE_MAIN = UUID.fromString("0000ff00-0000-1000-8000-00805f9b34fb");
+    private static final byte COMMAND_PREFIX = 0x73;
+    private static final byte COMMAND = 0x23;
+    private static final byte OPCODE_REBOOT = 0x25;
+    private static final byte OPCODE_INFO1 = 0x03;
+    private static final byte OPCODE_INFO2 = 0x13;
+    // the following already have checksums precalculated (last byte)
+    private static final byte[] COMMAND_GET_INFOS1 = new byte[]{COMMAND_PREFIX, 0x06, COMMAND, OPCODE_INFO1, 0x01, 0x54};
+    private static final byte[] COMMAND_GET_INFOS2 = new byte[]{COMMAND_PREFIX, 0x06, COMMAND, OPCODE_INFO2, 0x00, 0x45};
+    private static final byte[] COMMAND_REBOOT = new byte[]{COMMAND_PREFIX, 0x06, COMMAND, OPCODE_REBOOT, 0x01, 0x72};
+    private static final byte[] COMMAND_SET_AUTO_DISCHARGE = new byte[]{COMMAND_PREFIX, 0x06, COMMAND, 0x11, 0x00, 0x47};
+    private static final byte[] COMMAND_SET_POWERMETER_CHANNEL1 = new byte[]{COMMAND_PREFIX, 0x06, COMMAND, 0x2a, 0x00, 0x7c};
+    private static final byte[] COMMAND_SET_BATTERY_ALLOW_PASS_THOUGH = new byte[]{COMMAND_PREFIX, 0x06, COMMAND, 0x0d, 0x00, 0x5b};
+    private static final byte[] COMMAND_SET_BATTERY_DISALLOW_PASS_THOUGH = new byte[]{COMMAND_PREFIX, 0x06, COMMAND, 0x0d, 0x01, 0x5a};
+    private static final Logger LOG = LoggerFactory.getLogger(MarstekB2500DeviceSupport.class);
+    private boolean is_initialized = false;
+    private int firmwareVersion;
+
+    public MarstekB2500DeviceSupport() {
+        super(LOG);
+        addSupportedService(UUID_SERVICE_MAIN);
+    }
+
+    @Override
+    public boolean onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
+        super.onCharacteristicChanged(gatt, characteristic);
+
+        byte[] value = characteristic.getValue();
+
+        if (value[0] == COMMAND_PREFIX) {
+            if ((value[1] == 0x10) && (value[2] == COMMAND) && (value[3] == OPCODE_INFO1)) {
+                decodeInfos(value);
+                if (!is_initialized) {
+                    sendCommand("get infos 2 (initial)", COMMAND_GET_INFOS2);
+                }
+                return true;
+            } else if ((value[1] == 0x3a || value[1] == 0x22) && value[2] == COMMAND && value[3] == OPCODE_INFO2) {
+                decodeDischargeIntervalsToPreferences(value);
+                if (!is_initialized) {
+                    sendCommand("set time (initial)", encodeSetCurrentTime());
+                    gbDevice.setState(GBDevice.State.INITIALIZED);
+                    gbDevice.sendDeviceUpdateIntent(getContext());
+                    is_initialized = true;
+                }
+                return true;
+            }
+        }
+        return false;
+    }
+
+
+    @Override
+    public void onTestNewFunction() {
+        sendCommand("get infos 1", COMMAND_GET_INFOS1);
+        sendCommand("get infos 2", COMMAND_GET_INFOS2);
+    }
+
+    @Override
+    public void onReset(int flags) {
+        if ((flags & GBDeviceProtocol.RESET_FLAGS_REBOOT) != 0) {
+            sendCommand("reboot", COMMAND_REBOOT);
+        }
+    }
+
+    @Override
+    protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
+        builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext()));
+        getDevice().setFirmwareVersion("N/A");
+        getDevice().setFirmwareVersion2("N/A");
+        builder.requestMtu(512);
+        builder.notify(getCharacteristic(UUID_CHARACTERISTIC_MAIN), true);
+        builder.wait(3500);
+        builder.write(getCharacteristic(UUID_CHARACTERISTIC_MAIN), COMMAND_GET_INFOS1);
+        return builder;
+    }
+
+    @Override
+    public boolean useAutoConnect() {
+        return false;
+    }
+
+    private void sendCommand(String taskName, byte[] contents) {
+        TransactionBuilder builder = new TransactionBuilder(taskName);
+        BluetoothGattCharacteristic characteristic = getCharacteristic(UUID_CHARACTERISTIC_MAIN);
+        if (characteristic != null && contents != null) {
+            builder.write(characteristic, contents);
+            builder.wait(750);
+            builder.queue(getQueue());
+        }
+    }
+
+    @Override
+    public void onSetTime() {
+        sendCommand("set time", encodeSetCurrentTime());
+    }
+
+    @Override
+    public void onFetchRecordedData(int dataTypes) {
+        sendCommand("get infos 1", COMMAND_GET_INFOS1);
+    }
+
+    @Override
+    public void onSendConfiguration(final String config) {
+        Prefs devicePrefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress()));
+        switch (config) {
+            case PREF_BATTERY_DISCHARGE_INTERVALS_SET:
+                if (devicePrefs.getBoolean(PREF_BATTERY_DISCHARGE_MANAUAL, true)) {
+                    sendCommand("set discharge intervals", encodeDischargeIntervalsFromPreferences());
+                } else {
+                    sendCommand("set dynamic discharge", COMMAND_SET_AUTO_DISCHARGE);
+                    sendCommand("set channel auto", COMMAND_SET_POWERMETER_CHANNEL1);
+                }
+                return;
+            case PREF_BATTERY_MINIMUM_CHARGE:
+                sendCommand("set minimum charge", encodeMinimumChargeFromPreferences());
+                return;
+            case PREF_BATTERY_ALLOW_PASS_THOUGH:
+                if (devicePrefs.getBoolean(PREF_BATTERY_ALLOW_PASS_THOUGH, true)) {
+                    sendCommand("set allow pass-though", COMMAND_SET_BATTERY_ALLOW_PASS_THOUGH);
+                } else {
+                    sendCommand("set disallow pass-though", COMMAND_SET_BATTERY_DISALLOW_PASS_THOUGH);
+                }
+                return;
+        }
+
+        LOG.warn("Unknown config changed: {}", config);
+    }
+
+    private void decodeInfos(byte[] value) {
+        Prefs devicePrefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress()));
+        SharedPreferences.Editor devicePrefsEdit = devicePrefs.getPreferences().edit();
+        ByteBuffer buf = ByteBuffer.wrap(value);
+        buf.order(ByteOrder.LITTLE_ENDIAN);
+        buf.position(4); // skip header
+        boolean p1_active = buf.get() != 0x00;
+        boolean p2_active = buf.get() != 0x00;
+        int p1_watt = buf.getShort();
+        int p2_watt = buf.getShort();
+        buf.position(buf.position() + 2); // unknown;
+
+        firmwareVersion = buf.get() & 0xff;
+        boolean battery_allow_passthough = buf.get() != 0x01;
+        boolean battery_manual_discharge_intervals = buf.get() != 0x01;
+        buf.position(buf.position() + 3); // skip unknown
+        byte battery_max_use_pct = buf.get();
+        int output_to_inverter_target = buf.getShort();
+        buf.position(buf.position() + 1); // skip unknown
+        int battery_charge_kwh = buf.getShort();
+        int output_to_inverter_1_watt = buf.getShort();
+        int output_to_inverter_2_watt = buf.getShort();
+
+        int battery_minimum_charge_pct = 100 - battery_max_use_pct;
+        int output_to_inverter_sum_watt = output_to_inverter_1_watt + output_to_inverter_2_watt;
+
+        LOG.info("p1_active: {}, p2_active: {}, p1_watt: {}W, p2_watt: {}W, battery_charge_kwh: {}kWh, battery_minimum_charge_pct: {}%, battery_allow_passthrough: {}, battery_manual_discharge_intervals: {}, output_to_inverter_target: {}W, output_to_inverter: {}W+{}W={}W, firmwareVersion: V{}, ",
+                p1_active, p2_active, p1_watt, p2_watt, battery_charge_kwh, battery_minimum_charge_pct, battery_allow_passthough, battery_manual_discharge_intervals, output_to_inverter_target, output_to_inverter_1_watt, output_to_inverter_2_watt, output_to_inverter_sum_watt, firmwareVersion);
+
+        getDevice().setFirmwareVersion("V" + firmwareVersion);
+        getDevice().sendDeviceUpdateIntent(getContext());
+
+        devicePrefsEdit.putString(PREF_BATTERY_MINIMUM_CHARGE, String.valueOf(battery_minimum_charge_pct));
+        devicePrefsEdit.putBoolean(PREF_BATTERY_DISCHARGE_MANAUAL, battery_manual_discharge_intervals);
+        devicePrefsEdit.putBoolean(PREF_BATTERY_ALLOW_PASS_THOUGH, battery_allow_passthough);
+        devicePrefsEdit.apply();
+        devicePrefsEdit.commit();
+
+        int battery_percentage = (int) Math.ceil((battery_charge_kwh / 2240.0f) * 100);
+        getDevice().setBatteryLevel(battery_percentage);
+        getDevice().sendDeviceUpdateIntent(getContext());
+
+        Intent intent = new Intent(SolarEquipmentStatusActivity.ACTION_SEND_SOLAR_EQUIPMENT_STATUS);
+        intent.putExtra(SolarEquipmentStatusActivity.EXTRA_BATTERY_PCT, battery_percentage);
+        intent.putExtra(SolarEquipmentStatusActivity.EXTRA_PANEL1_WATT, p1_watt);
+        intent.putExtra(SolarEquipmentStatusActivity.EXTRA_PANEL2_WATT, p2_watt);
+        intent.putExtra(SolarEquipmentStatusActivity.EXTRA_OUTPUT1_WATT, output_to_inverter_1_watt);
+        intent.putExtra(SolarEquipmentStatusActivity.EXTRA_OUTPUT2_WATT, output_to_inverter_2_watt);
+        LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent);
+        getContext().sendBroadcast(intent);
+    }
+
+    private void decodeDischargeIntervalsToPreferences(byte[] value) {
+        Prefs devicePrefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress()));
+        SharedPreferences.Editor devicePrefsEdit = devicePrefs.getPreferences().edit();
+        ByteBuffer buf = ByteBuffer.wrap(value);
+        buf.order(ByteOrder.LITTLE_ENDIAN);
+        buf.position(5); // skip
+        for (int i = 1; i <= 5; i++) {
+            boolean enabled = buf.get() != 0x00;
+            int startHour = buf.get();
+            int startMinute = buf.get();
+            int endHour = buf.get();
+            int endMinute = buf.get();
+            int watt = buf.getShort();
+            devicePrefsEdit.putBoolean("battery_discharge_interval" + i + "_enabled", enabled);
+            devicePrefsEdit.putString("battery_discharge_interval" + i + "_start", DateTimeUtils.formatTime(startHour, startMinute));
+            devicePrefsEdit.putString("battery_discharge_interval" + i + "_end", DateTimeUtils.formatTime(endHour, endMinute));
+            devicePrefsEdit.putString("battery_discharge_interval" + i + "_watt", String.valueOf(watt));
+
+            if (i == 3) {
+                if (value.length == 0x22) // old fw only seems to return 3 settings and has 7 trailing bytes
+                    break;
+                buf.position(buf.position() + 17); // skip 17 bytes, there is a hole with unknown data
+            }
+        }
+        devicePrefsEdit.apply();
+        devicePrefsEdit.commit();
+    }
+
+    private byte[] encodeMinimumChargeFromPreferences() {
+        Prefs devicePrefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress()));
+        int minimum_charge = devicePrefs.getInt(PREF_BATTERY_MINIMUM_CHARGE, 10);
+        int maximum_use = 100 - minimum_charge;
+
+        byte length = 6;
+        ByteBuffer buf = ByteBuffer.allocate(length);
+        buf.order(ByteOrder.LITTLE_ENDIAN);
+
+        buf.put(COMMAND_PREFIX);
+        buf.put(length);
+        buf.put(COMMAND);
+        buf.put((byte) 0x0b);
+        buf.put((byte) maximum_use);
+        buf.put(getXORChecksum(buf.array()));
+
+        return buf.array();
+    }
+
+    private byte[] encodeSetCurrentTime() {
+        long ts = System.currentTimeMillis();
+        long ts_offset = (SimpleTimeZone.getDefault().getOffset(ts));
+
+        byte length = 13;
+        ByteBuffer buf = ByteBuffer.allocate(length);
+        buf.order(ByteOrder.LITTLE_ENDIAN);
+
+        buf.put(COMMAND_PREFIX);
+        buf.put(length);
+        buf.put(COMMAND);
+        buf.put((byte) 0x14);
+
+        final Calendar calendar = DateTimeUtils.getCalendarUTC();
+        buf.put((byte) ((calendar.get(Calendar.YEAR) - 1900) & 0xff));
+        buf.put((byte) calendar.get(Calendar.MONTH));
+        buf.put((byte) calendar.get(Calendar.DAY_OF_MONTH));
+        buf.put((byte) calendar.get(Calendar.HOUR_OF_DAY));
+        buf.put((byte) calendar.get(Calendar.MINUTE));
+        buf.put((byte) calendar.get(Calendar.SECOND));
+        buf.putShort((short) (ts_offset / 60000));
+
+        buf.put(getXORChecksum(buf.array()));
+        return buf.array();
+    }
+
+    private byte[] encodeDischargeIntervalsFromPreferences() {
+        Prefs devicePrefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress()));
+        if (devicePrefs.getBoolean(PREF_BATTERY_DISCHARGE_MANAUAL, true)) {
+            int nr_invervals = (firmwareVersion >= 220) ? 5 : 3; // old firmware V210 only had 3 intervals it seems, so set only 3
+            int length = 5 + nr_invervals * 7;
+
+            ByteBuffer buf = ByteBuffer.allocate(length);
+            buf.order(ByteOrder.LITTLE_ENDIAN);
+            buf.put(COMMAND_PREFIX);
+            buf.put((byte) length);
+            buf.put(COMMAND); // set power parameters ?
+            buf.put((byte) 0x12); // set discharge power timers ?
+            for (int i = 1; i <= nr_invervals; i++) {
+                boolean enabled = devicePrefs.getBoolean("battery_discharge_interval" + i + "_enabled", false);
+                LocalTime startTime = devicePrefs.getLocalTime("battery_discharge_interval" + i + "_start", "00:00");
+                LocalTime endTime = devicePrefs.getLocalTime("battery_discharge_interval" + i + "_end", "00:00");
+                short watt = (short) devicePrefs.getInt("battery_discharge_interval" + i + "_watt", 80);
+                buf.put((byte) (enabled ? 0x01 : 0x00));
+                buf.put((byte) startTime.getHour());
+                buf.put((byte) startTime.getMinute());
+                buf.put((byte) endTime.getHour());
+                buf.put((byte) endTime.getMinute());
+                buf.putShort(watt);
+            }
+            buf.put(getXORChecksum(buf.array()));
+
+            return buf.array();
+        }
+        return null;
+    }
+
+    private byte getXORChecksum(byte[] command) {
+        byte checksum = 0;
+        for (byte b : command) {
+            checksum ^= b;
+        }
+        return checksum;
+    }
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/AbstractInfo.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/AbstractInfo.java
new file mode 100644
index 0000000..8b2493d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/AbstractInfo.java
@@ -0,0 +1,26 @@
+/*  Copyright (C) 2015-2024 Carsten Pfeiffer
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.miband;
+
+public class AbstractInfo {
+    protected final byte[] mData;
+
+    public AbstractInfo(byte[] data) {
+        mData = new byte[data.length];
+        System.arraycopy(data, 0, mData, 0, data.length);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/AbstractMi1FirmwareInfo.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/AbstractMi1FirmwareInfo.java
new file mode 100644
index 0000000..edfc63f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/AbstractMi1FirmwareInfo.java
@@ -0,0 +1,126 @@
+/*  Copyright (C) 2016-2024 Andreas Shimokawa, Carsten Pfeiffer, Daniele
+    Gobbetti
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.miband;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import androidx.annotation.NonNull;
+import nodomain.freeyourgadget.gadgetbridge.util.ArrayUtils;
+
+/**
+ * Some helper methods for Mi1 and Mi1A firmware.
+ */
+public abstract class AbstractMi1FirmwareInfo extends AbstractMiFirmwareInfo {
+    private static final Logger LOG = LoggerFactory.getLogger(AbstractMi1FirmwareInfo.class);
+
+    private static final byte[] SINGLE_FW_HEADER = new byte[]{
+            0,
+            (byte) 0x98,
+            0,
+            (byte) 0x20,
+            (byte) 0x89,
+            4,
+            0,
+            (byte) 0x20
+    };
+    private static final int SINGLE_FW_HEADER_OFFSET = 0;
+
+    private static final int MI1_FW_BASE_OFFSET = 1056;
+
+    protected AbstractMi1FirmwareInfo(@NonNull byte[] wholeFirmwareBytes) {
+        super(wholeFirmwareBytes);
+    }
+
+    @Override
+    public boolean isSingleMiBandFirmware() {
+        return true;
+    }
+
+    @Override
+    public int getFirmwareOffset() {
+        return 0;
+    }
+
+    @Override
+    public int getFirmwareLength() {
+        return wholeFirmwareBytes.length;
+    }
+
+    @Override
+    public int getFirmwareVersion() {
+        return (wholeFirmwareBytes[getOffsetFirmwareVersionMajor()] << 24)
+                | (wholeFirmwareBytes[getOffsetFirmwareVersionMinor()] << 16)
+                | (wholeFirmwareBytes[getOffsetFirmwareVersionRevision()] << 8)
+                | wholeFirmwareBytes[getOffsetFirmwareVersionBuild()];
+    }
+
+    private int getOffsetFirmwareVersionMajor() {
+        return MI1_FW_BASE_OFFSET + 3;
+    }
+
+    private int getOffsetFirmwareVersionMinor() {
+        return MI1_FW_BASE_OFFSET + 2;
+    }
+
+    private int getOffsetFirmwareVersionRevision() {
+        return MI1_FW_BASE_OFFSET + 1;
+    }
+
+    private int getOffsetFirmwareVersionBuild() {
+        return MI1_FW_BASE_OFFSET;
+    }
+
+    @Override
+    protected boolean isGenerallySupportedFirmware() {
+        try {
+            if (!isHeaderValid()) {
+                LOG.info("unrecognized header");
+                return false;
+            }
+            int majorVersion = getFirmwareVersionMajor();
+            if (majorVersion == getSupportedMajorVersion()) {
+                return true;
+            } else {
+                LOG.info("Only major version " + getSupportedMajorVersion() + " is supported: " + majorVersion);
+            }
+        } catch (IllegalArgumentException ex) {
+            LOG.warn("invalid firmware or bug: " + ex.getLocalizedMessage(), ex);
+        } catch (IndexOutOfBoundsException ex) {
+            LOG.warn("not supported firmware: " + ex.getLocalizedMessage(), ex);
+        }
+        return false;
+    }
+
+    @Override
+    protected boolean isHeaderValid() {
+        // TODO: not sure if this is a correct check!
+        return ArrayUtils.equals(wholeFirmwareBytes, SINGLE_FW_HEADER, SINGLE_FW_HEADER_OFFSET);
+    }
+
+    @Override
+    public void checkValid() throws IllegalArgumentException {
+        super.checkValid();
+
+        if (wholeFirmwareBytes.length < SINGLE_FW_HEADER.length) {
+            throw new IllegalArgumentException("firmware too small: " + wholeFirmwareBytes.length);
+        }
+    }
+
+    protected abstract int getSupportedMajorVersion();
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/AbstractMi1SFirmwareInfo.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/AbstractMi1SFirmwareInfo.java
new file mode 100644
index 0000000..f6e0711
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/AbstractMi1SFirmwareInfo.java
@@ -0,0 +1,38 @@
+/*  Copyright (C) 2016-2024 Carsten Pfeiffer, Daniele Gobbetti
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.miband;
+
+import androidx.annotation.NonNull;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public abstract class AbstractMi1SFirmwareInfo extends AbstractMiFirmwareInfo {
+
+    public AbstractMi1SFirmwareInfo(@NonNull byte[] wholeFirmwareBytes) {
+        super(wholeFirmwareBytes);
+    }
+
+    @Override
+    public boolean isGenerallyCompatibleWith(GBDevice device) {
+        return MiBandConst.MI_1S.equals(device.getModel());
+    }
+
+    @Override
+    public boolean isSingleMiBandFirmware() {
+        return false;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/AbstractMiFirmwareInfo.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/AbstractMiFirmwareInfo.java
new file mode 100644
index 0000000..fff9e5f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/AbstractMiFirmwareInfo.java
@@ -0,0 +1,141 @@
+/*  Copyright (C) 2016-2024 Andreas Shimokawa, Carsten Pfeiffer, Daniele
+    Gobbetti
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.miband;
+
+import java.util.Arrays;
+
+import androidx.annotation.NonNull;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public abstract class AbstractMiFirmwareInfo {
+
+    /**
+     * @param wholeFirmwareBytes
+     * @return
+     * @throws IllegalArgumentException when the data is not recognized as firmware data
+     */
+    public static
+    @NonNull
+    AbstractMiFirmwareInfo determineFirmwareInfoFor(byte[] wholeFirmwareBytes) {
+        AbstractMiFirmwareInfo[] candidates = getFirmwareInfoCandidatesFor(wholeFirmwareBytes);
+        if (candidates.length == 0) {
+            throw new IllegalArgumentException("Unsupported data (maybe not even a firmware?).");
+        }
+        if (candidates.length == 1) {
+            return candidates[0];
+        }
+        throw new IllegalArgumentException("don't know for which device the firmware is, matches multiple devices");
+    }
+
+    private static AbstractMiFirmwareInfo[] getFirmwareInfoCandidatesFor(byte[] wholeFirmwareBytes) {
+        if (MiBandSupport.MI_1A_HR_FW_UPDATE_TEST_MODE_ENABLED) {
+            TestMi1AFirmwareInfo info = TestMi1AFirmwareInfo.getInstance(wholeFirmwareBytes);
+            if (info != null) {
+                return new AbstractMiFirmwareInfo[]{info};
+            }
+        }
+
+        AbstractMiFirmwareInfo[] candidates = new AbstractMiFirmwareInfo[3];
+        int i = 0;
+        Mi1FirmwareInfo mi1Info = Mi1FirmwareInfo.getInstance(wholeFirmwareBytes);
+        if (mi1Info != null) {
+            candidates[i++] = mi1Info;
+        }
+        Mi1AFirmwareInfo mi1aInfo = Mi1AFirmwareInfo.getInstance(wholeFirmwareBytes);
+        if (mi1aInfo != null) {
+            candidates[i++] = mi1aInfo;
+        }
+        Mi1SFirmwareInfo mi1sInfo = Mi1SFirmwareInfo.getInstance(wholeFirmwareBytes);
+        if (mi1sInfo != null) {
+            candidates[i++] = mi1sInfo;
+        }
+        return Arrays.copyOfRange(candidates, 0, i);
+    }
+
+    @NonNull
+    protected byte[] wholeFirmwareBytes;
+
+    public AbstractMiFirmwareInfo(@NonNull byte[] wholeFirmwareBytes) {
+        this.wholeFirmwareBytes = wholeFirmwareBytes;
+    }
+
+    public abstract int getFirmwareOffset();
+
+    public abstract int getFirmwareLength();
+
+    public abstract int getFirmwareVersion();
+
+    /**
+     * Returns true if the firmware data is recognized as such and can be
+     * handled by this instance. No further sanity checks are done at this point.
+     */
+    protected abstract boolean isGenerallySupportedFirmware();
+
+    /**
+     * This method checks whether the firmware data is recognized as such and can be handled
+     * by this instance. It will be called by #isGenerallySupportedFirmware() in order to check
+     * whether this instance can be used at all or shall be thrown away.
+     */
+    protected abstract boolean isHeaderValid();
+
+    /**
+     * Checks whether this instance, with the provided firmware data is compatible with the
+     * given device. Must be called to avoid installing Mi1 firmware on Mi1A, for example.
+     *
+     * @param device
+     */
+    public abstract boolean isGenerallyCompatibleWith(GBDevice device);
+
+    @NonNull
+    public byte[] getFirmwareBytes() {
+        return Arrays.copyOfRange(wholeFirmwareBytes, getFirmwareOffset(), getFirmwareOffset() + getFirmwareLength());
+    }
+
+    public int getFirmwareVersionMajor() {
+        int version = getFirmwareVersion();
+        if (version > 0) {
+            return (version >> 24);
+        }
+        throw new IllegalArgumentException("bad firmware version: " + version);
+    }
+
+    public abstract boolean isSingleMiBandFirmware();
+
+    /**
+     * Performs a thorough sanity check of the firmware data and throws IllegalArgumentException
+     * if there's any problem with it.
+     *
+     * @throws IllegalArgumentException
+     */
+    public void checkValid() throws IllegalArgumentException {
+    }
+
+    public AbstractMiFirmwareInfo getFirst() {
+        if (isSingleMiBandFirmware()) {
+            return this;
+        }
+        throw new UnsupportedOperationException(getClass().getName() + " must override getFirst() and getSecond()");
+    }
+
+    public AbstractMiFirmwareInfo getSecond() {
+        if (isSingleMiBandFirmware()) {
+            throw new UnsupportedOperationException(getClass().getName() + " only supports on firmware");
+        }
+        throw new UnsupportedOperationException(getClass().getName() + " must override getFirst() and getSecond()");
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/BatteryInfo.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/BatteryInfo.java
new file mode 100644
index 0000000..cb11a54
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/BatteryInfo.java
@@ -0,0 +1,82 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, Carsten Pfeiffer, Daniele
+    Gobbetti
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.miband;
+
+import java.util.GregorianCalendar;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandDateConverter;
+import nodomain.freeyourgadget.gadgetbridge.model.BatteryState;
+
+public class BatteryInfo extends AbstractInfo {
+    public static final byte DEVICE_BATTERY_NORMAL = 0;
+    public static final byte DEVICE_BATTERY_LOW = 1;
+    public static final byte DEVICE_BATTERY_CHARGING = 2;
+    public static final byte DEVICE_BATTERY_CHARGING_FULL = 3;
+    public static final byte DEVICE_BATTERY_CHARGE_OFF = 4;
+
+    public BatteryInfo(byte[] data) {
+        super(data);
+    }
+
+    public int getLevelInPercent() {
+        if (mData.length >= 1) {
+            return mData[0];
+        }
+        return 50; // actually unknown
+    }
+
+    public BatteryState getState() {
+        if (mData.length >= 10) {
+            int value = mData[9];
+            switch (value) {
+                case DEVICE_BATTERY_NORMAL:
+                    return BatteryState.BATTERY_NORMAL;
+                case DEVICE_BATTERY_LOW:
+                    return BatteryState.BATTERY_LOW;
+                case DEVICE_BATTERY_CHARGING:
+                    return BatteryState.BATTERY_CHARGING;
+                case DEVICE_BATTERY_CHARGING_FULL:
+                    return BatteryState.BATTERY_CHARGING_FULL;
+                case DEVICE_BATTERY_CHARGE_OFF:
+                    return BatteryState.BATTERY_NOT_CHARGING_FULL;
+            }
+        }
+        return BatteryState.UNKNOWN;
+    }
+
+    public GregorianCalendar getLastChargeTime(String deviceAddress) {
+        GregorianCalendar lastCharge = MiBandDateConverter.createCalendar();
+
+        if (mData.length >= 10) {
+            lastCharge = MiBandDateConverter.rawBytesToCalendar(new byte[]{
+                            mData[1], mData[2], mData[3], mData[4], mData[5], mData[6]},
+                    deviceAddress
+            );
+        }
+
+        return lastCharge;
+    }
+
+    public int getNumCharges() {
+        if (mData.length >= 10) {
+            return ((0xff & mData[7]) | ((0xff & mData[8]) << 8));
+
+        }
+        return -1;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/CheckAuthenticationNeededAction.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/CheckAuthenticationNeededAction.java
new file mode 100644
index 0000000..f5448c9
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/CheckAuthenticationNeededAction.java
@@ -0,0 +1,41 @@
+/*  Copyright (C) 2016-2024 Carsten Pfeiffer
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.miband;
+
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.AbortTransactionAction;
+
+public class CheckAuthenticationNeededAction extends AbortTransactionAction {
+    private final GBDevice mDevice;
+
+    public CheckAuthenticationNeededAction(GBDevice device) {
+        super();
+        mDevice = device;
+    }
+
+    @Override
+    protected boolean shouldAbort() {
+        // the state is set in MiBandSupport.handleNotificationNotif()
+        switch (mDevice.getState()) {
+            case AUTHENTICATION_REQUIRED: // fall through
+            case AUTHENTICATING:
+                return true; // abort the whole thing
+            default:
+                return false;
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/CompositeMiFirmwareInfo.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/CompositeMiFirmwareInfo.java
new file mode 100644
index 0000000..b11123a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/CompositeMiFirmwareInfo.java
@@ -0,0 +1,112 @@
+/*  Copyright (C) 2016-2024 Carsten Pfeiffer
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.miband;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Abstract Mi firmware info class with two child info instances.
+ */
+public abstract class CompositeMiFirmwareInfo<T extends AbstractMiFirmwareInfo> extends AbstractMiFirmwareInfo {
+    private static final Logger LOG = LoggerFactory.getLogger(CompositeMiFirmwareInfo.class);
+
+    private final T fw1Info;
+    private final T fw2Info;
+
+    protected CompositeMiFirmwareInfo(byte[] wholeFirmwareBytes, T info1, T info2) {
+        super(wholeFirmwareBytes);
+        fw1Info = info1;
+        fw2Info = info2;
+    }
+
+    @Override
+    public void checkValid() throws IllegalArgumentException {
+        super.checkValid();
+
+        if (getFirst().getFirmwareOffset() == getSecond().getFirmwareOffset()) {
+            throw new IllegalArgumentException("Illegal firmware offsets: " + getLengthsOffsetsString());
+        }
+        if (getFirst().getFirmwareOffset() < 0 || getSecond().getFirmwareOffset() < 0
+                || getFirst().getFirmwareLength() <= 0 || getSecond().getFirmwareLength() <= 0) {
+            throw new IllegalArgumentException("Illegal firmware offsets/lengths: " + getLengthsOffsetsString());
+        }
+
+        int firstEndIndex = getFirst().getFirmwareOffset() + getFirst().getFirmwareLength();
+        if (getSecond().getFirmwareOffset() < firstEndIndex) {
+            throw new IllegalArgumentException("Invalid firmware, second fw starts before first fw ends: " + firstEndIndex + "," + getSecond().getFirmwareOffset());
+        }
+        int secondEndIndex = getSecond().getFirmwareOffset();
+        if (wholeFirmwareBytes.length < firstEndIndex || wholeFirmwareBytes.length < secondEndIndex) {
+            throw new IllegalArgumentException("Invalid firmware size, or invalid offsets/lengths: " + getLengthsOffsetsString());
+        }
+
+        getFirst().checkValid();
+        getSecond().checkValid();
+    }
+
+    protected String getLengthsOffsetsString() {
+        return getFirst().getFirmwareOffset() + "," + getFirst().getFirmwareLength()
+                + "; "
+                + getSecond().getFirmwareOffset() + "," + getSecond().getFirmwareLength();
+    }
+
+    @Override
+    public T getFirst() {
+        return fw1Info;
+    }
+
+    @Override
+    public T getSecond() {
+        return fw2Info;
+    }
+
+    @Override
+    protected boolean isGenerallySupportedFirmware() {
+        try {
+            if (!isHeaderValid()) {
+                LOG.info("unrecognized header");
+                return false;
+            }
+            return fw1Info.isGenerallySupportedFirmware()
+                    && fw2Info.isGenerallySupportedFirmware()
+                    && fw1Info.getFirmwareBytes().length > 0
+                    && fw2Info.getFirmwareBytes().length > 0;
+        } catch (IndexOutOfBoundsException ex) {
+            LOG.warn("invalid firmware or bug: " + ex.getLocalizedMessage(), ex);
+            return false;
+        } catch (IllegalArgumentException ex) {
+            LOG.warn("not supported 1S firmware: " + ex.getLocalizedMessage(), ex);
+            return false;
+        }
+    }
+
+    @Override
+    public int getFirmwareOffset() {
+        throw new UnsupportedOperationException("call this method on getFirmwareXInfo()");
+    }
+
+    @Override
+    public int getFirmwareLength() {
+        throw new UnsupportedOperationException("call this method on getFirmwareXInfo()");
+    }
+
+    @Override
+    public int getFirmwareVersion() {
+        throw new UnsupportedOperationException("call this method on getFirmwareXInfo()");
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/DeviceInfo.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/DeviceInfo.java
new file mode 100644
index 0000000..262fd14
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/DeviceInfo.java
@@ -0,0 +1,155 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, Carsten Pfeiffer, Sergey
+    Trofimov
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.miband;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst;
+import nodomain.freeyourgadget.gadgetbridge.util.CheckSums;
+
+public class DeviceInfo extends AbstractInfo {
+    public final String deviceId;
+    public final int profileVersion;
+    /**
+     * Mi Band firmware version identifier
+     */
+    public final int fwVersion;
+    public final int hwVersion;
+    public final int feature;
+    public final int appearance;
+    /**
+     * Heart rate firmware version identifier
+     */
+    public final int fw2Version;
+    private boolean test1AHRMode;
+
+
+    private boolean isChecksumCorrect(byte[] data) {
+        int crc8 = CheckSums.getCRC8(new byte[]{data[0], data[1], data[2], data[3], data[4], data[5], data[6]});
+        return (data[7] & 255) == (crc8 ^ data[3] & 255);
+    }
+
+    public DeviceInfo(byte[] data) {
+        super(data);
+
+        if ((data.length == 16 || data.length == 20) && isChecksumCorrect(data)) {
+            deviceId = String.format("%02X%02X%02X%02X%02X%02X%02X%02X", data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7]);
+            profileVersion = getInt(data, 8);
+            fwVersion = getInt(data, 12);
+            hwVersion = data[6] & 255;
+            appearance = data[5] & 255;
+            feature = data[4] & 255;
+            if (data.length == 20) {
+                int s = 0;
+                for (int i = 0; i < 4; ++i) {
+                    s |= (data[16 + i] & 255) << i * 8;
+                }
+                fw2Version = s;
+            } else {
+                fw2Version = -1;
+            }
+        } else {
+            deviceId = "crc error";
+            profileVersion = -1;
+            fwVersion = -1;
+            hwVersion = -1;
+            feature = -1;
+            appearance = -1;
+            fw2Version = -1;
+        }
+    }
+
+    public static int getInt(byte[] data, int from, int len) {
+        int ret = 0;
+        for (int i = 0; i < len; ++i) {
+            ret |= (data[from + i] & 255) << i * 8;
+        }
+        return ret;
+    }
+
+    private int getInt(byte[] data, int from) {
+        return getInt(data, from, 4);
+    }
+
+    public int getFirmwareVersion() {
+        return fwVersion;
+    }
+
+    public int getHeartrateFirmwareVersion() {
+        if (test1AHRMode) {
+            return fwVersion;
+        }
+        return fw2Version;
+    }
+
+    public int getProfileVersion() {
+        return profileVersion;
+    }
+
+    public void setTest1AHRMode(boolean enableTestMode) {
+        test1AHRMode = enableTestMode;
+    }
+
+    public boolean supportsHeartrate() {
+        return isMili1S() || (test1AHRMode && isMili1A());
+    }
+
+    @Override
+    public String toString() {
+        return "DeviceInfo{" +
+                "deviceId='" + deviceId + '\'' +
+                ", profileVersion=" + profileVersion +
+                ", fwVersion=" + fwVersion +
+                ", hwVersion=" + hwVersion +
+                ", feature=" + feature +
+                ", appearance=" + appearance +
+                ", fw2Version (hr)=" + fw2Version +
+                '}';
+    }
+
+    public boolean isMili1() {
+        return hwVersion == 2 || (feature == 0 && appearance == 0 && hwVersion == 8 && fw2Version == -1);
+    }
+
+    public boolean isMili1A() {
+        return feature == 5 && appearance == 0 || feature == 0 && hwVersion == 208;
+    }
+
+    public boolean isMili1S() {
+        // TODO: this is probably not quite correct, but hopefully sufficient for early 1S support
+        return (feature == 4 && appearance == 0) || hwVersion == 4;
+    }
+
+    public boolean isAmazFit() {
+        return hwVersion == 6;
+    }
+
+    public String getHwVersion() {
+        if (isMili1()) {
+            return MiBandConst.MI_1;
+        }
+        if (isMili1A()) {
+            return MiBandConst.MI_1A;
+        }
+        if (isMili1S()) {
+            return MiBandConst.MI_1S;
+        }
+        if (isAmazFit()) {
+            return MiBandConst.MI_AMAZFIT;
+        }
+        return "?";
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/Mi1AFirmwareInfo.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/Mi1AFirmwareInfo.java
new file mode 100644
index 0000000..49b217e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/Mi1AFirmwareInfo.java
@@ -0,0 +1,52 @@
+/*  Copyright (C) 2016-2024 Carsten Pfeiffer, Daniele Gobbetti
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.miband;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import androidx.annotation.NonNull;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class Mi1AFirmwareInfo extends AbstractMi1FirmwareInfo {
+    private static final Logger LOG = LoggerFactory.getLogger(Mi1AFirmwareInfo.class);
+
+    public static Mi1AFirmwareInfo getInstance(byte[] wholeFirmwareBytes) {
+        Mi1AFirmwareInfo info = new Mi1AFirmwareInfo(wholeFirmwareBytes);
+        if (info.isGenerallySupportedFirmware()) {
+            return info;
+        }
+        LOG.info("firmware not supported");
+        return null;
+    }
+
+    protected Mi1AFirmwareInfo(@NonNull byte[] wholeFirmwareBytes) {
+        super(wholeFirmwareBytes);
+    }
+
+    @Override
+    protected int getSupportedMajorVersion() {
+        return 5;
+    }
+
+    @Override
+    public boolean isGenerallyCompatibleWith(GBDevice device) {
+        String hwVersion = device.getModel();
+        return MiBandConst.MI_1A.equals(hwVersion);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/Mi1FirmwareInfo.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/Mi1FirmwareInfo.java
new file mode 100644
index 0000000..23ff085
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/Mi1FirmwareInfo.java
@@ -0,0 +1,52 @@
+/*  Copyright (C) 2016-2024 Carsten Pfeiffer, Daniele Gobbetti
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.miband;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import androidx.annotation.NonNull;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class Mi1FirmwareInfo extends AbstractMi1FirmwareInfo {
+    private static final Logger LOG = LoggerFactory.getLogger(Mi1FirmwareInfo.class);
+
+    public static Mi1FirmwareInfo getInstance(byte[] wholeFirmwareBytes) {
+        Mi1FirmwareInfo info = new Mi1FirmwareInfo(wholeFirmwareBytes);
+        if (info.isGenerallySupportedFirmware()) {
+            return info;
+        }
+        LOG.info("firmware not supported");
+        return null;
+    }
+
+    protected Mi1FirmwareInfo(@NonNull byte[] wholeFirmwareBytes) {
+        super(wholeFirmwareBytes);
+    }
+
+    @Override
+    protected int getSupportedMajorVersion() {
+        return 1;
+    }
+
+    @Override
+    public boolean isGenerallyCompatibleWith(GBDevice device) {
+        String hwVersion = device.getModel();
+        return MiBandConst.MI_1.equals(hwVersion);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/Mi1SFirmwareInfo.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/Mi1SFirmwareInfo.java
new file mode 100644
index 0000000..d929b6b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/Mi1SFirmwareInfo.java
@@ -0,0 +1,72 @@
+/*  Copyright (C) 2016-2024 Andreas Shimokawa, Carsten Pfeiffer, Daniele
+    Gobbetti
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.miband;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import androidx.annotation.Nullable;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.util.ArrayUtils;
+
+/**
+ * FW1 is Mi Band firmware
+ * FW2 is heartrate firmware
+ */
+public class Mi1SFirmwareInfo extends CompositeMiFirmwareInfo {
+    private static final Logger LOG = LoggerFactory.getLogger(Mi1SFirmwareInfo.class);
+
+    private static final byte[] DOUBLE_FW_HEADER = new byte[]{
+            (byte) 0x78,
+            (byte) 0x75,
+            (byte) 0x63,
+            (byte) 0x6b
+    };
+    private static final int DOUBLE_FW_HEADER_OFFSET = 0;
+
+    private Mi1SFirmwareInfo(byte[] wholeFirmwareBytes) {
+        super(wholeFirmwareBytes, new Mi1SFirmwareInfoFW1(wholeFirmwareBytes), new Mi1SFirmwareInfoFW2(wholeFirmwareBytes));
+    }
+
+    @Override
+    public boolean isGenerallyCompatibleWith(GBDevice device) {
+        return MiBandConst.MI_1S.equals(device.getModel());
+    }
+
+    @Override
+    public boolean isSingleMiBandFirmware() {
+        return false;
+    }
+
+    @Override
+    protected boolean isHeaderValid() {
+        // TODO: not sure if this is a correct check!
+        return ArrayUtils.equals(wholeFirmwareBytes, DOUBLE_FW_HEADER, DOUBLE_FW_HEADER_OFFSET);
+    }
+
+    @Nullable
+    public static Mi1SFirmwareInfo getInstance(byte[] wholeFirmwareBytes) {
+        Mi1SFirmwareInfo info = new Mi1SFirmwareInfo(wholeFirmwareBytes);
+        if (info.isGenerallySupportedFirmware()) {
+            return info;
+        }
+        LOG.info("firmware not supported");
+        return null;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/Mi1SFirmwareInfoFW1.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/Mi1SFirmwareInfoFW1.java
new file mode 100644
index 0000000..ee0e0fb
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/Mi1SFirmwareInfoFW1.java
@@ -0,0 +1,80 @@
+/*  Copyright (C) 2016-2024 Andreas Shimokawa, Carsten Pfeiffer, Daniele
+    Gobbetti
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.miband;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import androidx.annotation.NonNull;
+
+/**
+ * FW1 is Mi Band firmware
+ * FW2 is heartrate firmware
+ */
+public class Mi1SFirmwareInfoFW1 extends AbstractMi1SFirmwareInfo {
+    private static final Logger LOG = LoggerFactory.getLogger(Mi1SFirmwareInfoFW1.class);
+    private static final int MI1S_FW_BASE_OFFSET = 1092;
+
+    Mi1SFirmwareInfoFW1(@NonNull byte[] wholeFirmwareBytes) {
+        super(wholeFirmwareBytes);
+    }
+
+    @Override
+    protected boolean isHeaderValid() {
+        return true;
+    }
+
+    @Override
+    public int getFirmwareOffset() {
+        return (wholeFirmwareBytes[12] & 255) << 24
+                | (wholeFirmwareBytes[13] & 255) << 16
+                | (wholeFirmwareBytes[14] & 255) << 8
+                | (wholeFirmwareBytes[15] & 255);
+    }
+
+    @Override
+    public int getFirmwareLength() {
+        return (wholeFirmwareBytes[16] & 255) << 24
+                | (wholeFirmwareBytes[17] & 255) << 16
+                | (wholeFirmwareBytes[18] & 255) << 8
+                | (wholeFirmwareBytes[19] & 255);
+    }
+
+    @Override
+    public int getFirmwareVersion() {
+        return (wholeFirmwareBytes[8] & 255) << 24
+                | (wholeFirmwareBytes[9] & 255) << 16
+                | (wholeFirmwareBytes[10] & 255) << 8
+                | wholeFirmwareBytes[11] & 255;
+    }
+
+    @Override
+    protected boolean isGenerallySupportedFirmware() {
+        try {
+            int majorVersion = getFirmwareVersionMajor();
+            if (majorVersion == 4) {
+                return true;
+            } else {
+                LOG.warn("Only major version 4 is supported for 1S fw1: " + majorVersion);
+            }
+        } catch (IllegalArgumentException ex) {
+            LOG.warn("not supported 1S firmware 1: " + ex.getLocalizedMessage(), ex);
+        }
+        return false;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/Mi1SFirmwareInfoFW2.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/Mi1SFirmwareInfoFW2.java
new file mode 100644
index 0000000..43cd1f3
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/Mi1SFirmwareInfoFW2.java
@@ -0,0 +1,79 @@
+/*  Copyright (C) 2016-2024 Andreas Shimokawa, Carsten Pfeiffer, Daniele
+    Gobbetti
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.miband;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import androidx.annotation.NonNull;
+
+/**
+ * FW1 is Mi Band firmware
+ * FW2 is heartrate firmware
+ */
+public class Mi1SFirmwareInfoFW2 extends AbstractMi1SFirmwareInfo {
+    private static final Logger LOG = LoggerFactory.getLogger(Mi1SFirmwareInfoFW2.class);
+
+    Mi1SFirmwareInfoFW2(@NonNull byte[] wholeFirmwareBytes) {
+        super(wholeFirmwareBytes);
+    }
+
+    @Override
+    protected boolean isHeaderValid() {
+        return true;
+    }
+
+    @Override
+    public int getFirmwareOffset() {
+        return (wholeFirmwareBytes[26] & 255) << 24
+                | (wholeFirmwareBytes[27] & 255) << 16
+                | (wholeFirmwareBytes[28] & 255) << 8
+                | (wholeFirmwareBytes[29] & 255);
+    }
+
+    @Override
+    public int getFirmwareLength() {
+        return (wholeFirmwareBytes[30] & 255) << 24
+                | (wholeFirmwareBytes[31] & 255) << 16
+                | (wholeFirmwareBytes[32] & 255) << 8
+                | (wholeFirmwareBytes[33] & 255);
+    }
+
+    @Override
+    protected boolean isGenerallySupportedFirmware() {
+        try {
+            int majorVersion = getFirmwareVersionMajor();
+            if (majorVersion == 1) {
+                return true;
+            } else {
+                LOG.warn("Only major version 1 is supported for 1S fw2: " + majorVersion);
+            }
+        } catch (IllegalArgumentException ex) {
+            LOG.warn("not supported 1S firmware 2: " + ex.getLocalizedMessage(), ex);
+        }
+        return false;
+    }
+
+    @Override
+    public int getFirmwareVersion() {
+        return (wholeFirmwareBytes[22] & 255) << 24
+                | (wholeFirmwareBytes[23] & 255) << 16
+                | (wholeFirmwareBytes[24] & 255) << 8
+                | wholeFirmwareBytes[25] & 255;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/MiBandSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/MiBandSupport.java
new file mode 100644
index 0000000..b0ea477
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/MiBandSupport.java
@@ -0,0 +1,1287 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, Arjan Schrijver, atkyritsis,
+    Carsten Pfeiffer, Christian Fischer, Daniele Gobbetti, Dmitry Markin,
+    freezed-or-frozen, JohnnySun, Jos茅 Rebelo, Julien Pivotto, Kasha, Sebastian
+    Kranz, Sergey Trofimov, Steffen Liebergeld, Toby Murray
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.miband;
+
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.content.Intent;
+import android.net.Uri;
+import android.widget.Toast;
+
+import androidx.annotation.Nullable;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.List;
+import java.util.UUID;
+
+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.GBDeviceEventBatteryInfo;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo;
+import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandDateConverter;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandService;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.VibrationProfile;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.entities.MiBandActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.entities.User;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice.State;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
+import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
+import nodomain.freeyourgadget.gadgetbridge.util.calendar.CalendarEvent;
+import nodomain.freeyourgadget.gadgetbridge.util.calendar.CalendarManager;
+import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
+import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.NotificationType;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEAction;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.GattCharacteristic;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.GattService;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.AbortTransactionAction;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.ConditionalWriteAction;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.WriteAction;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotification.AlertCategory;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.common.SimpleNotification;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.FetchActivityOperation;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.UpdateFirmwareOperation;
+import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol;
+import nodomain.freeyourgadget.gadgetbridge.util.AlarmUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+import nodomain.freeyourgadget.gadgetbridge.util.NotificationUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.DEFAULT_VALUE_FLASH_COLOUR;
+import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.DEFAULT_VALUE_FLASH_COUNT;
+import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.DEFAULT_VALUE_FLASH_DURATION;
+import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.DEFAULT_VALUE_FLASH_ORIGINAL_COLOUR;
+import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.DEFAULT_VALUE_VIBRATION_COUNT;
+import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.DEFAULT_VALUE_VIBRATION_DURATION;
+import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.DEFAULT_VALUE_VIBRATION_PAUSE;
+import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.DEFAULT_VALUE_VIBRATION_PROFILE;
+import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.FLASH_COLOUR;
+import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.FLASH_COUNT;
+import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.FLASH_DURATION;
+import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.FLASH_ORIGINAL_COLOUR;
+import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.VIBRATION_COUNT;
+import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.VIBRATION_DURATION;
+import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.VIBRATION_PAUSE;
+import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.VIBRATION_PROFILE;
+import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.getNotificationPrefIntValue;
+import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.getNotificationPrefStringValue;
+
+public class MiBandSupport extends AbstractBTLEDeviceSupport {
+
+    private static final Logger LOG = LoggerFactory.getLogger(MiBandSupport.class);
+    /**
+     * This is just for temporary testing of Mi1A double firmware update.
+     * DO NOT SET TO TRUE UNLESS YOU KNOW WHAT YOU'RE DOING!
+     */
+    public static final boolean MI_1A_HR_FW_UPDATE_TEST_MODE_ENABLED = false;
+    private volatile boolean telephoneRinging;
+    private volatile boolean isLocatingDevice;
+    private volatile boolean isReadingSensorData;
+
+    private DeviceInfo mDeviceInfo;
+
+    private final GBDeviceEventVersionInfo versionCmd = new GBDeviceEventVersionInfo();
+    private final GBDeviceEventBatteryInfo batteryCmd = new GBDeviceEventBatteryInfo();
+    private RealtimeSamplesSupport realtimeSamplesSupport;
+    private boolean alarmClockRinging;
+
+    public MiBandSupport() {
+        super(LOG);
+        addSupportedService(GattService.UUID_SERVICE_GENERIC_ACCESS);
+        addSupportedService(GattService.UUID_SERVICE_GENERIC_ATTRIBUTE);
+        addSupportedService(MiBandService.UUID_SERVICE_MIBAND_SERVICE);
+        addSupportedService(MiBandService.UUID_SERVICE_HEART_RATE);
+        addSupportedService(GattService.UUID_SERVICE_IMMEDIATE_ALERT);
+    }
+
+    @Override
+    protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
+        builder.add(new SetDeviceStateAction(getDevice(), State.INITIALIZING, getContext()));
+        enableNotifications(builder, true)
+                .setLowLatency(builder)
+                .readDate(builder) // without reading the data, we get sporadic connection problems, especially directly after turning on BT
+                .pair(builder)
+                .requestDeviceInfo(builder)
+                .sendUserInfo(builder)
+                .checkAuthenticationNeeded(builder, getDevice())
+                .setWearLocation(builder)
+                .setHeartrateSleepSupport(builder)
+                .setFitnessGoal(builder)
+                .enableFurtherNotifications(builder, true)
+                .setCurrentTime(builder)
+                .requestBatteryInfo(builder)
+                .setHighLatency(builder)
+                .setInitialized(builder);
+        return builder;
+    }
+
+    private MiBandSupport readDate(TransactionBuilder builder) {
+        builder.read(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_DATE_TIME));
+        return this;
+    }
+
+    public MiBandSupport setLowLatency(TransactionBuilder builder) {
+        builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_LE_PARAMS), getLowLatency());
+        return this;
+    }
+
+    public MiBandSupport setHighLatency(TransactionBuilder builder) {
+        builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_LE_PARAMS), getHighLatency());
+        return this;
+    }
+
+    private MiBandSupport checkAuthenticationNeeded(TransactionBuilder builder, GBDevice device) {
+        builder.add(new CheckAuthenticationNeededAction(device));
+        return this;
+    }
+
+    /**
+     * Last action of initialization sequence. Sets the device to initialized.
+     * It is only invoked if all other actions were successfully run, so the device
+     * must be initialized, then.
+     *
+     * @param builder
+     */
+    private void setInitialized(TransactionBuilder builder) {
+        builder.add(new SetDeviceStateAction(getDevice(), State.INITIALIZED, getContext()));
+    }
+
+    // TODO: tear down the notifications on quit
+    private MiBandSupport enableNotifications(TransactionBuilder builder, boolean enable) {
+        builder.notify(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_NOTIFICATION), enable);
+        return this;
+    }
+
+    private MiBandSupport enableFurtherNotifications(TransactionBuilder builder, boolean enable) {
+        builder.notify(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_REALTIME_STEPS), enable)
+                .notify(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_ACTIVITY_DATA), enable)
+                .notify(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_BATTERY), enable)
+                .notify(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_SENSOR_DATA), enable);
+        // cannot use supportsHeartrate() here because we don't have that information yet
+        BluetoothGattCharacteristic heartrateCharacteristic = getCharacteristic(MiBandService.UUID_CHARACTERISTIC_HEART_RATE_MEASUREMENT);
+        if (heartrateCharacteristic != null) {
+            builder.notify(heartrateCharacteristic, enable);
+        }
+
+        return this;
+    }
+
+    @Override
+    public boolean useAutoConnect() {
+        return true;
+    }
+
+    @Override
+    public boolean connectFirstTime() {
+        for (int i = 0; i < 5; i++) {
+            if (connect()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public DeviceInfo getDeviceInfo() {
+        return mDeviceInfo;
+    }
+
+    private MiBandSupport sendDefaultNotification(TransactionBuilder builder, SimpleNotification simpleNotification, short repeat, BtLEAction extraAction) {
+        LOG.info("Sending notification to MiBand: (" + repeat + " times)");
+        NotificationStrategy strategy = getNotificationStrategy();
+        for (short i = 0; i < repeat; i++) {
+            strategy.sendDefaultNotification(builder, simpleNotification, extraAction);
+        }
+        return this;
+    }
+
+    /**
+     * Adds a custom notification to the given transaction builder
+     *  @param vibrationProfile specifies how and how often the Band shall vibrate.
+     * @param simpleNotification
+     * @param flashTimes
+     * @param flashColour
+     * @param originalColour
+     * @param flashDuration
+     * @param extraAction      an extra action to be executed after every vibration and flash sequence. Allows to abort the repetition, for example.
+     * @param builder
+     */
+    private MiBandSupport sendCustomNotification(VibrationProfile vibrationProfile, @Nullable SimpleNotification simpleNotification, int flashTimes, int flashColour, int originalColour, long flashDuration, BtLEAction extraAction, TransactionBuilder builder) {
+        getNotificationStrategy().sendCustomNotification(vibrationProfile, simpleNotification, flashTimes, flashColour, originalColour, flashDuration, extraAction, builder);
+        LOG.info("Sending notification to MiBand");
+        return this;
+    }
+
+    private NotificationStrategy getNotificationStrategy() {
+        if (mDeviceInfo == null) {
+            // not initialized yet?
+            return new NoNotificationStrategy();
+        }
+        if (mDeviceInfo.getFirmwareVersion() < MiBandFWHelper.FW_16779790) {
+            return new V1NotificationStrategy(this);
+        } else {
+            //use the new alert characteristic
+            return new V2NotificationStrategy(this);
+        }
+    }
+
+    static final byte[] reboot = new byte[]{MiBandService.COMMAND_REBOOT};
+    static final byte[] factoryReset = new byte[]{MiBandService.COMMAND_FACTORYRESET};
+
+    static final byte[] startHeartMeasurementManual = new byte[]{0x15, MiBandService.COMMAND_SET_HR_MANUAL, 1};
+    static final byte[] stopHeartMeasurementManual = new byte[]{0x15, MiBandService.COMMAND_SET_HR_MANUAL, 0};
+    static final byte[] startHeartMeasurementContinuous = new byte[]{0x15, MiBandService.COMMAND_SET__HR_CONTINUOUS, 1};
+    static final byte[] stopHeartMeasurementContinuous = new byte[]{0x15, MiBandService.COMMAND_SET__HR_CONTINUOUS, 0};
+    static final byte[] startHeartMeasurementSleep = new byte[]{0x15, MiBandService.COMMAND_SET_HR_SLEEP, 1};
+    static final byte[] stopHeartMeasurementSleep = new byte[]{0x15, MiBandService.COMMAND_SET_HR_SLEEP, 0};
+
+    static final byte[] startRealTimeStepsNotifications = new byte[]{MiBandService.COMMAND_SET_REALTIME_STEPS_NOTIFICATION, 1};
+    static final byte[] stopRealTimeStepsNotifications = new byte[]{MiBandService.COMMAND_SET_REALTIME_STEPS_NOTIFICATION, 0};
+
+    private static final byte[] startSensorRead = new byte[]{MiBandService.COMMAND_GET_SENSOR_DATA, 1};
+    private static final byte[] stopSensorRead = new byte[]{MiBandService.COMMAND_GET_SENSOR_DATA, 0};
+
+    /**
+     * Part of device initialization process. Do not call manually.
+     *
+     * @param builder
+     * @return
+     */
+    private MiBandSupport sendUserInfo(TransactionBuilder builder) {
+        LOG.debug("Writing User Info!");
+        // Use a custom action instead of just builder.write() because mDeviceInfo
+        // is set by handleDeviceInfo *after* this action is created.
+        builder.add(new BtLEAction(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_USER_INFO)) {
+            @Override
+            public boolean expectsResult() {
+                return true;
+            }
+
+            @Override
+            public boolean run(BluetoothGatt gatt) {
+                // at this point, mDeviceInfo should be set
+                return new WriteAction(getCharacteristic(),
+                        MiBandCoordinator.getAnyUserInfo(getDevice().getAddress()).getData(mDeviceInfo)
+                ).run(gatt);
+            }
+        });
+        return this;
+    }
+
+    private MiBandSupport requestBatteryInfo(TransactionBuilder builder) {
+        LOG.debug("Requesting Battery Info!");
+        BluetoothGattCharacteristic characteristic = getCharacteristic(MiBandService.UUID_CHARACTERISTIC_BATTERY);
+        builder.read(characteristic);
+        return this;
+    }
+
+    private MiBandSupport requestDeviceInfo(TransactionBuilder builder) {
+        LOG.debug("Requesting Device Info!");
+        BluetoothGattCharacteristic deviceInfo = getCharacteristic(MiBandService.UUID_CHARACTERISTIC_DEVICE_INFO);
+        builder.read(deviceInfo);
+        BluetoothGattCharacteristic deviceName = getCharacteristic(GattCharacteristic.UUID_CHARACTERISTIC_DEVICE_NAME);
+        builder.read(deviceName);
+        return this;
+    }
+
+   /* private MiBandSupport requestHRInfo(TransactionBuilder builder) {
+        LOG.debug("Requesting HR Info!");
+        BluetoothGattCharacteristic HRInfo = getCharacteristic(MiBandService.UUID_CHAR_HEART_RATE_MEASUREMENT);
+        builder.read(HRInfo);
+        BluetoothGattCharacteristic HR_Point = getCharacteristic(GattCharacteristic.UUID_CHARACTERISTIC_HEART_RATE_CONTROL_POINT);
+        builder.read(HR_Point);
+        return this;
+    }
+    *//**
+     * Part of HR test. Do not call manually.
+     *
+     * @param transaction
+     * @return
+     *//*
+    private MiBandSupport heartrate(TransactionBuilder transaction) {
+        LOG.info("Attempting to read HR ...");
+        BluetoothGattCharacteristic characteristic = getCharacteristic(MiBandService.UUID_CHAR_HEART_RATE_MEASUREMENT);
+        if (characteristic != null) {
+            transaction.write(characteristic, new byte[]{MiBandService.COMMAND_SET__HR_CONTINUOUS});
+        } else {
+            LOG.info("Unable to read HR from  MI device -- characteristic not available");
+        }
+        return this;
+    }*/
+
+    /**
+     * Part of device initialization process. Do not call manually.
+     *
+     * @param transaction
+     * @return
+     */
+    private MiBandSupport pair(TransactionBuilder transaction) {
+// this is apparently only needed to get a more strict bond between mobile and mi band,
+// e.g. such that Mi Fit and Gadgetbridge can coexist without needing to re-pair (with
+// full device-data-reset).
+// Unfortunately this extra pairing causes problems when bonding is not used/does not work
+// so we only do this when configured to keep data on the device
+
+        Prefs prefs = GBApplication.getDevicePrefs(gbDevice);
+        if (prefs.getBoolean("keep_activity_data_on_device", false)) {
+            LOG.info("Attempting to pair MI device...");
+            BluetoothGattCharacteristic characteristic = getCharacteristic(MiBandService.UUID_CHARACTERISTIC_PAIR);
+            if (characteristic != null) {
+                transaction.write(characteristic, new byte[]{2});
+            } else {
+                LOG.info("Unable to pair MI device -- characteristic not available");
+            }
+        }
+        return this;
+    }
+
+    /**
+     * Part of device initialization process. Do not call manually.
+     *
+     * @param transaction
+     * @return
+     */
+
+    private MiBandSupport setFitnessGoal(TransactionBuilder transaction) {
+        LOG.info("Attempting to set Fitness Goal...");
+        BluetoothGattCharacteristic characteristic = getCharacteristic(MiBandService.UUID_CHARACTERISTIC_CONTROL_POINT);
+        if (characteristic != null) {
+            int fitnessGoal = GBApplication.getPrefs().getInt(ActivityUser.PREF_USER_STEPS_GOAL, ActivityUser.defaultUserStepsGoal);
+            transaction.write(characteristic, new byte[]{
+                    MiBandService.COMMAND_SET_FITNESS_GOAL,
+                    0,
+                    (byte) (fitnessGoal & 0xff),
+                    (byte) ((fitnessGoal >>> 8) & 0xff)
+            });
+        } else {
+            LOG.info("Unable to set Fitness Goal");
+        }
+        return this;
+    }
+
+    /**
+     * Part of device initialization process. Do not call manually.
+     *
+     * @param transaction
+     * @return
+     */
+    private MiBandSupport setWearLocation(TransactionBuilder transaction) {
+        LOG.info("Attempting to set wear location...");
+        BluetoothGattCharacteristic characteristic = getCharacteristic(MiBandService.UUID_CHARACTERISTIC_CONTROL_POINT);
+        if (characteristic != null) {
+            transaction.add(new ConditionalWriteAction(characteristic) {
+                @Override
+                protected byte[] checkCondition() {
+                    if (getDeviceInfo() != null && getDeviceInfo().isAmazFit()) {
+                        return null;
+                    }
+                    int location = MiBandCoordinator.getWearLocation(getDevice().getAddress());
+                    return new byte[]{
+                            MiBandService.COMMAND_SET_WEAR_LOCATION,
+                            (byte) location
+                    };
+                }
+            });
+        } else {
+            LOG.info("Unable to set Wear Location");
+        }
+        return this;
+    }
+
+    @Override
+    public void onEnableHeartRateSleepSupport(boolean enable) {
+        try {
+            TransactionBuilder builder = performInitialized("enable heart rate sleep support: " + enable);
+            setHeartrateSleepSupport(builder);
+            builder.queue(getQueue());
+        } catch (IOException e) {
+            GB.toast(getContext(), "Error toggling heart rate sleep support: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
+        }
+    }
+
+    /**
+     * Part of device initialization process. Do not call manually.
+     *
+     * @param builder
+     */
+    private MiBandSupport setHeartrateSleepSupport(TransactionBuilder builder) {
+        BluetoothGattCharacteristic characteristic = getCharacteristic(MiBandService.UUID_CHARACTERISTIC_HEART_RATE_CONTROL_POINT);
+        if (characteristic != null) {
+            builder.add(new ConditionalWriteAction(characteristic) {
+                @Override
+                protected byte[] checkCondition() {
+                    if (!supportsHeartRate()) {
+                        return null;
+                    }
+                    if (MiBandCoordinator.getHeartrateSleepSupport(getDevice().getAddress())) {
+                        LOG.info("Enabling heartrate sleep support...");
+                        return startHeartMeasurementSleep;
+                    } else {
+                        LOG.info("Disabling heartrate sleep support...");
+                        return stopHeartMeasurementSleep;
+                    }
+                }
+            });
+        }
+        return this;
+    }
+
+    private void performDefaultNotification(String task, SimpleNotification simpleNotification, short repeat, BtLEAction extraAction) {
+        try {
+            TransactionBuilder builder = performInitialized(task);
+            sendDefaultNotification(builder, simpleNotification, repeat, extraAction);
+            builder.queue(getQueue());
+        } catch (IOException ex) {
+            LOG.error("Unable to send notification to MI device", ex);
+        }
+    }
+
+    private void performPreferredNotification(String task, @Nullable SimpleNotification simpleNotification, String notificationOrigin, BtLEAction extraAction) {
+        try {
+            TransactionBuilder builder = performInitialized(task);
+            Prefs prefs = getDevicePrefs();
+            int vibrateDuration = getPreferredVibrateDuration(notificationOrigin, prefs);
+            int vibratePause = getPreferredVibratePause(notificationOrigin, prefs);
+            short vibrateTimes = getPreferredVibrateCount(notificationOrigin, prefs);
+            VibrationProfile profile = getPreferredVibrateProfile(notificationOrigin, prefs, vibrateTimes);
+
+            int flashTimes = getPreferredFlashCount(notificationOrigin, prefs);
+            int flashColour = getPreferredFlashColour(notificationOrigin, prefs);
+            int originalColour = getPreferredOriginalColour(notificationOrigin, prefs);
+            int flashDuration = getPreferredFlashDuration(notificationOrigin, prefs);
+
+//            setLowLatency(builder);
+            sendCustomNotification(profile, simpleNotification, flashTimes, flashColour, originalColour, flashDuration, extraAction, builder);
+//            setHighLatency(builder);
+//            sendCustomNotification(vibrateDuration, vibrateTimes, vibratePause, flashTimes, flashColour, originalColour, flashDuration, builder);
+            builder.queue(getQueue());
+        } catch (IOException ex) {
+            LOG.error("Unable to send notification to MI device", ex);
+        }
+    }
+
+    private int getPreferredFlashDuration(String notificationOrigin, Prefs prefs) {
+        return getNotificationPrefIntValue(FLASH_DURATION, notificationOrigin, prefs, DEFAULT_VALUE_FLASH_DURATION);
+    }
+
+    private int getPreferredOriginalColour(String notificationOrigin, Prefs prefs) {
+        return getNotificationPrefIntValue(FLASH_ORIGINAL_COLOUR, notificationOrigin, prefs, DEFAULT_VALUE_FLASH_ORIGINAL_COLOUR);
+    }
+
+    private int getPreferredFlashColour(String notificationOrigin, Prefs prefs) {
+        return getNotificationPrefIntValue(FLASH_COLOUR, notificationOrigin, prefs, DEFAULT_VALUE_FLASH_COLOUR);
+    }
+
+    private int getPreferredFlashCount(String notificationOrigin, Prefs prefs) {
+        return getNotificationPrefIntValue(FLASH_COUNT, notificationOrigin, prefs, DEFAULT_VALUE_FLASH_COUNT);
+    }
+
+    private int getPreferredVibratePause(String notificationOrigin, Prefs prefs) {
+        return getNotificationPrefIntValue(VIBRATION_PAUSE, notificationOrigin, prefs, DEFAULT_VALUE_VIBRATION_PAUSE);
+    }
+
+    private short getPreferredVibrateCount(String notificationOrigin, Prefs prefs) {
+        return (short) Math.min(Short.MAX_VALUE, getNotificationPrefIntValue(VIBRATION_COUNT, notificationOrigin, prefs, DEFAULT_VALUE_VIBRATION_COUNT));
+    }
+
+    private int getPreferredVibrateDuration(String notificationOrigin, Prefs prefs) {
+        return getNotificationPrefIntValue(VIBRATION_DURATION, notificationOrigin, prefs, DEFAULT_VALUE_VIBRATION_DURATION);
+    }
+
+    private VibrationProfile getPreferredVibrateProfile(String notificationOrigin, Prefs prefs, short repeat) {
+        String profileId = getNotificationPrefStringValue(VIBRATION_PROFILE, notificationOrigin, prefs, DEFAULT_VALUE_VIBRATION_PROFILE);
+        return VibrationProfile.getProfile(profileId, repeat);
+    }
+
+    @Override
+    public void onSetAlarms(ArrayList<? extends Alarm> alarms) {
+        try {
+            BluetoothGattCharacteristic characteristic = getCharacteristic(MiBandService.UUID_CHARACTERISTIC_CONTROL_POINT);
+            TransactionBuilder builder = performInitialized("Set alarm");
+            boolean anyAlarmEnabled = false;
+            for (Alarm alarm : alarms) {
+                anyAlarmEnabled |= alarm.getEnabled();
+                queueAlarm(alarm, builder, characteristic);
+            }
+            builder.queue(getQueue());
+            if (anyAlarmEnabled) {
+                GB.toast(getContext(), getContext().getString(R.string.user_feedback_miband_set_alarms_ok), Toast.LENGTH_SHORT, GB.INFO);
+            } else {
+                GB.toast(getContext(), getContext().getString(R.string.user_feedback_all_alarms_disabled), Toast.LENGTH_SHORT, GB.INFO);
+            }
+        } catch (IOException ex) {
+            GB.toast(getContext(), getContext().getString(R.string.user_feedback_miband_set_alarms_failed), Toast.LENGTH_LONG, GB.ERROR, ex);
+        }
+    }
+
+    @Override
+    public void onNotification(NotificationSpec notificationSpec) {
+        if (notificationSpec.type == NotificationType.GENERIC_ALARM_CLOCK) {
+            onAlarmClock(notificationSpec);
+            return;
+        }
+
+        String origin = notificationSpec.type.getGenericType();
+        performPreferredNotification(origin + " received", null, origin, null);
+    }
+
+    private void onAlarmClock(NotificationSpec notificationSpec) {
+        alarmClockRinging = true;
+        AbortTransactionAction abortAction = new AbortTransactionAction() {
+            @Override
+            protected boolean shouldAbort() {
+                return !isAlarmClockRinging();
+            }
+        };
+        String message = NotificationUtils.getPreferredTextFor(notificationSpec, 40, 40, getContext());
+        SimpleNotification simpleNotification = new SimpleNotification(message, AlertCategory.HighPriorityAlert, null);
+        performPreferredNotification("alarm clock ringing", simpleNotification, MiBandConst.ORIGIN_ALARM_CLOCK, abortAction);
+    }
+
+    @Override
+    public void onDeleteNotification(int id) {
+        alarmClockRinging = false; // we should have the notificationtype at least to check
+    }
+
+    @Override
+    public void onSetTime() {
+        try {
+            TransactionBuilder builder = performInitialized("Set date and time");
+            setCurrentTime(builder);
+            builder.queue(getQueue());
+        } catch (IOException ex) {
+            LOG.error("Unable to set time on MI device", ex);
+        }
+        //TODO: once we have a common strategy for sending events (e.g. EventHandler), remove this call from here. Meanwhile it does no harm.
+        sendCalendarEvents();
+    }
+
+    /**
+     * Sets the current time to the Mi device using the given builder.
+     *
+     * @param builder
+     */
+    private MiBandSupport setCurrentTime(TransactionBuilder builder) {
+        Calendar now = GregorianCalendar.getInstance();
+        Date date = now.getTime();
+        LOG.info("Sending current time to Mi Band: " + DateTimeUtils.formatDate(date) + " (" + date.toGMTString() + ")");
+        byte[] nowBytes = MiBandDateConverter.calendarToRawBytes(now, gbDevice.getAddress());
+        byte[] time = new byte[]{
+                nowBytes[0],
+                nowBytes[1],
+                nowBytes[2],
+                nowBytes[3],
+                nowBytes[4],
+                nowBytes[5],
+                (byte) 0x0f,
+                (byte) 0x0f,
+                (byte) 0x0f,
+                (byte) 0x0f,
+                (byte) 0x0f,
+                (byte) 0x0f
+        };
+        BluetoothGattCharacteristic characteristic = getCharacteristic(MiBandService.UUID_CHARACTERISTIC_DATE_TIME);
+        if (characteristic != null) {
+            builder.write(characteristic, time);
+        } else {
+            LOG.info("Unable to set time -- characteristic not available");
+        }
+        return this;
+    }
+
+    @Override
+    public void onSetCallState(CallSpec callSpec) {
+        if (callSpec.command == CallSpec.CALL_INCOMING) {
+            telephoneRinging = true;
+            AbortTransactionAction abortAction = new AbortTransactionAction() {
+                @Override
+                protected boolean shouldAbort() {
+                    return !isTelephoneRinging();
+                }
+            };
+            String message = NotificationUtils.getPreferredTextFor(callSpec);
+            SimpleNotification simpleNotification = new SimpleNotification(message, AlertCategory.IncomingCall, null);
+            performPreferredNotification("incoming call", simpleNotification, MiBandConst.ORIGIN_INCOMING_CALL, abortAction);
+        } else if ((callSpec.command == CallSpec.CALL_START) || (callSpec.command == CallSpec.CALL_END)) {
+            telephoneRinging = false;
+        }
+    }
+
+    private boolean isAlarmClockRinging() {
+        // don't synchronize, this is not really important
+        return alarmClockRinging;
+    }
+    private boolean isTelephoneRinging() {
+        // don't synchronize, this is not really important
+        return telephoneRinging;
+    }
+
+    @Override
+    public void onReset(int flags) {
+        try {
+            TransactionBuilder builder = performInitialized("reset");
+            if ((flags & GBDeviceProtocol.RESET_FLAGS_FACTORY_RESET) != 0) {
+                builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_CONTROL_POINT), factoryReset);
+            } else {
+                builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_CONTROL_POINT), reboot);
+            }
+            builder.queue(getQueue());
+        } catch (IOException ex) {
+            LOG.error("Unable to reset", ex);
+        }
+    }
+
+    @Override
+    public void onHeartRateTest() {
+        if (supportsHeartRate()) {
+            try {
+                TransactionBuilder builder = performInitialized("HeartRateTest");
+                builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_HEART_RATE_CONTROL_POINT), stopHeartMeasurementContinuous);
+                builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_HEART_RATE_CONTROL_POINT), stopHeartMeasurementManual);
+                builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_HEART_RATE_CONTROL_POINT), startHeartMeasurementManual);
+                builder.queue(getQueue());
+            } catch (IOException ex) {
+                LOG.error("Unable to read HearRate in  MI1S", ex);
+            }
+        } else {
+            GB.toast(getContext(), "Heart rate is not supported on this device", Toast.LENGTH_LONG, GB.ERROR);
+        }
+    }
+
+    @Override
+    public void onEnableRealtimeHeartRateMeasurement(boolean enable) {
+        if (supportsHeartRate()) {
+            try {
+                TransactionBuilder builder = performInitialized("EnableRealtimeHeartRateMeasurement");
+                if (enable) {
+                    builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_HEART_RATE_CONTROL_POINT), stopHeartMeasurementManual);
+                    builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_HEART_RATE_CONTROL_POINT), startHeartMeasurementContinuous);
+                } else {
+                    builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_HEART_RATE_CONTROL_POINT), stopHeartMeasurementContinuous);
+                }
+                builder.queue(getQueue());
+                enableRealtimeSamplesTimer(enable);
+            } catch (IOException ex) {
+                LOG.error("Unable to enable realtime heart rate measurement in  MI1S", ex);
+            }
+        }
+    }
+
+    public boolean supportsHeartRate() {
+        return getDeviceInfo() != null && getDeviceInfo().supportsHeartrate();
+    }
+
+    @Override
+    public void onFindDevice(boolean start) {
+        isLocatingDevice = start;
+
+        if (start) {
+            AbortTransactionAction abortAction = new AbortTransactionAction() {
+                @Override
+                protected boolean shouldAbort() {
+                    return !isLocatingDevice;
+                }
+            };
+            SimpleNotification simpleNotification = new SimpleNotification(getContext().getString(R.string.find_device_you_found_it), AlertCategory.HighPriorityAlert, null);
+            performDefaultNotification("locating device", simpleNotification, (short) 255, abortAction);
+        }
+    }
+
+    @Override
+    public void onFetchRecordedData(int dataTypes) {
+        try {
+            new FetchActivityOperation(this).perform();
+        } catch (IOException ex) {
+            LOG.error("Unable to fetch MI activity data", ex);
+        }
+    }
+
+    @Override
+    public void onEnableRealtimeSteps(boolean enable) {
+        try {
+            BluetoothGattCharacteristic controlPoint = getCharacteristic(MiBandService.UUID_CHARACTERISTIC_CONTROL_POINT);
+            if (enable) {
+                TransactionBuilder builder = performInitialized("Read realtime steps");
+                builder.read(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_REALTIME_STEPS)).queue(getQueue());
+            }
+            performInitialized(enable ? "Enabling realtime steps notifications" : "Disabling realtime steps notifications")
+                    .write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_LE_PARAMS), enable ? getLowLatency() : getHighLatency())
+                    .write(controlPoint, enable ? startRealTimeStepsNotifications : stopRealTimeStepsNotifications).queue(getQueue());
+            enableRealtimeSamplesTimer(enable);
+        } catch (IOException e) {
+            LOG.error("Unable to change realtime steps notification to: " + enable, e);
+        }
+    }
+
+    private byte[] getHighLatency() {
+        int minConnectionInterval = 460;
+        int maxConnectionInterval = 500;
+        int latency = 0;
+        int timeout = 500;
+        int advertisementInterval = 0;
+
+        return getLatency(minConnectionInterval, maxConnectionInterval, latency, timeout, advertisementInterval);
+    }
+
+    private byte[] getLatency(int minConnectionInterval, int maxConnectionInterval, int latency, int timeout, int advertisementInterval) {
+        byte result[] = new byte[12];
+        result[0] = (byte) (minConnectionInterval & 0xff);
+        result[1] = (byte) (0xff & minConnectionInterval >> 8);
+        result[2] = (byte) (maxConnectionInterval & 0xff);
+        result[3] = (byte) (0xff & maxConnectionInterval >> 8);
+        result[4] = (byte) (latency & 0xff);
+        result[5] = (byte) (0xff & latency >> 8);
+        result[6] = (byte) (timeout & 0xff);
+        result[7] = (byte) (0xff & timeout >> 8);
+        result[8] = 0;
+        result[9] = 0;
+        result[10] = (byte) (advertisementInterval & 0xff);
+        result[11] = (byte) (0xff & advertisementInterval >> 8);
+
+        return result;
+    }
+
+    private byte[] getLowLatency() {
+        int minConnectionInterval = 39;
+        int maxConnectionInterval = 49;
+        int latency = 0;
+        int timeout = 500;
+        int advertisementInterval = 0;
+
+        return getLatency(minConnectionInterval, maxConnectionInterval, latency, timeout, advertisementInterval);
+    }
+
+    @Override
+    public void onInstallApp(Uri uri) {
+        try {
+            new UpdateFirmwareOperation(uri, this).perform();
+        } catch (IOException ex) {
+            GB.toast(getContext(), "Firmware cannot be installed: " + ex.getMessage(), Toast.LENGTH_LONG, GB.ERROR, ex);
+        }
+    }
+
+    @Override
+    public boolean onCharacteristicChanged(BluetoothGatt gatt,
+                                           BluetoothGattCharacteristic characteristic) {
+        super.onCharacteristicChanged(gatt, characteristic);
+
+        UUID characteristicUUID = characteristic.getUuid();
+        if (MiBandService.UUID_CHARACTERISTIC_BATTERY.equals(characteristicUUID)) {
+            handleBatteryInfo(characteristic.getValue(), BluetoothGatt.GATT_SUCCESS);
+            return true;
+        } else if (MiBandService.UUID_CHARACTERISTIC_NOTIFICATION.equals(characteristicUUID)) {
+            handleNotificationNotif(characteristic.getValue());
+            return true;
+        } else if (MiBandService.UUID_CHARACTERISTIC_REALTIME_STEPS.equals(characteristicUUID)) {
+            handleRealtimeSteps(characteristic.getValue());
+            return true;
+        } else if (MiBandService.UUID_CHARACTERISTIC_HEART_RATE_MEASUREMENT.equals(characteristicUUID)) {
+            handleHeartrate(characteristic.getValue());
+            return true;
+        } else if (MiBandService.UUID_CHARACTERISTIC_SENSOR_DATA.equals(characteristicUUID)) {
+            handleSensorData(characteristic.getValue());
+        } else {
+            LOG.info("Unhandled characteristic changed: " + characteristicUUID);
+            logMessageContent(characteristic.getValue());
+        }
+        return false;
+    }
+
+    @Override
+    public boolean onCharacteristicRead(BluetoothGatt gatt,
+                                        BluetoothGattCharacteristic characteristic, int status) {
+        super.onCharacteristicRead(gatt, characteristic, status);
+
+        UUID characteristicUUID = characteristic.getUuid();
+        if (MiBandService.UUID_CHARACTERISTIC_DEVICE_INFO.equals(characteristicUUID)) {
+            handleDeviceInfo(characteristic.getValue(), status);
+            return true;
+        } else if (GattCharacteristic.UUID_CHARACTERISTIC_DEVICE_NAME.equals(characteristicUUID)) {
+            handleDeviceName(characteristic.getValue(), status);
+            return true;
+        } else if (MiBandService.UUID_CHARACTERISTIC_BATTERY.equals(characteristicUUID)) {
+            handleBatteryInfo(characteristic.getValue(), status);
+            return true;
+        } else if (MiBandService.UUID_CHARACTERISTIC_HEART_RATE_MEASUREMENT.equals(characteristicUUID)) {
+            logHeartrate(characteristic.getValue(), status);
+            return true;
+        } else if (MiBandService.UUID_CHARACTERISTIC_DATE_TIME.equals(characteristicUUID)) {
+            logDate(characteristic.getValue(), status);
+            return true;
+        } else {
+            LOG.info("Unhandled characteristic read: " + characteristicUUID);
+            logMessageContent(characteristic.getValue());
+        }
+        return false;
+    }
+
+    @Override
+    public boolean onCharacteristicWrite(BluetoothGatt gatt,
+                                         BluetoothGattCharacteristic characteristic, int status) {
+        UUID characteristicUUID = characteristic.getUuid();
+        if (MiBandService.UUID_CHARACTERISTIC_PAIR.equals(characteristicUUID)) {
+            handlePairResult(characteristic.getValue(), status);
+            return true;
+        } else if (MiBandService.UUID_CHARACTERISTIC_USER_INFO.equals(characteristicUUID)) {
+            handleUserInfoResult(characteristic.getValue(), status);
+            return true;
+        } else if (MiBandService.UUID_CHARACTERISTIC_CONTROL_POINT.equals(characteristicUUID)) {
+            handleControlPointResult(characteristic.getValue(), status);
+            return true;
+        }
+        return false;
+    }
+
+    public void logDate(byte[] value, int status) {
+        if (status == BluetoothGatt.GATT_SUCCESS) {
+            GregorianCalendar calendar = MiBandDateConverter.rawBytesToCalendar(value, gbDevice.getAddress());
+            LOG.info("Got Mi Band Date: " + DateTimeUtils.formatDateTime(calendar.getTime()));
+        } else {
+            logMessageContent(value);
+        }
+    }
+
+    public void logHeartrate(byte[] value, int status) {
+        if (status == BluetoothGatt.GATT_SUCCESS && value != null) {
+            LOG.info("Got heartrate:");
+            if (value.length == 2 && value[0] == 6) {
+                int hrValue = (value[1] & 0xff);
+                GB.toast(getContext(), "Heart Rate measured: " + hrValue, Toast.LENGTH_LONG, GB.INFO);
+            }
+            return;
+        }
+        logMessageContent(value);
+    }
+
+    private void handleHeartrate(byte[] value) {
+        if (value.length == 2 && value[0] == 6) {
+            int hrValue = (value[1] & 0xff);
+            if (LOG.isDebugEnabled()) {
+                LOG.debug("heart rate: " + hrValue);
+            }
+            RealtimeSamplesSupport realtimeSamplesSupport = getRealtimeSamplesSupport();
+            realtimeSamplesSupport.setHeartrateBpm(hrValue);
+            if (!realtimeSamplesSupport.isRunning()) {
+                // single shot measurement, manually invoke storage and result publishing
+                realtimeSamplesSupport.triggerCurrentSample();
+            }
+        }
+    }
+
+    private void handleRealtimeSteps(byte[] value) {
+        int steps = BLETypeConversions.toUint16(value);
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("realtime steps: " + steps);
+        }
+        getRealtimeSamplesSupport().setSteps(steps);
+    }
+
+    private void enableRealtimeSamplesTimer(boolean enable) {
+        if (enable) {
+            getRealtimeSamplesSupport().start();
+        } else {
+            if (realtimeSamplesSupport != null) {
+                realtimeSamplesSupport.stop();
+            }
+        }
+    }
+
+    public MiBandActivitySample createActivitySample(Device device, User user, int timestampInSeconds, SampleProvider provider) {
+        MiBandActivitySample sample = new MiBandActivitySample();
+        sample.setDevice(device);
+        sample.setUser(user);
+        sample.setTimestamp(timestampInSeconds);
+        sample.setProvider(provider);
+
+        return sample;
+    }
+
+    private RealtimeSamplesSupport getRealtimeSamplesSupport() {
+        if (realtimeSamplesSupport == null) {
+            realtimeSamplesSupport = new RealtimeSamplesSupport(1000, 1000) {
+                @Override
+                public void doCurrentSample() {
+
+                    try (DBHandler handler = GBApplication.acquireDB()) {
+                        DaoSession session = handler.getDaoSession();
+
+                        Device device = DBHelper.getDevice(getDevice(), session);
+                        User user = DBHelper.getUser(session);
+                        int ts = (int) (System.currentTimeMillis() / 1000);
+                        MiBandSampleProvider provider = new MiBandSampleProvider(gbDevice, session);
+                        MiBandActivitySample sample = createActivitySample(device, user, ts, provider);
+                        sample.setHeartRate(getHeartrateBpm());
+                        sample.setRawIntensity(ActivitySample.NOT_MEASURED);
+                        sample.setRawKind(MiBandSampleProvider.TYPE_ACTIVITY); // to make it visible in the charts TODO: add a MANUAL kind for that?
+
+                        provider.addGBActivitySample(sample);
+
+                        // set the steps only afterwards, since realtime steps are also recorded
+                        // in the regular samples and we must not count them twice
+                        // Note: we know that the DAO sample is never committed again, so we simply
+                        // change the value here in memory.
+                        sample.setSteps(getSteps());
+
+                        if (LOG.isDebugEnabled()) {
+                            LOG.debug("realtime sample: " + sample);
+                        }
+
+                        Intent intent = new Intent(DeviceService.ACTION_REALTIME_SAMPLES)
+                                .putExtra(GBDevice.EXTRA_DEVICE, getDevice())
+                                .putExtra(DeviceService.EXTRA_REALTIME_SAMPLE, sample);
+                        LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent);
+
+                    } catch (Exception e) {
+                        LOG.warn("Unable to acquire db for saving realtime samples", e);
+                    }
+                }
+            };
+        }
+        return realtimeSamplesSupport;
+    }
+
+    /**
+     * React to unsolicited messages sent by the Mi Band to the MiBandService.UUID_CHARACTERISTIC_NOTIFICATION
+     * characteristic,
+     * These messages appear to be always 1 byte long, with values that are listed in MiBandService.
+     * It is not excluded that there are further values which are still unknown.
+     * <p/>
+     * Upon receiving known values that request further action by GB, the appropriate method is called.
+     *
+     * @param value
+     */
+    private void handleNotificationNotif(byte[] value) {
+        if (value.length != 1) {
+            LOG.error("Notifications should be 1 byte long.");
+            LOG.info("RECEIVED DATA WITH LENGTH: " + value.length);
+            LOG.warn("DATA: " + GB.hexdump(value, 0, value.length));
+            return;
+        }
+        switch (value[0]) {
+            case MiBandService.NOTIFY_AUTHENTICATION_FAILED:
+                // we get first FAILED, then NOTIFY_STATUS_MOTOR_AUTH (0x13)
+                // which means, we need to authenticate by tapping
+                getDevice().setState(State.AUTHENTICATION_REQUIRED);
+                getDevice().sendDeviceUpdateIntent(getContext());
+                GB.toast(getContext(), "Band needs pairing", Toast.LENGTH_LONG, GB.ERROR);
+                break;
+            case MiBandService.NOTIFY_AUTHENTICATION_SUCCESS: // fall through -- not sure which one we get
+            case MiBandService.NOTIFY_RESET_AUTHENTICATION_SUCCESS: // for Mi 1A
+            case MiBandService.NOTIFY_STATUS_MOTOR_AUTH_SUCCESS:
+                LOG.info("Band successfully authenticated");
+                // maybe we can perform the rest of the initialization from here
+                doInitialize();
+                break;
+
+            case MiBandService.NOTIFY_STATUS_MOTOR_AUTH:
+                LOG.info("Band needs authentication (MOTOR_AUTH)");
+                getDevice().setState(State.AUTHENTICATING);
+                getDevice().sendDeviceUpdateIntent(getContext());
+                break;
+
+            case MiBandService.NOTIFY_SET_LATENCY_SUCCESS:
+                LOG.info("Setting latency succeeded.");
+                break;
+            default:
+                LOG.warn("DATA: " + GB.hexdump(value, 0, value.length));
+        }
+    }
+
+    private void doInitialize() {
+        try {
+            TransactionBuilder builder = performInitialized("just initializing after authentication");
+            builder.queue(getQueue());
+        } catch (IOException ex) {
+            LOG.error("Unable to initialize device after authentication", ex);
+        }
+    }
+
+    private void handleDeviceInfo(byte[] value, int status) {
+        if (status == BluetoothGatt.GATT_SUCCESS) {
+            mDeviceInfo = new DeviceInfo(value);
+            mDeviceInfo.setTest1AHRMode(MI_1A_HR_FW_UPDATE_TEST_MODE_ENABLED);
+            if (getDeviceInfo().supportsHeartrate()) {
+                getDevice().setFirmwareVersion2(MiBandFWHelper.formatFirmwareVersion(mDeviceInfo.getHeartrateFirmwareVersion()));
+            }
+            LOG.warn("Device info: " + mDeviceInfo);
+            versionCmd.hwVersion = mDeviceInfo.getHwVersion();
+            versionCmd.fwVersion = MiBandFWHelper.formatFirmwareVersion(mDeviceInfo.getFirmwareVersion());
+            handleGBDeviceEvent(versionCmd);
+        }
+    }
+
+    private void handleDeviceName(byte[] value, int status) {
+//        if (status == BluetoothGatt.GATT_SUCCESS) {
+//            versionCmd.hwVersion = new String(value);
+//            handleGBDeviceEvent(versionCmd);
+//        }
+    }
+
+    /**
+     * Convert an alarm from the GB internal structure to a Mi Band message and put on the specified
+     * builder queue as a write message for the passed characteristic
+     *
+     * @param alarm
+     * @param builder
+     * @param characteristic
+     */
+    private void queueAlarm(Alarm alarm, TransactionBuilder builder, BluetoothGattCharacteristic characteristic) {
+        byte[] alarmCalBytes = MiBandDateConverter.calendarToRawBytes(AlarmUtils.toCalendar(alarm), gbDevice.getAddress());
+
+        byte[] alarmMessage = new byte[]{
+                MiBandService.COMMAND_SET_TIMER,
+                (byte) alarm.getPosition(),
+                (byte) (alarm.getEnabled() ? 1 : 0),
+                alarmCalBytes[0],
+                alarmCalBytes[1],
+                alarmCalBytes[2],
+                alarmCalBytes[3],
+                alarmCalBytes[4],
+                alarmCalBytes[5],
+                (byte) (alarm.getSmartWakeup() ? 30 : 0),
+                (byte) alarm.getRepetition()
+        };
+        builder.write(characteristic, alarmMessage);
+    }
+
+    private void handleControlPointResult(byte[] value, int status) {
+        if (status != BluetoothGatt.GATT_SUCCESS) {
+            LOG.warn("Could not write to the control point.");
+        }
+        LOG.info("handleControlPoint write status:" + status + "; length: " + (value != null ? value.length : "(null)"));
+
+        if (value != null) {
+            for (byte b : value) {
+                LOG.info("handleControlPoint WROTE DATA:" + String.format("0x%8x", b));
+            }
+        } else {
+            LOG.warn("handleControlPoint WROTE null");
+        }
+    }
+
+    private void handleBatteryInfo(byte[] value, int status) {
+        if (status == BluetoothGatt.GATT_SUCCESS) {
+            BatteryInfo info = new BatteryInfo(value);
+            batteryCmd.level = ((short) info.getLevelInPercent());
+            batteryCmd.state = info.getState();
+            batteryCmd.lastChargeTime = info.getLastChargeTime(gbDevice.getAddress());
+            batteryCmd.numCharges = info.getNumCharges();
+            handleGBDeviceEvent(batteryCmd);
+        }
+    }
+
+    private void handleUserInfoResult(byte[] value, int status) {
+        // successfully transferred user info means we're initialized
+// commented out, because we have SetDeviceStateAction which sets initialized
+// state on every successful initialization.
+//        if (status == BluetoothGatt.GATT_SUCCESS) {
+//            setConnectionState(State.INITIALIZED);
+//        }
+    }
+
+    private void setConnectionState(State newState) {
+        getDevice().setState(newState);
+        getDevice().sendDeviceUpdateIntent(getContext());
+    }
+
+    private void handlePairResult(byte[] pairResult, int status) {
+        if (status != BluetoothGatt.GATT_SUCCESS) {
+            LOG.info("Pairing MI device failed: " + status);
+            return;
+        }
+
+        String value = null;
+        if (pairResult != null) {
+            if (pairResult.length == 1) {
+                try {
+                    if (pairResult[0] == 2) {
+                        LOG.info("Successfully paired  MI device");
+                        return;
+                    }
+                } catch (Exception ex) {
+                    LOG.warn("Error identifying pairing result", ex);
+                    return;
+                }
+            }
+            value = Arrays.toString(pairResult);
+        }
+        LOG.info("MI Band pairing result: " + value);
+    }
+
+    /**
+     * Fetch the events from the android device calendars and set the alarms on the miband.
+     */
+    private void sendCalendarEvents() {
+        try {
+            TransactionBuilder builder = performInitialized("Send upcoming events");
+            BluetoothGattCharacteristic characteristic = getCharacteristic(MiBandService.UUID_CHARACTERISTIC_CONTROL_POINT);
+
+            Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()));
+            int availableSlots = prefs.getInt(DeviceSettingsPreferenceConst.PREF_RESERVER_ALARMS_CALENDAR, 0);
+            if (availableSlots >3) {
+                availableSlots = 3;
+            }
+            if (availableSlots > 0) {
+                CalendarManager upcomingEvents = new CalendarManager(getContext(), getDevice().getAddress());
+                List<CalendarEvent> mEvents = upcomingEvents.getCalendarEventList();
+
+                int iteration = 0;
+                for (CalendarEvent mEvt : mEvents) {
+                    if (iteration >= availableSlots) {
+                        break;
+                    }
+                    int slotToUse = 2 - iteration;
+                    Calendar calendar = Calendar.getInstance();
+                    calendar.setTimeInMillis(mEvt.getBegin());
+                    Alarm alarm = AlarmUtils.createSingleShot(slotToUse, false, false, calendar);
+                    queueAlarm(alarm, builder, characteristic);
+                    iteration++;
+                }
+                builder.queue(getQueue());
+            }
+        } catch (IOException ex) {
+            LOG.error("Unable to send Events to MI device", ex);
+        }
+    }
+
+    @Override
+    public void onTestNewFunction() {
+        try {
+            TransactionBuilder builder = performInitialized("Toggle sensor reading");
+            if (isReadingSensorData) {
+                builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_CONTROL_POINT), stopSensorRead);
+                isReadingSensorData = false;
+            } else {
+                builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_CONTROL_POINT), startSensorRead);
+                isReadingSensorData = true;
+            }
+            builder.queue(getQueue());
+        } catch (IOException ex) {
+            LOG.error("Unable to toggle sensor reading MI", ex);
+        }
+    }
+
+    /**
+     * Analyse and decode sensor data from ADXL362 accelerometer
+     * @param value to decode
+     * @return nothing
+     *
+     * Each axis raw value is 16bits long and look like : ttssvvvvvvvvvvvv
+     *   tt : 2 bits for the type of data (00=x, 01=y, 10=z, 11=temperature)
+     *   ss : sign of the value
+     *   vvvvvvvvvvvv : accelerometer value encoded using two complements
+     *
+     * TODO: Because each accelerometer is different, all values should be calibrated with  :
+     *   a scale factor
+     *   an offset factor
+     */
+    private static void handleSensorData(byte[] value) {
+        int counter=0, step=0;
+        double xAxis=0.0, yAxis=0.0, zAxis=0.0;
+        double scale_factor = 1000.0;
+        double gravity = 9.81;
+
+        if ((value.length - 2) % 6 != 0) {
+            LOG.warn("GOT UNEXPECTED SENSOR DATA WITH LENGTH: " + value.length);
+            LOG.warn("DATA: " + GB.hexdump(value, 0, value.length));
+        }
+        else {
+            counter = (value[0] & 0xff) | ((value[1] & 0xff) << 8);
+            for (int idx = 0; idx < ((value.length - 2) / 6); idx++) {
+                step = idx * 6;
+
+                // Analyse X-axis data
+                int xAxisRawValue = (value[step+2] & 0xff) | ((value[step+3] & 0xff) << 8);
+                int xAxisSign = (value[step+3] & 0x30) >> 4;
+                int xAxisType = (value[step+3] & 0xc0) >> 6;
+                if (xAxisSign == 0) {
+                    xAxis = xAxisRawValue & 0xfff;
+                }
+                else {
+                    xAxis = (xAxisRawValue & 0xfff) - 4097;
+                }
+                xAxis = (xAxis*1.0 / scale_factor) * gravity;
+
+                // Analyse Y-axis data
+                int yAxisRawValue = (value[step+4] & 0xff) | ((value[step+5] & 0xff) << 8);
+                int yAxisSign = (value[step+5] & 0x30) >> 4;
+                int yAxisType = (value[step+5] & 0xc0) >> 6;
+                if (yAxisSign == 0) {
+                    yAxis = yAxisRawValue & 0xfff;
+                }
+                else {
+                    yAxis = (yAxisRawValue & 0xfff) - 4097;
+                }
+                yAxis = (yAxis / scale_factor) * gravity;
+
+                // Analyse Z-axis data
+                int zAxisRawValue = (value[step+6] & 0xff) | ((value[step+7] & 0xff) << 8);
+                int zAxisSign = (value[step+7] & 0x30) >> 4;
+                int zAxisType = (value[step+7] & 0xc0) >> 6;
+                if (zAxisSign == 0) {
+                    zAxis = zAxisRawValue & 0xfff;
+                }
+                else {
+                    zAxis = (zAxisRawValue & 0xfff) - 4097;
+                }
+                zAxis = (zAxis / scale_factor) * gravity;
+
+                // Print results in log
+                LOG.info("READ SENSOR DATA VALUES: counter:"+counter+" step:"+step+" x-axis:"+ String.format("%.03f",xAxis)+" y-axis:"+String.format("%.03f",yAxis)+" z-axis:"+String.format("%.03f",zAxis)+";");
+            }
+        }
+    }
+
+    @Override
+    public boolean getImplicitCallbackModify() {
+        return true;
+    }
+
+    @Override
+    public boolean getSendWriteRequestResponse() {
+        return false;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/NoNotificationStrategy.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/NoNotificationStrategy.java
new file mode 100644
index 0000000..7eea51f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/NoNotificationStrategy.java
@@ -0,0 +1,47 @@
+/*  Copyright (C) 2015-2024 Carsten Pfeiffer, Daniele Gobbetti
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.miband;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.VibrationProfile;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEAction;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.common.SimpleNotification;
+
+/**
+ * Does not do anything.
+ */
+public class NoNotificationStrategy implements NotificationStrategy {
+    private static final Logger LOG = LoggerFactory.getLogger(NoNotificationStrategy.class);
+
+    @Override
+    public void sendDefaultNotification(TransactionBuilder builder, SimpleNotification simpleNotification, BtLEAction extraAction) {
+        LOG.info("dummy notification stragegy: default notification");
+    }
+
+    @Override
+    public void sendCustomNotification(VibrationProfile vibrationProfile, SimpleNotification simpleNotification, int flashTimes, int flashColour, int originalColour, long flashDuration, BtLEAction extraAction, TransactionBuilder builder) {
+        LOG.info("dummy notification stragegy: custom notification: " + simpleNotification);
+    }
+
+    @Override
+    public void stopCurrentNotification(TransactionBuilder builder) {
+        LOG.info("dummy notification stragegy: stop notification");
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/NotificationStrategy.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/NotificationStrategy.java
new file mode 100644
index 0000000..312d355
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/NotificationStrategy.java
@@ -0,0 +1,46 @@
+/*  Copyright (C) 2015-2024 Carsten Pfeiffer, Daniele Gobbetti
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.miband;
+
+import androidx.annotation.Nullable;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.VibrationProfile;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEAction;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.common.SimpleNotification;
+
+public interface NotificationStrategy {
+    void sendDefaultNotification(TransactionBuilder builder, SimpleNotification simpleNotification, BtLEAction extraAction);
+
+    /**
+     * Adds a custom notification to the given transaction builder
+     * @param vibrationProfile specifies how and how often the Band shall vibrate.
+     * @param simpleNotification an optional notification containing a type and text message
+     * @param flashTimes
+     * @param flashColour
+     * @param originalColour
+     * @param flashDuration
+     * @param extraAction      an extra action to be executed after every vibration and flash sequence. Allows to abort the repetition, for example.
+     * @param builder
+     */
+    void sendCustomNotification(VibrationProfile vibrationProfile, @Nullable SimpleNotification simpleNotification, int flashTimes, int flashColour, int originalColour, long flashDuration, BtLEAction extraAction, TransactionBuilder builder);
+
+    /**
+     * Stops any current notification.
+     * @param builder
+     */
+    void stopCurrentNotification(TransactionBuilder builder);
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/RealtimeSamplesSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/RealtimeSamplesSupport.java
new file mode 100644
index 0000000..5d688ab
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/RealtimeSamplesSupport.java
@@ -0,0 +1,125 @@
+/*  Copyright (C) 2016-2024 Carsten Pfeiffer
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.miband;
+
+import java.util.Timer;
+import java.util.TimerTask;
+
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
+
+/**
+ * Basic support for aggregating different sources of realtime data that comes in in a mostly
+ * fixed interval. The aggregated data will be stored together.
+ *
+ * start() and stop() may be called multiple times, but the first stop() call will really
+ * stop the timer.
+ * manner.
+ *
+ * Subclasses must implement #doCurrentSample() and should override #resetCurrentValues()
+ * (but call super!).
+ */
+public abstract class RealtimeSamplesSupport {
+    private final long delay;
+    private final long period;
+
+    protected int steps;
+    protected int heartrateBpm;
+    private int lastSteps;
+    // subclasses may add more
+
+    private Timer realtimeStorageTimer;
+
+    public RealtimeSamplesSupport(long delay, long period) {
+        this.delay = delay;
+        this.period = period;
+    }
+
+    public synchronized void start() {
+        if (isRunning()) {
+            return; // already running
+        }
+        realtimeStorageTimer = new Timer("Mi Band Realtime Storage Timer");
+        realtimeStorageTimer.scheduleAtFixedRate(new TimerTask() {
+            @Override
+            public void run() {
+                triggerCurrentSample();
+            }
+        }, delay, period);
+    }
+
+    public synchronized void stop() {
+        if (realtimeStorageTimer != null) {
+            realtimeStorageTimer.cancel();
+            realtimeStorageTimer.purge();
+            realtimeStorageTimer = null;
+        }
+    }
+
+    public synchronized boolean isRunning() {
+        return realtimeStorageTimer != null;
+    }
+
+    public synchronized void setSteps(int stepsPerMinute) {
+        if (stepsPerMinute == ActivitySample.NOT_MEASURED || stepsPerMinute >= 0) {
+            this.steps = stepsPerMinute;
+        } else {
+            this.steps = ActivitySample.NOT_MEASURED;
+        }
+    }
+
+    /**
+     * Returns the number of steps recorded since the last measurements. If no
+     * steps are available yet, ActivitySample.NOT_MEASURED is returned.
+     * @return
+     */
+    public synchronized int getSteps() {
+        if (steps == ActivitySample.NOT_MEASURED) {
+            return ActivitySample.NOT_MEASURED;
+        }
+        if (lastSteps == 0)  {
+            return ActivitySample.NOT_MEASURED; // wait until we have a delta between two samples
+        }
+        int delta = steps - lastSteps;
+        if (delta < 0) {
+            return 0;
+        }
+        return delta;
+    }
+
+    public void setHeartrateBpm(int hrBpm) {
+        this.heartrateBpm = hrBpm;
+    }
+
+    public int getHeartrateBpm() {
+        return heartrateBpm;
+    }
+
+    public void triggerCurrentSample() {
+        doCurrentSample();
+        resetCurrentValues();
+    }
+
+    protected synchronized void resetCurrentValues() {
+        if (steps >= lastSteps) {
+            lastSteps = steps;
+        }
+        steps = ActivitySample.NOT_MEASURED;
+        heartrateBpm = ActivitySample.NOT_MEASURED;
+    }
+
+    protected abstract void doCurrentSample();
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/TestMi1AFirmwareInfo.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/TestMi1AFirmwareInfo.java
new file mode 100644
index 0000000..6b93461
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/TestMi1AFirmwareInfo.java
@@ -0,0 +1,92 @@
+/*  Copyright (C) 2016-2024 Andreas Shimokawa, Carsten Pfeiffer, Daniele
+    Gobbetti
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.miband;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import androidx.annotation.Nullable;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+/**
+ * This is a class just for testing the dual fw firmware update procedure.
+ * It uses two instances of the known-to-be-working Mi1A firmware update instances
+ * and combines them in a CompositeMiFirmwareInfo.
+ * <p/>
+ * Most methods simply delegate to one of the child instances (FW1).
+ * <p/>
+ * FW1 is the default Mi 1A Band firmware
+ * FW2 is the same default Mi 1A Band firmware
+ */
+public class TestMi1AFirmwareInfo extends CompositeMiFirmwareInfo {
+    private static final Logger LOG = LoggerFactory.getLogger(TestMi1AFirmwareInfo.class);
+
+    private TestMi1AFirmwareInfo(byte[] wholeFirmwareBytes) {
+        super(wholeFirmwareBytes, new Mi1AFirmwareInfo(wholeFirmwareBytes), new Mi1AFirmwareInfo(wholeFirmwareBytes));
+    }
+
+    @Override
+    public void checkValid() throws IllegalArgumentException {
+//        super.checkValid();
+// unfortunately we cannot use all of the checks in the superclass, so we roll our own
+
+        if (getFirst().getFirmwareOffset() != getSecond().getFirmwareOffset()) {
+            throw new IllegalArgumentException("Test firmware offsets should be the same: " + getLengthsOffsetsString());
+        }
+        if (getFirst().getFirmwareOffset() < 0 || getSecond().getFirmwareOffset() < 0
+                || getFirst().getFirmwareLength() <= 0 || getSecond().getFirmwareLength() <= 0) {
+            throw new IllegalArgumentException("Illegal test firmware offsets/lengths: " + getLengthsOffsetsString());
+        }
+
+        if (getFirst().getFirmwareLength() != getSecond().getFirmwareLength()) {
+            throw new IllegalArgumentException("Illegal test firmware lengths: " + getLengthsOffsetsString());
+        }
+        int firstEndIndex = getFirst().getFirmwareOffset() + getFirst().getFirmwareLength();
+        int secondEndIndex = getSecond().getFirmwareOffset();
+        if (wholeFirmwareBytes.length < firstEndIndex || wholeFirmwareBytes.length < secondEndIndex) {
+            throw new IllegalArgumentException("Invalid test firmware size, or invalid test offsets/lengths: " + getLengthsOffsetsString());
+        }
+
+        getFirst().checkValid();
+        getSecond().checkValid();
+    }
+
+    @Override
+    public boolean isGenerallyCompatibleWith(GBDevice device) {
+        return getFirst().isGenerallyCompatibleWith(device);
+    }
+
+    @Override
+    public boolean isSingleMiBandFirmware() {
+        return false;
+    }
+
+    protected boolean isHeaderValid() {
+        return getFirst().isHeaderValid();
+    }
+
+    @Nullable
+    public static TestMi1AFirmwareInfo getInstance(byte[] wholeFirmwareBytes) {
+        TestMi1AFirmwareInfo info = new TestMi1AFirmwareInfo(wholeFirmwareBytes);
+        if (info.isGenerallySupportedFirmware()) {
+            return info;
+        }
+        LOG.info("firmware not supported");
+        return null;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/V1NotificationStrategy.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/V1NotificationStrategy.java
new file mode 100644
index 0000000..39fe6e5
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/V1NotificationStrategy.java
@@ -0,0 +1,127 @@
+/*  Copyright (C) 2015-2024 Carsten Pfeiffer
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.miband;
+
+import android.bluetooth.BluetoothGattCharacteristic;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandService;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.VibrationProfile;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEAction;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.common.SimpleNotification;
+
+public class V1NotificationStrategy implements NotificationStrategy {
+    private static final Logger LOG = LoggerFactory.getLogger(V1NotificationStrategy.class);
+
+    static final byte[] startVibrate = new byte[]{MiBandService.COMMAND_SEND_NOTIFICATION, 1};
+    static final byte[] stopVibrate = new byte[]{MiBandService.COMMAND_STOP_MOTOR_VIBRATE};
+
+    private final AbstractBTLEDeviceSupport support;
+
+    public V1NotificationStrategy(AbstractBTLEDeviceSupport support) {
+        this.support = support;
+    }
+
+    @Override
+    public void sendDefaultNotification(TransactionBuilder builder, SimpleNotification simpleNotification, BtLEAction extraAction) {
+        BluetoothGattCharacteristic characteristic = support.getCharacteristic(MiBandService.UUID_CHARACTERISTIC_CONTROL_POINT);
+        builder.write(characteristic, getDefaultNotification());
+        builder.add(extraAction);
+    }
+
+    private byte[] getDefaultNotification() {
+        final int vibrateTimes = 1;
+        final long vibrateDuration = 250l;
+        final int flashTimes = 1;
+        final int flashColour = 0xFFFFFFFF;
+        final int originalColour = 0xFFFFFFFF;
+        final long flashDuration = 250l;
+
+        return getNotification(vibrateDuration, vibrateTimes, flashTimes, flashColour, originalColour, flashDuration);
+    }
+
+    private byte[] getNotification(long vibrateDuration, int vibrateTimes, int flashTimes, int flashColour, int originalColour, long flashDuration) {
+        byte[] vibrate = startVibrate;
+        byte r = 6;
+        byte g = 0;
+        byte b = 6;
+        boolean display = true;
+        //      byte[] flashColor = new byte[]{ 14, r, g, b, display ? (byte) 1 : (byte) 0 };
+        return vibrate;
+    }
+
+    /**
+     * Adds a custom notification to the given transaction builder
+     * @param vibrationProfile specifies how and how often the Band shall vibrate.
+     * @param simpleNotification
+     * @param flashTimes
+     * @param flashColour
+     * @param originalColour
+     * @param flashDuration
+     * @param extraAction      an extra action to be executed after every vibration and flash sequence. Allows to abort the repetition, for example.
+     * @param builder
+     */
+    @Override
+    public void sendCustomNotification(VibrationProfile vibrationProfile, SimpleNotification simpleNotification, int flashTimes, int flashColour, int originalColour, long flashDuration, BtLEAction extraAction, TransactionBuilder builder) {
+        BluetoothGattCharacteristic controlPoint = support.getCharacteristic(MiBandService.UUID_CHARACTERISTIC_CONTROL_POINT);
+        for (short i = 0; i < vibrationProfile.getRepeat(); i++) {
+            int[] onOffSequence = vibrationProfile.getOnOffSequence();
+            for (int j = 0; j < onOffSequence.length; j++) {
+                int on = onOffSequence[j];
+                on = Math.min(500, on); // longer than 500ms is not possible
+                builder.write(controlPoint, startVibrate);
+                builder.wait(on);
+                builder.write(controlPoint, stopVibrate);
+
+                if (++j < onOffSequence.length) {
+                    int off = Math.max(onOffSequence[j], 25); // wait at least 25ms
+                    builder.wait(off);
+                }
+
+                if (extraAction != null) {
+                    builder.add(extraAction);
+                }
+            }
+        }
+    }
+
+    @Override
+    public void stopCurrentNotification(TransactionBuilder builder) {
+        BluetoothGattCharacteristic controlPoint = support.getCharacteristic(MiBandService.UUID_CHARACTERISTIC_CONTROL_POINT);
+        builder.write(controlPoint, stopVibrate);
+    }
+
+//    private void sendCustomNotification(int vibrateDuration, int vibrateTimes, int pause, int flashTimes, int flashColour, int originalColour, long flashDuration, TransactionBuilder builder) {
+//        BluetoothGattCharacteristic controlPoint = getCharacteristic(MiBandService.UUID_CHARACTERISTIC_CONTROL_POINT);
+//        int vDuration = Math.min(500, vibrateDuration); // longer than 500ms is not possible
+//        for (int i = 0; i < vibrateTimes; i++) {
+//            builder.write(controlPoint, startVibrate);
+//            builder.wait(vDuration);
+//            builder.write(controlPoint, stopVibrate);
+//            if (pause > 0) {
+//                builder.wait(pause);
+//            }
+//        }
+//
+//        LOG.info("Sending notification to MiBand: " + controlPoint);
+//        builder.queue(getQueue());
+//    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/V2NotificationStrategy.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/V2NotificationStrategy.java
new file mode 100644
index 0000000..58e0496
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/V2NotificationStrategy.java
@@ -0,0 +1,83 @@
+/*  Copyright (C) 2015-2024 Carsten Pfeiffer, Daniele Gobbetti
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.miband;
+
+import android.bluetooth.BluetoothGattCharacteristic;
+
+import androidx.annotation.Nullable;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.VibrationProfile;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEAction;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.GattCharacteristic;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.common.SimpleNotification;
+
+public class V2NotificationStrategy<T extends AbstractBTLEDeviceSupport> implements NotificationStrategy {
+    private final T support;
+
+    public V2NotificationStrategy(T support) {
+        this.support = support;
+    }
+
+    protected T getSupport() {
+        return support;
+    }
+
+    @Override
+    public void sendDefaultNotification(TransactionBuilder builder, SimpleNotification simpleNotification, BtLEAction extraAction) {
+        VibrationProfile profile = VibrationProfile.getProfile(VibrationProfile.ID_MEDIUM, (short) 3);
+        sendCustomNotification(profile, simpleNotification, extraAction, builder);
+    }
+
+    protected void sendCustomNotification(VibrationProfile vibrationProfile, @Nullable SimpleNotification simpleNotification, BtLEAction extraAction, TransactionBuilder builder) {
+        //use the new alert characteristic
+        BluetoothGattCharacteristic alert = support.getCharacteristic(GattCharacteristic.UUID_CHARACTERISTIC_ALERT_LEVEL);
+        for (short i = 0; i < vibrationProfile.getRepeat(); i++) {
+            int[] onOffSequence = vibrationProfile.getOnOffSequence();
+            for (int j = 0; j < onOffSequence.length; j++) {
+                int on = onOffSequence[j];
+                on = Math.min(500, on); // longer than 500ms is not possible
+                builder.write(alert, new byte[]{GattCharacteristic.MILD_ALERT}); //MILD_ALERT lights up GREEN leds, HIGH_ALERT lights up RED leds
+//                builder.wait(on);
+//                builder.write(alert, new byte[]{GattCharacteristic.HIGH_ALERT});
+                builder.wait(on);
+                builder.write(alert, new byte[]{GattCharacteristic.NO_ALERT});
+
+                if (++j < onOffSequence.length) {
+                    int off = Math.max(onOffSequence[j], 25); // wait at least 25ms
+                    builder.wait(off);
+                }
+
+                if (extraAction != null) {
+                    builder.add(extraAction);
+                }
+            }
+        }
+    }
+
+    @Override
+    public void sendCustomNotification(VibrationProfile vibrationProfile, SimpleNotification simpleNotification, int flashTimes, int flashColour, int originalColour, long flashDuration, BtLEAction extraAction, TransactionBuilder builder) {
+        // all other parameters are unfortunately not supported anymore ;-(
+        sendCustomNotification(vibrationProfile, simpleNotification, extraAction, builder);
+    }
+
+    @Override
+    public void stopCurrentNotification(TransactionBuilder builder) {
+        BluetoothGattCharacteristic alert = support.getCharacteristic(GattCharacteristic.UUID_CHARACTERISTIC_ALERT_LEVEL);
+        builder.write(alert, new byte[]{GattCharacteristic.NO_ALERT});
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/operations/AbstractMiBand1Operation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/operations/AbstractMiBand1Operation.java
new file mode 100644
index 0000000..4bd72c9
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/operations/AbstractMiBand1Operation.java
@@ -0,0 +1,33 @@
+/*  Copyright (C) 2016-2024 Carsten Pfeiffer
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandService;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.MiBandSupport;
+
+public abstract class AbstractMiBand1Operation extends AbstractMiBandOperation<MiBandSupport> {
+    protected AbstractMiBand1Operation(MiBandSupport support) {
+        super(support);
+    }
+
+    @Override
+    protected void enableOtherNotifications(TransactionBuilder builder, boolean enable) {
+        builder.notify(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_REALTIME_STEPS), enable)
+                .notify(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_SENSOR_DATA), enable);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/operations/AbstractMiBandOperation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/operations/AbstractMiBandOperation.java
new file mode 100644
index 0000000..1031c58
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/operations/AbstractMiBandOperation.java
@@ -0,0 +1,82 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, Carsten Pfeiffer, Damien Gaignon
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations;
+
+import android.widget.Toast;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEOperation;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public abstract class AbstractMiBandOperation<T extends AbstractBTLEDeviceSupport> extends AbstractBTLEOperation<T> {
+    protected AbstractMiBandOperation(T support) {
+        super(support);
+    }
+
+    @Override
+    protected void prePerform() throws IOException {
+        super.prePerform();
+        getDevice().setBusyTask("Operation starting..."); // mark as busy quickly to avoid interruptions from the outside
+        TransactionBuilder builder = performInitialized("disabling some notifications");
+        enableOtherNotifications(builder, false);
+        enableNeededNotifications(builder, true);
+        builder.queue(getQueue());
+    }
+
+    @Override
+    protected void operationFinished() {
+        operationStatus = OperationStatus.FINISHED;
+        if (getDevice() != null && getDevice().isConnected()) {
+            unsetBusy();
+            try {
+                TransactionBuilder builder = performInitialized("reenabling disabled notifications");
+                handleFinished(builder);
+                builder.setCallback(null); // unset ourselves from being the queue's gatt callback
+                builder.queue(getQueue());
+            } catch (IOException ex) {
+                GB.toast(getContext(), "Error enabling Mi Band notifications, you may need to connect and disconnect", Toast.LENGTH_LONG, GB.ERROR, ex);
+            }
+        }
+    }
+
+
+    private void handleFinished(TransactionBuilder builder) {
+        enableNeededNotifications(builder, false);
+        enableOtherNotifications(builder, true);
+    }
+
+    /**
+     * Enables or disables the notifications that are needed for the entire operation.
+     * Enabled on operation start and disabled on operation finish.
+     * @param builder
+     * @param enable
+     */
+    protected abstract void enableNeededNotifications(TransactionBuilder builder, boolean enable);
+
+    /**
+     * Enables or disables certain kinds of notifications that could interfere with this
+     * operation. Call this method once initially to disable other notifications, and once
+     * when this operation has finished.
+     *
+     * @param builder
+     * @param enable  true to enable, false to disable the other notifications
+     */
+    protected abstract void enableOtherNotifications(TransactionBuilder builder, boolean enable);
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/operations/FetchActivityOperation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/operations/FetchActivityOperation.java
new file mode 100644
index 0000000..748615a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/operations/FetchActivityOperation.java
@@ -0,0 +1,460 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, Carsten Pfeiffer, Daniele
+    Gobbetti, Taavi Eom盲e
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations;
+
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.widget.Toast;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.text.DateFormat;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.UUID;
+import java.util.concurrent.TimeUnit;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandDateConverter;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandService;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.entities.MiBandActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.entities.User;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceBusyAction;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.MiBandSupport;
+import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+
+/**
+ * An operation that fetches activity data. For every fetch, a new operation must
+ * be created, i.e. an operation may not be reused for multiple fetches.
+ */
+public class FetchActivityOperation extends AbstractMiBand1Operation {
+    private static final Logger LOG = LoggerFactory.getLogger(FetchActivityOperation.class);
+    private static final byte[] fetch = new byte[]{MiBandService.COMMAND_FETCH_DATA};
+
+    //sometimes the Mi Band stops sending data, we confirm the part of the data transfer after some time
+    //private ScheduledExecutorService scheduleTaskExecutor;
+    //private ScheduledFuture scheduledTask;
+
+    private final int activityMetadataLength = 11;
+
+    private final boolean hasExtendedActivityData;
+    private final boolean hasPacketCounter;
+
+    private class ActivityStruct {
+        private int maxDataPacketLength;
+        private int lastNotifiedProgress;
+        private final byte[] activityDataHolder;
+        private final int activityDataHolderSize;
+        //index of the buffer above
+        private int activityDataHolderProgress = 0;
+        //number of bytes we will get in a single data transfer, used as counter
+        private int activityDataRemainingBytes = 0;
+        //same as above, but remains untouched for the ack message
+        private int activityDataUntilNextHeader = 0;
+        //timestamp of the single data transfer, incremented to store each minute's data
+        private GregorianCalendar activityDataTimestampProgress = null;
+        //same as above, but remains untouched for the ack message
+        private GregorianCalendar activityDataTimestampToAck = null;
+
+        ActivityStruct(int activityDataHolderSize, int maxDataPacketLength) {
+            this.activityDataHolderSize = activityDataHolderSize;
+            this.maxDataPacketLength = maxDataPacketLength;
+            activityDataHolder = new byte[activityDataHolderSize];
+        }
+
+        boolean hasRoomFor(byte[] value) {
+            return activityDataRemainingBytes >= value.length;
+        }
+
+        boolean isValidData(byte[] value) {
+            //I don't like this clause, but until we figure out why we get different data sometimes this should work
+            return value.length == maxDataPacketLength || value.length == activityDataRemainingBytes;
+        }
+
+        boolean isBufferFull() {
+            return activityDataHolderSize == activityDataHolderProgress;
+        }
+
+        public void buffer(byte[] value) {
+            System.arraycopy(value, 0, activityDataHolder, activityDataHolderProgress, value.length);
+            activityDataHolderProgress += value.length;
+            activityDataRemainingBytes -= value.length;
+
+            validate();
+        }
+
+        private void validate() {
+            GB.assertThat(activityDataRemainingBytes >= 0, "Illegal state, remaining bytes is negative");
+        }
+
+        boolean isFirstChunk() {
+            return activityDataTimestampProgress == null;
+        }
+
+        void startNewBlock(GregorianCalendar timestamp, int dataUntilNextHeader) {
+            GB.assertThat(timestamp != null, "Timestamp must not be null");
+
+            if (isFirstChunk()) {
+                activityDataTimestampProgress = timestamp;
+            } else {
+                if (timestamp.getTimeInMillis() >= activityDataTimestampProgress.getTimeInMillis()) {
+                    activityDataTimestampProgress = timestamp;
+                } else {
+                    // something is fishy here... better not trust the given timestamp and simply
+                    // (re)use the current one
+                    // we do accept the timestamp to ack though, so that the bogus data is properly cleared on the band
+                    LOG.warn("Got bogus timestamp: " + timestamp.getTime() + " that is smaller than the previous timestamp: " + activityDataTimestampProgress.getTime());
+                }
+            }
+            activityDataTimestampToAck = (GregorianCalendar) timestamp.clone();
+            activityDataRemainingBytes = activityDataUntilNextHeader = dataUntilNextHeader;
+            validate();
+        }
+
+        boolean isBlockFinished() {
+            return activityDataRemainingBytes == 0;
+        }
+
+        void bufferFlushed(int minutes) {
+            activityDataTimestampProgress.add(Calendar.MINUTE, minutes);
+            activityDataHolderProgress = 0;
+            lastNotifiedProgress = 0;
+        }
+    }
+
+    private ActivityStruct activityStruct;
+
+    public FetchActivityOperation(MiBandSupport support) throws IOException {
+        super(support);
+        if (support == null) {
+            throw new IOException("MiBandSupport was null");
+        }
+        if (support.getDeviceInfo() == null) {
+            throw new IOException("MiBandSupport getDeviceInfo returned null");
+        }
+        hasExtendedActivityData = support.getDeviceInfo().supportsHeartrate();
+        hasPacketCounter = support.getDeviceInfo().getProfileVersion() >= 0x02000700;
+        //temporary buffer, size is a multiple of 60 because we want to store complete minutes (1 minute = 3 or 4 bytes)
+        int activityDataHolderSize = getBytesPerMinuteOfActivityData() * 60 * 4;
+        int maxDataPacketLength = hasPacketCounter ? (hasExtendedActivityData ? 16 : 18) : 20;
+        activityStruct = new ActivityStruct(activityDataHolderSize, maxDataPacketLength);
+    }
+
+    @Override
+    protected void enableNeededNotifications(TransactionBuilder builder, boolean enable) {
+        // enabled all the time... maybe we should change that!
+    }
+
+    @Override
+    protected void doPerform() throws IOException {
+//        scheduleTaskExecutor = Executors.newScheduledThreadPool(1);
+
+        TransactionBuilder builder = performInitialized("fetch activity data");
+        getSupport().setLowLatency(builder);
+        builder.add(new SetDeviceBusyAction(getDevice(), getContext().getString(R.string.busy_task_fetch_activity_data), getContext()));
+        builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_CONTROL_POINT), fetch);
+        builder.queue(getQueue());
+    }
+
+    @Override
+    public boolean onCharacteristicChanged(BluetoothGatt gatt,
+                                           BluetoothGattCharacteristic characteristic) {
+        UUID characteristicUUID = characteristic.getUuid();
+        if (MiBandService.UUID_CHARACTERISTIC_ACTIVITY_DATA.equals(characteristicUUID)) {
+            handleActivityNotif(characteristic.getValue());
+            return true;
+        } else {
+            return super.onCharacteristicChanged(gatt, characteristic);
+        }
+    }
+
+    private void handleActivityFetchFinish() throws IOException {
+        LOG.info("Fetching activity data has finished.");
+        activityStruct = null;
+        operationFinished();
+        unsetBusy();
+        GB.signalActivityDataFinish(getDevice());
+    }
+
+    /**
+     * Method to handle the incoming activity data.
+     * There are two kind of messages we currently know:
+     * - the first one is 11 bytes long and contains metadata (how many bytes to expect, when the data starts, etc.)
+     * - the second one is 20 bytes long and contains the actual activity data
+     * <p/>
+     * The first message type is parsed by this method, for every other length of the value param, bufferActivityData is called.
+     *
+     * @param value
+     * @see #bufferActivityData(byte[])
+     */
+    private void handleActivityNotif(byte[] value) {
+        if (!isOperationRunning()) {
+            LOG.error("ignoring activity data notification because operation is not running. Data length: " + value.length);
+            getSupport().logMessageContent(value);
+            return;
+        }
+
+        if (value.length == activityMetadataLength) {
+            handleActivityMetadata(value);
+        } else {
+            if (hasPacketCounter) {
+                byte[] valueChopped = new byte[value.length - 1];
+                System.arraycopy(value, 1, valueChopped, 0, value.length - 1);
+                bufferActivityData(valueChopped);
+            } else {
+                bufferActivityData(value);
+            }
+        }
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("activity data: length: " + value.length + ", remaining bytes: " + activityStruct.activityDataRemainingBytes);
+        }
+
+        int progress = (int) (((float) (activityStruct.activityDataUntilNextHeader - activityStruct.activityDataRemainingBytes)) / activityStruct.activityDataUntilNextHeader * 100);
+        // avoid too many notifications overloading the system
+        if (progress - activityStruct.lastNotifiedProgress >= 8) {
+            activityStruct.lastNotifiedProgress = progress;
+            GB.updateTransferNotification(null, getContext().getString(R.string.busy_task_fetch_activity_data), true, progress, getContext());
+        }
+
+        if (activityStruct.isBlockFinished()) {
+            sendAckDataTransfer(activityStruct.activityDataTimestampToAck, activityStruct.activityDataUntilNextHeader);
+            GB.updateTransferNotification(null, "", false, 100, getContext());
+        }
+    }
+
+    private void handleActivityMetadata(byte[] value) {
+
+        if (value.length != activityMetadataLength) {
+            return;
+        }
+
+        // byte 0 is the data type: 1 means that each minute is represented by a triplet of bytes
+        int dataType = value[0];
+        // byte 1 to 6 represent a timestamp
+        GregorianCalendar timestamp = MiBandDateConverter.rawBytesToCalendar(value, 1, getDevice().getAddress());
+
+        // counter of all data held by the band
+        int totalDataToRead = (value[7] & 0xff) | ((value[8] & 0xff) << 8);
+        totalDataToRead *= (dataType == MiBandService.MODE_REGULAR_DATA_LEN_MINUTE) ? getBytesPerMinuteOfActivityData() : 1;
+
+
+        // counter of this data block
+        int dataUntilNextHeader = (value[9] & 0xff) | ((value[10] & 0xff) << 8);
+        dataUntilNextHeader *= (dataType == MiBandService.MODE_REGULAR_DATA_LEN_MINUTE) ? getBytesPerMinuteOfActivityData() : 1;
+
+        // there is a total of totalDataToRead that will come in chunks (3 or 4 bytes per minute if dataType == 1 (MiBandService.MODE_REGULAR_DATA_LEN_MINUTE)),
+        // these chunks are usually 20 bytes long and grouped in blocks
+        // after dataUntilNextHeader bytes we will get a new packet of 11 bytes that should be parsed
+        // as we just did
+
+        if (activityStruct.isFirstChunk() && dataUntilNextHeader != 0) {
+
+            GB.updateTransferNotification(getContext().getString(R.string.busy_task_fetch_activity_data), getContext().getString(R.string.user_feedback_miband_activity_data_transfer,
+                    DateTimeUtils.formatDurationHoursMinutes((totalDataToRead / getBytesPerMinuteOfActivityData()), TimeUnit.MINUTES),
+                    DateFormat.getDateTimeInstance().format(timestamp.getTime())), true, 0, getContext());
+        }
+        LOG.info("total data to read: " + totalDataToRead + " len: " + (totalDataToRead / getBytesPerMinuteOfActivityData()) + " minute(s)");
+        LOG.info("data to read until next header: " + dataUntilNextHeader + " len: " + (dataUntilNextHeader / getBytesPerMinuteOfActivityData()) + " minute(s)");
+        LOG.info("TIMESTAMP: " + DateFormat.getDateTimeInstance().format(timestamp.getTime()) + " magic byte: " + dataUntilNextHeader);
+
+        activityStruct.startNewBlock(timestamp, dataUntilNextHeader);
+    }
+
+    private int getBytesPerMinuteOfActivityData() {
+        return hasExtendedActivityData ? 4 : 3;
+    }
+
+    /**
+     * Method to store temporarily the activity data values got from the Mi Band.
+     * <p/>
+     * Since we expect chunks of 20 bytes each, we do not store the received bytes it the length is different.
+     *
+     * @param value
+     */
+    private void bufferActivityData(byte[] value) {
+/*
+        if (scheduledTask != null) {
+            scheduledTask.cancel(true);
+        }
+*/
+        if (activityStruct.hasRoomFor(value)) {
+            if (activityStruct.isValidData(value)) {
+                activityStruct.buffer(value);
+
+/*                scheduledTask = scheduleTaskExecutor.schedule(new Runnable() {
+                    @Override
+                    public void run() {
+                        GB.toast(getContext(), "chiederei " + activityStruct.activityDataTimestampToAck + "   "+ activityStruct.activityDataUntilNextHeader, Toast.LENGTH_LONG, GB.ERROR);
+                        //sendAckDataTransfer(activityStruct.activityDataTimestampToAck, activityStruct.activityDataUntilNextHeader);
+                        LOG.debug("runnable called");
+                    }
+                }, 10l, TimeUnit.SECONDS);
+*/
+                if (activityStruct.isBufferFull()) {
+                    flushActivityDataHolder();
+                }
+            } else {
+                // the length of the chunk is not what we expect. We need to make sense of this data
+                LOG.warn("GOT UNEXPECTED ACTIVITY DATA WITH LENGTH: " + value.length + ", EXPECTED LENGTH: " + activityStruct.activityDataRemainingBytes);
+                getSupport().logMessageContent(value);
+            }
+        } else {
+            GB.toast(getContext(), "error buffering activity data: remaining bytes: " + activityStruct.activityDataRemainingBytes + ", received: " + value.length, Toast.LENGTH_LONG, GB.ERROR);
+            try {
+                TransactionBuilder builder = performInitialized("send stop sync data");
+                builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_CONTROL_POINT), new byte[]{MiBandService.COMMAND_STOP_SYNC_DATA});
+                builder.queue(getQueue());
+                GB.updateTransferNotification(null,"Data transfer failed", false, 0, getContext());
+                handleActivityFetchFinish();
+
+            } catch (IOException e) {
+                LOG.error("error stopping activity sync", e);
+            }
+        }
+    }
+
+    /**
+     * empty the local buffer for activity data, arrange the values received in groups of three and
+     * store them in the DB
+     */
+    private void flushActivityDataHolder() {
+        if (activityStruct == null) {
+            LOG.debug("nothing to flush, struct is already null");
+            return;
+        }
+        int bpm = getBytesPerMinuteOfActivityData();
+        LOG.debug("flushing activity data samples: " + activityStruct.activityDataHolderProgress / bpm);
+        byte category, intensity, steps, heartrate = 0;
+
+        try (DBHandler dbHandler = GBApplication.acquireDB()){
+            MiBandSampleProvider provider = new MiBandSampleProvider(getDevice(), dbHandler.getDaoSession());
+            User user = DBHelper.getUser(dbHandler.getDaoSession());
+            Device device = DBHelper.getDevice(getDevice(), dbHandler.getDaoSession());
+            int minutes = 0;
+            try {
+                int timestampInSeconds = (int) (activityStruct.activityDataTimestampProgress.getTimeInMillis() / 1000);
+                if ((activityStruct.activityDataHolderProgress % bpm) != 0) {
+                    throw new IllegalStateException("Unexpected data, progress should be multiple of " + bpm + ": " + activityStruct.activityDataHolderProgress);
+                }
+                int numSamples = activityStruct.activityDataHolderProgress / bpm;
+                MiBandActivitySample[] samples = new MiBandActivitySample[numSamples];
+
+                for (int i = 0; i < activityStruct.activityDataHolderProgress; i += bpm) {
+                    category = activityStruct.activityDataHolder[i];
+                    intensity = activityStruct.activityDataHolder[i + 1];
+                    steps = activityStruct.activityDataHolder[i + 2];
+                    if (hasExtendedActivityData) {
+                        heartrate = activityStruct.activityDataHolder[i + 3];
+//                        LOG.debug("heartrate received: " + (heartrate & 0xff));
+                    }
+
+                    MiBandActivitySample sample = getSupport().createActivitySample(device, user, timestampInSeconds, provider);
+                    sample.setRawIntensity(intensity & 0xff);
+                    sample.setSteps(steps & 0xff);
+                    sample.setRawKind(category & 0xff);
+                    sample.setHeartRate(heartrate & 0xff);
+                    samples[minutes] = sample;
+
+                    if (LOG.isDebugEnabled()) {
+                        LOG.debug("sample: " + samples[minutes]);
+                    }
+
+                    // next minute
+                    minutes++;
+                    timestampInSeconds += 60;
+                }
+                provider.addGBActivitySamples(samples);
+            } finally {
+                activityStruct.bufferFlushed(minutes);
+            }
+        } catch (Exception ex) {
+            GB.toast(getContext(), ex.getMessage(), Toast.LENGTH_LONG, GB.ERROR, ex);
+        }
+    }
+
+    /**
+     * Acknowledge the transfer of activity data to the Mi Band.
+     * <p/>
+     * After receiving data from the band, it has to be acknowledged. This way the Mi Band will delete
+     * the data it has on record.
+     *
+     * @param time
+     * @param bytesTransferred
+     */
+    private void sendAckDataTransfer(Calendar time, int bytesTransferred) {
+        byte[] ackTime = MiBandDateConverter.calendarToRawBytes(time, getDevice().getAddress());
+        Prefs prefs = GBApplication.getDevicePrefs(getDevice());
+
+        byte[] ackChecksum = new byte[]{
+                (byte) (bytesTransferred & 0xff),
+                (byte) (0xff & (bytesTransferred >> 8))
+        };
+        if (prefs.getBoolean("keep_activity_data_on_device", false)) {
+            ackChecksum = new byte[]{
+                    (byte) (~bytesTransferred & 0xff),
+                    (byte) (0xff & (~bytesTransferred >> 8))
+            };
+        }
+        byte[] ack = new byte[]{
+                MiBandService.COMMAND_CONFIRM_ACTIVITY_DATA_TRANSFER_COMPLETE,
+                ackTime[0],
+                ackTime[1],
+                ackTime[2],
+                ackTime[3],
+                ackTime[4],
+                ackTime[5],
+                ackChecksum[0],
+                ackChecksum[1]
+        };
+        try {
+            TransactionBuilder builder = performInitialized("send acknowledge");
+            builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_CONTROL_POINT), ack);
+            builder.queue(getQueue());
+
+            // flush to the DB after queueing the ACK
+            flushActivityDataHolder();
+
+            //The last data chunk sent by the miband has always length 0.
+            //When we ack this chunk, the transfer is done.
+            if (getDevice().isBusy() && bytesTransferred == 0) {
+                //if we are not clearing miband's data, we have to stop the sync
+                if (prefs.getBoolean("keep_activity_data_on_device", false)) {
+                    builder = performInitialized("send acknowledge");
+                    builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_CONTROL_POINT), new byte[]{MiBandService.COMMAND_STOP_SYNC_DATA});
+                    getSupport().setHighLatency(builder);
+                    builder.queue(getQueue());
+                }
+                handleActivityFetchFinish();
+            }
+        } catch (IOException ex) {
+            LOG.error("Unable to send ack to MI", ex);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/operations/OperationStatus.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/operations/OperationStatus.java
new file mode 100644
index 0000000..ee20d3e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/operations/OperationStatus.java
@@ -0,0 +1,24 @@
+/*  Copyright (C) 2016-2024 Carsten Pfeiffer
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations;
+
+public enum OperationStatus {
+    INITIAL,
+    STARTED,
+    RUNNING,
+    FINISHED
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/operations/UpdateFirmwareOperation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/operations/UpdateFirmwareOperation.java
new file mode 100644
index 0000000..bcbd53a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/operations/UpdateFirmwareOperation.java
@@ -0,0 +1,490 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, Carsten Pfeiffer, Daniele
+    Gobbetti, Taavi Eom盲e
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations;
+
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.content.Context;
+import android.net.Uri;
+import android.widget.Toast;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventDisplayMessage;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandService;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.PlainAction;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceBusyAction;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetProgressAction;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.AbstractMiFirmwareInfo;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.MiBandSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol;
+import nodomain.freeyourgadget.gadgetbridge.util.CheckSums;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+public class UpdateFirmwareOperation extends AbstractMiBand1Operation {
+    private static final Logger LOG = LoggerFactory.getLogger(UpdateFirmwareOperation.class);
+
+    private final Uri uri;
+    private boolean firmwareInfoSent = false;
+    private UpdateCoordinator updateCoordinator;
+    final Prefs prefs = GBApplication.getPrefs();
+
+    public UpdateFirmwareOperation(Uri uri, MiBandSupport support) {
+        super(support);
+        this.uri = uri;
+    }
+
+    @Override
+    protected void enableNeededNotifications(TransactionBuilder builder, boolean enable) {
+    }
+
+    @Override
+    protected void doPerform() throws IOException {
+        MiBandFWHelper mFwHelper = new MiBandFWHelper(uri, getContext());
+
+        AbstractMiFirmwareInfo firmwareInfo = mFwHelper.getFirmwareInfo();
+        if (!firmwareInfo.isGenerallyCompatibleWith(getDevice())) {
+            throw new IOException("Firmware is not compatible with the given device: " + getDevice().getAddress());
+        }
+
+        if (getSupport().supportsHeartRate()) {
+            updateCoordinator = prepareFirmwareInfo1S(firmwareInfo);
+        } else {
+            updateCoordinator = prepareFirmwareInfo(mFwHelper.getFw(), mFwHelper.getFirmwareVersion());
+        }
+
+        updateCoordinator.initNextOperation();
+        if (!updateCoordinator.sendFwInfo()) {
+            displayMessage(getContext(), "Error sending firmware info, aborting.", Toast.LENGTH_LONG, GB.ERROR);
+            done();
+        }
+        /** the firmware will be sent by the {@link UpdateFirmwareOperation#handleNotificationNotif} if the band confirms that the metadata are ok. **/
+    }
+
+    private void done() {
+        LOG.info("Operation done.");
+        updateCoordinator = null;
+        operationFinished();
+        unsetBusy();
+    }
+
+    @Override
+    public boolean onCharacteristicChanged(BluetoothGatt gatt,
+                                           BluetoothGattCharacteristic characteristic) {
+        UUID characteristicUUID = characteristic.getUuid();
+        if (MiBandService.UUID_CHARACTERISTIC_NOTIFICATION.equals(characteristicUUID)) {
+            handleNotificationNotif(characteristic.getValue());
+        } else {
+            super.onCharacteristicChanged(gatt, characteristic);
+        }
+        return false;
+    }
+
+    /**
+     * React to messages sent by the Mi Band to the MiBandService.UUID_CHARACTERISTIC_NOTIFICATION
+     * characteristic,
+     * These messages appear to be always 1 byte long, with values that are listed in MiBandService.
+     * It is not excluded that there are further values which are still unknown.
+     * <p/>
+     * Upon receiving known values that request further action by GB, the appropriate method is called.
+     *
+     * @param value
+     */
+    private void handleNotificationNotif(byte[] value) {
+        if (value.length != 1) {
+            LOG.error("Notifications should be 1 byte long.");
+            getSupport().logMessageContent(value);
+            return;
+        }
+        if (updateCoordinator == null) {
+            LOG.error("received notification when updateCoordinator is null, ignoring (notification content follows):");
+            getSupport().logMessageContent(value);
+            return;
+        }
+
+        switch (value[0]) {
+            case MiBandService.NOTIFY_FW_CHECK_SUCCESS:
+                if (firmwareInfoSent) {
+                    displayMessage(getContext(), "Firmware metadata successfully sent.", Toast.LENGTH_LONG, GB.INFO);
+                    if (!updateCoordinator.sendFwData()) {
+                        displayMessage(getContext(), getContext().getString(R.string.updatefirmwareoperation_updateproblem_do_not_reboot), Toast.LENGTH_LONG, GB.ERROR);
+                        done();
+                    }
+                    firmwareInfoSent = false;
+                } else {
+                    LOG.warn("firmwareInfoSent is false -- not sending firmware data even though we got meta data success notification");
+                }
+                break;
+            case MiBandService.NOTIFY_FW_CHECK_FAILED:
+                displayMessage(getContext(), getContext().getString(R.string.updatefirmwareoperation_metadata_updateproblem), Toast.LENGTH_LONG, GB.ERROR);
+                firmwareInfoSent = false;
+                done();
+                break;
+            case MiBandService.NOTIFY_FIRMWARE_UPDATE_SUCCESS:
+                if (updateCoordinator.initNextOperation()) {
+                    displayMessage(getContext(), "Heart Rate Firmware successfully updated, now updating Mi Band Firmware", Toast.LENGTH_LONG, GB.INFO);
+                    if (!updateCoordinator.sendFwInfo()) {
+                        displayMessage(getContext(), "Error sending firmware info, aborting.", Toast.LENGTH_LONG, GB.ERROR);
+                        done();
+                    }
+                    break;
+                } else if (updateCoordinator.needsReboot()) {
+                    displayMessage(getContext(), getContext().getString(R.string.updatefirmwareoperation_update_complete_rebooting), Toast.LENGTH_LONG, GB.INFO);
+                    GB.updateInstallNotification(getContext().getString(R.string.updatefirmwareoperation_update_complete), false, 100, getContext());
+                    getSupport().onReset(GBDeviceProtocol.RESET_FLAGS_REBOOT);
+                } else {
+                    LOG.error("BUG: Successful firmware update without reboot???");
+                }
+                done();
+                break;
+            case MiBandService.NOTIFY_FIRMWARE_UPDATE_FAILED:
+                //TODO: the firmware transfer failed, but the miband should be still functional with the old firmware. What should we do?
+                displayMessage(getContext(), getContext().getString(R.string.updatefirmwareoperation_updateproblem_do_not_reboot), Toast.LENGTH_LONG, GB.ERROR);
+                GB.updateInstallNotification(getContext().getString(R.string.updatefirmwareoperation_write_failed), false, 0, getContext());
+                done();
+                break;
+
+            default:
+                getSupport().logMessageContent(value);
+                break;
+        }
+    }
+
+    private void displayMessage(Context context, String message, int duration, int severity) {
+        getSupport().handleGBDeviceEvent(new GBDeviceEventDisplayMessage(message, duration, severity));
+    }
+
+    /**
+     * Prepare the MiBand to receive the new firmware data.
+     * Some information about the new firmware version have to be pushed to the MiBand before sending
+     * the actual firmare.
+     * <p/>
+     * The Mi Band will send a notification after receiving these data to confirm if the metadata looks good to it.
+     *
+     * @param newFwVersion
+     */
+    private UpdateCoordinator prepareFirmwareInfo(byte[] fwBytes, int newFwVersion) throws IOException {
+        int newFwSize = fwBytes.length;
+        String mMac = getDevice().getAddress();
+        String[] mMacOctets = mMac.split(":");
+        int currentFwVersion = getSupport().getDeviceInfo().getFirmwareVersion();
+        int checksum = (Integer.decode("0x" + mMacOctets[4]) << 8 | Integer.decode("0x" + mMacOctets[5])) ^ CheckSums.getCRC16(fwBytes);
+
+        byte[] fwInfo = prepareFirmwareUpdateA(currentFwVersion, newFwVersion, newFwSize, checksum);
+        return new SingleUpdateCoordinator(fwInfo, fwBytes, true);
+    }
+
+    private UpdateCoordinator prepareFirmwareInfo1S(AbstractMiFirmwareInfo info) {
+        if (info.isSingleMiBandFirmware()) {
+            throw new IllegalArgumentException("preparing single fw not allowed for 1S");
+        }
+        int fw2Version = info.getSecond().getFirmwareVersion();
+        int fw1Version = info.getFirst().getFirmwareVersion();
+
+        String[] mMacOctets = getDevice().getAddress().split(":");
+        int encodedMac = (Integer.decode("0x" + mMacOctets[4]) << 8 | Integer.decode("0x" + mMacOctets[5]));
+
+        byte[] fw2Bytes = info.getSecond().getFirmwareBytes();
+        int fw2Checksum = CheckSums.getCRC16(fw2Bytes) ^ encodedMac;
+
+        byte[] fw1Bytes = info.getFirst().getFirmwareBytes();
+        int fw1Checksum = encodedMac ^ CheckSums.getCRC16(fw1Bytes);
+
+        // check firmware validity?
+
+        int fw1OldVersion = getSupport().getDeviceInfo().getFirmwareVersion();
+        int fw2OldVersion = getSupport().getDeviceInfo().getHeartrateFirmwareVersion();
+
+        boolean rebootWhenFinished = true;
+        if (info.isSingleMiBandFirmware()) {
+            LOG.info("is single Mi Band firmware");
+            byte[] fw1Info = prepareFirmwareInfo(fw1Bytes, fw1OldVersion, fw1Version, fw1Checksum, 0, rebootWhenFinished /*, progress monitor */);
+            return new SingleUpdateCoordinator(fw1Info, fw1Bytes, rebootWhenFinished);
+        } else {
+            LOG.info("is multi Mi Band firmware, sending fw2 (hr) first");
+            byte[] fw2Info = prepareFirmwareInfo(fw2Bytes, fw2OldVersion, fw2Version, fw2Checksum, 1, rebootWhenFinished /*, progress monitor */);
+            byte[] fw1Info = prepareFirmwareInfo(fw1Bytes, fw1OldVersion, fw1Version, fw1Checksum, 0, rebootWhenFinished /*, progress monitor */);
+            return new DoubleUpdateCoordinator(fw1Info, fw1Bytes, fw2Info, fw2Bytes, rebootWhenFinished);
+        }
+    }
+
+    private byte[] prepareFirmwareInfo(byte[] fwBytes, int currentFwVersion, int newFwVersion, int checksum, int something, boolean reboot) {
+        byte[] fwInfo;
+        switch (something) {
+            case -1:
+                fwInfo = prepareFirmwareUpdateA(currentFwVersion, newFwVersion, fwBytes.length, checksum);
+                break;
+            case -2:
+                fwInfo = prepareFirmwareUpdateB(currentFwVersion, newFwVersion, fwBytes.length, checksum, 0);
+                break;
+            default:
+                fwInfo = prepareFirmwareUpdateB(currentFwVersion, newFwVersion, fwBytes.length, checksum, something);
+        }
+        return fwInfo;
+    }
+
+    private byte[] prepareFirmwareUpdateA(int currentFwVersion, int newFwVersion, int newFwSize, int checksum) {
+        byte[] fwInfo = new byte[]{
+                MiBandService.COMMAND_SEND_FIRMWARE_INFO,
+                (byte) currentFwVersion,
+                (byte) (currentFwVersion >> 8),
+                (byte) (currentFwVersion >> 16),
+                (byte) (currentFwVersion >> 24),
+                (byte) newFwVersion,
+                (byte) (newFwVersion >> 8),
+                (byte) (newFwVersion >> 16),
+                (byte) (newFwVersion >> 24),
+                (byte) newFwSize,
+                (byte) (newFwSize >> 8),
+                (byte) checksum,
+                (byte) (checksum >> 8)
+        };
+        return fwInfo;
+    }
+
+    private byte[] prepareFirmwareUpdateB(int currentFwVersion, int newFwVersion, int newFwSize, int checksum, int something) {
+        byte[] fwInfo = new byte[]{
+                MiBandService.COMMAND_SEND_FIRMWARE_INFO,
+                (byte) currentFwVersion,
+                (byte) (currentFwVersion >> 8),
+                (byte) (currentFwVersion >> 16),
+                (byte) (currentFwVersion >> 24),
+                (byte) newFwVersion,
+                (byte) (newFwVersion >> 8),
+                (byte) (newFwVersion >> 16),
+                (byte) (newFwVersion >> 24),
+                (byte) newFwSize,
+                (byte) (newFwSize >> 8),
+                (byte) checksum,
+                (byte) (checksum >> 8),
+                (byte) something
+        };
+        return fwInfo;
+    }
+
+
+    /**
+     * Method that uploads a firmware (fwbytes) to the Mi Band.
+     * The firmware has to be split into chunks of 20 bytes each, and periodically a COMMAND_SYNC command has to be issued to the Mi Band.
+     * <p/>
+     * The Mi Band will send a notification after receiving this data to confirm if the firmware looks good to it.
+     *
+     * @param fwbytes
+     * @return whether the transfer succeeded or not. Only a BT layer exception will cause the transmission to fail.
+     */
+    private boolean sendFirmwareData(byte[] fwbytes) {
+        int len = fwbytes.length;
+        final int packetLength = 20;
+        int packets = len / packetLength;
+
+        BluetoothGattCharacteristic characteristicControlPoint = getCharacteristic(MiBandService.UUID_CHARACTERISTIC_CONTROL_POINT);
+        BluetoothGattCharacteristic characteristicFWData = getCharacteristic(MiBandService.UUID_CHARACTERISTIC_FIRMWARE_DATA);
+        try {
+            // going from 0 to len
+            int firmwareProgress = 0;
+
+            TransactionBuilder builder = performInitialized("send firmware packet");
+            if (GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress()).getBoolean("low_latency_fw_update", true)) {
+                getSupport().setLowLatency(builder);
+            }
+            for (int i = 0; i < packets; i++) {
+                byte[] fwChunk = Arrays.copyOfRange(fwbytes, i * packetLength, i * packetLength + packetLength);
+
+                builder.write(characteristicFWData, fwChunk);
+                firmwareProgress += packetLength;
+
+                int progressPercent = (int) ((((float) firmwareProgress) / len) * 100);
+                if ((i > 0) && (i % 50 == 0)) {
+                    builder.write(characteristicControlPoint, new byte[]{MiBandService.COMMAND_SYNC});
+                    builder.add(new SetProgressAction(getContext().getString(R.string.updatefirmwareoperation_update_in_progress), true, progressPercent, getContext()));
+                }
+            }
+
+            if (firmwareProgress < len) {
+                byte[] lastChunk = Arrays.copyOfRange(fwbytes, packets * packetLength, len);
+                builder.write(characteristicFWData, lastChunk);
+                firmwareProgress = len;
+            }
+
+            builder.write(characteristicControlPoint, new byte[]{MiBandService.COMMAND_SYNC});
+            builder.queue(getQueue());
+
+        } catch (IOException ex) {
+            LOG.error("Unable to send fw to MI", ex);
+            GB.updateInstallNotification(getContext().getString(R.string.updatefirmwareoperation_firmware_not_sent), false, 0, getContext());
+            return false;
+        }
+        return true;
+    }
+
+    private abstract class UpdateCoordinator {
+        private final boolean reboot;
+
+        public UpdateCoordinator(boolean needsReboot) {
+            this.reboot = needsReboot;
+        }
+
+        abstract byte[] getFirmwareInfo();
+
+        public abstract byte[] getFirmwareBytes();
+
+        public abstract boolean initNextOperation();
+
+        public boolean sendFwInfo() {
+            try {
+                TransactionBuilder builder = performInitialized("send firmware info");
+//                getSupport().setLowLatency(builder);
+                builder.add(new SetDeviceBusyAction(getDevice(), getContext().getString(R.string.updating_firmware), getContext()));
+                builder.add(new FirmwareInfoSentAction()); // Note: *before* actually sending the info, otherwise it's too late!
+                builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_CONTROL_POINT), getFirmwareInfo());
+                builder.queue(getQueue());
+                return true;
+            } catch (IOException e) {
+                LOG.error("Error sending firmware info: " + e.getLocalizedMessage(), e);
+                return false;
+            }
+        }
+
+        public boolean sendFwData() {
+//            if (true) {
+//            return true; // FIXME: temporarily disabled firmware sending
+//            }
+            return sendFirmwareData(getFirmwareBytes());
+        }
+
+        public boolean needsReboot() {
+            return reboot;
+        }
+    }
+
+    private class SingleUpdateCoordinator extends UpdateCoordinator {
+
+        private final byte[] fwInfo;
+        private final byte[] fwData;
+
+        public SingleUpdateCoordinator(byte[] fwInfo, byte[] fwData, boolean reboot) {
+            super(reboot);
+            this.fwInfo = fwInfo;
+            this.fwData = fwData;
+        }
+
+        @Override
+        public byte[] getFirmwareInfo() {
+            return fwInfo;
+        }
+
+        @Override
+        public byte[] getFirmwareBytes() {
+            return fwData;
+        }
+
+        @Override
+        public boolean initNextOperation() {
+            return false;
+        }
+    }
+
+    enum State {
+        INITIAL,
+        SEND_FW2,
+        SEND_FW1,
+        FINISHED,
+        UNKNOWN
+    }
+
+    private class DoubleUpdateCoordinator extends UpdateCoordinator {
+
+        private final byte[] fw1Info;
+        private final byte[] fw1Data;
+
+        private final byte[] fw2Info;
+        private final byte[] fw2Data;
+
+        private byte[] currentFwInfo;
+        private byte[] currentFwData;
+
+        private State state = State.INITIAL;
+
+        public DoubleUpdateCoordinator(byte[] fw1Info, byte[] fw1Data, byte[] fw2Info, byte[] fw2Data, boolean reboot) {
+            super(reboot);
+            this.fw1Info = fw1Info;
+            this.fw1Data = fw1Data;
+            this.fw2Info = fw2Info;
+            this.fw2Data = fw2Data;
+
+            // start with fw2 (heart rate)
+            currentFwInfo = fw2Info;
+            currentFwData = fw2Data;
+        }
+
+        @Override
+        public byte[] getFirmwareInfo() {
+            return currentFwInfo;
+        }
+
+        @Override
+        public byte[] getFirmwareBytes() {
+            return currentFwData;
+        }
+
+        @Override
+        public boolean initNextOperation() {
+            switch (state) {
+                case INITIAL:
+                    currentFwInfo = fw2Info;
+                    currentFwData = fw2Data;
+                    state = State.SEND_FW2;
+                    return true;
+                case SEND_FW2:
+                    currentFwInfo = fw1Info;
+                    currentFwData = fw1Data;
+                    state = State.SEND_FW1;
+                    return fw1Info != null && fw1Data != null;
+                case SEND_FW1:
+                    currentFwInfo = null;
+                    currentFwData = null;
+                    state = State.FINISHED;
+                    return false; // we're done
+                default:
+                    state = State.UNKNOWN;
+            }
+            return false;
+        }
+    }
+
+    private class FirmwareInfoSentAction extends PlainAction {
+        @Override
+        public boolean run(BluetoothGatt gatt) {
+            if (isOperationRunning()) {
+                firmwareInfoSent = true;
+            }
+            return true;
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/mijia_lywsd/MijiaLywsdSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/mijia_lywsd/MijiaLywsdSupport.java
new file mode 100644
index 0000000..f89aefd
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/mijia_lywsd/MijiaLywsdSupport.java
@@ -0,0 +1,329 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo, Severin von Wnuck-Lipinski
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.mijia_lywsd;
+
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.widget.Toast;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Objects;
+import java.util.SimpleTimeZone;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo;
+import nodomain.freeyourgadget.gadgetbridge.devices.mijia_lywsd.AbstractMijiaLywsdCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.BatteryState;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.GattService;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.IntentListener;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo.DeviceInfoProfile;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.*;
+import static nodomain.freeyourgadget.gadgetbridge.util.GB.hexdump;
+
+public class MijiaLywsdSupport extends AbstractBTLEDeviceSupport {
+
+    private static final Logger LOG = LoggerFactory.getLogger(MijiaLywsdSupport.class);
+    private static final UUID UUID_TIME = UUID.fromString("ebe0ccb7-7a0a-4b0c-8a1a-6ff2997da3a6");
+    private static final UUID UUID_BATTERY = UUID.fromString("ebe0ccc4-7a0a-4b0c-8a1a-6ff2997da3a6");
+    private static final UUID UUID_SCALE = UUID.fromString("ebe0ccbe-7a0a-4b0c-8a1a-6ff2997da3a6");
+    private static final UUID UUID_COMFORT_LEVEL = UUID.fromString("ebe0ccd7-7a0a-4b0c-8a1a-6ff2997da3a6");
+    private static final UUID UUID_CONN_INTERVAL = UUID.fromString("ebe0ccd8-7a0a-4b0c-8a1a-6ff2997da3a6");
+    private final DeviceInfoProfile<MijiaLywsdSupport> deviceInfoProfile;
+    private final GBDeviceEventVersionInfo versionCmd = new GBDeviceEventVersionInfo();
+    private final GBDeviceEventBatteryInfo batteryCmd = new GBDeviceEventBatteryInfo();
+    private final IntentListener mListener = new IntentListener() {
+        @Override
+        public void notify(Intent intent) {
+            String s = intent.getAction();
+            if (Objects.equals(s, DeviceInfoProfile.ACTION_DEVICE_INFO)) {
+                handleDeviceInfo((nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo.DeviceInfo) intent.getParcelableExtra(DeviceInfoProfile.EXTRA_DEVICE_INFO));
+            }
+        }
+    };
+
+    // Length of comfort level characteristic for different devices
+    public static final int COMFORT_LEVEL_LENGTH_LYWSD03 = 6;
+    public static final int COMFORT_LEVEL_LENGTH_XMWSDJ04 = 8;
+
+    public MijiaLywsdSupport() {
+        super(LOG);
+        addSupportedService(GattService.UUID_SERVICE_GENERIC_ACCESS);
+        addSupportedService(GattService.UUID_SERVICE_GENERIC_ATTRIBUTE);
+        addSupportedService(GattService.UUID_SERVICE_DEVICE_INFORMATION);
+        addSupportedService(UUID.fromString("ebe0ccb0-7a0a-4b0c-8a1a-6ff2997da3a6"));
+        deviceInfoProfile = new DeviceInfoProfile<>(this);
+        deviceInfoProfile.addListener(mListener);
+        addSupportedProfile(deviceInfoProfile);
+    }
+
+    @Override
+    protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
+        builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext()));
+        requestDeviceInfo(builder);
+
+        final boolean supportsSetTime = getCoordinator().supportsSetTime();
+        if (supportsSetTime && GBApplication.getPrefs().getBoolean("datetime_synconconnect", true)) {
+            setTime(builder);
+        }
+
+        getBatteryInfo(builder);
+        getComfortLevel(builder);
+        setConnectionInterval(builder);
+        setInitialized(builder);
+        return builder;
+    }
+
+    protected AbstractMijiaLywsdCoordinator getCoordinator() {
+        return (AbstractMijiaLywsdCoordinator) gbDevice.getDeviceCoordinator();
+    }
+
+    private void setTime(TransactionBuilder builder) {
+        BluetoothGattCharacteristic timeCharacteristc = getCharacteristic(MijiaLywsdSupport.UUID_TIME);
+        long ts = System.currentTimeMillis();
+        byte offsetHours = (byte) (SimpleTimeZone.getDefault().getOffset(ts) / (1000 * 60 * 60));
+        ts = (ts + 250 + 500) / 1000; // round to seconds with +250 ms to compensate for BLE connection interval
+        builder.write(timeCharacteristc, new byte[]{
+                (byte) (ts & 0xff),
+                (byte) ((ts >> 8) & 0xff),
+                (byte) ((ts >> 16) & 0xff),
+                (byte) ((ts >> 24) & 0xff),
+                offsetHours});
+    }
+
+    private void setConnectionInterval(TransactionBuilder builder) {
+        BluetoothGattCharacteristic intervalCharacteristc = getCharacteristic(MijiaLywsdSupport.UUID_CONN_INTERVAL);
+        builder.write(intervalCharacteristc, new byte[]{(byte) 0xf4, (byte) 0x01}); // maximum interval of 500 ms
+    }
+
+    private void getBatteryInfo(TransactionBuilder builder) {
+        BluetoothGattCharacteristic batteryCharacteristc = getCharacteristic(MijiaLywsdSupport.UUID_BATTERY);
+        builder.read(batteryCharacteristc);
+    }
+
+    private void getComfortLevel(TransactionBuilder builder) {
+        BluetoothGattCharacteristic comfortCharacteristc = getCharacteristic(MijiaLywsdSupport.UUID_COMFORT_LEVEL);
+        builder.read(comfortCharacteristc);
+    }
+
+    private void setTemperatureScale(TransactionBuilder builder, SharedPreferences prefs) {
+        String scale = prefs.getString(PREF_TEMPERATURE_SCALE_CF, "");
+        BluetoothGattCharacteristic scaleCharacteristc = getCharacteristic(MijiaLywsdSupport.UUID_SCALE);
+        builder.write(scaleCharacteristc, new byte[]{(byte) ("f".equals(scale) ? 0x01 : 0xff)});
+    }
+
+    private void setComfortLevel(TransactionBuilder builder, SharedPreferences prefs) {
+        int length = prefs.getInt(PREF_MIJIA_LYWSD_COMFORT_CHARACTERISTIC_LENGTH, 0);
+        int temperatureLower = prefs.getInt(PREF_MIJIA_LYWSD_COMFORT_TEMPERATURE_LOWER, 19);
+        int temperatureUpper = prefs.getInt(PREF_MIJIA_LYWSD_COMFORT_TEMPERATURE_UPPER, 27);
+        int humidityLower = prefs.getInt(PREF_MIJIA_LYWSD_COMFORT_HUMIDITY_LOWER, 20);
+        int humidityUpper = prefs.getInt(PREF_MIJIA_LYWSD_COMFORT_HUMIDITY_UPPER, 85);
+
+        // Ignore invalid values
+        if (temperatureLower > temperatureUpper || humidityLower > humidityUpper)
+            return;
+
+        BluetoothGattCharacteristic comfortCharacteristc = getCharacteristic(MijiaLywsdSupport.UUID_COMFORT_LEVEL);
+        ByteBuffer buf = ByteBuffer.allocate(length);
+
+        buf.order(ByteOrder.LITTLE_ENDIAN);
+
+        switch (length) {
+            case COMFORT_LEVEL_LENGTH_LYWSD03:
+                buf.putShort((short)(temperatureUpper * 100));
+                buf.putShort((short)(temperatureLower * 100));
+                buf.put((byte)humidityUpper);
+                buf.put((byte)humidityLower);
+                break;
+            case COMFORT_LEVEL_LENGTH_XMWSDJ04:
+                buf.putShort((short)(temperatureUpper * 10));
+                buf.putShort((short)(temperatureLower * 10));
+                buf.putShort((short)(humidityUpper * 10));
+                buf.putShort((short)(humidityLower * 10));
+                break;
+            default:
+                return;
+        }
+
+        builder.write(comfortCharacteristc, buf.array());
+    }
+
+    private void handleBatteryInfo(byte[] value, int status) {
+        if (status == BluetoothGatt.GATT_SUCCESS) {
+            batteryCmd.level = ((short) value[0]);
+            batteryCmd.state = (batteryCmd.level > 20) ? BatteryState.BATTERY_NORMAL : BatteryState.BATTERY_LOW;
+            handleGBDeviceEvent(batteryCmd);
+        }
+    }
+
+    private void handleComfortLevel(byte[] value, int status) {
+        if (status != BluetoothGatt.GATT_SUCCESS)
+            return;
+
+        ByteBuffer buf = ByteBuffer.wrap(value);
+        int temperatureLower, temperatureUpper;
+        int humidityLower, humidityUpper;
+
+        buf.order(ByteOrder.LITTLE_ENDIAN);
+
+        switch (value.length) {
+            case COMFORT_LEVEL_LENGTH_LYWSD03:
+                temperatureUpper = buf.getShort() / 100;
+                temperatureLower = buf.getShort() / 100;
+                humidityUpper = buf.get();
+                humidityLower = buf.get();
+                break;
+            case COMFORT_LEVEL_LENGTH_XMWSDJ04:
+                temperatureUpper = buf.getShort() / 10;
+                temperatureLower = buf.getShort() / 10;
+                humidityUpper = buf.getShort() / 10;
+                humidityLower = buf.getShort() / 10;
+                break;
+            default:
+                LOG.error("Unknown comfort level characteristic: {}", hexdump(value));
+                return;
+        }
+
+        SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress());
+
+        prefs.edit()
+             .putInt(PREF_MIJIA_LYWSD_COMFORT_CHARACTERISTIC_LENGTH, value.length)
+             .putInt(PREF_MIJIA_LYWSD_COMFORT_TEMPERATURE_LOWER, temperatureLower)
+             .putInt(PREF_MIJIA_LYWSD_COMFORT_TEMPERATURE_UPPER, temperatureUpper)
+             .putInt(PREF_MIJIA_LYWSD_COMFORT_HUMIDITY_LOWER, humidityLower)
+             .putInt(PREF_MIJIA_LYWSD_COMFORT_HUMIDITY_UPPER, humidityUpper)
+             .apply();
+    }
+
+    private void requestDeviceInfo(TransactionBuilder builder) {
+        LOG.debug("Requesting Device Info!");
+        deviceInfoProfile.requestDeviceInfo(builder);
+    }
+
+    private void setInitialized(TransactionBuilder builder) {
+        builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZED, getContext()));
+    }
+
+    @Override
+    public boolean useAutoConnect() {
+        return false;
+    }
+
+    private void handleDeviceInfo(nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo.DeviceInfo info) {
+        LOG.warn("Device info: " + info);
+        versionCmd.hwVersion = info.getHardwareRevision();
+        versionCmd.fwVersion = info.getFirmwareRevision();
+        handleGBDeviceEvent(versionCmd);
+    }
+
+    @Override
+    public void onSetTime() {
+        TransactionBuilder builder;
+        try {
+            builder = performInitialized("Set time");
+            setTime(builder);
+            builder.queue(getQueue());
+        } catch (IOException e) {
+            LOG.error("Error setting time on LYWSD02", e);
+            GB.toast("Error setting time on LYWSD02", Toast.LENGTH_LONG, GB.ERROR, e);
+        }
+    }
+
+    @Override
+    public boolean onCharacteristicChanged(BluetoothGatt gatt,
+                                           BluetoothGattCharacteristic characteristic) {
+        if (super.onCharacteristicChanged(gatt, characteristic)) {
+            return true;
+        }
+
+        UUID characteristicUUID = characteristic.getUuid();
+        LOG.info("Unhandled characteristic changed: " + characteristicUUID);
+        return false;
+    }
+
+    @Override
+    public boolean onCharacteristicRead(BluetoothGatt gatt,
+                                        BluetoothGattCharacteristic characteristic, int status) {
+        if (super.onCharacteristicRead(gatt, characteristic, status)) {
+            return true;
+        }
+        UUID characteristicUUID = characteristic.getUuid();
+
+        if (MijiaLywsdSupport.UUID_BATTERY.equals(characteristicUUID)) {
+            handleBatteryInfo(characteristic.getValue(), status);
+            return true;
+        }
+
+        if (MijiaLywsdSupport.UUID_COMFORT_LEVEL.equals(characteristicUUID)) {
+            handleComfortLevel(characteristic.getValue(), status);
+            return true;
+        }
+
+        LOG.info("Unhandled characteristic read: " + characteristicUUID);
+        return false;
+    }
+
+    @Override
+    public void onSendConfiguration(String config) {
+        SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress());
+
+        try {
+            TransactionBuilder builder = performInitialized("Sending configuration for option: " + config);
+
+            switch (config) {
+                case PREF_TEMPERATURE_SCALE_CF:
+                    setTemperatureScale(builder, prefs);
+                    break;
+                case PREF_MIJIA_LYWSD_COMFORT_TEMPERATURE_LOWER:
+                case PREF_MIJIA_LYWSD_COMFORT_TEMPERATURE_UPPER:
+                case PREF_MIJIA_LYWSD_COMFORT_HUMIDITY_LOWER:
+                case PREF_MIJIA_LYWSD_COMFORT_HUMIDITY_UPPER:
+                    setComfortLevel(builder, prefs);
+                    break;
+            }
+
+            builder.queue(getQueue());
+        } catch (IOException e) {
+            LOG.error("Error setting configuration on LYWSD02", e);
+            GB.toast("Error setting configuration", Toast.LENGTH_LONG, GB.ERROR, e);
+        }
+    }
+
+    @Override
+    public boolean getImplicitCallbackModify() {
+        return true;
+    }
+
+    @Override
+    public boolean getSendWriteRequestResponse() {
+        return false;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miscale/MiCompositionScaleDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miscale/MiCompositionScaleDeviceSupport.java
new file mode 100644
index 0000000..d6d882b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miscale/MiCompositionScaleDeviceSupport.java
@@ -0,0 +1,164 @@
+/*  Copyright (C) 2019-2024 Andreas Shimokawa, Arjan Schrijver, Damien Gaignon,
+    Jean-Fran莽ois Greffier
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.miscale;
+
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.os.Parcelable;
+import android.widget.Toast;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo;
+import nodomain.freeyourgadget.gadgetbridge.devices.miscale.MiScaleSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.MiScaleWeightSample;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.GattCharacteristic;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.GattService;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.IntentListener;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo.DeviceInfo;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo.DeviceInfoProfile;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class MiCompositionScaleDeviceSupport extends AbstractBTLEDeviceSupport {
+    private static final Logger LOG = LoggerFactory.getLogger(MiCompositionScaleDeviceSupport.class);
+
+    private final DeviceInfoProfile<MiCompositionScaleDeviceSupport> deviceInfoProfile;
+    private final GBDeviceEventVersionInfo versionCmd = new GBDeviceEventVersionInfo();
+
+    public MiCompositionScaleDeviceSupport() {
+        super(LOG);
+        addSupportedService(GattService.UUID_SERVICE_GENERIC_ACCESS);
+        addSupportedService(GattService.UUID_SERVICE_GENERIC_ATTRIBUTE);
+        addSupportedService(GattService.UUID_SERVICE_DEVICE_INFORMATION);
+        addSupportedService(GattService.UUID_SERVICE_BODY_COMPOSITION);
+        addSupportedService(UUID.fromString("00001530-0000-3512-2118-0009af100700"));
+
+        deviceInfoProfile = new DeviceInfoProfile<>(this);
+        final IntentListener mListener = intent -> {
+            if (DeviceInfoProfile.ACTION_DEVICE_INFO.equals(intent.getAction())) {
+                final Parcelable deviceInfo = intent.getParcelableExtra(DeviceInfoProfile.EXTRA_DEVICE_INFO);
+                if (deviceInfo != null) {
+                    handleDeviceInfo((DeviceInfo) deviceInfo);
+                }
+            }
+        };
+        deviceInfoProfile.addListener(mListener);
+        addSupportedProfile(deviceInfoProfile);
+    }
+
+    @Override
+    protected TransactionBuilder initializeDevice(final TransactionBuilder builder) {
+        builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext()));
+
+        LOG.debug("Requesting Device Info!");
+        deviceInfoProfile.requestDeviceInfo(builder);
+        builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZED, getContext()));
+
+        // Weight and body composition
+        builder.setCallback(this);
+        builder.notify(getCharacteristic(GattCharacteristic.UUID_CHARACTERISTIC_BODY_COMPOSITION_MEASUREMENT), true);
+
+        return builder;
+    }
+
+    @Override
+    public boolean onCharacteristicChanged(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic) {
+        if (super.onCharacteristicChanged(gatt, characteristic)) {
+            return true;
+        }
+
+        final UUID characteristicUUID = characteristic.getUuid();
+        if (characteristicUUID.equals(GattCharacteristic.UUID_CHARACTERISTIC_BODY_COMPOSITION_MEASUREMENT)) {
+            final byte[] data = characteristic.getValue();
+
+            final byte flags = data[1];
+            final boolean stabilized = testBit(flags, 5) && !testBit(flags, 7);
+
+            if (stabilized) {
+                final int year = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, 2);
+                final int month = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 4);
+                final int day = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 5);
+                final int hour = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 6);
+                final int minute = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 7);
+                final int second = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 8);
+                final Calendar c = GregorianCalendar.getInstance();
+                c.set(year, month - 1, day, hour, minute, second);
+                final Date date = c.getTime();
+
+                float weightKg = WeightMeasurement.weightToKg(
+                        characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, 11),
+                        flags
+                );
+                handleWeightInfo(date, weightKg);
+            }
+
+            return true;
+        }
+
+        return false;
+    }
+
+    private boolean testBit(final byte value, final int offset) {
+        return ((value >> offset) & 1) == 1;
+    }
+
+    private void handleDeviceInfo(final DeviceInfo info) {
+        LOG.debug("Device info: {}", info);
+        versionCmd.hwVersion = info.getHardwareRevision();
+        versionCmd.fwVersion = info.getSoftwareRevision();
+        handleGBDeviceEvent(versionCmd);
+    }
+
+    private void handleWeightInfo(final Date date, final float weightKg) {
+        GB.toast(getContext().getString(R.string.weight_kg, weightKg), Toast.LENGTH_SHORT, GB.INFO);
+
+        try (DBHandler db = GBApplication.acquireDB()) {
+            final MiScaleSampleProvider provider = new MiScaleSampleProvider(getDevice(), db.getDaoSession());
+            final Long userId = DBHelper.getUser(db.getDaoSession()).getId();
+            final Long deviceId = DBHelper.getDevice(getDevice(), db.getDaoSession()).getId();
+
+            provider.addSample(new MiScaleWeightSample(
+                    date.getTime(),
+                    deviceId,
+                    userId,
+                    weightKg
+            ));
+        } catch (final Exception e) {
+            LOG.error("Error saving weight sample", e);
+        }
+    }
+
+    @Override
+    public boolean useAutoConnect() {
+        return false;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miscale/MiSmartScaleDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miscale/MiSmartScaleDeviceSupport.java
new file mode 100644
index 0000000..ed55ae1
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miscale/MiSmartScaleDeviceSupport.java
@@ -0,0 +1,266 @@
+/*  Copyright (C) 2024 Severin von Wnuck-Lipinski
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.miscale;
+
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.content.SharedPreferences;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.GregorianCalendar;
+import java.util.List;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandService;
+import nodomain.freeyourgadget.gadgetbridge.devices.miscale.MiScaleSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.MiScaleWeightSample;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.GattCharacteristic;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.GattService;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo.DeviceInfo;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo.DeviceInfoProfile;
+import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol;
+
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.*;
+
+public class MiSmartScaleDeviceSupport extends AbstractBTLEDeviceSupport {
+    private static final Logger LOG = LoggerFactory.getLogger(MiSmartScaleDeviceSupport.class);
+
+    private static final UUID UUID_CHARACTERISTIC_CONFIG = UUID.fromString("00001542-0000-3512-2118-0009af100700");
+    private static final UUID UUID_CHARACTERISTIC_WEIGHT_HISTORY = UUID.fromString("00002a2f-0000-3512-2118-0009af100700");
+
+    // There's unfortunately no way to query the config options, they can only be set
+    private static final byte CFG_WEIGHT_UNIT = (byte)0x04;
+    private static final byte CFG_SMALL_OBJECTS = (byte)0x10;
+    private static final byte CFG_RESET_HISTORY = (byte)0x12;
+
+    private static final byte CMD_HISTORY_START = (byte)0x01;
+    private static final byte CMD_HISTORY_QUERY = (byte)0x02;
+    private static final byte CMD_HISTORY_COMPLETE = (byte)0x03;
+    private static final byte CMD_HISTORY_END = (byte)0x04;
+
+    // Threshold for small objects
+    private static final int SMALL_OBJECT_MAX_WEIGHT = 10;
+
+    private long userId = -1;
+
+    private final DeviceInfoProfile<MiSmartScaleDeviceSupport> deviceInfoProfile;
+
+    public MiSmartScaleDeviceSupport() {
+        super(LOG);
+
+        // Get unique user ID for weight history querying
+        try (DBHandler db = GBApplication.acquireDB()) {
+            userId = DBHelper.getUser(db.getDaoSession()).getId();
+        } catch (Exception e) {
+            LOG.error("Error acquiring database", e);
+        }
+
+        deviceInfoProfile = new DeviceInfoProfile<>(this);
+        deviceInfoProfile.addListener(intent -> {
+            if (!DeviceInfoProfile.ACTION_DEVICE_INFO.equals(intent.getAction()))
+                return;
+
+            DeviceInfo info = intent.getParcelableExtra(DeviceInfoProfile.EXTRA_DEVICE_INFO);
+
+            if (info == null)
+                return;
+
+            GBDeviceEventVersionInfo event = new GBDeviceEventVersionInfo();
+            event.fwVersion = info.getSoftwareRevision();
+            event.hwVersion = info.getHardwareRevision();
+
+            handleGBDeviceEvent(event);
+        });
+
+        addSupportedService(GattService.UUID_SERVICE_GENERIC_ACCESS);
+        addSupportedService(GattService.UUID_SERVICE_GENERIC_ATTRIBUTE);
+        addSupportedService(GattService.UUID_SERVICE_DEVICE_INFORMATION);
+        addSupportedService(GattService.UUID_SERVICE_WEIGHT_SCALE);
+        addSupportedService(UUID.fromString(MiBandService.UUID_SERVICE_WEIGHT_SERVICE));
+        addSupportedProfile(deviceInfoProfile);
+    }
+
+    @Override
+    protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
+        builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext()));
+
+        deviceInfoProfile.requestDeviceInfo(builder);
+
+        if (GBApplication.getPrefs().getBoolean("datetime_synconconnect", true))
+            setTime(builder);
+
+        builder.notify(getCharacteristic(GattCharacteristic.UUID_CHARACTERISTIC_WEIGHT_MEASUREMENT), true);
+        builder.notify(getCharacteristic(UUID_CHARACTERISTIC_WEIGHT_HISTORY), true);
+
+        // Query weight measurements saved by the scale
+        sendHistoryCommand(builder, CMD_HISTORY_START, true);
+        sendHistoryCommand(builder, CMD_HISTORY_QUERY, false);
+
+        builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZED, getContext()));
+
+        return builder;
+    }
+
+    @Override
+    public boolean onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
+        if (super.onCharacteristicChanged(gatt, characteristic))
+            return true;
+
+        UUID uuid = characteristic.getUuid();
+
+        if (!uuid.equals(GattCharacteristic.UUID_CHARACTERISTIC_WEIGHT_MEASUREMENT) &&
+            !uuid.equals(UUID_CHARACTERISTIC_WEIGHT_HISTORY))
+            return false;
+
+        byte[] data = characteristic.getValue();
+
+        if (data.length == 1 && data[0] == CMD_HISTORY_COMPLETE) {
+            TransactionBuilder builder = createTransactionBuilder("ack");
+
+            // Acknowledge weight history reception
+            sendHistoryCommand(builder, CMD_HISTORY_COMPLETE, false);
+            sendHistoryCommand(builder, CMD_HISTORY_END, true);
+            builder.notify(getCharacteristic(UUID_CHARACTERISTIC_WEIGHT_HISTORY), false);
+            builder.queue(getQueue());
+        } else {
+            ByteBuffer buf = ByteBuffer.wrap(characteristic.getValue());
+            List<WeightMeasurement> measurements = new ArrayList<>();
+            WeightMeasurement measurement = WeightMeasurement.decode(buf);
+
+            // Weight history characteristic often has two measurements in one packet
+            while (measurement != null) {
+                measurements.add(measurement);
+                measurement = WeightMeasurement.decode(buf);
+            }
+
+            saveMeasurements(measurements);
+        }
+
+        return true;
+    }
+
+    @Override
+    public void onReset(int flags) {
+        if ((flags & GBDeviceProtocol.RESET_FLAGS_FACTORY_RESET) == 0)
+            return;
+
+        try {
+            TransactionBuilder builder = performInitialized("reset");
+
+            setConfigValue(builder, CFG_RESET_HISTORY, (byte)0x00);
+            builder.queue(getQueue());
+        } catch (IOException e) {
+            LOG.error("Error", e);
+        }
+    }
+
+    @Override
+    public void onSendConfiguration(String config) {
+        SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress());
+
+        try {
+            TransactionBuilder builder = performInitialized("config");
+
+            if (config.equals(PREF_MISCALE_WEIGHT_UNIT)) {
+                int unit = Integer.parseInt(prefs.getString(PREF_MISCALE_WEIGHT_UNIT, "0"));
+
+                setConfigValue(builder, CFG_WEIGHT_UNIT, (byte)unit);
+            } else if (config.equals(PREF_MISCALE_SMALL_OBJECTS)) {
+                boolean enabled = prefs.getBoolean(PREF_MISCALE_SMALL_OBJECTS, false);
+
+                setConfigValue(builder, CFG_SMALL_OBJECTS, enabled ? (byte)0x01: (byte)0x00);
+            }
+
+            builder.queue(getQueue());
+        } catch (IOException e) {
+            LOG.error("Error", e);
+        }
+    }
+
+    @Override
+    public boolean useAutoConnect() {
+        return false;
+    }
+
+    private void setTime(TransactionBuilder builder) {
+        GregorianCalendar now = BLETypeConversions.createCalendar();
+        byte[] time = BLETypeConversions.calendarToCurrentTime(now, 0);
+
+        builder.write(getCharacteristic(GattCharacteristic.UUID_CHARACTERISTIC_CURRENT_TIME), time);
+    }
+
+    private void setConfigValue(TransactionBuilder builder, byte config, byte value) {
+        byte[] data = new byte[] { (byte)0x06, config, (byte)0x00, value };
+
+        builder.write(getCharacteristic(UUID_CHARACTERISTIC_CONFIG), data);
+    }
+
+    private void sendHistoryCommand(TransactionBuilder builder, byte cmd, boolean includeUserId) {
+        ByteBuffer buf = ByteBuffer.allocate(includeUserId ? 5 : 1);
+
+        buf.put(cmd);
+
+        // The user ID is directly related to the account ID in the Zepp Life app
+        if (includeUserId)
+            buf.putInt((int)userId);
+
+        builder.write(getCharacteristic(UUID_CHARACTERISTIC_WEIGHT_HISTORY), buf.array());
+    }
+
+    private void saveMeasurements(List<WeightMeasurement> measurements) {
+        SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress());
+        boolean allowSmallObjects = prefs.getBoolean(PREF_MISCALE_SMALL_OBJECTS, false);
+
+        try (DBHandler db = GBApplication.acquireDB()) {
+            MiScaleSampleProvider provider = new MiScaleSampleProvider(getDevice(), db.getDaoSession());
+            List<MiScaleWeightSample> samples = new ArrayList<>();
+            Long userId = DBHelper.getUser(db.getDaoSession()).getId();
+            Long deviceId = DBHelper.getDevice(getDevice(), db.getDaoSession()).getId();
+
+            for (WeightMeasurement measurement : measurements) {
+                // Skip measurements of small objects if not allowed
+                if (!allowSmallObjects && measurement.getWeightKg() < SMALL_OBJECT_MAX_WEIGHT)
+                    continue;
+
+                samples.add(new MiScaleWeightSample(
+                    measurement.getTimestamp().getTime(),
+                    deviceId,
+                    userId,
+                    measurement.getWeightKg()
+                ));
+            }
+
+            provider.addSamples(samples);
+        } catch (Exception e) {
+            LOG.error("Error acquiring database", e);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miscale/WeightMeasurement.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miscale/WeightMeasurement.java
new file mode 100644
index 0000000..237a897
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miscale/WeightMeasurement.java
@@ -0,0 +1,87 @@
+/*  Copyright (C) 2024 Severin von Wnuck-Lipinski
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.miscale;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Calendar;
+import java.util.Date;
+
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
+
+public class WeightMeasurement {
+    private static final Logger LOG = LoggerFactory.getLogger(WeightMeasurement.class);
+
+    private Date timestamp;
+    private float weightKg;
+
+    public Date getTimestamp() {
+        return timestamp;
+    }
+
+    public float getWeightKg() {
+        return weightKg;
+    }
+
+    private WeightMeasurement(Date timestamp, float weightKg) {
+        LOG.debug("Measurement: timestamp={}, weightKg={}", timestamp, String.format("%.2f", weightKg));
+
+        this.timestamp = timestamp;
+        this.weightKg = weightKg;
+    }
+
+    public static WeightMeasurement decode(ByteBuffer buf) {
+        if (buf.remaining() < 10)
+            return null;
+
+        buf.order(ByteOrder.LITTLE_ENDIAN);
+
+        byte flags = buf.get();
+        boolean stabilized = testBit(flags, 5) && !testBit(flags, 7);
+
+        // Only decode measurement once weight reading has stabilized
+        if (!stabilized)
+            return null;
+
+        float weightKg = weightToKg(buf.getShort(), flags);
+
+        byte[] timestamp = new byte[7];
+        buf.get(timestamp);
+        Calendar calendar = BLETypeConversions.rawBytesToCalendar(timestamp);
+
+        return new WeightMeasurement(calendar.getTime(), weightKg);
+    }
+
+    public static float weightToKg(float weight, byte flags) {
+        boolean isLbs = testBit(flags, 0);
+        boolean isJin = testBit(flags, 4);
+
+        if (isLbs)
+            return (weight / 100) * 0.45359237f;
+        else if (isJin)
+            return (weight / 100) * 0.5f;
+
+        return weight / 200;
+    }
+
+    private static boolean testBit(byte value, int offset) {
+        return ((value >> offset) & 1) == 1;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/moondrop/GaiaPacket.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/moondrop/GaiaPacket.java
new file mode 100644
index 0000000..fc17e72
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/moondrop/GaiaPacket.java
@@ -0,0 +1,136 @@
+/*  Copyright (C) 2024 Severin von Wnuck-Lipinski
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.moondrop;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+
+import static nodomain.freeyourgadget.gadgetbridge.util.GB.hexdump;
+
+public class GaiaPacket {
+    private static final Logger LOG = LoggerFactory.getLogger(GaiaPacket.class);
+
+    public static final byte PDU_COMMAND = (byte)0x00;
+    public static final byte PDU_NOTIFICATION = (byte)0x01;
+    public static final byte PDU_RESPONSE = (byte)0x02;
+    public static final byte PDU_ERROR = (byte)0x03;
+
+    private static final int HEADER_LENGTH = 8;
+    private static final byte START_OF_PACKET = (byte)0xff;
+    private static final byte VERSION = (byte)0x04;
+    private static final byte FLAGS_NO_CHECK = (byte)0x00;
+
+    // Qualcomm's vendor ID
+    private static final short VENDOR_ID = 0x001d;
+
+    private byte featureId;
+    private byte pduType;
+    private byte pduId;
+
+    private byte[] payload;
+
+    public GaiaPacket(byte featureId, byte pduId) {
+        this(featureId, pduId, new byte[] {});
+    }
+
+    public GaiaPacket(byte featureId, byte pduId, byte[] payload) {
+        this(featureId, PDU_COMMAND, pduId, payload);
+    }
+
+    private GaiaPacket(byte featureId, byte pduType, byte pduId, byte[] payload) {
+        LOG.debug(
+                "Packet: featureId={}, pduType={}, pduId={}, length={}",
+                String.format("0x%02x", featureId),
+                String.format("0x%02x", pduType),
+                String.format("0x%02x", pduId),
+                payload.length);
+
+        this.featureId = featureId;
+        this.pduType = pduType;
+        this.pduId = pduId;
+        this.payload = payload;
+    }
+
+    public byte getFeatureId() {
+        return featureId;
+    }
+
+    public byte getPduType() {
+        return pduType;
+    }
+
+    public byte getPduId() {
+        return pduId;
+    }
+
+    public byte[] getPayload() {
+        return payload;
+    }
+
+    public static GaiaPacket decode(ByteBuffer buf) {
+        if (buf.remaining() < HEADER_LENGTH)
+            return null;
+
+        if (buf.get() != START_OF_PACKET) {
+            LOG.error("Invalid GAIA start of packet: {}", hexdump(buf.array()));
+            return null;
+        }
+
+        if (buf.get() != VERSION) {
+            LOG.error("Invalid GAIA version: {}", hexdump(buf.array()));
+            return null;
+        }
+
+        if (buf.get() != FLAGS_NO_CHECK) {
+            LOG.error("Invalid GAIA flags: {}", hexdump(buf.array()));
+            return null;
+        }
+
+        byte length = buf.get();
+
+        if (buf.getShort() != VENDOR_ID) {
+            LOG.error("Invalid GAIA vendor ID: {}", hexdump(buf.array()));
+            return null;
+        }
+
+        short commandId = buf.getShort();
+        byte featureId = (byte)((commandId & 0xfe00) >> 9);
+        byte pduType = (byte)((commandId & 0x0180) >> 7);
+        byte pduId = (byte)(commandId & 0x7f);
+
+        byte[] payload = new byte[length];
+        buf.get(payload);
+
+        return new GaiaPacket(featureId, pduType, pduId, payload);
+    }
+
+    public byte[] encode() {
+        ByteBuffer buf = ByteBuffer.allocate(HEADER_LENGTH + payload.length);
+
+        buf.put(START_OF_PACKET);
+        buf.put(VERSION);
+        buf.put(FLAGS_NO_CHECK);
+        buf.put((byte) payload.length);
+        buf.putShort(VENDOR_ID);
+        buf.putShort((short)((featureId << 9) | (pduType << 7) | pduId));
+        buf.put(payload);
+
+        return buf.array();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/moondrop/MoondropSpaceTravelDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/moondrop/MoondropSpaceTravelDeviceSupport.java
new file mode 100644
index 0000000..db6dc6a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/moondrop/MoondropSpaceTravelDeviceSupport.java
@@ -0,0 +1,43 @@
+/*  Copyright (C) 2024 Severin von Wnuck-Lipinski
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.moondrop;
+
+import nodomain.freeyourgadget.gadgetbridge.service.AbstractHeadphoneDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceIoThread;
+import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol;
+
+public class MoondropSpaceTravelDeviceSupport extends AbstractHeadphoneDeviceSupport {
+    @Override
+    public boolean useAutoConnect() {
+        return false;
+    }
+
+    @Override
+    protected GBDeviceProtocol createDeviceProtocol() {
+        return new MoondropSpaceTravelProtocol(getDevice());
+    }
+
+    @Override
+    protected GBDeviceIoThread createDeviceIOThread() {
+        return new MoondropSpaceTravelIOThread(
+                getDevice(),
+                getContext(),
+                (MoondropSpaceTravelProtocol)getDeviceProtocol(),
+                MoondropSpaceTravelDeviceSupport.this,
+                getBluetoothAdapter());
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/moondrop/MoondropSpaceTravelIOThread.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/moondrop/MoondropSpaceTravelIOThread.java
new file mode 100644
index 0000000..5f55e28
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/moondrop/MoondropSpaceTravelIOThread.java
@@ -0,0 +1,64 @@
+/*  Copyright (C) 2024 Severin von Wnuck-Lipinski
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.moondrop;
+
+import android.bluetooth.BluetoothAdapter;
+import android.content.Context;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.btclassic.BtClassicIoThread;
+
+import static nodomain.freeyourgadget.gadgetbridge.util.GB.hexdump;
+
+public class MoondropSpaceTravelIOThread extends BtClassicIoThread {
+    private static final Logger LOG = LoggerFactory.getLogger(MoondropSpaceTravelIOThread.class);
+
+    private final MoondropSpaceTravelProtocol protocol;
+
+    public MoondropSpaceTravelIOThread(
+            GBDevice device,
+            Context context,
+            MoondropSpaceTravelProtocol protocol,
+            MoondropSpaceTravelDeviceSupport support,
+            BluetoothAdapter adapter) {
+        super(device, context, protocol, support, adapter);
+        this.protocol = protocol;
+    }
+
+    @Override
+    protected void initialize() {
+        write(protocol.encodeGetEqualizerPreset());
+        write(protocol.encodeGetTouchActions());
+        setUpdateState(GBDevice.State.INITIALIZED);
+    }
+
+    @Override
+    protected byte[] parseIncoming(InputStream stream) throws IOException {
+        byte[] buffer = new byte[1048576];
+        int bytes = stream.read(buffer);
+        LOG.debug("read " + bytes + " bytes. " + hexdump(buffer, 0, bytes));
+
+        return Arrays.copyOf(buffer, bytes);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/moondrop/MoondropSpaceTravelProtocol.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/moondrop/MoondropSpaceTravelProtocol.java
new file mode 100644
index 0000000..83b9548
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/moondrop/MoondropSpaceTravelProtocol.java
@@ -0,0 +1,187 @@
+/*  Copyright (C) 2024 Severin von Wnuck-Lipinski
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.moondrop;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdatePreferences;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.*;
+
+public class MoondropSpaceTravelProtocol extends GBDeviceProtocol {
+    private static final int EQUALIZER_PRESET_PKT_LEN = 1;
+    private static final byte EQUALIZER_PRESET_FEATURE = (byte)0x05;
+    private static final byte EQUALIZER_PRESET_PDU_GET = (byte)0x02;
+    private static final byte EQUALIZER_PRESET_PDU_SET = (byte)0x03;
+
+    private static final int TOUCH_ACTIONS_PKT_LEN = 12;
+    private static final byte TOUCH_ACTIONS_FEATURE = (byte)0x01;
+    private static final byte TOUCH_ACTIONS_PDU_GET = (byte)0x02;
+    private static final byte TOUCH_ACTIONS_PDU_SET = (byte)0x03;
+
+    protected MoondropSpaceTravelProtocol(GBDevice device) {
+        super(device);
+    }
+
+    @Override
+    public GBDeviceEvent[] decodeResponse(byte[] data) {
+        List<GBDeviceEvent> events = new ArrayList<>();
+        ByteBuffer buf = ByteBuffer.wrap(data);
+
+        while (buf.hasRemaining()) {
+            GaiaPacket packet = GaiaPacket.decode(buf);
+
+            if (packet == null)
+                break;
+
+            if (packet.getPduType() != GaiaPacket.PDU_RESPONSE)
+                continue;
+
+            short featureId = packet.getFeatureId();
+            short pduId = packet.getPduId();
+            byte[] payload = packet.getPayload();
+
+            if (featureId == EQUALIZER_PRESET_FEATURE && pduId == EQUALIZER_PRESET_PDU_GET)
+                events.add(handlePacketEqualizerPreset(payload));
+            else if (featureId == TOUCH_ACTIONS_FEATURE && pduId == TOUCH_ACTIONS_PDU_GET)
+                events.add(handlePacketTouchActions(payload));
+        }
+
+        return events.toArray(new GBDeviceEvent[0]);
+    }
+
+    private GBDeviceEvent handlePacketEqualizerPreset(byte[] payload) {
+        if (payload.length != EQUALIZER_PRESET_PKT_LEN)
+            return null;
+
+        byte preset = payload[0];
+
+        return new GBDeviceEventUpdatePreferences(PREF_MOONDROP_EQUALIZER_PRESET, String.valueOf(preset));
+    }
+
+    private GBDeviceEvent handlePacketTouchActions(byte[] payload) {
+        if (payload.length != TOUCH_ACTIONS_PKT_LEN)
+            return null;
+
+        Map<String, Object> prefs = new HashMap<>();
+
+        prefs.putAll(decodeTouchAction(PREF_MOONDROP_TOUCH_PLAY_PAUSE_EARBUD, PREF_MOONDROP_TOUCH_PLAY_PAUSE_TRIGGER, payload[1]));
+        prefs.putAll(decodeTouchAction(PREF_MOONDROP_TOUCH_MEDIA_PREV_EARBUD, PREF_MOONDROP_TOUCH_MEDIA_PREV_TRIGGER, payload[2]));
+        prefs.putAll(decodeTouchAction(PREF_MOONDROP_TOUCH_MEDIA_NEXT_EARBUD, PREF_MOONDROP_TOUCH_MEDIA_NEXT_TRIGGER, payload[3]));
+        prefs.putAll(decodeTouchAction(PREF_MOONDROP_TOUCH_CALL_PICK_HANG_EARBUD, PREF_MOONDROP_TOUCH_CALL_PICK_HANG_TRIGGER, payload[6]));
+        prefs.putAll(decodeTouchAction(PREF_MOONDROP_TOUCH_CALL_START_EARBUD, PREF_MOONDROP_TOUCH_CALL_START_TRIGGER, payload[7]));
+        prefs.putAll(decodeTouchAction(PREF_MOONDROP_TOUCH_ASSISTANT_EARBUD, PREF_MOONDROP_TOUCH_ASSISTANT_TRIGGER, payload[8]));
+        prefs.putAll(decodeTouchAction(PREF_MOONDROP_TOUCH_ANC_MODE_EARBUD, PREF_MOONDROP_TOUCH_ANC_MODE_TRIGGER, payload[10]));
+
+        return new GBDeviceEventUpdatePreferences(prefs);
+    }
+
+    @Override
+    public byte[] encodeSendConfiguration(String config) {
+        switch (config) {
+            case PREF_MOONDROP_EQUALIZER_PRESET:
+                return encodeSetEqualizerPreset();
+            case PREF_MOONDROP_TOUCH_PLAY_PAUSE_EARBUD:
+            case PREF_MOONDROP_TOUCH_PLAY_PAUSE_TRIGGER:
+            case PREF_MOONDROP_TOUCH_MEDIA_PREV_EARBUD:
+            case PREF_MOONDROP_TOUCH_MEDIA_PREV_TRIGGER:
+            case PREF_MOONDROP_TOUCH_MEDIA_NEXT_EARBUD:
+            case PREF_MOONDROP_TOUCH_MEDIA_NEXT_TRIGGER:
+            case PREF_MOONDROP_TOUCH_CALL_PICK_HANG_EARBUD:
+            case PREF_MOONDROP_TOUCH_CALL_PICK_HANG_TRIGGER:
+            case PREF_MOONDROP_TOUCH_CALL_START_EARBUD:
+            case PREF_MOONDROP_TOUCH_CALL_START_TRIGGER:
+            case PREF_MOONDROP_TOUCH_ASSISTANT_EARBUD:
+            case PREF_MOONDROP_TOUCH_ASSISTANT_TRIGGER:
+            case PREF_MOONDROP_TOUCH_ANC_MODE_EARBUD:
+            case PREF_MOONDROP_TOUCH_ANC_MODE_TRIGGER:
+                return encodeSetTouchActions();
+        }
+
+        return super.encodeSendConfiguration(config);
+    }
+
+    public byte[] encodeGetEqualizerPreset() {
+        return new GaiaPacket(EQUALIZER_PRESET_FEATURE, EQUALIZER_PRESET_PDU_GET).encode();
+    }
+
+    public byte[] encodeGetTouchActions() {
+        return new GaiaPacket(TOUCH_ACTIONS_FEATURE, TOUCH_ACTIONS_PDU_GET).encode();
+    }
+
+    private byte[] encodeSetEqualizerPreset() {
+        Prefs prefs = getDevicePrefs();
+        byte preset = Byte.parseByte(prefs.getString(PREF_MOONDROP_EQUALIZER_PRESET, "0"));
+
+        byte[] payload = new byte[] { preset };
+
+        return new GaiaPacket(EQUALIZER_PRESET_FEATURE, EQUALIZER_PRESET_PDU_SET, payload).encode();
+    }
+
+    private byte[] encodeSetTouchActions() {
+        Prefs prefs = getDevicePrefs();
+        byte actionPlayPause = encodeTouchAction(prefs, PREF_MOONDROP_TOUCH_PLAY_PAUSE_EARBUD, PREF_MOONDROP_TOUCH_PLAY_PAUSE_TRIGGER);
+        byte actionMediaPrev = encodeTouchAction(prefs, PREF_MOONDROP_TOUCH_MEDIA_PREV_EARBUD, PREF_MOONDROP_TOUCH_MEDIA_PREV_TRIGGER);
+        byte actionMediaNext = encodeTouchAction(prefs, PREF_MOONDROP_TOUCH_MEDIA_NEXT_EARBUD, PREF_MOONDROP_TOUCH_MEDIA_NEXT_TRIGGER);
+        byte actionCallPickHang = encodeTouchAction(prefs, PREF_MOONDROP_TOUCH_CALL_PICK_HANG_EARBUD, PREF_MOONDROP_TOUCH_CALL_PICK_HANG_TRIGGER);
+        byte actionCallStart = encodeTouchAction(prefs, PREF_MOONDROP_TOUCH_CALL_START_EARBUD, PREF_MOONDROP_TOUCH_CALL_START_TRIGGER);
+        byte actionAssistant = encodeTouchAction(prefs, PREF_MOONDROP_TOUCH_ASSISTANT_EARBUD, PREF_MOONDROP_TOUCH_ASSISTANT_TRIGGER);
+        byte actionAncMode = encodeTouchAction(prefs, PREF_MOONDROP_TOUCH_ANC_MODE_EARBUD, PREF_MOONDROP_TOUCH_ANC_MODE_TRIGGER);
+
+        byte[] payload = new byte[] {
+            (byte)0x01,
+            actionPlayPause,
+            actionMediaPrev,
+            actionMediaNext,
+            (byte)0x30,
+            (byte)0x30,
+            actionCallPickHang,
+            actionCallStart,
+            actionAssistant,
+            actionAssistant,
+            actionAncMode,
+            actionAncMode,
+        };
+
+        return new GaiaPacket(TOUCH_ACTIONS_FEATURE, TOUCH_ACTIONS_PDU_SET, payload).encode();
+    }
+
+    private byte encodeTouchAction(Prefs prefs, String prefEarbud, String prefTrigger) {
+        int earbud = Integer.valueOf(prefs.getString(prefEarbud, "3"));
+        int trigger = Integer.valueOf(prefs.getString(prefTrigger, "0"));
+
+        return (byte)((earbud << 4) | trigger);
+    }
+
+    private Map<String, Object> decodeTouchAction(String prefEarbud, String prefTrigger, byte action) {
+        int earbud = (action & 0xf0) >> 4;
+        int trigger = action & 0x0f;
+
+        return new HashMap<String, Object>() {{
+            put(prefEarbud, String.valueOf(earbud));
+            put(prefTrigger, String.valueOf(trigger));
+        }};
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/no1f1/No1F1Support.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/no1f1/No1F1Support.java
new file mode 100644
index 0000000..624b563
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/no1f1/No1F1Support.java
@@ -0,0 +1,602 @@
+/*  Copyright (C) 2017-2024 Andreas Shimokawa, Arjan Schrijver, Carsten
+    Pfeiffer, Damien Gaignon, Daniele Gobbetti, Pavel Elagin, protomors,
+    Sebastian Kranz
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.no1f1;
+
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.text.format.DateFormat;
+import android.widget.Toast;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.List;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.SettingsActivity;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo;
+import nodomain.freeyourgadget.gadgetbridge.devices.no1f1.No1F1Constants;
+import nodomain.freeyourgadget.gadgetbridge.devices.no1f1.No1F1SampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.No1F1ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
+import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
+import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceBusyAction;
+import nodomain.freeyourgadget.gadgetbridge.util.AlarmUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+import static org.apache.commons.lang3.math.NumberUtils.min;
+
+public class No1F1Support extends AbstractBTLEDeviceSupport {
+    private static final Logger LOG = LoggerFactory.getLogger(No1F1Support.class);
+    private final GBDeviceEventVersionInfo versionCmd = new GBDeviceEventVersionInfo();
+    private final GBDeviceEventBatteryInfo batteryCmd = new GBDeviceEventBatteryInfo();
+    public BluetoothGattCharacteristic ctrlCharacteristic = null;
+    public BluetoothGattCharacteristic measureCharacteristic = null;
+    private List<No1F1ActivitySample> samples = new ArrayList<>();
+    private byte crc = 0;
+    private int firstTimestamp = 0;
+
+    public No1F1Support() {
+        super(LOG);
+        addSupportedService(No1F1Constants.UUID_SERVICE_NO1);
+    }
+
+    @Override
+    protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
+        LOG.info("Initializing");
+
+        gbDevice.setState(GBDevice.State.INITIALIZING);
+        gbDevice.sendDeviceUpdateIntent(getContext());
+
+        measureCharacteristic = getCharacteristic(No1F1Constants.UUID_CHARACTERISTIC_MEASURE);
+        ctrlCharacteristic = getCharacteristic(No1F1Constants.UUID_CHARACTERISTIC_CONTROL);
+
+        builder.setCallback(this);
+        builder.notify(measureCharacteristic, true);
+
+        setTime(builder);
+        sendSettings(builder);
+
+        builder.write(ctrlCharacteristic, new byte[]{No1F1Constants.CMD_FIRMWARE_VERSION});
+        builder.write(ctrlCharacteristic, new byte[]{No1F1Constants.CMD_BATTERY});
+
+        gbDevice.setState(GBDevice.State.INITIALIZED);
+        gbDevice.sendDeviceUpdateIntent(getContext());
+
+        LOG.info("Initialization Done");
+
+        return builder;
+    }
+
+    @Override
+    public boolean onCharacteristicChanged(BluetoothGatt gatt,
+                                           BluetoothGattCharacteristic characteristic) {
+        if (super.onCharacteristicChanged(gatt, characteristic)) {
+            return true;
+        }
+
+        UUID characteristicUUID = characteristic.getUuid();
+        byte[] data = characteristic.getValue();
+        if (data.length == 0)
+            return true;
+
+        switch (data[0]) {
+            case No1F1Constants.CMD_FIRMWARE_VERSION:
+                versionCmd.fwVersion = new String(Arrays.copyOfRange(data, 1, data.length));
+                handleGBDeviceEvent(versionCmd);
+                LOG.info("Firmware version is: " + versionCmd.fwVersion);
+                return true;
+            case No1F1Constants.CMD_BATTERY:
+                batteryCmd.level = data[1];
+                handleGBDeviceEvent(batteryCmd);
+                LOG.info("Battery level is: " + data[1]);
+                return true;
+            case No1F1Constants.CMD_DATETIME:
+                LOG.info("Time is set to: " + (data[1] * 256 + ((int) data[2] & 0xff)) + "-" + data[3] + "-" + data[4] + " " + data[5] + ":" + data[6] + ":" + data[7]);
+                return true;
+            case No1F1Constants.CMD_USER_DATA:
+                LOG.info("User data updated");
+                return true;
+            case No1F1Constants.CMD_FETCH_STEPS:
+            case No1F1Constants.CMD_FETCH_SLEEP:
+            case No1F1Constants.CMD_FETCH_HEARTRATE:
+                handleActivityData(data);
+                return true;
+            case No1F1Constants.CMD_REALTIME_HEARTRATE:
+                handleRealtimeHeartRateData(data);
+                return true;
+            case No1F1Constants.CMD_NOTIFICATION:
+            case No1F1Constants.CMD_ICON:
+            case No1F1Constants.CMD_DEVICE_SETTINGS:
+            case No1F1Constants.CMD_DISPLAY_SETTINGS:
+                return true;
+            default:
+                LOG.warn("Unhandled characteristic change: " + characteristicUUID + " code: " + Arrays.toString(data));
+                return true;
+        }
+    }
+
+    @Override
+    public void onNotification(NotificationSpec notificationSpec) {
+        switch (notificationSpec.type) {
+            case GENERIC_SMS:
+                showNotification(No1F1Constants.NOTIFICATION_SMS, notificationSpec.phoneNumber, notificationSpec.body);
+                setVibration(1, 3);
+                break;
+            default:
+                showIcon(No1F1Constants.ICON_WECHAT);
+                setVibration(1, 2);
+                break;
+        }
+    }
+
+    @Override
+    public void onSetTime() {
+        try {
+            TransactionBuilder builder = performInitialized("setTime");
+            setTime(builder);
+            builder.queue(getQueue());
+        } catch (IOException e) {
+            GB.toast(getContext(), "Error setting time: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
+        }
+    }
+
+    @Override
+    public void onSetAlarms(ArrayList<? extends Alarm> alarms) {
+        try {
+            TransactionBuilder builder = performInitialized("Set alarm");
+            boolean anyAlarmEnabled = false;
+            for (Alarm alarm : alarms) {
+                anyAlarmEnabled |= alarm.getEnabled();
+                Calendar calendar = AlarmUtils.toCalendar(alarm);
+
+                int maxAlarms = 3;
+                if (alarm.getPosition() >= maxAlarms) {
+                    if (alarm.getEnabled()) {
+                        GB.toast(getContext(), "Only 3 alarms are supported.", Toast.LENGTH_LONG, GB.WARN);
+                    }
+                    return;
+                }
+
+                int daysMask = 0;
+                if (alarm.getEnabled()) {
+                    daysMask = alarm.getRepetition();
+                    // Mask for this device starts from sunday and not from monday.
+                    daysMask = (daysMask / 64) + (daysMask >> 1);
+                }
+                byte[] alarmMessage = new byte[]{
+                        No1F1Constants.CMD_ALARM,
+                        (byte) daysMask,
+                        (byte) calendar.get(Calendar.HOUR_OF_DAY),
+                        (byte) calendar.get(Calendar.MINUTE),
+                        (byte) (alarm.getEnabled() ? 2 : 0), // vibration duration
+                        (byte) (alarm.getEnabled() ? 10 : 0), // vibration count
+                        (byte) (alarm.getEnabled() ? 2 : 0), // unknown
+                        (byte) 0,
+                        (byte) (alarm.getPosition() + 1)
+                };
+                builder.write(ctrlCharacteristic, alarmMessage);
+            }
+            builder.queue(getQueue());
+            if (anyAlarmEnabled) {
+                GB.toast(getContext(), getContext().getString(R.string.user_feedback_miband_set_alarms_ok), Toast.LENGTH_SHORT, GB.INFO);
+            } else {
+                GB.toast(getContext(), getContext().getString(R.string.user_feedback_all_alarms_disabled), Toast.LENGTH_SHORT, GB.INFO);
+            }
+        } catch (IOException ex) {
+            GB.toast(getContext(), getContext().getString(R.string.user_feedback_miband_set_alarms_failed), Toast.LENGTH_LONG, GB.ERROR, ex);
+        }
+    }
+
+    @Override
+    public void onSetCallState(CallSpec callSpec) {
+        if (callSpec.command == CallSpec.CALL_INCOMING) {
+            showNotification(No1F1Constants.NOTIFICATION_CALL, callSpec.name, callSpec.number);
+            setVibration(3, 5);
+        } else {
+            stopNotification();
+            setVibration(0, 0);
+        }
+    }
+
+    @Override
+    public void onFetchRecordedData(int dataTypes) {
+        sendFetchCommand(No1F1Constants.CMD_FETCH_STEPS);
+    }
+
+    @Override
+    public void onHeartRateTest() {
+        try {
+            TransactionBuilder builder = performInitialized("heartRateTest");
+            byte[] msg = new byte[]{
+                    No1F1Constants.CMD_REALTIME_HEARTRATE,
+                    (byte) 0x11
+            };
+            builder.write(ctrlCharacteristic, msg);
+            builder.queue(getQueue());
+        } catch (IOException e) {
+            GB.toast(getContext(), "Error starting heart rate measurement: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
+        }
+    }
+
+    @Override
+    public void onFindDevice(boolean start) {
+        if (start)
+            setVibration(3, 10);
+        else
+            setVibration(0, 0);
+    }
+
+    @Override
+    public void onSendConfiguration(String config) {
+        TransactionBuilder builder;
+        try {
+            builder = performInitialized("Sending configuration for option: " + config);
+            switch (config) {
+                case SettingsActivity.PREF_MEASUREMENT_SYSTEM:
+                    setDisplaySettings(builder);
+                    break;
+            }
+            builder.queue(getQueue());
+        } catch (IOException e) {
+            GB.toast("Error setting configuration", Toast.LENGTH_LONG, GB.ERROR, e);
+        }
+    }
+
+    @Override
+    public boolean useAutoConnect() {
+        return true;
+    }
+
+    private void setTime(TransactionBuilder transaction) {
+        Calendar c = GregorianCalendar.getInstance();
+        byte[] datetimeBytes = new byte[]{
+                No1F1Constants.CMD_DATETIME,
+                (byte) ((c.get(Calendar.YEAR) / 256) & 0xff),
+                (byte) (c.get(Calendar.YEAR) % 256),
+                (byte) (c.get(Calendar.MONTH) + 1),
+                (byte) c.get(Calendar.DAY_OF_MONTH),
+                (byte) c.get(Calendar.HOUR_OF_DAY),
+                (byte) c.get(Calendar.MINUTE),
+                (byte) c.get(Calendar.SECOND)
+        };
+        transaction.write(ctrlCharacteristic, datetimeBytes);
+    }
+
+    /**
+     * Set display settings (time format and measurement system)
+     *
+     * @param transaction
+     * @return
+     */
+    private No1F1Support setDisplaySettings(TransactionBuilder transaction) {
+        byte[] displayBytes = new byte[]{
+                No1F1Constants.CMD_DISPLAY_SETTINGS,
+                0x00, // 1 - display distance in kilometers, 2 - in miles
+                0x00 // 1 - display 24-hour clock, 2 - for 12-hour with AM/PM
+        };
+        String units = GBApplication.getPrefs().getString(SettingsActivity.PREF_MEASUREMENT_SYSTEM, getContext().getString(R.string.p_unit_metric));
+        if (units.equals(getContext().getString(R.string.p_unit_metric))) {
+            displayBytes[1] = 1;
+        } else {
+            displayBytes[1] = 2;
+        }
+        if (DateFormat.is24HourFormat(getContext())) {
+            displayBytes[2] = 1;
+        } else {
+            displayBytes[2] = 2;
+        }
+        transaction.write(ctrlCharacteristic, displayBytes);
+        return this;
+    }
+
+    private void sendSettings(TransactionBuilder builder) {
+        // TODO Create custom settings page for changing hardcoded values
+
+        // set user data
+        ActivityUser activityUser = new ActivityUser();
+        byte[] userBytes = new byte[]{
+                No1F1Constants.CMD_USER_DATA,
+                0,
+                (byte) Math.round(activityUser.getHeightCm() * 0.43), // step length in cm
+                0,
+                (byte) activityUser.getWeightKg(),
+                5, // screen on time
+                0,
+                0,
+                (byte) (activityUser.getStepsGoal() / 256),
+                (byte) (activityUser.getStepsGoal() % 256),
+                1, // unknown
+                (byte) 0xff, // unknown
+                0,
+                (byte) activityUser.getAge(),
+                0
+        };
+        if (activityUser.getGender() == ActivityUser.GENDER_FEMALE)
+            userBytes[14] = 2; // female
+        else
+            userBytes[14] = 1; // male
+
+        builder.write(ctrlCharacteristic, userBytes);
+
+        // more settings
+        builder.write(ctrlCharacteristic, new byte[]{
+                No1F1Constants.CMD_DEVICE_SETTINGS,
+                0x00, // 1 - turns on inactivity alarm
+                0x3c,
+                0x02,
+                0x03,
+                0x01,
+                0x00
+        });
+
+        setDisplaySettings(builder);
+
+        // heart rate measurement mode
+        builder.write(ctrlCharacteristic, new byte[]{
+                No1F1Constants.CMD_HEARTRATE_SETTINGS,
+                0x02, // 1 - static (measure for 15 seconds), 2 - realtime
+        });
+
+        // periodic heart rate measurement
+        builder.write(ctrlCharacteristic, new byte[]{
+                No1F1Constants.CMD_HEARTRATE_SETTINGS,
+                0x01,
+                0x02 // measure heart rate every 2 hours (0 to turn off)
+        });
+    }
+
+    private void setVibration(int duration, int count) {
+        try {
+            TransactionBuilder builder = performInitialized("vibrate");
+            byte[] msg = new byte[]{
+                    No1F1Constants.CMD_ALARM,
+                    0,
+                    0,
+                    0,
+                    (byte) duration,
+                    (byte) count,
+                    2,
+                    1
+            };
+            builder.write(ctrlCharacteristic, msg);
+            builder.queue(getQueue());
+        } catch (IOException e) {
+            LOG.warn("Unable to set vibration", e);
+        }
+    }
+
+    private void showIcon(int iconId) {
+        try {
+            TransactionBuilder builder = performInitialized("showIcon");
+            byte[] msg = new byte[]{
+                    No1F1Constants.CMD_ICON,
+                    (byte) iconId
+            };
+            builder.write(ctrlCharacteristic, msg);
+            builder.queue(getQueue());
+        } catch (IOException e) {
+            GB.toast(getContext(), "Error showing icon: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
+        }
+    }
+
+    private void showNotification(int type, String header, String body) {
+        try {
+            // TODO Add transliteration.
+            TransactionBuilder builder = performInitialized("showNotification");
+            int length;
+            byte[] bytes;
+            byte[] msg;
+
+            // send header
+            bytes = header.getBytes("EUC-JP");
+            length = min(bytes.length, 18);
+            msg = new byte[length + 2];
+            msg[0] = No1F1Constants.CMD_NOTIFICATION;
+            msg[1] = No1F1Constants.NOTIFICATION_HEADER;
+            System.arraycopy(bytes, 0, msg, 2, length);
+            builder.write(ctrlCharacteristic, msg);
+
+            // send body
+            bytes = header.getBytes("EUC-JP");
+            length = min(bytes.length, 18);
+            msg = new byte[length + 2];
+            msg[0] = No1F1Constants.CMD_NOTIFICATION;
+            msg[1] = (byte) type;
+            System.arraycopy(bytes, 0, msg, 2, length);
+            builder.write(ctrlCharacteristic, msg);
+
+            builder.queue(getQueue());
+        } catch (IOException e) {
+            GB.toast(getContext(), "Error showing notificaton: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
+        }
+    }
+
+    private void stopNotification() {
+        try {
+            TransactionBuilder builder = performInitialized("clearNotification");
+            byte[] msg = new byte[]{
+                    No1F1Constants.CMD_NOTIFICATION,
+                    No1F1Constants.NOTIFICATION_STOP
+            };
+            builder.write(ctrlCharacteristic, msg);
+            builder.queue(getQueue());
+        } catch (IOException e) {
+            LOG.warn("Unable to stop notification", e);
+        }
+    }
+
+    private void sendFetchCommand(byte type) {
+        samples.clear();
+        crc = 0;
+        firstTimestamp = 0;
+        try {
+            TransactionBuilder builder = performInitialized("fetchActivityData");
+            builder.add(new SetDeviceBusyAction(getDevice(), getContext().getString(R.string.busy_task_fetch_activity_data), getContext()));
+            byte[] msg = new byte[]{
+                    type,
+                    (byte) 0xfa
+            };
+            builder.write(ctrlCharacteristic, msg);
+            builder.queue(getQueue());
+        } catch (IOException e) {
+            GB.toast(getContext(), "Error fetching activity data: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
+        }
+    }
+
+    private void handleActivityData(byte[] data) {
+        if (data[1] == (byte) 0xfd) {
+            LOG.info("CRC received: {}, calculated: {}", data[2] & 0xff, crc & 0xff);
+            if (data[2] != crc) {
+                GB.toast(getContext(), "Incorrect CRC. Try fetching data again.", Toast.LENGTH_LONG, GB.ERROR);
+                GB.updateTransferNotification(null,"Data transfer failed", false, 0, getContext());
+                if (getDevice().isBusy()) {
+                    getDevice().unsetBusyTask();
+                    getDevice().sendDeviceUpdateIntent(getContext());
+                }
+            } else if (!samples.isEmpty()) {
+                try (DBHandler dbHandler = GBApplication.acquireDB()) {
+                    Long userId = DBHelper.getUser(dbHandler.getDaoSession()).getId();
+                    Long deviceId = DBHelper.getDevice(getDevice(), dbHandler.getDaoSession()).getId();
+                    No1F1SampleProvider provider = new No1F1SampleProvider(getDevice(), dbHandler.getDaoSession());
+                    for (int i = 0; i < samples.size(); i++) {
+                        samples.get(i).setDeviceId(deviceId);
+                        samples.get(i).setUserId(userId);
+                        if (data[0] == No1F1Constants.CMD_FETCH_STEPS) {
+                            samples.get(i).setRawKind(ActivityKind.ACTIVITY.getCode());
+                            samples.get(i).setRawIntensity(samples.get(i).getSteps());
+                        } else if (data[0] == No1F1Constants.CMD_FETCH_SLEEP) {
+                            if (samples.get(i).getRawIntensity() < 7)
+                                samples.get(i).setRawKind(ActivityKind.DEEP_SLEEP.getCode());
+                            else
+                                samples.get(i).setRawKind(ActivityKind.LIGHT_SLEEP.getCode());
+                        }
+                        provider.addGBActivitySample(samples.get(i));
+                    }
+                    LOG.info("Activity data saved");
+                    if (data[0] == No1F1Constants.CMD_FETCH_STEPS) {
+                        sendFetchCommand(No1F1Constants.CMD_FETCH_SLEEP);
+                    } else if (data[0] == No1F1Constants.CMD_FETCH_SLEEP) {
+                        sendFetchCommand(No1F1Constants.CMD_FETCH_HEARTRATE);
+                    } else {
+                        GB.updateTransferNotification(null,"", false, 100, getContext());
+                        if (getDevice().isBusy()) {
+                            getDevice().unsetBusyTask();
+                            GB.signalActivityDataFinish(getDevice());
+                        }
+                    }
+                } catch (Exception ex) {
+                    GB.toast(getContext(), "Error saving activity data: " + ex.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
+                    GB.updateTransferNotification(null,"Data transfer failed", false, 0, getContext());
+                }
+            }
+        } else {
+            No1F1ActivitySample sample = new No1F1ActivitySample();
+
+            Calendar timestamp = GregorianCalendar.getInstance();
+            timestamp.set(Calendar.YEAR, data[1] * 256 + (data[2] & 0xff));
+            timestamp.set(Calendar.MONTH, (data[3] - 1) & 0xff);
+            timestamp.set(Calendar.DAY_OF_MONTH, data[4] & 0xff);
+            timestamp.set(Calendar.HOUR_OF_DAY, data[5] & 0xff);
+            timestamp.set(Calendar.SECOND, 0);
+
+            int startProgress = 0;
+            if (data[0] == No1F1Constants.CMD_FETCH_STEPS) {
+                timestamp.set(Calendar.MINUTE, 0);
+                sample.setSteps(data[6] * 256 + (data[7] & 0xff));
+                //noinspection lossy-conversions
+                crc ^= (data[6] ^ data[7]);
+            } else if (data[0] == No1F1Constants.CMD_FETCH_SLEEP) {
+                timestamp.set(Calendar.MINUTE, data[6] & 0xff);
+                sample.setRawIntensity(data[7] * 256 + (data[8] & 0xff));
+                //noinspection lossy-conversions
+                crc ^= (data[7] ^ data[8]);
+                startProgress = 33;
+            } else if (data[0] == No1F1Constants.CMD_FETCH_HEARTRATE) {
+                timestamp.set(Calendar.MINUTE, data[6] & 0xff);
+                sample.setHeartRate(data[7] & 0xff);
+                //noinspection lossy-conversions
+                crc ^= (data[6] ^ data[7]);
+                startProgress = 66;
+            }
+
+            sample.setTimestamp((int) (timestamp.getTimeInMillis() / 1000L));
+            samples.add(sample);
+
+            if (firstTimestamp == 0)
+                firstTimestamp = sample.getTimestamp();
+            int progress = startProgress + 33 * (sample.getTimestamp() - firstTimestamp) /
+                    ((int) (Calendar.getInstance().getTimeInMillis() / 1000L) - firstTimestamp);
+            GB.updateTransferNotification(null, getContext().getString(R.string.busy_task_fetch_activity_data), true, progress, getContext());
+        }
+    }
+
+    private void handleRealtimeHeartRateData(byte[] data) {
+        if (data.length == 2) {
+            if (data[1] == (byte) 0x11)
+                LOG.info("Heart rate measurement started.");
+            else
+                LOG.info("Heart rate measurement stopped.");
+            return;
+        }
+        // Check if data is valid. Otherwise ignore sample.
+        if (data[2] == 0) {
+            No1F1ActivitySample sample = new No1F1ActivitySample();
+            sample.setTimestamp((int) (GregorianCalendar.getInstance().getTimeInMillis() / 1000L));
+            sample.setHeartRate(data[3] & 0xff);
+            LOG.info("Current heart rate is: " + sample.getHeartRate() + " BPM");
+            try (DBHandler dbHandler = GBApplication.acquireDB()) {
+                Long userId = DBHelper.getUser(dbHandler.getDaoSession()).getId();
+                Long deviceId = DBHelper.getDevice(getDevice(), dbHandler.getDaoSession()).getId();
+                No1F1SampleProvider provider = new No1F1SampleProvider(getDevice(), dbHandler.getDaoSession());
+                sample.setDeviceId(deviceId);
+                sample.setUserId(userId);
+                provider.addGBActivitySample(sample);
+            } catch (Exception ex) {
+                LOG.warn("Error saving current heart rate: " + ex.getLocalizedMessage());
+            }
+        }
+    }
+
+    @Override
+    public boolean getImplicitCallbackModify() {
+        return true;
+    }
+
+    @Override
+    public boolean getSendWriteRequestResponse() {
+        return false;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/nothing/Ear1Support.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/nothing/Ear1Support.java
new file mode 100644
index 0000000..f0c8eb0
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/nothing/Ear1Support.java
@@ -0,0 +1,58 @@
+/*  Copyright (C) 2021-2024 Arjan Schrijver, Daniele Gobbetti, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.nothing;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import nodomain.freeyourgadget.gadgetbridge.service.AbstractHeadphoneDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceIoThread;
+import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol;
+
+public class Ear1Support extends AbstractHeadphoneDeviceSupport {
+    private static final Logger LOG = LoggerFactory.getLogger(Ear1Support.class);
+
+    @Override
+    public void onSendConfiguration(String config) {
+        super.onSendConfiguration(config);
+    }
+
+    @Override
+    public void onTestNewFunction() {
+        //getDeviceIOThread().write(((NothingProtocol) getDeviceProtocol()).encodeBatteryStatusReq());
+    }
+
+    @Override
+    public synchronized NothingIOThread getDeviceIOThread() {
+        return (NothingIOThread) super.getDeviceIOThread();
+    }
+
+    @Override
+    public boolean useAutoConnect() {
+        return false;
+    }
+
+    protected GBDeviceProtocol createDeviceProtocol() {
+        return new NothingProtocol(getDevice());
+    }
+
+    @Override
+    protected GBDeviceIoThread createDeviceIOThread() {
+        return new NothingIOThread(getDevice(), getContext(), (NothingProtocol) getDeviceProtocol(),
+                Ear1Support.this, getBluetoothAdapter());
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/nothing/NothingIOThread.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/nothing/NothingIOThread.java
new file mode 100644
index 0000000..889a546
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/nothing/NothingIOThread.java
@@ -0,0 +1,69 @@
+/*  Copyright (C) 2021-2024 Daniele Gobbetti, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.nothing;
+
+import android.bluetooth.BluetoothAdapter;
+import android.content.Context;
+import android.os.ParcelUuid;
+
+import androidx.annotation.NonNull;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.btclassic.BtClassicIoThread;
+
+import static nodomain.freeyourgadget.gadgetbridge.util.GB.hexdump;
+
+public class NothingIOThread extends BtClassicIoThread {
+    private static final Logger LOG = LoggerFactory.getLogger(NothingIOThread.class);
+
+    private final NothingProtocol mNothingProtocol;
+
+    @NonNull
+    protected UUID getUuidToConnect(@NonNull ParcelUuid[] uuids) {
+        return mNothingProtocol.UUID_DEVICE_CTRL;
+    }
+
+    @Override
+    protected void initialize() {
+        write(mNothingProtocol.encodeBatteryStatusReq());
+        write(mNothingProtocol.encodeAudioModeStatusReq());
+        setUpdateState(GBDevice.State.INITIALIZED);
+    }
+
+    public NothingIOThread(GBDevice device, Context context, NothingProtocol deviceProtocol,
+                           Ear1Support ear1Support, BluetoothAdapter bluetoothAdapter) {
+        super(device, context, deviceProtocol, ear1Support, bluetoothAdapter);
+        mNothingProtocol = deviceProtocol;
+    }
+
+    @Override
+    protected byte[] parseIncoming(InputStream inStream) throws IOException {
+        byte[] buffer = new byte[1048576]; //HUGE read
+        int bytes = inStream.read(buffer);
+        LOG.debug("read " + bytes + " bytes. " + hexdump(buffer, 0, bytes));
+        return Arrays.copyOf(buffer, bytes);
+    }
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/nothing/NothingProtocol.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/nothing/NothingProtocol.java
new file mode 100644
index 0000000..9d7c000
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/nothing/NothingProtocol.java
@@ -0,0 +1,341 @@
+/*  Copyright (C) 2021-2024 Daniele Gobbetti, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.nothing;
+
+import android.content.SharedPreferences;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo;
+import nodomain.freeyourgadget.gadgetbridge.devices.nothing.AbstractEarCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.BatteryState;
+import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol;
+
+import static nodomain.freeyourgadget.gadgetbridge.util.CheckSums.getCRC16ansi;
+import static nodomain.freeyourgadget.gadgetbridge.util.GB.hexdump;
+
+public class NothingProtocol extends GBDeviceProtocol {
+    private static final Logger LOG = LoggerFactory.getLogger(NothingProtocol.class);
+
+    final UUID UUID_DEVICE_CTRL = UUID.fromString("aeac4a03-dff5-498f-843a-34487cf133eb");
+
+    private boolean isFirstExchange = true;
+
+    private static final byte CONTROL_DEVICE_TYPE_TWS_HEADSET = 1;
+
+    private static final int CONTROL_CRC = 0x20;
+
+    private static final byte MASK_RSP_CODE = 0x1f;
+    private static final short MASK_DEVICE_TYPE = 0x0F00;
+
+
+    private static final short MASK_REQUEST_CMD = (short) 0x8000;
+
+    private static final byte MASK_BATTERY = 0x7f;
+    private static final byte MASK_BATTERY_CHARGING = (byte) 0x80;
+
+
+    //incoming
+    private static final short battery_status = (short) 0xe001;
+    private static final short battery_status2 = (short) 0xc007;
+    private static final short audio_mode_status = (short) 0xc01e;
+
+    private static final short unk_maybe_ack = (short) 0xf002;
+    private static final short unk_close_case = (short) 0xe002; //sent twice when the case is closed with earphones in
+
+    //outgoing
+    private static final short find_device = (short) 0xf002;
+    private static final short in_ear_detection = (short) 0xf004;
+    private static final short audio_mode = (short) 0xf00f;
+
+    private final boolean incrementCounter;
+    private int messageCounter = 0x00;
+
+    private HashMap<Byte, GBDeviceEventBatteryInfo> batteries;
+    private static final byte battery_earphone_left = 0x02;
+    private static final byte battery_earphone_right = 0x03;
+    private static final byte battery_case = 0x04;
+
+    @Override
+    public GBDeviceEvent[] decodeResponse(byte[] responseData) {
+        List<GBDeviceEvent> devEvts = new ArrayList<>();
+
+        if (isFirstExchange) {
+            isFirstExchange = false;
+            devEvts.add(new GBDeviceEventVersionInfo()); //TODO: this is a weird hack to make the DBHelper happy. Replace with proper firmware detection
+        }
+
+        ByteBuffer incoming = ByteBuffer.wrap(responseData);
+        incoming.order(ByteOrder.LITTLE_ENDIAN);
+
+        byte sof = incoming.get();
+        if (sof != 0x55) {
+            LOG.error("Error in message, wrong start of frame: " + hexdump(responseData));
+            return null;
+        }
+
+        short control = incoming.getShort();
+        if (!isSupportedDevice(control)) {
+            LOG.error("Unsupported device specified in message: " + hexdump(responseData));
+            return null;
+        }
+        if (!isOk(control)) {
+            LOG.error("Message is not ok: " + hexdump(responseData));
+            return null;
+        }
+        short command = incoming.getShort();
+        short length = incoming.getShort();
+        incoming.get();
+
+        byte[] payload = Arrays.copyOfRange(responseData, incoming.position(), incoming.position() + length);
+
+
+        switch (getRequestCommand(command)) {
+            case battery_status:
+            case battery_status2:
+                devEvts.addAll(handleBatteryInfo(payload));
+                break;
+            case audio_mode_status:
+                devEvts.add(handleAudioModeStatus(payload));
+                break;
+
+            case unk_maybe_ack:
+                LOG.debug("received ack");
+                break;
+            case unk_close_case:
+                LOG.debug("case closed");
+                break;
+
+            default:
+                LOG.debug("Incoming message - control:" + control + " requestCommand: " + (getRequestCommand(command) & 0xffff) + "length: " + length + " dump: " + hexdump(responseData));
+
+        }
+        return devEvts.toArray(new GBDeviceEvent[devEvts.size()]);
+    }
+
+    boolean isCrcNeeded(short control) {
+        return (control & CONTROL_CRC) != 0;
+    }
+
+    byte[] encodeMessage(short control, short command, byte[] payload) {
+
+        ByteBuffer msgBuf = ByteBuffer.allocate(8 + payload.length);
+        msgBuf.order(ByteOrder.LITTLE_ENDIAN);
+        msgBuf.put((byte) 0x55); //sof
+        msgBuf.putShort((short) (incrementCounter ? (control | 0x40) : control));
+        msgBuf.putShort(command);
+        msgBuf.putShort((short) payload.length);
+        msgBuf.put((byte) messageCounter); //fsn
+        if (incrementCounter) {
+            messageCounter++;
+            if ((byte) messageCounter == (byte) 0xfd) {
+                messageCounter = 0x00;
+            }
+        }
+        msgBuf.put(payload);
+
+        if (isCrcNeeded(control)) {
+            msgBuf.position(0);
+            ByteBuffer crcBuf = ByteBuffer.allocate(msgBuf.capacity() + 2);
+            crcBuf.order(ByteOrder.LITTLE_ENDIAN);
+            crcBuf.put(msgBuf);
+            crcBuf.putShort((short) getCRC16ansi(msgBuf.array()));
+            return crcBuf.array();
+        }
+
+        return msgBuf.array();
+    }
+
+    byte[] encodeBatteryStatusReq() {
+        return encodeMessage((short) 0x5120, battery_status2, new byte[]{});
+    }
+
+    byte[] encodeAudioModeStatusReq() {
+        return encodeMessage((short) 0x120, audio_mode_status, new byte[]{});
+    }
+
+    //TODO: unify mapping between bytes and strings in the following two functions
+    private GBDeviceEvent handleAudioModeStatus(byte[] payload) {
+        SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress());
+        SharedPreferences.Editor editor = prefs.edit();
+
+        if (Arrays.equals(payload, new byte[]{0x01, 0x01, 0x00})) {
+            editor.putString(DeviceSettingsPreferenceConst.PREF_NOTHING_EAR1_AUDIOMODE, "anc").apply();
+        } else if (Arrays.equals(payload, new byte[]{0x01, 0x03, 0x00})) {
+            editor.putString(DeviceSettingsPreferenceConst.PREF_NOTHING_EAR1_AUDIOMODE, "anc-light").apply();
+        } else if (Arrays.equals(payload, new byte[]{0x01, 0x05, 0x00})) {
+            editor.putString(DeviceSettingsPreferenceConst.PREF_NOTHING_EAR1_AUDIOMODE, "off").apply();
+        } else if (Arrays.equals(payload, new byte[]{0x01, 0x07, 0x00})) {
+            editor.putString(DeviceSettingsPreferenceConst.PREF_NOTHING_EAR1_AUDIOMODE, "transparency").apply();
+        } else {
+            LOG.warn("Unknown audio mode. Payload: " + hexdump(payload));
+        }
+        return null;
+    }
+
+    byte[] encodeAudioMode(String desired) {
+        byte[] payload = new byte[]{0x01, 0x05, 0x00};
+
+        switch (desired) {
+            case "anc":
+                payload[1] = 0x01;
+                break;
+            case "anc-light":
+                payload[1] = 0x03;
+                break;
+            case "transparency":
+                payload[1] = 0x07;
+                break;
+            case "off":
+            default:
+        }
+        return encodeMessage((short) 0x120, audio_mode, payload);
+    }
+
+
+    @Override
+    public byte[] encodeFindDevice(boolean start) {
+        byte payload = (byte) (start ? 0x01 : 0x00);
+        return encodeMessage((short) 0x120, find_device, new byte[]{payload});
+    }
+
+    @Override
+    public byte[] encodeSendConfiguration(String config) {
+
+        SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress());
+
+        switch (config) {
+            case DeviceSettingsPreferenceConst.PREF_NOTHING_EAR1_INEAR:
+                byte enabled = (byte) (prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_NOTHING_EAR1_INEAR, true) ? 0x01 : 0x00);
+                return encodeMessage((short) 0x120, in_ear_detection, new byte[]{0x01, 0x01, enabled});
+            // response: 55 20 01 04 70 00 00 00
+            case DeviceSettingsPreferenceConst.PREF_NOTHING_EAR1_AUDIOMODE:
+                return encodeAudioMode(prefs.getString(DeviceSettingsPreferenceConst.PREF_NOTHING_EAR1_AUDIOMODE, "off"));
+            // response: 55 20 01 0F 70 00 00 00
+
+            default:
+                LOG.debug("CONFIG: " + config);
+        }
+
+        return super.encodeSendConfiguration(config);
+    }
+
+    @Override
+    public byte[] encodeSetTime() {
+        // This are earphones, there is no time to set here. However this method gets called soon
+        // after connecting, hence we use it to perform some initializations.
+        // TODO: Find a way to send more requests during the first connection
+        return encodeAudioModeStatusReq();
+    }
+
+    private List<GBDeviceEvent> handleBatteryInfo(byte[] payload) {
+        List<GBDeviceEvent> batEvts = new ArrayList<>();
+
+        //LOG.debug("Battery payload: " + hexdump(payload));
+
+        /* payload:
+        1st byte is number of batteries, then $number pairs follow:
+        {idx, value}
+
+        idx is 0x02 for left ear, 0x03 for right ear, 0x04 for case
+        value goes from 0-64 (equivalent of 0-100 in hexadecimal)
+
+
+        Since Gadgetbridge supports only one battery, we use an average of the levels for the
+        battery level.
+        If one of the batteries is recharging, we consider the battery as recharging.
+         */
+
+//        GBDeviceEventBatteryInfo evBattery = new GBDeviceEventBatteryInfo();
+//        evBattery.level = 0;
+//        boolean batteryCharging = false;
+
+        int numBatteries = payload[0];
+        for (int i = 0; i < numBatteries; i++) {
+
+            batteries.get(payload[1 + 2 * i]).level = (payload[2 + 2 * i] & MASK_BATTERY);
+            batteries.get(payload[1 + 2 * i]).state =
+                    ((payload[2 + 2 * i] & MASK_BATTERY_CHARGING) == MASK_BATTERY_CHARGING) ? BatteryState.BATTERY_CHARGING : BatteryState.BATTERY_NORMAL;
+
+            batEvts.add(batteries.get(payload[1 + 2 * i]));
+
+//            evBattery.level += (short) ((payload[2 + 2 * i] & MASK_BATTERY) / numBatteries);
+//            if (!batteryCharging) {
+//                batteryCharging = ((payload[2 + 2 * i] & MASK_BATTERY_CHARGING) == MASK_BATTERY_CHARGING);
+//            }
+//            LOG.debug("single battery level: " + hexdump(payload, 2+2*i,1) +"-"+ ((payload[2+2*i] & 0xff))+":" + evBattery.level);
+            }
+
+//        evBattery.state = BatteryState.UNKNOWN;
+//        evBattery.state = batteryCharging ? BatteryState.BATTERY_CHARGING : evBattery.state;
+
+//        return evBattery;
+        return batEvts;
+    }
+
+    private short getRequestCommand(short command) {
+        return (short) (command | MASK_REQUEST_CMD);
+    }
+
+    private boolean isOk(short control) {
+        return (control & MASK_RSP_CODE) == 0;
+    }
+
+    private boolean isSupportedDevice(short control) {
+        return getDeviceType(control) == CONTROL_DEVICE_TYPE_TWS_HEADSET;
+    }
+
+    private byte getDeviceType(short control) {
+        return (byte) ((control & MASK_DEVICE_TYPE) >> 8);
+    }
+
+    private AbstractEarCoordinator getCoordinator() {
+        return (AbstractEarCoordinator) getDevice().getDeviceCoordinator();
+    }
+
+    protected NothingProtocol(GBDevice device) {
+        super(device);
+
+        batteries = new HashMap<>(3);
+
+        batteries.put(battery_earphone_left, new GBDeviceEventBatteryInfo());
+        batteries.put(battery_earphone_right, new GBDeviceEventBatteryInfo());
+        batteries.put(battery_case, new GBDeviceEventBatteryInfo());
+
+        batteries.get(battery_case).batteryIndex=0;
+        batteries.get(battery_earphone_left).batteryIndex=1;
+        batteries.get(battery_earphone_right).batteryIndex=2;
+
+        incrementCounter = getCoordinator().incrementCounter();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/nut/NutSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/nut/NutSupport.java
new file mode 100644
index 0000000..ce128ec
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/nut/NutSupport.java
@@ -0,0 +1,488 @@
+/*  Copyright (C) 2020-2024 Arjan Schrijver, Taavi Eom盲e
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.nut;
+
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.widget.Toast;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.math.BigInteger;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo;
+import nodomain.freeyourgadget.gadgetbridge.devices.nut.NutConstants;
+import nodomain.freeyourgadget.gadgetbridge.devices.nut.NutKey;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.IntentListener;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.battery.BatteryInfo;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.battery.BatteryInfoProfile;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo.DeviceInfo;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo.DeviceInfoProfile;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class NutSupport extends AbstractBTLEDeviceSupport {
+    private static final Logger LOG = LoggerFactory.getLogger(NutSupport.class);
+
+    private final GBDeviceEventBatteryInfo batteryCmd = new GBDeviceEventBatteryInfo();
+
+    private final DeviceInfoProfile<NutSupport> deviceInfoProfile;
+    private final BatteryInfoProfile<NutSupport> batteryInfoProfile;
+    private final IntentListener listener = new IntentListener() {
+        @Override
+        public void notify(Intent intent) {
+            String action = intent.getAction();
+            if (action.equals(DeviceInfoProfile.ACTION_DEVICE_INFO)) {
+                handleDeviceInfo((DeviceInfo) intent.getParcelableExtra(DeviceInfoProfile.EXTRA_DEVICE_INFO));
+            } else if (action.equals(BatteryInfoProfile.ACTION_BATTERY_INFO)) {
+                handleBatteryInfo((BatteryInfo) intent.getParcelableExtra(BatteryInfoProfile.EXTRA_BATTERY_INFO));
+            } else {
+                LOG.warn("Unhandled intent given to listener");
+            }
+        }
+    };
+    private SharedPreferences prefs = null;
+    /**
+     * It uses the proprietary Nut interface.
+     */
+    private boolean proprietary = false;
+    /**
+     * Proprietary Nut interface needs authentication.
+     * <p>
+     * Don't write characteristics until authenticated.
+     * <p>
+     * Will disconnect in a minute if you don't authenticate.
+     */
+    private boolean authenticated = true;
+    /**
+     * The two keys used for authentication
+     */
+    private BigInteger key1;
+    private BigInteger key2;
+
+
+    public NutSupport() {
+        super(LOG);
+        addSupportedService(NutConstants.SERVICE_BATTERY);
+        addSupportedService(NutConstants.SERVICE_DEVICE_INFO);
+        addSupportedService(NutConstants.SERVICE_IMMEDIATE_ALERT);
+        addSupportedService(NutConstants.SERVICE_LINK_LOSS);
+        addSupportedService(NutConstants.SERVICE_PROPRIETARY_NUT);
+        addSupportedService(NutConstants.SERVICE_UNKNOWN_2);
+
+        deviceInfoProfile = new DeviceInfoProfile<>(this);
+        deviceInfoProfile.addListener(listener);
+        addSupportedProfile(deviceInfoProfile);
+
+        batteryInfoProfile = new BatteryInfoProfile<>(this);
+        batteryInfoProfile.addListener(listener);
+        addSupportedProfile(batteryInfoProfile);
+    }
+
+    private void handleBatteryInfo(BatteryInfo info) {
+        LOG.info("Received Nut battery info");
+        batteryCmd.level = (short) info.getPercentCharged();
+        handleGBDeviceEvent(batteryCmd);
+    }
+
+    private void handleDeviceInfo(DeviceInfo info) {
+        LOG.info("Received Nut device info");
+        LOG.info(String.valueOf(info));
+        GBDeviceEventVersionInfo versionInfo = new GBDeviceEventVersionInfo();
+        if (info.getHardwareRevision() != null) {
+            versionInfo.hwVersion = info.getHardwareRevision();
+        }
+        if (info.getFirmwareRevision() != null) {
+            versionInfo.fwVersion = info.getFirmwareRevision();
+        }
+
+        handleGBDeviceEvent(versionInfo);
+    }
+
+    @Override
+    protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
+        builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext()));
+        builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZED, getContext()));
+
+        // Init prefs
+        prefs = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress());
+        loadKeysFromPrefs();
+
+        LOG.debug("Requesting device info!");
+        deviceInfoProfile.requestDeviceInfo(builder);
+        batteryInfoProfile.requestBatteryInfo(builder);
+
+        // If this characteristic exists, it has proprietary Nut interface
+        this.proprietary = (getCharacteristic(NutConstants.CHARAC_AUTH_STATUS) != null);
+
+        if (proprietary) {
+            this.authenticated = false;
+            /**
+             * Part of {@link NutConstants.SERVICE_PROPRIETARY_NUT}
+             * Enables proprietary notification
+             */
+            builder.notify(getCharacteristic(NutConstants.CHARAC_AUTH_STATUS), true);
+            LOG.info("Enabled authentication status notify");
+
+            /**
+             * Part of {@link NutConstants.SERVICE_UNKNOWN_2}
+             * Enables button-press notify
+             */
+            builder.notify(getCharacteristic(NutConstants.CHARAC_UNKNOWN_2), true);
+        } else {
+            /**
+             * Part of {@link NutConstants.SERVICE_UNKNOWN_1_WEIRDNESS}
+             * Enables button-press notify
+             */
+            builder.notify(getCharacteristic(NutConstants.CHARAC_CHANGE_POWER), true);
+        }
+
+        readDeviceInfo();
+        return builder;
+    }
+
+    @Override
+    public boolean useAutoConnect() {
+        return true;
+    }
+
+    @Override
+    public void onFindDevice(boolean enable) {
+        deviceImmediateAlert(enable);
+    }
+
+    @Override
+    public boolean onCharacteristicChanged(BluetoothGatt gatt,
+                                           BluetoothGattCharacteristic characteristic) {
+        if (super.onCharacteristicChanged(gatt, characteristic)) {
+            return true;
+        }
+
+        UUID characteristicUUID = characteristic.getUuid();
+        if (characteristicUUID.equals(NutConstants.CHARAC_AUTH_STATUS)) {
+            handleAuthResult(characteristic.getValue());
+            return true;
+        }
+        LOG.info("Unhandled characteristic changed: " + characteristicUUID);
+        return false;
+    }
+
+    @Override
+    public boolean onCharacteristicRead(BluetoothGatt gatt,
+                                        BluetoothGattCharacteristic characteristic,
+                                        int status) {
+        if (super.onCharacteristicRead(gatt, characteristic, status)) {
+            return true;
+        }
+        UUID characteristicUUID = characteristic.getUuid();
+
+        if (characteristicUUID.equals(NutConstants.CHARAC_SYSTEM_ID)) {
+            // TODO: Handle System ID read
+            return true;
+        }
+        LOG.info("Unhandled characteristic read: " + characteristicUUID);
+        return false;
+    }
+
+    /**
+     * Enables or disables link loss alert
+     */
+    private void deviceLinkLossAlert(boolean enable) {
+        UUID charac;
+        if (this.proprietary) {
+            /** Part of {@link NutConstants.SERVICE_PROPRIETARY_NUT} */
+            charac = NutConstants.CHARAC_CHANGE_POWER;
+        } else {
+            /** Part of {@link NutConstants.SERVICE_IMMEDIATE_ALERT} */
+            charac = NutConstants.CHARAC_LINK_LOSS_ALERT_LEVEL;
+        }
+
+        byte[] payload = new byte[]{(byte) (enable ? 0x00 : 0x01)};
+        if (enable) {
+            writeCharacteristic("Enable link loss alert", charac, payload);
+        } else {
+            writeCharacteristic("Disable link loss alert", charac, payload);
+        }
+    }
+
+    /**
+     * Should trigger an immediate alert
+     *
+     * @param enable turn on or not
+     */
+    private void deviceImmediateAlert(boolean enable) {
+        UUID charac;
+        if (this.proprietary) {
+            /** Part of {@link NutConstants.SERVICE_IMMEDIATE_ALERT} */
+            charac = NutConstants.CHARAC_LINK_LOSS_ALERT_LEVEL;
+            if (!authenticated) {
+                LOG.warn("Not authenticated, can't alert");
+                return;
+            }
+        } else {
+            /** Part of {@link NutConstants.SERVICE_PROPRIETARY_NUT} */
+            charac = NutConstants.CHARAC_CHANGE_POWER;
+        }
+
+        if (enable) {
+            writeCharacteristic("Start alert", charac, new byte[]{(byte) 0x04});
+        } else {
+            writeCharacteristic("Stop alert", charac, new byte[]{(byte) 0x03});
+        }
+    }
+
+    /**
+     * This will write a new key to the device
+     * <p>
+     * However, <b>it is irreversible</b>,
+     * if you can't generate the right packets,
+     * the device is basically bricked!
+     * <p>
+     * If you can generate the correct packets,
+     * it can be reset... somehow
+     *
+     * @param key key
+     */
+    private void deviceWriteNewKey(byte[] key) {
+        // TODO: Determine each nuance of how this
+        //  works before using it!
+        byte[] result_payload = new byte[key.length + 1];
+        result_payload[0] = (byte) 0x04;
+        System.arraycopy(key, 0, result_payload, 1, key.length);
+
+        writeCharacteristic("Write new key",
+                NutConstants.CHARAC_DFU_PW,
+                result_payload);
+    }
+
+    /**
+     * Turns the device off
+     */
+    private void deviceShutdown() {
+        writeCharacteristic("Shutdown", NutConstants.CHARAC_CHANGE_POWER, new byte[]{0x06});
+    }
+
+    /**
+     * Switches the device to Nordic's DFU mode
+     */
+    private void deviceDFU() {
+        writeCharacteristic("Enable DFU mode", NutConstants.CHARAC_DFU_PW, new byte[]{0x14});
+    }
+
+    /**
+     * Specifies how long the alert lasts
+     *
+     * @param duration in seconds (I think)
+     */
+    private void deviceWriteAlertDuration(int duration) {
+        if (duration == 0) {
+            duration = 15;
+        }
+
+        UUID charac;
+        if (this.proprietary) {
+            charac = NutConstants.CHARAC_DFU_PW;
+        } else {
+            charac = NutConstants.CHARAC_CHANGE_POWER;
+        }
+
+        writeCharacteristic("Write alert duration",
+                charac,
+                new byte[]{37, (byte) duration});
+    }
+
+    private void readHardwareInfo() {
+        BluetoothGattCharacteristic characteristic = getCharacteristic(NutConstants.CHARAC_HARDWARE_VERSION);
+        if (characteristic != null &&
+                ((characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_READ) > 0)) {
+            readCharacteristic("Device read hardware",
+                    NutConstants.CHARAC_HARDWARE_VERSION);
+        }
+
+        BluetoothGattCharacteristic characteristic1 = getCharacteristic(NutConstants.CHARAC_MANUFACTURER_NAME);
+        if (characteristic1 != null &&
+                (characteristic1.getProperties() & BluetoothGattCharacteristic.PROPERTY_READ) > 0) {
+            readCharacteristic("Read manufacturer",
+                    NutConstants.CHARAC_MANUFACTURER_NAME);
+        }
+    }
+
+    private void readFirmwareInfo() {
+        BluetoothGattCharacteristic characteristic = getCharacteristic(NutConstants.CHARAC_FIRMWARE_VERSION);
+        if (characteristic != null &&
+                (characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_READ) > 0) {
+            readCharacteristic("Read firmware version",
+                    NutConstants.CHARAC_FIRMWARE_VERSION);
+        }
+    }
+
+    private void readBatteryInfo() {
+        BluetoothGattCharacteristic characteristic = getCharacteristic(NutConstants.CHARAC_BATTERY_INFO);
+        if (characteristic != null &&
+                (characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_READ) > 0) {
+            readCharacteristic("Read battery info",
+                    NutConstants.CHARAC_BATTERY_INFO);
+        }
+    }
+
+    /**
+     * Loads the three keys from device-specific shared preferences
+     */
+    private void loadKeysFromPrefs() {
+        if (prefs != null) {
+            LOG.info("Reading keys");
+            key1 = new BigInteger(prefs.getString("nut_packet_key_1", "0"));
+            key2 = new BigInteger(prefs.getString("nut_packet_key_2", "0"));
+            if (key1.equals(BigInteger.ZERO) || key2.equals(BigInteger.ZERO)) {
+                byte[] challenge = NutKey.hexStringToByteArrayNut(prefs.getString("nut_packet_challenge", "00"));
+                byte[] response = NutKey.hexStringToByteArrayNut(prefs.getString("nut_response_challenge", "00"));
+                if (Arrays.equals(challenge, new byte[]{0x00}) ||
+                        Arrays.equals(response, new byte[]{0x00})) {
+                    GB.toast("No key available for the device", Toast.LENGTH_LONG, GB.ERROR);
+                    return;
+                }
+                Map.Entry<BigInteger, BigInteger> key = NutKey.reversePasswordGeneration(
+                        challenge,
+                        response,
+                        gbDevice.getAddress()
+                );
+                if (key == null) {
+                    GB.toast("No correct key available for the device", Toast.LENGTH_LONG, GB.ERROR);
+                    return;
+                }
+                key1 = key.getKey();
+                key2 = key.getValue();
+                LOG.debug("Key was extracted from challenge-response packets");
+            } else {
+                LOG.debug("Key was preset");
+            }
+        }
+    }
+
+    /**
+     * Processes the authentication flow of the proprietary Nut protocol
+     * See more: {@link NutConstants#SERVICE_PROPRIETARY_NUT}
+     *
+     * @param received the notify characteristic's content
+     */
+    public final void handleAuthResult(byte[] received) {
+        if (received != null && received.length != 0) {
+            if (received[0] == 0x01) {
+                // Password is needed
+                // Preamble, counter, rotating key, static key
+                byte[] payload = new byte[1 + 1 + 3 + 12];
+
+                // This is a response to the challenge
+                payload[0] = 0x02;
+
+                // Modify the challenge
+                byte[] response = NutKey.passwordGeneration(gbDevice.getAddress(), received, key1, key2);
+                System.arraycopy(response, 0, payload, 1, response.length);
+
+                writeCharacteristic("Authentication",
+                        NutConstants.CHARAC_DFU_PW,
+                        payload
+                );
+                LOG.debug("Successfully sent auth");
+            } else if (received[0] == 0x03) {
+                if (received[1] == 0x55) {
+                    LOG.debug("Successful password attempt or uninitialized");
+                    authenticated = true;
+                    initChara();
+                } else {
+                    LOG.debug("Error authenticating");
+                    // TODO: Disconnect
+                }
+            } else if (received[0] == 0x05) {
+                LOG.debug("Password has been set");
+            } else {
+                LOG.debug("Invalid packet");
+                // TODO: Disconnect
+            }
+        }
+    }
+
+    /**
+     * Initializes required characteristics
+     */
+    private void initChara() {
+        if (proprietary) {
+            writeCharacteristic("Init alert 1", NutConstants.CHARAC_LINK_LOSS_ALERT_LEVEL, new byte[]{(byte) 0x00});
+            writeCharacteristic("Init alert 2", NutConstants.CHARAC_LINK_LOSS_ALERT_LEVEL, new byte[]{(byte) 0x00});
+            writeCharacteristic("Init alert 3", NutConstants.CHARAC_LINK_LOSS_ALERT_LEVEL, new byte[]{(byte) 0x00});
+        }
+    }
+
+    /**
+     * Initiates a read of all the device information characteristics
+     */
+    private void readDeviceInfo() {
+        readBatteryInfo();
+        readHardwareInfo();
+        readFirmwareInfo();
+    }
+
+    /**
+     * Just wraps writing into a neat little function
+     *
+     * @param taskName something that describes the task a bit
+     * @param charac   the characteristic to write
+     * @param data     the data to write
+     */
+    private void writeCharacteristic(String taskName, UUID charac, byte[] data) {
+        BluetoothGattCharacteristic characteristic = getCharacteristic(charac);
+
+        TransactionBuilder builder = new TransactionBuilder(taskName);
+        builder.write(characteristic, data);
+        builder.queue(getQueue());
+    }
+
+    /**
+     * Just wraps reading into a neat little function
+     *
+     * @param taskName something that describes the task a bit
+     * @param charac   the characteristic to read
+     */
+    private void readCharacteristic(String taskName, UUID charac) {
+        BluetoothGattCharacteristic characteristic = getCharacteristic(charac);
+
+        TransactionBuilder builder = new TransactionBuilder(taskName);
+        builder.read(characteristic);
+        builder.queue(getQueue());
+    }
+
+    @Override
+    public boolean getImplicitCallbackModify() {
+        return true;
+    }
+
+    @Override
+    public boolean getSendWriteRequestResponse() {
+        return false;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/oppo/OppoHeadphonesIoThread.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/oppo/OppoHeadphonesIoThread.java
new file mode 100644
index 0000000..a67f667
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/oppo/OppoHeadphonesIoThread.java
@@ -0,0 +1,75 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.oppo;
+
+import static nodomain.freeyourgadget.gadgetbridge.util.GB.hexdump;
+
+import android.bluetooth.BluetoothAdapter;
+import android.content.Context;
+import android.os.ParcelUuid;
+
+import androidx.annotation.NonNull;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.btclassic.BtClassicIoThread;
+import nodomain.freeyourgadget.gadgetbridge.service.serial.AbstractSerialDeviceSupport;
+
+public class OppoHeadphonesIoThread extends BtClassicIoThread {
+    private static final Logger LOG = LoggerFactory.getLogger(OppoHeadphonesIoThread.class);
+
+    private final OppoHeadphonesProtocol mProtocol;
+
+    public OppoHeadphonesIoThread(final GBDevice gbDevice,
+                                  final Context context,
+                                  final OppoHeadphonesProtocol deviceProtocol,
+                                  final AbstractSerialDeviceSupport deviceSupport,
+                                  final BluetoothAdapter btAdapter) {
+        super(gbDevice, context, deviceProtocol, deviceSupport, btAdapter);
+        this.mProtocol = deviceProtocol;
+    }
+
+    @NonNull
+    @Override
+    protected UUID getUuidToConnect(@NonNull final ParcelUuid[] uuids) {
+        return UUID.fromString("0000079a-d102-11e1-9b23-00025b00a5a5");
+    }
+
+    @Override
+    protected void initialize() {
+        write(mProtocol.encodeFirmwareVersionReq());
+        write(mProtocol.encodeConfigurationReq());
+        write(mProtocol.encodeBatteryReq());
+        setUpdateState(GBDevice.State.INITIALIZED);
+    }
+
+    @Override
+    protected byte[] parseIncoming(final InputStream inStream) throws IOException {
+        final byte[] buffer = new byte[1048576]; //HUGE read
+        final int bytes = inStream.read(buffer);
+        // FIXME: We should buffer this and handle partial commands
+        LOG.debug("Read {} bytes: {}", bytes, hexdump(buffer, 0, bytes));
+        return Arrays.copyOf(buffer, bytes);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/oppo/OppoHeadphonesProtocol.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/oppo/OppoHeadphonesProtocol.java
new file mode 100644
index 0000000..304ec6b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/oppo/OppoHeadphonesProtocol.java
@@ -0,0 +1,363 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.oppo;
+
+import org.apache.commons.lang3.ArrayUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdatePreferences;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo;
+import nodomain.freeyourgadget.gadgetbridge.devices.oppo.OppoHeadphonesPreferences;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.BatteryState;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.oppo.commands.OppoCommand;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.oppo.commands.TouchConfigSide;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.oppo.commands.TouchConfigType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.oppo.commands.TouchConfigValue;
+import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol;
+import nodomain.freeyourgadget.gadgetbridge.util.preferences.DevicePrefs;
+
+public class OppoHeadphonesProtocol extends GBDeviceProtocol {
+    private static final Logger LOG = LoggerFactory.getLogger(OppoHeadphonesProtocol.class);
+
+    public static final byte CMD_PREAMBLE = (byte) 0xaa;
+
+    private int seqNum = 0;
+
+    protected OppoHeadphonesProtocol(final GBDevice device) {
+        super(device);
+    }
+
+    @Override
+    public GBDeviceEvent[] decodeResponse(final byte[] responseData) {
+        final List<GBDeviceEvent> events = new ArrayList<>();
+        final ByteBuffer buf = ByteBuffer.wrap(responseData);
+
+        while (buf.position() < buf.limit()) {
+            final byte preamble = buf.get();
+            if (preamble != CMD_PREAMBLE) {
+                LOG.warn("Unexpected preamble {}", preamble);
+                continue;
+            }
+
+            final int totalLength = buf.get() & 0xff;
+            if (buf.limit() - buf.position() < totalLength) {
+                LOG.error("Got partial response with {} bytes, expected {}", buf.limit() - buf.position(), totalLength);
+                break;
+            }
+
+            final byte[] singleResponse = new byte[totalLength + 2];
+            buf.position(buf.position() - 2);
+            buf.get(singleResponse);
+
+            events.addAll(handleSingleResponse(singleResponse));
+        }
+        return events.toArray(new GBDeviceEvent[0]);
+    }
+
+    private static List<GBDeviceEvent> handleSingleResponse(final byte[] responseData) {
+        final List<GBDeviceEvent> events = new ArrayList<>();
+
+        final ByteBuffer responseBuf = ByteBuffer.wrap(responseData).order(ByteOrder.LITTLE_ENDIAN);
+        final byte preamble = responseBuf.get();
+
+        if (preamble != CMD_PREAMBLE) {
+            LOG.error("Unexpected preamble {}", preamble);
+            return Collections.emptyList();
+        }
+
+        final int totalLength = responseBuf.get() & 0xff;
+        if (responseData.length != totalLength + 2) {
+            LOG.error("Invalid number of bytes {}, expected {}", responseData.length, totalLength + 2);
+            return Collections.emptyList();
+        }
+
+        final short zero = responseBuf.getShort();
+        if (zero != 0 && zero != 4) {
+            // 0 on oppo, 4 on realme?
+            LOG.warn("Unexpected bytes: {}, expected 0 or 4", zero);
+        }
+
+        final short code = responseBuf.getShort();
+        final OppoCommand command = OppoCommand.fromCode(code);
+        if (command == null) {
+            LOG.warn("Unknown command code {}", String.format(Locale.ROOT, "0x%04x", code));
+            return Collections.emptyList();
+        }
+
+        final int seq = responseBuf.get();
+        final short payloadLength = responseBuf.getShort();
+        final byte[] payload = new byte[payloadLength];
+        responseBuf.get(payload);
+
+        switch (command) {
+            case BATTERY_RET: {
+                if (payload[0] != 0) {
+                    LOG.error("Unknown battery ret {}", payload[0]);
+                    break;
+                }
+                events.addAll(parseBattery(payload));
+                break;
+            }
+            case DEVICE_INFO: {
+                switch (payload[0]) {
+                    case 1: // battery
+                        events.addAll(parseBattery(payload));
+                        break;
+                    case 2: // status
+                        LOG.debug("Got status");
+                        // TODO handle
+                        break;
+                    default:
+                        LOG.warn("Unknown device info {}", payload[0]);
+                }
+
+                break;
+            }
+            case FIRMWARE_RET: {
+                if (payload[0] != 0) {
+                    LOG.warn("Unexpected firmware ret {}", payload[0]);
+                    break;
+                }
+
+                final String fwString;
+                if (payload[payload.length - 1] == 0) {
+                    fwString = new String(ArrayUtils.subarray(payload, 2, payload.length - 1)).strip();
+                } else {
+                    fwString = new String(ArrayUtils.subarray(payload, 2, payload.length)).strip();
+                }
+                final String[] parts = fwString.split(",");
+                if (parts.length % 3 != 0) {
+                    LOG.warn("Fw parts length {} from '{}' is not divisible by 3", parts.length, fwString);
+
+                    // We need to persist something, otherwise Gb misbehaves
+                    final GBDeviceEventVersionInfo eventVersionInfo = new GBDeviceEventVersionInfo();
+                    eventVersionInfo.fwVersion = fwString;
+                    eventVersionInfo.hwVersion = GBApplication.getContext().getString(R.string.n_a);
+                    events.add(eventVersionInfo);
+
+                    break;
+                }
+                final String[] fwVersionParts = new String[3];
+                for (int i = 0; i < parts.length; i += 3) {
+                    final String versionPart = parts[i];
+                    final String versionType = parts[i + 1];
+                    final String version = parts[i + 2];
+                    if (!"2".equals(versionType)) {
+                        continue; // not fw
+                    }
+
+                    switch (versionPart) {
+                        case "1":
+                            fwVersionParts[0] = version;
+                            break;
+                        case "2":
+                            fwVersionParts[1] = version;
+                            break;
+                        case "3":
+                            fwVersionParts[2] = version;
+                            break;
+                        default:
+                            LOG.warn("Unknown firmware version part {}", versionPart);
+                    }
+                }
+
+                final List<String> nonNullParts = new ArrayList<>(fwVersionParts.length);
+                for (int i = 0; i < fwVersionParts.length; i++) {
+                    if (fwVersionParts[i] == null) {
+                        continue;
+                    }
+                    nonNullParts.add(fwVersionParts[i]);
+                    if (fwVersionParts[i].contains(".")) {
+                        // Realme devices have the version already with the dots, repeated multiple times
+                        break;
+                    }
+                }
+                final String fwVersion = String.join(".", nonNullParts);
+
+                final GBDeviceEventVersionInfo eventVersionInfo = new GBDeviceEventVersionInfo();
+                eventVersionInfo.fwVersion = fwVersion;
+                eventVersionInfo.hwVersion = GBApplication.getContext().getString(R.string.n_a);
+                events.add(eventVersionInfo);
+
+                LOG.debug("Got fw version: {}", fwVersion);
+
+                break;
+            }
+            case FIND_DEVICE_ACK: {
+                LOG.debug("Got find device ack, status={}", payload[0]);
+                break;
+            }
+            case TOUCH_CONFIG_RET: {
+                if (payload[0] != 0) {
+                    LOG.warn("Unknown config ret {}", payload[0]);
+                    break;
+                }
+                if ((payload.length - 2) % 4 != 0) {
+                    LOG.warn("Unexpected config ret payload size {}", payload.length);
+                    break;
+                }
+
+                final GBDeviceEventUpdatePreferences eventUpdatePreferences = new GBDeviceEventUpdatePreferences();
+
+                for (int i = 2; i < payload.length; i += 4) {
+                    final int sideCode = payload[i] & 0xff;
+                    final int typeCode = BLETypeConversions.toUint16(payload, i + 1);
+                    final int valueCode = payload[i + 3] & 0xff;
+                    final TouchConfigSide side = TouchConfigSide.fromCode(sideCode);
+                    final TouchConfigType type = TouchConfigType.fromCode(typeCode);
+                    final TouchConfigValue value = TouchConfigValue.fromCode(valueCode);
+
+                    if (side == null) {
+                        LOG.warn("Unknown touch side code {}", sideCode);
+                        continue;
+                    }
+                    if (type == null) {
+                        LOG.warn("Unknown touch type code {}", typeCode);
+                        continue;
+                    }
+                    if (value == null) {
+                        LOG.warn("Unknown touch value code {}", valueCode);
+                        continue;
+                    }
+
+                    LOG.debug("Got touch config for {} {} = {}", side, type, value);
+
+                    eventUpdatePreferences.withPreference(
+                            OppoHeadphonesPreferences.getKey(side, type),
+                            value.name().toLowerCase(Locale.ROOT)
+                    );
+                }
+
+                events.add(eventUpdatePreferences);
+
+                break;
+            }
+            case TOUCH_CONFIG_ACK: {
+                LOG.debug("Got config ack, status={}", payload[0]);
+                break;
+            }
+            default:
+                LOG.warn("Unhandled command {}", command);
+        }
+
+        return events;
+    }
+
+    private static List<GBDeviceEvent> parseBattery(final byte[] payload) {
+        final List<GBDeviceEvent> events = new ArrayList<>();
+
+        final int numBatteries = payload[1] & 0xff;
+        for (int i = 2; i < payload.length; i += 2) {
+            if ((payload[i] & 0xff) == 0xff) {
+                continue;
+            }
+            final int batteryIndex = payload[i] - 1;
+            if (batteryIndex < 0 || batteryIndex > 2) {
+                LOG.error("Unknown battery index {}", payload[i]);
+                break;
+            }
+
+            final int batteryLevel = payload[i + 1] & 0x7f;
+            final BatteryState batteryState = (payload[i + 1] & 0x80) != 0 ? BatteryState.BATTERY_CHARGING : BatteryState.BATTERY_NORMAL;
+
+            LOG.debug("Got battery {}: {}%, {}", batteryIndex, batteryLevel, batteryState);
+
+            final GBDeviceEventBatteryInfo eventBatteryInfo = new GBDeviceEventBatteryInfo();
+            eventBatteryInfo.batteryIndex = batteryIndex;
+            eventBatteryInfo.level = batteryLevel;
+            eventBatteryInfo.state = batteryState;
+            events.add(eventBatteryInfo);
+        }
+
+        return events;
+    }
+
+    @Override
+    public byte[] encodeFirmwareVersionReq() {
+        return encodeMessage(OppoCommand.FIRMWARE_GET, new byte[0]);
+    }
+
+    @Override
+    public byte[] encodeFindDevice(final boolean start) {
+        return encodeMessage(OppoCommand.FIND_DEVICE_REQ, new byte[]{(byte) (start ? 0x01 : 0x00)});
+    }
+
+    @Override
+    public byte[] encodeSendConfiguration(final String config) {
+        final DevicePrefs prefs = getDevicePrefs();
+
+        if (config.startsWith("oppo_touch__")) {
+            final String[] parts = config.split("__");
+            final TouchConfigSide side = TouchConfigSide.valueOf(parts[1].toUpperCase(Locale.ROOT));
+            final TouchConfigType type = TouchConfigType.valueOf(parts[2].toUpperCase(Locale.ROOT));
+            final String valueCode = prefs.getString(OppoHeadphonesPreferences.getKey(side, type), null);
+            if (valueCode == null) {
+                LOG.warn("Failed to get touch option value for {}/{}", side, type);
+                return super.encodeSendConfiguration(config);
+            }
+
+            final TouchConfigValue value = TouchConfigValue.valueOf(valueCode.toUpperCase(Locale.ROOT));
+
+            LOG.debug("Sending {} {} = {}", side, type, value);
+
+            final ByteBuffer buf = ByteBuffer.allocate(5).order(ByteOrder.LITTLE_ENDIAN);
+            buf.put((byte) 0x01);
+            buf.put((byte) side.getCode());
+            buf.putShort((short) type.getCode());
+            buf.put((byte) value.getCode());
+
+            return encodeMessage(OppoCommand.TOUCH_CONFIG_SET, buf.array());
+        }
+
+        return super.encodeSendConfiguration(config);
+    }
+
+    public byte[] encodeBatteryReq() {
+        return encodeMessage(OppoCommand.BATTERY_REQ, new byte[0]);
+    }
+
+    public byte[] encodeConfigurationReq() {
+        return encodeMessage(OppoCommand.TOUCH_CONFIG_REQ, new byte[]{0x02, 0x03, 0x01});
+    }
+
+    private byte[] encodeMessage(final OppoCommand command, final byte[] payload) {
+        final ByteBuffer buf = ByteBuffer.allocate(9 + payload.length).order(ByteOrder.LITTLE_ENDIAN);
+        buf.put(CMD_PREAMBLE);
+        buf.put((byte) (buf.limit() - 2));
+        buf.put((byte) 0);
+        buf.put((byte) 0);
+        buf.putShort(command.getCode());
+        buf.put((byte) seqNum++);
+        buf.putShort((short) payload.length);
+        buf.put(payload);
+        return buf.array();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/oppo/OppoHeadphonesSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/oppo/OppoHeadphonesSupport.java
new file mode 100644
index 0000000..2e391ec
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/oppo/OppoHeadphonesSupport.java
@@ -0,0 +1,44 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.oppo;
+
+import nodomain.freeyourgadget.gadgetbridge.service.AbstractHeadphoneDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceIoThread;
+import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol;
+
+public class OppoHeadphonesSupport extends AbstractHeadphoneDeviceSupport {
+    @Override
+    protected GBDeviceProtocol createDeviceProtocol() {
+        return new OppoHeadphonesProtocol(getDevice());
+    }
+
+    @Override
+    protected GBDeviceIoThread createDeviceIOThread() {
+        return new OppoHeadphonesIoThread(
+                getDevice(),
+                getContext(),
+                (OppoHeadphonesProtocol) getDeviceProtocol(),
+                OppoHeadphonesSupport.this,
+                getBluetoothAdapter()
+        );
+    }
+
+    @Override
+    public boolean useAutoConnect() {
+        return false;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/oppo/commands/OppoCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/oppo/commands/OppoCommand.java
new file mode 100644
index 0000000..0d594c8
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/oppo/commands/OppoCommand.java
@@ -0,0 +1,55 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.oppo.commands;
+
+import androidx.annotation.Nullable;
+
+public enum OppoCommand {
+    BATTERY_REQ(0x0106),
+    BATTERY_RET(0x8106),
+    DEVICE_INFO(0x0204),
+    FIRMWARE_GET(0x0105),
+    FIRMWARE_RET(0x8105),
+    TOUCH_CONFIG_REQ(0x0108),
+    TOUCH_CONFIG_SET(0x0401),
+    TOUCH_CONFIG_RET(0x8108),
+    TOUCH_CONFIG_ACK(0x8401),
+    FIND_DEVICE_REQ(0x0400),
+    FIND_DEVICE_ACK(0x8400),
+    ;
+
+    private final short code;
+
+    OppoCommand(final int code) {
+        this.code = (short) code;
+    }
+
+    public short getCode() {
+        return code;
+    }
+
+    @Nullable
+    public static OppoCommand fromCode(final short code) {
+        for (final OppoCommand cmd : OppoCommand.values()) {
+            if (cmd.code == code) {
+                return cmd;
+            }
+        }
+
+        return null;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/oppo/commands/TouchConfigSide.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/oppo/commands/TouchConfigSide.java
new file mode 100644
index 0000000..b717567
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/oppo/commands/TouchConfigSide.java
@@ -0,0 +1,47 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.oppo.commands;
+
+import androidx.annotation.Nullable;
+
+public enum TouchConfigSide {
+    LEFT(0x01),
+    RIGHT(0x02),
+    BOTH(0x04),
+    ;
+
+    private final int code;
+
+    TouchConfigSide(final int code) {
+        this.code = code;
+    }
+
+    public int getCode() {
+        return code;
+    }
+
+    @Nullable
+    public static TouchConfigSide fromCode(final int code) {
+        for (final TouchConfigSide param : TouchConfigSide.values()) {
+            if (param.code == code) {
+                return param;
+            }
+        }
+
+        return null;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/oppo/commands/TouchConfigType.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/oppo/commands/TouchConfigType.java
new file mode 100644
index 0000000..a47024e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/oppo/commands/TouchConfigType.java
@@ -0,0 +1,48 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.oppo.commands;
+
+import androidx.annotation.Nullable;
+
+public enum TouchConfigType {
+    UNK_1(0x0101),
+    TAP_2(0x0201),
+    TAP_3(0x0301),
+    HOLD(0x0401),
+    ;
+
+    private final int code;
+
+    TouchConfigType(final int code) {
+        this.code = code;
+    }
+
+    public int getCode() {
+        return code;
+    }
+
+    @Nullable
+    public static TouchConfigType fromCode(final int code) {
+        for (final TouchConfigType param : TouchConfigType.values()) {
+            if (param.code == code) {
+                return param;
+            }
+        }
+
+        return null;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/oppo/commands/TouchConfigValue.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/oppo/commands/TouchConfigValue.java
new file mode 100644
index 0000000..ec496ec
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/oppo/commands/TouchConfigValue.java
@@ -0,0 +1,53 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.oppo.commands;
+
+import androidx.annotation.Nullable;
+
+public enum TouchConfigValue {
+    OFF(0x00),
+    PLAY_PAUSE(0x01),
+    VOICE_ASSISTANT(0x03), // oppo
+    VOICE_ASSISTANT_REALME(0x04),
+    PREVIOUS(0x05),
+    NEXT(0x06),
+    GAME_MODE(0x11),
+    VOLUME_UP(0x0B),
+    VOLUME_DOWN(0x0C),
+    ;
+
+    private final int code;
+
+    TouchConfigValue(final int code) {
+        this.code = code;
+    }
+
+    public int getCode() {
+        return code;
+    }
+
+    @Nullable
+    public static TouchConfigValue fromCode(final int code) {
+        for (final TouchConfigValue param : TouchConfigValue.values()) {
+            if (param.code == code) {
+                return param;
+            }
+        }
+
+        return null;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandler.java
new file mode 100644
index 0000000..9313999
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandler.java
@@ -0,0 +1,91 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, Carsten Pfeiffer, Daniele
+    Gobbetti
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.pebble;
+
+
+import android.util.Pair;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventSendBytes;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
+import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.PebbleUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.preferences.DevicePrefs;
+
+class AppMessageHandler {
+    final PebbleProtocol mPebbleProtocol;
+    final UUID mUUID;
+    Map<String, Integer> messageKeys;
+    protected final DevicePrefs devicePrefs;
+
+
+    AppMessageHandler(UUID uuid, PebbleProtocol pebbleProtocol) {
+        mUUID = uuid;
+        mPebbleProtocol = pebbleProtocol;
+        devicePrefs = GBApplication.getDevicePrefs(pebbleProtocol.getDevice());
+    }
+
+    public boolean isEnabled() {
+        return true;
+    }
+
+    public UUID getUUID() {
+        return mUUID;
+    }
+
+    public GBDeviceEvent[] handleMessage(ArrayList<Pair<Integer, Object>> pairs) {
+        // Just ACK
+        GBDeviceEventSendBytes sendBytesAck = new GBDeviceEventSendBytes();
+        sendBytesAck.encodedBytes = mPebbleProtocol.encodeApplicationMessageAck(mUUID, mPebbleProtocol.last_id);
+        return new GBDeviceEvent[]{sendBytesAck};
+    }
+
+    public GBDeviceEvent[] onAppStart() {
+        return null;
+    }
+
+    public byte[] encodeUpdateWeather(WeatherSpec weatherSpec) {
+        return null;
+    }
+
+    protected GBDevice getDevice() {
+        return mPebbleProtocol.getDevice();
+    }
+
+    JSONObject getAppKeys() throws IOException, JSONException {
+        File destDir = PebbleUtils.getPbwCacheDir();
+        File configurationFile = new File(destDir, mUUID.toString() + ".json");
+        if (configurationFile.exists()) {
+            String jsonstring = FileUtils.getStringFromFile(configurationFile);
+            JSONObject json = new JSONObject(jsonstring);
+            return json.getJSONObject("appKeys");
+        }
+        throw new IOException();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerGBPebble.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerGBPebble.java
new file mode 100644
index 0000000..a59e5b2
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerGBPebble.java
@@ -0,0 +1,72 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, Carsten Pfeiffer
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+
+package nodomain.freeyourgadget.gadgetbridge.service.devices.pebble;
+
+import android.util.Pair;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventFindPhone;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventSendBytes;
+
+public class AppMessageHandlerGBPebble extends AppMessageHandler {
+
+    private static final Logger LOG = LoggerFactory.getLogger(AppMessageHandlerMisfit.class);
+
+    private static final int KEY_FIND_PHONE_START = 1;
+    private static final int KEY_FIND_PHONE_STOP = 2;
+
+
+    AppMessageHandlerGBPebble(UUID uuid, PebbleProtocol pebbleProtocol) {
+        super(uuid, pebbleProtocol);
+    }
+
+    @Override
+    public GBDeviceEvent[] handleMessage(ArrayList<Pair<Integer, Object>> pairs) {
+        GBDeviceEventFindPhone gbDeviceEventFindPhone = null;
+
+        for (Pair<Integer, Object> pair : pairs) {
+            switch (pair.first) {
+                case KEY_FIND_PHONE_START:
+                    LOG.info("find phone start");
+                    gbDeviceEventFindPhone = new GBDeviceEventFindPhone();
+                    gbDeviceEventFindPhone.event = GBDeviceEventFindPhone.Event.START;
+                    break;
+                case KEY_FIND_PHONE_STOP:
+                    LOG.info("find phone stop");
+                    gbDeviceEventFindPhone = new GBDeviceEventFindPhone();
+                    gbDeviceEventFindPhone.event = GBDeviceEventFindPhone.Event.STOP;
+                    break;
+                default:
+                    LOG.info("unhandled key: " + pair.first);
+                    break;
+            }
+        }
+
+        // always ack
+        GBDeviceEventSendBytes sendBytesAck = new GBDeviceEventSendBytes();
+        sendBytesAck.encodedBytes = mPebbleProtocol.encodeApplicationMessageAck(mUUID, mPebbleProtocol.last_id);
+
+        return new GBDeviceEvent[]{sendBytesAck, gbDeviceEventFindPhone};
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerHealthify.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerHealthify.java
new file mode 100644
index 0000000..31a3f05
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerHealthify.java
@@ -0,0 +1,85 @@
+/*  Copyright (C) 2016-2024 Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.pebble;
+
+import android.util.Pair;
+import android.widget.Toast;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventSendBytes;
+import nodomain.freeyourgadget.gadgetbridge.model.Weather;
+import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+class AppMessageHandlerHealthify extends AppMessageHandler {
+    private Integer KEY_TEMPERATURE;
+    private Integer KEY_CONDITIONS;
+
+    AppMessageHandlerHealthify(UUID uuid, PebbleProtocol pebbleProtocol) {
+        super(uuid, pebbleProtocol);
+
+        try {
+            JSONObject appKeys = getAppKeys();
+            KEY_TEMPERATURE = appKeys.getInt("TEMPERATURE");
+            KEY_CONDITIONS = appKeys.getInt("CONDITIONS");
+        } catch (JSONException e) {
+            GB.toast("There was an error accessing the Helthify watchface configuration.", Toast.LENGTH_LONG, GB.ERROR);
+        } catch (IOException ignore) {
+        }
+    }
+
+    private byte[] encodeHelthifyWeatherMessage(WeatherSpec weatherSpec) {
+        if (weatherSpec == null) {
+            return null;
+        }
+
+        ArrayList<Pair<Integer, Object>> pairs = new ArrayList<>(2);
+        pairs.add(new Pair<>(KEY_CONDITIONS, (Object) weatherSpec.currentCondition));
+        pairs.add(new Pair<>(KEY_TEMPERATURE, (Object) (weatherSpec.currentTemp - 273)));
+        byte[] weatherMessage = mPebbleProtocol.encodeApplicationMessagePush(PebbleProtocol.ENDPOINT_APPLICATIONMESSAGE, mUUID, pairs, null);
+
+        ByteBuffer buf = ByteBuffer.allocate(weatherMessage.length);
+
+        buf.put(weatherMessage);
+
+        return buf.array();
+    }
+
+    @Override
+    public GBDeviceEvent[] onAppStart() {
+        WeatherSpec weatherSpec = Weather.getInstance().getWeatherSpec();
+        if (weatherSpec == null) {
+            return new GBDeviceEvent[]{null};
+        }
+        GBDeviceEventSendBytes sendBytes = new GBDeviceEventSendBytes();
+        sendBytes.encodedBytes = encodeHelthifyWeatherMessage(weatherSpec);
+        return new GBDeviceEvent[]{sendBytes};
+    }
+
+    @Override
+    public byte[] encodeUpdateWeather(WeatherSpec weatherSpec) {
+        return encodeHelthifyWeatherMessage(weatherSpec);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerM7S.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerM7S.java
new file mode 100644
index 0000000..b467e14
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerM7S.java
@@ -0,0 +1,162 @@
+/*  Copyright (C) 2018-2024 Carsten Pfeiffer, jcrode, Johann C. Rode
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.pebble;
+
+import android.util.Pair;
+import android.widget.Toast;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Locale;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventSendBytes;
+import nodomain.freeyourgadget.gadgetbridge.model.Weather;
+import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+class AppMessageHandlerM7S extends AppMessageHandler {
+    private static final int CLEAR = 73;
+    private static final int CLOUDY = 34;
+    private static final int FOG = 60;
+    private static final int DRIZZLE = 45;
+    private static final int LIGHT_RAIN = 36;
+    private static final int RAIN = 36;
+    private static final int THUNDERSTORM = 70;
+    private static final int SNOW = 57;
+    private static final int HAIL = 51;
+    private static final int WIND = 66;
+    private static final int EXTREME_WIND = 66;
+    private static final int TORNADO = 88;
+    private static final int HURRICANE = 88;
+    private static final int EXTREME_COLD = 90;
+    private static final int EXTREME_HEAT = 93;
+    private static final int HAZE = 63;
+
+    private Integer KEY_LOCATION_NAME;
+    private Integer KEY_WEATHER_TEMP;
+    private Integer KEY_WEATHER_STRING_1;
+    private Integer KEY_WEATHER_STRING_2;
+    private Integer KEY_WEATHER_ICON;
+    private Integer KEY_WEATHER_DATA_TIME;
+
+    AppMessageHandlerM7S(UUID uuid, PebbleProtocol pebbleProtocol) {
+        super(uuid, pebbleProtocol);
+
+        try {
+            JSONObject appKeys = getAppKeys();
+            KEY_LOCATION_NAME = appKeys.getInt("KEY_LOCATION_NAME");
+            KEY_WEATHER_TEMP = appKeys.getInt("KEY_WEATHER_TEMP");
+            KEY_WEATHER_STRING_1 = appKeys.getInt("KEY_WEATHER_STRING_1");
+            KEY_WEATHER_STRING_2 = appKeys.getInt("KEY_WEATHER_STRING_2");
+            KEY_WEATHER_ICON = appKeys.getInt("KEY_WEATHER_ICON");
+            KEY_WEATHER_DATA_TIME = appKeys.getInt("KEY_WEATHER_DATA_TIME");
+        } catch (JSONException e) {
+            GB.toast("There was an error accessing the M7S watchface configuration.", Toast.LENGTH_LONG, GB.ERROR);
+        } catch (IOException ignore) {
+        }
+    }
+
+    private int getIconForConditionCode(int conditionCode) {
+        if (conditionCode == 800 || conditionCode == 951) {
+            return CLEAR;
+        } else if (conditionCode > 800 && conditionCode < 900) {
+            return CLOUDY;
+        } else if (conditionCode >= 300 && conditionCode < 313) {
+            return DRIZZLE;
+        } else if (conditionCode >= 313 && conditionCode < 400) {
+            return LIGHT_RAIN;
+        } else if (conditionCode >= 500 && conditionCode < 600) {
+            return RAIN;
+        } else if (conditionCode >= 700 && conditionCode < 732) {
+            return HAZE;
+        } else if (conditionCode == 741) {
+            return FOG;
+        } else if (conditionCode == 751 || conditionCode == 761 || conditionCode == 762 ) {
+            return HAZE;
+        } else if (conditionCode == 771) {
+            return WIND;
+        } else if (conditionCode == 781) {
+            return TORNADO;
+        } else if (conditionCode >= 200 && conditionCode < 300) {
+            return THUNDERSTORM;
+        } else if (conditionCode >= 600 && conditionCode < 700) {
+            return SNOW;
+        } else if (conditionCode == 906) {
+            return HAIL;
+        } else if (conditionCode >= 907 && conditionCode < 957) {
+            return WIND;
+        } else if (conditionCode == 905) {
+            return EXTREME_WIND;
+        } else if (conditionCode == 900) {
+            return TORNADO;
+        } else if (conditionCode == 901 || conditionCode == 902 || conditionCode == 962) {
+            return HURRICANE;
+        } else if (conditionCode == 903) {
+            return EXTREME_COLD;
+        } else if (conditionCode == 904) {
+            return EXTREME_HEAT;
+        }
+
+        return 0;
+    }
+
+    private byte[] encodeM7SWeatherMessage(WeatherSpec weatherSpec) {
+        if (weatherSpec == null) {
+            return null;
+        }
+
+        String wString1 = String.format(Locale.ENGLISH, "%.0f / %.0f__C \n%.0f %s", (weatherSpec.todayMaxTemp-273.15), (weatherSpec.todayMinTemp-273.15), weatherSpec.windSpeed, "km/h");
+        String wString2 = String.format(Locale.ENGLISH, "%d %%", weatherSpec.currentHumidity);
+
+        ArrayList<Pair<Integer, Object>> pairs = new ArrayList<>(2);
+        pairs.add(new Pair<>(KEY_LOCATION_NAME, (Object) (weatherSpec.location)));
+        pairs.add(new Pair<>(KEY_WEATHER_TEMP, (Object) ((int) Math.round(weatherSpec.currentTemp - 273.15))));
+        pairs.add(new Pair<>(KEY_WEATHER_DATA_TIME, (Object) (weatherSpec.timestamp)));
+        pairs.add(new Pair<>(KEY_WEATHER_STRING_1, (Object) (wString1)));
+        pairs.add(new Pair<>(KEY_WEATHER_STRING_2, (Object) (wString2)));
+        pairs.add(new Pair<>(KEY_WEATHER_ICON, (Object) (getIconForConditionCode(weatherSpec.currentConditionCode))));
+        byte[] weatherMessage = mPebbleProtocol.encodeApplicationMessagePush(PebbleProtocol.ENDPOINT_APPLICATIONMESSAGE, mUUID, pairs, null);
+
+        ByteBuffer buf = ByteBuffer.allocate(weatherMessage.length);
+
+        buf.put(weatherMessage);
+
+        return buf.array();
+    }
+
+    @Override
+    public GBDeviceEvent[] onAppStart() {
+        WeatherSpec weatherSpec = Weather.getInstance().getWeatherSpec();
+        if (weatherSpec == null) {
+            return new GBDeviceEvent[]{null};
+        }
+        GBDeviceEventSendBytes sendBytes = new GBDeviceEventSendBytes();
+        sendBytes.encodedBytes = encodeM7SWeatherMessage(weatherSpec);
+        return new GBDeviceEvent[]{sendBytes};
+    }
+
+    @Override
+    public byte[] encodeUpdateWeather(WeatherSpec weatherSpec) {
+        return encodeM7SWeatherMessage(weatherSpec);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerMarioTime.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerMarioTime.java
new file mode 100644
index 0000000..a35232f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerMarioTime.java
@@ -0,0 +1,71 @@
+/*  Copyright (C) 2016-2024 Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.pebble;
+
+import android.util.Pair;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventSendBytes;
+import nodomain.freeyourgadget.gadgetbridge.model.Weather;
+import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
+
+class AppMessageHandlerMarioTime extends AppMessageHandler {
+
+    private static final int KEY_WEATHER_ICON_ID = 10;
+    private static final int KEY_WEATHER_TEMPERATURE = 11;
+
+    AppMessageHandlerMarioTime(UUID uuid, PebbleProtocol pebbleProtocol) {
+        super(uuid, pebbleProtocol);
+    }
+
+    private byte[] encodeMarioWeatherMessage(WeatherSpec weatherSpec) {
+        if (weatherSpec == null) {
+            return null;
+        }
+
+        ArrayList<Pair<Integer, Object>> pairs = new ArrayList<>(2);
+        pairs.add(new Pair<>(KEY_WEATHER_ICON_ID, (Object) (byte) 1));
+        pairs.add(new Pair<>(KEY_WEATHER_TEMPERATURE, (Object) (byte) (weatherSpec.currentTemp - 273)));
+        byte[] weatherMessage = mPebbleProtocol.encodeApplicationMessagePush(PebbleProtocol.ENDPOINT_APPLICATIONMESSAGE, mUUID, pairs, null);
+
+        ByteBuffer buf = ByteBuffer.allocate(weatherMessage.length);
+
+        buf.put(weatherMessage);
+
+        return buf.array();
+    }
+
+    @Override
+    public GBDeviceEvent[] onAppStart() {
+        WeatherSpec weatherSpec = Weather.getInstance().getWeatherSpec();
+        if (weatherSpec == null) {
+            return new GBDeviceEvent[]{null};
+        }
+        GBDeviceEventSendBytes sendBytes = new GBDeviceEventSendBytes();
+        sendBytes.encodedBytes = encodeMarioWeatherMessage(weatherSpec);
+        return new GBDeviceEvent[]{sendBytes};
+    }
+
+    @Override
+    public byte[] encodeUpdateWeather(WeatherSpec weatherSpec) {
+        return encodeMarioWeatherMessage(weatherSpec);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerMisfit.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerMisfit.java
new file mode 100644
index 0000000..2eedbb1
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerMisfit.java
@@ -0,0 +1,129 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, Carsten Pfeiffer
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.pebble;
+
+import android.util.Pair;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.SimpleTimeZone;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventSendBytes;
+import nodomain.freeyourgadget.gadgetbridge.devices.pebble.PebbleMisfitSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.PebbleMisfitSample;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+class AppMessageHandlerMisfit extends AppMessageHandler {
+
+    private static final int KEY_SLEEPGOAL = 1;
+    private static final int KEY_STEP_ROGRESS = 2;
+    private static final int KEY_SLEEP_PROGRESS = 3;
+    private static final int KEY_VERSION = 4;
+    private static final int KEY_SYNC = 5;
+    private static final int KEY_INCOMING_DATA_BEGIN = 6;
+    private static final int KEY_INCOMING_DATA = 7;
+    private static final int KEY_INCOMING_DATA_END = 8;
+    private static final int KEY_SYNC_RESULT = 9;
+
+    private static final Logger LOG = LoggerFactory.getLogger(AppMessageHandlerMisfit.class);
+
+    AppMessageHandlerMisfit(UUID uuid, PebbleProtocol pebbleProtocol) {
+        super(uuid, pebbleProtocol);
+    }
+
+    @Override
+    public boolean isEnabled() {
+        return devicePrefs.getBoolean("pebble_sync_misfit", true);
+    }
+
+    @Override
+    public GBDeviceEvent[] handleMessage(ArrayList<Pair<Integer, Object>> pairs) {
+        GBDevice device = getDevice();
+        for (Pair<Integer, Object> pair : pairs) {
+            switch (pair.first) {
+                case KEY_INCOMING_DATA_BEGIN:
+                    LOG.info("incoming data start");
+                    break;
+                case KEY_INCOMING_DATA_END:
+                    GB.signalActivityDataFinish(getDevice());
+                    LOG.info("incoming data end");
+                    break;
+                case KEY_INCOMING_DATA:
+                    byte[] data = (byte[]) pair.second;
+                    ByteBuffer buf = ByteBuffer.wrap(data);
+                    buf.order(ByteOrder.LITTLE_ENDIAN);
+                    int timestamp = buf.getInt();
+                    int key = buf.getInt();
+                    int samples = (data.length - 8) / 2;
+                    if (samples <= 0) {
+                        break;
+                    }
+
+                    if (mPebbleProtocol.mFwMajor < 3) {
+                        timestamp -= SimpleTimeZone.getDefault().getOffset(timestamp * 1000L) / 1000;
+                    }
+                    Date startDate = new Date((long) timestamp * 1000L);
+                    Date endDate = new Date((long) (timestamp + samples * 60) * 1000L);
+                    LOG.info("got data from " + startDate + " to " + endDate);
+
+                    int totalSteps = 0;
+                    PebbleMisfitSample[] misfitSamples = new PebbleMisfitSample[samples];
+                    try (DBHandler db = GBApplication.acquireDB()) {
+                        PebbleMisfitSampleProvider sampleProvider = new PebbleMisfitSampleProvider(device, db.getDaoSession());
+                        Long userId = DBHelper.getUser(db.getDaoSession()).getId();
+                        Long deviceId = DBHelper.getDevice(getDevice(), db.getDaoSession()).getId();
+                        for (int i = 0; i < samples; i++) {
+                            short sample = buf.getShort();
+                            misfitSamples[i] = new PebbleMisfitSample(timestamp + i * 60, deviceId, userId, sample & 0xffff);
+                            misfitSamples[i].setProvider(sampleProvider);
+                            int steps = misfitSamples[i].getSteps();
+                            totalSteps += steps;
+                            LOG.info("got steps for sample " + i + " : " + steps + "(" + Integer.toHexString(sample & 0xffff) + ")");
+
+                        }
+                        LOG.info("total steps for above period: " + totalSteps);
+
+                        sampleProvider.addGBActivitySamples(misfitSamples);
+                    } catch (Exception e) {
+                        LOG.error("Error acquiring database", e);
+                        return null;
+                    }
+                    break;
+                default:
+                    LOG.info("unhandled key: " + pair.first);
+                    break;
+            }
+        }
+
+        // always ack
+        GBDeviceEventSendBytes sendBytesAck = new GBDeviceEventSendBytes();
+        sendBytesAck.encodedBytes = mPebbleProtocol.encodeApplicationMessageAck(mUUID, mPebbleProtocol.last_id);
+
+        return new GBDeviceEvent[]{sendBytesAck};
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerMorpheuz.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerMorpheuz.java
new file mode 100644
index 0000000..409a9af
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerMorpheuz.java
@@ -0,0 +1,185 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, Carsten Pfeiffer
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.pebble;
+
+import android.util.Pair;
+import android.widget.Toast;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Objects;
+import java.util.SimpleTimeZone;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventSendBytes;
+import nodomain.freeyourgadget.gadgetbridge.devices.pebble.PebbleMorpheuzSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.PebbleMorpheuzSample;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+class AppMessageHandlerMorpheuz extends AppMessageHandler {
+    private Integer keyPoint;
+    private Integer keyCtrl;
+    private Integer keyFrom;
+    private Integer keyTo;
+    private Integer keyBase;
+    private Integer keyVersion;
+    private Integer keyGoneoff;
+    private Integer keyTransmit;
+    private Integer keyAutoReset;
+    private Integer keySnoozes;
+    private Integer keyFault;
+
+    private static final int CTRL_TRANSMIT_DONE = 1;
+    private static final int CTRL_VERSION_DONE = 2;
+    private static final int CTRL_GONEOFF_DONE = 4;
+    private static final int CTRL_DO_NEXT = 8;
+    private static final int CTRL_SET_LAST_SENT = 16;
+    private static final int CTRL_LAZARUS = 32;
+    private static final int CTRL_SNOOZES_DONE = 64;
+
+    // data received from Morpheuz in native format
+    private int smartalarm_from = -1; // time in minutes relative from 0:00 for smart alarm (earliest)
+    private int smartalarm_to = -1;// time in minutes relative from 0:00 for smart alarm (latest)
+    private int recording_base_timestamp = -1; // timestamp for the first "point", all folowing are +10 minutes offset each
+    private int alarm_gone_off = -1; // time in minutes relative from 0:00 when alarm gone off
+
+    private static final Logger LOG = LoggerFactory.getLogger(AppMessageHandlerMorpheuz.class);
+
+    AppMessageHandlerMorpheuz(UUID uuid, PebbleProtocol pebbleProtocol) {
+        super(uuid, pebbleProtocol);
+
+        try {
+            JSONObject appKeys = getAppKeys();
+            keyPoint = appKeys.getInt("keyPoint");
+            keyCtrl = appKeys.getInt("keyCtrl");
+            keyFrom = appKeys.getInt("keyFrom");
+            keyTo = appKeys.getInt("keyTo");
+            keyBase = appKeys.getInt("keyBase");
+            keyVersion = appKeys.getInt("keyVersion");
+            keyGoneoff = appKeys.getInt("keyGoneoff");
+            keyTransmit = appKeys.getInt("keyTransmit");
+            keyAutoReset = appKeys.getInt("keyAutoReset");
+            keySnoozes = appKeys.getInt("keySnoozes");
+            keyFault = appKeys.getInt("keyFault");
+        } catch (JSONException e) {
+            GB.toast("There was an error accessing the morpheuz watchapp configuration.", Toast.LENGTH_LONG, GB.ERROR);
+        } catch (IOException ignore) {
+        }
+    }
+
+    private byte[] encodeMorpheuzMessage(int key, int value) {
+        ArrayList<Pair<Integer, Object>> pairs = new ArrayList<>();
+        pairs.add(new Pair<Integer, Object>(key, value));
+
+        return mPebbleProtocol.encodeApplicationMessagePush(PebbleProtocol.ENDPOINT_APPLICATIONMESSAGE, mUUID, pairs, null);
+    }
+
+    @Override
+    public boolean isEnabled() {
+        return devicePrefs.getBoolean("pebble_sync_morpheuz", true);
+    }
+
+    @Override
+    public GBDeviceEvent[] handleMessage(ArrayList<Pair<Integer, Object>> pairs) {
+        int ctrl_message = 0;
+
+        for (Pair<Integer, Object> pair : pairs) {
+            if (Objects.equals(pair.first, keyTransmit)) {
+                ctrl_message |= CTRL_TRANSMIT_DONE;
+                GB.signalActivityDataFinish(getDevice());
+            } else if (pair.first.equals(keyGoneoff)) {
+                alarm_gone_off = (int) pair.second;
+                LOG.info("got gone off: " + alarm_gone_off / 60 + ":" + alarm_gone_off % 60);
+                ctrl_message |= CTRL_DO_NEXT | CTRL_GONEOFF_DONE;
+            } else if (pair.first.equals(keyPoint)) {
+                if (recording_base_timestamp == -1) {
+                    // we have no base timestamp but received points, stop this
+                    ctrl_message = CTRL_VERSION_DONE | CTRL_GONEOFF_DONE | CTRL_TRANSMIT_DONE | CTRL_SET_LAST_SENT;
+                } else {
+                    int index = ((int) pair.second >> 16);
+                    int intensity = ((int) pair.second & 0xffff);
+                    LOG.info("got point:" + index + " " + intensity);
+                    if (index >= 0) {
+                        try (DBHandler db = GBApplication.acquireDB()) {
+                            Long userId = DBHelper.getUser(db.getDaoSession()).getId();
+                            Long deviceId = DBHelper.getDevice(getDevice(), db.getDaoSession()).getId();
+                            PebbleMorpheuzSampleProvider sampleProvider = new PebbleMorpheuzSampleProvider(getDevice(), db.getDaoSession());
+                            PebbleMorpheuzSample sample = new PebbleMorpheuzSample(recording_base_timestamp + index * 600, deviceId, userId, intensity);
+                            sample.setProvider(sampleProvider);
+                            sampleProvider.addGBActivitySample(sample);
+                        } catch (Exception e) {
+                            LOG.error("Error acquiring database", e);
+                        }
+                    }
+
+                    ctrl_message |= CTRL_SET_LAST_SENT | CTRL_DO_NEXT;
+                }
+            } else if (pair.first.equals(keyFrom)) {
+                smartalarm_from = (int) pair.second;
+                LOG.info("got from: " + smartalarm_from / 60 + ":" + smartalarm_from % 60);
+                ctrl_message |= CTRL_SET_LAST_SENT | CTRL_DO_NEXT;
+            } else if (pair.first.equals(keyTo)) {
+                smartalarm_to = (int) pair.second;
+                LOG.info("got to: " + smartalarm_to / 60 + ":" + smartalarm_to % 60);
+                ctrl_message |= CTRL_SET_LAST_SENT | CTRL_DO_NEXT;
+            } else if (pair.first.equals(keyVersion)) {
+                int version = (int) pair.second;
+                LOG.info("got version: " + ((float) version / 10.0f));
+                ctrl_message |= CTRL_VERSION_DONE;
+            } else if (pair.first.equals(keyBase)) {
+                recording_base_timestamp = (int) pair.second;
+                if (mPebbleProtocol.mFwMajor < 3) {
+                    recording_base_timestamp -= SimpleTimeZone.getDefault().getOffset(recording_base_timestamp * 1000L) / 1000;
+                }
+                LOG.info("got base: " + recording_base_timestamp);
+                ctrl_message |= CTRL_SET_LAST_SENT | CTRL_DO_NEXT;
+            } else if (pair.first.equals(keyAutoReset)) {
+                ctrl_message |= CTRL_SET_LAST_SENT | CTRL_DO_NEXT;
+            } else if (pair.first.equals(keySnoozes)) {
+                ctrl_message |= CTRL_SNOOZES_DONE | CTRL_DO_NEXT;
+            } else if (pair.first.equals(keyFault)) {
+                LOG.info("fault code: " + (int) pair.second);
+                ctrl_message |= CTRL_DO_NEXT;
+            } else {
+                LOG.info("unhandled key: " + pair.first);
+            }
+        }
+
+        // always ack
+        GBDeviceEventSendBytes sendBytesAck = new GBDeviceEventSendBytes();
+        sendBytesAck.encodedBytes = mPebbleProtocol.encodeApplicationMessageAck(mUUID, mPebbleProtocol.last_id);
+
+        // sometimes send control message
+        GBDeviceEventSendBytes sendBytesCtrl = null;
+        if (ctrl_message > 0) {
+            sendBytesCtrl = new GBDeviceEventSendBytes();
+            sendBytesCtrl.encodedBytes = encodeMorpheuzMessage(keyCtrl, ctrl_message);
+        }
+
+        // ctrl might be null, thats okay
+        return new GBDeviceEvent[]{sendBytesAck, sendBytesCtrl};
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerObsidian.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerObsidian.java
new file mode 100644
index 0000000..62bea84
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerObsidian.java
@@ -0,0 +1,176 @@
+/*  Copyright (C) 2017-2024 Andreas Shimokawa, Daniele Gobbetti, Johann C. Rode
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.pebble;
+
+import android.util.Pair;
+import android.widget.Toast;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventSendBytes;
+import nodomain.freeyourgadget.gadgetbridge.model.Weather;
+import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+class AppMessageHandlerObsidian extends AppMessageHandler {
+
+    /*
+      "appKeys": {
+    "CONFIG_WEATHER_REFRESH": 35,
+    "CONFIG_WEATHER_UNIT_LOCAL": 31,
+    "MSG_KEY_WEATHER_TEMP": 100,
+
+    "CONFIG_WEATHER_EXPIRATION": 36,
+    "MSG_KEY_FETCH_WEATHER": 102,
+    "MSG_KEY_WEATHER_ICON": 101,
+    "MSG_KEY_WEATHER_FAILED": 104,
+    "CONFIG_WEATHER_MODE_LOCAL": 30,
+    "CONFIG_WEATHER_APIKEY_LOCAL": 33,
+    "CONFIG_WEATHER_LOCAL": 28,
+    "CONFIG_COLOR_WEATHER": 29,
+    "CONFIG_WEATHER_LOCATION_LOCAL": 34,
+    "CONFIG_WEATHER_SOURCE_LOCAL": 32
+  }
+     */
+
+
+    private static final String ICON_01d = "a"; //night icons are just uppercase
+    private static final String ICON_02d = "b";
+    private static final String ICON_03d = "c";
+    private static final String ICON_04d = "d";
+    private static final String ICON_09d = "e";
+    private static final String ICON_10d = "f";
+    private static final String ICON_11d = "g";
+    private static final String ICON_13d = "h";
+    private static final String ICON_50d = "i";
+
+
+    AppMessageHandlerObsidian(UUID uuid, PebbleProtocol pebbleProtocol) {
+        super(uuid, pebbleProtocol);
+        messageKeys = new HashMap<>();
+        try {
+            JSONObject appKeys = getAppKeys();
+            Iterator<String> appKeysIterator = appKeys.keys();
+            while (appKeysIterator.hasNext()) {
+                String current = appKeysIterator.next();
+                switch (current) {
+                    case "CONFIG_WEATHER_REFRESH":
+                    case "CONFIG_WEATHER_UNIT_LOCAL":
+                    case "MSG_KEY_WEATHER_TEMP":
+                    case "MSG_KEY_WEATHER_ICON":
+                        messageKeys.put(current, appKeys.getInt(current));
+                        break;
+                }
+            }
+        } catch (JSONException e) {
+            GB.toast("There was an error accessing the timestyle watchface configuration.", Toast.LENGTH_LONG, GB.ERROR);
+        } catch (IOException ignore) {
+        }
+    }
+
+    private String getIconForConditionCode(int conditionCode, boolean isNight) {
+
+        int generalCondition = conditionCode / 100;
+        String iconToLoad;
+        // determine the correct icon
+        switch (generalCondition) {
+            case 2: //thunderstorm
+                iconToLoad = ICON_11d;
+                break;
+            case 3: //drizzle
+                iconToLoad = ICON_09d;
+                break;
+            case 5: //rain
+                if (conditionCode == 500) {
+                    iconToLoad = ICON_09d;
+                } else if (conditionCode < 505) {
+                    iconToLoad = ICON_10d;
+                } else if (conditionCode == 511) {
+                    iconToLoad = ICON_10d;
+                } else {
+                    iconToLoad = ICON_09d;
+                }
+                break;
+            case 6: //snow
+                if (conditionCode == 600 || conditionCode == 620) {
+                    iconToLoad = ICON_13d;
+                } else if (conditionCode > 610 && conditionCode < 620) {
+                    iconToLoad = ICON_13d;
+                } else {
+                    iconToLoad = ICON_13d;
+                }
+                break;
+            case 7: // fog, dust, etc
+                iconToLoad = ICON_03d;
+                break;
+            case 8: // clouds
+                if (conditionCode == 800) {
+                    iconToLoad = ICON_01d;
+                } else if (conditionCode < 803) {
+                    iconToLoad = ICON_02d;
+                } else {
+                    iconToLoad = ICON_04d;
+                }
+                break;
+            default:
+                iconToLoad = ICON_02d;
+                break;
+        }
+
+        return (!isNight) ? iconToLoad : iconToLoad.toUpperCase();
+    }
+
+    private byte[] encodeObsidianWeather(WeatherSpec weatherSpec) {
+
+        if (weatherSpec == null) {
+            return null;
+        }
+
+        ArrayList<Pair<Integer, Object>> pairs = new ArrayList<>();
+        boolean isNight = false;   //TODO: use the night icons when night
+        pairs.add(new Pair<>(messageKeys.get("CONFIG_WEATHER_REFRESH"), (Object) 60));
+        pairs.add(new Pair<>(messageKeys.get("CONFIG_WEATHER_UNIT_LOCAL"), (Object) 1)); //celsius
+        pairs.add(new Pair<>(messageKeys.get("MSG_KEY_WEATHER_ICON"), (Object) getIconForConditionCode(weatherSpec.currentConditionCode, isNight))); //celsius
+        pairs.add(new Pair<>(messageKeys.get("MSG_KEY_WEATHER_TEMP"), (Object) (weatherSpec.currentTemp - 273)));
+
+        return mPebbleProtocol.encodeApplicationMessagePush(PebbleProtocol.ENDPOINT_APPLICATIONMESSAGE, mUUID, pairs, null);
+    }
+
+    @Override
+    public GBDeviceEvent[] onAppStart() {
+        WeatherSpec weatherSpec = Weather.getInstance().getWeatherSpec();
+        if (weatherSpec == null) {
+            return new GBDeviceEvent[]{null};
+        }
+        GBDeviceEventSendBytes sendBytes = new GBDeviceEventSendBytes();
+        sendBytes.encodedBytes = encodeObsidianWeather(weatherSpec);
+        return new GBDeviceEvent[]{sendBytes};
+    }
+
+    @Override
+    public byte[] encodeUpdateWeather(WeatherSpec weatherSpec) {
+        return encodeObsidianWeather(weatherSpec);
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerPebStyle.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerPebStyle.java
new file mode 100644
index 0000000..29fcdd6
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerPebStyle.java
@@ -0,0 +1,137 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, Daniele Gobbetti, Uwe Hermann
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.pebble;
+
+import android.util.Pair;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
+import nodomain.freeyourgadget.gadgetbridge.devices.pebble.PebbleColor;
+import nodomain.freeyourgadget.gadgetbridge.model.Weather;
+import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
+
+class AppMessageHandlerPebStyle extends AppMessageHandler {
+    public static final int KEY_AMPM_TEXT = 21;
+    public static final int KEY_BLUETOOTH_ALERT = 2;
+    public static final int KEY_BLUETOOTH_ICON = 20;
+    public static final int KEY_CITY_NAME = 9;
+    public static final int KEY_COLOR_SELECTION = 15;
+    public static final int KEY_JSREADY = 6;
+    public static final int KEY_JS_TIMEZONE_OFFSET = 13;
+    public static final int KEY_LOCATION_SERVICE = 7;
+    public static final int KEY_MAIN_BG_COLOR = 16;
+    public static final int KEY_MAIN_CLOCK = 0;
+    public static final int KEY_MAIN_COLOR = 17;
+    public static final int KEY_SECONDARY_INFO_TYPE = 10;
+    public static final int KEY_SECOND_HAND = 1;
+    public static final int KEY_SIDEBAR_BG_COLOR = 18;
+    public static final int KEY_SIDEBAR_COLOR = 19;
+    public static final int KEY_SIDEBAR_LOCATION = 14;
+    public static final int KEY_TEMPERATURE_FORMAT = 8;
+    public static final int KEY_TIMEZONE_NAME = 11;
+    public static final int KEY_TIME_SEPARATOR = 12;
+    public static final int KEY_WEATHER_CODE = 3;
+    public static final int KEY_WEATHER_INTERVAL = 5;
+    public static final int KEY_WEATHER_TEMP = 4;
+
+
+    private static final Logger LOG = LoggerFactory.getLogger(AppMessageHandlerPebStyle.class);
+
+    public AppMessageHandlerPebStyle(UUID uuid, PebbleProtocol pebbleProtocol) {
+        super(uuid, pebbleProtocol);
+    }
+
+    private byte[] encodeAck() {
+        byte[] ackMessage = mPebbleProtocol.encodeApplicationMessageAck(mUUID, mPebbleProtocol.last_id);
+        ByteBuffer buf = ByteBuffer.allocate(ackMessage.length);
+        buf.put(ackMessage);
+        return buf.array();
+    }
+
+    private byte[] encodePebStyleConfig() {
+        ArrayList<Pair<Integer, Object>> pairs = new ArrayList<>();
+        //settings that give good legibility on pebble time
+        pairs.add(new Pair<>(KEY_SECOND_HAND, (Object) 0)); //1 enabled
+        pairs.add(new Pair<>(KEY_BLUETOOTH_ALERT, (Object) 0)); //1 silent, 2 weak, up to 5
+        pairs.add(new Pair<>(KEY_TEMPERATURE_FORMAT, (Object) 1)); //0 fahrenheit
+        pairs.add(new Pair<>(KEY_LOCATION_SERVICE, (Object) 2)); //0 auto, 1 manual
+        pairs.add(new Pair<>(KEY_SIDEBAR_LOCATION, (Object) 1)); //0 right
+        pairs.add(new Pair<>(KEY_COLOR_SELECTION, (Object) 1)); //1 custom
+        pairs.add(new Pair<>(KEY_MAIN_COLOR, (Object) PebbleColor.Black));
+        pairs.add(new Pair<>(KEY_MAIN_BG_COLOR, (Object) PebbleColor.White));
+        pairs.add(new Pair<>(KEY_SIDEBAR_BG_COLOR, (Object) PebbleColor.MediumSpringGreen));
+
+        //DIGITAL settings
+       /*
+        pairs.add(new Pair<>(KEY_MAIN_CLOCK, (Object) 1)); //0 analog
+        pairs.add(new Pair<>(KEY_SECONDARY_INFO_TYPE, (Object) 3)); //1 time, 2 location
+        */
+        //ANALOG + DIGITAL settings
+        pairs.add(new Pair<>(KEY_MAIN_CLOCK, (Object) 0)); //0 analog, 1 digital
+        pairs.add(new Pair<>(KEY_SECONDARY_INFO_TYPE, (Object) 1)); //1 time, 2 location
+
+
+        //WEATHER
+        WeatherSpec weather = Weather.getInstance().getWeatherSpec();
+        if (weather != null) {
+            //comment the same key in the general section above!
+            pairs.add(new Pair<>(KEY_LOCATION_SERVICE, (Object) 0)); //0 auto, 1 manual
+            pairs.add(new Pair<>(KEY_WEATHER_CODE, (Object) Weather.mapToYahooCondition(weather.currentConditionCode)));
+            pairs.add(new Pair<>(KEY_WEATHER_TEMP, (Object) (weather.currentTemp - 273)));
+        }
+
+        byte[] testMessage = mPebbleProtocol.encodeApplicationMessagePush(PebbleProtocol.ENDPOINT_APPLICATIONMESSAGE, mUUID, pairs, null);
+
+
+        ByteBuffer buf = ByteBuffer.allocate(testMessage.length);
+
+        // encode ack and put in front of push message (hack for acknowledging the last message)
+        buf.put(testMessage);
+
+        return buf.array();
+    }
+
+
+    @Override
+    public GBDeviceEvent[] handleMessage(ArrayList<Pair<Integer, Object>> pairs) {
+        return null;
+        /*
+        GBDeviceEventSendBytes sendBytes = new GBDeviceEventSendBytes();
+        ByteBuffer buf = ByteBuffer.allocate(encodeAck().length + encodePebStyleConfig().length);
+        buf.put(encodeAck());
+        buf.put(encodePebStyleConfig());
+        sendBytes.encodedBytes = buf.array();
+        return new GBDeviceEvent[]{sendBytes};
+        */
+    }
+
+    @Override
+    public GBDeviceEvent[] onAppStart() {
+        return null;
+        /*
+        GBDeviceEventSendBytes sendBytes = new GBDeviceEventSendBytes();
+        sendBytes.encodedBytes = encodePebStyleConfig();
+        return new GBDeviceEvent[]{sendBytes};
+        */
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerRealWeather.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerRealWeather.java
new file mode 100644
index 0000000..ccb66ec
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerRealWeather.java
@@ -0,0 +1,146 @@
+/*  Copyright (C) 2018-2024 Carsten Pfeiffer, Johann C. Rode
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.pebble;
+
+import android.util.Pair;
+import android.widget.Toast;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Locale;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventSendBytes;
+import nodomain.freeyourgadget.gadgetbridge.model.Weather;
+import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+class AppMessageHandlerRealWeather extends AppMessageHandler {
+    private static int CLEAR_DAY = 0;
+    private static int CLEAR_NIGHT = 1;
+    private static int WINDY = 2;
+    private static int COLD = 3;
+    private static int PARTLY_CLOUDY_DAY = 4;
+    private static int PARTLY_CLOUDY_NIGHT = 5;
+    private static int HAZE = 6;
+    private static int CLOUD = 7;
+    private static int RAIN = 8;
+    private static int SNOW = 9;
+    private static int HAIL = 10;
+    private static int CLOUDY = 11;
+    private static int STORM = 12;
+    private static int NA = 13;
+
+    private Integer KEY_WEATHER_ICON;
+    private Integer KEY_WEATHER_TEMP;
+
+    AppMessageHandlerRealWeather(UUID uuid, PebbleProtocol pebbleProtocol) {
+        super(uuid, pebbleProtocol);
+
+        try {
+            JSONObject appKeys = getAppKeys();
+            KEY_WEATHER_TEMP = appKeys.getInt("temperature");
+            KEY_WEATHER_ICON = appKeys.getInt("icon");
+        } catch (JSONException e) {
+            GB.toast("There was an error accessing the RealWeather watchface configuration.", Toast.LENGTH_LONG, GB.ERROR);
+        } catch (IOException ignore) {
+        }
+    }
+
+    private int getIconForConditionCode(int conditionCode, boolean isNight) {
+        if (conditionCode == 800 || conditionCode == 951) {
+            return isNight ? CLEAR_NIGHT : CLEAR_DAY;
+        } else if (conditionCode == 801 || conditionCode == 802) {
+            return isNight ? PARTLY_CLOUDY_NIGHT : PARTLY_CLOUDY_DAY;
+        } else if (conditionCode >= 300 && conditionCode < 313) {
+            return RAIN;
+        } else if (conditionCode >= 313 && conditionCode < 400) {
+            return RAIN;
+        } else if (conditionCode >= 500 && conditionCode < 600) {
+            return RAIN;
+        } else if (conditionCode >= 700 && conditionCode < 732) {
+            return CLOUDY;
+        } else if (conditionCode == 741 || conditionCode == 751 || conditionCode == 761 || conditionCode == 762 ) {
+            return HAZE;
+        } else if (conditionCode == 771) {
+            return WINDY;
+        } else if (conditionCode == 781) {
+            return STORM;
+        } else if (conditionCode >= 200 && conditionCode < 300) {
+            return STORM;
+        } else if (conditionCode == 600 || conditionCode == 601 || conditionCode == 602 ) {
+            return SNOW;
+        } else if (conditionCode == 611 || conditionCode == 612) {
+            return HAIL;
+        } else if (conditionCode == 615 || conditionCode == 616 || conditionCode == 620 || conditionCode == 621 || conditionCode == 622) {
+            return SNOW;
+        } else if (conditionCode == 906) {
+            return SNOW;
+        } else if (conditionCode == 803 || conditionCode == 804) {
+            return CLOUD;
+        } else if (conditionCode >= 907 && conditionCode < 957) {
+            return STORM;
+        } else if (conditionCode == 905) {
+            return STORM;
+        } else if (conditionCode == 900) {
+            return STORM;
+        } else if (conditionCode == 901 || conditionCode == 902 || conditionCode == 962) {
+            return STORM;
+        } else if (conditionCode == 903) {
+            return COLD;
+        }
+        return 0;
+    }
+
+    private byte[] encodeRealWeatherMessage(WeatherSpec weatherSpec) {
+        if (weatherSpec == null) {
+            return null;
+        }
+        boolean isNight = false; // TODO
+        ArrayList<Pair<Integer, Object>> pairs = new ArrayList<>(2);
+        pairs.add(new Pair<>(KEY_WEATHER_TEMP, (Object) (String.format(Locale.ENGLISH, "%.0f掳", weatherSpec.currentTemp - 273.15))));
+        pairs.add(new Pair<>(KEY_WEATHER_ICON, (Object) (getIconForConditionCode(weatherSpec.currentConditionCode, isNight))));
+        byte[] weatherMessage = mPebbleProtocol.encodeApplicationMessagePush(PebbleProtocol.ENDPOINT_APPLICATIONMESSAGE, mUUID, pairs, null);
+
+        ByteBuffer buf = ByteBuffer.allocate(weatherMessage.length);
+
+        buf.put(weatherMessage);
+
+        return buf.array();
+    }
+
+    @Override
+    public GBDeviceEvent[] onAppStart() {
+        WeatherSpec weatherSpec = Weather.getInstance().getWeatherSpec();
+        if (weatherSpec == null) {
+            return new GBDeviceEvent[]{null};
+        }
+        GBDeviceEventSendBytes sendBytes = new GBDeviceEventSendBytes();
+        sendBytes.encodedBytes = encodeRealWeatherMessage(weatherSpec);
+        return new GBDeviceEvent[]{sendBytes};
+    }
+
+    @Override
+    public byte[] encodeUpdateWeather(WeatherSpec weatherSpec) {
+        return encodeRealWeatherMessage(weatherSpec);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerSimplyLight.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerSimplyLight.java
new file mode 100644
index 0000000..4ecfbba
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerSimplyLight.java
@@ -0,0 +1,139 @@
+/*  Copyright (C) 2018-2024 Carsten Pfeiffer, Sergio Lopez
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.pebble;
+
+import android.util.Pair;
+import android.widget.Toast;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventSendBytes;
+import nodomain.freeyourgadget.gadgetbridge.model.Weather;
+import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+class AppMessageHandlerSimplyLight extends AppMessageHandler {
+    private static final int CLEAR = 0;
+    private static final int CLOUDY = 1;
+    private static final int FOG = 2;
+    private static final int LIGHT_RAIN = 3;
+    private static final int RAIN = 4;
+    private static final int THUNDERSTORM = 5;
+    private static final int SNOW = 6;
+    private static final int HAIL = 7;
+    private static final int WIND = 8;
+    private static final int EXTREME_WIND = 9;
+    private static final int TORNADO = 10;
+    private static final int HURRICANE = 11;
+    private static final int EXTREME_COLD = 12;
+    private static final int EXTREME_HEAT = 13;
+    private static final int SNOW_THUNDERSTORM = 14;
+
+    private Integer KEY_TEMPERATURE;
+    private Integer KEY_CONDITION;
+    private Integer KEY_ERR;
+
+    AppMessageHandlerSimplyLight(UUID uuid, PebbleProtocol pebbleProtocol) {
+        super(uuid, pebbleProtocol);
+
+        try {
+            JSONObject appKeys = getAppKeys();
+            KEY_TEMPERATURE = appKeys.getInt("temperature");
+            KEY_CONDITION = appKeys.getInt("condition");
+            KEY_ERR = appKeys.getInt("err");
+        } catch (JSONException e) {
+            GB.toast("There was an error accessing the Simply Light watchface configuration.", Toast.LENGTH_LONG, GB.ERROR);
+        } catch (IOException ignore) {
+        }
+    }
+
+
+private int getConditionForConditionCode(int conditionCode) {
+        if (conditionCode == 800 || conditionCode == 951) {
+            return CLEAR;
+        } else if (conditionCode > 800 && conditionCode < 900) {
+            return CLOUDY;
+        } else if (conditionCode >= 700 && conditionCode < 800) {
+            return FOG;
+        } else if (conditionCode >= 300 && conditionCode < 400) {
+            return LIGHT_RAIN;
+        } else if (conditionCode >= 500 && conditionCode < 600) {
+            return RAIN;
+        } else if (conditionCode >= 200 && conditionCode < 300) {
+            return THUNDERSTORM;
+        } else if (conditionCode >= 600 && conditionCode < 700) {
+            return SNOW;
+        } else if (conditionCode == 906) {
+            return HAIL;
+        } else if (conditionCode >= 907 && conditionCode < 957) {
+            return WIND;
+        } else if (conditionCode == 905) {
+            return EXTREME_WIND;
+        } else if (conditionCode == 900) {
+            return TORNADO;
+        } else if (conditionCode == 901 || conditionCode == 902 || conditionCode == 962) {
+            return HURRICANE;
+        } else if (conditionCode == 903) {
+            return EXTREME_COLD;
+        } else if (conditionCode == 904) {
+            return EXTREME_HEAT;
+        }
+
+        return 0;
+    }
+
+    private byte[] encodeSimplyLightWeatherMessage(WeatherSpec weatherSpec) {
+        if (weatherSpec == null) {
+            return null;
+        }
+
+        ArrayList<Pair<Integer, Object>> pairs = new ArrayList<>(2);
+        pairs.add(new Pair<>(KEY_TEMPERATURE, (Object) (weatherSpec.currentTemp - 273)));
+        pairs.add(new Pair<>(KEY_CONDITION, (Object) (getConditionForConditionCode(weatherSpec.currentConditionCode))));
+        pairs.add(new Pair<>(KEY_ERR, (Object) 0));
+        byte[] weatherMessage = mPebbleProtocol.encodeApplicationMessagePush(PebbleProtocol.ENDPOINT_APPLICATIONMESSAGE, mUUID, pairs, null);
+
+        ByteBuffer buf = ByteBuffer.allocate(weatherMessage.length);
+
+        buf.put(weatherMessage);
+
+        return buf.array();
+    }
+
+    @Override
+    public GBDeviceEvent[] onAppStart() {
+        WeatherSpec weatherSpec = Weather.getInstance().getWeatherSpec();
+        if (weatherSpec == null) {
+            return new GBDeviceEvent[]{null};
+        }
+        GBDeviceEventSendBytes sendBytes = new GBDeviceEventSendBytes();
+        sendBytes.encodedBytes = encodeSimplyLightWeatherMessage(weatherSpec);
+        return new GBDeviceEvent[]{sendBytes};
+    }
+
+    @Override
+    public byte[] encodeUpdateWeather(WeatherSpec weatherSpec) {
+        return encodeSimplyLightWeatherMessage(weatherSpec);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerSquare.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerSquare.java
new file mode 100644
index 0000000..80b22d7
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerSquare.java
@@ -0,0 +1,94 @@
+/*  Copyright (C) 2017-2024 Andreas Shimokawa, Daniele Gobbetti
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.pebble;
+
+import android.util.Pair;
+import android.widget.Toast;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventSendBytes;
+import nodomain.freeyourgadget.gadgetbridge.model.Weather;
+import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+class AppMessageHandlerSquare extends AppMessageHandler {
+    private int CfgKeyCelsiusTemperature;
+    private int CfgKeyConditions;
+    private int CfgKeyWeatherMode;
+    private int CfgKeyUseCelsius;
+    private int CfgKeyWeatherLocation;
+
+    AppMessageHandlerSquare(UUID uuid, PebbleProtocol pebbleProtocol) {
+        super(uuid, pebbleProtocol);
+
+        try {
+            JSONObject appKeys = getAppKeys();
+            CfgKeyCelsiusTemperature = appKeys.getInt("CfgKeyCelsiusTemperature");
+            CfgKeyConditions = appKeys.getInt("CfgKeyConditions");
+            CfgKeyWeatherMode = appKeys.getInt("CfgKeyWeatherMode");
+            CfgKeyUseCelsius = appKeys.getInt("CfgKeyUseCelsius");
+            CfgKeyWeatherLocation = appKeys.getInt("CfgKeyWeatherLocation");
+        } catch (JSONException e) {
+            GB.toast("There was an error accessing the Square watchface configuration.", Toast.LENGTH_LONG, GB.ERROR);
+        } catch (IOException ignore) {
+        }
+    }
+
+    private byte[] encodeSquareWeatherMessage(WeatherSpec weatherSpec) {
+        if (weatherSpec == null) {
+            return null;
+        }
+
+        ArrayList<Pair<Integer, Object>> pairs = new ArrayList<>(2);
+        pairs.add(new Pair<>(CfgKeyWeatherMode, (Object) 1));
+        pairs.add(new Pair<>(CfgKeyConditions, (Object) weatherSpec.currentCondition));
+        pairs.add(new Pair<>(CfgKeyUseCelsius, (Object) 1));
+        pairs.add(new Pair<>(CfgKeyCelsiusTemperature, (Object) (weatherSpec.currentTemp - 273)));
+        pairs.add(new Pair<>(CfgKeyWeatherLocation, (Object) (weatherSpec.location)));
+        byte[] weatherMessage = mPebbleProtocol.encodeApplicationMessagePush(PebbleProtocol.ENDPOINT_APPLICATIONMESSAGE, mUUID, pairs, null);
+
+        ByteBuffer buf = ByteBuffer.allocate(weatherMessage.length);
+
+        buf.put(weatherMessage);
+
+        return buf.array();
+    }
+
+    @Override
+    public GBDeviceEvent[] onAppStart() {
+        WeatherSpec weatherSpec = Weather.getInstance().getWeatherSpec();
+        if (weatherSpec == null) {
+            return new GBDeviceEvent[]{null};
+        }
+        GBDeviceEventSendBytes sendBytes = new GBDeviceEventSendBytes();
+        sendBytes.encodedBytes = encodeSquareWeatherMessage(weatherSpec);
+        return new GBDeviceEvent[]{sendBytes};
+    }
+
+    @Override
+    public byte[] encodeUpdateWeather(WeatherSpec weatherSpec) {
+        return encodeSquareWeatherMessage(weatherSpec);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerTimeStylePebble.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerTimeStylePebble.java
new file mode 100644
index 0000000..26612e3
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerTimeStylePebble.java
@@ -0,0 +1,171 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, Daniele Gobbetti, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.pebble;
+
+import android.util.Pair;
+import android.widget.Toast;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventSendBytes;
+import nodomain.freeyourgadget.gadgetbridge.model.Weather;
+import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+class AppMessageHandlerTimeStylePebble extends AppMessageHandler {
+
+    private static final int ICON_CLEAR_DAY = 0;
+    private static final int ICON_CLEAR_NIGHT = 1;
+    private static final int ICON_CLOUDY_DAY = 2;
+    private static final int ICON_HEAVY_RAIN = 3;
+    private static final int ICON_HEAVY_SNOW = 4;
+    private static final int ICON_LIGHT_RAIN = 5;
+    private static final int ICON_LIGHT_SNOW = 6;
+    private static final int ICON_PARTLY_CLOUDY_NIGHT = 7;
+    private static final int ICON_PARTLY_CLOUDY = 8;
+    private static final int ICON_RAINING_AND_SNOWING = 9;
+    private static final int ICON_THUNDERSTORM = 10;
+    private static final int ICON_WEATHER_GENERIC = 11;
+
+    AppMessageHandlerTimeStylePebble(UUID uuid, PebbleProtocol pebbleProtocol) {
+        super(uuid, pebbleProtocol);
+        messageKeys = new HashMap<>();
+        try {
+            JSONObject appKeys = getAppKeys();
+            Iterator<String> appKeysIterator = appKeys.keys();
+            while (appKeysIterator.hasNext()) {
+                String current = appKeysIterator.next();
+                switch (current) {
+                    case "WeatherCondition":
+                    case "WeatherForecastCondition":
+                    case "WeatherForecastHighTemp":
+                    case "WeatherForecastLowTemp":
+                    case "WeatherTemperature":
+                    case "SettingUseMetric":
+                    case "WeatherUseNightIcon":
+                        messageKeys.put(current, appKeys.getInt(current));
+                        break;
+                }
+            }
+        } catch (JSONException e) {
+            GB.toast("There was an error accessing the timestyle watchface configuration.", Toast.LENGTH_LONG, GB.ERROR);
+        } catch (IOException ignore) {
+        }
+    }
+
+    /*
+     * converted to JAVA from original JS
+     */
+    private int getIconForConditionCode(int conditionCode, boolean isNight) {
+        int generalCondition = conditionCode / 100;
+        int iconToLoad;
+        // determine the correct icon
+        switch (generalCondition) {
+            case 2: //thunderstorm
+                iconToLoad = ICON_THUNDERSTORM;
+                break;
+            case 3: //drizzle
+                iconToLoad = ICON_LIGHT_RAIN;
+                break;
+            case 5: //rain
+                if (conditionCode == 500) {
+                    iconToLoad = ICON_LIGHT_RAIN;
+                } else if (conditionCode < 505) {
+                    iconToLoad = ICON_HEAVY_RAIN;
+                } else if (conditionCode == 511) {
+                    iconToLoad = ICON_RAINING_AND_SNOWING;
+                } else {
+                    iconToLoad = ICON_LIGHT_RAIN;
+                }
+                break;
+            case 6: //snow
+                if (conditionCode == 600 || conditionCode == 620) {
+                    iconToLoad = ICON_LIGHT_SNOW;
+                } else if (conditionCode > 610 && conditionCode < 620) {
+                    iconToLoad = ICON_RAINING_AND_SNOWING;
+                } else {
+                    iconToLoad = ICON_HEAVY_SNOW;
+                }
+                break;
+            case 7: // fog, dust, etc
+                iconToLoad = ICON_CLOUDY_DAY;
+                break;
+            case 8: // clouds
+                if (conditionCode == 800) {
+                    iconToLoad = (!isNight) ? ICON_CLEAR_DAY : ICON_CLEAR_NIGHT;
+                } else if (conditionCode < 803) {
+                    iconToLoad = (!isNight) ? ICON_PARTLY_CLOUDY : ICON_PARTLY_CLOUDY_NIGHT;
+                } else {
+                    iconToLoad = ICON_CLOUDY_DAY;
+                }
+                break;
+            default:
+                iconToLoad = ICON_WEATHER_GENERIC;
+                break;
+        }
+
+        return iconToLoad;
+    }
+
+    private byte[] encodeTimeStylePebbleWeather(WeatherSpec weatherSpec) {
+
+        if (weatherSpec == null) {
+            return null;
+        }
+
+        ArrayList<Pair<Integer, Object>> pairs = new ArrayList<>();
+        boolean isNight = false;   //TODO: use the night icons when night
+        pairs.add(new Pair<>(messageKeys.get("SettingUseMetric"), (Object) 1)); //celsius
+        pairs.add(new Pair<>(messageKeys.get("WeatherUseNightIcon"), (Object) (isNight ? 1 : 0)));
+        pairs.add(new Pair<>(messageKeys.get("WeatherTemperature"), (Object) (weatherSpec.currentTemp - 273)));
+        pairs.add(new Pair<>(messageKeys.get("WeatherCondition"), (Object) (getIconForConditionCode(weatherSpec.currentConditionCode, isNight))));
+
+        if (weatherSpec.forecasts.size() > 0) {
+            WeatherSpec.Daily tomorrow = weatherSpec.forecasts.get(0);
+            pairs.add(new Pair<>(messageKeys.get("WeatherForecastCondition"), (Object) (getIconForConditionCode(tomorrow.conditionCode, isNight))));
+        }
+
+        pairs.add(new Pair<>(messageKeys.get("WeatherForecastHighTemp"), (Object) (weatherSpec.todayMaxTemp - 273)));
+        pairs.add(new Pair<>(messageKeys.get("WeatherForecastLowTemp"), (Object) (weatherSpec.todayMinTemp - 273)));
+
+        return mPebbleProtocol.encodeApplicationMessagePush(PebbleProtocol.ENDPOINT_APPLICATIONMESSAGE, mUUID, pairs, null);
+    }
+
+    @Override
+    public GBDeviceEvent[] onAppStart() {
+        WeatherSpec weatherSpec = Weather.getInstance().getWeatherSpec();
+        if (weatherSpec == null) {
+            return new GBDeviceEvent[]{null};
+        }
+        GBDeviceEventSendBytes sendBytes = new GBDeviceEventSendBytes();
+        sendBytes.encodedBytes = encodeTimeStylePebbleWeather(weatherSpec);
+        return new GBDeviceEvent[]{sendBytes};
+    }
+
+    @Override
+    public byte[] encodeUpdateWeather(WeatherSpec weatherSpec) {
+        return encodeTimeStylePebbleWeather(weatherSpec);
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerTrekVolle.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerTrekVolle.java
new file mode 100644
index 0000000..c7b0ca8
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerTrekVolle.java
@@ -0,0 +1,112 @@
+/*  Copyright (C) 2017-2024 Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.pebble;
+
+import android.util.Pair;
+import android.widget.Toast;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventSendBytes;
+import nodomain.freeyourgadget.gadgetbridge.model.Weather;
+import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+class AppMessageHandlerTrekVolle extends AppMessageHandler {
+    private Integer MESSAGE_KEY_WEATHER_TEMPERATURE;
+    private Integer MESSAGE_KEY_WEATHER_CONDITIONS;
+    private Integer MESSAGE_KEY_WEATHER_ICON;
+    private Integer MESSAGE_KEY_WEATHER_TEMPERATURE_MIN;
+    private Integer MESSAGE_KEY_WEATHER_TEMPERATURE_MAX;
+    private Integer MESSAGE_KEY_WEATHER_LOCATION;
+
+    AppMessageHandlerTrekVolle(UUID uuid, PebbleProtocol pebbleProtocol) {
+        super(uuid, pebbleProtocol);
+
+        try {
+            JSONObject appKeys = getAppKeys();
+            MESSAGE_KEY_WEATHER_TEMPERATURE = appKeys.getInt("WEATHER_TEMPERATURE");
+            MESSAGE_KEY_WEATHER_CONDITIONS = appKeys.getInt("WEATHER_CONDITIONS");
+            MESSAGE_KEY_WEATHER_ICON = appKeys.getInt("WEATHER_ICON");
+            MESSAGE_KEY_WEATHER_TEMPERATURE_MIN = appKeys.getInt("WEATHER_TEMPERATURE_MIN");
+            MESSAGE_KEY_WEATHER_TEMPERATURE_MAX = appKeys.getInt("WEATHER_TEMPERATURE_MAX");
+            MESSAGE_KEY_WEATHER_LOCATION = appKeys.getInt("WEATHER_LOCATION");
+        } catch (JSONException e) {
+            GB.toast("There was an error accessing the TrekVolle watchface configuration.", Toast.LENGTH_LONG, GB.ERROR);
+        } catch (IOException ignore) {
+        }
+    }
+
+    private int getIconForConditionCode(int conditionCode, boolean isNight) {
+        /*
+        case 1:  return RESOURCE_ID_IMAGE_WEATHER_CLEARNIGHT;
+        case 2:  return RESOURCE_ID_IMAGE_WEATHER_CLEAR;
+        case 3:  return RESOURCE_ID_IMAGE_WEATHER_CLOUDYNIGHT;
+        case 4:  return RESOURCE_ID_IMAGE_WEATHER_CLOUDY;
+        case 5:  return RESOURCE_ID_IMAGE_WEATHER_CLOUDS;
+        case 6:  return RESOURCE_ID_IMAGE_WEATHER_THICKCLOUDS;
+        case 7:  return RESOURCE_ID_IMAGE_WEATHER_RAIN;
+        case 8:  return RESOURCE_ID_IMAGE_WEATHER_RAINYNIGHT;
+        case 9:  return RESOURCE_ID_IMAGE_WEATHER_RAINY;
+        case 10: return RESOURCE_ID_IMAGE_WEATHER_LIGHTNING;
+        case 11: return RESOURCE_ID_IMAGE_WEATHER_SNOW;
+        case 12: return RESOURCE_ID_IMAGE_WEATHER_MIST;
+        */
+        return 2;
+    }
+
+    private byte[] encodeTrekVolleWeather(WeatherSpec weatherSpec) {
+
+        if (weatherSpec == null) {
+            return null;
+        }
+
+        boolean isNight = false; // FIXME
+        ArrayList<Pair<Integer, Object>> pairs = new ArrayList<>();
+        pairs.add(new Pair<>(MESSAGE_KEY_WEATHER_TEMPERATURE, (Object) (weatherSpec.currentTemp - 273)));
+        pairs.add(new Pair<>(MESSAGE_KEY_WEATHER_CONDITIONS, (Object) (weatherSpec.currentCondition)));
+        pairs.add(new Pair<>(MESSAGE_KEY_WEATHER_ICON, (Object) (getIconForConditionCode(weatherSpec.currentConditionCode, isNight))));
+        pairs.add(new Pair<>(MESSAGE_KEY_WEATHER_TEMPERATURE_MAX, (Object) (weatherSpec.todayMaxTemp - 273)));
+        pairs.add(new Pair<>(MESSAGE_KEY_WEATHER_TEMPERATURE_MIN, (Object) (weatherSpec.todayMinTemp - 273)));
+        pairs.add(new Pair<>(MESSAGE_KEY_WEATHER_LOCATION, (Object) weatherSpec.location));
+
+
+        return mPebbleProtocol.encodeApplicationMessagePush(PebbleProtocol.ENDPOINT_APPLICATIONMESSAGE, mUUID, pairs, null);
+    }
+
+    @Override
+    public GBDeviceEvent[] onAppStart() {
+        WeatherSpec weatherSpec = Weather.getInstance().getWeatherSpec();
+        if (weatherSpec == null) {
+            return new GBDeviceEvent[]{null};
+        }
+        GBDeviceEventSendBytes sendBytes = new GBDeviceEventSendBytes();
+        sendBytes.encodedBytes = encodeTrekVolleWeather(weatherSpec);
+        return new GBDeviceEvent[]{sendBytes};
+    }
+
+    @Override
+    public byte[] encodeUpdateWeather(WeatherSpec weatherSpec) {
+        return encodeTrekVolleWeather(weatherSpec);
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerYWeather.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerYWeather.java
new file mode 100644
index 0000000..cae3f27
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerYWeather.java
@@ -0,0 +1,244 @@
+/*  Copyright (C) 2018-2024 Carsten Pfeiffer, Johann C. Rode, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.pebble;
+
+import android.util.Pair;
+import android.widget.Toast;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Locale;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventSendBytes;
+import nodomain.freeyourgadget.gadgetbridge.model.Weather;
+import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+class AppMessageHandlerYWeather extends AppMessageHandler {
+    private static int CLEAR_DAY = 0;
+    private static int CLEAR_NIGHT = 1;
+    private static int WINDY = 2;
+    private static int COLD = 3;
+    private static int HOT = 4;
+    private static int PARTLY_CLOUDY_DAY = 5;
+    private static int PARTLY_CLOUDY_NIGHT = 6;
+    private static int FOG = 7;
+    private static int RAIN = 8;
+    private static int SNOW = 9;
+    private static int SLEET = 10;
+    private static int SNOW_SLEET = 11;
+    private static int RAIN_SLEET = 12;
+    private static int RAIN_SNOW = 13;
+    private static int CLOUDY = 14;
+    private static int STORM = 15;
+    private static int NA = 16;
+    private static int DRIZZLE = 17;
+
+    private Integer KEY_WEATHER_ICON;
+    private Integer KEY_WEATHER_TEMP;
+    private Integer KEY_LOCATION_NAME;
+    private Integer KEY_WEATHER_WIND_SPEED;
+    private Integer KEY_WEATHER_WIND_DIRECTION;
+    private Integer KEY_WEATHER_TODAY_MINTEMP;
+    private Integer KEY_WEATHER_TODAY_MAXTEMP;
+    private Integer KEY_WEATHER_D1_ICON;
+    private Integer KEY_WEATHER_D1_MINTEMP;
+    private Integer KEY_WEATHER_D1_MAXTEMP;
+    private Integer KEY_WEATHER_D2_ICON;
+    private Integer KEY_WEATHER_D2_MINTEMP;
+    private Integer KEY_WEATHER_D2_MAXTEMP;
+    private Integer KEY_WEATHER_D3_ICON;
+    private Integer KEY_WEATHER_D3_MINTEMP;
+    private Integer KEY_WEATHER_D3_MAXTEMP;
+
+    AppMessageHandlerYWeather(UUID uuid, PebbleProtocol pebbleProtocol) {
+        super(uuid, pebbleProtocol);
+
+        try {
+            JSONObject appKeys = getAppKeys();
+            KEY_LOCATION_NAME = appKeys.getInt("city");
+            KEY_WEATHER_TEMP = appKeys.getInt("temperature");
+            KEY_WEATHER_ICON = appKeys.getInt("icon");
+            KEY_WEATHER_WIND_SPEED = appKeys.getInt("wind");
+            KEY_WEATHER_WIND_DIRECTION = appKeys.getInt("wdirection");
+            KEY_WEATHER_TODAY_MINTEMP = appKeys.getInt("low");
+            KEY_WEATHER_TODAY_MAXTEMP = appKeys.getInt("high");
+            KEY_WEATHER_D1_ICON = appKeys.getInt("code1");
+            KEY_WEATHER_D1_MINTEMP = appKeys.getInt("low1");
+            KEY_WEATHER_D1_MAXTEMP = appKeys.getInt("high1");
+            KEY_WEATHER_D2_ICON = appKeys.getInt("code2");
+            KEY_WEATHER_D2_MINTEMP = appKeys.getInt("low2");
+            KEY_WEATHER_D2_MAXTEMP = appKeys.getInt("high2");
+            KEY_WEATHER_D3_ICON = appKeys.getInt("code3");
+            KEY_WEATHER_D3_MINTEMP = appKeys.getInt("low3");
+            KEY_WEATHER_D3_MAXTEMP = appKeys.getInt("high3");
+
+        } catch (JSONException e) {
+            GB.toast("There was an error accessing the YWeather watchface configuration.", Toast.LENGTH_LONG, GB.ERROR);
+        } catch (IOException ignore) {
+        }
+    }
+
+    private int getIconForConditionCode(int conditionCode, boolean isNight) {
+        if (conditionCode == 800 || conditionCode == 951) {
+            return isNight ? CLEAR_NIGHT : CLEAR_DAY;
+        } else if (conditionCode > 800 && conditionCode < 900) {
+            return isNight ? PARTLY_CLOUDY_NIGHT : PARTLY_CLOUDY_DAY;
+        } else if (conditionCode >= 300 && conditionCode < 313) {
+            return DRIZZLE;
+        } else if (conditionCode >= 313 && conditionCode < 400) {
+            return DRIZZLE;
+        } else if (conditionCode >= 500 && conditionCode < 600) {
+            return RAIN;
+        } else if (conditionCode >= 700 && conditionCode < 732) {
+            return CLOUDY;
+        } else if (conditionCode == 741 || conditionCode == 751 || conditionCode == 761 || conditionCode == 762 ) {
+            return FOG;
+        } else if (conditionCode == 771) {
+            return WINDY;
+        } else if (conditionCode == 781) {
+            return STORM;
+        } else if (conditionCode >= 200 && conditionCode < 300) {
+            return STORM;
+        } else if (conditionCode == 600 || conditionCode == 601 || conditionCode == 602 ) {
+            return SNOW;
+        } else if (conditionCode == 611 || conditionCode == 612) {
+            return SLEET;
+        } else if (conditionCode == 615 || conditionCode == 616 || conditionCode == 620 || conditionCode == 621 || conditionCode == 622) {
+            return RAIN_SNOW;
+        } else if (conditionCode == 906) {
+            return SLEET;
+        } else if (conditionCode >= 907 && conditionCode < 957) {
+            return STORM;
+        } else if (conditionCode == 905) {
+            return STORM;
+        } else if (conditionCode == 900) {
+            return STORM;
+        } else if (conditionCode == 901 || conditionCode == 902 || conditionCode == 962) {
+            return STORM;
+        } else if (conditionCode == 903) {
+            return COLD;
+        } else if (conditionCode == 904) {
+            return HOT;
+        }
+
+        return 0;
+    }
+
+    private String formatWindDirection(float wdirection) {
+        if (Float.isNaN(wdirection)) {
+            return "n/a";
+        }
+        if (wdirection >= 348.75 || wdirection <= 11.25) {
+            return "N";
+        } else if ( wdirection > 11.25 && wdirection <= 33.75 ) {
+            return "NNE";
+        } else if ( wdirection > 33.75 && wdirection <= 56.25 ) {
+            return "NE";
+        } else if ( wdirection > 56.25 && wdirection <= 78.75 ) {
+            return "ENE";
+        } else if ( wdirection > 78.75 && wdirection <= 101.25 ) {
+            return "E";
+        } else if ( wdirection > 101.25 && wdirection <= 123.75 ) {
+            return "ESE";
+        } else if ( wdirection > 123.75 && wdirection <= 146.25 ) {
+            return "SE";
+        } else if ( wdirection > 146.25 && wdirection <= 168.75 ) {
+            return "SSE";
+        } else if ( wdirection > 168.75 && wdirection <= 191.25 ) {
+            return "S";
+        } else if ( wdirection > 191.25 && wdirection <= 213.75 ) {
+            return "SSW";
+        } else if ( wdirection > 213.75 && wdirection <= 236.25 ) {
+            return "SW";
+        } else if ( wdirection > 236.25 && wdirection <= 258.75 ) {
+            return "WSW";
+        } else if ( wdirection > 258.75 && wdirection <= 281.25 ) {
+            return "W";
+        } else if ( wdirection > 281.25 && wdirection <= 303.75 ) {
+            return "WNW";
+        } else if ( wdirection > 303.75 && wdirection <= 326.25 ) {
+            return "NW";
+        } else if ( wdirection > 326.25 && wdirection <= 348.75 ) {
+            return "NNW";
+        } else {
+            return "n/a";
+        }
+    }
+
+    private byte[] encodeYWeatherMessage(WeatherSpec weatherSpec) {
+        if (weatherSpec == null) {
+            return null;
+        }
+        boolean isNight = false; // TODO
+        ArrayList<Pair<Integer, Object>> pairs = new ArrayList<>(2);
+        pairs.add(new Pair<>(KEY_LOCATION_NAME, (Object) (weatherSpec.location)));
+        pairs.add(new Pair<>(KEY_WEATHER_TEMP, (Object) (String.format(Locale.ENGLISH, "%.0f掳", weatherSpec.currentTemp - 273.15))));
+        pairs.add(new Pair<>(KEY_WEATHER_TODAY_MINTEMP, (Object) (String.format(Locale.ENGLISH, "%.0f掳C", weatherSpec.todayMinTemp - 273.15))));
+        pairs.add(new Pair<>(KEY_WEATHER_TODAY_MAXTEMP, (Object) (String.format(Locale.ENGLISH, "%.0f掳C", weatherSpec.todayMaxTemp - 273.15))));
+        pairs.add(new Pair<>(KEY_WEATHER_ICON, (Object) (getIconForConditionCode(weatherSpec.currentConditionCode, isNight))));
+        pairs.add(new Pair<>(KEY_WEATHER_WIND_SPEED, (Object) (String.format(Locale.ENGLISH, "%.0f", weatherSpec.windSpeed))));
+        pairs.add(new Pair<>(KEY_WEATHER_WIND_DIRECTION, (Object) (formatWindDirection(weatherSpec.windDirection))));
+        if (weatherSpec.forecasts.size() > 0) {
+            WeatherSpec.Daily day1 = weatherSpec.forecasts.get(0);
+            pairs.add(new Pair<>(KEY_WEATHER_D1_ICON, (Object) (getIconForConditionCode(day1.conditionCode, false))));
+            pairs.add(new Pair<>(KEY_WEATHER_D1_MINTEMP, (Object) (String.format(Locale.ENGLISH, "%.0f掳C", day1.minTemp - 273.15))));
+            pairs.add(new Pair<>(KEY_WEATHER_D1_MAXTEMP, (Object) (String.format(Locale.ENGLISH, "%.0f掳C", day1.maxTemp - 273.15))));
+        }
+        if (weatherSpec.forecasts.size() > 1) {
+            WeatherSpec.Daily day2 = weatherSpec.forecasts.get(1);
+            pairs.add(new Pair<>(KEY_WEATHER_D2_ICON, (Object) (getIconForConditionCode(day2.conditionCode, false))));
+            pairs.add(new Pair<>(KEY_WEATHER_D2_MINTEMP, (Object) (String.format(Locale.ENGLISH, "%.0f掳C", day2.minTemp - 273.15))));
+            pairs.add(new Pair<>(KEY_WEATHER_D2_MAXTEMP, (Object) (String.format(Locale.ENGLISH, "%.0f掳C", day2.maxTemp - 273.15))));
+        }
+        if (weatherSpec.forecasts.size() > 2) {
+            WeatherSpec.Daily day3 = weatherSpec.forecasts.get(2);
+            pairs.add(new Pair<>(KEY_WEATHER_D3_ICON, (Object) (getIconForConditionCode(day3.conditionCode, false))));
+            pairs.add(new Pair<>(KEY_WEATHER_D3_MINTEMP, (Object) (String.format(Locale.ENGLISH, "%.0f掳C", day3.minTemp - 273.15))));
+            pairs.add(new Pair<>(KEY_WEATHER_D3_MAXTEMP, (Object) (String.format(Locale.ENGLISH, "%.0f掳C", day3.maxTemp - 273.15))));
+        }
+        byte[] weatherMessage = mPebbleProtocol.encodeApplicationMessagePush(PebbleProtocol.ENDPOINT_APPLICATIONMESSAGE, mUUID, pairs, null);
+
+        ByteBuffer buf = ByteBuffer.allocate(weatherMessage.length);
+
+        buf.put(weatherMessage);
+
+        return buf.array();
+    }
+
+    @Override
+    public GBDeviceEvent[] onAppStart() {
+        WeatherSpec weatherSpec = Weather.getInstance().getWeatherSpec();
+        if (weatherSpec == null) {
+            return new GBDeviceEvent[]{null};
+        }
+        GBDeviceEventSendBytes sendBytes = new GBDeviceEventSendBytes();
+        sendBytes.encodedBytes = encodeYWeatherMessage(weatherSpec);
+        return new GBDeviceEvent[]{sendBytes};
+    }
+
+    @Override
+    public byte[] encodeUpdateWeather(WeatherSpec weatherSpec) {
+        return encodeYWeatherMessage(weatherSpec);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerZalewszczak.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerZalewszczak.java
new file mode 100644
index 0000000..765e9a6
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerZalewszczak.java
@@ -0,0 +1,99 @@
+/*  Copyright (C) 2017-2024 Andreas Shimokawa, Carsten Pfeiffer, Daniele
+    Gobbetti
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.pebble;
+
+import android.util.Pair;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventSendBytes;
+import nodomain.freeyourgadget.gadgetbridge.model.Weather;
+import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
+
+class AppMessageHandlerZalewszczak extends AppMessageHandler {
+    private static final int KEY_ICON = 0;
+    private static final int KEY_TEMP = 1; //celsius
+
+    AppMessageHandlerZalewszczak(UUID uuid, PebbleProtocol pebbleProtocol) {
+        super(uuid, pebbleProtocol);
+    }
+
+    /*
+ * converted to JAVA from original JS
+ */
+    private int getIconForConditionCode(int conditionCode) {
+        if (conditionCode < 300) {
+            return 7;
+        } else if (conditionCode < 400) {
+            return 6;
+        } else if (conditionCode == 511) {
+            return 8;
+        } else if (conditionCode < 600) {
+            return 6;
+        } else if (conditionCode < 700) {
+            return 8;
+        } else if (conditionCode < 800) {
+            return 10;
+        } else if (conditionCode == 800) {
+            return 1;
+        } else if (conditionCode == 801) {
+            return 2;
+        } else if (conditionCode < 900) {
+            return 5;
+        } else {
+            return 0;
+        }
+    }
+
+
+    private byte[] encodeWeatherMessage(WeatherSpec weatherSpec) {
+        if (weatherSpec == null) {
+            return null;
+        }
+
+        ArrayList<Pair<Integer, Object>> pairs = new ArrayList<>(2);
+        pairs.add(new Pair<Integer, Object>(KEY_TEMP, weatherSpec.currentTemp - 273 + "C"));
+        pairs.add(new Pair<Integer, Object>(KEY_ICON, getIconForConditionCode(weatherSpec.currentConditionCode)));
+        byte[] weatherMessage = mPebbleProtocol.encodeApplicationMessagePush(PebbleProtocol.ENDPOINT_APPLICATIONMESSAGE, mUUID, pairs, null);
+
+        ByteBuffer buf = ByteBuffer.allocate(weatherMessage.length);
+
+        buf.put(weatherMessage);
+
+        return buf.array();
+    }
+
+    @Override
+    public GBDeviceEvent[] onAppStart() {
+        WeatherSpec weatherSpec = Weather.getInstance().getWeatherSpec();
+        if (weatherSpec == null) {
+            return new GBDeviceEvent[]{null};
+        }
+        GBDeviceEventSendBytes sendBytes = new GBDeviceEventSendBytes();
+        sendBytes.encodedBytes = encodeWeatherMessage(weatherSpec);
+        return new GBDeviceEvent[]{sendBytes};
+    }
+
+    @Override
+    public byte[] encodeUpdateWeather(WeatherSpec weatherSpec) {
+        return encodeWeatherMessage(weatherSpec);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSession.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSession.java
new file mode 100644
index 0000000..54c70c2
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSession.java
@@ -0,0 +1,95 @@
+/*  Copyright (C) 2016-2024 Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.pebble;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.pebble.GBDeviceEventDataLogging;
+
+class DatalogSession {
+    private static final Logger LOG = LoggerFactory.getLogger(DatalogSession.class);
+
+    final byte id;
+    final int tag;
+    final UUID uuid;
+    final byte itemType;
+    final short itemSize;
+    final int timestamp;
+    String taginfo = "(unknown)";
+
+    DatalogSession(byte id, UUID uuid, int timestamp, int tag, byte itemType, short itemSize) {
+        this.id = id;
+        this.tag = tag;
+        this.uuid = uuid;
+        this.timestamp = timestamp;
+        this.itemType = itemType;
+        this.itemSize = itemSize;
+    }
+
+    GBDeviceEvent[] handleMessage(ByteBuffer buf, int length) {
+        return new GBDeviceEvent[]{null};
+    }
+
+    String getTaginfo() {
+        return taginfo;
+    }
+
+    GBDeviceEvent[] handleMessageForPebbleKit(ByteBuffer buf, int length) {
+        if (0 != (length % itemSize)) {
+            LOG.warn("invalid length");
+            return null;
+        }
+        int packetCount = length / itemSize;
+
+        if (packetCount <= 0) {
+            LOG.warn("invalid number of datalog elements");
+            return null;
+        }
+
+        GBDeviceEventDataLogging dataLogging = new GBDeviceEventDataLogging();
+        dataLogging.command = GBDeviceEventDataLogging.COMMAND_RECEIVE_DATA;
+        dataLogging.appUUID = uuid;
+        dataLogging.timestamp = timestamp & 0xffffffffL;
+        dataLogging.tag = tag;
+        dataLogging.pebbleDataType = itemType;
+        dataLogging.data = new Object[packetCount];
+
+        for (int i = 0; i < packetCount; i++) {
+            switch (itemType) {
+                case PebbleProtocol.TYPE_BYTEARRAY:
+                    byte[] itemData = new byte[itemSize];
+                    buf.get(itemData);
+                    dataLogging.data[i] = itemData;
+                    break;
+
+                case PebbleProtocol.TYPE_UINT:
+                    dataLogging.data[i] = buf.getInt() & 0xffffffffL;
+                    break;
+
+                case PebbleProtocol.TYPE_INT:
+                    dataLogging.data[i] = buf.getInt();
+                    break;
+            }
+        }
+        return new GBDeviceEvent[]{dataLogging, null};
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionAnalytics.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionAnalytics.java
new file mode 100644
index 0000000..6b74ecd
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionAnalytics.java
@@ -0,0 +1,68 @@
+/*  Copyright (C) 2017-2024 Andreas Shimokawa, Carsten Pfeiffer, Daniele
+    Gobbetti
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.pebble;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.BatteryState;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+class DatalogSessionAnalytics extends DatalogSession {
+    private static final Logger LOG = LoggerFactory.getLogger(DatalogSessionAnalytics.class);
+    private GBDeviceEventBatteryInfo mGBDeviceEventBatteryInfo = new GBDeviceEventBatteryInfo();
+
+    DatalogSessionAnalytics(byte id, UUID uuid, int timestamp, int tag, byte itemType, short itemSize, GBDevice device) {
+        super(id, uuid, timestamp, tag, itemType, itemSize);
+        // The default notification should not be too bad (one per hour) but we can override this if needed
+        //mGBDevice.setBatteryThresholdPercent((short) 5);
+
+        taginfo = "(analytics - " + tag + ")";
+    }
+
+    @Override
+    GBDeviceEvent[] handleMessage(ByteBuffer datalogMessage, int length) {
+        LOG.info("DATALOG " + taginfo + GB.hexdump(datalogMessage.array(), datalogMessage.position(), length));
+
+        datalogMessage.position(datalogMessage.position() + 3);
+        int messageTS = datalogMessage.getInt();
+
+        datalogMessage.position(datalogMessage.position() + 12);
+        short reportedMilliVolts = datalogMessage.getShort();
+
+        datalogMessage.position(datalogMessage.position() + 2);
+        byte reportedPercentage = datalogMessage.get();
+
+        LOG.info("Battery reading for TS " + messageTS + " is: " + reportedMilliVolts + " milliVolts, percentage: " + reportedPercentage);
+        if (messageTS > 0 && reportedMilliVolts < 5000) { //some safety checks
+            mGBDeviceEventBatteryInfo.state = BatteryState.BATTERY_NORMAL;
+            mGBDeviceEventBatteryInfo.level = reportedPercentage;
+
+            return new GBDeviceEvent[]{mGBDeviceEventBatteryInfo, null};
+        } else { //invalid data, but we ack nevertheless
+            return new GBDeviceEvent[]{null};
+        }
+
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionHealthHR.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionHealthHR.java
new file mode 100644
index 0000000..1e971ed
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionHealthHR.java
@@ -0,0 +1,44 @@
+/*  Copyright (C) 2016-2024 Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.pebble;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+class DatalogSessionHealthHR extends DatalogSessionPebbleHealth {
+
+    private static final Logger LOG = LoggerFactory.getLogger(DatalogSessionHealthHR.class);
+
+    DatalogSessionHealthHR(byte id, UUID uuid, int timestamp, int tag, byte item_type, short item_size, GBDevice device) {
+        super(id, uuid, timestamp, tag, item_type, item_size, device);
+        taginfo = "(Health - HR " + tag + " )";
+    }
+
+    @Override
+    public GBDeviceEvent[] handleMessage(ByteBuffer datalogMessage, int length) {
+        LOG.info("DATALOG " + taginfo + GB.hexdump(datalogMessage.array(), datalogMessage.position(), length));
+
+        return isPebbleHealthEnabled() ? new GBDeviceEvent[]{null} : null;
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionHealthOverlayData.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionHealthOverlayData.java
new file mode 100644
index 0000000..1bb2e5d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionHealthOverlayData.java
@@ -0,0 +1,127 @@
+/*  Copyright (C) 2016-2024 Andreas Shimokawa, Carsten Pfeiffer, Daniele
+    Gobbetti
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.pebble;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.PebbleHealthActivityOverlay;
+import nodomain.freeyourgadget.gadgetbridge.entities.PebbleHealthActivityOverlayDao;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+class DatalogSessionHealthOverlayData extends DatalogSessionPebbleHealth {
+
+    private static final Logger LOG = LoggerFactory.getLogger(DatalogSessionHealthOverlayData.class);
+
+    DatalogSessionHealthOverlayData(byte id, UUID uuid, int timestamp, int tag, byte item_type, short item_size, GBDevice device) {
+        super(id, uuid, timestamp, tag, item_type, item_size, device);
+        taginfo = "(Health - overlay data " + tag + " )";
+    }
+
+    @Override
+    public GBDeviceEvent[] handleMessage(ByteBuffer datalogMessage, int length) {
+        LOG.info("DATALOG " + taginfo + GB.hexdump(datalogMessage.array(), datalogMessage.position(), length));
+
+        if (!isPebbleHealthEnabled()) {
+            return null;
+        }
+
+        int initialPosition = datalogMessage.position();
+        int beginOfRecordPosition;
+        short recordVersion; //probably
+        short recordType; //probably: 1=sleep, 2=deep sleep, 5=??run??ignored for now
+
+        if (0 != (length % itemSize))
+            return null;//malformed message?
+
+        int recordCount = length / itemSize;
+        OverlayRecord[] overlayRecords = new OverlayRecord[recordCount];
+        byte[] tempRecord = new byte[itemSize];
+
+        for (int recordIdx = 0; recordIdx < recordCount; recordIdx++) {
+            beginOfRecordPosition = initialPosition + recordIdx * itemSize;
+            datalogMessage.position(beginOfRecordPosition);//we may not consume all the bytes of a record
+            datalogMessage.get(tempRecord);
+            overlayRecords[recordIdx] = new OverlayRecord(tempRecord);
+        }
+
+        store(overlayRecords);
+        return new GBDeviceEvent[]{null};
+    }
+
+    private void store(OverlayRecord[] overlayRecords) {
+        try (DBHandler dbHandler = GBApplication.acquireDB()) {
+            DaoSession session = dbHandler.getDaoSession();
+            Long userId = DBHelper.getUser(session).getId();
+            Long deviceId = DBHelper.getDevice(getDevice(), session).getId();
+
+            PebbleHealthActivityOverlayDao overlayDao = session.getPebbleHealthActivityOverlayDao();
+
+            List<PebbleHealthActivityOverlay> overlayList = new ArrayList<>();
+            for (OverlayRecord overlayRecord : overlayRecords) {
+                overlayList.add(new PebbleHealthActivityOverlay(overlayRecord.timestampStart, overlayRecord.timestampStart + overlayRecord.durationSeconds, overlayRecord.type, deviceId, userId, overlayRecord.getRawData()));
+            }
+            overlayDao.insertOrReplaceInTx(overlayList);
+        } catch (Exception ex) {
+            LOG.debug(ex.getMessage());
+        }
+    }
+
+    private class OverlayRecord {
+        byte[] knownVersions = {1, 3};
+        short version;
+        int type; //1=sleep, 2=deep sleep
+        int offsetUTC; //probably
+        int timestampStart;
+        int durationSeconds;
+        byte[] rawData;
+
+        OverlayRecord(byte[] rawData) {
+            this.rawData = rawData;
+            ByteBuffer record = ByteBuffer.wrap(rawData);
+            record.order(ByteOrder.LITTLE_ENDIAN);
+
+            this.version = record.getShort();
+            //TODO: check supported versions?
+            record.getShort();//throwaway, unknown
+            this.type = record.getShort();
+            this.offsetUTC = record.getInt();
+            this.timestampStart = record.getInt();
+            this.durationSeconds = record.getInt();
+        }
+
+        byte[] getRawData() {
+            if (storePebbleHealthRawRecord()) {
+                return rawData;
+            }
+            return null;
+        }
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionHealthSleep.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionHealthSleep.java
new file mode 100644
index 0000000..fb8fb00
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionHealthSleep.java
@@ -0,0 +1,130 @@
+/*  Copyright (C) 2016-2024 Andreas Shimokawa, Carsten Pfeiffer, Daniele
+    Gobbetti
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.pebble;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.PebbleHealthActivityOverlay;
+import nodomain.freeyourgadget.gadgetbridge.entities.PebbleHealthActivityOverlayDao;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+class DatalogSessionHealthSleep extends DatalogSessionPebbleHealth {
+
+    private static final Logger LOG = LoggerFactory.getLogger(DatalogSessionHealthSleep.class);
+
+    DatalogSessionHealthSleep(byte id, UUID uuid, int timestamp, int tag, byte item_type, short item_size, GBDevice device) {
+        super(id, uuid, timestamp, tag, item_type, item_size, device);
+        taginfo = "(Health - sleep " + tag + " )";
+    }
+
+    @Override
+    public GBDeviceEvent[] handleMessage(ByteBuffer datalogMessage, int length) {
+        LOG.info("DATALOG " + taginfo + GB.hexdump(datalogMessage.array(), datalogMessage.position(), length));
+
+        if (!isPebbleHealthEnabled()) {
+            return null;
+        }
+
+        int initialPosition = datalogMessage.position();
+        int beginOfRecordPosition;
+        short recordVersion; //probably
+
+        if (0 != (length % itemSize))
+            return null;//malformed message?
+
+        int recordCount = length / itemSize;
+        SleepRecord[] sleepRecords = new SleepRecord[recordCount];
+        byte[] tempRecord = new byte[itemSize];
+
+        for (int recordIdx = 0; recordIdx < recordCount; recordIdx++) {
+            beginOfRecordPosition = initialPosition + recordIdx * itemSize;
+            datalogMessage.position(beginOfRecordPosition);//we may not consume all the bytes of a record
+            datalogMessage.get(tempRecord);
+
+            sleepRecords[recordIdx] = new SleepRecord(tempRecord);
+        }
+
+        store(sleepRecords);
+        return new GBDeviceEvent[]{null};
+    }
+
+    private void store(SleepRecord[] sleepRecords) {
+        try (DBHandler dbHandler = GBApplication.acquireDB()) {
+            DaoSession session = dbHandler.getDaoSession();
+            Long userId = DBHelper.getUser(session).getId();
+            Long deviceId = DBHelper.getDevice(getDevice(), session).getId();
+
+            PebbleHealthActivityOverlayDao overlayDao = session.getPebbleHealthActivityOverlayDao();
+
+            List<PebbleHealthActivityOverlay> overlayList = new ArrayList<>();
+            for (SleepRecord sleepRecord : sleepRecords) {
+                //TODO: check the firmware version and don't use the sleep record if overlay is available?
+                overlayList.add(new PebbleHealthActivityOverlay(sleepRecord.bedTimeStart, sleepRecord.bedTimeEnd, sleepRecord.type, deviceId, userId, sleepRecord.getRawData()));
+            }
+            overlayDao.insertOrReplaceInTx(overlayList);
+        } catch (Exception ex) {
+            LOG.debug(ex.getMessage());
+        }
+    }
+
+    private class SleepRecord {
+        byte[] knownVersions = {1};
+        short version;
+        int type = 1; //sleep, hardcoded as we don't get other info
+        int offsetUTC; //probably
+        int bedTimeStart;
+        int bedTimeEnd;
+        int deepSleepSeconds;
+        byte[] rawData;
+
+        SleepRecord(byte[] rawData) {
+            this.rawData = rawData;
+            ByteBuffer record = ByteBuffer.wrap(rawData);
+            record.order(ByteOrder.LITTLE_ENDIAN);
+
+
+            this.version = record.getShort();
+            //TODO: check supported versions?
+            this.offsetUTC = record.getInt();
+            this.bedTimeStart = record.getInt();
+            this.bedTimeEnd = record.getInt();
+            this.deepSleepSeconds = record.getInt();
+        }
+
+        byte[] getRawData() {
+            if (storePebbleHealthRawRecord()) {
+                return rawData;
+            }
+            return null;
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionHealthSteps.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionHealthSteps.java
new file mode 100644
index 0000000..d436da2
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionHealthSteps.java
@@ -0,0 +1,164 @@
+/*  Copyright (C) 2016-2024 Andreas Shimokawa, Carsten Pfeiffer, Daniele
+    Gobbetti
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.pebble;
+
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
+import nodomain.freeyourgadget.gadgetbridge.devices.pebble.PebbleHealthSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.PebbleHealthActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+class DatalogSessionHealthSteps extends DatalogSessionPebbleHealth {
+
+    private static final Logger LOG = LoggerFactory.getLogger(DatalogSessionHealthSteps.class);
+
+    DatalogSessionHealthSteps(byte id, UUID uuid, int timestamp, int tag, byte item_type, short item_size, GBDevice device) {
+        super(id, uuid, timestamp, tag, item_type, item_size, device);
+        taginfo = "(Health - steps)";
+    }
+
+    @Override
+    public GBDeviceEvent[] handleMessage(ByteBuffer datalogMessage, int length) {
+        LOG.info("DATALOG " + taginfo + GB.hexdump(datalogMessage.array(), datalogMessage.position(), length));
+
+        if (!isPebbleHealthEnabled()) {
+            return null;
+        }
+
+        int timestamp;
+        byte recordLength, recordNum;
+        short recordVersion; //probably
+        int beginOfPacketPosition, beginOfRecordPosition;
+
+        int initialPosition = datalogMessage.position();
+        if (0 != (length % itemSize))
+            return null;//malformed message?
+
+        int packetCount = length / itemSize;
+
+        for (int packetIdx = 0; packetIdx < packetCount; packetIdx++) {
+            beginOfPacketPosition = initialPosition + packetIdx * itemSize;
+            datalogMessage.position(beginOfPacketPosition);//we may not consume all the records of a packet
+
+            recordVersion = datalogMessage.getShort();
+
+            if ((recordVersion != 5) && (recordVersion != 6) && (recordVersion != 7) && (recordVersion != 12) && (recordVersion != 13))
+                return null; //we don't know how to deal with the data TODO: this is not ideal because we will get the same message again and again since we NACK it
+
+            timestamp = datalogMessage.getInt();
+            datalogMessage.get(); //unknown, throw away
+            recordLength = datalogMessage.get();
+            recordNum = datalogMessage.get();
+
+            beginOfRecordPosition = datalogMessage.position();
+            StepsRecord[] stepsRecords = new StepsRecord[recordNum];
+            byte[] tempRecord = new byte[recordLength];
+
+            for (int recordIdx = 0; recordIdx < recordNum; recordIdx++) {
+                datalogMessage.position(beginOfRecordPosition + recordIdx * recordLength); //we may not consume all the bytes of a record
+                datalogMessage.get(tempRecord);
+                stepsRecords[recordIdx] = new StepsRecord(timestamp, recordVersion, tempRecord);
+                timestamp += 60;
+            }
+
+            store(stepsRecords);
+        }
+        return new GBDeviceEvent[]{null};//ACK by default
+    }
+
+    private void store(StepsRecord[] stepsRecords) {
+
+        try (DBHandler dbHandler = GBApplication.acquireDB()) {
+            PebbleHealthSampleProvider sampleProvider = new PebbleHealthSampleProvider(getDevice(), dbHandler.getDaoSession());
+            PebbleHealthActivitySample[] samples = new PebbleHealthActivitySample[stepsRecords.length];
+            // TODO: user and device
+            Long userId = DBHelper.getUser(dbHandler.getDaoSession()).getId();
+            Long deviceId = DBHelper.getDevice(getDevice(), dbHandler.getDaoSession()).getId();
+            for (int j = 0; j < stepsRecords.length; j++) {
+                StepsRecord stepsRecord = stepsRecords[j];
+                samples[j] = new PebbleHealthActivitySample(
+                        stepsRecord.timestamp,
+                        deviceId, userId,
+                        stepsRecord.getRawData(),
+                        stepsRecord.intensity,
+                        stepsRecord.steps,
+                        stepsRecord.heart_rate
+                );
+                samples[j].setProvider(sampleProvider);
+            }
+
+            sampleProvider.addGBActivitySamples(samples);
+        } catch (Exception ex) {
+            LOG.debug(ex.getMessage());
+        }
+    }
+
+    private class StepsRecord {
+        byte[] knownVersions = {5, 6, 7, 12, 13};
+        short version;
+        int timestamp;
+        int steps;
+        int orientation;
+        int intensity;
+        int light_intensity;
+        int heart_rate;
+
+        byte[] rawData;
+
+        StepsRecord(int timestamp, short version, byte[] rawData) {
+            this.timestamp = timestamp;
+            this.rawData = rawData;
+            ByteBuffer record = ByteBuffer.wrap(rawData);
+            record.order(ByteOrder.LITTLE_ENDIAN);
+
+            this.version = version;
+            //TODO: check supported versions?
+
+            this.steps = record.get() & 0xff;
+            this.orientation = record.get() & 0xff;
+            this.intensity = record.getShort() & 0xffff;
+            this.light_intensity = record.get() & 0xff;
+            if (version >= 7) {
+                // skip 7 bytes
+                record.getInt();
+                record.getShort();
+                record.get();
+                this.heart_rate = record.get() & 0xff;
+            }
+        }
+
+        byte[] getRawData() {
+            if (storePebbleHealthRawRecord()) {
+                return rawData;
+            }
+            return null;
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionPebbleHealth.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionPebbleHealth.java
new file mode 100644
index 0000000..be08d93
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionPebbleHealth.java
@@ -0,0 +1,48 @@
+/*  Copyright (C) 2016-2024 Andreas Shimokawa, Carsten Pfeiffer, Daniele
+    Gobbetti
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.pebble;
+
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.util.preferences.DevicePrefs;
+
+abstract class DatalogSessionPebbleHealth extends DatalogSession {
+
+    private final GBDevice mDevice;
+    private final DevicePrefs devicePrefs;
+
+    DatalogSessionPebbleHealth(byte id, UUID uuid, int timestamp, int tag, byte itemType, short itemSize, GBDevice device) {
+        super(id, uuid, timestamp, tag, itemType, itemSize);
+        mDevice = device;
+        devicePrefs = GBApplication.getDevicePrefs(mDevice);
+    }
+
+    public GBDevice getDevice() {
+        return mDevice;
+    }
+
+    boolean isPebbleHealthEnabled() {
+        return devicePrefs.getBoolean("pebble_sync_health", true);
+    }
+
+    boolean storePebbleHealthRawRecord() {
+        return devicePrefs.getBoolean("pebble_health_store_raw", true);
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleActiveAppTracker.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleActiveAppTracker.java
new file mode 100644
index 0000000..77114e7
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleActiveAppTracker.java
@@ -0,0 +1,56 @@
+/*  Copyright (C) 2019-2024 Matej Drobni膷
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.pebble;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.UUID;
+
+public class PebbleActiveAppTracker {
+    private @Nullable UUID mPreviousRunningApp = null;
+    private @Nullable UUID mCurrentRunningApp = null;
+
+    @Nullable
+    public UUID getPreviousRunningApp() {
+        return mPreviousRunningApp;
+    }
+
+    @Nullable
+    public UUID getCurrentRunningApp() {
+        return mCurrentRunningApp;
+    }
+
+    public void markAppClosed(@NonNull UUID app) {
+        if (mCurrentRunningApp == app) {
+            if (mPreviousRunningApp != null) {
+                markAppOpened(mPreviousRunningApp);
+            } else {
+                mCurrentRunningApp = null;
+            }
+        }
+    }
+
+    public void markAppOpened(@NonNull UUID openedApp) {
+        if (openedApp.equals(mCurrentRunningApp)) {
+            return;
+        }
+
+        mPreviousRunningApp = mCurrentRunningApp;
+        mCurrentRunningApp = openedApp;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleIoThread.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleIoThread.java
new file mode 100644
index 0000000..2ea3402
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleIoThread.java
@@ -0,0 +1,767 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, Carsten Pfeiffer, Daniel Dakhno,
+    Daniele Gobbetti, Julien Pivotto, Matej Drobni膷, Taavi Eom盲e, Uwe Hermann
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.pebble;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothSocket;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.ParcelUuid;
+import android.webkit.ValueCallback;
+import android.webkit.WebView;
+
+import androidx.annotation.NonNull;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.ExternalPebbleJSActivity;
+import nodomain.freeyourgadget.gadgetbridge.activities.appmanager.AbstractAppManagerFragment;
+import nodomain.freeyourgadget.gadgetbridge.activities.appmanager.AppManagerActivity;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventAppInfo;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventAppManagement;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventAppMessage;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.pebble.GBDeviceEventDataLogging;
+import nodomain.freeyourgadget.gadgetbridge.devices.pebble.PBWReader;
+import nodomain.freeyourgadget.gadgetbridge.devices.pebble.PebbleCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.pebble.PebbleInstallable;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.ble.PebbleLESupport;
+import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceIoThread;
+import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+import nodomain.freeyourgadget.gadgetbridge.util.PebbleUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+import nodomain.freeyourgadget.gadgetbridge.util.WebViewSingleton;
+import nodomain.freeyourgadget.gadgetbridge.util.preferences.DevicePrefs;
+
+class PebbleIoThread extends GBDeviceIoThread {
+    private static final Logger LOG = LoggerFactory.getLogger(PebbleIoThread.class);
+
+    private final Prefs prefs = GBApplication.getPrefs();
+    private final DevicePrefs devicePrefs;
+
+    private final PebbleProtocol mPebbleProtocol;
+    private final PebbleSupport mPebbleSupport;
+    private PebbleKitSupport mPebbleKitSupport;
+    private final PebbleActiveAppTracker mPebbleActiveAppTracker;
+    private final boolean mEnablePebblekit;
+
+    private boolean mIsTCP = false;
+    private BluetoothAdapter mBtAdapter = null;
+    private BluetoothSocket mBtSocket = null;
+    private Socket mTCPSocket = null; // for emulator
+    private InputStream mInStream = null;
+    private OutputStream mOutStream = null;
+    private PebbleLESupport mPebbleLESupport;
+
+    private boolean mQuit = false;
+    private boolean mIsConnected = false;
+    private boolean mIsInstalling = false;
+
+    private PBWReader mPBWReader = null;
+    private GBDeviceApp mCurrentlyInstallingApp = null;
+    private int mAppInstallToken = -1;
+    private InputStream mFis = null;
+    private PebbleAppInstallState mInstallState = PebbleAppInstallState.UNKNOWN;
+    private PebbleInstallable[] mPebbleInstallables = null;
+    private int mCurrentInstallableIndex = -1;
+    private int mInstallSlot = -2;
+    private int mCRC = -1;
+    private int mBinarySize = -1;
+    private int mBytesWritten = -1;
+
+    private void sendAppMessageJS(GBDeviceEventAppMessage appMessage) {
+        sendAppMessage(appMessage);
+        if (appMessage.type == GBDeviceEventAppMessage.TYPE_APPMESSAGE) {
+            write(mPebbleProtocol.encodeApplicationMessageAck(appMessage.appUUID, (byte) appMessage.id));
+        }
+    }
+
+    private static void sendAppMessage(GBDeviceEventAppMessage message) {
+        final String jsEvent;
+        try {
+            WebViewSingleton.getInstance().checkAppRunning(message.appUUID);
+        } catch (IllegalStateException ex) {
+            LOG.warn("Unable to send app message: " + message, ex);
+            return;
+        }
+
+        // TODO: handle ACK and NACK types with ids
+        if (message.type != GBDeviceEventAppMessage.TYPE_APPMESSAGE) {
+            jsEvent = (GBDeviceEventAppMessage.TYPE_NACK == GBDeviceEventAppMessage.TYPE_APPMESSAGE) ? "NACK" + message.id : "ACK" + message.id;
+            LOG.debug("WEBVIEW received ACK/NACK:" + message.message + " for uuid: " + message.appUUID + " ID: " + message.id);
+        } else {
+            jsEvent = "appmessage";
+        }
+
+        final String appMessage = PebbleUtils.parseIncomingAppMessage(message.message, message.appUUID, message.id);
+        LOG.debug("to WEBVIEW: event: " + jsEvent + " message: " + appMessage);
+        WebViewSingleton.getInstance().invokeWebview(new WebViewSingleton.WebViewRunnable() {
+            @Override
+            public void invoke(WebView webView) {
+                webView.evaluateJavascript("if (typeof Pebble == 'object') Pebble.evaluate('" + jsEvent + "',[" + appMessage + "]);", new ValueCallback<String>() {
+                    @Override
+                    public void onReceiveValue(String s) {
+                        //TODO: the message should be acked here instead of in PebbleIoThread
+                        LOG.debug("Callback from appmessage: " + s);
+                    }
+                });
+            }
+        });
+    }
+
+    PebbleIoThread(PebbleSupport pebbleSupport, GBDevice gbDevice, GBDeviceProtocol gbDeviceProtocol, BluetoothAdapter btAdapter, Context context) {
+        super(gbDevice, context);
+        devicePrefs = GBApplication.getDevicePrefs(gbDevice);
+        mPebbleProtocol = (PebbleProtocol) gbDeviceProtocol;
+        mBtAdapter = btAdapter;
+        mPebbleSupport = pebbleSupport;
+        mEnablePebblekit = devicePrefs.getBoolean("third_party_apps_set_settings", false);
+        mPebbleProtocol.setAlwaysACKPebbleKit(devicePrefs.getBoolean("pebble_always_ack_pebblekit", false));
+        mPebbleProtocol.setEnablePebbleKit(mEnablePebblekit);
+
+        mPebbleActiveAppTracker = new PebbleActiveAppTracker();
+    }
+
+    private int readWithException(InputStream inputStream, byte[] buffer, int byteOffset, int byteCount) throws IOException {
+        int ret = inputStream.read(buffer, byteOffset, byteCount);
+        if (ret == -1) {
+            throw new IOException("broken pipe");
+        }
+        return ret;
+    }
+
+    @Override
+    protected boolean connect() {
+        String deviceAddress = gbDevice.getAddress();
+        GBDevice.State originalState = gbDevice.getState();
+        gbDevice.setState(GBDevice.State.CONNECTING);
+        gbDevice.sendDeviceUpdateIntent(getContext());
+        try {
+            // contains only one ":"? then it is addr:port
+            int firstColon = deviceAddress.indexOf(":");
+            if (firstColon == deviceAddress.lastIndexOf(":")) {
+                mIsTCP = true;
+                InetAddress serverAddr = InetAddress.getByName(deviceAddress.substring(0, firstColon));
+                mTCPSocket = new Socket(serverAddr, Integer.parseInt(deviceAddress.substring(firstColon + 1)));
+                mInStream = mTCPSocket.getInputStream();
+                mOutStream = mTCPSocket.getOutputStream();
+            } else {
+                mIsTCP = false;
+                if (gbDevice.getVolatileAddress() != null && devicePrefs.getBoolean("pebble_force_le", false)) {
+                    deviceAddress = gbDevice.getVolatileAddress();
+                }
+                BluetoothDevice btDevice = mBtAdapter.getRemoteDevice(deviceAddress);
+                if (btDevice.getType() == BluetoothDevice.DEVICE_TYPE_LE) {
+                    LOG.info("This is a Pebble 2 or Pebble-LE/Pebble Time LE, will use BLE");
+                    mInStream = new PipedInputStream();
+                    mOutStream = new PipedOutputStream();
+                    mPebbleLESupport = new PebbleLESupport(this.getContext(), gbDevice, btDevice, (PipedInputStream) mInStream, (PipedOutputStream) mOutStream);
+                } else {
+                    ParcelUuid[] uuids = btDevice.getUuids();
+                    if (uuids == null) {
+                        return false;
+                    }
+                    for (ParcelUuid uuid : uuids) {
+                        LOG.info("found service UUID " + uuid);
+                    }
+
+                    final UUID UuidSDP = UUID.fromString("00001101-0000-1000-8000-00805f9b34fb");
+                    mBtSocket = btDevice.createRfcommSocketToServiceRecord(UuidSDP);
+
+                    // TODO: Why is this comment here?
+                    //mBtSocket = btDevice.createRfcommSocketToServiceRecord(uuids[0].getUuid());
+                    mBtSocket.connect();
+                    mInStream = mBtSocket.getInputStream();
+                    mOutStream = mBtSocket.getOutputStream();
+                }
+            }
+            if (((PebbleCoordinator) gbDevice.getDeviceCoordinator()).isBackgroundJsEnabled(gbDevice)) {
+                Intent startIntent = new Intent(getContext(), ExternalPebbleJSActivity.class);
+                startIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                startIntent.putExtra(GBDevice.EXTRA_DEVICE, gbDevice);
+                startIntent.putExtra(ExternalPebbleJSActivity.START_BG_WEBVIEW, true);
+                getContext().startActivity(startIntent);
+            } else {
+                LOG.debug("Not enabling background Webview, is disabled in preferences.");
+            }
+        } catch (IOException e) {
+            LOG.warn("error while connecting: " + e.getMessage(), e);
+            gbDevice.setState(originalState);
+            gbDevice.sendDeviceUpdateIntent(getContext());
+
+            mInStream = null;
+            mOutStream = null;
+            mBtSocket = null;
+            return false;
+        }
+
+        mPebbleProtocol.setForceProtocol(devicePrefs.getBoolean("pebble_force_protocol", false));
+
+        mIsConnected = true;
+        write(mPebbleProtocol.encodeFirmwareVersionReq());
+        gbDevice.setState(GBDevice.State.CONNECTED);
+        gbDevice.sendDeviceUpdateIntent(getContext());
+
+        return true;
+    }
+
+    @Override
+    public void run() {
+        mIsConnected = connect();
+        if (!mIsConnected) {
+            if (GBApplication.getPrefs().getAutoReconnect(getDevice()) && !mQuit) {
+                gbDevice.setState(GBDevice.State.WAITING_FOR_RECONNECT);
+                gbDevice.sendDeviceUpdateIntent(getContext());
+            }
+            return;
+        }
+
+        byte[] buffer = new byte[8192];
+        enablePebbleKitSupport(true);
+        mQuit = false;
+        while (!mQuit) {
+            try {
+                if (mIsInstalling) {
+                    switch (mInstallState) {
+                        case WAIT_SLOT:
+                            if (mInstallSlot == -1) {
+                                finishInstall(true); // no slots available
+                            } else if (mInstallSlot >= 0) {
+                                mInstallState = PebbleAppInstallState.START_INSTALL;
+                                continue;
+                            }
+                            break;
+                        case START_INSTALL:
+                            LOG.info("start installing app binary");
+                            PebbleInstallable pi = mPebbleInstallables[mCurrentInstallableIndex];
+                            mFis = mPBWReader.getInputStreamFile(pi.getFileName());
+                            mCRC = pi.getCRC();
+                            mBinarySize = pi.getFileSize();
+                            mBytesWritten = 0;
+                            writeInstallApp(mPebbleProtocol.encodeUploadStart(pi.getType(), mInstallSlot, mBinarySize, mPBWReader.isLanguage() ? "lang" : null));
+                            mAppInstallToken = -1;
+                            mInstallState = PebbleAppInstallState.WAIT_TOKEN;
+                            break;
+                        case WAIT_TOKEN:
+                            if (mAppInstallToken != -1) {
+                                LOG.info("got token " + mAppInstallToken);
+                                mInstallState = PebbleAppInstallState.UPLOAD_CHUNK;
+                                continue;
+                            }
+                            break;
+                        case UPLOAD_CHUNK:
+                            int bytes = 0;
+                            do {
+                                int read = mFis.read(buffer, bytes, 2000 - bytes);
+                                if (read <= 0) break;
+                                bytes += read;
+                            } while (bytes < 2000);
+
+                            if (bytes > 0) {
+                                GB.updateInstallNotification(getContext().getString(
+                                        R.string.installing_binary_d_d, (mCurrentInstallableIndex + 1), mPebbleInstallables.length), true, (int) (((float) mBytesWritten / mBinarySize) * 100), getContext());
+                                writeInstallApp(mPebbleProtocol.encodeUploadChunk(mAppInstallToken, buffer, bytes));
+                                mBytesWritten += bytes;
+                                mAppInstallToken = -1;
+                                mInstallState = PebbleAppInstallState.WAIT_TOKEN;
+                            } else {
+                                mInstallState = PebbleAppInstallState.UPLOAD_COMMIT;
+                                continue;
+                            }
+                            break;
+                        case UPLOAD_COMMIT:
+                            writeInstallApp(mPebbleProtocol.encodeUploadCommit(mAppInstallToken, mCRC));
+                            mAppInstallToken = -1;
+                            mInstallState = PebbleAppInstallState.WAIT_COMMIT;
+                            break;
+                        case WAIT_COMMIT:
+                            if (mAppInstallToken != -1) {
+                                LOG.info("got token " + mAppInstallToken);
+                                mInstallState = PebbleAppInstallState.UPLOAD_COMPLETE;
+                                continue;
+                            }
+                            break;
+                        case UPLOAD_COMPLETE:
+                            writeInstallApp(mPebbleProtocol.encodeUploadComplete(mAppInstallToken));
+                            if (++mCurrentInstallableIndex < mPebbleInstallables.length) {
+                                mInstallState = PebbleAppInstallState.START_INSTALL;
+                            } else {
+                                mInstallState = PebbleAppInstallState.APP_REFRESH;
+                            }
+                            break;
+                        case APP_REFRESH:
+                            if (mPBWReader.isFirmware()) {
+                                writeInstallApp(mPebbleProtocol.encodeInstallFirmwareComplete());
+                                finishInstall(false);
+                            } else if (mPBWReader.isLanguage() || mPebbleProtocol.mFwMajor >= 3) {
+                                finishInstall(false); // FIXME: don't know yet how to detect success
+                            } else {
+                                writeInstallApp(mPebbleProtocol.encodeAppRefresh(mInstallSlot));
+                            }
+                            break;
+                        default:
+                            break;
+                    }
+                }
+                if (mIsTCP) {
+                    mInStream.skip(6);
+                }
+                int bytes = readWithException(mInStream, buffer, 0, 4);
+
+                while (bytes < 4) {
+                    bytes += readWithException(mInStream, buffer, bytes, 4 - bytes);
+                }
+
+                ByteBuffer buf = ByteBuffer.wrap(buffer);
+                buf.order(ByteOrder.BIG_ENDIAN);
+                short length = buf.getShort();
+                short endpoint = buf.getShort();
+                if (length < 0 || length > 8192) {
+                    LOG.info("invalid length " + length);
+                    while (mInStream.available() > 0) {
+                        readWithException(mInStream, buffer, 0, buffer.length); // read all
+                    }
+                    continue;
+                }
+
+                bytes = readWithException(mInStream, buffer, 4, length);
+                while (bytes < length) {
+                    bytes += readWithException(mInStream, buffer, bytes + 4, length - bytes);
+                }
+
+                if (mIsTCP) {
+                    mInStream.skip(2);
+                }
+
+                GBDeviceEvent[] deviceEvents = mPebbleProtocol.decodeResponse(buffer);
+                if (deviceEvents == null) {
+                    LOG.info("unhandled message to endpoint " + endpoint + " (" + length + " bytes)");
+                } else {
+                    for (GBDeviceEvent deviceEvent : deviceEvents) {
+                        if (deviceEvent == null) {
+                            continue;
+                        }
+                        if (!evaluateGBDeviceEventPebble(deviceEvent)) {
+                            mPebbleSupport.evaluateGBDeviceEvent(deviceEvent);
+                        }
+                    }
+                }
+                try {
+                    Thread.sleep(100);
+                } catch (InterruptedException e) {
+                    e.printStackTrace();
+                }
+            } catch (IOException e) {
+                if (e.getMessage() != null && (e.getMessage().equals("broken pipe") || e.getMessage().contains("socket closed"))) { //FIXME: this does not feel right
+                    LOG.info(e.getMessage());
+                    mIsConnected = false;
+                    mBtSocket = null;
+                    LOG.info("Bluetooth socket closed, will quit IO Thread");
+                    break;
+                }
+            }
+        }
+        mIsConnected = false;
+        if (mBtSocket != null) {
+            try {
+                mBtSocket.close();
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+            mBtSocket = null;
+        }
+
+        enablePebbleKitSupport(false);
+
+        if (mQuit || !GBApplication.getPrefs().getAutoReconnect(getDevice())) {
+            gbDevice.setState(GBDevice.State.NOT_CONNECTED);
+        } else {
+            gbDevice.setState(GBDevice.State.WAITING_FOR_RECONNECT);
+        }
+
+        if (((PebbleCoordinator) gbDevice.getDeviceCoordinator()).isBackgroundJsEnabled(gbDevice)) {
+            WebViewSingleton.getInstance().disposeWebView();
+        }
+
+        gbDevice.sendDeviceUpdateIntent(getContext());
+    }
+
+    private void enablePebbleKitSupport(boolean enable) {
+        if (enable && mEnablePebblekit) {
+            mPebbleKitSupport = new PebbleKitSupport(getContext(), PebbleIoThread.this, mPebbleProtocol);
+        } else {
+            if (mPebbleKitSupport != null) {
+                mPebbleKitSupport.close();
+                mPebbleKitSupport = null;
+            }
+        }
+    }
+
+
+    private void write_real(byte[] bytes) {
+        try {
+            if (mIsTCP) {
+                ByteBuffer buf = ByteBuffer.allocate(bytes.length + 8);
+                buf.order(ByteOrder.BIG_ENDIAN);
+                buf.putShort((short) 0xfeed);
+                buf.putShort((short) 1);
+                buf.putShort((short) bytes.length);
+                buf.put(bytes);
+                buf.putShort((short) 0xbeef);
+                mOutStream.write(buf.array());
+                mOutStream.flush();
+            } else {
+                mOutStream.write(bytes);
+                mOutStream.flush();
+            }
+        } catch (IOException e) {
+            LOG.error("Error writing.", e);
+        }
+        try {
+            Thread.sleep(100);
+        } catch (InterruptedException ignored) {
+        }
+    }
+
+    @Override
+    synchronized public void write(byte[] bytes) {
+        if (bytes == null) {
+            return;
+        }
+        // on FW < 3.0 block writes if app installation in in progress
+        if (!mIsConnected || (mPebbleProtocol.mFwMajor < 3 && mIsInstalling && mInstallState != PebbleAppInstallState.WAIT_SLOT)) {
+            return;
+        }
+        write_real(bytes);
+    }
+
+    // FIXME: parts are supposed to be generic code
+    private boolean evaluateGBDeviceEventPebble(GBDeviceEvent deviceEvent) {
+
+        if (deviceEvent instanceof GBDeviceEventVersionInfo) {
+            if (prefs.getBoolean("datetime_synconconnect", true)) {
+                LOG.info("syncing time");
+                write(mPebbleProtocol.encodeSetTime());
+            }
+            write(mPebbleProtocol.encodeEnableAppLogs(devicePrefs.getBoolean("pebble_enable_applogs", false)));
+            write(mPebbleProtocol.encodeReportDataLogSessions());
+            gbDevice.setState(GBDevice.State.INITIALIZED);
+            return false;
+        } else if (deviceEvent instanceof GBDeviceEventAppManagement) {
+            GBDeviceEventAppManagement appMgmt = (GBDeviceEventAppManagement) deviceEvent;
+            switch (appMgmt.type) {
+                case DELETE:
+                    // right now on the Pebble we also receive this on a failed/successful installation ;/
+                    switch (appMgmt.event) {
+                        case FAILURE:
+                            if (mIsInstalling) {
+                                if (mInstallState == PebbleAppInstallState.WAIT_SLOT) {
+                                    // get the free slot
+                                    writeInstallApp(mPebbleProtocol.encodeAppInfoReq());
+                                } else {
+                                    finishInstall(true);
+                                }
+                            } else {
+                                LOG.info("failure removing app");
+                            }
+                            break;
+                        case SUCCESS:
+                            if (mIsInstalling) {
+                                if (mInstallState == PebbleAppInstallState.WAIT_SLOT) {
+                                    // get the free slot
+                                    writeInstallApp(mPebbleProtocol.encodeAppInfoReq());
+                                } else {
+                                    finishInstall(false);
+                                    // refresh app list
+                                    write(mPebbleProtocol.encodeAppInfoReq());
+                                }
+                            } else {
+                                LOG.info("successfully removed app");
+                                write(mPebbleProtocol.encodeAppInfoReq());
+                            }
+                            break;
+                        default:
+                            break;
+                    }
+                    break;
+                case INSTALL:
+                    switch (appMgmt.event) {
+                        case FAILURE:
+                            LOG.info("failure installing app"); // TODO: report to Installer
+                            finishInstall(true);
+                            break;
+                        case SUCCESS:
+                            setToken(appMgmt.token);
+                            break;
+                        case REQUEST:
+                            LOG.info("APPFETCH request: " + appMgmt.uuid + " / " + appMgmt.token);
+                            try {
+                                installApp(Uri.fromFile(new File(PebbleUtils.getPbwCacheDir(),appMgmt.uuid.toString() + ".pbw")), appMgmt.token);
+                            } catch (IOException e) {
+                                LOG.error("Error installing app: " + e.getMessage(), e);
+                            }
+                            break;
+                        default:
+                            break;
+                    }
+                    break;
+                case START:
+                    LOG.info("got GBDeviceEventAppManagement START event for uuid: " + appMgmt.uuid);
+                    if (((PebbleCoordinator) gbDevice.getDeviceCoordinator()).isBackgroundJsEnabled(gbDevice)) {
+                        if (mPebbleProtocol.hasAppMessageHandler(appMgmt.uuid)) {
+                            WebViewSingleton.getInstance().stopJavascriptInterface();
+                        } else {
+                            WebViewSingleton.getInstance().runJavascriptInterface(gbDevice, appMgmt.uuid);
+                        }
+                    }
+
+                    mPebbleActiveAppTracker.markAppOpened(appMgmt.uuid);
+                    break;
+                case STOP:
+                    mPebbleActiveAppTracker.markAppClosed(appMgmt.uuid);
+                    break;
+                default:
+                    break;
+            }
+            return true;
+        } else if (deviceEvent instanceof GBDeviceEventAppInfo) {
+            LOG.info("Got event for APP_INFO");
+            GBDeviceEventAppInfo appInfoEvent = (GBDeviceEventAppInfo) deviceEvent;
+            setInstallSlot(appInfoEvent.freeSlot);
+            return false;
+        } else if (deviceEvent instanceof GBDeviceEventAppMessage) {
+            if (((PebbleCoordinator) gbDevice.getDeviceCoordinator()).isBackgroundJsEnabled(gbDevice)) {
+                sendAppMessageJS((GBDeviceEventAppMessage) deviceEvent);
+            }
+            if (mEnablePebblekit) {
+                LOG.info("Got AppMessage event");
+                if (mPebbleKitSupport != null && ((GBDeviceEventAppMessage) deviceEvent).type == GBDeviceEventAppMessage.TYPE_APPMESSAGE) {
+                    mPebbleKitSupport.sendAppMessageIntent((GBDeviceEventAppMessage) deviceEvent);
+                }
+            }
+        } else if (deviceEvent instanceof GBDeviceEventDataLogging) {
+            if (mEnablePebblekit) {
+                LOG.info("Got Datalogging event");
+                if (mPebbleKitSupport != null) {
+                    mPebbleKitSupport.sendDataLoggingIntent((GBDeviceEventDataLogging) deviceEvent);
+                }
+            }
+        }
+
+        return false;
+    }
+
+    private void setToken(int token) {
+        mAppInstallToken = token;
+    }
+
+    private void setInstallSlot(int slot) {
+        if (mIsInstalling) {
+            mInstallSlot = slot;
+        }
+    }
+
+    synchronized private void writeInstallApp(byte[] bytes) {
+        if (!mIsInstalling) {
+            return;
+        }
+        LOG.info("got " + bytes.length + "bytes for writeInstallApp()");
+        write_real(bytes);
+    }
+
+    void installApp(Uri uri, int appId) {
+        if (mIsInstalling) {
+            return;
+        }
+
+        String platformName = PebbleUtils.getPlatformName(gbDevice.getModel());
+
+        try {
+            mPBWReader = new PBWReader(uri, getContext(), platformName);
+        } catch (FileNotFoundException e) {
+            LOG.warn("file not found: " + e.getMessage(), e);
+            return;
+        } catch (IOException e) {
+            LOG.warn("unable to read file: " + e.getMessage(), e);
+            return;
+        }
+
+        mPebbleInstallables = mPBWReader.getPebbleInstallables();
+        mCurrentInstallableIndex = 0;
+
+        if (mPBWReader.isFirmware()) {
+            LOG.info("starting firmware installation");
+            mIsInstalling = true;
+            mInstallSlot = 0;
+            writeInstallApp(mPebbleProtocol.encodeInstallFirmwareStart());
+            mInstallState = PebbleAppInstallState.START_INSTALL;
+
+            /*
+             * This is a hack for recovery mode, in which the blocking read has no timeout and the
+             * firmware installation command does not return any ack.
+             * In normal mode we would got at least out of the blocking read call after a while.
+             *
+             *
+             * ... we should really not handle installation from thread that does the blocking read
+             *
+             */
+            writeInstallApp(mPebbleProtocol.encodeGetTime());
+        } else {
+            mCurrentlyInstallingApp = mPBWReader.getGBDeviceApp();
+            if (mPebbleProtocol.mFwMajor >= 3 && !mPBWReader.isLanguage()) {
+                if (appId == 0) {
+                    // only install metadata - not the binaries
+                    write(mPebbleProtocol.encodeInstallMetadata(mCurrentlyInstallingApp.getUUID(), mCurrentlyInstallingApp.getName(), mPBWReader.getAppVersion(), mPBWReader.getSdkVersion(), mPBWReader.getFlags(), mPBWReader.getIconId()));
+                    write(mPebbleProtocol.encodeAppStart(mCurrentlyInstallingApp.getUUID(), true));
+                } else {
+                    // this came from an app fetch request, so do the real stuff
+                    mIsInstalling = true;
+                    mInstallSlot = appId;
+                    mInstallState = PebbleAppInstallState.START_INSTALL;
+
+                    writeInstallApp(mPebbleProtocol.encodeAppFetchAck());
+                }
+            } else {
+                mIsInstalling = true;
+                if (mPBWReader.isLanguage()) {
+                    mInstallSlot = 0;
+                    mInstallState = PebbleAppInstallState.START_INSTALL;
+
+                    // unblock HACK
+                    writeInstallApp(mPebbleProtocol.encodeGetTime());
+                } else {
+                    mInstallState = PebbleAppInstallState.WAIT_SLOT;
+                    writeInstallApp(mPebbleProtocol.encodeAppDelete(mCurrentlyInstallingApp.getUUID()));
+                }
+            }
+        }
+    }
+
+    void reopenLastApp(@NonNull UUID assumedCurrentApp) {
+        UUID currentApp = mPebbleActiveAppTracker.getCurrentRunningApp();
+        UUID previousApp = mPebbleActiveAppTracker.getPreviousRunningApp();
+
+        if (previousApp == null || !assumedCurrentApp.equals(currentApp)) {
+            write(mPebbleProtocol.encodeAppStart(assumedCurrentApp, false));
+        } else {
+            write(mPebbleProtocol.encodeAppStart(previousApp, true));
+        }
+    }
+
+    private void finishInstall(boolean hadError) {
+        if (!mIsInstalling) {
+            return;
+        }
+        if (hadError) {
+            GB.updateInstallNotification(getContext().getString(R.string.installation_failed_), false, 0, getContext());
+        } else {
+            GB.updateInstallNotification(getContext().getString(R.string.installation_successful), false, 0, getContext());
+            if (mPebbleProtocol.mFwMajor >= 3) {
+                String filenameSuffix;
+                if (mCurrentlyInstallingApp != null) {
+                    if (mCurrentlyInstallingApp.getType() == GBDeviceApp.Type.WATCHFACE) {
+                        filenameSuffix = ".watchfaces";
+                    } else {
+                        filenameSuffix = ".watchapps";
+                    }
+                    AppManagerActivity.addToAppOrderFile(gbDevice.getAddress() + filenameSuffix, mCurrentlyInstallingApp.getUUID());
+                    Intent refreshIntent = new Intent(AbstractAppManagerFragment.ACTION_REFRESH_APPLIST);
+                    LocalBroadcastManager.getInstance(getContext()).sendBroadcast(refreshIntent);
+                }
+            }
+        }
+        mInstallState = PebbleAppInstallState.UNKNOWN;
+
+        if (hadError && mAppInstallToken != -1) {
+            writeInstallApp(mPebbleProtocol.encodeUploadCancel(mAppInstallToken));
+        }
+
+        mPBWReader = null;
+        mIsInstalling = false;
+        mCurrentlyInstallingApp = null;
+
+        if (mFis != null) {
+            try {
+                mFis.close();
+            } catch (IOException e) {
+                // ignore
+            }
+        }
+        mFis = null;
+        mAppInstallToken = -1;
+        mInstallSlot = -2;
+    }
+
+    @Override
+    public void quit() {
+        mQuit = true;
+        if (mBtSocket != null) {
+            try {
+                mBtSocket.close();
+            } catch (IOException ignored) {
+            }
+            mBtSocket = null;
+        }
+        if (mTCPSocket != null) {
+            try {
+                mTCPSocket.close();
+            } catch (IOException ignored) {
+            }
+            mTCPSocket = null;
+        }
+        if (mPebbleLESupport != null) {
+            mPebbleLESupport.close();
+            mPebbleLESupport = null;
+        }
+    }
+
+    private enum PebbleAppInstallState {
+        UNKNOWN,
+        WAIT_SLOT,
+        START_INSTALL,
+        WAIT_TOKEN,
+        UPLOAD_CHUNK,
+        UPLOAD_COMMIT,
+        WAIT_COMMIT,
+        UPLOAD_COMPLETE,
+        APP_REFRESH,
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleKitSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleKitSupport.java
new file mode 100644
index 0000000..3702181
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleKitSupport.java
@@ -0,0 +1,201 @@
+/*  Copyright (C) 2017-2024 Andreas Shimokawa, Matej Drobni膷
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.pebble;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.util.Base64;
+
+import androidx.core.content.ContextCompat;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventAppMessage;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.pebble.GBDeviceEventDataLogging;
+
+class PebbleKitSupport {
+    //private static final String PEBBLEKIT_ACTION_PEBBLE_CONNECTED = "com.getpebble.action.PEBBLE_CONNECTED";
+    //private static final String PEBBLEKIT_ACTION_PEBBLE_DISCONNECTED = "com.getpebble.action.PEBBLE_DISCONNECTED";
+    private static final String PEBBLEKIT_ACTION_APP_ACK = "com.getpebble.action.app.ACK";
+    private static final String PEBBLEKIT_ACTION_APP_NACK = "com.getpebble.action.app.NACK";
+    private static final String PEBBLEKIT_ACTION_APP_RECEIVE = "com.getpebble.action.app.RECEIVE";
+    private static final String PEBBLEKIT_ACTION_APP_RECEIVE_ACK = "com.getpebble.action.app.RECEIVE_ACK";
+    //private static final String PEBBLEKIT_ACTION_APP_RECEIVE_NACK = "com.getpebble.action.app.RECEIVE_NACK";
+    private static final String PEBBLEKIT_ACTION_APP_SEND = "com.getpebble.action.app.SEND";
+    private static final String PEBBLEKIT_ACTION_APP_START = "com.getpebble.action.app.START";
+    private static final String PEBBLEKIT_ACTION_APP_STOP = "com.getpebble.action.app.STOP";
+
+    private static final String PEBBLEKIT_EXTRA_REOPEN_LAST_APP = "com.getpebble.action.app.REOPEN_LAST_APP";
+
+    private static final String PEBBLEKIT_ACTION_DL_RECEIVE_DATA_NEW = "com.getpebble.action.dl.RECEIVE_DATA_NEW";
+    //private static final String PEBBLEKIT_ACTION_DL_RECEIVE_DATA = "com.getpebble.action.dl.RECEIVE_DATA";
+    private static final String PEBBLEKIT_ACTION_DL_ACK_DATA = "com.getpebble.action.dl.ACK_DATA";
+    //private static final String PEBBLEKIT_ACTION_DL_REQUEST_DATA = "com.getpebble.action.dl.REQUEST_DATA";
+    private static final String PEBBLEKIT_ACTION_DL_FINISH_SESSION = "com.getpebble.action.dl.FINISH_SESSION_NEW";
+
+    private static final Logger LOG = LoggerFactory.getLogger(PebbleKitSupport.class);
+
+    private final PebbleProtocol mPebbleProtocol;
+    private final Context mContext;
+    private final PebbleIoThread mPebbleIoThread;
+
+    private int dataLogTransactionId = 1;
+
+    private final BroadcastReceiver mPebbleKitReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            if (action == null) {
+                LOG.warn("got empty action from PebbleKit Intent - ignoring");
+                return;
+            }
+            LOG.info("Got action: " + action);
+            UUID uuid;
+            switch (action) {
+                case PEBBLEKIT_ACTION_APP_START:
+                case PEBBLEKIT_ACTION_APP_STOP:
+                    uuid = (UUID) intent.getSerializableExtra("uuid");
+                    if (uuid != null) {
+                        if (action.equals(PEBBLEKIT_ACTION_APP_STOP) &&
+                                intent.getBooleanExtra(PEBBLEKIT_EXTRA_REOPEN_LAST_APP, false)) {
+                            mPebbleIoThread.reopenLastApp(uuid);
+                        } else {
+                            mPebbleIoThread.write(mPebbleProtocol.encodeAppStart(uuid, action.equals(PEBBLEKIT_ACTION_APP_START)));
+                        }
+                    }
+                    break;
+                case PEBBLEKIT_ACTION_APP_SEND:
+                    int transaction_id = intent.getIntExtra("transaction_id", -1);
+                    uuid = (UUID) intent.getSerializableExtra("uuid");
+                    String jsonString = intent.getStringExtra("msg_data");
+                    LOG.info("json string: " + jsonString);
+
+                    try {
+                        JSONArray jsonArray = new JSONArray(jsonString);
+                        mPebbleIoThread.write(mPebbleProtocol.encodeApplicationMessageFromJSON(uuid, jsonArray));
+                        //  if (transaction_id >= 0 && transaction_id <= 255) {
+                        sendAppMessageAck(transaction_id);
+                        //  }
+                    } catch (JSONException e) {
+                        LOG.error("failed decoding JSON", e);
+                    }
+                    break;
+                case PEBBLEKIT_ACTION_APP_ACK:
+                    transaction_id = intent.getIntExtra("transaction_id", -1);
+                    if (!mPebbleProtocol.mAlwaysACKPebbleKit) {
+                        if (transaction_id >= 0 && transaction_id <= 255) {
+                            mPebbleIoThread.write(mPebbleProtocol.encodeApplicationMessageAck(null, (byte) transaction_id));
+                        } else {
+                            LOG.warn("illegal transaction id " + transaction_id);
+                        }
+                    }
+                    break;
+                case PEBBLEKIT_ACTION_DL_ACK_DATA:
+                    LOG.info("GOT DL DATA ACK");
+                    break;
+                default:
+                    LOG.warn("Unhandled PebbleKit action: " + action);
+                    break;
+            }
+        }
+    };
+
+    PebbleKitSupport(Context context, PebbleIoThread pebbleIoThread, PebbleProtocol pebbleProtocol) {
+        mContext = context;
+        mPebbleIoThread = pebbleIoThread;
+        mPebbleProtocol = pebbleProtocol;
+
+        IntentFilter intentFilter = new IntentFilter();
+        intentFilter.addAction(PEBBLEKIT_ACTION_APP_ACK);
+        intentFilter.addAction(PEBBLEKIT_ACTION_APP_NACK);
+        intentFilter.addAction(PEBBLEKIT_ACTION_APP_SEND);
+        intentFilter.addAction(PEBBLEKIT_ACTION_APP_START);
+        intentFilter.addAction(PEBBLEKIT_ACTION_APP_STOP);
+        intentFilter.addAction(PEBBLEKIT_ACTION_DL_ACK_DATA);
+        ContextCompat.registerReceiver(mContext, mPebbleKitReceiver, intentFilter, ContextCompat.RECEIVER_EXPORTED);
+    }
+
+    void sendAppMessageIntent(GBDeviceEventAppMessage appMessage) {
+        Intent intent = new Intent();
+        intent.setAction(PEBBLEKIT_ACTION_APP_RECEIVE);
+        intent.putExtra("uuid", appMessage.appUUID);
+        intent.putExtra("msg_data", appMessage.message);
+        intent.putExtra("transaction_id", appMessage.id);
+        LOG.info("broadcasting to uuid " + appMessage.appUUID + " transaction id: " + appMessage.id + " JSON: " + appMessage.message);
+        mContext.sendBroadcast(intent);
+    }
+
+    private void sendAppMessageAck(int transactionId) {
+        Intent intent = new Intent();
+        intent.setAction(PEBBLEKIT_ACTION_APP_RECEIVE_ACK);
+        intent.putExtra("transaction_id", transactionId);
+        LOG.info("broadcasting ACK (transaction id " + transactionId + ")");
+        mContext.sendBroadcast(intent);
+    }
+
+    void close() {
+        try {
+            mContext.unregisterReceiver(mPebbleKitReceiver);
+        } catch (IllegalArgumentException ignore) {
+        }
+    }
+
+    void sendDataLoggingIntent(GBDeviceEventDataLogging dataLogging) {
+        Intent intent = new Intent();
+        intent.putExtra("data_log_timestamp", dataLogging.timestamp);
+        intent.putExtra("uuid", dataLogging.appUUID);
+        intent.putExtra("data_log_uuid", dataLogging.appUUID); // Is that really the same?
+        intent.putExtra("data_log_tag", dataLogging.tag);
+
+        switch (dataLogging.command) {
+            case GBDeviceEventDataLogging.COMMAND_RECEIVE_DATA:
+                intent.setAction(PEBBLEKIT_ACTION_DL_RECEIVE_DATA_NEW);
+                intent.putExtra("pbl_data_type", dataLogging.pebbleDataType);
+                for (Object dataObject : dataLogging.data) {
+                    intent.putExtra("pbl_data_id", dataLogTransactionId++);
+                    switch (dataLogging.pebbleDataType) {
+                        case PebbleProtocol.TYPE_BYTEARRAY:
+                            intent.putExtra("pbl_data_object", Base64.encodeToString((byte[]) dataObject, Base64.NO_WRAP));
+                            break;
+                        case PebbleProtocol.TYPE_UINT:
+                            intent.putExtra("pbl_data_object", (Long) dataObject);
+                            break;
+                        case PebbleProtocol.TYPE_INT:
+                            intent.putExtra("pbl_data_object", (Integer) dataObject);
+                            break;
+                    }
+                    LOG.info("broadcasting datalogging to uuid " + dataLogging.appUUID + " tag: " + dataLogging.tag + "transaction id: " + dataLogTransactionId + " type: " + dataLogging.pebbleDataType);
+                    mContext.sendBroadcast(intent);
+                }
+                break;
+            case GBDeviceEventDataLogging.COMMAND_FINISH_SESSION:
+                intent.setAction(PEBBLEKIT_ACTION_DL_FINISH_SESSION);
+                LOG.info("broadcasting datalogging finish session to uuid " + dataLogging.appUUID + " tag: " + dataLogging.tag);
+                mContext.sendBroadcast(intent);
+                break;
+            default:
+                LOG.warn("invalid datalog command");
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleProtocol.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleProtocol.java
new file mode 100644
index 0000000..7de164f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleProtocol.java
@@ -0,0 +1,2723 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, Carsten Pfeiffer, Daniele
+    Gobbetti, Frank Slezak, jcrode, Johann C. Rode, Jos茅 Rebelo, Julien Pivotto,
+    Kevin Richter, Matej Drobni膷, Sergio Lopez, Steffen Liebergeld, Uwe Hermann
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.pebble;
+
+import android.util.Base64;
+import android.util.Pair;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.SimpleTimeZone;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.SettingsActivity;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventAppInfo;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventAppManagement;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventAppMessage;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventCallControl;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventMusicControl;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventNotificationControl;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventScreenshot;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventSendBytes;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.pebble.GBDeviceEventDataLogging;
+import nodomain.freeyourgadget.gadgetbridge.devices.pebble.PebbleCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.pebble.PebbleIconID;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
+import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec.Action;
+import nodomain.freeyourgadget.gadgetbridge.model.NotificationType;
+import nodomain.freeyourgadget.gadgetbridge.model.Weather;
+import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
+import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class PebbleProtocol extends GBDeviceProtocol {
+
+    private static final Logger LOG = LoggerFactory.getLogger(PebbleProtocol.class);
+
+    private static final short ENDPOINT_TIME = 11;
+    private static final short ENDPOINT_FIRMWAREVERSION = 16;
+    private static final short ENDPOINT_PHONEVERSION = 17;
+    private static final short ENDPOINT_SYSTEMMESSAGE = 18;
+    private static final short ENDPOINT_MUSICCONTROL = 32;
+    private static final short ENDPOINT_PHONECONTROL = 33;
+    static final short ENDPOINT_APPLICATIONMESSAGE = 48;
+    private static final short ENDPOINT_LAUNCHER = 49;
+    private static final short ENDPOINT_APPRUNSTATE = 52; // FW >=3.x
+    private static final short ENDPOINT_LOGS = 2000;
+    private static final short ENDPOINT_PING = 2001;
+    private static final short ENDPOINT_LOGDUMP = 2002;
+    private static final short ENDPOINT_RESET = 2003;
+    private static final short ENDPOINT_APP = 2004;
+    private static final short ENDPOINT_APPLOGS = 2006;
+    private static final short ENDPOINT_NOTIFICATION = 3000; // FW 1.x-2-x
+    private static final short ENDPOINT_EXTENSIBLENOTIFS = 3010; // FW 2.x
+    private static final short ENDPOINT_RESOURCE = 4000;
+    private static final short ENDPOINT_SYSREG = 5000;
+    private static final short ENDPOINT_FCTREG = 5001;
+    private static final short ENDPOINT_APPMANAGER = 6000;
+    private static final short ENDPOINT_APPFETCH = 6001; // FW >=3.x
+    private static final short ENDPOINT_DATALOG = 6778;
+    private static final short ENDPOINT_RUNKEEPER = 7000;
+    private static final short ENDPOINT_SCREENSHOT = 8000;
+    private static final short ENDPOINT_AUDIOSTREAM = 10000;
+    private static final short ENDPOINT_VOICECONTROL = 11000;
+    private static final short ENDPOINT_NOTIFICATIONACTION = 11440; // FW >=3.x, TODO: find a better name
+    private static final short ENDPOINT_APPREORDER = (short) 0xabcd; // FW >=3.x
+    private static final short ENDPOINT_BLOBDB = (short) 0xb1db;  // FW >=3.x
+    private static final short ENDPOINT_PUTBYTES = (short) 0xbeef;
+
+    private static final byte APPRUNSTATE_START = 1;
+    private static final byte APPRUNSTATE_STOP = 2;
+
+    private static final byte BLOBDB_INSERT = 1;
+    private static final byte BLOBDB_DELETE = 4;
+    private static final byte BLOBDB_CLEAR = 5;
+
+    private static final byte BLOBDB_PIN = 1;
+    private static final byte BLOBDB_APP = 2;
+    private static final byte BLOBDB_REMINDER = 3;
+    private static final byte BLOBDB_NOTIFICATION = 4;
+    private static final byte BLOBDB_WEATHER = 5;
+    private static final byte BLOBDB_CANNED_MESSAGES = 6;
+    private static final byte BLOBDB_PREFERENCES = 7;
+    private static final byte BLOBDB_APPSETTINGS = 9;
+    private static final byte BLOBDB_APPGLANCE = 11;
+
+    private static final byte BLOBDB_SUCCESS = 1;
+    private static final byte BLOBDB_GENERALFAILURE = 2;
+    private static final byte BLOBDB_INVALIDOPERATION = 3;
+    private static final byte BLOBDB_INVALIDDATABASEID = 4;
+    private static final byte BLOBDB_INVALIDDATA = 5;
+    private static final byte BLOBDB_KEYDOESNOTEXIST = 6;
+    private static final byte BLOBDB_DATABASEFULL = 7;
+    private static final byte BLOBDB_DATASTALE = 8;
+
+
+    private static final byte NOTIFICATION_EMAIL = 0;
+    private static final byte NOTIFICATION_SMS = 1;
+    private static final byte NOTIFICATION_TWITTER = 2;
+    private static final byte NOTIFICATION_FACEBOOK = 3;
+
+    private static final byte PHONECONTROL_ANSWER = 1;
+    private static final byte PHONECONTROL_HANGUP = 2;
+    private static final byte PHONECONTROL_GETSTATE = 3;
+    private static final byte PHONECONTROL_INCOMINGCALL = 4;
+    private static final byte PHONECONTROL_OUTGOINGCALL = 5;
+    private static final byte PHONECONTROL_MISSEDCALL = 6;
+    private static final byte PHONECONTROL_RING = 7;
+    private static final byte PHONECONTROL_START = 8;
+    private static final byte PHONECONTROL_END = 9;
+
+    private static final byte MUSICCONTROL_SETMUSICINFO = 0x10;
+    private static final byte MUSICCONTROL_SETPLAYSTATE = 0x11;
+
+    private static final byte MUSICCONTROL_PLAYPAUSE = 1;
+    private static final byte MUSICCONTROL_PAUSE = 2;
+    private static final byte MUSICCONTROL_PLAY = 3;
+    private static final byte MUSICCONTROL_NEXT = 4;
+    private static final byte MUSICCONTROL_PREVIOUS = 5;
+    private static final byte MUSICCONTROL_VOLUMEUP = 6;
+    private static final byte MUSICCONTROL_VOLUMEDOWN = 7;
+    private static final byte MUSICCONTROL_GETNOWPLAYING = 8;
+
+    private static final byte MUSICCONTROL_STATE_PAUSED = 0x00;
+    private static final byte MUSICCONTROL_STATE_PLAYING = 0x01;
+    private static final byte MUSICCONTROL_STATE_REWINDING = 0x02;
+    private static final byte MUSICCONTROL_STATE_FASTWORWARDING = 0x03;
+    private static final byte MUSICCONTROL_STATE_UNKNOWN = 0x04;
+
+    private static final byte NOTIFICATIONACTION_ACK = 0;
+    private static final byte NOTIFICATIONACTION_NACK = 1;
+    private static final byte NOTIFICATIONACTION_INVOKE = 0x02;
+    private static final byte NOTIFICATIONACTION_RESPONSE = 0x11;
+
+    private static final byte TIME_GETTIME = 0;
+    private static final byte TIME_SETTIME = 2;
+    private static final byte TIME_SETTIME_UTC = 3;
+
+    private static final byte FIRMWAREVERSION_GETVERSION = 0;
+
+    private static final byte APPMANAGER_GETAPPBANKSTATUS = 1;
+    private static final byte APPMANAGER_REMOVEAPP = 2;
+    private static final byte APPMANAGER_REFRESHAPP = 3;
+    private static final byte APPMANAGER_GETUUIDS = 5;
+
+    private static final int APPMANAGER_RES_SUCCESS = 1;
+
+    private static final byte APPLICATIONMESSAGE_PUSH = 1;
+    private static final byte APPLICATIONMESSAGE_REQUEST = 2;
+    private static final byte APPLICATIONMESSAGE_ACK = (byte) 0xff;
+    private static final byte APPLICATIONMESSAGE_NACK = (byte) 0x7f;
+
+    private static final byte DATALOG_OPENSESSION = 0x01;
+    private static final byte DATALOG_SENDDATA = 0x02;
+    private static final byte DATALOG_CLOSE = 0x03;
+    private static final byte DATALOG_TIMEOUT = 0x07;
+    private static final byte DATALOG_REPORTSESSIONS = (byte) 0x84;
+    private static final byte DATALOG_ACK = (byte) 0x85;
+    private static final byte DATALOG_NACK = (byte) 0x86;
+
+    private static final byte PING_PING = 0;
+    private static final byte PING_PONG = 1;
+
+    private static final byte PUTBYTES_INIT = 1;
+    private static final byte PUTBYTES_SEND = 2;
+    private static final byte PUTBYTES_COMMIT = 3;
+    private static final byte PUTBYTES_ABORT = 4;
+    private static final byte PUTBYTES_COMPLETE = 5;
+
+    public static final byte PUTBYTES_TYPE_FIRMWARE = 1;
+    public static final byte PUTBYTES_TYPE_RECOVERY = 2;
+    public static final byte PUTBYTES_TYPE_SYSRESOURCES = 3;
+    public static final byte PUTBYTES_TYPE_RESOURCES = 4;
+    public static final byte PUTBYTES_TYPE_BINARY = 5;
+    public static final byte PUTBYTES_TYPE_FILE = 6;
+    public static final byte PUTBYTES_TYPE_WORKER = 7;
+
+    private static final byte RESET_REBOOT = 0;
+
+    private static final byte SCREENSHOT_TAKE = 0;
+
+    private static final byte SYSTEMMESSAGE_NEWFIRMWAREAVAILABLE = 0;
+    private static final byte SYSTEMMESSAGE_FIRMWARESTART = 1;
+    private static final byte SYSTEMMESSAGE_FIRMWARECOMPLETE = 2;
+    private static final byte SYSTEMMESSAGE_FIRMWAREFAIL = 3;
+    private static final byte SYSTEMMESSAGE_FIRMWARE_UPTODATE = 4;
+    private static final byte SYSTEMMESSAGE_FIRMWARE_OUTOFDATE = 5;
+    private static final byte SYSTEMMESSAGE_STOPRECONNECTING = 6;
+    private static final byte SYSTEMMESSAGE_STARTRECONNECTING = 7;
+
+    private static final byte PHONEVERSION_REQUEST = 0;
+    private static final byte PHONEVERSION_APPVERSION_MAGIC = 2; // increase this if pebble complains
+    private static final byte PHONEVERSION_APPVERSION_MAJOR = 2;
+    private static final byte PHONEVERSION_APPVERSION_MINOR = 3;
+    private static final byte PHONEVERSION_APPVERSION_PATCH = 0;
+
+
+    private static final int PHONEVERSION_SESSION_CAPS_GAMMARAY = 0x80000000;
+
+    private static final int PHONEVERSION_REMOTE_CAPS_TELEPHONY = 0x00000010;
+    private static final int PHONEVERSION_REMOTE_CAPS_SMS = 0x00000020;
+    private static final int PHONEVERSION_REMOTE_CAPS_GPS = 0x00000040;
+    private static final int PHONEVERSION_REMOTE_CAPS_BTLE = 0x00000080;
+    private static final int PHONEVERSION_REMOTE_CAPS_REARCAMERA = 0x00000100;
+    private static final int PHONEVERSION_REMOTE_CAPS_ACCEL = 0x00000200;
+    private static final int PHONEVERSION_REMOTE_CAPS_GYRO = 0x00000400;
+    private static final int PHONEVERSION_REMOTE_CAPS_COMPASS = 0x00000800;
+
+    private static final byte PHONEVERSION_REMOTE_OS_UNKNOWN = 0;
+    private static final byte PHONEVERSION_REMOTE_OS_IOS = 1;
+    private static final byte PHONEVERSION_REMOTE_OS_ANDROID = 2;
+    private static final byte PHONEVERSION_REMOTE_OS_OSX = 3;
+    private static final byte PHONEVERSION_REMOTE_OS_LINUX = 4;
+    private static final byte PHONEVERSION_REMOTE_OS_WINDOWS = 5;
+
+    static final byte TYPE_BYTEARRAY = 0;
+    private static final byte TYPE_CSTRING = 1;
+    static final byte TYPE_UINT = 2;
+    static final byte TYPE_INT = 3;
+
+    private final short LENGTH_PREFIX = 4;
+
+    private static final byte LENGTH_UUID = 16;
+
+    private static final long GB_UUID_MASK = 0x4767744272646700L;
+
+    // base is -8
+    private static final String[] hwRevisions = {
+            // Emulator
+            "silk_bb2", "robert_bb", "silk_bb",
+            "spalding_bb2", "snowy_bb2", "snowy_bb",
+            "bb2", "bb",
+            "unknown",
+            // Pebble Classic Series
+            "ev1", "ev2", "ev2_3", "ev2_4", "v1_5", "v2_0",
+            // Pebble Time Series
+            "snowy_evt2", "snowy_dvt", "spalding_dvt", "snowy_s3", "spalding",
+            // Pebble 2 Series
+            "silk_evt", "robert_evt", "silk"
+    };
+
+    private static final Random mRandom = new Random();
+
+    int mFwMajor = 3;
+    boolean mEnablePebbleKit = false;
+    boolean mAlwaysACKPebbleKit = false;
+    private boolean mForceProtocol = false;
+
+    private byte[] screenshotData = null;
+    private int screenshotWidth;
+    private int screenshotHeight;
+    private byte screenshotBpp;
+    private byte[] screenshotClut;
+    private int mScreenshotRemaining = -1;
+
+    //monochrome black + white
+    private static final byte[] clut_pebble = {
+            0x00, 0x00, 0x00, 0x00,
+            (byte) 0xff, (byte) 0xff, (byte) 0xff, 0x00
+    };
+
+    // linear BGR222 (6 bit, 64 entries)
+    private static final byte[] clut_pebbletime = new byte[]{
+            0x00, 0x00, 0x00, 0x00,
+            0x55, 0x00, 0x00, 0x00,
+            (byte) 0xaa, 0x00, 0x00, 0x00,
+            (byte) 0xff, 0x00, 0x00, 0x00,
+
+            0x00, 0x55, 0x00, 0x00,
+            0x55, 0x55, 0x00, 0x00,
+            (byte) 0xaa, 0x55, 0x00, 0x00,
+            (byte) 0xff, 0x55, 0x00, 0x00,
+
+            0x00, (byte) 0xaa, 0x00, 0x00,
+            0x55, (byte) 0xaa, 0x00, 0x00,
+            (byte) 0xaa, (byte) 0xaa, 0x00, 0x00,
+            (byte) 0xff, (byte) 0xaa, 0x00, 0x00,
+
+            0x00, (byte) 0xff, 0x00, 0x00,
+            0x55, (byte) 0xff, 0x00, 0x00,
+            (byte) 0xaa, (byte) 0xff, 0x00, 0x00,
+            (byte) 0xff, (byte) 0xff, 0x00, 0x00,
+
+            0x00, 0x00, 0x55, 0x00,
+            0x55, 0x00, 0x55, 0x00,
+            (byte) 0xaa, 0x00, 0x55, 0x00,
+            (byte) 0xff, 0x00, 0x55, 0x00,
+
+            0x00, 0x55, 0x55, 0x00,
+            0x55, 0x55, 0x55, 0x00,
+            (byte) 0xaa, 0x55, 0x55, 0x00,
+            (byte) 0xff, 0x55, 0x55, 0x00,
+
+            0x00, (byte) 0xaa, 0x55, 0x00,
+            0x55, (byte) 0xaa, 0x55, 0x00,
+            (byte) 0xaa, (byte) 0xaa, 0x55, 0x00,
+            (byte) 0xff, (byte) 0xaa, 0x55, 0x00,
+
+            0x00, (byte) 0xff, 0x55, 0x00,
+            0x55, (byte) 0xff, 0x55, 0x00,
+            (byte) 0xaa, (byte) 0xff, 0x55, 0x00,
+            (byte) 0xff, (byte) 0xff, 0x55, 0x00,
+
+            0x00, 0x00, (byte) 0xaa, 0x00,
+            0x55, 0x00, (byte) 0xaa, 0x00,
+            (byte) 0xaa, 0x00, (byte) 0xaa, 0x00,
+            (byte) 0xff, 0x00, (byte) 0xaa, 0x00,
+
+            0x00, 0x55, (byte) 0xaa, 0x00,
+            0x55, 0x55, (byte) 0xaa, 0x00,
+            (byte) 0xaa, 0x55, (byte) 0xaa, 0x00,
+            (byte) 0xff, 0x55, (byte) 0xaa, 0x00,
+
+            0x00, (byte) 0xaa, (byte) 0xaa, 0x00,
+            0x55, (byte) 0xaa, (byte) 0xaa, 0x00,
+            (byte) 0xaa, (byte) 0xaa, (byte) 0xaa, 0x00,
+            (byte) 0xff, (byte) 0xaa, (byte) 0xaa, 0x00,
+
+            0x00, (byte) 0xff, (byte) 0xaa, 0x00,
+            0x55, (byte) 0xff, (byte) 0xaa, 0x00,
+            (byte) 0xaa, (byte) 0xff, (byte) 0xaa, 0x00,
+            (byte) 0xff, (byte) 0xff, (byte) 0xaa, 0x00,
+
+            0x00, 0x00, (byte) 0xff, 0x00,
+            0x55, 0x00, (byte) 0xff, 0x00,
+            (byte) 0xaa, 0x00, (byte) 0xff, 0x00,
+            (byte) 0xff, 0x00, (byte) 0xff, 0x00,
+
+            0x00, 0x55, (byte) 0xff, 0x00,
+            0x55, 0x55, (byte) 0xff, 0x00,
+            (byte) 0xaa, 0x55, (byte) 0xff, 0x00,
+            (byte) 0xff, 0x55, (byte) 0xff, 0x00,
+
+            0x00, (byte) 0xaa, (byte) 0xff, 0x00,
+            0x55, (byte) 0xaa, (byte) 0xff, 0x00,
+            (byte) 0xaa, (byte) 0xaa, (byte) 0xff, 0x00,
+            (byte) 0xff, (byte) 0xaa, (byte) 0xff, 0x00,
+
+            0x00, (byte) 0xff, (byte) 0xff, 0x00,
+            0x55, (byte) 0xff, (byte) 0xff, 0x00,
+            (byte) 0xaa, (byte) 0xff, (byte) 0xff, 0x00,
+            (byte) 0xff, (byte) 0xff, (byte) 0xff, 0x00,
+
+    };
+
+
+    byte last_id = -1;
+    private final ArrayList<UUID> tmpUUIDS = new ArrayList<>();
+
+    public static final UUID UUID_PEBBLE_HEALTH = UUID.fromString("36d8c6ed-4c83-4fa1-a9e2-8f12dc941f8c"); // FIXME: store somewhere else, this is also accessed by other code
+    public static final UUID UUID_WORKOUT = UUID.fromString("fef82c82-7176-4e22-88de-35a3fc18d43f"); // FIXME: store somewhere else, this is also accessed by other code
+    public static final UUID UUID_WEATHER = UUID.fromString("61b22bc8-1e29-460d-a236-3fe409a439ff"); // FIXME: store somewhere else, this is also accessed by other code
+    public static final UUID UUID_NOTIFICATIONS = UUID.fromString("b2cae818-10f8-46df-ad2b-98ad2254a3c1");
+
+    private static final UUID UUID_GBPEBBLE = UUID.fromString("61476764-7465-7262-6469-656775527a6c");
+    private static final UUID UUID_MORPHEUZ = UUID.fromString("5be44f1d-d262-4ea6-aa30-ddbec1e3cab2");
+    private static final UUID UUID_MISFIT = UUID.fromString("0b73b76a-cd65-4dc2-9585-aaa213320858");
+    private static final UUID UUID_PEBBLE_TIMESTYLE = UUID.fromString("4368ffa4-f0fb-4823-90be-f754b076bdaa");
+    private static final UUID UUID_PEBSTYLE = UUID.fromString("da05e84d-e2a2-4020-a2dc-9cdcf265fcdd");
+    private static final UUID UUID_MARIOTIME = UUID.fromString("43caa750-2896-4f46-94dc-1adbd4bc1ff3");
+    private static final UUID UUID_HELTHIFY = UUID.fromString("7ee97b2c-95e8-4720-b94e-70fccd905d98");
+    private static final UUID UUID_TREKVOLLE = UUID.fromString("2da02267-7a19-4e49-9ed1-439d25db14e4");
+    private static final UUID UUID_SQUARE = UUID.fromString("cb332373-4ee5-4c5c-8912-4f62af2d756c");
+    private static final UUID UUID_ZALEWSZCZAK_CROWEX = UUID.fromString("a88b3151-2426-43c6-b1d0-9b288b3ec47e");
+    private static final UUID UUID_ZALEWSZCZAK_FANCY = UUID.fromString("014e17bf-5878-4781-8be1-8ef998cee1ba");
+    private static final UUID UUID_ZALEWSZCZAK_TALLY = UUID.fromString("abb51965-52e2-440a-b93c-843eeacb697d");
+    private static final UUID UUID_OBSIDIAN = UUID.fromString("ef42caba-0c65-4879-ab23-edd2bde68824");
+    private static final UUID UUID_SIMPLY_LIGHT = UUID.fromString("04a6e68a-42d6-4738-87b2-1c80a994dee4");
+    private static final UUID UUID_M7S = UUID.fromString("03adc57a-569b-4669-9a80-b505eaea314d");
+    private static final UUID UUID_YWEATHER = UUID.fromString("35a28a4d-0c9f-408f-9c6d-551e65f03186");
+    private static final UUID UUID_REALWEATHER = UUID.fromString("1f0b0701-cc8f-47ec-86e7-7181397f9a52");
+
+    private static final UUID UUID_ZERO = new UUID(0, 0);
+
+    private static final UUID UUID_LOCATION = UUID.fromString("2c7e6a86-51e5-4ddd-b606-db43d1e4ad28"); // might be the location of "Berlin" or "Auto"
+
+    private final Map<UUID, AppMessageHandler> mAppMessageHandlers = new HashMap<>();
+
+    private UUID currentRunningApp = UUID_ZERO;
+
+    public PebbleProtocol(GBDevice device) {
+        super(device);
+        mAppMessageHandlers.put(UUID_MORPHEUZ, new AppMessageHandlerMorpheuz(UUID_MORPHEUZ, PebbleProtocol.this));
+        mAppMessageHandlers.put(UUID_MISFIT, new AppMessageHandlerMisfit(UUID_MISFIT, PebbleProtocol.this));
+        if (!((PebbleCoordinator) device.getDeviceCoordinator()).isBackgroundJsEnabled(device)) {
+            mAppMessageHandlers.put(UUID_PEBBLE_TIMESTYLE, new AppMessageHandlerTimeStylePebble(UUID_PEBBLE_TIMESTYLE, PebbleProtocol.this));
+            mAppMessageHandlers.put(UUID_PEBSTYLE, new AppMessageHandlerPebStyle(UUID_PEBSTYLE, PebbleProtocol.this));
+            mAppMessageHandlers.put(UUID_MARIOTIME, new AppMessageHandlerMarioTime(UUID_MARIOTIME, PebbleProtocol.this));
+            mAppMessageHandlers.put(UUID_HELTHIFY, new AppMessageHandlerHealthify(UUID_HELTHIFY, PebbleProtocol.this));
+            mAppMessageHandlers.put(UUID_TREKVOLLE, new AppMessageHandlerTrekVolle(UUID_TREKVOLLE, PebbleProtocol.this));
+            mAppMessageHandlers.put(UUID_SQUARE, new AppMessageHandlerSquare(UUID_SQUARE, PebbleProtocol.this));
+            mAppMessageHandlers.put(UUID_ZALEWSZCZAK_CROWEX, new AppMessageHandlerZalewszczak(UUID_ZALEWSZCZAK_CROWEX, PebbleProtocol.this));
+            mAppMessageHandlers.put(UUID_ZALEWSZCZAK_FANCY, new AppMessageHandlerZalewszczak(UUID_ZALEWSZCZAK_FANCY, PebbleProtocol.this));
+            mAppMessageHandlers.put(UUID_ZALEWSZCZAK_TALLY, new AppMessageHandlerZalewszczak(UUID_ZALEWSZCZAK_TALLY, PebbleProtocol.this));
+            mAppMessageHandlers.put(UUID_OBSIDIAN, new AppMessageHandlerObsidian(UUID_OBSIDIAN, PebbleProtocol.this));
+            mAppMessageHandlers.put(UUID_GBPEBBLE, new AppMessageHandlerGBPebble(UUID_GBPEBBLE, PebbleProtocol.this));
+            mAppMessageHandlers.put(UUID_SIMPLY_LIGHT, new AppMessageHandlerSimplyLight(UUID_SIMPLY_LIGHT, PebbleProtocol.this));
+            mAppMessageHandlers.put(UUID_M7S, new AppMessageHandlerM7S(UUID_M7S, PebbleProtocol.this));
+            mAppMessageHandlers.put(UUID_YWEATHER, new AppMessageHandlerRealWeather(UUID_YWEATHER, PebbleProtocol.this));
+            mAppMessageHandlers.put(UUID_REALWEATHER, new AppMessageHandlerRealWeather(UUID_REALWEATHER, PebbleProtocol.this));
+        }
+    }
+
+    private final HashMap<Byte, DatalogSession> mDatalogSessions = new HashMap<>();
+
+    private Integer[] idLookup = new Integer[256];
+
+    private byte[] encodeSimpleMessage(short endpoint, byte command) {
+        final short LENGTH_SIMPLEMESSAGE = 1;
+        ByteBuffer buf = ByteBuffer.allocate(LENGTH_PREFIX + LENGTH_SIMPLEMESSAGE);
+        buf.order(ByteOrder.BIG_ENDIAN);
+        buf.putShort(LENGTH_SIMPLEMESSAGE);
+        buf.putShort(endpoint);
+        buf.put(command);
+
+        return buf.array();
+    }
+
+    private byte[] encodeMessage(short endpoint, byte type, int cookie, String[] parts) {
+        // Calculate length first
+        int length = LENGTH_PREFIX + 1;
+        if (parts != null) {
+            for (String s : parts) {
+                if (s == null || s.equals("")) {
+                    length++; // encode null or empty strings as 0x00 later
+                    continue;
+                }
+                length += (1 + s.getBytes().length);
+            }
+        }
+        if (endpoint == ENDPOINT_PHONECONTROL) {
+            length += 4; //for cookie;
+        }
+
+        // Encode Prefix
+        ByteBuffer buf = ByteBuffer.allocate(length);
+        buf.order(ByteOrder.BIG_ENDIAN);
+        buf.putShort((short) (length - LENGTH_PREFIX));
+        buf.putShort(endpoint);
+        buf.put(type);
+
+        if (endpoint == ENDPOINT_PHONECONTROL) {
+            buf.putInt(cookie);
+        }
+        // Encode Pascal-Style Strings
+        if (parts != null) {
+            for (String s : parts) {
+                if (s == null || s.equals("")) {
+                    buf.put((byte) 0x00);
+                    continue;
+                }
+
+                int partlength = s.getBytes().length;
+                if (partlength > 255) partlength = 255;
+                buf.put((byte) partlength);
+                buf.put(s.getBytes(), 0, partlength);
+            }
+        }
+        return buf.array();
+    }
+
+    @Override
+    public byte[] encodeNotification(NotificationSpec notificationSpec) {
+        int id = notificationSpec.getId() != -1 ? notificationSpec.getId() : mRandom.nextInt();
+        String title;
+        String subtitle = null;
+
+        // for SMS that came in though the SMS receiver
+        if (notificationSpec.sender != null) {
+            title = notificationSpec.sender;
+            subtitle = notificationSpec.subject;
+        } else {
+            title = notificationSpec.title;
+        }
+
+        long ts = System.currentTimeMillis();
+        if (mFwMajor < 3) {
+            ts += (SimpleTimeZone.getDefault().getOffset(ts));
+        }
+        ts /= 1000;
+
+        if (mFwMajor >= 3 || mForceProtocol || notificationSpec.type != NotificationType.GENERIC_EMAIL) {
+            // 3.x notification
+            return encodeNotification(id, (int) (ts & 0xffffffffL), title, subtitle, notificationSpec.body,
+                    notificationSpec.type, notificationSpec.pebbleColor,
+                    notificationSpec.cannedReplies, notificationSpec.attachedActions);
+        } else {
+            // 1.x notification on FW 2.X
+            String[] parts = {title, notificationSpec.body, String.valueOf(ts), subtitle};
+            // be aware that type is at this point always NOTIFICATION_EMAIL
+            return encodeMessage(ENDPOINT_NOTIFICATION, NOTIFICATION_EMAIL, 0, parts);
+        }
+    }
+
+    @Override
+    public byte[] encodeDeleteNotification(int id) {
+        return encodeBlobdb(new UUID(GB_UUID_MASK, id), BLOBDB_DELETE, BLOBDB_NOTIFICATION, null);
+    }
+
+    @Override
+    public byte[] encodeAddCalendarEvent(CalendarEventSpec calendarEventSpec) {
+        long id = calendarEventSpec.id != -1 ? calendarEventSpec.id : mRandom.nextLong();
+        int iconId;
+        ArrayList<Pair<Integer, Object>> attributes = new ArrayList<>();
+        attributes.add(new Pair<>(1, (Object) calendarEventSpec.title));
+        switch (calendarEventSpec.type) {
+            case CalendarEventSpec.TYPE_SUNRISE:
+                iconId = PebbleIconID.SUNRISE;
+                break;
+            case CalendarEventSpec.TYPE_SUNSET:
+                iconId = PebbleIconID.SUNSET;
+                break;
+            default:
+                iconId = PebbleIconID.TIMELINE_CALENDAR;
+                attributes.add(new Pair<>(3, (Object) calendarEventSpec.description));
+                attributes.add(new Pair<>(11, (Object) calendarEventSpec.location));
+        }
+
+        return encodeTimelinePin(new UUID(GB_UUID_MASK | calendarEventSpec.type, id), calendarEventSpec.timestamp, (short) (calendarEventSpec.durationInSeconds / 60), iconId, attributes);
+    }
+
+    @Override
+    public byte[] encodeDeleteCalendarEvent(byte type, long id) {
+        return encodeBlobdb(new UUID(GB_UUID_MASK | type, id), BLOBDB_DELETE, BLOBDB_PIN, null);
+    }
+
+    @Override
+    public byte[] encodeSetTime() {
+        final short LENGTH_SETTIME = 5;
+        long ts = System.currentTimeMillis();
+        long ts_offset = (SimpleTimeZone.getDefault().getOffset(ts));
+        ByteBuffer buf;
+        if (mFwMajor >= 3) {
+            String timezone = SimpleTimeZone.getDefault().getID();
+            short length = (short) (LENGTH_SETTIME + timezone.getBytes().length + 3);
+            buf = ByteBuffer.allocate(LENGTH_PREFIX + length);
+            buf.order(ByteOrder.BIG_ENDIAN);
+            buf.putShort(length);
+            buf.putShort(ENDPOINT_TIME);
+            buf.put(TIME_SETTIME_UTC);
+            buf.putInt((int) (ts / 1000));
+            buf.putShort((short) (ts_offset / 60000));
+            buf.put((byte) timezone.getBytes().length);
+            buf.put(timezone.getBytes());
+            LOG.info(timezone);
+        } else {
+            buf = ByteBuffer.allocate(LENGTH_PREFIX + LENGTH_SETTIME);
+            buf.order(ByteOrder.BIG_ENDIAN);
+            buf.putShort(LENGTH_SETTIME);
+            buf.putShort(ENDPOINT_TIME);
+            buf.put(TIME_SETTIME);
+            buf.putInt((int) ((ts + ts_offset) / 1000));
+        }
+        return buf.array();
+    }
+
+    @Override
+    public byte[] encodeFindDevice(boolean start) {
+        return encodeSetCallState("Where are you?", "Gadgetbridge", start ? CallSpec.CALL_INCOMING : CallSpec.CALL_END);
+        /*
+        int ts = (int) (System.currentTimeMillis() / 1000);
+
+        if (start) {
+            //return encodeWeatherPin(ts, "Weather", "1掳/-1掳", "Gadgetbridge is Sunny", "Berlin", 37);
+        }
+        */
+    }
+
+    private byte[] encodeBlobdb(Object key, byte command, byte db, byte[] blob) {
+
+        int length = 5;
+
+        int key_length;
+        if (key instanceof UUID) {
+            key_length = LENGTH_UUID;
+        } else if (key instanceof String) {
+            key_length = ((String) key).getBytes().length;
+        } else {
+            LOG.warn("unknown key type");
+            return null;
+        }
+        if (key_length > 255) {
+            LOG.warn("key is too long");
+            return null;
+        }
+        length += key_length;
+
+        if (blob != null) {
+            length += blob.length + 2;
+        }
+
+        ByteBuffer buf = ByteBuffer.allocate(LENGTH_PREFIX + length);
+
+        buf.order(ByteOrder.BIG_ENDIAN);
+        buf.putShort((short) length);
+        buf.putShort(ENDPOINT_BLOBDB);
+
+        buf.order(ByteOrder.LITTLE_ENDIAN);
+        buf.put(command);
+        buf.putShort((short) mRandom.nextInt()); // token
+        buf.put(db);
+
+        buf.put((byte) key_length);
+        if (key instanceof UUID) {
+            UUID uuid = (UUID) key;
+            buf.order(ByteOrder.BIG_ENDIAN);
+            buf.putLong(uuid.getMostSignificantBits());
+            buf.putLong(uuid.getLeastSignificantBits());
+            buf.order(ByteOrder.LITTLE_ENDIAN);
+        } else {
+            buf.put(((String) key).getBytes());
+        }
+
+        if (blob != null) {
+            buf.putShort((short) blob.length);
+            buf.put(blob);
+        }
+
+        return buf.array();
+    }
+
+    byte[] encodeActivateHealth(boolean activate) {
+        byte[] blob;
+        if (activate) {
+
+            ByteBuffer buf = ByteBuffer.allocate(9);
+            buf.order(ByteOrder.LITTLE_ENDIAN);
+
+            ActivityUser activityUser = new ActivityUser();
+            int heightMm = activityUser.getHeightCm() * 10;
+            buf.putShort((short) heightMm);
+            int weigthDag = activityUser.getWeightKg() * 100;
+            buf.putShort((short) weigthDag);
+            buf.put((byte) 0x01); //activate tracking
+            buf.put((byte) 0x00); //activity Insights
+            buf.put((byte) 0x00); //sleep Insights
+            buf.put((byte) activityUser.getAge());
+            buf.put((byte) activityUser.getGender());
+            blob = buf.array();
+        } else {
+            blob = new byte[]{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+        }
+        return encodeBlobdb("activityPreferences", BLOBDB_INSERT, BLOBDB_PREFERENCES, blob);
+    }
+
+    byte[] encodeSetSaneDistanceUnit(boolean sane) {
+        byte value;
+        if (sane) {
+            value = 0x00;
+        } else {
+            value = 0x01;
+        }
+        return encodeBlobdb("unitsDistance", BLOBDB_INSERT, BLOBDB_PREFERENCES, new byte[]{value});
+    }
+
+
+    byte[] encodeActivateHRM(boolean activate) {
+        return encodeBlobdb("hrmPreferences", BLOBDB_INSERT, BLOBDB_PREFERENCES,
+                activate ? new byte[]{0x01} : new byte[]{0x00});
+    }
+
+    byte[] encodeActivateWeather(boolean activate) {
+        if (activate) {
+            ByteBuffer buf = ByteBuffer.allocate(0x61);
+            buf.put((byte) 1);
+            buf.order(ByteOrder.BIG_ENDIAN);
+            buf.putLong(UUID_LOCATION.getMostSignificantBits());
+            buf.putLong(UUID_LOCATION.getLeastSignificantBits());
+            // disable remaining 5 possible location
+            buf.put(new byte[60 - LENGTH_UUID]);
+            return encodeBlobdb("weatherApp", BLOBDB_INSERT, BLOBDB_APPSETTINGS, buf.array());
+        } else {
+            return encodeBlobdb("weatherApp", BLOBDB_DELETE, BLOBDB_APPSETTINGS, null);
+        }
+    }
+
+    byte[] encodeReportDataLogSessions() {
+        return encodeSimpleMessage(ENDPOINT_DATALOG, DATALOG_REPORTSESSIONS);
+    }
+
+    private byte[] encodeBlobDBClear(byte database) {
+        final short LENGTH_BLOBDB_CLEAR = 4;
+        ByteBuffer buf = ByteBuffer.allocate(LENGTH_PREFIX + LENGTH_BLOBDB_CLEAR);
+
+        buf.order(ByteOrder.BIG_ENDIAN);
+        buf.putShort(LENGTH_BLOBDB_CLEAR);
+        buf.putShort(ENDPOINT_BLOBDB);
+
+        buf.order(ByteOrder.LITTLE_ENDIAN);
+        buf.put(BLOBDB_CLEAR);
+        buf.putShort((short) mRandom.nextInt()); // token
+        buf.put(database);
+
+        return buf.array();
+    }
+
+    private byte[] encodeTimelinePin(UUID uuid, int timestamp, short duration, int icon_id, List<Pair<Integer, Object>> attributes) {
+        final short TIMELINE_PIN_LENGTH = 46;
+
+        //FIXME: dont depend layout on icon :P
+        byte layout_id = 0x01;
+        if (icon_id == PebbleIconID.TIMELINE_CALENDAR) {
+            layout_id = 0x02;
+        }
+        icon_id |= 0x80000000;
+        byte attributes_count = 1;
+        byte actions_count = 0;
+
+        int attributes_length = 7;
+        for (Pair<Integer, Object> pair : attributes) {
+            if (pair.first == null || pair.second == null)
+                continue;
+            attributes_count++;
+            if (pair.second instanceof Integer) {
+                attributes_length += 7;
+            } else if (pair.second instanceof Byte) {
+                attributes_length += 4;
+            } else if (pair.second instanceof String) {
+                attributes_length += ((String) pair.second).getBytes().length + 3;
+            } else if (pair.second instanceof byte[]) {
+                attributes_length += ((byte[]) pair.second).length + 3;
+            } else {
+                LOG.warn("unsupported type for timeline attributes: " + pair.second.getClass().toString());
+            }
+        }
+
+        int pin_length = TIMELINE_PIN_LENGTH + attributes_length;
+        ByteBuffer buf = ByteBuffer.allocate(pin_length);
+
+        // pin - 46 bytes
+        buf.order(ByteOrder.BIG_ENDIAN);
+        buf.putLong(uuid.getMostSignificantBits());
+        buf.putLong(uuid.getLeastSignificantBits());
+        buf.putLong(0); // parent
+        buf.putLong(0);
+        buf.order(ByteOrder.LITTLE_ENDIAN);
+        buf.putInt(timestamp); // 32-bit timestamp
+        buf.putShort(duration);
+        buf.put((byte) 0x02); // type (0x02 = pin)
+        buf.putShort((short) 0x0001); // flags 0x0001 = ?
+        buf.put(layout_id); // layout was (0x02 = pin?), 0x01 needed for subtitle but seems to do no harm if there isn't one
+        buf.putShort((short) attributes_length); // total length of all attributes and actions in bytes
+        buf.put(attributes_count);
+        buf.put(actions_count);
+
+        buf.put((byte) 4); // icon
+        buf.putShort((short) 4); // length of int
+        buf.putInt(icon_id);
+
+        for (Pair<Integer, Object> pair : attributes) {
+            if (pair.first == null || pair.second == null)
+                continue;
+            buf.put(pair.first.byteValue());
+            if (pair.second instanceof Integer) {
+                buf.putShort((short) 4);
+                buf.putInt(((Integer) pair.second));
+            } else if (pair.second instanceof Byte) {
+                buf.putShort((short) 1);
+                buf.put((Byte) pair.second);
+            } else if (pair.second instanceof String) {
+                buf.putShort((short) ((String) pair.second).getBytes().length);
+                buf.put(((String) pair.second).getBytes());
+            } else if (pair.second instanceof byte[]) {
+                buf.putShort((short) ((byte[]) pair.second).length);
+                buf.put((byte[]) pair.second);
+            }
+        }
+
+        return encodeBlobdb(uuid, BLOBDB_INSERT, BLOBDB_PIN, buf.array());
+    }
+
+    private byte[] encodeNotification(int id, int timestamp, String title, String subtitle, String body,
+                                      NotificationType notificationType, byte backgroundColor, String[] cannedReplies, ArrayList<Action> attachedActions) {
+        final short NOTIFICATION_PIN_LENGTH = 46;
+        final short ACTION_LENGTH_MIN = 6;
+
+        String[] parts = {title, subtitle, body};
+
+        if(notificationType == null) {
+            notificationType = NotificationType.UNKNOWN;
+        }
+
+        int icon_id = notificationType.icon;
+
+        // Calculate length first
+        int actions_count = 0;
+        short actions_length = 0;
+
+        int replies_length = 0;
+        if (cannedReplies != null && cannedReplies.length > 0) {
+            //do not increment actions_count! reply is an action and was already added above
+            for (String reply : cannedReplies) {
+                replies_length += reply.getBytes().length + 1;
+            }
+            replies_length--;
+            //similarly, only the replies length has to be added, the length for the bare action was already added above
+
+        }
+
+        if (attachedActions != null && attachedActions.size() > 0) {
+            for (Action act : attachedActions) {
+                actions_count++;
+                actions_length += (short) (ACTION_LENGTH_MIN + act.title.getBytes().length);
+                if (act.type == Action.TYPE_WEARABLE_REPLY || act.type == Action.TYPE_SYNTECTIC_REPLY_PHONENR) {
+                    actions_length += (short) replies_length + 3;  // 3 = attribute id (byte) + length(short)
+                }
+            }
+        }
+
+        byte attributes_count = 0;
+        short attributes_length = actions_length;
+        if (mFwMajor >= 3) {
+            attributes_count += 2; // icon
+            attributes_length += 11;
+        }
+
+        for (String s : parts) {
+            if (s == null || s.equals("")) {
+                continue;
+            }
+            attributes_count++;
+            attributes_length += (short) (3 + s.getBytes().length);
+        }
+
+        short length;
+        int max_partlength;
+        byte dismiss_action_type;
+        ByteBuffer buf;
+        if (mFwMajor >= 3) {
+            length = (short) (NOTIFICATION_PIN_LENGTH + attributes_length);
+            max_partlength = 512;
+            dismiss_action_type = 0x02; // generic action, dismiss did not do anything
+            buf = ByteBuffer.allocate(length);
+        } else {
+            length = (short) (21 + attributes_length);
+            max_partlength = 256;
+            dismiss_action_type = 0x04; // dismiss
+            buf = ByteBuffer.allocate(length + LENGTH_PREFIX);
+        }
+
+        buf.order(ByteOrder.BIG_ENDIAN);
+
+        if (mFwMajor >= 3) {
+            // pin - 46 bytes
+            buf.putLong(GB_UUID_MASK);
+            buf.putLong(id);
+            buf.putLong(UUID_NOTIFICATIONS.getMostSignificantBits());
+            buf.putLong(UUID_NOTIFICATIONS.getLeastSignificantBits());
+            buf.order(ByteOrder.LITTLE_ENDIAN);
+            buf.putInt(timestamp); // 32-bit timestamp
+            buf.putShort((short) 0); // duration
+            buf.put((byte) 0x01); // type (0x01 = notification)
+            buf.putShort((short) 0x0001); // flags 0x0001 = ?
+            buf.put((byte) 0x04); // layout (0x04 = notification?)
+            buf.putShort(attributes_length); // total length of all attributes and actions in bytes
+        } else {
+            buf.putShort(length);
+            buf.putShort(ENDPOINT_EXTENSIBLENOTIFS);
+
+            buf.order(ByteOrder.LITTLE_ENDIAN); // !
+
+            buf.put((byte) 0x00); // ?
+            buf.put((byte) 0x01); // add notifications
+            buf.putInt(0x00000000); // flags - ?
+            buf.putInt(id);
+            buf.putInt(0x00000000); // ANCS id
+            buf.putInt(timestamp);
+            buf.put((byte) 0x01); // layout - ?
+
+        }
+        buf.put(attributes_count);
+        buf.put((byte) actions_count);
+
+        byte attribute_id = 0;
+        // Encode Pascal-Style Strings
+        for (String s : parts) {
+            attribute_id++;
+            if (s == null || s.equals("")) {
+                continue;
+            }
+
+            int partlength = s.getBytes().length;
+            if (partlength > max_partlength) partlength = max_partlength;
+            buf.put(attribute_id);
+            buf.putShort((short) partlength);
+            buf.put(s.getBytes(), 0, partlength);
+        }
+
+        if (mFwMajor >= 3) {
+            buf.put((byte) 4); // icon
+            buf.putShort((short) 4); // length of int
+            buf.putInt(0x80000000 | icon_id);
+
+            buf.put((byte) 28); // background_color
+            buf.putShort((short) 1); // length of int
+            buf.put(backgroundColor);
+        }
+
+        if (attachedActions != null && attachedActions.size() > 0) {
+            for (int ai = 0 ; ai<attachedActions.size(); ai++) {
+                Action act = attachedActions.get(ai);
+                switch (act.type) {
+                    case Action.TYPE_SYNTECTIC_OPEN:
+                        buf.put((byte) 0x01);
+                        break;
+                    case Action.TYPE_SYNTECTIC_DISMISS:
+                        buf.put((byte) 0x02);
+                        break;
+                    case Action.TYPE_SYNTECTIC_DISMISS_ALL:
+                        buf.put((byte) 0x03);
+                        break;
+                    case Action.TYPE_SYNTECTIC_MUTE:
+                        buf.put((byte) 0x04);
+                        break;
+                    default:
+                        buf.put((byte) (0x05 + ai));
+                }
+
+                if (act.type == Action.TYPE_WEARABLE_REPLY || act.type == Action.TYPE_SYNTECTIC_REPLY_PHONENR) {
+                    buf.put((byte) 0x03); // reply action
+                    buf.put((byte) 0x02); // number attributes
+                } else {
+                    if (act.type == Action.TYPE_SYNTECTIC_DISMISS) {
+                        buf.put(dismiss_action_type);
+                    } else {
+                        buf.put((byte) 0x02); // generic action
+                    }
+                    buf.put((byte) 0x01); // number attributes
+                }
+
+                buf.put((byte) 0x01); // attribute id (title)
+                buf.putShort((short) act.title.getBytes().length);
+                buf.put(act.title.getBytes());
+                if (act.type == Action.TYPE_WEARABLE_REPLY || act.type == Action.TYPE_SYNTECTIC_REPLY_PHONENR) {
+                    buf.put((byte) 0x08); // canned replies
+                    buf.putShort((short) replies_length);
+                    if (cannedReplies != null && cannedReplies.length > 0) {
+                        for (int i = 0; i < cannedReplies.length - 1; i++) {
+                            buf.put(cannedReplies[i].getBytes());
+                            buf.put((byte) 0x00);
+                        }
+                        // last one must not be zero terminated, else we get an additional emply reply
+                        buf.put(cannedReplies[cannedReplies.length - 1].getBytes());
+                    }
+                }
+            }
+        }
+        if (mFwMajor >= 3) {
+            return encodeBlobdb(UUID.randomUUID(), BLOBDB_INSERT, BLOBDB_NOTIFICATION, buf.array());
+        } else {
+            return buf.array();
+        }
+    }
+
+    private byte[] encodeActionResponse2x(int id, byte actionId, int iconId, String caption) {
+        short length = (short) (18 + caption.getBytes().length);
+        ByteBuffer buf = ByteBuffer.allocate(LENGTH_PREFIX + length);
+        buf.order(ByteOrder.BIG_ENDIAN);
+        buf.putShort(length);
+        buf.putShort(ENDPOINT_EXTENSIBLENOTIFS);
+        buf.order(ByteOrder.LITTLE_ENDIAN);
+        buf.put(NOTIFICATIONACTION_RESPONSE);
+        buf.putInt(id);
+        buf.put(actionId);
+        buf.put(NOTIFICATIONACTION_ACK);
+        buf.put((byte) 2); //nr of attributes
+        buf.put((byte) 6); // icon
+        buf.putShort((short) 4); // length
+        buf.putInt(iconId);
+        buf.put((byte) 2); // title
+        buf.putShort((short) caption.getBytes().length);
+        buf.put(caption.getBytes());
+        return buf.array();
+    }
+
+    private byte[] encodeWeatherPin(int timestamp, String title, String subtitle, String body, String location, int iconId) {
+        final short NOTIFICATION_PIN_LENGTH = 46;
+        final short ACTION_LENGTH_MIN = 6;
+
+        String[] parts = {title, subtitle, body, location, "test", "test"};
+
+        // Calculate length first
+        byte actions_count = 1;
+        short actions_length;
+        String remove_string = "Remove";
+        actions_length = (short) (ACTION_LENGTH_MIN * actions_count + remove_string.getBytes().length);
+
+        byte attributes_count = 3;
+        short attributes_length = (short) (21 + actions_length);
+        for (String s : parts) {
+            if (s == null || s.equals("")) {
+                continue;
+            }
+            attributes_count++;
+            attributes_length += (short) (3 + s.getBytes().length);
+        }
+
+        UUID uuid = UUID.fromString("61b22bc8-1e29-460d-a236-3fe409a43901");
+
+        short pin_length = (short) (NOTIFICATION_PIN_LENGTH + attributes_length);
+
+        ByteBuffer buf = ByteBuffer.allocate(pin_length);
+
+        // pin (46 bytes)
+        buf.order(ByteOrder.BIG_ENDIAN);
+        buf.putLong(uuid.getMostSignificantBits());
+        buf.putLong(uuid.getLeastSignificantBits());
+        buf.putLong(uuid.getMostSignificantBits());
+        buf.putLong(uuid.getLeastSignificantBits() | 0xff);
+        buf.order(ByteOrder.LITTLE_ENDIAN);
+        buf.putInt(timestamp); // 32-bit timestamp
+        buf.putShort((short) 0); // duration
+        buf.put((byte) 0x02); // type (0x02 = pin)
+        buf.putShort((short) 0x0001); // flags 0x0001 = ?
+        buf.put((byte) 0x06); // layout (0x06 = weather)
+        buf.putShort(attributes_length); // total length of all attributes and actions in bytes
+        buf.put(attributes_count);
+        buf.put(actions_count);
+
+        byte attribute_id = 0;
+        // Encode Pascal-Style Strings
+        for (String s : parts) {
+            attribute_id++;
+            if (s == null || s.equals("")) {
+                continue;
+            }
+
+            int partlength = s.getBytes().length;
+            if (partlength > 512) partlength = 512;
+            if (attribute_id == 4) {
+                buf.put((byte) 11);
+            } else if (attribute_id == 5) {
+                buf.put((byte) 25);
+            } else if (attribute_id == 6) {
+                buf.put((byte) 26);
+            } else {
+                buf.put(attribute_id);
+            }
+            buf.putShort((short) partlength);
+            buf.put(s.getBytes(), 0, partlength);
+        }
+
+        buf.put((byte) 4); // icon
+        buf.putShort((short) 4); // length of int
+        buf.putInt(0x80000000 | iconId);
+
+        buf.put((byte) 6); // icon
+        buf.putShort((short) 4); // length of int
+        buf.putInt(0x80000000 | iconId);
+
+        buf.put((byte) 14); // last updated
+        buf.putShort((short) 4); // length of int
+        buf.putInt(timestamp);
+
+        // remove action
+        buf.put((byte) 123); // action id
+        buf.put((byte) 0x09); // remove
+        buf.put((byte) 0x01); // number attributes
+        buf.put((byte) 0x01); // attribute id (title)
+        buf.putShort((short) remove_string.getBytes().length);
+        buf.put(remove_string.getBytes());
+
+        return encodeBlobdb(uuid, BLOBDB_INSERT, BLOBDB_PIN, buf.array());
+    }
+
+
+    @Override
+    public byte[] encodeSendWeather(WeatherSpec weatherSpec) {
+        byte[] forecastProtocol = null;
+        byte[] watchfaceProtocol = null;
+        int length = 0;
+        if (mFwMajor >= 4) {
+            forecastProtocol = encodeWeatherForecast(weatherSpec);
+            length += forecastProtocol.length;
+        }
+        AppMessageHandler handler = mAppMessageHandlers.get(currentRunningApp);
+        if (handler != null) {
+            watchfaceProtocol = handler.encodeUpdateWeather(weatherSpec);
+            if (watchfaceProtocol != null) {
+                length += watchfaceProtocol.length;
+            }
+        }
+        ByteBuffer buf = ByteBuffer.allocate(length);
+
+        if (forecastProtocol != null) {
+            buf.put(forecastProtocol);
+        }
+        if (watchfaceProtocol != null) {
+            buf.put(watchfaceProtocol);
+        }
+
+        return buf.array();
+    }
+
+    private byte[] encodeWeatherForecast(WeatherSpec weatherSpec) {
+
+        short currentTemp = (short) (weatherSpec.currentTemp - 273);
+        short todayMax = (short) (weatherSpec.todayMaxTemp - 273);
+        short todayMin = (short) (weatherSpec.todayMinTemp - 273);
+        short tomorrowMax = 0;
+        short tomorrowMin = 0;
+        int tomorrowConditionCode = 0;
+        if (weatherSpec.forecasts.size() > 0) {
+            WeatherSpec.Daily tomorrow = weatherSpec.forecasts.get(0);
+            tomorrowMax = (short) (tomorrow.maxTemp - 273);
+            tomorrowMin = (short) (tomorrow.minTemp - 273);
+            tomorrowConditionCode = tomorrow.conditionCode;
+        }
+
+        String units = GBApplication.getPrefs().getString(SettingsActivity.PREF_MEASUREMENT_SYSTEM, GBApplication.getContext().getString(R.string.p_unit_metric));
+        if (units.equals(GBApplication.getContext().getString(R.string.p_unit_imperial))) {
+            currentTemp = (short) (currentTemp * 1.8f + 32);
+            todayMax = (short) (todayMax * 1.8f + 32);
+            todayMin = (short) (todayMin * 1.8f + 32);
+            tomorrowMax = (short) (tomorrowMax * 1.8f + 32);
+            tomorrowMin = (short) (tomorrowMin * 1.8f + 32);
+        }
+        final short WEATHER_FORECAST_LENGTH = 20;
+
+        String[] parts = {weatherSpec.location, weatherSpec.currentCondition};
+
+        // Calculate length first
+        short attributes_length = 0;
+        for (String s : parts) {
+            if (s == null || s.equals("")) {
+                continue;
+            }
+            attributes_length += (short) (2 + s.getBytes().length);
+        }
+
+        short pin_length = (short) (WEATHER_FORECAST_LENGTH + attributes_length);
+
+        ByteBuffer buf = ByteBuffer.allocate(pin_length);
+        buf.order(ByteOrder.LITTLE_ENDIAN);
+        buf.put((byte) 3); // unknown, always 3?
+        buf.putShort(currentTemp);
+        buf.put(Weather.mapToPebbleCondition(weatherSpec.currentConditionCode));
+        buf.putShort(todayMax);
+        buf.putShort(todayMin);
+        buf.put(Weather.mapToPebbleCondition(tomorrowConditionCode));
+        buf.putShort(tomorrowMax);
+        buf.putShort(tomorrowMin);
+        buf.putInt(weatherSpec.timestamp);
+        buf.put((byte) 0); // automatic location 0=manual 1=auto
+        buf.putShort(attributes_length);
+
+        // Encode Pascal-Style Strings
+        for (String s : parts) {
+            if (s == null || s.equals("")) {
+                continue;
+            }
+
+            int partlength = s.getBytes().length;
+            if (partlength > 512) partlength = 512;
+            buf.putShort((short) partlength);
+            buf.put(s.getBytes(), 0, partlength);
+        }
+
+        return encodeBlobdb(UUID_LOCATION, BLOBDB_INSERT, BLOBDB_WEATHER, buf.array());
+    }
+
+    private byte[] encodeActionResponse(UUID uuid, int iconId, String caption) {
+        short length = (short) (29 + caption.getBytes().length);
+        ByteBuffer buf = ByteBuffer.allocate(LENGTH_PREFIX + length);
+        buf.order(ByteOrder.BIG_ENDIAN);
+        buf.putShort(length);
+        buf.putShort(ENDPOINT_NOTIFICATIONACTION);
+        buf.put(NOTIFICATIONACTION_RESPONSE);
+        buf.putLong(uuid.getMostSignificantBits());
+        buf.putLong(uuid.getLeastSignificantBits());
+        buf.order(ByteOrder.LITTLE_ENDIAN);
+        buf.put(NOTIFICATIONACTION_ACK);
+        buf.put((byte) 2); //nr of attributes
+        buf.put((byte) 6); // icon
+        buf.putShort((short) 4); // length
+        buf.putInt(0x80000000 | iconId);
+        buf.put((byte) 2); // title
+        buf.putShort((short) caption.getBytes().length);
+        buf.put(caption.getBytes());
+        return buf.array();
+    }
+
+    byte[] encodeInstallMetadata(UUID uuid, String appName, short appVersion, short sdkVersion, int flags, int iconId) {
+        final short METADATA_LENGTH = 126;
+
+        byte[] name_buf = new byte[96];
+        System.arraycopy(appName.getBytes(), 0, name_buf, 0, appName.getBytes().length);
+        ByteBuffer buf = ByteBuffer.allocate(METADATA_LENGTH);
+
+        buf.order(ByteOrder.BIG_ENDIAN);
+        buf.putLong(uuid.getMostSignificantBits()); // watchapp uuid
+        buf.putLong(uuid.getLeastSignificantBits());
+        buf.order(ByteOrder.LITTLE_ENDIAN);
+        buf.putInt(flags);
+        buf.putInt(iconId);
+        buf.putShort(appVersion);
+        buf.putShort(sdkVersion);
+        buf.put((byte) 0); // app_face_bgcolor
+        buf.put((byte) 0); // app_face_template_id
+        buf.put(name_buf); // 96 bytes
+
+        return encodeBlobdb(uuid, BLOBDB_INSERT, BLOBDB_APP, buf.array());
+    }
+
+    byte[] encodeAppFetchAck() {
+        final short LENGTH_APPFETCH = 2;
+        ByteBuffer buf = ByteBuffer.allocate(LENGTH_PREFIX + LENGTH_APPFETCH);
+        buf.order(ByteOrder.BIG_ENDIAN);
+        buf.putShort(LENGTH_APPFETCH);
+        buf.putShort(ENDPOINT_APPFETCH);
+        buf.put((byte) 0x01);
+        buf.put((byte) 0x01);
+
+        return buf.array();
+    }
+
+    byte[] encodeGetTime() {
+        return encodeSimpleMessage(ENDPOINT_TIME, TIME_GETTIME);
+    }
+
+    @Override
+    public byte[] encodeSetCallState(String number, String name, int command) {
+        String[] parts = {number, name};
+        byte pebbleCmd;
+        switch (command) {
+            case CallSpec.CALL_START:
+                pebbleCmd = PHONECONTROL_START;
+                break;
+            case CallSpec.CALL_END:
+                pebbleCmd = PHONECONTROL_END;
+                break;
+            case CallSpec.CALL_INCOMING:
+                pebbleCmd = PHONECONTROL_INCOMINGCALL;
+                break;
+            case CallSpec.CALL_OUTGOING:
+                // pebbleCmd = PHONECONTROL_OUTGOINGCALL;
+                /*
+                 *  HACK/WORKAROUND for non-working outgoing call display.
+                 *  Just send a incoming call command immediately followed by a start call command
+                 *  This prevents vibration of the Pebble.
+                 */
+                byte[] callmsg = encodeMessage(ENDPOINT_PHONECONTROL, PHONECONTROL_INCOMINGCALL, 0, parts);
+                byte[] startmsg = encodeMessage(ENDPOINT_PHONECONTROL, PHONECONTROL_START, 0, parts);
+                byte[] msg = new byte[callmsg.length + startmsg.length];
+                System.arraycopy(callmsg, 0, msg, 0, callmsg.length);
+                System.arraycopy(startmsg, 0, msg, startmsg.length, startmsg.length);
+                return msg;
+            // END HACK
+            default:
+                return null;
+        }
+        return encodeMessage(ENDPOINT_PHONECONTROL, pebbleCmd, 0, parts);
+    }
+
+    public byte[] encodeSetMusicState(byte state, int position, int playRate, byte shuffle, byte repeat) {
+        if (mFwMajor < 3) {
+            return null;
+        }
+
+        byte playState;
+
+        switch (state) {
+            case MusicStateSpec.STATE_PLAYING:
+                playState = MUSICCONTROL_STATE_PLAYING;
+                break;
+            case MusicStateSpec.STATE_PAUSED:
+                playState = MUSICCONTROL_STATE_PAUSED;
+                break;
+            default:
+                playState = MUSICCONTROL_STATE_UNKNOWN;
+                break;
+        }
+
+        int length = LENGTH_PREFIX + 12;
+        // Encode Prefix
+        ByteBuffer buf = ByteBuffer.allocate(length);
+        buf.order(ByteOrder.BIG_ENDIAN);
+        buf.putShort((short) (length - LENGTH_PREFIX));
+        buf.putShort(ENDPOINT_MUSICCONTROL);
+
+        buf.order(ByteOrder.LITTLE_ENDIAN);
+        buf.put(MUSICCONTROL_SETPLAYSTATE);
+        buf.put(playState);
+        buf.putInt(position * 1000);
+        buf.putInt(playRate);
+        buf.put(shuffle);
+        buf.put(repeat);
+
+        return buf.array();
+    }
+
+    @Override
+    public byte[] encodeSetMusicInfo(String artist, String album, String track, int duration, int trackCount, int trackNr) {
+        String[] parts = {artist, album, track};
+        if (duration == 0 || mFwMajor < 3) {
+            return encodeMessage(ENDPOINT_MUSICCONTROL, MUSICCONTROL_SETMUSICINFO, 0, parts);
+        } else {
+            // Calculate length first
+            int length = LENGTH_PREFIX + 9;
+            for (String s : parts) {
+                if (s == null || s.equals("")) {
+                    length++; // encode null or empty strings as 0x00 later
+                    continue;
+                }
+                length += (1 + s.getBytes().length);
+            }
+
+            // Encode Prefix
+            ByteBuffer buf = ByteBuffer.allocate(length);
+            buf.order(ByteOrder.BIG_ENDIAN);
+            buf.putShort((short) (length - LENGTH_PREFIX));
+            buf.putShort(ENDPOINT_MUSICCONTROL);
+            buf.put(MUSICCONTROL_SETMUSICINFO);
+
+            // Encode Pascal-Style Strings
+            for (String s : parts) {
+                if (s == null || s.equals("")) {
+                    buf.put((byte) 0x00);
+                    continue;
+                }
+
+                int partlength = s.getBytes().length;
+                if (partlength > 255) partlength = 255;
+                buf.put((byte) partlength);
+                buf.put(s.getBytes(), 0, partlength);
+            }
+
+            buf.order(ByteOrder.LITTLE_ENDIAN);
+            buf.putInt(duration * 1000);
+            buf.putShort((short) (trackCount & 0xffff));
+            buf.putShort((short) (trackNr & 0xffff));
+
+            return buf.array();
+        }
+    }
+
+    @Override
+    public byte[] encodeFirmwareVersionReq() {
+        return encodeSimpleMessage(ENDPOINT_FIRMWAREVERSION, FIRMWAREVERSION_GETVERSION);
+    }
+
+    @Override
+    public byte[] encodeAppInfoReq() {
+        if (mFwMajor >= 3) {
+            return null; // can't do this on 3.x :(
+        }
+        return encodeSimpleMessage(ENDPOINT_APPMANAGER, APPMANAGER_GETUUIDS);
+    }
+
+    @Override
+    public byte[] encodeAppStart(UUID uuid, boolean start) {
+        if (mFwMajor >= 3) {
+            final short LENGTH_APPRUNSTATE = 17;
+            ByteBuffer buf = ByteBuffer.allocate(LENGTH_PREFIX + LENGTH_APPRUNSTATE);
+            buf.order(ByteOrder.BIG_ENDIAN);
+            buf.putShort(LENGTH_APPRUNSTATE);
+            buf.putShort(ENDPOINT_APPRUNSTATE);
+            buf.put(start ? APPRUNSTATE_START : APPRUNSTATE_STOP);
+            buf.putLong(uuid.getMostSignificantBits());
+            buf.putLong(uuid.getLeastSignificantBits());
+            return buf.array();
+        } else {
+            ArrayList<Pair<Integer, Object>> pairs = new ArrayList<>();
+            int param = start ? 1 : 0;
+            pairs.add(new Pair<>(1, (Object) param));
+            return encodeApplicationMessagePush(ENDPOINT_LAUNCHER, uuid, pairs, null);
+        }
+    }
+
+    @Override
+    public byte[] encodeAppDelete(UUID uuid) {
+        if (mFwMajor >= 3) {
+            if (UUID_PEBBLE_HEALTH.equals(uuid)) {
+                return encodeActivateHealth(false);
+            }
+            if (UUID_WORKOUT.equals(uuid)) {
+                return encodeActivateHRM(false);
+            }
+            if (UUID_WEATHER.equals(uuid)) { //TODO: probably it wasn't present in firmware 3
+                return encodeActivateWeather(false);
+            }
+            return encodeBlobdb(uuid, BLOBDB_DELETE, BLOBDB_APP, null);
+        } else {
+            final short LENGTH_REMOVEAPP_2X = 17;
+            ByteBuffer buf = ByteBuffer.allocate(LENGTH_PREFIX + LENGTH_REMOVEAPP_2X);
+            buf.order(ByteOrder.BIG_ENDIAN);
+            buf.putShort(LENGTH_REMOVEAPP_2X);
+            buf.putShort(ENDPOINT_APPMANAGER);
+            buf.put(APPMANAGER_REMOVEAPP);
+            buf.putLong(uuid.getMostSignificantBits());
+            buf.putLong(uuid.getLeastSignificantBits());
+            return buf.array();
+        }
+    }
+
+    private byte[] encodePhoneVersion2x(byte os) {
+        final short LENGTH_PHONEVERSION = 17;
+        ByteBuffer buf = ByteBuffer.allocate(LENGTH_PREFIX + LENGTH_PHONEVERSION);
+        buf.order(ByteOrder.BIG_ENDIAN);
+        buf.putShort(LENGTH_PHONEVERSION);
+        buf.putShort(ENDPOINT_PHONEVERSION);
+        buf.put((byte) 0x01);
+        buf.putInt(-1); //0xffffffff
+
+        if (os == PHONEVERSION_REMOTE_OS_ANDROID) {
+            buf.putInt(PHONEVERSION_SESSION_CAPS_GAMMARAY);
+        } else {
+            buf.putInt(0);
+        }
+        buf.putInt(PHONEVERSION_REMOTE_CAPS_SMS | PHONEVERSION_REMOTE_CAPS_TELEPHONY | os);
+
+        buf.put(PHONEVERSION_APPVERSION_MAGIC);
+        buf.put(PHONEVERSION_APPVERSION_MAJOR);
+        buf.put(PHONEVERSION_APPVERSION_MINOR);
+        buf.put(PHONEVERSION_APPVERSION_PATCH);
+
+        return buf.array();
+    }
+
+    private byte[] encodePhoneVersion3x(byte os) {
+        final short LENGTH_PHONEVERSION3X = 25;
+        ByteBuffer buf = ByteBuffer.allocate(LENGTH_PREFIX + LENGTH_PHONEVERSION3X);
+        buf.order(ByteOrder.BIG_ENDIAN);
+        buf.putShort(LENGTH_PHONEVERSION3X);
+        buf.putShort(ENDPOINT_PHONEVERSION);
+        buf.put((byte) 0x01);
+        buf.putInt(-1); //0xffffffff
+        buf.putInt(0);
+
+        buf.putInt(os);
+
+        buf.put(PHONEVERSION_APPVERSION_MAGIC);
+        buf.put((byte) 4); // major
+        buf.put((byte) 1); // minor
+        buf.put((byte) 1); // patch
+        buf.order(ByteOrder.LITTLE_ENDIAN);
+        buf.putLong(0x00000000000029af); //flags
+
+        return buf.array();
+    }
+
+    private byte[] encodePhoneVersion(byte os) {
+        return encodePhoneVersion3x(os);
+    }
+
+    @Override
+    public byte[] encodeReset(int flags) {
+        return encodeSimpleMessage(ENDPOINT_RESET, RESET_REBOOT);
+    }
+
+    @Override
+    public byte[] encodeScreenshotReq() {
+        return encodeSimpleMessage(ENDPOINT_SCREENSHOT, SCREENSHOT_TAKE);
+    }
+
+    @Override
+    public byte[] encodeAppReorder(UUID[] uuids) {
+        int length = 2 + uuids.length * LENGTH_UUID;
+        ByteBuffer buf = ByteBuffer.allocate(LENGTH_PREFIX + length);
+        buf.order(ByteOrder.BIG_ENDIAN);
+        buf.putShort((short) length);
+        buf.putShort(ENDPOINT_APPREORDER);
+        buf.put((byte) 0x01);
+        buf.put((byte) uuids.length);
+        for (UUID uuid : uuids) {
+            buf.putLong(uuid.getMostSignificantBits());
+            buf.putLong(uuid.getLeastSignificantBits());
+        }
+
+        return buf.array();
+    }
+
+    @Override
+    public byte[] encodeSetCannedMessages(CannedMessagesSpec cannedMessagesSpec) {
+
+        if (cannedMessagesSpec.cannedMessages == null || cannedMessagesSpec.cannedMessages.length == 0) {
+            return null;
+        }
+
+        String blobDBKey;
+        switch (cannedMessagesSpec.type) {
+            case CannedMessagesSpec.TYPE_REJECTEDCALLS:
+                blobDBKey = "com.pebble.android.phone";
+                break;
+            case CannedMessagesSpec.TYPE_NEWSMS:
+                blobDBKey = "com.pebble.sendText";
+                break;
+            default:
+                return null;
+        }
+
+        int replies_length = -1;
+
+        for (String reply : cannedMessagesSpec.cannedMessages) {
+            replies_length += reply.getBytes().length + 1;
+        }
+
+        ByteBuffer buf = ByteBuffer.allocate(12 + replies_length);
+        buf.order(ByteOrder.LITTLE_ENDIAN);
+        buf.putInt(0x00000000); // unknown
+        buf.put((byte) 0x00); // attributes count?
+        buf.put((byte) 0x01); // actions count?
+
+        // action
+        buf.put((byte) 0x00); // action id
+        buf.put((byte) 0x03); // action type = reply
+        buf.put((byte) 0x01); // attributes count
+        buf.put((byte) 0x08); // canned messages
+        buf.putShort((short) replies_length);
+        for (int i = 0; i < cannedMessagesSpec.cannedMessages.length - 1; i++) {
+            buf.put(cannedMessagesSpec.cannedMessages[i].getBytes());
+            buf.put((byte) 0x00);
+        }
+        // last one must not be zero terminated, else we get an additional empty reply
+        buf.put(cannedMessagesSpec.cannedMessages[cannedMessagesSpec.cannedMessages.length - 1].getBytes());
+
+        return encodeBlobdb(blobDBKey, BLOBDB_INSERT, BLOBDB_CANNED_MESSAGES, buf.array());
+    }
+
+    /* pebble specific install methods */
+    byte[] encodeUploadStart(byte type, int app_id, int size, String filename) {
+        short length;
+        if (mFwMajor >= 3 && (type != PUTBYTES_TYPE_FILE)) {
+            length = (short) 10;
+            type |= 0b10000000;
+        } else {
+            length = (short) 7;
+        }
+
+        if (type == PUTBYTES_TYPE_FILE && filename != null) {
+            length += (short) filename.getBytes().length + 1;
+        }
+
+        ByteBuffer buf = ByteBuffer.allocate(LENGTH_PREFIX + length);
+        buf.order(ByteOrder.BIG_ENDIAN);
+        buf.putShort(length);
+        buf.putShort(ENDPOINT_PUTBYTES);
+        buf.put(PUTBYTES_INIT);
+        buf.putInt(size);
+        buf.put(type);
+
+        if (mFwMajor >= 3 && (type != PUTBYTES_TYPE_FILE)) {
+            buf.putInt(app_id);
+        } else {
+            // slot
+            buf.put((byte) app_id);
+        }
+
+        if (type == PUTBYTES_TYPE_FILE && filename != null) {
+            buf.put(filename.getBytes());
+            buf.put((byte) 0);
+        }
+
+        return buf.array();
+    }
+
+    byte[] encodeUploadChunk(int token, byte[] buffer, int size) {
+        final short LENGTH_UPLOADCHUNK = 9;
+        ByteBuffer buf = ByteBuffer.allocate(LENGTH_PREFIX + LENGTH_UPLOADCHUNK + size);
+        buf.order(ByteOrder.BIG_ENDIAN);
+        buf.putShort((short) (LENGTH_UPLOADCHUNK + size));
+        buf.putShort(ENDPOINT_PUTBYTES);
+        buf.put(PUTBYTES_SEND);
+        buf.putInt(token);
+        buf.putInt(size);
+        buf.put(buffer, 0, size);
+        return buf.array();
+    }
+
+    byte[] encodeUploadCommit(int token, int crc) {
+        final short LENGTH_UPLOADCOMMIT = 9;
+        ByteBuffer buf = ByteBuffer.allocate(LENGTH_PREFIX + LENGTH_UPLOADCOMMIT);
+        buf.order(ByteOrder.BIG_ENDIAN);
+        buf.putShort(LENGTH_UPLOADCOMMIT);
+        buf.putShort(ENDPOINT_PUTBYTES);
+        buf.put(PUTBYTES_COMMIT);
+        buf.putInt(token);
+        buf.putInt(crc);
+        return buf.array();
+    }
+
+    byte[] encodeUploadComplete(int token) {
+        final short LENGTH_UPLOADCOMPLETE = 5;
+        ByteBuffer buf = ByteBuffer.allocate(LENGTH_PREFIX + LENGTH_UPLOADCOMPLETE);
+        buf.order(ByteOrder.BIG_ENDIAN);
+        buf.putShort(LENGTH_UPLOADCOMPLETE);
+        buf.putShort(ENDPOINT_PUTBYTES);
+        buf.put(PUTBYTES_COMPLETE);
+        buf.putInt(token);
+        return buf.array();
+    }
+
+    byte[] encodeUploadCancel(int token) {
+        final short LENGTH_UPLOADCANCEL = 5;
+        ByteBuffer buf = ByteBuffer.allocate(LENGTH_PREFIX + LENGTH_UPLOADCANCEL);
+        buf.order(ByteOrder.BIG_ENDIAN);
+        buf.putShort(LENGTH_UPLOADCANCEL);
+        buf.putShort(ENDPOINT_PUTBYTES);
+        buf.put(PUTBYTES_ABORT);
+        buf.putInt(token);
+        return buf.array();
+    }
+
+    private byte[] encodeSystemMessage(byte systemMessage) {
+        final short LENGTH_SYSTEMMESSAGE = 2;
+        ByteBuffer buf = ByteBuffer.allocate(LENGTH_PREFIX + LENGTH_SYSTEMMESSAGE);
+        buf.order(ByteOrder.BIG_ENDIAN);
+        buf.putShort(LENGTH_SYSTEMMESSAGE);
+        buf.putShort(ENDPOINT_SYSTEMMESSAGE);
+        buf.put((byte) 0);
+        buf.put(systemMessage);
+        return buf.array();
+
+    }
+
+    byte[] encodeInstallFirmwareStart() {
+        return encodeSystemMessage(SYSTEMMESSAGE_FIRMWARESTART);
+    }
+
+    byte[] encodeInstallFirmwareComplete() {
+        return encodeSystemMessage(SYSTEMMESSAGE_FIRMWARECOMPLETE);
+    }
+
+    public byte[] encodeInstallFirmwareError() {
+        return encodeSystemMessage(SYSTEMMESSAGE_FIRMWAREFAIL);
+    }
+
+
+    byte[] encodeAppRefresh(int index) {
+        final short LENGTH_REFRESHAPP = 5;
+        ByteBuffer buf = ByteBuffer.allocate(LENGTH_PREFIX + LENGTH_REFRESHAPP);
+        buf.order(ByteOrder.BIG_ENDIAN);
+        buf.putShort(LENGTH_REFRESHAPP);
+        buf.putShort(ENDPOINT_APPMANAGER);
+        buf.put(APPMANAGER_REFRESHAPP);
+        buf.putInt(index);
+
+        return buf.array();
+    }
+
+    private byte[] encodeDatalog(byte handle, byte reply) {
+        ByteBuffer buf = ByteBuffer.allocate(LENGTH_PREFIX + 2);
+        buf.order(ByteOrder.BIG_ENDIAN);
+        buf.putShort((short) 2);
+        buf.putShort(ENDPOINT_DATALOG);
+        buf.put(reply);
+        buf.put(handle);
+
+        return buf.array();
+    }
+
+    byte[] encodeApplicationMessageAck(UUID uuid, byte id) {
+        if (uuid == null) {
+            uuid = currentRunningApp;
+        }
+        ByteBuffer buf = ByteBuffer.allocate(LENGTH_PREFIX + 18); // +ACK
+
+        buf.order(ByteOrder.BIG_ENDIAN);
+        buf.putShort((short) 18);
+        buf.putShort(ENDPOINT_APPLICATIONMESSAGE);
+        buf.put(APPLICATIONMESSAGE_ACK);
+        buf.put(id);
+        buf.putLong(uuid.getMostSignificantBits());
+        buf.putLong(uuid.getLeastSignificantBits());
+
+        return buf.array();
+    }
+
+    private byte[] encodePing(byte command, int cookie) {
+        final short LENGTH_PING = 5;
+        ByteBuffer buf = ByteBuffer.allocate(LENGTH_PREFIX + LENGTH_PING);
+        buf.order(ByteOrder.BIG_ENDIAN);
+        buf.putShort(LENGTH_PING);
+        buf.putShort(ENDPOINT_PING);
+        buf.put(command);
+        buf.putInt(cookie);
+
+        return buf.array();
+    }
+
+    byte[] encodeEnableAppLogs(boolean enable) {
+        final short LENGTH_APPLOGS = 1;
+        ByteBuffer buf = ByteBuffer.allocate(LENGTH_PREFIX + LENGTH_APPLOGS);
+        buf.order(ByteOrder.BIG_ENDIAN);
+        buf.putShort(LENGTH_APPLOGS);
+        buf.putShort(ENDPOINT_APPLOGS);
+        buf.put((byte) (enable ? 1 : 0));
+
+        return buf.array();
+    }
+
+    private ArrayList<Pair<Integer, Object>> decodeDict(ByteBuffer buf) {
+        ArrayList<Pair<Integer, Object>> dict = new ArrayList<>();
+        buf.order(ByteOrder.LITTLE_ENDIAN);
+        byte dictSize = buf.get();
+        while (dictSize-- > 0) {
+            Integer key = buf.getInt();
+            byte type = buf.get();
+            short length = buf.getShort();
+            switch (type) {
+                case TYPE_INT:
+                case TYPE_UINT:
+                    if (length == 1) {
+                        dict.add(new Pair<Integer, Object>(key, buf.get()));
+                    } else if (length == 2) {
+                        dict.add(new Pair<Integer, Object>(key, buf.getShort()));
+                    } else {
+                        dict.add(new Pair<Integer, Object>(key, buf.getInt()));
+                    }
+                    break;
+                case TYPE_CSTRING:
+                case TYPE_BYTEARRAY:
+                    byte[] bytes = new byte[length];
+                    buf.get(bytes);
+                    if (type == TYPE_BYTEARRAY) {
+                        dict.add(new Pair<Integer, Object>(key, bytes));
+                    } else {
+                        dict.add(new Pair<Integer, Object>(key, new String(bytes)));
+                    }
+                    break;
+                default:
+            }
+        }
+        return dict;
+    }
+
+    private GBDeviceEvent[] decodeDictToJSONAppMessage(UUID uuid, ByteBuffer buf) throws JSONException {
+        buf.order(ByteOrder.LITTLE_ENDIAN);
+        byte dictSize = buf.get();
+        if (dictSize == 0) {
+            LOG.info("dict size is 0, ignoring");
+            return null;
+        }
+        JSONArray jsonArray = new JSONArray();
+        while (dictSize-- > 0) {
+            JSONObject jsonObject = new JSONObject();
+            Integer key = buf.getInt();
+            byte type = buf.get();
+            short length = buf.getShort();
+            jsonObject.put("key", key);
+            if (type == TYPE_CSTRING) {
+                length--;
+            }
+            jsonObject.put("length", length);
+            switch (type) {
+                case TYPE_UINT:
+                    jsonObject.put("type", "uint");
+                    if (length == 1) {
+                        jsonObject.put("value", buf.get() & 0xff);
+                    } else if (length == 2) {
+                        jsonObject.put("value", buf.getShort() & 0xffff);
+                    } else {
+                        jsonObject.put("value", buf.getInt() & 0xffffffffL);
+                    }
+                    break;
+                case TYPE_INT:
+                    jsonObject.put("type", "int");
+                    if (length == 1) {
+                        jsonObject.put("value", buf.get());
+                    } else if (length == 2) {
+                        jsonObject.put("value", buf.getShort());
+                    } else {
+                        jsonObject.put("value", buf.getInt());
+                    }
+                    break;
+                case TYPE_BYTEARRAY:
+                case TYPE_CSTRING:
+                    byte[] bytes = new byte[length];
+                    buf.get(bytes);
+                    if (type == TYPE_BYTEARRAY) {
+                        jsonObject.put("type", "bytes");
+                        jsonObject.put("value", new String(Base64.encode(bytes, Base64.NO_WRAP)));
+                    } else {
+                        jsonObject.put("type", "string");
+                        jsonObject.put("value", new String(bytes));
+                        buf.get(); // skip null-termination;
+                    }
+                    break;
+                default:
+                    LOG.info("unknown type in appmessage, ignoring");
+                    return null;
+            }
+            jsonArray.put(jsonObject);
+        }
+
+        GBDeviceEventSendBytes sendBytesAck = null;
+        if (mAlwaysACKPebbleKit) {
+            // this is a hack we send an ack to the Pebble immediately because somebody said it helps some PebbleKit apps :P
+             sendBytesAck = new GBDeviceEventSendBytes();
+             sendBytesAck.encodedBytes = encodeApplicationMessageAck(uuid, last_id);
+        }
+        GBDeviceEventAppMessage appMessage = new GBDeviceEventAppMessage();
+        appMessage.appUUID = uuid;
+        appMessage.id = last_id & 0xff;
+        appMessage.message = jsonArray.toString();
+        return new GBDeviceEvent[]{appMessage, sendBytesAck};
+    }
+
+    byte[] encodeApplicationMessagePush(short endpoint, UUID uuid, ArrayList<Pair<Integer, Object>> pairs, Integer ext_id) {
+        int length = LENGTH_UUID + 3; // UUID + (PUSH + id + length of dict)
+        for (Pair<Integer, Object> pair : pairs) {
+            if (pair.first == null || pair.second == null)
+                continue;
+            length += 7; // key + type + length
+            if (pair.second instanceof Integer) {
+                length += 4;
+            } else if (pair.second instanceof Short) {
+                length += 2;
+            } else if (pair.second instanceof Byte) {
+                length += 1;
+            } else if (pair.second instanceof String) {
+                length += ((String) pair.second).getBytes().length + 1;
+            } else if (pair.second instanceof byte[]) {
+                length += ((byte[]) pair.second).length;
+            } else {
+                LOG.warn("unknown type: " + pair.second.getClass().toString());
+            }
+        }
+
+        ByteBuffer buf = ByteBuffer.allocate(LENGTH_PREFIX + length);
+        buf.order(ByteOrder.BIG_ENDIAN);
+        buf.putShort((short) length);
+        buf.putShort(endpoint); // 48 or 49
+        buf.put(APPLICATIONMESSAGE_PUSH);
+        buf.put(++last_id);
+        buf.putLong(uuid.getMostSignificantBits());
+        buf.putLong(uuid.getLeastSignificantBits());
+        buf.put((byte) pairs.size());
+
+        buf.order(ByteOrder.LITTLE_ENDIAN);
+        for (Pair<Integer, Object> pair : pairs) {
+            if (pair.first == null || pair.second == null)
+                continue;
+            buf.putInt(pair.first);
+            if (pair.second instanceof Integer) {
+                buf.put(TYPE_INT);
+                buf.putShort((short) 4); // length
+                buf.putInt((int) pair.second);
+            } else if (pair.second instanceof Short) {
+                buf.put(TYPE_INT);
+                buf.putShort((short) 2); // length
+                buf.putShort((short) pair.second);
+            } else if (pair.second instanceof Byte) {
+                buf.put(TYPE_INT);
+                buf.putShort((short) 1); // length
+                buf.put((byte) pair.second);
+            } else if (pair.second instanceof String) {
+                String str = (String) pair.second;
+                buf.put(TYPE_CSTRING);
+                buf.putShort((short) (str.getBytes().length + 1));
+                buf.put(str.getBytes());
+                buf.put((byte) 0);
+            } else if (pair.second instanceof byte[]) {
+                byte[] bytes = (byte[]) pair.second;
+                buf.put(TYPE_BYTEARRAY);
+                buf.putShort((short) bytes.length);
+                buf.put(bytes);
+            }
+        }
+
+        idLookup[last_id & 0xff] = ext_id;
+
+        return buf.array();
+    }
+
+    byte[] encodeApplicationMessageFromJSON(UUID uuid, JSONArray jsonArray) {
+        ArrayList<Pair<Integer, Object>> pairs = new ArrayList<>();
+        for (int i = 0; i < jsonArray.length(); i++) {
+            try {
+                JSONObject jsonObject = (JSONObject) jsonArray.get(i);
+                String type = (String) jsonObject.get("type");
+                int key = jsonObject.getInt("key");
+                int length = jsonObject.getInt("length");
+                switch (type) {
+                    case "uint":
+                    case "int":
+                        if (length == 1) {
+                            pairs.add(new Pair<>(key, (Object) (byte) jsonObject.getInt("value")));
+                        } else if (length == 2) {
+                            pairs.add(new Pair<>(key, (Object) (short) jsonObject.getInt("value")));
+                        } else {
+                            if (type.equals("uint")) {
+                                pairs.add(new Pair<>(key, (Object) (int) (jsonObject.getInt("value") & 0xffffffffL)));
+                            } else {
+                                pairs.add(new Pair<>(key, (Object) jsonObject.getInt("value")));
+                            }
+                        }
+                        break;
+                    case "string":
+                        pairs.add(new Pair<>(key, (Object) jsonObject.getString("value")));
+                        break;
+                    case "bytes":
+                        byte[] bytes = Base64.decode(jsonObject.getString("value"), Base64.NO_WRAP);
+                        pairs.add(new Pair<>(key, (Object) bytes));
+                        break;
+                }
+            } catch (JSONException e) {
+                LOG.error("error decoding JSON", e);
+                return null;
+            }
+        }
+
+        return encodeApplicationMessagePush(ENDPOINT_APPLICATIONMESSAGE, uuid, pairs, null);
+    }
+
+    private byte reverseBits(byte in) {
+        byte out = 0;
+        for (int i = 0; i < 8; i++) {
+            byte bit = (byte) (in & 1);
+            out = (byte) ((out << 1) | bit);
+            in = (byte) (in >> 1);
+        }
+        return out;
+    }
+
+    private GBDeviceEventScreenshot decodeScreenshot(ByteBuffer buf, int length) {
+        if (screenshotData == null) {
+            byte result = buf.get();
+            int version = buf.getInt();
+            if (result != 0) {
+                return null;
+            }
+            screenshotWidth = buf.getInt();
+            screenshotHeight = buf.getInt();
+
+            if (version == 1) {
+                screenshotBpp = 1;
+                screenshotClut = clut_pebble;
+            } else {
+                screenshotBpp = 8;
+                screenshotClut = clut_pebbletime;
+            }
+
+            mScreenshotRemaining = (screenshotWidth * screenshotHeight * screenshotBpp) / 8;
+
+            screenshotData = new byte[mScreenshotRemaining];
+            length -= 13;
+        }
+        if (mScreenshotRemaining == -1) {
+            return null;
+        }
+        for (int i = 0; i < length; i++) {
+            byte corrected = buf.get();
+            if (screenshotBpp == 1) {
+                corrected = reverseBits(corrected);
+            } else {
+                corrected = (byte) (corrected & 0b00111111);
+            }
+
+            screenshotData[screenshotData.length - mScreenshotRemaining + i] = corrected;
+        }
+        mScreenshotRemaining -= length;
+        LOG.info("Screenshot remaining bytes " + mScreenshotRemaining);
+        if (mScreenshotRemaining == 0) {
+            mScreenshotRemaining = -1;
+            LOG.info("Got screenshot : " + screenshotWidth + "x" + screenshotHeight + "  " + "pixels");
+            GBDeviceEventScreenshot devEventScreenshot = new GBDeviceEventScreenshot(encodeScreenshotBmp());
+            screenshotData = null;
+            return devEventScreenshot;
+        }
+        return null;
+    }
+
+    private byte[] encodeScreenshotBmp() {
+        final int FILE_HEADER_SIZE = 14;
+        final int INFO_HEADER_SIZE = 40;
+
+        try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
+            ByteBuffer headerbuf = ByteBuffer.allocate(FILE_HEADER_SIZE + INFO_HEADER_SIZE + screenshotClut.length);
+            headerbuf.order(ByteOrder.LITTLE_ENDIAN);
+
+            // file header
+            headerbuf.put((byte) 'B');
+            headerbuf.put((byte) 'M');
+            headerbuf.putInt(0); // size in bytes (uncompressed = 0)
+            headerbuf.putInt(0); // reserved
+            headerbuf.putInt(FILE_HEADER_SIZE + INFO_HEADER_SIZE + screenshotClut.length);
+
+            // info header
+            headerbuf.putInt(INFO_HEADER_SIZE);
+            headerbuf.putInt(screenshotWidth);
+            headerbuf.putInt(-screenshotHeight);
+            headerbuf.putShort((short) 1); // planes
+            headerbuf.putShort((short) screenshotBpp);
+            headerbuf.putInt(0); // compression
+            headerbuf.putInt(0); // length of pixeldata in bytes (uncompressed=0)
+            headerbuf.putInt(0); // pixels per meter (x)
+            headerbuf.putInt(0); // pixels per meter (y)
+            headerbuf.putInt(screenshotClut.length / 4); // number of colors in CLUT
+            headerbuf.putInt(0); // numbers of used colors
+            headerbuf.put(screenshotClut);
+            baos.write(headerbuf.array());
+            int rowbytes = (screenshotWidth * screenshotBpp) / 8;
+            byte[] pad = new byte[rowbytes % 4];
+            for (int i = 0; i < screenshotHeight; i++) {
+                baos.write(screenshotData, rowbytes * i, rowbytes);
+                baos.write(pad);
+            }
+            return baos.toByteArray();
+        } catch (final IOException e) {
+            LOG.warn("Failed to encode screenshot to bpm");
+            return null;
+        }
+    }
+
+    private GBDeviceEvent[] decodeAction(ByteBuffer buf) {
+        buf.order(ByteOrder.LITTLE_ENDIAN);
+        byte command = buf.get();
+        if (command == NOTIFICATIONACTION_INVOKE) {
+            int id;
+            UUID uuid = new UUID(0,0);
+            if (mFwMajor >= 3) {
+                uuid = getUUID(buf);
+                id = (int) (uuid.getLeastSignificantBits() & 0xffffffffL);
+            } else {
+                id = buf.getInt();
+            }
+            byte action = buf.get();
+            if (action >= 0x00 && action <= 0xf) {
+                GBDeviceEventNotificationControl devEvtNotificationControl = new GBDeviceEventNotificationControl();
+                devEvtNotificationControl.handle = id;
+                String caption = "undefined";
+                int icon_id = 1;
+                boolean needsAck2x = true;
+                switch (action) {
+                    case 0x01:
+                        devEvtNotificationControl.event = GBDeviceEventNotificationControl.Event.OPEN;
+                        caption = "Opened";
+                        icon_id = PebbleIconID.DURING_PHONE_CALL;
+                        break;
+                    case 0x02:
+                        devEvtNotificationControl.event = GBDeviceEventNotificationControl.Event.DISMISS;
+                        caption = "Dismissed";
+                        icon_id = PebbleIconID.RESULT_DISMISSED;
+                        needsAck2x = false;
+                        break;
+                    case 0x03:
+                        devEvtNotificationControl.event = GBDeviceEventNotificationControl.Event.DISMISS_ALL;
+                        caption = "All dismissed";
+                        icon_id = PebbleIconID.RESULT_DISMISSED;
+                        needsAck2x = false;
+                        break;
+                    case 0x04:
+                        devEvtNotificationControl.event = GBDeviceEventNotificationControl.Event.MUTE;
+                        caption = "Muted";
+                        icon_id = PebbleIconID.RESULT_MUTE;
+                        break;
+                        //TODO: 0x05 is not a special case anymore, and reply action might have an index that is higher. see default below
+                    case 0x00:
+                    default:
+                        boolean failed = true;
+                        byte attribute_count = buf.get();
+                        if (attribute_count > 0) {
+                                byte attribute = buf.get();
+                                if (attribute == 0x01) { // reply string is in attribute 0x01
+                                    short length = buf.getShort();
+                                    if (length > 64) length = 64;
+                                    byte[] reply = new byte[length];
+                                    buf.get(reply);
+                                    devEvtNotificationControl.phoneNumber = null;
+                                    if (buf.remaining() > 1 && buf.get() == 0x0c) {
+                                        short phoneNumberLength = buf.getShort();
+                                        byte[] phoneNumberBytes = new byte[phoneNumberLength];
+                                        buf.get(phoneNumberBytes);
+                                        devEvtNotificationControl.phoneNumber = new String(phoneNumberBytes);
+                                    }
+                                    devEvtNotificationControl.reply = new String(reply);
+                                    caption = "SENT";
+                                    icon_id = PebbleIconID.RESULT_SENT;
+                                    failed = false;
+                                }
+                        } else {
+                            icon_id = PebbleIconID.GENERIC_CONFIRMATION;
+                            caption = "EXECUTED";
+                            failed = false;
+                        }
+                        devEvtNotificationControl.event = GBDeviceEventNotificationControl.Event.REPLY;
+                        devEvtNotificationControl.handle = (devEvtNotificationControl.handle << 4) + action - 0x04;
+                        if (failed) {
+                            caption = "FAILED";
+                            icon_id = PebbleIconID.RESULT_FAILED;
+                            devEvtNotificationControl = null; // error
+                        }
+                        break;
+                }
+                GBDeviceEventSendBytes sendBytesAck = null;
+                if (mFwMajor >= 3 || needsAck2x) {
+                    sendBytesAck = new GBDeviceEventSendBytes();
+                    if (mFwMajor >= 3) {
+                        sendBytesAck.encodedBytes = encodeActionResponse(uuid, icon_id, caption);
+                    } else {
+                        sendBytesAck.encodedBytes = encodeActionResponse2x(id, action, 6, caption);
+                    }
+                }
+                return new GBDeviceEvent[]{sendBytesAck, devEvtNotificationControl};
+            }
+            LOG.info("unexpected action: " + action);
+        }
+
+        return null;
+    }
+
+    private GBDeviceEventSendBytes decodePing(ByteBuffer buf) {
+        byte command = buf.get();
+        if (command == PING_PING) {
+            int cookie = buf.getInt();
+            LOG.info("Received PING - will reply");
+            GBDeviceEventSendBytes sendBytes = new GBDeviceEventSendBytes();
+            sendBytes.encodedBytes = encodePing(PING_PONG, cookie);
+            return sendBytes;
+        }
+        return null;
+    }
+
+    private void decodeAppLogs(ByteBuffer buf) {
+        UUID uuid = getUUID(buf);
+        int timestamp = buf.getInt();
+        int logLevel = buf.get() & 0xff;
+        int messageLength = buf.get() & 0xff;
+        int lineNumber = buf.getShort() & 0xffff;
+        String fileName = getFixedString(buf, 16);
+        String message = getFixedString(buf, messageLength);
+        LOG.debug("APP_LOGS (" + logLevel +") from uuid " + uuid.toString() + " in " + fileName + ":" + lineNumber + " " + message);
+    }
+
+    private GBDeviceEvent decodeSystemMessage(ByteBuffer buf) {
+        buf.get(); // unknown;
+        byte command = buf.get();
+        final String ENDPOINT_NAME = "SYSTEM MESSAGE";
+        switch (command) {
+            case SYSTEMMESSAGE_STOPRECONNECTING:
+                LOG.info(ENDPOINT_NAME + ": stop reconnecting");
+                break;
+            case SYSTEMMESSAGE_STARTRECONNECTING:
+                LOG.info(ENDPOINT_NAME + ": start reconnecting");
+                break;
+            default:
+                LOG.info(ENDPOINT_NAME + ": " + command);
+                break;
+        }
+        return null;
+    }
+
+    private GBDeviceEvent[] decodeAppRunState(ByteBuffer buf) {
+        byte command = buf.get();
+        UUID uuid = getUUID(buf);
+        final String ENDPOINT_NAME = "APPRUNSTATE";
+        switch (command) {
+            case APPRUNSTATE_START:
+                LOG.info(ENDPOINT_NAME + ": started " + uuid);
+
+                AppMessageHandler handler = mAppMessageHandlers.get(uuid);
+                if (handler != null) {
+                    currentRunningApp = uuid;
+                    return handler.onAppStart();
+                }
+                else {
+                    if (!uuid.equals(currentRunningApp)) {
+                        currentRunningApp = uuid;
+                        GBDeviceEventAppManagement gbDeviceEventAppManagement = new GBDeviceEventAppManagement();
+                        gbDeviceEventAppManagement.uuid = uuid;
+                        gbDeviceEventAppManagement.type = GBDeviceEventAppManagement.EventType.START;
+                        gbDeviceEventAppManagement.event = GBDeviceEventAppManagement.Event.SUCCESS;
+                        return new GBDeviceEvent[]{gbDeviceEventAppManagement};
+                    }
+                }
+                break;
+            case APPRUNSTATE_STOP:
+                LOG.info(ENDPOINT_NAME + ": stopped " + uuid);
+
+                GBDeviceEventAppManagement gbDeviceEventAppManagement = new GBDeviceEventAppManagement();
+                gbDeviceEventAppManagement.uuid = uuid;
+                gbDeviceEventAppManagement.type = GBDeviceEventAppManagement.EventType.STOP;
+                gbDeviceEventAppManagement.event = GBDeviceEventAppManagement.Event.SUCCESS;
+                return new GBDeviceEvent[]{gbDeviceEventAppManagement};
+            default:
+                LOG.info(ENDPOINT_NAME + ": (cmd:" + command + ")" + uuid);
+                break;
+        }
+        return new GBDeviceEvent[]{null};
+    }
+
+    private GBDeviceEvent decodeBlobDb(ByteBuffer buf) {
+        final String ENDPOINT_NAME = "BLOBDB";
+        final String statusString[] = {
+                "unknown",
+                "success",
+                "general failure",
+                "invalid operation",
+                "invalid database id",
+                "invalid data",
+                "key does not exist",
+                "database full",
+                "data stale",
+        };
+        buf.order(ByteOrder.LITTLE_ENDIAN);
+        short token = buf.getShort();
+        byte status = buf.get();
+
+        if (status >= 0 && status < statusString.length) {
+            LOG.info(ENDPOINT_NAME + ": " + statusString[status] + " (token " + (token & 0xffff) + ")");
+        } else {
+            LOG.warn(ENDPOINT_NAME + ": unknown status " + status + " (token " + (token & 0xffff) + ")");
+        }
+        return null;
+    }
+
+    private GBDeviceEventAppManagement decodeAppFetch(ByteBuffer buf) {
+        byte command = buf.get();
+        if (command == 0x01) {
+            UUID uuid = getUUID(buf);
+            buf.order(ByteOrder.LITTLE_ENDIAN);
+            int app_id = buf.getInt();
+            GBDeviceEventAppManagement fetchRequest = new GBDeviceEventAppManagement();
+            fetchRequest.type = GBDeviceEventAppManagement.EventType.INSTALL;
+            fetchRequest.event = GBDeviceEventAppManagement.Event.REQUEST;
+            fetchRequest.token = app_id;
+            fetchRequest.uuid = uuid;
+            return fetchRequest;
+        }
+        return null;
+    }
+
+    private GBDeviceEvent[] decodeDatalog(ByteBuffer buf, short length) {
+        byte command = buf.get();
+        byte id = buf.get();
+        GBDeviceEvent[] devEvtsDataLogging = null;
+        switch (command) {
+            case DATALOG_TIMEOUT:
+                LOG.info("DATALOG TIMEOUT. id=" + (id & 0xff) + " - ignoring");
+                return null;
+            case DATALOG_SENDDATA:
+                buf.order(ByteOrder.LITTLE_ENDIAN);
+                int items_left = buf.getInt();
+                int crc = buf.getInt();
+                DatalogSession datalogSession = mDatalogSessions.get(id);
+                LOG.info("DATALOG SENDDATA. id=" + (id & 0xff) + ", items_left=" + items_left + ", total length=" + (length - 10));
+                if (datalogSession != null) {
+                    LOG.info("DATALOG UUID=" + datalogSession.uuid + ", tag=" + datalogSession.tag + datalogSession.getTaginfo() + ", itemSize=" + datalogSession.itemSize + ", itemType=" + datalogSession.itemType);
+                    if (!datalogSession.uuid.equals(UUID_ZERO) && datalogSession.getClass().equals(DatalogSession.class) && mEnablePebbleKit) {
+                        devEvtsDataLogging = datalogSession.handleMessageForPebbleKit(buf, length - 10);
+                    } else {
+                        devEvtsDataLogging = datalogSession.handleMessage(buf, length - 10);
+                    }
+                }
+                break;
+            case DATALOG_OPENSESSION:
+                UUID uuid = getUUID(buf);
+                buf.order(ByteOrder.LITTLE_ENDIAN);
+                int timestamp = buf.getInt();
+                int log_tag = buf.getInt();
+                byte item_type = buf.get();
+                short item_size = buf.getShort();
+                LOG.info("DATALOG OPENSESSION. id=" + (id & 0xff) + ", App UUID=" + uuid.toString() + ", log_tag=" + log_tag + ", item_type=" + item_type + ", itemSize=" + item_size);
+                if (!mDatalogSessions.containsKey(id)) {
+                    if (uuid.equals(UUID_ZERO) && log_tag == 78) {
+                        mDatalogSessions.put(id, new DatalogSessionAnalytics(id, uuid, timestamp, log_tag, item_type, item_size, getDevice()));
+                    } else if (uuid.equals(UUID_ZERO) && log_tag == 81) {
+                        mDatalogSessions.put(id, new DatalogSessionHealthSteps(id, uuid, timestamp, log_tag, item_type, item_size, getDevice()));
+                    } else if (uuid.equals(UUID_ZERO) && log_tag == 83) {
+                        mDatalogSessions.put(id, new DatalogSessionHealthSleep(id, uuid, timestamp, log_tag, item_type, item_size, getDevice()));
+                    } else if (uuid.equals(UUID_ZERO) && log_tag == 84) {
+                        mDatalogSessions.put(id, new DatalogSessionHealthOverlayData(id, uuid, timestamp, log_tag, item_type, item_size, getDevice()));
+                    } else if (uuid.equals(UUID_ZERO) && log_tag == 85) {
+                        mDatalogSessions.put(id, new DatalogSessionHealthHR(id, uuid, timestamp, log_tag, item_type, item_size, getDevice()));
+                    } else {
+                        mDatalogSessions.put(id, new DatalogSession(id, uuid, timestamp, log_tag, item_type, item_size));
+                    }
+                }
+                devEvtsDataLogging = new GBDeviceEvent[]{null};
+                break;
+            case DATALOG_CLOSE:
+                LOG.info("DATALOG_CLOSE. id=" + (id & 0xff));
+                datalogSession = mDatalogSessions.get(id);
+                if (datalogSession != null) {
+                    if (!datalogSession.uuid.equals(UUID_ZERO) && datalogSession.getClass().equals(DatalogSession.class) && mEnablePebbleKit) {
+                        GBDeviceEventDataLogging dataLogging = new GBDeviceEventDataLogging();
+                        dataLogging.command = GBDeviceEventDataLogging.COMMAND_FINISH_SESSION;
+                        dataLogging.appUUID = datalogSession.uuid;
+                        dataLogging.tag = datalogSession.tag;
+                        devEvtsDataLogging = new GBDeviceEvent[]{dataLogging, null};
+                    }
+                    if (datalogSession.uuid.equals(UUID_ZERO) && (datalogSession.tag == 81 || datalogSession.tag == 83 || datalogSession.tag == 84)) {
+                        GB.signalActivityDataFinish(getDevice());
+                    }
+                    mDatalogSessions.remove(id);
+                }
+                break;
+            default:
+                LOG.info("unknown DATALOG command: " + (command & 0xff));
+                break;
+        }
+        GBDeviceEventSendBytes sendBytes = new GBDeviceEventSendBytes();
+
+        if (devEvtsDataLogging != null) {
+            // append ack
+            LOG.info("sending ACK (0x85)");
+            sendBytes.encodedBytes = encodeDatalog(id, DATALOG_ACK);
+            devEvtsDataLogging[devEvtsDataLogging.length - 1] = sendBytes;
+        } else {
+            LOG.info("sending NACK (0x86)");
+            sendBytes.encodedBytes = encodeDatalog(id, DATALOG_NACK);
+            devEvtsDataLogging = new GBDeviceEvent[]{sendBytes};
+        }
+        return devEvtsDataLogging;
+    }
+
+    private GBDeviceEvent decodeAppReorder(ByteBuffer buf) {
+        byte status = buf.get();
+        if (status == 1) {
+            LOG.info("app reordering successful");
+        } else {
+            LOG.info("app reordering returned status " + status);
+        }
+        return null;
+    }
+
+    private GBDeviceEvent decodeVoiceControl(ByteBuffer buf) {
+        buf.order(ByteOrder.LITTLE_ENDIAN);
+        byte command = buf.get();
+        int flags = buf.getInt();
+        byte session_type = buf.get(); //0x01 dictation 0x02 command
+        short session_id = buf.getShort();
+        //attributes
+        byte count = buf.get();
+        byte type = buf.get();
+        short length = buf.getShort();
+        byte[] version = new byte[20];
+        buf.get(version); //it's a string like "1.2rc1"
+        int sample_rate = buf.getInt();
+        short bit_rate = buf.getShort();
+        byte bitstream_version = buf.get();
+        short frame_size = buf.getShort();
+
+        GBDeviceEventSendBytes sendBytes = new GBDeviceEventSendBytes();
+        if (command == 0x01) { //session setup
+            int replLenght = 7;
+            byte replStatus = 5; // 5 = disabled,  change to 0 to send success
+            ByteBuffer repl = ByteBuffer.allocate(LENGTH_PREFIX + replLenght);
+            repl.order(ByteOrder.BIG_ENDIAN);
+            repl.putShort((short) replLenght);
+            repl.putShort(ENDPOINT_VOICECONTROL);
+            repl.put(command);
+            repl.putInt(flags);
+            repl.put(session_type);
+            repl.put(replStatus);
+
+            sendBytes.encodedBytes = repl.array();
+
+        } else if (command == 0x02) { //dictation result (possibly it is something we send, not something we receive)
+            sendBytes.encodedBytes = null;
+        }
+        return sendBytes;
+    }
+
+    private GBDeviceEvent decodeAudioStream(ByteBuffer buf) {
+
+        return null;
+    }
+
+    @Override
+    public GBDeviceEvent[] decodeResponse(byte[] responseData) {
+        ByteBuffer buf = ByteBuffer.wrap(responseData);
+        buf.order(ByteOrder.BIG_ENDIAN);
+        short length = buf.getShort();
+        short endpoint = buf.getShort();
+        GBDeviceEvent devEvts[] = null;
+        byte pebbleCmd;
+        switch (endpoint) {
+            case ENDPOINT_MUSICCONTROL:
+                pebbleCmd = buf.get();
+                GBDeviceEventMusicControl musicCmd = new GBDeviceEventMusicControl();
+                switch (pebbleCmd) {
+                    case MUSICCONTROL_NEXT:
+                        musicCmd.event = GBDeviceEventMusicControl.Event.NEXT;
+                        break;
+                    case MUSICCONTROL_PREVIOUS:
+                        musicCmd.event = GBDeviceEventMusicControl.Event.PREVIOUS;
+                        break;
+                    case MUSICCONTROL_PLAY:
+                        musicCmd.event = GBDeviceEventMusicControl.Event.PLAY;
+                        break;
+                    case MUSICCONTROL_PAUSE:
+                        musicCmd.event = GBDeviceEventMusicControl.Event.PAUSE;
+                        break;
+                    case MUSICCONTROL_PLAYPAUSE:
+                        musicCmd.event = GBDeviceEventMusicControl.Event.PLAYPAUSE;
+                        break;
+                    case MUSICCONTROL_VOLUMEUP:
+                        musicCmd.event = GBDeviceEventMusicControl.Event.VOLUMEUP;
+                        break;
+                    case MUSICCONTROL_VOLUMEDOWN:
+                        musicCmd.event = GBDeviceEventMusicControl.Event.VOLUMEDOWN;
+                        break;
+                    default:
+                        break;
+                }
+                devEvts = new GBDeviceEvent[]{musicCmd};
+                break;
+            case ENDPOINT_PHONECONTROL:
+                pebbleCmd = buf.get();
+                GBDeviceEventCallControl callCmd = new GBDeviceEventCallControl();
+                switch (pebbleCmd) {
+                    case PHONECONTROL_HANGUP:
+                        callCmd.event = GBDeviceEventCallControl.Event.END;
+                        break;
+                    default:
+                        LOG.info("Unknown PHONECONTROL event" + pebbleCmd);
+                        break;
+                }
+                devEvts = new GBDeviceEvent[]{callCmd};
+                break;
+            case ENDPOINT_FIRMWAREVERSION:
+                pebbleCmd = buf.get();
+                GBDeviceEventVersionInfo versionCmd = new GBDeviceEventVersionInfo();
+
+                buf.getInt(); // skip
+                versionCmd.fwVersion = getFixedString(buf, 32);
+
+                mFwMajor = versionCmd.fwVersion.charAt(1) - 48;
+                LOG.info("Pebble firmware major detected as " + mFwMajor);
+
+                byte[] tmp = new byte[9];
+                buf.get(tmp, 0, 9);
+                int hwRev = buf.get() + 8;
+                if (hwRev >= 0 && hwRev < hwRevisions.length) {
+                    versionCmd.hwVersion = hwRevisions[hwRev];
+                }
+                devEvts = new GBDeviceEvent[]{versionCmd};
+                break;
+            case ENDPOINT_APPMANAGER:
+                pebbleCmd = buf.get();
+                switch (pebbleCmd) {
+                    case APPMANAGER_GETAPPBANKSTATUS:
+                        GBDeviceEventAppInfo appInfoCmd = new GBDeviceEventAppInfo();
+                        int slotCount = buf.getInt();
+                        int slotsUsed = buf.getInt();
+                        appInfoCmd.apps = new GBDeviceApp[slotsUsed];
+                        boolean[] slotInUse = new boolean[slotCount];
+
+                        for (int i = 0; i < slotsUsed; i++) {
+                            int id = buf.getInt();
+                            int index = buf.getInt();
+                            slotInUse[index] = true;
+                            String appName = getFixedString(buf, 32);
+                            String appCreator = getFixedString(buf, 32);
+
+                            int flags = buf.getInt();
+
+                            GBDeviceApp.Type appType;
+                            if ((flags & 16) == 16) {  // FIXME: verify this assumption
+                                appType = GBDeviceApp.Type.APP_ACTIVITYTRACKER;
+                            } else if ((flags & 1) == 1) {  // FIXME: verify this assumption
+                                appType = GBDeviceApp.Type.WATCHFACE;
+                            } else {
+                                appType = GBDeviceApp.Type.APP_GENERIC;
+                            }
+                            short appVersion = buf.getShort();
+                            appInfoCmd.apps[i] = new GBDeviceApp(tmpUUIDS.get(i), appName, appCreator, String.valueOf(appVersion), appType);
+                        }
+                        for (int i = 0; i < slotCount; i++) {
+                            if (!slotInUse[i]) {
+                                appInfoCmd.freeSlot = (byte) i;
+                                LOG.info("found free slot " + i);
+                                break;
+                            }
+                        }
+                        devEvts = new GBDeviceEvent[]{appInfoCmd};
+                        break;
+                    case APPMANAGER_GETUUIDS:
+                        GBDeviceEventSendBytes sendBytes = new GBDeviceEventSendBytes();
+                        sendBytes.encodedBytes = encodeSimpleMessage(ENDPOINT_APPMANAGER, APPMANAGER_GETAPPBANKSTATUS);
+                        devEvts = new GBDeviceEvent[]{sendBytes};
+                        tmpUUIDS.clear();
+                        slotsUsed = buf.getInt();
+                        for (int i = 0; i < slotsUsed; i++) {
+                            UUID uuid = getUUID(buf);
+                            LOG.info("found uuid: " + uuid);
+                            tmpUUIDS.add(uuid);
+                        }
+                        break;
+                    case APPMANAGER_REMOVEAPP:
+                        GBDeviceEventAppManagement deleteRes = new GBDeviceEventAppManagement();
+                        deleteRes.type = GBDeviceEventAppManagement.EventType.DELETE;
+
+                        int result = buf.getInt();
+                        switch (result) {
+                            case APPMANAGER_RES_SUCCESS:
+                                deleteRes.event = GBDeviceEventAppManagement.Event.SUCCESS;
+                                break;
+                            default:
+                                deleteRes.event = GBDeviceEventAppManagement.Event.FAILURE;
+                                break;
+                        }
+                        devEvts = new GBDeviceEvent[]{deleteRes};
+                        break;
+                    default:
+                        LOG.info("Unknown APPMANAGER event" + pebbleCmd);
+                        break;
+                }
+                break;
+            case ENDPOINT_PUTBYTES:
+                pebbleCmd = buf.get();
+                GBDeviceEventAppManagement installRes = new GBDeviceEventAppManagement();
+                installRes.type = GBDeviceEventAppManagement.EventType.INSTALL;
+                switch (pebbleCmd) {
+                    case PUTBYTES_INIT:
+                        installRes.token = buf.getInt();
+                        installRes.event = GBDeviceEventAppManagement.Event.SUCCESS;
+                        break;
+                    default:
+                        installRes.token = buf.getInt();
+                        installRes.event = GBDeviceEventAppManagement.Event.FAILURE;
+                        break;
+                }
+                devEvts = new GBDeviceEvent[]{installRes};
+                break;
+            case ENDPOINT_APPLICATIONMESSAGE:
+            case ENDPOINT_LAUNCHER:
+                pebbleCmd = buf.get();
+                last_id = buf.get();
+                UUID uuid = getUUID(buf);
+
+                switch (pebbleCmd) {
+                    case APPLICATIONMESSAGE_PUSH:
+                        LOG.info((endpoint == ENDPOINT_LAUNCHER ? "got LAUNCHER PUSH from UUID : " : "got APPLICATIONMESSAGE PUSH from UUID : ")  + uuid);
+                        AppMessageHandler handler = mAppMessageHandlers.get(uuid);
+                        if (handler != null) {
+                            currentRunningApp = uuid;
+                            if (handler.isEnabled()) {
+                                if (endpoint == ENDPOINT_APPLICATIONMESSAGE) {
+                                    ArrayList<Pair<Integer, Object>> dict = decodeDict(buf);
+                                    devEvts = handler.handleMessage(dict);
+                                }
+                                else {
+                                    devEvts = handler.onAppStart();
+                                }
+                            } else {
+                                devEvts = new GBDeviceEvent[]{null};
+                            }
+                        } else {
+                            try {
+                                devEvts = decodeDictToJSONAppMessage(uuid, buf);
+                            } catch (JSONException e) {
+                                LOG.error(e.getMessage());
+                            }
+                            if (!uuid.equals(currentRunningApp)) {
+                                GBDeviceEventAppManagement gbDeviceEventAppManagement = new GBDeviceEventAppManagement();
+                                gbDeviceEventAppManagement.uuid = uuid;
+                                gbDeviceEventAppManagement.type = GBDeviceEventAppManagement.EventType.START;
+                                gbDeviceEventAppManagement.event = GBDeviceEventAppManagement.Event.SUCCESS;
+
+                                // prepend the
+                                GBDeviceEvent concatEvents[] = new GBDeviceEvent[(devEvts != null ? devEvts.length : 0) + 1];
+                                concatEvents[0] = gbDeviceEventAppManagement;
+                                if (devEvts != null) {
+                                    System.arraycopy(devEvts, 0, concatEvents, 1, devEvts.length);
+                                }
+                                devEvts = concatEvents;
+                            }
+                        }
+                        currentRunningApp = uuid;
+                        break;
+                    case APPLICATIONMESSAGE_ACK:
+                    case APPLICATIONMESSAGE_NACK:
+                        if (pebbleCmd == APPLICATIONMESSAGE_ACK) {
+                            LOG.info("got APPLICATIONMESSAGE/LAUNCHER (EP " + endpoint + ") ACK");
+                        } else {
+                            LOG.info("got APPLICATIONMESSAGE/LAUNCHER (EP " + endpoint + ") NACK");
+                        }
+                        GBDeviceEventAppMessage evtAppMessage = null;
+                        if (endpoint == ENDPOINT_APPLICATIONMESSAGE && idLookup[last_id & 0xff] != null) {
+                            evtAppMessage = new GBDeviceEventAppMessage();
+                            if (pebbleCmd == APPLICATIONMESSAGE_ACK) {
+                                evtAppMessage.type = GBDeviceEventAppMessage.TYPE_ACK;
+                            } else {
+                                evtAppMessage.type = GBDeviceEventAppMessage.TYPE_NACK;
+                            }
+                            evtAppMessage.id = idLookup[last_id & 0xff];
+                            evtAppMessage.appUUID = currentRunningApp;
+                        }
+                        devEvts = new GBDeviceEvent[]{evtAppMessage};
+                        break;
+                    case APPLICATIONMESSAGE_REQUEST:
+                        LOG.info("got APPLICATIONMESSAGE/LAUNCHER (EP " + endpoint + ")  REQUEST");
+                        devEvts = new GBDeviceEvent[]{null};
+                        break;
+                    default:
+                        break;
+                }
+                break;
+            case ENDPOINT_PHONEVERSION:
+                pebbleCmd = buf.get();
+                switch (pebbleCmd) {
+                    case PHONEVERSION_REQUEST:
+                        LOG.info("Pebble asked for Phone/App Version - repLYING!");
+                        GBDeviceEventSendBytes sendBytes = new GBDeviceEventSendBytes();
+                        sendBytes.encodedBytes = encodePhoneVersion(PHONEVERSION_REMOTE_OS_ANDROID);
+                        devEvts = new GBDeviceEvent[]{sendBytes};
+                        break;
+                    default:
+                        break;
+                }
+                break;
+            case ENDPOINT_DATALOG:
+                devEvts = decodeDatalog(buf, length);
+                break;
+            case ENDPOINT_SCREENSHOT:
+                devEvts = new GBDeviceEvent[]{decodeScreenshot(buf, length)};
+                break;
+            case ENDPOINT_EXTENSIBLENOTIFS:
+            case ENDPOINT_NOTIFICATIONACTION:
+                devEvts = decodeAction(buf);
+                break;
+            case ENDPOINT_PING:
+                devEvts = new GBDeviceEvent[]{decodePing(buf)};
+                break;
+            case ENDPOINT_APPFETCH:
+                devEvts = new GBDeviceEvent[]{decodeAppFetch(buf)};
+                break;
+            case ENDPOINT_SYSTEMMESSAGE:
+                devEvts = new GBDeviceEvent[]{decodeSystemMessage(buf)};
+                break;
+            case ENDPOINT_APPRUNSTATE:
+                devEvts = decodeAppRunState(buf);
+                break;
+            case ENDPOINT_BLOBDB:
+                devEvts = new GBDeviceEvent[]{decodeBlobDb(buf)};
+                break;
+            case ENDPOINT_APPREORDER:
+                devEvts = new GBDeviceEvent[]{decodeAppReorder(buf)};
+                break;
+            case ENDPOINT_APPLOGS:
+                decodeAppLogs(buf);
+                break;
+            case ENDPOINT_VOICECONTROL:
+                devEvts = new GBDeviceEvent[]{decodeVoiceControl(buf)};
+                break;
+            case ENDPOINT_AUDIOSTREAM:
+                devEvts = new GBDeviceEvent[]{decodeAudioStream(buf)};
+//                LOG.debug("AUDIOSTREAM DATA: " + GB.hexdump(responseData, 4, length));
+                break;
+            default:
+                break;
+        }
+
+        return devEvts;
+    }
+
+    void setForceProtocol(boolean force) {
+        LOG.info("setting force protocol to " + force);
+        mForceProtocol = force;
+    }
+
+    void setAlwaysACKPebbleKit(boolean alwaysACKPebbleKit) {
+        LOG.info("setting always ACK PebbleKit to " + alwaysACKPebbleKit);
+        mAlwaysACKPebbleKit = alwaysACKPebbleKit;
+    }
+
+    void setEnablePebbleKit(boolean enablePebbleKit) {
+        LOG.info("setting enable PebbleKit support to " + enablePebbleKit);
+        mEnablePebbleKit = enablePebbleKit;
+    }
+
+    boolean hasAppMessageHandler(UUID uuid) {
+        return mAppMessageHandlers.containsKey(uuid);
+    }
+
+    private String getFixedString(ByteBuffer buf, int length) {
+        byte[] tmp = new byte[length];
+        buf.get(tmp, 0, length);
+
+        return new String(tmp).trim();
+    }
+
+    private UUID getUUID(ByteBuffer buf) {
+        ByteOrder byteOrder = buf.order();
+        buf.order(ByteOrder.BIG_ENDIAN);
+        long uuid_high = buf.getLong();
+        long uuid_low = buf.getLong();
+        buf.order(byteOrder);
+        return new UUID(uuid_high, uuid_low);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleSupport.java
new file mode 100644
index 0000000..8e09266
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleSupport.java
@@ -0,0 +1,263 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, Arjan Schrijver, Carsten
+    Pfeiffer, Daniele Gobbetti, Kasha, Sebastian Kranz, Steffen Liebergeld,
+    Taavi Eom盲e
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.pebble;
+
+import android.content.IntentFilter;
+import android.net.Uri;
+import android.util.Pair;
+
+import androidx.core.content.ContextCompat;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.SettingsActivity;
+import nodomain.freeyourgadget.gadgetbridge.externalevents.AlarmReceiver;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+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.serial.AbstractSerialDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceIoThread;
+import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol;
+
+public class PebbleSupport extends AbstractSerialDeviceSupport {
+    private static final Logger LOG = LoggerFactory.getLogger(PebbleSupport.class);
+    private AlarmReceiver mAlarmReceiver = null;
+
+    @Override
+    public void dispose() {
+        super.dispose();
+        unregisterSunriseSunsetAlarmReceiver();
+    }
+
+    private void registerSunriseSunsetAlarmReceiver() {
+        if (!getDevicePrefs().getBoolean("send_sunrise_sunset", false)) {
+            LOG.info("won't register sunrise and sunset receiver (disabled in preferences)");
+            return;
+        }
+        unregisterSunriseSunsetAlarmReceiver();
+        LOG.info("registering sunrise and sunset receiver");
+        this.mAlarmReceiver = new AlarmReceiver();
+        ContextCompat.registerReceiver(GBApplication.getContext(), mAlarmReceiver, new IntentFilter("DAILY_ALARM"), ContextCompat.RECEIVER_EXPORTED);
+    }
+
+    private void unregisterSunriseSunsetAlarmReceiver() {
+        if (mAlarmReceiver != null) {
+            GBApplication.getContext().unregisterReceiver(mAlarmReceiver);
+            mAlarmReceiver = null;
+        }
+    }
+
+    @Override
+    public boolean connect() {
+        getDeviceIOThread().start();
+        registerSunriseSunsetAlarmReceiver();
+        return true;
+    }
+
+    @Override
+    protected GBDeviceProtocol createDeviceProtocol() {
+        return new PebbleProtocol(getDevice());
+    }
+
+    @Override
+    protected GBDeviceIoThread createDeviceIOThread() {
+        return new PebbleIoThread(PebbleSupport.this, getDevice(), getDeviceProtocol(), getBluetoothAdapter(), getContext());
+    }
+
+    @Override
+    public boolean useAutoConnect() {
+        return true;
+    }
+
+    @Override
+    public void onInstallApp(Uri uri) {
+        PebbleProtocol pebbleProtocol = (PebbleProtocol) getDeviceProtocol();
+        PebbleIoThread pebbleIoThread = getDeviceIOThread();
+        // Catch fake URLs first
+        if (uri.equals(Uri.parse("fake://health"))) {
+            getDeviceIOThread().write(pebbleProtocol.encodeActivateHealth(true));
+            String units = GBApplication.getPrefs().getString(SettingsActivity.PREF_MEASUREMENT_SYSTEM, getContext().getString(R.string.p_unit_metric));
+            if (units.equals(getContext().getString(R.string.p_unit_metric))) {
+                pebbleIoThread.write(pebbleProtocol.encodeSetSaneDistanceUnit(true));
+            } else {
+                pebbleIoThread.write(pebbleProtocol.encodeSetSaneDistanceUnit(false));
+            }
+            return;
+        }
+        if (uri.equals(Uri.parse("fake://hrm"))) {
+            getDeviceIOThread().write(pebbleProtocol.encodeActivateHRM(true));
+            return;
+        }
+        if (uri.equals(Uri.parse("fake://weather"))) {
+            getDeviceIOThread().write(pebbleProtocol.encodeActivateWeather(true));
+            return;
+        }
+
+        // it is a real app
+        pebbleIoThread.installApp(uri, 0);
+    }
+
+    @Override
+    public void onAppConfiguration(UUID uuid, String config, Integer id) {
+        try {
+            ArrayList<Pair<Integer, Object>> pairs = new ArrayList<>();
+
+            JSONObject json = new JSONObject(config);
+            Iterator<String> keysIterator = json.keys();
+            while (keysIterator.hasNext()) {
+                String keyStr = keysIterator.next();
+                Object object = json.get(keyStr);
+                if (object instanceof JSONArray) {
+                    JSONArray jsonArray = (JSONArray) object;
+                    byte[] byteArray = new byte[jsonArray.length()];
+                    for (int i = 0; i < jsonArray.length(); i++) {
+                        byteArray[i] = ((Integer) jsonArray.get(i)).byteValue();
+                    }
+                    object = byteArray;
+                } else if (object instanceof Boolean) {
+                    object = (short) (((Boolean) object) ? 1 : 0);
+                } else if (object instanceof Double) {
+                    object = ((Double) object).intValue();
+                }
+                pairs.add(new Pair<>(Integer.parseInt(keyStr), object));
+            }
+            getDeviceIOThread().write(((PebbleProtocol) getDeviceProtocol()).encodeApplicationMessagePush(PebbleProtocol.ENDPOINT_APPLICATIONMESSAGE, uuid, pairs, id));
+        } catch (JSONException e) {
+            LOG.error("Error while parsing JSON", e);
+        }
+    }
+
+    @Override
+    public synchronized PebbleIoThread getDeviceIOThread() {
+        return (PebbleIoThread) super.getDeviceIOThread();
+    }
+
+    private boolean reconnect() {
+        if (!isConnected() && useAutoConnect()) {
+            if (getDevice().getState() == GBDevice.State.WAITING_FOR_RECONNECT) {
+                gbDeviceIOThread.quit();
+                gbDeviceIOThread.interrupt();
+                gbDeviceIOThread = null;
+                if (!connect()) {
+                    return false;
+                }
+                try {
+                    Thread.sleep(4000); // this is about the time the connect takes, so the notification can come though
+                } catch (InterruptedException ignored) {
+                }
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public void onNotification(NotificationSpec notificationSpec) {
+        String currentPrivacyMode = GBApplication.getDevicePrefs(gbDevice).getString("pebble_pref_privacy_mode", getContext().getString(R.string.p_pebble_privacy_mode_off));
+        if (getContext().getString(R.string.p_pebble_privacy_mode_complete).equals(currentPrivacyMode)) {
+            notificationSpec.body = null;
+            notificationSpec.sender = null;
+            notificationSpec.subject = null;
+            notificationSpec.title = null;
+            notificationSpec.phoneNumber = null;
+        } else if (getContext().getString(R.string.p_pebble_privacy_mode_content).equals(currentPrivacyMode)) {
+            if (notificationSpec.sender != null) {
+                notificationSpec.sender = "\n\n\n\n\n" + notificationSpec.sender;
+            } else if (notificationSpec.title != null) {
+                notificationSpec.title = "\n\n\n\n\n" + notificationSpec.title;
+            } else if (notificationSpec.subject != null) {
+                notificationSpec.subject = "\n\n\n\n\n" + notificationSpec.subject;
+            }
+        }
+        if (reconnect()) {
+            super.onNotification(notificationSpec);
+        }
+    }
+
+    @Override
+    public void onSetCallState(CallSpec callSpec) {
+        if (reconnect()) {
+            if ((callSpec.command != CallSpec.CALL_OUTGOING) || GBApplication.getDevicePrefs(gbDevice).getBoolean("pebble_enable_outgoing_call", true)) {
+                super.onSetCallState(callSpec);
+            }
+        }
+    }
+
+    @Override
+    public void onSetMusicState(MusicStateSpec musicStateSpec) {
+        if (reconnect()) {
+            super.onSetMusicState(musicStateSpec);
+        }
+    }
+
+    @Override
+    public void onSetMusicInfo(MusicSpec musicSpec) {
+        if (reconnect()) {
+            super.onSetMusicInfo(musicSpec);
+        }
+    }
+
+    @Override
+    public void onAddCalendarEvent(CalendarEventSpec calendarEventSpec) {
+        if (reconnect()) {
+            super.onAddCalendarEvent(calendarEventSpec);
+        }
+    }
+
+    @Override
+    public void onDeleteCalendarEvent(byte type, long id) {
+        if (reconnect()) {
+            super.onDeleteCalendarEvent(type, id);
+        }
+    }
+
+    @Override
+    public void onSendConfiguration(String config) {
+        if (reconnect()) {
+            super.onSendConfiguration(config);
+        }
+    }
+
+    @Override
+    public void onTestNewFunction() {
+        if (reconnect()) {
+            super.onTestNewFunction();
+        }
+    }
+
+    @Override
+    public void onSendWeather(ArrayList<WeatherSpec> weatherSpecs) {
+        if (reconnect()) {
+            super.onSendWeather(weatherSpecs);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/ble/PebbleGATTClient.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/ble/PebbleGATTClient.java
new file mode 100644
index 0000000..0759741
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/ble/PebbleGATTClient.java
@@ -0,0 +1,300 @@
+/*  Copyright (C) 2016-2024 Andreas Shimokawa, Taavi Eom盲e
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.ble;
+
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCallback;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.bluetooth.BluetoothGattDescriptor;
+import android.content.Context;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.lang.reflect.Method;
+import java.util.UUID;
+import java.util.concurrent.CountDownLatch;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+import static android.bluetooth.BluetoothGattCharacteristic.FORMAT_UINT16;
+import static android.bluetooth.BluetoothGattCharacteristic.PROPERTY_WRITE;
+
+
+class PebbleGATTClient extends BluetoothGattCallback {
+
+    private static final Logger LOG = LoggerFactory.getLogger(PebbleGATTClient.class);
+
+    private static final UUID SERVICE_UUID = UUID.fromString("0000fed9-0000-1000-8000-00805f9b34fb");
+    private static final UUID CONNECTIVITY_CHARACTERISTIC = UUID.fromString("00000001-328E-0FBB-C642-1AA6699BDADA");
+    private static final UUID PAIRING_TRIGGER_CHARACTERISTIC = UUID.fromString("00000002-328E-0FBB-C642-1AA6699BDADA");
+    private static final UUID MTU_CHARACTERISTIC = UUID.fromString("00000003-328e-0fbb-c642-1aa6699bdada");
+    private static final UUID CONNECTION_PARAMETERS_CHARACTERISTIC = UUID.fromString("00000005-328E-0FBB-C642-1AA6699BDADA");
+    private static final UUID CHARACTERISTIC_CONFIGURATION_DESCRIPTOR = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");
+
+    //PPoGATT service (Pebble side)
+    private static final UUID PPOGATT_SERVICE_UUID = UUID.fromString("30000003-328E-0FBB-C642-1AA6699BDADA");
+    private static final UUID PPOGATT_CHARACTERISTIC_READ = UUID.fromString("30000004-328E-0FBB-C642-1AA6699BDADA");
+    private static final UUID PPOGATT_CHARACTERISTIC_WRITE = UUID.fromString("30000006-328E-0FBB-C642-1AA6699BDADA");
+
+    private BluetoothGattCharacteristic writeCharacteristics;
+
+    private final Context mContext;
+    private final PebbleLESupport mPebbleLESupport;
+
+    private boolean oldPebble = false;
+    private final boolean doPairing = true;
+    private final boolean removeBond = false;
+    private BluetoothGatt mBluetoothGatt;
+
+    private CountDownLatch mWaitWriteCompleteLatch;
+
+    PebbleGATTClient(PebbleLESupport pebbleLESupport, Context context, BluetoothDevice btDevice) {
+        mContext = context;
+        mPebbleLESupport = pebbleLESupport;
+        connectToPebble(btDevice);
+    }
+
+    public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
+        if (!mPebbleLESupport.isExpectedDevice(gatt.getDevice())) {
+            return;
+        }
+
+        if (characteristic.getUuid().equals(MTU_CHARACTERISTIC)) {
+            int newMTU = characteristic.getIntValue(FORMAT_UINT16, 0);
+            LOG.info("Pebble requested MTU: " + newMTU);
+            mPebbleLESupport.setMTU(newMTU);
+        } else if (characteristic.getUuid().equals(PPOGATT_CHARACTERISTIC_READ)) {
+            mPebbleLESupport.handlePPoGATTPacket(characteristic.getValue().clone());
+        } else {
+            LOG.info("onCharacteristicChanged() " + characteristic.getUuid().toString() + " " + GB.hexdump(characteristic.getValue(), 0, -1));
+        }
+    }
+
+    public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
+        if (!mPebbleLESupport.isExpectedDevice(gatt.getDevice())) {
+            return;
+        }
+
+        LOG.info("onCharacteristicRead() status = " + status);
+        if (status == BluetoothGatt.GATT_SUCCESS) {
+            LOG.info("onCharacteristicRead() " + characteristic.getUuid().toString() + " " + GB.hexdump(characteristic.getValue(), 0, -1));
+
+            if (oldPebble) {
+                subscribeToConnectivity(gatt);
+            } else {
+                subscribeToConnectionParams(gatt);
+            }
+        }
+    }
+
+    public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
+        if (!mPebbleLESupport.isExpectedDevice(gatt.getDevice())) {
+            return;
+        }
+
+        LOG.info("onConnectionStateChange() status = " + status + " newState = " + newState);
+        if (newState == BluetoothGatt.STATE_CONNECTED) {
+            LOG.info("calling discoverServices()");
+            gatt.discoverServices();
+        } else if (newState == BluetoothGatt.STATE_DISCONNECTED) {
+            mPebbleLESupport.close();
+        }
+    }
+
+    public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
+        if (!mPebbleLESupport.isExpectedDevice(gatt.getDevice())) {
+            return;
+        }
+        if (characteristic.getUuid().equals(PPOGATT_CHARACTERISTIC_WRITE)) {
+            if (status != BluetoothGatt.GATT_SUCCESS) {
+                LOG.error("something went wrong when writing to PPoGATT characteristics");
+            }
+            if (mWaitWriteCompleteLatch != null) {
+                mWaitWriteCompleteLatch.countDown();
+            } else {
+                LOG.warn("mWaitWriteCompleteLatch is null!");
+            }
+        } else if (characteristic.getUuid().equals(PAIRING_TRIGGER_CHARACTERISTIC) || characteristic.getUuid().equals(CONNECTIVITY_CHARACTERISTIC)) {
+            //mBtDevice.createBond(); // did not work when last tried
+
+            if (oldPebble) {
+                subscribeToConnectivity(gatt);
+            } else {
+                subscribeToConnectionParams(gatt);
+            }
+        } else if (characteristic.getUuid().equals(MTU_CHARACTERISTIC)) {
+            gatt.requestMtu(339);
+        }
+    }
+
+    public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor bluetoothGattDescriptor, int status) {
+        if (!mPebbleLESupport.isExpectedDevice(gatt.getDevice())) {
+            return;
+        }
+
+        LOG.info("onDescriptorWrite() status=" + status);
+
+        UUID CHARACTERISTICUUID = bluetoothGattDescriptor.getCharacteristic().getUuid();
+
+        if (CHARACTERISTICUUID.equals(CONNECTION_PARAMETERS_CHARACTERISTIC)) {
+            subscribeToConnectivity(gatt);
+        } else if (CHARACTERISTICUUID.equals(CONNECTIVITY_CHARACTERISTIC)) {
+            subscribeToMTU(gatt);
+        } else if (CHARACTERISTICUUID.equals(MTU_CHARACTERISTIC)) {
+            if (mPebbleLESupport.clientOnly) {
+                subscribeToPPoGATT(gatt);
+            } else {
+                setMTU(gatt);
+            }
+        } else if (CHARACTERISTICUUID.equals(PPOGATT_CHARACTERISTIC_READ)) {
+            setMTU(gatt);
+        }
+    }
+
+    public void onServicesDiscovered(BluetoothGatt gatt, int status) {
+        if (!mPebbleLESupport.isExpectedDevice(gatt.getDevice())) {
+            return;
+        }
+
+        LOG.info("onServicesDiscovered() status = " + status);
+        if (status == BluetoothGatt.GATT_SUCCESS) {
+            BluetoothGattCharacteristic connectionPararmharacteristic = gatt.getService(SERVICE_UUID).getCharacteristic(CONNECTION_PARAMETERS_CHARACTERISTIC);
+            oldPebble = connectionPararmharacteristic == null;
+
+            if (oldPebble) {
+                LOG.info("This seems to be an older le enabled pebble");
+            }
+
+            if (doPairing) {
+                BluetoothGattCharacteristic characteristic = gatt.getService(SERVICE_UUID).getCharacteristic(PAIRING_TRIGGER_CHARACTERISTIC);
+                if ((characteristic.getProperties() & PROPERTY_WRITE) != 0) {
+                    LOG.info("This seems to be a >=4.0 FW Pebble, writing to pairing trigger");
+                    // flags:
+                    // 0 - always 1
+                    // 1 - unknown
+                    // 2 - always 0
+                    // 3 - unknown, set on kitkat (seems to help to get a "better" pairing)
+                    // 4 - unknown, set on some phones
+                    if (mPebbleLESupport.clientOnly) {
+                        characteristic.setValue(new byte[]{0x11}); // needed in clientOnly mode (TODO: try 0x19)
+                    } else {
+                        characteristic.setValue(new byte[]{0x09}); // I just keep this, because it worked
+                    }
+                    gatt.writeCharacteristic(characteristic);
+                } else {
+                    LOG.info("This seems to be some <4.0 FW Pebble, reading pairing trigger");
+                    gatt.readCharacteristic(characteristic);
+                }
+            } else {
+                if (oldPebble) {
+                    subscribeToConnectivity(gatt);
+                } else {
+                    subscribeToConnectionParams(gatt);
+                }
+            }
+        }
+    }
+
+    private void connectToPebble(BluetoothDevice btDevice) {
+        if (removeBond) {
+            try {
+                // TODO: Use BondingUtil
+                Method m = btDevice.getClass().getMethod("removeBond", (Class[]) null);
+                m.invoke(btDevice, (Object[]) null);
+            } catch (Exception e) {
+                LOG.warn(e.getMessage());
+            }
+            try {
+                Thread.sleep(1000);
+            } catch (InterruptedException ignore) {
+            }
+        }
+        if (mBluetoothGatt != null) {
+            this.close();
+        }
+        mBluetoothGatt = btDevice.connectGatt(mContext, false, this);
+    }
+
+    private void subscribeToConnectivity(BluetoothGatt gatt) {
+        LOG.info("subscribing to connectivity characteristic");
+        BluetoothGattDescriptor descriptor = gatt.getService(SERVICE_UUID).getCharacteristic(CONNECTIVITY_CHARACTERISTIC).getDescriptor(CHARACTERISTIC_CONFIGURATION_DESCRIPTOR);
+        descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
+        gatt.writeDescriptor(descriptor);
+        gatt.setCharacteristicNotification(gatt.getService(SERVICE_UUID).getCharacteristic(CONNECTIVITY_CHARACTERISTIC), true);
+    }
+
+    private void subscribeToMTU(BluetoothGatt gatt) {
+        LOG.info("subscribing to mtu characteristic");
+        BluetoothGattDescriptor descriptor = gatt.getService(SERVICE_UUID).getCharacteristic(MTU_CHARACTERISTIC).getDescriptor(CHARACTERISTIC_CONFIGURATION_DESCRIPTOR);
+        descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
+        gatt.writeDescriptor(descriptor);
+        gatt.setCharacteristicNotification(gatt.getService(SERVICE_UUID).getCharacteristic(MTU_CHARACTERISTIC), true);
+    }
+
+    private void subscribeToConnectionParams(BluetoothGatt gatt) {
+        LOG.info("subscribing to connection parameters characteristic");
+        BluetoothGattDescriptor descriptor = gatt.getService(SERVICE_UUID).getCharacteristic(CONNECTION_PARAMETERS_CHARACTERISTIC).getDescriptor(CHARACTERISTIC_CONFIGURATION_DESCRIPTOR);
+        descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
+        gatt.writeDescriptor(descriptor);
+        gatt.setCharacteristicNotification(gatt.getService(SERVICE_UUID).getCharacteristic(CONNECTION_PARAMETERS_CHARACTERISTIC), true);
+    }
+
+    private void setMTU(BluetoothGatt gatt) {
+        LOG.info("setting MTU");
+        BluetoothGattCharacteristic characteristic = gatt.getService(SERVICE_UUID).getCharacteristic(MTU_CHARACTERISTIC);
+        BluetoothGattDescriptor descriptor = characteristic.getDescriptor(CHARACTERISTIC_CONFIGURATION_DESCRIPTOR);
+        descriptor.setValue(new byte[]{0x0b, 0x01}); // unknown
+        gatt.writeCharacteristic(characteristic);
+    }
+
+    private void subscribeToPPoGATT(BluetoothGatt gatt) {
+        LOG.info("subscribing to PPoGATT read characteristic");
+        BluetoothGattDescriptor descriptor = gatt.getService(PPOGATT_SERVICE_UUID).getCharacteristic(PPOGATT_CHARACTERISTIC_READ).getDescriptor(CHARACTERISTIC_CONFIGURATION_DESCRIPTOR);
+        descriptor.setValue(new byte[]{1, 0});
+        gatt.writeDescriptor(descriptor);
+        gatt.setCharacteristicNotification(gatt.getService(PPOGATT_SERVICE_UUID).getCharacteristic(PPOGATT_CHARACTERISTIC_READ), true);
+        writeCharacteristics = gatt.getService(PPOGATT_SERVICE_UUID).getCharacteristic(PPOGATT_CHARACTERISTIC_WRITE);
+    }
+
+    synchronized void sendDataToPebble(byte[] data) {
+        mWaitWriteCompleteLatch = new CountDownLatch(1);
+        writeCharacteristics.setValue(data.clone());
+
+        boolean success = mBluetoothGatt.writeCharacteristic(writeCharacteristics);
+        if (!success) {
+            LOG.error("could not send data to pebble (error writing characteristic)");
+        } else {
+            try {
+                mWaitWriteCompleteLatch.await();
+            } catch (InterruptedException e) {
+                LOG.warn("interrupted while waiting for write complete latch");
+            }
+        }
+        mWaitWriteCompleteLatch = null;
+    }
+
+    public void close() {
+        if (mBluetoothGatt != null) {
+            mBluetoothGatt.disconnect();
+            mBluetoothGatt.close();
+            mBluetoothGatt = null;
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/ble/PebbleGATTServer.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/ble/PebbleGATTServer.java
new file mode 100644
index 0000000..ef0c182
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/ble/PebbleGATTServer.java
@@ -0,0 +1,195 @@
+/*  Copyright (C) 2016-2024 Andreas Shimokawa, Daniele Gobbetti, Uwe Hermann
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.ble;
+
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.bluetooth.BluetoothGattDescriptor;
+import android.bluetooth.BluetoothGattServer;
+import android.bluetooth.BluetoothGattServerCallback;
+import android.bluetooth.BluetoothGattService;
+import android.bluetooth.BluetoothManager;
+import android.content.Context;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.UUID;
+import java.util.concurrent.CountDownLatch;
+
+class PebbleGATTServer extends BluetoothGattServerCallback {
+    private static final Logger LOG = LoggerFactory.getLogger(PebbleGATTServer.class);
+    private static final UUID WRITE_CHARACTERISTICS = UUID.fromString("10000001-328E-0FBB-C642-1AA6699BDADA");
+    private static final UUID READ_CHARACTERISTICS = UUID.fromString("10000002-328E-0FBB-C642-1AA6699BDADA");
+    private static final UUID CHARACTERISTICS_CONFIGURATION_DESCRIPTOR = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");
+    private static final UUID SERVER_SERVICE = UUID.fromString("10000000-328E-0FBB-C642-1AA6699BDADA");
+    private static final UUID SERVER_SERVICE_BADBAD = UUID.fromString("BADBADBA-DBAD-BADB-ADBA-BADBADBADBAD");
+    private final BluetoothDevice mBtDevice;
+    private final PebbleLESupport mPebbleLESupport;
+    private Context mContext;
+    private BluetoothGattServer mBluetoothGattServer;
+    private BluetoothGattCharacteristic writeCharacteristics;
+    private CountDownLatch mWaitWriteCompleteLatch;
+
+    PebbleGATTServer(PebbleLESupport pebbleLESupport, Context context, BluetoothDevice btDevice) {
+        mContext = context;
+        mBtDevice = btDevice;
+        mPebbleLESupport = pebbleLESupport;
+    }
+
+    boolean initialize() {
+        BluetoothManager bluetoothManager = (BluetoothManager) mContext.getSystemService(Context.BLUETOOTH_SERVICE);
+        if (bluetoothManager == null) {
+            return false;
+        }
+        mBluetoothGattServer = bluetoothManager.openGattServer(mContext, this);
+        if (mBluetoothGattServer == null) {
+            return false;
+        }
+
+        BluetoothGattService pebbleGATTService = new BluetoothGattService(SERVER_SERVICE, BluetoothGattService.SERVICE_TYPE_PRIMARY);
+        pebbleGATTService.addCharacteristic(new BluetoothGattCharacteristic(READ_CHARACTERISTICS, BluetoothGattCharacteristic.PROPERTY_READ, BluetoothGattCharacteristic.PERMISSION_READ));
+
+        writeCharacteristics = new BluetoothGattCharacteristic(WRITE_CHARACTERISTICS, BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE | BluetoothGattCharacteristic.PROPERTY_NOTIFY, BluetoothGattCharacteristic.PERMISSION_WRITE);
+
+        writeCharacteristics.addDescriptor(new BluetoothGattDescriptor(CHARACTERISTICS_CONFIGURATION_DESCRIPTOR, BluetoothGattDescriptor.PERMISSION_WRITE));
+        pebbleGATTService.addCharacteristic(writeCharacteristics);
+        mBluetoothGattServer.addService(pebbleGATTService);
+
+        return true;
+    }
+
+    synchronized void sendDataToPebble(byte[] data) {
+        mWaitWriteCompleteLatch = new CountDownLatch(1);
+        writeCharacteristics.setValue(data.clone());
+
+        boolean success = mBluetoothGattServer.notifyCharacteristicChanged(mBtDevice, writeCharacteristics, false);
+        if (!success) {
+            LOG.error("could not send data to pebble (error writing characteristic)");
+        } else {
+            try {
+                mWaitWriteCompleteLatch.await();
+            } catch (InterruptedException e) {
+                LOG.warn("interrupted while waiting for write complete latch");
+            }
+        }
+        mWaitWriteCompleteLatch = null;
+    }
+
+    public void onCharacteristicReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattCharacteristic characteristic) {
+        if (!mPebbleLESupport.isExpectedDevice(device)) {
+            return;
+        }
+
+        if (!characteristic.getUuid().equals(READ_CHARACTERISTICS)) {
+            LOG.warn("unexpected read request");
+            return;
+        }
+
+        LOG.info("will send response to read request from device: " + device.getAddress());
+        if (!this.mBluetoothGattServer.sendResponse(device, requestId, 0, offset, new byte[]{0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1})) {
+            LOG.warn("error sending response");
+        }
+    }
+
+
+    public void onCharacteristicWriteRequest(BluetoothDevice device, int requestId, BluetoothGattCharacteristic characteristic,
+                                             boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
+        if (!mPebbleLESupport.isExpectedDevice(device)) {
+            return;
+        }
+
+        if (!characteristic.getUuid().equals(WRITE_CHARACTERISTICS)) {
+            LOG.warn("unexpected write request");
+            return;
+        }
+        mPebbleLESupport.handlePPoGATTPacket(value);
+    }
+
+    public void onConnectionStateChange(BluetoothDevice device, int status, int newState) {
+        if (!mPebbleLESupport.isExpectedDevice(device)) {
+            return;
+        }
+
+        LOG.info("Connection state change for device: " + device.getAddress() + "  status = " + status + " newState = " + newState);
+        if (newState == BluetoothGattServer.STATE_DISCONNECTED) {
+            mPebbleLESupport.close();
+        }
+    }
+
+    public void onDescriptorWriteRequest(BluetoothDevice device, int requestId, BluetoothGattDescriptor descriptor,
+                                         boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
+
+        if (!mPebbleLESupport.isExpectedDevice(device)) {
+            return;
+        }
+
+        if (!descriptor.getCharacteristic().getUuid().equals(WRITE_CHARACTERISTICS)) {
+            LOG.warn("unexpected write request");
+            return;
+        }
+
+        LOG.info("onDescriptorWriteRequest() notifications enabled = " + (value[0] == 1));
+        if (!this.mBluetoothGattServer.sendResponse(device, requestId, 0, offset, value)) {
+            LOG.warn("onDescriptorWriteRequest() error sending response!");
+        }
+    }
+
+    public void onServiceAdded(int status, BluetoothGattService service) {
+        LOG.info("onServiceAdded() status = " + status + " service = " + service.getUuid());
+        if (status == BluetoothGatt.GATT_SUCCESS && service.getUuid().equals(SERVER_SERVICE)) {
+            final BluetoothGattService badbadService = new BluetoothGattService(SERVER_SERVICE_BADBAD, BluetoothGattService.SERVICE_TYPE_PRIMARY);
+            badbadService.addCharacteristic(new BluetoothGattCharacteristic(SERVER_SERVICE_BADBAD, BluetoothGattCharacteristic.PROPERTY_READ, BluetoothGattCharacteristic.PERMISSION_READ));
+            mBluetoothGattServer.addService(badbadService);
+        }
+    }
+
+    @Override
+    public void onMtuChanged(BluetoothDevice device, int mtu) {
+        if (!mPebbleLESupport.isExpectedDevice(device)) {
+            return;
+        }
+
+        LOG.info("Pebble requested mtu for server: " + mtu);
+        mPebbleLESupport.setMTU(mtu);
+    }
+
+    public void onNotificationSent(BluetoothDevice bluetoothDevice, int status) {
+
+        if (!mPebbleLESupport.isExpectedDevice(bluetoothDevice)) {
+            return;
+        }
+        if (status != BluetoothGatt.GATT_SUCCESS) {
+            LOG.error("something went wrong when writing to PPoGATT characteristics");
+        }
+        if (mWaitWriteCompleteLatch != null) {
+            mWaitWriteCompleteLatch.countDown();
+        } else {
+            LOG.warn("mWaitWriteCompleteLatch is null!");
+        }
+//        LOG.info("onNotificationSent() status = " + status + " to device " + bluetoothDevice.getAddress());
+    }
+
+    void close() {
+        if (mBluetoothGattServer != null) {
+            mBluetoothGattServer.cancelConnection(mBtDevice);
+            mBluetoothGattServer.clearServices();
+            mBluetoothGattServer.close();
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/ble/PebbleLESupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/ble/PebbleLESupport.java
new file mode 100644
index 0000000..cf2636e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/ble/PebbleLESupport.java
@@ -0,0 +1,265 @@
+/*  Copyright (C) 2016-2024 Andreas Shimokawa, Daniele Gobbetti
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.ble;
+
+import android.bluetooth.BluetoothDevice;
+import android.content.Context;
+import android.os.Handler;
+import android.os.HandlerThread;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class PebbleLESupport {
+    private static final Logger LOG = LoggerFactory.getLogger(PebbleLESupport.class);
+    private final GBDevice mgbDevice;
+    private final BluetoothDevice mBtDevice;
+    private PipeReader mPipeReader;
+    private PebbleGATTServer mPebbleGATTServer;
+    private PebbleGATTClient mPebbleGATTClient;
+    private PipedInputStream mPipedInputStream;
+    private PipedOutputStream mPipedOutputStream;
+    private int mMTU = 20;
+    private int mMTULimit = Integer.MAX_VALUE;
+    public boolean clientOnly = false; // currently experimental, and only possible for Pebble 2
+    private boolean mIsConnected = false;
+    private HandlerThread mWriteHandlerThread;
+    private Handler mWriteHandler;
+
+    public PebbleLESupport(Context context, GBDevice gbDevice, final BluetoothDevice btDevice, PipedInputStream pipedInputStream, PipedOutputStream pipedOutputStream) throws IOException {
+        mgbDevice = gbDevice;
+        mBtDevice = btDevice;
+        mPipedInputStream = new PipedInputStream();
+        mPipedOutputStream = new PipedOutputStream();
+        try {
+            pipedOutputStream.connect(mPipedInputStream);
+            pipedInputStream.connect(mPipedOutputStream);
+        } catch (IOException e) {
+            LOG.warn("could not connect input stream");
+        }
+
+        mWriteHandlerThread = new HandlerThread("write handler thread");
+        mWriteHandlerThread.start();
+        mWriteHandler = new Handler(mWriteHandlerThread.getLooper());
+
+        mMTULimit = GBApplication.getDevicePrefs(mgbDevice).getInt("pebble_mtu_limit", 512);
+        mMTULimit = Math.max(mMTULimit, 20);
+        mMTULimit = Math.min(mMTULimit, 512);
+
+        clientOnly = GBApplication.getDevicePrefs(mgbDevice).getBoolean("pebble_gatt_clientonly", false);
+
+        if (!clientOnly) {
+            mPebbleGATTServer = new PebbleGATTServer(this, context, mBtDevice);
+        }
+        if (clientOnly || mPebbleGATTServer.initialize()) {
+            mPebbleGATTClient = new PebbleGATTClient(this, context, mBtDevice);
+            try {
+                synchronized (this) {
+                    wait(30000);
+                    if (mIsConnected) {
+                        return;
+                    }
+                }
+            } catch (InterruptedException ignored) {
+            }
+        }
+        this.close();
+        throw new IOException("connection failed");
+    }
+
+    private void writeToPipedOutputStream(byte[] value, int offset, int count) {
+        try {
+            mPipedOutputStream.write(value, offset, count);
+        } catch (IOException e) {
+            LOG.warn("error writing to output stream", e);
+        }
+    }
+
+    synchronized public void close() {
+        destroyPipedInputReader();
+        if (mPebbleGATTServer != null) {
+            mPebbleGATTServer.close();
+            mPebbleGATTServer = null;
+        }
+        if (mPebbleGATTClient != null) {
+            mPebbleGATTClient.close();
+            mPebbleGATTClient = null;
+        }
+        try {
+            mPipedInputStream.close();
+        } catch (IOException ignore) {
+        }
+        try {
+            mPipedOutputStream.close();
+        } catch (IOException ignore) {
+        }
+        if (mWriteHandlerThread != null) {
+            mWriteHandlerThread.quit();
+        }
+    }
+
+    private synchronized void createPipedInputReader() {
+        if (mPipeReader == null) {
+            mPipeReader = new PipeReader();
+        }
+        if (!mPipeReader.isAlive()) {
+            mPipeReader.start();
+        }
+    }
+
+    synchronized private void destroyPipedInputReader() {
+        if (mPipeReader != null) {
+            mPipeReader.interrupt();
+            try {
+                mPipeReader.join();
+            } catch (InterruptedException e) {
+                LOG.error(e.getMessage());
+            }
+            mPipeReader = null;
+        }
+    }
+
+    void setMTU(int mtu) {
+        mMTU = Math.min(mtu, mMTULimit);
+    }
+
+    public void handlePPoGATTPacket(byte[] value) {
+        if (!mIsConnected) {
+            mIsConnected = true;
+            synchronized (this) {
+                this.notify();
+            }
+        }
+        //LOG.info("write request: offset = " + offset + " value = " + GB.hexdump(value, 0, -1));
+        int header = value[0] & 0xff;
+        int command = header & 7;
+        int serial = header >> 3;
+        if (command == 0x01) {
+            LOG.info("got ACK for serial = " + serial);
+        }
+        if (command == 0x02) { // some request?
+            LOG.info("got command 0x02");
+            if (value.length > 1) {
+                sendDataToPebble(new byte[]{0x03, 0x19, 0x19}); // no we don't know what that means
+                createPipedInputReader(); // FIXME: maybe not here
+            } else {
+                sendDataToPebble(new byte[]{0x03}); // no we don't know what that means
+            }
+        } else if (command == 0) { // normal package
+            LOG.info("got PPoGATT package serial = " + serial + " sending ACK");
+
+            sendAckToPebble(serial);
+
+            writeToPipedOutputStream(value, 1, value.length - 1);
+        }
+    }
+
+    private void sendAckToPebble(int serial) {
+        sendDataToPebble(new byte[]{(byte) (((serial << 3) | 1) & 0xff)});
+    }
+
+    private synchronized void sendDataToPebble(final byte[] bytes) {
+        if (mPebbleGATTServer != null) {
+            mWriteHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mPebbleGATTServer.sendDataToPebble(bytes);
+                }
+            });
+        } else {
+            // For now only in experimental client only code
+            mWriteHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mPebbleGATTClient.sendDataToPebble(bytes);
+                }
+            });
+        }
+    }
+
+    private class PipeReader extends Thread {
+        int mmSequence = 0;
+
+        @Override
+        public void run() {
+            byte[] buf = new byte[8192];
+            int bytesRead;
+            while (true) {
+                try {
+                    // this code is very similar to iothread, that is bad
+                    // because we are the ones who prepared the buffer, there should be no
+                    // need to do crazy stuff just to find out the PP boundaries again.
+                    bytesRead = mPipedInputStream.read(buf, 0, 4);
+                    while (bytesRead < 4) {
+                        bytesRead += mPipedInputStream.read(buf, bytesRead, 4 - bytesRead);
+                    }
+
+                    int length = (buf[0] & 0xff) << 8 | (buf[1] & 0xff);
+                    bytesRead = mPipedInputStream.read(buf, 4, length);
+
+                    while (bytesRead < length) {
+                        bytesRead += mPipedInputStream.read(buf, bytesRead + 4, length - bytesRead);
+                    }
+
+
+                    int payloadToSend = bytesRead + 4;
+                    int srcPos = 0;
+                    while (payloadToSend > 0) {
+                        int chunkSize = (payloadToSend < (mMTU - 4)) ? payloadToSend : mMTU - 4;
+                        byte[] outBuf = new byte[chunkSize + 1];
+                        outBuf[0] = (byte) ((mmSequence++ << 3) & 0xff);
+                        System.arraycopy(buf, srcPos, outBuf, 1, chunkSize);
+                        sendDataToPebble(outBuf);
+                        srcPos += chunkSize;
+                        payloadToSend -= chunkSize;
+                    }
+                } catch (IOException e) {
+                    LOG.info(e.getMessage());
+                    Thread.currentThread().interrupt();
+                    break;
+                }
+            }
+            LOG.info("Pipereader thread shut down");
+        }
+
+        @Override
+        public void interrupt() {
+            super.interrupt();
+            try {
+                LOG.info("closing piped inputstream");
+                mPipedInputStream.close();
+            } catch (IOException ignore) {
+            }
+        }
+    }
+
+    boolean isExpectedDevice(BluetoothDevice device) {
+        if (!device.getAddress().equals(mBtDevice.getAddress())) {
+            LOG.info("unhandled device: " + device.getAddress() + " , ignoring, will only talk to " + mBtDevice.getAddress());
+            return false;
+        }
+        return true;
+    }
+}
+
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/webview/CurrentPosition.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/webview/CurrentPosition.java
new file mode 100644
index 0000000..f805ef4
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/webview/CurrentPosition.java
@@ -0,0 +1,93 @@
+/*  Copyright (C) 2017-2024 Andreas Shimokawa, Daniele Gobbetti
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.webview;
+
+import android.Manifest;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.location.Criteria;
+import android.location.Location;
+import android.location.LocationManager;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import androidx.core.app.ActivityCompat;
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+public class CurrentPosition {
+
+    private static final Logger LOG = LoggerFactory.getLogger(CurrentPosition.class);
+
+    private Location lastKnownLocation;
+    private float latitude, longitude;
+
+    long timestamp;
+    double altitude;
+    float accuracy, speed;
+
+    float getLatitude() {
+        return latitude;
+    }
+
+    float getLongitude() {
+        return longitude;
+    }
+
+    public Location getLastKnownLocation() {
+        return lastKnownLocation;
+    }
+
+    public CurrentPosition() {
+        Prefs prefs = GBApplication.getPrefs();
+        this.latitude = prefs.getFloat("location_latitude", 0);
+        this.longitude = prefs.getFloat("location_longitude", 0);
+
+        lastKnownLocation = new Location("preferences");
+        lastKnownLocation.setLatitude(this.latitude);
+        lastKnownLocation.setLongitude(this.longitude);
+
+        LOG.info("got longitude/latitude from preferences: " + latitude + "/" + longitude);
+
+        this.timestamp = System.currentTimeMillis() - 86400000; //let accessor know this value is really old
+
+        if (ActivityCompat.checkSelfPermission(GBApplication.getContext(), Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED &&
+                prefs.getBoolean("use_updated_location_if_available", false)) {
+            LocationManager locationManager = (LocationManager) GBApplication.getContext().getSystemService(Context.LOCATION_SERVICE);
+            Criteria criteria = new Criteria();
+            String provider = null;
+            if (locationManager != null) {
+                provider = locationManager.getBestProvider(criteria, false);
+            }
+            if (provider != null) {
+                Location lastKnownLocation = locationManager.getLastKnownLocation(provider);
+                if (lastKnownLocation != null) {
+                    this.lastKnownLocation = lastKnownLocation;
+                    this.timestamp = lastKnownLocation.getTime();
+                    this.timestamp = System.currentTimeMillis() - 1000; //TODO: request updating the location and don't fake its age
+
+                    this.latitude = (float) lastKnownLocation.getLatitude();
+                    this.longitude = (float) lastKnownLocation.getLongitude();
+                    this.accuracy = lastKnownLocation.getAccuracy();
+                    this.altitude = (float) lastKnownLocation.getAltitude();
+                    this.speed = lastKnownLocation.getSpeed();
+                }
+            }
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/webview/GBChromeClient.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/webview/GBChromeClient.java
new file mode 100644
index 0000000..c7406b1
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/webview/GBChromeClient.java
@@ -0,0 +1,45 @@
+/*  Copyright (C) 2017-2024 Daniele Gobbetti
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.webview;
+
+import android.webkit.ConsoleMessage;
+import android.webkit.WebChromeClient;
+import android.widget.Toast;
+
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class GBChromeClient extends WebChromeClient {
+
+    @Override
+    public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
+        if (ConsoleMessage.MessageLevel.ERROR.equals(consoleMessage.messageLevel())) {
+            GB.toast(formatConsoleMessage(consoleMessage), Toast.LENGTH_LONG, GB.ERROR);
+            //TODO: show error page
+        }
+        return super.onConsoleMessage(consoleMessage);
+    }
+
+    private static String formatConsoleMessage(ConsoleMessage message) {
+        String sourceId = message.sourceId();
+        if (sourceId == null || sourceId.length() == 0) {
+            sourceId = "unknown";
+        }
+        return String.format("%s (at %s: %d)", message.message(), sourceId, message.lineNumber());
+    }
+
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/webview/GBWebClient.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/webview/GBWebClient.java
new file mode 100644
index 0000000..3288925
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/webview/GBWebClient.java
@@ -0,0 +1,245 @@
+/*  Copyright (C) 2017-2024 Andreas Shimokawa, Carsten Pfeiffer, Daniele
+    Gobbetti, Pavel Elagin
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.webview;
+
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Message;
+import android.os.RemoteException;
+import android.webkit.WebResourceRequest;
+import android.webkit.WebResourceResponse;
+import android.webkit.WebView;
+import android.webkit.WebViewClient;
+
+import net.e175.klaus.solarpositioning.DeltaT;
+import net.e175.klaus.solarpositioning.SPA;
+import net.e175.klaus.solarpositioning.SunriseTransitSet;
+
+import org.apache.commons.lang3.StringUtils;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.ByteArrayInputStream;
+import java.time.LocalDate;
+import java.time.ZonedDateTime;
+import java.util.HashMap;
+import java.util.Map;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.Weather;
+import nodomain.freeyourgadget.gadgetbridge.util.WebViewSingleton;
+
+public class GBWebClient extends WebViewClient {
+
+    private String[] AllowedDomains = new String[]{
+            "openweathermap.org",   //for weather :)
+            "rawgit.com",           //for trekvolle
+            "tagesschau.de"         //for internal watchapp tests
+    };
+    private static final Logger LOG = LoggerFactory.getLogger(GBWebClient.class);
+
+    @Override
+    public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
+        Uri parsedUri = request.getUrl();
+        LOG.debug("WEBVIEW shouldInterceptRequest URL: " + parsedUri.toString());
+        WebResourceResponse mimickedReply = mimicReply(parsedUri);
+        if (mimickedReply != null)
+            return mimickedReply;
+        return super.shouldInterceptRequest(view, request);
+    }
+
+    @Override
+    public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
+        LOG.debug("WEBVIEW shouldInterceptRequest URL (legacy): " + url);
+        Uri parsedUri = Uri.parse(url);
+        WebResourceResponse mimickedReply = mimicReply(parsedUri);
+        if (mimickedReply != null)
+            return mimickedReply;
+        return super.shouldInterceptRequest(view, url);
+    }
+
+
+    private WebResourceResponse mimicReply(Uri requestedUri) {
+        if (requestedUri.getHost() != null && (StringUtils.indexOfAny(requestedUri.getHost(), AllowedDomains) != -1)) {
+            if (WebViewSingleton.getInstance().internetHelperBound) {
+                LOG.debug("WEBVIEW forwarding request to the internet helper");
+                Bundle bundle = new Bundle();
+                bundle.putString("URL", requestedUri.toString());
+                Message webRequest = Message.obtain();
+                webRequest.setData(bundle);
+                try {
+                    return WebViewSingleton.getInstance().send(webRequest);
+                } catch (RemoteException | InterruptedException e) {
+                    LOG.warn("Error downloading data from " + requestedUri, e);
+                }
+
+            } else {
+                if (StringUtils.endsWith(requestedUri.getHost(), "openweathermap.org")){
+                    LOG.debug("WEBVIEW request to openweathermap.org detected of type: " + requestedUri.getPath() + " params: " + requestedUri.getQuery());
+                    return mimicOpenWeatherMapResponse(requestedUri.getPath(), requestedUri.getQueryParameter("units"));
+                } else if (StringUtils.endsWith(requestedUri.getHost(), "rawgit.com")) {
+                    LOG.debug("WEBVIEW request to rawgit.com detected of type: " + requestedUri.getPath() + " params: " + requestedUri.getQuery());
+                    return mimicRawGitResponse(requestedUri.getPath());
+                } else {
+                    LOG.debug("WEBVIEW request to allowed domain detected but not intercepted: " + requestedUri.toString());
+                }
+            }
+        } else {
+            LOG.debug("WEBVIEW request:" + requestedUri.toString() + " not intercepted");
+        }
+        return null;
+    }
+
+    @Override
+    public boolean shouldOverrideUrlLoading(WebView view, String url) {
+        Uri parsedUri = Uri.parse(url);
+
+        if (parsedUri.getScheme().startsWith("http")) {
+            Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
+            i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
+            GBApplication.getContext().startActivity(i);
+        } else if (parsedUri.getScheme().startsWith("pebblejs")) {
+            url = url.replaceFirst("^pebblejs://close#", "file:///android_asset/app_config/configure.html?config=true&json=");
+            view.loadUrl(url);
+        } else if (parsedUri.getScheme().equals("data")) { //clay
+            view.loadUrl(url);
+        } else {
+            LOG.debug("WEBVIEW Ignoring unhandled scheme: " + parsedUri.getScheme());
+        }
+
+        return true;
+    }
+
+    private WebResourceResponse mimicRawGitResponse(String path) {
+        if("/aHcVolle/TrekVolle/master/online.html".equals(path)) { //TrekVolle online check
+            Map<String, String> headers = new HashMap<>();
+            headers.put("Access-Control-Allow-Origin", "*");
+            return new WebResourceResponse("text/html", "utf-8", 200, "OK",
+                    headers,
+                    new ByteArrayInputStream("1".getBytes())
+            );
+        }
+
+        return null;
+    }
+
+    private WebResourceResponse mimicOpenWeatherMapResponse(String type, String units) {
+
+        if (Weather.getInstance() == null) {
+            LOG.warn("WEBVIEW - Weather instance is null, cannot update weather");
+            return null;
+        }
+
+        CurrentPosition currentPosition = new CurrentPosition();
+
+        try {
+            JSONObject resp = Weather.getInstance().createReconstructedOWMWeatherReply();
+            if ("/data/2.5/weather".equals(type) && resp != null) {
+                JSONObject main = resp.getJSONObject("main");
+
+                convertTemps(main, units); //caller might want different units
+                JSONObject wind = resp.getJSONObject("wind");
+                convertSpeeds(wind, units);
+
+                resp.put("cod", 200);
+                resp.put("coord", coordObject(currentPosition));
+                resp.put("sys", sysObject(currentPosition));
+//            } else if ("/data/2.5/forecast".equals(type) && Weather.getInstance().getWeather2().reconstructedOWMForecast != null) { //this is wrong, as we only have daily data. Unfortunately it looks like daily forecasts cannot be reconstructed
+//                resp = new JSONObject(Weather.getInstance().getWeather2().reconstructedOWMForecast.toString());
+//
+//                JSONObject city = resp.getJSONObject("city");
+//                city.put("coord", coordObject(currentPosition));
+//
+//                JSONArray list = resp.getJSONArray("list");
+//                for (int i = 0, size = list.length(); i < size; i++) {
+//                    JSONObject item = list.getJSONObject(i);
+//                    JSONObject main = item.getJSONObject("main");
+//                    convertTemps(main, units); //caller might want different units
+//                }
+//
+//                resp.put("cod", 200);
+            } else {
+                LOG.warn("WEBVIEW - cannot mimick request of type " + type + " (unsupported or lack of data)");
+                return null;
+            }
+
+            LOG.info("WEBVIEW - mimic openweather response" + resp.toString());
+            Map<String, String> headers = new HashMap<>();
+            headers.put("Access-Control-Allow-Origin", "*");
+
+            return new WebResourceResponse("application/json", "utf-8", 200, "OK",
+                    headers,
+                    new ByteArrayInputStream(resp.toString().getBytes())
+            );
+        } catch (JSONException e) {
+            LOG.warn("Error building the JSON weather message.", e);
+        }
+
+        return null;
+
+    }
+
+
+    private static JSONObject sysObject(CurrentPosition currentPosition) throws JSONException {
+        final SunriseTransitSet sunriseTransitSet = SPA.calculateSunriseTransitSet(
+                ZonedDateTime.now(),
+                currentPosition.getLatitude(),
+                currentPosition.getLongitude(),
+                DeltaT.estimate(LocalDate.now())
+        );
+
+        JSONObject sys = new JSONObject();
+        sys.put("country", "World");
+        sys.put("sunrise", sunriseTransitSet.getSunrise().toInstant().getEpochSecond());
+        sys.put("sunset", sunriseTransitSet.getSunset().toInstant().getEpochSecond());
+
+        return sys;
+    }
+
+    private static void convertSpeeds(JSONObject wind, String units) throws JSONException {
+        if ("metric".equals(units)) {
+            wind.put("speed", (wind.getDouble("speed") * 3.6f) );
+        } else if ("imperial".equals(units)) { //it's 2018... this is so sad
+            wind.put("speed", (wind.getDouble("speed") * 2.237f) );
+        }
+    }
+
+    private static void convertTemps(JSONObject main, String units) throws JSONException {
+        if ("metric".equals(units)) {
+            main.put("temp", (int) main.get("temp") - 273);
+            main.put("temp_min", (int) main.get("temp_min") - 273);
+            main.put("temp_max", (int) main.get("temp_max") - 273);
+        } else if ("imperial".equals(units)) { //it's 2017... this is so sad
+            main.put("temp", ((int) (main.get("temp")) - 273.15f) * 1.8f + 32);         // lgtm [java/integer-multiplication-cast-to-long]
+            main.put("temp_min", ((int) (main.get("temp_min")) - 273.15f) * 1.8f + 32); // lgtm [java/integer-multiplication-cast-to-long]
+            main.put("temp_max", ((int) (main.get("temp_max")) - 273.15f) * 1.8f + 32); // lgtm [java/integer-multiplication-cast-to-long]
+        }
+    }
+
+    private static JSONObject coordObject(CurrentPosition currentPosition) throws JSONException {
+        JSONObject coord = new JSONObject();
+        coord.put("lat", currentPosition.getLatitude());
+        coord.put("lon", currentPosition.getLongitude());
+        return coord;
+    }
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/webview/JSInterface.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/webview/JSInterface.java
new file mode 100644
index 0000000..2957751
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/webview/JSInterface.java
@@ -0,0 +1,254 @@
+/*  Copyright (C) 2017-2024 Andreas Shimokawa, Carsten Pfeiffer, Daniele
+    Gobbetti
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.webview;
+
+import android.webkit.JavascriptInterface;
+import android.widget.Toast;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.io.Writer;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Iterator;
+import java.util.Scanner;
+import java.util.UUID;
+
+import androidx.annotation.NonNull;
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+import nodomain.freeyourgadget.gadgetbridge.util.PebbleUtils;
+
+public class JSInterface {
+
+    private UUID mUuid;
+    private GBDevice device;
+    private Integer lastTransaction;
+
+    private static final Logger LOG = LoggerFactory.getLogger(JSInterface.class);
+
+    public JSInterface(@NonNull GBDevice device, @NonNull UUID mUuid) {
+        LOG.debug("Creating JS interface for UUID: " + mUuid.toString());
+        this.device = device;
+        this.mUuid = mUuid;
+        this.lastTransaction = 0;
+    }
+
+
+    private boolean isLocationEnabledForWatchApp() {
+        return true; //FIXME: as long as we don't give watchapp internet access it's not a problem
+    }
+
+    @JavascriptInterface
+    public void gbLog(String msg) {
+        LOG.debug("WEBVIEW webpage log: " + msg);
+    }
+
+    @JavascriptInterface
+    public String sendAppMessage(String msg, String needsTransactionMsg) {
+        boolean needsTransaction = "true".equals(needsTransactionMsg);
+        LOG.debug("from WEBVIEW: " + msg + " needs a transaction: " + needsTransaction);
+        JSONObject knownKeys = PebbleUtils.getAppConfigurationKeys(this.mUuid);
+        if (knownKeys == null) {
+            LOG.warn("No app configuration keys for: " + mUuid);
+            return null;
+        }
+
+        try {
+            JSONObject in = new JSONObject(msg);
+            JSONObject out = new JSONObject();
+            String inKey, outKey;
+            boolean passKey;
+            for (Iterator<String> key = in.keys(); key.hasNext(); ) {
+                passKey = false;
+                inKey = key.next();
+                outKey = null;
+                int pebbleAppIndex = knownKeys.optInt(inKey, -1);
+                if (pebbleAppIndex != -1) {
+                    passKey = true;
+                    outKey = String.valueOf(pebbleAppIndex);
+                } else {
+                    //do not discard integer keys (see https://developer.pebble.com/guides/communication/using-pebblekit-js/ )
+                    Scanner scanner = new Scanner(inKey);
+                    if (scanner.hasNextInt() && inKey.equals("" + scanner.nextInt())) {
+                        passKey = true;
+                        outKey = inKey;
+                    }
+                }
+
+                if (passKey) {
+                    Object obj = in.get(inKey);
+                    out.put(outKey, obj);
+                } else {
+                    GB.toast("Discarded key " + inKey + ", not found in the local configuration and is not an integer key.", Toast.LENGTH_SHORT, GB.WARN);
+                }
+
+            }
+            LOG.info("WEBVIEW message to pebble: " + out.toString());
+            if (needsTransaction) {
+                this.lastTransaction++;
+                GBApplication.deviceService().onAppConfiguration(this.mUuid, out.toString(), this.lastTransaction);
+                return this.lastTransaction.toString();
+            } else {
+                GBApplication.deviceService().onAppConfiguration(this.mUuid, out.toString(), null);
+            }
+
+        } catch (JSONException e) {
+            LOG.warn("Error building the appmessage JSON object", e);
+        }
+        return null;
+    }
+
+    @JavascriptInterface
+    public String getActiveWatchInfo() {
+        JSONObject wi = new JSONObject();
+        try {
+            wi.put("firmware", device.getFirmwareVersion());
+            wi.put("platform", PebbleUtils.getPlatformName(device.getModel()));
+            wi.put("model", PebbleUtils.getModel(device.getModel()));
+            //TODO: use real info
+            wi.put("language", "en");
+        } catch (JSONException e) {
+            LOG.warn("Error building the ActiveWathcInfo JSON object", e);
+        }
+        //Json not supported apparently, we need to cast back and forth
+        return wi.toString();
+    }
+
+    @JavascriptInterface
+    public String getAppConfigurationFile() {
+        LOG.debug("WEBVIEW loading config file of " + this.mUuid.toString());
+        try {
+            File destDir = PebbleUtils.getPbwCacheDir();
+            File configurationFile = new File(destDir, this.mUuid.toString() + "_config.js");
+            if (configurationFile.exists()) {
+                return "file:///" + configurationFile.getAbsolutePath();
+            }
+        } catch (IOException e) {
+            LOG.warn("Error loading config file", e);
+        }
+        return null;
+    }
+
+    @JavascriptInterface
+    public String getAppStoredPreset() {
+        try {
+            File destDir = PebbleUtils.getPbwCacheDir();
+            File configurationFile = new File(destDir, this.mUuid.toString() + "_preset.json");
+            if (configurationFile.exists()) {
+                return FileUtils.getStringFromFile(configurationFile);
+            }
+        } catch (IOException e) {
+            GB.toast("Error reading presets", Toast.LENGTH_LONG, GB.ERROR);
+            LOG.warn("Error reading presets", e);
+        }
+        return null;
+    }
+
+    @JavascriptInterface
+    public void saveAppStoredPreset(String msg) {
+        Writer writer;
+
+        try {
+            File destDir = PebbleUtils.getPbwCacheDir();
+            File presetsFile = new File(destDir, this.mUuid.toString() + "_preset.json");
+            writer = new BufferedWriter(new FileWriter(presetsFile));
+            writer.write(msg);
+            writer.close();
+            GB.toast("Presets stored", Toast.LENGTH_SHORT, GB.INFO);
+        } catch (IOException e) {
+            GB.toast("Error storing presets", Toast.LENGTH_LONG, GB.ERROR);
+            LOG.warn("Error storing presets", e);
+        }
+    }
+
+    @JavascriptInterface
+    public String getAppUUID() {
+        return this.mUuid.toString();
+    }
+
+    @JavascriptInterface
+    public String getAppLocalstoragePrefix() {
+        String prefix = device.getAddress() + this.mUuid.toString();
+        try {
+            MessageDigest digest = MessageDigest.getInstance("SHA-1");
+            byte[] bytes = prefix.getBytes("UTF-8");
+            digest.update(bytes, 0, bytes.length);
+            bytes = digest.digest();
+            final StringBuilder sb = new StringBuilder();
+            for (byte aByte : bytes) {
+                sb.append(String.format("%02X", aByte));
+            }
+            return sb.toString().toLowerCase();
+        } catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
+            LOG.warn("Error definining local storage prefix", e);
+            return prefix;
+        }
+    }
+
+    @JavascriptInterface
+    public String getWatchToken() {
+        //specification says: A string that is guaranteed to be identical for each Pebble device for the same app across different mobile devices. The token is unique to your app and cannot be used to track Pebble devices across applications. see https://developer.pebble.com/docs/js/Pebble/
+        return "gb" + this.mUuid.toString();
+    }
+
+
+    @JavascriptInterface
+    public String getCurrentPosition() {
+        if (!isLocationEnabledForWatchApp()) {
+            return "";
+        }
+        //we need to override this because the coarse location is not enough for the android webview, we should add the permission for fine location.
+        JSONObject geoPosition = new JSONObject();
+        JSONObject coords = new JSONObject();
+        try {
+
+            CurrentPosition currentPosition = new CurrentPosition();
+
+            geoPosition.put("timestamp", currentPosition.timestamp);
+
+            coords.put("latitude", currentPosition.getLatitude());
+            coords.put("longitude", currentPosition.getLongitude());
+            coords.put("accuracy", currentPosition.accuracy);
+            coords.put("altitude", currentPosition.altitude);
+            coords.put("speed", currentPosition.speed);
+
+            geoPosition.put("coords", coords);
+
+        } catch (JSONException e) {
+            LOG.warn(e.getMessage());
+        }
+        LOG.info("WEBVIEW - geo position" + geoPosition.toString());
+        return geoPosition.toString();
+    }
+
+    @JavascriptInterface
+    public void eventFinished(String event) {
+        LOG.debug("WEBVIEW event finished: " + event);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pinetime/PineTimeJFSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pinetime/PineTimeJFSupport.java
new file mode 100644
index 0000000..8c2d3fe
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pinetime/PineTimeJFSupport.java
@@ -0,0 +1,1274 @@
+/*  Copyright (C) 2020-2024 Andreas Shimokawa, Arjan Schrijver, Daniel
+    Dakhno, Davis Mosenkovs, Ernst, FintasticMan, Gordon Williams, ITCactus,
+    Jean-Fran莽ois Milants, JF, Johannes Krude, kieranc001, Patric Gruber,
+    Petr Kadlec, Taavi Eom盲e, uli
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.pinetime;
+
+import static nodomain.freeyourgadget.gadgetbridge.devices.pinetime.weather.WeatherData.mapOpenWeatherConditionToCloudCover;
+import static nodomain.freeyourgadget.gadgetbridge.devices.pinetime.weather.WeatherData.mapOpenWeatherConditionToPineTimeCondition;
+import static nodomain.freeyourgadget.gadgetbridge.devices.pinetime.weather.WeatherData.mapOpenWeatherConditionToPineTimeObscuration;
+import static nodomain.freeyourgadget.gadgetbridge.devices.pinetime.weather.WeatherData.mapOpenWeatherConditionToPineTimePrecipitation;
+import static nodomain.freeyourgadget.gadgetbridge.devices.pinetime.weather.WeatherData.mapOpenWeatherConditionToPineTimeSpecial;
+
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.content.Intent;
+import android.net.Uri;
+import android.widget.Toast;
+
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.ByteArrayOutputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.List;
+import java.util.Locale;
+import java.util.Optional;
+import java.util.TimeZone;
+import java.util.UUID;
+
+import co.nstant.in.cbor.CborBuilder;
+import co.nstant.in.cbor.CborEncoder;
+import no.nordicsemi.android.dfu.DfuLogListener;
+import no.nordicsemi.android.dfu.DfuProgressListener;
+import no.nordicsemi.android.dfu.DfuProgressListenerAdapter;
+import no.nordicsemi.android.dfu.DfuServiceController;
+import no.nordicsemi.android.dfu.DfuServiceInitiator;
+import no.nordicsemi.android.dfu.DfuServiceListenerHelper;
+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.GBDeviceEventBatteryInfo;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventCallControl;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventMusicControl;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo;
+import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.pinetime.PineTimeActivitySampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.pinetime.PineTimeDFUService;
+import nodomain.freeyourgadget.gadgetbridge.devices.pinetime.PineTimeInstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.pinetime.PineTimeJFConstants;
+import nodomain.freeyourgadget.gadgetbridge.devices.pinetime.weather.WeatherData;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.entities.PineTimeActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.entities.User;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
+import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.NavigationInfoSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.NotificationType;
+import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.WorldClock;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.GattCharacteristic;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.GattService;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.IntentListener;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotification.AlertCategory;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotification.AlertNotificationProfile;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotification.NewAlert;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotification.OverflowStrategy;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.battery.BatteryInfoProfile;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo.DeviceInfoProfile;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class PineTimeJFSupport extends AbstractBTLEDeviceSupport implements DfuLogListener {
+    private static final Logger LOG = LoggerFactory.getLogger(PineTimeJFSupport.class);
+    private final GBDeviceEventVersionInfo versionCmd = new GBDeviceEventVersionInfo();
+    private final GBDeviceEventBatteryInfo batteryCmd = new GBDeviceEventBatteryInfo();
+
+    private final DeviceInfoProfile<PineTimeJFSupport> deviceInfoProfile;
+    private final BatteryInfoProfile<PineTimeJFSupport> batteryInfoProfile;
+
+    private final int MaxNotificationLength = 100;
+    private final int CutNotificationTitleMinAt = 25;
+    private int firmwareVersionMajor = 0;
+    private int firmwareVersionMinor = 0;
+    private int firmwareVersionPatch = 0;
+
+    private final int DAY_SECONDS = (24 * 60 * 60);
+    private int quarantinedSteps = 0;
+
+    private final int WEATHER_GRACE_TIME = 10;
+
+    /**
+     * These are used to keep track when long strings haven't changed,
+     * thus avoiding unnecessary transfers that are (potentially) very slow.
+     * <p>
+     * Makes the device's UI more responsive.
+     */
+    String lastAlbum;
+    String lastTrack;
+    String lastArtist;
+    PineTimeInstallHandler handler;
+    DfuServiceController controller;
+
+    private final DfuProgressListener progressListener = new DfuProgressListenerAdapter() {
+        private final LocalBroadcastManager manager = LocalBroadcastManager.getInstance(getContext());
+
+        /**
+         * Sets the progress bar to indeterminate or not, also makes it visible
+         *
+         * @param indeterminate if indeterminate
+         */
+        public void setIndeterminate(boolean indeterminate) {
+            manager.sendBroadcast(new Intent(GB.ACTION_SET_PROGRESS_BAR).putExtra(GB.PROGRESS_BAR_INDETERMINATE, indeterminate));
+        }
+
+        /**
+         * Sets the status text and logs it
+         */
+        public void setProgress(int progress) {
+            manager.sendBroadcast(new Intent(GB.ACTION_SET_PROGRESS_BAR).putExtra(GB.PROGRESS_BAR_PROGRESS, progress));
+        }
+
+        /**
+         * Sets the text that describes progress
+         *
+         * @param progressText text to display
+         */
+        public void setProgressText(String progressText) {
+            manager.sendBroadcast(new Intent(GB.ACTION_SET_PROGRESS_TEXT).putExtra(GB.DISPLAY_MESSAGE_MESSAGE, progressText));
+        }
+
+        @Override
+        public void onDeviceConnecting(final String mac) {
+            this.setIndeterminate(true);
+            this.setProgressText(getContext().getString(R.string.devicestatus_connecting));
+        }
+
+        @Override
+        public void onDeviceConnected(final String mac) {
+            this.setIndeterminate(true);
+            this.setProgressText(getContext().getString(R.string.devicestatus_connected));
+        }
+
+        @Override
+        public void onEnablingDfuMode(final String mac) {
+            this.setIndeterminate(true);
+            this.setProgressText(getContext().getString(R.string.devicestatus_upload_starting));
+        }
+
+        @Override
+        public void onDfuProcessStarting(final String mac) {
+            this.setIndeterminate(true);
+            this.setProgressText(getContext().getString(R.string.devicestatus_upload_starting));
+        }
+
+        @Override
+        public void onDfuProcessStarted(final String mac) {
+            this.setIndeterminate(true);
+            this.setProgressText(getContext().getString(R.string.devicestatus_upload_started));
+        }
+
+        @Override
+        public void onDeviceDisconnecting(final String mac) {
+            this.setProgressText(getContext().getString(R.string.devicestatus_disconnecting));
+        }
+
+        @Override
+        public void onDeviceDisconnected(final String mac) {
+            this.setIndeterminate(true);
+            this.setProgressText(getContext().getString(R.string.devicestatus_disconnected));
+        }
+
+        @Override
+        public void onDfuCompleted(final String mac) {
+            this.setProgressText(getContext().getString(R.string.devicestatus_upload_completed));
+            this.setIndeterminate(false);
+            this.setProgress(100);
+
+            handler = null;
+            controller = null;
+            DfuServiceListenerHelper.unregisterProgressListener(getContext(), progressListener);
+            gbDevice.unsetBusyTask();
+            // TODO: Request reconnection
+        }
+
+        @Override
+        public void onFirmwareValidating(final String mac) {
+            this.setIndeterminate(true);
+            this.setProgressText(getContext().getString(R.string.devicestatus_upload_validating));
+        }
+
+        @Override
+        public void onDfuAborted(final String mac) {
+            this.setProgressText(getContext().getString(R.string.devicestatus_upload_aborted));
+            gbDevice.unsetBusyTask();
+        }
+
+        @Override
+        public void onError(final String mac, int error, int errorType, final String message) {
+            this.setProgressText(getContext().getString(R.string.devicestatus_upload_failed));
+            gbDevice.unsetBusyTask();
+        }
+
+        @Override
+        public void onProgressChanged(final String mac,
+                                      int percent,
+                                      float speed,
+                                      float averageSpeed,
+                                      int segment,
+                                      int totalSegments) {
+            this.setProgress(percent);
+            this.setIndeterminate(false);
+            this.setProgressText(String.format(Locale.ENGLISH,
+                    getContext().getString(R.string.firmware_update_progress),
+                    percent, speed, averageSpeed, segment, totalSegments));
+        }
+    };
+
+    public PineTimeJFSupport() {
+        super(LOG);
+        addSupportedService(GattService.UUID_SERVICE_ALERT_NOTIFICATION);
+        addSupportedService(GattService.UUID_SERVICE_CURRENT_TIME);
+        addSupportedService(GattService.UUID_SERVICE_DEVICE_INFORMATION);
+        addSupportedService(GattService.UUID_SERVICE_BATTERY_SERVICE);
+        addSupportedService(PineTimeJFConstants.UUID_SERVICE_MUSIC_CONTROL);
+        addSupportedService(PineTimeJFConstants.UUID_SERVICE_WEATHER);
+        addSupportedService(PineTimeJFConstants.UUID_SERVICE_SIMPLE_WEATHER);
+        addSupportedService(PineTimeJFConstants.UUID_SERVICE_NAVIGATION);
+        addSupportedService(PineTimeJFConstants.UUID_CHARACTERISTIC_ALERT_NOTIFICATION_EVENT);
+        addSupportedService(PineTimeJFConstants.UUID_SERVICE_MOTION);
+        addSupportedService(PineTimeJFConstants.UUID_SERVICE_HEART_RATE);
+
+        IntentListener mListener = new IntentListener() {
+            @Override
+            public void notify(Intent intent) {
+                String action = intent.getAction();
+                if (DeviceInfoProfile.ACTION_DEVICE_INFO.equals(action)) {
+                    handleDeviceInfo((nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo.DeviceInfo) intent.getParcelableExtra(DeviceInfoProfile.EXTRA_DEVICE_INFO));
+                } else if (BatteryInfoProfile.ACTION_BATTERY_INFO.equals(action)) {
+                    handleBatteryInfo((nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.battery.BatteryInfo) intent.getParcelableExtra(BatteryInfoProfile.EXTRA_BATTERY_INFO));
+                }
+            }
+        };
+
+        deviceInfoProfile = new DeviceInfoProfile<>(this);
+        deviceInfoProfile.addListener(mListener);
+        addSupportedProfile(deviceInfoProfile);
+
+        AlertNotificationProfile<PineTimeJFSupport> alertNotificationProfile = new AlertNotificationProfile<>(this);
+        addSupportedProfile(alertNotificationProfile);
+
+        batteryInfoProfile = new BatteryInfoProfile<>(this);
+        batteryInfoProfile.addListener(mListener);
+        addSupportedProfile(batteryInfoProfile);
+    }
+
+    private void handleBatteryInfo(nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.battery.BatteryInfo info) {
+        batteryCmd.level = (short) info.getPercentCharged();
+        handleGBDeviceEvent(batteryCmd);
+    }
+
+    @Override
+    public boolean useAutoConnect() {
+        return false;
+    }
+
+    @Override
+    public void onNotification(NotificationSpec notificationSpec) {
+        TransactionBuilder builder = new TransactionBuilder("notification");
+
+        String message;
+        String source = null;
+        String bodyOrSubject = nodomain.freeyourgadget.gadgetbridge.util.StringUtils.getFirstOf(notificationSpec.body, notificationSpec.subject);
+        String senderOrTitle = nodomain.freeyourgadget.gadgetbridge.util.StringUtils.getFirstOf(notificationSpec.sender, notificationSpec.title);
+        if (!nodomain.freeyourgadget.gadgetbridge.util.StringUtils.isNullOrEmpty(notificationSpec.sourceName)) {
+            source = notificationSpec.sourceName;
+        } else if (notificationSpec.type == NotificationType.GENERIC_SMS) {
+            source = getContext().getString(R.string.pref_title_notifications_sms);
+        }
+
+        if (bodyOrSubject.length() > 0){
+            if (isFirmwareAtLeastVersion0_15()) {
+                if (!GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getBoolean(DeviceSettingsPreferenceConst.PREF_PREFIX_NOTIFICATION_WITH_APP, true)) {
+                    source = null;
+                }
+                int cutLength = Math.max(CutNotificationTitleMinAt, MaxNotificationLength - 3 - bodyOrSubject.length() - (source != null ? source.length() + 2 : 0));
+                if (cutLength < senderOrTitle.length() - 1) {
+                    for (; cutLength > 0 && senderOrTitle.charAt(cutLength - 1) == ' '; cutLength--);
+                    senderOrTitle = senderOrTitle.substring(0, cutLength) + ">";
+                }
+                message = nodomain.freeyourgadget.gadgetbridge.util.StringUtils.join(": ", source, senderOrTitle) + "\0" + bodyOrSubject;
+            } else {
+                message = bodyOrSubject;
+            }
+        } else {
+            if (isFirmwareAtLeastVersion0_15()) {
+                message = (source != null ? source : "") + "\0" + senderOrTitle;
+            } else {
+                message = senderOrTitle;
+            }
+        }
+
+        NewAlert alert = new NewAlert(AlertCategory.CustomHuami, 1, message);
+        AlertNotificationProfile<?> profile = new AlertNotificationProfile<>(this);
+        profile.setMaxLength(MaxNotificationLength);
+        profile.newAlert(builder, alert, OverflowStrategy.TRUNCATE);
+        builder.queue(getQueue());
+    }
+
+    @Override
+    public void onSetNavigationInfo(NavigationInfoSpec navigationInfoSpec) {
+        TransactionBuilder builder = new TransactionBuilder("navigation info");
+        if (navigationInfoSpec.instruction == null) {
+            navigationInfoSpec.instruction = "";
+        }
+        if (navigationInfoSpec.distanceToTurn == null) {
+            navigationInfoSpec.distanceToTurn = "";
+        }
+        safeWriteToCharacteristic(builder, PineTimeJFConstants.UUID_CHARACTERISTICS_NAVIGATION_NARRATIVE, navigationInfoSpec.instruction.getBytes(StandardCharsets.UTF_8));
+        safeWriteToCharacteristic(builder, PineTimeJFConstants.UUID_CHARACTERISTICS_NAVIGATION_MAN_DISTANCE, navigationInfoSpec.distanceToTurn.getBytes(StandardCharsets.UTF_8));
+        String iconname;
+        switch (navigationInfoSpec.nextAction) {
+            case NavigationInfoSpec.ACTION_CONTINUE:
+                iconname = "continue";
+                break;
+            case NavigationInfoSpec.ACTION_TURN_LEFT:
+                iconname = "turn-left";
+                break;
+            case NavigationInfoSpec.ACTION_TURN_LEFT_SLIGHTLY:
+                iconname = "turn-slight-left";
+                break;
+            case NavigationInfoSpec.ACTION_TURN_LEFT_SHARPLY:
+                iconname = "turn-sharp-left";
+                break;
+            case NavigationInfoSpec.ACTION_TURN_RIGHT:
+                iconname = "turn-right";
+                break;
+            case NavigationInfoSpec.ACTION_TURN_RIGHT_SLIGHTLY:
+                iconname = "turn-slight-right";
+                break;
+            case NavigationInfoSpec.ACTION_TURN_RIGHT_SHARPLY:
+                iconname = "turn-sharp-right";
+                break;
+            case NavigationInfoSpec.ACTION_KEEP_LEFT:
+                iconname = "continue-left";
+                break;
+            case NavigationInfoSpec.ACTION_KEEP_RIGHT:
+                iconname = "continue-right";
+                break;
+            case NavigationInfoSpec.ACTION_UTURN_LEFT:
+            case NavigationInfoSpec.ACTION_UTURN_RIGHT:
+                iconname = "uturn";
+                break;
+            case NavigationInfoSpec.ACTION_ROUNDABOUT_RIGHT:
+		iconname = "roundabout-right";
+		break;
+            case NavigationInfoSpec.ACTION_ROUNDABOUT_LEFT:
+                iconname = "roundabout-left";
+                break;
+            case NavigationInfoSpec.ACTION_OFFROUTE:
+                iconname = "close";
+                break;
+            default:
+                iconname = "invalid";
+                break;
+        }
+
+        safeWriteToCharacteristic(builder, PineTimeJFConstants.UUID_CHARACTERISTICS_NAVIGATION_FLAGS, iconname.getBytes(StandardCharsets.UTF_8));
+        builder.queue(getQueue());
+    }
+
+    @Override
+    public void onDeleteNotification(int id) {
+
+    }
+
+    @Override
+    public void onSetTime() {
+        // Since this is a standard we should generalize this in Gadgetbridge (properly)
+        GregorianCalendar now = BLETypeConversions.createCalendar();
+        byte[] bytesCurrentTime = BLETypeConversions.calendarToCurrentTime(now, 0);
+        byte[] bytesLocalTime = BLETypeConversions.calendarToLocalTime(now);
+
+        TransactionBuilder builder = new TransactionBuilder("set time");
+        builder.write(getCharacteristic(GattCharacteristic.UUID_CHARACTERISTIC_CURRENT_TIME), bytesCurrentTime);
+        builder.write(getCharacteristic(GattCharacteristic.UUID_CHARACTERISTIC_LOCAL_TIME), bytesLocalTime);
+        builder.queue(getQueue());
+    }
+
+    @Override
+    public void onSetCallState(CallSpec callSpec) {
+        if (callSpec.command == CallSpec.CALL_INCOMING) {
+            TransactionBuilder builder = new TransactionBuilder("incomingcall");
+
+            String message;
+            if (isFirmwareAtLeastVersion0_15() && callSpec.sourceName != null) {
+                message = (byte) 0x01 + callSpec.sourceName + "\0" + callSpec.name;
+            } else {
+                message = (byte) 0x01 + callSpec.name;
+            }
+
+            NewAlert alert = new NewAlert(AlertCategory.IncomingCall, 1, message);
+            AlertNotificationProfile<?> profile = new AlertNotificationProfile<>(this);
+            profile.setMaxLength(MaxNotificationLength);
+            profile.newAlert(builder, alert, OverflowStrategy.TRUNCATE);
+            builder.queue(getQueue());
+        }
+    }
+
+    @Override
+    public void onInstallApp(Uri uri) {
+        try {
+            handler = new PineTimeInstallHandler(uri, getContext());
+
+            if (handler.isValid()) {
+                gbDevice.setBusyTask("firmware upgrade");
+                DfuServiceInitiator starter = new DfuServiceInitiator(getDevice().getAddress())
+                        .setDeviceName(getDevice().getName())
+                        .setKeepBond(true)
+                        .setForeground(false)
+                        .setUnsafeExperimentalButtonlessServiceInSecureDfuEnabled(false)
+                        .disableMtuRequest()
+                        .setZip(uri);
+
+                controller = starter.start(getContext(), PineTimeDFUService.class);
+                DfuServiceListenerHelper.registerProgressListener(getContext(), progressListener);
+                DfuServiceListenerHelper.registerLogListener(getContext(), this);
+
+                LocalBroadcastManager.getInstance(getContext()).sendBroadcast(new Intent(GB.ACTION_SET_PROGRESS_BAR)
+                        .putExtra(GB.PROGRESS_BAR_INDETERMINATE, true)
+                );
+                LocalBroadcastManager.getInstance(getContext()).sendBroadcast(new Intent(GB.ACTION_SET_PROGRESS_TEXT)
+                        .putExtra(GB.DISPLAY_MESSAGE_MESSAGE, getContext().getString(R.string.devicestatus_upload_starting))
+                );
+            } else {
+                LocalBroadcastManager.getInstance(getContext()).sendBroadcast(new Intent(GB.ACTION_SET_PROGRESS_TEXT)
+                        .putExtra(GB.DISPLAY_MESSAGE_MESSAGE, getContext().getString(R.string.fwinstaller_firmware_not_compatible_to_device)));
+            }
+        } catch (Exception ex) {
+            GB.toast(getContext(), getContext().getString(R.string.updatefirmwareoperation_write_failed) + ":" + ex.getMessage(), Toast.LENGTH_LONG, GB.ERROR, ex);
+            if (gbDevice.isBusy() && gbDevice.getBusyTask().equals("firmware upgrade")) {
+                gbDevice.unsetBusyTask();
+            }
+        }
+    }
+
+    @Override
+    public void onFindDevice(boolean start) {
+        CallSpec callSpec = new CallSpec();
+        callSpec.command = start ? CallSpec.CALL_INCOMING : CallSpec.CALL_END;
+        callSpec.name = "Gadgetbridge";
+        onSetCallState(callSpec);
+    }
+
+    @Override
+    protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
+        builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext()));
+        requestDeviceInfo(builder);
+        if (GBApplication.getPrefs().getBoolean("datetime_synconconnect", true)) {
+            onSetTime();
+        }
+        setWorldClocks();
+        builder.notify(getCharacteristic(PineTimeJFConstants.UUID_CHARACTERISTICS_MUSIC_EVENT), true);
+        BluetoothGattCharacteristic alertNotificationEventCharacteristic = getCharacteristic(PineTimeJFConstants.UUID_CHARACTERISTIC_ALERT_NOTIFICATION_EVENT);
+        if (alertNotificationEventCharacteristic != null) {
+            builder.notify(alertNotificationEventCharacteristic, true);
+        }
+
+        if (getSupportedServices().contains(PineTimeJFConstants.UUID_SERVICE_MOTION)) {
+            builder.notify(getCharacteristic(PineTimeJFConstants.UUID_CHARACTERISTIC_MOTION_STEP_COUNT), true);
+            //builder.notify(getCharacteristic(PineTimeJFConstants.UUID_CHARACTERISTIC_MOTION_RAW_XYZ_VALUES), false); // issue #2527
+        }
+
+        builder.notify(getCharacteristic(PineTimeJFConstants.UUID_CHARACTERISTIC_HEART_RATE_MEASUREMENT), true);
+
+        setInitialized(builder);
+        batteryInfoProfile.requestBatteryInfo(builder);
+        batteryInfoProfile.enableNotify(builder, true);
+
+        builder.requestMtu(256);
+        return builder;
+    }
+
+    @Override
+    public void onSetMusicInfo(MusicSpec musicSpec) {
+        try {
+            TransactionBuilder builder = performInitialized("send playback info");
+
+            if (musicSpec.album != null && !musicSpec.album.equals(lastAlbum)) {
+                lastAlbum = musicSpec.album;
+                safeWriteToCharacteristic(builder, PineTimeJFConstants.UUID_CHARACTERISTICS_MUSIC_ALBUM, musicSpec.album.getBytes());
+            }
+            if (musicSpec.track != null && !musicSpec.track.equals(lastTrack)) {
+                lastTrack = musicSpec.track;
+                safeWriteToCharacteristic(builder, PineTimeJFConstants.UUID_CHARACTERISTICS_MUSIC_TRACK, musicSpec.track.getBytes());
+            }
+            if (musicSpec.artist != null && !musicSpec.artist.equals(lastArtist)) {
+                lastArtist = musicSpec.artist;
+                safeWriteToCharacteristic(builder, PineTimeJFConstants.UUID_CHARACTERISTICS_MUSIC_ARTIST, musicSpec.artist.getBytes());
+            }
+
+            if (musicSpec.duration != MusicSpec.MUSIC_UNKNOWN) {
+                safeWriteToCharacteristic(builder, PineTimeJFConstants.UUID_CHARACTERISTICS_MUSIC_LENGTH_TOTAL, intToBytes(musicSpec.duration));
+            }
+            if (musicSpec.trackNr != MusicSpec.MUSIC_UNKNOWN) {
+                safeWriteToCharacteristic(builder, PineTimeJFConstants.UUID_CHARACTERISTICS_MUSIC_TRACK_NUMBER, intToBytes(musicSpec.trackNr));
+            }
+            if (musicSpec.trackCount != MusicSpec.MUSIC_UNKNOWN) {
+                safeWriteToCharacteristic(builder, PineTimeJFConstants.UUID_CHARACTERISTICS_MUSIC_TRACK_TOTAL, intToBytes(musicSpec.trackCount));
+            }
+
+            builder.queue(getQueue());
+        } catch (Exception e) {
+            LOG.error("Error sending music info", e);
+        }
+    }
+
+    @Override
+    public void onSetMusicState(MusicStateSpec stateSpec) {
+        try {
+            TransactionBuilder builder = performInitialized("send playback state");
+
+            if (stateSpec.state != MusicStateSpec.STATE_UNKNOWN) {
+                byte[] state = new byte[1];
+                if (stateSpec.state == MusicStateSpec.STATE_PLAYING) {
+                    state[0] = 0x01;
+                }
+                safeWriteToCharacteristic(builder, PineTimeJFConstants.UUID_CHARACTERISTICS_MUSIC_STATUS, state);
+            }
+
+            if (stateSpec.playRate != MusicStateSpec.STATE_UNKNOWN) {
+                safeWriteToCharacteristic(builder, PineTimeJFConstants.UUID_CHARACTERISTICS_MUSIC_PLAYBACK_SPEED, intToBytes(stateSpec.playRate));
+            }
+
+            if (stateSpec.position != MusicStateSpec.STATE_UNKNOWN) {
+                safeWriteToCharacteristic(builder, PineTimeJFConstants.UUID_CHARACTERISTICS_MUSIC_POSITION, intToBytes(stateSpec.position));
+            }
+
+            if (stateSpec.repeat != MusicStateSpec.STATE_UNKNOWN) {
+                safeWriteToCharacteristic(builder, PineTimeJFConstants.UUID_CHARACTERISTICS_MUSIC_REPEAT, intToBytes(stateSpec.repeat));
+            }
+
+            if (stateSpec.shuffle != MusicStateSpec.STATE_UNKNOWN) {
+                safeWriteToCharacteristic(builder, PineTimeJFConstants.UUID_CHARACTERISTICS_MUSIC_SHUFFLE, intToBytes(stateSpec.repeat));
+            }
+
+            builder.queue(getQueue());
+
+        } catch (Exception e) {
+            LOG.error("Error sending music state", e);
+        }
+
+    }
+
+    @Override
+    public boolean onCharacteristicRead(BluetoothGatt gatt,
+                                        BluetoothGattCharacteristic characteristic, int status) {
+        if (super.onCharacteristicRead(gatt, characteristic, status)) {
+            return true;
+        }
+        UUID characteristicUUID = characteristic.getUuid();
+
+        LOG.info("Unhandled characteristic read: " + characteristicUUID);
+        return false;
+    }
+
+    private int getUtcOffset(WorldClock clock) {
+        int offsetMillisTimezone = TimeZone.getTimeZone(clock.getTimeZoneId()).getRawOffset();
+        return (offsetMillisTimezone / (1000 * 60 * 15));
+    }
+
+    private void sendWorldClocks(List<? extends WorldClock> clocks) {
+        final DeviceCoordinator coordinator = gbDevice.getDeviceCoordinator();
+        if (coordinator.getWorldClocksSlotCount() == 0) {
+            return;
+        }
+
+        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+
+        try {
+            for (final WorldClock clock : clocks) {
+                int utcOffsetInQuarterHours = getUtcOffset(clock);
+
+                baos.write(utcOffsetInQuarterHours);
+
+                baos.write(clock.getLabel().getBytes());
+
+                baos.write(0);
+
+                //pad string to 9 bytes
+                while(baos.size() % 10 != 0){
+                    baos.write(0);
+                }
+            }
+
+            while(baos.size() < coordinator.getWorldClocksSlotCount() * 10){
+                baos.write(-128); //invalid slot
+                baos.write(new byte[9]);
+            }
+
+            TransactionBuilder builder = new TransactionBuilder("set world clocks");
+            builder.write(getCharacteristic(PineTimeJFConstants.UUID_CHARACTERISTIC_WORLD_TIME), baos.toByteArray());
+            builder.queue(getQueue());
+        } catch (Exception e) {
+            LOG.error("Error sending world clocks", e);
+        }
+    }
+
+    private void setWorldClocks() {
+        final List<? extends WorldClock> clocks = DBHelper.getWorldClocks(gbDevice);
+        sendWorldClocks(clocks);
+    }
+
+    @Override
+    public void onSetWorldClocks(ArrayList<? extends WorldClock> clocks) {
+        sendWorldClocks(clocks);
+    }
+
+    @Override
+    public boolean onCharacteristicChanged(BluetoothGatt gatt,
+                                           BluetoothGattCharacteristic characteristic) {
+        if (super.onCharacteristicChanged(gatt, characteristic)) {
+            return true;
+        }
+
+        UUID characteristicUUID = characteristic.getUuid();
+        if (characteristicUUID.equals(PineTimeJFConstants.UUID_CHARACTERISTICS_MUSIC_EVENT)) {
+            byte[] value = characteristic.getValue();
+            GBDeviceEventMusicControl deviceEventMusicControl = new GBDeviceEventMusicControl();
+
+            switch (value[0]) {
+                case 0:
+                    deviceEventMusicControl.event = GBDeviceEventMusicControl.Event.PLAY;
+                    break;
+                case 1:
+                    deviceEventMusicControl.event = GBDeviceEventMusicControl.Event.PAUSE;
+                    break;
+                case 3:
+                    deviceEventMusicControl.event = GBDeviceEventMusicControl.Event.NEXT;
+                    break;
+                case 4:
+                    deviceEventMusicControl.event = GBDeviceEventMusicControl.Event.PREVIOUS;
+                    break;
+                case 5:
+                    deviceEventMusicControl.event = GBDeviceEventMusicControl.Event.VOLUMEUP;
+                    break;
+                case 6:
+                    deviceEventMusicControl.event = GBDeviceEventMusicControl.Event.VOLUMEDOWN;
+                    break;
+                default:
+                    return false;
+            }
+            evaluateGBDeviceEvent(deviceEventMusicControl);
+            return true;
+        } else if (characteristicUUID.equals(PineTimeJFConstants.UUID_CHARACTERISTIC_ALERT_NOTIFICATION_EVENT)) {
+            byte[] value = characteristic.getValue();
+            GBDeviceEventCallControl deviceEventCallControl = new GBDeviceEventCallControl();
+            switch (value[0]) {
+                case 0:
+                    deviceEventCallControl.event = GBDeviceEventCallControl.Event.REJECT;
+                    break;
+                case 1:
+                    deviceEventCallControl.event = GBDeviceEventCallControl.Event.ACCEPT;
+                    break;
+                case 2:
+                    deviceEventCallControl.event = GBDeviceEventCallControl.Event.IGNORE;
+                    break;
+                default:
+                    return false;
+            }
+            evaluateGBDeviceEvent(deviceEventCallControl);
+            return true;
+        } else if (characteristicUUID.equals(PineTimeJFConstants.UUID_CHARACTERISTIC_MOTION_STEP_COUNT)) {
+            int steps = BLETypeConversions.toUint32(characteristic.getValue());
+            if (LOG.isDebugEnabled()) {
+                LOG.debug("onCharacteristicChanged: MotionService:Steps=" + steps);
+            }
+            onReceiveStepsSample(steps);
+            return true;
+        } else if (characteristicUUID.equals(PineTimeJFConstants.UUID_CHARACTERISTIC_HEART_RATE_MEASUREMENT)) {
+            int heartrate = Byte.toUnsignedInt(characteristic.getValue()[1]);
+            if (LOG.isDebugEnabled()) {
+                LOG.debug("onCharacteristicChanged: HeartRateMeasurement:HeartRate=" + heartrate);
+            }
+            onReceiveHeartRateMeasurement(heartrate);
+            return true;
+        }
+
+        LOG.info("Unhandled characteristic changed: " + characteristicUUID);
+        return false;
+    }
+
+    private void onSendWeatherCBOR(WeatherSpec weatherSpec) {
+        if (weatherSpec.location != null) {
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            try {
+                new CborEncoder(baos).encode(new CborBuilder()
+                        .startMap() // This map is not fixed-size, which is not great, but it might come in a library update
+                        .put("Timestamp", System.currentTimeMillis() / 1000L)
+                        .put("Expires", 60 * 60 * 1 + WEATHER_GRACE_TIME) // 1h
+                        .put("EventType", WeatherData.EventType.Location.value)
+                        .put("Location", weatherSpec.location)
+                        .put("Altitude", 0)
+                        .put("Latitude", 0)
+                        .put("Longitude", 0)
+                        .end()
+                        .build()
+                );
+            } catch (Exception e) {
+                LOG.warn(String.valueOf(e));
+            }
+            byte[] encodedBytes = baos.toByteArray();
+            TransactionBuilder builder = createTransactionBuilder("WeatherData");
+            safeWriteToCharacteristic(builder,
+                    PineTimeJFConstants.UUID_CHARACTERISTIC_WEATHER_DATA,
+                    encodedBytes);
+
+            builder.queue(getQueue());
+        }
+
+        // Current condition
+        if (weatherSpec.currentCondition != null) {
+            // We can't do anything with this?
+        }
+
+        // Current humidity
+        if (weatherSpec.currentHumidity > 0) {
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            try {
+                new CborEncoder(baos).encode(new CborBuilder()
+                        .startMap() // This map is not fixed-size, which is not great, but it might come in a library update
+                        .put("Timestamp", System.currentTimeMillis() / 1000L)
+                        .put("Expires", 60 * 60 * 1 + WEATHER_GRACE_TIME) // 1h this should be the weather provider's interval, really
+                        .put("EventType", WeatherData.EventType.Humidity.value)
+                        .put("Humidity", (int) weatherSpec.currentHumidity)
+                        .end()
+                        .build()
+                );
+            } catch (Exception e) {
+                LOG.warn(String.valueOf(e));
+            }
+            byte[] encodedBytes = baos.toByteArray();
+            TransactionBuilder builder = createTransactionBuilder("WeatherData");
+            safeWriteToCharacteristic(builder,
+                    PineTimeJFConstants.UUID_CHARACTERISTIC_WEATHER_DATA,
+                    encodedBytes);
+
+            builder.queue(getQueue());
+        }
+
+        // Current temperature
+        if (weatherSpec.currentTemp >= -273.15) {
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            try {
+                new CborEncoder(baos).encode(new CborBuilder()
+                        .startMap() // This map is not fixed-size, which is not great, but it might come in a library update
+                        .put("Timestamp", System.currentTimeMillis() / 1000L)
+                        .put("Expires", 60 * 60 * 1 + WEATHER_GRACE_TIME) // 1h this should be the weather provider's interval, really
+                        .put("EventType", WeatherData.EventType.Temperature.value)
+                        .put("Temperature", (int) ((weatherSpec.currentTemp - 273.15) * 100))
+                        .put("DewPoint", (int) (-32768))
+                        .end()
+                        .build()
+                );
+            } catch (Exception e) {
+                LOG.warn(String.valueOf(e));
+            }
+            byte[] encodedBytes = baos.toByteArray();
+            TransactionBuilder builder = createTransactionBuilder("WeatherData");
+            safeWriteToCharacteristic(builder,
+                    PineTimeJFConstants.UUID_CHARACTERISTIC_WEATHER_DATA,
+                    encodedBytes);
+
+            builder.queue(getQueue());
+        }
+
+        // 24h temperature forecast
+        // TODO: This is disabled until WeatherSpec contains how often this data is pushed
+        /*
+        if (weatherSpec.todayMinTemp >= -273.15 &&
+                weatherSpec.todayMaxTemp >= -273.15) { // Some sanity checking, should really be nullable
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            try {
+                new CborEncoder(baos).encode(new CborBuilder()
+                        .startMap() // This map is not fixed-size, which is not great, but it might come in a library update
+                        .put("Timestamp", System.currentTimeMillis() / 1000L)
+                        .put("Expires", 60 * 60 * 24) // 24h, because the temperature is today's
+                        .put("EventType", WeatherData.EventType.Temperature.value)
+                        .put("Temperature", (int) ((((weatherSpec.todayMinTemp - 273.15) + (weatherSpec.todayMaxTemp - 273.15)) / 2) * 100))
+                        .put("DewPoint", (int) (-32768))
+                        .end()
+                        .build()
+                );
+            } catch (Exception e) {
+                LOG.warn(String.valueOf(e));
+            }
+            byte[] encodedBytes = baos.toByteArray();
+            TransactionBuilder builder = createTransactionBuilder("WeatherData");
+            safeWriteToCharacteristic(builder,
+                    PineTimeJFConstants.UUID_CHARACTERISTIC_WEATHER_DATA,
+                    encodedBytes);
+
+            builder.queue(getQueue());
+        }
+        */
+
+        // Wind speed
+        if (weatherSpec.windSpeed != 0.0f) {
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            try {
+                new CborEncoder(baos).encode(new CborBuilder()
+                        .startMap() // This map is not fixed-size, which is not great, but it might come in a library update
+                        .put("Timestamp", System.currentTimeMillis() / 1000L)
+                        .put("Expires", 60 * 60 * 1 + WEATHER_GRACE_TIME) // 1h
+                        .put("EventType", WeatherData.EventType.Wind.value)
+                        .put("SpeedMin", (int) (weatherSpec.windSpeed / 60 / 60 * 1000))
+                        .put("SpeedMax", (int) (weatherSpec.windSpeed / 60 / 60 * 1000))
+                        .put("DirectionMin", (int) (0.71 * weatherSpec.windDirection))
+                        .put("DirectionMax", (int) (0.71 * weatherSpec.windDirection))
+                        .end()
+                        .build()
+                );
+            } catch (Exception e) {
+                LOG.warn(String.valueOf(e));
+            }
+            byte[] encodedBytes = baos.toByteArray();
+            TransactionBuilder builder = createTransactionBuilder("WeatherData");
+            safeWriteToCharacteristic(builder,
+                    PineTimeJFConstants.UUID_CHARACTERISTIC_WEATHER_DATA,
+                    encodedBytes);
+
+            builder.queue(getQueue());
+        }
+
+        // Current weather condition
+        if (mapOpenWeatherConditionToPineTimePrecipitation(weatherSpec.currentConditionCode) != WeatherData.PrecipitationType.Length) {
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            try {
+                new CborEncoder(baos).encode(new CborBuilder()
+                        .startMap() // This map is not fixed-size, which is not great, but it might come in a library update
+                        .put("Timestamp", System.currentTimeMillis() / 1000L)
+                        .put("Expires", 60 * 60 * 1 + WEATHER_GRACE_TIME) // 1h
+                        .put("EventType", WeatherData.EventType.Precipitation.value)
+                        .put("Type", (int) mapOpenWeatherConditionToPineTimePrecipitation(weatherSpec.currentConditionCode).value)
+                        .put("Amount", (int) 0)
+                        .end()
+                        .build()
+                );
+            } catch (Exception e) {
+                LOG.warn(String.valueOf(e));
+            }
+            byte[] encodedBytes = baos.toByteArray();
+            TransactionBuilder builder = createTransactionBuilder("WeatherData");
+            safeWriteToCharacteristic(builder,
+                    PineTimeJFConstants.UUID_CHARACTERISTIC_WEATHER_DATA,
+                    encodedBytes);
+
+            builder.queue(getQueue());
+        }
+
+        if (mapOpenWeatherConditionToPineTimeObscuration(weatherSpec.currentConditionCode) != WeatherData.ObscurationType.Length) {
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            try {
+                new CborEncoder(baos).encode(new CborBuilder()
+                        .startMap() // This map is not fixed-size, which is not great, but it might come in a library update
+                        .put("Timestamp", System.currentTimeMillis() / 1000L)
+                        .put("Expires", 60 * 60 * 1 + WEATHER_GRACE_TIME) // 1h
+                        .put("EventType", WeatherData.EventType.Obscuration.value)
+                        .put("Type", (int) mapOpenWeatherConditionToPineTimeObscuration(weatherSpec.currentConditionCode).value)
+                        .put("Amount", (int) 65535)
+                        .end()
+                        .build()
+                );
+            } catch (Exception e) {
+                LOG.warn(String.valueOf(e));
+            }
+            byte[] encodedBytes = baos.toByteArray();
+            TransactionBuilder builder = createTransactionBuilder("WeatherData");
+            safeWriteToCharacteristic(builder,
+                    PineTimeJFConstants.UUID_CHARACTERISTIC_WEATHER_DATA,
+                    encodedBytes);
+
+            builder.queue(getQueue());
+        }
+
+        if (mapOpenWeatherConditionToPineTimeSpecial(weatherSpec.currentConditionCode) != WeatherData.SpecialType.Length) {
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            try {
+                new CborEncoder(baos).encode(new CborBuilder()
+                        .startMap() // This map is not fixed-size, which is not great, but it might come in a library update
+                        .put("Timestamp", System.currentTimeMillis() / 1000L)
+                        .put("Expires", 60 * 60 * 1 + WEATHER_GRACE_TIME) // 1h
+                        .put("EventType", WeatherData.EventType.Special.value)
+                        .put("Type", mapOpenWeatherConditionToPineTimeSpecial(weatherSpec.currentConditionCode).value)
+                        .end()
+                        .build()
+                );
+            } catch (Exception e) {
+                LOG.warn(String.valueOf(e));
+            }
+            byte[] encodedBytes = baos.toByteArray();
+            TransactionBuilder builder = createTransactionBuilder("WeatherData");
+            safeWriteToCharacteristic(builder,
+                    PineTimeJFConstants.UUID_CHARACTERISTIC_WEATHER_DATA,
+                    encodedBytes);
+
+            builder.queue(getQueue());
+        }
+
+        if (mapOpenWeatherConditionToCloudCover(weatherSpec.currentConditionCode) != -1) {
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            try {
+                new CborEncoder(baos).encode(new CborBuilder()
+                        .startMap() // This map is not fixed-size, which is not great, but it might come in a library update
+                        .put("Timestamp", System.currentTimeMillis() / 1000L)
+                        .put("Expires", 60 * 60 * 1 + WEATHER_GRACE_TIME) // 1h
+                        .put("EventType", WeatherData.EventType.Clouds.value)
+                        .put("Amount", (int) (mapOpenWeatherConditionToCloudCover(weatherSpec.currentConditionCode)))
+                        .end()
+                        .build()
+                );
+            } catch (Exception e) {
+                LOG.warn(String.valueOf(e));
+            }
+            byte[] encodedBytes = baos.toByteArray();
+            TransactionBuilder builder = createTransactionBuilder("WeatherData");
+            safeWriteToCharacteristic(builder,
+                    PineTimeJFConstants.UUID_CHARACTERISTIC_WEATHER_DATA,
+                    encodedBytes);
+
+            builder.queue(getQueue());
+        }
+
+        LOG.debug("Wrote weather data");
+    }
+
+    private void onSendWeatherSimple(WeatherSpec weatherSpec) {
+        long timestampLocal = weatherSpec.timestamp + Calendar.getInstance().getTimeZone().getOffset(Calendar.getInstance(TimeZone.getTimeZone("UTC")).getTimeInMillis()) / 1000L;
+
+        ByteBuffer currentPacket = ByteBuffer.allocate(49).order(ByteOrder.LITTLE_ENDIAN);
+        currentPacket.putLong(2, timestampLocal);
+        currentPacket.putShort(10, (short) ((weatherSpec.currentTemp - 273.15) * 100));
+        currentPacket.putShort(12, (short) ((weatherSpec.todayMinTemp - 273.15) * 100));
+        currentPacket.putShort(14, (short) ((weatherSpec.todayMaxTemp - 273.15) * 100));
+        if (weatherSpec.location != null) {
+            byte[] locationBytes = nodomain.freeyourgadget.gadgetbridge.util.StringUtils.truncateToBytes(weatherSpec.location, 32);
+            for (int i = 0; i < locationBytes.length; i++) {
+                currentPacket.put(16 + i, locationBytes[i]);
+            }
+        }
+        currentPacket.put(48, mapOpenWeatherConditionToPineTimeCondition(weatherSpec.currentConditionCode).value);
+
+        TransactionBuilder currentBuilder = createTransactionBuilder("SimpleWeatherData");
+        safeWriteToCharacteristic(currentBuilder,
+            PineTimeJFConstants.UUID_CHARACTERISTIC_SIMPLE_WEATHER_DATA,
+            currentPacket.array());
+
+        currentBuilder.queue(getQueue());
+
+        if (weatherSpec.forecasts == null) {
+            return;
+        }
+
+        ByteBuffer forecastPacket = ByteBuffer.allocate(36).order(ByteOrder.LITTLE_ENDIAN);
+        forecastPacket.put(0, (byte) 1);
+        forecastPacket.putLong(2, timestampLocal);
+        byte nbDays = (byte) Math.min(weatherSpec.forecasts.size(), 5);
+        forecastPacket.put(10, nbDays);
+        for (int i = 0; i < nbDays; i++) {
+            forecastPacket.putShort(11 + i * 5, (short) ((weatherSpec.forecasts.get(i).minTemp - 273.15) * 100));
+            forecastPacket.putShort(11 + i * 5 + 2, (short) ((weatherSpec.forecasts.get(i).maxTemp - 273.15) * 100));
+            forecastPacket.put(11 + i * 5 + 4, mapOpenWeatherConditionToPineTimeCondition(weatherSpec.forecasts.get(i).conditionCode).value);
+        }
+
+        TransactionBuilder forecastBuilder = createTransactionBuilder("SimpleWeatherData");
+        safeWriteToCharacteristic(forecastBuilder,
+                PineTimeJFConstants.UUID_CHARACTERISTIC_SIMPLE_WEATHER_DATA,
+                forecastPacket.array());
+
+        forecastBuilder.queue(getQueue());
+    }
+
+    @Override
+    public void onSendWeather(ArrayList<WeatherSpec> weatherSpecs) {
+        WeatherSpec weatherSpec = weatherSpecs.get(0);
+
+        if (this.firmwareVersionMajor < 1 || (this.firmwareVersionMajor == 1 && this.firmwareVersionMinor <= 7)) {
+            // Not supported
+            return;
+        }
+
+        if (this.firmwareVersionMajor == 1 && this.firmwareVersionMinor <= 13) {
+            this.onSendWeatherCBOR(weatherSpec);
+        } else {
+            this.onSendWeatherSimple(weatherSpec);
+        }
+    }
+
+    /**
+     * Helper function that just converts an integer into a byte array
+     */
+    private static byte[] intToBytes(int source) {
+        return ByteBuffer.allocate(4).putInt(source).array();
+    }
+
+    /**
+     * This will check if the characteristic exists and can be written
+     * <p>
+     * Keeps backwards compatibility with firmware that can't take all the information
+     */
+    private void safeWriteToCharacteristic(TransactionBuilder builder, UUID uuid, byte[] data) {
+        BluetoothGattCharacteristic characteristic = getCharacteristic(uuid);
+        if (characteristic != null &&
+                (characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_WRITE) > 0) {
+            builder.write(characteristic, data);
+        } else {
+            LOG.warn("Tried to write to a characteristic that did not exist or was not writable!");
+        }
+    }
+
+    private void setInitialized(TransactionBuilder builder) {
+        builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZED, getContext()));
+    }
+
+    private void requestDeviceInfo(TransactionBuilder builder) {
+        LOG.debug("Requesting Device Info!");
+        deviceInfoProfile.requestDeviceInfo(builder);
+    }
+
+    private void handleDeviceInfo(nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo.DeviceInfo info) {
+        LOG.warn("Device info: " + info);
+        versionCmd.hwVersion = info.getHardwareRevision();
+        versionCmd.fwVersion = info.getFirmwareRevision();
+
+        if (versionCmd.fwVersion != null && !versionCmd.fwVersion.isEmpty()) {
+            // FW version format : "major.minor.patch". Ex : "0.8.2"
+            String[] tokens = StringUtils.split(versionCmd.fwVersion, ".");
+            if (tokens.length == 3) {
+                firmwareVersionMajor = Integer.parseInt(tokens[0]);
+                firmwareVersionMinor = Integer.parseInt(tokens[1]);
+                firmwareVersionPatch = Integer.parseInt(tokens[2]);
+            }
+        }
+
+        handleGBDeviceEvent(versionCmd);
+    }
+
+    private boolean isFirmwareAtLeastVersion0_15() {
+        return firmwareVersionMajor > 0 || firmwareVersionMinor >= 15;
+    }
+
+    /**
+     * Nordic DFU needs this function to log DFU-related messages
+     */
+    @Override
+    public void onLogEvent(final String deviceAddress, final int level, final String message) {
+        LOG.debug(message);
+    }
+
+    private void onReceiveStepsSample(int steps) {
+        this.onReceiveStepsSample((int) (Calendar.getInstance().getTimeInMillis() / 1000l), steps);
+    }
+
+    private void onReceiveStepsSample(int timeStamp, int steps) {
+        PineTimeActivitySample sample = new PineTimeActivitySample();
+
+        int dayStepCount = this.getStepsOnDay(timeStamp);
+        int prevoiusDayStepCount = this.getStepsOnDay(timeStamp - DAY_SECONDS);
+        int diff = steps - dayStepCount;
+        logDebug(String.format("onReceiveStepsSample: \ndayStepCount=%d, \nsteps=%d, \ndiff=%d, \nprevoiusDayStepCount=%d, ", dayStepCount, steps, diff, prevoiusDayStepCount));
+
+        if (dayStepCount == 0) {
+            if (quarantinedSteps == 0 && steps > 0) {
+                logDebug("ignoring " + diff + " steps: ignore the first sync of the day as it could be outstanding values from the previous day");
+                quarantinedSteps = steps;
+                return;
+            } else if (quarantinedSteps > 0 && steps == 0) {
+                logDebug("dropp " + quarantinedSteps + " outstanding steps: those looks like outstanding values from the previous day");
+                quarantinedSteps = 0;
+            } else {
+                logDebug("reset " + quarantinedSteps + " quarantined steps...");
+                quarantinedSteps = 0;
+            }
+        }
+
+        if (diff > 0) {
+            logDebug("adding " + diff + " steps");
+
+            sample.setSteps(diff);
+            sample.setTimestamp(timeStamp);
+
+            // since it's a local timestamp, it should NOT be treated as Activity because it will spoil activity charts
+            sample.setRawKind(ActivityKind.UNKNOWN.getCode());
+
+            this.addGBActivitySample(sample);
+
+            Intent intent = new Intent(DeviceService.ACTION_REALTIME_SAMPLES)
+                    .putExtra(GBDevice.EXTRA_DEVICE, getDevice())
+                    .putExtra(DeviceService.EXTRA_REALTIME_SAMPLE, sample)
+                    .putExtra(DeviceService.EXTRA_TIMESTAMP, sample.getTimestamp());
+            LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent);
+        } else {
+            logDebug("ignoring " + diff + " steps");
+        }
+    }
+
+    private void onReceiveHeartRateMeasurement(int heartrate) {
+        this.onReceiveHeartRateMeasurement((int) (Calendar.getInstance().getTimeInMillis() / 1000l), heartrate);
+    }
+
+    private void onReceiveHeartRateMeasurement(int timeStamp, int heartrate) {
+        PineTimeActivitySample sample = new PineTimeActivitySample();
+
+        logDebug(String.format("onReceiveHeartRateMeasurement: \nheartrate=%d", heartrate));
+
+        if (heartrate > 0) {
+            sample.setHeartRate(heartrate);
+            sample.setTimestamp(timeStamp);
+            // since it's a local timestamp, it should NOT be treated as Activity because it will spoil activity charts
+            sample.setRawKind(ActivityKind.UNKNOWN.getCode());
+
+            this.addGBActivitySample(sample);
+
+            Intent intent = new Intent(DeviceService.ACTION_REALTIME_SAMPLES)
+                    .putExtra(GBDevice.EXTRA_DEVICE, getDevice())
+                    .putExtra(DeviceService.EXTRA_REALTIME_SAMPLE, sample)
+                    .putExtra(DeviceService.EXTRA_TIMESTAMP, sample.getTimestamp());
+            LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent);
+        } else {
+            logDebug("ignoring heartrate of 0");
+        }
+    }
+
+    /**
+     * @param timeStamp Time stamp (in seconds)  at some point during the requested day.
+     */
+    private int getStepsOnDay(int timeStamp) {
+        try (DBHandler dbHandler = GBApplication.acquireDB()) {
+
+            Calendar dayStart = Calendar.getInstance();
+            Calendar dayEnd = Calendar.getInstance();
+
+            this.getDayStartEnd(timeStamp, dayStart, dayEnd);
+
+            PineTimeActivitySampleProvider provider = new PineTimeActivitySampleProvider(this.getDevice(), dbHandler.getDaoSession());
+
+            List<PineTimeActivitySample> samples = provider.getAllActivitySamples(
+                    (int) (dayStart.getTimeInMillis() / 1000L),
+                    (int) (dayEnd.getTimeInMillis() / 1000L));
+
+            int totalSteps = 0;
+
+            for (PineTimeActivitySample sample : samples) {
+                totalSteps += sample.getSteps();
+            }
+
+            return totalSteps;
+
+        } catch (Exception ex) {
+            LOG.error(ex.getMessage());
+
+            return 0;
+        }
+    }
+
+    /**
+     * @param timeStampUtc in seconds
+     */
+    private void getDayStartEnd(int timeStampUtc, Calendar start, Calendar end) {
+        int timeOffsetToUtc = Calendar.getInstance().getTimeZone().getOffset(Calendar.getInstance(TimeZone.getTimeZone("UTC")).getTimeInMillis()) / 1000;
+        int timeStampLocal = timeStampUtc + timeOffsetToUtc;
+        int timeStampStartUtc = ((timeStampLocal / DAY_SECONDS) * DAY_SECONDS);
+        int timeStampEndUtc = (timeStampStartUtc + DAY_SECONDS - 1);
+
+        start.setTimeInMillis((timeStampStartUtc - timeOffsetToUtc) * 1000L);
+        end.setTimeInMillis((timeStampEndUtc - timeOffsetToUtc) * 1000L);
+
+        Calendar calTimeStamp = new GregorianCalendar();
+        calTimeStamp.setTimeInMillis(timeStampUtc * 1000L);
+
+        logDebug(String.format("getDayStartEnd: \ntimeStamp=%d (%s), \ntimeStampStartUtc=%d (-offset=%s), \ntimeStampEndUtc=%d (-offset=%s)", timeStampUtc, calTimeStamp.getTime(), timeStampStartUtc, start.getTime(), timeStampEndUtc, end.getTime()));
+    }
+
+
+    private void addGBActivitySamples(PineTimeActivitySample[] samples) {
+        try (DBHandler dbHandler = GBApplication.acquireDB()) {
+
+            User user = DBHelper.getUser(dbHandler.getDaoSession());
+            Device device = DBHelper.getDevice(this.getDevice(), dbHandler.getDaoSession());
+
+            PineTimeActivitySampleProvider provider = new PineTimeActivitySampleProvider(this.getDevice(), dbHandler.getDaoSession());
+
+            for (PineTimeActivitySample sample : samples) {
+                sample.setDevice(device);
+                sample.setUser(user);
+                sample.setProvider(provider);
+
+                sample.setRawIntensity(ActivitySample.NOT_MEASURED);
+
+                Optional<PineTimeActivitySample> storedSample = provider.getSampleForTimestamp(sample.getTimestamp());
+                if (storedSample.isPresent()) {
+                    sample.setHeartRate(Math.max(sample.getHeartRate(), storedSample.get().getHeartRate()));
+                    sample.setSteps(Math.max(sample.getSteps(), storedSample.get().getSteps()));
+                }
+
+                provider.addGBActivitySample(sample);
+            }
+
+            GB.signalActivityDataFinish(getDevice());
+
+        } catch (Exception ex) {
+            GB.toast(getContext(), "Error saving samples: " + ex.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
+            GB.updateTransferNotification(null, "Data transfer failed", false, 0, getContext());
+
+            LOG.error(ex.getMessage());
+        }
+    }
+
+    private void addGBActivitySample(PineTimeActivitySample sample) {
+        this.addGBActivitySamples(new PineTimeActivitySample[]{sample});
+    }
+
+    private void logDebug(String logMessage) {
+        logDebug(logMessage, logMessage);
+    }
+
+    private void logDebug(String logMessage, String toastMessage) {
+        LOG.debug(logMessage);
+        //GB.toast(getContext(), toastMessage, Toast.LENGTH_LONG, GB.WARN);
+    }
+
+    @Override
+    public boolean getImplicitCallbackModify() {
+        return true;
+    }
+
+    @Override
+    public boolean getSendWriteRequestResponse() {
+        return false;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qc35/QC35BaseSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qc35/QC35BaseSupport.java
new file mode 100644
index 0000000..808e412
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qc35/QC35BaseSupport.java
@@ -0,0 +1,46 @@
+/*  Copyright (C) 2021-2024 Arjan Schrijver, Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qc35;
+
+import nodomain.freeyourgadget.gadgetbridge.service.serial.AbstractSerialDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceIoThread;
+import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol;
+
+public class QC35BaseSupport extends AbstractSerialDeviceSupport {
+
+    @Override
+    public boolean connect() {
+        getDeviceProtocol();
+        getDeviceIOThread().start();
+        return true;
+    }
+
+    @Override
+    public boolean useAutoConnect() {
+        return false;
+    }
+
+    @Override
+    protected GBDeviceProtocol createDeviceProtocol() {
+        return new QC35Protocol(getDevice());
+    }
+
+    @Override
+    protected GBDeviceIoThread createDeviceIOThread() {
+        return new QC35IOThread(getDevice(), getContext(), (QC35Protocol) createDeviceProtocol(), this, getBluetoothAdapter());
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qc35/QC35IOThread.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qc35/QC35IOThread.java
new file mode 100644
index 0000000..0e75f3d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qc35/QC35IOThread.java
@@ -0,0 +1,78 @@
+/*  Copyright (C) 2021-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qc35;
+
+import android.bluetooth.BluetoothAdapter;
+import android.content.Context;
+import android.os.ParcelUuid;
+
+import androidx.annotation.NonNull;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.btclassic.BtClassicIoThread;
+import nodomain.freeyourgadget.gadgetbridge.service.serial.AbstractSerialDeviceSupport;
+
+public class QC35IOThread extends BtClassicIoThread {
+    QC35Protocol protocol;
+    byte[] buffer = new byte[1024];
+
+    private Logger logger = LoggerFactory.getLogger(getClass());
+
+    public QC35IOThread(GBDevice gbDevice, Context context, QC35Protocol deviceProtocol, AbstractSerialDeviceSupport deviceSupport, BluetoothAdapter btAdapter) {
+        super(gbDevice, context, deviceProtocol, deviceSupport, btAdapter);
+        this.protocol = deviceProtocol;
+    }
+
+    @NonNull
+    @Override
+    protected UUID getUuidToConnect(@NonNull ParcelUuid[] uuids) {
+        return UUID.fromString("00001101-0000-1000-8000-00805f9b34fb");
+    }
+
+    @Override
+    protected void initialize() {
+        super.initialize();
+
+        byte[] connectPayload = new byte[]{0x00, 0x01, 0x01, 0x00};
+        byte[] ncPayload = protocol.encodeSendConfiguration(DeviceSettingsPreferenceConst.PREF_QC35_NOISE_CANCELLING_LEVEL);
+        byte[] batteryPayload = new byte[]{0x02, 0x02, 0x01, 0x00};
+        byte[] packet = new byte[connectPayload.length + ncPayload.length + batteryPayload.length];
+        System.arraycopy(connectPayload, 0, packet, 0, connectPayload.length);
+        System.arraycopy(ncPayload, 0, packet, connectPayload.length, ncPayload.length);
+        System.arraycopy(batteryPayload, 0, packet, ncPayload.length + connectPayload.length, batteryPayload.length);
+
+        getDevice().setFirmwareVersion("0");
+
+        write(packet);
+    }
+    @Override
+    protected byte[] parseIncoming(InputStream inStream) throws IOException {
+        int size = inStream.read(buffer);
+        logger.debug("read bytes: {}", size);
+        byte[] actual = new byte[size];
+        System.arraycopy(buffer, 0, actual, 0, size);
+        return actual;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qc35/QC35Protocol.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qc35/QC35Protocol.java
new file mode 100644
index 0000000..7be16af
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qc35/QC35Protocol.java
@@ -0,0 +1,92 @@
+/*  Copyright (C) 2021-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qc35;
+
+import android.content.SharedPreferences;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.BatteryState;
+import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol;
+import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
+
+public class QC35Protocol extends GBDeviceProtocol {
+    Logger logger = LoggerFactory.getLogger(getClass());
+    protected QC35Protocol(GBDevice device) {
+        super(device);
+    }
+
+    @Override
+    public GBDeviceEvent[] decodeResponse(byte[] responseData) {
+        logger.debug("response: {}", StringUtils.bytesToHex(responseData));
+
+        ArrayList<GBDeviceEvent> events = new ArrayList<>();
+
+        ByteBuffer buffer = ByteBuffer.wrap(responseData);
+        while(buffer.remaining() > 0){
+            int first = buffer.get();
+            int second = buffer.get();
+            int third = buffer.get();
+            int length = buffer.get();
+            byte[] data = new byte[length];
+            buffer.get(data);
+            if(first == 0x02){
+                if(second == 0x02){
+                    if(third == 0x03){
+                        GBDeviceEventBatteryInfo batteryInfo = new GBDeviceEventBatteryInfo();
+                        batteryInfo.level = data[0];
+                        batteryInfo.state = BatteryState.BATTERY_NORMAL;
+                        events.add(batteryInfo);
+                    }
+                }
+            }
+        }
+
+        return events.toArray(new GBDeviceEvent[0]);
+    }
+
+    @Override
+    public byte[] encodeTestNewFunction() {
+        return new byte[]{0x02, 0x02, 0x01, 0x00};
+    }
+
+    @Override
+    public byte[] encodeSendConfiguration(String config) {
+        SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress());
+
+        if(config.equals(DeviceSettingsPreferenceConst.PREF_QC35_NOISE_CANCELLING_LEVEL)){
+            int level = prefs.getInt(config, 0);
+            if(level == 2){
+                level = 1;
+            }else if(level == 1){
+                level = 3;
+            }
+            return new byte[]{0x01, 0x06, 0x02, 0x01, (byte) level};
+        }
+
+        return null;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/QHybridBaseSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/QHybridBaseSupport.java
new file mode 100644
index 0000000..fe73109
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/QHybridBaseSupport.java
@@ -0,0 +1,42 @@
+/*  Copyright (C) 2019-2024 Arjan Schrijver, Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid;
+
+import org.slf4j.Logger;
+
+import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
+
+class QHybridBaseSupport extends AbstractBTLEDeviceSupport {
+    QHybridBaseSupport(Logger logger) {
+        super(logger);
+    }
+
+    @Override
+    public boolean useAutoConnect() {
+        return false;
+    }
+
+    @Override
+    public boolean getImplicitCallbackModify() {
+        return true;
+    }
+
+    @Override
+    public boolean getSendWriteRequestResponse() {
+        return false;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/QHybridSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/QHybridSupport.java
new file mode 100644
index 0000000..19fbb40
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/QHybridSupport.java
@@ -0,0 +1,854 @@
+/*  Copyright (C) 2019-2024 Andreas Shimokawa, Arjan Schrijver, Carsten
+    Pfeiffer, Daniel Dakhno, Dmitriy Bogdanov, Taavi Eom盲e
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid;
+
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.media.AudioManager;
+import android.net.Uri;
+import android.os.Bundle;
+import android.widget.Toast;
+
+import androidx.core.content.ContextCompat;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.UUID;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
+import nodomain.freeyourgadget.gadgetbridge.devices.qhybrid.NotificationConfiguration;
+import nodomain.freeyourgadget.gadgetbridge.devices.qhybrid.PackageConfigHelper;
+import nodomain.freeyourgadget.gadgetbridge.externalevents.NotificationListener;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
+import nodomain.freeyourgadget.gadgetbridge.model.BatteryState;
+import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.GenericItem;
+import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.NavigationInfoSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.RecordedDataTypes;
+import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.GattService;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.WatchAdapter;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.WatchAdapterFactory;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil.FossilWatchAdapter;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil_hr.FossilHRWatchAdapter;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.file.FileHandle;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.DownloadFileRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.MoveHandsRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.PlayNotificationRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol;
+import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class QHybridSupport extends QHybridBaseSupport {
+    public static final String QHYBRID_COMMAND_CONTROL = "qhybrid_command_control";
+    public static final String QHYBRID_COMMAND_UNCONTROL = "qhybrid_command_uncontrol";
+    public static final String QHYBRID_COMMAND_SET = "qhybrid_command_set";
+    public static final String QHYBRID_COMMAND_MOVE = "qhybrid_command_move";
+    public static final String QHYBRID_COMMAND_SAVE_CALIBRATION = "qhybrid_command_save_calibration";
+    public static final String QHYBRID_COMMAND_VIBRATE = "qhybrid_command_vibrate";
+    public static final String QHYBRID_COMMAND_UPDATE = "qhybrid_command_update";
+    public static final String QHYBRID_COMMAND_UPDATE_TIMEZONE = "qhybrid_command_update_timezone";
+    public static final String QHYBRID_COMMAND_NOTIFICATION = "qhybrid_command_notification";
+    public static final String QHYBRID_COMMAND_UPDATE_SETTINGS = "nodomain.freeyourgadget.gadgetbridge.Q_UPDATE_SETTINGS";
+    public static final String QHYBRID_COMMAND_OVERWRITE_BUTTONS = "nodomain.freeyourgadget.gadgetbridge.Q_OVERWRITE_BUTTONS";
+    public static final String QHYBRID_COMMAND_UPDATE_WIDGETS = "nodomain.freeyourgadget.gadgetbridge.Q_UPDATE_WIDGETS";
+    public static final String QHYBRID_COMMAND_SET_MENU_MESSAGE = "nodomain.freeyourgadget.gadgetbridge.Q_SET_MENU_MESSAGE";
+    public static final String QHYBRID_COMMAND_SEND_MENU_ITEMS = "nodomain.freeyourgadget.gadgetbridge.Q_SEND_MENU_ITEMS";
+    public static final String QHYBRID_COMMAND_SET_WIDGET_CONTENT = "nodomain.freeyourgadget.gadgetbridge.Q_SET_WIDGET_CONTENT";
+    public static final String QHYBRID_COMMAND_SET_BACKGROUND_IMAGE = "nodomain.freeyourgadget.gadgetbridge.Q_SET_BACKGROUND_IMAGE";
+    public static final String QHYBRID_COMMAND_UNINSTALL_APP = "nodomain.freeyourgadget.gadgetbridge.Q_UNINSTALL_APP";
+    public static final String QHYBRID_COMMAND_PUSH_CONFIG = "nodomain.freeyourgadget.gadgetbridge.Q_PUSH_CONFIG";
+    public static final String QHYBRID_COMMAND_SWITCH_WATCHFACE = "nodomain.freeyourgadget.gadgetbridge.Q_SWITCH_WATCHFACE";
+
+    public static final String QHYBRID_COMMAND_DOWNLOAD_FILE = "nodomain.freeyourgadget.gadgetbridge.Q_DOWNLOAD_FILE";
+    public static final String QHYBRID_COMMAND_UPLOAD_FILE = "nodomain.freeyourgadget.gadgetbridge.Q_UPLOAD_FILE";
+
+    public static final String QHYBRID_COMMAND_SET_MENU_STRUCTURE = "nodomain.freeyourgadget.gadgetbridge.Q_SET_MENU_STRUCTURE";
+
+    public static final String QHYBRID_ACTION_DOWNLOADED_FILE = "nodomain.freeyourgadget.gadgetbridge.Q_DOWNLOADED_FILE";
+    public static final String QHYBRID_ACTION_UPLOADED_FILE = "nodomain.freeyourgadget.gadgetbridge.Q_UPLOADED_FILE";
+
+    private static final String QHYBRID_ACTION_SET_ACTIVITY_HAND = "nodomain.freeyourgadget.gadgetbridge.Q_SET_ACTIVITY_HAND";
+
+    public static final String QHYBRID_EVENT_SETTINGS_UPDATED = "nodomain.freeyourgadget.gadgetbridge.Q_SETTINGS_UPDATED";
+    public static final String QHYBRID_EVENT_FILE_UPLOADED = "nodomain.freeyourgadget.gadgetbridge.Q_FILE_UPLOADED";
+    public static final String QHYBRID_COMMAND_NOTIFICATION_CONFIG_CHANGED = "nodomain.freeyourgadget.gadgetbridge.Q_NOTIFICATION_CONFIG_CHANGED";
+
+    public static final String QHYBRID_EVENT_BUTTON_PRESS = "nodomain.freeyourgadget.gadgetbridge.Q_BUTTON_PRESSED";
+    public static final String QHYBRID_EVENT_MULTI_BUTTON_PRESS = "nodomain.freeyourgadget.gadgetbridge.Q_MULTI_BUTTON_PRESSED";
+    public static final String QHYBRID_EVENT_COMMUTE_MENU = "nodomain.freeyourgadget.gadgetbridge.Q_COMMUTE_MENU";
+
+    public static final String ITEM_STEP_GOAL = "STEP_GOAL";
+    public static final String ITEM_STEP_COUNT = "STEP_COUNT";
+    public static final String ITEM_VIBRATION_STRENGTH = "VIBRATION_STRENGTH";
+    public static final String ITEM_ACTIVITY_POINT = "ACTIVITY_POINT";
+    public static final String ITEM_EXTENDED_VIBRATION_SUPPORT = "EXTENDED_VIBRATION";
+    public static final String ITEM_HAS_ACTIVITY_HAND = "HAS_ACTIVITY_HAND";
+    public static final String ITEM_USE_ACTIVITY_HAND = "USE_ACTIVITY_HAND";
+    public static final String ITEM_LAST_HEARTBEAT = "LAST_HEARTBEAT";
+    public static final String ITEM_TIMEZONE_OFFSET = "TIMEZONE_OFFSET_COUNT";
+    public static final String ITEM_HEART_RATE_MEASUREMENT_MODE = "HEART_RATE_MEASUREMENT_MODE";
+
+    private static final Logger logger = LoggerFactory.getLogger(QHybridSupport.class);
+    private final BroadcastReceiver commandReceiver;
+    private final BroadcastReceiver globalCommandReceiver;
+
+    private final PackageConfigHelper helper;
+
+    public volatile boolean searchDevice = false;
+
+    private long timeOffset;
+
+    private boolean useActivityHand;
+
+    private WatchAdapter watchAdapter;
+
+    public QHybridSupport() {
+        super(logger);
+        addSupportedService(UUID.fromString("108b5094-4c03-e51c-555e-105d1a1155f0"));
+        addSupportedService(UUID.fromString("3dda0001-957f-7d4a-34a6-74696673696d"));
+        addSupportedService(GattService.UUID_SERVICE_DEVICE_INFORMATION);
+        addSupportedService(GattService.UUID_SERVICE_GENERIC_ACCESS);
+        addSupportedService(GattService.UUID_SERVICE_HEART_RATE);
+        addSupportedService(GattService.UUID_SERVICE_BATTERY_SERVICE);
+        IntentFilter commandFilter = new IntentFilter(QHYBRID_COMMAND_CONTROL);
+        commandFilter.addAction(QHYBRID_COMMAND_UNCONTROL);
+        commandFilter.addAction(QHYBRID_COMMAND_SET);
+        commandFilter.addAction(QHYBRID_COMMAND_MOVE);
+        commandFilter.addAction(QHYBRID_COMMAND_SAVE_CALIBRATION);
+        commandFilter.addAction(QHYBRID_COMMAND_VIBRATE);
+        commandFilter.addAction(QHYBRID_COMMAND_UPDATE);
+        commandFilter.addAction(QHYBRID_COMMAND_UPDATE_TIMEZONE);
+        commandFilter.addAction(QHYBRID_COMMAND_NOTIFICATION);
+        commandFilter.addAction(QHYBRID_COMMAND_UPDATE_SETTINGS);
+        commandFilter.addAction(QHYBRID_COMMAND_OVERWRITE_BUTTONS);
+        commandFilter.addAction(QHYBRID_COMMAND_NOTIFICATION_CONFIG_CHANGED);
+        commandFilter.addAction(QHYBRID_COMMAND_UPDATE_WIDGETS);
+        commandFilter.addAction(QHYBRID_COMMAND_SEND_MENU_ITEMS);
+        commandFilter.addAction(QHYBRID_COMMAND_SET_BACKGROUND_IMAGE);
+        commandFilter.addAction(QHYBRID_COMMAND_UNINSTALL_APP);
+        commandFilter.addAction(QHYBRID_COMMAND_UPLOAD_FILE);
+        commandFilter.addAction(QHYBRID_COMMAND_DOWNLOAD_FILE);
+        commandReceiver = new BroadcastReceiver() {
+
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                Bundle extras = intent.getExtras();
+                NotificationConfiguration config = extras == null ? null : (NotificationConfiguration) intent.getExtras().get("CONFIG");
+                if (intent.getAction() == null) {
+                    return;
+                }
+
+                switch (intent.getAction()) {
+                    case QHYBRID_COMMAND_CONTROL: {
+                        log("sending control request");
+                        watchAdapter.requestHandsControl();
+                        MoveHandsRequest.MovementConfiguration movement = new MoveHandsRequest.MovementConfiguration(false);
+                        if (config != null) {
+                            if(config.getHour() != -1) movement.setHourDegrees(config.getHour());
+                            if(config.getMin() != -1) movement.setHourDegrees(config.getMin());
+                            if(config.getSubEye() != -1) movement.setHourDegrees(config.getSubEye());
+                            watchAdapter.setHands(movement);
+                        } else {
+                            movement.setHourDegrees(0);
+                            movement.setMinuteDegrees(0);
+                            movement.setSubDegrees(0);
+                            watchAdapter.setHands(movement);
+                        }
+                        break;
+                    }
+                    case QHYBRID_COMMAND_UNCONTROL: {
+                        watchAdapter.releaseHandsControl();
+                        break;
+                    }
+                    case QHYBRID_COMMAND_SET: {
+                        if(config == null) break;
+                        MoveHandsRequest.MovementConfiguration movement = new MoveHandsRequest.MovementConfiguration(false);
+                        if(config.getHour() != -1) movement.setHourDegrees(config.getHour());
+                        if(config.getMin() != -1) movement.setHourDegrees(config.getMin());
+                        if(config.getSubEye() != -1) movement.setHourDegrees(config.getSubEye());
+                        watchAdapter.setHands(movement);
+                        break;
+                    }
+                    case QHYBRID_COMMAND_MOVE: {
+                        MoveHandsRequest.MovementConfiguration movement = new MoveHandsRequest.MovementConfiguration(true);
+                        if(extras.containsKey("EXTRA_DISTANCE_HOUR")) movement.setHourDegrees(extras.getShort("EXTRA_DISTANCE_HOUR"));
+                        if(extras.containsKey("EXTRA_DISTANCE_MINUTE")) movement.setMinuteDegrees(extras.getShort("EXTRA_DISTANCE_MINUTE"));
+                        if(extras.containsKey("EXTRA_DISTANCE_SUB")) movement.setSubDegrees(extras.getShort("EXTRA_DISTANCE_SUB"));
+                        watchAdapter.setHands(movement);
+                        break;
+                    }
+                    case QHYBRID_COMMAND_SAVE_CALIBRATION: {
+                        watchAdapter.saveCalibration();
+                        break;
+                    }
+                    case QHYBRID_COMMAND_VIBRATE: {
+                        if (config != null) {
+                            watchAdapter.vibrate(config.getVibration());
+                        }
+                        break;
+                    }
+                    case QHYBRID_COMMAND_NOTIFICATION: {
+                        if (config != null) {
+                            watchAdapter.playNotification(config);
+                        }
+                        break;
+                    }
+                    case QHYBRID_COMMAND_UPDATE: {
+                        loadTimeOffset();
+                        onSetTime();
+                        break;
+                    }
+                    case QHYBRID_COMMAND_UPDATE_TIMEZONE:{
+                        loadTimezoneOffset();
+                        break;
+                    }
+                    case QHYBRID_COMMAND_UPDATE_SETTINGS: {
+                        String newSetting = intent.getStringExtra("EXTRA_SETTING");
+                        switch (newSetting) {
+                            case ITEM_VIBRATION_STRENGTH: {
+                                watchAdapter.setVibrationStrength(Short.parseShort(gbDevice.getDeviceInfo(ITEM_VIBRATION_STRENGTH).getDetails()));
+                                break;
+                            }
+                            case ITEM_STEP_GOAL: {
+                                watchAdapter.setStepGoal(Integer.parseInt(gbDevice.getDeviceInfo(ITEM_STEP_GOAL).getDetails()));
+                                break;
+                            }
+                            case ITEM_USE_ACTIVITY_HAND: {
+                                QHybridSupport.this.useActivityHand = gbDevice.getDeviceInfo(ITEM_USE_ACTIVITY_HAND).getDetails().equals("true");
+                                GBApplication.getPrefs().getPreferences().edit().putBoolean("QHYBRID_USE_ACTIVITY_HAND", useActivityHand).apply();
+                                break;
+                            }
+                        }
+
+                        LocalBroadcastManager.getInstance(getContext()).sendBroadcast(new Intent(QHYBRID_EVENT_SETTINGS_UPDATED));
+                        break;
+                    }
+                    case QHYBRID_COMMAND_OVERWRITE_BUTTONS: {
+                        String buttonConfig = intent.getStringExtra(FossilWatchAdapter.ITEM_BUTTONS);
+                        watchAdapter.overwriteButtons(buttonConfig);
+                        break;
+                    }
+                    case QHYBRID_COMMAND_NOTIFICATION_CONFIG_CHANGED: {
+                        watchAdapter.syncNotificationSettings();
+                        break;
+                    }
+                    case QHYBRID_COMMAND_UPDATE_WIDGETS: {
+                        watchAdapter.updateWidgets();
+                        break;
+                    }
+                    case QHYBRID_COMMAND_SET_BACKGROUND_IMAGE:{
+                        byte[] pixels = intent.getByteArrayExtra("EXTRA_PIXELS_ENCODED");
+                        watchAdapter.setBackgroundImage(pixels);
+                        break;
+                    }
+                    case QHYBRID_COMMAND_DOWNLOAD_FILE:{
+                        byte majorHandle = intent.getByteExtra("EXTRA_MAJORHANDLE", (byte) 0x00);
+                        byte minorHandle = intent.getByteExtra("EXTRA_MINORHANDLE", (byte) 0x00);
+                        String filename = intent.getStringExtra("EXTRA_NAME");
+                        watchAdapter.downloadFile(majorHandle, minorHandle, filename, intent.getBooleanExtra("EXTRA_ENCRYPTED", false), false);
+                        break;
+                    }
+                    case QHYBRID_COMMAND_UPLOAD_FILE:{
+                        handleFileUploadIntent(intent);
+                        break;
+                    }
+                    case QHYBRID_COMMAND_UNINSTALL_APP:{
+                        watchAdapter.uninstallApp(intent.getStringExtra("EXTRA_APP_NAME"));
+                        break;
+                    }
+                }
+            }
+        };
+        LocalBroadcastManager.getInstance(GBApplication.getContext()).registerReceiver(commandReceiver, commandFilter);
+
+        helper = new PackageConfigHelper(GBApplication.getContext());
+
+        IntentFilter globalFilter = new IntentFilter();
+        globalFilter.addAction(QHYBRID_ACTION_SET_ACTIVITY_HAND);
+        globalFilter.addAction(QHYBRID_COMMAND_SET_MENU_MESSAGE);
+        globalFilter.addAction(QHYBRID_COMMAND_SET_WIDGET_CONTENT);
+        globalFilter.addAction(QHYBRID_COMMAND_UPLOAD_FILE);
+        globalFilter.addAction(QHYBRID_COMMAND_PUSH_CONFIG);
+        globalFilter.addAction(QHYBRID_COMMAND_SWITCH_WATCHFACE);
+        globalFilter.addAction(QHYBRID_COMMAND_SET_MENU_STRUCTURE);
+        globalCommandReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                if (watchAdapter == null) {
+                    return;
+                }
+
+                switch (intent.getAction()) {
+                    case QHYBRID_ACTION_SET_ACTIVITY_HAND: {
+                        try {
+                            String extra = String.valueOf(intent.getExtras().get("EXTRA_PROGRESS"));
+                            float progress = Float.parseFloat(extra);
+                            watchAdapter.setActivityHand(progress);
+
+                            watchAdapter.playNotification(new NotificationConfiguration(
+                                    (short) -1,
+                                    (short) -1,
+                                    (short) (progress * 180),
+                                    PlayNotificationRequest.VibrationType.NO_VIBE
+                            ));
+                        } catch (Exception e) {
+                            GB.log("wrong number format", GB.ERROR, e);
+                            logger.debug("trash extra should be number 0.0-1.0");
+                        }
+                        break;
+                    }
+                    case QHYBRID_COMMAND_SET_MENU_MESSAGE: {
+                        String message = String.valueOf(intent.getExtras().get("EXTRA_MESSAGE"));
+                        boolean finished = Boolean.valueOf(String.valueOf(intent.getExtras().get("EXTRA_FINISHED")));
+
+                        watchAdapter.setCommuteMenuMessage(message, finished);
+
+                        break;
+                    }
+                    case QHYBRID_COMMAND_SET_WIDGET_CONTENT: {
+                        HashMap<String, String> widgetValues = new HashMap<>();
+
+                        for(String key : intent.getExtras().keySet()){
+                            if(key.matches("^EXTRA_WIDGET_ID_.*$")){
+                                widgetValues.put(key.substring(16), String.valueOf(intent.getExtras().get(key)));
+                            }
+                        }
+                        boolean render = intent.getBooleanExtra("EXTRA_RENDER", true);
+                        if(widgetValues.size() > 0){
+                            Iterator<String> valuesIterator = widgetValues.keySet().iterator();
+                            valuesIterator.next();
+
+                            while(valuesIterator.hasNext()){
+                                String id = valuesIterator.next();
+                                watchAdapter.setWidgetContent(id, widgetValues.get(id), false);
+                            }
+
+                            valuesIterator = widgetValues.keySet().iterator();
+                            String id = valuesIterator.next();
+                            watchAdapter.setWidgetContent(id, widgetValues.get(id), render);
+                        }else {
+                            String id = String.valueOf(intent.getExtras().get("EXTRA_WIDGET_ID"));
+                            String content = String.valueOf(intent.getExtras().get("EXTRA_CONTENT"));
+                            watchAdapter.setWidgetContent(id, content, render);
+                        }
+                        break;
+                    }
+                    case QHYBRID_COMMAND_UPLOAD_FILE:{
+                        if(!dangerousIntentsAllowed()) break;
+                        handleFileUploadIntent(intent);
+                        break;
+                    }
+                    case QHYBRID_COMMAND_PUSH_CONFIG:{
+                        handleConfigSetIntent(intent);
+                        break;
+                    }
+                    case QHYBRID_COMMAND_SWITCH_WATCHFACE:{
+                        handleSwitchWatchfaceIntent(intent);
+                        break;
+                    }
+                    case QHYBRID_COMMAND_SET_MENU_STRUCTURE:{
+                        handleSetMenuStructure(intent);
+                        break;
+                    }
+                }
+            }
+        };
+        ContextCompat.registerReceiver(GBApplication.getContext(), globalCommandReceiver, globalFilter, ContextCompat.RECEIVER_EXPORTED);
+    }
+
+    private void handleConfigSetIntent(Intent intent) {
+        String configJson = intent.getExtras().getString("EXTRA_CONFIG_JSON", "{}");
+        watchAdapter.pushConfigJson(configJson);
+    }
+
+    private void handleSwitchWatchfaceIntent(Intent intent) {
+        String watchfaceName = intent.getExtras().getString("WATCHFACE_NAME", "");
+        if (watchfaceName != "") {
+            ((FossilHRWatchAdapter) watchAdapter).activateWatchface(watchfaceName);
+        }
+    }
+
+    private void handleSetMenuStructure(Intent intent){
+        if(intent == null){
+            logger.error("intent null");
+            return;
+        }
+        String menuStructureJson = intent.getStringExtra("EXTRA_MENU_STRUCTURE_JSON");
+        if(menuStructureJson == null){
+            logger.error("Menu structure json null");
+            return;
+        }
+        if(menuStructureJson.isEmpty()){
+            logger.error("Menu structure json empty");
+            return;
+        }
+        try {
+            JSONObject menuStructure = new JSONObject(menuStructureJson);
+            watchAdapter.handleSetMenuStructure(menuStructure);
+            GB.toast(getContext().getString(R.string.info_menu_structure_set), Toast.LENGTH_SHORT, GB.INFO);
+        } catch (JSONException e) {
+            logger.error("Menu structure json empty");
+            GB.toast(getContext().getString(R.string.error_invalid_menu_structure), Toast.LENGTH_SHORT, GB.ERROR);
+        }
+    }
+
+    private boolean dangerousIntentsAllowed(){
+        return GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getBoolean(DeviceSettingsPreferenceConst.PREF_HYBRID_HR_DANGEROUS_EXTERNAL_INTENTS, true);
+    }
+
+    private void handleFileUploadIntent(Intent intent){
+        boolean generateHeader = intent.getBooleanExtra("EXTRA_GENERATE_FILE_HEADER", false);
+        String filePath = intent.getStringExtra("EXTRA_PATH");
+        if(!generateHeader){
+            watchAdapter.uploadFileIncludesHeader(filePath);
+            return;
+        }
+        Object handleObject = intent.getSerializableExtra("EXTRA_HANDLE");
+        if(handleObject == null)return;
+        if(handleObject instanceof String){
+            handleObject = FileHandle.fromName((String)handleObject);
+        }
+        if(!(handleObject instanceof FileHandle)) return;
+        FileHandle handle = (FileHandle) handleObject;
+        watchAdapter.uploadFileGenerateHeader(handle, filePath, intent.getBooleanExtra("EXTRA_ENCRYPTED", false));
+    }
+
+    @Override
+    public void onSetCallState(CallSpec callSpec) {
+        super.onSetCallState(callSpec);
+        watchAdapter.onSetCallState(callSpec);
+    }
+
+    @Override
+    public void dispose() {
+        LocalBroadcastManager.getInstance(getContext()).unregisterReceiver(commandReceiver);
+        GBApplication.getContext().unregisterReceiver(globalCommandReceiver);
+        if (watchAdapter != null) {
+            watchAdapter.dispose();
+        }
+        super.dispose();
+    }
+
+    @Override
+    public void onSetAlarms(ArrayList<? extends Alarm> alarms) {
+        super.onSetAlarms(alarms);
+        if(this.watchAdapter == null){
+            GB.toast("watch not connected", Toast.LENGTH_LONG, GB.ERROR);
+            return;
+        }
+        this.watchAdapter.onSetAlarms(alarms);
+    }
+
+    @Override
+    public void onSendConfiguration(String config) {
+        if (watchAdapter != null) {
+            watchAdapter.onSendConfiguration(config);
+        }
+    }
+    private void loadTimeOffset() {
+        timeOffset = getContext().getSharedPreferences(getContext().getPackageName(), Context.MODE_PRIVATE).getInt("QHYBRID_TIME_OFFSET", 0);
+    }
+
+    private void loadTimezoneOffset(){
+        short offset = (short) getContext().getSharedPreferences(getContext().getPackageName(), Context.MODE_PRIVATE).getInt("QHYBRID_TIMEZONE_OFFSET", 0);
+
+        this.watchAdapter.setTimezoneOffsetMinutes(offset);
+    }
+
+    public long getTimeOffset(){
+        return this.timeOffset;
+    }
+
+    @Override
+    protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
+        builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext()));
+
+        this.useActivityHand = GBApplication.getPrefs().getBoolean("QHYBRID_USE_ACTIVITY_HAND", false);
+        getDevice().addDeviceInfo(new GenericItem(ITEM_USE_ACTIVITY_HAND, String.valueOf(this.useActivityHand)));
+
+        if (GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getBoolean(DeviceSettingsPreferenceConst.PREF_USE_CUSTOM_DEVICEICON, true)) {
+            getDevice().setNotificationIconConnected(R.drawable.ic_notification_qhybrid);
+            getDevice().setNotificationIconDisconnected(R.drawable.ic_notification_disconnected_qhybrid);
+        }
+
+        for (int i = 2; i <= 7; i++)
+            builder.notify(getCharacteristic(UUID.fromString("3dda000" + i + "-957f-7d4a-34a6-74696673696d")), true);
+
+        builder.notify(getCharacteristic(UUID.fromString("010541ae-efe8-11c0-91c0-105d1a1155f0")), true);
+        builder.notify(getCharacteristic(UUID.fromString("fef9589f-9c21-4d19-9fc0-105d1a1155f0")), true);
+        builder.notify(getCharacteristic(UUID.fromString("842d2791-0d20-4ce4-1ada-105d1a1155f0")), true);
+
+        builder
+                .read(getCharacteristic(UUID.fromString("00002a19-0000-1000-8000-00805f9b34fb")))
+                .read(getCharacteristic(UUID.fromString("00002a26-0000-1000-8000-00805f9b34fb")))
+                .read(getCharacteristic(UUID.fromString("00002a24-0000-1000-8000-00805f9b34fb")))
+                // .notify(getCharacteristic(UUID.fromString("00002a37-0000-1000-8000-00805f9b34fb")), true)
+        ;
+
+
+        loadTimeOffset();
+
+        return builder;
+    }
+
+    @Override
+    public void onSetMusicInfo(MusicSpec musicSpec) {
+        super.onSetMusicInfo(musicSpec);
+
+        try {
+            watchAdapter.setMusicInfo(musicSpec);
+        }catch (Exception e){
+            GB.log("setMusicInfo error", GB.ERROR, e);
+        }
+    }
+
+    @Override
+    public void onSetMusicState(MusicStateSpec stateSpec) {
+        super.onSetMusicState(stateSpec);
+
+        watchAdapter.setMusicState(stateSpec);
+    }
+
+    @Override
+    public void onFetchRecordedData(int dataTypes) {
+        if ((dataTypes & RecordedDataTypes.TYPE_ACTIVITY) != 0) {
+            GB.updateTransferNotification(getContext().getString(R.string.busy_task_fetch_activity_data), "", true, 0, getContext());
+            getDevice().setBusyTask(getContext().getString(R.string.busy_task_fetch_activity_data));
+            getDevice().sendDeviceUpdateIntent(getContext());
+            this.watchAdapter.onFetchActivityData();
+        }
+    }
+
+    @Override
+    public void onNotification(NotificationSpec notificationSpec) {
+        log("notif from " + notificationSpec.sourceAppId + "  " + notificationSpec.sender + "   " + notificationSpec.phoneNumber);
+        //new Exception().printStackTrace();
+
+        if(this.watchAdapter instanceof FossilHRWatchAdapter){
+            if(((FossilHRWatchAdapter) watchAdapter).playRawNotification(notificationSpec)) return;
+        }
+
+        String packageName = notificationSpec.sourceName;
+
+        NotificationConfiguration config = null;
+        try {
+            config = helper.getNotificationConfiguration(packageName);
+        } catch (Exception e) {
+            GB.toast("error getting notification configuration", Toast.LENGTH_SHORT, GB.ERROR, e);
+        }
+        if (config == null) return;
+
+        log("handling notification");
+
+        if (config.getRespectSilentMode()) {
+            int mode = ((AudioManager) getContext().getApplicationContext().getSystemService(Context.AUDIO_SERVICE)).getRingerMode();
+            if (mode == AudioManager.RINGER_MODE_SILENT) return;
+        }
+
+        boolean enforceActivityHandNotification = config.getHour() == -1 && config.getMin() == -1;
+
+        playNotification(config);
+
+        showNotificationsByAllActive(enforceActivityHandNotification);
+    }
+
+    private void log(String message){
+        logger.debug(message);
+    }
+
+    @Override
+    public String customStringFilter(String inputString) {
+        // Remove Unicode code points that can't be displayed by the watch like emoji skin tones and variation selectors
+        // \x{1f3fb}-\x{1f3ff} = Emoji skin tones: https://en.wikipedia.org/wiki/Emoticons_(Unicode_block)
+        // \ufe00-\ufe0f = Variation selectors: https://en.wikipedia.org/wiki/Variation_Selectors_(Unicode_block)
+        return inputString.replaceAll("[\\x{1f3fb}-\\x{1f3ff}|\\ufe00-\\ufe0f]", "");
+    }
+
+    @Override
+    public void onDeleteNotification(int id) {
+        super.onDeleteNotification(id);
+
+        this.watchAdapter.onDeleteNotification(id);
+
+        showNotificationsByAllActive(true);
+    }
+
+    private void showNotificationsByAllActive(boolean enforceByNotification) {
+        if (!this.useActivityHand) return;
+        double progress = calculateNotificationProgress();
+        showNotificationCountOnActivityHand(progress);
+
+        if (enforceByNotification) {
+            watchAdapter.playNotification(new NotificationConfiguration(
+                    (short) -1,
+                    (short) -1,
+                    (short) (progress * 180),
+                    PlayNotificationRequest.VibrationType.NO_VIBE
+            ));
+        }
+    }
+
+    @Override
+    public void onReset(int flags) {
+        super.onReset(flags);
+        if ((flags & GBDeviceProtocol.RESET_FLAGS_FACTORY_RESET) != 0) {
+            this.watchAdapter.factoryReset();
+        }
+    }
+
+    public double calculateNotificationProgress() {
+        HashMap<NotificationConfiguration, Boolean> configs = new HashMap<>(0);
+        try {
+            for (NotificationConfiguration config : helper.getNotificationConfigurations()) {
+                configs.put(config, false);
+            }
+        } catch (Exception e) {
+            GB.toast("error getting notification configs", Toast.LENGTH_SHORT, GB.ERROR, e);
+        }
+
+        double notificationProgress = 0;
+
+        for (String notificationPackage : NotificationListener.notificationStack) {
+            for (NotificationConfiguration notificationConfiguration : configs.keySet()) {
+                if (configs.get(notificationConfiguration)) continue;
+                if (notificationConfiguration.getPackageName().equals(notificationPackage)) {
+                    notificationProgress += 0.25;
+                    configs.put(notificationConfiguration, true);
+                }
+            }
+        }
+
+        return notificationProgress;
+    }
+
+    @Override
+    public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
+        super.onConnectionStateChange(gatt, status, newState);
+        watchAdapter.onConnectionStateChange(gatt, status, newState);
+    }
+
+    //TODO toggle "Notifications when screen on" options on this check
+    private void showNotificationCountOnActivityHand(double progress) {
+        if (useActivityHand) {
+            watchAdapter.setActivityHand(progress);
+        }
+    }
+
+    @Override
+    public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
+        super.onMtuChanged(gatt, mtu, status);
+        if(watchAdapter == null) return;
+        watchAdapter.onMtuChanged(gatt, mtu, status);
+    }
+
+    private void playNotification(NotificationConfiguration config) {
+        if (config.getMin() == -1 && config.getHour() == -1 && config.getVibration() == PlayNotificationRequest.VibrationType.NO_VIBE)
+            return;
+        watchAdapter.playNotification(config);
+    }
+
+    @Override
+    public void onSetTime() {
+        watchAdapter.setTime();
+    }
+
+    @Override
+    public void onFindDevice(boolean start) {
+        watchAdapter.onFindDevice(start);
+    }
+
+    @Override
+    public void onSendWeather(ArrayList<WeatherSpec> weatherSpecs) {
+        watchAdapter.onSendWeather(weatherSpecs.get(0));
+    }
+
+    @Override
+    public void onTestNewFunction() {
+        watchAdapter.onTestNewFunction();
+    }
+
+    @Override
+    public void onInstallApp(Uri uri) {
+        watchAdapter.onInstallApp(uri);
+    }
+
+    private void backupFile(DownloadFileRequest request) {
+        try {
+            File file = FileUtils.getExternalFile("qFiles/" + request.timeStamp);
+            if (file.exists()) {
+                throw new Exception("file " + file.getPath() + " exists");
+            }
+            logger.debug("Writing file " + file.getPath());
+            try (FileOutputStream fos = new FileOutputStream(file)) {
+                fos.write(request.file);
+            }
+            logger.debug("file written.");
+
+            file = FileUtils.getExternalFile("qFiles/steps");
+            try (FileOutputStream fos = new FileOutputStream(file, true)) {
+                fos.write(("file " + request.timeStamp + " cut\n\n").getBytes());
+            }
+
+            //TODO file stuff
+            // queueWrite(new EraseFileRequest((short) request.fileHandle));
+        } catch (Exception e) {
+            GB.log("error", GB.ERROR, e);
+            if (request.fileHandle > 257) {
+                // queueWrite(new DownloadFileRequest((short) (request.fileHandle - 1)));
+            }
+        }
+    }
+
+    @Override
+    public void handleGBDeviceEvent(GBDeviceEventBatteryInfo deviceEvent){
+        super.handleGBDeviceEvent(deviceEvent);
+    }
+
+    @Override
+    public boolean onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
+        watchAdapter.onCharacteristicWrite(gatt, characteristic, status);
+        return super.onCharacteristicWrite(gatt, characteristic, status);
+    }
+
+    @Override
+    public boolean onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
+        switch (characteristic.getUuid().toString()) {
+            case "00002a26-0000-1000-8000-00805f9b34fb": {
+                String firmwareVersion = characteristic.getStringValue(0);
+                gbDevice.setFirmwareVersion(firmwareVersion);
+
+                Matcher matcher = Pattern
+                        .compile("(?<=[A-Z]{2}[0-9]\\.[0-9]\\.)[0-9]+\\.[0-9]+")
+                        .matcher(firmwareVersion);
+                if(matcher.find()){
+                    gbDevice.setFirmwareVersion2(matcher.group());
+                }
+
+                this.watchAdapter = new WatchAdapterFactory().createWatchAdapter(firmwareVersion, this);
+                this.watchAdapter.initialize();
+                showNotificationsByAllActive(false);
+                break;
+            }
+            case "00002a24-0000-1000-8000-00805f9b34fb": {
+                String modelNumber = characteristic.getStringValue(0);
+                gbDevice.setModel(modelNumber);
+                gbDevice.setName(watchAdapter.getModelName());
+                try {
+                    gbDevice.addDeviceInfo(new GenericItem(ITEM_EXTENDED_VIBRATION_SUPPORT, String.valueOf(watchAdapter.supportsExtendedVibration())));
+                    gbDevice.addDeviceInfo(new GenericItem(ITEM_HAS_ACTIVITY_HAND, String.valueOf(watchAdapter.supportsActivityHand())));
+                } catch (UnsupportedOperationException e) {
+                    GB.log("error", GB.ERROR, e);
+                    gbDevice.addDeviceInfo(new GenericItem(ITEM_EXTENDED_VIBRATION_SUPPORT, "false"));
+                }
+                break;
+            }
+            case "00002a19-0000-1000-8000-00805f9b34fb": {
+                short level = characteristic.getValue()[0];
+                gbDevice.setBatteryLevel(level);
+
+                GBDeviceEventBatteryInfo batteryInfo = new GBDeviceEventBatteryInfo();
+                batteryInfo.level = gbDevice.getBatteryLevel();
+                batteryInfo.state = BatteryState.BATTERY_NORMAL;
+                handleGBDeviceEvent(batteryInfo);
+                break;
+
+            }
+        }
+
+        return true;
+    }
+
+    @Override
+    public boolean onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic
+            characteristic) {
+        if(watchAdapter == null) return super.onCharacteristicChanged(gatt, characteristic);
+        return watchAdapter.onCharacteristicChanged(gatt, characteristic);
+    }
+
+    @Override
+    public void onSetCannedMessages(CannedMessagesSpec cannedMessagesSpec) {
+        if(this.watchAdapter instanceof FossilHRWatchAdapter){
+            ((FossilHRWatchAdapter) watchAdapter).setQuickRepliesConfiguration();
+        }
+    }
+
+    @Override
+    public void onAppInfoReq() {
+        if(this.watchAdapter instanceof FossilHRWatchAdapter){
+            ((FossilHRWatchAdapter) watchAdapter).listApplications();
+        }
+    }
+
+    @Override
+    public void onAppStart(UUID uuid, boolean start) {
+        if(this.watchAdapter instanceof FossilHRWatchAdapter) {
+            String appName = ((FossilHRWatchAdapter) watchAdapter).getInstalledAppNameFromUUID(uuid);
+            if ((appName != null) && (appName.endsWith("App"))) {
+                ((FossilHRWatchAdapter) watchAdapter).startAppOnWatch(appName);
+            } else {
+                ((FossilHRWatchAdapter) watchAdapter).activateWatchface(appName);
+            }
+        }
+    }
+
+    @Override
+    public void onAppDownload(UUID uuid) {
+        ((FossilHRWatchAdapter) watchAdapter).downloadAppToCache(uuid);
+    }
+
+    @Override
+    public void onAppDelete(UUID uuid) {
+        if(this.watchAdapter instanceof FossilHRWatchAdapter) {
+            String appName = ((FossilHRWatchAdapter) watchAdapter).getInstalledAppNameFromUUID(uuid);
+            if (appName != null) {
+                watchAdapter.uninstallApp(appName);
+            }
+        }
+    }
+
+    @Override
+    public void onSetNavigationInfo(NavigationInfoSpec navigationInfoSpec) {
+        ((FossilHRWatchAdapter) watchAdapter).onSetNavigationInfo(navigationInfoSpec);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/adapter/WatchAdapter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/adapter/WatchAdapter.java
new file mode 100644
index 0000000..249cd35
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/adapter/WatchAdapter.java
@@ -0,0 +1,179 @@
+/*  Copyright (C) 2019-2024 Andreas Shimokawa, Arjan Schrijver, Daniel Dakhno,
+    Hasan Ammar
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter;
+
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.content.Context;
+import android.net.Uri;
+
+import org.json.JSONObject;
+
+import java.util.ArrayList;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.qhybrid.NotificationConfiguration;
+import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
+import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.QHybridSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.file.FileHandle;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.MoveHandsRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.PlayNotificationRequest;
+
+public abstract class WatchAdapter {
+    private QHybridSupport deviceSupport;
+
+    public WatchAdapter(QHybridSupport deviceSupport){
+        this.deviceSupport = deviceSupport;
+    }
+
+    public QHybridSupport getDeviceSupport(){
+        return this.deviceSupport;
+    }
+
+    public Context getContext(){
+        return getDeviceSupport().getContext();
+    }
+
+    public abstract void initialize();
+
+    public abstract void playPairingAnimation();
+    public abstract void playNotification(NotificationConfiguration config);
+    public abstract void setTime();
+    public abstract void overwriteButtons(String buttonConfigJson);
+    public abstract void setActivityHand(double progress);
+    public abstract void setHands(MoveHandsRequest.MovementConfiguration movement);
+    public abstract void saveCalibration();
+    public abstract void vibrate(PlayNotificationRequest.VibrationType vibration);
+    public abstract void vibrateFindMyDevicePattern();
+    public abstract void requestHandsControl();
+    public abstract void releaseHandsControl();
+    public abstract void setStepGoal(int stepGoal);
+    public abstract void setVibrationStrength(short strength);
+    public abstract void syncNotificationSettings();
+    public abstract void onTestNewFunction();
+    public abstract void setTimezoneOffsetMinutes(short offset);
+    public abstract void onInstallApp(Uri uri);
+
+    public abstract boolean supportsFindDevice();
+    public abstract boolean supportsExtendedVibration();
+    public abstract boolean supportsActivityHand();
+
+    public String getModelName() {
+        String modelNumber = getDeviceSupport().getDevice().getModel();
+        switch (modelNumber) {
+            case "HW.0.0":
+                return "Q Commuter";
+            case "HL.0.0":
+                return "Q Activist";
+            case "IV.0.0":
+                return "Hybrid HR";
+            case "DN.1.0":
+                return "Hybrid HR Collider";
+            case "WA.0.0":
+            case "VA.0.0":
+                return "Fossil Gen. 6 Hybrid";
+        }
+        return "unknown Q";
+    }
+
+    public abstract void onFetchActivityData();
+
+    public abstract void onSetAlarms(ArrayList<? extends Alarm> alarms);
+
+    public abstract void onSendConfiguration(String config);
+
+    public abstract boolean onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic);
+    public void onMtuChanged(BluetoothGatt gatt, int mtu, int status){};
+
+
+    public String arrayToString(byte[] bytes) {
+        if (bytes.length == 0) return "";
+        StringBuilder s = new StringBuilder();
+        final String chars = "0123456789ABCDEF";
+        for (byte b : bytes) {
+            s.append(chars.charAt((b >> 4) & 0xF)).append(chars.charAt(b & 0xF)).append(" ");
+        }
+        return s.substring(0, s.length() - 1) + "\n";
+    }
+
+    public void setCommuteMenuMessage(String message, boolean finished) {
+    }
+
+    public void setMusicInfo(MusicSpec musicSpec) {
+    }
+
+    public void setMusicState(MusicStateSpec stateSpec) {
+    }
+
+    public void setWidgetContent(String widgetID, String content, boolean render) {
+    }
+
+    public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
+    }
+
+    public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
+    }
+
+    public void updateWidgets() {
+    }
+
+    public void onSetCallState(CallSpec callSpec) {
+    }
+
+    public void onFindDevice(boolean start) {
+    }
+
+    public void onSendWeather(WeatherSpec weatherSpec) {
+    }
+
+    public void setBackgroundImage(byte[] pixels) {
+    }
+
+    public void onDeleteNotification(int id) {
+    }
+
+    public void downloadFile(byte majorHandle, byte minorHandle, String name, boolean fileIsEncrypted, boolean toCache) {
+    }
+
+    public void uploadFileGenerateHeader(FileHandle handle, String filePath, boolean fileIsEncrypted) {
+    }
+
+    public void uploadFileIncludesHeader(String filePath){
+    }
+
+    public void factoryReset() {
+    }
+
+    public void uninstallApp(String appName) {
+    }
+
+    public void pushConfigJson(String configJson){
+
+    }
+
+    public void handleSetMenuStructure(JSONObject menuStructure) {
+
+    }
+
+    public void dispose() {
+
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/adapter/WatchAdapterFactory.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/adapter/WatchAdapterFactory.java
new file mode 100644
index 0000000..4e5d61a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/adapter/WatchAdapterFactory.java
@@ -0,0 +1,41 @@
+/*  Copyright (C) 2019-2024 Andreas Shimokawa, Daniel Dakhno, Hasan Ammar
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.QHybridSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil.FossilWatchAdapter;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil_hr.FossilHRWatchAdapter;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.misfit.MisfitWatchAdapter;
+
+public final class WatchAdapterFactory {
+    public final WatchAdapter createWatchAdapter(String firmwareVersion, QHybridSupport deviceSupport){
+        char hardwareVersion = firmwareVersion.charAt(2);
+        if(hardwareVersion == '1') return new FossilHRWatchAdapter(deviceSupport);
+        if(firmwareVersion.startsWith("IV0")) return new FossilHRWatchAdapter(deviceSupport);
+        if(firmwareVersion.startsWith("VA")) return new FossilHRWatchAdapter(deviceSupport);
+        if(firmwareVersion.startsWith("WA")) return new FossilHRWatchAdapter(deviceSupport);
+
+        char major = firmwareVersion.charAt(6);
+        switch (major){
+            case '0': return new MisfitWatchAdapter(deviceSupport);
+            case '1': return new MisfitWatchAdapter(deviceSupport);
+            case '2': return new FossilWatchAdapter(deviceSupport);
+        }
+
+        throw new UnsupportedOperationException("Firmware " + firmwareVersion + " not supported");
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/adapter/fossil/FossilWatchAdapter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/adapter/fossil/FossilWatchAdapter.java
new file mode 100644
index 0000000..824d5bd
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/adapter/fossil/FossilWatchAdapter.java
@@ -0,0 +1,911 @@
+/*  Copyright (C) 2019-2024 Andreas Shimokawa, Arjan Schrijver, Carsten
+    Pfeiffer, Daniel Dakhno, Hasan Ammar, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil;
+
+import static nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.QHybridSupport.ITEM_STEP_GOAL;
+import static nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.QHybridSupport.ITEM_TIMEZONE_OFFSET;
+import static nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.QHybridSupport.ITEM_VIBRATION_STRENGTH;
+import static nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.QHybridSupport.QHYBRID_EVENT_BUTTON_PRESS;
+import static nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.QHybridSupport.QHYBRID_EVENT_MULTI_BUTTON_PRESS;
+
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+import android.widget.Toast;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.text.DateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.TimeZone;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.BuildConfig;
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.qhybrid.HybridHRActivitySampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.qhybrid.NotificationConfiguration;
+import nodomain.freeyourgadget.gadgetbridge.devices.qhybrid.PackageConfigHelper;
+import nodomain.freeyourgadget.gadgetbridge.entities.HybridHRActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
+import nodomain.freeyourgadget.gadgetbridge.model.GenericItem;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.QHybridSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.WatchAdapter;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.buttonconfig.ConfigFileBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.buttonconfig.ConfigPayload;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.file.FileHandle;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.parser.ActivityEntry;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.parser.ActivityFileParser;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.Request;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.FossilRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.RequestMtuRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.SetDeviceStateRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.alarm.AlarmsSetRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.configuration.ConfigurationPutRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.device_info.DeviceInfo;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.device_info.DeviceSecurityVersionInfo;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.device_info.GetDeviceInfoRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.device_info.SupportedFileVersionsInfo;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.file.FileDeleteRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.file.FileLookupAndGetRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.file.FilePutRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.file.FirmwareFilePutRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.notification.NotificationFilterPutRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.notification.PlayTextNotificationRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.AnimationRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.MoveHandsRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.ReleaseHandsControlRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.RequestHandControlRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.SaveCalibrationRequest;
+import nodomain.freeyourgadget.gadgetbridge.util.AlarmUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+import nodomain.freeyourgadget.gadgetbridge.util.UriHelper;
+
+public class FossilWatchAdapter extends WatchAdapter {
+    private ArrayList<Request> requestQueue = new ArrayList<>();
+
+    protected FossilRequest fossilRequest;
+
+    private int MTU = 23;
+
+    private final String ITEM_MTU = "MTU";
+    static public final String ITEM_BUTTONS = "BUTTONS";
+
+    private final String CONFIG_ITEM_STEP_GOAL = "step_goal";
+    private final String CONFIG_ITEM_VIBRATION_STRENGTH = "vibration_strength";
+    private final String CONFIG_ITEM_TIMEZONE_OFFSET = "timezone_offset";
+    public final String CONFIG_ITEM_BUTTONS = "buttons";
+
+    private int lastButtonIndex = -1;
+
+    protected final Logger LOG = LoggerFactory.getLogger(getClass().getSimpleName());
+
+    SupportedFileVersionsInfo supportedFileVersions;
+
+    public FossilWatchAdapter(QHybridSupport deviceSupport) {
+        super(deviceSupport);
+    }
+
+    private final int REQUEST_TIMEOUT = 60 * 1000;
+
+    private Looper timeoutLooper = null;
+    private Handler timeoutHandler;
+    protected Thread timeoutThread = new Thread(new Runnable() {
+        @Override
+        public void run() {
+            Looper.prepare();
+            timeoutLooper = Looper.myLooper();
+            timeoutHandler = new Handler(timeoutLooper);
+            Looper.loop();
+        }
+    });
+
+    private Runnable requestTimeoutRunnable = new Runnable() {
+        @Override
+        public void run() {
+            String requestName = "unknown";
+            if(fossilRequest != null){
+                requestName = fossilRequest.getName();
+            }
+            log(String.format("Request %s timed out, queing next request", requestName));
+            fossilRequest = null;
+            queueNextRequest();
+        }
+    };
+
+    @Override
+    public void initialize() {
+        timeoutThread.start();
+
+        playPairingAnimation();
+
+        queueWrite(new RequestMtuRequest(512), false);
+
+        getDeviceInfos();
+    }
+
+    @Override
+    public void dispose() {
+        if (timeoutLooper != null) {
+            timeoutHandler.removeCallbacks(requestTimeoutRunnable);
+            timeoutLooper.quit();
+        }
+
+        super.dispose();
+    }
+
+    private void restartRequestTimeout(){
+        if(timeoutLooper == null){
+            return;
+        }
+        stopRequestTimeout();
+        log("restarting request timeout");
+        timeoutHandler.postDelayed(
+                requestTimeoutRunnable,
+                REQUEST_TIMEOUT
+        );
+    }
+
+    private void stopRequestTimeout(){
+        if(timeoutLooper == null){
+            return;
+        }
+        timeoutHandler.removeCallbacks(requestTimeoutRunnable);
+        log("stopped request timeout");
+    }
+
+    public short getSupportedFileVersion(FileHandle handle) {
+        return this.supportedFileVersions.getSupportedFileVersion(handle);
+    }
+
+    protected void initializeWithSupportedFileVersions() {
+        syncConfiguration();
+
+        syncNotificationSettings();
+
+        syncButtonSettings();
+
+        queueWrite(new SetDeviceStateRequest(GBDevice.State.INITIALIZED), false);
+    }
+
+    protected void getDeviceInfos() {
+        queueWrite(new GetDeviceInfoRequest(this) {
+            @Override
+            public void handleDeviceInfos(DeviceInfo[] deviceInfos) {
+                for (DeviceInfo info : deviceInfos) {
+                    if (info instanceof SupportedFileVersionsInfo) {
+                        FossilWatchAdapter.this.supportedFileVersions = (SupportedFileVersionsInfo) info;
+                    } else if (info instanceof DeviceSecurityVersionInfo) {
+                        getDeviceSupport().getDevice().addDeviceInfo(new GenericItem("DEVICE_SECURITY_VERSION", info.toString()));
+                    }
+                }
+                initializeWithSupportedFileVersions();
+            }
+        });
+    }
+
+    private void syncButtonSettings() {
+        String buttonConfig = getDeviceSpecificPreferences().getString(CONFIG_ITEM_BUTTONS, null);
+        getDeviceSupport().getDevice().addDeviceInfo(new GenericItem(ITEM_BUTTONS, buttonConfig));
+        overwriteButtons(buttonConfig);
+    }
+
+    @Override
+    public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
+        if (status != BluetoothGatt.GATT_SUCCESS) {
+            if (characteristic.getUuid().toString().equals("3dda0005-957f-7d4a-34a6-74696673696d")) {
+                GB.log("authentication failed", GB.ERROR, null);
+                setDeviceState(GBDevice.State.AUTHENTICATION_REQUIRED);
+                requestQueue.clear();
+            }
+            log("characteristic write failed: " + status);
+            if (BuildConfig.DEBUG)
+                GB.toast(fossilRequest.getName() + " characteristic write failed: " + status, Toast.LENGTH_SHORT, GB.ERROR);
+            fossilRequest = null;
+
+            queueNextRequest();
+        }
+    }
+
+    @Override
+    public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
+        log("status " + status + " newState: " + newState);
+        if (newState != BluetoothGatt.STATE_CONNECTED) {
+            log("status " + newState + "  clearing queue...");
+            requestQueue.clear();
+            fossilRequest = null;
+        }
+    }
+
+    protected SharedPreferences getDeviceSpecificPreferences() {
+        return GBApplication.getDeviceSpecificSharedPrefs(
+                getDeviceSupport().getDevice().getAddress()
+        );
+    }
+
+    private void syncConfiguration() {
+        SharedPreferences preferences = getDeviceSpecificPreferences();
+
+        int stepGoal = preferences.getInt(CONFIG_ITEM_STEP_GOAL, 1000000);
+        byte vibrationStrength = (byte) preferences.getInt(CONFIG_ITEM_VIBRATION_STRENGTH, 100);
+        int timezoneOffset = preferences.getInt(CONFIG_ITEM_TIMEZONE_OFFSET, 0);
+
+        GBDevice device = getDeviceSupport().getDevice();
+
+        device.addDeviceInfo(new GenericItem(ITEM_STEP_GOAL, String.valueOf(stepGoal)));
+        device.addDeviceInfo(new GenericItem(ITEM_VIBRATION_STRENGTH, String.valueOf(vibrationStrength)));
+        device.addDeviceInfo(new GenericItem(ITEM_TIMEZONE_OFFSET, String.valueOf(timezoneOffset)));
+
+        queueWrite(new ConfigurationPutRequest(new ConfigurationPutRequest.ConfigItem[]{
+                new ConfigurationPutRequest.DailyStepGoalConfigItem(stepGoal),
+                new ConfigurationPutRequest.VibrationStrengthConfigItem(vibrationStrength),
+                new ConfigurationPutRequest.TimezoneOffsetConfigItem((short) timezoneOffset)
+        }, this));
+
+        setTime();
+    }
+
+    public int getMTU() {
+        if (this.MTU < 0) throw new RuntimeException("MTU not configured");
+
+        return this.MTU;
+    }
+
+    @Override
+    public void playPairingAnimation() {
+        queueWrite(new AnimationRequest(), false);
+    }
+
+    @Override
+    public void playNotification(NotificationConfiguration config) {
+        if (config.getPackageName() == null) {
+            log("package name in notification not set");
+            return;
+        }
+        queueWrite(new PlayTextNotificationRequest(config.getPackageName(), this), false);
+    }
+
+    @Override
+    public void setTime() {
+        queueWrite(
+                new ConfigurationPutRequest(generateTimeConfigItemNow(), this), false
+        );
+    }
+
+    protected ConfigurationPutRequest.TimeConfigItem generateTimeConfigItemNow() {
+        long millis = System.currentTimeMillis();
+        TimeZone zone = new GregorianCalendar().getTimeZone();
+
+        return new ConfigurationPutRequest.TimeConfigItem(
+                (int) (millis / 1000 + getDeviceSupport().getTimeOffset() * 60),
+                (short) (millis % 1000),
+                (short) (zone.getOffset(millis) / 60000)
+        );
+    }
+
+    @Override
+    public void overwriteButtons(String jsonConfigString) {
+        try {
+            if (jsonConfigString == null) return;
+            getDeviceSpecificPreferences()
+                    .edit()
+                    .putString(CONFIG_ITEM_BUTTONS, jsonConfigString)
+                    .apply();
+            JSONArray buttonConfigJson = new JSONArray(jsonConfigString);
+            // JSONArray buttonConfigJson = new JSONArray(getDeviceSupport().getDevice().getDeviceInfo(ITEM_BUTTONS).getDetails());
+
+            ConfigPayload[] payloads = new ConfigPayload[buttonConfigJson.length()];
+
+            for (int i = 0; i < buttonConfigJson.length(); i++) {
+                try {
+                    payloads[i] = ConfigPayload.valueOf(buttonConfigJson.getString(i));
+                } catch (IllegalArgumentException e) {
+                    payloads[i] = ConfigPayload.FORWARD_TO_PHONE;
+                }
+            }
+
+            ConfigFileBuilder builder = new ConfigFileBuilder(payloads);
+
+            FilePutRequest fileUploadRequets = new FilePutRequest(FileHandle.SETTINGS_BUTTONS, builder.build(true), this) {
+                @Override
+                public void onFilePut(boolean success) {
+                    if (success)
+                        GB.toast("successfully overwritten button settings", Toast.LENGTH_SHORT, GB.INFO);
+                    else GB.toast("error overwriting button settings", Toast.LENGTH_SHORT, GB.INFO);
+                }
+            };
+            queueWrite(fileUploadRequets);
+        } catch (JSONException e) {
+            GB.log("error", GB.ERROR, e);
+        }
+    }
+
+    @Override
+    public void setActivityHand(double progress) {
+        queueWrite(new ConfigurationPutRequest(
+                new ConfigurationPutRequest.CurrentStepCountConfigItem(Math.min(999999, (int) (1000000 * progress))),
+                this
+        ), false);
+    }
+
+    @Override
+    public void setHands(MoveHandsRequest.MovementConfiguration movement) {
+        queueWrite(new MoveHandsRequest(movement), false);
+    }
+
+    @Override
+    public void saveCalibration() {
+        queueWrite(new SaveCalibrationRequest());
+    }
+
+    public void vibrate(nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.PlayNotificationRequest.VibrationType vibration) {
+        // queueWrite(new nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.PlayNotificationRequest(vibration, -1, -1));
+    }
+
+    @Override
+    public void vibrateFindMyDevicePattern() {
+
+    }
+
+
+    @Override
+    public void requestHandsControl() {
+        queueWrite(new RequestHandControlRequest(), false);
+    }
+
+    @Override
+    public void releaseHandsControl() {
+        queueWrite(new ReleaseHandsControlRequest(), false);
+    }
+
+    @Override
+    public void setStepGoal(int stepGoal) {
+        getDeviceSpecificPreferences()
+                .edit()
+                .putInt(CONFIG_ITEM_STEP_GOAL, stepGoal)
+                .apply();
+
+        queueWrite(new ConfigurationPutRequest(new ConfigurationPutRequest.DailyStepGoalConfigItem(stepGoal), this) {
+            @Override
+            public void onFilePut(boolean success) {
+                if (success)
+                    GB.toast("successfully updated step goal", Toast.LENGTH_SHORT, GB.INFO);
+                else GB.toast("error updating step goal", Toast.LENGTH_SHORT, GB.INFO);
+            }
+        }, false);
+    }
+
+    @Override
+    public void setVibrationStrength(short strength) {
+        getDeviceSpecificPreferences()
+                .edit()
+                .putInt(CONFIG_ITEM_VIBRATION_STRENGTH, (byte) strength)
+                .apply();
+
+        ConfigurationPutRequest.ConfigItem vibrationItem = new ConfigurationPutRequest.VibrationStrengthConfigItem((byte) strength);
+
+
+        queueWrite(
+                new ConfigurationPutRequest(new ConfigurationPutRequest.ConfigItem[]{vibrationItem}, this) {
+                    @Override
+                    public void onFilePut(boolean success) {
+                        if (success)
+                            GB.toast("successfully updated vibration strength", Toast.LENGTH_SHORT, GB.INFO);
+                        else
+                            GB.toast("error updating vibration strength", Toast.LENGTH_SHORT, GB.INFO);
+                    }
+                }, false
+        );
+        // queueWrite(new FileVerifyRequest((short) 0x0800));
+    }
+
+    @Override
+    public void syncNotificationSettings() {
+        log("syncing notification settings...");
+        try {
+            PackageConfigHelper helper = new PackageConfigHelper(getContext());
+            final ArrayList<NotificationConfiguration> configurations = helper.getNotificationConfigurations();
+            if (configurations.size() == 1) configurations.add(configurations.get(0));
+            queueWrite(new NotificationFilterPutRequest(configurations, FossilWatchAdapter.this) {
+                @Override
+                public void onFilePut(boolean success) {
+                    super.onFilePut(success);
+
+                    if (!success) {
+                        GB.toast("error writing notification settings", Toast.LENGTH_SHORT, GB.ERROR);
+
+                        getDeviceSupport().getDevice().setState(GBDevice.State.NOT_CONNECTED);
+                        getDeviceSupport().getDevice().sendDeviceUpdateIntent(getContext());
+                    }
+
+                    getDeviceSupport().getDevice().setState(GBDevice.State.INITIALIZED);
+                    getDeviceSupport().getDevice().sendDeviceUpdateIntent(getContext());
+                }
+            }, false);
+        } catch (Exception e) {
+            GB.log("error", GB.ERROR, e);
+        }
+    }
+
+    @Override
+    public void onTestNewFunction() {
+        queueWrite(new FilePutRequest(
+                FileHandle.HAND_ACTIONS,
+                new byte[]{
+                        (byte) 0x01, (byte) 0x00, (byte) 0x08, (byte) 0x01, (byte) 0x01, (byte) 0x24, (byte) 0x00, (byte) 0x85, (byte) 0x01, (byte) 0x30, (byte) 0x52, (byte) 0xFF, (byte) 0x26, (byte) 0x00, (byte) 0x03, (byte) 0x00, (byte) 0x09, (byte) 0x04, (byte) 0x01, (byte) 0x03, (byte) 0xA0, (byte) 0x00, (byte) 0x00, (byte) 0xA0, (byte) 0x00, (byte) 0x00, (byte) 0x08, (byte) 0x01, (byte) 0x05, (byte) 0x00, (byte) 0x93, (byte) 0x00, (byte) 0x02, (byte) 0x09, (byte) 0x04, (byte) 0x01, (byte) 0x03, (byte) 0x00, (byte) 0x24, (byte) 0x00, (byte) 0x00, (byte) 0x24, (byte) 0x00, (byte) 0x08, (byte) 0x01, (byte) 0x50, (byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0x1F, (byte) 0xBE, (byte) 0xB4, (byte) 0x1B
+                },
+                this)
+        );
+    }
+
+    @Override
+    public void setTimezoneOffsetMinutes(short offset) {
+        getDeviceSpecificPreferences()
+                .edit()
+                .putInt(CONFIG_ITEM_TIMEZONE_OFFSET, offset)
+                .apply();
+
+        queueWrite(new ConfigurationPutRequest(new ConfigurationPutRequest.TimezoneOffsetConfigItem(offset), this) {
+            @Override
+            public void onFilePut(boolean success) {
+                super.onFilePut(success);
+
+                if (success) GB.toast("successfully updated timezone", Toast.LENGTH_SHORT, GB.INFO);
+                else GB.toast("error updating timezone", Toast.LENGTH_SHORT, GB.ERROR);
+            }
+        });
+    }
+
+    @Override
+    public void onInstallApp(Uri uri) {
+        UriHelper uriHelper = null;
+        try {
+            uriHelper = UriHelper.get(uri, getContext());
+        } catch (IOException e) {
+            GB.toast(getContext(), "Could not open firmare: " + e.getMessage(), Toast.LENGTH_LONG, GB.ERROR, e);
+        }
+        if (uriHelper != null) {
+            try (InputStream in = new BufferedInputStream(uriHelper.openInputStream())) {
+                byte[] firmwareBytes = FileUtils.readAll(in, 1024 * 2024); // 2MB
+                queueWrite(new FirmwareFilePutRequest(firmwareBytes, this));
+            } catch (Exception e) {
+                GB.toast(getContext(), "Firmware cannot be installed: " + e.getMessage(), Toast.LENGTH_LONG, GB.ERROR, e);
+            }
+        }
+    }
+
+    @Override
+    public boolean supportsFindDevice() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsExtendedVibration() {
+        String modelNumber = getDeviceSupport().getDevice().getModel();
+        switch (modelNumber) {
+            case "HW.0.0":
+            case "IV.0.0":
+            case "DN.1.0":
+            case "WA.0.0":
+            case "VA.0.0":
+                return true;
+            case "HL.0.0":
+                return false;
+        }
+        throw new UnsupportedOperationException("model " + modelNumber + " not supported");
+    }
+
+    @Override
+    public boolean supportsActivityHand() {
+        String modelNumber = getDeviceSupport().getDevice().getModel();
+        switch (modelNumber) {
+            case "HW.0.0":
+                return true;
+            case "HL.0.0":
+            case "IV.0.0":
+            case "DN.1.0":
+            case "WA.0.0":
+            case "VA.0.0":
+                return false;
+        }
+        throw new UnsupportedOperationException("Model " + modelNumber + " not supported");
+    }
+
+    @Override
+    public void onFetchActivityData() {
+        queueWrite(new FileLookupAndGetRequest(FileHandle.ACTIVITY_FILE, this) {
+            @Override
+            public void handleFileData(byte[] fileData) {
+                try (DBHandler dbHandler = GBApplication.acquireDB()) {
+                    ActivityFileParser parser = new ActivityFileParser();
+                    ArrayList<ActivityEntry> entries = parser.parseFile(fileData).getKey();
+                    HybridHRActivitySampleProvider provider = new HybridHRActivitySampleProvider(getDeviceSupport().getDevice(), dbHandler.getDaoSession());
+
+                    HybridHRActivitySample[] samples = new HybridHRActivitySample[entries.size()];
+
+                    Long userId = DBHelper.getUser(dbHandler.getDaoSession()).getId();
+                    Long deviceId = DBHelper.getDevice(getDeviceSupport().getDevice(), dbHandler.getDaoSession()).getId();
+                    for (int i = 0; i < entries.size(); i++) {
+                        samples[i] = entries.get(i).toDAOActivitySample(userId, deviceId);
+                    }
+
+                    provider.addGBActivitySamples(samples);
+
+                    queueWrite(new FileDeleteRequest(getHandle()));
+                    GB.updateTransferNotification(null, "", false, 100, getContext());
+                    GB.signalActivityDataFinish(getDeviceSupport().getDevice());
+                    LOG.debug("Synchronized activity data");
+                } catch (Exception ex) {
+                    GB.toast(getContext(), "Error saving steps data: " + ex.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
+                    LOG.error("Error saving steps data: ", ex);
+                    GB.updateTransferNotification(null, "Data transfer failed", false, 0, getContext());
+                }
+                getDeviceSupport().getDevice().unsetBusyTask();
+                getDeviceSupport().getDevice().sendDeviceUpdateIntent(getContext());
+            }
+
+            @Override
+            public void handleFileLookupError(FILE_LOOKUP_ERROR error) {
+                if(error == FILE_LOOKUP_ERROR.FILE_EMPTY){
+                    LOG.debug("No activity data to sync");
+                }
+                getDeviceSupport().getDevice().unsetBusyTask();
+                GB.updateTransferNotification(null, "", false, 100, getContext());
+                getDeviceSupport().getDevice().sendDeviceUpdateIntent(getContext());
+            }
+        });
+    }
+
+    @Override
+    public void onSetAlarms(ArrayList<? extends Alarm> alarms) {
+
+        // handle one-shot alarm from the widget:
+        // this device doesn't have concept of on-off alarm, so use the last slot for this and store
+        // this alarm in the database so the user knows what is going on and can disable it
+
+        if (alarms.toArray().length == 1 && alarms.get(0).getRepetition() == 0) { //single shot?
+            Alarm oneshot = alarms.get(0);
+            alarms = (ArrayList<? extends Alarm>) AlarmUtils.mergeOneshotToDeviceAlarms(getDeviceSupport().getDevice(), (nodomain.freeyourgadget.gadgetbridge.entities.Alarm) oneshot, 5);
+        }
+
+        ArrayList<nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.alarm.Alarm> activeAlarms = new ArrayList<>();
+        for (Alarm alarm : alarms) {
+            if (!alarm.getEnabled() || alarm.getUnused()) continue;
+            if (alarm.getRepetition() == 0) {
+                activeAlarms.add(new nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.alarm.Alarm(
+                        (byte) alarm.getMinute(),
+                        (byte) alarm.getHour(),
+                        alarm.getTitle(),
+                        alarm.getDescription()
+                ));
+                continue;
+            }
+            int repitition = alarm.getRepetition();
+            repitition = (repitition << 1) | ((repitition >> 6) & 1);
+            activeAlarms.add(new nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.alarm.Alarm(
+                    (byte) alarm.getMinute(),
+                    (byte) alarm.getHour(),
+                    (byte) repitition,
+                    alarm.getTitle(),
+                    alarm.getDescription()
+            ));
+        }
+        queueWrite(new AlarmsSetRequest(activeAlarms.toArray(new nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.alarm.Alarm[0]), this) {
+            @Override
+            public void onFilePut(boolean success) {
+                super.onFilePut(success);
+                if (success) GB.toast(getContext().getString(R.string.user_feedback_miband_set_alarms_ok), Toast.LENGTH_SHORT, GB.INFO);
+                else GB.toast(getContext().getString(R.string.user_feedback_miband_set_alarms_failed), Toast.LENGTH_SHORT, GB.INFO);
+            }
+        });
+    }
+
+    @Override
+    public void onSendConfiguration(String config) {
+
+    }
+
+    @Override
+    public boolean onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
+        switch (characteristic.getUuid().toString()) {
+            case "3dda0006-957f-7d4a-34a6-74696673696d": {
+                handleBackgroundCharacteristic(characteristic);
+                break;
+            }
+            case "00002a37-0000-1000-8000-00805f9b34fb": {
+                handleHeartRateCharacteristic(characteristic);
+                break;
+            }
+            case "3dda0002-957f-7d4a-34a6-74696673696d":
+            case "3dda0004-957f-7d4a-34a6-74696673696d":
+            case "3dda0005-957f-7d4a-34a6-74696673696d":
+            case "3dda0003-957f-7d4a-34a6-74696673696d": {
+                if (fossilRequest != null) {
+                    boolean requestFinished;
+                    try {
+                        if (characteristic.getUuid().toString().equals("3dda0003-957f-7d4a-34a6-74696673696d")) {
+                            byte requestType = (byte) (characteristic.getValue()[0] & 0x0F);
+
+                            if (requestType != 0x0A && requestType != fossilRequest.getType()) {
+                                // throw new RuntimeException("Answer type " + requestType + " does not match current request " + fossilRequest.getType());
+                            }
+                        }
+
+                        fossilRequest.handleResponse(characteristic);
+                        requestFinished = fossilRequest.isFinished();
+                    } catch (RuntimeException e) {
+                        if (characteristic.getUuid().toString().equals("3dda0005-957f-7d4a-34a6-74696673696d")) {
+                            GB.log("authentication failed", GB.ERROR, e);
+                            // setDeviceState(GBDevice.State.AUTHENTICATION_REQUIRED);
+                        }else {
+                            GB.log("error", GB.ERROR, e);
+                            if (BuildConfig.DEBUG)
+                                GB.toast(fossilRequest.getName() + " failed", Toast.LENGTH_SHORT, GB.ERROR);
+                        }
+
+                        requestFinished = true;
+                    }
+
+                    if (requestFinished) {
+                        log(fossilRequest.getName() + " finished");
+                        fossilRequest = null;
+                        stopRequestTimeout();
+                    } else {
+                        return true;
+                    }
+                }
+                queueNextRequest();
+            }
+        }
+        return true;
+    }
+
+    public void handleHeartRateCharacteristic(BluetoothGattCharacteristic characteristic) {
+    }
+
+    @Override
+    public void onFindDevice(boolean start) {
+        try {
+            if (!this.supportsExtendedVibration()) {
+                if (start) {
+                    new TransactionBuilder("vibrate find")
+                            .write(
+                                    getDeviceSupport().getCharacteristic(UUID.fromString("3dda0005-957f-7d4a-34a6-74696673696d")),
+                                    new byte[]{(byte) 0x01, (byte) 0x04, (byte) 0x30, (byte) 0x75, (byte) 0x00, (byte) 0x00}
+                            )
+                            .queue(getDeviceSupport().getQueue());
+                } else {
+                    new TransactionBuilder("vibrate find")
+                            .write(
+                                    getDeviceSupport().getCharacteristic(UUID.fromString("3dda0005-957f-7d4a-34a6-74696673696d")),
+                                    new byte[]{(byte) 0x02, (byte) 0x05, (byte) 0x04}
+                            )
+                            .queue(getDeviceSupport().getQueue());
+                }
+            }
+        } catch (UnsupportedOperationException e) {
+            GB.log("error", GB.ERROR, e);
+        }
+
+        if (start && getDeviceSupport().searchDevice) return;
+
+        getDeviceSupport().searchDevice = start;
+
+        if (start) {
+            new Thread(new Runnable() {
+                @Override
+                public void run() {
+                    int i = 0;
+                    while (getDeviceSupport().searchDevice) {
+                        vibrateFindMyDevicePattern();
+                        try {
+                            Thread.sleep(2500);
+                        } catch (InterruptedException e) {
+                            GB.log("error", GB.ERROR, e);
+                        }
+                    }
+                }
+            }).start();
+        }
+    }
+
+    protected void handleBackgroundCharacteristic(BluetoothGattCharacteristic characteristic) {
+        byte[] value = characteristic.getValue();
+        switch (value[1]) {
+            case 2: {
+                byte syncId = value[2];
+                getDeviceSupport().getDevice().addDeviceInfo(new GenericItem(QHybridSupport.ITEM_LAST_HEARTBEAT, DateFormat.getTimeInstance().format(new Date())));
+                break;
+            }
+            case 8: {
+                if (value.length != 12) {
+                    throw new RuntimeException("wrong button message");
+                }
+                int index = value[2] & 0xFF;
+                int button = value[9] >> 4 & 0xFF;
+
+                if (index != this.lastButtonIndex) {
+                    lastButtonIndex = index;
+                    log("Button press on button " + button);
+
+                    Intent i = new Intent(QHYBRID_EVENT_BUTTON_PRESS);
+                    i.setPackage(BuildConfig.APPLICATION_ID);
+                    i.putExtra("BUTTON", button);
+                    getContext().sendBroadcast(i);
+                }
+                break;
+            }
+
+            case 5: {
+                if (value.length != 4) {
+                    throw new RuntimeException("wrong button message");
+                }
+                int action = value[3];
+
+                String actionString = "SINGLE";
+                if (action == 3) actionString = "DOUBLE";
+                else if (action == 4) actionString = "LONG";
+
+                // lastButtonIndex = index;
+                log(actionString + " button press");
+
+                Intent i = new Intent(QHYBRID_EVENT_MULTI_BUTTON_PRESS);
+                i.putExtra("ACTION", actionString);
+                getContext().sendBroadcast(i);
+                break;
+            }
+        }
+    }
+
+
+    @Override
+    public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
+        super.onMtuChanged(gatt, mtu, status);
+
+        if (this.MTU == mtu) {
+            log("MTU changed, same value tho");
+            return;
+        }
+
+        log("MTU changed: " + mtu);
+
+        this.MTU = mtu;
+
+        getDeviceSupport().getDevice().addDeviceInfo(new GenericItem(ITEM_MTU, String.valueOf(mtu)));
+        getDeviceSupport().getDevice().sendDeviceUpdateIntent(getContext());
+
+        ((RequestMtuRequest) fossilRequest).setFinished(true);
+        queueNextRequest();
+    }
+
+    public void queueWrite(RequestMtuRequest request, boolean priorise) {
+        log("is connected: " + getDeviceSupport().isConnected());
+        if (!getDeviceSupport().isConnected()) {
+            log("dropping requetst " + request.getName());
+            return;
+        }
+        new TransactionBuilder("requestMtu")
+                .requestMtu(512)
+                .queue(getDeviceSupport().getQueue());
+
+        this.fossilRequest = request;
+    }
+
+    private void log(String message) {
+        LOG.debug(message);
+    }
+
+    public void queueWrite(SetDeviceStateRequest request, boolean priorise) {
+        if (fossilRequest != null && !fossilRequest.isFinished()) {
+            log("queing request: " + request.getName());
+            if (priorise) {
+                requestQueue.add(0, request);
+            } else {
+                requestQueue.add(request);
+            }
+            return;
+        }
+        log("setting device state: " + request.getDeviceState());
+        setDeviceState(request.getDeviceState());
+        queueNextRequest();
+    }
+
+    private void setDeviceState(GBDevice.State state) {
+        getDeviceSupport().getDevice().setState(state);
+        getDeviceSupport().getDevice().sendDeviceUpdateIntent(getContext(), GBDevice.DeviceUpdateSubject.DEVICE_STATE);
+    }
+
+    public void queueWrite(FossilRequest request, boolean priorise) {
+        log("is connected: " + getDeviceSupport().isConnected());
+        if (!getDeviceSupport().isConnected()) {
+            log("dropping requetst " + request.getName());
+            return;
+        }
+        if (fossilRequest != null && !fossilRequest.isFinished()) {
+            log("queing request: " + request.getName());
+            if (priorise) {
+                requestQueue.add(0, request);
+            } else {
+                requestQueue.add(request);
+            }
+            return;
+        }
+        log("executing request: " + request.getName());
+        restartRequestTimeout();
+        this.fossilRequest = request;
+        new TransactionBuilder(request.getClass().getSimpleName()).write(getDeviceSupport().getCharacteristic(request.getRequestUUID()), request.getRequestData()).queue(getDeviceSupport().getQueue());
+
+        if (request.isFinished()) {
+            this.fossilRequest = null;
+            stopRequestTimeout();
+            queueNextRequest();
+        }
+    }
+
+    public void queueWrite(Request request, boolean priorise) {
+        log("is connected: " + getDeviceSupport().isConnected());
+        if (!getDeviceSupport().isConnected()) {
+            log("dropping requetst " + request.getName());
+            return;
+        }
+        restartRequestTimeout();
+        new TransactionBuilder(request.getClass().getSimpleName()).write(getDeviceSupport().getCharacteristic(request.getRequestUUID()), request.getRequestData()).queue(getDeviceSupport().getQueue());
+
+        queueNextRequest();
+    }
+
+    protected void queueWrite(Request request) {
+        log("is connected: " + getDeviceSupport().isConnected());
+        if (!getDeviceSupport().isConnected()) {
+            log("dropping requetst " + request.getName());
+            return;
+        }
+        if (request instanceof SetDeviceStateRequest)
+            queueWrite((SetDeviceStateRequest) request, false);
+        else if (request instanceof RequestMtuRequest)
+            queueWrite((RequestMtuRequest) request, false);
+        else if (request instanceof FossilRequest) queueWrite((FossilRequest) request, false);
+        else queueWrite(request, false);
+    }
+
+    private void queueNextRequest() {
+        try {
+            Request request = requestQueue.remove(0);
+            queueWrite(request);
+        } catch (IndexOutOfBoundsException e) {
+            log("requestsQueue empty");
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/adapter/fossil_hr/FossilHRWatchAdapter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/adapter/fossil_hr/FossilHRWatchAdapter.java
new file mode 100644
index 0000000..46125d7
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/adapter/fossil_hr/FossilHRWatchAdapter.java
@@ -0,0 +1,2160 @@
+/*  Copyright (C) 2019-2024 Andreas Shimokawa, Arjan Schrijver, Carsten
+    Pfeiffer, Daniel Dakhno, Enrico Brambilla, Hasan Ammar, Jos茅 Rebelo, Morten
+    Rieger Hannemose, mvn23, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil_hr;
+
+import static nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.configuration.ConfigurationPutRequest.FitnessConfigItem;
+import static nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.configuration.ConfigurationPutRequest.InactivityWarningItem;
+import static nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.configuration.ConfigurationPutRequest.UnitsConfigItem;
+import static nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.configuration.ConfigurationPutRequest.VibrationStrengthConfigItem;
+import static nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.music.MusicControlRequest.MUSIC_PHONE_REQUEST;
+import static nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.music.MusicControlRequest.MUSIC_WATCH_REQUEST;
+import static nodomain.freeyourgadget.gadgetbridge.util.BitmapUtil.convertDrawableToBitmap;
+import static nodomain.freeyourgadget.gadgetbridge.util.GB.NOTIFICATION_CHANNEL_ID;
+import static nodomain.freeyourgadget.gadgetbridge.util.StringUtils.shortenPackageName;
+
+import android.app.Service;
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.drawable.Drawable;
+import android.location.Location;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+import android.widget.Toast;
+
+import androidx.core.app.NotificationCompat;
+import androidx.core.content.res.ResourcesCompat;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import net.e175.klaus.solarpositioning.DeltaT;
+import net.e175.klaus.solarpositioning.SPA;
+import net.e175.klaus.solarpositioning.SunriseTransitSet;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.BufferOverflowException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.StandardCharsets;
+import java.time.ZonedDateTime;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.SettingsActivity;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventAppInfo;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventCallControl;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventFindPhone;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventMusicControl;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventNotificationControl;
+import nodomain.freeyourgadget.gadgetbridge.devices.qhybrid.CommuteActionsActivity;
+import nodomain.freeyourgadget.gadgetbridge.devices.qhybrid.FossilFileReader;
+import nodomain.freeyourgadget.gadgetbridge.devices.qhybrid.FossilHRInstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.qhybrid.HybridHRActivitySampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.qhybrid.HybridHRSpo2SampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.qhybrid.NotificationHRConfiguration;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.entities.User;
+import nodomain.freeyourgadget.gadgetbridge.entities.HybridHRActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.entities.HybridHRSpo2Sample;
+import nodomain.freeyourgadget.gadgetbridge.externalevents.NotificationListener;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp;
+import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.GenericItem;
+import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.NavigationInfoSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.Weather;
+import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.webview.CurrentPosition;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.QHybridSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil.FossilWatchAdapter;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.file.FileHandle;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.parser.ActivityEntry;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.parser.ActivityFileParser;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.FossilRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.RequestMtuRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.SetDeviceStateRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.file.FileDeleteRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.file.FileGetRawRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.file.FileLookupRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.file.FilePutRawRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.file.FilePutRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.notification.DismissTextNotificationRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.notification.PlayCallNotificationRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.notification.PlayTextNotificationRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.alexa.AlexaMessageSetRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.application.ApplicationInformation;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.application.ApplicationsListRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.async.ConfirmAppStatusRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.authentication.CheckDeviceNeedsConfirmationRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.authentication.CheckDevicePairingRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.authentication.ConfirmOnDeviceRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.authentication.PerformDevicePairingRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.authentication.VerifyPrivateKeyRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.buttons.ButtonConfiguration;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.buttons.ButtonConfigurationPutRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.commute.CommuteConfigPutRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.configuration.ConfigurationGetRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.configuration.ConfigurationPutRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.file.AssetFilePutRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.file.FileEncryptedGetRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.file.FileEncryptedInterface;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.image.AssetImage;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.image.AssetImageFactory;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.image.ImagesSetRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.json.JsonPutRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.menu.SetCommuteMenuMessage;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.music.MusicControlRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.music.MusicInfoSetRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.notification.NotificationFilterPutHRRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.notification.NotificationImage;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.notification.NotificationImagePutRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.quickreply.QuickReplyConfigurationPutRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.quickreply.QuickReplyConfirmationPutRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.theme.SelectedThemePutRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.widget.CustomBackgroundWidgetElement;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.widget.CustomTextWidgetElement;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.widget.CustomWidget;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.widget.CustomWidgetElement;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.widget.Widget;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.widget.WidgetsPutRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.workout.WorkoutRequestHandler;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.FactoryResetRequest;
+import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+import nodomain.freeyourgadget.gadgetbridge.util.NotificationUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.UriHelper;
+import nodomain.freeyourgadget.gadgetbridge.util.Version;
+
+public class FossilHRWatchAdapter extends FossilWatchAdapter {
+    public static final int MESSAGE_WHAT_VOICE_DATA_RECEIVED = 0;
+
+    private byte[] phoneRandomNumber;
+    private byte[] watchRandomNumber;
+
+    private static ArrayList<Widget> widgets = new ArrayList<>();
+
+    private NotificationHRConfiguration[] notificationConfigurations;
+
+    private CallSpec currentCallSpec = null;
+    private MusicSpec currentSpec = null;
+
+    private byte jsonIndex = 0;
+
+    private AssetImage backGroundImage = null;
+
+    public FossilHRWatchAdapter(QHybridSupport deviceSupport) {
+        super(deviceSupport);
+    }
+
+    private boolean saveRawActivityFiles = false;
+    private boolean notifiedAboutMissingNavigationApp = false;
+
+    HashMap<String, Bitmap> appIconCache = new HashMap<>();
+    String lastPostedApp = null;
+
+    List<ApplicationInformation> installedApplications = new ArrayList();
+
+    Messenger voiceMessenger = null;
+
+    private Version cleanFirmwareVersion = null;
+
+    ServiceConnection voiceServiceConnection = new ServiceConnection() {
+        @Override
+        public void onServiceConnected(ComponentName name, IBinder service) {
+            GB.log("attached to voice service", GB.INFO, null);
+            voiceMessenger = new Messenger(service);
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName name) {
+            GB.log("detached from voice service", GB.INFO, null);
+            voiceMessenger = null;
+        }
+    };
+
+    enum CONNECTION_MODE {
+        NOT_INITIALIZED,
+        AUTHENTICATED,
+        NOT_AUTHENTICATED
+    }
+
+    CONNECTION_MODE connectionMode = CONNECTION_MODE.NOT_INITIALIZED;
+
+    @Override
+    public void initialize() {
+        timeoutThread.start();
+
+        saveRawActivityFiles = getDeviceSpecificPreferences().getBoolean("save_raw_activity_files", false);
+
+        queueWrite(new RequestMtuRequest(512));
+
+        listApplications();
+        getDeviceInfos();
+    }
+
+    @Override
+    protected void initializeWithSupportedFileVersions() {
+        if (getDeviceSupport().getDevice().getFirmwareVersion().contains("prod")) {
+            GB.toast("Dummy FW, skipping initialization", Toast.LENGTH_LONG, GB.INFO);
+            queueWrite(new SetDeviceStateRequest(GBDevice.State.INITIALIZED), false);
+            return;
+        }
+
+        queueWrite(new SetDeviceStateRequest(GBDevice.State.AUTHENTICATING));
+
+        negotiateSymmetricKey();
+    }
+
+    public void listApplications() {
+        queueWrite(new ApplicationsListRequest(this){
+            @Override
+            public void handleApplicationsList(List<ApplicationInformation> installedApplications) {
+                ((FossilHRWatchAdapter) getAdapter()).setInstalledApplications(installedApplications);
+                GBDevice device = getAdapter().getDeviceSupport().getDevice();
+                JSONArray array = new JSONArray();
+                for(ApplicationInformation info : installedApplications){
+                    array.put(info.getAppName());
+                }
+                device.addDeviceInfo(new GenericItem("INSTALLED_APPS", array.toString()));
+                device.sendDeviceUpdateIntent(getAdapter().getContext());
+            }
+        });
+    }
+
+    private void initializeAfterAuthentication(boolean authenticated) {
+        queueWrite(new SetDeviceStateRequest(GBDevice.State.INITIALIZING));
+
+        if (!authenticated) {
+            GB.toast(getContext().getString(R.string.fossil_hr_auth_failed), Toast.LENGTH_LONG, GB.ERROR);
+            initializeAfterWatchConfirmation(false);
+            return;
+        }
+        boolean versionSupportsConfirmation = getCleanFWVersion().greaterOrEqualThan(new Version("2.22"));
+        if(!versionSupportsConfirmation){
+            initializeAfterWatchConfirmation(true);
+            return;
+        }
+        boolean shouldAuthenticateOnWatch = getDeviceSpecificPreferences().getBoolean("enable_on_device_confirmation", true);
+        if (!shouldAuthenticateOnWatch) {
+            GB.toast(getContext().getString(R.string.fossil_hr_confirmation_skipped), Toast.LENGTH_SHORT, GB.INFO);
+            initializeAfterWatchConfirmation(false);
+            return;
+        }
+        confirmOnWatch();
+    }
+
+    TimerTask confirmTimeoutRunnable = new TimerTask() {
+        @Override
+        public void run() {
+            if (!(fossilRequest instanceof ConfirmOnDeviceRequest)) {
+                return;
+            }
+            GB.toast(getContext().getString(R.string.fossil_hr_confirmation_timeout), Toast.LENGTH_SHORT, GB.INFO);
+            ((ConfirmOnDeviceRequest) fossilRequest).onResult(false);
+        }
+    };
+
+    private void confirmOnWatch() {
+        queueWrite(new CheckDeviceNeedsConfirmationRequest() {
+            @Override
+            public void onResult(boolean needsConfirmation) {
+                GB.log("needs confirmation: " + needsConfirmation, GB.INFO, null);
+                if (needsConfirmation) {
+                    final Timer timer = new Timer();
+                    GB.toast(getContext().getString(R.string.fossil_hr_confirm_connection), Toast.LENGTH_SHORT, GB.INFO);
+                    queueWrite(new ConfirmOnDeviceRequest() {
+                        @Override
+                        public void onResult(boolean confirmationSuccess) {
+                            isFinished = true;
+                            timer.cancel();
+                            if (confirmationSuccess) {
+                                pairToWatch();
+                            } else {
+                                GB.toast(getContext().getString(R.string.fossil_hr_connection_not_confirmed), Toast.LENGTH_LONG, GB.ERROR);
+                            }
+                            initializeAfterWatchConfirmation(confirmationSuccess);
+                        }
+                    }, true);
+                    timer.schedule(confirmTimeoutRunnable, 30000);
+                } else {
+                    initializeAfterWatchConfirmation(true);
+                }
+            }
+        });
+    }
+
+    private void pairToWatch() {
+        queueWrite(new CheckDevicePairingRequest() {
+            @Override
+            public void onResult(boolean pairingStatus) {
+                GB.log("watch pairing status: " + pairingStatus, GB.INFO, null);
+                if (!pairingStatus) {
+                    queueWrite(new PerformDevicePairingRequest() {
+                        @Override
+                        public void onResult(boolean pairingSuccess) {
+                            isFinished = true;
+                            GB.log("watch pairing result: " + pairingSuccess, GB.INFO, null);
+                            if (pairingSuccess) {
+                                GB.toast(getContext().getString(R.string.fossil_hr_pairing_successful), Toast.LENGTH_LONG, GB.ERROR);
+                            } else {
+                                GB.toast(getContext().getString(R.string.fossil_hr_pairing_failed), Toast.LENGTH_LONG, GB.ERROR);
+                            }
+                        }
+                    }, true);
+                }
+            }
+        });
+    }
+
+    private void respondToAlexa(String message, boolean isResponse){
+        queueWrite(new AlexaMessageSetRequest(message, isResponse, this));
+    }
+
+    private void attachToVoiceService(){
+        String servicePackage = getDeviceSpecificPreferences().getString("voice_service_package", "");
+        String servicePath = getDeviceSpecificPreferences().getString("voice_service_class", "");
+
+        if(servicePackage.isEmpty()){
+            GB.toast("voice service package is not configured", Toast.LENGTH_LONG, GB.ERROR);
+            respondToAlexa("voice service package not configured on phone", true);
+            return;
+        }
+
+        if(servicePath.isEmpty()){
+            respondToAlexa("voice service class not configured on phone", true);
+            GB.toast("voice service class is not configured", Toast.LENGTH_LONG, GB.ERROR);
+            return;
+        }
+
+        ComponentName component = new ComponentName(servicePackage, servicePath);
+
+        // extract to somewhere
+        Intent voiceIntent = new Intent("nodomain.freeyourgadget.gadgetbridge.VOICE_COMMAND");
+        voiceIntent.setComponent(component);
+
+        int flags = 0;
+
+        flags |= Service.BIND_AUTO_CREATE;
+
+        GB.log("binding to voice service...", GB.INFO, null);
+
+        getContext().bindService(
+                voiceIntent,
+                voiceServiceConnection,
+                flags
+        );
+
+        PackageManager pm = getContext().getPackageManager();
+        boolean serviceEnabled = true;
+        try {
+            int enabledState = pm.getComponentEnabledSetting(component);
+
+            if(enabledState != PackageManager.COMPONENT_ENABLED_STATE_ENABLED && enabledState != PackageManager.COMPONENT_ENABLED_STATE_DEFAULT){
+                respondToAlexa("voice service is disabled on phone", true);
+                GB.toast("voice service is disabled", Toast.LENGTH_LONG, GB.ERROR);
+                serviceEnabled = false;
+            }
+        }catch (IllegalArgumentException e){
+            serviceEnabled = false;
+            respondToAlexa("voice service not found on phone", true);
+            GB.toast("voice service not found", Toast.LENGTH_LONG, GB.ERROR);
+        }
+
+        if(!serviceEnabled){
+            detachFromVoiceService();
+        }
+    }
+
+    private void detachFromVoiceService(){
+        getContext().unbindService(voiceServiceConnection);
+    }
+
+    private void handleVoiceStatus(byte status){
+        if(status == 0x00){
+            attachToVoiceService();
+        }else if(status == 0x01){
+            detachFromVoiceService();
+        }
+    }
+
+    private void handleVoiceStatusCharacteristic(BluetoothGattCharacteristic characteristic){
+        byte[] value = characteristic.getValue();
+        handleVoiceStatus(value[0]);
+    }
+
+    private void handleVoiceDataCharacteristic(BluetoothGattCharacteristic characteristic){
+        if(voiceMessenger == null){
+            return;
+        }
+        Message message = Message.obtain(
+                null,
+                MESSAGE_WHAT_VOICE_DATA_RECEIVED
+        );
+        Bundle dataBundle = new Bundle(1);
+        dataBundle.putByteArray("VOICE_DATA", characteristic.getValue());
+        dataBundle.putString("VOICE_ENCODING", "OPUS");
+        message.setData(dataBundle);
+        try {
+            voiceMessenger.send(message);
+        } catch (RemoteException e) {
+            GB.log("error sending voice data to service", GB.ERROR, e);
+            GB.toast("error sending voice data to service", Toast.LENGTH_LONG, GB.ERROR);
+            voiceMessenger = null;
+            detachFromVoiceService();
+            respondToAlexa("error sending voice data to service", true);
+        }
+    }
+
+    @Override
+    public boolean onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
+        switch (characteristic.getUuid().toString()){
+            case "010541ae-efe8-11c0-91c0-105d1a1155f0":
+                handleVoiceStatusCharacteristic(characteristic);
+                return true;
+            case "842d2791-0d20-4ce4-1ada-105d1a1155f0":
+                handleVoiceDataCharacteristic(characteristic);
+                return true;
+        }
+        return super.onCharacteristicChanged(gatt, characteristic);
+    }
+
+    private void initializeAfterWatchConfirmation(boolean authenticated) {
+        setNotificationConfigurations();
+        setQuickRepliesConfiguration();
+
+        if (authenticated) {
+            // setVibrationStrengthFromConfig();
+            setUnitsConfig();
+            syncSettings();
+            setTime();
+        }
+
+        overwriteButtons(null);
+        loadBackground();
+        loadWidgets();
+        // renderWidgets();
+        // dunno if there is any point in doing this at start since when no watch is connected the QHybridSupport will not receive any intents anyway
+
+        updateBuiltinAppsInCache();
+
+        queueWrite(new SetDeviceStateRequest(GBDevice.State.INITIALIZED));
+    }
+
+    private void handleAuthenticationResult(boolean success) {
+        if (this.connectionMode != CONNECTION_MODE.NOT_INITIALIZED) return;
+        this.connectionMode = success ? CONNECTION_MODE.AUTHENTICATED : CONNECTION_MODE.NOT_AUTHENTICATED;
+        this.initializeAfterAuthentication(success);
+    }
+
+    @Override
+    public void uninstallApp(String appName) {
+        for (ApplicationInformation appInfo : this.installedApplications) {
+            if (appInfo.getAppName().equals(appName)) {
+                byte handle = appInfo.getFileHandle();
+                short fullFileHandle = (short) ((FileHandle.APP_CODE.getMajorHandle()) << 8 | handle);
+                queueWrite(new FileDeleteRequest(fullFileHandle));
+                listApplications();
+                break;
+            }
+        }
+    }
+
+    public void activateWatchface(String appName) {
+        queueWrite(new SelectedThemePutRequest(this, appName));
+    }
+
+    public void startAppOnWatch(String appName) {
+        try {
+            JSONObject jsonObject = new JSONObject()
+                    .put("push", new JSONObject()
+                            .put("set", new JSONObject()
+                                    .put("customWatchFace._.config.start_app", appName)
+                            )
+                    );
+            queueWrite(new JsonPutRequest(jsonObject, this));
+        } catch (JSONException e) {
+            LOG.warn("Couldn't start app on watch: " + e.getMessage());
+        }
+    }
+
+    public void downloadAppToCache(UUID uuid) {
+        LOG.info("Going to download app with UUID " + uuid.toString());
+        for (ApplicationInformation appInfo : installedApplications) {
+            if (UUID.nameUUIDFromBytes(appInfo.getAppName().getBytes(StandardCharsets.UTF_8)).equals(uuid)) {
+                LOG.info("Going to download app with name " + appInfo.getAppName() + " and handle " + appInfo.getFileHandle());
+                downloadFile(FileHandle.APP_CODE.getMajorHandle(), appInfo.getFileHandle(), appInfo.getAppName(), false, true);
+            }
+        }
+    }
+
+    private void setVibrationStrengthFromConfig() {
+        Prefs prefs = new Prefs(getDeviceSpecificPreferences());
+        int vibrationStrengh = prefs.getInt(DeviceSettingsPreferenceConst.PREF_VIBRATION_STRENGH_PERCENTAGE, 2);
+        if (vibrationStrengh > 0) {
+            vibrationStrengh = (vibrationStrengh + 1) * 25; // Seems 0,50,75,100 are working...
+        }
+        setVibrationStrength((short) (vibrationStrengh));
+    }
+
+    private void setUnitsConfig() {
+        Prefs prefs = GBApplication.getPrefs();
+        String unit = prefs.getString(SettingsActivity.PREF_MEASUREMENT_SYSTEM, GBApplication.getContext().getString(R.string.p_unit_metric));
+        int value = 8; // dont know what this bit means but it was set for me before tampering
+        if (!unit.equals("metric")) {
+            value |= (4 | 1); // temperature and distance
+        }
+        queueWrite(
+                (FileEncryptedInterface) new ConfigurationPutRequest(new UnitsConfigItem(value), this)
+        );
+
+    }
+
+    @Override
+    public void setVibrationStrength(short strength) {
+        if (connectionMode == CONNECTION_MODE.NOT_AUTHENTICATED) {
+            GB.toast(getContext().getString(R.string.fossil_hr_unavailable_unauthed), Toast.LENGTH_LONG, GB.ERROR);
+            return;
+        }
+
+        queueWrite(
+                (FileEncryptedInterface) new ConfigurationPutRequest(new VibrationStrengthConfigItem((byte) strength), this)
+        );
+    }
+
+    private void setNotificationConfigurations() {
+        // Set default icons
+        ArrayList<NotificationImage> images = new ArrayList<>();
+        images.add(new NotificationImage("icIncomingCall.icon", NotificationImage.getEncodedIconFromDrawable(getContext().getResources().getDrawable(R.drawable.ic_phone_outline)), 24, 24));
+        images.add(new NotificationImage("icMissedCall.icon", NotificationImage.getEncodedIconFromDrawable(getContext().getResources().getDrawable(R.drawable.ic_phone_missed_outline)), 24, 24));
+        images.add(new NotificationImage("icMessage.icon", NotificationImage.getEncodedIconFromDrawable(getContext().getResources().getDrawable(R.drawable.ic_message_outline)), 24, 24));
+        images.add(new NotificationImage("general_white.bin", NotificationImage.getEncodedIconFromDrawable(getContext().getResources().getDrawable(R.drawable.ic_alert_circle_outline)), 24, 24));
+
+        // Set default notification filters
+        ArrayList<NotificationHRConfiguration> notificationFilters = new ArrayList<>();
+        notificationFilters.add(new NotificationHRConfiguration("generic", "general_white.bin"));
+        notificationFilters.add(new NotificationHRConfiguration("call", new byte[]{(byte) 0x80, (byte) 0x00, (byte) 0x59, (byte) 0xB7}, "icIncomingCall.icon"));
+
+        // Add icons and notification filters from cached past notifications
+        Set<Map.Entry<String, Bitmap>> entrySet = this.appIconCache.entrySet();
+        for (Map.Entry<String, Bitmap> entry : entrySet) {
+            String iconName = shortenPackageName(entry.getKey()) + ".icon";
+            images.add(new NotificationImage(iconName, entry.getValue()));
+            notificationFilters.add(new NotificationHRConfiguration(entry.getKey(), iconName));
+        }
+
+        // Send notification icons
+        try {
+            queueWrite(new NotificationImagePutRequest(images.toArray(new NotificationImage[images.size()]), this));
+        } catch (IOException e) {
+            LOG.error("Error while sending notification icons", e);
+        }
+
+        // Send notification filters configuration
+        this.notificationConfigurations = notificationFilters.toArray(new NotificationHRConfiguration[notificationFilters.size()]);
+        queueWrite(new NotificationFilterPutHRRequest(this.notificationConfigurations, this));
+    }
+
+    private String[] getQuickReplies() {
+        ArrayList<String> configuredReplies = new ArrayList<>();
+        Prefs prefs = new Prefs(getDeviceSpecificPreferences());
+        for (int i = 1; i <= 16; i++) {
+            String quickReply = prefs.getString("canned_message_dismisscall_" + i, null);
+            if (quickReply != null) {
+                configuredReplies.add(quickReply);
+            }
+        }
+        return configuredReplies.toArray(new String[0]);
+    }
+
+    public void setQuickRepliesConfiguration() {
+        String[] quickReplies = getQuickReplies();
+        if (quickReplies.length > 0) {
+            NotificationImage quickReplyIcon = new NotificationImage("icMessage.icon", NotificationImage.getEncodedIconFromDrawable(getContext().getResources().getDrawable(R.drawable.ic_message_outline)), 24, 24);
+            queueWrite(new NotificationImagePutRequest(quickReplyIcon, this));
+            queueWrite(new QuickReplyConfigurationPutRequest(quickReplies, this));
+        }
+    }
+
+    private File getBackgroundFile() {
+        return new File(getContext().getExternalFilesDir(null), "hr_background.bin");
+    }
+
+    private void loadBackground() {
+        this.backGroundImage = null;
+        try {
+            FileInputStream fis = new FileInputStream(getBackgroundFile());
+            int count = fis.available();
+            if (count != 14400) {
+                throw new RuntimeException("wrong background file length");
+            }
+            byte[] file = new byte[14400];
+            fis.read(file);
+            fis.close();
+            this.backGroundImage = AssetImageFactory.createAssetImage(file, 0, 0, 0);
+        } catch (FileNotFoundException e) {
+            SharedPreferences preferences = getDeviceSpecificPreferences();
+            if (preferences.getBoolean("force_white_color_scheme", false)) {
+                Bitmap whiteBitmap = Bitmap.createBitmap(239, 239, Bitmap.Config.ARGB_8888);
+                new Canvas(whiteBitmap).drawColor(Color.WHITE);
+
+                try {
+                    this.backGroundImage = AssetImageFactory.createAssetImage(whiteBitmap, true, 0, 1, 0);
+                } catch (IOException e2) {
+                    LOG.error("Backgroundimage error", e2);
+                }
+            }
+        } catch (IOException | RuntimeException e) {
+            GB.log("error opening background file", GB.ERROR, e);
+            GB.toast("error opening background file", Toast.LENGTH_LONG, GB.ERROR);
+        }
+    }
+
+    @Override
+    public void setBackgroundImage(byte[] pixels) {
+        if (pixels == null) {
+            getBackgroundFile().delete();
+            loadBackground(); // recreates the white background in force-white mode, else backgroundImage=null
+        } else {
+            this.backGroundImage = AssetImageFactory.createAssetImage(pixels, 0, 0, 0);
+            try {
+                FileOutputStream fos = new FileOutputStream(getBackgroundFile(), false);
+                fos.write(pixels);
+            } catch (IOException e) {
+                GB.log("error saving background", GB.ERROR, e);
+                GB.toast("error persistent saving background", Toast.LENGTH_LONG, GB.ERROR);
+            }
+        }
+        renderWidgets();
+    }
+
+    private void loadWidgets() {
+        Version firmwareVersion = getCleanFWVersion();
+        if (firmwareVersion != null && firmwareVersion.greaterOrEqualThan(new Version("2.20"))) {
+            return; // this does not work on newer firmware versions
+        }
+        Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(getDeviceSupport().getDevice().getAddress()));
+        boolean forceWhiteBackground = prefs.getBoolean("force_white_color_scheme", false);
+        String fontColor = forceWhiteBackground ? "black" : "default";
+
+        Widget[] oldWidgets = widgets.toArray(new Widget[0]);
+
+        widgets.clear();
+        String widgetJson = GBApplication.getPrefs().getPreferences().getString("FOSSIL_HR_WIDGETS", "{}");
+        String customWidgetJson = GBApplication.getPrefs().getString("QHYBRID_CUSTOM_WIDGETS", "[]");
+
+        try {
+            JSONObject widgetConfig = new JSONObject(widgetJson);
+            JSONArray customWidgets = new JSONArray(customWidgetJson);
+
+            Iterator<String> keyIterator = widgetConfig.keys();
+            HashMap<String, Integer> positionMap = new HashMap<>(4);
+            positionMap.put("top", 0);
+            positionMap.put("right", 90);
+            positionMap.put("bottom", 180);
+            positionMap.put("left", 270);
+
+            while (keyIterator.hasNext()) {
+                String position = keyIterator.next();
+                String identifier = widgetConfig.getString(position);
+                Widget.WidgetType type = Widget.WidgetType.fromJsonIdentifier(identifier);
+
+                Widget widget = null;
+                if (type != null) {
+                    widget = new Widget(type, positionMap.get(position), 63, fontColor);
+                } else {
+                    identifier = identifier.substring(7);
+                    for (int i = 0; i < customWidgets.length(); i++) {
+                        JSONObject customWidget = customWidgets.getJSONObject(i);
+                        if (customWidget.getString("name").equals(identifier)) {
+                            boolean drawCircle = false;
+                            if (customWidget.has("drawCircle"))
+                                drawCircle = customWidget.getBoolean("drawCircle");
+                            CustomWidget newWidget = new CustomWidget(
+                                    customWidget.getString("name"),
+                                    positionMap.get(position),
+                                    63,
+                                    fontColor
+                            );
+                            JSONArray elements = customWidget.getJSONArray("elements");
+
+                            for (int i2 = 0; i2 < elements.length(); i2++) {
+                                JSONObject element = elements.getJSONObject(i2);
+                                if (element.getString("type").equals("text")) {
+                                    newWidget.addElement(new CustomTextWidgetElement(
+                                            element.getString("id"),
+                                            element.getString("value"),
+                                            element.getInt("x"),
+                                            element.getInt("y")
+                                    ));
+                                } else if (element.getString("type").equals("background")) {
+                                    newWidget.addElement(new CustomBackgroundWidgetElement(
+                                            element.getString("id"),
+                                            element.getString("value")
+                                    ));
+                                }
+                            }
+                            widget = newWidget;
+                        }
+                    }
+                }
+
+                if (widget == null) continue;
+                widgets.add(widget);
+            }
+        } catch (JSONException e) {
+            LOG.error("Error while updating widgets", e);
+        }
+
+        for (Widget oldWidget : oldWidgets) {
+            if (!(oldWidget instanceof CustomWidget)) continue;
+            CustomWidget customOldWidget = (CustomWidget) oldWidget;
+            for (CustomWidgetElement oldElement : customOldWidget.getElements()) {
+                for (Widget newWidget : widgets) {
+                    if (newWidget instanceof CustomWidget) {
+                        ((CustomWidget) newWidget).updateElementValue(oldElement.getId(), oldElement.getValue());
+                    }
+                }
+            }
+
+        }
+
+        uploadWidgets();
+    }
+
+    public void setInstalledApplications(List<ApplicationInformation> installedApplications) {
+        this.installedApplications = installedApplications;
+        GBDeviceEventAppInfo appInfoEvent = new GBDeviceEventAppInfo();
+        appInfoEvent.apps = new GBDeviceApp[installedApplications.size()];
+        for (int i = 0; i < installedApplications.size(); i++) {
+            String appName = installedApplications.get(i).getAppName();
+            String appVersion = installedApplications.get(i).getAppVersion();
+            UUID appUUID = UUID.nameUUIDFromBytes(appName.getBytes(StandardCharsets.UTF_8));
+            GBDeviceApp.Type appType;
+            if (installedApplications.get(i).getAppName().endsWith("App")) {
+                appType = GBDeviceApp.Type.APP_GENERIC;
+            } else {
+                appType = GBDeviceApp.Type.WATCHFACE;
+            }
+            appInfoEvent.apps[i] = new GBDeviceApp(appUUID, appName, "(unknown)", appVersion, appType);
+        }
+        getDeviceSupport().evaluateGBDeviceEvent(appInfoEvent);
+    }
+
+    private void uploadWidgets() {
+        ArrayList<Widget> systemWidgets = new ArrayList<>(widgets.size());
+        for (Widget widget : widgets) {
+            if (!(widget instanceof CustomWidget) && !widget.getWidgetType().isCustom())
+                systemWidgets.add(widget);
+        }
+        queueWrite(new WidgetsPutRequest(systemWidgets.toArray(new Widget[0]), this));
+    }
+
+    private void renderWidgets() {
+        Version firmwareVersion = getCleanFWVersion();
+        if (firmwareVersion != null && firmwareVersion.greaterOrEqualThan(new Version("2.20"))) {
+            return; // this does not work on newer firmware versions
+        }
+        Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(getDeviceSupport().getDevice().getAddress()));
+        boolean forceWhiteBackground = prefs.getBoolean("force_white_color_scheme", false);
+        boolean drawCircles = prefs.getBoolean("widget_draw_circles", false);
+
+        Bitmap circleBitmap = null;
+        if (drawCircles) {
+            circleBitmap = Bitmap.createBitmap(76, 76, Bitmap.Config.ARGB_8888);
+            Canvas circleCanvas = new Canvas(circleBitmap);
+            Paint circlePaint = new Paint();
+            circlePaint.setAntiAlias(true);
+            circlePaint.setColor(forceWhiteBackground ? Color.WHITE : Color.BLACK);
+            circlePaint.setStyle(Paint.Style.FILL);
+            circlePaint.setStrokeWidth(3);
+            circleCanvas.drawCircle(38, 38, 35, circlePaint);
+
+            circlePaint.setColor(forceWhiteBackground ? Color.BLACK : Color.WHITE);
+            circlePaint.setStyle(Paint.Style.STROKE);
+            circlePaint.setStrokeWidth(3);
+            circleCanvas.drawCircle(38, 38, 35, circlePaint);
+        }
+
+        try {
+            ArrayList<AssetImage> widgetImages = new ArrayList<>();
+
+            if (this.backGroundImage != null) {
+                widgetImages.add(this.backGroundImage);
+            }
+
+
+            for (int i = 0; i < widgets.size(); i++) {
+                Widget w = widgets.get(i);
+                if (!(w instanceof CustomWidget)) {
+                    if (w.getWidgetType() == Widget.WidgetType.LAST_NOTIFICATION) {
+                        Bitmap widgetBitmap = Bitmap.createBitmap(76, 76, Bitmap.Config.ARGB_8888);
+                        Canvas widgetCanvas = new Canvas(widgetBitmap);
+                        if (drawCircles) {
+                            widgetCanvas.drawBitmap(circleBitmap, 0, 0, null);
+                        }
+
+                        Paint p = new Paint();
+                        p.setStyle(Paint.Style.FILL);
+                        p.setTextSize(10);
+                        p.setColor(Color.WHITE);
+
+                        if (this.lastPostedApp != null) {
+
+                            Bitmap icon = Bitmap.createScaledBitmap(appIconCache.get(this.lastPostedApp), 40, 40, true);
+
+                            if (icon != null) {
+
+                                widgetCanvas.drawBitmap(
+                                        icon,
+                                        (float) (38 - (icon.getWidth() / 2.0)),
+                                        (float) (38 - (icon.getHeight() / 2.0)),
+                                        null
+                                );
+                            }
+                        }
+
+                        widgetImages.add(AssetImageFactory.createAssetImage(
+                                widgetBitmap,
+                                true,
+                                w.getAngle(),
+                                w.getDistance(),
+                                1
+                        ));
+                    } else if (drawCircles) {
+                        widgetImages.add(AssetImageFactory.createAssetImage(
+                                circleBitmap,
+                                true,
+                                w.getAngle(),
+                                w.getDistance(),
+                                1
+                        ));
+                    }
+                    continue;
+                }
+
+                CustomWidget widget = (CustomWidget) w;
+
+                Bitmap widgetBitmap = Bitmap.createBitmap(76, 76, Bitmap.Config.ARGB_8888);
+
+                Canvas widgetCanvas = new Canvas(widgetBitmap);
+
+                if (drawCircles) {
+                    widgetCanvas.drawBitmap(circleBitmap, 0, 0, null);
+                }
+
+                for (CustomWidgetElement element : widget.getElements()) {
+                    if (element.getWidgetElementType() == CustomWidgetElement.WidgetElementType.TYPE_BACKGROUND) {
+                        File imageFile = new File(element.getValue());
+
+                        if (!imageFile.exists() || !imageFile.isFile()) {
+                            LOG.debug("Image file " + element.getValue() + " not found");
+                            continue;
+                        }
+                        Bitmap imageBitmap = BitmapFactory.decodeFile(element.getValue());
+                        if (imageBitmap == null) {
+                            LOG.debug("image file " + element.getValue() + " could not be decoded");
+                            continue;
+                        }
+                        Bitmap scaledBitmap = Bitmap.createScaledBitmap(imageBitmap, 76, 76, false);
+
+                        widgetCanvas.drawBitmap(
+                                scaledBitmap,
+                                0,
+                                0,
+                                null);
+                        break;
+                    }
+                }
+
+                for (CustomWidgetElement element : widget.getElements()) {
+                    if (element.getWidgetElementType() == CustomWidgetElement.WidgetElementType.TYPE_TEXT) {
+                        Paint textPaint = new Paint();
+                        textPaint.setStrokeWidth(4);
+                        textPaint.setTextSize(17f);
+                        textPaint.setStyle(Paint.Style.FILL);
+                        textPaint.setColor(forceWhiteBackground ? Color.BLACK : Color.WHITE);
+                        textPaint.setTextAlign(Paint.Align.CENTER);
+
+                        widgetCanvas.drawText(element.getValue(), element.getX(), element.getY() - (textPaint.descent() + textPaint.ascent()) / 2f, textPaint);
+                    } else if (element.getWidgetElementType() == CustomWidgetElement.WidgetElementType.TYPE_IMAGE) {
+                        Bitmap imageBitmap = BitmapFactory.decodeFile(element.getValue());
+
+                        widgetCanvas.drawBitmap(imageBitmap, element.getX() - imageBitmap.getWidth() / 2f, element.getY() - imageBitmap.getHeight() / 2f, null);
+                    }
+                }
+                widgetImages.add(AssetImageFactory.createAssetImage(
+                        widgetBitmap,
+                        true,
+                        widget.getAngle(),
+                        widget.getDistance(),
+                        1
+                ));
+            }
+
+
+            AssetImage[] images = widgetImages.toArray(new AssetImage[0]);
+
+            ArrayList<AssetImage> pushFiles = new ArrayList<>(4);
+            imgloop:
+            for (AssetImage image : images) {
+                for (AssetImage pushedImage : pushFiles) {
+                    // no need to send same file multiple times, filtering by name since name is hash
+                    if (image.getFileName().equals(pushedImage.getFileName())) continue imgloop;
+                }
+                pushFiles.add(image);
+            }
+
+            if (pushFiles.size() > 0) {
+                queueWrite(new AssetFilePutRequest(
+                        pushFiles.toArray(new AssetImage[0]),
+                        FileHandle.ASSET_BACKGROUND_IMAGES,
+                        this
+                ));
+            }
+            // queueWrite(new FileDeleteRequest((short) 0x0503));
+            queueWrite(new ImagesSetRequest(
+                    images,
+                    this
+            ));
+        } catch (IOException e) {
+            LOG.error("Error while rendering widgets", e);
+        }
+    }
+
+    private void handleFileDownload(String name, byte[] file, boolean toCache) {
+        Intent resultIntent = new Intent(QHybridSupport.QHYBRID_ACTION_DOWNLOADED_FILE);
+        File outputFile = new File(getContext().getExternalFilesDir("download"), name + "_" + System.currentTimeMillis() + ".bin");
+        try {
+            FileOutputStream fos = new FileOutputStream(outputFile);
+            fos.write(file);
+            fos.close();
+            resultIntent.putExtra("EXTRA_SUCCESS", true);
+            resultIntent.putExtra("EXTRA_PATH", outputFile.getAbsolutePath());
+            resultIntent.putExtra("EXTRA_NAME", name);
+            resultIntent.putExtra("EXTRA_TOCACHE", toCache);
+            LOG.info("Wrote downloaded file to " + outputFile.getAbsolutePath());
+        } catch (IOException e) {
+            LOG.error("Error while downloading file", e);
+            resultIntent.putExtra("EXTRA_SUCCESS", false);
+        }
+        LocalBroadcastManager.getInstance(getContext()).sendBroadcast(resultIntent);
+    }
+
+    @Override
+    public void uploadFileGenerateHeader(FileHandle handle, String filePath, boolean fileIsEncrypted) {
+        final Intent resultIntent = new Intent(QHybridSupport.QHYBRID_ACTION_UPLOADED_FILE);
+        byte[] fileData;
+
+        try {
+            FileInputStream fis = new FileInputStream(filePath);
+            fileData = new byte[fis.available()];
+            fis.read(fileData);
+            fis.close();
+        } catch (IOException e) {
+            LOG.error("Error while reading file", e);
+            resultIntent.putExtra("EXTRA_SUCCESS", false);
+            LocalBroadcastManager.getInstance(getContext()).sendBroadcast(resultIntent);
+            return;
+        }
+
+        queueWrite(new FilePutRequest(handle, fileData, this) {
+            @Override
+            public void onFilePut(boolean success) {
+                resultIntent.putExtra("EXTRA_SUCCESS", success);
+                LocalBroadcastManager.getInstance(getContext()).sendBroadcast(resultIntent);
+            }
+        });
+    }
+
+    @Override
+    public void uploadFileIncludesHeader(String filePath) {
+        final Intent resultIntent = new Intent(QHybridSupport.QHYBRID_ACTION_UPLOADED_FILE);
+        try {
+            FileInputStream fis = new FileInputStream(filePath);
+            uploadFileIncludesHeader(fis);
+            fis.close();
+        } catch (Exception e) {
+            LOG.error("Error while uploading file", e);
+            resultIntent.putExtra("EXTRA_SUCCESS", false);
+            LocalBroadcastManager.getInstance(getContext()).sendBroadcast(resultIntent);
+        }
+    }
+
+    private void uploadFileIncludesHeader(InputStream fis) throws IOException {
+        final Intent resultIntent = new Intent(QHybridSupport.QHYBRID_ACTION_UPLOADED_FILE);
+        byte[] fileData = new byte[fis.available()];
+        fis.read(fileData);
+
+        short handleBytes = (short) (fileData[0] & 0xFF | ((fileData[1] & 0xFF) << 8));
+        FileHandle handle = FileHandle.fromHandle(handleBytes);
+
+        if (handle == null) {
+            throw new RuntimeException("unknown handle");
+        }
+
+        queueWrite(new FilePutRawRequest(handle, fileData, this) {
+            @Override
+            public void onFilePut(boolean success) {
+                resultIntent.putExtra("EXTRA_SUCCESS", success);
+                LocalBroadcastManager.getInstance(getContext()).sendBroadcast(resultIntent);
+            }
+        });
+
+        if (handle == FileHandle.APP_CODE) {
+            listApplications();
+        }
+    }
+
+    @Override
+    public void downloadFile(byte majorHandle, byte minorHandle, String name, boolean fileIsEncrypted, boolean toCache) {
+        if (fileIsEncrypted) {
+            queueWrite((FileEncryptedInterface) new FileEncryptedGetRequest(majorHandle, minorHandle, this) {
+                @Override
+                public void handleFileData(byte[] fileData) {
+                    LOG.debug("downloaded encrypted file");
+                    handleFileDownload(name, fileData, toCache);
+                }
+            });
+        } else {
+            queueWrite(new FileGetRawRequest(majorHandle, minorHandle, this) {
+                @Override
+                public void handleFileRawData(byte[] fileData) {
+                    LOG.debug("downloaded regular file");
+                    handleFileDownload(name, fileData, toCache);
+                }
+            });
+        }
+    }
+
+    @Override
+    public void handleSetMenuStructure(JSONObject menuStructure) {
+        String serialized = menuStructure.toString();
+        getDeviceSpecificPreferences()
+                .edit()
+                .putString("MENU_STRUCTURE_JSON", serialized)
+                .apply();
+
+        try {
+            String payload = new JSONObject()
+                    .put("push", new JSONObject()
+                            .put("set", new JSONObject()
+                                    .put("customWatchFace._.config.menu_structure", menuStructure)
+                            )
+                    ).toString();
+            pushConfigJson(payload);
+        } catch (JSONException e) {
+            throw new RuntimeException(e);
+        }
+
+    }
+
+    @Override
+    public void setWidgetContent(String widgetID, String content, boolean renderOnWatch) {
+        boolean update = false;
+        for (Widget widget : widgets) {
+            if (!(widget instanceof CustomWidget)) continue;
+            if (((CustomWidget) widget).updateElementValue(widgetID, content)) update = true;
+        }
+
+        if (renderOnWatch && update) renderWidgets();
+    }
+
+    private void queueWrite(final FileEncryptedInterface request) {
+        try {
+            queueWrite(new VerifyPrivateKeyRequest(
+                    this.getSecretKey(),
+                    this
+            ) {
+                @Override
+                protected void handleAuthenticationResult(boolean success) {
+                    if (success) {
+                        GB.log("success auth", GB.INFO, null);
+                        queueWrite((FossilRequest) request, true);
+                    }
+                }
+            });
+        } catch (IllegalAccessException e) {
+            GB.toast("error getting key: " + e.getMessage(), Toast.LENGTH_LONG, GB.ERROR, e);
+        }
+    }
+
+    @Override
+    public void onInstallApp(Uri uri) {
+        FossilFileReader fossilFile;
+        try {
+            fossilFile = new FossilFileReader(uri, getContext());
+            if (fossilFile.isFirmware()) {
+                super.onInstallApp(uri);
+            } else if (fossilFile.isApp() || fossilFile.isWatchface()) {
+                UriHelper uriHelper = UriHelper.get(uri, getContext());
+                InputStream in = new BufferedInputStream(uriHelper.openInputStream());
+                uploadFileIncludesHeader(in);
+                in.close();
+            }
+        } catch (Exception ignored) {
+        }
+    }
+
+    private void negotiateSymmetricKey() {
+        try {
+            queueWrite(new VerifyPrivateKeyRequest(
+                    this.getSecretKey(),
+                    this
+            ) {
+                @Override
+                protected void handleAuthenticationResult(boolean success) {
+                    FossilHRWatchAdapter.this.handleAuthenticationResult(success);
+                }
+            });
+        } catch (IllegalAccessException e) {
+            GB.toast("error getting key: " + e.getMessage(), Toast.LENGTH_LONG, GB.ERROR, e);
+            this.handleAuthenticationResult(false);
+        }
+    }
+
+    @Override
+    public void setTime() {
+        if (connectionMode == CONNECTION_MODE.NOT_AUTHENTICATED) {
+            GB.toast(getContext().getString(R.string.fossil_hr_unavailable_unauthed), Toast.LENGTH_LONG, GB.ERROR);
+            return;
+        }
+        queueWrite(
+                (FileEncryptedInterface) new ConfigurationPutRequest(this.generateTimeConfigItemNow(), this)
+        );
+    }
+
+    @Override
+    public void setMusicInfo(MusicSpec musicSpec) {
+        musicSpec = new MusicSpec(musicSpec);
+        if(musicSpec.album == null) musicSpec.album = "";
+        if(musicSpec.artist == null) musicSpec.artist = "";
+        if(musicSpec.track == null) musicSpec.track = "";
+        if (
+                currentSpec != null
+                        && currentSpec.album.equals(musicSpec.album)
+                        && currentSpec.artist.equals(musicSpec.artist)
+                        && currentSpec.track.equals(musicSpec.track)
+        ) return;
+        currentSpec = musicSpec;
+        try {
+            queueWrite(new MusicInfoSetRequest(
+                    musicSpec.artist,
+                    musicSpec.album,
+                    musicSpec.track,
+                    this
+            ));
+        } catch (BufferOverflowException e) {
+            GB.log("musicInfo: " + musicSpec, GB.ERROR, e);
+        }
+    }
+
+    @Override
+    public void setMusicState(MusicStateSpec stateSpec) {
+        super.setMusicState(stateSpec);
+
+        queueWrite(new MusicControlRequest(
+                stateSpec.state == MusicStateSpec.STATE_PLAYING ? MUSIC_PHONE_REQUEST.MUSIC_REQUEST_SET_PLAYING : MUSIC_PHONE_REQUEST.MUSIC_REQUEST_SET_PAUSED
+        ));
+    }
+
+    @Override
+    public void updateWidgets() {
+        loadWidgets();
+        renderWidgets();
+    }
+
+    @Override
+    public void onFetchActivityData() {
+        if (connectionMode == CONNECTION_MODE.NOT_AUTHENTICATED) {
+            GB.toast(getContext().getString(R.string.fossil_hr_unavailable_unauthed), Toast.LENGTH_LONG, GB.ERROR);
+            return;
+        }
+
+        syncSettings();
+
+        queueWrite(new FileLookupRequest(FileHandle.ACTIVITY_FILE, this) {
+            @Override
+            public void handleFileLookup(final short fileHandle) {
+                queueWrite((FileEncryptedInterface) new FileEncryptedGetRequest(fileHandle, FossilHRWatchAdapter.this) {
+                    @Override
+                    public void handleFileData(byte[] fileData) {
+                        try (DBHandler dbHandler = GBApplication.acquireDB()) {
+                            User user = DBHelper.getUser(dbHandler.getDaoSession());
+                            Long userId = user.getId();
+                            Device device = DBHelper.getDevice(getDeviceSupport().getDevice(), dbHandler.getDaoSession());
+                            Long deviceId = device.getId();
+                            ActivityFileParser parser = new ActivityFileParser();
+                            Map.Entry<ArrayList<ActivityEntry>, ArrayList<HybridHRSpo2Sample>> parsedEntries = parser.parseFile(fileData);
+                            // Activities
+                            ArrayList<ActivityEntry> entries = parsedEntries.getKey();
+                            HybridHRActivitySampleProvider provider = new HybridHRActivitySampleProvider(getDeviceSupport().getDevice(), dbHandler.getDaoSession());
+                            HybridHRActivitySample[] samples = new HybridHRActivitySample[entries.size()];
+                            for (int i = 0; i < entries.size(); i++) {
+                                samples[i] = entries.get(i).toDAOActivitySample(userId, deviceId);
+                            }
+                            provider.addGBActivitySamples(samples);
+                            // SpO2, should be empty for an unsupported device
+                            ArrayList<HybridHRSpo2Sample> spo2Samples = parsedEntries.getValue();
+                            HybridHRSpo2SampleProvider spo2Provider = new HybridHRSpo2SampleProvider(getDeviceSupport().getDevice(), dbHandler.getDaoSession());
+                            for (HybridHRSpo2Sample sample : spo2Samples) {
+                                sample.setDevice(device);
+                                sample.setUser(user);
+                            }
+                            spo2Provider.addSamples(spo2Samples);
+
+                            if (saveRawActivityFiles) {
+                                writeFile(String.valueOf(System.currentTimeMillis()), fileData);
+                            }
+                            queueWrite(new FileDeleteRequest(fileHandle));
+                            GB.updateTransferNotification(null, "", false, 100, getContext());
+                            GB.signalActivityDataFinish(getDeviceSupport().getDevice());
+                            LOG.debug("Synchronized activity data");
+                        } catch (Exception ex) {
+                            GB.toast(getContext(), "Error saving steps data: " + ex.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
+                            LOG.error("Error saving steps data: ", ex);
+                            GB.updateTransferNotification(null, "Data transfer failed", false, 0, getContext());
+                        }
+                        getDeviceSupport().getDevice().unsetBusyTask();
+                        getDeviceSupport().getDevice().sendDeviceUpdateIntent(getContext());
+                    }
+                });
+            }
+
+            @Override
+            public void handleFileLookupError(FILE_LOOKUP_ERROR error) {
+                if (error == FILE_LOOKUP_ERROR.FILE_EMPTY) {
+                    LOG.debug("No activity data to sync");
+                } else {
+                    throw new RuntimeException("strange lookup stuff");
+                }
+                getDeviceSupport().getDevice().unsetBusyTask();
+                GB.updateTransferNotification(null, "", false, 100, getContext());
+                getDeviceSupport().getDevice().sendDeviceUpdateIntent(getContext());
+            }
+        });
+    }
+
+    private void writeFile(String fileName, byte[] value) {
+        File activityDir = new File(getContext().getExternalFilesDir(null), "activity_hr");
+        activityDir.mkdir();
+        File f = new File(activityDir, fileName);
+        try {
+            f.createNewFile();
+            FileOutputStream fos = new FileOutputStream(f);
+            fos.write(value);
+            fos.close();
+            GB.toast("saved file data", Toast.LENGTH_SHORT, GB.INFO);
+        } catch (IOException e) {
+            GB.log("file error", GB.ERROR, e);
+        }
+    }
+
+    private void syncSettings() {
+        if (connectionMode == CONNECTION_MODE.NOT_AUTHENTICATED) {
+            GB.toast(getContext().getString(R.string.fossil_hr_unavailable_unauthed), Toast.LENGTH_LONG, GB.ERROR);
+            return;
+        }
+
+        queueWrite((FileEncryptedInterface) new ConfigurationGetRequest(this));
+    }
+
+    @Override
+    public void setActivityHand(double progress) {
+        // super.setActivityHand(progress);
+    }
+
+    private boolean isNotificationWidgetVisible() {
+        for (Widget widget : widgets) {
+            if (widget.getWidgetType() == Widget.WidgetType.LAST_NOTIFICATION) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public boolean playRawNotification(NotificationSpec notificationSpec) {
+        String sourceAppId = notificationSpec.sourceAppId;
+        String senderOrTitle = StringUtils.getFirstOf(notificationSpec.sender, notificationSpec.title);
+
+        // Retrieve and store notification or app icon
+        if (sourceAppId != null) {
+            if (appIconCache.get(sourceAppId) == null) {
+                try {
+                    Drawable icon = null;
+                    if (notificationSpec.iconId != 0) {
+                        Context sourcePackageContext = getContext().createPackageContext(sourceAppId, 0);
+                        icon = ResourcesCompat.getDrawable(sourcePackageContext.getResources(), notificationSpec.iconId, null);
+                    }
+                    if (icon == null) {
+                        icon = NotificationUtils.getAppIcon(getContext(), sourceAppId);
+                    }
+                    Bitmap iconBitmap = convertDrawableToBitmap(icon);
+                    appIconCache.put(sourceAppId, iconBitmap);
+                    setNotificationConfigurations();
+                } catch (PackageManager.NameNotFoundException e) {
+                    LOG.error("Error while updating notification icons", e);
+                }
+            }
+        }
+
+        boolean packageFound = false;
+
+        // Send notification to watch
+        try {
+            for (NotificationHRConfiguration configuration : this.notificationConfigurations) {
+                if (configuration.getPackageName().equals(sourceAppId)) {
+                    LOG.info("Package found in notificationConfigurations, using custom icon: " + sourceAppId);
+                    queueWrite(new PlayTextNotificationRequest(sourceAppId, senderOrTitle, notificationSpec, this));
+                    packageFound = true;
+                }
+            }
+
+            if (!packageFound) {
+                LOG.info("Package not found in notificationConfigurations, using generic icon: " + sourceAppId);
+                queueWrite(new PlayTextNotificationRequest("generic", senderOrTitle, notificationSpec, this));
+            }
+        } catch (Exception e) {
+            LOG.error("Error while forwarding notification", e);
+        }
+
+        // Update notification icon custom widget
+        if (isNotificationWidgetVisible() && sourceAppId != null) {
+            if (!sourceAppId.equals(this.lastPostedApp)) {
+                this.lastPostedApp = sourceAppId;
+                renderWidgets();
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public void onDeleteNotification(int id) {
+        super.onDeleteNotification(id);
+
+        // send notification dismissal message to watch
+        try {
+            queueWrite(new DismissTextNotificationRequest(id, this));
+        } catch (Exception e) {
+            LOG.error("Error while dismissing notification", e);
+        }
+
+        // only delete app icon when no notification of said app is present
+        for (String app : NotificationListener.notificationStack) {
+            if (app.equals(this.lastPostedApp)) return;
+        }
+
+        this.lastPostedApp = null;
+
+        if (isNotificationWidgetVisible()) {
+            renderWidgets();
+        }
+    }
+
+    @Override
+    public void onSetCallState(CallSpec callSpec) {
+        super.onSetCallState(callSpec);
+        String[] quickReplies = getQuickReplies();
+        boolean quickRepliesEnabled = quickReplies.length > 0 && callSpec.number != null && callSpec.number.matches("^\\+(?:[0-9] ?){6,14}[0-9]$");
+        if (callSpec.command == CallSpec.CALL_INCOMING) {
+            currentCallSpec = callSpec;
+            queueWrite(new PlayCallNotificationRequest(StringUtils.getFirstOf(callSpec.name, callSpec.number), true, quickRepliesEnabled, callSpec.dndSuppressed, this));
+        } else {
+            currentCallSpec = null;
+            queueWrite(new PlayCallNotificationRequest(StringUtils.getFirstOf(callSpec.name, callSpec.number), false, quickRepliesEnabled, callSpec.dndSuppressed, this));
+        }
+    }
+
+    // this method is based on the one from AppMessageHandlerYWeather.java
+    private int getIconForConditionCode(int conditionCode, boolean isNight) {
+        final int CLEAR_DAY = 0;
+        final int CLEAR_NIGHT = 1;
+        final int CLOUDY = 2;
+        final int PARTLY_CLOUDY_DAY = 3;
+        final int PARTLY_CLOUDY_NIGHT = 4;
+        final int RAIN = 5;
+        final int SNOW = 6;
+        final int SNOW_2 = 7; // same as 6?
+        final int THUNDERSTORM = 8;
+        final int CLOUDY_2 = 9; // same as 2?
+        final int WINDY = 10;
+
+        if (conditionCode == 800 || conditionCode == 951) {
+            return isNight ? CLEAR_NIGHT : CLEAR_DAY;
+        } else if (conditionCode > 800 && conditionCode < 900) {
+            return isNight ? PARTLY_CLOUDY_NIGHT : PARTLY_CLOUDY_DAY;
+        } else if (conditionCode >= 300 && conditionCode < 400) {
+            return RAIN; // drizzle mapped to rain
+        } else if (conditionCode >= 500 && conditionCode < 600) {
+            return RAIN;
+        } else if (conditionCode >= 700 && conditionCode < 732) {
+            return CLOUDY;
+        } else if (conditionCode == 741 || conditionCode == 751 || conditionCode == 761 || conditionCode == 762) {
+            return CLOUDY; // fog mapped to cloudy
+        } else if (conditionCode == 771) {
+            return CLOUDY; // squalls mapped to cloudy
+        } else if (conditionCode == 781) {
+            return WINDY; // tornato mapped to windy
+        } else if (conditionCode >= 200 && conditionCode < 300) {
+            return THUNDERSTORM;
+        } else if (conditionCode >= 600 && conditionCode <= 602) {
+            return SNOW;
+        } else if (conditionCode >= 611 && conditionCode <= 622) {
+            return RAIN;
+        } else if (conditionCode == 906) {
+            return RAIN; // hail mapped to rain
+        } else if (conditionCode >= 907 && conditionCode < 957) {
+            return WINDY;
+        } else if (conditionCode == 905) {
+            return WINDY;
+        } else if (conditionCode == 900) {
+            return WINDY;
+        } else if (conditionCode == 901 || conditionCode == 902 || conditionCode == 962) {
+            return WINDY;
+        }
+        return isNight ? CLEAR_NIGHT : CLEAR_DAY;
+    }
+
+    @Override
+    public void onSendWeather(WeatherSpec weatherSpec) {
+        boolean isNight = false;
+        if (weatherSpec.sunRise != 0 && weatherSpec.sunSet != 0) {
+            isNight = weatherSpec.sunRise * 1000L > System.currentTimeMillis() || weatherSpec.sunSet * 1000L < System.currentTimeMillis();
+        } else {
+            Location location = weatherSpec.getLocation();
+            if (location == null) {
+                location = new CurrentPosition().getLastKnownLocation();
+            }
+            final ZonedDateTime now = ZonedDateTime.now();
+            final SunriseTransitSet sunriseTransitSet = SPA.calculateSunriseTransitSet(
+                    now,
+                    location.getLatitude(),
+                    location.getLongitude(),
+                    DeltaT.estimate(now.toLocalDate())
+            );
+            if (sunriseTransitSet.getSunrise() != null && sunriseTransitSet.getSunset() != null) {
+                isNight = sunriseTransitSet.getSunrise().isAfter(now) || sunriseTransitSet.getSunset().isBefore(now);
+            }
+        }
+
+        long ts = System.currentTimeMillis();
+        ts /= 1000;
+        try {
+            JSONObject responseObject = new JSONObject()
+                    .put("res", new JSONObject()
+                            .put("id", 0) // seems the id does not matter?
+                            .put("set", new JSONObject()
+                                    .put("weatherInfo", new JSONObject()
+                                            .put("alive", ts + 60 * 60)
+                                            .put("unit", "c") // FIXME: do not hardcode
+                                            .put("temp", weatherSpec.currentTemp - 273)
+                                            .put("cond_id", getIconForConditionCode(weatherSpec.currentConditionCode, isNight))
+                                    )
+                            )
+                    );
+
+            queueWrite(new JsonPutRequest(responseObject, this));
+
+            JSONArray forecastWeekArray = new JSONArray();
+            final String[] weekdays = {"", "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
+            Calendar cal = Calendar.getInstance();
+            cal.setTimeInMillis(weatherSpec.timestamp * 1000L);
+            int i = 0;
+            for (WeatherSpec.Daily forecast : weatherSpec.forecasts) {
+                cal.add(Calendar.DATE, 1);
+                int dayOfWeek = cal.get(Calendar.DAY_OF_WEEK);
+                forecastWeekArray.put(new JSONObject()
+                        .put("day", weekdays[dayOfWeek])
+                        .put("cond_id", getIconForConditionCode(forecast.conditionCode, false))
+                        .put("high", forecast.maxTemp - 273)
+                        .put("low", forecast.minTemp - 273)
+                );
+                if (++i == 3) break; // max 3
+            }
+
+            JSONArray forecastDayArray = new JSONArray();
+            final int[] hours = {0, 0, 0};
+
+            for (int hour : hours) {
+                forecastDayArray.put(new JSONObject()
+                        .put("hour", hour)
+                        .put("cond_id", 0)
+                        .put("temp", 0)
+                );
+            }
+
+            JSONObject forecastResponseObject = new JSONObject()
+                    .put("res", new JSONObject()
+                            .put("id", 0)
+                            .put("set", new JSONObject()
+                                    .put("weatherApp._.config.locations", new JSONArray()
+                                            .put(new JSONObject()
+                                                    .put("alive", ts + 60 * 60)
+                                                    .put("city", weatherSpec.location)
+                                                    .put("unit", "c") // FIXME: do not hardcode
+                                                    .put("temp", weatherSpec.currentTemp - 273)
+                                                    .put("high", weatherSpec.todayMaxTemp - 273)
+                                                    .put("low", weatherSpec.todayMinTemp - 273)
+                                                    .put("rain", weatherSpec.precipProbability)
+                                                    .put("uv", Math.round(weatherSpec.uvIndex))
+                                                    .put("message", weatherSpec.currentCondition)
+                                                    .put("cond_id", getIconForConditionCode(weatherSpec.currentConditionCode, isNight))
+                                                    .put("forecast_day", forecastDayArray)
+                                                    .put("forecast_week", forecastWeekArray)
+                                            )
+                                    )
+                            )
+                    );
+
+            queueWrite(new JsonPutRequest(forecastResponseObject, this));
+
+        } catch (JSONException e) {
+            LOG.error("JSON exception: ", e);
+        }
+    }
+
+    public void onSendChanceOfRain(WeatherSpec weatherSpec) {
+        long ts = System.currentTimeMillis();
+        ts /= 1000;
+        try {
+            JSONObject rainObject = new JSONObject()
+                .put("res", new JSONObject()
+                    .put("set", new JSONObject()
+                        .put("widgetChanceOfRain._.config.info", new JSONObject()
+                            .put("alive", ts + 60 * 15)
+                            .put("rain", weatherSpec.precipProbability)
+                        )
+                    )
+                );
+
+            queueWrite(new JsonPutRequest(rainObject, this));
+        } catch (JSONException e) {
+            LOG.error("JSON exception: ", e);
+        }
+    }
+
+    public void onSendUVIndex(WeatherSpec weatherSpec) {
+        long ts = System.currentTimeMillis();
+        ts /= 1000;
+        try {
+            JSONObject rainObject = new JSONObject()
+                .put("res", new JSONObject()
+                    .put("set", new JSONObject()
+                        .put("widgetUV._.config.info", new JSONObject()
+                            .put("alive", ts + 60 * 15)
+                            .put("uv", Math.round(weatherSpec.uvIndex))
+                        )
+                    )
+                );
+
+            queueWrite(new JsonPutRequest(rainObject, this));
+        } catch (JSONException e) {
+            LOG.error("JSON exception: ", e);
+        }
+    }
+
+    @Override
+    public void factoryReset() {
+        queueWrite(new FactoryResetRequest());
+    }
+
+    @Override
+    public void onTestNewFunction() {
+        setVibrationStrengthFromConfig();
+    }
+
+    public byte[] getSecretKey() throws IllegalAccessException {
+        byte[] authKeyBytes = new byte[16];
+
+        SharedPreferences sharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(getDeviceSupport().getDevice().getAddress());
+
+        String authKey = sharedPrefs.getString("authkey", null);
+        if (authKey != null && !authKey.isEmpty()) {
+            authKey = authKey.replace(" ", "");
+            authKey = authKey.replace("0x", "");
+            if (authKey.length() != 32) {
+                throw new IllegalAccessException("Key should be 16 bytes long as hex string");
+            }
+            byte[] srcBytes = GB.hexStringToByteArray(authKey);
+
+            System.arraycopy(srcBytes, 0, authKeyBytes, 0, Math.min(srcBytes.length, 16));
+        }
+
+        return authKeyBytes;
+    }
+
+    @Override
+    public void pushConfigJson(String configJson) {
+        configJson = configJson.replace("\n", "");
+        queueWrite(new JsonPutRequest(configJson, this));
+    }
+
+    public void setPhoneRandomNumber(byte[] phoneRandomNumber) {
+        this.phoneRandomNumber = phoneRandomNumber;
+    }
+
+    public byte[] getPhoneRandomNumber() {
+        return phoneRandomNumber;
+    }
+
+    public void setWatchRandomNumber(byte[] watchRandomNumber) {
+        this.watchRandomNumber = watchRandomNumber;
+    }
+
+    public byte[] getWatchRandomNumber() {
+        return watchRandomNumber;
+    }
+
+    @Override
+    public void overwriteButtons(String jsonConfigString) {
+        try {
+            SharedPreferences prefs = getDeviceSpecificPreferences();
+
+            String singlePressEvent = "short_press_release";
+
+            Version firmwareVersion = getCleanFWVersion();
+            if (firmwareVersion != null && firmwareVersion.smallerThan(new Version("2.19"))) {
+                singlePressEvent = "single_click";
+            }
+            ArrayList<ButtonConfiguration> configs = new ArrayList<>(6);
+            configs.add(new ButtonConfiguration("top_" + singlePressEvent, prefs.getString(DeviceSettingsPreferenceConst.PREF_BUTTON_1_FUNCTION_SHORT, "weatherApp")));
+            configs.add(new ButtonConfiguration("top_hold", prefs.getString(DeviceSettingsPreferenceConst.PREF_BUTTON_1_FUNCTION_LONG, "weatherApp")));
+            // configs.add(new ButtonConfiguration("top_double_click", prefs.getString(DeviceSettingsPreferenceConst.PREF_BUTTON_1_FUNCTION_DOUBLE, "weatherApp")));
+            configs.add(new ButtonConfiguration("middle_" + singlePressEvent, prefs.getString(DeviceSettingsPreferenceConst.PREF_BUTTON_2_FUNCTION_SHORT, "commuteApp")));
+            if (firmwareVersion != null && firmwareVersion.greaterOrEqualThan(new Version("3.0"))) {
+                configs.add(new ButtonConfiguration("middle_hold", prefs.getString(DeviceSettingsPreferenceConst.PREF_BUTTON_2_FUNCTION_LONG, "launcherApp")));
+            }
+            // configs.add(new ButtonConfiguration("middle_double_click", prefs.getString(DeviceSettingsPreferenceConst.PREF_BUTTON_2_FUNCTION_DOUBLE, "commuteApp")));
+            configs.add(new ButtonConfiguration("bottom_" + singlePressEvent, prefs.getString(DeviceSettingsPreferenceConst.PREF_BUTTON_3_FUNCTION_SHORT, "musicApp")));
+            configs.add(new ButtonConfiguration("bottom_hold", prefs.getString(DeviceSettingsPreferenceConst.PREF_BUTTON_3_FUNCTION_LONG, "musicApp")));
+            // configs.add(new ButtonConfiguration("bottom_double_click", prefs.getString(DeviceSettingsPreferenceConst.PREF_BUTTON_3_FUNCTION_DOUBLE, "musicApp")));
+
+            // filter out all apps not installed on watch
+            ArrayList<ButtonConfiguration> availableConfigs = new ArrayList<>();
+            outerLoop:
+            for (ButtonConfiguration config : configs) {
+                for (ApplicationInformation installedApp : installedApplications) {
+                    if (installedApp.getAppName().equals(config.getAction()) ||
+                            config.getAction().equals("workoutApp") //workoutApp is part of internal firmware
+                    ) {
+                        availableConfigs.add(config);
+                        continue outerLoop;
+                    }
+                }
+            }
+
+            queueWrite(new ButtonConfigurationPutRequest(
+                    availableConfigs.toArray(new ButtonConfiguration[0]),
+                    this
+            ));
+
+            for (ApplicationInformation info : installedApplications) {
+                if (info.getAppName().equals("commuteApp")) {
+                    JSONArray jsonArray = new JSONArray(
+                            GBApplication.getPrefs().getString(CommuteActionsActivity.CONFIG_KEY_Q_ACTIONS, "[]")
+                    );
+                    String[] menuItems = new String[jsonArray.length()];
+                    for (int i = 0; i < jsonArray.length(); i++)
+                        menuItems[i] = jsonArray.getString(i);
+                    queueWrite(new CommuteConfigPutRequest(menuItems, this));
+                    break;
+                }
+            }
+        } catch (JSONException e) {
+            LOG.error("Error while configuring buttons", e);
+        }
+    }
+
+    private void setActivityRecognition(){
+        SharedPreferences prefs = getDeviceSpecificPreferences();
+        boolean runningEnabled = prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_HYBRID_HR_ACTIVITY_RECOGNITION_RUNNING_ENABLED, false);
+        boolean runningAskFirst = prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_HYBRID_HR_ACTIVITY_RECOGNITION_RUNNING_ASK_FIRST, false);
+        int runningMinutes = Integer.parseInt(prefs.getString(DeviceSettingsPreferenceConst.PREF_HYBRID_HR_ACTIVITY_RECOGNITION_RUNNING_MINUTES, "3"));
+        boolean bikingEnabled = prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_HYBRID_HR_ACTIVITY_RECOGNITION_BIKING_ENABLED, false);
+        boolean bikingAskFirst = prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_HYBRID_HR_ACTIVITY_RECOGNITION_BIKING_ASK_FIRST, false);
+        int bikingMinutes = Integer.parseInt(prefs.getString(DeviceSettingsPreferenceConst.PREF_HYBRID_HR_ACTIVITY_RECOGNITION_BIKING_MINUTES, "5"));
+        boolean walkingEnabled = prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_HYBRID_HR_ACTIVITY_RECOGNITION_WALKING_ENABLED, false);
+        boolean walkingAskFirst = prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_HYBRID_HR_ACTIVITY_RECOGNITION_WALKING_ASK_FIRST, false);
+        int walkingMinutes = Integer.parseInt(prefs.getString(DeviceSettingsPreferenceConst.PREF_HYBRID_HR_ACTIVITY_RECOGNITION_WALKING_MINUTES, "10"));
+        boolean rowingEnabled = prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_HYBRID_HR_ACTIVITY_RECOGNITION_ROWING_ENABLED, false);
+        boolean rowingAskFirst = prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_HYBRID_HR_ACTIVITY_RECOGNITION_ROWING_ASK_FIRST, false);
+        int rowingMinutes = Integer.parseInt(prefs.getString(DeviceSettingsPreferenceConst.PREF_HYBRID_HR_ACTIVITY_RECOGNITION_ROWING_MINUTES, "3"));
+
+        if (runningMinutes < 1) runningMinutes = 1;
+        if (runningMinutes > 255) runningMinutes = 255;
+        if (bikingMinutes < 1) bikingMinutes = 1;
+        if (bikingMinutes > 255) bikingMinutes = 255;
+        if (walkingMinutes < 1) walkingMinutes = 1;
+        if (walkingMinutes > 255) walkingMinutes = 255;
+        if (rowingMinutes < 1) rowingMinutes = 1;
+        if (rowingMinutes > 255) rowingMinutes = 255;
+
+        FitnessConfigItem fitnessConfigItem = new FitnessConfigItem(
+                runningEnabled,
+                runningAskFirst,
+                runningMinutes,
+                bikingEnabled,
+                bikingAskFirst,
+                bikingMinutes,
+                walkingEnabled,
+                walkingAskFirst,
+                walkingMinutes,
+                rowingEnabled,
+                rowingAskFirst,
+                rowingMinutes
+        );
+
+        queueWrite((FileEncryptedInterface) new ConfigurationPutRequest(fitnessConfigItem, this));
+    }
+
+    private void setInactivityWarning(){
+        SharedPreferences prefs = getDeviceSpecificPreferences();
+        boolean enabled = prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_INACTIVITY_ENABLE, false);
+        int threshold = Integer.parseInt(prefs.getString(DeviceSettingsPreferenceConst.PREF_INACTIVITY_THRESHOLD, "60"));
+        String start = prefs.getString(DeviceSettingsPreferenceConst.PREF_INACTIVITY_START, "06:00");
+        String end = prefs.getString(DeviceSettingsPreferenceConst.PREF_INACTIVITY_END, "22:00");
+
+        int startHour = Integer.parseInt(start.split(":")[0]);
+        int startMinute = Integer.parseInt(start.split(":")[1]);
+        int endHour = Integer.parseInt(end.split(":")[0]);
+        int endMinute = Integer.parseInt(end.split(":")[1]);
+
+        InactivityWarningItem inactivityWarningItem = new InactivityWarningItem(
+                startHour, startMinute, endHour, endMinute, threshold, enabled
+        );
+
+        queueWrite((FileEncryptedInterface) new ConfigurationPutRequest(inactivityWarningItem, this));
+    }
+
+    @Override
+    public void onSendConfiguration(String config) {
+        switch (config) {
+            case DeviceSettingsPreferenceConst.PREF_BUTTON_1_FUNCTION_SHORT:
+            case DeviceSettingsPreferenceConst.PREF_BUTTON_2_FUNCTION_SHORT:
+            case DeviceSettingsPreferenceConst.PREF_BUTTON_3_FUNCTION_SHORT:
+            case DeviceSettingsPreferenceConst.PREF_BUTTON_1_FUNCTION_LONG:
+            case DeviceSettingsPreferenceConst.PREF_BUTTON_2_FUNCTION_LONG:
+            case DeviceSettingsPreferenceConst.PREF_BUTTON_3_FUNCTION_LONG:
+            case DeviceSettingsPreferenceConst.PREF_BUTTON_1_FUNCTION_DOUBLE:
+            case DeviceSettingsPreferenceConst.PREF_BUTTON_2_FUNCTION_DOUBLE:
+            case DeviceSettingsPreferenceConst.PREF_BUTTON_3_FUNCTION_DOUBLE:
+                overwriteButtons(null);
+                break;
+            case DeviceSettingsPreferenceConst.PREF_VIBRATION_STRENGH_PERCENTAGE:
+                setVibrationStrengthFromConfig();
+                break;
+            case "force_white_color_scheme":
+                loadBackground();
+                // not break here
+            case "widget_draw_circles": {
+                renderWidgets();
+                break;
+            }
+            case DeviceSettingsPreferenceConst.PREF_HYBRID_HR_SAVE_RAW_ACTIVITY_FILES: {
+                saveRawActivityFiles = getDeviceSpecificPreferences().getBoolean("save_raw_activity_files", false);
+                break;
+            }
+            case SettingsActivity.PREF_MEASUREMENT_SYSTEM:
+                setUnitsConfig();
+                break;
+            case DeviceSettingsPreferenceConst.PREF_HYBRID_HR_ACTIVITY_RECOGNITION_RUNNING_ENABLED:
+            case DeviceSettingsPreferenceConst.PREF_HYBRID_HR_ACTIVITY_RECOGNITION_RUNNING_ASK_FIRST:
+            case DeviceSettingsPreferenceConst.PREF_HYBRID_HR_ACTIVITY_RECOGNITION_RUNNING_MINUTES:
+            case DeviceSettingsPreferenceConst.PREF_HYBRID_HR_ACTIVITY_RECOGNITION_BIKING_ENABLED:
+            case DeviceSettingsPreferenceConst.PREF_HYBRID_HR_ACTIVITY_RECOGNITION_BIKING_ASK_FIRST:
+            case DeviceSettingsPreferenceConst.PREF_HYBRID_HR_ACTIVITY_RECOGNITION_BIKING_MINUTES:
+            case DeviceSettingsPreferenceConst.PREF_HYBRID_HR_ACTIVITY_RECOGNITION_WALKING_ENABLED:
+            case DeviceSettingsPreferenceConst.PREF_HYBRID_HR_ACTIVITY_RECOGNITION_WALKING_ASK_FIRST:
+            case DeviceSettingsPreferenceConst.PREF_HYBRID_HR_ACTIVITY_RECOGNITION_WALKING_MINUTES:
+            case DeviceSettingsPreferenceConst.PREF_HYBRID_HR_ACTIVITY_RECOGNITION_ROWING_ENABLED:
+            case DeviceSettingsPreferenceConst.PREF_HYBRID_HR_ACTIVITY_RECOGNITION_ROWING_ASK_FIRST:
+            case DeviceSettingsPreferenceConst.PREF_HYBRID_HR_ACTIVITY_RECOGNITION_ROWING_MINUTES:
+                setActivityRecognition();
+                break;
+            case DeviceSettingsPreferenceConst.PREF_INACTIVITY_ENABLE:
+            case DeviceSettingsPreferenceConst.PREF_INACTIVITY_THRESHOLD:
+            case DeviceSettingsPreferenceConst.PREF_INACTIVITY_START:
+            case DeviceSettingsPreferenceConst.PREF_INACTIVITY_END:
+                setInactivityWarning();
+                break;
+        }
+    }
+
+    @Override
+    public void handleHeartRateCharacteristic(BluetoothGattCharacteristic characteristic) {
+        super.handleHeartRateCharacteristic(characteristic);
+
+        byte[] value = characteristic.getValue();
+
+        int heartRate = value[1];
+
+        LOG.debug("heart rate: " + heartRate);
+    }
+
+    @Override
+    protected void handleBackgroundCharacteristic(BluetoothGattCharacteristic characteristic) {
+        super.handleBackgroundCharacteristic(characteristic);
+
+        byte[] value = characteristic.getValue();
+
+        byte requestType = value[1];
+
+        if (requestType == (byte) 0x04) {
+            if (value[7] == 0x00 || value[7] == 0x01) {
+                handleCallRequest(value);
+            } else if (value[7] == 0x02) {
+                handleDeleteNotification(value);
+            } else if (value[7] == 0x03) {
+                handleQuickReplyRequest(value);
+            }
+        } else if (requestType == (byte) 0x05) {
+            handleMusicRequest(value);
+        } else if (requestType == (byte) 0x01) {
+            int eventId = value[2];
+            LOG.info("got event id " + eventId);
+            try {
+                String jsonString = new String(value, 3, value.length - 3);
+                // logger.info(jsonString);
+                JSONObject requestJson = new JSONObject(jsonString);
+
+                JSONObject request = requestJson.getJSONObject("req");
+                int requestId = request.getInt("id");
+
+                if (request.has("ringMyPhone")) {
+                    String action = request.getJSONObject("ringMyPhone").getString("action");
+                    LOG.info("got ringMyPhone request; " + action);
+                    GBDeviceEventFindPhone findPhoneEvent = new GBDeviceEventFindPhone();
+
+                    JSONObject responseObject = new JSONObject()
+                            .put("res", new JSONObject()
+                                    .put("id", requestId)
+                                    .put("set", new JSONObject()
+                                            .put("ringMyPhone", new JSONObject()
+                                            )
+                                    )
+                            );
+
+                    if ("on".equals(action)) {
+                        findPhoneEvent.event = GBDeviceEventFindPhone.Event.START;
+                        getDeviceSupport().evaluateGBDeviceEvent(findPhoneEvent);
+                        responseObject
+                                .getJSONObject("res")
+                                .getJSONObject("set")
+                                .getJSONObject("ringMyPhone")
+                                .put("result", "on");
+                        queueWrite(new JsonPutRequest(responseObject, this));
+                    } else if ("off".equals(action)) {
+                        findPhoneEvent.event = GBDeviceEventFindPhone.Event.STOP;
+                        getDeviceSupport().evaluateGBDeviceEvent(findPhoneEvent);
+                        responseObject
+                                .getJSONObject("res")
+                                .getJSONObject("set")
+                                .getJSONObject("ringMyPhone")
+                                .put("result", "off");
+                        queueWrite(new JsonPutRequest(responseObject, this));
+                    }
+                } else if (request.has("weatherInfo") || request.has("weatherApp._.config.locations")) {
+                    LOG.info("Got weatherInfo request");
+                    WeatherSpec weatherSpec = Weather.getInstance().getWeatherSpec();
+                    if (weatherSpec != null) {
+                        onSendWeather(weatherSpec);
+                    } else {
+                        LOG.info("no weather data available  - ignoring request");
+                    }
+                } else if (request.has("widgetChanceOfRain._.config.info")) {
+                    LOG.info("Got widgetChanceOfRain request");
+                    WeatherSpec weatherSpec = Weather.getInstance().getWeatherSpec();
+                    if (weatherSpec != null) {
+                        onSendChanceOfRain(weatherSpec);
+                    } else {
+                        LOG.info("no weather data available  - ignoring request");
+                    }
+                } else if (request.has("widgetUV._.config.info")) {
+                    LOG.info("Got widgetUV request");
+                    WeatherSpec weatherSpec = Weather.getInstance().getWeatherSpec();
+                    if (weatherSpec != null) {
+                        onSendUVIndex(weatherSpec);
+                    } else {
+                        LOG.info("no weather data available  - ignoring request");
+                    }
+                } else if (request.has("commuteApp._.config.commute_info")) {
+                    String action = request.getJSONObject("commuteApp._.config.commute_info")
+                            .getString("dest");
+
+                    String startStop = request.getJSONObject("commuteApp._.config.commute_info")
+                            .getString("action");
+
+                    if (startStop.equals("stop")) {
+                        // overwriteButtons(null);
+                        return;
+                    }
+
+                    Intent menuIntent = new Intent(QHybridSupport.QHYBRID_EVENT_COMMUTE_MENU);
+                    menuIntent.putExtra("EXTRA_ACTION", action);
+                    getContext().sendBroadcast(menuIntent);
+                } else if (request.has("master._.config.app_status")) {
+                    queueWrite(new ConfirmAppStatusRequest(requestId, this));
+                } else if (request.has("workoutApp")) {
+                    JSONObject workoutRequest = request.getJSONObject("workoutApp");
+                    String workoutState = workoutRequest.optString("state");
+                    String workoutType = workoutRequest.optString("type");
+                    LOG.info("Got workoutApp request, state=" + workoutState + ", type=" + workoutType);
+                    JSONObject workoutResponse = WorkoutRequestHandler.handleRequest(getContext(), requestId, workoutRequest);
+                    if (workoutResponse.length() > 0) {
+                        JSONObject responseObject = new JSONObject()
+                            .put("res", new JSONObject()
+                                .put("id", requestId)
+                                .put("set", workoutResponse)
+                            );
+                        queueWrite(new JsonPutRequest(responseObject, this));
+                    }
+                } else if (request.optString("custom_menu").equals("request_config")) {
+                    PackageManager manager = getContext().getPackageManager();
+                    try{
+                        // only show toast when companion app is installed
+                        manager.getApplicationInfo("d.d.hrmenucompanion", 0);
+                        GB.toast(getContext().getString(R.string.info_fossil_rebuild_watchface_custom_menu), Toast.LENGTH_SHORT, GB.INFO);
+                    }catch (PackageManager.NameNotFoundException e){
+                    }
+                } else {
+                    LOG.warn("Unhandled request from watch: " + requestJson.toString());
+                }
+            } catch (JSONException e) {
+                LOG.error("Error while handling received characteristic", e);
+            }
+        }
+    }
+
+    @Override
+    public void onFindDevice(boolean start) {
+        super.onFindDevice(start);
+
+        boolean versionSupportsConfirmation = getCleanFWVersion().greaterOrEqualThan(new Version("2.22"));
+
+        if(!versionSupportsConfirmation){
+            GB.toast("not supported in this version", Toast.LENGTH_SHORT, GB.ERROR);
+            return;
+        }
+
+        if (start) {
+            queueWrite(new ConfirmOnDeviceRequest());
+        }
+    }
+
+    private void handleDeleteNotification(byte[] value) {
+        ByteBuffer buffer = ByteBuffer.wrap(value);
+        buffer.order(ByteOrder.LITTLE_ENDIAN);
+
+        int notifId = buffer.getInt(3);
+
+        Intent deleteIntent = new Intent(NotificationListener.ACTION_DISMISS);
+        deleteIntent.putExtra("handle", (long) notifId);
+        LocalBroadcastManager.getInstance(getContext()).sendBroadcast(deleteIntent);
+    }
+
+    private void handleCallRequest(byte[] value) {
+        SharedPreferences prefs = getDeviceSpecificPreferences();
+        String rejectMethodPref = prefs.getString(DeviceSettingsPreferenceConst.PREF_CALL_REJECT_METHOD, "reject");
+        GBDeviceEventCallControl.Event rejectMethod = GBDeviceEventCallControl.Event.REJECT;
+        if (rejectMethodPref.equals("ignore")) rejectMethod = GBDeviceEventCallControl.Event.IGNORE;
+
+        boolean acceptCall = value[7] == (byte) 0x00;
+        queueWrite(new PlayCallNotificationRequest("", false, false, 0,this));
+
+        GBDeviceEventCallControl callControlEvent = new GBDeviceEventCallControl();
+        callControlEvent.event = acceptCall ? GBDeviceEventCallControl.Event.START : rejectMethod;
+
+        getDeviceSupport().evaluateGBDeviceEvent(callControlEvent);
+    }
+
+    private void handleQuickReplyRequest(byte[] value) {
+        if (currentCallSpec == null) {
+            return;
+        }
+        String[] quickReplies = getQuickReplies();
+        byte callId = value[3];
+        byte replyChoice = value[8];
+        if (replyChoice >= quickReplies.length) {
+            return;
+        }
+        GBDeviceEventNotificationControl devEvtNotificationControl = new GBDeviceEventNotificationControl();
+        devEvtNotificationControl.handle = callId;
+        devEvtNotificationControl.phoneNumber = currentCallSpec.number;
+        devEvtNotificationControl.reply = quickReplies[replyChoice];
+        devEvtNotificationControl.event = GBDeviceEventNotificationControl.Event.REPLY;
+        getDeviceSupport().evaluateGBDeviceEvent(devEvtNotificationControl);
+        queueWrite(new QuickReplyConfirmationPutRequest(callId));
+    }
+
+    private void handleMusicRequest(byte[] value) {
+        byte command = value[3];
+        LOG.info("got music command: " + command);
+        MUSIC_WATCH_REQUEST request = MUSIC_WATCH_REQUEST.fromCommandByte(command);
+
+        GBDeviceEventMusicControl deviceEventMusicControl = new GBDeviceEventMusicControl();
+        deviceEventMusicControl.event = GBDeviceEventMusicControl.Event.PLAY;
+
+        // TODO add skipping/seeking
+
+        switch (request) {
+            case MUSIC_REQUEST_PLAY_PAUSE: {
+                queueWrite(new MusicControlRequest(MUSIC_PHONE_REQUEST.MUSIC_REQUEST_PLAY_PAUSE));
+                deviceEventMusicControl.event = GBDeviceEventMusicControl.Event.PLAYPAUSE;
+                break;
+            }
+            case MUSIC_REQUEST_NEXT: {
+                queueWrite(new MusicControlRequest(MUSIC_PHONE_REQUEST.MUSIC_REQUEST_NEXT));
+                deviceEventMusicControl.event = GBDeviceEventMusicControl.Event.NEXT;
+                break;
+            }
+            case MUSIC_REQUEST_PREVIOUS: {
+                queueWrite(new MusicControlRequest(MUSIC_PHONE_REQUEST.MUSIC_REQUEST_PREVIOUS));
+                deviceEventMusicControl.event = GBDeviceEventMusicControl.Event.PREVIOUS;
+                break;
+            }
+            case MUSIC_REQUEST_LOUDER: {
+                queueWrite(new MusicControlRequest(MUSIC_PHONE_REQUEST.MUSIC_REQUEST_LOUDER));
+                deviceEventMusicControl.event = GBDeviceEventMusicControl.Event.VOLUMEUP;
+                break;
+            }
+            case MUSIC_REQUEST_QUITER: {
+                queueWrite(new MusicControlRequest(MUSIC_PHONE_REQUEST.MUSIC_REQUEST_QUITER));
+                deviceEventMusicControl.event = GBDeviceEventMusicControl.Event.VOLUMEDOWN;
+                break;
+            }
+        }
+
+        getDeviceSupport().evaluateGBDeviceEvent(deviceEventMusicControl);
+    }
+
+    @Override
+    public void setCommuteMenuMessage(String message, boolean finished) {
+        queueWrite(new SetCommuteMenuMessage(message, finished, this));
+    }
+
+    public byte getJsonIndex() {
+        return jsonIndex++;
+    }
+
+    private Version getCleanFWVersion() {
+        return new Version(getDeviceSupport().getDevice().getFirmwareVersion2());
+    }
+
+    public String getInstalledAppNameFromUUID(UUID uuid) {
+        for (ApplicationInformation appInfo : installedApplications) {
+            if (UUID.nameUUIDFromBytes(appInfo.getAppName().getBytes(StandardCharsets.UTF_8)).equals(uuid)) {
+                return appInfo.getAppName();
+            }
+        }
+        if (uuid.equals(UUID.nameUUIDFromBytes("workoutApp".getBytes(StandardCharsets.UTF_8)))) {
+            return "workoutApp";
+        }
+        return null;
+    }
+
+    public void onSetNavigationInfo(NavigationInfoSpec navigationInfoSpec) {
+        SharedPreferences prefs = getDeviceSpecificPreferences();
+        String installedAppsJson = getDeviceSupport().getDevice().getDeviceInfo("INSTALLED_APPS").getDetails();
+        if (installedAppsJson == null || !installedAppsJson.contains("navigationApp")) {
+            if (!notifiedAboutMissingNavigationApp) {
+                notifiedAboutMissingNavigationApp = true;
+                NotificationCompat.Builder ncomp = new NotificationCompat.Builder(getContext(), NOTIFICATION_CHANNEL_ID)
+                        .setContentTitle(getContext().getString(R.string.fossil_hr_nav_app_not_installed_notify_title))
+                        .setContentText(getContext().getString(R.string.fossil_hr_nav_app_not_installed_notify_text))
+                        .setTicker(getContext().getString(R.string.fossil_hr_nav_app_not_installed_notify_text))
+                        .setSmallIcon(R.drawable.ic_notification)
+                        .setAutoCancel(true);
+                GB.notify((int) System.currentTimeMillis(), ncomp.build(), getContext());
+                GB.toast(getContext().getString(R.string.fossil_hr_nav_app_not_installed_notify_text), Toast.LENGTH_LONG, GB.WARN);
+            }
+            return;
+        }
+        try {
+            JSONObject navJson = new JSONObject()
+                    .put("push", new JSONObject()
+                            .put("set", new JSONObject()
+                                    .put("navigationApp._.config.info", new JSONObject()
+                                            .put("distance", navigationInfoSpec.distanceToTurn)
+                                            .put("eta", navigationInfoSpec.ETA)
+                                            .put("instruction", navigationInfoSpec.instruction)
+                                            .put("nextAction", navigationInfoSpec.nextAction)
+                                            .put("autoFg", prefs.getBoolean("fossil_hr_nav_auto_foreground", true))
+                                            .put("vibrate", prefs.getBoolean("fossil_hr_nav_vibrate", true))
+                                    )
+                            )
+                    );
+
+            queueWrite(new JsonPutRequest(navJson, this));
+        } catch (JSONException e) {
+            LOG.error("JSON exception: ", e);
+        }
+    }
+
+    private void updateBuiltinAppsInCache() {
+        FossilFileReader fileReader;
+        try {
+            fileReader = new FossilFileReader(FileUtils.getUriForAsset("fossil_hr/navigationApp.wapp", getContext()), getContext());
+            if (FossilHRInstallHandler.saveAppInCache(fileReader, fileReader.getBackground(), fileReader.getPreview(), getDeviceSupport().getDevice().getDeviceCoordinator(), getContext())) {
+                LOG.info("Successfully copied navigationApp for Fossil Hybrids to cache");
+            }
+        } catch (IOException e) {
+            LOG.warn("Could not copy navigationApp to cache", e);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/adapter/misfit/MisfitWatchAdapter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/adapter/misfit/MisfitWatchAdapter.java
new file mode 100644
index 0000000..7c14fa7
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/adapter/misfit/MisfitWatchAdapter.java
@@ -0,0 +1,510 @@
+/*  Copyright (C) 2019-2024 Andreas Shimokawa, Carsten Pfeiffer, Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.misfit;
+
+import static nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.QHybridSupport.ITEM_ACTIVITY_POINT;
+import static nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.QHybridSupport.ITEM_STEP_COUNT;
+import static nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.QHybridSupport.ITEM_STEP_GOAL;
+import static nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.QHybridSupport.ITEM_VIBRATION_STRENGTH;
+import static nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.QHybridSupport.QHYBRID_EVENT_BUTTON_PRESS;
+import static nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.QHybridSupport.QHYBRID_EVENT_FILE_UPLOADED;
+
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.content.Intent;
+import android.net.Uri;
+import android.util.SparseArray;
+import android.widget.Toast;
+
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.NoSuchElementException;
+import java.util.Queue;
+import java.util.TimeZone;
+
+import nodomain.freeyourgadget.gadgetbridge.BuildConfig;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
+import nodomain.freeyourgadget.gadgetbridge.devices.DeviceManager;
+import nodomain.freeyourgadget.gadgetbridge.devices.qhybrid.NotificationConfiguration;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
+import nodomain.freeyourgadget.gadgetbridge.model.BatteryState;
+import nodomain.freeyourgadget.gadgetbridge.model.GenericItem;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.QHybridSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.WatchAdapter;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.Request;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.ActivityPointGetRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.AnimationRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.BatteryLevelRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.DownloadFileRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.EraseFileRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.FileRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.GetCountdownSettingsRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.GetCurrentStepCountRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.GetStepGoalRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.GetVibrationStrengthRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.GoalTrackingGetRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.ListFilesRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.MoveHandsRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.OTAEnterRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.OTAEraseRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.PlayNotificationRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.ReleaseHandsControlRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.RequestHandControlRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.SaveCalibrationRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.SetCurrentStepCountRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.SetStepGoalRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.SetTimeRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.SetVibrationStrengthRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.UploadFileRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.VibrateRequest;
+import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class MisfitWatchAdapter extends WatchAdapter {
+    private int lastButtonIndex = -1;
+    private final SparseArray<Request> responseFilters = new SparseArray<>();
+
+    private UploadFileRequest uploadFileRequest;
+    private Request fileRequest = null;
+
+    private Queue<Request> requestQueue = new ArrayDeque<>();
+
+    private Logger logger = LoggerFactory.getLogger(getClass());
+
+    public MisfitWatchAdapter(QHybridSupport deviceSupport) {
+        super(deviceSupport);
+
+        fillResponseList();
+    }
+
+    @Override
+    public void initialize() {
+        requestQueue.add(new GetStepGoalRequest());
+        requestQueue.add(new GetVibrationStrengthRequest());
+        requestQueue.add(new ActivityPointGetRequest());
+        requestQueue.add(prepareSetTimeRequest());
+        requestQueue.add(new AnimationRequest());
+        requestQueue.add(new SetCurrentStepCountRequest((int) (999999 * getDeviceSupport().calculateNotificationProgress())));
+
+        queueWrite(new GetCurrentStepCountRequest());
+
+        getDeviceSupport().getDevice().setState(GBDevice.State.INITIALIZED);
+        getDeviceSupport().getDevice().sendDeviceUpdateIntent(getContext());
+    }
+
+
+    private SetTimeRequest prepareSetTimeRequest() {
+        long millis = System.currentTimeMillis();
+        TimeZone zone = new GregorianCalendar().getTimeZone();
+        return new SetTimeRequest(
+                (int) (millis / 1000 + getDeviceSupport().getTimeOffset() * 60),
+                (short) (millis % 1000),
+                (short) ((zone.getRawOffset() + zone.getDSTSavings()) / 60000));
+    }
+
+
+    @Override
+    public void playPairingAnimation() {
+        queueWrite(new AnimationRequest());
+    }
+
+    @Override
+    public void playNotification(NotificationConfiguration config) {
+        queueWrite(new PlayNotificationRequest(
+                config.getVibration(),
+                config.getHour(),
+                config.getMin(),
+                config.getSubEye()
+        ));
+    }
+
+    @Override
+    public void setTime() {
+        queueWrite(prepareSetTimeRequest());
+    }
+
+    @Override
+    public boolean onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
+        GBDevice gbDevice = getDeviceSupport().getDevice();
+        switch (characteristic.getUuid().toString()) {
+            case "3dda0004-957f-7d4a-34a6-74696673696d":
+            case "3dda0003-957f-7d4a-34a6-74696673696d": {
+                return handleFileDownloadCharacteristic(characteristic);
+            }
+            case "3dda0007-957f-7d4a-34a6-74696673696d": {
+                return handleFileUploadCharacteristic(characteristic);
+            }
+            case "3dda0002-957f-7d4a-34a6-74696673696d": {
+                return handleBasicCharacteristic(characteristic);
+            }
+            case "3dda0006-957f-7d4a-34a6-74696673696d": {
+                return handleButtonCharacteristic(characteristic);
+            }
+            case "00002a19-0000-1000-8000-00805f9b34fb": {
+                short level = characteristic.getValue()[0];
+                gbDevice.setBatteryLevel(level);
+
+                GBDeviceEventBatteryInfo batteryInfo = new GBDeviceEventBatteryInfo();
+                batteryInfo.level = gbDevice.getBatteryLevel();
+                batteryInfo.state = BatteryState.BATTERY_NORMAL;
+                getDeviceSupport().handleGBDeviceEvent(batteryInfo);
+                break;
+            }
+            default: {
+                log("unknown shit on " + characteristic.getUuid().toString() + ":  " + arrayToString(characteristic.getValue()));
+                try {
+                    File charLog = FileUtils.getExternalFile("qFiles/charLog.txt");
+                    try (FileOutputStream fos = new FileOutputStream(charLog, true)) {
+                        fos.write((new Date().toString() + ": " + characteristic.getUuid().toString() + ": " + arrayToString(characteristic.getValue())).getBytes());
+                    }
+                } catch (IOException e) {
+                    GB.log("error", GB.ERROR, e);
+                }
+                break;
+            }
+        }
+        return getDeviceSupport().onCharacteristicChanged(gatt, characteristic);
+    }
+
+    private void fillResponseList() {
+        Class<? extends Request>[] classes = new Class[]{
+                BatteryLevelRequest.class,
+                GetStepGoalRequest.class,
+                GetVibrationStrengthRequest.class,
+                GetCurrentStepCountRequest.class,
+                OTAEnterRequest.class,
+                GoalTrackingGetRequest.class,
+                ActivityPointGetRequest.class,
+                GetCountdownSettingsRequest.class
+        };
+        for (Class<? extends Request> c : classes) {
+            try {
+                c.getSuperclass().getDeclaredMethod("handleResponse", BluetoothGattCharacteristic.class);
+                Request object = c.newInstance();
+                byte[] sequence = object.getStartSequence();
+                if (sequence.length > 1) {
+                    responseFilters.put((int) object.getStartSequence()[1], object);
+                    log("response filter " + object.getStartSequence()[1] + ": " + c.getSimpleName());
+                }
+            } catch (NoSuchMethodException | IllegalAccessException | InstantiationException e) {
+                log("skipping class " + c.getName());
+            }
+        }
+    }
+
+    private boolean handleBasicCharacteristic(BluetoothGattCharacteristic characteristic) {
+        byte[] values = characteristic.getValue();
+        Request request = resolveAnswer(characteristic);
+        GBDevice gbDevice = getDeviceSupport().getDevice();
+
+        if (request == null) {
+            StringBuilder valueString = new StringBuilder(String.valueOf(values[0]));
+            for (int i = 1; i < characteristic.getValue().length; i++) {
+                valueString.append(", ").append(values[i]);
+            }
+            log("unable to resolve " + characteristic.getUuid().toString() + ": " + valueString);
+            return true;
+        }
+        log("response: " + request.getClass().getSimpleName());
+        request.handleResponse(characteristic);
+
+        if (request instanceof GetStepGoalRequest) {
+            gbDevice.addDeviceInfo(new GenericItem(ITEM_STEP_GOAL, String.valueOf(((GetStepGoalRequest) request).stepGoal)));
+        } else if (request instanceof GetVibrationStrengthRequest) {
+            int strength = ((GetVibrationStrengthRequest) request).strength;
+            gbDevice.addDeviceInfo(new GenericItem(ITEM_VIBRATION_STRENGTH, String.valueOf(strength)));
+        } else if (request instanceof GetCurrentStepCountRequest) {
+            int steps = ((GetCurrentStepCountRequest) request).steps;
+            logger.debug("get current steps: " + steps);
+            try {
+                File file = FileUtils.getExternalFile("qFiles/steps");
+                logger.debug("Writing file " + file.getPath());
+                try (FileOutputStream fos = new FileOutputStream(file, true)) {
+                    fos.write((System.currentTimeMillis() + ": " + steps + "\n").getBytes());
+                }
+                logger.debug("file written.");
+            } catch (Exception e) {
+                GB.log("error", GB.ERROR, e);
+            }
+            gbDevice.addDeviceInfo(new GenericItem(ITEM_STEP_COUNT, String.valueOf(((GetCurrentStepCountRequest) request).steps)));
+        } else if (request instanceof OTAEnterRequest) {
+            if (((OTAEnterRequest) request).success) {
+                fileRequest = new OTAEraseRequest(1024 << 16);
+                queueWrite(fileRequest);
+            }
+        } else if (request instanceof ActivityPointGetRequest) {
+            gbDevice.addDeviceInfo(new GenericItem(ITEM_ACTIVITY_POINT, String.valueOf(((ActivityPointGetRequest) request).activityPoint)));
+        }
+        try {
+            queueWrite(requestQueue.remove());
+        } catch (NoSuchElementException e) {
+        }
+        LocalBroadcastManager.getInstance(getContext()).sendBroadcast(new Intent(DeviceManager.ACTION_DEVICES_CHANGED));
+        return true;
+    }
+
+
+    private Request resolveAnswer(BluetoothGattCharacteristic characteristic) {
+        byte[] values = characteristic.getValue();
+        if (values[0] != 3) return null;
+        return responseFilters.get(values[1]);
+    }
+
+    private boolean handleFileDownloadCharacteristic(BluetoothGattCharacteristic characteristic) {
+        Request request;
+        request = fileRequest;
+        request.handleResponse(characteristic);
+        if (request instanceof ListFilesRequest) {
+            if (((ListFilesRequest) request).completed) {
+                logger.debug("File count: " + ((ListFilesRequest) request).fileCount + "  size: " + ((ListFilesRequest) request).size);
+                if (((ListFilesRequest) request).fileCount == 0) return true;
+                // queueWrite(new DownloadFileRequest((short) (256 + ((ListFilesRequest) request).fileCount)));
+            }
+        } else if (request instanceof DownloadFileRequest) {
+            if (((FileRequest) request).completed) {
+                logger.debug("file " + ((DownloadFileRequest) request).fileHandle + " completed: " + ((DownloadFileRequest) request).size);
+                // backupFile((DownloadFileRequest) request);
+            }
+        } else if (request instanceof EraseFileRequest) {
+            if (((EraseFileRequest) request).fileHandle > 257) {
+                queueWrite(new DownloadFileRequest((short) (((EraseFileRequest) request).fileHandle - 1)));
+            }
+        }
+        return true;
+    }
+
+
+    private boolean handleFileUploadCharacteristic(BluetoothGattCharacteristic characteristic) {
+        if (uploadFileRequest == null) {
+            logger.debug("no uploadFileRequest to handle response");
+            return true;
+        }
+
+        uploadFileRequest.handleResponse(characteristic);
+
+        switch (uploadFileRequest.state) {
+            case ERROR:
+                Intent fileIntent = new Intent(QHYBRID_EVENT_FILE_UPLOADED);
+                fileIntent.putExtra("EXTRA_ERROR", true);
+                LocalBroadcastManager.getInstance(getContext()).sendBroadcast(fileIntent);
+                uploadFileRequest = null;
+                break;
+            case UPLOAD:
+                for (byte[] packet : this.uploadFileRequest.packets) {
+                    new TransactionBuilder("File upload").write(characteristic, packet).queue(getDeviceSupport().getQueue());
+                }
+                break;
+            case UPLOADED:
+                fileIntent = new Intent(QHYBRID_EVENT_FILE_UPLOADED);
+                LocalBroadcastManager.getInstance(getContext()).sendBroadcast(fileIntent);
+                uploadFileRequest = null;
+                break;
+        }
+        return true;
+    }
+
+    private boolean handleButtonCharacteristic(BluetoothGattCharacteristic characteristic) {
+        byte[] value = characteristic.getValue();
+        if (value.length != 11) {
+            logger.debug("wrong button message");
+            return true;
+        }
+        int index = value[6] & 0xFF;
+        int button = value[8] >> 4 & 0xFF;
+
+        if (index != this.lastButtonIndex) {
+            lastButtonIndex = index;
+            logger.debug("Button press on button " + button);
+
+            Intent i = new Intent(QHYBRID_EVENT_BUTTON_PRESS);
+            i.setPackage(BuildConfig.APPLICATION_ID);
+            i.putExtra("BUTTON", button);
+
+            //ByteBuffer buffer = ByteBuffer.allocate(16);
+            //buffer.put(new byte[]{0x01, 0x00, 0x08});
+            //buffer.put(value, 2, 8);
+            //buffer.put(new byte[]{(byte)0xFF, 0x05, 0x00, 0x01, 0x00});
+
+            //FilePutRequest request = new FilePutRequest((short)0, buffer.array());
+            //for(byte[] packet : request.packets){
+            //    new TransactionBuilder("File upload").write(getCharacteristic(UUID.fromString("3dda0007-957f-7d4a-34a6-74696673696d")), packet).queue(getQueue());
+            //}
+
+            getContext().sendBroadcast(i);
+        }
+        return true;
+    }
+
+    private void log(String message){
+        logger.debug(message);
+    }
+
+    public void setActivityHand(double progress) {
+        queueWrite(new SetCurrentStepCountRequest(Math.min((int) (1000000 * progress), 999999)));
+    }
+
+    public void vibrate(PlayNotificationRequest.VibrationType vibration) {
+        queueWrite(new PlayNotificationRequest(vibration, -1, -1));
+    }
+
+    @Override
+    public void vibrateFindMyDevicePattern() {
+        queueWrite(new VibrateRequest(false, (short) 4, (short) 1));
+    }
+
+    @Override
+    public void requestHandsControl() {
+        queueWrite(new RequestHandControlRequest());
+    }
+
+    @Override
+    public void releaseHandsControl() {
+        queueWrite(new ReleaseHandsControlRequest());
+    }
+
+    @Override
+    public void setHands(MoveHandsRequest.MovementConfiguration movement) {
+        queueWrite(new MoveHandsRequest(movement));
+    }
+
+    @Override
+    public void saveCalibration() {
+        queueWrite(new SaveCalibrationRequest());
+    }
+
+    @Override
+    public void setStepGoal(int stepGoal) {
+        queueWrite(new SetStepGoalRequest(stepGoal));
+    }
+
+    @Override
+    public void setVibrationStrength(short strength) {
+        queueWrite(new SetVibrationStrengthRequest(strength));
+    }
+
+    @Override
+    public void syncNotificationSettings() {
+
+    }
+
+    @Override
+    public void onTestNewFunction() {
+
+    }
+
+    @Override
+    public void setTimezoneOffsetMinutes(short offset) {
+        GB.toast("old firmware does't support timezones", Toast.LENGTH_LONG, GB.ERROR);
+    }
+
+    @Override
+    public void onInstallApp(Uri uri) {
+
+    }
+
+    @Override
+    public boolean supportsFindDevice() {
+        return supportsExtendedVibration();
+    }
+
+    @Override
+    public boolean supportsExtendedVibration() {
+        String modelNumber = getDeviceSupport().getDevice().getModel();
+        switch (modelNumber) {
+            case "HW.0.0":
+                return true;
+            case "HL.0.0":
+                return false;
+            case "DN.1.0":
+                return true;
+        }
+        throw new UnsupportedOperationException("Model " + modelNumber + " not supported");
+    }
+
+    @Override
+    public boolean supportsActivityHand() {
+        String modelNumber = getDeviceSupport().getDevice().getModel();
+        switch (modelNumber) {
+            case "HW.0.0":
+                return true;
+            case "HL.0.0":
+                return false;
+            case "DN.1.0":
+                return false;
+        }
+        throw new UnsupportedOperationException("Model " + modelNumber + " not supported");
+    }
+
+    @Override
+    public void onFetchActivityData() {
+        requestQueue.add(new BatteryLevelRequest());
+        requestQueue.add(new GetCurrentStepCountRequest());
+        // requestQueue.add(new ListFilesRequest());
+        queueWrite(new ActivityPointGetRequest());
+        getDeviceSupport().getDevice().unsetBusyTask();
+        GB.updateTransferNotification(null, "", false, 100, getContext());
+        getDeviceSupport().getDevice().sendDeviceUpdateIntent(getContext());
+        GB.signalActivityDataFinish(getDeviceSupport().getDevice());
+    }
+
+    @Override
+    public void onSetAlarms(ArrayList<? extends Alarm> alarms) {
+        GB.toast("alarms not supported with this firmware", Toast.LENGTH_LONG, GB.ERROR);
+        return;
+    }
+
+    @Override
+    public void onSendConfiguration(String config) {
+
+    }
+
+    @Override
+    public void overwriteButtons(String jsonConfigString) {
+        uploadFileRequest = new UploadFileRequest((short) 0x0800, new byte[]{
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x03, (byte) 0x10, (byte) 0x01, (byte) 0x01, (byte) 0x01, (byte) 0x0C, (byte) 0x00, (byte) 0x00, (byte) 0x20, (byte) 0x01, (byte) 0x01, (byte) 0x01, (byte) 0x0C, (byte) 0x00, (byte) 0x00,
+                (byte) 0x30, (byte) 0x01, (byte) 0x01, (byte) 0x01, (byte) 0x0C, (byte) 0x00, (byte) 0x00, (byte) 0x01, (byte) 0x01, (byte) 0x00, (byte) 0x01, (byte) 0x01, (byte) 0x0C, (byte) 0x2E, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01,
+                (byte) 0x00, (byte) 0x06, (byte) 0x00, (byte) 0x01, (byte) 0x01, (byte) 0x01, (byte) 0x03, (byte) 0x00, (byte) 0x02, (byte) 0x01, (byte) 0x0F, (byte) 0x00, (byte) 0x8B, (byte) 0x00, (byte) 0x00, (byte) 0x93, (byte) 0x00, (byte) 0x01,
+                (byte) 0x08, (byte) 0x01, (byte) 0x14, (byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0xFE, (byte) 0x08, (byte) 0x00, (byte) 0x93, (byte) 0x00, (byte) 0x02, (byte) 0x01, (byte) 0x00, (byte) 0xBF, (byte) 0xD5, (byte) 0x54, (byte) 0xD1,
+                (byte) 0x00
+        });
+        queueWrite(uploadFileRequest);
+    }
+
+    private void queueWrite(Request request) {
+        new TransactionBuilder(request.getClass().getSimpleName()).write(getDeviceSupport().getCharacteristic(request.getRequestUUID()), request.getRequestData()).queue(getDeviceSupport().getQueue());
+        // if (request instanceof FileRequest) this.fileRequest = request;
+
+        if (!request.expectsResponse()) {
+            try {
+                queueWrite(requestQueue.remove());
+            } catch (NoSuchElementException e) {
+            }
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/buttonconfig/ConfigFileBuilder.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/buttonconfig/ConfigFileBuilder.java
new file mode 100644
index 0000000..4ad6c5d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/buttonconfig/ConfigFileBuilder.java
@@ -0,0 +1,98 @@
+/*  Copyright (C) 2019-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.buttonconfig;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.zip.CRC32;
+
+public class ConfigFileBuilder {
+    private ConfigPayload[] configs;
+
+    public ConfigFileBuilder(ConfigPayload[] configs) {
+        this.configs = configs;
+    }
+
+    public byte[] build(boolean appendChecksum) {
+        int payloadSize = 0;
+        for (ConfigPayload payload : this.configs) {
+            payloadSize += payload.getData().length;
+        }
+
+        int headerSize = 0;
+        for (ConfigPayload payload : this.configs) {
+            headerSize += payload.getHeader().length + 3; // button + version + null;
+        }
+
+        ByteBuffer buffer = ByteBuffer.allocate(
+                3 // version bytes
+                        + 1 // header count byte
+                        + headerSize
+                        + 1 // payload count byte
+                        + payloadSize
+                        + 1 // customization count byte
+                        + (appendChecksum ? 4 : 0) // checksum
+        );
+        buffer.order(ByteOrder.LITTLE_ENDIAN);
+
+        buffer.put(new byte[]{(byte) 0x01, (byte) 0x00, (byte) 0x00}); // version
+        buffer.put((byte) this.configs.length);
+        int buttonIndex = 0x00;
+        for (ConfigPayload payload : configs) {
+            buffer.put((byte) (buttonIndex += 0x10));
+            buffer.put((byte) 0x01);
+            buffer.put(payload.getHeader());
+            buffer.put((byte) 0x00);
+        }
+
+        ArrayList<ConfigPayload> distinctPayloads = new ArrayList<>(3);
+
+        // distinctPayloads.add(configs[0].getData());
+
+        compareLoop:
+        for (int payloadIndex = 0; payloadIndex < configs.length; payloadIndex++) {
+            for (int compareTo = 0; compareTo < distinctPayloads.size(); compareTo++) {
+                if (configs[payloadIndex].equals(distinctPayloads.get(compareTo))) {
+                    continue compareLoop;
+                }
+            }
+            distinctPayloads.add(configs[payloadIndex]);
+        }
+
+        buffer.put((byte) distinctPayloads.size());
+        for (ConfigPayload payload : distinctPayloads) {
+            buffer.put(payload.getData());
+        }
+
+        buffer.put((byte) 0x00);
+
+        ByteBuffer buffer2 = ByteBuffer.allocate(buffer.position() + (appendChecksum ? 4 : 0));
+        buffer2.order(ByteOrder.LITTLE_ENDIAN);
+        buffer2.put(buffer.array(), 0, buffer.position());
+
+        if (!appendChecksum) return buffer2.array();
+
+        CRC32 crc = new CRC32();
+        crc.update(buffer.array(), 0, buffer.position());
+
+        buffer2.putInt((int) crc.getValue());
+
+        return buffer2.array();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/buttonconfig/ConfigPayload.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/buttonconfig/ConfigPayload.java
new file mode 100644
index 0000000..ea4728a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/buttonconfig/ConfigPayload.java
@@ -0,0 +1,105 @@
+/*  Copyright (C) 2019-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.buttonconfig;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Arrays;
+
+public enum ConfigPayload {
+    FORWARD_TO_PHONE(
+            "forward to phone", 
+            new byte[]{(byte) 0x01, (byte) 0x01, (byte) 0x0C, (byte) 0x00},
+            new byte[]{(byte) 0x01, (byte) 0x00, (byte) 0x01, (byte) 0x01, (byte) 0x0C, (byte) 0x2E, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0x06, (byte) 0x00, (byte) 0x01, (byte) 0x01, (byte) 0x01, (byte) 0x03, (byte) 0x00, (byte) 0x02, (byte) 0x01, (byte) 0x0F, (byte) 0x00, (byte) 0x8B, (byte) 0x00, (byte) 0x00, (byte) 0x93, (byte) 0x00, (byte) 0x01, (byte) 0x08, (byte) 0x01, (byte) 0x14, (byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0xFE, (byte) 0x08, (byte) 0x00, (byte) 0x93, (byte) 0x00, (byte) 0x02, (byte) 0x01, (byte) 0x00, (byte) 0xBF, (byte) 0xD5, (byte) 0x54, (byte) 0xD1,}
+            ),
+    FORWARD_TO_PHONE_MULTI(
+            "forward to phone (multifunction)",
+            new byte[]{(byte) 0x01, (byte) 0x06, (byte) 0x12, (byte) 0x00},
+            new byte[]{(byte) 0x01, (byte) 0x00, (byte) 0x01, (byte) 0x06, (byte) 0x12, (byte) 0x63, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0x06, (byte) 0x00, (byte) 0x01, (byte) 0x01, (byte) 0x01, (byte) 0x03, (byte) 0x00, (byte) 0x05, (byte) 0x01, (byte) 0x1D, (byte) 0x00, (byte) 0x85, (byte) 0x01, (byte) 0xF6, (byte) 0x00, (byte) 0x00, (byte) 0x85, (byte) 0x01, (byte) 0x42, (byte) 0x02, (byte) 0x00, (byte) 0x85, (byte) 0x01, (byte) 0x43, (byte) 0x03, (byte) 0x00, (byte) 0x85, (byte) 0x01, (byte) 0x44, (byte) 0x04, (byte) 0x00, (byte) 0x08, (byte) 0x01, (byte) 0x1E, (byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0x02, (byte) 0x0D, (byte) 0x00, (byte) 0x8C, (byte) 0x01, (byte) 0xCD, (byte) 0x00, (byte) 0x01, (byte) 0x93, (byte) 0x00, (byte) 0x01, (byte) 0x01, (byte) 0x00, (byte) 0x03, (byte) 0x0D, (byte) 0x00, (byte) 0x8C, (byte) 0x01, (byte) 0xB6, (byte) 0x00, (byte) 0x01, (byte) 0x93, (byte) 0x00, (byte) 0x01, (byte) 0x01, (byte) 0x00, (byte) 0x04, (byte) 0x0D, (byte) 0x00, (byte) 0x8C, (byte) 0x01, (byte) 0xB5, (byte) 0x00, (byte) 0x01, (byte) 0x93, (byte) 0x00, (byte) 0x01, (byte) 0x01, (byte) 0x00, (byte) 0xFE, (byte) 0x08, (byte) 0x00, (byte) 0x93, (byte) 0x00, (byte) 0x02, (byte) 0x01, (byte) 0x00, (byte) 0x7B, (byte) 0x56, (byte) 0x4E, (byte) 0x97}
+    ),
+    STOPWATCH(
+            "stopwatch",
+            new byte[]{(byte) 0x02, (byte) 0x01, (byte) 0x20, (byte) 0x01},
+            new byte[]{(byte) 0x01, (byte) 0x00, (byte) 0x02, (byte) 0x01, (byte) 0x20, (byte) 0x20, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01, (byte) 0x01, (byte) 0x07, (byte) 0x00, (byte) 0x03, (byte) 0x00, (byte) 0x00, (byte) 0x07, (byte) 0x01, (byte) 0x00, (byte) 0x01, (byte) 0x01, (byte) 0x08, (byte) 0x00, (byte) 0x92, (byte) 0x00, (byte) 0x01, (byte) 0x01, (byte) 0x00, (byte) 0x0F, (byte) 0xC0, (byte) 0x5F, (byte) 0x2A}
+    ),
+    DATE(
+            "show date",
+            new byte[]{(byte) 0x01, (byte) 0x01, (byte) 0x14, (byte) 0x00},
+            new byte[]{(byte) 0x01 , (byte) 0x00 , (byte) 0x01 , (byte) 0x01 , (byte) 0x14 , (byte) 0x2D , (byte) 0x00 , (byte) 0x00 , (byte) 0x00 , (byte) 0x01 , (byte) 0x00 , (byte) 0x06 , (byte) 0x00 , (byte) 0x02 , (byte) 0x00 , (byte) 0x00 , (byte) 0x07 , (byte) 0x00 , (byte) 0x01 , (byte) 0x01 , (byte) 0x16 , (byte) 0x00 , (byte) 0x89 , (byte) 0x05 , (byte) 0x01 , (byte) 0x07 , (byte) 0xB0 , (byte) 0x00 , (byte) 0x00 , (byte) 0xB0 , (byte) 0x00 , (byte) 0x00 , (byte) 0xB0 , (byte) 0x00 , (byte) 0x00 , (byte) 0x08 , (byte) 0x01 , (byte) 0x50 , (byte) 0x00 , (byte) 0x01 , (byte) 0x00 , (byte) 0xD0 , (byte) 0x89 , (byte) 0xDE , (byte) 0x6E}
+    ),
+    LAST_NOTIFICATION(
+            "show last notification",
+            new byte[]{(byte) 0x01, (byte) 0x01, (byte) 0x18, (byte) 0x00},
+            new byte[]{(byte) 0x01, (byte) 0x00, (byte) 0x01, (byte) 0x01, (byte) 0x18, (byte) 0x2F, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0x08, (byte) 0x00, (byte) 0x04, (byte) 0x00, (byte) 0x00, (byte) 0x07, (byte) 0x02, (byte) 0x01, (byte) 0x00, (byte) 0x01, (byte) 0x01, (byte) 0x16, (byte) 0x00, (byte) 0x89, (byte) 0x05, (byte) 0x01, (byte) 0x07, (byte) 0xB0, (byte) 0x02, (byte) 0x00, (byte) 0xB0, (byte) 0x02, (byte) 0x00, (byte) 0xB0, (byte) 0x02, (byte) 0x00, (byte) 0x08, (byte) 0x01, (byte) 0x50, (byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0x6B, (byte) 0x9D, (byte) 0x55, (byte) 0x3A}
+    ),
+    SECOND_TIMEZONE(
+            "show second timezone",
+            new byte[]{0x01, (byte) 0x01, (byte) 0x16, (byte) 0x00},
+            new byte[]{0x01, (byte) 0x00, (byte) 0x01, (byte) 0x01, (byte) 0x16, (byte) 0x2F, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0x08, (byte) 0x00, (byte) 0x04, (byte) 0x00, (byte) 0x00, (byte) 0x07, (byte) 0x02, (byte) 0x02, (byte) 0x00, (byte) 0x01, (byte) 0x01, (byte) 0x16, (byte) 0x00, (byte) 0x89, (byte) 0x05, (byte) 0x01, (byte) 0x07, (byte) 0xB0, (byte) 0x01, (byte) 0x00, (byte) 0xB0, (byte) 0x01, (byte) 0x00, (byte) 0xB0, (byte) 0x01, (byte) 0x00, (byte) 0x08, (byte) 0x01, (byte) 0x50, (byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0x3D, (byte) 0x07, (byte) 0x28, (byte) 0x01}
+    )
+    /* PLAY_PAUSE(
+            "play/pause music",
+            new byte[]{(byte) 0x01, (byte) 0x06, (byte) 0x12, (byte) 0x00},
+            new byte[]{(byte) 0x01, (byte) 0x00, (byte) 0x01, (byte) 0x06, (byte) 0x12, (byte) 0x63, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0x06, (byte) 0x00, (byte) 0x01, (byte) 0x01, (byte) 0x01, (byte) 0x03, (byte) 0x00, (byte) 0x05, (byte) 0x01, (byte) 0x1D, (byte) 0x00, (byte) 0x85, (byte) 0x01, (byte) 0xF6, (byte) 0x00, (byte) 0x00, (byte) 0x85, (byte) 0x01, (byte) 0x42, (byte) 0x02, (byte) 0x00, (byte) 0x85, (byte) 0x01, (byte) 0x43, (byte) 0x03, (byte) 0x00, (byte) 0x85, (byte) 0x01, (byte) 0x44, (byte) 0x04, (byte) 0x00, (byte) 0x08, (byte) 0x01, (byte) 0x1E, (byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0x02, (byte) 0x0D, (byte) 0x00, (byte) 0x8C, (byte) 0x01, (byte) 0xCD, (byte) 0x00, (byte) 0x01, (byte) 0x93, (byte) 0x00, (byte) 0x01, (byte) 0x01, (byte) 0x00, (byte) 0x03, (byte) 0x0D, (byte) 0x00, (byte) 0x8C, (byte) 0x01, (byte) 0xB6, (byte) 0x00, (byte) 0x01, (byte) 0x93, (byte) 0x00, (byte) 0x01, (byte) 0x01, (byte) 0x00, (byte) 0x04, (byte) 0x0D, (byte) 0x00, (byte) 0x8C, (byte) 0x01, (byte) 0xB5, (byte) 0x00, (byte) 0x01, (byte) 0x93, (byte) 0x00, (byte) 0x01, (byte) 0x01, (byte) 0x00, (byte) 0xFE, (byte) 0x08, (byte) 0x00, (byte) 0x93, (byte) 0x00, (byte) 0x02, (byte) 0x01, (byte) 0x00, (byte) 0x7B, (byte) 0x56, (byte) 0x4E, (byte) 0x97}
+    ),
+    VOLUME_UP(
+            "music volume up",
+            new byte[]{(byte) 0x01, (byte) 0x04, (byte) 0x12, (byte) 0x00},
+            new byte[]{(byte) 0x01, (byte) 0x00, (byte) 0x01, (byte) 0x04, (byte) 0x12, (byte) 0x5E, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0x06, (byte) 0x00, (byte) 0x01, (byte) 0x01, (byte) 0x01, (byte) 0x03, (byte) 0x00, (byte) 0x05, (byte) 0x01, (byte) 0x1D, (byte) 0x00, (byte) 0x85, (byte) 0x01, (byte) 0xF6, (byte) 0x00, (byte) 0x00, (byte) 0x85, (byte) 0x01, (byte) 0x42, (byte) 0x02, (byte) 0x00, (byte) 0x85, (byte) 0x01, (byte) 0x43, (byte) 0x03, (byte) 0x00, (byte) 0x85, (byte) 0x01, (byte) 0x48, (byte) 0x04, (byte) 0x00, (byte) 0x08, (byte) 0x01, (byte) 0x1E, (byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0x02, (byte) 0x0D, (byte) 0x00, (byte) 0x8C, (byte) 0x01, (byte) 0xE9, (byte) 0x00, (byte) 0x01, (byte) 0x93, (byte) 0x00, (byte) 0x01, (byte) 0x01, (byte) 0x00, (byte) 0x03, (byte) 0x0B, (byte) 0x00, (byte) 0x8C, (byte) 0x01, (byte) 0xE9, (byte) 0x00, (byte) 0x00, (byte) 0x93, (byte) 0x00, (byte) 0x01, (byte) 0x04, (byte) 0x0A, (byte) 0x00, (byte) 0x8C, (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x01, (byte) 0x01, (byte) 0x00, (byte) 0xFE, (byte) 0x08, (byte) 0x00, (byte) 0x93, (byte) 0x00, (byte) 0x02, (byte) 0x01, (byte) 0x00, (byte) 0xC6, (byte) 0xB2, (byte) 0xCB, (byte) 0xAC}
+    ),
+    VOLUME_DOWN(
+            "music volume down",
+            new byte[]{(byte) 0x01, (byte) 0x05, (byte) 0x12, (byte) 0x00},
+            new byte[]{(byte) 0x01, (byte) 0x00, (byte) 0x01, (byte) 0x05, (byte) 0x12, (byte) 0x5E, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0x06, (byte) 0x00, (byte) 0x01, (byte) 0x01, (byte) 0x01, (byte) 0x03, (byte) 0x00, (byte) 0x05, (byte) 0x01, (byte) 0x1D, (byte) 0x00, (byte) 0x85, (byte) 0x01, (byte) 0xF6, (byte) 0x00, (byte) 0x00, (byte) 0x85, (byte) 0x01, (byte) 0x42, (byte) 0x02, (byte) 0x00, (byte) 0x85, (byte) 0x01, (byte) 0x43, (byte) 0x03, (byte) 0x00, (byte) 0x85, (byte) 0x01, (byte) 0x48, (byte) 0x04, (byte) 0x00, (byte) 0x08, (byte) 0x01, (byte) 0x1E, (byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0x02, (byte) 0x0D, (byte) 0x00, (byte) 0x8C, (byte) 0x01, (byte) 0xEA, (byte) 0x00, (byte) 0x01, (byte) 0x93, (byte) 0x00, (byte) 0x01, (byte) 0x01, (byte) 0x00, (byte) 0x03, (byte) 0x0B, (byte) 0x00, (byte) 0x8C, (byte) 0x01, (byte) 0xEA, (byte) 0x00, (byte) 0x00, (byte) 0x93, (byte) 0x00, (byte) 0x01, (byte) 0x04, (byte) 0x0A, (byte) 0x00, (byte) 0x8C, (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x01, (byte) 0x01, (byte) 0x00, (byte) 0xFE, (byte) 0x08, (byte) 0x00, (byte) 0x93, (byte) 0x00, (byte) 0x02, (byte) 0x01, (byte) 0x00, (byte) 0xFA, (byte) 0x18, (byte) 0x49, (byte) 0x03}
+    ) */
+    ;
+    private byte[] header, data;
+
+    static public ConfigPayload fromId(short id) throws RuntimeException{
+        for(ConfigPayload payload : ConfigPayload.values()){
+            ByteBuffer buffer = ByteBuffer.wrap(payload.header);
+            buffer.order(ByteOrder.LITTLE_ENDIAN);
+            if(id == buffer.getShort(1)) return payload;
+        }
+
+        throw new RuntimeException("app " + id + " not found");
+    }
+
+    public byte[] getHeader() {
+        return header;
+    }
+
+    public byte[] getData() {
+        return data;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public boolean equals(ConfigPayload p1, ConfigPayload p2){
+        return Arrays.equals(p1.getData(), p2.getData());
+    }
+
+    private String description;
+
+    ConfigPayload(String description, byte[] header, byte[] data) {
+        this.description = description;
+        this.header = header;
+        this.data = data;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/encoder/RLEEncoder.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/encoder/RLEEncoder.java
new file mode 100644
index 0000000..e518f39
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/encoder/RLEEncoder.java
@@ -0,0 +1,50 @@
+/*  Copyright (C) 2020-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.encoder;
+
+import java.io.ByteArrayOutputStream;
+
+public class RLEEncoder {
+    public static byte[] RLEEncode(byte[] data) {
+        ByteArrayOutputStream bos = new ByteArrayOutputStream(data.length * 2);
+
+        int lastByte = data[0];
+        int count = 1;
+        byte currentByte = -1;
+
+        for (int i = 1; i < data.length; i++) {
+            currentByte = data[i];
+
+            if (currentByte != lastByte || count >= 255) {
+                bos.write(count);
+                bos.write(data[i - 1]);
+
+                count = 1;
+                lastByte = data[i];
+            } else {
+                count++;
+            }
+        }
+
+        bos.write(count);
+        bos.write(currentByte);
+
+        byte[] result = bos.toByteArray();
+
+        return result;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/file/FileHandle.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/file/FileHandle.java
new file mode 100644
index 0000000..cf0a992
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/file/FileHandle.java
@@ -0,0 +1,80 @@
+/*  Copyright (C) 2020-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.file;
+
+public enum FileHandle {
+    OTA_FILE(0x00, 0x00),
+    ACTIVITY_FILE(0x01, 0x00),
+    HARDWARE_LOG_FILE(0x02, 0x00),
+    FONT_FILE(0x03, 0x00),
+    MUSIC_INFO(0x04, 0x00),
+    UI_CONTROL(0x05, 0x00),
+    HAND_ACTIONS(0x06, 0x00),
+    SETTINGS_BUTTONS(0x06, 0x00),
+    ASSET_BACKGROUND_IMAGES(0x07, 0x00),
+    ASSET_NOTIFICATION_IMAGES(0x07, 0x01),
+    ASSET_TRANSLATIONS(0x07, 0x02),
+    ASSET_REPLY_IMAGES(0x07, 0x03),
+    CONFIGURATION(0x08, 0x00),
+    NOTIFICATION_PLAY(0x09, 0x00),
+    ALARMS(0x0A, 0x00),
+    DEVICE_INFO(0x0b, 0x00),
+    NOTIFICATION_FILTER(0x0C, 0x00),
+    WATCH_PARAMETERS(0x0E, 0x00),
+    LOOK_UP_TABLE(0x0f, 0x00),
+    RATE(0x10, 0x00),
+    REPLY_MESSAGES(0x13, 0x00),
+    APP_CODE(0x15, 0xFE),
+    ;
+
+    private int handle, subHandle;
+
+    FileHandle(int handle, int subHandle) {
+        this.handle = handle;
+        this.subHandle = subHandle;
+    }
+
+    public static FileHandle fromName(String name){
+        for(FileHandle handle : FileHandle.values()){
+            if(handle.toString().equals(name)){
+                return handle;
+            }
+        }
+        return null;
+    }
+
+    public static FileHandle fromHandle(short handleBytes){
+        for(FileHandle handle : FileHandle.values()){
+            if(handle.getHandle() == handleBytes){
+                return handle;
+            }
+        }
+        return null;
+    }
+
+    public short getHandle(){
+        return (short)((handle << 8) | (subHandle));
+    }
+
+    public byte getMinorHandle(){
+        return (byte) subHandle;
+    }
+
+    public byte getMajorHandle() {
+        return (byte) handle;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/parser/ActivityEntry.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/parser/ActivityEntry.java
new file mode 100644
index 0000000..235225d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/parser/ActivityEntry.java
@@ -0,0 +1,78 @@
+/*  Copyright (C) 2020-2024 Andreas Shimokawa, Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.parser;
+
+import nodomain.freeyourgadget.gadgetbridge.entities.HybridHRActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+
+public class ActivityEntry {
+    public int id;
+    public int heartRate;
+    public int variability, maxVariability;
+    public int calories;
+    public int stepCount;
+    public boolean isActive;
+
+    public int timestamp;
+
+    public int heartRateQuality;
+
+    public WEARING_STATE wearingState;
+
+    public HybridHRActivitySample toDAOActivitySample(long userId, long deviceId) {
+        return new HybridHRActivitySample(
+                timestamp,
+                deviceId,
+                userId,
+                stepCount,
+                calories,
+                variability,
+                maxVariability,
+                heartRateQuality,
+                isActive,
+                wearingState.value,
+                heartRate
+        );
+    }
+
+    public enum WEARING_STATE{
+        WEARING((byte) 0, ActivityKind.NOT_MEASURED),
+        NOT_WEARING((byte) 1, ActivityKind.NOT_WORN),
+        UNKNOWN((byte) 2, ActivityKind.UNKNOWN);
+
+        final byte value;
+        final ActivityKind activityKind;
+
+        WEARING_STATE(byte value, ActivityKind activityKind){
+            this.value = value;
+            this.activityKind = activityKind;
+        }
+
+        public ActivityKind getActivityKind() {
+            return activityKind;
+        }
+
+        static public WEARING_STATE fromValue(byte value){
+            switch (value){
+                case 0: return WEARING_STATE.WEARING;
+                case 1: return WEARING_STATE.NOT_WEARING;
+                case 2: return WEARING_STATE.UNKNOWN;
+                default: throw new RuntimeException("value " + value + " not valid state value");
+            }
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/parser/ActivityFileParser.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/parser/ActivityFileParser.java
new file mode 100644
index 0000000..51356e7
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/parser/ActivityFileParser.java
@@ -0,0 +1,246 @@
+/*  Copyright (C) 2020-2024 Andreas Shimokawa, Benjamin Swartley, Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.parser;
+import nodomain.freeyourgadget.gadgetbridge.entities.HybridHRSpo2Sample;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.Map;
+
+public class ActivityFileParser {
+    
+    // state flags;
+    int heartRateQuality;
+    ActivityEntry.WEARING_STATE wearingState = ActivityEntry.WEARING_STATE.WEARING;
+    int currentTimestamp = 0; // Aligns with `e2 04` from my testing
+    ActivityEntry currentSample = null;
+    int currentId = 1;
+    
+
+    public Map.Entry<ArrayList<ActivityEntry>, ArrayList<HybridHRSpo2Sample>> parseFile(byte[] file) {
+        ByteBuffer buffer = ByteBuffer.wrap(file);
+        buffer.order(ByteOrder.LITTLE_ENDIAN);
+
+        // read file version
+        short version = buffer.getShort(2);
+        if (version != 22) throw new RuntimeException("File version " + version + ", 16 required");
+
+        this.currentTimestamp = buffer.getInt(8); 
+
+        short timeOffsetMinutes = buffer.getShort(12);
+
+        short fileId = buffer.getShort(16);
+
+        buffer.position(52); // Seem to be another 32 bytes after the initial 20 stop 
+
+        ArrayList<ActivityEntry> samples = new ArrayList<>();
+        ArrayList<HybridHRSpo2Sample> spo2Samples = new ArrayList<>();
+        finishCurrentPacket(samples);
+
+
+        while (buffer.position() < buffer.capacity() - 4) {
+            byte next = buffer.get();
+
+            switch (next) {
+                case (byte) 0xCE:
+                    parseWearByte(buffer.get());
+                    byte f1 = buffer.get();
+                    byte f2 = buffer.get();
+
+                    if (f1 == (byte) 0xE2 && f2 == (byte) 0x04) {
+                        int timestamp = buffer.getInt();
+                        buffer.getShort(); // duration
+                        buffer.getShort(); // minutes offset
+                        this.currentTimestamp = timestamp; 
+                    
+                    } else if (f1 == (byte) 0xD3) { // Workout-related 
+                        int hr1 = f2 & 0xFF; // Might be min HR during workout sometimes?
+                        byte[] infoB = new byte[2];
+                        buffer.get(infoB);
+
+                        byte v1 = buffer.get();
+                        byte v2 = buffer.get(buffer.position()); // Could be important for 11 byte packet  
+                        if (v1 == (byte) 0xDF) {
+                            int hr2 = v2 & 0xFF; // Max HR during workout - extra data inside? 
+                            buffer.get();
+                            if (infoB[0] == (byte) 0x08) 
+                                buffer.get(new byte[11]); // ?
+                            
+                            else if (!elemValidFlags(buffer.get(buffer.position() + 4))) 
+                                buffer.get(new byte[3]);
+                            
+
+                        } else if (v1 == (byte) 0xE2 && v2 == (byte) 0x04) {
+                            buffer.get(new byte[13]);
+
+                            if (!elemValidFlags(buffer.get(buffer.position())))
+                                buffer.get(new byte[3]);
+    
+                        } else if (!elemValidFlags(buffer.get(buffer.position() + 4))) 
+                            buffer.get();
+                        
+
+                    } else if (f1 == (byte) 0xCF || f1 == (byte) 0xDF) {
+                        continue; // Not sure what to do with this                                  
+                    
+                    } else if (f1 == (byte) 0xD6) {
+                        HybridHRSpo2Sample spo2Sample = new HybridHRSpo2Sample();
+                        spo2Sample.setTimestamp(currentTimestamp * 1000L);
+                        spo2Sample.setSpo2(buffer.get() & 0xFF);
+                        spo2Samples.add(spo2Sample);
+                        
+                        buffer.get(new byte[3]); // Likely something to do with sample statistics
+
+                    } else if (f1 == (byte) 0xFE && f2 == (byte) 0xFE) {
+                        if (buffer.get(buffer.position()) == (byte) 0xFE) { buffer.get(); } // WHY?
+                                                                     
+                    } else if (elemValidFlags(buffer.get(buffer.position() + 2))) {
+                        parseVariabilityBytes(f1, f2);
+                        int heartRate = buffer.get() & 0xFF; 
+                        int calories = buffer.get() & 0xFF;
+                        boolean isActive = (calories & 0x40) == 0x40; 
+                        calories &= 0x3F; 
+
+                        currentSample.heartRate = heartRate; 
+                        currentSample.calories = calories; 
+                        currentSample.isActive = isActive; 
+                        finishCurrentPacket(samples);
+
+                        continue;
+                    } 
+                    
+                    if (buffer.position() > buffer.capacity() - 4) {
+                        continue;
+                    } 
+
+                    parseVariabilityBytes(buffer.get(), buffer.get());
+                    int heartRate = buffer.get() & 0xFF;
+                    int calories = buffer.get() & 0xFF;
+                    boolean isActive = (calories & 0x40) == 0x40; // upper two bits
+                    calories &= 0x3F; // delete upper two bits
+
+                    currentSample.heartRate = heartRate;
+                    currentSample.calories = calories;
+                    currentSample.isActive = isActive;
+                    finishCurrentPacket(samples);
+
+                    break;
+             
+             case (byte) 0xC2: // Or `c2 X` `ac X` as per #2884
+                    buffer.get(new byte[3]); 
+                    
+                    break;
+
+             case (byte) 0xE2: 
+                    buffer.get(new byte[9]);
+
+                    if (!elemValidFlags(buffer.get(buffer.position()))) {
+                        buffer.get(new byte[6]);
+                    }
+                    
+                    break;
+
+             case (byte) 0xE0:
+                    // Workout Info
+                    for (int i = 0; i < 14; i++) {
+                        buffer.get(); // Attribute # 
+                        byte size = buffer.get();
+                        buffer.get(new byte[size & 0xFF]); // Can eventually use this, nowhere to pass for now 
+                    }
+                    
+                    break;
+
+             case (byte) 0xDD:
+                    buffer.get(new byte[20]); // No idea what this is
+                    
+                    break;
+
+            case (byte) 0xD6:
+                    buffer.get(); // Likely some statistic, notably different from 0xCE 0xD6
+                    HybridHRSpo2Sample spo2Sample = new HybridHRSpo2Sample();
+                    spo2Sample.setTimestamp(currentTimestamp * 1000L);
+                    spo2Sample.setSpo2(buffer.get() & 0xFF);
+                    spo2Samples.add(spo2Sample);
+                    break;
+            
+            case (byte) 0xCB: // Very rare, may even be removed
+            case (byte) 0xCC: // Around 73 or 74
+            case (byte) 0xCF: // Almost always 128 (0x80)
+                    buffer.get();
+                    break;
+
+             default:
+                    ;
+            }
+
+    
+
+        }
+        return Map.entry(samples, spo2Samples);
+    }
+
+    private static boolean elemValidFlags(byte value) {
+    for (byte i : new byte[]{(byte) 0xCE, (byte) 0xDD, (byte) 0xCB, (byte) 0xCC, (byte) 0xCF, (byte) 0xD6, (byte) 0xE2})
+        if (value == i) 
+            return true;
+
+    return false;
+    }
+
+    private void parseVariabilityBytes(byte lower, byte higher){
+        if((lower & 0b0000001) == 0b0000001){
+            currentSample.maxVariability = (higher & 0b00000011) * 25 + 1;
+            currentSample.stepCount = lower & 0b1110;
+            if((lower & 0b10000000) == 0b10000000){
+                int factor = (lower >> 4) & 0b111;
+                currentSample.variability = 512 + factor * 64 + (higher >> 2 & 0b111111);
+            }else {
+                currentSample.variability = lower & 0b01110000;
+                currentSample.variability <<= 2;
+                currentSample.variability |= (higher >> 2) & 0b111111;
+            }
+        }else{
+            currentSample.stepCount = lower & 0b11111110;
+            currentSample.variability = (int) higher * (int) higher * 64;
+            currentSample.maxVariability = 10000;
+        }
+    }
+
+    private void parseWearByte(byte wearArg) {
+        byte wearBits = (byte)((wearArg & 0b00011000) >> 3);
+        if (wearBits == 0) this.wearingState = ActivityEntry.WEARING_STATE.NOT_WEARING;
+        else if (wearBits == 1) this.wearingState = ActivityEntry.WEARING_STATE.WEARING;
+        else this.wearingState = ActivityEntry.WEARING_STATE.UNKNOWN;
+
+        byte heartRateQualityBits = (byte)((wearArg & 0b11100000) >> 5);
+        this.heartRateQuality = heartRateQualityBits;
+    }
+
+    private void finishCurrentPacket(ArrayList<ActivityEntry> samples) {
+        if (currentSample != null) {
+            currentSample.timestamp = currentTimestamp;
+            currentSample.heartRateQuality = this.heartRateQuality;
+            currentSample.wearingState = wearingState;
+            currentTimestamp += 60;
+            samples.add(currentSample);
+            currentSample = null;
+        }
+        this.currentSample = new ActivityEntry();
+        this.currentSample.id = currentId++;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/Request.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/Request.java
new file mode 100644
index 0000000..a7a3d3b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/Request.java
@@ -0,0 +1,81 @@
+/*  Copyright (C) 2019-2024 Andreas Shimokawa, Carsten Pfeiffer, Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests;
+
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.util.Log;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.UUID;
+
+public abstract class Request {
+    protected byte[] data;
+    private Logger logger = (Logger) LoggerFactory.getLogger(getName());
+    //protected ByteBuffer buffer;
+
+    public Request(){
+        this.data = getStartSequence();
+    }
+
+
+    public ByteBuffer createBuffer(){
+        return createBuffer(getPayloadLength());
+    }
+
+    public ByteBuffer createBuffer(int length){
+        ByteBuffer buffer = ByteBuffer.allocate(length);
+        buffer.order(ByteOrder.LITTLE_ENDIAN);
+        buffer.put(getStartSequence());
+        return buffer;
+    }
+
+    public byte[] getRequestData(){
+        return data;
+    }
+
+    public UUID getRequestUUID(){
+        return UUID.fromString("3dda0002-957f-7d4a-34a6-74696673696d");
+    }
+
+    public int getPayloadLength(){ return getStartSequence().length; }
+
+    public abstract byte[] getStartSequence();
+
+    public void handleResponse(BluetoothGattCharacteristic characteristic) {}
+
+    public String getName(){
+        Class thisClass = getClass();
+        while(thisClass.isAnonymousClass()) thisClass = thisClass.getSuperclass();
+        return thisClass.getSimpleName();
+    }
+
+    protected void log(String message){
+        logger.debug(message);
+    }
+
+    public boolean isBasicRequest(){
+        return this.getRequestUUID().toString().equals("3dda0002-957f-7d4a-34a6-74696673696d");
+    }
+
+    public boolean expectsResponse(){
+        return this.data[0] == 1;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/FossilRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/FossilRequest.java
new file mode 100644
index 0000000..5c6bce2
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/FossilRequest.java
@@ -0,0 +1,34 @@
+/*  Copyright (C) 2019-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil;
+
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.Request;
+
+public abstract class FossilRequest extends Request {
+    public abstract boolean isFinished();
+
+    public byte getType(){
+        return getStartSequence()[0];
+    }
+
+    @Override
+    public UUID getRequestUUID() {
+        return UUID.fromString("3dda0003-957f-7d4a-34a6-74696673696d");
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/RequestMtuRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/RequestMtuRequest.java
new file mode 100644
index 0000000..579d037
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/RequestMtuRequest.java
@@ -0,0 +1,48 @@
+/*  Copyright (C) 2019-2024 Andreas Shimokawa, Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil;
+
+import android.os.Build;
+
+import androidx.annotation.RequiresApi;
+
+public class RequestMtuRequest extends FossilRequest {
+    private int mtu;
+    private boolean finished = false;
+
+    public RequestMtuRequest(int mtu) {
+        this.mtu = mtu;
+    }
+
+    public int getMtu() {
+        return mtu;
+    }
+
+    @Override
+    public boolean isFinished() {
+        return finished;
+    }
+
+    public void setFinished(boolean finished) {
+        this.finished = finished;
+    }
+
+    @Override
+    public byte[] getStartSequence() {
+        return new byte[0];
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/SetDeviceStateRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/SetDeviceStateRequest.java
new file mode 100644
index 0000000..a3938c0
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/SetDeviceStateRequest.java
@@ -0,0 +1,41 @@
+/*  Copyright (C) 2019-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil;
+
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class SetDeviceStateRequest extends FossilRequest {
+    private GBDevice.State deviceState;
+
+    public SetDeviceStateRequest(GBDevice.State deviceState) {
+        this.deviceState = deviceState;
+    }
+
+    public GBDevice.State getDeviceState() {
+        return deviceState;
+    }
+
+    @Override
+    public boolean isFinished() {
+        return true;
+    }
+
+    @Override
+    public byte[] getStartSequence() {
+        return new byte[0];
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/alarm/Alarm.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/alarm/Alarm.java
new file mode 100644
index 0000000..e5495e1
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/alarm/Alarm.java
@@ -0,0 +1,121 @@
+/*  Copyright (C) 2019-2024 Daniel Dakhno, Taavi Eom盲e
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.alarm;
+
+import androidx.annotation.NonNull;
+
+public class Alarm {
+    public final int WEEKDAY_SUNDAY = 0;
+    public final int WEEKDAY_MONDAY = 1;
+    public final int WEEKDAY_TUESDAY = 2;
+    public final int WEEKDAY_THURSDAY = 3;
+    public final int WEEKDAY_WEDNESDAY = 4;
+    public final int WEEKDAY_FRIDAY = 5;
+    public final int WEEKDAY_SATURDAY = 6;
+    private byte days = 0;
+    private final byte minute;
+    private final byte hour;
+    private final boolean repeat;
+    private String title, message;
+
+    public Alarm(byte minute, byte hour, String title, String message) {
+        this.minute = minute;
+        this.hour = hour;
+        this.repeat = false;
+
+        this.title = title;
+        this.message = message;
+    }
+
+    public Alarm(byte minute, byte hour, byte days, String title, String message){
+        this.minute = minute;
+        this.hour = hour;
+        this.repeat = true;
+        this.days = days;
+
+        this.title = title;
+        this.message = message;
+    }
+
+    public void setDayEnabled(int day, boolean enabled){
+        if(enabled) this.days |= 1 << day;
+        else this.days &= ~(1 << day);
+    }
+
+    public byte[] getData(){
+        byte first = (byte) 0xFF;
+        if(repeat){
+            first = (byte) (0x80 | this.days);
+        }
+
+        byte second = (byte) this.minute;
+
+        if(repeat) second |= 0x80;
+
+        byte third = this.hour;
+
+        return new byte[]{first, second, third};
+    }
+
+    static public Alarm fromBytes(byte[] bytes){
+        if(bytes.length != 3) throw new RuntimeException("alarm bytes length must be 3");
+
+        byte days = bytes[0];
+
+        byte minutes = (byte)(bytes[1] & 0b01111111);
+        boolean repeat = (bytes[1] & 0x80) == 0x80;
+
+        if(repeat) {
+            return new Alarm(minutes, bytes[2], days, "some title", "i dunno this should't happen");
+        }
+        return new Alarm(minutes, bytes[2], "some title", "i dunno this should't happen");
+    }
+
+    public String getTitle() {
+        return title;
+    }
+
+    public void setTitle(String title) {
+        this.title = title;
+    }
+
+    public String getMessage() {
+        return message;
+    }
+
+    public void setMessage(String message) {
+        this.message = message;
+    }
+
+    @NonNull
+    @Override
+    public String toString() {
+        StringBuilder description = new StringBuilder(this.hour + ":" + this.minute + "  ");
+        if(repeat){
+            String[] dayNames = new String[]{"sunday", "monday", "tuesday", "thursday", "wednesday", "friday", "saturday"};
+            for(int i = WEEKDAY_SUNDAY; i <= WEEKDAY_SATURDAY; i++){
+                if((days & 1 << i) != 0){
+                    description.append(dayNames[i]).append(" ");
+                }
+            }
+        }else{
+            description.append("not repeating");
+        }
+
+        return description.toString();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/alarm/AlarmsGetRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/alarm/AlarmsGetRequest.java
new file mode 100644
index 0000000..28c831d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/alarm/AlarmsGetRequest.java
@@ -0,0 +1,56 @@
+/*  Copyright (C) 2019-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.alarm;
+
+import android.widget.Toast;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil.FossilWatchAdapter;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.file.FileHandle;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.file.FileGetRequest;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class AlarmsGetRequest extends FileGetRequest {
+    public AlarmsGetRequest(FossilWatchAdapter adapter) {
+        super(FileHandle.ALARMS, adapter);
+    }
+
+    @Override
+    public void handleFileData(byte[] fileData) {
+        ByteBuffer buffer = ByteBuffer.wrap(fileData);
+        buffer.order(ByteOrder.LITTLE_ENDIAN);
+
+        int length = fileData.length / 3;
+        Alarm[] alarms = new Alarm[length];
+
+        for (int i = 0; i < length; i++){
+            byte[] alarmBytes = new byte[]{
+                    buffer.get(),
+                    buffer.get(),
+                    buffer.get()
+            };
+            alarms[i] = Alarm.fromBytes(alarmBytes);
+        }
+
+        this.handleAlarms(alarms);
+    }
+
+    public void handleAlarms(Alarm[] alarms){
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/alarm/AlarmsSetRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/alarm/AlarmsSetRequest.java
new file mode 100644
index 0000000..1f42bc7
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/alarm/AlarmsSetRequest.java
@@ -0,0 +1,88 @@
+/*  Copyright (C) 2019-2024 Arjan Schrijver, Daniel Dakhno, Taavi Eom盲e
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.alarm;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil.FossilWatchAdapter;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.file.FileHandle;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.file.FilePutRequest;
+import nodomain.freeyourgadget.gadgetbridge.util.Version;
+
+public class AlarmsSetRequest extends FilePutRequest {
+    public AlarmsSetRequest(Alarm[] alarms, FossilWatchAdapter adapter) {
+        super(FileHandle.ALARMS, createFileFromAlarms(alarms, adapter.getSupportedFileVersion(FileHandle.ALARMS)), adapter);
+    }
+
+    static public byte[] createFileFromAlarms(Alarm[] alarms, short fileFormat) {
+        ByteBuffer buffer;
+        boolean newFormat = fileFormat == 0x03;
+        if (!newFormat) {
+            buffer = ByteBuffer.allocate(alarms.length * 3);
+            for (Alarm alarm : alarms) buffer.put(alarm.getData());
+        } else {
+            int sizeWhole = 17 * alarms.length;
+            for(Alarm alarm : alarms){
+                String label = alarm.getTitle();
+                if (label == null || label.isEmpty()) {
+                    label = "---";
+                }
+                label = label.substring(0, Math.min(label.length(), 15));
+                alarm.setTitle(label);
+
+                String message = alarm.getMessage();
+                if (message == null || message.isEmpty()) {
+                    message = "---";
+                }
+                message = message.substring(0, Math.min(message.length(), 50));
+                alarm.setMessage(message);
+
+                sizeWhole += label.getBytes().length + message.getBytes().length;
+            }
+            buffer = ByteBuffer.allocate(sizeWhole); // 4 for overall length
+            buffer.order(ByteOrder.LITTLE_ENDIAN);
+
+            for (Alarm alarm : alarms) {
+                String label = alarm.getTitle();
+                String message = alarm.getMessage();
+                int alarmSize = 17 + label.getBytes().length + message.getBytes().length;
+
+                buffer.put((byte) 0x00); // No information why
+                buffer.putShort((short) (alarmSize - 3)); // Alarm size, 0 above and this does not count
+                buffer.put((byte) 0x00); // Probably entry id time data
+                buffer.putShort((short) 3); // Probably entry length
+                buffer.put(alarm.getData());
+
+                buffer.put((byte) 0x01); // Another entry id label
+                buffer.putShort((short) (label.getBytes().length + 1));  // Entry length
+                buffer.put(label.getBytes());
+                buffer.put((byte) 0x00); // Null terminator
+
+                buffer.put((byte) 0x02); // Entry ID subtext
+                buffer.putShort((short) (message.getBytes().length + 1)); // Entry length
+                buffer.put(message.getBytes());
+                buffer.put((byte) 0x00); // Null terminator
+            }
+        }
+
+        return buffer.array();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/button/ButtonConfigurationGetRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/button/ButtonConfigurationGetRequest.java
new file mode 100644
index 0000000..46863ff
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/button/ButtonConfigurationGetRequest.java
@@ -0,0 +1,63 @@
+/*  Copyright (C) 2019-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.button;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil.FossilWatchAdapter;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.buttonconfig.ConfigPayload;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.file.FileHandle;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.file.FileGetRequest;
+
+public class ButtonConfigurationGetRequest extends FileGetRequest {
+    public ButtonConfigurationGetRequest(FossilWatchAdapter adapter) {
+        super(FileHandle.SETTINGS_BUTTONS, adapter);
+    }
+
+    @Override
+    public void handleFileData(byte[] fileData) {
+        log("fileData");
+
+        ByteBuffer buffer = ByteBuffer.wrap(fileData);
+        buffer.order(ByteOrder.LITTLE_ENDIAN);
+
+        byte count = buffer.get(3);
+
+        ConfigPayload[] configs = new ConfigPayload[count];
+
+        buffer.position(4);
+        for(int i = 0; i < count; i++){
+            int buttonIndex = buffer.get() >> 4;
+            int entryCount = buffer.get();
+            buffer.get();
+            short appId = buffer.getShort();
+
+            buffer.position(buffer.position() + entryCount * 5 - 3);
+
+            try {
+                configs[buttonIndex - 1] = ConfigPayload.fromId(appId);
+            }catch (RuntimeException e){
+                configs[buttonIndex - 1] =  null;
+            }
+        }
+
+        this.onConfigurationsGet(configs);
+    }
+
+    public void onConfigurationsGet(ConfigPayload[] configs){}
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/configuration/ConfigurationGetRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/configuration/ConfigurationGetRequest.java
new file mode 100644
index 0000000..7b35d72
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/configuration/ConfigurationGetRequest.java
@@ -0,0 +1,54 @@
+/*  Copyright (C) 2019-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.configuration;
+
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.GenericItem;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.QHybridSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil.FossilWatchAdapter;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.file.FileHandle;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.file.FileGetRequest;
+
+public class ConfigurationGetRequest extends FileGetRequest {
+    public ConfigurationGetRequest(FossilWatchAdapter adapter) {
+        super(FileHandle.CONFIGURATION, adapter);
+    }
+
+    @Override
+    public void handleFileData(byte[] fileData) {
+        GBDevice device = getAdapter().getDeviceSupport().getDevice();
+
+        ConfigurationPutRequest.ConfigItem[] items = ConfigurationPutRequest.parsePayload(fileData);
+        for(ConfigurationPutRequest.ConfigItem item : items){
+            if(item instanceof ConfigurationPutRequest.VibrationStrengthConfigItem){
+                device.addDeviceInfo(new GenericItem(QHybridSupport.ITEM_VIBRATION_STRENGTH, String.valueOf(((ConfigurationPutRequest.VibrationStrengthConfigItem) item).getValue())));
+            }else if(item instanceof ConfigurationPutRequest.DailyStepGoalConfigItem){
+                device.addDeviceInfo(new GenericItem(QHybridSupport.ITEM_STEP_GOAL, String.valueOf(((ConfigurationPutRequest.DailyStepGoalConfigItem) item).getValue())));
+            }else if(item instanceof ConfigurationPutRequest.CurrentStepCountConfigItem){
+                device.addDeviceInfo(new GenericItem(QHybridSupport.ITEM_STEP_COUNT, String.valueOf(((ConfigurationPutRequest.CurrentStepCountConfigItem) item).getValue())));
+            }else if(item instanceof ConfigurationPutRequest.TimezoneOffsetConfigItem) {
+                device.addDeviceInfo(new GenericItem(QHybridSupport.ITEM_TIMEZONE_OFFSET, String.valueOf(((ConfigurationPutRequest.TimezoneOffsetConfigItem) item).getValue())));
+            }
+        }
+
+        device.sendDeviceUpdateIntent(getAdapter().getContext());
+
+        handleConfigurationLoaded();
+    }
+
+    public void handleConfigurationLoaded(){}
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/configuration/ConfigurationPutRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/configuration/ConfigurationPutRequest.java
new file mode 100644
index 0000000..9faaeb0
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/configuration/ConfigurationPutRequest.java
@@ -0,0 +1,529 @@
+/*  Copyright (C) 2019-2024 Andreas Shimokawa, Arjan Schrijver, Daniel Dakhno,
+    Taavi Eom盲e
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.configuration;
+
+import androidx.annotation.NonNull;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil.FossilWatchAdapter;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.file.FileHandle;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.file.FilePutRequest;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class ConfigurationPutRequest extends FilePutRequest {
+    private static final HashMap<Short, Class<? extends ConfigItem>> itemsById = new HashMap<>();
+
+    static {
+        itemsById.put((short) 0x02, CurrentStepCountConfigItem.class);
+        itemsById.put((short) 0x03, DailyStepGoalConfigItem.class);
+        itemsById.put((short) 0x09, InactivityWarningItem.class);
+        itemsById.put((short) 0x0A, VibrationStrengthConfigItem.class);
+        itemsById.put((short) 0x0C, TimeConfigItem.class);
+        itemsById.put((short) 0x0D, BatteryConfigItem.class);
+        itemsById.put((short) 0x0E, HeartRateMeasurementModeItem.class);
+        itemsById.put((short) 0x10, UnitsConfigItem.class);
+        itemsById.put((short) 0x14, FitnessConfigItem.class);
+    }
+
+    public static ConfigItem[] parsePayload(byte[] data) {
+        ByteBuffer buffer = ByteBuffer.wrap(data);
+        buffer.order(ByteOrder.LITTLE_ENDIAN);
+
+        ArrayList<ConfigItem> configItems = new ArrayList<>();
+
+        while (buffer.hasRemaining()) {
+            short id = buffer.getShort();
+            byte length = buffer.get();
+            byte[] payload = new byte[length];
+
+            for (int i = 0; i < length; i++) {
+                payload[i] = buffer.get();
+            }
+
+            Class<? extends ConfigItem> configClass = itemsById.get(id);
+            if (configClass == null) {
+                continue;
+            }
+
+            ConfigItem item;
+            try {
+                item = configClass.newInstance();
+            } catch (IllegalAccessException | InstantiationException e) {
+                GB.log("error", GB.ERROR, e);
+                continue;
+            }
+            item.parseData(payload);
+
+            configItems.add(item);
+        }
+
+        return configItems.toArray(new ConfigItem[0]);
+    }
+
+    public ConfigurationPutRequest(ConfigItem item, FossilWatchAdapter adapter) {
+        super(FileHandle.CONFIGURATION, createFileContent(new ConfigItem[]{item}), adapter);
+    }
+
+    public ConfigurationPutRequest(ConfigItem[] items, FossilWatchAdapter adapter) {
+        super(FileHandle.CONFIGURATION, createFileContent(items), adapter);
+    }
+
+    private static byte[] createFileContent(ConfigItem[] items) {
+        int overallSize = 0;
+        for (ConfigItem item : items) {
+            overallSize += item.getItemSize() + 3;
+        }
+        ByteBuffer buffer = ByteBuffer.allocate(overallSize);
+        buffer.order(ByteOrder.LITTLE_ENDIAN);
+        for (ConfigItem item : items) {
+            buffer.putShort(item.getId());
+            buffer.put((byte) item.getItemSize());
+            buffer.put(item.getContent());
+        }
+
+        return buffer.array();
+    }
+
+    public static abstract class ConfigItem {
+        public abstract int getItemSize();
+
+        public abstract short getId();
+
+        public abstract byte[] getContent();
+
+        public abstract void parseData(byte[] data);
+    }
+
+    static public class GenericConfigItem<T> extends ConfigItem {
+        private T value;
+        private final short configId;
+
+        public GenericConfigItem(short configId, T value) {
+            this.value = value;
+            this.configId = configId;
+        }
+
+        public T getValue() {
+            return value;
+        }
+
+        @Override
+        public int getItemSize() {
+            switch (value.getClass().getName()) {
+                case "java.lang.Byte":
+                    return 1;
+                case "java.lang.Short":
+                    return 2;
+                case "java.lang.Integer":
+                    return 4;
+                case "java.lang.Long":
+                    return 8;
+            }
+            throw new UnsupportedOperationException("config type " + value.getClass().getName() + " not supported");
+        }
+
+        @Override
+        public short getId() {
+            return this.configId;
+        }
+
+        @Override
+        public byte[] getContent() {
+            ByteBuffer buffer = ByteBuffer.allocate(getItemSize());
+            buffer.order(ByteOrder.LITTLE_ENDIAN);
+            switch (value.getClass().getName()) {
+                case "java.lang.Byte": {
+                    buffer.put((Byte) this.value);
+                    break;
+                }
+                case "java.lang.Integer": {
+                    buffer.putInt((Integer) this.value);
+                    break;
+                }
+                case "java.lang.Long": {
+                    buffer.putLong((Long) this.value);
+                    break;
+                }
+                case "java.lang.Short": {
+                    buffer.putShort((Short) this.value);
+                    break;
+                }
+            }
+            return buffer.array();
+        }
+
+        @Override
+        public void parseData(byte[] data) {
+            ByteBuffer buffer = ByteBuffer.wrap(data);
+            buffer.order(ByteOrder.LITTLE_ENDIAN);
+
+            switch (data.length) {
+                case 1: {
+                    this.value = (T) (Byte) buffer.get();
+                    break;
+                }
+                case 2: {
+                    this.value = (T) (Short) buffer.getShort();
+                    break;
+                }
+                case 4: {
+                    this.value = (T) (Integer) buffer.getInt();
+                    break;
+                }
+                case 8: {
+                    this.value = (T) (Long) buffer.getLong();
+                    break;
+                }
+            }
+        }
+    }
+
+    static public class BatteryConfigItem extends ConfigItem {
+        private int batteryPercentage, batteryVoltage;
+
+        public int getBatteryPercentage() {
+            return batteryPercentage;
+        }
+
+        public int getBatteryVoltage() {
+            return batteryVoltage;
+        }
+
+        @Override
+        public int getItemSize() {
+            return 3;
+        }
+
+        @Override
+        public short getId() {
+            return 0x0D;
+        }
+
+        @Override
+        public byte[] getContent() {
+            return new byte[0];
+        }
+
+        @Override
+        public void parseData(byte[] data) {
+            ByteBuffer buffer = ByteBuffer.wrap(data);
+            buffer.order(ByteOrder.LITTLE_ENDIAN);
+
+            this.batteryVoltage = buffer.getShort();
+            this.batteryPercentage = buffer.get();
+        }
+    }
+
+    static public class HeartRateMeasurementModeItem extends GenericConfigItem<Byte> {
+        public HeartRateMeasurementModeItem() {
+            this((byte) -1);
+        }
+
+        public HeartRateMeasurementModeItem(byte value) {
+            super((short) 14, value);
+        }
+    }
+
+    static public class DailyStepGoalConfigItem extends GenericConfigItem<Integer> {
+        public DailyStepGoalConfigItem() {
+            this(-1);
+        }
+
+        public DailyStepGoalConfigItem(int value) {
+            super((short) 3, value);
+        }
+    }
+
+    static public class TimezoneOffsetConfigItem extends GenericConfigItem<Short> {
+        public TimezoneOffsetConfigItem(Short value) {
+            super((short) 17, value);
+        }
+    }
+
+    static public class VibrationStrengthConfigItem extends GenericConfigItem<Byte> {
+        public VibrationStrengthConfigItem() {
+            this((byte) -1);
+        }
+
+        public VibrationStrengthConfigItem(Byte value) {
+            super((short) 10, value);
+        }
+    }
+
+    static public class CurrentStepCountConfigItem extends GenericConfigItem<Integer> {
+        public CurrentStepCountConfigItem() {
+            this(-1);
+        }
+
+        public CurrentStepCountConfigItem(Integer value) {
+            super((short) 2, value);
+        }
+    }
+
+    static public class UnitsConfigItem extends GenericConfigItem<Integer> {
+        public UnitsConfigItem() {
+            this(-1);
+        }
+
+        public UnitsConfigItem(Integer value) {
+            super((short) 16, value);
+        }
+    }
+
+    static public class TimeConfigItem extends ConfigItem {
+        private int epochSeconds;
+        private short millis, offsetMinutes;
+
+        public TimeConfigItem() {
+            this(-1, (short) -1, (short) -1);
+        }
+
+        public TimeConfigItem(int epochSeconds, short millis, short offsetMinutes) {
+            this.epochSeconds = epochSeconds;
+            this.millis = millis;
+            this.offsetMinutes = offsetMinutes;
+        }
+
+
+        @Override
+        public int getItemSize() {
+            return 8;
+        }
+
+        @Override
+        public short getId() {
+            return (short) 12;
+        }
+
+        @Override
+        public byte[] getContent() {
+            ByteBuffer buffer = ByteBuffer.allocate(getItemSize());
+            buffer.order(ByteOrder.LITTLE_ENDIAN);
+            buffer.putInt(this.epochSeconds);
+            buffer.putShort(millis);
+            buffer.putShort(offsetMinutes);
+            return buffer.array();
+        }
+
+        @Override
+        public void parseData(byte[] data) {
+            if (data.length != 8) throw new RuntimeException("wrong data");
+
+            ByteBuffer buffer = ByteBuffer.wrap(data);
+            buffer.order(ByteOrder.LITTLE_ENDIAN);
+
+            this.epochSeconds = buffer.getInt();
+            this.millis = buffer.getShort();
+            this.offsetMinutes = buffer.getShort();
+        }
+    }
+
+
+    static public class FitnessConfigItem extends ConfigItem {
+        boolean recognizeRunning = false;
+        boolean askRunning = false;
+        int minutesRunning = 3;
+
+        boolean recognizeBiking = false;
+        boolean askBiking = false;
+        int minutesBiking = 5;
+
+        boolean recognizeWalking = false;
+        boolean askWalking = false;
+        int minutesWalking = 10;
+
+        boolean recognizeRowing = false;
+        boolean askRowing = false;
+        int minutesRowing = 3;
+
+        public FitnessConfigItem(boolean recognizeRunning,
+                                 boolean askRunning,
+                                 int minutesRunning,
+                                 boolean recognizeBiking,
+                                 boolean askBiking,
+                                 int minutesBiking,
+                                 boolean recognizeWalking,
+                                 boolean askWalking,
+                                 int minutesWalking,
+                                 boolean recognizeRowing,
+                                 boolean askRowing,
+                                 int minutesRowing) {
+            this.recognizeRunning = recognizeRunning;
+            this.askRunning = askRunning;
+            this.minutesRunning = minutesRunning;
+            this.recognizeBiking = recognizeBiking;
+            this.askBiking = askBiking;
+            this.minutesBiking = minutesBiking;
+            this.recognizeWalking = recognizeWalking;
+            this.askWalking = askWalking;
+            this.minutesWalking = minutesWalking;
+            this.recognizeRowing = recognizeRowing;
+            this.askRowing = askRowing;
+            this.minutesRowing = minutesRowing;
+        }
+
+        public FitnessConfigItem() {
+        }
+
+        @Override
+        public int getItemSize() {
+            return 30;
+        }
+
+        @Override
+        public short getId() {
+            return 0x14;
+        }
+
+        @Override
+        public byte[] getContent() {
+            byte[] data = new byte[]{
+                    // 2nd byte of each workout type is activation mode
+                    // 3rd byte of each is amount of minutes before asking/activating
+                    // Running
+                    (byte) 0x01, (byte) 0x00, (byte) 0x03, (byte) 0x01, (byte) 0x01, (byte) 0x05,
+                    // Biking
+                    (byte) 0x02, (byte) 0x00, (byte) 0x05, (byte) 0x01, (byte) 0x01, (byte) 0x01,
+                    // Walking
+                    (byte) 0x08, (byte) 0x00, (byte) 0x0A, (byte) 0x01, (byte) 0x01, (byte) 0x05,
+                    // Rowing
+                    (byte) 0x09, (byte) 0x00, (byte) 0x03, (byte) 0x01, (byte) 0x01, (byte) 0x01
+            };
+            if (recognizeRunning) {
+                data[1] |= 0x01;
+                if (askRunning) {
+                    data[1] |= 0x02;
+                }
+                data[2] = (byte) (minutesRunning & 0xFF);
+            }
+            if (recognizeBiking) {
+                data[7] |= 0x01;
+                if (askBiking) {
+                    data[7] |= 0x02;
+                }
+                data[8] = (byte) (minutesBiking & 0xFF);
+            }
+            if (recognizeWalking) {
+                data[13] |= 0x01;
+                if (askWalking) {
+                    data[13] |= 0x02;
+                }
+                data[14] = (byte) (minutesWalking & 0xFF);
+            }
+            if (recognizeRowing) {
+                data[19] |= 0x01;
+                if (askRowing) {
+                    data[19] |= 0x02;
+                }
+                data[20] = (byte) (minutesRowing & 0xFF);
+            }
+            return data;
+        }
+
+        @Override
+        public void parseData(byte[] data) {
+            recognizeRunning = (data[1] & 0x01) == 0x01;
+            askRunning = (data[1] & 0x02) == 0x02;
+            minutesRunning = data[2] & 0xFF;
+
+            recognizeBiking = (data[7] & 0x01) == 0x01;
+            askBiking = (data[7] & 0x02) == 0x02;
+            minutesBiking = data[8] & 0xFF;
+
+            recognizeWalking = (data[13] & 0x01) == 0x01;
+            askWalking = (data[13] & 0x02) == 0x02;
+            minutesWalking = data[14] & 0xFF;
+
+            recognizeRowing = (data[19] & 0x01) == 0x01;
+            askRowing = (data[19] & 0x02) == 0x02;
+            minutesRowing = data[20] & 0xFF;
+        }
+
+        @NonNull
+        @Override
+        public String toString() {
+            return
+                    "recognizeRunning: " + recognizeRunning + "   askRunning: " + askRunning + "  minutesRunning: " + minutesRunning + "\n" +
+                            "recognizeBiking: " + recognizeBiking + "   askBiking: " + askBiking + "  minutesBiking: " + minutesBiking + "\n" +
+                            "recognizeWalking: " + recognizeWalking + "   askWalking: " + askWalking + "  minutesWalking: " + minutesWalking + "\n" +
+                            "recognizeRowing: " + recognizeRowing + "   askRowing: " + askRowing + "  minutesRowing: " + minutesRowing;
+        }
+    }
+
+    static public class InactivityWarningItem extends ConfigItem {
+        private int fromTimeHour, fromTimeMinute, untilTimeHour, untilTimeMinute, inactiveMinutes;
+        boolean enabled;
+
+        public InactivityWarningItem() {
+            this(0, 0, 0, 0, 0, false);
+        }
+
+        public InactivityWarningItem(int fromTimeHour, int fromTimeMinute, int untilTimeHour, int untilTimeMinute, int inactiveMinutes, boolean enabled) {
+            this.fromTimeHour = fromTimeHour;
+            this.fromTimeMinute = fromTimeMinute;
+            this.untilTimeHour = untilTimeHour;
+            this.untilTimeMinute = untilTimeMinute;
+            this.inactiveMinutes = inactiveMinutes;
+            this.enabled = enabled;
+        }
+
+        @Override
+        public int getItemSize() {
+            return 6;
+        }
+
+        @Override
+        public short getId() {
+            return (short) 9;
+        }
+
+        @Override
+        public byte[] getContent() {
+            ByteBuffer buffer = ByteBuffer.allocate(getItemSize());
+            buffer.order(ByteOrder.LITTLE_ENDIAN);
+            buffer.put((byte) this.fromTimeHour);
+            buffer.put((byte) this.fromTimeMinute);
+            buffer.put((byte) this.untilTimeHour);
+            buffer.put((byte) this.untilTimeMinute);
+            buffer.put((byte) this.inactiveMinutes);
+            buffer.put((byte) (this.enabled ? 1 : 0));
+            return buffer.array();
+        }
+
+        @Override
+        public void parseData(byte[] data) {
+            if (data.length != getItemSize()) throw new RuntimeException("wrong data");
+
+            ByteBuffer buffer = ByteBuffer.wrap(data);
+            buffer.order(ByteOrder.LITTLE_ENDIAN);
+
+            this.fromTimeHour = buffer.get();
+            this.fromTimeMinute = buffer.get();
+            this.untilTimeHour = buffer.get();
+            this.untilTimeMinute = buffer.get();
+            this.inactiveMinutes = buffer.get();
+            this.enabled = buffer.get() == 0x01;
+        }
+    }
+
+}
+
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/connection/SetConnectionParametersRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/connection/SetConnectionParametersRequest.java
new file mode 100644
index 0000000..529c74a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/connection/SetConnectionParametersRequest.java
@@ -0,0 +1,53 @@
+/*  Copyright (C) 2019-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.connection;
+
+import android.bluetooth.BluetoothGattCharacteristic;
+
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.FossilRequest;
+
+public class SetConnectionParametersRequest extends FossilRequest {
+    private boolean finished = false;
+
+    @Override
+    public boolean isFinished() {
+        return finished;
+    }
+
+    @Override
+    public UUID getRequestUUID() {
+        return UUID.fromString("3dda0002-957f-7d4a-34a6-74696673696d");
+    }
+
+    @Override
+    public void handleResponse(BluetoothGattCharacteristic characteristic) {
+        super.handleResponse(characteristic);
+        this.finished = true;
+    }
+
+    @Override
+    public byte[] getStartSequence() {
+        return new byte[]{0x02, 0x09, 0x0C, 0x00, 0x0C, 0x00, 0x2D, 0x00, 0x58, 0x02};
+    }
+
+    @Override
+    public boolean isBasicRequest() {
+        return false;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/device_info/DeviceInfo.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/device_info/DeviceInfo.java
new file mode 100644
index 0000000..178d7f6
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/device_info/DeviceInfo.java
@@ -0,0 +1,21 @@
+/*  Copyright (C) 2020-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.device_info;
+
+public interface DeviceInfo {
+    public void parsePayload(byte[] payload);
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/device_info/DeviceSecurityVersionInfo.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/device_info/DeviceSecurityVersionInfo.java
new file mode 100644
index 0000000..3f5aa71
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/device_info/DeviceSecurityVersionInfo.java
@@ -0,0 +1,43 @@
+/*  Copyright (C) 2020-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.device_info;
+
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.HashMap;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.file.FileHandle;
+
+public class DeviceSecurityVersionInfo implements DeviceInfo {
+    private int versionMajor, versionMinor;
+
+    @Override
+    public void parsePayload(byte[] payload) {
+        versionMajor = payload[0];
+        versionMinor = payload[1];
+    }
+
+    @NonNull
+    @Override
+    public String toString() {
+        return versionMajor + "." + versionMinor;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/device_info/GetDeviceInfoRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/device_info/GetDeviceInfoRequest.java
new file mode 100644
index 0000000..46ac1ea
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/device_info/GetDeviceInfoRequest.java
@@ -0,0 +1,93 @@
+/*  Copyright (C) 2020-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.device_info;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil.FossilWatchAdapter;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.file.FileHandle;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.file.FileGetRequest;
+
+public class GetDeviceInfoRequest extends FileGetRequest {
+    enum INFO_CLASS{
+        SUPPORTED_FILE_VERSIONS((short) 0x0a, SupportedFileVersionsInfo.class),
+        DEVICE_SECURITY_VERSION((short) 0x0e, DeviceSecurityVersionInfo.class),
+        ;
+        private short identifier;
+        private  Class<? extends DeviceInfo> itemClass;
+
+        private INFO_CLASS(short identifier, Class<? extends DeviceInfo> itemClass){
+            this.identifier = identifier;
+            this.itemClass = itemClass;
+        }
+
+        static INFO_CLASS getByIdentifier(short identifier){
+            for(INFO_CLASS infoClass : values()){
+                if(infoClass.getIdentifier() == identifier) return infoClass;
+            }
+            return null;
+        }
+
+        public short getIdentifier() {
+            return identifier;
+        }
+
+        public Class<? extends DeviceInfo> getItemClass() {
+            return itemClass;
+        }
+    }
+
+    public GetDeviceInfoRequest(FossilWatchAdapter adapter) {
+        super(FileHandle.DEVICE_INFO, adapter);
+    }
+
+    @Override
+    public void handleFileData(byte[] fileData) {
+        ByteBuffer buffer = ByteBuffer.wrap(fileData);
+        buffer.order(ByteOrder.LITTLE_ENDIAN);
+
+        ArrayList<DeviceInfo> deviceInfos = new ArrayList<>();
+
+        while(buffer.remaining() > 0){
+            short type = buffer.getShort();
+            int length = buffer.get();
+            byte[] payload = new byte[length];
+            buffer.get(payload);
+
+            INFO_CLASS infoClass = INFO_CLASS.getByIdentifier(type);
+            if(infoClass == null) continue;
+
+            Class<? extends DeviceInfo> infoC = infoClass.getItemClass();
+            try {
+                DeviceInfo info = infoC.newInstance();
+                info.parsePayload(payload);
+                deviceInfos.add(info);
+            } catch (IllegalAccessException e) {
+                e.printStackTrace();
+            } catch (InstantiationException e) {
+                e.printStackTrace();
+            }
+        }
+        handleDeviceInfos(deviceInfos.toArray(new DeviceInfo[0]));
+    }
+
+    public void handleDeviceInfos(DeviceInfo[] deviceInfos){
+        log("got infos");
+    };
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/device_info/SupportedFileVersionsInfo.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/device_info/SupportedFileVersionsInfo.java
new file mode 100644
index 0000000..3eb3c7f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/device_info/SupportedFileVersionsInfo.java
@@ -0,0 +1,52 @@
+/*  Copyright (C) 2020-2024 Arjan Schrijver, Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.device_info;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.HashMap;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.file.FileHandle;
+
+public class SupportedFileVersionsInfo implements DeviceInfo {
+    private HashMap<Byte, Short> supportedFileVersions = new HashMap<>();
+
+    @Override
+    public void parsePayload(byte[] payload) {
+        ByteBuffer buffer = ByteBuffer.wrap(payload);
+        buffer.order(ByteOrder.LITTLE_ENDIAN);
+
+        while(buffer.remaining() > 0){
+            byte handle = buffer.get();
+            short version = buffer.getShort();
+            supportedFileVersions.put(handle, version);
+        }
+
+        // Add quick replies packet type
+        supportedFileVersions.put((byte) 0x13, (short) 0x0002);
+        // Add phone app packet type
+        supportedFileVersions.put((byte) 0x15, (short) 0x0003);
+    }
+
+    public short getSupportedFileVersion(FileHandle fileHandle){
+        return getSupportedFileVersion(fileHandle.getMajorHandle());
+    }
+
+    public short getSupportedFileVersion(byte fileHandle){
+        return supportedFileVersions.get(fileHandle);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/file/FileCloseAndPutRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/file/FileCloseAndPutRequest.java
new file mode 100644
index 0000000..7eadb80
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/file/FileCloseAndPutRequest.java
@@ -0,0 +1,54 @@
+/*  Copyright (C) 2019-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.file;
+
+import android.widget.Toast;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.qhybrid.NotificationConfiguration;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil.FossilWatchAdapter;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.file.FileHandle;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.notification.NotificationFilterPutRequest;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class FileCloseAndPutRequest extends FileCloseRequest {
+    FossilWatchAdapter adapter;
+    byte[] data;
+
+    public FileCloseAndPutRequest(FileHandle fileHandle, byte[] data, FossilWatchAdapter adapter) {
+        super(fileHandle);
+        this.adapter = adapter;
+        this.data = data;
+    }
+
+    @Override
+    public void onPrepare() {
+        super.onPrepare();
+        adapter.queueWrite(new FilePutRequest(getHandle(), this.data, adapter) {
+            @Override
+            public void onFilePut(boolean success) {
+                super.onFilePut(success);
+                FileCloseAndPutRequest.this.onFilePut(success);
+            }
+        }, false);
+    }
+
+    public void onFilePut(boolean success){
+
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/file/FileCloseRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/file/FileCloseRequest.java
new file mode 100644
index 0000000..19931e6
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/file/FileCloseRequest.java
@@ -0,0 +1,98 @@
+/*  Copyright (C) 2019-2024 Andreas Shimokawa, Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.file;
+
+import android.bluetooth.BluetoothGattCharacteristic;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.file.FileHandle;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.Request;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.FossilRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.file.ResultCode;
+
+public class FileCloseRequest extends FossilRequest {
+    private boolean isFinished = false;
+    private FileHandle handle;
+
+    public FileCloseRequest(FileHandle fileHandle) {
+        this.handle = fileHandle;
+        ByteBuffer buffer = this.createBuffer();
+        buffer.putShort(fileHandle.getHandle());
+
+        this.data = buffer.array();
+    }
+
+    public FileHandle getHandle() {
+        return handle;
+    }
+
+    @Override
+    public void handleResponse(BluetoothGattCharacteristic characteristic) {
+        super.handleResponse(characteristic);
+
+        if(!characteristic.getUuid().toString().equals(this.getRequestUUID().toString())){
+            throw new RuntimeException("wrong response UUID");
+        }
+
+        byte[] value = characteristic.getValue();
+
+        byte type = (byte)(value[0] & 0x0F);
+
+        if(type != 9) throw new RuntimeException("wrong response type");
+
+        if(value.length != 4) throw new RuntimeException("wrong response length");
+
+        ByteBuffer buffer = ByteBuffer.wrap(value);
+        buffer.order(ByteOrder.LITTLE_ENDIAN);
+
+        if(this.handle.getHandle() != buffer.getShort(1)) throw new RuntimeException("wrong response handle");
+
+        byte status = buffer.get(3);
+
+        ResultCode code = ResultCode.fromCode(status);
+        if(!code.inidicatesSuccess()) throw new RuntimeException("wrong response status: " + code + "   (" + status + ")");
+
+        this.isFinished = true;
+
+        this.onPrepare();
+    }
+
+    public void onPrepare(){}
+
+    @Override
+    public byte[] getStartSequence() {
+        return new byte[]{9};
+    }
+
+    @Override
+    public int getPayloadLength() {
+        return 3;
+    }
+
+    @Override
+    public boolean isFinished(){
+        return this.isFinished;
+    }
+
+    @Override
+    public UUID getRequestUUID() {
+        return UUID.fromString("3dda0003-957f-7d4a-34a6-74696673696d");
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/file/FileDeleteRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/file/FileDeleteRequest.java
new file mode 100644
index 0000000..ef45c69
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/file/FileDeleteRequest.java
@@ -0,0 +1,83 @@
+/*  Copyright (C) 2019-2024 Andreas Shimokawa, Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.file;
+
+import android.bluetooth.BluetoothGattCharacteristic;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.file.FileHandle;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.FossilRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.file.ResultCode;
+
+public class FileDeleteRequest extends FossilRequest {
+    private boolean finished = false;
+    private short handle;
+
+    public FileDeleteRequest(short handle) {
+        this.handle = handle;
+
+        ByteBuffer buffer = createBuffer();
+
+        buffer.putShort(handle);
+
+        this.data = buffer.array();
+    }
+
+    public FileDeleteRequest(FileHandle handle){
+        this(handle.getHandle());
+    }
+
+    @Override
+    public void handleResponse(BluetoothGattCharacteristic characteristic) {
+        super.handleResponse(characteristic);
+        if(!characteristic.getUuid().toString().equals("3dda0003-957f-7d4a-34a6-74696673696d"))
+            throw new RuntimeException("wrong response UUID");
+        byte[] value = characteristic.getValue();
+
+        if(value.length != 4) throw new RuntimeException("wrong response length");
+
+        if(value[0] != (byte) 0x8B) throw new RuntimeException("wrong response start");
+
+        ByteBuffer buffer = ByteBuffer.wrap(value);
+        buffer.order(ByteOrder.LITTLE_ENDIAN);
+
+        if(buffer.getShort(1) != this.handle) throw new RuntimeException("wrong response handle");
+
+        byte status = buffer.get(3);
+        ResultCode code = ResultCode.fromCode(status);
+        if(!code.inidicatesSuccess()) throw new RuntimeException("wrong response status: " + code + "(" + status + ")");
+
+        this.finished = true;
+    }
+
+    @Override
+    public boolean isFinished() {
+        return finished;
+    }
+
+    @Override
+    public byte[] getStartSequence() {
+        return new byte[]{(byte) 0x0B};
+    }
+
+    @Override
+    public int getPayloadLength() {
+        return 3;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/file/FileGetRawRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/file/FileGetRawRequest.java
new file mode 100644
index 0000000..abca481
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/file/FileGetRawRequest.java
@@ -0,0 +1,151 @@
+/*  Copyright (C) 2020-2024 Arjan Schrijver, Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.file;
+
+import android.bluetooth.BluetoothGattCharacteristic;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.UUID;
+import java.util.zip.CRC32;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil.FossilWatchAdapter;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil_hr.FossilHRWatchAdapter;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.file.FileHandle;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.FossilRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.file.ResultCode;
+
+public abstract class FileGetRawRequest extends FossilRequest {
+    private byte majorHandle;
+    private byte minorHandle;
+    private FossilWatchAdapter adapter;
+
+    private ByteBuffer fileBuffer;
+
+    private byte[] fileData;
+
+    private boolean finished = false;
+
+    public FileGetRawRequest(byte majorHandle, byte minorHandle, FossilWatchAdapter adapter) {
+        this.majorHandle = majorHandle;
+        this.minorHandle = minorHandle;
+        this.adapter = adapter;
+
+        this.data =
+                createBuffer()
+                        .put(minorHandle)
+                        .put(majorHandle)
+                        .putInt(0)
+                        .putInt(0xFFFFFFFF)
+                        .array();
+    }
+
+    public FileGetRawRequest(FileHandle handle, FossilWatchAdapter adapter) {
+        this(handle.getMajorHandle(), handle.getMinorHandle(), adapter);
+    }
+
+    public FileGetRawRequest(short handle, FossilWatchAdapter adapter) {
+        this((byte) ((handle >> 8) & 0xff), (byte) (handle), adapter);
+    }
+
+    public FossilWatchAdapter getAdapter() {
+        return adapter;
+    }
+
+    @Override
+    public boolean isFinished(){
+        return finished;
+    }
+
+    @Override
+    public void handleResponse(BluetoothGattCharacteristic characteristic) {
+        byte[] value = characteristic.getValue();
+        byte first = value[0];
+        if(characteristic.getUuid().toString().equals("3dda0003-957f-7d4a-34a6-74696673696d")){
+            if((first & 0x0F) == 1){
+                ByteBuffer buffer = ByteBuffer.wrap(value);
+                buffer.order(ByteOrder.LITTLE_ENDIAN);
+
+                short minorHandle = buffer.get(1);
+                short majorHandle = buffer.get(2);
+                int size = buffer.getInt(4);
+
+                byte status = buffer.get(3);
+
+                ResultCode code = ResultCode.fromCode(status);
+                if(!code.inidicatesSuccess()){
+                    throw new RuntimeException("FileGet error: " + code + "   (" + status + ")");
+                }
+
+                if (this.minorHandle != minorHandle) {
+                    throw new RuntimeException("minor handle: " + minorHandle + "   expected: " + this.minorHandle);
+                }
+                if (this.majorHandle != majorHandle) {
+                    throw new RuntimeException("major handle: " + majorHandle + "   expected: " + this.majorHandle);
+                }
+                log("file size: " + size);
+                fileBuffer = ByteBuffer.allocate(size);
+            }else if((first & 0x0F) == 8){
+                this.finished = true;
+
+                ByteBuffer buffer = ByteBuffer.wrap(value);
+                buffer.order(ByteOrder.LITTLE_ENDIAN);
+
+                short handle = buffer.getShort(1);
+                if (this.minorHandle != minorHandle) {
+                    throw new RuntimeException("minor handle: " + minorHandle + "   expected: " + this.minorHandle);
+                }
+                if (this.majorHandle != majorHandle) {
+                    throw new RuntimeException("major handle: " + majorHandle + "   expected: " + this.majorHandle);
+                }
+
+                CRC32 crc = new CRC32();
+                crc.update(this.fileData);
+
+                int crcExpected = buffer.getInt(8);
+
+                if((int) crc.getValue() != crcExpected){
+                    throw new RuntimeException("crc: " + crc.getValue() + "   expected: " + crcExpected);
+                }
+
+                this.handleFileRawData(this.fileData);
+            }
+        }else if(characteristic.getUuid().toString().equals("3dda0004-957f-7d4a-34a6-74696673696d")){
+            fileBuffer.put(value, 1, value.length - 1);
+            if((first & 0x80) == 0x80){
+                this.fileData = fileBuffer.array();
+            }
+        }
+    }
+
+    @Override
+    public UUID getRequestUUID() {
+        return UUID.fromString("3dda0003-957f-7d4a-34a6-74696673696d");
+    }
+
+    @Override
+    public byte[] getStartSequence() {
+        return new byte[]{1};
+    }
+
+    @Override
+    public int getPayloadLength() {
+        return 11;
+    }
+
+    abstract public void handleFileRawData(byte[] fileData);
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/file/FileGetRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/file/FileGetRequest.java
new file mode 100644
index 0000000..47afa67
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/file/FileGetRequest.java
@@ -0,0 +1,40 @@
+/*  Copyright (C) 2019-2024 Andreas Shimokawa, Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.file;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil.FossilWatchAdapter;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.file.FileHandle;
+
+public abstract class FileGetRequest extends FileGetRawRequest {
+    public FileGetRequest(short handle, FossilWatchAdapter adapter) {
+        super(handle, adapter);
+    }
+
+    public FileGetRequest(FileHandle handle, FossilWatchAdapter adapter) {
+        super(handle, adapter);
+    }
+
+    @Override
+    public void handleFileRawData(byte[] fileData) {
+        byte[] file = new byte[fileData.length - 12 - 4]; // 12 = header   4 = crc end
+        System.arraycopy(fileData, 12, file, 0, file.length);
+
+        this.handleFileData(file);
+    }
+
+    public abstract void handleFileData(byte[] fileData);
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/file/FileLookupAndGetRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/file/FileLookupAndGetRequest.java
new file mode 100644
index 0000000..1ecef6e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/file/FileLookupAndGetRequest.java
@@ -0,0 +1,38 @@
+/*  Copyright (C) 2019-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.file;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil.FossilWatchAdapter;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.file.FileHandle;
+
+public abstract class FileLookupAndGetRequest extends FileLookupRequest {
+    public FileLookupAndGetRequest(FileHandle fileHandle, FossilWatchAdapter adapter) {
+        super(fileHandle, adapter);
+    }
+
+    @Override
+    public void handleFileLookup(short fileHandle){
+        getAdapter().queueWrite(new FileGetRawRequest(getHandle(), getAdapter()) {
+            @Override
+            public void handleFileRawData(byte[] fileData) {
+                FileLookupAndGetRequest.this.handleFileData(fileData);
+            }
+        }, true);
+    }
+
+    abstract public void handleFileData(byte[] fileData);
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/file/FileLookupRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/file/FileLookupRequest.java
new file mode 100644
index 0000000..40aa793
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/file/FileLookupRequest.java
@@ -0,0 +1,150 @@
+/*  Copyright (C) 2019-2024 Andreas Shimokawa, Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.file;
+
+import android.bluetooth.BluetoothGattCharacteristic;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.UUID;
+import java.util.zip.CRC32;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil.FossilWatchAdapter;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.file.FileHandle;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.FossilRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.file.ResultCode;
+
+public abstract class FileLookupRequest extends FossilRequest {
+    private short handle = -1;
+    private byte fileType;
+
+    private FossilWatchAdapter adapter;
+
+    private ByteBuffer fileBuffer;
+
+    private byte[] fileData;
+
+    protected boolean finished = false;
+
+    public FileLookupRequest(FileHandle fileHandle, FossilWatchAdapter adapter) {
+        this.fileType = fileHandle.getMajorHandle();
+        this.adapter = adapter;
+
+        this.data =
+                createBuffer()
+                        .put(fileType)
+                        .array();
+    }
+
+    protected FossilWatchAdapter getAdapter() {
+        return adapter;
+    }
+
+    public short getHandle() {
+        if(!finished){
+            throw new UnsupportedOperationException("File lookup not finished");
+        }
+        return handle;
+    }
+
+    @Override
+    public boolean isFinished(){
+        return finished;
+    }
+
+    @Override
+    public void handleResponse(BluetoothGattCharacteristic characteristic) {
+        byte[] value = characteristic.getValue();
+        byte first = value[0];
+        if(characteristic.getUuid().toString().equals("3dda0003-957f-7d4a-34a6-74696673696d")){
+            if((first & 0x0F) == 2){
+                ByteBuffer buffer = ByteBuffer.wrap(value);
+                buffer.order(ByteOrder.LITTLE_ENDIAN);
+
+                short handle = buffer.getShort(1);
+                int size = buffer.getInt(4);
+
+                byte status = buffer.get(3);
+
+                ResultCode code = ResultCode.fromCode(status);
+                if(!code.inidicatesSuccess()){
+                    throw new RuntimeException("file lookup error: " + code + "   (" + status + ")");
+                }
+
+                if(this.handle != handle){
+                    // throw new RuntimeException("handle: " + handle + "   expected: " + this.handle);
+                }
+                log("file size: " + size);
+                if(size == 0){
+                    this.handleFileLookupError(FILE_LOOKUP_ERROR.FILE_EMPTY);
+                    finished = true;
+                    return;
+                }
+                fileBuffer = ByteBuffer.allocate(size);
+            }else if((first & 0x0F) == 8){
+                this.finished = true;
+
+                ByteBuffer buffer = ByteBuffer.wrap(value);
+                buffer.order(ByteOrder.LITTLE_ENDIAN);
+
+                CRC32 crc = new CRC32();
+                crc.update(this.fileData);
+
+                int crcExpected = buffer.getInt(8);
+
+                if((int) crc.getValue() != crcExpected){
+                    throw new RuntimeException("handle: " + handle + "   expected: " + this.handle);
+                }
+
+                ByteBuffer dataBuffer = ByteBuffer.wrap(fileData);
+                dataBuffer.order(ByteOrder.LITTLE_ENDIAN);
+
+                this.handle = dataBuffer.getShort(0);
+
+                this.handleFileLookup(this.handle);
+            }
+        }else if(characteristic.getUuid().toString().equals("3dda0004-957f-7d4a-34a6-74696673696d")){
+            fileBuffer.put(value, 1, value.length - 1);
+            if((first & 0x80) == 0x80){
+                this.fileData = fileBuffer.array();
+            }
+        }
+    }
+
+    public abstract void handleFileLookup(short fileHandle);
+
+    public abstract void handleFileLookupError(FILE_LOOKUP_ERROR error);
+
+    @Override
+    public UUID getRequestUUID() {
+        return UUID.fromString("3dda0003-957f-7d4a-34a6-74696673696d");
+    }
+
+    @Override
+    public byte[] getStartSequence() {
+        return new byte[]{2, (byte) 0xFF};
+    }
+
+    @Override
+    public int getPayloadLength() {
+        return 3;
+    }
+
+    public enum FILE_LOOKUP_ERROR{
+        FILE_EMPTY;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/file/FilePutRawRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/file/FilePutRawRequest.java
new file mode 100644
index 0000000..e1b1bf5
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/file/FilePutRawRequest.java
@@ -0,0 +1,241 @@
+/*  Copyright (C) 2020-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.file;
+
+import android.bluetooth.BluetoothGattCharacteristic;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.UUID;
+import java.util.zip.CRC32;
+
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil.FossilWatchAdapter;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.file.FileHandle;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.FossilRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.file.ResultCode;
+
+public class FilePutRawRequest extends FossilRequest {
+    public enum UploadState {INITIALIZED, UPLOADING, CLOSING, UPLOADED}
+
+    public UploadState state;
+
+    public ArrayList<byte[]> packets = new ArrayList<>();
+
+    private short handle;
+
+    private FossilWatchAdapter adapter;
+
+    byte[] file;
+
+    int fullCRC;
+
+    public FilePutRawRequest(short handle, byte[] file, FossilWatchAdapter adapter) {
+        this.handle = handle;
+        this.adapter = adapter;
+
+        int fileLength = file.length;
+        ByteBuffer buffer = this.createBuffer();
+        buffer.putShort(1, handle);
+        buffer.putInt(3, 0);
+        buffer.putInt(7, fileLength);
+        buffer.putInt(11, fileLength);
+
+        this.data = buffer.array();
+
+        this.file = file;
+
+        state = UploadState.INITIALIZED;
+    }
+
+    public FilePutRawRequest(FileHandle handle, byte[] file, FossilWatchAdapter adapter) {
+        this(handle.getHandle(), file, adapter);
+    }
+
+        public short getHandle() {
+        return handle;
+    }
+
+    @Override
+    public void handleResponse(BluetoothGattCharacteristic characteristic) {
+        byte[] value = characteristic.getValue();
+        if (characteristic.getUuid().toString().equals("3dda0003-957f-7d4a-34a6-74696673696d")) {
+            int responseType = value[0] & 0x0F;
+            log("response: " + responseType);
+            switch (responseType) {
+                case 3: {
+                    if (value.length != 5 || (value[0] & 0x0F) != 3) {
+                        throw new RuntimeException("wrong answer header");
+                    }
+                    state = UploadState.UPLOADING;
+
+                    TransactionBuilder transactionBuilder = new TransactionBuilder("file upload");
+                    BluetoothGattCharacteristic uploadCharacteristic = adapter.getDeviceSupport().getCharacteristic(UUID.fromString("3dda0004-957f-7d4a-34a6-74696673696d"));
+
+                    this.prepareFilePackets(this.file);
+
+                    for (int i = 0, packetCount = packets.size(); i < packetCount; i++) {
+                        byte[] packet = packets.get(i);
+                        transactionBuilder.write(uploadCharacteristic, packet);
+                        onPacketWritten(transactionBuilder, i, packetCount);
+                    }
+
+                    transactionBuilder.queue(adapter.getDeviceSupport().getQueue());
+                    break;
+                }
+                case 8: {
+                    if (value.length == 4) return;
+                    ByteBuffer buffer = ByteBuffer.wrap(value);
+                    buffer.order(ByteOrder.LITTLE_ENDIAN);
+                    short handle = buffer.getShort(1);
+                    int crc = buffer.getInt(8);
+                    byte status = value[3];
+
+                    ResultCode code = ResultCode.fromCode(status);
+                    if(!code.inidicatesSuccess()){
+                        throw new RuntimeException("upload status: " + code + "   (" + status + ")");
+                    }
+
+                    if (handle != this.handle) {
+                        throw new RuntimeException("wrong response handle");
+                    }
+
+                    if (crc != this.fullCRC) {
+                        throw new RuntimeException("file upload exception: wrong crc");
+                    }
+
+
+                    ByteBuffer buffer2 = ByteBuffer.allocate(3);
+                    buffer2.order(ByteOrder.LITTLE_ENDIAN);
+                    buffer2.put((byte) 4);
+                    buffer2.putShort(this.handle);
+
+                    new TransactionBuilder("file close")
+                            .write(
+                                    adapter.getDeviceSupport().getCharacteristic(UUID.fromString("3dda0003-957f-7d4a-34a6-74696673696d")),
+                                    buffer2.array()
+                            )
+                            .queue(adapter.getDeviceSupport().getQueue());
+
+                    this.state = UploadState.CLOSING;
+                    break;
+                }
+                case 4: {
+                    if (value.length == 9) return;
+                    if (value.length != 4 || (value[0] & 0x0F) != 4) {
+                        throw new RuntimeException("wrong file closing header");
+                    }
+                    ByteBuffer buffer = ByteBuffer.wrap(value);
+                    buffer.order(ByteOrder.LITTLE_ENDIAN);
+
+                    short handle = buffer.getShort(1);
+
+                    if (handle != this.handle) {
+                        onFilePut(false);
+                        throw new RuntimeException("wrong file closing handle");
+                    }
+
+                    byte status = buffer.get(3);
+
+                    ResultCode code = ResultCode.fromCode(status);
+                    if(!code.inidicatesSuccess()){
+                        onFilePut(false);
+                        throw new RuntimeException("wrong closing status: " + code + "   (" + status + ")");
+                    }
+
+                    this.state = UploadState.UPLOADED;
+
+                    onFilePut(true);
+
+                    log("uploaded file");
+
+                    break;
+                }
+                case 9: {
+                    this.onFilePut(false);
+                    throw new RuntimeException("file put timeout");
+                    /*timeout = true;
+                    ByteBuffer buffer2 = ByteBuffer.allocate(3);
+                    buffer2.order(ByteOrder.LITTLE_ENDIAN);
+                    buffer2.put((byte) 4);
+                    buffer2.putShort(this.handle);
+
+                    new TransactionBuilder("file close")
+                            .write(
+                                    adapter.getDeviceSupport().getCharacteristic(UUID.fromString("3dda0003-957f-7d4a-34a6-74696673696d")),
+                                    buffer2.array()
+                            )
+                            .queue(adapter.getDeviceSupport().getQueue());
+
+                    this.state = UploadState.CLOSING;
+                    break;*/
+                }
+            }
+        }
+    }
+
+    @Override
+    public boolean isFinished() {
+        return this.state == UploadState.UPLOADED;
+    }
+
+    private void prepareFilePackets(byte[] file) {
+        int maxPacketSize = adapter.getMTU() - 4;
+
+        byte[] data = file;
+
+        CRC32 fullCRC = new CRC32();
+
+        fullCRC.update(data);
+        this.fullCRC = (int) fullCRC.getValue();
+
+        int packetCount = (int) Math.ceil(data.length / (float) maxPacketSize);
+
+        for (int i = 0; i < packetCount; i++) {
+            int currentPacketLength = Math.min(maxPacketSize, data.length - i * maxPacketSize);
+            byte[] packet = new byte[currentPacketLength + 1];
+            packet[0] = (byte) i;
+            System.arraycopy(data, i * maxPacketSize, packet, 1, currentPacketLength);
+
+            packets.add(packet);
+        }
+    }
+
+    public void onFilePut(boolean success) {
+
+    }
+
+    public void onPacketWritten(TransactionBuilder transactionBuilder, int packetNr, int packetCount) {
+
+    }
+
+    @Override
+    public byte[] getStartSequence() {
+        return new byte[]{0x03};
+    }
+
+    @Override
+    public int getPayloadLength() {
+        return 15;
+    }
+
+    @Override
+    public UUID getRequestUUID() {
+        return UUID.fromString("3dda0003-957f-7d4a-34a6-74696673696d");
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/file/FilePutRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/file/FilePutRequest.java
new file mode 100644
index 0000000..93f12de
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/file/FilePutRequest.java
@@ -0,0 +1,61 @@
+/*  Copyright (C) 2019-2024 Andreas Shimokawa, Arjan Schrijver, Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.file;
+
+import android.bluetooth.BluetoothGattCharacteristic;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.UUID;
+import java.util.zip.CRC32;
+
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil.FossilWatchAdapter;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.file.FileHandle;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.FossilRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.file.ResultCode;
+import nodomain.freeyourgadget.gadgetbridge.util.CRC32C;
+
+public class FilePutRequest extends FilePutRawRequest {
+    public FilePutRequest(FileHandle fileHandle, byte[] file, FossilWatchAdapter adapter) {
+        super(fileHandle, createFilePayload(fileHandle, file, adapter.getSupportedFileVersion(fileHandle)), adapter);
+    }
+
+    private static byte[] createFilePayload(FileHandle fileHandle, byte[] file, short fileVersion){
+        ByteBuffer buffer = ByteBuffer.allocate(file.length + 12 + 4);
+        buffer.order(ByteOrder.LITTLE_ENDIAN);
+
+        buffer.putShort(fileHandle.getHandle());
+        buffer.putShort(fileVersion);
+        if (fileHandle == FileHandle.REPLY_MESSAGES) {
+            buffer.put(new byte[]{(byte) 0x00, (byte) 0x00, (byte) 0x0d, (byte) 0x00});
+        } else {
+            buffer.putInt(0);
+        }
+        buffer.putInt(file.length);
+
+        buffer.put(file);
+
+        CRC32C crc = new CRC32C();
+
+        crc.update(file,0,file.length);
+        buffer.putInt((int) crc.getValue());
+
+        return buffer.array();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/file/FileVerifyRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/file/FileVerifyRequest.java
new file mode 100644
index 0000000..2e4eeb5
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/file/FileVerifyRequest.java
@@ -0,0 +1,94 @@
+/*  Copyright (C) 2019-2024 Andreas Shimokawa, Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.file;
+
+import android.bluetooth.BluetoothGattCharacteristic;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.file.FileHandle;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.FossilRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.file.ResultCode;
+
+public class FileVerifyRequest extends FossilRequest {
+    private boolean isFinished = false;
+    private FileHandle handle;
+
+    public FileVerifyRequest(FileHandle fileHandle) {
+        this.handle = fileHandle;
+        ByteBuffer buffer = this.createBuffer();
+        buffer.putShort(fileHandle.getHandle());
+
+        this.data = buffer.array();
+    }
+
+    public FileHandle getHandle() {
+        return handle;
+    }
+
+    @Override
+    public void handleResponse(BluetoothGattCharacteristic characteristic) {
+        super.handleResponse(characteristic);
+
+        if(!characteristic.getUuid().toString().equals(this.getRequestUUID().toString())){
+            throw new RuntimeException("wrong response UUID");
+        }
+
+        byte[] value = characteristic.getValue();
+
+        byte type = (byte)(value[0] & 0x0F);
+
+        if(type == 0x0A) return;
+
+        if(type != 4) throw new RuntimeException("wrong response type");
+
+        if(value.length != 4) throw new RuntimeException("wrong response length");
+
+        ByteBuffer buffer = ByteBuffer.wrap(value);
+        buffer.order(ByteOrder.LITTLE_ENDIAN);
+
+        if(this.handle.getHandle() != buffer.getShort(1)) throw new RuntimeException("wrong response handle");
+
+        byte status = buffer.get(3);
+
+        ResultCode code = ResultCode.fromCode(status);
+        if(!code.inidicatesSuccess()) throw new RuntimeException("wrong response status: " + code + "   (" + status + ")");
+
+        this.isFinished = true;
+
+        this.onPrepare();
+    }
+
+    public void onPrepare(){}
+
+    @Override
+    public byte[] getStartSequence() {
+        return new byte[]{4};
+    }
+
+    @Override
+    public int getPayloadLength() {
+        return 3;
+    }
+
+    @Override
+    public boolean isFinished(){
+        return this.isFinished;
+    }
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/file/FirmwareFilePutRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/file/FirmwareFilePutRequest.java
new file mode 100644
index 0000000..a16e4c4
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/file/FirmwareFilePutRequest.java
@@ -0,0 +1,50 @@
+/*  Copyright (C) 2020-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.file;
+
+import android.content.Context;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetProgressAction;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil.FossilWatchAdapter;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil_hr.FossilHRWatchAdapter;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.file.FilePutRawRequest;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class FirmwareFilePutRequest extends FilePutRawRequest {
+    public FirmwareFilePutRequest(byte[] firmwareBytes, FossilWatchAdapter adapter) {
+        super((short) 0x00FF, firmwareBytes, adapter);
+    }
+
+    @Override
+    public void onPacketWritten(TransactionBuilder transactionBuilder, int packetNr, int packetCount) {
+        int progressPercent = (int) ((((float) packetNr) / packetCount) * 100);
+        transactionBuilder.add(new SetProgressAction(GBApplication.getContext().getString(R.string.updatefirmwareoperation_update_in_progress), true, progressPercent, GBApplication.getContext()));
+    }
+
+    @Override
+    public void onFilePut(boolean success) {
+        Context context = GBApplication.getContext();
+        if (success) {
+            GB.updateInstallNotification(context.getString(R.string.updatefirmwareoperation_update_complete), false, 100, context);
+        } else {
+            GB.updateInstallNotification(context.getString(R.string.updatefirmwareoperation_write_failed), false, 0, context);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/microapp/MicroAppCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/microapp/MicroAppCommand.java
new file mode 100644
index 0000000..0e73b80
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/microapp/MicroAppCommand.java
@@ -0,0 +1,176 @@
+/*  Copyright (C) 2019-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.microapp;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+public interface MicroAppCommand {
+    byte[] getData();
+}
+
+class StartCriticalCommand implements MicroAppCommand{
+    @Override
+    public byte[] getData() {
+        return new byte[]{(byte) 0x03, (byte) 0x00};
+    }
+}
+
+class CloseCommand implements MicroAppCommand{
+    @Override
+    public byte[] getData() {
+        return new byte[]{(byte) 0x01, (byte) 0x00};
+    }
+}
+
+class DelayCommand implements MicroAppCommand{
+    private double delayInSeconds;
+
+    public DelayCommand(double delayInSeconds) {
+        this.delayInSeconds = delayInSeconds;
+    }
+
+    @Override
+    public byte[] getData() {
+        return ByteBuffer.wrap(new byte[]{0x08, 0x01, 0x00, 0x00})
+                .order(ByteOrder.LITTLE_ENDIAN)
+                .putShort(2, (short)(delayInSeconds * 10f))
+                .array();
+    }
+}
+
+enum VibrationType{
+    NORMAL((byte) 0x04);
+
+    private byte value;
+
+    VibrationType(byte value){
+        this.value = value;
+    }
+
+    public byte getValue(){ return this.value; }
+}
+
+class VibrateCommand implements MicroAppCommand{
+    private VibrationType vibrationType;
+
+    public VibrateCommand(VibrationType vibrationType) {
+        this.vibrationType = vibrationType;
+    }
+
+    @Override
+    public byte[] getData() {
+        return ByteBuffer.wrap(new byte[]{(byte) 0x93, 0x00, 0x00})
+                .put(2, vibrationType.getValue())
+                .array();
+    }
+}
+
+enum MovingDirection{
+    CLOCKWISE((byte) 0x00),
+    COUNTER_CLOCKWISE((byte) 0x01),
+    SHORTEST((byte) 0x02),
+    ;
+    private byte value;
+
+    MovingDirection(byte value){ this.value = value; }
+
+    public byte getValue() {
+        return value;
+    }
+}
+
+enum MovingSpeed{
+    MAX((byte) 0x00),
+    HALF((byte) 0x01),
+    QUARTER((byte) 0x02),
+    EIGHTH((byte) 0x03),
+    SIXTEENTH((byte) 0x04),
+    ;
+    private byte value;
+
+    MovingSpeed(byte value){ this.value = value; }
+
+    public byte getValue() {
+        return value;
+    }
+}
+
+class StreamCommand implements MicroAppCommand{
+    private byte type;
+
+    public StreamCommand(byte type) {
+        this.type = type;
+    }
+
+
+    @Override
+    public byte[] getData() {
+        return new byte[]{(byte) 0x8B, (byte) 0x00, this.type};
+    }
+}
+
+class RepeatStartCommand implements MicroAppCommand{
+    private byte count;
+
+    public RepeatStartCommand(byte count) {
+        this.count = count;
+    }
+
+
+    @Override
+    public byte[] getData() {
+        return new byte[]{(byte) 0x86, (byte) 0x00, this.count};
+    }
+}
+
+class RepeatStopCommand implements MicroAppCommand{
+    @Override
+    public byte[] getData() {
+        return new byte[]{(byte) 0x07, (byte) 0x00};
+    }
+}
+
+class AnimationCommand implements MicroAppCommand{
+    private short hour, minute;
+    private MovingDirection direction;
+    private MovingSpeed speed;
+    private byte absoluteMovementFlag;
+
+    public AnimationCommand(short hour, short minute) {
+        this.hour = hour;
+        this.minute = minute;
+        this.speed = MovingSpeed.MAX;
+        this.direction = MovingDirection.SHORTEST;
+        this.absoluteMovementFlag = 1;
+    }
+
+    @Override
+    public byte[] getData() {
+        return ByteBuffer.allocate(10)
+                .order(ByteOrder.LITTLE_ENDIAN)
+                .put((byte) 0x09)
+                .put((byte) 0x04)
+                .put((byte) 0x01)
+                .put((byte) 0x03)
+                .put((byte) ((direction.getValue() << 6) | (byte)(absoluteMovementFlag << 5) | this.speed.getValue()))
+                .putShort(this.hour)
+                .put((byte) ((direction.getValue() << 6) | (byte)(absoluteMovementFlag << 5) | this.speed.getValue()))
+                .putShort(this.minute)
+                .array();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/microapp/PlayCrazyShitRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/microapp/PlayCrazyShitRequest.java
new file mode 100644
index 0000000..8c15166
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/microapp/PlayCrazyShitRequest.java
@@ -0,0 +1,76 @@
+/*  Copyright (C) 2019-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.microapp;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.zip.CRC32;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil.FossilWatchAdapter;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.file.FileHandle;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.file.FilePutRequest;
+
+public class PlayCrazyShitRequest extends FilePutRequest {
+    public PlayCrazyShitRequest(byte[] appData, FossilWatchAdapter adapter) {
+        super(FileHandle.HAND_ACTIONS, createPayload(appData), adapter);
+    }
+
+    private static byte[] createPayload(byte[] appData) {
+        List<MicroAppCommand> commands = new ArrayList<>();
+
+        commands.add(new StartCriticalCommand());
+        // commands.add(new RepeatStartCommand((byte) 10));
+        commands.add(new VibrateCommand(VibrationType.NORMAL));
+        commands.add(new DelayCommand(1));
+        // commands.add(new RepeatStopCommand());
+        // commands.add(new StreamCommand((byte) 0b11111111));
+        // commands.add(new AnimationCommand((short) 300, (short) 60));
+        // commands.add(new DelayCommand(2));
+        commands.add(new CloseCommand());
+
+        int length = 0;
+
+        for (MicroAppCommand command : commands) length += command.getData().length;
+
+        ByteBuffer buffer = ByteBuffer.allocate(
+                3 /* magic bytes */
+                        + 8 /* button header copy */
+                        + 1 /* 0xFF */
+                        + 2 /* payload length */
+                        + length
+                        + 4 /* crc */
+        );
+        buffer.order(ByteOrder.LITTLE_ENDIAN);
+        buffer.put((byte) 0x01);
+        buffer.put((byte) 0x00);
+        buffer.put((byte) 0x08);
+        buffer.put(appData, 3, 8);
+        buffer.put((byte) 0xFF);
+        buffer.putShort((short)(length + 3));
+
+        for(MicroAppCommand command : commands) buffer.put(command.getData());
+
+        CRC32 crc = new CRC32();
+        crc.update(buffer.array(), 0, buffer.position());
+
+        buffer.putInt((int) crc.getValue());
+
+        return buffer.array();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/notification/DismissTextNotificationRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/notification/DismissTextNotificationRequest.java
new file mode 100644
index 0000000..0694c71
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/notification/DismissTextNotificationRequest.java
@@ -0,0 +1,25 @@
+/*  Copyright (C) 2021-2024 Arjan Schrijver
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.notification;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil.FossilWatchAdapter;
+
+public class DismissTextNotificationRequest extends PlayNotificationRequest {
+    public DismissTextNotificationRequest(int messageId, FossilWatchAdapter adapter) {
+        super(NotificationType.DISMISS_NOTIFICATION, 2, "", "", "", messageId, adapter);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/notification/NotificationFilterGetRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/notification/NotificationFilterGetRequest.java
new file mode 100644
index 0000000..07e28fc
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/notification/NotificationFilterGetRequest.java
@@ -0,0 +1,36 @@
+/*  Copyright (C) 2019-2024 Andreas Shimokawa, Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.notification;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil.FossilWatchAdapter;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.file.FileHandle;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.file.FileGetRequest;
+import nodomain.freeyourgadget.gadgetbridge.util.CRC32C;
+
+public class NotificationFilterGetRequest extends FileGetRequest {
+    public NotificationFilterGetRequest(FossilWatchAdapter adapter) {
+        super(FileHandle.NOTIFICATION_FILTER, adapter);
+    }
+
+    @Override
+    public void handleFileData(byte[] fileData) {
+
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/notification/NotificationFilterPutRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/notification/NotificationFilterPutRequest.java
new file mode 100644
index 0000000..5653ffe
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/notification/NotificationFilterPutRequest.java
@@ -0,0 +1,107 @@
+/*  Copyright (C) 2019-2024 Andreas Shimokawa, Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.notification;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.zip.CRC32;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.qhybrid.NotificationConfiguration;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil.FossilWatchAdapter;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.file.FileHandle;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.file.FileCloseAndPutRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.file.FilePutRequest;
+
+public class NotificationFilterPutRequest extends FilePutRequest {
+    public NotificationFilterPutRequest(NotificationConfiguration[] configs, FossilWatchAdapter adapter) {
+        super(FileHandle.NOTIFICATION_FILTER, createFile(configs), adapter);
+    }
+
+
+    public NotificationFilterPutRequest(ArrayList<NotificationConfiguration> configs, FossilWatchAdapter adapter) {
+        super(FileHandle.NOTIFICATION_FILTER, createFile(configs.toArray(new NotificationConfiguration[0])), adapter);
+    }
+
+    private static byte[] createFile(NotificationConfiguration[] configs){
+        ByteBuffer buffer = ByteBuffer.allocate(configs.length * 27);
+        buffer.order(ByteOrder.LITTLE_ENDIAN);
+
+        for(NotificationConfiguration config : configs){
+            buffer.putShort((short) 25); //packet length
+
+            CRC32 crc = new CRC32();
+            crc.update(config.getPackageName().getBytes());
+
+            buffer.put(PacketID.PACKAGE_NAME_CRC.id);
+            buffer.put((byte) 4);
+            buffer.putInt((int) crc.getValue());
+
+            buffer.put(PacketID.GROUP_ID.id);
+            buffer.put((byte) 1);
+            buffer.put((byte) 2);
+
+            buffer.put(PacketID.PRIORITY.id);
+            buffer.put((byte) 1);
+            buffer.put((byte) 0xFF);
+
+            buffer.put(PacketID.MOVEMENT.id);
+            buffer.put((byte) 8);
+            buffer.putShort(config.getHour())
+                    .putShort(config.getMin())
+                    .putShort(config.getSubEye())
+                    .putShort((short) 5000);
+
+            buffer.put(PacketID.VIBRATION.id);
+            buffer.put((byte) 1);
+            buffer.put(config.getVibration().getValue());
+        }
+
+        return buffer.array();
+    }
+
+    enum PacketID{
+        PACKAGE_NAME((byte) 0x01),
+        SENDER_NAME((byte) 0x02),
+        PACKAGE_NAME_CRC((byte) 0x04),
+        GROUP_ID((byte) 0x80),
+        APP_DISPLAY_NAME((byte) 0x81),
+        ICON((byte) 0x82),
+        PRIORITY((byte) 0xC1),
+        MOVEMENT((byte) 0xC2),
+        VIBRATION((byte) 0xC3);
+
+        byte id;
+
+        PacketID(byte id){
+            this.id = id;
+        }
+    }
+
+    enum VibrationType{
+        SINGLE_SHORT((byte) 5),
+        DOUBLE_SHORT((byte) 6),
+        TRIPLE_SHORT((byte) 7),
+        SINGLE_LONG((byte) 8),
+        SILENT((byte) 9);
+
+        byte id;
+        VibrationType(byte id){
+            this.id = id;
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/notification/NotificationType.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/notification/NotificationType.java
new file mode 100644
index 0000000..d220de4
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/notification/NotificationType.java
@@ -0,0 +1,37 @@
+/*  Copyright (C) 2021-2024 Arjan Schrijver
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.notification;
+
+public enum NotificationType {
+    INCOMING_CALL(1),
+    TEXT(2),
+    NOTIFICATION(3),
+    EMAIL(4),
+    CALENDAR(5),
+    MISSED_CALL(6),
+    DISMISS_NOTIFICATION(7);
+
+    private int type;
+
+    NotificationType(int type) {
+        this.type = type;
+    }
+
+    public int getType() {
+        return type;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/notification/PlayCallNotificationRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/notification/PlayCallNotificationRequest.java
new file mode 100644
index 0000000..391c61b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/notification/PlayCallNotificationRequest.java
@@ -0,0 +1,44 @@
+/*  Copyright (C) 2020-2024 Arjan Schrijver, Daniel Dakhno, mvn23
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.notification;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil.FossilWatchAdapter;
+
+public class PlayCallNotificationRequest extends PlayNotificationRequest {
+    private final static int MESSAGE_ID_CALL = 1;
+
+    private static int notificationFlags(boolean callStart, boolean quickReplies, int dndSuppressed) {
+        int flags = 0;
+        if (callStart && quickReplies) {
+            flags = 0b00111000;
+        } else if (callStart) {
+            flags = 0b00011000;
+        } else {
+            flags = 0b00000010;
+        }
+        return (flags | dndSuppressed);
+    }
+
+    public PlayCallNotificationRequest(String number, boolean callStart, boolean quickReplies, int dndSuppressed, FossilWatchAdapter adapter) {
+        super(callStart ? NotificationType.INCOMING_CALL : NotificationType.DISMISS_NOTIFICATION, notificationFlags(callStart, quickReplies, dndSuppressed),
+                ByteBuffer.wrap(new byte[]{(byte) 0x80, (byte) 0x00, (byte) 0x59, (byte) 0xB7}).order(ByteOrder.LITTLE_ENDIAN).getInt(),
+                number, "Incoming Call", MESSAGE_ID_CALL, adapter);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/notification/PlayNotificationRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/notification/PlayNotificationRequest.java
new file mode 100644
index 0000000..7583e1a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/notification/PlayNotificationRequest.java
@@ -0,0 +1,94 @@
+/*  Copyright (C) 2019-2024 Andreas Shimokawa, Arjan Schrijver, Daniel
+    Dakhno, hackoder
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.notification;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.Charset;
+import java.util.Arrays;
+import java.util.zip.CRC32;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil.FossilWatchAdapter;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.file.FileHandle;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.file.FilePutRequest;
+import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
+
+public abstract class PlayNotificationRequest extends FilePutRequest {
+    public PlayNotificationRequest(NotificationType notificationType, int flags, String packageName, FossilWatchAdapter adapter) {
+        super(FileHandle.NOTIFICATION_PLAY, createFile(notificationType, flags, packageName, packageName, packageName, getCurrentMessageId()), adapter);
+    }
+
+    public PlayNotificationRequest(NotificationType notificationType, int flags, String packageName, String sender, String message, int notificationId, FossilWatchAdapter adapter) {
+        super(FileHandle.NOTIFICATION_PLAY, createFile(notificationType, flags, packageName, sender, message, notificationId), adapter);
+    }
+
+    public PlayNotificationRequest(NotificationType notificationType, int flags, int packageCRC, String sender, String message, int messageId, FossilWatchAdapter adapter) {
+        super(FileHandle.NOTIFICATION_PLAY, createFile(notificationType, flags, "whatever", sender, message, packageCRC, messageId), adapter);
+    }
+
+    private static int getCurrentMessageId(){
+        return (int) System.currentTimeMillis();
+    }
+
+    private static byte[] createFile(NotificationType notificationType, int flags, String packageName, String sender, String message, int messageId){
+        CRC32 crc = new CRC32();
+        crc.update(packageName.getBytes());
+        return createFile(notificationType, flags, packageName, sender, message, (int)crc.getValue(), messageId);
+    }
+
+    private static byte[] createFile(NotificationType notificationType, int flags, String title, String sender, String message, int packageCrc, int messageId) {
+        byte lengthBufferLength = (byte) 10;
+        byte uidLength = (byte) 4;
+        byte appBundleCRCLength = (byte) 4;
+
+        Charset charsetUTF8 = Charset.forName("UTF-8");
+
+        String nullTerminatedTitle = StringUtils.terminateNull(title);
+        byte[] titleBytes = nullTerminatedTitle.getBytes(charsetUTF8);
+        String nullTerminatedSender = StringUtils.terminateNull(sender);
+        byte[] senderBytes = nullTerminatedSender.getBytes(charsetUTF8);
+        if (message.length() > 475) {
+            message = message.substring(0, 475);
+        }
+        String nullTerminatedMessage = StringUtils.terminateNull(message);
+        byte[] messageBytes = nullTerminatedMessage.getBytes(charsetUTF8);
+        short mainBufferLength = (short) (lengthBufferLength + uidLength + appBundleCRCLength + titleBytes.length + senderBytes.length + messageBytes.length);
+
+        ByteBuffer mainBuffer = ByteBuffer.allocate(mainBufferLength);
+        mainBuffer.order(ByteOrder.LITTLE_ENDIAN);
+
+        mainBuffer.putShort(mainBufferLength);
+
+        mainBuffer.put(lengthBufferLength);
+        mainBuffer.put((byte) notificationType.getType());
+        mainBuffer.put((byte) flags);
+        mainBuffer.put(uidLength);
+        mainBuffer.put(appBundleCRCLength);
+        mainBuffer.put((byte) titleBytes.length);
+        mainBuffer.put((byte) senderBytes.length);
+        mainBuffer.put((byte) messageBytes.length);
+
+        mainBuffer.putInt(messageId);
+        mainBuffer.putInt(packageCrc);
+        mainBuffer.put(titleBytes);
+        mainBuffer.put(senderBytes);
+        mainBuffer.put(messageBytes);
+        return mainBuffer.array();
+    }
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/notification/PlayTextNotificationRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/notification/PlayTextNotificationRequest.java
new file mode 100644
index 0000000..3436e8e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil/notification/PlayTextNotificationRequest.java
@@ -0,0 +1,30 @@
+/*  Copyright (C) 2020-2024 Arjan Schrijver, Daniel Dakhno, mvn23
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.notification;
+
+import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil.FossilWatchAdapter;
+
+public class PlayTextNotificationRequest extends PlayNotificationRequest {
+    public PlayTextNotificationRequest(String packageName, String sender, NotificationSpec notificationSpec, FossilWatchAdapter adapter) {
+        super(NotificationType.NOTIFICATION, 0x02 | notificationSpec.dndSuppressed, packageName, sender, notificationSpec.body, notificationSpec.getId(), adapter);
+    }
+
+    public PlayTextNotificationRequest(String packageName, FossilWatchAdapter adapter) {
+        super(NotificationType.NOTIFICATION, 0x02, packageName, adapter);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/alexa/AlexaMessageSetRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/alexa/AlexaMessageSetRequest.java
new file mode 100644
index 0000000..459adae
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/alexa/AlexaMessageSetRequest.java
@@ -0,0 +1,46 @@
+/*  Copyright (C) 2022-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.alexa;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil_hr.FossilHRWatchAdapter;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.json.JsonPutRequest;
+
+public class AlexaMessageSetRequest extends JsonPutRequest {
+    public AlexaMessageSetRequest(String message, boolean isResponse, FossilHRWatchAdapter adapter) {
+        super(createResponseObject(message, isResponse), adapter);
+    }
+
+    public static JSONObject createResponseObject(String message, boolean isResponse){
+        try {
+            return new JSONObject()
+                    .put("push", new JSONObject()
+                            .put("set", new JSONObject()
+                                    .put("AlexaApp._.config.msg", new JSONObject()
+                                            .put("res", message)
+                                            .put("is_resp", isResponse)
+                                    )
+                            )
+                    );
+        } catch (JSONException e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/application/ApplicationInformation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/application/ApplicationInformation.java
new file mode 100644
index 0000000..e6f285a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/application/ApplicationInformation.java
@@ -0,0 +1,47 @@
+/*  Copyright (C) 2021-2024 Arjan Schrijver, Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.application;
+
+public class ApplicationInformation implements Comparable<ApplicationInformation> {
+    String appName, version;
+    int hash;
+    byte fileHandle;
+
+    public ApplicationInformation(String appName, String version, int hash, byte fileHandle) {
+        this.appName = appName;
+        this.version = version;
+        this.hash = hash;
+        this.fileHandle = fileHandle;
+    }
+
+    public String getAppName() {
+        return appName;
+    }
+
+    public String getAppVersion() {
+        return version;
+    }
+
+    public byte getFileHandle() {
+        return fileHandle;
+    }
+
+    @Override
+    public int compareTo(ApplicationInformation o) {
+        return this.appName.toLowerCase().compareTo(o.getAppName().toLowerCase());
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/application/ApplicationsListRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/application/ApplicationsListRequest.java
new file mode 100644
index 0000000..b5903df
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/application/ApplicationsListRequest.java
@@ -0,0 +1,79 @@
+/*  Copyright (C) 2021-2024 Arjan Schrijver, Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.application;
+
+import android.content.pm.ApplicationInfo;
+
+import org.json.JSONArray;
+import org.json.JSONObject;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.GenericItem;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil_hr.FossilHRWatchAdapter;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.file.FileLookupAndGetRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.file.FileHandle;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.file.FileLookupRequest.FILE_LOOKUP_ERROR;
+
+public class ApplicationsListRequest extends FileLookupAndGetRequest{
+    public ApplicationsListRequest(FossilHRWatchAdapter adapter) {
+        super(FileHandle.APP_CODE, adapter);
+    }
+
+    public void handleFileData(byte[] fileData){
+        ArrayList<ApplicationInformation> applicationInfos = new ArrayList<>();
+        ByteBuffer buffer = ByteBuffer.wrap(fileData);
+        buffer.order(ByteOrder.LITTLE_ENDIAN);
+        buffer.position(12);
+        while(buffer.remaining() > 4){
+            short packetLength = buffer.getShort();
+            buffer.get();
+            int nameLength = buffer.get() - 1; // cutting off null byte
+            byte[] nameBuffer = new byte[nameLength];
+            buffer.get(nameBuffer);
+            String name = new String(nameBuffer);
+            buffer.get(); // null byte
+            byte handle = buffer.get();
+            int hash = buffer.getInt();
+            String version = String.format("%d.%d", buffer.get(), buffer.get());
+            buffer.get();  // unknown
+            buffer.get();  // unknown
+
+            applicationInfos.add(new ApplicationInformation(
+                    name,
+                    version,
+                    hash,
+                    handle
+            ));
+        }
+        Collections.sort(applicationInfos);
+        this.handleApplicationsList(applicationInfos);
+    }
+
+    public void handleApplicationsList(List<ApplicationInformation> installedApplications){
+
+    }
+
+    public void handleFileLookupError(FILE_LOOKUP_ERROR error){
+
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/async/ConfirmAppStatusRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/async/ConfirmAppStatusRequest.java
new file mode 100644
index 0000000..27f3431
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/async/ConfirmAppStatusRequest.java
@@ -0,0 +1,48 @@
+/*  Copyright (C) 2020-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.async;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil_hr.FossilHRWatchAdapter;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.json.JsonPutRequest;
+
+public class ConfirmAppStatusRequest extends JsonPutRequest {
+    public ConfirmAppStatusRequest(int requestId, FossilHRWatchAdapter adapter) {
+        super(createResponseObject(requestId), adapter);
+    }
+
+    private static JSONObject createResponseObject(int requestId){
+        try {
+            return new JSONObject()
+                    .put("res",
+                            new JSONObject()
+                            .put("id", requestId)
+                            .put("set", new JSONObject()
+                                    .put("master._.config.app_status", new JSONObject()
+                                            .put("message", "")
+                                            .put("type", "success")
+                                    )
+                            )
+                    );
+        } catch (JSONException e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/authentication/AuthenticationRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/authentication/AuthenticationRequest.java
new file mode 100644
index 0000000..e6d50be
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/authentication/AuthenticationRequest.java
@@ -0,0 +1,28 @@
+/*  Copyright (C) 2021-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.authentication;
+
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.FossilRequest;
+
+public abstract class AuthenticationRequest extends FossilRequest {
+    @Override
+    public UUID getRequestUUID() {
+        return UUID.fromString("3dda0005-957f-7d4a-34a6-74696673696d");
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/authentication/CheckDeviceNeedsConfirmationRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/authentication/CheckDeviceNeedsConfirmationRequest.java
new file mode 100644
index 0000000..675c318
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/authentication/CheckDeviceNeedsConfirmationRequest.java
@@ -0,0 +1,50 @@
+/*  Copyright (C) 2021-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.authentication;
+
+import android.bluetooth.BluetoothGattCharacteristic;
+
+public abstract class CheckDeviceNeedsConfirmationRequest extends AuthenticationRequest {
+    private boolean isFinished = false;
+    @Override
+    public byte[] getStartSequence() {
+        return new byte[]{0x01, 0x07};
+    }
+
+    @Override
+    public void handleResponse(BluetoothGattCharacteristic characteristic) {
+        if(!characteristic.getUuid().equals(getRequestUUID())){
+            throw new RuntimeException("wrong characteristic responded to authentication");
+        }
+        byte[] value = characteristic.getValue();
+        if(value.length != 3){
+            throw new RuntimeException("wrong authentication response length");
+        }
+        if(value[0] != 0x03 || value[1] != 0x07){
+            throw new RuntimeException("wrong authentication response bytes");
+        }
+        this.onResult(value[2] == 0x00);
+        this.isFinished = true;
+    }
+
+    public abstract void onResult(boolean needsConfirmation);
+
+    @Override
+    public boolean isFinished() {
+        return isFinished;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/authentication/CheckDevicePairingRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/authentication/CheckDevicePairingRequest.java
new file mode 100644
index 0000000..326b07a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/authentication/CheckDevicePairingRequest.java
@@ -0,0 +1,60 @@
+/*  Copyright (C) 2023-2024 Arjan Schrijver
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.authentication;
+
+import android.bluetooth.BluetoothGattCharacteristic;
+
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.FossilRequest;
+
+public class CheckDevicePairingRequest extends FossilRequest {
+    protected boolean isFinished = false;
+
+    @Override
+    public byte[] getStartSequence() {
+        return new byte[]{0x01, 0x16};
+    }
+
+    @Override
+    public void handleResponse(BluetoothGattCharacteristic characteristic) {
+        if(!characteristic.getUuid().equals(getRequestUUID())){
+            throw new RuntimeException("wrong characteristic responded to pairing");
+        }
+        byte[] value = characteristic.getValue();
+        if(value.length != 3){
+            throw new RuntimeException("wrong pairing response length");
+        }
+        if(value[0] != 0x03 || value[1] != 0x16){
+            throw new RuntimeException("wrong pairing response bytes");
+        }
+        this.onResult(value[2] == 0x01);
+        this.isFinished = true;
+    }
+
+    public void onResult(boolean confirmationSuccess){};
+
+    @Override
+    public boolean isFinished() {
+        return isFinished;
+    }
+
+    @Override
+    public UUID getRequestUUID() {
+        return UUID.fromString("3dda0002-957f-7d4a-34a6-74696673696d");
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/authentication/ConfirmOnDeviceRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/authentication/ConfirmOnDeviceRequest.java
new file mode 100644
index 0000000..a6001d8
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/authentication/ConfirmOnDeviceRequest.java
@@ -0,0 +1,51 @@
+/*  Copyright (C) 2021-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.authentication;
+
+import android.bluetooth.BluetoothGattCharacteristic;
+
+public class ConfirmOnDeviceRequest extends AuthenticationRequest {
+    protected boolean isFinished = false;
+
+    @Override
+    public byte[] getStartSequence() {
+        return new byte[]{0x02, 0x06, 0x30, 0x75, 0x00, 0x00, 0x00};
+    }
+
+    @Override
+    public void handleResponse(BluetoothGattCharacteristic characteristic) {
+        if(!characteristic.getUuid().equals(getRequestUUID())){
+            throw new RuntimeException("wrong characteristic responded to authentication");
+        }
+        byte[] value = characteristic.getValue();
+        if(value.length != 4){
+            throw new RuntimeException("wrong authentication response length");
+        }
+        if(value[0] != 0x03 || value[1] != 0x06 || value[2] != 0x00){
+            throw new RuntimeException("wrong authentication response bytes");
+        }
+        this.onResult(value[3] == 0x01);
+        this.isFinished = true;
+    }
+
+    public void onResult(boolean confirmationSuccess){};
+
+    @Override
+    public boolean isFinished() {
+        return isFinished;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/authentication/PerformDevicePairingRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/authentication/PerformDevicePairingRequest.java
new file mode 100644
index 0000000..1fa9975
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/authentication/PerformDevicePairingRequest.java
@@ -0,0 +1,24 @@
+/*  Copyright (C) 2023-2024 Arjan Schrijver
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.authentication;
+
+public class PerformDevicePairingRequest extends CheckDevicePairingRequest {
+    @Override
+    public byte[] getStartSequence() {
+        return new byte[]{0x02, 0x16};
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/authentication/VerifyPrivateKeyRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/authentication/VerifyPrivateKeyRequest.java
new file mode 100644
index 0000000..9c3e15e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/authentication/VerifyPrivateKeyRequest.java
@@ -0,0 +1,135 @@
+/*  Copyright (C) 2019-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.authentication;
+
+import android.bluetooth.BluetoothGattCharacteristic;
+
+import java.nio.ByteBuffer;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.util.Random;
+import java.util.UUID;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil_hr.FossilHRWatchAdapter;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.FossilRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.file.ResultCode;
+
+public class VerifyPrivateKeyRequest extends AuthenticationRequest {
+    private final FossilHRWatchAdapter adapter;
+    private byte[] key, randomPhoneNumber;
+    private boolean isFinished = false;
+
+    public VerifyPrivateKeyRequest(byte[] key, FossilHRWatchAdapter adapter) {
+        this.adapter = adapter;
+        this.key = key;
+
+    }
+
+    @Override
+    public void handleResponse(BluetoothGattCharacteristic characteristic) {
+        super.handleResponse(characteristic);
+        byte[] value = characteristic.getValue();
+
+        ByteBuffer buffer = ByteBuffer.wrap(value);
+
+        if (value[1] == 1) {
+            try {
+                byte[] bytesToDecrypt = new byte[16];
+
+                buffer.position(4);
+
+                buffer.get(bytesToDecrypt, 0, 16);
+
+                SecretKeySpec keySpec = new SecretKeySpec(this.key, "AES");
+                Cipher cipher = null;
+                cipher = Cipher.getInstance("AES/CBC/NoPadding");
+                cipher.init(Cipher.DECRYPT_MODE, keySpec, new IvParameterSpec(new byte[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}));
+                byte[] result = cipher.doFinal(bytesToDecrypt);
+
+                byte[] bytesToEncrypt = new byte[16];
+
+                System.arraycopy(result, 0, bytesToEncrypt, 8, 8);
+                System.arraycopy(result, 8, bytesToEncrypt, 0, 8);
+
+                byte[] watchRandomNumber = new byte[8];
+                System.arraycopy(result, 0, watchRandomNumber, 0, 8);
+
+                adapter.setWatchRandomNumber(watchRandomNumber);
+                adapter.setPhoneRandomNumber(randomPhoneNumber);
+
+                cipher = Cipher.getInstance("AES/CBC/NoPadding");
+                cipher.init(Cipher.ENCRYPT_MODE, keySpec, new IvParameterSpec(new byte[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}));
+                result = cipher.doFinal(bytesToEncrypt);
+
+                byte[] payload = new byte[19];
+                payload[0] = 2;
+                payload[1] = 2;
+                payload[2] = 1;
+
+                System.arraycopy(result, 0, payload, 3, 16);
+
+                new TransactionBuilder("send encrypted random numbers")
+                        .write(characteristic, payload)
+                        .queue(this.adapter.getDeviceSupport().getQueue());
+            } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException | InvalidAlgorithmParameterException e) {
+                throw new RuntimeException(e);
+            }
+        } else if (value[1] == 2) {
+            ResultCode code = ResultCode.fromCode(value[2]);
+
+            handleAuthenticationResult(code.inidicatesSuccess());
+
+            if (!code.inidicatesSuccess()) throw new RuntimeException("Authentication error: " + code + "   (" + value[2] + ")");
+
+
+            this.isFinished = true;
+        }
+    }
+
+    protected void handleAuthenticationResult(boolean success){}
+
+    @Override
+    public boolean isFinished() {
+        return isFinished;
+    }
+
+    @Override
+    public byte[] getStartSequence() {
+        ByteBuffer buffer = ByteBuffer.allocate(11);
+
+        buffer.put((byte) 0x02);
+        buffer.put((byte) 0x01);
+        buffer.put((byte) 0x01);
+
+        this.randomPhoneNumber = new byte[8];
+
+        new Random().nextBytes(randomPhoneNumber);
+
+        buffer.put(randomPhoneNumber);
+
+        return buffer.array();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/buttons/ButtonConfiguration.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/buttons/ButtonConfiguration.java
new file mode 100644
index 0000000..703cb10
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/buttons/ButtonConfiguration.java
@@ -0,0 +1,45 @@
+/*  Copyright (C) 2020-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.buttons;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+public class ButtonConfiguration {
+    private String triggerEvent;
+    private String action;
+
+    public ButtonConfiguration(String triggerEvent, String action) {
+        this.triggerEvent = triggerEvent;
+        this.action = action;
+    }
+
+    public String getAction() {
+        return action;
+    }
+
+    public JSONObject toJsonObject(){
+        try {
+            return new JSONObject()
+                    .put("button_evt", triggerEvent)
+                    .put("name", action);
+        } catch (JSONException e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/buttons/ButtonConfigurationPutRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/buttons/ButtonConfigurationPutRequest.java
new file mode 100644
index 0000000..f17f7b6
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/buttons/ButtonConfigurationPutRequest.java
@@ -0,0 +1,53 @@
+/*  Copyright (C) 2019-2024 Andreas Shimokawa, Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.buttons;
+
+import android.widget.Toast;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil.FossilWatchAdapter;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil_hr.FossilHRWatchAdapter;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.json.JsonPutRequest;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class ButtonConfigurationPutRequest extends JsonPutRequest {
+    public ButtonConfigurationPutRequest(ButtonConfiguration[] buttonConfigurations, FossilHRWatchAdapter adapter) {
+        super(createObject(buttonConfigurations), adapter);
+    }
+
+    private static JSONObject createObject(ButtonConfiguration[] buttonConfigurations) {
+        try {
+            JSONArray configuration = new JSONArray();
+            for(ButtonConfiguration buttonConfiguration : buttonConfigurations){
+                configuration.put(buttonConfiguration.toJsonObject());
+            }
+            return new JSONObject()
+                    .put("push", new JSONObject()
+                            .put("set", new JSONObject()
+                                    .put("master._.config.buttons", configuration)
+                            )
+                    );
+        } catch (JSONException e) {
+            GB.toast("error creating json", Toast.LENGTH_LONG, GB.ERROR, e);
+        }
+
+        return null;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/commute/CommuteConfigPutRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/commute/CommuteConfigPutRequest.java
new file mode 100644
index 0000000..06d0940
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/commute/CommuteConfigPutRequest.java
@@ -0,0 +1,49 @@
+/*  Copyright (C) 2021-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.commute;
+
+import android.widget.Toast;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil_hr.FossilHRWatchAdapter;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.buttons.ButtonConfiguration;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.json.JsonPutRequest;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class CommuteConfigPutRequest extends JsonPutRequest {
+    public CommuteConfigPutRequest(String[] menuItems, FossilHRWatchAdapter adapter) {
+        super(createObject(menuItems), adapter);
+    }
+
+    private static JSONObject createObject(String[] menuItems) {
+        try {
+            return new JSONObject()
+                    .put("push", new JSONObject()
+                            .put("set", new JSONObject()
+                                    .put("commuteApp._.config.destinations", new JSONArray(menuItems))
+                            )
+                    );
+        } catch (JSONException e) {
+            GB.toast("error creating json", Toast.LENGTH_LONG, GB.ERROR, e);
+        }
+
+        return null;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/configuration/ConfigurationGetRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/configuration/ConfigurationGetRequest.java
new file mode 100644
index 0000000..d9d49c1
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/configuration/ConfigurationGetRequest.java
@@ -0,0 +1,87 @@
+/*  Copyright (C) 2019-2024 Arjan Schrijver, Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.configuration;
+
+import android.widget.Toast;
+
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.BatteryState;
+import nodomain.freeyourgadget.gadgetbridge.model.GenericItem;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.QHybridSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil_hr.FossilHRWatchAdapter;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.file.FileHandle;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.configuration.ConfigurationPutRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.file.FileEncryptedInterface;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.file.FileEncryptedLookupAndGetRequest;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class ConfigurationGetRequest extends FileEncryptedLookupAndGetRequest implements FileEncryptedInterface {
+    public ConfigurationGetRequest(FossilHRWatchAdapter adapter) {
+        super(FileHandle.CONFIGURATION, adapter);
+    }
+
+    @Override
+    public void handleFileData(byte[] fileData) {
+        byte[] data = new byte[fileData.length - 12 - 4];
+
+        System.arraycopy(fileData, 12, data, 0, data.length);
+        log("config file: " + getAdapter().arrayToString(fileData));
+        log("config file: " + getAdapter().arrayToString(data));
+
+        GBDevice device = getAdapter().getDeviceSupport().getDevice();
+
+        ConfigurationPutRequest.ConfigItem[] items = ConfigurationPutRequest.parsePayload(data);
+
+        for(ConfigurationPutRequest.ConfigItem item : items){
+            if(item instanceof ConfigurationPutRequest.VibrationStrengthConfigItem){
+                device.addDeviceInfo(new GenericItem(QHybridSupport.ITEM_VIBRATION_STRENGTH, String.valueOf(((ConfigurationPutRequest.VibrationStrengthConfigItem) item).getValue())));
+            }else if(item instanceof ConfigurationPutRequest.DailyStepGoalConfigItem){
+                device.addDeviceInfo(new GenericItem(QHybridSupport.ITEM_STEP_GOAL, String.valueOf(((ConfigurationPutRequest.DailyStepGoalConfigItem) item).getValue())));
+            }else if(item instanceof ConfigurationPutRequest.CurrentStepCountConfigItem){
+                device.addDeviceInfo(new GenericItem(QHybridSupport.ITEM_STEP_COUNT, String.valueOf(((ConfigurationPutRequest.CurrentStepCountConfigItem) item).getValue())));
+            }else if(item instanceof ConfigurationPutRequest.TimezoneOffsetConfigItem) {
+                device.addDeviceInfo(new GenericItem(QHybridSupport.ITEM_TIMEZONE_OFFSET, String.valueOf(((ConfigurationPutRequest.TimezoneOffsetConfigItem) item).getValue())));
+            }else if(item instanceof ConfigurationPutRequest.BatteryConfigItem){
+                device.setBatteryLevel((short) ((ConfigurationPutRequest.BatteryConfigItem) item).getBatteryPercentage());
+                device.setBatteryVoltage(((ConfigurationPutRequest.BatteryConfigItem) item).getBatteryVoltage() / 1000f);
+
+                GBDeviceEventBatteryInfo batteryInfo = new GBDeviceEventBatteryInfo();
+                batteryInfo.level = (short) ((ConfigurationPutRequest.BatteryConfigItem) item).getBatteryPercentage();
+                batteryInfo.state = BatteryState.BATTERY_NORMAL;
+                getAdapter().getDeviceSupport().handleGBDeviceEvent(batteryInfo);
+            }else if(item instanceof ConfigurationPutRequest.HeartRateMeasurementModeItem){
+                device.addDeviceInfo(new GenericItem(QHybridSupport.ITEM_HEART_RATE_MEASUREMENT_MODE, String.valueOf(((ConfigurationPutRequest.HeartRateMeasurementModeItem) item).getValue())));
+            }
+        }
+
+        device.sendDeviceUpdateIntent(getAdapter().getContext());
+
+        handleConfiguration(items);
+    }
+
+    @Override
+    public void handleFileLookupError(FILE_LOOKUP_ERROR error) {
+        if(error == FILE_LOOKUP_ERROR.FILE_EMPTY){
+            GB.toast("config file empty", Toast.LENGTH_LONG,  GB.ERROR);
+        }else{
+            throw new RuntimeException("strange lookup stuff");
+        }
+    }
+
+    protected void handleConfiguration(ConfigurationPutRequest.ConfigItem[] items){}
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/configuration/ConfigurationPutRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/configuration/ConfigurationPutRequest.java
new file mode 100644
index 0000000..9849e1b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/configuration/ConfigurationPutRequest.java
@@ -0,0 +1,51 @@
+/*  Copyright (C) 2019-2024 Andreas Shimokawa, Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.configuration;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil_hr.FossilHRWatchAdapter;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.configuration.ConfigurationPutRequest.ConfigItem;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.file.FileEncryptedPutRequest;
+
+public class ConfigurationPutRequest extends FileEncryptedPutRequest {
+    public ConfigurationPutRequest(ConfigItem item, FossilHRWatchAdapter adapter) {
+        super((short) 0x0800, createFileContent(new ConfigItem[]{item}), adapter);
+    }
+
+    public ConfigurationPutRequest(ConfigItem[] items, FossilHRWatchAdapter adapter) {
+        super((short) 0x0800, createFileContent(items), adapter);
+    }
+
+    private static byte[] createFileContent(ConfigItem[] items) {
+        int overallSize = items.length * 3;
+        for(ConfigItem item : items){
+            overallSize += item.getItemSize();
+        }
+        ByteBuffer buffer = ByteBuffer.allocate(overallSize);
+        buffer.order(ByteOrder.LITTLE_ENDIAN);
+        for(ConfigItem item : items){
+            buffer.putShort(item.getId());
+            buffer.put((byte) item.getItemSize());
+            buffer.put(item.getContent());
+        }
+
+        return buffer.array();
+    }
+}
+
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/file/AssetFile.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/file/AssetFile.java
new file mode 100644
index 0000000..7757883
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/file/AssetFile.java
@@ -0,0 +1,58 @@
+/*  Copyright (C) 2020-2024 Andreas Shimokawa, Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.file;
+
+import java.nio.ByteBuffer;
+import java.util.zip.CRC32;
+
+import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
+
+
+public class AssetFile {
+    private String fileName;
+    private byte[] fileData;
+
+    protected AssetFile(String fileName, byte[] fileData) {
+        this.fileName = fileName;
+        this.fileData = fileData;
+    }
+
+    public AssetFile(byte[] fileData) {
+        CRC32 crc = new CRC32();
+        crc.update(fileData);
+
+        this.fileName = StringUtils.bytesToHex(
+                ByteBuffer.allocate(4)
+                .putInt((int) crc.getValue())
+                .array()
+        );
+
+        this.fileData = fileData;
+    }
+
+    public String getFileName() {
+        return fileName;
+    }
+
+    public byte[] getFileData() {
+        return fileData;
+    }
+
+    public void setFileName(String fileName) {
+        this.fileName = fileName;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/file/AssetFilePutRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/file/AssetFilePutRequest.java
new file mode 100644
index 0000000..0f759aa
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/file/AssetFilePutRequest.java
@@ -0,0 +1,61 @@
+/*  Copyright (C) 2019-2024 Andreas Shimokawa, Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.file;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil.FossilWatchAdapter;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.file.FileHandle;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.file.FilePutRequest;
+
+public class AssetFilePutRequest extends FilePutRequest {
+    public AssetFilePutRequest(AssetFile[] files, FileHandle handle, FossilWatchAdapter adapter) throws IOException {
+        super(handle, prepareFileData(files), adapter);
+    }
+    public AssetFilePutRequest(AssetFile file, FileHandle handle, FossilWatchAdapter adapter) {
+        super(handle, prepareFileData(file), adapter);
+    }
+
+    private static byte[] prepareFileData(AssetFile[] files) throws IOException {
+        ByteArrayOutputStream stream = new ByteArrayOutputStream();
+
+        for (AssetFile file : files) {
+            stream.write(
+                    prepareFileData(file)
+            );
+        }
+
+        return stream.toByteArray();
+    }
+
+    private static byte[] prepareFileData(AssetFile file){
+        int size = file.getFileName().length() + file.getFileData().length + 1; // null byte
+        ByteBuffer buffer = ByteBuffer.allocate(size + 2);
+
+        buffer.order(ByteOrder.LITTLE_ENDIAN);
+
+        buffer.putShort((short)(size));
+        buffer.put(file.getFileName().getBytes());
+        buffer.put((byte) 0x00);
+        buffer.put(file.getFileData());
+
+        return buffer.array();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/file/FileEncryptedGetRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/file/FileEncryptedGetRequest.java
new file mode 100644
index 0000000..ce9a3bf
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/file/FileEncryptedGetRequest.java
@@ -0,0 +1,244 @@
+/*  Copyright (C) 2019-2024 Arjan Schrijver, Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.file;
+
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.widget.Toast;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.util.UUID;
+import java.util.zip.CRC32;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil.FossilWatchAdapter;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil_hr.FossilHRWatchAdapter;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.file.FileHandle;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.FossilRequest;
+import nodomain.freeyourgadget.gadgetbridge.util.CRC32C;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
+
+public abstract class FileEncryptedGetRequest extends FossilRequest implements FileEncryptedInterface{
+    private byte majorHandle;
+    private byte minorHandle;
+    private FossilHRWatchAdapter adapter;
+
+    private ByteBuffer fileBuffer;
+
+    private byte[] fileData;
+
+    private boolean finished = false;
+
+    private Cipher cipher;
+    private SecretKeySpec keySpec;
+    private byte[] originalIv;
+
+    int fileSize;
+
+    private int packetCount = 0;
+    private int ivIncrementor = 0x1f;
+
+    public FileEncryptedGetRequest(byte majorHandle, byte minorHandle, FossilHRWatchAdapter adapter) {
+        this.majorHandle = majorHandle;
+        this.minorHandle = minorHandle;
+        this.adapter = adapter;
+
+        this.data =
+                createBuffer()
+                        .put(minorHandle)
+                        .put(majorHandle)
+                        .putInt(0)
+                        .putInt(0xFFFFFFFF)
+                        .array();
+    }
+
+    public FileEncryptedGetRequest(short handle, FossilHRWatchAdapter adapter) {
+        this((byte) ((handle >> 8) & 0xff), (byte) (handle), adapter);
+    }
+
+    private void initDecryption() {
+        try {
+            cipher = Cipher.getInstance("AES/CTR/NoPadding");
+            try {
+                keySpec = new SecretKeySpec(this.adapter.getSecretKey(), "AES");
+            } catch (IllegalAccessException e) {
+                GB.toast("error getting key: " + e.getMessage(), Toast.LENGTH_LONG, GB.ERROR, e);
+                return;
+            }
+
+
+            originalIv = new byte[16];
+
+
+            byte[] phoneRandomNumber = adapter.getPhoneRandomNumber();
+            byte[] watchRandomNumber = adapter.getWatchRandomNumber();
+
+            System.arraycopy(phoneRandomNumber, 0, originalIv, 2, 6);
+            System.arraycopy(watchRandomNumber, 0, originalIv, 9, 7);
+
+            originalIv[7]++;
+        } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
+            e.printStackTrace();
+        }
+    }
+
+    public FossilWatchAdapter getAdapter() {
+        return adapter;
+    }
+
+    private byte[] incrementIV(byte[] iv, int amount){
+        byte[] incrementedIv = new byte[iv.length];
+        System.arraycopy(iv, 0, incrementedIv, 0, iv.length);
+        ByteBuffer buffer = ByteBuffer.wrap(incrementedIv);
+        int number = buffer.getInt(12);
+        number += amount;
+        buffer.position(12);
+        buffer.putInt(number);
+        return buffer.array();
+    }
+
+    @Override
+    public boolean isFinished() {
+        return finished;
+    }
+
+    @Override
+    public void handleResponse(BluetoothGattCharacteristic characteristic) {
+        byte[] value = characteristic.getValue();
+        byte first = value[0];
+        if (characteristic.getUuid().toString().equals("3dda0003-957f-7d4a-34a6-74696673696d")) {
+            if ((first & 0x0F) == 1) {
+                ByteBuffer buffer = ByteBuffer.wrap(value);
+                buffer.order(ByteOrder.LITTLE_ENDIAN);
+
+                this.initDecryption();
+
+                short minorHandle = buffer.get(1);
+                short majorHandle = buffer.get(2);
+                fileSize = buffer.getInt(4);
+
+                byte status = buffer.get(3);
+
+                ResultCode code = ResultCode.fromCode(status);
+                if (!code.inidicatesSuccess()) {
+                    throw new RuntimeException("FileGet error: " + code + "   (" + status + ")");
+                }
+
+                if (this.minorHandle != minorHandle) {
+                    throw new RuntimeException("minor handle: " + minorHandle + "   expected: " + this.minorHandle);
+                }
+                if (this.majorHandle != majorHandle) {
+                    throw new RuntimeException("major handle: " + majorHandle + "   expected: " + this.majorHandle);
+                }
+                log("file size: " + fileSize);
+                fileBuffer = ByteBuffer.allocate(fileSize);
+            } else if ((first & 0x0F) == 8) {
+                this.finished = true;
+
+                ByteBuffer buffer = ByteBuffer.wrap(value);
+                buffer.order(ByteOrder.LITTLE_ENDIAN);
+
+                short handle = buffer.getShort(1);
+                if (this.minorHandle != minorHandle) {
+                    throw new RuntimeException("minor handle: " + minorHandle + "   expected: " + this.minorHandle);
+                }
+                if (this.majorHandle != majorHandle) {
+                    throw new RuntimeException("major handle: " + majorHandle + "   expected: " + this.majorHandle);
+                }
+
+                CRC32 crc = new CRC32();
+                crc.update(this.fileData);
+
+                CRC32C c = new CRC32C();
+                c.update(this.fileData, 0, fileData.length);
+
+                int crcExpected = buffer.getInt(8);
+
+                if ((int) crc.getValue() != crcExpected) {
+                    throw new RuntimeException("crc: " + crc.getValue() + "   expected: " + crcExpected);
+                }
+
+                this.handleFileData(this.fileData);
+            }
+        } else if (characteristic.getUuid().toString().equals("3dda0004-957f-7d4a-34a6-74696673696d")) {
+            try {
+                byte[] result = null;
+                if(packetCount == 1) {
+                    for(int testIvSummand = 0x1e; testIvSummand < 0x30; testIvSummand++){
+                        byte[] iv = incrementIV(originalIv, testIvSummand);
+                        cipher.init(Cipher.DECRYPT_MODE, keySpec, new IvParameterSpec(iv));
+                        result = cipher.doFinal(value);
+
+                        int currentLength = fileBuffer.position() + result.length - 1;
+
+                        byte expectedByte = (currentLength == fileSize) ? (byte)0x81 : (byte)0x01; // 0x81 indicated the last payload
+
+                        if(result[0] == expectedByte){
+                            this.ivIncrementor = testIvSummand;
+                            log("iv summand: " + testIvSummand);
+                            break;
+                        }
+                        log("no iv summand found");
+                    }
+                }else{
+                    byte[] iv = incrementIV(originalIv, ivIncrementor * packetCount);
+
+                    cipher.init(Cipher.DECRYPT_MODE, keySpec, new IvParameterSpec(iv));
+                    result = cipher.doFinal(value);
+                }
+
+                log("decryption result: " + StringUtils.bytesToHex(result));
+                packetCount++;
+
+                fileBuffer.put(result, 1, result.length - 1);
+                if ((result[0] & 0x80) == 0x80) {
+                    this.fileData = fileBuffer.array();
+                }
+            } catch (BadPaddingException | IllegalBlockSizeException | InvalidAlgorithmParameterException | InvalidKeyException e) {
+                e.printStackTrace();
+                throw new RuntimeException(e);
+            }
+        }
+    }
+
+    @Override
+    public UUID getRequestUUID() {
+        return UUID.fromString("3dda0003-957f-7d4a-34a6-74696673696d");
+    }
+
+    @Override
+    public byte[] getStartSequence() {
+        return new byte[]{1};
+    }
+
+    @Override
+    public int getPayloadLength() {
+        return 11;
+    }
+
+    abstract public void handleFileData(byte[] fileData);
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/file/FileEncryptedInterface.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/file/FileEncryptedInterface.java
new file mode 100644
index 0000000..c4dbc5e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/file/FileEncryptedInterface.java
@@ -0,0 +1,20 @@
+/*  Copyright (C) 2020-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.file;
+
+public interface FileEncryptedInterface {
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/file/FileEncryptedLookupAndGetRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/file/FileEncryptedLookupAndGetRequest.java
new file mode 100644
index 0000000..6b6d5f7
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/file/FileEncryptedLookupAndGetRequest.java
@@ -0,0 +1,39 @@
+/*  Copyright (C) 2019-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.file;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil_hr.FossilHRWatchAdapter;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.file.FileHandle;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.file.FileLookupRequest;
+
+public abstract class FileEncryptedLookupAndGetRequest extends FileLookupRequest {
+    public FileEncryptedLookupAndGetRequest(FileHandle fileHandle, FossilHRWatchAdapter adapter) {
+        super(fileHandle, adapter);
+    }
+
+    @Override
+    public void handleFileLookup(short fileHandle){
+        getAdapter().queueWrite(new FileEncryptedGetRequest(getHandle(), (FossilHRWatchAdapter) getAdapter()) {
+            @Override
+            public void handleFileData(byte[] fileData) {
+                FileEncryptedLookupAndGetRequest.this.handleFileData(fileData);
+            }
+        }, true);
+    }
+
+    abstract public void handleFileData(byte[] fileData);
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/file/FileEncryptedPutRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/file/FileEncryptedPutRequest.java
new file mode 100644
index 0000000..a45e65d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/file/FileEncryptedPutRequest.java
@@ -0,0 +1,279 @@
+/*  Copyright (C) 2019-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.file;
+
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.widget.Toast;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.UUID;
+import java.util.zip.CRC32;
+
+import javax.crypto.Cipher;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil.FossilWatchAdapter;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil_hr.FossilHRWatchAdapter;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.FossilRequest;
+import nodomain.freeyourgadget.gadgetbridge.util.CRC32C;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class FileEncryptedPutRequest extends FossilRequest implements FileEncryptedInterface {
+    public enum UploadState {INITIALIZED, UPLOADING, CLOSING, UPLOADED}
+
+    public UploadState state;
+
+    private ArrayList<byte[]> packets = new ArrayList<>();
+
+    private short handle;
+
+    private FossilHRWatchAdapter adapter;
+
+    private byte[] file;
+
+    private int fullCRC;
+
+    public FileEncryptedPutRequest(short handle, byte[] file, FossilHRWatchAdapter adapter) {
+        this.handle = handle;
+        this.adapter = adapter;
+
+        int fileLength = file.length + 16;
+        ByteBuffer buffer = this.createBuffer();
+        buffer.putShort(1, handle);
+        buffer.putInt(3, 0);
+        buffer.putInt(7, fileLength);
+        buffer.putInt(11, fileLength);
+
+        this.data = buffer.array();
+
+        this.file = file;
+
+        state = UploadState.INITIALIZED;
+    }
+
+    public short getHandle() {
+        return handle;
+    }
+
+    @Override
+    public void handleResponse(BluetoothGattCharacteristic characteristic) {
+        byte[] value = characteristic.getValue();
+        if (characteristic.getUuid().toString().equals("3dda0003-957f-7d4a-34a6-74696673696d")) {
+            int responseType = value[0] & 0x0F;
+            log("response: " + responseType);
+            switch (responseType) {
+                case 3: {
+                    if (value.length != 5 || (value[0] & 0x0F) != 3) {
+                        throw new RuntimeException("wrong answer header");
+                    }
+                    state = UploadState.UPLOADING;
+
+                    TransactionBuilder transactionBuilder = new TransactionBuilder("file upload");
+                    BluetoothGattCharacteristic uploadCharacteristic = adapter.getDeviceSupport().getCharacteristic(UUID.fromString("3dda0004-957f-7d4a-34a6-74696673696d"));
+
+                    this.prepareFilePackets(this.file);
+
+                    SecretKeySpec keySpec = null;
+                    try {
+                        keySpec = new SecretKeySpec(this.adapter.getSecretKey(), "AES");
+                    } catch (IllegalAccessException e) {
+                        GB.toast("error getting key: " + e.getMessage(), Toast.LENGTH_LONG, GB.ERROR, e);
+                        return;
+                    }
+                    try {
+                        Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");
+
+                        byte[] fileIV = new byte[16];
+
+
+                        byte[] phoneRandomNumber = adapter.getPhoneRandomNumber();
+                        byte[] watchRandomNumber = adapter.getWatchRandomNumber();
+
+                        System.arraycopy(phoneRandomNumber, 0, fileIV, 2, 6);
+                        System.arraycopy(watchRandomNumber, 0, fileIV, 9, 7);
+
+                        fileIV[7]++;
+
+                        cipher.init(Cipher.ENCRYPT_MODE, keySpec, new IvParameterSpec(fileIV));
+
+                        for (byte[] packet : packets) {
+                            byte[] result = cipher.doFinal(packet);
+                            transactionBuilder.write(uploadCharacteristic, result);
+                        }
+                    }catch (Exception e){
+                        GB.toast("error encrypting file", Toast.LENGTH_LONG, GB.ERROR, e);
+                    }
+
+                    transactionBuilder.queue(adapter.getDeviceSupport().getQueue());
+                    break;
+                }
+                case 8: {
+                    if (value.length == 4) return;
+                    ByteBuffer buffer = ByteBuffer.wrap(value);
+                    buffer.order(ByteOrder.LITTLE_ENDIAN);
+                    short handle = buffer.getShort(1);
+                    int crc = buffer.getInt(8);
+                    byte status = value[3];
+
+                    ResultCode code = ResultCode.fromCode(status);
+                    if(!code.inidicatesSuccess()){
+                        throw new RuntimeException("upload status: " + code + "   (" + status + ")");
+                    }
+
+                    if (handle != this.handle) {
+                        throw new RuntimeException("wrong response handle");
+                    }
+
+                    if (crc != this.fullCRC) {
+                        throw new RuntimeException("file upload exception: wrong crc");
+                    }
+
+
+                    ByteBuffer buffer2 = ByteBuffer.allocate(3);
+                    buffer2.order(ByteOrder.LITTLE_ENDIAN);
+                    buffer2.put((byte) 4);
+                    buffer2.putShort(this.handle);
+
+                    new TransactionBuilder("file close")
+                            .write(
+                                    adapter.getDeviceSupport().getCharacteristic(UUID.fromString("3dda0003-957f-7d4a-34a6-74696673696d")),
+                                    buffer2.array()
+                            )
+                            .queue(adapter.getDeviceSupport().getQueue());
+
+                    this.state = UploadState.CLOSING;
+                    break;
+                }
+                case 4: {
+                    if (value.length == 9) return;
+                    if (value.length != 4 || (value[0] & 0x0F) != 4) {
+                        throw new RuntimeException("wrong file closing header");
+                    }
+                    ByteBuffer buffer = ByteBuffer.wrap(value);
+                    buffer.order(ByteOrder.LITTLE_ENDIAN);
+
+                    short handle = buffer.getShort(1);
+
+                    if (handle != this.handle) {
+                        onFilePut(false);
+                        throw new RuntimeException("wrong file closing handle");
+                    }
+
+                    byte status = buffer.get(3);
+
+                    ResultCode code = ResultCode.fromCode(status);
+                    if(!code.inidicatesSuccess()){
+                        onFilePut(false);
+                        throw new RuntimeException("wrong closing status: " + code + "   (" + status + ")");
+                    }
+
+                    this.state = UploadState.UPLOADED;
+
+                    onFilePut(true);
+
+                    log("uploaded file");
+
+                    break;
+                }
+                case 9: {
+                    this.onFilePut(false);
+                    throw new RuntimeException("file put timeout");
+                    /*timeout = true;
+                    ByteBuffer buffer2 = ByteBuffer.allocate(3);
+                    buffer2.order(ByteOrder.LITTLE_ENDIAN);
+                    buffer2.put((byte) 4);
+                    buffer2.putShort(this.handle);
+
+                    new TransactionBuilder("file close")
+                            .write(
+                                    adapter.getDeviceSupport().getCharacteristic(UUID.fromString("3dda0003-957f-7d4a-34a6-74696673696d")),
+                                    buffer2.array()
+                            )
+                            .queue(adapter.getDeviceSupport().getQueue());
+
+                    this.state = UploadState.CLOSING;
+                    break;*/
+                }
+            }
+        }
+    }
+
+    @Override
+    public boolean isFinished() {
+        return this.state == UploadState.UPLOADED;
+    }
+
+    private void prepareFilePackets(byte[] file) {
+        int maxPacketSize = adapter.getMTU() - 4;
+
+        ByteBuffer buffer = ByteBuffer.allocate(file.length + 12 + 4);
+        buffer.order(ByteOrder.LITTLE_ENDIAN);
+
+        buffer.putShort(handle);
+        buffer.put((byte) 2);
+        buffer.put((byte) 0);
+        buffer.putInt(0);
+        buffer.putInt(file.length);
+
+        buffer.put(file);
+
+        CRC32C crc = new CRC32C();
+
+        crc.update(file,0,file.length);
+        buffer.putInt((int) crc.getValue());
+
+        byte[] data = buffer.array();
+
+        CRC32 fullCRC = new CRC32();
+
+        fullCRC.update(data);
+        this.fullCRC = (int) fullCRC.getValue();
+
+        int packetCount = (int) Math.ceil(data.length / (float) maxPacketSize);
+
+        for (int i = 0; i < packetCount; i++) {
+            int currentPacketLength = Math.min(maxPacketSize, data.length - i * maxPacketSize);
+            byte[] packet = new byte[currentPacketLength + 1];
+            packet[0] = (byte) i;
+            System.arraycopy(data, i * maxPacketSize, packet, 1, currentPacketLength);
+
+            packets.add(packet);
+        }
+    }
+
+    public void onFilePut(boolean success) {
+    }
+
+    @Override
+    public byte[] getStartSequence() {
+        return new byte[]{0x03};
+    }
+
+    @Override
+    public int getPayloadLength() {
+        return 15;
+    }
+
+    @Override
+    public UUID getRequestUUID() {
+        return UUID.fromString("3dda0003-957f-7d4a-34a6-74696673696d");
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/file/ResultCode.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/file/ResultCode.java
new file mode 100644
index 0000000..01e0101
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/file/ResultCode.java
@@ -0,0 +1,72 @@
+/*  Copyright (C) 2019-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.file;
+
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public enum ResultCode {
+    SUCCESS(0, true),
+    INVALID_OPERATION_DATA(1, false),
+    OPERATION_IN_PROGRESS(2, false),
+    MISS_PACKET(3, false),
+    SOCKET_BUSY(4, false),
+    VERIFICATION_FAIL(5, false),
+    OVERFLOW(6, false),
+    SIZE_OVER_LIMIT(7, false),
+    FIRMWARE_INTERNAL_ERROR(128, false),
+    FIRMWARE_INTERNAL_ERROR_NOT_OPEN(129, false),
+    FIRMWARE_INTERNAL_ERROR_ACCESS_ERROR(130, false),
+    FIRMWARE_INTERNAL_ERROR_NOT_FOUND(131, false),
+    FIRMWARE_INTERNAL_ERROR_NOT_VALID(132, false),
+    FIRMWARE_INTERNAL_ERROR_ALREADY_CREATE(133, false),
+    FIRMWARE_INTERNAL_ERROR_NOT_ENOUGH_MEMORY(134, false),
+    FIRMWARE_INTERNAL_ERROR_NOT_IMPLEMENTED(135, false),
+    FIRMWARE_INTERNAL_ERROR_NOT_SUPPORT(136, false),
+    FIRMWARE_INTERNAL_ERROR_SOCKET_BUSY(137, false),
+    FIRMWARE_INTERNAL_ERROR_SOCKET_ALREADY_OPEN(138, false),
+    FIRMWARE_INTERNAL_ERROR_INPUT_DATA_INVALID(139, false),
+    FIRMWARE_INTERNAL_NOT_AUTHENTICATE(140, false),
+    FIRMWARE_INTERNAL_SIZE_OVER_LIMIT(141, false),
+    UNKNOWN(-1, false),
+
+    // no clue what there one mean
+    UNKNOWN_1(-125, false);
+
+    boolean success;
+    int code;
+
+    private ResultCode(int code, boolean success){
+        this.code = code;
+        this.success = success;
+    }
+
+    public boolean inidicatesSuccess(){
+        return this.success;
+    }
+
+    public static ResultCode fromCode(int code){
+        for (ResultCode resultCode : ResultCode.values()){
+            if(resultCode.code == code) {
+                if(resultCode == UNKNOWN_1){
+                    GB.log("dunno what code this is: " + code, GB.INFO, null);
+                }
+                return resultCode;
+            }
+        }
+        return UNKNOWN;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/image/AssetImage.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/image/AssetImage.java
new file mode 100644
index 0000000..e784b3e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/image/AssetImage.java
@@ -0,0 +1,90 @@
+/*  Copyright (C) 2020-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.image;
+
+import androidx.annotation.NonNull;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.file.AssetFile;
+
+public class AssetImage extends AssetFile {
+    private int angle, distance, indexZ;
+
+    protected AssetImage(byte[] fileData, int angle, int distance, int indexZ) {
+        super(fileData);
+        this.angle = angle;
+        this.distance = distance;
+        this.indexZ = indexZ;
+    }
+
+    protected AssetImage(byte[] fileData, String fileName, int angle, int distance, int indexZ) {
+        super(fileName, fileData);
+        this.angle = angle;
+        this.distance = distance;
+        this.indexZ = indexZ;
+    }
+
+    @NonNull
+    @Override
+    public String toString() {
+        return toJsonObject().toString();
+    }
+
+    public JSONObject toJsonObject(){
+        try {
+            return new JSONObject()
+                    .put("image_name", getFileName())
+                    .put("pos",
+                        new JSONObject()
+                            .put("angle", angle)
+                            .put("distance", distance)
+                            .put("z_index", indexZ)
+                    );
+        } catch (JSONException e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+
+    public int getAngle() {
+        return angle;
+    }
+
+    public void setAngle(int angle) {
+        this.angle = angle;
+    }
+
+    public int getDistance() {
+        return distance;
+    }
+
+    public void setDistance(int distance) {
+        this.distance = distance;
+    }
+
+    public int getIndexZ() {
+        return indexZ;
+    }
+
+    public void setIndexZ(int indexZ) {
+        this.indexZ = indexZ;
+    }
+
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/image/AssetImageFactory.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/image/AssetImageFactory.java
new file mode 100644
index 0000000..f828f81
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/image/AssetImageFactory.java
@@ -0,0 +1,40 @@
+/*  Copyright (C) 2020-2024 Arjan Schrijver, Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.image;
+
+import android.graphics.Bitmap;
+import java.io.IOException;
+
+import static nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.image.ImageConverter.encodeToRLEImage;
+import static nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.image.ImageConverter.encodeToRawImage;
+import static nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.image.ImageConverter.get2BitsRAWImageBytes;
+import static nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.image.ImageConverter.get2BitsRLEImageBytes;
+
+public class AssetImageFactory {
+    public static AssetImage createAssetImage(byte[] fileData, int angle, int distance, int indexZ){
+        return new AssetImage(fileData, angle, distance, indexZ);
+    }
+
+    public static AssetImage createAssetImage(Bitmap fileData, boolean RLEencode, int angle, int distance, int indexZ) throws IOException {
+        if(RLEencode == (distance == 0)) throw new RuntimeException("when RLEencoding distance must be 0, image must be at center of screen");
+        if(RLEencode){
+            return new AssetImage(encodeToRLEImage(get2BitsRLEImageBytes(fileData), fileData.getHeight(), fileData.getWidth()), angle, distance, indexZ);
+        }else{
+            return new AssetImage(encodeToRawImage(get2BitsRAWImageBytes(fileData)), angle, distance, indexZ);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/image/ImageConverter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/image/ImageConverter.java
new file mode 100644
index 0000000..c56a8db
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/image/ImageConverter.java
@@ -0,0 +1,170 @@
+/*  Copyright (C) 2020-2024 Arjan Schrijver, Daniel Dakhno, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.image;
+
+import android.graphics.Bitmap;
+import android.graphics.Color;
+
+import androidx.annotation.ColorInt;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.qhybrid.FossilFileReader;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.encoder.RLEEncoder;
+
+public class ImageConverter {
+    private static final Logger LOG = LoggerFactory.getLogger(ImageConverter.class);
+
+    public static byte[] get2BitsRLEImageBytes(Bitmap bitmap) {
+        int[] pixels = new int[bitmap.getWidth() * bitmap.getHeight()];
+        bitmap.getPixels(pixels, 0, bitmap.getWidth(), 0, 0, bitmap.getWidth(), bitmap.getHeight());
+        byte[] b_pixels = new byte[pixels.length];
+        for (int i = 0; i < pixels.length; i++) {
+            int monochrome = convertToMonochrome(pixels[i]);
+            monochrome >>= 6;
+
+            int alpha = Color.alpha(pixels[i]);
+            monochrome |= (~((alpha & 0xFF) >> 4) & 0b00001100);
+
+            b_pixels[i] = (byte) monochrome;
+        }
+        return b_pixels;
+    }
+
+    public static byte[] get2BitsRAWImageBytes(Bitmap bitmap) {
+        int width = bitmap.getWidth();
+        int height = bitmap.getHeight();
+        byte[] pixelBytes = new byte[width * height];
+
+        for(int y = 0; y < height; y++){
+            for(int x = 0; x < width; x++){
+                int pixel = bitmap.getPixel(x, y);
+                int monochrome = convertToMonochrome(pixel);
+                pixelBytes[pixelBytes.length - 1 - (y * width + x)] = (byte) monochrome;
+            }
+        }
+        return pixelBytes;
+    }
+
+    public static byte[] encodeToRLEImage(byte[] monochromeImage, int height, int width) throws IOException {
+        ByteArrayOutputStream bos = new ByteArrayOutputStream(monochromeImage.length * 2);
+
+        bos.write((byte) height);
+        bos.write((byte) width);
+
+        bos.write(RLEEncoder.RLEEncode(monochromeImage));
+
+        bos.write((byte) 0x0FF);
+        bos.write((byte) 0x0FF);
+
+        return bos.toByteArray();
+    }
+
+    public static byte[] encodeToRawImage(byte[] monochromeImage){
+        int imageSize = monochromeImage.length;
+
+        byte[] result = new byte[imageSize / 4]; // 4 pixels per byte e.g. 2 bits per pixel
+
+        for(int i = 0; i < imageSize; i++){
+            int resultPixelIndex = i / 4;
+            int shiftIndex = 6 - i % 4 * 2;
+
+            result[resultPixelIndex] |= (byte) (((monochromeImage[i] & 0xFF) >> 6) << shiftIndex);
+            assert Boolean.TRUE;
+        }
+
+        return result;
+    }
+
+    public static int convertToMonochrome(@ColorInt int color){
+        int sum = Color.red(color) + Color.green(color) + Color.blue(color);
+        sum /= 3;
+        return sum;
+    }
+
+    public static @ColorInt int convertFromMonochrome(int color) {
+        int result = 0;
+        switch (color) {
+            case 1:
+                result = 0xff * 1/3;
+                break;
+            case 2:
+                result = 0xff * 2/3;
+                break;
+            case 3:
+                result = 0xff;
+                break;
+        }
+        return Color.rgb(result, result, result);
+    }
+
+    public static Bitmap decodeFromRLEImage(byte[] rleImage) {
+        ByteBuffer buf = ByteBuffer.wrap(rleImage);
+        buf.order(ByteOrder.LITTLE_ENDIAN);
+        int width = buf.get() & 0xff;
+        int height = buf.get() & 0xff;
+        Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+        int posX = 0;
+        int posY = 0;
+        while (buf.remaining() > 2) {
+            int repetitions = buf.get() & 0xff;
+            int pixel = buf.get() & 0xff;
+            int color = convertFromMonochrome(pixel & 0b00000011);
+            for (int i=0; i<repetitions; i++) {
+                bitmap.setPixel(posX, posY, color);
+                posX++;
+                if (posX >= width) {
+                    posX = 0;
+                    posY++;
+                }
+            }
+        }
+        return bitmap;
+    }
+
+    public static Bitmap decodeFromRAWImage(byte[] rawImage, int width, int height) throws Exception {
+        int imageSize = rawImage.length;
+        if (imageSize * 4 != width * height) {
+            // imageSize is multiplied by 4 because there are four 2-bit pixels stored in every byte
+            throw new Exception("decodeFromRAWImage: provided pixels (" + imageSize * 4 + ") not equal to resolution " + width + "*" + height);
+        }
+        ByteBuffer buf = ByteBuffer.wrap(rawImage);
+        buf.order(ByteOrder.LITTLE_ENDIAN);
+        Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+        int posX = 239;
+        int posY = 239;
+        while (buf.remaining() > 0) {
+            int currentPixel = buf.get() & 0xff;
+            for (int shift=6; shift>=0; shift-=2) {
+                int color = convertFromMonochrome((currentPixel >> shift) & 0b00000011);
+                bitmap.setPixel(posX, posY, color);
+                posX--;
+                if (posX < 0) {
+                    posX = 239;
+                    posY--;
+                }
+            }
+        }
+        return bitmap;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/image/ImagesSetRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/image/ImagesSetRequest.java
new file mode 100644
index 0000000..734766b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/image/ImagesSetRequest.java
@@ -0,0 +1,51 @@
+/*  Copyright (C) 2020-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.image;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil.FossilWatchAdapter;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil_hr.FossilHRWatchAdapter;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.json.JsonPutRequest;
+
+public class ImagesSetRequest extends JsonPutRequest {
+    public ImagesSetRequest(AssetImage[] images, FossilHRWatchAdapter adapter) {
+        super(prepareObject(images), adapter);
+    }
+
+    private static JSONObject prepareObject(AssetImage[] images){
+        try {
+            JSONArray imageArray = new JSONArray();
+            for (AssetImage image : images) imageArray.put(image.toJsonObject());
+            return new JSONObject()
+                    .put("push",
+                            new JSONObject()
+                            .put("set",
+                                    new JSONObject()
+                                    .put("watchFace._.config.backgrounds",
+                                            imageArray
+                                    )
+                            )
+                    );
+        } catch (JSONException e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/json/JsonPutRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/json/JsonPutRequest.java
new file mode 100644
index 0000000..f261dae
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/json/JsonPutRequest.java
@@ -0,0 +1,32 @@
+/*  Copyright (C) 2019-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.json;
+
+import org.json.JSONObject;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil_hr.FossilHRWatchAdapter;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.file.FilePutRawRequest;
+
+public class JsonPutRequest extends FilePutRawRequest {
+    public JsonPutRequest(JSONObject object, FossilHRWatchAdapter adapter) {
+        this(object.toString(), adapter);
+    }
+
+    public JsonPutRequest(String json, FossilHRWatchAdapter adapter) {
+        super((short)(0x0500 | (adapter.getJsonIndex() & 0xFF)), json.getBytes(), adapter);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/menu/SetCommuteMenuMessage.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/menu/SetCommuteMenuMessage.java
new file mode 100644
index 0000000..f863546
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/menu/SetCommuteMenuMessage.java
@@ -0,0 +1,47 @@
+/*  Copyright (C) 2019-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.menu;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil.FossilWatchAdapter;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil_hr.FossilHRWatchAdapter;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.json.JsonPutRequest;
+
+public class SetCommuteMenuMessage extends JsonPutRequest {
+    public SetCommuteMenuMessage(String message, boolean finished, FossilHRWatchAdapter adapter) {
+        super(createObject(message, finished), adapter);
+    }
+
+    private static JSONObject createObject(String message, boolean finished) {
+        try {
+            return new JSONObject()
+                    .put("push", new JSONObject()
+                            .put("set", new JSONObject()
+                                    .put("commuteApp._.config.commute_info", new JSONObject()
+                                            .put("message", message)
+                                            .put("type", finished ? "end" : "in_progress")
+                                    )
+                            )
+                    );
+        } catch (JSONException e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/music/MusicControlRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/music/MusicControlRequest.java
new file mode 100644
index 0000000..66b0d0d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/music/MusicControlRequest.java
@@ -0,0 +1,92 @@
+/*  Copyright (C) 2019-2024 Andreas Shimokawa, Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.music;
+
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.FossilRequest;
+
+public class MusicControlRequest extends FossilRequest {
+    private MUSIC_PHONE_REQUEST request;
+
+    public MusicControlRequest(MUSIC_PHONE_REQUEST request) {
+        this.request = request;
+
+        this.data = new byte[]{
+                (byte) 0x02,
+                (byte) 0x05,
+                this.request.getCommandByte(),
+                (byte) 0x00
+        };
+    }
+
+    @Override
+    public boolean isFinished() {
+        return true;
+    }
+
+    @Override
+    public byte[] getStartSequence() {
+        return null;
+    }
+
+    @Override
+    public UUID getRequestUUID() {
+        return UUID.fromString("3dda0006-957f-7d4a-34a6-74696673696d");
+    }
+
+    public enum MUSIC_WATCH_REQUEST {
+        MUSIC_REQUEST_PLAY_PAUSE((byte) 0x02),
+        MUSIC_REQUEST_NEXT((byte) 0x03),
+        MUSIC_REQUEST_PREVIOUS((byte) 0x04),
+        MUSIC_REQUEST_LOUDER((byte) 0x05),
+        MUSIC_REQUEST_QUITER((byte) 0x06),
+        ;
+        private byte commandByte;
+
+        MUSIC_WATCH_REQUEST(byte commandByte) {
+            this.commandByte = commandByte;
+        }
+
+        public static MUSIC_WATCH_REQUEST fromCommandByte(byte commandByte){
+            for(MUSIC_WATCH_REQUEST request : MUSIC_WATCH_REQUEST.values()){
+                if(request.commandByte == commandByte) return request;
+            }
+            return null;
+        }
+    }
+
+    public enum MUSIC_PHONE_REQUEST {
+        MUSIC_REQUEST_SET_PLAYING((byte) 0x00),
+        MUSIC_REQUEST_SET_PAUSED((byte) 0x01),
+        MUSIC_REQUEST_PLAY_PAUSE((byte) 0x02),
+        MUSIC_REQUEST_NEXT((byte) 0x03),
+        MUSIC_REQUEST_PREVIOUS((byte) 0x04),
+        MUSIC_REQUEST_LOUDER((byte) 0x05),
+        MUSIC_REQUEST_QUITER((byte) 0x06),
+        ;
+        private byte commandByte;
+
+        public byte getCommandByte() {
+            return commandByte;
+        }
+
+        private MUSIC_PHONE_REQUEST(byte commandByte) {
+            this.commandByte = commandByte;
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/music/MusicInfoSetRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/music/MusicInfoSetRequest.java
new file mode 100644
index 0000000..3c7a469
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/music/MusicInfoSetRequest.java
@@ -0,0 +1,63 @@
+/*  Copyright (C) 2019-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.music;
+
+import java.nio.BufferOverflowException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil.FossilWatchAdapter;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.file.FileHandle;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.file.FilePutRequest;
+
+public class MusicInfoSetRequest extends FilePutRequest {
+    public MusicInfoSetRequest(String artist, String album, String title, FossilWatchAdapter adapter) {
+        super(FileHandle.MUSIC_INFO, createFile(artist, album, title), adapter);
+    }
+
+    private static byte[] createFile(String artist, String album, String title) {
+        //counting byte array length because of utf chars, they may take up two bytes
+        int titleLength = title.getBytes().length + 1; // +1 = null terminator
+        int albumLength = album.getBytes().length + 1;
+        int artistLength = artist.getBytes().length + 1;
+
+        int length = artistLength + albumLength + titleLength
+                + 8; // length and header
+
+        ByteBuffer buffer = ByteBuffer.allocate(length);
+        buffer.order(ByteOrder.LITTLE_ENDIAN);
+
+        buffer.putShort((short) length);
+        buffer.put((byte) 0x01); // dunno
+        buffer.put((byte) (titleLength));
+        buffer.put((byte) (artistLength));
+        buffer.put((byte) (albumLength));
+        buffer.put((byte) 0x0C); // dunno
+        buffer.put((byte) 0x00); // dunno
+
+        buffer.put(title.getBytes())
+                .put((byte) 0x00); // null terminator
+
+        buffer.put(artist.getBytes())
+                .put((byte) 0x00); // null terminator
+
+        buffer.put(album.getBytes())
+                .put((byte) 0x00); // null terminator
+
+        return buffer.array();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/notification/NotificationFilterPutHRRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/notification/NotificationFilterPutHRRequest.java
new file mode 100644
index 0000000..ae2a414
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/notification/NotificationFilterPutHRRequest.java
@@ -0,0 +1,149 @@
+/*  Copyright (C) 2019-2024 Andreas Shimokawa, Arjan Schrijver, Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.notification;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.qhybrid.NotificationHRConfiguration;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil.FossilWatchAdapter;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.file.FileHandle;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.file.FilePutRequest;
+import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
+
+public class NotificationFilterPutHRRequest extends FilePutRequest {
+    public NotificationFilterPutHRRequest(NotificationHRConfiguration[] configs, FossilWatchAdapter adapter) {
+        super(FileHandle.NOTIFICATION_FILTER, createFile(configs), adapter);
+    }
+
+
+    public NotificationFilterPutHRRequest(ArrayList<NotificationHRConfiguration> configs, FossilWatchAdapter adapter) {
+        super(FileHandle.NOTIFICATION_FILTER, createFile(configs.toArray(new NotificationHRConfiguration[0])), adapter);
+    }
+
+    private static byte[] createFile(NotificationHRConfiguration[] configs) {
+        int payloadLength = 0;
+        for (NotificationHRConfiguration config : configs) {
+            if (config.getIconName().equals("")) {
+                // Notification filters without icon are possible
+                payloadLength += 14;
+            } else {
+                payloadLength += config.getIconName().length() + 20;
+            }
+            if (config.getPackageName().equals("call")) {
+                // Extra payload space needed for call notifications due to multi-icon
+                payloadLength += 44;
+            }
+        }
+
+        ByteBuffer buffer = ByteBuffer.allocate(payloadLength);
+        buffer.order(ByteOrder.LITTLE_ENDIAN);
+
+        for (NotificationHRConfiguration config : configs) {
+            if (config.getIconName().equals("")) {
+                payloadLength = 12;
+            } else {
+                payloadLength = config.getIconName().length() + 18;
+            }
+            if (config.getPackageName().equals("call")) {
+                payloadLength += 44;
+            }
+
+            buffer.putShort((short) payloadLength); // current filter config length
+
+            // 6 bytes
+            buffer.put(PacketID.PACKAGE_NAME_CRC.id)
+                    .put((byte) 4)  // crc length
+                    .put(config.getPackageCrc());
+
+            // 3 bytes
+            buffer.put(PacketID.GROUP_ID.id)
+                    .put((byte) 1)
+                    .put((byte) 0);
+
+            // 3 bytes
+            buffer.put(PacketID.PRIORITY.id)
+                    .put((byte) 1)
+                    .put((byte) 0xFF);
+
+            if (config.getPackageName().equals("call")) {
+                // Call notifications have a specific multi-icon filter config
+                buffer.put(PacketID.ICON.id)
+                        .put((byte) ("icIncomingCall.icon".length() + 4))
+                        .put((byte) 0x02)
+                        .put((byte) 0x00)
+                        .put((byte) ("icIncomingCall.icon".length() + 1))
+                        .put("icIncomingCall.icon".getBytes())
+                        .put((byte) 0x00)
+                        .put((byte) 0x40)
+                        .put((byte) 0x00)
+                        .put((byte) ("icMissedCall.icon".length() + 1))
+                        .put("icMissedCall.icon".getBytes())
+                        .put((byte) 0x00)
+                        .put((byte) 0xbd)
+                        .put((byte) 0x00)
+                        .put((byte) ("icIncomingCall.icon".length() + 1))
+                        .put("icIncomingCall.icon".getBytes())
+                        .put((byte) 0x00);
+            } else if (!config.getIconName().equals("")) {
+                // 6 bytes + icon name
+                buffer.put(PacketID.ICON.id)
+                        .put((byte) (config.getIconName().length() + 4))
+                        .put((byte) 0xFF)
+                        .put((byte) 0x00)
+                        .put((byte) (config.getIconName().length() + 1))
+                        .put(config.getIconName().getBytes())
+                        .put((byte) 0x00);
+            }
+        }
+
+        return buffer.array();
+    }
+
+    enum PacketID {
+        PACKAGE_NAME((byte) 0x01),
+        SENDER_NAME((byte) 0x02),
+        PACKAGE_NAME_CRC((byte) 0x04),
+        GROUP_ID((byte) 0x80),
+        APP_DISPLAY_NAME((byte) 0x81),
+        ICON((byte) 0x82),
+        PRIORITY((byte) 0xC1),
+        MOVEMENT((byte) 0xC2),
+        VIBRATION((byte) 0xC3);
+
+        byte id;
+
+        PacketID(byte id) {
+            this.id = id;
+        }
+    }
+
+    enum VibrationType {
+        SINGLE_SHORT((byte) 5),
+        DOUBLE_SHORT((byte) 6),
+        TRIPLE_SHORT((byte) 7),
+        SINGLE_LONG((byte) 8),
+        SILENT((byte) 9);
+
+        byte id;
+
+        VibrationType(byte id) {
+            this.id = id;
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/notification/NotificationImage.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/notification/NotificationImage.java
new file mode 100644
index 0000000..dc32786
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/notification/NotificationImage.java
@@ -0,0 +1,55 @@
+/*  Copyright (C) 2019-2024 Arjan Schrijver, Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.notification;
+
+import android.graphics.Bitmap;
+import android.graphics.drawable.Drawable;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.file.AssetFile;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.image.ImageConverter;
+import nodomain.freeyourgadget.gadgetbridge.util.BitmapUtil;
+
+import static nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.encoder.RLEEncoder.RLEEncode;
+
+public class NotificationImage extends AssetFile {
+    public static final int MAX_ICON_WIDTH = 24;
+    public static final int MAX_ICON_HEIGHT = 24;
+    private int width;
+    private int height;
+
+    public NotificationImage(String fileName, byte[] imageData, int width, int height) {
+        super(fileName, imageData);
+        this.width = width;
+        this.height = height;
+    }
+
+    public NotificationImage(String fileName, Bitmap iconBitmap) {
+        super(fileName, RLEEncode(ImageConverter.get2BitsRLEImageBytes(BitmapUtil.scaleWithMax(iconBitmap, MAX_ICON_WIDTH, MAX_ICON_HEIGHT))));
+        this.width = Math.min(iconBitmap.getWidth(), MAX_ICON_WIDTH);
+        this.height = Math.min(iconBitmap.getHeight(), MAX_ICON_HEIGHT);
+    }
+
+    public byte[] getImageData() { return getFileData(); }
+    public String getFileName() { return super.getFileName(); }
+    public int getWidth() { return width; }
+    public int getHeight() { return height; }
+
+    public static byte[] getEncodedIconFromDrawable(Drawable drawable) {
+        Bitmap iconBitmap = BitmapUtil.scaleWithMax(BitmapUtil.convertDrawableToBitmap(drawable), MAX_ICON_WIDTH, MAX_ICON_HEIGHT);
+        return RLEEncode(ImageConverter.get2BitsRLEImageBytes(iconBitmap));
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/notification/NotificationImagePutRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/notification/NotificationImagePutRequest.java
new file mode 100644
index 0000000..f78fa16
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/notification/NotificationImagePutRequest.java
@@ -0,0 +1,66 @@
+/*  Copyright (C) 2019-2024 Andreas Shimokawa, Arjan Schrijver, Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.notification;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil.FossilWatchAdapter;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.file.FileHandle;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.file.FilePutRequest;
+
+public class NotificationImagePutRequest extends FilePutRequest {
+    public NotificationImagePutRequest(NotificationImage[] images, FossilWatchAdapter adapter) throws IOException {
+        super(FileHandle.ASSET_NOTIFICATION_IMAGES, prepareFileData(images), adapter);
+    }
+
+    public NotificationImagePutRequest(NotificationImage image, FossilWatchAdapter adapter) {
+        super(FileHandle.ASSET_REPLY_IMAGES, prepareFileData(image), adapter);
+    }
+
+    private static byte[] prepareFileData(NotificationImage[] images) throws IOException {
+        ByteArrayOutputStream stream = new ByteArrayOutputStream();
+
+        for (NotificationImage image : images) {
+            stream.write(
+                    prepareFileData(image)
+            );
+        }
+
+        return stream.toByteArray();
+    }
+
+    private static byte[] prepareFileData(NotificationImage image){
+        int size = image.getFileName().length() + 3 + image.getFileData().length + 2;
+        ByteBuffer buffer = ByteBuffer.allocate(2 + size);
+
+        buffer.order(ByteOrder.LITTLE_ENDIAN);
+
+        buffer.putShort((short)(size));
+        buffer.put(image.getFileName().getBytes());
+        buffer.put((byte) 0x00);
+        buffer.put((byte) image.getWidth());
+        buffer.put((byte) image.getHeight());
+        buffer.put(image.getImageData());
+        buffer.put((byte) 0xff);
+        buffer.put((byte) 0xff);
+
+        return buffer.array();
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/quickreply/QuickReplyConfigurationPutRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/quickreply/QuickReplyConfigurationPutRequest.java
new file mode 100644
index 0000000..a2357a5
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/quickreply/QuickReplyConfigurationPutRequest.java
@@ -0,0 +1,72 @@
+/*  Copyright (C) 2021-2024 Andreas Shimokawa, Arjan Schrijver
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.quickreply;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil_hr.FossilHRWatchAdapter;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.file.FileHandle;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.file.FilePutRequest;
+import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
+
+public class QuickReplyConfigurationPutRequest extends FilePutRequest {
+    public QuickReplyConfigurationPutRequest(String[] replies, FossilHRWatchAdapter adapter) {
+        super(FileHandle.REPLY_MESSAGES, createFile(replies), adapter);
+    }
+
+    private static byte[] createFile(String[] replies) {
+        String[] processedReplies = new String[replies.length];
+        int fileLength = 0;
+
+        byte[] mysteryHeader = new byte[]{(byte) 0x02, (byte) 0x0b, (byte) 0x46, (byte) 0x00, (byte) 0x03, (byte) 0x19, (byte) 0x00, (byte) 0x00, (byte) 0x00};
+
+        Charset charsetUTF8 = StandardCharsets.UTF_8;
+        String iconName = StringUtils.terminateNull("icMessage.icon");
+        byte[] iconNameBytes = iconName.getBytes(charsetUTF8);
+
+        for (int index=0; index< replies.length; index++) {
+            String reply = replies[index];
+            if (reply.length() > 50) {
+                reply = reply.substring(0, 50);
+            }
+            processedReplies[index] = StringUtils.terminateNull(reply);
+            fileLength += 8 + processedReplies[index].getBytes(charsetUTF8).length + iconNameBytes.length;
+        }
+
+        ByteBuffer mainBuffer = ByteBuffer.allocate(mysteryHeader.length + 4 + fileLength);
+        mainBuffer.order(ByteOrder.LITTLE_ENDIAN);
+
+        mainBuffer.put(mysteryHeader);
+        mainBuffer.putInt(fileLength);
+
+        for (int index=0; index < processedReplies.length; index++) {
+            byte[] msgBytes = processedReplies[index].getBytes(charsetUTF8);
+            mainBuffer.putShort((short) (8 + msgBytes.length + iconNameBytes.length));
+            mainBuffer.put((byte) 0x08);
+            mainBuffer.put((byte) index);
+            mainBuffer.putShort((short) msgBytes.length);
+            mainBuffer.putShort((short) iconNameBytes.length);
+            mainBuffer.put(msgBytes);
+            mainBuffer.put(iconNameBytes);
+        }
+
+        return mainBuffer.array();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/quickreply/QuickReplyConfirmationPutRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/quickreply/QuickReplyConfirmationPutRequest.java
new file mode 100644
index 0000000..44e4c0e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/quickreply/QuickReplyConfirmationPutRequest.java
@@ -0,0 +1,55 @@
+/*  Copyright (C) 2021-2024 Arjan Schrijver
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.quickreply;
+
+import java.util.UUID;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.FossilRequest;
+
+
+public class QuickReplyConfirmationPutRequest extends FossilRequest {
+    /**
+     * Contains the bytes to confirm to the watch that a quick reply SMS has been sent.
+     * @param callId
+     */
+    public QuickReplyConfirmationPutRequest(byte callId) {
+        this.data = new byte[]{
+                (byte) 0x02,
+                (byte) 0x04,
+                callId,
+                (byte) 0x00,
+                (byte) 0x00,
+                (byte) 0x00,
+                (byte) 0x03,
+                (byte) 0x00
+        };
+    }
+
+    @Override
+    public boolean isFinished() {
+        return true;
+    }
+
+    @Override
+    public byte[] getStartSequence() {
+        return null;
+    }
+
+    @Override
+    public UUID getRequestUUID() {
+        return UUID.fromString("3dda0006-957f-7d4a-34a6-74696673696d");
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/theme/SelectedThemePutRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/theme/SelectedThemePutRequest.java
new file mode 100644
index 0000000..4664287
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/theme/SelectedThemePutRequest.java
@@ -0,0 +1,48 @@
+/*  Copyright (C) 2021-2024 Arjan Schrijver
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.theme;
+
+import android.widget.Toast;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil_hr.FossilHRWatchAdapter;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.json.JsonPutRequest;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class SelectedThemePutRequest extends JsonPutRequest {
+    public SelectedThemePutRequest(FossilHRWatchAdapter adapter, String themeName) {
+        super(createObject(themeName), adapter);
+    }
+
+    private static JSONObject createObject(String themeName) {
+        try {
+            return new JSONObject()
+                    .put("push", new JSONObject()
+                            .put("set", new JSONObject()
+                                    .put("themeApp._.config.selected_theme", themeName)
+                            )
+                    );
+        } catch (JSONException e) {
+            GB.toast("error creating json", Toast.LENGTH_LONG, GB.ERROR, e);
+        }
+
+        return null;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/translation/TranslationData.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/translation/TranslationData.java
new file mode 100644
index 0000000..f665ad7
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/translation/TranslationData.java
@@ -0,0 +1,61 @@
+/*  Copyright (C) 2020-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.translation;
+
+import nodomain.freeyourgadget.gadgetbridge.service.btle.Transaction;
+
+public class TranslationData {
+    private String locale;
+    private TranslationItem[] translations;
+
+    public TranslationData(String locale, TranslationItem[] translations) {
+        this.locale = locale;
+        this.translations = translations;
+    }
+
+    public String getLocale() {
+        return locale;
+    }
+
+    public void setLocale(String locale) {
+        this.locale = locale;
+    }
+
+    public TranslationItem[] getTranslations() {
+        return translations;
+    }
+
+    public void setTranslations(TranslationItem[] translations) {
+        this.translations = translations;
+    }
+
+    public void replaceByOriginal(String originalKey, String translated){
+        for(TranslationItem translationItem : this.translations){
+            if(translationItem.getOriginal().equals(originalKey)){
+                translationItem.setTranslated(translated);
+            }
+        }
+    }
+
+    public void replaceByTranslated(String translatedOld, String translatedNew){
+        for(TranslationItem translationItem : this.translations){
+            if(translationItem.getTranslated().equals(translatedOld)){
+                translationItem.setTranslated(translatedNew);
+            }
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/translation/TranslationItem.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/translation/TranslationItem.java
new file mode 100644
index 0000000..469d8f1
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/translation/TranslationItem.java
@@ -0,0 +1,51 @@
+/*  Copyright (C) 2020-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.translation;
+
+import androidx.annotation.NonNull;
+
+public class TranslationItem {
+    private String originalTranslation;
+    private String translated;
+
+    public TranslationItem(String originalTranslation, String translated) {
+        this.originalTranslation = originalTranslation;
+        this.translated = translated;
+    }
+
+    public String getOriginal() {
+        return originalTranslation;
+    }
+
+    public void setOriginalTranslation(String originalTranslation) {
+        this.originalTranslation = originalTranslation;
+    }
+
+    public String getTranslated() {
+        return translated;
+    }
+
+    public void setTranslated(String translated) {
+        this.translated = translated;
+    }
+
+    @NonNull
+    @Override
+    public String toString() {
+        return this.originalTranslation + " => " + this.translated;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/translation/TranslationsGetRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/translation/TranslationsGetRequest.java
new file mode 100644
index 0000000..b938862
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/translation/TranslationsGetRequest.java
@@ -0,0 +1,71 @@
+/*  Copyright (C) 2020-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.translation;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil.FossilWatchAdapter;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.file.FileHandle;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.file.FileGetRequest;
+
+public abstract class TranslationsGetRequest extends FileGetRequest {
+    public TranslationsGetRequest(FossilWatchAdapter adapter) {
+        super(FileHandle.ASSET_TRANSLATIONS, adapter);
+    }
+
+    @Override
+    public void handleFileData(byte[] fileData) {
+        ByteBuffer buffer = ByteBuffer.wrap(fileData);
+        buffer.order(ByteOrder.LITTLE_ENDIAN);
+
+        byte[] localeBytes = new byte[5];
+        buffer.get(localeBytes);
+        String locale = new String(localeBytes);
+
+        buffer.get(); // locale null byte
+
+        ArrayList<TranslationItem> translations = new ArrayList<>();
+
+        while(buffer.remaining() > 0){
+            int originalLength = buffer.getShort() - 1; // subtracting null terminator
+            byte[] originalBytes = new byte[originalLength];
+            buffer.get(originalBytes);
+            buffer.get(); // should always return null terminator
+            int translatedLength = buffer.getShort() - 1;
+            byte[] translatedBytes = new byte[translatedLength];
+            buffer.get(translatedBytes);
+            buffer.get(); // should always return null terminator
+
+            String original = new String(originalBytes);
+            String translated = new String(translatedBytes);
+
+            translations.add(new TranslationItem(original, translated));
+        }
+
+        handleTranslations(
+                new TranslationData(
+                        locale,
+                        translations.toArray(new TranslationItem[0])
+                )
+        );
+
+    }
+
+    public abstract void handleTranslations(TranslationData translationDate);
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/translation/TranslationsPutRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/translation/TranslationsPutRequest.java
new file mode 100644
index 0000000..dcff01a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/translation/TranslationsPutRequest.java
@@ -0,0 +1,57 @@
+/*  Copyright (C) 2020-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.translation;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil.FossilWatchAdapter;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.file.FileHandle;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.file.FilePutRequest;
+
+public class TranslationsPutRequest extends FilePutRequest {
+    public TranslationsPutRequest(TranslationData translationData, FossilWatchAdapter adapter) {
+        super(FileHandle.ASSET_TRANSLATIONS, createPayload(translationData), adapter);
+    }
+
+    private static byte[] createPayload(TranslationData translationData){
+        TranslationItem[] translations = translationData.getTranslations();
+        int fileLength = 6 + translations.length * 6; // locale + one null-byte per translation + lengths per translation
+        for(TranslationItem translation : translations){
+            fileLength += translation.getOriginal().getBytes().length + translation.getTranslated().getBytes().length;
+        }
+
+        ByteBuffer buffer = ByteBuffer.allocate(fileLength);
+        buffer.order(ByteOrder.LITTLE_ENDIAN);
+
+        buffer.put(translationData.getLocale().getBytes())
+                .put((byte)0);
+
+        for(TranslationItem translation : translations){
+            byte[] originalBytes = translation.getOriginal().getBytes();
+            byte[] translatedBytes = translation.getTranslated().getBytes();
+            buffer.putShort((short)(originalBytes.length + 1))
+                    .put(originalBytes)
+                    .put((byte)0) // null terminator
+                    .putShort((short)(translatedBytes.length + 1))
+                    .put(translatedBytes)
+                    .put((byte)0); // null terminator
+        }
+
+        return buffer.array();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/widget/CustomBackgroundWidgetElement.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/widget/CustomBackgroundWidgetElement.java
new file mode 100644
index 0000000..3b29016
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/widget/CustomBackgroundWidgetElement.java
@@ -0,0 +1,27 @@
+/*  Copyright (C) 2020-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.widget;
+
+public class CustomBackgroundWidgetElement extends CustomWidgetElement{
+    public CustomBackgroundWidgetElement(String id, String value) {
+        super(WidgetElementType.TYPE_BACKGROUND, id, value, 0, 0);
+    }
+
+    public CustomBackgroundWidgetElement(String value) {
+        super(WidgetElementType.TYPE_BACKGROUND, null, value, 0, 0);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/widget/CustomTextWidgetElement.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/widget/CustomTextWidgetElement.java
new file mode 100644
index 0000000..c3a5cab
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/widget/CustomTextWidgetElement.java
@@ -0,0 +1,27 @@
+/*  Copyright (C) 2020-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.widget;
+
+public class CustomTextWidgetElement extends CustomWidgetElement{
+    public CustomTextWidgetElement(String id, String value, int x, int y) {
+        super(WidgetElementType.TYPE_TEXT, id, value, x, y);
+    }
+
+    public CustomTextWidgetElement(String value, int x, int y) {
+        super(WidgetElementType.TYPE_TEXT, null, value, x, y);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/widget/CustomWidget.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/widget/CustomWidget.java
new file mode 100644
index 0000000..809fa7f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/widget/CustomWidget.java
@@ -0,0 +1,75 @@
+/*  Copyright (C) 2020-2024 Andreas Shimokawa, Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.widget;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+
+public class CustomWidget extends Widget {
+    private ArrayList<CustomWidgetElement> elements = new ArrayList<>();
+    private int angle, distance;
+    private String name;
+
+    public CustomWidget(String name, int angle, int distance, String fontColor) {
+        super(null, angle, distance, fontColor);
+        this.angle = angle;
+        this.distance = distance;
+        this.name = name;
+    }
+
+    public void setElements(ArrayList<CustomWidgetElement> elements) {
+        this.elements = elements;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public ArrayList<CustomWidgetElement> getElements(){
+        return this.elements;
+    }
+
+    public void addElement(CustomWidgetElement element){
+        this.elements.add(element);
+    }
+
+    public boolean updateElementValue(String id, String value){
+        boolean updatedValue = false;
+        for(CustomWidgetElement element : this.elements){
+            String elementId = element.getId();
+            if(elementId != null && elementId.equals(id)){
+                element.setValue(value);
+                updatedValue = true;
+            }
+        }
+        return updatedValue;
+    }
+
+    private CustomWidgetElement getElement(String id){
+        for(CustomWidgetElement element : this.elements){
+            String elementId = element.getId();
+            if(elementId != null && elementId.equals(id)) return element;
+        }
+        return null;
+    }
+
+    public String getName() {
+        return name;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/widget/CustomWidgetElement.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/widget/CustomWidgetElement.java
new file mode 100644
index 0000000..3efcf67
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/widget/CustomWidgetElement.java
@@ -0,0 +1,108 @@
+/*  Copyright (C) 2020-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.widget;
+
+import java.io.Serializable;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+
+public class CustomWidgetElement implements Serializable {
+    public enum WidgetElementType {
+        TYPE_TEXT(R.id.qhybrid_widget_elements_type_text, "text"),
+        TYPE_IMAGE(0, "image"),
+        TYPE_BACKGROUND(R.id.qhybrid_widget_elements_type_background, "background");
+
+        private String jsonIdentifier;
+        private int radioButtonResource;
+
+        WidgetElementType(int radioButtonResource, String jsonIdentifier){
+            this.radioButtonResource = radioButtonResource;
+            this.jsonIdentifier = jsonIdentifier;
+        }
+
+        public int getRadioButtonResource() {
+            return radioButtonResource;
+        }
+
+        public String getJsonIdentifier() {
+            return jsonIdentifier;
+        }
+
+        static public WidgetElementType fromRadioButtonRessource(int radioButtonResource){
+            for(WidgetElementType type : values()){
+                if(type.getRadioButtonResource() == radioButtonResource) return type;
+            }
+            return null;
+        }
+    }
+
+    public final static int X_CENTER = 38;
+    public final static int Y_UPPER_HALF = (int) (76f / 3 * 1);
+    public final static int Y_LOWER_HALF = (int) (76f / 3 * 2);
+
+    private WidgetElementType widgetElementType;
+    private String id, value;
+    private int x, y;
+
+    public void setWidgetElementType(WidgetElementType widgetElementType) {
+        this.widgetElementType = widgetElementType;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public void setX(int x) {
+        this.x = x;
+    }
+
+    public void setY(int y) {
+        this.y = y;
+    }
+
+    protected CustomWidgetElement(WidgetElementType widgetElementType, String id, String value, int x, int y) {
+        this.widgetElementType = widgetElementType;
+        this.id = id;
+        this.value = value;
+        this.x = x;
+        this.y = y;
+    }
+
+    public int getX() {
+        return x;
+    }
+
+    public int getY() {
+        return y;
+    }
+
+    public WidgetElementType getWidgetElementType() {
+        return widgetElementType;
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    public String getValue() {
+        return value;
+    }
+
+    public void setValue(String value) {
+        this.value = value;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/widget/Widget.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/widget/Widget.java
new file mode 100644
index 0000000..a93d79d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/widget/Widget.java
@@ -0,0 +1,139 @@
+/*  Copyright (C) 2019-2024 Andreas Shimokawa, Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.widget;
+
+import androidx.annotation.NonNull;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.Serializable;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+
+public class Widget implements Serializable {
+    private WidgetType widgetType;
+    private int angle, distance;
+    private String fontColor;
+
+    public Widget(WidgetType type, int angle, int distance, String fontColor) {
+        this.widgetType = type;
+        this.angle = angle;
+        this.distance = distance;
+        this.fontColor = fontColor;
+    }
+
+    public WidgetType getWidgetType() {
+        return widgetType;
+    }
+
+    public int getAngle() {
+        return angle;
+    }
+
+    public int getDistance() {
+        return distance;
+    }
+
+    public void setAngle(int angle) {
+        this.angle = angle;
+    }
+
+    public void setDistance(int distance) {
+        this.distance = distance;
+    }
+
+    @NonNull
+    @Override
+    public String toString() {
+        return toJson().toString();
+    }
+
+    public JSONObject toJson() {
+        JSONObject object = new JSONObject();
+
+        try {
+            String type;
+            if(widgetType == null) type = "Custom widget";
+            else type = widgetType.getIdentifier();
+            object
+                    .put("name", type)
+                    .put("pos",
+                            new JSONObject()
+                                    .put("angle", angle)
+                                    .put("distance", distance)
+                    )
+                    .put("data", new JSONObject())
+                    .put("theme",
+                            new JSONObject()
+                                    .put("font_color", fontColor)
+                    );
+        } catch (JSONException e) {
+            e.printStackTrace();
+        }
+
+        return object;
+    }
+
+
+    public enum WidgetType {
+        HEART_RATE("hrSSE", R.string.hr_widget_heart_rate),
+        STEPS("stepsSSE", R.string.hr_widget_steps),
+        DATE("dateSSE", R.string.hr_widget_date),
+        ACTIVE_MINUTES("activeMinutesSSE", R.string.hr_widget_active_minutes),
+        CALORIES("caloriesSSE", R.string.hr_widget_calories),
+        BATTERY("batterySSE", R.string.hr_widget_battery),
+        WEATHER("weatherSSE", R.string.hr_widget_weather),
+        LAST_NOTIFICATION("last_notification", R.string.hr_widget_last_notification, true),
+        NOTHING(null, R.string.hr_widget_nothing);
+
+        private String identifier;
+        private int stringResource;
+        private boolean custom;
+
+        WidgetType(String identifier, int stringResource) {
+            this.identifier = identifier;
+            this.stringResource = stringResource;
+            this.custom = false;
+        }
+
+        WidgetType(String identifier, int stringResource, boolean custom) {
+            this.identifier = identifier;
+            this.stringResource = stringResource;
+            this.custom = custom;
+        }
+
+        public static WidgetType fromJsonIdentifier(String jsonIdentifier){
+            for(WidgetType type : values()){
+                if(type.getIdentifier() != null && type.getIdentifier().equals(jsonIdentifier)) return type;
+            }
+            return null;
+        }
+
+        public int getStringResource() {
+            return stringResource;
+        }
+
+        public String getIdentifier() {
+            return this.identifier;
+        }
+
+        public boolean isCustom() {
+            return custom;
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/widget/WidgetsPutRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/widget/WidgetsPutRequest.java
new file mode 100644
index 0000000..0611035
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/widget/WidgetsPutRequest.java
@@ -0,0 +1,55 @@
+/*  Copyright (C) 2019-2024 Andreas Shimokawa, Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.widget;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil_hr.FossilHRWatchAdapter;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.json.JsonPutRequest;
+
+public class WidgetsPutRequest extends JsonPutRequest {
+    public WidgetsPutRequest(Widget[] widgets, FossilHRWatchAdapter adapter) {
+        super(prepareFile(widgets), adapter);
+    }
+
+    private static JSONObject prepareFile(Widget[] widgets){
+        try {
+            JSONArray widgetArray = new JSONArray();
+
+            for(Widget widget : widgets){
+                widgetArray.put(widget.toJson());
+            }
+
+            JSONObject object = new JSONObject()
+                    .put(
+                            "push",
+                            new JSONObject()
+                            .put("set",
+                                new JSONObject().put(
+                                        "watchFace._.config.comps", widgetArray
+                                )
+                            )
+                    );
+            return object;
+        } catch (JSONException e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/workout/WorkoutRequestHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/workout/WorkoutRequestHandler.java
new file mode 100644
index 0000000..6e38794
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/workout/WorkoutRequestHandler.java
@@ -0,0 +1,84 @@
+/*  Copyright (C) 2022-2024 Arjan Schrijver, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.workout;
+
+import android.content.Context;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.devices.qhybrid.QHybridConstants;
+import nodomain.freeyourgadget.gadgetbridge.externalevents.opentracks.OpenTracksController;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+
+public class WorkoutRequestHandler {
+    public static void addStateResponse(JSONObject workoutResponse, String type, String msg) throws JSONException {
+        workoutResponse.put("workoutApp._.config.response", new JSONObject()
+                .put("message", msg)
+                .put("type", type)
+        );
+    }
+
+    public static JSONObject handleRequest(Context context, int requestId, JSONObject workoutRequest) throws JSONException {
+        final Logger LOG = LoggerFactory.getLogger(WorkoutRequestHandler.class);
+
+        JSONObject workoutResponse = new JSONObject();
+        if (workoutRequest.optString("state").equals("started") && workoutRequest.optString("gps").equals("on")) {
+            int activityType = workoutRequest.optInt("activity", -1);
+            final ActivityKind activityKind = QHybridConstants.WORKOUT_TYPES_TO_ACTIVITY_KIND.getOrDefault(activityType, ActivityKind.UNKNOWN);
+            LOG.info("Workout started, activity type is {}/{}", activityType, activityKind);
+            addStateResponse(workoutResponse, "success", "");
+            //noinspection DataFlowIssue fp, never null
+            OpenTracksController.startRecording(context, activityKind);
+        } else if (workoutRequest.optString("type").equals("req_distance")) {
+            long timeSecs = Math.round(GBApplication.app().getOpenTracksObserver().getTimeMillisChange() / 1000f);
+            float distanceCM = GBApplication.app().getOpenTracksObserver().getDistanceMeterChange() * 100;
+            LOG.info("Workout distance requested, returning {} cm, {} sec", distanceCM, timeSecs);
+            workoutResponse.put("workoutApp._.config.gps", new JSONObject()
+                    .put("distance", distanceCM)
+                    .put("duration", timeSecs)
+            );
+        } else if (workoutRequest.optString("state").equals("paused")) {
+            LOG.info("Workout paused");
+            addStateResponse(workoutResponse, "success", "");
+            // Pause OpenTracks recording?
+        } else if (workoutRequest.optString("state").equals("resumed")) {
+            LOG.info("Workout resumed");
+            addStateResponse(workoutResponse, "success", "");
+            // Resume OpenTracks recording?
+        } else if (workoutRequest.optString("state").equals("end")) {
+            LOG.info("Workout stopped");
+            addStateResponse(workoutResponse, "success", "");
+            OpenTracksController.stopRecording(context);
+        } else if (workoutRequest.optString("type").equals("req_route")) {
+            LOG.info("Workout route image requested, returning error");
+            addStateResponse(workoutResponse, "error", "");
+            // Send the traveled route as an RLE encoded image (example name: 58270405)
+            // Send back a JSON packet, example:
+            // {"res":{"id":21,"set":{"workoutApp._.config.images":{"session_id":1213693133,"route":{"name":"58270405"},"layout_type":"vertical"}}}}
+            // or
+            // {"res":{"id":34,"set":{"workoutApp._.config.images":{"session_id":504875,"route":{"name":"211631088"},"layout_type":"horizontal"}}}}
+        } else {
+            LOG.info("Request not recognized: " + workoutRequest);
+        }
+        return workoutResponse;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/ActivityPointGetRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/ActivityPointGetRequest.java
new file mode 100644
index 0000000..2e9b464
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/ActivityPointGetRequest.java
@@ -0,0 +1,46 @@
+/*  Copyright (C) 2019-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit;
+
+import android.bluetooth.BluetoothGattCharacteristic;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.Request;
+
+public class ActivityPointGetRequest extends Request {
+    public int activityPoint;
+
+    @Override
+    public byte[] getStartSequence() {
+        return new byte[]{1, 6};
+    }
+
+    @Override
+    public void handleResponse(BluetoothGattCharacteristic characteristic) {
+        super.handleResponse(characteristic);
+
+        byte[] value = characteristic.getValue();
+
+        if (value.length != 6) return;
+
+        ByteBuffer wrap = ByteBuffer.wrap(value);
+        wrap.order(ByteOrder.LITTLE_ENDIAN);
+        activityPoint = wrap.getInt(2) >> 8;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/AnimationRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/AnimationRequest.java
new file mode 100644
index 0000000..39deddf
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/AnimationRequest.java
@@ -0,0 +1,26 @@
+/*  Copyright (C) 2019-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.Request;
+
+public class AnimationRequest extends Request {
+    @Override
+    public byte[] getStartSequence() {
+        return new byte[]{(byte)2, (byte) -15, (byte)5};
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/BatteryLevelRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/BatteryLevelRequest.java
new file mode 100644
index 0000000..f15c1ee
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/BatteryLevelRequest.java
@@ -0,0 +1,45 @@
+/*  Copyright (C) 2019-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit;
+
+import android.bluetooth.BluetoothGattCharacteristic;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.Request;
+
+public class BatteryLevelRequest extends Request {
+    public short level = -1;
+    @Override
+    public byte[] getStartSequence() {
+        return new byte[]{1, 8};
+    }
+
+    @Override
+    public void handleResponse(BluetoothGattCharacteristic characteristic) {
+        super.handleResponse(characteristic);
+
+        byte[] value = characteristic.getValue();
+
+        if (value.length >= 3) {
+            ByteBuffer buffer = ByteBuffer.wrap(value);
+            buffer.order(ByteOrder.LITTLE_ENDIAN);
+            level = buffer.get(2);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/DownloadFileRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/DownloadFileRequest.java
new file mode 100644
index 0000000..9ce9095
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/DownloadFileRequest.java
@@ -0,0 +1,117 @@
+/*  Copyright (C) 2019-2024 Andreas Shimokawa, Daniel Dakhno, Taavi Eom盲e
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit;
+
+import android.bluetooth.BluetoothGattCharacteristic;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.zip.CRC32;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.file.ResultCode;
+
+public class DownloadFileRequest extends FileRequest {
+    ByteBuffer buffer = null;
+    public byte[] file = null;
+    public int fileHandle;
+    public int size;
+    public long timeStamp;
+
+    public DownloadFileRequest(short handle){
+        init(handle, 0, 65535);
+    }
+
+    public DownloadFileRequest(short handle, int offset, int length) {
+        init(handle, offset, length);
+    }
+
+    private void init(short handle, int offset, int length) {
+        ByteBuffer buffer = createBuffer();
+        buffer.putShort(handle);
+        buffer.putInt(offset);
+        buffer.putInt(length);
+        this.data = buffer.array();
+        this.fileHandle = handle;
+        this.timeStamp = System.currentTimeMillis();
+    }
+
+    @Override
+    public byte[] getStartSequence() {
+        return new byte[]{1};
+    }
+
+    @Override
+    public int getPayloadLength() {
+        return 11;
+    }
+
+    @Override
+    public void handleResponse(BluetoothGattCharacteristic characteristic) {
+        super.handleResponse(characteristic);
+        byte[] data = characteristic.getValue();
+        if(characteristic.getUuid().toString().equals("3dda0003-957f-7d4a-34a6-74696673696d")){
+            if(buffer == null){
+                buffer = ByteBuffer.allocate(4096);
+                ByteBuffer buffer1 = ByteBuffer.wrap(data);
+                buffer1.order(ByteOrder.LITTLE_ENDIAN);
+                this.status = buffer1.get(3);
+                short realHandle = buffer1.getShort(1);
+                ResultCode code = ResultCode.fromCode(status);
+                if(!code.inidicatesSuccess()){
+                    log("wrong status: " + code + "   (" + status + ")");
+                }else if(realHandle != fileHandle){
+                    log("wrong handle: " + realHandle);
+                    completed = true;
+                }else{
+                    log("handle: " + realHandle);
+                }
+            }else{
+                completed = true;
+            }
+        }else if(characteristic.getUuid().toString().equals("3dda0004-957f-7d4a-34a6-74696673696d")){
+            buffer.put(data, 1, data.length - 1);
+            if((data[0] & -128) != 0){
+                ByteBuffer buffer1 = ByteBuffer.allocate(buffer.position());
+                buffer1.put(buffer.array(), 0, buffer.position());
+                buffer1.order(ByteOrder.LITTLE_ENDIAN);
+                file = buffer1.array();
+                CRC32 crc = new CRC32();
+                crc.update(file, 0, file.length - 4);
+                this.size = file.length;
+                log("file content: " + bytesToString(file));
+                if(crc.getValue() != cutBits(buffer1.getInt(size - 4))){
+                    log("checksum invalid    expected: " + buffer1.getInt(size - 4) + "   actual: " + crc.getValue());
+                }
+            }
+        }
+    }
+
+    long cutBits(int value) {
+        return value & 0b11111111111111111111111111111111L;
+
+    }
+
+    private String bytesToString(byte[] bytes){
+        StringBuilder s = new StringBuilder();
+        String chars = "0123456789ABCDEF";
+        for (byte b : bytes) {
+            s.append(chars.charAt((b >> 4) & 0xF));
+            s.append(chars.charAt((b >> 0) & 0xF));
+        }
+        return s.toString();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/EraseFileRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/EraseFileRequest.java
new file mode 100644
index 0000000..b22e1d9
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/EraseFileRequest.java
@@ -0,0 +1,59 @@
+/*  Copyright (C) 2019-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit;
+
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.util.Log;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+public class EraseFileRequest extends FileRequest{
+    public short fileHandle, deletedHandle;
+
+    public EraseFileRequest(short handle) {
+        fileHandle = handle;
+        ByteBuffer buffer = createBuffer();
+        buffer.putShort(handle);
+        this.data = buffer.array();
+    }
+
+    @Override
+    public void handleResponse(BluetoothGattCharacteristic characteristic) {
+        super.handleResponse(characteristic);
+        if(!characteristic.getUuid().toString().equals(getRequestUUID().toString())){
+            log("wrong descriptor");
+            return;
+        }
+        ByteBuffer buffer = ByteBuffer.wrap(characteristic.getValue());
+        buffer.order(ByteOrder.LITTLE_ENDIAN);
+        deletedHandle = buffer.getShort(1);
+        status = buffer.get(3);
+
+        log("file " + deletedHandle + " erased: " + status);
+    }
+
+    @Override
+    public int getPayloadLength() {
+        return 3;
+    }
+
+    @Override
+    public byte[] getStartSequence() {
+        return new byte[]{3};
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/EventStreamRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/EventStreamRequest.java
new file mode 100644
index 0000000..1874c93
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/EventStreamRequest.java
@@ -0,0 +1,47 @@
+/*  Copyright (C) 2019-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit;
+
+import java.nio.ByteBuffer;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.Request;
+
+public class EventStreamRequest extends Request {
+    public EventStreamRequest(short handle) {
+        super();
+        ByteBuffer buffer = createBuffer();
+        buffer.putShort(1, handle);
+        this.data = buffer.array();
+    }
+
+
+    @Override
+    public UUID getRequestUUID() {
+        return UUID.fromString("3dda0006-957f-7d4a-34a6-74696673696d");
+    }
+
+    @Override
+    public byte[] getStartSequence() {
+        return new byte[]{1};
+    }
+
+    @Override
+    public int getPayloadLength() {
+        return 3;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/FactoryResetRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/FactoryResetRequest.java
new file mode 100644
index 0000000..56a0814
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/FactoryResetRequest.java
@@ -0,0 +1,26 @@
+/*  Copyright (C) 2020-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.Request;
+
+public class FactoryResetRequest extends Request {
+    @Override
+    public byte[] getStartSequence() {
+        return new byte[]{(byte) 0x02, (byte) 0xF1, (byte) 0x23, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF};
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/FileRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/FileRequest.java
new file mode 100644
index 0000000..0835a6a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/FileRequest.java
@@ -0,0 +1,35 @@
+/*  Copyright (C) 2019-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit;
+
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.Request;
+
+public class FileRequest extends Request {
+    public boolean completed = false;
+    public int status;
+    @Override
+    public UUID getRequestUUID() {
+        return UUID.fromString("3dda0003-957f-7d4a-34a6-74696673696d");
+    }
+
+    @Override
+    public byte[] getStartSequence() {
+        return null;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/GetCountdownSettingsRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/GetCountdownSettingsRequest.java
new file mode 100644
index 0000000..4e640fd
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/GetCountdownSettingsRequest.java
@@ -0,0 +1,55 @@
+/*  Copyright (C) 2019-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit;
+
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.util.Log;
+
+import java.nio.ByteBuffer;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.Request;
+
+public class GetCountdownSettingsRequest extends Request {
+    @Override
+    public byte[] getStartSequence() {
+        return new byte[]{1, 19, 1};
+    }
+
+    @Override
+    public void handleResponse(BluetoothGattCharacteristic characteristic) {
+        byte[] value = characteristic.getValue();
+        if (value.length != 14) {
+            return;
+        }
+        ByteBuffer buffer = ByteBuffer.wrap(value);
+        long startTime = j(buffer.getInt(3));
+        long endTime = j(buffer.getInt(7));
+        byte progress = buffer.get(13);
+        short offset = buffer.getShort(11);
+
+        log("progress: " + progress);
+
+    }
+
+
+    public static long j(final int n) {
+        if (n < 0) {
+            return 4294967296L + n;
+        }
+        return n;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/GetCurrentStepCountRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/GetCurrentStepCountRequest.java
new file mode 100644
index 0000000..5d290b4
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/GetCurrentStepCountRequest.java
@@ -0,0 +1,46 @@
+/*  Copyright (C) 2019-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit;
+
+import android.bluetooth.BluetoothGattCharacteristic;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.Request;
+
+public class GetCurrentStepCountRequest extends Request {
+    public int steps = -1;
+
+    @Override
+    public byte[] getStartSequence() {
+        return new byte[]{1, 17};
+    }
+
+    @Override
+    public void handleResponse(BluetoothGattCharacteristic characteristic) {
+        super.handleResponse(characteristic);
+
+        byte[] value = characteristic.getValue();
+
+        if (value.length >= 6) {
+            ByteBuffer buffer = ByteBuffer.wrap(value);
+            buffer.order(ByteOrder.LITTLE_ENDIAN);
+            steps = buffer.getInt(2);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/GetStepGoalRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/GetStepGoalRequest.java
new file mode 100644
index 0000000..b7c539e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/GetStepGoalRequest.java
@@ -0,0 +1,45 @@
+/*  Copyright (C) 2019-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit;
+
+import android.bluetooth.BluetoothGattCharacteristic;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.Request;
+
+public class GetStepGoalRequest extends Request {
+    public int stepGoal = -1;
+    @Override
+    public void handleResponse(BluetoothGattCharacteristic characteristic) {
+        super.handleResponse(characteristic);
+        byte[] value = characteristic.getValue();
+        if (value.length < 6) {
+            return;
+        } else {
+            ByteBuffer buffer = ByteBuffer.wrap(value);
+            buffer.order(ByteOrder.LITTLE_ENDIAN);
+            stepGoal = buffer.getInt(2);
+        }
+    }
+
+    @Override
+    public byte[] getStartSequence() {
+        return new byte[]{1, 16};
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/GetTripleTapEnabledRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/GetTripleTapEnabledRequest.java
new file mode 100644
index 0000000..22742df
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/GetTripleTapEnabledRequest.java
@@ -0,0 +1,26 @@
+/*  Copyright (C) 2019-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.Request;
+
+public class GetTripleTapEnabledRequest extends Request {
+    @Override
+    public byte[] getStartSequence() {
+        return new byte[]{1, 7, 3};
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/GetVibrationStrengthRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/GetVibrationStrengthRequest.java
new file mode 100644
index 0000000..744ed3c
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/GetVibrationStrengthRequest.java
@@ -0,0 +1,45 @@
+/*  Copyright (C) 2019-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit;
+
+import android.bluetooth.BluetoothGattCharacteristic;
+
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.Request;
+
+public class GetVibrationStrengthRequest extends Request {
+    public int strength = -1;
+
+    public void handleResponse(BluetoothGattCharacteristic characteristic) {
+        byte[] value = characteristic.getValue();
+        if (value.length < 4) {
+            return;
+        } else {
+            ByteBuffer buffer = ByteBuffer.wrap(value);
+            buffer.order(ByteOrder.LITTLE_ENDIAN);
+            strength = (int) buffer.get(3);
+        }
+    }
+
+    @Override
+    public byte[] getStartSequence() {
+        return new byte[]{1, 15, 8};
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/GoalTrackingGetRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/GoalTrackingGetRequest.java
new file mode 100644
index 0000000..76290bf
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/GoalTrackingGetRequest.java
@@ -0,0 +1,44 @@
+/*  Copyright (C) 2019-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit;
+
+import android.bluetooth.BluetoothGattCharacteristic;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.Request;
+
+public class GoalTrackingGetRequest extends Request {
+    @Override
+    public void handleResponse(BluetoothGattCharacteristic characteristic) {
+        byte[] value = characteristic.getValue();
+        if(value.length != 5) return;
+
+        ByteBuffer buffer = ByteBuffer.wrap(value);
+        buffer.order(ByteOrder.LITTLE_ENDIAN);
+
+        short id = buffer.get(3);
+
+        boolean state = buffer.get(4) == 1;
+    }
+
+    @Override
+    public byte[] getStartSequence() {
+        return new byte[]{01, 20, 01};
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/GoalTrackingSetRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/GoalTrackingSetRequest.java
new file mode 100644
index 0000000..28cc1db
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/GoalTrackingSetRequest.java
@@ -0,0 +1,35 @@
+/*  Copyright (C) 2019-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.Request;
+
+public class GoalTrackingSetRequest extends Request {
+    public GoalTrackingSetRequest(int id, boolean state) {
+
+    }
+
+    @Override
+    public byte[] getStartSequence() {
+        return new byte[]{02, 20, 01};
+    }
+
+    @Override
+    public int getPayloadLength() {
+        return 5;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/ListFilesRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/ListFilesRequest.java
new file mode 100644
index 0000000..13eb741
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/ListFilesRequest.java
@@ -0,0 +1,58 @@
+/*  Copyright (C) 2019-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit;
+
+import android.bluetooth.BluetoothGattCharacteristic;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+public class ListFilesRequest extends FileRequest{
+    public int fileCount = -1;
+    public int size = 0;
+    private ByteBuffer buffer = null;
+    private int length = 0;
+
+
+    @Override
+    public void handleResponse(BluetoothGattCharacteristic characteristic) {
+        String uuid = characteristic.getUuid().toString();
+        byte[] value = characteristic.getValue();
+
+        if(uuid.equals("3dda0004-957f-7d4a-34a6-74696673696d")){
+            buffer.put(value, 1, value.length - 1);
+            length += value.length - 1;
+            if((value[0] & -128) != 0){
+                ByteBuffer buffer2 = ByteBuffer.wrap(buffer.array(), 0, length);
+                buffer2.order(ByteOrder.LITTLE_ENDIAN);
+                fileCount = buffer2.get(0);
+                size = buffer2.getInt(1);
+            }
+        }else if(uuid.equals("3dda0003-957f-7d4a-34a6-74696673696d")){
+            if(buffer == null){
+                buffer = ByteBuffer.allocate(128);
+            }else{
+                completed = true;
+            }
+        }
+    }
+
+    @Override
+    public byte[] getStartSequence() {
+        return new byte[]{(byte)5};
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/MoveHandsRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/MoveHandsRequest.java
new file mode 100644
index 0000000..c54c739
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/MoveHandsRequest.java
@@ -0,0 +1,136 @@
+/*  Copyright (C) 2019-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit;
+
+
+import java.nio.ByteBuffer;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.Request;
+
+public class MoveHandsRequest extends Request {
+    public MoveHandsRequest(MovementConfiguration movement){
+        init(movement);
+    }
+
+    private void init(MovementConfiguration movement) {
+        int count = 0;
+        if(movement.isHourSet()) count++;
+        if(movement.isMinuteSet()) count++;
+        if(movement.isSubSet()) count++;
+
+        ByteBuffer buffer = createBuffer(count * 5 + 5);
+        buffer.put(movement.isRelativeMovement() ? 1 : (byte)2);
+        buffer.put((byte)count);
+
+        if(movement.isHourSet()){
+            buffer.put((byte)1);
+            buffer.putShort((short)Math.abs(movement.getHourDegrees()));
+            if(movement.isRelativeMovement()){
+                buffer.put(movement.getHourDegrees() >= 0 ? (byte) 1 : (byte) 2);
+            }else {
+                buffer.put((byte)3);
+            }
+            buffer.put((byte)1);
+        }
+
+        if(movement.isMinuteSet()){
+            buffer.put((byte)2);
+            buffer.putShort((short)Math.abs(movement.getMinuteDegrees()));
+            if(movement.isRelativeMovement()){
+                buffer.put(movement.getMinuteDegrees() >= 0 ? (byte) 1 : (byte) 2);
+            }else {
+                buffer.put((byte)3);
+            }
+            buffer.put((byte)1);
+        }
+
+        if(movement.isSubSet()){
+            buffer.put((byte)3);
+            buffer.putShort((short)Math.abs(movement.getSubDegrees()));
+            if(movement.isRelativeMovement()){
+                buffer.put(movement.getSubDegrees() >= 0 ? (byte) 1 : (byte) 2);
+            }else {
+                buffer.put((byte)3);
+            }
+            buffer.put((byte)1);
+        }
+
+        this.data = buffer.array();
+    }
+
+    @Override
+    public byte[] getStartSequence() {
+        return new byte[]{2, 21, 3};
+    }
+
+    static public class MovementConfiguration{
+        private int hourDegrees, minuteDegrees, subDegrees;
+        private boolean hourSet = false, minuteSet = false, subSet = false;
+        private boolean relativeMovement;
+
+        public MovementConfiguration(boolean relativeMovement) {
+            this.relativeMovement = relativeMovement;
+        }
+
+        public boolean isRelativeMovement() {
+            return relativeMovement;
+        }
+
+        public void setRelativeMovement(boolean relativeMovement) {
+            this.relativeMovement = relativeMovement;
+        }
+
+        public int getHourDegrees() {
+            return hourDegrees;
+        }
+
+        public void setHourDegrees(int hourDegrees) {
+            this.hourDegrees = hourDegrees;
+            this.hourSet = true;
+        }
+
+        public int getMinuteDegrees() {
+            return minuteDegrees;
+        }
+
+        public void setMinuteDegrees(int minuteDegrees) {
+            this.minuteDegrees = minuteDegrees;
+            this.minuteSet = true;
+        }
+
+        public int getSubDegrees() {
+            return subDegrees;
+        }
+
+        public void setSubDegrees(int subDegrees) {
+            this.subDegrees = subDegrees;
+            this.subSet = true;
+        }
+
+        public boolean isHourSet() {
+            return hourSet;
+        }
+
+        public boolean isMinuteSet() {
+            return minuteSet;
+        }
+
+        public boolean isSubSet() {
+            return subSet;
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/OTAEnterRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/OTAEnterRequest.java
new file mode 100644
index 0000000..b20d112
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/OTAEnterRequest.java
@@ -0,0 +1,36 @@
+/*  Copyright (C) 2019-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit;
+
+import android.bluetooth.BluetoothGattCharacteristic;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.Request;
+
+public class OTAEnterRequest extends Request {
+    public boolean success = false;
+
+    @Override
+    public byte[] getStartSequence() {
+        return new byte[]{2, -15, 8};
+    }
+
+    @Override
+    public void handleResponse(BluetoothGattCharacteristic characteristic) {
+        byte[] result = characteristic.getValue();
+        success = result[2] == 9;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/OTAEraseRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/OTAEraseRequest.java
new file mode 100644
index 0000000..e1267ad
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/OTAEraseRequest.java
@@ -0,0 +1,61 @@
+/*  Copyright (C) 2019-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit;
+
+import android.bluetooth.BluetoothGattCharacteristic;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.Request;
+
+public class OTAEraseRequest extends Request {
+
+    public OTAEraseRequest(int pageOffset) {
+        ByteBuffer buffer = createBuffer();
+        buffer.putShort((short) 23131);
+        buffer.putInt(pageOffset);
+
+        this.data = buffer.array();
+    }
+
+    @Override
+    public byte[] getStartSequence() {
+        return new byte[]{18};
+    }
+
+    @Override
+    public int getPayloadLength() {
+        return 7;
+    }
+
+
+    public UUID getRequestUUID(){
+        return UUID.fromString("3dda0003-957f-7d4a-34a6-74696673696d");
+    }
+
+    @Override
+    public void handleResponse(BluetoothGattCharacteristic characteristic) {
+        byte[] bytes = characteristic.getValue();
+        final ByteBuffer wrap = ByteBuffer.wrap(bytes);
+        wrap.order(ByteOrder.LITTLE_ENDIAN);
+        short fileHandle = wrap.getShort(1);
+        byte status = wrap.get(3);
+        int sizeWritten = wrap.getInt(4);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/OTAResetRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/OTAResetRequest.java
new file mode 100644
index 0000000..1157206
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/OTAResetRequest.java
@@ -0,0 +1,26 @@
+/*  Copyright (C) 2019-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.Request;
+
+public class OTAResetRequest extends Request {
+    @Override
+    public byte[] getStartSequence() {
+        return new byte[]{2, -15, 10};
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/PlayNotificationRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/PlayNotificationRequest.java
new file mode 100644
index 0000000..328711f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/PlayNotificationRequest.java
@@ -0,0 +1,86 @@
+/*  Copyright (C) 2019-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit;
+
+import java.nio.ByteBuffer;
+import java.security.InvalidParameterException;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.Request;
+
+public class PlayNotificationRequest extends Request {
+
+    public enum VibrationType{
+        SINGLE_SHORT(3),
+        DOUBLE_SHORT(2),
+        TRIPLE_SHORT(1),
+        SINGLE_NORMAL(5),
+        DOUBLE_NORMAL(6),
+        TRIPLE_NORMAL(7),
+        SINGLE_LONG(8),
+        NO_VIBE(9);
+
+        private byte value;
+
+        VibrationType(int value) {
+            this.value = (byte)value;
+        }
+
+        public static VibrationType fromValue(byte value){
+            for(VibrationType type : values()){
+                if(type.getValue() == value) return type;
+            }
+            throw new InvalidParameterException("vibration Type not supported");
+        }
+
+        public byte getValue() {
+            return value;
+        }
+    }
+
+    public PlayNotificationRequest(VibrationType vibrationType, int degreesHour, int degreesMins, int degreesActivityHand){
+        int length = 0;
+        if(degreesHour > -1) length++;
+        if(degreesMins > -1) length++;
+        if(degreesActivityHand > -1) length++;
+        ByteBuffer buffer = createBuffer(length * 2 + 10);
+        buffer.put(vibrationType.getValue());
+        buffer.put((byte)5);
+        buffer.put((byte)(length * 2 + 2));
+        buffer.putShort((short)0);
+        if(degreesHour > -1){
+            buffer.putShort((short) ((degreesHour % 360) | (1 << 12)));
+        }
+        if(degreesMins > -1){
+            buffer.putShort((short)((degreesMins % 360) | (2 << 12)));
+        }
+        if(degreesActivityHand > -1) {
+            buffer.putShort((short)((degreesActivityHand % 360) | (3 << 12)));
+        }
+        this.data = buffer.array();
+    }
+
+
+
+    public PlayNotificationRequest(VibrationType vibrationType, int degreesHour, int degreesMins){
+        this(vibrationType, degreesHour, degreesMins, -1);
+    }
+
+    @Override
+    public byte[] getStartSequence() {
+        return new byte[]{2, 7, 15, 10, 1};
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/PutSettingsFileRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/PutSettingsFileRequest.java
new file mode 100644
index 0000000..dafdc9a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/PutSettingsFileRequest.java
@@ -0,0 +1,26 @@
+/*  Copyright (C) 2019-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.Request;
+
+public class PutSettingsFileRequest extends Request {
+    @Override
+    public byte[] getStartSequence() {
+        return new byte[0];
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/ReleaseHandsControlRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/ReleaseHandsControlRequest.java
new file mode 100644
index 0000000..c54d696
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/ReleaseHandsControlRequest.java
@@ -0,0 +1,45 @@
+/*  Copyright (C) 2019-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit;
+
+
+import java.nio.ByteBuffer;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.Request;
+
+public class ReleaseHandsControlRequest extends Request {
+    public ReleaseHandsControlRequest(){
+        super();
+        init((short)0);
+    }
+
+    private void init(short delayBeforeRelease) {
+        ByteBuffer buffer = createBuffer();
+        buffer.putShort(3, delayBeforeRelease);
+        this.data = buffer.array();
+    }
+
+    @Override
+    public byte[] getStartSequence() {
+        return new byte[]{2, 21, 2};
+    }
+
+    @Override
+    public int getPayloadLength() {
+        return 5;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/RequestHandControlRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/RequestHandControlRequest.java
new file mode 100644
index 0000000..d9a7901
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/RequestHandControlRequest.java
@@ -0,0 +1,52 @@
+/*  Copyright (C) 2019-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit;
+
+
+import java.nio.ByteBuffer;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.Request;
+
+public class RequestHandControlRequest extends Request {
+    public RequestHandControlRequest(byte priority, boolean moveCompleteNotify, boolean controlLostNOtify){
+        super();
+        init(priority, moveCompleteNotify, controlLostNOtify);
+    }
+
+    public RequestHandControlRequest(){
+        super();
+        init((byte)1, false, false);
+    }
+
+    private void init(byte priority, boolean moveCompleteNotify, boolean controlLostNOtify) {
+        ByteBuffer buffer = createBuffer();
+        buffer.put(priority);
+        buffer.put(moveCompleteNotify ? (byte)1 : 0);
+        buffer.put(controlLostNOtify ? (byte)1 : 0);
+        this.data = buffer.array();
+    }
+
+    @Override
+    public byte[] getStartSequence() {
+        return new byte[]{2, 21, 1};
+    }
+
+    @Override
+    public int getPayloadLength() {
+        return 6;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/SaveCalibrationRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/SaveCalibrationRequest.java
new file mode 100644
index 0000000..06b2ce7
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/SaveCalibrationRequest.java
@@ -0,0 +1,26 @@
+/*  Copyright (C) 2020-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.Request;
+
+public class SaveCalibrationRequest extends Request {
+    @Override
+    public byte[] getStartSequence() {
+        return new byte[]{0x02, (byte) 0xF2, 0x0E};
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/SetCountdownSettings.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/SetCountdownSettings.java
new file mode 100644
index 0000000..1f370f2
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/SetCountdownSettings.java
@@ -0,0 +1,44 @@
+/*  Copyright (C) 2019-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit;
+
+import java.nio.ByteBuffer;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.Request;
+
+public class SetCountdownSettings extends Request {
+    public SetCountdownSettings(int startTime, int endTime, short offset) {
+        ByteBuffer buffer = createBuffer();
+
+        buffer.putInt(startTime);
+        buffer.putInt(endTime);
+        buffer.putShort(offset);
+        // buff
+
+        this.data = buffer.array();
+    }
+
+    @Override
+    public byte[] getStartSequence() {
+        return new byte[]{2, 19, 1};
+    }
+
+    @Override
+    public int getPayloadLength() {
+        return 13;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/SetCurrentStepCountRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/SetCurrentStepCountRequest.java
new file mode 100644
index 0000000..87b8924
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/SetCurrentStepCountRequest.java
@@ -0,0 +1,40 @@
+/*  Copyright (C) 2019-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit;
+
+import java.nio.ByteBuffer;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.Request;
+
+public class SetCurrentStepCountRequest extends Request {
+    public SetCurrentStepCountRequest(int steps){
+        super();
+        ByteBuffer buffer = createBuffer();
+        buffer.putInt(steps);
+        this.data = buffer.array();
+    }
+
+    @Override
+    public int getPayloadLength() {
+        return 6;
+    }
+
+    @Override
+    public byte[] getStartSequence() {
+        return new byte[]{2, 17};
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/SetCurrentTimeServiceRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/SetCurrentTimeServiceRequest.java
new file mode 100644
index 0000000..169f6b7
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/SetCurrentTimeServiceRequest.java
@@ -0,0 +1,45 @@
+/*  Copyright (C) 2019-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit;
+
+import java.nio.ByteBuffer;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.Request;
+
+public class SetCurrentTimeServiceRequest extends Request {
+    public SetCurrentTimeServiceRequest(int timeStampSecs, short millis, short offsetInMins){
+        super();
+        init(timeStampSecs, millis, offsetInMins);
+    }
+    private void init(int timeStampSecs, short millis, short offsetInMins){
+        ByteBuffer buffer = createBuffer();
+        buffer.putInt(timeStampSecs);
+        buffer.putShort(millis);
+        buffer.putShort(offsetInMins);
+        this.data = buffer.array();
+    }
+
+    @Override
+    public int getPayloadLength() {
+        return 11;
+    }
+
+    @Override
+    public byte[] getStartSequence() {
+        return new byte[]{2, 18, 2};
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/SetStepGoalRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/SetStepGoalRequest.java
new file mode 100644
index 0000000..7791cd8
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/SetStepGoalRequest.java
@@ -0,0 +1,44 @@
+/*  Copyright (C) 2019-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit;
+
+import java.nio.ByteBuffer;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.Request;
+
+public class SetStepGoalRequest extends Request {
+    public SetStepGoalRequest(int goal){
+        super();
+        init(goal);
+    }
+
+    private void init(int goal) {
+        ByteBuffer buffer = createBuffer();
+        buffer.putInt(goal);
+        this.data = buffer.array();
+    }
+
+    @Override
+    public int getPayloadLength() {
+        return 6;
+    }
+
+    @Override
+    public byte[] getStartSequence() {
+        return new byte[]{2, 16};
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/SetTimeRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/SetTimeRequest.java
new file mode 100644
index 0000000..95e634f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/SetTimeRequest.java
@@ -0,0 +1,42 @@
+/*  Copyright (C) 2019-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit;
+
+import java.nio.ByteBuffer;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.Request;
+
+public class SetTimeRequest extends Request {
+    public SetTimeRequest(int epochSeconds, short millis, short minutesOffset) {
+        ByteBuffer buffer = createBuffer();
+        buffer.putInt(epochSeconds);
+        buffer.putShort(millis);
+        buffer.putShort(minutesOffset);
+
+        this.data = buffer.array();
+    }
+
+    @Override
+    public byte[] getStartSequence() {
+        return new byte[]{2, 4};
+    }
+
+    @Override
+    public int getPayloadLength() {
+        return 10;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/SetTripleTapEnabledRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/SetTripleTapEnabledRequest.java
new file mode 100644
index 0000000..d28abec
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/SetTripleTapEnabledRequest.java
@@ -0,0 +1,26 @@
+/*  Copyright (C) 2019-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.Request;
+
+public class SetTripleTapEnabledRequest extends Request {
+    @Override
+    public byte[] getStartSequence() {
+        return new byte[]{2, 7, 3, 1};
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/SetVibrationStrengthRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/SetVibrationStrengthRequest.java
new file mode 100644
index 0000000..a8d7bed
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/SetVibrationStrengthRequest.java
@@ -0,0 +1,45 @@
+/*  Copyright (C) 2019-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit;
+
+
+import java.nio.ByteBuffer;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.Request;
+
+public class SetVibrationStrengthRequest extends Request {
+    public SetVibrationStrengthRequest(short strength){
+        super();
+        init(strength);
+    }
+
+    private void init(int strength){
+        ByteBuffer buffer = createBuffer();
+        buffer.put((byte)strength);
+        this.data = buffer.array();
+    }
+
+    @Override
+    public int getPayloadLength() {
+        return 4;
+    }
+
+    @Override
+    public byte[] getStartSequence() {
+        return new byte[]{2, 15, 8};
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/SettingsFilePutRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/SettingsFilePutRequest.java
new file mode 100644
index 0000000..9bd5a9b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/SettingsFilePutRequest.java
@@ -0,0 +1,54 @@
+/*  Copyright (C) 2019-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit;
+
+import java.nio.ByteBuffer;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.Request;
+
+public class SettingsFilePutRequest extends Request {
+    public int fileLength;
+    public byte[] file;
+
+    public SettingsFilePutRequest(byte[] file){
+        this.fileLength = file.length;
+        this.file = file;
+        ByteBuffer buffer = this.createBuffer();
+        buffer.putShort(1, (short)0x0800);
+        buffer.putInt(3, 0);
+        buffer.putInt(7, fileLength - 10);
+        buffer.putInt(11, fileLength - 10);
+
+        this.data = buffer.array();
+    }
+
+    @Override
+    public int getPayloadLength() {
+        return 15;
+    }
+
+    @Override
+    public byte[] getStartSequence() {
+        return new byte[]{17};
+    }
+
+    @Override
+    public UUID getRequestUUID() {
+        return UUID.fromString("3dda0007-957f-7d4a-34a6-74696673696d");
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/UploadFileRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/UploadFileRequest.java
new file mode 100644
index 0000000..7714565
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/UploadFileRequest.java
@@ -0,0 +1,113 @@
+/*  Copyright (C) 2019-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit;
+
+import android.bluetooth.BluetoothGattCharacteristic;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.UUID;
+import java.util.zip.CRC32;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.Request;
+
+public class UploadFileRequest extends Request {
+    public enum UploadState{INITIALIZED, UPLOAD, UPLOADED, ERROR}
+
+    public UploadState state;
+
+    public ArrayList<byte[]> packets = new ArrayList<>();
+
+    public UploadFileRequest(short handle, byte[] file) {
+        int fileLength = file.length + 4;
+        ByteBuffer buffer = this.createBuffer();
+        buffer.putShort(1, handle);
+        buffer.putInt(3, 0);
+        buffer.putInt(7, fileLength);
+        buffer.putInt(11, fileLength);
+
+        this.data = buffer.array();
+
+        prepareFilePackets(file);
+
+        state = UploadState.INITIALIZED;
+    }
+
+    @Override
+    public void handleResponse(BluetoothGattCharacteristic characteristic) {
+        byte[] value = characteristic.getValue();
+        if (value.length == 4) {
+            if (value[1] != 0) {
+                state = UploadState.ERROR;
+                return;
+            }
+            state = UploadState.UPLOAD;
+        }else if(value.length == 9){
+            if(value[1] != 0){
+                state = UploadState.ERROR;
+                return;
+            }
+            state = UploadState.UPLOADED;
+        }
+    }
+
+    private void prepareFilePackets(byte[] file) {
+        ByteBuffer buffer = ByteBuffer.allocate(file.length + 4);
+        buffer.order(ByteOrder.LITTLE_ENDIAN);
+
+        buffer.put(file);
+
+        CRC32 crc = new CRC32();
+        crc.update(file);
+        buffer.putInt((int) crc.getValue());
+
+        byte[] fileFull = buffer.array();
+        for (int i = 0, sequence = 0; i < fileFull.length + 4; i += 18, sequence++) {
+            byte[] packet;
+            if (i + 18 >= fileFull.length) {
+                packet = new byte[fileFull.length - i + 2];
+                System.arraycopy(fileFull, i, packet, 2, fileFull.length - i);
+            } else {
+                packet = new byte[20];
+                System.arraycopy(fileFull, i, packet, 2, 18);
+            }
+            packet[0] = 0x12;
+            packet[1] = (byte) sequence;
+            packets.add(packet);
+        }
+        packets.get(0)[1] |= 0x40;
+        if (packets.size() > 1) {
+            packets.get(packets.size() - 1)[1] |= 0x80;
+        }
+    }
+
+    @Override
+    public byte[] getStartSequence() {
+        return new byte[]{17};
+    }
+
+    @Override
+    public int getPayloadLength() {
+        return 15;
+    }
+
+    @Override
+    public UUID getRequestUUID() {
+        return UUID.fromString("3dda0007-957f-7d4a-34a6-74696673696d");
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/VibrateRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/VibrateRequest.java
new file mode 100644
index 0000000..970b39a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/misfit/VibrateRequest.java
@@ -0,0 +1,44 @@
+/*  Copyright (C) 2019-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit;
+
+
+import java.nio.ByteBuffer;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.Request;
+
+public class VibrateRequest extends Request {
+
+    public VibrateRequest(boolean longVibration, short repeats, short millisBetween){
+        ByteBuffer buffer = createBuffer();
+
+        buffer.put(longVibration ? (byte)1 : 0);
+        buffer.put((byte) repeats);
+        buffer.putShort(millisBetween);
+        this.data = buffer.array();
+    }
+
+    @Override
+    public int getPayloadLength() {
+        return 7;
+    }
+
+    @Override
+    public byte[] getStartSequence() {
+        return new byte[]{2, 15, 5};
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/redmibuds5pro/RedmiBuds5ProDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/redmibuds5pro/RedmiBuds5ProDeviceSupport.java
new file mode 100644
index 0000000..af69f49
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/redmibuds5pro/RedmiBuds5ProDeviceSupport.java
@@ -0,0 +1,46 @@
+/*  Copyright (C) 2024 Jonathan Gobbo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.redmibuds5pro;
+
+import nodomain.freeyourgadget.gadgetbridge.service.serial.AbstractSerialDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceIoThread;
+import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol;
+
+public class RedmiBuds5ProDeviceSupport extends AbstractSerialDeviceSupport {
+    @Override
+    protected GBDeviceProtocol createDeviceProtocol() {
+        return new RedmiBuds5ProProtocol(getDevice());
+    }
+
+    @Override
+    protected GBDeviceIoThread createDeviceIOThread() {
+        return new RedmiBuds5ProIOThread(getDevice(), getContext(),
+                (RedmiBuds5ProProtocol) getDeviceProtocol(),
+                RedmiBuds5ProDeviceSupport.this, getBluetoothAdapter());
+    }
+
+    @Override
+    public boolean connect() {
+        getDeviceIOThread().start();
+        return true;
+    }
+
+    @Override
+    public boolean useAutoConnect() {
+        return false;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/redmibuds5pro/RedmiBuds5ProIOThread.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/redmibuds5pro/RedmiBuds5ProIOThread.java
new file mode 100644
index 0000000..e42bcc8
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/redmibuds5pro/RedmiBuds5ProIOThread.java
@@ -0,0 +1,66 @@
+/*  Copyright (C) 2024 Jonathan Gobbo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.redmibuds5pro;
+
+import static nodomain.freeyourgadget.gadgetbridge.util.GB.hexdump;
+
+import android.bluetooth.BluetoothAdapter;
+import android.content.Context;
+import android.os.ParcelUuid;
+
+import androidx.annotation.NonNull;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.btclassic.BtClassicIoThread;
+
+public class RedmiBuds5ProIOThread extends BtClassicIoThread {
+    private static final Logger LOG = LoggerFactory.getLogger(RedmiBuds5ProIOThread.class);
+    private final RedmiBuds5ProProtocol redmiProtocol;
+
+    public RedmiBuds5ProIOThread(GBDevice gbDevice, Context context, RedmiBuds5ProProtocol redmiProtocol, RedmiBuds5ProDeviceSupport deviceSupport, BluetoothAdapter btAdapter) {
+        super(gbDevice, context, redmiProtocol, deviceSupport, btAdapter);
+        this.redmiProtocol = redmiProtocol;
+    }
+
+    @Override
+    protected byte[] parseIncoming(InputStream stream) throws IOException {
+        byte[] buffer = new byte[1048576];
+        int bytes = stream.read(buffer);
+        LOG.debug("read {} bytes. {}", bytes, hexdump(buffer, 0, bytes));
+        return Arrays.copyOf(buffer, bytes);
+    }
+
+    @NonNull
+    @Override
+    protected UUID getUuidToConnect(@NonNull ParcelUuid[] uuids) {
+        return this.redmiProtocol.UUID_DEVICE_CTRL;
+    }
+
+    @Override
+    protected void initialize() {
+        write(redmiProtocol.encodeStartAuthentication());
+        setUpdateState(GBDevice.State.INITIALIZING);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/redmibuds5pro/RedmiBuds5ProProtocol.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/redmibuds5pro/RedmiBuds5ProProtocol.java
new file mode 100644
index 0000000..a1d4076
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/redmibuds5pro/RedmiBuds5ProProtocol.java
@@ -0,0 +1,548 @@
+/*  Copyright (C) 2024 Jonathan Gobbo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.redmibuds5pro;
+
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.*;
+import static nodomain.freeyourgadget.gadgetbridge.util.GB.hexdump;
+
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.Editor;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventSendBytes;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdateDeviceState;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo;
+import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.redmibuds5pro.prefs.Configuration.Config;
+import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.redmibuds5pro.prefs.Configuration.StrengthTarget;
+import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.redmibuds5pro.prefs.Gestures.InteractionType;
+import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.redmibuds5pro.prefs.Gestures.Position;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice.State;
+import nodomain.freeyourgadget.gadgetbridge.model.BatteryState;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.redmibuds5pro.protocol.Authentication;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.redmibuds5pro.protocol.Message;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.redmibuds5pro.protocol.MessageType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.redmibuds5pro.protocol.Opcode;
+import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+public class RedmiBuds5ProProtocol extends GBDeviceProtocol {
+
+    private static final Logger LOG = LoggerFactory.getLogger(RedmiBuds5ProProtocol.class);
+    final UUID UUID_DEVICE_CTRL = UUID.fromString("0000fd2d-0000-1000-8000-00805f9b34fb");
+
+    private byte sequenceNumber = 0;
+
+    protected RedmiBuds5ProProtocol(GBDevice device) {
+        super(device);
+    }
+
+    public byte[] encodeStartAuthentication() {
+        byte[] authRnd = Authentication.getRandomChallenge();
+        LOG.debug("[AUTH] Sending challenge: {}", hexdump(authRnd));
+
+        byte[] payload = new byte[17];
+        payload[0] = 0x01;
+        System.arraycopy(authRnd, 0, payload, 1, 16);
+        return new Message(MessageType.PHONE_REQUEST, Opcode.AUTH_CHALLENGE, sequenceNumber++, payload).encode();
+    }
+
+    @Override
+    public byte[] encodeSendConfiguration(String config) {
+        switch (config) {
+            case PREF_REDMI_BUDS_5_PRO_AMBIENT_SOUND_CONTROL:
+                return encodeSetAmbientSoundControl();
+            case PREF_REDMI_BUDS_5_PRO_NOISE_CANCELLING_STRENGTH:
+                return encodeSetEffectStrength(config, StrengthTarget.ANC);
+            case PREF_REDMI_BUDS_5_PRO_TRANSPARENCY_STRENGTH:
+                return encodeSetEffectStrength(config, StrengthTarget.TRANSPARENCY);
+            case PREF_REDMI_BUDS_5_PRO_ADAPTIVE_NOISE_CANCELLING:
+                return encodeSetBooleanConfig(config, Config.ADAPTIVE_ANC);
+//            case PREF_REDMI_BUDS_5_PRO_PERSONALIZED_NOISE_CANCELLING:
+//                return encodeSetBooleanConfig(config, Config.CUSTOMIZED_ANC);
+
+            case PREF_REDMI_BUDS_5_PRO_CONTROL_SINGLE_TAP_LEFT:
+                return encodeSetGesture(config, InteractionType.SINGLE, Position.LEFT);
+            case PREF_REDMI_BUDS_5_PRO_CONTROL_SINGLE_TAP_RIGHT:
+                return encodeSetGesture(config, InteractionType.SINGLE, Position.RIGHT);
+            case PREF_REDMI_BUDS_5_PRO_CONTROL_DOUBLE_TAP_LEFT:
+                return encodeSetGesture(config, InteractionType.DOUBLE, Position.LEFT);
+            case PREF_REDMI_BUDS_5_PRO_CONTROL_DOUBLE_TAP_RIGHT:
+                return encodeSetGesture(config, InteractionType.DOUBLE, Position.RIGHT);
+            case PREF_REDMI_BUDS_5_PRO_CONTROL_TRIPLE_TAP_LEFT:
+                return encodeSetGesture(config, InteractionType.TRIPLE, Position.LEFT);
+            case PREF_REDMI_BUDS_5_PRO_CONTROL_TRIPLE_TAP_RIGHT:
+                return encodeSetGesture(config, InteractionType.TRIPLE, Position.RIGHT);
+            case PREF_REDMI_BUDS_5_PRO_CONTROL_LONG_TAP_MODE_LEFT:
+                return encodeSetGesture(config, InteractionType.LONG, Position.LEFT);
+            case PREF_REDMI_BUDS_5_PRO_CONTROL_LONG_TAP_MODE_RIGHT:
+                return encodeSetGesture(config, InteractionType.LONG, Position.RIGHT);
+            case PREF_REDMI_BUDS_5_PRO_CONTROL_LONG_TAP_SETTINGS_LEFT:
+                return encodeSetLongGestureMode(config, Position.LEFT);
+            case PREF_REDMI_BUDS_5_PRO_CONTROL_LONG_TAP_SETTINGS_RIGHT:
+                return encodeSetLongGestureMode(config, Position.RIGHT);
+
+            case PREF_REDMI_BUDS_5_PRO_WEARING_DETECTION:
+                return encodeSetEarDetection();
+            case PREF_REDMI_BUDS_5_PRO_AUTO_REPLY_PHONECALL:
+                return encodeSetBooleanConfig(config, Config.AUTO_ANSWER);
+            case PREF_REDMI_BUDS_5_PRO_DOUBLE_CONNECTION:
+                return encodeSetBooleanConfig(config, Config.DOUBLE_CONNECTION);
+            case PREF_REDMI_BUDS_5_PRO_ADAPTIVE_SOUND:
+                return encodeSetBooleanConfig(config, Config.ADAPTIVE_SOUND);
+
+            case PREF_REDMI_BUDS_5_PRO_EQUALIZER_PRESET:
+                return encodeSetIntegerConfig(config, Config.EQ_PRESET);
+            case PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_62:
+            case PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_125:
+            case PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_250:
+            case PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_500:
+            case PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_1k:
+            case PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_2k:
+            case PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_4k:
+            case PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_8k:
+            case PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_12k:
+            case PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_16k:
+                return encodeSetCustomEqualizer();
+
+            default:
+                LOG.debug("Unsupported config: {}", config);
+        }
+
+        return super.encodeSendConfiguration(config);
+    }
+
+    public byte[] encodeSetCustomEqualizer() {
+        Prefs prefs = getDevicePrefs();
+
+        List<String> bands = List.of(PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_62, PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_125,
+                PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_250, PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_500, PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_1k,
+                PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_2k, PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_4k, PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_8k,
+                PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_12k, PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_16k);
+
+        byte[] eqCurve = new byte[10];
+        for (int i = 0; i < 10; i++) {
+            eqCurve[i] = (byte) Integer.parseInt(prefs.getString(bands.get(i), "0"));
+        }
+        return new Message(MessageType.PHONE_REQUEST, Opcode.SET_CONFIG, sequenceNumber++, new byte[]{
+                0x24, 0x00, 0x37, 0x05, 0x01, 0x01, 0x0A,
+                0x00, 0x3E, eqCurve[0], 0x00, 0x7D, eqCurve[1],
+                0x00, (byte) 0xFA, eqCurve[2], 0x01, (byte) 0xF4, eqCurve[3],
+                0x03, (byte) 0xE8, eqCurve[4], 0x07, (byte) 0xE0, eqCurve[5],
+                0x0F, (byte) 0xA0, eqCurve[6], 0x1F, 0x40, eqCurve[7],
+                0x2E, (byte) 0xE0, eqCurve[8], 0x3E, (byte) 0x80, eqCurve[9]
+        }).encode();
+    }
+
+    public byte[] encodeSetEarDetection() {
+        Prefs prefs = getDevicePrefs();
+        byte value = (byte) (prefs.getBoolean(PREF_REDMI_BUDS_5_PRO_WEARING_DETECTION, false) ? 0x00 : 0x01);
+        return new Message(MessageType.PHONE_REQUEST, Opcode.ANC, sequenceNumber++, new byte[]{0x02, 0x06, value}).encode();
+    }
+
+    public byte[] encodeSetLongGestureMode(String config, Position position) {
+        Prefs prefs = getDevicePrefs();
+        byte value = (byte) Integer.parseInt(prefs.getString(config, "7"));
+        byte[] payload = new byte[]{0x04, 0x00, 0x0a, (byte) 0xFF, (byte) 0xFF};
+        if (position == Position.LEFT) {
+            payload[3] = value;
+        } else {
+            payload[4] = value;
+        }
+        return new Message(MessageType.PHONE_REQUEST, Opcode.SET_CONFIG, sequenceNumber++, payload).encode();
+    }
+
+    public byte[] encodeSetGesture(String config, InteractionType interactionType, Position position) {
+        Prefs prefs = getDevicePrefs();
+        byte value = (byte) Integer.parseInt(prefs.getString(config, "1"));
+        byte[] payload = new byte[]{0x05, 0x00, 0x02, interactionType.value, (byte) 0xFF, (byte) 0xFF};
+        if (position == Position.LEFT) {
+            payload[4] = value;
+        } else {
+            payload[5] = value;
+        }
+        return new Message(MessageType.PHONE_REQUEST, Opcode.SET_CONFIG, sequenceNumber++, payload).encode();
+    }
+
+    public byte[] encodeSetEffectStrength(String pref, StrengthTarget effect) {
+        Prefs prefs = getDevicePrefs();
+        byte mode = (byte) Integer.parseInt(prefs.getString(pref, "0"));
+        return new Message(MessageType.PHONE_REQUEST, Opcode.SET_CONFIG, sequenceNumber++, new byte[]{0x04, 0x00, 0x0b, effect.value, mode}).encode();
+    }
+
+    public byte[] encodeSetIntegerConfig(String pref, Config config) {
+        Prefs prefs = getDevicePrefs();
+        byte value = (byte) Integer.parseInt(prefs.getString(pref, "0"));
+        return new Message(MessageType.PHONE_REQUEST, Opcode.SET_CONFIG, sequenceNumber++, new byte[]{0x03, 0x00, config.value, value}).encode();
+    }
+
+    public byte[] encodeSetBooleanConfig(String pref, Config config) {
+        Prefs prefs = getDevicePrefs();
+        byte value = (byte) (prefs.getBoolean(pref, false) ? 0x01 : 0x00);
+        return new Message(MessageType.PHONE_REQUEST, Opcode.SET_CONFIG, sequenceNumber++, new byte[]{0x03, 0x00, config.value, value}).encode();
+    }
+
+    public byte[] encodeGetConfig() {
+        List<Config> configs = List.of(Config.EFFECT_STRENGTH, Config.ADAPTIVE_ANC, // Config.CUSTOMIZED_ANC,
+                Config.GESTURES, Config.LONG_GESTURES, Config.EAR_DETECTION, Config.DOUBLE_CONNECTION,
+                Config.AUTO_ANSWER, Config.ADAPTIVE_SOUND, Config.EQ_PRESET, Config.EQ_CURVE);
+
+        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        try {
+            for (Config config : configs) {
+                Message message = new Message(MessageType.PHONE_REQUEST, Opcode.GET_CONFIG, sequenceNumber++, new byte[]{0x00, config.value});
+                outputStream.write(message.encode());
+            }
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+        return outputStream.toByteArray();
+    }
+
+    public byte[] encodeSetAmbientSoundControl() {
+        Prefs prefs = getDevicePrefs();
+        byte mode = (byte) Integer.parseInt(prefs.getString(PREF_REDMI_BUDS_5_PRO_AMBIENT_SOUND_CONTROL, "0"));
+        return new Message(MessageType.PHONE_REQUEST, Opcode.ANC, sequenceNumber++, new byte[]{0x02, 0x04, mode}).encode();
+    }
+
+    public void decodeGetConfig(byte[] configPayload) {
+
+        SharedPreferences preferences = getDevicePrefs().getPreferences();
+        Editor editor = preferences.edit();
+        Config config = Config.fromCode(configPayload[2]);
+        switch (config) {
+            case GESTURES:
+                editor.putString(PREF_REDMI_BUDS_5_PRO_CONTROL_SINGLE_TAP_LEFT, Integer.toString(configPayload[4]));
+                editor.putString(PREF_REDMI_BUDS_5_PRO_CONTROL_SINGLE_TAP_RIGHT, Integer.toString(configPayload[5]));
+
+                editor.putString(PREF_REDMI_BUDS_5_PRO_CONTROL_DOUBLE_TAP_LEFT, Integer.toString(configPayload[7]));
+                editor.putString(PREF_REDMI_BUDS_5_PRO_CONTROL_DOUBLE_TAP_RIGHT, Integer.toString(configPayload[8]));
+
+                editor.putString(PREF_REDMI_BUDS_5_PRO_CONTROL_TRIPLE_TAP_LEFT, Integer.toString(configPayload[10]));
+                editor.putString(PREF_REDMI_BUDS_5_PRO_CONTROL_TRIPLE_TAP_RIGHT, Integer.toString(configPayload[11]));
+
+                editor.putString(PREF_REDMI_BUDS_5_PRO_CONTROL_LONG_TAP_MODE_LEFT, Integer.toString(configPayload[13]));
+                editor.putString(PREF_REDMI_BUDS_5_PRO_CONTROL_LONG_TAP_MODE_RIGHT, Integer.toString(configPayload[14]));
+                break;
+            case AUTO_ANSWER:
+                editor.putBoolean(PREF_REDMI_BUDS_5_PRO_AUTO_REPLY_PHONECALL, configPayload[3] == 0x01);
+                break;
+            case DOUBLE_CONNECTION:
+                editor.putBoolean(PREF_REDMI_BUDS_5_PRO_DOUBLE_CONNECTION, configPayload[3] == 0x01);
+                break;
+            case EQ_PRESET:
+                editor.putString(PREF_REDMI_BUDS_5_PRO_EQUALIZER_PRESET, Integer.toString(configPayload[3]));
+                break;
+            case LONG_GESTURES:
+                editor.putString(PREF_REDMI_BUDS_5_PRO_CONTROL_LONG_TAP_SETTINGS_LEFT, Integer.toString(configPayload[3]));
+                editor.putString(PREF_REDMI_BUDS_5_PRO_CONTROL_LONG_TAP_SETTINGS_RIGHT, Integer.toString(configPayload[4]));
+                break;
+            case EFFECT_STRENGTH:
+                byte mode = configPayload[4];
+                if (configPayload[3] == StrengthTarget.ANC.value) {
+                    editor.putString(PREF_REDMI_BUDS_5_PRO_NOISE_CANCELLING_STRENGTH, Integer.toString(mode));
+                } else if (configPayload[3] == StrengthTarget.TRANSPARENCY.value) {
+                    editor.putString(PREF_REDMI_BUDS_5_PRO_TRANSPARENCY_STRENGTH, Integer.toString(mode));
+                }
+                break;
+            case ADAPTIVE_ANC:
+                editor.putBoolean(PREF_REDMI_BUDS_5_PRO_ADAPTIVE_NOISE_CANCELLING, configPayload[3] == 0x01);
+                break;
+            case ADAPTIVE_SOUND:
+                editor.putBoolean(PREF_REDMI_BUDS_5_PRO_ADAPTIVE_SOUND, configPayload[3] == 0x01);
+                break;
+            case EQ_CURVE:
+                editor.putString(PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_62, Integer.toString(configPayload[12] & 0xFF));
+                editor.putString(PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_125, Integer.toString(configPayload[15] & 0xFF));
+                editor.putString(PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_250, Integer.toString(configPayload[18] & 0xFF));
+                editor.putString(PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_500, Integer.toString(configPayload[21] & 0xFF));
+                editor.putString(PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_1k, Integer.toString(configPayload[24] & 0xFF));
+                editor.putString(PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_2k, Integer.toString(configPayload[27] & 0xFF));
+                editor.putString(PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_4k, Integer.toString(configPayload[30] & 0xFF));
+                editor.putString(PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_8k, Integer.toString(configPayload[33] & 0xFF));
+                editor.putString(PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_12k, Integer.toString(configPayload[36] & 0xFF));
+                editor.putString(PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_16k, Integer.toString(configPayload[39] & 0xFF));
+                break;
+//            case CUSTOMIZED_ANC:
+//                editor.putBoolean(PREF_REDMI_BUDS_5_PRO_PERSONALIZED_NOISE_CANCELLING, configPayload[3] == 0x01);
+//                break;
+            default:
+                LOG.debug("Unhandled device update: {}", hexdump(configPayload));
+        }
+        editor.apply();
+    }
+
+    private GBDeviceEventBatteryInfo parseBatteryInfo(byte batteryInfo, int index) {
+
+        if (batteryInfo == (byte) 0xff) {
+            return null;
+        }
+        GBDeviceEventBatteryInfo batteryEvent = new GBDeviceEventBatteryInfo();
+        batteryEvent.state = (batteryInfo & 128) != 0 ? BatteryState.BATTERY_CHARGING : BatteryState.BATTERY_NORMAL;
+        batteryEvent.batteryIndex = index;
+        batteryEvent.level = (batteryInfo & 127);
+        LOG.debug("Battery {}: {}", index, batteryEvent.level);
+        return batteryEvent;
+    }
+
+    private GBDeviceEvent[] decodeDeviceInfo(byte[] deviceInfoPayload) {
+
+        List<GBDeviceEvent> events = new ArrayList<>();
+
+        GBDeviceEventVersionInfo info = new GBDeviceEventVersionInfo();
+        byte[] fw = new byte[4];
+        byte[] vidPid = new byte[4];
+        byte[] batteryData = new byte[3];
+        int i = 0;
+        while (i < deviceInfoPayload.length) {
+            byte len = deviceInfoPayload[i];
+            byte index = deviceInfoPayload[i + 1];
+            switch (index) {
+                case 0x01:
+                    System.arraycopy(deviceInfoPayload, i + 2, fw, 0, 4);
+                    break;
+                case 0x03:
+                    System.arraycopy(deviceInfoPayload, i + 2, vidPid, 0, 4);
+                    break;
+                case 0x07:
+                    System.arraycopy(deviceInfoPayload, i + 2, batteryData, 0, 3);
+                    break;
+            }
+            i += len + 1;
+        }
+
+        String fwVersion1 = ((fw[0] >> 4) & 0xF) + "." + (fw[0] & 0xF) + "." + ((fw[1] >> 4) & 0xF) + "." + (fw[1] & 0xF);
+        String fwVersion2 = ((fw[2] >> 4) & 0xF) + "." + (fw[2] & 0xF) + "." + ((fw[3] >> 4) & 0xF) + "." + (fw[3] & 0xF);
+        String hwVersion = String.format("VID: 0x%02X%02X, PID: 0x%02X%02X", vidPid[0], vidPid[1], vidPid[2], vidPid[3]);
+
+        info.fwVersion = fwVersion1;
+        info.fwVersion2 = fwVersion2;
+        info.hwVersion = hwVersion;
+
+        events.add(parseBatteryInfo(batteryData[0], 1));
+        events.add(parseBatteryInfo(batteryData[1], 2));
+        events.add(parseBatteryInfo(batteryData[2], 0));
+        events.add(info);
+
+        return events.toArray(new GBDeviceEvent[0]);
+    }
+
+    private void decodeDeviceRunInfo(byte[] deviceRunInfoPayload) {
+        int i = 0;
+        while (i < deviceRunInfoPayload.length) {
+            byte len = deviceRunInfoPayload[i];
+            byte index = deviceRunInfoPayload[i + 1];
+            SharedPreferences preferences = getDevicePrefs().getPreferences();
+            Editor editor = preferences.edit();
+            switch (index) {
+                case 0x09:
+                    byte mode = deviceRunInfoPayload[i + 2];
+                    editor.putString(PREF_REDMI_BUDS_5_PRO_AMBIENT_SOUND_CONTROL, Integer.toString(mode));
+                    break;
+                case 0x0A:
+                    editor.putBoolean(PREF_REDMI_BUDS_5_PRO_WEARING_DETECTION, deviceRunInfoPayload[i + 2] == 0x00);
+            }
+            editor.apply();
+            i += len + 1;
+        }
+    }
+
+    private GBDeviceEvent[] decodeDeviceUpdate(Message updateMessage) {
+        byte[] updatePayload = updateMessage.getPayload();
+        List<GBDeviceEvent> events = new ArrayList<>();
+
+        int i = 0;
+        while (i < updatePayload.length) {
+            byte len = updatePayload[i];
+            byte index = updatePayload[i + 1];
+            switch (index) {
+                case 0x00:
+                    events.add(parseBatteryInfo(updatePayload[i + 2], 1));
+                    events.add(parseBatteryInfo(updatePayload[i + 3], 2));
+                    events.add(parseBatteryInfo(updatePayload[i + 4], 0));
+                    break;
+                case 0x04:
+                    SharedPreferences preferences = getDevicePrefs().getPreferences();
+                    Editor editor = preferences.edit();
+
+                    byte mode = updatePayload[i + 2];
+                    editor.putString(PREF_REDMI_BUDS_5_PRO_AMBIENT_SOUND_CONTROL, Integer.toString(mode));
+                    editor.apply();
+                    break;
+                default:
+                    LOG.debug("Unimplemented device update: {}", hexdump(updatePayload));
+            }
+            i += len + 1;
+        }
+        events.add(new GBDeviceEventSendBytes(new Message(MessageType.RESPONSE, Opcode.REPORT_STATUS, updateMessage.getSequenceNumber(), new byte[]{}).encode()));
+        return events.toArray(new GBDeviceEvent[0]);
+    }
+
+    private GBDeviceEvent[] decodeNotifyConfig(Message notifyMessage) {
+
+        byte[] notifyPayload = notifyMessage.getPayload();
+        List<GBDeviceEvent> events = new ArrayList<>();
+
+        int i = 0;
+        while (i < notifyPayload.length) {
+            byte len = notifyPayload[i];
+            byte index = notifyPayload[i + 2];
+            switch (index) {
+                case 0x0C:
+                    LOG.debug("Received earbuds position info");
+                    /*
+                    e.g. 0C 03
+                            0011
+                            wearing left, wearing right, left in case, right in case
+                     */
+                    break;
+                case 0x0B:
+                    SharedPreferences preferences = getDevicePrefs().getPreferences();
+                    Editor editor = preferences.edit();
+
+                    byte soundCtrlMode = notifyPayload[i + 3];
+                    editor.putString(PREF_REDMI_BUDS_5_PRO_AMBIENT_SOUND_CONTROL, Integer.toString(soundCtrlMode));
+
+                    byte mode = notifyPayload[i + 4];
+                    if (notifyPayload[i + 3] == 0x01) {
+                        editor.putString(PREF_REDMI_BUDS_5_PRO_NOISE_CANCELLING_STRENGTH, Integer.toString(mode));
+                    } else {
+                        editor.putString(PREF_REDMI_BUDS_5_PRO_TRANSPARENCY_STRENGTH, Integer.toString(mode));
+                    }
+
+                    editor.apply();
+                    break;
+            }
+
+            i += len + 1;
+        }
+        events.add(new GBDeviceEventSendBytes(new Message(MessageType.RESPONSE, Opcode.NOTIFY_CONFIG, notifyMessage.getSequenceNumber(), new byte[]{}).encode()));
+        return events.toArray(new GBDeviceEvent[0]);
+    }
+
+    private GBDeviceEvent[] handleAuthentication(Message authMessage) {
+        List<GBDeviceEvent> events = new ArrayList<>();
+        switch (authMessage.getOpcode()) {
+            case AUTH_CHALLENGE:
+                if (authMessage.getType() == MessageType.RESPONSE) {
+                    LOG.debug("[AUTH] Received Challenge Response");
+                    /*
+                        Should check if equal, but does not really matter
+                     */
+                    LOG.debug("[AUTH] Sending authentication confirmation");
+                    events.add(new GBDeviceEventSendBytes(new Message(MessageType.PHONE_REQUEST, Opcode.AUTH_CONFIRM, sequenceNumber++, new byte[]{0x01, 0x00}).encode()));
+                } else {
+                    byte[] responsePayload = authMessage.getPayload();
+                    byte[] challenge = new byte[16];
+                    System.arraycopy(responsePayload, 1, challenge, 0, 16);
+
+                    LOG.info("[AUTH] Received Challenge: {}", hexdump(challenge));
+                    Authentication auth = new Authentication();
+                    byte[] challengeResponse = auth.computeChallengeResponse(challenge);
+                    LOG.info("[AUTH] Sending Challenge Response: {}", hexdump(challengeResponse));
+
+                    byte[] payload = new byte[17];
+                    payload[0] = 0x01;
+                    System.arraycopy(challengeResponse, 0, payload, 1, 16);
+                    Message res = new Message(MessageType.RESPONSE, Opcode.AUTH_CHALLENGE, authMessage.getSequenceNumber(), payload);
+                    events.add(new GBDeviceEventSendBytes(res.encode()));
+                }
+                break;
+            case AUTH_CONFIRM:
+                if (authMessage.getType() == MessageType.RESPONSE) {
+                    LOG.debug("[AUTH] Confirmed first authentication step");
+                } else {
+                    LOG.debug("[AUTH] Received authentication confirmation");
+                    Message res = new Message(MessageType.RESPONSE, Opcode.AUTH_CONFIRM, authMessage.getSequenceNumber(), new byte[]{0x01});
+                    LOG.debug("[AUTH] Sending final authentication confirmation");
+                    events.add(new GBDeviceEventSendBytes(res.encode()));
+
+                    LOG.debug("[INIT] Sending device info request");
+                    Message info = new Message(MessageType.PHONE_REQUEST, Opcode.GET_DEVICE_INFO, sequenceNumber++, new byte[]{(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff});
+                    events.add(new GBDeviceEventSendBytes(info.encode()));
+
+                    LOG.debug("[INIT] Sending device run info request");
+                    Message runInfo = new Message(MessageType.PHONE_REQUEST, Opcode.GET_DEVICE_RUN_INFO, sequenceNumber++, new byte[]{(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff});
+                    events.add(new GBDeviceEventSendBytes(runInfo.encode()));
+
+                    LOG.debug("[INIT] Sending configuration request");
+                    events.add(new GBDeviceEventSendBytes(encodeGetConfig()));
+                }
+                break;
+        }
+        return events.toArray(new GBDeviceEvent[0]);
+    }
+
+    @Override
+    public GBDeviceEvent[] decodeResponse(byte[] responseData) {
+
+        LOG.debug("Incoming message: {}", hexdump(responseData));
+
+        List<GBDeviceEvent> events = new ArrayList<>();
+
+        List<Message> incomingMessages = Message.splitPiggybackedMessages(responseData);
+
+        for (Message message : incomingMessages) {
+
+            LOG.debug("Parsed message: {}", message);
+
+            switch (message.getOpcode()) {
+                case AUTH_CHALLENGE:
+                case AUTH_CONFIRM:
+                    events.addAll(Arrays.asList(handleAuthentication(message)));
+                    break;
+                case GET_DEVICE_INFO:
+                    LOG.debug("[INIT] Received device info");
+                    if (getDevice().getState() != State.INITIALIZED) {
+                        events.addAll(Arrays.asList(decodeDeviceInfo(message.getPayload())));
+                        LOG.debug("[INIT] Device Initialized");
+                        events.add(new GBDeviceEventUpdateDeviceState(State.INITIALIZED));
+                    }
+                    break;
+                case GET_DEVICE_RUN_INFO:
+                    LOG.debug("[INIT] Received device run info");
+                    decodeDeviceRunInfo(message.getPayload());
+                    break;
+                case REPORT_STATUS:
+                    events.addAll(Arrays.asList(decodeDeviceUpdate(message)));
+                    break;
+                case GET_CONFIG:
+                    decodeGetConfig(message.getPayload());
+                    break;
+                case NOTIFY_CONFIG:
+                    events.addAll(Arrays.asList(decodeNotifyConfig(message)));
+                    break;
+                default:
+                    LOG.debug("Unhandled message: {}", message);
+                    break;
+            }
+        }
+        return events.toArray(new GBDeviceEvent[0]);
+    }
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/redmibuds5pro/protocol/AuthData.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/redmibuds5pro/protocol/AuthData.java
new file mode 100644
index 0000000..7925698
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/redmibuds5pro/protocol/AuthData.java
@@ -0,0 +1,25 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.redmibuds5pro.protocol;
+
+class AuthData {
+
+    final static byte[] SEQ = { 0x11, 0x22, 0x33, 0x33, 0x22, 0x11, 0x11, 0x22, 0x33, 0x33, 0x22, 0x11, 0x11, 0x22, 0x33, 0x33 };
+
+    final static int[][] COEFFICIENTS = {
+            {2,1,1,1,4,2,1,1,2,2,4,2,4,4,16,8},
+            {2,1,1,1,4,2,1,1,1,1,2,1,2,2,8,4},
+            {1,1,4,2,2,2,4,2,16,8,4,4,2,1,1,1},
+            {1,1,4,2,1,1,2,1,8,4,2,2,2,1,1,1},
+            {16,8,2,2,4,2,4,4,1,1,4,2,1,1,2,1},
+            {8,4,1,1,2,1,2,2,1,1,4,2,1,1,2,1},
+            {2,2,4,2,4,4,16,8,2,1,1,1,4,2,1,1},
+            {1,1,2,1,2,2,8,4,2,1,1,1,4,2,1,1},
+            {4,2,4,4,16,8,2,2,1,1,2,1,1,1,4,2},
+            {2,1,2,2,8,4,1,1,1,1,2,1,1,1,4,2},
+            {4,4,16,8,1,1,2,1,4,2,1,1,4,2,2,2},
+            {2,2,8,4,1,1,2,1,4,2,1,1,2,1,1,1},
+            {1,1,2,1,1,1,4,2,4,4,16,8,2,2,4,2},
+            {1,1,2,1,1,1,4,2,2,2,8,4,1,1,2,1},
+            {4,2,1,1,2,1,1,1,4,2,2,2,16,8,4,4},
+            {4,2,1,1,2,1,1,1,2,1,1,1,8,4,2,2}
+    };
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/redmibuds5pro/protocol/Authentication.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/redmibuds5pro/protocol/Authentication.java
new file mode 100644
index 0000000..867d897
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/redmibuds5pro/protocol/Authentication.java
@@ -0,0 +1,181 @@
+/*  Copyright (C) 2024 Jonathan Gobbo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.redmibuds5pro.protocol;
+import java.math.BigInteger;
+import java.security.SecureRandom;
+import java.util.ArrayList;
+import java.util.List;
+
+/*
+    Authentication based on the custom Bluetooth version of the SAFER+ encryption algorithm with:
+    - 128 bit key size
+    - 8 rounds
+ */
+public class Authentication {
+
+    private final static int PATTERN = 0x9999;
+    private final static int BLOCK_SIZE = 16;
+
+    byte[][] biasMatrix;
+    byte[] expTab;
+    byte[] logTab;
+
+    public Authentication() {
+        generateBiasMatrix();
+        generateExpTab();
+        generateLogTab();
+    }
+
+    private void generateBiasMatrix() {
+        biasMatrix = new byte[16][];
+
+        for (int i = 0; i < 16; i++) {
+            byte[] biasVec = new byte[16];
+            for (int j = 0; j < 16; j++) {
+                int exponent = (17 * (i + 2) + (j + 1));
+                BigInteger base = BigInteger.valueOf(45);
+                BigInteger modExp = base.pow(base.pow(exponent).mod(BigInteger.valueOf(257)).intValue())
+                        .mod(BigInteger.valueOf(257));
+                byte val = (byte) (modExp.intValue() == 256 ? 0 : modExp.intValue());
+                biasVec[j] = val;
+            }
+            biasMatrix[i] = biasVec;
+        }
+    }
+
+    private void generateExpTab() {
+        expTab = new byte[256];
+
+        for (int i = 0; i < 256; i++) {
+            BigInteger base = BigInteger.valueOf(45);
+            BigInteger exp = base.pow(i).mod(BigInteger.valueOf(257));
+            expTab[i] = (byte) (i == 128 ? 0 : exp.intValue());
+        }
+    }
+
+    private void generateLogTab() {
+        logTab = new byte[256];
+
+        for (int i = 0; i < 256; i++) {
+            if (i == 0) {
+                logTab[i] = (byte) 128;
+            } else {
+                BigInteger base = BigInteger.valueOf(45);
+                BigInteger modExp = base.pow(i).mod(BigInteger.valueOf(257));
+                if (modExp.intValue() != 256) {
+                    logTab[modExp.intValue()] = (byte) i;
+                }
+            }
+        }
+    }
+
+    private byte[][] keySchedule(byte[] keyInit) {
+
+        keyInit[15] ^= 6;
+
+        byte[][] keys = new byte[17][];
+        keys[0] = keyInit;
+
+        List<Byte> register = new ArrayList<>();
+        for (byte b : keyInit) {
+            register.add(b);
+        }
+        byte xor = register.stream().reduce((byte) 0x0, (cSum, e) -> (byte) (cSum ^ e));
+        register.add(xor);
+
+        for (int keyIdx = 1; keyIdx < keys.length; keyIdx++) {
+            byte[] keyI = new byte[16];
+            for (int i = 0; i < 17; i++) {
+                byte rot = (byte) (((register.get(i) & 0xff) >>> 5) | ((register.get(i) & 0xff) << (8 - 5)));
+                register.set(i, rot);
+            }
+            for (int i = 0; i < 16; i++) {
+                keyI[i] = (byte) (register.get((keyIdx + i) % 17) + biasMatrix[keyIdx - 1][i]);
+            }
+            keys[keyIdx] = keyI;
+        }
+
+        return keys;
+    }
+
+    private byte[] encrypt(byte[] plaintext, byte[][] keys) {
+
+        byte[] ciphertext = plaintext.clone();
+        for (int round = 0; round < 8; round++) {
+            if (round == 2) {
+                for (int i = 0; i < BLOCK_SIZE; i++) {
+                    if ((1 << i & PATTERN) != 0) {
+                        ciphertext[i] ^= plaintext[i];
+                    } else {
+                        ciphertext[i] += plaintext[i];
+                    }
+                }
+            }
+            for (int i = 0; i < BLOCK_SIZE; i++) {
+                if ((1 << i & PATTERN) != 0) {
+                    ciphertext[i] ^= keys[round * 2][i];
+                } else {
+                    ciphertext[i] += keys[round * 2][i];
+                }
+            }
+            for (int i = 0; i < BLOCK_SIZE; i++) {
+                if ((1 << i & PATTERN) != 0) {
+                    ciphertext[i] = expTab[ciphertext[i] & 0xff];
+                } else {
+                    ciphertext[i] = logTab[ciphertext[i] & 0xff];
+                }
+            }
+            for (int i = 0; i < BLOCK_SIZE; i++) {
+                if ((1 << i & PATTERN) != 0) {
+                    ciphertext[i] = (byte) (keys[round * 2 + 1][i] + ciphertext[i]);
+                } else {
+                    ciphertext[i] = (byte) (keys[round * 2 + 1][i] ^ ciphertext[i]);
+                }
+            }
+            byte[] ciphertextCopy = ciphertext.clone();
+            for (int i = 0; i < BLOCK_SIZE; i++) {
+                byte cSum = 0;
+                for (int j = 0; j < BLOCK_SIZE; j++) {
+                    cSum += (byte) (AuthData.COEFFICIENTS[i][j] * ciphertextCopy[j]);
+                }
+                ciphertext[i] = cSum;
+            }
+        }
+
+        for (int i = 0; i < BLOCK_SIZE; i++) {
+            if ((1 << i & PATTERN) != 0) {
+                ciphertext[i] = (byte) (keys[16][i] ^ ciphertext[i]);
+            } else {
+                ciphertext[i] = (byte) (keys[16][i] + ciphertext[i]);
+            }
+        }
+        return ciphertext;
+    }
+
+    public static byte[] getRandomChallenge() {
+        byte[] res = new byte[BLOCK_SIZE];
+        SecureRandom rnd = new SecureRandom();
+        rnd.nextBytes(res);
+        return res;
+    }
+
+    public byte[] computeChallengeResponse(byte[] challenge) {
+        byte[][] keys = keySchedule(challenge);
+        return encrypt(AuthData.SEQ, keys);
+    }
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/redmibuds5pro/protocol/Message.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/redmibuds5pro/protocol/Message.java
new file mode 100644
index 0000000..be88ea9
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/redmibuds5pro/protocol/Message.java
@@ -0,0 +1,124 @@
+/*  Copyright (C) 2024 Jonathan Gobbo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.redmibuds5pro.protocol;
+
+import static nodomain.freeyourgadget.gadgetbridge.util.GB.hexdump;
+
+import androidx.annotation.NonNull;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+public class Message {
+
+    private static final byte[] MESSAGE_HEADER = {(byte) 0xfe, (byte) 0xdc, (byte) 0xba};
+    private static final byte MESSAGE_TRAILER = (byte) 0xef;
+
+    private static final int MESSAGE_OFFSET = MESSAGE_HEADER.length;
+
+    private final MessageType type;
+    private final Opcode opcode;
+    private final byte sequenceNumber;
+    private final byte[] payload;
+
+    public Message(final MessageType type, final Opcode opcode, final byte sequenceNumber, final byte[] payload) {
+        this.type = type;
+        this.opcode = opcode;
+        this.sequenceNumber = sequenceNumber;
+        this.payload = payload;
+    }
+
+    public static Message fromBytes(byte[] message) {
+        MessageType type = MessageType.fromCode(message[MESSAGE_OFFSET]);
+        Opcode opcode = Opcode.fromCode(message[MESSAGE_OFFSET + 1]);
+
+        int payloadOffset = MESSAGE_OFFSET + ((!type.isRequest()) ? 6 : 5);
+        byte sequenceNumber = message[payloadOffset - 1];
+
+        int actualPayloadLength = message.length - payloadOffset - 1;
+        byte[] payload = new byte[actualPayloadLength];
+        System.arraycopy(message, payloadOffset, payload, 0, actualPayloadLength);
+        return new Message(type, opcode, sequenceNumber, payload);
+    }
+
+    public static List<Message> splitPiggybackedMessages(byte[] input) {
+        List<Message> messages = new ArrayList<>();
+
+        List<Integer> startHeader = new ArrayList<>();
+        for (int i = 0; i < input.length - 3; i++) {
+            if (input[i] == MESSAGE_HEADER[0] && input[i + 1] == MESSAGE_HEADER[1] && input[i + 2] == MESSAGE_HEADER[2]) {
+                startHeader.add(i);
+            }
+        }
+
+        for (int i = 0; i < startHeader.size(); i++) {
+            if (i == startHeader.size() - 1) {
+                messages.add(fromBytes(Arrays.copyOfRange(input, startHeader.get(i), input.length)));
+            } else {
+                messages.add(fromBytes(Arrays.copyOfRange(input, startHeader.get(i), startHeader.get(i + 1))));
+            }
+        }
+        return messages;
+    }
+
+    public byte[] encode() {
+
+        int size = (!type.isRequest()) ? 2 : 1;
+        final ByteBuffer buf = ByteBuffer.allocate(payload.length + 8 + size);
+        int payloadLength = payload.length + size;
+
+        buf.order(ByteOrder.BIG_ENDIAN);
+
+        buf.put(MESSAGE_HEADER);
+        buf.put(type.getCode());
+        buf.put(opcode.getOpcode());
+        buf.putShort((short) payloadLength);
+        if (!type.isRequest()) {
+            buf.put((byte) 0x00);
+        }
+        buf.put(sequenceNumber);
+        buf.put(payload);
+        buf.put(MESSAGE_TRAILER);
+
+        return buf.array();
+    }
+
+    @NonNull
+    @Override
+    public String toString() {
+        return "Message{" + "type=" + type + ", opcode=" + opcode + ", sequenceNumber=" + sequenceNumber + ", payload=" + hexdump(payload) + '}';
+    }
+
+    public MessageType getType() {
+        return type;
+    }
+
+    public Opcode getOpcode() {
+        return opcode;
+    }
+
+    public byte getSequenceNumber() {
+        return sequenceNumber;
+    }
+
+    public byte[] getPayload() {
+        return payload;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/redmibuds5pro/protocol/MessageType.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/redmibuds5pro/protocol/MessageType.java
new file mode 100644
index 0000000..112eb98
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/redmibuds5pro/protocol/MessageType.java
@@ -0,0 +1,35 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.redmibuds5pro.protocol;
+
+public enum MessageType {
+    PHONE_REQUEST(0xC4),
+    RESPONSE(0x04),
+    EARBUDS_REQUEST(0xC0),
+    EARBUDS_NOTIFY(0xC7),
+    UNKNOWN(0xFF);
+
+    private final byte code;
+    private final boolean isRequest;
+
+    MessageType(final int code) {
+        this.code = (byte) code;
+        this.isRequest = (this.code & 0x40) != 0;
+    }
+
+    public byte getCode() {
+        return this.code;
+    }
+
+    public boolean isRequest() {
+        return isRequest;
+    }
+
+    public static MessageType fromCode(final byte code) {
+        for (final MessageType messageType : values()) {
+            if (messageType.code == code) {
+                return messageType;
+            }
+        }
+
+        return MessageType.UNKNOWN;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/redmibuds5pro/protocol/Opcode.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/redmibuds5pro/protocol/Opcode.java
new file mode 100644
index 0000000..f6b3ed1
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/redmibuds5pro/protocol/Opcode.java
@@ -0,0 +1,33 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.redmibuds5pro.protocol;
+
+public enum Opcode {
+    GET_DEVICE_INFO(0x02),
+    ANC(0x08),
+    GET_DEVICE_RUN_INFO(0x09),
+    REPORT_STATUS(0x0E),
+    AUTH_CHALLENGE(0x50),
+    AUTH_CONFIRM(0x51),
+    SET_CONFIG(0xF2),
+    GET_CONFIG(0xF3),
+    NOTIFY_CONFIG(0xF4),
+    UNKNOWN(0xFF);
+
+    private final byte opcode;
+
+    Opcode(final int opcode) {
+        this.opcode = (byte) opcode;
+    }
+
+    public byte getOpcode() {
+        return opcode;
+    }
+
+    public static Opcode fromCode(final byte code) {
+        for (final Opcode opcode : values()) {
+            if (opcode.opcode == code) {
+                return opcode;
+            }
+        }
+        return Opcode.UNKNOWN;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/roidmi/Roidmi1Protocol.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/roidmi/Roidmi1Protocol.java
new file mode 100644
index 0000000..66a4a77
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/roidmi/Roidmi1Protocol.java
@@ -0,0 +1,148 @@
+/*  Copyright (C) 2018-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.roidmi;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventFmFrequency;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventLEDColor;
+import nodomain.freeyourgadget.gadgetbridge.devices.roidmi.RoidmiConst;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class Roidmi1Protocol extends RoidmiProtocol {
+    private static final Logger LOG = LoggerFactory.getLogger(Roidmi1Protocol.class);
+
+    public Roidmi1Protocol(GBDevice device) {
+        super(device);
+    }
+
+    private static final byte[] PACKET_HEADER = new byte[]{(byte) 0xaa, 0x55};
+    private static final byte[] PACKET_TRAILER = new byte[]{(byte) 0xc3, 0x3c};
+    private static final byte COMMAND_SET_FREQUENCY = 0x10;
+    private static final byte COMMAND_GET_FREQUENCY = (byte) 0x80;
+    private static final byte COMMAND_SET_COLOR = 0x11;
+    private static final byte COMMAND_GET_COLOR = (byte) 0x81;
+
+    private static final int PACKET_MIN_LENGTH = 6;
+
+    private static final int LED_COLOR_RED = 1;
+    private static final int LED_COLOR_GREEN = 2;
+    private static final int LED_COLOR_BLUE = 3;
+    private static final int LED_COLOR_YELLOW = 4; // not official
+    private static final int LED_COLOR_SKY_BLUE = 5;
+    private static final int LED_COLOR_PINK = 6; // not official
+    private static final int LED_COLOR_WHITE = 7; // not official
+    private static final int LED_COLOR_OFF = 8;
+
+    // Other commands:
+    // App periodically sends aa5502018588c33c and receives aa5506018515111804cec33c
+    private static final byte[] COMMAND_PERIODIC = new byte[]{(byte) 0xaa, 0x55, 0x02, 0x01, (byte) 0x85, (byte) 0x88, (byte) 0xc3, 0x3c};
+
+    @Override
+    public GBDeviceEvent[] decodeResponse(final byte[] responseData) {
+        if (responseData.length <= PACKET_MIN_LENGTH) {
+            LOG.info("Response too small");
+            return null;
+        }
+
+        for (int i = 0; i < packetHeader().length; i++) {
+            if (responseData[i] != packetHeader()[i]) {
+                LOG.info("Invalid response header");
+                return null;
+            }
+        }
+
+        for (int i = 0; i < packetTrailer().length; i++) {
+            if (responseData[responseData.length - packetTrailer().length + i] != packetTrailer()[i]) {
+                LOG.info("Invalid response trailer");
+                return null;
+            }
+        }
+
+        if (calcChecksum(responseData) != responseData[responseData.length - packetTrailer().length - 1]) {
+            LOG.info("Invalid response checksum");
+            return null;
+        }
+
+        switch (responseData[3]) {
+            case COMMAND_GET_COLOR:
+                final int color = responseData[5];
+                LOG.debug("Got color: " + color);
+                final GBDeviceEventLEDColor evColor = new GBDeviceEventLEDColor(RoidmiConst.COLOR_PRESETS[color - 1]);
+                return new GBDeviceEvent[]{evColor};
+            case COMMAND_GET_FREQUENCY:
+                final String frequencyHex = GB.hexdump(responseData, 4, 2);
+                final float frequency = Float.parseFloat(frequencyHex) / 10.0f;
+                LOG.debug("Got frequency: " + frequency);
+                final GBDeviceEventFmFrequency evFrequency = new GBDeviceEventFmFrequency(frequency);
+                return new GBDeviceEvent[]{evFrequency};
+            default:
+                LOG.error("Unrecognized response type 0x" + GB.hexdump(responseData, packetHeader().length, 1));
+                return null;
+        }
+    }
+
+    @Override
+    public byte[] encodeLedColor(final int color) {
+        final int[] presets = RoidmiConst.COLOR_PRESETS;
+        int color_id = -1;
+        for (int i = 0; i < presets.length; i++) {
+            if (presets[i] == color) {
+                color_id = (i + 1) & 255;
+                break;
+            }
+        }
+
+        if (color_id < 0 || color_id > 8)
+            throw new IllegalArgumentException("color must belong to RoidmiConst.COLOR_PRESETS");
+
+        return encodeCommand(COMMAND_SET_COLOR, (byte) 0, (byte) color_id);
+    }
+
+    @Override
+    public byte[] encodeFmFrequency(final float frequency) {
+        if (frequency < 87.5 || frequency > 108.0)
+            throw new IllegalArgumentException("Frequency must be >= 87.5 and <= 180.0");
+
+        final byte[] freq = frequencyToBytes(frequency);
+
+        return encodeCommand(COMMAND_SET_FREQUENCY, freq[0], freq[1]);
+    }
+
+    public byte[] encodeGetLedColor() {
+        return encodeCommand(COMMAND_GET_COLOR, (byte) 0, (byte) 0);
+    }
+
+    public byte[] encodeGetFmFrequency() {
+        return encodeCommand(COMMAND_GET_FREQUENCY, (byte) 0, (byte) 0);
+    }
+
+    public byte[] encodeGetVoltage() {
+        return null;
+    }
+
+    public byte[] packetHeader() {
+        return PACKET_HEADER;
+    }
+
+    public byte[] packetTrailer() {
+        return PACKET_TRAILER;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/roidmi/Roidmi3Protocol.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/roidmi/Roidmi3Protocol.java
new file mode 100644
index 0000000..33ea8f4
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/roidmi/Roidmi3Protocol.java
@@ -0,0 +1,156 @@
+/*  Copyright (C) 2018-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.roidmi;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventFmFrequency;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventLEDColor;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.BatteryState;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class Roidmi3Protocol extends RoidmiProtocol {
+    private static final Logger LOG = LoggerFactory.getLogger(Roidmi3Protocol.class);
+
+    public Roidmi3Protocol(GBDevice device) {
+        super(device);
+    }
+
+    // Commands below need to be wrapped in a packet
+
+    private static final byte[] COMMAND_GET_COLOR = new byte[]{0x02, (byte) 0x81};
+    private static final byte[] COMMAND_GET_FREQUENCY = new byte[]{0x05, (byte) 0x81};
+    private static final byte[] COMMAND_GET_VOLTAGE = new byte[]{0x06, (byte) 0x81};
+
+    private static final byte[] COMMAND_SET_COLOR = new byte[]{0x02, 0x01, 0x00, 0x00, 0x00};
+    private static final byte[] COMMAND_SET_FREQUENCY = new byte[]{0x05, 0x01, 0x09, 0x64};
+    private static final byte[] COMMAND_DENOISE_ON = new byte[]{0x05, 0x06, 0x12};
+    private static final byte[] COMMAND_DENOISE_OFF = new byte[]{0x05, 0x06, 0x00};
+
+    private static final byte RESPONSE_COLOR = 0x02;
+    private static final byte RESPONSE_FREQUENCY = 0x05;
+    private static final byte RESPONSE_VOLTAGE = 0x06;
+    // Next response byte is always 0x81, followed by the value
+
+    private static final int PACKET_MIN_LENGTH = 4;
+
+    @Override
+    public GBDeviceEvent[] decodeResponse(byte[] res) {
+        if (res.length <= PACKET_MIN_LENGTH) {
+            LOG.info("Response too small");
+            return null;
+        }
+
+        if (calcChecksum(res) != res[res.length - 1]) {
+            LOG.info("Invalid response checksum");
+            return null;
+        }
+
+        if (res[0] + 2 != res.length) {
+            LOG.info("Packet length doesn't match");
+            return null;
+        }
+
+        if (res[2] != (byte) 0x81) {
+            LOG.warn("Potentially unsupported response: " + GB.hexdump(res, 0, res.length));
+        }
+
+        switch(res[1]) {
+            case RESPONSE_VOLTAGE:
+                final String voltageHex = GB.hexdump(res, 3, 2);
+                final float voltage = Float.parseFloat(voltageHex) / 100.0f;
+                LOG.debug("Got voltage: " + voltage);
+                GBDeviceEventBatteryInfo evBattery = new GBDeviceEventBatteryInfo();
+                evBattery.state = BatteryState.NO_BATTERY;
+                evBattery.level = GBDevice.BATTERY_UNKNOWN;
+                evBattery.voltage = voltage;
+                return new GBDeviceEvent[]{evBattery};
+            case RESPONSE_COLOR:
+                LOG.debug("Got color: #" + GB.hexdump(res, 3, 3));
+                final int color = 0xFF000000 | ((res[3] << 16) & 0xFF0000) | ((res[4] << 8) & 0xFF00) | (res[5] & 0xFF);
+                final GBDeviceEventLEDColor evColor = new GBDeviceEventLEDColor(color);
+                return new GBDeviceEvent[]{evColor};
+            case RESPONSE_FREQUENCY:
+                final String frequencyHex = GB.hexdump(res, 3, 2);
+                final float frequency = Float.parseFloat(frequencyHex) / 10.0f;
+                LOG.debug("Got frequency: " + frequency);
+                final GBDeviceEventFmFrequency evFrequency = new GBDeviceEventFmFrequency(frequency);
+                return new GBDeviceEvent[]{evFrequency};
+            default:
+                LOG.error("Unrecognized response: " + GB.hexdump(res, 0, res.length));
+        }
+
+        return null;
+    }
+
+    @Override
+    public byte[] encodeLedColor(final int color) {
+        final byte[] cmd = COMMAND_SET_COLOR.clone();
+
+        cmd[2] = (byte) (color >> 16);
+        cmd[3] = (byte) (color >> 8);
+        cmd[4] = (byte) color;
+
+        return encodeCommand(cmd);
+    }
+
+    @Override
+    public byte[] encodeFmFrequency(final float frequency) {
+        if (frequency < 87.5 || frequency > 108.0)
+            throw new IllegalArgumentException("Frequency must be >= 87.5 and <= 180.0");
+
+        final byte[] cmd = COMMAND_SET_FREQUENCY.clone();
+        final byte[] freq = frequencyToBytes(frequency);
+        cmd[2] = freq[0];
+        cmd[3] = freq[1];
+
+        return encodeCommand(cmd);
+    }
+
+    @Override
+    public byte[] encodeGetLedColor() {
+        return encodeCommand(COMMAND_GET_COLOR);
+    }
+
+    @Override
+    public byte[] encodeGetFmFrequency() {
+        return encodeCommand(COMMAND_GET_FREQUENCY);
+    }
+
+    @Override
+    public byte[] packetHeader() {
+        return new byte[0];
+    }
+
+    @Override
+    public byte[] packetTrailer() {
+        return new byte[0];
+    }
+
+    public byte[] encodeGetVoltage() {
+        return encodeCommand(COMMAND_GET_VOLTAGE);
+    }
+
+    public byte[] encodeDenoise(final boolean enabled) {
+        final byte[] cmd = enabled ? COMMAND_DENOISE_ON : COMMAND_DENOISE_OFF;
+        return encodeCommand(cmd);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/roidmi/RoidmiIoThread.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/roidmi/RoidmiIoThread.java
new file mode 100644
index 0000000..4d3671e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/roidmi/RoidmiIoThread.java
@@ -0,0 +1,70 @@
+/*  Copyright (C) 2018-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.roidmi;
+
+import android.bluetooth.BluetoothAdapter;
+import android.content.Context;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.btclassic.BtClassicIoThread;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class RoidmiIoThread extends BtClassicIoThread {
+    private static final Logger LOG = LoggerFactory.getLogger(RoidmiIoThread.class);
+
+    private final byte[] HEADER;
+    private final byte[] TRAILER;
+
+    public RoidmiIoThread(GBDevice gbDevice, Context context, RoidmiProtocol roidmiProtocol, RoidmiSupport roidmiSupport, BluetoothAdapter roidmiBtAdapter) {
+        super(gbDevice, context, roidmiProtocol, roidmiSupport, roidmiBtAdapter);
+
+        HEADER = roidmiProtocol.packetHeader();
+        TRAILER = roidmiProtocol.packetTrailer();
+    }
+
+    @Override
+    protected byte[] parseIncoming(InputStream inputStream) throws IOException {
+        final ByteArrayOutputStream msgStream = new ByteArrayOutputStream();
+
+        boolean finished = false;
+        final byte[] incoming = new byte[1];
+
+        while (!finished) {
+            inputStream.read(incoming);
+            msgStream.write(incoming);
+
+            final byte[] arr = msgStream.toByteArray();
+            if (arr.length > HEADER.length) {
+                int expectedLength = HEADER.length + TRAILER.length + arr[HEADER.length] + 2;
+                if (arr.length == expectedLength) {
+                    finished = true;
+                }
+            }
+        }
+
+        final byte[] msgArray = msgStream.toByteArray();
+        LOG.debug("Packet: " + GB.hexdump(msgArray, 0, msgArray.length));
+        return msgArray;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/roidmi/RoidmiProtocol.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/roidmi/RoidmiProtocol.java
new file mode 100644
index 0000000..27867c8
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/roidmi/RoidmiProtocol.java
@@ -0,0 +1,93 @@
+/*  Copyright (C) 2018-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.roidmi;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Locale;
+
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol;
+
+public abstract class RoidmiProtocol extends GBDeviceProtocol {
+    private static final Logger LOG = LoggerFactory.getLogger(RoidmiProtocol.class);
+
+    // Packet structure: HEADER N_PARAMS PARAM_1 ... PARAM_N CHECKSUM TRAILER
+
+    public RoidmiProtocol(GBDevice device) {
+        super(device);
+    }
+
+    @Override
+    public abstract GBDeviceEvent[] decodeResponse(byte[] responseData);
+
+    @Override
+    public abstract byte[] encodeLedColor(int color);
+
+    @Override
+    public abstract byte[] encodeFmFrequency(float frequency);
+
+    public abstract byte[] encodeGetLedColor();
+
+    public abstract byte[] encodeGetFmFrequency();
+
+    public abstract byte[] encodeGetVoltage();
+
+    public abstract byte[] packetHeader();
+
+    public abstract byte[] packetTrailer();
+
+    public byte[] encodeCommand(byte... params) {
+        final byte[] cmd = new byte[packetHeader().length + packetTrailer().length + params.length + 2];
+
+        for (int i = 0; i < packetHeader().length; i++)
+            cmd[i] = packetHeader()[i];
+        for (int i = 0; i < packetTrailer().length; i++)
+            cmd[cmd.length - packetTrailer().length + i] = packetTrailer()[i];
+
+        cmd[packetHeader().length] = (byte) params.length;
+        for (int i = 0; i < params.length; i++) {
+            cmd[packetHeader().length + 1 + i] = params[i];
+        }
+        cmd[cmd.length - packetTrailer().length - 1] = calcChecksum(cmd);
+
+        return cmd;
+    }
+
+    public byte calcChecksum(byte[] packet) {
+        int chk = 0;
+        for (int i = packetHeader().length; i < packet.length - packetTrailer().length - 1; i++) {
+            chk += packet[i] & 255;
+        }
+        return (byte) chk;
+    }
+
+    public byte[] frequencyToBytes(float frequency) {
+        final byte[] res = new byte[2];
+        final String format = String.format(Locale.ROOT, "%04d", (int) (10.0f * frequency));
+        try {
+            res[0] = (byte) (Integer.parseInt(format.substring(0, 2), 16) & 255);
+            res[1] = (byte) (Integer.parseInt(format.substring(2), 16) & 255);
+        } catch (final Exception e) {
+            LOG.error("Failed to format frequency {}", frequency, e);
+        }
+
+        return res;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/roidmi/RoidmiSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/roidmi/RoidmiSupport.java
new file mode 100644
index 0000000..f889fa6
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/roidmi/RoidmiSupport.java
@@ -0,0 +1,146 @@
+/*  Copyright (C) 2018-2024 Arjan Schrijver, Daniel Dakhno, Jos茅 Rebelo,
+    Sebastian Kranz
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.roidmi;
+
+import android.net.Uri;
+import android.os.Handler;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.roidmi.RoidmiConst;
+import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.service.serial.AbstractSerialDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceIoThread;
+import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol;
+
+public class RoidmiSupport extends AbstractSerialDeviceSupport {
+    private static final Logger LOG = LoggerFactory.getLogger(RoidmiSupport.class);
+
+    private final Handler handler = new Handler();
+    private int infoRequestTries = 0;
+    private final Runnable infosRunnable = new Runnable() {
+        public void run() {
+            infoRequestTries += 1;
+
+            try {
+                boolean infoMissing = false;
+
+                if (getDevice().getExtraInfo("led_color") == null) {
+                    infoMissing = true;
+                    onSendConfiguration(RoidmiConst.ACTION_GET_LED_COLOR);
+                }
+
+                if (getDevice().getExtraInfo("fm_frequency") == null) {
+                    infoMissing = true;
+
+                    onSendConfiguration(RoidmiConst.ACTION_GET_FM_FREQUENCY);
+                }
+
+                if (getDevice().getType() == DeviceType.ROIDMI3) {
+                    if (getDevice().getBatteryVoltage() == -1) {
+                        infoMissing = true;
+
+                        onSendConfiguration(RoidmiConst.ACTION_GET_VOLTAGE);
+                    }
+                }
+
+                if (infoMissing) {
+                    if (infoRequestTries < 6) {
+                        requestDeviceInfos(500 + infoRequestTries * 120);
+                    } else {
+                        LOG.error("Failed to get Roidmi infos after 6 tries");
+                    }
+                }
+            } catch (final Exception e) {
+                LOG.error("Failed to get Roidmi infos", e);
+            }
+        }
+    };
+
+    private void requestDeviceInfos(int delayMillis) {
+        handler.postDelayed(infosRunnable, delayMillis);
+    }
+
+    @Override
+    public boolean connect() {
+        getDeviceIOThread().start();
+
+        requestDeviceInfos(1500);
+
+        return true;
+    }
+
+    @Override
+    protected GBDeviceProtocol createDeviceProtocol() {
+        final DeviceType deviceType = getDevice().getType();
+
+        switch(deviceType) {
+            case ROIDMI:
+                return new Roidmi1Protocol(getDevice());
+            case ROIDMI3:
+                return new Roidmi3Protocol(getDevice());
+            default:
+                LOG.error("Unsupported device type {} with key = {}", deviceType, deviceType.name());
+        }
+
+        return null;
+    }
+
+    @Override
+    public void onSendConfiguration(final String config) {
+        LOG.debug("onSendConfiguration " + config);
+
+        final RoidmiIoThread roidmiIoThread = getDeviceIOThread();
+        final RoidmiProtocol roidmiProtocol = (RoidmiProtocol) getDeviceProtocol();
+
+        switch (config) {
+            case RoidmiConst.ACTION_GET_LED_COLOR:
+                roidmiIoThread.write(roidmiProtocol.encodeGetLedColor());
+                break;
+            case RoidmiConst.ACTION_GET_FM_FREQUENCY:
+                roidmiIoThread.write(roidmiProtocol.encodeGetFmFrequency());
+                break;
+            case RoidmiConst.ACTION_GET_VOLTAGE:
+                roidmiIoThread.write(roidmiProtocol.encodeGetVoltage());
+                break;
+            default:
+                LOG.error("Invalid Roidmi configuration " + config);
+                break;
+        }
+    }
+
+    @Override
+    protected GBDeviceIoThread createDeviceIOThread() {
+        return new RoidmiIoThread(getDevice(), getContext(), (RoidmiProtocol) getDeviceProtocol(), RoidmiSupport.this, getBluetoothAdapter());
+    }
+
+    @Override
+    public synchronized RoidmiIoThread getDeviceIOThread() {
+        return (RoidmiIoThread) super.getDeviceIOThread();
+    }
+
+    @Override
+    public boolean useAutoConnect() {
+        return false;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/soflow/SoFlowSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/soflow/SoFlowSupport.java
new file mode 100644
index 0000000..fc166f6
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/soflow/SoFlowSupport.java
@@ -0,0 +1,240 @@
+/*  Copyright (C) 2022-2024 Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.soflow;
+
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.content.SharedPreferences;
+import android.widget.Toast;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.GattService;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.IntentListener;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo.DeviceInfoProfile;
+import nodomain.freeyourgadget.gadgetbridge.util.CryptoUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class SoFlowSupport extends AbstractBTLEDeviceSupport {
+
+    public static final UUID UUID_CHARACTERISICS_NOTIFICATION = UUID.fromString("60000002-0000-1000-8000-00805f9b34fb");
+    public static final UUID UUID_CHARACTERISICS_WRITE = UUID.fromString("60000003-0000-1000-8000-00805f9b34fb");
+    public static final byte[] COMMAND_REQUEST_SESSION = new byte[]{0x06, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+    public static final byte[] COMMAND_REQUEST_LOCK = new byte[]{0x05, 0x0e, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+    public static final byte[] COMMAND_LOCK = new byte[]{0x05, 0x0c, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+    public static final byte[] COMMAND_UNLOCK = new byte[]{0x05, 0x01, 0x06, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+    public static final byte[] COMMAND_SET_LIGHT = new byte[]{0x05, 0x47, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+    private static final Logger LOG = LoggerFactory.getLogger(SoFlowSupport.class);
+    private final DeviceInfoProfile<SoFlowSupport> deviceInfoProfile;
+    private final GBDeviceEventVersionInfo versionCmd = new GBDeviceEventVersionInfo();
+
+    private byte[] aesKey;
+    private final byte[] session = new byte[]{0, 0, 0, 0};
+
+    public SoFlowSupport() {
+        super(LOG);
+        addSupportedService(GattService.UUID_SERVICE_GENERIC_ACCESS);
+        addSupportedService(GattService.UUID_SERVICE_GENERIC_ATTRIBUTE);
+        addSupportedService(GattService.UUID_SERVICE_DEVICE_INFORMATION);
+        addSupportedService(GattService.UUID_SERVICE_BATTERY_SERVICE);
+        addSupportedService(UUID.fromString("60000001-0000-1000-8000-00805f9b34fb"));
+
+        deviceInfoProfile = new DeviceInfoProfile<>(this);
+        IntentListener mListener = intent -> {
+            String s = intent.getAction();
+            if (DeviceInfoProfile.ACTION_DEVICE_INFO.equals(s)) {
+                handleDeviceInfo(intent.getParcelableExtra(DeviceInfoProfile.EXTRA_DEVICE_INFO));
+            }
+        };
+        deviceInfoProfile.addListener(mListener);
+        addSupportedProfile(deviceInfoProfile);
+    }
+
+    @Override
+    protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
+        aesKey = getSecretKey();
+        builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext()));
+        requestDeviceInfo(builder);
+        builder.notify(getCharacteristic(UUID_CHARACTERISICS_NOTIFICATION), true);
+        writeEncrypted(builder, COMMAND_REQUEST_SESSION);
+        setInitialized(builder);
+        return builder;
+    }
+
+    private void requestDeviceInfo(TransactionBuilder builder) {
+        LOG.debug("Requesting Device Info!");
+        deviceInfoProfile.requestDeviceInfo(builder);
+    }
+
+    private void setInitialized(TransactionBuilder builder) {
+        builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZED, getContext()));
+    }
+
+    private void writeEncrypted(TransactionBuilder builder, byte[] data) {
+        try {
+            LOG.debug("will encrypt " + GB.hexdump(data));
+            builder.write(getCharacteristic(UUID_CHARACTERISICS_WRITE), CryptoUtils.encryptAES(data, aesKey));
+        } catch (Exception e) {
+            LOG.error("error while encrypting data");
+        }
+    }
+
+    @Override
+    public boolean useAutoConnect() {
+        return false;
+    }
+
+    private void handleDeviceInfo(nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo.DeviceInfo info) {
+        LOG.warn("Device info: " + info);
+        versionCmd.hwVersion = info.getHardwareRevision();
+        versionCmd.fwVersion = info.getFirmwareRevision();
+        handleGBDeviceEvent(versionCmd);
+    }
+
+    protected byte[] getSecretKey() {
+        byte[] authKeyBytes;
+
+        SharedPreferences sharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress());
+
+        String authKey = sharedPrefs.getString("authkey", null);
+        if (authKey != null && !authKey.isEmpty()) {
+            authKey = authKey.trim();
+            if (authKey.length() == 34 && authKey.startsWith("0x")) {
+                authKeyBytes = GB.hexStringToByteArray(authKey.substring(2));
+            } else if (authKey.length() == 32) {
+                authKeyBytes = GB.hexStringToByteArray(authKey);
+            } else {
+                return null;
+            }
+            return authKeyBytes;
+        }
+        return null;
+    }
+
+    @Override
+    public boolean onCharacteristicChanged(BluetoothGatt gatt,
+                                           BluetoothGattCharacteristic characteristic) {
+        if (super.onCharacteristicChanged(gatt, characteristic)) {
+            return true;
+        }
+
+        UUID characteristicUUID = characteristic.getUuid();
+        if (UUID_CHARACTERISICS_NOTIFICATION.equals(characteristicUUID)) {
+            try {
+                byte[] data = CryptoUtils.decryptAES(characteristic.getValue(), aesKey);
+                if (data[0] == 0x06 && data[1] == 0x01 && data[2] == 0x07) {
+                    session[0] = data[3];
+                    session[1] = data[4];
+                    session[2] = data[5];
+                    session[3] = data[6];
+                    LOG.info("Got session");
+                    COMMAND_LOCK[4] = session[0];
+                    COMMAND_LOCK[5] = session[1];
+                    COMMAND_LOCK[6] = session[2];
+                    COMMAND_LOCK[7] = session[3];
+                    COMMAND_REQUEST_LOCK[4] = session[0];
+                    COMMAND_REQUEST_LOCK[5] = session[1];
+                    COMMAND_REQUEST_LOCK[6] = session[2];
+                    COMMAND_REQUEST_LOCK[7] = session[3];
+                    COMMAND_SET_LIGHT[4] = session[0];
+                    COMMAND_SET_LIGHT[5] = session[1];
+                    COMMAND_SET_LIGHT[6] = session[2];
+                    COMMAND_SET_LIGHT[7] = session[3];
+                    COMMAND_UNLOCK[9] = session[0];
+                    COMMAND_UNLOCK[10] = session[1];
+                    COMMAND_UNLOCK[11] = session[2];
+                    COMMAND_UNLOCK[12] = session[3];
+                }
+                else {
+                    GB.toast(GB.hexdump(data),5,1);
+                }
+            } catch (Exception e) {
+                LOG.error("error while decrypting");
+            }
+
+            return true;
+        }
+        LOG.info("Unhandled characteristic changed: " + characteristicUUID);
+        return false;
+    }
+
+    @Override
+    public void onSendConfiguration(String config) {
+        TransactionBuilder builder;
+        try {
+            builder = performInitialized("Sending configuration for option: " + config);
+            if ("lock".equals(config)) {
+                SharedPreferences sharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress());
+                boolean lock = sharedPrefs.getBoolean("lock", false);
+                if (lock) {
+                    writeEncrypted(builder, COMMAND_LOCK);
+                } else {
+                    writeEncrypted(builder, COMMAND_UNLOCK);
+                }
+            }
+            builder.queue(getQueue());
+        } catch (IOException e) {
+            GB.toast("Error setting configuration", Toast.LENGTH_LONG, GB.ERROR, e);
+        }
+    }
+
+    @Override
+    public boolean onCharacteristicRead(BluetoothGatt gatt,
+                                        BluetoothGattCharacteristic characteristic, int status) {
+        if (super.onCharacteristicRead(gatt, characteristic, status)) {
+            return true;
+        }
+        UUID characteristicUUID = characteristic.getUuid();
+
+        LOG.info("Unhandled characteristic read: " + characteristicUUID);
+        return false;
+    }
+
+    @Override
+    public void onTestNewFunction() {
+        TransactionBuilder builder;
+        try {
+            builder = performInitialized("request unknown");
+            writeEncrypted(builder,COMMAND_REQUEST_LOCK);
+            builder.queue(getQueue());
+        } catch (IOException e) {
+            GB.toast("Error setting configuration", Toast.LENGTH_LONG, GB.ERROR, e);
+        }
+    }
+
+    @Override
+    public boolean getImplicitCallbackModify() {
+        return true;
+    }
+
+    @Override
+    public boolean getSendWriteRequestResponse() {
+        return false;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/headphones/SonyHeadphonesIoThread.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/headphones/SonyHeadphonesIoThread.java
new file mode 100644
index 0000000..cc12743
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/headphones/SonyHeadphonesIoThread.java
@@ -0,0 +1,157 @@
+/*  Copyright (C) 2021-2024 Daniel Dakhno, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones;
+
+import android.bluetooth.BluetoothAdapter;
+import android.content.Context;
+import android.os.Handler;
+import android.os.ParcelUuid;
+
+import androidx.annotation.NonNull;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.SonyHeadphonesCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.btclassic.BtClassicIoThread;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.Message;
+import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class SonyHeadphonesIoThread extends BtClassicIoThread {
+    private static final Logger LOG = LoggerFactory.getLogger(SonyHeadphonesIoThread.class);
+
+    private final SonyHeadphonesProtocol mProtocol;
+
+    private final UUID btrfcommUuidV1 = UUID.fromString("96CC203E-5068-46ad-B32D-E316F5E069BA");
+    private final UUID btrfcommUuidV2 = UUID.fromString("956C7B26-D49A-4BA8-B03F-B17D393CB6E2");
+
+    // Track whether we got the first init reply
+    private final Handler handler = new Handler();
+    private int initRetries = 0;
+
+    /**
+     * Sometimes the headphones will ignore the first init request, so we retry a few times
+     * TODO: Implement this in a more elegant way. Ideally, we should retry every command for which we didn't get an ACK.
+     */
+    private final Runnable initSendRunnable = new Runnable() {
+        public void run() {
+            // If we still haven't got any reply, re-send the init
+            if (!mProtocol.hasProtocolImplementation()) {
+                if (initRetries++ < 2) {
+                    LOG.warn("Init retry {}", initRetries);
+
+                    mProtocol.decreasePendingAcks();
+                    write(mProtocol.encodeInit());
+                    scheduleInitRetry();
+                } else {
+                    LOG.error("Failed to start headphones init after {} tries", initRetries);
+                    quit();
+                }
+            }
+        }
+    };
+
+    public SonyHeadphonesIoThread(final GBDevice gbDevice,
+                                  final Context context,
+                                  final SonyHeadphonesProtocol protocol,
+                                  final SonyHeadphonesSupport support,
+                                  final BluetoothAdapter btAdapter) {
+        super(gbDevice, context, protocol, support, btAdapter);
+        mProtocol = protocol;
+    }
+
+    @Override
+    protected void initialize() {
+        write(mProtocol.encodeInit());
+        scheduleInitRetry();
+        setUpdateState(GBDevice.State.INITIALIZING);
+    }
+
+    @Override
+    public synchronized void write(final byte[] bytes) {
+        // Log the human-readable message, for debugging
+        LOG.info("Writing {}", Message.fromBytes(bytes));
+
+        super.write(bytes);
+    }
+
+    @Override
+    protected byte[] parseIncoming(final InputStream inputStream) throws IOException {
+        final ByteArrayOutputStream msgStream = new ByteArrayOutputStream();
+        final byte[] incoming = new byte[1];
+
+        do {
+            inputStream.read(incoming);
+
+            if (incoming[0] == Message.MESSAGE_HEADER) {
+                msgStream.reset();
+            }
+
+            msgStream.write(incoming);
+        } while (incoming[0] != Message.MESSAGE_TRAILER);
+
+        LOG.trace("Raw message: {}", GB.hexdump(msgStream.toByteArray()));
+
+        return msgStream.toByteArray();
+    }
+
+    @NonNull
+    @Override
+    protected UUID getUuidToConnect(@NonNull final ParcelUuid[] uuids) {
+        boolean hasV1 = false;
+        boolean hasV2 = false;
+        boolean preferV2 = getCoordinator().preferServiceV2();
+        for (final ParcelUuid uuid : uuids) {
+            if (uuid.getUuid().equals(btrfcommUuidV1)) {
+                LOG.info("Found Sony UUID V1");
+                hasV1 = true;
+            } else if (uuid.getUuid().equals(btrfcommUuidV2)) {
+                LOG.info("Found Sony UUID V2");
+                hasV2 = true;
+            }
+        }
+
+        if (hasV2) {
+            LOG.info("Using Sony UUID V2");
+            return btrfcommUuidV2;
+        } else if (hasV1) {
+            LOG.info("Using Sony UUID V1");
+            return btrfcommUuidV1;
+        }
+
+        LOG.warn("Failed to find a known Sony UUID, will fallback to {}", (preferV2 ? "V2" : "V1"));
+
+        return preferV2 ? btrfcommUuidV2 : btrfcommUuidV1;
+    }
+
+    private void scheduleInitRetry() {
+        LOG.info("Scheduling init retry");
+
+        handler.postDelayed(initSendRunnable, 1250);
+    }
+
+    private SonyHeadphonesCoordinator getCoordinator() {
+        return (SonyHeadphonesCoordinator) getDevice().getDeviceCoordinator();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/headphones/SonyHeadphonesProtocol.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/headphones/SonyHeadphonesProtocol.java
new file mode 100644
index 0000000..b117dd2
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/headphones/SonyHeadphonesProtocol.java
@@ -0,0 +1,361 @@
+/*  Copyright (C) 2021-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones;
+
+import android.content.SharedPreferences;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Queue;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventSendBytes;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdateDeviceState;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdatePreferences;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.AdaptiveVolumeControl;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.AmbientSoundControl;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.AmbientSoundControlButtonMode;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.AudioUpsampling;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.AutomaticPowerOff;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.ButtonModes;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.EqualizerCustomBands;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.EqualizerPreset;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.PauseWhenTakenOff;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.QuickAccess;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.SpeakToChatConfig;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.SpeakToChatEnabled;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.VoiceNotifications;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.SoundPosition;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.SurroundMode;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.TouchSensor;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.WideAreaTap;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.Request;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.Message;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.MessageType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.impl.AbstractSonyProtocolImpl;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.impl.v1.SonyProtocolImplV1;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.impl.v2.SonyProtocolImplV2;
+import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol;
+
+public class SonyHeadphonesProtocol extends GBDeviceProtocol {
+    private static final Logger LOG = LoggerFactory.getLogger(SonyHeadphonesProtocol.class);
+
+    private byte sequenceNumber = 0;
+
+    // Request queue, sent every ACK, as we can't send them all at once
+    // Initially, it should contain all the init requests
+    private final Queue<Request> requestQueue = new LinkedList<>();
+    private int pendingAcks = 0;
+
+    private AbstractSonyProtocolImpl protocolImpl = null;
+
+    public SonyHeadphonesProtocol(GBDevice device) {
+        super(device);
+    }
+
+    @Override
+    public GBDeviceEvent[] decodeResponse(byte[] res) {
+        final Message message = Message.fromBytes(res);
+        if (message == null) {
+            return null;
+        }
+
+        LOG.info("Received {}", message);
+
+        final MessageType messageType = message.getType();
+
+        if (messageType == MessageType.ACK) {
+            if (sequenceNumber == message.getSequenceNumber()) {
+                LOG.warn("Unexpected ACK sequence number {}", message.getSequenceNumber());
+                return null;
+            }
+
+            sequenceNumber = message.getSequenceNumber();
+
+            return new GBDeviceEvent[]{handleAck()};
+        }
+
+        if (message.getPayload().length == 0) {
+            LOG.warn("Empty message: {}", message);
+            return null;
+        }
+
+        final SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress());
+
+        final List<GBDeviceEvent> events = new ArrayList<>();
+
+        if (protocolImpl == null) {
+            // Check if we got an init response, which should indicate the protocol version
+            if (MessageType.COMMAND_1.equals(messageType) && message.getPayload()[0] == 0x01) {
+                // Init reply, set the protocol version
+
+                final GBDeviceEventUpdatePreferences eventUpdatePreferences = new GBDeviceEventUpdatePreferences(
+                        DeviceSettingsPreferenceConst.PREF_SONY_ACTUAL_PROTOCOL_VERSION, null
+                );
+                events.add(eventUpdatePreferences);
+
+                final String protocolVersionPref = prefs.getString("pref_protocol_version", "auto");
+                final String protocolVersion;
+                if (protocolVersionPref.equals("auto")) {
+                    if (message.getPayload().length == 4) {
+                        // WH-1000XM3:      01:00:40:10
+                        // WF-SP800N 1.0.1: 01:00:70:00
+                        // Wi-SP600N:       01:00:40:10
+                        protocolVersion = "v1";
+                    } else if (message.getPayload().length == 8) {
+                        // WF-1000XM4 1.1.5: 01:00:01:00:00:00:00:00
+                        // LinkBuds S 2.0.2: 01:00:03:00:00:07:00:00
+                        // WH-1000XM5 1.1.3: 01:00:03:00:00:00:00:00
+                        // WF-1000XM5 2.0.1: 01:00:03:00:10:04:00:00
+                        // LinkBuds   1.0.3: 01:00:02:00:10:00:00:00
+                        protocolVersion = "v2";
+                    } else {
+                        LOG.error("Unexpected init response payload length: {}", message.getPayload().length);
+                        return events.toArray(new GBDeviceEvent[0]);
+                    }
+                } else {
+                    protocolVersion = protocolVersionPref;
+                }
+
+                LOG.info("Sony protocol version: {}/{}", protocolVersionPref, protocolVersion);
+
+                eventUpdatePreferences.withPreference(
+                        DeviceSettingsPreferenceConst.PREF_SONY_ACTUAL_PROTOCOL_VERSION,
+                        protocolVersion
+                );
+
+                switch (protocolVersion) {
+                    case "v1":
+                        protocolImpl = new SonyProtocolImplV1(getDevice());
+                        break;
+                    case "v2":
+                        protocolImpl = new SonyProtocolImplV2(getDevice());
+                        break;
+                    default:
+                        LOG.warn("Unknown protocol version {}", protocolVersion);
+                        return events.toArray(new GBDeviceEvent[0]);
+                }
+            }
+        }
+
+        if (protocolImpl == null) {
+            LOG.error("No protocol implementation, ignoring message");
+            return events.toArray(new GBDeviceEvent[0]);
+        }
+
+        try {
+            switch (messageType) {
+                case COMMAND_1:
+                case COMMAND_2:
+                    events.add(new GBDeviceEventSendBytes(encodeAck(message.getSequenceNumber())));
+                    events.addAll(protocolImpl.handlePayload(messageType, message.getPayload()));
+                    break;
+                default:
+                    LOG.warn("Unknown message type for {}", message);
+                    return events.toArray(new GBDeviceEvent[0]);
+            }
+        } catch (final Exception e) {
+            // Don't crash the app if we somehow fail to handle the payload
+            LOG.error("Error handling payload", e);
+        }
+
+        return events.toArray(new GBDeviceEvent[0]);
+    }
+
+    @Override
+    public byte[] encodeSendConfiguration(String config) {
+        if (protocolImpl == null) {
+            LOG.error("No protocol implementation, ignoring config {}", config);
+            return super.encodeSendConfiguration(config);
+        }
+
+        final SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress());
+
+        final Request configRequest;
+
+        switch (config) {
+            case DeviceSettingsPreferenceConst.PREF_SONY_AMBIENT_SOUND_CONTROL:
+            case DeviceSettingsPreferenceConst.PREF_SONY_FOCUS_VOICE:
+            case DeviceSettingsPreferenceConst.PREF_SONY_AMBIENT_SOUND_LEVEL:
+                configRequest = protocolImpl.setAmbientSoundControl(AmbientSoundControl.fromPreferences(prefs));
+                break;
+            case DeviceSettingsPreferenceConst.PREF_SONY_AMBIENT_SOUND_CONTROL_BUTTON_MODE:
+                configRequest = protocolImpl.setAmbientSoundControlButtonMode(AmbientSoundControlButtonMode.fromPreferences(prefs));
+                break;
+            case DeviceSettingsPreferenceConst.PREF_SONY_NOISE_OPTIMIZER_START:
+                configRequest = protocolImpl.startNoiseCancellingOptimizer(true);
+                break;
+            case DeviceSettingsPreferenceConst.PREF_SONY_NOISE_OPTIMIZER_CANCEL:
+                configRequest = protocolImpl.startNoiseCancellingOptimizer(false);
+                break;
+            case DeviceSettingsPreferenceConst.PREF_SONY_SOUND_POSITION:
+                configRequest = protocolImpl.setSoundPosition(SoundPosition.fromPreferences(prefs));
+                break;
+            case DeviceSettingsPreferenceConst.PREF_SONY_SURROUND_MODE:
+                configRequest = protocolImpl.setSurroundMode(SurroundMode.fromPreferences(prefs));
+                break;
+            case DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_MODE:
+                configRequest = protocolImpl.setEqualizerPreset(EqualizerPreset.fromPreferences(prefs));
+                break;
+            case DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_BAND_400:
+            case DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_BAND_1000:
+            case DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_BAND_2500:
+            case DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_BAND_6300:
+            case DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_BAND_16000:
+            case DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_BASS:
+                configRequest = protocolImpl.setEqualizerCustomBands(EqualizerCustomBands.fromPreferences(prefs));
+                break;
+            case DeviceSettingsPreferenceConst.PREF_SONY_AUDIO_UPSAMPLING:
+                configRequest = protocolImpl.setAudioUpsampling(AudioUpsampling.fromPreferences(prefs));
+                break;
+            case DeviceSettingsPreferenceConst.PREF_VOLUME:
+                configRequest = protocolImpl.setVolume(prefs.getInt(config, 15));
+                break;
+            case DeviceSettingsPreferenceConst.PREF_SONY_TOUCH_SENSOR:
+                configRequest = protocolImpl.setTouchSensor(TouchSensor.fromPreferences(prefs));
+                break;
+            case DeviceSettingsPreferenceConst.PREF_SONY_AUTOMATIC_POWER_OFF:
+                configRequest = protocolImpl.setAutomaticPowerOff(AutomaticPowerOff.fromPreferences(prefs));
+                break;
+            case DeviceSettingsPreferenceConst.PREF_SONY_BUTTON_MODE_LEFT:
+            case DeviceSettingsPreferenceConst.PREF_SONY_BUTTON_MODE_RIGHT:
+                configRequest = protocolImpl.setButtonModes(ButtonModes.fromPreferences(prefs));
+                break;
+            case DeviceSettingsPreferenceConst.PREF_SONY_QUICK_ACCESS_DOUBLE_TAP:
+            case DeviceSettingsPreferenceConst.PREF_SONY_QUICK_ACCESS_TRIPLE_TAP:
+                configRequest = protocolImpl.setQuickAccess(QuickAccess.fromPreferences(prefs));
+                break;
+            case DeviceSettingsPreferenceConst.PREF_SONY_PAUSE_WHEN_TAKEN_OFF:
+                configRequest = protocolImpl.setPauseWhenTakenOff(PauseWhenTakenOff.fromPreferences(prefs));
+                break;
+            case DeviceSettingsPreferenceConst.PREF_SONY_NOTIFICATION_VOICE_GUIDE:
+                configRequest = protocolImpl.setVoiceNotifications(VoiceNotifications.fromPreferences(prefs));
+                break;
+            case DeviceSettingsPreferenceConst.PREF_SONY_CONNECT_TWO_DEVICES:
+                LOG.warn("Connection to two devices not implemented ('{}')", config);
+                return super.encodeSendConfiguration(config);
+            case DeviceSettingsPreferenceConst.PREF_SONY_SPEAK_TO_CHAT:
+                configRequest = protocolImpl.setSpeakToChatEnabled(SpeakToChatEnabled.fromPreferences(prefs));
+                break;
+            case DeviceSettingsPreferenceConst.PREF_SONY_SPEAK_TO_CHAT_SENSITIVITY:
+            case DeviceSettingsPreferenceConst.PREF_SONY_SPEAK_TO_CHAT_FOCUS_ON_VOICE:
+            case DeviceSettingsPreferenceConst.PREF_SONY_SPEAK_TO_CHAT_TIMEOUT:
+                configRequest = protocolImpl.setSpeakToChatConfig(SpeakToChatConfig.fromPreferences(prefs));
+                break;
+            case DeviceSettingsPreferenceConst.PREF_SONY_ADAPTIVE_VOLUME_CONTROL:
+                configRequest = protocolImpl.setAdaptiveVolumeControl(AdaptiveVolumeControl.fromPreferences(prefs));
+                break;
+            case DeviceSettingsPreferenceConst.PREF_SONY_WIDE_AREA_TAP:
+                configRequest = protocolImpl.setWideAreaTap(WideAreaTap.fromPreferences(prefs));
+                break;
+            default:
+                LOG.warn("Unknown config '{}'", config);
+                return super.encodeSendConfiguration(config);
+        }
+
+        if (configRequest == null) {
+            LOG.warn("Failed to encode config request for {}", config);
+            return super.encodeSendConfiguration(config);
+        }
+
+        pendingAcks++;
+
+        return configRequest.encode(sequenceNumber);
+    }
+
+    @Override
+    public byte[] encodeTestNewFunction() {
+        //return Request.fromHex(MessageType.COMMAND_1, "c40100").encode(sequenceNumber);
+
+        return null;
+    }
+
+    @Override
+    public byte[] encodePowerOff() {
+        if (protocolImpl != null) {
+            return protocolImpl.powerOff().encode(sequenceNumber);
+        }
+
+        return super.encodePowerOff();
+    }
+
+    public byte[] encodeAck(byte sequenceNumber) {
+        return new Message(MessageType.ACK, (byte) (1 - sequenceNumber), new byte[0]).encode();
+    }
+
+    public byte[] encodeInit() {
+        pendingAcks++;
+        return new Message(
+                MessageType.COMMAND_1,
+                sequenceNumber,
+                new byte[]{
+                        (byte) 0x00,
+                        (byte) 0x00
+                }
+        ).encode();
+    }
+
+    public void enqueueRequests(final List<Request> requests) {
+        LOG.debug("Enqueueing {} requests", requests.size());
+
+        requestQueue.addAll(requests);
+    }
+
+    public int getPendingAcks() {
+        return pendingAcks;
+    }
+
+    public void decreasePendingAcks() {
+        pendingAcks--;
+    }
+
+    public byte[] getFromQueue() {
+        return requestQueue.remove().encode(sequenceNumber);
+    }
+
+    public boolean hasProtocolImplementation() {
+        return protocolImpl != null;
+    }
+
+    private GBDeviceEvent handleAck() {
+        pendingAcks--;
+
+        if (!requestQueue.isEmpty()) {
+            LOG.debug("Outstanding requests in queue: {}", requestQueue.size());
+
+            final Request request = requestQueue.remove();
+
+            return new GBDeviceEventSendBytes(request.encode(sequenceNumber));
+        }
+
+        if (GBDevice.State.INITIALIZING.equals(getDevice().getState())) {
+            // The queue is now empty, so we have got all the information from the device
+            // Mark it as initialized
+
+            return new GBDeviceEventUpdateDeviceState(GBDevice.State.INITIALIZED);
+        }
+
+        return null;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/headphones/SonyHeadphonesSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/headphones/SonyHeadphonesSupport.java
new file mode 100644
index 0000000..2064f3d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/headphones/SonyHeadphonesSupport.java
@@ -0,0 +1,64 @@
+/*  Copyright (C) 2021-2024 Arjan Schrijver, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones;
+
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
+import nodomain.freeyourgadget.gadgetbridge.service.AbstractHeadphoneDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.deviceevents.SonyHeadphonesEnqueueRequestEvent;
+import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceIoThread;
+import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol;
+
+public class SonyHeadphonesSupport extends AbstractHeadphoneDeviceSupport {
+    @Override
+    protected GBDeviceProtocol createDeviceProtocol() {
+        return new SonyHeadphonesProtocol(getDevice());
+    }
+
+    @Override
+    protected GBDeviceIoThread createDeviceIOThread() {
+        return new SonyHeadphonesIoThread(getDevice(), getContext(), (SonyHeadphonesProtocol) getDeviceProtocol(), SonyHeadphonesSupport.this, getBluetoothAdapter());
+    }
+
+    @Override
+    public synchronized SonyHeadphonesIoThread getDeviceIOThread() {
+        return (SonyHeadphonesIoThread) super.getDeviceIOThread();
+    }
+
+    @Override
+    public void evaluateGBDeviceEvent(GBDeviceEvent deviceEvent) {
+        final SonyHeadphonesProtocol sonyProtocol = (SonyHeadphonesProtocol) getDeviceProtocol();
+
+        if (deviceEvent instanceof SonyHeadphonesEnqueueRequestEvent) {
+            final SonyHeadphonesEnqueueRequestEvent enqueueRequestEvent = (SonyHeadphonesEnqueueRequestEvent) deviceEvent;
+            sonyProtocol.enqueueRequests(enqueueRequestEvent.getRequests());
+
+            if (sonyProtocol.getPendingAcks() == 0) {
+                // There are no pending acks, send one request from the queue
+                // TODO: A more elegant way of scheduling these?
+                SonyHeadphonesIoThread deviceIOThread = getDeviceIOThread();
+                deviceIOThread.write(sonyProtocol.getFromQueue());
+            }
+        }
+
+        super.evaluateGBDeviceEvent(deviceEvent);
+    }
+
+    @Override
+    public boolean useAutoConnect() {
+        return false;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/headphones/deviceevents/SonyHeadphonesEnqueueRequestEvent.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/headphones/deviceevents/SonyHeadphonesEnqueueRequestEvent.java
new file mode 100644
index 0000000..ac6870e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/headphones/deviceevents/SonyHeadphonesEnqueueRequestEvent.java
@@ -0,0 +1,40 @@
+/*  Copyright (C) 2021-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.deviceevents;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.Request;
+
+public class SonyHeadphonesEnqueueRequestEvent extends GBDeviceEvent {
+    private final List<Request> requests = new ArrayList<>();
+
+    public SonyHeadphonesEnqueueRequestEvent(final Request request) {
+        this.requests.add(request);
+    }
+
+    public SonyHeadphonesEnqueueRequestEvent(final Collection<Request> requests) {
+        this.requests.addAll(requests);
+    }
+
+    public List<Request> getRequests() {
+        return this.requests;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/headphones/protocol/Message.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/headphones/protocol/Message.java
new file mode 100644
index 0000000..4c5437c
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/headphones/protocol/Message.java
@@ -0,0 +1,213 @@
+/*  Copyright (C) 2021-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Locale;
+
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class Message {
+    private static final Logger LOG = LoggerFactory.getLogger(Message.class);
+
+    /**
+     * Message format:
+     * <p>
+     * - MESSAGE_HEADER
+     * - Message Type ({@link MessageType})
+     * - Sequence Number - needs to be updated with the one sent in the ACK responses
+     * - Payload Length - 4-byte big endian int with number of bytes that will follow
+     * - N bytes of payload data (first being the PayloadType)
+     * - Checksum (1-byte sum, excluding header)
+     * - MESSAGE_TRAILER
+     * <p>
+     * Data between MESSAGE_HEADER and MESSAGE_TRAILER is escaped with MESSAGE_ESCAPE, and the
+     * following byte masked with MESSAGE_ESCAPE_MASK.
+     */
+
+    public static final byte MESSAGE_HEADER = 0x3e;
+    public static final byte MESSAGE_TRAILER = 0x3c;
+    public static final byte MESSAGE_ESCAPE = 0x3d;
+    public static final byte MESSAGE_ESCAPE_MASK = (byte) 0b11101111;
+
+    private final MessageType type;
+    private final byte sequenceNumber;
+    private final byte[] payload;
+
+    public Message(final MessageType type, final byte sequenceNumber, final byte[] payload) {
+        this.type = type;
+        this.sequenceNumber = sequenceNumber;
+        this.payload = payload;
+    }
+
+    public byte[] encode() {
+        final ByteBuffer buf = ByteBuffer.allocate(payload.length + 6);
+        buf.order(ByteOrder.BIG_ENDIAN);
+
+        buf.put(type.getCode());
+        buf.put(sequenceNumber);
+        buf.putInt(payload.length);
+        buf.put(payload);
+
+        return encodeMessage(buf.array());
+    }
+
+    public MessageType getType() {
+        return this.type;
+    }
+
+    public byte getSequenceNumber() {
+        return this.sequenceNumber;
+    }
+
+    public byte[] getPayload() {
+        return this.payload;
+    }
+
+    public String toString() {
+        if (payload.length > 0) {
+            return String.format(Locale.getDefault(), "Message{Cmd=%s, Seq=%d, PayloadType=%d, Payload=%s}", type, sequenceNumber, payload[0], GB.hexdump(payload));
+        } else {
+            return String.format(Locale.getDefault(), "Message{Cmd=%s, Seq=%d}", type, sequenceNumber);
+        }
+    }
+
+    public static Message fromBytes(final byte[] rawBytes) {
+        if (rawBytes[0] != MESSAGE_HEADER) {
+            throw new IllegalArgumentException(String.format("Invalid header %02x", rawBytes[0]));
+        }
+
+        if (rawBytes[rawBytes.length - 1] != MESSAGE_TRAILER) {
+            throw new IllegalArgumentException(String.format("Invalid trailer %02x", rawBytes[0]));
+        }
+
+        final byte[] messageBytes = unescape(rawBytes);
+        final String hexdump = GB.hexdump(messageBytes, 0, messageBytes.length);
+
+        final byte messageChecksum = messageBytes[messageBytes.length - 2];
+        final byte expectedChecksum = calcChecksum(messageBytes, 1, messageBytes.length - 2);
+        if (messageChecksum != expectedChecksum) {
+            LOG.warn(String.format("Invalid checksum %02x for %s (expected %02x)", messageChecksum, hexdump, expectedChecksum));
+            return null;
+        }
+
+        final int payloadLength = ((messageBytes[3] << 24) & 0xFF000000) |
+                ((messageBytes[4] << 16) & 0xFF0000) |
+                ((messageBytes[5] << 8) & 0xFF00) |
+                (messageBytes[6] & 0xFF);
+        if (payloadLength != messageBytes.length - 9) {
+            LOG.warn("Unexpected payload length {}, expected {}", messageBytes.length - 7, payloadLength);
+            return null;
+        }
+
+        final byte rawMessageType = messageBytes[1];
+        final MessageType messageType = MessageType.fromCode(rawMessageType);
+        if (messageType == null) {
+            LOG.warn("Unknown message type {}", String.format("%02x", rawMessageType));
+            return null;
+        }
+
+        final byte sequenceNumber = messageBytes[2];
+        final byte[] payload = new byte[payloadLength];
+        System.arraycopy(messageBytes, 7, payload, 0, payloadLength);
+
+        return new Message(messageType, sequenceNumber, payload);
+    }
+
+    public static byte[] encodeMessage(byte[] message) {
+        final ByteArrayOutputStream cmdStream = new ByteArrayOutputStream(message.length + 2);
+
+        cmdStream.write(MESSAGE_HEADER);
+
+        final byte checksum = calcChecksum(message, 0, message.length);
+
+        try {
+            cmdStream.write(escape(message));
+            cmdStream.write(escape(new byte[]{checksum}));
+        } catch (final IOException e) {
+            LOG.error("This should never happen", e);
+        }
+
+        cmdStream.write(MESSAGE_TRAILER);
+
+        return cmdStream.toByteArray();
+    }
+
+    public static byte[] escape(byte[] bytes) {
+        final ByteArrayOutputStream escapedStream = new ByteArrayOutputStream(bytes.length);
+
+        for (byte b : bytes) {
+            switch (b) {
+                case MESSAGE_HEADER:
+                case MESSAGE_TRAILER:
+                case MESSAGE_ESCAPE:
+                    escapedStream.write(MESSAGE_ESCAPE);
+                    escapedStream.write(b & MESSAGE_ESCAPE_MASK);
+                    break;
+                default:
+                    escapedStream.write(b);
+                    break;
+            }
+        }
+
+        return escapedStream.toByteArray();
+    }
+
+    public static byte[] unescape(byte[] bytes) {
+        final ByteArrayOutputStream unescapedStream = new ByteArrayOutputStream(bytes.length);
+
+        for (int i = 0; i < bytes.length; i++) {
+            final byte b = bytes[i];
+            if (b == MESSAGE_ESCAPE) {
+                if (++i >= bytes.length) {
+                    throw new IllegalArgumentException("Invalid escape character at end of array");
+                }
+                unescapedStream.write(bytes[i] | ~MESSAGE_ESCAPE_MASK);
+            } else {
+                unescapedStream.write(b);
+            }
+        }
+
+        return unescapedStream.toByteArray();
+    }
+
+    public static byte calcChecksum(byte[] message, int start, int end) {
+        int chk = 0;
+        for (int i = start; i < end; i++) {
+            chk += message[i] & 255;
+        }
+        return (byte) chk;
+    }
+
+    public static byte[] hexToBytes(final String payloadHex) {
+        final String[] parts = payloadHex.split(":");
+
+        final ByteArrayOutputStream stream = new ByteArrayOutputStream();
+
+        for (final String b : parts) {
+            stream.write((byte) ((Character.digit(b.charAt(0), 16) << 4) + Character.digit(b.charAt(1), 16)));
+        }
+
+        return stream.toByteArray();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/headphones/protocol/MessageType.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/headphones/protocol/MessageType.java
new file mode 100644
index 0000000..11198c0
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/headphones/protocol/MessageType.java
@@ -0,0 +1,45 @@
+/*  Copyright (C) 2021-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol;
+
+public enum MessageType {
+    ACK(0x01),
+    COMMAND_1(0x0c),
+    COMMAND_2(0x0e),
+
+    UNKNOWN(0xff);
+
+    private final byte code;
+
+    MessageType(final int code) {
+        this.code = (byte) code;
+    }
+
+    public byte getCode() {
+        return this.code;
+    }
+
+    public static MessageType fromCode(final byte code) {
+        for (final MessageType messageType : values()) {
+            if (messageType.code == code) {
+                return messageType;
+            }
+        }
+
+        return MessageType.UNKNOWN;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/headphones/protocol/Request.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/headphones/protocol/Request.java
new file mode 100644
index 0000000..ab28542
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/headphones/protocol/Request.java
@@ -0,0 +1,58 @@
+/*  Copyright (C) 2021-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol;
+
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class Request {
+    private final MessageType messageType;
+    private final byte[] payload;
+
+    public Request(final MessageType messageType, final byte[] payload) {
+        this.messageType = messageType;
+        this.payload = payload;
+    }
+
+    public Request(final MessageType messageType, final int... payload) {
+        this.messageType = messageType;
+        this.payload = new byte[payload.length];
+
+        for (int i = 0; i < payload.length; i++) {
+            this.payload[i] = (byte) payload[i];
+        }
+    }
+
+    public MessageType messageType() {
+        return this.messageType;
+    }
+
+    public byte[] payload() {
+        return this.payload;
+    }
+
+    public final Message toMessage(final byte sequenceNumber) {
+        return new Message(messageType(), sequenceNumber, payload());
+    }
+
+    public final byte[] encode(final byte sequenceNumber) {
+        return toMessage(sequenceNumber).encode();
+    }
+
+    public static Request fromHex(final MessageType messageType, final String payload) {
+        return new Request(messageType, GB.hexStringToByteArray(payload));
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/headphones/protocol/impl/AbstractSonyProtocolImpl.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/headphones/protocol/impl/AbstractSonyProtocolImpl.java
new file mode 100644
index 0000000..884b4df
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/headphones/protocol/impl/AbstractSonyProtocolImpl.java
@@ -0,0 +1,145 @@
+/*  Copyright (C) 2021-2024 Daniel Dakhno, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.impl;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.SonyHeadphonesCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.AdaptiveVolumeControl;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.AmbientSoundControl;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.AmbientSoundControlButtonMode;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.AudioUpsampling;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.AutomaticPowerOff;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.ButtonModes;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.EqualizerCustomBands;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.EqualizerPreset;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.PauseWhenTakenOff;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.QuickAccess;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.SoundPosition;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.SpeakToChatConfig;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.SpeakToChatEnabled;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.SurroundMode;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.TouchSensor;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.VoiceNotifications;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.WideAreaTap;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.Request;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.MessageType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.impl.v1.params.BatteryType;
+import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
+
+public abstract class AbstractSonyProtocolImpl {
+    private final GBDevice device;
+
+    public AbstractSonyProtocolImpl(final GBDevice device) {
+        this.device = device;
+    }
+
+    protected GBDevice getDevice() {
+        return this.device;
+    }
+
+    protected SonyHeadphonesCoordinator getCoordinator() {
+        return (SonyHeadphonesCoordinator) getDevice().getDeviceCoordinator();
+    }
+
+    public abstract Request getAmbientSoundControl();
+
+    public abstract Request setAmbientSoundControl(final AmbientSoundControl config);
+
+
+    public abstract Request setAdaptiveVolumeControl(final AdaptiveVolumeControl config);
+
+    public abstract Request getAdaptiveVolumeControl();
+
+    public abstract Request setSpeakToChatEnabled(final SpeakToChatEnabled config);
+
+    public abstract Request getSpeakToChatEnabled();
+
+    public abstract Request setSpeakToChatConfig(final SpeakToChatConfig config);
+
+    public abstract Request getSpeakToChatConfig();
+
+    public abstract Request getNoiseCancellingOptimizerState();
+
+    public abstract Request getAudioCodec();
+
+    public abstract Request getBattery(final BatteryType batteryType);
+
+    public abstract Request getFirmwareVersion();
+
+    public abstract Request getAudioUpsampling();
+
+    public abstract Request setAudioUpsampling(final AudioUpsampling config);
+
+    public abstract Request getAutomaticPowerOff();
+
+    public abstract Request setAutomaticPowerOff(final AutomaticPowerOff config);
+
+    public abstract Request setWideAreaTap(final WideAreaTap config);
+
+    public abstract Request getWideAreaTap();
+
+    public abstract Request getButtonModes();
+
+    public abstract Request setButtonModes(final ButtonModes config);
+
+    public abstract Request getQuickAccess();
+
+    public abstract Request setQuickAccess(final QuickAccess quickAccess);
+
+    public abstract Request getAmbientSoundControlButtonMode();
+
+    public abstract Request setAmbientSoundControlButtonMode(final AmbientSoundControlButtonMode ambientSoundControlButtonMode);
+
+    public abstract Request getPauseWhenTakenOff();
+
+    public abstract Request setPauseWhenTakenOff(final PauseWhenTakenOff config);
+
+    public abstract Request getEqualizer();
+
+    public abstract Request setEqualizerPreset(final EqualizerPreset config);
+
+    public abstract Request setEqualizerCustomBands(final EqualizerCustomBands config);
+
+    public abstract Request getSoundPosition();
+
+    public abstract Request setSoundPosition(final SoundPosition config);
+
+    public abstract Request getSurroundMode();
+
+    public abstract Request setSurroundMode(final SurroundMode config);
+
+    public abstract Request getTouchSensor();
+
+    public abstract Request setTouchSensor(final TouchSensor config);
+
+    public abstract Request getVoiceNotifications();
+
+    public abstract Request setVoiceNotifications(final VoiceNotifications config);
+
+    public abstract Request startNoiseCancellingOptimizer(final boolean start);
+
+    public abstract Request powerOff();
+
+    public abstract Request getVolume();
+
+    public abstract Request setVolume(final int volume);
+
+    public abstract List<? extends GBDeviceEvent> handlePayload(final MessageType messageType, final byte[] payload);
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/headphones/protocol/impl/v1/PayloadTypeV1.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/headphones/protocol/impl/v1/PayloadTypeV1.java
new file mode 100644
index 0000000..abc86b5
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/headphones/protocol/impl/v1/PayloadTypeV1.java
@@ -0,0 +1,128 @@
+/*  Copyright (C) 2022-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.impl.v1;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.MessageType;
+
+public enum PayloadTypeV1 {
+    INIT_REQUEST(MessageType.COMMAND_1, 0x00),
+    INIT_REPLY(MessageType.COMMAND_1, 0x01),
+
+    FW_VERSION_REQUEST(MessageType.COMMAND_1, 0x04),
+    FW_VERSION_REPLY(MessageType.COMMAND_1, 0x05),
+
+    INIT_2_REQUEST(MessageType.COMMAND_1, 0x06),
+    INIT_2_REPLY(MessageType.COMMAND_1, 0x07),
+
+    BATTERY_LEVEL_REQUEST(MessageType.COMMAND_1, 0x10),
+    BATTERY_LEVEL_REPLY(MessageType.COMMAND_1, 0x11),
+    BATTERY_LEVEL_NOTIFY(MessageType.COMMAND_1, 0x13),
+
+    AUDIO_CODEC_REQUEST(MessageType.COMMAND_1, 0x18),
+    AUDIO_CODEC_REPLY(MessageType.COMMAND_1, 0x19),
+    AUDIO_CODEC_NOTIFY(MessageType.COMMAND_1, 0x1b),
+
+    POWER_OFF(MessageType.COMMAND_1, 0x22),
+
+    SOUND_POSITION_OR_MODE_GET(MessageType.COMMAND_1, 0x46),
+    SOUND_POSITION_OR_MODE_RET(MessageType.COMMAND_1, 0x47),
+    SOUND_POSITION_OR_MODE_SET(MessageType.COMMAND_1, 0x48),
+    SOUND_POSITION_OR_MODE_NOTIFY(MessageType.COMMAND_1, 0x49),
+
+    EQUALIZER_GET(MessageType.COMMAND_1, 0x56),
+    EQUALIZER_RET(MessageType.COMMAND_1, 0x57),
+    EQUALIZER_SET(MessageType.COMMAND_1, 0x58),
+    EQUALIZER_NOTIFY(MessageType.COMMAND_1, 0x59),
+
+    AMBIENT_SOUND_CONTROL_GET(MessageType.COMMAND_1, 0x66),
+    AMBIENT_SOUND_CONTROL_RET(MessageType.COMMAND_1, 0x67),
+    AMBIENT_SOUND_CONTROL_SET(MessageType.COMMAND_1, 0x68),
+    AMBIENT_SOUND_CONTROL_NOTIFY(MessageType.COMMAND_1, 0x69),
+
+    VOLUME_GET(MessageType.COMMAND_1, 0xa6),
+    VOLUME_RET(MessageType.COMMAND_1, 0xa7),
+    VOLUME_SET(MessageType.COMMAND_1, 0xa8),
+    VOLUME_NOTIFY(MessageType.COMMAND_1, 0xa9),
+
+    NOISE_CANCELLING_OPTIMIZER_START(MessageType.COMMAND_1, 0x84),
+    NOISE_CANCELLING_OPTIMIZER_STATUS(MessageType.COMMAND_1, 0x85),
+
+    NOISE_CANCELLING_OPTIMIZER_STATE_GET(MessageType.COMMAND_1, 0x86),
+    NOISE_CANCELLING_OPTIMIZER_STATE_RET(MessageType.COMMAND_1, 0x87),
+    NOISE_CANCELLING_OPTIMIZER_STATE_NOTIFY(MessageType.COMMAND_1, 0x89),
+
+    TOUCH_SENSOR_GET(MessageType.COMMAND_1, 0xd6),
+    TOUCH_SENSOR_RET(MessageType.COMMAND_1, 0xd7),
+    TOUCH_SENSOR_SET(MessageType.COMMAND_1, 0xd8),
+    TOUCH_SENSOR_NOTIFY(MessageType.COMMAND_1, 0xd9),
+
+    AUDIO_UPSAMPLING_GET(MessageType.COMMAND_1, 0xe6),
+    AUDIO_UPSAMPLING_RET(MessageType.COMMAND_1, 0xe7),
+    AUDIO_UPSAMPLING_SET(MessageType.COMMAND_1, 0xe8),
+    AUDIO_UPSAMPLING_NOTIFY(MessageType.COMMAND_1, 0xe9),
+
+    AUTOMATIC_POWER_OFF_BUTTON_MODE_GET(MessageType.COMMAND_1, 0xf6),
+    AUTOMATIC_POWER_OFF_BUTTON_MODE_RET(MessageType.COMMAND_1, 0xf7),
+    AUTOMATIC_POWER_OFF_BUTTON_MODE_SET(MessageType.COMMAND_1, 0xf8),
+    AUTOMATIC_POWER_OFF_BUTTON_MODE_NOTIFY(MessageType.COMMAND_1, 0xf9),
+
+    SPEAK_TO_CHAT_CONFIG_GET(MessageType.COMMAND_1, 0xfa),
+    SPEAK_TO_CHAT_CONFIG_RET(MessageType.COMMAND_1, 0xfb),
+    SPEAK_TO_CHAT_CONFIG_SET(MessageType.COMMAND_1, 0xfc),
+    SPEAK_TO_CHAT_CONFIG_NOTIFY(MessageType.COMMAND_1, 0xfd),
+
+    // TODO: The headphones spit out a lot of json, analyze it
+    JSON_GET(MessageType.COMMAND_1, 0xc4),
+    JSON_RET(MessageType.COMMAND_1, 0xc9),
+
+    // TODO: App sends those sometimes
+    SOMETHING_GET(MessageType.COMMAND_1, 0x90),
+    SOMETHING_RET(MessageType.COMMAND_1, 0x91),
+
+    VOICE_NOTIFICATIONS_GET(MessageType.COMMAND_2, 0x46),
+    VOICE_NOTIFICATIONS_RET(MessageType.COMMAND_2, 0x47),
+    VOICE_NOTIFICATIONS_SET(MessageType.COMMAND_2, 0x48),
+    VOICE_NOTIFICATIONS_NOTIFY(MessageType.COMMAND_2, 0x49),
+
+    UNKNOWN(MessageType.UNKNOWN, 0xff);
+
+    private final MessageType messageType;
+    private final byte code;
+
+    PayloadTypeV1(final MessageType messageType, final int code) {
+        this.messageType = messageType;
+        this.code = (byte) code;
+    }
+
+    public MessageType getMessageType() {
+        return this.messageType;
+    }
+
+    public byte getCode() {
+        return this.code;
+    }
+
+    public static PayloadTypeV1 fromCode(final MessageType messageType, final byte code) {
+        for (final PayloadTypeV1 payloadType : values()) {
+            if (messageType.equals(payloadType.messageType) && payloadType.code == code) {
+                return payloadType;
+            }
+        }
+
+        return PayloadTypeV1.UNKNOWN;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/headphones/protocol/impl/v1/SonyProtocolImplV1.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/headphones/protocol/impl/v1/SonyProtocolImplV1.java
new file mode 100644
index 0000000..4d3e382
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/headphones/protocol/impl/v1/SonyProtocolImplV1.java
@@ -0,0 +1,1340 @@
+/*  Copyright (C) 2021-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.impl.v1;
+
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONY_NOISE_OPTIMIZER_STATE_PRESSURE;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONY_NOISE_OPTIMIZER_STATUS;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdateDeviceInfo;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdatePreferences;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.SonyHeadphonesCapabilities;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.SonyHeadphonesCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.AdaptiveVolumeControl;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.AmbientSoundControl;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.AmbientSoundControlButtonMode;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.AudioUpsampling;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.AutomaticPowerOff;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.ButtonModes;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.EqualizerCustomBands;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.EqualizerPreset;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.PauseWhenTakenOff;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.QuickAccess;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.SoundPosition;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.SpeakToChatConfig;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.SpeakToChatEnabled;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.SurroundMode;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.TouchSensor;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.VoiceNotifications;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.WideAreaTap;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.BatteryState;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.deviceevents.SonyHeadphonesEnqueueRequestEvent;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.Request;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.MessageType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.impl.AbstractSonyProtocolImpl;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.impl.v1.params.AudioCodec;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.impl.v1.params.BatteryType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.impl.v1.params.NoiseCancellingOptimizerStatus;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.impl.v1.params.VirtualSoundParam;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class SonyProtocolImplV1 extends AbstractSonyProtocolImpl {
+    private static final Logger LOG = LoggerFactory.getLogger(SonyProtocolImplV1.class);
+
+    public SonyProtocolImplV1(GBDevice device) {
+        super(device);
+    }
+
+    @Override
+    public Request getAmbientSoundControl() {
+        return new Request(
+                PayloadTypeV1.AMBIENT_SOUND_CONTROL_GET.getMessageType(),
+                new byte[]{
+                        PayloadTypeV1.AMBIENT_SOUND_CONTROL_GET.getCode(),
+                        (byte) 0x02
+                }
+        );
+    }
+
+    @Override
+    public Request setAmbientSoundControl(final AmbientSoundControl ambientSoundControl) {
+        final ByteBuffer buf = ByteBuffer.allocate(8);
+
+        buf.put(PayloadTypeV1.AMBIENT_SOUND_CONTROL_SET.getCode());
+        buf.put((byte) 0x02);
+
+        if (AmbientSoundControl.Mode.OFF.equals(ambientSoundControl.getMode())) {
+            buf.put((byte) 0x00);
+        } else {
+            buf.put((byte) 0x11);
+        }
+
+        if (supportsWindNoiseCancelling()) {
+            buf.put((byte) 0x02);
+
+            switch (ambientSoundControl.getMode()) {
+                case NOISE_CANCELLING:
+                    buf.put((byte) 2);
+                    break;
+                case WIND_NOISE_REDUCTION:
+                    buf.put((byte) 1);
+                    break;
+                case OFF:
+                case AMBIENT_SOUND:
+                default:
+                    buf.put((byte) 0);
+                    break;
+            }
+        } else {
+            buf.put((byte) 0x00);
+
+            if (AmbientSoundControl.Mode.NOISE_CANCELLING.equals(ambientSoundControl.getMode())) {
+                buf.put((byte) 1);
+            } else {
+                buf.put((byte) 0);
+            }
+        }
+
+        buf.put((byte) 0x01);  // ?
+        buf.put((byte) (ambientSoundControl.isFocusOnVoice() ? 0x01 : 0x00));
+
+        switch (ambientSoundControl.getMode()) {
+            case OFF:
+            case AMBIENT_SOUND:
+                buf.put((byte) (ambientSoundControl.getAmbientSound()));
+                break;
+            case WIND_NOISE_REDUCTION:
+            case NOISE_CANCELLING:
+                buf.put((byte) 0);
+                break;
+        }
+
+        return new Request(PayloadTypeV1.AMBIENT_SOUND_CONTROL_SET.getMessageType(), buf.array());
+    }
+
+    @Override
+    public Request setAdaptiveVolumeControl(final AdaptiveVolumeControl config) {
+        LOG.warn("Adaptive volume control not implemented for V1");
+        return null;
+    }
+
+    @Override
+    public Request getAdaptiveVolumeControl() {
+        LOG.warn("Adaptive volume control not implemented for V1");
+        return null;
+    }
+
+    @Override
+    public Request setSpeakToChatEnabled(final SpeakToChatEnabled config) {
+        return new Request(
+                PayloadTypeV1.AUTOMATIC_POWER_OFF_BUTTON_MODE_SET.getMessageType(),
+                new byte[]{
+                        PayloadTypeV1.AUTOMATIC_POWER_OFF_BUTTON_MODE_SET.getCode(),
+                        (byte) 0x05,
+                        (byte) 0x01,
+                        (byte) (config.isEnabled() ? 0x01 : 0x00)
+                }
+        );
+    }
+
+    @Override
+    public Request getSpeakToChatEnabled() {
+        return new Request(
+                PayloadTypeV1.AUTOMATIC_POWER_OFF_BUTTON_MODE_GET.getMessageType(),
+                new byte[]{
+                        PayloadTypeV1.AUTOMATIC_POWER_OFF_BUTTON_MODE_GET.getCode(),
+                        (byte) 0x05
+                }
+        );
+    }
+
+    @Override
+    public Request setSpeakToChatConfig(final SpeakToChatConfig config) {
+        return new Request(
+                PayloadTypeV1.SPEAK_TO_CHAT_CONFIG_SET.getMessageType(),
+                new byte[]{
+                        PayloadTypeV1.SPEAK_TO_CHAT_CONFIG_SET.getCode(),
+                        (byte) 0x05,
+                        (byte) 0x00,
+                        config.getSensitivity().getCode(),
+                        (byte) (config.isFocusOnVoice() ? 0x01 : 0x00),
+                        config.getTimeout().getCode()
+                }
+        );
+    }
+
+    @Override
+    public Request getSpeakToChatConfig() {
+        return new Request(
+                PayloadTypeV1.SPEAK_TO_CHAT_CONFIG_GET.getMessageType(),
+                new byte[]{
+                        PayloadTypeV1.SPEAK_TO_CHAT_CONFIG_GET.getCode(),
+                        (byte) 0x05
+                }
+        );
+    }
+
+    @Override
+    public Request getNoiseCancellingOptimizerState() {
+        return new Request(
+                PayloadTypeV1.NOISE_CANCELLING_OPTIMIZER_STATE_GET.getMessageType(),
+                new byte[]{
+                        PayloadTypeV1.NOISE_CANCELLING_OPTIMIZER_STATE_GET.getCode(),
+                        (byte) 0x01
+                }
+        );
+    }
+
+    @Override
+    public Request getAudioCodec() {
+        return new Request(
+                PayloadTypeV1.AUDIO_CODEC_REQUEST.getMessageType(),
+                new byte[]{
+                        PayloadTypeV1.AUDIO_CODEC_REQUEST.getCode(),
+                        (byte) 0x00
+                }
+        );
+    }
+
+    @Override
+    public Request getBattery(final BatteryType batteryType) {
+        return new Request(
+                PayloadTypeV1.BATTERY_LEVEL_REQUEST.getMessageType(),
+                new byte[]{
+                        PayloadTypeV1.BATTERY_LEVEL_REQUEST.getCode(),
+                        encodeBatteryType(batteryType)
+                }
+        );
+    }
+
+    @Override
+    public Request getFirmwareVersion() {
+        return new Request(
+                PayloadTypeV1.FW_VERSION_REQUEST.getMessageType(),
+                new byte[]{
+                        PayloadTypeV1.FW_VERSION_REQUEST.getCode(),
+                        (byte) 0x02
+                }
+        );
+    }
+
+    @Override
+    public Request getAudioUpsampling() {
+        return new Request(
+                PayloadTypeV1.AUDIO_UPSAMPLING_GET.getMessageType(),
+                new byte[]{
+                        PayloadTypeV1.AUDIO_UPSAMPLING_GET.getCode(),
+                        (byte) 0x02
+                }
+        );
+    }
+
+    @Override
+    public Request setAudioUpsampling(final AudioUpsampling config) {
+        return new Request(
+                PayloadTypeV1.AUDIO_UPSAMPLING_SET.getMessageType(),
+                new byte[]{
+                        PayloadTypeV1.AUDIO_UPSAMPLING_SET.getCode(),
+                        (byte) 0x02,
+                        (byte) 0x00,
+                        (byte) (config.isEnabled() ? 0x01 : 0x00)
+                }
+        );
+    }
+
+    @Override
+    public Request getAutomaticPowerOff() {
+        return new Request(
+                PayloadTypeV1.AUTOMATIC_POWER_OFF_BUTTON_MODE_GET.getMessageType(),
+                new byte[]{
+                        PayloadTypeV1.AUTOMATIC_POWER_OFF_BUTTON_MODE_GET.getCode(),
+                        (byte) 0x04
+                }
+        );
+    }
+
+    @Override
+    public Request setAutomaticPowerOff(final AutomaticPowerOff config) {
+        return new Request(
+                PayloadTypeV1.AUTOMATIC_POWER_OFF_BUTTON_MODE_SET.getMessageType(),
+                new byte[]{
+                        PayloadTypeV1.AUTOMATIC_POWER_OFF_BUTTON_MODE_SET.getCode(),
+                        (byte) 0x04,
+                        (byte) 0x01,
+                        config.getCode()[0],
+                        config.getCode()[1]
+                }
+        );
+    }
+
+    @Override
+    public Request setWideAreaTap(final WideAreaTap config) {
+        LOG.warn("Adaptive volume control not implemented for V1");
+        return null;
+    }
+
+    @Override
+    public Request getWideAreaTap() {
+        LOG.warn("Adaptive volume control not implemented for V1");
+        return null;
+    }
+
+    public Request getButtonModes() {
+        return new Request(
+                PayloadTypeV1.AUTOMATIC_POWER_OFF_BUTTON_MODE_GET.getMessageType(),
+                new byte[]{
+                        PayloadTypeV1.AUTOMATIC_POWER_OFF_BUTTON_MODE_GET.getCode(),
+                        (byte) 0x06
+                }
+        );
+    }
+
+    public Request setButtonModes(final ButtonModes config) {
+        return new Request(
+                PayloadTypeV1.AUTOMATIC_POWER_OFF_BUTTON_MODE_SET.getMessageType(),
+                new byte[]{
+                        PayloadTypeV1.AUTOMATIC_POWER_OFF_BUTTON_MODE_SET.getCode(),
+                        (byte) 0x06,
+                        (byte) 0x02,
+                        encodeButtonMode(config.getModeLeft()),
+                        encodeButtonMode(config.getModeRight())
+                }
+        );
+    }
+
+    @Override
+    public Request getQuickAccess() {
+        LOG.warn("Quick access not implemented for V1");
+        return null;
+    }
+
+    @Override
+    public Request setQuickAccess(final QuickAccess quickAccess) {
+        LOG.warn("Quick access not implemented for V1");
+        return null;
+    }
+
+    @Override
+    public Request getAmbientSoundControlButtonMode() {
+        LOG.warn("Ambient sound control button modes not implemented for V1");
+        return null;
+    }
+
+    @Override
+    public Request setAmbientSoundControlButtonMode(final AmbientSoundControlButtonMode ambientSoundControlButtonMode) {
+        LOG.warn("Ambient sound control button modes not implemented for V1");
+        return null;
+    }
+
+    @Override
+    public Request getPauseWhenTakenOff() {
+        return new Request(
+                PayloadTypeV1.AUTOMATIC_POWER_OFF_BUTTON_MODE_GET.getMessageType(),
+                new byte[]{
+                        PayloadTypeV1.AUTOMATIC_POWER_OFF_BUTTON_MODE_GET.getCode(),
+                        (byte) 0x03
+                }
+        );
+    }
+
+    @Override
+    public Request setPauseWhenTakenOff(final PauseWhenTakenOff config) {
+        return new Request(
+                PayloadTypeV1.AUTOMATIC_POWER_OFF_BUTTON_MODE_SET.getMessageType(),
+                new byte[]{
+                        PayloadTypeV1.AUTOMATIC_POWER_OFF_BUTTON_MODE_SET.getCode(),
+                        (byte) 0x03,
+                        (byte) 0x00,
+                        (byte) (config.isEnabled() ? 0x01 : 0x00)
+                }
+        );
+    }
+
+    @Override
+    public Request getEqualizer() {
+        return new Request(
+                PayloadTypeV1.EQUALIZER_GET.getMessageType(),
+                new byte[]{
+                        PayloadTypeV1.EQUALIZER_GET.getCode(),
+                        (byte) 0x01
+                }
+        );
+    }
+
+    @Override
+    public Request setEqualizerPreset(final EqualizerPreset config) {
+        return new Request(
+                PayloadTypeV1.EQUALIZER_SET.getMessageType(),
+                new byte[]{
+                        PayloadTypeV1.EQUALIZER_SET.getCode(),
+                        (byte) 0x01,
+                        config.getCode(),
+                        (byte) 0x00
+                }
+        );
+    }
+
+    @Override
+    public Request setEqualizerCustomBands(final EqualizerCustomBands config) {
+        final ByteBuffer buf = ByteBuffer.allocate(10);
+
+        buf.put(PayloadTypeV1.EQUALIZER_SET.getCode());
+        buf.put((byte) 0x01);
+        buf.put((byte) 0xff);
+        buf.put((byte) 0x06);
+
+        buf.put((byte) (config.getBass() + 10));
+        for (final Integer band : config.getBands()) {
+            buf.put((byte) (band + 10));
+        }
+
+        return new Request(
+                PayloadTypeV1.EQUALIZER_SET.getMessageType(),
+                buf.array()
+        );
+    }
+
+    @Override
+    public Request getSoundPosition() {
+        return new Request(
+                PayloadTypeV1.SOUND_POSITION_OR_MODE_GET.getMessageType(),
+                new byte[]{
+                        PayloadTypeV1.SOUND_POSITION_OR_MODE_GET.getCode(),
+                        (byte) 0x02
+                }
+        );
+    }
+
+    @Override
+    public Request setSoundPosition(final SoundPosition config) {
+        return new Request(
+                PayloadTypeV1.SOUND_POSITION_OR_MODE_SET.getMessageType(),
+                new byte[]{
+                        PayloadTypeV1.SOUND_POSITION_OR_MODE_SET.getCode(),
+                        VirtualSoundParam.SOUND_POSITION.getCode(),
+                        config.getCode()
+                }
+        );
+    }
+
+    @Override
+    public Request getSurroundMode() {
+        return new Request(
+                PayloadTypeV1.SOUND_POSITION_OR_MODE_GET.getMessageType(),
+                new byte[]{
+                        PayloadTypeV1.SOUND_POSITION_OR_MODE_GET.getCode(),
+                        VirtualSoundParam.SURROUND_MODE.getCode()
+                }
+        );
+    }
+
+    @Override
+    public Request setSurroundMode(final SurroundMode config) {
+        return new Request(
+                PayloadTypeV1.SOUND_POSITION_OR_MODE_SET.getMessageType(),
+                new byte[]{
+                        PayloadTypeV1.SOUND_POSITION_OR_MODE_SET.getCode(),
+                        VirtualSoundParam.SURROUND_MODE.getCode(),
+                        config.getCode()
+                }
+        );
+    }
+
+    @Override
+    public Request getTouchSensor() {
+        return new Request(
+                PayloadTypeV1.TOUCH_SENSOR_GET.getMessageType(),
+                new byte[]{
+                        PayloadTypeV1.TOUCH_SENSOR_GET.getCode(),
+                        (byte) 0xd2
+                }
+        );
+    }
+
+    @Override
+    public Request setTouchSensor(final TouchSensor config) {
+        return new Request(
+                PayloadTypeV1.TOUCH_SENSOR_SET.getMessageType(),
+                new byte[]{
+                        PayloadTypeV1.TOUCH_SENSOR_SET.getCode(),
+                        (byte) 0xd2,
+                        (byte) 0x01,
+                        (byte) (config.isEnabled() ? 0x01 : 0x00)
+                }
+        );
+    }
+
+    @Override
+    public Request getVoiceNotifications() {
+        return new Request(
+                PayloadTypeV1.VOICE_NOTIFICATIONS_GET.getMessageType(),
+                new byte[]{
+                        PayloadTypeV1.VOICE_NOTIFICATIONS_GET.getCode(),
+                        (byte) 0x01,
+                        (byte) 0x01
+                }
+        );
+    }
+
+    @Override
+    public Request setVoiceNotifications(final VoiceNotifications config) {
+        return new Request(
+                PayloadTypeV1.VOICE_NOTIFICATIONS_SET.getMessageType(),
+                new byte[]{
+                        PayloadTypeV1.VOICE_NOTIFICATIONS_SET.getCode(),
+                        (byte) 0x01,
+                        (byte) 0x01,
+                        (byte) (config.isEnabled() ? 0x01 : 0x00)
+                }
+        );
+    }
+
+    @Override
+    public Request startNoiseCancellingOptimizer(final boolean start) {
+        return new Request(
+                PayloadTypeV1.NOISE_CANCELLING_OPTIMIZER_START.getMessageType(),
+                new byte[]{
+                        PayloadTypeV1.NOISE_CANCELLING_OPTIMIZER_START.getCode(),
+                        (byte) 0x01,
+                        (byte) 0x00,
+                        (byte) (start ? 0x01 : 0x00)
+                }
+        );
+    }
+
+    @Override
+    public Request powerOff() {
+        return new Request(
+                PayloadTypeV1.POWER_OFF.getMessageType(),
+                new byte[]{
+                        PayloadTypeV1.POWER_OFF.getCode(),
+                        (byte) 0x00,
+                        (byte) 0x01
+                }
+        );
+    }
+
+    @Override
+    public Request getVolume() {
+        return new Request(
+                PayloadTypeV1.VOLUME_GET.getMessageType(),
+                new byte[]{
+                        PayloadTypeV1.VOLUME_GET.getCode(),
+                        (byte) 0x01,
+                        (byte) 0x20
+                }
+        );
+    }
+
+    @Override
+    public Request setVolume(final int volume) {
+        return new Request(
+                PayloadTypeV1.VOLUME_SET.getMessageType(),
+                new byte[]{
+                        PayloadTypeV1.VOLUME_SET.getCode(),
+                        (byte) 0x01,
+                        (byte) 0x20,
+                        (byte) volume
+                }
+        );
+    }
+
+    @Override
+    public List<? extends GBDeviceEvent> handlePayload(final MessageType messageType, final byte[] payload) {
+        final PayloadTypeV1 payloadType = PayloadTypeV1.fromCode(messageType, payload[0]);
+
+        switch (payloadType) {
+            case INIT_REPLY:
+                return handleInitResponse(payload);
+            case FW_VERSION_REPLY:
+                return handleFirmwareVersion(payload);
+            case BATTERY_LEVEL_REPLY:
+            case BATTERY_LEVEL_NOTIFY:
+                return handleBattery(payload);
+            case AUDIO_CODEC_REPLY:
+            case AUDIO_CODEC_NOTIFY:
+                return handleAudioCodec(payload);
+            case SOUND_POSITION_OR_MODE_RET:
+            case SOUND_POSITION_OR_MODE_NOTIFY:
+                return handleVirtualSound(payload);
+            case EQUALIZER_RET:
+            case EQUALIZER_NOTIFY:
+                return handleEqualizer(payload);
+            case AMBIENT_SOUND_CONTROL_RET:
+            case AMBIENT_SOUND_CONTROL_NOTIFY:
+                return handleAmbientSoundControl(payload);
+            case VOLUME_RET:
+            case VOLUME_NOTIFY:
+                return handleVolume(payload);
+            case NOISE_CANCELLING_OPTIMIZER_STATUS:
+                return handleNoiseCancellingOptimizerStatus(payload);
+            case NOISE_CANCELLING_OPTIMIZER_STATE_RET:
+            case NOISE_CANCELLING_OPTIMIZER_STATE_NOTIFY:
+                return handleNoiseCancellingOptimizerState(payload);
+            case TOUCH_SENSOR_RET:
+            case TOUCH_SENSOR_NOTIFY:
+                return handleTouchSensor(payload);
+            case AUDIO_UPSAMPLING_RET:
+            case AUDIO_UPSAMPLING_NOTIFY:
+                return handleAudioUpsampling(payload);
+            case AUTOMATIC_POWER_OFF_BUTTON_MODE_RET:
+            case AUTOMATIC_POWER_OFF_BUTTON_MODE_NOTIFY:
+                return handleAutomaticPowerOffButtonMode(payload);
+            case SPEAK_TO_CHAT_CONFIG_RET:
+            case SPEAK_TO_CHAT_CONFIG_NOTIFY:
+                return handleSpeakToChatConfig(payload);
+            case VOICE_NOTIFICATIONS_RET:
+            case VOICE_NOTIFICATIONS_NOTIFY:
+                return handleVoiceNotifications(payload);
+            case JSON_RET:
+                return handleJson(payload);
+        }
+
+        LOG.warn("Unhandled payload type code {}", String.format("%02x", payload[0]));
+
+        return Collections.emptyList();
+    }
+
+    public List<? extends GBDeviceEvent> handleInitResponse(final byte[] payload) {
+        final SonyHeadphonesCoordinator coordinator = getCoordinator();
+
+        // Populate the init requests
+        final List<Request> capabilityRequests = new ArrayList<>();
+
+        capabilityRequests.add(getFirmwareVersion());
+        capabilityRequests.add(getAudioCodec());
+
+        final Map<SonyHeadphonesCapabilities, Request> capabilityRequestMap = new LinkedHashMap<SonyHeadphonesCapabilities, Request>() {{
+            put(SonyHeadphonesCapabilities.BatterySingle, getBattery(BatteryType.SINGLE));
+            put(SonyHeadphonesCapabilities.BatteryDual, getBattery(BatteryType.DUAL));
+            put(SonyHeadphonesCapabilities.BatteryDual2, getBattery(BatteryType.DUAL2));
+            put(SonyHeadphonesCapabilities.BatteryCase, getBattery(BatteryType.CASE));
+            put(SonyHeadphonesCapabilities.AmbientSoundControl, getAmbientSoundControl());
+            put(SonyHeadphonesCapabilities.AmbientSoundControl2, getAmbientSoundControl());
+            put(SonyHeadphonesCapabilities.AncOptimizer, getNoiseCancellingOptimizerState());
+            put(SonyHeadphonesCapabilities.AudioUpsampling, getAudioUpsampling());
+            put(SonyHeadphonesCapabilities.ButtonModesLeftRight, getButtonModes());
+            put(SonyHeadphonesCapabilities.VoiceNotifications, getVoiceNotifications());
+            put(SonyHeadphonesCapabilities.AutomaticPowerOffWhenTakenOff, getAutomaticPowerOff());
+            put(SonyHeadphonesCapabilities.AutomaticPowerOffByTime, getAutomaticPowerOff());
+            put(SonyHeadphonesCapabilities.TouchSensorSingle, getTouchSensor());
+            put(SonyHeadphonesCapabilities.EqualizerSimple, getEqualizer());
+            put(SonyHeadphonesCapabilities.EqualizerWithCustomBands, getEqualizer());
+            put(SonyHeadphonesCapabilities.SoundPosition, getSoundPosition());
+            put(SonyHeadphonesCapabilities.SurroundMode, getSurroundMode());
+            put(SonyHeadphonesCapabilities.SpeakToChatEnabled, getSpeakToChatEnabled());
+            put(SonyHeadphonesCapabilities.SpeakToChatConfig, getSpeakToChatConfig());
+            put(SonyHeadphonesCapabilities.PauseWhenTakenOff, getPauseWhenTakenOff());
+            put(SonyHeadphonesCapabilities.AmbientSoundControlButtonMode, getAmbientSoundControlButtonMode());
+            put(SonyHeadphonesCapabilities.QuickAccess, getQuickAccess());
+            put(SonyHeadphonesCapabilities.Volume, getVolume());
+            put(SonyHeadphonesCapabilities.AdaptiveVolumeControl, getAdaptiveVolumeControl());
+            put(SonyHeadphonesCapabilities.WideAreaTap, getWideAreaTap());
+        }};
+
+        for (Map.Entry<SonyHeadphonesCapabilities, Request> capabilityEntry : capabilityRequestMap.entrySet()) {
+            if (coordinator.supports(capabilityEntry.getKey())) {
+                capabilityRequests.add(capabilityEntry.getValue());
+            }
+        }
+
+        // Remove any requests that are not supported by other protocol version
+        capabilityRequests.removeAll(Collections.singleton(null));
+
+        return Collections.singletonList(new SonyHeadphonesEnqueueRequestEvent(capabilityRequests));
+    }
+
+    public List<? extends GBDeviceEvent> handleAmbientSoundControl(final byte[] payload) {
+        if (payload.length != 8) {
+            LOG.warn("Unexpected payload length {}", payload.length);
+            return Collections.emptyList();
+        }
+
+        AmbientSoundControl.Mode mode = null;
+
+        if (payload[2] == (byte) 0x00) {
+            mode = AmbientSoundControl.Mode.OFF;
+        } else if (payload[2] == (byte) 0x01) {
+            // Enabled, determine mode
+
+            if (payload[3] == 0x00) {
+                // Only ANC  and Ambient Sound supported?
+                if (payload[4] == (byte) 0x00) {
+                    mode = AmbientSoundControl.Mode.AMBIENT_SOUND;
+                } else if (payload[4] == (byte) 0x01) {
+                    mode = AmbientSoundControl.Mode.NOISE_CANCELLING;
+                }
+            } else if (payload[3] == 0x02) {
+                // Supports wind noise reduction
+                if (payload[4] == (byte) 0x00) {
+                    mode = AmbientSoundControl.Mode.AMBIENT_SOUND;
+                } else if (payload[4] == (byte) 0x01) {
+                    mode = AmbientSoundControl.Mode.WIND_NOISE_REDUCTION;
+                } else if (payload[4] == (byte) 0x02) {
+                    mode = AmbientSoundControl.Mode.NOISE_CANCELLING;
+                }
+            }
+        }
+
+        if (mode == null) {
+            LOG.warn("Unable to determine ambient sound control mode from {}", GB.hexdump(payload));
+            return Collections.emptyList();
+        }
+
+        final Boolean focusOnVoice = booleanFromByte(payload[6]);
+        if (focusOnVoice == null) {
+            LOG.warn("Unknown focus on voice mode {}", String.format("%02x", payload[6]));
+            return Collections.emptyList();
+        }
+
+        int ambientSound = payload[7];
+        if (ambientSound < 0 || ambientSound > 20) {
+            LOG.warn("Ambient sound level {} is out of range", String.format("%02x", payload[7]));
+            return Collections.emptyList();
+        }
+
+        final AmbientSoundControl ambientSoundControl = new AmbientSoundControl(mode, focusOnVoice, ambientSound);
+
+        LOG.debug("Ambient sound control: {}", ambientSoundControl);
+
+        final GBDeviceEventUpdatePreferences eventUpdatePreferences = new GBDeviceEventUpdatePreferences()
+                .withPreferences(ambientSoundControl.toPreferences());
+
+        return Collections.singletonList(eventUpdatePreferences);
+    }
+
+    public List<? extends GBDeviceEvent> handleVolume(final byte[] payload) {
+        if (payload.length != 4) {
+            LOG.warn("Unexpected payload length {}", payload.length);
+            return Collections.emptyList();
+        }
+
+        AmbientSoundControl.Mode mode = null;
+
+        if (payload[1] != (byte) 0x01) {
+            LOG.warn("Unexpected byte at position 1 for volume: {}", payload[1]);
+            return Collections.emptyList();
+        }
+        if (payload[2] != (byte) 0x20) {
+            LOG.warn("Unexpected byte at position 2 for volume: {}", payload[1]);
+            return Collections.emptyList();
+        }
+
+        final int volume = payload[3];
+        if (volume < 0 || volume > 30) {
+            LOG.warn("Volume {} is out of range", String.format("%02x", payload[3]));
+            return Collections.emptyList();
+        }
+
+        LOG.debug("Volume: {}", volume);
+
+        final GBDeviceEventUpdatePreferences eventUpdatePreferences = new GBDeviceEventUpdatePreferences()
+                .withPreference(DeviceSettingsPreferenceConst.PREF_VOLUME, volume);
+
+        return Collections.singletonList(eventUpdatePreferences);
+    }
+
+    public List<? extends GBDeviceEvent> handleNoiseCancellingOptimizerStatus(final byte[] payload) {
+        if (payload.length != 4) {
+            LOG.warn("Unexpected payload length {}", payload.length);
+            return Collections.emptyList();
+        }
+
+        final NoiseCancellingOptimizerStatus status = NoiseCancellingOptimizerStatus.fromCode(payload[3]);
+        if (status == null) {
+            LOG.warn("Unable to determine noise cancelling opptimizer status from {}", GB.hexdump(payload));
+            return Collections.emptyList();
+        }
+
+        LOG.info("Noise Cancelling Optimizer status: {}", status);
+
+        final GBDeviceEventUpdatePreferences event = new GBDeviceEventUpdatePreferences()
+                .withPreference(PREF_SONY_NOISE_OPTIMIZER_STATUS, status.name().toLowerCase(Locale.ROOT));
+
+        return Collections.singletonList(event);
+    }
+
+    public List<? extends GBDeviceEvent> handleNoiseCancellingOptimizerState(final byte[] payload) {
+        // 89 01 01 01 01 0A
+        if (payload.length != 6) {
+            LOG.warn("Unexpected payload length {}", payload.length);
+            return Collections.emptyList();
+        }
+
+        final float pressure = payload[5] / 10.0f;
+
+        if (pressure <= 0 || pressure > 1.0f) {
+            LOG.warn("Invalid Noise Cancelling Optimizer pressure: {} atm, ignoring", pressure);
+            return Collections.emptyList();
+        }
+
+        LOG.info("Noise Cancelling Optimizer pressure: {} atm", pressure);
+
+        final GBDeviceEventUpdatePreferences event = new GBDeviceEventUpdatePreferences()
+                .withPreference(PREF_SONY_NOISE_OPTIMIZER_STATE_PRESSURE, String.format(Locale.getDefault(), "%.2f atm", pressure));
+
+        return Collections.singletonList(event);
+    }
+
+
+    public List<? extends GBDeviceEvent> handleAudioUpsampling(final byte[] payload) {
+        if (payload.length != 4) {
+            LOG.warn("Unexpected payload length {}", payload.length);
+            return Collections.emptyList();
+        }
+
+        final Boolean enabled = booleanFromByte(payload[3]);
+        if (enabled == null) {
+            LOG.warn("Unknown audio upsampling code {}", String.format("%02x", payload[3]));
+            return Collections.emptyList();
+        }
+
+        LOG.debug("Audio Upsampling: {}", enabled);
+
+        final GBDeviceEventUpdatePreferences event = new GBDeviceEventUpdatePreferences()
+                .withPreferences(new AudioUpsampling(enabled).toPreferences());
+
+        return Collections.singletonList(event);
+    }
+
+    public List<? extends GBDeviceEvent> handleAutomaticPowerOff(final byte[] payload) {
+        if (payload.length != 5) {
+            LOG.warn("Unexpected payload length {}", payload.length);
+            return Collections.emptyList();
+        }
+
+        if (payload[1] != 0x04) {
+            // TODO: Handle these (Button Mode uses the same payload type?)
+            LOG.warn("Not automatic power off config, ignoring");
+            return Collections.emptyList();
+        }
+
+        final AutomaticPowerOff mode = AutomaticPowerOff.fromCode(payload[3], payload[4]);
+        if (mode == null) {
+            LOG.warn("Unknown automatic power off codes {}", String.format("%02x %02x", payload[3], payload[4]));
+            return Collections.emptyList();
+        }
+
+        LOG.debug("Automatic Power Off: {}", mode);
+
+        final GBDeviceEventUpdatePreferences event = new GBDeviceEventUpdatePreferences()
+                .withPreferences(mode.toPreferences());
+
+        return Collections.singletonList(event);
+    }
+
+    public List<? extends GBDeviceEvent> handleSpeakToChatEnabled(final byte[] payload) {
+        if (payload.length != 4) {
+            LOG.warn("Unexpected payload length {}", payload.length);
+            return Collections.emptyList();
+        }
+
+        if (payload[2] != 0x01) {
+            // TODO: Handle these, setting speak to chat sends a 0x02 back
+            LOG.warn("Not speak to chat enabled, ignoring");
+            return Collections.emptyList();
+        }
+
+        final Boolean enabled = booleanFromByte(payload[3]);
+        if (enabled == null) {
+            LOG.warn("Unknown speak to chat code {}", String.format("%02x", payload[3]));
+            return Collections.emptyList();
+        }
+
+        LOG.debug("Speak to chat: {}", enabled);
+
+        final GBDeviceEventUpdatePreferences event = new GBDeviceEventUpdatePreferences()
+                .withPreferences(new SpeakToChatEnabled(enabled).toPreferences());
+
+        return Collections.singletonList(event);
+    }
+
+    public List<? extends GBDeviceEvent> handleButtonModes(final byte[] payload) {
+        if (payload.length != 5) {
+            LOG.warn("Unexpected payload length {}", payload.length);
+            return Collections.emptyList();
+        }
+
+        final ButtonModes.Mode modeLeft = decodeButtonMode(payload[3]);
+        final ButtonModes.Mode modeRight = decodeButtonMode(payload[4]);
+        if (modeLeft == null || modeRight == null) {
+            LOG.warn("Unknown button mode codes {}", String.format("%02x %02x", payload[3], payload[4]));
+            return Collections.emptyList();
+        }
+
+        LOG.debug("Button Modes: L: {}, R: {}", modeLeft, modeRight);
+
+        final GBDeviceEventUpdatePreferences event = new GBDeviceEventUpdatePreferences()
+                .withPreferences(new ButtonModes(modeLeft, modeRight).toPreferences());
+
+        return Collections.singletonList(event);
+    }
+
+    public List<? extends GBDeviceEvent> handlePauseWhenTakenOff(final byte[] payload) {
+        if (payload.length != 4) {
+            LOG.warn("Unexpected payload length {}", payload.length);
+            return Collections.emptyList();
+        }
+
+        final Boolean enabled = booleanFromByte(payload[3]);
+        if (enabled == null) {
+            LOG.warn("Unknown pause when taken off code {}", String.format("%02x", payload[3]));
+            return Collections.emptyList();
+        }
+
+        LOG.debug("Pause when taken off: {}", enabled);
+
+        final GBDeviceEventUpdatePreferences event = new GBDeviceEventUpdatePreferences()
+                .withPreferences(new PauseWhenTakenOff(enabled).toPreferences());
+
+        return Collections.singletonList(event);
+    }
+
+    public List<? extends GBDeviceEvent> handleBattery(final byte[] payload) {
+        final BatteryType batteryType = decodeBatteryType(payload[1]);
+
+        if (batteryType == null) {
+            LOG.warn("Unknown battery type code {}", String.format("%02x", payload[1]));
+            return Collections.emptyList();
+        }
+
+        final List<GBDeviceEventBatteryInfo> batteryEvents = new ArrayList<>();
+
+        if (BatteryType.SINGLE.equals(batteryType) || BatteryType.CASE.equals(batteryType)) {
+            // Single battery / Case battery
+            LOG.debug("Battery Level: {}: {}", batteryType, payload[2]);
+
+            final GBDeviceEventBatteryInfo singleBatteryInfo = new GBDeviceEventBatteryInfo();
+            singleBatteryInfo.batteryIndex = 0;
+            singleBatteryInfo.level = payload[2];
+            singleBatteryInfo.state = payload[3] == 1 ? BatteryState.BATTERY_CHARGING : BatteryState.BATTERY_NORMAL;
+
+            batteryEvents.add(singleBatteryInfo);
+        } else if (BatteryType.DUAL.equals(batteryType) || BatteryType.DUAL2.equals(batteryType)) {
+            // Dual Battery (L / R)
+            LOG.debug("Battery Level: {}: L: {}, R: {}", batteryType, payload[2], payload[4]);
+
+            boolean hasCaseBattery = getCoordinator().supports(SonyHeadphonesCapabilities.BatteryCase);
+
+            if (payload[2] != 0) {
+                final GBDeviceEventBatteryInfo gbDeviceEventBatteryInfoLeft = new GBDeviceEventBatteryInfo();
+
+                gbDeviceEventBatteryInfoLeft.batteryIndex = hasCaseBattery ? 1 : 0;
+                gbDeviceEventBatteryInfoLeft.level = payload[2];
+                gbDeviceEventBatteryInfoLeft.state = payload[3] == 1 ? BatteryState.BATTERY_CHARGING : BatteryState.BATTERY_NORMAL;
+
+                batteryEvents.add(gbDeviceEventBatteryInfoLeft);
+            }
+
+            if (payload[4] != 0) {
+                final GBDeviceEventBatteryInfo gbDeviceEventBatteryInfoRight = new GBDeviceEventBatteryInfo();
+
+                gbDeviceEventBatteryInfoRight.batteryIndex = hasCaseBattery ? 2 : 1;
+                gbDeviceEventBatteryInfoRight.level = payload[4];
+                gbDeviceEventBatteryInfoRight.state = payload[5] == 1 ? BatteryState.BATTERY_CHARGING : BatteryState.BATTERY_NORMAL;
+
+                batteryEvents.add(gbDeviceEventBatteryInfoRight);
+            }
+        }
+
+        return batteryEvents;
+    }
+
+    public List<? extends GBDeviceEvent> handleAudioCodec(final byte[] payload) {
+        if (payload.length != 3) {
+            LOG.warn("Unexpected payload length {}", payload.length);
+            return Collections.emptyList();
+        }
+
+        if (payload[1] != 0x00) {
+            LOG.warn("Not audio codec, ignoring");
+            return Collections.emptyList();
+        }
+
+        final AudioCodec audioCodec = AudioCodec.fromCode(payload[2]);
+        if (audioCodec == null) {
+            LOG.warn("Unable to determine audio codec from {}", GB.hexdump(payload));
+            return Collections.emptyList();
+        }
+
+        final GBDeviceEventUpdateDeviceInfo gbDeviceEventUpdateDeviceInfo = new GBDeviceEventUpdateDeviceInfo("AUDIO_CODEC: ", audioCodec.name());
+
+        final GBDeviceEventUpdatePreferences gbDeviceEventUpdatePreferences = new GBDeviceEventUpdatePreferences()
+                .withPreference(DeviceSettingsPreferenceConst.PREF_SONY_AUDIO_CODEC, audioCodec.name().toLowerCase(Locale.getDefault()));
+
+        return Arrays.asList(gbDeviceEventUpdateDeviceInfo, gbDeviceEventUpdatePreferences);
+    }
+
+    public List<? extends GBDeviceEvent> handleEqualizer(final byte[] payload) {
+        if (payload.length != 10) {
+            LOG.warn("Unexpected payload length {}", payload.length);
+            return Collections.emptyList();
+        }
+
+        final EqualizerPreset mode = EqualizerPreset.fromCode(payload[2]);
+        if (mode == null) {
+            LOG.warn("Unknown equalizer preset code {}", String.format("%02x", payload[2]));
+            return Collections.emptyList();
+        }
+
+        LOG.debug("Equalizer Preset: {}", mode);
+
+        final int clearBass = payload[4] - 10;
+        final List<Integer> bands = new ArrayList<>(5);
+
+        for (int i = 0; i < 5; i++) {
+            bands.add(payload[5 + i] - 10);
+        }
+
+        final EqualizerCustomBands customBands = new EqualizerCustomBands(bands, clearBass);
+
+        LOG.info("Equalizer Custom Bands: {}", customBands);
+
+        final GBDeviceEventUpdatePreferences event = new GBDeviceEventUpdatePreferences()
+                .withPreferences(mode.toPreferences())
+                .withPreferences(customBands.toPreferences());
+
+        return Collections.singletonList(event);
+    }
+
+    public List<? extends GBDeviceEvent> handleFirmwareVersion(final byte[] payload) {
+        final Pattern VERSION_REGEX = Pattern.compile("^[0-9.\\-a-zA-Z_]+$");
+
+        if (payload[1] != 0x02) {
+            LOG.warn("Not firmware version, ignoring");
+            return Collections.emptyList();
+        }
+
+        if (payload.length < 4) {
+            LOG.warn("Unexpected payload length {}", payload.length);
+            return Collections.emptyList();
+        }
+
+        final String firmwareVersion = new String(Arrays.copyOfRange(payload, 3, payload.length));
+
+        final int expectedLength = payload[2] & 0xff;
+        if (firmwareVersion.length() != expectedLength) {
+            LOG.warn("Unexpected firmware version length {}, expected {}", firmwareVersion.length(), expectedLength);
+            return Collections.emptyList();
+        }
+
+        if (!VERSION_REGEX.matcher(firmwareVersion).find()) {
+            LOG.warn("Unexpected characters in version '{}'", firmwareVersion);
+            return Collections.emptyList();
+        }
+
+        LOG.debug("Firmware Version: {}", firmwareVersion);
+
+        final GBDeviceEventVersionInfo gbDeviceEventVersionInfo = new GBDeviceEventVersionInfo();
+        gbDeviceEventVersionInfo.fwVersion = firmwareVersion;
+        return Collections.singletonList(gbDeviceEventVersionInfo);
+    }
+
+    public List<? extends GBDeviceEvent> handleJson(final byte[] payload) {
+        // TODO analyze json?
+
+        if (payload.length < 4) {
+            LOG.warn("Unexpected payload length {}", payload.length);
+            return Collections.emptyList();
+        }
+
+        final String jsonString = new String(Arrays.copyOfRange(payload, 4, payload.length));
+
+        LOG.debug("Got json: {}", jsonString);
+
+        return Collections.emptyList();
+    }
+
+    public List<? extends GBDeviceEvent> handleAutomaticPowerOffButtonMode(final byte[] payload) {
+        switch (payload[1]) {
+            case 0x04:
+                return handleAutomaticPowerOff(payload);
+            case 0x03:
+                return handlePauseWhenTakenOff(payload);
+            case 0x05:
+                return handleSpeakToChatEnabled(payload);
+            case 0x06:
+                return handleButtonModes(payload);
+        }
+
+        return Collections.emptyList();
+    }
+
+    public List<? extends GBDeviceEvent> handleVirtualSound(final byte[] payload) {
+        if (payload.length != 3) {
+            LOG.warn("Unexpected payload length {}", payload.length);
+            return Collections.emptyList();
+        }
+
+        final VirtualSoundParam virtualSoundParam = VirtualSoundParam.fromCode(payload[1]);
+        if (virtualSoundParam == null) {
+            LOG.warn("Unknown payload subtype code {}", String.format("%02x", payload[1]));
+            return Collections.emptyList();
+        }
+
+        switch (virtualSoundParam) {
+            case SURROUND_MODE:
+                return handleSurroundMode(payload);
+            case SOUND_POSITION:
+                return handleSoundPosition(payload);
+        }
+
+        return Collections.emptyList();
+    }
+
+    public List<? extends GBDeviceEvent> handleSoundPosition(final byte[] payload) {
+        if (payload.length != 3) {
+            LOG.warn("Unexpected payload length {}", payload.length);
+            return Collections.emptyList();
+        }
+
+        SoundPosition mode = null;
+
+        for (SoundPosition value : SoundPosition.values()) {
+            if (value.getCode() == payload[2]) {
+                mode = value;
+                break;
+            }
+        }
+
+        if (mode == null) {
+            LOG.warn("Unknown sound position code {}", String.format("%02x", payload[2]));
+            return Collections.emptyList();
+        }
+
+        LOG.debug("Sound Position: {}", mode);
+
+        final GBDeviceEventUpdatePreferences event = new GBDeviceEventUpdatePreferences()
+                .withPreferences(mode.toPreferences());
+
+        return Collections.singletonList(event);
+    }
+
+    public List<? extends GBDeviceEvent> handleSurroundMode(final byte[] payload) {
+        if (payload.length != 3) {
+            LOG.warn("Unexpected payload length {}", payload.length);
+            return Collections.emptyList();
+        }
+
+        SurroundMode mode = null;
+
+        for (SurroundMode value : SurroundMode.values()) {
+            if (value.getCode() == payload[2]) {
+                mode = value;
+                break;
+            }
+        }
+
+        if (mode == null) {
+            LOG.warn("Unknown surround mode code {}", String.format("%02x", payload[2]));
+            return Collections.emptyList();
+        }
+
+        LOG.debug("Surround Mode: {}", mode);
+
+        final GBDeviceEventUpdatePreferences event = new GBDeviceEventUpdatePreferences()
+                .withPreferences(mode.toPreferences());
+
+        return Collections.singletonList(event);
+    }
+
+    public List<? extends GBDeviceEvent> handleTouchSensor(final byte[] payload) {
+        if (payload.length != 4) {
+            LOG.warn("Unexpected payload length {}", payload.length);
+            return Collections.emptyList();
+        }
+
+        boolean enabled;
+
+        switch (payload[3]) {
+            case 0x00:
+                enabled = false;
+                break;
+            case 0x01:
+                enabled = true;
+                break;
+            default:
+                LOG.warn("Unknown touch sensor code {}", String.format("%02x", payload[3]));
+                return Collections.emptyList();
+        }
+
+        LOG.debug("Touch Sensor: {}", enabled);
+
+        final GBDeviceEventUpdatePreferences event = new GBDeviceEventUpdatePreferences()
+                .withPreferences(new TouchSensor(enabled).toPreferences());
+
+        return Collections.singletonList(event);
+    }
+
+    public List<? extends GBDeviceEvent> handleSpeakToChatConfig(final byte[] payload) {
+        if (payload.length != 6) {
+            LOG.warn("Unexpected payload length {}", payload.length);
+            return Collections.emptyList();
+        }
+
+        if (payload[1] != 0x05) {
+            LOG.warn("Not speak to chat config, ignoring");
+            return Collections.emptyList();
+        }
+
+        final SpeakToChatConfig.Sensitivity sensitivity = SpeakToChatConfig.Sensitivity.fromCode(payload[3]);
+        if (sensitivity == null) {
+            LOG.warn("Unknown sensitivity code {}", String.format("%02x", payload[3]));
+            return Collections.emptyList();
+        }
+
+        final Boolean focusOnVoice = booleanFromByte(payload[4]);
+        if (focusOnVoice == null) {
+            LOG.warn("Unknown focus on voice code {}", String.format("%02x", payload[3]));
+            return Collections.emptyList();
+        }
+
+        final SpeakToChatConfig.Timeout timeout = SpeakToChatConfig.Timeout.fromCode(payload[5]);
+        if (timeout == null) {
+            LOG.warn("Unknown timeout code {}", String.format("%02x", payload[3]));
+            return Collections.emptyList();
+        }
+
+        final SpeakToChatConfig speakToChatConfig = new SpeakToChatConfig(focusOnVoice, sensitivity, timeout);
+
+        LOG.debug("Speak to chat config: {}", speakToChatConfig);
+
+        final GBDeviceEventUpdatePreferences event = new GBDeviceEventUpdatePreferences()
+                .withPreferences(speakToChatConfig.toPreferences());
+
+        return Collections.singletonList(event);
+    }
+
+    public List<? extends GBDeviceEvent> handleVoiceNotifications(final byte[] payload) {
+        if (payload.length != 4) {
+            LOG.warn("Unexpected payload length {}", payload.length);
+            return Collections.emptyList();
+        }
+
+        boolean enabled;
+
+        switch (payload[3]) {
+            case 0x00:
+                enabled = false;
+                break;
+            case 0x01:
+                enabled = true;
+                break;
+            default:
+                LOG.warn("Unknown voice notifications code {}", String.format("%02x", payload[3]));
+                return Collections.emptyList();
+        }
+
+        LOG.debug("Voice Notifications: {}", enabled);
+
+        final GBDeviceEventUpdatePreferences event = new GBDeviceEventUpdatePreferences()
+                .withPreferences(new VoiceNotifications(enabled).toPreferences());
+
+        return Collections.singletonList(event);
+    }
+
+    protected static Boolean booleanFromByte(final byte b) {
+        switch (b) {
+            case 0x00:
+                return false;
+            case 0x01:
+                return true;
+            default:
+        }
+
+        return null;
+    }
+
+    protected boolean supportsWindNoiseCancelling() {
+        final SonyHeadphonesCoordinator coordinator = getCoordinator();
+
+        return coordinator.supports(SonyHeadphonesCapabilities.WindNoiseReduction);
+    }
+
+    protected BatteryType decodeBatteryType(final byte b) {
+        switch (b) {
+            case 0x00:
+                return BatteryType.SINGLE;
+            case 0x01:
+                return BatteryType.DUAL;
+            case 0x02:
+                return BatteryType.CASE;
+        }
+
+        return null;
+    }
+
+    protected byte encodeBatteryType(final BatteryType batteryType) {
+        switch (batteryType) {
+            case SINGLE:
+                return 0x00;
+            case DUAL:
+                return 0x01;
+            case CASE:
+                return 0x02;
+        }
+
+        throw new IllegalArgumentException("Unknown battery type " + batteryType);
+    }
+
+    protected ButtonModes.Mode decodeButtonMode(final byte b) {
+        switch (b) {
+            case (byte) 0xff:
+                return ButtonModes.Mode.OFF;
+            case (byte) 0x00:
+                return ButtonModes.Mode.AMBIENT_SOUND_CONTROL;
+            case (byte) 0x20:
+                return ButtonModes.Mode.PLAYBACK_CONTROL;
+            case (byte) 0x10:
+                return ButtonModes.Mode.VOLUME_CONTROL;
+        }
+
+        return null;
+    }
+
+    protected byte encodeButtonMode(final ButtonModes.Mode buttonMode) {
+        switch (buttonMode) {
+            case OFF:
+                return (byte) 0xff;
+            case AMBIENT_SOUND_CONTROL:
+                return (byte) 0x00;
+            case PLAYBACK_CONTROL:
+                return (byte) 0x20;
+            case VOLUME_CONTROL:
+                return (byte) 0x10;
+        }
+
+        throw new IllegalArgumentException("Unknown button mode " + buttonMode);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/headphones/protocol/impl/v1/params/AudioCodec.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/headphones/protocol/impl/v1/params/AudioCodec.java
new file mode 100644
index 0000000..7892eac
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/headphones/protocol/impl/v1/params/AudioCodec.java
@@ -0,0 +1,46 @@
+/*  Copyright (C) 2021-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.impl.v1.params;
+
+public enum AudioCodec {
+    UNKNOWN(0x00),
+    SBC(0x01),
+    AAC(0x02),
+    LDAC(0x10),
+    APTX(0x20),
+    APTX_HD(0x21);
+
+    private final byte code;
+
+    AudioCodec(final int code) {
+        this.code = (byte) code;
+    }
+
+    public byte getCode() {
+        return this.code;
+    }
+
+    public static AudioCodec fromCode(final byte code) {
+        for (final AudioCodec audioCodec : values()) {
+            if (audioCodec.code == code) {
+                return audioCodec;
+            }
+        }
+
+        return null;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/headphones/protocol/impl/v1/params/BatteryType.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/headphones/protocol/impl/v1/params/BatteryType.java
new file mode 100644
index 0000000..76b9c6b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/headphones/protocol/impl/v1/params/BatteryType.java
@@ -0,0 +1,24 @@
+/*  Copyright (C) 2021-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.impl.v1.params;
+
+public enum BatteryType {
+    SINGLE,
+    DUAL,
+    DUAL2,
+    CASE,
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/headphones/protocol/impl/v1/params/NoiseCancellingOptimizerStatus.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/headphones/protocol/impl/v1/params/NoiseCancellingOptimizerStatus.java
new file mode 100644
index 0000000..912a9ba
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/headphones/protocol/impl/v1/params/NoiseCancellingOptimizerStatus.java
@@ -0,0 +1,56 @@
+/*  Copyright (C) 2022-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.impl.v1.params;
+
+import android.content.Context;
+
+import java.util.Locale;
+
+public enum NoiseCancellingOptimizerStatus {
+    NOT_RUNNING(0x00),
+    WEARING_CONDITION(0x01),
+    ATMOSPHERIC_PRESSURE(0x02),
+    ANALYZING(0x10),
+    FINISHED(0x11),
+    ;
+
+    private final byte code;
+
+    NoiseCancellingOptimizerStatus(final int code) {
+        this.code = (byte) code;
+    }
+
+    public byte getCode() {
+        return this.code;
+    }
+
+    public String i18n(final Context context) {
+        final String stringName = String.format("sony_anc_optimizer_status_%s", name().toLowerCase(Locale.ROOT));
+        final int stringId = context.getResources().getIdentifier(stringName, "string", context.getPackageName());
+        return context.getString(stringId);
+    }
+
+    public static NoiseCancellingOptimizerStatus fromCode(final byte code) {
+        for (final NoiseCancellingOptimizerStatus audioCodec : values()) {
+            if (audioCodec.code == code) {
+                return audioCodec;
+            }
+        }
+
+        return null;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/headphones/protocol/impl/v1/params/VirtualSoundParam.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/headphones/protocol/impl/v1/params/VirtualSoundParam.java
new file mode 100644
index 0000000..8bf6540
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/headphones/protocol/impl/v1/params/VirtualSoundParam.java
@@ -0,0 +1,43 @@
+/*  Copyright (C) 2021-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.impl.v1.params;
+
+public enum VirtualSoundParam {
+    SURROUND_MODE(0x01),
+    SOUND_POSITION(0x02),
+    ;
+
+    private final byte code;
+
+    VirtualSoundParam(final int code) {
+        this.code = (byte) code;
+    }
+
+    public byte getCode() {
+        return this.code;
+    }
+
+    public static VirtualSoundParam fromCode(final byte code) {
+        for (final VirtualSoundParam virtualSound : values()) {
+            if (virtualSound.code == code) {
+                return virtualSound;
+            }
+        }
+
+        return null;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/headphones/protocol/impl/v2/PayloadTypeV2.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/headphones/protocol/impl/v2/PayloadTypeV2.java
new file mode 100644
index 0000000..fac7b01
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/headphones/protocol/impl/v2/PayloadTypeV2.java
@@ -0,0 +1,68 @@
+/*  Copyright (C) 2022-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.impl.v2;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.MessageType;
+
+public enum PayloadTypeV2 {
+    AUDIO_CODEC_REQUEST(MessageType.COMMAND_1, 0x12),
+    AUDIO_CODEC_REPLY(MessageType.COMMAND_1, 0x13),
+    AUDIO_CODEC_NOTIFY(MessageType.COMMAND_1, 0x15),
+
+    BATTERY_LEVEL_REQUEST(MessageType.COMMAND_1, 0x22),
+    BATTERY_LEVEL_REPLY(MessageType.COMMAND_1, 0x23),
+    POWER_SET(MessageType.COMMAND_1, 0x24),
+    BATTERY_LEVEL_NOTIFY(MessageType.COMMAND_1, 0x25),
+
+    AUTOMATIC_POWER_OFF_GET(MessageType.COMMAND_1, 0x26),
+    AUTOMATIC_POWER_OFF_RET(MessageType.COMMAND_1, 0x27),
+    AUTOMATIC_POWER_OFF_SET(MessageType.COMMAND_1, 0x28),
+    AUTOMATIC_POWER_OFF_NOTIFY(MessageType.COMMAND_1, 0x29),
+
+    AMBIENT_SOUND_CONTROL_BUTTON_MODE_GET(MessageType.COMMAND_1, 0xfa),
+    AMBIENT_SOUND_CONTROL_BUTTON_MODE_RET(MessageType.COMMAND_1, 0xfb),
+    AMBIENT_SOUND_CONTROL_BUTTON_MODE_SET(MessageType.COMMAND_1, 0xfc),
+    AMBIENT_SOUND_CONTROL_BUTTON_MODE_NOTIFY(MessageType.COMMAND_1, 0xfd),
+
+    UNKNOWN(MessageType.UNKNOWN, 0xff);
+
+    private final MessageType messageType;
+    private final byte code;
+
+    PayloadTypeV2(final MessageType messageType, final int code) {
+        this.messageType = messageType;
+        this.code = (byte) code;
+    }
+
+    public MessageType getMessageType() {
+        return this.messageType;
+    }
+
+    public byte getCode() {
+        return this.code;
+    }
+
+    public static PayloadTypeV2 fromCode(final MessageType messageType, final byte code) {
+        for (final PayloadTypeV2 payloadType : values()) {
+            if (messageType.equals(payloadType.messageType) && payloadType.code == code) {
+                return payloadType;
+            }
+        }
+
+        return PayloadTypeV2.UNKNOWN;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/headphones/protocol/impl/v2/SonyProtocolImplV2.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/headphones/protocol/impl/v2/SonyProtocolImplV2.java
new file mode 100644
index 0000000..0dcc8bd
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/headphones/protocol/impl/v2/SonyProtocolImplV2.java
@@ -0,0 +1,1090 @@
+/*  Copyright (C) 2022-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.impl.v2;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdateDeviceInfo;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdatePreferences;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.SonyHeadphonesCapabilities;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.AdaptiveVolumeControl;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.AmbientSoundControl;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.AmbientSoundControlButtonMode;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.AudioUpsampling;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.AutomaticPowerOff;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.ButtonModes;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.EqualizerCustomBands;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.EqualizerPreset;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.PauseWhenTakenOff;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.QuickAccess;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.SoundPosition;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.SpeakToChatConfig;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.SpeakToChatEnabled;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.SurroundMode;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.TouchSensor;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.VoiceNotifications;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.WideAreaTap;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.MessageType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.Request;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.impl.v1.PayloadTypeV1;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.impl.v1.SonyProtocolImplV1;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.impl.v1.params.AudioCodec;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.impl.v1.params.BatteryType;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class SonyProtocolImplV2 extends SonyProtocolImplV1 {
+    private static final Logger LOG = LoggerFactory.getLogger(SonyProtocolImplV2.class);
+
+    public SonyProtocolImplV2(final GBDevice device) {
+        super(device);
+    }
+
+    @Override
+    public Request getAmbientSoundControl() {
+        return new Request(
+                PayloadTypeV1.AMBIENT_SOUND_CONTROL_GET.getMessageType(),
+                new byte[]{
+                        PayloadTypeV1.AMBIENT_SOUND_CONTROL_GET.getCode(),
+                        (byte) (supportsWindNoiseCancelling() || getCoordinator().supports(SonyHeadphonesCapabilities.AmbientSoundControl2) ? 0x17 : 0x15)
+                }
+        );
+    }
+
+    @Override
+    public Request setAmbientSoundControl(final AmbientSoundControl ambientSoundControl) {
+        final ByteBuffer buf = ByteBuffer.allocate(supportsWindNoiseCancelling() ? 8 : 7);
+
+        buf.put(PayloadTypeV1.AMBIENT_SOUND_CONTROL_SET.getCode());
+        buf.put((byte) (supportsWindNoiseCancelling() || getCoordinator().supports(SonyHeadphonesCapabilities.AmbientSoundControl2) ? 0x17 : 0x15));
+        buf.put((byte) 0x01); // 0x00 while dragging the slider?
+
+        if (AmbientSoundControl.Mode.OFF.equals(ambientSoundControl.getMode())) {
+            buf.put((byte) 0x00);
+        } else {
+            buf.put((byte) 0x01);
+        }
+
+        if (AmbientSoundControl.Mode.AMBIENT_SOUND.equals(ambientSoundControl.getMode())) {
+            buf.put((byte) 0x01);
+        } else {
+            buf.put((byte) 0x00);
+        }
+
+        if (supportsWindNoiseCancelling()) {
+            if (AmbientSoundControl.Mode.WIND_NOISE_REDUCTION.equals(ambientSoundControl.getMode())) {
+                buf.put((byte) 0x03);
+            } else {
+                buf.put((byte) 0x02);
+            }
+        }
+
+        buf.put((byte) (ambientSoundControl.isFocusOnVoice() ? 0x01 : 0x00));
+        buf.put((byte) (ambientSoundControl.getAmbientSound()));
+
+        return new Request(PayloadTypeV1.AMBIENT_SOUND_CONTROL_SET.getMessageType(), buf.array());
+    }
+
+    @Override
+    public Request setAdaptiveVolumeControl(final AdaptiveVolumeControl config) {
+        return new Request(
+                PayloadTypeV1.AUTOMATIC_POWER_OFF_BUTTON_MODE_SET.getMessageType(),
+                new byte[]{
+                        PayloadTypeV1.AUTOMATIC_POWER_OFF_BUTTON_MODE_SET.getCode(),
+                        (byte) 0x0a,
+                        (byte) (config.isEnabled() ? 0x00 : 0x01) // this is reversed on V2...?
+                }
+        );
+    }
+
+    @Override
+    public Request getAdaptiveVolumeControl() {
+        return new Request(
+                PayloadTypeV1.AUTOMATIC_POWER_OFF_BUTTON_MODE_GET.getMessageType(),
+                new byte[]{
+                        PayloadTypeV1.AUTOMATIC_POWER_OFF_BUTTON_MODE_GET.getCode(),
+                        (byte) 0x0a
+                }
+        );
+    }
+
+    @Override
+    public Request setSpeakToChatEnabled(SpeakToChatEnabled config) {
+        return new Request(
+                PayloadTypeV1.AUTOMATIC_POWER_OFF_BUTTON_MODE_SET.getMessageType(),
+                new byte[]{
+                        PayloadTypeV1.AUTOMATIC_POWER_OFF_BUTTON_MODE_SET.getCode(),
+                        (byte) 0x0c,
+                        (byte) (config.isEnabled() ? 0x00 : 0x01), // TODO it's reversed?
+                        (byte) 0x01
+                }
+        );
+    }
+
+    @Override
+    public Request getSpeakToChatEnabled() {
+        return new Request(
+                PayloadTypeV1.AUTOMATIC_POWER_OFF_BUTTON_MODE_GET.getMessageType(),
+                new byte[]{
+                        PayloadTypeV1.AUTOMATIC_POWER_OFF_BUTTON_MODE_GET.getCode(),
+                        (byte) 0x0c
+                }
+        );
+    }
+
+    @Override
+    public Request setSpeakToChatConfig(SpeakToChatConfig config) {
+        return new Request(
+                PayloadTypeV1.SPEAK_TO_CHAT_CONFIG_SET.getMessageType(),
+                new byte[]{
+                        PayloadTypeV1.SPEAK_TO_CHAT_CONFIG_SET.getCode(),
+                        (byte) 0x0c,
+                        config.getSensitivity().getCode(),
+                        config.getTimeout().getCode()
+                }
+        );
+    }
+
+    @Override
+    public Request getSpeakToChatConfig() {
+        return new Request(
+                PayloadTypeV1.SPEAK_TO_CHAT_CONFIG_GET.getMessageType(),
+                new byte[]{
+                        PayloadTypeV1.SPEAK_TO_CHAT_CONFIG_GET.getCode(),
+                        (byte) 0x0c
+                }
+        );
+    }
+
+    @Override
+    public Request getNoiseCancellingOptimizerState() {
+        LOG.warn("Noise cancelling optimizer not implemented for V2");
+        return null;
+    }
+
+    @Override
+    public Request getAudioCodec() {
+        return new Request(
+                PayloadTypeV2.AUDIO_CODEC_REQUEST.getMessageType(),
+                new byte[]{
+                        PayloadTypeV2.AUDIO_CODEC_REQUEST.getCode(),
+                        (byte) 0x02
+                }
+        );
+    }
+
+    @Override
+    public Request getBattery(final BatteryType batteryType) {
+        return new Request(
+                PayloadTypeV2.BATTERY_LEVEL_REQUEST.getMessageType(),
+                new byte[]{
+                        PayloadTypeV2.BATTERY_LEVEL_REQUEST.getCode(),
+                        encodeBatteryType(batteryType)
+                }
+        );
+    }
+
+    @Override
+    public Request getFirmwareVersion() {
+        return super.getFirmwareVersion();
+    }
+
+    @Override
+    public Request getAudioUpsampling() {
+        return new Request(
+                PayloadTypeV1.AUDIO_UPSAMPLING_GET.getMessageType(),
+                new byte[]{
+                        PayloadTypeV1.AUDIO_UPSAMPLING_GET.getCode(),
+                        (byte) 0x01
+                }
+        );
+    }
+
+    @Override
+    public Request setAudioUpsampling(final AudioUpsampling config) {
+        return new Request(
+                PayloadTypeV1.AUDIO_UPSAMPLING_SET.getMessageType(),
+                new byte[]{
+                        PayloadTypeV1.AUDIO_UPSAMPLING_SET.getCode(),
+                        (byte) 0x01,
+                        (byte) (config.isEnabled() ? 0x01 : 0x00)
+                }
+        );
+    }
+
+    @Override
+    public Request getAutomaticPowerOff() {
+        return new Request(
+                PayloadTypeV2.AUTOMATIC_POWER_OFF_GET.getMessageType(),
+                new byte[]{
+                        PayloadTypeV2.AUTOMATIC_POWER_OFF_GET.getCode(),
+                        (byte) 0x05
+                }
+        );
+    }
+
+    @Override
+    public Request setAutomaticPowerOff(final AutomaticPowerOff config) {
+        return new Request(
+                PayloadTypeV2.AUTOMATIC_POWER_OFF_SET.getMessageType(),
+                new byte[]{
+                        PayloadTypeV2.AUTOMATIC_POWER_OFF_SET.getCode(),
+                        (byte) 0x05,
+                        config.getCode()[0],
+                        config.getCode()[1]
+                }
+        );
+    }
+
+    @Override
+    public Request setWideAreaTap(final WideAreaTap config) {
+        return new Request(
+                PayloadTypeV1.TOUCH_SENSOR_SET.getMessageType(),
+                new byte[]{
+                        PayloadTypeV1.TOUCH_SENSOR_SET.getCode(),
+                        (byte) 0xd1,
+                        (byte) 0x00,
+                        (byte) (config.isEnabled() ? 0x00 : 0x01) // this is reversed on V2...?
+                }
+        );
+    }
+
+    @Override
+    public Request getWideAreaTap() {
+        return new Request(
+                PayloadTypeV1.TOUCH_SENSOR_GET.getMessageType(),
+                new byte[]{
+                        PayloadTypeV1.TOUCH_SENSOR_GET.getCode(),
+                        (byte) 0xd1
+                }
+        );
+    }
+
+    @Override
+    public Request getButtonModes() {
+        return new Request(
+                PayloadTypeV1.AUTOMATIC_POWER_OFF_BUTTON_MODE_GET.getMessageType(),
+                new byte[]{
+                        PayloadTypeV1.AUTOMATIC_POWER_OFF_BUTTON_MODE_GET.getCode(),
+                        (byte) 0x03
+                }
+        );
+    }
+
+    @Override
+    public Request setButtonModes(final ButtonModes config) {
+        return new Request(
+                PayloadTypeV1.AUTOMATIC_POWER_OFF_BUTTON_MODE_SET.getMessageType(),
+                new byte[]{
+                        PayloadTypeV1.AUTOMATIC_POWER_OFF_BUTTON_MODE_SET.getCode(),
+                        (byte) 0x03,
+                        (byte) 0x02,
+                        encodeButtonMode(config.getModeLeft()),
+                        encodeButtonMode(config.getModeRight())
+                }
+        );
+    }
+
+    @Override
+    public Request getQuickAccess() {
+        return new Request(
+                PayloadTypeV1.AUTOMATIC_POWER_OFF_BUTTON_MODE_GET.getMessageType(),
+                new byte[]{
+                        PayloadTypeV1.AUTOMATIC_POWER_OFF_BUTTON_MODE_GET.getCode(),
+                        (byte) 0x0d
+                }
+        );
+    }
+
+    @Override
+    public Request setQuickAccess(final QuickAccess quickAccess) {
+        return new Request(
+                PayloadTypeV1.AUTOMATIC_POWER_OFF_BUTTON_MODE_SET.getMessageType(),
+                new byte[]{
+                        PayloadTypeV1.AUTOMATIC_POWER_OFF_BUTTON_MODE_SET.getCode(),
+                        (byte) 0x0d,
+                        (byte) 0x02,
+                        quickAccess.getModeDoubleTap().getCode(),
+                        quickAccess.getModeTripleTap().getCode()
+                }
+        );
+    }
+
+    @Override
+    public Request getAmbientSoundControlButtonMode() {
+        return new Request(
+                PayloadTypeV2.AMBIENT_SOUND_CONTROL_BUTTON_MODE_GET.getMessageType(),
+                new byte[]{
+                        PayloadTypeV2.AMBIENT_SOUND_CONTROL_BUTTON_MODE_GET.getCode(),
+                        (byte) 0x03
+                }
+        );
+    }
+
+    @Override
+    public Request setAmbientSoundControlButtonMode(final AmbientSoundControlButtonMode ambientSoundControlButtonMode) {
+        return new Request(
+                PayloadTypeV2.AMBIENT_SOUND_CONTROL_BUTTON_MODE_SET.getMessageType(),
+                new byte[]{
+                        PayloadTypeV2.AMBIENT_SOUND_CONTROL_BUTTON_MODE_SET.getCode(),
+                        (byte) 0x03,
+                        (byte) 0x01,
+                        (byte) (getCoordinator().supports(SonyHeadphonesCapabilities.AmbientSoundControl2) ? 0x00 : 0x35),
+                        (byte) 0x01,
+                        (byte) 0x00,
+                        ambientSoundControlButtonMode.getCode()
+                }
+        );
+    }
+
+    @Override
+    public Request getPauseWhenTakenOff() {
+        return new Request(
+                PayloadTypeV2.AUTOMATIC_POWER_OFF_GET.getMessageType(),
+                new byte[]{
+                        PayloadTypeV2.AUTOMATIC_POWER_OFF_GET.getCode(),
+                        (byte) 0x01
+                }
+        );
+    }
+
+    @Override
+    public Request setPauseWhenTakenOff(final PauseWhenTakenOff config) {
+        return new Request(
+                PayloadTypeV1.AUTOMATIC_POWER_OFF_BUTTON_MODE_SET.getMessageType(),
+                new byte[]{
+                        PayloadTypeV1.AUTOMATIC_POWER_OFF_BUTTON_MODE_SET.getCode(),
+                        (byte) 0x01,
+                        (byte) (config.isEnabled() ? 0x00 : 0x01) // this is reversed on V2...?
+                }
+        );
+    }
+
+    @Override
+    public Request getEqualizer() {
+        return new Request(
+                PayloadTypeV1.EQUALIZER_GET.getMessageType(),
+                new byte[]{
+                        PayloadTypeV1.EQUALIZER_GET.getCode(),
+                        (byte) 0x00
+                }
+        );
+    }
+
+    @Override
+    public Request setEqualizerPreset(final EqualizerPreset config) {
+        return new Request(
+                PayloadTypeV1.EQUALIZER_SET.getMessageType(),
+                new byte[]{
+                        PayloadTypeV1.EQUALIZER_SET.getCode(),
+                        (byte) 0x00,
+                        config.getCode(),
+                        (byte) 0x00
+                }
+        );
+    }
+
+    @Override
+    public Request setEqualizerCustomBands(final EqualizerCustomBands config) {
+        final ByteBuffer buf = ByteBuffer.allocate(10);
+
+        buf.put(PayloadTypeV1.EQUALIZER_SET.getCode());
+        buf.put((byte) 0x00);
+        buf.put((byte) 0xa0);
+        buf.put((byte) 0x06);
+
+        buf.put((byte) (config.getBass() + 10));
+        for (final Integer band : config.getBands()) {
+            buf.put((byte) (band + 10));
+        }
+
+        return new Request(
+                PayloadTypeV1.EQUALIZER_SET.getMessageType(),
+                buf.array()
+        );
+    }
+
+    @Override
+    public Request getSoundPosition() {
+        LOG.warn("Sound position not implemented for V2");
+        return null;
+    }
+
+    @Override
+    public Request setSoundPosition(final SoundPosition config) {
+        LOG.warn("Sound position not implemented for V2");
+        return null;
+    }
+
+    @Override
+    public Request getSurroundMode() {
+        LOG.warn("Surround mode not implemented for V2");
+        return null;
+    }
+
+    @Override
+    public Request setSurroundMode(final SurroundMode config) {
+        LOG.warn("Surround mode not implemented for V2");
+        return null;
+    }
+
+    @Override
+    public Request getTouchSensor() {
+        LOG.warn("Touch sensor not implemented for V2");
+        return null;
+    }
+
+    @Override
+    public Request setTouchSensor(final TouchSensor config) {
+        LOG.warn("Touch sensor not implemented for V2");
+        return null;
+    }
+
+    @Override
+    public Request getVoiceNotifications() {
+        return new Request(
+                PayloadTypeV1.VOICE_NOTIFICATIONS_GET.getMessageType(),
+                new byte[]{
+                        PayloadTypeV1.VOICE_NOTIFICATIONS_GET.getCode(),
+                        (byte) 0x01
+                }
+        );
+    }
+
+    @Override
+    public Request setVoiceNotifications(final VoiceNotifications config) {
+        return new Request(
+                PayloadTypeV1.VOICE_NOTIFICATIONS_SET.getMessageType(),
+                new byte[]{
+                        PayloadTypeV1.VOICE_NOTIFICATIONS_SET.getCode(),
+                        (byte) 0x01,
+                        (byte) (config.isEnabled() ? 0x00 : 0x01)  // reversed?
+                }
+        );
+    }
+
+    @Override
+    public Request startNoiseCancellingOptimizer(final boolean start) {
+        LOG.warn("Noise cancelling optimizer not implemented for V2");
+        return null;
+    }
+
+    @Override
+    public Request powerOff() {
+        return new Request(
+                PayloadTypeV2.POWER_SET.getMessageType(),
+                new byte[]{
+                        PayloadTypeV2.POWER_SET.getCode(),
+                        (byte) 0x03,
+                        (byte) 0x01
+                }
+        );
+    }
+
+    @Override
+    public Request getVolume() {
+        LOG.warn("Volume not implemented for V2");
+        return null;
+    }
+
+    @Override
+    public Request setVolume(final int volume) {
+        LOG.warn("Volume not implemented for V2");
+        return null;
+    }
+
+    @Override
+    public List<? extends GBDeviceEvent> handlePayload(final MessageType messageType, final byte[] payload) {
+        final PayloadTypeV2 payloadType = PayloadTypeV2.fromCode(messageType, payload[0]);
+
+        switch (payloadType) {
+            case AUDIO_CODEC_REPLY:
+            case AUDIO_CODEC_NOTIFY:
+                return handleAudioCodec(payload);
+            case BATTERY_LEVEL_NOTIFY:
+            case BATTERY_LEVEL_REPLY:
+                return handleBattery(payload);
+            case AUTOMATIC_POWER_OFF_RET:
+            case AUTOMATIC_POWER_OFF_NOTIFY:
+                return handleAutomaticPowerOff(payload);
+            case AMBIENT_SOUND_CONTROL_BUTTON_MODE_RET:
+            case AMBIENT_SOUND_CONTROL_BUTTON_MODE_NOTIFY:
+                return handleAmbientSoundControlButtonMode(payload);
+        }
+
+        return super.handlePayload(messageType, payload);
+    }
+
+    @Override
+    public List<? extends GBDeviceEvent> handleInitResponse(final byte[] payload) {
+        return super.handleInitResponse(payload);
+    }
+
+    @Override
+    public List<? extends GBDeviceEvent> handleAmbientSoundControl(final byte[] payload) {
+        if (payload.length != 8 && payload.length != 7) {
+            LOG.warn("Unexpected payload length {}", payload.length);
+            return Collections.emptyList();
+        }
+
+        if (payload[1] != 0x15 && payload[1] != 0x17) {
+            LOG.warn("Not ambient sound control, ignoring {}", payload[1]);
+            return Collections.emptyList();
+        }
+
+        final boolean includesWindNoiseReduction = payload[1] == 0x17 && payload.length > 7;
+
+        AmbientSoundControl.Mode mode = null;
+
+        if (payload[3] == (byte) 0x00) {
+            mode = AmbientSoundControl.Mode.OFF;
+        } else if (payload[3] == (byte) 0x01) {
+            // Enabled, determine mode
+
+            if (includesWindNoiseReduction) {
+                if (payload[5] == 0x03 || payload[5] == 0x05) {
+                    mode = AmbientSoundControl.Mode.WIND_NOISE_REDUCTION;
+                } else if (payload[5] == 0x02) {
+                    if (payload[4] == (byte) 0x00) {
+                        mode = AmbientSoundControl.Mode.NOISE_CANCELLING;
+                    } else if (payload[4] == (byte) 0x01) {
+                        mode = AmbientSoundControl.Mode.AMBIENT_SOUND;
+                    }
+                }
+            } else {
+                if (payload[4] == (byte) 0x00) {
+                    mode = AmbientSoundControl.Mode.NOISE_CANCELLING;
+                } else if (payload[4] == (byte) 0x01) {
+                    mode = AmbientSoundControl.Mode.AMBIENT_SOUND;
+                }
+            }
+        }
+
+        if (mode == null) {
+            LOG.warn("Unable to determine ambient sound control mode from {}", GB.hexdump(payload));
+            return Collections.emptyList();
+        }
+
+        int i = includesWindNoiseReduction ? 6 : 5;
+        final Boolean focusOnVoice = booleanFromByte(payload[i]);
+        if (focusOnVoice == null) {
+            LOG.warn("Unknown focus on voice mode {}", String.format("%02x", payload[i]));
+            return Collections.emptyList();
+        }
+
+        i++;
+        int ambientSound = payload[i];
+        if (ambientSound < 0 || ambientSound > 20) {
+            LOG.warn("Ambient sound level {} is out of range", String.format("%02x", payload[i]));
+            return Collections.emptyList();
+        }
+
+        final AmbientSoundControl ambientSoundControl = new AmbientSoundControl(mode, focusOnVoice, ambientSound);
+
+        LOG.debug("Ambient sound control: {}", ambientSoundControl);
+
+        final GBDeviceEventUpdatePreferences eventUpdatePreferences = new GBDeviceEventUpdatePreferences()
+                .withPreferences(ambientSoundControl.toPreferences());
+
+        return Collections.singletonList(eventUpdatePreferences);
+    }
+
+    @Override
+    public List<? extends GBDeviceEvent> handleNoiseCancellingOptimizerStatus(final byte[] payload) {
+        LOG.warn("Touch sensor not implemented for V2");
+        return Collections.emptyList();
+    }
+
+    @Override
+    public List<? extends GBDeviceEvent> handleNoiseCancellingOptimizerState(final byte[] payload) {
+        LOG.warn("Touch sensor not implemented for V2");
+        return Collections.emptyList();
+    }
+
+
+    @Override
+    public List<? extends GBDeviceEvent> handleAudioUpsampling(final byte[] payload) {
+        if (payload.length != 3) {
+            LOG.warn("Unexpected payload length {}", payload.length);
+            return Collections.emptyList();
+        }
+
+        if (payload[1] != 0x01) {
+            LOG.warn("Not audio upsampling, ignoring {}", payload[1]);
+            return Collections.emptyList();
+        }
+
+        final Boolean enabled = booleanFromByte(payload[2]);
+        if (enabled == null) {
+            LOG.warn("Unknown audio upsampling code {}", String.format("%02x", payload[2]));
+            return Collections.emptyList();
+        }
+
+        LOG.debug("Audio Upsampling: {}", enabled);
+
+        final GBDeviceEventUpdatePreferences event = new GBDeviceEventUpdatePreferences()
+                .withPreferences(new AudioUpsampling(enabled).toPreferences());
+
+        return Collections.singletonList(event);
+    }
+
+    @Override
+    public List<? extends GBDeviceEvent> handleAutomaticPowerOff(final byte[] payload) {
+        if (payload.length != 4) {
+            LOG.warn("Unexpected payload length {}", payload.length);
+            return Collections.emptyList();
+        }
+
+        if (payload[1] != 0x05) {
+            LOG.warn("Not automatic power off config, ignoring");
+            return Collections.emptyList();
+        }
+
+        final AutomaticPowerOff mode = AutomaticPowerOff.fromCode(payload[2], payload[3]);
+        if (mode == null) {
+            LOG.warn("Unknown automatic power off codes {}", String.format("%02x %02x", payload[3], payload[4]));
+            return Collections.emptyList();
+        }
+
+        LOG.debug("Automatic Power Off: {}", mode);
+
+        final GBDeviceEventUpdatePreferences event = new GBDeviceEventUpdatePreferences()
+                .withPreferences(mode.toPreferences());
+
+        return Collections.singletonList(event);
+    }
+
+    public List<? extends GBDeviceEvent> handleAdaptiveVolumeControl(final byte[] payload) {
+        if (payload.length != 3) {
+            LOG.warn("Unexpected payload length {}", payload.length);
+            return Collections.emptyList();
+        }
+
+        if (payload[1] != 0x0a) {
+            LOG.warn("Not adaptive volume control, ignoring");
+            return Collections.emptyList();
+        }
+
+        final Boolean disabled = booleanFromByte(payload[2]);
+        if (disabled == null) {
+            LOG.warn("Unknown adaptive volume control code {}", String.format("%02x", payload[2]));
+            return Collections.emptyList();
+        }
+
+        LOG.debug("Adaptive volume control: {}", !disabled);
+
+        final GBDeviceEventUpdatePreferences event = new GBDeviceEventUpdatePreferences()
+                .withPreferences(new AdaptiveVolumeControl(!disabled).toPreferences());
+
+        return Collections.singletonList(event);
+    }
+
+    @Override
+    public List<? extends GBDeviceEvent> handleSpeakToChatEnabled(final byte[] payload) {
+        if (payload.length != 4) {
+            LOG.warn("Unexpected payload length {}", payload.length);
+            return Collections.emptyList();
+        }
+
+        if (payload[1] != 0x0c) {
+            LOG.warn("Not speak to chat enabled, ignoring");
+            return Collections.emptyList();
+        }
+
+        final Boolean disabled = booleanFromByte(payload[2]);
+        if (disabled == null) {
+            LOG.warn("Unknown speak to chat enabled code {}", String.format("%02x", payload[2]));
+            return Collections.emptyList();
+        }
+
+        LOG.debug("Speak to chat: {}", !disabled);
+
+        final GBDeviceEventUpdatePreferences event = new GBDeviceEventUpdatePreferences()
+                .withPreferences(new SpeakToChatEnabled(!disabled).toPreferences());
+
+        return Collections.singletonList(event);
+    }
+
+    @Override
+    public List<? extends GBDeviceEvent> handleSpeakToChatConfig(final byte[] payload) {
+        if (payload.length != 4) {
+            LOG.warn("Unexpected payload length {}", payload.length);
+            return Collections.emptyList();
+        }
+
+        if (payload[1] != 0x0c) {
+            LOG.warn("Not speak to chat config, ignoring");
+            return Collections.emptyList();
+        }
+
+        final SpeakToChatConfig.Sensitivity sensitivity = SpeakToChatConfig.Sensitivity.fromCode(payload[2]);
+        if (sensitivity == null) {
+            LOG.warn("Unknown sensitivity code {}", String.format("%02x", payload[2]));
+            return Collections.emptyList();
+        }
+
+        final SpeakToChatConfig.Timeout timeout = SpeakToChatConfig.Timeout.fromCode(payload[3]);
+        if (timeout == null) {
+            LOG.warn("Unknown timeout code {}", String.format("%02x", payload[3]));
+            return Collections.emptyList();
+        }
+
+        final SpeakToChatConfig speakToChatConfig = new SpeakToChatConfig(false, sensitivity, timeout);
+
+        LOG.debug("Speak to chat config: {}", speakToChatConfig);
+
+        final GBDeviceEventUpdatePreferences event = new GBDeviceEventUpdatePreferences()
+                .withPreferences(speakToChatConfig.toPreferences());
+
+        return Collections.singletonList(event);
+    }
+
+    public List<? extends GBDeviceEvent> handleQuickAccess(final byte[] payload) {
+        if (payload.length != 5) {
+            LOG.warn("Unexpected payload length {}", payload.length);
+            return Collections.emptyList();
+        }
+
+        if (payload[1] != 0x0d || payload[2] != 0x02) {
+            LOG.warn("Unexpected quick access payload bytes {}", String.format("%02x %02x", payload[1], payload[2]));
+            return Collections.emptyList();
+        }
+
+        final QuickAccess.Mode modeDouble = QuickAccess.Mode.fromCode(payload[3]);
+        final QuickAccess.Mode modeTriple = QuickAccess.Mode.fromCode(payload[4]);
+        if (modeDouble == null || modeTriple == null) {
+            LOG.warn("Unknown quick access codes {}", String.format("%02x %02x", payload[3], payload[4]));
+            return Collections.emptyList();
+        }
+
+        LOG.debug("Quick Access: Double Tap: {}, Triple Tap: {}", modeDouble, modeTriple);
+
+        final GBDeviceEventUpdatePreferences event = new GBDeviceEventUpdatePreferences()
+                .withPreferences(new QuickAccess(modeDouble, modeTriple).toPreferences());
+
+        return Collections.singletonList(event);
+    }
+
+    public List<? extends GBDeviceEvent> handleAmbientSoundControlButtonMode(final byte[] payload) {
+        if (payload.length == 4 && payload[1] == 0x0c) {
+            // FIXME split this
+            return handleSpeakToChatConfig(payload);
+        }
+
+        if (payload.length != 7) {
+            LOG.warn("Unexpected payload length {}", payload.length);
+            return Collections.emptyList();
+        }
+
+        if (payload[1] != 0x03 || payload[2] != 0x01 || (payload[3] != 0x00 && payload[3] != 0x35) || payload[4] != 0x01 || payload[5] != 0x00) {
+            LOG.warn(
+                    "Unexpected ambient sound control button mode payload bytes {}",
+                    String.format("%02x %02x %02x %02x %02x", payload[1], payload[2], payload[3], payload[4], payload[5])
+            );
+            return Collections.emptyList();
+        }
+
+        final AmbientSoundControlButtonMode mode = AmbientSoundControlButtonMode.fromCode(payload[6]);
+        if (mode == null) {
+            LOG.warn("Unknown ambient sound control button mode code {}", String.format("%02x", payload[6]));
+            return Collections.emptyList();
+        }
+
+        LOG.debug("Ambient Sound Control Button Mode: {}", mode);
+
+        final GBDeviceEventUpdatePreferences event = new GBDeviceEventUpdatePreferences()
+                .withPreferences(mode.toPreferences());
+
+        return Collections.singletonList(event);
+    }
+
+    @Override
+    public List<? extends GBDeviceEvent> handleButtonModes(final byte[] payload) {
+        if (payload.length != 5) {
+            LOG.warn("Unexpected payload length {}", payload.length);
+            return Collections.emptyList();
+        }
+
+        if (payload[1] != 0x03) {
+            LOG.warn("Not button mode config, ignoring");
+            return Collections.emptyList();
+        }
+
+        final ButtonModes.Mode modeLeft = decodeButtonMode(payload[3]);
+        final ButtonModes.Mode modeRight = decodeButtonMode(payload[4]);
+
+        if (modeLeft == null || modeRight == null) {
+            LOG.warn("Unknown button mode codes {}", String.format("%02x %02x", payload[3], payload[4]));
+            return Collections.emptyList();
+        }
+
+        LOG.debug("Button Modes: L: {}, R: {}", modeLeft, modeRight);
+
+        final GBDeviceEventUpdatePreferences event = new GBDeviceEventUpdatePreferences()
+                .withPreferences(new ButtonModes(modeLeft, modeRight).toPreferences());
+
+        return Collections.singletonList(event);
+    }
+
+    @Override
+    public List<? extends GBDeviceEvent> handlePauseWhenTakenOff(final byte[] payload) {
+        if (payload.length != 3) {
+            LOG.warn("Unexpected payload length {}", payload.length);
+            return Collections.emptyList();
+        }
+
+        if (payload[1] != 0x01) {
+            LOG.warn("Not pause when taken off, ignoring");
+            return Collections.emptyList();
+        }
+
+        final Boolean disabled = booleanFromByte(payload[2]);
+        if (disabled == null) {
+            LOG.warn("Unknown pause when taken off code {}", String.format("%02x", payload[2]));
+            return Collections.emptyList();
+        }
+
+        LOG.debug("Pause when taken off: {}", !disabled);
+
+        final GBDeviceEventUpdatePreferences event = new GBDeviceEventUpdatePreferences()
+                .withPreferences(new PauseWhenTakenOff(!disabled).toPreferences());
+
+        return Collections.singletonList(event);
+    }
+
+    @Override
+    public List<? extends GBDeviceEvent> handleBattery(final byte[] payload) {
+        return super.handleBattery(payload);
+    }
+
+    @Override
+    public List<? extends GBDeviceEvent> handleAudioCodec(final byte[] payload) {
+        if (payload.length != 3) {
+            LOG.warn("Unexpected payload length {}", payload.length);
+            return Collections.emptyList();
+        }
+
+        if (payload[1] != 0x02) {
+            LOG.warn("Not audio codec, ignoring");
+            return Collections.emptyList();
+        }
+
+        final AudioCodec audioCodec = AudioCodec.fromCode(payload[2]);
+        if (audioCodec == null) {
+            LOG.warn("Unable to determine audio codec from {}", GB.hexdump(payload));
+            return Collections.emptyList();
+        }
+
+        final GBDeviceEventUpdateDeviceInfo gbDeviceEventUpdateDeviceInfo = new GBDeviceEventUpdateDeviceInfo("AUDIO_CODEC: ", audioCodec.name());
+
+        final GBDeviceEventUpdatePreferences gbDeviceEventUpdatePreferences = new GBDeviceEventUpdatePreferences()
+                .withPreference(DeviceSettingsPreferenceConst.PREF_SONY_AUDIO_CODEC, audioCodec.name().toLowerCase(Locale.getDefault()));
+
+        return Arrays.asList(gbDeviceEventUpdateDeviceInfo, gbDeviceEventUpdatePreferences);
+    }
+
+    @Override
+    public List<? extends GBDeviceEvent> handleEqualizer(final byte[] payload) {
+        return super.handleEqualizer(payload);
+    }
+
+    @Override
+    public List<? extends GBDeviceEvent> handleFirmwareVersion(final byte[] payload) {
+        return super.handleFirmwareVersion(payload);
+    }
+
+    @Override
+    public List<? extends GBDeviceEvent> handleJson(final byte[] payload) {
+        LOG.warn("JSON not implemented for V2");
+        return Collections.emptyList();
+    }
+
+    @Override
+    public List<? extends GBDeviceEvent> handleAutomaticPowerOffButtonMode(final byte[] payload) {
+        switch (payload[1]) {
+            case 0x01:
+                return handlePauseWhenTakenOff(payload);
+            case 0x03:
+                return handleButtonModes(payload);
+            case 0x0a:
+                return handleAdaptiveVolumeControl(payload);
+            case 0x0c:
+                return handleSpeakToChatEnabled(payload);
+            case 0x0d:
+                return handleQuickAccess(payload);
+        }
+
+        return Collections.emptyList();
+    }
+
+    @Override
+    public List<? extends GBDeviceEvent> handleVirtualSound(final byte[] payload) {
+        LOG.warn("Virtual sound not implemented for V2");
+        return Collections.emptyList();
+    }
+
+    @Override
+    public List<? extends GBDeviceEvent> handleSoundPosition(final byte[] payload) {
+        LOG.warn("Sound position not implemented for V2");
+        return Collections.emptyList();
+    }
+
+    @Override
+    public List<? extends GBDeviceEvent> handleSurroundMode(final byte[] payload) {
+        LOG.warn("Surround mode not implemented for V2");
+        return Collections.emptyList();
+    }
+
+    @Override
+    public List<? extends GBDeviceEvent> handleTouchSensor(final byte[] payload) {
+        if (payload.length != 4) {
+            LOG.warn("Unexpected payload length {}", payload.length);
+            return Collections.emptyList();
+        }
+
+        if (payload[1] != (byte) 0xd1) {
+            LOG.warn("Not wide area tap");
+            return Collections.emptyList();
+        }
+
+        boolean enabled;
+
+        // reversed?
+        switch (payload[3]) {
+            case 0x00:
+                enabled = true;
+                break;
+            case 0x01:
+                enabled = false;
+                break;
+            default:
+                LOG.warn("Unknown wide area tap code {}", String.format("%02x", payload[3]));
+                return Collections.emptyList();
+        }
+
+        LOG.debug("Wide Area Tap: {}", enabled);
+
+        final GBDeviceEventUpdatePreferences event = new GBDeviceEventUpdatePreferences()
+                .withPreferences(new WideAreaTap(enabled).toPreferences());
+
+        return Collections.singletonList(event);
+    }
+
+    @Override
+    public List<? extends GBDeviceEvent> handleVoiceNotifications(final byte[] payload) {
+        if (payload.length != 4) {
+            LOG.warn("Unexpected payload length {}", payload.length);
+            return Collections.emptyList();
+        }
+
+        boolean enabled;
+
+        // reversed?
+        switch (payload[2]) {
+            case 0x00:
+                enabled = true;
+                break;
+            case 0x01:
+                enabled = false;
+                break;
+            default:
+                LOG.warn("Unknown voice notifications code {}", String.format("%02x", payload[3]));
+                return Collections.emptyList();
+        }
+
+        LOG.debug("Voice Notifications: {}", enabled);
+
+        final GBDeviceEventUpdatePreferences event = new GBDeviceEventUpdatePreferences()
+                .withPreferences(new VoiceNotifications(enabled).toPreferences());
+
+        return Collections.singletonList(event);
+    }
+
+    @Override
+    protected BatteryType decodeBatteryType(final byte b) {
+        switch (b) {
+            case 0x00:
+                return BatteryType.SINGLE;
+            case 0x01:
+                return BatteryType.DUAL2;
+            case 0x09:
+                return BatteryType.DUAL;
+            case 0x0a:
+                return BatteryType.CASE;
+        }
+
+        return null;
+    }
+
+    @Override
+    protected byte encodeBatteryType(final BatteryType batteryType) {
+        switch (batteryType) {
+            case SINGLE:
+                return 0x00;
+            case DUAL2:
+                return 0x01;
+            case DUAL:
+                return 0x09;
+            case CASE:
+                return 0x0a;
+        }
+
+        throw new IllegalArgumentException("Unknown battery type " + batteryType);
+    }
+
+    @Override
+    protected ButtonModes.Mode decodeButtonMode(final byte b) {
+        switch (b) {
+            case (byte) 0xff:
+                return ButtonModes.Mode.OFF;
+            case (byte) 0x00:
+            case (byte) 0x35:  // Seems to be the only one that differs?
+                return ButtonModes.Mode.AMBIENT_SOUND_CONTROL;
+            case (byte) 0x20:
+                return ButtonModes.Mode.PLAYBACK_CONTROL;
+            case (byte) 0x10:
+                return ButtonModes.Mode.VOLUME_CONTROL;
+        }
+
+        return null;
+    }
+
+    @Override
+    protected byte encodeButtonMode(final ButtonModes.Mode buttonMode) {
+        switch (buttonMode) {
+            case OFF:
+                return (byte) 0xff;
+            case AMBIENT_SOUND_CONTROL:
+                return (byte) (supportsWindNoiseCancelling() ? 0x35 : 0x00); // Seems to be the only one that differs?
+            case PLAYBACK_CONTROL:
+                return (byte) 0x20;
+            case VOLUME_CONTROL:
+                return (byte) 0x10;
+        }
+
+        throw new IllegalArgumentException("Unknown button mode " + buttonMode);
+    }
+}
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 0000000..0a7dd94
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/SonyWena3DeviceSupport.java
@@ -0,0 +1,1091 @@
+/*  Copyright (C) 2023-2024 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 <https://www.gnu.org/licenses/>. */
+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.time.LocalDate;
+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 MusicStateSpec lastMusicState = null;
+    private final List<CalendarEventSpec> 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");
+                if(lastMusicState != null && lastMusicState.state == MusicStateSpec.STATE_PLAYING && lastMusicInfo != null) {
+                    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) {
+            sendMusicInfo("");
+        }
+        lastMusicState = stateSpec;
+    }
+
+    @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(ArrayList<WeatherSpec> weatherSpecs) {
+        WeatherSpec weatherSpec = weatherSpecs.get(0);
+        if(weatherSpec.forecasts.size() < 4) return;
+
+        ArrayList<WeatherDay> days = new ArrayList<>();
+        // Add today
+        days.add(
+                new WeatherDay(
+                        Weather.fromOpenWeatherMap(weatherSpec.currentConditionCode),
+                        Weather.fromOpenWeatherMap(weatherSpec.currentConditionCode),
+                        weatherSpec.todayMaxTemp,
+                        weatherSpec.todayMinTemp
+                )
+        );
+
+        // Add other days
+        for(int i = 0; i < 4; i++) {
+            days.add(WeatherDay.fromSpec(weatherSpec.forecasts.get(i)));
+        }
+
+        WeatherReport report = new WeatherReport(days);
+        sendWeatherInfo(report, null);
+    }
+
+    @Override
+    public void onSetAlarms(ArrayList<? extends Alarm> alarms) {
+        try {
+            Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress()));
+            TransactionBuilder builder = performInitialized("alarmSetting");
+
+            assert alarms.size() <= SonyWena3Constants.ALARM_SLOTS;
+
+            int wakeupMargin = prefs.getInt(SonyWena3SettingKeys.SMART_WAKEUP_MARGIN_MINUTES,
+                    SonyWena3Constants.ALARM_DEFAULT_SMART_WAKEUP_MARGIN_MINUTES);
+
+            for(
+                int i = 0;
+                i < SonyWena3Constants.ALARM_SLOTS;
+                i += AlarmListSettings.MAX_ALARMS_IN_PACKET
+            ) {
+                AlarmListSettings pkt = new AlarmListSettings(new ArrayList<>(), i);
+
+                for(int j = 0; j < AlarmListSettings.MAX_ALARMS_IN_PACKET; j++) {
+                    if(i + j < alarms.size()) {
+                        Alarm alarm = alarms.get(i + j);
+                        SingleAlarmSetting sas = new SingleAlarmSetting(
+                                alarm.getEnabled(),
+                                (byte) alarm.getRepetition(),
+                                alarm.getSmartWakeup() ? wakeupMargin : 0,
+                                alarm.getHour(),
+                                alarm.getMinute()
+                        );
+                        pkt.alarms.add(sas);
+                    }
+                }
+
+                builder.write(
+                        getCharacteristic(SonyWena3Constants.COMMON_SERVICE_CHARACTERISTIC_CONTROL_UUID),
+                        pkt.toByteArray()
+                );
+            }
+
+            performImmediately(builder);
+        } catch (IOException e) {
+            LOG.warn("Unable to send alarms", e);
+            GB.toast("Failed to save alarms", Toast.LENGTH_SHORT, GB.ERROR);
+        }
+    }
+
+    private void sendDisplaySettings(TransactionBuilder b) {
+        Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress()));
+
+        String localeString = prefs.getString(DeviceSettingsPreferenceConst.PREF_LANGUAGE, DeviceSettingsPreferenceConst.PREF_LANGUAGE_AUTO);
+        if (localeString == null || localeString.equals(DeviceSettingsPreferenceConst.PREF_LANGUAGE_AUTO)) {
+            String language = Locale.getDefault().getLanguage();
+            String country = Locale.getDefault().getCountry();
+
+            if (country == null) {
+                country = language;
+            }
+            localeString = language + "_" + country.toUpperCase();
+        }
+        LOG.info("Setting device to locale: " + localeString);
+
+    Language languageCode = Language.ENGLISH;
+
+        switch (localeString.substring(0, 2)) {
+            case "en":
+                languageCode = Language.ENGLISH;
+                break;
+            case "ja":
+                languageCode = Language.JAPANESE;
+                break;
+        }
+        LOG.info("Resolved locale: " + languageCode.name());
+
+        DisplaySetting pkt = new DisplaySetting(
+                prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_SCREEN_LIFT_WRIST, false),
+                languageCode,
+                prefs.getInt(DeviceSettingsPreferenceConst.PREF_SCREEN_TIMEOUT, 5),
+                (prefs.getString(DeviceSettingsPreferenceConst.PREF_WEARLOCATION, "left")
+                        .equals("left") ? DisplayOrientation.LEFT_HAND : DisplayOrientation.RIGHT_HAND),
+                (prefs.getBoolean(SonyWena3SettingKeys.RICH_DESIGN_MODE, false) ? DisplayDesign.RICH : DisplayDesign.NORMAL),
+                (prefs.getBoolean(SonyWena3SettingKeys.LARGE_FONT_SIZE, false) ? FontSize.LARGE : FontSize.NORMAL),
+                prefs.getBoolean(SonyWena3SettingKeys.WEATHER_IN_STATUSBAR, true)
+        );
+
+        b.write(
+                getCharacteristic(SonyWena3Constants.COMMON_SERVICE_CHARACTERISTIC_CONTROL_UUID),
+                pkt.toByteArray()
+        );
+    }
+
+    private void sendDnDSettings(TransactionBuilder b) {
+        Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress()));
+        String dndMode = prefs.getString(DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_NOAUTO, DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_OFF);
+        boolean isDndOn = (dndMode != null && dndMode.equals(DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_SCHEDULED));
+        String start = prefs.getString(DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_NOAUTO_START, "22:00");
+        String end = prefs.getString(DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_NOAUTO_END, "06:00");
+
+        Calendar startCalendar = GregorianCalendar.getInstance();
+        Calendar endCalendar = GregorianCalendar.getInstance();
+        DateFormat df = new SimpleDateFormat("HH:mm");
+
+        try {
+            startCalendar.setTime(df.parse(start));
+            endCalendar.setTime(df.parse(end));
+        } catch (ParseException e) {
+            LOG.error("settings error: " + e);
+        }
+
+        byte startH = (byte)startCalendar.get(Calendar.HOUR_OF_DAY);
+        byte startM = (byte)startCalendar.get(Calendar.MINUTE);
+        byte endH = (byte)endCalendar.get(Calendar.HOUR_OF_DAY);
+        byte endM = (byte)endCalendar.get(Calendar.MINUTE);
+
+        DoNotDisturbSettings dndPkt = new DoNotDisturbSettings(isDndOn, startH, startM, endH, endM);
+        b.write(
+                getCharacteristic(SonyWena3Constants.COMMON_SERVICE_CHARACTERISTIC_CONTROL_UUID),
+                dndPkt.toByteArray()
+        );
+    }
+
+    private void sendAutoPowerSettings(TransactionBuilder b) {
+        Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress()));
+        String autoPowerMode = prefs.getString(SonyWena3SettingKeys.AUTO_POWER_SCHEDULE_KIND, DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_OFF);
+        boolean isAutoPowerOffEnabled = (autoPowerMode != null && autoPowerMode.equals(DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_SCHEDULED));
+        String start = prefs.getString(SonyWena3SettingKeys.AUTO_POWER_SCHEDULE_START_HHMM, "22:00");
+        String end = prefs.getString(SonyWena3SettingKeys.AUTO_POWER_SCHEDULE_END_HHMM, "06:00");
+
+        Calendar startCalendar = GregorianCalendar.getInstance();
+        Calendar endCalendar = GregorianCalendar.getInstance();
+        DateFormat df = new SimpleDateFormat("HH:mm");
+
+        try {
+            startCalendar.setTime(df.parse(start));
+            endCalendar.setTime(df.parse(end));
+        } catch (ParseException e) {
+            LOG.error("settings error: " + e);
+        }
+
+        byte startH = (byte)startCalendar.get(Calendar.HOUR_OF_DAY);
+        byte startM = (byte)startCalendar.get(Calendar.MINUTE);
+        byte endH = (byte)endCalendar.get(Calendar.HOUR_OF_DAY);
+        byte endM = (byte)endCalendar.get(Calendar.MINUTE);
+
+        AutoPowerOffSettings powerOffPkt = new AutoPowerOffSettings(isAutoPowerOffEnabled, startH, startM, endH, endM);
+        b.write(
+                getCharacteristic(SonyWena3Constants.COMMON_SERVICE_CHARACTERISTIC_CONTROL_UUID),
+                powerOffPkt.toByteArray()
+        );
+    }
+
+    private void sendVibrationSettings(TransactionBuilder b) {
+        Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress()));
+        boolean smartVibration = prefs.getBoolean(SonyWena3SettingKeys.SMART_VIBRATION, true);
+        VibrationStrength strength = VibrationStrength.valueOf(prefs.getString(SonyWena3SettingKeys.VIBRATION_STRENGTH, VibrationStrength.NORMAL.name()).toUpperCase());
+        VibrationSetting pkt = new VibrationSetting(smartVibration, strength);
+
+        b.write(
+                getCharacteristic(SonyWena3Constants.COMMON_SERVICE_CHARACTERISTIC_CONTROL_UUID),
+                pkt.toByteArray()
+        );
+    }
+
+    private void sendHomeScreenSettings(TransactionBuilder b) {
+        Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress()));
+        String leftIdName = prefs.getString(SonyWena3SettingKeys.LEFT_HOME_ICON, HomeIconId.MUSIC.name()).toUpperCase();
+        String centerIdName = prefs.getString(SonyWena3SettingKeys.CENTER_HOME_ICON, HomeIconId.PEDOMETER.name()).toUpperCase();
+        String rightIdName = prefs.getString(SonyWena3SettingKeys.RIGHT_HOME_ICON, HomeIconId.CALORIES.name()).toUpperCase();
+
+        b.write(
+                getCharacteristic(SonyWena3Constants.COMMON_SERVICE_CHARACTERISTIC_CONTROL_UUID),
+                new HomeIconOrderSetting(
+                        HomeIconId.valueOf(leftIdName),
+                        HomeIconId.valueOf(centerIdName),
+                        HomeIconId.valueOf(rightIdName)
+                ).toByteArray()
+        );
+    }
+
+    private void sendMenuSettings(TransactionBuilder b) {
+        Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress()));
+        String[] csv = prefs.getString(SonyWena3SettingKeys.MENU_ICON_CSV_KEY,
+                        TextUtils.join(",", getContext().getResources().getStringArray(R.array.prefs_wena3_menu_icons_default_list)))
+                .toUpperCase()
+                .split(",");
+
+        MenuIconSetting menu = new MenuIconSetting();
+
+        for(String iconIdName: csv) {
+            if(!iconIdName.equals(MenuIconId.NONE.name())) {
+                menu.iconList.add(MenuIconId.valueOf(iconIdName));
+            }
+        }
+
+        b.write(
+                getCharacteristic(SonyWena3Constants.COMMON_SERVICE_CHARACTERISTIC_CONTROL_UUID),
+                menu.toByteArray()
+        );
+    }
+
+    private void sendStatusPageSettings(TransactionBuilder b) {
+        Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress()));
+        StatusPageOrderSetting pageOrderSetting = new StatusPageOrderSetting();
+        String[] csv = prefs.getString(SonyWena3SettingKeys.STATUS_PAGE_CSV_KEY,
+                        TextUtils.join(",", getContext().getResources().getStringArray(R.array.prefs_wena3_status_page_default_list)))
+                .toUpperCase()
+                .split(",");
+        for(String idName: csv) {
+            if(!idName.equals(StatusPageId.NONE.name())) {
+                pageOrderSetting.pages.add(StatusPageId.valueOf(idName));
+            }
+        }
+
+        b.write(
+                getCharacteristic(SonyWena3Constants.COMMON_SERVICE_CHARACTERISTIC_CONTROL_UUID),
+                pageOrderSetting.toByteArray()
+        );
+    }
+
+    private void sendActivityGoalSettings(TransactionBuilder b) {
+        Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress()));
+        ActivityUser user = new ActivityUser();
+        LocalDate dateOfBirth = user.getDateOfBirth();
+        if(dateOfBirth.getYear() < 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)dateOfBirth.getYear(),
+                (short)dateOfBirth.getMonthValue(),
+                (short)dateOfBirth.getDayOfMonth(),
+                (short)user.getHeightCm(),
+                (short)user.getWeightKg()
+        );
+        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_DATE_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);
+        }
+    }
+
+    @Override
+    public boolean getImplicitCallbackModify() {
+        return true;
+    }
+
+    @Override
+    public boolean getSendWriteRequestResponse() {
+        return false;
+    }
+}
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 0000000..09deabb
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/Wena3Packetable.java
@@ -0,0 +1,21 @@
+/*  Copyright (C) 2023-2024 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 <https://www.gnu.org/licenses/>. */
+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 0000000..ab92236
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/logic/ActivityPacketCrc.java
@@ -0,0 +1,90 @@
+/*  Copyright (C) 2023-2024 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 <https://www.gnu.org/licenses/>. */
+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 0000000..856ea8a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/logic/ActivityPacketParser.java
@@ -0,0 +1,30 @@
+/*  Copyright (C) 2023-2024 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 <https://www.gnu.org/licenses/>. */
+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 0000000..339c45e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/logic/ActivitySyncPacketProcessor.java
@@ -0,0 +1,115 @@
+/*  Copyright (C) 2023-2024 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 <https://www.gnu.org/licenses/>. */
+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<ActivityPacketParser> 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 0000000..8fd969c
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/logic/StepsSampleCollection.java
@@ -0,0 +1,30 @@
+/*  Copyright (C) 2023-2024 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 <https://www.gnu.org/licenses/>. */
+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<Integer> everyMinuteCount;
+
+    public StepsSampleCollection(Date startDate, List<Integer> 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 0000000..a6bd266
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/logic/parsers/BehaviorPacketParser.java
@@ -0,0 +1,104 @@
+/*  Copyright (C) 2023-2024 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 <https://www.gnu.org/licenses/>. */
+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<BehaviorSample>  {
+    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<Wena3BehaviorSample> 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 0000000..448d7ee
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/logic/parsers/CaloriesPacketParser.java
@@ -0,0 +1,80 @@
+/*  Copyright (C) 2023-2024 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 <https://www.gnu.org/licenses/>. */
+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<Integer> {
+    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<Wena3CaloriesSample> 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 0000000..f2d7e59
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/logic/parsers/EnergyPacketParser.java
@@ -0,0 +1,68 @@
+/*  Copyright (C) 2023-2024 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 <https://www.gnu.org/licenses/>. */
+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<Wena3EnergySample> 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 0000000..42ba2bd
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/logic/parsers/HeartRatePacketParser.java
@@ -0,0 +1,78 @@
+/*  Copyright (C) 2023-2024 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 <https://www.gnu.org/licenses/>. */
+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<Wena3HeartRateSample> 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 0000000..643d9a2
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/logic/parsers/LinearSamplePacketParser.java
@@ -0,0 +1,65 @@
+/*  Copyright (C) 2023-2024 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 <https://www.gnu.org/licenses/>. */
+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<Sample> extends SamplePacketParser<Sample> {
+    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 0000000..586e39e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/logic/parsers/OneBytePerSamplePacketParser.java
@@ -0,0 +1,37 @@
+/*  Copyright (C) 2023-2024 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 <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.logic.parsers;
+
+import java.nio.ByteBuffer;
+
+public class OneBytePerSamplePacketParser extends LinearSamplePacketParser<Integer> {
+    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 0000000..ba35c34
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/logic/parsers/SamplePacketParser.java
@@ -0,0 +1,98 @@
+/*  Copyright (C) 2023-2024 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 <https://www.gnu.org/licenses/>. */
+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<Sample> implements ActivityPacketParser {
+    private static final Logger LOG = LoggerFactory.getLogger(SamplePacketParser.class);
+    private final int headerMarker;
+    public List<Sample> 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 0000000..2a9f573
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/logic/parsers/StepsPacketParser.java
@@ -0,0 +1,79 @@
+/*  Copyright (C) 2023-2024 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 <https://www.gnu.org/licenses/>. */
+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<Wena3ActivitySample> 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 0000000..7d6c1a7
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/logic/parsers/StressPacketParser.java
@@ -0,0 +1,81 @@
+/*  Copyright (C) 2023-2024 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 <https://www.gnu.org/licenses/>. */
+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<Wena3StressSample> 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.setTypeNum(StressSample.Type.AUTOMATIC.getNum());
+                    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 0000000..5c49bc1
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/logic/parsers/Vo2MaxPacketParser.java
@@ -0,0 +1,93 @@
+/*  Copyright (C) 2023-2024 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 <https://www.gnu.org/licenses/>. */
+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<Vo2MaxSample> {
+    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<Wena3Vo2Sample> 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 0000000..fab905d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/activity/ActivitySyncDataPacket.java
@@ -0,0 +1,94 @@
+/*  Copyright (C) 2023-2024 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 <https://www.gnu.org/licenses/>. */
+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("<ASDP type %d, Seq.No: %d, CRC: %x, Valid: %s, Data: %s>", 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 0000000..d4a99f1
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/activity/ActivitySyncStartPacket.java
@@ -0,0 +1,29 @@
+/*  Copyright (C) 2023-2024 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 <https://www.gnu.org/licenses/>. */
+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 0000000..aca0c45
--- /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-2024 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 <https://www.gnu.org/licenses/>. */
+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 0000000..2219738
--- /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-2024 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 <https://www.gnu.org/licenses/>. */
+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 0000000..2357e8f
--- /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-2024 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 <https://www.gnu.org/licenses/>. */
+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 0000000..feba501
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/activity/BehaviorSample.java
@@ -0,0 +1,57 @@
+/*  Copyright (C) 2023-2024 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 <https://www.gnu.org/licenses/>. */
+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 0000000..976e9df
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/activity/Vo2MaxSample.java
@@ -0,0 +1,29 @@
+/*  Copyright (C) 2023-2024 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 <https://www.gnu.org/licenses/>. */
+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 0000000..9957c71
--- /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-2024 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 <https://www.gnu.org/licenses/>. */
+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 0000000..8238fa0
--- /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-2024 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 <https://www.gnu.org/licenses/>. */
+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 0000000..9ba409b
--- /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-2024 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 <https://www.gnu.org/licenses/>. */
+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 0000000..71deb9c
--- /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-2024 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 <https://www.gnu.org/licenses/>. */
+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 0000000..5a7c8e5
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/notification/defines/LedColor.java
@@ -0,0 +1,53 @@
+/*  Copyright (C) 2023-2024 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 <https://www.gnu.org/licenses/>. */
+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 0000000..559b471
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/notification/defines/NotificationFlags.java
@@ -0,0 +1,29 @@
+/*  Copyright (C) 2023-2024 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 <https://www.gnu.org/licenses/>. */
+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 0000000..8ba3cfc
--- /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-2024 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 <https://www.gnu.org/licenses/>. */
+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 0000000..2115079
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/notification/defines/VibrationCount.java
@@ -0,0 +1,49 @@
+/*  Copyright (C) 2023-2024 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 <https://www.gnu.org/licenses/>. */
+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 0000000..5964246
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/notification/defines/VibrationKind.java
@@ -0,0 +1,55 @@
+/*  Copyright (C) 2023-2024 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 <https://www.gnu.org/licenses/>. */
+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 0000000..5a5fda2
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/notification/defines/VibrationOptions.java
@@ -0,0 +1,30 @@
+/*  Copyright (C) 2023-2024 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 <https://www.gnu.org/licenses/>. */
+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 0000000..1dd6996
--- /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-2024 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 <https://www.gnu.org/licenses/>. */
+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<SingleAlarmSetting> alarms;
+    // Usually 0 for first packet, 3 for second, 6 for third
+    public final int alarmListOffset;
+
+    public AlarmListSettings(List<SingleAlarmSetting> 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 0000000..9ca8748
--- /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-2024 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 <https://www.gnu.org/licenses/>. */
+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 0000000..798a352
--- /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-2024 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 <https://www.gnu.org/licenses/>. */
+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)
+                .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 0000000..845156e
--- /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-2024 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 <https://www.gnu.org/licenses/>. */
+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 0000000..39ae6c5
--- /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-2024 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 <https://www.gnu.org/licenses/>. */
+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 0000000..a381a46
--- /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-2024 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 <https://www.gnu.org/licenses/>. */
+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 0000000..e070b7d
--- /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-2024 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 <https://www.gnu.org/licenses/>. */
+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 0000000..b5f7631
--- /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-2024 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 <https://www.gnu.org/licenses/>. */
+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 0000000..b1a367e
--- /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-2024 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 <https://www.gnu.org/licenses/>. */
+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 0000000..1eec5b3
--- /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-2024 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 <https://www.gnu.org/licenses/>. */
+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 0000000..8e93a6b
--- /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-2024 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 <https://www.gnu.org/licenses/>. */
+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 0000000..e629957
--- /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-2024 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 <https://www.gnu.org/licenses/>. */
+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<MenuIconId> 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 0000000..9b8da80
--- /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-2024 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 <https://www.gnu.org/licenses/>. */
+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 0000000..1f01c4a
--- /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-2024 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 <https://www.gnu.org/licenses/>. */
+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<StatusPageId> 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 0000000..dcf1c56
--- /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-2024 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 <https://www.gnu.org/licenses/>. */
+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 0000000..8c9ea4b
--- /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-2024 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 <https://www.gnu.org/licenses/>. */
+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 0000000..b876a9b
--- /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-2024 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 <https://www.gnu.org/licenses/>. */
+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 0000000..c5833db
--- /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-2024 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 <https://www.gnu.org/licenses/>. */
+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 0000000..9a4c348
--- /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-2024 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 <https://www.gnu.org/licenses/>. */
+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 0000000..4347f46
--- /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-2024 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 <https://www.gnu.org/licenses/>. */
+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 0000000..84f9042
--- /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-2024 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 <https://www.gnu.org/licenses/>. */
+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 0000000..5896280
--- /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-2024 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 <https://www.gnu.org/licenses/>. */
+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 0000000..96a18ac
--- /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-2024 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 <https://www.gnu.org/licenses/>. */
+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 0000000..799fc28
--- /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-2024 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 <https://www.gnu.org/licenses/>. */
+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 0000000..823ce57
--- /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-2024 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 <https://www.gnu.org/licenses/>. */
+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 0000000..ba75750
--- /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-2024 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 <https://www.gnu.org/licenses/>. */
+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 0000000..fc54b80
--- /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-2024 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 <https://www.gnu.org/licenses/>. */
+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 0000000..805a47a
--- /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-2024 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 <https://www.gnu.org/licenses/>. */
+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 0000000..dc3d689
--- /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-2024 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 <https://www.gnu.org/licenses/>. */
+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 0000000..f077302
--- /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-2024 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 <https://www.gnu.org/licenses/>. */
+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 0000000..ce3343b
--- /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-2024 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 <https://www.gnu.org/licenses/>. */
+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 0000000..8e1bb64
--- /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-2024 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 <https://www.gnu.org/licenses/>. */
+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 0000000..5aa1f4d
--- /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-2024 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 <https://www.gnu.org/licenses/>. */
+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 0000000..2c7998f
--- /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-2024 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 <https://www.gnu.org/licenses/>. */
+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 0000000..008e1d9
--- /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-2024 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 <https://www.gnu.org/licenses/>. */
+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<WeatherDay> fiveDays;
+
+    public WeatherReport(List<WeatherDay> 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 0000000..f21a24c
--- /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-2024 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 <https://www.gnu.org/licenses/>. */
+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 0000000..17871f0
--- /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-2024 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 <https://www.gnu.org/licenses/>. */
+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/service/devices/sonyswr12/SonySWR12Constants.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/SonySWR12Constants.java
new file mode 100644
index 0000000..2b6cdec
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/SonySWR12Constants.java
@@ -0,0 +1,34 @@
+/*  Copyright (C) 2020-2024 opavlov
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12;
+
+import java.util.UUID;
+
+public class SonySWR12Constants {
+    //accessory host service
+    public static final String BASE_UUID_AHS = "0000%s-37CB-11E3-8682-0002A5D5C51B";
+    public static final UUID UUID_SERVICE_AHS = UUID.fromString(String.format(BASE_UUID_AHS, "0200"));
+    public static final UUID UUID_CHARACTERISTIC_ALARM = UUID.fromString(String.format(BASE_UUID_AHS, "0204"));
+    public static final UUID UUID_CHARACTERISTIC_EVENT = UUID.fromString(String.format(BASE_UUID_AHS, "0205"));
+    public static final UUID UUID_CHARACTERISTIC_TIME = UUID.fromString(String.format(BASE_UUID_AHS, "020B"));
+    public static final UUID UUID_CHARACTERISTIC_CONTROL_POINT = UUID.fromString(String.format(BASE_UUID_AHS, "0208"));
+
+    public static final int TYPE_ACTIVITY = 0;
+    public static final int TYPE_LIGHT = 1;
+    public static final int TYPE_DEEP = 2;
+    public static final int TYPE_NOT_WORN = 3;
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/SonySWR12DeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/SonySWR12DeviceSupport.java
new file mode 100644
index 0000000..37e501f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/SonySWR12DeviceSupport.java
@@ -0,0 +1,246 @@
+/*  Copyright (C) 2020-2024 Arjan Schrijver, opavlov
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12;
+
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.content.Intent;
+import android.widget.Toast;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.GattService;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.IntentListener;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.battery.BatteryInfo;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.battery.BatteryInfoProfile;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.activity.EventBase;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.activity.EventFactory;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.alarm.BandAlarm;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.alarm.BandAlarms;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.control.CommandCode;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.control.ControlPointLowVibration;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.control.ControlPointWithValue;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.time.BandTime;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+// done:
+// - time sync
+// - alarms (also smart)
+// - fetching activity(walking, sleep)
+// - stamina mode
+// - vibration intensity
+// - realtime heart rate
+// todo options:
+// - "get moving"
+// - get notified: -call, -notification, -notification from, -do not disturb
+// - media control: media/find phone(tap once for play pause, tap twice for next, tap triple for previous)
+
+public class SonySWR12DeviceSupport extends AbstractBTLEDeviceSupport {
+    private static final Logger LOG = LoggerFactory.getLogger(SonySWR12DeviceSupport.class);
+    private SonySWR12HandlerThread processor = null;
+
+    private final BatteryInfoProfile<SonySWR12DeviceSupport> batteryInfoProfile;
+    private final IntentListener mListener = new IntentListener() {
+        @Override
+        public void notify(Intent intent) {
+            if (intent.getAction().equals(BatteryInfoProfile.ACTION_BATTERY_INFO)) {
+                BatteryInfo info = intent.getParcelableExtra(BatteryInfoProfile.EXTRA_BATTERY_INFO);
+                GBDeviceEventBatteryInfo gbInfo = new GBDeviceEventBatteryInfo();
+                gbInfo.level = (short) info.getPercentCharged();
+                handleGBDeviceEvent(gbInfo);
+            }
+        }
+    };
+
+    public SonySWR12DeviceSupport() {
+        super(LOG);
+        addSupportedService(GattService.UUID_SERVICE_BATTERY_SERVICE);
+        addSupportedService(SonySWR12Constants.UUID_SERVICE_AHS);
+        batteryInfoProfile = new BatteryInfoProfile<>(this);
+        batteryInfoProfile.addListener(mListener);
+        addSupportedProfile(batteryInfoProfile);
+    }
+
+    @Override
+    protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
+        initialize();
+        setTime(builder);
+        batteryInfoProfile.requestBatteryInfo(builder);
+        return builder;
+    }
+
+    private SonySWR12HandlerThread getProcessor() {
+        if (processor == null) {
+            processor = new SonySWR12HandlerThread(getDevice(), getContext());
+            processor.start();
+        }
+        return processor;
+    }
+
+    private void initialize() {
+        if (gbDevice.getState() != GBDevice.State.INITIALIZED) {
+            gbDevice.setFirmwareVersion("N/A");
+            gbDevice.setFirmwareVersion2("N/A");
+            gbDevice.setState(GBDevice.State.INITIALIZED);
+            gbDevice.sendDeviceUpdateIntent(getContext());
+        }
+    }
+
+    @Override
+    public boolean useAutoConnect() {
+        return false;
+    }
+
+    @Override
+    public void onSetTime() {
+        try {
+            TransactionBuilder builder = performInitialized("setTime");
+            setTime(builder);
+            builder.queue(getQueue());
+        } catch (Exception e) {
+            GB.toast(getContext(), "Error setting time: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
+        }
+    }
+
+    private void setTime(TransactionBuilder builder) {
+        BluetoothGattCharacteristic timeCharacteristic = getCharacteristic(SonySWR12Constants.UUID_CHARACTERISTIC_TIME);
+        builder.write(timeCharacteristic, new BandTime(Calendar.getInstance()).toByteArray());
+    }
+
+    @Override
+    public void onSetAlarms(ArrayList<? extends Alarm> alarms) {
+        try {
+            BluetoothGattCharacteristic alarmCharacteristic = getCharacteristic(SonySWR12Constants.UUID_CHARACTERISTIC_ALARM);
+            TransactionBuilder builder = performInitialized("alarm");
+            int prefInterval = Integer.valueOf(GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress())
+                    .getString(DeviceSettingsPreferenceConst.PREF_SONYSWR12_SMART_INTERVAL, "0"));
+            ArrayList<BandAlarm> bandAlarmList = new ArrayList<>();
+            for (Alarm alarm : alarms) {
+                BandAlarm bandAlarm = BandAlarm.fromAppAlarm(alarm, bandAlarmList.size(), alarm.getSmartWakeup() ? prefInterval : 0);
+                if (bandAlarm != null)
+                    bandAlarmList.add(bandAlarm);
+            }
+            builder.write(alarmCharacteristic, new BandAlarms(bandAlarmList).toByteArray());
+            builder.queue(getQueue());
+        } catch (Exception e) {
+            GB.toast(getContext(), "Error setting alarms: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
+        }
+    }
+
+    @Override
+    public boolean onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
+        return super.onCharacteristicRead(gatt, characteristic, status);
+    }
+
+    @Override
+    public boolean onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
+        if (super.onCharacteristicChanged(gatt, characteristic))
+            return true;
+        UUID uuid = characteristic.getUuid();
+        if (uuid.equals(SonySWR12Constants.UUID_CHARACTERISTIC_EVENT)) {
+            try {
+                EventBase event = EventFactory.readEventFromByteArray(characteristic.getValue());
+                getProcessor().process(event);
+            } catch (Exception e) {
+                return false;
+            }
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public void onFetchRecordedData(int dataTypes) {
+        try {
+            TransactionBuilder builder = performInitialized("fetchActivity");
+            builder.notify(getCharacteristic(SonySWR12Constants.UUID_CHARACTERISTIC_EVENT), true);
+            ControlPointWithValue flushControl = new ControlPointWithValue(CommandCode.FLUSH_ACTIVITY, 0);
+            builder.write(getCharacteristic(SonySWR12Constants.UUID_CHARACTERISTIC_CONTROL_POINT), flushControl.toByteArray());
+            builder.queue(getQueue());
+        } catch (Exception e) {
+            LOG.error("failed to fetch activity data", e);
+        }
+    }
+
+    @Override
+    public void onEnableRealtimeHeartRateMeasurement(boolean enable) {
+        try {
+            TransactionBuilder builder = performInitialized("HeartRateTest");
+            builder.notify(getCharacteristic(SonySWR12Constants.UUID_CHARACTERISTIC_EVENT), enable);
+            ControlPointWithValue controlPointHeart = new ControlPointWithValue(CommandCode.HEARTRATE_REALTIME, enable ? 1 : 0);
+            builder.write(getCharacteristic(SonySWR12Constants.UUID_CHARACTERISTIC_CONTROL_POINT), controlPointHeart.toByteArray());
+            builder.queue(getQueue());
+        } catch (IOException ex) {
+            LOG.error("Unable to read heart rate from Sony device", ex);
+        }
+    }
+
+    @Override
+    public void onSendConfiguration(String config) {
+        try {
+            switch (config) {
+                case DeviceSettingsPreferenceConst.PREF_SONYSWR12_STAMINA: {
+                    //stamina can be:
+                    //disabled = 0, enabled = 1 or todo auto on low battery = 2
+                    int status = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getBoolean(config, false) ? 1 : 0;
+                    TransactionBuilder builder = performInitialized(config);
+                    ControlPointWithValue vibrationControl = new ControlPointWithValue(CommandCode.STAMINA_MODE, status);
+                    builder.write(getCharacteristic(SonySWR12Constants.UUID_CHARACTERISTIC_CONTROL_POINT), vibrationControl.toByteArray());
+                    builder.queue(getQueue());
+                    break;
+                }
+                case DeviceSettingsPreferenceConst.PREF_SONYSWR12_LOW_VIBRATION: {
+                    boolean isEnabled = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getBoolean(config, false);
+                    TransactionBuilder builder = performInitialized(config);
+                    ControlPointLowVibration vibrationControl = new ControlPointLowVibration(isEnabled);
+                    builder.write(getCharacteristic(SonySWR12Constants.UUID_CHARACTERISTIC_CONTROL_POINT), vibrationControl.toByteArray());
+                    builder.queue(getQueue());
+                    break;
+                }
+                case DeviceSettingsPreferenceConst.PREF_SONYSWR12_SMART_INTERVAL: {
+                    onSetAlarms(new ArrayList(DBHelper.getAlarms(gbDevice)));
+                }
+            }
+        } catch (Exception exc) {
+            LOG.error("failed to send config " + config, exc);
+        }
+    }
+
+    @Override
+    public boolean getImplicitCallbackModify() {
+        return true;
+    }
+
+    @Override
+    public boolean getSendWriteRequestResponse() {
+        return false;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/SonySWR12HandlerThread.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/SonySWR12HandlerThread.java
new file mode 100644
index 0000000..c08b8fd
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/SonySWR12HandlerThread.java
@@ -0,0 +1,152 @@
+/*  Copyright (C) 2020-2024 opavlov
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12;
+
+import android.content.Context;
+import android.content.Intent;
+
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+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.sonyswr12.SonySWR12SampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.SonySWR12Sample;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.activity.ActivityBase;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.activity.ActivitySleep;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.activity.ActivityWithData;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.activity.EventBase;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.activity.EventCode;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.activity.EventWithActivity;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.activity.EventWithValue;
+import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceIoThread;
+
+public class SonySWR12HandlerThread extends GBDeviceIoThread {
+    private static final Logger LOG = LoggerFactory.getLogger(SonySWR12HandlerThread.class);
+
+    public SonySWR12HandlerThread(GBDevice gbDevice, Context context) {
+        super(gbDevice, context);
+    }
+
+    public void process(EventBase event) {
+        if (event instanceof EventWithValue) {
+            if (event.getCode() == EventCode.HEART_RATE) {
+                processRealTimeHeartRate((EventWithValue) event);
+            }
+        } else if (event instanceof EventWithActivity) {
+            processWithActivity((EventWithActivity) event);
+        }
+    }
+
+    private void processRealTimeHeartRate(EventWithValue event) {
+        try {
+            DBHandler dbHandler = GBApplication.acquireDB();
+            Long userId = DBHelper.getUser(dbHandler.getDaoSession()).getId();
+            Long deviceId = DBHelper.getDevice(getDevice(), dbHandler.getDaoSession()).getId();
+            SonySWR12SampleProvider provider = new SonySWR12SampleProvider(getDevice(), dbHandler.getDaoSession());
+            int timestamp = getTimestamp();
+            SonySWR12Sample sample = new SonySWR12Sample(timestamp, deviceId, userId, (int) event.value, ActivitySample.NOT_MEASURED, 0, 1);
+            provider.addGBActivitySample(sample);
+            GBApplication.releaseDB();
+            Intent intent = new Intent(DeviceService.ACTION_REALTIME_SAMPLES)
+                    .putExtra(GBDevice.EXTRA_DEVICE, getDevice())
+                    .putExtra(DeviceService.EXTRA_REALTIME_SAMPLE, sample)
+                    .putExtra(DeviceService.EXTRA_TIMESTAMP, timestamp);
+            LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent);
+        } catch (Exception e) {
+            LOG.error(e.getMessage(), e);
+        }
+    }
+
+    private int getTimestamp() {
+        return (int) (System.currentTimeMillis() / 1000);
+    }
+
+    private void processWithActivity(EventWithActivity event) {
+        List<ActivityBase> payloadList = event.activityList;
+        for (ActivityBase activity : payloadList) {
+            switch (activity.getType()) {
+                case WALK:
+                case RUN:
+                    addActivity((ActivityWithData) activity);
+                    break;
+                case SLEEP:
+                    addSleep((ActivitySleep) activity);
+                    break;
+            }
+        }
+    }
+
+    private void addActivity(ActivityWithData activity) {
+        try {
+            DBHandler dbHandler = GBApplication.acquireDB();
+            Long userId = DBHelper.getUser(dbHandler.getDaoSession()).getId();
+            Long deviceId = DBHelper.getDevice(getDevice(), dbHandler.getDaoSession()).getId();
+            SonySWR12SampleProvider provider = new SonySWR12SampleProvider(getDevice(), dbHandler.getDaoSession());
+            int kind = SonySWR12Constants.TYPE_ACTIVITY;
+            SonySWR12Sample sample = new SonySWR12Sample(activity.getTimeStampSec(), deviceId, userId, ActivitySample.NOT_MEASURED, activity.data, kind, 1);
+            provider.addGBActivitySample(sample);
+            GBApplication.releaseDB();
+        } catch (Exception e) {
+            LOG.error(e.getMessage(), e);
+        }
+    }
+
+    private void addSleep(ActivitySleep activity) {
+        try {
+            DBHandler dbHandler = GBApplication.acquireDB();
+            Long userId = DBHelper.getUser(dbHandler.getDaoSession()).getId();
+            Long deviceId = DBHelper.getDevice(getDevice(), dbHandler.getDaoSession()).getId();
+            SonySWR12SampleProvider provider = new SonySWR12SampleProvider(getDevice(), dbHandler.getDaoSession());
+            int kind;
+            switch (activity.sleepLevel) {
+                case LIGHT:
+                    kind = SonySWR12Constants.TYPE_LIGHT;
+                    break;
+                case DEEP:
+                    kind = SonySWR12Constants.TYPE_DEEP;
+                    break;
+                default:
+                    kind = SonySWR12Constants.TYPE_ACTIVITY;
+                    break;
+            }
+            if (kind == SonySWR12Constants.TYPE_LIGHT || kind == SonySWR12Constants.TYPE_DEEP) {
+                //need so much samples because sleep has exact duration
+                //so empty samples are for right representation of sleep on activity charts
+                SonySWR12Sample sample = new SonySWR12Sample(activity.getTimeStampSec(), deviceId, userId, ActivitySample.NOT_MEASURED, 0, SonySWR12Constants.TYPE_NOT_WORN, 1);
+                provider.addGBActivitySample(sample);
+                sample = new SonySWR12Sample(activity.getTimeStampSec() + 2, deviceId, userId, ActivitySample.NOT_MEASURED, 0, kind, 1);
+                provider.addGBActivitySample(sample);
+                sample = new SonySWR12Sample(activity.getTimeStampSec() + activity.durationMin * 60 - 2, deviceId, userId, ActivitySample.NOT_MEASURED, 0, kind, 1);
+                provider.addGBActivitySample(sample);
+                sample = new SonySWR12Sample(activity.getTimeStampSec() + activity.durationMin * 60, deviceId, userId, ActivitySample.NOT_MEASURED, 0, SonySWR12Constants.TYPE_NOT_WORN, 1);
+                provider.addGBActivitySample(sample);
+            }
+            GBApplication.releaseDB();
+        } catch (Exception e) {
+            LOG.error(e.getMessage(), e);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/SonySWR12Util.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/SonySWR12Util.java
new file mode 100644
index 0000000..4cd246b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/SonySWR12Util.java
@@ -0,0 +1,38 @@
+/*  Copyright (C) 2020-2024 opavlov
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12;
+
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.TimeZone;
+
+public class SonySWR12Util {
+
+    public static long secSince2013() {
+        //sony uses time on band since 2013 for some reason
+        final Calendar instance = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
+        instance.set(2013, 0, 1, 0, 0, 0);
+        instance.set(14, 0);
+        return instance.getTimeInMillis()/1000;
+    }
+
+    public static String timeToString(long sec) {
+        SimpleDateFormat format = new SimpleDateFormat("MM-dd HH:mm:ss");
+        return format.format(new Date(sec * 1000));
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/ActivityBase.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/ActivityBase.java
new file mode 100644
index 0000000..bf1817f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/ActivityBase.java
@@ -0,0 +1,38 @@
+/*  Copyright (C) 2020-2024 opavlov
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.activity;
+
+public abstract class ActivityBase {
+    protected final ActivityType type;
+    private final long timeStampSec;
+
+    public ActivityBase(ActivityType type, int timeOffsetMin, long timeStampSec) {
+        if (timeOffsetMin < 0 || timeOffsetMin > 1440) {
+            throw new IllegalArgumentException("activity time offset out of range: " + timeOffsetMin);
+        }
+        this.type = type;
+        this.timeStampSec = timeStampSec + timeOffsetMin * 60;
+    }
+
+    public final int getTimeStampSec() {
+        return (int) (timeStampSec);
+    }
+
+    public final ActivityType getType() {
+        return this.type;
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/ActivityHeartRate.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/ActivityHeartRate.java
new file mode 100644
index 0000000..ac8d2f8
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/ActivityHeartRate.java
@@ -0,0 +1,29 @@
+/*  Copyright (C) 2020-2024 opavlov
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.activity;
+
+public class ActivityHeartRate extends ActivityBase {
+    public final int bpm;
+
+    public ActivityHeartRate(int timeOffsetMin, int bpm, Long timeStampSec) {
+        super(ActivityType.HEART_RATE, timeOffsetMin, timeStampSec);
+        if (bpm < 0 || bpm > 65535) {
+            throw new IllegalArgumentException("bpm out of range: " + bpm);
+        }
+        this.bpm = bpm;
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/ActivitySleep.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/ActivitySleep.java
new file mode 100644
index 0000000..de220e6
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/ActivitySleep.java
@@ -0,0 +1,28 @@
+/*  Copyright (C) 2020-2024 opavlov
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.activity;
+
+public class ActivitySleep extends ActivityBase {
+    public final SleepLevel sleepLevel;
+    public final int durationMin;
+
+    public ActivitySleep(int timeOffsetMin, int durationMin, SleepLevel sleepLevel, Long timeStampSec) {
+        super(ActivityType.SLEEP, timeOffsetMin, timeStampSec);
+        this.durationMin = durationMin;
+        this.sleepLevel = sleepLevel;
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/ActivityType.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/ActivityType.java
new file mode 100644
index 0000000..f6db090
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/ActivityType.java
@@ -0,0 +1,39 @@
+/*  Copyright (C) 2020-2024 opavlov
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.activity;
+
+public enum ActivityType {
+    WALK(1),
+    RUN(2),
+    SLEEP(3),
+    HEART_RATE(10),
+    END(14);
+
+    final int value;
+
+    ActivityType(int value) {
+        this.value = value;
+    }
+
+    public static ActivityType fromInt(int i) {
+        for (ActivityType type : values()){
+            if (type.value == i)
+                return type;
+        }
+        throw new IllegalArgumentException("wrong activity type: " + i);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/ActivityWithData.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/ActivityWithData.java
new file mode 100644
index 0000000..c249c21
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/ActivityWithData.java
@@ -0,0 +1,29 @@
+/*  Copyright (C) 2020-2024 opavlov
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.activity;
+
+public class ActivityWithData extends ActivityBase {
+    public final int data;
+
+    public ActivityWithData(ActivityType activityType, int timeOffsetMin, int data, Long timeStampSec) {
+        super(activityType, timeOffsetMin, timeStampSec);
+        if (data < 0 || data > 65535) {
+            throw new IllegalArgumentException("data out of range: " + data);
+        }
+        this.data = data;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/EventBase.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/EventBase.java
new file mode 100644
index 0000000..e3eb436
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/EventBase.java
@@ -0,0 +1,37 @@
+/*  Copyright (C) 2020-2024 opavlov
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.activity;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.util.ByteArrayWriter;
+
+public abstract class EventBase {
+    protected final EventCode eventCode;
+
+    protected EventBase(EventCode eventCode) {
+        this.eventCode = eventCode;
+    }
+
+    public EventCode getCode() {
+        return this.eventCode;
+    }
+
+    protected ByteArrayWriter getValueWriter() {
+        ByteArrayWriter byteArrayWriter = new ByteArrayWriter();
+        byteArrayWriter.appendUint8(this.eventCode.value);
+        return byteArrayWriter;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/EventCode.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/EventCode.java
new file mode 100644
index 0000000..7f0dd88
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/EventCode.java
@@ -0,0 +1,37 @@
+/*  Copyright (C) 2020-2024 opavlov
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.activity;
+
+public enum EventCode {
+    STEPS(3),
+    ACTIVITY_DATA(5),
+    HEART_RATE(9);
+
+    final int value;
+
+    EventCode(int value) {
+        this.value = value;
+    }
+
+    static EventCode fromInt(int i) {
+        for (EventCode code : values()){
+            if (code.value == i)
+                return code;
+        }
+        throw new RuntimeException("wrong event code: " + i);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/EventFactory.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/EventFactory.java
new file mode 100644
index 0000000..c5f8bb0
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/EventFactory.java
@@ -0,0 +1,42 @@
+/*  Copyright (C) 2020-2024 opavlov
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.activity;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.util.IntFormat;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.util.ByteArrayReader;
+
+public class EventFactory {
+
+    public static EventBase readEventFromByteArray(byte[] array) {
+        try {
+            ByteArrayReader byteArrayReader = new ByteArrayReader(array);
+            EventCode eventCode = EventCode.fromInt(byteArrayReader.readUint8());
+            switch (eventCode) {
+                case HEART_RATE: {
+                    long value = byteArrayReader.readInt(IntFormat.UINT32);
+                    return new EventWithValue(eventCode, value);
+                }
+                case ACTIVITY_DATA: {
+                    return EventWithActivity.fromByteArray(byteArrayReader);
+                }
+                default: return null;
+            }
+        } catch (Exception ex) {
+            return null;
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/EventWithActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/EventWithActivity.java
new file mode 100644
index 0000000..37e8210
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/EventWithActivity.java
@@ -0,0 +1,68 @@
+/*  Copyright (C) 2020-2024 opavlov
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.activity;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.SonySWR12Util;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.util.IntFormat;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.util.UIntBitReader;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.util.ByteArrayReader;
+
+public class EventWithActivity extends EventBase {
+    public final long timeStampSec;
+    public final List<ActivityBase> activityList;
+
+    private EventWithActivity(long timeStampSec, List<ActivityBase> activityList) {
+        super(EventCode.ACTIVITY_DATA);
+        this.timeStampSec = timeStampSec;
+        this.activityList = activityList;
+    }
+
+    public static EventWithActivity fromByteArray(ByteArrayReader byteArrayReader) {
+        long timeOffset = byteArrayReader.readInt(IntFormat.UINT32);
+        long timeStampSec = SonySWR12Util.secSince2013() + timeOffset;
+        ArrayList<ActivityBase> activities = new ArrayList<>();
+        while (byteArrayReader.getBytesLeft() > 0) {
+            UIntBitReader uIntBitReader = new UIntBitReader(byteArrayReader.readInt(IntFormat.UINT32), 32);
+            ActivityType activityType = ActivityType.fromInt(uIntBitReader.read(4));
+            int offsetMin = uIntBitReader.read(12);
+            ActivityBase activityPayload;
+            switch (activityType) {
+                case SLEEP: {
+                    int duration = uIntBitReader.read(14);
+                    SleepLevel sleepLevel = SleepLevel.fromInt(uIntBitReader.read(2));
+                    activityPayload = new ActivitySleep(offsetMin, duration, sleepLevel, timeStampSec);
+                    break;
+                }
+                case HEART_RATE: {
+                    int bpm = uIntBitReader.read(16);
+                    activityPayload = new ActivityHeartRate(offsetMin, bpm, timeStampSec);
+                    break;
+                }
+                default: {
+                    int data = uIntBitReader.read(16);
+                    activityPayload = new ActivityWithData(activityType, offsetMin, data, timeStampSec);
+                    break;
+                }
+            }
+            activities.add(activityPayload);
+        }
+        return new EventWithActivity(timeStampSec, activities);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/EventWithValue.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/EventWithValue.java
new file mode 100644
index 0000000..5723da7
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/EventWithValue.java
@@ -0,0 +1,34 @@
+/*  Copyright (C) 2020-2024 opavlov
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.activity;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.util.ByteArrayWriter;
+
+public class EventWithValue extends EventBase {
+    public final long value;
+
+    public EventWithValue(EventCode eventCode, long value) {
+        super(eventCode);
+        this.value = value;
+    }
+
+    public byte[] toByteArray() {
+        ByteArrayWriter byteArrayWriter = this.getValueWriter();
+        byteArrayWriter.appendUint32(this.value);
+        return byteArrayWriter.getByteArray();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/SleepLevel.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/SleepLevel.java
new file mode 100644
index 0000000..bd2eb38
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/SleepLevel.java
@@ -0,0 +1,37 @@
+/*  Copyright (C) 2020-2024 opavlov
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.activity;
+
+public enum SleepLevel {
+    AWAKE(0),
+    LIGHT(1),
+    DEEP(2);
+
+    final int value;
+
+    SleepLevel(int value){
+        this.value = value;
+    }
+
+    public static SleepLevel fromInt(int i) {
+        for (SleepLevel level : values()){
+            if (level.value == i)
+                return level;
+        }
+        throw new RuntimeException("wrong sleep level: " + i);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/alarm/AlarmRepeat.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/alarm/AlarmRepeat.java
new file mode 100644
index 0000000..0f1ff0a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/alarm/AlarmRepeat.java
@@ -0,0 +1,69 @@
+/*  Copyright (C) 2020-2024 opavlov
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.alarm;
+
+import java.util.Arrays;
+
+import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.util.UIntBitWriter;
+
+public class AlarmRepeat {
+    private final boolean[] repeat = new boolean[7];
+
+    public AlarmRepeat(Alarm alarm) {
+        super();
+        setRepeatOnDay(0, alarm.getRepetition(Alarm.ALARM_MON));
+        setRepeatOnDay(1, alarm.getRepetition(Alarm.ALARM_TUE));
+        setRepeatOnDay(2, alarm.getRepetition(Alarm.ALARM_WED));
+        setRepeatOnDay(3, alarm.getRepetition(Alarm.ALARM_THU));
+        setRepeatOnDay(4, alarm.getRepetition(Alarm.ALARM_FRI));
+        setRepeatOnDay(5, alarm.getRepetition(Alarm.ALARM_SAT));
+        setRepeatOnDay(6, alarm.getRepetition(Alarm.ALARM_SUN));
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this != o) {
+            if (o == null || this.getClass() != o.getClass()) {
+                return false;
+            }
+            return Arrays.equals(this.repeat, ((AlarmRepeat) o).repeat);
+        }
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        return Arrays.hashCode(this.repeat);
+    }
+
+    public void setRepeatOnDay(int i, boolean b) {
+        this.repeat[i] = b;
+    }
+
+    public int toInt() {
+        UIntBitWriter uIntBitWriter = new UIntBitWriter(7);
+        uIntBitWriter.appendBoolean(this.repeat[6]);
+        uIntBitWriter.appendBoolean(this.repeat[5]);
+        uIntBitWriter.appendBoolean(this.repeat[4]);
+        uIntBitWriter.appendBoolean(this.repeat[3]);
+        uIntBitWriter.appendBoolean(this.repeat[2]);
+        uIntBitWriter.appendBoolean(this.repeat[1]);
+        uIntBitWriter.appendBoolean(this.repeat[0]);
+        return (int) uIntBitWriter.getValue();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/alarm/AlarmState.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/alarm/AlarmState.java
new file mode 100644
index 0000000..fc7e276
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/alarm/AlarmState.java
@@ -0,0 +1,29 @@
+/*  Copyright (C) 2020-2024 opavlov
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.alarm;
+
+public enum AlarmState {
+    TRIGGERED( 0),
+    SNOOZED(1),
+    IDLE(2);
+
+    final int value;
+
+    AlarmState(int value) {
+        this.value = value;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/alarm/BandAlarm.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/alarm/BandAlarm.java
new file mode 100644
index 0000000..b321028
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/alarm/BandAlarm.java
@@ -0,0 +1,76 @@
+/*  Copyright (C) 2020-2024 opavlov
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.alarm;
+
+import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
+
+public class BandAlarm {
+    public static BandAlarm fromAppAlarm(Alarm alarm, int index, int interval) {
+        if (!alarm.getEnabled()) return null;
+        //smart wakeup = (0,10..60 min)/5
+        int ahsInterval = interval / 5;
+        return new BandAlarm(AlarmState.IDLE, index, ahsInterval, alarm.getHour(), alarm.getMinute(), new AlarmRepeat(alarm));
+    }
+
+    public AlarmState state;
+    public int index;
+    public int interval;
+    public int hour;
+    public int minute;
+    public AlarmRepeat repeat;
+
+    public BandAlarm(AlarmState state, int index, int interval, int hour, int minute, AlarmRepeat repeat) {
+        this.state = state;
+        this.index = index;
+        this.interval = interval;
+        this.hour = hour;
+        this.minute = minute;
+        this.repeat = repeat;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this != o) {
+            if (o == null || this.getClass() != o.getClass()) {
+                return false;
+            }
+            BandAlarm bandAlarm = (BandAlarm) o;
+            if (this.index != bandAlarm.index) {
+                return false;
+            }
+            if (this.hour != bandAlarm.hour) {
+                return false;
+            }
+            if (this.interval != bandAlarm.interval) {
+                return false;
+            }
+            if (this.minute != bandAlarm.minute) {
+                return false;
+            }
+            if (!this.repeat.equals(bandAlarm.repeat)) {
+                return false;
+            }
+            return this.state == bandAlarm.state;
+        }
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        return ((((this.state.hashCode() * 31 + this.index) * 31 + this.interval) * 31 + this.hour) * 31 + this.minute) * 31 + this.repeat.hashCode();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/alarm/BandAlarms.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/alarm/BandAlarms.java
new file mode 100644
index 0000000..7b799ef
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/alarm/BandAlarms.java
@@ -0,0 +1,51 @@
+/*  Copyright (C) 2020-2024 opavlov
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.alarm;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.util.UIntBitWriter;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.util.ByteArrayWriter;
+
+public class BandAlarms {
+    public final List<BandAlarm> alarms;
+
+    public BandAlarms(List<BandAlarm> alarms) {
+        this.alarms = alarms;
+    }
+
+    public byte[] toByteArray() {
+        ByteArrayWriter byteArrayWriter = new ByteArrayWriter();
+        if (this.alarms.size() == 0) {
+            byteArrayWriter.appendUint32(1073741824L);
+        } else {
+            for (BandAlarm bandAlarm : this.alarms) {
+                UIntBitWriter uIntBitWriter = new UIntBitWriter(32);
+                uIntBitWriter.append(2, 0);
+                uIntBitWriter.append(4, bandAlarm.index);
+                uIntBitWriter.append(2, bandAlarm.state.value);
+                uIntBitWriter.append(4, bandAlarm.interval);
+                uIntBitWriter.append(6, bandAlarm.hour);
+                uIntBitWriter.append(6, bandAlarm.minute);
+                uIntBitWriter.append(1, 0);
+                uIntBitWriter.append(7, bandAlarm.repeat.toInt());
+                byteArrayWriter.appendUint32(uIntBitWriter.getValue());
+            }
+        }
+        return byteArrayWriter.getByteArray();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/control/CommandCode.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/control/CommandCode.java
new file mode 100644
index 0000000..54f2a09
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/control/CommandCode.java
@@ -0,0 +1,31 @@
+/*  Copyright (C) 2020-2024 opavlov
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.control;
+
+public enum CommandCode {
+    FLUSH_ACTIVITY(7),
+    HEARTRATE_REALTIME(11),
+    STAMINA_MODE(17),
+    MANUAL_ALARM(19),
+    LOW_VIBRATION(25);
+
+    public final int value;
+
+    CommandCode(int value) {
+        this.value = value;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/control/ControlPoint.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/control/ControlPoint.java
new file mode 100644
index 0000000..a20a83b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/control/ControlPoint.java
@@ -0,0 +1,33 @@
+/*  Copyright (C) 2020-2024 opavlov
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.control;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.util.ByteArrayWriter;
+
+public abstract class ControlPoint {
+    protected final CommandCode code;
+
+    public ControlPoint(CommandCode code) {
+        this.code = code;
+    }
+
+    protected final ByteArrayWriter getValueWriter() {
+        final ByteArrayWriter byteArrayWriter = new ByteArrayWriter();
+        byteArrayWriter.appendUint8(this.code.value);
+        return byteArrayWriter;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/control/ControlPointLowVibration.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/control/ControlPointLowVibration.java
new file mode 100644
index 0000000..0dcd279
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/control/ControlPointLowVibration.java
@@ -0,0 +1,44 @@
+/*  Copyright (C) 2020-2024 opavlov
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.control;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.util.UIntBitWriter;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.util.ByteArrayWriter;
+
+public final class ControlPointLowVibration extends ControlPoint {
+    public boolean smartWakeUp;
+    public boolean incomingCall;
+    public boolean notification;
+
+    public ControlPointLowVibration(boolean isEnabled){
+        super(CommandCode.LOW_VIBRATION);
+        this.smartWakeUp = isEnabled;
+        this.incomingCall = isEnabled;
+        this.notification = isEnabled;
+    }
+
+    public final byte[] toByteArray() {
+        final UIntBitWriter uIntBitWriter = new UIntBitWriter(16);
+        uIntBitWriter.append(13, 0);
+        uIntBitWriter.appendBoolean(this.smartWakeUp);
+        uIntBitWriter.appendBoolean(this.incomingCall);
+        uIntBitWriter.appendBoolean(this.notification);
+        final ByteArrayWriter byteArrayWriter = this.getValueWriter();
+        byteArrayWriter.appendUint16((int) uIntBitWriter.getValue());
+        return byteArrayWriter.getByteArray();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/control/ControlPointWithValue.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/control/ControlPointWithValue.java
new file mode 100644
index 0000000..a6553ae
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/control/ControlPointWithValue.java
@@ -0,0 +1,37 @@
+/*  Copyright (C) 2020-2024 opavlov
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.control;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.util.ByteArrayWriter;
+
+public class ControlPointWithValue extends ControlPoint {
+    protected final int value;
+
+    public ControlPointWithValue(final CommandCode commandCode, final int value) {
+        super(commandCode);
+        if (value < 0 || value > 65535) {
+            throw new IllegalArgumentException("command value out of range " + value);
+        }
+        this.value = value;
+    }
+
+    public final byte[] toByteArray() {
+        final ByteArrayWriter byteArrayWriter = this.getValueWriter();
+        byteArrayWriter.appendUint16(this.value);
+        return byteArrayWriter.getByteArray();
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/time/BandDaylightSavingTime.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/time/BandDaylightSavingTime.java
new file mode 100644
index 0000000..844a23e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/time/BandDaylightSavingTime.java
@@ -0,0 +1,40 @@
+/*  Copyright (C) 2020-2024 opavlov
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.time;
+
+public enum BandDaylightSavingTime {
+    STANDARD_TIME(0, 0),
+    HALF_AN_HOUR_DST(2, 30),
+    DST(4, 60),
+    DOUBLE_DST( 8, 120);
+
+    final int key;
+    private final long saving;
+
+    BandDaylightSavingTime(int key, int min) {
+        this.key = key;
+        this.saving = 60000L * min;
+    }
+
+    public static BandDaylightSavingTime fromOffset(final int dstSaving) {
+        for (BandDaylightSavingTime dst: values()){
+            if (dst.saving == dstSaving)
+                return dst;
+        }
+        throw new RuntimeException("wrong dst saving: " + dstSaving);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/time/BandTime.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/time/BandTime.java
new file mode 100644
index 0000000..af5096b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/time/BandTime.java
@@ -0,0 +1,78 @@
+/*  Copyright (C) 2020-2024 opavlov
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.time;
+
+import java.util.Calendar;
+import java.util.TimeZone;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.util.IntFormat;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.util.ByteArrayWriter;
+
+public class BandTime {
+    private final int year;
+    private final int month;
+    private final int dayOfMonth;
+    private final int hour;
+    private final int min;
+    private final int sec;
+    private final int dayOfWeek;
+    private final BandTimeZone timeZone;
+    private final BandDaylightSavingTime dst;
+
+    public BandTime(Calendar calendar) {
+        int dayOfWeek = 7;
+        if (calendar == null) {
+            throw new IllegalArgumentException("Calendar cant be null");
+        }
+        this.year = calendar.get(1);
+        if (this.year > 2099 || this.year < 2013) {
+            throw new RuntimeException("out of 2013-2099");
+        }
+        this.month = calendar.get(2) + 1;
+        this.dayOfMonth = calendar.get(5);
+        int value = calendar.get(7);
+        if (value != 1) {
+            dayOfWeek = value - 1;
+        }
+        this.dayOfWeek = dayOfWeek;
+        this.hour = calendar.get(11);
+        this.min = calendar.get(12);
+        this.sec = calendar.get(13);
+        TimeZone timeZone = calendar.getTimeZone();
+        this.timeZone = BandTimeZone.fromOffset(timeZone.getRawOffset());
+        if (timeZone.inDaylightTime(calendar.getTime())) {
+            this.dst = BandDaylightSavingTime.fromOffset(timeZone.getDSTSavings());
+            return;
+        }
+        this.dst = BandDaylightSavingTime.STANDARD_TIME;
+    }
+
+    public byte[] toByteArray() {
+        ByteArrayWriter byteArrayWriter = new ByteArrayWriter();
+        byteArrayWriter.appendUint16(this.year);
+        byteArrayWriter.appendUint8(this.month);
+        byteArrayWriter.appendUint8(this.dayOfMonth);
+        byteArrayWriter.appendUint8(this.hour);
+        byteArrayWriter.appendUint8(this.min);
+        byteArrayWriter.appendUint8(this.sec);
+        byteArrayWriter.appendUint8(this.dayOfWeek);
+        byteArrayWriter.appendValue(this.timeZone.key, IntFormat.SINT8);
+        byteArrayWriter.appendUint8(this.dst.key);
+        return byteArrayWriter.getByteArray();
+    }
+}
+
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/time/BandTimeZone.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/time/BandTimeZone.java
new file mode 100644
index 0000000..489c972
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/time/BandTimeZone.java
@@ -0,0 +1,77 @@
+/*  Copyright (C) 2020-2024 opavlov
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.time;
+
+public enum BandTimeZone {
+    UTC_PLUS_06_30(26, 6, 30),
+    UTC_PLUS_07_00(28, 7, 0),
+    UTC_PLUS_08_00(32, 8, 0),
+    UTC_PLUS_08_45(35, 8, 45),
+    UTC_PLUS_09_00(36, 9, 0),
+    UTC_PLUS_09_30(38, 9, 30),
+    UTC_PLUS_10_00(40, 10, 0),
+    UTC_PLUS_10_30(42, 10, 30),
+    UTC_PLUS_11_00(44, 11, 0),
+    UTC_PLUS_11_30(46, 11, 30),
+    UTC_PLUS_12_00(48, 12, 0),
+    UTC_PLUS_12_45(51, 12, 45),
+    UTC_PLUS_13_00( 52, 13, 0),
+    UTC_PLUS_14_00(56, 14, 0),
+    UTC_MINUS_12_00(-48, -12, 0),
+    UTC_MINUS_11_00(-44, -11, 0),
+    UTC_MINUS_10_00(-40, -10, 0),
+    UTC_MINUS_09_30(-38, -9, -30),
+    UTC_MINUS_09_00(-36, -9, 0),
+    UTC_MINUS_08_00(-32, -8, 0),
+    UTC_MINUS_07_00(-28, -7, 0),
+    UTC_MINUS_06_00(-24, -6, 0),
+    UTC_MINUS_05_00(-20, -5, 0),
+    UTC_MINUS_04_30(-18, -4, -30),
+    UTC_MINUS_04_00(-16, -4, 0),
+    UTC_MINUS_03_30(-14, -3, -30),
+    UTC_MINUS_03_00(-12, -3, 0),
+    UTC_MINUS_02_00(-8, -2, 0),
+    UTC_MINUS_01_00(-4, -1, 0),
+    UTC_PLUS_00_00(0, 0, 0),
+    UTC_PLUS_01_00(4, 1, 0),
+    UTC_PLUS_02_00(8, 2, 0),
+    UTC_PLUS_03_00(12, 3, 0),
+    UTC_PLUS_03_30(14, 3, 30),
+    UTC_PLUS_04_00(16, 4, 0),
+    UTC_PLUS_04_30(18, 4, 30),
+    UTC_PLUS_05_00(20, 5, 0),
+    UTC_PLUS_05_30(22, 5, 30),
+    UTC_PLUS_05_45(23, 5, 45),
+    UTC_PLUS_06_00(24, 6, 0);
+
+    final int key;
+    private final long rawOffset;
+
+    BandTimeZone(int key, int hourOffset, int minOffset) {
+        this.key = key;
+        this.rawOffset = 3600000L * hourOffset + 60000L * minOffset;
+    }
+
+    public static BandTimeZone fromOffset(long rawOffset) {
+        for (BandTimeZone zone : values()){
+            if (zone.rawOffset == rawOffset)
+                return zone;
+        }
+        throw new RuntimeException("wrong raw offset: " + rawOffset);
+    }
+}
+
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/util/ByteArrayReader.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/util/ByteArrayReader.java
new file mode 100644
index 0000000..d4a44cc
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/util/ByteArrayReader.java
@@ -0,0 +1,69 @@
+/*  Copyright (C) 2020-2024 opavlov
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.util;
+
+public class ByteArrayReader {
+    public final byte[] byteArray;
+    public int bytesRead;
+
+    public ByteArrayReader(byte[] array) {
+        this.bytesRead = 0;
+        if (array == null || array.length <= 0) {
+            throw new IllegalArgumentException("wrong byte array");
+        }
+        this.byteArray = array.clone();
+    }
+
+    public int getBytesLeft() {
+        return this.byteArray.length - this.bytesRead;
+    }
+
+    public long readInt(IntFormat intFormat) {
+        if (intFormat == null) {
+            throw new IllegalArgumentException("wrong intFormat");
+        }
+        int i = 0;
+        long n = 0L;
+        try {
+            while (i < intFormat.bytesCount) {
+                long n2 = this.byteArray[this.bytesRead++] & 0xFF;
+                int n3 = i + 1;
+                n += n2 << i * 8;
+                i = n3;
+            }
+            long n4 = n;
+            if (intFormat.isSigned) {
+                int n5 = intFormat.bytesCount * 8;
+                n4 = n;
+                if (((long) (1 << n5 - 1) & n) != 0x0L) {
+                    n4 = ((1 << n5 - 1) - (n & (long) ((1 << n5 - 1) - 1))) * -1L;
+                }
+            }
+            return n4;
+        } catch (ArrayIndexOutOfBoundsException ex) {
+            throw new RuntimeException("reading outside of byte array", ex.getCause());
+        }
+    }
+
+    public int readUint16() {
+        return (int) this.readInt(IntFormat.UINT16);
+    }
+
+    public int readUint8() {
+        return (int) this.readInt(IntFormat.UINT8);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/util/ByteArrayWriter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/util/ByteArrayWriter.java
new file mode 100644
index 0000000..85c1efa
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/util/ByteArrayWriter.java
@@ -0,0 +1,77 @@
+/*  Copyright (C) 2020-2024 opavlov
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.util;
+
+import java.util.Arrays;
+
+public class ByteArrayWriter {
+    public byte[] byteArray;
+    private int bytesWritten;
+
+    public ByteArrayWriter() {
+        this.bytesWritten = 0;
+    }
+
+    private void addIntToValue(long n, IntFormat intFormat) {
+        for (int i = 0; i < intFormat.bytesCount; ++i) {
+            this.byteArray[this.bytesWritten++] = (byte) (n >> i * 8 & 0xFFL);
+        }
+    }
+
+    public void appendUint16(int n) {
+        this.appendValue(n, IntFormat.UINT16);
+    }
+
+    public void appendUint32(long n) {
+        this.appendValue(n, IntFormat.UINT32);
+    }
+
+    public void appendUint8(int n) {
+        this.appendValue(n, IntFormat.UINT8);
+    }
+
+    public void appendValue(long lng, IntFormat intFormat) {
+        if (intFormat == null) {
+            throw new IllegalArgumentException("wrong int format");
+        }
+        if (lng > intFormat.max || lng < intFormat.min) {
+            throw new IllegalArgumentException("wrong value for intFormat. max: " + intFormat.max + " min: " + intFormat.min + " value: " + lng);
+        }
+        this.increaseByteArray(intFormat.bytesCount);
+        long n = lng;
+        if (intFormat.isSigned) {
+            int n2 = intFormat.bytesCount * 8;
+            n = lng;
+            if (lng < 0L) {
+                n = (1 << n2 - 1) + ((long) ((1 << n2 - 1) - 1) & lng);
+            }
+        }
+        this.addIntToValue(n, intFormat);
+    }
+
+    public void increaseByteArray(int n) {
+        if (this.byteArray == null) {
+            this.byteArray = new byte[n];
+            return;
+        }
+        this.byteArray = Arrays.copyOf(this.byteArray, this.byteArray.length + n);
+    }
+
+    public byte[] getByteArray() {
+        return this.byteArray.clone();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/util/IntFormat.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/util/IntFormat.java
new file mode 100644
index 0000000..58e0c84
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/util/IntFormat.java
@@ -0,0 +1,51 @@
+/*  Copyright (C) 2020-2024 opavlov
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.util;
+
+public enum IntFormat {
+    UINT8(1, false),
+    SINT8(1, true),
+    UINT16( 2, false),
+    SINT16( 2, true),
+    UINT32(4, false),
+    SINT32(4, true);
+
+    final int bytesCount;
+    final boolean isSigned;
+    final long max;
+    final long min;
+
+    IntFormat(int bytesCount, boolean isSigned) {
+        this.bytesCount = bytesCount;
+        this.isSigned = isSigned;
+        int bitsCount = bytesCount * 8;
+        long max;
+        if (isSigned) {
+            max = (long) Math.pow(2.0, bitsCount - 1) - 1L;
+        } else {
+            max = (long) (Math.pow(2.0, bitsCount) - 1.0);
+        }
+        this.max = max;
+        long min;
+        if (isSigned) {
+            min = (long) (-1.0 * Math.pow(2.0, bitsCount - 1));
+        } else {
+            min = 0L;
+        }
+        this.min = min;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/util/UIntBitReader.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/util/UIntBitReader.java
new file mode 100644
index 0000000..022ce8a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/util/UIntBitReader.java
@@ -0,0 +1,43 @@
+/*  Copyright (C) 2020-2024 opavlov
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.util;
+
+public class UIntBitReader {
+    private final long value;
+    private int offset;
+
+    public UIntBitReader(long value, int offset) {
+        this.value = value;
+        this.offset = offset;
+    }
+
+    public int read(int offset) {
+        this.offset -= offset;
+        if (this.offset < 0) {
+            throw new IllegalArgumentException("Read out of range");
+        }
+        return (int) ((long) ((1 << offset) - 1) & this.value >>> this.offset);
+    }
+
+    public boolean readBoolean() {
+        boolean b = true;
+        if (this.read(1) == 0) {
+            b = false;
+        }
+        return b;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/util/UIntBitWriter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/util/UIntBitWriter.java
new file mode 100644
index 0000000..a4bf1be
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/util/UIntBitWriter.java
@@ -0,0 +1,53 @@
+/*  Copyright (C) 2020-2024 opavlov
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.util;
+
+public class UIntBitWriter {
+    private long value;
+    private long offset;
+
+    public UIntBitWriter(int offset) {
+        this.value = 0L;
+        this.offset = offset;
+    }
+
+    public void append(int offset, int value) {
+        if (value < 0 || value > (1 << offset) - 1) {
+            throw new IllegalArgumentException("value is out of range: " + value);
+        }
+        this.offset -= offset;
+        if (this.offset < 0L) {
+            throw new IllegalArgumentException("Write offset out of range");
+        }
+        this.value |= (long) value << (int) this.offset;
+    }
+
+    public void appendBoolean(boolean b) {
+        if (b) {
+            this.append(1, 1);
+            return;
+        }
+        this.append(1, 0);
+    }
+
+    public long getValue() {
+        if (this.offset != 0L) {
+            throw new IllegalStateException("value is not complete yet: " + this.offset);
+        }
+        return this.value;
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/soundcore/SoundcorePacket.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/soundcore/SoundcorePacket.java
new file mode 100644
index 0000000..22c5089
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/soundcore/SoundcorePacket.java
@@ -0,0 +1,112 @@
+/*  Copyright (C) 2024 Severin von Wnuck-Lipinski
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.soundcore;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+import static nodomain.freeyourgadget.gadgetbridge.util.GB.hexdump;
+
+public class SoundcorePacket {
+    private static final Logger LOG = LoggerFactory.getLogger(SoundcorePacket.class);
+
+    private static final int HEADER_LENGTH = 10;
+    private static final short START_OF_PACKET_HOST = (short)0xee08;
+    private static final short START_OF_PACKET_DEVICE = (short)0xff09;
+    private static final byte DIRECTION_HOST = (byte)0x00;
+    private static final byte DIRECTION_DEVICE = (byte)0x01;
+
+    private short command;
+    private byte[] payload;
+
+    public SoundcorePacket(short command) {
+        this(command, new byte[] {});
+    }
+
+    public SoundcorePacket(short command, byte[] payload) {
+        LOG.debug("Packet: command={}", String.format("0x%04x", command));
+
+        this.command = command;
+        this.payload = payload;
+    }
+
+    public short getCommand() {
+        return command;
+    }
+
+    public byte[] getPayload() {
+        return payload;
+    }
+
+    public static SoundcorePacket decode(ByteBuffer buf) {
+        if (buf.remaining() < HEADER_LENGTH)
+            return null;
+
+        buf.order(ByteOrder.LITTLE_ENDIAN);
+
+        if (buf.getShort() != START_OF_PACKET_DEVICE) {
+            LOG.error("Invalid start of packet: {}", hexdump(buf.array()));
+            return null;
+        }
+
+        // Skip two zero bytes
+        buf.getShort();
+
+        if (buf.get() != DIRECTION_DEVICE) {
+            LOG.error("Invalid direction: {}", hexdump(buf.array()));
+            return null;
+        }
+
+        short command = buf.getShort();
+        short length = buf.getShort();
+
+        if (length < HEADER_LENGTH) {
+            LOG.error("Invalid length: {}", hexdump(buf.array()));
+            return null;
+        }
+
+        // Skip checksum byte at the end
+        byte[] payload = new byte[length - HEADER_LENGTH];
+        buf.get(payload);
+
+        return new SoundcorePacket(command, payload);
+    }
+
+    public byte[] encode() {
+        ByteBuffer buf = ByteBuffer.allocate(HEADER_LENGTH + payload.length);
+
+        buf.order(ByteOrder.LITTLE_ENDIAN);
+        buf.putShort(START_OF_PACKET_HOST);
+        buf.putShort((short)0x0000);
+        buf.put(DIRECTION_HOST);
+        buf.putShort(command);
+        buf.putShort((short)(HEADER_LENGTH + payload.length));
+        buf.put(payload);
+
+        byte checksum = 0;
+
+        for (byte val : buf.array())
+            checksum += val;
+
+        buf.put(checksum);
+
+        return buf.array();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/soundcore/liberty/SoundcoreLiberty3ProDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/soundcore/liberty/SoundcoreLiberty3ProDeviceSupport.java
new file mode 100644
index 0000000..7279ad4
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/soundcore/liberty/SoundcoreLiberty3ProDeviceSupport.java
@@ -0,0 +1,39 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.soundcore.liberty;
+
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.service.serial.AbstractSerialDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceIoThread;
+import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol;
+
+public class SoundcoreLiberty3ProDeviceSupport extends AbstractSerialDeviceSupport {
+    public static final UUID UUID_DEVICE_CTRL = UUID.fromString("0cf12d31-fac3-4553-bd80-d6832e7b3952");
+
+    @Override
+    public boolean connect() {
+        getDeviceIOThread().start();
+        return true;
+    }
+
+    @Override
+    public boolean useAutoConnect() {
+        return false;
+    }
+
+    @Override
+    protected GBDeviceProtocol createDeviceProtocol() {
+        return new SoundcoreLibertyProtocol(getDevice());
+    }
+
+    @Override
+    protected synchronized GBDeviceIoThread createDeviceIOThread() {
+        return new SoundcoreLibertyIOThread(
+                getDevice(),
+                getContext(),
+                (SoundcoreLibertyProtocol) getDeviceProtocol(),
+                UUID_DEVICE_CTRL,
+                this,
+                getBluetoothAdapter()
+        );
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/soundcore/liberty/SoundcoreLiberty4NCDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/soundcore/liberty/SoundcoreLiberty4NCDeviceSupport.java
new file mode 100644
index 0000000..653a6db
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/soundcore/liberty/SoundcoreLiberty4NCDeviceSupport.java
@@ -0,0 +1,39 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.soundcore.liberty;
+
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.service.serial.AbstractSerialDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceIoThread;
+import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol;
+
+public class SoundcoreLiberty4NCDeviceSupport extends AbstractSerialDeviceSupport {
+    public static final UUID UUID_DEVICE_CTRL = UUID.fromString("0cf12d31-fac3-4553-bd80-d6832e7b3947");
+
+    @Override
+    public boolean connect() {
+        getDeviceIOThread().start();
+        return true;
+    }
+
+    @Override
+    public boolean useAutoConnect() {
+        return false;
+    }
+
+    @Override
+    protected GBDeviceProtocol createDeviceProtocol() {
+        return new SoundcoreLibertyProtocol(getDevice());
+    }
+
+    @Override
+    protected synchronized GBDeviceIoThread createDeviceIOThread() {
+        return new SoundcoreLibertyIOThread(
+                getDevice(),
+                getContext(),
+                (SoundcoreLibertyProtocol) getDeviceProtocol(),
+                UUID_DEVICE_CTRL,
+                this,
+                getBluetoothAdapter()
+        );
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/soundcore/liberty/SoundcoreLibertyIOThread.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/soundcore/liberty/SoundcoreLibertyIOThread.java
new file mode 100644
index 0000000..f0d3086
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/soundcore/liberty/SoundcoreLibertyIOThread.java
@@ -0,0 +1,57 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.soundcore.liberty;
+
+import static nodomain.freeyourgadget.gadgetbridge.util.GB.hexdump;
+
+import android.bluetooth.BluetoothAdapter;
+import android.content.Context;
+import android.os.ParcelUuid;
+
+import androidx.annotation.NonNull;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.btclassic.BtClassicIoThread;
+import nodomain.freeyourgadget.gadgetbridge.service.serial.AbstractSerialDeviceSupport;
+
+public class SoundcoreLibertyIOThread extends BtClassicIoThread {
+    private static final Logger LOG = LoggerFactory.getLogger(SoundcoreLibertyIOThread.class);
+    private final SoundcoreLibertyProtocol mSoundcoreProtocol;
+    private final UUID mUuidToConnect;
+
+    public SoundcoreLibertyIOThread(final GBDevice gbDevice,
+                                    final Context context,
+                                    final SoundcoreLibertyProtocol deviceProtocol,
+                                    final UUID uuidToConnect,
+                                    final AbstractSerialDeviceSupport deviceSupport,
+                                    final BluetoothAdapter btAdapter) {
+        super(gbDevice, context, deviceProtocol, deviceSupport, btAdapter);
+        mSoundcoreProtocol = deviceProtocol;
+        mUuidToConnect = uuidToConnect;
+    }
+
+    @Override
+    protected void initialize() {
+        write(mSoundcoreProtocol.encodeDeviceInfoRequest());
+        setUpdateState(GBDevice.State.INITIALIZED);
+    }
+
+    @NonNull
+    protected UUID getUuidToConnect(@NonNull ParcelUuid[] uuids) {
+        return mUuidToConnect;
+    }
+
+    @Override
+    protected byte[] parseIncoming(InputStream inStream) throws IOException {
+        byte[] buffer = new byte[1048576]; //HUGE read
+        int bytes = inStream.read(buffer);
+        LOG.debug("read {} bytes. {}", bytes, hexdump(buffer, 0, bytes));
+        return Arrays.copyOf(buffer, bytes);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/soundcore/liberty/SoundcoreLibertyProtocol.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/soundcore/liberty/SoundcoreLibertyProtocol.java
new file mode 100644
index 0000000..c23efae
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/soundcore/liberty/SoundcoreLibertyProtocol.java
@@ -0,0 +1,348 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.soundcore.liberty;
+
+import static nodomain.freeyourgadget.gadgetbridge.util.GB.hexdump;
+
+import android.content.SharedPreferences;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.AmbientSoundControlButtonMode;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.soundcore.SoundcorePacket;
+import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+public class SoundcoreLibertyProtocol extends GBDeviceProtocol {
+
+    private static final Logger LOG = LoggerFactory.getLogger(SoundcoreLibertyProtocol.class);
+
+    private static final int battery_case = 0;
+    private static final int battery_earphone_left = 1;
+    private static final int battery_earphone_right = 2;
+
+    protected SoundcoreLibertyProtocol(GBDevice device) {
+        super(device);
+    }
+
+    private GBDeviceEventBatteryInfo buildBatteryInfo(int batteryIndex, int level) {
+        GBDeviceEventBatteryInfo info = new GBDeviceEventBatteryInfo();
+        info.batteryIndex = batteryIndex;
+        info.level = level;
+        return info;
+    }
+
+    private GBDeviceEventVersionInfo buildVersionInfo(String firmware1, String firmware2, String serialNumber) {
+        GBDeviceEventVersionInfo info = new GBDeviceEventVersionInfo();
+        info.hwVersion = serialNumber;
+        info.fwVersion = firmware1;
+        info.fwVersion2 = firmware2;
+        return info;
+    }
+
+    private String readString(byte[] data, int position, int size) {
+        if (position + size > data.length) throw new IllegalStateException();
+        return new String(data, position, size, StandardCharsets.UTF_8);
+    }
+    @Override
+    public GBDeviceEvent[] decodeResponse(byte[] responseData) {
+        ByteBuffer buf = ByteBuffer.wrap(responseData);
+        SoundcorePacket packet = SoundcorePacket.decode(buf);
+
+        if (packet == null)
+            return null;
+
+        List<GBDeviceEvent> devEvts = new ArrayList<>();
+        short cmd = packet.getCommand();
+        byte[] payload = packet.getPayload();
+
+        if (cmd == (short) 0x0101) {
+            // a lot of other data is in here, anything interesting?
+            String firmware1 = readString(payload, 6, 5);
+            String firmware2 = readString(payload, 11, 5);
+            String serialNumber = readString(payload, 16, 16);
+            devEvts.add(buildVersionInfo(firmware1, firmware2, serialNumber));
+        } else if (cmd == (short) 0x8d01) {
+            LOG.debug("Unknown incoming message - command: " + cmd + ", dump: " + hexdump(responseData));
+        } else if (cmd == (short) 0x8205) {
+            LOG.debug("Unknown incoming message - command: " + cmd + ", dump: " + hexdump(responseData));
+        } else if (cmd == (short) 0x0105) {
+            LOG.debug("Unknown incoming message - command: " + cmd + ", dump: " + hexdump(responseData));
+        } else if (cmd == (short) 0x0106) { //Sound Mode Update
+            decodeAudioMode(payload);
+        } else if (cmd == (short) 0x0301) { // Battery Update
+            int batteryLeft = payload[0] * 20;
+            int batteryRight = payload[1] * 20;
+            int batteryCase = payload[2] * 20;
+
+            devEvts.add(buildBatteryInfo(battery_case, batteryCase));
+            devEvts.add(buildBatteryInfo(battery_earphone_left, batteryLeft));
+            devEvts.add(buildBatteryInfo(battery_earphone_right, batteryRight));
+        } else {
+            // see https://github.com/gmallios/SoundcoreManager/blob/master/soundcore-lib/src/models/packet_kind.rs
+            // for a mapping for other soundcore devices (similar protocol?)
+            LOG.debug("Unknown incoming message - command: " + cmd + ", dump: " + hexdump(responseData));
+        }
+        return devEvts.toArray(new GBDeviceEvent[devEvts.size()]);
+    }
+
+    private void decodeAudioMode(byte[] payload) {
+        SharedPreferences prefs = getDevicePrefs().getPreferences();
+        SharedPreferences.Editor editor = prefs.edit();
+        String soundmode = "off";
+        int anc_strength = 0;
+
+        if (payload[0] == 0x00) {
+            soundmode = "noise_cancelling";
+        } else if (payload[0] == 0x01) {
+            soundmode = "ambient_sound";
+        } else if (payload[0] == 0x02) {
+            soundmode = "off";
+        }
+
+        if (payload[1] == 0x10) {
+            anc_strength = 0;
+        } else if (payload[1] == 0x20) {
+            anc_strength = 1;
+        } else if (payload[1] == 0x30) {
+            anc_strength = 2;
+        }
+
+        boolean vocal_mode = (payload[2] == 0x01);
+        boolean adaptive_anc = (payload[3] == 0x01);
+        boolean windnoiseReduction = (payload[4] == 0x01);
+
+        editor.putString(DeviceSettingsPreferenceConst.PREF_SOUNDCORE_AMBIENT_SOUND_CONTROL, soundmode);
+        editor.putInt(DeviceSettingsPreferenceConst.PREF_SONY_AMBIENT_SOUND_LEVEL, anc_strength);
+        editor.putBoolean(DeviceSettingsPreferenceConst.PREF_SOUNDCORE_TRANSPARENCY_VOCAL_MODE, vocal_mode);
+        editor.putBoolean(DeviceSettingsPreferenceConst.PREF_SOUNDCORE_ADAPTIVE_NOISE_CANCELLING, adaptive_anc);
+        editor.putBoolean(DeviceSettingsPreferenceConst.PREF_SOUNDCORE_WIND_NOISE_REDUCTION, windnoiseReduction);
+        editor.apply();
+    }
+
+    @Override
+    public byte[] encodeSendConfiguration(String config) {
+        Prefs prefs = getDevicePrefs();
+        String pref_string;
+
+        switch (config) {
+            // Ambient Sound Modes
+            case DeviceSettingsPreferenceConst.PREF_SOUNDCORE_AMBIENT_SOUND_CONTROL:
+            case DeviceSettingsPreferenceConst.PREF_SOUNDCORE_WIND_NOISE_REDUCTION:
+            case DeviceSettingsPreferenceConst.PREF_SOUNDCORE_TRANSPARENCY_VOCAL_MODE:
+            case DeviceSettingsPreferenceConst.PREF_SOUNDCORE_ADAPTIVE_NOISE_CANCELLING:
+            case DeviceSettingsPreferenceConst.PREF_SONY_AMBIENT_SOUND_LEVEL:
+                return encodeAudioMode();
+
+            // Control
+            case DeviceSettingsPreferenceConst.PREF_SOUNDCORE_CONTROL_SINGLE_TAP_DISABLED:
+                return encodeControlTouchLockMessage(TapAction.SINGLE_TAP, prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_SOUNDCORE_CONTROL_SINGLE_TAP_DISABLED, false));
+            case DeviceSettingsPreferenceConst.PREF_SOUNDCORE_CONTROL_DOUBLE_TAP_DISABLED:
+                return encodeControlTouchLockMessage(TapAction.DOUBLE_TAP, prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_SOUNDCORE_CONTROL_DOUBLE_TAP_DISABLED, false));
+            case DeviceSettingsPreferenceConst.PREF_SOUNDCORE_CONTROL_TRIPLE_TAP_DISABLED:
+                return encodeControlTouchLockMessage(TapAction.TRIPLE_TAP, prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_SOUNDCORE_CONTROL_TRIPLE_TAP_DISABLED, false));
+            case DeviceSettingsPreferenceConst.PREF_SOUNDCORE_CONTROL_LONG_PRESS_DISABLED:
+                return encodeControlTouchLockMessage(TapAction.LONG_PRESS, prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_SOUNDCORE_CONTROL_LONG_PRESS_DISABLED, false));
+
+            case DeviceSettingsPreferenceConst.PREF_SOUNDCORE_CONTROL_SINGLE_TAP_ACTION_LEFT:
+                pref_string = prefs.getString(DeviceSettingsPreferenceConst.PREF_SOUNDCORE_CONTROL_SINGLE_TAP_ACTION_LEFT, "");
+                return encodeControlFunctionMessage(TapAction.SINGLE_TAP, false, TapFunction.valueOf(pref_string));
+            case DeviceSettingsPreferenceConst.PREF_SOUNDCORE_CONTROL_SINGLE_TAP_ACTION_RIGHT:
+                pref_string = prefs.getString(DeviceSettingsPreferenceConst.PREF_SOUNDCORE_CONTROL_SINGLE_TAP_ACTION_RIGHT, "");
+                return encodeControlFunctionMessage(TapAction.SINGLE_TAP, true, TapFunction.valueOf(pref_string));
+            case DeviceSettingsPreferenceConst.PREF_SOUNDCORE_CONTROL_DOUBLE_TAP_ACTION_LEFT:
+                pref_string = prefs.getString(DeviceSettingsPreferenceConst.PREF_SOUNDCORE_CONTROL_DOUBLE_TAP_ACTION_LEFT, "");
+                return encodeControlFunctionMessage(TapAction.DOUBLE_TAP, false, TapFunction.valueOf(pref_string));
+            case DeviceSettingsPreferenceConst.PREF_SOUNDCORE_CONTROL_DOUBLE_TAP_ACTION_RIGHT:
+                pref_string = prefs.getString(DeviceSettingsPreferenceConst.PREF_SOUNDCORE_CONTROL_DOUBLE_TAP_ACTION_RIGHT, "");
+                return encodeControlFunctionMessage(TapAction.DOUBLE_TAP, true, TapFunction.valueOf(pref_string));
+            case DeviceSettingsPreferenceConst.PREF_SOUNDCORE_CONTROL_TRIPLE_TAP_ACTION_LEFT:
+                pref_string = prefs.getString(DeviceSettingsPreferenceConst.PREF_SOUNDCORE_CONTROL_TRIPLE_TAP_ACTION_LEFT, "");
+                return encodeControlFunctionMessage(TapAction.TRIPLE_TAP, false, TapFunction.valueOf(pref_string));
+            case DeviceSettingsPreferenceConst.PREF_SOUNDCORE_CONTROL_TRIPLE_TAP_ACTION_RIGHT:
+                pref_string = prefs.getString(DeviceSettingsPreferenceConst.PREF_SOUNDCORE_CONTROL_TRIPLE_TAP_ACTION_RIGHT, "");
+                return encodeControlFunctionMessage(TapAction.TRIPLE_TAP, true, TapFunction.valueOf(pref_string));
+            case DeviceSettingsPreferenceConst.PREF_SOUNDCORE_CONTROL_LONG_PRESS_ACTION_LEFT:
+                pref_string = prefs.getString(DeviceSettingsPreferenceConst.PREF_SOUNDCORE_CONTROL_LONG_PRESS_ACTION_LEFT, "");
+                return encodeControlFunctionMessage(TapAction.LONG_PRESS, false, TapFunction.valueOf(pref_string));
+            case DeviceSettingsPreferenceConst.PREF_SOUNDCORE_CONTROL_LONG_PRESS_ACTION_RIGHT:
+                pref_string = prefs.getString(DeviceSettingsPreferenceConst.PREF_SOUNDCORE_CONTROL_LONG_PRESS_ACTION_RIGHT, "");
+                return encodeControlFunctionMessage(TapAction.LONG_PRESS, true, TapFunction.valueOf(pref_string));
+
+            case DeviceSettingsPreferenceConst.PREF_SONY_AMBIENT_SOUND_CONTROL_BUTTON_MODE:
+                AmbientSoundControlButtonMode modes = AmbientSoundControlButtonMode.fromPreferences(prefs.getPreferences());
+                switch (modes) {
+                    case NC_AS_OFF:
+                        return encodeControlAmbientModeMessage(true, true, true);
+                    case NC_AS:
+                        return encodeControlAmbientModeMessage(true, true, false);
+                    case NC_OFF:
+                        return encodeControlAmbientModeMessage(true, false, true);
+                    case AS_OFF:
+                        return encodeControlAmbientModeMessage(false, true, true);
+                }
+
+            // Miscellaneous Settings
+            case DeviceSettingsPreferenceConst.PREF_SOUNDCORE_WEARING_DETECTION:
+                boolean wearingDetection = prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_SOUNDCORE_WEARING_DETECTION, false);
+                return new SoundcorePacket((short) 0x8101, new byte[]{encodeBoolean(wearingDetection)}).encode();
+            case DeviceSettingsPreferenceConst.PREF_SOUNDCORE_WEARING_TONE:
+                boolean wearingTone = prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_SOUNDCORE_WEARING_TONE, false);
+                return new SoundcorePacket((short) 0x8c01, new byte[]{encodeBoolean(wearingTone)}).encode();
+            case DeviceSettingsPreferenceConst.PREF_SOUNDCORE_TOUCH_TONE:
+                boolean touchTone = prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_SOUNDCORE_TOUCH_TONE, false);
+                return new SoundcorePacket((short) 0x8301, new byte[]{encodeBoolean(touchTone)}).encode();
+            default:
+                LOG.debug("Unsupported CONFIG: " + config);
+        }
+
+        return super.encodeSendConfiguration(config);
+    }
+
+    byte[] encodeDeviceInfoRequest() {
+        return new SoundcorePacket((short) 0x0101).encode();
+    }
+
+    byte[] encodeMysteryDataRequest1() {
+        byte[] payload = new byte[]{0x00};
+        return new SoundcorePacket((short) 0x8d01, payload).encode();
+    }
+    byte[] encodeMysteryDataRequest2() {
+        return new SoundcorePacket((short) 0x0105).encode();
+    }
+    byte[] encodeMysteryDataRequest3() {
+        byte[] payload = new byte[]{0x00};
+        return new SoundcorePacket((short) 0x8205, payload).encode();
+    }
+
+    /**
+     * Encodes the following settings to a payload to set the audio-mode on the headphones:
+     * PREF_SOUNDCORE_AMBIENT_SOUND_CONTROL If ANC, Transparent or neither should be active
+     * PREF_SOUNDCORE_ADAPTIVE_NOISE_CANCELLING If the strenght of the ANC should be set manual or adaptively according to ambient noise
+     * PREF_SONY_AMBIENT_SOUND_LEVEL How strong the ANC should be in manual mode
+     * PREF_SOUNDCORE_TRANSPARENCY_VOCAL_MODE If the Transparency should focus on vocals or should be fully transparent
+     * PREF_SOUNDCORE_WIND_NOISE_REDUCTION If Transparency or ANC should reduce Wind Noise
+     * @return The payload
+     */
+    private byte[] encodeAudioMode() {
+        Prefs prefs = getDevicePrefs();
+
+        byte anc_mode;
+        switch (prefs.getString(DeviceSettingsPreferenceConst.PREF_SOUNDCORE_AMBIENT_SOUND_CONTROL, "off")) {
+            case "noise_cancelling":
+                anc_mode = 0x00;
+                break;
+            case "ambient_sound":
+                anc_mode = 0x01;
+                break;
+            case "off":
+                anc_mode = 0x02;
+                break;
+            default:
+                LOG.error("Invalid Audio Mode selected");
+                return null;
+        }
+
+        byte anc_strength;
+        switch (prefs.getInt(DeviceSettingsPreferenceConst.PREF_SONY_AMBIENT_SOUND_LEVEL, 0)) {
+            case 0:
+                anc_strength = 0x10;
+                break;
+            case 1:
+                anc_strength = 0x20;
+                break;
+            case 2:
+                anc_strength = 0x30;
+                break;
+            default:
+                LOG.error("Invalid ANC Strength selected");
+                return null;
+        }
+
+        byte adaptive_anc = encodeBoolean(prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_SOUNDCORE_ADAPTIVE_NOISE_CANCELLING, true));
+        byte vocal_mode = encodeBoolean(prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_SOUNDCORE_TRANSPARENCY_VOCAL_MODE, false));
+        byte windnoise_reduction = encodeBoolean(prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_SOUNDCORE_WIND_NOISE_REDUCTION, false));
+
+        byte[] payload = new byte[]{anc_mode, anc_strength, vocal_mode, adaptive_anc, windnoise_reduction, 0x01};
+        return new SoundcorePacket((short) 0x8106, payload).encode();
+    }
+
+    /**
+     * Enables or disables a tap-action
+     * @param action The byte that encodes the action (single/double/triple or long tap)
+     * @param disabled If the action should be enabled or disabled
+     * @return
+     */
+    private byte[] encodeControlTouchLockMessage(TapAction action, boolean disabled) {
+        boolean enabled = !disabled;
+        byte enabled_byte;
+        byte[] payload;
+        switch (action) {
+            case SINGLE_TAP:
+            case TRIPLE_TAP:
+                enabled_byte = encodeBoolean(enabled);
+                break;
+            case DOUBLE_TAP:
+            case LONG_PRESS:
+                enabled_byte = enabled?(byte) 0x11: (byte) 0x10;
+                break;
+            default:
+                LOG.error("Invalid Tap action");
+                return null;
+        }
+        payload = new byte[]{0x00, action.getCode(), enabled_byte};
+        return new SoundcorePacket((short) 0x8304, payload).encode();
+    }
+
+    /**
+     * Assigns a function (eg play/pause) to an action (eg single tap on right bud)
+     * @param action The byte that encodes the action (single/double/triple or long tap)
+     * @param right  If the right or left earbud is meant
+     * @param function The byte that encodes the triggered function (eg play/pause)
+     * @return The encoded message
+     */
+    private byte[] encodeControlFunctionMessage(TapAction action, boolean right, TapFunction function) {
+        byte function_byte;
+        switch (action) {
+            case SINGLE_TAP:
+            case DOUBLE_TAP:
+                function_byte = (byte) (16*6 + function.getCode());
+                break;
+            case TRIPLE_TAP:
+                function_byte = (byte) (16*4 + function.getCode());
+                break;
+            case LONG_PRESS:
+                function_byte = (byte) (16*5 + function.getCode());
+                break;
+            default:
+                LOG.error("Invalid Tap action");
+                return null;
+        }
+        byte[] payload = new byte[] {encodeBoolean(right), action.getCode(), function_byte};
+        return new SoundcorePacket((short) 0x8104, payload).encode();
+    }
+
+    /**
+     * Encodes between which Audio Modes a tap should switch, if it is set to switch the Audio Mode.
+     * Zb ANC -> -> Transparency -> Normal -> ANC -> ....
+     */
+    private byte[] encodeControlAmbientModeMessage(boolean anc, boolean transparency, boolean normal) {
+        // Original app does not allow only one true flag. Unsure if Earbuds accept this state.
+        byte ambientModes = (byte) (4 * (normal?1:0) + 2 * (transparency?1:0) + (anc?1:0));
+        return new SoundcorePacket((short) 0x8206, new byte[] {ambientModes}).encode();
+    }
+
+    private byte encodeBoolean(boolean bool) {
+        if (bool) return 0x01;
+        else return 0x00;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/soundcore/liberty/TapAction.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/soundcore/liberty/TapAction.java
new file mode 100644
index 0000000..097acfb
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/soundcore/liberty/TapAction.java
@@ -0,0 +1,19 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.soundcore.liberty;
+
+enum TapAction {
+    SINGLE_TAP((byte) 0x02),
+    DOUBLE_TAP((byte) 0x00),
+    TRIPLE_TAP((byte) 0x05),
+    LONG_PRESS((byte) 0x01)
+    ;
+
+    private final byte code;
+
+    TapAction(final byte code) {
+        this.code = code;
+    }
+
+    public byte getCode() {
+        return code;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/soundcore/liberty/TapFunction.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/soundcore/liberty/TapFunction.java
new file mode 100644
index 0000000..70eb8ff
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/soundcore/liberty/TapFunction.java
@@ -0,0 +1,22 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.soundcore.liberty;
+
+enum TapFunction {
+    VOLUME_DOWN(1),
+    VOLUME_UP(0),
+    MEDIA_NEXT( 3),
+    MEDIA_PREV(2),
+    PLAYPAUSE(6),
+    VOICE_ASSISTANT(5),
+    AMBIENT_SOUND_CONTROL(4)
+    ;
+
+    private final int code;
+
+    TapFunction(final int code) {
+        this.code = code;
+    }
+
+    public int getCode() {
+        return code;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/soundcore/motion300/SoundcoreMotion300DeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/soundcore/motion300/SoundcoreMotion300DeviceSupport.java
new file mode 100644
index 0000000..487c140
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/soundcore/motion300/SoundcoreMotion300DeviceSupport.java
@@ -0,0 +1,43 @@
+/*  Copyright (C) 2024 Severin von Wnuck-Lipinski
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.soundcore.motion300;
+
+import nodomain.freeyourgadget.gadgetbridge.service.AbstractHeadphoneDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceIoThread;
+import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol;
+
+public class SoundcoreMotion300DeviceSupport extends AbstractHeadphoneDeviceSupport {
+    @Override
+    public boolean useAutoConnect() {
+        return false;
+    }
+
+    @Override
+    protected GBDeviceProtocol createDeviceProtocol() {
+        return new SoundcoreMotion300Protocol(getDevice());
+    }
+
+    @Override
+    protected GBDeviceIoThread createDeviceIOThread() {
+        return new SoundcoreMotion300IOThread(
+                getDevice(),
+                getContext(),
+                (SoundcoreMotion300Protocol)getDeviceProtocol(),
+                SoundcoreMotion300DeviceSupport.this,
+                getBluetoothAdapter());
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/soundcore/motion300/SoundcoreMotion300IOThread.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/soundcore/motion300/SoundcoreMotion300IOThread.java
new file mode 100644
index 0000000..8c48945
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/soundcore/motion300/SoundcoreMotion300IOThread.java
@@ -0,0 +1,75 @@
+/*  Copyright (C) 2024 Severin von Wnuck-Lipinski
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.soundcore.motion300;
+
+import android.bluetooth.BluetoothAdapter;
+import android.content.Context;
+import android.os.Handler;
+import android.os.ParcelUuid;
+import androidx.annotation.NonNull;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.btclassic.BtClassicIoThread;
+
+import static nodomain.freeyourgadget.gadgetbridge.util.GB.hexdump;
+
+public class SoundcoreMotion300IOThread extends BtClassicIoThread {
+    private static final Logger LOG = LoggerFactory.getLogger(SoundcoreMotion300IOThread.class);
+
+    private final SoundcoreMotion300Protocol protocol;
+    private final Handler handler = new Handler();
+
+    public SoundcoreMotion300IOThread(
+            GBDevice device,
+            Context context,
+            SoundcoreMotion300Protocol protocol,
+            SoundcoreMotion300DeviceSupport support,
+            BluetoothAdapter adapter) {
+        super(device, context, protocol, support, adapter);
+        this.protocol = protocol;
+    }
+
+    @Override
+    protected void initialize() {
+        setUpdateState(GBDevice.State.INITIALIZING);
+
+        // Device requires a little delay to respond to commands
+        handler.postDelayed(() -> write(protocol.encodeGetDeviceInfo()), 500);
+    }
+
+    @NonNull
+    protected UUID getUuidToConnect(@NonNull ParcelUuid[] uuids) {
+        return UUID.fromString("0cf12d31-fac3-4553-bd80-d6832e7b3135");
+    }
+
+    @Override
+    protected byte[] parseIncoming(InputStream stream) throws IOException {
+        byte[] buffer = new byte[1048576];
+        int bytes = stream.read(buffer);
+        LOG.debug("read " + bytes + " bytes. " + hexdump(buffer, 0, bytes));
+
+        return Arrays.copyOf(buffer, bytes);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/soundcore/motion300/SoundcoreMotion300Protocol.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/soundcore/motion300/SoundcoreMotion300Protocol.java
new file mode 100644
index 0000000..5f218ec
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/soundcore/motion300/SoundcoreMotion300Protocol.java
@@ -0,0 +1,385 @@
+/*  Copyright (C) 2024 Severin von Wnuck-Lipinski
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.soundcore.motion300;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Map;
+
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventSendBytes;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdateDeviceInfo;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdateDeviceState;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdatePreferences;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.BatteryState;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.soundcore.SoundcorePacket;
+import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.*;
+
+public class SoundcoreMotion300Protocol extends GBDeviceProtocol {
+    private static final Logger LOG = LoggerFactory.getLogger(SoundcoreMotion300Protocol.class);
+
+    // Some of these commands are not used right now, they serve as documentation
+    private static final short CMD_GET_DEVICE_INFO = (short)0x0101;
+    private static final short CMD_GET_LDAC_MODE = (short)0x7f01;
+    private static final short CMD_GET_BUTTON_BRIGHTNESS = (short)0x9310;
+    private static final short CMD_GET_EQUALIZER = (short)0x8902;
+    private static final short CMD_GET_CURRENT_DIRECTION = (short)0x8c02;
+
+    private static final short CMD_SET_VOICE_PROMPTS = (short)0x9001;
+    private static final short CMD_SET_BUTTON_BRIGHTNESS = (short)0x9210;
+    private static final short CMD_SET_AUTO_POWER_OFF = (short)0x8601;
+    private static final short CMD_SET_LDAC_MODE = (short)0xff01;
+    private static final short CMD_SET_ADAPTIVE_DIRECTION = (short)0x8a02;
+    private static final short CMD_SET_EQUALIZER_PRESET = (short)0x8b02;
+    private static final short CMD_SET_EQUALIZER_CUSTOM = (short)0x8d02;
+
+    private static final short CMD_NOTIFY_BATTERY_INFO = (short)0x0301;
+    private static final short CMD_NOTIFY_CHARGING_INFO = (short)0x0401;
+    private static final short CMD_NOTIFY_VOLUME_INFO = (short)0x0901;
+    private static final short CMD_NOTIFY_PLAYBACK_INFO = (short)0x2101;
+    private static final short CMD_NOTIFY_BASS_MODE = (short)0x8e02;
+
+    private static final short CMD_POWER_OFF = (short)0x8901;
+
+    public static final String[] EQUALIZER_PREFS_FREQ = new String[] {
+        PREF_SOUNDCORE_EQUALIZER_BAND1_FREQ,
+        PREF_SOUNDCORE_EQUALIZER_BAND2_FREQ,
+        PREF_SOUNDCORE_EQUALIZER_BAND3_FREQ,
+        PREF_SOUNDCORE_EQUALIZER_BAND4_FREQ,
+        PREF_SOUNDCORE_EQUALIZER_BAND5_FREQ,
+        PREF_SOUNDCORE_EQUALIZER_BAND6_FREQ,
+        PREF_SOUNDCORE_EQUALIZER_BAND7_FREQ,
+        PREF_SOUNDCORE_EQUALIZER_BAND8_FREQ,
+        PREF_SOUNDCORE_EQUALIZER_BAND9_FREQ
+    };
+
+    public static final String[] EQUALIZER_PREFS_VALUE = new String[] {
+        PREF_SOUNDCORE_EQUALIZER_BAND1_VALUE,
+        PREF_SOUNDCORE_EQUALIZER_BAND2_VALUE,
+        PREF_SOUNDCORE_EQUALIZER_BAND3_VALUE,
+        PREF_SOUNDCORE_EQUALIZER_BAND4_VALUE,
+        PREF_SOUNDCORE_EQUALIZER_BAND5_VALUE,
+        PREF_SOUNDCORE_EQUALIZER_BAND6_VALUE,
+        PREF_SOUNDCORE_EQUALIZER_BAND7_VALUE,
+        PREF_SOUNDCORE_EQUALIZER_BAND8_VALUE,
+        PREF_SOUNDCORE_EQUALIZER_BAND9_VALUE
+    };
+
+    private final GBDeviceEventBatteryInfo batteryInfo = new GBDeviceEventBatteryInfo();
+
+    protected SoundcoreMotion300Protocol(GBDevice device) {
+        super(device);
+    }
+
+    @Override
+    public GBDeviceEvent[] decodeResponse(byte[] data) {
+        ByteBuffer buf = ByteBuffer.wrap(data);
+        SoundcorePacket packet = SoundcorePacket.decode(buf);
+
+        if (packet == null)
+            return null;
+
+        short cmd = packet.getCommand();
+        byte[] payload = packet.getPayload();
+
+        switch (cmd) {
+            case CMD_GET_DEVICE_INFO:
+                return handlePacketDeviceInfo(payload);
+            case CMD_GET_LDAC_MODE:
+                return handlePacketLdacMode(payload);
+            case CMD_GET_BUTTON_BRIGHTNESS:
+                return handlePacketButtonBrightness(payload);
+            case CMD_GET_EQUALIZER:
+                return handlePacketEqualizer(payload);
+            case CMD_SET_ADAPTIVE_DIRECTION:
+                return handlePacketAdaptiveDirection(payload);
+            case CMD_NOTIFY_BATTERY_INFO:
+                return handlePacketBatteryInfo(payload);
+            case CMD_NOTIFY_CHARGING_INFO:
+                return handlePacketChargingInfo(payload);
+        }
+
+        return null;
+    }
+
+    private GBDeviceEvent[] handlePacketDeviceInfo(byte[] payload) {
+        if (payload.length != 29)
+            return null;
+
+        ByteBuffer buf = ByteBuffer.wrap(payload);
+        byte volume = buf.get();
+        byte batteryLevel = buf.get();
+        byte batteryCharging = buf.get();
+        byte currentlyPlaying = buf.get();
+        byte voicePrompts = buf.get();
+        byte autoPowerOffEnabled = buf.get();
+        byte autoPowerOffDuration = buf.get();
+        byte[] firmwareBytes = new byte[5];
+        byte[] serialBytes = new byte[17];
+
+        buf.get(firmwareBytes);
+        buf.get(serialBytes);
+
+        String fwVersion = new String(firmwareBytes, StandardCharsets.UTF_8);
+        String serialNumber = new String(serialBytes, StandardCharsets.UTF_8);
+
+        GBDeviceEventVersionInfo versionInfo = new GBDeviceEventVersionInfo();
+        versionInfo.fwVersion = fwVersion;
+        batteryInfo.state = batteryCharging == (byte)0x00 ? BatteryState.BATTERY_NORMAL : BatteryState.BATTERY_CHARGING;
+        batteryInfo.level = batteryLevel * 20;
+
+        LOG.debug(
+            "Device info: volume={}, battery={}, charging={}, playing={}, prompts={}, autoOff={}, autoOffDuration={}",
+            volume, batteryLevel, batteryCharging, currentlyPlaying, voicePrompts, autoPowerOffEnabled, autoPowerOffDuration
+        );
+
+        return new GBDeviceEvent[] {
+            versionInfo,
+            batteryInfo,
+            new GBDeviceEventUpdateDeviceInfo("SERIAL: ", serialNumber),
+            new GBDeviceEventUpdatePreferences(PREF_SOUNDCORE_VOICE_PROMPTS, voicePrompts != (byte)0x00),
+            new GBDeviceEventUpdatePreferences(PREF_SOUNDCORE_AUTO_POWER_OFF, String.valueOf(autoPowerOffDuration + 1)),
+            new GBDeviceEventSendBytes(encodeRequest(CMD_GET_LDAC_MODE)),
+        };
+    }
+
+    private GBDeviceEvent[] handlePacketLdacMode(byte[] payload) {
+        if (payload.length != 1)
+            return null;
+
+        return new GBDeviceEvent[] {
+            new GBDeviceEventUpdatePreferences(PREF_SOUNDCORE_LDAC_MODE, payload[0] != (byte)0x00),
+            new GBDeviceEventSendBytes(encodeRequest(CMD_GET_BUTTON_BRIGHTNESS)),
+        };
+    }
+
+    private GBDeviceEvent[] handlePacketButtonBrightness(byte[] payload) {
+        if (payload.length != 1)
+            return null;
+
+        return new GBDeviceEvent[] {
+            new GBDeviceEventUpdatePreferences(PREF_SOUNDCORE_BUTTON_BRIGHTNESS, String.valueOf(payload[0])),
+            new GBDeviceEventSendBytes(encodeRequest(CMD_GET_EQUALIZER)),
+        };
+    }
+
+    private GBDeviceEvent[] handlePacketEqualizer(byte[] payload) {
+        if (payload.length != 57)
+            return null;
+
+        // Get direction chosen in custom equalizer preferences
+        Prefs prefs = getDevicePrefs();
+        int equalizerDirection = Integer.parseInt(prefs.getString(PREF_SOUNDCORE_EQUALIZER_DIRECTION, "0"));
+
+        // Equalizer preset might be larger than 127 -> convert to unsigned int
+        ByteBuffer buf = ByteBuffer.wrap(payload);
+        byte adaptiveDirection = buf.get();
+        byte currentDirection = buf.get();
+        int equalizerPreset = Byte.toUnsignedInt(buf.get());
+        byte[] equalizer = new byte[18];
+
+        // Choose one of the three custom equalizer configurations
+        buf.position(buf.position() + equalizerDirection * equalizer.length);
+        buf.get(equalizer);
+
+        LOG.debug(
+            "Equalizer: adaptiveDirection={}, direction={}, preset={}",
+            adaptiveDirection, currentDirection, equalizerPreset
+        );
+
+        Map<String, Object> newPrefs = equalizerToPrefs(equalizer);
+        newPrefs.put(PREF_SOUNDCORE_ADAPTIVE_DIRECTION, adaptiveDirection != (byte)0x00);
+        newPrefs.put(PREF_SOUNDCORE_EQUALIZER_PRESET, String.valueOf(equalizerPreset));
+
+        return new GBDeviceEvent[] {
+            new GBDeviceEventUpdatePreferences(newPrefs),
+            new GBDeviceEventUpdateDeviceState(GBDevice.State.INITIALIZED),
+        };
+    }
+
+    private GBDeviceEvent[] handlePacketAdaptiveDirection(byte[] payload) {
+        if (payload.length != 0)
+            return null;
+
+        Prefs prefs = getDevicePrefs();
+
+        // Ignore if adaptive direction is enabled
+        if (prefs.getBoolean(PREF_SOUNDCORE_ADAPTIVE_DIRECTION, true))
+            return null;
+
+        // Set equalizer direction to "standing" and get current configuration
+        return new GBDeviceEvent[] {
+            new GBDeviceEventUpdatePreferences(PREF_SOUNDCORE_EQUALIZER_DIRECTION, "0"),
+            new GBDeviceEventSendBytes(encodeRequest(CMD_GET_EQUALIZER)),
+        };
+    }
+
+    private GBDeviceEvent[] handlePacketBatteryInfo(byte[] payload) {
+        if (payload.length != 1)
+            return null;
+
+        batteryInfo.level = payload[0] * 20;
+
+        return new GBDeviceEvent[] { batteryInfo };
+    }
+
+    private GBDeviceEvent[] handlePacketChargingInfo(byte[] payload) {
+        if (payload.length != 1)
+            return null;
+
+        batteryInfo.state = payload[0] == (byte)0x00 ? BatteryState.BATTERY_NORMAL : BatteryState.BATTERY_CHARGING;
+
+        return new GBDeviceEvent[] { batteryInfo };
+    }
+
+    @Override
+    public byte[] encodeSendConfiguration(String config) {
+        Prefs prefs = getDevicePrefs();
+
+        switch (config) {
+            case PREF_SOUNDCORE_VOICE_PROMPTS:
+                return encodeSetBoolean(prefs, PREF_SOUNDCORE_VOICE_PROMPTS, CMD_SET_VOICE_PROMPTS);
+            case PREF_SOUNDCORE_BUTTON_BRIGHTNESS:
+                return encodeSetByte(prefs, PREF_SOUNDCORE_BUTTON_BRIGHTNESS, CMD_SET_BUTTON_BRIGHTNESS);
+            case PREF_SOUNDCORE_AUTO_POWER_OFF:
+                return encodeSetAutoPowerOff(prefs);
+            case PREF_SOUNDCORE_LDAC_MODE:
+                return encodeSetBoolean(prefs, PREF_SOUNDCORE_LDAC_MODE, CMD_SET_LDAC_MODE);
+            case PREF_SOUNDCORE_ADAPTIVE_DIRECTION:
+                return encodeSetBoolean(prefs, PREF_SOUNDCORE_ADAPTIVE_DIRECTION, CMD_SET_ADAPTIVE_DIRECTION);
+            case PREF_SOUNDCORE_EQUALIZER_PRESET:
+                return encodeSetByte(prefs, PREF_SOUNDCORE_EQUALIZER_PRESET, CMD_SET_EQUALIZER_PRESET);
+            case PREF_SOUNDCORE_EQUALIZER_DIRECTION:
+                return encodeRequest(CMD_GET_EQUALIZER);
+            case PREF_SOUNDCORE_EQUALIZER_BAND1_FREQ:
+            case PREF_SOUNDCORE_EQUALIZER_BAND1_VALUE:
+            case PREF_SOUNDCORE_EQUALIZER_BAND2_FREQ:
+            case PREF_SOUNDCORE_EQUALIZER_BAND2_VALUE:
+            case PREF_SOUNDCORE_EQUALIZER_BAND3_FREQ:
+            case PREF_SOUNDCORE_EQUALIZER_BAND3_VALUE:
+            case PREF_SOUNDCORE_EQUALIZER_BAND4_FREQ:
+            case PREF_SOUNDCORE_EQUALIZER_BAND4_VALUE:
+            case PREF_SOUNDCORE_EQUALIZER_BAND5_FREQ:
+            case PREF_SOUNDCORE_EQUALIZER_BAND5_VALUE:
+            case PREF_SOUNDCORE_EQUALIZER_BAND6_FREQ:
+            case PREF_SOUNDCORE_EQUALIZER_BAND6_VALUE:
+            case PREF_SOUNDCORE_EQUALIZER_BAND7_FREQ:
+            case PREF_SOUNDCORE_EQUALIZER_BAND7_VALUE:
+            case PREF_SOUNDCORE_EQUALIZER_BAND8_FREQ:
+            case PREF_SOUNDCORE_EQUALIZER_BAND8_VALUE:
+            case PREF_SOUNDCORE_EQUALIZER_BAND9_FREQ:
+            case PREF_SOUNDCORE_EQUALIZER_BAND9_VALUE:
+                return encodeSetEqualizerCustom(prefs);
+        }
+
+        return super.encodeSendConfiguration(config);
+    }
+
+    private byte[] encodeRequest(short cmd) {
+        return new SoundcorePacket(cmd).encode();
+    }
+
+    public byte[] encodeGetDeviceInfo() {
+        return encodeRequest(CMD_GET_DEVICE_INFO);
+    }
+
+    @Override
+    public byte[] encodePowerOff() {
+        return encodeRequest(CMD_POWER_OFF);
+    }
+
+    private byte[] encodeSetBoolean(Prefs prefs, String pref, short cmd) {
+        boolean enabled = prefs.getBoolean(pref, true);
+        byte[] payload = new byte[] { enabled ? (byte)0x01 : (byte)0x00 };
+
+        return new SoundcorePacket(cmd, payload).encode();
+    }
+
+    private byte[] encodeSetByte(Prefs prefs, String pref, short cmd) {
+        byte value = (byte)Integer.parseInt(prefs.getString(pref, "0"));
+        byte[] payload = new byte[] { value };
+
+        return new SoundcorePacket(cmd, payload).encode();
+    }
+
+    private byte[] encodeSetAutoPowerOff(Prefs prefs) {
+        byte duration = (byte)Integer.parseInt(prefs.getString(PREF_SOUNDCORE_AUTO_POWER_OFF, "2"));
+        byte[] payload;
+
+        if (duration > 0)
+            payload = new byte[] { (byte)0x01, (byte)(duration - 1) };
+        else
+            payload = new byte[] { (byte)0x00, (byte)0x00 };
+
+        return new SoundcorePacket(CMD_SET_AUTO_POWER_OFF, payload).encode();
+    }
+
+    private byte[] encodeSetEqualizerCustom(Prefs prefs) {
+        ByteBuffer buf = ByteBuffer.allocate(21);
+        int eqDirection = Integer.parseInt(prefs.getString(PREF_SOUNDCORE_EQUALIZER_DIRECTION, "0"));
+        byte[] equalizer = equalizerFromPrefs(prefs);
+
+        // Bit combination of the equalizer directions that should be changed
+        buf.put((byte)(1 << eqDirection));
+        buf.put((byte)0x01);
+        buf.put((byte)0xff);
+        buf.put(equalizer);
+
+        return new SoundcorePacket(CMD_SET_EQUALIZER_CUSTOM, buf.array()).encode();
+    }
+
+    private Map<String, Object> equalizerToPrefs(byte[] equalizer) {
+        Map<String, Object> prefs = new HashMap<>();
+
+        for (int i = 0; i < EQUALIZER_PREFS_FREQ.length; i++) {
+            // Equalizer values range from 60 to 180, with 120 as "neutral"
+            byte freq = equalizer[i * 2 + 1];
+            int value = Byte.toUnsignedInt(equalizer[i * 2]);
+
+            // Map equalizer value to preference range (0 to 120)
+            prefs.put(EQUALIZER_PREFS_FREQ[i], freq);
+            prefs.put(EQUALIZER_PREFS_VALUE[i], value - 60);
+        }
+
+        return prefs;
+    }
+
+    private byte[] equalizerFromPrefs(Prefs prefs) {
+        byte[] equalizer = new byte[EQUALIZER_PREFS_FREQ.length * 2];
+
+        for (int i = 0; i < EQUALIZER_PREFS_FREQ.length; i++) {
+            int freq = Integer.parseInt(prefs.getString(EQUALIZER_PREFS_FREQ[i], "0"));
+            int value = prefs.getInt(EQUALIZER_PREFS_VALUE[i], 60);
+
+            // Map equalizer values from 0 - 120 back to 60 - 180
+            equalizer[i * 2 + 1] = ((byte)freq);
+            equalizer[i * 2] = (byte)(value + 60);
+        }
+
+        return equalizer;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/supercars/SuperCarsSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/supercars/SuperCarsSupport.java
new file mode 100644
index 0000000..0e32388
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/supercars/SuperCarsSupport.java
@@ -0,0 +1,224 @@
+/*  Copyright (C) 2022-2024 Arjan Schrijver, Damien Gaignon, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.supercars;
+
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
+import nodomain.freeyourgadget.gadgetbridge.devices.supercars.SuperCarsConstants;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.BatteryState;
+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.util.CryptoUtils;
+
+public class SuperCarsSupport extends AbstractBTLEDeviceSupport {
+    private static final Logger LOG = LoggerFactory.getLogger(SuperCarsSupport.class);
+    public static final String COMMAND_DRIVE_CONTROL = "nodomain.freeyourgadget.gadgetbridge.supercars.command.DRIVE_CONTROL";
+    public static final String EXTRA_DIRECTION = "EXTRA_DIRECTION";
+    public static final String EXTRA_MOVEMENT = "EXTRA_MOVEMENT";
+    public static final String EXTRA_SPEED = "EXTRA_SPEED";
+    public static final String EXTRA_LIGHT = "EXTRA_LIGHT";
+
+    public SuperCarsSupport() {
+        super(LOG);
+        addSupportedService(SuperCarsConstants.SERVICE_UUID_FFF);
+    }
+
+    @Override
+    protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
+        builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext()));
+        builder.notify(getCharacteristic(SuperCarsConstants.CHARACTERISTIC_UUID_FFF4), true); //for battery
+        builder.setCallback(this);
+        LocalBroadcastManager broadcastManager = LocalBroadcastManager.getInstance(getContext());
+
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(COMMAND_DRIVE_CONTROL);
+        broadcastManager.registerReceiver(commandReceiver, filter);
+        getDevice().setFirmwareVersion("N/A");
+        getDevice().setFirmwareVersion2("N/A");
+        builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZED, getContext()));
+        LOG.debug("Connected to: " + gbDevice.getName());
+        return builder;
+    }
+
+    BroadcastReceiver commandReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (intent.getAction().equals(COMMAND_DRIVE_CONTROL)) {
+                queueDataToBLE(
+                        (SuperCarsConstants.Speed) intent.getSerializableExtra(EXTRA_SPEED),
+                        (SuperCarsConstants.Movement) intent.getSerializableExtra(EXTRA_MOVEMENT),
+                        (SuperCarsConstants.Light) intent.getSerializableExtra(EXTRA_LIGHT),
+                        (SuperCarsConstants.Direction) intent.getSerializableExtra(EXTRA_DIRECTION)
+                );
+            }
+        }
+    };
+
+    @Override
+    public boolean onCharacteristicChanged(BluetoothGatt gatt,
+                                           BluetoothGattCharacteristic characteristic) {
+        if (super.onCharacteristicChanged(gatt, characteristic)) {
+            return true;
+        }
+
+        UUID characteristicUUID = characteristic.getUuid();
+        byte[] byte_data = characteristic.getValue();
+        LOG.debug("got characteristics: " + characteristicUUID);
+        /*
+        //keep here for now. This requires descriptor reading
+        if (getDevice().getFirmwareVersion() == "N/A") {
+            // this is probably not the best way to retrieve UUID descriptor
+            // but i have not found a better way at this moment
+            BluetoothGattCharacteristic characteristic1 = getCharacteristic(SuperCarsConstants.CHARACTERISTIC_UUID_FFF1);
+            BluetoothGattDescriptor descriptor1 = characteristic1.getDescriptor(GattDescriptor.UUID_DESCRIPTOR_GATT_CHARACTERISTIC_USER_DESCRIPTION);
+            gatt.readDescriptor(descriptor1);
+            getDevice().setFirmwareVersion(new String(descriptor1.getValue()));
+            getDevice().sendDeviceUpdateIntent(getContext());
+        } else if (getDevice().getFirmwareVersion2() == "N/A") {
+            BluetoothGattCharacteristic characteristic2 = getCharacteristic(SuperCarsConstants.CHARACTERISTIC_UUID_FFF2);
+            BluetoothGattDescriptor descriptor2 = characteristic2.getDescriptor(GattDescriptor.UUID_DESCRIPTOR_GATT_CHARACTERISTIC_USER_DESCRIPTION);
+            gatt.readDescriptor(descriptor2);
+            //getDevice().setFirmwareVersion2(new String(descriptor2.getValue()));
+            //getDevice().sendDeviceUpdateIntent(getContext());
+        }
+        */
+
+        if (SuperCarsConstants.CHARACTERISTIC_UUID_FFF4.equals(characteristicUUID)) {
+            byte[] decoded_data = decryptData(byte_data);
+
+            if (decoded_data.length == 16) {
+                GBDeviceEventBatteryInfo batteryEvent = new GBDeviceEventBatteryInfo();
+                batteryEvent.state = BatteryState.BATTERY_NORMAL;
+                int level = decoded_data[4];
+                batteryEvent.level = level;
+                evaluateGBDeviceEvent(batteryEvent);
+            }
+        }
+        return true;
+    }
+
+    private byte[] decryptData(byte[] data) {
+        byte[] decodedResult = new byte[0];
+        try {
+            decodedResult = CryptoUtils.decryptAES(data, SuperCarsConstants.aes_key);
+        } catch (Exception e) {
+            LOG.error("Error while decoding received data: " + e);
+        }
+        return decodedResult;
+    }
+
+    private byte[] encryptData(byte[] data) {
+        byte[] encodedResult = new byte[0];
+        try {
+            encodedResult = CryptoUtils.encryptAES(data, SuperCarsConstants.aes_key);
+        } catch (Exception e) {
+            LOG.error("Error while encoding data to be sent: " + e);
+        }
+        return encodedResult;
+    }
+
+    @Override
+    public boolean useAutoConnect() {
+        return false;
+    }
+
+    private void queueDataToBLE(SuperCarsConstants.Speed speed,
+                                SuperCarsConstants.Movement movement,
+                                SuperCarsConstants.Light light,
+                                SuperCarsConstants.Direction direction) {
+
+        byte[] command = craft_packet(speed, direction, movement, light);
+        TransactionBuilder builder = new TransactionBuilder("send data");
+        BluetoothGattCharacteristic writeCharacteristic = getCharacteristic(SuperCarsConstants.CHARACTERISTIC_UUID_FFF1);
+        builder.write(writeCharacteristic, encryptData(command));
+        builder.queue(getQueue());
+    }
+
+    private byte[] craft_packet(SuperCarsConstants.Speed speed,
+                                SuperCarsConstants.Direction direction,
+                                SuperCarsConstants.Movement movement,
+                                SuperCarsConstants.Light light) {
+
+        byte[] packet = new byte[]{0x0, 0x43, 0x54, 0x4c, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0};
+
+        switch (movement) {
+            case UP:
+                packet[4] = 0x1;
+                break;
+            case DOWN:
+                packet[5] = 0x1;
+                break;
+        }
+        switch (direction) {
+            case LEFT:
+                packet[6] = 0x1;
+                break;
+            case RIGHT:
+                packet[7] = 0x1;
+                break;
+        }
+        switch (light) {
+            case ON:
+                packet[8] = 0x0;
+                break;
+            case OFF:
+                packet[8] = 0x1;
+                break;
+        }
+        switch (speed) {
+            case NORMAL:
+                packet[9] = 0x50;
+                break;
+            case TURBO:
+                packet[9] = 0x64;
+                break;
+        }
+        return packet;
+    }
+
+    @Override
+    public void dispose() {
+        super.dispose();
+        LocalBroadcastManager.getInstance(getContext()).unregisterReceiver(commandReceiver);
+    }
+
+    @Override
+    public boolean getImplicitCallbackModify() {
+        return true;
+    }
+
+    @Override
+    public boolean getSendWriteRequestResponse() {
+        return false;
+    }
+}
+
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/test/TestDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/test/TestDeviceSupport.java
new file mode 100644
index 0000000..07b96d2
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/test/TestDeviceSupport.java
@@ -0,0 +1,84 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.test;
+
+import android.os.Handler;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.test.TestDeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.test.TestFeature;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.AbstractDeviceSupport;
+
+public class TestDeviceSupport extends AbstractDeviceSupport {
+    private static final Logger LOG = LoggerFactory.getLogger(TestDeviceSupport.class);
+
+    private final Handler handler = new Handler();
+
+    @Override
+    public boolean connect() {
+        LOG.info("Connecting");
+
+        getDevice().setState(GBDevice.State.CONNECTING);
+        getDevice().sendDeviceUpdateIntent(getContext());
+
+        handler.postDelayed(() -> {
+            LOG.info("Initialized");
+
+            getDevice().setFirmwareVersion("1.0.0");
+            getDevice().setFirmwareVersion2("N/A");
+            getDevice().setModel("0.1.7");
+
+            if (getCoordinator().supportsLedColor()) {
+                getDevice().setExtraInfo("led_color", 0xff3061e3);
+            } else {
+                getDevice().setExtraInfo("led_color", null);
+            }
+
+            if (getCoordinator().supports(getDevice(), TestFeature.FM_FREQUENCY)) {
+                getDevice().setExtraInfo("fm_frequency", 90.2f);
+            } else {
+                getDevice().setExtraInfo("fm_frequency", null);
+            }
+
+            // TODO battery percentages
+            // TODO hr measurements
+            // TODO app list
+            // TODO screenshots
+
+            getDevice().setState(GBDevice.State.INITIALIZED);
+            getDevice().sendDeviceUpdateIntent(getContext());
+        }, 1000);
+
+        return true;
+    }
+
+    @Override
+    public void dispose() {
+    }
+
+    @Override
+    public boolean useAutoConnect() {
+        return false;
+    }
+
+    protected TestDeviceCoordinator getCoordinator() {
+        return (TestDeviceCoordinator) getDevice().getDeviceCoordinator();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/tlw64/TLW64Support.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/tlw64/TLW64Support.java
new file mode 100644
index 0000000..8d0d99f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/tlw64/TLW64Support.java
@@ -0,0 +1,598 @@
+/*  Copyright (C) 2020-2024 115ek, Arjan Schrijver, Damien Gaignon
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+
+package nodomain.freeyourgadget.gadgetbridge.service.devices.tlw64;
+
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.text.format.DateFormat;
+import android.widget.Toast;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.List;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.SettingsActivity;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo;
+import nodomain.freeyourgadget.gadgetbridge.devices.tlw64.TLW64Constants;
+import nodomain.freeyourgadget.gadgetbridge.devices.tlw64.TLW64SampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.TLW64ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
+import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
+import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceBusyAction;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
+import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol;
+import nodomain.freeyourgadget.gadgetbridge.util.AlarmUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+import static org.apache.commons.lang3.math.NumberUtils.min;
+
+public class TLW64Support extends AbstractBTLEDeviceSupport {
+
+    private static final Logger LOG = LoggerFactory.getLogger(TLW64Support.class);
+
+    private final GBDeviceEventBatteryInfo batteryCmd = new GBDeviceEventBatteryInfo();
+    private final GBDeviceEventVersionInfo versionCmd = new GBDeviceEventVersionInfo();
+    public BluetoothGattCharacteristic ctrlCharacteristic = null;
+    public BluetoothGattCharacteristic notifyCharacteristic = null;
+    private List<TLW64ActivitySample> samples = new ArrayList<>();
+    private byte crc = 0;
+    private int firstTimestamp = 0;
+
+    public TLW64Support() {
+        super(LOG);
+        addSupportedService(TLW64Constants.UUID_SERVICE_NO1);
+    }
+
+    @Override
+    protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
+        LOG.info("Initializing");
+
+        builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext()));
+
+        ctrlCharacteristic = getCharacteristic(TLW64Constants.UUID_CHARACTERISTIC_CONTROL);
+        notifyCharacteristic = getCharacteristic(TLW64Constants.UUID_CHARACTERISTIC_NOTIFY);
+
+        builder.setCallback(this);
+        builder.notify(notifyCharacteristic, true);
+
+        setTime(builder);
+        setDisplaySettings(builder);
+        sendSettings(builder);
+
+        builder.write(ctrlCharacteristic, new byte[]{TLW64Constants.CMD_BATTERY});
+        builder.write(ctrlCharacteristic, new byte[]{TLW64Constants.CMD_FIRMWARE_VERSION});
+
+        builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZED, getContext()));
+
+        LOG.info("Initialization Done");
+
+        return builder;
+    }
+
+    @Override
+    public boolean useAutoConnect() {
+        return false;
+    }
+
+    @Override
+    public boolean onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
+        if (super.onCharacteristicChanged(gatt, characteristic)) {
+            return true;
+        }
+
+        UUID characteristicUUID = characteristic.getUuid();
+        byte[] data = characteristic.getValue();
+        if (data.length == 0)
+            return true;
+
+        switch (data[0]) {
+            case TLW64Constants.CMD_DISPLAY_SETTINGS:
+                LOG.info("Display settings updated");
+                return true;
+            case TLW64Constants.CMD_FIRMWARE_VERSION:
+                // TODO: firmware version reported "RM07JV000404" but original app: "RM07JV000404_15897"
+                versionCmd.fwVersion = new String(Arrays.copyOfRange(data, 1, data.length));
+                handleGBDeviceEvent(versionCmd);
+                LOG.info("Firmware version is: " + versionCmd.fwVersion);
+                return true;
+            case TLW64Constants.CMD_BATTERY:
+                batteryCmd.level = data[1];
+                handleGBDeviceEvent(batteryCmd);
+                LOG.info("Battery level is: " + data[1]);
+                return true;
+            case TLW64Constants.CMD_DATETIME:
+                LOG.info("Time is set to: " + (data[1] * 256 + ((int) data[2] & 0xff)) + "-" + data[3] + "-" + data[4] + " " + data[5] + ":" + data[6] + ":" + data[7]);
+                return true;
+            case TLW64Constants.CMD_USER_DATA:
+                LOG.info("User data updated");
+                return true;
+            case TLW64Constants.CMD_ALARM:
+                LOG.info("Alarm updated");
+                return true;
+            case TLW64Constants.CMD_FACTORY_RESET:
+                LOG.info("Factory reset requested");
+                return true;
+            case TLW64Constants.CMD_FETCH_STEPS:
+            case TLW64Constants.CMD_FETCH_SLEEP:
+                handleActivityData(data);
+                return true;
+            case TLW64Constants.CMD_NOTIFICATION:
+                LOG.info("Notification is displayed");
+                return true;
+            case TLW64Constants.CMD_ICON:
+                LOG.info("Icon is displayed");
+                return true;
+            case TLW64Constants.CMD_DEVICE_SETTINGS:
+                LOG.info("Device settings updated");
+                return true;
+            default:
+                LOG.warn("Unhandled characteristic change: " + characteristicUUID + " code: " + Arrays.toString(data));
+                return true;
+        }
+    }
+
+    @Override
+    public void onNotification(NotificationSpec notificationSpec) {
+        switch (notificationSpec.type) {
+            case GENERIC_SMS:
+                showNotification(TLW64Constants.NOTIFICATION_SMS, notificationSpec.sender);
+                setVibration(1, 1);
+                break;
+            case WECHAT:
+                showIcon(TLW64Constants.ICON_WECHAT);
+                setVibration(1, 1);
+                break;
+            default:
+                showIcon(TLW64Constants.ICON_MAIL);
+                setVibration(1, 1);
+                break;
+        }
+    }
+
+    @Override
+    public void onSetTime() {
+        try {
+            TransactionBuilder builder = performInitialized("setTime");
+            setTime(builder);
+            builder.queue(getQueue());
+        } catch (IOException e) {
+            GB.toast(getContext(), "Error setting time: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
+        }
+    }
+
+    @Override
+    public void onSetAlarms(ArrayList<? extends Alarm> alarms) {
+        try {
+            TransactionBuilder builder = performInitialized("Set alarm");
+            boolean anyAlarmEnabled = false;
+            for (Alarm alarm : alarms) {
+                anyAlarmEnabled |= alarm.getEnabled();
+                Calendar calendar = AlarmUtils.toCalendar(alarm);
+
+                int maxAlarms = 3;
+                if (alarm.getPosition() >= maxAlarms) {
+                    if (alarm.getEnabled()) {
+                        GB.toast(getContext(), "Only 3 alarms are supported.", Toast.LENGTH_LONG, GB.WARN);
+                    }
+                    return;
+                }
+
+                byte repetition = 0x00;
+
+                switch (alarm.getRepetition()) {
+                    // TODO: case Alarm.ALARM_ONCE is not supported! Need to notify user somehow...
+                    case Alarm.ALARM_MON:
+                        repetition |= TLW64Constants.ARG_SET_ALARM_REMINDER_REPEAT_MONDAY;
+                    case Alarm.ALARM_TUE:
+                        repetition |= TLW64Constants.ARG_SET_ALARM_REMINDER_REPEAT_TUESDAY;
+                    case Alarm.ALARM_WED:
+                        repetition |= TLW64Constants.ARG_SET_ALARM_REMINDER_REPEAT_WEDNESDAY;
+                    case Alarm.ALARM_THU:
+                        repetition |= TLW64Constants.ARG_SET_ALARM_REMINDER_REPEAT_THURSDAY;
+                    case Alarm.ALARM_FRI:
+                        repetition |= TLW64Constants.ARG_SET_ALARM_REMINDER_REPEAT_FRIDAY;
+                    case Alarm.ALARM_SAT:
+                        repetition |= TLW64Constants.ARG_SET_ALARM_REMINDER_REPEAT_SATURDAY;
+                    case Alarm.ALARM_SUN:
+                        repetition |= TLW64Constants.ARG_SET_ALARM_REMINDER_REPEAT_SUNDAY;
+                        break;
+
+                    default:
+                        LOG.warn("invalid alarm repetition " + alarm.getRepetition());
+                        break;
+                }
+
+                byte[] alarmMessage = new byte[]{
+                        TLW64Constants.CMD_ALARM,
+                        (byte) repetition,
+                        (byte) calendar.get(Calendar.HOUR_OF_DAY),
+                        (byte) calendar.get(Calendar.MINUTE),
+                        (byte) (alarm.getEnabled() ? 2 : 0),    // vibration duration
+                        (byte) (alarm.getEnabled() ? 10 : 0),   // vibration count
+                        (byte) (alarm.getEnabled() ? 2 : 0),    // unknown
+                        (byte) 0x00,
+                        (byte) (alarm.getPosition() + 1)
+                };
+                builder.write(ctrlCharacteristic, alarmMessage);
+            }
+            builder.queue(getQueue());
+            if (anyAlarmEnabled) {
+                GB.toast(getContext(), getContext().getString(R.string.user_feedback_miband_set_alarms_ok), Toast.LENGTH_SHORT, GB.INFO);
+            } else {
+                GB.toast(getContext(), getContext().getString(R.string.user_feedback_all_alarms_disabled), Toast.LENGTH_SHORT, GB.INFO);
+            }
+        } catch (IOException ex) {
+            GB.toast(getContext(), getContext().getString(R.string.user_feedback_miband_set_alarms_failed), Toast.LENGTH_LONG, GB.ERROR, ex);
+        }
+    }
+
+    @Override
+    public void onSetCallState(CallSpec callSpec) {
+        if (callSpec.command == CallSpec.CALL_INCOMING) {
+            showNotification(TLW64Constants.NOTIFICATION_CALL, callSpec.name);
+            setVibration(1, 30);
+        } else {
+            stopNotification();
+            setVibration(0, 0);
+        }
+    }
+
+    @Override
+    public void onFetchRecordedData(int dataTypes) {
+        sendFetchCommand(TLW64Constants.CMD_FETCH_STEPS);
+    }
+
+    @Override
+    public void onReset(int flags) {
+        if (flags == GBDeviceProtocol.RESET_FLAGS_FACTORY_RESET) {
+            try {
+                TransactionBuilder builder = performInitialized("factoryReset");
+                byte[] msg = new byte[]{
+                        TLW64Constants.CMD_FACTORY_RESET,
+                };
+                builder.write(ctrlCharacteristic, msg);
+                builder.queue(getQueue());
+            } catch (IOException e) {
+                GB.toast(getContext(), "Error during factory reset: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
+            }
+        }
+    }
+
+    @Override
+    public void onFindDevice(boolean start) {
+        if (start) {
+            setVibration(1, 3);
+        }
+    }
+
+    private void setVibration(int duration, int count) {
+        try {
+            TransactionBuilder builder = performInitialized("vibrate");
+            byte[] msg = new byte[]{
+                    TLW64Constants.CMD_ALARM,
+                    (byte) 0x00,
+                    (byte) 0x00,
+                    (byte) 0x00,
+                    (byte) duration,
+                    (byte) count,
+                    (byte) 0x07,       // unknown, sniffed by original app
+                    (byte) 0x01
+            };
+            builder.write(ctrlCharacteristic, msg);
+            builder.queue(getQueue());
+        } catch (IOException e) {
+            LOG.warn("Unable to set vibration", e);
+        }
+    }
+
+    private void setTime(TransactionBuilder transaction) {
+        Calendar c = GregorianCalendar.getInstance();
+        byte[] datetimeBytes = new byte[]{
+                TLW64Constants.CMD_DATETIME,
+                (byte) (c.get(Calendar.YEAR) / 256),
+                (byte) (c.get(Calendar.YEAR) % 256),
+                (byte) (c.get(Calendar.MONTH) + 1),
+                (byte) c.get(Calendar.DAY_OF_MONTH),
+                (byte) c.get(Calendar.HOUR_OF_DAY),
+                (byte) c.get(Calendar.MINUTE),
+                (byte) c.get(Calendar.SECOND)
+        };
+        transaction.write(ctrlCharacteristic, datetimeBytes);
+    }
+
+    private void setDisplaySettings(TransactionBuilder transaction) {
+        byte[] displayBytes = new byte[]{
+                TLW64Constants.CMD_DISPLAY_SETTINGS,
+                (byte) 0x00,   // 1 - display distance in kilometers, 2 - in miles
+                (byte) 0x00    // 1 - display 24-hour clock, 2 - for 12-hour with AM/PM
+        };
+        String units = GBApplication.getPrefs().getString(SettingsActivity.PREF_MEASUREMENT_SYSTEM, getContext().getString(R.string.p_unit_metric));
+        if (units.equals(getContext().getString(R.string.p_unit_metric))) {
+            displayBytes[1] = 1;
+        } else {
+            displayBytes[1] = 2;
+        }
+
+        String timeformat = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getString(DeviceSettingsPreferenceConst.PREF_TIMEFORMAT, "auto");
+        switch (timeformat) {
+            case "24h":
+                displayBytes[2] = 1;
+                break;
+            case "am/pm":
+                displayBytes[2] = 2;
+                break;
+            case "auto":
+            default:
+                if (DateFormat.is24HourFormat(getContext())) {
+                    displayBytes[2] = 1;
+                } else {
+                    displayBytes[2] = 2;
+                }
+        }
+
+        transaction.write(ctrlCharacteristic, displayBytes);
+        return;
+    }
+
+    private void sendSettings(TransactionBuilder builder) {
+        // TODO Create custom settings page for changing hardcoded values
+
+        // set user data
+        ActivityUser activityUser = new ActivityUser();
+        byte[] userBytes = new byte[]{
+                TLW64Constants.CMD_USER_DATA,
+                (byte) 0x00,  // unknown
+                (byte) 0x00,  // step length [cm]
+                (byte) 0x00,  // unknown
+                (byte) activityUser.getWeightKg(),
+                (byte) 0x05,  // screen on time / display timeout
+                (byte) 0x00,  // unknown
+                (byte) 0x00,  // unknown
+                (byte) (activityUser.getStepsGoal() / 256),
+                (byte) (activityUser.getStepsGoal() % 256),
+                (byte) 0x00,  // raise hand to turn on screen, ON = 1, OFF = 0
+                (byte) 0xff,  // unknown
+                (byte) 0x00,  // unknown
+                (byte) activityUser.getAge(),
+                (byte) 0x00,  // gender
+                (byte) 0x00,  // lost function, ON = 1, OFF = 0 TODO: find out what this does
+                (byte) 0x02   // unknown
+        };
+
+        if (GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getBoolean(DeviceSettingsPreferenceConst.PREF_LIFTWRIST_NOSHED, false)) {
+            userBytes[10] = (byte) 0x01;
+        }
+
+        if (activityUser.getGender() == ActivityUser.GENDER_FEMALE) {
+            userBytes[14] = 2; // female
+            // default and factor from https://livehealthy.chron.com/determine-stride-pedometer-height-weight-4518.html
+            if (activityUser.getHeightCm() != 0)
+                userBytes[2] = (byte) Math.ceil(activityUser.getHeightCm() * 0.413);
+            else
+                userBytes[2] = 70; // default
+        } else {
+            userBytes[14] = 1; // male
+            if (activityUser.getHeightCm() != 0)
+                userBytes[2] = (byte) Math.ceil(activityUser.getHeightCm() * 0.415);
+            else
+                userBytes[2] = 78; // default
+        }
+
+        builder.write(ctrlCharacteristic, userBytes);
+
+        // device settings
+        byte[] deviceBytes = new byte[]{
+                TLW64Constants.CMD_DEVICE_SETTINGS,
+                (byte) 0x00,   // 1 - turns on inactivity alarm
+                (byte) 0x3c,   // unknown, sniffed by original app
+                (byte) 0x02,   // unknown, sniffed by original app
+                (byte) 0x03,   // unknown, sniffed by original app
+                (byte) 0x01,   // unknown, sniffed by original app
+                (byte) 0x00    // unknown, sniffed by original app
+        };
+
+        if (GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getBoolean(DeviceSettingsPreferenceConst.PREF_INACTIVITY_ENABLE_NOSHED, false)) {
+            deviceBytes[1] = (byte) 0x01;
+        }
+
+        builder.write(ctrlCharacteristic, deviceBytes);
+    }
+
+    private void showIcon(int iconId) {
+        try {
+            TransactionBuilder builder = performInitialized("showIcon");
+            byte[] msg = new byte[]{
+                    TLW64Constants.CMD_ICON,
+                    (byte) iconId
+            };
+            builder.write(ctrlCharacteristic, msg);
+            builder.queue(getQueue());
+        } catch (IOException e) {
+            GB.toast(getContext(), "Error showing icon: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
+        }
+    }
+
+    private void showNotification(int type, String text) {
+        try {
+            TransactionBuilder builder = performInitialized("showNotification");
+            int length;
+            byte[] bytes;
+            byte[] msg;
+
+            // send text
+            bytes = text.getBytes("EUC-JP");
+            length = min(bytes.length, 18);
+            msg = new byte[length + 2];
+            msg[0] = TLW64Constants.CMD_NOTIFICATION;
+            msg[1] = TLW64Constants.NOTIFICATION_HEADER;
+            System.arraycopy(bytes, 0, msg, 2, length);
+            builder.write(ctrlCharacteristic, msg);
+
+            // send notification type
+            msg = new byte[2];
+            msg[0] = TLW64Constants.CMD_NOTIFICATION;
+            msg[1] = (byte) type;
+            builder.write(ctrlCharacteristic, msg);
+
+            builder.queue(getQueue());
+        } catch (IOException e) {
+            GB.toast(getContext(), "Error showing notificaton: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
+        }
+    }
+
+    private void stopNotification() {
+        try {
+            TransactionBuilder builder = performInitialized("clearNotification");
+            byte[] msg = new byte[]{
+                    TLW64Constants.CMD_NOTIFICATION,
+                    TLW64Constants.NOTIFICATION_STOP
+            };
+            builder.write(ctrlCharacteristic, msg);
+            builder.queue(getQueue());
+        } catch (IOException e) {
+            LOG.warn("Unable to stop notification", e);
+        }
+    }
+
+    private void sendFetchCommand(byte type) {
+        samples.clear();
+        crc = 0;
+        firstTimestamp = 0;
+        try {
+            TransactionBuilder builder = performInitialized("fetchActivityData");
+            builder.add(new SetDeviceBusyAction(getDevice(), getContext().getString(R.string.busy_task_fetch_activity_data), getContext()));
+            byte[] msg = new byte[]{
+                    type,
+                    (byte) 0xfa
+            };
+            builder.write(ctrlCharacteristic, msg);
+            builder.queue(getQueue());
+        } catch (IOException e) {
+            GB.toast(getContext(), "Error fetching activity data: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
+        }
+    }
+
+    private void handleActivityData(byte[] data) {
+        if (data[1] == (byte) 0xfd) {
+            LOG.info("CRC received: {}, calculated: {}", data[2] & 0xff, crc & 0xff);
+            if (data[2] != crc) {
+                GB.toast(getContext(), "Incorrect CRC. Try fetching data again.", Toast.LENGTH_LONG, GB.ERROR);
+                GB.updateTransferNotification(null, "Data transfer failed", false, 0, getContext());
+                if (getDevice().isBusy()) {
+                    getDevice().unsetBusyTask();
+                    getDevice().sendDeviceUpdateIntent(getContext());
+                }
+            } else if (!samples.isEmpty()) {
+                try (DBHandler dbHandler = GBApplication.acquireDB()) {
+                    Long userId = DBHelper.getUser(dbHandler.getDaoSession()).getId();
+                    Long deviceId = DBHelper.getDevice(getDevice(), dbHandler.getDaoSession()).getId();
+                    TLW64SampleProvider provider = new TLW64SampleProvider(getDevice(), dbHandler.getDaoSession());
+                    for (int i = 0; i < samples.size(); i++) {
+                        samples.get(i).setDeviceId(deviceId);
+                        samples.get(i).setUserId(userId);
+                        if (data[0] == TLW64Constants.CMD_FETCH_STEPS) {
+                            samples.get(i).setRawKind(ActivityKind.ACTIVITY.getCode());
+                            samples.get(i).setRawIntensity(samples.get(i).getSteps());
+                        } else if (data[0] == TLW64Constants.CMD_FETCH_SLEEP) {
+                            if (samples.get(i).getRawIntensity() < 7) {
+                                samples.get(i).setRawKind(ActivityKind.DEEP_SLEEP.getCode());
+                            } else
+                                samples.get(i).setRawKind(ActivityKind.LIGHT_SLEEP.getCode());
+                        }
+                        provider.addGBActivitySample(samples.get(i));
+                    }
+                    LOG.info("Activity data saved");
+                    if (data[0] == TLW64Constants.CMD_FETCH_STEPS) {
+                        sendFetchCommand(TLW64Constants.CMD_FETCH_SLEEP);
+                    } else {
+                        GB.updateTransferNotification(null, "", false, 100, getContext());
+                        if (getDevice().isBusy()) {
+                            getDevice().unsetBusyTask();
+                            getDevice().sendDeviceUpdateIntent(getContext());
+                            GB.signalActivityDataFinish(getDevice());
+                        }
+                    }
+                } catch (Exception ex) {
+                    GB.toast(getContext(), "Error saving activity data: " + ex.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
+                    GB.updateTransferNotification(null, "Data transfer failed", false, 0, getContext());
+                }
+            }
+        } else {
+            TLW64ActivitySample sample = new TLW64ActivitySample();
+
+            Calendar timestamp = GregorianCalendar.getInstance();
+            timestamp.set(Calendar.YEAR, data[1] * 256 + (data[2] & 0xff));
+            timestamp.set(Calendar.MONTH, (data[3] - 1) & 0xff);
+            timestamp.set(Calendar.DAY_OF_MONTH, data[4] & 0xff);
+            timestamp.set(Calendar.HOUR_OF_DAY, data[5] & 0xff);
+            timestamp.set(Calendar.SECOND, 0);
+
+            int startProgress = 0;
+            if (data[0] == TLW64Constants.CMD_FETCH_STEPS) {
+                timestamp.set(Calendar.MINUTE, 0);
+                sample.setSteps(data[6] * 256 + (data[7] & 0xff));
+                //noinspection lossy-conversions
+                crc ^= (data[6] ^ data[7]);
+            } else if (data[0] == TLW64Constants.CMD_FETCH_SLEEP) {
+                timestamp.set(Calendar.MINUTE, data[6] & 0xff);
+                sample.setRawIntensity(data[7] * 256 + (data[8] & 0xff));
+                //noinspection lossy-conversions
+                crc ^= (data[7] ^ data[8]);
+                startProgress = 33;
+            }
+
+            sample.setTimestamp((int) (timestamp.getTimeInMillis() / 1000L));
+            samples.add(sample);
+
+            if (firstTimestamp == 0)
+                firstTimestamp = sample.getTimestamp();
+            int progress = startProgress + 33 * (sample.getTimestamp() - firstTimestamp) /
+                    ((int) (Calendar.getInstance().getTimeInMillis() / 1000L) - firstTimestamp);
+            GB.updateTransferNotification(null, getContext().getString(R.string.busy_task_fetch_activity_data), true, progress, getContext());
+        }
+    }
+
+    @Override
+    public boolean getImplicitCallbackModify() {
+        return true;
+    }
+
+    @Override
+    public boolean getSendWriteRequestResponse() {
+        return false;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/um25/Data/CaptureGroup.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/um25/Data/CaptureGroup.java
new file mode 100644
index 0000000..cd3d2a1
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/um25/Data/CaptureGroup.java
@@ -0,0 +1,55 @@
+/*  Copyright (C) 2020-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.um25.Data;
+
+import java.io.Serializable;
+
+public class CaptureGroup implements Serializable {
+    private int index;
+    private int flownCurrent;
+    private int flownWattage;
+
+    public CaptureGroup(int index, int flownCurrent, int flownWattage) {
+        this.flownCurrent = flownCurrent;
+        this.flownWattage = flownWattage;
+        this.index = index;
+    }
+
+    public int getIndex() {
+        return index;
+    }
+
+    public void setIndex(int index) {
+        this.index = index;
+    }
+
+    public int getFlownCurrent() {
+        return flownCurrent;
+    }
+
+    public void setFlownCurrent(int flownCurrent) {
+        this.flownCurrent = flownCurrent;
+    }
+
+    public int getFlownWattage() {
+        return flownWattage;
+    }
+
+    public void setFlownWattage(int flownWattage) {
+        this.flownWattage = flownWattage;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/um25/Data/MeasurementData.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/um25/Data/MeasurementData.java
new file mode 100644
index 0000000..48927b9
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/um25/Data/MeasurementData.java
@@ -0,0 +1,103 @@
+/*  Copyright (C) 2020-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.um25.Data;
+
+import java.io.Serializable;
+
+public class MeasurementData implements Serializable {
+    private int voltage; // voltage in millivolts
+    private int current; // current in milliampere
+    private int wattage; // wattage in milliwatt
+    private int temperatureCelcius;
+    private int temperatureFahreheit;
+    private CaptureGroup[] captureGroups;
+    private int voltageDataPositive;
+    private int voltageDataNegative;
+    private int chargedCurrent; // charged current in milliAmpereHours
+    private int chargedWattage; // charged current in milliWattHours
+    private int thresholdCurrent; // threshold current for charging detection
+    private int chargingSeconds;
+    private int cableResistance; // cable resistance in ohms
+
+    public MeasurementData(int voltage, int current, int wattage, int temperatureCelcius, int temperatureFahreheit, CaptureGroup[] captureGroups, int voltageDataPositive, int voltageDataNegative, int chargedCurrent, int chargedWattage, int thresholdCurrent, int chargingSeconds, int cableResistance) {
+        this.voltage = voltage;
+        this.current = current;
+        this.wattage = wattage;
+        this.temperatureCelcius = temperatureCelcius;
+        this.temperatureFahreheit = temperatureFahreheit;
+        this.captureGroups = captureGroups;
+        this.voltageDataPositive = voltageDataPositive;
+        this.voltageDataNegative = voltageDataNegative;
+        this.chargedCurrent = chargedCurrent;
+        this.chargedWattage = chargedWattage;
+        this.thresholdCurrent = thresholdCurrent;
+        this.chargingSeconds = chargingSeconds;
+        this.cableResistance = cableResistance;
+    }
+
+    public int getVoltage() {
+        return voltage;
+    }
+
+    public int getCurrent() {
+        return current;
+    }
+
+    public int getWattage() {
+        return wattage;
+    }
+
+    public int getTemperatureCelcius() {
+        return temperatureCelcius;
+    }
+
+    public int getTemperatureFahreheit() {
+        return temperatureFahreheit;
+    }
+
+    public CaptureGroup[] getCaptureGroups() {
+        return captureGroups;
+    }
+
+    public int getVoltageDataPositive() {
+        return voltageDataPositive;
+    }
+
+    public int getVoltageDataNegative() {
+        return voltageDataNegative;
+    }
+
+    public int getChargedCurrent() {
+        return chargedCurrent;
+    }
+
+    public int getChargedWattage() {
+        return chargedWattage;
+    }
+
+    public int getThresholdCurrent() {
+        return thresholdCurrent * 10;
+    }
+
+    public int getChargingSeconds() {
+        return chargingSeconds;
+    }
+
+    public int getCableResistance() {
+        return cableResistance;
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/um25/Support/UM25BaseSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/um25/Support/UM25BaseSupport.java
new file mode 100644
index 0000000..3677fa0
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/um25/Support/UM25BaseSupport.java
@@ -0,0 +1,42 @@
+/*  Copyright (C) 2020-2024 Arjan Schrijver, Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.um25.Support;
+
+import org.slf4j.Logger;
+
+import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
+
+public class UM25BaseSupport extends AbstractBTLEDeviceSupport {
+    public UM25BaseSupport(Logger logger) {
+        super(logger);
+    }
+
+    @Override
+    public boolean useAutoConnect() {
+        return false;
+    }
+
+    @Override
+    public boolean getImplicitCallbackModify() {
+        return true;
+    }
+
+    @Override
+    public boolean getSendWriteRequestResponse() {
+        return false;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/um25/Support/UM25Support.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/um25/Support/UM25Support.java
new file mode 100644
index 0000000..40deb09
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/um25/Support/UM25Support.java
@@ -0,0 +1,286 @@
+/*  Copyright (C) 2020-2024 Daniel Dakhno, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.um25.Support;
+
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.SharedPreferences;
+
+import androidx.core.app.NotificationCompat;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.BufferOverflowException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.UUID;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+import nodomain.freeyourgadget.gadgetbridge.BuildConfig;
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+import nodomain.freeyourgadget.gadgetbridge.devices.um25.Activity.DataActivity;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEAction;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.um25.Data.CaptureGroup;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.um25.Data.MeasurementData;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+import nodomain.freeyourgadget.gadgetbridge.util.PendingIntentUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
+
+public class UM25Support extends UM25BaseSupport {
+    public static final String UUID_SERVICE = "0000ffe0-0000-1000-8000-00805f9b34fb";
+    public static final String UUID_CHAR    = "0000ffe1-0000-1000-8000-00805f9b34fb";
+
+    public static final String ACTION_MEASUREMENT_TAKEN = "com.nodomain.gadgetbridge.um25.MEASUREMENT_TAKEN";
+    public static final String ACTION_RESET_STATS = "com.nodomain.gadgetbridge.um25.RESET_STATS";
+    public static final String EXTRA_KEY_MEASUREMENT_DATA = "EXTRA_MEASUREMENT_DATA";
+    public static final int LOOP_DELAY = 500;
+
+    private final byte[] COMMAND_UPDATE = new byte[]{(byte) 0xF0};
+    private final byte[] COMMAND_RESET_STATS = new byte[]{(byte) 0xF4};
+    private final int PAYLOAD_LENGTH = 130;
+
+    private ByteBuffer buffer = ByteBuffer.allocate(PAYLOAD_LENGTH);
+
+    ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(1);
+
+    private static final  Logger logger = LoggerFactory.getLogger(UM25Support.class);
+
+    SharedPreferences preferences;
+
+    private boolean notifyOnCurrentThreshold;
+    private int notificationCurrentThreshold;
+    private boolean wasOverNotificationCurrent = false;
+    private long lastOverThresholdTimestamp = 0;
+
+    public UM25Support() {
+        super(logger);
+        addSupportedService(UUID.fromString(UUID_SERVICE));
+        this.buffer.mark();
+    }
+
+    BroadcastReceiver resetReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if(!ACTION_RESET_STATS.equals(intent.getAction())){
+                return;
+            }
+            new TransactionBuilder("reset stats")
+                    .write(getCharacteristic(UUID.fromString(UUID_CHAR)), COMMAND_RESET_STATS)
+                    .queue(getQueue());
+        }
+    };
+
+    void readPreferences(){
+        notifyOnCurrentThreshold = preferences.getBoolean(DeviceSettingsPreferenceConst.PREF_UM25_SHOW_THRESHOLD_NOTIFICATION, false);
+        notificationCurrentThreshold = Integer.parseInt(preferences.getString(DeviceSettingsPreferenceConst.PREF_UM25_SHOW_THRESHOLD, "100"));
+    }
+
+    @Override
+    public void onSendConfiguration(String config) {
+        readPreferences();
+    }
+
+    @Override
+    protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
+        preferences = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress());
+
+        readPreferences();
+
+        getDevice().setFirmwareVersion("1.0");
+
+        return builder
+                .add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext()))
+                .notify(getCharacteristic(UUID.fromString(UUID_CHAR)), true)
+                .add(new BtLEAction(null) {
+                    @Override
+                    public boolean expectsResult() {
+                        return false;
+                    }
+
+                    @Override
+                    public boolean run(BluetoothGatt gatt) {
+                        logger.debug("initialized, starting timers");
+                        LocalBroadcastManager.getInstance(getContext())
+                                .registerReceiver(
+                                        resetReceiver,
+                                        new IntentFilter(ACTION_RESET_STATS)
+                                );
+                        startLoop();
+                        return true;
+                    }
+                })
+                .add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZED, getContext()));
+    }
+
+    @Override
+    public void dispose() {
+        super.dispose();
+        LocalBroadcastManager.getInstance(getContext())
+                .unregisterReceiver(resetReceiver);
+        executor.shutdown();
+    }
+
+    private void startLoop(){
+        executor.scheduleWithFixedDelay(this::sendReadCommand, 0, LOOP_DELAY, TimeUnit.MILLISECONDS);
+    }
+
+    private void sendReadCommand(){
+        if(!getDevice().isConnected()){
+            logger.debug("device disconnected, stopping executor");
+            executor.shutdown();
+            return;
+        }
+
+        logger.debug("sending read command");
+        buffer.reset();
+        new TransactionBuilder("send read command")
+                .write(getCharacteristic(UUID.fromString(UUID_CHAR)), COMMAND_UPDATE)
+                .queue(getQueue());
+        logger.debug("sent command");
+    }
+
+    @Override
+    public boolean onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
+        if(!characteristic.getUuid().toString().equals(UUID_CHAR)) return false;
+
+        try{
+            buffer.put(characteristic.getValue());
+
+            if(buffer.position() == PAYLOAD_LENGTH){
+                handlePayload(buffer);
+            }
+        }catch (BufferOverflowException e){
+            logger.error("buffer overflow");
+        }
+
+        return true;
+    }
+
+    private void handleCurrentNotification(int currentMa){
+        logger.debug("current: " + currentMa);
+
+        if(!notifyOnCurrentThreshold){
+            return;
+        }
+
+        boolean isOverNotificationCurrent = currentMa > notificationCurrentThreshold;
+
+        long now = System.currentTimeMillis();
+
+        if(isOverNotificationCurrent){
+            lastOverThresholdTimestamp = now;
+            wasOverNotificationCurrent = true;
+            return;
+        }
+        long deltaSinceOverThreshold = now - lastOverThresholdTimestamp;
+
+        if(deltaSinceOverThreshold < 5000){
+            // must be below threshold for over certain time before triggering notification
+            return;
+        }
+
+        if(wasOverNotificationCurrent){
+            // handle change from over threshold to below threshold
+            wasOverNotificationCurrent = false;
+            Intent activityIntent = new Intent(getContext(), DataActivity.class);
+            activityIntent.setPackage(BuildConfig.APPLICATION_ID);
+            Notification notification = new NotificationCompat.Builder(getContext(), GB.NOTIFICATION_CHANNEL_HIGH_PRIORITY_ID)
+                    .setSmallIcon(R.drawable.ic_notification_low_battery)
+                    .setContentTitle("USB current")
+                    .setContentText("USB current below threshold")
+                    .setContentIntent(PendingIntentUtils.getActivity(getContext(), 0, activityIntent, PendingIntent.FLAG_CANCEL_CURRENT, false))
+                    .build();
+
+            GB.notify(
+                    GB.NOTIFICATION_ID_LOW_BATTERY,
+                    notification,
+                    getContext()
+            );
+        }
+    }
+
+    private void handlePayload(ByteBuffer payload){
+        String payloadString = StringUtils.bytesToHex(payload.array());
+        payloadString = payloadString.replaceAll("(..)", "$1 ");
+        logger.debug("payload: " + payloadString);
+        payload.order(ByteOrder.BIG_ENDIAN);
+        int voltage = payload.getShort(2);
+        int current = payload.getShort(4);
+        int wattage = payload.getShort(8);
+        int temperatureCelsius = payload.getShort(10);
+        int temperatureFahrenheit = payload.getShort(12);
+
+        final int STORAGE_START = 16;
+
+        CaptureGroup[] groups = new CaptureGroup[10];
+
+        for(int i = 0; i < 10; i++){
+            groups[i] = new CaptureGroup(
+                    i,
+                    payload.getInt(STORAGE_START + i * 4 + 0),
+                    payload.getInt(STORAGE_START + i * 4 + 4)
+            );
+        }
+
+        int voltagePositive = payload.getShort(96);
+        int voltageNegative = payload.getShort(98);
+        int chargedCurrent = payload.getInt(102);
+        int chargedWattage = payload.getInt(106);
+        int thresholdCurrent = payload.get(111);
+        int chargingSeconds = payload.getInt(112);
+        int cableResistance = payload.getInt(122);
+
+        MeasurementData data = new MeasurementData(
+                voltage,
+                current,
+                wattage,
+                temperatureCelsius,
+                temperatureFahrenheit,
+                groups,
+                voltagePositive,
+                voltageNegative,
+                chargedCurrent,
+                chargedWattage,
+                thresholdCurrent,
+                chargingSeconds,
+                cableResistance
+        );
+
+        Intent measurementIntent = new Intent(ACTION_MEASUREMENT_TAKEN);
+
+        measurementIntent.putExtra(EXTRA_KEY_MEASUREMENT_DATA, data);
+
+        handleCurrentNotification(current / 10);
+
+        LocalBroadcastManager.getInstance(getContext())
+                .sendBroadcast(measurementIntent);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/unknown/UnknownDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/unknown/UnknownDeviceSupport.java
new file mode 100644
index 0000000..9dfc26b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/unknown/UnknownDeviceSupport.java
@@ -0,0 +1,35 @@
+/*  Copyright (C) 2023-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.unknown;
+
+import nodomain.freeyourgadget.gadgetbridge.service.AbstractDeviceSupport;
+
+public class UnknownDeviceSupport extends AbstractDeviceSupport {
+    @Override
+    public boolean connect() {
+        return false;
+    }
+
+    @Override
+    public void dispose() {
+    }
+
+    @Override
+    public boolean useAutoConnect() {
+        return false;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/vesc/CommandType.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/vesc/CommandType.java
new file mode 100644
index 0000000..914df84
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/vesc/CommandType.java
@@ -0,0 +1,63 @@
+/*  Copyright (C) 2021-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.vesc;
+
+public enum CommandType {
+    COMM_FW_VERSION,
+    COMM_JUMP_TO_BOOTLOADER,
+    COMM_ERASE_NEW_APP,
+    COMM_WRITE_NEW_APP_DATA,
+    COMM_GET_VALUES,
+    COMM_SET_DUTY,
+    COMM_SET_CURRENT,
+    COMM_SET_CURRENT_BRAKE,
+    COMM_SET_RPM,
+    COMM_SET_POS,
+    COMM_SET_HANDBRAKE,
+    COMM_SET_DETECT,
+    COMM_SET_SERVO_POS,
+    COMM_SET_MCCONF,
+    COMM_GET_MCCONF,
+    COMM_GET_MCCONF_DEFAULT,
+    COMM_SET_APPCONF,
+    COMM_GET_APPCONF,
+    COMM_GET_APPCONF_DEFAULT,
+    COMM_SAMPLE_PRINT,
+    COMM_TERMINAL_CMD,
+    COMM_PRINT,
+    COMM_ROTOR_POSITION,
+    COMM_EXPERIMENT_SAMPLE,
+    COMM_DETECT_MOTOR_PARAM,
+    COMM_DETECT_MOTOR_R_L,
+    COMM_DETECT_MOTOR_FLUX_LINKAGE,
+    COMM_DETECT_ENCODER,
+    COMM_DETECT_HALL_FOC,
+    COMM_REBOOT,
+    COMM_ALIVE,
+    COMM_GET_DECODED_PPM,
+    COMM_GET_DECODED_ADC,
+    COMM_GET_DECODED_CHUK,
+    COMM_FORWARD_CAN,
+    COMM_SET_CHUCK_DATA,
+    COMM_CUSTOM_APP_DATA,
+    COMM_NRF_START_PAIRING
+    ;
+
+    public byte getCommandByte(){
+        return (byte) this.ordinal();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/vesc/VescBaseDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/vesc/VescBaseDeviceSupport.java
new file mode 100644
index 0000000..7c7f045
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/vesc/VescBaseDeviceSupport.java
@@ -0,0 +1,45 @@
+/*  Copyright (C) 2021-2024 Arjan Schrijver, Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.vesc;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
+
+public class VescBaseDeviceSupport extends AbstractBTLEDeviceSupport {
+    private static final Logger LOG = LoggerFactory.getLogger(VescBaseDeviceSupport.class);
+
+    public VescBaseDeviceSupport() {
+        super(LOG);
+    }
+
+    @Override
+    public boolean useAutoConnect() {
+        return false;
+    }
+
+    @Override
+    public boolean getImplicitCallbackModify() {
+        return true;
+    }
+
+    @Override
+    public boolean getSendWriteRequestResponse() {
+        return false;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/vesc/VescDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/vesc/VescDeviceSupport.java
new file mode 100644
index 0000000..c9d7e2d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/vesc/VescDeviceSupport.java
@@ -0,0 +1,338 @@
+/*  Copyright (C) 2021-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.vesc;
+
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.bluetooth.BluetoothGattDescriptor;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.SharedPreferences;
+
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+import nodomain.freeyourgadget.gadgetbridge.devices.vesc.VescCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
+import nodomain.freeyourgadget.gadgetbridge.util.CheckSums;
+
+public class VescDeviceSupport extends VescBaseDeviceSupport {
+    BluetoothGattCharacteristic serialWriteCharacteristic, serialReadCharacteristic;
+
+    public static final String COMMAND_SET_RPM = "nodomain.freeyourgadget.gadgetbridge.vesc.command.SET_RPM";
+    public static final String COMMAND_SET_CURRENT = "nodomain.freeyourgadget.gadgetbridge.vesc.command.SET_CURRENT";
+    public static final String COMMAND_SET_BREAK_CURRENT = "nodomain.freeyourgadget.gadgetbridge.vesc.command.SET_BREAK_CURRENT";
+    public static final String COMMAND_GET_VALUES = "nodomain.freeyourgadget.gadgetbridge.vesc.command.GET_VALUES";
+    public static final String EXTRA_RPM = "EXTRA_RPM";
+    public static final String EXTRA_CURRENT = "EXTRA_CURRENT";
+    public static final String EXTRA_VOLTAGE = "EXTRA_VOLTAGE";
+
+    public static final String ACTION_GOT_VALUES = "nodomain.freeyourgadget.gadgetbridge.vesc.action.GOT_VALUES";
+
+    private Logger logger = LoggerFactory.getLogger(getClass());
+
+    private DeviceType deviceType;
+
+    private ByteBuffer responseBuffer = ByteBuffer.allocate(100);
+
+    public VescDeviceSupport(DeviceType type) {
+        super();
+        responseBuffer.order(ByteOrder.BIG_ENDIAN);
+
+        deviceType = type;
+        addSupportedService(UUID.fromString(VescCoordinator.UUID_SERVICE_SERIAL_NRF));
+        addSupportedService(UUID.fromString(VescCoordinator.UUID_SERVICE_SERIAL_HM10));
+    }
+
+    @Override
+    public void onFetchRecordedData(int dataTypes) {
+        getValues();
+    }
+
+    @Override
+    protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
+        logger.debug("initializing device");
+
+        builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext()));
+
+        initBroadcast();
+
+
+        this.serialWriteCharacteristic = getCharacteristic(UUID.fromString(VescCoordinator.UUID_CHARACTERISTIC_SERIAL_TX_NRF));
+        this.serialReadCharacteristic = getCharacteristic(UUID.fromString(VescCoordinator.UUID_CHARACTERISTIC_SERIAL_RX_NRF));
+        if(this.serialWriteCharacteristic == null || this.serialReadCharacteristic == null){
+            this.serialWriteCharacteristic = getCharacteristic(UUID.fromString(VescCoordinator.UUID_CHARACTERISTIC_SERIAL_TX_HM10));
+            this.serialReadCharacteristic = getCharacteristic(UUID.fromString(VescCoordinator.UUID_CHARACTERISTIC_SERIAL_RX_HM10));
+        }
+
+        builder.notify(this.serialReadCharacteristic, true);
+
+        return builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZED, getContext()));
+    }
+
+    @Override
+    public boolean onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
+        super.onDescriptorWrite(gatt, descriptor, status);
+        getValues();
+
+        return true;
+    }
+
+    @Override
+    public boolean onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
+        handleRxCharacteristic(characteristic);
+
+        return true;
+    }
+
+    private void handleRxCharacteristic(BluetoothGattCharacteristic characteristic) {
+        if (characteristic != serialReadCharacteristic) return;
+
+        responseBuffer.put(characteristic.getValue());
+        short length = 0;
+        int oldPosition = responseBuffer.position();
+        responseBuffer.position(0);
+        byte lengthType = responseBuffer.get();
+        if (lengthType == 2) {
+            length = responseBuffer.get();
+        } else if (lengthType == 3) {
+            length = responseBuffer.getShort();
+        } else {
+            return;
+        }
+        if (length == oldPosition - 5) {
+            // whole message transmitted
+            responseBuffer.position(oldPosition);
+            handleResponseBuffer(responseBuffer);
+
+            oldPosition = 0;
+        }
+        responseBuffer.position(oldPosition);
+    }
+
+    private void handleResponseBuffer(ByteBuffer responseBuffer) {
+        int bufferLength = responseBuffer.position();
+        int payloadStartPosition = responseBuffer.get(0);
+
+        byte[] payload = new byte[bufferLength - 3 - payloadStartPosition];
+        System.arraycopy(responseBuffer.array(), payloadStartPosition, payload, 0, payload.length);
+
+        int actualCrc = CheckSums.getCRC16(payload, 0);
+        int expectedCrc = responseBuffer.getShort(bufferLength - 3);
+
+        byte responseType = payload[0];
+        if (responseType == 0x04) {
+            handleResponseValues(responseBuffer);
+        }
+    }
+
+    private void handleResponseValues(ByteBuffer valueBuffer) {
+        valueBuffer.position(3);
+        float temp_mos = buffer_get_float16(valueBuffer, 1e1);
+        float temp_motor = buffer_get_float16(valueBuffer, 1e1);
+        float current_motor = buffer_get_float32(valueBuffer, 1e2);
+        float current_in = buffer_get_float32(valueBuffer, 1e2);
+        float id = buffer_get_float32(valueBuffer, 1e2);
+        float iq = buffer_get_float32(valueBuffer, 1e2);
+        float duty_now = buffer_get_float16(valueBuffer, 1e3);
+        float rpm = buffer_get_float32(valueBuffer, 1e0);
+        float v_in = buffer_get_float16(valueBuffer, 1e1);
+        float amp_hours = buffer_get_float32(valueBuffer, 1e4);
+        float amp_hours_charged = buffer_get_float32(valueBuffer, 1e4);
+        float watt_hours = buffer_get_float32(valueBuffer, 1e4);
+        float watt_hours_charged = buffer_get_float32(valueBuffer, 1e4);
+        float tachometer = buffer_get_int32(valueBuffer);
+        float tachometer_abs = buffer_get_int32(valueBuffer);
+
+        handleBatteryVoltage(v_in);
+
+        Intent intent = new Intent(ACTION_GOT_VALUES);
+        intent.putExtra(EXTRA_VOLTAGE, v_in);
+    }
+
+
+
+    void handleBatteryVoltage(float voltage){
+        SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress());
+        float minimalVoltage = Float.parseFloat(prefs.getString(DeviceSettingsPreferenceConst.PREF_VESC_MINIMUM_VOLTAGE, "-1"));
+        float maximalVoltage = Float.parseFloat(prefs.getString(DeviceSettingsPreferenceConst.PREF_VESC_MAXIMUM_VOLTAGE, "-1"));
+
+        if(minimalVoltage == -1){
+            return;
+        }
+        if(maximalVoltage == -1){
+            return;
+        }
+
+        float voltageAboveMinimum = voltage - minimalVoltage;
+        float voltageRange = maximalVoltage - minimalVoltage;
+        float fullness = voltageAboveMinimum / voltageRange;
+
+        int fullnessPercent = (int)(fullness * 100);
+        fullnessPercent = Math.max(fullnessPercent, 0);
+        fullnessPercent = Math.min(fullnessPercent, 100);
+
+        getDevice().setBatteryLevel(fullnessPercent);
+        getDevice().setBatteryVoltage(voltage);
+        getDevice().sendDeviceUpdateIntent(getContext());
+    }
+
+    float buffer_get_float16(ByteBuffer buffer, double scale){
+        return (float) (buffer.getShort() / scale);
+    }
+
+    float buffer_get_float32(ByteBuffer buffer, double scale){
+        return (float) (buffer.getInt() / scale);
+    }
+
+    int buffer_get_int32(ByteBuffer buffer){
+        return buffer.getInt();
+    }
+
+    @Override
+    public void onTestNewFunction() {
+        getValues();
+        // getDecodedADC();
+    }
+
+    private void getDecodedADC() {
+        buildAndQueryPacket(CommandType.COMM_GET_DECODED_ADC);
+    }
+
+    private void initBroadcast() {
+        LocalBroadcastManager broadcastManager = LocalBroadcastManager.getInstance(getContext());
+
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(COMMAND_SET_RPM);
+        filter.addAction(COMMAND_SET_CURRENT);
+        filter.addAction(COMMAND_SET_BREAK_CURRENT);
+        filter.addAction(COMMAND_GET_VALUES);
+
+        broadcastManager.registerReceiver(commandReceiver, filter);
+    }
+
+    @Override
+    public void dispose() {
+        super.dispose();
+        LocalBroadcastManager.getInstance(getContext()).unregisterReceiver(commandReceiver);
+    }
+
+    BroadcastReceiver commandReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (intent.getAction().equals(COMMAND_SET_RPM)) {
+                VescDeviceSupport.this.setRPM(
+                        intent.getIntExtra(EXTRA_RPM, 0)
+                );
+            } else if (intent.getAction().equals(COMMAND_SET_BREAK_CURRENT)) {
+                VescDeviceSupport.this.setBreakCurrent(
+                        intent.getIntExtra(EXTRA_CURRENT, 0)
+                );
+            } else if (intent.getAction().equals(COMMAND_SET_CURRENT)) {
+                VescDeviceSupport.this.setCurrent(
+                        intent.getIntExtra(EXTRA_CURRENT, 0)
+                );
+            } else if (intent.getAction().equals(COMMAND_GET_VALUES)) {
+                VescDeviceSupport.this.getValues();
+            }
+        }
+    };
+
+    public void setCurrent(int currentMillisAmperes) {
+        buildAndQueryPacket(CommandType.COMM_SET_CURRENT, currentMillisAmperes);
+    }
+
+    public void setBreakCurrent(int breakCurrentMillisAmperes) {
+        buildAndQueryPacket(CommandType.COMM_SET_CURRENT_BRAKE, breakCurrentMillisAmperes);
+    }
+
+    public void getValues() {
+        buildAndQueryPacket(CommandType.COMM_GET_VALUES);
+    }
+
+    public void setRPM(int rpm) {
+        buildAndQueryPacket(CommandType.COMM_SET_RPM, rpm);
+    }
+
+    public void buildAndQueryPacket(CommandType commandType, Object... args) {
+        byte[] data = buildPacket(commandType, args);
+        queryPacket(data);
+    }
+
+    public void queryPacket(byte[] data) {
+        new TransactionBuilder("write serial packet")
+                .write(this.serialWriteCharacteristic, data)
+                .queue(getQueue());
+    }
+
+    public byte[] buildPacket(CommandType commandType, Object... args) {
+        int dataLength = 0;
+        for (Object arg : args) {
+            if (arg instanceof Integer) dataLength += 4;
+            else if (arg instanceof Short) dataLength += 2;
+        }
+        ByteBuffer buffer = ByteBuffer.allocate(dataLength);
+
+        for (Object arg : args) {
+            if (arg instanceof Integer) buffer.putInt((Integer) arg);
+            if (arg instanceof Short) buffer.putShort((Short) arg);
+        }
+
+        return buildPacket(commandType, buffer.array());
+    }
+
+    public byte[] buildPacket(CommandType commandType, byte[] data) {
+        return buildPacket(commandType.getCommandByte(), data);
+    }
+
+    private byte[] buildPacket(byte commandByte, byte[] data) {
+        byte[] contents = new byte[data.length + 1];
+        contents[0] = commandByte;
+        System.arraycopy(data, 0, contents, 1, data.length);
+        return buildPacket(contents);
+    }
+
+    private byte[] buildPacket(byte[] contents) {
+        int dataLength = contents.length;
+        ByteBuffer buffer = ByteBuffer.allocate(dataLength + (dataLength < 256 ? 5 : 6));
+        if (dataLength < 256) {
+            buffer.put((byte) 0x02);
+            buffer.put((byte) dataLength);
+        } else {
+            buffer.put((byte) 0x03);
+            buffer.putShort((short) dataLength);
+        }
+        buffer.put(contents);
+        buffer.putShort((short) CheckSums.getCRC16(contents, 0));
+        buffer.put((byte) 0x03);
+
+        return buffer.array();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/vibratissimo/VibratissimoSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/vibratissimo/VibratissimoSupport.java
new file mode 100644
index 0000000..1aa6ad2
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/vibratissimo/VibratissimoSupport.java
@@ -0,0 +1,162 @@
+/*  Copyright (C) 2016-2024 Andreas Shimokawa, Arjan Schrijver, Carsten
+    Pfeiffer, Sebastian Kranz
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.vibratissimo;
+
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.content.Intent;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.GattService;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.IntentListener;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.battery.BatteryInfoProfile;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo.DeviceInfoProfile;
+
+public class VibratissimoSupport extends AbstractBTLEDeviceSupport {
+
+    private static final Logger LOG = LoggerFactory.getLogger(VibratissimoSupport.class);
+    private final DeviceInfoProfile<VibratissimoSupport> deviceInfoProfile;
+    private final BatteryInfoProfile<VibratissimoSupport> batteryInfoProfile;
+    private final GBDeviceEventVersionInfo versionCmd = new GBDeviceEventVersionInfo();
+    private final GBDeviceEventBatteryInfo batteryCmd = new GBDeviceEventBatteryInfo();
+    private final IntentListener mListener = new IntentListener() {
+        @Override
+        public void notify(Intent intent) {
+            String s = intent.getAction();
+            if (s.equals(DeviceInfoProfile.ACTION_DEVICE_INFO)) {
+                handleDeviceInfo((nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo.DeviceInfo) intent.getParcelableExtra(DeviceInfoProfile.EXTRA_DEVICE_INFO));
+            } else if (s.equals(BatteryInfoProfile.ACTION_BATTERY_INFO)) {
+                handleBatteryInfo((nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.battery.BatteryInfo) intent.getParcelableExtra(BatteryInfoProfile.EXTRA_BATTERY_INFO));
+            }
+        }
+    };
+
+    public VibratissimoSupport() {
+        super(LOG);
+        addSupportedService(GattService.UUID_SERVICE_GENERIC_ACCESS);
+        addSupportedService(GattService.UUID_SERVICE_GENERIC_ATTRIBUTE);
+        addSupportedService(GattService.UUID_SERVICE_DEVICE_INFORMATION);
+        addSupportedService(GattService.UUID_SERVICE_BATTERY_SERVICE);
+        addSupportedService(UUID.fromString("00001523-1212-efde-1523-785feabcd123"));
+
+        deviceInfoProfile = new DeviceInfoProfile<>(this);
+        deviceInfoProfile.addListener(mListener);
+        batteryInfoProfile = new BatteryInfoProfile<>(this);
+        batteryInfoProfile.addListener(mListener);
+        addSupportedProfile(deviceInfoProfile);
+        addSupportedProfile(batteryInfoProfile);
+    }
+
+    private void handleBatteryInfo(nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.battery.BatteryInfo info) {
+        batteryCmd.level = (short) info.getPercentCharged();
+        handleGBDeviceEvent(batteryCmd);
+    }
+
+    @Override
+    protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
+        builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext()));
+        requestDeviceInfo(builder);
+        setInitialized(builder);
+        batteryInfoProfile.requestBatteryInfo(builder);
+        return builder;
+    }
+
+    private void requestDeviceInfo(TransactionBuilder builder) {
+        LOG.debug("Requesting Device Info!");
+        deviceInfoProfile.requestDeviceInfo(builder);
+    }
+
+    private void setInitialized(TransactionBuilder builder) {
+        builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZED, getContext()));
+    }
+
+
+    @Override
+    public boolean useAutoConnect() {
+        return true;
+    }
+
+    private void handleDeviceInfo(nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo.DeviceInfo info) {
+        LOG.warn("Device info: " + info);
+        versionCmd.hwVersion = info.getHardwareRevision();
+        versionCmd.fwVersion = info.getFirmwareRevision();
+        handleGBDeviceEvent(versionCmd);
+    }
+
+    @Override
+    public void onFindDevice(boolean start) {
+        onSetConstantVibration(start ? 0xff : 0x00);
+    }
+
+    @Override
+    public void onSetConstantVibration(int intensity) {
+        getQueue().clear();
+        BluetoothGattCharacteristic characteristic2 = getCharacteristic(UUID.fromString("00001526-1212-efde-1523-785feabcd123"));
+        BluetoothGattCharacteristic characteristic1 = getCharacteristic(UUID.fromString("00001524-1212-efde-1523-785feabcd123"));
+
+        TransactionBuilder builder = new TransactionBuilder("vibration");
+        builder.write(characteristic1, new byte[]{0x03, (byte) 0x80});
+
+        builder.write(characteristic2, new byte[]{(byte) intensity, 0x00});
+        builder.queue(getQueue());
+    }
+
+    @Override
+    public boolean onCharacteristicChanged(BluetoothGatt gatt,
+                                           BluetoothGattCharacteristic characteristic) {
+        if (super.onCharacteristicChanged(gatt, characteristic)) {
+            return true;
+        }
+
+        UUID characteristicUUID = characteristic.getUuid();
+        LOG.info("Unhandled characteristic changed: " + characteristicUUID);
+        return false;
+    }
+
+    @Override
+    public boolean onCharacteristicRead(BluetoothGatt gatt,
+                                        BluetoothGattCharacteristic characteristic, int status) {
+        if (super.onCharacteristicRead(gatt, characteristic, status)) {
+            return true;
+        }
+        UUID characteristicUUID = characteristic.getUuid();
+
+        LOG.info("Unhandled characteristic read: " + characteristicUUID);
+        return false;
+    }
+
+    @Override
+    public boolean getImplicitCallbackModify() {
+        return true;
+    }
+
+    @Override
+    public boolean getSendWriteRequestResponse() {
+        return false;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/waspos/WaspOSDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/waspos/WaspOSDeviceSupport.java
new file mode 100644
index 0000000..e4bd404
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/waspos/WaspOSDeviceSupport.java
@@ -0,0 +1,406 @@
+/*  Copyright (C) 2020-2024 Andreas Shimokawa, Arjan Schrijver, Damien Gaignon,
+    Daniel Thompson, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.waspos;
+
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.text.format.DateFormat;
+import android.widget.Toast;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Calendar;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventCallControl;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventFindPhone;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventMusicControl;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventNotificationControl;
+import nodomain.freeyourgadget.gadgetbridge.devices.waspos.WaspOSConstants;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
+import nodomain.freeyourgadget.gadgetbridge.model.BatteryState;
+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.util.AlarmUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+public class WaspOSDeviceSupport extends AbstractBTLEDeviceSupport {
+    private static final Logger LOG = LoggerFactory.getLogger(WaspOSDeviceSupport.class);
+    private BluetoothGattCharacteristic rxCharacteristic = null;
+    private BluetoothGattCharacteristic txCharacteristic = null;
+
+    private String receivedLine = "";
+
+    public WaspOSDeviceSupport() {
+        super(LOG);
+        addSupportedService(WaspOSConstants.UUID_SERVICE_NORDIC_UART);
+    }
+
+    @Override
+    protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
+        LOG.info("Initializing");
+
+        gbDevice.setState(GBDevice.State.INITIALIZING);
+        gbDevice.sendDeviceUpdateIntent(getContext());
+
+        rxCharacteristic = getCharacteristic(WaspOSConstants.UUID_CHARACTERISTIC_NORDIC_UART_RX);
+        txCharacteristic = getCharacteristic(WaspOSConstants.UUID_CHARACTERISTIC_NORDIC_UART_TX);
+        builder.setCallback(this);
+        builder.notify(rxCharacteristic, true);
+
+        uartTx(builder, " \u0003"); // clear active line
+
+        Prefs prefs = GBApplication.getPrefs();
+        if (prefs.getBoolean("datetime_synconconnect", true))
+          setTime(builder);
+        //sendSettings(builder);
+
+        // get version
+
+        gbDevice.setState(GBDevice.State.INITIALIZED);
+        gbDevice.sendDeviceUpdateIntent(getContext());
+
+        LOG.info("Initialization Done");
+
+        return builder;
+    }
+
+    /// Write a string of data, and chunk it up
+    private void uartTx(TransactionBuilder builder, String str) {
+        LOG.info("UART TX: " + str);
+        byte[] bytes;
+        bytes = str.getBytes(StandardCharsets.ISO_8859_1);
+        for (int i=0;i<bytes.length;i+=8) {
+            int l = bytes.length-i;
+            if (l>8) l=8;
+            byte[] packet = new byte[l];
+            System.arraycopy(bytes, i, packet, 0, l);
+            builder.write(txCharacteristic, packet);
+        }
+    }
+
+    /// Write a string of data, and chunk it up
+    private void uartTxJSON(String taskName, JSONObject json) {
+        try {
+            TransactionBuilder builder = performInitialized(taskName);
+            uartTx(builder, "\u0010GB("+json.toString()+")\n");
+            builder.queue(getQueue());
+        } catch (IOException e) {
+            GB.toast(getContext(), "Error in "+taskName+": " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
+        }
+    }
+
+    private void handleUartRxLine(String line) {
+        LOG.info("UART RX LINE: " + line);
+
+        if (">Uncaught ReferenceError: \"gb\" is not defined".equals(line))
+          GB.toast(getContext(), "Gadgetbridge plugin not installed on Bangle.js", Toast.LENGTH_LONG, GB.ERROR);
+        else if (line.charAt(0)=='{') {
+            // JSON - we hope!
+            try {
+                JSONObject json = new JSONObject(line);
+                handleUartRxJSON(json);
+            } catch (JSONException e) {
+                GB.toast(getContext(), "Malformed JSON from Bangle.js: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
+            }
+        }
+    }
+
+    private void handleUartRxJSON(JSONObject json) throws JSONException {
+        switch (json.getString("t")) {
+            case "info":
+                GB.toast(getContext(), "Bangle.js: " + json.getString("msg"), Toast.LENGTH_LONG, GB.INFO);
+                break;
+            case "warn":
+                GB.toast(getContext(), "Bangle.js: " + json.getString("msg"), Toast.LENGTH_LONG, GB.WARN);
+                break;
+            case "error":
+                GB.toast(getContext(), "Bangle.js: " + json.getString("msg"), Toast.LENGTH_LONG, GB.ERROR);
+                break;
+            case "status": {
+                GBDeviceEventBatteryInfo batteryInfo = new GBDeviceEventBatteryInfo();
+                if (json.has("bat")) {
+                    int b = json.getInt("bat");
+                    if (b < 0) b = 0;
+                    if (b > 100) b = 100;
+                    batteryInfo.level = b;
+                    batteryInfo.state = BatteryState.BATTERY_NORMAL;
+                }
+                if (json.has("volt"))
+                    batteryInfo.voltage = (float) json.getDouble("volt");
+                handleGBDeviceEvent(batteryInfo);
+            } break;
+            case "findPhone": {
+                boolean start = json.has("n") && json.getBoolean("n");
+                GBDeviceEventFindPhone deviceEventFindPhone = new GBDeviceEventFindPhone();
+                deviceEventFindPhone.event = start ? GBDeviceEventFindPhone.Event.START : GBDeviceEventFindPhone.Event.STOP;
+                evaluateGBDeviceEvent(deviceEventFindPhone);
+            } break;
+            case "music": {
+                GBDeviceEventMusicControl deviceEventMusicControl = new GBDeviceEventMusicControl();
+                deviceEventMusicControl.event = GBDeviceEventMusicControl.Event.valueOf(json.getString("n").toUpperCase());
+                evaluateGBDeviceEvent(deviceEventMusicControl);
+            } break;
+            case "call": {
+                GBDeviceEventCallControl deviceEventCallControl = new GBDeviceEventCallControl();
+                deviceEventCallControl.event = GBDeviceEventCallControl.Event.valueOf(json.getString("n").toUpperCase());
+                evaluateGBDeviceEvent(deviceEventCallControl);
+            } break;
+            case "notify" : {
+                GBDeviceEventNotificationControl deviceEvtNotificationControl = new GBDeviceEventNotificationControl();
+                // .title appears unused
+                deviceEvtNotificationControl.event = GBDeviceEventNotificationControl.Event.valueOf(json.getString("n").toUpperCase());
+                if (json.has("id"))
+                    deviceEvtNotificationControl.handle = json.getInt("id");
+                if (json.has("tel"))
+                    deviceEvtNotificationControl.phoneNumber = json.getString("tel");
+                if (json.has("msg"))
+                    deviceEvtNotificationControl.reply = json.getString("msg");
+                evaluateGBDeviceEvent(deviceEvtNotificationControl);
+            } break;
+            /*case "activity": {
+                WaspOSActivitySample sample = new WaspOSActivitySample();
+                sample.setTimestamp((int) (GregorianCalendar.getInstance().getTimeInMillis() / 1000L));
+                sample.setHeartRate(json.getInteger("hrm"));
+                try (DBHandler dbHandler = GBApplication.acquireDB()) {
+                    Long userId = DBHelper.getUser(dbHandler.getDaoSession()).getId();
+                    Long deviceId = DBHelper.getDevice(getDevice(), dbHandler.getDaoSession()).getId();
+                    WaspOSSampleProvider provider = new WaspOSSampleProvider(getDevice(), dbHandler.getDaoSession());
+                    sample.setDeviceId(deviceId);
+                    sample.setUserId(userId);
+                    provider.addGBActivitySample(sample);
+                } catch (Exception ex) {
+                    LOG.warn("Error saving current heart rate: " + ex.getLocalizedMessage());
+                }
+            } break;*/
+        }
+    }
+
+    @Override
+    public boolean onCharacteristicChanged(BluetoothGatt gatt,
+                                           BluetoothGattCharacteristic characteristic) {
+        if (super.onCharacteristicChanged(gatt, characteristic)) {
+            return true;
+        }
+        if (WaspOSConstants.UUID_CHARACTERISTIC_NORDIC_UART_RX.equals(characteristic.getUuid())) {
+            byte[] chars = characteristic.getValue();
+            String packetStr = new String(chars);
+            LOG.info("RX: " + packetStr);
+            receivedLine += packetStr;
+            while (receivedLine.contains("\n")) {
+                int p = receivedLine.indexOf("\n");
+                String line =  receivedLine.substring(0,p-1);
+                receivedLine = receivedLine.substring(p+1);
+                handleUartRxLine(line);
+            }
+        }
+        return false;
+    }
+
+
+    void setTime(TransactionBuilder builder) {
+        CharSequence formattedDate = DateFormat.format("(yyyy, MM, dd, HH, mm, ss)", new java.util.Date());
+        String cmd = "\u0010watch.rtc.set_localtime(" + formattedDate + ")\n";
+        uartTx(builder, cmd + "\n");
+    }
+
+    @Override
+    public boolean useAutoConnect() {
+        return true;
+    }
+
+    @Override
+    public void onNotification(NotificationSpec notificationSpec) {
+        try {
+            JSONObject o = new JSONObject();
+            o.put("t", "notify");
+            o.put("id", notificationSpec.getId());
+            o.put("src", notificationSpec.sourceName);
+            o.put("title", notificationSpec.title);
+            o.put("subject", notificationSpec.subject);
+            o.put("body", notificationSpec.body);
+            o.put("sender", notificationSpec.sender);
+            o.put("tel", notificationSpec.phoneNumber);
+            uartTxJSON("onNotification", o);
+        } catch (JSONException e) {
+            LOG.info("JSONException: " + e.getLocalizedMessage());
+        }
+    }
+
+    @Override
+    public void onDeleteNotification(int id) {
+        try {
+            JSONObject o = new JSONObject();
+            o.put("t", "notify-");
+            o.put("id", id);
+            uartTxJSON("onDeleteNotification", o);
+        } catch (JSONException e) {
+            LOG.info("JSONException: " + e.getLocalizedMessage());
+        }
+    }
+
+    @Override
+    public void onSetTime() {
+        try {
+            TransactionBuilder builder = performInitialized("setTime");
+            setTime(builder);
+            builder.queue(getQueue());
+        } catch (Exception e) {
+            GB.toast(getContext(), "Error setting time: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
+        }
+    }
+
+    @Override
+    public void onSetAlarms(ArrayList<? extends Alarm> alarms) {
+        try {
+            JSONObject o = new JSONObject();
+            o.put("t", "alarm");
+            JSONArray jsonalarms = new JSONArray();
+            o.put("d", jsonalarms);
+
+            for (Alarm alarm : alarms) {
+                if (!alarm.getEnabled()) continue;
+                JSONObject jsonalarm = new JSONObject();
+                jsonalarms.put(jsonalarm);
+
+                Calendar calendar = AlarmUtils.toCalendar(alarm);
+                // TODO: getRepetition to ensure it only happens on correct day?
+                jsonalarm.put("h", alarm.getHour());
+                jsonalarm.put("m", alarm.getMinute());
+            }
+            uartTxJSON("onSetAlarms", o);
+        } catch (JSONException e) {
+            LOG.info("JSONException: " + e.getLocalizedMessage());
+        }
+    }
+
+    @Override
+    public void onSetCallState(CallSpec callSpec) {
+        try {
+            JSONObject o = new JSONObject();
+            o.put("t", "call");
+            String[] cmdString = {"", "undefined", "accept", "incoming", "outgoing", "reject", "start", "end"};
+            o.put("cmd", cmdString[callSpec.command]);
+            o.put("name", callSpec.name);
+            o.put("number", callSpec.number);
+            uartTxJSON("onSetCallState", o);
+        } catch (JSONException e) {
+            LOG.info("JSONException: " + e.getLocalizedMessage());
+        }
+    }
+
+    @Override
+    public void onSetMusicState(MusicStateSpec stateSpec) {
+        try {
+            JSONObject o = new JSONObject();
+            o.put("t", "musicstate");
+            String[] musicStates = {"play", "pause", "stop", ""};
+            o.put("state", musicStates[stateSpec.state]);
+            o.put("position", stateSpec.position);
+            o.put("shuffle", stateSpec.shuffle);
+            o.put("repeat", stateSpec.repeat);
+            uartTxJSON("onSetMusicState", o);
+        } catch (JSONException e) {
+            LOG.info("JSONException: " + e.getLocalizedMessage());
+        }
+    }
+
+    @Override
+    public void onSetMusicInfo(MusicSpec musicSpec) {
+        try {
+            JSONObject o = new JSONObject();
+            o.put("t", "musicinfo");
+            o.put("artist", musicSpec.artist);
+            o.put("album", musicSpec.album);
+            o.put("track", musicSpec.track);
+            o.put("dur", musicSpec.duration);
+            o.put("c", musicSpec.trackCount);
+            o.put("n", musicSpec.trackNr);
+            uartTxJSON("onSetMusicInfo", o);
+        } catch (JSONException e) {
+            LOG.info("JSONException: " + e.getLocalizedMessage());
+        }
+    }
+
+    @Override
+    public void onFindDevice(boolean start) {
+        try {
+            JSONObject o = new JSONObject();
+            o.put("t", "find");
+            o.put("n", start);
+            uartTxJSON("onFindDevice", o);
+        } catch (JSONException e) {
+            LOG.info("JSONException: " + e.getLocalizedMessage());
+        }
+    }
+
+    @Override
+    public void onSetConstantVibration(int integer) {
+        try {
+            JSONObject o = new JSONObject();
+            o.put("t", "vibrate");
+            o.put("n", integer);
+            uartTxJSON("onSetConstantVibration", o);
+        } catch (JSONException e) {
+            LOG.info("JSONException: " + e.getLocalizedMessage());
+        }
+    }
+
+    @Override
+    public void onSendWeather(ArrayList<WeatherSpec> weatherSpecs) {
+        WeatherSpec weatherSpec = weatherSpecs.get(0);
+        try {
+            JSONObject o = new JSONObject();
+            o.put("t", "weather");
+            o.put("temp", weatherSpec.currentTemp);
+            o.put("hum", weatherSpec.currentHumidity);
+            o.put("code", weatherSpec.currentConditionCode);
+            o.put("txt", weatherSpec.currentCondition);
+            o.put("wind", weatherSpec.windSpeed);
+            o.put("loc", weatherSpec.location);
+            uartTxJSON("onSendWeather", o);
+        } catch (JSONException e) {
+            LOG.info("JSONException: " + e.getLocalizedMessage());
+        }
+    }
+
+    @Override
+    public boolean getImplicitCallbackModify() {
+        return true;
+    }
+
+    @Override
+    public boolean getSendWriteRequestResponse() {
+        return false;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/watch9/Watch9DeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/watch9/Watch9DeviceSupport.java
new file mode 100644
index 0000000..47346b2
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/watch9/Watch9DeviceSupport.java
@@ -0,0 +1,497 @@
+/*  Copyright (C) 2018-2024 Andreas B枚hler, Andreas Shimokawa, Arjan
+    Schrijver, Carsten Pfeiffer, Damien Gaignon, Daniele Gobbetti, maxirnilian,
+    Sebastian Kranz
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.watch9;
+
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.Locale;
+import java.util.UUID;
+
+import androidx.annotation.IntRange;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo;
+import nodomain.freeyourgadget.gadgetbridge.devices.watch9.Watch9Constants;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
+import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
+import nodomain.freeyourgadget.gadgetbridge.model.BatteryState;
+import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.GattService;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.watch9.operations.InitOperation;
+import nodomain.freeyourgadget.gadgetbridge.util.AlarmUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.ArrayUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.BcdUtil;
+
+public class Watch9DeviceSupport extends AbstractBTLEDeviceSupport {
+
+    private boolean needsAuth;
+    private int sequenceNumber = 0;
+    private boolean isCalibrationActive = false;
+
+    private byte ACK_CALIBRATION = 0;
+
+    private final GBDeviceEventVersionInfo versionInfo = new GBDeviceEventVersionInfo();
+    private final GBDeviceEventBatteryInfo batteryInfo = new GBDeviceEventBatteryInfo();
+
+    private static final Logger LOG = LoggerFactory.getLogger(Watch9DeviceSupport.class);
+
+    private final BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String broadcastAction = intent.getAction();
+            switch (broadcastAction) {
+                case Watch9Constants.ACTION_CALIBRATION:
+                    enableCalibration(intent.getBooleanExtra(Watch9Constants.ACTION_ENABLE, false));
+                    break;
+                case Watch9Constants.ACTION_CALIBRATION_SEND:
+                    int hour = intent.getIntExtra(Watch9Constants.VALUE_CALIBRATION_HOUR, -1);
+                    int minute = intent.getIntExtra(Watch9Constants.VALUE_CALIBRATION_MINUTE, -1);
+                    int second = intent.getIntExtra(Watch9Constants.VALUE_CALIBRATION_SECOND, -1);
+                    if (hour != -1 && minute != -1 && second != -1) {
+                        sendCalibrationData(hour, minute, second);
+                    }
+                    break;
+                case Watch9Constants.ACTION_CALIBRATION_HOLD:
+                    holdCalibration();
+                    break;
+            }
+        }
+    };
+
+    public Watch9DeviceSupport() {
+        super(LOG);
+        addSupportedService(GattService.UUID_SERVICE_GENERIC_ACCESS);
+        addSupportedService(GattService.UUID_SERVICE_GENERIC_ATTRIBUTE);
+        addSupportedService(Watch9Constants.UUID_SERVICE_WATCH9);
+
+        LocalBroadcastManager broadcastManager = LocalBroadcastManager.getInstance(getContext());
+        IntentFilter intentFilter = new IntentFilter();
+        intentFilter.addAction(Watch9Constants.ACTION_CALIBRATION);
+        intentFilter.addAction(Watch9Constants.ACTION_CALIBRATION_SEND);
+        intentFilter.addAction(Watch9Constants.ACTION_CALIBRATION_HOLD);
+        broadcastManager.registerReceiver(broadcastReceiver, intentFilter);
+    }
+
+    @Override
+    protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
+        try {
+            boolean auth = needsAuth;
+            needsAuth = false;
+            new InitOperation(auth, this, builder).perform();
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+
+        return builder;
+    }
+
+    @Override
+    public boolean connectFirstTime() {
+        needsAuth = true;
+        return super.connect();
+    }
+
+    @Override
+    public boolean useAutoConnect() {
+        return true;
+    }
+
+    @Override
+    public void onNotification(NotificationSpec notificationSpec) {
+        sendNotification(Watch9Constants.NOTIFICATION_CHANNEL_DEFAULT, false);
+    }
+
+    private void sendNotification(int notificationChannel, boolean isStopNotification) {
+        try {
+            TransactionBuilder builder = performInitialized("showNotification");
+            byte[] command = Watch9Constants.CMD_NOTIFICATION_TASK;
+            command[1] = (byte) (isStopNotification ? 0x04 : 0x01);
+            builder.write(getCharacteristic(Watch9Constants.UUID_CHARACTERISTIC_WRITE),
+                    buildCommand(command,
+                            Watch9Constants.TASK,
+                            Conversion.toByteArr32(notificationChannel)));
+            performImmediately(builder);
+        } catch (IOException e) {
+            LOG.warn("Unable to send notification", e);
+        }
+    }
+
+    private Watch9DeviceSupport enableNotificationChannels(TransactionBuilder builder) {
+        builder.write(getCharacteristic(Watch9Constants.UUID_CHARACTERISTIC_WRITE),
+                buildCommand(Watch9Constants.CMD_NOTIFICATION_SETTINGS,
+                        Watch9Constants.WRITE_VALUE,
+                        new byte[]{(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF}));
+
+        return this;
+    }
+
+    public Watch9DeviceSupport authorizationRequest(TransactionBuilder builder, boolean firstConnect) {
+        builder.write(getCharacteristic(Watch9Constants.UUID_CHARACTERISTIC_WRITE),
+                buildCommand(Watch9Constants.CMD_AUTHORIZATION_TASK,
+                        Watch9Constants.TASK,
+                        new byte[]{(byte) (firstConnect ? 0x00 : 0x01)})); //possibly not the correct meaning
+
+        return this;
+    }
+
+    private Watch9DeviceSupport enableDoNotDisturb(TransactionBuilder builder, boolean active) {
+        builder.write(getCharacteristic(Watch9Constants.UUID_CHARACTERISTIC_WRITE),
+                buildCommand(Watch9Constants.CMD_DO_NOT_DISTURB_SETTINGS,
+                        Watch9Constants.WRITE_VALUE,
+                        new byte[]{(byte) (active ? 0x01 : 0x00)}));
+
+        return this;
+    }
+
+    private void enableCalibration(boolean enable) {
+        try {
+            TransactionBuilder builder = performInitialized("enableCalibration");
+            builder.write(getCharacteristic(Watch9Constants.UUID_CHARACTERISTIC_WRITE),
+                    buildCommand(Watch9Constants.CMD_CALIBRATION_INIT_TASK,
+                            Watch9Constants.TASK,
+                            new byte[]{(byte) (enable ? 0x01 : 0x00)}));
+            performImmediately(builder);
+        } catch (IOException e) {
+            LOG.warn("Unable to start/stop calibration mode", e);
+        }
+    }
+
+    private void holdCalibration() {
+        try {
+            TransactionBuilder builder = performInitialized("holdCalibration");
+            builder.write(getCharacteristic(Watch9Constants.UUID_CHARACTERISTIC_WRITE),
+                    buildCommand(Watch9Constants.CMD_CALIBRATION_KEEP_ALIVE,
+                            Watch9Constants.KEEP_ALIVE));
+            performImmediately(builder);
+        } catch (IOException e) {
+            LOG.warn("Unable to keep calibration mode alive", e);
+        }
+    }
+
+    private void sendCalibrationData(@IntRange(from=0,to=23)int hour, @IntRange(from=0,to=59)int minute, @IntRange(from=0,to=59)int second) {
+        try {
+            isCalibrationActive = true;
+            TransactionBuilder builder = performInitialized("calibrate");
+            int handsPosition = ((hour % 12) * 60 + minute) * 60 + second;
+            builder.write(getCharacteristic(Watch9Constants.UUID_CHARACTERISTIC_WRITE),
+                    buildCommand(Watch9Constants.CMD_CALIBRATION_TASK,
+                            Watch9Constants.TASK,
+                            Conversion.toByteArr16(handsPosition)));
+            performImmediately(builder);
+        } catch (IOException e) {
+            isCalibrationActive = false;
+            LOG.warn("Unable to send calibration data", e);
+        }
+    }
+
+    private void getTime() {
+        try {
+            TransactionBuilder builder = performInitialized("getTime");
+            builder.write(getCharacteristic(Watch9Constants.UUID_CHARACTERISTIC_WRITE),
+                    buildCommand(Watch9Constants.CMD_TIME_SETTINGS,
+                            Watch9Constants.READ_VALUE));
+            performImmediately(builder);
+        } catch (IOException e) {
+            LOG.warn("Unable to get device time", e);
+        }
+    }
+
+    private void handleTime(byte[] time) {
+        GregorianCalendar now = BLETypeConversions.createCalendar();
+        GregorianCalendar nowDevice = BLETypeConversions.createCalendar();
+        int year = (nowDevice.get(Calendar.YEAR) / 100) * 100 + BcdUtil.fromBcd8(time[8]);
+        nowDevice.set(year,
+                BcdUtil.fromBcd8(time[9]) - 1,
+                BcdUtil.fromBcd8(time[10]),
+                BcdUtil.fromBcd8(time[11]),
+                BcdUtil.fromBcd8(time[12]),
+                BcdUtil.fromBcd8(time[13]));
+        nowDevice.set(Calendar.DAY_OF_WEEK, BcdUtil.fromBcd8(time[16]) + 1);
+
+        long timeDiff = (Math.abs(now.getTimeInMillis() - nowDevice.getTimeInMillis())) / 1000;
+        if (10 < timeDiff && timeDiff < 120) {
+            enableCalibration(true);
+            setTime(BLETypeConversions.createCalendar());
+            enableCalibration(false);
+        }
+    }
+
+    private void setTime(Calendar calendar) {
+        try {
+            TransactionBuilder builder = performInitialized("setTime");
+            int timezoneOffsetMinutes = (calendar.get(Calendar.ZONE_OFFSET) + calendar.get(Calendar.DST_OFFSET)) / (60 * 1000);
+            int timezoneOffsetIndustrialMinutes = Math.round((Math.abs(timezoneOffsetMinutes) % 60) * 100f / 60f);
+            byte[] time = new byte[]{BcdUtil.toBcd8(calendar.get(Calendar.YEAR) % 100),
+                    BcdUtil.toBcd8(calendar.get(Calendar.MONTH) + 1),
+                    BcdUtil.toBcd8(calendar.get(Calendar.DAY_OF_MONTH)),
+                    BcdUtil.toBcd8(calendar.get(Calendar.HOUR_OF_DAY)),
+                    BcdUtil.toBcd8(calendar.get(Calendar.MINUTE)),
+                    BcdUtil.toBcd8(calendar.get(Calendar.SECOND)),
+                    (byte) (timezoneOffsetMinutes / 60),
+                    (byte) timezoneOffsetIndustrialMinutes,
+                    (byte) (calendar.get(Calendar.DAY_OF_WEEK) - 1)
+            };
+            builder.write(getCharacteristic(Watch9Constants.UUID_CHARACTERISTIC_WRITE),
+                    buildCommand(Watch9Constants.CMD_TIME_SETTINGS,
+                            Watch9Constants.WRITE_VALUE,
+                            time));
+            performImmediately(builder);
+        } catch (IOException e) {
+            LOG.warn("Unable to set time", e);
+        }
+    }
+
+    public Watch9DeviceSupport getFirmwareVersion(TransactionBuilder builder) {
+        builder.write(getCharacteristic(Watch9Constants.UUID_CHARACTERISTIC_WRITE),
+                buildCommand(Watch9Constants.CMD_FIRMWARE_INFO,
+                        Watch9Constants.READ_VALUE));
+
+        return this;
+    }
+
+    private Watch9DeviceSupport getBatteryState(TransactionBuilder builder) {
+        builder.write(getCharacteristic(Watch9Constants.UUID_CHARACTERISTIC_WRITE),
+                buildCommand(Watch9Constants.CMD_BATTERY_INFO,
+                        Watch9Constants.READ_VALUE));
+
+        return this;
+    }
+
+    private Watch9DeviceSupport setFitnessGoal(TransactionBuilder builder) {
+        int fitnessGoal = new ActivityUser().getStepsGoal();
+        builder.write(getCharacteristic(Watch9Constants.UUID_CHARACTERISTIC_WRITE),
+                buildCommand(Watch9Constants.CMD_FITNESS_GOAL_SETTINGS,
+                        Watch9Constants.WRITE_VALUE,
+                        Conversion.toByteArr16(fitnessGoal)));
+
+        return this;
+    }
+
+    public Watch9DeviceSupport initialize(TransactionBuilder builder) {
+        getFirmwareVersion(builder)
+                .getBatteryState(builder)
+                .enableNotificationChannels(builder)
+                .enableDoNotDisturb(builder, false)
+                .setFitnessGoal(builder);
+        builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZED, getContext()));
+        builder.setCallback(this);
+
+        return this;
+    }
+
+    @Override
+    public void onSetTime() {
+        getTime();
+    }
+
+    @Override
+    public void onSetAlarms(ArrayList<? extends Alarm> alarms) {
+        try {
+            TransactionBuilder builder = performInitialized("setAlarms");
+            for (Alarm alarm : alarms) {
+                setAlarm(alarm, alarm.getPosition() + 1, builder);
+            }
+            builder.queue(getQueue());
+        } catch (IOException e) {
+            LOG.warn("Unable to set alarms", e);
+        }
+    }
+
+    // No useful use case at the moment, used to clear alarm slots for testing.
+    private void deleteAlarm(TransactionBuilder builder, int index) {
+        if (0 < index && index < 4) {
+            byte[] alarmValue = new byte[]{(byte) index, 0x00, 0x00, 0x00, 0x00, 0x00};
+            builder.write(getCharacteristic(Watch9Constants.UUID_CHARACTERISTIC_WRITE),
+                    buildCommand(Watch9Constants.CMD_ALARM_SETTINGS,
+                            Watch9Constants.WRITE_VALUE,
+                            alarmValue));
+        }
+    }
+
+    private void setAlarm(Alarm alarm, int index, TransactionBuilder builder) {
+        // Shift the GB internal repetition mask to match the device specific one.
+        byte repetitionMask = (byte) ((alarm.getRepetition() << 1) | (alarm.isRepetitive() ? 0x80 : 0x00));
+        repetitionMask |= (alarm.getRepetition(Alarm.ALARM_SUN) ? 0x01 : 0x00);
+        if (0 < index && index < 4) {
+            byte[] alarmValue = new byte[]{(byte) index,
+                    BcdUtil.toBcd8(AlarmUtils.toCalendar(alarm).get(Calendar.HOUR_OF_DAY)),
+                    BcdUtil.toBcd8(AlarmUtils.toCalendar(alarm).get(Calendar.MINUTE)),
+                    repetitionMask,
+                    (byte) (alarm.getEnabled() ? 0x01 : 0x00),
+                    0x00 // TODO: Unknown
+            };
+            builder.write(getCharacteristic(Watch9Constants.UUID_CHARACTERISTIC_WRITE),
+                    buildCommand(Watch9Constants.CMD_ALARM_SETTINGS,
+                            Watch9Constants.WRITE_VALUE,
+                            alarmValue));
+        }
+    }
+
+    @Override
+    public void onSetCallState(CallSpec callSpec) {
+        switch (callSpec.command) {
+            case CallSpec.CALL_INCOMING:
+                sendNotification(Watch9Constants.NOTIFICATION_CHANNEL_PHONE_CALL, false);
+                break;
+            case CallSpec.CALL_START:
+            case CallSpec.CALL_END:
+                sendNotification(Watch9Constants.NOTIFICATION_CHANNEL_PHONE_CALL, true);
+                break;
+            default:
+                break;
+        }
+    }
+
+    @Override
+    public void onSendConfiguration(String config) {
+        TransactionBuilder builder;
+        try {
+            builder = performInitialized("sendConfig: " + config);
+            switch (config) {
+                case ActivityUser.PREF_USER_STEPS_GOAL:
+                    setFitnessGoal(builder);
+                    break;
+            }
+            builder.queue(getQueue());
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+
+    @Override
+    public boolean onCharacteristicChanged(BluetoothGatt gatt,
+                                           BluetoothGattCharacteristic characteristic) {
+        super.onCharacteristicChanged(gatt, characteristic);
+
+        UUID characteristicUUID = characteristic.getUuid();
+        if (Watch9Constants.UUID_CHARACTERISTIC_WRITE.equals(characteristicUUID)) {
+            byte[] value = characteristic.getValue();
+            if (ArrayUtils.equals(value, Watch9Constants.RESP_FIRMWARE_INFO, 5)) {
+                handleFirmwareInfo(value);
+            } else if (ArrayUtils.equals(value, Watch9Constants.RESP_BATTERY_INFO, 5)) {
+                handleBatteryState(value);
+            } else if (ArrayUtils.equals(value, Watch9Constants.RESP_TIME_SETTINGS, 5)) {
+                handleTime(value);
+            } else if (ArrayUtils.equals(value, Watch9Constants.RESP_BUTTON_INDICATOR, 5)) {
+                LOG.info("Unhandled action: Button pressed");
+            } else if (ArrayUtils.equals(value, Watch9Constants.RESP_ALARM_INDICATOR, 5)) {
+                LOG.info("Alarm active: id=" + value[8]);
+            } else if (isCalibrationActive && value.length == 7 && value[4] == ACK_CALIBRATION) {
+                setTime(BLETypeConversions.createCalendar());
+                isCalibrationActive = false;
+            }
+
+            return true;
+        } else {
+            LOG.info("Unhandled characteristic changed: " + characteristicUUID);
+            logMessageContent(characteristic.getValue());
+        }
+
+        return false;
+    }
+
+    private byte[] buildCommand(byte[] command, byte action) {
+        return buildCommand(command, action, null);
+    }
+
+    private byte[] buildCommand(byte[] command, byte action, byte[] value) {
+        if (Arrays.equals(command, Watch9Constants.CMD_CALIBRATION_TASK)) {
+            ACK_CALIBRATION = (byte) sequenceNumber;
+        }
+        command = BLETypeConversions.join(command, value);
+        byte[] result = new byte[7 + command.length];
+        System.arraycopy(Watch9Constants.CMD_HEADER, 0, result, 0, 5);
+        System.arraycopy(command, 0, result, 6, command.length);
+        result[2] = (byte) (command.length + 1);
+        result[3] = Watch9Constants.REQUEST;
+        result[4] = (byte) sequenceNumber++;
+        result[5] = action;
+        result[result.length - 1] = calculateChecksum(result);
+
+        return result;
+    }
+
+    private byte calculateChecksum(byte[] bytes) {
+        byte checksum = 0x00;
+        for (int i = 0; i < bytes.length - 1; i++) {
+            checksum += (bytes[i] ^ i) & 0xFF;
+        }
+        return (byte) (checksum & 0xFF);
+    }
+
+    private void handleFirmwareInfo(byte[] value) {
+        versionInfo.fwVersion = String.format(Locale.US,"%d.%d.%d", value[8], value[9], value[10]);
+        handleGBDeviceEvent(versionInfo);
+    }
+
+    private void handleBatteryState(byte[] value) {
+        batteryInfo.state = value[9] == 1 ? BatteryState.BATTERY_NORMAL : BatteryState.BATTERY_LOW;
+        batteryInfo.level = GBDevice.BATTERY_UNKNOWN;
+        handleGBDeviceEvent(batteryInfo);
+    }
+
+    @Override
+    public void dispose() {
+        LocalBroadcastManager broadcastManager = LocalBroadcastManager.getInstance(getContext());
+        broadcastManager.unregisterReceiver(broadcastReceiver);
+        super.dispose();
+    }
+
+    @Override
+    public boolean getImplicitCallbackModify() {
+        return true;
+    }
+
+    @Override
+    public boolean getSendWriteRequestResponse() {
+        return false;
+    }
+
+    private static class Conversion {
+        static byte[] toByteArr16(int value) {
+            return new byte[]{(byte) (value >> 8), (byte) value};
+        }
+
+        static byte[] toByteArr32(int value) {
+            return new byte[]{(byte) (value >> 24),
+                    (byte) (value >> 16),
+                    (byte) (value >> 8),
+                    (byte) value};
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/watch9/operations/InitOperation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/watch9/operations/InitOperation.java
new file mode 100644
index 0000000..31fd8e9
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/watch9/operations/InitOperation.java
@@ -0,0 +1,93 @@
+/*  Copyright (C) 2018-2024 Damien Gaignon, maxirnilian
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.watch9.operations;
+
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.widget.Toast;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.watch9.Watch9Constants;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEOperation;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.watch9.Watch9DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.util.ArrayUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class InitOperation extends AbstractBTLEOperation<Watch9DeviceSupport>{
+
+    private static final Logger LOG = LoggerFactory.getLogger(InitOperation.class);
+
+    private final TransactionBuilder builder;
+    private final boolean needsAuth;
+    private final BluetoothGattCharacteristic cmdCharacteristic = getCharacteristic(Watch9Constants.UUID_CHARACTERISTIC_WRITE);
+
+    public InitOperation(boolean needsAuth, Watch9DeviceSupport support, TransactionBuilder builder) {
+        super(support);
+        this.needsAuth = needsAuth;
+        this.builder = builder;
+        builder.setCallback(this);
+    }
+
+    @Override
+    protected void doPerform() throws IOException {
+        builder.notify(cmdCharacteristic, true);
+        if (needsAuth) {
+            builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.AUTHENTICATING, getContext()));
+            getSupport().authorizationRequest(builder, needsAuth);
+        } else {
+            builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext()));
+            getSupport().initialize(builder);
+            getSupport().performImmediately(builder);
+        }
+    }
+
+    @Override
+    public boolean onCharacteristicChanged(BluetoothGatt gatt,
+                                           BluetoothGattCharacteristic characteristic) {
+        UUID characteristicUUID = characteristic.getUuid();
+        if (Watch9Constants.UUID_CHARACTERISTIC_WRITE.equals(characteristicUUID) && needsAuth) {
+            try {
+                byte[] value = characteristic.getValue();
+                getSupport().logMessageContent(value);
+                if (ArrayUtils.equals(value, Watch9Constants.RESP_AUTHORIZATION_TASK, 5) && value[8] == 0x01) {
+                    TransactionBuilder builder = getSupport().createTransactionBuilder("authInit");
+                    builder.setCallback(this);
+                    builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext()));
+                    getSupport().initialize(builder).performImmediately(builder);
+                } else {
+                    return super.onCharacteristicChanged(gatt, characteristic);
+                }
+            } catch (Exception e) {
+                GB.toast(getContext(), "Error authenticating Watch 9", Toast.LENGTH_LONG, GB.ERROR, e);
+            }
+            return true;
+        } else {
+            LOG.info("Unhandled characteristic changed: " + characteristicUUID);
+            return super.onCharacteristicChanged(gatt, characteristic);
+        }
+    }
+
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/AuthenticationHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/AuthenticationHandler.java
new file mode 100644
index 0000000..d290d8f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/AuthenticationHandler.java
@@ -0,0 +1,128 @@
+/*  Copyright (C) 2023-2024 Frank Ertl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr;
+
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.os.Build;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
+import java.util.Random;
+
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.WithingsUUID;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.conversation.AbstractResponseHandler;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.Challenge;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.ChallengeResponse;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.Probe;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.ProbeOsVersion;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.ProbeReply;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.WithingsStructure;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.Message;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.WithingsMessage;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.WithingsMessageType;
+
+public class AuthenticationHandler extends AbstractResponseHandler {
+    private static final Logger logger = LoggerFactory.getLogger(AuthenticationHandler.class);
+
+    // TODO: Save this somewhere if we actually decide to use te secret for more security:
+    private final String secret = "2EM5zNP37QzM00hmP6BFTD92nG15XwNd";
+    private WithingsSteelHRDeviceSupport support;
+    private Challenge challengeToSend;
+
+    public AuthenticationHandler(WithingsSteelHRDeviceSupport support) {
+        super(support);
+        this.support = support;
+    }
+
+    @Override
+    public void handleResponse(Message response) {
+        short messageType = response.getType();
+        if (messageType == WithingsMessageType.PROBE) {
+            handleProbeReply(response);
+        } else if (messageType == WithingsMessageType.CHALLENGE) {
+            handleChallenge(response);
+        } else {
+            logger.warn("Received unkown message: " + messageType + ", will ignore this.");
+        }
+    }
+
+    private void handleChallenge(Message challengeMessage) {
+        try {
+            Challenge challenge = getTypeFromReply(Challenge.class, challengeMessage);
+            ChallengeResponse challengeResponse = new ChallengeResponse();
+            challengeResponse.setResponse(createResponse(challenge));
+            Message message = new WithingsMessage(WithingsMessageType.CHALLENGE);
+            message.addDataStructure(challengeResponse);
+            challengeToSend = new Challenge();
+            challengeToSend.setMacAddress(challenge.getMacAddress());
+            byte[] bArr = new byte[16];
+            new Random().nextBytes(bArr);
+            challengeToSend.setChallenge(bArr);
+            message.addDataStructure(challengeToSend);
+            support.sendToDevice(message);
+        } catch (Exception e) {
+            logger.error("Failed to create response to challenge: " + e.getMessage());
+        }
+    }
+
+    private void handleProbeReply(Message message) {
+        ProbeReply probeReply = getTypeFromReply(ProbeReply.class, message);
+        if (probeReply == null) {
+            throw new IllegalArgumentException("Message does not contain the required datastructure ProbeReply");
+        }
+
+        ChallengeResponse response = getTypeFromReply(ChallengeResponse.class, message);
+
+        if (response == null || Arrays.equals(response.getResponse(), createResponse(challengeToSend))) {
+            support.getDevice().setFirmwareVersion(String.valueOf(probeReply.getFirmwareVersion()));
+        } else {
+            throw new SecurityException("Response is not the one expected!");
+        }
+
+        support.onAuthenticationFinished();
+    }
+
+    private byte[] createResponse(Challenge challenge) {
+        try {
+            ByteBuffer allocate = ByteBuffer.allocate(challenge.getChallenge().length + challenge.getMacAddress().getBytes().length + secret.getBytes().length);
+            allocate.put(challenge.getChallenge());
+            allocate.put(challenge.getMacAddress().getBytes());
+            allocate.put(secret.getBytes());
+            return MessageDigest.getInstance("SHA1").digest(allocate.array());
+        } catch (NoSuchAlgorithmException e) {
+            logger.error("Failed to create response to challenge: " + e.getMessage());
+        }
+
+        return new byte[0];
+    }
+
+    private <T extends WithingsStructure> T getTypeFromReply(Class<T> type, Message message) {
+        for (WithingsStructure structure : message.getDataStructures()) {
+            if (type.isInstance(structure)) {
+                return (T)structure;
+            }
+        }
+
+        return null;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/IconHelper.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/IconHelper.java
new file mode 100644
index 0000000..d90603b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/IconHelper.java
@@ -0,0 +1,71 @@
+/*  Copyright (C) 2023-2024 Frank Ertl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr;
+
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+
+import nodomain.freeyourgadget.gadgetbridge.util.BitmapUtil;
+
+public class IconHelper {
+
+    public static byte[] getIconBytesFromDrawable(Drawable drawable) {
+        Bitmap bitmap = BitmapUtil.toBitmap(drawable);
+        Bitmap scaledBitmap = Bitmap.createScaledBitmap(bitmap, 22, 24, true);
+        int size = scaledBitmap.getRowBytes() * scaledBitmap.getHeight();
+        return toByteArray(scaledBitmap);
+    }
+
+    public static byte[] toByteArray(Bitmap bitmap) {
+        int width = bitmap.getWidth();
+        int height = bitmap.getHeight();
+        int bytesPerColumn = getBytesPerColumn(height);
+        byte[] rawData = new byte[bytesPerColumn * width];
+        for (int col = 0; col < width; col++) {
+            for (int row = 0; row < height; row++) {
+                int pixel = bitmap.getPixel(col, row);
+                if (shouldPixelbeAdded(pixel)) {
+                    int bitIndex = bytesPerColumn * col + row / 8;
+                    rawData[bitIndex] = setBit(rawData[bitIndex], row);
+                }
+            }
+        }
+
+        return rawData;
+    }
+
+    private static boolean shouldPixelbeAdded(int pixel) {
+        double luma = ((Color.red(pixel) * 0.2126d) + (Color.green(pixel) * 0.7152d) + (Color.blue(pixel) * 0.0722d)) * (Color.alpha(pixel) / 255.0f);
+        return luma > 0;
+    }
+
+    private static byte setBit(byte bits, int position) {
+        bits |= 1 << (position % 8);
+        return bits;
+    }
+
+    private static int getBytesPerColumn(int rowCount) {
+        int result = (int) rowCount / 8;
+        if (result * 8 < rowCount) {
+            result++;
+        }
+
+        return result;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/WithingsSteelHRDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/WithingsSteelHRDeviceSupport.java
new file mode 100644
index 0000000..cffa53d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/WithingsSteelHRDeviceSupport.java
@@ -0,0 +1,748 @@
+/*  Copyright (C) 2023-2024 Ascense, Frank Ertl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr;
+
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.bluetooth.BluetoothGattDescriptor;
+import android.bluetooth.BluetoothGattService;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.SharedPreferences;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.widget.Toast;
+
+import androidx.annotation.NonNull;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.SettingsActivity;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
+import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
+import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.NotificationType;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.GattService;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.ServerTransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.activity.WithingsActivityType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.WithingsServerAction;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.WithingsUUID;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.conversation.ActivitySampleHandler;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.conversation.BatteryStateHandler;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.conversation.Conversation;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.conversation.ConversationQueue;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.conversation.HeartRateHandler;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.conversation.ResponseHandler;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.conversation.SetupFinishedHandler;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.conversation.SimpleConversation;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.conversation.SyncFinishedHandler;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.conversation.WorkoutScreenListHandler;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.ActivityTarget;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.AlarmName;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.AlarmSettings;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.AlarmStatus;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.AncsStatus;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.DataStructureFactory;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.EndOfTransmission;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.GetActivitySamples;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.ImageData;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.ImageMetaData;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.Locale;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.MoveHand;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.Probe;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.ProbeOsVersion;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.ScreenSettings;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.Time;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.TypeVersion;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.User;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.UserUnit;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.UserUnitConstants;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.WorkoutScreen;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.ExpectedResponse;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.Message;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.MessageBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.MessageFactory;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.SimpleHexToByteMessage;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.WithingsMessage;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.WithingsMessageType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.incoming.IncomingMessageHandler;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.incoming.IncomingMessageHandlerFactory;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.incoming.LiveWorkoutHandler;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.notification.GetNotificationAttributes;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.notification.GetNotificationAttributesResponse;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.notification.NotificationProvider;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.notification.NotificationSource;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
+
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_LANGUAGE;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_LANGUAGE_AUTO;
+
+public class WithingsSteelHRDeviceSupport extends AbstractBTLEDeviceSupport {
+
+    private static final Logger logger = LoggerFactory.getLogger(WithingsSteelHRDeviceSupport.class);
+    public static final String LAST_ACTIVITY_SYNC = "lastActivitySync";
+    public static final String HANDS_CALIBRATION_CMD = "withings_hands_calibration";
+    public static final String START_HANDS_CALIBRATION_CMD = "start_withings_hands_calibration";
+    public static final String STOP_HANDS_CALIBRATION_CMD = "stop_withings_hands_calibration";
+    private static Prefs prefs = GBApplication.getPrefs();
+    private MessageBuilder messageBuilder;
+    private LiveWorkoutHandler liveWorkoutHandler;
+    private ActivitySampleHandler activitySampleHandler;
+    private ConversationQueue conversationQueue;
+    private boolean firstTimeConnect;
+    private BluetoothGattCharacteristic notificationSourceCharacteristic;
+    private BluetoothGattCharacteristic dataSourceCharacteristic;
+    private BluetoothDevice device;
+    private boolean syncInProgress;
+    private ActivityUser activityUser;
+    private NotificationProvider notificationProvider;
+    private IncomingMessageHandlerFactory incomingMessageHandlerFactory;
+    private final BroadcastReceiver commandReceiver;
+    private int mtuSize = 115;
+
+    public WithingsSteelHRDeviceSupport() {
+        super(logger);
+        notificationProvider = NotificationProvider.getInstance(this);
+        messageBuilder = new MessageBuilder(this, new MessageFactory(new DataStructureFactory()));
+        liveWorkoutHandler = new LiveWorkoutHandler(this);
+        incomingMessageHandlerFactory = IncomingMessageHandlerFactory.getInstance(this);
+        addSupportedService(WithingsUUID.WITHINGS_SERVICE_UUID);
+        addSupportedService(GattService.UUID_SERVICE_GENERIC_ACCESS);
+        addSupportedService(GattService.UUID_SERVICE_GENERIC_ATTRIBUTE);
+        addANCSService();
+        activityUser = new ActivityUser();
+
+        IntentFilter commandFilter = new IntentFilter(HANDS_CALIBRATION_CMD);
+        commandFilter.addAction(START_HANDS_CALIBRATION_CMD);
+        commandFilter.addAction(STOP_HANDS_CALIBRATION_CMD);
+        commandReceiver = new BroadcastReceiver() {
+
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                if (intent.getAction() == null) {
+                    return;
+                }
+
+                switch (intent.getAction()) {
+                    case HANDS_CALIBRATION_CMD:
+                        MoveHand moveHand = new MoveHand();
+                        moveHand.setHand(intent.getShortExtra("hand", (short)1));
+                        moveHand.setMovement(intent.getShortExtra("movementAmount", (short)1));
+                        sendToDevice(new WithingsMessage(WithingsMessageType.MOVE_HAND, moveHand));
+                        break;
+                    case START_HANDS_CALIBRATION_CMD:
+                        sendToDevice(new WithingsMessage(WithingsMessageType.START_HANDS_CALIBRATION));
+                        break;
+                    case STOP_HANDS_CALIBRATION_CMD:
+                        sendToDevice(new WithingsMessage(WithingsMessageType.STOP_HANDS_CALIBRATION));
+                        break;
+                }
+            }
+        };
+
+        LocalBroadcastManager.getInstance(GBApplication.getContext()).registerReceiver(commandReceiver, commandFilter);
+    }
+
+    @Override
+    protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
+        logger.debug("Starting initialization...");
+        conversationQueue = new ConversationQueue(this);
+        builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext()));
+        getDevice().setFirmwareVersion("N/A");
+        getDevice().setFirmwareVersion2("N/A");
+        BluetoothGattCharacteristic characteristic = getCharacteristic(WithingsUUID.WITHINGS_WRITE_CHARACTERISTIC_UUID);
+        builder.notify(characteristic, true);
+        logger.debug("Requesting change of MTU...");
+        builder.requestMtu(119);
+        return builder;
+    }
+
+    @Override
+    public boolean connectFirstTime() {
+        firstTimeConnect = true;
+        return connect();
+    }
+
+    @Override
+    public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
+        logger.debug("MTU has changed to " + mtu);
+        mtuSize = mtu;
+        if (firstTimeConnect) {
+            addSimpleConversationToQueue(new WithingsMessage(WithingsMessageType.INITIAL_CONNECT));
+            addSimpleConversationToQueue(new WithingsMessage(WithingsMessageType.SET_LOCALE, getLocale()));
+            addSimpleConversationToQueue(new WithingsMessage(WithingsMessageType.START_HANDS_CALIBRATION));
+            addSimpleConversationToQueue(new WithingsMessage(WithingsMessageType.STOP_HANDS_CALIBRATION));
+            addSimpleConversationToQueue(new WithingsMessage(WithingsMessageType.SET_TIME, new Time()));
+            addSimpleConversationToQueue(new WithingsMessage(WithingsMessageType.SET_USER_UNIT, new UserUnit(UserUnitConstants.DISTANCE, getUnit())));
+            addSimpleConversationToQueue(new WithingsMessage(WithingsMessageType.SET_USER_UNIT, new UserUnit(UserUnitConstants.CLOCK_MODE, getTimeMode())));
+            addSimpleConversationToQueue(new WithingsMessage(WithingsMessageType.SET_ACTIVITY_TARGET, new ActivityTarget(activityUser.getStepsGoal())));
+            addSimpleConversationToQueue(new WithingsMessage(WithingsMessageType.GET_ANCS_STATUS));
+            addSimpleConversationToQueue(new WithingsMessage(WithingsMessageType.SET_ANCS_STATUS, new AncsStatus(true)));
+            addSimpleConversationToQueue(new WithingsMessage(WithingsMessageType.GET_BATTERY_STATUS), new BatteryStateHandler(this));
+            addScreenListCommands();
+            addSimpleConversationToQueue(new WithingsMessage(WithingsMessageType.SETUP_FINISHED), new SetupFinishedHandler(this));
+        } else {
+            Message message = new WithingsMessage(WithingsMessageType.PROBE);
+            message.addDataStructure(new Probe((short) 1, (short) 1, 5100401));
+            message.addDataStructure(new ProbeOsVersion((short) Build.VERSION.SDK_INT));
+            conversationQueue.clear();
+            addSimpleConversationToQueue(message, new AuthenticationHandler(this));
+        }
+
+        if (!firstTimeConnect) {
+            finishInitialization();
+        }
+        conversationQueue.send();
+    }
+
+    public void doSync() {
+        activitySampleHandler = new ActivitySampleHandler(this);
+        conversationQueue.clear();
+        try {
+            if (syncInProgress || !shoudSync()) {
+                return;
+            }
+
+            getDevice().setBusyTask("Syncing");
+            syncInProgress = true;
+            addSimpleConversationToQueue(new WithingsMessage(WithingsMessageType.INITIAL_CONNECT));
+            addSimpleConversationToQueue(new WithingsMessage(WithingsMessageType.GET_ANCS_STATUS));
+            addSimpleConversationToQueue(new WithingsMessage(WithingsMessageType.GET_BATTERY_STATUS), new BatteryStateHandler(this));
+            addSimpleConversationToQueue(new WithingsMessage(WithingsMessageType.SET_TIME, new Time()));
+            WithingsMessage message = new WithingsMessage(WithingsMessageType.SET_USER);
+            message.addDataStructure(getUser());
+            // The UserSecret appears in the original communication with the HealthMate app. Until now GB works without the secret.
+            // This makes the "authentication" far easier. However if it turns out that this is needed, we would need to find a way to savely store a unique generated secret.
+            //  message.addDataStructure(new UserSecret());
+            addSimpleConversationToQueue(message);
+            addSimpleConversationToQueue(new WithingsMessage(WithingsMessageType.SET_ACTIVITY_TARGET, new ActivityTarget(activityUser.getStepsGoal())));
+            addSimpleConversationToQueue(new WithingsMessage(WithingsMessageType.SET_USER_UNIT, new UserUnit(UserUnitConstants.DISTANCE, getUnit())));
+            addSimpleConversationToQueue(new WithingsMessage(WithingsMessageType.SET_USER_UNIT, new UserUnit(UserUnitConstants.CLOCK_MODE, getTimeMode())));
+            addSimpleConversationToQueue(new WithingsMessage(WithingsMessageType.GET_ALARM_SETTINGS));
+            addSimpleConversationToQueue(new WithingsMessage(WithingsMessageType.GET_SCREEN_SETTINGS));
+            addSimpleConversationToQueue(new WithingsMessage(WithingsMessageType.GET_ALARM));
+            addSimpleConversationToQueue(new WithingsMessage(WithingsMessageType.GET_ALARM_ENABLED));
+            addSimpleConversationToQueue(new WithingsMessage(WithingsMessageType.GET_WORKOUT_SCREEN_LIST), new WorkoutScreenListHandler(this));
+            Calendar c = Calendar.getInstance();
+            c.setTimeInMillis(getLastSyncTimestamp());
+            message = new WithingsMessage(WithingsMessageType.GET_ACTIVITY_SAMPLES, ExpectedResponse.EOT);
+            message.addDataStructure(new GetActivitySamples(c.getTimeInMillis() / 1000, (short) 0));
+            addSimpleConversationToQueue(message, activitySampleHandler);
+            message = new WithingsMessage(WithingsMessageType.GET_MOVEMENT_SAMPLES, ExpectedResponse.EOT);
+            message.addDataStructure(new GetActivitySamples(c.getTimeInMillis() / 1000, (short) 0));
+            message.addDataStructure(new TypeVersion());
+            addSimpleConversationToQueue(message, activitySampleHandler);
+            message = new WithingsMessage(WithingsMessageType.GET_HEARTRATE_SAMPLES, ExpectedResponse.EOT);
+            message.addDataStructure(new GetActivitySamples(c.getTimeInMillis() / 1000, (short) 0));
+            message.addDataStructure(new TypeVersion());
+            addSimpleConversationToQueue(message, activitySampleHandler);
+        } catch (Exception e) {
+            logger.error("Could not synchronize! ", e);
+            conversationQueue.clear();
+        } finally {
+            // This must be done in all cases or the watch won't respond anymore!
+            addSimpleConversationToQueue(new WithingsMessage(WithingsMessageType.SYNC_OK), new SyncFinishedHandler(this));
+        }
+        conversationQueue.send();
+    }
+
+
+    @Override
+    public boolean onCharacteristicChanged(BluetoothGatt gatt,
+                                           BluetoothGattCharacteristic characteristic) {
+        if (super.onCharacteristicChanged(gatt, characteristic)) {
+            return true;
+        }
+
+        byte[] data = characteristic.getValue();
+
+        boolean complete = messageBuilder.buildMessage(data);
+        if (complete) {
+            Message message = messageBuilder.getMessage();
+            if (message.isIncomingMessage()) {
+                logger.debug("received incoming message: " + message.getType());
+                IncomingMessageHandler handler = incomingMessageHandlerFactory.getHandler(message);
+                handler.handleMessage(message);
+            } else {
+                conversationQueue.processResponse(message);
+            }
+        }
+
+        return true;
+    }
+
+    @Override
+    public void onSetCallState(CallSpec callSpec) {
+        if (callSpec.command == CallSpec.CALL_INCOMING) {
+            NotificationSpec notificationSpec = new NotificationSpec();
+            notificationSpec.sourceAppId = "incoming.call";
+            notificationSpec.title = callSpec.number;
+            notificationSpec.sender = callSpec.name;
+            notificationSpec.type = NotificationType.GENERIC_PHONE;
+            notificationProvider.notifyClient(notificationSpec);
+        } else {
+            logger.info("Received yet unhandled call command: " + callSpec.command);
+        }
+    }
+
+    @Override
+    public void onNotification(NotificationSpec notificationSpec) {
+        notificationProvider.notifyClient(notificationSpec);
+    }
+
+    @Override
+    public void onSetAlarms(ArrayList<? extends Alarm> alarms) {
+        if (alarms.size() > 3) {
+            throw new IllegalArgumentException("Steel HR does only have three alarmslots!");
+        }
+
+        if (alarms.size() == 0) {
+            return;
+        }
+
+        boolean noAlarmsEnabled = true;
+        conversationQueue.clear();
+        addSimpleConversationToQueue(new WithingsMessage(WithingsMessageType.GET_ALARM));
+        for (Alarm alarm : alarms) {
+            if (alarm.getEnabled() && !alarm.getUnused()) {
+                noAlarmsEnabled = false;
+                addAlarm(alarm);
+            }
+        }
+
+        if (noAlarmsEnabled) {
+            addSimpleConversationToQueue(new WithingsMessage(WithingsMessageType.SET_ALARM_ENABLED, new AlarmStatus(false)));
+        }
+
+        conversationQueue.send();
+    }
+
+    @Override
+    public boolean onDescriptorWriteRequest(BluetoothDevice device, int requestId, BluetoothGattDescriptor descriptor, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
+        this.device = device;
+        return true;
+    }
+
+    @Override
+    public boolean onCharacteristicWriteRequest(BluetoothDevice device, int requestId, BluetoothGattCharacteristic characteristic, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
+        if (characteristic.getUuid().equals(WithingsUUID.CONTROL_POINT_CHARACTERISTIC_UUID)) {
+            logger.debug("Got GetNotificationAttributesRequest: " + GB.hexdump(value));
+            GetNotificationAttributes request = new GetNotificationAttributes();
+            request.deserialize(value);
+            notificationProvider.handleNotificationAttributeRequest(request);
+        }
+
+        return true;
+    }
+
+    @Override
+    public void onFetchRecordedData(int dataTypes) {
+        doSync();
+    }
+
+    @Override
+    public void onHeartRateTest() {
+        conversationQueue.clear();
+        addSimpleConversationToQueue(new WithingsMessage(WithingsMessageType.GET_HR), new HeartRateHandler(this));
+        conversationQueue.send();
+    }
+
+    @Override
+    public void onSendConfiguration(String config) {
+        try {
+            switch (config) {
+                case HuamiConst.PREF_WORKOUT_ACTIVITY_TYPES_SORTABLE:
+                    setWorkoutActivityTypes();
+                    break;
+                case PREF_LANGUAGE:
+                    setLanguage();
+                    break;
+                default:
+                    logger.debug("unknown configuration setting received: " + config);
+            }
+        } catch (Exception e) {
+            GB.toast("Error setting configuration", Toast.LENGTH_LONG, GB.ERROR, e);
+        }
+    }
+
+    @Override
+    public void onTestNewFunction() {
+        String hexMessage = "0105080015050900111006040102030507000000000000000000";
+        conversationQueue.clear();
+        addSimpleConversationToQueue(new SimpleHexToByteMessage(hexMessage));
+        conversationQueue.send();
+    }
+
+    @Override
+    public boolean useAutoConnect() {
+        return false;
+    }
+
+    public void sendToDevice(Message message) {
+        if (message == null) {
+            return;
+        }
+
+        try {
+            TransactionBuilder builder = createTransactionBuilder("conversation");
+            builder.setCallback(this);
+            BluetoothGattCharacteristic characteristic = getCharacteristic(WithingsUUID.WITHINGS_WRITE_CHARACTERISTIC_UUID);
+            if (characteristic == null) {
+                logger.info("Characteristic with UUID " + WithingsUUID.WITHINGS_WRITE_CHARACTERISTIC_UUID + " not found.");
+                return;
+            }
+
+            byte[] rawData = message.getRawData();
+            builder.writeChunkedData(characteristic, rawData, mtuSize - 4);
+            builder.queue(getQueue());
+        } catch (Exception e) {
+            logger.warn("Could not send message because of " + e.getMessage());
+        }
+    }
+
+    public void sendAncsNotificationSourceNotification(NotificationSource notificationSource) {
+        try {
+            ServerTransactionBuilder builder = performServer("notificationSourceNotification");
+            notificationSourceCharacteristic.setValue(notificationSource.serialize());
+            builder.add(new WithingsServerAction(device, notificationSourceCharacteristic));
+            builder.queue(getQueue());
+        } catch (IOException e) {
+            logger.error("Could not send notification.", e);
+            GB.toast("Could not send notification.", Toast.LENGTH_LONG, GB.ERROR, e);
+        }
+    }
+
+    public void sendAncsDataSourceNotification(GetNotificationAttributesResponse response) {
+        try {
+            ServerTransactionBuilder builder = performServer("dataSourceNotification");
+            byte[] data = response.serialize();
+            dataSourceCharacteristic.setValue(response.serialize());
+            builder.add(new WithingsServerAction(device, dataSourceCharacteristic));
+            builder.queue(getQueue());
+        } catch (IOException e) {
+            logger.error("Could not send notification.", e);
+            GB.toast("Could not send notification.", Toast.LENGTH_LONG, GB.ERROR, e);
+        }
+    }
+
+    public void finishInitialization() {
+        TransactionBuilder builder = createTransactionBuilder("setupFinished");
+        builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZED, getContext()));
+        builder.queue(getQueue());
+        logger.debug("Finished initialization.");
+    }
+
+    public void finishSync() {
+        syncInProgress = false;
+        if (getDevice().isBusy()) {
+            getDevice().unsetBusyTask();
+            getDevice().sendDeviceUpdateIntent(getContext());
+        }
+        activitySampleHandler.onSyncFinished();
+        saveLastSyncTimestamp(new Date().getTime());
+    }
+
+    void onAuthenticationFinished() {
+        if (!firstTimeConnect) {
+            addScreenListCommands();
+            doSync();
+        } else {
+            addSimpleConversationToQueue(new WithingsMessage(WithingsMessageType.SET_ANCS_STATUS, new AncsStatus(true)));
+            addSimpleConversationToQueue(new WithingsMessage(WithingsMessageType.GET_ANCS_STATUS));
+            addSimpleConversationToQueue(new WithingsMessage(WithingsMessageType.GET_BATTERY_STATUS), new BatteryStateHandler(this));
+            conversationQueue.send();
+        }
+    }
+
+    private void addAlarm(Alarm alarm) {
+        AlarmSettings alarmSettings = new AlarmSettings();
+        alarmSettings.setHour((short) alarm.getHour());
+        alarmSettings.setMinute((short) alarm.getMinute());
+        alarmSettings.setDayOfWeek(mapRepetitionToWithingsValue(alarm));
+        if (alarm.getSmartWakeup()) {
+            // Healthmate has the possibility to change the minutecount, in GB we use a fixed value of 15
+            alarmSettings.setSmartWakeupMinutes((short) 15);
+        }
+
+        Message alarmMessage = new WithingsMessage(WithingsMessageType.SET_ALARM, alarmSettings);
+        if (!StringUtils.isEmpty(alarm.getTitle())) {
+            AlarmName alarmName = new AlarmName(alarm.getTitle());
+            alarmMessage.addDataStructure(alarmName);
+        }
+
+        addSimpleConversationToQueue(alarmMessage);
+        addSimpleConversationToQueue(new WithingsMessage(WithingsMessageType.SET_ALARM_ENABLED, new AlarmStatus(true)));
+    }
+
+    private short mapRepetitionToWithingsValue(Alarm alarm) {
+        int repetition = 0;
+        if (alarm.getRepetition(Alarm.ALARM_MON)) {
+            repetition += 0x02;
+        }
+        if (alarm.getRepetition(Alarm.ALARM_TUE)) {
+            repetition += 0x04;
+        }
+        if (alarm.getRepetition(Alarm.ALARM_WED)) {
+            repetition += 0x08;
+        }
+        if (alarm.getRepetition(Alarm.ALARM_THU)) {
+            repetition += 0x10;
+        }
+        if (alarm.getRepetition(Alarm.ALARM_FRI)) {
+            repetition += 0x20;
+        }
+        if (alarm.getRepetition(Alarm.ALARM_SAT)) {
+            repetition += 0x40;
+        }
+        if (alarm.getRepetition(Alarm.ALARM_SUN)) {
+            repetition += 0x01;
+        }
+
+        return (short)(repetition + 0x80);
+    }
+
+    private void addANCSService() {
+        BluetoothGattService withingsGATTService = new BluetoothGattService(WithingsUUID.WITHINGS_ANCS_SERVICE_UUID, BluetoothGattService.SERVICE_TYPE_PRIMARY);
+        notificationSourceCharacteristic = new BluetoothGattCharacteristic(WithingsUUID.NOTIFICATION_SOURCE_CHARACTERISTIC_UUID, BluetoothGattCharacteristic.PROPERTY_NOTIFY, BluetoothGattCharacteristic.PERMISSION_READ);
+        notificationSourceCharacteristic.addDescriptor(new BluetoothGattDescriptor(WithingsUUID.CCC_DESCRIPTOR_UUID, BluetoothGattCharacteristic.PERMISSION_WRITE));
+        withingsGATTService.addCharacteristic(notificationSourceCharacteristic);
+        withingsGATTService.addCharacteristic(new BluetoothGattCharacteristic(WithingsUUID.CONTROL_POINT_CHARACTERISTIC_UUID, BluetoothGattCharacteristic.PROPERTY_WRITE, BluetoothGattCharacteristic.PERMISSION_WRITE));
+        dataSourceCharacteristic = new BluetoothGattCharacteristic(WithingsUUID.DATA_SOURCE_CHARACTERISTIC_UUID, BluetoothGattCharacteristic.PROPERTY_NOTIFY, BluetoothGattCharacteristic.PERMISSION_READ);
+        dataSourceCharacteristic.addDescriptor(new BluetoothGattDescriptor(WithingsUUID.CCC_DESCRIPTOR_UUID, BluetoothGattCharacteristic.PERMISSION_WRITE));
+        withingsGATTService.addCharacteristic(dataSourceCharacteristic);
+        addSupportedServerService(withingsGATTService);
+    }
+
+    private void addSimpleConversationToQueue(Message message) {
+        addSimpleConversationToQueue(message, null);
+    }
+
+    private void addSimpleConversationToQueue(Message message, ResponseHandler handler) {
+        Conversation conversation = new SimpleConversation(handler);
+        conversation.setRequest(message);
+        conversationQueue.addConversation(conversation);
+    }
+
+    private void saveLastSyncTimestamp(@NonNull long timestamp) {
+        SharedPreferences.Editor editor = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress()).edit();
+        editor.putLong(LAST_ACTIVITY_SYNC, timestamp);
+        editor.apply();
+    }
+
+    private long getLastSyncTimestamp() {
+        SharedPreferences settings = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress());
+        long lastSyncTime =  settings.getLong(LAST_ACTIVITY_SYNC, 0);
+        if (lastSyncTime > 0) {
+            return lastSyncTime;
+        } else {
+            Date currentDate = new Date();
+            Calendar c = Calendar.getInstance();
+            c.setTimeInMillis(currentDate.getTime());
+            c.add(Calendar.HOUR, - 10);
+            return c.getTimeInMillis();
+        }
+    }
+
+    private boolean shoudSync() {
+        long lastSynced = getLastSyncTimestamp();
+        int minuteInMillis = 60 * 1000;
+        return new Date().getTime() - lastSynced > minuteInMillis;
+    }
+
+    private User getUser() {
+        User user = new User();
+        ActivityUser activityUser = new ActivityUser();
+        user.setName(activityUser.getName());
+        user.setGender((byte) activityUser.getGender());
+        user.setHeight(activityUser.getHeightCm());
+        user.setWeight(activityUser.getWeightKg());
+        user.setBirthdate(activityUser.getUserBirthday());
+        return user;
+    }
+
+    private void addScreenListCommands() {
+        // TODO: this needs to be more reworked, at the moment for example the notification screen is always on and this is full of magic numbers that need to be identified properly:
+        Message message = new WithingsMessage(WithingsMessageType.SET_SCREEN_LIST);
+        ScreenSettings settings = new ScreenSettings();
+        settings.setId(0xff);
+        settings.setIdOnDevice((byte)6);
+        message.addDataStructure(settings);
+
+        settings = new ScreenSettings();
+        settings.setId(0x3d);
+        settings.setIdOnDevice((byte)1);
+        message.addDataStructure(settings);
+
+        settings = new ScreenSettings();
+        settings.setId(0x33);
+        settings.setIdOnDevice((byte)4);
+        message.addDataStructure(settings);
+
+        settings = new ScreenSettings();
+        settings.setId(0x2d);
+        settings.setIdOnDevice((byte)2);
+        message.addDataStructure(settings);
+
+        settings = new ScreenSettings();
+        settings.setId(0x2a);
+        settings.setIdOnDevice((byte)3);
+        message.addDataStructure(settings);
+
+        settings = new ScreenSettings();
+        settings.setId(0x26);
+        settings.setIdOnDevice((byte)7);
+        message.addDataStructure(settings);
+
+        settings = new ScreenSettings();
+        settings.setId(0x39);
+        settings.setIdOnDevice((byte)9);
+        message.addDataStructure(settings);
+
+        message.addDataStructure(new EndOfTransmission());
+        addSimpleConversationToQueue(message);
+    }
+
+    private void setWorkoutActivityTypes() {
+        final SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress());
+
+        final List<String> allActivityTypes = Arrays.asList(getContext().getResources().getStringArray(R.array.pref_withings_steel_activity_types_values));
+        final List<String> defaultActivityTypes = Arrays.asList(getContext().getResources().getStringArray(R.array.pref_withings_steel_activity_types_default));
+        final String activityTypesPref = prefs.getString("workout_activity_types_sortable", null);
+
+        final List<String> enabledActivityTypes;
+        if (activityTypesPref == null || activityTypesPref.equals("")) {
+            enabledActivityTypes = defaultActivityTypes;
+        } else {
+            enabledActivityTypes = Arrays.asList(activityTypesPref.split(","));
+        }
+
+        conversationQueue.clear();
+        for (int i = 0; i < enabledActivityTypes.size(); i++) {
+            String workoutType = enabledActivityTypes.get(i);
+            try {
+                Message message = createWorkoutScreenMessage(workoutType);
+                if (i == enabledActivityTypes.size() - 1) {
+                    message.addDataStructure(new EndOfTransmission());
+                }
+                addSimpleConversationToQueue(message);
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+        }
+
+        conversationQueue.send();
+    }
+
+    @NonNull
+    private Message createWorkoutScreenMessage(String workoutType) {
+        WithingsActivityType withingsActivityType = WithingsActivityType.fromPrefValue(workoutType);
+        int code = withingsActivityType.getCode();
+        Message message = new WithingsMessage(WithingsMessageType.SET_WORKOUT_SCREEN, ExpectedResponse.NONE);
+        WorkoutScreen workoutScreen = new WorkoutScreen();
+        workoutScreen.setId(code);
+        final int stringId = getContext().getResources().getIdentifier("activity_type_" + workoutType, "string", getContext().getPackageName());
+        workoutScreen.setName(getContext().getString(stringId));
+        message.addDataStructure(workoutScreen);
+
+        ImageMetaData imageMetaData = new ImageMetaData();
+        imageMetaData.setHeight((byte)24);
+        imageMetaData.setWidth((byte)22);
+        message.addDataStructure(imageMetaData);
+
+        ImageData imageData = new ImageData();
+        final int drawableId = withingsActivityType.toActivityKind().getIcon();
+        Drawable drawable = getContext().getDrawable(drawableId);
+        imageData.setImageData(IconHelper.getIconBytesFromDrawable(drawable));
+        message.addDataStructure(imageData);
+
+        return message;
+    }
+
+    protected void setLanguage() {
+        Locale locale = getLocale();
+
+        conversationQueue.clear();
+        addSimpleConversationToQueue(new WithingsMessage(WithingsMessageType.SET_LOCALE, locale));
+        conversationQueue.send();
+    }
+
+    private Locale getLocale() {
+        String localeString = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress())
+                .getString(PREF_LANGUAGE, PREF_LANGUAGE_AUTO);
+
+        if (localeString.equals(PREF_LANGUAGE_AUTO)) {
+            localeString = java.util.Locale.getDefault().getLanguage();
+        }
+
+        String language = localeString.substring(0, 2);
+        switch (language) {
+            case "de":
+            case "en":
+            case "es":
+            case "fr":
+            case "it":
+                return new Locale(language);
+            default:
+                return new Locale("en");
+        }
+    }
+
+    private short getTimeMode() {
+        if ("24h".equals(getDevicePrefs().getTimeFormat())) {
+            return UserUnitConstants.UNIT_24H;
+        } else {
+            return UserUnitConstants.UNIT_12H;
+        }
+    }
+
+    private short getUnit() {
+        String units = prefs.getString(SettingsActivity.PREF_MEASUREMENT_SYSTEM, GBApplication.getContext().getString(R.string.p_unit_metric));
+
+        if (units.equals(GBApplication.getContext().getString(R.string.p_unit_metric))) {
+            return UserUnitConstants.UNIT_KM;
+        } else {
+            return UserUnitConstants.UNIT_MILES;
+        }
+    }
+
+    @Override
+    public boolean getImplicitCallbackModify() {
+        return true;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/activity/ActivityEntry.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/activity/ActivityEntry.java
new file mode 100644
index 0000000..7843478
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/activity/ActivityEntry.java
@@ -0,0 +1,101 @@
+/*  Copyright (C) 2023-2024 Frank Ertl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.activity;
+
+public class ActivityEntry {
+    private int timestamp;
+    private int duration;
+    private int rawKind = -1;
+    private int heartrate;
+    private int steps;
+    private int calories;
+    private int distance;
+    private int rawIntensity;
+    private boolean isHeartrate;
+
+    public int getTimestamp() {
+        return timestamp;
+    }
+
+    public void setTimestamp(int timestamp) {
+        this.timestamp = timestamp;
+    }
+
+    public int getDuration() {
+        return duration;
+    }
+
+    public void setDuration(int duration) {
+        this.duration = duration;
+    }
+
+    public int getHeartrate() {
+        return heartrate;
+    }
+
+    public void setIsHeartrate(int heartrate) {
+        this.heartrate = heartrate;
+    }
+
+    public int getRawKind() {
+        return rawKind;
+    }
+
+    public void setRawKind(int rawKind) {
+        this.rawKind = rawKind;
+    }
+
+    public int getSteps() {
+        return steps;
+    }
+
+    public void setSteps(int steps) {
+        this.steps = steps;
+    }
+
+    public int getCalories() {
+        return calories;
+    }
+
+    public void setCalories(int calories) {
+        this.calories = calories;
+    }
+
+    public int getDistance() {
+        return distance;
+    }
+
+    public void setDistance(int distance) {
+        this.distance = distance;
+    }
+
+    public int getRawIntensity() {
+        return rawIntensity;
+    }
+
+    public void setRawIntensity(int rawIntensity) {
+        this.rawIntensity = rawIntensity;
+    }
+
+    public boolean isHeartrate() {
+        return isHeartrate;
+    }
+
+    public void setIsHeartrate(boolean heartrate) {
+        isHeartrate = heartrate;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/activity/SleepActivitySampleHelper.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/activity/SleepActivitySampleHelper.java
new file mode 100644
index 0000000..8bde32b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/activity/SleepActivitySampleHelper.java
@@ -0,0 +1,86 @@
+/*  Copyright (C) 2023-2024 Frank Ertl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.activity;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.withingssteelhr.WithingsSteelHRSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.WithingsSteelHRActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+
+/**
+ * This class is needed for sleep tracking as the withings steel HR sends heartrate while sleeping in an extra activity.
+ * This leads to breaking the sleep session in the sleep calculation of GB.
+ */
+public class SleepActivitySampleHelper {
+    public static WithingsSteelHRActivitySample mergeIfNecessary(WithingsSteelHRSampleProvider provider, WithingsSteelHRActivitySample sample) {
+        if (!shouldMerge(sample)) {
+            return sample;
+        }
+
+        WithingsSteelHRActivitySample overlappingSample = getOverlappingSample(provider, (int)sample.getTimestamp());
+        if (overlappingSample != null) {
+            sample = doMerge(overlappingSample, sample);
+        }
+
+        return sample;
+    }
+
+    private static WithingsSteelHRActivitySample getOverlappingSample(WithingsSteelHRSampleProvider provider, long timestamp) {
+        List<WithingsSteelHRActivitySample> samples = provider.getActivitySamples((int)timestamp - 500, (int)timestamp);
+        if (samples.isEmpty()) {
+            return null;
+        }
+
+        for (int i = samples.size()-1; i >= 0; i--) {
+            WithingsSteelHRActivitySample lastSample = samples.get(i);
+            if (isNotHeartRateOnly(lastSample)) {
+                return lastSample;
+            }
+        }
+
+        return null;
+    }
+
+    private static boolean isNotHeartRateOnly(WithingsSteelHRActivitySample lastSample) {
+        return lastSample.getRawKind() != ActivityKind.NOT_MEASURED.getCode(); // && lastSample.getTimestamp() <= timestamp && (lastSample.getTimestamp() + lastSample.getDuration()) >= timestamp);
+    }
+
+    private static boolean shouldMerge(WithingsSteelHRActivitySample sample) {
+        return sample.getSteps() == 0
+                && sample.getDistance() == 0
+                && sample.getRawKind() == -1
+                && sample.getCalories() == 0
+                && sample.getHeartRate() > 1
+                && sample.getRawIntensity() == 0;
+    }
+
+    private static WithingsSteelHRActivitySample doMerge(WithingsSteelHRActivitySample origin, WithingsSteelHRActivitySample update) {
+        WithingsSteelHRActivitySample mergeResult = new WithingsSteelHRActivitySample();
+        mergeResult.setTimestamp(update.getTimestamp());
+        mergeResult.setRawKind(origin.getRawKind());
+        mergeResult.setRawIntensity(origin.getRawIntensity());
+        mergeResult.setDuration(origin.getDuration() - (update.getTimestamp() - origin.getTimestamp()));
+        mergeResult.setDevice(origin.getDevice());
+        mergeResult.setDeviceId(origin.getDeviceId());
+        mergeResult.setUser(origin.getUser());
+        mergeResult.setUserId(origin.getUserId());
+        mergeResult.setProvider(origin.getProvider());
+        mergeResult.setHeartRate(update.getHeartRate());
+        return mergeResult;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/activity/WithingsActivityType.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/activity/WithingsActivityType.java
new file mode 100644
index 0000000..3ea2eb0
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/activity/WithingsActivityType.java
@@ -0,0 +1,168 @@
+/*  Copyright (C) 2023-2024 Frank Ertl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.activity;
+
+import java.util.Locale;
+
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiWorkoutScreenActivityType;
+
+public enum WithingsActivityType {
+
+    WALKING(1),
+    RUNNING(2),
+    HIKING(3),
+    BIKING(6),
+    SWIMMING(7),
+    SURFING(8),
+    KITESURFING(9),
+    WINDSURFING(10),
+    TENNIS(12),
+    PINGPONG(13),
+    SQUASH(14),
+    BADMINTON(15),
+    WEIGHTLIFTING(16),
+    GYMNASTICS(17),
+    ELLIPTICAL(18),
+    PILATES(19),
+    BASKETBALL(20),
+    SOCCER(21),
+    FOOTBALL(22),
+    RUGBY(23),
+    VOLLEYBALL(24),
+    GOLFING(227),
+    YOGA(28),
+    DANCING(29),
+    BOXING(30),
+    SKIING(34),
+    SNOWBOARDING(35),
+    ROWING(0), // The code has yet to be identified.
+    ZUMBA(188),
+    BASEBALL(191),
+    HANDBALL(192),
+    HOCKEY(193),
+    ICEHOCKEY(194),
+    CLIMBING(195),
+    ICESKATING(196),
+    RIDING(26),
+    OTHER(36);
+
+    private final int code;
+
+    WithingsActivityType(int typeCode) {
+        this.code = typeCode;
+    }
+
+    public static WithingsActivityType fromCode(int withingsCode) {
+        for (WithingsActivityType type : values()) {
+            if (type.code == withingsCode) {
+                return type;
+            }
+        }
+        throw new RuntimeException("No matching WithingsActivityType for code: " + withingsCode);
+    }
+
+    public int getCode() {
+        return code;
+    }
+
+    public ActivityKind toActivityKind() {
+        switch (this) {
+            case WALKING:
+                return ActivityKind.WALKING;
+            case RUNNING:
+                return ActivityKind.RUNNING;
+            case HIKING:
+                return ActivityKind.HIKING;
+            case BIKING:
+                return ActivityKind.CYCLING;
+            case SWIMMING:
+                return ActivityKind.SWIMMING;
+            case SURFING:
+                return ActivityKind.ACTIVITY;
+            case KITESURFING:
+                return ActivityKind.ACTIVITY;
+            case WINDSURFING:
+                return ActivityKind.ACTIVITY;
+            case TENNIS:
+                return ActivityKind.ACTIVITY;
+            case PINGPONG:
+                return ActivityKind.PINGPONG;
+            case SQUASH:
+                return ActivityKind.ACTIVITY;
+            case BADMINTON:
+                return ActivityKind.BADMINTON;
+            case WEIGHTLIFTING:
+                return ActivityKind.ACTIVITY;
+            case GYMNASTICS:
+                return ActivityKind.EXERCISE;
+            case ELLIPTICAL:
+                return ActivityKind.ELLIPTICAL_TRAINER;
+            case PILATES:
+                return ActivityKind.YOGA;
+            case BASKETBALL:
+                return ActivityKind.BASKETBALL;
+            case SOCCER:
+                return ActivityKind.SOCCER;
+            case FOOTBALL:
+                return ActivityKind.ACTIVITY;
+            case RUGBY:
+                return ActivityKind.ACTIVITY;
+            case VOLLEYBALL:
+                return ActivityKind.ACTIVITY;
+            case GOLFING:
+                return ActivityKind.ACTIVITY;
+            case YOGA:
+                return ActivityKind.YOGA;
+            case DANCING:
+                return ActivityKind.ACTIVITY;
+            case BOXING:
+                return ActivityKind.ACTIVITY;
+            case SKIING:
+                return ActivityKind.ACTIVITY;
+            case SNOWBOARDING:
+                return ActivityKind.ACTIVITY;
+            case ROWING:
+                return ActivityKind.ROWING_MACHINE;
+            case ZUMBA:
+                return ActivityKind.ACTIVITY;
+            case BASEBALL:
+                return ActivityKind.CRICKET;
+            case HANDBALL:
+                return ActivityKind.ACTIVITY;
+            case HOCKEY:
+                return ActivityKind.ACTIVITY;
+            case ICEHOCKEY:
+                return ActivityKind.ACTIVITY;
+            case CLIMBING:
+                return ActivityKind.CLIMBING;
+            case ICESKATING:
+                return ActivityKind.ACTIVITY;
+            default:
+                return ActivityKind.UNKNOWN;
+        }
+    }
+
+    public static WithingsActivityType fromPrefValue(final String prefValue) {
+        for (final WithingsActivityType type : values()) {
+            if (type.name().toLowerCase(Locale.ROOT).equals(prefValue.replace("_", "").toLowerCase(Locale.ROOT))) {
+                return type;
+            }
+        }
+        throw new RuntimeException("No matching WithingsActivityType for pref value: " + prefValue);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/WithingsServerAction.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/WithingsServerAction.java
new file mode 100644
index 0000000..5bf42c6
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/WithingsServerAction.java
@@ -0,0 +1,43 @@
+/*  Copyright (C) 2023-2024 Frank Ertl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication;
+
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.bluetooth.BluetoothGattServer;
+
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEServerAction;
+
+public class WithingsServerAction extends BtLEServerAction
+{
+    private BluetoothGattCharacteristic characteristic;
+
+    public WithingsServerAction(BluetoothDevice device, BluetoothGattCharacteristic characteristic) {
+        super(device);
+        this.characteristic = characteristic;
+    }
+
+    @Override
+    public boolean expectsResult() {
+        return false;
+    }
+
+    @Override
+    public boolean run(BluetoothGattServer server) {
+        return server.notifyCharacteristicChanged(getDevice(), characteristic, false);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/WithingsUUID.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/WithingsUUID.java
new file mode 100644
index 0000000..9c767d4
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/WithingsUUID.java
@@ -0,0 +1,34 @@
+/*  Copyright (C) 2023-2024 Frank Ertl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication;
+
+import java.util.UUID;
+
+public final class WithingsUUID {
+
+    public static final UUID WITHINGS_SERVICE_UUID = UUID.fromString("00000020-5749-5448-0037-000000000000");
+    public static final UUID WITHINGS_WRITE_CHARACTERISTIC_UUID = UUID.fromString("00000024-5749-5448-0037-000000000000");
+    public static final UUID WITHINGS_APP_CHARACTERISTIC_UUID = UUID.fromString("10000059-5749-5448-0037-000000000000");
+    public static final UUID WITHINGS_APP_CHARACTERISTIC2_UUID = UUID.fromString("10000028-5749-5448-0037-000000000000");
+    public static final UUID CCC_DESCRIPTOR_UUID = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");
+    public static final UUID WITHINGS_ANCS_SERVICE_UUID = UUID.fromString("10000057-5749-5448-0037-000000000000");
+    public static final UUID NOTIFICATION_SOURCE_CHARACTERISTIC_UUID = UUID.fromString("10000059-5749-5448-0037-000000000000");
+    public static final UUID CONTROL_POINT_CHARACTERISTIC_UUID = UUID.fromString("10000058-5749-5448-0037-000000000000");
+    public static final UUID DATA_SOURCE_CHARACTERISTIC_UUID = UUID.fromString("1000005a-5749-5448-0037-000000000000");
+
+    private WithingsUUID() {}
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/conversation/AbstractConversation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/conversation/AbstractConversation.java
new file mode 100644
index 0000000..5bee785
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/conversation/AbstractConversation.java
@@ -0,0 +1,107 @@
+/*  Copyright (C) 2023-2024 Frank Ertl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.conversation;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.WithingsStructure;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.WithingsStructureType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.Message;
+
+public abstract class AbstractConversation implements Conversation {
+
+    private List<ConversationObserver> observers = new ArrayList();
+
+    private boolean complete;
+
+    protected Message request;
+
+    private short requestType;
+
+    protected ResponseHandler responseHandler;
+
+    public AbstractConversation(ResponseHandler responseHandler) {
+        this.responseHandler = responseHandler;
+    }
+
+    @Override
+    public void registerObserver(ConversationObserver observer) {
+        observers.add(observer);
+    }
+
+    @Override
+    public void removeObserver(ConversationObserver observer) {
+        observers.remove(observer);
+    }
+
+    @Override
+    public void setRequest(Message message) {
+        this.request = message;
+        this.requestType = message.getType();
+    }
+
+    @Override
+    public Message getRequest() {
+        return request;
+    }
+
+    @Override
+    public void handleResponse(Message response) {
+        if (response.getType() == requestType) {
+            if (request.needsResponse()) {
+                complete = true;
+            } else if (request.needsEOT()) {
+                complete = hasEOT(response);
+            }
+
+            doHandleResponse(response);
+            if (complete) {
+                notifyObservers(requestType);
+            }
+        }
+    }
+
+    @Override
+    public boolean isComplete() {
+        return complete;
+    }
+
+    protected void notifyObservers(short messageType) {
+        for (ConversationObserver observer : observers) {
+            observer.onConversationCompleted(messageType);
+        }
+    }
+
+    private boolean hasEOT(Message message) {
+        List<WithingsStructure> dataList = message.getDataStructures();
+        if (dataList != null) {
+            for (WithingsStructure strucuter :
+                    dataList) {
+                if (strucuter.getType() == WithingsStructureType.END_OF_TRANSMISSION) {
+                    return true;
+                }
+            }
+        }
+
+        return false;
+    }
+
+    protected abstract void doSendRequest(Message message);
+
+    protected abstract void doHandleResponse(Message message);
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/conversation/AbstractResponseHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/conversation/AbstractResponseHandler.java
new file mode 100644
index 0000000..107bbaf
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/conversation/AbstractResponseHandler.java
@@ -0,0 +1,30 @@
+/*  Copyright (C) 2023-2024 Frank Ertl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.conversation;
+
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.WithingsSteelHRDeviceSupport;
+
+public abstract class AbstractResponseHandler implements ResponseHandler {
+    protected GBDevice device;
+    protected WithingsSteelHRDeviceSupport support;
+
+    public AbstractResponseHandler(WithingsSteelHRDeviceSupport support) {
+        this.support = support;
+        this.device = support.getDevice();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/conversation/ActivitySampleHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/conversation/ActivitySampleHandler.java
new file mode 100644
index 0000000..dbbe0ec
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/conversation/ActivitySampleHandler.java
@@ -0,0 +1,269 @@
+/*  Copyright (C) 2023-2024 Frank Ertl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.conversation;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+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.withingssteelhr.WithingsSteelHRSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.WithingsSteelHRActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.WithingsSteelHRDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.activity.ActivityEntry;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.activity.WithingsActivityType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.WorkoutType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.ActivitySampleCalories;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.ActivitySampleCalories2;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.ActivitySampleDuration;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.ActivitySampleMovement;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.ActivitySampleSleep;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.ActivitySampleTime;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.ActivityHeartrate;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.WithingsStructure;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.WithingsStructureType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.Message;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.WithingsMessageType;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class ActivitySampleHandler extends AbstractResponseHandler {
+
+    private static final Logger logger = LoggerFactory.getLogger(ActivitySampleHandler.class);
+    private ActivityEntry activityEntry;
+    private List<ActivityEntry> activityEntries = new ArrayList<>();
+    private List<ActivityEntry> heartrateEntries = new ArrayList<>();
+
+    public ActivitySampleHandler(WithingsSteelHRDeviceSupport support) {
+        super(support);
+    }
+
+    @Override
+    public void handleResponse(Message response) {
+        List<WithingsStructure> data = response.getDataStructures();
+        if (data != null) {
+            handleActivityData(data, response.getType());
+        }
+    }
+
+    public void onSyncFinished() {
+        mergeHeartrateSamplesIntoActivitySammples();
+        saveData();
+    }
+
+    private void handleActivityData(List<WithingsStructure> dataList, short activityType) {
+        for (WithingsStructure data : dataList) {
+            switch (data.getType()) {
+                case WithingsStructureType.ACTIVITY_SAMPLE_TIME:
+                    handleTimestamp(data, activityType);
+                    break;
+                case WithingsStructureType.ACTIVITY_SAMPLE_DURATION:
+                    handleDuration(data);
+                    break;
+                case WithingsStructureType.ACTIVITY_SAMPLE_MOVEMENT:
+                    handleMovement(data);
+                    break;
+                case WithingsStructureType.ACTIVITY_SAMPLE_CALORIES:
+                    handleCalories1(data);
+                    break;
+                case WithingsStructureType.ACTIVITY_SAMPLE_CALORIES_2:
+                    handleCalories2(data);
+                    break;
+                case WithingsStructureType.ACTIVITY_SAMPLE_SLEEP:
+                    handleSleep(data);
+                    break;
+                case WithingsStructureType.ACTIVITY_SAMPLE_WALK:
+                    handleWalk(data);
+                    break;
+                case WithingsStructureType.ACTIVITY_SAMPLE_RUN:
+                    handleRun(data);
+                    break;
+                case WithingsStructureType.ACTIVITY_SAMPLE_SWIM:
+                    handleSwim(data);
+                    break;
+                case WithingsStructureType.ACTIVITY_HR:
+                    handleHeartrate(data);
+                    break;
+                case WithingsStructureType.WORKOUT_TYPE:
+                    handleWorkoutType(data);
+                    break;
+                default:
+                    logger.info("Received yet unhandled activity data of type '{}' with data '{}'.", data.getType(), GB.hexdump(data.getRawData()));
+            }
+        }
+
+        if (activityEntry != null) {
+            addToList(activityEntry);
+        }
+
+    }
+
+    private void handleTimestamp(WithingsStructure data, short activityType) {
+        if (activityEntry != null) {
+            addToList(activityEntry);
+        }
+
+        activityEntry = new ActivityEntry();
+        activityEntry.setIsHeartrate(activityType == WithingsMessageType.GET_HEARTRATE_SAMPLES);
+        activityEntry.setTimestamp((int) (((ActivitySampleTime) data).getDate().getTime() / 1000));
+    }
+
+    private void handleWorkoutType(WithingsStructure data) {
+        WithingsActivityType activityType = WithingsActivityType.fromCode(((WorkoutType) data).getActivityType());
+        activityEntry.setRawKind(activityType.toActivityKind().getCode());
+    }
+
+    private void handleDuration(WithingsStructure data) {
+        activityEntry.setDuration(((ActivitySampleDuration) data).getDuration());
+    }
+
+    private void handleHeartrate(WithingsStructure data) {
+        activityEntry.setIsHeartrate(((ActivityHeartrate) data).getHeartrate());
+    }
+
+    private void handleMovement(WithingsStructure data) {
+        activityEntry.setRawKind(ActivityKind.UNKNOWN.getCode());
+        activityEntry.setSteps(((ActivitySampleMovement) data).getSteps());
+        activityEntry.setDistance(((ActivitySampleMovement) data).getDistance());
+    }
+
+    private void handleWalk(WithingsStructure data) {
+        activityEntry.setRawKind(ActivityKind.WALKING.getCode());
+    }
+
+    private void handleRun(WithingsStructure data) {
+        activityEntry.setRawKind(ActivityKind.RUNNING.getCode());
+    }
+
+    private void handleSwim(WithingsStructure data) {
+        activityEntry.setRawKind(ActivityKind.SWIMMING.getCode());
+    }
+
+    private void handleSleep(WithingsStructure data) {
+        ActivityKind sleepType;
+        switch (((ActivitySampleSleep) data).getSleepType()) {
+            case 0:
+                sleepType = ActivityKind.LIGHT_SLEEP;
+                activityEntry.setRawIntensity(10);
+                break;
+            case 2:
+                sleepType = ActivityKind.DEEP_SLEEP;
+                activityEntry.setRawIntensity(70);
+                break;
+            case 3:
+                sleepType = ActivityKind.REM_SLEEP;
+                activityEntry.setRawIntensity(80);
+                break;
+            default:
+                sleepType = ActivityKind.LIGHT_SLEEP;
+                activityEntry.setRawIntensity(50);
+        }
+
+        activityEntry.setRawKind(sleepType.getCode());
+    }
+
+    private void handleCalories1(WithingsStructure data) {
+        activityEntry.setRawIntensity(((ActivitySampleCalories) data).getMet());
+        activityEntry.setCalories(((ActivitySampleCalories) data).getCalories());
+    }
+
+    private void handleCalories2(WithingsStructure data) {
+        activityEntry.setRawIntensity(((ActivitySampleCalories2) data).getMet());
+        activityEntry.setCalories(((ActivitySampleCalories2) data).getCalories());
+
+    }
+
+    private void addToList(ActivityEntry activityEntry) {
+        if (activityEntry.isHeartrate()) {
+            heartrateEntries.add(activityEntry);
+        } else {
+            activityEntries.add(activityEntry);
+        }
+    }
+
+    private void saveData() {
+        List<WithingsSteelHRActivitySample> activitySamples = new ArrayList<>();
+        for (ActivityEntry activityEntry : activityEntries) {
+            convertToSampleAndAddToList(activitySamples, activityEntry);
+        }
+        for (ActivityEntry activityEntry : heartrateEntries) {
+            convertToSampleAndAddToList(activitySamples, activityEntry);
+        }
+
+        writeToDB(activitySamples);
+    }
+
+    private void writeToDB(List<WithingsSteelHRActivitySample> activitySamples) {
+        try (DBHandler dbHandler = GBApplication.acquireDB()) {
+            Long userId = DBHelper.getUser(dbHandler.getDaoSession()).getId();
+            Long deviceId = DBHelper.getDevice(device, dbHandler.getDaoSession()).getId();
+            WithingsSteelHRSampleProvider provider = new WithingsSteelHRSampleProvider(device, dbHandler.getDaoSession());
+            for (WithingsSteelHRActivitySample sample : activitySamples) {
+                sample.setDeviceId(deviceId);
+                sample.setUserId(userId);
+            }
+            provider.addGBActivitySamples(activitySamples.toArray(new WithingsSteelHRActivitySample[0]));
+        } catch (Exception ex) {
+            logger.warn("Error saving activity data: " + ex.getLocalizedMessage());
+        }
+    }
+
+    private void mergeHeartrateSamplesIntoActivitySammples() {
+        for (ActivityEntry heartrateEntry : heartrateEntries) {
+            for (ActivityEntry activityEntry : activityEntries) {
+                if (doActivitiesOverlap(heartrateEntry, activityEntry)) {
+                    updateHeartrateEntry(heartrateEntry, activityEntry);
+                }
+            }
+        }
+    }
+
+    private boolean doActivitiesOverlap(ActivityEntry heartrateEntry, ActivityEntry activityEntry) {
+        return activityEntry.getTimestamp() <= heartrateEntry.getTimestamp()
+                && (activityEntry.getTimestamp() + activityEntry.getDuration()) >= heartrateEntry.getTimestamp();
+    }
+
+    private void updateHeartrateEntry(ActivityEntry heartRateEntry, ActivityEntry activityEntry) {
+        heartRateEntry.setRawKind(activityEntry.getRawKind());
+        heartRateEntry.setRawIntensity(activityEntry.getRawIntensity());
+        heartRateEntry.setDuration(activityEntry.getDuration() - (heartRateEntry.getTimestamp() - activityEntry.getTimestamp()));
+        // If timestamps are exactly the same and only then, the heartrate entry would overwrite the activity entry in the DB, so we set more values.
+        // If we would do so everytime, steps and so on would be multiplicated.
+        if (heartRateEntry.getTimestamp() == activityEntry.getTimestamp()) {
+            heartRateEntry.setSteps(activityEntry.getSteps());
+            heartRateEntry.setDistance(activityEntry.getDistance());
+            heartRateEntry.setCalories(activityEntry.getCalories());
+        }
+    }
+
+    private void convertToSampleAndAddToList(List<WithingsSteelHRActivitySample> activitySamples, ActivityEntry activityEntry) {
+        WithingsSteelHRActivitySample sample = new WithingsSteelHRActivitySample();
+        sample.setTimestamp(activityEntry.getTimestamp());
+        sample.setDuration(activityEntry.getDuration());
+        sample.setHeartRate(activityEntry.getHeartrate());
+        sample.setSteps(activityEntry.getSteps());
+        sample.setRawKind(activityEntry.getRawKind());
+        sample.setCalories(activityEntry.getCalories());
+        sample.setDistance(activityEntry.getDistance());
+        sample.setRawIntensity(activityEntry.getRawIntensity());
+        activitySamples.add(sample);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/conversation/BatteryStateHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/conversation/BatteryStateHandler.java
new file mode 100644
index 0000000..ad7bc5d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/conversation/BatteryStateHandler.java
@@ -0,0 +1,57 @@
+/*  Copyright (C) 2023-2024 Frank Ertl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.conversation;
+
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.BatteryState;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.WithingsSteelHRDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.BatteryValues;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.Message;
+
+public class BatteryStateHandler extends AbstractResponseHandler {
+
+    public BatteryStateHandler(WithingsSteelHRDeviceSupport support) {
+        super(support);
+    }
+
+    @Override
+    public void handleResponse(Message response) {
+        handleBatteryState(response.getStructureByType(BatteryValues.class));
+    }
+
+    private void handleBatteryState(BatteryValues batteryValues) {
+        if (batteryValues == null) {
+            return;
+        }
+
+        GBDeviceEventBatteryInfo batteryInfo = new GBDeviceEventBatteryInfo();
+        batteryInfo.level = batteryValues.getPercent();
+        switch (batteryValues.getStatus()) {
+            case 0:
+                batteryInfo.state = BatteryState.BATTERY_CHARGING;
+                break;
+            case 1:
+                batteryInfo.state = BatteryState.BATTERY_LOW;
+                break;
+            default:
+                batteryInfo.state = BatteryState.BATTERY_NORMAL;
+        }
+        batteryInfo.voltage = batteryValues.getVolt();
+        support.evaluateGBDeviceEvent(batteryInfo);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/conversation/Conversation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/conversation/Conversation.java
new file mode 100644
index 0000000..9720a0c
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/conversation/Conversation.java
@@ -0,0 +1,28 @@
+/*  Copyright (C) 2023-2024 Frank Ertl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.conversation;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.Message;
+
+public interface Conversation {
+    void registerObserver(ConversationObserver observer);
+    void removeObserver(ConversationObserver observer);
+    void setRequest(Message message);
+    Message getRequest();
+    void handleResponse(Message mesage);
+    boolean isComplete();
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/conversation/ConversationObserver.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/conversation/ConversationObserver.java
new file mode 100644
index 0000000..37d809b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/conversation/ConversationObserver.java
@@ -0,0 +1,21 @@
+/*  Copyright (C) 2023-2024 Frank Ertl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.conversation;
+
+public interface ConversationObserver {
+    void onConversationCompleted(short conversationType);
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/conversation/ConversationQueue.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/conversation/ConversationQueue.java
new file mode 100644
index 0000000..d301793
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/conversation/ConversationQueue.java
@@ -0,0 +1,100 @@
+/*  Copyright (C) 2023-2024 Frank Ertl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.conversation;
+
+import android.bluetooth.BluetoothGattCharacteristic;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Arrays;
+import java.util.Deque;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.Queue;
+import java.util.concurrent.LinkedBlockingQueue;
+
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.WithingsSteelHRDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.WithingsUUID;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.Message;
+import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
+
+public class ConversationQueue implements ConversationObserver
+{
+    private static final Logger logger = LoggerFactory.getLogger(WithingsSteelHRDeviceSupport.class);
+    private final LinkedList<Conversation> queue = new LinkedList<>();
+    private WithingsSteelHRDeviceSupport support;
+
+    public ConversationQueue(WithingsSteelHRDeviceSupport support) {
+        this.support = support;
+    }
+
+    @Override
+    public void onConversationCompleted(short conversationType) {
+        queue.remove(getConversation(conversationType));
+        send();
+    }
+
+    public void clear() {
+        queue.clear();
+    }
+
+    public void send() {
+        logger.debug("Sending of queued messages has been requested.");
+        if (!queue.isEmpty()) {
+            Conversation nextInLine = queue.peek();
+            if (nextInLine!= null) {
+                logger.debug("Sending next queued message.");
+                Message request = nextInLine.getRequest();
+                support.sendToDevice(request);
+            }
+        }
+    }
+
+    public void addConversation(Conversation conversation) {
+        if (conversation == null) {
+            return;
+        }
+
+        if (conversation.getRequest().needsResponse() || conversation.getRequest().needsEOT()) {
+            queue.add(conversation);
+            conversation.registerObserver(this);
+        } else {
+            support.sendToDevice(conversation.getRequest());
+        }
+    }
+
+    public void processResponse(Message response) {
+        Conversation conversation = getConversation(response.getType());
+        if (conversation != null) {
+            conversation.handleResponse(response);
+        }
+    }
+
+    private Conversation getConversation(short requestType) {
+        for (Conversation conversation : queue) {
+            if (conversation.getRequest() != null && conversation.getRequest().getType() == requestType) {
+                return conversation;
+            }
+        }
+
+        return null;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/conversation/HeartRateHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/conversation/HeartRateHandler.java
new file mode 100644
index 0000000..55777ed
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/conversation/HeartRateHandler.java
@@ -0,0 +1,89 @@
+/*  Copyright (C) 2023-2024 Frank Ertl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.conversation;
+
+import android.content.Intent;
+
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.GregorianCalendar;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.withingssteelhr.WithingsSteelHRSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.WithingsSteelHRActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.WithingsSteelHRDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.activity.SleepActivitySampleHelper;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.HeartRate;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.LiveHeartRate;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.WithingsStructure;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.Message;
+
+public class HeartRateHandler extends AbstractResponseHandler {
+    private static final Logger logger = LoggerFactory.getLogger(HeartRateHandler.class);
+
+    public HeartRateHandler(WithingsSteelHRDeviceSupport support) {
+        super(support);
+    }
+
+    @Override
+    public void handleResponse(Message response) {
+        if (response.getDataStructures().size() > 0) {
+            handleHeartRateData(response.getDataStructures().get(0));
+        }
+    }
+
+    private void handleHeartRateData(WithingsStructure structure) {
+        int heartRate = 0;
+        if (structure instanceof HeartRate) {
+            heartRate = ((HeartRate)structure).getHeartrate();
+        } else if (structure instanceof LiveHeartRate) {
+            heartRate = ((LiveHeartRate)structure).getHeartrate();
+        }
+
+        if (heartRate > 0) {
+            saveHeartRateData(heartRate);
+        }
+    }
+
+    private void saveHeartRateData(int heartRate) {
+        WithingsSteelHRActivitySample sample = new WithingsSteelHRActivitySample();
+        sample.setTimestamp((int) (GregorianCalendar.getInstance().getTimeInMillis() / 1000L));
+        sample.setHeartRate(heartRate);
+        try (DBHandler dbHandler = GBApplication.acquireDB()) {
+            Long userId = DBHelper.getUser(dbHandler.getDaoSession()).getId();
+            Long deviceId = DBHelper.getDevice(device, dbHandler.getDaoSession()).getId();
+            WithingsSteelHRSampleProvider provider = new WithingsSteelHRSampleProvider(device, dbHandler.getDaoSession());
+            sample.setDeviceId(deviceId);
+            sample.setUserId(userId);
+            sample = SleepActivitySampleHelper.mergeIfNecessary(provider, sample);
+            provider.addGBActivitySample(sample);
+            Intent intent = new Intent(DeviceService.ACTION_REALTIME_SAMPLES)
+                    .putExtra(GBDevice.EXTRA_DEVICE, support.getDevice())
+                    .putExtra(DeviceService.EXTRA_REALTIME_SAMPLE, sample);
+            LocalBroadcastManager.getInstance(support.getContext()).sendBroadcast(intent);
+        } catch (Exception ex) {
+            logger.warn("Error saving current heart rate: " + ex.getLocalizedMessage());
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/conversation/ResponseHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/conversation/ResponseHandler.java
new file mode 100644
index 0000000..239bf97
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/conversation/ResponseHandler.java
@@ -0,0 +1,23 @@
+/*  Copyright (C) 2023-2024 Frank Ertl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.conversation;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.Message;
+
+public interface ResponseHandler {
+    void handleResponse(Message response);
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/conversation/SetupFinishedHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/conversation/SetupFinishedHandler.java
new file mode 100644
index 0000000..6d3f2aa
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/conversation/SetupFinishedHandler.java
@@ -0,0 +1,35 @@
+/*  Copyright (C) 2023-2024 Frank Ertl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.conversation;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.WithingsSteelHRDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.Message;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.WithingsMessageType;
+
+public class SetupFinishedHandler extends AbstractResponseHandler {
+
+    public SetupFinishedHandler(WithingsSteelHRDeviceSupport support) {
+        super(support);
+    }
+
+    @Override
+    public void handleResponse(Message response) {
+        if (response.getType() == WithingsMessageType.SETUP_FINISHED) {
+            support.finishInitialization();;
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/conversation/SimpleConversation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/conversation/SimpleConversation.java
new file mode 100644
index 0000000..0c959b4
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/conversation/SimpleConversation.java
@@ -0,0 +1,42 @@
+/*  Copyright (C) 2023-2024 Frank Ertl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.conversation;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.Message;
+
+public class SimpleConversation extends AbstractConversation {
+
+    public SimpleConversation(ResponseHandler responseHandler) {
+        super(responseHandler);
+    }
+
+    public SimpleConversation() {
+        super(null);
+    }
+
+    @Override
+    protected void doSendRequest(Message message) {
+        // Do nothing
+    }
+
+    @Override
+    protected void doHandleResponse(Message message) {
+        if (responseHandler != null) {
+            responseHandler.handleResponse(message);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/conversation/SyncFinishedHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/conversation/SyncFinishedHandler.java
new file mode 100644
index 0000000..6c0bcf5
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/conversation/SyncFinishedHandler.java
@@ -0,0 +1,37 @@
+/*  Copyright (C) 2023-2024 Frank Ertl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.conversation;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.WithingsSteelHRDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.Message;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.WithingsMessageType;
+
+public class SyncFinishedHandler extends AbstractResponseHandler {
+
+    public SyncFinishedHandler(WithingsSteelHRDeviceSupport support) {
+        super(support);
+    }
+
+
+
+    @Override
+    public void handleResponse(Message response) {
+        if (response.getType() == WithingsMessageType.SYNC_OK) {
+            support.finishSync();
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/conversation/WorkoutScreenListHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/conversation/WorkoutScreenListHandler.java
new file mode 100644
index 0000000..56cc8a7
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/conversation/WorkoutScreenListHandler.java
@@ -0,0 +1,65 @@
+/*  Copyright (C) 2023-2024 Andreas Shimokawa, Frank Ertl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.conversation;
+
+import android.content.SharedPreferences;
+import android.text.TextUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.WithingsSteelHRDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.activity.WithingsActivityType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.WithingsStructure;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.WorkoutScreenList;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.Message;
+
+public class WorkoutScreenListHandler extends AbstractResponseHandler {
+
+    public WorkoutScreenListHandler(WithingsSteelHRDeviceSupport support) {
+        super(support);
+    }
+
+    @Override
+    public void handleResponse(Message response) {
+        List<WithingsStructure> data = response.getDataStructures();
+        if (data != null && !data.isEmpty()) {
+            WorkoutScreenList screenList = (WorkoutScreenList) data.get(0);
+            saveScreenList(screenList);
+        }
+    }
+
+    private void saveScreenList(WorkoutScreenList screenList) {
+        int[] workoutIds = screenList.getWorkoutIds();
+        List<String> prefValues = new ArrayList<>();
+        for (int i = 0; i < workoutIds.length; i++) {
+            int currentId = workoutIds[i];
+            if (currentId > 0) {
+                WithingsActivityType type = WithingsActivityType.fromCode(currentId);
+                prefValues.add(type.name().toLowerCase(Locale.ROOT));
+            }
+         }
+
+        String workoutActivityTypes = TextUtils.join(",", prefValues);
+        GBDevice device = support.getDevice();
+        final SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(device.getAddress());
+        prefs.edit().putString("workout_activity_types_sortable", workoutActivityTypes).apply();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/ActivityHeartrate.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/ActivityHeartrate.java
new file mode 100644
index 0000000..3673ff1
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/ActivityHeartrate.java
@@ -0,0 +1,47 @@
+/*  Copyright (C) 2023-2024 Frank Ertl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures;
+
+import java.nio.ByteBuffer;
+
+public class ActivityHeartrate extends WithingsStructure
+{
+    private int heartrate;
+
+    public int getHeartrate() {
+        return heartrate;
+    }
+
+    @Override
+    public short getLength() {
+        return 8;
+    }
+
+    @Override
+    protected void fillinTypeSpecificData(ByteBuffer buffer) {
+    }
+
+    @Override
+    protected void fillFromRawDataAsBuffer(ByteBuffer rawDataBuffer) {
+        heartrate = rawDataBuffer.get(0) & 0xff;
+    }
+
+    @Override
+    public short getType() {
+        return WithingsStructureType.ACTIVITY_HR;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/ActivitySampleCalories.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/ActivitySampleCalories.java
new file mode 100644
index 0000000..c80c358
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/ActivitySampleCalories.java
@@ -0,0 +1,54 @@
+/*  Copyright (C) 2023-2024 Frank Ertl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures;
+
+import java.nio.ByteBuffer;
+
+public class ActivitySampleCalories extends WithingsStructure {
+
+    private int calories;
+    private int met;
+
+    public int getCalories() {
+        return calories;
+    }
+
+    public int getMet() {
+        return met;
+    }
+
+    @Override
+    public short getLength() {
+        return 8;
+    }
+
+    @Override
+    protected void fillinTypeSpecificData(ByteBuffer buffer) {
+
+    }
+
+    @Override
+    public void fillFromRawDataAsBuffer(ByteBuffer rawDataBuffer) {
+        calories = rawDataBuffer.getShort() & 65535;
+        met = rawDataBuffer.getShort() & 65535;
+    }
+
+    @Override
+    public short getType() {
+        return WithingsStructureType.ACTIVITY_SAMPLE_CALORIES;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/ActivitySampleCalories2.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/ActivitySampleCalories2.java
new file mode 100644
index 0000000..b94c064
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/ActivitySampleCalories2.java
@@ -0,0 +1,25 @@
+/*  Copyright (C) 2023-2024 Frank Ertl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures;
+
+public class ActivitySampleCalories2 extends ActivitySampleCalories {
+
+    @Override
+    public short getType() {
+        return WithingsStructureType.ACTIVITY_SAMPLE_CALORIES_2;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/ActivitySampleDuration.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/ActivitySampleDuration.java
new file mode 100644
index 0000000..bc85b63
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/ActivitySampleDuration.java
@@ -0,0 +1,51 @@
+/*  Copyright (C) 2023-2024 Frank Ertl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures;
+
+import java.nio.ByteBuffer;
+
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
+
+public class ActivitySampleDuration extends WithingsStructure {
+
+    private short duration;
+
+    public short getDuration() {
+        return duration;
+    }
+
+    @Override
+    public short getLength() {
+        return 6;
+    }
+
+    @Override
+    protected void fillinTypeSpecificData(ByteBuffer buffer) {
+
+    }
+
+    @Override
+    public void fillFromRawDataAsBuffer(ByteBuffer rawDataBuffer) {
+        duration = rawDataBuffer.getShort();
+    }
+
+
+    @Override
+    public short getType() {
+        return WithingsStructureType.ACTIVITY_SAMPLE_DURATION;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/ActivitySampleMovement.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/ActivitySampleMovement.java
new file mode 100644
index 0000000..9361b02
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/ActivitySampleMovement.java
@@ -0,0 +1,69 @@
+/*  Copyright (C) 2023-2024 Frank Ertl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures;
+
+import java.nio.ByteBuffer;
+
+public class ActivitySampleMovement extends WithingsStructure {
+
+    private short steps;
+    private int distance;
+    private int asc;
+    private int desc;
+
+    public short getSteps() {
+        return steps;
+    }
+
+    public void setSteps(short steps) {
+        this.steps = steps;
+    }
+
+    public int getDistance() {
+        return distance;
+    }
+
+    public void setDistance(int distance) {
+        this.distance = distance;
+    }
+
+    @Override
+    public short getLength() {
+        return 18;
+    }
+
+    @Override
+    protected void fillinTypeSpecificData(ByteBuffer buffer) {
+        buffer.putShort(steps);
+        buffer.putInt(distance);
+        buffer.putInt(asc);
+        buffer.putInt(desc);
+    }
+
+    @Override
+    public void fillFromRawDataAsBuffer(ByteBuffer rawDataBuffer) {
+        steps = rawDataBuffer.getShort();
+        distance = rawDataBuffer.getInt();
+        asc = rawDataBuffer.getInt();
+        desc = rawDataBuffer.getInt();
+    }
+
+    @Override
+    public short getType() {
+        return WithingsStructureType.ACTIVITY_SAMPLE_MOVEMENT;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/ActivitySampleRun.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/ActivitySampleRun.java
new file mode 100644
index 0000000..d3d2d94
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/ActivitySampleRun.java
@@ -0,0 +1,36 @@
+/*  Copyright (C) 2023-2024 Frank Ertl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures;
+
+import java.nio.ByteBuffer;
+
+public class ActivitySampleRun extends WithingsStructure {
+    @Override
+    public short getLength() {
+        return 0;
+    }
+
+    @Override
+    protected void fillinTypeSpecificData(ByteBuffer buffer) {
+
+    }
+
+    @Override
+    public short getType() {
+        return WithingsStructureType.ACTIVITY_SAMPLE_RUN;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/ActivitySampleSleep.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/ActivitySampleSleep.java
new file mode 100644
index 0000000..d9a9130
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/ActivitySampleSleep.java
@@ -0,0 +1,50 @@
+/*  Copyright (C) 2023-2024 Frank Ertl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures;
+
+import java.nio.ByteBuffer;
+
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
+
+public class ActivitySampleSleep extends WithingsStructure {
+
+    private short sleepType;
+
+    public short getSleepType() {
+        return sleepType;
+    }
+
+    @Override
+    public short getLength() {
+        return 6;
+    }
+
+    @Override
+    protected void fillinTypeSpecificData(ByteBuffer buffer) {
+
+    }
+
+    @Override
+    public void fillFromRawDataAsBuffer(ByteBuffer rawDataBuffer) {
+        sleepType = rawDataBuffer.getShort();
+    }
+
+    @Override
+    public short getType() {
+        return WithingsStructureType.ACTIVITY_SAMPLE_SLEEP;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/ActivitySampleSwim.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/ActivitySampleSwim.java
new file mode 100644
index 0000000..18110ea
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/ActivitySampleSwim.java
@@ -0,0 +1,36 @@
+/*  Copyright (C) 2023-2024 Frank Ertl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures;
+
+import java.nio.ByteBuffer;
+
+public class ActivitySampleSwim extends WithingsStructure {
+    @Override
+    public short getLength() {
+        return 0;
+    }
+
+    @Override
+    protected void fillinTypeSpecificData(ByteBuffer buffer) {
+
+    }
+
+    @Override
+    public short getType() {
+        return WithingsStructureType.ACTIVITY_SAMPLE_SWIM;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/ActivitySampleTime.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/ActivitySampleTime.java
new file mode 100644
index 0000000..2d040af
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/ActivitySampleTime.java
@@ -0,0 +1,52 @@
+/*  Copyright (C) 2023-2024 Frank Ertl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures;
+
+import java.nio.ByteBuffer;
+import java.util.Date;
+
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
+
+public class ActivitySampleTime extends WithingsStructure {
+
+    private Date date;
+
+    public Date getDate() {
+        return date;
+    }
+
+    @Override
+    public short getLength() {
+        return 8;
+    }
+
+    @Override
+    protected void fillinTypeSpecificData(ByteBuffer buffer) {
+
+    }
+
+    @Override
+    public void fillFromRawDataAsBuffer(ByteBuffer rawDataBuffer) {
+        long timestampInSeconds = rawDataBuffer.getInt() & 4294967295L;
+        date = new Date(timestampInSeconds * 1000);
+    }
+
+    @Override
+    public short getType() {
+        return WithingsStructureType.ACTIVITY_SAMPLE_TIME;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/ActivitySampleUnknown.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/ActivitySampleUnknown.java
new file mode 100644
index 0000000..d06ff2a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/ActivitySampleUnknown.java
@@ -0,0 +1,36 @@
+/*  Copyright (C) 2023-2024 Frank Ertl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures;
+
+import java.nio.ByteBuffer;
+
+public class ActivitySampleUnknown extends WithingsStructure {
+    @Override
+    public short getLength() {
+        return 8;
+    }
+
+    @Override
+    protected void fillinTypeSpecificData(ByteBuffer buffer) {
+
+    }
+
+    @Override
+    public short getType() {
+        return WithingsStructureType.ACTIVITY_SAMPLE_UNKNOWN;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/ActivitySampleWalk.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/ActivitySampleWalk.java
new file mode 100644
index 0000000..40d54a6
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/ActivitySampleWalk.java
@@ -0,0 +1,50 @@
+/*  Copyright (C) 2023-2024 Frank Ertl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures;
+
+import java.nio.ByteBuffer;
+
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
+
+public class ActivitySampleWalk extends WithingsStructure {
+
+    private short level;
+
+    public short getLevel() {
+        return level;
+    }
+
+    @Override
+    public short getLength() {
+        return 6;
+    }
+
+    @Override
+    protected void fillinTypeSpecificData(ByteBuffer buffer) {
+
+    }
+
+    @Override
+    public void fillFromRawDataAsBuffer(ByteBuffer rawDataBuffer) {
+        level = (short) (rawDataBuffer.getShort() & 65535);
+    }
+
+    @Override
+    public short getType() {
+        return WithingsStructureType.ACTIVITY_SAMPLE_WALK;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/ActivityTarget.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/ActivityTarget.java
new file mode 100644
index 0000000..e764837
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/ActivityTarget.java
@@ -0,0 +1,51 @@
+/*  Copyright (C) 2023-2024 Frank Ertl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures;
+
+import java.nio.ByteBuffer;
+
+public class ActivityTarget extends WithingsStructure {
+
+    private long targetCount;
+
+    public ActivityTarget(long targetCount) {
+        this.targetCount = targetCount;
+    }
+
+    public long getTargetCount() {
+        return targetCount;
+    }
+
+    public void setTargetCount(long targetCount) {
+        this.targetCount = targetCount;
+    }
+
+    @Override
+    public short getLength() {
+        return 12;
+    }
+
+    @Override
+    protected void fillinTypeSpecificData(ByteBuffer rawDataBuffer) {
+        rawDataBuffer.putLong(targetCount);
+    }
+
+    @Override
+    public short getType() {
+        return WithingsStructureType.ACTIVITY_TARGET;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/AlarmName.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/AlarmName.java
new file mode 100644
index 0000000..c66f3f4
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/AlarmName.java
@@ -0,0 +1,44 @@
+/*  Copyright (C) 2023-2024 Frank Ertl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures;
+
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+
+public class AlarmName extends WithingsStructure {
+
+    private String name;
+
+    public AlarmName(String name) {
+        this.name = name;
+    }
+
+    @Override
+    public short getLength() {
+        return (short) ((name != null ? name.getBytes().length : 0) + 1 + HEADER_SIZE);
+    }
+
+    @Override
+    protected void fillinTypeSpecificData(ByteBuffer rawDataBuffer) {
+        addStringAsBytesWithLengthByte(rawDataBuffer, name);
+    }
+
+    @Override
+    public short getType() {
+        return WithingsStructureType.ALARM_NAME;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/AlarmSettings.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/AlarmSettings.java
new file mode 100644
index 0000000..5e8e744
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/AlarmSettings.java
@@ -0,0 +1,111 @@
+/*  Copyright (C) 2023-2024 Frank Ertl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures;
+
+import java.nio.ByteBuffer;
+
+public class AlarmSettings extends WithingsStructure {
+    private short hour;
+    private short minute;
+    private short dayOfWeek;
+    private short dayOfMonth;
+    private short month;
+    private short year;
+    private short smartWakeupMinutes;
+
+    public short getHour() {
+        return hour;
+    }
+
+    public void setHour(short hour) {
+        this.hour = hour;
+    }
+
+    public short getMinute() {
+        return minute;
+    }
+
+    public void setMinute(short minute) {
+        this.minute = minute;
+    }
+
+    public short getDayOfWeek() {
+        return dayOfWeek;
+    }
+
+    public void setDayOfWeek(short dayOfWeek) {
+        this.dayOfWeek = dayOfWeek;
+    }
+
+    public short getDayOfMonth() {
+        return dayOfMonth;
+    }
+
+    public void setDayOfMonth(short dayOfMonth) {
+        this.dayOfMonth = dayOfMonth;
+    }
+
+    public short getMonth() {
+        return month;
+    }
+
+    public void setMonth(short month) {
+        this.month = month;
+    }
+
+    public short getYear() {
+        return year;
+    }
+
+    public void setYear(short year) {
+        this.year = year;
+    }
+
+    public short getYetUnkown() {
+        return smartWakeupMinutes;
+    }
+
+    public void setSmartWakeupMinutes(short smartWakeupMinutes) {
+        this.smartWakeupMinutes = smartWakeupMinutes;
+    }
+
+    @Override
+    public short getLength() {
+        return 11;
+    }
+
+    @Override
+    protected void fillinTypeSpecificData(ByteBuffer buffer) {
+        buffer.put((byte)hour);
+        buffer.put((byte)minute);
+        buffer.put((byte)dayOfWeek);
+        buffer.put((byte)dayOfMonth);
+        buffer.put((byte)month);
+        buffer.put((byte)year);
+        buffer.put((byte)smartWakeupMinutes);
+    }
+
+    @Override
+    public short getType() {
+        return WithingsStructureType.ALARM;
+    }
+
+    @Override
+    public boolean withEndOfMessage() {
+        return true;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/AlarmStatus.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/AlarmStatus.java
new file mode 100644
index 0000000..300f881
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/AlarmStatus.java
@@ -0,0 +1,51 @@
+/*  Copyright (C) 2023-2024 Frank Ertl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures;
+
+import java.nio.ByteBuffer;
+
+public class AlarmStatus extends WithingsStructure {
+
+    private boolean enabled;
+
+    public AlarmStatus(boolean enabled) {
+        this.enabled = enabled;
+    }
+
+    public boolean isEnabled() {
+        return enabled;
+    }
+
+    public void setEnabled(boolean enabled) {
+        this.enabled = enabled;
+    }
+
+    @Override
+    public short getLength() {
+        return 5;
+    }
+
+    @Override
+    protected void fillinTypeSpecificData(ByteBuffer buffer) {
+        buffer.put(enabled? (byte) 1 : (byte) 0);
+    }
+
+    @Override
+    public short getType() {
+        return WithingsStructureType.ALARM_STATUS;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/AncsStatus.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/AncsStatus.java
new file mode 100644
index 0000000..2bbea2a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/AncsStatus.java
@@ -0,0 +1,45 @@
+/*  Copyright (C) 2023-2024 Frank Ertl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures;
+
+import java.nio.ByteBuffer;
+
+public class AncsStatus extends WithingsStructure {
+
+    private boolean isOn;
+
+    public AncsStatus() {}
+
+    public AncsStatus(boolean isOn) {
+        this.isOn = isOn;
+    }
+
+    @Override
+    public short getLength() {
+        return 5;
+    }
+
+    @Override
+    protected void fillinTypeSpecificData(ByteBuffer buffer) {
+        buffer.put(isOn? (byte)0x01 : 0x00);
+    }
+
+    @Override
+    public short getType() {
+        return WithingsStructureType.ANCS_STATUS;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/BatteryValues.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/BatteryValues.java
new file mode 100644
index 0000000..0f57128
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/BatteryValues.java
@@ -0,0 +1,73 @@
+/*  Copyright (C) 2023-2024 Frank Ertl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures;
+
+import java.nio.ByteBuffer;
+
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
+
+public class BatteryValues extends WithingsStructure {
+
+    private short percent;
+    private short status;
+    private int volt;
+
+    public short getPercent() {
+        return percent;
+    }
+
+    public void setPercent(short percent) {
+        this.percent = percent;
+    }
+
+    public short getStatus() {
+        return status;
+    }
+
+    public void setStatus(short status) {
+        this.status = status;
+    }
+
+    public int getVolt() {
+        return volt;
+    }
+
+    public void setVolt(int volt) {
+        this.volt = volt;
+    }
+
+    @Override
+    public short getLength() {
+        return 14;
+    }
+
+    @Override
+    public void fillFromRawDataAsBuffer(ByteBuffer rawDataBuffer) {
+        percent = (short)(rawDataBuffer.get() & 255);
+        status = (short)(rawDataBuffer.get() & 255);
+        volt = rawDataBuffer.getInt();
+    }
+
+    @Override
+    protected void fillinTypeSpecificData(ByteBuffer buffer) {
+    }
+
+    @Override
+    public short getType() {
+        return WithingsStructureType.BATTERY_STATUS;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/Challenge.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/Challenge.java
new file mode 100644
index 0000000..4cb888f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/Challenge.java
@@ -0,0 +1,80 @@
+/*  Copyright (C) 2023-2024 Frank Ertl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures;
+
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+
+public class Challenge extends WithingsStructure {
+
+    private String macAddress;
+
+    private byte[] challenge;
+
+    public void setMacAddress(String macAddress) {
+        this.macAddress = macAddress;
+    }
+
+    public void setChallenge(byte[] challenge) {
+        this.challenge = challenge;
+    }
+
+    public String getMacAddress() {
+        return macAddress;
+    }
+
+    public byte[] getChallenge() {
+        return challenge;
+    }
+
+    @Override
+    public short getLength() {
+        int challengeLength = 0;
+        int macAddressLength = (macAddress != null ? macAddress.getBytes().length : 0) + 1;
+        if (challenge != null) {
+            challengeLength = challenge.length;
+        }
+
+        return (short) (macAddressLength + challengeLength + 5);
+    }
+
+    @Override
+    protected void fillinTypeSpecificData(ByteBuffer buffer) {
+        addStringAsBytesWithLengthByte(buffer, macAddress);
+
+        if (challenge != null) {
+            buffer.put((byte) challenge.length);
+            buffer.put(challenge);
+        } else {
+            buffer.put((byte)0);
+        }
+    }
+
+    @Override
+    public void fillFromRawDataAsBuffer(ByteBuffer rawDataBuffer) {
+        macAddress = getNextString(rawDataBuffer);
+        challenge = getNextByteArray(rawDataBuffer);
+    }
+
+    @Override
+    public short getType() {
+        return WithingsStructureType.CHALLENGE;
+    }
+
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/ChallengeResponse.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/ChallengeResponse.java
new file mode 100644
index 0000000..8bb2c05
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/ChallengeResponse.java
@@ -0,0 +1,54 @@
+/*  Copyright (C) 2023-2024 Frank Ertl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures;
+
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+
+public class ChallengeResponse extends WithingsStructure {
+
+    private byte[] response = new byte[0];
+
+    public byte[] getResponse() {
+        return response;
+    }
+
+    public void setResponse(byte[] response) {
+        this.response = response;
+    }
+
+    @Override
+    public short getLength() {
+        return (short) ((response != null ? response.length : 0) + 5);
+    }
+
+    @Override
+    protected void fillinTypeSpecificData(ByteBuffer buffer) {
+        addByteArrayWithLengthByte(buffer, response);
+    }
+
+    @Override
+    public void fillFromRawDataAsBuffer(ByteBuffer rawDataBuffer) {
+        response = getNextByteArray(rawDataBuffer);
+    }
+
+    @Override
+    public short getType() {
+        return WithingsStructureType.CHALLENGE_RESPONSE;
+    }
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/DataStructureFactory.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/DataStructureFactory.java
new file mode 100644
index 0000000..251f8d1
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/DataStructureFactory.java
@@ -0,0 +1,168 @@
+/*  Copyright (C) 2023-2024 Frank Ertl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class DataStructureFactory {
+
+    private static final Logger logger = LoggerFactory.getLogger(DataStructureFactory.class.getSimpleName());
+    private static final int HEADER_SIZE = 4;
+
+    public List<WithingsStructure> createStructuresFromRawData(byte[] rawData) {
+        List<WithingsStructure> structures = new ArrayList<>();
+        if (rawData == null) {
+            return structures;
+        }
+
+        List<byte[]> rawDataStructures = splitRawData(rawData);
+        for (byte[] rawDataStructure : rawDataStructures) {
+            WithingsStructure structure = null;
+
+            short structureTypeFromResponse = (short) BLETypeConversions.toInt16(rawDataStructure[1], rawDataStructure[0]);
+
+            switch (structureTypeFromResponse) {
+                case WithingsStructureType.HR:
+                    structure = new HeartRate();
+                    break;
+                case WithingsStructureType.LIVE_HR:
+                    structure = new LiveHeartRate();
+                    break;
+                case WithingsStructureType.BATTERY_STATUS:
+                    structure = new BatteryValues();
+                    break;
+                case WithingsStructureType.SCREEN_SETTINGS:
+                    structure = new ScreenSettings();
+                    break;
+                case WithingsStructureType.ANCS_STATUS:
+                    structure = new AncsStatus();
+                    break;
+                case WithingsStructureType.ACTIVITY_SAMPLE_TIME:
+                    structure = new ActivitySampleTime();
+                    break;
+                case WithingsStructureType.ACTIVITY_SAMPLE_DURATION:
+                    structure = new ActivitySampleDuration();
+                    break;
+                case WithingsStructureType.ACTIVITY_SAMPLE_MOVEMENT:
+                    structure = new ActivitySampleMovement();
+                    break;
+                case WithingsStructureType.ACTIVITY_SAMPLE_CALORIES:
+                    structure = new ActivitySampleCalories();
+                    break;
+                case WithingsStructureType.ACTIVITY_SAMPLE_CALORIES_2:
+                    structure = new ActivitySampleCalories2();
+                    break;
+                case WithingsStructureType.ACTIVITY_SAMPLE_SLEEP:
+                    structure = new ActivitySampleSleep();
+                    break;
+                case WithingsStructureType.ACTIVITY_SAMPLE_WALK:
+                    structure = new ActivitySampleWalk();
+                    break;
+                case WithingsStructureType.ACTIVITY_SAMPLE_RUN:
+                    structure = new ActivitySampleRun();
+                    break;
+                case WithingsStructureType.ACTIVITY_SAMPLE_SWIM:
+                    structure = new ActivitySampleSwim();
+                    break;
+                case WithingsStructureType.ACTIVITY_HR:
+                    structure = new ActivityHeartrate();
+                    break;
+                case WithingsStructureType.PROBE_REPLY:
+                    structure = new ProbeReply();
+                    break;
+                case WithingsStructureType.CHALLENGE:
+                    structure = new Challenge();
+                    break;
+                case WithingsStructureType.CHALLENGE_RESPONSE:
+                    structure = new ChallengeResponse();
+                    break;
+                case WithingsStructureType.ACTIVITY_SAMPLE_UNKNOWN:
+                    structure = new ActivitySampleUnknown();
+                    break;
+                case WithingsStructureType.END_OF_TRANSMISSION:
+                    structure = new EndOfTransmission();
+                    break;
+                case WithingsStructureType.WORKOUT_TYPE:
+                    structure = new WorkoutType();
+                    break;
+                case WithingsStructureType.LIVE_WORKOUT_START:
+                    structure = new LiveWorkoutStart();
+                    break;
+                case WithingsStructureType.LIVE_WORKOUT_END:
+                    structure = new LiveWorkoutEnd();
+                    break;
+                case WithingsStructureType.LIVE_WORKOUT_PAUSE_STATE:
+                    structure = new LiveWorkoutPauseState();
+                    break;
+                case WithingsStructureType.WORKOUT_SCREEN_LIST:
+                    structure = new WorkoutScreenList();
+                    break;
+                case WithingsStructureType.IMAGE_META_DATA:
+                    structure = new ImageMetaData();
+                    break;
+                case WithingsStructureType.GLYPH_ID:
+                    structure = new GlyphId();
+                    break;
+                case WithingsStructureType.NOTIFICATION_APP_ID:
+                    structure = new GlyphId();
+                    break;
+                default:
+                    structure = null;
+                    logger.info("Received yet unknown structure type: " + structureTypeFromResponse);
+            }
+
+            if (structure != null) {
+                structure.fillFromRawData(removeHeaderBytes(rawDataStructure));
+                structures.add(structure);
+            }
+        }
+
+        return structures;
+    }
+
+    private List<byte[]> splitRawData(byte[] rawData) {
+        int remainingBytes = rawData.length;
+        List<byte[]> result = new ArrayList<>();
+
+        while(remainingBytes > 3) {
+            short structureLength = (short) BLETypeConversions.toInt16(rawData[3], rawData[2]);
+            remainingBytes -= (structureLength + HEADER_SIZE);
+            try {
+                result.add(Arrays.copyOfRange(rawData, 0, structureLength + HEADER_SIZE));
+                if (remainingBytes > 0) {
+                    rawData = Arrays.copyOfRange(rawData, structureLength + HEADER_SIZE, rawData.length);
+                }
+            } catch (Exception e) {
+                logger.warn("Splitting of data failed: " + GB.hexdump(rawData));
+            }
+        }
+
+        return result;
+    }
+
+    private byte[] removeHeaderBytes(byte[] data) {
+        return Arrays.copyOfRange(data, HEADER_SIZE, data.length);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/EndOfTransmission.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/EndOfTransmission.java
new file mode 100644
index 0000000..491696e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/EndOfTransmission.java
@@ -0,0 +1,44 @@
+/*  Copyright (C) 2023-2024 Frank Ertl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures;
+
+import java.nio.ByteBuffer;
+
+public class EndOfTransmission extends WithingsStructure {
+    @Override
+    public short getLength() {
+        return 4;
+    }
+
+    @Override
+    protected void fillinTypeSpecificData(ByteBuffer buffer) {
+
+    }
+
+    @Override
+    public byte[] getRawData() {
+        ByteBuffer rawDataBuffer = ByteBuffer.allocate(4);
+        rawDataBuffer.putShort(getType());
+        rawDataBuffer.putShort((short)0);
+        return rawDataBuffer.array();
+    }
+
+    @Override
+    public short getType() {
+        return WithingsStructureType.END_OF_TRANSMISSION;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/GetActivitySamples.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/GetActivitySamples.java
new file mode 100644
index 0000000..c7c7799
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/GetActivitySamples.java
@@ -0,0 +1,47 @@
+/*  Copyright (C) 2023-2024 Frank Ertl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures;
+
+import java.nio.ByteBuffer;
+
+public class GetActivitySamples extends WithingsStructure {
+
+    public long timestampFrom;
+
+    public short maxSampleCount;
+
+    public GetActivitySamples(long timestampFrom, short maxSampleCount) {
+        this.timestampFrom = timestampFrom;
+        this.maxSampleCount = maxSampleCount;
+    }
+
+    @Override
+    public short getLength() {
+        return 10;
+    }
+
+    @Override
+    protected void fillinTypeSpecificData(ByteBuffer buffer) {
+        buffer.putInt((int)(timestampFrom & 4294967295L));
+        buffer.putShort((short)maxSampleCount);
+    }
+
+    @Override
+    public short getType() {
+        return WithingsStructureType.GET_ACTIVITY_SAMPLES;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/GlyphId.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/GlyphId.java
new file mode 100644
index 0000000..25a20ac
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/GlyphId.java
@@ -0,0 +1,50 @@
+/*  Copyright (C) 2023-2024 Frank Ertl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+public class GlyphId extends WithingsStructure {
+
+    private long unicode;
+
+    public long getUnicode() {
+        return unicode;
+    }
+
+    @Override
+    public short getLength() {
+        return 8;
+    }
+
+    @Override
+    protected void fillFromRawDataAsBuffer(ByteBuffer rawDataBuffer) {
+        int value = rawDataBuffer.getInt();
+        unicode = ByteBuffer.allocate(4).putInt(value).order(ByteOrder.LITTLE_ENDIAN).getInt(0);
+    }
+
+    @Override
+    protected void fillinTypeSpecificData(ByteBuffer buffer) {
+
+    }
+
+    @Override
+    public short getType() {
+        return WithingsStructureType.GLYPH_ID;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/HeartRate.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/HeartRate.java
new file mode 100644
index 0000000..29c8e54
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/HeartRate.java
@@ -0,0 +1,47 @@
+/*  Copyright (C) 2023-2024 Frank Ertl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures;
+
+import java.nio.ByteBuffer;
+
+public class HeartRate extends WithingsStructure {
+
+    private int heartrate;
+
+    public int getHeartrate() {
+        return heartrate;
+    }
+
+    @Override
+    public short getLength() {
+        return 5;
+    }
+
+    @Override
+    public void fillFromRawDataAsBuffer(ByteBuffer rawDataBuffer) {
+        heartrate = rawDataBuffer.get(1) & 0xff;
+    }
+
+    @Override
+    protected void fillinTypeSpecificData(ByteBuffer buffer) {
+    }
+
+    @Override
+    public short getType() {
+        return WithingsStructureType.HR;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/ImageData.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/ImageData.java
new file mode 100644
index 0000000..b79bcd9
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/ImageData.java
@@ -0,0 +1,47 @@
+/*  Copyright (C) 2023-2024 Frank Ertl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures;
+
+import java.nio.ByteBuffer;
+
+public class ImageData extends WithingsStructure {
+
+    byte [] imageData;
+
+    public void setImageData(byte[] imageData) {
+        this.imageData = imageData;
+    }
+
+    @Override
+    public short getLength() {
+        return imageData != null ? (short)(imageData.length + 1 + HEADER_SIZE) : 1 + HEADER_SIZE;
+    }
+
+    @Override
+    protected void fillinTypeSpecificData(ByteBuffer buffer) {
+        if (imageData != null) {
+            addByteArrayWithLengthByte(buffer, imageData);
+        } else {
+            addByteArrayWithLengthByte(buffer, new byte[0]);
+        }
+    }
+
+    @Override
+    public short getType() {
+        return WithingsStructureType.IMAGE_DATA;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/ImageMetaData.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/ImageMetaData.java
new file mode 100644
index 0000000..e6d6f1c
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/ImageMetaData.java
@@ -0,0 +1,66 @@
+/*  Copyright (C) 2023-2024 Frank Ertl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures;
+
+import java.nio.ByteBuffer;
+
+public class ImageMetaData extends WithingsStructure {
+
+    private byte unknown = 0x00;
+    private byte width;
+    private byte height;
+
+    public byte getWidth() {
+        return width;
+    }
+
+    public void setWidth(byte width) {
+        this.width = width;
+    }
+
+    public byte getHeight() {
+        return height;
+    }
+
+    public void setHeight(byte height) {
+        this.height = height;
+    }
+
+    @Override
+    public short getLength() {
+        return 7;
+    }
+
+    @Override
+    protected void fillinTypeSpecificData(ByteBuffer buffer) {
+        buffer.put(unknown);
+        buffer.put(width);
+        buffer.put(height);
+    }
+
+    @Override
+    public void fillFromRawDataAsBuffer(ByteBuffer rawDataBuffer) {
+        unknown = rawDataBuffer.get();
+        width = rawDataBuffer.get();
+        height = rawDataBuffer.get();
+    }
+
+    @Override
+    public short getType() {
+        return WithingsStructureType.IMAGE_META_DATA;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/LiveHeartRate.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/LiveHeartRate.java
new file mode 100644
index 0000000..594fbf5
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/LiveHeartRate.java
@@ -0,0 +1,48 @@
+/*  Copyright (C) 2023-2024 Frank Ertl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures;
+
+import java.nio.ByteBuffer;
+
+public class LiveHeartRate extends WithingsStructure {
+
+    private int heartrate;
+
+    public int getHeartrate() {
+        return heartrate;
+    }
+
+    @Override
+    public short getLength() {
+        return 1;
+    }
+
+    @Override
+    protected void fillinTypeSpecificData(ByteBuffer buffer) {
+
+    }
+
+    @Override
+    public void fillFromRawDataAsBuffer(ByteBuffer rawDataBuffer) {
+        heartrate = rawDataBuffer.get() & 0xff;
+    }
+
+    @Override
+    public short getType() {
+        return WithingsStructureType.LIVE_HR;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/LiveWorkoutEnd.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/LiveWorkoutEnd.java
new file mode 100644
index 0000000..d4561eb
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/LiveWorkoutEnd.java
@@ -0,0 +1,50 @@
+/*  Copyright (C) 2023-2024 Frank Ertl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures;
+
+import java.nio.ByteBuffer;
+import java.util.Date;
+
+public class LiveWorkoutEnd extends WithingsStructure {
+
+    private Date endtime;
+
+    public Date getEndtime() {
+        return endtime;
+    }
+
+    @Override
+    public short getLength() {
+        return 8;
+    }
+
+    @Override
+    protected void fillinTypeSpecificData(ByteBuffer buffer) {
+
+    }
+
+    @Override
+    protected void fillFromRawDataAsBuffer(ByteBuffer rawDataBuffer) {
+        long timestampInSeconds = rawDataBuffer.getInt() & 4294967295L;
+        endtime = new Date(timestampInSeconds * 1000);
+    }
+
+    @Override
+    public short getType() {
+        return WithingsStructureType.LIVE_WORKOUT_END;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/LiveWorkoutPauseState.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/LiveWorkoutPauseState.java
new file mode 100644
index 0000000..2268425
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/LiveWorkoutPauseState.java
@@ -0,0 +1,66 @@
+/*  Copyright (C) 2023-2024 Frank Ertl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures;
+
+import java.nio.ByteBuffer;
+import java.util.Date;
+
+public class LiveWorkoutPauseState extends WithingsStructure {
+
+    private byte yetunknown;
+
+    // Is always the same as long as the actual pause continues:
+    private Date starttime;
+
+    // This is just a guess, but observation show that this is quite possible the meaning of this value that is send when the pause is over
+    private int lengthInSeconds;
+
+    public Date getStarttime() {
+        return starttime;
+    }
+
+    public int getLengthInSeconds() {
+        return lengthInSeconds;
+    }
+
+    @Override
+    public short getLength() {
+        return 13;
+    }
+
+    @Override
+    protected void fillinTypeSpecificData(ByteBuffer buffer) {
+
+    }
+
+    @Override
+    protected void fillFromRawDataAsBuffer(ByteBuffer rawDataBuffer) {
+        yetunknown = rawDataBuffer.get();
+
+        long timestampInSeconds = rawDataBuffer.getInt() & 4294967295L;
+        if (timestampInSeconds > 0) {
+            starttime = new Date(timestampInSeconds * 1000);
+        }
+
+        lengthInSeconds = rawDataBuffer.getInt();
+    }
+
+    @Override
+    public short getType() {
+        return WithingsStructureType.LIVE_WORKOUT_PAUSE_STATE;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/LiveWorkoutStart.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/LiveWorkoutStart.java
new file mode 100644
index 0000000..f7a91a7
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/LiveWorkoutStart.java
@@ -0,0 +1,50 @@
+/*  Copyright (C) 2023-2024 Frank Ertl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures;
+
+import java.nio.ByteBuffer;
+import java.util.Date;
+
+public class LiveWorkoutStart extends WithingsStructure {
+
+    private Date starttime;
+
+    public Date getStarttime() {
+        return starttime;
+    }
+
+    @Override
+    public short getLength() {
+        return 8;
+    }
+
+    @Override
+    protected void fillinTypeSpecificData(ByteBuffer buffer) {
+
+    }
+
+    @Override
+    protected void fillFromRawDataAsBuffer(ByteBuffer rawDataBuffer) {
+        long timestampInSeconds = rawDataBuffer.getInt() & 4294967295L;
+        starttime = new Date(timestampInSeconds * 1000);
+    }
+
+    @Override
+    public short getType() {
+        return WithingsStructureType.LIVE_WORKOUT_START;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/Locale.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/Locale.java
new file mode 100644
index 0000000..dc55634
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/Locale.java
@@ -0,0 +1,44 @@
+/*  Copyright (C) 2023-2024 Frank Ertl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures;
+
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+
+public class Locale extends WithingsStructure {
+
+    private String locale = "en";
+
+    public Locale(String locale) {
+        this.locale = locale;
+    }
+
+    @Override
+    public short getLength() {
+        return (short) ((locale != null ? locale.getBytes().length : 0) + 1 + HEADER_SIZE);
+    }
+
+    @Override
+    protected void fillinTypeSpecificData(ByteBuffer rawDataBuffer) {
+        addStringAsBytesWithLengthByte(rawDataBuffer, locale);
+    }
+
+    @Override
+    public short getType() {
+        return WithingsStructureType.LOCALE;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/MoveHand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/MoveHand.java
new file mode 100644
index 0000000..06e3347
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/MoveHand.java
@@ -0,0 +1,49 @@
+/*  Copyright (C) 2023-2024 Frank Ertl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures;
+
+import java.nio.ByteBuffer;
+
+public class MoveHand extends WithingsStructure {
+
+    private short hand;
+    private short movement;
+
+    public void setHand(short hand) {
+        this.hand = hand;
+    }
+
+    public void setMovement(short movement) {
+        this.movement = movement;
+    }
+
+    @Override
+    public short getLength() {
+        return 7;
+    }
+
+    @Override
+    protected void fillinTypeSpecificData(ByteBuffer buffer) {
+        buffer.put((byte) (hand & 255));
+        buffer.putShort(movement);
+    }
+
+    @Override
+    public short getType() {
+        return WithingsStructureType.MOVE_HAND;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/Probe.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/Probe.java
new file mode 100644
index 0000000..62be1b5
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/Probe.java
@@ -0,0 +1,51 @@
+/*  Copyright (C) 2023-2024 Frank Ertl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures;
+
+import java.nio.ByteBuffer;
+
+public class Probe extends WithingsStructure {
+
+    private short os;
+
+    private short app;
+
+    private long version;
+
+    public Probe(short os, short app, long version) {
+        this.os = os;
+        this.app = app;
+        this.version = version;
+    }
+
+    @Override
+    public short getLength() {
+        return 10;
+    }
+
+    @Override
+    protected void fillinTypeSpecificData(ByteBuffer buffer) {
+        buffer.put((byte) (os & 255));
+        buffer.put((byte) (app & 255));
+        buffer.putInt((int) (version & 4294967295L));
+    }
+
+    @Override
+    public short getType() {
+        return WithingsStructureType.PROBE;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/ProbeOsVersion.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/ProbeOsVersion.java
new file mode 100644
index 0000000..3ff5cd1
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/ProbeOsVersion.java
@@ -0,0 +1,43 @@
+/*  Copyright (C) 2023-2024 Frank Ertl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures;
+
+import java.nio.ByteBuffer;
+
+public class ProbeOsVersion extends WithingsStructure {
+
+    private short osVersion;
+
+    public ProbeOsVersion(short osVersion) {
+        this.osVersion = osVersion;
+    }
+
+    @Override
+    public short getLength() {
+        return 6;
+    }
+
+    @Override
+    protected void fillinTypeSpecificData(ByteBuffer buffer) {
+        buffer.putShort(osVersion);
+    }
+
+    @Override
+    public short getType() {
+        return WithingsStructureType.PROBE_OS_VERSION;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/ProbeReply.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/ProbeReply.java
new file mode 100644
index 0000000..c694527
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/ProbeReply.java
@@ -0,0 +1,111 @@
+/*  Copyright (C) 2023-2024 Frank Ertl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.AuthenticationHandler;
+import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
+
+public class ProbeReply extends WithingsStructure {
+    private static final Logger logger = LoggerFactory.getLogger(ProbeReply.class);
+    private int yetUnknown1;
+    private String name;
+    private String mac;
+    private String secret;
+    private int yetUnknown2;
+    private String mId;
+    private int yetUnknown3;
+    private int firmwareVersion;
+    private int yetUnknown4;
+
+    public int getYetUnknown1() {
+        return yetUnknown1;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public String getMac() {
+        return mac;
+    }
+
+    public String getSecret() {
+        return secret;
+    }
+
+    public int getYetUnknown2() {
+        return yetUnknown2;
+    }
+
+    public String getmId() {
+        return mId;
+    }
+
+    public int getYetUnknown3() {
+        return yetUnknown3;
+    }
+
+    public int getFirmwareVersion() {
+        return firmwareVersion;
+    }
+
+    public int getYetUnknown4() {
+        return yetUnknown4;
+    }
+
+    @Override
+    public short getLength() {
+        int length = (name != null ? name.getBytes(StandardCharsets.UTF_8).length : 0) + 1;
+        length += (mac != null ? mac.getBytes().length : 0) + 1;
+        length += (secret != null ? secret.getBytes().length : 0) + 1;
+        length += (mId != null ? mId.getBytes().length : 0) + 1;
+        return (short) (length + 24);
+    }
+
+    @Override
+    protected void fillinTypeSpecificData(ByteBuffer buffer) {
+
+    }
+
+    @Override
+    public void fillFromRawDataAsBuffer(ByteBuffer rawDataBuffer) {
+        try {
+            yetUnknown1 = rawDataBuffer.getInt();
+            name = getNextString(rawDataBuffer);
+            mac = getNextString(rawDataBuffer);
+            secret = getNextString(rawDataBuffer);
+            yetUnknown2 = rawDataBuffer.getInt();
+            mId = getNextString(rawDataBuffer);
+            yetUnknown3 = rawDataBuffer.getInt();
+            firmwareVersion = rawDataBuffer.getInt();
+            yetUnknown4 = rawDataBuffer.getInt();
+        } catch (Exception e) {
+            logger.warn("Could not handle buffer with data " + StringUtils.bytesToHex(rawDataBuffer.array()));
+        }
+    }
+
+    @Override
+    public short getType() {
+        return WithingsStructureType.PROBE_REPLY;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/ScreenSettings.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/ScreenSettings.java
new file mode 100644
index 0000000..6300379
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/ScreenSettings.java
@@ -0,0 +1,67 @@
+/*  Copyright (C) 2023-2024 Frank Ertl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures;
+
+import java.nio.ByteBuffer;
+
+public class ScreenSettings extends WithingsStructure {
+
+    private int id;
+
+    // TODO change to an actual unique ID. Must then be changed in User too.
+    private int userId = 123456;
+    private int yetUnknown1 = 0;
+    private int yetUnknown2 = 0;
+    private byte idOnDevice;
+    private byte yetUnkown3 = 0x01;
+
+    public int getId() {
+        return id;
+    }
+
+    public void setId(int id) {
+        this.id = id;
+    }
+
+    public byte getIdOnDevice() {
+        return idOnDevice;
+    }
+
+    public void setIdOnDevice(byte idOnDevice) {
+        this.idOnDevice = idOnDevice;
+    }
+
+    @Override
+    public short getLength() {
+        return 22;
+    }
+
+    @Override
+    protected void fillinTypeSpecificData(ByteBuffer buffer) {
+        buffer.putInt(this.id);
+        buffer.putInt(this.userId);
+        buffer.putInt(this.yetUnknown1);
+        buffer.putInt(this.yetUnknown2);
+        buffer.put(this.idOnDevice);
+        buffer.put(this.yetUnkown3);
+    }
+
+    @Override
+    public short getType() {
+        return WithingsStructureType.SCREEN_SETTINGS;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/SourceAppId.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/SourceAppId.java
new file mode 100644
index 0000000..ce30e10
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/SourceAppId.java
@@ -0,0 +1,52 @@
+/*  Copyright (C) 2023-2024 Frank Ertl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures;
+
+import java.nio.ByteBuffer;
+
+public class SourceAppId extends WithingsStructure {
+
+    private String appId;
+
+    public String getAppId() {
+        return appId;
+    }
+
+    public void setAppId(String appId) {
+        this.appId = appId;
+    }
+
+    @Override
+    public short getLength() {
+        return (short) ((appId != null ? appId.getBytes().length : 0) + 5);
+    }
+
+    @Override
+    protected void fillFromRawDataAsBuffer(ByteBuffer rawDataBuffer) {
+        appId = getNextString(rawDataBuffer);
+    }
+
+    @Override
+    protected void fillinTypeSpecificData(ByteBuffer buffer) {
+        addStringAsBytesWithLengthByte(buffer, appId);
+    }
+
+    @Override
+    public short getType() {
+        return WithingsStructureType.NOTIFICATION_APP_ID;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/Status.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/Status.java
new file mode 100644
index 0000000..0b210a2
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/Status.java
@@ -0,0 +1,36 @@
+/*  Copyright (C) 2023-2024 Frank Ertl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures;
+
+import java.nio.ByteBuffer;
+
+public class Status extends WithingsStructure {
+    @Override
+    public short getLength() {
+        return 5;
+    }
+
+    @Override
+    protected void fillinTypeSpecificData(ByteBuffer buffer) {
+        buffer.put((byte) 0x01);
+    }
+
+    @Override
+    public short getType() {
+        return (short) 2420;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/Time.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/Time.java
new file mode 100644
index 0000000..5a42328
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/Time.java
@@ -0,0 +1,98 @@
+/*  Copyright (C) 2023-2024 Frank Ertl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures;
+
+import java.nio.ByteBuffer;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.zone.ZoneOffsetTransition;
+import java.time.zone.ZoneRules;
+import java.util.TimeZone;
+
+public class Time extends WithingsStructure {
+
+    private Instant now;
+    private int timeOffsetInSeconds;
+    private Instant nextDaylightSavingTransition;
+    private int nextDaylightSavingTransitionOffsetInSeconds;
+
+    public Time() {
+        now = Instant.now();
+        final TimeZone timezone = TimeZone.getDefault();
+        timeOffsetInSeconds = timezone.getOffset(now.toEpochMilli()) / 1000;
+        final ZoneId zoneId = ZoneId.systemDefault();
+        final ZoneRules zoneRules = zoneId.getRules();
+        final ZoneOffsetTransition nextTransition = zoneRules.nextTransition(Instant.now());
+        long nextTransitionTs = 0;
+        if (nextTransition != null) {
+            nextTransitionTs = nextTransition.getDateTimeBefore().atZone(zoneId).toEpochSecond();
+            nextDaylightSavingTransitionOffsetInSeconds = nextTransition.getOffsetAfter().getTotalSeconds();
+        }
+
+        nextDaylightSavingTransition = Instant.ofEpochSecond(nextTransitionTs);
+    }
+
+    public Instant getNow() {
+        return now;
+    }
+
+    public void setNow(Instant now) {
+        this.now = now;
+    }
+
+    public int getTimeOffsetInSeconds() {
+        return timeOffsetInSeconds;
+    }
+
+    public void setTimeOffsetInSeconds(int TimeOffsetInSeconds) {
+        this.timeOffsetInSeconds = TimeOffsetInSeconds;
+    }
+
+    public Instant getNextDaylightSavingTransition() {
+        return nextDaylightSavingTransition;
+    }
+
+    public void setNextDaylightSavingTransition(Instant nextDaylightSavingTransition) {
+        this.nextDaylightSavingTransition = nextDaylightSavingTransition;
+    }
+
+    public int getNextDaylightSavingTransitionOffsetInSeconds() {
+        return nextDaylightSavingTransitionOffsetInSeconds;
+    }
+
+    public void setNextDaylightSavingTransitionOffsetInSeconds(int nextDaylightSavingTransitionOffsetInSeconds) {
+        this.nextDaylightSavingTransitionOffsetInSeconds = nextDaylightSavingTransitionOffsetInSeconds;
+    }
+
+    @Override
+    public short getType() {
+        return WithingsStructureType.TIME;
+    }
+
+    @Override
+    public short getLength() {
+        return 20;
+    }
+
+    @Override
+    protected void fillinTypeSpecificData(ByteBuffer rawDataBuffer) {
+        rawDataBuffer.putInt((int)now.getEpochSecond());
+        rawDataBuffer.putInt(timeOffsetInSeconds);
+        rawDataBuffer.putInt((int)nextDaylightSavingTransition.getEpochSecond());
+        rawDataBuffer.putInt(nextDaylightSavingTransitionOffsetInSeconds);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/TypeVersion.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/TypeVersion.java
new file mode 100644
index 0000000..3e12144
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/TypeVersion.java
@@ -0,0 +1,39 @@
+/*  Copyright (C) 2023-2024 Frank Ertl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures;
+
+import java.nio.ByteBuffer;
+
+public class TypeVersion extends WithingsStructure {
+
+    private byte version = 0x01;
+
+    @Override
+    public short getLength() {
+        return 5;
+    }
+
+    @Override
+    protected void fillinTypeSpecificData(ByteBuffer buffer) {
+        buffer.put(version);
+    }
+
+    @Override
+    public short getType() {
+        return 2401;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/User.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/User.java
new file mode 100644
index 0000000..e33035a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/User.java
@@ -0,0 +1,102 @@
+/*  Copyright (C) 2023-2024 Frank Ertl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures;
+
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.Date;
+
+public class User extends WithingsStructure {
+
+    // This is just a dummy value as this seems to be the withings account id,
+    // which we do not need, but the watch expects:
+    private int userID = 123456;
+    private int weight;
+    private int height;
+    //Seems to be 0x00 for male and 0x01 for female. Found no other in my tests.
+    private byte gender;
+    private Date birthdate;
+    private String name;
+
+    public int getUserID() {
+        return userID;
+    }
+
+    public void setUserID(int userID) {
+        this.userID = userID;
+    }
+
+    public int getWeight() {
+        return weight;
+    }
+
+    public void setWeight(int weight) {
+        this.weight = weight;
+    }
+
+    public int getHeight() {
+        return height;
+    }
+
+    public void setHeight(int height) {
+        this.height = height;
+    }
+
+    public byte getGender() {
+        return gender;
+    }
+
+    public void setGender(byte gender) {
+        this.gender = gender;
+    }
+
+    public Date getBirthdate() {
+        return birthdate;
+    }
+
+    public void setBirthdate(Date birthdate) {
+        this.birthdate = birthdate;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    @Override
+    public short getLength() {
+        return (short) ((name != null ? name.getBytes(StandardCharsets.UTF_8).length : 0) + 22);
+    }
+
+    @Override
+    protected void fillinTypeSpecificData(ByteBuffer rawDataBuffer) {
+        rawDataBuffer.putInt(userID);
+        rawDataBuffer.putInt(weight);
+        rawDataBuffer.putInt(height);
+        rawDataBuffer.put(gender);
+        rawDataBuffer.putInt((int)(birthdate.getTime()/1000));
+        addStringAsBytesWithLengthByte(rawDataBuffer, name);
+    }
+
+    @Override
+    public short getType() {
+        return WithingsStructureType.USER;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/UserSecret.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/UserSecret.java
new file mode 100644
index 0000000..98785e8
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/UserSecret.java
@@ -0,0 +1,39 @@
+/*  Copyright (C) 2023-2024 Frank Ertl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures;
+
+import java.nio.ByteBuffer;
+
+public class UserSecret extends WithingsStructure {
+
+    private String secret = "2EM5zNP37QzM00hmP6BFTD92nG15XwNd";
+
+    @Override
+    public short getLength() {
+        return 37;
+    }
+
+    @Override
+    protected void fillinTypeSpecificData(ByteBuffer buffer) {
+        addStringAsBytesWithLengthByte(buffer, secret);
+    }
+
+    @Override
+    public short getType() {
+        return WithingsStructureType.USER_SECRET;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/UserUnit.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/UserUnit.java
new file mode 100644
index 0000000..f1de01c
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/UserUnit.java
@@ -0,0 +1,52 @@
+/*  Copyright (C) 2023-2024 Frank Ertl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures;
+
+import java.nio.ByteBuffer;
+
+public class UserUnit extends WithingsStructure {
+
+    private byte unknown1 = 0;
+    private byte unknown2 = 0;
+    private short type;
+    private short unit;
+
+    public UserUnit() {}
+
+    public UserUnit(short type, short unit) {
+        this.type = type;
+        this.unit = unit;
+    }
+
+    @Override
+    public short getLength() {
+        return 10;
+    }
+
+    @Override
+    protected void fillinTypeSpecificData(ByteBuffer buffer) {
+        buffer.put(unknown1);
+        buffer.put(unknown2);
+        buffer.putShort(type);
+        buffer.putShort(unit);
+    }
+
+    @Override
+    public short getType() {
+        return WithingsStructureType.USER_UNIT;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/UserUnitConstants.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/UserUnitConstants.java
new file mode 100644
index 0000000..68cc301
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/UserUnitConstants.java
@@ -0,0 +1,29 @@
+/*  Copyright (C) 2023-2024 Frank Ertl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures;
+
+public final class UserUnitConstants {
+
+    public static final short DISTANCE = 2;
+    public static final short CLOCK_MODE = 4;
+    public static final short UNIT_KM = 24;
+    public static final short UNIT_MILES = 25;
+    public static final short UNIT_24H = 26;
+    public static final short UNIT_12H = 27;
+
+    private UserUnitConstants() {}
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/WithingsStructure.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/WithingsStructure.java
new file mode 100644
index 0000000..97a46fc
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/WithingsStructure.java
@@ -0,0 +1,103 @@
+/*  Copyright (C) 2023-2024 Frank Ertl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures;
+
+import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.Message;
+
+/**
+ * This abstract class is the common denominator for all data structures used inside commands and the corresponding responses.
+ * @see Message
+ */
+public abstract class WithingsStructure {
+    protected final static short HEADER_SIZE = 4;
+
+    /**
+     * Some messages have some end bytes, some have not.
+     * Subclasses that need to have the eom appended need to overwrite this class and return true.
+     * The default value is false.
+     *
+     * @return true if some end of message should be appended
+     */
+    public boolean withEndOfMessage() {
+        return false;
+    }
+
+    public byte[] getRawData() {
+        short length = (getLength());
+        ByteBuffer rawDataBuffer = ByteBuffer.allocate(length);
+        rawDataBuffer.putShort(getType());
+        rawDataBuffer.putShort((short)(length - HEADER_SIZE));
+        fillinTypeSpecificData(rawDataBuffer);
+        return rawDataBuffer.array();
+    }
+
+    protected void addStringAsBytesWithLengthByte(ByteBuffer buffer, String str) {
+        if (str == null) {
+            buffer.put((byte)0);
+        } else {
+            byte[] stringAsBytes = str.getBytes(StandardCharsets.UTF_8);
+            buffer.put((byte)stringAsBytes.length);
+            buffer.put(stringAsBytes);
+        }
+    }
+
+    protected void fillFromRawData(byte[] rawData) {
+        fillFromRawDataAsBuffer(ByteBuffer.wrap(rawData));
+    };
+
+    protected void fillFromRawDataAsBuffer(ByteBuffer rawDataBuffer) {};
+
+    public abstract short getLength();
+
+    protected abstract void fillinTypeSpecificData(ByteBuffer buffer);
+    public abstract short getType();
+
+    protected String getNextString(ByteBuffer byteBuffer) {
+        // For strings in the raw data the first byte of the data is the length of the string:
+        int stringLength = (short)(byteBuffer.get() & 255);
+        byte[] stringBytes = new byte[stringLength];
+        byteBuffer.get(stringBytes);
+        return new String(stringBytes, Charset.forName("UTF-8"));
+    }
+
+    protected byte[] getNextByteArray(ByteBuffer byteBuffer) {
+        int arrayLength = (short)(byteBuffer.get() & 255);
+        byte[] nextByteArray = new byte[arrayLength];
+        byteBuffer.get(nextByteArray);
+        return nextByteArray;
+    }
+
+    protected int[] getNextIntArray(ByteBuffer byteBuffer) {
+        int arrayLength = (short)(byteBuffer.get() & 255);
+        int[] nextIntArray = new int[arrayLength];
+        for (int i = 0; i < arrayLength; i++) {
+            nextIntArray[i] = byteBuffer.getInt();
+        }
+        return nextIntArray;
+    }
+
+    protected void addByteArrayWithLengthByte(ByteBuffer buffer, byte[] data) {
+        buffer.put((byte) data.length);
+        if (data.length != 0) {
+            buffer.put(data);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/WithingsStructureType.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/WithingsStructureType.java
new file mode 100644
index 0000000..6ae75c9
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/WithingsStructureType.java
@@ -0,0 +1,72 @@
+/*  Copyright (C) 2023-2024 Frank Ertl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures;
+
+public class WithingsStructureType {
+
+    public static final short END_OF_TRANSMISSION = 256;
+    public static final short PROBE_REPLY = 257;
+    public static final short PROBE = 298;
+    public static final short CHALLENGE = 290;
+    public static final short CHALLENGE_RESPONSE = 291;
+    public static final short PROBE_OS_VERSION = 2344;
+    public static final short TIME = 1281;
+    public static final short SCREEN_SETTINGS = 1302;
+    public static final short WORKOUT_SCREEN_SETTINGS = 317;
+    public static final short BATTERY_STATUS = 1284;
+    public static final short USER = 1283;
+    public static final short USER_SECRET = 1299;
+    public static final short USER_UNIT = 281;
+    public static final short ACTIVITY_TARGET = 1297;
+    public static final short LOCALE = 289;
+    public static final short LIVE_HR = 2369;
+    public static final short HR = 2343;
+    public static final short ACTIVITY_HR = 2345;
+    public static final short ALARM = 1298;
+    public static final short ALARM_STATUS = 2329;
+    public static final short ALARM_NAME = 1300;
+    public static final short STEPS = 2390;
+    public static final short IMAGE_META_DATA = 2397;
+    public static final short IMAGE_DATA = 2398;
+    public static final short ANCS_STATUS = 2346;
+    public static final short NOTIFICATION_APP_ID = 2404;
+    public static final short GLYPH_ID = 2396;
+    public static final short MOVE_HAND = 1292;
+
+    public static final short GET_ACTIVITY_SAMPLES = 1286;
+    public static final short ACTIVITY_SAMPLE_TIME = 1537;
+    public static final short ACTIVITY_SAMPLE_DURATION = 1538;
+    public static final short ACTIVITY_SAMPLE_MOVEMENT = 1539;
+    public static final short ACTIVITY_SAMPLE_WALK = 1540;
+    public static final short ACTIVITY_SAMPLE_RUN = 1541;
+    public static final short ACTIVITY_SAMPLE_SWIM = 1549;
+    public static final short ACTIVITY_SAMPLE_SLEEP = 1543;
+    // There are two structure types containing information about calories:
+    public static final short ACTIVITY_SAMPLE_CALORIES = 1544;
+    public static final short ACTIVITY_SAMPLE_CALORIES_2 = 1546;
+    // No idea what this is, however it is in the response to requesting activities:
+    public static final short ACTIVITY_SAMPLE_UNKNOWN = 1547;
+    public static final short WORKOUT_TYPE = 2409;
+    public static final short LIVE_WORKOUT_START = 2418;
+    public static final short LIVE_WORKOUT_END = 2419;
+    public static final short LIVE_WORKOUT_PAUSE_STATE = 2439;
+    public static final short WORKOUT_GPS_STATE = 321;
+    public static final short WORKOUT_SCREEN_LIST = 316;
+    public static final short WORKOUT_SCREEN_DATA = 317;
+
+    private WithingsStructureType() {}
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/WorkoutGpsState.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/WorkoutGpsState.java
new file mode 100644
index 0000000..5ef6571
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/WorkoutGpsState.java
@@ -0,0 +1,43 @@
+/*  Copyright (C) 2023-2024 Frank Ertl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures;
+
+import java.nio.ByteBuffer;
+
+public class WorkoutGpsState extends WithingsStructure {
+
+    private final boolean gpsEnabled;
+
+    public WorkoutGpsState(boolean gpsEnabled) {
+        this.gpsEnabled = gpsEnabled;
+    }
+
+    @Override
+    public short getLength() {
+        return 6;
+    }
+
+    @Override
+    protected void fillinTypeSpecificData(ByteBuffer buffer) {
+        buffer.putShort(gpsEnabled? (short)1 : 0);
+    }
+
+    @Override
+    public short getType() {
+        return WithingsStructureType.WORKOUT_GPS_STATE;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/WorkoutScreen.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/WorkoutScreen.java
new file mode 100644
index 0000000..3634146
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/WorkoutScreen.java
@@ -0,0 +1,67 @@
+/*  Copyright (C) 2023-2024 Frank Ertl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures;
+
+import java.nio.ByteBuffer;
+
+public class WorkoutScreen extends WithingsStructure {
+
+    public static final byte MODE_PACE = 2;
+    public static final byte MODE_SPEED = 3;
+    public static final byte MODE_ELSE = 1;
+
+    public int id;
+
+    public byte yetunknown1 = 0;
+
+    public String name;
+
+    public byte mode = MODE_PACE;
+
+    public short yetunknown2 = 0;
+
+    public void setId(int id) {
+        this.id = id;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public void setMode(byte mode) {
+        this.mode = mode;
+    }
+
+    @Override
+    public short getType() {
+        return WithingsStructureType.WORKOUT_SCREEN_SETTINGS;
+    }
+
+    @Override
+    public short getLength() {
+        return (short) ((name != null ? name.getBytes().length : 0) + 9 + HEADER_SIZE);
+    }
+
+    @Override
+    protected void fillinTypeSpecificData(ByteBuffer rawDataBuffer) {
+        rawDataBuffer.putInt(id);
+        rawDataBuffer.put(yetunknown1);
+        addStringAsBytesWithLengthByte(rawDataBuffer, name);
+        rawDataBuffer.put(mode);
+        rawDataBuffer.putShort(yetunknown2);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/WorkoutScreenData.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/WorkoutScreenData.java
new file mode 100644
index 0000000..255beee
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/WorkoutScreenData.java
@@ -0,0 +1,52 @@
+/*  Copyright (C) 2023-2024 Frank Ertl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures;
+
+import java.nio.ByteBuffer;
+
+public class WorkoutScreenData extends WithingsStructure {
+
+    public long id;
+    public short version;
+    public String name;
+    public short faceMode;
+    public int flag;
+
+    @Override
+    public short getLength() {
+        return (short) ((name != null ? name.getBytes().length : 0) + 13);
+    }
+
+    @Override
+    protected void fillinTypeSpecificData(ByteBuffer buffer) {
+
+    }
+
+    @Override
+    protected void fillFromRawDataAsBuffer(ByteBuffer rawDataBuffer) {
+        this.id = rawDataBuffer.getInt() & 4294967295L;
+        this.version = (short) (rawDataBuffer.get() & 255);
+        this.name = getNextString(rawDataBuffer);
+        this.faceMode = (short) (rawDataBuffer.get() & 255);
+        this.flag = rawDataBuffer.getShort() & 65535;
+    }
+
+    @Override
+    public short getType() {
+        return WithingsStructureType.WORKOUT_SCREEN_DATA;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/WorkoutScreenList.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/WorkoutScreenList.java
new file mode 100644
index 0000000..cc83d07
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/WorkoutScreenList.java
@@ -0,0 +1,48 @@
+/*  Copyright (C) 2023-2024 Frank Ertl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures;
+
+import java.nio.ByteBuffer;
+
+public class WorkoutScreenList extends WithingsStructure {
+
+    private int[] workoutIds;
+
+    public int[] getWorkoutIds() {
+        return workoutIds;
+    }
+
+    @Override
+    public short getLength() {
+        return 37;
+    }
+
+    @Override
+    protected void fillinTypeSpecificData(ByteBuffer buffer) {
+
+    }
+
+    @Override
+    protected void fillFromRawDataAsBuffer(ByteBuffer rawDataBuffer) {
+        workoutIds = getNextIntArray(rawDataBuffer);
+    }
+
+    @Override
+    public short getType() {
+        return WithingsStructureType.WORKOUT_SCREEN_LIST;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/WorkoutType.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/WorkoutType.java
new file mode 100644
index 0000000..8c83aa1
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/WorkoutType.java
@@ -0,0 +1,50 @@
+/*  Copyright (C) 2023-2024 Frank Ertl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures;
+
+import java.nio.ByteBuffer;
+
+public class WorkoutType extends WithingsStructure {
+
+    public static short RUNNING = 0;
+
+    private short activityType;
+
+    public short getActivityType() {
+        return activityType;
+    }
+
+    @Override
+    public short getLength() {
+        return 6;
+    }
+
+    @Override
+    protected void fillinTypeSpecificData(ByteBuffer buffer) {
+
+    }
+
+    @Override
+    public void fillFromRawDataAsBuffer(ByteBuffer rawDataBuffer) {
+        activityType = rawDataBuffer.getShort();
+    }
+
+    @Override
+    public short getType() {
+        return WithingsStructureType.WORKOUT_TYPE;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/message/AbstractMessage.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/message/AbstractMessage.java
new file mode 100644
index 0000000..1445edd
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/message/AbstractMessage.java
@@ -0,0 +1,98 @@
+/*  Copyright (C) 2023-2024 Frank Ertl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.WithingsStructure;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public abstract class AbstractMessage implements Message {
+
+    /**
+     * The header consist of the first byte 0x01 (probably the message format identifier),
+     * two bytes for the message type and 2 bytes for the actual datalength.
+     */
+    private static final int HEADER_SIZE = 5;
+    protected final static short EOM_SIZE = 4;
+
+    private List<WithingsStructure> dataStructures = new ArrayList<WithingsStructure>();
+
+    public List<WithingsStructure> getDataStructures() {
+        return Collections.unmodifiableList(dataStructures);
+    }
+
+    @Override
+    public void addDataStructure(WithingsStructure data) {
+        dataStructures.add(data);
+    }
+
+    @Override
+    public byte[] getRawData() {
+        short structureLength = 0;
+        boolean setEndOfMessage = false;
+        for (WithingsStructure structure : dataStructures) {
+            if (structure.withEndOfMessage()) {
+                setEndOfMessage = true;
+            }
+            structureLength += (short)(structure.getLength());
+        }
+
+        if (setEndOfMessage) {
+            structureLength += EOM_SIZE;
+        }
+
+        ByteBuffer rawDataBuffer = ByteBuffer.allocate(HEADER_SIZE + structureLength);
+        rawDataBuffer.put((byte)0x01); // <= This seems to be always 0x01 for all commands
+        rawDataBuffer.putShort(getType());
+        rawDataBuffer.putShort(structureLength);
+
+        for (WithingsStructure structure : dataStructures) {
+            rawDataBuffer.put(structure.getRawData());
+        }
+
+        if (setEndOfMessage) {
+            addEndOfMessageBytes(rawDataBuffer);
+        }
+
+        return rawDataBuffer.array();
+    }
+
+    @Override
+    public <T extends WithingsStructure> T getStructureByType(Class<T> type) {
+        for (WithingsStructure structure : this.getDataStructures()) {
+            if (type.isInstance(structure)) {
+                return (T)structure;
+            }
+        }
+
+        return null;
+    }
+
+    private void addEndOfMessageBytes(ByteBuffer buffer) {
+        buffer.putShort((short)256);
+        buffer.putShort((short)0);
+    }
+
+    public String toString() {
+        return GB.hexdump(this.getRawData()).toLowerCase(Locale.ROOT);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/message/ExpectedResponse.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/message/ExpectedResponse.java
new file mode 100644
index 0000000..bbcfd7b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/message/ExpectedResponse.java
@@ -0,0 +1,23 @@
+/*  Copyright (C) 2023-2024 Frank Ertl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message;
+
+public enum ExpectedResponse {
+    NONE,
+    SIMPLE,
+    EOT
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/message/GlyphRequestHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/message/GlyphRequestHandler.java
new file mode 100644
index 0000000..125dc2c
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/message/GlyphRequestHandler.java
@@ -0,0 +1,92 @@
+/*  Copyright (C) 2023-2024 Frank Ertl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.widget.Toast;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.IconHelper;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.WithingsSteelHRDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.GlyphId;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.ImageData;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.ImageMetaData;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.incoming.IncomingMessageHandler;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class GlyphRequestHandler implements IncomingMessageHandler {
+    private static final Logger logger = LoggerFactory.getLogger(GlyphRequestHandler.class);
+    private final WithingsSteelHRDeviceSupport support;
+
+    public GlyphRequestHandler(WithingsSteelHRDeviceSupport support) {
+        this.support = support;
+    }
+
+    @Override
+    public void handleMessage(Message message) {
+        try {
+            GlyphId glyphId = message.getStructureByType(GlyphId.class);
+            ImageMetaData imageMetaData = message.getStructureByType(ImageMetaData.class);
+            Message reply = new WithingsMessage(WithingsMessageType.GET_UNICODE_GLYPH);
+            reply.addDataStructure(glyphId);
+            reply.addDataStructure(imageMetaData);
+            ImageData imageData = new ImageData();
+            imageData.setImageData(createUnicodeImage(glyphId.getUnicode(), imageMetaData));
+            reply.addDataStructure(imageData);
+            logger.info("Sending reply to glyph request: " + reply);
+            support.sendToDevice(reply);
+        } catch (Exception e) {
+            logger.error("Failed to respond to glyph request.", e);
+            GB.toast("Failed to respond to glyph request:" + e.getMessage(), Toast.LENGTH_LONG, GB.WARN);
+        }
+    }
+
+    private byte[] createUnicodeImage(long unicode, ImageMetaData metaData) {
+        String str = new String(Character.toChars((int)unicode));
+        Paint paint = new Paint();
+        paint.setTypeface(null);
+        Rect rect = new Rect();
+        paint.setTextSize(calculateTextsize(paint, metaData.getHeight()));
+        paint.setAntiAlias(true);
+        paint.getTextBounds(str, 0, str.length(), rect);
+        paint.setColor(-1);
+        Paint.FontMetricsInt fontMetricsInt = paint.getFontMetricsInt();
+        int width = rect.width();
+        if (width <= 0) {
+            return new byte[0];
+        }
+        Bitmap createBitmap = Bitmap.createBitmap(width, metaData.getHeight(), Bitmap.Config.ARGB_8888);
+        new Canvas(createBitmap).drawText(str, -rect.left, -fontMetricsInt.top, paint);
+        return IconHelper.toByteArray(createBitmap);
+    }
+
+    private int calculateTextsize(Paint paint, int height) {
+        Paint.FontMetricsInt fontMetricsInt;
+        int textsize = 0;
+        do {
+            textsize++;
+            paint.setTextSize(textsize);
+            fontMetricsInt = paint.getFontMetricsInt();
+        } while (fontMetricsInt.bottom - fontMetricsInt.top < height);
+        return textsize - 1;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/message/Message.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/message/Message.java
new file mode 100644
index 0000000..1e7b7d3
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/message/Message.java
@@ -0,0 +1,36 @@
+/*  Copyright (C) 2023-2024 Frank Ertl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.WithingsStructure;
+
+/**
+ * This interface is the common denominator for all messages passed to and from the Steel HR.
+ *
+ */
+public interface Message {
+    List<WithingsStructure> getDataStructures();
+    void addDataStructure(WithingsStructure data);
+    short getType();
+    byte[] getRawData();
+    boolean needsResponse();
+    boolean needsEOT();
+    boolean isIncomingMessage();
+    <T extends WithingsStructure> T getStructureByType(Class<T> type);
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/message/MessageBuilder.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/message/MessageBuilder.java
new file mode 100644
index 0000000..53ca9ab
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/message/MessageBuilder.java
@@ -0,0 +1,89 @@
+/*  Copyright (C) 2023-2024 Frank Ertl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.Arrays;
+
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.WithingsSteelHRDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
+
+public class MessageBuilder {
+
+    private static final Logger logger = LoggerFactory.getLogger(MessageBuilder.class);
+    private WithingsSteelHRDeviceSupport support;
+    private MessageFactory messageFactory;
+    private ByteArrayOutputStream pendingMessage;
+    private Message message;
+
+    public MessageBuilder(WithingsSteelHRDeviceSupport support, MessageFactory messageFactory) {
+        this.support = support;
+        this.messageFactory = messageFactory;
+    }
+
+    public synchronized boolean buildMessage(byte[] rawData) {
+        if (pendingMessage == null && rawData[0] == 0x01) {
+            pendingMessage = new ByteArrayOutputStream();
+        } else if (pendingMessage == null) {
+            return false;
+        }
+
+        try {
+            pendingMessage.write(rawData);
+        } catch(IOException e) {
+            logger.error("Could not write data to stream: " + StringUtils.bytesToHex(rawData));
+            return false;
+        }
+
+        if (!isMessageComplete(pendingMessage.toByteArray())) {
+            return false;
+        } else {
+            Message message = messageFactory.createMessageFromRawData(pendingMessage.toByteArray());
+            pendingMessage = null;
+            if (message == null) {
+                logger.info("Cannot handle null message");
+                return false;
+            }
+
+            this.message = message;
+            return true;
+        }
+    }
+
+    public Message getMessage() {
+        return message;
+    }
+
+    private boolean isMessageComplete(byte[] messageData) {
+        if (messageData.length < 5) {
+            return false;
+        }
+
+        short totalDataLength = (short) BLETypeConversions.toInt16(messageData[4], messageData[3]);
+        byte[] rawStructureData = Arrays.copyOfRange(messageData, 5, messageData.length);
+        if (rawStructureData.length == totalDataLength) {
+            return true;
+        }
+
+        return false;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/message/MessageFactory.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/message/MessageFactory.java
new file mode 100644
index 0000000..8032812
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/message/MessageFactory.java
@@ -0,0 +1,55 @@
+/*  Copyright (C) 2023-2024 Frank Ertl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Arrays;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.DataStructureFactory;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.WithingsStructure;
+
+
+public class MessageFactory {
+    private static final Logger logger = LoggerFactory.getLogger(MessageFactory.class);
+    private DataStructureFactory dataStructureFactory;
+
+    public MessageFactory(DataStructureFactory dataStructureFactory) {
+        this.dataStructureFactory = new DataStructureFactory();
+    }
+
+    public Message createMessageFromRawData(byte[] rawData) {
+        if (rawData.length < 5 || rawData[0] != 0x01) {
+            return null;
+        }
+
+        short messageTypeFromResponse = (short) (BLETypeConversions.toInt16(rawData[2], rawData[1]) & 16383);
+        short totalDataLength = (short) BLETypeConversions.toInt16(rawData[4], rawData[3]);
+        boolean isIncoming = rawData[1] == 65 || rawData[1] == -127;
+        Message message = new WithingsMessage(messageTypeFromResponse, isIncoming);
+        byte[] rawStructureData = Arrays.copyOfRange(rawData, 5, rawData.length);
+        List<WithingsStructure> structures = dataStructureFactory.createStructuresFromRawData(rawStructureData);
+        for (WithingsStructure structure : structures) {
+            message.addDataStructure(structure);
+        }
+
+        return message;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/message/SimpleHexToByteMessage.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/message/SimpleHexToByteMessage.java
new file mode 100644
index 0000000..b170d1a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/message/SimpleHexToByteMessage.java
@@ -0,0 +1,71 @@
+/*  Copyright (C) 2023-2024 Frank Ertl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message;
+
+import java.nio.ByteBuffer;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.WithingsStructure;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class SimpleHexToByteMessage implements Message {
+    private String hexString;
+
+    public SimpleHexToByteMessage(String hexString) {
+        this.hexString = hexString;
+    }
+
+    @Override
+    public List<WithingsStructure> getDataStructures() {
+        return null;
+    }
+
+    @Override
+    public void addDataStructure(WithingsStructure data) {
+
+    }
+
+    @Override
+    public short getType() {
+        return 0;
+    }
+
+    @Override
+    public byte[] getRawData() {
+        return GB.hexStringToByteArray(hexString);
+    }
+
+    @Override
+    public boolean needsResponse() {
+        return false;
+    }
+
+    @Override
+    public boolean needsEOT() {
+        return false;
+    }
+
+    @Override
+    public boolean isIncomingMessage() {
+        return false;
+    }
+
+    @Override
+    public <T extends WithingsStructure> T getStructureByType(Class<T> type) {
+        return null;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/message/WithingsMessage.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/message/WithingsMessage.java
new file mode 100644
index 0000000..d2789a5
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/message/WithingsMessage.java
@@ -0,0 +1,64 @@
+/*  Copyright (C) 2023-2024 Frank Ertl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.WithingsStructure;
+
+public class WithingsMessage extends AbstractMessage {
+    private short type;
+    private ExpectedResponse expectedResponse = ExpectedResponse.SIMPLE;
+    private boolean isIncoming;
+
+    public WithingsMessage(short type) {
+        this.type = type;
+    }
+
+    public WithingsMessage(short type, boolean incoming) {
+        this.type = type;
+        this.isIncoming = incoming;
+    }
+
+    public WithingsMessage(short type, ExpectedResponse expectedResponse) {
+        this.type = type;
+        this.expectedResponse = expectedResponse;
+    }
+
+    public WithingsMessage(short type, WithingsStructure dataStructure) {
+        this.type = type;
+        this.addDataStructure(dataStructure);
+    }
+
+    @Override
+    public boolean needsResponse() {
+        return expectedResponse == ExpectedResponse.SIMPLE;
+    }
+
+    @Override
+    public boolean needsEOT() {
+        return expectedResponse == ExpectedResponse.EOT;
+    }
+
+    @Override
+    public short getType() {
+        return type;
+    }
+
+    @Override
+    public boolean isIncomingMessage() {
+        return isIncoming;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/message/WithingsMessageType.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/message/WithingsMessageType.java
new file mode 100644
index 0000000..d45de5c
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/message/WithingsMessageType.java
@@ -0,0 +1,68 @@
+/*  Copyright (C) 2023-2024 Frank Ertl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message;
+
+/**
+ * Contains all identified commandtypes in the used TLV format of the messages exchanged
+ * between device and app.
+ */
+public final class WithingsMessageType {
+
+    public static final short PROBE = 257;
+    public static final short CHALLENGE = 296;
+    public static final short SET_TIME = 1281;
+    public static final short GET_BATTERY_STATUS = 1284;
+    public static final short SET_SCREEN_LIST = 1292;
+    public static final short INITIAL_CONNECT = 273;
+    public static final short START_HANDS_CALIBRATION = 286;
+    public static final short STOP_HANDS_CALIBRATION = 287;
+    public static final short MOVE_HAND = 284;
+    public static final short SET_ACTIVITY_TARGET = 1290;
+    public static final short SET_USER = 1282;
+    public static final short GET_USER = 1283;
+    public static final short SET_USER_UNIT = 274;
+    public static final short SET_LOCALE = 282;
+    public static final short SETUP_FINISHED = 275;
+    public static final short GET_HR = 2343;
+    public static final short GET_WORKOUT_SCREEN_LIST = 315;
+    public static final short SET_WORKOUT_SCREEN = 316;
+    public static final short START_LIVE_WORKOUT = 317;
+    public static final short STOP_LIVE_WORKOUT = 318;
+    public static final short SYNC = 321;
+    public static final short SYNC_RESPONSE = 16705;
+    public static final short SYNC_OK = 277;
+    public static final short GET_ALARM_SETTINGS = 298;
+    public static final short SET_ALARM = 325;
+    public static final short GET_ALARM = 293;
+    public static final short GET_ALARM_ENABLED = 2330;
+    public static final short SET_ALARM_ENABLED = 2331;
+    public static final short GET_ANCS_STATUS = 2353;
+    public static final short SET_ANCS_STATUS = 2345;
+    public static final short GET_SCREEN_SETTINGS = 1293;
+    // The next two do nearly the same, when I look at the responses, though only the first seems to deliver sleep samples
+    public static final short GET_ACTIVITY_SAMPLES = 2424;
+    public static final short GET_MOVEMENT_SAMPLES = 1286;
+
+    public static final short GET_SPORT_MODE = 2371;
+    public static final short GET_WORKOUT_GPS_STATUS = 323;
+    public static final short GET_HEARTRATE_SAMPLES = 2344;
+    public static final short LIVE_WORKOUT_DATA = 320;
+    public static final short GET_NOTIFICATION = 2404;
+    public static final short GET_UNICODE_GLYPH = 2403;
+
+    private WithingsMessageType() {}
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/message/incoming/IncomingMessageHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/message/incoming/IncomingMessageHandler.java
new file mode 100644
index 0000000..c7b9441
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/message/incoming/IncomingMessageHandler.java
@@ -0,0 +1,23 @@
+/*  Copyright (C) 2023-2024 Frank Ertl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.incoming;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.Message;
+
+public interface IncomingMessageHandler {
+    public void handleMessage(Message message);
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/message/incoming/IncomingMessageHandlerFactory.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/message/incoming/IncomingMessageHandlerFactory.java
new file mode 100644
index 0000000..e686721
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/message/incoming/IncomingMessageHandlerFactory.java
@@ -0,0 +1,86 @@
+/*  Copyright (C) 2023-2024 Frank Ertl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.incoming;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.WithingsSteelHRDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.GlyphRequestHandler;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.Message;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.WithingsMessageType;
+
+public class IncomingMessageHandlerFactory {
+
+    private static final Logger logger = LoggerFactory.getLogger(IncomingMessageHandlerFactory.class);
+    private static IncomingMessageHandlerFactory instance;
+    private final WithingsSteelHRDeviceSupport support;
+    private Map<Short, IncomingMessageHandler> handlers = new HashMap<>();
+
+    private IncomingMessageHandlerFactory(WithingsSteelHRDeviceSupport support) {
+        this.support = support;
+    }
+
+    public static IncomingMessageHandlerFactory getInstance(WithingsSteelHRDeviceSupport support) {
+        if (instance == null) {
+            instance = new IncomingMessageHandlerFactory(support);
+        }
+
+        return instance;
+    }
+
+    public IncomingMessageHandler getHandler(Message message) {
+        IncomingMessageHandler handler = handlers.get(message.getType());
+        switch (message.getType()) {
+            case WithingsMessageType.START_LIVE_WORKOUT:
+            case WithingsMessageType.STOP_LIVE_WORKOUT:
+            case WithingsMessageType.GET_WORKOUT_GPS_STATUS:
+                if (handler == null) {
+                    handlers.put(message.getType(), new LiveWorkoutHandler(support));
+                }
+                break;
+            case WithingsMessageType.LIVE_WORKOUT_DATA:
+                if (handler == null) {
+                    handlers.put(message.getType(), new LiveHeartrateHandler(support));
+                }
+                break;
+            case WithingsMessageType.GET_NOTIFICATION:
+                if (handler == null) {
+                    handlers.put(message.getType(), new NotificationRequestHandler(support));
+                }
+                break;
+            case WithingsMessageType.GET_UNICODE_GLYPH:
+                if (handler == null) {
+                    handlers.put(message.getType(), new GlyphRequestHandler(support));
+                }
+                break;
+            case WithingsMessageType.SYNC:
+                if (handler == null) {
+                    handlers.put(message.getType(), new SyncRequestHandler(support));
+                }
+                break;
+            default:
+                logger.warn("Unhandled incoming message type: " + message.getType());
+        }
+
+        return handlers.get(message.getType());
+    }
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/message/incoming/LiveHeartrateHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/message/incoming/LiveHeartrateHandler.java
new file mode 100644
index 0000000..f835d4c
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/message/incoming/LiveHeartrateHandler.java
@@ -0,0 +1,90 @@
+/*  Copyright (C) 2023-2024 Frank Ertl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.incoming;
+
+import android.content.Intent;
+
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.GregorianCalendar;
+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.withingssteelhr.WithingsSteelHRSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.WithingsSteelHRActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.WithingsSteelHRDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.activity.SleepActivitySampleHelper;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.LiveHeartRate;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.WithingsStructure;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.Message;
+
+public class LiveHeartrateHandler implements IncomingMessageHandler {
+    private static final Logger logger = LoggerFactory.getLogger(LiveHeartrateHandler.class);
+    private final WithingsSteelHRDeviceSupport support;
+
+    public LiveHeartrateHandler(WithingsSteelHRDeviceSupport support) {
+        this.support = support;
+    }
+
+
+    @Override
+    public void handleMessage(Message message) {
+        List<WithingsStructure> data = message.getDataStructures();
+        if (data == null || data.isEmpty()) {
+            return;
+        }
+
+        WithingsStructure structure = data.get(0);
+        int heartRate = 0;
+        if (structure instanceof LiveHeartRate) {
+            heartRate = ((LiveHeartRate)structure).getHeartrate();
+        }
+
+        if (heartRate > 0) {
+            saveHeartRateData(heartRate);
+        }
+    
+    }
+    
+    private void saveHeartRateData(int heartRate) {
+        WithingsSteelHRActivitySample sample = new WithingsSteelHRActivitySample();
+        sample.setTimestamp((int) (GregorianCalendar.getInstance().getTimeInMillis() / 1000L));
+        sample.setHeartRate(heartRate);
+        try (DBHandler dbHandler = GBApplication.acquireDB()) {
+            Long userId = DBHelper.getUser(dbHandler.getDaoSession()).getId();
+            Long deviceId = DBHelper.getDevice(support.getDevice(), dbHandler.getDaoSession()).getId();
+            WithingsSteelHRSampleProvider provider = new WithingsSteelHRSampleProvider(support.getDevice(), dbHandler.getDaoSession());
+            sample.setDeviceId(deviceId);
+            sample.setUserId(userId);
+            sample = SleepActivitySampleHelper.mergeIfNecessary(provider, sample);
+            provider.addGBActivitySample(sample);
+        } catch (Exception ex) {
+            logger.warn("Error saving current heart rate: " + ex.getLocalizedMessage());
+        }
+        Intent intent = new Intent(DeviceService.ACTION_REALTIME_SAMPLES)
+                .putExtra(GBDevice.EXTRA_DEVICE, support.getDevice())
+                .putExtra(DeviceService.EXTRA_REALTIME_SAMPLE, sample);
+        LocalBroadcastManager.getInstance(support.getContext()).sendBroadcast(intent);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/message/incoming/LiveWorkoutHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/message/incoming/LiveWorkoutHandler.java
new file mode 100644
index 0000000..16c0429
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/message/incoming/LiveWorkoutHandler.java
@@ -0,0 +1,147 @@
+/*  Copyright (C) 2023-2024 Frank Ertl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.incoming;
+
+import android.location.LocationManager;
+import android.widget.Toast;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+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.entities.BaseActivitySummary;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.entities.User;
+import nodomain.freeyourgadget.gadgetbridge.externalevents.opentracks.OpenTracksController;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.WithingsSteelHRDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.activity.WithingsActivityType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.LiveWorkoutEnd;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.LiveWorkoutPauseState;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.LiveWorkoutStart;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.WithingsStructure;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.WithingsStructureType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.WorkoutGpsState;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.WorkoutType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.Message;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.WithingsMessage;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.WithingsMessageType;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class LiveWorkoutHandler implements IncomingMessageHandler {
+    private static final Logger logger = LoggerFactory.getLogger(LiveWorkoutHandler.class);
+    private final WithingsSteelHRDeviceSupport support;
+    private BaseActivitySummary baseActivitySummary;
+
+    public LiveWorkoutHandler(WithingsSteelHRDeviceSupport support) {
+        this.support = support;
+    }
+
+    public void handleMessage(Message message) {
+        List<WithingsStructure> data = message.getDataStructures();
+        if (data != null) {
+            handleLiveData(data);
+        }
+    }
+
+    private void handleLiveData(List<WithingsStructure> dataList) {
+        for (WithingsStructure data : dataList) {
+            switch (data.getType()) {
+                case WithingsStructureType.LIVE_WORKOUT_START:
+                    handleStart((LiveWorkoutStart) data);
+                    break;
+                case WithingsStructureType.LIVE_WORKOUT_END:
+                    handleEnd((LiveWorkoutEnd) data);
+                    break;
+                case WithingsStructureType.LIVE_WORKOUT_PAUSE_STATE:
+                    handlePause((LiveWorkoutPauseState) data);
+                    break;
+                case WithingsStructureType.WORKOUT_TYPE:
+                    handleType((WorkoutType) data);
+                    break;
+                default:
+                    logger.info("Received yet unhandled live workout data of type '" + data.getType() + "' with data '" + GB.hexdump(data.getRawData()) + "'.");
+            }
+        }
+    }
+
+    private void handleStart(LiveWorkoutStart workoutStart) {
+        sendGpsState();
+        if (baseActivitySummary == null) {
+            baseActivitySummary = new BaseActivitySummary();
+        }
+
+        baseActivitySummary.setStartTime(workoutStart.getStarttime());
+    }
+
+    private void handlePause(LiveWorkoutPauseState workoutPause) {
+        // Not sure what to do with these events at the moment so we just log them.
+        if (workoutPause.getStarttime() == null) {
+            if (workoutPause.getLengthInSeconds() > 0) {
+                logger.info("Got workout pause end with duration: " + workoutPause.getLengthInSeconds());
+            } else {
+                logger.info("Currently no pause happened");
+            }
+        } else {
+            logger.info("Got workout pause started at: " + workoutPause.getStarttime());
+        }
+    }
+
+    private void handleEnd(LiveWorkoutEnd workoutEnd) {
+        OpenTracksController.stopRecording(support.getContext());
+        baseActivitySummary.setEndTime(workoutEnd.getEndtime());
+        saveBaseActivitySummary();
+        baseActivitySummary = null;
+    }
+
+    private void handleType(WorkoutType workoutType) {
+        WithingsActivityType withingsWorkoutType = WithingsActivityType.fromCode(workoutType.getActivityType());
+        OpenTracksController.startRecording(support.getContext(), withingsWorkoutType.toActivityKind());
+        if (baseActivitySummary == null) {
+            baseActivitySummary = new BaseActivitySummary();
+        }
+
+        baseActivitySummary.setActivityKind(withingsWorkoutType.toActivityKind().getCode());
+    }
+
+    private void sendGpsState() {
+        Message message = new WithingsMessage((short)(WithingsMessageType.START_LIVE_WORKOUT | 16384), new WorkoutGpsState(isGpsEnabled()));
+        support.sendToDevice(message);
+    }
+
+    private boolean isGpsEnabled() {
+        final LocationManager manager = (LocationManager) support.getContext().getSystemService(support.getContext().LOCATION_SERVICE );
+        return manager.isProviderEnabled(LocationManager.GPS_PROVIDER );
+    }
+
+    private void saveBaseActivitySummary() {
+        try (DBHandler dbHandler = GBApplication.acquireDB()) {
+            DaoSession session = dbHandler.getDaoSession();
+            Device device = DBHelper.getDevice(support.getDevice(), session);
+            User user = DBHelper.getUser(session);
+            baseActivitySummary.setDevice(device);
+            baseActivitySummary.setUser(user);
+            session.getBaseActivitySummaryDao().insertOrReplace(baseActivitySummary);
+        } catch (Exception ex) {
+            GB.toast(support.getContext(), "Error saving activity summary", Toast.LENGTH_LONG, GB.ERROR, ex);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/message/incoming/NotificationRequestHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/message/incoming/NotificationRequestHandler.java
new file mode 100644
index 0000000..f1564c4
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/message/incoming/NotificationRequestHandler.java
@@ -0,0 +1,101 @@
+/*  Copyright (C) 2023-2024 Frank Ertl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.incoming;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.graphics.drawable.Drawable;
+import android.widget.Toast;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.IconHelper;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.WithingsSteelHRDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.ImageData;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.ImageMetaData;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.SourceAppId;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.Message;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.WithingsMessage;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.WithingsMessageType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.notification.NotificationProvider;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class NotificationRequestHandler implements IncomingMessageHandler {
+    private static final Logger logger = LoggerFactory.getLogger(NotificationRequestHandler.class);
+
+    private final WithingsSteelHRDeviceSupport support;
+    private Map<String, byte[]> appIconCache = new HashMap<>();
+
+    public NotificationRequestHandler(WithingsSteelHRDeviceSupport support) {
+        this.support = support;
+    }
+
+    @Override
+    public void handleMessage(Message message) {
+        try {
+            SourceAppId appId = message.getStructureByType(SourceAppId.class);
+            ImageMetaData imageMetaData = message.getStructureByType(ImageMetaData.class);
+            Message reply = new WithingsMessage(WithingsMessageType.GET_NOTIFICATION);
+            reply.addDataStructure(appId);
+            reply.addDataStructure(imageMetaData);
+            ImageData imageData = new ImageData();
+            imageData.setImageData(getImageData(appId.getAppId()));
+            reply.addDataStructure(imageData);
+            logger.info("Sending reply to notification request: " + reply);
+            support.sendToDevice(reply);
+        } catch (Exception e) {
+            logger.error("Failed to respond to notification request.", e);
+            GB.toast("Failed to respond to notification request:" + e.getMessage(), Toast.LENGTH_LONG, GB.WARN);
+        }
+    }
+
+    private byte[] getImageData(String sourceAppId) {
+        byte[] imageData = appIconCache.get(sourceAppId);
+        if (imageData == null) {
+            NotificationSpec notificationSpec = NotificationProvider.getInstance(support).getNotificationSpecForSourceAppId(sourceAppId);
+            if (notificationSpec != null) {
+                int iconId = notificationSpec.iconId;
+                try {
+                    Drawable icon = null;
+                    if (notificationSpec.iconId != 0) {
+                        Context sourcePackageContext = support.getContext().createPackageContext(sourceAppId, 0);
+                        icon = sourcePackageContext.getResources().getDrawable(notificationSpec.iconId);
+                    }
+                    if (icon == null) {
+                        PackageManager pm = support.getContext().getPackageManager();
+                        icon = pm.getApplicationIcon(sourceAppId);
+                    }
+
+                    imageData = IconHelper.getIconBytesFromDrawable(icon);
+                    appIconCache.put(sourceAppId, imageData);
+                } catch (PackageManager.NameNotFoundException e) {
+                    logger.error("Error while updating notification icons", e);
+                    imageData = new byte[0];
+                }
+            } else {
+                imageData = new byte[0];
+            }
+        }
+
+        return imageData;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/message/incoming/SyncRequestHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/message/incoming/SyncRequestHandler.java
new file mode 100644
index 0000000..c3a85fe
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/message/incoming/SyncRequestHandler.java
@@ -0,0 +1,38 @@
+/*  Copyright (C) 2023-2024 Frank Ertl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.incoming;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.WithingsSteelHRDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.ExpectedResponse;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.Message;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.WithingsMessage;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.WithingsMessageType;
+
+public class SyncRequestHandler implements IncomingMessageHandler {
+
+    private final WithingsSteelHRDeviceSupport support;
+
+    public SyncRequestHandler(WithingsSteelHRDeviceSupport support) {
+        this.support = support;
+    }
+
+    @Override
+    public void handleMessage(Message message) {
+        support.sendToDevice(new WithingsMessage(WithingsMessageType.SYNC_RESPONSE));
+        support.doSync();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/notification/AncsConstants.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/notification/AncsConstants.java
new file mode 100644
index 0000000..bd58b32
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/notification/AncsConstants.java
@@ -0,0 +1,45 @@
+/*  Copyright (C) 2023-2024 Frank Ertl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.notification;
+
+public final class AncsConstants {
+
+    public static final byte EVENT_ID_NOTIFICATION_ADDED = 0;
+    public static final byte EVENT_ID_NOTIFICATION_MODIFIED = 1;
+    public static final byte EVENT_ID_NOTIFICATION_REMOVED = 2;
+
+    public static final byte EVENT_FLAGS_SILENT = (1 << 0);
+    public static final byte EVENT_FLAGS_IMPORTANT = (1 << 1);
+    public static final byte EVENT_FLAGS_PREEXISTING = (1 << 2);
+    public static final byte EVENT_FLAGS_POSITIVE_ACTION = (1 << 3);
+    public static final byte EVENT_FLAGS_NEGATIVE_ACTION = (1 << 4);
+
+    public static final byte CATEGORY_ID_OTHER = 0;
+    public static final byte CATEGORY_ID_INCOMING_CALL = 1;
+    public static final byte CATEGORY_ID_MISSED_CALL = 2;
+    public static final byte CATEGORY_ID_VOICEMAIL = 3;
+    public static final byte CATEGORY_ID_SOCIAL = 4;
+    public static final byte CATEGORY_ID_SCHEDULE = 5;
+    public static final byte CATEGORY_ID_EMAIL = 6;
+    public static final byte CATEGORY_ID_NEWS = 7;
+    public static final byte CATEGORY_ID_HEALTHANDFITNESS = 8;
+    public static final byte CATEGORY_ID_BUSINESSANDFINANCE = 9;
+    public static final byte CATEGORY_ID_LOCATION = 10;
+    public static final byte CATEGORY_ID_ENTERTAINMENT = 11;
+
+    private AncsConstants(){}
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/notification/GetNotificationAttributes.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/notification/GetNotificationAttributes.java
new file mode 100644
index 0000000..a877171
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/notification/GetNotificationAttributes.java
@@ -0,0 +1,76 @@
+/*  Copyright (C) 2023-2024 Frank Ertl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.notification;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Random;
+
+public class GetNotificationAttributes {
+    private byte commandID;
+    private int notificationUID;
+    private List<RequestedNotificationAttribute> attributes = new ArrayList<>();
+
+    public byte getCommandID() {
+        return commandID;
+    }
+
+    public void setCommandID(byte commandID) {
+        this.commandID = commandID;
+    }
+
+    public int getNotificationUID() {
+        return notificationUID;
+    }
+
+    public void setNotificationUID(int notificationUID) {
+        this.notificationUID = notificationUID;
+    }
+
+    public List<RequestedNotificationAttribute> getAttributes() {
+        return Collections.unmodifiableList(attributes);
+    }
+
+    public void addAttribute(RequestedNotificationAttribute attribute) {
+        attributes.add(attribute);
+    }
+
+    public void deserialize(byte[] rawData) {
+        ByteBuffer buffer = ByteBuffer.wrap(rawData);
+        commandID = buffer.get();
+        notificationUID = buffer.getInt();
+        while (buffer.hasRemaining()) {
+            RequestedNotificationAttribute requestedNotificationAttribute = new RequestedNotificationAttribute();
+            int length = 1;
+            if (buffer.remaining() >= 3) {
+                length = 3;
+            }
+
+            byte[] rawAttributeData = new byte[length];
+            buffer.get(rawAttributeData);
+            requestedNotificationAttribute.deserialize(rawAttributeData);
+            attributes.add(requestedNotificationAttribute);
+        }
+    }
+
+    public byte[] serialize() {
+        return new byte[0];
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/notification/GetNotificationAttributesResponse.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/notification/GetNotificationAttributesResponse.java
new file mode 100644
index 0000000..2c8a0ed
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/notification/GetNotificationAttributesResponse.java
@@ -0,0 +1,57 @@
+/*  Copyright (C) 2023-2024 Frank Ertl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.notification;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.List;
+
+public class GetNotificationAttributesResponse {
+    private byte commandID = 0;
+    private int notificationUID;
+    private List<NotificationAttribute> attributes = new ArrayList<>();
+
+    public GetNotificationAttributesResponse(int notificationUID) {
+        this.notificationUID = notificationUID;
+    }
+
+    public void addAttribute(NotificationAttribute attribute) {
+        attributes.add(attribute);
+    }
+
+    public byte[] serialize() {
+        ByteBuffer buffer = ByteBuffer.allocate(getLength());
+        buffer.put(commandID);
+        buffer.order(ByteOrder.LITTLE_ENDIAN);
+        buffer.putInt(notificationUID);
+        buffer.order(ByteOrder.BIG_ENDIAN);
+        for (NotificationAttribute attribute : attributes) {
+            buffer.put(attribute.serialize());
+        }
+        return buffer.array();
+    }
+
+    private int getLength() {
+        int length = 5;
+        for (NotificationAttribute attribute : attributes) {
+            length += attribute.getAttributeLength() + 3;
+        }
+
+        return length;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/notification/NotificationAttribute.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/notification/NotificationAttribute.java
new file mode 100644
index 0000000..25b0574
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/notification/NotificationAttribute.java
@@ -0,0 +1,88 @@
+/*  Copyright (C) 2023-2024 Frank Ertl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.notification;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.StandardCharsets;
+
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
+
+public class NotificationAttribute {
+    private byte attributeID;
+    private short attributeLength;
+    private short attributeMaxLength;
+    private String value;
+
+    public void setAttributeMaxLength(short attributeMaxLength) {
+        this.attributeMaxLength = attributeMaxLength;
+    }
+
+    public byte getAttributeID() {
+        return attributeID;
+    }
+
+    public void setAttributeID(byte attributeID) {
+        this.attributeID = attributeID;
+    }
+
+    public short getAttributeLength() {
+        short length = (short)(value != null? value.getBytes(StandardCharsets.UTF_8).length : 0);
+        if (attributeMaxLength > 0 && length >  attributeMaxLength) {
+            length = attributeMaxLength;
+        }
+
+        return length;
+    }
+
+    public String getValue() {
+        return value;
+    }
+
+    public void setValue(String value) {
+        this.value = value;
+    }
+
+    public byte[] serialize() {
+        attributeLength = getAttributeLength();
+        int length = attributeLength + 3;
+        ByteBuffer buffer = ByteBuffer.allocate(length);
+        buffer.put(attributeID);
+        buffer.order(ByteOrder.LITTLE_ENDIAN);
+        buffer.putShort(attributeLength);
+        if (value != null) {
+            buffer.order(ByteOrder.BIG_ENDIAN);
+            buffer.put(value.getBytes(StandardCharsets.UTF_8), 0, attributeLength);
+        }
+
+        return buffer.array();
+    }
+
+    public void deserialize(byte[] rawData) {
+        ByteBuffer buffer = ByteBuffer.wrap(rawData);
+        attributeID = buffer.get();
+        buffer.order(ByteOrder.LITTLE_ENDIAN);
+        attributeLength = buffer.getShort();
+        buffer.order(ByteOrder.BIG_ENDIAN);
+        if (attributeLength > 0) {
+            byte[] rawValue = new byte[attributeLength];
+            buffer.get(rawValue);
+            value = new String(rawValue, StandardCharsets.UTF_8);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/notification/NotificationProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/notification/NotificationProvider.java
new file mode 100644
index 0000000..e27a25f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/notification/NotificationProvider.java
@@ -0,0 +1,184 @@
+/*  Copyright (C) 2023-2024 Frank Ertl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.notification;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.NotificationType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.WithingsSteelHRDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class NotificationProvider {
+
+    private static final Logger logger = LoggerFactory.getLogger(NotificationProvider.class);
+    private final WithingsSteelHRDeviceSupport support;
+    private final Map<Integer, NotificationSpec> pendingNotifications = new HashMap<>();
+    private static NotificationProvider instance;
+
+    public static NotificationProvider getInstance(WithingsSteelHRDeviceSupport support) {
+        if (instance == null) {
+            instance = new NotificationProvider(support);
+        }
+
+        return instance;
+    }
+
+    private NotificationProvider(WithingsSteelHRDeviceSupport support) {
+        this.support = support;
+    }
+
+    public void notifyClient(NotificationSpec spec) {
+        NotificationSource notificationSource = new NotificationSource(spec.getId(),
+                                                                        AncsConstants.EVENT_ID_NOTIFICATION_ADDED,
+                                                                        AncsConstants.EVENT_FLAGS_IMPORTANT,
+                                                                        mapNotificationType(spec.type),
+                                                                        (byte)1);
+        pendingNotifications.put(notificationSource.getNotificationUID(), spec);
+        support.sendAncsNotificationSourceNotification(notificationSource);
+    }
+
+    public void handleNotificationAttributeRequest(GetNotificationAttributes request) {
+        logger.debug("Request has ID: " + request.getNotificationUID());
+        NotificationSpec spec = pendingNotifications.get(request.getNotificationUID());
+        if (spec == null) {
+            logger.info("No pending notification with notificationUID " + request.getNotificationUID());
+            NotificationSource notificationSource = new NotificationSource(request.getNotificationUID(),
+                                                                            AncsConstants.EVENT_ID_NOTIFICATION_REMOVED,
+                                                                            AncsConstants.EVENT_FLAGS_IMPORTANT,
+                                                                            (byte)0,
+                                                                            (byte)1);
+            support.sendAncsNotificationSourceNotification(notificationSource);
+            return;
+        }
+
+        GetNotificationAttributesResponse response = new GetNotificationAttributesResponse(request.getNotificationUID());
+        List<RequestedNotificationAttribute> requestedAttributes = request.getAttributes();
+        logger.debug(requestedAttributes.size() + " attributes requested.");
+
+        boolean complete = false;
+
+        for (RequestedNotificationAttribute requestedAttribute : requestedAttributes) {
+            NotificationAttribute attribute = new NotificationAttribute();
+            attribute.setAttributeID(requestedAttribute.getAttributeID());
+            attribute.setAttributeMaxLength(requestedAttribute.getAttributeMaxLength());
+            logger.debug("Handling attribute " + attribute.getAttributeID() + " with maxLength " + attribute.getAttributeLength());
+            String value = "";
+            if (requestedAttribute.getAttributeID() == 0) {
+                value = spec.sourceAppId;
+            }
+            if (requestedAttribute.getAttributeID() == 1) {
+                complete = true;
+                value = spec.sender != null? spec.sender : (spec.phoneNumber != null? spec.phoneNumber : (spec.sourceName != null? spec.sourceName : "Unknown"));
+            }
+            if (requestedAttribute.getAttributeID() == 2) {
+                complete = true;
+                value = spec.title != null? spec.title : (spec.subject != null? spec.subject : " ");
+            }
+            if (requestedAttribute.getAttributeID() == 3) {
+                complete = true;
+                value = (spec.body != null? spec.body : " ");
+            }
+
+            if (value != null) {
+                // Remove linefeed and carriage returns as the watch cannot display this:
+                value = value.replace("\n", " ");
+                value = value.replace("\r", " ");
+                if (requestedAttribute.getAttributeMaxLength() == 0 || requestedAttribute.getAttributeMaxLength() >= value.length()) {
+                    attribute.setValue(value);
+                } else {
+                    attribute.setValue(value.substring(0, requestedAttribute.getAttributeMaxLength()));
+                }
+            }
+
+            logger.debug("Sending attribute " + attribute.getAttributeID() + " with value " + attribute.getValue());
+            response.addAttribute(attribute);
+        }
+
+        support.sendAncsDataSourceNotification(response);
+
+        if (complete) {
+            pendingNotifications.remove(request.getNotificationUID());
+        }
+    }
+
+    public NotificationSpec getNotificationSpecForSourceAppId(String sourceAppId) {
+        for (NotificationSpec notificationSpec : pendingNotifications.values()) {
+            if (notificationSpec.sourceAppId != null && notificationSpec.sourceAppId.equalsIgnoreCase(sourceAppId)) {
+                return notificationSpec;
+            }
+        }
+
+        return null;
+    }
+
+    private byte mapNotificationType(NotificationType type) {
+        switch (type) {
+            case GENERIC_ALARM_CLOCK:
+            case BUSINESS_CALENDAR:
+            case GENERIC_CALENDAR:
+                return AncsConstants.CATEGORY_ID_SCHEDULE;
+            case GENERIC_EMAIL:
+            case YAHOO_MAIL:
+            case GOOGLE_INBOX:
+            case GMAIL:
+            case OUTLOOK:
+                return AncsConstants.CATEGORY_ID_EMAIL;
+            case GENERIC_NAVIGATION:
+                return AncsConstants.CATEGORY_ID_LOCATION;
+            case GENERIC_PHONE:
+                return AncsConstants.CATEGORY_ID_INCOMING_CALL;
+            case MAILBOX:
+                return AncsConstants.CATEGORY_ID_MISSED_CALL;
+            case LINE:
+            case RIOT:
+            case SIGNAL:
+            case WIRE:
+            case SKYPE:
+            case SLACK:
+            case SNAPCHAT:
+            case TELEGRAM:
+            case THREEMA:
+            case KONTALK:
+            case ANTOX:
+            case DISCORD:
+            case TRANSIT:
+            case TWITTER:
+            case VIBER:
+            case WECHAT:
+            case WHATSAPP:
+            case FACEBOOK:
+            case FACEBOOK_MESSENGER:
+            case LINKEDIN:
+            case HIPCHAT:
+            case INSTAGRAM:
+            case KAKAO_TALK:
+            case GENERIC_SMS:
+            case GOOGLE_MESSENGER:
+            case GOOGLE_HANGOUTS:
+                return AncsConstants.CATEGORY_ID_SOCIAL;
+            default:
+                return AncsConstants.CATEGORY_ID_OTHER;
+        }
+    }
+    
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/notification/NotificationSource.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/notification/NotificationSource.java
new file mode 100644
index 0000000..bcd31a3
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/notification/NotificationSource.java
@@ -0,0 +1,55 @@
+/*  Copyright (C) 2023-2024 Frank Ertl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.notification;
+
+import java.nio.ByteBuffer;
+import java.util.Locale;
+import java.util.Random;
+
+public class NotificationSource {
+    private byte eventID;
+    private byte eventFlags;
+    private byte categoryId;
+    private byte categoryCount;
+    private int notificationUID;
+
+    public NotificationSource(int notificationUID, byte eventID, byte eventFlags, byte categoryId, byte categoryCount) {
+        this.eventID = eventID;
+        this.eventFlags = eventFlags;
+        this.categoryId = categoryId;
+        this.categoryCount = categoryCount;
+        this.notificationUID = Integer.valueOf(new Random().nextInt());
+    }
+
+    public int getNotificationUID() {
+        return notificationUID;
+    }
+
+    void setNotificationUID(int notificationUID) {
+        this.notificationUID = notificationUID;
+    }
+
+    public byte[] serialize() {
+        ByteBuffer buffer = ByteBuffer.allocate(8);
+        buffer.put(eventID);
+        buffer.put(eventFlags);
+        buffer.put(categoryId);
+        buffer.put(categoryCount);
+        buffer.putInt(notificationUID);
+        return buffer.array();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/notification/RequestedNotificationAttribute.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/notification/RequestedNotificationAttribute.java
new file mode 100644
index 0000000..581db30
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/notification/RequestedNotificationAttribute.java
@@ -0,0 +1,58 @@
+/*  Copyright (C) 2023-2024 Frank Ertl
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.notification;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+public class RequestedNotificationAttribute {
+    private byte attributeID;
+    private short attributeMaxLength;
+
+    public byte getAttributeID() {
+        return attributeID;
+    }
+
+    public void setAttributeID(byte attributeID) {
+        this.attributeID = attributeID;
+    }
+
+    public short getAttributeMaxLength() {
+        return attributeMaxLength;
+    }
+
+    public void setAttributeMaxLength(short attributeMaxLength) {
+        this.attributeMaxLength = attributeMaxLength;
+    }
+
+    public byte[] serialize() {
+        ByteBuffer buffer = ByteBuffer.allocate(3);
+        buffer.order(ByteOrder.LITTLE_ENDIAN);
+        buffer.put(attributeID);
+        buffer.putShort(attributeMaxLength);
+        return buffer.array();
+    }
+
+    public void deserialize(byte[] rawData) {
+        ByteBuffer buffer = ByteBuffer.wrap(rawData);
+        buffer.order(ByteOrder.LITTLE_ENDIAN);
+        attributeID = buffer.get();
+        if (buffer.capacity() >= 3) {
+            attributeMaxLength = buffer.getShort();
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/AbstractXiaomiSppProtocol.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/AbstractXiaomiSppProtocol.java
new file mode 100644
index 0000000..80de02f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/AbstractXiaomiSppProtocol.java
@@ -0,0 +1,49 @@
+/*  Copyright (C) 2024 Yoran Vulker
+
+    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 <http://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.XiaomiChannelHandler.Channel;
+
+public abstract class AbstractXiaomiSppProtocol {
+
+    public static class ParseResult {
+        public enum Status {
+            Invalid,
+            Incomplete,
+            Complete,
+        };
+
+        final Status status;
+        int packetSize;
+
+        public ParseResult(final Status status) {
+            this.status = status;
+        }
+
+        public ParseResult(final Status status, final int packetSize) {
+            this(status);
+            this.packetSize = packetSize;
+        }
+    };
+
+    public abstract int findNextPacketOffset(final byte[] buffer);
+    public abstract ParseResult processPacket(final byte[] buffer);
+    public abstract byte[] encodePacket(Channel channel, byte[] chunk);
+    public boolean initializeSession() {
+        return true;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiAuthService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiAuthService.java
new file mode 100644
index 0000000..2f2a14b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiAuthService.java
@@ -0,0 +1,395 @@
+/*  Copyright (C) 2023-2024 Andreas Shimokawa, Jos茅 Rebelo, Yoran Vulker
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.Build;
+import android.widget.Toast;
+
+import androidx.annotation.Nullable;
+
+import com.google.protobuf.ByteString;
+
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.bouncycastle.shaded.crypto.CryptoException;
+import org.bouncycastle.shaded.crypto.engines.AESEngine;
+import org.bouncycastle.shaded.crypto.modes.CCMBlockCipher;
+import org.bouncycastle.shaded.crypto.params.AEADParameters;
+import org.bouncycastle.shaded.crypto.params.KeyParameter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.security.GeneralSecurityException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.util.Arrays;
+import java.util.Locale;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.Mac;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.proto.xiaomi.XiaomiProto;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.services.AbstractXiaomiService;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class XiaomiAuthService extends AbstractXiaomiService {
+    private static final Logger LOG = LoggerFactory.getLogger(XiaomiAuthService.class);
+
+
+    public static final int COMMAND_TYPE = 1;
+
+    public static final int CMD_SEND_USERID = 5;
+    public static final int CMD_NONCE = 26;
+    public static final int CMD_AUTH = 27;
+
+    private boolean encryptionInitialized = false;
+    private boolean checkDecryptionMac = true;
+
+    private final byte[] secretKey = new byte[16];
+    private final byte[] nonce = new byte[16];
+    private final byte[] encryptionKey = new byte[16];
+    private final byte[] decryptionKey = new byte[16];
+    private final byte[] encryptionNonce = new byte[4];
+    private final byte[] decryptionNonce = new byte[4];
+
+    public XiaomiAuthService(final XiaomiSupport support) {
+        super(support);
+    }
+
+    public boolean isEncryptionInitialized() {
+        return encryptionInitialized;
+    }
+
+    protected void startEncryptedHandshake() {
+        encryptionInitialized = false;
+
+        System.arraycopy(getSecretKey(getSupport().getDevice()), 0, secretKey, 0, 16);
+        new SecureRandom().nextBytes(nonce);
+
+        getSupport().sendCommand("auth step 1", buildNonceCommand(nonce));
+    }
+
+    protected void startClearTextHandshake() {
+        final XiaomiProto.Auth auth = XiaomiProto.Auth.newBuilder()
+                .setUserId(getUserId(getSupport().getDevice()))
+                .build();
+
+        final XiaomiProto.Command command = XiaomiProto.Command.newBuilder()
+                .setType(XiaomiAuthService.COMMAND_TYPE)
+                .setSubtype(XiaomiAuthService.CMD_SEND_USERID)
+                .setAuth(auth)
+                .build();
+
+        getSupport().sendCommand("auth step 1", command);
+    }
+
+    @Override
+    public void setContext(final Context context) {
+        super.setContext(context);
+        this.checkDecryptionMac = getCoordinator().checkDecryptionMac();
+    }
+
+    @Override
+    public void handleCommand(final XiaomiProto.Command cmd) {
+        if (cmd.getType() != COMMAND_TYPE) {
+            throw new IllegalArgumentException("Not an auth command");
+        }
+
+        switch (cmd.getSubtype()) {
+            case CMD_NONCE: {
+                LOG.debug("Got watch nonce");
+
+                // Watch nonce
+                final XiaomiProto.Command command = handleWatchNonce(cmd.getAuth().getWatchNonce());
+
+                if (command == null) {
+                    GB.toast(getSupport().getContext(), R.string.authentication_failed_check_key, Toast.LENGTH_LONG, GB.WARN);
+                    LOG.error("handleWatchNonce returned null, disconnecting");
+                    final GBDevice device = getSupport().getDevice();
+
+                    if (device != null) {
+                        GBApplication.deviceService(device).disconnect();
+                    }
+
+                    return;
+                }
+
+                getSupport().sendCommand("auth step 2", command);
+                break;
+            }
+
+            case CMD_AUTH:
+            case CMD_SEND_USERID: {
+                if (cmd.getSubtype() == CMD_AUTH || cmd.getAuth().getStatus() == 1) {
+                    encryptionInitialized = cmd.getSubtype() == CMD_AUTH;
+
+                    LOG.info("Authenticated, further communications are {}", encryptionInitialized ? "encrypted" : "in plaintext");
+
+                    getSupport().getDevice().setState(GBDevice.State.INITIALIZED);
+                    getSupport().getDevice().sendDeviceUpdateIntent(getSupport().getContext(), GBDevice.DeviceUpdateSubject.DEVICE_STATE);
+
+                    getSupport().onAuthSuccess();
+                } else {
+                    LOG.warn("Authentication failed, subtype={}, status={}", cmd.getSubtype(), cmd.getStatus());
+                    GB.toast(getSupport().getContext(), R.string.authentication_failed_check_key, Toast.LENGTH_LONG, GB.WARN);
+
+                    final GBDevice device = getSupport().getDevice();
+                    if (device != null) {
+                        GBApplication.deviceService(device).disconnect();
+                    }
+                }
+                break;
+            }
+            default:
+                LOG.warn("Unknown auth payload subtype {}", cmd.getSubtype());
+        }
+    }
+
+    public byte[] encrypt(final byte[] arr, final int i) {
+        final ByteBuffer packetNonce = ByteBuffer.allocate(12).order(ByteOrder.LITTLE_ENDIAN)
+                .put(encryptionNonce)
+                .putInt(0)
+                .putInt(i);
+
+        try {
+            return encrypt(encryptionKey, packetNonce.array(), arr);
+        } catch (final CryptoException e) {
+            throw new RuntimeException("failed to encrypt", e);
+        }
+    }
+
+    public byte[] decrypt(final byte[] arr) {
+        final ByteBuffer packetNonce = ByteBuffer.allocate(12).order(ByteOrder.LITTLE_ENDIAN);
+        packetNonce.put(decryptionNonce);
+        packetNonce.putInt(0);
+        packetNonce.putInt(0);
+
+        try {
+            return decrypt(decryptionKey, packetNonce.array(), arr, checkDecryptionMac);
+        } catch (final CryptoException e) {
+            throw new RuntimeException("failed to decrypt", e);
+        }
+    }
+
+    @Nullable
+    private XiaomiProto.Command handleWatchNonce(final XiaomiProto.WatchNonce watchNonce) {
+        final byte[] step2hmac = computeAuthStep3Hmac(secretKey, nonce, watchNonce.getNonce().toByteArray());
+
+        System.arraycopy(step2hmac, 0, decryptionKey, 0, 16);
+        System.arraycopy(step2hmac, 16, encryptionKey, 0, 16);
+        System.arraycopy(step2hmac, 32, decryptionNonce, 0, 4);
+        System.arraycopy(step2hmac, 36, encryptionNonce, 0, 4);
+
+        LOG.debug("decryptionKey: {}", GB.hexdump(decryptionKey));
+        LOG.debug("encryptionKey: {}", GB.hexdump(encryptionKey));
+        LOG.debug("decryptionNonce: {}", GB.hexdump(decryptionNonce));
+        LOG.debug("encryptionNonce: {}", GB.hexdump(encryptionNonce));
+
+        final byte[] decryptionConfirmation = hmacSHA256(decryptionKey, ArrayUtils.addAll(watchNonce.getNonce().toByteArray(), nonce));
+        if (!Arrays.equals(decryptionConfirmation, watchNonce.getHmac().toByteArray())) {
+            LOG.warn("Watch hmac mismatch");
+            return null;
+        }
+
+        final XiaomiProto.AuthDeviceInfo authDeviceInfo = XiaomiProto.AuthDeviceInfo.newBuilder()
+                .setUnknown1(0) // TODO ?
+                .setPhoneApiLevel(Build.VERSION.SDK_INT)
+                .setPhoneName(Build.MODEL)
+                .setUnknown3(224) // TODO ?
+                // TODO region should be actual device region?
+                .setRegion(Locale.getDefault().getLanguage().substring(0, 2).toUpperCase(Locale.ROOT))
+                .build();
+
+        final byte[] encryptedNonces = hmacSHA256(encryptionKey, ArrayUtils.addAll(nonce, watchNonce.getNonce().toByteArray()));
+        final byte[] encryptedDeviceInfo = encrypt(authDeviceInfo.toByteArray(), 0);
+        final XiaomiProto.AuthStep3 authStep3 = XiaomiProto.AuthStep3.newBuilder()
+                .setEncryptedNonces(ByteString.copyFrom(encryptedNonces))
+                .setEncryptedDeviceInfo(ByteString.copyFrom(encryptedDeviceInfo))
+                .build();
+
+        final XiaomiProto.Command.Builder cmd = XiaomiProto.Command.newBuilder();
+        cmd.setType(COMMAND_TYPE);
+        cmd.setSubtype(CMD_AUTH);
+
+        final XiaomiProto.Auth.Builder auth = XiaomiProto.Auth.newBuilder();
+        auth.setAuthStep3(authStep3);
+
+        return cmd.setAuth(auth.build()).build();
+    }
+
+    public static XiaomiProto.Command buildNonceCommand(final byte[] nonce) {
+        final XiaomiProto.PhoneNonce.Builder phoneNonce = XiaomiProto.PhoneNonce.newBuilder();
+        phoneNonce.setNonce(ByteString.copyFrom(nonce));
+
+        final XiaomiProto.Auth.Builder auth = XiaomiProto.Auth.newBuilder();
+        auth.setPhoneNonce(phoneNonce.build());
+
+        final XiaomiProto.Command.Builder command = XiaomiProto.Command.newBuilder();
+        command.setType(COMMAND_TYPE);
+        command.setSubtype(CMD_NONCE);
+        command.setAuth(auth.build());
+        return command.build();
+    }
+
+    public static byte[] computeAuthStep3Hmac(final byte[] secretKey,
+                                              final byte[] phoneNonce,
+                                              final byte[] watchNonce) {
+        final byte[] miwearAuthBytes = "miwear-auth".getBytes();
+
+        final Mac mac;
+        try {
+            mac = Mac.getInstance("HmacSHA256");
+            // Compute the actual key and re-initialize the mac
+            mac.init(new SecretKeySpec(ArrayUtils.addAll(phoneNonce, watchNonce), "HmacSHA256"));
+            final byte[] hmacKeyBytes = mac.doFinal(secretKey);
+            final SecretKeySpec key = new SecretKeySpec(hmacKeyBytes, "HmacSHA256");
+            mac.init(key);
+        } catch (final NoSuchAlgorithmException | InvalidKeyException e) {
+            throw new IllegalStateException("Failed to initialize hmac for auth step 2", e);
+        }
+
+        final byte[] output = new byte[64];
+        byte[] tmp = new byte[0];
+        byte b = 1;
+        int i = 0;
+        while (i < output.length) {
+            mac.update(tmp);
+            mac.update(miwearAuthBytes);
+            mac.update(b);
+            tmp = mac.doFinal();
+            for (int j = 0; j < tmp.length && i < output.length; j++, i++) {
+                output[i] = tmp[j];
+            }
+            b++;
+        }
+        return output;
+    }
+
+    protected static byte[] getSecretKey(final GBDevice device) {
+        final byte[] authKeyBytes = new byte[16];
+
+        final SharedPreferences sharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(device.getAddress());
+
+        final String authKey = sharedPrefs.getString("authkey", "").trim();
+        if (StringUtils.isNotBlank(authKey)) {
+            final byte[] srcBytes;
+            // Allow both with and without 0x, to avoid user mistakes
+            if (authKey.length() == 34 && authKey.startsWith("0x")) {
+                srcBytes = GB.hexStringToByteArray(authKey.trim().substring(2));
+            } else {
+                srcBytes = GB.hexStringToByteArray(authKey.trim());
+            }
+            System.arraycopy(srcBytes, 0, authKeyBytes, 0, Math.min(srcBytes.length, 16));
+        }
+
+        return authKeyBytes;
+    }
+
+    protected static String getUserId(final GBDevice device) {
+        final SharedPreferences sharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(device.getAddress());
+
+        final String authKey = sharedPrefs.getString("authkey", null);
+        if (StringUtils.isNotBlank(authKey)) {
+            return authKey;
+        }
+
+        return "0000000000";
+    }
+
+    protected static byte[] hmacSHA256(final byte[] key, final byte[] input) {
+        try {
+            final Mac mac = Mac.getInstance("HmacSHA256");
+            mac.init(new SecretKeySpec(key, "HmacSHA256"));
+            return mac.doFinal(input);
+        } catch (final Exception e) {
+            throw new RuntimeException("Failed to hmac", e);
+        }
+    }
+
+    public static byte[] encrypt(final byte[] key, final byte[] nonce, final byte[] payload) throws
+            CryptoException {
+        final CCMBlockCipher cipher = createBlockCipher(true, new SecretKeySpec(key, "AES"), 32, nonce);
+        final byte[] out = new byte[cipher.getOutputSize(payload.length)];
+        final int outBytes = cipher.processBytes(payload, 0, payload.length, out, 0);
+        cipher.doFinal(out, outBytes);
+        return out;
+    }
+
+    public static byte[] decrypt(final byte[] key,
+                                 final byte[] nonce,
+                                 final byte[] encryptedPayload,
+                                 final boolean checkMac) throws CryptoException {
+        final int macSizeBits = checkMac ? 32 : 0;
+        final int actualEncryptedLength = checkMac ? encryptedPayload.length : encryptedPayload.length - 4;
+        final CCMBlockCipher cipher = createBlockCipher(false, new SecretKeySpec(key, "AES"), macSizeBits, nonce);
+        final byte[] decrypted = new byte[cipher.getOutputSize(actualEncryptedLength)];
+        cipher.doFinal(decrypted, cipher.processBytes(encryptedPayload, 0, actualEncryptedLength, decrypted, 0));
+        return decrypted;
+    }
+
+    public static CCMBlockCipher createBlockCipher(final boolean forEncrypt,
+                                                   final SecretKey secretKey,
+                                                   final int macSizeBits,
+                                                   final byte[] nonce) {
+        final AESEngine aesFastEngine = new AESEngine();
+        aesFastEngine.init(forEncrypt, new KeyParameter(secretKey.getEncoded()));
+        final CCMBlockCipher blockCipher = new CCMBlockCipher(aesFastEngine);
+        blockCipher.init(forEncrypt, new AEADParameters(new KeyParameter(secretKey.getEncoded()), macSizeBits, nonce, null));
+        return blockCipher;
+    }
+
+    public byte[] encryptV2(final byte[] message) {
+        try {
+            // I wish I was kidding
+            return ctrCrypt(Cipher.ENCRYPT_MODE, encryptionKey, encryptionKey, message);
+        } catch (final GeneralSecurityException ex) {
+            throw new RuntimeException("failed to encrypt message", ex);
+        }
+    }
+
+    public byte[] decryptV2(final byte[] ciphertext) {
+        try {
+            // I wish I was kidding
+            return ctrCrypt(Cipher.DECRYPT_MODE, decryptionKey, decryptionKey, ciphertext);
+        } catch (final GeneralSecurityException ex) {
+            throw new RuntimeException("failed to decrypt message", ex);
+        }
+    }
+
+    public byte[] ctrCrypt(final int op, final byte[] key, final byte[] iv, final byte[] message) throws GeneralSecurityException {
+        final Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");
+        cipher.init(
+                op,
+                new SecretKeySpec(key, "AES"),
+                new IvParameterSpec(iv)
+        );
+        return cipher.doFinal(message);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiBitmapUtils.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiBitmapUtils.java
new file mode 100644
index 0000000..727d3f6
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiBitmapUtils.java
@@ -0,0 +1,362 @@
+/*  Copyright (C) 2024 Yoran Vulker
+
+    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 <http://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.util.ArrayUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class XiaomiBitmapUtils {
+    private static final Logger LOG = LoggerFactory.getLogger(XiaomiBitmapUtils.class);
+    private static final byte[] LVGL_RLE_HEADER = new byte[] {(byte) 0xe0, 0x21, (byte) 0xa5, 0x5a };
+
+    public static final int PIXEL_FORMAT_RGB_565_LE = 0;
+    public static final int PIXEL_FORMAT_RGB_565_BE = 1;
+    public static final int PIXEL_FORMAT_XRGB_8888_LE = 2;
+    public static final int PIXEL_FORMAT_ARGB_8888_LE = 3;
+    public static final int PIXEL_FORMAT_ARGB_8565_LE = 7;
+    public static final int PIXEL_FORMAT_ABGR_8565_LE = 8;
+
+    public static String getPixelFormatString(final int pixelFormat) {
+        switch (pixelFormat) {
+            case PIXEL_FORMAT_RGB_565_LE:
+                return "RGB_565_LE";
+            case PIXEL_FORMAT_RGB_565_BE:
+                return "RGB_565_BE";
+            case PIXEL_FORMAT_XRGB_8888_LE:
+                return "XRGB_8888_LE";
+            case PIXEL_FORMAT_ARGB_8888_LE:
+                return "ARGB_8888_LE";
+            case PIXEL_FORMAT_ARGB_8565_LE:
+                return "ARGB_8565_LE";
+            case PIXEL_FORMAT_ABGR_8565_LE:
+                return "ABGR_8565_LE";
+        }
+
+        return "UNKNOWN";
+    }
+
+    private static Bitmap fit(final Drawable drawable, final int width, final int height) {
+        final Rect originalBounds = drawable.copyBounds();
+        final Bitmap result = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+        final Canvas canvas = new Canvas(result);
+        drawable.setBounds(0, 0, width, height);
+        drawable.draw(canvas);
+        drawable.setBounds(originalBounds);
+        return result;
+    }
+
+    public static byte[] convertToRgb565(final Bitmap bitmap, final boolean littleEndian) {
+        final ByteBuffer buffer = ByteBuffer.allocate(bitmap.getWidth() * bitmap.getHeight() * 2);
+
+        if (littleEndian)
+            buffer.order(ByteOrder.LITTLE_ENDIAN);
+
+        for (int y = 0; y < bitmap.getHeight(); y++) {
+            for (int x = 0; x < bitmap.getWidth(); x++) {
+                final int pixel = bitmap.getPixel(x, y);
+                final int r = (pixel >> 19) & 0x1f,
+                        g = (pixel >> 10) & 0x3f,
+                        b = pixel & 0x1f;
+                buffer.putShort((short) ((r << 11) | (g << 5) | b));
+            }
+        }
+
+        return buffer.array();
+    }
+
+    public static byte[] convertToRgb565L(final Drawable drawable, final int boundsWidth, final int boundsHeight) {
+        final Bitmap bitmap = fit(drawable, boundsWidth, boundsHeight);
+        final byte[] rawBitmap = convertToRgb565(bitmap, true);
+        bitmap.recycle();
+        return rawBitmap;
+    }
+
+    public static byte[] convertToRgb565B(final Drawable drawable, final int boundsWidth, final int boundsHeight) {
+        final Bitmap bitmap = fit(drawable, boundsWidth, boundsHeight);
+        final byte[] rawBitmap = convertToRgb565(bitmap, true);
+        bitmap.recycle();
+        return rawBitmap;
+    }
+
+    public static byte[] convertToArgb8565(final Bitmap bitmap, final boolean swapChannels) {
+        final ByteBuffer buffer = ByteBuffer.allocate(bitmap.getWidth() * bitmap.getHeight() * 3).order(ByteOrder.LITTLE_ENDIAN);
+
+        for (int y = 0; y < bitmap.getHeight(); y++) {
+            for (int x = 0; x < bitmap.getWidth(); x++) {
+                final int pixel = bitmap.getPixel(x, y);
+                final int a = (pixel >> 24) & 0xff,
+                        r = (pixel >> 19) & 0x1f,
+                        g = (pixel >> 10) & 0x3f,
+                        b = (pixel >> 3) & 0x1f;
+                // emulate int24
+                buffer.putShort((short) (((swapChannels ? b : r) << 11) | (g << 5) | (swapChannels ? r : b)));
+                buffer.put((byte) a);
+            }
+        }
+
+        return buffer.array();
+    }
+
+    public static byte[] convertToArgb8565(final Drawable drawable, final int boundsWidth, final int boundsHeight) {
+        final Bitmap bitmap = fit(drawable, boundsWidth, boundsHeight);
+        final byte[] rawBitmap = convertToArgb8565(bitmap, false);
+        bitmap.recycle();
+        return rawBitmap;
+    }
+
+    public static byte[] convertToAbgr8565(final Drawable drawable, final int boundsWidth, final int boundsHeight) {
+        final Bitmap bitmap = fit(drawable, boundsWidth, boundsHeight);
+        final byte[] rawBitmap = convertToArgb8565(bitmap, true);
+        bitmap.recycle();
+        return rawBitmap;
+    }
+
+    public static byte[] convertToArgb8888(final Bitmap bitmap) {
+        final ByteBuffer buffer = ByteBuffer.allocate(bitmap.getWidth() * bitmap.getHeight() * 4).order(ByteOrder.LITTLE_ENDIAN);
+        for (int y = 0; y < bitmap.getHeight(); y++) {
+            for (int x = 0; x < bitmap.getWidth(); x++) {
+                buffer.putInt(bitmap.getPixel(x, y));
+            }
+        }
+        return buffer.array();
+    }
+
+    public static byte[] convertToArgb8888(final Drawable drawable, final int boundsWidth, final int boundsHeight) {
+        final Bitmap bitmap = fit(drawable, boundsWidth, boundsHeight);
+        final byte[] rawBitmap = convertToArgb8888(bitmap);
+        bitmap.recycle();
+        return rawBitmap;
+    }
+
+    public static byte[] convertToPixelFormat(final int pixelFormat, final Drawable drawable, final int boundsWidth, final int boundsHeight) {
+        switch (pixelFormat) {
+            case PIXEL_FORMAT_RGB_565_LE:
+                return convertToRgb565L(drawable, boundsWidth, boundsHeight);
+            case PIXEL_FORMAT_RGB_565_BE:
+                return convertToRgb565B(drawable, boundsWidth, boundsHeight);
+            case PIXEL_FORMAT_XRGB_8888_LE:
+            case PIXEL_FORMAT_ARGB_8888_LE:
+                return convertToArgb8888(drawable, boundsWidth, boundsHeight);
+            case PIXEL_FORMAT_ARGB_8565_LE:
+                return convertToArgb8565(drawable, boundsWidth, boundsHeight);
+            case PIXEL_FORMAT_ABGR_8565_LE:
+                return convertToAbgr8565(drawable, boundsWidth, boundsHeight);
+        }
+
+        LOG.error("Unknown pixel format {}", pixelFormat);
+        return null;
+    }
+
+    public static byte[] decompressLvglRleV1(final byte[] bitmapData) {
+        if (!ArrayUtils.equals(bitmapData, LVGL_RLE_HEADER, 0)) {
+            LOG.debug("Compressed data does not start with expected LVGL RLE header (found {})",
+                    GB.hexdump(bitmapData, 0, 4));
+            return null;
+        }
+
+        int chunkSize = bitmapData[4] & 0xf;
+        if (chunkSize == 0) {
+            chunkSize = 1;
+        }
+
+        final ByteBuffer bb = ByteBuffer.wrap(bitmapData).order(ByteOrder.LITTLE_ENDIAN);
+        bb.getInt(); // magic
+        final int decompressedSize = (bb.getInt() >> 4) & 0xfffffff;
+
+        final byte[] out = new byte[decompressedSize];
+        int outOff = 0;
+
+        while (bb.hasRemaining()) {
+            byte control = bb.get();
+            int n = control & 0x7f;
+
+            if (outOff + chunkSize * (n+1) > out.length) {
+                LOG.error("decompression overflow");
+                return null;
+            }
+
+            if ((control & 0x80) != 0) {
+                // copy next chunk n+1 times to out
+                if (bb.remaining() < chunkSize) {
+                    LOG.error("not enough data to decompress");
+                    return null;
+                }
+
+                final byte[] chunk = new byte[chunkSize];
+                bb.get(chunk);
+
+                for (int i = 0; i < n + 1; i++) {
+                    System.arraycopy(chunk, 0, out, outOff, chunk.length);
+                    outOff += chunk.length;
+                }
+            } else {
+                // copy next n+1 chunks to out
+                if (bb.remaining() < chunkSize * (n + 1)) {
+                    LOG.error("not enough data to decompress");
+                    return null;
+                }
+
+                final byte[] chunk = new byte[chunkSize * (n+1)];
+                bb.get(chunk);
+                System.arraycopy(chunk, 0, out, outOff, chunk.length);
+                outOff += chunk.length;
+            }
+        }
+
+        return out;
+    }
+
+    public static byte[] decompressLvglRleV2(final byte[] bitmapData) {
+        if (!ArrayUtils.equals(bitmapData, LVGL_RLE_HEADER, 0)) {
+            LOG.debug("Compressed data does not start with expected LVGL RLE header (found {})",
+                    GB.hexdump(bitmapData, 0, 4));
+            return null;
+        }
+
+        int chunkSize = bitmapData[4] & 0xf;
+        if (chunkSize == 0) {
+            chunkSize = 1;
+        }
+
+        LOG.debug("Chunk size: {}", chunkSize);
+        final ByteBuffer bb = ByteBuffer.wrap(bitmapData).order(ByteOrder.LITTLE_ENDIAN);
+        bb.getInt(); // magic
+        final int decompressedSize = (bb.getInt() >> 4) & 0xfffffff;
+        LOG.debug("Compressed size: {}, decompressed size: {}", bb.remaining(), decompressedSize);
+
+        final byte[] out = new byte[decompressedSize];
+        int outOff = 0;
+
+        while (bb.hasRemaining()) {
+            byte control = bb.get();
+            int n = control & 0x7f;
+
+            if (outOff + chunkSize * n > out.length) {
+                LOG.error("decompression overflow");
+                return null;
+            }
+
+            if ((control & 0x80) != 0) {
+                // copy next n+1 chunks to out
+                if (bb.remaining() < chunkSize * n) {
+                    LOG.error("not enough data to decompress");
+                    return null;
+                }
+
+                final byte[] chunk = new byte[chunkSize * n];
+                bb.get(chunk);
+                System.arraycopy(chunk, 0, out, outOff, chunk.length);
+                outOff += chunk.length;
+            } else {
+                // copy next chunk n+1 times to out
+                if (bb.remaining() < chunkSize) {
+                    LOG.error("not enough data to decompress");
+                    return null;
+                }
+
+                final byte[] chunk = new byte[chunkSize];
+                bb.get(chunk);
+
+                for (int i = 0; i < n; i++) {
+                    System.arraycopy(chunk, 0, out, outOff, chunk.length);
+                    outOff += chunk.length;
+                }
+            }
+        }
+
+        return out;
+    }
+
+    public static Bitmap decodeWatchfaceImage(final byte[] bitmapData, final int bitmapFormat, final boolean swapRedBlueChannel, final int width, final int height) {
+        final int expectedInputSize;
+        switch (bitmapFormat) {
+            case 0:
+                expectedInputSize = width * height * 4;
+                break;
+            case 1:
+            case 4:
+            case 7:
+                expectedInputSize = width * height * 2;
+                break;
+            case 16:
+                expectedInputSize = 256 * 4 + width * height;
+                break;
+            default:
+                LOG.warn("bitmap format {} unknown", bitmapFormat);
+                return null;
+        }
+
+        if (expectedInputSize > bitmapData.length) {
+            LOG.error("Not enough pixel data (expected {} bytes, got {})",
+                    expectedInputSize,
+                    bitmapData.length);
+            return null;
+        }
+
+        final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+        final ByteBuffer bb = ByteBuffer.wrap(bitmapData);
+        bb.order(bitmapFormat == 7 ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN);
+
+
+        int[] palette = new int[0];
+        if (bitmapFormat == 16) {
+            palette = new int[256];
+            for (int i = 0; i < palette.length; i++) {
+                palette[i] = bb.getInt();
+            }
+        }
+
+        for (int y = 0; y < height; y++) {
+            for (int x = 0; x < width; x++) {
+                switch (bitmapFormat) {
+                    case 0x00:
+                        bitmap.setPixel(x, y, bb.getInt());
+                        break;
+                    case 0x01:
+                    case 0x04:
+                    case 0x07:
+                        final int c565 = bb.getShort() & 0xffff;
+                        final int pixel = 0xff000000 |
+                                ((c565 & 0xf800) << 8) |
+                                ((c565 & 0x07e0) << 5) |
+                                ((c565 & 0x001f) << 3);
+                        bitmap.setPixel(x, y, pixel);
+                        break;
+                    case 0x10:
+                        final int paletteId = bb.get() & 0xff;
+                        bitmap.setPixel(x, y, palette[paletteId]);
+                        break;
+                }
+            }
+        }
+
+        return bitmap;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiBleSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiBleSupport.java
new file mode 100644
index 0000000..4ff03dc
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiBleSupport.java
@@ -0,0 +1,341 @@
+/*  Copyright (C) 2023 Jos茅 Rebelo, Yoran Vulker
+
+    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 <http://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.content.Context;
+import android.widget.Toast;
+
+import androidx.annotation.Nullable;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.proto.xiaomi.XiaomiProto;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEQueue;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.PlainAction;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetProgressAction;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class XiaomiBleSupport extends XiaomiConnectionSupport {
+    private static final Logger LOG = LoggerFactory.getLogger(XiaomiBleSupport.class);
+
+    private XiaomiCharacteristic characteristicCommandRead;
+    private XiaomiCharacteristic characteristicCommandWrite;
+    private XiaomiCharacteristic characteristicActivityData;
+    private XiaomiCharacteristic characteristicDataUpload;
+
+    private final XiaomiSupport mXiaomiSupport;
+
+    final AbstractBTLEDeviceSupport commsSupport = new AbstractBTLEDeviceSupport(LOG) {
+        @Override
+        public boolean useAutoConnect() {
+            return mXiaomiSupport.useAutoConnect();
+        }
+
+        @Override
+        protected Set<UUID> getSupportedServices() {
+            return XiaomiUuids.BLE_UUIDS.keySet();
+        }
+
+        @Override
+        public boolean getAutoReconnect() {
+            return mXiaomiSupport.getAutoReconnect();
+        }
+
+        @Override
+        protected TransactionBuilder initializeDevice(final TransactionBuilder builder) {
+            XiaomiUuids.XiaomiBleUuidSet uuidSet = null;
+            BluetoothGattCharacteristic btCharacteristicCommandRead = null;
+            BluetoothGattCharacteristic btCharacteristicCommandWrite = null;
+            BluetoothGattCharacteristic btCharacteristicActivityData = null;
+            BluetoothGattCharacteristic btCharacteristicDataUpload = null;
+
+            // Attempt to find a known xiaomi service
+            for (Map.Entry<UUID, XiaomiUuids.XiaomiBleUuidSet> xiaomiUuid : XiaomiUuids.BLE_UUIDS.entrySet()) {
+                final XiaomiUuids.XiaomiBleUuidSet currentUuidSet = xiaomiUuid.getValue();
+                UUID currentChar;
+
+                if ((currentChar = currentUuidSet.getCharacteristicCommandRead()) == null ||
+                        (btCharacteristicCommandRead = getCharacteristic(currentChar)) == null) {
+                    continue;
+                }
+
+                if ((currentChar = currentUuidSet.getCharacteristicCommandWrite()) == null ||
+                        (btCharacteristicCommandWrite = getCharacteristic(currentChar)) == null) {
+                    continue;
+                }
+
+                if ((currentChar = currentUuidSet.getCharacteristicActivityData()) == null ||
+                        (btCharacteristicActivityData = getCharacteristic(currentChar)) == null) {
+                    continue;
+                }
+
+                if ((currentChar = currentUuidSet.getCharacteristicDataUpload()) == null ||
+                        (btCharacteristicDataUpload= getCharacteristic(currentChar)) == null) {
+                    LOG.warn("btCharacteristicDataUpload characteristic is null");
+                    // this characteristic may not be supported by all models
+                }
+
+                LOG.debug("Found Xiaomi service: {}", xiaomiUuid.getKey());
+                uuidSet = xiaomiUuid.getValue();
+
+                break;
+            }
+
+            if (uuidSet == null) {
+                GB.toast(getContext(), "Failed to find known Xiaomi service", Toast.LENGTH_LONG, GB.ERROR);
+                LOG.warn("Failed to find known Xiaomi service");
+                builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.NOT_CONNECTED, getContext()));
+                return builder;
+            }
+
+            // FIXME unsetDynamicState unsets the fw version, which causes problems..
+            if (getDevice().getFirmwareVersion() == null) {
+                getDevice().setFirmwareVersion(mXiaomiSupport.getCachedFirmwareVersion() != null ?
+                        mXiaomiSupport.getCachedFirmwareVersion() :
+                        "N/A");
+            }
+
+            if (btCharacteristicCommandRead == null || btCharacteristicCommandWrite == null) {
+                LOG.warn("Characteristics are null, will attempt to reconnect");
+                builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.WAITING_FOR_RECONNECT, getContext()));
+                return builder;
+            }
+
+            // FIXME:
+            // Because the first handshake packet is sent before the actions in the builder are run,
+            // the maximum message size is not properly initialized if the device itself does not request
+            // the MTU to be upgraded. However, since we will upgrade the MTU ourselves to the highest
+            // possible (512) and the device will (likely) respond with something higher than 247,
+            // we will initialize the characteristics with that MTU.
+            final int expectedMtu = 247;
+            characteristicCommandRead = new XiaomiCharacteristic(XiaomiBleSupport.this, btCharacteristicCommandRead, mXiaomiSupport.getAuthService());
+            characteristicCommandRead.setEncrypted(uuidSet.isEncrypted());
+            characteristicCommandRead.setChannelHandler(mXiaomiSupport::handleCommandBytes);
+            characteristicCommandRead.setMtu(expectedMtu);
+            characteristicCommandWrite = new XiaomiCharacteristic(XiaomiBleSupport.this, btCharacteristicCommandWrite, mXiaomiSupport.getAuthService());
+            characteristicCommandWrite.setEncrypted(uuidSet.isEncrypted());
+            characteristicCommandWrite.setMtu(expectedMtu);
+            characteristicActivityData = new XiaomiCharacteristic(XiaomiBleSupport.this, btCharacteristicActivityData, mXiaomiSupport.getAuthService());
+            characteristicActivityData.setChannelHandler(mXiaomiSupport.getHealthService().getActivityFetcher()::addChunk);
+            characteristicActivityData.setEncrypted(uuidSet.isEncrypted());
+            characteristicActivityData.setMtu(expectedMtu);
+            characteristicDataUpload = new XiaomiCharacteristic(XiaomiBleSupport.this, btCharacteristicDataUpload, mXiaomiSupport.getAuthService());
+            characteristicDataUpload.setEncrypted(uuidSet.isEncrypted());
+            characteristicDataUpload.setIncrementNonce(false);
+            characteristicDataUpload.setMtu(expectedMtu);
+
+            // request highest possible MTU; device should response with the highest supported MTU anyway
+            builder.requestMtu(512);
+            builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext()));
+            builder.notify(btCharacteristicCommandWrite, true);
+            builder.notify(btCharacteristicCommandRead, true);
+            builder.notify(btCharacteristicActivityData, true);
+            builder.notify(btCharacteristicDataUpload, true);
+            builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.AUTHENTICATING, getContext()));
+
+            if (uuidSet.isEncrypted()) {
+                builder.add(new PlainAction() {
+                    @Override
+                    public boolean run(BluetoothGatt gatt) {
+                        mXiaomiSupport.getAuthService().startEncryptedHandshake();
+                        return true;
+                    }
+                });
+            } else {
+                builder.add(new PlainAction() {
+                    @Override
+                    public boolean run(BluetoothGatt gatt) {
+                        mXiaomiSupport.getAuthService().startClearTextHandshake();
+                        return true;
+                    }
+                });
+            }
+
+            return builder;
+        }
+
+        @Override
+        public boolean onCharacteristicChanged(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic) {
+            if (super.onCharacteristicChanged(gatt, characteristic)) {
+                return true;
+            }
+
+            final UUID characteristicUUID = characteristic.getUuid();
+            final byte[] value = characteristic.getValue();
+
+            if (characteristicCommandRead.getCharacteristicUUID().equals(characteristicUUID)) {
+                characteristicCommandRead.onCharacteristicChanged(value);
+                return true;
+            } else if (characteristicCommandWrite.getCharacteristicUUID().equals(characteristicUUID)) {
+                characteristicCommandWrite.onCharacteristicChanged(value);
+                return true;
+            } else if (characteristicActivityData.getCharacteristicUUID().equals(characteristicUUID)) {
+                characteristicActivityData.onCharacteristicChanged(value);
+                return true;
+            } else if (characteristicDataUpload.getCharacteristicUUID().equals(characteristicUUID)) {
+                characteristicDataUpload.onCharacteristicChanged(value);
+                return true;
+            }
+
+            LOG.warn("Unhandled characteristic changed: {} {}", characteristicUUID, GB.hexdump(value));
+            return false;
+        }
+
+        @Override
+        public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
+            super.onMtuChanged(gatt, mtu, status);
+
+            if (characteristicCommandRead != null)
+                characteristicCommandRead.setMtu(mtu);
+            if (characteristicCommandWrite != null)
+                characteristicCommandWrite.setMtu(mtu);
+            if (characteristicDataUpload != null)
+                characteristicDataUpload.setMtu(mtu);
+            if (characteristicActivityData != null)
+                characteristicActivityData.setMtu(mtu);
+        }
+
+        @Override
+        public void dispose() {
+            mXiaomiSupport.onDisconnect();
+            super.dispose();
+        }
+    };
+
+    public XiaomiBleSupport(final XiaomiSupport xiaomiSupport) {
+        this.mXiaomiSupport = xiaomiSupport;
+    }
+
+    public void onAuthSuccess() {
+        characteristicCommandRead.reset();
+        characteristicCommandWrite.reset();
+        characteristicActivityData.reset();
+        characteristicDataUpload.reset();
+    }
+
+    @Override
+    public void setContext(GBDevice device, BluetoothAdapter adapter, Context context) {
+        this.commsSupport.setContext(device, adapter, context);
+    }
+
+    public void sendCommand(final String taskName, final XiaomiProto.Command command) {
+        if (this.characteristicCommandWrite == null) {
+            // Can sometimes happen in race conditions when connecting + receiving calendar event or weather updates
+            LOG.warn("characteristicCommandWrite is null!");
+            return;
+        }
+
+        this.characteristicCommandWrite.write(taskName, command.toByteArray());
+    }
+
+    @Override
+    public void sendDataChunk(String taskName, byte[] chunk, @Nullable XiaomiCharacteristic.SendCallback callback) {
+        if (this.characteristicDataUpload == null) {
+            LOG.warn("characteristicDataUpload is null!");
+            return;
+        }
+
+        this.characteristicDataUpload.write(taskName, chunk, callback);
+    }
+
+    @Override
+    public void setAutoReconnect(boolean enabled) {
+        this.commsSupport.setAutoReconnect(enabled);
+    }
+
+    /**
+     * Realistically, this function should only be used during auth, as we must schedule the command after
+     * notifications were enabled on the characteristics, and for that we need the builder to guarantee the
+     * order.
+     */
+    public void sendCommand(final TransactionBuilder builder, final XiaomiProto.Command command) {
+        if (this.characteristicCommandWrite == null) {
+            // Can sometimes happen in race conditions when connecting + receiving calendar event or weather updates
+            LOG.warn("characteristicCommandWrite is null!");
+            return;
+        }
+
+        this.characteristicCommandWrite.write(builder, command.toByteArray());
+    }
+
+    public TransactionBuilder createTransactionBuilder(String taskName) {
+        return commsSupport.createTransactionBuilder(taskName);
+    }
+
+    public BtLEQueue getQueue() {
+        return commsSupport.getQueue();
+    }
+
+    @Override
+    public void onUploadProgress(int textRsrc, int progressPercent, boolean ongoing) {
+        try {
+            final TransactionBuilder builder = commsSupport.createTransactionBuilder("send data upload progress");
+            builder.add(new SetProgressAction(
+                    commsSupport.getContext().getString(textRsrc),
+                    ongoing,
+                    progressPercent,
+                    commsSupport.getContext()
+            ));
+            builder.queue(commsSupport.getQueue());
+        } catch (final Exception e) {
+            LOG.error("Failed to update progress notification", e);
+        }
+    }
+
+    @Override
+    public boolean connect() {
+        return commsSupport.connect();
+    }
+
+    @Override
+    public void runOnQueue(String taskName, Runnable runnable) {
+        final TransactionBuilder b = commsSupport.createTransactionBuilder("run task " + taskName + " on queue");
+        b.add(new PlainAction() {
+            @Override
+            public boolean run(BluetoothGatt gatt) {
+                runnable.run();
+                return true;
+            }
+        });
+        b.queue(commsSupport.getQueue());
+    }
+
+    @Override
+    public void dispose() {
+        commsSupport.dispose();
+        if (characteristicCommandRead != null)
+            characteristicCommandRead.dispose();
+        if (characteristicCommandWrite != null)
+            characteristicCommandWrite.dispose();
+        if (characteristicDataUpload != null)
+            characteristicDataUpload.dispose();
+        if (characteristicActivityData != null)
+            characteristicActivityData.dispose();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiChannelHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiChannelHandler.java
new file mode 100644
index 0000000..0b08151
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiChannelHandler.java
@@ -0,0 +1,30 @@
+/*  Copyright (C) 2023-2024 Yoran Vulker
+
+    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 <http://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi;
+
+public interface XiaomiChannelHandler {
+    enum Channel {
+        Unknown,
+        Version,
+        ProtobufCommand,
+        Activity,
+        Data,
+        Authentication,
+    };
+
+    void handle(final byte[] payload);
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiCharacteristic.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiCharacteristic.java
new file mode 100644
index 0000000..266cdc7
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiCharacteristic.java
@@ -0,0 +1,570 @@
+/*  Copyright (C) 2023-2024 Andreas Shimokawa, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi;
+
+
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.os.Handler;
+import android.os.Looper;
+
+import androidx.annotation.Nullable;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Queue;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEQueue;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class XiaomiCharacteristic {
+    private final Logger LOG = LoggerFactory.getLogger(XiaomiCharacteristic.class);
+    private static final long TIMEOUT_TASK_DELAY = 5000L;
+
+    public static final byte[] PAYLOAD_ACK = new byte[]{0, 0, 3, 0};
+
+    private final XiaomiBleSupport mSupport;
+
+    private final BluetoothGattCharacteristic bluetoothGattCharacteristic;
+    private final UUID characteristicUUID;
+
+    // Encryption
+    private final XiaomiAuthService authService;
+    private boolean isEncrypted;
+    public boolean incrementNonce = true;
+    private int encryptedIndex = 0;
+
+    // max chunk size, including headers
+    private int maxWriteSize = 244; // MTU of 247 - 3 bytes for the ATT overhead (based on lowest MTU observed after increasing MTU to 512)
+    private int maxWriteSizeForCurrentMessage;
+
+    // Chunking
+    private int numChunks = 0;
+    private final Handler timeoutHandler = new Handler(Looper.getMainLooper());
+    private final Map<Integer, byte[]> receivedChunks = new HashMap<>();
+
+    // Scheduling
+    // TODO timeouts
+    private final Queue<Payload> payloadQueue = new LinkedList<>();
+    private boolean waitingAck = false;
+    private boolean sendingChunked = false;
+    private Payload currentPayload = null;
+
+    private XiaomiChannelHandler channelHandler = null;
+
+    public XiaomiCharacteristic(final XiaomiBleSupport support,
+                                final BluetoothGattCharacteristic bluetoothGattCharacteristic,
+                                @Nullable final XiaomiAuthService authService) {
+        this.mSupport = support;
+        this.bluetoothGattCharacteristic = bluetoothGattCharacteristic;
+        this.authService = authService;
+        this.isEncrypted = authService != null;
+        this.characteristicUUID = bluetoothGattCharacteristic.getUuid();
+    }
+
+    public UUID getCharacteristicUUID() {
+        return characteristicUUID;
+    }
+
+    public void setChannelHandler(final XiaomiChannelHandler handler) {
+        this.channelHandler = handler;
+    }
+
+    public void setEncrypted(final boolean encrypted) {
+        this.isEncrypted = encrypted;
+    }
+
+    public void setIncrementNonce(final boolean incrementNonce) {
+        this.incrementNonce = incrementNonce;
+    }
+
+    public void reset() {
+        this.numChunks = 0;
+        this.encryptedIndex = 1; // 0 is used by auth service
+        this.receivedChunks.clear();
+        this.payloadQueue.clear();
+        this.waitingAck = false;
+        this.sendingChunked = false;
+        this.currentPayload = null;
+        cancelTimeoutTask();
+    }
+
+    /**
+     * Write bytes to this characteristic, encrypting and splitting it into chunks if necessary.
+     * Callback will be notified when a (n)ack has been received by the remote device.
+     */
+    public void write(final String taskName, final byte[] value, final SendCallback callback) {
+        write(null, new Payload(taskName, value, callback));
+    }
+
+    /**
+     * Write bytes to this characteristic, encrypting and splitting it into chunks if necessary.
+     */
+    public void write(final String taskName, final byte[] value) {
+        write(taskName, value, null);
+    }
+
+    /**
+     * Write bytes to this characteristic, encrypting and splitting it into chunks if necessary. Uses
+     * the provided builder if we need to schedule something, otherwise it will be queued as other
+     * commands. The callback will be notified when a (n)ack has been received from the remote
+     * device in response to the payload being sent.
+     */
+    public void write(final TransactionBuilder builder, final byte[] value, final SendCallback callback) {
+        write(builder, new Payload(builder.getTaskName(), value, callback));
+    }
+
+    /**
+     * Write bytes to this characteristic, encrypting and splitting it into chunks if necessary. Uses
+     * the provided if we need to schedule something, otherwise it will be queued as other commands.
+     */
+    public void write(final TransactionBuilder builder, final byte[] value) {
+        write(builder, value, null);
+    }
+
+    private void write(final TransactionBuilder builder, final Payload payload) {
+        payloadQueue.add(payload);
+        sendNext(builder);
+    }
+
+    private void sendChunk(final TransactionBuilder builder, final int index, final int chunkPayloadSize) {
+        final byte[] payload = currentPayload.getBytesToSend();
+        final int startIndex = index * chunkPayloadSize;
+        final int endIndex = Math.min((index + 1) * chunkPayloadSize, payload.length);
+        LOG.debug("Sending chunk {} from {} to {} for {}", index, startIndex, endIndex, currentPayload.getTaskName());
+        final byte[] chunkToSend = new byte[2 + endIndex - startIndex];
+        BLETypeConversions.writeUint16(chunkToSend, 0, index + 1);
+        System.arraycopy(payload, startIndex, chunkToSend, 2, endIndex - startIndex);
+        builder.write(bluetoothGattCharacteristic, chunkToSend);
+    }
+
+    public void dispose() {
+        cancelTimeoutTask();
+    }
+
+    private void requestMissingChunks() {
+        if (!(numChunks > 0)) {
+            LOG.warn("Timeout task ran but not expecting any chunks");
+            return;
+        }
+
+        LOG.debug("Timeout reached while waiting for all chunks from device");
+        final List<Integer> missingChunks = new ArrayList<>();
+        for (int i = 0; i < numChunks; i++) {
+            if (!this.receivedChunks.containsKey(i + 1)) {
+                missingChunks.add(i + 1);
+            }
+        }
+
+        // prevent going over maximum message length
+        int reqChunkCount = Math.min(missingChunks.size(), (maxWriteSize - 4) / 2);
+        if (reqChunkCount < missingChunks.size()) {
+            LOG.debug("Missing {} chunk(s), only requesting first {}: {}", missingChunks.size(), reqChunkCount, missingChunks);
+        } else {
+            LOG.debug("Missing {} chunk(s): {}", missingChunks.size(), missingChunks);
+        }
+
+        final ByteBuffer bb = ByteBuffer.allocate(4 + reqChunkCount * 2).order(ByteOrder.LITTLE_ENDIAN);
+        bb.putShort((short) 0); // chunk ID
+        bb.put((byte) 1); // type CHUNKED_ACK
+        bb.put((byte) 5); // indicate partially received transmission, followed by missing chunks
+        for (int i = 0; i < reqChunkCount; i++) {
+            bb.putShort(missingChunks.get(i).shortValue());
+        }
+        final TransactionBuilder tb = mSupport.createTransactionBuilder(String.format("send nack with missing chunks %s", missingChunks));
+        tb.write(bluetoothGattCharacteristic, bb.array());
+        tb.queue(mSupport.getQueue());
+    }
+
+    private void cancelTimeoutTask() {
+        this.timeoutHandler.removeCallbacksAndMessages(null);
+    }
+
+    private void rescheduleTimeoutTask() {
+        cancelTimeoutTask();
+        this.timeoutHandler.postDelayed(this::requestMissingChunks, TIMEOUT_TASK_DELAY);
+    }
+
+    private byte[] reconstructPayloadFromChunks() {
+        final ByteArrayOutputStream out = new ByteArrayOutputStream();
+
+        try {
+            for (int i = 0; i < this.numChunks; i++) {
+                if (!this.receivedChunks.containsKey(i + 1) || this.receivedChunks.get(i + 1) == null) {
+                    LOG.error("Missing chunk {}", i + 1);
+                    return new byte[0];
+                }
+
+                out.write(this.receivedChunks.get(i + 1));
+            }
+        } catch (final IOException ex) {
+            LOG.error("Failed to reconstruct payload", ex);
+            return new byte[0];
+        }
+
+        return out.toByteArray();
+    }
+
+    public void onCharacteristicChanged(final byte[] value) {
+        final ByteBuffer buf = ByteBuffer.wrap(value).order(ByteOrder.LITTLE_ENDIAN);
+
+        final int chunk = buf.getShort();
+        if (chunk != 0) {
+            // Chunked packet
+            LOG.debug("Got chunk {} of {}", chunk, numChunks);
+
+            if (chunk > numChunks) {
+                LOG.warn("Ignoring chunk {} exceeding upper bound {}", chunk, numChunks);
+                return;
+            }
+
+            if (this.receivedChunks.containsKey(chunk)) {
+                LOG.warn("Already received chunk {}", chunk);
+            }
+
+            final byte[] chunkBytes = new byte[buf.limit() - buf.position()];
+            buf.get(chunkBytes);
+            this.receivedChunks.put(chunk, chunkBytes);
+            rescheduleTimeoutTask();
+
+            if (this.receivedChunks.keySet().size() == numChunks) {
+                cancelTimeoutTask();
+                sendChunkEndAck();
+                final byte[] payload = reconstructPayloadFromChunks();
+
+                if (payload.length == 0) {
+                    LOG.warn("Payload reconstructed from chunks was empty");
+                } else if (channelHandler != null) {
+                    if (isEncrypted) {
+                        // chunks are always encrypted if an auth service is available
+                        channelHandler.handle(authService.decrypt(payload));
+                    } else {
+                        channelHandler.handle(payload);
+                    }
+                } else {
+                    LOG.warn("Channel handler for char {} is null!", characteristicUUID);
+                }
+
+                this.numChunks = 0;
+                this.receivedChunks.clear();
+            }
+        } else {
+            // Not a chunk / single-packet
+            final byte type = buf.get();
+
+            switch (type) {
+                case 0:
+                    // Chunked start request
+                    // TODO verify previous transfer completed
+                    final byte messageEncrypted = buf.get();
+                    byte expectedResult = (byte) (isEncrypted ? 1 : 0);
+                    if (messageEncrypted != expectedResult) {
+                        LOG.warn("Chunked start request: expected {}, got {}", expectedResult, messageEncrypted);
+                        return;
+                    }
+                    this.numChunks = buf.getShort();
+                    this.receivedChunks.clear();
+                    LOG.debug("Got chunked start request for {} chunks", this.numChunks);
+                    sendChunkStartAck();
+                    return;
+                case 1:
+                    // Chunked ack
+                    final byte subtype = buf.get();
+
+                    final byte[] remaining = new byte[buf.remaining()];
+                    if (buf.hasRemaining()) {
+                        buf.get(remaining);
+                        LOG.debug("Operation CHUNK_ACK of type {} has additional payload: {}",
+                                subtype, GB.hexdump(remaining));
+                    }
+
+                    switch (subtype) {
+                        case 0: {
+                            LOG.debug("Got chunked ack end");
+                            if (currentPayload != null && currentPayload.getCallback() != null) {
+                                currentPayload.getCallback().onSend();
+                            }
+                            currentPayload = null;
+                            sendingChunked = false;
+                            sendNext(null);
+                            return;
+                        }
+                        case 1: {
+                            LOG.debug("Got chunked ack start");
+                            final TransactionBuilder builder = mSupport.createTransactionBuilder("send chunks for " + currentPayload.getTaskName());
+                            final byte[] payload = currentPayload.getBytesToSend();
+                            final int chunkPayloadSize = maxWriteSizeForCurrentMessage - 2;
+
+                            for (int i = 0; i * chunkPayloadSize < payload.length; i++) {
+                                sendChunk(builder, i, chunkPayloadSize);
+                            }
+
+                            builder.queue(mSupport.getQueue());
+                            return;
+                        }
+                        case 2: {
+                            LOG.warn("Got chunked nack for {}", currentPayload.getTaskName());
+                            if (currentPayload != null && currentPayload.getCallback() != null) {
+                                currentPayload.getCallback().onNack();
+                            }
+                            currentPayload = null;
+                            sendingChunked = false;
+                            sendNext(null);
+                            return;
+                        }
+                        case 5: {
+                            short[] invalidChunks = new short[remaining.length / 2];
+                            if (remaining.length > 0) {
+                                ByteBuffer remainingBuffer = ByteBuffer.wrap(remaining).order(ByteOrder.LITTLE_ENDIAN);
+                                for (int i = 0; i < remaining.length / 2; i++) {
+                                    invalidChunks[i] = remainingBuffer.getShort();
+                                }
+
+                                LOG.info("Got chunk request, requested chunks: {}", Arrays.toString(invalidChunks));
+                                final TransactionBuilder builder = mSupport.createTransactionBuilder("resend chunks for " + currentPayload.getTaskName());
+
+                                for (short chunkIndex : invalidChunks) {
+                                    // chunk indices start at 1
+                                    sendChunk(builder, chunkIndex - 1, maxWriteSizeForCurrentMessage - 2);
+                                }
+                            } else {
+                                LOG.warn("Got chunk request, no chunk indices requested");
+
+                                if (maxWriteSize != maxWriteSizeForCurrentMessage) {
+                                    LOG.info("MTU changed while sending message, prepending message to queue and resending");
+                                    ((LinkedList<Payload>) payloadQueue).addFirst(currentPayload);
+                                    currentPayload = null;
+                                    sendingChunked = false;
+                                    sendNext(null);
+                                    return;
+                                }
+                            }
+                        }
+                    }
+
+                    LOG.warn("Unknown chunked ack subtype {} for {}", subtype, currentPayload.getTaskName());
+                    return;
+                case 2:
+                    // Single command
+                    sendAck();
+
+                    final byte encryption = buf.get();
+                    final byte[] plainValue;
+                    if (encryption == 1) {
+                        final byte[] encryptedValue = new byte[buf.limit() - buf.position()];
+                        buf.get(encryptedValue);
+                        plainValue = authService.decrypt(encryptedValue);
+                    } else {
+                        plainValue = new byte[buf.limit() - buf.position()];
+                        buf.get(plainValue);
+                    }
+
+                    if (channelHandler != null)
+                        channelHandler.handle(plainValue);
+                    else
+                        LOG.warn("Channel handler for char {} is null!", characteristicUUID);
+
+                    return;
+                case 3:
+                    // ack
+                    final byte result = buf.get();
+
+                    if (result == 0) {
+                        LOG.debug("Got ack for {}", currentPayload.getTaskName());
+
+                        if (currentPayload != null && currentPayload.getCallback() != null) {
+                            currentPayload.getCallback().onSend();
+                        }
+                    } else {
+                        LOG.warn("Got single cmd NACK ({}) for {}", result, currentPayload.getTaskName());
+
+                        if (currentPayload != null && currentPayload.getCallback() != null) {
+                            currentPayload.getCallback().onNack();
+                        }
+                    }
+
+                    currentPayload = null;
+                    waitingAck = false;
+                    sendNext(null);
+                    return;
+            }
+
+            LOG.warn("Unhandled command type {}", type);
+        }
+    }
+
+    private void sendNext(@Nullable final TransactionBuilder b) {
+        if (waitingAck || sendingChunked) {
+            LOG.debug("Already sending something");
+            return;
+        }
+
+        currentPayload = payloadQueue.poll();
+        if (currentPayload == null) {
+            LOG.debug("Nothing to send");
+            return;
+        }
+
+        LOG.debug("Will send {}", GB.hexdump(currentPayload.getBytesToSend()));
+
+        final boolean encrypt = isEncrypted && authService.isEncryptionInitialized();
+
+        if (encrypt) {
+            currentPayload.setBytesToSend(authService.encrypt(currentPayload.getBytesToSend(), incrementNonce ? encryptedIndex : 0));
+        }
+
+        // before checking whether message should be chunked, read the maximum message size for this transaction
+        maxWriteSizeForCurrentMessage = maxWriteSize;
+
+        if (shouldWriteChunked(currentPayload.getBytesToSend())) {
+            if (encrypt && incrementNonce) {
+                // Prepend encrypted index for the nonce
+                currentPayload.setBytesToSend(
+                        ByteBuffer.allocate(2 + currentPayload.getBytesToSend().length).order(ByteOrder.LITTLE_ENDIAN)
+                                .putShort((short) encryptedIndex++)
+                                .put(currentPayload.getBytesToSend())
+                                .array()
+                );
+            }
+
+            LOG.debug("Sending {} - chunked", currentPayload.getTaskName());
+
+            sendingChunked = true;
+
+            final ByteBuffer buf = ByteBuffer.allocate(6).order(ByteOrder.LITTLE_ENDIAN);
+            buf.putShort((short) 0);
+            buf.put((byte) 0);
+            buf.put((byte) (encrypt ? 1 : 0));
+            buf.putShort((short) Math.ceil(currentPayload.getBytesToSend().length / (float) (maxWriteSizeForCurrentMessage - 2)));
+
+            final TransactionBuilder builder = b == null ? mSupport.createTransactionBuilder("send chunked start for " + currentPayload.getTaskName()) : b;
+            builder.write(bluetoothGattCharacteristic, buf.array());
+            if (b == null) {
+                builder.queue(mSupport.getQueue());
+            }
+        } else {
+            LOG.debug("Sending {} - single", currentPayload.getTaskName());
+
+            // Encrypt single command
+            final int commandLength = (encrypt ? 6 : 4) + currentPayload.getBytesToSend().length;
+
+            final ByteBuffer buf = ByteBuffer.allocate(commandLength).order(ByteOrder.LITTLE_ENDIAN);
+            buf.putShort((short) 0);
+            buf.put((byte) 2); // 2 for command
+            buf.put((byte) (encrypt ? 1 : 2));
+            if (encrypt) {
+                if (incrementNonce) {
+                    buf.putShort((short) encryptedIndex++);
+                } else {
+                    buf.putShort((short) 0);
+                }
+            }
+            buf.put(currentPayload.getBytesToSend()); // it's already encrypted
+
+            waitingAck = true;
+
+            final TransactionBuilder builder = b == null ? mSupport.createTransactionBuilder("send single command for " + currentPayload.getTaskName()) : b;
+            builder.write(bluetoothGattCharacteristic, buf.array());
+            if (b == null) {
+                builder.queue(mSupport.getQueue());
+            }
+        }
+    }
+
+    private boolean shouldWriteChunked(final byte[] payload) {
+        if (!isEncrypted) {
+            // non-encrypted are always chunked
+            return true;
+        }
+
+        // payload + 6 bytes at the start with the encryption stuff
+        return payload.length + 6 > maxWriteSizeForCurrentMessage;
+    }
+
+    private void sendAck() {
+        final TransactionBuilder builder = mSupport.createTransactionBuilder("send ack");
+        builder.write(bluetoothGattCharacteristic, PAYLOAD_ACK);
+        builder.queue(mSupport.getQueue());
+    }
+
+    private void sendChunkStartAck() {
+        final TransactionBuilder builder = mSupport.createTransactionBuilder("send chunked start ack");
+        builder.write(bluetoothGattCharacteristic, new byte[]{0x00, 0x00, 0x01, 0x01});
+        builder.queue(mSupport.getQueue());
+    }
+
+    private void sendChunkEndAck() {
+        final TransactionBuilder builder = mSupport.createTransactionBuilder("send chunked end ack");
+        builder.write(bluetoothGattCharacteristic, new byte[]{0x00, 0x00, 0x01, 0x00});
+        builder.queue(mSupport.getQueue());
+    }
+
+    public void setMtu(final int newMtu) {
+        // subtract ATT packet header size
+        maxWriteSize = newMtu - 3;
+    }
+
+    private static class Payload {
+        private final String taskName;
+        private final byte[] bytes;
+
+        // Bytes that will actually be sent (might be encrypted)
+        private byte[] bytesToSend;
+        private final SendCallback callback;
+
+        public Payload(final String taskName, final byte[] bytes, final SendCallback callback) {
+            this.taskName = taskName;
+            this.bytes = bytes;
+            this.callback = callback;
+        }
+
+        public Payload(final String taskName, final byte[] bytes) {
+            this(taskName, bytes, null);
+        }
+
+        public String getTaskName() {
+            return taskName;
+        }
+
+        public void setBytesToSend(final byte[] bytesToSend) {
+            this.bytesToSend = bytesToSend;
+        }
+
+        public byte[] getBytesToSend() {
+            return bytesToSend != null ? bytesToSend : bytes;
+        }
+        public SendCallback getCallback() { return this.callback; }
+    }
+
+    public interface SendCallback {
+        void onSend();
+        void onNack();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiConnectionSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiConnectionSupport.java
new file mode 100644
index 0000000..8adbd72
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiConnectionSupport.java
@@ -0,0 +1,37 @@
+/*  Copyright (C) 2023 Jos茅 Rebelo, Yoran Vulker
+
+    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 <http://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi;
+
+import android.bluetooth.BluetoothAdapter;
+import android.content.Context;
+
+import androidx.annotation.Nullable;
+
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.proto.xiaomi.XiaomiProto;
+
+public abstract class XiaomiConnectionSupport {
+    public abstract boolean connect();
+    public void onAuthSuccess() {}
+    public abstract void onUploadProgress(int textRsrc, int progressPercent, boolean ongoing);
+    public abstract void runOnQueue(String taskName, Runnable run);
+    public abstract void dispose();
+    public abstract void setContext(final GBDevice device, final BluetoothAdapter adapter, final Context context);
+    public abstract void sendCommand(final String taskName, final XiaomiProto.Command command);
+    public abstract void sendDataChunk(final String taskName, final byte[] chunk, @Nullable final XiaomiCharacteristic.SendCallback callback);
+    public void setAutoReconnect(final boolean enabled) {}
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiPreferences.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiPreferences.java
new file mode 100644
index 0000000..556c9f4
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiPreferences.java
@@ -0,0 +1,94 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi;
+
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collections;
+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.devices.xiaomi.XiaomiWorkoutType;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.proto.xiaomi.XiaomiProto;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+public final class XiaomiPreferences {
+    public static final String PREF_ALARM_SLOTS = "alarm_slots";
+    public static final String PREF_REMINDER_SLOTS = "reminder_slots";
+    public static final String PREF_CANNED_MESSAGES_MIN = "canned_messages_min";
+    public static final String PREF_CANNED_MESSAGES_MAX = "canned_messages_max";
+    public static final String PREF_WORKOUT_TYPES = "workout_types";
+    public static final String PREF_WIDGET_SCREENS = "widget_screens"; // FIXME the value is a protobuf hex string
+    public static final String PREF_WIDGET_PARTS = "widget_parts"; // FIXME the value is a protobuf hex string
+
+    public static final String FEAT_WEAR_MODE = "feat_wear_mode";
+    public static final String FEAT_DEVICE_ACTIONS = "feat_device_actions";
+    public static final String FEAT_DISPLAY_ITEMS = "feat_display_items";
+    public static final String FEAT_STRESS = "feat_stress";
+    public static final String FEAT_SPO2 = "feat_spo2";
+    public static final String FEAT_PASSWORD = "feat_password";
+    public static final String FEAT_INACTIVITY = "feat_inactivity";
+    public static final String FEAT_SLEEP_MODE_SCHEDULE = "feat_sleep_mode_schedule";
+    public static final String FEAT_GOAL_NOTIFICATION = "feat_goal_notification";
+    public static final String FEAT_GOAL_SECONDARY = "feat_goal_secondary";
+    public static final String FEAT_VITALITY_SCORE = "feat_vitality_score";
+    public static final String FEAT_SCREEN_ON_ON_NOTIFICATIONS = "feat_screen_on_on_notifications";
+    public static final String FEAT_CAMERA_REMOTE = "feat_camera_remote";
+    public static final String FEAT_WIDGETS = "feat_widgets";
+    public static final String FEAT_MULTIPLE_WEATHER_LOCATIONS = "feat_multiple_weather_locations";
+
+    private XiaomiPreferences() {
+        // util class
+    }
+
+    public static String prefFromHourMin(final XiaomiProto.HourMinute hourMinute) {
+        return String.format(Locale.ROOT, "%02d:%02d", hourMinute.getHour(), hourMinute.getMinute());
+    }
+
+    public static XiaomiProto.HourMinute prefToHourMin(final Date date) {
+        final Calendar calendar = GregorianCalendar.getInstance();
+        calendar.setTime(date);
+
+        return XiaomiProto.HourMinute.newBuilder()
+                .setHour(calendar.get(Calendar.HOUR_OF_DAY))
+                .setMinute(calendar.get(Calendar.MINUTE))
+                .build();
+    }
+
+    public static Date toDate(final XiaomiProto.Date date, final XiaomiProto.Time time) {
+        // For some reason, the watch expects those in UTC...
+        // TODO double-check with official app, this does not make sense
+        final Calendar calendar = GregorianCalendar.getInstance(TimeZone.getTimeZone("UTC"));
+        calendar.set(
+                date.getYear(), date.getMonth() - 1, date.getDay(),
+                time.getHour(), time.getMinute(), time.getSecond()
+        );
+
+        return calendar.getTime();
+    }
+
+    public static boolean keepActivityDataOnDevice(final GBDevice gbDevice) {
+        final Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()));
+        return prefs.getBoolean("keep_activity_data_on_device", false);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiSppPacketV1.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiSppPacketV1.java
new file mode 100644
index 0000000..9d97c98
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiSppPacketV1.java
@@ -0,0 +1,331 @@
+/*  Copyright (C) 2023-2024 Yoran Vulker
+
+    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 <http://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi;
+
+import androidx.annotation.NonNull;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Arrays;
+import java.util.Locale;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import nodomain.freeyourgadget.gadgetbridge.proto.xiaomi.XiaomiProto;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.XiaomiChannelHandler.Channel;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class XiaomiSppPacketV1 {
+    private static final Logger LOG = LoggerFactory.getLogger(XiaomiSppPacketV1.class);
+
+    public static final byte[] PACKET_PREAMBLE = new byte[]{(byte) 0xba, (byte) 0xdc, (byte) 0xfe};
+    public static final byte[] PACKET_EPILOGUE = new byte[]{(byte) 0xef};
+
+    public static final int CHANNEL_VERSION = 0;
+    /**
+     * Channel ID for PROTO messages received from device
+     */
+    public static final int CHANNEL_PROTO_RX = 1;
+
+    /**
+     * Channel ID for PROTO messages sent to device
+     */
+    public static final int CHANNEL_PROTO_TX = 2;
+    public static final int CHANNEL_FITNESS = 3;
+    public static final int CHANNEL_VOICE = 4;
+    public static final int CHANNEL_MASS = 5;
+    public static final int CHANNEL_OTA = 7;
+
+    public static final int DATA_TYPE_PLAIN = 0;
+    public static final int DATA_TYPE_ENCRYPTED = 1;
+    public static final int DATA_TYPE_AUTH = 2;
+
+    public static final int OPCODE_READ = 0;
+    public static final int OPCODE_SEND = 2;
+
+    private byte[] payload;
+    private boolean flag, needsResponse;
+    private Channel channel;
+    private int rawChannel, opCode, frameSerial, dataType;
+
+    public static int getDataTypeForChannel(final Channel channel) {
+        switch (channel) {
+            case Authentication:
+                return DATA_TYPE_AUTH;
+            case ProtobufCommand:
+            case Version:
+            case Data:
+                return DATA_TYPE_ENCRYPTED;
+            default:
+                LOG.warn("getDataTypeForChannel(): cannot determine data type for channel {}", channel);
+                // fall through
+            case Activity: // and voice
+                return DATA_TYPE_PLAIN;
+        }
+    }
+
+    public static class Builder {
+        private byte[] payload = new byte[0];
+        private boolean flag = true, needsResponse = false;
+        private Channel channel = Channel.Unknown;
+        private int opCode = -1, frameSerial = -1, dataType = -1;
+
+        public XiaomiSppPacketV1 build() {
+            XiaomiSppPacketV1 result = new XiaomiSppPacketV1();
+
+            result.channel = channel;
+            result.flag = flag;
+            result.needsResponse = needsResponse;
+            result.opCode = opCode;
+            result.frameSerial = frameSerial;
+            result.dataType = dataType;
+            result.payload = payload;
+            result.rawChannel = getRawChannel(channel, true);
+
+            return result;
+        }
+
+        public Builder channel(final Channel channel) {
+            this.channel = channel;
+            return this;
+        }
+
+        public Builder flag(final boolean flag) {
+            this.flag = flag;
+            return this;
+        }
+
+        public Builder needsResponse(final boolean needsResponse) {
+            this.needsResponse = needsResponse;
+            return this;
+        }
+
+        public Builder opCode(final int opCode) {
+            this.opCode = opCode;
+            return this;
+        }
+
+        public Builder frameSerial(final int frameSerial) {
+            this.frameSerial = frameSerial;
+            return this;
+        }
+
+        public Builder dataType(final int dataType) {
+            this.dataType = dataType;
+            return this;
+        }
+
+        public Builder payload(final byte[] payload) {
+            this.payload = payload;
+            return this;
+        }
+    }
+
+    public Channel getChannel() {
+        return channel;
+    }
+
+    public int getDataType() {
+        return dataType;
+    }
+
+    public byte[] getPayload() {
+        return payload;
+    }
+
+    public byte[] getDecryptedPayload(final XiaomiAuthService authService) {
+        if (payload == null) {
+            LOG.warn("getDecryptedPayload(): payload is null");
+            return null;
+        }
+
+        if (authService == null) {
+            LOG.warn("getDecryptedPayload(): authService is null");
+            return payload;
+        }
+
+        if (!authService.isEncryptionInitialized() && dataType == DATA_TYPE_ENCRYPTED) {
+            LOG.warn("getDecryptedPayload(): authService is not ready to decrypt");
+            return payload;
+        }
+
+        if (dataType == DATA_TYPE_ENCRYPTED) {
+            return authService.decrypt(payload);
+        }
+
+        return payload;
+    }
+
+    public boolean needsResponse() {
+        return needsResponse;
+    }
+
+    public boolean hasFlag() {
+        return this.flag;
+    }
+
+    public static XiaomiSppPacketV1 fromXiaomiCommand(final XiaomiProto.Command command, int frameCounter, boolean needsResponse) {
+        return newBuilder().channel(Channel.ProtobufCommand).needsResponse(needsResponse).dataType(
+                command.getType() == XiaomiAuthService.COMMAND_TYPE && command.getSubtype() >= 17 ? DATA_TYPE_AUTH : DATA_TYPE_ENCRYPTED
+        ).frameSerial(frameCounter).opCode(OPCODE_SEND).payload(command.toByteArray()).build();
+    }
+
+    public static Builder newBuilder() {
+        return new Builder();
+    }
+
+    @NonNull
+    @Override
+    public String toString() {
+        return String.format(Locale.ROOT,
+                "SppPacket{ channel=%s, rawChannel=%d, flag=%b, needsResponse=%b, opCode=0x%x, frameSerial=0x%x, dataType=0x%x, payloadSize=%d }",
+                channel, rawChannel, flag, needsResponse, opCode, frameSerial, dataType, payload.length);
+    }
+
+    public static int getRawChannel(final Channel channel, final boolean tx) {
+        switch (channel) {
+            case Version:
+                return CHANNEL_VERSION;
+            case Authentication:
+            case ProtobufCommand:
+                return tx ? CHANNEL_PROTO_TX : CHANNEL_PROTO_RX;
+            case Activity:
+                return CHANNEL_FITNESS;
+            case Data:
+                return CHANNEL_MASS;
+            default:
+                LOG.warn("Raw channel for {} unknown", channel);
+                return -1;
+        }
+    }
+
+    public static Channel getChannel(final byte rawChannel) {
+        switch (rawChannel & 0xff) {
+            case CHANNEL_PROTO_RX:
+            case CHANNEL_PROTO_TX:
+                return Channel.ProtobufCommand;
+            case CHANNEL_FITNESS:
+                return Channel.Activity;
+            case CHANNEL_MASS:
+                return Channel.Data;
+            case CHANNEL_VERSION:
+                return Channel.Version;
+            default:
+                LOG.warn("Cannot convert raw channel {} to known channel", rawChannel & 0xff);
+                return Channel.Unknown;
+        }
+    }
+
+    public static XiaomiSppPacketV1 decode(final byte[] packet) {
+        if (packet.length < 11) {
+            LOG.error("Cannot decode incomplete packet");
+            return null;
+        }
+
+        ByteBuffer buffer = ByteBuffer.wrap(packet).order(ByteOrder.LITTLE_ENDIAN);
+        byte[] preamble = new byte[PACKET_PREAMBLE.length];
+        buffer.get(preamble);
+
+        if (!Arrays.equals(PACKET_PREAMBLE, preamble)) {
+            LOG.error("Expected preamble (0x{}) does not match found preamble (0x{})",
+                    GB.hexdump(PACKET_PREAMBLE),
+                    GB.hexdump(preamble));
+            return null;
+        }
+
+        byte channel = buffer.get();
+
+        if ((channel & 0xf0) != 0) {
+            LOG.warn("Reserved bits in channel byte are non-zero: 0b{}", Integer.toBinaryString((channel & 0xf0) >> 4));
+            channel = 0x0f;
+        }
+
+        byte flags = buffer.get();
+        boolean flag = (flags & 0x80) != 0;
+        boolean needsResponse = (flags & 0x40) != 0;
+
+        if ((flags & 0x0f) != 0) {
+            LOG.warn("Reserved bits in flags byte are non-zero: 0b{}", Integer.toBinaryString(flags & 0x0f));
+        }
+
+        // payload header is included in size
+        int payloadLength = (buffer.getShort() & 0xffff) - 3;
+
+        if (payloadLength + 11 > packet.length) {
+            LOG.error("Packet incomplete (expected length: {}, actual length: {})", payloadLength + 11, packet.length);
+            return null;
+        }
+
+        int opCode = buffer.get() & 0xff;
+        int frameSerial = buffer.get() & 0xff;
+        int dataType = buffer.get() & 0xff;
+        byte[] payload = new byte[payloadLength];
+        buffer.get(payload);
+
+        byte[] epilogue = new byte[PACKET_EPILOGUE.length];
+        buffer.get(epilogue);
+
+        if (!Arrays.equals(PACKET_EPILOGUE, epilogue)) {
+            LOG.error("Expected epilogue (0x{}) does not match actual epilogue (0x{})",
+                    GB.hexdump(PACKET_EPILOGUE),
+                    GB.hexdump(epilogue));
+            return null;
+        }
+
+        XiaomiSppPacketV1 result = new XiaomiSppPacketV1();
+        result.rawChannel = channel;
+        result.channel = getChannel(channel);
+        result.flag = flag;
+        result.needsResponse = needsResponse;
+        result.opCode = opCode;
+        result.frameSerial = frameSerial;
+        result.dataType = dataType;
+        result.payload = payload;
+
+        return result;
+    }
+
+    public byte[] encode(final XiaomiAuthService authService, final AtomicInteger encryptionCounter) {
+        byte[] payload = this.payload;
+
+        if (dataType == DATA_TYPE_ENCRYPTED && channel == Channel.ProtobufCommand) {
+            int packetCounter = encryptionCounter.incrementAndGet();
+            payload = authService.encrypt(payload, packetCounter);
+            payload = ByteBuffer.allocate(payload.length + 2).order(ByteOrder.LITTLE_ENDIAN).putShort((short) packetCounter).put(payload).array();
+        } else if (dataType == DATA_TYPE_ENCRYPTED) {
+            payload = authService.encrypt(payload, (short) 0);
+        }
+
+        ByteBuffer buffer = ByteBuffer.allocate(11 + payload.length).order(ByteOrder.LITTLE_ENDIAN);
+        buffer.put(PACKET_PREAMBLE);
+
+        buffer.put((byte) (getRawChannel(channel, true) & 0xf));
+        buffer.put((byte) ((flag ? 0x80 : 0) | (needsResponse ? 0x40 : 0)));
+        buffer.putShort((short) (payload.length + 3));
+
+        buffer.put((byte) (opCode & 0xff));
+        buffer.put((byte) (frameSerial & 0xff));
+        buffer.put((byte) (dataType & 0xff));
+
+        buffer.put(payload);
+
+        buffer.put(PACKET_EPILOGUE);
+        return buffer.array();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiSppPacketV2.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiSppPacketV2.java
new file mode 100644
index 0000000..c699fe0
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiSppPacketV2.java
@@ -0,0 +1,514 @@
+/*  Copyright (C) 2024 Yoran Vulker
+
+    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 <http://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Arrays;
+
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+import static nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.XiaomiChannelHandler.Channel;
+
+public abstract class XiaomiSppPacketV2 {
+    private static final Logger LOG = LoggerFactory.getLogger(XiaomiSppPacketV2.class);
+
+    public static final byte[] PACKET_PREAMBLE = new byte[]{(byte) 0xa5, (byte) 0xa5};
+
+    // TODO NACK
+    public static final int PACKET_TYPE_UNKNOWN = -1;
+    public static final int PACKET_TYPE_ACK = 1;
+    public static final int PACKET_TYPE_SESSION_CONFIG = 2;
+    public static final int PACKET_TYPE_DATA = 3;
+
+    private final int sequenceNumber;
+    private final int packetType;
+
+    protected abstract byte[] getPacketPayloadBytes(XiaomiAuthService authService);
+
+    public static abstract class Builder<T extends Builder<T>> {
+        int packetNumber = -1;
+        int packetType = PACKET_TYPE_UNKNOWN;
+
+        public T setSequenceNumber(int packetNumber) {
+            this.packetNumber = packetNumber;
+            return (T) this;
+        }
+
+        /**
+         * @noinspection UnusedReturnValue
+         */
+        public T setPacketType(int packetType) {
+            this.packetType = packetType;
+            return (T) this;
+        }
+
+        public abstract XiaomiSppPacketV2 build();
+    }
+
+    public static class AckPacket extends XiaomiSppPacketV2 {
+        public static class Builder extends XiaomiSppPacketV2.Builder<Builder> {
+            public Builder() {
+                setPacketType(PACKET_TYPE_ACK);
+            }
+
+            @Override
+            public XiaomiSppPacketV2 build() {
+                return new AckPacket(this);
+            }
+        }
+
+        protected AckPacket(final Builder builder) {
+            super(builder.packetType, builder.packetNumber);
+        }
+
+        @Override
+        protected byte[] getPacketPayloadBytes(XiaomiAuthService authService) {
+            return new byte[0];
+        }
+    }
+
+    public static class SessionConfigPacket extends XiaomiSppPacketV2 {
+        public static final int OPCODE_START_SESSION_REQUEST = 1;
+        public static final int OPCODE_START_SESSION_RESPONSE = 2;
+        public static final int OPCODE_STOP_SESSION_REQUEST = 3;
+        public static final int OPCODE_STOP_SESSION_RESPONSE = 4;
+
+        public static final int KEY_VERSION = 1;
+        public static final int KEY_MAX_PACKET_SIZE = 2;
+        public static final int KEY_TX_WIN = 3;
+        public static final int KEY_SEND_TIMEOUT = 4;
+
+        private static final int VALUE_SIZE_VERSION = 3;
+        private static final int VALUE_SIZE_MAX_PACKET_SIZE = 2;
+        private static final int VALUE_SIZE_TX_WIN = 2;
+        private static final int VALUE_SIZE_SEND_TIMEOUT = 2;
+
+        public static class Builder extends XiaomiSppPacketV2.Builder<Builder> {
+            private int opCode = -1;
+
+            public Builder() {
+                setPacketType(PACKET_TYPE_SESSION_CONFIG);
+            }
+
+            public Builder setOpCode(final int opCode) {
+                this.opCode = opCode;
+                return this;
+            }
+
+            @Override
+            public XiaomiSppPacketV2 build() {
+                return new SessionConfigPacket(this);
+            }
+        }
+
+        private final int opCode;
+
+        protected SessionConfigPacket(final Builder builder) {
+            super(builder.packetType, builder.packetNumber);
+            this.opCode = builder.opCode;
+        }
+
+        public int getOpCode() {
+            return this.opCode;
+        }
+
+        @Override
+        protected byte[] getPacketPayloadBytes(XiaomiAuthService authService) {
+            // from packet dump of official app
+            return new byte[]{
+                    // opcode
+                    (byte) this.opCode,
+
+                    // VERSION (type 1) = 01.00.00
+                    KEY_VERSION,
+                    0x03, 0x00,
+                    0x01, 0x00, 0x00,
+
+                    // MAX_FRAME_SIZE (type 2) = 0xfc00 -> 64512 bytes
+                    KEY_MAX_PACKET_SIZE,
+                    0x02, 0x00,
+                    0x00, (byte) 0xfc,
+
+                    // TX_WIN (type 3) = 0x0020 -> 32 frames
+                    KEY_TX_WIN,
+                    0x02, 0x00,
+                    0x20, 0x00,
+
+                    // SEND_TIMEOUT (type 4) = 0x2710 -> 10000ms
+                    KEY_SEND_TIMEOUT,
+                    0x02,
+                    0x10, 0x27,
+            };
+        }
+
+        public static XiaomiSppPacketV2 decodePayloadBytes(final int sequenceNumber, final byte[] payloadBytes) {
+            final ByteBuffer buffer = ByteBuffer.wrap(payloadBytes).order(ByteOrder.LITTLE_ENDIAN);
+
+            if (buffer.remaining() < 1) {
+                LOG.warn("SessionConfig.decodePayloadBytes(): at least 1 byte required to decode");
+                return null;
+            }
+
+            final int opCode = buffer.get() & 0xff;
+
+            switch (opCode) {
+                case OPCODE_START_SESSION_REQUEST:
+                case OPCODE_START_SESSION_RESPONSE: {
+                    while (buffer.remaining() >= 3) {
+                        final int key = buffer.get() & 0xff;
+                        final int valueSize = buffer.getShort() & 0xffff;
+
+                        if (buffer.remaining() < valueSize) {
+                            LOG.warn("not enough bytes remaining to extract value");
+                            break;
+                        }
+
+                        // TODO store and handle values
+                        switch (key) {
+                            case KEY_VERSION: {
+                                if (valueSize != VALUE_SIZE_VERSION) {
+                                    LOG.warn("expected {} bytes for version value, got {}", VALUE_SIZE_VERSION, valueSize);
+                                    buffer.get(new byte[valueSize]);
+                                    break;
+                                }
+
+                                final byte[] version = new byte[valueSize];
+                                buffer.get(version);
+                                LOG.debug("received SPPv2 version: {}", GB.hexdump(version));
+                                break;
+                            }
+                            case KEY_MAX_PACKET_SIZE: {
+                                if (valueSize != VALUE_SIZE_MAX_PACKET_SIZE) {
+                                    LOG.warn("expected 2 bytes for maximum packet size, got {}", valueSize);
+                                    buffer.get(new byte[valueSize]);
+                                    break;
+                                }
+
+                                LOG.debug("received max packet size: {}", buffer.getShort() & 0xffff);
+                                break;
+                            }
+                            case KEY_TX_WIN: {
+                                if (valueSize != VALUE_SIZE_TX_WIN) {
+                                    LOG.warn("expected {} bytes for transmission window, got {}", VALUE_SIZE_TX_WIN, valueSize);
+                                    buffer.get(new byte[valueSize]);
+                                    break;
+                                }
+
+                                LOG.debug("received tx win: {}", buffer.getShort() & 0xffff);
+                                break;
+                            }
+                            case KEY_SEND_TIMEOUT: {
+                                if (valueSize != VALUE_SIZE_SEND_TIMEOUT) {
+                                    LOG.warn("expected {} bytes for send timeout value, got {}", VALUE_SIZE_SEND_TIMEOUT, valueSize);
+                                    buffer.get(new byte[valueSize]);
+                                    break;
+                                }
+
+                                LOG.debug("received send timeout: {}ms", buffer.getShort() & 0xffff);
+                                break;
+                            }
+                            default: {
+                                final byte[] value = new byte[valueSize];
+                                LOG.debug("received unknown config type {} with byte value {}",
+                                        key,
+                                        GB.hexdump(value));
+                                break;
+                            }
+                        }
+                    }
+
+                    break;
+                }
+                case OPCODE_STOP_SESSION_REQUEST:
+                case OPCODE_STOP_SESSION_RESPONSE: {
+                    break;
+                }
+                default: {
+                    LOG.error("SessionConfigPacket#decode(): unknown opcode {}", opCode);
+                    break;
+                }
+            }
+
+            return new Builder()
+                    .setSequenceNumber(sequenceNumber)
+                    .setOpCode(opCode)
+                    .build();
+        }
+    }
+
+    public static class DataPacket extends XiaomiSppPacketV2 {
+        private static final int CHANNEL_UNKNOWN = -1;
+        private static final int CHANNEL_PROTOBUF = 1; // encrypted after authentication
+        private static final int CHANNEL_DATA = 2; // not encrypted
+        private static final int CHANNEL_ACTIVITY = 5; // encrypted
+
+        public static final int OPCODE_UNKNOWN = -1;
+        public static final int OPCODE_SEND_PLAINTEXT = 1;
+        public static final int OPCODE_SEND_ENCRYPTED = 2;
+
+        public static class Builder extends XiaomiSppPacketV2.Builder<Builder> {
+            private Channel channel = Channel.Unknown;
+            private int opCode = OPCODE_UNKNOWN;
+            private byte[] payload = new byte[0];
+
+            public Builder() {
+                setPacketType(PACKET_TYPE_DATA);
+            }
+
+            public Builder setOpCode(final int opCode) {
+                this.opCode = opCode;
+                return this;
+            }
+
+            public Builder setChannel(final Channel channel) {
+                this.channel = channel;
+                return this;
+            }
+
+            public Builder setPayload(final byte[] payload) {
+                this.payload = payload;
+                return this;
+            }
+
+            public XiaomiSppPacketV2 build() {
+                return new DataPacket(this);
+            }
+        }
+
+        private final Channel channel;
+        private final int opCode;
+        private final byte[] payload;
+
+        protected DataPacket(final Builder builder) {
+            super(builder.packetType, builder.packetNumber);
+            this.channel = builder.channel;
+            this.opCode = builder.opCode;
+            this.payload = builder.payload;
+        }
+
+        private static byte getRawChannel(final Channel channel) {
+            switch (channel) {
+                case Authentication: // fall through
+                case ProtobufCommand:
+                    return CHANNEL_PROTOBUF;
+                case Data:
+                    return CHANNEL_DATA;
+                case Activity:
+                    return CHANNEL_ACTIVITY;
+                default:
+                    LOG.warn("getRawChannel(): unable to get raw channel value for channel '{}'", channel);
+                    return CHANNEL_UNKNOWN;
+            }
+        }
+
+        private static Channel getChannelFromRaw(final int rawChannel) {
+            switch (rawChannel) {
+                case CHANNEL_PROTOBUF:
+                    return Channel.ProtobufCommand;
+                case CHANNEL_ACTIVITY:
+                    return Channel.Activity;
+                case CHANNEL_DATA:
+                    return Channel.Data;
+                default:
+                    LOG.warn("getChannelFromRaw(): unknown raw channel {}", rawChannel);
+                    return Channel.Unknown;
+            }
+        }
+
+        public static int getOpCodeForChannel(final Channel channel) {
+            switch (channel) {
+                case Authentication:
+                case Data:
+                    return OPCODE_SEND_PLAINTEXT;
+                case ProtobufCommand:
+                case Activity:
+                    return OPCODE_SEND_ENCRYPTED;
+                default:
+                    LOG.warn("getOpCodeForChannel(): conversion for channel {} unknown", channel);
+                    return OPCODE_UNKNOWN;
+            }
+        }
+
+        public static XiaomiSppPacketV2 decodePacketPayload(int sequenceNumber, byte[] payloadBytes) {
+            if (payloadBytes == null || payloadBytes.length < 2) {
+                LOG.error("DataPacket.decodePacketPayload(): not enough bytes to decode data packet payload");
+                return null;
+            }
+
+            final ByteBuffer buffer = ByteBuffer.wrap(payloadBytes).order(ByteOrder.LITTLE_ENDIAN);
+            final int rawChannel = buffer.get() & 0xf;
+            final int opCode = buffer.get() & 0xff;
+            final byte[] payload = new byte[buffer.remaining()];
+            buffer.get(payload);
+
+            return new Builder()
+                    .setSequenceNumber(sequenceNumber)
+                    .setChannel(getChannelFromRaw(rawChannel))
+                    .setOpCode(opCode)
+                    .setPayload(payload)
+                    .build();
+        }
+
+        @Override
+        protected byte[] getPacketPayloadBytes(XiaomiAuthService authService) {
+            final ByteBuffer buffer = ByteBuffer.allocate(2 + payload.length);
+            buffer.put((byte) (getRawChannel(this.channel) & 0xf));
+            buffer.put((byte) (opCode & 0xff));
+            buffer.put(opCode == OPCODE_SEND_ENCRYPTED ? authService.encryptV2(payload) : payload);
+            return buffer.array();
+        }
+
+        public Channel getChannel() {
+            return channel;
+        }
+
+        public byte[] getPayloadBytes(final XiaomiAuthService authService) {
+            if (this.opCode == OPCODE_SEND_ENCRYPTED) {
+                return authService.decryptV2(this.payload);
+            }
+
+            return this.payload;
+        }
+    }
+
+    protected XiaomiSppPacketV2(final int packetType, final int sequenceNumber) {
+        this.packetType = packetType;
+        this.sequenceNumber = sequenceNumber;
+    }
+
+    public static SessionConfigPacket.Builder newSessionConfigPacketBuilder() {
+        return new SessionConfigPacket.Builder();
+    }
+
+    public static DataPacket.Builder newDataPacketBuilder() {
+        return new DataPacket.Builder();
+    }
+
+    public int getPacketType() {
+        return this.packetType;
+    }
+
+    public int getSequenceNumber() {
+        return this.sequenceNumber;
+    }
+
+    private static int calculatePayloadChecksum(final byte[] payload) {
+        // consider moving to nodomain.freeyourgadget.gadgetbridge.util.CheckSums
+        // configuration: CRC-16/ARC (poly=0x8005, init=0, xorout=0. refin, refout)
+        int crc = 0;
+        for (final byte b : payload) {
+            for (int j = 0; j < 8; j++) {
+                crc <<= 1;
+                if ((((crc >> 16) & 1) ^ ((b >> j) & 1)) == 1)
+                    crc ^= 0x8005;
+            }
+        }
+        return (Integer.reverse(crc) >>> 16);
+    }
+
+    public byte[] encode(final XiaomiAuthService authService) {
+        final byte[] payloadBytes = getPacketPayloadBytes(authService);
+        final ByteBuffer buffer = ByteBuffer.allocate(8 + payloadBytes.length).order(ByteOrder.LITTLE_ENDIAN);
+        buffer.put(PACKET_PREAMBLE);
+        buffer.put((byte) (packetType & 0xf));
+        buffer.put((byte) (sequenceNumber & 0xff));
+        buffer.putShort((short) payloadBytes.length);
+        buffer.putShort((short) calculatePayloadChecksum(payloadBytes));
+        buffer.put(payloadBytes);
+        return buffer.array();
+    }
+
+    public static XiaomiSppPacketV2 decode(final byte[] packetBytes) {
+        if (packetBytes.length < 8) {
+            // caller should have checked if a full packet is in the given buffer
+            LOG.warn("decode(): at least 8 bytes required, got {}", packetBytes.length);
+            return null;
+        }
+
+        final ByteBuffer packetBuffer = ByteBuffer.wrap(packetBytes).order(ByteOrder.LITTLE_ENDIAN);
+
+        // verify packet preamble
+        {
+            final byte[] preamble = new byte[PACKET_PREAMBLE.length];
+            packetBuffer.get(preamble);
+            if (!Arrays.equals(PACKET_PREAMBLE, preamble)) {
+                LOG.error("decode(): packet header mismatch: expected {}, got {}", GB.hexdump(PACKET_PREAMBLE), GB.hexdump(preamble));
+                return null;
+            }
+        }
+
+        final int packetType, sequenceNumber, payloadLength, givenChecksum;
+        final byte[] payloadBytes;
+
+        // extract header fields and verify all bytes present
+        {
+            final byte b = packetBuffer.get(); // flags and packet type
+            // TODO process flags
+            packetType = b & 0xf;
+            sequenceNumber = packetBuffer.get() & 0xff;
+            payloadLength = packetBuffer.getShort() & 0xffff;
+            givenChecksum = packetBuffer.getShort() & 0xffff;
+
+            if (packetBuffer.remaining() < payloadLength) {
+                LOG.error("decode(): expected at least {} bytes in buffer, got {} (missing {} bytes to complete packet)",
+                        payloadLength + 8,
+                        packetBytes.length,
+                        payloadLength - packetBuffer.remaining());
+                return null;
+            }
+        }
+
+        // get payload and verify checksum
+        {
+            payloadBytes = new byte[payloadLength];
+            packetBuffer.get(payloadBytes);
+            final int calculatedChecksum = calculatePayloadChecksum(payloadBytes);
+
+            if (calculatedChecksum != givenChecksum) {
+                LOG.error("decode(): payload checksum mismatch (given {} != calculated {})",
+                        givenChecksum,
+                        calculatedChecksum);
+                return null;
+            }
+        }
+
+        final XiaomiSppPacketV2 decodedPacket;
+
+        switch (packetType) {
+            case PACKET_TYPE_SESSION_CONFIG:
+                decodedPacket = SessionConfigPacket.decodePayloadBytes(sequenceNumber, payloadBytes);
+                break;
+            case PACKET_TYPE_DATA:
+                decodedPacket = DataPacket.decodePacketPayload(sequenceNumber, payloadBytes);
+                break;
+            case PACKET_TYPE_ACK:
+                decodedPacket = new AckPacket.Builder()
+                        .setSequenceNumber(sequenceNumber)
+                        .build();
+                break;
+            default:
+                LOG.warn("decode(): unhandled packet type {}", packetType);
+                decodedPacket = null;
+                break;
+        }
+
+        return decodedPacket;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiSppProtocolV1.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiSppProtocolV1.java
new file mode 100644
index 0000000..a3efd27
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiSppProtocolV1.java
@@ -0,0 +1,120 @@
+/*  Copyright (C) 2024 Yoran Vulker
+
+    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 <http://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Arrays;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+import static nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.XiaomiSppPacketV1.OPCODE_SEND;
+import static nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.XiaomiSppPacketV1.PACKET_PREAMBLE;
+import static nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.XiaomiSppPacketV1.getDataTypeForChannel;
+
+public class XiaomiSppProtocolV1 extends AbstractXiaomiSppProtocol {
+    private static final Logger LOG = LoggerFactory.getLogger(XiaomiSppProtocolV1.class);
+
+    private final XiaomiSppSupport support;
+    private final AtomicInteger frameCounter = new AtomicInteger(0);
+    private final AtomicInteger encryptionCounter = new AtomicInteger(0);
+
+    public XiaomiSppProtocolV1(XiaomiSppSupport support) {
+        this.support = support;
+    }
+
+    @Override
+    public int findNextPacketOffset(byte[] buffer) {
+        for (int i = 1; i < buffer.length; i++) {
+            // just check for the first byte, the processPacket method checks the full magic
+            if (buffer[i] == PACKET_PREAMBLE[0]) {
+                return i;
+            }
+        }
+
+        return -1;
+    }
+
+    @Override
+    public ParseResult processPacket(byte[] buffer) {
+        if (buffer.length < 11) {
+            LOG.debug("processPacket(): not enough bytes in rx buffer to decode packet header");
+            return new ParseResult(ParseResult.Status.Incomplete);
+        }
+
+        final ByteBuffer headerBuffer = ByteBuffer.wrap(buffer).order(ByteOrder.LITTLE_ENDIAN);
+        final int packetSize;
+
+        // verify preamble
+        {
+            byte[] preamble = new byte[PACKET_PREAMBLE.length];
+            headerBuffer.get(preamble);
+
+            if (!Arrays.equals(PACKET_PREAMBLE, preamble)) {
+                LOG.debug("processPacket(): header mismatch, expected {}, got {}",
+                        GB.hexdump(PACKET_PREAMBLE),
+                        GB.hexdump(preamble));
+                return new ParseResult(ParseResult.Status.Invalid);
+            }
+        }
+
+        // verify packet size
+        {
+            headerBuffer.getShort(); // skip flags and channel ID
+            int payloadSize = headerBuffer.getShort() & 0xffff;
+            packetSize = payloadSize + 8; // payload size includes payload header
+
+            if (buffer.length < packetSize) {
+                LOG.debug("processPacket(): received {}, missing {}/{} packet bytes",
+                        buffer.length,
+                        packetSize - buffer.length,
+                        packetSize);
+                return new ParseResult(ParseResult.Status.Incomplete);
+            }
+
+            LOG.debug("processPacket(): all bytes for packet of {} bytes in buffer", packetSize);
+        }
+
+        XiaomiSppPacketV1 receivedPacket = XiaomiSppPacketV1.decode(buffer);
+
+        if (receivedPacket == null) {
+            LOG.debug("processPacket(): decoded packet is null");
+            return new ParseResult(ParseResult.Status.Invalid);
+        }
+
+        LOG.debug("processPacket(): Packet received: {}", receivedPacket);
+        support.onPacketReceived(receivedPacket.getChannel(), receivedPacket.getDecryptedPayload(support.getAuthService()));
+        // TODO send response if requested by device
+        return new ParseResult(ParseResult.Status.Complete, packetSize);
+    }
+
+    @Override
+    public byte[] encodePacket(XiaomiChannelHandler.Channel channel, byte[] data) {
+        return XiaomiSppPacketV1.newBuilder()
+                .channel(channel)
+                .opCode(OPCODE_SEND)
+                .frameSerial(frameCounter.getAndIncrement())
+                .dataType(getDataTypeForChannel(channel))
+                .payload(data)
+                .build()
+                .encode(support.getAuthService(), encryptionCounter);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiSppProtocolV2.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiSppProtocolV2.java
new file mode 100644
index 0000000..a937ab1
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiSppProtocolV2.java
@@ -0,0 +1,150 @@
+/*  Copyright (C) 2024 Yoran Vulker
+
+    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 <http://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Arrays;
+import java.util.Locale;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import nodomain.freeyourgadget.gadgetbridge.service.btbr.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+import static nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.XiaomiSppPacketV2.PACKET_PREAMBLE;
+import static nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.XiaomiSppPacketV2.PACKET_TYPE_ACK;
+import static nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.XiaomiSppPacketV2.PACKET_TYPE_DATA;
+import static nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.XiaomiSppPacketV2.PACKET_TYPE_SESSION_CONFIG;
+
+public class XiaomiSppProtocolV2 extends AbstractXiaomiSppProtocol {
+    private static final Logger LOG = LoggerFactory.getLogger(XiaomiSppProtocolV2.class);
+
+    private final AtomicInteger packetSequenceCounter = new AtomicInteger(0);
+    private final XiaomiSppSupport support;
+
+    public XiaomiSppProtocolV2(final XiaomiSppSupport support) {
+        this.support = support;
+    }
+
+    private void sendAck(final int sequenceNumber) {
+        final TransactionBuilder b = support.commsSupport.createTransactionBuilder(String.format(Locale.ROOT, "send ack for %d", sequenceNumber));
+        b.write(new XiaomiSppPacketV2.AckPacket.Builder()
+                .setSequenceNumber(sequenceNumber)
+                .build()
+                .encode(null));
+        b.queue(support.commsSupport.getQueue());
+    }
+
+    @Override
+    public int findNextPacketOffset(byte[] buffer) {
+        for (int i = 1; i < buffer.length; i++) {
+            if (buffer[i] == PACKET_PREAMBLE[0])
+                return i;
+        }
+
+        return -1;
+    }
+
+    @Override
+    public ParseResult processPacket(byte[] rxBuf) {
+        if (rxBuf.length < 8) {
+            LOG.debug("processPacket(): not enough bytes in buffer to process packet (got {} of required {} bytes)",
+                    rxBuf.length,
+                    8);
+            return new ParseResult(ParseResult.Status.Incomplete);
+        }
+
+        final ByteBuffer buffer = ByteBuffer.wrap(rxBuf).order(ByteOrder.LITTLE_ENDIAN);
+        final byte[] headerMagic = new byte[PACKET_PREAMBLE.length];
+        buffer.get(headerMagic);
+
+        if (!Arrays.equals(PACKET_PREAMBLE, headerMagic)) {
+            LOG.warn("processPacket(): invalid header magic (expected {}, got {})",
+                    GB.hexdump(PACKET_PREAMBLE),
+                    GB.hexdump(headerMagic));
+            return new ParseResult(ParseResult.Status.Invalid);
+        }
+
+        buffer.get(); // flags and packet type
+        buffer.get(); // packet sequence number
+        final int packetSize = 8 + (buffer.getShort() & 0xffff);
+        buffer.getShort(); // checksum
+
+        if (rxBuf.length < packetSize) {
+            LOG.debug("processPacket(): missing {} bytes (got {}/{} bytes)",
+                    packetSize - rxBuf.length,
+                    rxBuf.length,
+                    packetSize);
+            return new ParseResult(ParseResult.Status.Incomplete);
+        }
+
+        final XiaomiSppPacketV2 decodedPacket = XiaomiSppPacketV2.decode(rxBuf);
+        if (decodedPacket != null) {
+            switch (decodedPacket.getPacketType()) {
+                case PACKET_TYPE_SESSION_CONFIG:
+                    // TODO handle device's session config
+                    LOG.info("Received session config, opcode={}", ((XiaomiSppPacketV2.SessionConfigPacket)decodedPacket).getOpCode());
+                    support.getAuthService().startEncryptedHandshake();
+                    break;
+                case PACKET_TYPE_DATA:
+                    XiaomiSppPacketV2.DataPacket dataPacket = (XiaomiSppPacketV2.DataPacket) decodedPacket;
+                    try {
+                        support.onPacketReceived(dataPacket.getChannel(), dataPacket.getPayloadBytes(support.getAuthService()));
+                    } catch (final Exception ex) {
+                        LOG.error("Exception while handling received packet", ex);
+                    }
+                    // TODO: only directly ack protobuf packets, bulk ack others
+                    sendAck(decodedPacket.getSequenceNumber());
+                    break;
+                case PACKET_TYPE_ACK:
+                    LOG.debug("receive ack for packet {}", decodedPacket.getSequenceNumber());
+                    break;
+                default:
+                    LOG.warn("Unhandled packet with type {} (decoded type {})", decodedPacket.getPacketType(), decodedPacket.getClass().getSimpleName());
+                    break;
+            }
+        }
+
+        return new ParseResult(ParseResult.Status.Complete, packetSize);
+    }
+
+    @Override
+    public boolean initializeSession() {
+        final TransactionBuilder builder = support.commsSupport.createTransactionBuilder("send session config");
+        builder.write(XiaomiSppPacketV2.newSessionConfigPacketBuilder()
+                .setOpCode(XiaomiSppPacketV2.SessionConfigPacket.OPCODE_START_SESSION_REQUEST)
+                .setSequenceNumber(0)
+                .build()
+                .encode(null));
+        builder.queue(support.commsSupport.getQueue());
+        return false;
+    }
+
+    @Override
+    public byte[] encodePacket(XiaomiChannelHandler.Channel channel, byte[] payloadBytes) {
+        return XiaomiSppPacketV2.newDataPacketBuilder()
+                .setChannel(channel)
+                .setSequenceNumber(packetSequenceCounter.getAndIncrement())
+                .setOpCode(XiaomiSppPacketV2.DataPacket.getOpCodeForChannel(channel))
+                .setPayload(payloadBytes)
+                .build()
+                .encode(support.getAuthService());
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiSppSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiSppSupport.java
new file mode 100644
index 0000000..66c225c
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiSppSupport.java
@@ -0,0 +1,303 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo, Yoran Vulker
+
+    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 <http://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi;
+
+import static nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.XiaomiSppPacketV1.DATA_TYPE_PLAIN;
+import static nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.XiaomiSppPacketV1.OPCODE_READ;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothSocket;
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+
+import androidx.annotation.Nullable;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdateDeviceInfo;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.proto.xiaomi.XiaomiProto;
+import nodomain.freeyourgadget.gadgetbridge.service.btbr.AbstractBTBRDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.btbr.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.btbr.actions.PlainAction;
+import nodomain.freeyourgadget.gadgetbridge.service.btbr.actions.SetDeviceStateAction;
+import nodomain.freeyourgadget.gadgetbridge.service.btbr.actions.SetProgressAction;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.XiaomiChannelHandler.Channel;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class XiaomiSppSupport extends XiaomiConnectionSupport {
+    private static final Logger LOG = LoggerFactory.getLogger(XiaomiSppSupport.class);
+
+    AbstractBTBRDeviceSupport commsSupport = new AbstractBTBRDeviceSupport(LOG) {
+        @Override
+        public boolean useAutoConnect() {
+            return mXiaomiSupport.useAutoConnect();
+        }
+
+        @Override
+        public void onSocketRead(byte[] data) {
+            XiaomiSppSupport.this.onSocketRead(data);
+        }
+
+        @Override
+        protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
+            // FIXME unsetDynamicState unsets the fw version, which causes problems..
+            if (getDevice().getFirmwareVersion() == null) {
+                getDevice().setFirmwareVersion(mXiaomiSupport.getCachedFirmwareVersion() != null ?
+                        mXiaomiSupport.getCachedFirmwareVersion() :
+                        "N/A");
+            }
+
+            builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext()));
+            builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.AUTHENTICATING, getContext()));
+            builder.write(XiaomiSppPacketV1.newBuilder()
+                    .channel(Channel.Version)
+                    .needsResponse(true)
+                    .opCode(OPCODE_READ)
+                    .dataType(DATA_TYPE_PLAIN)
+                    .frameSerial(0)
+                    .build()
+                    .encode(null, null));
+            builder.add(new PlainAction() {
+                @Override
+                public boolean run(BluetoothSocket socket) {
+                    mVersionResponseTimeoutHandler.postDelayed(new VersionTimeoutRunnable(), 5000L);
+                    return true;
+                }
+            });
+
+            return builder;
+        }
+
+        @Override
+        protected UUID getSupportedService() {
+            return XiaomiUuids.UUID_SERVICE_SERIAL_PORT_PROFILE;
+        }
+
+        @Override
+        public void dispose() {
+            mXiaomiSupport.onDisconnect();
+            super.dispose();
+        }
+    };
+
+    ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+    private final XiaomiSupport mXiaomiSupport;
+    private final Map<Channel, XiaomiChannelHandler> mChannelHandlers = new HashMap<>();
+    private final Handler mVersionResponseTimeoutHandler = new Handler(Looper.getMainLooper());
+    private AbstractXiaomiSppProtocol mProtocol = new XiaomiSppProtocolV1(this);
+
+    public XiaomiSppSupport(final XiaomiSupport xiaomiSupport) {
+        this.mXiaomiSupport = xiaomiSupport;
+
+        mChannelHandlers.put(Channel.Version, this::handleVersionPacket);
+        mChannelHandlers.put(Channel.ProtobufCommand, this.mXiaomiSupport::handleCommandBytes);
+        mChannelHandlers.put(Channel.Activity, this.mXiaomiSupport.getHealthService().getActivityFetcher()::addChunk);
+    }
+
+    @Override
+    public void setContext(GBDevice device, BluetoothAdapter adapter, Context context) {
+        this.commsSupport.setContext(device, adapter, context);
+    }
+
+    @Override
+    public boolean connect() {
+        return commsSupport.connect();
+    }
+
+    @Override
+    public void dispose() {
+        commsSupport.dispose();
+    }
+
+    protected XiaomiAuthService getAuthService() {
+        return mXiaomiSupport.getAuthService();
+    }
+
+    @Override
+    public void onUploadProgress(final int textRsrc, final int progressPercent, final boolean ongoing) {
+        try {
+            final TransactionBuilder builder = commsSupport.createTransactionBuilder("send data upload progress");
+            builder.add(new SetProgressAction(
+                    commsSupport.getContext().getString(textRsrc),
+                    ongoing,
+                    progressPercent,
+                    commsSupport.getContext()
+            ));
+            builder.queue(commsSupport.getQueue());
+        } catch (final Exception e) {
+            LOG.error("Failed to update progress notification", e);
+        }
+    }
+
+    @Override
+    public void runOnQueue(String taskName, Runnable runnable) {
+        if (commsSupport == null) {
+            LOG.error("commsSupport is null, unable to queue task");
+            return;
+        }
+
+        final TransactionBuilder b = commsSupport.createTransactionBuilder("run task " + taskName + " on queue");
+        b.add(new PlainAction() {
+            @Override
+            public boolean run(BluetoothSocket socket) {
+                runnable.run();
+                return true;
+            }
+        });
+        b.queue(commsSupport.getQueue());
+    }
+
+    private void skipBuffer(int newStart) {
+        final byte[] bufferState = buffer.toByteArray();
+        buffer.reset();
+
+        if (newStart < 0) {
+            newStart = bufferState.length;
+        }
+
+        if (newStart >= bufferState.length) {
+            return;
+        }
+
+        buffer.write(bufferState, newStart, bufferState.length - newStart);
+    }
+
+    private void processBuffer() {
+        boolean shouldProcess = true;
+        while (shouldProcess) {
+            final byte[] bufferState = buffer.toByteArray();
+            final AbstractXiaomiSppProtocol.ParseResult parseResult = mProtocol.processPacket(bufferState);
+            LOG.debug("processBuffer(): protocol.processPacket() returned status {}", parseResult.status);
+            int skipBytes;
+
+            switch (parseResult.status) {
+                case Incomplete:
+                    skipBytes = 0;
+                    shouldProcess = false;
+                    break;
+                case Complete:
+                    skipBytes = parseResult.packetSize;
+                    break;
+                case Invalid:
+                    skipBytes = mProtocol.findNextPacketOffset(bufferState);
+                    if (skipBytes < 0) {
+                        skipBytes = bufferState.length;
+                    }
+                    break;
+                default:
+                    throw new IllegalStateException(String.format("Unhandled parse state %s", parseResult.status));
+            }
+
+            if (skipBytes > 0) {
+                LOG.debug("processBuffer(): skipping {} bytes for state {}", skipBytes, parseResult.status);
+                skipBuffer(skipBytes);
+            }
+        }
+    }
+
+    public void onSocketRead(byte[] data) {
+        try {
+            buffer.write(data);
+        } catch (IOException ex) {
+            LOG.error("Exception while writing buffer: ", ex);
+        }
+
+        processBuffer();
+    }
+
+    protected void onPacketReceived(final Channel channel, final byte[] payload) {
+        final XiaomiChannelHandler handler = mChannelHandlers.get(channel);
+        if (handler != null) {
+            handler.handle(payload);
+        } else {
+            LOG.warn("Unhandled SppPacket on channel {}", channel);
+        }
+    }
+
+    @Override
+    public void sendCommand(final String taskName, final XiaomiProto.Command command) {
+        try {
+            final TransactionBuilder builder = this.commsSupport.createTransactionBuilder("send " + taskName);
+            sendCommand(builder, command);
+            builder.queue(this.commsSupport.getQueue());
+        } catch (final Exception ex) {
+            LOG.error("Caught unexpected exception while sending command, device may not have been informed!", ex);
+        }
+    }
+
+    public void sendCommand(final TransactionBuilder builder, final XiaomiProto.Command command) {
+        LOG.debug("sendCommand(): encoded command for task '{}': {}", builder.getTransaction().getTaskName(), GB.hexdump(command.toByteArray()));
+        if (command.getType() == XiaomiAuthService.COMMAND_TYPE) {
+            builder.write(mProtocol.encodePacket(Channel.Authentication, command.toByteArray()));
+        } else {
+            builder.write(mProtocol.encodePacket(Channel.ProtobufCommand, command.toByteArray()));
+        }
+        // do not queue here, that's the job of the caller
+    }
+
+    public void sendDataChunk(final String taskName, final byte[] chunk, @Nullable final XiaomiCharacteristic.SendCallback callback) {
+        LOG.debug("sendDataChunk(): encoded data chunk for task '{}': {}", taskName, GB.hexdump(chunk));
+        this.commsSupport.createTransactionBuilder("send " + taskName)
+            .write(mProtocol.encodePacket(Channel.Data, chunk))
+            .queue(commsSupport.getQueue());
+
+        if (callback != null) {
+            // callback puts a SetProgressAction onto the queue
+            callback.onSend();
+        }
+    }
+
+    private void handleVersionPacket(final byte[] payloadBytes) {
+        // remove timeout actions from handler
+        mVersionResponseTimeoutHandler.removeCallbacksAndMessages(null);
+
+        if (payloadBytes != null && payloadBytes.length > 0) {
+            LOG.debug("Received SPP protocol version: {}", GB.hexdump(payloadBytes));
+
+            // show in details
+            final GBDeviceEventUpdateDeviceInfo event = new GBDeviceEventUpdateDeviceInfo("SPP_PROTOCOL: ", GB.hexdump(payloadBytes));
+            mXiaomiSupport.evaluateGBDeviceEvent(event);
+
+            // TODO handle different protocol versions
+            if (payloadBytes[0] >= 2) {
+                LOG.info("handleVersionPacket(): detected protocol version higher than 2, switching protocol");
+                mProtocol = new XiaomiSppProtocolV2(this);
+            }
+        }
+
+        if (mProtocol.initializeSession()) {
+            mXiaomiSupport.getAuthService().startEncryptedHandshake();
+        }
+    }
+
+    class VersionTimeoutRunnable implements Runnable {
+        @Override
+        public void run() {
+            LOG.warn("SPP protocol version request timed out");
+            XiaomiSppSupport.this.handleVersionPacket(new byte[0]);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiSupport.java
new file mode 100644
index 0000000..ff25570
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiSupport.java
@@ -0,0 +1,760 @@
+/*  Copyright (C) 2023-2024 Andreas Shimokawa, Jos茅 Rebelo, Yoran Vulker
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi;
+
+
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_FORCE_CONNECTION_TYPE;
+
+import android.bluetooth.BluetoothAdapter;
+import android.content.Context;
+import android.location.Location;
+import android.net.Uri;
+import android.os.Handler;
+import android.widget.Toast;
+
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdatePreferences;
+import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.XiaomiCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.XiaomiFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
+import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.Contact;
+import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.Reminder;
+import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.WorldClock;
+import nodomain.freeyourgadget.gadgetbridge.proto.xiaomi.XiaomiProto;
+import nodomain.freeyourgadget.gadgetbridge.service.AbstractDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.activity.XiaomiActivityFileId;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.activity.XiaomiActivityParser;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.services.AbstractXiaomiService;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.services.XiaomiCalendarService;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.services.XiaomiDataUploadService;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.services.XiaomiHealthService;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.services.XiaomiMusicService;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.services.XiaomiNotificationService;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.services.XiaomiPhonebookService;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.services.XiaomiScheduleService;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.services.XiaomiSystemService;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.services.XiaomiWatchfaceService;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.services.XiaomiWeatherService;
+import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+public class XiaomiSupport extends AbstractDeviceSupport {
+    private static final Logger LOG = LoggerFactory.getLogger(XiaomiSupport.class);
+
+    private final XiaomiAuthService authService = new XiaomiAuthService(this);
+    private final XiaomiMusicService musicService = new XiaomiMusicService(this);
+    private final XiaomiHealthService healthService = new XiaomiHealthService(this);
+    private final XiaomiNotificationService notificationService = new XiaomiNotificationService(this);
+    private final XiaomiScheduleService scheduleService = new XiaomiScheduleService(this);
+    private final XiaomiWeatherService weatherService = new XiaomiWeatherService(this);
+    private final XiaomiSystemService systemService = new XiaomiSystemService(this);
+    private final XiaomiCalendarService calendarService = new XiaomiCalendarService(this);
+    private final XiaomiWatchfaceService watchfaceService = new XiaomiWatchfaceService(this);
+    private final XiaomiDataUploadService dataUploadService = new XiaomiDataUploadService(this);
+    private final XiaomiPhonebookService phonebookService = new XiaomiPhonebookService(this);
+
+    private String cachedFirmwareVersion = null;
+    private XiaomiConnectionSupport connectionSupport = null;
+
+    private final Map<Integer, AbstractXiaomiService> mServiceMap = new LinkedHashMap<Integer, AbstractXiaomiService>() {{
+        put(XiaomiAuthService.COMMAND_TYPE, authService);
+        put(XiaomiMusicService.COMMAND_TYPE, musicService);
+        put(XiaomiHealthService.COMMAND_TYPE, healthService);
+        put(XiaomiNotificationService.COMMAND_TYPE, notificationService);
+        put(XiaomiScheduleService.COMMAND_TYPE, scheduleService);
+        put(XiaomiWeatherService.COMMAND_TYPE, weatherService);
+        put(XiaomiSystemService.COMMAND_TYPE, systemService);
+        put(XiaomiCalendarService.COMMAND_TYPE, calendarService);
+        put(XiaomiWatchfaceService.COMMAND_TYPE, watchfaceService);
+        put(XiaomiDataUploadService.COMMAND_TYPE, dataUploadService);
+        put(XiaomiPhonebookService.COMMAND_TYPE, phonebookService);
+    }};
+
+    @Override
+    public boolean useAutoConnect() {
+        return true;
+    }
+
+    @Override
+    public void setAutoReconnect(boolean enabled) {
+        super.setAutoReconnect(enabled);
+        if (this.connectionSupport != null) {
+            this.connectionSupport.setAutoReconnect(enabled);
+        }
+    }
+
+    private DeviceCoordinator.ConnectionType getForcedConnectionTypeFromPrefs() {
+        final String connTypeAuto = getContext().getString(R.string.pref_force_connection_type_auto_value);
+        String connTypePref = getDevicePrefs().getString(PREF_FORCE_CONNECTION_TYPE, connTypeAuto);
+
+        if (getContext().getString(R.string.pref_force_connection_type_ble_value).equals(connTypePref))
+            return DeviceCoordinator.ConnectionType.BLE;
+
+        if (getContext().getString(R.string.pref_force_connection_type_bt_classic_value).equals(connTypePref))
+            return DeviceCoordinator.ConnectionType.BT_CLASSIC;
+
+        // either set to default, unknown option selected, or has not been set
+        return DeviceCoordinator.ConnectionType.BOTH;
+    }
+
+    private XiaomiConnectionSupport createConnectionSpecificSupport() {
+        DeviceCoordinator.ConnectionType connType = getCoordinator().getConnectionType();
+
+        if (connType == DeviceCoordinator.ConnectionType.BOTH) {
+            connType = getForcedConnectionTypeFromPrefs();
+        }
+
+        switch (connType) {
+            case BLE:
+            case BOTH:
+                return new XiaomiBleSupport(this);
+            case BT_CLASSIC:
+                return new XiaomiSppSupport(this);
+        }
+
+        LOG.error("Cannot create connection-specific support, unhanded {} connection type", connType);
+        return null;
+    }
+
+    public XiaomiConnectionSupport getConnectionSpecificSupport() {
+        if (connectionSupport == null) {
+            connectionSupport = createConnectionSpecificSupport();
+        }
+
+        return connectionSupport;
+    }
+
+    @Override
+    public boolean connect() {
+        if (getConnectionSpecificSupport() != null)
+            return getConnectionSpecificSupport().connect();
+
+        LOG.error("getConnectionSpecificSupport returned null, could not connect");
+        return false;
+    }
+
+    @Override
+    public void dispose() {
+        if (this.connectionSupport != null) {
+            XiaomiConnectionSupport connectionSupport = this.connectionSupport;
+            this.connectionSupport = null;
+            connectionSupport.dispose();
+        }
+    }
+
+    public void setContext(final GBDevice device, final BluetoothAdapter adapter, final Context context) {
+        // FIXME unsetDynamicState unsets the fw version, which causes problems..
+        if (device.getFirmwareVersion() != null) {
+            setCachedFirmwareVersion(device.getFirmwareVersion());
+        }
+
+        super.setContext(device, adapter, context);
+
+        for (AbstractXiaomiService service : mServiceMap.values()) {
+            service.setContext(context);
+        }
+
+        if (getConnectionSpecificSupport() != null) {
+            getConnectionSpecificSupport().setContext(device, adapter, context);
+        }
+    }
+
+    public String getCachedFirmwareVersion() {
+        return this.cachedFirmwareVersion;
+    }
+
+    public void setCachedFirmwareVersion(String version) {
+        this.cachedFirmwareVersion = version;
+    }
+
+    public void onDisconnect() {
+        // propagate disconnection to services
+        for (AbstractXiaomiService service : mServiceMap.values()) {
+            service.onDisconnect();
+        }
+    }
+
+    public void handleCommandBytes(final byte[] plainValue) {
+        LOG.debug("Got command: {}", GB.hexdump(plainValue));
+
+        final XiaomiProto.Command cmd;
+        try {
+            cmd = XiaomiProto.Command.parseFrom(plainValue);
+        } catch (final Exception e) {
+            LOG.error("Failed to parse bytes as protobuf command payload", e);
+            return;
+        }
+
+        final AbstractXiaomiService service = mServiceMap.get(cmd.getType());
+        if (service != null) {
+            service.handleCommand(cmd);
+            return;
+        }
+
+        LOG.warn("Unexpected watch command type {}", cmd.getType());
+    }
+
+    @Override
+    public void onSendConfiguration(final String config) {
+        final Prefs prefs = getDevicePrefs();
+
+        // Check if any of the services handles this config
+        for (final AbstractXiaomiService service : mServiceMap.values()) {
+            if (service.onSendConfiguration(config, prefs)) {
+                return;
+            }
+        }
+
+        LOG.warn("Unhandled config changed: {}", config);
+    }
+
+    @Override
+    public void onSetTime() {
+        systemService.setCurrentTime();
+
+        if (getCoordinator().supportsCalendarEvents()) {
+            // TODO this should not be done here
+            calendarService.syncCalendar();
+        }
+    }
+
+    @Override
+    public void onTestNewFunction() {
+        //sendCommand("test new function", 2, 29);
+        parseAllActivityFilesFromStorage();
+    }
+
+    @Override
+    public void onFindPhone(final boolean start) {
+        systemService.onFindPhone(start);
+    }
+
+    @Override
+    public void onFindDevice(final boolean start) {
+        systemService.onFindWatch(start);
+    }
+
+    @Override
+    public void onSetPhoneVolume(final float volume) {
+        musicService.onSetPhoneVolume(volume);
+    }
+
+    @Override
+    public void onSetGpsLocation(final Location location) {
+        healthService.onSetGpsLocation(location);
+    }
+
+    @Override
+    public void onSetReminders(final ArrayList<? extends Reminder> reminders) {
+        scheduleService.onSetReminders(reminders);
+    }
+
+    @Override
+    public void onSetWorldClocks(final ArrayList<? extends WorldClock> clocks) {
+        scheduleService.onSetWorldClocks(clocks);
+    }
+
+    @Override
+    public void onNotification(final NotificationSpec notificationSpec) {
+        notificationService.onNotification(notificationSpec);
+    }
+
+    @Override
+    public void onDeleteNotification(final int id) {
+        notificationService.onDeleteNotification(id);
+    }
+
+    @Override
+    public void onSetAlarms(final ArrayList<? extends Alarm> alarms) {
+        scheduleService.onSetAlarms(alarms);
+    }
+
+    @Override
+    public void onSetCallState(final CallSpec callSpec) {
+        notificationService.onSetCallState(callSpec);
+    }
+
+    @Override
+    public void onSetCannedMessages(final CannedMessagesSpec cannedMessagesSpec) {
+        notificationService.onSetCannedMessages(cannedMessagesSpec);
+    }
+
+    @Override
+    public void onSetMusicState(final MusicStateSpec stateSpec) {
+        musicService.onSetMusicState(stateSpec);
+    }
+
+    @Override
+    public void onSetMusicInfo(final MusicSpec musicSpec) {
+        musicService.onSetMusicInfo(musicSpec);
+    }
+
+    @Override
+    public void onInstallApp(final Uri uri) {
+        final XiaomiFWHelper fwHelper = new XiaomiFWHelper(uri, getContext());
+
+        if (!fwHelper.isValid()) {
+            LOG.warn("Uri {} is not valid", uri);
+            return;
+        }
+
+        if (fwHelper.isFirmware()) {
+            systemService.installFirmware(fwHelper);
+        } else if (fwHelper.isWatchface()) {
+            watchfaceService.installWatchface(fwHelper);
+        } else {
+            LOG.warn("Unknown fwhelper for {}", uri);
+        }
+    }
+
+    @Override
+    public void onAppInfoReq() {
+        watchfaceService.requestWatchfaceList();
+    }
+
+    @Override
+    public void onAppStart(final UUID uuid, boolean start) {
+        if (start) {
+            watchfaceService.setWatchface(uuid);
+        }
+    }
+
+    @Override
+    public void onAppDelete(final UUID uuid) {
+        watchfaceService.deleteWatchface(uuid);
+    }
+
+    @Override
+    public void onFetchRecordedData(final int dataTypes) {
+        healthService.onFetchRecordedData(dataTypes);
+    }
+
+    @Override
+    public void onHeartRateTest() {
+        healthService.onHeartRateTest();
+    }
+
+    @Override
+    public void onEnableRealtimeHeartRateMeasurement(final boolean enable) {
+        healthService.enableRealtimeStats(enable);
+    }
+
+    @Override
+    public void onEnableRealtimeSteps(final boolean enable) {
+        healthService.enableRealtimeStats(enable);
+    }
+
+    @Override
+    public void onEnableHeartRateSleepSupport(final boolean enable) {
+        healthService.setHeartRateConfig();
+    }
+
+    @Override
+    public void onSetHeartRateMeasurementInterval(final int seconds) {
+        healthService.setHeartRateConfig();
+    }
+
+    @Override
+    public void onAddCalendarEvent(final CalendarEventSpec calendarEventSpec) {
+        calendarService.onAddCalendarEvent(calendarEventSpec);
+    }
+
+    @Override
+    public void onDeleteCalendarEvent(final byte type, long id) {
+        calendarService.onDeleteCalendarEvent(type, id);
+    }
+
+    @Override
+    public void onSendWeather(final ArrayList<WeatherSpec> weatherSpecs) {
+        weatherService.onSendWeather(weatherSpecs);
+    }
+
+    @Override
+    public void onSetContacts(ArrayList<? extends Contact> contacts) {
+        //noinspection unchecked
+        phonebookService.setContacts((List<Contact>) contacts);
+    }
+
+    public XiaomiCoordinator getCoordinator() {
+        return (XiaomiCoordinator) gbDevice.getDeviceCoordinator();
+    }
+
+    protected void onAuthSuccess() {
+        LOG.info("onAuthSuccess");
+
+        getConnectionSpecificSupport().onAuthSuccess();
+
+        if (GBApplication.getPrefs().getBoolean("datetime_synconconnect", true)) {
+            systemService.setCurrentTime();
+        }
+
+        for (final AbstractXiaomiService service : mServiceMap.values()) {
+            service.initialize();
+        }
+    }
+
+    public void sendCommand(final String taskName, final XiaomiProto.Command command) {
+        getConnectionSpecificSupport().sendCommand(taskName, command);
+    }
+
+    public void sendCommand(final String taskName, final int type, final int subtype) {
+        sendCommand(
+                taskName,
+                XiaomiProto.Command.newBuilder()
+                        .setType(type)
+                        .setSubtype(subtype)
+                        .build()
+        );
+    }
+
+    public XiaomiAuthService getAuthService() {
+        return this.authService;
+    }
+
+    public XiaomiDataUploadService getDataUploadService() {
+        return this.dataUploadService;
+    }
+
+    public XiaomiHealthService getHealthService() {
+        return this.healthService;
+    }
+
+    @Override
+    public String customStringFilter(final String inputString) {
+        return StringUtils.replaceEach(inputString, EMOJI_SOURCE, EMOJI_TARGET);
+    }
+
+    boolean parsingActivityFilesFromStorage = false;
+
+    private void parseAllActivityFilesFromStorage() {
+        if (parsingActivityFilesFromStorage) {
+            GB.toast(getContext(), "Already parsing!", Toast.LENGTH_LONG, GB.ERROR);
+            return;
+        }
+
+        parsingActivityFilesFromStorage = true;
+
+        LOG.info("Parsing all activity files from storage");
+
+        final File[] activityFiles;
+        try {
+            final File externalFilesDir = getCoordinator().getWritableExportDirectory(getDevice());
+            final File exportDir = new File(externalFilesDir, "rawFetchOperations");
+
+            if (!exportDir.exists() || !exportDir.isDirectory()) {
+                LOG.error("export directory {} not found", exportDir);
+                GB.toast(getContext(), "export directory " + exportDir + " not found", Toast.LENGTH_LONG, GB.ERROR);
+                return;
+            }
+
+            activityFiles = exportDir.listFiles((dir, name) -> name.startsWith("xiaomi_"));
+            if (activityFiles == null) {
+                LOG.error("activityFiles is null for {}", exportDir);
+                GB.toast(getContext(), "activityFiles is null for " + exportDir, Toast.LENGTH_LONG, GB.ERROR);
+                return;
+            }
+            if (activityFiles.length == 0) {
+                LOG.error("No activity files found in {}", exportDir);
+                GB.toast(getContext(), "No activity files found in " + exportDir, Toast.LENGTH_LONG, GB.ERROR);
+                return;
+            }
+        } catch (final Exception e) {
+            LOG.error("Failed to parse from storage", e);
+            GB.toast(getContext(), "Failed to parse from storage", Toast.LENGTH_LONG, GB.ERROR, e);
+            return;
+        }
+
+        GB.toast(getContext(), "Check notification for progress", Toast.LENGTH_LONG, GB.INFO);
+        GB.updateTransferNotification("Parsing activity files", "...", true, 0, getContext());
+        final long[] lastNotificationUpdateTs = new long[]{System.currentTimeMillis()};
+
+        final Handler handler = new Handler(getContext().getMainLooper());
+        new Thread(() -> {
+            try {
+                int[] i = new int[]{0};
+                for (final File activityFile : activityFiles) {
+                    i[0]++;
+
+                    LOG.debug("Parsing {}", activityFile);
+
+                    final long now = System.currentTimeMillis();
+                    if (now - lastNotificationUpdateTs[0] > 1500L) {
+                        lastNotificationUpdateTs[0] = now;
+                        handler.post(() -> {
+                            GB.updateTransferNotification(
+                                    "Parsing activity files", "File " + i[0] + " of " + activityFiles.length,
+                                    true,
+                                    (i[0] * 100) / activityFiles.length, getContext()
+                            );
+                            ;
+                        });
+                    }
+
+                    // The logic below just replicates XiaomiActivityFileFetcher
+
+                    final byte[] data;
+                    try (InputStream in = new FileInputStream(activityFile)) {
+                        data = FileUtils.readAll(in, 999999);
+                    } catch (final IOException ioe) {
+                        LOG.error("Failed to read {}", activityFile, ioe);
+                        continue;
+                    }
+
+                    final byte[] fileIdBytes = Arrays.copyOfRange(data, 0, 7);
+                    final XiaomiActivityFileId fileId = XiaomiActivityFileId.from(fileIdBytes);
+
+                    final XiaomiActivityParser activityParser = XiaomiActivityParser.create(fileId);
+                    if (activityParser == null) {
+                        LOG.warn("Failed to find parser for {}", fileId);
+                        continue;
+                    }
+
+                    try {
+                        if (activityParser.parse(this, fileId, data)) {
+                            LOG.info("Successfully parsed {}", fileId);
+                        } else {
+                            LOG.warn("Failed to parse {}", fileId);
+                        }
+                    } catch (final Exception ex) {
+                        LOG.error("Exception while parsing {}", fileId, ex);
+                    }
+                }
+            } catch (final Exception e) {
+                LOG.error("Failed to parse from storage", e);
+            }
+
+            handler.post(() -> {
+                parsingActivityFilesFromStorage = false;
+                GB.updateTransferNotification("", "", false, 100, getContext());
+                GB.signalActivityDataFinish(getDevice());
+            });
+        }).start();
+    }
+
+    public void setFeatureSupported(final String featureKey, final boolean supported) {
+        LOG.debug("Setting feature {} -> {}", featureKey, supported ? "supported" : "not supported");
+        evaluateGBDeviceEvent(new GBDeviceEventUpdatePreferences(featureKey, supported));
+    }
+
+    private static final String[] EMOJI_SOURCE = new String[]{
+            "\uD83D\uDE0D", // 馃槏
+            "\uD83D\uDE18", // 馃槝
+            "\uD83D\uDE02", // 馃槀
+            "\uD83D\uDE0A", // 馃槉
+            "\uD83D\uDE0E", // 馃槑
+            "\uD83D\uDE09", // 馃槈
+            "\uD83D\uDC8B", // 馃拫
+            "\uD83D\uDC4D", // 馃憤
+            "\uD83E\uDD23", // 馃ぃ
+            "\uD83D\uDC95", // 馃挄
+            "\uD83D\uDE00", // 馃榾
+            "\uD83D\uDE04", // 馃槃
+            "\uD83D\uDE2D", // 馃槶
+            "\uD83E\uDD7A", // 馃ズ
+            "\uD83D\uDE4F", // 馃檹
+            "\uD83E\uDD70", // 馃グ
+            "\uD83E\uDD14", // 馃
+            "\uD83D\uDD25", // 馃敟
+            "\uD83D\uDE29", // 馃槱
+            "\uD83D\uDE14", // 馃様
+            "\uD83D\uDE01", // 馃榿
+            "\uD83D\uDC4C", // 馃憣
+            "\uD83D\uDE0F", // 馃槒
+            "\uD83D\uDE05", // 馃槄
+            "\uD83E\uDD0D", // 馃
+            "\uD83D\uDC94", // 馃挃
+            "\uD83D\uDE0C", // 馃槍
+            "\uD83D\uDE22", // 馃槩
+            "\uD83D\uDC99", // 馃挋
+            "\uD83D\uDC9C", // 馃挏
+            "\uD83C\uDFB6", // 馃幎
+            "\uD83D\uDE33", // 馃槼
+            "\uD83D\uDC96", // 馃挅
+            "\uD83D\uDE4C", // 馃檶
+            "\uD83D\uDCAF", // 馃挴
+            "\uD83D\uDE48", // 馃檲
+            "\uD83D\uDE0B", // 馃構
+            "\uD83D\uDE11", // 馃槕
+            "\uD83D\uDE34", // 馃槾
+            "\uD83D\uDE2A", // 馃槳
+            "\uD83D\uDE1C", // 馃槣
+            "\uD83D\uDE1B", // 馃槢
+            "\uD83D\uDE1D", // 馃槤
+            "\uD83D\uDE1E", // 馃槥
+            "\uD83D\uDE15", // 馃槙
+            "\uD83D\uDC97", // 馃挆
+            "\uD83D\uDC4F", // 馃憦
+            "\uD83D\uDE10", // 馃槓
+            "\uD83D\uDC49", // 馃憠
+            "\uD83D\uDC9B", // 馃挍
+            "\uD83D\uDC9E", // 馃挒
+            "\uD83D\uDCAA", // 馃挭
+            "\uD83C\uDF39", // 馃尮
+            "\uD83D\uDC80", // 馃拃
+            "\uD83D\uDE31", // 馃槺
+            "\uD83D\uDC98", // 馃挊
+            "\uD83E\uDD1F", // 馃
+            "\uD83D\uDE21", // 馃槨
+            "\uD83D\uDCF7", // 馃摲
+            "\uD83C\uDF38", // 馃尭
+            "\uD83D\uDE08", // 馃槇
+            "\uD83D\uDC48", // 馃憟
+            "\uD83C\uDF89", // 馃帀
+            "\uD83D\uDC81", // 馃拋
+            "\uD83D\uDE4A", // 馃檴
+            "\uD83D\uDC9A", // 馃挌
+            "\uD83D\uDE2B", // 馃槴
+            "\uD83D\uDE24", // 馃槫
+            "\uD83D\uDC93", // 馃挀
+            "\uD83C\uDF1A", // 馃寶
+            "\uD83D\uDC47", // 馃憞
+            "\uD83D\uDE07", // 馃槆
+            "\uD83D\uDC4A", // 馃憡
+            "\uD83D\uDC51", // 馃憫
+            "\uD83D\uDE13", // 馃槗
+            "\uD83D\uDE3B", // 馃樆
+            "\uD83D\uDD34", // 馃敶
+            "\uD83D\uDE25", // 馃槬
+            "\uD83E\uDD29", // 馃ぉ
+            "\uD83D\uDE1A", // 馃槡
+            "\uD83D\uDE37", // 馃樂
+            "\uD83D\uDC4B", // 馃憢
+            "\uD83D\uDCA5", // 馃挜
+            "\uD83E\uDD2D", // 馃き
+            "\uD83C\uDF1F", // 馃専
+            "\uD83E\uDD71", // 馃ケ
+            "\uD83D\uDCA9", // 馃挬
+            "\uD83D\uDE80", // 馃殌
+    };
+
+    private static final String[] EMOJI_TARGET = new String[]{
+            "陘�", // 馃槏
+            "陘�", // 馃槝
+            "陘�", // 馃槀
+            "陘�", // 馃槉
+            "陘�", // 馃槑
+            "陘�", // 馃槈
+            "陘�", // 馃拫
+            "陘�", // 馃憤
+            "陘�", // 馃ぃ
+            "陘�", // 馃挄
+            "陘�", // 馃榾
+            "陘�", // 馃槃
+            "陘�", // 馃槶
+            "陘�", // 馃ズ
+            "陘�", // 馃檹
+            "陘�", // 馃グ
+            "陘�", // 馃
+            "陘�", // 馃敟
+            "陘�", // 馃槱
+            "陘�", // 馃様
+            "陘�", // 馃榿
+            "陘�", // 馃憣
+            "陘�", // 馃槒
+            "陘�", // 馃槄
+            "陘�", // 馃
+            "陘�", // 馃挃
+            "陘�", // 馃槍
+            "陘�", // 馃槩
+            "陘�", // 馃挋
+            "陘�", // 馃挏
+            "陘�", // 馃幎
+            "陘�", // 馃槼
+            "陘�", // 馃挅
+            "陘�", // 馃檶
+            "陘�", // 馃挴
+            "陘�", // 馃檲
+            "陘�", // 馃構
+            "陘�", // 馃槕
+            "陘�", // 馃槾
+            "陘�", // 馃槳
+            "陘�", // 馃槣
+            "陘�", // 馃槢
+            "陘�", // 馃槤
+            "陘�", // 馃槥
+            "陘�", // 馃槙
+            "陘�", // 馃挆
+            "陘�", // 馃憦
+            "陘�", // 馃槓
+            "陘�", // 馃憠
+            "陘�", // 馃挍
+            "陘�", // 馃挒
+            "陘�", // 馃挭
+            "陘�", // 馃尮
+            "陘�", // 馃拃
+            "陘�", // 馃槺
+            "陘�", // 馃挊
+            "陘�", // 馃
+            "陙�", // 馃槨
+            "陙�", // 馃摲
+            "陙�", // 馃尭
+            "陙�", // 馃槇
+            "陙�", // 馃憟
+            "陙�", // 馃帀
+            "陙�", // 馃拋
+            "陙�", // 馃檴
+            "陙�", // 馃挌
+            "陙�", // 馃槴
+            "陙�", // 馃槫
+            "陙�", // 馃挀
+            "陙�", // 馃寶
+            "陙�", // 馃憞
+            "陙�", // 馃槆
+            "陙�", // 馃憡
+            "陙�", // 馃憫
+            "陙�", // 馃槗
+            "陙�", // 馃樆
+            "陙�", // 馃敶
+            "陙�", // 馃槬
+            "陙�", // 馃ぉ
+            "陙�", // 馃槡
+            "陙�", // 馃樂
+            "陙�", // 馃憢
+            "陙�", // 馃挜
+            "陙�", // 馃き
+            "陙�", // 馃専
+            "陙�", // 馃ケ
+            "陙�", // 馃挬
+            "陙�", // 馃殌
+    };
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiUuids.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiUuids.java
new file mode 100644
index 0000000..62534d7
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiUuids.java
@@ -0,0 +1,121 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi;
+
+import static nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport.BASE_UUID;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.UUID;
+
+public class XiaomiUuids {
+    public static final UUID UUID_SERVICE_SERIAL_PORT_PROFILE = UUID.fromString(String.format(BASE_UUID, "1101"));
+    public static final Map<UUID, XiaomiBleUuidSet> BLE_UUIDS = new LinkedHashMap<UUID, XiaomiBleUuidSet>() {{
+        // all encrypted devices seem to share the same characteristics
+        // Mi Band 8
+        // Redmi Watch 3 Active
+        // Xiaomi Watch S1 (Active)
+        // Redmi Smart Band 2
+        // Redmi Watch 2 Lite
+        put(UUID.fromString("0000fe95-0000-1000-8000-00805f9b34fb"), new XiaomiBleUuidSet(
+                true,
+                UUID.fromString("00000051-0000-1000-8000-00805f9b34fb"),
+                UUID.fromString("00000052-0000-1000-8000-00805f9b34fb"),
+                UUID.fromString("00000053-0000-1000-8000-00805f9b34fb"),
+                UUID.fromString("00000055-0000-1000-8000-00805f9b34fb")
+        ));
+
+        // Mi Watch Lite
+        // Redmi Watch
+        put(UUID.fromString("16186f00-0000-1000-8000-00807f9b34fb"), new XiaomiBleUuidSet(
+                false,
+                UUID.fromString("16186f01-0000-1000-8000-00807f9b34fb"),
+                UUID.fromString("16186f02-0000-1000-8000-00807f9b34fb"),
+                UUID.fromString("16186f03-0000-1000-8000-00807f9b34fb"),
+                UUID.fromString("16186f04-0000-1000-8000-00807f9b34fb")
+        ));
+
+        // Mi Smart Watch 4C
+        // Redmi Band
+        put(UUID.fromString("16187f00-0000-1000-8000-00807f9b34fb"), new XiaomiBleUuidSet(
+                false, // FIXME check
+                UUID.fromString("16187f02-0000-1000-8000-00807f9b34fb"),
+                UUID.fromString("16187f01-0000-1000-8000-00807f9b34fb"),
+                UUID.fromString("16187f03-0000-1000-8000-00807f9b34fb"),
+                UUID.fromString("16187f04-0000-1000-8000-00807f9b34fb")
+        ));
+
+        // Mi Watch (Color (Sport))
+        put(UUID.fromString("1314f000-1000-9000-7000-301291e21220"), new XiaomiBleUuidSet(
+                false,
+                UUID.fromString("1314f005-1000-9000-7000-301291e21220"),
+                UUID.fromString("1314f001-1000-9000-7000-301291e21220"),
+                UUID.fromString("1314f002-1000-9000-7000-301291e21220"),
+                UUID.fromString("1314f007-1000-9000-7000-301291e21220")
+        ));
+
+        // Mi Watch CN
+        put(UUID.fromString("7495fe00-a7f3-424b-92dd-4a006a3aef56"), new XiaomiBleUuidSet(
+                false, // FIXME check
+                UUID.fromString("74950002-a7f3-424b-92dd-4a006a3aef56"),
+                UUID.fromString("74950001-a7f3-424b-92dd-4a006a3aef56"),
+                UUID.fromString("74950003-a7f3-424b-92dd-4a006a3aef56"),
+                null
+        ));
+    }};
+
+    public static class XiaomiBleUuidSet {
+        private final boolean encrypted;
+        private final UUID characteristicCommandRead;
+        private final UUID characteristicCommandWrite;
+        private final UUID characteristicActivityData;
+        private final UUID characteristicDataUpload;
+
+        public XiaomiBleUuidSet(final boolean encrypted,
+                                final UUID characteristicCommandRead,
+                                final UUID characteristicCommandWrite,
+                                final UUID characteristicActivityData,
+                                final UUID characteristicDataUpload) {
+
+            this.encrypted = encrypted;
+            this.characteristicCommandRead = characteristicCommandRead;
+            this.characteristicCommandWrite = characteristicCommandWrite;
+            this.characteristicActivityData = characteristicActivityData;
+            this.characteristicDataUpload = characteristicDataUpload;
+        }
+
+        protected boolean isEncrypted() {
+            return encrypted;
+        }
+
+        protected UUID getCharacteristicCommandRead() {
+            return characteristicCommandRead;
+        }
+
+        protected UUID getCharacteristicCommandWrite() {
+            return characteristicCommandWrite;
+        }
+
+        protected UUID getCharacteristicActivityData() {
+            return characteristicActivityData;
+        }
+
+        protected UUID getCharacteristicDataUpload() {
+            return characteristicDataUpload;
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/activity/XiaomiActivityFileFetcher.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/activity/XiaomiActivityFileFetcher.java
new file mode 100644
index 0000000..688391c
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/activity/XiaomiActivityFileFetcher.java
@@ -0,0 +1,218 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo, Yoran Vulker
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.activity;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.OutputStream;
+import java.util.Arrays;
+import java.util.List;
+import java.util.PriorityQueue;
+import java.util.Queue;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.XiaomiPreferences;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.XiaomiSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.services.XiaomiHealthService;
+import nodomain.freeyourgadget.gadgetbridge.util.CheckSums;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class XiaomiActivityFileFetcher {
+    private static final Logger LOG = LoggerFactory.getLogger(XiaomiActivityFileFetcher.class);
+
+    private final XiaomiHealthService mHealthService;
+
+    private final Queue<XiaomiActivityFileId> mFetchQueue = new PriorityQueue<>();
+    private ByteArrayOutputStream mBuffer = new ByteArrayOutputStream();
+    private boolean isFetching = false;
+
+    private final Handler timeoutHandler = new Handler(Looper.getMainLooper());
+
+    public XiaomiActivityFileFetcher(final XiaomiHealthService healthService) {
+        this.mHealthService = healthService;
+    }
+
+    public void dispose() {
+        clearTimeout();
+    }
+
+    private void clearTimeout() {
+        this.timeoutHandler.removeCallbacksAndMessages(null);
+    }
+
+    private void setTimeout() {
+        // #4305 - Set the timeout in case the watch does not send the file
+        this.timeoutHandler.postDelayed(() -> {
+            LOG.warn("Timed out waiting for activity file with {} bytes in the buffer", mBuffer.size());
+            triggerNextFetch();
+        }, 5000L);
+    }
+
+    public void addChunk(final byte[] chunk) {
+        clearTimeout();
+
+        final int total = BLETypeConversions.toUint16(chunk, 0);
+        final int num = BLETypeConversions.toUint16(chunk, 2);
+
+        if (num == 1) {
+            // reset buffer
+            mBuffer = new ByteArrayOutputStream();
+        }
+
+        LOG.debug("Got activity chunk {}/{}", num, total);
+
+        mBuffer.write(chunk, 4, chunk.length - 4);
+
+        if (num != total) {
+            setTimeout();
+            return;
+        }
+
+        final byte[] data = mBuffer.toByteArray();
+        mBuffer = new ByteArrayOutputStream();
+
+        if (data.length < 13) {
+            LOG.warn("Activity data length of {} is too short", data.length);
+            // FIXME this may mess up the order.. maybe we should just abort
+            triggerNextFetch();
+            return;
+        }
+
+        final int arrCrc32 = CheckSums.getCRC32(data, 0, data.length - 4);
+        final int expectedCrc32 = BLETypeConversions.toUint32(data, data.length - 4);
+
+        if (arrCrc32 != expectedCrc32) {
+            LOG.warn(
+                    "Invalid activity data checksum: got {}, expected {}",
+                    String.format("%08X", arrCrc32),
+                    String.format("%08X", expectedCrc32)
+            );
+            // FIXME this may mess up the order.. maybe we should just abort
+            triggerNextFetch();
+            return;
+        }
+
+        if (data[7] != 0) {
+            LOG.warn(
+                    "Unexpected activity payload byte {} at position 7 - parsing might fail",
+                    String.format("0x%02X", data[7])
+            );
+        }
+
+        final byte[] fileIdBytes = Arrays.copyOfRange(data, 0, 7);
+        final XiaomiActivityFileId fileId = XiaomiActivityFileId.from(fileIdBytes);
+
+        dumpBytesToExternalStorage(fileId, data);
+
+        if (!XiaomiPreferences.keepActivityDataOnDevice(mHealthService.getSupport().getDevice())) {
+            LOG.debug("Acking recorded data {}", fileId);
+            // TODO is this too early?
+            mHealthService.ackRecordedData(fileId);
+        }
+
+        final XiaomiActivityParser activityParser = XiaomiActivityParser.create(fileId);
+        if (activityParser == null) {
+            LOG.warn("Failed to find parser for {}", fileId);
+            triggerNextFetch();
+            return;
+        }
+
+        try {
+            if (activityParser.parse(mHealthService.getSupport(), fileId, data)) {
+                LOG.info("Successfully parsed {}", fileId);
+            } else {
+                LOG.warn("Failed to parse {}", fileId);
+            }
+        } catch (final Exception ex) {
+            LOG.error("Exception while parsing {}", fileId, ex);
+        }
+
+        triggerNextFetch();
+    }
+
+    public void fetch(final List<XiaomiActivityFileId> fileIds) {
+        // #4305 - ensure unique files
+        for (final XiaomiActivityFileId fileId : fileIds) {
+            if (!mFetchQueue.contains(fileId)) {
+                mFetchQueue.add(fileId);
+            } else {
+                LOG.warn("Ignoring duplicated file {}", fileId);
+            }
+        }
+
+        if (!isFetching) {
+            // Currently not fetching anything, fetch the next
+            isFetching = true;
+            final XiaomiSupport support = mHealthService.getSupport();
+            final Context context = support.getContext();
+            GB.updateTransferNotification(context.getString(R.string.busy_task_fetch_activity_data), "", true, 0, context);
+            support.getDevice().setBusyTask(context.getString(R.string.busy_task_fetch_activity_data));
+            support.getDevice().sendDeviceUpdateIntent(support.getContext());
+            triggerNextFetch();
+        }
+    }
+
+    private void triggerNextFetch() {
+        clearTimeout();
+        mBuffer = new ByteArrayOutputStream();
+
+        final XiaomiActivityFileId fileId = mFetchQueue.poll();
+
+        if (fileId == null) {
+            LOG.debug("Nothing more to fetch");
+            isFetching = false;
+            mHealthService.getSupport().getDevice().unsetBusyTask();
+            GB.signalActivityDataFinish(mHealthService.getSupport().getDevice());
+            GB.updateTransferNotification(null, "", false, 100, mHealthService.getSupport().getContext());
+            mHealthService.getSupport().getDevice().sendDeviceUpdateIntent(mHealthService.getSupport().getContext());
+            return;
+        }
+
+        LOG.debug("Triggering next fetch for: {}", fileId);
+
+        setTimeout();
+
+        mHealthService.requestRecordedData(fileId);
+    }
+
+    protected void dumpBytesToExternalStorage(final XiaomiActivityFileId fileId, final byte[] bytes) {
+        try {
+            final GBDevice device = mHealthService.getSupport().getDevice();
+            final File exportDirectory = device.getDeviceCoordinator().getWritableExportDirectory(device);
+            final File targetDir = new File(exportDirectory, "rawFetchOperations");
+            targetDir.mkdirs();
+
+            final File outputFile = new File(targetDir, fileId.getFilename());
+
+            final OutputStream outputStream = new FileOutputStream(outputFile);
+            outputStream.write(bytes);
+            outputStream.close();
+        } catch (final Exception e) {
+            LOG.error("Failed to dump bytes to storage", e);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/activity/XiaomiActivityFileId.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/activity/XiaomiActivityFileId.java
new file mode 100644
index 0000000..ee6a1c3
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/activity/XiaomiActivityFileId.java
@@ -0,0 +1,276 @@
+/*  Copyright (C) 2023-2024 Andreas Shimokawa, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.activity;
+
+import androidx.annotation.NonNull;
+
+import org.apache.commons.lang3.builder.CompareToBuilder;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+
+import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
+
+public class XiaomiActivityFileId implements Comparable<XiaomiActivityFileId> {
+    private final Date timestamp;
+    private final int timezone;
+    private final int type;
+    private final int subtype;
+    private final int detailType;
+    private final int version;
+
+    public XiaomiActivityFileId(final Date timestamp,
+                                final int timezone,
+                                final int type,
+                                final int subtype,
+                                final int detailType,
+                                final int version) {
+        this.timestamp = timestamp;
+        this.timezone = timezone;
+        this.type = type;
+        this.subtype = subtype;
+        this.detailType = detailType;
+        this.version = version;
+    }
+
+    public Date getTimestamp() {
+        return timestamp;
+    }
+
+    public int getTimezone() {
+        return timezone;
+    }
+
+    public Type getType() {
+        return Type.fromCode(type);
+    }
+
+    public int getTypeCode() {
+        return type;
+    }
+
+    public Subtype getSubtype() {
+        return Subtype.fromCode(getType(), subtype);
+    }
+
+    public int getSubtypeCode() {
+        return subtype;
+    }
+
+    public DetailType getDetailType() {
+        return DetailType.fromCode(detailType);
+    }
+
+    public int getDetailTypeCode() {
+        return detailType;
+    }
+
+    public int getVersion() {
+        return version;
+    }
+
+    public byte[] toBytes() {
+        final ByteBuffer buf = ByteBuffer.allocate(7).order(ByteOrder.LITTLE_ENDIAN);
+
+        buf.putInt((int) (timestamp.getTime() / 1000));
+        buf.put((byte) timezone);
+        buf.put((byte) version);
+        buf.put((byte) (type << 7 | subtype << 2 | detailType));
+
+        return buf.array();
+    }
+
+    public static XiaomiActivityFileId from(final byte[] bytes) {
+        assert bytes.length == 7;
+
+        return from(ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN));
+    }
+
+    public static XiaomiActivityFileId from(final ByteBuffer buf) {
+        final int ts = buf.getInt();
+        final int tz = buf.get(); // 15 min blocks
+        final int version = buf.get();
+        final int flags = buf.get();
+        // bit 0 is type - 0 activity, 1 sports
+        final int type = (flags >> 7) & 1;
+        // bit 1 to 6 bits are subtype
+        //   for activity: activity, sleep, measurements, etc
+        //   for workout: workout type (8 freestyle)
+        final int subtype = (flags & 127) >> 2;
+        // bit 6 and 7 - 0 details, 1 summary
+        final int detailType = flags & 3;
+
+        return new XiaomiActivityFileId(new Date(ts * 1000L), tz, type, subtype, detailType, version);
+    }
+
+    @NonNull
+    @Override
+    public String toString() {
+        final Type typeName = Type.fromCode(type);
+        final Subtype subtypeName = Subtype.fromCode(typeName, subtype);
+        final DetailType detailTypeName = DetailType.fromCode(detailType);
+
+        return getClass().getSimpleName() + "{" +
+                "timestamp=" + DateTimeUtils.formatIso8601(timestamp) +
+                ", timezone=" + timezone +
+                ", type=" + (typeName + String.format("(0x%02X)", type)) +
+                ", subtype=" + (subtypeName + String.format("(0x%02X)", subtype)) +
+                ", detailType=" + (detailTypeName + String.format("(0x%02X)", detailType)) +
+                ", version=" + version +
+                "}";
+    }
+
+    @Override
+    public int compareTo(final XiaomiActivityFileId o) {
+        return new CompareToBuilder()
+                .append(timestamp, o.timestamp)
+                .append(timezone, o.timezone)
+                .append(type, o.type)
+                .append(subtype, o.subtype)
+                .append(getDetailType().getFetchOrder(), o.getDetailType().getFetchOrder())
+                .append(version, o.version)
+                .build();
+    }
+
+    public String getFilename() {
+        final SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd'T'HHmmss", Locale.US);
+
+        return String.format(
+                Locale.ROOT,
+                "xiaomi_%s_%02X_%02X_%02X_v%d.bin",
+                sdf.format(getTimestamp()),
+                getTypeCode(),
+                getSubtypeCode(),
+                getDetailTypeCode(),
+                getVersion()
+        );
+    }
+
+    public enum Type {
+        UNKNOWN(-1),
+        ACTIVITY(0),
+        SPORTS(1),
+        ;
+
+        private final int code;
+
+        Type(final int code) {
+            this.code = code;
+        }
+
+        public int getCode() {
+            return code;
+        }
+
+        public static Type fromCode(final int code) {
+            for (final Type type : values()) {
+                if (type.getCode() == code) {
+                    return type;
+                }
+            }
+            return UNKNOWN;
+        }
+    }
+
+    public enum Subtype {
+        UNKNOWN(Type.UNKNOWN, -1),
+        ACTIVITY_DAILY(Type.ACTIVITY, 0x00),
+        ACTIVITY_SLEEP_STAGES(Type.ACTIVITY, 0x03),
+        ACTIVITY_MANUAL_SAMPLES(Type.ACTIVITY, 0x06),
+        ACTIVITY_SLEEP(Type.ACTIVITY, 0x08),
+        SPORTS_OUTDOOR_RUNNING(Type.SPORTS, 0x01),
+        SPORTS_OUTDOOR_WALKING_V1(Type.SPORTS, 0x02),
+        SPORTS_TREADMILL(Type.SPORTS, 0x03),
+        SPORTS_OUTDOOR_CYCLING_V2(Type.SPORTS, 0x06),
+        SPORTS_INDOOR_CYCLING(Type.SPORTS, 0x07),
+        SPORTS_FREESTYLE(Type.SPORTS, 0x08),
+        SPORTS_POOL_SWIMMING(Type.SPORTS, 0x09),
+        SPORTS_HIIT(Type.SPORTS, 0x10),
+        SPORTS_ELLIPTICAL(Type.SPORTS, 0x0B),
+        SPORTS_ROWING(Type.SPORTS, 0x0D),
+        SPORTS_JUMP_ROPING(Type.SPORTS, 0x0E),
+        SPORTS_OUTDOOR_WALKING_V2(Type.SPORTS, 0x16),
+        SPORTS_OUTDOOR_CYCLING(Type.SPORTS, 0x17),
+        ;
+
+        private final Type type;
+        private final int code;
+
+        Subtype(final Type type, final int code) {
+            this.type = type;
+            this.code = code;
+        }
+
+        public int getCode() {
+            return code;
+        }
+
+        public static Subtype fromCode(final Type type, final int code) {
+            for (final Subtype subtype : values()) {
+                if (subtype.type == type && subtype.getCode() == code) {
+                    return subtype;
+                }
+            }
+            return UNKNOWN;
+        }
+    }
+
+    public enum DetailType {
+        UNKNOWN(-1),
+        DETAILS(0),
+        SUMMARY(0x01),
+        GPS_TRACK(0x02),
+        ;
+
+        private final int code;
+
+        DetailType(final int code) {
+            this.code = code;
+        }
+
+        public int getCode() {
+            return code;
+        }
+
+        public int getFetchOrder() {
+            // Fetch summary first, so we have the summary data for workouts
+            // before parsing the gps track
+            switch (this) {
+                case SUMMARY:
+                    return 0;
+                case DETAILS:
+                    return 1;
+                case GPS_TRACK:
+                    return 2;
+            }
+
+            return 3;
+        }
+
+        public static DetailType fromCode(final int code) {
+            for (final DetailType detailType : values()) {
+                if (detailType.getCode() == code) {
+                    return detailType;
+                }
+            }
+            return UNKNOWN;
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/activity/XiaomiActivityParser.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/activity/XiaomiActivityParser.java
new file mode 100644
index 0000000..801cd27
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/activity/XiaomiActivityParser.java
@@ -0,0 +1,132 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.activity;
+
+import androidx.annotation.Nullable;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+import de.greenrobot.dao.query.QueryBuilder;
+import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary;
+import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummaryDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.entities.User;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.XiaomiSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.activity.impl.ManualSamplesParser;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.activity.impl.DailyDetailsParser;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.activity.impl.DailySummaryParser;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.activity.impl.SleepDetailsParser;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.activity.impl.SleepStagesParser;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.activity.impl.WorkoutGpsParser;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.activity.impl.WorkoutSummaryParser;
+
+public abstract class XiaomiActivityParser {
+    private static final Logger LOG = LoggerFactory.getLogger(XiaomiActivityParser.class);
+
+    public abstract boolean parse(final XiaomiSupport support, final XiaomiActivityFileId fileId, final byte[] bytes);
+
+    protected BaseActivitySummary findOrCreateBaseActivitySummary(final DaoSession session,
+                                                                  final Device device,
+                                                                  final User user,
+                                                                  final XiaomiActivityFileId fileId) {
+        final BaseActivitySummaryDao summaryDao = session.getBaseActivitySummaryDao();
+        final QueryBuilder<BaseActivitySummary> qb = summaryDao.queryBuilder();
+        qb.where(BaseActivitySummaryDao.Properties.StartTime.eq(fileId.getTimestamp()));
+        qb.where(BaseActivitySummaryDao.Properties.DeviceId.eq(device.getId()));
+        qb.where(BaseActivitySummaryDao.Properties.UserId.eq(user.getId()));
+        final List<BaseActivitySummary> summaries = qb.build().list();
+        if (summaries.isEmpty()) {
+            final BaseActivitySummary summary = new BaseActivitySummary();
+            summary.setStartTime(fileId.getTimestamp());
+            summary.setDevice(device);
+            summary.setUser(user);
+
+            // These will be set later, once we parse the summary
+            summary.setEndTime(fileId.getTimestamp());
+            summary.setActivityKind(ActivityKind.UNKNOWN.getCode());
+
+            return summary;
+        }
+        if (summaries.size() > 1) {
+            LOG.warn("Found multiple summaries for {}", fileId);
+        }
+        return summaries.get(0);
+    }
+
+    @Nullable
+    public static XiaomiActivityParser create(final XiaomiActivityFileId fileId) {
+        switch (fileId.getType()) {
+            case ACTIVITY:
+                return createForActivity(fileId);
+            case SPORTS:
+                return createForSports(fileId);
+        }
+
+        LOG.warn("Unknown file type for {}", fileId);
+        return null;
+    }
+
+    private static XiaomiActivityParser createForActivity(final XiaomiActivityFileId fileId) {
+        assert fileId.getType() == XiaomiActivityFileId.Type.ACTIVITY;
+
+        switch (fileId.getSubtype()) {
+            case ACTIVITY_DAILY:
+                if (fileId.getDetailType() == XiaomiActivityFileId.DetailType.DETAILS) {
+                    return new DailyDetailsParser();
+                }
+                if (fileId.getDetailType() == XiaomiActivityFileId.DetailType.SUMMARY) {
+                    return new DailySummaryParser();
+                }
+
+                break;
+            case ACTIVITY_SLEEP_STAGES:
+                if (fileId.getDetailType() == XiaomiActivityFileId.DetailType.DETAILS) {
+                    return new SleepStagesParser();
+                }
+
+                break;
+            case ACTIVITY_MANUAL_SAMPLES:
+                if (fileId.getDetailType() == XiaomiActivityFileId.DetailType.DETAILS) {
+                    return new ManualSamplesParser();
+                }
+
+                break;
+            case ACTIVITY_SLEEP:
+                return new SleepDetailsParser();
+        }
+
+        return null;
+    }
+
+    private static XiaomiActivityParser createForSports(final XiaomiActivityFileId fileId) {
+        assert fileId.getType() == XiaomiActivityFileId.Type.SPORTS;
+
+        switch (fileId.getDetailType()) {
+            case SUMMARY:
+                return new WorkoutSummaryParser();
+            case GPS_TRACK:
+                return new WorkoutGpsParser();
+        }
+
+        return null;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/activity/impl/DailyDetailsParser.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/activity/impl/DailyDetailsParser.java
new file mode 100644
index 0000000..147b82b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/activity/impl/DailyDetailsParser.java
@@ -0,0 +1,198 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.activity.impl;
+
+import android.widget.Toast;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.Calendar;
+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.DeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.entities.User;
+import nodomain.freeyourgadget.gadgetbridge.entities.XiaomiActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.XiaomiSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.activity.XiaomiActivityFileId;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.activity.XiaomiActivityParser;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class DailyDetailsParser extends XiaomiActivityParser {
+    private static final Logger LOG = LoggerFactory.getLogger(DailyDetailsParser.class);
+
+    @Override
+    public boolean parse(final XiaomiSupport support, final XiaomiActivityFileId fileId, final byte[] bytes) {
+        final int version = fileId.getVersion();
+        final int headerSize;
+        switch (version) {
+            case 1:
+            case 2:
+                headerSize = 4;
+                break;
+            case 3:
+                headerSize = 5;
+                break;
+            case 4:
+                headerSize = 6;
+                break;
+            default:
+                LOG.warn("Unable to parse daily details version {}", fileId.getVersion());
+                return false;
+        }
+
+        final ByteBuffer buf = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN);
+        buf.limit(buf.limit() - 4); // discard crc at the end
+        buf.get(new byte[7]); // skip fileId bytes
+        final byte fileIdPadding = buf.get();
+        if (fileIdPadding != 0) {
+            LOG.warn("Expected 0 padding after fileId, got {} - parsing might fail", fileIdPadding);
+        }
+
+        final byte[] header = new byte[headerSize];
+        buf.get(header);
+
+        LOG.debug("Daily Details Header: {}", GB.hexdump(header));
+
+        final XiaomiComplexActivityParser complexParser = new XiaomiComplexActivityParser(header, buf);
+
+        final Calendar timestamp = Calendar.getInstance();
+        timestamp.setTime(fileId.getTimestamp());
+
+        final List<XiaomiActivitySample> samples = new ArrayList<>();
+        while (buf.position() < buf.limit()) {
+            complexParser.reset();
+
+            final XiaomiActivitySample sample = new XiaomiActivitySample();
+            sample.setTimestamp((int) (timestamp.getTimeInMillis() / 1000));
+
+            int includeExtraEntry = 0;
+            if (complexParser.nextGroup(16)) {
+                // TODO what's the first bit?
+
+                if (complexParser.hasSecond()) {
+                    includeExtraEntry = complexParser.get(1, 1);
+                }
+                if (complexParser.hasThird()) {
+                    sample.setSteps(complexParser.get(2, 14));
+                }
+            }
+
+            if (complexParser.nextGroup(8)) {
+                // TODO activity type?
+                if (complexParser.hasSecond()) {
+                    final int calories = complexParser.get(2, 6);
+                }
+            }
+
+            if (complexParser.nextGroup(8)) {
+                // TODO
+            }
+
+            if (complexParser.nextGroup(16)) {
+                // TODO distance
+            }
+
+            if (complexParser.nextGroup(8)) {
+                if (complexParser.hasFirst()) {
+                    // hr, 8 bits
+                    sample.setHeartRate(complexParser.get(0, 8));
+                }
+            }
+
+            if (complexParser.nextGroup(8)) {
+                if (complexParser.hasFirst()) {
+                    // energy, 8 bits
+                }
+            }
+
+            if (complexParser.nextGroup(16)) {
+                // TODO
+            }
+
+            if (version >= 3) {
+                if (complexParser.nextGroup(8)) {
+                    if (complexParser.hasFirst()) {
+                        // spo2, 8 bits
+                        sample.setSpo2(complexParser.get(0, 8));
+                    }
+                }
+                if (complexParser.nextGroup(8)) {
+                    if (complexParser.hasFirst()) {
+                        // stress, 8 bits
+                        final int stress = complexParser.get(0, 8);
+                        if (stress != 255) {
+                            sample.setStress(stress);
+                        }
+                    }
+                }
+            }
+
+            if (includeExtraEntry == 1) {
+                if (complexParser.nextGroup(8)) {
+                    // TODO
+                }
+            }
+
+            if (version >= 4) {
+                // TODO: light value (short)
+                complexParser.nextGroup(16);
+
+                // TODO: body momentum (short)
+                complexParser.nextGroup(16);
+            }
+
+            samples.add(sample);
+
+            timestamp.add(Calendar.MINUTE, 1);
+        }
+
+        // save all the samples that we got
+        try (DBHandler handler = GBApplication.acquireDB()) {
+            final DaoSession session = handler.getDaoSession();
+
+            final GBDevice gbDevice = support.getDevice();
+            final DeviceCoordinator coordinator = gbDevice.getDeviceCoordinator();
+            final SampleProvider<XiaomiActivitySample> sampleProvider = (SampleProvider<XiaomiActivitySample>) coordinator.getSampleProvider(gbDevice, session);
+            final Device device = DBHelper.getDevice(gbDevice, session);
+            final User user = DBHelper.getUser(session);
+
+            for (final XiaomiActivitySample sample : samples) {
+                sample.setDevice(device);
+                sample.setUser(user);
+                sample.setProvider(sampleProvider);
+            }
+            sampleProvider.addGBActivitySamples(samples.toArray(new XiaomiActivitySample[0]));
+
+            return true;
+        } catch (final Exception e) {
+            GB.toast(support.getContext(), "Error saving activity samples", Toast.LENGTH_LONG, GB.ERROR);
+            LOG.error("Error saving activity samples", e);
+            return false;
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/activity/impl/DailySummaryParser.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/activity/impl/DailySummaryParser.java
new file mode 100644
index 0000000..c401326
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/activity/impl/DailySummaryParser.java
@@ -0,0 +1,126 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.activity.impl;
+
+import android.widget.Toast;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.XiaomiDailySummarySampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.XiaomiDailySummarySample;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.XiaomiSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.activity.XiaomiActivityFileId;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.activity.XiaomiActivityParser;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class DailySummaryParser extends XiaomiActivityParser {
+    private static final Logger LOG = LoggerFactory.getLogger(DailySummaryParser.class);
+
+    @Override
+    public boolean parse(final XiaomiSupport support, final XiaomiActivityFileId fileId, final byte[] bytes) {
+        final int version = fileId.getVersion();
+        final int headerSize;
+        switch (version) {
+            case 5:
+                headerSize = 4;
+                break;
+            default:
+                LOG.warn("Unable to parse daily summary version {}", fileId.getVersion());
+                return false;
+        }
+
+        final ByteBuffer buf = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN);
+        buf.get(new byte[7]); // skip fileId bytes
+        final byte fileIdPadding = buf.get();
+        if (fileIdPadding != 0) {
+            LOG.warn("Expected 0 padding after fileId, got {} - parsing might fail", fileIdPadding);
+        }
+
+        final byte[] header = new byte[headerSize];
+        buf.get(header);
+
+        LOG.debug("Header: {}", GB.hexdump(header));
+
+        final XiaomiDailySummarySample sample = new XiaomiDailySummarySample();
+        sample.setTimestamp(fileId.getTimestamp().getTime());
+        sample.setTimezone(fileId.getTimezone());
+
+        sample.setSteps(buf.getInt());
+        final int unk1 = buf.get() & 0xff; // 0
+        final int unk2 = buf.get() & 0xff; // 0
+        final int unk3 = buf.get() & 0xff; // 0
+        sample.setHrResting(buf.get() & 0xff);
+        sample.setHrMax(buf.get() & 0xff);
+        sample.setHrMaxTs(buf.getInt());
+        sample.setHrMin(buf.get() & 0xff);
+        sample.setHrMinTs(buf.getInt());
+        sample.setHrAvg(buf.get() & 0xff);
+        sample.setStressAvg(buf.get() & 0xff);
+        sample.setStressMax(buf.get() & 0xff);
+        sample.setStressMin(buf.get() & 0xff);
+        final byte[] standingArr = new byte[3];
+        buf.get(standingArr);
+        // each bit represents one hour where the user was standing up for that day,
+        // starting at 00:00-01:00. Let's convert it to an int
+        int standing = (standingArr[0] | (standingArr[1] << 8) | (standingArr[2] << 16)) & 0x00FFFFFF;
+        sample.setStanding(standing);
+        sample.setCalories((int) buf.getShort());
+        final int unk7 = buf.get() & 0xff; // 0
+        final int unk8 = buf.get() & 0xff; // 0
+        final int unk9 = buf.get() & 0xff; // 0
+        sample.setSpo2Max(buf.get() & 0xff);
+        sample.setSpo2MaxTs(buf.getInt());
+        sample.setSpo2Min(buf.get() & 0xff);
+        sample.setSpo2MinTs(buf.getInt());
+        sample.setSpo2Avg(buf.get() & 0xff);
+        sample.setTrainingLoadDay((int) buf.getShort());
+        sample.setTrainingLoadWeek((int) buf.getShort());
+        sample.setTrainingLoadLevel(buf.get() & 0xff); // TODO confirm - 1 for low training load level?
+        sample.setVitalityIncreaseLight(buf.get() & 0xff);
+        sample.setVitalityIncreaseModerate(buf.get() & 0xff);
+        sample.setVitalityIncreaseHigh(buf.get() & 0xff);
+        sample.setVitalityCurrent((int) buf.getShort());
+
+        LOG.debug("Persisting 1 daily summary sample");
+
+        try (DBHandler handler = GBApplication.acquireDB()) {
+            final DaoSession session = handler.getDaoSession();
+            final GBDevice device = support.getDevice();
+
+            sample.setDevice(DBHelper.getDevice(device, session));
+            sample.setUser(DBHelper.getUser(session));
+
+            final XiaomiDailySummarySampleProvider sampleProvider = new XiaomiDailySummarySampleProvider(device, session);
+            sampleProvider.addSample(sample);
+        } catch (final Exception e) {
+            GB.toast(support.getContext(), "Error saving daily summary", Toast.LENGTH_LONG, GB.ERROR);
+            LOG.error("Error saving daily summary", e);
+            return false;
+        }
+
+        return true;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/activity/impl/ManualSamplesParser.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/activity/impl/ManualSamplesParser.java
new file mode 100644
index 0000000..2a34f9c
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/activity/impl/ManualSamplesParser.java
@@ -0,0 +1,132 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.activity.impl;
+
+import android.widget.Toast;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+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.xiaomi.XiaomiManualSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.entities.User;
+import nodomain.freeyourgadget.gadgetbridge.entities.XiaomiManualSample;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.XiaomiSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.activity.XiaomiActivityFileId;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.activity.XiaomiActivityParser;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class ManualSamplesParser extends XiaomiActivityParser {
+    private static final Logger LOG = LoggerFactory.getLogger(ManualSamplesParser.class);
+
+    @Override
+    public boolean parse(final XiaomiSupport support, final XiaomiActivityFileId fileId, final byte[] bytes) {
+        if (fileId.getVersion() != 2) {
+            LOG.warn("Unknown manual samples version {}", fileId.getVersion());
+            return false;
+        }
+
+        final ByteBuffer buf = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN);
+        buf.limit(buf.limit() - 4); // discard crc at the end
+        buf.get(new byte[7]); // skip fileId bytes
+        final byte fileIdPadding = buf.get();
+        if (fileIdPadding != 0) {
+            LOG.warn("Expected 0 padding after fileId, got {} - parsing might fail", fileIdPadding);
+        }
+
+        // Looks like there is no header, it starts right away with samples:
+        // 8A90A965 12 63 <- spo2
+        // ... multiple 13 00
+        // C793A965 13 00
+        // 9698A965 13 1C <- stress
+        // E79CA965 44 5A0E0000 <- body temperature
+        // 729FA965 44 590E0000 <- body temperature
+
+        final List<XiaomiManualSample> samples = new ArrayList<>();
+
+        while (buf.position() < buf.limit()) {
+            final int timestamp = buf.getInt();
+            final int type = buf.get() & 0xff;
+
+            final int value;
+            // FIXME: This is incomplete - the type is actually composed of 2 nibbles that
+            // define the data length + type
+            // see https://codeberg.org/Freeyourgadget/Gadgetbridge/issues/3517#issuecomment-1516353
+            switch (type) {
+                case XiaomiManualSampleProvider.TYPE_HR:
+                case XiaomiManualSampleProvider.TYPE_SPO2:
+                case XiaomiManualSampleProvider.TYPE_STRESS:
+                    value = buf.get() & 0xff;
+                    break;
+                case XiaomiManualSampleProvider.TYPE_TEMPERATURE:
+                    // FIXME: This is actually 2 2-byte values, see the comment linked above
+                    value = buf.getInt();
+                    break;
+                // TODO blood pressure, see the comment linked above
+                default:
+                    LOG.warn("Unknown sample type {}", type);
+                    // We need to abort parsing, as we don't know the sample size
+                    return false;
+            }
+
+            if (value == 0) {
+                continue;
+            }
+
+            LOG.debug("Got manual sample: ts={} type={} value={}", timestamp, type, value);
+
+            final XiaomiManualSample sample = new XiaomiManualSample();
+            sample.setTimestamp(timestamp * 1000L);
+            sample.setType(type);
+            sample.setValue(value);
+
+            samples.add(sample);
+        }
+
+        try (DBHandler handler = GBApplication.acquireDB()) {
+            final DaoSession session = handler.getDaoSession();
+
+            final GBDevice gbDevice = support.getDevice();
+            final Device device = DBHelper.getDevice(gbDevice, session);
+            final User user = DBHelper.getUser(session);
+
+            for (final XiaomiManualSample sample : samples) {
+                sample.setDevice(device);
+                sample.setUser(user);
+            }
+
+            final XiaomiManualSampleProvider sampleProvider = new XiaomiManualSampleProvider(gbDevice, session);
+            sampleProvider.addSamples(samples);
+        } catch (final Exception e) {
+            GB.toast(support.getContext(), "Error saving manual samples", Toast.LENGTH_LONG, GB.ERROR);
+            LOG.error("Error saving manual samples", e);
+            return false;
+        }
+
+        return true;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/activity/impl/SleepDetailsParser.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/activity/impl/SleepDetailsParser.java
new file mode 100644
index 0000000..6d7f058
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/activity/impl/SleepDetailsParser.java
@@ -0,0 +1,391 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.activity.impl;
+
+import android.widget.Toast;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+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.HeartPulseSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.XiaomiSleepStageSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.XiaomiSleepTimeSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.entities.HeartPulseSample;
+import nodomain.freeyourgadget.gadgetbridge.entities.User;
+import nodomain.freeyourgadget.gadgetbridge.entities.XiaomiSleepStageSample;
+import nodomain.freeyourgadget.gadgetbridge.entities.XiaomiSleepTimeSample;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.XiaomiSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.activity.XiaomiActivityFileId;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.activity.XiaomiActivityParser;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class SleepDetailsParser extends XiaomiActivityParser {
+    private static final Logger LOG = LoggerFactory.getLogger(SleepDetailsParser.class);
+
+    @Override
+    public boolean parse(final XiaomiSupport support, final XiaomiActivityFileId fileId, final byte[] bytes) {
+        // Seems to come both as DetailType.DETAILS (version 2) and DetailType.SUMMARY (version 4)
+        if (fileId.getVersion() > 4) {
+            LOG.warn("Unknown sleep details version {}", fileId.getVersion());
+            return false;
+        }
+
+        // Stores number of fields which are only present in certain versions of the message
+        // this is required for correct header offset calculation
+        int versionDependentFields = 0;
+
+        final ByteBuffer buf = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN);
+        buf.get(new byte[7]); // skip fileId bytes
+        final byte fileIdPadding = buf.get();
+        if (fileIdPadding != 0) {
+            LOG.warn("Expected 0 padding after fileId, got {} - parsing might fail", fileIdPadding);
+        }
+
+        final byte header = buf.get();
+
+        final int isAwake = buf.get() & 0xff; // 0/1 - more correctly this would be !isSleepFinish
+        final int bedTime = buf.getInt();
+        final int wakeupTime = buf.getInt();
+        int sleepQuality = -1;
+        if (fileId.getVersion() >= 4) {
+            versionDependentFields += 1;
+            sleepQuality = buf.get() & 0xff;
+        }
+
+        // note on RR-intervals (msg type 1) on Xiaomi band 9, FW 2.3.93, HW M2345B1:
+        //
+        // the band collects RR interval in groups of ~10 minutes, which are then sent as msg type 1
+        // along with an associated RTC timestamp. RTC timestamp jitter and drift of up to
+        // +/-several seconds was observed, compared to the sum of intervals in a given msg.
+        //
+        // In order to preserve RR-interval continuity throughout the entire duration of sleep, as
+        // is necessary for breath-detection algorithm, and to avoid negative or impossibly large
+        // (false) RR intervals, we maintain an running timestamp of last heart pulse.
+        // While processing msgs, in case the detected jitter is too large, the running timestamp
+        // is reset to the current msg timestamp.
+        long lastHeartPulseTimestamp = 0;  // tracks the true timestamp of last heart pulse across the jittery 10-minute segments
+
+        LOG.debug("Sleep sample: bedTime: {}, wakeupTime: {}, isAwake: {}", bedTime, wakeupTime, isAwake);
+
+        final List<XiaomiSleepTimeSample> summaries = new ArrayList<>();
+
+        XiaomiSleepTimeSample sample = new XiaomiSleepTimeSample();
+        sample.setTimestamp(bedTime * 1000L);
+        sample.setWakeupTime(wakeupTime * 1000L);
+        sample.setIsAwake(isAwake == 1);
+
+        // Heart rate samples
+        if ((header & (1 << (5 - versionDependentFields))) != 0) {
+            LOG.debug("Heart rate samples from offset {}", Integer.toHexString(buf.position()));
+            final int unit = buf.getShort(); // Time unit (i.e sample rate)
+            final int count = buf.getShort();
+
+            if (count > 0) {
+                // If version is less than 2 firstRecordTime is bedTime
+                if (fileId.getVersion() >= 2) {
+                    final int firstRecordTime = buf.getInt();
+                }
+
+                // Skip count samples - each sample is a u8
+                //   timestamp of each sample is firstRecordTime + (unit * index)
+                buf.position(buf.position() + count);
+            }
+        }
+
+        // SpO2 samples
+        if ((header & (1 << (4 - versionDependentFields))) != 0) {
+            LOG.debug("SpO鈧� samples from offset {}", Integer.toHexString(buf.position()));
+            final int unit = buf.getShort(); // Time unit (i.e sample rate)
+            final int count = buf.getShort();
+
+            if (count > 0) {
+                // If version is less than 2 firstRecordTime is bedTime
+                if (fileId.getVersion() >= 2) {
+                    final int firstRecordTime = buf.getInt();
+                }
+
+                // Skip count samples - each sample is a u8
+                //   timestamp of each sample is firstRecordTime + (unit * index)
+                buf.position(buf.position() + count);
+            }
+        }
+
+        // snore samples
+        if (fileId.getVersion() >= 3 && (header & (1 << (3 - versionDependentFields))) != 0) {
+            LOG.debug("Snore level samples from offset {}", Integer.toHexString(buf.position()));
+            final int unit = buf.getShort(); // Time unit (i.e sample rate)
+            final int count = buf.getShort();
+
+            if (count > 0) {
+                // If version is less than 2 firstRecordTime is bedTime
+                if (fileId.getVersion() >= 2) {
+                    final int firstRecordTime = buf.getInt();
+                }
+
+                // Skip count samples - each sample is a float
+                //   timestamp of each sample is firstRecordTime + (unit * index)
+                buf.position(buf.position() + count * 4);
+            }
+        }
+
+        final List<XiaomiSleepStageSample> stages = new ArrayList<>();
+        final List<HeartPulseSample> heartPulseSamples = new ArrayList<>();
+        LOG.debug("Sleep stage packets from offset {}", Integer.toHexString(buf.position()));
+
+        // Do not crash if we face a buffer underflow, as the next parsing is not 100% fool-proof,
+        // and we still want to persist whatever we got so far
+        boolean stagesParseFailed = false;
+        try {
+            while (buf.remaining() >= 17) {
+                if (!readStagePacketHeader(buf)) {
+                    break;
+                }
+
+                final int headerLen = buf.get() & 0xFF; // this seems to always be 17
+
+                // This timestamp is kind of weird, is seems to sometimes be in seconds
+                // and other times in nanoseconds. Message types 16 and 17 are in seconds
+                final long ts = buf.getLong();
+                final int parity = buf.get() & 0xFF; // sum of stage bit count should be uneven
+                final int type = buf.get() & 0xFF;
+                final int dataLen = ((buf.get() & 0xFF) << 8) | (buf.get() & 0xFF);
+
+                // Known types:
+                //  - acc_unk = 0,
+                //  - ppg_unk = 1,
+                //  - fall_asleep = 2,
+                //  - wake_up = 3,
+                //  - switch_ts_unk1 = 12,
+                //  - switch_ts_unk2 = 13,
+                //  - Summary = 16,
+                //  - Stages = 17
+
+                if (type == 0x2 || type == 0x3 || type == 0x9 || type == 0xc || type == 0xd || type == 0xe || type == 0xf) {
+                    // the bytes reserved for the data length are believed to be flags, as they
+                    // do not actually have any data following the headers
+                    continue;
+                }
+
+                final byte[] data = new byte[dataLen];
+                buf.get(data);
+
+                final ByteBuffer dataBuf = ByteBuffer.wrap(data).order(ByteOrder.BIG_ENDIAN);
+
+                if (type == 1) {
+                    // RR intervals: https://en.wikipedia.org/wiki/RR_interval
+                    // add first heartbeat before intervals, if necessary
+                    if (Math.abs(lastHeartPulseTimestamp - ts) > 30000) {  // 30 seconds
+                        // we drifted too far from RTC timestamp, or this is the very first sample:
+                        lastHeartPulseTimestamp = ts;  // resync
+                        final HeartPulseSample heartPulseSample = new HeartPulseSample();
+                        heartPulseSample.setTimestamp(lastHeartPulseTimestamp);
+                        heartPulseSamples.add(heartPulseSample);
+                    }
+                    // add heartbeats after intervals
+                    while (dataBuf.position() < dataBuf.limit()) {
+                        final int delta = dataBuf.get() & 0xff;  // delta is in 10msec units
+                        lastHeartPulseTimestamp += 10 * delta;  // convert to 1msec units for timestamps
+                        final HeartPulseSample heartPulseSample = new HeartPulseSample();
+                        heartPulseSample.setTimestamp(lastHeartPulseTimestamp);
+                        heartPulseSamples.add(heartPulseSample);
+                    }
+                } else if (type == 16) {
+                    final int data_0 = dataBuf.get() & 0xFF;
+                    final int sleep_index = data_0 >> 4;
+                    final int wake_count = data_0 & 0x0F;
+
+                    final int sleep_duration = dataBuf.getShort() & 0xFFFF;
+                    final int wake_duration  = dataBuf.getShort() & 0xFFFF;
+                    final int light_duration = dataBuf.getShort() & 0xFFFF;
+                    final int rem_duration   = dataBuf.getShort() & 0xFFFF;
+                    final int deep_duration  = dataBuf.getShort() & 0xFFFF;
+
+                    final int data_1 = dataBuf.get() & 0xFF;
+                    final boolean has_rem = (data_1 >> 4) == 1;
+                    final boolean has_stage = (data_1 >> 2) == 1;
+
+                    // Could probably be an "awake" duration after sleep
+                    final int unk_duration_minutes = dataBuf.get() & 0xFF;
+
+                    if (sample == null) {
+                        sample = new XiaomiSleepTimeSample();
+                    }
+
+                    sample.setTimestamp(bedTime * 1000L);
+                    sample.setWakeupTime(wakeupTime * 1000L);
+                    sample.setTotalDuration(sleep_duration);
+                    sample.setDeepSleepDuration(deep_duration);
+                    sample.setLightSleepDuration(light_duration);
+                    sample.setRemSleepDuration(rem_duration);
+                    sample.setAwakeDuration(wake_duration);
+
+                    // FIXME: This is an array, but we end up persisting only the last sample, since
+                    //        the timestamp is the primary key
+                    summaries.add(sample);
+                    sample = null;
+                } else if (type == 17) { // Stages
+                    long currentTime = ts * 1000;
+                    for (int i = 0; i < dataLen / 2; i++) {
+                        // when the change to the phase occurs
+                        final int val = dataBuf.getShort() & 0xFFFF;
+
+                        final int stage = val >> 12;
+                        final int offsetMinutes = val & 0xFFF;
+
+                        final XiaomiSleepStageSample stageSample = new XiaomiSleepStageSample();
+                        stageSample.setTimestamp(currentTime);
+                        stageSample.setStage(decodeStage(stage));
+                        stages.add(stageSample);
+
+                        currentTime += offsetMinutes * 60000;
+                    }
+                }
+            }
+        } catch (final BufferUnderflowException e) {
+            LOG.warn("Buffer underflow while parsing sleep stages...", e);
+            stagesParseFailed = true;
+        }
+
+        if (summaries.isEmpty()) {
+            // We did not manage to find sleep stage samples - ensure we at least persist the base one
+            summaries.add(sample);
+        }
+
+        boolean persistSuccess = !stagesParseFailed;
+
+        // save all the samples that we got
+        try (DBHandler handler = GBApplication.acquireDB()) {
+            final DaoSession session = handler.getDaoSession();
+            final GBDevice gbDevice = support.getDevice();
+
+            final XiaomiSleepTimeSampleProvider sampleProvider = new XiaomiSleepTimeSampleProvider(gbDevice, session);
+
+            for (final XiaomiSleepTimeSample summary : summaries) {
+                summary.setDevice(DBHelper.getDevice(gbDevice, session));
+                summary.setUser(DBHelper.getUser(session));
+
+                // Check if there is already a later sleep sample - if so, ignore this one
+                // Samples for the same sleep will always have the same bedtime (timestamp), but we might get
+                // multiple bedtimes until the user wakes up
+                final List<XiaomiSleepTimeSample> existingSamples = sampleProvider.getAllSamples(summary.getTimestamp(), summary.getTimestamp());
+                if (!existingSamples.isEmpty()) {
+                    final XiaomiSleepTimeSample existingSample = existingSamples.get(0);
+                    if (existingSample.getWakeupTime() > summary.getWakeupTime()) {
+                        LOG.warn("Ignoring sleep sample - existing sample is more recent ({})", existingSample.getWakeupTime());
+                        continue;
+                    }
+                }
+
+                sampleProvider.addSample(summary);
+            }
+        } catch (final Exception e) {
+            GB.toast(support.getContext(), "Error saving sleep sample", Toast.LENGTH_LONG, GB.ERROR);
+            LOG.error("Error saving sleep sample", e);
+            persistSuccess = false;
+        }
+
+        if (!stagesParseFailed && !stages.isEmpty()) {
+            LOG.debug("Persisting {} sleep stage samples", stages.size());
+
+            // Save the sleep stage samples
+            try (DBHandler handler = GBApplication.acquireDB()) {
+                final DaoSession session = handler.getDaoSession();
+                final GBDevice gbDevice = support.getDevice();
+                final Device device = DBHelper.getDevice(gbDevice, session);
+                final User user = DBHelper.getUser(session);
+
+                final XiaomiSleepStageSampleProvider sampleProvider = new XiaomiSleepStageSampleProvider(gbDevice, session);
+
+                for (final XiaomiSleepStageSample stageSample : stages) {
+                    stageSample.setDevice(device);
+                    stageSample.setUser(user);
+                }
+
+                sampleProvider.addSamples(stages);
+            } catch (final Exception e) {
+                GB.toast(support.getContext(), "Error saving sleep stage samples", Toast.LENGTH_LONG, GB.ERROR);
+                LOG.error("Error saving sleep stage samples", e);
+                persistSuccess = false;
+            }
+        }
+
+        // Save the heart pulse samples
+        try (DBHandler handler = GBApplication.acquireDB()) {
+            final DaoSession session = handler.getDaoSession();
+            final GBDevice gbDevice = support.getDevice();
+            final Device device = DBHelper.getDevice(gbDevice, session);
+            final User user = DBHelper.getUser(session);
+
+            final HeartPulseSampleProvider sampleProvider = new HeartPulseSampleProvider(gbDevice, session);
+
+            for (final HeartPulseSample stageSample : heartPulseSamples) {
+                stageSample.setDevice(device);
+                stageSample.setUser(user);
+            }
+
+            sampleProvider.addSamples(heartPulseSamples);
+        } catch (final Exception e) {
+            GB.toast(support.getContext(), "Error saving heart pulse samples", Toast.LENGTH_LONG, GB.ERROR);
+            LOG.error("Error saving heart pulse samples", e);
+            persistSuccess = false;
+        }
+
+        return persistSuccess;
+    }
+
+    private static boolean readStagePacketHeader(final ByteBuffer buffer) {
+        while (buffer.remaining() >= 17) {
+            if (buffer.getInt() != 0xfffcfafb) {
+                // rollback to second byte of header
+                buffer.position(buffer.position() - 3);
+                continue;
+            }
+
+            return true;
+        }
+        return false;
+    }
+
+    private static int decodeStage(int rawStage) {
+        switch (rawStage) {
+            case 0:
+                return 5; // AWAKE
+            case 1:
+                return 3; // LIGHT_SLEEP
+            case 2:
+                return 2; // DEEP_SLEEP
+            case 3:
+                return 4; // REM_SLEEP
+            case 4:
+                return 0; // NOT_SLEEP
+            default:
+                return 1; // N/A
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/activity/impl/SleepStagesParser.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/activity/impl/SleepStagesParser.java
new file mode 100644
index 0000000..3faba95
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/activity/impl/SleepStagesParser.java
@@ -0,0 +1,176 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.activity.impl;
+
+import android.widget.Toast;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+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.xiaomi.XiaomiSleepStageSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.XiaomiSleepTimeSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.entities.User;
+import nodomain.freeyourgadget.gadgetbridge.entities.XiaomiSleepStageSample;
+import nodomain.freeyourgadget.gadgetbridge.entities.XiaomiSleepTimeSample;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.XiaomiSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.activity.XiaomiActivityFileId;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.activity.XiaomiActivityParser;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class SleepStagesParser extends XiaomiActivityParser {
+    private static final Logger LOG = LoggerFactory.getLogger(SleepStagesParser.class);
+
+    @Override
+    public boolean parse(final XiaomiSupport support, final XiaomiActivityFileId fileId, final byte[] bytes) {
+        if (fileId.getVersion() != 2) {
+            LOG.warn("Unknown sleep stages version {}", fileId.getVersion());
+            return false;
+        }
+
+        final ByteBuffer buf = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN);
+        buf.get(new byte[7]); // skip fileId bytes
+        final byte fileIdPadding = buf.get();
+        if (fileIdPadding != 0) {
+            LOG.warn("Expected 0 padding after fileId, got {} - parsing might fail", fileIdPadding);
+        }
+
+        // over 4 days
+        // first 2 bytes: always FF FF
+        // bytes 3,4 small-medium
+        // byte 5 ???
+        // byte 6,7 small
+        final byte[] unk1 = new byte[7];
+        buf.get(unk1);
+
+        // total sleep duration in minutes
+        final short sleepDuration = buf.getShort();
+        // timestamp when watch counts "real" sleep start, might be later than first phase change
+        final int bedTime = buf.getInt();
+        // timestamp when sleep ended (have not observed, but may also be earlier than last phase?)
+        final int wakeupTime = buf.getInt();
+
+        // byte 8 medium
+        // bytes 9,10 look like a short
+        final byte[] unk2 = new byte[3];
+        buf.get(unk2);
+
+        // sum of all "real" deep sleep durations
+        final short deepSleepDuration = buf.getShort();
+        // sum of all "real" light sleep durations
+        final short lightSleepDuration = buf.getShort();
+        // sum of all "real" REM durations
+        final short REMDuration = buf.getShort();
+        // sum of all "real" awake durations
+        final short wakeDuration = buf.getShort();
+
+        LOG.debug("Sleep stages sample: bedTime: {}, wakeupTime: {}, sleepDuration: {}", bedTime, wakeupTime, sleepDuration);
+
+        if (bedTime == 0 || wakeupTime == 0 || sleepDuration == 0) {
+            LOG.warn("Ignoring sleep stages sample with no data");
+            return true;
+        }
+
+        final XiaomiSleepTimeSample sample = new XiaomiSleepTimeSample();
+        sample.setTimestamp(bedTime * 1000L);
+        sample.setWakeupTime(wakeupTime * 1000L);
+        sample.setIsAwake(false);
+        sample.setTotalDuration((int) sleepDuration);
+        sample.setDeepSleepDuration((int) deepSleepDuration);
+        sample.setLightSleepDuration((int) lightSleepDuration);
+        sample.setRemSleepDuration((int) REMDuration);
+        sample.setAwakeDuration((int) wakeDuration);
+
+        final List<XiaomiSleepStageSample> stages = new ArrayList<>();
+
+        // byte 11 small-medium
+        final byte unk3 = buf.get();
+        while (buf.position() < buf.limit()) {
+            // when the change to the phase occurs
+            final int time = buf.getInt();
+            // what phase state changed to
+            final int sleepPhase = buf.get() & 0xff;
+
+            final XiaomiSleepStageSample stageSample = new XiaomiSleepStageSample();
+            stageSample.setTimestamp(time * 1000L);
+            stageSample.setStage(sleepPhase);
+            stages.add(stageSample);
+        }
+
+        // Save the sleep time sample
+        try (DBHandler handler = GBApplication.acquireDB()) {
+            final DaoSession session = handler.getDaoSession();
+            final GBDevice gbDevice = support.getDevice();
+
+            sample.setDevice(DBHelper.getDevice(gbDevice, session));
+            sample.setUser(DBHelper.getUser(session));
+
+            final XiaomiSleepTimeSampleProvider sampleProvider = new XiaomiSleepTimeSampleProvider(gbDevice, session);
+
+            // Check if there is already a later sleep sample - if so, ignore this one
+            // Samples for the same sleep will always have the same bedtime (timestamp), but we might get
+            // multiple bedtimes until the user wakes up
+            final List<XiaomiSleepTimeSample> existingSamples = sampleProvider.getAllSamples(sample.getTimestamp(), sample.getTimestamp());
+            if (!existingSamples.isEmpty()) {
+                final XiaomiSleepTimeSample existingSample = existingSamples.get(0);
+                if (existingSample.getWakeupTime() > sample.getWakeupTime()) {
+                    LOG.warn("Ignoring sleep sample - existing sample is more recent ({})", existingSample.getWakeupTime());
+                    return true;
+                }
+            }
+
+            sampleProvider.addSample(sample);
+        } catch (final Exception e) {
+            GB.toast(support.getContext(), "Error saving sleep sample", Toast.LENGTH_LONG, GB.ERROR);
+            LOG.error("Error saving sleep sample", e);
+            return false;
+        }
+
+        // Save the sleep stage samples
+        try (DBHandler handler = GBApplication.acquireDB()) {
+            final DaoSession session = handler.getDaoSession();
+            final GBDevice gbDevice = support.getDevice();
+            final Device device = DBHelper.getDevice(gbDevice, session);
+            final User user = DBHelper.getUser(session);
+
+            final XiaomiSleepStageSampleProvider sampleProvider = new XiaomiSleepStageSampleProvider(gbDevice, session);
+
+            for (final XiaomiSleepStageSample stageSample : stages) {
+                stageSample.setDevice(device);
+                stageSample.setUser(user);
+            }
+
+            sampleProvider.addSamples(stages);
+        } catch (final Exception e) {
+            GB.toast(support.getContext(), "Error saving sleep stage samples", Toast.LENGTH_LONG, GB.ERROR);
+            LOG.error("Error saving sleep stage samples", e);
+            return false;
+        }
+
+        return true;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/activity/impl/WorkoutGpsParser.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/activity/impl/WorkoutGpsParser.java
new file mode 100644
index 0000000..705799c
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/activity/impl/WorkoutGpsParser.java
@@ -0,0 +1,180 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.activity.impl;
+
+import android.widget.Toast;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Date;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.entities.User;
+import nodomain.freeyourgadget.gadgetbridge.export.ActivityTrackExporter;
+import nodomain.freeyourgadget.gadgetbridge.export.GPXExporter;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityPoint;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityTrack;
+import nodomain.freeyourgadget.gadgetbridge.model.GPSCoordinate;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.XiaomiSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.activity.XiaomiActivityFileId;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.activity.XiaomiActivityParser;
+import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class WorkoutGpsParser extends XiaomiActivityParser {
+    private static final Logger LOG = LoggerFactory.getLogger(WorkoutGpsParser.class);
+
+    @Override
+    public boolean parse(final XiaomiSupport support, final XiaomiActivityFileId fileId, final byte[] bytes) {
+        final int version = fileId.getVersion();
+        final int headerSize;
+        final int sampleSize;
+        switch (version) {
+            case 1:
+                headerSize = 1;
+                sampleSize = 12;
+                break;
+            case 2:
+                headerSize = 1;
+                sampleSize = 18;
+                break;
+            default:
+                LOG.warn("Unable to parse workout gps version {}", fileId.getVersion());
+                return false;
+        }
+
+        final ByteBuffer buf = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN);
+        buf.limit(buf.limit() - 4); // discard crc at the end
+        buf.get(new byte[7]); // skip fileId bytes
+        final byte fileIdPadding = buf.get();
+        if (fileIdPadding != 0) {
+            LOG.warn("Expected 0 padding after fileId, got {} - parsing might fail", fileIdPadding);
+        }
+        final byte[] header = new byte[headerSize];
+        buf.get(header);
+
+        LOG.debug("Workout gps Header: {}", GB.hexdump(header));
+
+        if ((buf.limit() - buf.position()) % sampleSize != 0) {
+            LOG.warn("Remaining data in the buffer is not a multiple of {}", sampleSize);
+        }
+
+        final ActivityTrack activityTrack = new ActivityTrack();
+
+        // GPS V1 contains no speed data, therefore second while loop to avoid too many ifs within the loop
+        if (version == 1) {
+            while (buf.position() < buf.limit()) {
+                final int ts = buf.getInt();
+                final float longitude = buf.getFloat();
+                final float latitude = buf.getFloat();
+
+                final ActivityPoint ap = new ActivityPoint(new Date(ts * 1000L));
+                ap.setLocation(new GPSCoordinate(longitude, latitude, 0));
+                activityTrack.addTrackPoint(ap);
+                LOG.trace("ActivityPoint V1: ts={} lon={} lat={}", ts, longitude, latitude);
+            }
+        } else { 
+            while (buf.position() < buf.limit()) {
+                final int ts = buf.getInt();
+                final float longitude = buf.getFloat();
+                final float latitude = buf.getFloat();
+                final int unk1 = buf.getInt(); // 0
+                final float speed = (buf.getShort() >> 2) / 10.0f;
+
+                final ActivityPoint ap = new ActivityPoint(new Date(ts * 1000L));
+                ap.setLocation(new GPSCoordinate(longitude, latitude, 0));
+                activityTrack.addTrackPoint(ap);
+                LOG.trace("ActivityPoint: ts={} lon={} lat={} unk1={} speed={}", ts, longitude, latitude, unk1, speed);
+            }
+        }
+
+       try (DBHandler dbHandler = GBApplication.acquireDB()) {
+            final DaoSession session = dbHandler.getDaoSession();
+            final Device device = DBHelper.getDevice(support.getDevice(), session);
+            final User user = DBHelper.getUser(session);
+
+            // Find the matching summary
+            final BaseActivitySummary summary = findOrCreateBaseActivitySummary(session, device, user, fileId);
+
+            // Set the info on the activity track
+            activityTrack.setUser(user);
+            activityTrack.setDevice(device);
+            activityTrack.setName(ActivityKind.fromCode(summary.getActivityKind()).getLabel(support.getContext()));
+
+            // Save the raw bytes
+            final String rawBytesPath = saveRawBytes(fileId, bytes);
+
+            // Save the gpx file
+            final GPXExporter exporter = new GPXExporter();
+
+            final String gpxFileName = FileUtils.makeValidFileName("gadgetbridge-" + DateTimeUtils.formatIso8601(fileId.getTimestamp()) + ".gpx");
+            final File gpxTargetFile = new File(FileUtils.getExternalFilesDir(), gpxFileName);
+
+            boolean exportGpxSuccess = true;
+            try {
+                exporter.performExport(activityTrack, gpxTargetFile);
+            } catch (final ActivityTrackExporter.GPXTrackEmptyException ex) {
+                exportGpxSuccess = false;
+                GB.toast(support.getContext(), "This activity does not contain GPX tracks.", Toast.LENGTH_LONG, GB.ERROR, ex);
+            }
+
+            if (exportGpxSuccess) {
+                summary.setGpxTrack(gpxTargetFile.getAbsolutePath());
+            }
+            if (rawBytesPath != null) {
+                summary.setRawDetailsPath(rawBytesPath);
+            }
+            session.getBaseActivitySummaryDao().insertOrReplace(summary);
+        } catch (final Exception e) {
+            GB.toast(support.getContext(), "Error saving workout gps", Toast.LENGTH_LONG, GB.ERROR, e);
+            return false;
+        }
+
+        return true;
+    }
+
+    private String saveRawBytes(final XiaomiActivityFileId fileId, final byte[] bytes) {
+        try {
+            final File targetFolder = new File(FileUtils.getExternalFilesDir(), "rawDetails");
+            //noinspection ResultOfMethodCallIgnored
+            targetFolder.mkdirs();
+            final File targetFile = new File(targetFolder, fileId.getFilename());
+            FileOutputStream outputStream = new FileOutputStream(targetFile);
+            outputStream.write(fileId.toBytes());
+            outputStream.write(bytes);
+            outputStream.close();
+            return targetFile.getAbsolutePath();
+        } catch (final IOException e) {
+            LOG.error("Failed to save raw bytes", e);
+        }
+
+        return null;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/activity/impl/WorkoutSummaryParser.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/activity/impl/WorkoutSummaryParser.java
new file mode 100644
index 0000000..bc8f348
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/activity/impl/WorkoutSummaryParser.java
@@ -0,0 +1,879 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo, Yoran Vulker
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.activity.impl;
+
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.ACTIVE_SECONDS;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.ALTITUDE_AVG;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.ALTITUDE_MAX;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.ALTITUDE_MIN;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.CADENCE_AVG;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.CALORIES_BURNT;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.DISTANCE_METERS;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.ELEVATION_GAIN;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.ELEVATION_LOSS;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.HR_AVG;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.HR_MAX;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.HR_MIN;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.HR_ZONE_AEROBIC;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.HR_ZONE_ANAEROBIC;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.HR_ZONE_EXTREME;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.HR_ZONE_FAT_BURN;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.HR_ZONE_WARM_UP;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.LAPS;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.LAP_PACE_AVERAGE;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.MAXIMUM_OXYGEN_UPTAKE;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.PACE_AVG_SECONDS_KM;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.PACE_MAX;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.PACE_MIN;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.RECOVERY_TIME;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.SPEED_AVG;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.SPEED_MAX;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.STEPS;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.STEP_RATE_MAX;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.STROKES;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.STROKE_RATE_AVG;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.JUMPS;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.JUMP_RATE_AVG;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.JUMP_RATE_MAX;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.SWIM_STYLE;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.SWOLF_AVG;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.TIME_END;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.TIME_START;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.TRAINING_EFFECT_AEROBIC;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.TRAINING_EFFECT_ANAEROBIC;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.UNIT_BPM;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.UNIT_HOURS;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.UNIT_KCAL;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.UNIT_KMPH;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.UNIT_LAPS;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.UNIT_METERS;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.UNIT_ML_KG_MIN;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.UNIT_NONE;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.UNIT_SECONDS;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.UNIT_SECONDS_PER_KM;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.UNIT_SECONDS_PER_M;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.UNIT_SPM;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.UNIT_STEPS;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.UNIT_STROKES;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.UNIT_STROKES_PER_MINUTE;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.UNIT_JUMPS;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.UNIT_JUMPS_PER_MINUTE;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.UNIT_UNIX_EPOCH_SECONDS;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.WORKOUT_LOAD;
+import static nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.activity.impl.XiaomiSimpleActivityParser.XIAOMI_WORKOUT_TYPE;
+
+import android.widget.Toast;
+
+import androidx.annotation.Nullable;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.entities.User;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryParser;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.XiaomiSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.activity.XiaomiActivityFileId;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.activity.XiaomiActivityParser;
+import nodomain.freeyourgadget.gadgetbridge.util.CheckSums;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class WorkoutSummaryParser extends XiaomiActivityParser implements ActivitySummaryParser {
+    private static final Logger LOG = LoggerFactory.getLogger(WorkoutSummaryParser.class);
+
+    @Override
+    public boolean parse(final XiaomiSupport support, final XiaomiActivityFileId fileId, final byte[] bytes) {
+        BaseActivitySummary summary = new BaseActivitySummary();
+
+        summary.setStartTime(fileId.getTimestamp()); // due to a bug this has to be set
+        summary.setEndTime(fileId.getTimestamp()); // due to a bug this has to be set
+        summary.setActivityKind(ActivityKind.UNKNOWN.getCode());
+        summary.setRawSummaryData(bytes);
+
+        try {
+            summary = parseBinaryData(summary, true);
+        } catch (final Exception e) {
+            LOG.error("Failed to parse workout summary", e);
+            GB.toast(support.getContext(), "Failed to parse workout summary", Toast.LENGTH_LONG, GB.ERROR, e);
+            return false;
+        }
+
+        // parseBinaryData may return null in case the version is not supported
+        if (summary == null) {
+            LOG.warn("summary is null - should never happen {}", fileId);
+            return false;
+        }
+
+        summary.setSummaryData(null); // remove json before saving to database
+
+        try (DBHandler dbHandler = GBApplication.acquireDB()) {
+            final DaoSession session = dbHandler.getDaoSession();
+            final Device device = DBHelper.getDevice(support.getDevice(), session);
+            final User user = DBHelper.getUser(session);
+
+            final BaseActivitySummary existingSummary = findOrCreateBaseActivitySummary(session, device, user, fileId);
+            existingSummary.setEndTime(summary.getEndTime());
+            existingSummary.setActivityKind(summary.getActivityKind());
+            existingSummary.setRawSummaryData(summary.getRawSummaryData());
+            existingSummary.setSummaryData(null);  // remove json before saving to database
+
+            session.getBaseActivitySummaryDao().insertOrReplace(existingSummary);
+        } catch (final Exception e) {
+            GB.toast(support.getContext(), "Error saving activity summary", Toast.LENGTH_LONG, GB.ERROR, e);
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public BaseActivitySummary parseBinaryData(final BaseActivitySummary summary, final boolean forDetails) {
+        final byte[] data = summary.getRawSummaryData();
+        if (data == null) {
+            return summary;
+        }
+
+        final int arrCrc32 = CheckSums.getCRC32(data, 0, data.length - 4);
+        final int expectedCrc32 = BLETypeConversions.toUint32(data, data.length - 4);
+
+        final ByteBuffer buf;
+        if (arrCrc32 != expectedCrc32) {
+            // If the CRC32 is not valid, we're missing 1 header padding byte due to a previous bug
+            // This previous version also did not include the CRC at the end
+            // More info: https://codeberg.org/Freeyourgadget/Gadgetbridge/issues/3916
+            buf = ByteBuffer.allocate(data.length + 1).order(ByteOrder.LITTLE_ENDIAN);
+            buf.put(data, 0, 7);
+            buf.put((byte) 0);
+            buf.put(data, 7, data.length - 7);
+            buf.flip();
+        } else {
+            // Valid full file, skip crc
+            buf = ByteBuffer.wrap(data, 0, data.length - 4).order(ByteOrder.LITTLE_ENDIAN);
+        }
+
+        final XiaomiActivityFileId fileId = XiaomiActivityFileId.from(buf);
+
+        final byte fileIdPadding = buf.get();
+        if (fileIdPadding != 0) {
+            LOG.warn("Expected 0 padding after fileId, got {} - parsing might fail", fileIdPadding);
+        }
+
+        XiaomiSimpleActivityParser parser = null;
+
+        switch (fileId.getSubtype()) {
+            case SPORTS_OUTDOOR_WALKING_V1:
+                summary.setActivityKind(ActivityKind.WALKING.getCode());
+                parser = getOutdoorWalkingV1Parser(fileId);
+                break;
+            case SPORTS_OUTDOOR_RUNNING:
+                summary.setActivityKind(ActivityKind.RUNNING.getCode());
+                parser = getOutdoorWalkingV1Parser(fileId);
+                break;
+            case SPORTS_INDOOR_CYCLING:
+                summary.setActivityKind(ActivityKind.INDOOR_CYCLING.getCode());
+                parser = getIndoorCyclingParser(fileId);
+                break;
+            case SPORTS_FREESTYLE:
+                summary.setActivityKind(ActivityKind.EXERCISE.getCode());
+                parser = getFreestyleParser(fileId);
+                break;
+            case SPORTS_POOL_SWIMMING:
+                summary.setActivityKind(ActivityKind.SWIMMING.getCode());
+                parser = getPoolSwimmingParser(fileId);
+                break;
+            case SPORTS_HIIT:
+                summary.setActivityKind(ActivityKind.HIIT.getCode());
+                parser = getHiitParser(fileId);
+                break;
+            case SPORTS_ELLIPTICAL:
+                summary.setActivityKind(ActivityKind.ELLIPTICAL_TRAINER.getCode());
+                parser = getEllipticalParser(fileId);
+                break;
+            case SPORTS_OUTDOOR_WALKING_V2:
+                parser = getOutdoorWalkingV2Parser(fileId);
+                break;
+            case SPORTS_OUTDOOR_CYCLING_V2:
+                summary.setActivityKind(ActivityKind.OUTDOOR_CYCLING.getCode());
+                parser = getOutdoorCyclingV2Parser(fileId);
+                break;
+            case SPORTS_OUTDOOR_CYCLING:
+                parser = getOutdoorCyclingParser(fileId);
+                break;
+            case SPORTS_TREADMILL:
+                summary.setActivityKind(ActivityKind.TREADMILL.getCode());
+                parser = getTreadmillParser(fileId);
+                break;
+            case SPORTS_ROWING:
+                summary.setActivityKind(ActivityKind.ROWING.getCode());
+                parser = getRowingParser(fileId);
+                break;
+            case SPORTS_JUMP_ROPING:
+                summary.setActivityKind(ActivityKind.JUMP_ROPING.getCode());
+                parser = getJumpRopingParser(fileId);
+                break;
+            default:
+                LOG.warn("No workout summary parser for {}", fileId.getSubtypeCode());
+                break;
+        }
+
+        if (parser != null) {
+            parser.parse(summary, buf);
+        }
+
+        return summary;
+    }
+
+    @Nullable
+    private XiaomiSimpleActivityParser getFreestyleParser(final XiaomiActivityFileId fileId) {
+        final int version = fileId.getVersion();
+        final int headerSize;
+        switch (version) {
+            case 5:   // for Smart Band 8 Active
+                headerSize = 3;
+                break;
+            case 8:
+            case 9:
+            case 10:
+                headerSize = 6;
+                break;
+            default:
+                LOG.warn("Unable to parse workout summary version {}", fileId.getVersion());
+                return null;
+        }
+
+        final XiaomiSimpleActivityParser.Builder builder = new XiaomiSimpleActivityParser.Builder();
+        builder.setHeaderSize(headerSize);
+        builder.addInt(TIME_START, UNIT_UNIX_EPOCH_SECONDS);
+        builder.addInt(TIME_END, UNIT_UNIX_EPOCH_SECONDS);
+        builder.addInt(ACTIVE_SECONDS, UNIT_SECONDS);
+        builder.addShort(CALORIES_BURNT, UNIT_KCAL);
+        builder.addByte(HR_AVG, UNIT_BPM);
+        builder.addByte(HR_MAX, UNIT_BPM);
+        builder.addByte(HR_MIN, UNIT_BPM);
+        if (version == 5) {
+            builder.addUnknown(7); // probably training effect aerobic and recovery time?
+        } else {
+            builder.addUnknown(6);
+            builder.addFloat(TRAINING_EFFECT_AEROBIC, UNIT_NONE);
+            builder.addUnknown(1);
+            builder.addUnknown(1);
+            builder.addShort(RECOVERY_TIME, UNIT_HOURS);
+        }
+        builder.addInt(HR_ZONE_EXTREME, UNIT_SECONDS);
+        builder.addInt(HR_ZONE_ANAEROBIC, UNIT_SECONDS);
+        builder.addInt(HR_ZONE_AEROBIC, UNIT_SECONDS);
+        builder.addInt(HR_ZONE_FAT_BURN, UNIT_SECONDS);
+        builder.addInt(HR_ZONE_WARM_UP, UNIT_SECONDS);
+        if (version == 5) {
+            builder.addUnknown(10); // Probably the same as for v8 below?
+            builder.addShort(XIAOMI_WORKOUT_TYPE, XIAOMI_WORKOUT_TYPE);
+            builder.addUnknown(2);
+            builder.addInt("configuredTimeGoal", UNIT_SECONDS);
+            builder.addShort("configuredCaloriesGoal", UNIT_KCAL);
+        } else {
+            builder.addUnknown(2);
+            builder.addUnknown(4); // activeSeconds again?, UNIT_SECONDS
+            builder.addFloat(TRAINING_EFFECT_ANAEROBIC, UNIT_NONE);
+            builder.addUnknown(1);
+            builder.addShort(XIAOMI_WORKOUT_TYPE, XIAOMI_WORKOUT_TYPE);
+            builder.addUnknown(2);
+            builder.addInt("configuredTimeGoal", UNIT_SECONDS);
+            builder.addShort("configuredCaloriesGoal", UNIT_KCAL);
+            builder.addShort(WORKOUT_LOAD, UNIT_NONE);
+            builder.addUnknown(1);
+            builder.addByte("vitality_gain", UNIT_NONE);
+        }
+        return builder.build();
+    }
+
+    @Nullable
+    private XiaomiSimpleActivityParser getIndoorCyclingParser(final XiaomiActivityFileId fileId) {
+        final int version = fileId.getVersion();
+        final int headerSize;
+        switch (version) {
+            case 8:
+                headerSize = 7;
+                break;
+            case 9:
+                headerSize = 8;
+                break;
+            default:
+                LOG.warn("Unable to parse workout summary version {}", fileId.getVersion());
+                return null;
+        }
+
+        final XiaomiSimpleActivityParser.Builder builder = new XiaomiSimpleActivityParser.Builder();
+        builder.setHeaderSize(headerSize);
+        builder.addInt(TIME_START, UNIT_UNIX_EPOCH_SECONDS);
+        builder.addInt(TIME_END, UNIT_UNIX_EPOCH_SECONDS);
+        builder.addInt(ACTIVE_SECONDS, UNIT_SECONDS);
+        builder.addUnknown(4);
+        builder.addShort(CALORIES_BURNT, UNIT_KCAL);
+        builder.addUnknown(4);
+        builder.addByte(HR_AVG, UNIT_BPM);
+        builder.addByte(HR_MAX, UNIT_BPM);
+        builder.addByte(HR_MIN, UNIT_BPM);
+        builder.addFloat(TRAINING_EFFECT_AEROBIC, UNIT_NONE);
+        builder.addUnknown(1);
+        builder.addUnknown(1);
+        builder.addShort(RECOVERY_TIME, UNIT_HOURS);
+        builder.addInt(HR_ZONE_EXTREME, UNIT_SECONDS);
+        builder.addInt(HR_ZONE_ANAEROBIC, UNIT_SECONDS);
+        builder.addInt(HR_ZONE_AEROBIC, UNIT_SECONDS);
+        builder.addInt(HR_ZONE_FAT_BURN, UNIT_SECONDS);
+        builder.addInt(HR_ZONE_WARM_UP, UNIT_SECONDS);
+        builder.addUnknown(2);
+        builder.addUnknown(4);
+        builder.addFloat(TRAINING_EFFECT_ANAEROBIC, UNIT_NONE);
+        // FIXME identify field lengths to align with the header
+        builder.addUnknown(3);
+        builder.addInt("configuredTimeGoal", UNIT_SECONDS);
+        builder.addShort("configuredCaloriesGoal", UNIT_KCAL);
+        builder.addShort("maximumCaloriesGoal", UNIT_KCAL);  // TODO: mhm?
+        builder.addUnknown(28);
+        builder.addShort(WORKOUT_LOAD, UNIT_NONE); // training load
+        builder.addUnknown(24);
+        builder.addByte("configuredSets", UNIT_NONE);
+        builder.addUnknown(13);
+        builder.addInt("startTime2", UNIT_SECONDS);
+        builder.addInt("endTime2", UNIT_SECONDS);
+        builder.addInt("goal", UNIT_NONE); // TODO match against goalType
+        builder.addInt("duration2", UNIT_SECONDS);
+        builder.addInt("intervalTime", UNIT_SECONDS);
+        builder.addUnknown(56);
+        builder.addInt("hrZoneExtreme2", UNIT_SECONDS);
+        builder.addInt("hrZoneAnaerobic2", UNIT_SECONDS);
+        builder.addInt("hrZoneAerobic2", UNIT_SECONDS);
+        builder.addInt("hrZoneFatBurn2", UNIT_SECONDS);
+        builder.addInt("hrZoneWarmUp2", UNIT_SECONDS);
+        builder.addUnknown(16);
+        builder.addShort("vitality_gain", UNIT_NONE);
+        builder.addShort("training_load2", UNIT_NONE);
+        builder.addShort("recovery_time2", UNIT_HOURS);
+
+        return builder.build();
+    }
+
+    @Nullable
+    private XiaomiSimpleActivityParser getOutdoorWalkingV1Parser(final XiaomiActivityFileId fileId) {
+        final int version = fileId.getVersion();
+        final int headerSize;
+        switch (version) {
+            case 4:
+                headerSize = 4;
+                break;
+            default:
+                LOG.warn("Unable to parse workout summary version {}", fileId.getVersion());
+                return null;
+        }
+
+        final XiaomiSimpleActivityParser.Builder builder = new XiaomiSimpleActivityParser.Builder();
+        builder.setHeaderSize(headerSize);
+        builder.addInt(TIME_START, UNIT_UNIX_EPOCH_SECONDS);
+        builder.addInt(TIME_END, UNIT_UNIX_EPOCH_SECONDS);
+        builder.addInt(ACTIVE_SECONDS, UNIT_SECONDS);
+        builder.addInt(DISTANCE_METERS, UNIT_METERS);
+        builder.addInt(CALORIES_BURNT, UNIT_KCAL);
+        builder.addInt(PACE_MAX, UNIT_SECONDS_PER_M);
+        builder.addInt(PACE_MIN, UNIT_SECONDS_PER_M);
+        builder.addUnknown(4);
+        builder.addInt(STEPS, UNIT_STEPS);
+        builder.addUnknown(2); // pace?
+        builder.addByte(HR_AVG, UNIT_BPM);
+        builder.addByte(HR_MAX, UNIT_BPM);
+        builder.addByte(HR_MIN, UNIT_BPM);
+        // FIXME identify field lengths to align with the header
+        builder.addUnknown(20);
+        builder.addFloat("recoveryValue", "recoveryValue");
+        builder.addUnknown(9);
+        builder.addByte(RECOVERY_TIME, UNIT_SECONDS);
+        builder.addUnknown(2);
+        builder.addInt(HR_ZONE_EXTREME, UNIT_SECONDS);
+        builder.addInt(HR_ZONE_ANAEROBIC, UNIT_SECONDS);
+        builder.addInt(HR_ZONE_AEROBIC, UNIT_SECONDS);
+        builder.addInt(HR_ZONE_FAT_BURN, UNIT_SECONDS);
+        builder.addInt(HR_ZONE_WARM_UP, UNIT_SECONDS);
+        builder.addInt("configured_time_goal", UNIT_SECONDS);
+
+        return builder.build();
+    }
+
+    @Nullable
+    private XiaomiSimpleActivityParser getOutdoorWalkingV2Parser(final XiaomiActivityFileId fileId) {
+        final int version = fileId.getVersion();
+        final int headerSize;
+        switch (version) {
+            case 1:  // for Smart Band 8 Active
+                headerSize = 5;
+                break;
+            case 4:
+                headerSize = 7;
+                break;
+            case 5:
+            case 6:
+                headerSize = 9;
+                break;
+            default:
+                LOG.warn("Unable to parse workout summary version {}", fileId.getVersion());
+                return null;
+        }
+        final XiaomiSimpleActivityParser.Builder builder = new XiaomiSimpleActivityParser.Builder();
+        builder.setHeaderSize(headerSize);
+        builder.addShort(XIAOMI_WORKOUT_TYPE, XIAOMI_WORKOUT_TYPE);
+        builder.addInt(TIME_START, UNIT_UNIX_EPOCH_SECONDS);
+        builder.addInt(TIME_END, UNIT_UNIX_EPOCH_SECONDS);
+        builder.addInt(ACTIVE_SECONDS, UNIT_SECONDS);
+        builder.addUnknown(4);
+        builder.addInt(DISTANCE_METERS, UNIT_METERS);
+        builder.addUnknown(2);
+        builder.addShort(CALORIES_BURNT, UNIT_KCAL);
+        if (version == 1) {
+            builder.addInt(PACE_MAX, UNIT_SECONDS_PER_KM);
+            builder.addInt(PACE_MIN, UNIT_SECONDS_PER_KM);
+            builder.addFloat(SPEED_MAX, UNIT_KMPH);
+            builder.addInt(STEPS, UNIT_STEPS);
+            builder.addShort(STEP_RATE_MAX, UNIT_SPM);					   
+            builder.addByte(HR_AVG, UNIT_BPM);
+            builder.addByte(HR_MAX, UNIT_BPM);
+            builder.addByte(HR_MIN, UNIT_BPM);
+            builder.addUnknown(33);
+            builder.addInt(HR_ZONE_EXTREME, UNIT_SECONDS);
+            builder.addInt(HR_ZONE_ANAEROBIC, UNIT_SECONDS);
+            builder.addInt(HR_ZONE_AEROBIC, UNIT_SECONDS);
+            builder.addInt(HR_ZONE_FAT_BURN, UNIT_SECONDS);
+            builder.addInt(HR_ZONE_WARM_UP, UNIT_SECONDS);
+            builder.addUnknown(21);
+        } else {
+            if (version >= 5) {
+                builder.addInt(PACE_AVG_SECONDS_KM, UNIT_SECONDS_PER_KM);
+            }
+            builder.addInt(PACE_MAX, UNIT_SECONDS_PER_KM);
+            builder.addInt(PACE_MIN, UNIT_SECONDS_PER_KM);
+            if (version >= 5) {
+                builder.addFloat(SPEED_AVG, UNIT_KMPH);
+            }
+            builder.addFloat(SPEED_MAX, UNIT_KMPH);
+            builder.addInt(STEPS, UNIT_STEPS);
+            if (version >= 5) {
+                builder.addUnknown(4);
+            }
+            builder.addShort(STEP_RATE_MAX, UNIT_SPM);
+            builder.addByte(HR_AVG, UNIT_BPM);
+            builder.addByte(HR_MAX, UNIT_BPM);
+            builder.addByte(HR_MIN, UNIT_BPM);
+            builder.addUnknown(20);
+            builder.addFloat(TRAINING_EFFECT_AEROBIC, UNIT_NONE);
+            builder.addUnknown(1);
+            builder.addFloat(TRAINING_EFFECT_ANAEROBIC, UNIT_NONE);
+            builder.addUnknown(4);
+            builder.addByte(RECOVERY_TIME, UNIT_HOURS);
+            builder.addUnknown(2);
+            builder.addInt(HR_ZONE_EXTREME, UNIT_SECONDS);
+            builder.addInt(HR_ZONE_ANAEROBIC, UNIT_SECONDS);
+            builder.addInt(HR_ZONE_AEROBIC, UNIT_SECONDS);
+            builder.addInt(HR_ZONE_FAT_BURN, UNIT_SECONDS);
+            builder.addInt(HR_ZONE_WARM_UP, UNIT_SECONDS);
+            builder.addInt("configuredTimeGoal", UNIT_SECONDS);
+            builder.addShort("configuredCaloriesGoal", UNIT_KCAL);
+            builder.addInt("configuredDistanceGoal", UNIT_METERS);
+            builder.addUnknown(11);
+            builder.addShort(WORKOUT_LOAD, UNIT_NONE); // training load
+            builder.addUnknown(7);
+            builder.addByte("vitality_gain", UNIT_NONE);
+            builder.addUnknown(16);
+            builder.addUnknown(1); // HR_AVG duplicate
+            builder.addUnknown(1); // HR_MAX duplicate
+            builder.addUnknown(1); // HR_MIN duplicate
+            builder.addUnknown(2);
+            builder.addByte(CADENCE_AVG, UNIT_SPM);
+        }
+        return builder.build();
+    }
+
+    @Nullable
+    private XiaomiSimpleActivityParser getOutdoorCyclingParser(final XiaomiActivityFileId fileId) {
+        final int version = fileId.getVersion();
+        final int headerSize;
+        switch (version) {
+            case 4:
+            case 5:
+                headerSize = 6;
+                break;
+            case 6:
+                headerSize = 7;
+                break;
+            default:
+                LOG.warn("Unable to parse workout summary version {}", fileId.getVersion());
+                return null;
+        }
+
+        final XiaomiSimpleActivityParser.Builder builder = new XiaomiSimpleActivityParser.Builder();
+        builder.setHeaderSize(headerSize);
+        builder.addShort(XIAOMI_WORKOUT_TYPE, XIAOMI_WORKOUT_TYPE);
+        builder.addInt(TIME_START, UNIT_UNIX_EPOCH_SECONDS);
+        builder.addInt(TIME_END, UNIT_UNIX_EPOCH_SECONDS);
+        builder.addInt(ACTIVE_SECONDS, UNIT_SECONDS);
+        builder.addUnknown(4);
+        builder.addInt(DISTANCE_METERS, UNIT_METERS);
+        builder.addUnknown(2);
+        builder.addShort(CALORIES_BURNT, UNIT_KCAL);
+        builder.addUnknown(4);
+        builder.addUnknown(4);
+        if (version >= 5) {
+            builder.addFloat(SPEED_AVG, UNIT_KMPH);
+        }
+        builder.addFloat(SPEED_MAX, UNIT_KMPH);
+        builder.addByte(HR_AVG, UNIT_BPM);
+        builder.addByte(HR_MAX, UNIT_BPM);
+        builder.addByte(HR_MIN, UNIT_BPM);
+        builder.addFloat(ELEVATION_GAIN, UNIT_METERS);
+        builder.addFloat(ELEVATION_LOSS, UNIT_METERS);
+        builder.addFloat(ALTITUDE_AVG, UNIT_METERS);
+        builder.addFloat(ALTITUDE_MAX, UNIT_METERS);
+        builder.addFloat(ALTITUDE_MIN, UNIT_METERS);
+        builder.addFloat(TRAINING_EFFECT_AEROBIC, UNIT_NONE);
+        builder.addUnknown(1);
+        builder.addFloat(TRAINING_EFFECT_ANAEROBIC, UNIT_NONE);
+        builder.addUnknown(1);
+        builder.addByte(MAXIMUM_OXYGEN_UPTAKE, UNIT_ML_KG_MIN);
+        builder.addUnknown(1);
+        builder.addUnknown(1);
+        builder.addShort(RECOVERY_TIME, UNIT_HOURS);
+        builder.addUnknown(1);
+        builder.addInt(HR_ZONE_EXTREME, UNIT_SECONDS);
+        builder.addInt(HR_ZONE_ANAEROBIC, UNIT_SECONDS);
+        builder.addInt(HR_ZONE_AEROBIC, UNIT_SECONDS);
+        builder.addInt(HR_ZONE_FAT_BURN, UNIT_SECONDS);
+        builder.addInt(HR_ZONE_WARM_UP, UNIT_SECONDS);
+
+        return builder.build();
+    }
+
+    @Nullable
+    private XiaomiSimpleActivityParser getHiitParser(final XiaomiActivityFileId fileId){
+        final int version = fileId.getVersion();
+        final int headerSize;
+        switch (version) {
+            case 5:
+                headerSize = 4;
+                break;
+            default:
+                LOG.warn("Unable to parse workout summary version {}", fileId.getVersion());
+                return null;
+        }
+
+        final XiaomiSimpleActivityParser.Builder builder = new XiaomiSimpleActivityParser.Builder();
+        builder.setHeaderSize(headerSize);
+        builder.addInt(TIME_START, UNIT_UNIX_EPOCH_SECONDS);
+        builder.addInt(TIME_END, UNIT_UNIX_EPOCH_SECONDS);
+        builder.addInt(ACTIVE_SECONDS, UNIT_SECONDS);
+        builder.addShort(CALORIES_BURNT, UNIT_KCAL);
+        builder.addByte(HR_AVG, UNIT_BPM);
+        builder.addByte(HR_MAX, UNIT_BPM);
+        builder.addByte(HR_MIN, UNIT_BPM);
+        builder.addFloat(TRAINING_EFFECT_AEROBIC, UNIT_NONE);
+        builder.addUnknown(1);
+        builder.addUnknown(1);
+        builder.addShort(RECOVERY_TIME, UNIT_HOURS);
+        builder.addInt(HR_ZONE_EXTREME, UNIT_SECONDS);
+        builder.addInt(HR_ZONE_ANAEROBIC, UNIT_SECONDS);
+        builder.addInt(HR_ZONE_AEROBIC, UNIT_SECONDS);
+        builder.addInt(HR_ZONE_FAT_BURN, UNIT_SECONDS);
+        builder.addInt(HR_ZONE_WARM_UP, UNIT_SECONDS);
+
+        return builder.build();
+    }
+
+    @Nullable
+    private XiaomiSimpleActivityParser getPoolSwimmingParser(final XiaomiActivityFileId fileId) {
+        final int version = fileId.getVersion();
+        final int headerSize;
+        switch (version) {
+            case 6:
+                headerSize = 4;
+                break;
+            default:
+                LOG.warn("Unable to parse workout summary version {}", fileId.getVersion());
+                return null;
+        }
+
+        final XiaomiSimpleActivityParser.Builder builder = new XiaomiSimpleActivityParser.Builder();
+        builder.setHeaderSize(headerSize);
+        builder.addInt(TIME_START, UNIT_UNIX_EPOCH_SECONDS);
+        builder.addInt(TIME_END, UNIT_UNIX_EPOCH_SECONDS);
+        builder.addInt(ACTIVE_SECONDS, UNIT_SECONDS);
+        builder.addInt(DISTANCE_METERS, UNIT_METERS);
+        builder.addShort(CALORIES_BURNT, UNIT_KCAL);
+        builder.addUnknown(11);
+        builder.addShort(STROKES, UNIT_STROKES);
+        builder.addByte(SWIM_STYLE, UNIT_NONE);
+        builder.addUnknown(1);
+        builder.addShort(LAPS, UNIT_LAPS);
+        builder.addShort(SWOLF_AVG, UNIT_NONE);
+        builder.addUnknown(2); // minSWOLF
+        builder.addByte("configuredLaneLength", UNIT_METERS);
+        builder.addUnknown(6);
+        // builder.addInt("activeSec2", UNIT_SECONDS);
+        builder.addInt("configuredTimeGoal", UNIT_SECONDS);
+        builder.addShort("configuredCaloriesGoal", UNIT_KCAL);
+        builder.addUnknown(8);
+        builder.addByte("configuredLengthsGoal", UNIT_NONE);
+        builder.addUnknown(14);
+        builder.addByte("vitality_gain", UNIT_NONE);
+        builder.addUnknown(60);
+        // builder.addInt("startingTime2", UNIT_UNIX_EPOCH_SECONDS);
+        // builder.addInt("endTime2", UNIT_UNIX_EPOCH_SECONDS);
+        // builder.addUnknown(4);
+        // builder.addInt("activeSec3", UNIT_SECONDS);
+        // builder.addInt("activeSec4", UNIT_SECONDS);
+        // builder.addUnknown(8);
+        // builder.addInt("caloriesBurnt2", UNIT_KCAL);
+        // builder.addInt("distanceMeter2", UNIT_METERS);
+        // builder.addUnknown(4);
+        builder.addInt(LAP_PACE_AVERAGE, UNIT_SECONDS);
+        builder.addInt(PACE_MAX, UNIT_SECONDS); // not confirmed
+        builder.addUnknown(4);
+        builder.addInt(STROKE_RATE_AVG, UNIT_STROKES_PER_MINUTE);
+        builder.addUnknown(14);
+        // builder.addInt("laps2", UNIT_LAPS);
+        // builder.addShort("avgSWOLF2", UNIT_NONE);
+        builder.addShort("minSWOLF", UNIT_NONE);
+        builder.addShort("maxSWOLF", UNIT_NONE);
+        // builder.addInt("totalStrokes2", UNIT_STROKES);
+
+        return builder.build();
+    }
+
+    @Nullable
+    private XiaomiSimpleActivityParser getEllipticalParser(final XiaomiActivityFileId fileId) {
+        final int version = fileId.getVersion();
+        final int headerSize;
+        switch (version) {
+            case 3:
+                headerSize = 4;
+                break;
+            default:
+                LOG.warn("Unable to parse workout summary version {}", fileId.getVersion());
+                return null;
+        }
+
+        final XiaomiSimpleActivityParser.Builder builder = new XiaomiSimpleActivityParser.Builder();
+        builder.setHeaderSize(headerSize);
+        builder.addInt(TIME_START, UNIT_UNIX_EPOCH_SECONDS);
+        builder.addInt(TIME_END, UNIT_UNIX_EPOCH_SECONDS);
+        builder.addInt(ACTIVE_SECONDS, UNIT_SECONDS);
+        builder.addShort(CALORIES_BURNT, UNIT_KCAL);
+        builder.addInt(STEPS, UNIT_STEPS);
+        builder.addUnknown(2);        // MAX_STEPS_PER_MINUTE, UNIT_STEPS_PER_MINUTE
+        builder.addByte(HR_AVG, UNIT_BPM);
+        builder.addByte(HR_MAX, UNIT_BPM);
+        builder.addByte(HR_MIN, UNIT_BPM);
+        builder.addUnknown(7);
+        builder.addInt(HR_ZONE_EXTREME, UNIT_SECONDS);
+        builder.addInt(HR_ZONE_ANAEROBIC, UNIT_SECONDS);
+        builder.addInt(HR_ZONE_AEROBIC, UNIT_SECONDS);
+        builder.addInt(HR_ZONE_FAT_BURN, UNIT_SECONDS);
+        builder.addInt(HR_ZONE_WARM_UP, UNIT_SECONDS);
+        builder.addUnknown(20);
+
+        return builder.build();
+    }
+    
+    @Nullable
+    private XiaomiSimpleActivityParser getRowingParser(final XiaomiActivityFileId fileId) {
+        final int version = fileId.getVersion();
+        final int headerSize;
+        switch (version) {
+            case 4:
+                headerSize = 4;
+                break;
+            default:
+                LOG.warn("Unable to parse workout summary version {}", fileId.getVersion());
+                return null;
+        }
+
+        final XiaomiSimpleActivityParser.Builder builder = new XiaomiSimpleActivityParser.Builder();
+        builder.setHeaderSize(headerSize);
+        builder.addInt(TIME_START, UNIT_UNIX_EPOCH_SECONDS);
+        builder.addInt(TIME_END, UNIT_UNIX_EPOCH_SECONDS);
+        builder.addInt(ACTIVE_SECONDS, UNIT_SECONDS);
+        builder.addShort(CALORIES_BURNT, UNIT_KCAL);
+        builder.addByte(HR_AVG, UNIT_BPM);
+        builder.addByte(HR_MAX, UNIT_BPM);
+        builder.addByte(HR_MIN, UNIT_BPM);
+        builder.addUnknown(7);
+        builder.addInt(HR_ZONE_EXTREME, UNIT_SECONDS);
+        builder.addInt(HR_ZONE_ANAEROBIC, UNIT_SECONDS);
+        builder.addInt(HR_ZONE_AEROBIC, UNIT_SECONDS);
+        builder.addInt(HR_ZONE_FAT_BURN, UNIT_SECONDS);
+        builder.addInt(HR_ZONE_WARM_UP, UNIT_SECONDS);
+        builder.addUnknown(2);
+        builder.addInt(STROKES, UNIT_STROKES);
+        builder.addInt(STROKE_RATE_AVG, UNIT_STROKES_PER_MINUTE);
+        builder.addUnknown(4);   // STROKE_RATE_MAX, UNIT_STROKES_PER_MINUTE
+        builder.addUnknown(31);
+        return builder.build();
+    }    
+    
+    @Nullable
+    private XiaomiSimpleActivityParser getTreadmillParser(final XiaomiActivityFileId fileId) {
+        final int version = fileId.getVersion();
+        final int headerSize;
+        switch (version) {
+            case 5:
+                headerSize = 4;
+                break;
+            default:
+                LOG.warn("Unable to parse workout summary version {}", fileId.getVersion());
+                return null;
+        }
+
+        final XiaomiSimpleActivityParser.Builder builder = new XiaomiSimpleActivityParser.Builder();
+        builder.setHeaderSize(headerSize);
+        builder.addInt(TIME_START, UNIT_UNIX_EPOCH_SECONDS);
+        builder.addInt(TIME_END, UNIT_UNIX_EPOCH_SECONDS);
+        builder.addInt(ACTIVE_SECONDS, UNIT_SECONDS);
+        builder.addInt(DISTANCE_METERS, UNIT_METERS);
+        builder.addShort(CALORIES_BURNT, UNIT_KCAL);
+        builder.addUnknown(4);
+        builder.addUnknown(4);
+        builder.addInt(STEPS, UNIT_STEPS);
+        builder.addUnknown(2);        // MAX_STEPS_PER_MINUTE, UNIT_STEPS_PER_MINUTE
+        builder.addByte(HR_AVG, UNIT_BPM);
+        builder.addByte(HR_MAX, UNIT_BPM);
+        builder.addByte(HR_MIN, UNIT_BPM);
+        builder.addUnknown(8);
+        builder.addInt(HR_ZONE_EXTREME, UNIT_SECONDS);
+        builder.addInt(HR_ZONE_ANAEROBIC, UNIT_SECONDS);
+        builder.addInt(HR_ZONE_AEROBIC, UNIT_SECONDS);
+        builder.addInt(HR_ZONE_FAT_BURN, UNIT_SECONDS);
+        builder.addInt(HR_ZONE_WARM_UP, UNIT_SECONDS);
+        builder.addUnknown(32);
+
+        return builder.build();
+    }
+
+    @Nullable
+    private XiaomiSimpleActivityParser getOutdoorCyclingV2Parser(final XiaomiActivityFileId fileId) {
+        final int version = fileId.getVersion();
+        final int headerSize;
+        switch (version) {
+            case 4:
+                headerSize = 5;
+                break;
+            default:
+                LOG.warn("Unable to parse workout summary version {}", fileId.getVersion());
+                return null;
+        }
+
+        final XiaomiSimpleActivityParser.Builder builder = new XiaomiSimpleActivityParser.Builder();
+        builder.setHeaderSize(headerSize);
+        builder.addInt(TIME_START, UNIT_UNIX_EPOCH_SECONDS);
+        builder.addInt(TIME_END, UNIT_UNIX_EPOCH_SECONDS);
+        builder.addInt(ACTIVE_SECONDS, UNIT_SECONDS);
+        builder.addInt(DISTANCE_METERS, UNIT_METERS);
+        builder.addShort(CALORIES_BURNT, UNIT_KCAL);
+        builder.addUnknown(8);
+        builder.addFloat(SPEED_MAX, UNIT_KMPH);
+        builder.addByte(HR_AVG, UNIT_BPM);
+        builder.addByte(HR_MAX, UNIT_BPM);
+        builder.addByte(HR_MIN, UNIT_BPM);
+        builder.addUnknown(28);
+        builder.addInt(HR_ZONE_EXTREME, UNIT_SECONDS);
+        builder.addInt(HR_ZONE_ANAEROBIC, UNIT_SECONDS);
+        builder.addInt(HR_ZONE_AEROBIC, UNIT_SECONDS);
+        builder.addInt(HR_ZONE_FAT_BURN, UNIT_SECONDS);
+        builder.addInt(HR_ZONE_WARM_UP, UNIT_SECONDS);
+        builder.addUnknown(18);
+        builder.addUnknown(2); // configuredTimeGoal, UNIT_SECONDS
+        builder.addUnknown(6);
+
+        return builder.build();
+    }
+
+    @Nullable
+    private XiaomiSimpleActivityParser getJumpRopingParser(final XiaomiActivityFileId fileId) {
+        final int version = fileId.getVersion();
+        final int headerSize;
+        switch (version) {
+            case 3:
+            case 5:
+                headerSize = 5;
+                break;
+            default:
+                LOG.warn("Unable to parse workout summary version {}", fileId.getVersion());
+                return null;
+        }
+
+        final XiaomiSimpleActivityParser.Builder builder = new XiaomiSimpleActivityParser.Builder();
+        builder.setHeaderSize(headerSize);
+        builder.addInt(TIME_START, UNIT_UNIX_EPOCH_SECONDS);
+        builder.addInt(TIME_END, UNIT_UNIX_EPOCH_SECONDS);
+        builder.addInt(ACTIVE_SECONDS, UNIT_SECONDS);
+        builder.addShort(CALORIES_BURNT, UNIT_KCAL);
+        builder.addByte(HR_AVG, UNIT_BPM);
+        builder.addByte(HR_MAX, UNIT_BPM);
+        builder.addByte(HR_MIN, UNIT_BPM);
+        builder.addFloat(TRAINING_EFFECT_AEROBIC, UNIT_NONE);
+        if (version == 3) {
+            builder.addUnknown(3); // unknown byte and recovery time?
+        } else {
+            builder.addUnknown(2);
+            builder.addShort(RECOVERY_TIME, UNIT_HOURS);
+        }
+        builder.addInt(HR_ZONE_EXTREME, UNIT_SECONDS);
+        builder.addInt(HR_ZONE_ANAEROBIC, UNIT_SECONDS);
+        builder.addInt(HR_ZONE_AEROBIC, UNIT_SECONDS);
+        builder.addInt(HR_ZONE_FAT_BURN, UNIT_SECONDS);
+        builder.addInt(HR_ZONE_WARM_UP, UNIT_SECONDS);
+        builder.addUnknown(2);
+        builder.addInt(JUMPS, UNIT_JUMPS);
+        builder.addShort(JUMP_RATE_AVG, UNIT_JUMPS_PER_MINUTE);
+        builder.addUnknown(2);
+        builder.addShort(JUMP_RATE_MAX, UNIT_JUMPS_PER_MINUTE);
+        if (version == 3) {
+            builder.addUnknown(43);   // probably the same as for v5 below
+            builder.addUnknown(2);    // configuredJumpsGoal, UNIT_JUMPS, probably includes 2 more bytes
+            builder.addUnknown(2);
+        } else {
+            builder.addUnknown(27);
+            builder.addUnknown(4); // activeSeconds again?, UNIT_SECONDS
+            builder.addFloat(TRAINING_EFFECT_ANAEROBIC, UNIT_NONE);
+            builder.addUnknown(3);
+            builder.addInt("configuredTimeGoal", UNIT_SECONDS);
+            builder.addShort("configuredCaloriesGoal", UNIT_KCAL);
+            builder.addInt("configuredJumpsGoal", UNIT_JUMPS);
+            builder.addShort(WORKOUT_LOAD, UNIT_NONE);
+            builder.addUnknown(1);
+            builder.addByte("vitality_gain", UNIT_NONE); // vitality, UNIT_NONE
+        }
+
+        return builder.build();
+    }
+}
+
+
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/activity/impl/XiaomiComplexActivityParser.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/activity/impl/XiaomiComplexActivityParser.java
new file mode 100644
index 0000000..c2d33a6
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/activity/impl/XiaomiComplexActivityParser.java
@@ -0,0 +1,116 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.activity.impl;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+
+public class XiaomiComplexActivityParser {
+    private static final Logger LOG = LoggerFactory.getLogger(XiaomiComplexActivityParser.class);
+
+    private final byte[] header;
+    private final ByteBuffer buf;
+
+    private int currentGroup = -1;
+    private int currentGroupBits;
+    private int currentVal;
+
+    public XiaomiComplexActivityParser(final byte[] header, final ByteBuffer buf) {
+        this.header = header;
+        this.buf = buf;
+    }
+
+    public void reset() {
+        currentGroup = -1;
+        currentGroupBits = 0;
+        currentVal = 0;
+    }
+
+    /**
+     * Initializes the next group, for n bits.
+     * @return whether the next group exists
+     */
+    public boolean nextGroup(final int nBits) {
+        currentGroup++;
+        if (currentGroup >= header.length * 2) {
+            LOG.error("Header too small for group {}", currentGroup);
+            // We're now in an error state, but we'll consume so the buffer advances and we avoid an
+            // infinite loop
+            consume(nBits);
+            return false;
+        }
+
+        if ((getCurrentNibble() & 8) == 0) {
+            // group does not exist, return and do not consume anything from the buffer
+            return false;
+        }
+
+        currentGroupBits = nBits;
+        currentVal = consume(nBits);
+
+        return (getCurrentNibble() & 8) != 0;
+    }
+
+    private int consume(final int nBits) {
+        switch (nBits) {
+            case 8:
+                return buf.get() & 0xff;
+            case 16:
+                return buf.getShort() & 0xffff;
+            case 32:
+                return buf.getInt();
+        }
+
+        throw new IllegalArgumentException("Unsupported number of bits " + nBits);
+    }
+
+    private int getCurrentNibble() {
+        final int headerByte = currentGroup / 2;
+        if (currentGroup % 2 == 0) {
+            return (header[headerByte] & 0xf0) >> 4;
+        } else {
+            return header[headerByte] & 0x0f;
+        }
+    }
+
+    public boolean hasFirst() {
+        return isValid(0);
+    }
+
+    public boolean hasSecond() {
+        return isValid(1);
+    }
+
+    public boolean hasThird() {
+        return isValid(2);
+    }
+
+    public boolean isValid(final int idx) {
+        if (idx < 0 || idx > 2) {
+            throw new IllegalArgumentException("Invalid idx " + idx);
+        }
+
+        return (getCurrentNibble() & (1 << (2 - idx))) != 0;
+    }
+
+    public int get(final int idx, final int nBits) {
+        final int shift = currentGroupBits - idx - nBits;
+        return (currentVal & (((1 << nBits) - 1) << shift)) >>> shift;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/activity/impl/XiaomiSimpleActivityParser.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/activity/impl/XiaomiSimpleActivityParser.java
new file mode 100644
index 0000000..10722c2
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/activity/impl/XiaomiSimpleActivityParser.java
@@ -0,0 +1,253 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.activity.impl;
+
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.SWIM_STYLE;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.TIME_END;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.TIME_START;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.UNIT_UNIX_EPOCH_SECONDS;
+
+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.entities.BaseActivitySummary;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryData;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class XiaomiSimpleActivityParser {
+    private static final Logger LOG = LoggerFactory.getLogger(XiaomiSimpleActivityParser.class);
+
+    public static final String XIAOMI_WORKOUT_TYPE = "xiaomiWorkoutType";
+
+    private final int headerSize;
+    private final List<XiaomiSimpleDataEntry> dataEntries;
+
+    public XiaomiSimpleActivityParser(final int headerSize, final List<XiaomiSimpleDataEntry> dataEntries) {
+        this.headerSize = headerSize;
+        this.dataEntries = dataEntries;
+    }
+
+    public void parse(final BaseActivitySummary summary, final ByteBuffer buf) {
+        final ActivitySummaryData summaryData = new ActivitySummaryData();
+
+        final byte[] header = new byte[headerSize];
+        buf.get(header);
+
+        LOG.debug("Header: {}", GB.hexdump(header));
+
+        for (int i = 0; i < dataEntries.size(); i++) {
+            final XiaomiSimpleDataEntry dataEntry = dataEntries.get(i);
+
+            final Number value = dataEntry.get(buf);
+            if (value == null) {
+                LOG.debug("Skipping unknown field {}", i);
+                continue;
+            }
+
+            // Each bit in the header marks whether the data is valid or not, in order of the fields
+            final boolean validData = (header[i / 8] & (1 << (7 - (i % 8)))) != 0;
+            // FIXME: We can't use the header before identifying the correct field lenggths for unknown fields
+            // or parsing gets out of sync with the header and we will potentially ignore valid data
+            //if (!validData) {
+            //    LOG.debug("Ignoring non-valid data {}", i);
+            //    continue;
+            //}
+
+            if (dataEntry.getKey().equals(TIME_END)) {
+                if (dataEntry.getUnit().equals(UNIT_UNIX_EPOCH_SECONDS)) {
+                    summary.setEndTime(new Date(value.intValue() * 1000L));
+                } else {
+                    throw new IllegalArgumentException("endTime should be an unix epoch");
+                }
+            } else if (dataEntry.getKey().equals(TIME_START)) {
+                // ignored
+            } else if (dataEntry.getKey().equals(SWIM_STYLE)) {
+                String swimStyleName = "unknown";
+                final float swimStyle = value.floatValue();
+
+                if (swimStyle == 0) {
+                    swimStyleName = "medley";
+                } else if (swimStyle == 1) {
+                    swimStyleName = "breaststroke";
+                } else if (swimStyle == 2) {
+                    swimStyleName = "freestyle";
+                } else if (swimStyle == 3) {
+                    swimStyleName = "backstroke";
+                } else if (swimStyle == 4) {
+                    swimStyleName = "butterfly";
+                }
+
+                summaryData.add(dataEntry.getKey(), swimStyleName);
+            } else if (dataEntry.getKey().equals(XIAOMI_WORKOUT_TYPE)) {
+                switch (value.intValue()) {
+                    case 1:
+                        summary.setActivityKind(ActivityKind.OUTDOOR_RUNNING.getCode());
+                        break;
+                    case 2:
+                        summary.setActivityKind(ActivityKind.WALKING.getCode());
+                        break;
+                    case 4:
+                        summary.setActivityKind(ActivityKind.TREKKING.getCode());
+                        break;
+                    case 5:
+                        summary.setActivityKind(ActivityKind.TRAIL_RUN.getCode());
+                        break;
+                    case 6:
+                        summary.setActivityKind(ActivityKind.OUTDOOR_CYCLING.getCode());
+                        break;
+                    case 7:   // indoor cycling   0x0007
+                        summary.setActivityKind(ActivityKind.INDOOR_CYCLING.getCode());
+                        break;
+                    case 8:   // freestyle        0x0008
+                        summary.setActivityKind(ActivityKind.FREE_TRAINING.getCode());
+                        break;
+                    case 12:  // yoga             0x000c
+                        summary.setActivityKind(ActivityKind.YOGA.getCode());
+                        break;
+                    case 15:
+                        summary.setActivityKind(ActivityKind.OUTDOOR_WALKING.getCode());
+                        break;
+                    case 16:  // HIIT             0x0010
+                        summary.setActivityKind(ActivityKind.HIIT.getCode());
+                        break;
+                    case 201: // skateboard       0x00c9
+                        summary.setActivityKind(ActivityKind.SKATEBOARDING.getCode());
+                        break;
+                    case 202: // roller skating   0x00ca
+                        summary.setActivityKind(ActivityKind.ROLLER_SKATING.getCode());
+                        break;
+                    case 301: // stair climbing   0x012d
+                        summary.setActivityKind(ActivityKind.STAIRS.getCode());
+                        break;
+                    case 303: // core training    0x012f
+                        summary.setActivityKind(ActivityKind.CORE_TRAINING.getCode());
+                        break;
+                    case 304: // flexibility      0x0130
+                        summary.setActivityKind(ActivityKind.FLEXIBILITY.getCode());
+                        break;
+                    case 305: // pilates          0x0131
+                        summary.setActivityKind(ActivityKind.PILATES.getCode());
+                        break;
+                    case 307: // stretching       0x0133
+                        summary.setActivityKind(ActivityKind.STRETCHING.getCode());
+                        break;
+                    case 308: // strength         0x0134
+                        summary.setActivityKind(ActivityKind.STRENGTH_TRAINING.getCode());
+                        break;
+                    case 310: // aerobics         0x0136
+                        summary.setActivityKind(ActivityKind.AEROBICS.getCode());
+                        break;
+                    case 399: // indoor-Fitness   0x018f
+                        summary.setActivityKind(ActivityKind.INDOOR_FITNESS.getCode());
+                        break;
+                    case 499: // dancing          0x01f3
+                        summary.setActivityKind(ActivityKind.DANCE.getCode());
+                        break;
+                    case 600: // Soccer           0x0258
+                        summary.setActivityKind(ActivityKind.SOCCER.getCode());
+                        break;
+                    case 601: // basketball       0x0259
+                        summary.setActivityKind(ActivityKind.BASKETBALL.getCode());
+                        break;
+                    case 607: // table tennis     0x025f
+                        summary.setActivityKind(ActivityKind.TABLE_TENNIS.getCode());
+                        break;
+                    case 608: // badminton        0x0260
+                        summary.setActivityKind(ActivityKind.BADMINTON.getCode());
+                        break;
+                    case 609: // tennis           0x0261
+                        summary.setActivityKind(ActivityKind.TENNIS.getCode());
+                        break;
+                    case 614: // billiard          0x0266
+                        summary.setActivityKind(ActivityKind.BILLIARDS.getCode());
+                        break;
+                    case 619: // golf             0x026b
+                        summary.setActivityKind(ActivityKind.GOLF.getCode());
+                        break;
+                    case 700: // ice skating      0x02bc
+                        summary.setActivityKind(ActivityKind.ICE_SKATING.getCode());
+                        break;
+                    case 708: // snowboard        0x02c4
+                        summary.setActivityKind(ActivityKind.SNOWBOARDING.getCode());
+                        break;
+                    case 709: // skiing           0x02c5
+                        summary.setActivityKind(ActivityKind.SKIING.getCode());
+                        break;
+                    case 808: // shuttlecock      0x0328
+                        summary.setActivityKind(ActivityKind.SHUTTLECOCK.getCode());
+                        break;
+                    default:
+                        summary.setActivityKind(ActivityKind.UNKNOWN.getCode());
+                }
+            } else {
+                summaryData.add(dataEntry.getKey(), value.floatValue(), dataEntry.getUnit());
+            }
+        }
+
+        summary.setSummaryData(summaryData.toString());
+    }
+
+    public static class Builder {
+        private int headerSize;
+        private List<XiaomiSimpleDataEntry> dataEntries = new ArrayList<>();
+
+        public Builder setHeaderSize(final int headerSize) {
+            this.headerSize = headerSize;
+            return this;
+        }
+
+        public Builder addByte(final String key, final String unit) {
+            dataEntries.add(new XiaomiSimpleDataEntry(key, unit, buf -> buf.get() & 0xff));
+            return this;
+        }
+
+        public Builder addShort(final String key, final String unit) {
+            dataEntries.add(new XiaomiSimpleDataEntry(key, unit, ByteBuffer::getShort));
+            return this;
+        }
+
+        public Builder addInt(final String key, final String unit) {
+            dataEntries.add(new XiaomiSimpleDataEntry(key, unit, ByteBuffer::getInt));
+            return this;
+        }
+
+        public Builder addFloat(final String key, final String unit) {
+            dataEntries.add(new XiaomiSimpleDataEntry(key, unit, ByteBuffer::getFloat));
+            return this;
+        }
+
+        public Builder addUnknown(final int sizeBytes) {
+            dataEntries.add(new XiaomiSimpleDataEntry(null, null, buf -> {
+                for (int i = 0; i < sizeBytes; i++) {
+                    buf.get();
+                }
+                return null;
+            }));
+            return this;
+        }
+
+        public XiaomiSimpleActivityParser build() {
+            return new XiaomiSimpleActivityParser(headerSize, dataEntries);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/activity/impl/XiaomiSimpleDataEntry.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/activity/impl/XiaomiSimpleDataEntry.java
new file mode 100644
index 0000000..80482bd
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/activity/impl/XiaomiSimpleDataEntry.java
@@ -0,0 +1,47 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.activity.impl;
+
+import java.nio.ByteBuffer;
+
+public class XiaomiSimpleDataEntry {
+    private final String key;
+    private final String unit;
+    private final Getter getter;
+
+    public XiaomiSimpleDataEntry(final String key, final String unit, final Getter getter) {
+        this.key = key;
+        this.unit = unit;
+        this.getter = getter;
+    }
+
+    public String getKey() {
+        return key;
+    }
+
+    public String getUnit() {
+        return unit;
+    }
+
+    public Number get(final ByteBuffer buf) {
+        return getter.get(buf);
+    }
+
+    public interface Getter {
+        Number get(ByteBuffer buf);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/services/AbstractXiaomiService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/services/AbstractXiaomiService.java
new file mode 100644
index 0000000..90f29ad
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/services/AbstractXiaomiService.java
@@ -0,0 +1,72 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.services;
+
+import android.content.Context;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.XiaomiCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.proto.xiaomi.XiaomiProto;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.XiaomiSupport;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+import nodomain.freeyourgadget.gadgetbridge.util.preferences.DevicePrefs;
+
+public abstract class AbstractXiaomiService {
+    private final XiaomiSupport mSupport;
+
+    public AbstractXiaomiService(final XiaomiSupport support) {
+        this.mSupport = support;
+    }
+
+    public void setContext(final Context context) {
+
+    }
+
+    public abstract void handleCommand(final XiaomiProto.Command cmd);
+
+    public void initialize() {
+
+    }
+
+    public void dispose() {
+
+    }
+
+    /**
+     * Handle a preference change.
+     * @param config the preference key
+     * @param prefs the device preferences
+     * @return true if the preference was handled, false otherwise
+     */
+    public boolean onSendConfiguration(final String config, final Prefs prefs) {
+        return false;
+    }
+
+    public XiaomiSupport getSupport() {
+        return mSupport;
+    }
+
+    protected XiaomiCoordinator getCoordinator() {
+        return (XiaomiCoordinator) getSupport().getDevice().getDeviceCoordinator();
+    }
+
+    protected DevicePrefs getDevicePrefs() {
+        return GBApplication.getDevicePrefs(getSupport().getDevice());
+    }
+
+    public void onDisconnect() {}
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/services/XiaomiCalendarService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/services/XiaomiCalendarService.java
new file mode 100644
index 0000000..f01b11a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/services/XiaomiCalendarService.java
@@ -0,0 +1,147 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.services;
+
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SYNC_CALENDAR;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
+import nodomain.freeyourgadget.gadgetbridge.proto.xiaomi.XiaomiProto;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.XiaomiSupport;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.calendar.CalendarEvent;
+import nodomain.freeyourgadget.gadgetbridge.util.calendar.CalendarManager;
+
+public class XiaomiCalendarService extends AbstractXiaomiService {
+    private static final Logger LOG = LoggerFactory.getLogger(XiaomiCalendarService.class);
+
+    public static final int COMMAND_TYPE = 12;
+
+    private static final int CMD_CALENDAR_SET = 1;
+
+    private static final int MAX_EVENTS = 50; // TODO confirm actual limit
+
+    private final Set<CalendarEvent> lastSync = new HashSet<>();
+
+    public XiaomiCalendarService(final XiaomiSupport support) {
+        super(support);
+    }
+
+    @Override
+    public void handleCommand(final XiaomiProto.Command cmd) {
+        LOG.warn("Unknown calendar command {}", cmd.getSubtype());
+    }
+
+    @Override
+    public void initialize() {
+        syncCalendar();
+    }
+
+    @Override
+    public boolean onSendConfiguration(final String config, final Prefs prefs) {
+        switch (config) {
+            case DeviceSettingsPreferenceConst.PREF_SYNC_CALENDAR:
+                syncCalendar();
+                return true;
+        }
+
+        return false;
+    }
+
+    public void onAddCalendarEvent(final CalendarEventSpec ignoredCalendarEventSpec) {
+        // we must sync everything
+        syncCalendar();
+    }
+
+    public void onDeleteCalendarEvent(final byte ignoredType, final long ignoredId) {
+        // we must sync everything
+        syncCalendar();
+    }
+
+    public void syncCalendar() {
+        final boolean syncEnabled = GBApplication.getDeviceSpecificSharedPrefs(getSupport().getDevice().getAddress())
+                .getBoolean(PREF_SYNC_CALENDAR, false);
+
+        final XiaomiProto.CalendarSync.Builder calendarSync = XiaomiProto.CalendarSync.newBuilder();
+
+        if (!syncEnabled) {
+            LOG.debug("Calendar sync is disabled");
+            lastSync.clear();
+            calendarSync.setDisabled(true);
+        } else {
+            final CalendarManager upcomingEvents = new CalendarManager(getSupport().getContext(), getSupport().getDevice().getAddress());
+            final List<CalendarEvent> calendarEvents = upcomingEvents.getCalendarEventList();
+
+            final Set<CalendarEvent> thisSync = new HashSet<>();
+            int nEvents = 0;
+
+            for (final CalendarEvent calendarEvent : calendarEvents) {
+                if (nEvents++ > MAX_EVENTS) {
+                    LOG.warn("Syncing only first {} events of {}", MAX_EVENTS, calendarEvents.size());
+                    break;
+                }
+
+                thisSync.add(calendarEvent);
+
+                int notifyMinutesBefore = 0;
+                if (!calendarEvent.getRemindersAbsoluteTs().isEmpty()) {
+                    notifyMinutesBefore = (int) ((calendarEvent.getBeginSeconds() * 1000L - calendarEvent.getRemindersAbsoluteTs().get(0)) / (1000 * 60));
+                }
+
+                final XiaomiProto.CalendarEvent xiaomiCalendarEvent = XiaomiProto.CalendarEvent.newBuilder()
+                        .setTitle(calendarEvent.getTitle())
+                        .setDescription(StringUtils.ensureNotNull(calendarEvent.getDescription()))
+                        .setLocation(StringUtils.ensureNotNull(calendarEvent.getLocation()))
+                        .setStart(calendarEvent.getBeginSeconds())
+                        .setEnd((int) (calendarEvent.getEnd() / 1000))
+                        .setAllDay(calendarEvent.isAllDay())
+                        .setNotifyMinutesBefore(notifyMinutesBefore)
+                        .build();
+
+                calendarSync.addEvent(xiaomiCalendarEvent);
+            }
+
+            if (thisSync.equals(lastSync)) {
+                LOG.debug("Already synced this set of events, won't send to device");
+                return;
+            }
+
+            lastSync.clear();
+            lastSync.addAll(thisSync);
+        }
+
+        LOG.debug("Syncing {} calendar events", lastSync.size());
+
+        getSupport().sendCommand(
+                "sync calendar",
+                XiaomiProto.Command.newBuilder()
+                        .setType(COMMAND_TYPE)
+                        .setSubtype(CMD_CALENDAR_SET)
+                        .setCalendar(XiaomiProto.Calendar.newBuilder().setCalendarSync(calendarSync))
+                        .build()
+        );
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/services/XiaomiDataUploadService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/services/XiaomiDataUploadService.java
new file mode 100644
index 0000000..4153d2c
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/services/XiaomiDataUploadService.java
@@ -0,0 +1,191 @@
+/*  Copyright (C) 2023-2024 Andreas Shimokawa, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.services;
+
+import androidx.annotation.Nullable;
+
+import com.google.protobuf.ByteString;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Objects;
+
+import nodomain.freeyourgadget.gadgetbridge.proto.xiaomi.XiaomiProto;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.XiaomiCharacteristic;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.XiaomiSupport;
+import nodomain.freeyourgadget.gadgetbridge.util.CheckSums;
+
+public class XiaomiDataUploadService extends AbstractXiaomiService {
+    private static final Logger LOG = LoggerFactory.getLogger(XiaomiDataUploadService.class);
+
+    public static final int COMMAND_TYPE = 22;
+
+    public static final int CMD_UPLOAD_START = 0;
+
+    public static final byte TYPE_WATCHFACE = 16;
+    public static final byte TYPE_FIRMWARE = 32;
+    public static final byte TYPE_NOTIFICATION_ICON = 50;
+
+    private Callback callback;
+
+    private byte currentType;
+    private byte[] currentBytes;
+
+    private int chunkSize;
+
+    public XiaomiDataUploadService(final XiaomiSupport support) {
+        super(support);
+    }
+
+    @Override
+    public void handleCommand(final XiaomiProto.Command cmd) {
+        switch (cmd.getSubtype()) {
+            case CMD_UPLOAD_START:
+                final XiaomiProto.DataUploadAck dataUploadAck = cmd.getDataUpload().getDataUploadAck();
+                LOG.debug("Got upload start, unknown2={}, resumePosition={}", dataUploadAck.getUnknown2(), dataUploadAck.getResumePosition());
+
+                if (dataUploadAck.getUnknown2() != 0 || dataUploadAck.getResumePosition() != 0) {
+                    LOG.warn("Unexpected response");
+                    onUploadFinish(false);
+                    return;
+                }
+
+                if (dataUploadAck.hasChunkSize()) {
+                    chunkSize = dataUploadAck.getChunkSize();
+                } else {
+                    chunkSize = 2048;
+                }
+
+                LOG.debug("Using chunk size of {} bytes", chunkSize);
+                doUpload(currentType, currentBytes);
+                return;
+        }
+
+        LOG.warn("Unknown data upload command {}", cmd.getSubtype());
+    }
+
+    public void setCallback(@Nullable final Callback callback) {
+        if (callback != null && currentBytes != null) {
+            LOG.warn("Already uploading {} for another callback, refusing new callback", currentType);
+            return;
+        }
+
+        this.callback = callback;
+    }
+
+    public void requestUpload(final byte type, final byte[] bytes) {
+        if (this.currentBytes != null) {
+            LOG.warn("Already uploading {}, refusing upload of {}", currentType, type);
+            return;
+        }
+
+        LOG.debug("Requesting upload for {} bytes of type {}", bytes.length, type);
+
+        this.currentType = type;
+        this.currentBytes = bytes;
+
+        getSupport().sendCommand(
+                "request upload",
+                XiaomiProto.Command.newBuilder()
+                        .setType(COMMAND_TYPE)
+                        .setSubtype(CMD_UPLOAD_START)
+                        .setDataUpload(XiaomiProto.DataUpload.newBuilder().setDataUploadRequest(
+                                XiaomiProto.DataUploadRequest.newBuilder()
+                                        .setType(type)
+                                        .setMd5Sum(ByteString.copyFrom(Objects.requireNonNull(CheckSums.md5(bytes))))
+                                        .setSize(bytes.length)
+                        ))
+                        .build()
+        );
+    }
+
+    private void doUpload(final short type, final byte[] bytes) {
+        LOG.debug("Doing upload for {} bytes of type {}", bytes.length, type);
+
+        // type + md5 + size + bytes + crc32
+        final ByteBuffer buf1 = ByteBuffer.allocate(2 + 16 + 4 + bytes.length).order(ByteOrder.LITTLE_ENDIAN);
+        final byte[] md5 = CheckSums.md5(bytes);
+        if (md5 == null) {
+            onUploadFinish(false);
+            return;
+        }
+
+        buf1.put((byte) 0);
+        buf1.put((byte) type);
+        buf1.put(md5);
+        buf1.putInt(bytes.length);
+        buf1.put(bytes);
+
+        final ByteBuffer buf2 = ByteBuffer.allocate(buf1.capacity() + 4).order(ByteOrder.LITTLE_ENDIAN);
+        buf2.put(buf1.array());
+        buf2.putInt(CheckSums.getCRC32(buf1.array()));
+
+        final byte[] payload = buf2.array();
+        final int partSize = chunkSize - 4; // 2 + 2 at beginning of each for total and progress
+        final int totalParts = (int) Math.ceil(payload.length / (float) partSize);
+
+        for (int i = 0; i * partSize < payload.length; i++) {
+            final int currentPart = i + 1;
+            final int startIndex = i * partSize;
+            final int endIndex = Math.min(currentPart * partSize, payload.length);
+            LOG.debug("Uploading part {} of {}, from {} to {}", currentPart, totalParts, startIndex, endIndex);
+            final byte[] chunkToSend = new byte[4 + endIndex - startIndex];
+            BLETypeConversions.writeUint16(chunkToSend, 0, totalParts);
+            BLETypeConversions.writeUint16(chunkToSend, 2, currentPart);
+            System.arraycopy(payload, startIndex, chunkToSend, 4, endIndex - startIndex);
+
+            getSupport().getConnectionSpecificSupport().sendDataChunk("upload part " + currentPart + " of " + totalParts, chunkToSend, new XiaomiCharacteristic.SendCallback() {
+                @Override
+                public void onSend() {
+                    final int progressPercent = Math.round((100.0f * currentPart) / totalParts);
+                    LOG.debug("Data upload progress: {}/{} parts sent ({}% done)", currentPart, totalParts, progressPercent);
+
+                    if (currentPart >= totalParts) {
+                        onUploadFinish(true);
+                    } else if (callback != null) {
+                        callback.onUploadProgress(progressPercent);
+                    }
+                }
+
+                @Override
+                public void onNack() {
+                    LOG.warn("NACK received while uploading part {}/{}", currentPart, totalParts);
+                    // TODO callback.onUploadFinish(false); ?
+                }
+            });
+        }
+    }
+
+    private void onUploadFinish(final boolean success) {
+        this.currentType = 0;
+        this.currentBytes = null;
+
+        if (callback != null) {
+            callback.onUploadFinish(success);
+        }
+    }
+
+    public interface Callback {
+        void onUploadFinish(boolean success);
+
+        void onUploadProgress(int progress);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/services/XiaomiHealthService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/services/XiaomiHealthService.java
new file mode 100644
index 0000000..189da3c
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/services/XiaomiHealthService.java
@@ -0,0 +1,952 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo, Yoran Vulker
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.services;
+
+import android.content.Intent;
+import android.location.Location;
+import android.os.Handler;
+
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import com.google.protobuf.ByteString;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.time.LocalDate;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Set;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdatePreferences;
+import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.XiaomiSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.entities.User;
+import nodomain.freeyourgadget.gadgetbridge.entities.XiaomiActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.externalevents.gps.GBLocationProviderType;
+import nodomain.freeyourgadget.gadgetbridge.externalevents.gps.GBLocationService;
+import nodomain.freeyourgadget.gadgetbridge.externalevents.opentracks.OpenTracksController;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
+import nodomain.freeyourgadget.gadgetbridge.proto.xiaomi.XiaomiProto;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.XiaomiPreferences;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.XiaomiSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.activity.XiaomiActivityFileFetcher;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.activity.XiaomiActivityFileId;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+public class XiaomiHealthService extends AbstractXiaomiService {
+    private static final Logger LOG = LoggerFactory.getLogger(XiaomiHealthService.class);
+
+    public static final int COMMAND_TYPE = 8;
+
+    private static final int CMD_SET_USER_INFO = 0;
+    private static final int CMD_ACTIVITY_FETCH_TODAY = 1;
+    private static final int CMD_ACTIVITY_FETCH_PAST = 2;
+    private static final int CMD_ACTIVITY_FETCH_REQUEST = 3;
+    private static final int CMD_ACTIVITY_FETCH_ACK = 5;
+    private static final int CMD_CONFIG_SPO2_GET = 8;
+    private static final int CMD_CONFIG_SPO2_SET = 9;
+    private static final int CMD_CONFIG_HEART_RATE_GET = 10;
+    private static final int CMD_CONFIG_HEART_RATE_SET = 11;
+    private static final int CMD_CONFIG_STANDING_REMINDER_GET = 12;
+    private static final int CMD_CONFIG_STANDING_REMINDER_SET = 13;
+    private static final int CMD_CONFIG_STRESS_GET = 14;
+    private static final int CMD_CONFIG_STRESS_SET = 15;
+    private static final int CMD_CONFIG_GOAL_NOTIFICATION_GET = 21;
+    private static final int CMD_CONFIG_GOAL_NOTIFICATION_SET = 22;
+    private static final int CMD_WORKOUT_WATCH_STATUS = 26;
+    private static final int CMD_WORKOUT_WATCH_OPEN = 30;
+    private static final int CMD_CONFIG_VITALITY_SCORE_GET = 35;
+    private static final int CMD_CONFIG_VITALITY_SCORE_SET = 36;
+    private static final int CMD_WORKOUT_LOCATION = 48;
+    private static final int CMD_CONFIG_GOALS_GET = 42;
+    private static final int CMD_CONFIG_GOALS_SET = 43;
+    private static final int CMD_REALTIME_STATS_START = 45;
+    private static final int CMD_REALTIME_STATS_STOP = 46;
+    private static final int CMD_REALTIME_STATS_EVENT = 47;
+
+    private static final int GENDER_MALE = 1;
+    private static final int GENDER_FEMALE = 2;
+
+    private static final int WORKOUT_STARTED = 0;
+    private static final int WORKOUT_RESUMED = 1;
+    private static final int WORKOUT_PAUSED = 2;
+    private static final int WORKOUT_FINISHED = 3;
+
+    private boolean realtimeStarted = false;
+    private boolean realtimeOneShot = false;
+    private int previousSteps = -1;
+
+    private boolean gpsStarted = false;
+    private boolean gpsFixAcquired = false;
+    private boolean workoutStarted = false;
+    private final Handler gpsTimeoutHandler = new Handler();
+
+    private final Set<Integer> currentGoals = new LinkedHashSet<>();
+    private final Set<Integer> supportedGoals = new LinkedHashSet<>();
+
+    private static final int GOAL_STEPS = 1; // TODO confirm
+    private static final int GOAL_CALORIES = 2; // TODO confirm
+    private static final int GOAL_MOVING_TIME = 3;
+    private static final int GOAL_STANDING_TIME = 4;
+
+    private final XiaomiActivityFileFetcher activityFetcher = new XiaomiActivityFileFetcher(this);
+
+    public XiaomiHealthService(final XiaomiSupport support) {
+        super(support);
+    }
+
+    @Override
+    public void handleCommand(final XiaomiProto.Command cmd) {
+        switch (cmd.getSubtype()) {
+            case CMD_SET_USER_INFO:
+                LOG.debug("Got user info set ack, status={}", cmd.getStatus());
+                return;
+            case CMD_ACTIVITY_FETCH_TODAY:
+            case CMD_ACTIVITY_FETCH_PAST:
+                handleActivityFetchResponse(cmd.getSubtype(), cmd.getHealth().getActivityRequestFileIds().toByteArray());
+                return;
+            case CMD_CONFIG_SPO2_GET:
+                handleSpo2Config(cmd.getHealth().getSpo2());
+                return;
+            case CMD_CONFIG_SPO2_SET:
+                LOG.debug("Got spo2 set ack, status={}", cmd.getStatus());
+                return;
+            case CMD_CONFIG_HEART_RATE_SET:
+                LOG.debug("Got heart rate set ack, status={}", cmd.getStatus());
+                return;
+            case CMD_CONFIG_HEART_RATE_GET:
+                handleHeartRateConfig(cmd.getHealth().getHeartRate());
+                return;
+            case CMD_CONFIG_STANDING_REMINDER_GET:
+                handleStandingReminderConfig(cmd.getHealth().getStandingReminder());
+                return;
+            case CMD_CONFIG_STANDING_REMINDER_SET:
+                LOG.debug("Got standing reminder set ack, status={}", cmd.getStatus());
+                return;
+            case CMD_CONFIG_STRESS_GET:
+                handleStressConfig(cmd.getHealth().getStress());
+                return;
+            case CMD_CONFIG_STRESS_SET:
+                LOG.debug("Got stress set ack, status={}", cmd.getStatus());
+                return;
+            case CMD_CONFIG_GOAL_NOTIFICATION_GET:
+                handleGoalNotificationConfig(cmd.getHealth().getGoalNotification());
+                return;
+            case CMD_CONFIG_GOAL_NOTIFICATION_SET:
+                LOG.debug("Got goal notification set ack, status={}", cmd.getStatus());
+                return;
+            case CMD_CONFIG_GOALS_GET:
+                handleGoalsConfig(cmd.getHealth().getGoalsConfig());
+                return;
+            case CMD_CONFIG_GOALS_SET:
+                LOG.debug("Got goals config set ack, status={}", cmd.getStatus());
+                return;
+            case CMD_CONFIG_VITALITY_SCORE_GET:
+                handleVitalityScore(cmd.getHealth().getVitalityScore());
+                return;
+            case CMD_CONFIG_VITALITY_SCORE_SET:
+                LOG.debug("Got vitality score set ack, status={}", cmd.getStatus());
+                return;
+            case CMD_WORKOUT_WATCH_STATUS:
+                handleWorkoutStatus(cmd.getHealth().getWorkoutStatusWatch());
+                return;
+            case CMD_WORKOUT_WATCH_OPEN:
+                handleWorkoutOpen(cmd.getHealth().getWorkoutOpenWatch());
+                return;
+            case CMD_REALTIME_STATS_EVENT:
+                handleRealtimeStats(cmd.getHealth().getRealTimeStats());
+                return;
+        }
+
+        LOG.warn("Unknown health command {}", cmd.getSubtype());
+    }
+
+    @Override
+    public void initialize() {
+        setUserInfo();
+        getSupport().sendCommand("get spo2 config", COMMAND_TYPE, CMD_CONFIG_SPO2_GET);
+        getSupport().sendCommand("get heart rate config", COMMAND_TYPE, CMD_CONFIG_HEART_RATE_GET);
+        getSupport().sendCommand("get standing reminders config", COMMAND_TYPE, CMD_CONFIG_STANDING_REMINDER_GET);
+        getSupport().sendCommand("get stress config", COMMAND_TYPE, CMD_CONFIG_STRESS_GET);
+        getSupport().sendCommand("get goal notification config", COMMAND_TYPE, CMD_CONFIG_GOAL_NOTIFICATION_GET);
+        getSupport().sendCommand("get goals config", COMMAND_TYPE, CMD_CONFIG_GOALS_GET);
+        getSupport().sendCommand("get vitality score config", COMMAND_TYPE, CMD_CONFIG_VITALITY_SCORE_GET);
+    }
+
+    @Override
+    public void dispose() {
+        activityFetcher.dispose();
+    }
+
+    @Override
+    public boolean onSendConfiguration(final String config, final Prefs prefs) {
+        switch (config) {
+            case ActivityUser.PREF_USER_HEIGHT_CM:
+            case ActivityUser.PREF_USER_WEIGHT_KG:
+            case ActivityUser.PREF_USER_DATE_OF_BIRTH:
+            case ActivityUser.PREF_USER_GENDER:
+            case ActivityUser.PREF_USER_CALORIES_BURNT:
+            case ActivityUser.PREF_USER_STEPS_GOAL:
+            case ActivityUser.PREF_USER_GOAL_STANDING_TIME_HOURS:
+            case ActivityUser.PREF_USER_ACTIVETIME_MINUTES:
+                setUserInfo();
+                return true;
+            case DeviceSettingsPreferenceConst.PREF_USER_FITNESS_GOAL_NOTIFICATION:
+                sendGoalNotificationConfig();
+                return true;
+            case DeviceSettingsPreferenceConst.PREF_USER_FITNESS_GOAL_SECONDARY:
+                sendGoalsConfig();
+                return true;
+            case DeviceSettingsPreferenceConst.PREF_VITALITY_SCORE_7_DAY:
+            case DeviceSettingsPreferenceConst.PREF_VITALITY_SCORE_DAILY:
+                sendVitalityScoreConfig();
+                return true;
+            case DeviceSettingsPreferenceConst.PREF_HEARTRATE_USE_FOR_SLEEP_DETECTION:
+            case DeviceSettingsPreferenceConst.PREF_HEARTRATE_SLEEP_BREATHING_QUALITY_MONITORING:
+            case DeviceSettingsPreferenceConst.PREF_HEARTRATE_MEASUREMENT_INTERVAL:
+            case DeviceSettingsPreferenceConst.PREF_HEARTRATE_ALERT_ENABLED:
+            case DeviceSettingsPreferenceConst.PREF_HEARTRATE_ALERT_HIGH_THRESHOLD:
+            case DeviceSettingsPreferenceConst.PREF_HEARTRATE_ALERT_LOW_THRESHOLD:
+                setHeartRateConfig();
+                return true;
+            case DeviceSettingsPreferenceConst.PREF_SPO2_ALL_DAY_MONITORING:
+            case DeviceSettingsPreferenceConst.PREF_SPO2_LOW_ALERT_THRESHOLD:
+                setSpo2Config();
+                return true;
+            case DeviceSettingsPreferenceConst.PREF_INACTIVITY_ENABLE:
+            case DeviceSettingsPreferenceConst.PREF_INACTIVITY_START:
+            case DeviceSettingsPreferenceConst.PREF_INACTIVITY_END:
+            case DeviceSettingsPreferenceConst.PREF_INACTIVITY_DND:
+            case DeviceSettingsPreferenceConst.PREF_INACTIVITY_DND_START:
+            case DeviceSettingsPreferenceConst.PREF_INACTIVITY_DND_END:
+                setStandingReminderConfig();
+                return true;
+            case DeviceSettingsPreferenceConst.PREF_HEARTRATE_STRESS_MONITORING:
+            case DeviceSettingsPreferenceConst.PREF_HEARTRATE_STRESS_RELAXATION_REMINDER:
+                setStressConfig();
+                return true;
+        }
+
+        return false;
+    }
+
+    public void setUserInfo() {
+        LOG.debug("Setting user info");
+
+        final ActivityUser activityUser = new ActivityUser();
+        final LocalDate dateOfBirth = activityUser.getDateOfBirth();
+        final int birthYear = dateOfBirth.getYear();
+        final byte birthMonth = (byte) dateOfBirth.getMonthValue();
+        final byte birthDay = (byte) dateOfBirth.getDayOfMonth();
+
+        final int genderInt = activityUser.getGender() != ActivityUser.GENDER_FEMALE ? GENDER_MALE : GENDER_FEMALE;  // TODO other gender?
+
+        final int age = activityUser.getAge();
+        // Compute the approximate max heart rate from the user age
+        // TODO max heart rate should be input by the user
+        int maxHeartRate = (int) Math.round(age <= 40 ? 220 - age : 207 - 0.7 * age);
+        if (maxHeartRate < 100 || maxHeartRate > 220) {
+            maxHeartRate = 175;
+        }
+
+        final XiaomiProto.UserInfo userInfo = XiaomiProto.UserInfo.newBuilder()
+                .setHeight(activityUser.getHeightCm())
+                .setWeight(activityUser.getWeightKg())
+                .setBirthday(Integer.parseInt(String.format(Locale.ROOT, "%04d%02d%02d", birthYear, birthMonth, birthDay)))
+                .setGender(genderInt)
+                .setMaxHeartRate(maxHeartRate)
+                .setGoalCalories(activityUser.getCaloriesBurntGoal())
+                .setGoalSteps(activityUser.getStepsGoal())
+                .setGoalStanding(activityUser.getStandingTimeGoalHours())
+                .setGoalMoving(activityUser.getActiveTimeGoalMinutes())
+                .build();
+
+        final XiaomiProto.Health health = XiaomiProto.Health.newBuilder()
+                .setUserInfo(userInfo)
+                .build();
+
+        getSupport().sendCommand(
+                "set user info",
+                XiaomiProto.Command.newBuilder()
+                        .setType(COMMAND_TYPE)
+                        .setSubtype(CMD_SET_USER_INFO)
+                        .setHealth(health)
+                        .build()
+        );
+    }
+
+    private void handleGoalNotificationConfig(final XiaomiProto.GoalNotification goalNotification) {
+        LOG.debug("Got goal notification config");
+
+        final GBDeviceEventUpdatePreferences eventUpdatePreferences = new GBDeviceEventUpdatePreferences()
+                .withPreference(XiaomiPreferences.FEAT_GOAL_NOTIFICATION, true)
+                .withPreference(DeviceSettingsPreferenceConst.PREF_USER_FITNESS_GOAL_NOTIFICATION, goalNotification.getEnabled());
+
+        getSupport().evaluateGBDeviceEvent(eventUpdatePreferences);
+    }
+
+    public void sendGoalNotificationConfig() {
+        final boolean enabled = getDevicePrefs().getBoolean(DeviceSettingsPreferenceConst.PREF_USER_FITNESS_GOAL_NOTIFICATION, false);
+
+        LOG.debug("Setting goal notification enabled = {}", enabled);
+
+        final XiaomiProto.GoalNotification.Builder goalNotification = XiaomiProto.GoalNotification.newBuilder()
+                .setEnabled(enabled)
+                .setUnknown2(1);
+
+        final XiaomiProto.Health health = XiaomiProto.Health.newBuilder()
+                .setGoalNotification(goalNotification)
+                .build();
+
+        getSupport().sendCommand(
+                "set goal notification config",
+                XiaomiProto.Command.newBuilder()
+                        .setType(COMMAND_TYPE)
+                        .setSubtype(CMD_CONFIG_GOAL_NOTIFICATION_SET)
+                        .setHealth(health)
+                        .build()
+        );
+    }
+
+    private void handleGoalsConfig(final XiaomiProto.GoalsConfig goalsConfig) {
+        LOG.debug("Got goals config");
+
+        currentGoals.clear();
+        supportedGoals.clear();
+
+        for (final XiaomiProto.Goal goal : goalsConfig.getCurrentGoalsList()) {
+            currentGoals.add(goal.getId());
+        }
+        for (final XiaomiProto.Goal goal : goalsConfig.getSupportedGoalsList()) {
+            supportedGoals.add(goal.getId());
+        }
+
+        final boolean secondaryGoalSupported = supportedGoals.contains(GOAL_STANDING_TIME) || supportedGoals.contains(GOAL_MOVING_TIME);
+        final String secondaryValue = currentGoals.contains(GOAL_MOVING_TIME) ? "active_time" : "standing_time";
+
+        final GBDeviceEventUpdatePreferences eventUpdatePreferences = new GBDeviceEventUpdatePreferences()
+                .withPreference(XiaomiPreferences.FEAT_GOAL_SECONDARY, secondaryGoalSupported)
+                .withPreference(DeviceSettingsPreferenceConst.PREF_USER_FITNESS_GOAL_SECONDARY, secondaryValue);
+
+        getSupport().evaluateGBDeviceEvent(eventUpdatePreferences);
+    }
+
+    public void sendGoalsConfig() {
+        final String goalSecondary = getDevicePrefs().getString(DeviceSettingsPreferenceConst.PREF_USER_FITNESS_GOAL_SECONDARY, "standing_time");
+
+        LOG.debug("Setting goals config = {}", goalSecondary);
+
+        final XiaomiProto.GoalsConfig.Builder goalsConfig = XiaomiProto.GoalsConfig.newBuilder();
+
+        for (final Integer currentGoal : currentGoals) {
+            if (!currentGoal.equals(GOAL_STANDING_TIME) && !currentGoal.equals(GOAL_MOVING_TIME)) {
+                goalsConfig.addCurrentGoals(XiaomiProto.Goal.newBuilder().setId(currentGoal));
+            }
+        }
+
+        if (goalSecondary.equals("active_time")) {
+            goalsConfig.addCurrentGoals(XiaomiProto.Goal.newBuilder().setId(GOAL_MOVING_TIME));
+        } else {
+            goalsConfig.addCurrentGoals(XiaomiProto.Goal.newBuilder().setId(GOAL_STANDING_TIME));
+        }
+
+        for (final Integer supportedGoal : supportedGoals) {
+            goalsConfig.addSupportedGoals(XiaomiProto.Goal.newBuilder().setId(supportedGoal));
+        }
+
+        final XiaomiProto.Health health = XiaomiProto.Health.newBuilder()
+                .setGoalsConfig(goalsConfig)
+                .build();
+
+        getSupport().sendCommand(
+                "set goals config",
+                XiaomiProto.Command.newBuilder()
+                        .setType(COMMAND_TYPE)
+                        .setSubtype(CMD_CONFIG_GOALS_SET)
+                        .setHealth(health)
+                        .build()
+        );
+    }
+
+    private void handleVitalityScore(final XiaomiProto.VitalityScore vitalityScore) {
+        LOG.debug("Got vitality score config");
+
+        final GBDeviceEventUpdatePreferences eventUpdatePreferences = new GBDeviceEventUpdatePreferences()
+                .withPreference(XiaomiPreferences.FEAT_VITALITY_SCORE, true)
+                .withPreference(DeviceSettingsPreferenceConst.PREF_VITALITY_SCORE_7_DAY, vitalityScore.getSevenDay())
+                .withPreference(DeviceSettingsPreferenceConst.PREF_VITALITY_SCORE_DAILY, vitalityScore.getDailyProgress());
+
+        getSupport().evaluateGBDeviceEvent(eventUpdatePreferences);
+    }
+
+    public void sendVitalityScoreConfig() {
+        final boolean prefSevenDay = getDevicePrefs().getBoolean(DeviceSettingsPreferenceConst.PREF_VITALITY_SCORE_7_DAY, false);
+        final boolean prefDaily = getDevicePrefs().getBoolean(DeviceSettingsPreferenceConst.PREF_VITALITY_SCORE_DAILY, false);
+
+        LOG.debug("Setting vitality score config, 7day={}, daily={}", prefSevenDay, prefDaily);
+
+        final XiaomiProto.VitalityScore vitalityScore = XiaomiProto.VitalityScore.newBuilder()
+                .setSevenDay(prefSevenDay)
+                .setDailyProgress(prefDaily)
+                .build();
+
+        final XiaomiProto.Health health = XiaomiProto.Health.newBuilder()
+                .setVitalityScore(vitalityScore)
+                .build();
+
+        getSupport().sendCommand(
+                "set vitality score config",
+                XiaomiProto.Command.newBuilder()
+                        .setType(COMMAND_TYPE)
+                        .setSubtype(CMD_CONFIG_VITALITY_SCORE_SET)
+                        .setHealth(health)
+                        .build()
+        );
+    }
+
+    private void handleSpo2Config(final XiaomiProto.SpO2 spo2) {
+        LOG.debug("Got SpO2 config");
+
+        final GBDeviceEventUpdatePreferences eventUpdatePreferences = new GBDeviceEventUpdatePreferences()
+                .withPreference(XiaomiPreferences.FEAT_SPO2, true)
+                .withPreference(DeviceSettingsPreferenceConst.PREF_SPO2_ALL_DAY_MONITORING, spo2.getAllDayTracking())
+                .withPreference(
+                        DeviceSettingsPreferenceConst.PREF_SPO2_LOW_ALERT_THRESHOLD,
+                        String.valueOf(spo2.getAlarmLow().getAlarmLowEnabled() ? spo2.getAlarmLow().getAlarmLowThreshold() : 0)
+                );
+
+        getSupport().evaluateGBDeviceEvent(eventUpdatePreferences);
+    }
+
+    private void setSpo2Config() {
+        LOG.debug("Set SpO2 config");
+
+        final Prefs prefs = getDevicePrefs();
+        final boolean allDayMonitoring = prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_SPO2_ALL_DAY_MONITORING, false);
+        final int lowAlertThreshold = prefs.getInt(DeviceSettingsPreferenceConst.PREF_SPO2_LOW_ALERT_THRESHOLD, 0);
+
+        final XiaomiProto.Spo2AlarmLow.Builder spo2alarmLowBuilder = XiaomiProto.Spo2AlarmLow.newBuilder()
+                .setAlarmLowEnabled(lowAlertThreshold != 0);
+
+        if (lowAlertThreshold != 0) {
+            spo2alarmLowBuilder.setAlarmLowThreshold(lowAlertThreshold);
+        }
+
+        final XiaomiProto.SpO2.Builder spo2 = XiaomiProto.SpO2.newBuilder()
+                .setUnknown1(1)
+                .setAllDayTracking(allDayMonitoring)
+                .setAlarmLow(spo2alarmLowBuilder);
+
+        getSupport().sendCommand(
+                "set spo2 config",
+                XiaomiProto.Command.newBuilder()
+                        .setType(COMMAND_TYPE)
+                        .setSubtype(CMD_CONFIG_SPO2_SET)
+                        .setHealth(XiaomiProto.Health.newBuilder().setSpo2(spo2))
+                        .build()
+        );
+    }
+
+    private void handleHeartRateConfig(final XiaomiProto.HeartRate heartRate) {
+        LOG.debug("Got heart rate config");
+
+        final GBDeviceEventUpdatePreferences eventUpdatePreferences = new GBDeviceEventUpdatePreferences();
+        if (heartRate.getDisabled()) {
+            eventUpdatePreferences.withPreference(DeviceSettingsPreferenceConst.PREF_HEARTRATE_MEASUREMENT_INTERVAL, "0");
+        } else if (heartRate.getInterval() == 0) {
+            // smart
+            eventUpdatePreferences.withPreference(DeviceSettingsPreferenceConst.PREF_HEARTRATE_MEASUREMENT_INTERVAL, "-1");
+        } else {
+            eventUpdatePreferences.withPreference(DeviceSettingsPreferenceConst.PREF_HEARTRATE_MEASUREMENT_INTERVAL, String.valueOf(heartRate.getInterval()));
+        }
+
+        eventUpdatePreferences.withPreference(DeviceSettingsPreferenceConst.PREF_HEARTRATE_USE_FOR_SLEEP_DETECTION, heartRate.getAdvancedMonitoring().getEnabled());
+        eventUpdatePreferences.withPreference(DeviceSettingsPreferenceConst.PREF_HEARTRATE_SLEEP_BREATHING_QUALITY_MONITORING, heartRate.getBreathingScore() == 1);
+
+        eventUpdatePreferences.withPreference(
+                DeviceSettingsPreferenceConst.PREF_HEARTRATE_ALERT_HIGH_THRESHOLD,
+                String.valueOf(heartRate.getAlarmHighEnabled() ? heartRate.getAlarmHighThreshold() : 0)
+        );
+
+        eventUpdatePreferences.withPreference(
+                DeviceSettingsPreferenceConst.PREF_HEARTRATE_ALERT_LOW_THRESHOLD,
+                String.valueOf(heartRate.getHeartRateAlarmLow().getAlarmLowEnabled() ? heartRate.getHeartRateAlarmLow().getAlarmLowThreshold() : 0)
+        );
+
+        getSupport().evaluateGBDeviceEvent(eventUpdatePreferences);
+    }
+
+    public void setHeartRateConfig() {
+        final Prefs prefs = getDevicePrefs();
+
+        final boolean sleepDetection = prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_HEARTRATE_USE_FOR_SLEEP_DETECTION, false);
+        final boolean sleepBreathingQuality = prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_HEARTRATE_SLEEP_BREATHING_QUALITY_MONITORING, false);
+        final int intervalSeconds = prefs.getInt(DeviceSettingsPreferenceConst.PREF_HEARTRATE_MEASUREMENT_INTERVAL, 0);
+        final int alertHigh = prefs.getInt(DeviceSettingsPreferenceConst.PREF_HEARTRATE_ALERT_HIGH_THRESHOLD, 0);
+        final int alertLow = prefs.getInt(DeviceSettingsPreferenceConst.PREF_HEARTRATE_ALERT_LOW_THRESHOLD, 0);
+
+        int intervalMin;
+        if (intervalSeconds == -1) {
+            // Smart
+            intervalMin = 0;
+        } else {
+            intervalMin = intervalSeconds / 60;
+        }
+
+        LOG.debug(
+                "Set heart rate config: sleepDetection={}, sleepBreathingQuality={}, intervalSeconds={}, alertHigh={}, alertLow={}",
+                sleepDetection,
+                sleepBreathingQuality,
+                intervalSeconds,
+                alertHigh,
+                alertLow
+        );
+
+        final XiaomiProto.HeartRate.Builder heartRate = XiaomiProto.HeartRate.newBuilder()
+                .setDisabled(intervalSeconds == 0)
+                .setInterval(intervalMin)
+                .setAdvancedMonitoring(XiaomiProto.AdvancedMonitoring.newBuilder()
+                        .setEnabled(sleepDetection))
+                .setBreathingScore(sleepBreathingQuality ? 1 : 2)
+                .setAlarmHighEnabled(alertHigh > 0)
+                .setAlarmHighThreshold(alertHigh)
+                .setHeartRateAlarmLow(XiaomiProto.HeartRateAlarmLow.newBuilder()
+                        .setAlarmLowEnabled(alertLow > 0)
+                        .setAlarmLowThreshold(alertLow))
+                .setUnknown7(1);
+
+        getSupport().sendCommand(
+                "set heart rate config",
+                XiaomiProto.Command.newBuilder()
+                        .setType(COMMAND_TYPE)
+                        .setSubtype(CMD_CONFIG_HEART_RATE_SET)
+                        .setHealth(XiaomiProto.Health.newBuilder().setHeartRate(heartRate))
+                        .build()
+        );
+    }
+
+    private void handleStandingReminderConfig(final XiaomiProto.StandingReminder standingReminder) {
+        LOG.debug("Got standing reminder config");
+
+        final String start = XiaomiPreferences.prefFromHourMin(standingReminder.getStart());
+        final String end = XiaomiPreferences.prefFromHourMin(standingReminder.getEnd());
+        final String dndStart = XiaomiPreferences.prefFromHourMin(standingReminder.getDndStart());
+        final String dndEnd = XiaomiPreferences.prefFromHourMin(standingReminder.getDndEnd());
+
+        final GBDeviceEventUpdatePreferences eventUpdatePreferences = new GBDeviceEventUpdatePreferences()
+                .withPreference(XiaomiPreferences.FEAT_INACTIVITY, true)
+                .withPreference(DeviceSettingsPreferenceConst.PREF_INACTIVITY_ENABLE, standingReminder.getEnabled())
+                .withPreference(DeviceSettingsPreferenceConst.PREF_INACTIVITY_START, start)
+                .withPreference(DeviceSettingsPreferenceConst.PREF_INACTIVITY_END, end)
+                .withPreference(DeviceSettingsPreferenceConst.PREF_INACTIVITY_DND, standingReminder.getDnd())
+                .withPreference(DeviceSettingsPreferenceConst.PREF_INACTIVITY_DND_START, dndStart)
+                .withPreference(DeviceSettingsPreferenceConst.PREF_INACTIVITY_DND_END, dndEnd);
+
+        getSupport().evaluateGBDeviceEvent(eventUpdatePreferences);
+    }
+
+    private void setStandingReminderConfig() {
+        LOG.debug("Set standing reminder config");
+
+        final Prefs prefs = getDevicePrefs();
+        final boolean enabled = prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_INACTIVITY_ENABLE, false);
+        final Date start = prefs.getTimePreference(DeviceSettingsPreferenceConst.PREF_INACTIVITY_START, "06:00");
+        final Date end = prefs.getTimePreference(DeviceSettingsPreferenceConst.PREF_INACTIVITY_END, "22:00");
+        final boolean dnd = prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_INACTIVITY_DND, false);
+        final Date dndStart = prefs.getTimePreference(DeviceSettingsPreferenceConst.PREF_INACTIVITY_DND_START, "12:00");
+        final Date dndEnd = prefs.getTimePreference(DeviceSettingsPreferenceConst.PREF_INACTIVITY_DND_END, "14:00");
+
+        final XiaomiProto.StandingReminder standingReminder = XiaomiProto.StandingReminder.newBuilder()
+                .setEnabled(enabled)
+                .setStart(XiaomiPreferences.prefToHourMin(start))
+                .setEnd(XiaomiPreferences.prefToHourMin(end))
+                .setDnd(dnd)
+                .setDndStart(XiaomiPreferences.prefToHourMin(dndStart))
+                .setDndEnd(XiaomiPreferences.prefToHourMin(dndEnd))
+                .build();
+
+        getSupport().sendCommand(
+                "set standing reminder config",
+                XiaomiProto.Command.newBuilder()
+                        .setType(COMMAND_TYPE)
+                        .setSubtype(CMD_CONFIG_STANDING_REMINDER_SET)
+                        .setHealth(XiaomiProto.Health.newBuilder().setStandingReminder(standingReminder))
+                        .build()
+        );
+    }
+
+    private void handleStressConfig(final XiaomiProto.Stress stress) {
+        LOG.debug("Got stress config");
+
+        final GBDeviceEventUpdatePreferences eventUpdatePreferences = new GBDeviceEventUpdatePreferences()
+                .withPreference(XiaomiPreferences.FEAT_STRESS, true)
+                .withPreference(DeviceSettingsPreferenceConst.PREF_HEARTRATE_STRESS_MONITORING, stress.getAllDayTracking())
+                .withPreference(DeviceSettingsPreferenceConst.PREF_HEARTRATE_STRESS_RELAXATION_REMINDER, stress.getRelaxReminder().getEnabled());
+
+        getSupport().evaluateGBDeviceEvent(eventUpdatePreferences);
+    }
+
+    private void setStressConfig() {
+        LOG.debug("Set stress config");
+
+        final Prefs prefs = getDevicePrefs();
+        final boolean enabled = prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_HEARTRATE_STRESS_MONITORING, false);
+        final boolean relaxReminder = prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_HEARTRATE_STRESS_RELAXATION_REMINDER, false);
+
+        final XiaomiProto.Stress.Builder stress = XiaomiProto.Stress.newBuilder()
+                .setAllDayTracking(enabled)
+                .setRelaxReminder(XiaomiProto.RelaxReminder.newBuilder().setEnabled(relaxReminder).setUnknown2(0));
+
+        getSupport().sendCommand(
+                "set stress config",
+                XiaomiProto.Command.newBuilder()
+                        .setType(COMMAND_TYPE)
+                        .setSubtype(CMD_CONFIG_STRESS_SET)
+                        .setHealth(XiaomiProto.Health.newBuilder().setStress(stress))
+                        .build()
+        );
+    }
+
+    private void handleWorkoutOpen(final XiaomiProto.WorkoutOpenWatch workoutOpenWatch) {
+        LOG.debug(
+                "Workout open on watch: {}, workoutStarted={}, gpsStarted={}, gpsFixAcquired={}",
+                workoutOpenWatch.getSport(),
+                workoutStarted,
+                gpsStarted,
+                gpsFixAcquired
+        );
+
+        workoutStarted = false;
+
+        final boolean sendGpsToBand = getDevicePrefs().getBoolean(DeviceSettingsPreferenceConst.PREF_WORKOUT_SEND_GPS_TO_BAND, false);
+        if (!sendGpsToBand) {
+            getSupport().sendCommand(
+                    "send location disabled",
+                    XiaomiProto.Command.newBuilder()
+                            .setType(COMMAND_TYPE)
+                            .setSubtype(CMD_WORKOUT_WATCH_OPEN)
+                            .setHealth(XiaomiProto.Health.newBuilder().setWorkoutOpenReply(
+                                    XiaomiProto.WorkoutOpenReply.newBuilder()
+                                            .setUnknown1(3)
+                                            .setUnknown2(2)
+                                            .setUnknown3(10)
+                            ))
+                            .build()
+            );
+            return;
+        }
+
+        if (!gpsStarted) {
+            gpsStarted = true;
+            gpsFixAcquired = false;
+            GBLocationService.start(getSupport().getContext(), getSupport().getDevice(), GBLocationProviderType.GPS, 1000);
+        }
+
+        gpsTimeoutHandler.removeCallbacksAndMessages(null);
+        // Timeout if the watch stops sending workout open
+        gpsTimeoutHandler.postDelayed(() -> {
+            LOG.debug("Timed out waiting for workout");
+            gpsStarted = false;
+            gpsFixAcquired = false;
+            GBLocationService.stop(getSupport().getContext(), getSupport().getDevice());
+        }, 5000);
+    }
+
+    private void handleWorkoutStatus(final XiaomiProto.WorkoutStatusWatch workoutStatus) {
+        LOG.debug("Got workout status: {}", workoutStatus.getStatus());
+
+        final boolean startOnPhone = getDevicePrefs().getBoolean(DeviceSettingsPreferenceConst.PREF_WORKOUT_START_ON_PHONE, false);
+
+        switch (workoutStatus.getStatus()) {
+            case WORKOUT_STARTED:
+                workoutStarted = true;
+                gpsTimeoutHandler.removeCallbacksAndMessages(null);
+                if (startOnPhone) {
+                    OpenTracksController.startRecording(getSupport().getContext(), sportToActivityKind(workoutStatus.getSport()));
+                }
+                break;
+            case WORKOUT_RESUMED:
+            case WORKOUT_PAUSED:
+                break;
+            case WORKOUT_FINISHED:
+                gpsStarted = false;
+                gpsFixAcquired = false;
+                GBLocationService.stop(getSupport().getContext(), getSupport().getDevice());
+                if (startOnPhone) {
+                    OpenTracksController.stopRecording(getSupport().getContext());
+                }
+                break;
+        }
+    }
+
+    public void onSetGpsLocation(final Location location) {
+        if (!gpsFixAcquired) {
+            gpsFixAcquired = true;
+            getSupport().sendCommand(
+                    "send gps fix",
+                    XiaomiProto.Command.newBuilder()
+                            .setType(COMMAND_TYPE)
+                            .setSubtype(CMD_WORKOUT_WATCH_OPEN)
+                            .setHealth(XiaomiProto.Health.newBuilder().setWorkoutOpenReply(
+                                    XiaomiProto.WorkoutOpenReply.newBuilder()
+                                            .setUnknown1(0)
+                                            .setUnknown2(2)
+                                            .setUnknown3(2)
+                            ))
+                            .build()
+            );
+        }
+
+        if (workoutStarted) {
+            final XiaomiProto.WorkoutLocation.Builder workoutLocation = XiaomiProto.WorkoutLocation.newBuilder()
+                    .setUnknown1(2)
+                    .setTimestamp((int) (location.getTime() / 1000L))
+                    .setLongitude(location.getLongitude())
+                    .setLatitude(location.getLatitude())
+                    .setAltitude(location.getAltitude())
+                    .setSpeed(location.getSpeed())
+                    .setBearing(location.getBearing());
+
+            // FIXME: Check the value for these during actual workouts, but it seems to work without them
+            //if (location.hasAccuracy() && location.getAccuracy() != 100) {
+            //    workoutLocation.setHorizontalAccuracy(location.getAccuracy());
+            //}
+            //if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && location.hasVerticalAccuracy() && location.getVerticalAccuracyMeters() != 100) {
+            //    workoutLocation.setVerticalAccuracy(location.getVerticalAccuracyMeters());
+            //}
+
+            getSupport().sendCommand(
+                    "send gps location",
+                    XiaomiProto.Command.newBuilder()
+                            .setType(COMMAND_TYPE)
+                            .setSubtype(CMD_WORKOUT_LOCATION)
+                            .setHealth(XiaomiProto.Health.newBuilder().setWorkoutLocation(workoutLocation))
+                            .build()
+            );
+        }
+    }
+
+    private ActivityKind sportToActivityKind(final int sport) {
+        switch (sport) {
+            case 1: // outdoor run
+            case 5: // trail run
+                return ActivityKind.RUNNING;
+            case 2:
+                return ActivityKind.WALKING;
+            case 3: // hiking
+            case 4: // trekking
+                return ActivityKind.HIKING;
+            case 6:
+                return ActivityKind.CYCLING;
+        }
+
+        LOG.warn("Unknown sport {}", sport);
+
+        return ActivityKind.UNKNOWN;
+    }
+
+    public XiaomiActivityFileFetcher getActivityFetcher() {
+        return activityFetcher;
+    }
+
+    public void onFetchRecordedData(final int dataTypes) {
+        LOG.debug("Fetch recorded data: {}", String.format("0x%08X", dataTypes));
+
+        fetchRecordedDataToday();
+    }
+
+    private void fetchRecordedDataToday() {
+        getSupport().sendCommand(
+                "fetch recorded data today",
+                XiaomiProto.Command.newBuilder()
+                        .setType(COMMAND_TYPE)
+                        .setSubtype(CMD_ACTIVITY_FETCH_TODAY)
+                        .setHealth(XiaomiProto.Health.newBuilder().setActivitySyncRequestToday(
+                                // TODO official app sends 0, but sometimes 1?
+                                XiaomiProto.ActivitySyncRequestToday.newBuilder().setUnknown1(0)
+                        ))
+                        .build()
+        );
+    }
+
+    private void fetchRecordedDataPast() {
+        getSupport().sendCommand(
+                "fetch recorded data past",
+                XiaomiProto.Command.newBuilder()
+                        .setType(COMMAND_TYPE)
+                        .setSubtype(CMD_ACTIVITY_FETCH_PAST)
+                        .build()
+        );
+    }
+
+    public void requestRecordedData(final XiaomiActivityFileId fileId) {
+        getSupport().sendCommand(
+                "request recorded data",
+                XiaomiProto.Command.newBuilder()
+                        .setType(COMMAND_TYPE)
+                        .setSubtype(CMD_ACTIVITY_FETCH_REQUEST)
+                        .setHealth(XiaomiProto.Health.newBuilder().setActivityRequestFileIds(
+                                ByteString.copyFrom(fileId.toBytes())
+                        ))
+                        .build()
+        );
+    }
+
+    public void ackRecordedData(final XiaomiActivityFileId fileId) {
+        getSupport().sendCommand(
+                "ack recorded data",
+                XiaomiProto.Command.newBuilder()
+                        .setType(COMMAND_TYPE)
+                        .setSubtype(CMD_ACTIVITY_FETCH_ACK)
+                        .setHealth(XiaomiProto.Health.newBuilder().setActivitySyncAckFileIds(
+                                ByteString.copyFrom(fileId.toBytes())
+                        ))
+                        .build()
+        );
+    }
+
+    public void handleActivityFetchResponse(final int subtype, final byte[] recordIds) {
+        if ((recordIds.length % 7) != 0) {
+            LOG.warn("recordIds {} length = {}, not a multiple of 7, can't parse", GB.hexdump(recordIds), recordIds.length);
+            return;
+        }
+
+        LOG.debug("Got {} activity file IDs", recordIds.length / 7);
+
+        final ByteBuffer buf = ByteBuffer.wrap(recordIds).order(ByteOrder.LITTLE_ENDIAN);
+        final List<XiaomiActivityFileId> fileIds = new ArrayList<>();
+
+        while (buf.position() < buf.limit()) {
+            final XiaomiActivityFileId fileId = XiaomiActivityFileId.from(buf);
+            LOG.debug("Got activity to fetch: {}", fileId);
+            if (fileId.getTimestamp().getTime() == 0 && fileId.getVersion() == 0) {
+                LOG.warn("Skipping invalid file with no timestamp and version");
+                continue;
+            }
+            fileIds.add(fileId);
+        }
+        activityFetcher.fetch(fileIds);
+
+        if (subtype == CMD_ACTIVITY_FETCH_TODAY) {
+            LOG.debug("Fetch recorded data from the past");
+            fetchRecordedDataPast();
+        }
+    }
+
+    public void onHeartRateTest() {
+        LOG.debug("Trigger heart rate one-shot test");
+
+        realtimeStarted = true;
+        realtimeOneShot = true;
+
+        getSupport().sendCommand(
+                "heart rate test",
+                XiaomiProto.Command.newBuilder()
+                        .setType(COMMAND_TYPE)
+                        .setSubtype(CMD_REALTIME_STATS_START)
+                        .build()
+        );
+    }
+
+    public void enableRealtimeStats(final boolean enable) {
+        LOG.debug("Enable realtime stats: {}", enable);
+
+        if (realtimeStarted == enable) {
+            // same state, ignore
+            return;
+        }
+
+        realtimeStarted = enable;
+        realtimeOneShot = false;
+        previousSteps = -1;
+
+        getSupport().sendCommand(
+                "realtime data",
+                XiaomiProto.Command.newBuilder()
+                        .setType(COMMAND_TYPE)
+                        .setSubtype(enable ? CMD_REALTIME_STATS_START : CMD_REALTIME_STATS_STOP)
+                        .build()
+        );
+    }
+
+    private void handleRealtimeStats(final XiaomiProto.RealTimeStats realTimeStats) {
+        LOG.debug("Got realtime stats");
+
+        if (!realtimeOneShot && !realtimeStarted) {
+            // Failsafe in case it gets out of sync, stop it
+            enableRealtimeStats(false);
+            return;
+        }
+
+        if (realtimeOneShot) {
+            if (realTimeStats.getHeartRate() <= 10) {
+                return;
+            }
+            enableRealtimeStats(false);
+        }
+
+        if (previousSteps == -1) {
+            previousSteps = realTimeStats.getSteps();
+        }
+
+        final XiaomiActivitySample sample;
+        try (final DBHandler dbHandler = GBApplication.acquireDB()) {
+            final DaoSession session = dbHandler.getDaoSession();
+
+            final GBDevice gbDevice = getSupport().getDevice();
+            final Device device = DBHelper.getDevice(gbDevice, session);
+            final User user = DBHelper.getUser(session);
+            final int ts = (int) (System.currentTimeMillis() / 1000);
+            final XiaomiSampleProvider provider = new XiaomiSampleProvider(gbDevice, session);
+            sample = provider.createActivitySample();
+
+            sample.setDeviceId(device.getId());
+            sample.setUserId(user.getId());
+            sample.setTimestamp(ts);
+            sample.setHeartRate(realTimeStats.getHeartRate());
+            sample.setSteps(realTimeStats.getSteps() - previousSteps);
+            sample.setRawKind(ActivityKind.UNKNOWN.getCode());
+            sample.setRawIntensity(ActivitySample.NOT_MEASURED);
+        } catch (final Exception e) {
+            LOG.error("Error creating activity sample", e);
+            return;
+        }
+
+        previousSteps = realTimeStats.getSteps();
+
+        final Intent intent = new Intent(DeviceService.ACTION_REALTIME_SAMPLES)
+                .putExtra(GBDevice.EXTRA_DEVICE, getSupport().getDevice())
+                .putExtra(DeviceService.EXTRA_REALTIME_SAMPLE, sample);
+        LocalBroadcastManager.getInstance(getSupport().getContext()).sendBroadcast(intent);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/services/XiaomiMusicService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/services/XiaomiMusicService.java
new file mode 100644
index 0000000..cae0183
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/services/XiaomiMusicService.java
@@ -0,0 +1,163 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo, Yoran Vulker
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.services;
+
+import android.content.Context;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventMusicControl;
+import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
+import nodomain.freeyourgadget.gadgetbridge.proto.xiaomi.XiaomiProto;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.XiaomiSupport;
+import nodomain.freeyourgadget.gadgetbridge.util.MediaManager;
+
+public class XiaomiMusicService extends AbstractXiaomiService {
+    private static final Logger LOG = LoggerFactory.getLogger(XiaomiMusicService.class);
+
+    public static final int COMMAND_TYPE = 18;
+
+    private static final int CMD_MUSIC_GET = 0;
+    private static final int CMD_MUSIC_SEND = 1;
+    private static final int CMD_MUSIC_BUTTON = 2;
+
+    private static final byte BUTTON_PLAY = 0x00;
+    private static final byte BUTTON_PAUSE = 0x01;
+    private static final byte BUTTON_PREVIOUS = 0x03;
+    private static final byte BUTTON_NEXT = 0x04;
+    private static final byte BUTTON_VOLUME = 0x05;
+
+    private static final byte STATE_NOTHING = 0x00;
+    private static final byte STATE_PLAYING = 0x01;
+    private static final byte STATE_PAUSED = 0x02;
+
+    protected MediaManager mediaManager = null;
+
+    public XiaomiMusicService(final XiaomiSupport support) {
+        super(support);
+    }
+
+    @Override
+    public void setContext(Context context) {
+        super.setContext(context);
+        this.mediaManager = new MediaManager(context);
+    }
+
+    @Override
+    public void handleCommand(final XiaomiProto.Command cmd) {
+        final XiaomiProto.Music music = cmd.getMusic();
+
+        switch (cmd.getSubtype()) {
+            case CMD_MUSIC_GET:
+                LOG.debug("Got music request from watch");
+                mediaManager.refresh();
+                sendMusicStateToDevice();
+                return;
+            case CMD_MUSIC_BUTTON:
+                LOG.debug("Got music button from watch: {}", music.getMediaKey().getKey());
+                final GBDeviceEventMusicControl deviceEventMusicControl = new GBDeviceEventMusicControl();
+                switch (music.getMediaKey().getKey()) {
+                    case BUTTON_PLAY:
+                        deviceEventMusicControl.event = GBDeviceEventMusicControl.Event.PLAY;
+                        break;
+                    case BUTTON_PAUSE:
+                        deviceEventMusicControl.event = GBDeviceEventMusicControl.Event.PAUSE;
+                        break;
+                    case BUTTON_PREVIOUS:
+                        deviceEventMusicControl.event = GBDeviceEventMusicControl.Event.PREVIOUS;
+                        break;
+                    case BUTTON_NEXT:
+                        deviceEventMusicControl.event = GBDeviceEventMusicControl.Event.NEXT;
+                        break;
+                    case BUTTON_VOLUME: {
+                        final int requestedVolume = music.getMediaKey().getVolume();
+                        final int currentVolume = MediaManager.getPhoneVolume(getSupport().getContext());
+
+                        if (requestedVolume > currentVolume) {
+                            deviceEventMusicControl.event = GBDeviceEventMusicControl.Event.VOLUMEUP;
+                        } else {
+                            deviceEventMusicControl.event = GBDeviceEventMusicControl.Event.VOLUMEDOWN;
+                        }
+
+                        break;
+                    }
+                    default:
+                        LOG.warn("Unexpected media button key {}", music.getMediaKey().getKey());
+                        return;
+                }
+                getSupport().evaluateGBDeviceEvent(deviceEventMusicControl);
+                return;
+        }
+
+        LOG.warn("Unknown music command {}", cmd.getSubtype());
+    }
+
+    public void onSetMusicState(final MusicStateSpec stateSpec) {
+        if (mediaManager.onSetMusicState(stateSpec)) {
+            sendMusicStateToDevice();
+        }
+    }
+
+    public void onSetPhoneVolume(final float ignoredVolume) {
+        sendMusicStateToDevice();
+    }
+
+    public void onSetMusicInfo(final MusicSpec musicSpec) {
+        if (mediaManager.onSetMusicInfo(musicSpec)) {
+            sendMusicStateToDevice();
+        }
+    }
+
+    private void sendMusicStateToDevice() {
+        final MusicSpec musicSpec = mediaManager.getBufferMusicSpec();
+        final MusicStateSpec musicStateSpec = mediaManager.getBufferMusicStateSpec();
+
+        final XiaomiProto.MusicInfo.Builder musicInfo = XiaomiProto.MusicInfo.newBuilder()
+                .setVolume(mediaManager.getPhoneVolume());
+
+        if (musicSpec == null || musicStateSpec == null) {
+            musicInfo.setState(STATE_NOTHING);
+        } else {
+            if (musicStateSpec.state == MusicStateSpec.STATE_PLAYING) {
+                musicInfo.setState(STATE_PLAYING);
+            } else {
+                musicInfo.setState(STATE_PAUSED);
+            }
+
+            musicInfo.setVolume(mediaManager.getPhoneVolume())
+                    .setTrack(musicSpec.track != null ? musicSpec.track : "")
+                    .setArtist(musicSpec.artist != null ? musicSpec.artist : "")
+                    .setPosition(musicStateSpec.position)
+                    .setDuration(musicSpec.duration);
+        }
+
+        final XiaomiProto.Music music = XiaomiProto.Music.newBuilder()
+                .setMusicInfo(musicInfo.build())
+                .build();
+
+        getSupport().sendCommand(
+                "send music",
+                XiaomiProto.Command.newBuilder()
+                        .setType(COMMAND_TYPE)
+                        .setSubtype(CMD_MUSIC_SEND)
+                        .setMusic(music)
+                        .build()
+        );
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/services/XiaomiNotificationService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/services/XiaomiNotificationService.java
new file mode 100644
index 0000000..67a381c
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/services/XiaomiNotificationService.java
@@ -0,0 +1,570 @@
+/*  Copyright (C) 2023-2024 Andreas Shimokawa, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.services;
+
+import android.Manifest;
+import android.content.pm.PackageManager;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.LinkedList;
+import java.util.Locale;
+import java.util.Queue;
+
+import nodomain.freeyourgadget.gadgetbridge.BuildConfig;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventCallControl;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventNotificationControl;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdatePreferences;
+import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
+import nodomain.freeyourgadget.gadgetbridge.proto.xiaomi.XiaomiProto;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.XiaomiPreferences;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.XiaomiSupport;
+import nodomain.freeyourgadget.gadgetbridge.util.BitmapUtil;
+import nodomain.freeyourgadget.gadgetbridge.util.LimitedQueue;
+import nodomain.freeyourgadget.gadgetbridge.util.NotificationUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
+
+import static nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.XiaomiBitmapUtils.convertToPixelFormat;
+import static nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.XiaomiBitmapUtils.getPixelFormatString;
+
+public class XiaomiNotificationService extends AbstractXiaomiService implements XiaomiDataUploadService.Callback {
+    private static final Logger LOG = LoggerFactory.getLogger(XiaomiNotificationService.class);
+
+    private static final SimpleDateFormat TIMESTAMP_SDF = new SimpleDateFormat("yyyyMMdd'T'HHmmss", Locale.ROOT);
+
+    public static final int COMMAND_TYPE = 7;
+
+    public static final int CMD_NOTIFICATION_SEND = 0;
+    public static final int CMD_NOTIFICATION_DISMISS = 1;
+    public static final int CMD_CALL_REJECT = 2;
+    public static final int CMD_CALL_IGNORE = 5;
+    public static final int CMD_SCREEN_ON_ON_NOTIFICATIONS_GET = 6;
+    public static final int CMD_SCREEN_ON_ON_NOTIFICATIONS_SET = 7;
+    public static final int CMD_OPEN_ON_PHONE = 8;
+    public static final int CMD_CANNED_MESSAGES_GET = 9;
+    public static final int CMD_CANNED_MESSAGES_SET = 12; // also canned message reply
+    public static final int CMD_CALL_REPLY_SEND = 13;
+    public static final int CMD_CALL_REPLY_ACK = 14;
+    public static final int CMD_NOTIFICATION_ICON_REQUEST = 15;
+    public static final int CMD_NOTIFICATION_ICON_QUERY = 16;
+
+    // Maintain a queue of the last seen package names, since the band will send
+    // requests with the package name truncated, and without an ID
+    private final Queue<String> mPackages = new LinkedList<>();
+
+    // Keep track of package names and keys for notification dismissal
+    private final LimitedQueue<Integer, String> mNotificationPackageName = new LimitedQueue<>(128);
+    private final LimitedQueue<Integer, String> mNotificationKey = new LimitedQueue<>(128);
+
+    private String iconPackageName;
+
+    public XiaomiNotificationService(final XiaomiSupport support) {
+        super(support);
+    }
+
+    @Override
+    public void initialize() {
+        getSupport().sendCommand("get screen on on notifications", COMMAND_TYPE, CMD_SCREEN_ON_ON_NOTIFICATIONS_GET);
+        requestCannedMessages();
+    }
+
+    @Override
+    public void handleCommand(final XiaomiProto.Command cmd) {
+        final GBDeviceEventCallControl deviceEvtCallControl = new GBDeviceEventCallControl();
+        final GBDeviceEventNotificationControl deviceEvtNotificationControl = new GBDeviceEventNotificationControl();
+
+        switch (cmd.getSubtype()) {
+            case CMD_NOTIFICATION_DISMISS:
+                LOG.info("Watch dismiss {} notifications", cmd.getNotification().getNotificationDismiss().getNotificationIdCount());
+                for (final XiaomiProto.NotificationId notificationId : cmd.getNotification().getNotificationDismiss().getNotificationIdList()) {
+                    LOG.debug("Watch dismiss {}", notificationId.getId());
+                    deviceEvtNotificationControl.handle = notificationId.getId();
+                    deviceEvtNotificationControl.event = GBDeviceEventNotificationControl.Event.DISMISS;
+                    getSupport().evaluateGBDeviceEvent(deviceEvtNotificationControl);
+                }
+                return;
+            case CMD_CALL_REJECT:
+                LOG.debug("Reject call");
+                deviceEvtCallControl.event = GBDeviceEventCallControl.Event.REJECT;
+                getSupport().evaluateGBDeviceEvent(deviceEvtCallControl);
+                return;
+            case CMD_CALL_IGNORE:
+                LOG.debug("Ignore call");
+                deviceEvtCallControl.event = GBDeviceEventCallControl.Event.IGNORE;
+                getSupport().evaluateGBDeviceEvent(deviceEvtCallControl);
+                return;
+            case CMD_SCREEN_ON_ON_NOTIFICATIONS_GET:
+                final boolean screenOnOnNotifications = cmd.getNotification().getScreenOnOnNotifications();
+                LOG.debug("Got screen on on notifications: {}", screenOnOnNotifications);
+                final GBDeviceEventUpdatePreferences eventUpdatePreferences = new GBDeviceEventUpdatePreferences(
+                        DeviceSettingsPreferenceConst.PREF_SCREEN_ON_ON_NOTIFICATIONS,
+                        screenOnOnNotifications
+                ).withPreference(
+                        XiaomiPreferences.FEAT_SCREEN_ON_ON_NOTIFICATIONS,
+                        true
+                );
+                getSupport().evaluateGBDeviceEvent(eventUpdatePreferences);
+                return;
+            case CMD_OPEN_ON_PHONE:
+                LOG.debug("Open on phone {}", cmd.getNotification().getOpenOnPhone().getId());
+                deviceEvtNotificationControl.handle = cmd.getNotification().getOpenOnPhone().getId();
+                deviceEvtNotificationControl.event = GBDeviceEventNotificationControl.Event.OPEN;
+                getSupport().evaluateGBDeviceEvent(deviceEvtNotificationControl);
+            case CMD_CANNED_MESSAGES_GET:
+                handleCannedMessages(cmd.getNotification().getCannedMessages());
+                return;
+            case CMD_CALL_REPLY_SEND:
+                handleCannedSmsReply(cmd.getNotification().getNotificationReply());
+                return;
+            case CMD_NOTIFICATION_ICON_REQUEST:
+                handleNotificationIconRequest(cmd.getNotification().getNotificationIconRequest());
+                return;
+            case CMD_NOTIFICATION_ICON_QUERY:
+                handleNotificationIconQuery(cmd.getNotification().getNotificationIconQuery());
+                return;
+        }
+
+        LOG.warn("Unhandled notification command {}", cmd.getSubtype());
+    }
+
+    @Override
+    public boolean onSendConfiguration(final String config, final Prefs prefs) {
+        switch (config) {
+            case DeviceSettingsPreferenceConst.PREF_SCREEN_ON_ON_NOTIFICATIONS:
+                setScreenOnOnNotifications();
+                return true;
+        }
+
+        return super.onSendConfiguration(config, prefs);
+    }
+
+    public void onNotification(final NotificationSpec notificationSpec) {
+        if (!getDevicePrefs().getBoolean(DeviceSettingsPreferenceConst.PREF_SEND_APP_NOTIFICATIONS, true)) {
+            LOG.debug("App notifications disabled - ignoring");
+            return;
+        }
+
+        final XiaomiProto.Notification3.Builder notification3 = XiaomiProto.Notification3.newBuilder()
+                .setId(notificationSpec.getId())
+                .setUnknown4("") // ?
+                .setTimestamp(TIMESTAMP_SDF.format(new Date(notificationSpec.when)));
+
+        if (notificationSpec.sourceAppId != null) {
+            notification3.setPackage(notificationSpec.sourceAppId);
+        } else {
+            notification3.setPackage(BuildConfig.APPLICATION_ID);
+        }
+
+        mNotificationPackageName.add(notificationSpec.getId(), notificationSpec.sourceAppId);
+        mNotificationKey.add(notificationSpec.getId(), notificationSpec.key);
+
+        mPackages.add(notification3.getPackage());
+        if (mPackages.size() > 32) {
+            mPackages.poll();
+        }
+
+        final String senderOrTitle = StringUtils.getFirstOf(notificationSpec.sender, notificationSpec.title);
+        if (!senderOrTitle.isEmpty()) {
+            notification3.setTitle(senderOrTitle);
+        }
+
+        if (notificationSpec.body != null) {
+            notification3.setBody(notificationSpec.body);
+        }
+
+        if (notificationSpec.sourceName != null) {
+            notification3.setAppName(notificationSpec.sourceName);
+        } else {
+            // Should never happen, but notification is not shown otherwise
+            notification3.setAppName("UNKNOWN");
+        }
+
+        if (notificationSpec.key != null) {
+            notification3.setKey(notificationSpec.key);
+            notification3.setOpenOnPhone(true);
+        }
+
+        final XiaomiProto.Notification2 notification2 = XiaomiProto.Notification2.newBuilder()
+                .setNotification3(notification3)
+                .build();
+
+        final XiaomiProto.Notification notification = XiaomiProto.Notification.newBuilder()
+                .setNotification2(notification2)
+                .build();
+
+        getSupport().sendCommand(
+                "send notification " + notificationSpec.getId(),
+                XiaomiProto.Command.newBuilder()
+                        .setType(COMMAND_TYPE)
+                        .setSubtype(CMD_NOTIFICATION_SEND)
+                        .setNotification(notification)
+                        .build()
+        );
+    }
+
+    public void onDeleteNotification(final int id) {
+        if (!getDevicePrefs().getBoolean(DeviceSettingsPreferenceConst.PREF_SEND_APP_NOTIFICATIONS, true)) {
+            LOG.debug("App notifications disabled - ignoring delete");
+            return;
+        }
+
+        if (mNotificationPackageName.lookup(id) == null) {
+            LOG.warn("Unable to lookup notification {} to delete", id);
+            return;
+        }
+
+        final XiaomiProto.NotificationId notificationId = XiaomiProto.NotificationId.newBuilder()
+                .setId(id)
+                .setPackage(mNotificationPackageName.lookup(id))
+                .setKey(mNotificationKey.lookup(id))
+                .build();
+
+        final XiaomiProto.NotificationDismiss notificationDismiss = XiaomiProto.NotificationDismiss.newBuilder()
+                .addNotificationId(notificationId)
+                .build();
+
+        final XiaomiProto.Notification notification = XiaomiProto.Notification.newBuilder()
+                .setNotificationDismiss(notificationDismiss)
+                .build();
+
+        getSupport().sendCommand(
+                "delete notification " + id,
+                XiaomiProto.Command.newBuilder()
+                        .setType(COMMAND_TYPE)
+                        .setSubtype(CMD_NOTIFICATION_DISMISS)
+                        .setNotification(notification)
+                        .build()
+        );
+    }
+
+    public void onSetCallState(final CallSpec callSpec) {
+        if (callSpec.command == CallSpec.CALL_OUTGOING) {
+            return;
+        }
+
+        if (callSpec.command != CallSpec.CALL_INCOMING) {
+            final XiaomiProto.NotificationDismiss.Builder notification4 = XiaomiProto.NotificationDismiss.newBuilder()
+                    .addNotificationId(XiaomiProto.NotificationId.newBuilder().setId(0).setPackage("phone"));
+
+            final XiaomiProto.Notification notification = XiaomiProto.Notification.newBuilder()
+                    .setNotificationDismiss(notification4)
+                    .build();
+
+            getSupport().sendCommand(
+                    "send call end",
+                    XiaomiProto.Command.newBuilder()
+                            .setType(COMMAND_TYPE)
+                            .setSubtype(CMD_NOTIFICATION_DISMISS)
+                            .setNotification(notification)
+                            .build()
+            );
+            return;
+        }
+
+        final XiaomiProto.Notification3.Builder notification3 = XiaomiProto.Notification3.newBuilder()
+                .setId(0) // ?
+                .setUnknown4("") // ?
+                .setIsCall(true)
+                .setRepliesAllowed(canSendSms())
+                .setTimestamp(TIMESTAMP_SDF.format(new Date()));
+
+        notification3.setPackage("phone");
+        notification3.setAppName("phone");
+
+        if (callSpec.name != null) {
+            notification3.setTitle(callSpec.name);
+        } else {
+            notification3.setTitle("?");
+        }
+        if (callSpec.number != null) {
+            notification3.setBody(callSpec.number);
+        } else {
+            notification3.setBody("?");
+        }
+
+        final XiaomiProto.Notification2 notification2 = XiaomiProto.Notification2.newBuilder()
+                .setNotification3(notification3)
+                .build();
+
+        final XiaomiProto.Notification notification = XiaomiProto.Notification.newBuilder()
+                .setNotification2(notification2)
+                .build();
+
+        getSupport().sendCommand(
+                "send call",
+                XiaomiProto.Command.newBuilder()
+                        .setType(COMMAND_TYPE)
+                        .setSubtype(CMD_NOTIFICATION_SEND)
+                        .setNotification(notification)
+                        .build()
+        );
+    }
+
+    private void setScreenOnOnNotifications() {
+        final Prefs prefs = getDevicePrefs();
+
+        final boolean screenOnOnNotificationsEnabled = prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_SCREEN_ON_ON_NOTIFICATIONS, true);
+
+        LOG.info("Setting screen on on notification: {}", screenOnOnNotificationsEnabled);
+
+        getSupport().sendCommand(
+                "set screen on on notification",
+                XiaomiProto.Command.newBuilder()
+                        .setType(COMMAND_TYPE)
+                        .setSubtype(CMD_SCREEN_ON_ON_NOTIFICATIONS_SET)
+                        .setNotification(XiaomiProto.Notification.newBuilder().setScreenOnOnNotifications(screenOnOnNotificationsEnabled).build())
+                        .build()
+        );
+    }
+
+    public void onSetCannedMessages(final CannedMessagesSpec cannedMessagesSpec) {
+        if (cannedMessagesSpec.type != CannedMessagesSpec.TYPE_REJECTEDCALLS) {
+            LOG.warn("Got unsupported canned messages type: {}", cannedMessagesSpec.type);
+            return;
+        }
+
+        final int minReplies = getDevicePrefs().getInt(XiaomiPreferences.PREF_CANNED_MESSAGES_MIN, 0);
+        final int maxReplies = getDevicePrefs().getInt(XiaomiPreferences.PREF_CANNED_MESSAGES_MAX, 0);
+
+        if (maxReplies == 0) {
+            LOG.warn("Attempting to set canned messages, but max replies is 0");
+            return;
+        }
+
+        final XiaomiProto.CannedMessages.Builder cannedMessagesBuilder = XiaomiProto.CannedMessages.newBuilder()
+                .setMinReplies(minReplies)
+                .setMaxReplies(maxReplies);
+        int i = 0;
+        for (final String cannedMessage : cannedMessagesSpec.cannedMessages) {
+            if (i >= maxReplies) {
+                LOG.warn("Got too many canned messages ({}), limit is {}", cannedMessagesSpec.cannedMessages.length, maxReplies);
+                break;
+            }
+
+            cannedMessagesBuilder.addReply(cannedMessage);
+
+            i++;
+        }
+
+        for (; i < minReplies; i++) {
+            cannedMessagesBuilder.addReply("-");
+        }
+
+        final XiaomiProto.Notification.Builder notificationBuilder = XiaomiProto.Notification.newBuilder()
+                .setCannedMessages(cannedMessagesBuilder);
+
+        getSupport().sendCommand(
+                "set canned messages",
+                XiaomiProto.Command.newBuilder()
+                        .setType(COMMAND_TYPE)
+                        .setSubtype(CMD_CANNED_MESSAGES_SET)
+                        .setNotification(notificationBuilder)
+                        .build()
+        );
+    }
+
+    public void requestCannedMessages() {
+        getSupport().sendCommand(
+                "get canned messages",
+                XiaomiProto.Command.newBuilder()
+                        .setType(COMMAND_TYPE)
+                        .setSubtype(CMD_CANNED_MESSAGES_GET)
+                        .setNotification(XiaomiProto.Notification.newBuilder().setUnknown8(1))
+                        .build()
+        );
+    }
+
+    public void handleCannedMessages(final XiaomiProto.CannedMessages cannedMessages) {
+        LOG.info("Got {} canned messages", cannedMessages.getReplyCount());
+
+        final int minReplies = cannedMessages.getMinReplies();
+        final int maxReplies = cannedMessages.getMaxReplies();
+        final GBDeviceEventUpdatePreferences gbDeviceEventUpdatePreferences = new GBDeviceEventUpdatePreferences()
+                .withPreference(XiaomiPreferences.PREF_CANNED_MESSAGES_MIN, minReplies)
+                .withPreference(XiaomiPreferences.PREF_CANNED_MESSAGES_MAX, maxReplies);
+
+        int i = 1;
+        for (final String reply : cannedMessages.getReplyList()) {
+            gbDeviceEventUpdatePreferences.withPreference("canned_message_dismisscall_" + i, reply);
+            i++;
+
+            if (i > maxReplies || i > 16) {
+                LOG.warn("Got too many canned messages ({})", i);
+                break;
+            }
+        }
+
+        for (int j = i; j <= maxReplies; j++) {
+            gbDeviceEventUpdatePreferences.withPreference("canned_message_dismisscall_" + j, null);
+        }
+
+        getSupport().evaluateGBDeviceEvent(gbDeviceEventUpdatePreferences);
+    }
+
+    public boolean canSendSms() {
+        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
+            return getSupport().getContext().checkSelfPermission(Manifest.permission.SEND_SMS) == PackageManager.PERMISSION_GRANTED;
+        } else {
+            return true;
+        }
+    }
+
+    private void handleNotificationIconQuery(final XiaomiProto.NotificationIconPackage notificationIconPackage) {
+        LOG.debug("Watch querying notification icon for {}", notificationIconPackage.getPackage());
+
+        iconPackageName = notificationIconPackage.getPackage();
+
+        if (NotificationUtils.getAppIcon(getSupport().getContext(), iconPackageName) == null) {
+            // Attempt to find truncated package name
+            for (final String fullPackage : mPackages) {
+                if (fullPackage.startsWith(iconPackageName)) {
+                    iconPackageName = fullPackage;
+                    break;
+                }
+            }
+
+            if (iconPackageName.equals(notificationIconPackage.getPackage())) {
+                LOG.warn("Failed to match a full package for {}", iconPackageName);
+                return;
+            }
+
+            if (NotificationUtils.getAppIcon(getSupport().getContext(), iconPackageName) == null) {
+                LOG.warn("Failed to find icon for {} and {}", notificationIconPackage.getPackage(), iconPackageName);
+                return;
+            }
+        }
+
+        getSupport().sendCommand(
+                "send notification reply for " + iconPackageName,
+                XiaomiProto.Command.newBuilder()
+                        .setType(COMMAND_TYPE)
+                        .setSubtype(CMD_NOTIFICATION_ICON_REQUEST)
+                        .setNotification(XiaomiProto.Notification.newBuilder()
+                                .setNotificationIconReply(notificationIconPackage)
+                        ).build()
+        );
+    }
+
+    private void handleCannedSmsReply(final XiaomiProto.NotificationReply notificationReply) {
+        final String phoneNumber = notificationReply.getNumber();
+        if (phoneNumber == null) {
+            LOG.warn("Missing phone number for sms reply");
+            ackSmsReply(false);
+            return;
+        }
+
+        final String message = notificationReply.getMessage();
+        if (message == null) {
+            LOG.warn("Missing message for sms reply");
+            ackSmsReply(false);
+            return;
+        }
+
+        LOG.debug("Sending SMS message '{}' to number '{}' and rejecting call", message, phoneNumber);
+
+        final GBDeviceEventNotificationControl devEvtNotificationControl = new GBDeviceEventNotificationControl();
+        devEvtNotificationControl.handle = -1;
+        devEvtNotificationControl.phoneNumber = phoneNumber;
+        devEvtNotificationControl.reply = message;
+        devEvtNotificationControl.event = GBDeviceEventNotificationControl.Event.REPLY;
+        getSupport().evaluateGBDeviceEvent(devEvtNotificationControl);
+
+        final GBDeviceEventCallControl rejectCallCmd = new GBDeviceEventCallControl(GBDeviceEventCallControl.Event.REJECT);
+        getSupport().evaluateGBDeviceEvent(rejectCallCmd);
+
+        // FIXME probably premature
+        ackSmsReply(true);
+    }
+
+    private void ackSmsReply(final boolean success) {
+        getSupport().sendCommand(
+                "ack sms reply success=" + success,
+                XiaomiProto.Command.newBuilder()
+                        .setType(COMMAND_TYPE)
+                        .setSubtype(CMD_CALL_REPLY_ACK)
+                        .setNotification(XiaomiProto.Notification.newBuilder()
+                                .setNotificationReplyStatus(success ? 0 : 1)
+                        ).build()
+        );
+    }
+
+    private void handleNotificationIconRequest(final XiaomiProto.NotificationIconRequest notificationIconRequest) {
+        if (iconPackageName == null) {
+            LOG.warn("No icon package name");
+            return;
+        }
+
+        final int status = notificationIconRequest.getStatus();
+        final int pixelFormat = notificationIconRequest.getPixelFormat();
+
+        final int size = notificationIconRequest.getSize();
+        LOG.debug("Got notification icon request for size {} for {}, status={}, pixelFormat={} ({})",
+                size,
+                iconPackageName,
+                status,
+                getPixelFormatString(pixelFormat),
+                pixelFormat);
+
+        if (status != 0) {
+            return;
+        }
+
+        final Drawable icon = NotificationUtils.getAppIcon(getSupport().getContext(), iconPackageName);
+        if (icon == null) {
+            // FIXME the packageName is sometimes truncated
+            LOG.warn("Failed to get icon for {}", iconPackageName);
+            return;
+        }
+
+        final byte[] bitmap = convertToPixelFormat(pixelFormat, icon, size, size);
+        if (bitmap == null || bitmap.length == 0) {
+            LOG.error("Failed to convert app icon");
+            return;
+        }
+
+        getSupport().getDataUploadService().setCallback(this);
+        getSupport().getDataUploadService().requestUpload(XiaomiDataUploadService.TYPE_NOTIFICATION_ICON, bitmap);
+    }
+
+    @Override
+    public void onUploadFinish(final boolean success) {
+        LOG.debug("Notification icon upload finished: {}", success);
+        getSupport().getConnectionSpecificSupport().runOnQueue(
+                "notification icon upload finish",
+                () -> getSupport().getDataUploadService().setCallback(null)
+        );
+    }
+
+    @Override
+    public void onUploadProgress(final int progressPercent) {
+        LOG.debug("Notification icon upload progress: {}", progressPercent);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/services/XiaomiPhonebookService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/services/XiaomiPhonebookService.java
new file mode 100644
index 0000000..ee1a0d7
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/services/XiaomiPhonebookService.java
@@ -0,0 +1,165 @@
+/*  Copyright (C) 2023-2024 Andreas Shimokawa, Yoran Vulker
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.services;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.ContactsContract;
+import android.text.TextUtils;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.model.Contact;
+import nodomain.freeyourgadget.gadgetbridge.proto.xiaomi.XiaomiProto;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.XiaomiSupport;
+import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
+
+public class XiaomiPhonebookService extends AbstractXiaomiService {
+
+    public static final Integer COMMAND_TYPE = 21;
+    private static final Logger LOG = LoggerFactory.getLogger(XiaomiPhonebookService.class.getSimpleName());
+
+    private static final int CMD_GET_CONTACT = 2;
+    private static final int CMD_GET_CONTACT_RESPONSE = 3;
+    private static final int CMD_ADD_CONTACT_LIST = 5;
+    private static final int CMD_SET_CONTACT_LIST = 7;
+
+    public XiaomiPhonebookService(final XiaomiSupport support) {
+        super(support);
+    }
+
+    @Override
+    public void handleCommand(XiaomiProto.Command cmd) {
+        if (cmd.getType() != COMMAND_TYPE) {
+            throw new IllegalArgumentException("Not a phonebook command");
+        }
+
+        XiaomiProto.Phonebook payload = cmd.getPhonebook();
+
+        if (payload == null) {
+            LOG.warn("Received phonebook command without phonebook payload");
+        }
+
+        switch (cmd.getSubtype()) {
+            case CMD_GET_CONTACT:
+                if (payload == null || TextUtils.isEmpty(payload.getRequestedPhoneNumber())) {
+                    LOG.error("Receive request for contact info without payload or requested phone number");
+                    return;
+                }
+
+                handleContactRequest(payload.getRequestedPhoneNumber());
+                return;
+        }
+
+        LOG.warn("Unhandled Phonebook command {}", cmd.getSubtype());
+    }
+
+    public void handleContactRequest(String phoneNumber) {
+        LOG.debug("Received request for contact info for {}", phoneNumber);
+
+        XiaomiProto.ContactInfo contact = getContactInfoForPhoneNumber(phoneNumber);
+
+        getSupport().sendCommand(
+                "send requested contact information",
+                XiaomiProto.Command.newBuilder()
+                        .setType(COMMAND_TYPE)
+                        .setSubtype(CMD_GET_CONTACT_RESPONSE)
+                        .setPhonebook(XiaomiProto.Phonebook.newBuilder().setContactInfo(contact))
+                        .build()
+        );
+    }
+
+    /**
+     * Returns contact information from Android contact list
+     *
+     * @param number contact number
+     * @return the contact display name, if found, otherwise the phone number
+     */
+    private XiaomiProto.ContactInfo getContactInfoForPhoneNumber(String number) {
+        Context context = getSupport().getContext();
+        String currentPrivacyMode = GBApplication.getPrefs().getString("pref_call_privacy_mode", GBApplication.getContext().getString(R.string.p_call_privacy_mode_off));
+
+        // mask the display name if complete privacy is set in preferences
+        if (currentPrivacyMode.equals(context.getString(R.string.p_call_privacy_mode_complete))) {
+            return XiaomiProto.ContactInfo.newBuilder().setDisplayName("********").setPhoneNumber(number).build();
+        }
+
+        // send empty contact name if name privacy is set in preferences, as the device will show
+        // the phone number instead
+        if (currentPrivacyMode.equals(context.getString(R.string.p_call_privacy_mode_name))) {
+            return XiaomiProto.ContactInfo.newBuilder().setDisplayName("").setPhoneNumber(number).build();
+        }
+
+        String name = "";
+
+        // prevent lookup of null or empty phone number
+        if (!TextUtils.isEmpty(number)) {
+            // search contact's display name in Android contact list
+            Uri uri = Uri.withAppendedPath(ContactsContract.PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI, Uri.encode(number));
+
+            try (Cursor contactLookup = getSupport().getContext().getContentResolver().query(uri, new String[] { ContactsContract.Data.DISPLAY_NAME}, null, null, null)) {
+                if (contactLookup != null && contactLookup.getCount() > 0) {
+                    contactLookup.moveToNext();
+                    name = contactLookup.getString(0);
+                }
+            } catch (SecurityException e) {
+                // ignore, just return name below
+            }
+        }
+
+        XiaomiProto.ContactInfo.Builder contactInfoBuilder = XiaomiProto.ContactInfo.newBuilder();
+
+        // prevent the number from getting displayed if an empty contact name was retrieved from the
+        // contact list
+        if (TextUtils.isEmpty(name) && currentPrivacyMode.equals(context.getString(R.string.p_call_privacy_mode_number))) {
+            name = "********";
+        }
+
+        contactInfoBuilder.setPhoneNumber(number);
+        contactInfoBuilder.setDisplayName(name);
+        return contactInfoBuilder.build();
+    }
+
+    public void setContacts(List<Contact> contacts) {
+        final XiaomiProto.ContactList.Builder contactList = XiaomiProto.ContactList.newBuilder();
+        int maxContacts = 10; // TODO:verify, do not copy and paste
+        int numContacts = Math.min(contacts.size(), maxContacts);
+
+        for (int i = 0; i < numContacts; i++) {
+            final Contact contact = contacts.get(i);
+            if (!StringUtils.isNullOrEmpty(contact.getName()) && !StringUtils.isNullOrEmpty(contact.getNumber())) {
+                contactList.addContactInfo(XiaomiProto.ContactInfo.newBuilder().setDisplayName(contact.getName()).setPhoneNumber(contact.getNumber()));
+            }
+        }
+
+        getSupport().sendCommand(
+                "send contact list",
+                XiaomiProto.Command.newBuilder()
+                        .setType(COMMAND_TYPE)
+                        .setSubtype(CMD_SET_CONTACT_LIST)
+                        .setPhonebook(XiaomiProto.Phonebook.newBuilder().setContactList(contactList))
+                        .build()
+        );
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/services/XiaomiScheduleService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/services/XiaomiScheduleService.java
new file mode 100644
index 0000000..611eaf3
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/services/XiaomiScheduleService.java
@@ -0,0 +1,630 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.services;
+
+import android.content.Intent;
+
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.TimeZone;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdatePreferences;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.entities.User;
+import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
+import nodomain.freeyourgadget.gadgetbridge.model.Reminder;
+import nodomain.freeyourgadget.gadgetbridge.model.WorldClock;
+import nodomain.freeyourgadget.gadgetbridge.proto.xiaomi.XiaomiProto;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.XiaomiPreferences;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.XiaomiSupport;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+public class XiaomiScheduleService extends AbstractXiaomiService {
+    private static final Logger LOG = LoggerFactory.getLogger(XiaomiScheduleService.class);
+
+    public static final int COMMAND_TYPE = 17;
+
+    private static final int CMD_ALARMS_GET = 0;
+    private static final int CMD_ALARMS_CREATE = 1;
+    private static final int CMD_ALARMS_EDIT = 2;
+    private static final int CMD_ALARMS_DELETE = 4;
+    private static final int CMD_SLEEP_MODE_GET = 8;
+    private static final int CMD_SLEEP_MODE_SET = 9;
+    private static final int CMD_WORLD_CLOCKS_GET = 10;
+    private static final int CMD_WORLD_CLOCKS_SET = 11;
+    private static final int CMD_REMINDERS_GET = 14;
+    private static final int CMD_REMINDERS_CREATE = 15;
+    private static final int CMD_REMINDERS_EDIT = 17;
+    private static final int CMD_REMINDERS_DELETE = 18;
+
+    private static final int REPETITION_ONCE = 0;
+    private static final int REPETITION_DAILY = 1;
+    private static final int REPETITION_WEEKLY = 5;
+    private static final int REPETITION_MONTHLY = 7;
+    private static final int REPETITION_YEARLY = 8;
+
+    private static final int ALARM_SMART = 1;
+    private static final int ALARM_NORMAL = 2;
+
+    // Reminders created by this service will have this prefix
+    private static final String REMINDER_DB_PREFIX = "xiaomi_";
+
+    private static final Map<String, String> WORLD_CLOCK_CODES = new HashMap<String, String>() {{
+        put("Europe/Lisbon", "C173");
+        put("Australia/Sydney", "C151");
+        // TODO map everything
+    }};
+
+    // Map of alarm position to Alarm/Reminder, as returned by the watch, indexed by GB position (0-indexed),
+    // does NOT match the ID returned by the watch, but should be offset by 1
+    private final Map<Integer, Alarm> watchAlarms = new HashMap<>();
+    private final Map<String, Reminder> watchReminders = new HashMap<>();
+
+    private int pendingAlarmAcks = 0;
+    private int pendingReminderAcks = 0;
+
+    public XiaomiScheduleService(final XiaomiSupport support) {
+        super(support);
+    }
+
+    @Override
+    public void handleCommand(final XiaomiProto.Command cmd) {
+        switch (cmd.getSubtype()) {
+            case CMD_ALARMS_GET:
+                handleAlarms(cmd.getSchedule().getAlarms());
+                return;
+            case CMD_ALARMS_CREATE:
+                pendingAlarmAcks--;
+                LOG.debug("Got alarms create ack, remaining {}", pendingAlarmAcks);
+                if (pendingAlarmAcks <= 0) {
+                    LOG.debug("Requesting alarms after all acks");
+                    requestAlarms();
+                }
+                return;
+            case CMD_SLEEP_MODE_SET:
+                LOG.debug("Got sleep mode set ack, status={}", cmd.getStatus());
+                return;
+            case CMD_WORLD_CLOCKS_GET:
+                handleWorldClocks(cmd.getSchedule().getWorldClocks());
+                return;
+            case CMD_SLEEP_MODE_GET:
+                handleSleepModeConfig(cmd.getSchedule().getSleepMode());
+                return;
+            case CMD_REMINDERS_GET:
+                handleReminders(cmd.getSchedule().getReminders());
+                return;
+            case CMD_REMINDERS_CREATE:
+                pendingReminderAcks--;
+                LOG.debug("Got alarms create ack, remaining {}", pendingReminderAcks);
+                if (pendingReminderAcks <= 0) {
+                    LOG.debug("Requesting reminders after all acks");
+                    requestReminders();
+                }
+                return;
+        }
+
+        LOG.warn("Unknown schedule command {}", cmd.getSubtype());
+    }
+
+    @Override
+    public void initialize() {
+        if (getCoordinator().supportsAlarms()) {
+            requestAlarms();
+        }
+        requestReminders();
+        requestWorldClocks();
+        getSupport().sendCommand("get sleep mode", COMMAND_TYPE, CMD_SLEEP_MODE_GET);
+    }
+
+    @Override
+    public boolean onSendConfiguration(final String config, final Prefs prefs) {
+        switch (config) {
+            case DeviceSettingsPreferenceConst.PREF_SLEEP_MODE_SCHEDULE_ENABLED:
+            case DeviceSettingsPreferenceConst.PREF_SLEEP_MODE_SCHEDULE_START:
+            case DeviceSettingsPreferenceConst.PREF_SLEEP_MODE_SCHEDULE_END:
+                setSleepModeConfig();
+                return true;
+        }
+
+        return false;
+    }
+
+    public void requestReminders() {
+        getSupport().sendCommand("request reminders", COMMAND_TYPE, CMD_REMINDERS_GET);
+    }
+
+    public void handleReminders(final XiaomiProto.Reminders reminders) {
+        LOG.debug("Got {} reminders from the watch", reminders.getReminderCount());
+
+        final GBDeviceEventUpdatePreferences eventUpdatePreferences = new GBDeviceEventUpdatePreferences()
+                .withPreference(XiaomiPreferences.PREF_REMINDER_SLOTS, reminders.getMaxReminders());
+
+        getSupport().evaluateGBDeviceEvent(eventUpdatePreferences);
+
+        watchReminders.clear();
+        for (final XiaomiProto.Reminder reminder : reminders.getReminderList()) {
+            final nodomain.freeyourgadget.gadgetbridge.entities.Reminder gbReminder = new nodomain.freeyourgadget.gadgetbridge.entities.Reminder();
+            gbReminder.setReminderId(REMINDER_DB_PREFIX + reminder.getId());
+            gbReminder.setMessage(reminder.getReminderDetails().getTitle());
+            gbReminder.setDate(XiaomiPreferences.toDate(reminder.getReminderDetails().getDate(), reminder.getReminderDetails().getTime()));
+
+            switch (reminder.getReminderDetails().getRepeatMode()) {
+                case REPETITION_ONCE:
+                    gbReminder.setRepetition(Alarm.ALARM_ONCE);
+                    break;
+                case REPETITION_DAILY:
+                    gbReminder.setRepetition(Alarm.ALARM_DAILY);
+                    break;
+                case REPETITION_WEEKLY:
+                    gbReminder.setRepetition(reminder.getReminderDetails().getRepeatFlags());
+                    break;
+            }
+
+            watchReminders.put(gbReminder.getReminderId(), gbReminder);
+        }
+
+        final List<nodomain.freeyourgadget.gadgetbridge.entities.Reminder> dbReminders = DBHelper.getReminders(getSupport().getDevice());
+
+        final Set<String> dbReminderIds = new HashSet<>();
+
+        int numUpdatedReminders = 0;
+
+        // Delete reminders that do not exist on the watch anymore
+        for (nodomain.freeyourgadget.gadgetbridge.entities.Reminder reminder : dbReminders) {
+            if (!reminder.getReminderId().startsWith(REMINDER_DB_PREFIX)) {
+                LOG.debug("Deleting reminder {}", reminder.getReminderId());
+                DBHelper.delete(reminder);
+                numUpdatedReminders++;
+                continue;
+            }
+
+            dbReminderIds.add(reminder.getReminderId());
+        }
+
+        // Persist unknown reminders
+        // We assume that reminders are not modifiable from the watch, unlike alarms
+        try (DBHandler db = GBApplication.acquireDB()) {
+            final DaoSession daoSession = db.getDaoSession();
+            final Device device = DBHelper.getDevice(getSupport().getDevice(), daoSession);
+            final User user = DBHelper.getUser(daoSession);
+
+            for (final Reminder watchReminder : watchReminders.values()) {
+                final String reminderId = watchReminder.getReminderId();
+                if (dbReminderIds.contains(reminderId)) {
+                    continue;
+                }
+
+                // Reminder not known - persist it to database
+                LOG.info("Persisting reminder {}", reminderId);
+
+                final nodomain.freeyourgadget.gadgetbridge.entities.Reminder reminder = new nodomain.freeyourgadget.gadgetbridge.entities.Reminder();
+                reminder.setReminderId(watchReminder.getReminderId());
+                reminder.setDate(watchReminder.getDate());
+                reminder.setMessage(watchReminder.getMessage());
+                reminder.setRepetition(watchReminder.getRepetition());
+                reminder.setDeviceId(device.getId());
+                reminder.setUserId(user.getId());
+
+                DBHelper.store(reminder);
+
+                numUpdatedReminders++;
+            }
+        } catch (final Exception e) {
+            LOG.error("Error accessing database", e);
+        }
+
+        if (numUpdatedReminders > 0) {
+            final Intent intent = new Intent(DeviceService.ACTION_SAVE_REMINDERS);
+            LocalBroadcastManager.getInstance(getSupport().getContext()).sendBroadcast(intent);
+        }
+    }
+
+    public void onSetReminders(final ArrayList<? extends Reminder> reminders) {
+        final List<Integer> remindersToDelete = new ArrayList<>();
+
+        pendingReminderAcks = 0;
+
+        final Set<String> newReminderIds = new HashSet<>();
+        for (final Reminder reminder : reminders) {
+            newReminderIds.add(reminder.getReminderId());
+        }
+
+        for (final Reminder watchReminder : watchReminders.values()) {
+            if (!newReminderIds.contains(watchReminder.getReminderId())) {
+                final Integer watchId = Integer.parseInt(watchReminder.getReminderId().replace(REMINDER_DB_PREFIX, ""));
+                remindersToDelete.add(watchId);
+            }
+        }
+
+        for (final Integer id : remindersToDelete) {
+            watchReminders.remove(REMINDER_DB_PREFIX + id);
+        }
+
+        for (final Reminder reminder : reminders) {
+            final boolean isCreateReminder;
+            if (reminder.getReminderId().startsWith(REMINDER_DB_PREFIX) && watchReminders.containsKey(reminder.getReminderId())) {
+                // Update reminder on the watch if needed
+                final Reminder watchReminder = watchReminders.get(reminder.getReminderId());
+                if (watchReminder != null && remindersEqual(reminder, watchReminder)) {
+                    LOG.debug("Reminder {} is already up-to-date on watch", watchReminder.getReminderId());
+                    continue;
+                }
+
+                isCreateReminder = (watchReminder == null);
+            } else {
+                isCreateReminder = true;
+            }
+
+            final Calendar reminderTime = GregorianCalendar.getInstance(TimeZone.getTimeZone("UTC"));
+            reminderTime.setTimeInMillis(reminder.getDate().getTime());
+
+            final XiaomiProto.ReminderDetails.Builder reminderDetails = XiaomiProto.ReminderDetails.newBuilder()
+                    .setTime(XiaomiProto.Time.newBuilder()
+                            .setHour(reminderTime.get(Calendar.HOUR_OF_DAY))
+                            .setMinute(reminderTime.get(Calendar.MINUTE))
+                            .setSecond(reminderTime.get(Calendar.SECOND))
+                            .setMillisecond(reminderTime.get(Calendar.MILLISECOND))
+                            .build())
+                    .setDate(XiaomiProto.Date.newBuilder()
+                            .setYear(reminderTime.get(Calendar.YEAR))
+                            .setMonth(reminderTime.get(Calendar.MONTH) + 1)
+                            .setDay(reminderTime.get(Calendar.DATE))
+                            .build())
+                    .setTitle(reminder.getMessage());
+
+            switch (reminder.getRepetition()) {
+                case Alarm.ALARM_ONCE:
+                    reminderDetails.setRepeatMode(REPETITION_ONCE);
+                    break;
+                case Alarm.ALARM_DAILY:
+                    reminderDetails.setRepeatMode(REPETITION_DAILY);
+                    break;
+                default:
+                    reminderDetails.setRepeatMode(REPETITION_WEEKLY);
+                    reminderDetails.setRepeatFlags(reminder.getRepetition());
+                    break;
+            }
+
+            final XiaomiProto.Schedule.Builder schedule = XiaomiProto.Schedule.newBuilder();
+
+            if (!isCreateReminder) {
+                // update existing alarm
+                LOG.debug("Update reminder {}", reminder.getReminderId());
+                watchReminders.put(reminder.getReminderId(), reminder);
+                schedule.setEditReminder(
+                        XiaomiProto.Reminder.newBuilder()
+                                .setId(Integer.parseInt(reminder.getReminderId().replace(REMINDER_DB_PREFIX, "")))
+                                .setReminderDetails(reminderDetails)
+                                .build()
+                );
+            } else {
+                LOG.debug("Create reminder {}", reminder.getReminderId());
+                // watchReminders will be updated later, since we don't know the correct ID here
+                pendingReminderAcks++;
+                schedule.setCreateReminder(reminderDetails);
+            }
+
+            getSupport().sendCommand(
+                    (isCreateReminder ? "create" : "update") + " reminder " + reminder.getReminderId(),
+                    XiaomiProto.Command.newBuilder()
+                            .setType(COMMAND_TYPE)
+                            .setSubtype(isCreateReminder ? CMD_REMINDERS_CREATE : CMD_REMINDERS_EDIT)
+                            .setSchedule(schedule)
+                            .build()
+            );
+        }
+
+        if (!remindersToDelete.isEmpty()) {
+            final XiaomiProto.ReminderDelete reminderDelete = XiaomiProto.ReminderDelete.newBuilder()
+                    .addAllId(remindersToDelete)
+                    .build();
+
+            final XiaomiProto.Schedule schedule = XiaomiProto.Schedule.newBuilder()
+                    .setDeleteReminder(reminderDelete)
+                    .build();
+
+            getSupport().sendCommand(
+                    "delete " + remindersToDelete.size() + " reminders",
+                    XiaomiProto.Command.newBuilder()
+                            .setType(COMMAND_TYPE)
+                            .setSubtype(CMD_REMINDERS_DELETE)
+                            .setSchedule(schedule)
+                            .build()
+            );
+        }
+    }
+
+    public void onSetWorldClocks(final ArrayList<? extends WorldClock> clocks) {
+        final XiaomiProto.WorldClocks.Builder worldClocksBuilder = XiaomiProto.WorldClocks.newBuilder();
+
+        for (final WorldClock clock : clocks) {
+            final String clockCode = WORLD_CLOCK_CODES.get(clock.getTimeZoneId());
+            if (clockCode != null) {
+                worldClocksBuilder.addWorldClock(clockCode);
+            } else {
+                LOG.warn("Unknown timezone code for {}", clock.getTimeZoneId());
+            }
+        }
+
+        final XiaomiProto.Schedule schedule = XiaomiProto.Schedule.newBuilder()
+                .setWorldClocks(worldClocksBuilder.build())
+                .build();
+
+        getSupport().sendCommand(
+                "send world clocks",
+                XiaomiProto.Command.newBuilder()
+                        .setType(COMMAND_TYPE)
+                        .setSubtype(CMD_WORLD_CLOCKS_SET)
+                        .setSchedule(schedule)
+                        .build()
+        );
+    }
+
+    public void requestWorldClocks() {
+        getSupport().sendCommand("get world clocks", COMMAND_TYPE, CMD_WORLD_CLOCKS_GET);
+    }
+
+    public void handleWorldClocks(final XiaomiProto.WorldClocks worldClocks) {
+        LOG.info("Got {} world clocks: {}", worldClocks.getWorldClockCount(), worldClocks.getWorldClockList());
+        // TODO map the world clock codes
+    }
+
+    public void onSetAlarms(final ArrayList<? extends Alarm> alarms) {
+        final List<Integer> alarmsToDelete = new ArrayList<>();
+
+        pendingAlarmAcks = 0;
+
+        for (final Alarm alarm : alarms) {
+            final Alarm watchAlarm = watchAlarms.get(alarm.getPosition());
+
+            if (alarm.getUnused() && watchAlarm == null) {
+                // Disabled on both
+                //LOG.debug("Alarm {} is unused on both", alarm.getPosition());
+                continue;
+            }
+
+            if (alarm.getUnused() && watchAlarm != null) {
+                // Delete from watch
+                alarmsToDelete.add(watchAlarm.getPosition() + 1); // watch positions, not GB
+                watchAlarms.remove(alarm.getPosition());
+                LOG.debug("Delete alarm {} from watch", alarm.getPosition());
+                continue;
+            }
+
+            if (watchAlarm != null && alarmsEqual(alarm, watchAlarm)) {
+                //LOG.debug("Alarm {} is already up-to-date on watch", alarm.getPosition());
+                continue;
+            }
+
+            final XiaomiProto.HourMinute hourMinute = XiaomiProto.HourMinute.newBuilder()
+                    .setHour(alarm.getHour())
+                    .setMinute(alarm.getMinute())
+                    .build();
+
+            final XiaomiProto.AlarmDetails.Builder alarmDetails = XiaomiProto.AlarmDetails.newBuilder()
+                    .setTime(hourMinute)
+                    .setEnabled(alarm.getEnabled())
+                    .setSmart(alarm.getSmartWakeup() ? ALARM_SMART : ALARM_NORMAL);
+
+            switch (alarm.getRepetition()) {
+                case Alarm.ALARM_ONCE:
+                    alarmDetails.setRepeatMode(REPETITION_ONCE);
+                    break;
+                case Alarm.ALARM_DAILY:
+                    alarmDetails.setRepeatMode(REPETITION_DAILY);
+                    break;
+                default:
+                    alarmDetails.setRepeatMode(REPETITION_WEEKLY);
+                    alarmDetails.setRepeatFlags(alarm.getRepetition());
+                    break;
+            }
+
+            final XiaomiProto.Schedule.Builder schedule = XiaomiProto.Schedule.newBuilder();
+
+            if (watchAlarm != null) {
+                // update existing alarm
+                LOG.debug("Update alarm {}", alarm.getPosition());
+                watchAlarms.put(alarm.getPosition(), alarm);
+                schedule.setEditAlarm(
+                        XiaomiProto.Alarm.newBuilder()
+                                .setId(alarm.getPosition() + 1)
+                                .setAlarmDetails(alarmDetails)
+                                .build()
+                );
+            } else {
+                LOG.debug("Create alarm {}", alarm.getPosition());
+                // watchAlarms will be updated later, since we don't know the correct ID here
+                pendingAlarmAcks++;
+                schedule.setCreateAlarm(alarmDetails);
+            }
+
+            getSupport().sendCommand(
+                    (watchAlarm == null ? "create" : "update") + " alarm " + alarm.getPosition(),
+                    XiaomiProto.Command.newBuilder()
+                            .setType(COMMAND_TYPE)
+                            .setSubtype(watchAlarm == null ? CMD_ALARMS_CREATE : CMD_ALARMS_EDIT)
+                            .setSchedule(schedule)
+                            .build()
+            );
+        }
+
+        if (!alarmsToDelete.isEmpty()) {
+            final XiaomiProto.AlarmDelete alarmDelete = XiaomiProto.AlarmDelete.newBuilder()
+                    .addAllId(alarmsToDelete)
+                    .build();
+
+            final XiaomiProto.Schedule schedule = XiaomiProto.Schedule.newBuilder()
+                    .setDeleteAlarm(alarmDelete)
+                    .build();
+
+            getSupport().sendCommand(
+                    "delete " + alarmsToDelete.size() + " unused alarms",
+                    XiaomiProto.Command.newBuilder()
+                            .setType(COMMAND_TYPE)
+                            .setSubtype(CMD_ALARMS_DELETE)
+                            .setSchedule(schedule)
+                            .build()
+            );
+        }
+    }
+
+    public void requestAlarms() {
+        getSupport().sendCommand("get alarms", COMMAND_TYPE, CMD_ALARMS_GET);
+    }
+
+    public void handleAlarms(final XiaomiProto.Alarms alarms) {
+        LOG.debug("Got {} alarms from the watch", alarms.getAlarmCount());
+
+        final GBDeviceEventUpdatePreferences eventUpdatePreferences = new GBDeviceEventUpdatePreferences()
+                .withPreference(XiaomiPreferences.PREF_ALARM_SLOTS, alarms.getMaxAlarms());
+
+        getSupport().evaluateGBDeviceEvent(eventUpdatePreferences);
+
+        watchAlarms.clear();
+        for (final XiaomiProto.Alarm alarm : alarms.getAlarmList()) {
+            final nodomain.freeyourgadget.gadgetbridge.entities.Alarm gbAlarm = new nodomain.freeyourgadget.gadgetbridge.entities.Alarm();
+            gbAlarm.setUnused(false); // If the band sent it, it's not unused
+            gbAlarm.setPosition(alarm.getId() - 1); // band id starts at 1
+            gbAlarm.setEnabled(alarm.getAlarmDetails().getEnabled());
+            gbAlarm.setSmartWakeup(alarm.getAlarmDetails().getSmart() == ALARM_SMART);
+            gbAlarm.setHour(alarm.getAlarmDetails().getTime().getHour());
+            gbAlarm.setMinute(alarm.getAlarmDetails().getTime().getMinute());
+            switch (alarm.getAlarmDetails().getRepeatMode()) {
+                case REPETITION_ONCE:
+                    gbAlarm.setRepetition(Alarm.ALARM_ONCE);
+                    break;
+                case REPETITION_DAILY:
+                    gbAlarm.setRepetition(Alarm.ALARM_DAILY);
+                    break;
+                case REPETITION_WEEKLY:
+                    gbAlarm.setRepetition(alarm.getAlarmDetails().getRepeatFlags());
+                    break;
+            }
+
+            watchAlarms.put(gbAlarm.getPosition(), gbAlarm);
+        }
+
+        final List<nodomain.freeyourgadget.gadgetbridge.entities.Alarm> dbAlarms = DBHelper.getAlarms(getSupport().getDevice());
+        int numUpdatedAlarms = 0;
+
+        for (nodomain.freeyourgadget.gadgetbridge.entities.Alarm alarm : dbAlarms) {
+            final int pos = alarm.getPosition();
+            final Alarm updatedAlarm = watchAlarms.get(pos);
+
+            final boolean alarmNeedsUpdate;
+            if (updatedAlarm == null) {
+                alarmNeedsUpdate = !alarm.getUnused();
+            } else {
+                alarmNeedsUpdate = !alarmsEqual(alarm, updatedAlarm);
+            }
+            if (alarmNeedsUpdate) {
+                numUpdatedAlarms++;
+                LOG.info("Updating alarm index={}, unused={}", pos, updatedAlarm == null);
+                alarm.setUnused(updatedAlarm == null);
+                if (updatedAlarm != null) {
+                    alarm.setEnabled(updatedAlarm.getEnabled());
+                    alarm.setSmartWakeup(updatedAlarm.getSmartWakeup());
+                    alarm.setHour(updatedAlarm.getHour());
+                    alarm.setMinute(updatedAlarm.getMinute());
+                    alarm.setRepetition(updatedAlarm.getRepetition());
+                }
+                DBHelper.store(alarm);
+            }
+        }
+
+        if (numUpdatedAlarms > 0) {
+            final Intent intent = new Intent(DeviceService.ACTION_SAVE_ALARMS);
+            LocalBroadcastManager.getInstance(getSupport().getContext()).sendBroadcast(intent);
+        }
+    }
+
+    private boolean alarmsEqual(final Alarm alarm1, final Alarm alarm2) {
+        return alarm1.getUnused() == alarm2.getUnused() &&
+                alarm1.getEnabled() == alarm2.getEnabled() &&
+                alarm1.getSmartWakeup() == alarm2.getSmartWakeup() &&
+                alarm1.getHour() == alarm2.getHour() &&
+                alarm1.getMinute() == alarm2.getMinute() &&
+                alarm1.getRepetition() == alarm2.getRepetition();
+    }
+
+    private boolean remindersEqual(final Reminder reminder1, final Reminder reminder2) {
+        return Objects.equals(reminder1.getMessage(), reminder2.getMessage()) &&
+                Objects.equals(reminder1.getDate(), reminder2.getDate()) &&
+                reminder1.getRepetition() == reminder2.getRepetition();
+    }
+
+    private void handleSleepModeConfig(final XiaomiProto.SleepMode sleepMode) {
+        LOG.debug("Got sleep mode config");
+
+        final String start = XiaomiPreferences.prefFromHourMin(sleepMode.getSchedule().getStart());
+        final String end = XiaomiPreferences.prefFromHourMin(sleepMode.getSchedule().getEnd());
+
+        final GBDeviceEventUpdatePreferences eventUpdatePreferences = new GBDeviceEventUpdatePreferences()
+                .withPreference(XiaomiPreferences.FEAT_SLEEP_MODE_SCHEDULE, true)
+                .withPreference("prefs_enable_sleep_time", null)
+                .withPreference(DeviceSettingsPreferenceConst.PREF_SLEEP_MODE_SCHEDULE_ENABLED, sleepMode.getEnabled())
+                .withPreference(DeviceSettingsPreferenceConst.PREF_SLEEP_MODE_SCHEDULE_START, start)
+                .withPreference(DeviceSettingsPreferenceConst.PREF_SLEEP_MODE_SCHEDULE_END, end);
+
+        getSupport().evaluateGBDeviceEvent(eventUpdatePreferences);
+    }
+
+    private void setSleepModeConfig() {
+        LOG.debug("Set sleep mode config");
+
+        final Prefs prefs = getDevicePrefs();
+        final boolean enabled = prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_SLEEP_MODE_SCHEDULE_ENABLED, false);
+        final Date start = prefs.getTimePreference(DeviceSettingsPreferenceConst.PREF_SLEEP_MODE_SCHEDULE_START, "22:00");
+        final Date end = prefs.getTimePreference(DeviceSettingsPreferenceConst.PREF_SLEEP_MODE_SCHEDULE_END, "06:00");
+
+        final XiaomiProto.SleepMode sleepMode = XiaomiProto.SleepMode.newBuilder()
+                .setEnabled(enabled)
+                .setSchedule(XiaomiProto.SleepModeSchedule.newBuilder()
+                        .setUnknown3(0)
+                        .setStart(XiaomiPreferences.prefToHourMin(start))
+                        .setEnd(XiaomiPreferences.prefToHourMin(end)))
+                .build();
+
+        getSupport().sendCommand(
+                "set sleep mode",
+                XiaomiProto.Command.newBuilder()
+                        .setType(COMMAND_TYPE)
+                        .setSubtype(CMD_SLEEP_MODE_SET)
+                        .setSchedule(XiaomiProto.Schedule.newBuilder().setSleepMode(sleepMode))
+                        .build()
+        );
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/services/XiaomiSystemService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/services/XiaomiSystemService.java
new file mode 100644
index 0000000..f63ba8f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/services/XiaomiSystemService.java
@@ -0,0 +1,1005 @@
+/*  Copyright (C) 2023-2024 Andreas Shimokawa, Jos茅 Rebelo, LuK1337,
+    Yoran Vulker
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.services;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.text.TextUtils;
+
+import com.google.protobuf.InvalidProtocolBufferException;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.GregorianCalendar;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+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.activities.devicesettings.DeviceSettingsUtils;
+import nodomain.freeyourgadget.gadgetbridge.capabilities.password.PasswordCapabilityImpl;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventFindPhone;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventSleepStateDetection;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdateDeviceInfo;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdatePreferences;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventWearState;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst;
+import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.XiaomiFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.BatteryState;
+import nodomain.freeyourgadget.gadgetbridge.model.SleepState;
+import nodomain.freeyourgadget.gadgetbridge.model.WearingState;
+import nodomain.freeyourgadget.gadgetbridge.proto.xiaomi.XiaomiProto;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.XiaomiPreferences;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.XiaomiSupport;
+import nodomain.freeyourgadget.gadgetbridge.util.CheckSums;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+import nodomain.freeyourgadget.gadgetbridge.util.SilentMode;
+import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.preferences.DevicePrefs;
+
+public class XiaomiSystemService extends AbstractXiaomiService implements XiaomiDataUploadService.Callback {
+    private static final Logger LOG = LoggerFactory.getLogger(XiaomiSystemService.class);
+
+    // We persist the settings code when receiving the display items,
+    // so we can enforce it when sending them
+    private static final String PREF_SETTINGS_DISPLAY_ITEM_CODE = "xiaomi_settings_display_item_code";
+
+    public static final int COMMAND_TYPE = 2;
+
+    public static final int CMD_BATTERY = 1;
+    public static final int CMD_DEVICE_INFO = 2;
+    public static final int CMD_CLOCK = 3;
+    public static final int CMD_FIRMWARE_INSTALL = 5;
+    public static final int CMD_LANGUAGE = 6;
+    public static final int CMD_CAMERA_REMOTE_GET = 7;
+    public static final int CMD_CAMERA_REMOTE_SET = 8;
+    public static final int CMD_PASSWORD_GET = 9;
+    public static final int CMD_MISC_SETTING_GET = 14;
+    public static final int CMD_MISC_SETTING_SET = 15;
+    public static final int CMD_FIND_PHONE = 17;
+    public static final int CMD_FIND_WATCH = 18;
+    public static final int CMD_PASSWORD_SET = 21;
+    public static final int CMD_DND_MODE_SET = 23;
+    public static final int CMD_DISPLAY_ITEMS_GET = 29;
+    public static final int CMD_DISPLAY_ITEMS_SET = 30;
+    public static final int CMD_WORKOUT_TYPES_GET = 39;
+    public static final int CMD_MISC_SETTING_SET_FROM_BAND = 42;
+    public static final int CMD_SILENT_MODE_GET = 43;
+    public static final int CMD_SILENT_MODE_SET_FROM_PHONE = 44;
+    public static final int CMD_SILENT_MODE_SET_FROM_WATCH = 45;
+    public static final int CMD_WIDGET_SCREENS_GET = 51;
+    public static final int CMD_WIDGET_SCREENS_SET = 52;
+    public static final int CMD_WIDGET_PARTS_GET = 53;
+    public static final int CMD_DEVICE_STATE_GET = 78;
+    public static final int CMD_DEVICE_STATE = 79;
+
+    // Not null if we're installing a firmware
+    private XiaomiFWHelper fwHelper = null;
+    private final Handler handler = new Handler(Looper.getMainLooper());
+    private final Runnable batteryStateRequestRunnable = () -> {
+        getSupport().sendCommand("get device status", COMMAND_TYPE, CMD_DEVICE_STATE_GET);
+        getSupport().sendCommand("get battery state", COMMAND_TYPE, CMD_BATTERY);
+    };
+
+    private WearingState currentWearingState = WearingState.UNKNOWN;
+    private BatteryState currentBatteryState = BatteryState.UNKNOWN;
+    private SleepState currentSleepDetectionState = SleepState.UNKNOWN;
+
+    public XiaomiSystemService(final XiaomiSupport support) {
+        super(support);
+    }
+
+    @Override
+    public void initialize() {
+        // Request device info and configs
+        getSupport().sendCommand("get device info", COMMAND_TYPE, CMD_DEVICE_INFO);
+        getSupport().sendCommand("get device status", COMMAND_TYPE, CMD_DEVICE_STATE_GET);
+        // device status request may initialize wearing, charger, sleeping, and activity state, so
+        // get battery level as a failsafe for devices that don't support CMD_DEVICE_STATE_SET command
+        getSupport().sendCommand("get battery state", COMMAND_TYPE, CMD_BATTERY);
+        getSupport().sendCommand("get password", COMMAND_TYPE, CMD_PASSWORD_GET);
+        getSupport().sendCommand("get display items", COMMAND_TYPE, CMD_DISPLAY_ITEMS_GET);
+        getSupport().sendCommand("get camera remote", COMMAND_TYPE, CMD_CAMERA_REMOTE_GET);
+        getSupport().sendCommand("get widgets", COMMAND_TYPE, CMD_WIDGET_SCREENS_GET);
+        getSupport().sendCommand("get widget parts", COMMAND_TYPE, CMD_WIDGET_PARTS_GET);
+        getSupport().sendCommand("get workout types", COMMAND_TYPE, CMD_WORKOUT_TYPES_GET);
+
+        rearmBatteryStateRequestTimer();
+    }
+
+    @Override
+    public void handleCommand(final XiaomiProto.Command cmd) {
+        switch (cmd.getSubtype()) {
+            case CMD_DEVICE_INFO:
+                handleDeviceInfo(cmd.getSystem().getDeviceInfo());
+                return;
+            case CMD_BATTERY:
+                handleBattery(cmd.getSystem().getPower().getBattery());
+                return;
+            case CMD_FIRMWARE_INSTALL:
+                final int installStatus = cmd.getSystem().getFirmwareInstallResponse().getStatus();
+                if (installStatus != 0) {
+                    LOG.warn("Invalid firmware install status {} for {}", installStatus, fwHelper.getId());
+                    return;
+                }
+
+                LOG.debug("Firmware install status 0, uploading");
+                setDeviceBusy();
+                getSupport().getDataUploadService().setCallback(this);
+                getSupport().getDataUploadService().requestUpload(XiaomiDataUploadService.TYPE_FIRMWARE, fwHelper.getBytes());
+                return;
+            case CMD_PASSWORD_GET:
+                handlePassword(cmd.getSystem().getPassword());
+                return;
+            case CMD_DND_MODE_SET:
+                LOG.debug("Got set DND, status={}", cmd.getSystem().getDndStatus());
+                return;
+            case CMD_MISC_SETTING_SET:
+                LOG.debug("Got misc setting set ack, status={}", cmd.getStatus());
+                return;
+            case CMD_CAMERA_REMOTE_GET:
+                handleCameraRemote(cmd.getSystem().getCamera());
+                return;
+            case CMD_CAMERA_REMOTE_SET:
+                LOG.debug("Got camera remote set ack, status={}", cmd.getStatus());
+                return;
+            case CMD_FIND_PHONE:
+                if (cmd.hasSystem()) {
+                    LOG.debug("Got find phone: {}", cmd.getSystem().getFindDevice());
+                    final GBDeviceEventFindPhone findPhoneEvent = new GBDeviceEventFindPhone();
+                    if (cmd.getSystem().getFindDevice() == 0) {
+                        findPhoneEvent.event = GBDeviceEventFindPhone.Event.START;
+                    } else {
+                        findPhoneEvent.event = GBDeviceEventFindPhone.Event.STOP;
+                    }
+                    getSupport().evaluateGBDeviceEvent(findPhoneEvent);
+                }
+                return;
+            case CMD_FIND_WATCH:
+                if (cmd.hasSystem()) {
+                    LOG.debug("Got find device: {}", cmd.getSystem().getFindDevice());
+                    // TODO mark device as found
+                }
+                return;
+            case CMD_DISPLAY_ITEMS_GET:
+                handleDisplayItems(cmd.getSystem().getDisplayItems());
+                return;
+            case CMD_WORKOUT_TYPES_GET:
+                handleWorkoutTypes(cmd.getSystem().getWorkoutTypes());
+                return;
+            case CMD_MISC_SETTING_SET_FROM_BAND:
+                handleMiscSettingSet(cmd.getSystem().getMiscSettingSet());
+                return;
+            case CMD_SILENT_MODE_GET:
+                handlePhoneSilentModeGet();
+                return;
+            case CMD_SILENT_MODE_SET_FROM_WATCH:
+                handlePhoneSilentModeSet(cmd.getSystem().getPhoneSilentModeSet());
+                return;
+            case CMD_WIDGET_SCREENS_GET:
+                handleWidgetScreens(cmd.getSystem().getWidgetScreens());
+                return;
+            case CMD_WIDGET_SCREENS_SET:
+                LOG.debug("Got widget screens set ack, status={}", cmd.getStatus());
+                return;
+            case CMD_WIDGET_PARTS_GET:
+                handleWidgetParts(cmd.getSystem().getWidgetParts());
+                return;
+            case CMD_DEVICE_STATE_GET:
+                handleBasicDeviceState(cmd.getSystem().hasBasicDeviceState()
+                        ? cmd.getSystem().getBasicDeviceState()
+                        : null);
+                return;
+            case CMD_DEVICE_STATE:
+                handleDeviceState(cmd.getSystem().hasDeviceState()
+                        ? cmd.getSystem().getDeviceState()
+                        : null);
+                return;
+        }
+
+        LOG.warn("Unknown system command {}", cmd.getSubtype());
+    }
+
+    @Override
+    public boolean onSendConfiguration(final String config, final Prefs prefs) {
+        switch (config) {
+            case DeviceSettingsPreferenceConst.PREF_BATTERY_POLLING_ENABLE:
+            case DeviceSettingsPreferenceConst.PREF_BATTERY_POLLING_INTERVAL:
+                rearmBatteryStateRequestTimer();
+                break;
+            case DeviceSettingsPreferenceConst.PREF_WEARMODE:
+                setWearMode();
+                return true;
+            case DeviceSettingsPreferenceConst.PREF_CAMERA_REMOTE:
+                setCameraRemoteConfig();
+                return true;
+            case DeviceSettingsPreferenceConst.PREF_LANGUAGE:
+                setLanguage();
+                return true;
+            case DeviceSettingsPreferenceConst.PREF_TIMEFORMAT:
+                setCurrentTime();
+                return true;
+            case PasswordCapabilityImpl.PREF_PASSWORD_ENABLED:
+            case PasswordCapabilityImpl.PREF_PASSWORD:
+                setPassword();
+                return true;
+            case HuamiConst.PREF_DISPLAY_ITEMS_SORTABLE:
+                setDisplayItems();
+                return true;
+            case DeviceSettingsPreferenceConst.PREF_WIDGETS:
+                setWidgets();
+                return true;
+        }
+
+        return super.onSendConfiguration(config, prefs);
+    }
+
+    public void setLanguage() {
+        String localeString = GBApplication.getDeviceSpecificSharedPrefs(getSupport().getDevice().getAddress()).getString(
+                DeviceSettingsPreferenceConst.PREF_LANGUAGE, DeviceSettingsPreferenceConst.PREF_LANGUAGE_AUTO
+        );
+        if (DeviceSettingsPreferenceConst.PREF_LANGUAGE_AUTO.equals(localeString)) {
+            String language = Locale.getDefault().getLanguage();
+            String country = Locale.getDefault().getCountry();
+
+            if (StringUtils.isNullOrEmpty(country)) {
+                // sometimes country is null, no idea why, guess it.
+                country = language;
+            }
+            localeString = language + "_" + country.toUpperCase();
+        }
+
+        LOG.info("Set language: {}", localeString);
+
+        getSupport().sendCommand(
+                "set language",
+                XiaomiProto.Command.newBuilder()
+                        .setType(COMMAND_TYPE)
+                        .setSubtype(CMD_LANGUAGE)
+                        .setSystem(XiaomiProto.System.newBuilder().setLanguage(
+                                XiaomiProto.Language.newBuilder().setCode(localeString.toLowerCase(Locale.ROOT))
+                        ))
+                        .build()
+        );
+    }
+
+    public void setCurrentTime() {
+        LOG.debug("Setting current time");
+
+        final Calendar now = GregorianCalendar.getInstance();
+        final TimeZone tz = TimeZone.getDefault();
+
+        final String timeFormat = getDevicePrefs().getTimeFormat();
+        final boolean is24hour = DeviceSettingsPreferenceConst.PREF_TIMEFORMAT_24H.equals(timeFormat);
+
+        final XiaomiProto.Clock clock = XiaomiProto.Clock.newBuilder()
+                .setTime(XiaomiProto.Time.newBuilder()
+                        .setHour(now.get(Calendar.HOUR_OF_DAY))
+                        .setMinute(now.get(Calendar.MINUTE))
+                        .setSecond(now.get(Calendar.SECOND))
+                        .setMillisecond(now.get(Calendar.MILLISECOND))
+                        .build())
+                .setDate(XiaomiProto.Date.newBuilder()
+                        .setYear(now.get(Calendar.YEAR))
+                        .setMonth(now.get(Calendar.MONTH) + 1)
+                        .setDay(now.get(Calendar.DATE))
+                        .build())
+                .setTimezone(XiaomiProto.TimeZone.newBuilder()
+                        .setZoneOffset(((now.get(Calendar.ZONE_OFFSET) / 1000) / 60) / 15)
+                        .setDstOffset(((now.get(Calendar.DST_OFFSET) / 1000) / 60) / 15)
+                        .setName(tz.getID())
+                        .build())
+                .setIsNot24Hour(!is24hour)
+                .build();
+
+        getSupport().sendCommand(
+                "set time",
+                XiaomiProto.Command.newBuilder()
+                        .setType(COMMAND_TYPE)
+                        .setSubtype(CMD_CLOCK)
+                        .setSystem(XiaomiProto.System.newBuilder().setClock(clock).build())
+                        .build()
+        );
+    }
+
+    private void handleDeviceInfo(final XiaomiProto.DeviceInfo deviceInfo) {
+        LOG.debug("Got device info: fw={} hw={} sn={}", deviceInfo.getFirmware(), deviceInfo.getModel(), deviceInfo.getSerialNumber());
+
+        final GBDeviceEventVersionInfo gbDeviceEventVersionInfo = new GBDeviceEventVersionInfo();
+        gbDeviceEventVersionInfo.fwVersion = deviceInfo.getFirmware();
+        //gbDeviceEventVersionInfo.fwVersion2 = "N/A";
+        gbDeviceEventVersionInfo.hwVersion = deviceInfo.getModel();
+        getSupport().evaluateGBDeviceEvent(gbDeviceEventVersionInfo);
+
+        final GBDeviceEventUpdateDeviceInfo gbDeviceEventUpdateDeviceInfo = new GBDeviceEventUpdateDeviceInfo("SERIAL: ", deviceInfo.getSerialNumber());
+        getSupport().evaluateGBDeviceEvent(gbDeviceEventUpdateDeviceInfo);
+    }
+
+    private BatteryState convertBatteryStateFromRawValue(int chargerState) {
+        switch (chargerState) {
+            case 1:
+                return BatteryState.BATTERY_CHARGING;
+            case 2:
+            case 3:
+                return BatteryState.BATTERY_NORMAL;
+        }
+
+        return BatteryState.UNKNOWN;
+    }
+
+    private void handleBattery(final XiaomiProto.Battery battery) {
+        LOG.debug("Got battery: {}", battery.getLevel());
+
+        final GBDeviceEventBatteryInfo batteryInfo = new GBDeviceEventBatteryInfo();
+        batteryInfo.batteryIndex = 0;
+        batteryInfo.level = battery.getLevel();
+
+        // currentBatteryState may already be set if the DeviceState message contained the field,
+        // but since some models report their charger state through this message, we will update it
+        // from here
+        if (battery.hasState()) {
+            currentBatteryState = convertBatteryStateFromRawValue(battery.getState());
+
+            if (currentBatteryState == BatteryState.UNKNOWN) {
+                LOG.warn("Unknown battery state {}", battery.getState());
+            }
+        }
+
+        batteryInfo.state = currentBatteryState;
+        getSupport().evaluateGBDeviceEvent(batteryInfo);
+
+        // reset battery level request timer
+        rearmBatteryStateRequestTimer();
+    }
+
+    private void setPassword() {
+        final Prefs prefs = getDevicePrefs();
+
+        final boolean passwordEnabled = prefs.getBoolean(PasswordCapabilityImpl.PREF_PASSWORD_ENABLED, false);
+        final String password = prefs.getString(PasswordCapabilityImpl.PREF_PASSWORD, null);
+
+        LOG.info("Setting password: {}, {}", passwordEnabled, password);
+
+        if (password == null || password.isEmpty()) {
+            LOG.warn("Invalid password: {}", password);
+            return;
+        }
+
+        final XiaomiProto.Password.Builder passwordBuilder = XiaomiProto.Password.newBuilder()
+                .setState(passwordEnabled ? 2 : 1)
+                .setPassword(password);
+
+        getSupport().sendCommand(
+                "set password",
+                XiaomiProto.Command.newBuilder()
+                        .setType(COMMAND_TYPE)
+                        .setSubtype(CMD_PASSWORD_SET)
+                        .setSystem(XiaomiProto.System.newBuilder().setPassword(passwordBuilder).build())
+                        .build()
+        );
+    }
+
+    private void handlePassword(final XiaomiProto.Password password) {
+        LOG.debug("Got device password");
+        final GBDeviceEventUpdatePreferences eventUpdatePreferences = new GBDeviceEventUpdatePreferences(
+                PasswordCapabilityImpl.PREF_PASSWORD_ENABLED,
+                password.getState() == 2
+        );
+        if (password.hasPassword()) {
+            eventUpdatePreferences.withPreference(
+                    PasswordCapabilityImpl.PREF_PASSWORD,
+                    password.getPassword()
+            );
+        }
+        eventUpdatePreferences.withPreference(
+                XiaomiPreferences.FEAT_PASSWORD,
+                true
+        );
+
+        getSupport().evaluateGBDeviceEvent(eventUpdatePreferences);
+    }
+
+    private void handleMiscSettingSet(final XiaomiProto.MiscSettingSet miscSettingSet) {
+        LOG.debug("Got misc setting set");
+
+        final GBDeviceEventUpdatePreferences eventUpdatePreferences = new GBDeviceEventUpdatePreferences();
+
+        if (miscSettingSet.hasWearingMode()) {
+            final String wearMode;
+
+            switch (miscSettingSet.getWearingMode().getMode()) {
+                case 0:
+                    wearMode = "band";
+                    break;
+                case 1:
+                    wearMode = "pebble";
+                    break;
+                case 2:
+                    wearMode = "necklace";
+                    break;
+                default:
+                    wearMode = null;
+                    LOG.warn("Unknown wear mode {}", miscSettingSet.getWearingMode().getMode());
+                    break;
+            }
+
+            eventUpdatePreferences.withPreference(XiaomiPreferences.FEAT_WEAR_MODE, wearMode != null)
+                    .withPreference(DeviceSettingsPreferenceConst.PREF_WEARMODE, wearMode);
+        }
+
+        getSupport().evaluateGBDeviceEvent(eventUpdatePreferences);
+    }
+
+    private void setWearMode() {
+        final String wearMode = getDevicePrefs().getString(DeviceSettingsPreferenceConst.PREF_WEARMODE, "band");
+
+        LOG.debug("Set wear mode to {}", wearMode);
+
+        final int wearModeInt;
+
+        if ("band".equals(wearMode)) {
+            wearModeInt = 0;
+        } else if ("pebble".equals(wearMode)) {
+            wearModeInt = 1;
+        } else if ("necklace".equals(wearMode)) {
+            wearModeInt = 2;
+        } else {
+            LOG.warn("Unknown wear mode {}", wearMode);
+            return;
+        }
+
+        getSupport().sendCommand(
+                "set wear mode",
+                XiaomiProto.Command.newBuilder()
+                        .setType(COMMAND_TYPE)
+                        .setSubtype(CMD_MISC_SETTING_SET)
+                        .setSystem(XiaomiProto.System.newBuilder().setMiscSettingSet(
+                                XiaomiProto.MiscSettingSet.newBuilder().setWearingMode(
+                                        XiaomiProto.WearingMode.newBuilder().setMode(wearModeInt)
+                                )
+                        ))
+                        .build()
+        );
+    }
+
+    private void handleCameraRemote(final XiaomiProto.Camera camera) {
+        LOG.debug("Got camera remote enabled={}", camera.getEnabled());
+
+        final GBDeviceEventUpdatePreferences eventUpdatePreferences = new GBDeviceEventUpdatePreferences()
+                .withPreference(XiaomiPreferences.FEAT_CAMERA_REMOTE, true)
+                .withPreference(DeviceSettingsPreferenceConst.PREF_CAMERA_REMOTE, camera.getEnabled());
+
+        getSupport().evaluateGBDeviceEvent(eventUpdatePreferences);
+    }
+
+    private void setCameraRemoteConfig() {
+        final boolean enabled = getDevicePrefs().getBoolean(DeviceSettingsPreferenceConst.PREF_CAMERA_REMOTE, false);
+
+        LOG.debug("Set camera remote enabled={}", enabled);
+
+        getSupport().sendCommand(
+                "set camera remote",
+                XiaomiProto.Command.newBuilder()
+                        .setType(COMMAND_TYPE)
+                        .setSubtype(CMD_CAMERA_REMOTE_SET)
+                        .setSystem(XiaomiProto.System.newBuilder().setCamera(
+                                XiaomiProto.Camera.newBuilder().setEnabled(enabled)
+                        ))
+                        .build()
+        );
+    }
+
+    private void setDisplayItems() {
+        final Prefs prefs = getDevicePrefs();
+        final List<String> allScreens = new ArrayList<>(prefs.getList(DeviceSettingsUtils.getPrefPossibleValuesKey(HuamiConst.PREF_DISPLAY_ITEMS_SORTABLE), Collections.emptyList()));
+        final List<String> allLabels = new ArrayList<>(prefs.getList(DeviceSettingsUtils.getPrefPossibleValueLabelsKey(HuamiConst.PREF_DISPLAY_ITEMS_SORTABLE), Collections.emptyList()));
+        final List<String> enabledScreens = new ArrayList<>(prefs.getList(HuamiConst.PREF_DISPLAY_ITEMS_SORTABLE, Collections.emptyList()));
+        final String settingsCode = prefs.getString(PREF_SETTINGS_DISPLAY_ITEM_CODE, null);
+
+        if (allScreens.isEmpty()) {
+            LOG.warn("No list of all screens");
+            return;
+        }
+
+        if (allScreens.size() != allLabels.size()) {
+            LOG.warn(
+                    "Mismatched allScreens ({}) and allLabels ({}) sizes - this should never happen",
+                    allScreens.size(),
+                    allLabels.size()
+            );
+            return;
+        }
+
+        final Map<String, String> labelsMap = new HashMap<>();
+        for (int i = 0; i < allScreens.size(); i++) {
+            labelsMap.put(allScreens.get(i), allLabels.get(i));
+        }
+
+        LOG.debug("Setting display items: {}", enabledScreens);
+
+        if (settingsCode != null && !enabledScreens.contains(settingsCode)) {
+            enabledScreens.add(settingsCode);
+        }
+
+        boolean inMoreSection = false;
+        final XiaomiProto.DisplayItems.Builder displayItems = XiaomiProto.DisplayItems.newBuilder();
+        for (final String enabledScreen : enabledScreens) {
+            if (enabledScreen.equals("more")) {
+                inMoreSection = true;
+                continue;
+            }
+            if (labelsMap.get(enabledScreen) == null) {
+                continue;
+            }
+
+            final XiaomiProto.DisplayItem.Builder displayItem = XiaomiProto.DisplayItem.newBuilder()
+                    .setCode(enabledScreen)
+                    .setName(labelsMap.get(enabledScreen))
+                    .setUnknown5(1);
+
+            if (inMoreSection) {
+                displayItem.setInMoreSection(true);
+            }
+
+            if (enabledScreen.equals(settingsCode)) {
+                displayItem.setIsSettings(1);
+            }
+
+            displayItems.addDisplayItem(displayItem);
+        }
+
+        for (final String screen : allScreens) {
+            if (enabledScreens.contains(screen)) {
+                continue;
+            }
+
+            final XiaomiProto.DisplayItem.Builder displayItem = XiaomiProto.DisplayItem.newBuilder()
+                    .setCode(screen)
+                    .setName(labelsMap.get(screen))
+                    .setDisabled(true)
+                    .setUnknown5(1);
+
+            displayItems.addDisplayItem(displayItem);
+        }
+
+        getSupport().sendCommand(
+                "set display items",
+                XiaomiProto.Command.newBuilder()
+                        .setType(COMMAND_TYPE)
+                        .setSubtype(CMD_DISPLAY_ITEMS_SET)
+                        .setSystem(XiaomiProto.System.newBuilder().setDisplayItems(displayItems))
+                        .build()
+        );
+    }
+
+    private void handleDisplayItems(final XiaomiProto.DisplayItems displayItems) {
+        LOG.debug("Got {} display items", displayItems.getDisplayItemCount());
+
+        final List<String> allScreens = new ArrayList<>();
+        final List<String> allScreensLabels = new ArrayList<>();
+        final List<String> mainScreens = new ArrayList<>();
+        final List<String> moreScreens = new ArrayList<>();
+        String settingsCode = null;
+        for (final XiaomiProto.DisplayItem displayItem : displayItems.getDisplayItemList()) {
+            allScreens.add(displayItem.getCode());
+            allScreensLabels.add(displayItem.getName().replace(",", ""));
+            if (!displayItem.getDisabled()) {
+                if (displayItem.getInMoreSection()) {
+                    moreScreens.add(displayItem.getCode());
+                } else {
+                    mainScreens.add(displayItem.getCode());
+                }
+            }
+
+            if (displayItem.getIsSettings() == 1) {
+                settingsCode = displayItem.getCode();
+            }
+        }
+
+        final List<String> enabledScreens = new ArrayList<>(mainScreens);
+        if (!moreScreens.isEmpty()) {
+            enabledScreens.add("more");
+            enabledScreens.addAll(moreScreens);
+        }
+
+        allScreens.add("more");
+        allScreensLabels.add(getSupport().getContext().getString(R.string.menuitem_more));
+
+        final String allScreensPrefValue = StringUtils.join(",", allScreens.toArray(new String[0])).toString();
+        final String allScreensLabelsPrefValue = StringUtils.join(",", allScreensLabels.toArray(new String[0])).toString();
+        final String prefValue = StringUtils.join(",", enabledScreens.toArray(new String[0])).toString();
+
+        final GBDeviceEventUpdatePreferences eventUpdatePreferences = new GBDeviceEventUpdatePreferences()
+                .withPreference(XiaomiPreferences.FEAT_DISPLAY_ITEMS, displayItems.getDisplayItemCount() > 0)
+                .withPreference(DeviceSettingsUtils.getPrefPossibleValuesKey(HuamiConst.PREF_DISPLAY_ITEMS_SORTABLE), allScreensPrefValue)
+                .withPreference(DeviceSettingsUtils.getPrefPossibleValueLabelsKey(HuamiConst.PREF_DISPLAY_ITEMS_SORTABLE), allScreensLabelsPrefValue)
+                .withPreference(PREF_SETTINGS_DISPLAY_ITEM_CODE, settingsCode)
+                .withPreference(HuamiConst.PREF_DISPLAY_ITEMS_SORTABLE, prefValue);
+
+        getSupport().evaluateGBDeviceEvent(eventUpdatePreferences);
+    }
+
+    private void handleWorkoutTypes(final XiaomiProto.WorkoutTypes workoutTypes) {
+        LOG.debug("Got {} workout types", workoutTypes.getWorkoutTypeCount());
+
+        final List<String> codes = new ArrayList<>(workoutTypes.getWorkoutTypeCount());
+        for (final XiaomiProto.WorkoutType workoutType : workoutTypes.getWorkoutTypeList()) {
+            codes.add(String.valueOf(workoutType.getType()));
+        }
+
+        final GBDeviceEventUpdatePreferences eventUpdatePreferences = new GBDeviceEventUpdatePreferences()
+                .withPreference(XiaomiPreferences.PREF_WORKOUT_TYPES, TextUtils.join(",", codes));
+
+        getSupport().evaluateGBDeviceEvent(eventUpdatePreferences);
+    }
+
+    private void handleWearingState(int newStateValue) {
+        WearingState newState;
+
+        switch (newStateValue) {
+            case 1:
+                newState = WearingState.WEARING;
+                break;
+            case 2:
+                newState = WearingState.NOT_WEARING;
+                break;
+            default:
+                LOG.warn("Unknown wearing state {}", newStateValue);
+                return;
+        }
+
+        LOG.debug("Current wearing state = {}, new wearing state = {}", currentWearingState, newState);
+
+        if (currentWearingState != WearingState.UNKNOWN && currentWearingState != newState) {
+            GBDeviceEventWearState event = new GBDeviceEventWearState();
+            event.wearingState = newState;
+            getSupport().evaluateGBDeviceEvent(event);
+        }
+
+        currentWearingState = newState;
+    }
+
+    private void handleSleepDetectionState(int newStateValue) {
+        SleepState newState;
+
+        switch (newStateValue) {
+            case 1:
+                newState = SleepState.ASLEEP;
+                break;
+            case 2:
+                newState = SleepState.AWAKE;
+                break;
+            default:
+                LOG.warn("Unknown sleep detection state {}", newStateValue);
+                return;
+        }
+
+        LOG.debug("Current sleep detection state = {}, new sleep detection state = {}", currentSleepDetectionState, newState);
+
+        if (currentSleepDetectionState != SleepState.UNKNOWN && currentSleepDetectionState != newState) {
+            GBDeviceEventSleepStateDetection event = new GBDeviceEventSleepStateDetection();
+            event.sleepState = newState;
+            getSupport().evaluateGBDeviceEvent(event);
+        }
+
+        currentSleepDetectionState = newState;
+    }
+
+    public void handlePhoneSilentModeGet() {
+        LOG.debug("Watch requested phone silent mode");
+        sendPhoneSilentMode(SilentMode.isPhoneInSilenceMode(getSupport().getDevice().getAddress()));
+    }
+
+    public void handlePhoneSilentModeSet(final XiaomiProto.PhoneSilentModeSet phoneSilentModeSet) {
+        final boolean silent = phoneSilentModeSet.getPhoneSilentMode().getSilent();
+
+        LOG.debug("Set phone silent mode = {}", silent);
+        SilentMode.setPhoneSilentMode(getSupport().getDevice().getAddress(), silent);
+    }
+
+    private void sendPhoneSilentMode(final boolean enabled) {
+        getSupport().sendCommand(
+                "send phone silent mode = " + enabled,
+                XiaomiProto.Command.newBuilder()
+                        .setType(COMMAND_TYPE)
+                        .setSubtype(CMD_SILENT_MODE_SET_FROM_PHONE)
+                        .setSystem(XiaomiProto.System.newBuilder().setPhoneSilentModeSet(
+                                XiaomiProto.PhoneSilentModeSet.newBuilder().setPhoneSilentMode(
+                                        XiaomiProto.PhoneSilentMode.newBuilder().setSilent(enabled)
+                                )
+                        ))
+                        .build()
+        );
+    }
+
+    public void handleBasicDeviceState(XiaomiProto.BasicDeviceState deviceState) {
+        LOG.debug("Got basic device state: {}", deviceState);
+
+        if (null == deviceState) {
+            LOG.warn("Got null for BasicDeviceState, requesting battery state and returning");
+            getSupport().sendCommand("request battery state", COMMAND_TYPE, CMD_BATTERY);
+            return;
+        }
+
+        // If we got basic device state, we support device actions
+        final GBDeviceEventUpdatePreferences eventUpdatePreferences = new GBDeviceEventUpdatePreferences(
+                XiaomiPreferences.FEAT_DEVICE_ACTIONS,
+                true
+        );
+        getSupport().evaluateGBDeviceEvent(eventUpdatePreferences);
+
+        // handle battery info from message
+        {
+            BatteryState newBatteryState = deviceState.getIsCharging() ? BatteryState.BATTERY_CHARGING : BatteryState.BATTERY_NORMAL;
+            LOG.debug("Previous charging state: {}, new charging state: {}", currentBatteryState, newBatteryState);
+
+            currentBatteryState = newBatteryState;
+
+            // if the device state did not have a battery level, request it from the device through other means.
+            // the battery state is now cached, so that it can be used when another response with battery level is received.
+            if (!deviceState.hasBatteryLevel()) {
+                getSupport().sendCommand("request battery state", COMMAND_TYPE, CMD_BATTERY);
+            } else {
+                GBDeviceEventBatteryInfo event = new GBDeviceEventBatteryInfo();
+                event.batteryIndex = 0;
+                event.state = newBatteryState;
+                event.level = deviceState.getBatteryLevel();
+                getSupport().evaluateGBDeviceEvent(event);
+            }
+        }
+
+        // handle sleep state from message
+        {
+            SleepState newSleepState = deviceState.getIsUserAsleep() ? SleepState.ASLEEP : SleepState.AWAKE;
+            LOG.debug("Previous sleep state: {}, new sleep state: {}", currentSleepDetectionState, newSleepState);
+
+            // send event if the previous state is known and the new state is different from cached
+            if (currentSleepDetectionState != SleepState.UNKNOWN && currentSleepDetectionState != newSleepState) {
+                GBDeviceEventSleepStateDetection event = new GBDeviceEventSleepStateDetection();
+                event.sleepState = newSleepState;
+                getSupport().evaluateGBDeviceEvent(event);
+            }
+
+            currentSleepDetectionState = newSleepState;
+        }
+
+        // handle wearing state from message
+        {
+            WearingState newWearingState = deviceState.getIsWorn() ? WearingState.WEARING : WearingState.NOT_WEARING;
+            LOG.debug("Previous wearing state: {}, new wearing state: {}", currentWearingState, newWearingState);
+
+            if (currentWearingState != WearingState.UNKNOWN && currentWearingState != newWearingState) {
+                GBDeviceEventWearState event = new GBDeviceEventWearState();
+                event.wearingState = newWearingState;
+                getSupport().evaluateGBDeviceEvent(event);
+            }
+
+            currentWearingState = newWearingState;
+        }
+
+        // TODO: handle activity state
+
+        // reset battery level refresh timer
+        rearmBatteryStateRequestTimer();
+    }
+
+    public void handleDeviceState(XiaomiProto.DeviceState deviceState) {
+        LOG.debug("Got device state: {}", deviceState);
+
+        if (null == deviceState) {
+            LOG.warn("Got null for DeviceState, requesting battery state and returning");
+            getSupport().sendCommand("request battery state", COMMAND_TYPE, CMD_BATTERY);
+            return;
+        }
+
+        if (deviceState.hasWearingState()) {
+            handleWearingState(deviceState.getWearingState());
+        }
+
+        // The charger state of some devices can only be known when listening for device status
+        // updates. If available, this state will be cached here and updated in the GBDevice upon
+        // the next retrieval of the battery level
+        if (deviceState.hasChargingState()) {
+            BatteryState newBatteryState = convertBatteryStateFromRawValue(deviceState.getChargingState());
+
+            LOG.debug("Current battery state = {}, new battery state = {}", currentBatteryState, newBatteryState);
+
+            if (currentBatteryState != newBatteryState) {
+                currentBatteryState = newBatteryState;
+            }
+        }
+
+        if (deviceState.hasSleepState()) {
+            handleSleepDetectionState(deviceState.getSleepState());
+        }
+
+        // TODO process warning (unknown possible values) and activity information
+
+        // request battery state to request battery level and charger state on supported models
+        getSupport().sendCommand("request battery state", COMMAND_TYPE, CMD_BATTERY);
+    }
+
+    private void handleWidgetScreens(final XiaomiProto.WidgetScreens widgetScreens) {
+        LOG.debug("Got {} widget screens", widgetScreens.getWidgetScreenCount());
+        final GBDeviceEventUpdatePreferences eventUpdatePreferences = new GBDeviceEventUpdatePreferences()
+                // FIXME we're just persisting the protobuf bytes - probably not a good idea
+                .withPreference(XiaomiPreferences.PREF_WIDGET_SCREENS, GB.hexdump(widgetScreens.toByteArray()));
+
+        getSupport().evaluateGBDeviceEvent(eventUpdatePreferences);
+    }
+
+    private void handleWidgetParts(final XiaomiProto.WidgetParts widgetParts) {
+        LOG.debug("Got {} widget parts", widgetParts.getWidgetPartCount());
+        final GBDeviceEventUpdatePreferences eventUpdatePreferences = new GBDeviceEventUpdatePreferences()
+                .withPreference(XiaomiPreferences.FEAT_WIDGETS, widgetParts.getWidgetPartCount() > 0)
+                // FIXME we're just persisting the protobuf bytes - probably not a good idea
+                .withPreference(XiaomiPreferences.PREF_WIDGET_PARTS, GB.hexdump(widgetParts.toByteArray()));
+
+        getSupport().evaluateGBDeviceEvent(eventUpdatePreferences);
+    }
+
+    private void setWidgets() {
+        // Just take the persisted protobuf and send it (see above and XiaomiWidgetManager)
+        final String hex = getDevicePrefs().getString(XiaomiPreferences.PREF_WIDGET_SCREENS, null);
+        if (hex == null) {
+            LOG.warn("raw widget screens hex is null");
+            return;
+        }
+
+        final XiaomiProto.WidgetScreens widgetScreens;
+        try {
+            widgetScreens = XiaomiProto.WidgetScreens.parseFrom(GB.hexStringToByteArray(hex));
+        } catch (final InvalidProtocolBufferException e) {
+            LOG.warn("failed to parse raw widget screns hex");
+            return;
+        }
+
+        LOG.debug("Setting {} widget screens", widgetScreens.getWidgetScreenCount());
+
+        getSupport().sendCommand(
+                "set widgets",
+                XiaomiProto.Command.newBuilder()
+                        .setType(COMMAND_TYPE)
+                        .setSubtype(CMD_WIDGET_SCREENS_SET)
+                        .setSystem(XiaomiProto.System.newBuilder().setWidgetScreens(widgetScreens))
+                        .build()
+        );
+    }
+
+    public void onFindPhone(final boolean start) {
+        LOG.debug("Find phone: {}", start);
+
+        if (!start) {
+            // Stop on watch
+            getSupport().sendCommand(
+                    "find phone stop",
+                    XiaomiProto.Command.newBuilder()
+                            .setType(COMMAND_TYPE)
+                            .setSubtype(CMD_FIND_PHONE)
+                            .setSystem(XiaomiProto.System.newBuilder().setFindDevice(1).build())
+                            .build()
+            );
+        }
+    }
+
+    public void onFindWatch(final boolean start) {
+        LOG.debug("Find watch: {}", start);
+
+        getSupport().sendCommand(
+                "find watch " + start,
+                XiaomiProto.Command.newBuilder()
+                        .setType(COMMAND_TYPE)
+                        .setSubtype(CMD_FIND_WATCH)
+                        .setSystem(XiaomiProto.System.newBuilder().setFindDevice(start ? 0 : 1).build())
+                        .build()
+        );
+    }
+
+    public void installFirmware(final XiaomiFWHelper fwHelper) {
+        assert fwHelper.isValid();
+        assert fwHelper.isFirmware();
+
+        this.fwHelper = fwHelper;
+
+        getSupport().sendCommand(
+                "install firmware " + fwHelper.getVersion(),
+                XiaomiProto.Command.newBuilder()
+                        .setType(COMMAND_TYPE)
+                        .setSubtype(CMD_FIRMWARE_INSTALL)
+                        .setSystem(XiaomiProto.System.newBuilder().setFirmwareInstallRequest(
+                                XiaomiProto.FirmwareInstallRequest.newBuilder()
+                                        .setUnknown1(0)
+                                        .setUnknown2(0)
+                                        .setVersion(fwHelper.getVersion())
+                                        .setMd5(GB.hexdump(CheckSums.md5(fwHelper.getBytes())).toLowerCase(Locale.ROOT))
+                        ))
+                        .build()
+        );
+    }
+
+    private void setDeviceBusy() {
+        final GBDevice device = getSupport().getDevice();
+        device.setBusyTask(getSupport().getContext().getString(R.string.updating_firmware));
+        device.sendDeviceUpdateIntent(getSupport().getContext());
+    }
+
+    private void unsetDeviceBusy() {
+        final GBDevice device = getSupport().getDevice();
+        if (device != null && device.isConnected()) {
+            if (device.isBusy()) {
+                device.unsetBusyTask();
+                device.sendDeviceUpdateIntent(getSupport().getContext());
+            }
+            device.sendDeviceUpdateIntent(getSupport().getContext());
+        }
+    }
+
+    @Override
+    public void onUploadFinish(final boolean success) {
+        LOG.debug("Firmware upload finished: {}", success);
+
+        if (getSupport().getConnectionSpecificSupport() != null) {
+            getSupport().getConnectionSpecificSupport().runOnQueue("firmware upload finish", () -> {
+                getSupport().getDataUploadService().setCallback(null);
+
+                final int notificationMessage = success ?
+                        R.string.updatefirmwareoperation_update_complete :
+                        R.string.updatefirmwareoperation_write_failed;
+
+                onUploadProgress(notificationMessage, 100, false);
+                unsetDeviceBusy();
+
+                fwHelper = null;
+            });
+        }
+    }
+
+    @Override
+    public void onUploadProgress(final int progressPercent) {
+        onUploadProgress(R.string.updatefirmwareoperation_update_in_progress, progressPercent, true);
+    }
+
+    public void onUploadProgress(final int stringResource, final int progressPercent, final boolean ongoing) {
+        getSupport().getConnectionSpecificSupport().onUploadProgress(stringResource, progressPercent, ongoing);
+    }
+
+    private void rearmBatteryStateRequestTimer() {
+        this.handler.removeCallbacks(this.batteryStateRequestRunnable);
+        final DevicePrefs devicePrefs = getDevicePrefs();
+        if (devicePrefs.getBatteryPollingEnabled()) {
+            this.handler.postDelayed(this.batteryStateRequestRunnable, devicePrefs.getBatteryPollingIntervalMinutes() * 60 * 1000L);
+        }
+    }
+
+    @Override
+    public void onDisconnect() {
+        this.handler.removeCallbacks(this.batteryStateRequestRunnable);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/services/XiaomiWatchfaceService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/services/XiaomiWatchfaceService.java
new file mode 100644
index 0000000..eff5a3d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/services/XiaomiWatchfaceService.java
@@ -0,0 +1,279 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.services;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventAppInfo;
+import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.XiaomiFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp;
+import nodomain.freeyourgadget.gadgetbridge.proto.xiaomi.XiaomiProto;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetProgressAction;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.XiaomiSupport;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class XiaomiWatchfaceService extends AbstractXiaomiService implements XiaomiDataUploadService.Callback {
+    private static final Logger LOG = LoggerFactory.getLogger(XiaomiWatchfaceService.class);
+
+    public static final int COMMAND_TYPE = 4;
+
+    public static final int CMD_WATCHFACE_LIST = 0;
+    public static final int CMD_WATCHFACE_SET = 1;
+    public static final int CMD_WATCHFACE_DELETE = 2;
+    public static final int CMD_WATCHFACE_INSTALL = 4;
+
+    private final Set<UUID> allWatchfaces = new HashSet<>();
+    private final Set<UUID> userWatchfaces = new HashSet<>();
+    private UUID activeWatchface = null;
+
+    // Not null if we're installing a firmware
+    private XiaomiFWHelper fwHelper = null;
+
+    public XiaomiWatchfaceService(final XiaomiSupport support) {
+        super(support);
+    }
+
+    @Override
+    public void handleCommand(final XiaomiProto.Command cmd) {
+        switch (cmd.getSubtype()) {
+            case CMD_WATCHFACE_LIST:
+                handleWatchfaceList(cmd.getWatchface().getWatchfaceList());
+                return;
+            case CMD_WATCHFACE_SET:
+                LOG.debug("Got watchface set response, ack={}", cmd.getWatchface().getAck());
+                //requestWatchfaceList();
+                return;
+            case CMD_WATCHFACE_DELETE:
+                LOG.debug("Got watchface delete response, ack={}", cmd.getWatchface().getAck());
+                requestWatchfaceList();
+                return;
+            case CMD_WATCHFACE_INSTALL:
+                final int installStatus = cmd.getWatchface().getInstallStatus();
+                if (installStatus != 0) {
+                    LOG.warn("Invalid watchface install status {} for {}", installStatus, fwHelper.getId());
+                    return;
+                }
+
+                LOG.debug("Watchface install status 0, uploading");
+                setDeviceBusy();
+                getSupport().getDataUploadService().setCallback(this);
+                getSupport().getDataUploadService().requestUpload(XiaomiDataUploadService.TYPE_WATCHFACE, fwHelper.getBytes());
+                return;
+        }
+
+        LOG.warn("Unknown watchface command {}", cmd.getSubtype());
+    }
+
+    public void requestWatchfaceList() {
+        getSupport().sendCommand("request watchface list", COMMAND_TYPE, CMD_WATCHFACE_LIST);
+    }
+
+    private void handleWatchfaceList(final XiaomiProto.WatchfaceList watchfaceList) {
+        LOG.debug("Got {} watchfaces", watchfaceList.getWatchfaceCount());
+
+        allWatchfaces.clear();
+        userWatchfaces.clear();
+        activeWatchface = null;
+
+        final List<GBDeviceApp> gbDeviceApps = new ArrayList<>();
+
+        for (final XiaomiProto.WatchfaceInfo watchface : watchfaceList.getWatchfaceList()) {
+            final UUID uuid = toWatchfaceUUID(watchface.getId());
+            allWatchfaces.add(uuid);
+            if (watchface.getCanDelete()) {
+                userWatchfaces.add(uuid);
+            }
+            if (watchface.getActive()) {
+                activeWatchface = uuid;
+            }
+
+            GBDeviceApp gbDeviceApp = new GBDeviceApp(
+                    uuid,
+                    watchface.getName(),
+                    "",
+                    "",
+                    GBDeviceApp.Type.WATCHFACE
+            );
+            gbDeviceApps.add(gbDeviceApp);
+        }
+
+        final GBDeviceEventAppInfo appInfoCmd = new GBDeviceEventAppInfo();
+        appInfoCmd.apps = gbDeviceApps.toArray(new GBDeviceApp[0]);
+        getSupport().evaluateGBDeviceEvent(appInfoCmd);
+    }
+
+    public void setWatchface(final UUID uuid) {
+        final String id = toWatchfaceId(uuid);
+
+        // TODO for now we need to allow when installing a watchface
+        //if (!allWatchfaces.contains(uuid)) {
+        //    LOG.warn("Unknown watchface {}", uuid);
+        //    return;
+        //}
+
+        activeWatchface = uuid;
+
+        LOG.debug("Set watchface to {}", id);
+
+        getSupport().sendCommand(
+                "set watchface to " + uuid,
+                XiaomiProto.Command.newBuilder()
+                        .setType(COMMAND_TYPE)
+                        .setSubtype(CMD_WATCHFACE_SET)
+                        .setWatchface(XiaomiProto.Watchface.newBuilder().setWatchfaceId(id))
+                        .build()
+        );
+    }
+
+    public void setWatchface(final String watchfaceId) {
+        setWatchface(toWatchfaceUUID(watchfaceId));
+    }
+
+    public void deleteWatchface(final UUID uuid) {
+        final String id = toWatchfaceId(uuid);
+
+        if (!userWatchfaces.contains(uuid)) {
+            LOG.warn("Refusing to delete non-user watchface {}", id);
+            return;
+        }
+
+        if (!allWatchfaces.contains(uuid)) {
+            LOG.warn("Refusing to delete unknown watchface {}", id);
+            return;
+        }
+
+        if (uuid.equals(activeWatchface)) {
+            LOG.warn("Refusing to delete active watchface {}", id);
+            return;
+        }
+
+        LOG.debug("Delete watchface {}", id);
+
+        allWatchfaces.remove(uuid);
+        userWatchfaces.remove(uuid);
+
+        getSupport().sendCommand(
+                "delete watchface " + id,
+                XiaomiProto.Command.newBuilder()
+                        .setType(COMMAND_TYPE)
+                        .setSubtype(CMD_WATCHFACE_DELETE)
+                        .setWatchface(XiaomiProto.Watchface.newBuilder().setWatchfaceId(id))
+                        .build()
+        );
+    }
+
+    public void installWatchface(final XiaomiFWHelper fwHelper) {
+        assert fwHelper.isValid();
+        assert fwHelper.isWatchface();
+
+        this.fwHelper = fwHelper;
+
+        getSupport().sendCommand(
+                "install watchface " + fwHelper.getId(),
+                XiaomiProto.Command.newBuilder()
+                        .setType(COMMAND_TYPE)
+                        .setSubtype(CMD_WATCHFACE_INSTALL)
+                        .setWatchface(XiaomiProto.Watchface.newBuilder().setWatchfaceInstallStart(
+                                XiaomiProto.WatchfaceInstallStart.newBuilder()
+                                        .setId(fwHelper.getId())
+                                        .setSize(fwHelper.getBytes().length)
+                        ))
+                        .build()
+        );
+    }
+
+    public static UUID toWatchfaceUUID(final String id) {
+        // Watchface IDs are numbers as strings - pad them to the right with F
+        // and encode as UUID
+        final String padded = String.format("%-32s", id).replace(' ', 'F');
+        return UUID.fromString(
+                padded.substring(0, 8) + "-" +
+                        padded.substring(8, 12) + "-" +
+                        padded.substring(12, 16) + "-" +
+                        padded.substring(16, 20) + "-" +
+                        padded.substring(20, 32)
+        );
+    }
+
+    public static String toWatchfaceId(final UUID uuid) {
+        return uuid.toString()
+                .replaceAll("-", "")
+                .replaceAll("f", "")
+                .replaceAll("F", "");
+    }
+
+    @Override
+    public void onUploadFinish(final boolean success) {
+        final int notificationMessage = success ?
+                R.string.uploadwatchfaceoperation_complete :
+                R.string.uploadwatchfaceoperation_failed;
+
+        onUploadProgress(notificationMessage, 100, false);
+
+        if (getSupport().getConnectionSpecificSupport() != null) {
+            getSupport().getConnectionSpecificSupport().runOnQueue("watchface upload finish", () -> {
+                LOG.debug("Watchface upload finished: {}", success);
+                getSupport().getDataUploadService().setCallback(null);
+                unsetDeviceBusy();
+
+                if (success) {
+                    setWatchface(fwHelper.getId());
+                    requestWatchfaceList();
+                }
+
+                fwHelper = null;
+            });
+        }
+    }
+
+    @Override
+    public void onUploadProgress(final int progressPercent) {
+        onUploadProgress(R.string.uploadwatchfaceoperation_in_progress, progressPercent, true);
+    }
+
+    private void onUploadProgress(final int stringResource, final int progressPercent, final boolean ongoing) {
+        if (getSupport().getConnectionSpecificSupport() != null)
+            getSupport().getConnectionSpecificSupport().onUploadProgress(stringResource, progressPercent, ongoing);
+    }
+
+    private void setDeviceBusy() {
+        final GBDevice device = getSupport().getDevice();
+        device.setBusyTask(getSupport().getContext().getString(R.string.uploading_watchface));
+        device.sendDeviceUpdateIntent(getSupport().getContext());
+    }
+
+    private void unsetDeviceBusy() {
+        final GBDevice device = getSupport().getDevice();
+        if (device != null && device.isConnected()) {
+            if (device.isBusy()) {
+                device.unsetBusyTask();
+                device.sendDeviceUpdateIntent(getSupport().getContext());
+            }
+            device.sendDeviceUpdateIntent(getSupport().getContext());
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/services/XiaomiWeatherService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/services/XiaomiWeatherService.java
new file mode 100644
index 0000000..cf6670b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/services/XiaomiWeatherService.java
@@ -0,0 +1,651 @@
+/*  Copyright (C) 2023-2024 Andreas Shimokawa, Jos茅 Rebelo, opcode
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.services;
+
+import android.text.TextUtils;
+
+import androidx.annotation.NonNull;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Set;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.SettingsActivity;
+import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.XiaomiWeatherConditions;
+import nodomain.freeyourgadget.gadgetbridge.model.Weather;
+import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
+import nodomain.freeyourgadget.gadgetbridge.proto.xiaomi.XiaomiProto;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.XiaomiPreferences;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.XiaomiSupport;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
+
+public class XiaomiWeatherService extends AbstractXiaomiService {
+    private static final Logger LOG = LoggerFactory.getLogger(XiaomiWeatherService.class);
+
+    private static final int TEMPERATURE_SCALE_CELSIUS = 1;
+    private static final int TEMPERATURE_SCALE_FAHRENHEIT = 2;
+
+    public static final int COMMAND_TYPE = 10;
+
+    private static final int CMD_SET_CURRENT_WEATHER = 0;
+    private static final int CMD_UPDATE_DAILY_FORECAST = 1;
+    private static final int CMD_UPDATE_HOURLY_FORECAST = 2;
+    private static final int CMD_REQUEST_CONDITIONS_FOR_LOCATION = 3;
+    private static final int CMD_GET_LOCATIONS = 5;
+    private static final int CMD_SET_LOCATIONS = 6;
+    private static final int CMD_ADD_LOCATION = 7;
+    private static final int CMD_REMOVE_LOCATIONS = 8;
+    private static final int CMD_GET_WEATHER_PREFS = 9;
+    private static final int CMD_SET_WEATHER_PREFS = 10;
+
+    private final Set<XiaomiProto.WeatherLocation> cachedWeatherLocations = new HashSet<>();
+
+    public XiaomiWeatherService(final XiaomiSupport support) {
+        super(support);
+    }
+
+    @Override
+    public void initialize() {
+        // since temperature unit is app-wide instead of device-specific, update device setting during init
+        setMeasurementSystem();
+
+        // determine whether multiple weather locations are supported by device
+        // device should respond with status 1 if unsupported, we will then send cached weather
+        getSupport().sendCommand("get weather locations", COMMAND_TYPE, CMD_GET_LOCATIONS);
+    }
+
+    @Override
+    public void handleCommand(final XiaomiProto.Command cmd) {
+        if (cmd.hasStatus() && cmd.getStatus() != 0) {
+            LOG.warn("Received Weather command {} with status code {}", cmd.getSubtype(), cmd.getStatus());
+        }
+
+        switch (cmd.getSubtype()) {
+            case CMD_UPDATE_DAILY_FORECAST: {
+                if (!cmd.hasStatus()) {
+
+                    LOG.warn("Received unexpected response to daily forecast update");
+
+                    return;
+                }
+
+                switch (cmd.getStatus()) {
+                    case 0:
+                        LOG.debug("Successfully sent daily forecast update");
+                        break;
+                    case 1:
+                        LOG.warn("Daily forecasts not supported by device");
+                        break;
+                    default:
+                        LOG.error("Unexpected status code {} in response to daily forecast update", cmd.getStatus());
+                        break;
+                }
+
+                return;
+            }
+            case CMD_UPDATE_HOURLY_FORECAST: {
+                if (!cmd.hasStatus()) {
+                    LOG.warn("Received unexpected response to hourly forecast update");
+                    return;
+                }
+
+                switch (cmd.getStatus()) {
+                    case 0:
+                        LOG.debug("Successfully sent hourly forecast update");
+                        break;
+                    case 1:
+                        LOG.warn("Hourly forecasts not supported by device");
+                        break;
+                    default:
+                        LOG.error("Unexpected status code {} in response to hourly forecast update", cmd.getStatus());
+                        break;
+                }
+
+                return;
+            }
+            case CMD_REQUEST_CONDITIONS_FOR_LOCATION: {
+                onConditionRequestReceived(cmd);
+                return;
+            }
+            case CMD_GET_LOCATIONS: {
+                onWeatherLocationsReceived(cmd);
+                return;
+            }
+            case CMD_SET_LOCATIONS: {
+                if (!cmd.hasStatus()) {
+                    LOG.warn("Received unexpected response after setting locations");
+                    return;
+                }
+
+                switch (cmd.getStatus()) {
+                    case 0:
+                        LOG.debug("Successfully updated location list");
+                        break;
+                    case 1:
+                        LOG.warn("Device reported that setting the location list is not supported!");
+                        break;
+                    default:
+                        LOG.error("Received status code {} for setting location list", cmd.getStatus());
+                        break;
+                }
+
+                return;
+            }
+            case CMD_ADD_LOCATION: {
+                if (!cmd.hasStatus()) {
+                    LOG.warn("Unexpected response to add location command");
+                    return;
+                }
+
+                switch (cmd.getStatus()) {
+                    case 0:
+                        LOG.debug("Successfully added weather location");
+                        break;
+                    case 1:
+                        LOG.debug("Adding single weather location not supported on this device");
+                        break;
+                    case 3:
+                        LOG.debug("Failed to add single weather location; this location may have already been added");
+                        break;
+                    default:
+                        LOG.warn("Unexpected status code {} in response to adding single weather location", cmd.getStatus());
+                        break;
+                }
+
+                return;
+            }
+            case CMD_REMOVE_LOCATIONS: {
+                if (!cmd.hasStatus()) {
+                    LOG.warn("Unexpected response to remove locations command");
+                    return;
+                }
+
+                switch (cmd.getStatus()) {
+                    case 0:
+                        LOG.debug("Successfully removed weather locations");
+                        break;
+                    case 1:
+                        LOG.debug("Removing weather locations not supported on this device");
+                        break;
+                    default:
+                        LOG.warn("Unexpected status code {} in response to removing weather locations", cmd.getStatus());
+                        break;
+                }
+
+                return;
+            }
+            case CMD_SET_WEATHER_PREFS: {
+                if (!cmd.hasStatus()) {
+                    LOG.warn("Received unexpected response after setting weather preferences");
+                    return;
+                }
+
+                switch (cmd.getStatus()) {
+                    case 0:
+                        LOG.debug("Successfully updated weather-related preferences");
+                        break;
+                    case 1:
+                        LOG.warn("Weather-related preferences are not supported on this device");
+                        break;
+                    default:
+                        LOG.debug("Unexpected status code {} received in response to weather prefs update request", cmd.getStatus());
+                        break;
+                }
+
+                return;
+            }
+        }
+
+        LOG.warn("Unhandled weather service command {}", cmd.getSubtype());
+        LOG.debug("Unhandled command content: {}", cmd);
+    }
+
+    @Override
+    public boolean onSendConfiguration(final String config, final Prefs prefs) {
+        // TODO add preference for warning notifications (if that has any effect at all)
+
+        switch (config) {
+            case SettingsActivity.PREF_MEASUREMENT_SYSTEM:
+                setMeasurementSystem();
+                return true;
+        }
+
+        return false;
+    }
+
+    private static XiaomiProto.WeatherMetadata getWeatherMetaFromSpec(final WeatherSpec weatherSpec) {
+        final String location = StringUtils.ensureNotNull(weatherSpec.location);
+        return XiaomiProto.WeatherMetadata.newBuilder()
+                .setPublicationTimestamp(unixTimestampToISOWithColons(weatherSpec.timestamp))
+                .setCityName("")
+                .setLocationName(location)
+                .setLocationKey(getLocationKey(location)) // FIXME: placeholder because key is not present in spec
+                .setIsCurrentLocation(weatherSpec.isCurrentLocation == 1)
+                .build();
+    }
+
+    private static XiaomiProto.WeatherLocation getWeatherLocationFromSpec(final WeatherSpec weatherSpec) {
+        return XiaomiProto.WeatherLocation.newBuilder()
+                .setCode(getLocationKey(weatherSpec.location))
+                .setName(StringUtils.ensureNotNull(weatherSpec.location))
+                .build();
+    }
+
+    private static String getLocationKey(final String locationName) {
+        return String.format(Locale.ROOT, "accu:%d", Math.abs(StringUtils.ensureNotNull(locationName).hashCode()) % 1000000);
+    }
+
+    private static XiaomiProto.WeatherUnitValue buildUnitValue(final int value, final String unit) {
+        return XiaomiProto.WeatherUnitValue.newBuilder()
+                .setUnit(unit)
+                .setValue(value)
+                .build();
+    }
+
+    private void addWeatherLocation(final XiaomiProto.WeatherLocation location) {
+        LOG.debug("Adding weather location: code={}, name={}", location.getCode(), location.getName());
+
+        getSupport().sendCommand("add weather location", XiaomiProto.Command.newBuilder()
+                .setType(COMMAND_TYPE)
+                .setSubtype(CMD_ADD_LOCATION)
+                .setWeather(XiaomiProto.Weather.newBuilder()
+                        .setLocation(location))
+                .build());
+    }
+
+    private void addWeatherLocationFromSpec(final WeatherSpec spec) {
+        addWeatherLocation(getWeatherLocationFromSpec(spec));
+    }
+
+    public void sendCurrentConditions(final WeatherSpec weatherSpec) {
+        LOG.debug("Sending current weather conditions for {}", weatherSpec.location);
+
+        XiaomiProto.Command command = XiaomiProto.Command.newBuilder()
+                .setType(COMMAND_TYPE)
+                .setSubtype(CMD_SET_CURRENT_WEATHER)
+                .setWeather(XiaomiProto.Weather.newBuilder().setCurrent(
+                        XiaomiProto.WeatherCurrent.newBuilder()
+                                .setMetadata(getWeatherMetaFromSpec(weatherSpec))
+                                .setWeatherCondition(XiaomiWeatherConditions.convertOwmConditionToXiaomi(weatherSpec.currentConditionCode))
+                                .setTemperature(buildUnitValue(weatherSpec.currentTemp - 273, "鈩�"))
+                                .setHumidity(buildUnitValue(weatherSpec.currentHumidity, "%"))
+                                .setWind(buildUnitValue(weatherSpec.windSpeedAsBeaufort(), Integer.toString(weatherSpec.windDirection)))
+                                .setUv(buildUnitValue(Math.round(weatherSpec.uvIndex), "")) // This is sent as an sint but seems to be displayed with a decimal point
+                                .setAqi(buildUnitValue(
+                                        weatherSpec.airQuality != null && weatherSpec.airQuality.aqi >= 0 ? weatherSpec.airQuality.aqi : 0,
+                                        "Unknown" // some string like "Moderate"
+                                ))
+                                .setWarning(XiaomiProto.WeatherWarnings.newBuilder()) // TODO add warnings when they become available through spec
+                                .setPressure(weatherSpec.pressure * 100f)
+                ))
+                .build();
+
+        getSupport().sendCommand("set current weather", command);
+    }
+
+    public void sendDailyForecast(final WeatherSpec weatherSpec) {
+        final XiaomiProto.ForecastEntries.Builder entryListBuilder = XiaomiProto.ForecastEntries.newBuilder();
+        final int daysToSend = Math.min(6, weatherSpec.forecasts.size());
+
+        // reconstruct first forecast element from current conditions, as the first forecast
+        // is expected to apply to today
+        {
+            entryListBuilder.addEntry(XiaomiProto.ForecastEntry.newBuilder()
+                    .setAqi(buildUnitValue(
+                            weatherSpec.airQuality != null && weatherSpec.airQuality.aqi >= 0 ? weatherSpec.airQuality.aqi : 0,
+                            "Unknown" // TODO describe AQI level
+                    ))
+                    .setTemperatureRange(XiaomiProto.WeatherRange.newBuilder()
+                            .setFrom(weatherSpec.todayMinTemp - 273)
+                            .setTo(weatherSpec.todayMaxTemp - 273))
+                    // FIXME: should preferable be replaced with a best and worst case condition whenever that becomes available
+                    .setConditionRange(XiaomiProto.WeatherRange.newBuilder()
+                            .setFrom(XiaomiWeatherConditions.convertOwmConditionToXiaomi(weatherSpec.currentConditionCode))
+                            .setTo(XiaomiWeatherConditions.convertOwmConditionToXiaomi(weatherSpec.currentConditionCode)))
+                    .setTemperatureSymbol("鈩�")
+                    .setSunriseSunset(XiaomiProto.WeatherSunriseSunset.newBuilder()
+                            .setSunrise(weatherSpec.sunRise != 0 ? unixTimestampToISOWithColons(weatherSpec.sunRise) : "")
+                            .setSunset(weatherSpec.sunSet != 0 ? unixTimestampToISOWithColons(weatherSpec.sunSet) : "")));
+        }
+
+        // loop over available forecast entries in weatherSpec
+        for (WeatherSpec.Daily currentEntry : weatherSpec.forecasts.subList(0, daysToSend)) {
+            entryListBuilder.addEntry(XiaomiProto.ForecastEntry.newBuilder()
+                    .setAqi(buildUnitValue(
+                            currentEntry.airQuality != null && currentEntry.airQuality.aqi >= 0 ? currentEntry.airQuality.aqi : 0,
+                            "Unknown" // TODO describe AQI level
+                    ))
+                    // FIXME should preferable be replaced with a best and worst case condition whenever that becomes available
+                    .setConditionRange(XiaomiProto.WeatherRange.newBuilder()
+                            .setFrom(XiaomiWeatherConditions.convertOwmConditionToXiaomi(currentEntry.conditionCode))
+                            .setTo(XiaomiWeatherConditions.convertOwmConditionToXiaomi(currentEntry.conditionCode)))
+                    .setTemperatureRange(XiaomiProto.WeatherRange.newBuilder()
+                            .setTo(currentEntry.maxTemp - 273)
+                            .setFrom(currentEntry.minTemp - 273))
+                    .setTemperatureSymbol("鈩�")
+                    .setSunriseSunset(XiaomiProto.WeatherSunriseSunset.newBuilder()
+                            .setSunrise(currentEntry.sunRise != 0 ? unixTimestampToISOWithColons(currentEntry.sunRise) : "")
+                            .setSunset(currentEntry.sunSet != 0 ? unixTimestampToISOWithColons(currentEntry.sunSet) : "")));
+        }
+
+        LOG.debug("Sending daily forecast with {} days of info", entryListBuilder.getEntryCount());
+
+        XiaomiProto.Command command = XiaomiProto.Command.newBuilder()
+                .setType(COMMAND_TYPE)
+                .setSubtype(CMD_UPDATE_DAILY_FORECAST)
+                .setWeather(XiaomiProto.Weather.newBuilder().setForecast(
+                        XiaomiProto.WeatherForecast.newBuilder()
+                                .setMetadata(getWeatherMetaFromSpec(weatherSpec))
+                                .setEntries(entryListBuilder)))
+                .build();
+
+        getSupport().sendCommand("set daily forecast", command);
+    }
+
+    public void sendHourlyForecast(final WeatherSpec weatherSpec) {
+        final XiaomiProto.ForecastEntries.Builder entriesBuilder = XiaomiProto.ForecastEntries.newBuilder();
+        final int hoursToSend = Math.min(23, weatherSpec.hourly.size());
+
+        for (WeatherSpec.Hourly hourly : weatherSpec.hourly.subList(0, hoursToSend)) {
+            entriesBuilder.addEntry(XiaomiProto.ForecastEntry.newBuilder()
+                    .setAqi(buildUnitValue(0, "Unknown")) // FIXME when available through spec
+                    .setTemperatureRange(XiaomiProto.WeatherRange.newBuilder()
+                            .setFrom(0) // not set, but required
+                            .setTo(hourly.temp - 273))
+                    .setConditionRange(XiaomiProto.WeatherRange.newBuilder()
+                            .setFrom(0) // not set, but required
+                            .setTo(XiaomiWeatherConditions.convertOwmConditionToXiaomi(hourly.conditionCode)))
+                    .setTemperatureSymbol("鈩�")
+                    .setWind(buildUnitValue(hourly.windSpeedAsBeaufort(), Integer.toString(hourly.windDirection))));
+        }
+
+        LOG.debug("Sending hourly forecast with {} hours of info", entriesBuilder.getEntryCount());
+
+        final XiaomiProto.Command command = XiaomiProto.Command.newBuilder()
+                .setType(COMMAND_TYPE)
+                .setSubtype(CMD_UPDATE_HOURLY_FORECAST)
+                .setWeather(XiaomiProto.Weather.newBuilder()
+                        .setForecast(XiaomiProto.WeatherForecast.newBuilder()
+                                .setMetadata(getWeatherMetaFromSpec(weatherSpec))
+                                .setEntries(entriesBuilder)))
+                .build();
+
+        getSupport().sendCommand("update hourly forecast", command);
+    }
+
+    private boolean supportsMultipleWeatherLocations() {
+        return getDevicePrefs().getBoolean(XiaomiPreferences.FEAT_MULTIPLE_WEATHER_LOCATIONS, false);
+    }
+
+    public void onSendWeather(@NonNull final List<WeatherSpec> weatherSpecList) {
+        if (supportsMultipleWeatherLocations()) {
+            sendWeatherSpecList(weatherSpecList);
+        } else {
+            if (!weatherSpecList.isEmpty() && weatherSpecList.get(0) != null) {
+                final WeatherSpec specToSend = weatherSpecList.get(0);
+                addWeatherLocationFromSpec(specToSend);
+                sendWeatherSpec(specToSend);
+            }
+        }
+    }
+
+    private void sendWeatherSpecList(@NonNull final List<WeatherSpec> weatherSpecs) {
+        // FIXME: the MB8P seems to support more locations than the original app allows, which is
+        //        undoubtedly also applicable to other devices
+        List<WeatherSpec> specsToSend = weatherSpecs.subList(0, Math.min(5, weatherSpecs.size()));
+        List<XiaomiProto.WeatherLocation> weatherLocations = new ArrayList<>(specsToSend.size());
+
+        LOG.debug("Updating weather for {} location(s): {}", specsToSend.size(), extractWeatherSpecLocations(specsToSend));
+
+        // find locations not present on device
+        {
+            for (final WeatherSpec spec : specsToSend) {
+                final XiaomiProto.WeatherLocation location = getWeatherLocationFromSpec(spec);
+
+                if (!cachedWeatherLocations.contains(location)) {
+                    addWeatherLocation(location);
+
+                    // assume adding location goes according to plan
+                    cachedWeatherLocations.add(location);
+                }
+
+                weatherLocations.add(location);
+            }
+        }
+
+        // update order of locations list on device
+        {
+            getSupport().sendCommand("set weather locations order", XiaomiProto.Command.newBuilder()
+                    .setType(COMMAND_TYPE)
+                    .setSubtype(CMD_SET_LOCATIONS)
+                    .setWeather(XiaomiProto.Weather.newBuilder()
+                            .setLocations(XiaomiProto.WeatherLocations.newBuilder()
+                                    .addAllLocation(weatherLocations)))
+                    .build());
+        }
+
+        for (WeatherSpec spec : specsToSend) {
+            sendWeatherSpec(spec);
+        }
+
+        // request current location list from device to remove dangling locations
+        getSupport().sendCommand("request weather locations", COMMAND_TYPE, CMD_GET_LOCATIONS);
+    }
+
+    private void sendWeatherSpec(@NonNull final WeatherSpec weatherSpec) {
+        LOG.debug("Send weather for location {}", weatherSpec.location);
+
+        sendCurrentConditions(weatherSpec);
+        sendDailyForecast(weatherSpec);
+        sendHourlyForecast(weatherSpec);
+    }
+
+    private void setMeasurementSystem() {
+        final String metricScale = getSupport().getContext().getString(R.string.p_unit_metric);
+        final String imperialScale = getSupport().getContext().getString(R.string.p_unit_imperial);
+        final String measurementSystem = GBApplication.getPrefs().getString(SettingsActivity.PREF_MEASUREMENT_SYSTEM, metricScale);
+        LOG.info("Setting measurement system to {}", measurementSystem);
+
+        int unitValue = TEMPERATURE_SCALE_CELSIUS;
+
+        if (measurementSystem.equals(imperialScale)) {
+            unitValue = TEMPERATURE_SCALE_FAHRENHEIT;
+        } else if (!measurementSystem.equals(metricScale)) {
+            LOG.warn("Unknown measurement system, defaulting to celsius");
+        }
+
+        getSupport().sendCommand(
+                "set temperature scale",
+                XiaomiProto.Command.newBuilder()
+                        .setType(COMMAND_TYPE)
+                        .setSubtype(CMD_SET_WEATHER_PREFS)
+                        .setWeather(XiaomiProto.Weather.newBuilder().setPrefs(
+                                XiaomiProto.WeatherPrefs.newBuilder().setTemperatureScale(unitValue)
+                        ))
+                        .build()
+        );
+    }
+
+    private void onConditionRequestReceived(final XiaomiProto.Command command) {
+        if (command.hasStatus() && command.getStatus() != 0) {
+            LOG.warn("Received request for conditions with unexpected status code {}", command.getStatus());
+            return;
+        }
+
+        if (command.hasWeather() && command.getWeather().hasLocation()) {
+            final String locationKey = command.getWeather().getLocation().getCode();
+            final String locationName = command.getWeather().getLocation().getName();
+
+            if (!TextUtils.isEmpty(locationKey) && !TextUtils.isEmpty(locationName)) {
+                LOG.debug("Received request for conditions (location key = {}, name = {})", locationKey, locationName);
+
+                final List<WeatherSpec> knownWeathers = Weather.getInstance().getWeatherSpecs();
+                for (WeatherSpec spec : knownWeathers) {
+                    if (TextUtils.equals(spec.location, locationName)) {
+                        sendWeatherSpec(spec);
+                        return;
+                    }
+                }
+            } else {
+                LOG.debug("Received request for conditions at current location");
+            }
+        }
+
+        final WeatherSpec spec = Weather.getInstance().getWeatherSpec();
+
+        if (spec == null) {
+            LOG.warn("Not sending weather conditions: active weather spec is null!");
+            return;
+        }
+
+        sendWeatherSpec(Weather.getInstance().getWeatherSpec());
+    }
+
+    private static String[] weatherLocationsToStringArray(final Collection<XiaomiProto.WeatherLocation> locations) {
+        final List<String> result = new ArrayList<>();
+
+        for (XiaomiProto.WeatherLocation l : locations) {
+            result.add(String.format("{code=%s, name=%s}", l.getCode(), l.getName()));
+        }
+
+        return result.toArray(new String[0]);
+    }
+
+    private static String[] extractWeatherSpecLocations(final Collection<WeatherSpec> specs) {
+        final List<String> result = new ArrayList<>();
+
+        for (final WeatherSpec spec : specs) {
+            result.add(StringUtils.ensureNotNull(spec.location));
+        }
+
+        return result.toArray(new String[0]);
+    }
+
+    private void onWeatherLocationsReceived(final XiaomiProto.Command cmd) {
+        // status code 1 = explicitly not supported
+        if (cmd.hasStatus() && cmd.getStatus() == 1) {
+            LOG.warn("Multiple weather locations not supported by this device");
+            getSupport().setFeatureSupported(XiaomiPreferences.FEAT_MULTIPLE_WEATHER_LOCATIONS, false);
+
+            // now that the feature flag has been updated, send cached weather
+            onSendWeather(Weather.getInstance().getWeatherSpecs());
+
+            return;
+        }
+
+        if (cmd.hasStatus() && cmd.getStatus() != 0) {
+            LOG.warn("Received unexpected status code for configured weather locations request: {}", cmd.getStatus());
+            return;
+        }
+
+        getSupport().setFeatureSupported(XiaomiPreferences.FEAT_MULTIPLE_WEATHER_LOCATIONS, true);
+
+        if (!cmd.hasWeather() || !cmd.getWeather().hasLocations()) {
+            LOG.warn("Received unexpected payload in response to configured weather locations request");
+            LOG.debug("Unexpected weather locations command: {}", cmd);
+        }
+
+        final List<XiaomiProto.WeatherLocation> retrievedLocations = cmd.getWeather().getLocations().getLocationList();
+
+        LOG.debug("Received {} weather locations: {}", retrievedLocations.size(), weatherLocationsToStringArray(retrievedLocations));
+
+        // remove any duplicate locations from device before caching locations
+        {
+            final Set<XiaomiProto.WeatherLocation> duplicateLocations = new HashSet<>();
+
+            for (XiaomiProto.WeatherLocation l : retrievedLocations) {
+                if (Collections.frequency(retrievedLocations, l) > 1) {
+                    duplicateLocations.add(l);
+                }
+            }
+
+            if (!duplicateLocations.isEmpty()) {
+                LOG.debug("Removing {} locations which were found as duplicates: {}", duplicateLocations.size(), weatherLocationsToStringArray(duplicateLocations));
+
+                getSupport().sendCommand("remove duplicate weather locations", XiaomiProto.Command.newBuilder()
+                        .setType(COMMAND_TYPE)
+                        .setSubtype(CMD_REMOVE_LOCATIONS)
+                        .setWeather(XiaomiProto.Weather.newBuilder()
+                                .setLocations(XiaomiProto.WeatherLocations.newBuilder()
+                                        .addAllLocation(duplicateLocations)))
+                        .build());
+            }
+        }
+
+        final Set<XiaomiProto.WeatherLocation> specLocations = new HashSet<>();
+
+        for (final WeatherSpec s : Weather.getInstance().getWeatherSpecs()) {
+            specLocations.add(getWeatherLocationFromSpec(s));
+        }
+
+        // remove locations for which a cached weather spec cannot be found
+        {
+            final Set<XiaomiProto.WeatherLocation> locationsMissingSpec = new HashSet<>();
+
+            for (XiaomiProto.WeatherLocation l : retrievedLocations) {
+                if (!specLocations.contains(l)) {
+                    locationsMissingSpec.add(l);
+                }
+            }
+
+            if (!locationsMissingSpec.isEmpty()) {
+                LOG.debug("Removing {} weather locations for which a weather spec is not found cached: {}",
+                        locationsMissingSpec.size(),
+                        weatherLocationsToStringArray(locationsMissingSpec));
+
+                getSupport().sendCommand("remove non-cached weather locations", XiaomiProto.Command.newBuilder()
+                        .setType(COMMAND_TYPE)
+                        .setSubtype(CMD_REMOVE_LOCATIONS)
+                        .setWeather(XiaomiProto.Weather.newBuilder()
+                                .setLocations(XiaomiProto.WeatherLocations.newBuilder()
+                                        .addAllLocation(locationsMissingSpec))).build());
+            }
+        }
+
+        // cache location that were unique
+        final Set<XiaomiProto.WeatherLocation> presentLocations = new HashSet<>();
+        for (XiaomiProto.WeatherLocation l : retrievedLocations) {
+            if (specLocations.contains(l) && Collections.frequency(retrievedLocations, l) == 1) {
+                presentLocations.add(l);
+            }
+        }
+
+        LOG.debug("Caching {} weather locations: {}", presentLocations.size(), weatherLocationsToStringArray(presentLocations));
+        cachedWeatherLocations.clear();
+        cachedWeatherLocations.addAll(presentLocations);
+    }
+
+    public static String unixTimestampToISOWithColons(int timestamp) {
+        return new StringBuilder(
+                new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.US)
+                        .format(new Date(timestamp * 1000L)))
+                .insert(22, ':') // FIXME: I bet this fails for some, but all this java date craps sucks
+                .toString();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xwatch/XWatchSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xwatch/XWatchSupport.java
new file mode 100644
index 0000000..bc67f2a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xwatch/XWatchSupport.java
@@ -0,0 +1,491 @@
+/*  Copyright (C) 2018-2024 Andreas Shimokawa, Arjan Schrijver, Carsten
+    Pfeiffer, ladbsoft, mamucho, SalavatR, Sebastian Kranz
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.xwatch;
+
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.content.Context;
+import android.media.AudioManager;
+import android.view.KeyEvent;
+import android.widget.Toast;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo;
+import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.xwatch.XWatchSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.xwatch.XWatchService;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.entities.User;
+import nodomain.freeyourgadget.gadgetbridge.entities.XWatchActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice.State;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
+import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
+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.miband.DeviceInfo;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class XWatchSupport extends AbstractBTLEDeviceSupport {
+    private static final Logger LOG = LoggerFactory.getLogger(XWatchSupport.class);
+    private final GBDeviceEventVersionInfo versionCmd = new GBDeviceEventVersionInfo();
+    TransactionBuilder builder = null;
+    private byte dayToFetch; //0 = Today; 1 = Yesterday ...
+    private byte maxDayToFetch;
+    long lastButtonTimestamp;
+
+    public XWatchSupport() {
+        super(LOG);
+
+        addSupportedService(XWatchService.UUID_SERVICE);
+        addSupportedService(XWatchService.UUID_WRITE);
+        addSupportedService(XWatchService.UUID_NOTIFY);
+    }
+
+    public static byte[] crcChecksum(byte[] data) {
+        byte[] return_data = new byte[(data.length + 1)];
+        byte checksum = 0;
+
+        for (int i = 0; i < data.length; i++) {
+            return_data[i] = data[i];
+            checksum += data[i];
+        }
+        return_data[return_data.length - 1] = checksum;
+
+        return return_data;
+    }
+
+    @Override
+    protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
+        builder.add(new SetDeviceStateAction(getDevice(), State.INITIALIZING, getContext()));
+
+        enableNotifications(builder)
+                .setDateTime(builder)
+                .setInitialized(builder);
+
+        return builder;
+    }
+
+    /**
+     * Last action of initialization sequence. Sets the device to initialized.
+     * It is only invoked if all other actions were successfully run, so the device
+     * must be initialized, then.
+     *
+     * @param builder
+     */
+    private void setInitialized(TransactionBuilder builder) {
+        builder.add(new SetDeviceStateAction(getDevice(), State.INITIALIZED, getContext()));
+    }
+
+    @Override
+    public boolean useAutoConnect() {
+        return true;
+    }
+
+    @Override
+    public boolean connectFirstTime() {
+        for (int i = 0; i < 5; i++) {
+            if (connect()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private XWatchSupport setDateTime(TransactionBuilder builder) {
+        byte[] data;
+
+        LOG.debug("Sending current date to the XWatch");
+        BluetoothGattCharacteristic deviceData = getCharacteristic(XWatchService.UUID_WRITE);
+
+        String time = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());
+        String y = time.substring(2, 4);
+        String M = time.substring(4, 6);
+        String d = time.substring(6, 8);
+        String H = time.substring(8, 10);
+        String m = time.substring(10, 12);
+        String s = time.substring(12, 14);
+        System.out.println(y + ":" + M + ":" + d + ":" + H + ":" + m + ":" + time.substring(12, 14));
+
+        data = new byte[]{(byte) 1,
+                (byte) Integer.parseInt(y, 16),
+                (byte) Integer.parseInt(M, 16),
+                (byte) Integer.parseInt(d, 16),
+                (byte) Integer.parseInt(H, 16),
+                (byte) Integer.parseInt(m, 16),
+                (byte) Integer.parseInt(s, 16),
+                (byte) 0,
+                (byte) 0,
+                (byte) 0,
+                (byte) 0,
+                (byte) 0,
+                (byte) 0,
+                (byte) 0,
+                (byte) 0};
+
+        data = crcChecksum(data);
+
+        builder.write(deviceData, data);
+
+        return this;
+    }
+
+    private XWatchSupport enableNotifications(TransactionBuilder builder) {
+        LOG.debug("Enabling action button");
+        BluetoothGattCharacteristic deviceInfo = getCharacteristic(XWatchService.UUID_NOTIFY);
+        builder.notify(deviceInfo, true);
+        return this;
+    }
+
+    @Override
+    public void onSetAlarms(ArrayList<? extends Alarm> alarms) {
+        //TODO: Implement
+    }
+
+    @Override
+    public void onNotification(NotificationSpec notificationSpec) {
+        try {
+            TransactionBuilder builder = performInitialized("xwatch notification");
+            BluetoothGattCharacteristic deviceData = getCharacteristic(XWatchService.UUID_WRITE);
+            byte[] data = new byte[]{XWatchService.COMMAND_NOTIFICATION, XWatchService.COMMAND_NOTIFICATION_MESSAGE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+            builder.write(deviceData, crcChecksum(data));
+            builder.queue(getQueue());
+        } catch (IOException ex) {
+            LOG.error("Unable to send message notification on XWatch device", ex);
+        }
+    }
+
+    @Override
+    public void onDeleteNotification(int id) {
+        //TODO: Implement
+    }
+
+    @Override
+    public void onSetTime() {
+        try {
+            TransactionBuilder builder = performInitialized("Set date and time");
+            setDateTime(builder);
+            builder.queue(getQueue());
+        } catch (IOException ex) {
+            LOG.error("Unable to set time and date on XWatch device", ex);
+        }
+    }
+
+    @Override
+    public void onSetCallState(CallSpec callSpec) {
+        if (callSpec.command == CallSpec.CALL_INCOMING) {
+            LOG.debug("Incoming call8");
+            try {
+                TransactionBuilder builder = performInitialized("callnotification");
+                BluetoothGattCharacteristic deviceData = getCharacteristic(XWatchService.UUID_WRITE);
+                byte[] data = new byte[]{XWatchService.COMMAND_NOTIFICATION, XWatchService.COMMAND_NOTIFICATION_PHONE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+                builder.write(deviceData, crcChecksum(data));
+                builder.queue(getQueue());
+            } catch (IOException ex) {
+                LOG.error("Unable to send call notification on XWatch device", ex);
+            }
+        }
+    }
+
+    @Override
+    public void onFindDevice(boolean start) {
+        //TODO: Implement
+    }
+
+    @Override
+    public void onSetConstantVibration(int intensity) {
+        //TODO: Implement
+    }
+
+    @Override
+    public void onFetchRecordedData(int dataTypes) {
+        try {
+            if (builder == null) {
+                builder = performInitialized("fetchActivityData");
+            }
+            requestSummarizedData(builder);
+            performConnected(builder.getTransaction());
+        } catch (IOException e) {
+            GB.toast(getContext(), "Error fetching activity data: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
+        }
+    }
+
+    @Override
+    public boolean onCharacteristicChanged(BluetoothGatt gatt,
+                                           BluetoothGattCharacteristic characteristic) {
+        super.onCharacteristicChanged(gatt, characteristic);
+
+        UUID characteristicUUID = characteristic.getUuid();
+        if (XWatchService.UUID_NOTIFY.equals(characteristicUUID)) {
+            byte[] data = characteristic.getValue();
+            if (data[0] == XWatchService.COMMAND_ACTIVITY_TOTALS) {
+                handleSummarizedData(characteristic.getValue());
+            } else if (data[0] == XWatchService.COMMAND_ACTIVITY_DATA) {
+                handleDetailedData(characteristic.getValue());
+            } else if (data[0] == XWatchService.COMMAND_ACTION_BUTTON) {
+                handleButtonPressed(characteristic.getValue());
+            } else if (data[0] == XWatchService.COMMAND_CONNECTED) {
+                handleDeviceInfo(data, BluetoothGatt.GATT_SUCCESS);
+            } else {
+                LOG.info("Handled characteristic with unknown data: " + characteristicUUID);
+                logMessageContent(characteristic.getValue());
+            }
+        } else {
+            LOG.info("Unhandled characteristic changed: " + characteristicUUID);
+            logMessageContent(characteristic.getValue());
+        }
+        return false;
+    }
+
+    @Override
+    public boolean onCharacteristicRead(BluetoothGatt gatt,
+                                        BluetoothGattCharacteristic characteristic, int status) {
+        return super.onCharacteristicChanged(gatt, characteristic);
+        //TODO: Implement (if necessary)
+    }
+
+    @Override
+    public boolean onCharacteristicWrite(BluetoothGatt gatt,
+                                         BluetoothGattCharacteristic characteristic, int status) {
+        return super.onCharacteristicWrite(gatt, characteristic, status);
+        //TODO: Implement (if necessary)
+    }
+
+    public XWatchActivitySample createActivitySample(Device device, User user, int timestampInSeconds, SampleProvider provider) {
+        XWatchActivitySample sample = new XWatchActivitySample();
+        sample.setDevice(device);
+        sample.setUser(user);
+        sample.setTimestamp(timestampInSeconds);
+        sample.setProvider(provider);
+
+        return sample;
+    }
+
+    private void handleDeviceInfo(byte[] value, int status) {
+        if (status == BluetoothGatt.GATT_SUCCESS) {
+            DeviceInfo mDeviceInfo = new DeviceInfo(value);
+            LOG.warn("Device info: " + mDeviceInfo);
+            versionCmd.hwVersion = "1.0";
+            versionCmd.fwVersion = "1.0";
+            handleGBDeviceEvent(versionCmd);
+        }
+    }
+
+    private void handleSummarizedData(byte[] value) {
+        int daysIntTotal;
+        int daysIntPart;
+
+        if (value.length != 16) {
+            LOG.warn("GOT UNEXPECTED SENSOR DATA WITH LENGTH: " + value.length);
+            for (byte b : value) {
+                LOG.warn("DATA: " + String.format("0x%4x", b));
+            }
+        } else {
+            daysIntPart = (value[1] & 255) << 24;
+            daysIntTotal = daysIntPart;
+            daysIntPart = (value[2] & 255) << 16;
+            daysIntTotal += daysIntPart;
+            daysIntPart = (value[3] & 255) << 8;
+            daysIntTotal += daysIntPart;
+            daysIntPart = (value[4] & 255);
+            daysIntTotal += daysIntPart;
+
+            dayToFetch = 0;
+            maxDayToFetch = (byte) Integer.bitCount(daysIntTotal);
+
+            try {
+                requestDetailedData(builder);
+                performConnected(builder.getTransaction());
+            } catch (IOException e) {
+                GB.toast(getContext(), "Error fetching activity data: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
+            }
+        }
+    }
+
+    private void handleDetailedData(byte[] value) {
+        int category, intensity, steps = 0;
+
+        if (value.length != 16) {
+            LOG.warn("GOT UNEXPECTED SENSOR DATA WITH LENGTH: " + value.length);
+            for (byte b : value) {
+                LOG.warn("DATA: " + String.format("0x%4x", b));
+            }
+        } else {
+            try (DBHandler dbHandler = GBApplication.acquireDB()) {
+                XWatchSampleProvider provider = new XWatchSampleProvider(getDevice(), dbHandler.getDaoSession());
+                User user = DBHelper.getUser(dbHandler.getDaoSession());
+                Device device = DBHelper.getDevice(getDevice(), dbHandler.getDaoSession());
+                int timestampInSeconds = 0;
+
+                timestampInSeconds = getTimestampFromData(
+                        value[2],
+                        value[3],
+                        value[4],
+                        value[5]
+                );
+
+                category = ActivityKind.ACTIVITY.getCode();
+                intensity = (value[7] & 255) + ((value[8] & 255) << 8);
+                steps = (value[9] & 255) + ((value[10] & 255) << 8);
+
+                XWatchActivitySample sample = createActivitySample(device, user, timestampInSeconds, provider);
+                sample.setRawIntensity(intensity);
+                sample.setSteps(steps);
+                sample.setRawKind(category);
+
+                if (LOG.isDebugEnabled()) {
+                    LOG.debug("sample: " + sample);
+                }
+
+                provider.addGBActivitySample(sample);
+
+                if (value[5] == 95) {
+                    dayToFetch++;
+                    if (dayToFetch <= maxDayToFetch) {
+                        try {
+                            builder = performInitialized("fetchActivityData");
+                            requestDetailedData(builder);
+                            builder.queue(getQueue());
+                        } catch (IOException e) {
+                            GB.toast(getContext(), "Error fetching activity data: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
+                        }
+                    }
+                }
+            } catch (Exception ex) {
+                GB.toast(getContext(), ex.getMessage(), Toast.LENGTH_LONG, GB.ERROR, ex);
+            }
+        }
+    }
+
+    private void handleButtonPressed(byte[] value) {
+        long currentTimestamp = System.currentTimeMillis();
+
+        AudioManager audioManager = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
+        if (audioManager.isWiredHeadsetOn()) {
+            if (currentTimestamp - lastButtonTimestamp < 1000) {
+                if (audioManager.isMusicActive()) {
+                    audioManager.dispatchMediaKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_NEXT));
+                    audioManager.dispatchMediaKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MEDIA_NEXT));
+                } else {
+                    audioManager.dispatchMediaKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE));
+                    audioManager.dispatchMediaKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE));
+                    audioManager.dispatchMediaKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_NEXT));
+                    audioManager.dispatchMediaKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MEDIA_NEXT));
+                }
+            } else {
+                audioManager.dispatchMediaKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE));
+                audioManager.dispatchMediaKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE));
+            }
+        }
+
+        lastButtonTimestamp = currentTimestamp;
+    }
+
+    private void requestSummarizedData(TransactionBuilder builder) {
+        byte[] fetch = new byte[]{(byte) XWatchService.COMMAND_ACTIVITY_TOTALS,
+                (byte) 0,
+                (byte) 0,
+                (byte) 0,
+                (byte) 0,
+                (byte) 0,
+                (byte) 0,
+                (byte) 0,
+                (byte) 0,
+                (byte) 0,
+                (byte) 0,
+                (byte) 0,
+                (byte) 0,
+                (byte) 0,
+                (byte) 0};
+
+        fetch = XWatchSupport.crcChecksum(fetch);
+        builder.write(getCharacteristic(XWatchService.UUID_WRITE), fetch);
+    }
+
+    private void requestDetailedData(TransactionBuilder builder) {
+        byte[] fetch = new byte[]{(byte) XWatchService.COMMAND_ACTIVITY_DATA,
+                (byte) dayToFetch,
+                (byte) 0,
+                (byte) 0,
+                (byte) 0,
+                (byte) 0,
+                (byte) 0,
+                (byte) 0,
+                (byte) 0,
+                (byte) 0,
+                (byte) 0,
+                (byte) 0,
+                (byte) 0,
+                (byte) 0,
+                (byte) 0};
+
+        fetch = XWatchSupport.crcChecksum(fetch);
+        builder.write(getCharacteristic(XWatchService.UUID_WRITE), fetch);
+    }
+
+    private int getTimestampFromData(byte year, byte month, byte day, byte hoursminutes) {
+        int timestamp = 0;
+        int yearInt, monthInt, dayInt, hoursMinutesInt = 0;
+        int hours, minutes = 0;
+
+        yearInt = Integer.valueOf(String.format("%02x", year), 16);
+        monthInt = Integer.valueOf(String.format("%02x", month), 16);
+        dayInt = Integer.valueOf(String.format("%02x", day), 16);
+        hoursMinutesInt = Integer.valueOf(String.format("%02x", hoursminutes), 16);
+
+        minutes = hoursMinutesInt % 4;
+        hours = (hoursMinutesInt - minutes) / 4;
+        minutes = minutes * 15;
+
+        GregorianCalendar cal = new GregorianCalendar(
+                2000 + yearInt,
+                monthInt - 1,
+                dayInt,
+                hours,
+                minutes
+        );
+
+        timestamp = (int) (cal.getTimeInMillis() / 1000);
+
+        return timestamp;
+    }
+
+    @Override
+    public boolean getImplicitCallbackModify() {
+        return true;
+    }
+
+    @Override
+    public boolean getSendWriteRequestResponse() {
+        return false;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/zetime/ZeTimeDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/zetime/ZeTimeDeviceSupport.java
new file mode 100644
index 0000000..9be0fd5
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/zetime/ZeTimeDeviceSupport.java
@@ -0,0 +1,2251 @@
+/*  Copyright (C) 2018-2024 Andreas Shimokawa, Arjan Schrijver, Carsten
+    Pfeiffer, chklump, Damien Gaignon, Jos茅 Rebelo, Maxim Baz, Petr Van臎k,
+    Sebastian Kranz
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.zetime;
+
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.content.SharedPreferences;
+import android.widget.Toast;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.List;
+import java.util.Locale;
+import java.util.UUID;
+
+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.GBDeviceEventBatteryInfo;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventCallControl;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventMusicControl;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo;
+import nodomain.freeyourgadget.gadgetbridge.devices.zetime.ZeTimeConstants;
+import nodomain.freeyourgadget.gadgetbridge.devices.zetime.ZeTimeSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.ZeTimeActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
+import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
+import nodomain.freeyourgadget.gadgetbridge.model.BatteryState;
+import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
+import nodomain.freeyourgadget.gadgetbridge.util.calendar.CalendarEvent;
+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.Weather;
+import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.GattService;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.WaitAction;
+import nodomain.freeyourgadget.gadgetbridge.util.AlarmUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+import nodomain.freeyourgadget.gadgetbridge.util.calendar.CalendarManager;
+
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SYNC_CALENDAR;
+
+
+public class ZeTimeDeviceSupport extends AbstractBTLEDeviceSupport {
+    private static final Logger LOG = LoggerFactory.getLogger(ZeTimeDeviceSupport.class);
+    private final GBDeviceEventBatteryInfo batteryCmd = new GBDeviceEventBatteryInfo();
+    private final GBDeviceEventVersionInfo versionCmd = new GBDeviceEventVersionInfo();
+    private final GBDeviceEventMusicControl musicCmd = new GBDeviceEventMusicControl();
+    private final GBDeviceEventCallControl callCmd = new GBDeviceEventCallControl();
+    private final int eightHourOffset = 28800;
+    private byte[] lastMsg;
+    private byte msgPart;
+    private int availableSleepData;
+    private int availableStepsData;
+    private int availableHeartRateData;
+    private int progressSteps;
+    private int progressSleep;
+    private int progressHeartRate;
+    private final int maxMsgLength = 20;
+    private boolean callIncoming = false;
+    private String songtitle = null;
+    private byte musicState = -1;
+    public byte[] music = null;
+    private byte volume = 50;
+    private byte[][] remindersOnWatch = new byte[3][10];
+
+    private BluetoothGattCharacteristic notifyCharacteristic = null;
+    private BluetoothGattCharacteristic writeCharacteristic = null;
+    private BluetoothGattCharacteristic ackCharacteristic = null;
+    private BluetoothGattCharacteristic replyCharacteristic = null;
+
+    public ZeTimeDeviceSupport() {
+        super(LOG);
+        addSupportedService(GattService.UUID_SERVICE_GENERIC_ACCESS);
+        addSupportedService(GattService.UUID_SERVICE_GENERIC_ATTRIBUTE);
+        addSupportedService(ZeTimeConstants.UUID_SERVICE_BASE);
+        addSupportedService(ZeTimeConstants.UUID_SERVICE_EXTEND);
+        addSupportedService(ZeTimeConstants.UUID_SERVICE_HEART_RATE);
+    }
+
+    @Override
+    protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
+        LOG.info("Initializing");
+        msgPart = 0;
+        availableStepsData = 0;
+        availableHeartRateData = 0;
+        availableSleepData = 0;
+        progressSteps = 0;
+        progressSleep = 0;
+        progressHeartRate = 0;
+        builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext()));
+
+        notifyCharacteristic = getCharacteristic(ZeTimeConstants.UUID_NOTIFY_CHARACTERISTIC);
+        writeCharacteristic = getCharacteristic(ZeTimeConstants.UUID_WRITE_CHARACTERISTIC);
+        ackCharacteristic = getCharacteristic(ZeTimeConstants.UUID_ACK_CHARACTERISTIC);
+        replyCharacteristic = getCharacteristic(ZeTimeConstants.UUID_REPLY_CHARACTERISTIC);
+
+        builder.notify(ackCharacteristic, true);
+        builder.notify(notifyCharacteristic, true);
+        requestDeviceInfo(builder);
+        requestBatteryInfo(builder);
+        setUserInfo(builder);
+        setUserGoals(builder);
+        setLanguage(builder);
+        requestActivityInfo(builder);
+        synchronizeTime(builder);
+        initMusicVolume(builder);
+        onReadReminders(builder);
+        sendUpcomingCalendarEvents(builder);
+        return builder;
+    }
+
+    @Override
+    public void onSendConfiguration(String config) {
+        try {
+            TransactionBuilder builder = performInitialized("sendConfiguration");
+            switch (config) {
+                case DeviceSettingsPreferenceConst.PREF_WEARLOCATION:
+                    setWrist(builder);
+                    break;
+                case DeviceSettingsPreferenceConst.PREF_LANGUAGE:
+                    setLanguage(builder);
+                    break;
+                case ZeTimeConstants.PREF_SCREENTIME:
+                    setScreenTime(builder);
+                    break;
+                case ZeTimeConstants.PREF_ANALOG_MODE:
+                    setAnalogMode(builder);
+                    break;
+                case ZeTimeConstants.PREF_ACTIVITY_TRACKING:
+                    setActivityTracking(builder);
+                    break;
+                case ZeTimeConstants.PREF_HANDMOVE_DISPLAY:
+                    setDisplayOnMovement(builder);
+                    break;
+                case DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_NOAUTO:
+                case DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_NOAUTO_START:
+                case DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_NOAUTO_END:
+                    setDoNotDisturb(builder);
+                    break;
+                case ZeTimeConstants.PREF_CALORIES_TYPE:
+                    setCaloriesType(builder);
+                    break;
+                case DeviceSettingsPreferenceConst.PREF_TIMEFORMAT:
+                    setTimeFormate(builder);
+                    break;
+                case ZeTimeConstants.PREF_DATE_FORMAT:
+                    setDateFormate(builder);
+                    break;
+                case DeviceSettingsPreferenceConst.PREF_INACTIVITY_KEY:
+                case DeviceSettingsPreferenceConst.PREF_INACTIVITY_ENABLE:
+                case DeviceSettingsPreferenceConst.PREF_INACTIVITY_START:
+                case DeviceSettingsPreferenceConst.PREF_INACTIVITY_END:
+                case DeviceSettingsPreferenceConst.PREF_INACTIVITY_THRESHOLD:
+                case DeviceSettingsPreferenceConst.PREF_INACTIVITY_MO:
+                case DeviceSettingsPreferenceConst.PREF_INACTIVITY_TU:
+                case DeviceSettingsPreferenceConst.PREF_INACTIVITY_WE:
+                case DeviceSettingsPreferenceConst.PREF_INACTIVITY_TH:
+                case DeviceSettingsPreferenceConst.PREF_INACTIVITY_FR:
+                case DeviceSettingsPreferenceConst.PREF_INACTIVITY_SA:
+                case DeviceSettingsPreferenceConst.PREF_INACTIVITY_SU:
+                    setInactivityAlert(builder);
+                    break;
+                case ZeTimeConstants.PREF_SMS_SIGNALING:
+                case ZeTimeConstants.PREF_CALL_SIGNALING:
+                case ZeTimeConstants.PREF_MISSED_CALL_SIGNALING:
+                case ZeTimeConstants.PREF_EMAIL_SIGNALING:
+                case ZeTimeConstants.PREF_SOCIAL_SIGNALING:
+                case ZeTimeConstants.PREF_CALENDAR_SIGNALING:
+                case ZeTimeConstants.PREF_INACTIVITY_SIGNALING:
+                case ZeTimeConstants.PREF_LOW_POWER_SIGNALING:
+                case ZeTimeConstants.PREF_ANTI_LOSS_SIGNALING:
+                    setSignaling(builder, config);
+                    break;
+                case ZeTimeConstants.PREF_SHOCK_STRENGTH:
+                    setShockStrength(builder);
+                case ZeTimeConstants.PREF_ZETIME_HEARTRATE_ALARM:
+                case ZeTimeConstants.PREF_ZETIME_MAX_HEARTRATE:
+                case ZeTimeConstants.PREF_ZETIME_MIN_HEARTRATE:
+                    setHeartRateLimits(builder);
+                    break;
+                case DeviceSettingsPreferenceConst.PREF_USER_FITNESS_GOAL:
+                case ActivityUser.PREF_USER_SLEEP_DURATION:
+                case ActivityUser.PREF_USER_CALORIES_BURNT:
+                case ActivityUser.PREF_USER_DISTANCE_METERS:
+                case ActivityUser.PREF_USER_ACTIVETIME_MINUTES:
+                    setUserGoals(builder);
+                    break;
+            }
+            builder.queue(getQueue());
+        } catch (IOException e) {
+            GB.toast(getContext(), "Error sending configuration: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
+        }
+    }
+
+    @Override
+    public void onFindDevice(boolean start) {
+        try {
+            TransactionBuilder builder = performInitialized("onFindDevice");
+            byte[] testSignaling = {
+                    ZeTimeConstants.CMD_PREAMBLE,
+                    ZeTimeConstants.CMD_TEST_SIGNALING,
+                    ZeTimeConstants.CMD_SEND,
+                    (byte) 0x1,
+                    (byte) 0x0,
+                    (byte) (start ? 1 : 0),
+                    ZeTimeConstants.CMD_END
+            };
+            sendMsgToWatch(builder, testSignaling);
+            builder.queue(getQueue());
+        } catch (IOException e) {
+            GB.toast(getContext(), "Error on function onFindDevice: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
+        }
+    }
+
+    @Override
+    public boolean useAutoConnect() {
+        return false;
+    }
+
+    @Override
+    public void onSetHeartRateMeasurementInterval(int seconds) {
+        int heartRateMeasurementIntervall; // 0 means off
+        heartRateMeasurementIntervall = seconds / 60; // zetime accepts only minutes
+
+        byte[] heartrate = {ZeTimeConstants.CMD_PREAMBLE,
+                ZeTimeConstants.CMD_AUTO_HEARTRATE,
+                ZeTimeConstants.CMD_SEND,
+                (byte) 0x1,
+                (byte) 0x0,
+                (byte) heartRateMeasurementIntervall,
+                ZeTimeConstants.CMD_END};
+
+        try {
+            TransactionBuilder builder = performInitialized("enableAutoHeartRate");
+            sendMsgToWatch(builder, heartrate);
+            builder.queue(getQueue());
+        } catch (IOException e) {
+            GB.toast(getContext(), "Error enable auto heart rate measurement: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
+        }
+    }
+
+    @Override
+    public void onSetAlarms(ArrayList<? extends Alarm> alarms) {
+        byte[] alarmMessage = null;
+        try {
+            TransactionBuilder builder = performInitialized("setAlarms");
+
+            Prefs prefs = getDevicePrefs();
+
+            for (Alarm alarm : alarms) {
+                if (remindersOnWatch[alarm.getPosition()][0] == 0) {
+                    alarmMessage = new byte[]{
+                            ZeTimeConstants.CMD_PREAMBLE,
+                            ZeTimeConstants.CMD_REMINDERS,
+                            ZeTimeConstants.CMD_SEND,
+                            (byte) 0xb,
+                            (byte) 0x0,
+                            (byte) 0x0,//(byte) alarm.getPosition(), // index
+                            ZeTimeConstants.REMINDER_ALARM,
+                            (byte) 0x0, // year low byte
+                            (byte) 0x0, // year high byte
+                            (byte) 0x0, // month
+                            (byte) 0x0, // day
+                            (byte) AlarmUtils.toCalendar(alarm).get(Calendar.HOUR_OF_DAY),
+                            (byte) AlarmUtils.toCalendar(alarm).get(Calendar.MINUTE),
+                            (byte) alarm.getRepetition(),
+                            (byte) (alarm.getEnabled() ? 1 : 0),
+                            (byte) prefs.getInt(ZeTimeConstants.PREF_ALARM_SIGNALING, 11), // reminder signaling
+                            ZeTimeConstants.CMD_END
+                    };
+                    System.arraycopy(alarmMessage, 6, remindersOnWatch[alarm.getPosition()], 0, 10);
+                } else {
+                    alarmMessage = new byte[]{
+                            ZeTimeConstants.CMD_PREAMBLE,
+                            ZeTimeConstants.CMD_REMINDERS,
+                            ZeTimeConstants.CMD_SEND,
+                            (byte) 0x15,
+                            (byte) 0x0,
+                            (byte) 0x1, // edit alarm
+                            remindersOnWatch[alarm.getPosition()][0],
+                            remindersOnWatch[alarm.getPosition()][1],
+                            remindersOnWatch[alarm.getPosition()][2],
+                            remindersOnWatch[alarm.getPosition()][3],
+                            remindersOnWatch[alarm.getPosition()][4],
+                            remindersOnWatch[alarm.getPosition()][5],
+                            remindersOnWatch[alarm.getPosition()][6],
+                            remindersOnWatch[alarm.getPosition()][7],
+                            remindersOnWatch[alarm.getPosition()][8],
+                            remindersOnWatch[alarm.getPosition()][9],
+                            ZeTimeConstants.REMINDER_ALARM,
+                            (byte) 0x0, // year low byte
+                            (byte) 0x0, // year high byte
+                            (byte) 0x0, // month
+                            (byte) 0x0, // day
+                            (byte) AlarmUtils.toCalendar(alarm).get(Calendar.HOUR_OF_DAY),
+                            (byte) AlarmUtils.toCalendar(alarm).get(Calendar.MINUTE),
+                            (byte) alarm.getRepetition(),
+                            (byte) (alarm.getEnabled() ? 1 : 0),
+                            (byte) prefs.getInt(ZeTimeConstants.PREF_ALARM_SIGNALING, 11), // reminder signaling
+                            ZeTimeConstants.CMD_END
+                    };
+                    System.arraycopy(alarmMessage, 16, remindersOnWatch[alarm.getPosition()], 0, 10);
+                }
+                sendMsgToWatch(builder, alarmMessage);
+            }
+            builder.queue(getQueue());
+        } catch (IOException e) {
+            GB.toast(getContext(), "Error set alarms: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
+        }
+    }
+
+    @Override
+    public void onSetMusicInfo(MusicSpec musicSpec) {
+        songtitle = musicSpec.track;
+        if (musicState != -1) {
+            music = new byte[songtitle.getBytes(StandardCharsets.UTF_8).length + 7]; // 7 bytes for status and overhead
+            music[0] = ZeTimeConstants.CMD_PREAMBLE;
+            music[1] = ZeTimeConstants.CMD_MUSIC_CONTROL;
+            music[2] = ZeTimeConstants.CMD_REQUEST_RESPOND;
+            music[3] = (byte) ((songtitle.getBytes(StandardCharsets.UTF_8).length + 1) & 0xff);
+            music[4] = (byte) ((songtitle.getBytes(StandardCharsets.UTF_8).length + 1) >> 8);
+            music[5] = musicState;
+            System.arraycopy(songtitle.getBytes(StandardCharsets.UTF_8), 0, music, 6, songtitle.getBytes(StandardCharsets.UTF_8).length);
+            music[music.length - 1] = ZeTimeConstants.CMD_END;
+            try {
+                TransactionBuilder builder = performInitialized("setMusicStateInfo");
+                replyMsgToWatch(builder, music);
+                builder.queue(getQueue());
+            } catch (IOException e) {
+                GB.toast(getContext(), "Error setting music state and info: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
+            }
+        }
+    }
+
+    @Override
+    public void onSetCallState(CallSpec callSpec) {
+        int subject_length = 0;
+        int notification_length = 0;
+        byte[] subject = null;
+        byte[] notification;
+        Calendar time = GregorianCalendar.getInstance();
+        // convert every single digit of the date to ascii characters
+        // we do it like so: use the base chrachter of '0' and add the digit
+        byte[] datetimeBytes = new byte[]{
+                (byte) ((time.get(Calendar.YEAR) / 1000) + '0'),
+                (byte) (((time.get(Calendar.YEAR) / 100) % 10) + '0'),
+                (byte) (((time.get(Calendar.YEAR) / 10) % 10) + '0'),
+                (byte) ((time.get(Calendar.YEAR) % 10) + '0'),
+                (byte) (((time.get(Calendar.MONTH) + 1) / 10) + '0'),
+                (byte) (((time.get(Calendar.MONTH) + 1) % 10) + '0'),
+                (byte) ((time.get(Calendar.DAY_OF_MONTH) / 10) + '0'),
+                (byte) ((time.get(Calendar.DAY_OF_MONTH) % 10) + '0'),
+                (byte) 'T',
+                (byte) ((time.get(Calendar.HOUR_OF_DAY) / 10) + '0'),
+                (byte) ((time.get(Calendar.HOUR_OF_DAY) % 10) + '0'),
+                (byte) ((time.get(Calendar.MINUTE) / 10) + '0'),
+                (byte) ((time.get(Calendar.MINUTE) % 10) + '0'),
+                (byte) ((time.get(Calendar.SECOND) / 10) + '0'),
+                (byte) ((time.get(Calendar.SECOND) % 10) + '0'),
+        };
+
+        if (callIncoming || (callSpec.command == CallSpec.CALL_INCOMING)) {
+            if (callSpec.command == CallSpec.CALL_INCOMING) {
+                if (callSpec.name != null) {
+                    notification_length += callSpec.name.getBytes(StandardCharsets.UTF_8).length;
+                    subject_length = callSpec.name.getBytes(StandardCharsets.UTF_8).length;
+                    subject = new byte[subject_length];
+                    System.arraycopy(callSpec.name.getBytes(StandardCharsets.UTF_8), 0, subject, 0, subject_length);
+                } else if (callSpec.number != null) {
+                    notification_length += callSpec.number.getBytes(StandardCharsets.UTF_8).length;
+                    subject_length = callSpec.number.getBytes(StandardCharsets.UTF_8).length;
+                    subject = new byte[subject_length];
+                    System.arraycopy(callSpec.number.getBytes(StandardCharsets.UTF_8), 0, subject, 0, subject_length);
+                }
+                notification_length += datetimeBytes.length + 10; // add message overhead
+                notification = new byte[notification_length];
+                notification[0] = ZeTimeConstants.CMD_PREAMBLE;
+                notification[1] = ZeTimeConstants.CMD_PUSH_EX_MSG;
+                notification[2] = ZeTimeConstants.CMD_SEND;
+                notification[3] = (byte) ((notification_length - 6) & 0xff);
+                notification[4] = (byte) ((notification_length - 6) >> 8);
+                notification[5] = ZeTimeConstants.NOTIFICATION_INCOME_CALL;
+                notification[6] = 1;
+                notification[7] = (byte) subject_length;
+                notification[8] = (byte) 0;
+                System.arraycopy(subject, 0, notification, 9, subject_length);
+                System.arraycopy(datetimeBytes, 0, notification, 9 + subject_length, datetimeBytes.length);
+                notification[notification_length - 1] = ZeTimeConstants.CMD_END;
+                callIncoming = true;
+            } else {
+                notification_length = datetimeBytes.length + 10; // add message overhead
+                notification = new byte[notification_length];
+                notification[0] = ZeTimeConstants.CMD_PREAMBLE;
+                notification[1] = ZeTimeConstants.CMD_PUSH_EX_MSG;
+                notification[2] = ZeTimeConstants.CMD_SEND;
+                notification[3] = (byte) ((notification_length - 6) & 0xff);
+                notification[4] = (byte) ((notification_length - 6) >> 8);
+                notification[5] = ZeTimeConstants.NOTIFICATION_CALL_OFF;
+                notification[6] = 1;
+                notification[7] = (byte) 0;
+                notification[8] = (byte) 0;
+                System.arraycopy(datetimeBytes, 0, notification, 9, datetimeBytes.length);
+                notification[notification_length - 1] = ZeTimeConstants.CMD_END;
+                callIncoming = false;
+            }
+            try {
+                TransactionBuilder builder = performInitialized("setCallState");
+                sendMsgToWatch(builder, notification);
+                builder.queue(getQueue());
+            } catch (IOException e) {
+                GB.toast(getContext(), "Error set call state: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
+            }
+        }
+
+    }
+
+    @Override
+    public void onFetchRecordedData(int dataTypes) {
+        try {
+            TransactionBuilder builder = performInitialized("fetchActivityData");
+            requestActivityInfo(builder);
+            builder.queue(getQueue());
+        } catch (IOException e) {
+            GB.toast(getContext(), "Error on fetching activity data: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
+        }
+    }
+
+    @Override
+    public void onTestNewFunction() {
+//        byte[] strength = {
+//                ZeTimeConstants.CMD_PREAMBLE,
+//                ZeTimeConstants.CMD_SHOCK_STRENGTH,
+//                ZeTimeConstants.CMD_REQUEST,
+//                (byte)0x1,
+//                (byte)0x0,
+//                (byte)0x0,
+//                ZeTimeConstants.CMD_END
+//        };
+//        try {
+//            TransactionBuilder builder = performInitialized("testNewFunction");
+//            sendMsgToWatch(builder, strength);
+//            builder.queue(getQueue());
+//        } catch (IOException e) {
+//            GB.toast(getContext(), "Error on testing new function: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
+//        }
+    }
+
+    @Override
+    public void onSetMusicState(MusicStateSpec stateSpec) {
+        musicState = stateSpec.state;
+        if (songtitle != null) {
+            music = new byte[songtitle.getBytes(StandardCharsets.UTF_8).length + 7]; // 7 bytes for status and overhead
+            music[0] = ZeTimeConstants.CMD_PREAMBLE;
+            music[1] = ZeTimeConstants.CMD_MUSIC_CONTROL;
+            music[2] = ZeTimeConstants.CMD_REQUEST_RESPOND;
+            music[3] = (byte) ((songtitle.getBytes(StandardCharsets.UTF_8).length + 1) & 0xff);
+            music[4] = (byte) ((songtitle.getBytes(StandardCharsets.UTF_8).length + 1) >> 8);
+            if (stateSpec.state == MusicStateSpec.STATE_PLAYING) {
+                music[5] = 0;
+            } else {
+                music[5] = 1;
+            }
+            System.arraycopy(songtitle.getBytes(StandardCharsets.UTF_8), 0, music, 6, songtitle.getBytes(StandardCharsets.UTF_8).length);
+            music[music.length - 1] = ZeTimeConstants.CMD_END;
+            try {
+                TransactionBuilder builder = performInitialized("setMusicStateInfo");
+                replyMsgToWatch(builder, music);
+                builder.queue(getQueue());
+            } catch (IOException e) {
+                GB.toast(getContext(), "Error setting music state and info: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
+            }
+        }
+    }
+
+    @Override
+    public void onAddCalendarEvent(CalendarEventSpec calendarEventSpec) {
+        // This is not used currently since we cannot add and remove calendar event one by one pebble style.
+        byte[] calendarEvent = encodeCalendarEvent(calendarEventSpec.title, calendarEventSpec.timestamp, (byte) (calendarEventSpec.type + 1)); // HACK)
+        try {
+            TransactionBuilder builder = performInitialized("sendCalendarEvent");
+            sendMsgToWatch(builder, calendarEvent);
+            builder.queue(getQueue());
+        } catch (IOException e) {
+            GB.toast(getContext(), "Error sending calendar event: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
+        }
+    }
+
+    private byte[] encodeCalendarEvent(String title, int timestamp, byte opcode) {
+        Calendar time = GregorianCalendar.getInstance();
+        byte[] calendarEvent = new byte[title.getBytes(StandardCharsets.UTF_8).length + 16]; // 26 bytes for calendar and overhead
+        time.setTimeInMillis(timestamp * 1000L);
+        calendarEvent[0] = ZeTimeConstants.CMD_PREAMBLE;
+        calendarEvent[1] = ZeTimeConstants.CMD_PUSH_CALENDAR_DAY;
+        calendarEvent[2] = ZeTimeConstants.CMD_SEND;
+        calendarEvent[3] = (byte) ((title.getBytes(StandardCharsets.UTF_8).length + 10) & 0xff);
+        calendarEvent[4] = (byte) ((title.getBytes(StandardCharsets.UTF_8).length + 10) >> 8);
+        // 0 = delete all expect the new one?. 4 = delete all?, 2 = add event?, 1 = first?, 3=last?
+        calendarEvent[5] = opcode;
+        calendarEvent[6] = (byte) (time.get(Calendar.YEAR) & 0xff);
+        calendarEvent[7] = (byte) (time.get(Calendar.YEAR) >> 8);
+        calendarEvent[8] = (byte) (time.get(Calendar.MONTH) + 1);
+        calendarEvent[9] = (byte) time.get(Calendar.DAY_OF_MONTH);
+        calendarEvent[10] = (byte) (time.get(Calendar.HOUR_OF_DAY) & 0xff);
+        calendarEvent[11] = (byte) (time.get(Calendar.MINUTE) & 0xff);
+        calendarEvent[12] = 0; // ?
+        calendarEvent[13] = 0; // ?
+        calendarEvent[14] = (byte) title.getBytes(StandardCharsets.UTF_8).length;
+        System.arraycopy(title.getBytes(StandardCharsets.UTF_8), 0, calendarEvent, 15, title.getBytes(StandardCharsets.UTF_8).length);
+        calendarEvent[calendarEvent.length - 1] = ZeTimeConstants.CMD_END;
+
+        return calendarEvent;
+    }
+
+    private void sendUpcomingCalendarEvents(TransactionBuilder builder) {
+        boolean syncCalendar = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getBoolean(PREF_SYNC_CALENDAR, false);
+        if (!syncCalendar) {
+            return;
+        }
+
+        CalendarManager upcomingEvents = new CalendarManager(getContext(), getDevice().getAddress());
+        List<CalendarEvent> calendarEvents = upcomingEvents.getCalendarEventList();
+
+        int eventCount = 0;
+        for (CalendarEvent calendarEvent : calendarEvents) {
+            if (calendarEvent.isAllDay()) {
+                continue;
+            }
+            String body = calendarEvent.getTitle();
+            if (body == null) {
+                body = "";
+            }
+            String description = calendarEvent.getDescription();
+            if (description != null) {
+                body += " : " + description;
+            }
+            byte opcode = 2;
+            if (eventCount == 0) opcode = 1;
+
+            byte[] message = encodeCalendarEvent(body, calendarEvent.getBeginSeconds(), opcode);
+            sendMsgToWatch(builder, message);
+            builder.add(new WaitAction(300)); // Urgh, seems it is a general problem when sending data too fast
+
+            if (eventCount++ == 16) { // limit this to 16 for now
+                break;
+            }
+        }
+    }
+
+    @Override
+    public void onSetTime() {
+        try {
+            TransactionBuilder builder = performInitialized("synchronizeTime");
+            synchronizeTime(builder);
+            builder.queue(getQueue());
+        } catch (IOException e) {
+            GB.toast(getContext(), "Error setting the time: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
+        }
+    }
+
+    @Override
+    public void onSendWeather(ArrayList<WeatherSpec> weatherSpecs) {
+        WeatherSpec weatherSpec = weatherSpecs.get(0);
+        byte[] weather = new byte[weatherSpec.location.getBytes(StandardCharsets.UTF_8).length + 26]; // 26 bytes for weatherdata and overhead
+        weather[0] = ZeTimeConstants.CMD_PREAMBLE;
+        weather[1] = ZeTimeConstants.CMD_PUSH_WEATHER_DATA;
+        weather[2] = ZeTimeConstants.CMD_SEND;
+        weather[3] = (byte) ((weatherSpec.location.getBytes(StandardCharsets.UTF_8).length + 20) & 0xff);
+        weather[4] = (byte) ((weatherSpec.location.getBytes(StandardCharsets.UTF_8).length + 20) >> 8);
+        weather[5] = 0; // celsius
+        weather[6] = (byte) (weatherSpec.currentTemp - 273);
+        weather[7] = (byte) (weatherSpec.todayMinTemp - 273);
+        weather[8] = (byte) (weatherSpec.todayMaxTemp - 273);
+
+        boolean newWeather = false;
+        if (versionCmd.fwVersion.length() >= 24) {
+            String release = versionCmd.fwVersion.substring(8, 12);
+            String buildnumber = versionCmd.fwVersion.substring(versionCmd.fwVersion.length() - 4);
+            // if using firmware 1.7 Build 41 and above use newer icons
+            if (release.compareTo("R1.7") > 0 || (release.compareTo("R1.7") == 0 && buildnumber.compareTo("B4.1") >= 0)) {
+                newWeather = true;
+            }
+        } else {
+            LOG.warn("We do not have a sane fw version string available, firmware too old/new?");
+        }
+        if (newWeather) {
+            weather[9] = Weather.mapToZeTimeCondition(weatherSpec.currentConditionCode);
+        } else {
+            weather[9] = Weather.mapToZeTimeConditionOld(weatherSpec.currentConditionCode);
+        }
+        for (int forecast = 0; forecast < 3; forecast++) {
+            weather[10 + (forecast * 5)] = 0; // celsius
+            weather[11 + (forecast * 5)] = (byte) 0xff;
+            weather[12 + (forecast * 5)] = (byte) (weatherSpec.forecasts.get(forecast).minTemp - 273);
+            weather[13 + (forecast * 5)] = (byte) (weatherSpec.forecasts.get(forecast).maxTemp - 273);
+            if (newWeather) {
+                weather[14 + (forecast * 5)] = Weather.mapToZeTimeCondition(weatherSpec.forecasts.get(forecast).conditionCode);
+            } else {
+                weather[14 + (forecast * 5)] = Weather.mapToZeTimeConditionOld(weatherSpec.forecasts.get(forecast).conditionCode);
+            }
+        }
+        System.arraycopy(weatherSpec.location.getBytes(StandardCharsets.UTF_8), 0, weather, 25, weatherSpec.location.getBytes(StandardCharsets.UTF_8).length);
+        weather[weather.length - 1] = ZeTimeConstants.CMD_END;
+        try {
+            TransactionBuilder builder = performInitialized("sendWeahter");
+            sendMsgToWatch(builder, weather);
+            builder.queue(getQueue());
+        } catch (IOException e) {
+            GB.toast(getContext(), "Error sending weather: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
+        }
+    }
+
+    @Override
+    public void onNotification(NotificationSpec notificationSpec) {
+
+        int subject_length = 0;
+        int body_length = notificationSpec.body.getBytes(StandardCharsets.UTF_8).length;
+        if (body_length > 255) {
+            body_length = 255;
+        }
+        int notification_length = body_length;
+        byte[] subject = null;
+        byte[] notification;
+        //noinspection ExtractMethodRecommender
+        Calendar time = GregorianCalendar.getInstance();
+        // convert every single digit of the date to ascii characters
+        // we do it like so: use the base chrachter of '0' and add the digit
+        byte[] datetimeBytes = new byte[]{
+                (byte) ((time.get(Calendar.YEAR) / 1000) + '0'),
+                (byte) (((time.get(Calendar.YEAR) / 100) % 10) + '0'),
+                (byte) (((time.get(Calendar.YEAR) / 10) % 10) + '0'),
+                (byte) ((time.get(Calendar.YEAR) % 10) + '0'),
+                (byte) (((time.get(Calendar.MONTH) + 1) / 10) + '0'),
+                (byte) (((time.get(Calendar.MONTH) + 1) % 10) + '0'),
+                (byte) ((time.get(Calendar.DAY_OF_MONTH) / 10) + '0'),
+                (byte) ((time.get(Calendar.DAY_OF_MONTH) % 10) + '0'),
+                (byte) 'T',
+                (byte) ((time.get(Calendar.HOUR_OF_DAY) / 10) + '0'),
+                (byte) ((time.get(Calendar.HOUR_OF_DAY) % 10) + '0'),
+                (byte) ((time.get(Calendar.MINUTE) / 10) + '0'),
+                (byte) ((time.get(Calendar.MINUTE) % 10) + '0'),
+                (byte) ((time.get(Calendar.SECOND) / 10) + '0'),
+                (byte) ((time.get(Calendar.SECOND) % 10) + '0'),
+        };
+
+        if (notificationSpec.sender != null) {
+            notification_length += notificationSpec.sender.getBytes(StandardCharsets.UTF_8).length;
+            subject_length = notificationSpec.sender.getBytes(StandardCharsets.UTF_8).length;
+            subject = new byte[subject_length];
+            System.arraycopy(notificationSpec.sender.getBytes(StandardCharsets.UTF_8), 0, subject, 0, subject_length);
+        } else if (notificationSpec.phoneNumber != null) {
+            notification_length += notificationSpec.phoneNumber.getBytes(StandardCharsets.UTF_8).length;
+            subject_length = notificationSpec.phoneNumber.getBytes(StandardCharsets.UTF_8).length;
+            subject = new byte[subject_length];
+            System.arraycopy(notificationSpec.phoneNumber.getBytes(StandardCharsets.UTF_8), 0, subject, 0, subject_length);
+        } else if (notificationSpec.subject != null) {
+            notification_length += notificationSpec.subject.getBytes(StandardCharsets.UTF_8).length;
+            subject_length = notificationSpec.subject.getBytes(StandardCharsets.UTF_8).length;
+            subject = new byte[subject_length];
+            System.arraycopy(notificationSpec.subject.getBytes(StandardCharsets.UTF_8), 0, subject, 0, subject_length);
+        } else if (notificationSpec.title != null) {
+            notification_length += notificationSpec.title.getBytes(StandardCharsets.UTF_8).length;
+            subject_length = notificationSpec.title.getBytes(StandardCharsets.UTF_8).length;
+            subject = new byte[subject_length];
+            System.arraycopy(notificationSpec.title.getBytes(StandardCharsets.UTF_8), 0, subject, 0, subject_length);
+        }
+        notification_length += datetimeBytes.length + 10; // add message overhead
+        notification = new byte[notification_length];
+        notification[0] = ZeTimeConstants.CMD_PREAMBLE;
+        notification[1] = ZeTimeConstants.CMD_PUSH_EX_MSG;
+        notification[2] = ZeTimeConstants.CMD_SEND;
+        notification[3] = (byte) ((notification_length - 6) & 0xff);
+        notification[4] = (byte) ((notification_length - 6) >> 8);
+        notification[6] = 1;
+        notification[7] = (byte) subject_length;
+        notification[8] = (byte) body_length;
+        System.arraycopy(subject, 0, notification, 9, subject_length);
+        System.arraycopy(notificationSpec.body.getBytes(StandardCharsets.UTF_8), 0, notification, 9 + subject_length, body_length);
+        System.arraycopy(datetimeBytes, 0, notification, 9 + subject_length + body_length, datetimeBytes.length);
+        notification[notification_length - 1] = ZeTimeConstants.CMD_END;
+
+        switch (notificationSpec.type) {
+            case GENERIC_SMS:
+                notification[5] = ZeTimeConstants.NOTIFICATION_SMS;
+                break;
+            case GENERIC_PHONE:
+                notification[5] = ZeTimeConstants.NOTIFICATION_MISSED_CALL;
+                break;
+            case GMAIL:
+            case GOOGLE_INBOX:
+            case MAILBOX:
+            case OUTLOOK:
+            case YAHOO_MAIL:
+            case GENERIC_EMAIL:
+                notification[5] = ZeTimeConstants.NOTIFICATION_EMAIL;
+                break;
+            case WECHAT:
+                notification[5] = ZeTimeConstants.NOTIFICATION_WECHAT;
+                break;
+            case VIBER:
+                notification[5] = ZeTimeConstants.NOTIFICATION_VIBER;
+                break;
+            case WHATSAPP:
+                notification[5] = ZeTimeConstants.NOTIFICATION_WHATSAPP;
+                break;
+            case FACEBOOK:
+            case FACEBOOK_MESSENGER:
+                notification[5] = ZeTimeConstants.NOTIFICATION_FACEBOOK;
+                break;
+            case GOOGLE_HANGOUTS:
+                notification[5] = ZeTimeConstants.NOTIFICATION_HANGOUTS;
+                break;
+            case LINE:
+                notification[5] = ZeTimeConstants.NOTIFICATION_LINE;
+                break;
+            case SKYPE:
+                notification[5] = ZeTimeConstants.NOTIFICATION_SKYPE;
+                break;
+            case CONVERSATIONS:
+            case RIOT:
+            case SIGNAL:
+            case WIRE:
+            case TELEGRAM:
+            case THREEMA:
+            case KONTALK:
+            case ANTOX:
+            case GOOGLE_MESSENGER:
+            case HIPCHAT:
+            case KIK:
+            case KAKAO_TALK:
+            case SLACK:
+                notification[5] = ZeTimeConstants.NOTIFICATION_MESSENGER;
+                break;
+            case SNAPCHAT:
+                notification[5] = ZeTimeConstants.NOTIFICATION_SNAPCHAT;
+                break;
+            case INSTAGRAM:
+                notification[5] = ZeTimeConstants.NOTIFICATION_INSTAGRAM;
+                break;
+            case TWITTER:
+                notification[5] = ZeTimeConstants.NOTIFICATION_TWITTER;
+                break;
+            case LINKEDIN:
+                notification[5] = ZeTimeConstants.NOTIFICATION_LINKEDIN;
+                break;
+            case GENERIC_CALENDAR:
+                notification[5] = ZeTimeConstants.NOTIFICATION_CALENDAR;
+                break;
+            default:
+                notification[5] = ZeTimeConstants.NOTIFICATION_SOCIAL;
+                break;
+        }
+        try {
+            TransactionBuilder builder = performInitialized("sendNotification");
+            sendMsgToWatch(builder, notification);
+            builder.queue(getQueue());
+        } catch (IOException e) {
+            GB.toast(getContext(), "Error sending notification: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
+        }
+    }
+
+    @Override
+    public boolean onCharacteristicChanged(BluetoothGatt gatt,
+                                           BluetoothGattCharacteristic characteristic) {
+        super.onCharacteristicChanged(gatt, characteristic);
+
+        UUID characteristicUUID = characteristic.getUuid();
+        if (ZeTimeConstants.UUID_ACK_CHARACTERISTIC.equals(characteristicUUID)) {
+            byte[] data = receiveCompleteMsg(characteristic.getValue());
+            if (isMsgFormatOK(data)) {
+                switch (data[1]) {
+                    case ZeTimeConstants.CMD_WATCH_ID:
+                        break;
+                    case ZeTimeConstants.CMD_DEVICE_VERSION:
+                        handleDeviceInfo(data);
+                        break;
+                    case ZeTimeConstants.CMD_BATTERY_POWER:
+                        handleBatteryInfo(data);
+                        break;
+                    case ZeTimeConstants.CMD_AVAIABLE_DATA:
+                        handleActivityFetching(data);
+                        break;
+                    case ZeTimeConstants.CMD_GET_STEP_COUNT:
+                        handleStepsData(data);
+                        break;
+                    case ZeTimeConstants.CMD_GET_SLEEP_DATA:
+                        handleSleepData(data);
+                        break;
+                    case ZeTimeConstants.CMD_GET_HEARTRATE_EXDATA:
+                        handleHeartRateData(data);
+                        break;
+                    case ZeTimeConstants.CMD_MUSIC_CONTROL:
+                        handleMusicControl(data);
+                        break;
+                    case ZeTimeConstants.CMD_TIME_SURFACE_SETTINGS:
+                        getDateTimeFormat(data);
+                        break;
+                    case ZeTimeConstants.CMD_SHOCK_MODE:
+                        getSignaling(data);
+                        break;
+                    case ZeTimeConstants.CMD_DO_NOT_DISTURB:
+                        getDoNotDisturb(data);
+                        break;
+                    case ZeTimeConstants.CMD_ANALOG_MODE:
+                        getAnalogMode(data);
+                        break;
+                    case ZeTimeConstants.CMD_CONTROL_DEVICE:
+                        getActivityTracking(data);
+                        break;
+                    case ZeTimeConstants.CMD_DISPLAY_TIMEOUT:
+                        getScreenTime(data);
+                        break;
+                    case ZeTimeConstants.CMD_USAGE_HABITS:
+                        getWrist(data);
+                        break;
+                    case ZeTimeConstants.CMD_AUTO_HEARTRATE:
+                        getHeartRateMeasurement(data);
+                        break;
+                    case ZeTimeConstants.CMD_HEARTRATE_ALARM_LIMITS:
+                        getHeartRateLimits(data);
+                        break;
+                    case ZeTimeConstants.CMD_INACTIVITY_ALERT:
+                        getInactivityAlert(data);
+                        break;
+                    case ZeTimeConstants.CMD_CALORIES_TYPE:
+                        getCaloriesType(data);
+                        break;
+                    case ZeTimeConstants.CMD_SWITCH_SETTINGS:
+                        getDisplayOnMovement(data);
+                        break;
+                    case ZeTimeConstants.CMD_REMINDERS:
+                        storeActualReminders(data);
+                        break;
+                }
+            }
+            return true;
+        } else if (ZeTimeConstants.UUID_NOTIFY_CHARACTERISTIC.equals(characteristicUUID)) {
+            byte[] data = receiveCompleteMsg(characteristic.getValue());
+            if (isMsgFormatOK(data)) {
+                switch (data[1]) {
+                    case ZeTimeConstants.CMD_MUSIC_CONTROL:
+                        handleMusicControl(data);
+                        break;
+                    case ZeTimeConstants.CMD_CALL_CONTROL:
+                        handleCallControl(data);
+                        break;
+                }
+                return true;
+            }
+        } else {
+            LOG.warn("Unhandled characteristic changed: {}", characteristicUUID);
+            logMessageContent(characteristic.getValue());
+        }
+        return false;
+    }
+
+    private boolean isMsgFormatOK(byte[] msg) {
+        if (msg != null) {
+            if (msg[0] == ZeTimeConstants.CMD_PREAMBLE) {
+                if ((msg[3] != 0) || (msg[4] != 0)) {
+                    int payloadSize = (msg[4] << 8) & 0xff00 | (msg[3] & 0xff);
+                    int msgLength = payloadSize + 6;
+                    if (msgLength == msg.length) {
+                        if (msg[msgLength - 1] == ZeTimeConstants.CMD_END) {
+                            return true;
+                        }
+                    }
+                }
+            }
+        }
+        return false;
+    }
+
+    private byte[] receiveCompleteMsg(byte[] msg) {
+        if (msgPart == 0) {
+            int payloadSize = (msg[4] << 8) & 0xff00 | (msg[3] & 0xff);
+            if (payloadSize > 14) {
+                lastMsg = new byte[msg.length];
+                System.arraycopy(msg, 0, lastMsg, 0, msg.length);
+                msgPart++;
+                return null;
+            } else {
+                return msg;
+            }
+        } else {
+            byte[] completeMsg = new byte[lastMsg.length + msg.length];
+            System.arraycopy(lastMsg, 0, completeMsg, 0, lastMsg.length);
+            System.arraycopy(msg, 0, completeMsg, lastMsg.length, msg.length);
+            msgPart = 0;
+            return completeMsg;
+        }
+    }
+
+    private void requestBatteryInfo(TransactionBuilder builder) {
+        LOG.debug("Requesting Battery Info!");
+        builder.write(writeCharacteristic, new byte[]{ZeTimeConstants.CMD_PREAMBLE,
+                ZeTimeConstants.CMD_BATTERY_POWER,
+                ZeTimeConstants.CMD_REQUEST,
+                0x01,
+                0x00,
+                0x00,
+                ZeTimeConstants.CMD_END});
+        builder.write(ackCharacteristic, new byte[]{ZeTimeConstants.CMD_ACK_WRITE});
+    }
+
+    private void requestDeviceInfo(TransactionBuilder builder) {
+        LOG.debug("Requesting Device Info!");
+        builder.write(writeCharacteristic, new byte[]{ZeTimeConstants.CMD_PREAMBLE,
+                ZeTimeConstants.CMD_WATCH_ID,
+                ZeTimeConstants.CMD_REQUEST,
+                0x01,
+                0x00,
+                0x00,
+                ZeTimeConstants.CMD_END});
+        builder.write(ackCharacteristic, new byte[]{ZeTimeConstants.CMD_ACK_WRITE});
+
+        builder.write(writeCharacteristic, new byte[]{ZeTimeConstants.CMD_PREAMBLE,
+                ZeTimeConstants.CMD_DEVICE_VERSION,
+                ZeTimeConstants.CMD_REQUEST,
+                0x01,
+                0x00,
+                0x05,
+                ZeTimeConstants.CMD_END});
+        builder.write(ackCharacteristic, new byte[]{ZeTimeConstants.CMD_ACK_WRITE});
+
+        builder.write(writeCharacteristic, new byte[]{ZeTimeConstants.CMD_PREAMBLE,
+                ZeTimeConstants.CMD_DEVICE_VERSION,
+                ZeTimeConstants.CMD_REQUEST,
+                0x01,
+                0x00,
+                0x02,
+                ZeTimeConstants.CMD_END});
+        builder.write(ackCharacteristic, new byte[]{ZeTimeConstants.CMD_ACK_WRITE});
+    }
+
+    private void requestActivityInfo(TransactionBuilder builder) {
+        builder.write(writeCharacteristic, new byte[]{ZeTimeConstants.CMD_PREAMBLE,
+                ZeTimeConstants.CMD_AVAIABLE_DATA,
+                ZeTimeConstants.CMD_REQUEST,
+                0x01,
+                0x00,
+                0x00,
+                ZeTimeConstants.CMD_END});
+        builder.write(ackCharacteristic, new byte[]{ZeTimeConstants.CMD_ACK_WRITE});
+    }
+
+    private ZeTimeDeviceSupport requestShockStrength(TransactionBuilder builder) {
+        builder.write(writeCharacteristic, new byte[]{ZeTimeConstants.CMD_PREAMBLE,
+                ZeTimeConstants.CMD_SHOCK_STRENGTH,
+                ZeTimeConstants.CMD_REQUEST,
+                0x01,
+                0x00,
+                0x00,
+                ZeTimeConstants.CMD_END});
+        builder.write(ackCharacteristic, new byte[]{ZeTimeConstants.CMD_ACK_WRITE});
+        return this;
+    }
+
+    private void handleBatteryInfo(byte[] value) {
+        batteryCmd.level = ((short) value[5]);
+        if (batteryCmd.level <= 25) {
+            batteryCmd.state = BatteryState.BATTERY_LOW;
+        } else {
+            batteryCmd.state = BatteryState.BATTERY_NORMAL;
+        }
+        evaluateGBDeviceEvent(batteryCmd);
+    }
+
+    private void handleDeviceInfo(byte[] value) {
+        value[value.length - 1] = 0; // convert the end to a String end
+        byte[] string = Arrays.copyOfRange(value, 6, value.length - 1);
+        if (value[5] == 5) {
+            versionCmd.fwVersion = new String(string);
+        } else {
+            versionCmd.hwVersion = new String(string);
+        }
+        evaluateGBDeviceEvent(versionCmd);
+
+        TransactionBuilder builder = new TransactionBuilder("setDeviceInitialized");
+        builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZED, getContext()));
+        try {
+            performConnected(builder.getTransaction());
+        } catch (IOException e) {
+            LOG.error("could not set device to initzialized: ", e);
+        }
+
+        LOG.info("Initialization Done");
+    }
+
+    private void handleActivityFetching(byte[] msg) {
+        availableStepsData = (msg[5] & 0xff) | (msg[6] << 8) & 0xff00;
+        availableSleepData = (msg[7] & 0xff) | (msg[8] << 8) & 0xff00;
+        availableHeartRateData = (msg[9] & 0xff) | (msg[10] << 8) & 0xff00;
+        if (availableStepsData > 0) {
+            getStepData();
+        } else if (availableHeartRateData > 0) {
+            getHeartRateData();
+        } else if (availableSleepData > 0) {
+            getSleepData();
+        } else {
+            GB.signalActivityDataFinish(getDevice());
+        }
+    }
+
+    private void getStepData() {
+        try {
+            TransactionBuilder builder = performInitialized("fetchStepData");
+            builder.write(writeCharacteristic, new byte[]{ZeTimeConstants.CMD_PREAMBLE,
+                    ZeTimeConstants.CMD_GET_STEP_COUNT,
+                    ZeTimeConstants.CMD_REQUEST,
+                    0x02,
+                    0x00,
+                    0x00,
+                    0x00,
+                    ZeTimeConstants.CMD_END});
+            builder.write(ackCharacteristic, new byte[]{ZeTimeConstants.CMD_ACK_WRITE});
+            builder.queue(getQueue());
+        } catch (IOException e) {
+            GB.toast(getContext(), "Error fetching activity data: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
+        }
+    }
+
+    private void deleteStepData() {
+        try {
+            TransactionBuilder builder = performInitialized("deleteStepData");
+            sendMsgToWatch(builder, new byte[]{ZeTimeConstants.CMD_PREAMBLE,
+                    ZeTimeConstants.CMD_DELETE_STEP_COUNT,
+                    ZeTimeConstants.CMD_SEND,
+                    0x01,
+                    0x00,
+                    0x00,
+                    ZeTimeConstants.CMD_END});
+            builder.queue(getQueue());
+        } catch (IOException e) {
+            GB.toast(getContext(), "Error deleting activity data: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
+        }
+    }
+
+    private void getHeartRateData() {
+        try {
+            TransactionBuilder builder = performInitialized("fetchHeartRateData");
+            builder.write(writeCharacteristic, new byte[]{ZeTimeConstants.CMD_PREAMBLE,
+                    ZeTimeConstants.CMD_GET_HEARTRATE_EXDATA,
+                    ZeTimeConstants.CMD_REQUEST,
+                    0x01,
+                    0x00,
+                    0x00,
+                    ZeTimeConstants.CMD_END});
+            builder.write(ackCharacteristic, new byte[]{ZeTimeConstants.CMD_ACK_WRITE});
+            builder.queue(getQueue());
+        } catch (IOException e) {
+            GB.toast(getContext(), "Error fetching heart rate data: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
+        }
+    }
+
+    private void deleteHeartRateData() {
+        try {
+            TransactionBuilder builder = performInitialized("deleteHeartRateData");
+            sendMsgToWatch(builder, new byte[]{ZeTimeConstants.CMD_PREAMBLE,
+                    ZeTimeConstants.CMD_DELETE_HEARTRATE_DATA,
+                    ZeTimeConstants.CMD_SEND,
+                    0x01,
+                    0x00,
+                    0x00,
+                    ZeTimeConstants.CMD_END});
+            builder.queue(getQueue());
+        } catch (IOException e) {
+            GB.toast(getContext(), "Error deleting heart rate data: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
+        }
+    }
+
+    private void getSleepData() {
+        try {
+            TransactionBuilder builder = performInitialized("fetchSleepData");
+            builder.write(writeCharacteristic, new byte[]{ZeTimeConstants.CMD_PREAMBLE,
+                    ZeTimeConstants.CMD_GET_SLEEP_DATA,
+                    ZeTimeConstants.CMD_REQUEST,
+                    0x02,
+                    0x00,
+                    0x00,
+                    0x00,
+                    ZeTimeConstants.CMD_END});
+            builder.write(ackCharacteristic, new byte[]{ZeTimeConstants.CMD_ACK_WRITE});
+            builder.queue(getQueue());
+        } catch (IOException e) {
+            GB.toast(getContext(), "Error fetching sleep data: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
+        }
+    }
+
+    private void deleteSleepData() {
+        try {
+            TransactionBuilder builder = performInitialized("deleteSleepData");
+            sendMsgToWatch(builder, new byte[]{ZeTimeConstants.CMD_PREAMBLE,
+                    ZeTimeConstants.CMD_DELETE_SLEEP_DATA,
+                    ZeTimeConstants.CMD_SEND,
+                    0x01,
+                    0x00,
+                    0x00,
+                    ZeTimeConstants.CMD_END});
+            builder.queue(getQueue());
+        } catch (IOException e) {
+            GB.toast(getContext(), "Error deleting sleep data: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
+        }
+    }
+
+    private void handleStepsData(byte[] msg) {
+        ZeTimeActivitySample sample = new ZeTimeActivitySample();
+        Calendar now = GregorianCalendar.getInstance();
+        int timestamp = (msg[10] << 24) & 0xff000000 | (msg[9] << 16) & 0xff0000 | (msg[8] << 8) & 0xff00 | (msg[7] & 0xff);
+        timestamp += eightHourOffset; // the timestamp from the watch has an offset of eight hours, do not know why...
+        timestamp -= ((now.get(Calendar.ZONE_OFFSET) / 1000) + (now.get(Calendar.DST_OFFSET) / 1000));  // TimeZone hour + daylight saving
+        sample.setTimestamp(timestamp);
+        sample.setSteps((msg[14] << 24) & 0xff000000 | (msg[13] << 16) & 0xff0000 | (msg[12] << 8) & 0xff00 | (msg[11] & 0xff));
+        sample.setCaloriesBurnt((msg[18] << 24) & 0xff000000 | (msg[17] << 16) & 0xff0000 | (msg[16] << 8) & 0xff00 | (msg[15] & 0xff));
+        sample.setDistanceMeters((msg[22] << 24) & 0xff000000 | (msg[21] << 16) & 0xff0000 | (msg[20] << 8) & 0xff00 | (msg[19] & 0xff));
+        sample.setActiveTimeMinutes((msg[26] << 24) & 0xff000000 | (msg[25] << 16) & 0xff0000 | (msg[24] << 8) & 0xff00 | (msg[23] & 0xff));
+        sample.setRawKind(ActivityKind.ACTIVITY.getCode());
+        sample.setRawIntensity(sample.getSteps());
+
+        try (DBHandler dbHandler = GBApplication.acquireDB()) {
+            sample.setUserId(DBHelper.getUser(dbHandler.getDaoSession()).getId());
+            sample.setDeviceId(DBHelper.getDevice(getDevice(), dbHandler.getDaoSession()).getId());
+            ZeTimeSampleProvider provider = new ZeTimeSampleProvider(getDevice(), dbHandler.getDaoSession());
+            provider.addGBActivitySample(sample);
+        } catch (Exception ex) {
+            GB.toast(getContext(), "Error saving steps data: " + ex.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
+            GB.updateTransferNotification(null, "Data transfer failed", false, 0, getContext());
+        }
+
+        progressSteps = (msg[5] & 0xff) | ((msg[6] << 8) & 0xff00);
+        GB.updateTransferNotification(null, getContext().getString(R.string.busy_task_fetch_activity_data), true, progressSteps * 100 / availableStepsData, getContext());
+        if (progressSteps == availableStepsData) {
+            Prefs prefs = getDevicePrefs();
+            progressSteps = 0;
+            availableStepsData = 0;
+            GB.updateTransferNotification(null, "", false, 100, getContext());
+            if (getDevice().isBusy()) {
+                getDevice().unsetBusyTask();
+                getDevice().sendDeviceUpdateIntent(getContext());
+            }
+            if (!prefs.getBoolean(ZeTimeConstants.PREF_ZETIME_DONT_DEL_ACTDATA, false)) {
+                deleteStepData();
+            }
+            if (availableHeartRateData > 0) {
+                getHeartRateData();
+            } else if (availableSleepData > 0) {
+                getSleepData();
+            } else {
+                GB.signalActivityDataFinish(getDevice());
+            }
+        }
+    }
+
+    private void handleSleepData(byte[] msg) {
+        ZeTimeActivitySample sample = new ZeTimeActivitySample();
+        Calendar now = GregorianCalendar.getInstance();
+        int timestamp = (msg[10] << 24) & 0xff000000 | (msg[9] << 16) & 0xff0000 | (msg[8] << 8) & 0xff00 | (msg[7] & 0xff);
+        timestamp += eightHourOffset; // the timestamp from the watch has an offset of eight hours, do not know why...
+        timestamp -= ((now.get(Calendar.ZONE_OFFSET) / 1000) + (now.get(Calendar.DST_OFFSET) / 1000));  // TimeZone hour + daylight saving
+        sample.setTimestamp(timestamp);
+        if (msg[11] == 0) {
+            sample.setRawKind(ActivityKind.DEEP_SLEEP.getCode());
+        } else if (msg[11] == 1) {
+            sample.setRawKind(ActivityKind.LIGHT_SLEEP.getCode());
+        } else {
+            sample.setRawKind(ActivityKind.UNKNOWN.getCode());
+        }
+
+        try (DBHandler dbHandler = GBApplication.acquireDB()) {
+            sample.setUserId(DBHelper.getUser(dbHandler.getDaoSession()).getId());
+            sample.setDeviceId(DBHelper.getDevice(getDevice(), dbHandler.getDaoSession()).getId());
+            ZeTimeSampleProvider provider = new ZeTimeSampleProvider(getDevice(), dbHandler.getDaoSession());
+            provider.addGBActivitySample(sample);
+        } catch (Exception ex) {
+            GB.toast(getContext(), "Error saving steps data: " + ex.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
+            GB.updateTransferNotification(null, "Data transfer failed", false, 0, getContext());
+        }
+
+        progressSleep = (msg[5] & 0xff) | (msg[6] << 8) & 0xff00;
+        GB.updateTransferNotification(null, getContext().getString(R.string.busy_task_fetch_activity_data), true, progressSleep * 100 / availableSleepData, getContext());
+        if (progressSleep == availableSleepData) {
+            Prefs prefs = getDevicePrefs();
+            progressSleep = 0;
+            availableSleepData = 0;
+            GB.updateTransferNotification(null, "", false, 100, getContext());
+            if (getDevice().isBusy()) {
+                getDevice().unsetBusyTask();
+                GB.signalActivityDataFinish(getDevice());
+            }
+            if (!prefs.getBoolean(ZeTimeConstants.PREF_ZETIME_DONT_DEL_ACTDATA, false)) {
+                deleteSleepData();
+            }
+        }
+    }
+
+    private void handleHeartRateData(byte[] msg) {
+        ZeTimeActivitySample sample = new ZeTimeActivitySample();
+        Calendar now = GregorianCalendar.getInstance();
+        int timestamp = (msg[10] << 24) & 0xff000000 | (msg[9] << 16) & 0xff0000 | (msg[8] << 8) & 0xff00 | (msg[7] & 0xff);
+        timestamp += eightHourOffset; // the timestamp from the watch has an offset of eight hours, do not know why...
+        timestamp -= ((now.get(Calendar.ZONE_OFFSET) / 1000) + (now.get(Calendar.DST_OFFSET) / 1000));  // TimeZone hour + daylight saving
+        sample.setHeartRate(msg[11]);
+        sample.setTimestamp(timestamp);
+
+        try (DBHandler dbHandler = GBApplication.acquireDB()) {
+            sample.setUserId(DBHelper.getUser(dbHandler.getDaoSession()).getId());
+            sample.setDeviceId(DBHelper.getDevice(getDevice(), dbHandler.getDaoSession()).getId());
+            ZeTimeSampleProvider provider = new ZeTimeSampleProvider(getDevice(), dbHandler.getDaoSession());
+            provider.addGBActivitySample(sample);
+        } catch (Exception ex) {
+            GB.toast(getContext(), "Error saving steps data: " + ex.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
+            GB.updateTransferNotification(null, "Data transfer failed", false, 0, getContext());
+        }
+
+        progressHeartRate = (msg[5] & 0xff) | ((msg[6] << 8) & 0xff00);
+        GB.updateTransferNotification(null, getContext().getString(R.string.busy_task_fetch_activity_data), true, progressHeartRate * 100 / availableHeartRateData, getContext());
+
+        if (((msg[4] << 8) & 0xff00 | (msg[3] & 0xff)) == 0xe) // if the message is longer than 0x7, than it has two measurements (payload = 0xe)
+        {
+            timestamp = (msg[17] << 24) & 0xff000000 | (msg[16] << 16) & 0xff0000 | (msg[15] << 8) & 0xff00 | (msg[14] & 0xff);
+            timestamp += eightHourOffset; // the timestamp from the watch has an offset of eight hours, do not know why...
+            timestamp -= ((now.get(Calendar.ZONE_OFFSET) / 1000) + (now.get(Calendar.DST_OFFSET) / 1000));  // TimeZone hour + daylight saving
+            sample.setHeartRate(msg[18]);
+            sample.setTimestamp(timestamp);
+
+            try (DBHandler dbHandler = GBApplication.acquireDB()) {
+                sample.setUserId(DBHelper.getUser(dbHandler.getDaoSession()).getId());
+                sample.setDeviceId(DBHelper.getDevice(getDevice(), dbHandler.getDaoSession()).getId());
+                ZeTimeSampleProvider provider = new ZeTimeSampleProvider(getDevice(), dbHandler.getDaoSession());
+                provider.addGBActivitySample(sample);
+            } catch (Exception ex) {
+                GB.toast(getContext(), "Error saving steps data: " + ex.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
+                GB.updateTransferNotification(null, "Data transfer failed", false, 0, getContext());
+            }
+
+            progressHeartRate = (msg[12] & 0xff) | ((msg[13] << 8) & 0xff00);
+            GB.updateTransferNotification(null, getContext().getString(R.string.busy_task_fetch_activity_data), true, (int) (progressHeartRate * 100 / availableHeartRateData), getContext());
+        }
+
+        if (progressHeartRate == availableHeartRateData) {
+            Prefs prefs = getDevicePrefs();
+            progressHeartRate = 0;
+            availableHeartRateData = 0;
+            GB.updateTransferNotification(null, "", false, 100, getContext());
+            if (getDevice().isBusy()) {
+                getDevice().unsetBusyTask();
+                getDevice().sendDeviceUpdateIntent(getContext());
+            }
+            if (!prefs.getBoolean(ZeTimeConstants.PREF_ZETIME_DONT_DEL_ACTDATA, false)) {
+                deleteHeartRateData();
+            }
+            if (availableSleepData > 0) {
+                getSleepData();
+            } else {
+                GB.signalActivityDataFinish(getDevice());
+            }
+        }
+    }
+
+    private void sendMsgToWatch(TransactionBuilder builder, byte[] msg) {
+        if (msg.length > maxMsgLength) {
+            int msgpartlength = 0;
+            byte[] msgpart = null;
+
+            do {
+                if ((msg.length - msgpartlength) < maxMsgLength) {
+                    msgpart = new byte[msg.length - msgpartlength];
+                    System.arraycopy(msg, msgpartlength, msgpart, 0, msg.length - msgpartlength);
+                    msgpartlength += (msg.length - msgpartlength);
+                } else {
+                    msgpart = new byte[maxMsgLength];
+                    System.arraycopy(msg, msgpartlength, msgpart, 0, maxMsgLength);
+                    msgpartlength += maxMsgLength;
+                }
+                builder.write(writeCharacteristic, msgpart);
+            } while (msgpartlength < msg.length);
+        } else {
+            builder.write(writeCharacteristic, msg);
+        }
+        builder.write(ackCharacteristic, new byte[]{ZeTimeConstants.CMD_ACK_WRITE});
+    }
+
+    private void handleMusicControl(byte[] musicControlMsg) {
+        if (musicControlMsg[2] == ZeTimeConstants.CMD_SEND) {
+            switch (musicControlMsg[5]) {
+                case 0: // play current song
+                    musicCmd.event = GBDeviceEventMusicControl.Event.PLAY;
+                    break;
+                case 1: // pause current song
+                    musicCmd.event = GBDeviceEventMusicControl.Event.PAUSE;
+                    break;
+                case 2: // skip to previous song
+                    musicCmd.event = GBDeviceEventMusicControl.Event.PREVIOUS;
+                    break;
+                case 3: // skip to next song
+                    musicCmd.event = GBDeviceEventMusicControl.Event.NEXT;
+                    break;
+                case 4: // change volume
+                    if (musicControlMsg[6] > volume) {
+                        musicCmd.event = GBDeviceEventMusicControl.Event.VOLUMEUP;
+                        if (volume < 90) {
+                            volume += (byte) 10;
+                        }
+                    } else {
+                        musicCmd.event = GBDeviceEventMusicControl.Event.VOLUMEDOWN;
+                        if (volume > 10) {
+                            volume -= (byte) 10;
+                        }
+                    }
+                    try {
+                        TransactionBuilder builder = performInitialized("replyMusicVolume");
+                        replyMsgToWatch(builder, new byte[]{ZeTimeConstants.CMD_PREAMBLE,
+                                ZeTimeConstants.CMD_MUSIC_CONTROL,
+                                ZeTimeConstants.CMD_REQUEST_RESPOND,
+                                0x02,
+                                0x00,
+                                0x02,
+                                volume,
+                                ZeTimeConstants.CMD_END});
+                        builder.queue(getQueue());
+                    } catch (IOException e) {
+                        GB.toast(getContext(), "Error reply the music volume: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
+                    }
+                    break;
+            }
+            evaluateGBDeviceEvent(musicCmd);
+        } else {
+            if (music != null) {
+                music[2] = ZeTimeConstants.CMD_REQUEST_RESPOND;
+                try {
+                    TransactionBuilder builder = performInitialized("replyMusicState");
+                    replyMsgToWatch(builder, music);
+                    builder.queue(getQueue());
+                } catch (IOException e) {
+                    GB.toast(getContext(), "Error reply the music state: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
+                }
+            }
+        }
+    }
+
+    private void handleCallControl(byte[] callControlMsg) {
+        if (callControlMsg.length == 7
+                && callControlMsg[2] == ZeTimeConstants.CMD_SEND
+                && callControlMsg[3] == 0x01
+                && callControlMsg[4] == 0x00
+                && callControlMsg[5] == 0x01) {
+            callCmd.event = GBDeviceEventCallControl.Event.REJECT;
+            evaluateGBDeviceEvent(callCmd);
+        }
+    }
+
+    private void replyMsgToWatch(TransactionBuilder builder, byte[] msg) {
+        if (msg.length > maxMsgLength) {
+            int msgpartlength = 0;
+            byte[] msgpart;
+
+            do {
+                if ((msg.length - msgpartlength) < maxMsgLength) {
+                    msgpart = new byte[msg.length - msgpartlength];
+                    System.arraycopy(msg, msgpartlength, msgpart, 0, msg.length - msgpartlength);
+                    msgpartlength += (msg.length - msgpartlength);
+                } else {
+                    msgpart = new byte[maxMsgLength];
+                    System.arraycopy(msg, msgpartlength, msgpart, 0, maxMsgLength);
+                    msgpartlength += maxMsgLength;
+                }
+                builder.write(replyCharacteristic, msgpart);
+            } while (msgpartlength < msg.length);
+        } else {
+            builder.write(replyCharacteristic, msg);
+        }
+    }
+
+    private void synchronizeTime(TransactionBuilder builder) {
+        Calendar now = GregorianCalendar.getInstance();
+        byte[] timeSync = new byte[]{ZeTimeConstants.CMD_PREAMBLE,
+                ZeTimeConstants.CMD_DATE_TIME,
+                ZeTimeConstants.CMD_SEND,
+                0x0c,
+                0x00,
+                (byte) (now.get(Calendar.YEAR) & 0xff),
+                (byte) (now.get(Calendar.YEAR) >> 8),
+                (byte) (now.get(Calendar.MONTH) + 1),
+                (byte) now.get(Calendar.DAY_OF_MONTH),
+                (byte) now.get(Calendar.HOUR_OF_DAY),
+                (byte) now.get(Calendar.MINUTE),
+                (byte) now.get(Calendar.SECOND),
+                0x00, // is 24h
+                0x00, // SetTime after calibration
+                0x01, // Unit
+                (byte) ((now.get(Calendar.ZONE_OFFSET) / 3600000) + (now.get(Calendar.DST_OFFSET) / 3600000)), // TimeZone hour + daylight saving
+                0x00, // TimeZone minute
+                ZeTimeConstants.CMD_END};
+        sendMsgToWatch(builder, timeSync);
+    }
+
+    // function serving the settings
+    private void setWrist(TransactionBuilder builder) {
+        String value = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getString(DeviceSettingsPreferenceConst.PREF_WEARLOCATION, "left");
+
+        byte[] wrist = {ZeTimeConstants.CMD_PREAMBLE,
+                ZeTimeConstants.CMD_USAGE_HABITS,
+                ZeTimeConstants.CMD_SEND,
+                (byte) 0x1,
+                (byte) 0x0,
+                ZeTimeConstants.WEAR_ON_LEFT_WRIST,
+                ZeTimeConstants.CMD_END};
+        if ("right".equals(value)) {
+            wrist[5] = ZeTimeConstants.WEAR_ON_RIGHT_WRIST;
+        }
+
+        LOG.warn("Wrist: " + wrist[5]);
+        sendMsgToWatch(builder, wrist);
+    }
+
+    private void setScreenTime(TransactionBuilder builder) {
+        int value = getDevicePrefs().getInt(ZeTimeConstants.PREF_SCREENTIME, 30);
+        if (value > ZeTimeConstants.MAX_SCREEN_ON_TIME) {
+            GB.toast(getContext(), "Value for screen on time is greater than 18h! ", Toast.LENGTH_LONG, GB.ERROR);
+            value = ZeTimeConstants.MAX_SCREEN_ON_TIME;
+        } else if (value < ZeTimeConstants.MIN_SCREEN_ON_TIME) {
+            GB.toast(getContext(), "Value for screen on time is lesser than 10s! ", Toast.LENGTH_LONG, GB.ERROR);
+            value = ZeTimeConstants.MIN_SCREEN_ON_TIME;
+        }
+
+        byte[] screentime = {ZeTimeConstants.CMD_PREAMBLE,
+                ZeTimeConstants.CMD_DISPLAY_TIMEOUT,
+                ZeTimeConstants.CMD_SEND,
+                (byte) 0x2,
+                (byte) 0x0,
+                (byte) (value & 0xff),
+                (byte) (value >> 8),
+                ZeTimeConstants.CMD_END};
+
+        sendMsgToWatch(builder, screentime);
+    }
+
+    private void setUserInfo(TransactionBuilder builder) {
+        ActivityUser activityUser = new ActivityUser();
+        byte gender = (byte) activityUser.getGender();
+        int age = activityUser.getAge();
+        int height = activityUser.getHeightCm();
+        int weight = activityUser.getWeightKg() * 10; // weight is set and get in 100g granularity
+
+        if (gender == ActivityUser.GENDER_MALE) // translate gender for zetime
+        {
+            gender = 0;
+        } else if (gender == ActivityUser.GENDER_FEMALE) {
+            gender = 1;
+        } else {
+            gender = 2;
+        }
+
+        byte[] userinfo = {ZeTimeConstants.CMD_PREAMBLE,
+                ZeTimeConstants.CMD_USER_INFO,
+                ZeTimeConstants.CMD_SEND,
+                (byte) 0x5,
+                (byte) 0x0,
+                gender,
+                (byte) age,
+                (byte) height,
+                (byte) (weight & 0xff),
+                (byte) (weight >> 8),
+                ZeTimeConstants.CMD_END};
+        sendMsgToWatch(builder, userinfo);
+    }
+
+    private void setUserGoals(TransactionBuilder builder) {
+        ActivityUser activityUser = new ActivityUser();
+        int steps = activityUser.getStepsGoal() / 100; // ZeTime expect the steps in 100 increment
+        int calories = activityUser.getCaloriesBurntGoal();
+        int distance = activityUser.getDistanceGoalMeters() / 1000;  // ZeTime only accepts km goals
+        int sleep = activityUser.getSleepDurationGoal();
+        int activeTime = activityUser.getActiveTimeGoalMinutes();
+
+        // set steps goal
+        byte[] goal_steps = {ZeTimeConstants.CMD_PREAMBLE,
+                ZeTimeConstants.CMD_GOALS,
+                ZeTimeConstants.CMD_SEND,
+                (byte) 0x4,
+                (byte) 0x0,
+                (byte) 0x0,
+                (byte) (steps & 0xff),
+                (byte) (steps >> 8),
+                (byte) 0x1,
+                ZeTimeConstants.CMD_END};
+        sendMsgToWatch(builder, goal_steps);
+
+        byte[] goal_calories = new byte[goal_steps.length];
+        System.arraycopy(goal_steps, 0, goal_calories, 0, goal_steps.length);
+        // set calories goal
+        goal_calories[5] = (byte) 0x1;
+        goal_calories[6] = (byte) (calories & 0xff);
+        goal_calories[7] = (byte) (calories >> 8);
+        sendMsgToWatch(builder, goal_calories);
+
+        byte[] goal_distance = new byte[goal_steps.length];
+        System.arraycopy(goal_steps, 0, goal_distance, 0, goal_steps.length);
+        // set distance goal
+        goal_distance[5] = (byte) 0x2;
+        goal_distance[6] = (byte) (distance & 0xff);
+        goal_distance[7] = (byte) (distance >> 8);
+        sendMsgToWatch(builder, goal_distance);
+
+        byte[] goal_sleep = new byte[goal_steps.length];
+        System.arraycopy(goal_steps, 0, goal_sleep, 0, goal_steps.length);
+        // set sleep goal
+        goal_sleep[5] = (byte) 0x3;
+        goal_sleep[6] = (byte) (sleep & 0xff);
+        goal_sleep[7] = (byte) (sleep >> 8);
+        sendMsgToWatch(builder, goal_sleep);
+
+        byte[] goal_activeTime = new byte[goal_steps.length];
+        System.arraycopy(goal_steps, 0, goal_activeTime, 0, goal_steps.length);
+        // set active time goal
+        goal_activeTime[5] = (byte) 0x4;
+        goal_activeTime[6] = (byte) (activeTime & 0xff);
+        goal_activeTime[7] = (byte) (activeTime >> 8);
+        sendMsgToWatch(builder, goal_activeTime);
+    }
+
+    private void setHeartRateLimits(TransactionBuilder builder) {
+        Prefs prefs = getDevicePrefs();
+
+        boolean alarmEnabled = prefs.getBoolean(ZeTimeConstants.PREF_ZETIME_HEARTRATE_ALARM, false);
+        int maxHR = prefs.getInt(ZeTimeConstants.PREF_ZETIME_MAX_HEARTRATE, 180);
+        int minHR = prefs.getInt(ZeTimeConstants.PREF_ZETIME_MIN_HEARTRATE, 60);
+
+        byte[] heartrateAlarm = {ZeTimeConstants.CMD_PREAMBLE,
+                ZeTimeConstants.CMD_HEARTRATE_ALARM_LIMITS,
+                ZeTimeConstants.CMD_SEND,
+                (byte) 0x3,
+                (byte) 0x0,
+                (byte) (maxHR & 0xff),
+                (byte) (minHR & 0xff),
+                (byte) (alarmEnabled ? 1 : 0),  // activate alarm
+                ZeTimeConstants.CMD_END};
+        sendMsgToWatch(builder, heartrateAlarm);
+    }
+
+    private void initMusicVolume(TransactionBuilder builder) {
+        replyMsgToWatch(builder, new byte[]{ZeTimeConstants.CMD_PREAMBLE,
+                ZeTimeConstants.CMD_MUSIC_CONTROL,
+                ZeTimeConstants.CMD_REQUEST_RESPOND,
+                0x02,
+                0x00,
+                0x02,
+                volume,
+                ZeTimeConstants.CMD_END});
+    }
+
+    private void setAnalogMode(TransactionBuilder builder) {
+        Prefs prefs = getDevicePrefs();
+        int mode = prefs.getInt(ZeTimeConstants.PREF_ANALOG_MODE, 0);
+
+        byte[] analog = {ZeTimeConstants.CMD_PREAMBLE,
+                ZeTimeConstants.CMD_ANALOG_MODE,
+                ZeTimeConstants.CMD_SEND,
+                (byte) 0x1,
+                (byte) 0x0,
+                (byte) mode,
+                ZeTimeConstants.CMD_END};
+
+        sendMsgToWatch(builder, analog);
+    }
+
+    private void setActivityTracking(TransactionBuilder builder) {
+        Prefs prefs = getDevicePrefs();
+        boolean tracking = prefs.getBoolean(ZeTimeConstants.PREF_ACTIVITY_TRACKING, false);
+
+        byte[] activity = {ZeTimeConstants.CMD_PREAMBLE,
+                ZeTimeConstants.CMD_CONTROL_DEVICE,
+                ZeTimeConstants.CMD_SEND,
+                (byte) 0x1,
+                (byte) 0x0,
+                (byte) 0x9,
+                ZeTimeConstants.CMD_END};
+        if (tracking) {
+            activity[5] = (byte) 0xa;
+        }
+        sendMsgToWatch(builder, activity);
+    }
+
+    private void setDisplayOnMovement(TransactionBuilder builder) {
+        Prefs prefs = getDevicePrefs();
+        boolean movement = prefs.getBoolean(ZeTimeConstants.PREF_HANDMOVE_DISPLAY, false);
+
+        byte[] handmove = {ZeTimeConstants.CMD_PREAMBLE,
+                ZeTimeConstants.CMD_SWITCH_SETTINGS,
+                ZeTimeConstants.CMD_SEND,
+                (byte) 0x3,
+                (byte) 0x0,
+                (byte) 0x1,
+                (byte) 0xe,
+                (byte) 0x0,
+                ZeTimeConstants.CMD_END};
+        if (movement) {
+            handmove[7] = (byte) 0x1;
+        }
+        sendMsgToWatch(builder, handmove);
+    }
+
+    private void setDoNotDisturb(TransactionBuilder builder) {
+        Prefs prefs = getDevicePrefs();
+        String scheduled = prefs.getString(DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_NOAUTO, "off");
+        String dndScheduled = getContext().getString(R.string.p_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, "07:00");
+        DateFormat df_start = new SimpleDateFormat("HH:mm");
+        DateFormat df_end = new SimpleDateFormat("HH:mm");
+        Calendar calendar = GregorianCalendar.getInstance();
+        Calendar calendar_end = GregorianCalendar.getInstance();
+
+        try {
+            calendar.setTime(df_start.parse(start));
+            try {
+                calendar_end.setTime(df_end.parse(end));
+
+                byte[] doNotDisturb = {ZeTimeConstants.CMD_PREAMBLE,
+                        ZeTimeConstants.CMD_DO_NOT_DISTURB,
+                        ZeTimeConstants.CMD_SEND,
+                        (byte) 0x5,
+                        (byte) 0x0,
+                        (byte) 0x0,
+                        (byte) calendar.get(Calendar.HOUR_OF_DAY),
+                        (byte) calendar.get(Calendar.MINUTE),
+                        (byte) calendar_end.get(Calendar.HOUR_OF_DAY),
+                        (byte) calendar_end.get(Calendar.MINUTE),
+                        ZeTimeConstants.CMD_END};
+
+                if (scheduled.equals(dndScheduled)) {
+                    doNotDisturb[5] = (byte) 0x1;
+                }
+                sendMsgToWatch(builder, doNotDisturb);
+            } catch (Exception e) {
+                LOG.error("Unexpected exception in ZeTimeDeviceSupport.setDoNotDisturb: " + e.getMessage());
+            }
+        } catch (Exception e) {
+            LOG.error("Unexpected exception in ZeTimeDeviceSupport.setDoNotDisturb: " + e.getMessage());
+        }
+    }
+
+    private void setCaloriesType(TransactionBuilder builder) {
+        Prefs prefs = getDevicePrefs();
+        int type = prefs.getInt(ZeTimeConstants.PREF_CALORIES_TYPE, 0);
+
+        byte[] calories = {ZeTimeConstants.CMD_PREAMBLE,
+                ZeTimeConstants.CMD_CALORIES_TYPE,
+                ZeTimeConstants.CMD_SEND,
+                (byte) 0x1,
+                (byte) 0x0,
+                (byte) type,
+                ZeTimeConstants.CMD_END};
+
+        sendMsgToWatch(builder, calories);
+    }
+
+    private void setTimeFormate(TransactionBuilder builder) {
+        String timeFormat = getDevicePrefs().getTimeFormat();
+        int type = 1;
+        if ("am/pm".equals(timeFormat)) {
+            type = 2;
+        }
+
+        byte[] timeformat = {ZeTimeConstants.CMD_PREAMBLE,
+                ZeTimeConstants.CMD_TIME_SURFACE_SETTINGS,
+                ZeTimeConstants.CMD_SEND,
+                (byte) 0x8,
+                (byte) 0x0,
+                (byte) 0xff, // set to ff to not change anything on the watch
+                (byte) type,
+                (byte) 0xff, // set to ff to not change anything on the watch
+                (byte) 0xff, // set to ff to not change anything on the watch
+                (byte) 0xff, // set to ff to not change anything on the watch
+                (byte) 0xff, // set to ff to not change anything on the watch
+                (byte) 0xff, // set to ff to not change anything on the watch
+                (byte) 0xff, // set to ff to not change anything on the watch
+                ZeTimeConstants.CMD_END};
+
+        sendMsgToWatch(builder, timeformat);
+    }
+
+    private void setDateFormate(TransactionBuilder builder) {
+        Prefs prefs = getDevicePrefs();
+        int type = prefs.getInt(ZeTimeConstants.PREF_DATE_FORMAT, 0);
+
+        byte[] dateformat = {ZeTimeConstants.CMD_PREAMBLE,
+                ZeTimeConstants.CMD_TIME_SURFACE_SETTINGS,
+                ZeTimeConstants.CMD_SEND,
+                (byte) 0x8,
+                (byte) 0x0,
+                (byte) type,
+                (byte) 0xff, // set to ff to not change anything on the watch
+                (byte) 0xff, // set to ff to not change anything on the watch
+                (byte) 0xff, // set to ff to not change anything on the watch
+                (byte) 0xff, // set to ff to not change anything on the watch
+                (byte) 0xff, // set to ff to not change anything on the watch
+                (byte) 0xff, // set to ff to not change anything on the watch
+                (byte) 0xff, // set to ff to not change anything on the watch
+                ZeTimeConstants.CMD_END};
+
+        sendMsgToWatch(builder, dateformat);
+    }
+
+    private void setInactivityAlert(TransactionBuilder builder) {
+        Prefs prefs = getDevicePrefs();
+        boolean enabled = prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_INACTIVITY_ENABLE, false);
+        int threshold = prefs.getInt(DeviceSettingsPreferenceConst.PREF_INACTIVITY_THRESHOLD, 60);
+
+        if (threshold > 0xff) {
+            threshold = 0xff;
+            GB.toast(getContext(), "Value for inactivity threshold is greater than 255min! ", Toast.LENGTH_LONG, GB.ERROR);
+        }
+
+        byte[] inactivity = {
+                ZeTimeConstants.CMD_PREAMBLE,
+                ZeTimeConstants.CMD_INACTIVITY_ALERT,
+                ZeTimeConstants.CMD_SEND,
+                (byte) 0x8,
+                (byte) 0x0,
+                (byte) 0x0,
+                (byte) threshold,
+                (byte) 0x0,
+                (byte) 0x0,
+                (byte) 0x0,
+                (byte) 0x0,
+                (byte) 0x64,
+                (byte) 0x0,
+                ZeTimeConstants.CMD_END
+        };
+
+        if (enabled) {
+            String start = prefs.getString(DeviceSettingsPreferenceConst.PREF_INACTIVITY_START, "06:00");
+            String end = prefs.getString(DeviceSettingsPreferenceConst.PREF_INACTIVITY_END, "22:00");
+            DateFormat df_start = new SimpleDateFormat("HH:mm");
+            DateFormat df_end = new SimpleDateFormat("HH:mm");
+            Calendar calendar = GregorianCalendar.getInstance();
+            Calendar calendar_end = GregorianCalendar.getInstance();
+
+            int reps = (1 << 7); // set inactivity active: set bit 7
+            reps |= (prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_INACTIVITY_MO, false) ? 1 : 0);
+            reps |= ((prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_INACTIVITY_TU, false) ? 1 : 0) << 1);
+            reps |= ((prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_INACTIVITY_WE, false) ? 1 : 0) << 2);
+            reps |= ((prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_INACTIVITY_TH, false) ? 1 : 0) << 3);
+            reps |= ((prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_INACTIVITY_FR, false) ? 1 : 0) << 4);
+            reps |= ((prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_INACTIVITY_SA, false) ? 1 : 0) << 5);
+            reps |= ((prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_INACTIVITY_SU, false) ? 1 : 0) << 6);
+
+            inactivity[5] = (byte) reps;
+
+            try {
+                calendar.setTime(df_start.parse(start));
+                try {
+                    calendar_end.setTime(df_end.parse(end));
+
+                    inactivity[7] = (byte) calendar.get(Calendar.HOUR_OF_DAY);
+                    inactivity[8] = (byte) calendar.get(Calendar.MINUTE);
+                    inactivity[9] = (byte) calendar_end.get(Calendar.HOUR_OF_DAY);
+                    inactivity[10] = (byte) calendar_end.get(Calendar.MINUTE);
+                } catch (Exception e) {
+                    LOG.error("Unexpected exception in ZeTimeDeviceSupport.setInactivityAlert: " + e.getMessage());
+                }
+            } catch (Exception e) {
+                LOG.error("Unexpected exception in ZeTimeDeviceSupport.setInactivityAlert: " + e.getMessage());
+            }
+        }
+
+        sendMsgToWatch(builder, inactivity);
+    }
+
+    private void setShockStrength(TransactionBuilder builder) {
+        Prefs prefs = getDevicePrefs();
+        int shockStrength = prefs.getInt(ZeTimeConstants.PREF_SHOCK_STRENGTH, 255);
+
+        byte[] strength = {
+                ZeTimeConstants.CMD_PREAMBLE,
+                ZeTimeConstants.CMD_SHOCK_STRENGTH,
+                ZeTimeConstants.CMD_SEND,
+                (byte) 0x1,
+                (byte) 0x0,
+                (byte) shockStrength,
+                ZeTimeConstants.CMD_END
+        };
+
+        sendMsgToWatch(builder, strength);
+    }
+
+    private void setSignaling(final TransactionBuilder builder, final String signalingType) {
+        final Prefs prefs = getDevicePrefs();
+
+        final byte typeByte;
+        final int signalTypeDefault;
+
+        switch (signalingType) {
+            case ZeTimeConstants.PREF_SMS_SIGNALING:
+                typeByte = ZeTimeConstants.SMS_TYPE;
+                signalTypeDefault = 2;
+                break;
+            case ZeTimeConstants.PREF_CALL_SIGNALING:
+                typeByte = ZeTimeConstants.CALL_TYPE;
+                signalTypeDefault = 13;
+                break;
+            case ZeTimeConstants.PREF_MISSED_CALL_SIGNALING:
+                typeByte = ZeTimeConstants.MISSED_CALL_TYPE;
+                signalTypeDefault = 12;
+                break;
+            case ZeTimeConstants.PREF_EMAIL_SIGNALING:
+                typeByte = ZeTimeConstants.EMAIL_TYPE;
+                signalTypeDefault = 12;
+                break;
+            case ZeTimeConstants.PREF_SOCIAL_SIGNALING:
+                typeByte = ZeTimeConstants.SOCIAL_TYPE;
+                signalTypeDefault = 12;
+                break;
+            case ZeTimeConstants.PREF_CALENDAR_SIGNALING:
+                typeByte = ZeTimeConstants.CALENDAR_TYPE;
+                signalTypeDefault = 12;
+                break;
+            case ZeTimeConstants.PREF_INACTIVITY_SIGNALING:
+                typeByte = ZeTimeConstants.INACTIVITY_TYPE;
+                signalTypeDefault = 12;
+                break;
+            case ZeTimeConstants.PREF_LOW_POWER_SIGNALING:
+                typeByte = ZeTimeConstants.LOW_POWER_TYPE;
+                signalTypeDefault = 4;
+                break;
+            case ZeTimeConstants.PREF_ANTI_LOSS_SIGNALING:
+                typeByte = ZeTimeConstants.ANTI_LOSS_TYPE;
+                signalTypeDefault = 13;
+                break;
+            default:
+                LOG.error("Unknown signaling type {}", signalingType);
+                return;
+        }
+
+        final int signalType = prefs.getInt(signalingType, signalTypeDefault);
+
+        final byte[] signaling = {
+                ZeTimeConstants.CMD_PREAMBLE,
+                ZeTimeConstants.CMD_SHOCK_MODE,
+                ZeTimeConstants.CMD_SEND,
+                (byte) 0x2,
+                (byte) 0x0,
+                typeByte,
+                (byte) signalType,
+                ZeTimeConstants.CMD_END
+        };
+
+        sendMsgToWatch(builder, signaling);
+    }
+
+    @Override
+    public void onReadConfiguration(String config) {
+        try {
+            TransactionBuilder builder = performInitialized("readConfiguration");
+
+            if (!getDevice().isBusy()) {
+                getDevice().setBusyTask("readConfiguration");
+            }
+
+            byte[] configRead1 = {
+                    ZeTimeConstants.CMD_PREAMBLE,
+                    ZeTimeConstants.CMD_TIME_SURFACE_SETTINGS,
+                    ZeTimeConstants.CMD_REQUEST,
+                    (byte) 0x1,
+                    (byte) 0x0,
+                    (byte) 0x0,
+                    ZeTimeConstants.CMD_END
+            };
+            sendMsgToWatch(builder, configRead1);
+
+            byte[] configRead2 = new byte[configRead1.length];
+            System.arraycopy(configRead1, 0, configRead2, 0, configRead1.length);
+            configRead2[1] = ZeTimeConstants.CMD_SHOCK_MODE;
+            sendMsgToWatch(builder, configRead2);
+
+            byte[] configRead3 = new byte[configRead1.length];
+            System.arraycopy(configRead1, 0, configRead3, 0, configRead1.length);
+            configRead3[1] = ZeTimeConstants.CMD_DO_NOT_DISTURB;
+            sendMsgToWatch(builder, configRead3);
+
+            byte[] configRead4 = new byte[configRead1.length];
+            System.arraycopy(configRead1, 0, configRead4, 0, configRead1.length);
+            configRead4[1] = ZeTimeConstants.CMD_ANALOG_MODE;
+            sendMsgToWatch(builder, configRead4);
+
+            byte[] configRead5 = new byte[configRead1.length];
+            System.arraycopy(configRead1, 0, configRead5, 0, configRead1.length);
+            configRead5[1] = ZeTimeConstants.CMD_CONTROL_DEVICE;
+            sendMsgToWatch(builder, configRead5);
+
+            byte[] configRead6 = new byte[configRead1.length];
+            System.arraycopy(configRead1, 0, configRead6, 0, configRead1.length);
+            configRead6[1] = ZeTimeConstants.CMD_DISPLAY_TIMEOUT;
+            sendMsgToWatch(builder, configRead6);
+
+            byte[] configRead7 = new byte[configRead1.length];
+            System.arraycopy(configRead1, 0, configRead7, 0, configRead1.length);
+            configRead7[1] = ZeTimeConstants.CMD_USAGE_HABITS;
+            sendMsgToWatch(builder, configRead7);
+
+            byte[] configRead8 = new byte[configRead1.length];
+            System.arraycopy(configRead1, 0, configRead8, 0, configRead1.length);
+            configRead8[1] = ZeTimeConstants.CMD_AUTO_HEARTRATE;
+            sendMsgToWatch(builder, configRead8);
+
+            byte[] configRead9 = new byte[configRead1.length];
+            System.arraycopy(configRead1, 0, configRead9, 0, configRead1.length);
+            configRead9[1] = ZeTimeConstants.CMD_HEARTRATE_ALARM_LIMITS;
+            sendMsgToWatch(builder, configRead9);
+
+            byte[] configRead10 = new byte[configRead1.length];
+            System.arraycopy(configRead1, 0, configRead10, 0, configRead1.length);
+            configRead10[1] = ZeTimeConstants.CMD_INACTIVITY_ALERT;
+            sendMsgToWatch(builder, configRead10);
+
+            byte[] configRead11 = new byte[configRead1.length];
+            System.arraycopy(configRead1, 0, configRead11, 0, configRead1.length);
+            configRead11[1] = ZeTimeConstants.CMD_CALORIES_TYPE;
+            sendMsgToWatch(builder, configRead11);
+
+            byte[] configRead12 = new byte[configRead1.length];
+            System.arraycopy(configRead1, 0, configRead12, 0, configRead1.length);
+            configRead12[1] = ZeTimeConstants.CMD_SWITCH_SETTINGS;
+            sendMsgToWatch(builder, configRead12);
+
+            builder.queue(getQueue());
+        } catch (IOException e) {
+            GB.toast(getContext(), "Error reading configuration: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
+        }
+    }
+
+    private void onReadReminders(TransactionBuilder builder) {
+//        try {
+//            TransactionBuilder builder = performInitialized("readReminders");
+
+        byte[] reminders = {
+                ZeTimeConstants.CMD_PREAMBLE,
+                ZeTimeConstants.CMD_REMINDERS,
+                ZeTimeConstants.CMD_REQUEST,
+                (byte) 0x1,
+                (byte) 0x0,
+                (byte) 0x0,
+                ZeTimeConstants.CMD_END
+        };
+        sendMsgToWatch(builder, reminders);
+
+        //builder.queue(getQueue());
+//        } catch (IOException e) {
+//            GB.toast(getContext(), "Error reading reminders: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
+//        }
+    }
+
+    private void getDateTimeFormat(byte[] msg) {
+        SharedPreferences.Editor prefs = getDevicePrefs().getPreferences().edit();
+
+        prefs.putString(ZeTimeConstants.PREF_DATE_FORMAT, Integer.toString(msg[5]));
+
+        String timeFormat = "24h";
+        if (msg[6] == 2) {
+            timeFormat = "am/pm";
+        }
+        prefs.putString(DeviceSettingsPreferenceConst.PREF_TIMEFORMAT, timeFormat);
+
+        prefs.apply();
+    }
+
+    private void getSignaling(byte[] msg) {
+        SharedPreferences.Editor prefs = getDevicePrefs().getPreferences().edit();
+
+        prefs.putString(ZeTimeConstants.PREF_ANTI_LOSS_SIGNALING, Integer.toString(msg[5]));
+        prefs.putString(ZeTimeConstants.PREF_CALL_SIGNALING, Integer.toString(msg[7]));
+        prefs.putString(ZeTimeConstants.PREF_MISSED_CALL_SIGNALING, Integer.toString(msg[8]));
+        prefs.putString(ZeTimeConstants.PREF_SMS_SIGNALING, Integer.toString(msg[9]));
+        prefs.putString(ZeTimeConstants.PREF_SOCIAL_SIGNALING, Integer.toString(msg[10]));
+        prefs.putString(ZeTimeConstants.PREF_EMAIL_SIGNALING, Integer.toString(msg[11]));
+        prefs.putString(ZeTimeConstants.PREF_CALENDAR_SIGNALING, Integer.toString(msg[12]));
+        prefs.putString(ZeTimeConstants.PREF_INACTIVITY_SIGNALING, Integer.toString(msg[13]));
+        prefs.putString(ZeTimeConstants.PREF_LOW_POWER_SIGNALING, Integer.toString(msg[14]));
+        prefs.apply();
+    }
+
+    private void getDoNotDisturb(byte[] msg) {
+        SharedPreferences.Editor prefs = getDevicePrefs().getPreferences().edit();
+        String starttime = String.format("%02d:%02d", msg[6], msg[7]);
+        String endtime = String.format("%02d:%02d", msg[8], msg[9]);
+
+        if (0x1 == msg[5]) {
+            prefs.putString(DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_NOAUTO, "scheduled");
+        } else {
+            prefs.putString(DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_NOAUTO, "off");
+        }
+        prefs.putString(DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_NOAUTO_START, starttime);
+        prefs.putString(DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_NOAUTO_END, endtime);
+        prefs.apply();
+    }
+
+    private void getAnalogMode(byte[] msg) {
+        SharedPreferences.Editor prefs = getDevicePrefs().getPreferences().edit();
+
+        prefs.putString(ZeTimeConstants.PREF_ANALOG_MODE, Integer.toString(msg[5]));
+        prefs.apply();
+    }
+
+    private void getActivityTracking(byte[] msg) {
+        SharedPreferences.Editor prefs = getDevicePrefs().getPreferences().edit();
+
+        prefs.putBoolean(ZeTimeConstants.PREF_ACTIVITY_TRACKING, 0x1 != msg[6]);
+        prefs.apply();
+    }
+
+    private void getScreenTime(byte[] msg) {
+        SharedPreferences.Editor prefs = getDevicePrefs().getPreferences().edit();
+
+        prefs.putString(ZeTimeConstants.PREF_SCREENTIME, Integer.toString((msg[5] | (msg[6] << 8))));
+        prefs.apply();
+    }
+
+    private void getWrist(byte[] msg) {
+        SharedPreferences.Editor prefs = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).edit();
+
+        if (ZeTimeConstants.WEAR_ON_LEFT_WRIST == msg[5]) {
+            prefs.putString(DeviceSettingsPreferenceConst.PREF_WEARLOCATION, "left");
+        } else if (ZeTimeConstants.WEAR_ON_RIGHT_WRIST == msg[5]) {
+            prefs.putString(DeviceSettingsPreferenceConst.PREF_WEARLOCATION, "right");
+        }
+        prefs.apply();
+    }
+
+    private void getHeartRateMeasurement(byte[] msg) {
+        SharedPreferences.Editor prefs = getDevicePrefs().getPreferences().edit();
+        prefs.putString(ZeTimeConstants.PREF_ZETIME_HEARTRATE_INTERVAL, Integer.toString((msg[5] * 60))); // multiply with 60 because of the conversion from minutes to seconds
+        prefs.apply();
+    }
+
+    private void getHeartRateLimits(byte[] msg) {
+        SharedPreferences.Editor prefs = getDevicePrefs().getPreferences().edit();
+        prefs.apply();
+    }
+
+    private void getInactivityAlert(byte[] msg) {
+        SharedPreferences.Editor prefs = getDevicePrefs().getPreferences().edit();
+        String starttime = String.format("%02d:%02d", msg[7], msg[8]);
+        String endtime = String.format("%02d:%02d", msg[9], msg[10]);
+
+        prefs.putString(DeviceSettingsPreferenceConst.PREF_INACTIVITY_THRESHOLD, Integer.toString(msg[6]));
+        if (0 != msg[5]) {
+            prefs.putBoolean(DeviceSettingsPreferenceConst.PREF_INACTIVITY_ENABLE, true);
+            prefs.putString(DeviceSettingsPreferenceConst.PREF_INACTIVITY_START, starttime);
+            prefs.putString(DeviceSettingsPreferenceConst.PREF_INACTIVITY_END, endtime);
+            if (0 != (msg[5] & (1 << 0))) {
+                prefs.putBoolean(DeviceSettingsPreferenceConst.PREF_INACTIVITY_MO, true);
+            } else {
+                prefs.putBoolean(DeviceSettingsPreferenceConst.PREF_INACTIVITY_MO, false);
+            }
+            if (0 != (msg[5] & (1 << 1))) {
+                prefs.putBoolean(DeviceSettingsPreferenceConst.PREF_INACTIVITY_TU, true);
+            } else {
+                prefs.putBoolean(DeviceSettingsPreferenceConst.PREF_INACTIVITY_TU, false);
+            }
+            if (0 != (msg[5] & (1 << 2))) {
+                prefs.putBoolean(DeviceSettingsPreferenceConst.PREF_INACTIVITY_WE, true);
+            } else {
+                prefs.putBoolean(DeviceSettingsPreferenceConst.PREF_INACTIVITY_WE, false);
+            }
+            if (0 != (msg[5] & (1 << 3))) {
+                prefs.putBoolean(DeviceSettingsPreferenceConst.PREF_INACTIVITY_TH, true);
+            } else {
+                prefs.putBoolean(DeviceSettingsPreferenceConst.PREF_INACTIVITY_TH, false);
+            }
+            if (0 != (msg[5] & (1 << 4))) {
+                prefs.putBoolean(DeviceSettingsPreferenceConst.PREF_INACTIVITY_FR, true);
+            } else {
+                prefs.putBoolean(DeviceSettingsPreferenceConst.PREF_INACTIVITY_FR, false);
+            }
+            if (0 != (msg[5] & (1 << 5))) {
+                prefs.putBoolean(DeviceSettingsPreferenceConst.PREF_INACTIVITY_SA, true);
+            } else {
+                prefs.putBoolean(DeviceSettingsPreferenceConst.PREF_INACTIVITY_SA, false);
+            }
+            if (0 != (msg[5] & (1 << 6))) {
+                prefs.putBoolean(DeviceSettingsPreferenceConst.PREF_INACTIVITY_SU, true);
+            } else {
+                prefs.putBoolean(DeviceSettingsPreferenceConst.PREF_INACTIVITY_SU, false);
+            }
+        }
+        prefs.apply();
+    }
+
+    private void getCaloriesType(byte[] msg) {
+        SharedPreferences prefs = getDevicePrefs().getPreferences();
+        SharedPreferences.Editor myedit = prefs.edit();
+
+        myedit.putString(ZeTimeConstants.PREF_CALORIES_TYPE, Integer.toString(msg[5]));
+        myedit.apply();
+    }
+
+    private void getDisplayOnMovement(byte[] msg) {
+        SharedPreferences.Editor prefs = getDevicePrefs().getPreferences().edit();
+        if (0 != (msg[6] & (1 << 6))) {
+            prefs.putBoolean(ZeTimeConstants.PREF_HANDMOVE_DISPLAY, true);
+        } else {
+            prefs.putBoolean(ZeTimeConstants.PREF_HANDMOVE_DISPLAY, false);
+        }
+        prefs.apply();
+        if (getDevice().isBusy()) {
+            getDevice().unsetBusyTask();
+            getDevice().sendDeviceUpdateIntent(getContext());
+
+        }
+    }
+
+    private void storeActualReminders(byte[] msg) {
+        if (msg[3] == 0xb) // there is a reminder on the watch
+        {
+            System.arraycopy(msg, 6, remindersOnWatch[msg[5] - 1], 0, 10);
+        }
+    }
+
+    private void setLanguage(TransactionBuilder builder) {
+        String localeString = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getString(DeviceSettingsPreferenceConst.PREF_LANGUAGE, "auto");
+
+        byte[] languageMsg = {
+                ZeTimeConstants.CMD_PREAMBLE,
+                ZeTimeConstants.CMD_LANGUAGE_SETTINGS,
+                ZeTimeConstants.CMD_SEND,
+                (byte) 0x1,
+                (byte) 0x0,
+                (byte) 0x0,
+                ZeTimeConstants.CMD_END
+        };
+
+        if (localeString == null || localeString.equals("auto")) {
+            String language = Locale.getDefault().getLanguage();
+            String country = Locale.getDefault().getCountry();
+
+            if (country == null) {
+                // sometimes country is null, no idea why, guess it.
+                country = language;
+            }
+            localeString = language + "_" + country.toUpperCase();
+        }
+
+        switch (localeString.substring(0, 2)) {
+            case "zh":
+                if (localeString.equals("zh_CN")) {
+                    languageMsg[5] = 1;
+                } else {
+                    languageMsg[5] = 2;
+                }
+                break;
+            case "ko":
+                languageMsg[5] = 3;
+                break;
+            case "th":
+                languageMsg[5] = 4;
+                break;
+            case "ja":
+                languageMsg[5] = 5;
+                break;
+            case "fr":
+                languageMsg[5] = 6;
+                break;
+            case "es":
+                languageMsg[5] = 7;
+                break;
+            case "de":
+                languageMsg[5] = 8;
+                break;
+            case "it":
+                languageMsg[5] = 9;
+                break;
+            case "pl":
+                languageMsg[5] = 10;
+                break;
+            case "pt":
+                languageMsg[5] = 11;
+                break;
+            case "ru":
+                languageMsg[5] = 12;
+                break;
+            case "nl":
+                languageMsg[5] = 13;
+                break;
+            case "ar":
+                languageMsg[5] = 14;
+                break;
+            case "el":
+                languageMsg[5] = 15;
+                break;
+            case "he":
+                languageMsg[5] = 16;
+                break;
+            case "sv":
+                languageMsg[5] = 17;
+                break;
+            case "cs":
+                languageMsg[5] = 18;
+                break;
+            case "en":
+            default:
+                languageMsg[5] = 0;
+        }
+        sendMsgToWatch(builder, languageMsg);
+    }
+
+    @Override
+    public boolean getImplicitCallbackModify() {
+        return true;
+    }
+
+    @Override
+    public boolean getSendWriteRequestResponse() {
+        return false;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/receivers/AutoConnectIntervalReceiver.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/receivers/AutoConnectIntervalReceiver.java
new file mode 100644
index 0000000..cf403b5
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/receivers/AutoConnectIntervalReceiver.java
@@ -0,0 +1,121 @@
+/*  Copyright (C) 2019-2024 Andreas Shimokawa, Daniel Dakhno, Ganblejs,
+    Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.receivers;
+
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Build;
+
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Calendar;
+
+import nodomain.freeyourgadget.gadgetbridge.BuildConfig;
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.devices.DeviceManager;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceCommunicationService;
+import nodomain.freeyourgadget.gadgetbridge.util.PendingIntentUtils;
+
+public class AutoConnectIntervalReceiver extends BroadcastReceiver {
+
+    final DeviceCommunicationService service;
+    static int mDelay = 4;
+    private static final Logger LOG = LoggerFactory.getLogger(AutoConnectIntervalReceiver.class);
+
+    public AutoConnectIntervalReceiver(DeviceCommunicationService service) {
+        this.service = service;
+        IntentFilter filterLocal = new IntentFilter();
+        filterLocal.addAction(DeviceManager.ACTION_DEVICES_CHANGED);
+        LocalBroadcastManager.getInstance(service).registerReceiver(this, filterLocal);
+    }
+
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        String action = intent.getAction();
+        if (action == null) {
+            return;
+        }
+
+        GBDevice[] devices = service.getGBDevices();
+        if (action.equals(DeviceManager.ACTION_DEVICES_CHANGED)){
+            boolean scheduleAutoConnect = false;
+            boolean allDevicesInitialized = true;
+            for(GBDevice device : devices){
+                if(!device.isInitialized()) {
+                    allDevicesInitialized = false;
+                    if (device.getState() == GBDevice.State.WAITING_FOR_RECONNECT) {
+                        scheduleAutoConnect = true;
+                    }
+                }
+            }
+
+            if(allDevicesInitialized){
+                LOG.info("will reset connection delay, all devices are initialized!");
+                mDelay = 4;
+                return;
+            }
+            if(scheduleAutoConnect){
+                scheduleReconnect();
+            }
+        }else if (action.equals("GB_RECONNECT")){
+            for(GBDevice device : devices){
+                if(device.getState() == GBDevice.State.WAITING_FOR_RECONNECT) {
+                    LOG.info("Will re-connect to " + device.getAddress() + "(" + device.getName() + ")");
+                    GBApplication.deviceService(device).connect();
+                }
+            }
+        }
+    }
+
+    public void scheduleReconnect() {
+        mDelay*=2;
+        if (mDelay > 64) {
+            mDelay = 64;
+        }
+        scheduleReconnect(mDelay);
+    }
+
+    public void scheduleReconnect(int delay) {
+        LOG.info("scheduling reconnect in " + delay + " seconds");
+        AlarmManager am = (AlarmManager) (GBApplication.getContext().getSystemService(Context.ALARM_SERVICE));
+        Intent intent = new Intent("GB_RECONNECT");
+        intent.setPackage(BuildConfig.APPLICATION_ID);
+        PendingIntent pendingIntent = PendingIntentUtils.getBroadcast(GBApplication.getContext(), 0, intent, 0, false);
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+            am.setAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, Calendar.getInstance().
+                    getTimeInMillis() + delay * 1000, pendingIntent);
+        } else {
+            am.set(AlarmManager.RTC_WAKEUP, Calendar.getInstance().
+                    getTimeInMillis() + delay * 1000, pendingIntent);
+        }
+    }
+
+    public void destroy() {
+        LocalBroadcastManager.getInstance(service).unregisterReceiver(this);
+    }
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/receivers/GBAutoFetchReceiver.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/receivers/GBAutoFetchReceiver.java
new file mode 100644
index 0000000..5d191d7
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/receivers/GBAutoFetchReceiver.java
@@ -0,0 +1,56 @@
+/*  Copyright (C) 2018-2024 Daniele Gobbetti, Jos茅 Rebelo, Martin
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.receivers;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+import org.apache.commons.lang3.time.DateUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Date;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.model.RecordedDataTypes;
+
+
+public class GBAutoFetchReceiver extends BroadcastReceiver {
+    private static final Logger LOG = LoggerFactory.getLogger(GBAutoFetchReceiver.class);
+
+    private Date lastSync = new Date();
+
+    @Override
+    public void onReceive(final Context context, final Intent intent) {
+        synchronized (this) {
+            final Date now = new Date();
+            final long timeSinceLast = now.getTime() - lastSync.getTime();
+            if (timeSinceLast < 2500L) {
+                // #4165 - prevent multiple syncs in very quick succession
+                LOG.warn("Throttling auto fetch by {}, last one was {}ms ago", intent.getAction(), timeSinceLast);
+                return;
+            }
+            final Date nextSync = DateUtils.addMinutes(lastSync, GBApplication.getPrefs().getInt("auto_fetch_interval_limit", 0));
+            if (nextSync.before(now)) {
+                LOG.info("Trigger auto fetch by {}", intent.getAction());
+                GBApplication.deviceService().onFetchRecordedData(RecordedDataTypes.TYPE_SYNC);
+                lastSync = now;
+            }
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/receivers/GBCallControlReceiver.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/receivers/GBCallControlReceiver.java
new file mode 100644
index 0000000..197c487
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/receivers/GBCallControlReceiver.java
@@ -0,0 +1,92 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, Andrzej Surowiec, Carsten
+    Pfeiffer
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.receivers;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Build;
+import android.telecom.TelecomManager;
+import android.telephony.TelephonyManager;
+
+import androidx.annotation.RequiresApi;
+
+import com.android.internal.telephony.ITelephony;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.lang.reflect.Method;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventCallControl;
+
+public class GBCallControlReceiver extends BroadcastReceiver {
+    public static final String ACTION_CALLCONTROL = "nodomain.freeyourgadget.gadgetbridge.callcontrol";
+    private static final Logger LOG = LoggerFactory.getLogger(GBCallControlReceiver.class);
+    private Context mContext = GBApplication.getContext();
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        GBDeviceEventCallControl.Event callCmd = GBDeviceEventCallControl.Event.values()[intent.getIntExtra("event", 0)];
+
+        if (GBApplication.isRunningPieOrLater()) {
+            handleCallCmdTelecomManager(callCmd);
+        } else {
+            switch (callCmd) {
+                case END:
+                case REJECT:
+                case START:
+                    try {
+                        TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
+                        Class clazz = Class.forName(telephonyManager.getClass().getName());
+                        Method method = clazz.getDeclaredMethod("getITelephony");
+                        method.setAccessible(true);
+                        ITelephony telephonyService = (ITelephony) method.invoke(telephonyManager);
+                        if (callCmd == GBDeviceEventCallControl.Event.END || callCmd == GBDeviceEventCallControl.Event.REJECT) {
+                            telephonyService.endCall();
+                        } else {
+                            telephonyService.answerRingingCall();
+                        }
+                    } catch (Exception e) {
+                        LOG.warn("could not start or hangup call");
+                    }
+                    break;
+                default:
+            }
+        }
+    }
+
+    @RequiresApi(api = Build.VERSION_CODES.P)
+    public void handleCallCmdTelecomManager(GBDeviceEventCallControl.Event callCmd) {
+        try {
+            TelecomManager tm = (TelecomManager) mContext.getSystemService(Context.TELECOM_SERVICE);
+
+            if (callCmd == GBDeviceEventCallControl.Event.END || callCmd == GBDeviceEventCallControl.Event.REJECT) {
+                tm.endCall();
+            } else if (callCmd == GBDeviceEventCallControl.Event.START || callCmd == GBDeviceEventCallControl.Event.ACCEPT) {
+                tm.acceptRingingCall();
+            }
+
+        } catch (SecurityException e) {
+            LOG.warn("no permission to start or hangup call");
+        } catch (Exception e) {
+            LOG.warn("could not start or hangup call");
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/receivers/GBMusicControlReceiver.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/receivers/GBMusicControlReceiver.java
new file mode 100644
index 0000000..6345234
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/receivers/GBMusicControlReceiver.java
@@ -0,0 +1,220 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, Benjamin Swartley, Carsten
+    Pfeiffer, Daniele Gobbetti, Gabe Schrecker, Jos茅 Rebelo, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.receivers;
+
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.media.AudioManager;
+import android.media.session.MediaController;
+import android.media.session.MediaSessionManager;
+import android.media.session.PlaybackState;
+import android.os.SystemClock;
+import android.view.KeyEvent;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventMusicControl;
+import nodomain.freeyourgadget.gadgetbridge.externalevents.NotificationListener;
+import nodomain.freeyourgadget.gadgetbridge.util.GBPrefs;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+public class GBMusicControlReceiver extends BroadcastReceiver {
+    private static final Logger LOG = LoggerFactory.getLogger(GBMusicControlReceiver.class);
+
+    public static final String ACTION_MUSICCONTROL = "nodomain.freeyourgadget.gadgetbridge.musiccontrol";
+
+    @Override
+    public void onReceive(final Context context, final Intent intent) {
+        final int event = intent.getIntExtra("event", 0);
+        final GBDeviceEventMusicControl.Event musicCmd = GBDeviceEventMusicControl.Event.values()[event];
+        final int keyCode;
+
+        final AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+
+        switch (musicCmd) {
+            case NEXT:
+                keyCode = KeyEvent.KEYCODE_MEDIA_NEXT;
+                break;
+            case PREVIOUS:
+                keyCode = KeyEvent.KEYCODE_MEDIA_PREVIOUS;
+                break;
+            case PLAY:
+                keyCode = KeyEvent.KEYCODE_MEDIA_PLAY;
+                break;
+            case PAUSE:
+                keyCode = KeyEvent.KEYCODE_MEDIA_PAUSE;
+                break;
+            case PLAYPAUSE:
+                keyCode = KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE;
+                break;
+            case REWIND:
+                keyCode = KeyEvent.KEYCODE_MEDIA_REWIND;
+                break;
+            case FORWARD:
+                keyCode = KeyEvent.KEYCODE_MEDIA_FAST_FORWARD;
+                break;
+            case VOLUMEUP:
+                audioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC, AudioManager.ADJUST_RAISE, 0);
+                sendPhoneVolume(audioManager);
+                return;
+            case VOLUMEDOWN:
+                audioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC, AudioManager.ADJUST_LOWER, 0);
+                sendPhoneVolume(audioManager);
+                return;
+            default:
+                LOG.error("Unknown event {}", event);
+                return;
+        }
+
+        final GBPrefs prefs = GBApplication.getPrefs();
+
+        if (prefs.getBoolean("pref_deprecated_media_control", false)) {
+            // Deprecated path - mb_intents works for some players and not others, and vice-versa
+
+            final long eventTime = SystemClock.uptimeMillis();
+
+            if (prefs.getBoolean("mb_intents", false)) {
+                String audioPlayer = getAudioPlayer(context);
+
+                LOG.debug("Sending key press {} to {}", musicCmd, audioPlayer);
+
+                final Intent downIntent = new Intent(Intent.ACTION_MEDIA_BUTTON, null);
+                final KeyEvent downEvent = new KeyEvent(eventTime, eventTime, KeyEvent.ACTION_DOWN, keyCode, 0);
+                downIntent.putExtra(Intent.EXTRA_KEY_EVENT, downEvent);
+                if (!"default".equals(audioPlayer)) {
+                    downIntent.setPackage(audioPlayer);
+                }
+                context.sendOrderedBroadcast(downIntent, null);
+
+                final Intent upIntent = new Intent(Intent.ACTION_MEDIA_BUTTON, null);
+                final KeyEvent upEvent = new KeyEvent(eventTime, eventTime, KeyEvent.ACTION_UP, keyCode, 0);
+                upIntent.putExtra(Intent.EXTRA_KEY_EVENT, upEvent);
+                if (!"default".equals(audioPlayer)) {
+                    upIntent.setPackage(audioPlayer);
+                }
+                context.sendOrderedBroadcast(upIntent, null);
+            } else {
+                LOG.debug("Sending key press {} generally", musicCmd);
+                final KeyEvent downEvent = new KeyEvent(eventTime, eventTime, KeyEvent.ACTION_DOWN, keyCode, 0);
+                audioManager.dispatchMediaKeyEvent(downEvent);
+
+                final KeyEvent upEvent = new KeyEvent(eventTime, eventTime, KeyEvent.ACTION_UP, keyCode, 0);
+                audioManager.dispatchMediaKeyEvent(upEvent);
+            }
+        } else {
+            try {
+                final MediaSessionManager mediaSessionManager = (MediaSessionManager) context.getSystemService(Context.MEDIA_SESSION_SERVICE);
+                final List<MediaController> controllers = mediaSessionManager.getActiveSessions(
+                        new ComponentName(context, NotificationListener.class)
+                );
+
+                if (controllers.isEmpty()) {
+                    LOG.warn("No media controller found to handle {}", musicCmd);
+                    return;
+                }
+
+                final MediaController controller = controllers.get(0);
+
+                switch (musicCmd) {
+                    case NEXT:
+                        controller.getTransportControls().skipToNext();
+                        return;
+                    case PREVIOUS:
+                        controller.getTransportControls().skipToPrevious();
+                        return;
+                    case PLAY:
+                        controller.getTransportControls().play();
+                        return;
+                    case PAUSE:
+                        controller.getTransportControls().pause();
+                        return;
+                    case PLAYPAUSE:
+                        final PlaybackState playbackState = controller.getPlaybackState();
+                        if (playbackState != null) {
+                            switch (playbackState.getState()) {
+                                case PlaybackState.STATE_NONE:
+                                case PlaybackState.STATE_STOPPED:
+                                case PlaybackState.STATE_PAUSED:
+                                case PlaybackState.STATE_FAST_FORWARDING:
+                                case PlaybackState.STATE_REWINDING:
+                                    controller.getTransportControls().play();
+                                    return;
+                                case PlaybackState.STATE_PLAYING:
+                                    controller.getTransportControls().pause();
+                                    return;
+                                default:
+                                    return;
+                            }
+                        } else {
+                            LOG.warn("Failed to determine playback state, attempting to play");
+                            controller.getTransportControls().play();
+                        }
+                        return;
+                    case REWIND:
+                        controller.getTransportControls().rewind();
+                        return;
+                    case FORWARD:
+                        controller.getTransportControls().fastForward();
+                }
+            } catch (final SecurityException e) {
+                LOG.warn("Failed to get media controller - did not grant notification access?", e);
+            } catch (final Exception e) {
+                LOG.error("Failed to get media controller", e);
+            }
+        }
+    }
+
+    private static void sendPhoneVolume(final AudioManager audioManager) {
+        final int volumeLevel = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
+        final int volumeMax = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
+        final int volumePercentage = (byte) Math.round(100 * (volumeLevel / (float) volumeMax));
+
+        GBApplication.deviceService().onSetPhoneVolume(volumePercentage);
+    }
+
+    @Deprecated
+    private static String getAudioPlayer(final Context context) {
+        final Prefs prefs = GBApplication.getPrefs();
+        final String audioPlayer = prefs.getString("audio_player", "default");
+        final MediaSessionManager mediaSessionManager = (MediaSessionManager) context.getSystemService(Context.MEDIA_SESSION_SERVICE);
+        try {
+            final List<MediaController> controllers = mediaSessionManager.getActiveSessions(
+                    new ComponentName(context, NotificationListener.class)
+            );
+
+            if (controllers.isEmpty()) {
+                LOG.warn("No media controller available");
+                return audioPlayer;
+            }
+
+            return controllers.get(0).getPackageName();
+        } catch (final SecurityException e) {
+            LOG.warn("No permission to get media sessions - did not grant notification access?", e);
+        } catch (final Exception e) {
+            LOG.error("Failed to get media controller", e);
+        }
+
+        return audioPlayer;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/AbstractSerialDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/AbstractSerialDeviceSupport.java
new file mode 100644
index 0000000..17d8c73
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/AbstractSerialDeviceSupport.java
@@ -0,0 +1,315 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, Carsten Pfeiffer, Jos茅 Rebelo,
+    Julien Pivotto, Steffen Liebergeld
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.serial;
+
+import android.location.Location;
+import android.os.Bundle;
+
+import java.util.ArrayList;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventSendBytes;
+import nodomain.freeyourgadget.gadgetbridge.devices.EventHandler;
+import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
+import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.Reminder;
+import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.WorldClock;
+import nodomain.freeyourgadget.gadgetbridge.service.AbstractDeviceSupport;
+
+/**
+ * An abstract base class for devices speaking a serial protocol, like via
+ * an rfcomm bluetooth socket or a TCP socket.
+ * <p/>
+ * This class uses two helper classes to deal with that:
+ * - GBDeviceIoThread, which creates and maintains the actual socket connection and implements the transport layer
+ * - GBDeviceProtocol, which implements the encoding and decoding of messages, i.e. the actual device specific protocol
+ * <p/>
+ * Note that these two classes need to be implemented in a device specific way.
+ * <p/>
+ * This implementation implements all methods of {@link EventHandler}, calls the {@link GBDeviceProtocol device protocol}
+ * to create the device specific message for the respective events and sends them to the device via {@link #sendToDevice(byte[])}.
+ */
+public abstract class AbstractSerialDeviceSupport extends AbstractDeviceSupport {
+    private GBDeviceProtocol gbDeviceProtocol;
+    protected GBDeviceIoThread gbDeviceIOThread;
+
+    /**
+     * Factory method to create the device specific GBDeviceProtocol instance to be used.
+     */
+    protected abstract GBDeviceProtocol createDeviceProtocol();
+
+    /**
+     * Factory method to create the device specific GBDeviceIoThread instance to be used.
+     */
+    protected abstract GBDeviceIoThread createDeviceIOThread();
+
+    @Override
+    public void dispose() {
+        // currently only one thread allowed
+        if (gbDeviceIOThread != null) {
+            gbDeviceIOThread.quit();
+            gbDeviceIOThread.interrupt();
+            gbDeviceIOThread = null;
+        }
+    }
+
+    /**
+     * Lazily creates and returns the GBDeviceProtocol instance to be used.
+     */
+    protected synchronized GBDeviceProtocol getDeviceProtocol() {
+        if (gbDeviceProtocol == null) {
+            gbDeviceProtocol = createDeviceProtocol();
+        }
+        return gbDeviceProtocol;
+    }
+
+    /**
+     * Lazily creates and returns the GBDeviceIoThread instance to be used.
+     */
+    public synchronized GBDeviceIoThread getDeviceIOThread() {
+        if (gbDeviceIOThread == null) {
+            gbDeviceIOThread = createDeviceIOThread();
+        }
+        return gbDeviceIOThread;
+    }
+
+    /**
+     * Sends the given message to the device. This implementation delegates the
+     * writing to the {@link #getDeviceIOThread device io thread}
+     *
+     * @param bytes the message to send to the device
+     */
+    private void sendToDevice(byte[] bytes) {
+        if (bytes != null && gbDeviceIOThread != null) {
+            gbDeviceIOThread.write(bytes);
+        }
+    }
+
+    private void handleGBDeviceEvent(GBDeviceEventSendBytes sendBytes) {
+        sendToDevice(sendBytes.encodedBytes);
+    }
+
+    @Override
+    public void evaluateGBDeviceEvent(GBDeviceEvent deviceEvent) {
+        if (deviceEvent instanceof GBDeviceEventSendBytes) {
+            handleGBDeviceEvent((GBDeviceEventSendBytes) deviceEvent);
+            return;
+        }
+        super.evaluateGBDeviceEvent(deviceEvent);
+    }
+
+    @Override
+    public void onNotification(NotificationSpec notificationSpec) {
+        byte[] bytes = gbDeviceProtocol.encodeNotification(notificationSpec);
+        sendToDevice(bytes);
+    }
+
+    @Override
+    public void onDeleteNotification(int id) {
+        byte[] bytes = gbDeviceProtocol.encodeDeleteNotification(id);
+        sendToDevice(bytes);
+    }
+
+    @Override
+    public void onSetTime() {
+        byte[] bytes = gbDeviceProtocol.encodeSetTime();
+        sendToDevice(bytes);
+    }
+
+    @Override
+    public void onSetCallState(CallSpec callSpec) {
+        byte[] bytes = gbDeviceProtocol.encodeSetCallState(callSpec.number, callSpec.name, callSpec.command);
+        sendToDevice(bytes);
+    }
+
+    @Override
+    public void onSetCannedMessages(CannedMessagesSpec cannedMessagesSpec) {
+        byte[] bytes = gbDeviceProtocol.encodeSetCannedMessages(cannedMessagesSpec);
+        sendToDevice(bytes);
+    }
+
+    @Override
+    public void onSetMusicState(MusicStateSpec stateSpec) {
+        byte[] bytes = gbDeviceProtocol.encodeSetMusicState(stateSpec.state, stateSpec.position, stateSpec.playRate, stateSpec.shuffle, stateSpec.repeat);
+        sendToDevice(bytes);
+    }
+
+    @Override
+    public void onSetMusicInfo(MusicSpec musicSpec) {
+        byte[] bytes = gbDeviceProtocol.encodeSetMusicInfo(musicSpec.artist, musicSpec.album, musicSpec.track, musicSpec.duration, musicSpec.trackCount, musicSpec.trackNr);
+        sendToDevice(bytes);
+    }
+
+    @Override
+    public void onSetPhoneVolume(final float volume) {
+        byte[] bytes = gbDeviceProtocol.encodeVolume(volume);
+        sendToDevice(bytes);
+    }
+
+    @Override
+    public void onAppInfoReq() {
+        byte[] bytes = gbDeviceProtocol.encodeAppInfoReq();
+        sendToDevice(bytes);
+    }
+
+    @Override
+    public void onAppStart(UUID uuid, boolean start) {
+        byte[] bytes = gbDeviceProtocol.encodeAppStart(uuid, start);
+        sendToDevice(bytes);
+    }
+
+    @Override
+    public void onAppDelete(UUID uuid) {
+        byte[] bytes = gbDeviceProtocol.encodeAppDelete(uuid);
+        sendToDevice(bytes);
+    }
+
+    @Override
+    public void onAppReorder(UUID[] uuids) {
+        byte[] bytes = gbDeviceProtocol.encodeAppReorder(uuids);
+        sendToDevice(bytes);
+    }
+
+    @Override
+    public void onFetchRecordedData(int dataTypes) {
+        byte[] bytes = gbDeviceProtocol.encodeSynchronizeActivityData();
+        sendToDevice(bytes);
+    }
+
+    @Override
+    public void onReset(int flags) {
+        byte[] bytes = gbDeviceProtocol.encodeReset(flags);
+        sendToDevice(bytes);
+    }
+
+    @Override
+    public void onFindDevice(boolean start) {
+        byte[] bytes = gbDeviceProtocol.encodeFindDevice(start);
+        sendToDevice(bytes);
+    }
+
+    @Override
+    public void onFindPhone(boolean start) {
+        byte[] bytes = gbDeviceProtocol.encodeFindPhone(start);
+        sendToDevice(bytes);
+    }
+
+    @Override
+    public void onScreenshotReq() {
+        byte[] bytes = gbDeviceProtocol.encodeScreenshotReq();
+        sendToDevice(bytes);
+    }
+
+    @Override
+    public void onEnableRealtimeSteps(boolean enable) {
+        byte[] bytes = gbDeviceProtocol.encodeEnableRealtimeSteps(enable);
+        sendToDevice(bytes);
+    }
+
+    @Override
+    public void onEnableHeartRateSleepSupport(boolean enable) {
+        byte[] bytes = gbDeviceProtocol.encodeEnableHeartRateSleepSupport(enable);
+        sendToDevice(bytes);
+    }
+
+    @Override
+    public void onEnableRealtimeHeartRateMeasurement(boolean enable) {
+        byte[] bytes = gbDeviceProtocol.encodeEnableRealtimeHeartRateMeasurement(enable);
+        sendToDevice(bytes);
+    }
+
+    @Override
+    public void onAddCalendarEvent(CalendarEventSpec calendarEventSpec) {
+        byte[] bytes = gbDeviceProtocol.encodeAddCalendarEvent(calendarEventSpec);
+        sendToDevice(bytes);
+    }
+
+    @Override
+    public void onDeleteCalendarEvent(byte type, long id) {
+        byte[] bytes = gbDeviceProtocol.encodeDeleteCalendarEvent(type, id);
+        sendToDevice(bytes);
+    }
+
+    @Override
+    public void onSendConfiguration(String config) {
+        byte[] bytes = gbDeviceProtocol.encodeSendConfiguration(config);
+        sendToDevice(bytes);
+    }
+
+    @Override
+    public void onTestNewFunction() {
+        byte[] bytes = gbDeviceProtocol.encodeTestNewFunction();
+        sendToDevice(bytes);
+    }
+
+    @Override
+    public void onSendWeather(ArrayList<WeatherSpec> weatherSpecs) {
+        WeatherSpec weatherSpec = weatherSpecs.get(0);
+        byte[] bytes = gbDeviceProtocol.encodeSendWeather(weatherSpec);
+        sendToDevice(bytes);
+    }
+
+    @Override
+    public void onSetFmFrequency(float frequency) {
+        byte[] bytes = gbDeviceProtocol.encodeFmFrequency(frequency);
+        sendToDevice(bytes);
+    }
+
+    @Override
+    public void onSetLedColor(int color) {
+        byte[] bytes = gbDeviceProtocol.encodeLedColor(color);
+        sendToDevice(bytes);
+    }
+
+    @Override
+    public void onPowerOff() {
+        byte[] bytes = gbDeviceProtocol.encodePowerOff();
+        sendToDevice(bytes);
+    }
+
+    @Override
+    public void onSetAlarms(ArrayList<? extends Alarm> alarms) {
+        byte[] bytes = gbDeviceProtocol.encodeSetAlarms(alarms);
+        sendToDevice(bytes);
+    }
+
+    @Override
+    public void onSetReminders(ArrayList<? extends Reminder> reminders) {
+        byte[] bytes = gbDeviceProtocol.encodeReminders(reminders);
+        sendToDevice(bytes);
+    }
+
+    @Override
+    public void onSetWorldClocks(ArrayList<? extends WorldClock> clocks) {
+        byte[] bytes = gbDeviceProtocol.encodeWorldClocks(clocks);
+        sendToDevice(bytes);
+    }
+
+    @Override
+    public void onSetGpsLocation(Location location) {
+        byte[] bytes = gbDeviceProtocol.encodeGpsLocation(location);
+        sendToDevice(bytes);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/GBDeviceIoThread.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/GBDeviceIoThread.java
new file mode 100644
index 0000000..528f455
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/GBDeviceIoThread.java
@@ -0,0 +1,53 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, Carsten Pfeiffer
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.serial;
+
+import android.content.Context;
+
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public abstract class GBDeviceIoThread extends Thread {
+    protected final GBDevice gbDevice;
+    private final Context context;
+
+    public GBDeviceIoThread(GBDevice gbDevice, Context context) {
+        this.gbDevice = gbDevice;
+        this.context = context;
+    }
+
+    public Context getContext() {
+        return context;
+    }
+
+    public GBDevice getDevice() {
+        return gbDevice;
+    }
+
+    protected boolean connect() {
+        return false;
+    }
+
+    @Override
+    public void run() {
+    }
+
+    synchronized public void write(byte[] bytes) {
+    }
+
+    public void quit() {
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/GBDeviceProtocol.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/GBDeviceProtocol.java
new file mode 100644
index 0000000..38f68f4
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/GBDeviceProtocol.java
@@ -0,0 +1,187 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, Carsten Pfeiffer, Jos茅 Rebelo,
+    Julien Pivotto, Steffen Liebergeld
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.serial;
+
+import android.location.Location;
+
+import java.util.ArrayList;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
+import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.Reminder;
+import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.WorldClock;
+import nodomain.freeyourgadget.gadgetbridge.util.preferences.DevicePrefs;
+
+public abstract class GBDeviceProtocol {
+
+    public static final int RESET_FLAGS_REBOOT = 1;
+    public static final int RESET_FLAGS_FACTORY_RESET = 2;
+
+    private GBDevice mDevice;
+
+    protected GBDeviceProtocol(GBDevice device) {
+        mDevice = device;
+    }
+
+    public byte[] encodeNotification(NotificationSpec notificationSpec) {
+        return null;
+    }
+
+    public byte[] encodeDeleteNotification(int id) {
+        return null;
+    }
+
+    public byte[] encodeSetTime() {
+        return null;
+    }
+
+    public byte[] encodeSetCallState(String number, String name, int command) {
+        return null;
+    }
+
+    public byte[] encodeSetCannedMessages(CannedMessagesSpec cannedMessagesSpec) {
+        return null;
+    }
+
+    public byte[] encodeSetMusicInfo(String artist, String album, String track, int duration, int trackCount, int trackNr) {
+        return null;
+    }
+
+    public byte[] encodeVolume(float volume) {
+        return null;
+    }
+
+    public byte[] encodeSetMusicState(byte state, int position, int playRate, byte shuffle, byte repeat) {
+        return null;
+    }
+
+    public byte[] encodeFirmwareVersionReq() {
+        return null;
+    }
+
+    public byte[] encodeAppInfoReq() {
+        return null;
+    }
+
+    public byte[] encodeScreenshotReq() {
+        return null;
+    }
+
+    public byte[] encodeAppDelete(UUID uuid) {
+        return null;
+    }
+
+    public byte[] encodeAppStart(UUID uuid, boolean start) {
+        return null;
+    }
+
+    public byte[] encodeAppReorder(UUID[] uuids) {
+        return null;
+    }
+
+    public byte[] encodeSynchronizeActivityData() {
+        return null;
+    }
+
+    public byte[] encodeReset(int flags) {
+        return null;
+    }
+
+    public byte[] encodeFindDevice(boolean start) {
+        return null;
+    }
+
+    public byte[] encodeFindPhone(boolean start) {
+        return null;
+    }
+
+    public byte[] encodeEnableRealtimeSteps(boolean enable) {
+        return null;
+    }
+
+    public byte[] encodeEnableHeartRateSleepSupport(boolean enable) {
+        return null;
+    }
+
+    public byte[] encodeEnableRealtimeHeartRateMeasurement(boolean enable) { return null; }
+
+    public byte[] encodeAddCalendarEvent(CalendarEventSpec calendarEventSpec) {
+        return null;
+    }
+
+    public byte[] encodeDeleteCalendarEvent(byte type, long id) {
+        return null;
+    }
+
+    public byte[] encodeSendConfiguration(String config) {
+        return null;
+    }
+
+    public byte[] encodeTestNewFunction() { return null; }
+
+    public GBDeviceEvent[] decodeResponse(byte[] responseData) {
+        return null;
+    }
+
+    public GBDevice getDevice() {
+        return mDevice;
+    }
+
+    public byte[] encodeSendWeather(WeatherSpec weatherSpec) {
+        return null;
+    }
+
+    public byte[] encodeLedColor(int color) {
+        return null;
+    }
+
+    public byte[] encodePowerOff() {
+        return null;
+    }
+
+    public byte[] encodeSetAlarms(ArrayList<? extends Alarm> alarms)  {
+        return null;
+    }
+
+    public byte[] encodeReminders(ArrayList<? extends Reminder> reminders) {
+        return null;
+    }
+
+    public byte[] encodeWorldClocks(ArrayList<? extends WorldClock> clocks) {
+        return null;
+    }
+
+    public byte[] encodeFmFrequency(float frequency) {
+        return null;
+    }
+
+    public byte[] encodeGpsLocation(Location location) {
+        return null;
+    }
+
+    protected DevicePrefs getDevicePrefs() {
+        return GBApplication.getDevicePrefs(getDevice());
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/.gitignore b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/.gitignore
new file mode 100644
index 0000000..8242cf9
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/.gitignore
@@ -0,0 +1,2 @@
+small/
+SmallHelper.java
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/Accumulator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/Accumulator.java
new file mode 100644
index 0000000..e4b2e07
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/Accumulator.java
@@ -0,0 +1,47 @@
+package nodomain.freeyourgadget.gadgetbridge.util;
+
+/**
+ * A simple class to accumulate stats (min, max, count, avg).
+ */
+public class Accumulator {
+    private double min = Double.MAX_VALUE;
+    private double max = -Double.MAX_VALUE;
+    private double sum = 0;
+    private int count;
+
+    public void add(final double value) {
+        sum += value;
+        count++;
+
+        if (value > max) {
+            max = value;
+        }
+        if (value < min) {
+            min = value;
+        }
+    }
+
+    public double getMin() {
+        return min;
+    }
+
+    public double getMax() {
+        return max;
+    }
+
+    public double getSum() {
+        return sum;
+    }
+
+    public int getCount() {
+        return count;
+    }
+
+    public double getAverage() {
+        if (count > 0) {
+            return sum / count;
+        } else {
+            return 0;
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/ActivitySummaryUtils.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/ActivitySummaryUtils.java
new file mode 100644
index 0000000..431c011
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/ActivitySummaryUtils.java
@@ -0,0 +1,87 @@
+package nodomain.freeyourgadget.gadgetbridge.util;
+
+import androidx.annotation.Nullable;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary;
+import nodomain.freeyourgadget.gadgetbridge.export.ActivityTrackExporter;
+import nodomain.freeyourgadget.gadgetbridge.export.GPXExporter;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityPoint;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityTrack;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.FitFile;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitRecord;
+
+public final class ActivitySummaryUtils {
+    private static final Logger LOG = LoggerFactory.getLogger(ActivitySummaryUtils.class);
+
+    private ActivitySummaryUtils() {
+        // utility class
+    }
+
+    @Nullable
+    public static File getTrackFile(final BaseActivitySummary summary) {
+        final String gpxTrack = summary.getGpxTrack();
+        if (gpxTrack != null) {
+            return FileUtils.tryFixPath(new File(gpxTrack));
+        }
+        final String rawDetails = summary.getRawDetailsPath();
+        if (rawDetails != null && rawDetails.endsWith(".fit")) {
+            return FileUtils.tryFixPath(new File(rawDetails));
+        }
+        return null;
+    }
+
+    @Nullable
+    public static File getGpxFile(final BaseActivitySummary summary) {
+        final File trackFile = getTrackFile(summary);
+        if (trackFile == null) {
+            return null;
+        }
+
+        try {
+            if (trackFile.getName().endsWith(".gpx")) {
+                return trackFile;
+            } else if (trackFile.getName().endsWith(".fit")) {
+                return convertFitToGpx(summary, trackFile);
+            } else {
+                LOG.error("Unknown track format for {}", trackFile.getName());
+            }
+        } catch (final Exception e) {
+            LOG.error("Failed to get gpx track", e);
+        }
+
+        return null;
+    }
+
+    private static File convertFitToGpx(final BaseActivitySummary summary, final File file) throws IOException, ActivityTrackExporter.GPXTrackEmptyException {
+        final FitFile fitFile = FitFile.parseIncoming(file);
+        final List<ActivityPoint> activityPoints = fitFile.getRecords().stream()
+                .filter(r -> r instanceof FitRecord)
+                .map(r -> ((FitRecord) r).toActivityPoint())
+                .filter(ap -> ap.getLocation() != null)
+                .collect(Collectors.toList());
+
+        final ActivityTrack activityTrack = new ActivityTrack();
+        activityTrack.setName(summary.getName());
+        activityTrack.addTrackPoints(activityPoints);
+
+        final File cacheDir = GBApplication.getContext().getCacheDir();
+        final File rawCacheDir = new File(cacheDir, "gpx");
+        //noinspection ResultOfMethodCallIgnored
+        rawCacheDir.mkdir();
+        final File gpxFile = new File(rawCacheDir, file.getName().replace(".fit", ".gpx"));
+
+        final GPXExporter gpxExporter = new GPXExporter();
+        gpxExporter.performExport(activityTrack, gpxFile);
+
+        return gpxFile;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/AlarmUtils.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/AlarmUtils.java
new file mode 100644
index 0000000..5cccdcf
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/AlarmUtils.java
@@ -0,0 +1,194 @@
+/*  Copyright (C) 2019-2024 Andreas Shimokawa, Carsten Pfeiffer, Daniele
+    Gobbetti, Dmitry Markin, Petr Van臎k, Taavi Eom盲e
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.util;
+
+import androidx.annotation.NonNull;
+
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst;
+import nodomain.freeyourgadget.gadgetbridge.entities.Alarm;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.entities.User;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+/**
+ * Some utility methods for dealing with Alarms.
+ */
+public class AlarmUtils {
+    /**
+     * Creates an auto-generated (not user-configurable), non-recurring alarm. This alarm
+     * may not be stored in the database. Some features are not available (e.g. device id, user id).
+     * @param index
+     * @param smartWakeup
+     * @param calendar
+     * @return
+     */
+    public static nodomain.freeyourgadget.gadgetbridge.model.Alarm createSingleShot(int index, boolean smartWakeup, boolean snooze, Calendar calendar) {
+        // TODO: add interval setting?
+        return new Alarm(-1, -1, index, true, smartWakeup, null, snooze, Alarm.ALARM_ONCE, calendar.get(Calendar.HOUR_OF_DAY), calendar.get(Calendar.MINUTE), false, GBApplication.getContext().getString(R.string.quick_alarm), GBApplication.getContext().getString(R.string.quick_alarm_description));
+    }
+
+    /**
+     * Creates a default Alarm
+     * @param device
+     * @param position
+     */
+    public static Alarm createDefaultAlarm(GBDevice gbDevice, int position) {
+        try (DBHandler db = GBApplication.acquireDB()) {
+            DaoSession daoSession = db.getDaoSession();
+            return createDefaultAlarm(daoSession, gbDevice, position);
+        } catch (Exception e) {
+            GB.log("Error accessing database", GB.ERROR, e);
+            return null;
+        }
+    }
+
+    /**
+     * Creates a default Alarm
+     * @param daoSession
+     * @param position
+     */
+    public static Alarm createDefaultAlarm(DaoSession daoSession, GBDevice gbDevice, int position) {
+        Device device = DBHelper.getDevice(gbDevice, daoSession);
+        User user = DBHelper.getUser(daoSession);
+        return new Alarm(device.getId(), user.getId(), position, false, false, null, false, 0, 6, 30, false, null, null);
+    }
+
+    /**
+     * Creates  a Calendar object representing the time of the given alarm (not taking the
+     * day/date into account.
+     * @param alarm
+     * @return
+     */
+    public static Calendar toCalendar(nodomain.freeyourgadget.gadgetbridge.model.Alarm alarm) {
+        Calendar result = Calendar.getInstance();
+        Calendar now = Calendar.getInstance();
+        result.set(Calendar.HOUR_OF_DAY, alarm.getHour());
+        result.set(Calendar.MINUTE, alarm.getMinute());
+        if (now.after(result) && alarm.getRepetition() == Alarm.ALARM_ONCE) {
+            //if the alarm is in the past set it to tomorrow
+            result.add(Calendar.DATE, 1);
+        }
+        return result;
+    }
+
+    /**
+     * Returns a repetition mask suitable for {@link Alarm#repetition}
+     * @param mon whether the alarm shall repeat every Monday
+     * @param tue whether the alarm shall repeat every Tuesday
+     * @param wed whether the alarm shall repeat every Wednesday
+     * @param thu whether the alarm shall repeat every Thursday
+     * @param fri whether the alarm shall repeat every Friday
+     * @param sat whether the alarm shall repeat every Saturday
+     * @param sun whether the alarm shall repeat every Sunday
+     * @return the created repetition mask
+     */
+    public static int createRepetitionMask(boolean mon, boolean tue, boolean wed, boolean thu, boolean fri, boolean sat, boolean sun) {
+        int repetitionMask = (mon ? Alarm.ALARM_MON : 0) |
+                (tue ? Alarm.ALARM_TUE : 0) |
+                (wed ? Alarm.ALARM_WED : 0) |
+                (thu ? Alarm.ALARM_THU : 0) |
+                (fri ? Alarm.ALARM_FRI : 0) |
+                (sat ? Alarm.ALARM_SAT : 0) |
+                (sun ? Alarm.ALARM_SUN : 0);
+        return repetitionMask;
+    }
+
+    /**
+     * Just for backward compatibility, do not call in new code.
+     * @param gbDevice
+     * @return
+     * @deprecated use {@link DBHelper#getAlarms(GBDevice)} instead
+     */
+    @NonNull
+    public static List<Alarm> readAlarmsFromPrefs(GBDevice gbDevice) {
+        Prefs prefs = GBApplication.getPrefs();
+        Set<String> stringAlarms = prefs.getStringSet(MiBandConst.PREF_MIBAND_ALARMS, new HashSet<String>());
+        List<Alarm> alarms = new ArrayList<>(stringAlarms.size());
+
+        try (DBHandler db = GBApplication.acquireDB()) {
+            DaoSession daoSession = db.getDaoSession();
+            User user = DBHelper.getUser(daoSession);
+            Device device = DBHelper.getDevice(gbDevice, daoSession);
+            for (String stringAlarm : stringAlarms) {
+                alarms.add(createAlarmFromPreference(stringAlarm, device, user));
+            }
+            Collections.sort(alarms, AlarmUtils.createComparator());
+            return alarms;
+        } catch (Exception e) {
+            GB.log("Error accessing database", GB.ERROR, e);
+            return Collections.emptyList();
+        }
+    }
+
+    private static Alarm createAlarmFromPreference(String fromPreferences, Device device, User user) {
+        String[] tokens = fromPreferences.split(",");
+        int index = Integer.parseInt(tokens[0]);
+        boolean enabled = Boolean.parseBoolean(tokens[1]);
+        boolean smartWakeup = Boolean.parseBoolean(tokens[2]);
+        int repetition = Integer.parseInt(tokens[3]);
+        int hour = Integer.parseInt(tokens[4]);
+        int minute = Integer.parseInt(tokens[5]);
+
+        return new Alarm(device.getId(), user.getId(), index, enabled, smartWakeup, null, false, repetition, hour, minute, false, null, null);
+    }
+
+    private static Comparator<Alarm> createComparator() {
+        return new Comparator<Alarm>() {
+
+            @Override
+            public int compare(Alarm o1, Alarm o2) {
+                int p1 = o1.getPosition();
+                int p2 = o2.getPosition();
+
+                return Integer.compare(p1, p2);
+            }
+        };
+    }
+
+    public static List<Alarm> mergeOneshotToDeviceAlarms(GBDevice gbDevice, Alarm oneshot, int position) {
+        List<Alarm> all_alarms = new ArrayList<>();
+        try {
+            DBHandler db = GBApplication.acquireDB();
+            DaoSession daoSession = db.getDaoSession();
+            Device device = DBHelper.getDevice(gbDevice, daoSession);
+            User user = DBHelper.getUser(daoSession);
+            oneshot.setPosition(position);
+            oneshot.setDeviceId(device.getId());
+            oneshot.setUserId(user.getId());
+            daoSession.insertOrReplace(oneshot);
+            all_alarms = DBHelper.getAlarms(gbDevice);
+            GBApplication.releaseDB();
+        } catch (Exception e) {
+            GB.log("error storing one shot quick alarm", GB.ERROR, e);
+        }
+        return all_alarms;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/AndroidUtils.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/AndroidUtils.java
new file mode 100644
index 0000000..8ce2ca3
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/AndroidUtils.java
@@ -0,0 +1,355 @@
+/*  Copyright (C) 2016-2024 Andreas Shimokawa, Arjan Schrijver, Carsten
+    Pfeiffer, Daniel Dakhno, Daniele Gobbetti, Felix Konstantin Maurer, Jos茅
+    Rebelo, Marc Nause, Petr Van臎k, Taavi Eom盲e
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.util;
+
+import android.annotation.SuppressLint;
+import android.content.ActivityNotFoundException;
+import android.content.BroadcastReceiver;
+import android.content.ContentUris;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.content.res.TypedArray;
+import android.database.Cursor;
+import android.graphics.Color;
+import android.net.Uri;
+import android.os.Environment;
+import android.os.ParcelUuid;
+import android.os.Parcelable;
+import android.os.PowerManager;
+import android.provider.DocumentsContract;
+import android.provider.MediaStore;
+import android.text.TextUtils;
+import android.widget.Toast;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.core.content.FileProvider;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import com.google.android.material.color.DynamicColors;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.util.Locale;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+
+public class AndroidUtils {
+    private static final Logger LOG = LoggerFactory.getLogger(AndroidUtils.class);
+
+    /**
+     * Creates a new {@link ParcelUuid} array with the contents of the given uuids.
+     * The given array is expected to contain only {@link ParcelUuid} elements.
+     * @param uuids an array of {@link ParcelUuid} elements
+     * @return a {@link ParcelUuid} array instance with the same contents
+     */
+    public static ParcelUuid[] toParcelUuids(Parcelable[] uuids) {
+        if (uuids == null) {
+            return null;
+        }
+        ParcelUuid[] uuids2 = new ParcelUuid[uuids.length];
+        System.arraycopy(uuids, 0, uuids2, 0, uuids.length);
+        return uuids2;
+    }
+
+    /**
+     * Unregisters the given receiver from the given context.
+     * @param context the context from which to unregister
+     * @param receiver the receiver to unregister
+     * @return true if it was successfully unregistered, or false if the receiver was not registered
+     */
+    public static boolean safeUnregisterBroadcastReceiver(Context context, BroadcastReceiver receiver) {
+        try {
+            context.unregisterReceiver(receiver);
+            return true;
+        } catch (IllegalArgumentException ex) {
+            return false;
+        }
+    }
+
+    /**
+     * Unregisters the given receiver from the given {@link LocalBroadcastManager}.
+     * @param manager the manager  from which to unregister
+     * @param receiver the receiver to unregister
+     * @return true if it was successfully unregistered, or false if the receiver was not registered
+     */
+    public static boolean safeUnregisterBroadcastReceiver(LocalBroadcastManager manager, BroadcastReceiver receiver) {
+        try {
+            manager.unregisterReceiver(receiver);
+            return true;
+        } catch (IllegalArgumentException ex) {
+            return false;
+        }
+    }
+
+    public static void setLanguage(Context context, Locale language) {
+        Configuration config = new Configuration();
+        config.setLocale(language);
+
+        // FIXME: I have no idea what I am doing
+        context.getResources().updateConfiguration(config, context.getResources().getDisplayMetrics());
+
+        Locale.setDefault(language);
+    }
+
+    /**
+     * Returns the theme dependent text color as a css-style hex string.
+     * @param context the context to access the colour
+     */
+    public static String getTextColorHex(Context context) {
+        int color;
+        if (DynamicColors.isDynamicColorAvailable() && GBApplication.areDynamicColorsEnabled()) {
+            Context dynamicColorContext;
+            if (GBApplication.isDarkThemeEnabled()) {
+                dynamicColorContext = DynamicColors.wrapContextIfAvailable(context, R.style.GadgetbridgeThemeDynamicDark);
+            } else {
+                dynamicColorContext = DynamicColors.wrapContextIfAvailable(context, R.style.GadgetbridgeThemeDynamicLight);
+            }
+            int[] attrsToResolve = {com.google.android.material.R.attr.colorOnSurface};
+            @SuppressLint("ResourceType")
+            TypedArray ta = dynamicColorContext.obtainStyledAttributes(attrsToResolve);
+            color = ta.getColor(0, 0);
+            ta.recycle();
+        } else if (GBApplication.isDarkThemeEnabled()) {
+            color = context.getResources().getColor(R.color.primarytext_dark);
+        } else {
+            color = context.getResources().getColor(R.color.primarytext_light);
+        }
+        return colorToHex(color);
+    }
+
+    /**
+     * Returns the theme dependent background color as a css-style hex string.
+     * @param context the context to access the colour
+     */
+    public static String getBackgroundColorHex(Context context) {
+        int color;
+        if (DynamicColors.isDynamicColorAvailable() && GBApplication.areDynamicColorsEnabled()) {
+            Context dynamicColorContext;
+            if (GBApplication.isDarkThemeEnabled()) {
+                dynamicColorContext = DynamicColors.wrapContextIfAvailable(context, R.style.GadgetbridgeThemeDynamicDark);
+            } else {
+                dynamicColorContext = DynamicColors.wrapContextIfAvailable(context, R.style.GadgetbridgeThemeDynamicLight);
+            }
+            int[] attrsToResolve = {com.google.android.material.R.attr.colorSurface};
+            @SuppressLint("ResourceType")
+            TypedArray ta = dynamicColorContext.obtainStyledAttributes(attrsToResolve);
+            color = ta.getColor(0, 0);
+            ta.recycle();
+        } else if (GBApplication.isDarkThemeEnabled()) {
+            color = context.getResources().getColor(androidx.cardview.R.color.cardview_dark_background);
+        } else {
+            color = context.getResources().getColor(androidx.cardview.R.color.cardview_light_background);
+        }
+        return colorToHex(color);
+    }
+
+    public static int getBackgroundColor(Context context) {
+        int color;
+        if (GBApplication.isDarkThemeEnabled()) {
+            color = context.getResources().getColor(androidx.cardview.R.color.cardview_dark_background);
+        } else {
+            color = context.getResources().getColor(androidx.cardview.R.color.cardview_light_background);
+        }
+        return color;
+    }
+
+
+    private static String colorToHex(int color) {
+        return "#"
+                + Integer.toHexString(Color.red(color))
+                + Integer.toHexString(Color.green(color))
+                + Integer.toHexString(Color.blue(color));
+    }
+
+    /**
+     * As seen on StackOverflow https://stackoverflow.com/a/36714242/1207186
+     * Try to find the file path of a document uri
+     *
+     * @param context the application context
+     * @param uri     the Uri for which the path should be resolved
+     * @return the path corresponding to the Uri as a String
+     * @throws IllegalArgumentException on any problem decoding the uri to a path
+     */
+    public static @NonNull String getFilePath(@NonNull Context context, @NonNull Uri uri) throws IllegalArgumentException {
+        try {
+            String path = internalGetFilePath(context, uri);
+            if (TextUtils.isEmpty(path)) {
+                throw new IllegalArgumentException("Unable to decode the given uri to a file path: " + uri);
+            }
+            return path;
+        } catch (IllegalArgumentException ex) {
+            throw ex;
+        } catch (Exception ex) {
+            throw new IllegalArgumentException("Unable to decode the given uri to a file path: " + uri, ex);
+        }
+    }
+
+    /**
+     * As seen on StackOverflow https://stackoverflow.com/a/36714242/1207186
+     * Try to find the file path of a document uri
+     * @param context the application context
+     * @param uri the Uri for which the path should be resolved
+     * @return the path corresponding to the Uri as a String
+     * @throws URISyntaxException
+    */
+    private static @Nullable String internalGetFilePath(@NonNull Context context, @NonNull Uri uri) throws URISyntaxException {
+        String selection = null;
+        String[] selectionArgs = null;
+
+        if (DocumentsContract.isDocumentUri(context.getApplicationContext(), uri)) {
+            if ("com.android.externalstorage.documents".equals(uri.getAuthority())) {
+                final String docId = DocumentsContract.getDocumentId(uri);
+                final String[] split = docId.split(":");
+                return Environment.getExternalStorageDirectory() + "/" + split[1];
+            } else if ("com.android.providers.downloads.documents".equals(uri.getAuthority())) {
+                final String id = DocumentsContract.getDocumentId(uri);
+                if (!TextUtils.isEmpty(id)) {
+                    if (id.startsWith("raw:")) {
+                        return id.replaceFirst("raw:", "");
+                    }
+                    uri = ContentUris.withAppendedId(
+                            Uri.parse("content://downloads/public_downloads"), Long.parseLong(id));
+                }
+            } else if ("com.android.providers.media.documents".equals(uri.getAuthority())) {
+                final String docId = DocumentsContract.getDocumentId(uri);
+                final String[] split = docId.split(":");
+                final String type = split[0];
+                if ("image".equals(type)) {
+                    uri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
+                } else if ("video".equals(type)) {
+                    uri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
+                } else if ("audio".equals(type)) {
+                    uri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
+                }
+                selection = "_id=?";
+                selectionArgs = new String[]{
+                        split[1]
+                };
+            }
+        }
+        if ("content".equalsIgnoreCase(uri.getScheme())) {
+            String[] projection = {
+                    MediaStore.Images.Media.DATA
+            };
+            Cursor cursor = context.getContentResolver()
+                    .query(uri, projection, selection, selectionArgs, null);
+            int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
+            if (cursor.moveToFirst()) {
+                return cursor.getString(column_index);
+            }
+        } else if ("file".equalsIgnoreCase(uri.getScheme())) {
+            return uri.getPath();
+        }
+        throw new IllegalArgumentException("Unable to decode the given uri to a file path: " + uri);
+    }
+
+    public static void viewFile(String path, String mimeType, Context context) throws IOException {
+        Intent intent = new Intent(Intent.ACTION_VIEW);
+        File file = new File(path);
+
+        Uri contentUri = FileProvider.getUriForFile(context,
+                context.getApplicationContext().getPackageName() + ".screenshot_provider", file);
+        intent.setFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
+        intent.setDataAndType(contentUri, mimeType);
+        try {
+            context.startActivity(intent);
+        } catch (ActivityNotFoundException e) {
+            Toast.makeText(context, R.string.activity_error_no_app_for_gpx, Toast.LENGTH_LONG).show();
+        }
+    }
+
+    public static void shareBytesAsFile(final Context context, final String name, final byte[] bytes) throws IOException {
+        final File cacheDir = context.getCacheDir();
+        final File rawCacheDir = new File(cacheDir, "raw");
+        rawCacheDir.mkdir();
+        final File file = new File(rawCacheDir, name);
+
+        try (FileOutputStream outputStream = new FileOutputStream(file)) {
+            outputStream.write(bytes);
+        } catch (final IOException e) {
+            LOG.error("Failed to write bytes to temporary file", e);
+            return;
+        }
+
+        shareFile(context, file);
+    }
+
+    public static void shareFile(final Context context, final File file) throws IOException {
+        if (!file.exists()) {
+            LOG.warn("File {} does not exist", file.getPath());
+            return;
+        }
+
+        shareFile(context, file, "*/*");
+    }
+
+    public static void shareFile(final Context context, final File file, final String type) throws IOException {
+        final Uri contentUri = FileProvider.getUriForFile(
+                context,
+                context.getApplicationContext().getPackageName() + ".screenshot_provider",
+                file
+        );
+
+        final Intent intent = new Intent(Intent.ACTION_SEND);
+        intent.setType(type);
+        intent.putExtra(Intent.EXTRA_STREAM, contentUri);
+        intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+        try {
+            context.startActivity(Intent.createChooser(intent, "Share file"));
+        } catch (ActivityNotFoundException e) {
+            Toast.makeText(context, R.string.activity_error_share_failed, Toast.LENGTH_LONG).show();
+        }
+    }
+
+    public static void openWebsite(String url){
+        Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
+        i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
+        GBApplication.getContext().startActivity(i);
+    }
+
+    public static void openApp(String packageName) throws ClassNotFoundException {
+        Context context = GBApplication.getContext();
+
+        Intent launchIntent = context.getPackageManager().getLaunchIntentForPackage(packageName);
+        if(launchIntent == null){
+            throw new ClassNotFoundException("App " + packageName + " cannot be found");
+        }
+        GBApplication.getContext().startActivity(launchIntent);
+    }
+
+    public static PowerManager.WakeLock acquirePartialWakeLock(Context context, String tag, long timeout) {
+        try {
+            PowerManager powermanager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+            PowerManager.WakeLock wl = powermanager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Gadgetbridge:" + tag);
+            wl.acquire(timeout);
+            return wl;
+        } catch (final Exception e) {
+            LOG.error("Failed to take partial wake lock {}: ", tag, e);
+            return null;
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/ArrayUtils.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/ArrayUtils.java
new file mode 100644
index 0000000..cb68d12
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/ArrayUtils.java
@@ -0,0 +1,123 @@
+/*  Copyright (C) 2016-2024 Andreas Shimokawa, Carsten Pfeiffer, Petr Kadlec,
+    Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.util;
+
+import java.util.Collection;
+
+public class ArrayUtils {
+    /**
+     * Checks the two given arrays for equality, but comparing the second array with a specified
+     * subset of the first array.
+     *
+     * @param first            the array in which to look for the second array
+     * @param second           the data to look for inside the first array
+     * @param startIndex the start index (inclusive) inside the first array from which to start the comparison
+     * @return whether the second byte array is equal to the specified subset of the first byte array
+     * @throws IllegalArgumentException when one of the arrays is null or start index/length are wrong
+     */
+    public static boolean equals(byte[] first, byte[] second, int startIndex) {
+        if (first == null) {
+            throw new IllegalArgumentException("first must not be null");
+        }
+        if (second == null) {
+            throw new IllegalArgumentException("second must not be null");
+        }
+        if (startIndex < 0) {
+            throw new IllegalArgumentException("startIndex must be >= 0");
+        }
+
+        if (second.length + startIndex > first.length) {
+            return false;
+        }
+        for (int i = 0; i < second.length; i++) {
+            if (first[startIndex + i] != second[i]) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Converts a collection of Integer values to an int[] array.
+     * @param values
+     * @return null if the given collection is null, otherwise an array of the same size as the collection
+     * @throws NullPointerException when an element of the collection is null
+     */
+    public static int[] toIntArray(Collection<Integer> values) {
+        if (values == null) {
+            return null;
+        }
+        int i = 0;
+        int[] result = new int[values.size()];
+        for (Integer value : values) {
+            result[i++] = value;
+        }
+        return result;
+    }
+
+    /**
+     * Returns true if the given byte array starts with the given values
+     * @param array the array to check
+     * @param values the values which the other array is checked to start with
+     * @return
+     */
+    public static boolean startsWith(byte[] array, byte[] values) {
+        return equals(array, values, 0);
+    }
+
+    /**
+     * Converts an array to string representation
+     *
+     * @param array the array to convert
+     * @return
+     */
+    public static String arrayToString(byte[] array) {
+        StringBuilder stringBuilder = new StringBuilder();
+        for (byte i : array) {
+            stringBuilder.append(String.format("0x%02X ", i));
+        }
+        return stringBuilder.toString();
+    }
+
+
+    /**
+     * Finds a value in a byte array
+     * @param value the value to find in the array
+     * @param array the array to search
+     * @return index of the first occurrence of the value in the array, -1 if the array does not contain the value
+     */
+    public static int indexOf(byte value, byte[] array) {
+        return indexOf(value, array, 0, array.length);
+    }
+
+    /**
+     * Finds a value in a byte array
+     * @param value  the value to find in the array
+     * @param array  the array to search
+     * @param offset initial offset in the array to be searched (0 = start at the beginning)
+     * @param size   number of bytes to search beginning at the given offset
+     * @return index of the first occurrence of the value in the array (from the beginning of the array, i.e. not from
+     * the offset position), -1 if the array does not contain the value
+     */
+    public static int indexOf(byte value, byte[] array, int offset, int size) {
+        for (int i = offset; i < offset + size; ++i) {
+            if (array[i] == value) return i;
+        }
+        return -1;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/BcdUtil.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/BcdUtil.java
new file mode 100644
index 0000000..df6126e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/BcdUtil.java
@@ -0,0 +1,33 @@
+/*  Copyright (C) 2020-2024 Andreas B枚hler
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.util;
+
+import androidx.annotation.IntRange;
+
+public class BcdUtil {
+    public static byte toBcd8(@IntRange(from = 0, to = 99) int value) {
+        int high = (value / 10) << 4;
+        int low = value % 10;
+        return (byte) (high | low);
+    }
+
+    public static int fromBcd8(byte value) {
+        int high = ((value & 0xF0) >> 4) * 10;
+        int low = value & 0x0F;
+        return high + low;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/BitmapUtil.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/BitmapUtil.java
new file mode 100644
index 0000000..b0fefef
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/BitmapUtil.java
@@ -0,0 +1,397 @@
+/*  Copyright (C) 2017-2024 Arjan Schrijver, Frank Slezak, HelloCodeberg,
+    Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+
+package nodomain.freeyourgadget.gadgetbridge.util;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.ColorMatrix;
+import android.graphics.ColorMatrixColorFilter;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+
+import androidx.annotation.Nullable;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+public class BitmapUtil {
+    private static final Logger LOG = LoggerFactory.getLogger(BitmapUtil.class);
+
+    /**
+     * Downscale a bitmap to a maximum resolution. Doesn't scale if the bitmap is already smaller than the max resolution.
+     *
+     * @param bitmap
+     * @param maxWidth
+     * @param maxHeight
+     * @return
+     */
+    public static Bitmap scaleWithMax(Bitmap bitmap, int maxWidth, int maxHeight) {
+        // Scale image only if necessary
+        if ((bitmap.getWidth() > maxWidth) || (bitmap.getHeight() > maxHeight)) {
+            bitmap = Bitmap.createScaledBitmap(bitmap, maxWidth, maxHeight, true);
+        }
+        return bitmap;
+    }
+
+    /**
+     * Get a Bitmap from any given Drawable.
+     *
+     * Note that this code will fail if the drawable is 0x0.
+     *
+     * @param drawable A Drawable to convert.
+     * @return A Bitmap representing the drawable.
+     */
+    public static Bitmap convertDrawableToBitmap(Drawable drawable) {
+        // If whoever made this drawable decided to be nice to us...
+        if (drawable instanceof BitmapDrawable) {
+            return ((BitmapDrawable) drawable).getBitmap();
+        }
+
+        Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
+        Canvas canvas = new Canvas(bitmap);
+
+        drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
+        drawable.draw(canvas);
+
+        return bitmap;
+    }
+
+    /**
+     * Converts the provided Bitmap to grayscale.
+     *
+     * @param bitmap
+     * @return
+     */
+    public static Bitmap convertToGrayscale(Bitmap bitmap) {
+        Canvas c = new Canvas(bitmap);
+        Paint paint = new Paint();
+        ColorMatrix cm = new ColorMatrix();
+        cm.setSaturation(0);
+        ColorMatrixColorFilter f = new ColorMatrixColorFilter(cm);
+        paint.setColorFilter(f);
+        c.drawBitmap(bitmap, 0, 0, paint);
+        return bitmap;
+    }
+
+    /**
+     * Change the contrast and brightness on a Bitmap
+     *
+     * Code from: https://stackoverflow.com/questions/12891520/how-to-programmatically-change-contrast-of-a-bitmap-in-android#17887577
+     *
+     * @param bmp input bitmap
+     * @param contrast 0..10 1 is default
+     * @param brightness -255..255 0 is default
+     * @return new bitmap
+     */
+    public static Bitmap changeBitmapContrastBrightness(Bitmap bmp, float contrast, float brightness)
+    {
+        ColorMatrix cm = new ColorMatrix(new float[]
+                {
+                        contrast, 0, 0, 0, brightness,
+                        0, contrast, 0, 0, brightness,
+                        0, 0, contrast, 0, brightness,
+                        0, 0, 0, 1, 0
+                });
+
+        Bitmap ret = Bitmap.createBitmap(bmp.getWidth(), bmp.getHeight(), bmp.getConfig());
+
+        Canvas canvas = new Canvas(ret);
+
+        Paint paint = new Paint();
+        paint.setColorFilter(new ColorMatrixColorFilter(cm));
+        canvas.drawBitmap(bmp, 0, 0, paint);
+
+        return ret;
+    }
+
+
+    /**
+     * Invert the colors of a Bitmap
+     *
+     * @param bmp input bitmap
+     * @return new bitmap
+     */
+    public static Bitmap invertBitmapColors(Bitmap bmp)
+    {
+        ColorMatrix colorMatrix_Inverted =
+                new ColorMatrix(new float[] {
+                        -1,  0,  0,  0, 255,
+                        0, -1,  0,  0, 255,
+                        0,  0, -1,  0, 255,
+                        0,  0,  0,  1,   0});
+
+        Bitmap ret = Bitmap.createBitmap(bmp.getWidth(), bmp.getHeight(), bmp.getConfig());
+
+        Canvas canvas = new Canvas(ret);
+
+        Paint paint = new Paint();
+        paint.setColorFilter(new ColorMatrixColorFilter(colorMatrix_Inverted));
+        canvas.drawBitmap(bmp, 0, 0, paint);
+
+        return ret;
+    }
+
+
+    /**
+     * Crops a circular image from the center of the provided Bitmap.
+     * From: https://www.tutorialspoint.com/android-how-to-crop-circular-area-from-bitmap
+     * @param srcBitmap
+     * @return
+     */
+    public static Bitmap getCircularBitmap(Bitmap srcBitmap) {
+        // Calculate the circular bitmap width with border
+        int squareBitmapWidth = Math.min(srcBitmap.getWidth(), srcBitmap.getHeight());
+        // Initialize a new instance of Bitmap
+        Bitmap dstBitmap = Bitmap.createBitmap (
+                squareBitmapWidth, // Width
+                squareBitmapWidth, // Height
+                Bitmap.Config.ARGB_8888 // Config
+        );
+        Canvas canvas = new Canvas(dstBitmap);
+        // Initialize a new Paint instance
+        Paint paint = new Paint();
+        paint.setAntiAlias(true);
+        Rect rect = new Rect(0, 0, squareBitmapWidth, squareBitmapWidth);
+        RectF rectF = new RectF(rect);
+        canvas.drawOval(rectF, paint);
+        paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
+        // Calculate the left and top of copied bitmap
+        float left = (squareBitmapWidth-srcBitmap.getWidth())/2;
+        float top = (squareBitmapWidth-srcBitmap.getHeight())/2;
+        canvas.drawBitmap(srcBitmap, left, top, paint);
+        // Return the circular bitmap
+        return dstBitmap;
+    }
+
+    /**
+     * Rotates a given Bitmap
+     * @param bitmap input bitmap
+     * @param degree int Degree of rotation
+     * @return new bitmap
+     */
+    public static Bitmap rotateImage(Bitmap bitmap, int degree) {
+        Matrix matrix = new Matrix();
+        matrix.postRotate(degree);
+        return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
+    }
+
+
+    /**
+     * Overlays two bitmaps on top of each other,
+     * bmp1 is assumed to be larger or equal to bmp2
+     * From: https://stackoverflow.com/a/2287218
+     * @param bmp1
+     * @param bmp2
+     * @return new Bitmap
+     */
+    public static Bitmap overlay(Bitmap bmp1, Bitmap bmp2) {
+        Bitmap bmOverlay = Bitmap.createBitmap(bmp1.getWidth(), bmp1.getHeight(), bmp1.getConfig());
+        Canvas canvas = new Canvas(bmOverlay);
+        canvas.drawBitmap(bmp1, new Matrix(), null);
+        canvas.drawBitmap(bmp2, new Matrix(), null);
+        return bmOverlay;
+    }
+
+    /**
+     * Converts a {@link Drawable} to a {@link Bitmap}, in ARGB8888 mode.
+     *
+     * @param drawable the {@link Drawable}
+     * @return the {@link Bitmap}
+     */
+    public static Bitmap toBitmap(final Drawable drawable) {
+        if (drawable instanceof BitmapDrawable) {
+            return ((BitmapDrawable) drawable).getBitmap();
+        }
+
+        final Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
+        final Canvas canvas = new Canvas(bitmap);
+        drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
+        drawable.draw(canvas);
+        return bitmap;
+    }
+
+    /**
+     * Converts a {@link Bitmap} to an uncompressed TGA image, as raw bytes, in RGB565 encoding.
+     * @param bmp the {@link Bitmap} to convert.
+     * @param width the target width
+     * @param height the target height
+     * @param id the TGA ID
+     * @return the raw bytes for the TGA image
+     */
+    public static byte[] convertToTgaRGB565(final Bitmap bmp, final int width, final int height, final byte[] id) {
+        final Bitmap bmp565;
+        if (bmp.getConfig().equals(Bitmap.Config.RGB_565) && bmp.getWidth() == width && bmp.getHeight() == height) {
+            // Right encoding and size
+            bmp565 = bmp;
+        } else {
+            // Convert encoding / scale
+            bmp565 = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565);
+            final Canvas canvas = new Canvas(bmp565);
+            final Rect rect = new Rect(0, 0, width, height);
+            canvas.drawBitmap(bmp, null, rect, null);
+        }
+
+        int size = bmp565.getRowBytes() * bmp565.getHeight();
+        final ByteBuffer bmp565buf = ByteBuffer.allocate(size);
+        bmp565.copyPixelsToBuffer(bmp565buf);
+
+        // As per https://en.wikipedia.org/wiki/Truevision_TGA
+        // 18 bytes
+        final byte[] header = {
+                // ID length
+                (byte) id.length,
+                // Color map type - (0 - no color map)
+                0x00,
+                // Image type (2 - uncompressed true-color image)
+                0x02,
+                // Color map specification (5 bytes)
+                0x00, 0x00, // first entry index
+                0x00, 0x00, /// color map length
+                0x00, // color map entry size
+                // Image dimensions and format (10 bytes)
+                0x00, 0x00, // x origin
+                0x00, 0x00, // y origin
+                (byte) (width & 0xff), (byte) ((width >> 8) & 0xff), // width
+                (byte) (height & 0xff), (byte) ((height >> 8) & 0xff), // height
+                16, // bits per pixel (10)
+                0x20, // image descriptor (0x20, 00100000)
+                // bits 3-0 give the alpha channel depth, bits 5-4 give pixel ordering
+                // Bit 4 of the image descriptor byte indicates right-to-left pixel ordering if set.
+                // Bit 5 indicates an ordering of top-to-bottom. Otherwise, pixels are stored in bottom-to-top, left-to-right order.
+        };
+
+        final ByteBuffer tga565buf = ByteBuffer.allocate(header.length + id.length + size);
+
+        tga565buf.put(header);
+        tga565buf.put(id);
+        tga565buf.put(bmp565buf.array());
+
+        return tga565buf.array();
+    }
+
+    public static boolean isPng(final byte[] data) {
+        return ArrayUtils.equals(data, new byte[] {(byte) 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a}, 0);
+    }
+
+    @Nullable
+    public static Bitmap decodeTga(final byte[] bytes) {
+        final ByteBuffer buf = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN);
+        final byte idLength = buf.get();
+        final byte colorMapType = buf.get();
+        final byte imageType = buf.get();
+        final short colorMapFirstEntryIndex = buf.getShort();
+        final short colorMapLength = buf.getShort();
+        final byte colorMapEntrySize = buf.get();
+        final short xOrigin = buf.getShort();
+        final short yOrigin = buf.getShort();
+        final short width = buf.getShort();
+        final short height = buf.getShort();
+        final byte bitsPerPixel = buf.get();
+        final byte imageDescriptor = buf.get();
+
+        final byte[] id = new byte[idLength];
+        buf.get(id);
+
+        if (imageType== 2 && colorMapType == 0) {
+            // Parse true-color uncompressed image data as RGB565
+            final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565);
+            bitmap.copyPixelsFromBuffer(buf);
+
+            return bitmap;
+        }
+
+        if (colorMapType != 0x01) {
+            LOG.warn("Unknown color map type {}", colorMapType);
+            return null;
+        }
+
+        if (colorMapEntrySize != 32) {
+            LOG.warn("Color map entry size {} not supported", colorMapEntrySize);
+            return null;
+        }
+
+        final int[] colorMap = new int[colorMapLength];
+        for (int i = 0; i < colorMapLength; i++) {
+            colorMap[i] = buf.getInt();
+        }
+
+        final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+
+        switch (imageType) {
+            case 1:
+                if (bitsPerPixel != 8) {
+                    LOG.warn("Unsupported bits per pixel {} for imageType 1", bitsPerPixel);
+                    return null;
+                }
+
+                // uncompressed color-mapped image
+                for (int y = 0; y < height; y++) {
+                    for (int x = 0; x < width; x++) {
+                        bitmap.setPixel(x, y, colorMap[buf.get() & 0xff]);
+                    }
+                }
+                break;
+            case 9:
+                // run-length encoded color-mapped image
+                int i = 0;
+                while (buf.position() < buf.limit()) {
+                    final byte b = buf.get();
+                    final int count = (b & 127) + 1;
+                    if ((b & 128) != 0) {
+                        // msb 1 - run-length encoded
+                        final int val = buf.get() & 0xff;
+                        for (int j = 0; j < count; j++) {
+                            int y = i / width;
+                            int x = i % width;
+                            bitmap.setPixel(x, y, colorMap[val]);
+                            i++;
+                        }
+                    } else {
+                        // msb 0 - raw pixels
+                        for (int j = 0; j < count; j++) {
+                            int y = i / width;
+                            int x = i % width;
+                            bitmap.setPixel(x, y, colorMap[buf.get() & 0xff]);
+                            i++;
+                        }
+                    }
+                }
+                break;
+            default:
+                LOG.warn("Image type {} not supported", imageType);
+                return null;
+        }
+
+        final int remainingBytes = buf.limit() - buf.position();
+        if (remainingBytes != 0) {
+            LOG.warn("There are {} bytes remaining in the buffer", remainingBytes);
+        }
+
+        return bitmap;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/BondingInterface.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/BondingInterface.java
new file mode 100644
index 0000000..f0f6fe9
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/BondingInterface.java
@@ -0,0 +1,54 @@
+/*  Copyright (C) 2020-2024 Andreas B枚hler, Taavi Eom盲e
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.util;
+
+
+import android.content.Context;
+
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
+
+public interface BondingInterface {
+    /**
+     * Called when pairing is complete
+     **/
+    void onBondingComplete(boolean success);
+
+    /**
+     * Should return the device that is currently being paired
+     **/
+    GBDeviceCandidate getCurrentTarget();
+
+    /**
+     * This forces bonding activities to encapsulate the removal
+     * of all broadcast receivers on demand
+     **/
+    void unregisterBroadcastReceivers();
+
+    String getMacAddress();
+
+    boolean getAttemptToConnect();
+    /**
+     * This forces bonding activities to handle the addition
+     * of all broadcast receivers in the same place
+     **/
+    void registerBroadcastReceivers();
+
+    /**
+     * Just returns the Context
+     */
+    Context getContext();
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/BondingUtil.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/BondingUtil.java
new file mode 100644
index 0000000..a4d1937
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/BondingUtil.java
@@ -0,0 +1,441 @@
+/*  Copyright (C) 2020-2024 Andreas B枚hler, Arjan Schrijver, Daniel Dakhno,
+    Jos茅 Rebelo, Taavi Eom盲e
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.util;
+
+import static androidx.core.app.ActivityCompat.startIntentSenderForResult;
+import static nodomain.freeyourgadget.gadgetbridge.util.GB.toast;
+
+import android.app.Activity;
+import android.bluetooth.BluetoothClass;
+import android.bluetooth.BluetoothDevice;
+import android.companion.AssociationRequest;
+import android.companion.BluetoothDeviceFilter;
+import android.companion.CompanionDeviceManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentSender;
+import android.os.Build;
+import android.os.Handler;
+import android.os.Looper;
+import android.widget.Toast;
+
+import androidx.annotation.RequiresApi;
+
+import com.google.android.material.dialog.MaterialAlertDialogBuilder;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Locale;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
+
+public class BondingUtil {
+    public static final String STATE_DEVICE_CANDIDATE = "stateDeviceCandidate";
+
+    private static final int REQUEST_CODE = 1;
+    private static final Logger LOG = LoggerFactory.getLogger(BondingUtil.class);
+    private static final long DELAY_AFTER_BONDING = 1000; // 1s
+
+    /**
+     * Returns a BroadcastReceiver that handles Gadgetbridge's device changed broadcasts
+     */
+    public static BroadcastReceiver getPairingReceiver(final BondingInterface activity) {
+        return new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                if (GBDevice.ACTION_DEVICE_CHANGED.equals(intent.getAction())) {
+                    GBDevice device = intent.getParcelableExtra(GBDevice.EXTRA_DEVICE);
+                    LOG.debug("Pairing receiver: device changed: " + device);
+                    if (activity.getMacAddress().equals(device.getAddress())) {
+                        if (device.isInitialized()) {
+                            LOG.info("Device is initialized, finish things up");
+                            activity.onBondingComplete(true);
+                        } else if (device.isConnecting() || device.isInitializing()) {
+                            LOG.info("Still connecting/initializing device...");
+                        }
+                    }
+                }
+            }
+        };
+    }
+
+    /**
+     * Returns a BroadcastReceiver that handles Bluetooth chance broadcasts
+     */
+    public static BroadcastReceiver getBondingReceiver(final BondingInterface bondingInterface) {
+        return new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                if (BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(intent.getAction())) {
+                    BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+                    String bondingMacAddress = bondingInterface.getMacAddress();
+
+                    LOG.info("Bond state changed: " + device + ", state: " + device.getBondState() + ", expected address: " + bondingMacAddress);
+                    if (bondingMacAddress != null && bondingMacAddress.equals(device.getAddress())) {
+                        int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.BOND_NONE);
+                        switch (bondState) {
+                            case BluetoothDevice.BOND_BONDED: {
+                                LOG.info("Bonded with " + device.getAddress());
+                                //noinspection StatementWithEmptyBody
+                                if (isLePebble(device) || isPebble2(device) || !bondingInterface.getAttemptToConnect()) {
+                                    // Do not initiate connection to LE Pebble and some others!
+                                } else {
+                                    attemptToFirstConnect(bondingInterface.getCurrentTarget().getDevice());
+                                }
+                                return;
+                            }
+                            case BluetoothDevice.BOND_NONE: {
+                                LOG.info("Not bonded with " + device.getAddress() + ", attempting to connect anyway.");
+                                if(bondingInterface.getAttemptToConnect())
+                                    attemptToFirstConnect(bondingInterface.getCurrentTarget().getDevice());
+                                return;
+                            }
+                            case BluetoothDevice.BOND_BONDING: {
+                                LOG.info("Bonding in progress with " + device.getAddress());
+                                return;
+                            }
+                            default: {
+                                LOG.warn("Unknown bond state for device " + device.getAddress() + ": " + bondState);
+                                bondingInterface.onBondingComplete(false);
+                            }
+                        }
+                    }
+                }
+            }
+        };
+    }
+
+    /**
+     * Connect to candidate after a certain delay
+     *
+     * @param candidate the device to connect to
+     */
+    public static void attemptToFirstConnect(final BluetoothDevice candidate) {
+        Looper mainLooper = Looper.getMainLooper();
+        new Handler(mainLooper).postDelayed(new Runnable() {
+            @Override
+            public void run() {
+                GBApplication.deviceService().disconnect();
+                GBDevice device = DeviceHelper.getInstance().toSupportedDevice(candidate);
+                connectToGBDevice(device);
+            }
+        }, DELAY_AFTER_BONDING);
+    }
+
+    /**
+     * Just calls DeviceService connect with the "first time" flag
+     */
+    private static void connectToGBDevice(GBDevice device) {
+        if (device != null) {
+            GBApplication.deviceService(device).connect(true);
+        } else {
+            GB.toast("Unable to connect, can't recognize the device type", Toast.LENGTH_LONG, GB.ERROR);
+        }
+    }
+
+    /**
+     * Connects to the device and calls callback
+     */
+    public static void connectThenComplete(BondingInterface bondingInterface, GBDeviceCandidate deviceCandidate) {
+        GBDevice device = DeviceHelper.getInstance().toSupportedDevice(deviceCandidate);
+        connectThenComplete(bondingInterface, device);
+    }
+
+    /**
+     * Connects to the device and calls callback
+     */
+    public static void connectThenComplete(BondingInterface bondingInterface, GBDevice device) {
+        toast(bondingInterface.getContext(), bondingInterface.getContext().getString(R.string.discovery_trying_to_connect_to, device.getName()), Toast.LENGTH_SHORT, GB.INFO);
+        // Disconnect when LE Pebble so that the user can manually initiate a connection
+        GBApplication.deviceService(device).disconnect();
+        GBApplication.deviceService(device).connect(true);
+        bondingInterface.onBondingComplete(true);
+    }
+
+    /**
+     * Checks the type of bonding needed for the device and continues accordingly
+     */
+    public static void initiateCorrectBonding(final BondingInterface bondingInterface, final GBDeviceCandidate deviceCandidate, DeviceCoordinator coordinator) {
+        int bondingStyle = coordinator.getBondingStyle();
+        if (bondingStyle == DeviceCoordinator.BONDING_STYLE_NONE ||
+            bondingStyle == DeviceCoordinator.BONDING_STYLE_LAZY ) {
+            // Do nothing
+            return;
+        } else if (bondingStyle == DeviceCoordinator.BONDING_STYLE_ASK) {
+            new MaterialAlertDialogBuilder(bondingInterface.getContext())
+                    .setCancelable(true)
+                    .setTitle(bondingInterface.getContext().getString(R.string.discovery_pair_title, deviceCandidate.getName()))
+                    .setMessage(bondingInterface.getContext().getString(R.string.discovery_pair_question))
+                    .setPositiveButton(bondingInterface.getContext().getString(R.string.discovery_yes_pair), new DialogInterface.OnClickListener() {
+                        @Override
+                        public void onClick(DialogInterface dialog, int which) {
+                            BondingUtil.tryBondThenComplete(bondingInterface, deviceCandidate.getDevice(), deviceCandidate.getMacAddress());
+                        }
+                    })
+                    .setNegativeButton(R.string.discovery_dont_pair, new DialogInterface.OnClickListener() {
+                        @Override
+                        public void onClick(DialogInterface dialog, int which) {
+                            BondingUtil.connectThenComplete(bondingInterface, deviceCandidate);
+                        }
+                    })
+                    .show();
+        } else {
+            BondingUtil.tryBondThenComplete(bondingInterface, deviceCandidate.getDevice(), deviceCandidate.getMacAddress());
+        }
+        LOG.debug("Bonding initiated");
+    }
+
+    /**
+     * Tries to create a BluetoothDevice bond
+     * Do not call directly, use createBond(Activity, GBDeviceCandidate) instead!
+     */
+    private static void bluetoothBond(BondingInterface context, BluetoothDevice device) {
+        if (device.createBond()) {
+            // Async, results will be delivered via a broadcast
+            LOG.info("Bonding in progress...");
+        } else {
+            LOG.error(String.format(Locale.getDefault(),
+                    "Bonding failed immediately! %1$s (%2$s) %3$d",
+                    device.getName(),
+                    device.getAddress(),
+                    device.getType())
+            );
+
+            BluetoothClass bluetoothClass = device.getBluetoothClass();
+            if (bluetoothClass != null) {
+                LOG.error(String.format(Locale.getDefault(),
+                        "BluetoothClass: %1$s",
+                        bluetoothClass.toString()));
+            }
+
+            // Theoretically we shouldn't be doing this
+            // because this function shouldn't've been called
+            // with an already bonded device
+            if (device.getBondState() == BluetoothDevice.BOND_BONDED) {
+                LOG.warn("For some reason the device is already bonded, but let's try first connect");
+                attemptToFirstConnect(context.getCurrentTarget().getDevice());
+            } else if (device.getBondState() == BluetoothDevice.BOND_BONDING) {
+                LOG.warn("Device is still bonding after an error");
+                // TODO: Not sure we can handle this better, it's weird already.
+            } else {
+                LOG.warn("Bonding failed immediately and no bond was made");
+                toast(context.getContext(), context.getContext().getString(R.string.discovery_bonding_failed_immediately, device.getName()), Toast.LENGTH_SHORT, GB.ERROR);
+            }
+        }
+    }
+
+    /**
+     * Handles the activity result and checks if there's anything CompanionDeviceManager-related going on
+     */
+    public static void handleActivityResult(BondingInterface bondingInterface, int requestCode, int resultCode, Intent data) {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O &&
+                requestCode == BondingUtil.REQUEST_CODE &&
+                resultCode == Activity.RESULT_OK) {
+
+            BluetoothDevice deviceToPair =
+                    data.getParcelableExtra(CompanionDeviceManager.EXTRA_DEVICE);
+
+            if (deviceToPair != null) {
+                if (bondingInterface.getCurrentTarget().getDevice().getAddress().equals(deviceToPair.getAddress())) {
+                    if (deviceToPair.getBondState() != BluetoothDevice.BOND_BONDED) {
+                        BondingUtil.bluetoothBond(bondingInterface, bondingInterface.getCurrentTarget().getDevice());
+                    } else {
+                        bondingInterface.onBondingComplete(true);
+                    }
+                } else {
+                    bondingInterface.onBondingComplete(false);
+                }
+            }
+        }
+    }
+
+    /**
+     * Checks if device is LE Pebble
+     */
+    public static boolean isLePebble(BluetoothDevice device) {
+        return (device.getType() == BluetoothDevice.DEVICE_TYPE_DUAL || device.getType() == BluetoothDevice.DEVICE_TYPE_LE) &&
+                (device.getName().startsWith("Pebble-LE ") || device.getName().startsWith("Pebble Time LE "));
+    }
+
+    /**
+     * Checks if device is Pebble 2
+     */
+    public static boolean isPebble2(BluetoothDevice device) {
+        return device.getType() == BluetoothDevice.DEVICE_TYPE_LE &&
+                device.getName().startsWith("Pebble ") &&
+                !device.getName().startsWith("Pebble Time LE ");
+    }
+
+    /**
+     * Uses the CompanionDeviceManager bonding method
+     */
+    @RequiresApi(Build.VERSION_CODES.O)
+    private static void companionDeviceManagerBond(BondingInterface bondingInterface,
+                                                   BluetoothDevice device,
+                                                   String macAddress) {
+        BluetoothDeviceFilter deviceFilter = new BluetoothDeviceFilter.Builder()
+                .setAddress(macAddress)
+                .build();
+
+        AssociationRequest pairingRequest = new AssociationRequest.Builder()
+                .addDeviceFilter(deviceFilter)
+                .setSingleDevice(true)
+                .build();
+
+        CompanionDeviceManager manager = (CompanionDeviceManager) bondingInterface.getContext().getSystemService(Context.COMPANION_DEVICE_SERVICE);
+        LOG.debug(String.format("Searching for %s associations", macAddress));
+        for (String association : manager.getAssociations()) {
+            LOG.debug(String.format("Already associated with: %s", association));
+            if (association.equals(macAddress)) {
+                LOG.info("The device has already been bonded through CompanionDeviceManager, using regular");
+                // If it's already "associated", we should immediately pair
+                // because the callback is never called (AFAIK?)
+                BondingUtil.bluetoothBond(bondingInterface, device);
+                return;
+            }
+        }
+
+        LOG.debug("Starting association request");
+        manager.associate(pairingRequest,
+                getCompanionDeviceManagerCallback(bondingInterface),
+                null);
+    }
+
+    /**
+     * This is a bit hacky, but it does stop a bonding that might be otherwise stuck,
+     * use with some caution
+     */
+    public static void stopBluetoothBonding(BluetoothDevice device) {
+        try {
+            //noinspection JavaReflectionMemberAccess
+            device.getClass().getMethod("cancelBondProcess").invoke(device);
+        } catch (Throwable ignore) {
+        }
+    }
+
+    /**
+     * Finalizes bonded device
+     */
+    public static void handleDeviceBonded(BondingInterface bondingInterface, GBDeviceCandidate deviceCandidate) {
+        if (deviceCandidate == null) {
+            LOG.error("deviceCandidate was null! Can't handle bonded device!");
+            return;
+        }
+
+        toast(bondingInterface.getContext(), bondingInterface.getContext().getString(R.string.discovery_successfully_bonded, deviceCandidate.getName()), Toast.LENGTH_SHORT, GB.INFO);
+        connectThenComplete(bondingInterface, deviceCandidate);
+    }
+
+    /**
+     * Use this function to initiate bonding to a GBDeviceCandidate
+     */
+    public static void tryBondThenComplete(final BondingInterface bondingInterface, final BluetoothDevice device, final String macAddress) {
+        bondingInterface.registerBroadcastReceivers();
+
+        final int bondState = device.getBondState();
+
+        if (bondState == BluetoothDevice.BOND_BONDING) {
+            GB.toast(bondingInterface.getContext(), bondingInterface.getContext().getString(R.string.pairing_in_progress, device.getName(), device.getAddress()), Toast.LENGTH_LONG, GB.INFO);
+            return;
+        }
+
+        // FIXME: We can only attempt companion pairing if the context of the bondingInterface is an activity
+        // however, when called from the BondAction it will always be the global Application context
+        // See https://codeberg.org/Freeyourgadget/Gadgetbridge/issues/3784#issuecomment-2121792
+        final boolean contextIsActivity = bondingInterface.getContext() instanceof Activity;
+
+        if (bondState == BluetoothDevice.BOND_BONDED) {
+            GB.toast(bondingInterface.getContext().getString(R.string.pairing_already_bonded, device.getName(), device.getAddress()), Toast.LENGTH_SHORT, GB.INFO);
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && !isPebble2(device) && contextIsActivity) {
+                // If CompanionDeviceManager is available, skip connection and go bond
+                // TODO: It would theoretically be nice to check if it's already been granted,
+                //  but re-bond works
+                askCompanionPairing(bondingInterface, device, macAddress);
+            } else {
+                attemptToFirstConnect(bondingInterface.getCurrentTarget().getDevice());
+            }
+            return;
+        }
+
+        GB.toast(bondingInterface.getContext(), bondingInterface.getContext().getString(R.string.pairing_creating_bond_with, device.getName(), device.getAddress()), Toast.LENGTH_LONG, GB.INFO);
+
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && !isPebble2(device) && contextIsActivity) {
+            askCompanionPairing(bondingInterface, device, macAddress);
+        } else if (isPebble2(device)) {
+            // TODO: start companionDevicePairing after connecting to Pebble 2 but before writing to pairing trigger
+            attemptToFirstConnect(device);
+        } else {
+            bluetoothBond(bondingInterface, device);
+        }
+    }
+
+    @RequiresApi(Build.VERSION_CODES.O)
+    private static void askCompanionPairing(BondingInterface bondingInterface, BluetoothDevice device, String macAddress) {
+        new MaterialAlertDialogBuilder(bondingInterface.getContext())
+                .setTitle(R.string.companion_pairing_request_title)
+                .setMessage(R.string.companion_pairing_request_description)
+                .setPositiveButton(R.string.yes, (dialog, whichButton) -> {
+                    companionDeviceManagerBond(bondingInterface, device, macAddress);
+                })
+                .setNegativeButton(R.string.no, (dialog, whichButton) -> {
+                    bluetoothBond(bondingInterface, device);
+                })
+                .show();
+    }
+
+    /**
+     * Returns a callback for CompanionDeviceManager
+     *
+     * @param bondingInterface the activity that started the CDM bonding process
+     * @return CompanionDeviceManager.Callback that handles the CompanionDeviceManager bonding process results
+     */
+    @RequiresApi(Build.VERSION_CODES.O)
+    private static CompanionDeviceManager.Callback getCompanionDeviceManagerCallback(final BondingInterface bondingInterface) {
+        return new CompanionDeviceManager.Callback() {
+            @Override
+            public void onFailure(CharSequence error) {
+                LOG.error("Bonding failed immediately: {}", error);
+                toast(bondingInterface.getContext(), bondingInterface.getContext().getString(R.string.discovery_bonding_failed_immediately), Toast.LENGTH_SHORT, GB.ERROR);
+            }
+
+            @Override
+            public void onDeviceFound(IntentSender chooserLauncher) {
+                try {
+                    startIntentSenderForResult((Activity) bondingInterface.getContext(),
+                            chooserLauncher,
+                            REQUEST_CODE,
+                            null,
+                            0,
+                            0,
+                            0,
+                            null);
+                } catch (IntentSender.SendIntentException e) {
+                    LOG.error(e.toString());
+                }
+            }
+        };
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/CRC32C.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/CRC32C.java
new file mode 100644
index 0000000..a13ccea
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/CRC32C.java
@@ -0,0 +1,633 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * 
+ * Some portions of this file Copyright (c) 2004-2006 Intel Corportation
+ * and licensed under the BSD license.
+ */
+package nodomain.freeyourgadget.gadgetbridge.util;
+
+import java.util.zip.Checksum;
+
+/**
+ * A pure-java implementation of the CRC32 checksum that uses
+ * the CRC32-C polynomial, the same polynomial used by iSCSI
+ * and implemented on many Intel chipsets supporting SSE4.2.
+ */
+
+public class CRC32C implements Checksum {
+
+  /** the current CRC value, bit-flipped */
+  private int crc;
+
+  /** Create a new PureJavaCrc32 object. */
+  public CRC32C() {
+    reset();
+  }
+
+  @Override
+  public long getValue() {
+    long ret = crc;
+    return (~ret) & 0xffffffffL;
+  }
+
+  @Override
+  public void reset() {
+    crc = 0xffffffff;
+  }
+
+  @Override
+  public void update(byte[] b, int off, int len) {
+    int localCrc = crc;
+
+    while(len > 7) {
+      final int c0 =(b[off+0] ^ localCrc) & 0xff;
+      final int c1 =(b[off+1] ^ (localCrc >>>= 8)) & 0xff;
+      final int c2 =(b[off+2] ^ (localCrc >>>= 8)) & 0xff;
+      final int c3 =(b[off+3] ^ (localCrc >>>= 8)) & 0xff;
+      localCrc = (T[T8_7_start + c0] ^ T[T8_6_start + c1])
+          ^ (T[T8_5_start + c2] ^ T[T8_4_start + c3]);
+
+      final int c4 = b[off+4] & 0xff;
+      final int c5 = b[off+5] & 0xff;
+      final int c6 = b[off+6] & 0xff;
+      final int c7 = b[off+7] & 0xff;
+
+      localCrc ^= (T[T8_3_start + c4] ^ T[T8_2_start + c5])
+           ^ (T[T8_1_start + c6] ^ T[T8_0_start + c7]);
+
+      off += 8;
+      len -= 8;
+    }
+
+    /* loop unroll - duff's device style */
+    switch(len) {
+      case 7: localCrc = (localCrc >>> 8) ^ T[T8_0_start + ((localCrc ^ b[off++]) & 0xff)];
+      case 6: localCrc = (localCrc >>> 8) ^ T[T8_0_start + ((localCrc ^ b[off++]) & 0xff)];
+      case 5: localCrc = (localCrc >>> 8) ^ T[T8_0_start + ((localCrc ^ b[off++]) & 0xff)];
+      case 4: localCrc = (localCrc >>> 8) ^ T[T8_0_start + ((localCrc ^ b[off++]) & 0xff)];
+      case 3: localCrc = (localCrc >>> 8) ^ T[T8_0_start + ((localCrc ^ b[off++]) & 0xff)];
+      case 2: localCrc = (localCrc >>> 8) ^ T[T8_0_start + ((localCrc ^ b[off++]) & 0xff)];
+      case 1: localCrc = (localCrc >>> 8) ^ T[T8_0_start + ((localCrc ^ b[off++]) & 0xff)];
+      default:
+        /* nothing */
+    }
+    
+    // Publish crc out to object
+    crc = localCrc;
+  }
+
+  @Override
+  final public void update(int b) {
+    crc = (crc >>> 8) ^ T[T8_0_start + ((crc ^ b) & 0xff)];
+  }
+    
+  // CRC polynomial tables generated by:
+  // java -cp build/test/classes/:build/classes/ \
+  //   org.apache.hadoop.util.TestPureJavaCrc32\$Table 82F63B78
+
+  private static final int T8_0_start = 0*256;
+  private static final int T8_1_start = 1*256;
+  private static final int T8_2_start = 2*256;
+  private static final int T8_3_start = 3*256;
+  private static final int T8_4_start = 4*256;
+  private static final int T8_5_start = 5*256;
+  private static final int T8_6_start = 6*256;
+  private static final int T8_7_start = 7*256;
+
+  private static final int[] T = new int[] {
+    /* T8_0 */
+    0x00000000, 0xF26B8303, 0xE13B70F7, 0x1350F3F4, 
+    0xC79A971F, 0x35F1141C, 0x26A1E7E8, 0xD4CA64EB, 
+    0x8AD958CF, 0x78B2DBCC, 0x6BE22838, 0x9989AB3B, 
+    0x4D43CFD0, 0xBF284CD3, 0xAC78BF27, 0x5E133C24, 
+    0x105EC76F, 0xE235446C, 0xF165B798, 0x030E349B, 
+    0xD7C45070, 0x25AFD373, 0x36FF2087, 0xC494A384, 
+    0x9A879FA0, 0x68EC1CA3, 0x7BBCEF57, 0x89D76C54, 
+    0x5D1D08BF, 0xAF768BBC, 0xBC267848, 0x4E4DFB4B, 
+    0x20BD8EDE, 0xD2D60DDD, 0xC186FE29, 0x33ED7D2A, 
+    0xE72719C1, 0x154C9AC2, 0x061C6936, 0xF477EA35, 
+    0xAA64D611, 0x580F5512, 0x4B5FA6E6, 0xB93425E5, 
+    0x6DFE410E, 0x9F95C20D, 0x8CC531F9, 0x7EAEB2FA, 
+    0x30E349B1, 0xC288CAB2, 0xD1D83946, 0x23B3BA45, 
+    0xF779DEAE, 0x05125DAD, 0x1642AE59, 0xE4292D5A, 
+    0xBA3A117E, 0x4851927D, 0x5B016189, 0xA96AE28A, 
+    0x7DA08661, 0x8FCB0562, 0x9C9BF696, 0x6EF07595, 
+    0x417B1DBC, 0xB3109EBF, 0xA0406D4B, 0x522BEE48, 
+    0x86E18AA3, 0x748A09A0, 0x67DAFA54, 0x95B17957, 
+    0xCBA24573, 0x39C9C670, 0x2A993584, 0xD8F2B687, 
+    0x0C38D26C, 0xFE53516F, 0xED03A29B, 0x1F682198, 
+    0x5125DAD3, 0xA34E59D0, 0xB01EAA24, 0x42752927, 
+    0x96BF4DCC, 0x64D4CECF, 0x77843D3B, 0x85EFBE38, 
+    0xDBFC821C, 0x2997011F, 0x3AC7F2EB, 0xC8AC71E8, 
+    0x1C661503, 0xEE0D9600, 0xFD5D65F4, 0x0F36E6F7, 
+    0x61C69362, 0x93AD1061, 0x80FDE395, 0x72966096, 
+    0xA65C047D, 0x5437877E, 0x4767748A, 0xB50CF789, 
+    0xEB1FCBAD, 0x197448AE, 0x0A24BB5A, 0xF84F3859, 
+    0x2C855CB2, 0xDEEEDFB1, 0xCDBE2C45, 0x3FD5AF46, 
+    0x7198540D, 0x83F3D70E, 0x90A324FA, 0x62C8A7F9, 
+    0xB602C312, 0x44694011, 0x5739B3E5, 0xA55230E6, 
+    0xFB410CC2, 0x092A8FC1, 0x1A7A7C35, 0xE811FF36, 
+    0x3CDB9BDD, 0xCEB018DE, 0xDDE0EB2A, 0x2F8B6829, 
+    0x82F63B78, 0x709DB87B, 0x63CD4B8F, 0x91A6C88C, 
+    0x456CAC67, 0xB7072F64, 0xA457DC90, 0x563C5F93, 
+    0x082F63B7, 0xFA44E0B4, 0xE9141340, 0x1B7F9043, 
+    0xCFB5F4A8, 0x3DDE77AB, 0x2E8E845F, 0xDCE5075C, 
+    0x92A8FC17, 0x60C37F14, 0x73938CE0, 0x81F80FE3, 
+    0x55326B08, 0xA759E80B, 0xB4091BFF, 0x466298FC, 
+    0x1871A4D8, 0xEA1A27DB, 0xF94AD42F, 0x0B21572C, 
+    0xDFEB33C7, 0x2D80B0C4, 0x3ED04330, 0xCCBBC033, 
+    0xA24BB5A6, 0x502036A5, 0x4370C551, 0xB11B4652, 
+    0x65D122B9, 0x97BAA1BA, 0x84EA524E, 0x7681D14D, 
+    0x2892ED69, 0xDAF96E6A, 0xC9A99D9E, 0x3BC21E9D, 
+    0xEF087A76, 0x1D63F975, 0x0E330A81, 0xFC588982, 
+    0xB21572C9, 0x407EF1CA, 0x532E023E, 0xA145813D, 
+    0x758FE5D6, 0x87E466D5, 0x94B49521, 0x66DF1622, 
+    0x38CC2A06, 0xCAA7A905, 0xD9F75AF1, 0x2B9CD9F2, 
+    0xFF56BD19, 0x0D3D3E1A, 0x1E6DCDEE, 0xEC064EED, 
+    0xC38D26C4, 0x31E6A5C7, 0x22B65633, 0xD0DDD530, 
+    0x0417B1DB, 0xF67C32D8, 0xE52CC12C, 0x1747422F, 
+    0x49547E0B, 0xBB3FFD08, 0xA86F0EFC, 0x5A048DFF, 
+    0x8ECEE914, 0x7CA56A17, 0x6FF599E3, 0x9D9E1AE0, 
+    0xD3D3E1AB, 0x21B862A8, 0x32E8915C, 0xC083125F, 
+    0x144976B4, 0xE622F5B7, 0xF5720643, 0x07198540, 
+    0x590AB964, 0xAB613A67, 0xB831C993, 0x4A5A4A90, 
+    0x9E902E7B, 0x6CFBAD78, 0x7FAB5E8C, 0x8DC0DD8F, 
+    0xE330A81A, 0x115B2B19, 0x020BD8ED, 0xF0605BEE, 
+    0x24AA3F05, 0xD6C1BC06, 0xC5914FF2, 0x37FACCF1, 
+    0x69E9F0D5, 0x9B8273D6, 0x88D28022, 0x7AB90321, 
+    0xAE7367CA, 0x5C18E4C9, 0x4F48173D, 0xBD23943E, 
+    0xF36E6F75, 0x0105EC76, 0x12551F82, 0xE03E9C81, 
+    0x34F4F86A, 0xC69F7B69, 0xD5CF889D, 0x27A40B9E, 
+    0x79B737BA, 0x8BDCB4B9, 0x988C474D, 0x6AE7C44E, 
+    0xBE2DA0A5, 0x4C4623A6, 0x5F16D052, 0xAD7D5351, 
+    /* T8_1 */
+    0x00000000, 0x13A29877, 0x274530EE, 0x34E7A899, 
+    0x4E8A61DC, 0x5D28F9AB, 0x69CF5132, 0x7A6DC945, 
+    0x9D14C3B8, 0x8EB65BCF, 0xBA51F356, 0xA9F36B21, 
+    0xD39EA264, 0xC03C3A13, 0xF4DB928A, 0xE7790AFD, 
+    0x3FC5F181, 0x2C6769F6, 0x1880C16F, 0x0B225918, 
+    0x714F905D, 0x62ED082A, 0x560AA0B3, 0x45A838C4, 
+    0xA2D13239, 0xB173AA4E, 0x859402D7, 0x96369AA0, 
+    0xEC5B53E5, 0xFFF9CB92, 0xCB1E630B, 0xD8BCFB7C, 
+    0x7F8BE302, 0x6C297B75, 0x58CED3EC, 0x4B6C4B9B, 
+    0x310182DE, 0x22A31AA9, 0x1644B230, 0x05E62A47, 
+    0xE29F20BA, 0xF13DB8CD, 0xC5DA1054, 0xD6788823, 
+    0xAC154166, 0xBFB7D911, 0x8B507188, 0x98F2E9FF, 
+    0x404E1283, 0x53EC8AF4, 0x670B226D, 0x74A9BA1A, 
+    0x0EC4735F, 0x1D66EB28, 0x298143B1, 0x3A23DBC6, 
+    0xDD5AD13B, 0xCEF8494C, 0xFA1FE1D5, 0xE9BD79A2, 
+    0x93D0B0E7, 0x80722890, 0xB4958009, 0xA737187E, 
+    0xFF17C604, 0xECB55E73, 0xD852F6EA, 0xCBF06E9D, 
+    0xB19DA7D8, 0xA23F3FAF, 0x96D89736, 0x857A0F41, 
+    0x620305BC, 0x71A19DCB, 0x45463552, 0x56E4AD25, 
+    0x2C896460, 0x3F2BFC17, 0x0BCC548E, 0x186ECCF9, 
+    0xC0D23785, 0xD370AFF2, 0xE797076B, 0xF4359F1C, 
+    0x8E585659, 0x9DFACE2E, 0xA91D66B7, 0xBABFFEC0, 
+    0x5DC6F43D, 0x4E646C4A, 0x7A83C4D3, 0x69215CA4, 
+    0x134C95E1, 0x00EE0D96, 0x3409A50F, 0x27AB3D78, 
+    0x809C2506, 0x933EBD71, 0xA7D915E8, 0xB47B8D9F, 
+    0xCE1644DA, 0xDDB4DCAD, 0xE9537434, 0xFAF1EC43, 
+    0x1D88E6BE, 0x0E2A7EC9, 0x3ACDD650, 0x296F4E27, 
+    0x53028762, 0x40A01F15, 0x7447B78C, 0x67E52FFB, 
+    0xBF59D487, 0xACFB4CF0, 0x981CE469, 0x8BBE7C1E, 
+    0xF1D3B55B, 0xE2712D2C, 0xD69685B5, 0xC5341DC2, 
+    0x224D173F, 0x31EF8F48, 0x050827D1, 0x16AABFA6, 
+    0x6CC776E3, 0x7F65EE94, 0x4B82460D, 0x5820DE7A, 
+    0xFBC3FAF9, 0xE861628E, 0xDC86CA17, 0xCF245260, 
+    0xB5499B25, 0xA6EB0352, 0x920CABCB, 0x81AE33BC, 
+    0x66D73941, 0x7575A136, 0x419209AF, 0x523091D8, 
+    0x285D589D, 0x3BFFC0EA, 0x0F186873, 0x1CBAF004, 
+    0xC4060B78, 0xD7A4930F, 0xE3433B96, 0xF0E1A3E1, 
+    0x8A8C6AA4, 0x992EF2D3, 0xADC95A4A, 0xBE6BC23D, 
+    0x5912C8C0, 0x4AB050B7, 0x7E57F82E, 0x6DF56059, 
+    0x1798A91C, 0x043A316B, 0x30DD99F2, 0x237F0185, 
+    0x844819FB, 0x97EA818C, 0xA30D2915, 0xB0AFB162, 
+    0xCAC27827, 0xD960E050, 0xED8748C9, 0xFE25D0BE, 
+    0x195CDA43, 0x0AFE4234, 0x3E19EAAD, 0x2DBB72DA, 
+    0x57D6BB9F, 0x447423E8, 0x70938B71, 0x63311306, 
+    0xBB8DE87A, 0xA82F700D, 0x9CC8D894, 0x8F6A40E3, 
+    0xF50789A6, 0xE6A511D1, 0xD242B948, 0xC1E0213F, 
+    0x26992BC2, 0x353BB3B5, 0x01DC1B2C, 0x127E835B, 
+    0x68134A1E, 0x7BB1D269, 0x4F567AF0, 0x5CF4E287, 
+    0x04D43CFD, 0x1776A48A, 0x23910C13, 0x30339464, 
+    0x4A5E5D21, 0x59FCC556, 0x6D1B6DCF, 0x7EB9F5B8, 
+    0x99C0FF45, 0x8A626732, 0xBE85CFAB, 0xAD2757DC, 
+    0xD74A9E99, 0xC4E806EE, 0xF00FAE77, 0xE3AD3600, 
+    0x3B11CD7C, 0x28B3550B, 0x1C54FD92, 0x0FF665E5, 
+    0x759BACA0, 0x663934D7, 0x52DE9C4E, 0x417C0439, 
+    0xA6050EC4, 0xB5A796B3, 0x81403E2A, 0x92E2A65D, 
+    0xE88F6F18, 0xFB2DF76F, 0xCFCA5FF6, 0xDC68C781, 
+    0x7B5FDFFF, 0x68FD4788, 0x5C1AEF11, 0x4FB87766, 
+    0x35D5BE23, 0x26772654, 0x12908ECD, 0x013216BA, 
+    0xE64B1C47, 0xF5E98430, 0xC10E2CA9, 0xD2ACB4DE, 
+    0xA8C17D9B, 0xBB63E5EC, 0x8F844D75, 0x9C26D502, 
+    0x449A2E7E, 0x5738B609, 0x63DF1E90, 0x707D86E7, 
+    0x0A104FA2, 0x19B2D7D5, 0x2D557F4C, 0x3EF7E73B, 
+    0xD98EEDC6, 0xCA2C75B1, 0xFECBDD28, 0xED69455F, 
+    0x97048C1A, 0x84A6146D, 0xB041BCF4, 0xA3E32483, 
+    /* T8_2 */
+    0x00000000, 0xA541927E, 0x4F6F520D, 0xEA2EC073, 
+    0x9EDEA41A, 0x3B9F3664, 0xD1B1F617, 0x74F06469, 
+    0x38513EC5, 0x9D10ACBB, 0x773E6CC8, 0xD27FFEB6, 
+    0xA68F9ADF, 0x03CE08A1, 0xE9E0C8D2, 0x4CA15AAC, 
+    0x70A27D8A, 0xD5E3EFF4, 0x3FCD2F87, 0x9A8CBDF9, 
+    0xEE7CD990, 0x4B3D4BEE, 0xA1138B9D, 0x045219E3, 
+    0x48F3434F, 0xEDB2D131, 0x079C1142, 0xA2DD833C, 
+    0xD62DE755, 0x736C752B, 0x9942B558, 0x3C032726, 
+    0xE144FB14, 0x4405696A, 0xAE2BA919, 0x0B6A3B67, 
+    0x7F9A5F0E, 0xDADBCD70, 0x30F50D03, 0x95B49F7D, 
+    0xD915C5D1, 0x7C5457AF, 0x967A97DC, 0x333B05A2, 
+    0x47CB61CB, 0xE28AF3B5, 0x08A433C6, 0xADE5A1B8, 
+    0x91E6869E, 0x34A714E0, 0xDE89D493, 0x7BC846ED, 
+    0x0F382284, 0xAA79B0FA, 0x40577089, 0xE516E2F7, 
+    0xA9B7B85B, 0x0CF62A25, 0xE6D8EA56, 0x43997828, 
+    0x37691C41, 0x92288E3F, 0x78064E4C, 0xDD47DC32, 
+    0xC76580D9, 0x622412A7, 0x880AD2D4, 0x2D4B40AA, 
+    0x59BB24C3, 0xFCFAB6BD, 0x16D476CE, 0xB395E4B0, 
+    0xFF34BE1C, 0x5A752C62, 0xB05BEC11, 0x151A7E6F, 
+    0x61EA1A06, 0xC4AB8878, 0x2E85480B, 0x8BC4DA75, 
+    0xB7C7FD53, 0x12866F2D, 0xF8A8AF5E, 0x5DE93D20, 
+    0x29195949, 0x8C58CB37, 0x66760B44, 0xC337993A, 
+    0x8F96C396, 0x2AD751E8, 0xC0F9919B, 0x65B803E5, 
+    0x1148678C, 0xB409F5F2, 0x5E273581, 0xFB66A7FF, 
+    0x26217BCD, 0x8360E9B3, 0x694E29C0, 0xCC0FBBBE, 
+    0xB8FFDFD7, 0x1DBE4DA9, 0xF7908DDA, 0x52D11FA4, 
+    0x1E704508, 0xBB31D776, 0x511F1705, 0xF45E857B, 
+    0x80AEE112, 0x25EF736C, 0xCFC1B31F, 0x6A802161, 
+    0x56830647, 0xF3C29439, 0x19EC544A, 0xBCADC634, 
+    0xC85DA25D, 0x6D1C3023, 0x8732F050, 0x2273622E, 
+    0x6ED23882, 0xCB93AAFC, 0x21BD6A8F, 0x84FCF8F1, 
+    0xF00C9C98, 0x554D0EE6, 0xBF63CE95, 0x1A225CEB, 
+    0x8B277743, 0x2E66E53D, 0xC448254E, 0x6109B730, 
+    0x15F9D359, 0xB0B84127, 0x5A968154, 0xFFD7132A, 
+    0xB3764986, 0x1637DBF8, 0xFC191B8B, 0x595889F5, 
+    0x2DA8ED9C, 0x88E97FE2, 0x62C7BF91, 0xC7862DEF, 
+    0xFB850AC9, 0x5EC498B7, 0xB4EA58C4, 0x11ABCABA, 
+    0x655BAED3, 0xC01A3CAD, 0x2A34FCDE, 0x8F756EA0, 
+    0xC3D4340C, 0x6695A672, 0x8CBB6601, 0x29FAF47F, 
+    0x5D0A9016, 0xF84B0268, 0x1265C21B, 0xB7245065, 
+    0x6A638C57, 0xCF221E29, 0x250CDE5A, 0x804D4C24, 
+    0xF4BD284D, 0x51FCBA33, 0xBBD27A40, 0x1E93E83E, 
+    0x5232B292, 0xF77320EC, 0x1D5DE09F, 0xB81C72E1, 
+    0xCCEC1688, 0x69AD84F6, 0x83834485, 0x26C2D6FB, 
+    0x1AC1F1DD, 0xBF8063A3, 0x55AEA3D0, 0xF0EF31AE, 
+    0x841F55C7, 0x215EC7B9, 0xCB7007CA, 0x6E3195B4, 
+    0x2290CF18, 0x87D15D66, 0x6DFF9D15, 0xC8BE0F6B, 
+    0xBC4E6B02, 0x190FF97C, 0xF321390F, 0x5660AB71, 
+    0x4C42F79A, 0xE90365E4, 0x032DA597, 0xA66C37E9, 
+    0xD29C5380, 0x77DDC1FE, 0x9DF3018D, 0x38B293F3, 
+    0x7413C95F, 0xD1525B21, 0x3B7C9B52, 0x9E3D092C, 
+    0xEACD6D45, 0x4F8CFF3B, 0xA5A23F48, 0x00E3AD36, 
+    0x3CE08A10, 0x99A1186E, 0x738FD81D, 0xD6CE4A63, 
+    0xA23E2E0A, 0x077FBC74, 0xED517C07, 0x4810EE79, 
+    0x04B1B4D5, 0xA1F026AB, 0x4BDEE6D8, 0xEE9F74A6, 
+    0x9A6F10CF, 0x3F2E82B1, 0xD50042C2, 0x7041D0BC, 
+    0xAD060C8E, 0x08479EF0, 0xE2695E83, 0x4728CCFD, 
+    0x33D8A894, 0x96993AEA, 0x7CB7FA99, 0xD9F668E7, 
+    0x9557324B, 0x3016A035, 0xDA386046, 0x7F79F238, 
+    0x0B899651, 0xAEC8042F, 0x44E6C45C, 0xE1A75622, 
+    0xDDA47104, 0x78E5E37A, 0x92CB2309, 0x378AB177, 
+    0x437AD51E, 0xE63B4760, 0x0C158713, 0xA954156D, 
+    0xE5F54FC1, 0x40B4DDBF, 0xAA9A1DCC, 0x0FDB8FB2, 
+    0x7B2BEBDB, 0xDE6A79A5, 0x3444B9D6, 0x91052BA8, 
+    /* T8_3 */
+    0x00000000, 0xDD45AAB8, 0xBF672381, 0x62228939, 
+    0x7B2231F3, 0xA6679B4B, 0xC4451272, 0x1900B8CA, 
+    0xF64463E6, 0x2B01C95E, 0x49234067, 0x9466EADF, 
+    0x8D665215, 0x5023F8AD, 0x32017194, 0xEF44DB2C, 
+    0xE964B13D, 0x34211B85, 0x560392BC, 0x8B463804, 
+    0x924680CE, 0x4F032A76, 0x2D21A34F, 0xF06409F7, 
+    0x1F20D2DB, 0xC2657863, 0xA047F15A, 0x7D025BE2, 
+    0x6402E328, 0xB9474990, 0xDB65C0A9, 0x06206A11, 
+    0xD725148B, 0x0A60BE33, 0x6842370A, 0xB5079DB2, 
+    0xAC072578, 0x71428FC0, 0x136006F9, 0xCE25AC41, 
+    0x2161776D, 0xFC24DDD5, 0x9E0654EC, 0x4343FE54, 
+    0x5A43469E, 0x8706EC26, 0xE524651F, 0x3861CFA7, 
+    0x3E41A5B6, 0xE3040F0E, 0x81268637, 0x5C632C8F, 
+    0x45639445, 0x98263EFD, 0xFA04B7C4, 0x27411D7C, 
+    0xC805C650, 0x15406CE8, 0x7762E5D1, 0xAA274F69, 
+    0xB327F7A3, 0x6E625D1B, 0x0C40D422, 0xD1057E9A, 
+    0xABA65FE7, 0x76E3F55F, 0x14C17C66, 0xC984D6DE, 
+    0xD0846E14, 0x0DC1C4AC, 0x6FE34D95, 0xB2A6E72D, 
+    0x5DE23C01, 0x80A796B9, 0xE2851F80, 0x3FC0B538, 
+    0x26C00DF2, 0xFB85A74A, 0x99A72E73, 0x44E284CB, 
+    0x42C2EEDA, 0x9F874462, 0xFDA5CD5B, 0x20E067E3, 
+    0x39E0DF29, 0xE4A57591, 0x8687FCA8, 0x5BC25610, 
+    0xB4868D3C, 0x69C32784, 0x0BE1AEBD, 0xD6A40405, 
+    0xCFA4BCCF, 0x12E11677, 0x70C39F4E, 0xAD8635F6, 
+    0x7C834B6C, 0xA1C6E1D4, 0xC3E468ED, 0x1EA1C255, 
+    0x07A17A9F, 0xDAE4D027, 0xB8C6591E, 0x6583F3A6, 
+    0x8AC7288A, 0x57828232, 0x35A00B0B, 0xE8E5A1B3, 
+    0xF1E51979, 0x2CA0B3C1, 0x4E823AF8, 0x93C79040, 
+    0x95E7FA51, 0x48A250E9, 0x2A80D9D0, 0xF7C57368, 
+    0xEEC5CBA2, 0x3380611A, 0x51A2E823, 0x8CE7429B, 
+    0x63A399B7, 0xBEE6330F, 0xDCC4BA36, 0x0181108E, 
+    0x1881A844, 0xC5C402FC, 0xA7E68BC5, 0x7AA3217D, 
+    0x52A0C93F, 0x8FE56387, 0xEDC7EABE, 0x30824006, 
+    0x2982F8CC, 0xF4C75274, 0x96E5DB4D, 0x4BA071F5, 
+    0xA4E4AAD9, 0x79A10061, 0x1B838958, 0xC6C623E0, 
+    0xDFC69B2A, 0x02833192, 0x60A1B8AB, 0xBDE41213, 
+    0xBBC47802, 0x6681D2BA, 0x04A35B83, 0xD9E6F13B, 
+    0xC0E649F1, 0x1DA3E349, 0x7F816A70, 0xA2C4C0C8, 
+    0x4D801BE4, 0x90C5B15C, 0xF2E73865, 0x2FA292DD, 
+    0x36A22A17, 0xEBE780AF, 0x89C50996, 0x5480A32E, 
+    0x8585DDB4, 0x58C0770C, 0x3AE2FE35, 0xE7A7548D, 
+    0xFEA7EC47, 0x23E246FF, 0x41C0CFC6, 0x9C85657E, 
+    0x73C1BE52, 0xAE8414EA, 0xCCA69DD3, 0x11E3376B, 
+    0x08E38FA1, 0xD5A62519, 0xB784AC20, 0x6AC10698, 
+    0x6CE16C89, 0xB1A4C631, 0xD3864F08, 0x0EC3E5B0, 
+    0x17C35D7A, 0xCA86F7C2, 0xA8A47EFB, 0x75E1D443, 
+    0x9AA50F6F, 0x47E0A5D7, 0x25C22CEE, 0xF8878656, 
+    0xE1873E9C, 0x3CC29424, 0x5EE01D1D, 0x83A5B7A5, 
+    0xF90696D8, 0x24433C60, 0x4661B559, 0x9B241FE1, 
+    0x8224A72B, 0x5F610D93, 0x3D4384AA, 0xE0062E12, 
+    0x0F42F53E, 0xD2075F86, 0xB025D6BF, 0x6D607C07, 
+    0x7460C4CD, 0xA9256E75, 0xCB07E74C, 0x16424DF4, 
+    0x106227E5, 0xCD278D5D, 0xAF050464, 0x7240AEDC, 
+    0x6B401616, 0xB605BCAE, 0xD4273597, 0x09629F2F, 
+    0xE6264403, 0x3B63EEBB, 0x59416782, 0x8404CD3A, 
+    0x9D0475F0, 0x4041DF48, 0x22635671, 0xFF26FCC9, 
+    0x2E238253, 0xF36628EB, 0x9144A1D2, 0x4C010B6A, 
+    0x5501B3A0, 0x88441918, 0xEA669021, 0x37233A99, 
+    0xD867E1B5, 0x05224B0D, 0x6700C234, 0xBA45688C, 
+    0xA345D046, 0x7E007AFE, 0x1C22F3C7, 0xC167597F, 
+    0xC747336E, 0x1A0299D6, 0x782010EF, 0xA565BA57, 
+    0xBC65029D, 0x6120A825, 0x0302211C, 0xDE478BA4, 
+    0x31035088, 0xEC46FA30, 0x8E647309, 0x5321D9B1, 
+    0x4A21617B, 0x9764CBC3, 0xF54642FA, 0x2803E842, 
+    /* T8_4 */
+    0x00000000, 0x38116FAC, 0x7022DF58, 0x4833B0F4, 
+    0xE045BEB0, 0xD854D11C, 0x906761E8, 0xA8760E44, 
+    0xC5670B91, 0xFD76643D, 0xB545D4C9, 0x8D54BB65, 
+    0x2522B521, 0x1D33DA8D, 0x55006A79, 0x6D1105D5, 
+    0x8F2261D3, 0xB7330E7F, 0xFF00BE8B, 0xC711D127, 
+    0x6F67DF63, 0x5776B0CF, 0x1F45003B, 0x27546F97, 
+    0x4A456A42, 0x725405EE, 0x3A67B51A, 0x0276DAB6, 
+    0xAA00D4F2, 0x9211BB5E, 0xDA220BAA, 0xE2336406, 
+    0x1BA8B557, 0x23B9DAFB, 0x6B8A6A0F, 0x539B05A3, 
+    0xFBED0BE7, 0xC3FC644B, 0x8BCFD4BF, 0xB3DEBB13, 
+    0xDECFBEC6, 0xE6DED16A, 0xAEED619E, 0x96FC0E32, 
+    0x3E8A0076, 0x069B6FDA, 0x4EA8DF2E, 0x76B9B082, 
+    0x948AD484, 0xAC9BBB28, 0xE4A80BDC, 0xDCB96470, 
+    0x74CF6A34, 0x4CDE0598, 0x04EDB56C, 0x3CFCDAC0, 
+    0x51EDDF15, 0x69FCB0B9, 0x21CF004D, 0x19DE6FE1, 
+    0xB1A861A5, 0x89B90E09, 0xC18ABEFD, 0xF99BD151, 
+    0x37516AAE, 0x0F400502, 0x4773B5F6, 0x7F62DA5A, 
+    0xD714D41E, 0xEF05BBB2, 0xA7360B46, 0x9F2764EA, 
+    0xF236613F, 0xCA270E93, 0x8214BE67, 0xBA05D1CB, 
+    0x1273DF8F, 0x2A62B023, 0x625100D7, 0x5A406F7B, 
+    0xB8730B7D, 0x806264D1, 0xC851D425, 0xF040BB89, 
+    0x5836B5CD, 0x6027DA61, 0x28146A95, 0x10050539, 
+    0x7D1400EC, 0x45056F40, 0x0D36DFB4, 0x3527B018, 
+    0x9D51BE5C, 0xA540D1F0, 0xED736104, 0xD5620EA8, 
+    0x2CF9DFF9, 0x14E8B055, 0x5CDB00A1, 0x64CA6F0D, 
+    0xCCBC6149, 0xF4AD0EE5, 0xBC9EBE11, 0x848FD1BD, 
+    0xE99ED468, 0xD18FBBC4, 0x99BC0B30, 0xA1AD649C, 
+    0x09DB6AD8, 0x31CA0574, 0x79F9B580, 0x41E8DA2C, 
+    0xA3DBBE2A, 0x9BCAD186, 0xD3F96172, 0xEBE80EDE, 
+    0x439E009A, 0x7B8F6F36, 0x33BCDFC2, 0x0BADB06E, 
+    0x66BCB5BB, 0x5EADDA17, 0x169E6AE3, 0x2E8F054F, 
+    0x86F90B0B, 0xBEE864A7, 0xF6DBD453, 0xCECABBFF, 
+    0x6EA2D55C, 0x56B3BAF0, 0x1E800A04, 0x269165A8, 
+    0x8EE76BEC, 0xB6F60440, 0xFEC5B4B4, 0xC6D4DB18, 
+    0xABC5DECD, 0x93D4B161, 0xDBE70195, 0xE3F66E39, 
+    0x4B80607D, 0x73910FD1, 0x3BA2BF25, 0x03B3D089, 
+    0xE180B48F, 0xD991DB23, 0x91A26BD7, 0xA9B3047B, 
+    0x01C50A3F, 0x39D46593, 0x71E7D567, 0x49F6BACB, 
+    0x24E7BF1E, 0x1CF6D0B2, 0x54C56046, 0x6CD40FEA, 
+    0xC4A201AE, 0xFCB36E02, 0xB480DEF6, 0x8C91B15A, 
+    0x750A600B, 0x4D1B0FA7, 0x0528BF53, 0x3D39D0FF, 
+    0x954FDEBB, 0xAD5EB117, 0xE56D01E3, 0xDD7C6E4F, 
+    0xB06D6B9A, 0x887C0436, 0xC04FB4C2, 0xF85EDB6E, 
+    0x5028D52A, 0x6839BA86, 0x200A0A72, 0x181B65DE, 
+    0xFA2801D8, 0xC2396E74, 0x8A0ADE80, 0xB21BB12C, 
+    0x1A6DBF68, 0x227CD0C4, 0x6A4F6030, 0x525E0F9C, 
+    0x3F4F0A49, 0x075E65E5, 0x4F6DD511, 0x777CBABD, 
+    0xDF0AB4F9, 0xE71BDB55, 0xAF286BA1, 0x9739040D, 
+    0x59F3BFF2, 0x61E2D05E, 0x29D160AA, 0x11C00F06, 
+    0xB9B60142, 0x81A76EEE, 0xC994DE1A, 0xF185B1B6, 
+    0x9C94B463, 0xA485DBCF, 0xECB66B3B, 0xD4A70497, 
+    0x7CD10AD3, 0x44C0657F, 0x0CF3D58B, 0x34E2BA27, 
+    0xD6D1DE21, 0xEEC0B18D, 0xA6F30179, 0x9EE26ED5, 
+    0x36946091, 0x0E850F3D, 0x46B6BFC9, 0x7EA7D065, 
+    0x13B6D5B0, 0x2BA7BA1C, 0x63940AE8, 0x5B856544, 
+    0xF3F36B00, 0xCBE204AC, 0x83D1B458, 0xBBC0DBF4, 
+    0x425B0AA5, 0x7A4A6509, 0x3279D5FD, 0x0A68BA51, 
+    0xA21EB415, 0x9A0FDBB9, 0xD23C6B4D, 0xEA2D04E1, 
+    0x873C0134, 0xBF2D6E98, 0xF71EDE6C, 0xCF0FB1C0, 
+    0x6779BF84, 0x5F68D028, 0x175B60DC, 0x2F4A0F70, 
+    0xCD796B76, 0xF56804DA, 0xBD5BB42E, 0x854ADB82, 
+    0x2D3CD5C6, 0x152DBA6A, 0x5D1E0A9E, 0x650F6532, 
+    0x081E60E7, 0x300F0F4B, 0x783CBFBF, 0x402DD013, 
+    0xE85BDE57, 0xD04AB1FB, 0x9879010F, 0xA0686EA3, 
+    /* T8_5 */
+    0x00000000, 0xEF306B19, 0xDB8CA0C3, 0x34BCCBDA, 
+    0xB2F53777, 0x5DC55C6E, 0x697997B4, 0x8649FCAD, 
+    0x6006181F, 0x8F367306, 0xBB8AB8DC, 0x54BAD3C5, 
+    0xD2F32F68, 0x3DC34471, 0x097F8FAB, 0xE64FE4B2, 
+    0xC00C303E, 0x2F3C5B27, 0x1B8090FD, 0xF4B0FBE4, 
+    0x72F90749, 0x9DC96C50, 0xA975A78A, 0x4645CC93, 
+    0xA00A2821, 0x4F3A4338, 0x7B8688E2, 0x94B6E3FB, 
+    0x12FF1F56, 0xFDCF744F, 0xC973BF95, 0x2643D48C, 
+    0x85F4168D, 0x6AC47D94, 0x5E78B64E, 0xB148DD57, 
+    0x370121FA, 0xD8314AE3, 0xEC8D8139, 0x03BDEA20, 
+    0xE5F20E92, 0x0AC2658B, 0x3E7EAE51, 0xD14EC548, 
+    0x570739E5, 0xB83752FC, 0x8C8B9926, 0x63BBF23F, 
+    0x45F826B3, 0xAAC84DAA, 0x9E748670, 0x7144ED69, 
+    0xF70D11C4, 0x183D7ADD, 0x2C81B107, 0xC3B1DA1E, 
+    0x25FE3EAC, 0xCACE55B5, 0xFE729E6F, 0x1142F576, 
+    0x970B09DB, 0x783B62C2, 0x4C87A918, 0xA3B7C201, 
+    0x0E045BEB, 0xE13430F2, 0xD588FB28, 0x3AB89031, 
+    0xBCF16C9C, 0x53C10785, 0x677DCC5F, 0x884DA746, 
+    0x6E0243F4, 0x813228ED, 0xB58EE337, 0x5ABE882E, 
+    0xDCF77483, 0x33C71F9A, 0x077BD440, 0xE84BBF59, 
+    0xCE086BD5, 0x213800CC, 0x1584CB16, 0xFAB4A00F, 
+    0x7CFD5CA2, 0x93CD37BB, 0xA771FC61, 0x48419778, 
+    0xAE0E73CA, 0x413E18D3, 0x7582D309, 0x9AB2B810, 
+    0x1CFB44BD, 0xF3CB2FA4, 0xC777E47E, 0x28478F67, 
+    0x8BF04D66, 0x64C0267F, 0x507CEDA5, 0xBF4C86BC, 
+    0x39057A11, 0xD6351108, 0xE289DAD2, 0x0DB9B1CB, 
+    0xEBF65579, 0x04C63E60, 0x307AF5BA, 0xDF4A9EA3, 
+    0x5903620E, 0xB6330917, 0x828FC2CD, 0x6DBFA9D4, 
+    0x4BFC7D58, 0xA4CC1641, 0x9070DD9B, 0x7F40B682, 
+    0xF9094A2F, 0x16392136, 0x2285EAEC, 0xCDB581F5, 
+    0x2BFA6547, 0xC4CA0E5E, 0xF076C584, 0x1F46AE9D, 
+    0x990F5230, 0x763F3929, 0x4283F2F3, 0xADB399EA, 
+    0x1C08B7D6, 0xF338DCCF, 0xC7841715, 0x28B47C0C, 
+    0xAEFD80A1, 0x41CDEBB8, 0x75712062, 0x9A414B7B, 
+    0x7C0EAFC9, 0x933EC4D0, 0xA7820F0A, 0x48B26413, 
+    0xCEFB98BE, 0x21CBF3A7, 0x1577387D, 0xFA475364, 
+    0xDC0487E8, 0x3334ECF1, 0x0788272B, 0xE8B84C32, 
+    0x6EF1B09F, 0x81C1DB86, 0xB57D105C, 0x5A4D7B45, 
+    0xBC029FF7, 0x5332F4EE, 0x678E3F34, 0x88BE542D, 
+    0x0EF7A880, 0xE1C7C399, 0xD57B0843, 0x3A4B635A, 
+    0x99FCA15B, 0x76CCCA42, 0x42700198, 0xAD406A81, 
+    0x2B09962C, 0xC439FD35, 0xF08536EF, 0x1FB55DF6, 
+    0xF9FAB944, 0x16CAD25D, 0x22761987, 0xCD46729E, 
+    0x4B0F8E33, 0xA43FE52A, 0x90832EF0, 0x7FB345E9, 
+    0x59F09165, 0xB6C0FA7C, 0x827C31A6, 0x6D4C5ABF, 
+    0xEB05A612, 0x0435CD0B, 0x308906D1, 0xDFB96DC8, 
+    0x39F6897A, 0xD6C6E263, 0xE27A29B9, 0x0D4A42A0, 
+    0x8B03BE0D, 0x6433D514, 0x508F1ECE, 0xBFBF75D7, 
+    0x120CEC3D, 0xFD3C8724, 0xC9804CFE, 0x26B027E7, 
+    0xA0F9DB4A, 0x4FC9B053, 0x7B757B89, 0x94451090, 
+    0x720AF422, 0x9D3A9F3B, 0xA98654E1, 0x46B63FF8, 
+    0xC0FFC355, 0x2FCFA84C, 0x1B736396, 0xF443088F, 
+    0xD200DC03, 0x3D30B71A, 0x098C7CC0, 0xE6BC17D9, 
+    0x60F5EB74, 0x8FC5806D, 0xBB794BB7, 0x544920AE, 
+    0xB206C41C, 0x5D36AF05, 0x698A64DF, 0x86BA0FC6, 
+    0x00F3F36B, 0xEFC39872, 0xDB7F53A8, 0x344F38B1, 
+    0x97F8FAB0, 0x78C891A9, 0x4C745A73, 0xA344316A, 
+    0x250DCDC7, 0xCA3DA6DE, 0xFE816D04, 0x11B1061D, 
+    0xF7FEE2AF, 0x18CE89B6, 0x2C72426C, 0xC3422975, 
+    0x450BD5D8, 0xAA3BBEC1, 0x9E87751B, 0x71B71E02, 
+    0x57F4CA8E, 0xB8C4A197, 0x8C786A4D, 0x63480154, 
+    0xE501FDF9, 0x0A3196E0, 0x3E8D5D3A, 0xD1BD3623, 
+    0x37F2D291, 0xD8C2B988, 0xEC7E7252, 0x034E194B, 
+    0x8507E5E6, 0x6A378EFF, 0x5E8B4525, 0xB1BB2E3C, 
+    /* T8_6 */
+    0x00000000, 0x68032CC8, 0xD0065990, 0xB8057558, 
+    0xA5E0C5D1, 0xCDE3E919, 0x75E69C41, 0x1DE5B089, 
+    0x4E2DFD53, 0x262ED19B, 0x9E2BA4C3, 0xF628880B, 
+    0xEBCD3882, 0x83CE144A, 0x3BCB6112, 0x53C84DDA, 
+    0x9C5BFAA6, 0xF458D66E, 0x4C5DA336, 0x245E8FFE, 
+    0x39BB3F77, 0x51B813BF, 0xE9BD66E7, 0x81BE4A2F, 
+    0xD27607F5, 0xBA752B3D, 0x02705E65, 0x6A7372AD, 
+    0x7796C224, 0x1F95EEEC, 0xA7909BB4, 0xCF93B77C, 
+    0x3D5B83BD, 0x5558AF75, 0xED5DDA2D, 0x855EF6E5, 
+    0x98BB466C, 0xF0B86AA4, 0x48BD1FFC, 0x20BE3334, 
+    0x73767EEE, 0x1B755226, 0xA370277E, 0xCB730BB6, 
+    0xD696BB3F, 0xBE9597F7, 0x0690E2AF, 0x6E93CE67, 
+    0xA100791B, 0xC90355D3, 0x7106208B, 0x19050C43, 
+    0x04E0BCCA, 0x6CE39002, 0xD4E6E55A, 0xBCE5C992, 
+    0xEF2D8448, 0x872EA880, 0x3F2BDDD8, 0x5728F110, 
+    0x4ACD4199, 0x22CE6D51, 0x9ACB1809, 0xF2C834C1, 
+    0x7AB7077A, 0x12B42BB2, 0xAAB15EEA, 0xC2B27222, 
+    0xDF57C2AB, 0xB754EE63, 0x0F519B3B, 0x6752B7F3, 
+    0x349AFA29, 0x5C99D6E1, 0xE49CA3B9, 0x8C9F8F71, 
+    0x917A3FF8, 0xF9791330, 0x417C6668, 0x297F4AA0, 
+    0xE6ECFDDC, 0x8EEFD114, 0x36EAA44C, 0x5EE98884, 
+    0x430C380D, 0x2B0F14C5, 0x930A619D, 0xFB094D55, 
+    0xA8C1008F, 0xC0C22C47, 0x78C7591F, 0x10C475D7, 
+    0x0D21C55E, 0x6522E996, 0xDD279CCE, 0xB524B006, 
+    0x47EC84C7, 0x2FEFA80F, 0x97EADD57, 0xFFE9F19F, 
+    0xE20C4116, 0x8A0F6DDE, 0x320A1886, 0x5A09344E, 
+    0x09C17994, 0x61C2555C, 0xD9C72004, 0xB1C40CCC, 
+    0xAC21BC45, 0xC422908D, 0x7C27E5D5, 0x1424C91D, 
+    0xDBB77E61, 0xB3B452A9, 0x0BB127F1, 0x63B20B39, 
+    0x7E57BBB0, 0x16549778, 0xAE51E220, 0xC652CEE8, 
+    0x959A8332, 0xFD99AFFA, 0x459CDAA2, 0x2D9FF66A, 
+    0x307A46E3, 0x58796A2B, 0xE07C1F73, 0x887F33BB, 
+    0xF56E0EF4, 0x9D6D223C, 0x25685764, 0x4D6B7BAC, 
+    0x508ECB25, 0x388DE7ED, 0x808892B5, 0xE88BBE7D, 
+    0xBB43F3A7, 0xD340DF6F, 0x6B45AA37, 0x034686FF, 
+    0x1EA33676, 0x76A01ABE, 0xCEA56FE6, 0xA6A6432E, 
+    0x6935F452, 0x0136D89A, 0xB933ADC2, 0xD130810A, 
+    0xCCD53183, 0xA4D61D4B, 0x1CD36813, 0x74D044DB, 
+    0x27180901, 0x4F1B25C9, 0xF71E5091, 0x9F1D7C59, 
+    0x82F8CCD0, 0xEAFBE018, 0x52FE9540, 0x3AFDB988, 
+    0xC8358D49, 0xA036A181, 0x1833D4D9, 0x7030F811, 
+    0x6DD54898, 0x05D66450, 0xBDD31108, 0xD5D03DC0, 
+    0x8618701A, 0xEE1B5CD2, 0x561E298A, 0x3E1D0542, 
+    0x23F8B5CB, 0x4BFB9903, 0xF3FEEC5B, 0x9BFDC093, 
+    0x546E77EF, 0x3C6D5B27, 0x84682E7F, 0xEC6B02B7, 
+    0xF18EB23E, 0x998D9EF6, 0x2188EBAE, 0x498BC766, 
+    0x1A438ABC, 0x7240A674, 0xCA45D32C, 0xA246FFE4, 
+    0xBFA34F6D, 0xD7A063A5, 0x6FA516FD, 0x07A63A35, 
+    0x8FD9098E, 0xE7DA2546, 0x5FDF501E, 0x37DC7CD6, 
+    0x2A39CC5F, 0x423AE097, 0xFA3F95CF, 0x923CB907, 
+    0xC1F4F4DD, 0xA9F7D815, 0x11F2AD4D, 0x79F18185, 
+    0x6414310C, 0x0C171DC4, 0xB412689C, 0xDC114454, 
+    0x1382F328, 0x7B81DFE0, 0xC384AAB8, 0xAB878670, 
+    0xB66236F9, 0xDE611A31, 0x66646F69, 0x0E6743A1, 
+    0x5DAF0E7B, 0x35AC22B3, 0x8DA957EB, 0xE5AA7B23, 
+    0xF84FCBAA, 0x904CE762, 0x2849923A, 0x404ABEF2, 
+    0xB2828A33, 0xDA81A6FB, 0x6284D3A3, 0x0A87FF6B, 
+    0x17624FE2, 0x7F61632A, 0xC7641672, 0xAF673ABA, 
+    0xFCAF7760, 0x94AC5BA8, 0x2CA92EF0, 0x44AA0238, 
+    0x594FB2B1, 0x314C9E79, 0x8949EB21, 0xE14AC7E9, 
+    0x2ED97095, 0x46DA5C5D, 0xFEDF2905, 0x96DC05CD, 
+    0x8B39B544, 0xE33A998C, 0x5B3FECD4, 0x333CC01C, 
+    0x60F48DC6, 0x08F7A10E, 0xB0F2D456, 0xD8F1F89E, 
+    0xC5144817, 0xAD1764DF, 0x15121187, 0x7D113D4F, 
+    /* T8_7 */
+    0x00000000, 0x493C7D27, 0x9278FA4E, 0xDB448769, 
+    0x211D826D, 0x6821FF4A, 0xB3657823, 0xFA590504, 
+    0x423B04DA, 0x0B0779FD, 0xD043FE94, 0x997F83B3, 
+    0x632686B7, 0x2A1AFB90, 0xF15E7CF9, 0xB86201DE, 
+    0x847609B4, 0xCD4A7493, 0x160EF3FA, 0x5F328EDD, 
+    0xA56B8BD9, 0xEC57F6FE, 0x37137197, 0x7E2F0CB0, 
+    0xC64D0D6E, 0x8F717049, 0x5435F720, 0x1D098A07, 
+    0xE7508F03, 0xAE6CF224, 0x7528754D, 0x3C14086A, 
+    0x0D006599, 0x443C18BE, 0x9F789FD7, 0xD644E2F0, 
+    0x2C1DE7F4, 0x65219AD3, 0xBE651DBA, 0xF759609D, 
+    0x4F3B6143, 0x06071C64, 0xDD439B0D, 0x947FE62A, 
+    0x6E26E32E, 0x271A9E09, 0xFC5E1960, 0xB5626447, 
+    0x89766C2D, 0xC04A110A, 0x1B0E9663, 0x5232EB44, 
+    0xA86BEE40, 0xE1579367, 0x3A13140E, 0x732F6929, 
+    0xCB4D68F7, 0x827115D0, 0x593592B9, 0x1009EF9E, 
+    0xEA50EA9A, 0xA36C97BD, 0x782810D4, 0x31146DF3, 
+    0x1A00CB32, 0x533CB615, 0x8878317C, 0xC1444C5B, 
+    0x3B1D495F, 0x72213478, 0xA965B311, 0xE059CE36, 
+    0x583BCFE8, 0x1107B2CF, 0xCA4335A6, 0x837F4881, 
+    0x79264D85, 0x301A30A2, 0xEB5EB7CB, 0xA262CAEC, 
+    0x9E76C286, 0xD74ABFA1, 0x0C0E38C8, 0x453245EF, 
+    0xBF6B40EB, 0xF6573DCC, 0x2D13BAA5, 0x642FC782, 
+    0xDC4DC65C, 0x9571BB7B, 0x4E353C12, 0x07094135, 
+    0xFD504431, 0xB46C3916, 0x6F28BE7F, 0x2614C358, 
+    0x1700AEAB, 0x5E3CD38C, 0x857854E5, 0xCC4429C2, 
+    0x361D2CC6, 0x7F2151E1, 0xA465D688, 0xED59ABAF, 
+    0x553BAA71, 0x1C07D756, 0xC743503F, 0x8E7F2D18, 
+    0x7426281C, 0x3D1A553B, 0xE65ED252, 0xAF62AF75, 
+    0x9376A71F, 0xDA4ADA38, 0x010E5D51, 0x48322076, 
+    0xB26B2572, 0xFB575855, 0x2013DF3C, 0x692FA21B, 
+    0xD14DA3C5, 0x9871DEE2, 0x4335598B, 0x0A0924AC, 
+    0xF05021A8, 0xB96C5C8F, 0x6228DBE6, 0x2B14A6C1, 
+    0x34019664, 0x7D3DEB43, 0xA6796C2A, 0xEF45110D, 
+    0x151C1409, 0x5C20692E, 0x8764EE47, 0xCE589360, 
+    0x763A92BE, 0x3F06EF99, 0xE44268F0, 0xAD7E15D7, 
+    0x572710D3, 0x1E1B6DF4, 0xC55FEA9D, 0x8C6397BA, 
+    0xB0779FD0, 0xF94BE2F7, 0x220F659E, 0x6B3318B9, 
+    0x916A1DBD, 0xD856609A, 0x0312E7F3, 0x4A2E9AD4, 
+    0xF24C9B0A, 0xBB70E62D, 0x60346144, 0x29081C63, 
+    0xD3511967, 0x9A6D6440, 0x4129E329, 0x08159E0E, 
+    0x3901F3FD, 0x703D8EDA, 0xAB7909B3, 0xE2457494, 
+    0x181C7190, 0x51200CB7, 0x8A648BDE, 0xC358F6F9, 
+    0x7B3AF727, 0x32068A00, 0xE9420D69, 0xA07E704E, 
+    0x5A27754A, 0x131B086D, 0xC85F8F04, 0x8163F223, 
+    0xBD77FA49, 0xF44B876E, 0x2F0F0007, 0x66337D20, 
+    0x9C6A7824, 0xD5560503, 0x0E12826A, 0x472EFF4D, 
+    0xFF4CFE93, 0xB67083B4, 0x6D3404DD, 0x240879FA, 
+    0xDE517CFE, 0x976D01D9, 0x4C2986B0, 0x0515FB97, 
+    0x2E015D56, 0x673D2071, 0xBC79A718, 0xF545DA3F, 
+    0x0F1CDF3B, 0x4620A21C, 0x9D642575, 0xD4585852, 
+    0x6C3A598C, 0x250624AB, 0xFE42A3C2, 0xB77EDEE5, 
+    0x4D27DBE1, 0x041BA6C6, 0xDF5F21AF, 0x96635C88, 
+    0xAA7754E2, 0xE34B29C5, 0x380FAEAC, 0x7133D38B, 
+    0x8B6AD68F, 0xC256ABA8, 0x19122CC1, 0x502E51E6, 
+    0xE84C5038, 0xA1702D1F, 0x7A34AA76, 0x3308D751, 
+    0xC951D255, 0x806DAF72, 0x5B29281B, 0x1215553C, 
+    0x230138CF, 0x6A3D45E8, 0xB179C281, 0xF845BFA6, 
+    0x021CBAA2, 0x4B20C785, 0x906440EC, 0xD9583DCB, 
+    0x613A3C15, 0x28064132, 0xF342C65B, 0xBA7EBB7C, 
+    0x4027BE78, 0x091BC35F, 0xD25F4436, 0x9B633911, 
+    0xA777317B, 0xEE4B4C5C, 0x350FCB35, 0x7C33B612, 
+    0x866AB316, 0xCF56CE31, 0x14124958, 0x5D2E347F, 
+    0xE54C35A1, 0xAC704886, 0x7734CFEF, 0x3E08B2C8, 
+    0xC451B7CC, 0x8D6DCAEB, 0x56294D82, 0x1F1530A5 
+  };
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/CheckSums.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/CheckSums.java
new file mode 100644
index 0000000..d6e4270
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/CheckSums.java
@@ -0,0 +1,193 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, Carsten Pfeiffer, Damien
+    Gaignon, Daniele Gobbetti, Jos茅 Rebelo, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.util;
+
+import androidx.annotation.Nullable;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.ByteArrayOutputStream;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Locale;
+import java.util.zip.CRC32;
+
+public class CheckSums {
+    private static final Logger LOG = LoggerFactory.getLogger(CheckSums.class);
+
+    public static int getCRC8(byte[] seq) {
+        int len = seq.length;
+        int i = 0;
+        byte crc = 0x00;
+
+        while (len-- > 0) {
+            byte extract = seq[i++];
+            for (byte tempI = 8; tempI != 0; tempI--) {
+                byte sum = (byte) ((crc & 0xff) ^ (extract & 0xff));
+                sum = (byte) ((sum & 0xff) & 0x01);
+                crc = (byte) ((crc & 0xff) >>> 1);
+                if (sum != 0) {
+                    crc = (byte) ((crc & 0xff) ^ 0x8c);
+                }
+                extract = (byte) ((extract & 0xff) >>> 1);
+            }
+        }
+        return (crc & 0xff);
+    }
+
+    //thanks http://stackoverflow.com/questions/13209364/convert-c-crc16-to-java-crc16
+    public static int getCRC16(byte[] seq) {
+        return getCRC16(seq, 0xFFFF);
+    }
+    
+    public static int getCRC16(byte[] seq, int crc) {
+        for (byte b : seq) {
+            crc = ((crc >>> 8) | (crc << 8)) & 0xffff;
+            crc ^= (b & 0xff);//byte to int, trunc sign
+            crc ^= ((crc & 0xff) >> 4);
+            crc ^= (crc << 12) & 0xffff;
+            crc ^= ((crc & 0xFF) << 5) & 0xffff;
+        }
+        crc &= 0xffff;
+        return crc;
+    }
+    
+    public static int getCRC16ansi(byte[] seq) {
+        int crc = 0xffff;
+        int polynomial = 0xA001;
+
+        for (int i = 0; i < seq.length; i++) {
+            crc ^= seq[i] & 0xFF;
+            for (int j = 0; j < 8; j++) {
+                if ((crc & 1) != 0) {
+                    crc = (crc >>> 1) ^ polynomial;
+                } else {
+                    crc = crc >>> 1;
+                }
+            }
+        }
+
+        return crc & 0xFFFF;
+    }
+
+    public static int getCRC32(byte[] seq) {
+        CRC32 crc = new CRC32();
+        crc.update(seq);
+        return (int) (crc.getValue());
+    }
+
+    public static int getCRC32(byte[] seq,int offset, int length) {
+        CRC32 crc = new CRC32();
+        crc.update(seq,offset,length);
+        return (int) (crc.getValue());
+    }
+
+    public static void main(String[] args) throws IOException {
+        if (args == null || args.length == 0) {
+            throw new IllegalArgumentException("Pass the files to be checksummed as arguments");
+        }
+        for (String name : args) {
+            try (FileInputStream in = new FileInputStream(name)) {
+                byte[] bytes = readAll(in, 1000 * 1000);
+                System.out.println(name + " : " + getCRC16(bytes));
+            }
+        }
+    }
+
+    // copy&paste of FileUtils.readAll() to have it free from Android dependencies
+    private static byte[] readAll(InputStream in, long maxLen) throws IOException {
+        ByteArrayOutputStream out = new ByteArrayOutputStream(Math.max(8192, in.available()));
+        byte[] buf = new byte[8192];
+        int read;
+        long totalRead = 0;
+        while ((read = in.read(buf)) > 0) {
+            out.write(buf, 0, read);
+            totalRead += read;
+            if (totalRead > maxLen) {
+                throw new IOException("Too much data to read into memory. Got already " + totalRead);
+            }
+        }
+        return out.toByteArray();
+    }
+
+    // https://github.com/ThePBone/GalaxyBudsClient/blob/master/GalaxyBudsClient/Utils/CRC16.cs
+
+    private static int[] Crc16Tab =
+            {0, 4129, 8258, 12387, 16516, 20645, 24774, 28903, 33032, 37161, 41290,
+                    45419, 49548, 53677, 57806, 61935, 4657, 528, 12915, 8786, 21173, 17044, 29431, 25302,
+                    37689, 33560, 45947, 41818, 54205, 50076, 62463, 58334, 9314, 13379, 1056, 5121, 25830,
+                    29895, 17572, 21637, 42346, 46411, 34088, 38153, 58862, 62927, 50604, 54669, 13907,
+                    9842, 5649, 1584, 30423, 26358, 22165, 18100, 46939, 42874, 38681, 34616, 63455, 59390,
+                    55197, 51132, 18628, 22757, 26758, 30887, 2112, 6241, 10242, 14371, 51660, 55789,
+                    59790, 63919, 35144, 39273, 43274, 47403, 23285, 19156, 31415, 27286, 6769, 2640,
+                    14899, 10770, 56317, 52188, 64447, 60318, 39801, 35672, 47931, 43802, 27814, 31879,
+                    19684, 23749, 11298, 15363, 3168, 7233, 60846, 64911, 52716, 56781, 44330, 48395,
+                    36200, 40265, 32407, 28342, 24277, 20212, 15891, 11826, 7761, 3696, 65439, 61374,
+                    57309, 53244, 48923, 44858, 40793, 36728, 37256, 33193, 45514, 41451, 53516, 49453,
+                    61774, 57711, 4224, 161, 12482, 8419, 20484, 16421, 28742, 24679, 33721, 37784, 41979,
+                    46042, 49981, 54044, 58239, 62302, 689, 4752, 8947, 13010, 16949, 21012, 25207, 29270,
+                    46570, 42443, 38312, 34185, 62830, 58703, 54572, 50445, 13538, 9411, 5280, 1153, 29798,
+                    25671, 21540, 17413, 42971, 47098, 34713, 38840, 59231, 63358, 50973, 55100, 9939,
+                    14066, 1681, 5808, 26199, 30326, 17941, 22068, 55628, 51565, 63758, 59695, 39368,
+                    35305, 47498, 43435, 22596, 18533, 30726, 26663, 6336, 2273, 14466, 10403, 52093,
+                    56156, 60223, 64286, 35833, 39896, 43963, 48026, 19061, 23124, 27191, 31254, 2801,
+                    6864, 10931, 14994, 64814, 60687, 56684, 52557, 48554, 44427, 40424, 36297, 31782,
+                    27655, 23652, 19525, 15522, 11395, 7392, 3265, 61215, 65342, 53085, 57212, 44955,
+                    49082, 36825, 40952, 28183, 32310, 20053, 24180, 11923, 16050, 3793, 7920};
+
+    // // https://github.com/ThePBone/GalaxyBudsClient/blob/master/GalaxyBudsClient/Utils/CRC16.cs
+    public static int crc16_ccitt(byte[] data) {
+
+        int i2 = 0;
+        for (int i3 = 0; i3 < data.length; i3++)
+            i2 = Crc16Tab[((i2 >> 8) ^ data[i3]) & 255] ^ (i2 << 8);
+
+        return 65535 & i2;
+    }
+
+    @Nullable
+    public static byte[] md5(final byte[] data) {
+        final MessageDigest md;
+        try {
+            md = MessageDigest.getInstance("MD5");
+        } catch (final NoSuchAlgorithmException e) {
+            LOG.error("Failed to get md5 digest", e);
+            return null;
+        }
+        md.update(data);
+        return md.digest();
+    }
+
+    @Nullable
+    public static String md5(final String str) {
+        final MessageDigest md;
+        try {
+            md = MessageDigest.getInstance("MD5");
+        } catch (final NoSuchAlgorithmException e) {
+            LOG.error("Failed to get md5 digest", e);
+            return null;
+        }
+        md.update(str.getBytes(StandardCharsets.UTF_8));
+        return GB.hexdump(md.digest()).toLowerCase(Locale.ROOT);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/CryptoUtils.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/CryptoUtils.java
new file mode 100644
index 0000000..c5e4cc6
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/CryptoUtils.java
@@ -0,0 +1,142 @@
+/*  Copyright (C) 2021-2024 Andreas Shimokawa, Damien Gaignon, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.util;
+
+import android.annotation.SuppressLint;
+
+import java.io.UnsupportedEncodingException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.InvalidKeySpecException;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.Mac;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKey;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.GCMParameterSpec;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.PBEKeySpec;
+import javax.crypto.spec.SecretKeySpec;
+
+public class CryptoUtils {
+    public static byte[] encryptAES(byte[] value, byte[] secretKey) throws InvalidKeyException, NoSuchPaddingException, NoSuchAlgorithmException, BadPaddingException, IllegalBlockSizeException {
+        @SuppressLint("GetInstance") Cipher ecipher = Cipher.getInstance("AES/ECB/NoPadding");
+        SecretKeySpec newKey = new SecretKeySpec(secretKey, "AES");
+        ecipher.init(Cipher.ENCRYPT_MODE, newKey);
+        return ecipher.doFinal(value);
+    }
+
+    public static byte[] decryptAES(byte[] value, byte[] secretKey) throws InvalidKeyException, NoSuchPaddingException, NoSuchAlgorithmException, BadPaddingException, IllegalBlockSizeException {
+        @SuppressLint("GetInstance") Cipher ecipher = Cipher.getInstance("AES/ECB/NoPadding");
+        SecretKeySpec newKey = new SecretKeySpec(secretKey, "AES");
+        ecipher.init(Cipher.DECRYPT_MODE, newKey);
+        return ecipher.doFinal(value);
+    }
+
+    public static byte[] encryptAES_CBC_Pad(byte[] data, byte[] key, byte[] iv) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
+        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
+        SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
+        AlgorithmParameterSpec paramSpec = new IvParameterSpec(iv);
+        cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, paramSpec);
+        return cipher.doFinal(data);
+    }
+
+    public static byte[] decryptAES_CBC_Pad(byte[] data, byte[] key, byte[] iv) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
+        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
+        SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
+        AlgorithmParameterSpec paramSpec = new IvParameterSpec(iv);
+        cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, paramSpec);
+        return cipher.doFinal(data);
+    }
+
+    public static byte[] encryptAES_GCM_NoPad(byte[] data, byte[] key, byte[] iv, byte[] aad) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
+        Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
+        SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
+        GCMParameterSpec paramSpec = new GCMParameterSpec(16 * 8, iv);
+        cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, paramSpec);
+        if (aad != null) {
+            cipher.updateAAD(aad);
+        }
+        return cipher.doFinal(data);
+    }
+
+    public static byte[] decryptAES_GCM_NoPad(byte[] data, byte[] key, byte[] iv, byte[] aad) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
+        Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
+        SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
+        GCMParameterSpec paramSpec = new GCMParameterSpec(16 * 8, iv);
+        cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, paramSpec);
+        if (aad != null) {
+            cipher.updateAAD(aad);
+        }
+        return cipher.doFinal(data);
+    }
+
+    public static byte[] calcHmacSha256(byte[] secretKey, byte[] message) throws NoSuchAlgorithmException, InvalidKeyException {
+        Mac mac = Mac.getInstance("HmacSHA256");
+        SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey, "HmacSHA256");
+        mac.init(secretKeySpec);
+        return mac.doFinal(message);
+    }
+
+    public static byte[] digest(byte[] message) throws NoSuchAlgorithmException {
+        MessageDigest digest = MessageDigest.getInstance("SHA-256");
+        return digest.digest(message);
+    }
+
+    // Thanks to https://www.javatips.net/api/keywhiz-master/hkdf/src/main/java/keywhiz/hkdf/Hkdf.java for light code
+    public static byte[] hkdfSha256(byte[] secretKey, byte[] salt, byte[] info, int outputLength) throws InvalidKeyException, NoSuchAlgorithmException { // return 32 byte len session key - outputLength=32 ?
+        //extract start
+        byte[] pseudoRandomKey = calcHmacSha256(salt, secretKey);
+        SecretKey pseudoSecretKey = new SecretKeySpec(pseudoRandomKey, "HmacSHA256");
+        //extract end
+        int hashLen = 32;
+        int n = (outputLength % hashLen == 0) ? outputLength / hashLen : (outputLength / hashLen) + 1;
+        byte[] hashRound = new byte[0];
+
+        ByteBuffer generatedBytes = ByteBuffer.allocate(n * hashLen);
+        Mac mac = Mac.getInstance("HmacSHA256");
+        mac.init(pseudoSecretKey);
+        for (int roundNum = 1; roundNum <= n; roundNum++) {
+          mac.reset();
+          ByteBuffer t = ByteBuffer.allocate(hashRound.length + info.length + 1);
+          t.put(hashRound);
+          t.put(info);
+          t.put((byte)roundNum);
+          hashRound = mac.doFinal(t.array());
+          generatedBytes.put(hashRound);
+        }
+
+        byte[] result = new byte[outputLength];
+        generatedBytes.rewind();
+        generatedBytes.get(result, 0, outputLength);
+        return result;
+    }
+
+    public static byte[] pbkdf2Sha256(String key, String iv, int count, int length) throws InvalidKeySpecException, NoSuchAlgorithmException, UnsupportedEncodingException {
+        SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
+        PBEKeySpec keySpec = new PBEKeySpec(key.toCharArray(), iv.getBytes("utf-8"), count, length);
+        return secretKeyFactory.generateSecret(keySpec).getEncoded();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DashboardUtils.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DashboardUtils.java
new file mode 100644
index 0000000..650712e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DashboardUtils.java
@@ -0,0 +1,235 @@
+/*  Copyright (C) 2024 Arjan Schrijver
+
+    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 <http://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.util;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.activities.DashboardFragment;
+import nodomain.freeyourgadget.gadgetbridge.activities.charts.StepAnalysis;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.AbstractActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary;
+import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummaryDao;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySession;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
+import nodomain.freeyourgadget.gadgetbridge.model.DailyTotals;
+
+public class DashboardUtils {
+    private static final Logger LOG = LoggerFactory.getLogger(DashboardUtils.class);
+
+    public static DailyTotals getDailyTotals(GBDevice device, DBHandler db, int timeTo) {
+        Calendar day = GregorianCalendar.getInstance();
+        day.setTimeInMillis(timeTo * 1000L);
+        return DailyTotals.getDailyTotalsForDevice(device, day, db);
+    }
+
+    public static int getStepsTotal(DashboardFragment.DashboardData dashboardData) {
+        List<GBDevice> devices = GBApplication.app().getDeviceManager().getDevices();
+        int totalSteps = 0;
+        try (DBHandler dbHandler = GBApplication.acquireDB()) {
+            for (GBDevice dev : devices) {
+                if ((dashboardData.showAllDevices || dashboardData.showDeviceList.contains(dev.getAddress())) && dev.getDeviceCoordinator().supportsActivityTracking()) {
+                    totalSteps += (int) getDailyTotals(dev, dbHandler, dashboardData.timeTo).getSteps();
+                }
+            }
+        } catch (Exception e) {
+            LOG.warn("Could not calculate total amount of steps: ", e);
+        }
+        return totalSteps;
+    }
+
+    public static int getActiveCaloriesTotal(DashboardFragment.DashboardData dashboardData) {
+        List<GBDevice> devices = GBApplication.app().getDeviceManager().getDevices();
+        int totalActiveCalories = 0;
+        try (DBHandler dbHandler = GBApplication.acquireDB()) {
+            for (GBDevice dev : devices) {
+                if ((dashboardData.showAllDevices || dashboardData.showDeviceList.contains(dev.getAddress())) && dev.getDeviceCoordinator().supportsActiveCalories()) {
+                    totalActiveCalories += (int) getDailyTotals(dev, dbHandler, dashboardData.timeTo).getActiveCalories();
+                }
+            }
+        } catch (Exception e) {
+            LOG.warn("Could not calculate total amount of active calories: ", e);
+        }
+        // Convert calories to kcal
+        return totalActiveCalories / 1000;
+    }
+
+    public static int getRestingCaloriesTotal(DashboardFragment.DashboardData dashboardData) {
+        List<GBDevice> devices = GBApplication.app().getDeviceManager().getDevices();
+        int totalRestingCalories = 0;
+        try (DBHandler dbHandler = GBApplication.acquireDB()) {
+            for (GBDevice dev : devices) {
+                if ((dashboardData.showAllDevices || dashboardData.showDeviceList.contains(dev.getAddress())) && dev.getDeviceCoordinator().supportsActiveCalories()) {
+                    totalRestingCalories += (int) getDailyTotals(dev, dbHandler, dashboardData.timeTo).getRestingCalories();
+                }
+            }
+        } catch (Exception e) {
+            LOG.warn("Could not calculate total amount of resting calories: ", e);
+        }
+        return totalRestingCalories;
+    }
+
+    public static float getStepsGoalFactor(DashboardFragment.DashboardData dashboardData) {
+        ActivityUser activityUser = new ActivityUser();
+        float stepsGoal = activityUser.getStepsGoal();
+        float goalFactor = getStepsTotal(dashboardData) / stepsGoal;
+        if (goalFactor > 1) goalFactor = 1;
+
+        return goalFactor;
+    }
+
+    public static long getSleep(GBDevice device, DBHandler db, int timeTo) {
+        Calendar day = GregorianCalendar.getInstance();
+        day.setTimeInMillis(timeTo * 1000L);
+        return DailyTotals.getDailyTotalsForDevice(device, day, db).getSleep();
+    }
+
+    public static long getSleepMinutesTotal(DashboardFragment.DashboardData dashboardData) {
+        List<GBDevice> devices = GBApplication.app().getDeviceManager().getDevices();
+        long totalSleepMinutes = 0;
+        try (DBHandler dbHandler = GBApplication.acquireDB()) {
+            for (GBDevice dev : devices) {
+                if ((dashboardData.showAllDevices || dashboardData.showDeviceList.contains(dev.getAddress())) && dev.getDeviceCoordinator().supportsActivityTracking()) {
+                    totalSleepMinutes += getSleep(dev, dbHandler, dashboardData.timeTo);
+                }
+            }
+        } catch (Exception e) {
+            LOG.warn("Could not calculate total amount of sleep: ", e);
+        }
+        return totalSleepMinutes;
+    }
+
+    public static float getSleepMinutesGoalFactor(DashboardFragment.DashboardData dashboardData) {
+        ActivityUser activityUser = new ActivityUser();
+        int sleepMinutesGoal = activityUser.getSleepDurationGoal() * 60;
+        float goalFactor = (float) getSleepMinutesTotal(dashboardData) / sleepMinutesGoal;
+        if (goalFactor > 1) goalFactor = 1;
+
+        return goalFactor;
+    }
+
+    public static float getDistanceTotal(DashboardFragment.DashboardData dashboardData) {
+        ActivityUser activityUser = new ActivityUser();
+        int stepLength = activityUser.getStepLengthCm();
+
+        List<GBDevice> devices = GBApplication.app().getDeviceManager().getDevices();
+        long totalDistanceCm = 0;
+        try (DBHandler dbHandler = GBApplication.acquireDB()) {
+            for (GBDevice dev : devices) {
+                if ((dashboardData.showAllDevices || dashboardData.showDeviceList.contains(dev.getAddress())) && dev.getDeviceCoordinator().supportsActivityTracking()) {
+                    final DailyTotals dailyTotals = getDailyTotals(dev, dbHandler, dashboardData.timeTo);
+                    if (dailyTotals.getSteps() > 0 && dailyTotals.getDistance() > 0) {
+                        totalDistanceCm += dailyTotals.getDistance();
+                    } else {
+                        totalDistanceCm += dailyTotals.getSteps() * stepLength;
+                    }
+                }
+            }
+        } catch (Exception e) {
+            LOG.warn("Could not calculate total distance: ", e);
+        }
+        return totalDistanceCm * 0.01f;
+    }
+
+    public static float getDistanceGoalFactor(DashboardFragment.DashboardData dashboardData) {
+        ActivityUser activityUser = new ActivityUser();
+        int distanceGoal = activityUser.getDistanceGoalMeters();
+        float goalFactor = getDistanceTotal(dashboardData) / distanceGoal;
+        if (goalFactor > 1) goalFactor = 1;
+
+        return goalFactor;
+    }
+
+    public static float getActiveCaloriesGoalFactor(DashboardFragment.DashboardData dashboardData) {
+        ActivityUser activityUser = new ActivityUser();
+        int caloriesGoal = activityUser.getCaloriesBurntGoal();
+        float goalFactor = (float) getActiveCaloriesTotal(dashboardData) / caloriesGoal;
+        if (goalFactor > 1) goalFactor = 1;
+
+        return goalFactor;
+    }
+
+    public static long getActiveMinutesTotal(DashboardFragment.DashboardData dashboardData) {
+        List<GBDevice> devices = GBApplication.app().getDeviceManager().getDevices();
+        long totalActiveMinutes = 0;
+        try (DBHandler dbHandler = GBApplication.acquireDB()) {
+            for (GBDevice dev : devices) {
+                if ((dashboardData.showAllDevices || dashboardData.showDeviceList.contains(dev.getAddress())) && dev.getDeviceCoordinator().supportsActivityTracking()) {
+                    totalActiveMinutes += getActiveMinutes(dev, dbHandler, dashboardData);
+                }
+            }
+        } catch (Exception e) {
+            LOG.warn("Could not calculate total amount of activity: ", e);
+        }
+        return totalActiveMinutes;
+    }
+
+    public static float getActiveMinutesGoalFactor(DashboardFragment.DashboardData dashboardData) {
+        ActivityUser activityUser = new ActivityUser();
+        int activeTimeGoal = activityUser.getActiveTimeGoalMinutes();
+        float goalFactor = (float) getActiveMinutesTotal(dashboardData) / activeTimeGoal;
+        if (goalFactor > 1) goalFactor = 1;
+
+        return goalFactor;
+    }
+
+    public static long getActiveMinutes(GBDevice gbDevice, DBHandler db, DashboardFragment.DashboardData dashboardData) {
+        ActivitySession stepSessionsSummary = new ActivitySession();
+        List<ActivitySession> stepSessions;
+        List<? extends ActivitySample> activitySamples = getAllSamples(db, gbDevice, dashboardData);
+        StepAnalysis stepAnalysis = new StepAnalysis();
+
+        boolean isEmptySummary = false;
+        if (activitySamples != null) {
+            stepSessions = stepAnalysis.calculateStepSessions(activitySamples);
+            if (stepSessions.toArray().length == 0) {
+                isEmptySummary = true;
+            }
+            stepSessionsSummary = stepAnalysis.calculateSummary(stepSessions, isEmptySummary);
+        }
+        long duration = stepSessionsSummary.getEndTime().getTime() - stepSessionsSummary.getStartTime().getTime();
+        return duration / 1000 / 60;
+    }
+
+    public static List<? extends ActivitySample> getAllSamples(DBHandler db, GBDevice device, DashboardFragment.DashboardData dashboardData) {
+        SampleProvider<? extends ActivitySample> provider = getProvider(db, device);
+        return provider.getAllActivitySamples(dashboardData.timeFrom, dashboardData.timeTo);
+    }
+
+    protected static SampleProvider<? extends AbstractActivitySample> getProvider(DBHandler db, GBDevice device) {
+        DeviceCoordinator coordinator = device.getDeviceCoordinator();
+        return coordinator.getSampleProvider(device, db.getDaoSession());
+    }
+
+    public static List<BaseActivitySummary> getWorkoutSamples(DBHandler db, DashboardFragment.DashboardData dashboardData) {
+        return db.getDaoSession().getBaseActivitySummaryDao().queryBuilder().where(
+                BaseActivitySummaryDao.Properties.StartTime.gt(new Date(dashboardData.timeFrom * 1000L)),
+                BaseActivitySummaryDao.Properties.EndTime.lt(new Date(dashboardData.timeTo * 1000L))
+        ).build().list();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DateTimeUtils.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DateTimeUtils.java
new file mode 100644
index 0000000..e8f2dd5
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DateTimeUtils.java
@@ -0,0 +1,322 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, AndrewH, Carsten Pfeiffer,
+    Daniele Gobbetti, Jos茅 Rebelo, Pavel Elagin, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.util;
+
+import android.content.Context;
+import android.text.format.DateUtils;
+
+import com.github.pfichtner.durationformatter.DurationFormatter;
+
+import java.text.FieldPosition;
+import java.text.ParseException;
+import java.text.ParsePosition;
+import java.text.SimpleDateFormat;
+import java.time.LocalDate;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.Locale;
+import java.util.TimeZone;
+import java.util.concurrent.TimeUnit;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+
+public class DateTimeUtils {
+    private static SimpleDateFormat DAY_STORAGE_FORMAT = new SimpleDateFormat("yyyy-MM-dd", Locale.US);
+    private static SimpleDateFormat HOURS_MINUTES_FORMAT = new SimpleDateFormat("HH:mm", Locale.US);
+    public static SimpleDateFormat ISO_8601_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.US){
+        //see https://github.com/Freeyourgadget/Gadgetbridge/issues/1076#issuecomment-383834116 and https://stackoverflow.com/a/30221245
+
+        @Override
+        public Date parse(String text, ParsePosition pos) {
+            if (text.length() > 3) {
+                text = text.substring(0, text.length() - 3) + text.substring(text.length() - 2);
+            }
+            return super.parse(text, pos);
+
+        }
+
+        @Override
+        public StringBuffer format(Date date, StringBuffer toAppendTo, FieldPosition pos) {
+            StringBuffer rfcFormat = super.format(date, toAppendTo, pos);
+            if (this.getTimeZone().equals(TimeZone.getTimeZone("UTC"))) {
+                rfcFormat.setLength(rfcFormat.length()-5);
+                return rfcFormat.append("Z");
+            } else {
+                return rfcFormat.insert(rfcFormat.length() - 2, ":");
+            }
+        }
+
+    }; //no public access, we have to workaround Android bugs
+
+    public static String formatDateTime(Date date) {
+        return DateUtils.formatDateTime(GBApplication.getContext(), date.getTime(), DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_NO_YEAR);
+    }
+
+    public static String formatIso8601(Date date) {
+        if(GBApplication.isRunningNougatOrLater()){
+            return new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX", Locale.US).format(date);
+        }
+        ISO_8601_FORMAT.setTimeZone(TimeZone.getDefault());
+        return ISO_8601_FORMAT.format(date);
+    }
+
+    public static String formatIso8601UTC(Date date) {
+        if(GBApplication.isRunningNougatOrLater()){
+            final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX", Locale.US);
+            sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
+            return sdf.format(date);
+        }
+        ISO_8601_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC"));
+        return ISO_8601_FORMAT.format(date);
+    }
+
+    public static String formatDate(Date date) {
+        return DateUtils.formatDateTime(GBApplication.getContext(), date.getTime(), DateUtils.FORMAT_SHOW_DATE|DateUtils.FORMAT_ABBREV_ALL);
+    }
+
+    public static String formatDate(Date date, int extraFlags) {
+        return DateUtils.formatDateTime(GBApplication.getContext(), date.getTime(), DateUtils.FORMAT_SHOW_DATE|DateUtils.FORMAT_ABBREV_ALL|extraFlags);
+    }
+
+    public static String formatDurationHoursMinutes(long duration, TimeUnit unit) {
+        DurationFormatter df = DurationFormatter.Builder.SYMBOLS
+                .maximum(TimeUnit.DAYS)
+                .minimum(TimeUnit.SECONDS)
+                .suppressZeros(DurationFormatter.SuppressZeros.LEADING, DurationFormatter.SuppressZeros.TRAILING)
+                .maximumAmountOfUnitsToShow(2)
+                .build();
+        return df.format(duration, unit);
+    }
+
+    public static String formatDateRange(Date from, Date to) {
+        return DateUtils.formatDateRange(GBApplication.getContext(), from.getTime(), to.getTime(), DateUtils.FORMAT_SHOW_DATE|DateUtils.FORMAT_ABBREV_ALL);
+    }
+
+    public static String formatDateRange(Date from, Date to, int extraFlags) {
+        return DateUtils.formatDateRange(GBApplication.getContext(), from.getTime(), to.getTime(), DateUtils.FORMAT_SHOW_DATE|DateUtils.FORMAT_ABBREV_ALL|extraFlags);
+    }
+
+    public static Date shiftByDays(Date date, int offset) {
+        Calendar cal = GregorianCalendar.getInstance();
+        cal.setTime(date);
+        cal.add(GregorianCalendar.DAY_OF_YEAR, offset);
+        Date newDate = cal.getTime();
+        return newDate;
+    }
+
+    public static Date dayStart(final Date date) {
+        final Calendar calendar = Calendar.getInstance();
+        calendar.setTime(date);
+        calendar.set(Calendar.HOUR_OF_DAY, 0);
+        calendar.set(Calendar.MINUTE, 0);
+        calendar.set(Calendar.SECOND, 0);
+        calendar.set(Calendar.MILLISECOND, 0);
+        return calendar.getTime();
+    }
+
+    public static Calendar dayStart(final Calendar calendar) {
+        final Calendar ret = (Calendar) calendar.clone();
+        ret.set(Calendar.HOUR_OF_DAY, 0);
+        ret.set(Calendar.MINUTE, 0);
+        ret.set(Calendar.SECOND, 0);
+        ret.set(Calendar.MILLISECOND, 0);
+        return ret;
+    }
+
+    public static Date dayStartUtc(final LocalDate date) {
+        final Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
+        calendar.set(Calendar.YEAR, date.getYear());
+        calendar.set(Calendar.MONTH, date.getMonthValue() - 1);
+        calendar.set(Calendar.DAY_OF_MONTH, date.getDayOfMonth());
+        calendar.set(Calendar.HOUR_OF_DAY, 0);
+        calendar.set(Calendar.MINUTE, 0);
+        calendar.set(Calendar.SECOND, 0);
+        calendar.set(Calendar.MILLISECOND, 0);
+        return calendar.getTime();
+    }
+
+    public static long utcDateTimeToLocal(final long timestamp) {
+        final Calendar utcCalendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
+        utcCalendar.setTimeInMillis(timestamp);
+        final Calendar localCalendar = Calendar.getInstance(TimeZone.getDefault());
+        localCalendar.set(Calendar.YEAR, utcCalendar.get(Calendar.YEAR));
+        localCalendar.set(Calendar.MONTH, utcCalendar.get(Calendar.MONTH));
+        localCalendar.set(Calendar.DAY_OF_MONTH, utcCalendar.get(Calendar.DAY_OF_MONTH));
+        localCalendar.set(Calendar.HOUR_OF_DAY, utcCalendar.get(Calendar.HOUR_OF_DAY));
+        localCalendar.set(Calendar.MINUTE, utcCalendar.get(Calendar.MINUTE));
+        localCalendar.set(Calendar.SECOND, utcCalendar.get(Calendar.SECOND));
+        localCalendar.set(Calendar.MILLISECOND, utcCalendar.get(Calendar.MILLISECOND));
+        return localCalendar.getTimeInMillis();
+    }
+
+    public static Date dayEnd(final Date date) {
+        final Calendar calendar = Calendar.getInstance();
+        calendar.setTime(date);
+        calendar.set(Calendar.HOUR_OF_DAY, 23);
+        calendar.set(Calendar.MINUTE, 59);
+        calendar.set(Calendar.SECOND, 59);
+        calendar.set(Calendar.MILLISECOND, 999);
+        return calendar.getTime();
+    }
+
+    public static Date parseTimeStamp(int timestamp) {
+        GregorianCalendar cal = (GregorianCalendar) GregorianCalendar.getInstance();
+        cal.setTimeInMillis(timestamp * 1000L); // make sure it's converted to long
+        return cal.getTime();
+    }
+
+    public static Date parseTimestampMillis(long timestamp) {
+        GregorianCalendar cal = (GregorianCalendar) GregorianCalendar.getInstance();
+        cal.setTimeInMillis(timestamp);
+        return cal.getTime();
+    }
+
+    public static String dayToString(Date date) {
+        return DAY_STORAGE_FORMAT.format(date);
+    }
+
+    public static Date dayFromString(String day) throws ParseException {
+        return DAY_STORAGE_FORMAT.parse(day);
+    }
+
+    public static String timeToString(Date date) {
+        return HOURS_MINUTES_FORMAT.format(date);
+    }
+
+    public static String formatTime(int hours, int minutes) {
+        return String.format(Locale.US, "%02d", hours) + ":" + String.format(Locale.US, "%02d", minutes);
+    }
+
+
+    public static Date todayUTC() {
+        Calendar cal = getCalendarUTC();
+        return cal.getTime();
+    }
+
+    public static Calendar getCalendarUTC() {
+        return GregorianCalendar.getInstance(TimeZone.getTimeZone("UTC"));
+    }
+
+    public static String minutesToHHMM(int minutes) {
+        return String.format(Locale.US, "%d:%02d", minutes / 60, minutes % 60); // no I do not want to use durationformatter :P
+    }
+    public static boolean isYesterday(Date d) {
+        return DateUtils.isToday(d.getTime() + DateUtils.DAY_IN_MILLIS);
+    }
+
+    /**
+     * Calculates new timestamp with a month offset (positive to add or negative to remove)
+     * from a given time
+     *
+     * @param time
+     * @param month
+     */
+    public static int shiftMonths(int time, int month) {
+        Calendar day = Calendar.getInstance();
+        day.setTimeInMillis(time * 1000L);
+        day.add(Calendar.MONTH, month);
+        return (int) (day.getTimeInMillis() / 1000);
+    }
+
+    /**
+     * Calculates new timestamp with a day offset (positive to add or negative to remove)
+     * from a given time
+     *
+     * @param time
+     * @param days
+     */
+    public static int shiftDays(int time, int days) {
+        Calendar day = Calendar.getInstance();
+        day.setTimeInMillis(time * 1000L);
+        day.add(Calendar.DAY_OF_YEAR, days);
+        return (int) (day.getTimeInMillis() / 1000);
+    }
+
+    /**
+     * Calculates difference in days between two timestamps
+     *
+     * @param time1
+     * @param time2
+     */
+    public static int  getDaysBetweenTimes(int time1, int time2) {
+        return (int) TimeUnit.MILLISECONDS.toDays((time2 - time1) * 1000L);
+    }
+
+    /**
+     * Determine whether two Calendar instances are on the same day
+     *
+     * @param calendar1 The first calendar to compare
+     * @param calendar2 The second calendar to compare
+     * @return true if the Calendar instances are on the same day
+     */
+    public static boolean isSameDay(Calendar calendar1, Calendar calendar2) {
+        return calendar1.get(Calendar.YEAR) == calendar2.get(Calendar.YEAR)
+                && calendar1.get(Calendar.MONTH) == calendar2.get(Calendar.MONTH)
+                && calendar1.get(Calendar.DAY_OF_MONTH) == calendar2.get(Calendar.DAY_OF_MONTH);
+    }
+
+    /**
+     * Determine whether two Date instances are on the same day
+     *
+     * @param date1 The first date to compare
+     * @param date2 The second date to compare
+     * @return true if the Date instances are on the same day
+     */
+    public static boolean isSameDay(Date date1, Date date2) {
+        final Calendar calendar1 = GregorianCalendar.getInstance();
+        calendar1.setTime(date1);
+        final Calendar calendar2 = GregorianCalendar.getInstance();
+        calendar2.setTime(date2);
+
+        return isSameDay(calendar1, calendar2);
+    }
+
+    /**
+     * Determine whether two Calendar instances are in the same month
+     *
+     * @param calendar1 The first calendar to compare
+     * @param calendar2 The second calendar to compare
+     * @return true if the Calendar instances are in the same month
+     */
+    public static boolean isSameMonth(Calendar calendar1, Calendar calendar2) {
+        return calendar1.get(Calendar.YEAR) == calendar2.get(Calendar.YEAR)
+                && calendar1.get(Calendar.MONTH) == calendar2.get(Calendar.MONTH);
+    }
+
+    public static String formatDateRelative(final Context context, final Date date) {
+        if (DateUtils.isToday(date.getTime())) {
+            return context.getString(R.string.activity_summary_today);
+        } else if (DateTimeUtils.isYesterday(date)) {
+            return context.getString(R.string.activity_summary_yesterday);
+        } else {
+            return DateTimeUtils.formatDate(date, DateUtils.FORMAT_SHOW_WEEKDAY);
+        }
+    }
+
+    public static String formatDateTimeRelative(final Context context, final Date date) {
+        if (date != null) {
+            final String activityDay = formatDateRelative(context, date);
+            final String activityTime = DateTimeUtils.formatTime(date.getHours(), date.getMinutes());
+            return context.getString(R.string.date_placeholders__date__time, activityDay, activityTime);
+        }
+
+        return context.getString(R.string.unknown);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DeviceHelper.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DeviceHelper.java
new file mode 100644
index 0000000..054f294
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DeviceHelper.java
@@ -0,0 +1,225 @@
+/*  Copyright (C) 2015-2024 115ek, akasaka / Genjitsu Labs, Andreas B枚hler,
+    Andreas Shimokawa, Andrew Watkins, angelpup, Arjan Schrijver, Carsten Pfeiffer,
+    Cre3per, DanialHanif, Daniel Dakhno, Daniele Gobbetti, Daniel Thompson, Da Pa,
+    Dmytro Bielik, Frank Ertl, GeekosaurusR3x, Gordon Williams, Jean-Fran莽ois
+    Greffier, jfgreffier, jhey, Joa虄o Paulo Barraca, Jochen S, Johannes Krude,
+    Jos茅 Rebelo, ksiwczynski, ladbsoft, Lesur Frederic, Maciej Ku艣nierz, mamucho,
+    Manuel Ru脽, maxirnilian, mkusnierz, narektor, Noodlez, odavo32nof, opavlov,
+    pangwalla, Pavel Elagin, Petr Kadlec, Petr Van臎k, protomors, Quallenauge,
+    Quang Ng么, Raghd Hamzeh, Sami Alaoui, Sebastian Kranz, sedy89, Sophanimus,
+    Stefan Bora, Taavi Eom盲e, thermatk, tiparega, Vadim Kaushan, x29a, xaos,
+    Yukai Li
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.util;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.content.Context;
+import android.widget.Toast;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.GBException;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.entities.DeviceAttributes;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
+import nodomain.freeyourgadget.gadgetbridge.model.BatteryConfig;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+
+public class DeviceHelper {
+    private static final Logger LOG = LoggerFactory.getLogger(DeviceHelper.class);
+
+    private static final DeviceHelper instance = new DeviceHelper();
+
+    private DeviceType[] orderedDeviceTypes = null;
+
+    public static DeviceHelper getInstance() {
+        return instance;
+    }
+
+    private final HashMap<String, DeviceType> deviceTypeCache = new HashMap<>();
+
+    public GBDevice findAvailableDevice(String deviceAddress, Context context) {
+        Set<GBDevice> availableDevices = getAvailableDevices(context);
+        for (GBDevice availableDevice : availableDevices) {
+            if (deviceAddress.equals(availableDevice.getAddress())) {
+                return availableDevice;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Returns the list of all available devices that are supported by Gadgetbridge.
+     * Note that no state is known about the returned devices. Even if one of those
+     * devices is connected, it will report the default not-connected state.
+     *
+     * Clients interested in the "live" devices being managed should use the class
+     * DeviceManager.
+     * @param context
+     * @return
+     */
+    public Set<GBDevice> getAvailableDevices(Context context) {
+        BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter();
+
+        if (btAdapter == null) {
+            GB.toast(context, context.getString(R.string.bluetooth_is_not_supported_), Toast.LENGTH_SHORT, GB.WARN);
+        } else if (!btAdapter.isEnabled()) {
+            GB.toast(context, context.getString(R.string.bluetooth_is_disabled_), Toast.LENGTH_SHORT, GB.WARN);
+        }
+
+        Set<GBDevice> availableDevices = new LinkedHashSet<>(getDatabaseDevices());
+        Prefs prefs = GBApplication.getPrefs();
+        return availableDevices;
+    }
+
+    public GBDevice toSupportedDevice(BluetoothDevice device) {
+        GBDeviceCandidate candidate = new GBDeviceCandidate(device, GBDevice.RSSI_UNKNOWN, device.getUuids());
+        candidate.refreshNameIfUnknown();
+        return toSupportedDevice(candidate);
+    }
+
+    public GBDevice toSupportedDevice(GBDeviceCandidate candidate) {
+        DeviceType resolvedType = resolveDeviceType(candidate);
+        return resolvedType.getDeviceCoordinator().createDevice(candidate, resolvedType);
+    }
+
+    private DeviceType[] getOrderedDeviceTypes(){
+        if(orderedDeviceTypes == null){
+            ArrayList<DeviceType> orderedDevices = new ArrayList<>(Arrays.asList(DeviceType.values()));
+            Collections.sort(orderedDevices, Comparator.comparingInt(dc -> dc.getDeviceCoordinator().getOrderPriority()));
+            orderedDeviceTypes = orderedDevices.toArray(new DeviceType[0]);
+        }
+
+        return orderedDeviceTypes;
+    }
+    public DeviceType resolveDeviceType(GBDeviceCandidate deviceCandidate) {
+        return resolveDeviceType(deviceCandidate, true);
+    }
+
+    public DeviceType resolveDeviceType(GBDeviceCandidate deviceCandidate, boolean useCache){
+        synchronized (this) {
+            if(useCache) {
+                DeviceType cachedType =
+                        deviceTypeCache.get(deviceCandidate.getMacAddress().toLowerCase());
+                if (cachedType != null) {
+                    return cachedType;
+                }
+            }
+
+            for (DeviceType type : getOrderedDeviceTypes()) {
+                if (type.getDeviceCoordinator().supports(deviceCandidate)) {
+                    deviceTypeCache.put(deviceCandidate.getMacAddress().toLowerCase(), type);
+                    return type;
+                }
+            }
+            deviceTypeCache.put(deviceCandidate.getMacAddress().toLowerCase(), DeviceType.UNKNOWN);
+        }
+        return DeviceType.UNKNOWN;
+    }
+
+    public DeviceCoordinator resolveCoordinator(GBDeviceCandidate device) {
+        return resolveDeviceType(device).getDeviceCoordinator();
+    }
+
+    private List<GBDevice> getDatabaseDevices() {
+        List<GBDevice> result = new ArrayList<>();
+        try (DBHandler lockHandler = GBApplication.acquireDB()) {
+            List<Device> activeDevices = DBHelper.getActiveDevices(lockHandler.getDaoSession());
+            for (Device dbDevice : activeDevices) {
+                GBDevice gbDevice = toGBDevice(dbDevice);
+                if (gbDevice != null && gbDevice.getType().isSupported()) {
+                    result.add(gbDevice);
+                }
+            }
+            return result;
+
+        } catch (Exception e) {
+            GB.toast(GBApplication.getContext().getString(R.string.error_retrieving_devices_database), Toast.LENGTH_SHORT, GB.ERROR, e);
+            return Collections.emptyList();
+        }
+    }
+
+    /**
+     * Converts a known device from the database to a GBDevice.
+     * Note: The device might not be supported anymore, so callers should verify that.
+     * @param dbDevice
+     * @return
+     */
+    public GBDevice toGBDevice(Device dbDevice) {
+        DeviceType deviceType = DeviceType.fromName(dbDevice.getTypeName());
+        GBDevice gbDevice = new GBDevice(dbDevice.getIdentifier(), dbDevice.getName(), dbDevice.getAlias(), dbDevice.getParentFolder(), deviceType);
+        DeviceCoordinator coordinator = gbDevice.getDeviceCoordinator();
+        for (BatteryConfig batteryConfig : coordinator.getBatteryConfig(gbDevice)) {
+            gbDevice.setBatteryIcon(batteryConfig.getBatteryIcon(), batteryConfig.getBatteryIndex());
+            gbDevice.setBatteryLabel(batteryConfig.getBatteryLabel(), batteryConfig.getBatteryIndex());
+        }
+
+        List<DeviceAttributes> deviceAttributesList = dbDevice.getDeviceAttributesList();
+        if (deviceAttributesList.size() > 0) {
+            gbDevice.setModel(dbDevice.getModel());
+            DeviceAttributes attrs = deviceAttributesList.get(0);
+            gbDevice.setFirmwareVersion(attrs.getFirmwareVersion1());
+            gbDevice.setFirmwareVersion2(attrs.getFirmwareVersion2());
+            gbDevice.setVolatileAddress(attrs.getVolatileIdentifier());
+        }
+
+        return gbDevice;
+    }
+
+    /**
+     * Attempts to removing the bonding with the given device. Returns true
+     * if bonding was supposedly successful and false if anything went wrong
+     * @param device
+     * @return
+     */
+    public boolean removeBond(GBDevice device) throws GBException {
+        BluetoothAdapter defaultAdapter = BluetoothAdapter.getDefaultAdapter();
+        if (defaultAdapter != null) {
+            BluetoothDevice remoteDevice = defaultAdapter.getRemoteDevice(device.getAddress());
+            if (remoteDevice != null) {
+                try {
+                    Method method = BluetoothDevice.class.getMethod("removeBond", (Class[]) null);
+                    Object result = method.invoke(remoteDevice, (Object[]) null);
+                    return Boolean.TRUE.equals(result);
+                } catch (Exception e) {
+                    throw new GBException("Error removing bond to device: " + device, e);
+                }
+            }
+        }
+        return false;
+    }
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/ECDH_B163.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/ECDH_B163.java
new file mode 100644
index 0000000..28d3509
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/ECDH_B163.java
@@ -0,0 +1,515 @@
+/*  Copyright (C) 2022-2024 Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+
+
+/*
+    This class is a really dumb pure java port of tiny-EDCH from here
+    https://github.com/kokke/tiny-ECDH-c/
+
+    What I did:
+    - remove all curves except B163 to make porting easier
+    - port to java with brain switched off
+    - fix the "java has no unsigned" bugs
+    - add some helpers to convert int[] to byte[] and back because java has no casts
+
+    The result is ugly, no one would write such crappy code from scratch, but I tried to
+    keep it as close to the C code as possible to prevent bugs. Since I did not know what
+    I was doing.
+ */
+
+
+package nodomain.freeyourgadget.gadgetbridge.util;
+
+public class ECDH_B163 {
+
+    static final int CURVE_DEGREE = 163;
+    static final int ECC_PRV_KEY_SIZE = 24;
+    static final int ECC_PUB_KEY_SIZE = 2 * ECC_PRV_KEY_SIZE;
+
+    /* margin for overhead needed in intermediate calculations */
+    static final int BITVEC_MARGIN = 3;
+    static final int BITVEC_NBITS = (CURVE_DEGREE + BITVEC_MARGIN);
+    static final int BITVEC_NWORDS = ((BITVEC_NBITS + 31) / 32);
+    static final int BITVEC_NBYTES = (4 * BITVEC_NWORDS);
+
+    /******************************************************************************/
+
+    /* Here the curve parameters are defined. */
+
+    /* NIST B-163 */
+    static final int[] polynomial = {0x000000c9, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000008};
+    static final int[] coeff_b = {0x4a3205fd, 0x512f7874, 0x1481eb10, 0xb8c953ca, 0x0a601907, 0x00000002};
+    static final int[] base_x = {0xe8343e36, 0xd4994637, 0xa0991168, 0x86a2d57e, 0xf0eba162, 0x00000003};
+    static final int[] base_y = {0x797324f1, 0xb11c5c0c, 0xa2cdd545, 0x71a0094f, 0xd51fbc6c, 0x00000000};
+    static final int[] base_order = {0xa4234c33, 0x77e70c12, 0x000292fe, 0x00000000, 0x00000000, 0x00000004};
+
+    /*************************************************************************************************/
+
+    /* Private / static functions: */
+
+    /* some basic bit-manipulation routines that act on bit-vectors follow */
+    static int bitvec_get_bit(final int[] x, final int idx) {
+        return (int) ((((((long) x[idx / 32] & 0xffffffffL) >> (idx & 31)) & 1)));
+    }
+
+    static void bitvec_clr_bit(final int[] x, final int idx) {
+        x[idx / 32] &= ~(1 << (idx & 31));
+    }
+
+    static void bitvec_copy(int[] x, int[] y) {
+        int i;
+        for (i = 0; i < BITVEC_NWORDS; ++i) {
+            x[i] = y[i];
+        }
+    }
+
+    static void bitvec_swap(int[] x, int[] y) {
+        int[] tmp = new int[BITVEC_NWORDS];
+        bitvec_copy(tmp, x);
+        bitvec_copy(x, y);
+        bitvec_copy(y, tmp);
+    }
+
+    /* fast version of equality test */
+    static boolean bitvec_equal(final int[] x, final int[] y) {
+        int i;
+        for (i = 0; i < BITVEC_NWORDS; ++i) {
+            if (x[i] != y[i]) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    static void bitvec_set_zero(int[] x) {
+        int i;
+        for (i = 0; i < BITVEC_NWORDS; ++i) {
+            x[i] = 0;
+        }
+    }
+
+    /* fast implementation */
+    static boolean bitvec_is_zero(final int[] x) {
+        int i = 0;
+        while (i < BITVEC_NWORDS) {
+            if (x[i] != 0) {
+                break;
+            }
+            i += 1;
+        }
+        return (i == BITVEC_NWORDS);
+    }
+
+    /* return the number of the highest one-bit + 1 */
+    static int bitvec_degree(final int[] x) {
+        int i = BITVEC_NWORDS * 32;
+
+        /* Start at the back of the vector (MSB) */
+        int y = BITVEC_NWORDS;
+
+        /* Skip empty / zero words */
+        while ((i > 0)
+                && (x[--y] == 0)) {
+            i -= 32;
+        }
+        /* Run through rest if count is not multiple of bitsize of DTYPE */
+        if (i != 0) {
+            int u32mask = ((int) 1 << 31);
+            while (((x[y]) & u32mask) == 0) {
+                u32mask = (int) (((long) u32mask & 0xffffffffL) >> 1);
+                i -= 1;
+            }
+        }
+        return i;
+    }
+
+    /* left-shift by 'count' digits */
+    static void bitvec_lshift(int[] x, final int[] y, int nbits) {
+        int nwords = (nbits / 32);
+
+        /* Shift whole words first if nwords > 0 */
+        int i, j;
+        for (i = 0; i < nwords; ++i) {
+            /* Zero-initialize from least-significant word until offset reached */
+            x[i] = 0;
+        }
+        j = 0;
+        /* Copy to x output */
+        while (i < BITVEC_NWORDS) {
+            x[i] = y[j];
+            i += 1;
+            j += 1;
+        }
+
+        /* Shift the rest if count was not multiple of bitsize of DTYPE */
+        nbits &= 31;
+        if (nbits != 0) {
+            /* Left shift rest */
+            for (i = (BITVEC_NWORDS - 1); i > 0; --i) {
+                x[i] = (int) (((long) (x[i]) << nbits) | (((long) x[i - 1] & 0xffffffffL) >> (32 - nbits)));
+            }
+            x[0] = (int) ((long) (x[0]) << nbits);
+        }
+    }
+
+    /*************************************************************************************************/
+    /*
+     * Code that does arithmetic on bit-vectors in the Galois Field
+     * GF(2^CURVE_DEGREE).
+     */
+
+    /*************************************************************************************************/
+
+    static void gf2field_set_one(int[] x) {
+        /* Set first word to one */
+        x[0] = 1;
+        /* .. and the rest to zero */
+        int i;
+        for (i = 1; i < BITVEC_NWORDS; ++i) {
+            x[i] = 0;
+        }
+    }
+
+
+    /* fastest check if x == 1 */
+    static boolean gf2field_is_one(int[] x) {
+        /* Check if first word == 1 */
+        if (x[0] != 1) {
+            return false;
+        }
+        /* ...and if rest of words == 0 */
+        int i;
+        for (i = 1; i < BITVEC_NWORDS; ++i) {
+            if (x[i] != 0) {
+                break;
+            }
+        }
+        return (i == BITVEC_NWORDS);
+    }
+
+    /* galois field(2^m) addition is modulo 2, so XOR is used instead - 'z := a + b' */
+    static void gf2field_add(int[] z, final int[] x, final int[] y) {
+        int i;
+        for (i = 0; i < BITVEC_NWORDS; ++i) {
+            z[i] = (x[i] ^ y[i]);
+        }
+    }
+
+    /* increment element */
+    static void gf2field_inc(int[] x) {
+        x[0] ^= 1;
+    }
+
+    /* field multiplication 'z := (x * y)' */
+    static void gf2field_mul(int[] z, final int[] x, final int[] y) {
+        int i;
+        int[] tmp = new int[BITVEC_NWORDS];
+        assert (z != y);
+
+        bitvec_copy(tmp, x);
+
+        /* LSB set? Then start with x */
+        if (bitvec_get_bit(y, 0) != 0) {
+            bitvec_copy(z, x);
+        } else /* .. or else start with zero */ {
+            bitvec_set_zero(z);
+        }
+
+        /* Then add 2^i * x for the rest */
+        for (i = 1; i < CURVE_DEGREE; ++i) {
+            /* lshift 1 - doubling the value of tmp */
+            bitvec_lshift(tmp, tmp, 1);
+
+            /* Modulo reduction polynomial if degree(tmp) > CURVE_DEGREE */
+            if (bitvec_get_bit(tmp, CURVE_DEGREE) != 0) {
+                gf2field_add(tmp, tmp, polynomial);
+            }
+
+            /* Add 2^i * tmp if this factor in y is non-zero */
+            if (bitvec_get_bit(y, i) != 0) {
+                gf2field_add(z, z, tmp);
+            }
+        }
+    }
+
+    /* field inversion 'z := 1/x' */
+    static void gf2field_inv(int[] z, final int[] x) {
+        int[] u = new int[BITVEC_NWORDS];
+        int[] v = new int[BITVEC_NWORDS];
+        int[] g = new int[BITVEC_NWORDS];
+        int[] h = new int[BITVEC_NWORDS];
+
+        int i;
+
+        bitvec_copy(u, x);
+        bitvec_copy(v, polynomial);
+        bitvec_set_zero(g);
+        gf2field_set_one(z);
+
+        while (!gf2field_is_one(u)) {
+            i = (bitvec_degree(u) - bitvec_degree(v));
+
+            if (i < 0) {
+                bitvec_swap(u, v);
+                bitvec_swap(g, z);
+                i = -i;
+            }
+            bitvec_lshift(h, v, i);
+            gf2field_add(u, u, h);
+            bitvec_lshift(h, g, i);
+            gf2field_add(z, z, h);
+        }
+    }
+
+    /*************************************************************************************************/
+    /*
+     * The following code takes care of Galois-Field arithmetic.
+     * Elliptic curve points are represented by pairs (x,y) of bitvec_t.
+     * It is assumed that curve coefficient 'a' is {0,1}
+     * This is the case for all NIST binary curves.
+     * Coefficient 'b' is given in 'coeff_b'.
+     * '(base_x, base_y)' is a point that generates a large prime order group.
+     */
+
+    /*************************************************************************************************/
+
+
+    static void gf2point_copy(int[] x1, int[] y1, final int[] x2, final int[] y2) {
+        bitvec_copy(x1, x2);
+        bitvec_copy(y1, y2);
+    }
+
+    static void gf2point_set_zero(int[] x, int[] y) {
+        bitvec_set_zero(x);
+        bitvec_set_zero(y);
+    }
+
+    static boolean gf2point_is_zero(final int[] x, final int[] y) {
+        return (bitvec_is_zero(x)
+                && bitvec_is_zero(y));
+    }
+
+    /* double the point (x,y) */
+    static void gf2point_double(int[] x, int[] y) {
+        /* iff P = O (zero or infinity): 2 * P = P */
+        if (bitvec_is_zero(x)) {
+            bitvec_set_zero(y);
+        } else {
+            int[] l = new int[BITVEC_NWORDS];
+            gf2field_inv(l, x);
+            gf2field_mul(l, l, y);
+            gf2field_add(l, l, x);
+            gf2field_mul(y, x, x);
+            gf2field_mul(x, l, l);
+            gf2field_inc(l);
+            gf2field_add(x, x, l);
+            gf2field_mul(l, l, x);
+            gf2field_add(y, y, l);
+        }
+    }
+
+    /* add two points together (x1, y1) := (x1, y1) + (x2, y2) */
+    static void gf2point_add(int[] x1, int[] y1, final int[] x2, final int[] y2) {
+        if (!gf2point_is_zero(x2, y2)) {
+            if (gf2point_is_zero(x1, y1)) {
+                gf2point_copy(x1, y1, x2, y2);
+            } else {
+                if (bitvec_equal(x1, x2)) {
+                    if (bitvec_equal(y1, y2)) {
+                        gf2point_double(x1, y1);
+                    } else {
+                        gf2point_set_zero(x1, y1);
+                    }
+                } else {
+                    /* Arithmetic with temporary variables */
+                    int[] a = new int[BITVEC_NWORDS];
+                    int[] b = new int[BITVEC_NWORDS];
+                    int[] c = new int[BITVEC_NWORDS];
+                    int[] d = new int[BITVEC_NWORDS];
+
+                    gf2field_add(a, y1, y2);
+                    gf2field_add(b, x1, x2);
+                    gf2field_inv(c, b);
+                    gf2field_mul(c, c, a);
+                    gf2field_mul(d, c, c);
+                    gf2field_add(d, d, c);
+                    gf2field_add(d, d, b);
+                    gf2field_inc(d);
+                    gf2field_add(x1, x1, d);
+                    gf2field_mul(a, x1, c);
+                    gf2field_add(a, a, d);
+                    gf2field_add(y1, y1, a);
+                    bitvec_copy(x1, d);
+                }
+            }
+        }
+    }
+
+
+    /* point multiplication via double-and-add algorithm */
+    static void gf2point_mul(int[] x, int[] y, final int[] exp) {
+        int[] tmpx = new int[BITVEC_NWORDS];
+        int[] tmpy = new int[BITVEC_NWORDS];
+
+        int i;
+        int nbits = bitvec_degree(exp);
+        gf2point_set_zero(tmpx, tmpy);
+
+        for (i = (nbits - 1); i >= 0; --i) {
+            gf2point_double(tmpx, tmpy);
+
+            if (bitvec_get_bit(exp, i) != 0) {
+                gf2point_add(tmpx, tmpy, x, y);
+            }
+        }
+
+        gf2point_copy(x, y, tmpx, tmpy);
+    }
+
+
+    /* check if y^2 + x*y = x^3 + a*x^2 + coeff_b holds */
+    static boolean gf2point_on_curve(final int[] x, final int[] y) {
+        int[] a = new int[BITVEC_NWORDS];
+        int[] b = new int[BITVEC_NWORDS];
+
+        if (gf2point_is_zero(x, y)) {
+            return false;
+        } else {
+            gf2field_mul(a, x, x);
+            gf2field_mul(b, a, x);
+            gf2field_add(a, a, b);
+            gf2field_add(a, a, coeff_b);
+            gf2field_mul(b, y, y);
+            gf2field_add(a, a, b);
+            gf2field_mul(b, x, y);
+
+            return bitvec_equal(a, b);
+        }
+    }
+
+    // helper needed for C->Java conversion (Java cant cast pointers)
+    static int[] bytes_to_int(byte[] bytes, int offset) {
+        int[] value = new int[BITVEC_NWORDS];
+        int byteptr = offset;
+        for (int i = 0; i < BITVEC_NWORDS; i++) {
+            value[i] = ((bytes[byteptr++] & 0xff)) | ((bytes[byteptr++] & 0xff) << 8) | ((bytes[byteptr++] & 0xff) << 16) | ((bytes[byteptr++] & 0xff) << 24);
+        }
+        return value;
+    }
+
+    // helper needed for C->Java conversion (Java cant cast pointers)
+    static void ints_to_bytes(byte[] bytes, int[] ints, int offset) {
+        int byteptr = offset;
+        for (int i = 0; i < BITVEC_NWORDS; i++) {
+            bytes[byteptr++] = (byte) (ints[i] & 0x000000ff);
+            bytes[byteptr++] = (byte) ((ints[i] & 0x0000ff00) >> 8);
+            bytes[byteptr++] = (byte) ((ints[i] & 0x00ff0000) >> 16);
+            bytes[byteptr++] = (byte) ((ints[i] & 0xff000000) >> 24);
+        }
+    }
+
+    /*************************************************************************************************/
+    /*
+     * Elliptic Curve Diffie-Hellman key exchange protocol.
+     */
+
+    /*************************************************************************************************/
+
+    /* NOTE: private should contain random data a-priori! */
+    static boolean ecdh_generate_keys(byte[] public_key, byte[] private_key) {
+        int[] private_key_int32 = bytes_to_int(private_key, 0);
+        int[] public_key_int32_1 = bytes_to_int(public_key, 0);
+        int[] public_key_int32_2 = bytes_to_int(public_key, BITVEC_NBYTES);
+        /* Get copy of "base" point 'G' */
+        gf2point_copy(public_key_int32_1, public_key_int32_2, base_x, base_y);
+
+        /* Abort key generation if random number is too small */
+        if (bitvec_degree(private_key_int32) < (CURVE_DEGREE / 2)) {
+            return false;
+        } else {
+            /* Clear bits > CURVE_DEGREE in highest word to satisfy constraint 1 <= exp < n. */
+            int nbits = bitvec_degree(base_order);
+            int i;
+
+            for (i = (nbits - 1); i < (BITVEC_NWORDS * 32); ++i) {
+                bitvec_clr_bit(private_key_int32, i);
+            }
+
+            /* Multiply base-point with scalar (private-key) */
+            gf2point_mul(public_key_int32_1, public_key_int32_2, private_key_int32);
+
+            ints_to_bytes(public_key, public_key_int32_1, 0);
+            ints_to_bytes(public_key, public_key_int32_2, BITVEC_NBYTES);
+
+            return true;
+        }
+    }
+
+    static boolean ecdh_shared_secret(byte[] private_key, byte[] others_pub, byte[] output) {
+        int[] private_key_int32 = bytes_to_int(private_key, 0);
+        int[] others_pub_int32_1 = bytes_to_int(others_pub, 0);
+        int[] others_pub_int32_2 = bytes_to_int(others_pub, BITVEC_NBYTES);
+
+        /* Do some basic validation of other party's public key */
+
+        if (!gf2point_is_zero(others_pub_int32_1, others_pub_int32_2)
+                && gf2point_on_curve(others_pub_int32_1, others_pub_int32_2)) {
+            /* Copy other side's public key to output */
+            int i;
+            for (i = 0; i < (BITVEC_NBYTES * 2); ++i) {
+                output[i] = others_pub[i];
+            }
+
+            /* Clear bits > CURVE_DEGREE in highest word to satisfy constraint 1 <= exp < n. */
+            int nbits = bitvec_degree(base_order);
+
+            for (i = (nbits - 1); i < (BITVEC_NWORDS * 32); ++i) {
+                bitvec_clr_bit(private_key_int32, i);
+            }
+
+            /* Multiply other side's public key with own private key */
+            int[] output_int32_1 = bytes_to_int(output, 0);
+            int[] output_int32_2 = bytes_to_int(output, BITVEC_NBYTES);
+
+            gf2point_mul(output_int32_1, output_int32_2, private_key_int32);
+
+            ints_to_bytes(output, output_int32_1, 0);
+            ints_to_bytes(output, output_int32_2, BITVEC_NBYTES);
+
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    // these are wrappers around the above C-style methods for Gadgetbridge to use
+    public static byte[] ecdh_generate_public(byte[] privateEC) {
+        byte[] pubKey = new byte[ECC_PUB_KEY_SIZE];
+        if (ecdh_generate_keys(pubKey, privateEC)) {
+            return pubKey;
+        }
+        return null;
+    }
+
+    public static byte[] ecdh_generate_shared(byte[] privateEC, byte[] remotePublicEC) {
+        byte[] sharedKey = new byte[ECC_PUB_KEY_SIZE];
+        if (ecdh_shared_secret(privateEC, remotePublicEC, sharedKey)) {
+            return sharedKey;
+        }
+        return null;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/EmojiConverter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/EmojiConverter.java
new file mode 100644
index 0000000..f5e83cf
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/EmojiConverter.java
@@ -0,0 +1,131 @@
+/*  Copyright (C) 2018-2024 Carsten Pfeiffer, Matthieu Baerts, Taavi Eom盲e
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+
+package nodomain.freeyourgadget.gadgetbridge.util;
+
+import android.content.Context;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+import io.wax911.emojify.EmojiManager;
+import io.wax911.emojify.contract.model.IEmoji;
+import io.wax911.emojify.parser.EmojiParserKt;
+import io.wax911.emojify.parser.candidate.UnicodeCandidate;
+import io.wax911.emojify.parser.transformer.EmojiTransformer;
+import io.wax911.emojify.serializer.gson.GsonDeserializer;
+
+public class EmojiConverter {
+    private static final Logger LOG = LoggerFactory.getLogger(EmojiConverter.class);
+
+    private static final String[][] simpleEmojiMapping = {
+            {"\uD83D\uDE00", ":-D"},  // grinning
+            {"\uD83D\uDE01", ":-D"},  // grinning_face_with_smiling_eyes
+            {"\uD83D\uDE02", ":'D"},  // face_with_tears_of_joy
+            {"\uD83D\uDE03", ":-D"},  // smiling_face_with_open_mouth
+            {"\uD83D\uDE04", ":-D"},  // smiling_face_with_open_mouth_and_smiling_eyes
+            {"\uD83D\uDE05", ":'D"},  // smiling_face_with_open_mouth_and_cold_sweat
+            {"\uD83D\uDE06", "X-D"},  // smiling_face_with_open_mouth_and_tightly-closed_eyes
+            {"\uD83D\uDE07", "O:-)"}, // innocent
+            {"\uD83D\uDE09", ";-)"},  // wink
+            {"\uD83D\uDE0A", ":-)"},  // blush
+            {"\uD83D\uDE0B", ":-p"},  // yum
+            {"\uD83D\uDE0E", "B-)"},  // sunglasses
+            {"\uD83D\uDE15", ":-/"},  // confused
+            {"\uD83D\uDE16", ":-S"},  // confounded_face
+            {"\uD83D\uDE17", ":*"},   // kissing_face
+            {"\uD83D\uDE18", ";-*"},  // face_throwing_a_kiss
+            {"\uD83D\uDE19", ":-*"},  // kissing_face_with_smiling_eyes
+            {"\uD83D\uDE1A", ":-*"},  // kissing_closed_eyes
+            {"\uD83D\uDE1B", ":-P"},  // stuck_out_tongue
+            {"\uD83D\uDE1C", ";-P"},  // stuck_out_tongue_winking_eye
+            {"\uD83D\uDE1D", "X-P"},  // stuck_out_tongue_and_tightly-closed_eyes
+            {"\uD83D\uDE1E", ":-S"},  // disappointed
+            {"\uD83D\uDE20", ":-@"},  // angry_face
+            {"\uD83D\uDE21", ":-@"},  // pouting_face
+            {"\uD83D\uDE22", ":'("},  // cry
+            {"\uD83D\uDE23", ":-("},  // preserving_face
+            {"\uD83D\uDE25", ":'("},  // disappointed_but_relieved_face
+            {"\uD83D\uDE2D", ":'("},  // loudly_crying_face
+            {"\uD83D\uDE2E", ":-O"},  // open_mouth
+            {"\uD83D\uDE32", "X-o"},  // astonished_face
+            {"\uD83D\uDE42", ":)"},   // slightly_smiling_face
+            {"\uD83D\uDE43", "(-:"},  // upside_down_face
+            {"\u2639", ":-("},        // frowning_face
+            {"\u2764", "<3"},         // heart
+    };
+
+    private static EmojiManager emojiManagerInstance;
+
+    /**
+     * An emoji transformer that removes fitzpatrick modifiers. This partially reimplements
+     * EmojiParser.parseToAliases since I was unable to call it directly with FitzpatrickAction.REMOVE
+     */
+    private static final EmojiTransformer EMOJI_TRANSFORMER_NO_FITZPATRICK = new EmojiTransformer() {
+        @Nullable
+        @Override
+        public String invoke(@NonNull final UnicodeCandidate unicodeCandidate) {
+            final IEmoji emoji = unicodeCandidate.getEmoji();
+            if (emoji == null) {
+                return null;
+            }
+            final List<String> aliases = emoji.getAliases();
+            if (aliases == null || aliases.isEmpty()) {
+                return null;
+            }
+            return String.format(":%s:", aliases.get(0));
+        }
+    };
+
+    private static String convertSimpleEmojiToAscii(String text) {
+        for (String[] emojiMap : simpleEmojiMapping) {
+            text = text.replace(emojiMap[0], emojiMap[1]);
+        }
+        return text;
+    }
+
+    public static synchronized EmojiManager getEmojiManager(final Context context) {
+        // Do a lazy initialisation not to slowdown the startup and when it is needed
+        if (emojiManagerInstance == null) {
+            emojiManagerInstance = EmojiManager.Companion.create(context, new GsonDeserializer());
+        }
+        return emojiManagerInstance;
+    }
+
+    private static String convertAdvancedEmojiToAscii(final String text, final Context context) {
+        final EmojiManager emojiManager = getEmojiManager(context);
+        try {
+            return EmojiParserKt.parseFromUnicode(emojiManager, text, EMOJI_TRANSFORMER_NO_FITZPATRICK);
+        } catch (final Exception e) {
+            LOG.warn("An exception occurred when converting advanced emoji to ASCII: {}", text);
+            return text;
+        }
+    }
+
+    public static String convertUnicodeEmojiToAscii(String text, final Context context) {
+        text = convertSimpleEmojiToAscii(text);
+
+        text = convertAdvancedEmojiToAscii(text, context);
+
+        return text;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/FileUtils.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/FileUtils.java
new file mode 100644
index 0000000..ea8ab1e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/FileUtils.java
@@ -0,0 +1,445 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, Arjan Schrijver, Carsten
+    Pfeiffer, Daniele Gobbetti, Felix Konstantin Maurer, JohnnySun, Jos茅 Rebelo,
+    Petr Van臎k, Taavi Eom盲e
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.util;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.net.Uri;
+import android.os.Environment;
+import android.widget.Toast;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.nio.channels.FileChannel;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.GBEnvironment;
+
+public class FileUtils {
+    // Don't use slf4j here -- would be a bootstrapping problem
+    private static final String TAG = "FileUtils";
+
+    private static final List<String> KNOWN_PACKAGES = Arrays.asList(
+            "nodomain.freeyourgadget.gadgetbridge",
+            "nodomain.freeyourgadget.gadgetbridge.nightly",
+            "nodomain.freeyourgadget.gadgetbridge.nightly_nopebble",
+            "com.espruino.gadgetbridge.banglejs",
+            "com.espruino.gadgetbridge.banglejs.nightly"
+    );
+
+    /**
+     * Copies the the given sourceFile to destFile, overwriting it, in case it exists.
+     *
+     * @param sourceFile
+     * @param destFile
+     * @throws IOException
+     */
+    public static void copyFile(File sourceFile, File destFile) throws IOException {
+        if (!sourceFile.exists()) {
+            throw new IOException("Does not exist: " + sourceFile.getAbsolutePath());
+        }
+        try (FileInputStream in = new FileInputStream(sourceFile); FileOutputStream out = new FileOutputStream(destFile)) {
+            copyFile(in, out);
+        }
+    }
+
+    private static void copyFile(FileInputStream sourceStream, FileOutputStream destStream) throws IOException {
+        try (FileChannel fromChannel = sourceStream.getChannel(); FileChannel toChannel = destStream.getChannel()) {
+            fromChannel.transferTo(0, fromChannel.size(), toChannel);
+        }
+    }
+
+    /**
+     * Copies the contents of the given input stream to the destination file.
+     * @param inputStream the contents to write. Note: the caller has to close the input stream!
+     * @param destFile the file to write to
+     * @throws IOException
+     */
+    public static void copyStreamToFile(InputStream inputStream, File destFile) throws IOException {
+        try (FileOutputStream fout = new FileOutputStream(destFile)) {
+            byte[] buf = new byte[4096];
+            while (inputStream.available() > 0) {
+                int bytes = inputStream.read(buf);
+                fout.write(buf, 0, bytes);
+            }
+        }
+    }
+
+    /**
+     * Copies the contents of the given string to the destination file.
+     * @param string the contents to write.
+     * @param dst the file to write to
+     * @throws IOException
+     */
+    public static void copyStringToFile(String string, File dst, String mode) throws IOException {
+        boolean append = true;
+        if (!Objects.equals(mode, "append")) append = false;
+
+        try (BufferedWriter writer = new BufferedWriter(new FileWriter(dst, append))) {
+            writer.write(string);
+        }
+    }
+
+    /**
+     * Copies the contents of the given file to the destination output stream.
+     * @param src the file from which to read.
+     * @param dst the output stream that is written to. Note: the caller has to close the output stream!
+     * @throws IOException
+     */
+    public static void copyFileToStream(File src, OutputStream dst) throws IOException {
+        try (FileInputStream in = new FileInputStream(src)) {
+            byte[] buf = new byte[4096];
+            while(in.available() > 0) {
+                int bytes = in.read(buf);
+                dst.write(buf, 0, bytes);
+            }
+        }
+    }
+
+    public static void copyURItoFile(Context ctx, Uri uri, File destFile) throws IOException {
+        if (uri.getPath().equals(destFile.getPath())) {
+            return;
+        }
+
+        ContentResolver cr = ctx.getContentResolver();
+        InputStream in = cr.openInputStream(uri);
+        if (in == null) {
+            throw new IOException("unable to open input stream: " + uri);
+        }
+        try (InputStream fin = new BufferedInputStream(in)) {
+            copyStreamToFile(fin, destFile);
+            fin.close();
+        }
+    }
+
+    /**
+     * Copies the content of a file to an uri,
+     * which for example was retrieved using the storage access framework.
+     * @param context the application context.
+     * @param src the file from which the content should be copied.
+     * @param dst the destination uri.
+     * @throws IOException
+     */
+    public static void copyFileToURI(Context context, File src, Uri dst) throws IOException {
+        OutputStream out = context.getContentResolver().openOutputStream(dst);
+        if (out == null) {
+            throw new IOException("Unable to open output stream for " + dst.toString());
+        }
+        try (OutputStream bufOut = new BufferedOutputStream(out)) {
+            copyFileToStream(src, bufOut);
+        }
+    }
+
+    /**
+     * Returns the textual contents of the given file. The contents is expected to be
+     * in UTF-8 encoding.
+     * @param file the file to read
+     * @return the file contents as a newline-delimited string
+     * @throws IOException
+     * @see #getStringFromFile(File, String)
+     */
+    public static String getStringFromFile(File file) throws IOException {
+        return getStringFromFile(file, StandardCharsets.UTF_8.name());
+    }
+
+    /**
+     * Returns the textual contents of the given file. The contents will be interpreted using the
+     * given encoding.
+     * @param file the file to read
+     * @return the file contents as a newline-delimited string
+     * @throws IOException
+     * @see #getStringFromFile(File)
+     */
+    public static String getStringFromFile(File file, String encoding) throws IOException {
+        FileInputStream fin = new FileInputStream(file);
+
+        try (BufferedReader reader = new BufferedReader(new InputStreamReader(fin, encoding))) {
+            StringBuilder sb = new StringBuilder();
+            String line;
+            while ((line = reader.readLine()) != null) {
+                sb.append(line).append("\n");
+            }
+            return sb.toString();
+        }
+    }
+
+    /**
+     * Returns the existing external storage dir. The directory is guaranteed to
+     * exist and to be writable.
+     *
+     * @throws IOException when the directory is not available
+     */
+    public static File getExternalFilesDir() throws IOException {
+        List<File> dirs = getWritableExternalFilesDirs();
+        for (File dir : dirs) {
+            if (canWriteTo(dir)) {
+                return dir;
+            }
+        }
+        throw new IOException("no writable external directory found");
+    }
+
+    /**
+     * Returns a File object representing the "child" argument, but relative
+     * to the Android "external files directory" (e.g. /sdcard).
+     * It doesn't matter whether child shall represent a file or a directory.
+     * The parent directory will automatically be created, if necessary.
+     * @param child the path to become relative to the external files directory
+     * @throws IOException
+     * @see #getExternalFilesDir()
+     */
+    public static File getExternalFile(String child) throws IOException {
+        File file = new File(getExternalFilesDir(), child);
+        File dir = file.getParentFile();
+        if (!dir.exists() && !dir.mkdirs()) {
+            throw new IOException("Unable to create directory " + file.getParent());
+        }
+        return file;
+    }
+
+
+    private static boolean canWriteTo(File dir) {
+        File file = new File(dir, "gbtest");
+        try {
+            FileOutputStream test = new FileOutputStream(file);
+            try {
+                test.close();
+            } catch (IOException e) {
+                // ignore
+            }
+            file.delete();
+            return true;
+        } catch (FileNotFoundException e) {
+            GB.log("Cannot write to directory: " + dir.getAbsolutePath(), GB.INFO, e);
+            return false;
+        }
+    }
+
+    /**
+     * Returns a list of directories to write to. The list is sorted by priority,
+     * i.e. the first directory should be preferred, the last one is the least
+     * preferred one.
+     * <p/>
+     * Note that the directories may not exist, so it is not guaranteed that you
+     * can actually write to them. But when created, they *should* be writable.
+     *
+     * @return the list of writable directories
+     * @throws IOException
+     */
+    @NonNull
+    private static List<File> getWritableExternalFilesDirs() throws IOException {
+        Context context = GBApplication.getContext();
+        File[] dirs;
+        try {
+            dirs = context.getExternalFilesDirs(null);
+        } catch (NullPointerException | UnsupportedOperationException ex) {
+            // workaround for robolectric 3.1.2 not implementing getExternalFilesDirs()
+            // https://github.com/robolectric/robolectric/issues/2531
+            File dir = context.getExternalFilesDir(null);
+            if (dir != null) {
+                dirs = new File[] { dir };
+            } else {
+                throw ex;
+            }
+        }
+        if (dirs == null) {
+            throw new IOException("Unable to access external files dirs: null");
+        }
+        List<File> result = new ArrayList<>(dirs.length);
+        if (dirs.length == 0) {
+            throw new IOException("Unable to access external files dirs: 0");
+        }
+        for (int i = 0; i < dirs.length; i++) {
+            File dir = dirs[i];
+            if (dir == null) {
+                continue;
+            }
+            if (!dir.exists() && !dir.mkdirs()) {
+                GB.log("Unable to create directories: " + dir.getAbsolutePath(), GB.INFO, null);
+                continue;
+            }
+
+            if (!GBEnvironment.env().isLocalTest()) { // don't do this with robolectric
+                final String storageState = Environment.getExternalStorageState(dir);
+                if (!Environment.MEDIA_MOUNTED.equals(storageState)) {
+                    GB.log("ignoring '" +  storageState + "' external storage dir: " + dir, GB.INFO, null);
+                    continue;
+                }
+            }
+            result.add(dir); // add last
+        }
+        return result;
+    }
+
+    /**
+     * Reads the contents of the given InputStream into a byte array, but does not
+     * read more than maxLen bytes. If the stream provides more than maxLen bytes,
+     * an IOException is thrown.
+     *
+     * @param in     the stream to read from
+     * @param maxLen the maximum number of bytes to read/return
+     * @return the bytes read from the InputStream
+     * @throws IOException when reading failed or when maxLen was exceeded
+     */
+    public static byte[] readAll(InputStream in, long maxLen) throws IOException {
+        ByteArrayOutputStream out = new ByteArrayOutputStream(Math.max(8192, in.available()));
+        byte[] buf = new byte[8192];
+        int read;
+        long totalRead = 0;
+        while ((read = in.read(buf)) > 0) {
+            out.write(buf, 0, read);
+            totalRead += read;
+            if (totalRead > maxLen) {
+                throw new IOException("Too much data to read into memory. Got already " + totalRead);
+            }
+        }
+        return out.toByteArray();
+    }
+
+    public static boolean deleteRecursively(File dir) {
+        if (!dir.exists()) {
+            return true;
+        }
+        if (dir.isFile()) {
+            return dir.delete();
+        }
+        for (File sub : dir.listFiles()) {
+            if (!deleteRecursively(sub)) {
+                return false;
+            }
+        }
+        return dir.delete();
+    }
+
+    public static File createTempDir(String prefix) throws IOException {
+        File parent = new File(System.getProperty("java.io.tmpdir", "/tmp"));
+        for (int i = 1; i < 100; i++) {
+            String name = prefix + (int) (Math.random() * 100000);
+            File dir = new File(parent, name);
+            if (dir.mkdirs()) {
+                return dir;
+            }
+        }
+        throw new IOException("Cannot create temporary directory in " + parent);
+    }
+
+    /**
+     * Replaces some wellknown invalid characters in the given filename
+     * to underscrores.
+     * @param name the file name to make valid
+     * @return the valid file name
+     */
+    public static String makeValidFileName(String name) {
+        return name.replaceAll("[\0/:\\r\\n\\\\]", "_");
+    }
+
+    /**
+     *Returns extension of a file
+     * @param file string filename
+     */
+    public static String getExtension(String file) {
+        int i = file.lastIndexOf('.');
+        String extension = "";
+        if (i > 0) {
+            extension = file.substring(i + 1);
+        }
+        return extension;
+    }
+
+    /**
+     * Returns a Uri referencing a temporary file with the contents of the given asset
+     * @param assetPath relative path to the assets file
+     * @param context current context for getting AssetManager
+     * @return Uri that points to the created temporary file
+     * @throws IOException thrown when a file could not be created or opened
+     */
+    public static Uri getUriForAsset(String assetPath, Context context) throws IOException {
+        File tempFile = File.createTempFile("tmpfile" + System.currentTimeMillis(), null);
+        tempFile.deleteOnExit();
+        FileOutputStream fos = new FileOutputStream(tempFile);
+        InputStream asset = context.getAssets().open(assetPath);
+        byte[] buffer = new byte[1024];
+        int read;
+        while ((read = asset.read(buffer)) != -1) {
+            fos.write(buffer, 0, read);
+        }
+        fos.close();
+        return Uri.fromFile(tempFile);
+    }
+
+    /**
+     * When migrating the database between Gadgetbridge versions or phones, we may end up with the
+     * wrong path persisted in the database. Attempt to find the file in the current external data.
+     *
+     * @return the fixed file path, if it exists, null otherwise
+     */
+    @Nullable
+    public static File tryFixPath(final File file) {
+        if (file == null || (file.isFile() && file.canRead())) {
+            return file;
+        }
+
+        File externalFilesDir;
+        try {
+            externalFilesDir = getExternalFilesDir();
+        } catch (final IOException e) {
+            return null;
+        }
+
+        final String absolutePath = file.getAbsolutePath();
+        for (final String knownPackage : KNOWN_PACKAGES) {
+            final int i = absolutePath.indexOf(knownPackage);
+            if (i < 0) {
+                continue;
+            }
+
+            // We found the gadgetbridge package in the path!
+            String relativePath = absolutePath.substring(i + knownPackage.length() + 1);
+            if (relativePath.startsWith("files/")) {
+                relativePath = relativePath.substring(6);
+            }
+            final File fixedFile = new File(externalFilesDir, relativePath);
+            if (fixedFile.exists()) {
+                return fixedFile;
+            }
+        }
+
+        return null;
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/FormatUtils.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/FormatUtils.java
new file mode 100644
index 0000000..f04483c
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/FormatUtils.java
@@ -0,0 +1,62 @@
+/*  Copyright (C) 2021-2024 Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+
+package nodomain.freeyourgadget.gadgetbridge.util;
+
+import java.text.DecimalFormat;
+import java.text.DecimalFormatSymbols;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.SettingsActivity;
+
+public class FormatUtils {
+
+    /**
+     * This method formats a given number into a string with m (meters), km (kilometers),
+     * ft (feet) or mi (miles). These string units can be translated, so the resulting text might
+     * be different in each language.
+     * The number is also localizes through DecimalFormatSymbols based on current locale.
+     *
+     * @param distance
+     */
+    public static String getFormattedDistanceLabel(double distance) {
+        double distanceMeters = distance;
+        double distanceFeet = distanceMeters * 3.28084f;
+        double distanceFormatted = 0;
+
+        String unit = GBApplication.getContext().getString(R.string.distance_format_meters);
+        distanceFormatted = distanceMeters;
+        if (distanceMeters > 2000) {
+            distanceFormatted = distanceMeters / 1000;
+            unit = GBApplication.getContext().getString(R.string.distance_format_kilometers);
+        }
+        String units = GBApplication.getPrefs().getString(SettingsActivity.PREF_MEASUREMENT_SYSTEM, GBApplication.getContext().getString(R.string.p_unit_metric));
+        if (units.equals(GBApplication.getContext().getString(R.string.p_unit_imperial))) {
+            unit = GBApplication.getContext().getString(R.string.distance_format_feet);
+            distanceFormatted = distanceFeet;
+            if (distanceFeet > 6000) {
+                distanceFormatted = distanceFeet * 0.0001893939f;
+                unit = GBApplication.getContext().getString(R.string.distance_format_miles);
+            }
+        }
+        DecimalFormatSymbols symbols = new DecimalFormatSymbols(GBApplication.getLanguage());
+        DecimalFormat df = new DecimalFormat(unit, symbols);
+
+        return df.format(distanceFormatted);
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GB.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GB.java
new file mode 100644
index 0000000..87c52c7
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GB.java
@@ -0,0 +1,700 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, Carsten Pfeiffer, Daniel Dakhno,
+    Daniele Gobbetti, Davis Mosenkovs, Dmitriy Bogdanov, Felix Konstantin Maurer,
+    Ganblejs, Jos茅 Rebelo, Pauli Salmenrinne, Petr Van臎k, Roberto P. Rubio,
+    Taavi Eom盲e, Uwe Hermann, Yar
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.util;
+
+import static nodomain.freeyourgadget.gadgetbridge.GBApplication.isRunningOreoOrLater;
+import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_RECORDED_DATA_TYPES;
+
+import android.app.Activity;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.bluetooth.BluetoothAdapter;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.Handler;
+import android.os.Looper;
+import android.text.Html;
+import android.text.SpannableString;
+import android.widget.Toast;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.StringRes;
+import androidx.core.app.ActivityCompat;
+import androidx.core.app.NotificationCompat;
+import androidx.core.app.NotificationManagerCompat;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.BuildConfig;
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.GBEnvironment;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.ControlCenterv2;
+import nodomain.freeyourgadget.gadgetbridge.activities.SettingsActivity;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventScreenshot;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceCommunicationService;
+import nodomain.freeyourgadget.gadgetbridge.util.preferences.DevicePrefs;
+
+public class GB {
+    public static final String ACTION_ACTIVITY_SYNC = "nodomain.freeyourgadget.gadgetbridge.action.ACTIVITY_SYNC_FINISH";
+
+    public static final String NOTIFICATION_CHANNEL_ID = "gadgetbridge";
+    public static final String NOTIFICATION_CHANNEL_ID_CONNECTION_STATUS = "gadgetbridge connection status";
+    public static final String NOTIFICATION_CHANNEL_ID_SCAN_SERVICE = "gadgetbridge_scan_service";
+    public static final String NOTIFICATION_CHANNEL_HIGH_PRIORITY_ID = "gadgetbridge_high_priority";
+    public static final String NOTIFICATION_CHANNEL_ID_TRANSFER = "gadgetbridge transfer";
+    public static final String NOTIFICATION_CHANNEL_ID_LOW_BATTERY = "low_battery";
+    public static final String NOTIFICATION_CHANNEL_ID_FULL_BATTERY = "full_battery";
+    public static final String NOTIFICATION_CHANNEL_ID_GPS = "gps";
+
+    public static final int NOTIFICATION_ID = 1;
+    public static final int NOTIFICATION_ID_INSTALL = 2;
+    public static final int NOTIFICATION_ID_LOW_BATTERY = 3;
+    public static final int NOTIFICATION_ID_TRANSFER = 4;
+    public static final int NOTIFICATION_ID_EXPORT_FAILED = 5;
+    public static final int NOTIFICATION_ID_PHONE_FIND = 6;
+    public static final int NOTIFICATION_ID_GPS = 7;
+    public static final int NOTIFICATION_ID_SCAN = 8;
+    public static final int NOTIFICATION_ID_FULL_BATTERY = 9;
+    public static final int NOTIFICATION_ID_ERROR = 42;
+
+    private static final Logger LOG = LoggerFactory.getLogger(GB.class);
+    public static final int INFO = 1;
+    public static final int WARN = 2;
+    public static final int ERROR = 3;
+    public static final String ACTION_DISPLAY_MESSAGE = "GB_Display_Message";
+    public static final String DISPLAY_MESSAGE_MESSAGE = "message";
+    public static final String DISPLAY_MESSAGE_DURATION = "duration";
+    public static final String DISPLAY_MESSAGE_SEVERITY = "severity";
+
+    /** Commands related to the progress (bar) on the screen */
+    public static final String ACTION_SET_PROGRESS_BAR = "GB_Set_Progress_Bar";
+    public static final String PROGRESS_BAR_INDETERMINATE = "indeterminate";
+    public static final String PROGRESS_BAR_MAX = "max";
+    public static final String PROGRESS_BAR_PROGRESS = "progress";
+    public static final String ACTION_SET_PROGRESS_TEXT = "GB_Set_Progress_Text";
+    public static final String ACTION_SET_INFO_TEXT = "GB_Set_Info_Text";
+
+    private static boolean notificationChannelsCreated;
+
+    private static final String TAG = "GB";
+
+    public static void createNotificationChannels(Context context) {
+        if (notificationChannelsCreated) return;
+
+        if (isRunningOreoOrLater()) {
+            NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
+
+            NotificationChannel channelGeneral = new NotificationChannel(
+                    NOTIFICATION_CHANNEL_ID,
+                    context.getString(R.string.notification_channel_name),
+                    NotificationManager.IMPORTANCE_LOW);
+            notificationManager.createNotificationChannel(channelGeneral);
+
+            NotificationChannel channelConnwectionStatus = new NotificationChannel(
+                    NOTIFICATION_CHANNEL_ID_CONNECTION_STATUS,
+                    context.getString(R.string.notification_channel_connection_status_name),
+                    NotificationManager.IMPORTANCE_LOW);
+            notificationManager.createNotificationChannel(channelConnwectionStatus);
+
+            NotificationChannel channelScanService = new NotificationChannel(
+                    NOTIFICATION_CHANNEL_ID_SCAN_SERVICE,
+                    context.getString(R.string.notification_channel_scan_service_name),
+                    NotificationManager.IMPORTANCE_LOW);
+            notificationManager.createNotificationChannel(channelScanService);
+
+            NotificationChannel channelHighPriority = new NotificationChannel(
+                    NOTIFICATION_CHANNEL_HIGH_PRIORITY_ID,
+                    context.getString(R.string.notification_channel_high_priority_name),
+                    NotificationManager.IMPORTANCE_HIGH);
+            notificationManager.createNotificationChannel(channelHighPriority);
+
+            NotificationChannel channelTransfer = new NotificationChannel(
+                    NOTIFICATION_CHANNEL_ID_TRANSFER,
+                    context.getString(R.string.notification_channel_transfer_name),
+                    NotificationManager.IMPORTANCE_LOW);
+            notificationManager.createNotificationChannel(channelTransfer);
+
+            NotificationChannel channelLowBattery = new NotificationChannel(
+                    NOTIFICATION_CHANNEL_ID_LOW_BATTERY,
+                    context.getString(R.string.notification_channel_low_battery_name),
+                    NotificationManager.IMPORTANCE_DEFAULT);
+            notificationManager.createNotificationChannel(channelLowBattery);
+
+            NotificationChannel channelFullBattery = new NotificationChannel(
+                    NOTIFICATION_CHANNEL_ID_FULL_BATTERY,
+                    context.getString(R.string.notification_channel_full_battery_name),
+                    NotificationManager.IMPORTANCE_DEFAULT);
+            notificationManager.createNotificationChannel(channelFullBattery);
+
+            NotificationChannel channelGps = new NotificationChannel(
+                    NOTIFICATION_CHANNEL_ID_GPS,
+                    context.getString(R.string.notification_channel_gps),
+                    NotificationManager.IMPORTANCE_MIN);
+            notificationManager.createNotificationChannel(channelGps);
+        }
+
+        notificationChannelsCreated = true;
+    }
+
+    private static PendingIntent getContentIntent(Context context) {
+        Intent notificationIntent = new Intent(context, ControlCenterv2.class);
+        notificationIntent.setPackage(BuildConfig.APPLICATION_ID);
+        notificationIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+                | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+        PendingIntent pendingIntent = PendingIntentUtils.getActivity(context, 0,
+                notificationIntent, 0, false);
+
+        return pendingIntent;
+    }
+
+    public static Notification createNotification(List<GBDevice> devices, Context context) {
+        NotificationCompat.Builder builder = new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID_CONNECTION_STATUS);
+        if(devices.size() == 0){
+            builder.setContentTitle(context.getString(R.string.info_no_devices_connected))
+                    .setSmallIcon(R.drawable.ic_notification_disconnected)
+                    .setContentIntent(getContentIntent(context))
+                    .setShowWhen(false)
+                    .setOngoing(true);
+
+            if (!GBApplication.isRunningTwelveOrLater()) {
+                builder.setColor(context.getResources().getColor(R.color.accent));
+            }
+        }else if(devices.size() == 1) {
+            GBDevice device = devices.get(0);
+            String deviceName = device.getAliasOrName();
+            String text = device.getStateString(context);
+
+            text += buildDeviceBatteryString(context, device);
+
+            boolean connected = device.isInitialized();
+            builder.setContentTitle(deviceName)
+                    .setTicker(deviceName + " - " + text)
+                    .setContentText(text)
+                    .setSmallIcon(connected ? device.getNotificationIconConnected() : device.getNotificationIconDisconnected())
+                    .setContentIntent(getContentIntent(context))
+                    .setShowWhen(false)
+                    .setOngoing(true);
+
+            if (!GBApplication.isRunningTwelveOrLater()) {
+                builder.setColor(context.getResources().getColor(R.color.accent));
+            }
+
+            Intent deviceCommunicationServiceIntent = new Intent(context, DeviceCommunicationService.class);
+            deviceCommunicationServiceIntent.setPackage(BuildConfig.APPLICATION_ID);
+            if (connected) {
+                deviceCommunicationServiceIntent.setAction(DeviceService.ACTION_DISCONNECT);
+                PendingIntent disconnectPendingIntent = PendingIntentUtils.getService(context, 0, deviceCommunicationServiceIntent, PendingIntent.FLAG_ONE_SHOT, false);
+                builder.addAction(R.drawable.ic_notification_disconnected, context.getString(R.string.controlcenter_disconnect), disconnectPendingIntent);
+                if (device.getDeviceCoordinator().supportsActivityDataFetching()) {
+                    deviceCommunicationServiceIntent.setAction(DeviceService.ACTION_FETCH_RECORDED_DATA);
+                    deviceCommunicationServiceIntent.putExtra(EXTRA_RECORDED_DATA_TYPES, ActivityKind.ACTIVITY);
+                    PendingIntent fetchPendingIntent = PendingIntentUtils.getService(context, 1, deviceCommunicationServiceIntent, PendingIntent.FLAG_ONE_SHOT, false);
+                    builder.addAction(R.drawable.ic_refresh, context.getString(R.string.controlcenter_fetch_activity_data), fetchPendingIntent);
+                }
+            } else if (device.getState().equals(GBDevice.State.WAITING_FOR_RECONNECT) || device.getState().equals(GBDevice.State.NOT_CONNECTED)) {
+                deviceCommunicationServiceIntent.setAction(DeviceService.ACTION_CONNECT);
+                deviceCommunicationServiceIntent.putExtra(GBDevice.EXTRA_DEVICE, device);
+                PendingIntent reconnectPendingIntent = PendingIntentUtils.getService(context, 2, deviceCommunicationServiceIntent, PendingIntent.FLAG_UPDATE_CURRENT, false);
+                builder.addAction(R.drawable.ic_notification, context.getString(R.string.controlcenter_connect), reconnectPendingIntent);
+            }
+        }else{
+            StringBuilder contentText = new StringBuilder();
+            boolean isConnected = true;
+            boolean anyDeviceSupportesActivityDataFetching = false;
+            for(GBDevice device : devices){
+                if(!device.isInitialized()){
+                    isConnected = false;
+                }
+
+                anyDeviceSupportesActivityDataFetching |= device.getDeviceCoordinator().supportsActivityDataFetching();
+
+                String deviceName = device.getAliasOrName();
+                String text = device.getStateString(context);
+                text += buildDeviceBatteryString(context, device);
+                contentText.append(deviceName).append(" (").append(text).append(")<br>");
+            }
+
+            SpannableString formated = new SpannableString(
+                    Html.fromHtml(contentText.substring(0, contentText.length() - 4)) // cut away last <br>
+            );
+
+            String title = context.getString(R.string.info_connected_count, devices.size());
+
+            builder.setContentTitle(title)
+                    .setContentText(formated)
+                    .setSmallIcon(isConnected ? R.drawable.ic_notification : R.drawable.ic_notification_disconnected)
+                    .setContentIntent(getContentIntent(context))
+                    .setStyle(new NotificationCompat.BigTextStyle().bigText(formated).setBigContentTitle(title))
+                    .setShowWhen(false)
+                    .setOngoing(true);
+
+            if (!GBApplication.isRunningTwelveOrLater()) {
+                builder.setColor(context.getResources().getColor(R.color.accent));
+            }
+
+            if (anyDeviceSupportesActivityDataFetching) {
+                Intent deviceCommunicationServiceIntent = new Intent(context, DeviceCommunicationService.class);
+                deviceCommunicationServiceIntent.setPackage(BuildConfig.APPLICATION_ID);
+                deviceCommunicationServiceIntent.setAction(DeviceService.ACTION_FETCH_RECORDED_DATA);
+                deviceCommunicationServiceIntent.putExtra(EXTRA_RECORDED_DATA_TYPES, ActivityKind.ACTIVITY);
+                PendingIntent fetchPendingIntent = PendingIntentUtils.getService(context, 1, deviceCommunicationServiceIntent, PendingIntent.FLAG_ONE_SHOT, false);
+                builder.addAction(R.drawable.ic_refresh, context.getString(R.string.controlcenter_fetch_activity_data), fetchPendingIntent);
+            }
+        }
+
+        builder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC);
+
+        if (GBApplication.minimizeNotification()) {
+            builder.setPriority(Notification.PRIORITY_MIN);
+        }
+        return builder.build();
+    }
+
+    public static String buildDeviceBatteryString(final Context context, final GBDevice device) {
+        final DevicePrefs devicePrefs = GBApplication.getDevicePrefs(device);
+        final List<Integer> batteryLevels = new ArrayList<>();
+        final StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < 3; i++) {
+            if (devicePrefs.getBatteryShowInNotification(i) && device.getBatteryLevel(i) != GBDevice.BATTERY_UNKNOWN) {
+                batteryLevels.add(device.getBatteryLevel(i));
+            }
+        }
+        if (!batteryLevels.isEmpty()) {
+            sb.append(": ").append(context.getString(R.string.battery)).append(" ");
+
+            for (int i = 0; i < batteryLevels.size(); i++) {
+                sb.append(batteryLevels.get(i)).append("%");
+                if (i + 1 < batteryLevels.size()) {
+                    sb.append(", ");
+                }
+            }
+        }
+
+        return sb.toString();
+    }
+
+    public static Notification createNotification(String text, Context context) {
+        NotificationCompat.Builder builder = new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID_CONNECTION_STATUS);
+        builder.setTicker(text)
+                .setContentText(text)
+                .setSmallIcon(R.drawable.ic_notification_disconnected)
+                .setContentIntent(getContentIntent(context))
+                .setShowWhen(false)
+                .setOngoing(true);
+
+        if (!GBApplication.isRunningTwelveOrLater()) {
+            builder.setColor(context.getResources().getColor(R.color.accent));
+        }
+
+        // A small bug: When "Reconnect only to connected devices" is disabled, the intent will be added even when there are no devices in GB
+        // Not sure whether it is worth the complexity to fix this
+        if (!GBApplication.getPrefs().getBoolean(GBPrefs.RECONNECT_ONLY_TO_CONNECTED, true) || !GBApplication.getPrefs().getStringSet(GBPrefs.LAST_DEVICE_ADDRESSES, Collections.emptySet()).isEmpty()) {
+            Intent deviceCommunicationServiceIntent = new Intent(context, DeviceCommunicationService.class);
+            deviceCommunicationServiceIntent.setPackage(BuildConfig.APPLICATION_ID);
+            deviceCommunicationServiceIntent.setAction(DeviceService.ACTION_CONNECT);
+            PendingIntent reconnectPendingIntent = PendingIntentUtils.getService(context, 2, deviceCommunicationServiceIntent, PendingIntent.FLAG_ONE_SHOT, false);
+            builder.addAction(R.drawable.ic_notification, context.getString(R.string.controlcenter_connect), reconnectPendingIntent);
+        }
+
+        builder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC);
+
+        if (GBApplication.minimizeNotification()) {
+            builder.setPriority(Notification.PRIORITY_MIN);
+        }
+        return builder.build();
+    }
+
+    public static void updateNotification(List<GBDevice> devices, Context context) {
+        Notification notification = createNotification(devices, context);
+        notify(NOTIFICATION_ID, notification, context);
+    }
+
+    public static void notify(int id, @NonNull Notification notification, Context context) {
+        createNotificationChannels(context);
+
+        try {
+            NotificationManagerCompat.from(context).notify(id, notification);
+        } catch (SecurityException e) {
+            toast(context.getString(R.string.warning_missing_notification_permission), Toast.LENGTH_SHORT, WARN);
+        }
+    }
+
+    public static void removeNotification(int id, Context context) {
+        NotificationManagerCompat.from(context).cancel(id);
+    }
+
+    public static boolean isBluetoothEnabled() {
+        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+        return adapter != null && adapter.isEnabled();
+    }
+
+    public static boolean supportsBluetoothLE() {
+        return GBApplication.getContext().getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE);
+    }
+
+    public static final char[] HEX_CHARS = "0123456789ABCDEF".toCharArray();
+
+    public static String hexdump(byte[] buffer, int offset, int length) {
+        if (length == -1) {
+            length = buffer.length - offset;
+        }
+
+        char[] hexChars = new char[length * 2];
+        for (int i = 0; i < length; i++) {
+            int v = buffer[i + offset] & 0xFF;
+            hexChars[i * 2] = HEX_CHARS[v >>> 4];
+            hexChars[i * 2 + 1] = HEX_CHARS[v & 0x0F];
+        }
+        return new String(hexChars);
+    }
+
+    public static String hexdump(byte[] buffer) {
+        return hexdump(buffer, 0, buffer.length);
+    }
+
+    /**
+     * https://stackoverflow.com/a/140861/4636860
+     */
+    public static byte[] hexStringToByteArray(String s) {
+        int len = s.length();
+        byte[] data = new byte[len / 2];
+        for (int i = 0; i < len; i += 2) {
+            data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
+                    + Character.digit(s.charAt(i + 1), 16));
+        }
+        return data;
+    }
+
+    public static String formatRssi(short rssi) {
+        return String.valueOf(rssi);
+    }
+
+    public static String writeScreenshot(GBDeviceEventScreenshot screenshot, String filename) throws IOException {
+        LOG.info("Will write screenshot as {}", filename);
+
+        final File dir = FileUtils.getExternalFilesDir();
+        final File outputFile = new File(dir, filename);
+        try (FileOutputStream fos = new FileOutputStream(outputFile)) {
+            fos.write(screenshot.getData());
+        }
+        return outputFile.getAbsolutePath();
+    }
+
+    /**
+     * Creates and display a Toast message using the application context.
+     * Additionally the toast is logged using the provided severity.
+     * Can be called from any thread.
+     *
+     * @param message     the message to display.
+     * @param displayTime something like Toast.LENGTH_SHORT
+     * @param severity    either INFO, WARNING, ERROR
+     */
+    public static void toast(String message, int displayTime, int severity) {
+        toast(GBApplication.getContext(), message, displayTime, severity, null);
+    }
+
+    /**
+     * Creates and display a Toast message using the application context.
+     * Additionally the toast is logged using the provided severity.
+     * Can be called from any thread.
+     *
+     * @param message     the message to display.
+     * @param displayTime something like Toast.LENGTH_SHORT
+     * @param severity    either INFO, WARNING, ERROR
+     */
+    public static void toast(String message, int displayTime, int severity, Throwable ex) {
+        toast(GBApplication.getContext(), message, displayTime, severity, ex);
+    }
+
+    /**
+     * Creates and display a Toast message using the application context
+     * Can be called from any thread.
+     *
+     * @param context     the context to use
+     * @param message     the message to display
+     * @param displayTime something like Toast.LENGTH_SHORT
+     * @param severity    either INFO, WARNING, ERROR
+     */
+    public static void toast(final Context context, final String message, final int displayTime, final int severity) {
+        toast(context, message, displayTime, severity, null);
+    }
+
+    public static void toast(final Context context, @StringRes final int message, final int displayTime, final int severity) {
+        toast(context, context.getString(message), displayTime, severity, null);
+    }
+
+    /**
+     * Creates and display a Toast message using the application context
+     * Can be called from any thread.
+     *
+     * @param context     the context to use
+     * @param message     the message to display
+     * @param displayTime something like Toast.LENGTH_SHORT
+     * @param severity    either INFO, WARNING, ERROR
+     * @param ex          optional exception to be logged
+     */
+    public static void toast(final Context context, final String message, final int displayTime, final int severity, final Throwable ex) {
+        log(message, severity, ex); // log immediately, not delayed
+        if (GBEnvironment.env().isLocalTest()) {
+            return;
+        }
+        Looper mainLooper = Looper.getMainLooper();
+        if (Thread.currentThread() == mainLooper.getThread()) {
+            Toast.makeText(context, message, displayTime).show();
+        } else {
+            Runnable runnable = new Runnable() {
+                @Override
+                public void run() {
+                    Toast.makeText(context, message, displayTime).show();
+                }
+            };
+
+            if (context instanceof Activity) {
+                ((Activity) context).runOnUiThread(runnable);
+            } else {
+                new Handler(mainLooper).post(runnable);
+            }
+        }
+    }
+
+    public static void log(String message, int severity, Throwable ex) {
+        switch (severity) {
+            case INFO:
+                LOG.info(message, ex);
+                break;
+            case WARN:
+                LOG.warn(message, ex);
+                break;
+            case ERROR:
+                LOG.error(message, ex);
+                break;
+        }
+    }
+
+    private static Notification createTransferNotification(String title, String text, boolean ongoing,
+                                                           int percentage, Context context) {
+        Intent notificationIntent = new Intent(context, ControlCenterv2.class);
+        notificationIntent.setPackage(BuildConfig.APPLICATION_ID);
+        notificationIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+                | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+        PendingIntent pendingIntent = PendingIntentUtils.getActivity(context, 0,
+                notificationIntent, 0, false);
+
+        NotificationCompat.Builder nb = new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID_TRANSFER)
+                .setTicker((title == null) ? context.getString(R.string.app_name) : title)
+                .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
+                .setContentTitle((title == null) ? context.getString(R.string.app_name) : title)
+                .setStyle(new NotificationCompat.BigTextStyle().bigText(text))
+                .setContentText(text)
+                .setContentIntent(pendingIntent)
+                .setOnlyAlertOnce(percentage > 0 && percentage < 100)
+                .setOngoing(ongoing);
+
+        if (ongoing) {
+            nb.setProgress(100, percentage, percentage == 0);
+            nb.setSmallIcon(android.R.drawable.stat_sys_download);
+        } else {
+            nb.setProgress(0, 0, false);
+            nb.setSmallIcon(android.R.drawable.stat_sys_download_done);
+        }
+
+        return nb.build();
+    }
+
+    public static void updateTransferNotification(String title, String text, boolean ongoing, int percentage, Context context) {
+        if (percentage == 100) {
+            removeNotification(NOTIFICATION_ID_TRANSFER, context);
+        } else {
+            Notification notification = createTransferNotification(title, text, ongoing, percentage, context);
+            notify(NOTIFICATION_ID_TRANSFER, notification, context);
+        }
+    }
+
+    private static Notification createInstallNotification(String text, boolean ongoing,
+                                                          int percentage, Context context) {
+        Intent notificationIntent = new Intent(context, ControlCenterv2.class);
+        notificationIntent.setPackage(BuildConfig.APPLICATION_ID);
+        notificationIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+                | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+        PendingIntent pendingIntent = PendingIntentUtils.getActivity(context, 0,
+                notificationIntent, 0, false);
+
+        NotificationCompat.Builder nb = new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID)
+                .setContentTitle(context.getString(R.string.app_name))
+                .setContentText(text)
+                .setTicker(text)
+                .setContentIntent(pendingIntent)
+                .setOngoing(ongoing);
+
+        if (ongoing) {
+            nb.setProgress(100, percentage, percentage == 0);
+            nb.setSmallIcon(android.R.drawable.stat_sys_upload);
+
+        } else {
+            nb.setSmallIcon(android.R.drawable.stat_sys_upload_done);
+        }
+
+        return nb.build();
+    }
+
+    public static void updateInstallNotification(String text, boolean ongoing, int percentage, Context context) {
+        Notification notification = createInstallNotification(text, ongoing, percentage, context);
+        notify(NOTIFICATION_ID_INSTALL, notification, context);
+    }
+
+    private static Notification createBatteryLowNotification(String text, String bigText, Context context) {
+        Intent notificationIntent = new Intent(context, ControlCenterv2.class);
+        notificationIntent.setPackage(BuildConfig.APPLICATION_ID);
+        notificationIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+                | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+        PendingIntent pendingIntent = PendingIntentUtils.getActivity(context, 0,
+                notificationIntent, 0, false);
+
+        NotificationCompat.Builder nb = new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID_LOW_BATTERY)
+                .setContentTitle(context.getString(R.string.notif_battery_low_title))
+                .setContentText(text)
+                .setContentIntent(pendingIntent)
+                .setSmallIcon(R.drawable.ic_notification_low_battery)
+                .setPriority(Notification.PRIORITY_HIGH)
+                .setOngoing(false);
+
+        if (bigText != null) {
+            nb.setStyle(new NotificationCompat.BigTextStyle().bigText(bigText));
+        }
+
+        return nb.build();
+    }
+
+    private static Notification createBatteryFullNotification(String text, String bigText, Context context) {
+        Intent notificationIntent = new Intent(context, ControlCenterv2.class);
+        notificationIntent.setPackage(BuildConfig.APPLICATION_ID);
+        notificationIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+                | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+        PendingIntent pendingIntent = PendingIntentUtils.getActivity(context, 0,
+                notificationIntent, 0, false);
+
+        NotificationCompat.Builder nb = new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID_FULL_BATTERY)
+                .setContentTitle(context.getString(R.string.notif_battery_full_title))
+                .setContentText(text)
+                .setContentIntent(pendingIntent)
+                .setSmallIcon(R.drawable.ic_notification_full_battery)
+                .setPriority(Notification.PRIORITY_HIGH)
+                .setOngoing(false);
+
+        if (bigText != null) {
+            nb.setStyle(new NotificationCompat.BigTextStyle().bigText(bigText));
+        }
+
+        return nb.build();
+    }
+
+    public static void updateBatteryLowNotification(String text, String bigText, Context context) {
+        if (GBEnvironment.env().isLocalTest()) {
+            return;
+        }
+        Notification notification = createBatteryLowNotification(text, bigText, context);
+        notify(NOTIFICATION_ID_LOW_BATTERY, notification, context);
+    }
+
+    public static void removeBatteryLowNotification(Context context) {
+        removeNotification(NOTIFICATION_ID_LOW_BATTERY, context);
+    }
+
+    public static void updateBatteryFullNotification(String text, String bigText, Context context) {
+        if (GBEnvironment.env().isLocalTest()) {
+            return;
+        }
+        Notification notification = createBatteryFullNotification(text, bigText, context);
+        notify(NOTIFICATION_ID_FULL_BATTERY, notification, context);
+    }
+
+    public static void removeBatteryFullNotification(Context context) {
+        removeNotification(NOTIFICATION_ID_FULL_BATTERY, context);
+    }
+
+    public static Notification createExportFailedNotification(String text, Context context) {
+        Intent notificationIntent = new Intent(context, SettingsActivity.class);
+        notificationIntent.setPackage(BuildConfig.APPLICATION_ID);
+        notificationIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+                | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+        PendingIntent pendingIntent = PendingIntentUtils.getActivity(context, 0,
+                notificationIntent, 0, false);
+
+        NotificationCompat.Builder nb = new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID)
+                .setContentTitle(context.getString(R.string.notif_export_failed_title))
+                .setContentText(text)
+                .setContentIntent(pendingIntent)
+                .setSmallIcon(R.drawable.ic_notification)
+                .setPriority(Notification.PRIORITY_HIGH)
+                .setOngoing(false);
+
+        return nb.build();
+    }
+
+    public static void updateExportFailedNotification(String text, Context context) {
+        if (GBEnvironment.env().isLocalTest()) {
+            return;
+        }
+        Notification notification = createExportFailedNotification(text, context);
+        notify(NOTIFICATION_ID_EXPORT_FAILED, notification, context);
+    }
+
+    public static void assertThat(boolean condition, String errorMessage) {
+        if (!condition) {
+            throw new AssertionError(errorMessage);
+        }
+    }
+
+    public static void signalActivityDataFinish(final GBDevice device) {
+        final Intent intent = new Intent(GBApplication.ACTION_NEW_DATA);
+        intent.putExtra(GBDevice.EXTRA_DEVICE, device);
+
+        LocalBroadcastManager.getInstance(GBApplication.getContext()).sendBroadcast(intent);
+
+        if (!GBApplication.getPrefs().getBoolean("intent_api_broadcast_activity_sync", false)) {
+            return;
+        }
+
+        LOG.info("Broadcasting activity sync finish");
+
+        final Intent activitySyncFinishIntent = new Intent(ACTION_ACTIVITY_SYNC);
+        GBApplication.getContext().sendBroadcast(activitySyncFinishIntent);
+    }
+
+    public static boolean checkPermission(final Context context, final String permission) {
+        return ActivityCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GBChangeLog.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GBChangeLog.java
new file mode 100644
index 0000000..0fea1db
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GBChangeLog.java
@@ -0,0 +1,115 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.util;
+
+import android.content.Context;
+import android.content.DialogInterface;
+import android.util.SparseArray;
+import android.webkit.WebView;
+
+import androidx.appcompat.app.AlertDialog;
+
+import com.google.android.material.dialog.MaterialAlertDialogBuilder;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import de.cketti.library.changelog.ChangeLog;
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+
+public class GBChangeLog extends ChangeLog {
+    private static final Logger LOG = LoggerFactory.getLogger(GBChangeLog.class);
+
+    public GBChangeLog(Context context, String css) {
+        super(context, css);
+    }
+
+    @Override
+    protected SparseArray<ReleaseItem> getMasterChangeLog(boolean full) {
+        if (GBApplication.isNightly()) {
+            try {
+                return readChangeLogFromResource(R.xml.changelog_git, full);
+            } catch (final Exception e) {
+                // Just in case the git changelog is broken somehow..
+                LOG.error("Failed to read git changelog for nightly", e);
+            }
+        }
+
+        return super.getMasterChangeLog(full);
+    }
+
+    public AlertDialog getMaterialLogDialog() {
+        return getMaterialDialog(isFirstRunEver());
+    }
+
+    public AlertDialog getMaterialFullLogDialog() {
+        return getMaterialDialog(true);
+    }
+
+    /**
+     * Checks whether there are changes to display.
+     */
+    public boolean hasChanges(final boolean full) {
+        return !getChangeLog(full).isEmpty();
+    }
+
+    protected AlertDialog getMaterialDialog(boolean full) {
+        WebView wv = new WebView(mContext);
+        wv.setBackgroundColor(0); // transparent
+        wv.loadDataWithBaseURL(null, getLog(full), "text/html", "UTF-8", null);
+
+        MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(mContext);
+        builder.setTitle(
+                        mContext.getResources().getString(
+                                full ? R.string.changelog_full_title : R.string.changelog_title))
+                .setView(wv)
+                .setCancelable(false)
+                // OK button
+                .setPositiveButton(
+                        mContext.getResources().getString(R.string.changelog_ok_button),
+                        new DialogInterface.OnClickListener() {
+                            @Override
+                            public void onClick(DialogInterface dialog, int which) {
+                                // The user clicked "OK" so save the current version code as
+                                // "last version code".
+                                updateVersionInPreferences();
+                            }
+                        });
+
+        if (!full) {
+            // Show "More鈥�" button if we're only displaying a partial change log.
+            builder.setNegativeButton(R.string.changelog_show_full,
+                    new DialogInterface.OnClickListener() {
+                        @Override
+                        public void onClick(DialogInterface dialog, int id) {
+                            getMaterialFullLogDialog().show();
+                        }
+                    });
+        }
+
+        return builder.create();
+    }
+
+    public static GBChangeLog createChangeLog(Context context) {
+        String css = GBChangeLog.DEFAULT_CSS;
+        css += "body { "
+                + "color: " + AndroidUtils.getTextColorHex(context) + "; "
+                + "}";
+        return new GBChangeLog(context, css);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GBPrefs.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GBPrefs.java
new file mode 100644
index 0000000..dea0221
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GBPrefs.java
@@ -0,0 +1,155 @@
+/*  Copyright (C) 2016-2024 Andreas Shimokawa, Anemograph, Carsten Pfeiffer,
+    Daniel Dakhno, Daniele Gobbetti, Davis Mosenkovs, Dikay900, Felix Konstantin
+    Maurer, Jos茅 Rebelo, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.util;
+
+import android.Manifest;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
+import android.location.Criteria;
+import android.location.Location;
+import android.location.LocationManager;
+import android.util.Log;
+
+import androidx.core.app.ActivityCompat;
+
+import java.text.ParseException;
+import java.time.LocalTime;
+import java.util.Date;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.SettingsActivity;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class GBPrefs extends Prefs {
+    // Since this class must not log to slf4j, we use plain android.util.Log
+    private static final String TAG = "GBPrefs";
+
+    public static final String PACKAGE_BLACKLIST = "package_blacklist";
+    public static final String PACKAGE_PEBBLEMSG_BLACKLIST = "package_pebblemsg_blacklist";
+    public static final String CALENDAR_BLACKLIST = "calendar_blacklist";
+    public static final String DEVICE_AUTO_RECONNECT = "prefs_key_device_auto_reconnect";
+    public static final String DEVICE_CONNECT_BACK = "prefs_key_device_reconnect_on_acl";
+    private static final String AUTO_START = "general_autostartonboot";
+    public static final String AUTO_EXPORT_ENABLED = "auto_export_enabled";
+    public static final String AUTO_EXPORT_LOCATION = "auto_export_location";
+    public static final String PING_TONE = "ping_tone";
+    public static final String AUTO_EXPORT_INTERVAL = "auto_export_interval";
+    private static final boolean AUTO_START_DEFAULT = true;
+    public static final String RTL_SUPPORT = "rtl";
+    public static final String RTL_CONTEXTUAL_ARABIC = "contextualArabic";
+    public static boolean AUTO_RECONNECT_DEFAULT = true;
+    public static final String PREF_ALLOW_INTENT_API = "prefs_key_allow_bluetooth_intent_api";
+
+    public static final String RECONNECT_SCAN_KEY = "prefs_general_key_auto_reconnect_scan";
+    public static final boolean RECONNECT_SCAN_DEFAULT = false;
+
+    public static final String USER_NAME = "mi_user_alias";
+    public static final String USER_NAME_DEFAULT = "gadgetbridge-user";
+    private static final String USER_BIRTHDAY = "";
+
+    public static final String CHART_MAX_HEART_RATE = "chart_max_heart_rate";
+    public static final String CHART_MIN_HEART_RATE = "chart_min_heart_rate";
+
+    public static final String LAST_DEVICE_ADDRESSES = "last_device_addresses";
+    public static final String RECONNECT_ONLY_TO_CONNECTED = "general_reconnectonlytoconnected";
+
+    @Deprecated
+    public GBPrefs(Prefs prefs) {
+        this(prefs.getPreferences());
+    }
+
+    public GBPrefs(final SharedPreferences sharedPrefs) {
+        super(sharedPrefs);
+    }
+
+    public boolean getAutoReconnect(GBDevice device) {
+        SharedPreferences deviceSpecificPreferences = GBApplication.getDeviceSpecificSharedPrefs(device.getAddress());
+        return deviceSpecificPreferences.getBoolean(DEVICE_AUTO_RECONNECT, AUTO_RECONNECT_DEFAULT);
+    }
+
+    public boolean getAutoReconnectByScan() {
+        return getBoolean(RECONNECT_SCAN_KEY, RECONNECT_SCAN_DEFAULT);
+    }
+
+    public boolean getAutoStart() {
+        return getBoolean(AUTO_START, AUTO_START_DEFAULT);
+    }
+
+    public String getUserName() {
+        return getString(USER_NAME, USER_NAME_DEFAULT);
+    }
+
+    public Date getUserBirthday() {
+        String date = getString(USER_BIRTHDAY, null);
+        if (date == null) {
+            return null;
+        }
+        try {
+            return DateTimeUtils.dayFromString(date);
+        } catch (ParseException ex) {
+            GB.log("Error parsing date: " + date, GB.ERROR, ex);
+            return null;
+        }
+    }
+
+    public int getUserGender() {
+        return 0;
+    }
+
+    public float[] getLongLat(Context context) {
+        float latitude = getFloat("location_latitude", 0);
+        float longitude = getFloat("location_longitude", 0);
+        Log.i(TAG, "got longitude/latitude from preferences: " + latitude + "/" + longitude);
+
+        if (ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED &&
+                getBoolean("use_updated_location_if_available", false)) {
+            LocationManager locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
+            Criteria criteria = new Criteria();
+            String provider = locationManager.getBestProvider(criteria, false);
+            if (provider != null) {
+                Location lastKnownLocation = locationManager.getLastKnownLocation(provider);
+                if (lastKnownLocation != null) {
+                    latitude = (float) lastKnownLocation.getLatitude();
+                    longitude = (float) lastKnownLocation.getLongitude();
+                    Log.i(TAG, "got longitude/latitude from last known location: " + latitude + "/" + longitude);
+                }
+            }
+        }
+        return new float[]{longitude, latitude};
+    }
+
+    public boolean getNotificationTimesEnabled() {
+        return getBoolean("notification_times_enabled", false);
+    }
+
+    public LocalTime getNotificationTimesStart() {
+        return getLocalTime("notification_times_start", "08:00");
+    }
+
+    public LocalTime getNotificationTimesEnd() {
+        return getLocalTime("notification_times_end", "22:00");
+    }
+
+    public boolean isMetricUnits() {
+        return getString(SettingsActivity.PREF_MEASUREMENT_SYSTEM, "metric").equals("metric");
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GBTarFile.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GBTarFile.java
new file mode 100644
index 0000000..d2bd038
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GBTarFile.java
@@ -0,0 +1,79 @@
+package nodomain.freeyourgadget.gadgetbridge.util;
+
+import org.bouncycastle.shaded.util.Arrays;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+
+
+public class GBTarFile {
+    private static final Logger LOG = LoggerFactory.getLogger(GBTarFile.class);
+    private final byte[] tarBytes;
+    public static final int TAR_MAGIC_BYTES_OFFSET = 257;
+    public static final byte[] TAR_MAGIC_BYTES = new byte[]{
+            'u', 's', 't', 'a', 'r', '\0'
+    };
+    public static final int TAR_BLOCK_SIZE = 512;
+    public static final int TAR_HEADER_FILE_NAME_OFFSET = 0;
+    public static final int TAR_HEADER_FILE_NAME_LENGTH = 100;
+    public static final int TAR_HEADER_FILE_SIZE_OFFSET = 124;
+    public static final int TAR_HEADER_FILE_SIZE_LENGTH = 12;
+
+
+    public GBTarFile(byte[] tarBytes) {
+        this.tarBytes = tarBytes;
+    }
+
+    public static boolean isTarFile(byte[] data) {
+        return ArrayUtils.equals(data, TAR_MAGIC_BYTES, TAR_MAGIC_BYTES_OFFSET);
+    }
+
+    public List<String> listFileNames() {
+        final List<String> fileNames = new ArrayList<>();
+        for (TarHeader header: listHeaders()) {
+            fileNames.add(header.fileName);
+        }
+        return fileNames;
+    }
+
+    public boolean containsFile(String fileName) {
+        for (TarHeader header: listHeaders()) {
+            if (fileName.equals(header.fileName)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private List<TarHeader> listHeaders() {
+        final List<TarHeader> headers = new ArrayList<>();
+        int offset = 0;
+        while (ArrayUtils.equals(tarBytes, TAR_MAGIC_BYTES, offset + TAR_MAGIC_BYTES_OFFSET)) {
+            final TarHeader tarHeader = new TarHeader(Arrays.copyOfRange(tarBytes, offset, offset + TAR_BLOCK_SIZE));
+            headers.add(tarHeader);
+            offset += (((tarHeader.fileSize + TAR_BLOCK_SIZE - 1) / TAR_BLOCK_SIZE) + 1) * TAR_BLOCK_SIZE;
+        }
+        return headers;
+    }
+
+    private static class TarHeader {
+        final String fileName;
+        final int fileSize;
+
+        public TarHeader(byte[] header) {
+            fileName = parseString(header, TAR_HEADER_FILE_NAME_OFFSET, TAR_HEADER_FILE_NAME_LENGTH);
+            fileSize = Integer.parseInt(parseString(header, TAR_HEADER_FILE_SIZE_OFFSET, TAR_HEADER_FILE_SIZE_LENGTH).trim(), 8);
+        }
+
+        private static String parseString(final byte[] data, final int offset, final int maxLength) {
+            int length = 0;
+            while (length < maxLength && offset + length < data.length && data[offset + length] != 0) {
+                length++;
+            }
+            return new String(data, offset, length, StandardCharsets.US_ASCII);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GBTextToSpeech.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GBTextToSpeech.java
new file mode 100644
index 0000000..682f9cb
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GBTextToSpeech.java
@@ -0,0 +1,77 @@
+package nodomain.freeyourgadget.gadgetbridge.util;
+
+import android.content.Context;
+import android.media.AudioManager;
+import android.os.Bundle;
+import android.speech.tts.TextToSpeech;
+import android.speech.tts.UtteranceProgressListener;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+public class GBTextToSpeech {
+    private static final Logger LOG = LoggerFactory.getLogger(GBTextToSpeech.class);
+    private final Context context;
+    private TextToSpeech textToSpeech;
+    private boolean isConnected = false;
+    private final AudioManager audioManager;
+    private int audioFocus;
+
+    public GBTextToSpeech(Context context, UtteranceProgressListener callback, int audioFocus) {
+        this.context = context;
+        initializeTTS(callback);
+        this.audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+        this.audioFocus = audioFocus;
+    }
+
+    public void setAudioFocus(int audioFocus) {
+        this.audioFocus = audioFocus;
+    }
+
+    public boolean isConnected() {
+        return isConnected;
+    }
+
+    private void initializeTTS(UtteranceProgressListener callback) {
+        textToSpeech = new TextToSpeech(context, status -> {
+            if (status == TextToSpeech.SUCCESS) {
+                if (textToSpeech.getVoice() == null) {
+                    LOG.error("TTS returned error: No voice available.");
+                } else {
+                    this.isConnected = true;
+                    textToSpeech.setOnUtteranceProgressListener(callback);
+                }
+            } else {
+                LOG.error("TTS returned error: Initialization failed.");
+            }
+        });
+    }
+
+    public void speak(String text) {
+        Bundle params = new Bundle();
+        // Put the audio stream type into the Bundle
+        params.putInt(TextToSpeech.Engine.KEY_PARAM_STREAM, AudioManager.STREAM_RING);
+        textToSpeech.speak(text, TextToSpeech.QUEUE_FLUSH, params, "call");
+    }
+
+    public void speakNotification(String text) {
+        int result = audioManager.requestAudioFocus(null, AudioManager.STREAM_MUSIC, this.audioFocus);
+        if (AudioManager.AUDIOFOCUS_REQUEST_GRANTED != result)
+            LOG.warn("AudioManager did not grant us the requested focus");
+        Bundle params = new Bundle();
+        params.putInt(TextToSpeech.Engine.KEY_PARAM_STREAM, AudioManager.STREAM_MUSIC);
+        textToSpeech.speak(text, TextToSpeech.QUEUE_FLUSH, params, "notification");
+    }
+
+    public void shutdown() {
+        if (textToSpeech != null) {
+            textToSpeech.stop();
+            textToSpeech.shutdown();
+        }
+    }
+
+    public void abandonFocus() {
+        audioManager.abandonAudioFocus(null);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GBToStringBuilder.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GBToStringBuilder.java
new file mode 100644
index 0000000..cce70c3
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GBToStringBuilder.java
@@ -0,0 +1,50 @@
+package nodomain.freeyourgadget.gadgetbridge.util;
+
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+
+public class GBToStringBuilder extends ToStringBuilder {
+    public static final GBToStringStyle STYLE = new GBToStringStyle();
+
+    private static final SimpleDateFormat SDF = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.US);
+
+    public GBToStringBuilder(final Object object) {
+        super(object, STYLE);
+    }
+
+    public static class GBToStringStyle extends ToStringStyle {
+        public GBToStringStyle() {
+            super();
+
+            this.setUseShortClassName(true);
+            this.setUseIdentityHashCode(false);
+
+            this.setContentStart("{");
+            this.setContentEnd("}");
+
+            this.setArrayStart("[");
+            this.setArrayEnd("]");
+
+            this.setFieldSeparator(", ");
+            this.setFieldNameValueSeparator("=");
+
+            this.setNullText("null");
+        }
+
+        @Override
+        public void append(final StringBuffer buffer, final String fieldName, final Object value, final Boolean fullDetail) {
+            // omit nulls
+            if (value != null) {
+                if (value instanceof Date) {
+                    super.append(buffer, fieldName, SDF.format(value), fullDetail);
+                } else {
+                    super.append(buffer, fieldName, value, fullDetail);
+                }
+            }
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GBZipFile.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GBZipFile.java
new file mode 100644
index 0000000..08c33e1
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GBZipFile.java
@@ -0,0 +1,131 @@
+/*  Copyright (C) 2022-2024 Jos茅 Rebelo, MPeter
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.util;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipException;
+import java.util.zip.ZipInputStream;
+
+/**
+ * Utility class for recognition and reading of ZIP archives.
+ */
+public class GBZipFile {
+    private static final Logger LOG = LoggerFactory.getLogger(GBZipFile.class);
+    public static final byte[] ZIP_HEADER = new byte[]{
+        0x50, 0x4B, 0x03, 0x04
+    };
+
+    private final byte[] zipBytes;
+
+    /**
+     * Open ZIP file from byte array already in memory.
+     * @param zipBytes data to handle as a ZIP file.
+     */
+    public GBZipFile(byte[] zipBytes) {
+        this.zipBytes = zipBytes;
+    }
+
+    /**
+     * Open ZIP file from InputStream.<br>
+     * This will read the entire file into memory at once.
+     * @param inputStream data to handle as a ZIP file.
+     */
+    public GBZipFile(InputStream inputStream) throws IOException {
+        this.zipBytes = readAllBytes(inputStream);
+    }
+
+    /**
+     * Checks if data resembles a ZIP file.<br>
+     * The check is not infallible: it may report self-extracting or other exotic ZIP archives as not a ZIP file, and it may report a corrupted ZIP file as a ZIP file.
+     * @param data The data to check.
+     * @return Whether data resembles a ZIP file.
+     */
+    public static boolean isZipFile(byte[] data) {
+        return ArrayUtils.equals(data, ZIP_HEADER, 0);
+    }
+
+    /**
+     * Reads the contents of file at path into a byte array.
+     * @param path Path of the file in the ZIP file.
+     * @return byte array contatining the contents of the requested file.
+     * @throws ZipFileException If the specified path does not exist or references a directory, or if some other I/O error occurs. In other words, if return value would otherwise be null.
+     */
+    public byte[] getFileFromZip(final String path) throws ZipFileException {
+        try (InputStream is = new ByteArrayInputStream(zipBytes); ZipInputStream zipInputStream = new ZipInputStream(is)) {
+            ZipEntry zipEntry;
+            while ((zipEntry = zipInputStream.getNextEntry()) != null) {
+                if (!zipEntry.getName().equals(path)) continue; // TODO: is this always a path? The documentation is very vague.
+
+                if (zipEntry.isDirectory()) {
+                    throw new ZipFileException(String.format("Path in ZIP file is a directory: %s", path));
+                }
+
+                return readAllBytes(zipInputStream);
+            }
+
+            throw new ZipFileException(String.format("Path in ZIP file was not found: %s", path));
+
+        } catch (ZipException e) {
+            throw new ZipFileException("The ZIP file might be corrupted", e);
+        } catch (IOException e) {
+            throw new ZipFileException("General IO error", e);
+        }
+    }
+
+    public boolean fileExists(final String path) throws ZipFileException {
+        try (InputStream is = new ByteArrayInputStream(zipBytes); ZipInputStream zipInputStream = new ZipInputStream(is)) {
+            ZipEntry zipEntry;
+            while ((zipEntry = zipInputStream.getNextEntry()) != null) {
+                if (!zipEntry.getName().equals(path)) {
+                    continue;
+                }
+
+                if (zipEntry.isDirectory()) {
+                    return false;
+                }
+
+                return true;
+            }
+
+            return false;
+        } catch (final ZipException e) {
+            throw new ZipFileException("The ZIP file might be corrupted", e);
+        } catch (final IOException e) {
+            throw new ZipFileException("General IO error", e);
+        }
+    }
+
+    public static byte[] readAllBytes(final InputStream is) throws IOException {
+        final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+
+        int n;
+        byte[] buf = new byte[16384];
+
+        while ((n = is.read(buf, 0, buf.length)) != -1) {
+            buffer.write(buf, 0, n);
+        }
+
+        return buffer.toByteArray();
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GridAutoFitLayoutManager.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GridAutoFitLayoutManager.java
new file mode 100644
index 0000000..6b35656
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GridAutoFitLayoutManager.java
@@ -0,0 +1,77 @@
+// https://gist.github.com/omidraha/af3aa017d4ec06342bdc03c49d4b83b1
+
+// https://stackoverflow.com/a/30256880/538284
+// https://stackoverflow.com/a/42241730/538284
+// https://stackoverflow.com/a/38082715/538284
+
+package nodomain.freeyourgadget.gadgetbridge.util;
+
+import android.content.Context;
+import android.util.TypedValue;
+
+import androidx.recyclerview.widget.GridLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+public class GridAutoFitLayoutManager extends GridLayoutManager {
+    private int mColumnWidth;
+    private boolean mColumnWidthChanged = true;
+    private boolean mWidthChanged = true;
+    private int mWidth;
+    private static final int sColumnWidth = 200; // assume cell width of 200dp
+
+    public GridAutoFitLayoutManager(Context context, int columnWidth) {
+        /* Initially set spanCount to 1, will be changed automatically later. */
+        super(context, 1);
+        setColumnWidth(checkedColumnWidth(context, columnWidth));
+    }
+
+    public GridAutoFitLayoutManager(Context context, int columnWidth, int orientation, boolean reverseLayout) {
+        /* Initially set spanCount to 1, will be changed automatically later. */
+        super(context, 1, orientation, reverseLayout);
+        setColumnWidth(checkedColumnWidth(context, columnWidth));
+    }
+
+    private int checkedColumnWidth(Context context, int columnWidth) {
+        if (columnWidth <= 0) {
+            columnWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, sColumnWidth,
+                    context.getResources().getDisplayMetrics());
+        } else {
+            columnWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, columnWidth,
+                    context.getResources().getDisplayMetrics());
+        }
+        return columnWidth;
+    }
+
+    private void setColumnWidth(int newColumnWidth) {
+        if (newColumnWidth > 0 && newColumnWidth != mColumnWidth) {
+            mColumnWidth = newColumnWidth;
+            mColumnWidthChanged = true;
+        }
+    }
+
+    @Override
+    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+        int width = getWidth();
+        int height = getHeight();
+
+        if (width != mWidth) {
+            mWidthChanged = true;
+            mWidth = width;
+        }
+
+        if (mColumnWidthChanged && mColumnWidth > 0 && width > 0 && height > 0
+                || mWidthChanged) {
+            int totalSpace;
+            if (getOrientation() == VERTICAL) {
+                totalSpace = width - getPaddingRight() - getPaddingLeft();
+            } else {
+                totalSpace = height - getPaddingTop() - getPaddingBottom();
+            }
+            int spanCount = Math.max(1, totalSpace / mColumnWidth);
+            setSpanCount(spanCount);
+            mColumnWidthChanged = false;
+            mWidthChanged = false;
+        }
+        super.onLayoutChildren(recycler, state);
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/HttpUtils.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/HttpUtils.java
new file mode 100644
index 0000000..4c804f8
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/HttpUtils.java
@@ -0,0 +1,42 @@
+package nodomain.freeyourgadget.gadgetbridge.util;
+
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.net.URL;
+import java.net.URLDecoder;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+public class HttpUtils {
+    private static final Logger LOG = LoggerFactory.getLogger(HttpUtils.class);
+
+    private HttpUtils() {
+        // utility class
+    }
+
+    public static Map<String, String> urlQueryParameters(final URL url) {
+        final String query = url.getQuery();
+        if (StringUtils.isBlank(query)) {
+            return Collections.emptyMap();
+        }
+        final Map<String, String> queryParameters = new LinkedHashMap<>();
+        final String[] pairs = query.split("&");
+        for (final String pair : pairs) {
+            final String[] parts = pair.split("=", 2);
+            try {
+                final String key = URLDecoder.decode(parts[0], "UTF-8");
+                if (parts.length == 2) {
+                    queryParameters.put(key, URLDecoder.decode(parts[1], "UTF-8"));
+                } else {
+                    queryParameters.put(key, "");
+                }
+            } catch (final Exception e) {
+                LOG.error("Failed to decode query", e);
+            }
+        }
+        return queryParameters;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/ImportExportSharedPreferences.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/ImportExportSharedPreferences.java
new file mode 100644
index 0000000..3803a83
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/ImportExportSharedPreferences.java
@@ -0,0 +1,186 @@
+/*  Copyright (C) 2017-2024 Alberto, Andreas Shimokawa, Carsten Pfeiffer,
+    Daniele Gobbetti, Joel Beckmeyer, Jos茅 Rebelo, Taavi Eom盲e
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.util;
+
+
+import android.content.SharedPreferences;
+import android.util.Xml;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.Reader;
+import java.io.Writer;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminPreferences;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst;
+import nodomain.freeyourgadget.gadgetbridge.devices.test.TestDeviceConst;
+
+@Deprecated // use JsonBackupPreferences
+public class ImportExportSharedPreferences {
+    private static final Logger LOG = LoggerFactory.getLogger(ImportExportSharedPreferences.class);
+
+    private static final String BOOLEAN = Boolean.class.getSimpleName();
+    private static final String FLOAT = Float.class.getSimpleName();
+    private static final String INTEGER = Integer.class.getSimpleName();
+    private static final String LONG = Long.class.getSimpleName();
+    private static final String STRING = String.class.getSimpleName();
+    private static final String HASHSET = HashSet.class.getSimpleName();
+
+    private static final String NAME = "name";
+    private static final String PREFERENCES = "preferences";
+
+    public static void exportToFile(SharedPreferences sharedPreferences, File outFile) throws IOException {
+        try (FileWriter outputWriter = new FileWriter(outFile)) {
+            export(sharedPreferences, outputWriter);
+        }
+    }
+
+    public static void export(SharedPreferences sharedPreferences, Writer writer) throws IOException {
+        XmlSerializer serializer = Xml.newSerializer();
+        serializer.setOutput(writer);
+        serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
+        serializer.startDocument("UTF-8", true);
+        serializer.startTag("", PREFERENCES);
+        for (Map.Entry<String, ?> entry : sharedPreferences.getAll().entrySet()) {
+            String key = entry.getKey();
+
+            Object valueObject = entry.getValue();
+            // Skip this entry if the value is null;
+            if (valueObject == null) continue;
+
+            String valueType = valueObject.getClass().getSimpleName();
+            String value = valueObject.toString();
+            serializer.startTag("", valueType);
+            serializer.attribute("", NAME, key);
+            serializer.text(value);
+            serializer.endTag("", valueType);
+        }
+        serializer.endTag("", PREFERENCES);
+        serializer.endDocument();
+    }
+
+    public static boolean importFromFile(SharedPreferences sharedPreferences, File inFile)
+            throws Exception {
+        return importFromReader(sharedPreferences, new FileReader(inFile));
+    }
+
+    public static boolean importFromReader(SharedPreferences sharedPreferences, Reader in)
+            throws Exception {
+        SharedPreferences.Editor editor = sharedPreferences.edit();
+        editor.clear();
+        XmlPullParser parser = Xml.newPullParser();
+        parser.setInput(in);
+        int eventType = parser.getEventType();
+        String name = null;
+        String key = null;
+        while (eventType != XmlPullParser.END_DOCUMENT) {
+            switch (eventType) {
+                case XmlPullParser.START_TAG:
+                    name = parser.getName();
+                    key = parser.getAttributeValue("", NAME);
+                    break;
+                case XmlPullParser.TEXT:
+                    // The parser is reading text outside an element if name is null,
+                    // so simply ignore this text part (which is usually something like '\n')
+                    if (name == null) break;
+                    String text = parser.getText();
+                    if (BOOLEAN.equals(name)) {
+                        editor.putBoolean(key, Boolean.parseBoolean(text));
+                    } else if (FLOAT.equals(name)) {
+                        editor.putFloat(key, Float.parseFloat(text));
+                    } else if (INTEGER.equals(name)) {
+                        editor.putInt(key, Integer.parseInt(text));
+                    } else if (LONG.equals(name)) {
+                        editor.putLong(key, Long.parseLong(text));
+                    } else if (STRING.equals(name)) {
+                        editor.putString(key, text);
+                    } else if (HASHSET.equals(name)) {
+                        // FIXME: We can only deserialize values that are guaranteed to not contain commas,
+                        // spaces at the end or start, square brackets
+                        switch (key) {
+                            case GBPrefs.PACKAGE_BLACKLIST:
+                                GBApplication.setAppsNotifBlackList(stringToSet(text), editor);
+                                break;
+                            case GBPrefs.PACKAGE_PEBBLEMSG_BLACKLIST:
+                                GBApplication.setAppsPebbleBlackList(stringToSet(text), editor);
+                                break;
+                            // @array/device_action_values
+                            case DeviceSettingsPreferenceConst.PREF_DEVICE_ACTION_FELL_SLEEP_SELECTIONS:
+                            case DeviceSettingsPreferenceConst.PREF_DEVICE_ACTION_START_NON_WEAR_SELECTIONS:
+                            case DeviceSettingsPreferenceConst.PREF_DEVICE_ACTION_WOKE_UP_SELECTIONS:
+                            // GarminCapability enum
+                            case GarminPreferences.PREF_GARMIN_CAPABILITIES:
+                            // mac addresses
+                            case "dashboard_devices_multiselect":
+                            case GBPrefs.LAST_DEVICE_ADDRESSES:
+                            // display items
+                            case "bip_display_items":
+                            case "cor_display_items":
+                            case "mi2_display_items":
+                            case "miband3_display_items":
+                            case HuamiConst.PREF_DISPLAY_ITEMS:
+                            // @array/pref_amazfitneo_sounds_values
+                            case DeviceSettingsPreferenceConst.PREF_SOUNDS:
+                            // TestFeature enum
+                            case TestDeviceConst.PREF_TEST_FEATURES:
+                            // Ignored due to unsafe values
+                            //case GBPrefs.CALENDAR_BLACKLIST: // user-controlled names
+                            //case LoyaltyCardsSettingsConst.LOYALTY_CARDS_SYNC_GROUPS: // user-controlled names
+                            //case MiBandConst.PREF_MIBAND_ALARMS: // unknown potential values
+                            //case "casio_features_current_values": // unknown potential values
+                                editor.putStringSet(key, stringToSet(text));
+                                break;
+                            default:
+                                LOG.warn("Unknown hashset preference {}, will not import", key);
+                        }
+                    } else if (!PREFERENCES.equals(name)) {
+                        throw new Exception("Unknown type " + name + " for pref " + key);
+                    }
+                    break;
+                case XmlPullParser.END_TAG:
+                    name = null;
+                    break;
+            }
+            eventType = parser.next();
+        }
+        return editor.commit();
+    }
+
+    private static Set<String> stringToSet(final String text) {
+        final Set<String> ret = new HashSet<>();
+        final String[] split = text.replace("[", "")
+                .replace("]", "")
+                .split(",");
+        for (final String s : split) {
+            ret.add(s.trim());
+        }
+        return ret;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/JavaExtensions.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/JavaExtensions.java
new file mode 100644
index 0000000..89646ac
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/JavaExtensions.java
@@ -0,0 +1,31 @@
+/*  Copyright (C) 2017-2024 ivanovlev
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.util;
+
+public class JavaExtensions {
+
+    /**
+     * Equivalent c# '??' operator
+     * @param one first value
+     * @param two second value
+     * @return first if not null, or second if first is null
+     */
+    public static <T> T coalesce(T one, T two)
+    {
+        return one != null ? one : two;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/LimitedQueue.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/LimitedQueue.java
new file mode 100644
index 0000000..40110f5
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/LimitedQueue.java
@@ -0,0 +1,66 @@
+/*  Copyright (C) 2016-2024 Andreas Shimokawa, Daniel Dakhno, Jos茅 Rebelo,
+    Julien Pivotto
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.util;
+
+import android.util.Pair;
+
+import java.util.Iterator;
+import java.util.LinkedList;
+
+public class LimitedQueue<K, V> {
+    private final int limit;
+    private final LinkedList<Pair<K, V>> list = new LinkedList<>();
+
+    public LimitedQueue(final int limit) {
+        this.limit = limit;
+    }
+
+    synchronized public void add(final K id, final V obj) {
+        if (list.size() > limit - 1) {
+            list.removeFirst();
+        }
+        list.add(new Pair<>(id, obj));
+    }
+
+    synchronized public void remove(final K id) {
+        for (final Iterator<Pair<K, V>> it = list.iterator(); it.hasNext(); ) {
+            Pair<K, V> pair = it.next();
+            if (id.equals(pair.first)) {
+                it.remove();
+            }
+        }
+    }
+
+    synchronized public V lookup(final K id) {
+        for (final Pair<K, V> entry : list) {
+            if (id.equals(entry.first)) {
+                return entry.second;
+            }
+        }
+        return null;
+    }
+
+    synchronized public K lookupByValue(final V value){
+        for (final Pair<K, V> entry : list) {
+            if (value.equals(entry.second)) {
+                return entry.first;
+            }
+        }
+        return null;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/MapUtils.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/MapUtils.java
new file mode 100644
index 0000000..b5df5b7
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/MapUtils.java
@@ -0,0 +1,42 @@
+/*  Copyright (C) 2022-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.util;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public final class MapUtils {
+    /**
+     * Reverses a map. If there are multiple values for the same key, the first one will take precedence.
+     *
+     * @param map the map to reverse
+     * @param <V> the type for the values
+     * @param <K> the type for the keys
+     * @return the reversed map
+     */
+    public static <V, K> Map<V, K> reverse(final Map<K, V> map) {
+        final Map<V, K> reversed = new HashMap<>();
+
+        for (final Map.Entry<K, V> entry : map.entrySet()) {
+            if (!reversed.containsKey(entry.getValue())) {
+                reversed.put(entry.getValue(), entry.getKey());
+            }
+        }
+
+        return reversed;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/MediaManager.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/MediaManager.java
new file mode 100644
index 0000000..af76060
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/MediaManager.java
@@ -0,0 +1,183 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.util;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.media.AudioManager;
+import android.media.MediaMetadata;
+import android.media.session.MediaController;
+import android.media.session.MediaSessionManager;
+import android.media.session.PlaybackState;
+
+import androidx.annotation.Nullable;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.externalevents.NotificationListener;
+import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
+
+public class MediaManager {
+    private static final Logger LOG = LoggerFactory.getLogger(MediaManager.class);
+
+    private final Context context;
+
+    private MusicSpec bufferMusicSpec = null;
+    private MusicStateSpec bufferMusicStateSpec = null;
+
+    public MediaManager(final Context context) {
+        this.context = context;
+    }
+
+    public MusicSpec getBufferMusicSpec() {
+        return bufferMusicSpec;
+    }
+
+    public MusicStateSpec getBufferMusicStateSpec() {
+        return bufferMusicStateSpec;
+    }
+
+    /**
+     * Returns true if the spec changed, so the device should be updated.
+     */
+    public boolean onSetMusicState(final MusicStateSpec stateSpec) {
+        if (stateSpec != null && !stateSpec.equals(bufferMusicStateSpec)) {
+            bufferMusicStateSpec = stateSpec;
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Returns true if the spec changed, so the device should be updated.
+     */
+    public boolean onSetMusicInfo(MusicSpec musicSpec) {
+        if (musicSpec != null && !musicSpec.equals(bufferMusicSpec)) {
+            bufferMusicSpec = musicSpec;
+            if (bufferMusicStateSpec != null) {
+                bufferMusicStateSpec.state = 0;
+                bufferMusicStateSpec.position = 0;
+            }
+            return true;
+        }
+        return false;
+    }
+
+    public void refresh() {
+        LOG.info("Refreshing media state");
+
+        final MediaSessionManager mediaSessionManager =
+                (MediaSessionManager) context.getSystemService(Context.MEDIA_SESSION_SERVICE);
+
+        try {
+            final List<MediaController> controllers = mediaSessionManager.getActiveSessions(
+                    new ComponentName(context, NotificationListener.class)
+            );
+            if (controllers.isEmpty()) {
+                LOG.debug("No media controller available");
+                return;
+            }
+            final MediaController controller = controllers.get(0);
+
+            bufferMusicSpec = extractMusicSpec(controller.getMetadata());
+            bufferMusicStateSpec = extractMusicStateSpec(controller.getPlaybackState());
+        } catch (final SecurityException e) {
+            LOG.warn("No permission to get media sessions - did not grant notification access?", e);
+        } catch (final Exception e) {
+            LOG.error("Failed to get media info", e);
+        }
+    }
+
+    @Nullable
+    public static MusicSpec extractMusicSpec(final MediaMetadata d) {
+        if (d == null) {
+            return null;
+        }
+
+        final MusicSpec musicSpec = new MusicSpec();
+
+        try {
+            if (d.containsKey(MediaMetadata.METADATA_KEY_ARTIST))
+                musicSpec.artist = d.getString(MediaMetadata.METADATA_KEY_ARTIST);
+            if (d.containsKey(MediaMetadata.METADATA_KEY_ALBUM))
+                musicSpec.album = d.getString(MediaMetadata.METADATA_KEY_ALBUM);
+            if (d.containsKey(MediaMetadata.METADATA_KEY_TITLE))
+                musicSpec.track = d.getString(MediaMetadata.METADATA_KEY_TITLE);
+            if (d.containsKey(MediaMetadata.METADATA_KEY_DURATION))
+                musicSpec.duration = (int) d.getLong(MediaMetadata.METADATA_KEY_DURATION) / 1000;
+            if (d.containsKey(MediaMetadata.METADATA_KEY_NUM_TRACKS))
+                musicSpec.trackCount = (int) d.getLong(MediaMetadata.METADATA_KEY_NUM_TRACKS);
+            if (d.containsKey(MediaMetadata.METADATA_KEY_TRACK_NUMBER))
+                musicSpec.trackNr = (int) d.getLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER);
+        } catch (final Exception e) {
+            LOG.error("Failed to extract music spec", e);
+        }
+
+        return musicSpec;
+    }
+
+    @Nullable
+    public static MusicStateSpec extractMusicStateSpec(final PlaybackState s) {
+        if (s == null) {
+            return null;
+        }
+
+        final MusicStateSpec stateSpec = new MusicStateSpec();
+
+        try {
+            stateSpec.position = (int) (s.getPosition() / 1000);
+            stateSpec.playRate = Math.round(100 * s.getPlaybackSpeed());
+            stateSpec.repeat = MusicStateSpec.STATE_UNKNOWN;
+            stateSpec.shuffle = MusicStateSpec.STATE_UNKNOWN;
+            switch (s.getState()) {
+                case PlaybackState.STATE_PLAYING:
+                    stateSpec.state = MusicStateSpec.STATE_PLAYING;
+                    break;
+                case PlaybackState.STATE_STOPPED:
+                    stateSpec.state = MusicStateSpec.STATE_STOPPED;
+                    break;
+                case PlaybackState.STATE_PAUSED:
+                    stateSpec.state = MusicStateSpec.STATE_PAUSED;
+                    break;
+                default:
+                    stateSpec.state = MusicStateSpec.STATE_UNKNOWN;
+                    break;
+            }
+        } catch (final Exception e) {
+            LOG.error("Failed to extract music state spec", e);
+        }
+
+        return stateSpec;
+    }
+
+    public int getPhoneVolume() {
+        return getPhoneVolume(context);
+    }
+
+    public static int getPhoneVolume(final Context context) {
+        final AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+
+        final int volumeLevel = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
+        final int volumeMax = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
+        return Math.round(100 * (volumeLevel / (float) volumeMax));
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/NotificationUtils.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/NotificationUtils.java
new file mode 100644
index 0000000..797ba75
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/NotificationUtils.java
@@ -0,0 +1,162 @@
+/*  Copyright (C) 2017-2024 Andreas Shimokawa, Carsten Pfeiffer, Daniele
+    Gobbetti, Jos茅 Rebelo, Lukas Veneziano, Maxim Baz
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.util;
+
+import android.content.Context;
+import android.content.pm.LauncherActivityInfo;
+import android.content.pm.LauncherApps;
+import android.content.pm.PackageManager;
+import android.graphics.drawable.Drawable;
+import android.os.UserHandle;
+import android.os.UserManager;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
+
+public class NotificationUtils {
+    private static final Logger LOG = LoggerFactory.getLogger(NotificationUtils.class);
+
+    @NonNull
+    public static String getPreferredTextFor(NotificationSpec notificationSpec, int lengthBody, int lengthSubject, Context context) {
+        switch (notificationSpec.type) {
+            case GENERIC_ALARM_CLOCK:
+                return StringUtils.getFirstOf(notificationSpec.title, notificationSpec.subject);
+            case GENERIC_SMS:
+            case GENERIC_EMAIL:
+                return formatText(notificationSpec.sender, notificationSpec.subject, notificationSpec.body, lengthBody, lengthSubject, context);
+            case GENERIC_NAVIGATION:
+                return StringUtils.getFirstOf(notificationSpec.title, notificationSpec.body);
+            case CONVERSATIONS:
+            case FACEBOOK_MESSENGER:
+            case GOOGLE_MESSENGER:
+            case GOOGLE_HANGOUTS:
+            case HIPCHAT:
+            case KAKAO_TALK:
+            case LINE:
+            case RIOT:
+            case SIGNAL:
+            case WIRE:
+            case SKYPE:
+            case SNAPCHAT:
+            case TELEGRAM:
+            case THREEMA:
+            case KONTALK:
+            case ANTOX:
+            case TWITTER:
+            case WHATSAPP:
+            case VIBER:
+            case WECHAT:
+                return StringUtils.ensureNotNull(notificationSpec.body);
+        }
+        return "";
+    }
+
+    @NonNull
+    public static String formatSender(String sender, Context context) {
+        if (sender == null || sender.length() == 0) {
+            return "";
+        }
+        return context.getString(R.string.StringUtils_sender, sender);
+    }
+
+
+    @NonNull
+    public static String formatText(String sender, String subject, String body, int lengthBody, int lengthSubject, Context context) {
+        String fBody = StringUtils.truncate(body, lengthBody);
+        String fSubject = StringUtils.truncate(subject, lengthSubject);
+        String fSender = formatSender(sender, context);
+
+        StringBuilder builder = StringUtils.join(" ", fBody, fSubject, fSender);
+        return builder.toString().trim();
+    }
+
+    public static String getPreferredTextFor(CallSpec callSpec) {
+        return StringUtils.getFirstOf(callSpec.name, callSpec.number);
+    }
+
+    @Nullable
+    public static Drawable getAppIcon(final Context context, final String packageName) {
+        try {
+            return context.getPackageManager().getApplicationIcon(packageName);
+        } catch (final PackageManager.NameNotFoundException ignored) {
+            LOG.warn("Failed to find icon for {}, attempting fallback", packageName);
+            final LauncherActivityInfo launcherActivityInfo = getLauncherActivityInfo(context, packageName);
+            if (launcherActivityInfo != null) {
+                return launcherActivityInfo.getIcon(0);
+            }
+
+            return null;
+        }
+    }
+
+    @Nullable
+    public static String getApplicationLabel(final Context context, final String packageName) {
+        final PackageManager pm = context.getPackageManager();
+        try {
+            return pm.getApplicationLabel(pm.getApplicationInfo(packageName, 0)).toString();
+        } catch (final PackageManager.NameNotFoundException ignored) {
+            LOG.warn("Failed to find application label for {}, attempting fallback", packageName);
+            final LauncherActivityInfo launcherActivityInfo = getLauncherActivityInfo(context, packageName);
+            if (launcherActivityInfo != null) {
+                return launcherActivityInfo.getLabel().toString();
+            }
+
+            return null;
+        }
+    }
+
+    /**
+     * Fallback method to get an app info - iterate through all the users and attempt to find the
+     * app. This includes work profiles.
+     */
+    @Nullable
+    private static LauncherActivityInfo getLauncherActivityInfo(final Context context, final String packageName) {
+        try {
+            final LauncherApps launcherAppsService = (LauncherApps) context.getSystemService(Context.LAUNCHER_APPS_SERVICE);
+            final UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
+            final List<UserHandle> userProfiles = userManager.getUserProfiles();
+            // Skip user 0 (current, main user)
+            for (int i = 1; i < userProfiles.size(); i++) {
+                final UserHandle userHandle = userProfiles.get(i);
+                final List<LauncherActivityInfo> activityList = launcherAppsService.getActivityList(packageName, userHandle);
+
+                if (!activityList.isEmpty()) {
+                    LOG.debug("Found {} launcher activity infos for {} in user {}", activityList.size(), packageName, userHandle);
+                    return activityList.get(0);
+                }
+            }
+
+            LOG.warn("Failed to find launcher activity info for {}", packageName);
+        } catch (final Exception e) {
+            LOG.error("Error during launcher activity info search", e);
+        }
+
+        return null;
+    }
+
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/PebbleUtils.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/PebbleUtils.java
new file mode 100644
index 0000000..32ba6e8
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/PebbleUtils.java
@@ -0,0 +1,187 @@
+/*  Copyright (C) 2016-2024 Andreas Shimokawa, Carsten Pfeiffer, Daniele
+    Gobbetti, Frank Slezak
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.util;
+
+import android.graphics.Color;
+import android.util.SparseArray;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.UUID;
+
+public class PebbleUtils {
+    private static final Logger LOG = LoggerFactory.getLogger(PebbleUtils.class);
+
+    public static String getPlatformName(String hwRev) {
+        final String DEFAULT_PLATFORM = "aplite";
+        if (hwRev == null || hwRev.isEmpty()) {
+            return DEFAULT_PLATFORM;
+        }
+
+        if (hwRev.startsWith("snowy")) {
+            return "basalt";
+        } else if (hwRev.startsWith("spalding")) {
+            return "chalk";
+        } else if (hwRev.startsWith("silk")) {
+            return "diorite";
+        } else if (hwRev.startsWith("robert")) {
+            return "emery";
+        }
+        return DEFAULT_PLATFORM;
+    }
+
+    public static String getModel(String hwRev) {
+        //TODO: get real data?
+        final String DEFAULT_MODEL = "pebble_black";
+        if (hwRev == null || hwRev.isEmpty()) {
+            return DEFAULT_MODEL;
+        }
+        if (hwRev.startsWith("snowy")) {
+            return "pebble_time_black";
+        } else if (hwRev.startsWith("spalding")) {
+            return "pebble_time_round_black_20mm";
+        } else if (hwRev.startsWith("silk")) {
+            return "pebble2_black";
+        } else if (hwRev.startsWith("robert")) {
+            return "pebble_time2_black";
+        }
+        return DEFAULT_MODEL;
+    }
+
+    public static int getFwMajor(String fwString) {
+        return fwString.charAt(1) - 48;
+    }
+
+    public static boolean hasHRM(String hwRev) {
+        String platformName = getPlatformName(hwRev);
+        return "diorite".equals(platformName) || "emery".equals(platformName);
+    }
+
+    public static boolean hasHealth(String hwRev) {
+        String platformName = getPlatformName(hwRev);
+        return !"aplite".equals(platformName);
+    }
+
+    /**
+     * Get the closest Pebble-compatible color from the associated Android Color Integer.
+     * @param color An Android Color Integer to convert
+     * @return A byte representing the closest Pebble color.
+     */
+    public static byte getPebbleColor(int color) {
+        // 85 here is determined by dividing 255 by 3, or reducing an 8-bit color to a 2-bit color. (2^3 = 8)
+
+        int colorRed = ((color >> 16) & 0xFF) / 85;
+        int colorGreen = ((color >> 8) & 0xFF) / 85;
+        int colorBlue = (color & 0xFF) / 85;
+
+        // Bit shifting, woo!
+        return (byte) ((0b11 << 6) | ((colorRed & 0b11) << 4) | ((colorGreen & 0b11) << 2) | (colorBlue & 0b11));
+    }
+
+    /**
+     * Get the closest Pebble-compatible color from the associated Hex string.
+     * @param colorHex A Hex-formatted string (#FFDD00) to convert.
+     * @return A byte representing the closest Pebble color.
+     */
+    public static byte getPebbleColor(String colorHex) {
+        return getPebbleColor(Color.parseColor(colorHex));
+    }
+
+
+    /**
+     * Returns the directory containing the .pbw cache.
+     * @throws IOException when the external files directory cannot be accessed
+     */
+    public static File getPbwCacheDir() throws IOException {
+        return new File(FileUtils.getExternalFilesDir(), "pbw-cache");
+    }
+
+    public static JSONObject getAppConfigurationKeys(UUID uuid) {
+        try {
+            File destDir = getPbwCacheDir();
+            File configurationFile = new File(destDir, uuid.toString() + ".json");
+            if (configurationFile.exists()) {
+                String jsonString = FileUtils.getStringFromFile(configurationFile);
+                JSONObject json = new JSONObject(jsonString);
+                return json.getJSONObject("appKeys");
+            }
+        } catch (IOException | JSONException e) {
+            LOG.warn("Unable to parse configuration JSON file", e);
+        }
+        return null;
+    }
+
+    public static String parseIncomingAppMessage(String msg, UUID uuid, int transactionId) {
+        JSONObject jsAppMessage = new JSONObject();
+
+        JSONObject knownKeys = PebbleUtils.getAppConfigurationKeys(uuid);
+        SparseArray<String> appKeysMap = new SparseArray<>();
+        String inKey, outKey;
+
+//      TODO: The fact that knownKeys is null for the passed UUID means that the
+//      watchapp was installed by some other app, hence we cannot communicate with it.
+//      The user could be warned somehow.
+        if (knownKeys == null || msg == null) {
+            msg = "[]";
+        } else {
+            //knownKeys contains "name"->"index", we need to reverse that
+            for (Iterator<String> key = knownKeys.keys(); key.hasNext(); ) {
+                inKey = key.next();
+                appKeysMap.put(knownKeys.optInt(inKey), inKey);
+            }
+        }
+
+        try {
+            JSONArray incoming = new JSONArray(msg);
+            JSONObject outgoing = new JSONObject();
+            for (int i = 0; i < incoming.length(); i++) {
+                JSONObject in = incoming.getJSONObject(i);
+                outKey = null;
+                Object outValue = null;
+                for (Iterator<String> key = in.keys(); key.hasNext(); ) {
+                    inKey = key.next();
+                    switch (inKey) {
+                        case "key":
+                            outKey = appKeysMap.get(in.optInt(inKey));
+                            break;
+                        case "value":
+                            outValue = in.get(inKey);
+                    }
+                }
+                if (outKey != null && outValue != null) {
+                    outgoing.put(outKey, outValue);
+                }
+            }
+            jsAppMessage.put("payload", outgoing);
+            JSONObject data = new JSONObject();
+            data.put("transactionId", transactionId);
+            jsAppMessage.put("data", data);
+
+        } catch (Exception e) {
+            LOG.warn("Unable to parse incoming app message", e);
+        }
+        return jsAppMessage.toString();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/PendingIntentUtils.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/PendingIntentUtils.java
new file mode 100644
index 0000000..f6540bf
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/PendingIntentUtils.java
@@ -0,0 +1,77 @@
+/*  Copyright (C) 2022-2024 Ganblejs
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.util;
+
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Build;
+
+public  class PendingIntentUtils {
+    
+    public static PendingIntent getBroadcast(Context context,
+                                             int requestCode,
+                                             Intent intent,
+                                             int flags,
+                                             boolean makeMutable) {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+          if (makeMutable) {
+            flags |= PendingIntent.FLAG_MUTABLE;
+          } else {
+            flags |= PendingIntent.FLAG_IMMUTABLE;
+          }
+            return PendingIntent.getBroadcast(context, requestCode, intent, flags);
+        }
+
+        return PendingIntent.getBroadcast(context, requestCode, intent, flags);
+    }
+    
+    public static PendingIntent getActivity(Context context,
+                                             int requestCode,
+                                             Intent intent,
+                                             int flags,
+                                             boolean makeMutable) {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+          if (makeMutable) {
+            flags |= PendingIntent.FLAG_MUTABLE;
+          } else {
+            flags |= PendingIntent.FLAG_IMMUTABLE;
+          }
+            return PendingIntent.getActivity(context, requestCode, intent, flags);
+        }
+
+        return PendingIntent.getActivity(context, requestCode, intent, flags);
+    }
+    
+    public static PendingIntent getService(Context context,
+                                             int requestCode,
+                                             Intent intent,
+                                             int flags,
+                                             boolean makeMutable) {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+          if (makeMutable) {
+            flags |= PendingIntent.FLAG_MUTABLE;
+          } else {
+            flags |= PendingIntent.FLAG_IMMUTABLE;
+          }
+            return PendingIntent.getService(context, requestCode, intent, flags);
+        }
+
+        return PendingIntent.getService(context, requestCode, intent, flags);
+    }
+    
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/PermissionsUtils.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/PermissionsUtils.java
new file mode 100644
index 0000000..468f8cb
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/PermissionsUtils.java
@@ -0,0 +1,328 @@
+/*  Copyright (C) 2024 Arjan Schrijver
+
+    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 <http://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.util;
+
+import android.Manifest;
+import android.app.Activity;
+import android.app.NotificationManager;
+import android.content.ActivityNotFoundException;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.widget.Toast;
+
+import androidx.annotation.RequiresApi;
+import androidx.core.app.ActivityCompat;
+import androidx.core.app.NotificationManagerCompat;
+import androidx.core.content.ContextCompat;
+
+import com.google.android.material.dialog.MaterialAlertDialogBuilder;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+import nodomain.freeyourgadget.gadgetbridge.BuildConfig;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.externalevents.NotificationListener;
+
+public class PermissionsUtils {
+    private static final Logger LOG = LoggerFactory.getLogger(PermissionsUtils.class);
+
+    public static final String CUSTOM_PERM_NOTIFICATION_LISTENER = "custom_perm_notifications_listener";
+    public static final String CUSTOM_PERM_NOTIFICATION_SERVICE = "custom_perm_notifications_service";
+    public static final String CUSTOM_PERM_DISPLAY_OVER = "custom_perm_display_over";
+
+    public static final List<String> specialPermissions = new ArrayList<String>() {{
+        add(CUSTOM_PERM_NOTIFICATION_LISTENER);
+        add(CUSTOM_PERM_NOTIFICATION_SERVICE);
+        add(CUSTOM_PERM_DISPLAY_OVER);
+        add(Manifest.permission.ACCESS_FINE_LOCATION);
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+            add(Manifest.permission.ACCESS_BACKGROUND_LOCATION);
+        }
+    }};
+
+    public static ArrayList<PermissionDetails> getRequiredPermissionsList(Activity activity) {
+        ArrayList<PermissionDetails> permissionsList = new ArrayList<>();
+        permissionsList.add(new PermissionDetails(
+                CUSTOM_PERM_NOTIFICATION_LISTENER,
+                activity.getString(R.string.menuitem_notifications),
+                activity.getString(R.string.permission_notifications_summary)));
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+            permissionsList.add(new PermissionDetails(
+                    CUSTOM_PERM_NOTIFICATION_SERVICE,
+                    activity.getString(R.string.permission_manage_dnd_title),
+                    activity.getString(R.string.permission_manage_dnd_summary)));
+            permissionsList.add(new PermissionDetails(
+                    CUSTOM_PERM_DISPLAY_OVER,
+                    activity.getString(R.string.permission_displayover_title),
+                    activity.getString(R.string.permission_displayover_summary)));
+        }
+        permissionsList.add(new PermissionDetails(
+                Manifest.permission.ACCESS_FINE_LOCATION,
+                activity.getString(R.string.permission_fine_location_title),
+                activity.getString(R.string.permission_fine_location_summary)));
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+            permissionsList.add(new PermissionDetails(
+                    Manifest.permission.ACCESS_BACKGROUND_LOCATION,
+                    activity.getString(R.string.permission_background_location_title),
+                    activity.getString(R.string.permission_background_location_summary)));
+        }
+        if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) {
+            permissionsList.add(new PermissionDetails(
+                    Manifest.permission.BLUETOOTH,
+                    activity.getString(R.string.permission_bluetooth_title),
+                    activity.getString(R.string.permission_bluetooth_summary)));
+            permissionsList.add(new PermissionDetails(
+                    Manifest.permission.BLUETOOTH_ADMIN,
+                    activity.getString(R.string.permission_bluetooth_admin_title),
+                    activity.getString(R.string.permission_bluetooth_admin_summary)));
+        }
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+            permissionsList.add(new PermissionDetails(
+                    Manifest.permission.BLUETOOTH_SCAN,
+                    activity.getString(R.string.permission_bluetooth_scan_title),
+                    activity.getString(R.string.permission_bluetooth_scan_summary)));
+            permissionsList.add(new PermissionDetails(
+                    Manifest.permission.BLUETOOTH_CONNECT,
+                    activity.getString(R.string.permission_bluetooth_connect_title),
+                    activity.getString(R.string.permission_bluetooth_connect_summary)));
+        }
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+            permissionsList.add(new PermissionDetails(
+                    Manifest.permission.POST_NOTIFICATIONS,
+                    activity.getString(R.string.permission_post_notification_title),
+                    activity.getString(R.string.permission_post_notification_summary)));
+        }
+        if (BuildConfig.INTERNET_ACCESS) {
+            permissionsList.add(new PermissionDetails(
+                    Manifest.permission.INTERNET,
+                    activity.getString(R.string.permission_internet_access_title),
+                    activity.getString(R.string.permission_internet_access_summary)));
+        }
+//        permissionsList.add(new PermissionDetails(  // NOTE: can't request this, it's only allowed for system apps
+//                Manifest.permission.MEDIA_CONTENT_CONTROL,
+//                "Media content control",
+//                "Read and control media playback"));
+        permissionsList.add(new PermissionDetails(
+                Manifest.permission.READ_CONTACTS,
+                activity.getString(R.string.permission_contacts_title),
+                activity.getString(R.string.permission_contacts_summary)));
+        permissionsList.add(new PermissionDetails(
+                Manifest.permission.READ_CALENDAR,
+                activity.getString(R.string.permission_calendar_title),
+                activity.getString(R.string.permission_calendar_summary)));
+        permissionsList.add(new PermissionDetails(
+                Manifest.permission.RECEIVE_SMS,
+                activity.getString(R.string.permission_receive_sms_title),
+                activity.getString(R.string.permission_receive_sms_summary)));
+        permissionsList.add(new PermissionDetails(
+                Manifest.permission.SEND_SMS,
+                activity.getString(R.string.permission_send_sms_title),
+                activity.getString(R.string.permission_send_sms_summary)));
+        permissionsList.add(new PermissionDetails(
+                Manifest.permission.READ_CALL_LOG,
+                activity.getString(R.string.permission_read_call_log_title),
+                activity.getString(R.string.permission_read_call_log_summary)));
+        permissionsList.add(new PermissionDetails(
+                Manifest.permission.READ_PHONE_STATE,
+                activity.getString(R.string.permission_read_phone_state_title),
+                activity.getString(R.string.permission_read_phone_state_summary)));
+        permissionsList.add(new PermissionDetails(
+                Manifest.permission.CALL_PHONE,
+                activity.getString(R.string.permission_call_phone_title),
+                activity.getString(R.string.permission_call_phone_summary)));
+        permissionsList.add(new PermissionDetails(
+                Manifest.permission.PROCESS_OUTGOING_CALLS,
+                activity.getString(R.string.permission_process_outgoing_calls_title),
+                activity.getString(R.string.permission_process_outgoing_calls_summary)));
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+            permissionsList.add(new PermissionDetails(
+                    Manifest.permission.ANSWER_PHONE_CALLS,
+                    activity.getString(R.string.permission_answer_phone_calls_title),
+                    activity.getString(R.string.permission_answer_phone_calls_summary)));
+        }
+        if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.S_V2) {
+            permissionsList.add(new PermissionDetails(
+                    Manifest.permission.READ_EXTERNAL_STORAGE,
+                    activity.getString(R.string.permission_external_storage_title),
+                    activity.getString(R.string.permission_external_storage_summary)));
+        }
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+            permissionsList.add(new PermissionDetails(
+                    Manifest.permission.QUERY_ALL_PACKAGES,
+                    activity.getString(R.string.permission_query_all_packages_title),
+                    activity.getString(R.string.permission_query_all_packages_summary)));
+        }
+        return permissionsList;
+    }
+
+    public static boolean checkPermission(Context context, String permission) {
+        if (permission.equals(CUSTOM_PERM_NOTIFICATION_LISTENER)) {
+            Set<String> set = NotificationManagerCompat.getEnabledListenerPackages(context);
+            return set.contains(context.getPackageName());
+        } else if (permission.equals(CUSTOM_PERM_NOTIFICATION_SERVICE) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+            return ((NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE)).isNotificationPolicyAccessGranted();
+        } else if (permission.equals(CUSTOM_PERM_DISPLAY_OVER) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+            return Settings.canDrawOverlays(context);
+        } else {
+            return ContextCompat.checkSelfPermission(context, permission) != PackageManager.PERMISSION_DENIED;
+        }
+    }
+
+    public static boolean checkAllPermissions(Activity activity) {
+        boolean result = true;
+        for (PermissionDetails permission : getRequiredPermissionsList(activity)) {
+            if (!checkPermission(activity, permission.getPermission())) {
+                result = false;
+            }
+        }
+        return result;
+    }
+
+    public static void requestPermission(Activity activity, String permission) {
+        if (permission.equals(CUSTOM_PERM_NOTIFICATION_LISTENER)) {
+            showNotifyListenerPermissionsDialog(activity);
+        } else if (permission.equals(CUSTOM_PERM_NOTIFICATION_SERVICE) && (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)) {
+            showNotifyPolicyPermissionsDialog(activity);
+        } else if (permission.equals(CUSTOM_PERM_DISPLAY_OVER)) {
+            showDisplayOverOthersPermissionsDialog(activity);
+        } else if (permission.equals(Manifest.permission.ACCESS_BACKGROUND_LOCATION) && (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R)) {
+            showBackgroundLocationPermissionsDialog(activity);
+        } else {
+            ActivityCompat.requestPermissions(activity, new String[]{permission}, 0);
+        }
+    }
+
+    public static class PermissionDetails {
+        private String permission;
+        private String title;
+        private String summary;
+
+        public PermissionDetails(String permission, String title, String summary) {
+            this.permission = permission;
+            this.title = title;
+            this.summary = summary;
+        }
+
+        public String getPermission() {
+            return permission;
+        }
+
+        public String getTitle() {
+            return title;
+        }
+
+        public String getSummary() {
+            return summary;
+        }
+    }
+
+    private static void showNotifyListenerPermissionsDialog(Activity activity) {
+        new MaterialAlertDialogBuilder(activity)
+                .setMessage(activity.getString(R.string.permission_notification_listener,
+                        activity.getString(R.string.app_name),
+                        activity.getString(R.string.ok)))
+                .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
+                    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP_MR1)
+                    public void onClick(DialogInterface dialog, int id) {
+                        try {
+                            Intent intent;
+                            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+                                intent = new Intent(Settings.ACTION_NOTIFICATION_LISTENER_DETAIL_SETTINGS);
+                                intent.putExtra(Settings.EXTRA_NOTIFICATION_LISTENER_COMPONENT_NAME, new ComponentName(BuildConfig.APPLICATION_ID, NotificationListener.class.getName()).flattenToString());
+                            } else {
+                                intent = new Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS);
+                            }
+                            String showArgs = BuildConfig.APPLICATION_ID + "/" + NotificationListener.class.getName();
+                            intent.putExtra(":settings:fragment_args_key", showArgs);
+                            Bundle bundle = new Bundle();
+                            bundle.putString(":settings:fragment_args_key", showArgs);
+                            intent.putExtra(":settings:show_fragment_args", bundle);
+                            activity.startActivity(intent);
+                        } catch (ActivityNotFoundException e) {
+                            GB.toast(activity, "'Notification Listener Settings' activity not found", Toast.LENGTH_LONG, GB.ERROR);
+                            LOG.error("'Notification Listener Settings' activity not found");
+                        }
+                    }
+                })
+                .show();
+    }
+
+    private static void showNotifyPolicyPermissionsDialog(Activity activity) {
+        new MaterialAlertDialogBuilder(activity)
+                .setMessage(activity.getString(R.string.permission_notification_policy_access,
+                        activity.getString(R.string.app_name),
+                        activity.getString(R.string.ok)))
+                .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
+                    @RequiresApi(api = Build.VERSION_CODES.M)
+                    public void onClick(DialogInterface dialog, int id) {
+                        try {
+                            activity.startActivity(new Intent(android.provider.Settings.ACTION_NOTIFICATION_POLICY_ACCESS_SETTINGS));
+                        } catch (ActivityNotFoundException e) {
+                            GB.toast(activity, "'Notification Policy' activity not found", Toast.LENGTH_LONG, GB.ERROR);
+                            LOG.error("'Notification Policy' activity not found");
+                        }
+                    }
+                })
+                .show();
+    }
+
+    private static void showDisplayOverOthersPermissionsDialog(Activity activity) {
+        new MaterialAlertDialogBuilder(activity)
+                .setMessage(activity.getString(R.string.permission_display_over_other_apps,
+                        activity.getString(R.string.app_name),
+                        activity.getString(R.string.ok)))
+                .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
+                    @RequiresApi(api = Build.VERSION_CODES.M)
+                    public void onClick(DialogInterface dialog, int id) {
+                        Intent enableIntent = new Intent(
+                                android.provider.Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
+                                Uri.parse("package:" + BuildConfig.APPLICATION_ID)
+                        );
+                        activity.startActivity(enableIntent);
+                    }
+                })
+                .setNegativeButton(R.string.dismiss, new DialogInterface.OnClickListener() {
+                    public void onClick(DialogInterface dialog, int id) {
+                    }
+                })
+                .show();
+    }
+
+    @RequiresApi(api = Build.VERSION_CODES.R)
+    private static void showBackgroundLocationPermissionsDialog(Activity activity) {
+        new MaterialAlertDialogBuilder(activity)
+                .setMessage(activity.getString(R.string.permission_location,
+                        activity.getString(R.string.app_name),
+                        activity.getPackageManager().getBackgroundPermissionOptionLabel()))
+                .setPositiveButton(R.string.ok, (dialog, id) -> {
+                    ActivityCompat.requestPermissions(activity, new String[]{Manifest.permission.ACCESS_BACKGROUND_LOCATION}, 0);
+                })
+                .show();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/Prefs.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/Prefs.java
new file mode 100644
index 0000000..c1344d0
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/Prefs.java
@@ -0,0 +1,255 @@
+/*  Copyright (C) 2016-2024 Carsten Pfeiffer, JohnnySun, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.util;
+
+import android.content.SharedPreferences;
+import android.util.Log;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.time.LocalDate;
+import java.time.LocalTime;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Set;
+
+/**
+ * Wraps SharedPreferences to avoid ClassCastExceptions and others.
+ */
+public class Prefs {
+    private static final String TAG = "Prefs";
+    // DO NOT use slf4j logger here, this would break its configuration via GBApplication
+//    private static final Logger LOG = LoggerFactory.getLogger(Prefs.class);
+
+    private final SharedPreferences preferences;
+
+    public Prefs(SharedPreferences preferences) {
+        this.preferences = preferences;
+    }
+
+    public String getString(String key, String defaultValue) {
+        String value = preferences.getString(key, defaultValue);
+        if (value == null || "".equals(value)) {
+            return defaultValue;
+        }
+        return value;
+    }
+
+    public Set<String> getStringSet(String key, Set<String> defaultValue) {
+        Set<String> value = preferences.getStringSet(key, defaultValue);
+        if (value == null || value.isEmpty()) {
+            return defaultValue;
+        }
+        return value;
+    }
+
+    /**
+     * Returns the preference saved under the given key as an integer value.
+     * Note that it is irrelevant whether the preference value was actually
+     * saved as an integer value or a string value.
+     * @param key the preference key
+     * @param defaultValue the default value to return if the preference value is unset
+     * @return the saved preference value or the given defaultValue
+     */
+    public int getInt(String key, int defaultValue) {
+        try {
+            return preferences.getInt(key, defaultValue);
+        } catch (Exception ex) {
+            try {
+                String value = preferences.getString(key, String.valueOf(defaultValue));
+                if ("".equals(value)) {
+                    return defaultValue;
+                }
+                return Integer.parseInt(value);
+            } catch (Exception ex2) {
+                logReadError(key, ex);
+                return defaultValue;
+            }
+        }
+    }
+
+    /**
+     * Returns the preference saved under the given key as a long value.
+     * Note that it is irrelevant whether the preference value was actually
+     * saved as a long value or a string value.
+     * @param key the preference key
+     * @param defaultValue the default value to return if the preference value is unset
+     * @return the saved preference value or the given defaultValue
+     */
+    public long getLong(String key, long defaultValue) {
+        try {
+            return preferences.getLong(key, defaultValue);
+        } catch (Exception ex) {
+            try {
+                String value = preferences.getString(key, String.valueOf(defaultValue));
+                if ("".equals(value)) {
+                    return defaultValue;
+                }
+                return Long.parseLong(value);
+            } catch (Exception ex2) {
+                logReadError(key, ex);
+                return defaultValue;
+            }
+        }
+    }
+
+    /**
+     * Returns the preference saved under the given key as a float value.
+     * Note that it is irrelevant whether the preference value was actually
+     * saved as a float value or a string value.
+     * @param key the preference key
+     * @param defaultValue the default value to return if the preference value is unset
+     * @return the saved preference value or the given defaultValue
+     */
+    public float getFloat(String key, float defaultValue) {
+        try {
+            return preferences.getFloat(key, defaultValue);
+        } catch (Exception ex) {
+            try {
+                String value = preferences.getString(key, String.valueOf(defaultValue));
+                if ("".equals(value)) {
+                    return defaultValue;
+                }
+                return Float.parseFloat(value);
+            } catch (Exception ex2) {
+                logReadError(key, ex);
+                return defaultValue;
+            }
+        }
+    }
+
+    /**
+     * Returns the preference saved under the given key as a boolean value.
+     * Note that it is irrelevant whether the preference value was actually
+     * saved as a boolean value or a string value.
+     * @param key the preference key
+     * @param defaultValue the default value to return if the preference value is unset
+     * @return the saved preference value or the given defaultValue
+     */
+    public boolean getBoolean(String key, boolean defaultValue) {
+        try {
+            return preferences.getBoolean(key, defaultValue);
+        } catch (Exception ex) {
+            try {
+                String value = preferences.getString(key, String.valueOf(defaultValue));
+                if ("".equals(value)) {
+                    return defaultValue;
+                }
+                return Boolean.parseBoolean(value);
+            } catch (Exception ex2) {
+                logReadError(key, ex);
+                return defaultValue;
+            }
+        }
+    }
+
+    /**
+     * Returns the preference saved under the given key as a list of strings.
+     * The preference is assumed to be a string, with each value separated by a comma.
+     * @param key the preference key
+     * @param defaultValue the default value to return if the preference value is unset
+     * @param separator the separator to use to split the string
+     * @return the saved preference value or the given defaultValue
+     */
+    public List<String> getList(final String key, final List<String> defaultValue, final String separatorRegex) {
+        final String stringValue = preferences.getString(key, null);
+        if (stringValue == null) {
+            return defaultValue;
+        }
+        return Arrays.asList(stringValue.split(separatorRegex));
+    }
+
+    public List<String> getList(final String key, final List<String> defaultValue) {
+        return getList(key, defaultValue, ",");
+    }
+
+    @Deprecated  // use getLocalTime
+    public Date getTimePreference(final String key, final String defaultValue) {
+        final String time = getString(key, defaultValue);
+
+        final DateFormat df = new SimpleDateFormat("HH:mm", Locale.ROOT);
+        try {
+            return df.parse(time);
+        } catch (final Exception e) {
+            Log.e(TAG, "Error reading datetime preference value: " + key + "; returning default current time", e); // log the first exception
+        }
+
+        return new Date();
+    }
+
+    public LocalTime getLocalTime(final String key, final String defaultValue) {
+        final String time = getString(key, defaultValue);
+
+        final DateFormat df = new SimpleDateFormat("HH:mm", Locale.ROOT);
+        try {
+            final Date parse = df.parse(time);
+            final Calendar calendar = GregorianCalendar.getInstance();
+            calendar.setTime(parse);
+
+            return LocalTime.of(calendar.get(Calendar.HOUR_OF_DAY), calendar.get(Calendar.MINUTE), 0);
+        } catch (final Exception e) {
+            Log.e(TAG, "Error reading localtime preference value: " + key + "; returning default current time", e); // log the first exception
+        }
+
+        return LocalTime.now();
+    }
+
+    public LocalDate getLocalDate(final String key, final String defaultValue) {
+        final String time = getString(key, defaultValue);
+
+        try {
+            return LocalDate.parse(time);
+        } catch (final Exception e) {
+            Log.e(TAG, "Error reading localdate preference value: " + key + "; returning default current day", e); // log the first exception
+        }
+
+        return LocalDate.now();
+    }
+
+    private void logReadError(String key, Exception ex) {
+        Log.e(TAG, "Error reading preference value: " + key + "; returning default value", ex); // log the first exception
+    }
+
+    public boolean contains(final String key) {
+        return preferences.contains(key);
+    }
+
+    /**
+     * Access to the underlying SharedPreferences, typically only used for editing values.
+     * @return the underlying SharedPreferences object.
+     */
+    public SharedPreferences getPreferences() {
+        return preferences;
+    }
+
+    /**
+     * Ugly workaround for Set<String> preferences not consistently applying.
+     * @param editor
+     * @param preference
+     * @param value
+     */
+    public static void putStringSet(SharedPreferences.Editor editor, String preference, HashSet<String> value) {
+        editor.putStringSet(preference, null);
+        editor.commit();
+        editor.putStringSet(preference, new HashSet<>(value));
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/RangeMap.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/RangeMap.java
new file mode 100644
index 0000000..f34e812
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/RangeMap.java
@@ -0,0 +1,87 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.util;
+
+import android.util.Pair;
+
+import androidx.annotation.Nullable;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+/**
+ * A map of bounds for ranges. Returns the value closest to the key, in upper or lower bound mode.
+ */
+public class RangeMap<K extends Comparable<K>, V> {
+    private final List<Pair<K, V>> list = new ArrayList<>();
+    private boolean isSorted = false;
+    private final Comparator<K> comparator;
+
+    public RangeMap() {
+        this(Mode.LOWER_BOUND);
+    }
+
+    public RangeMap(final Mode mode) {
+        switch (mode) {
+            case LOWER_BOUND:
+                comparator = (k1, k2) -> k1.compareTo(k2);
+                break;
+            case UPPER_BOUND:
+                comparator = (k1, k2) -> k2.compareTo(k1);
+                break;
+            default:
+                throw new IllegalArgumentException("Unknown mode " + mode);
+        }
+    }
+
+    public void put(final K key, final V value) {
+        list.add(Pair.create(key, value));
+        isSorted = false;
+    }
+
+    @Nullable
+    public V get(final K key) {
+        if (!isSorted) {
+            Collections.sort(list, (a, b) -> comparator.compare(a.first, b.first));
+            isSorted = true;
+        }
+
+        for (int i = list.size() - 1; i >= 0; i--) {
+            if (comparator.compare(key, list.get(i).first) >= 0) {
+                return list.get(i).second;
+            }
+        }
+
+        return null;
+    }
+
+    public boolean isEmpty() {
+        return list.isEmpty();
+    }
+
+    public int size() {
+        return list.size();
+    }
+
+    public enum Mode {
+        LOWER_BOUND,
+        UPPER_BOUND,
+        ;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/RtlUtils.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/RtlUtils.java
new file mode 100644
index 0000000..1d94bca
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/RtlUtils.java
@@ -0,0 +1,612 @@
+/*  Copyright (C) 2018-2024 Andreas Shimokawa, Carsten Pfeiffer, Roi Greenberg
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.util;
+
+import android.text.TextUtils;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+
+public class RtlUtils {
+
+    /**
+     * Checks the status of right-to-left option
+     * @return true if right-to-left option is On, and false, if Off or not exist
+     */
+    public static boolean rtlSupport()
+    {
+        return GBApplication.getPrefs().getBoolean(GBPrefs.RTL_SUPPORT, false);
+    }
+
+    public enum characterType{
+        ltr,
+        rtl,
+        rtl_arabic,
+        punctuation,
+        lineEnd,
+        space,
+    }
+
+    public static characterType getCharacterType(Character c){
+        characterType type;
+        switch (Character.getDirectionality(c)) {
+            case Character.DIRECTIONALITY_RIGHT_TO_LEFT:
+                type = characterType.rtl;
+                break;
+            case Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC:
+                type = characterType.rtl_arabic;
+                break;
+            case Character.DIRECTIONALITY_EUROPEAN_NUMBER_SEPARATOR:
+            case Character.DIRECTIONALITY_EUROPEAN_NUMBER_TERMINATOR:
+            case Character.DIRECTIONALITY_COMMON_NUMBER_SEPARATOR:
+            case Character.DIRECTIONALITY_OTHER_NEUTRALS:
+                type = characterType.punctuation;
+                break;
+            case Character.DIRECTIONALITY_BOUNDARY_NEUTRAL:
+            case Character.DIRECTIONALITY_PARAGRAPH_SEPARATOR:
+                type = characterType.lineEnd;
+                break;
+            case Character.DIRECTIONALITY_WHITESPACE:
+                type = characterType.space;
+                break;
+            default:
+                type = characterType.ltr;
+        }
+
+        return type;
+    }
+
+    /**
+     * Checks the status of right-to-left option
+     * @return true if right-to-left option is On, and false, if Off or not exist
+     */
+    public static boolean contextualSupport()
+    {
+        return GBApplication.getPrefs().getBoolean(GBPrefs.RTL_CONTEXTUAL_ARABIC, false);
+    }
+
+    //map with brackets chars to change there direction
+    private static Map<Character, Character> directionSignsMap = new HashMap<Character, Character>(){
+        {
+            put('(', ')'); put(')', '('); put('[', ']'); put(']', '['); put('{','}'); put('}','{');
+
+
+        }
+    };
+
+    /**
+     * @return true if the char is in the rtl range, otherwise false
+     */
+    public static boolean isHebrew(Character c){
+
+        return getCharacterType(c) == characterType.rtl;
+    }
+
+    /**
+     * @return true if the char is in the rtl range, otherwise false
+     */
+    public static boolean isArabic(Character c){
+
+        return getCharacterType(c) == characterType.rtl_arabic;
+    }
+
+    /**
+     * @return true if the char is in the rtl range, otherwise false
+     */
+    public static boolean isLtr(Character c){
+
+        return getCharacterType(c) == characterType.ltr;
+    }
+
+    /**
+     * @return true if the char is in the rtl range, otherwise false
+     */
+    public static boolean isRtl(Character c){
+
+        return (getCharacterType(c) == characterType.rtl) || (getCharacterType(c) == characterType.rtl_arabic);
+    }
+
+    /**
+     * @return true if the char is in the punctuations range, otherwise false
+     */
+    public static boolean isPunctuations(Character c){
+
+        return getCharacterType(c) == characterType.punctuation;
+    }
+
+
+    /**
+     * @return true if the char is in the end of word list, otherwise false
+     */
+    public static boolean isSpaceSign(Character c){
+
+        return getCharacterType(c) == characterType.space;
+    }
+
+    /**
+     * @return true if the char is in the end of word list, otherwise false
+     */
+    public static boolean isEndLineSign(Character c){
+
+        return getCharacterType(c) == characterType.lineEnd;
+    }
+
+    //map from Arabian characters to their contextual form in the beginning of the word
+    private static Map<Character, Character> contextualArabicIsolated = new HashMap<Character, Character>(){
+        {
+            put('丕', '\uFE8D');
+            put('亘', '\uFE8F');
+            put('鬲', '\uFE95');
+            put('孬', '\uFE99');
+            put('噩', '\uFE9D');
+            put('丨', '\uFEA1');
+            put('禺', '\uFEA5');
+            put('丿', '\uFEA9');
+            put('匕', '\uFEAB');
+            put('乇', '\uFEAD');
+            put('夭', '\uFEAF');
+            put('爻', '\uFEB1');
+            put('卮', '\uFEB5');
+            put('氐', '\uFEB9');
+            put('囟', '\uFEBD');
+            put('胤', '\uFEC1');
+            put('馗', '\uFEC5');
+            put('毓', '\uFEC9');
+            put('睾', '\uFECD');
+            put('賮', '\uFED1');
+            put('賯', '\uFED5');
+            put('賰', '\uFED9');
+            put('賱', '\uFEDD');
+            put('賲', '\uFEE1');
+            put('賳', '\uFEE5');
+            put('賴', '\uFEE9');
+            put('賵', '\uFEED');
+            put('賷', '\uFEF1');
+            put('丌', '\uFE81');
+            put('丞', '\uFE93');
+            put('賶', '\uFEEF');
+            put('卅', '\uFE89');
+            put('廿', '\uFE87');
+            put('兀', '\uFE83');
+            put('亍', '\uFE80');
+            put('丐', '\uFE85');
+            put((char)('賱' + '丌'), '\uFEF5');
+            put((char)('賱' + '兀'), '\uFEF7');
+            put((char)('賱' + '廿'), '\uFEF9');
+            put((char)('賱' + '丕'), '\uFEFB');
+            //Farsi
+            put('诏', '\uFB92');
+            put('讴', '\uFB8E');
+            put('趩', '\uFB7A');
+            put('倬', '\uFB56');
+            put('跇', '\uFB8A');
+            put('蹖', '\uFBFC');
+
+        }
+    };
+
+    //map from Arabian characters to their contextual form in the beginning of the word
+    private static Map<Character, Character> contextualArabicBeginning = new HashMap<Character, Character>(){
+        {
+            put('亘', '\uFE91');
+            put('鬲', '\uFE97');
+            put('孬', '\uFE9B');
+            put('噩', '\uFE9F');
+            put('丨', '\uFEA3');
+            put('禺', '\uFEA7');
+            put('爻', '\uFEB3');
+            put('卮', '\uFEB7');
+            put('氐', '\uFEBB');
+            put('囟', '\uFEBF');
+            put('胤', '\uFEC3');
+            put('馗', '\uFEC7');
+            put('毓', '\uFECB');
+            put('睾', '\uFECF');
+            put('賮', '\uFED3');
+            put('賯', '\uFED7');
+            put('賰', '\uFEDB');
+            put('賱', '\uFEDF');
+            put('賲', '\uFEE3');
+            put('賳', '\uFEE7');
+            put('賴', '\uFEEB');
+            put('賷', '\uFEF3');
+            put('卅', '\uFE8B');
+            //Farsi
+            put('诏', '\uFB94');
+            put('讴', '\uFB90');
+            put('趩', '\uFB7C');
+            put('倬', '\uFB58');
+            put('蹖', '\uFBFE');
+        }
+    };
+
+    //map from Arabian characters to their contextual form in the middle of the word
+    private static Map<Character, Character> contextualArabicMiddle = new HashMap<Character, Character>(){
+        {
+            put('亘', '\uFE92');
+            put('鬲', '\uFE98');
+            put('孬', '\uFE9C');
+            put('噩', '\uFEA0');
+            put('丨', '\uFEA4');
+            put('禺', '\uFEA8');
+            put('爻', '\uFEB4');
+            put('卮', '\uFEB8');
+            put('氐', '\uFEBC');
+            put('囟', '\uFEC0');
+            put('胤', '\uFEC4');
+            put('馗', '\uFEC8');
+            put('毓', '\uFECC');
+            put('睾', '\uFED0');
+            put('賮', '\uFED4');
+            put('賯', '\uFED8');
+            put('賰', '\uFEDC');
+            put('賱', '\uFEE0');
+            put('賲', '\uFEE4');
+            put('賳', '\uFEE8');
+            put('賴', '\uFEEC');
+            put('賷', '\uFEF4');
+            put('卅', '\uFE8C');
+            //Farsi
+            put('诏', '\uFB95');
+            put('讴', '\uFB91');
+            put('趩', '\uFB7D');
+            put('倬', '\uFB59');
+            put('蹖', '\uFBFF');
+        }
+    };
+
+    //map from Arabian characters to their contextual form in the end of the word
+    private static Map<Character, Character> contextualArabicEnd = new HashMap<Character, Character>(){
+        {
+            put('丕', '\uFE8E');
+            put('亘', '\uFE90');
+            put('鬲', '\uFE96');
+            put('孬', '\uFE9A');
+            put('噩', '\uFE9E');
+            put('丨', '\uFEA2');
+            put('禺', '\uFEA6');
+            put('丿', '\uFEAA');
+            put('匕', '\uFEAC');
+            put('乇', '\uFEAE');
+            put('夭', '\uFEB0');
+            put('爻', '\uFEB2');
+            put('卮', '\uFEB6');
+            put('氐', '\uFEBA');
+            put('囟', '\uFEBE');
+            put('胤', '\uFEC2');
+            put('馗', '\uFEC6');
+            put('毓', '\uFECA');
+            put('睾', '\uFECE');
+            put('賮', '\uFED2');
+            put('賯', '\uFED6');
+            put('賰', '\uFEDA');
+            put('賱', '\uFEDE');
+            put('賲', '\uFEE2');
+            put('賳', '\uFEE6');
+            put('賴', '\uFEEA');
+            put('賵', '\uFEEE');
+            put('賷', '\uFEF2');
+            put('丌', '\uFE82');
+            put('丞', '\uFE94');
+            put('賶', '\uFEF0');
+            put('卅', '\uFE8A');
+            put('廿', '\uFE88');
+            put('兀', '\uFE84');
+            put('丐', '\uFE86');
+            put((char)('賱' + '丌'), '\uFEF6');
+            put((char)('賱' + '兀'), '\uFEF8');
+            put((char)('賱' + '廿'), '\uFEFA');
+            put((char)('賱' + '丕'), '\uFEFC');
+            //Farsi
+            put('诏', '\uFB93');
+            put('讴', '\uFB8F');
+            put('趩', '\uFB7B');
+            put('倬', '\uFB57');
+            put('跇', '\uFB8B');
+            put('蹖', '\uFBFD');
+        }
+    };
+    public enum contextualState{
+        isolate,
+        begin,
+        middle,
+        end
+    }
+
+    public static boolean exceptionAfterLam(Character c){
+        switch (c){
+            case '\u0622':
+            case '\u0623':
+            case '\u0625':
+            case '\u0627':
+                return true;
+            default:
+                return false;
+
+        }
+    }
+
+    /**
+     * This function return the contextual form of Arabic characters in a given state
+     * @param c - the character to convert
+     * @param state - the character state: beginning, middle, end or isolated
+     * @return the contextual character
+     */
+    public static Character getContextualSymbol(Character c, contextualState state) {
+        Character newChar;
+        switch (state){
+            case begin:
+                newChar = contextualArabicBeginning.get(c);
+                break;
+            case middle:
+                newChar = contextualArabicMiddle.get(c);
+                break;
+            case end:
+                newChar = contextualArabicEnd.get(c);
+                break;
+            case isolate:
+            default:
+                newChar  = contextualArabicIsolated.get(c);;
+        }
+        if (newChar != null){
+            return newChar;
+        } else{
+            return c;
+        }
+    }
+
+    /**
+     * This function return the contextual state of a given character, depend of the previous
+     * character state and the next charachter.
+     * @param prevState - previous character state or isolated if none
+     * @param curChar - the current character
+     * @param nextChar - the next character or null if none
+     * @return the current character contextual state
+     */
+    public static contextualState getCharContextualState(contextualState prevState, Character curChar, Character nextChar) {
+        contextualState curState;
+        if ((prevState == contextualState.isolate || prevState == contextualState.end) &&
+                contextualArabicBeginning.containsKey(curChar) &&
+                contextualArabicEnd.containsKey(nextChar)){
+
+            curState = contextualState.begin;
+
+        } else if ((prevState == contextualState.begin || prevState == contextualState.middle) &&
+                contextualArabicEnd.containsKey(curChar)){
+
+            if (contextualArabicMiddle.containsKey(curChar) && contextualArabicEnd.containsKey(nextChar)){
+                curState = contextualState.middle;
+            }else{
+                curState = contextualState.end;
+            }
+        }else{
+            curState = contextualState.isolate;
+        }
+        return curState;
+    }
+
+    /**
+     * this function convert given string to it's contextual form
+     * @param s - the given string
+     * @return the contextual form of the string
+     */
+    public static String convertToContextual(String s){
+        if (s == null || s.isEmpty() || s.length() == 1){
+            return s;
+        }
+
+        int length = s.length();
+        StringBuilder newWord = new StringBuilder(length);
+
+        Character curChar, nextChar = s.charAt(0);
+        contextualState prevState = contextualState.isolate;
+        contextualState curState = contextualState.isolate;
+
+        for (int i = 0; i < length - 1; i++){
+            curChar = nextChar;
+            nextChar = s.charAt(i + 1);
+
+            if (curChar == '賱' && exceptionAfterLam(nextChar)){
+                i++;
+                curChar = (char)(nextChar + curChar);
+                if (i < length - 1) {
+                    nextChar = s.charAt(i + 1);
+                }else{
+                    nextChar = curChar;
+                    prevState = curState;
+                    break;
+                }
+
+            }
+
+            curState = getCharContextualState(prevState, curChar, nextChar);
+            newWord.append(getContextualSymbol(curChar, curState));
+            prevState = curState;
+
+
+        }
+        curState = getCharContextualState(prevState, nextChar, null);
+        newWord.append(getContextualSymbol(nextChar, curState));
+
+        return newWord.toString();
+    }
+
+
+    /**
+     * The function get a string and reverse it.
+     * in case of end-of-word sign, it will leave it at the end.
+     * in case of sign with direction like brackets, it will change the direction.
+     * @param s - the string to reverse
+     * @return reversed string
+     */
+    public static String reverse(String s) {
+        int j = s.length();
+        int isEndLine = 0;
+        char[] newWord = new char[j];
+
+        if (j == 0) {
+            return s;
+        }
+
+        for (int i = 0; i < s.length() - isEndLine; i++) {
+            if (directionSignsMap.containsKey(s.charAt(i))) {
+                newWord[--j] = directionSignsMap.get(s.charAt(i));
+            } else {
+                newWord[--j] = s.charAt(i);
+            }
+        }
+
+        return new String(newWord);
+    }
+
+    public static String fixWhitespace(String s){
+        int length = s.length();
+
+        if (length > 0 && isSpaceSign(s.charAt(length - 1))){
+            return s.charAt(length - 1) + s.substring(0, length - 1);
+        } else {
+            return s;
+        }
+    }
+
+    /**
+     * The function get a string and fix the rtl words.
+     * since simple reverse puts the beginning of the text at the end, the text should have been from bottom to top.
+     * To avoid that, we save the text in lines (line max size can be change in the settings)
+     * @param oldString - the string to fix.
+     * @return a fix string.
+     */
+    public static String fixRtl(String oldString) {
+        if (oldString == null || oldString.isEmpty()){
+            return oldString;
+        }
+        debug("before: |" + org.apache.commons.lang3.StringEscapeUtils.escapeJava(oldString) + "|");
+
+        int length = oldString.length();
+        String newString = "";
+        List<String> lines = new ArrayList<>();
+        char[] newWord = new char[length];
+        int line_max_size = GBApplication.getPrefs().getInt("rtl_max_line_length", 18);
+
+        int startPos = 0;
+        int endPos = 0;
+        characterType CurRtlType = isRtl(oldString.charAt(0))? characterType.rtl : characterType.ltr;
+        characterType PhraseRtlType = CurRtlType;
+
+        Character c;
+//        String word = "", phrase = "", line = "";
+        StringBuilder word = new StringBuilder();
+        StringBuilder phrase = new StringBuilder();
+        StringBuilder line = new StringBuilder();
+        String phraseString = "";
+        boolean addCharToWord = false;
+        for (int i = 0; i < length; i++) {
+            c = oldString.charAt(i);
+            addCharToWord = false;
+            debug("char: " + c + " :" + Character.getDirectionality(c));
+//            debug( "hex : " + (int)c);
+
+            if (isLtr(c)){
+                CurRtlType = characterType.ltr;
+            } else if (isRtl(c)) {
+                CurRtlType = characterType.rtl;
+            }
+
+            if ((CurRtlType == PhraseRtlType) && !(isSpaceSign(c) || isEndLineSign(c))){
+                debug("add: " + c + " to: " + word);
+                word.append(c);
+                addCharToWord = true;
+                if (i < length - 1) {
+                    continue;
+                }
+            }
+
+
+
+            do {
+                if ((line.length() + phrase.length() + word.length() < line_max_size) ||
+                        (line.length() == 0 && word.length() >= line_max_size)) {
+                    if (isSpaceSign(c)) {
+                        word.append(c);
+                        addCharToWord = true;
+                    }
+
+                    phrase.append(word);
+                    word.setLength(0);
+
+                    if (isSpaceSign(c)) {
+                        break;
+                    }
+                }
+
+
+                phraseString = phrase.toString();
+                debug("phrase:   |" + phraseString + "|");
+                if (PhraseRtlType == characterType.rtl) {
+                    if (contextualSupport()) {
+                        phraseString = convertToContextual(phraseString);
+                    }
+                    phraseString = reverse(phraseString);
+                }
+
+                line.insert(0, fixWhitespace(phraseString));
+                debug("line now: |" + line + "|");
+                phrase.setLength(0);
+
+                if (word.length() > 0){
+                    line.append('\n');
+                } else if (isEndLineSign(c)) {
+                    line.append(c);
+                } else if (!addCharToWord) {
+                    word.append(c);
+                    if (i == length - 1){
+                        addCharToWord = true;
+                        continue;
+                    }
+                    PhraseRtlType = PhraseRtlType == characterType.rtl ? characterType.ltr : characterType.rtl;
+                    break;
+                }
+
+                lines.add(line.toString());
+                debug("line: |" + line + "|");
+                line.setLength(0);
+
+                if (word.length() == 0){
+                    break;
+                }
+
+            } while (true);
+
+        }
+
+        lines.add(line.toString());
+
+        newString = TextUtils.join("", lines);
+
+        debug("after : |" + org.apache.commons.lang3.StringEscapeUtils.escapeJava(newString) + "|");
+
+        return newString;
+    }
+
+    private static void debug(String s) {
+//        Log.d("ROIGR", s);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/SilentMode.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/SilentMode.java
new file mode 100644
index 0000000..120493e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/SilentMode.java
@@ -0,0 +1,99 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.util;
+
+import static nodomain.freeyourgadget.gadgetbridge.GBApplication.getContext;
+
+import android.content.Context;
+import android.media.AudioManager;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Locale;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+
+public class SilentMode {
+    private static final Logger LOG = LoggerFactory.getLogger(SilentMode.class);
+
+    public enum RingerMode {
+        NORMAL(AudioManager.RINGER_MODE_NORMAL),
+        VIBRATE(AudioManager.RINGER_MODE_VIBRATE),
+        SILENT(AudioManager.RINGER_MODE_SILENT),
+        UNKNOWN(-1);
+
+        private final int code;
+
+        RingerMode(final int code) {
+            this.code = code;
+        }
+
+        public int getCode() {
+            return code;
+        }
+
+        public static RingerMode fromCode(final int code) {
+            for (final RingerMode ringerMode : values()) {
+                if (ringerMode.code == code) {
+                    return ringerMode;
+                }
+            }
+
+            return RingerMode.UNKNOWN;
+        }
+    }
+
+    public static void setPhoneSilentMode(final String deviceAddress, final boolean enabled) {
+        final RingerMode[] phoneSilentMode = getPhoneSilentMode(deviceAddress);
+        final RingerMode ringerMode = phoneSilentMode[enabled ? 1 : 0];
+
+        LOG.debug("Set phone silent mode = {} ({})", enabled, ringerMode);
+
+        setRingerMode(ringerMode);
+    }
+
+    public static boolean isPhoneInSilenceMode(final String deviceAddress) {
+        final AudioManager audioManager = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
+        final RingerMode currentRingerMode = RingerMode.fromCode(audioManager.getRingerMode());
+        final RingerMode[] phoneSilentMode = getPhoneSilentMode(deviceAddress);
+
+        // Check if current mode "is more silent than" desired ringer mode
+        return currentRingerMode.getCode() < phoneSilentMode[0].getCode();
+    }
+
+    public static RingerMode[] getPhoneSilentMode(final String deviceAddress) {
+        final Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(deviceAddress));
+        final String phoneSilentModePref = prefs.getString(DeviceSettingsPreferenceConst.PREF_PHONE_SILENT_MODE, "normal_silent").toUpperCase(Locale.ROOT);
+        final String[] prefSplit = phoneSilentModePref.split("_");
+        return new RingerMode[]{
+                RingerMode.valueOf(prefSplit[0]),
+                RingerMode.valueOf(prefSplit[1])
+        };
+    }
+
+    public static void setRingerMode(final RingerMode mode) {
+        if (mode == RingerMode.UNKNOWN) {
+            LOG.warn("Unable to set unknown ringer mode");
+            return;
+        }
+
+        final AudioManager audioManager = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
+        audioManager.setRingerMode(mode.getCode());
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/StringUtils.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/StringUtils.java
new file mode 100644
index 0000000..f25b9ec
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/StringUtils.java
@@ -0,0 +1,226 @@
+/*  Copyright (C) 2017-2024 Andreas Shimokawa, Arjan Schrijver, Carsten
+    Pfeiffer, Daniel Dakhno, Daniele Gobbetti, Joa虄o Paulo Barraca, Jos茅 Rebelo,
+    Nephiel, Roi Greenberg, Taavi Eom盲e, Zhong Jianxin
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.util;
+
+import java.io.ByteArrayOutputStream;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.StandardCharsets;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import org.apache.commons.lang3.ArrayUtils;
+
+public class StringUtils {
+
+
+
+    @NonNull
+    public static String truncate(String s, int maxLength){
+        if (s == null) {
+            return "";
+        }
+
+        int length = Math.min(s.length(), maxLength);
+        if(length < 0) {
+            return "";
+        }
+
+        return s.substring(0, length);
+    }
+
+    /**
+     * Truncate a string to a certain maximum number of bytes, assuming UTF-8 encoding.
+     * Does not include the null terminator. Due to multi-byte characters, it's possible
+     * that the resulting array is smaller than len, but never larger.
+     */
+    public static byte[] truncateToBytes(final String s, final int len) {
+        if (StringUtils.isNullOrEmpty(s)) {
+            return new byte[]{};
+        }
+
+        int i = 0;
+        while (++i < s.length()) {
+            final String subString = s.substring(0, i + 1);
+            if (subString.getBytes(StandardCharsets.UTF_8).length > len) {
+                break;
+            }
+        }
+
+        return s.substring(0, i).getBytes(StandardCharsets.UTF_8);
+    }
+
+    public static int utf8ByteLength(String string, int length) {
+        if (string == null) {
+            return 0;
+        }
+        ByteBuffer outBuf = ByteBuffer.allocate(length);
+        CharBuffer inBuf = CharBuffer.wrap(string.toCharArray());
+        StandardCharsets.UTF_8.newEncoder().encode(inBuf, outBuf, true);
+        return outBuf.position();
+    }
+
+    public static String pad(String s, int length){
+        return pad(s, length, ' ');
+    }
+
+    public static String pad(String s, int length, char padChar) {
+        StringBuilder sBuilder = new StringBuilder(s);
+        while (sBuilder.length() < length) {
+            sBuilder.append(padChar);
+        }
+        s = sBuilder.toString();
+        return s;
+    }
+
+    /**
+     * Joins the given elements and adds a separator between each element in the resulting string.
+     * There will be no separator at the start or end of the string. There will be no consecutive
+     * separators (even in case an element is null or empty).
+     * @param separator the separator string
+     * @param elements the elements to concatenate to a new string
+     * @return the joined strings, separated by the separator
+     */
+    @NonNull
+    public static StringBuilder join(String separator, String... elements) {
+        StringBuilder builder = new StringBuilder();
+        if (elements == null) {
+            return builder;
+        }
+        boolean hasAdded = false;
+        for (String element : elements) {
+            if (element != null && element.length() > 0) {
+                if (hasAdded) {
+                    builder.append(separator);
+                }
+                builder.append(element);
+                hasAdded = true;
+            }
+        }
+        return builder;
+    }
+
+    @NonNull
+    public static String getFirstOf(String first, String second) {
+        if (first != null && !first.isEmpty()) {
+            return first;
+        }
+        if (second != null) {
+            return second;
+        }
+        return "";
+    }
+
+    public static boolean isNullOrEmpty(CharSequence string){
+        return string == null || string.length() == 0;
+    }
+
+    public static boolean isEmpty(CharSequence string) {
+        return string != null && string.length() == 0;
+    }
+
+    public static String ensureNotNull(String message) {
+        if (message != null) {
+            return message;
+        }
+        return "";
+    }
+
+    public static String terminateNull(String input) {
+        if (input == null || input.length() == 0) {
+            return new String(new byte[]{(byte) 0});
+        }
+        char lastChar = input.charAt(input.length() - 1);
+        if (lastChar == 0) return input;
+
+        byte[] newArray = new byte[input.getBytes().length + 1];
+        System.arraycopy(input.getBytes(), 0, newArray, 0, input.getBytes().length);
+
+        newArray[newArray.length - 1] = 0;
+
+        return new String(newArray);
+    }
+
+    @Nullable
+    public static String untilNullTerminator(final byte[] bytes, final int startOffset) {
+        for (int i = startOffset; i < bytes.length; i++) {
+            if (bytes[i] == 0) {
+                return new String(ArrayUtils.subarray(bytes, startOffset, i));
+            }
+        }
+
+        return null;
+    }
+
+    @Nullable
+    public static String untilNullTerminator(final ByteBuffer buf) {
+        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+
+        while (buf.position() < buf.limit()) {
+            final byte b = buf.get();
+
+            if (b == 0) {
+                return baos.toString();
+            }
+
+            baos.write(b);
+        }
+
+        return null;
+    }
+
+    public static String bytesToHex(byte[] array) {
+        return GB.hexdump(array, 0, -1);
+    }
+
+    public static byte[] hexToBytes(String hexString) {
+        if((hexString.length() % 2) == 1) {
+            // pad with zero
+            hexString = "0" + hexString;
+        }
+        byte[] bytes = new byte[hexString.length() / 2];
+        for(int i = 0; i < bytes.length; i++) {
+            String slice = hexString.substring(i * 2, i * 2 + 2);
+            bytes[i] = (byte) Integer.parseInt(slice, 16);
+        }
+
+        return bytes;
+    }
+
+    /**
+     * Creates a shortened version of an Android package name by using only the first
+     * character of every non-last part of the package name.
+     * Example: "nodomain.freeyourgadget.gadgetbridge" is shortened to "n.f.gadgetbridge"
+     * @param packageName the original package name
+     * @return the shortened package name
+     */
+    public static String shortenPackageName(String packageName) {
+        String[] parts = packageName.split("\\.");
+        StringBuilder result = new StringBuilder();
+        for (int index=0; index < parts.length; index++) {
+            if (index == parts.length - 1) {
+                result.append(parts[index]);
+                break;
+            }
+            result.append(parts[index].charAt(0)).append(".");
+        }
+        return result.toString();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/SwipeEvents.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/SwipeEvents.java
new file mode 100644
index 0000000..d9e2ddf
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/SwipeEvents.java
@@ -0,0 +1,74 @@
+/*  Copyright (C) 2020-2024 Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.util;
+
+import android.content.Context;
+import android.view.GestureDetector;
+import android.view.MotionEvent;
+import android.view.View;
+
+//simple swipe detector based on GestureDetector, inspired by https://stackoverflow.com/a/19506010
+public class SwipeEvents implements View.OnTouchListener {
+
+    private final GestureDetector gestureDetector;
+
+    public SwipeEvents(Context context) {
+        gestureDetector = new GestureDetector(context, new GestureListener());
+    }
+
+    public void onSwipeLeft() {
+    }
+
+    public void onSwipeRight() {
+    }
+
+    public boolean onTouch(View v, MotionEvent event) {
+        return gestureDetector.onTouchEvent(event);
+    }
+    private final class GestureListener extends GestureDetector.SimpleOnGestureListener {
+
+        private static final int SWIPE_DISTANCE_THRESHOLD = 100;
+        private static final int SWIPE_VELOCITY_THRESHOLD = 100;
+
+        @Override
+        public boolean onDown(MotionEvent e) {
+            return false;
+        }
+
+        @Override
+        public boolean onSingleTapConfirmed(MotionEvent e) {
+            return false;
+        }
+
+        @Override
+        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
+            if (e1 == null || e2 == null){
+                return false;
+            }
+            float distanceX = e2.getX() - e1.getX();
+            float distanceY = e2.getY() - e1.getY();
+            if (Math.abs(distanceX) > Math.abs(distanceY) && Math.abs(distanceX) > SWIPE_DISTANCE_THRESHOLD && Math.abs(velocityX) > SWIPE_VELOCITY_THRESHOLD) {
+                if (distanceX > 0)
+                    onSwipeRight();
+                else
+                    onSwipeLeft();
+                return true;
+            }
+            return false;
+        }
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/TimePreference.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/TimePreference.java
new file mode 100644
index 0000000..f95278b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/TimePreference.java
@@ -0,0 +1,120 @@
+/*  Copyright (C) 2017-2024 Carsten Pfeiffer, Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.util;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.preference.DialogPreference;
+import android.text.format.DateFormat;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.TimePicker;
+
+public class TimePreference extends DialogPreference {
+    private int hour = 0;
+    private int minute = 0;
+
+    private TimePicker picker = null;
+
+    public TimePreference(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    protected View onCreateDialogView() {
+        picker = new TimePicker(getContext());
+        picker.setIs24HourView(DateFormat.is24HourFormat(getContext()));
+        picker.setPadding(0, 50, 0, 50);
+
+        return picker;
+    }
+
+    @Override
+    protected void onBindDialogView(View v) {
+        super.onBindDialogView(v);
+
+        picker.setCurrentHour(hour);
+        picker.setCurrentMinute(minute);
+    }
+
+    @Override
+    protected void onDialogClosed(boolean positiveResult) {
+        super.onDialogClosed(positiveResult);
+
+        if (positiveResult) {
+            hour = picker.getCurrentHour();
+            minute = picker.getCurrentMinute();
+
+            String time = getTime24h();
+
+            if (callChangeListener(time)) {
+                persistString(time);
+
+                updateSummary();
+            }
+        }
+    }
+
+    @Override
+    protected Object onGetDefaultValue(TypedArray a, int index) {
+        return a.getString(index);
+    }
+
+    @Override
+    protected void onSetInitialValue(boolean restoreValue, Object defaultValue) {
+        String time;
+
+        if (restoreValue) {
+            if (defaultValue == null) {
+                time = getPersistedString("00:00");
+            } else {
+                time = getPersistedString(defaultValue.toString());
+            }
+        } else {
+            if (defaultValue != null) {
+                time = defaultValue.toString();
+            } else {
+                time = "00:00";
+            }
+        }
+
+        String[] pieces = time.split(":");
+
+        hour = Integer.parseInt(pieces[0]);
+        minute = Integer.parseInt(pieces[1]);
+
+        updateSummary();
+    }
+
+    public void updateSummary() {
+        if (DateFormat.is24HourFormat(getContext()))
+            setSummary(getTime24h());
+        else
+            setSummary(getTime12h());
+    }
+
+    public String getTime24h() {
+        return String.format("%02d", hour) + ":" + String.format("%02d", minute);
+    }
+
+    public String getTime12h() {
+        String suffix = hour < 12 ? " AM" : " PM";
+        int h = hour > 12 ? hour - 12 : hour;
+
+        return String.valueOf(h) + ":" + String.format("%02d", minute) + suffix;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/UriHelper.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/UriHelper.java
new file mode 100644
index 0000000..4d86181
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/UriHelper.java
@@ -0,0 +1,198 @@
+/*  Copyright (C) 2016-2024 Carsten Pfeiffer, Daniele Gobbetti, Taavi Eom盲e
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.util;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.MediaStore;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+
+public class UriHelper {
+    @NonNull
+    private final Uri uri;
+    @NonNull
+    private final Context context;
+    private String fileName;
+    private long fileSize;
+    @Nullable
+    private File file;
+
+    private static final Logger LOG = LoggerFactory.getLogger(UriHelper.class);
+
+    private UriHelper(@NonNull Uri uri, @NonNull Context context) {
+        this.uri = uri;
+        this.context = context;
+    }
+
+    /**
+     * Returns the uri as passed to #get(Uri, Context)
+     */
+    @NonNull
+    public Uri getUri() {
+        return uri;
+    }
+
+    /**
+     * Returns the context as passed to #get(Uri, Context)
+     */
+    @NonNull
+    public Context getContext() {
+        return context;
+    }
+
+    /**
+     * Returns an immutable helper to access the given Uri. In case the uri cannot be read/resolved
+     * an IOException is thrown.
+     * @param uri the uri to access
+     * @param context the context for accessing uris
+     * @throws IOException
+     */
+    @NonNull
+    public static UriHelper get(@NonNull Uri uri, @NonNull Context context) throws FileNotFoundException, IOException {
+        UriHelper helper = new UriHelper(uri, context);
+        helper.resolveMetadata();
+        return helper;
+    }
+
+    /**
+     * Opens a stream to read the contents of the uri.
+     * Note: the caller has to close the stream after usage.
+     * Every invocation of this method will open a new stream.
+     * FIXME: make sure that every caller actually closes the returned stream!
+     * @throws FileNotFoundException
+     */
+    @NonNull
+    public InputStream openInputStream() throws FileNotFoundException {
+        ContentResolver cr = context.getContentResolver();
+        InputStream inputStream = cr.openInputStream(uri);
+        if (inputStream != null) {
+            return new BufferedInputStream(inputStream);
+        }
+        throw new FileNotFoundException("Unable to open inputstream for " + uri);
+    }
+
+    /**
+     * Returns the content length (file size) in bytes
+     */
+    public long getFileSize() {
+        return fileSize;
+    }
+
+    /**
+     * Returns the name of the file referenced by the Uri. Does not include the path.
+     */
+    @NonNull
+    public String getFileName() {
+        return fileName;
+    }
+
+    /**
+     * Returns the file behind the uri, or null in case it is not a file:/ Uri.
+     * @return the file or null
+     */
+    @Nullable
+    public File getFile() {
+        return file;
+    }
+
+    private void resolveMetadata() throws IOException {
+        if (uri == null) {
+            throw new IOException("URI was null, can't query metadata");
+        }
+
+        String uriScheme = uri.getScheme();
+
+        if (uriScheme == null) {
+            throw new IOException("URI scheme was null, can't query metadata");
+        }
+
+        switch (uriScheme) {
+            case ContentResolver.SCHEME_CONTENT:
+                Cursor cursor;
+                try {
+                    ContentResolver resolver = context.getContentResolver();
+                    cursor = resolver.query(
+                            uri,
+                            new String[]{
+                                    MediaStore.MediaColumns.DISPLAY_NAME,
+                                    MediaStore.MediaColumns.SIZE
+                            }, null, null, null);
+                } catch (IllegalStateException e) {
+                    LOG.error(e.toString());
+                    throw new IOException("IllegalStateException when trying to query metadata for: " + uri);
+                }
+
+                if (cursor == null) {
+                    throw new IOException("Unable to query metadata for: " + uri);
+                }
+
+                try {
+                    if (cursor.moveToFirst()) {
+                        int name_index = cursor.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME);
+                        if (name_index == -1) {
+                            throw new IOException("Unable to retrieve name for: " + uri);
+                        }
+                        int size_index = cursor.getColumnIndex(MediaStore.MediaColumns.SIZE);
+                        if (size_index == -1) {
+                            throw new IOException("Unable to retrieve size for: " + uri);
+                        }
+                        try {
+                            fileName = cursor.getString(name_index);
+                            if (fileName == null) {
+                                throw new IOException("Unable to retrieve name for: " + uri);
+                            }
+                            fileSize = cursor.getLong(size_index);
+                            if (fileSize < 0) {
+                                throw new IOException("Unable to retrieve size for: " + uri);
+                            }
+                        } catch (Exception ex) {
+                            throw new IOException("Unable to retrieve metadata for: " + uri + ": " + ex.getMessage());
+                        }
+                    }
+                } finally {
+                    cursor.close();
+                }
+                break;
+            case ContentResolver.SCHEME_FILE:
+                file = new File(uri.getPath());
+                if (!file.exists()) {
+                    throw new FileNotFoundException("Does not exist: " + file);
+                }
+                fileName = file.getName();
+                fileSize = file.length();
+                break;
+            case ContentResolver.SCHEME_ANDROID_RESOURCE:
+                // we could actually read it, but I don't see how we can determine the file size
+                throw new IOException("Unsupported scheme for uri: " + uri);
+            default:
+                throw new IOException("Unsupported scheme for uri: " + uri);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/Version.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/Version.java
new file mode 100644
index 0000000..e88ce02
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/Version.java
@@ -0,0 +1,100 @@
+/*  Copyright (C) 2017-2024 Andreas Shimokawa, Carsten Pfeiffer, Daniel
+    Dakhno, Michal Novotny
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.util;
+
+import androidx.annotation.NonNull;
+
+// http://stackoverflow.com/questions/198431/how-do-you-compare-two-version-strings-in-java
+public class Version implements Comparable<Version> {
+
+    private String version;
+
+    public final String get() {
+        return this.version;
+    }
+
+    public Version(String version) {
+        if(version == null)
+            throw new IllegalArgumentException("Version can not be null");
+
+        version = version.trim();
+        if (!version.matches("[0-9]+(\\.[0-9]+)*"))
+            throw new IllegalArgumentException("Invalid version format");
+        this.version = version;
+    }
+
+    public boolean smallerOrEqualThan(Version that){
+        return !greaterThan(that);
+    }
+
+    public boolean greaterOrEqualThan(Version that){
+        return !smallerThan(that);
+    }
+
+    public boolean smallerThan(Version that){
+        return compareTo(that) == -1;
+    }
+
+    public boolean greaterThan(Version that){
+        return compareTo(that) == 1;
+    }
+
+    public boolean sameAs(Version that){
+        return compareTo(that) == 0;
+    }
+
+    @Override public int compareTo(Version that) {
+        if(that == null)
+            return 1;
+        String[] thisParts = this.get().split("\\.");
+        String[] thatParts = that.get().split("\\.");
+        int length = Math.max(thisParts.length, thatParts.length);
+        for(int i = 0; i < length; i++) {
+            int thisPart = i < thisParts.length ?
+                    Integer.parseInt(thisParts[i]) : 0;
+            int thatPart = i < thatParts.length ?
+                    Integer.parseInt(thatParts[i]) : 0;
+            if(thisPart < thatPart)
+                return -1;
+            if(thisPart > thatPart)
+                return 1;
+        }
+        return 0;
+    }
+
+    @Override public boolean equals(Object that) {
+        if(this == that)
+            return true;
+        if(that == null)
+            return false;
+        if(this.getClass() != that.getClass())
+            return false;
+        return this.compareTo((Version) that) == 0;
+    }
+
+    @Override
+    public int hashCode() {
+        return version.hashCode();
+    }
+
+    @NonNull
+    @Override
+    public String toString() {
+        return version;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/WebViewSingleton.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/WebViewSingleton.java
new file mode 100644
index 0000000..d776abe
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/WebViewSingleton.java
@@ -0,0 +1,284 @@
+/*  Copyright (C) 2016-2024 Andreas Shimokawa, Carsten Pfeiffer, Daniele
+    Gobbetti, Jos茅 Rebelo, Taavi Eom盲e
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.util;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.MutableContextWrapper;
+import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+import android.webkit.WebResourceResponse;
+import android.webkit.WebSettings;
+import android.webkit.WebView;
+
+import androidx.annotation.NonNull;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.ByteArrayInputStream;
+import java.nio.charset.Charset;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.CountDownLatch;
+
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.webview.GBChromeClient;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.webview.GBWebClient;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.webview.JSInterface;
+
+public class WebViewSingleton {
+
+    private static final Logger LOG = LoggerFactory.getLogger(WebViewSingleton.class);
+    private static final WebViewSingleton instance = new WebViewSingleton();
+
+    private WebView webView = null;
+    private MutableContextWrapper contextWrapper;
+    private Looper mainLooper;
+    private UUID currentRunningUUID;
+    private Messenger internetHelper = null;
+    public boolean internetHelperBound;
+    private boolean internetHelperInstalled;
+    private CountDownLatch latch;
+    private WebResourceResponse internetResponse;
+    private Messenger internetHelperListener;
+
+    private WebViewSingleton() {
+    }
+
+    //Internet helper outgoing connection
+    private final ServiceConnection internetHelperConnection = new ServiceConnection() {
+        public void onServiceConnected(ComponentName className, IBinder service) {
+            LOG.info("internet helper service bound");
+            internetHelperBound = true;
+            internetHelper = new Messenger(service);
+        }
+
+        public void onServiceDisconnected(ComponentName className) {
+            LOG.info("internet helper service unbound");
+            internetHelper = null;
+            internetHelperBound = false;
+        }
+    };
+
+    public static synchronized void ensureCreated(Activity context) {
+        if (instance.webView == null) {
+            instance.contextWrapper = new MutableContextWrapper(context);
+            instance.mainLooper = context.getMainLooper();
+            instance.webView = new WebView(instance.contextWrapper);
+            WebView.setWebContentsDebuggingEnabled(true);
+            instance.webView.setWillNotDraw(true);
+            instance.webView.clearCache(true);
+            instance.webView.setWebViewClient(new GBWebClient());
+            instance.webView.setWebChromeClient(new GBChromeClient());
+            WebSettings webSettings = instance.webView.getSettings();
+            //noinspection SetJavaScriptEnabled
+            webSettings.setJavaScriptEnabled(true);
+            //needed to access the DOM
+            webSettings.setDomStorageEnabled(true);
+            //needed for localstorage
+            webSettings.setDatabaseEnabled(true);
+            // #3373 #3424 - Fix configuration for pebble apps
+            // TODO: this should use a WebViewAssetLoader
+            webSettings.setAllowFileAccess(true);
+        }
+    }
+
+    public static WebViewSingleton getInstance() {
+        return instance;
+    }
+
+    public WebResourceResponse send(Message webRequest) throws RemoteException, InterruptedException {
+        webRequest.replyTo = internetHelperListener;
+        latch = new CountDownLatch(1); //the messenger should run on a single thread, hence we don't need to be worried about concurrency. This approach however is certainly not ideal.
+        internetHelper.send(webRequest);
+        latch.await();
+        return internetResponse;
+    }
+
+    //Internet helper inbound (responses) handler
+    private class IncomingHandler extends Handler {
+        public IncomingHandler(Looper looper) {
+            super(looper);
+        }
+
+        private String getCharsetFromHeaders(String contentType) {
+            if (contentType != null && contentType.toLowerCase().trim().contains("charset=")) {
+                String[] parts = contentType.toLowerCase().trim().split("=");
+                if (parts.length > 0)
+                    return parts[1];
+            }
+            return null;
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            Bundle data = msg.getData();
+            LOG.debug("WEBVIEW: internet helper returned: " + data.getString("response"));
+            Map<String, String> headers = new HashMap<>();
+            headers.put("Access-Control-Allow-Origin", "*");
+
+            internetResponse = new WebResourceResponse(data.getString("content-type"), data.getString("content-encoding"), 200, "OK",
+                    headers,
+                    new ByteArrayInputStream(data.getString("response").getBytes(Charset.forName(getCharsetFromHeaders(data.getString("content-type")))))
+            );
+
+            latch.countDown();
+        }
+    }
+
+    @NonNull
+    public WebView getWebView(Context context) {
+        contextWrapper.setBaseContext(context);
+        return webView;
+    }
+
+    /**
+     * Checks that the webview is up and the given app is running.
+     * @param uuid the uuid of the application expected to be running
+     * @throws IllegalStateException when the webview is not active or the app is not running
+     */
+    public void checkAppRunning(@NonNull UUID uuid) {
+        if (webView == null) {
+            throw new IllegalStateException("instance.webView is null!");
+        }
+        if (!uuid.equals(currentRunningUUID)) {
+            throw new IllegalStateException("Expected app " + uuid + " is not running, but " + currentRunningUUID + " is.");
+        }
+    }
+
+    public void runJavascriptInterface(@NonNull Activity context, @NonNull GBDevice device, @NonNull UUID uuid) {
+        ensureCreated(context);
+        runJavascriptInterface(device, uuid);
+    }
+
+    public void runJavascriptInterface(@NonNull GBDevice device, @NonNull UUID uuid) {
+        if (uuid.equals(currentRunningUUID)) {
+            LOG.debug("WEBVIEW uuid not changed keeping the old context");
+        } else {
+            final JSInterface jsInterface = new JSInterface(device, uuid);
+            LOG.debug("WEBVIEW uuid changed, restarting");
+            currentRunningUUID = uuid;
+            invokeWebview(new WebViewRunnable() {
+                @Override
+                public void invoke(WebView webView) {
+                    webView.onResume();
+                    webView.removeJavascriptInterface("GBjs");
+                    webView.addJavascriptInterface(jsInterface, "GBjs");
+                    webView.loadUrl("file:///android_asset/app_config/configure.html?rand=" + Math.random() * 500);
+                }
+            });
+            if (contextWrapper != null && !internetHelperBound && !internetHelperInstalled) {
+                String internetHelperPkg = "nodomain.freeyourgadget.internethelper";
+                String internetHelperCls = internetHelperPkg + ".MyService";
+                try {
+                    contextWrapper.getPackageManager().getApplicationInfo(internetHelperPkg, 0);
+                    Intent intent = new Intent();
+                    intent.setComponent(new ComponentName(internetHelperPkg, internetHelperCls));
+                    contextWrapper.getApplicationContext().bindService(intent, internetHelperConnection, Context.BIND_AUTO_CREATE);
+                    Thread thread = new Thread(new Runnable() {
+                        @Override
+                        public void run() {
+                            Looper.prepare();
+                            internetHelperListener = new Messenger(new IncomingHandler(Looper.myLooper()));
+                            Looper.loop();
+                        }
+                    });
+                    thread.start();
+                    internetHelperInstalled = true;
+                }
+                catch (PackageManager.NameNotFoundException e) {
+                    internetHelperInstalled = false;
+                    LOG.info("WEBVIEW: Internet helper not installed, only mimicked HTTP requests will work.");
+                }
+            }
+        }
+    }
+
+    public void stopJavascriptInterface() {
+        invokeWebview(new WebViewRunnable() {
+            @Override
+            public void invoke(WebView webView) {
+                webView.removeJavascriptInterface("GBjs");
+                webView.loadUrl("about:blank");
+            }
+        });
+    }
+
+    public void disposeWebView() {
+        if (internetHelperBound) {
+            LOG.debug("WEBVIEW: will unbind the internet helper");
+            contextWrapper.getApplicationContext().unbindService(internetHelperConnection);
+            internetHelperBound = false;
+        }
+        currentRunningUUID = null;
+        invokeWebview(new WebViewRunnable() {
+            @Override
+            public void invoke(WebView webView) {
+                webView.removeJavascriptInterface("GBjs");
+//                    webView.setWebChromeClient(null);
+//                    webView.setWebViewClient(null);
+                webView.clearHistory();
+                webView.clearCache(true);
+                webView.loadUrl("about:blank");
+//                    webView.freeMemory();
+                webView.pauseTimers();
+//                    webView.destroy();
+//                    webView = null;
+//                    contextWrapper = null;
+//                    jsInterface = null;
+            }
+        });
+    }
+
+    public void invokeWebview(final WebViewRunnable runnable) {
+        if (webView == null || mainLooper == null) {
+            LOG.warn("Webview already disposed, ignoring runnable");
+            return;
+        }
+        new Handler(mainLooper).post(new Runnable() {
+            @Override
+            public void run() {
+                if (webView == null) {
+                    LOG.warn("Webview already disposed, cannot invoke runnable");
+                    return;
+                }
+                runnable.invoke(webView);
+            }
+        });
+    }
+
+    public interface WebViewRunnable {
+        /**
+         * Called in the main thread with a non-null webView webView
+         * @param webView the webview, never null
+         */
+        void invoke(WebView webView);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/WidgetPreferenceStorage.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/WidgetPreferenceStorage.java
new file mode 100644
index 0000000..be3a4d5
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/WidgetPreferenceStorage.java
@@ -0,0 +1,181 @@
+/*  Copyright (C) 2020-2024 Arjan Schrijver, Petr Van臎k
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.util;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.preference.PreferenceManager;
+import android.widget.Toast;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class WidgetPreferenceStorage {
+    private static final Logger LOG = LoggerFactory.getLogger(WidgetPreferenceStorage.class);
+    boolean isWidgetInPrefs = false;
+    String PREFS_WIDGET_SETTINGS = "widget_settings";
+
+    public String getSavedDeviceAddress(Context context, int appWidgetId) {
+        String savedDeviceAddress = null;
+
+        SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(context);
+        String savedWidgetsPreferencesData = sharedPrefs.getString(PREFS_WIDGET_SETTINGS, "");
+        JSONArray savedWidgetsPreferencesDataArray = null;
+        try {
+            savedWidgetsPreferencesDataArray = new JSONArray(savedWidgetsPreferencesData);
+        } catch (
+                JSONException e) {
+            LOG.error(e.getMessage());
+        }
+
+        LOG.debug("widget JSON loaded: " + savedWidgetsPreferencesDataArray);
+        if (savedWidgetsPreferencesDataArray == null) {
+            return null;
+        }
+        for (int i = 0; i < savedWidgetsPreferencesDataArray.length(); i++) {
+            try {
+                JSONArray a = savedWidgetsPreferencesDataArray.getJSONArray(i);
+                if (appWidgetId == a.getInt(0)) {
+                    isWidgetInPrefs = true;
+                    savedDeviceAddress = a.getString(1);
+                }
+            } catch (JSONException e) {
+                LOG.error(e.getMessage());
+            }
+        }
+        return savedDeviceAddress;
+    }
+
+    public void removeWidgetById(Context context, int appWidgetId) {
+        LOG.debug("widget trying to remove: " + appWidgetId);
+
+        SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(context);
+        String savedWidgetsPreferencesData = sharedPrefs.getString(PREFS_WIDGET_SETTINGS, "");
+        JSONArray savedWidgetsPreferencesDataArray = null;
+        try {
+            savedWidgetsPreferencesDataArray = new JSONArray(savedWidgetsPreferencesData);
+        } catch (
+                JSONException e) {
+            LOG.error(e.getMessage());
+        }
+
+        if (savedWidgetsPreferencesDataArray == null) {
+            return;
+        }
+        for (int i = 0; i < savedWidgetsPreferencesDataArray.length(); i++) {
+            try {
+                JSONArray a = savedWidgetsPreferencesDataArray.getJSONArray(i);
+                if (appWidgetId == a.getInt(0)) {
+                    GB.toast("Removing widget preferences: " + appWidgetId, Toast.LENGTH_SHORT, GB.INFO);
+                    savedWidgetsPreferencesDataArray.remove(i);
+                }
+            } catch (JSONException e) {
+                LOG.error(e.getMessage());
+            }
+        }
+        SharedPreferences.Editor editor = sharedPrefs.edit();
+        editor.putString(PREFS_WIDGET_SETTINGS, savedWidgetsPreferencesDataArray.toString());
+        editor.apply();
+    }
+
+    public void saveWidgetPrefs(Context context, String AppWidgetId, String HwAddress) {
+
+        SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(context);
+        String savedWidgetsPreferencesData = sharedPrefs.getString(PREFS_WIDGET_SETTINGS, "");
+        JSONArray savedWidgetsPreferencesDataArray = null;
+        try {
+            savedWidgetsPreferencesDataArray = new JSONArray(savedWidgetsPreferencesData);
+        } catch (
+                JSONException e) {
+            LOG.error(e.getMessage());
+        }
+
+        if (savedWidgetsPreferencesDataArray == null) {
+            savedWidgetsPreferencesDataArray = new JSONArray();
+        }
+
+
+        try {
+            savedWidgetsPreferencesDataArray.put(new JSONArray(new String[]{AppWidgetId, HwAddress}));
+        } catch (JSONException e) {
+            LOG.error(e.getMessage());
+        }
+
+
+        SharedPreferences.Editor editor = sharedPrefs.edit();
+        editor.putString(PREFS_WIDGET_SETTINGS, savedWidgetsPreferencesDataArray.toString());
+        editor.apply();
+    }
+
+    public boolean isWidgetInPrefs() {
+        return isWidgetInPrefs;
+    }
+
+    public void deleteWidgetsPrefs(Context context) {
+        SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(context);
+        SharedPreferences.Editor editor = sharedPrefs.edit();
+        editor.putString(PREFS_WIDGET_SETTINGS, "");
+        editor.apply();
+    }
+
+    public void showAppWidgetsPrefs(Context context) {
+        SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(context);
+        String savedWidgetsPreferencesData = sharedPrefs.getString(PREFS_WIDGET_SETTINGS, "");
+        JSONArray savedWidgetsPreferencesDataArray = null;
+        try {
+            savedWidgetsPreferencesDataArray = new JSONArray(savedWidgetsPreferencesData);
+        } catch (
+                JSONException e) {
+            LOG.error(e.getMessage());
+        }
+        GB.toast("Saved app widget preferences: " + savedWidgetsPreferencesDataArray, Toast.LENGTH_SHORT, GB.INFO);
+    }
+
+    public GBDevice getDeviceForWidget(int appWidgetId) {
+        Context context = GBApplication.getContext();
+        if (!(context instanceof GBApplication)) {
+            return null;
+        }
+
+        String savedDeviceAddress = getSavedDeviceAddress(context, appWidgetId);
+
+        if (savedDeviceAddress != null) {
+            return getDeviceByMAC(context.getApplicationContext(), savedDeviceAddress); //this would probably only happen if device no longer exists in GB
+        }
+        return null;
+    }
+
+    private GBDevice getDeviceByMAC(Context appContext, String HwAddress) {
+        GBApplication gbApp = (GBApplication) appContext;
+        List<? extends GBDevice> devices = gbApp.getDeviceManager().getDevices();
+        for (GBDevice device : devices) {
+            if (device.getAddress().equals(HwAddress)) {
+                return device;
+            }
+        }
+        return null;
+    }
+
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/XDatePreference.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/XDatePreference.java
new file mode 100644
index 0000000..8721767
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/XDatePreference.java
@@ -0,0 +1,114 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.util;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+
+import androidx.preference.DialogPreference;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+
+public class XDatePreference extends DialogPreference {
+    private int year;
+    private int month;
+    private int day;
+    private long minDate; // TODO actually read minDate
+    private long maxDate; // TODO actually read maxDate
+
+    public XDatePreference(final Context context, final AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    protected Object onGetDefaultValue(final TypedArray a, final int index) {
+        return a.getString(index);
+    }
+
+    @Override
+    protected void onSetInitialValue(final Object defaultValue) {
+        final String persistedString = getPersistedString((String) defaultValue);
+
+        final String dateStr;
+
+        if (StringUtils.isNullOrEmpty(persistedString)) {
+            final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd", Locale.ROOT);
+            dateStr = getPersistedString(sdf.format(new Date()));
+        } else {
+            dateStr = persistedString;
+        }
+
+        final String[] pieces = dateStr.split("-");
+
+        year = Integer.parseInt(pieces[0]);
+        month = Integer.parseInt(pieces[1]);
+        day = Integer.parseInt(pieces[2]);
+
+        updateSummary();
+    }
+
+    public void setMinDate(final long minDate) {
+        this.minDate = minDate;
+    }
+
+    public void setMaxDate(final long maxDate) {
+        this.maxDate = maxDate;
+    }
+
+    public int getYear() {
+        return year;
+    }
+
+    public int getMonth() {
+        return month;
+    }
+
+    public int getDay() {
+        return day;
+    }
+
+    public long getMinDate() {
+        return minDate;
+    }
+
+    public long getMaxDate() {
+        return maxDate;
+    }
+
+    String getPrefValue() {
+        return String.format(Locale.ROOT, "%04d-%02d-%02d", year, month, day);
+    }
+
+    public void setValue(final int year, final int month, final int day) {
+        this.year = year;
+        this.month = month;
+        this.day = day;
+
+        persistStringValue(getPrefValue());
+    }
+
+    void updateSummary() {
+        setSummary(String.format(Locale.ROOT, "%04d-%02d-%02d", year, month, day));
+    }
+
+    void persistStringValue(final String value) {
+        persistString(value);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/XDatePreferenceFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/XDatePreferenceFragment.java
new file mode 100644
index 0000000..74d6ae2
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/XDatePreferenceFragment.java
@@ -0,0 +1,80 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.util;
+
+import android.content.Context;
+import android.view.View;
+import android.widget.DatePicker;
+
+import androidx.annotation.NonNull;
+import androidx.preference.DialogPreference;
+import androidx.preference.Preference;
+
+import nodomain.freeyourgadget.gadgetbridge.util.dialogs.MaterialPreferenceDialogFragment;
+
+public class XDatePreferenceFragment extends MaterialPreferenceDialogFragment implements DialogPreference.TargetFragment {
+    private DatePicker picker = null;
+
+    @Override
+    protected View onCreateDialogView(final Context context) {
+        picker = new DatePicker(context);
+        picker.setPadding(0, 50, 0, 50);
+
+        return picker;
+    }
+
+    @Override
+    protected void onBindDialogView(final View v) {
+        super.onBindDialogView(v);
+        final XDatePreference pref = (XDatePreference) getPreference();
+
+        picker.init(pref.getYear(), pref.getMonth() - 1, pref.getDay(), null);
+
+        if (pref.getMinDate() != 0) {
+            picker.setMinDate(pref.getMinDate());
+        }
+
+        if (pref.getMaxDate() != 0) {
+            picker.setMaxDate(pref.getMaxDate());
+        }
+    }
+
+    @Override
+    public void onDialogClosed(final boolean positiveResult) {
+        if (!positiveResult) {
+            return;
+        }
+
+        final XDatePreference pref = (XDatePreference) getPreference();
+        pref.setValue(
+                picker.getYear(),
+                picker.getMonth() + 1,
+                picker.getDayOfMonth()
+        );
+
+        final String date = pref.getPrefValue();
+        if (pref.callChangeListener(date)) {
+            pref.persistStringValue(date);
+            pref.updateSummary();
+        }
+    }
+
+    @Override
+    public Preference findPreference(@NonNull final CharSequence key) {
+        return getPreference();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/XTimePreference.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/XTimePreference.java
new file mode 100644
index 0000000..ac3349d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/XTimePreference.java
@@ -0,0 +1,128 @@
+/*  Copyright (C) 2019-2024 Andreas Shimokawa
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.util;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.text.format.DateFormat;
+import android.util.AttributeSet;
+
+import androidx.preference.DialogPreference;
+
+import java.util.Locale;
+
+public class XTimePreference extends DialogPreference {
+    protected int hour = 0;
+    protected int minute = 0;
+
+    protected Format format = Format.AUTO;
+
+    public XTimePreference(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    protected Object onGetDefaultValue(TypedArray a, int index) {
+        return a.getString(index);
+    }
+
+    @Override
+    protected void onSetInitialValue(boolean restoreValue, Object defaultValue) {
+        String time;
+
+        if (restoreValue) {
+            if (defaultValue == null) {
+                time = getPersistedString("00:00");
+            } else {
+                time = getPersistedString(defaultValue.toString());
+            }
+        } else {
+            if (defaultValue != null) {
+                time = defaultValue.toString();
+            } else {
+                time = "00:00";
+            }
+        }
+
+        String[] pieces = time.split(":");
+
+        hour = Integer.parseInt(pieces[0]);
+        minute = Integer.parseInt(pieces[1]);
+
+        updateSummary();
+    }
+
+    public String getPrefValue() {
+        return String.format(Locale.ROOT, "%02d:%02d", hour, minute);
+    }
+
+    public void setValue(final int hour, final int minute) {
+        this.hour = hour;
+        this.minute = minute;
+
+        persistStringValue(getPrefValue());
+    }
+
+    void updateSummary() {
+        if (is24HourFormat())
+            setSummary(getTime24h());
+        else
+            setSummary(getTime12h());
+    }
+
+    String getTime24h() {
+        return String.format("%02d", hour) + ":" + String.format("%02d", minute);
+    }
+
+    private String getTime12h() {
+        String suffix = hour < 12 ? " AM" : " PM";
+        int h = hour > 12 ? hour - 12 : hour;
+
+        return h + ":" + String.format("%02d", minute) + suffix;
+    }
+
+    public void setFormat(final Format format) {
+        this.format = format;
+    }
+
+    public Format getFormat() {
+        return format;
+    }
+
+    void persistStringValue(String value) {
+        persistString(value);
+    }
+
+    public boolean is24HourFormat() {
+        switch (format) {
+            case FORMAT_24H:
+                return true;
+            case FORMAT_12H:
+                return false;
+            case AUTO:
+            default:
+                return DateFormat.is24HourFormat(getContext());
+        }
+    }
+
+    public enum Format {
+        AUTO,
+        FORMAT_24H,
+        FORMAT_12H,
+        ;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/XTimePreferenceFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/XTimePreferenceFragment.java
new file mode 100644
index 0000000..b8f6bf9
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/XTimePreferenceFragment.java
@@ -0,0 +1,72 @@
+/*  Copyright (C) 2019-2024 Andreas Shimokawa, Arjan Schrijver
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.util;
+
+import android.content.Context;
+import android.text.format.DateFormat;
+import android.view.View;
+import android.widget.TimePicker;
+
+import androidx.preference.DialogPreference;
+import androidx.preference.Preference;
+
+import nodomain.freeyourgadget.gadgetbridge.util.dialogs.MaterialPreferenceDialogFragment;
+
+public class XTimePreferenceFragment extends MaterialPreferenceDialogFragment implements DialogPreference.TargetFragment {
+    private TimePicker picker = null;
+
+    @Override
+    protected View onCreateDialogView(Context context) {
+        picker = new TimePicker(context);
+        picker.setIs24HourView(((XTimePreference) getPreference()).is24HourFormat());
+        picker.setPadding(0, 50, 0, 50);
+
+        return picker;
+    }
+
+    @Override
+    protected void onBindDialogView(View v) {
+        super.onBindDialogView(v);
+        XTimePreference pref = (XTimePreference) getPreference();
+
+        picker.setCurrentHour(pref.hour);
+        picker.setCurrentMinute(pref.minute);
+    }
+
+    @Override
+    public void onDialogClosed(boolean positiveResult) {
+
+        if (positiveResult) {
+            XTimePreference pref = (XTimePreference) getPreference();
+
+            pref.hour = picker.getCurrentHour();
+            pref.minute = picker.getCurrentMinute();
+
+            String time = pref.getTime24h();
+            if (pref.callChangeListener(time)) {
+                pref.persistStringValue(time);
+                pref.updateSummary();
+            }
+        }
+    }
+
+
+    @Override
+    public Preference findPreference(CharSequence key) {
+        return getPreference();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/ZipFileException.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/ZipFileException.java
new file mode 100644
index 0000000..d5eaabc
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/ZipFileException.java
@@ -0,0 +1,27 @@
+/*  Copyright (C) 2022-2024 Jos茅 Rebelo, MPeter
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.util;
+
+public class ZipFileException extends Exception {
+    public ZipFileException(final String message) {
+        super(String.format("Error while reading ZIP file: %s", message));
+    }
+
+    public ZipFileException(final String message, final Throwable cause) {
+        super(String.format("Error while reading ZIP file: %s", message), cause);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/backup/AbstractZipBackupJob.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/backup/AbstractZipBackupJob.java
new file mode 100644
index 0000000..acd1a97
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/backup/AbstractZipBackupJob.java
@@ -0,0 +1,100 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.util.backup;
+
+import android.content.Context;
+import android.os.Handler;
+
+import androidx.annotation.StringRes;
+import androidx.annotation.WorkerThread;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+
+import java.util.Date;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import nodomain.freeyourgadget.gadgetbridge.util.gson.GsonUtcDateAdapter;
+
+public abstract class AbstractZipBackupJob implements Runnable {
+    public static final String METADATA_FILENAME = "gadgetbridge.json";
+    public static final String DATABASE_FILENAME = "database/Gadgetbridge";
+    public static final String PREFS_GLOBAL_FILENAME = "preferences/global.json";
+    public static final String PREFS_DEVICE_FILENAME = "preferences/device_%s.json";
+    public static final String EXTERNAL_FILES_FOLDER = "files";
+
+    public static final int VERSION = 1;
+
+    protected static final Gson GSON = new GsonBuilder()
+            .registerTypeAdapter(Date.class, new GsonUtcDateAdapter())
+            .setPrettyPrinting()
+            .serializeNulls()
+            .create();
+
+    private final Context mContext;
+    private final Handler mHandler;
+    private final ZipBackupCallback mCallback;
+
+    private final AtomicBoolean aborted = new AtomicBoolean(false);
+
+    private long lastProgressUpdateTs;
+    private long lastProgressUpdateMessage;
+
+    public AbstractZipBackupJob(final Context context, final ZipBackupCallback callback) {
+        this.mContext = context;
+        this.mHandler = new Handler(context.getMainLooper());
+        this.mCallback = callback;
+    }
+
+    public Context getContext() {
+        return mContext;
+    }
+
+    public void abort() {
+        aborted.set(true);
+    }
+
+    public boolean isAborted() {
+        return aborted.get();
+    }
+
+    @WorkerThread
+    protected void updateProgress(final int percentage, @StringRes final int message, final Object... formatArgs) {
+        final long now = System.currentTimeMillis();
+        if (percentage != 100 && now - lastProgressUpdateTs < 1000L) {
+            // Avoid updating the notification too frequently, but still do if the message changed
+            if (lastProgressUpdateMessage == message) {
+                return;
+            }
+        }
+        lastProgressUpdateTs = now;
+        lastProgressUpdateMessage = message;
+        mHandler.post(() -> {
+            mCallback.onProgress(percentage, getContext().getString(message, formatArgs));
+        });
+    }
+
+    @WorkerThread
+    protected void onSuccess(final String warnings) {
+        mHandler.post(() -> mCallback.onSuccess(warnings));
+    }
+
+    @WorkerThread
+    protected void onFailure(final String errorMessage) {
+        mHandler.post(() -> mCallback.onFailure(errorMessage));
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/backup/JsonBackupPreferences.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/backup/JsonBackupPreferences.java
new file mode 100644
index 0000000..8c8150f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/backup/JsonBackupPreferences.java
@@ -0,0 +1,203 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.util.backup;
+
+import android.content.SharedPreferences;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.TypeAdapterFactory;
+
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import com.google.gson.typeadapters.RuntimeTypeAdapterFactory;
+
+public class JsonBackupPreferences {
+    private static final Gson GSON = new GsonBuilder()
+            .registerTypeAdapterFactory(getTypeAdapterFactory())
+            .setPrettyPrinting()
+            .serializeNulls()
+            .create();
+
+    private final Map<String, PreferenceValue> preferences;
+
+    private static final String BOOLEAN = "Boolean";
+    private static final String FLOAT = "Float";
+    private static final String INTEGER = "Integer";
+    private static final String LONG = "Long";
+    private static final String STRING = "String";
+    private static final String HASHSET = "HashSet";
+
+    public JsonBackupPreferences(final Map<String, PreferenceValue> preferences) {
+        this.preferences = preferences;
+    }
+
+    public static JsonBackupPreferences fromJson(final InputStream inputStream) {
+        return GSON.fromJson(
+                new InputStreamReader(inputStream, StandardCharsets.UTF_8),
+                JsonBackupPreferences.class
+        );
+    }
+
+    public String toJson() {
+        return GSON.toJson(this);
+    }
+
+    /**
+     * @noinspection BooleanMethodIsAlwaysInverted
+     */
+    public boolean importInto(final SharedPreferences sharedPreferences) {
+        final SharedPreferences.Editor editor = sharedPreferences.edit();
+        editor.clear();
+        for (final Map.Entry<String, PreferenceValue> e : preferences.entrySet()) {
+            e.getValue().put(editor, e.getKey());
+        }
+        return editor.commit();
+    }
+
+    public static JsonBackupPreferences exportFrom(final SharedPreferences sharedPreferences) {
+        final Map<String, PreferenceValue> values = new HashMap<>();
+
+        for (final Map.Entry<String, ?> entry : sharedPreferences.getAll().entrySet()) {
+            final String key = entry.getKey();
+
+            final Object valueObject = entry.getValue();
+            // Skip this entry if the value is null;
+            if (valueObject == null) continue;
+
+            final String valueType = valueObject.getClass().getSimpleName();
+
+            if (BOOLEAN.equals(valueType)) {
+                values.put(key, new BooleanPreferenceValue((Boolean) valueObject));
+            } else if (FLOAT.equals(valueType)) {
+                values.put(key, new FloatPreferenceValue((Float) valueObject));
+            } else if (INTEGER.equals(valueType)) {
+                values.put(key, new IntegerPreferenceValue((Integer) valueObject));
+            } else if (LONG.equals(valueType)) {
+                values.put(key, new LongPreferenceValue((Long) valueObject));
+            } else if (STRING.equals(valueType)) {
+                values.put(key, new StringPreferenceValue((String) valueObject));
+            } else if (HASHSET.equals(valueType)) {
+                values.put(key, new StringSetPreferenceValue((HashSet) valueObject));
+            } else {
+                throw new IllegalArgumentException("Unknown preference type " + valueType);
+            }
+        }
+
+        return new JsonBackupPreferences(values);
+    }
+
+    public interface PreferenceValue {
+        void put(final SharedPreferences.Editor editor, final String key);
+    }
+
+    public static class BooleanPreferenceValue implements PreferenceValue {
+        private final boolean value;
+
+        public BooleanPreferenceValue(final boolean value) {
+            this.value = value;
+        }
+
+        @Override
+        public void put(final SharedPreferences.Editor editor, final String key) {
+            editor.putBoolean(key, value);
+        }
+    }
+
+    public static class FloatPreferenceValue implements PreferenceValue {
+        private final float value;
+
+        public FloatPreferenceValue(final float value) {
+            this.value = value;
+        }
+
+        @Override
+        public void put(final SharedPreferences.Editor editor, final String key) {
+            editor.putFloat(key, value);
+        }
+    }
+
+    public static class IntegerPreferenceValue implements PreferenceValue {
+        private final int value;
+
+        public IntegerPreferenceValue(final int value) {
+            this.value = value;
+        }
+
+        @Override
+        public void put(final SharedPreferences.Editor editor, final String key) {
+            editor.putInt(key, value);
+        }
+    }
+
+    public static class LongPreferenceValue implements PreferenceValue {
+        private final long value;
+
+        public LongPreferenceValue(final long value) {
+            this.value = value;
+        }
+
+        @Override
+        public void put(final SharedPreferences.Editor editor, final String key) {
+            editor.putLong(key, value);
+        }
+    }
+
+    public static class StringPreferenceValue implements PreferenceValue {
+        private final String value;
+
+        public StringPreferenceValue(final String value) {
+            this.value = value;
+        }
+
+        @Override
+        public void put(final SharedPreferences.Editor editor, final String key) {
+            editor.putString(key, value);
+        }
+    }
+
+    public static class StringSetPreferenceValue implements PreferenceValue {
+        private final Set<String> value;
+
+        public StringSetPreferenceValue(final Set<String> value) {
+            this.value = value;
+        }
+
+        @Override
+        public void put(final SharedPreferences.Editor editor, final String key) {
+            editor.putStringSet(key, new HashSet<>(value));
+        }
+    }
+
+    public static TypeAdapterFactory getTypeAdapterFactory() {
+        return RuntimeTypeAdapterFactory
+                .of(PreferenceValue.class, "type")
+                .registerSubtype(BooleanPreferenceValue.class, BOOLEAN)
+                .registerSubtype(FloatPreferenceValue.class, FLOAT)
+                .registerSubtype(IntegerPreferenceValue.class, INTEGER)
+                .registerSubtype(LongPreferenceValue.class, LONG)
+                .registerSubtype(StringPreferenceValue.class, STRING)
+                .registerSubtype(StringSetPreferenceValue.class, HASHSET)
+                .recognizeSubtypes();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/backup/ZipBackupCallback.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/backup/ZipBackupCallback.java
new file mode 100644
index 0000000..0cfbcda
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/backup/ZipBackupCallback.java
@@ -0,0 +1,27 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.util.backup;
+
+import androidx.annotation.Nullable;
+
+public interface ZipBackupCallback {
+    void onProgress(final int progress, final String message);
+
+    void onSuccess(final String warnings);
+
+    void onFailure(@Nullable final String errorMessage);
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/backup/ZipBackupExportJob.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/backup/ZipBackupExportJob.java
new file mode 100644
index 0000000..912c124
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/backup/ZipBackupExportJob.java
@@ -0,0 +1,249 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.util.backup;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.net.Uri;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+
+import nodomain.freeyourgadget.gadgetbridge.BuildConfig;
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
+
+public class ZipBackupExportJob extends AbstractZipBackupJob {
+    private static final Logger LOG = LoggerFactory.getLogger(ZipBackupExportJob.class);
+
+    private final Uri mUri;
+
+    private final byte[] copyBuffer = new byte[8192];
+
+    public ZipBackupExportJob(final Context context, final ZipBackupCallback callback, final Uri uri) {
+        super(context, callback);
+        this.mUri = uri;
+    }
+
+    @Override
+    public void run() {
+        try (final OutputStream outputStream = getContext().getContentResolver().openOutputStream(mUri);
+             final ZipOutputStream zipOut = new ZipOutputStream(outputStream)) {
+
+            if (isAborted()) return;
+
+            // Preferences
+            updateProgress(0, R.string.backup_restore_exporting_preferences);
+            exportPreferences(zipOut);
+
+            if (isAborted()) return;
+
+            // Database
+            updateProgress(10, R.string.backup_restore_exporting_database);
+            exportDatabase(zipOut, getContext());
+
+            if (isAborted()) return;
+
+            // External files
+            updateProgress(25, R.string.backup_restore_exporting_files);
+
+            final File externalFilesDir = FileUtils.getExternalFilesDir();
+            LOG.debug("Exporting external files from {}", externalFilesDir);
+
+            final List<String> allExternalFiles = getAllRelativeFiles(externalFilesDir);
+            LOG.debug("Got {} files to export", allExternalFiles.size());
+
+            for (int i = 0; i < allExternalFiles.size() && !isAborted(); i++) {
+                final String child = allExternalFiles.get(i);
+                exportSingleExternalFile(zipOut, externalFilesDir, child);
+
+                final int progress = (int) Math.min(99, 50 + 49 * (i / (float) allExternalFiles.size()));
+                updateProgress(progress, R.string.backup_restore_exporting_files_i_of_n, i + 1, allExternalFiles.size());
+            }
+
+            // Metadata
+            updateProgress(99, R.string.backup_restore_exporting_finishing);
+
+            if (isAborted()) return;
+
+            addMetadata(zipOut);
+
+            zipOut.finish();
+            zipOut.flush();
+
+            if (isAborted()) return;
+
+            LOG.info("Export complete");
+
+            onSuccess(null);
+        } catch (final Exception e) {
+            LOG.error("Export failed", e);
+
+            if (!isAborted()) {
+                onFailure(e.getLocalizedMessage());
+            }
+        }
+    }
+
+    private static void exportPreferences(final ZipOutputStream zipOut) throws IOException {
+        LOG.debug("Exporting global preferences");
+
+        final SharedPreferences globalPreferences = GBApplication.getPrefs().getPreferences();
+        exportPreferences(zipOut, globalPreferences, PREFS_GLOBAL_FILENAME);
+
+        try (DBHandler dbHandler = GBApplication.acquireDB()) {
+            final List<Device> activeDevices = DBHelper.getActiveDevices(dbHandler.getDaoSession());
+            for (Device dbDevice : activeDevices) {
+                LOG.debug("Exporting device preferences for {}", dbDevice.getIdentifier());
+                final SharedPreferences devicePrefs = GBApplication.getDeviceSpecificSharedPrefs(dbDevice.getIdentifier());
+                if (devicePrefs != null) {
+                    exportPreferences(zipOut, devicePrefs, String.format(Locale.ROOT, PREFS_DEVICE_FILENAME, dbDevice.getIdentifier()));
+                }
+            }
+        } catch (final Exception e) {
+            throw new IOException("Failed to export device preferences", e);
+        }
+    }
+
+    private static void exportPreferences(final ZipOutputStream zipOut,
+                                          final SharedPreferences sharedPreferences,
+                                          final String zipEntryName) throws IOException {
+        LOG.debug("Exporting preferences to {}", zipEntryName);
+
+        final JsonBackupPreferences jsonBackupPreferences = JsonBackupPreferences.exportFrom(sharedPreferences);
+        final String preferencesJson = jsonBackupPreferences.toJson();
+
+        final ZipEntry zipEntry = new ZipEntry(zipEntryName);
+        zipOut.putNextEntry(zipEntry);
+        zipOut.write(preferencesJson.getBytes(StandardCharsets.UTF_8));
+    }
+
+    private static void exportDatabase(final ZipOutputStream zipOut, final Context context) throws IOException {
+        LOG.debug("Exporting database");
+
+        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        try (DBHandler dbHandler = GBApplication.acquireDB()) {
+            final DBHelper helper = new DBHelper(context);
+            helper.exportDB(dbHandler, baos);
+        } catch (final Exception e) {
+            throw new IOException("Failed to export database", e);
+        }
+
+        final ZipEntry zipEntry = new ZipEntry(DATABASE_FILENAME);
+        zipOut.putNextEntry(zipEntry);
+        zipOut.write(baos.toByteArray());
+    }
+
+    /**
+     * Gets a list of the relative path of all files from a directory, recursively.
+     */
+    private static List<String> getAllRelativeFiles(final File dir) {
+        final List<String> ret = new ArrayList<>();
+
+        final String[] childEntries = dir.list();
+        if (childEntries == null) {
+            LOG.warn("Files in external dir are null");
+            return ret;
+        }
+
+        for (final String child : childEntries) {
+            getAllRelativeFilesAux(ret, dir, child);
+        }
+
+        return ret;
+    }
+
+    private static void getAllRelativeFilesAux(final List<String> currentList,
+                                               final File externalFilesDir,
+                                               final String relativePath) {
+        final File file = new File(externalFilesDir, relativePath);
+        if (file.isDirectory()) {
+            final String[] childEntries = file.list();
+            if (childEntries == null) {
+                LOG.warn("Files in {} are null", file);
+                return;
+            }
+
+            for (final String child : childEntries) {
+                getAllRelativeFilesAux(currentList, externalFilesDir, relativePath + "/" + child);
+            }
+        } else if (file.isFile()) {
+            currentList.add(relativePath);
+        } else {
+            // Should never happen?
+            LOG.error("Unknown file type for {}", file);
+        }
+    }
+
+    private void exportSingleExternalFile(final ZipOutputStream zipOut,
+                                          final File externalFilesDir,
+                                          final String relativePath) throws IOException {
+        final File file = new File(externalFilesDir, relativePath);
+        if (!file.isFile()) {
+            throw new IOException("Not a file: " + file);
+        }
+
+        LOG.trace("Exporting file: {}", relativePath);
+
+        final ZipEntry zipEntry = new ZipEntry(EXTERNAL_FILES_FOLDER + "/" + relativePath);
+        zipOut.putNextEntry(zipEntry);
+
+        try (final InputStream in = new FileInputStream(new File(externalFilesDir, relativePath))) {
+            int read;
+            while ((read = in.read(copyBuffer)) > 0) {
+                zipOut.write(copyBuffer, 0, read);
+            }
+        } catch (final Exception e) {
+            throw new IOException("Failed to write " + relativePath, e);
+        }
+    }
+
+    private static void addMetadata(final ZipOutputStream zipOut) throws IOException {
+        LOG.debug("Adding metadata");
+
+        final ZipBackupMetadata metadata = new ZipBackupMetadata(
+                BuildConfig.APPLICATION_ID,
+                BuildConfig.VERSION_NAME,
+                BuildConfig.VERSION_CODE,
+                VERSION,
+                new Date()
+        );
+        final String metadataJson = GSON.toJson(metadata);
+
+        final ZipEntry zipEntry = new ZipEntry(METADATA_FILENAME);
+        zipOut.putNextEntry(zipEntry);
+        zipOut.write(metadataJson.getBytes(StandardCharsets.UTF_8));
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/backup/ZipBackupImportJob.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/backup/ZipBackupImportJob.java
new file mode 100644
index 0000000..7372881
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/backup/ZipBackupImportJob.java
@@ -0,0 +1,243 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.util.backup;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.net.Uri;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Locale;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+import nodomain.freeyourgadget.gadgetbridge.BuildConfig;
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
+
+public class ZipBackupImportJob extends AbstractZipBackupJob {
+    private static final Logger LOG = LoggerFactory.getLogger(ZipBackupImportJob.class);
+
+    private final Uri mUri;
+    private final byte[] copyBuffer = new byte[8192];
+
+    public ZipBackupImportJob(final Context context, final ZipBackupCallback callback, final Uri uri) {
+        super(context, callback);
+        this.mUri = uri;
+    }
+
+    @Override
+    public void run() {
+        try {
+            updateProgress(0, R.string.backup_restore_importing_loading);
+
+            // Load zip to temporary file so we can seek
+            LOG.debug("Getting zip file from {}", mUri);
+            final ZipFile zipFile = getZipFromUri(getContext(), mUri);
+
+            if (isAborted()) return;
+
+            // Validate file
+            updateProgress(10, R.string.backup_restore_importing_validating);
+            validateBackupFile(zipFile);
+
+            LOG.debug("Valid zip file: {}", mUri);
+
+            if (isAborted()) return;
+
+            final List<ZipEntry> externalFiles = new ArrayList<>();
+            final List<ZipEntry> devicePreferences = new ArrayList<>();
+
+            final Enumeration<? extends ZipEntry> entries = zipFile.entries();
+            while (entries.hasMoreElements() && !isAborted()) {
+                final ZipEntry zipEntry = entries.nextElement();
+
+                if (zipEntry.getName().startsWith(EXTERNAL_FILES_FOLDER + "/")) {
+                    if (zipEntry.getName().endsWith(".log") || zipEntry.getName().endsWith(".log.zip")) {
+                        continue;
+                    }
+
+                    externalFiles.add(zipEntry);
+                } else if (zipEntry.getName().startsWith("preferences/device_")) {
+                    devicePreferences.add(zipEntry);
+                }
+            }
+
+            LOG.debug("Got {} external files, {} device preferences", externalFiles.size(), devicePreferences.size());
+
+            // Restore external files
+            final File externalFilesDir = FileUtils.getExternalFilesDir();
+            final List<String> failedFiles = new ArrayList<>();
+
+            for (int i = 0; i < externalFiles.size() && !isAborted(); i++) {
+                final ZipEntry externalFile = externalFiles.get(i);
+                final File targetExternalFile = new File(externalFilesDir, externalFile.getName().replaceFirst(EXTERNAL_FILES_FOLDER + "/", ""));
+                final File parentFile = targetExternalFile.getParentFile();
+                if (parentFile == null) {
+                    LOG.warn("Parent file for {} is null", targetExternalFile);
+                } else {
+                    if (!parentFile.exists()) {
+                        if (!parentFile.mkdirs()) {
+                            LOG.warn("Failed to create parent dirs for {}", targetExternalFile);
+                        }
+                    }
+                }
+
+                try (InputStream inputStream = zipFile.getInputStream(externalFile);
+                     FileOutputStream fout = new FileOutputStream(targetExternalFile)) {
+                    while (inputStream.available() > 0) {
+                        final int bytes = inputStream.read(copyBuffer);
+                        fout.write(copyBuffer, 0, bytes);
+                    }
+                } catch (final Exception e) {
+                    LOG.error("Failed to restore file {}", externalFile);
+                    failedFiles.add(externalFile.getName());
+                }
+
+                // 10% to 75%
+                final int progress = (int) (10 + 65 * (i / (float) externalFiles.size()));
+                updateProgress(progress, R.string.backup_restore_importing_files_i_of_n, i + 1, externalFiles.size());
+            }
+
+            if (isAborted()) return;
+
+            // Restore database
+            LOG.debug("Importing database");
+            updateProgress(75, R.string.backup_restore_importing_database);
+            try (DBHandler dbHandler = GBApplication.acquireDB()) {
+                final DBHelper helper = new DBHelper(getContext());
+                final SQLiteOpenHelper sqLiteOpenHelper = dbHandler.getHelper();
+                try (InputStream databaseInputStream = zipFile.getInputStream(zipFile.getEntry(DATABASE_FILENAME))) {
+                    helper.importDB(dbHandler, databaseInputStream);
+                    helper.validateDB(sqLiteOpenHelper);
+                }
+            }
+
+            if (isAborted()) return;
+
+            // Restore preferences
+            LOG.debug("Importing global preferences");
+            updateProgress(85, R.string.backup_restore_importing_preferences);
+            try (InputStream globalPrefsInputStream = zipFile.getInputStream(zipFile.getEntry(PREFS_GLOBAL_FILENAME))) {
+                final SharedPreferences globalPreferences = GBApplication.getPrefs().getPreferences();
+
+                final JsonBackupPreferences jsonBackupPreferences = JsonBackupPreferences.fromJson(globalPrefsInputStream);
+                if (!jsonBackupPreferences.importInto(globalPreferences)) {
+                    LOG.warn("Global preferences were not commited");
+                }
+
+                // We may be restoring from different versions - reset the changelog version
+                globalPreferences.edit()
+                        .putInt("ckChangeLog_last_version_code", BuildConfig.VERSION_CODE)
+                        .apply();
+            }
+
+            if (isAborted()) return;
+
+            // At this point we already restored the db, so we can list the devices from there
+            try (DBHandler dbHandler = GBApplication.acquireDB()) {
+                final List<Device> activeDevices = DBHelper.getActiveDevices(dbHandler.getDaoSession());
+                for (Device dbDevice : activeDevices) {
+                    LOG.debug("Importing device preferences for {}", dbDevice.getIdentifier());
+                    final SharedPreferences devicePrefs = GBApplication.getDeviceSpecificSharedPrefs(dbDevice.getIdentifier());
+                    if (devicePrefs != null && !isAborted()) {
+                        final ZipEntry devicePrefsZipEntry = zipFile.getEntry(String.format(Locale.ROOT, PREFS_DEVICE_FILENAME, dbDevice.getIdentifier()));
+                        if (devicePrefsZipEntry == null) {
+                            continue;
+                        }
+                        try (InputStream devicePrefsInputStream = zipFile.getInputStream(devicePrefsZipEntry)) {
+                            final JsonBackupPreferences jsonBackupPreferences = JsonBackupPreferences.fromJson(devicePrefsInputStream);
+                            if (!jsonBackupPreferences.importInto(devicePrefs)) {
+                                LOG.warn("Device preferences for {} were not commited", dbDevice.getIdentifier());
+                            }
+                        }
+                    }
+                }
+            }
+
+            if (isAborted()) return;
+
+            LOG.info("Import complete");
+
+            if (!failedFiles.isEmpty()) {
+                final String failedFilesListMessage = "- " + String.join("\n- ", failedFiles);
+                onSuccess(getContext().getString(R.string.backup_restore_warning_files, failedFiles.size(), failedFilesListMessage));
+            } else {
+                onSuccess(null);
+            }
+        } catch (final Exception e) {
+            LOG.error("Import failed", e);
+            onFailure(e.getLocalizedMessage());
+        }
+    }
+
+    private ZipFile getZipFromUri(final Context context, final Uri uri) throws IOException {
+        final File tmpFile = File.createTempFile("gb-backup-zip-import", "zip", context.getCacheDir());
+        tmpFile.deleteOnExit();
+
+        try (InputStream inputStream = context.getContentResolver().openInputStream(uri);
+             FileOutputStream outputStream = new FileOutputStream(tmpFile)) {
+            if (inputStream == null) {
+                throw new IOException("Failed to get input stream");
+            }
+
+            int len;
+            while ((len = inputStream.read(copyBuffer)) != -1) {
+                outputStream.write(copyBuffer, 0, len);
+            }
+        }
+
+        return new ZipFile(tmpFile);
+    }
+
+    private static void validateBackupFile(final ZipFile zipFile) throws IOException {
+        final ZipEntry metadataEntry = zipFile.getEntry(METADATA_FILENAME);
+        if (metadataEntry == null) {
+            throw new IOException("Zip file has no metadata");
+        }
+        final InputStream inputStream = zipFile.getInputStream(metadataEntry);
+        final ZipBackupMetadata zipBackupMetadata = GSON.fromJson(
+                new InputStreamReader(inputStream, StandardCharsets.UTF_8),
+                ZipBackupMetadata.class
+        );
+
+        if (zipBackupMetadata.getBackupVersion() > VERSION) {
+            throw new IOException("Unsupported backup version " + zipBackupMetadata.getBackupVersion());
+        }
+
+        final ZipEntry databaseEntry = zipFile.getEntry(DATABASE_FILENAME);
+        if (databaseEntry == null) {
+            throw new IOException("Zip file has no database");
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/backup/ZipBackupMetadata.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/backup/ZipBackupMetadata.java
new file mode 100644
index 0000000..3563e6c
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/backup/ZipBackupMetadata.java
@@ -0,0 +1,60 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.util.backup;
+
+import java.util.Date;
+
+public class ZipBackupMetadata {
+    private final String appId;
+    private final String appVersionName;
+    private final int appVersionCode;
+
+    private final int backupVersion;
+    private final Date backupDate;
+
+    public ZipBackupMetadata(final String appId,
+                             final String appVersionName,
+                             final int appVersionCode,
+                             final int backupVersion,
+                             final Date backupDate) {
+        this.appId = appId;
+        this.appVersionName = appVersionName;
+        this.appVersionCode = appVersionCode;
+        this.backupVersion = backupVersion;
+        this.backupDate = backupDate;
+    }
+
+    public String getAppId() {
+        return appId;
+    }
+
+    public String getAppVersionName() {
+        return appVersionName;
+    }
+
+    public int getAppVersionCode() {
+        return appVersionCode;
+    }
+
+    public int getBackupVersion() {
+        return backupVersion;
+    }
+
+    public Date getBackupDate() {
+        return backupDate;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/calendar/CalendarEvent.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/calendar/CalendarEvent.java
new file mode 100644
index 0000000..3ab7c4b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/calendar/CalendarEvent.java
@@ -0,0 +1,189 @@
+/*  Copyright (C) 2022-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.util.calendar;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+public class CalendarEvent {
+    private final long begin;
+    private final long end;
+    private final long id;
+    private final String title;
+    private final String description;
+    private final String location;
+    private final String calName;
+    private final String calAccountName;
+    private final String calAccountType;
+    private final String calendarId;
+    private final String organizer;
+    private final int color;
+    private final boolean allDay;
+    private final String rrule;
+    private List<Long> remindersAbsoluteTs = new ArrayList<>();
+
+    public CalendarEvent(long begin, long end, long id, String title, String description, String location, String calName, String calAccountName, int color, boolean allDay, String organizer, String calAccountType, String calendarId, String rrule) {
+        this.begin = begin;
+        this.end = end;
+        this.id = id;
+        this.title = title;
+        this.description = description;
+        this.location = location;
+        this.calName = calName;
+        this.calAccountName = calAccountName;
+        this.color = color;
+        this.allDay = allDay;
+        this.organizer = organizer;
+        this.calAccountType = calAccountType;
+        this.calendarId = calendarId;
+        this.rrule = rrule;
+    }
+
+    public List<Long> getRemindersAbsoluteTs() {
+        return remindersAbsoluteTs;
+    }
+
+    public void setRemindersAbsoluteTs(List<Long> remindersAbsoluteTs) {
+        this.remindersAbsoluteTs = remindersAbsoluteTs;
+    }
+
+    public long getBegin() {
+        return begin;
+    }
+
+    public int getBeginSeconds() {
+        return (int) (begin / 1000);
+    }
+
+    public long getEnd() {
+        return end;
+    }
+
+    public int getEndSeconds() {
+        return (int) (end / 1000);
+    }
+
+    public long getDuration() {
+        return end - begin;
+    }
+
+    public int getDurationSeconds() {
+        return (int) ((getDuration()) / 1000);
+    }
+
+    public short getDurationMinutes() {
+        return (short) (getDurationSeconds() / 60);
+    }
+
+
+    public long getId() {
+        return id;
+    }
+
+    public String getTitle() {
+        return title;
+    }
+
+    public String getOrganizer() {
+        return organizer;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public String getLocation() {
+        return location;
+    }
+
+    public String getCalName() {
+        return calName;
+    }
+
+    public String getCalAccountName() {
+        return calAccountName;
+    }
+
+    public String getUniqueCalName() {
+        return getCalAccountName() + '/' + getCalName();
+    }
+
+    public int getColor() {
+        return color;
+    }
+
+    public boolean isAllDay() {
+        return allDay;
+    }
+
+    public String getCalAccountType() {
+        return calAccountType;
+    }
+
+    public String getCalendarId() {
+        return calendarId;
+    }
+
+    public String getRrule() {
+        return rrule;
+    }
+
+    @Override
+    public boolean equals(Object other) {
+        if (other instanceof CalendarEvent) {
+            CalendarEvent e = (CalendarEvent) other;
+            return (this.getId() == e.getId()) &&
+                    Objects.equals(this.getTitle(), e.getTitle()) &&
+                    (this.getBegin() == e.getBegin()) &&
+                    Objects.equals(this.getLocation(), e.getLocation()) &&
+                    Objects.equals(this.getDescription(), e.getDescription()) &&
+                    (this.getEnd() == e.getEnd()) &&
+                    Objects.equals(this.getCalName(), e.getCalName()) &&
+                    Objects.equals(this.getCalAccountName(), e.getCalAccountName()) &&
+                    (this.getColor() == e.getColor()) &&
+                    (this.isAllDay() == e.isAllDay()) &&
+                    Objects.equals(this.getOrganizer(), e.getOrganizer()) &&
+                    Objects.equals(this.getRemindersAbsoluteTs(), e.getRemindersAbsoluteTs()) &&
+                    Objects.equals(this.getCalAccountType(), e.getCalAccountType()) &&
+                    Objects.equals(this.getCalendarId(), e.getCalendarId()) &&
+                    Objects.equals(this.getRrule(), e.getRrule());
+        } else {
+            return false;
+        }
+    }
+
+    @Override
+    public int hashCode() {
+        int result = (int) id;
+        result = 31 * result + Objects.hash(title);
+        result = 31 * result + Long.valueOf(begin).hashCode();
+        result = 31 * result + Objects.hash(location);
+        result = 31 * result + Objects.hash(description);
+        result = 31 * result + Long.valueOf(end).hashCode();
+        result = 31 * result + Objects.hash(calName);
+        result = 31 * result + Objects.hash(calAccountName);
+        result = 31 * result + Integer.valueOf(color).hashCode();
+        result = 31 * result + Boolean.valueOf(allDay).hashCode();
+        result = 31 * result + Objects.hash(organizer);
+        result = 31 * result + Objects.hash(remindersAbsoluteTs);
+        result = 31 * result + Objects.hash(calAccountType);
+        result = 31 * result + Objects.hash(calendarId);
+        result = 31 * result + Objects.hash(rrule);
+        return result;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/calendar/CalendarManager.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/calendar/CalendarManager.java
new file mode 100644
index 0000000..c1823dc
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/calendar/CalendarManager.java
@@ -0,0 +1,326 @@
+/*  Copyright (C) 2022-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.util.calendar;
+
+import android.content.ContentUris;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.CalendarContract;
+import android.provider.CalendarContract.Instances;
+import android.provider.ContactsContract;
+import android.text.format.Time;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+import java.util.TimeZone;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.GBPrefs;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+public class CalendarManager {
+    private static final Logger LOG = LoggerFactory.getLogger(CalendarManager.class);
+
+    // needed for pebble: time, duration, layout, reminders, actions
+    // layout: type, title, subtitle, body (max 512), tinyIcon, smallIcon, largeIcon
+    //further: primaryColor, secondaryColor, backgroundColor, headings, paragraphs, lastUpdated
+    // taken from: https://developer.getpebble.com/guides/timeline/pin-structure/
+
+    // needed for MiBand:
+    // time
+
+    private static final String[] EVENT_INSTANCE_PROJECTION = new String[]{
+            Instances._ID,
+
+            Instances.BEGIN,
+            Instances.END,
+            Instances.DURATION,
+            Instances.TITLE,
+            Instances.DESCRIPTION,
+            Instances.EVENT_LOCATION,
+            Instances.ORGANIZER,
+            Instances.CALENDAR_DISPLAY_NAME,
+            CalendarContract.Calendars.ACCOUNT_NAME,
+            Instances.CALENDAR_COLOR,
+            Instances.ALL_DAY,
+            Instances.EVENT_ID, //needed for reminders
+            CalendarContract.Calendars.ACCOUNT_TYPE,
+            Instances.CALENDAR_ID,
+            Instances.RRULE
+    };
+
+    private final String deviceAddress;
+    private final Context mContext;
+
+    public CalendarManager(final Context context, final String deviceAddress) {
+        this.mContext = context;
+        this.deviceAddress = deviceAddress;
+
+        loadCalendarsBlackList();
+    }
+
+    public List<CalendarEvent> getCalendarEventList() {
+        loadCalendarsBlackList();
+
+        final SharedPreferences sharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(deviceAddress);
+        final Prefs prefs = new Prefs(sharedPrefs);
+        int lookaheadDays = Math.max(1, prefs.getInt("calendar_lookahead_days", 7));
+        final List<CalendarEvent> calendarEventList = getCalendarEvents(lookaheadDays);
+        if (sharedPrefs.getBoolean("sync_birthdays", false)) {
+            calendarEventList.addAll(getBirthdays(lookaheadDays));
+            calendarEventList.sort(Comparator.comparingInt(CalendarEvent::getBeginSeconds));
+        }
+        return calendarEventList;
+    }
+
+    public List<CalendarEvent> getCalendarEvents(final int lookaheadDays) {
+        final List<CalendarEvent> calendarEventList = new ArrayList<>();
+
+        Calendar cal = GregorianCalendar.getInstance();
+        long dtStart = cal.getTimeInMillis();
+        cal.add(Calendar.DATE, lookaheadDays);
+        long dtEnd = cal.getTimeInMillis();
+
+        Uri.Builder eventsUriBuilder = Instances.CONTENT_URI.buildUpon();
+        ContentUris.appendId(eventsUriBuilder, dtStart);
+        ContentUris.appendId(eventsUriBuilder, dtEnd);
+        Uri eventsUri = eventsUriBuilder.build();
+
+        try (Cursor evtCursor = mContext.getContentResolver().query(eventsUri, EVENT_INSTANCE_PROJECTION, null, null, Instances.BEGIN + " ASC")) {
+            if (evtCursor == null || evtCursor.getCount() == 0) {
+                return calendarEventList;
+            }
+            while (evtCursor.moveToNext()) {
+                long start = evtCursor.getLong(evtCursor.getColumnIndexOrThrow(Instances.BEGIN));
+                long end = evtCursor.getLong(evtCursor.getColumnIndexOrThrow(Instances.END));
+                if (end == 0) {
+                    LOG.info("no end time, will parse duration string");
+                    Time time = new Time(); //FIXME: deprecated FTW
+                    time.parse(evtCursor.getString(evtCursor.getColumnIndexOrThrow(Instances.DURATION)));
+                    end = start + time.toMillis(false);
+                }
+
+                CalendarEvent calEvent = new CalendarEvent(
+                        start,
+                        end,
+                        evtCursor.getLong(evtCursor.getColumnIndexOrThrow(Instances._ID)),
+                        evtCursor.getString(evtCursor.getColumnIndexOrThrow(Instances.TITLE)),
+                        evtCursor.getString(evtCursor.getColumnIndexOrThrow(Instances.DESCRIPTION)),
+                        evtCursor.getString(evtCursor.getColumnIndexOrThrow(Instances.EVENT_LOCATION)),
+                        evtCursor.getString(evtCursor.getColumnIndexOrThrow(Instances.CALENDAR_DISPLAY_NAME)),
+                        evtCursor.getString(evtCursor.getColumnIndexOrThrow(CalendarContract.Calendars.ACCOUNT_NAME)),
+                        evtCursor.getInt(evtCursor.getColumnIndexOrThrow(Instances.CALENDAR_COLOR)),
+                        !evtCursor.getString(evtCursor.getColumnIndexOrThrow(Instances.ALL_DAY)).equals("0"),
+                        evtCursor.getString(evtCursor.getColumnIndexOrThrow(Instances.ORGANIZER)),
+                        evtCursor.getString(evtCursor.getColumnIndexOrThrow(CalendarContract.Calendars.ACCOUNT_TYPE)),
+                        evtCursor.getString(evtCursor.getColumnIndexOrThrow(Instances.CALENDAR_ID)),
+                        evtCursor.getString(evtCursor.getColumnIndexOrThrow(Instances.RRULE))
+                );
+
+                // Query reminders for this event
+                final Cursor reminderCursor = mContext.getContentResolver().query(
+                        CalendarContract.Reminders.CONTENT_URI,
+                        null,
+                        CalendarContract.Reminders.EVENT_ID + " = ?",
+                        new String[]{String.valueOf(evtCursor.getLong(evtCursor.getColumnIndexOrThrow(Instances.EVENT_ID)))},
+                        null
+                );
+
+                if (reminderCursor != null && reminderCursor.getCount() > 0) {
+                    final List<Long> reminders = new ArrayList<>();
+                    while (reminderCursor.moveToNext()) {
+                        int minutes = reminderCursor.getInt(reminderCursor.getColumnIndexOrThrow(CalendarContract.Reminders.MINUTES));
+                        int method = reminderCursor.getInt(reminderCursor.getColumnIndexOrThrow(CalendarContract.Reminders.METHOD));
+                        LOG.debug("Reminder Method: {}, Minutes: {}", method, minutes);
+
+                        if (method == 1) //METHOD_ALERT
+                            reminders.add(calEvent.getBegin() - minutes * 60 * 1000L);
+
+                    }
+                    reminderCursor.close();
+
+                    calEvent.setRemindersAbsoluteTs(reminders);
+                }
+
+                if (!calendarIsBlacklisted(calEvent.getUniqueCalName())) {
+                    calendarEventList.add(calEvent);
+                } else {
+                    LOG.debug("calendar {} skipped because it's blacklisted", calEvent.getUniqueCalName());
+                }
+            }
+            return calendarEventList;
+        } catch (final Exception e) {
+            LOG.error("could not query calendar, permission denied?", e);
+            return calendarEventList;
+        }
+    }
+
+    public List<CalendarEvent> getBirthdays(final int lookaheadDays) {
+        final String[] projection = new String[]{
+                ContactsContract.CommonDataKinds.Event.CONTACT_ID,
+                ContactsContract.CommonDataKinds.Event.START_DATE,
+                ContactsContract.CommonDataKinds.Event.DISPLAY_NAME
+        };
+        final String selection = ContactsContract.Data.MIMETYPE + " = ? AND " +
+                ContactsContract.CommonDataKinds.Event.TYPE + " = ?";
+        final String[] selectionArgs = new String[]{
+                ContactsContract.CommonDataKinds.Event.CONTENT_ITEM_TYPE,
+                String.valueOf(ContactsContract.CommonDataKinds.Event.TYPE_BIRTHDAY)
+        };
+        final List<CalendarEvent> birthdays = new LinkedList<>();
+        final LocalDate maxDate = LocalDate.now().plusDays(lookaheadDays);
+
+        try (Cursor birthdayCursor = mContext.getContentResolver().query(ContactsContract.Data.CONTENT_URI, projection, selection, selectionArgs, ContactsContract.CommonDataKinds.Event.START_DATE + " ASC")) {
+            if (birthdayCursor == null || birthdayCursor.getCount() == 0) {
+                return birthdays;
+            }
+            while (birthdayCursor.moveToNext()) {
+                final String contactId = birthdayCursor.getString(birthdayCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Event.CONTACT_ID));
+                final String birthdayStr = birthdayCursor.getString(birthdayCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Event.START_DATE));
+                final String displayName = birthdayCursor.getString(birthdayCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Event.DISPLAY_NAME));
+                final LocalDate birthday = parseBirthday(birthdayStr);
+                if (birthday == null || birthday.isAfter(maxDate)) {
+                    continue;
+                }
+
+                // Follow the same logic as CalendarContract - all day events have the start
+                // timestamp at the UTC midnight boundary
+                final long startTimestampUtc = DateTimeUtils.dayStartUtc(birthday).getTime();
+
+                birthdays.add(new CalendarEvent(
+                        startTimestampUtc,
+                        startTimestampUtc + 86400000L - 1L,
+                        contactId.hashCode(),
+                        mContext.getString(R.string.contact_birthday, displayName),
+                        null,
+                        null,
+                        mContext.getString(R.string.birthdays),
+                        mContext.getString(R.string.pref_contacts_title),
+                        0,
+                        true,
+                        null,
+                        CalendarContract.ACCOUNT_TYPE_LOCAL,
+                        null,
+                        null
+                ));
+            }
+        } catch (final Exception e) {
+            LOG.error("could not query birthdays, permission denied?", e);
+        }
+        return birthdays;
+    }
+
+    private LocalDate parseBirthday(final String birthdayStr) {
+        final LocalDate birthday;
+        final LocalDate now = LocalDate.now();
+
+        try {
+            if (birthdayStr.startsWith("--")) {
+                // MM-DD
+                final String monthDay = birthdayStr.substring(2);
+                birthday = LocalDate.parse(now.getYear() + "-" + monthDay, DateTimeFormatter.ofPattern("yyyy-MM-dd"));
+            } else {
+                birthday = LocalDate.parse(birthdayStr, DateTimeFormatter.ISO_LOCAL_DATE).withYear(now.getYear());
+            }
+        } catch (final DateTimeParseException e) {
+            LOG.error("Failed to parse birthday {}", birthdayStr, e);
+            return null;
+        }
+
+        if (birthday.isAfter(now) || birthday.isEqual(now)) {
+            return birthday;
+        }
+
+        return birthday.plusYears(1);
+    }
+
+    private static HashSet<String> calendars_blacklist = null;
+
+    public boolean calendarIsBlacklisted(String calendarUniqueName) {
+        if (calendars_blacklist == null) {
+            LOG.warn("calendarIsBlacklisted: calendars_blacklist is null!");
+        }
+        return calendars_blacklist != null && calendars_blacklist.contains(calendarUniqueName);
+    }
+
+    public void setCalendarsBlackList(Set<String> calendarNames) {
+        if (calendarNames == null) {
+            LOG.info("Set null apps_notification_blacklist");
+            calendars_blacklist = new HashSet<>();
+        } else {
+            calendars_blacklist = new HashSet<>(calendarNames);
+        }
+        LOG.info("New calendars_blacklist has {} entries", calendars_blacklist.size());
+        saveCalendarsBlackList();
+    }
+
+    public void addCalendarToBlacklist(String calendarUniqueName) {
+        if (calendars_blacklist.add(calendarUniqueName)) {
+            LOG.info("Blacklisted calendar {}", calendarUniqueName);
+            saveCalendarsBlackList();
+        } else {
+            LOG.warn("Calendar {} already blacklisted!", calendarUniqueName);
+        }
+    }
+
+    public void removeFromCalendarBlacklist(String calendarUniqueName) {
+        calendars_blacklist.remove(calendarUniqueName);
+        LOG.info("Unblacklisted calendar {}", calendarUniqueName);
+        saveCalendarsBlackList();
+    }
+
+    private void loadCalendarsBlackList() {
+        SharedPreferences sharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(deviceAddress);
+
+        LOG.info("Loading calendars_blacklist");
+        calendars_blacklist = (HashSet<String>) sharedPrefs.getStringSet(GBPrefs.CALENDAR_BLACKLIST, null);
+        if (calendars_blacklist == null) {
+            calendars_blacklist = new HashSet<>();
+        }
+        LOG.info("Loaded calendars_blacklist has {} entries", calendars_blacklist.size());
+    }
+
+    private void saveCalendarsBlackList() {
+        final SharedPreferences sharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(deviceAddress);
+
+        LOG.info("Saving calendars_blacklist with {} entries", calendars_blacklist.size());
+        SharedPreferences.Editor editor = sharedPrefs.edit();
+        if (calendars_blacklist.isEmpty()) {
+            editor.putStringSet(GBPrefs.CALENDAR_BLACKLIST, null);
+        } else {
+            Prefs.putStringSet(editor, GBPrefs.CALENDAR_BLACKLIST, calendars_blacklist);
+        }
+        editor.apply();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/dialogs/MaterialDialogFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/dialogs/MaterialDialogFragment.java
new file mode 100644
index 0000000..e4db48e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/dialogs/MaterialDialogFragment.java
@@ -0,0 +1,45 @@
+/*  Copyright (C) 2023-2024 Arjan Schrijver
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.util.dialogs;
+
+import android.app.Dialog;
+import android.os.Bundle;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.fragment.app.DialogFragment;
+
+import com.google.android.material.dialog.MaterialAlertDialogBuilder;
+
+public class MaterialDialogFragment extends DialogFragment {
+    View theDialogView;
+
+    @NonNull
+    @Override
+    public Dialog onCreateDialog(Bundle savedInstanceState) {
+        MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(requireActivity());
+        theDialogView = onCreateView(getLayoutInflater(), null, savedInstanceState);
+        builder.setView(theDialogView);
+
+        return builder.create();
+    }
+
+    @Override
+    public View getView() {
+        return theDialogView;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/dialogs/MaterialEditTextPreferenceDialogFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/dialogs/MaterialEditTextPreferenceDialogFragment.java
new file mode 100644
index 0000000..b15029b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/dialogs/MaterialEditTextPreferenceDialogFragment.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * This file is part of and modified for Gadgetbridge.
+ */
+
+package nodomain.freeyourgadget.gadgetbridge.util.dialogs;
+
+import android.os.Bundle;
+import android.view.View;
+import android.widget.EditText;
+
+import androidx.annotation.NonNull;
+import androidx.preference.EditTextPreference;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+public class MaterialEditTextPreferenceDialogFragment extends MaterialPreferenceDialogFragment {
+    private static final Logger LOG = LoggerFactory.getLogger(MaterialEditTextPreferenceDialogFragment.class);
+
+    private static final String SAVE_STATE_TEXT = "EditTextPreferenceDialogFragment.text";
+
+    private EditText mEditText;
+
+    private CharSequence mText;
+
+    public static MaterialEditTextPreferenceDialogFragment newInstance(String key) {
+        final MaterialEditTextPreferenceDialogFragment
+                fragment = new MaterialEditTextPreferenceDialogFragment();
+        final Bundle b = new Bundle(1);
+        b.putString(ARG_KEY, key);
+        fragment.setArguments(b);
+        return fragment;
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        if (savedInstanceState == null) {
+            mText = getEditTextPreference().getText();
+        } else {
+            mText = savedInstanceState.getCharSequence(SAVE_STATE_TEXT);
+        }
+    }
+
+    @Override
+    public void onSaveInstanceState(@NonNull Bundle outState) {
+        super.onSaveInstanceState(outState);
+        outState.putCharSequence(SAVE_STATE_TEXT, mText);
+    }
+
+    @Override
+    protected void onBindDialogView(View view) {
+        super.onBindDialogView(view);
+
+        mEditText = view.findViewById(android.R.id.edit);
+
+        if (mEditText == null) {
+            throw new IllegalStateException("Dialog view must contain an EditText with id" +
+                    " @android:id/edit");
+        }
+
+        mEditText.requestFocus();
+        mEditText.setText(mText);
+        // Place cursor at the end
+        mEditText.setSelection(mEditText.getText().length());
+        // Use reflection to be able to call EditTextPreference.getOnBindEditTextListener(), which is package-private
+        Method getOnBindEditTextListener = null;
+        try {
+            getOnBindEditTextListener = EditTextPreference.class.getDeclaredMethod("getOnBindEditTextListener");
+            getOnBindEditTextListener.setAccessible(true);
+            EditTextPreference.OnBindEditTextListener listener = (EditTextPreference.OnBindEditTextListener) getOnBindEditTextListener.invoke(getEditTextPreference());
+            if (listener != null) {
+                listener.onBindEditText(mEditText);
+            }
+        } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
+            LOG.error("Error when using reflection to access EditTextPreference.getOnBindEditTextListener()", e);
+        }
+    }
+
+    private EditTextPreference getEditTextPreference() {
+        return (EditTextPreference) getPreference();
+    }
+
+    /** @hide */
+    @Override
+    protected boolean needInputMethod() {
+        // We want the input method to show, if possible, when dialog is displayed
+        return true;
+    }
+
+    @Override
+    public void onDialogClosed(boolean positiveResult) {
+        if (positiveResult) {
+            String value = mEditText.getText().toString();
+            final EditTextPreference preference = getEditTextPreference();
+            if (preference.callChangeListener(value)) {
+                preference.setText(value);
+            }
+        }
+    }
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/dialogs/MaterialListPreferenceDialogFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/dialogs/MaterialListPreferenceDialogFragment.java
new file mode 100644
index 0000000..04556b9
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/dialogs/MaterialListPreferenceDialogFragment.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * This file is part of and modified for Gadgetbridge.
+ */
+
+package nodomain.freeyourgadget.gadgetbridge.util.dialogs;
+
+import android.content.DialogInterface;
+import android.os.Bundle;
+
+import androidx.annotation.NonNull;
+import androidx.preference.ListPreference;
+
+import com.google.android.material.dialog.MaterialAlertDialogBuilder;
+
+public class MaterialListPreferenceDialogFragment extends MaterialPreferenceDialogFragment {
+
+    private static final String SAVE_STATE_INDEX = "ListPreferenceDialogFragment.index";
+    private static final String SAVE_STATE_ENTRIES = "ListPreferenceDialogFragment.entries";
+    private static final String SAVE_STATE_ENTRY_VALUES =
+            "ListPreferenceDialogFragment.entryValues";
+
+    @SuppressWarnings("WeakerAccess") /* synthetic access */
+            int mClickedDialogEntryIndex;
+    private CharSequence[] mEntries;
+    private CharSequence[] mEntryValues;
+
+    public static MaterialListPreferenceDialogFragment newInstance(String key) {
+        final MaterialListPreferenceDialogFragment fragment =
+                new MaterialListPreferenceDialogFragment();
+        final Bundle b = new Bundle(1);
+        b.putString(ARG_KEY, key);
+        fragment.setArguments(b);
+        return fragment;
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        if (savedInstanceState == null) {
+            final ListPreference preference = getListPreference();
+
+            if (preference.getEntries() == null || preference.getEntryValues() == null) {
+                throw new IllegalStateException(
+                        "ListPreference requires an entries array and an entryValues array.");
+            }
+
+            mClickedDialogEntryIndex = preference.findIndexOfValue(preference.getValue());
+            mEntries = preference.getEntries();
+            mEntryValues = preference.getEntryValues();
+        } else {
+            mClickedDialogEntryIndex = savedInstanceState.getInt(SAVE_STATE_INDEX, 0);
+            mEntries = savedInstanceState.getCharSequenceArray(SAVE_STATE_ENTRIES);
+            mEntryValues = savedInstanceState.getCharSequenceArray(SAVE_STATE_ENTRY_VALUES);
+        }
+    }
+
+    @Override
+    public void onSaveInstanceState(@NonNull Bundle outState) {
+        super.onSaveInstanceState(outState);
+        outState.putInt(SAVE_STATE_INDEX, mClickedDialogEntryIndex);
+        outState.putCharSequenceArray(SAVE_STATE_ENTRIES, mEntries);
+        outState.putCharSequenceArray(SAVE_STATE_ENTRY_VALUES, mEntryValues);
+    }
+
+    private ListPreference getListPreference() {
+        return (ListPreference) getPreference();
+    }
+
+    @Override
+    protected void onPrepareDialogBuilder(MaterialAlertDialogBuilder builder) {
+        super.onPrepareDialogBuilder(builder);
+
+        builder.setSingleChoiceItems(mEntries, mClickedDialogEntryIndex,
+                new DialogInterface.OnClickListener() {
+                    @Override
+                    public void onClick(DialogInterface dialog, int which) {
+                        mClickedDialogEntryIndex = which;
+
+                        // Clicking on an item simulates the positive button click, and dismisses
+                        // the dialog.
+                        MaterialListPreferenceDialogFragment.this.onClick(dialog,
+                                DialogInterface.BUTTON_POSITIVE);
+                        dialog.dismiss();
+                    }
+                });
+
+        // The typical interaction for list-based dialogs is to have click-on-an-item dismiss the
+        // dialog instead of the user having to press 'Ok'.
+        builder.setPositiveButton(null, null);
+    }
+
+    @Override
+    public void onDialogClosed(boolean positiveResult) {
+        if (positiveResult && mClickedDialogEntryIndex >= 0) {
+            String value = mEntryValues[mClickedDialogEntryIndex].toString();
+            final ListPreference preference = getListPreference();
+            if (preference.callChangeListener(value)) {
+                preference.setValue(value);
+            }
+        }
+    }
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/dialogs/MaterialMultiSelectListPreferenceDialogFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/dialogs/MaterialMultiSelectListPreferenceDialogFragment.java
new file mode 100644
index 0000000..4bc04fa
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/dialogs/MaterialMultiSelectListPreferenceDialogFragment.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * This file is part of and modified for Gadgetbridge.
+ */
+
+package nodomain.freeyourgadget.gadgetbridge.util.dialogs;
+
+import android.content.DialogInterface;
+import android.os.Bundle;
+
+import androidx.annotation.NonNull;
+import androidx.preference.MultiSelectListPreference;
+
+import com.google.android.material.dialog.MaterialAlertDialogBuilder;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Set;
+
+public class MaterialMultiSelectListPreferenceDialogFragment extends MaterialPreferenceDialogFragment {
+
+    private static final String SAVE_STATE_VALUES =
+            "MultiSelectListPreferenceDialogFragmentCompat.values";
+    private static final String SAVE_STATE_CHANGED =
+            "MultiSelectListPreferenceDialogFragmentCompat.changed";
+    private static final String SAVE_STATE_ENTRIES =
+            "MultiSelectListPreferenceDialogFragmentCompat.entries";
+    private static final String SAVE_STATE_ENTRY_VALUES =
+            "MultiSelectListPreferenceDialogFragmentCompat.entryValues";
+
+    @SuppressWarnings("WeakerAccess") /* synthetic access */
+            Set<String> mNewValues = new HashSet<>();
+    @SuppressWarnings("WeakerAccess") /* synthetic access */
+            boolean mPreferenceChanged;
+    @SuppressWarnings("WeakerAccess") /* synthetic access */
+            CharSequence[] mEntries;
+    @SuppressWarnings("WeakerAccess") /* synthetic access */
+            CharSequence[] mEntryValues;
+
+    public static MaterialMultiSelectListPreferenceDialogFragment newInstance(String key) {
+        final MaterialMultiSelectListPreferenceDialogFragment fragment =
+                new MaterialMultiSelectListPreferenceDialogFragment();
+        final Bundle b = new Bundle(1);
+        b.putString(ARG_KEY, key);
+        fragment.setArguments(b);
+        return fragment;
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        if (savedInstanceState == null) {
+            final MultiSelectListPreference preference = getListPreference();
+
+            if (preference.getEntries() == null || preference.getEntryValues() == null) {
+                throw new IllegalStateException(
+                        "MultiSelectListPreference requires an entries array and " +
+                                "an entryValues array.");
+            }
+
+            mNewValues.clear();
+            mNewValues.addAll(preference.getValues());
+            mPreferenceChanged = false;
+            mEntries = preference.getEntries();
+            mEntryValues = preference.getEntryValues();
+        } else {
+            mNewValues.clear();
+            mNewValues.addAll(savedInstanceState.getStringArrayList(SAVE_STATE_VALUES));
+            mPreferenceChanged = savedInstanceState.getBoolean(SAVE_STATE_CHANGED, false);
+            mEntries = savedInstanceState.getCharSequenceArray(SAVE_STATE_ENTRIES);
+            mEntryValues = savedInstanceState.getCharSequenceArray(SAVE_STATE_ENTRY_VALUES);
+        }
+    }
+
+    @Override
+    public void onSaveInstanceState(@NonNull Bundle outState) {
+        super.onSaveInstanceState(outState);
+        outState.putStringArrayList(SAVE_STATE_VALUES, new ArrayList<>(mNewValues));
+        outState.putBoolean(SAVE_STATE_CHANGED, mPreferenceChanged);
+        outState.putCharSequenceArray(SAVE_STATE_ENTRIES, mEntries);
+        outState.putCharSequenceArray(SAVE_STATE_ENTRY_VALUES, mEntryValues);
+    }
+
+    private MultiSelectListPreference getListPreference() {
+        return (MultiSelectListPreference) getPreference();
+    }
+
+    @Override
+    protected void onPrepareDialogBuilder(MaterialAlertDialogBuilder builder) {
+        super.onPrepareDialogBuilder(builder);
+
+        final int entryCount = mEntryValues.length;
+        final boolean[] checkedItems = new boolean[entryCount];
+        for (int i = 0; i < entryCount; i++) {
+            checkedItems[i] = mNewValues.contains(mEntryValues[i].toString());
+        }
+        builder.setMultiChoiceItems(mEntries, checkedItems,
+                new DialogInterface.OnMultiChoiceClickListener() {
+                    @Override
+                    public void onClick(DialogInterface dialog, int which, boolean isChecked) {
+                        if (isChecked) {
+                            mPreferenceChanged |= mNewValues.add(
+                                    mEntryValues[which].toString());
+                        } else {
+                            mPreferenceChanged |= mNewValues.remove(
+                                    mEntryValues[which].toString());
+                        }
+                    }
+                });
+    }
+
+    @Override
+    public void onDialogClosed(boolean positiveResult) {
+        if (positiveResult && mPreferenceChanged) {
+            final MultiSelectListPreference preference = getListPreference();
+            if (preference.callChangeListener(mNewValues)) {
+                preference.setValues(mNewValues);
+            }
+        }
+        mPreferenceChanged = false;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/dialogs/MaterialPreferenceDialogFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/dialogs/MaterialPreferenceDialogFragment.java
new file mode 100644
index 0000000..9358d60
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/dialogs/MaterialPreferenceDialogFragment.java
@@ -0,0 +1,269 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * This file is part of and modified for Gadgetbridge.
+ */
+
+package nodomain.freeyourgadget.gadgetbridge.util.dialogs;
+
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowManager;
+import android.widget.TextView;
+
+import androidx.annotation.LayoutRes;
+import androidx.annotation.NonNull;
+import androidx.fragment.app.DialogFragment;
+import androidx.fragment.app.Fragment;
+import androidx.preference.DialogPreference;
+
+import com.google.android.material.dialog.MaterialAlertDialogBuilder;
+
+/**
+ * Abstract base class which presents a dialog associated with a {@link DialogPreference}. Since
+ * the preference object may not be available during fragment re-creation, the necessary
+ * information for displaying the dialog is read once during the initial call to
+ * {@link #onCreate(Bundle)} and saved/restored in the saved instance state. Custom subclasses
+ * should also follow this pattern.
+ */
+public abstract class MaterialPreferenceDialogFragment extends DialogFragment implements
+        DialogInterface.OnClickListener {
+
+    protected static final String ARG_KEY = "key";
+
+    private static final String SAVE_STATE_TITLE = "PreferenceDialogFragment.title";
+    private static final String SAVE_STATE_POSITIVE_TEXT = "PreferenceDialogFragment.positiveText";
+    private static final String SAVE_STATE_NEGATIVE_TEXT = "PreferenceDialogFragment.negativeText";
+    private static final String SAVE_STATE_MESSAGE = "PreferenceDialogFragment.message";
+    private static final String SAVE_STATE_LAYOUT = "PreferenceDialogFragment.layout";
+    private static final String SAVE_STATE_ICON = "PreferenceDialogFragment.icon";
+
+    private DialogPreference mPreference;
+
+    private CharSequence mDialogTitle;
+    private CharSequence mPositiveButtonText;
+    private CharSequence mNegativeButtonText;
+    private CharSequence mDialogMessage;
+    private @LayoutRes int mDialogLayoutRes;
+
+    private BitmapDrawable mDialogIcon;
+
+    /** Which button was clicked. */
+    private int mWhichButtonClicked;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        final Fragment rawFragment = getTargetFragment();
+        if (!(rawFragment instanceof DialogPreference.TargetFragment)) {
+            throw new IllegalStateException("Target fragment must implement TargetFragment" +
+                    " interface");
+        }
+
+        final DialogPreference.TargetFragment fragment =
+                (DialogPreference.TargetFragment) rawFragment;
+
+        final String key = getArguments().getString(ARG_KEY);
+        if (savedInstanceState == null) {
+            mPreference = fragment.findPreference(key);
+            mDialogTitle = mPreference.getDialogTitle();
+            mPositiveButtonText = mPreference.getPositiveButtonText();
+            mNegativeButtonText = mPreference.getNegativeButtonText();
+            mDialogMessage = mPreference.getDialogMessage();
+            mDialogLayoutRes = mPreference.getDialogLayoutResource();
+
+            final Drawable icon = mPreference.getDialogIcon();
+            if (icon == null || icon instanceof BitmapDrawable) {
+                mDialogIcon = (BitmapDrawable) icon;
+            } else {
+                final Bitmap bitmap = Bitmap.createBitmap(icon.getIntrinsicWidth(),
+                        icon.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
+                final Canvas canvas = new Canvas(bitmap);
+                icon.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
+                icon.draw(canvas);
+                mDialogIcon = new BitmapDrawable(getResources(), bitmap);
+            }
+        } else {
+            mDialogTitle = savedInstanceState.getCharSequence(SAVE_STATE_TITLE);
+            mPositiveButtonText = savedInstanceState.getCharSequence(SAVE_STATE_POSITIVE_TEXT);
+            mNegativeButtonText = savedInstanceState.getCharSequence(SAVE_STATE_NEGATIVE_TEXT);
+            mDialogMessage = savedInstanceState.getCharSequence(SAVE_STATE_MESSAGE);
+            mDialogLayoutRes = savedInstanceState.getInt(SAVE_STATE_LAYOUT, 0);
+            final Bitmap bitmap = savedInstanceState.getParcelable(SAVE_STATE_ICON);
+            if (bitmap != null) {
+                mDialogIcon = new BitmapDrawable(getResources(), bitmap);
+            }
+        }
+    }
+
+    @Override
+    public void onSaveInstanceState(@NonNull Bundle outState) {
+        super.onSaveInstanceState(outState);
+
+        outState.putCharSequence(SAVE_STATE_TITLE, mDialogTitle);
+        outState.putCharSequence(SAVE_STATE_POSITIVE_TEXT, mPositiveButtonText);
+        outState.putCharSequence(SAVE_STATE_NEGATIVE_TEXT, mNegativeButtonText);
+        outState.putCharSequence(SAVE_STATE_MESSAGE, mDialogMessage);
+        outState.putInt(SAVE_STATE_LAYOUT, mDialogLayoutRes);
+        if (mDialogIcon != null) {
+            outState.putParcelable(SAVE_STATE_ICON, mDialogIcon.getBitmap());
+        }
+    }
+
+    @Override
+    public @NonNull
+    Dialog onCreateDialog(Bundle savedInstanceState) {
+        final Context context = getActivity();
+        mWhichButtonClicked = DialogInterface.BUTTON_NEGATIVE;
+
+        final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(context)
+                .setTitle(mDialogTitle)
+                .setIcon(mDialogIcon)
+                .setPositiveButton(mPositiveButtonText, this)
+                .setNegativeButton(mNegativeButtonText, this);
+
+        View contentView = onCreateDialogView(context);
+        if (contentView != null) {
+            onBindDialogView(contentView);
+            builder.setView(contentView);
+        } else {
+            builder.setMessage(mDialogMessage);
+        }
+
+        onPrepareDialogBuilder(builder);
+
+        // Create the dialog
+        final Dialog dialog = builder.create();
+        if (needInputMethod()) {
+            requestInputMethod(dialog);
+        }
+
+        return dialog;
+    }
+
+    /**
+     * Get the preference that requested this dialog. Available after {@link #onCreate(Bundle)} has
+     * been called on the {@link PreferenceFragmentCompat} which launched this dialog.
+     *
+     * @return The {@link DialogPreference} associated with this dialog
+     */
+    public DialogPreference getPreference() {
+        if (mPreference == null) {
+            final String key = getArguments().getString(ARG_KEY);
+            final DialogPreference.TargetFragment fragment =
+                    (DialogPreference.TargetFragment) getTargetFragment();
+            mPreference = fragment.findPreference(key);
+        }
+        return mPreference;
+    }
+
+    /**
+     * Prepares the dialog builder to be shown when the preference is clicked.
+     * Use this to set custom properties on the dialog.
+     *
+     * <p>Do not {@link MaterialAlertDialogBuilder#create()} or {@link MaterialAlertDialogBuilder#show()}.
+     */
+    protected void onPrepareDialogBuilder(MaterialAlertDialogBuilder builder) {}
+
+    /**
+     * Returns whether the preference needs to display a soft input method when the dialog is
+     * displayed. Default is false. Subclasses should override this method if they need the soft
+     * input method brought up automatically.
+     *
+     * <p>Note: If your application targets P or above, ensure your subclass manually requests
+     * focus (ideally in {@link #onBindDialogView(View)}) for the input field in order to
+     * correctly attach the input method to the field.
+     *
+     * @hide
+     */
+    protected boolean needInputMethod() {
+        return false;
+    }
+
+    /**
+     * Sets the required flags on the dialog window to enable input method window to show up.
+     */
+    private void requestInputMethod(Dialog dialog) {
+        Window window = dialog.getWindow();
+        window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
+    }
+
+    /**
+     * Creates the content view for the dialog (if a custom content view is required).
+     * By default, it inflates the dialog layout resource if it is set.
+     *
+     * @return The content view for the dialog
+     * @see DialogPreference#setLayoutResource(int)
+     */
+    protected View onCreateDialogView(Context context) {
+        final int resId = mDialogLayoutRes;
+        if (resId == 0) {
+            return null;
+        }
+
+        return getLayoutInflater().inflate(resId, null);
+    }
+
+    /**
+     * Binds views in the content view of the dialog to data.
+     *
+     * <p>Make sure to call through to the superclass implementation.
+     *
+     * @param view The content view of the dialog, if it is custom
+     */
+    protected void onBindDialogView(View view) {
+        View dialogMessageView = view.findViewById(android.R.id.message);
+
+        if (dialogMessageView != null) {
+            final CharSequence message = mDialogMessage;
+            int newVisibility = View.GONE;
+
+            if (!TextUtils.isEmpty(message)) {
+                if (dialogMessageView instanceof TextView) {
+                    ((TextView) dialogMessageView).setText(message);
+                }
+
+                newVisibility = View.VISIBLE;
+            }
+
+            if (dialogMessageView.getVisibility() != newVisibility) {
+                dialogMessageView.setVisibility(newVisibility);
+            }
+        }
+    }
+
+    @Override
+    public void onClick(DialogInterface dialog, int which) {
+        mWhichButtonClicked = which;
+    }
+
+    @Override
+    public void onDismiss(@NonNull DialogInterface dialog) {
+        super.onDismiss(dialog);
+        onDialogClosed(mWhichButtonClicked == DialogInterface.BUTTON_POSITIVE);
+    }
+
+    public abstract void onDialogClosed(boolean positiveResult);
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/gpx/GpxParseException.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/gpx/GpxParseException.java
new file mode 100644
index 0000000..cd917c3
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/gpx/GpxParseException.java
@@ -0,0 +1,27 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.util.gpx;
+
+public class GpxParseException extends Exception {
+    public GpxParseException(final String message) {
+        super(message);
+    }
+
+    public GpxParseException(final String message, final Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/gpx/GpxParser.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/gpx/GpxParser.java
new file mode 100644
index 0000000..a86d1d7
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/gpx/GpxParser.java
@@ -0,0 +1,295 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.util.gpx;
+
+import androidx.annotation.Nullable;
+
+import com.google.gson.internal.bind.util.ISO8601Utils;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlPullParserFactory;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.text.ParsePosition;
+import java.util.Date;
+
+import nodomain.freeyourgadget.gadgetbridge.util.ArrayUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.gpx.model.GpxFile;
+import nodomain.freeyourgadget.gadgetbridge.util.gpx.model.GpxTrack;
+import nodomain.freeyourgadget.gadgetbridge.util.gpx.model.GpxTrackPoint;
+import nodomain.freeyourgadget.gadgetbridge.util.gpx.model.GpxTrackSegment;
+import nodomain.freeyourgadget.gadgetbridge.util.gpx.model.GpxWaypoint;
+
+public class GpxParser {
+    private static final Logger LOG = LoggerFactory.getLogger(GpxParser.class);
+
+    public static final byte[] XML_HEADER = new byte[]{
+            '<', '?', 'x', 'm', 'l'
+    };
+
+    // Some gpx files start with "<gpx" directly.. this needs to be improved
+    public static final byte[] GPX_START = new byte[]{
+            '<', 'g', 'p', 'x'
+    };
+
+    private final XmlPullParser parser;
+    private int eventType;
+
+    private final GpxFile.Builder fileBuilder;
+
+
+    @Nullable
+    public static GpxFile parseGpx(final byte[] xmlBytes) {
+        if (!isGpxFile(xmlBytes)) {
+            return null;
+        }
+
+        try (ByteArrayInputStream bais = new ByteArrayInputStream(xmlBytes)) {
+            final GpxParser gpxParser = new GpxParser(bais);
+            return gpxParser.getGpxFile();
+        } catch (final IOException e) {
+            LOG.error("Failed to read xml", e);
+        } catch (final GpxParseException e) {
+            LOG.error("Failed to parse gpx", e);
+        }
+
+        return null;
+    }
+
+    public static boolean isGpxFile(final byte[] data) {
+        // TODO improve this
+        return ArrayUtils.equals(data, XML_HEADER, 0) || ArrayUtils.equals(data, GPX_START, 0);
+    }
+
+    public GpxParser(final InputStream stream) throws GpxParseException {
+        this.fileBuilder = new GpxFile.Builder();
+
+        try {
+            parser = createXmlParser(stream);
+            parseGpx();
+        } catch (final Exception e) {
+            throw new GpxParseException("Failed to parse gpx", e);
+        }
+    }
+
+    public GpxFile getGpxFile() {
+        return fileBuilder.build();
+    }
+
+    private static XmlPullParser createXmlParser(InputStream stream) throws XmlPullParserException {
+        final XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
+        factory.setNamespaceAware(true);
+        XmlPullParser parser = factory.newPullParser();
+        parser.setInput(stream, null);
+        return parser;
+    }
+
+    private void parseGpx() throws Exception {
+        eventType = parser.getEventType();
+
+        while (eventType != XmlPullParser.END_DOCUMENT) {
+            if (eventType == XmlPullParser.START_TAG) {
+                switch (parser.getName()) {
+                    case "trk":
+                        final GpxTrack track = parseTrack();
+                        if (!track.isEmpty()) {
+                            fileBuilder.withTrack(track);
+                        }
+                        continue;
+                    case "wpt":
+                        fileBuilder.withWaypoints(parseWaypoint());
+                        continue;
+                    case "metadata":
+                        parseMetadata();
+                        continue;
+                }
+            }
+
+            eventType = parser.next();
+        }
+    }
+
+    private GpxTrack parseTrack() throws Exception {
+        final GpxTrack.Builder trackBuilder = new GpxTrack.Builder();
+
+        while (eventType != XmlPullParser.END_TAG || !parser.getName().equals("trk")) {
+            if (eventType == XmlPullParser.START_TAG) {
+                switch (parser.getName()) {
+                    case "name":
+                        trackBuilder.withName(parseStringContent("name"));
+                        continue;
+                    case "type":
+                        trackBuilder.withType(parseStringContent("type"));
+                        continue;
+                    case "trkseg":
+                        final GpxTrackSegment segment = parseTrackSegment();
+                        if (!segment.getTrackPoints().isEmpty()) {
+                            trackBuilder.withTrackSegment(segment);
+                        }
+                        continue;
+                }
+            }
+
+            eventType = parser.next();
+        }
+
+        return trackBuilder.build();
+    }
+
+    private GpxTrackSegment parseTrackSegment() throws Exception {
+        final GpxTrackSegment.Builder segmentBuilder = new GpxTrackSegment.Builder();
+
+        while (eventType != XmlPullParser.END_TAG || !parser.getName().equals("trkseg")) {
+            if (eventType == XmlPullParser.START_TAG) {
+                switch (parser.getName()) {
+                    case "trkpt":
+                        final GpxTrackPoint trackPoint = parseTrackPoint();
+                        segmentBuilder.withTrackPoint(trackPoint);
+                        continue;
+                }
+            }
+
+            eventType = parser.next();
+        }
+
+        return segmentBuilder.build();
+    }
+
+    private String parseStringContent(final String tag) throws Exception {
+        String retString = "";
+        while (eventType != XmlPullParser.END_TAG || !parser.getName().equals(tag)) {
+            if (eventType == XmlPullParser.TEXT) {
+                retString = parser.getText();
+            }
+            eventType = parser.next();
+        }
+        return retString;
+    }
+
+    private double parseElevation() throws Exception {
+        return Double.parseDouble(parseStringContent("ele"));
+    }
+
+    private Date parseTime() throws Exception {
+        return ISO8601Utils.parse(parseStringContent("time"), new ParsePosition(0));
+    }
+
+    private GpxTrackPoint parseTrackPoint() throws Exception {
+        final GpxTrackPoint.Builder trackPointBuilder = new GpxTrackPoint.Builder();
+
+        final String latString = parser.getAttributeValue(null, "lat");
+        final String lonString = parser.getAttributeValue(null, "lon");
+        trackPointBuilder.withLatitude(latString != null ? Double.parseDouble(latString) : 0);
+        trackPointBuilder.withLongitude(lonString != null ? Double.parseDouble(lonString) : 0);
+
+        while (eventType != XmlPullParser.END_TAG || !parser.getName().equals("trkpt")) {
+            if (parser.getEventType() == XmlPullParser.START_TAG) {
+                switch (parser.getName()) {
+                    case "ele":
+                        trackPointBuilder.withAltitude(parseElevation());
+                        continue;
+                    case "time":
+                        trackPointBuilder.withTime(parseTime());
+                        continue;
+                    case "extensions":
+                        parseExtensions(trackPointBuilder);
+                        continue;
+                }
+            } 
+
+            eventType = parser.next();
+        }
+
+        return trackPointBuilder.build();
+    }
+
+    private void parseExtensions(final GpxTrackPoint.Builder trackPointBuilder) throws Exception {
+        while (eventType != XmlPullParser.END_TAG || !parser.getName().equals("extensions")) {
+            if (parser.getEventType() == XmlPullParser.START_TAG) {
+                switch (parser.getName()) {
+                    case "TrackPointExtension":
+                        parseTrackPointExtensions(trackPointBuilder);
+                        continue;
+                }
+            }
+
+            eventType = parser.next();
+        }
+    }
+
+    private void parseTrackPointExtensions(final GpxTrackPoint.Builder trackPointBuilder) throws Exception {
+        while (eventType != XmlPullParser.END_TAG || !parser.getName().equals("TrackPointExtension")) {
+            if (parser.getEventType() == XmlPullParser.START_TAG) {
+                switch (parser.getName()) {
+                    case "hr":
+                        trackPointBuilder.withHeartRate(Integer.parseInt(parseStringContent("hr")));
+                        continue;
+                }
+            }
+
+            eventType = parser.next();
+        }
+    }
+
+    private GpxWaypoint parseWaypoint() throws Exception {
+        final GpxWaypoint.Builder waypointBuilder = new GpxWaypoint.Builder();
+
+        final String latString = parser.getAttributeValue(null, "lat");
+        final String lonString = parser.getAttributeValue(null, "lon");
+        waypointBuilder.withLatitude(latString != null ? Double.parseDouble(latString) : 0);
+        waypointBuilder.withLongitude(lonString != null ? Double.parseDouble(lonString) : 0);
+
+        while (eventType != XmlPullParser.END_TAG || !parser.getName().equals("wpt")) {
+            if (parser.getEventType() == XmlPullParser.START_TAG) {
+                switch (parser.getName()) {
+                    case "ele":
+                        waypointBuilder.withAltitude(parseElevation());
+                        continue;
+                    case "name":
+                        waypointBuilder.withName(parseStringContent("name"));
+                        continue;
+                }
+            }
+
+            eventType = parser.next();
+        }
+
+        return waypointBuilder.build();
+    }
+
+    private void parseMetadata() throws Exception {
+        while (eventType != XmlPullParser.END_TAG || !parser.getName().equals("metadata")) {
+            if (parser.getEventType() == XmlPullParser.START_TAG) {
+                switch (parser.getName()) {
+                    case "name":
+                        fileBuilder.withName(parseStringContent("name"));
+                        continue;
+                    case "author":
+                        // TODO parse author
+                        break;
+                }
+            }
+
+            eventType = parser.next();
+        }
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/gpx/model/GpxFile.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/gpx/model/GpxFile.java
new file mode 100644
index 0000000..9099a5d8
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/gpx/model/GpxFile.java
@@ -0,0 +1,106 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.util.gpx.model;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityPoint;
+
+public class GpxFile {
+    private final String name;
+    private final String author;
+
+    private final List<GpxTrack> tracks;
+    private final List<GpxWaypoint> waypoints;
+
+    public GpxFile(final String name, final String author, final List<GpxTrack> tracks, final List<GpxWaypoint> waypoints) {
+        this.name = name;
+        this.author = author;
+        this.tracks = tracks;
+        this.waypoints = waypoints;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public String getAuthor() {
+        return author;
+    }
+
+    public List<GpxTrack> getTracks() {
+        return tracks;
+    }
+
+    public List<GpxWaypoint> getWaypoints() {
+        return waypoints;
+    }
+
+    public List<GpxTrackPoint> getPoints() {
+        final List<GpxTrackPoint> allPoints = new ArrayList<>();
+
+        for (final GpxTrack track : tracks) {
+            for (final GpxTrackSegment trackSegment : track.getTrackSegments()) {
+                allPoints.addAll(trackSegment.getTrackPoints());
+            }
+        }
+
+        return allPoints;
+    }
+
+    public List<ActivityPoint> getActivityPoints() {
+        return tracks.stream()
+                .flatMap(t -> t.getTrackSegments().stream())
+                .flatMap(s -> s.getTrackPoints().stream())
+                .map(GpxTrackPoint::toActivityPoint)
+                .collect(Collectors.toList());
+    }
+
+    public static class Builder {
+        private String name;
+        private String author;
+
+        private List<GpxTrack> tracks = new ArrayList<>();
+        private List<GpxWaypoint> waypoints = new ArrayList<>();
+
+        public Builder withName(final String name) {
+            this.name = name;
+            return this;
+        }
+
+        public Builder withAuthor(final String author) {
+            this.author = author;
+            return this;
+        }
+
+        public Builder withTrack(final GpxTrack track) {
+            this.tracks.add(track);
+            return this;
+        }
+
+        public Builder withWaypoints(final GpxWaypoint waypoint) {
+            this.waypoints.add(waypoint);
+            return this;
+        }
+
+        public GpxFile build() {
+            return new GpxFile(name, author, tracks, waypoints);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/gpx/model/GpxTrack.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/gpx/model/GpxTrack.java
new file mode 100644
index 0000000..03cf2d6
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/gpx/model/GpxTrack.java
@@ -0,0 +1,80 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.util.gpx.model;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class GpxTrack {
+    private final String name;
+
+    private final String type;
+    private final List<GpxTrackSegment> trackSegments;
+
+    public GpxTrack(String name, String type, final List<GpxTrackSegment> trackSegments) {
+        this.name = name;
+        this.type = type;
+        this.trackSegments = trackSegments;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public List<GpxTrackSegment> getTrackSegments() {
+        return trackSegments;
+    }
+
+    public boolean isEmpty() {
+        for (final GpxTrackSegment trackSegment : trackSegments) {
+            if (!trackSegment.getTrackPoints().isEmpty()) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    public static class Builder {
+        private String name;
+        private String type;
+        private final List<GpxTrackSegment> trackSegments = new ArrayList<>();
+
+        public Builder withName(final String name) {
+            this.name = name;
+            return this;
+        }
+
+        public Builder withType(final String type) {
+            this.type = type;
+            return this;
+        }
+
+        public Builder withTrackSegment(final GpxTrackSegment trackSegment) {
+            trackSegments.add(trackSegment);
+            return this;
+        }
+
+        public GpxTrack build() {
+            return new GpxTrack(name, type, trackSegments);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/gpx/model/GpxTrackPoint.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/gpx/model/GpxTrackPoint.java
new file mode 100644
index 0000000..6b729f0
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/gpx/model/GpxTrackPoint.java
@@ -0,0 +1,115 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.util.gpx.model;
+
+import androidx.annotation.NonNull;
+
+import java.util.Date;
+import java.util.Objects;
+
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityPoint;
+import nodomain.freeyourgadget.gadgetbridge.model.GPSCoordinate;
+
+public class GpxTrackPoint extends GPSCoordinate {
+    private final Date time;
+    private final int heartRate;
+
+    public GpxTrackPoint(final double longitude, final double latitude, final double altitude, final Date time) {
+        this(longitude, latitude, altitude, time, -1);
+    }
+
+    public GpxTrackPoint(final double longitude, final double latitude, final double altitude, final Date time, final int heartRate) {
+        super(longitude, latitude, altitude);
+        this.time = time;
+        this.heartRate = heartRate;
+    }
+
+    public Date getTime() {
+        return time;
+    }
+
+    public int getHeartRate() {
+        return heartRate;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof GpxTrackPoint)) return false;
+        if (!super.equals(o)) return false;
+        final GpxTrackPoint that = (GpxTrackPoint) o;
+        return Objects.equals(time, that.time) &&
+                Objects.equals(heartRate, that.heartRate);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(super.hashCode(), time, heartRate);
+    }
+
+    @NonNull
+    @Override
+    public String toString() {
+        return "ts: " + time.getTime() + ", " + super.toString() + ", heartRate: " + heartRate;
+    }
+
+    public ActivityPoint toActivityPoint() {
+        final ActivityPoint activityPoint = new ActivityPoint();
+        activityPoint.setTime(time);
+        activityPoint.setLocation(this);
+        activityPoint.setHeartRate(heartRate);
+
+        return activityPoint;
+    }
+
+    public static class Builder {
+        private double longitude;
+        private double latitude;
+        private double altitude;
+        private Date time;
+        private int heartRate = -1;
+
+        public Builder withLongitude(final double longitude) {
+            this.longitude = longitude;
+            return this;
+        }
+
+        public Builder withLatitude(final double latitude) {
+            this.latitude = latitude;
+            return this;
+        }
+
+        public Builder withAltitude(final double altitude) {
+            this.altitude = altitude;
+            return this;
+        }
+
+        public Builder withTime(final Date time) {
+            this.time = time;
+            return this;
+        }
+
+        public Builder withHeartRate(final int heartRate) {
+            this.heartRate = heartRate;
+            return this;
+        }
+
+        public GpxTrackPoint build() {
+            return new GpxTrackPoint(longitude, latitude, altitude, time, heartRate);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/gpx/model/GpxTrackSegment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/gpx/model/GpxTrackSegment.java
new file mode 100644
index 0000000..93ca5b4
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/gpx/model/GpxTrackSegment.java
@@ -0,0 +1,49 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.util.gpx.model;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class GpxTrackSegment {
+    private final List<GpxTrackPoint> trackPoints;
+
+    public GpxTrackSegment(final List<GpxTrackPoint> trackPoints) {
+        this.trackPoints = trackPoints;
+    }
+
+    public List<GpxTrackPoint> getTrackPoints() {
+        return trackPoints;
+    }
+
+    public static class Builder {
+        private final List<GpxTrackPoint> trackPoints = new ArrayList<>();
+
+        public Builder withTrackPoint(final GpxTrackPoint trackPoint) {
+            trackPoints.add(trackPoint);
+            return this;
+        }
+
+        public boolean isEmpty() {
+            return trackPoints.isEmpty();
+        }
+
+        public GpxTrackSegment build() {
+            return new GpxTrackSegment(trackPoints);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/gpx/model/GpxWaypoint.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/gpx/model/GpxWaypoint.java
new file mode 100644
index 0000000..9874271
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/gpx/model/GpxWaypoint.java
@@ -0,0 +1,67 @@
+/*  Copyright (C) 2023-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.util.gpx.model;
+
+import androidx.annotation.Nullable;
+
+import nodomain.freeyourgadget.gadgetbridge.model.GPSCoordinate;
+
+public class GpxWaypoint extends GPSCoordinate {
+    @Nullable
+    private final String name;
+
+    public GpxWaypoint(final double longitude, final double latitude, final double altitude, @Nullable final String name) {
+        super(longitude, latitude, altitude);
+        this.name = name;
+    }
+
+    @Nullable
+    public String getName() {
+        return name;
+    }
+
+    public static class Builder {
+        private double longitude;
+        private double latitude;
+        private double altitude;
+        private String name;
+
+        public Builder withLongitude(final double longitude) {
+            this.longitude = longitude;
+            return this;
+        }
+
+        public Builder withLatitude(final double latitude) {
+            this.latitude = latitude;
+            return this;
+        }
+
+        public Builder withAltitude(final double altitude) {
+            this.altitude = altitude;
+            return this;
+        }
+
+        public Builder withName(final String name) {
+            this.name = name;
+            return this;
+        }
+
+        public GpxWaypoint build() {
+            return new GpxWaypoint(longitude, latitude, altitude, name);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/gson/GsonUtcDateAdapter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/gson/GsonUtcDateAdapter.java
new file mode 100644
index 0000000..7cf8147
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/gson/GsonUtcDateAdapter.java
@@ -0,0 +1,56 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.util.gson;
+
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonParseException;
+import com.google.gson.JsonPrimitive;
+import com.google.gson.JsonSerializationContext;
+import com.google.gson.JsonSerializer;
+
+import java.lang.reflect.Type;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+import java.util.TimeZone;
+
+public class GsonUtcDateAdapter implements JsonSerializer<Date>, JsonDeserializer<Date> {
+    private final DateFormat dateFormat;
+
+    public GsonUtcDateAdapter() {
+        dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.ROOT);
+        dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
+    }
+
+    @Override
+    public Date deserialize(final JsonElement json, final Type typeOfT, final JsonDeserializationContext context) throws JsonParseException {
+        try {
+            return dateFormat.parse(json.getAsString());
+        } catch (final ParseException e) {
+            throw new JsonParseException(e);
+        }
+    }
+
+    @Override
+    public JsonElement serialize(final Date date, final Type typeOfSrc, final JsonSerializationContext context) {
+        return new JsonPrimitive(dateFormat.format(date));
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/LanguageUtils.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/LanguageUtils.java
new file mode 100644
index 0000000..79167d6
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/LanguageUtils.java
@@ -0,0 +1,149 @@
+/*  Copyright (C) 2022-2024 C茅dric Bellegarde, Davis Mosenkovs, Jos茅 Rebelo,
+    roolx, ssilverr, thirschbuechler
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.util.language;
+
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_TRANSLITERATION_LANGUAGES;
+
+import androidx.annotation.Nullable;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+import nodomain.freeyourgadget.gadgetbridge.util.language.impl.ArabicTransliterator;
+import nodomain.freeyourgadget.gadgetbridge.util.language.impl.ArmenianTransliterator;
+import nodomain.freeyourgadget.gadgetbridge.util.language.impl.BengaliTransliterator;
+import nodomain.freeyourgadget.gadgetbridge.util.language.impl.CommonSymbolsTransliterator;
+import nodomain.freeyourgadget.gadgetbridge.util.language.impl.CroatianTransliterator;
+import nodomain.freeyourgadget.gadgetbridge.util.language.impl.CzechTransliterator;
+import nodomain.freeyourgadget.gadgetbridge.util.language.impl.EstonianTransliterator;
+import nodomain.freeyourgadget.gadgetbridge.util.language.impl.ExtendedAsciiTransliterator;
+import nodomain.freeyourgadget.gadgetbridge.util.language.impl.FlattenToAsciiTransliterator;
+import nodomain.freeyourgadget.gadgetbridge.util.language.impl.FrenchTransliterator;
+import nodomain.freeyourgadget.gadgetbridge.util.language.impl.GeorgianTransliterator;
+import nodomain.freeyourgadget.gadgetbridge.util.language.impl.GermanTransliterator;
+import nodomain.freeyourgadget.gadgetbridge.util.language.impl.GreekTransliterator;
+import nodomain.freeyourgadget.gadgetbridge.util.language.impl.HebrewTransliterator;
+import nodomain.freeyourgadget.gadgetbridge.util.language.impl.HungarianTransliterator;
+import nodomain.freeyourgadget.gadgetbridge.util.language.impl.IcelandicTransliterator;
+import nodomain.freeyourgadget.gadgetbridge.util.language.impl.KoreanTransliterator;
+import nodomain.freeyourgadget.gadgetbridge.util.language.impl.LatvianTransliterator;
+import nodomain.freeyourgadget.gadgetbridge.util.language.impl.LithuanianTransliterator;
+import nodomain.freeyourgadget.gadgetbridge.util.language.impl.PersianTransliterator;
+import nodomain.freeyourgadget.gadgetbridge.util.language.impl.PolishTransliterator;
+import nodomain.freeyourgadget.gadgetbridge.util.language.impl.RussianTransliterator;
+import nodomain.freeyourgadget.gadgetbridge.util.language.impl.ScandinavianTransliterator;
+import nodomain.freeyourgadget.gadgetbridge.util.language.impl.SerbianTransliterator;
+import nodomain.freeyourgadget.gadgetbridge.util.language.impl.TurkishTransliterator;
+import nodomain.freeyourgadget.gadgetbridge.util.language.impl.UkranianTransliterator;
+
+public class LanguageUtils {
+    private static final Logger LOG = LoggerFactory.getLogger(LanguageUtils.class);
+
+    private static final Map<String, Transliterator> TRANSLITERATORS_MAP = new HashMap<String, Transliterator>() {{
+        put("arabic", new ArabicTransliterator());
+        put("bengali", new BengaliTransliterator());
+        put("common_symbols", new CommonSymbolsTransliterator());
+        put("croatian", new CroatianTransliterator());
+        put("czech", new CzechTransliterator());
+        put("estonian", new EstonianTransliterator());
+        put("extended_ascii", new ExtendedAsciiTransliterator());
+        put("french", new FrenchTransliterator());
+        put("georgian", new GeorgianTransliterator());
+        put("german", new GermanTransliterator());
+        put("greek", new GreekTransliterator());
+        put("hebrew", new HebrewTransliterator());
+        put("hungarian", new HungarianTransliterator());
+        put("icelandic", new IcelandicTransliterator());
+        put("korean", new KoreanTransliterator());
+        put("latvian", new LatvianTransliterator());
+        put("lithuanian", new LithuanianTransliterator());
+        put("persian", new PersianTransliterator());
+        put("polish", new PolishTransliterator());
+        put("russian", new RussianTransliterator());
+        put("scandinavian", new ScandinavianTransliterator());
+        put("serbian", new SerbianTransliterator());
+        put("turkish", new TurkishTransliterator());
+        put("ukranian", new UkranianTransliterator());
+        put("armenian", new ArmenianTransliterator());
+    }};
+
+    /**
+     * Get a {@link Transliterator} for a specific language.
+     *
+     * @param language the language
+     * @return the transliterator, if it exists
+     * @throws IllegalArgumentException if there is no transliterator for the provided language
+     */
+    public static Transliterator getTransliterator(final String language) {
+        if (!TRANSLITERATORS_MAP.containsKey(language)) {
+            throw new IllegalArgumentException(String.format("Transliterator for %s not found", language));
+        }
+
+        return TRANSLITERATORS_MAP.get(language);
+    }
+
+    /**
+     * Get the configured transliterator for the provided {@link GBDevice}, if any.
+     *
+     * @param device the device
+     * @return the configured transliterator, null if not configured
+     */
+    @Nullable
+    public static Transliterator getTransliterator(final GBDevice device) {
+        final DeviceCoordinator coordinator = device.getDeviceCoordinator();
+        final Prefs devicePrefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(device.getAddress()));
+        final String transliterateLanguagesPref = devicePrefs.getString(PREF_TRANSLITERATION_LANGUAGES, "");
+
+        if (transliterateLanguagesPref.isEmpty()) {
+            return null;
+        }
+
+        final List<String> languages = Arrays.asList(transliterateLanguagesPref.split(","));
+        final List<Transliterator> transliterators = new ArrayList<>(languages.size());
+
+        for (String language : languages) {
+            if (!TRANSLITERATORS_MAP.containsKey(language)) {
+                LOG.warn("Transliterator for {} not found", language);
+                continue;
+            }
+
+            transliterators.add(TRANSLITERATORS_MAP.get(language));
+        }
+
+        if (!coordinator.supportsUnicodeEmojis()) {
+            // For now, assume that if the device does not support unicode emoji, it also doesn't
+            // support utf, so flatten to ASCII. This allows for devices that support unicode
+            // characters to still use transliterators for languages not supported by the device,
+            // and still get emoji
+            // TODO: Maybe this should be configurable, or at least separate from the emoji setting
+            transliterators.add(new FlattenToAsciiTransliterator());
+        }
+
+        return new MultiTransliterator(transliterators);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/MultiTransliterator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/MultiTransliterator.java
new file mode 100644
index 0000000..537963b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/MultiTransliterator.java
@@ -0,0 +1,42 @@
+/*  Copyright (C) 2022-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.util.language;
+
+import java.text.Normalizer;
+import java.util.List;
+import java.util.Map;
+
+public class MultiTransliterator implements Transliterator {
+    private final List<Transliterator> transliterators;
+
+    public MultiTransliterator(final List<Transliterator> transliterators) {
+        this.transliterators = transliterators;
+    }
+
+    @Override
+    public String transliterate(String txt) {
+        if (txt == null || txt.isEmpty()) {
+            return txt;
+        }
+
+        for (Transliterator transliterator : transliterators) {
+            txt = transliterator.transliterate(txt);
+        }
+
+        return txt;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/SimpleTransliterator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/SimpleTransliterator.java
new file mode 100644
index 0000000..a165135
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/SimpleTransliterator.java
@@ -0,0 +1,69 @@
+/*  Copyright (C) 2022-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.util.language;
+
+import org.apache.commons.lang3.text.WordUtils;
+
+import java.util.Map;
+
+public class SimpleTransliterator implements Transliterator {
+    private final Map<Character, String> transliterateMap;
+    private final boolean convertToLowercase;
+
+    public SimpleTransliterator(final Map<Character, String> transliterateMap, final boolean convertToLowercase) {
+        this.transliterateMap = transliterateMap;
+        this.convertToLowercase = convertToLowercase;
+    }
+
+    public SimpleTransliterator(final Map<Character, String> transliterateMap) {
+        this(transliterateMap, true);
+    }
+
+    @Override
+    public String transliterate(String txt) {
+        if (txt == null || txt.isEmpty()) {
+            return txt;
+        }
+
+        final StringBuilder messageBuilder = new StringBuilder();
+
+        // Simple, char-by-char transliteration.
+        final char[] chars = txt.toCharArray();
+        for (char c : chars) {
+            messageBuilder.append(transliterate(c));
+        }
+        final String message = messageBuilder.toString();
+
+        return message;
+    }
+
+    private String transliterate(final char c) {
+        final char sourceChar = convertToLowercase ? Character.toLowerCase(c) : c;
+
+        if (transliterateMap.containsKey(sourceChar)) {
+            final String replace = transliterateMap.get(sourceChar);
+
+            if (sourceChar != c) {
+                return convertToLowercase ? WordUtils.capitalize(replace) : replace;
+            }
+
+            return replace;
+        }
+
+        return String.valueOf(c);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/Transliterator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/Transliterator.java
new file mode 100644
index 0000000..59676cb
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/Transliterator.java
@@ -0,0 +1,21 @@
+/*  Copyright (C) 2022-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.util.language;
+
+public interface Transliterator {
+    String transliterate(String txt);
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/ArabicTransliterator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/ArabicTransliterator.java
new file mode 100644
index 0000000..9c63a23
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/ArabicTransliterator.java
@@ -0,0 +1,37 @@
+/*  Copyright (C) 2022-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.util.language.impl;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import nodomain.freeyourgadget.gadgetbridge.util.language.SimpleTransliterator;
+
+public class ArabicTransliterator extends SimpleTransliterator {
+    public ArabicTransliterator() {
+        super(new HashMap<Character, String>() {{
+            put('丕', "a"); put('亘', "b"); put('鬲', "t"); put('孬', "th"); put('噩', "j"); put('丨', "7"); put('禺', "5");
+            put('丿', "d"); put('匕', "th"); put('乇', "r"); put('夭', "z"); put('爻', "s"); put('卮', "sh"); put('氐', "9");
+            put('囟', "9'"); put('胤', "6"); put('馗', "6'"); put('毓', "3"); put('睾', "3'"); put('賮', "f");
+            put('賯', "q"); put('賰', "k"); put('賱', "l"); put('賲', "m"); put('賳', "n"); put('賴', "h");
+            put('賵', "w"); put('賷', "y"); put('賶', "a"); put('锖�', "");
+            put('丌', "2"); put('卅', "2"); put('廿', "2"); put('丐', "2"); put('兀', "2"); put('亍', "2");
+            put('贍', "0"); put('佟', "1"); put('佗', "2"); put('伲', "3"); put('伽', "4"); put('佶', "5");
+            put('佴', "6"); put('侑', "7"); put('侉', "8"); put('侃', "9");
+        }});
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/ArmenianTransliterator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/ArmenianTransliterator.java
new file mode 100644
index 0000000..3986e38
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/ArmenianTransliterator.java
@@ -0,0 +1,305 @@
+/*  Copyright (C) 2021-2024 Alik Aslanyan
+
+    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 <http://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.util.language.impl;
+import nodomain.freeyourgadget.gadgetbridge.util.language.Transliterator;
+
+import org.ahocorasick.trie.Emit;
+import org.ahocorasick.trie.Trie;
+import org.apache.commons.lang3.text.WordUtils;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+public class ArmenianTransliterator implements Transliterator {
+    // Transliteration map ordered by priority
+    // Armenian has some rules regarding reading of '崭' in the middle of the word it reads as english O
+    // But if word starts with it's read as sound of 'vo'
+    // Or if it has '謧' symbol after it, then we should read it as 'u' (as double o in booze)
+    private static final Map<String, String> transliterateMap = new LinkedHashMap<String, String>() {
+        {
+            // Simple substitutions
+            Map<String, String> simpleSubstitions = new HashMap<String, String>() {
+                {
+                    put("铡","a");
+                    put("闸","b");
+                    put("眨","g");
+                    put("栅","d");
+                    put("榨","e");
+                    put("咋","z");
+                    put("乍","e");
+                    put("炸","y");
+                    put("诈","t");
+                    put("摘","j");
+                    put("斋","i");
+                    put("宅","l");
+                    put("窄","x");
+                    put("债","c");
+                    put("寨","k");
+                    put("瞻","h");
+                    put("毡","dz");
+                    put("詹","x");
+                    put("粘","c");
+                    put("沾","m");
+                    put("盏","y");
+                    put("斩","n");
+                    put("辗","sh");
+                    put("崭", "vo");
+                    put("展","ch");
+                    put("蘸","p");
+                    put("栈","j");
+                    put("占","r");
+                    put("战","s");
+                    put("站","v");
+                    put("湛","t");
+                    put("謤","r");
+                    put("謥","c");
+                    put("謨","p");
+                    put("謩","q");
+                    put("謪","o");
+                    put("謬","ev");
+                    put("謫","f");
+                    put("諠", "`");
+                    put("諡", "?");
+                    put("謮", ":");
+                    put("鈥�", ".");
+                }
+            };
+
+            // Capitalize existing simple substitutions here
+            for (final Entry<String, String> entry : new ArrayList<Entry<String, String>>(simpleSubstitions.entrySet())) {
+                String capitalKey = entry.getKey().toUpperCase();
+                if (!capitalKey.equals(entry.getKey())) {
+                    simpleSubstitions.put(capitalKey, entry.getValue().toUpperCase());
+                }
+            }
+
+            // Letter + '崭謧'
+            final String[] letterMapU = {
+                "铡",
+                "闸",
+                "眨",
+                "栅",
+                "榨",
+                "咋",
+                "乍",
+                "炸",
+                "诈",
+                "摘",
+                "斋",
+                "宅",
+                "窄",
+                "债",
+                "寨",
+                "瞻",
+                "毡",
+                "詹",
+                "粘",
+                "沾",
+                "盏",
+                "斩",
+                "辗",
+                "展",
+                "蘸",
+                "栈",
+                "占",
+                "战",
+                "站",
+                "湛",
+                "謤",
+                "謥",
+                "謨",
+                "謩",
+                "謪",
+                "謬",
+                "謫",
+                "崭"
+            };
+
+            for (final String letter : letterMapU) {
+                final String capitalLetter = letter.toUpperCase();
+                final String transliteratedLetter = Objects.requireNonNull(simpleSubstitions.get(letter), letter);
+                final String transliteratedCapitalLetter = Objects.requireNonNull(simpleSubstitions.get(capitalLetter), capitalLetter);
+
+                put(letter + "崭謧", transliteratedLetter + "u");
+                put(capitalLetter + "崭謧", transliteratedCapitalLetter + "u");
+
+                put(letter + "請諕", transliteratedLetter + "U");
+                put(capitalLetter + "請諕", transliteratedCapitalLetter + "U");
+                put(letter + "請謧", transliteratedLetter + "U");
+                put(capitalLetter + "請謧", transliteratedCapitalLetter + "U");
+
+                put(letter + "崭諕", transliteratedLetter + "U");
+                put(capitalLetter + "崭諕", transliteratedCapitalLetter + "U");
+            }
+
+            put("崭謧","u");
+            put("請謧","U");
+            put("崭諕","U");
+            put("請諕","U");
+
+            // Letter + '崭'
+            final String[] letterMapVo = {
+                "闸",
+                "眨",
+                "栅",
+                "咋",
+                "诈",
+                "摘",
+                "宅",
+                "窄",
+                "债",
+                "寨",
+                "瞻",
+                "毡",
+                "詹",
+                "粘",
+                "沾",
+                "盏",
+                "斩",
+                "辗",
+                "崭", // 崭 + 崭 should be voo
+                "展",
+                "蘸",
+                "栈",
+                "占",
+                "战",
+                "站",
+                "湛",
+                "謤",
+                "謥",
+                "謨",
+                "謩",
+                "謬",
+                "謫"
+            };
+
+            for (String letter : letterMapVo) {
+                String capitalLetter = letter.toUpperCase();
+                final String transliteratedLetter = Objects.requireNonNull(simpleSubstitions.get(letter));
+                final String transliteratedCapitalLetter = Objects.requireNonNull(simpleSubstitions.get(capitalLetter));
+
+                put(letter + "崭", transliteratedLetter + "o");
+                put(capitalLetter + "崭", transliteratedCapitalLetter + "o");
+
+                put(letter + "請", transliteratedLetter + "諘");
+                put(capitalLetter + "請", transliteratedCapitalLetter + "諘");
+            }
+
+            put("崭","vo");
+            put("請","VO");
+
+            // Two different ways to write, we support all.
+            put("榨謧","ev");
+            put("榨站","ev");
+            put("缘謧","Ev");
+            put("缘站","Ev");
+            put("缘諕","EV");
+            put("缘諑","EV");
+
+            // If this symbol wasn't used in the combination with others, then it's meaningless
+            put("謧","");
+            put("諕","");
+
+            // Simple substitutions have last priority
+            for (final Map.Entry<String,String> entry : simpleSubstitions.entrySet()) {
+                put(entry.getKey(), entry.getValue());
+                put(entry.getKey().toUpperCase(), entry.getValue().toUpperCase());
+            }
+        }};
+
+    private static final Map<String, Integer> transliterationPriorityMap = new HashMap<String, Integer>() {{
+        int priority = 0;
+        for (final String key : transliterateMap.keySet()) {
+            put(key, priority++);
+        }
+    }};
+
+    // Aho-Corasick trie
+    private static final Trie transliterationTrie;
+    static {
+        final Trie.TrieBuilder builder = Trie.builder();
+        for (final String key :  ArmenianTransliterator.transliterateMap.keySet()) {
+            builder.addKeyword(key);
+        }
+        transliterationTrie = builder.build();
+    }
+
+    private static String ahoCorasick(final String text) {
+        // Create a buffer sufficiently large that re-allocations are minimized.
+        final StringBuilder sb = new StringBuilder(text.length() * 10 / 12);
+
+        // The complexity of the Aho-Corasick algorithm O(N + L + Z)
+        // Where N is the length of the text, L is the length of keywords and the Z is a number of matches.
+        // This algorithm allows us to do fast substring search
+        final List<Emit> emits = new ArrayList<Emit>(transliterationTrie.parseText(text));
+
+        // Sort collection first by starting position, then by priority.
+        Collections.sort(emits, new Comparator<Emit>() {
+            @Override
+            public int compare(Emit a, Emit b) {
+                int cmp = Integer.compare(a.getStart(), b.getStart());
+                if (cmp != 0) {
+                    return cmp;
+                }
+
+                int priorityA = transliterationPriorityMap.get(a.getKeyword());
+                int priorityB = transliterationPriorityMap.get(b.getKeyword());
+                return Integer.compare(priorityA, priorityB);
+            }
+        });
+
+        int prevIndex = 0;
+
+        for (final Emit emit : emits) {
+            final int matchIndex = emit.getStart();
+
+            // Skip if we already substituted this part
+            if (matchIndex < prevIndex) {
+                continue;
+            }
+
+            // Add part which shouldn't be substituted
+            sb.append(text.substring(prevIndex, matchIndex));
+
+            // Substitute and append to the builder
+            sb.append(Objects.requireNonNull(ArmenianTransliterator.transliterateMap.get(emit.getKeyword())));
+
+            prevIndex = emit.getEnd() + 1;
+        }
+
+        // Add the remainder of the string (contains no more matches).
+        sb.append(text.substring(prevIndex));
+
+        return sb.toString();
+    }
+
+    @Override
+    public String transliterate(String txt) {
+        if (txt == null || txt.isEmpty()) {
+            return txt;
+        }
+
+        return ahoCorasick(txt);
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/BengaliTransliterator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/BengaliTransliterator.java
new file mode 100644
index 0000000..18667d2
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/BengaliTransliterator.java
@@ -0,0 +1,322 @@
+/*  Copyright (C) 2022-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.util.language.impl;
+
+import java.util.HashMap;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.util.language.Transliterator;
+
+// What's the reason to extending LanguageUtils?
+// Just doing it because already done in the previous code.
+public class BengaliTransliterator implements Transliterator {
+        // Composite Letters.
+    private final static HashMap<String, String> composites = new HashMap<String, String>() {
+        {
+            put("唳曕唳�", "kkh");
+            put("唳炧唳�", "NC");
+            put("唳炧唳�", "NCh");
+            put("唳炧唳�", "Ng");
+            put("唳溹唳�", "gg");
+            put("唳炧唳�", "Ngh");
+            put("唰嵿Π", "r");
+            put("唰嵿Σ", "l");
+            put("唳粪唳�", "SSh");
+            put("唳班", "r");
+            put("唰嵿Ο", "y");
+            put("唰嵿Μ", "w");
+        }
+    };
+
+    // Vowels Only
+    private final static HashMap<String, String> vowels = new HashMap<String, String>() {
+        {
+            put("唳�", "aa");
+            put("唳�", "a");
+            put("唳�", "i");
+            put("唳�", "ii");
+            put("唳�", "u");
+            put("唳�", "uu");
+            put("唳�", "ri");
+            put("唳�", "e");
+            put("唳�", "oi");
+            put("唳�", "o");
+            put("唳�", "ou");
+        }
+    };
+
+    // Vowels and Hasants
+    private final static HashMap<String, String> vowelsAndHasants = new HashMap<String, String>() {
+        {
+            put("唳�", "aa");
+            put("唳�", "a");
+            put("唳�", "i");
+            put("唳�", "ii");
+            put("唳�", "u");
+            put("唳�", "uu");
+            put("唳�", "ri");
+            put("唳�", "e");
+            put("唳�", "oi");
+            put("唳�", "o");
+            put("唳�", "ou");
+            put("唳�", "aa");
+            put("唳�", "i");
+            put("唰�", "ii");
+            put("唰�", "u");
+            put("唰�", "uu");
+            put("唰�", "r");
+            put("唰�", "e");
+            put("唰�", "o");
+            put("唰�", "oi");
+            put("唰�", "ou");
+            put("唰�", "ou");
+            put("唳�", "ng");
+            put("唳�", "h");
+            put("啷�", ".");
+        }
+    };
+
+    // Single Character Letters.
+    private final static HashMap<String, String> letters = new HashMap<String, String>() {
+        {
+            put("唳�", "aa");
+            put("唳�", "a");
+            put("唳�", "i");
+            put("唳�", "ii");
+            put("唳�", "u");
+            put("唳�", "uu");
+            put("唳�", "ri");
+            put("唳�", "e");
+            put("唳�", "oi");
+            put("唳�", "o");
+            put("唳�", "ou");
+            put("唳�", "k");
+            put("唳�", "kh");
+            put("唳�", "g");
+            put("唳�", "gh");
+            put("唳�", "ng");
+            put("唳�", "ch");
+            put("唳�", "chh");
+            put("唳�", "j");
+            put("唳�", "jh");
+            put("唳�", "Ng");
+            put("唳�", "T");
+            put("唳�", "Th");
+            put("唳�", "D");
+            put("唳�", "Dh");
+            put("唳�", "N");
+            put("唳�", "t");
+            put("唳�", "th");
+            put("唳�", "d");
+            put("唳�", "dh");
+            put("唳�", "n");
+            put("唳�", "p");
+            put("唳�", "ph");
+            put("唳�", "b");
+            put("唳�", "bh");
+            put("唳�", "m");
+            put("唳�", "J");
+            put("唳�", "r");
+            put("唳�", "l");
+            put("唳�", "sh");
+            put("唳�", "Sh");
+            put("唳�", "s");
+            put("唳�", "h");
+            put("唰�", "rh");
+            put("唰�", "rH");
+            put("唰�", "y");
+            put("唰�", "t");
+            put("唰�", "0");
+            put("唰�", "1");
+            put("唰�", "2");
+            put("唰�", "3");
+            put("唰�", "4");
+            put("唰�", "5");
+            put("唰�", "6");
+            put("唰�", "7");
+            put("唰�", "8");
+            put("唰�", "9");
+            put("唳�", "aa");
+            put("唳�", "i");
+            put("唰�", "ii");
+            put("唰�", "u");
+            put("唰�", "uu");
+            put("唰�", "r");
+            put("唰�", "e");
+            put("唰�", "o");
+            put("唰�", "oi");
+            put("唰�", "ou");
+            put("唰�", "ou");
+            put("唳�", "ng");
+            put("唳�", "h");
+            put("唳�", "nN");
+            put("啷�", ".");
+        }
+    };
+
+    // The regex to extract Bengali characters in nested groups.
+    private final static String pattern = "(唳班){0,1}(([唳�-唳灌-唰焆)(唰�([唳�-唳Χ-唳灌-唰焆))*)((鈥�){0,1}(唰�([唳�-唳瞉))){0,1}([唳�-唰宂){0,1}|([唰嵿唳冟唰庎Е-唰イ])|(\\s)";
+
+    private final static Pattern bengaliRegex = Pattern.compile(pattern);
+
+    private static String getVal(String key) {
+        if (key != null) {
+            String comp = composites.get(key);
+            if (comp != null) {
+                return comp;
+            }
+            String sl = letters.get(key);
+            if (sl != null) {
+                return letters.get(key);
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public String transliterate(String txt) {
+        if (txt == null || txt.isEmpty()) {
+            return txt;
+        }
+
+        Matcher m = bengaliRegex.matcher(txt);
+        StringBuffer sb = new StringBuffer();
+        String lastChar = "";
+        boolean lastHadComposition = false;
+        boolean lastHadKaar = false;
+        boolean nextNeedsO = false;
+        int lastHadO = 0;
+        while (m.find()) {
+            boolean thisNeedsO = false;
+            boolean changePronounciation = false;
+            boolean thisHadKaar = false;
+            String appendableString = "";
+            String reff = m.group(1);
+            if (reff != null) {
+                appendableString = appendableString + "rr";
+            }
+            // This is a filter-down approach. First considering larger groups,
+            // If found any match breaks their. Else go to the next step.
+            // Helpful to solve some corner-cases.
+            String mainPart = getVal(m.group(2));
+            if (mainPart != null) {
+                appendableString = appendableString + mainPart;
+            } else {
+                String firstPart = getVal(m.group(3));
+                if (firstPart != null) {
+                    appendableString = appendableString + firstPart;
+                }
+                int g = 4;
+                while (g < 6) {
+                    String part = getVal(m.group(g));
+                    if (part != null) {
+                        appendableString = appendableString + part;
+                        break;
+                    }
+                    g = g + 1;
+                }
+            }
+            if (m.group(2) != null && m.group(2).equals("唳曕唳�")) {
+                changePronounciation = true;
+                thisNeedsO = true;
+            }
+            int g = 6;
+            while (g < 10) {
+                String key = getVal(m.group(g));
+                if (key != null) {
+                    appendableString = appendableString + key;
+                    break;
+                }
+                g = g + 1;
+            }
+            String phala = m.group(8);
+            if (phala != null && phala.equals("唰嵿Ο")) {
+                changePronounciation = true;
+                thisNeedsO = true;
+            }
+            String jukto = m.group(4);
+            if (jukto != null) {
+                thisNeedsO = true;
+            }
+            String kaar = m.group(10);
+            if (kaar != null) {
+                String kaarStr = letters.get(kaar);
+                if (kaarStr != null) {
+                    appendableString = appendableString + kaarStr;
+                    if (kaarStr.equals("i") || kaarStr.equals("ii") || kaarStr.equals("u") || kaarStr.equals("uu")) {
+                        changePronounciation = true;
+                    }
+                }
+            }
+            String singleton = m.group(11);
+            if (singleton != null) {
+                String singleStr = letters.get(singleton);
+                if (singleStr != null) {
+                    appendableString = appendableString + singleStr;
+                }
+            }
+            if (changePronounciation && lastChar.equals("a")) {
+                sb.setCharAt(sb.length() - 1, 'o');
+            }
+            String others = m.group(0);
+            if (others != null) {
+
+                if (appendableString.length() <= 0) {
+                    appendableString = appendableString + others;
+                }
+            }
+            String whitespace = m.group(12);
+            if (nextNeedsO && kaar == null && whitespace == null && !vowels.containsKey(m.group(0))) {
+                appendableString = appendableString + "o";
+                lastHadO++;
+                thisNeedsO = false;
+            }
+
+            if (((kaar != null && lastHadO > 1) || whitespace != null) && !lastHadKaar && sb.length() > 0
+                    && sb.charAt(sb.length() - 1) == 'o' && !lastHadComposition) {
+                sb.deleteCharAt(sb.length() - 1);
+                lastHadO = 0;
+            }
+            nextNeedsO = false;
+            if (thisNeedsO && kaar == null && whitespace == null && !vowels.containsKey(m.group(0))) {
+                appendableString = appendableString + "o";
+                lastHadO++;
+            }
+            if (appendableString.length() > 0 && !vowelsAndHasants.containsKey(m.group(0)) && kaar == null) {
+                nextNeedsO = true;
+            }
+            if (reff != null || m.group(4) != null || m.group(6) != null) {
+                lastHadComposition = true;
+            } else {
+                lastHadComposition = false;
+            }
+            if (kaar != null) {
+                lastHadKaar = true;
+            } else {
+                lastHadKaar = false;
+            }
+            m.appendReplacement(sb, appendableString);
+            lastChar = appendableString;
+        }
+        if (!lastHadKaar && sb.length() > 0 && sb.charAt(sb.length() - 1) == 'o' && !lastHadComposition) {
+            sb.deleteCharAt(sb.length() - 1);
+        }
+        m.appendTail(sb);
+        return sb.toString();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/CommonSymbolsTransliterator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/CommonSymbolsTransliterator.java
new file mode 100644
index 0000000..11057a6
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/CommonSymbolsTransliterator.java
@@ -0,0 +1,35 @@
+/*  Copyright (C) 2023-2024 Daniel Dakhno, Davis Mosenkovs
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.util.language.impl;
+
+import java.util.HashMap;
+
+import nodomain.freeyourgadget.gadgetbridge.util.language.SimpleTransliterator;
+
+public class CommonSymbolsTransliterator extends SimpleTransliterator {
+    public CommonSymbolsTransliterator() {
+        super(new HashMap<Character, String>() {{
+            put('鈥�', "\""); put('鈥�', "\""); put('鈥�', "\""); put('鈥�', "\""); put('鈥�', "'"); put('鈥�', "'"); put('鈥�', "'"); put('鈥�', "'"); put('芦', "<"); put('禄', ">"); put('鈥�', "<"); put('鈥�', ">");
+            put('漏', "(c)"); put('庐', "(r)"); put('鈩�', "(tm)"); put('掳', "*"); put('鈧�', "EUR");
+            put('鈥�', "-"); put('飧�', "-"); put('藯', "-"); put('釔�', "-"); put('鈥�', "-"); put('鈥�', "-"); put('鈥�', "-"); put('鈥�', "-"); put('鈥�', "-"); put('鈭�', "-");
+            put('鈳�', "-"); put('鈴�', "-"); put('鈹�', "-"); put('鉃�', "-"); put('飧�', "-"); put('銋�', "-"); put('釁�', "-"); put('銉�', "-"); put('涓�', "-"); put('锕�', "-");
+            put('锕�', "-"); put('锛�', "-"); put('\udc4b', "-"); put('\udc52', "-"); put('藴', "~"); put('鈦�', "~"); put('鈭�', "~"); put('銆�', "~"); put('銆�', "~~"); put('锝�', "~");
+            put('鈦�', "0"); put('鹿', "1"); put('虏', "2"); put('鲁', "3"); put('鈦�', "4"); put('鈦�', "5"); put('鈦�', "6"); put('鈦�', "7"); put('鈦�', "8"); put('鈦�', "9");
+            put('鈧�', "0"); put('鈧�', "1"); put('鈧�', "2"); put('鈧�', "3"); put('鈧�', "4"); put('鈧�', "5"); put('鈧�', "6"); put('鈧�', "7"); put('鈧�', "8"); put('鈧�', "9");
+        }});
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/CroatianTransliterator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/CroatianTransliterator.java
new file mode 100644
index 0000000..5038b25
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/CroatianTransliterator.java
@@ -0,0 +1,34 @@
+/*  Copyright (C) 2023-2024 thirschbuechler
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+    package nodomain.freeyourgadget.gadgetbridge.util.language.impl;
+
+    import java.util.HashMap;
+    
+    import nodomain.freeyourgadget.gadgetbridge.util.language.SimpleTransliterator;
+    
+    public class CroatianTransliterator extends SimpleTransliterator {
+        public CroatianTransliterator() {
+            super(new HashMap<Character, String>() {{
+                put('膶', "C");  put('膷', "c");
+                put('膯', "C");  put('膰', "c");
+                put('膼', "D");  put('膽', "d");
+                put('艩', "S");  put('拧', "s");
+                put('沤', "z");  put('啪', "z");
+            }});
+        }
+    }
+    
\ No newline at end of file
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/CzechTransliterator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/CzechTransliterator.java
new file mode 100644
index 0000000..bdd7c14
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/CzechTransliterator.java
@@ -0,0 +1,31 @@
+/*  Copyright (C) 2022-2024 Jos茅 Rebelo, Petr Kadlec
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.util.language.impl;
+
+import java.util.HashMap;
+
+import nodomain.freeyourgadget.gadgetbridge.util.language.SimpleTransliterator;
+
+public class CzechTransliterator extends SimpleTransliterator {
+    public CzechTransliterator() {
+        super(new HashMap<Character, String>() {{
+            put('艡',"r"); put('臎',"e"); put('媒',"y"); put('谩',"a"); put('铆',"i"); put('茅',"e");
+            put('贸',"o"); put('煤',"u"); put('暖',"u"); put('膹',"d"); put('钮',"t"); put('艌',"n");
+            put('鈥�', "\""); put('鈥�', "\""); put('鈥�', "'"); put('鈥�', "'");
+        }});
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/EstonianTransliterator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/EstonianTransliterator.java
new file mode 100644
index 0000000..eb68a78
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/EstonianTransliterator.java
@@ -0,0 +1,32 @@
+/*  Copyright (C) 2022-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.util.language.impl;
+
+import java.util.HashMap;
+
+import nodomain.freeyourgadget.gadgetbridge.util.language.SimpleTransliterator;
+
+public class EstonianTransliterator extends SimpleTransliterator {
+    public EstonianTransliterator() {
+        super(new HashMap<Character, String>() {{
+            put('盲', "a"); put('脛', "A");
+            put('枚', "o"); put('玫', "o");
+            put('脰', "O"); put('脮', "O");
+            put('眉', "u"); put('脺', "U");
+        }});
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/ExtendedAsciiTransliterator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/ExtendedAsciiTransliterator.java
new file mode 100644
index 0000000..3e125cc
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/ExtendedAsciiTransliterator.java
@@ -0,0 +1,29 @@
+/*  Copyright (C) 2022-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.util.language.impl;
+
+import java.util.HashMap;
+
+import nodomain.freeyourgadget.gadgetbridge.util.language.SimpleTransliterator;
+
+public class ExtendedAsciiTransliterator extends SimpleTransliterator {
+    public ExtendedAsciiTransliterator() {
+        super(new HashMap<Character, String>() {{
+            put('艙', "oe"); put('陋', "a"); put('潞', "o"); put('芦',"\""); put('禄',"\"");
+        }});
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/FlattenToAsciiTransliterator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/FlattenToAsciiTransliterator.java
new file mode 100644
index 0000000..edb2e94
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/FlattenToAsciiTransliterator.java
@@ -0,0 +1,40 @@
+/*  Copyright (C) 2022-2024 Arjan Schrijver, Jos茅 Rebelo, Petr Kadlec
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.util.language.impl;
+
+import java.nio.charset.StandardCharsets;
+import java.text.Normalizer;
+import java.util.HashMap;
+
+import nodomain.freeyourgadget.gadgetbridge.util.language.SimpleTransliterator;
+import nodomain.freeyourgadget.gadgetbridge.util.language.Transliterator;
+
+public class FlattenToAsciiTransliterator implements Transliterator {
+    @Override
+    public String transliterate(String txt) {
+        if (txt == null || txt.isEmpty()) {
+            return txt;
+        }
+
+        // Decompose the string into its compatible decomposition (splits base characters from accents/marks, and changes some characters to compatibility version)
+        txt = Normalizer.normalize(txt, Normalizer.Form.NFKD);
+        // Remove all marks (characters intended to be combined with another character), keeping the base glyphs
+        txt = txt.replaceAll("\\p{M}", "");
+        // Flatten the resulting string to ASCII
+        return new String(txt.getBytes(StandardCharsets.US_ASCII), StandardCharsets.US_ASCII);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/FrenchTransliterator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/FrenchTransliterator.java
new file mode 100644
index 0000000..1960e24
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/FrenchTransliterator.java
@@ -0,0 +1,32 @@
+/*  Copyright (C) 2022-2024 C茅dric Bellegarde
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.util.language.impl;
+
+import java.util.HashMap;
+
+import nodomain.freeyourgadget.gadgetbridge.util.language.SimpleTransliterator;
+
+public class FrenchTransliterator extends SimpleTransliterator {
+    public FrenchTransliterator() {
+        super(new HashMap<Character, String>() {{
+            put('脿',"a"); put('芒',"a");
+            put('茅',"e"); put('猫',"e"); put('锚',"e"); put('毛',"e");
+            put('霉',"u"); put('眉',"u");
+            put('每', "y"); put('莽',"c");
+        }});
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/GeorgianTransliterator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/GeorgianTransliterator.java
new file mode 100644
index 0000000..e094b90
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/GeorgianTransliterator.java
@@ -0,0 +1,40 @@
+/*  Copyright (C) 2023-2024 Petr Van臎k, roolx
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+    package nodomain.freeyourgadget.gadgetbridge.util.language.impl;
+
+    import java.util.HashMap;
+    
+    import nodomain.freeyourgadget.gadgetbridge.util.language.SimpleTransliterator;
+    
+    public class GeorgianTransliterator extends SimpleTransliterator {
+        public GeorgianTransliterator() {
+            super(new HashMap<Character, String>() {{
+                put('醿�', "a"); put('醿�', "b"); put('醿�', "g"); 
+                put('醿�', "d"); put('醿�', "e"); put('醿�', "v"); 
+                put('醿�', "z"); put('醿�', "T"); put('醿�', "i");
+                put('醿�', "k"); put('醿�', "l"); put('醿�', "m"); 
+                put('醿�', "n"); put('醿�', "o"); put('醿�', "p"); 
+                put('醿�', "J"); put('醿�', "r"); put('醿�', "s"); 
+                put('醿�', "t"); put('醿�', "u"); put('醿�', "f"); 
+                put('醿�', "q"); put('醿�', "R"); put('醿�', "y"); 
+                put('醿�', "S"); put('醿�', "C"); put('醿�', "c"); 
+                put('醿�', "Z"); put('醿�', "w"); put('醿�', "W"); 
+                put('醿�', "x"); put('醿�', "j"); put('醿�', "h"); 
+            }});
+        }
+    }
+    
\ No newline at end of file
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/GermanTransliterator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/GermanTransliterator.java
new file mode 100644
index 0000000..b1083e9
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/GermanTransliterator.java
@@ -0,0 +1,31 @@
+/*  Copyright (C) 2022-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.util.language.impl;
+
+import java.util.HashMap;
+
+import nodomain.freeyourgadget.gadgetbridge.util.language.SimpleTransliterator;
+
+public class GermanTransliterator extends SimpleTransliterator {
+    public GermanTransliterator() {
+        super(new HashMap<Character, String>() {{
+            put('盲',"ae"); put('枚',"oe"); put('眉',"ue");
+            put('脛',"Ae"); put('脰',"Oe"); put('脺',"脺e");
+            put('脽',"ss"); put('岷�',"SS");
+        }});
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/GreekTransliterator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/GreekTransliterator.java
new file mode 100644
index 0000000..f9c2bfa
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/GreekTransliterator.java
@@ -0,0 +1,36 @@
+/*  Copyright (C) 2022-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.util.language.impl;
+
+import java.util.HashMap;
+
+import nodomain.freeyourgadget.gadgetbridge.util.language.SimpleTransliterator;
+
+public class GreekTransliterator extends SimpleTransliterator {
+    public GreekTransliterator() {
+        super(new HashMap<Character, String>() {{
+            put('伪',"a");put('维',"a");put('尾',"v");put('纬',"g");put('未',"d");put('蔚',"e");put('苇',"e");put('味',"z");put('畏',"i");
+            put('萎',"i");put('胃',"th");put('喂',"i");put('委',"i");put('蠆',"i");put('螑',"i");put('魏',"k");put('位',"l");put('渭',"m");
+            put('谓',"n");put('尉',"ks");put('慰',"o");put('蠈',"o");put('蟺',"p");put('蟻',"r");put('蟽',"s");put('蟼',"s");put('蟿',"t");
+            put('蠀',"y");put('蠉',"y");put('蠇',"y");put('伟',"y");put('蠁',"f");put('蠂',"ch");put('蠄',"ps");put('蠅',"o");put('蠋',"o");
+            put('螒',"A");put('螁',"A");put('螔',"B");put('螕',"G");put('螖',"D");put('螘',"E");put('螆',"E");put('螙',"Z");put('螚',"I");
+            put('螇',"I");put('螛',"TH");put('螜',"I");put('螉',"I");put('为',"I");put('螝',"K");put('螞',"L");put('螠',"M");put('螡',"N");
+            put('螢',"KS");put('螣',"O");put('螌',"O");put('螤',"P");put('巍',"R");put('危',"S");put('韦',"T");put('违',"Y");put('螏',"Y");
+            put('潍',"Y");put('桅',"F");put('围',"CH");put('唯',"PS");put('惟',"O");put('螐',"O");
+        }});
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/HebrewTransliterator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/HebrewTransliterator.java
new file mode 100644
index 0000000..9847386
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/HebrewTransliterator.java
@@ -0,0 +1,32 @@
+/*  Copyright (C) 2022-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.util.language.impl;
+
+import java.util.HashMap;
+
+import nodomain.freeyourgadget.gadgetbridge.util.language.SimpleTransliterator;
+
+public class HebrewTransliterator extends SimpleTransliterator {
+    public HebrewTransliterator() {
+        super(new HashMap<Character, String>() {{
+            put('讗', "a"); put('讘', "b"); put('讙', "g");  put('讚', "d"); put('讛', "h"); put('讜', "u"); put('讝', "z"); put('讞', "kh");
+            put('讟', "t"); put('讬', "y"); put('讻', "c"); put('诇', "l"); put('诪', "m"); put('谞', "n"); put('住', "s");  put('注', "'");
+            put('驻', "p"); put('爪', "ts"); put('拽', "k");  put('专', "r"); put('砖', "sh"); put('转', "th"); put('祝', "f"); put('抓', "ts");
+            put('讱', "ch");put('诐', "m");put('谉', "n");
+        }});
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/HungarianTransliterator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/HungarianTransliterator.java
new file mode 100644
index 0000000..4749a58
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/HungarianTransliterator.java
@@ -0,0 +1,36 @@
+/*  Copyright (C) 2023-2024 ssilverr
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.util.language.impl;
+
+import java.util.HashMap;
+
+import nodomain.freeyourgadget.gadgetbridge.util.language.SimpleTransliterator;
+
+public class HungarianTransliterator extends SimpleTransliterator {
+    public HungarianTransliterator() {
+        super(new HashMap<Character, String>() {{
+            put('谩', "a");
+            put('茅', "e");
+            put('铆', "i");
+            put('贸', "o");
+            put('枚', "o");
+            put('艖', "o");
+            put('眉', "u");
+            put('疟', "u");
+        }});
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/IcelandicTransliterator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/IcelandicTransliterator.java
new file mode 100644
index 0000000..0ecf482
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/IcelandicTransliterator.java
@@ -0,0 +1,29 @@
+/*  Copyright (C) 2022-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.util.language.impl;
+
+import java.util.HashMap;
+
+import nodomain.freeyourgadget.gadgetbridge.util.language.SimpleTransliterator;
+
+public class IcelandicTransliterator extends SimpleTransliterator {
+    public IcelandicTransliterator() {
+        super(new HashMap<Character, String>() {{
+            put('脼',"Th"); put('镁',"th"); put('脨',"D"); put('冒',"d");
+        }});
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/KoreanTransliterator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/KoreanTransliterator.java
new file mode 100644
index 0000000..b19e1df
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/KoreanTransliterator.java
@@ -0,0 +1,344 @@
+/*  Copyright (C) 2022-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.util.language.impl;
+
+import java.text.Normalizer;
+import java.util.Optional;
+
+import nodomain.freeyourgadget.gadgetbridge.util.language.Transliterator;
+
+// Implements Revised Romanization of Korean as well as we can without understanding any grammar.
+//
+// https://en.wikipedia.org/wiki/Revised_Romanization_of_Korean
+public class KoreanTransliterator implements Transliterator {
+    // https://en.wikipedia.org/wiki/Hangul_Jamo_%28Unicode_block%29
+    private static final char JAMO_BLOCK_START = 0x1100;
+    private static final char JAMO_BLOCK_END = 0x11FF;
+    // https://en.wikipedia.org/wiki/Hangul_Syllables
+    private static final char SYLLABLES_BLOCK_START = 0xAC00;
+    private static final char SYLLABLES_BLOCK_END = 0xD7A3;
+    // https://en.wikipedia.org/wiki/Hangul_Compatibility_Jamo
+    private static final char COMPAT_JAMO_BLOCK_START = 0x3131;
+    private static final char COMPAT_JAMO_BLOCK_END = 0x318E;
+
+    // Returns whether a char is in the given block. Both bounds are inclusive.
+    private static boolean inRange(char c, char start, char end) {
+        return c >= start && c <= end;
+    }
+
+    // User input consisting of isolated jamo is usually mapped to the KS X 1001 compatibility
+    // block, but jamo resulting from decomposed syllables are mapped to the modern one. This
+    // function maps compat jamo to modern ones where possible and returns all other characters
+    // unmodified.
+    //
+    // https://en.wikipedia.org/wiki/Hangul_Compatibility_Jamo
+    // https://en.wikipedia.org/wiki/Hangul_Jamo_%28Unicode_block%29
+    private static char decompatJamo(char jamo) {
+        // KS X 1001 Hangul filler, not used in modern Unicode. A useful landmark in the
+        // compatibility jamo block.
+        // https://en.wikipedia.org/wiki/KS_X_1001#Hangul_Filler
+        final char HANGUL_FILLER = 0x3164;
+
+        // Don't do anything to characters outside the compatibility jamo block.
+        if (!inRange(jamo, COMPAT_JAMO_BLOCK_START, COMPAT_JAMO_BLOCK_END)) { return jamo; }
+
+        // Vowels are contiguous, in the same order, and unambiguous, so it's a simple offset.
+        if (jamo >= 0x314F && jamo < HANGUL_FILLER) {
+            return (char)(jamo - 0x1FEE);
+        }
+
+        // Consonants are organized differently. No clean way to do this.
+        //
+        // The compatibility jamo block doesn't distinguish between Choseong (leading) and Jongseong
+        // (final) positions, but the modern block does. We map to Choseong here.
+        switch (jamo) {
+            case 0x3131: return 0x1100;     // 銊�
+            case 0x3132: return 0x1101;     // 銊�
+            case 0x3134: return 0x1102;     // 銊�
+            case 0x3137: return 0x1103;     // 銊�
+            case 0x3138: return 0x1104;     // 銊�
+            case 0x3139: return 0x1105;     // 銊�
+            case 0x3141: return 0x1106;     // 銋�
+            case 0x3142: return 0x1107;     // 銋�
+            case 0x3143: return 0x1108;     // 銋�
+            case 0x3145: return 0x1109;     // 銋�
+            case 0x3146: return 0x110A;     // 銋�
+            case 0x3147: return 0x110B;     // 銋�
+            case 0x3148: return 0x110C;     // 銋�
+            case 0x3149: return 0x110D;     // 銋�
+            case 0x314A: return 0x110E;     // 銋�
+            case 0x314B: return 0x110F;     // 銋�
+            case 0x314C: return 0x1110;     // 銋�
+            case 0x314D: return 0x1111;     // 銋�
+            case 0x314E: return 0x1112;     // 銋�
+        }
+
+        // The rest of the compatibility block consists of archaic compounds that are unlikely to be
+        // encountered in modern systems. Just leave them alone.
+        return jamo;
+    }
+
+    // Transliterates jamo one at a time. Returns its input if it isn't in the modern jamo block.
+    private static String transliterateSingleJamo(char jamo) {
+        jamo = decompatJamo(jamo);
+
+        switch (jamo) {
+            // Choseong (leading position consonants)
+            case 0x1100: return "g";    // 銊�
+            case 0x1101: return "kk";   // 銊�
+            case 0x1102: return "n";    // 銊�
+            case 0x1103: return "d";    // 銊�
+            case 0x1104: return "tt";   // 銊�
+            case 0x1105: return "r";    // 銊�
+            case 0x1106: return "m";    // 銋�
+            case 0x1107: return "b";    // 銋�
+            case 0x1108: return "pp";   // 銋�
+            case 0x1109: return "s";    // 銋�
+            case 0x110A: return "ss";   // 銋�
+            case 0x110B: return "";     // 銋�
+            case 0x110C: return "j";    // 銋�
+            case 0x110D: return "jj";   // 銋�
+            case 0x110E: return "ch";   // 銋�
+            case 0x110F: return "k";    // 銋�
+            case 0x1110: return "t";    // 銋�
+            case 0x1111: return "p";    // 銋�
+            case 0x1112: return "h";    // 銋�
+            // Jungseong (vowels)
+            case 0x1161: return "a";    // 銋�
+            case 0x1162: return "ae";   // 銋�
+            case 0x1163: return "ya";   // 銋�
+            case 0x1164: return "yae";  // 銋�
+            case 0x1165: return "eo";   // 銋�
+            case 0x1166: return "e";    // 銋�
+            case 0x1167: return "yeo";  // 銋�
+            case 0x1168: return "ye";   // 銋�
+            case 0x1169: return "o";    // 銋�
+            case 0x116A: return "wa";   // 銋�
+            case 0x116B: return "wae";  // 銋�
+            case 0x116C: return "oe";   // 銋�
+            case 0x116D: return "yo";   // 銋�
+            case 0x116E: return "u";    // 銋�
+            case 0x116F: return "wo";   // 銋�
+            case 0x1170: return "we";   // 銋�
+            case 0x1171: return "wi";   // 銋�
+            case 0x1172: return "yu";   // 銋�
+            case 0x1173: return "eu";   // 銋�
+            case 0x1174: return "ui";   // 銋�
+            case 0x1175: return "i";    // 銋�
+            // Jongseong (final position consonants)
+            case 0x11A8: return "k";    // 銊�
+            case 0x11A9: return "k";    // 銊�
+            case 0x11AB: return "n";    // 銊�
+            case 0x11AE: return "t";    // 銊�
+            case 0x11AF: return "l";    // 銊�
+            case 0x11B7: return "m";    // 銋�
+            case 0x11B8: return "p";    // 銋�
+            case 0x11BA: return "t";    // 銋�
+            case 0x11BB: return "t";    // 銋�
+            case 0x11BC: return "ng";   // 銋�
+            case 0x11BD: return "t";    // 銋�
+            case 0x11BE: return "t";    // 銋�
+            case 0x11BF: return "k";    // 銋�
+            case 0x11C0: return "t";    // 銋�
+            case 0x11C1: return "p";    // 銋�
+            case 0x11C2: return "t";    // 銋�
+        }
+
+        // Input was not jamo.
+        return String.valueOf(jamo);
+    }
+
+    // Some combinations of ending jamo in one syllable and initial jamo in the next are romanized
+    // irregularly. These exceptions are called "special provisions". In cases where multiple
+    // romanizations are permitted, we use the one that's least commonly used elsewhere.
+    //
+    // Returns no value if either character is not in the modern jamo block, or if there is no
+    // special provision for that pair of jamo.
+    public static Optional<String> transliterateSpecialProvisions(char previousEnding, char nextInitial) {
+        // Special provisions only apply if both characters are in the modern jamo block.
+        if (!inRange(previousEnding, JAMO_BLOCK_START, JAMO_BLOCK_END)) { return Optional.empty(); }
+        if (!inRange(nextInitial, JAMO_BLOCK_START, JAMO_BLOCK_END)) { return Optional.empty(); }
+
+        // Jongseong (final position) 銋� has a number of special provisions.
+        if (previousEnding == 0x11C2) { // 銋�
+            switch (nextInitial) {
+                case 0x110B: return Optional.of("h");       // 銋�
+                case 0x1100: return Optional.of("k");       // 銊�
+                case 0x1102: return Optional.of("nn");      // 銊�
+                case 0x1103: return Optional.of("t");       // 銊�
+                case 0x1105: return Optional.of("nn");      // 銊�
+                case 0x1106: return Optional.of("nm");      // 銋�
+                case 0x1107: return Optional.of("p");       // 銋�
+                case 0x1109: return Optional.of("hs");      // 銋�
+                case 0x110C: return Optional.of("ch");      // 銋�
+                case 0x1112: return Optional.of("t");       // 銋�
+                default: return Optional.empty();
+            }
+        }
+
+        // Otherwise, special provisions are denser when grouped by the second jamo.
+        switch (nextInitial) {
+            case 0x1100: // 銊�
+                switch (previousEnding) {
+                    case 0x11AB: return Optional.of("n-g"); // 銊�
+                    default: return Optional.empty();
+                }
+            case 0x1102: // 銊�
+                switch (previousEnding) {
+                    case 0x11A8: return Optional.of("ngn"); // 銊�
+                    case 0x11AE:                            // 銊�
+                    case 0x11BA:                            // 銋�
+                    case 0x11BD:                            // 銋�
+                    case 0x11BE:                            // 銋�
+                    case 0x11C0:                            // 銋�
+                        return Optional.of("nn");
+                    case 0x11AF: return Optional.of("ll");  // 銊�
+                    case 0x11B8: return Optional.of("mn");  // 銋�
+                    default: return Optional.empty();
+                }
+            case 0x1105: // 銊�
+                switch (previousEnding) {
+                    case 0x11A8:                            // 銊�
+                    case 0x11AB:                            // 銊�
+                    case 0x11AF:                            // 銊�
+                        return Optional.of("ll");
+                    case 0x11AE:                            // 銊�
+                    case 0x11BA:                            // 銋�
+                    case 0x11BD:                            // 銋�
+                    case 0x11BE:                            // 銋�
+                    case 0x11C0:                            // 銋�
+                        return Optional.of("nn");
+                    case 0x11B7:                            // 銋�
+                    case 0x11B8:                            // 銋�
+                        return Optional.of("mn");
+                    case 0x11BC: return Optional.of("ngn"); // 銋�
+                    default: return Optional.empty();
+                }
+            case 0x1106: // 銋�
+                switch (previousEnding) {
+                    case 0x11A8: return Optional.of("ngm"); // 銊�
+                    case 0x11AE:                            // 銊�
+                    case 0x11BA:                            // 銋�
+                    case 0x11BD:                            // 銋�
+                    case 0x11BE:                            // 銋�
+                    case 0x11C0:                            // 銋�
+                        return Optional.of("nm");
+                    case 0x11B8: return Optional.of("mm");  // 銋�
+                    default: return Optional.empty();
+                }
+            case 0x110B: // 銋�
+                switch (previousEnding) {
+                    case 0x11A8: return Optional.of("g");   // 銊�
+                    case 0x11AE: return Optional.of("d");   // 銊�
+                    case 0x11AF: return Optional.of("r");   // 銊�
+                    case 0x11B8: return Optional.of("b");   // 銋�
+                    case 0x11BA: return Optional.of("s");   // 銋�
+                    case 0x11BC: return Optional.of("ng-"); // 銋�
+                    case 0x11BD: return Optional.of("j");   // 銋�
+                    case 0x11BE: return Optional.of("ch");  // 銋�
+                    default: return Optional.empty();
+                }
+            case 0x110F: // 銋�
+                switch (previousEnding) {
+                    case 0x11A8: return Optional.of("k-k"); // 銊�
+                    default: return Optional.empty();
+                }
+            case 0x1110: // 銋�
+                switch (previousEnding) {
+                    case 0x11AE:                            // 銊�
+                    case 0x11BA:                            // 銋�
+                    case 0x11BD:                            // 銋�
+                    case 0x11BE:                            // 銋�
+                    case 0x11C0:                            // 銋�
+                        return Optional.of("t-t");
+                    default: return Optional.empty();
+                }
+            case 0x1111: // 銋�
+                switch (previousEnding) {
+                    case 0x11B8: return Optional.of("p-p"); // 銋�
+                    default: return Optional.empty();
+                }
+            default: return Optional.empty();
+        }
+    }
+
+    // Decompose a syllable into several jamo. Returns its input if that isn't possible.
+    public static char[] decompose(char syllable) {
+        String normalized = Normalizer.normalize(String.valueOf(syllable), Normalizer.Form.NFD);
+        return normalized.toCharArray();
+    }
+
+    // Transliterate any Hangul in the given string. Leaves any non-Hangul characters unmodified.
+    @Override
+    public String transliterate(String txt) {
+        if (txt == null || txt.isEmpty()) {
+            return txt;
+        }
+
+        // Most of the bulk of these loops is for handling special provisions - situations where the
+        // last jamo of one syllable and the first of the next need to be romanized as a pair in an
+        // irregular way.
+        StringBuilder builder = new StringBuilder();
+        boolean nextInitialJamoConsumed = false;
+        char[] syllables = txt.toCharArray();
+        for (int i = 0; i < syllables.length; i++) {
+            char thisSyllable = syllables[i];
+            // If this isn't in any of the Hangul blocks we know about, emit it as-is.
+            if (!inRange(thisSyllable, JAMO_BLOCK_START, JAMO_BLOCK_END)
+                    && !inRange(thisSyllable, SYLLABLES_BLOCK_START, SYLLABLES_BLOCK_END)
+                    && !inRange(thisSyllable, COMPAT_JAMO_BLOCK_START, COMPAT_JAMO_BLOCK_END)) {
+                builder.append(thisSyllable);
+                continue;
+            }
+
+            char[] theseJamo = decompose(thisSyllable);
+            for (int j = 0; j < theseJamo.length; j++) {
+                char thisJamo = theseJamo[j];
+
+                // If we already transliterated the first jamo of this syllable as part of a special
+                // provision, skip it. Otherwise, handle it in the unconditional else branch.
+                if (j == 0 && nextInitialJamoConsumed) {
+                    nextInitialJamoConsumed = false;
+                    continue;
+                }
+
+                // If this is the last jamo of this syllable and not the last syllable of the
+                // string, check for special provisions. If the next char is whitespace or not
+                // Hangul, it's the responsibility of transliterateSpecialProvisions() to return no
+                // value.
+                if (j == theseJamo.length - 1 && i < syllables.length - 1) {
+                    char nextSyllable = syllables[i + 1];
+                    char nextJamo = decompose(nextSyllable)[0];
+                    Optional<String> specialProvision = transliterateSpecialProvisions(thisJamo, nextJamo);
+                    if (specialProvision.isPresent()) {
+                        builder.append(specialProvision.get());
+                        nextInitialJamoConsumed = true;
+                    } else {
+                        // No special provision applies. Transliterate in isolation.
+                        builder.append(transliterateSingleJamo(thisJamo));
+                    }
+                    continue;
+                }
+
+                // Everything else is transliterated in isolation.
+                builder.append(transliterateSingleJamo(thisJamo));
+            }
+        }
+
+        return builder.toString();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/LatvianTransliterator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/LatvianTransliterator.java
new file mode 100644
index 0000000..9b0f1f5
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/LatvianTransliterator.java
@@ -0,0 +1,30 @@
+/*  Copyright (C) 2023-2024 Davis Mosenkovs
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.util.language.impl;
+
+import java.util.HashMap;
+
+import nodomain.freeyourgadget.gadgetbridge.util.language.SimpleTransliterator;
+
+public class LatvianTransliterator extends SimpleTransliterator {
+    public LatvianTransliterator() {
+        super(new HashMap<Character, String>() {{
+            put('膩', "a"); put('膷', "c"); put('膿', "e"); put('模', "g"); put('墨', "i"); put('姆', "k"); put('募', "l"); put('艈', "n"); put('拧', "s"); put('奴', "u"); put('啪', "z");
+            put('膧', "A"); put('膶', "C"); put('膾', "E"); put('蘑', "G"); put('莫', "I"); put('亩', "K"); put('幕', "L"); put('艆', "N"); put('艩', "S"); put('弄', "U"); put('沤', "Z");
+        }});
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/LithuanianTransliterator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/LithuanianTransliterator.java
new file mode 100644
index 0000000..e406a0d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/LithuanianTransliterator.java
@@ -0,0 +1,29 @@
+/*  Copyright (C) 2022-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.util.language.impl;
+
+import java.util.HashMap;
+
+import nodomain.freeyourgadget.gadgetbridge.util.language.SimpleTransliterator;
+
+public class LithuanianTransliterator extends SimpleTransliterator {
+    public LithuanianTransliterator() {
+        super(new HashMap<Character, String>() {{
+            put('膮', "a"); put('膷', "c"); put('臋', "e"); put('臈', "e"); put('寞', "i"); put('拧', "s"); put('懦', "u"); put('奴', "u"); put('啪', "z");
+        }});
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/PersianTransliterator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/PersianTransliterator.java
new file mode 100644
index 0000000..4fb5d47
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/PersianTransliterator.java
@@ -0,0 +1,33 @@
+/*  Copyright (C) 2022-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.util.language.impl;
+
+import java.util.HashMap;
+
+import nodomain.freeyourgadget.gadgetbridge.util.language.SimpleTransliterator;
+
+public class PersianTransliterator extends SimpleTransliterator {
+    public PersianTransliterator() {
+        super(new HashMap<Character, String>() {{
+            // Persian (Farsi)
+            put('倬', "p"); put('趩', "ch"); put('跇', "zh"); put('讴', "k"); put('诏', "g"); put('蹖', "y"); put('鈥�', " ");
+            put('責', "?"); put('侏', "%"); put('貨', ";"); put('貙', ","); put('郾', "1"); put('鄄', "2"); put('鄢', "3");
+            put('鄞', "4"); put('鄣', "5"); put('鄱', "6"); put('鄯', "7"); put('鄹', "8"); put('酃', "9"); put('郯', "0");
+            put('禄', "<"); put('芦', ">"); put('賽', "e"); put('賻', "a"); put('購', "o"); put('賾', "");
+        }});
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/PolishTransliterator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/PolishTransliterator.java
new file mode 100644
index 0000000..b39ad11
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/PolishTransliterator.java
@@ -0,0 +1,30 @@
+/*  Copyright (C) 2022-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.util.language.impl;
+
+import java.util.HashMap;
+
+import nodomain.freeyourgadget.gadgetbridge.util.language.SimpleTransliterator;
+
+public class PolishTransliterator extends SimpleTransliterator {
+    public PolishTransliterator() {
+        super(new HashMap<Character, String>() {{
+            // Polish
+            put('艁', "L"); put('艂', "l");
+        }});
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/RussianTransliterator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/RussianTransliterator.java
new file mode 100644
index 0000000..4aafa76
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/RussianTransliterator.java
@@ -0,0 +1,35 @@
+/*  Copyright (C) 2022-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.util.language.impl;
+
+import java.util.HashMap;
+
+import nodomain.freeyourgadget.gadgetbridge.util.language.SimpleTransliterator;
+
+public class RussianTransliterator extends SimpleTransliterator {
+    public RussianTransliterator() {
+        super(new HashMap<Character, String>() {{
+            put('邪', "a"); put('斜', "b"); put('胁', "v");  put('谐', "g"); put('写', "d"); put('械', "e"); put('褢', "jo"); put('卸', "zh");
+            put('蟹', "z"); put('懈', "i"); put('泄', "jj"); put('泻', "k"); put('谢', "l"); put('屑', "m"); put('薪', "n");  put('芯', "o");
+            put('锌', "p"); put('褉', "r"); put('褋', "s");  put('褌', "t"); put('褍', "u"); put('褎', "f"); put('褏', "kh"); put('褑', "c");
+            put('褔', "ch");put('褕', "sh");put('褖', "shh");put('褗', "\"");put('褘', "y"); put('褜', "'"); put('褝', "eh"); put('褞', "ju");
+            put('褟', "ja");
+
+            put('玫', "o"); put('膷', "c");
+        }});
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/ScandinavianTransliterator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/ScandinavianTransliterator.java
new file mode 100644
index 0000000..a7c7f3a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/ScandinavianTransliterator.java
@@ -0,0 +1,31 @@
+/*  Copyright (C) 2022-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.util.language.impl;
+
+import java.util.HashMap;
+
+import nodomain.freeyourgadget.gadgetbridge.util.language.SimpleTransliterator;
+
+public class ScandinavianTransliterator extends SimpleTransliterator {
+    public ScandinavianTransliterator() {
+        super(new HashMap<Character, String>() {{
+            put('脝',"Ae"); put('忙',"ae");
+            put('脴',"Oe"); put('酶',"oe");
+            put('脜',"Aa"); put('氓',"aa");
+        }});
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/SerbianTransliterator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/SerbianTransliterator.java
new file mode 100644
index 0000000..e7c4c0d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/SerbianTransliterator.java
@@ -0,0 +1,68 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+    package nodomain.freeyourgadget.gadgetbridge.util.language.impl;
+
+import java.util.HashMap;
+
+import nodomain.freeyourgadget.gadgetbridge.util.language.SimpleTransliterator;
+
+public class SerbianTransliterator extends SimpleTransliterator {
+    public SerbianTransliterator() {
+        super(new HashMap<Character, String>() {{
+            // As per https://en.wikipedia.org/wiki/Serbian_Cyrillic_alphabet#Modern_alphabet
+            put('袗', "A");   put('邪', "a");
+            put('袘', "B");   put('斜', "b");
+            put('袙', "V");   put('胁', "v");
+            put('袚', "G");   put('谐', "g");
+            put('袛', "D");   put('写', "d");
+            put('袀', "Dj");   put('褣', "dj"); // from 膼 / 膽 - from suggestion in #3727
+            put('袝', "E");   put('械', "e");
+            put('袞', "Z");   put('卸', "z"); // from 沤 / 啪
+            put('袟', "Z");   put('蟹', "z");
+            put('袠', "I");   put('懈', "i");
+            put('袌', "J");   put('褬', "j");
+            put('袣', "K");   put('泻', "k");
+            put('袥', "L");   put('谢', "l");
+            put('袎', "Lj");  put('褭', "lj");
+            put('袦', "M");   put('屑', "m");
+            put('袧', "N");   put('薪', "n");
+            put('袏', "Nj");  put('褮', "nj");
+            put('袨', "O");   put('芯', "o");
+            put('袩', "P");   put('锌', "p");
+            put('袪', "R");   put('褉', "r");
+            put('小', "S");   put('褋', "s");
+            put('孝', "T");   put('褌', "t");
+            put('袐', "C");   put('褯', "c"); // from 膯 / 膰
+            put('校', "U");   put('褍', "u");
+            put('肖', "F");   put('褎', "f");
+            put('啸', "H");   put('褏', "h");
+            put('笑', "C");   put('褑', "c");
+            put('效', "C");   put('褔', "c"); // from 膶 / 膷
+            put('袕', "Dz");  put('褵', "dz"); // from D啪 / d啪
+            put('楔', "S");   put('褕', "s"); // From 艩 / 拧
+
+            // Not in the table, pulled from Croatian
+            put('膯', "C");  put('膰', "c");
+            put('膼', "Dj");  put('膽', "dj");
+            put('艩', "S");  put('拧', "s");
+            put('沤', "z");  put('啪', "z");
+
+            // Suggested in #3727
+            put('膶', "C"); put('膷', "c");
+        }}, false);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/TurkishTransliterator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/TurkishTransliterator.java
new file mode 100644
index 0000000..99bda40
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/TurkishTransliterator.java
@@ -0,0 +1,29 @@
+/*  Copyright (C) 2022-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.util.language.impl;
+
+import java.util.HashMap;
+
+import nodomain.freeyourgadget.gadgetbridge.util.language.SimpleTransliterator;
+
+public class TurkishTransliterator extends SimpleTransliterator {
+    public TurkishTransliterator() {
+        super(new HashMap<Character, String>() {{
+            put('谋',"i");
+        }});
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/UkranianTransliterator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/UkranianTransliterator.java
new file mode 100644
index 0000000..280b2c6
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/UkranianTransliterator.java
@@ -0,0 +1,29 @@
+/*  Copyright (C) 2022-2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.util.language.impl;
+
+import java.util.HashMap;
+
+import nodomain.freeyourgadget.gadgetbridge.util.language.SimpleTransliterator;
+
+public class UkranianTransliterator extends SimpleTransliterator {
+    public UkranianTransliterator() {
+        super(new HashMap<Character, String>() {{
+            put('覒', "gh"); put('褦', "je"); put('褨', "i"); put('褩', "ji"); put('覑', "GH"); put('袆', "JE"); put('袉', "I"); put('袊', "JI");
+        }});
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/preferences/DevicePrefs.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/preferences/DevicePrefs.java
new file mode 100644
index 0000000..d9cc1b8
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/preferences/DevicePrefs.java
@@ -0,0 +1,118 @@
+/*  Copyright (C) 2016-2024 Andreas Shimokawa, Anemograph, Carsten Pfeiffer,
+    Daniel Dakhno, Daniele Gobbetti, Davis Mosenkovs, Dikay900, Felix Konstantin
+    Maurer, Jos茅 Rebelo, Petr Van臎k, Johannes Krude
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.util.preferences;
+
+import java.util.Locale;
+
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.*;
+
+import android.content.SharedPreferences;
+import android.text.format.DateFormat;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.model.BatteryConfig;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+
+public class DevicePrefs extends Prefs {
+    private GBDevice gbDevice;
+
+    public DevicePrefs(final SharedPreferences preferences, GBDevice gbDevice) {
+        super(preferences);
+        this.gbDevice = gbDevice;
+    }
+
+    public boolean getBatteryShowInNotification(final int batteryIndex) {
+        return getBoolean(PREF_BATTERY_SHOW_IN_NOTIFICATION + batteryIndex, true);
+    }
+
+    public boolean getBatteryNotifyLowEnabled(final BatteryConfig batteryConfig) {
+        return getBoolean(PREF_BATTERY_NOTIFY_LOW_ENABLED + batteryConfig.getBatteryIndex(), true);
+    }
+
+    public int getBatteryNotifyLowThreshold(final BatteryConfig batteryConfig) {
+        return getInt(PREF_BATTERY_NOTIFY_LOW_THRESHOLD + batteryConfig.getBatteryIndex(), batteryConfig.getDefaultLowThreshold());
+    }
+
+    public boolean getBatteryNotifyFullEnabled(final BatteryConfig batteryConfig) {
+        return getBoolean(PREF_BATTERY_NOTIFY_FULL_ENABLED + batteryConfig.getBatteryIndex(), true);
+    }
+
+    public int getBatteryNotifyFullThreshold(final BatteryConfig batteryConfig) {
+        return getInt(PREF_BATTERY_NOTIFY_FULL_THRESHOLD + batteryConfig.getBatteryIndex(), batteryConfig.getDefaultFullThreshold());
+    }
+
+    public boolean getBatteryPollingEnabled() {
+        return getBoolean(PREF_BATTERY_POLLING_ENABLE, true);
+    }
+
+    public int getBatteryPollingIntervalMinutes() {
+        return getInt(PREF_BATTERY_POLLING_INTERVAL, 15);
+    }
+
+    public boolean getFetchUnknownFiles() {
+        return getBoolean("fetch_unknown_files", false);
+    }
+
+    public String getTimeFormat() {
+        String timeFormat = getString(DeviceSettingsPreferenceConst.PREF_TIMEFORMAT, DeviceSettingsPreferenceConst.PREF_TIMEFORMAT_AUTO);
+        if (DeviceSettingsPreferenceConst.PREF_TIMEFORMAT_AUTO.equals(timeFormat)) {
+            if (DateFormat.is24HourFormat(GBApplication.getContext())) {
+                timeFormat = DeviceSettingsPreferenceConst.PREF_TIMEFORMAT_24H;
+            } else {
+                timeFormat = DeviceSettingsPreferenceConst.PREF_TIMEFORMAT_12H;
+            }
+        }
+
+        return timeFormat;
+    }
+
+    public String getDateFormatDayMonthOrder() {
+        String dateFormat = getString(DeviceSettingsPreferenceConst.PREF_DATEFORMAT, DeviceSettingsPreferenceConst.PREF_DATEFORMAT_AUTO);
+        if (DeviceSettingsPreferenceConst.PREF_TIMEFORMAT_AUTO.equals(dateFormat)) {
+            String pattern = DateFormat.getBestDateTimePattern(Locale.getDefault(), "dM");
+            boolean quoted = false;
+            for (char c: pattern.toCharArray()) {
+                if (c == '\'') {
+                    quoted = !quoted;
+                    continue;
+                }
+                if (quoted)
+                    continue;
+                if (c == 'd')
+                    return DeviceSettingsPreferenceConst.PREF_DATEFORMAT_DAY_MONTH;
+                if (c == 'M' || c == 'L')
+                    return DeviceSettingsPreferenceConst.PREF_DATEFORMAT_MONTH_DAY;
+            }
+            return DeviceSettingsPreferenceConst.PREF_DATEFORMAT_DAY_MONTH;
+        }
+
+        return dateFormat;
+    }
+
+    public int getReservedReminderCalendarSlots() {
+        if (!gbDevice.getDeviceCoordinator().getReserveReminderSlotsForCalendar())
+            return 0;
+        if (!getBoolean(DeviceSettingsPreferenceConst.PREF_SYNC_CALENDAR, false))
+            return 0;
+        return getInt(DeviceSettingsPreferenceConst.PREF_RESERVE_REMINDERS_CALENDAR, 9);
+    }
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/preferences/GBSimpleSummaryProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/preferences/GBSimpleSummaryProvider.java
new file mode 100644
index 0000000..3035f68
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/preferences/GBSimpleSummaryProvider.java
@@ -0,0 +1,40 @@
+package nodomain.freeyourgadget.gadgetbridge.util.preferences;
+
+import android.text.TextUtils;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.StringRes;
+import androidx.preference.EditTextPreference;
+import androidx.preference.Preference;
+
+/**
+ * Like the EditTextPreference.SimpleSummaryProvider, but with a customizable not-set string.
+ */
+public final class GBSimpleSummaryProvider implements Preference.SummaryProvider<EditTextPreference> {
+    private final String defaultText;
+
+    @StringRes
+    private final int templateString;
+
+    public GBSimpleSummaryProvider(final String defaultText) {
+        this(defaultText, 0);
+    }
+
+    public GBSimpleSummaryProvider(final String defaultText, final int templateString) {
+        this.defaultText = defaultText;
+        this.templateString = templateString;
+    }
+
+    @Nullable
+    @Override
+    public CharSequence provideSummary(@NonNull final EditTextPreference preference) {
+        if (TextUtils.isEmpty(preference.getText())) {
+            return defaultText;
+        } else if (templateString != 0) {
+            return preference.getContext().getString(templateString, preference.getText());
+        } else {
+            return preference.getText();
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/preferences/MinMaxTextWatcher.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/preferences/MinMaxTextWatcher.java
new file mode 100644
index 0000000..1dcb897
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/preferences/MinMaxTextWatcher.java
@@ -0,0 +1,59 @@
+package nodomain.freeyourgadget.gadgetbridge.util.preferences;
+
+import android.text.Editable;
+import android.text.TextUtils;
+import android.text.TextWatcher;
+import android.widget.EditText;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+
+public class MinMaxTextWatcher implements TextWatcher {
+    private final EditText editText;
+    private final int min;
+    private final int max;
+    private final boolean allowEmpty;
+
+    public MinMaxTextWatcher(final EditText editText, final int min, final int max) {
+        this(editText, min, max, false);
+    }
+
+    public MinMaxTextWatcher(final EditText editText, final int min, final int max, final boolean allowEmpty) {
+        this.editText = editText;
+        this.min = min;
+        this.max = max;
+        this.allowEmpty = allowEmpty;
+    }
+
+    @Override
+    public void beforeTextChanged(final CharSequence s, final int start, final int count, final int after) {
+    }
+
+    @Override
+    public void onTextChanged(final CharSequence s, final int start, final int before, final int count) {
+    }
+
+    @Override
+    public void afterTextChanged(final Editable editable) {
+        if (TextUtils.isEmpty(editable.toString()) && allowEmpty) {
+            editText.getRootView().findViewById(android.R.id.button1)
+                    .setEnabled(true);
+            return;
+        }
+
+        try {
+            final int val = Integer.parseInt(editable.toString());
+            editText.getRootView().findViewById(android.R.id.button1)
+                    .setEnabled(val >= min && val <= max);
+            if (val < min) {
+                editText.setError(editText.getContext().getString(R.string.min_val, min));
+            } else if (val > max) {
+                editText.setError(editText.getContext().getString(R.string.max_val, max));
+            } else {
+                editText.setError(null);
+            }
+        } catch (final NumberFormatException e) {
+            editText.getRootView().findViewById(android.R.id.button1)
+                    .setEnabled(false);
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/preferences/PreferenceCategoryMultiline.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/preferences/PreferenceCategoryMultiline.java
new file mode 100644
index 0000000..7d870ce
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/preferences/PreferenceCategoryMultiline.java
@@ -0,0 +1,35 @@
+package nodomain.freeyourgadget.gadgetbridge.util.preferences;
+
+import android.content.Context;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.preference.PreferenceCategory;
+import androidx.preference.PreferenceViewHolder;
+
+public class PreferenceCategoryMultiline extends PreferenceCategory {
+
+    private int maxSummaryLines = 5;
+
+    public PreferenceCategoryMultiline(Context ctx) {
+        super(ctx, null);
+    }
+
+    public PreferenceCategoryMultiline(Context ctx, int maxSummaryLines) {
+        super(ctx, null);
+        this.maxSummaryLines = maxSummaryLines;
+    }
+
+    @Override
+    public void onBindViewHolder(@NonNull PreferenceViewHolder holder) {
+        super.onBindViewHolder(holder);
+
+        TextView summary = (TextView) holder.findViewById(android.R.id.summary);
+        if (summary == null)
+            return;
+        if (maxSummaryLines == 0 || maxSummaryLines == 1)
+            return;
+        summary.setSingleLine(false);
+        summary.setMaxLines(maxSummaryLines);
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/protobuf/ProtobufUtils.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/protobuf/ProtobufUtils.java
new file mode 100644
index 0000000..d25538e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/protobuf/ProtobufUtils.java
@@ -0,0 +1,40 @@
+/*  Copyright (C) 2022-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.util.protobuf;
+
+import java.io.ByteArrayOutputStream;
+
+public class ProtobufUtils {
+    public static byte[] encode_varint(int value){
+        if(value == 0){
+            return new byte[]{0x00};
+        }
+
+        ByteArrayOutputStream bytes = new ByteArrayOutputStream(4);
+
+        while(value > 0){
+            byte newByte = (byte)(value & 0b01111111);
+            value >>= 7;
+            if(value != 0){
+                newByte |= 0b10000000;
+            }
+            bytes.write(newByte);
+        }
+
+        return bytes.toByteArray();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/protobuf/messagefields/BytesMessageField.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/protobuf/messagefields/BytesMessageField.java
new file mode 100644
index 0000000..1dc9a22
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/protobuf/messagefields/BytesMessageField.java
@@ -0,0 +1,34 @@
+/*  Copyright (C) 2022-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.util.protobuf.messagefields;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+public class BytesMessageField extends MessageField{
+    private byte[] fieldValueBytes;
+
+    protected BytesMessageField(int fieldNumber, byte[] value) {
+        super(fieldNumber, FieldType.LENGTH_DELIMITED);
+        fieldValueBytes = value;
+    }
+
+    @Override
+    public void encode(ByteArrayOutputStream os) throws IOException {
+
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/protobuf/messagefields/Int32MessageField.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/protobuf/messagefields/Int32MessageField.java
new file mode 100644
index 0000000..68092b1
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/protobuf/messagefields/Int32MessageField.java
@@ -0,0 +1,24 @@
+/*  Copyright (C) 2022-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.util.protobuf.messagefields;
+
+public class Int32MessageField extends VarintMessageField{
+    public Int32MessageField(int fieldNumber, int value){
+        super(fieldNumber, value);
+        this.fieldType = FieldType.INT_32_BIT;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/protobuf/messagefields/Int64MessageField.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/protobuf/messagefields/Int64MessageField.java
new file mode 100644
index 0000000..e94e963
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/protobuf/messagefields/Int64MessageField.java
@@ -0,0 +1,24 @@
+/*  Copyright (C) 2022-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.util.protobuf.messagefields;
+
+public class Int64MessageField extends VarintMessageField{
+    public Int64MessageField(int fieldNumber, int value){
+        super(fieldNumber, value);
+        this.fieldType = FieldType.INT_64_BIT;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/protobuf/messagefields/MessageField.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/protobuf/messagefields/MessageField.java
new file mode 100644
index 0000000..056c530
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/protobuf/messagefields/MessageField.java
@@ -0,0 +1,74 @@
+/*  Copyright (C) 2022-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.util.protobuf.messagefields;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+
+public abstract class MessageField {
+    public static enum FieldType{
+        VARINT,
+        INT_64_BIT,
+        LENGTH_DELIMITED,
+        START_GROUP,
+        END_GROUP,
+        INT_32_BIT
+    }
+
+    int fieldNumber;
+    FieldType fieldType;
+
+    protected MessageField(int fieldNumber, FieldType fieldType){
+        this.fieldNumber = fieldNumber;
+        this.fieldType = fieldType;
+    }
+
+    protected byte[] getStartBytes(){
+        ByteArrayOutputStream bytes = new ByteArrayOutputStream(4);
+
+        byte result = (byte) (fieldType.ordinal() & 0b111);
+
+        int fieldNumber = this.fieldNumber;
+
+        result |= (fieldNumber & 0b1111) << 3;
+        fieldNumber >>= 4;
+        if(fieldNumber > 0){
+            result |= 0b10000000;
+        }
+        bytes.write(result);
+
+        while(fieldNumber > 0){
+            byte newByte = (byte)(fieldNumber & 0b01111111);
+            fieldNumber >>= 7;
+            if(fieldNumber != 0){
+                newByte |= 0b10000000;
+            }
+            bytes.write(newByte);
+        }
+
+        return bytes.toByteArray();
+    }
+
+    abstract public void encode(ByteArrayOutputStream os) throws IOException;
+
+    public byte[] encodeToBytes() throws IOException {
+        ByteArrayOutputStream os = new ByteArrayOutputStream();
+        encode(os);
+        return os.toByteArray();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/protobuf/messagefields/NestedMessageField.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/protobuf/messagefields/NestedMessageField.java
new file mode 100644
index 0000000..571a0ec
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/protobuf/messagefields/NestedMessageField.java
@@ -0,0 +1,43 @@
+/*  Copyright (C) 2022-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.util.protobuf.messagefields;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.util.protobuf.ProtobufUtils;
+
+public class NestedMessageField extends MessageField{
+    MessageField[] children;
+
+    public NestedMessageField(int fieldNumber, MessageField... children) {
+        super(fieldNumber, FieldType.LENGTH_DELIMITED);
+        this.children = children;
+    }
+
+    @Override
+    public void encode(ByteArrayOutputStream os) throws IOException {
+        ByteArrayOutputStream childStream = new ByteArrayOutputStream();
+        for(MessageField child : children){
+            child.encode(childStream);
+        }
+
+        os.write(getStartBytes());
+        os.write(ProtobufUtils.encode_varint(childStream.size()));
+        os.write(childStream.toByteArray());
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/protobuf/messagefields/RootMessageField.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/protobuf/messagefields/RootMessageField.java
new file mode 100644
index 0000000..14bb6e5
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/protobuf/messagefields/RootMessageField.java
@@ -0,0 +1,39 @@
+/*  Copyright (C) 2022-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.util.protobuf.messagefields;
+
+public class RootMessageField extends NestedMessageField{
+    public RootMessageField(MessageField... children) {
+        super(0, children);
+    }
+
+    @Override
+    protected byte[] getStartBytes() {
+        return new byte[0];
+    }
+
+    /*
+    @Override
+    public void encode(ByteArrayOutputStream os) throws IOException {
+        ByteArrayOutputStream childrenOs = new ByteArrayOutputStream();
+        super.encode(childrenOs);
+
+        os.write(utils.encode_varint(childrenOs.size()));
+        os.write(childrenOs.toByteArray());
+    }
+    */
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/protobuf/messagefields/StringMessageField.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/protobuf/messagefields/StringMessageField.java
new file mode 100644
index 0000000..df59e05
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/protobuf/messagefields/StringMessageField.java
@@ -0,0 +1,40 @@
+/*  Copyright (C) 2022-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.util.protobuf.messagefields;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.util.protobuf.ProtobufUtils;
+
+public class StringMessageField extends MessageField{
+    String fieldValueString;
+
+    public StringMessageField(int fieldNumber, String value) {
+        super(fieldNumber, FieldType.LENGTH_DELIMITED);
+        fieldValueString = value;
+    }
+
+    @Override
+    public void encode(ByteArrayOutputStream os) throws IOException {
+        os.write(getStartBytes());
+
+        os.write(ProtobufUtils.encode_varint(fieldValueString.length()));
+
+        os.write(fieldValueString.getBytes());
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/protobuf/messagefields/VarintMessageField.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/protobuf/messagefields/VarintMessageField.java
new file mode 100644
index 0000000..7bcf63f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/protobuf/messagefields/VarintMessageField.java
@@ -0,0 +1,40 @@
+/*  Copyright (C) 2022-2024 Daniel Dakhno
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.util.protobuf.messagefields;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.util.protobuf.ProtobufUtils;
+
+public class VarintMessageField extends MessageField{
+    int fieldValueInt;
+
+    public VarintMessageField(int fieldNumber, int value){
+        super(fieldNumber, FieldType.VARINT);
+        this.fieldValueInt = value;
+    }
+
+
+
+    @Override
+    public void encode(ByteArrayOutputStream os) throws IOException {
+        os.write(getStartBytes());
+
+        os.write(ProtobufUtils.encode_varint(fieldValueInt));
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/womp/WompApi.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/womp/WompApi.java
new file mode 100644
index 0000000..3065c7d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/womp/WompApi.java
@@ -0,0 +1,85 @@
+package nodomain.freeyourgadget.gadgetbridge.womp;
+
+import android.os.Handler;
+import android.os.Looper;
+
+import org.json.JSONObject;
+
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * 璋冪敤 midway /app 鎺ュ彛銆�
+ */
+public final class WompApi {
+    public interface Callback {
+        void onResult(boolean ok, String body);
+    }
+
+    private WompApi() {}
+
+    public static void post(final String path, final JSONObject body, final Callback callback) {
+        new Thread(() -> {
+            HttpURLConnection conn = null;
+            try {
+                URL url = new URL(WompConfig.API_BASE + path);
+                conn = (HttpURLConnection) url.openConnection();
+                conn.setRequestMethod("POST");
+                conn.setConnectTimeout(15000);
+                conn.setReadTimeout(20000);
+                conn.setDoOutput(true);
+                conn.setRequestProperty("Content-Type", "application/json; charset=utf-8");
+                byte[] bytes = (body == null ? "{}" : body.toString()).getBytes(StandardCharsets.UTF_8);
+                conn.setFixedLengthStreamingMode(bytes.length);
+                OutputStream os = conn.getOutputStream();
+                os.write(bytes);
+                os.flush();
+                os.close();
+                int code = conn.getResponseCode();
+                InputStream is = code >= 400 ? conn.getErrorStream() : conn.getInputStream();
+                if (is == null) {
+                    deliver(callback, false, "{\"code\":0,\"message\":\"缃戠粶寮傚父锛岃绋嶅悗閲嶈瘯\"}");
+                    return;
+                }
+                ByteArrayOutputStream bos = new ByteArrayOutputStream();
+                byte[] buf = new byte[4096];
+                int n;
+                while ((n = is.read(buf)) > 0) {
+                    bos.write(buf, 0, n);
+                }
+                is.close();
+                deliver(callback, code >= 200 && code < 300, bos.toString("UTF-8"));
+            } catch (java.net.UnknownHostException | java.net.ConnectException
+                    | java.net.SocketTimeoutException | java.net.NoRouteToHostException e) {
+                deliver(callback, false, "{\"code\":0,\"message\":\"缃戠粶涓嶉�氾紝璇锋鏌ョ綉缁滃悗閲嶈瘯\"}");
+            } catch (javax.net.ssl.SSLException e) {
+                deliver(callback, false, "{\"code\":0,\"message\":\"缃戠粶杩炴帴澶辫触锛岃绋嶅悗閲嶈瘯\"}");
+            } catch (Exception e) {
+                deliver(callback, false, "{\"code\":0,\"message\":\"" + networkMessage(e) + "\"}");
+            } finally {
+                if (conn != null) {
+                    conn.disconnect();
+                }
+            }
+        }).start();
+    }
+
+    private static String networkMessage(Exception e) {
+        String raw = e.getMessage() == null ? "" : e.getMessage().toLowerCase();
+        if (raw.contains("unable to resolve") || raw.contains("failed to connect")
+                || raw.contains("timeout") || raw.contains("econnrefused")
+                || raw.contains("network") || raw.contains("unreachable")) {
+            return "缃戠粶涓嶉�氾紝璇锋鏌ョ綉缁滃悗閲嶈瘯";
+        }
+        return "缃戠粶寮傚父锛岃绋嶅悗閲嶈瘯";
+    }
+
+    private static void deliver(final Callback callback, final boolean ok, final String body) {
+        if (callback == null) return;
+        new Handler(Looper.getMainLooper()).post(() -> callback.onResult(ok, body));
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/womp/WompConfig.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/womp/WompConfig.java
new file mode 100644
index 0000000..cab4a9c
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/womp/WompConfig.java
@@ -0,0 +1,19 @@
+package nodomain.freeyourgadget.gadgetbridge.womp;
+
+/**
+ * 鍒嗘嫞鍛� App 閰嶇疆銆傝皟璇曟椂鎵嬫満涓庣數鑴戝悓涓� WiFi锛岃闂數鑴戝眬鍩熺綉鍦板潃銆�
+ */
+public final class WompConfig {
+    private WompConfig() {}
+
+    public static final String API_BASE = "http://192.168.1.2:8001";
+    public static final long LOGIN_KEEP_MS = 7L * 24 * 60 * 60 * 1000;
+    public static final long GPS_INTERVAL_MS = 30_000;
+    public static final long HEALTH_INTERVAL_MS = 60_000;
+    public static final int HR_ABNORMAL_LOW = 50;
+    public static final int HR_ABNORMAL_HIGH = 140;
+    public static final long ABNORMAL_INTERVAL_MS = 60_000;
+    public static final String PREFS = "womp_sorter";
+    public static final String CHANNEL_KEEPALIVE = "womp_health_live";
+    public static final int NOTIFY_KEEPALIVE = 41001;
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/womp/WompJsBridge.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/womp/WompJsBridge.java
new file mode 100644
index 0000000..82bb3bf
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/womp/WompJsBridge.java
@@ -0,0 +1,116 @@
+package nodomain.freeyourgadget.gadgetbridge.womp;
+
+import android.app.Activity;
+import android.webkit.JavascriptInterface;
+import android.widget.Toast;
+
+import org.json.JSONObject;
+
+/**
+ * WebView 涓庡師鐢熸ˉ锛氱綉缁滆姹傘�佹壂鐮併�佺櫥褰曟�併��
+ */
+public class WompJsBridge {
+    public interface Host {
+        void scan(String callbackId);
+        void closePage();
+        void onLoggedIn();
+        void onLogout();
+        void evalJs(String script);
+    }
+
+    private final Activity activity;
+    private final Host host;
+
+    public WompJsBridge(Activity activity, Host host) {
+        this.activity = activity;
+        this.host = host;
+    }
+
+    @JavascriptInterface
+    public String getSorter() {
+        return WompSession.getSorterJson(activity);
+    }
+
+    @JavascriptInterface
+    public String getSavedPhone() {
+        return WompSession.getPhone(activity);
+    }
+
+    @JavascriptInterface
+    public String getSavedPassword() {
+        return WompSession.getPassword(activity);
+    }
+
+    @JavascriptInterface
+    public void saveCredentials(String phone, String password) {
+        WompSession.saveCredentials(activity, phone, password);
+    }
+
+    @JavascriptInterface
+    public void toast(final String msg) {
+        activity.runOnUiThread(() -> Toast.makeText(activity, msg, Toast.LENGTH_SHORT).show());
+    }
+
+    @JavascriptInterface
+    public void scan(String callbackId) {
+        activity.runOnUiThread(() -> host.scan(callbackId));
+    }
+
+    @JavascriptInterface
+    public void close() {
+        activity.runOnUiThread(host::closePage);
+    }
+
+    @JavascriptInterface
+    public void loginSuccess(String json) {
+        try {
+            JSONObject obj = new JSONObject(json);
+            String phone = obj.optString("phone");
+            String password = obj.optString("password");
+            WompSession.saveLogin(activity, phone, password, obj);
+            activity.runOnUiThread(host::onLoggedIn);
+        } catch (Exception e) {
+            activity.runOnUiThread(() ->
+                    Toast.makeText(activity, "鐧诲綍鏁版嵁寮傚父", Toast.LENGTH_SHORT).show());
+        }
+    }
+
+    @JavascriptInterface
+    public void logout() {
+        WompSession.clear(activity);
+        activity.runOnUiThread(host::onLogout);
+    }
+
+    @JavascriptInterface
+    public boolean isCheckedIn() {
+        return WompSession.isCheckedIn(activity);
+    }
+
+    @JavascriptInterface
+    public void setCheckedIn(boolean checkedIn) {
+        WompSession.setCheckedIn(activity, checkedIn);
+    }
+
+    @JavascriptInterface
+    public void getLocation(String callbackId) {
+        activity.runOnUiThread(() -> WompLocation.fetch(activity, (ok, json) -> deliver(callbackId, ok, json)));
+    }
+
+    @JavascriptInterface
+    public void request(String path, String jsonBody, final String callbackId) {
+        JSONObject body;
+        try {
+            body = jsonBody == null || jsonBody.length() == 0 ? new JSONObject() : new JSONObject(jsonBody);
+        } catch (Exception e) {
+            deliver(callbackId, false, "{\"message\":\"鍙傛暟閿欒\"}");
+            return;
+        }
+        WompApi.post(path, body, (ok, resp) -> deliver(callbackId, ok, resp));
+    }
+
+    private void deliver(String callbackId, boolean ok, String body) {
+        final String script = "window.__wompCb && window.__wompCb("
+                + JSONObject.quote(callbackId) + "," + ok + "," + JSONObject.quote(body) + ")";
+        activity.runOnUiThread(() -> host.evalJs(script));
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/womp/WompKeepAliveService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/womp/WompKeepAliveService.java
new file mode 100644
index 0000000..b56c794
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/womp/WompKeepAliveService.java
@@ -0,0 +1,329 @@
+package nodomain.freeyourgadget.gadgetbridge.womp;
+
+import android.Manifest;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.pm.ServiceInfo;
+import android.location.Location;
+import android.location.LocationListener;
+import android.location.LocationManager;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+
+import androidx.annotation.Nullable;
+import androidx.core.app.NotificationCompat;
+import androidx.core.content.ContextCompat;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import org.json.JSONObject;
+
+import java.io.Serializable;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.ControlCenterv2;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
+import nodomain.freeyourgadget.gadgetbridge.util.PendingIntentUtils;
+
+/**
+ * 鍓嶅彴淇濇椿锛氶�氱煡灞曠ず鎵嬬幆蹇冪巼/姝ユ暟锛屽悗鍙伴潤榛樻帴鏀舵墜鐜暟鎹苟瀹氭椂涓婃姤浣嶇疆銆�
+ */
+public class WompKeepAliveService extends Service implements LocationListener {
+    private final Handler handler = new Handler(Looper.getMainLooper());
+    private Location lastLocation;
+    private int lastHeartRate;
+    private int lastSteps;
+    private long lastNotifyAt;
+    private long lastAbnormalAt;
+    private boolean locating;
+    private final android.content.BroadcastReceiver sampleReceiver = new android.content.BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (!DeviceService.ACTION_REALTIME_SAMPLES.equals(intent.getAction())) {
+                return;
+            }
+            Serializable extra = intent.getSerializableExtra(DeviceService.EXTRA_REALTIME_SAMPLE);
+            if (extra instanceof ActivitySample) {
+                ActivitySample sample = (ActivitySample) extra;
+                boolean changed = false;
+                if (sample.getHeartRate() > 0 && sample.getHeartRate() != lastHeartRate) {
+                    lastHeartRate = sample.getHeartRate();
+                    changed = true;
+                    maybeReportAbnormal(sample.getHeartRate());
+                }
+                if (sample.getSteps() > 0) {
+                    if (sample.getSteps() >= lastSteps || sample.getSteps() > 200) {
+                        lastSteps = sample.getSteps();
+                    } else {
+                        lastSteps += sample.getSteps();
+                    }
+                    changed = true;
+                }
+                if (changed) {
+                    refreshNotification(false);
+                }
+            }
+        }
+    };
+
+    public static void start(Context context) {
+        Intent i = new Intent(context, WompKeepAliveService.class);
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+            context.startForegroundService(i);
+        } else {
+            context.startService(i);
+        }
+    }
+
+    public static void stop(Context context) {
+        context.stopService(new Intent(context, WompKeepAliveService.class));
+    }
+
+    public static void onAttendanceChanged(Context context) {
+        start(context);
+    }
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        createChannel();
+        Notification notification = buildNotification();
+        try {
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+                startForeground(WompConfig.NOTIFY_KEEPALIVE, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION);
+            } else {
+                startForeground(WompConfig.NOTIFY_KEEPALIVE, notification);
+            }
+        } catch (Exception e) {
+            startForeground(WompConfig.NOTIFY_KEEPALIVE, notification);
+        }
+        IntentFilter filter = new IntentFilter(DeviceService.ACTION_REALTIME_SAMPLES);
+        LocalBroadcastManager.getInstance(this).registerReceiver(sampleReceiver, filter);
+        syncLocationTracking();
+        handler.post(gpsTask);
+        handler.postDelayed(healthTask, 8_000);
+        handler.postDelayed(notifyTask, 15_000);
+    }
+
+    private final Runnable gpsTask = new Runnable() {
+        @Override
+        public void run() {
+            syncLocationTracking();
+            if (WompSession.isCheckedIn(WompKeepAliveService.this)) {
+                reportGps();
+            }
+            handler.postDelayed(this, WompConfig.GPS_INTERVAL_MS);
+        }
+    };
+
+    private final Runnable healthTask = new Runnable() {
+        @Override
+        public void run() {
+            if (WompSession.isCheckedIn(WompKeepAliveService.this)) {
+                reportHealth("鍛ㄦ湡");
+            }
+            handler.postDelayed(this, WompConfig.HEALTH_INTERVAL_MS);
+        }
+    };
+
+    private final Runnable notifyTask = new Runnable() {
+        @Override
+        public void run() {
+            refreshNotification(true);
+            handler.postDelayed(this, 20_000);
+        }
+    };
+
+    private void syncLocationTracking() {
+        if (WompSession.isCheckedIn(this)) {
+            startLocation();
+        } else {
+            stopLocation();
+        }
+    }
+
+    private void startLocation() {
+        if (locating) {
+            return;
+        }
+        if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED
+                && ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
+            return;
+        }
+        boolean started = false;
+        try {
+            lm.requestLocationUpdates(LocationManager.GPS_PROVIDER, WompConfig.GPS_INTERVAL_MS, 5, this, Looper.getMainLooper());
+            started = true;
+        } catch (Exception ignored) {
+        }
+        try {
+            lm.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, WompConfig.GPS_INTERVAL_MS, 5, this, Looper.getMainLooper());
+            started = true;
+        } catch (Exception ignored) {
+        }
+        try {
+            lastLocation = lm.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
+            if (lastLocation == null) {
+                lastLocation = lm.getLastKnownLocation(LocationManager.GPS_PROVIDER);
+            }
+            WompLocation.remember(lastLocation);
+        } catch (Exception ignored) {
+        }
+        locating = started;
+    }
+
+    private void stopLocation() {
+        if (!locating) {
+            return;
+        }
+        LocationManager lm = (LocationManager) getSystemService(LOCATION_SERVICE);
+        try {
+            lm.removeUpdates(this);
+        } catch (Exception ignored) {
+        }
+        locating = false;
+    }
+
+    private void reportGps() {
+        if (!WompSession.isLoggedIn(this) || !WompSession.isCheckedIn(this) || lastLocation == null) {
+            return;
+        }
+        try {
+            JSONObject body = new JSONObject();
+            body.put("sorterId", WompSession.getSorterId(this));
+            body.put("longitude", lastLocation.getLongitude());
+            body.put("latitude", lastLocation.getLatitude());
+            body.put("height", lastLocation.hasAltitude() ? lastLocation.getAltitude() : 0);
+            body.put("speed", lastLocation.hasSpeed() ? lastLocation.getSpeed() : 0);
+            body.put("accuracy", lastLocation.hasAccuracy() ? lastLocation.getAccuracy() : 0);
+            WompApi.post("/app/push/sorter/gps", body, null);
+        } catch (Exception ignored) {
+        }
+    }
+
+    private void reportHealth(String dataType) {
+        if (!WompSession.isLoggedIn(this)) {
+            return;
+        }
+        if (lastHeartRate <= 0 && lastSteps <= 0) {
+            return;
+        }
+        try {
+            JSONObject body = new JSONObject();
+            body.put("sorterId", WompSession.getSorterId(this));
+            if (lastHeartRate > 0) {
+                body.put("heartRate", lastHeartRate);
+            }
+            body.put("stepCount", lastSteps);
+            body.put("dataType", dataType);
+            WompApi.post("/app/push/sorter/health", body, null);
+        } catch (Exception ignored) {
+        }
+    }
+
+    private void maybeReportAbnormal(int heartRate) {
+        if (heartRate < WompConfig.HR_ABNORMAL_LOW || heartRate > WompConfig.HR_ABNORMAL_HIGH) {
+            long now = System.currentTimeMillis();
+            if (now - lastAbnormalAt < WompConfig.ABNORMAL_INTERVAL_MS) {
+                return;
+            }
+            lastAbnormalAt = now;
+            reportHealth("寮傚父");
+        }
+    }
+
+    @Override
+    public void onLocationChanged(Location location) {
+        lastLocation = location;
+        WompLocation.remember(location);
+    }
+
+    @Override
+    public void onStatusChanged(String provider, int status, Bundle extras) {}
+
+    @Override
+    public void onProviderEnabled(String provider) {}
+
+    @Override
+    public void onProviderDisabled(String provider) {}
+
+    @Nullable
+    @Override
+    public IBinder onBind(Intent intent) {
+        return null;
+    }
+
+    @Override
+    public int onStartCommand(Intent intent, int flags, int startId) {
+        syncLocationTracking();
+        return START_STICKY;
+    }
+
+    @Override
+    public void onDestroy() {
+        handler.removeCallbacksAndMessages(null);
+        LocalBroadcastManager.getInstance(this).unregisterReceiver(sampleReceiver);
+        locating = true;
+        stopLocation();
+        super.onDestroy();
+    }
+
+    private void createChannel() {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+            NotificationChannel ch = new NotificationChannel(
+                    WompConfig.CHANNEL_KEEPALIVE,
+                    "鍋ュ悍鎵嬬幆",
+                    NotificationManager.IMPORTANCE_LOW);
+            ch.setDescription("瀹炴椂鏄剧ず鎵嬬幆蹇冪巼鍜屾鏁�");
+            ch.setShowBadge(false);
+            ch.enableLights(false);
+            ch.enableVibration(false);
+            ch.setSound(null, null);
+            NotificationManager nm = getSystemService(NotificationManager.class);
+            nm.createNotificationChannel(ch);
+        }
+    }
+
+    private void refreshNotification(boolean force) {
+        long now = System.currentTimeMillis();
+        if (!force && now - lastNotifyAt < 1500) {
+            return;
+        }
+        lastNotifyAt = now;
+        NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
+        nm.notify(WompConfig.NOTIFY_KEEPALIVE, buildNotification());
+    }
+
+    private Notification buildNotification() {
+        Intent open = new Intent(this, ControlCenterv2.class);
+        PendingIntent pi = PendingIntentUtils.getActivity(this, 0, open, 0, false);
+        String hr = lastHeartRate > 0 ? (lastHeartRate + " 娆�/鍒�") : "--";
+        String steps = lastSteps > 0 ? String.valueOf(lastSteps) : "--";
+        String text = "蹇冪巼 " + hr + "    姝ユ暟 " + steps;
+        NotificationCompat.Builder builder = new NotificationCompat.Builder(this, WompConfig.CHANNEL_KEEPALIVE)
+                .setSmallIcon(R.drawable.ic_heart)
+                .setContentTitle("鍋ュ悍鎵嬬幆")
+                .setContentText(text)
+                .setStyle(new NotificationCompat.BigTextStyle().bigText(text))
+                .setOngoing(true)
+                .setOnlyAlertOnce(true)
+                .setShowWhen(false)
+                .setSilent(true)
+                .setContentIntent(pi)
+                .setVisibility(NotificationCompat.VISIBILITY_PUBLIC);
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+            builder.setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE);
+        }
+        return builder.build();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/womp/WompLocation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/womp/WompLocation.java
new file mode 100644
index 0000000..0f28573
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/womp/WompLocation.java
@@ -0,0 +1,145 @@
+package nodomain.freeyourgadget.gadgetbridge.womp;
+
+import android.Manifest;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.location.Location;
+import android.location.LocationListener;
+import android.location.LocationManager;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+
+import androidx.core.content.ContextCompat;
+
+import org.json.JSONObject;
+
+/**
+ * 鍙栦竴娆″綋鍓嶄綅缃紝浼樺厛鐢ㄦ渶杩戠紦瀛橈紝蹇呰鏃跺崟娆″埛鏂般��
+ */
+public final class WompLocation {
+    public interface Callback {
+        void onResult(boolean ok, String json);
+    }
+
+    private static volatile Location lastFix;
+
+    private WompLocation() {}
+
+    public static void remember(Location location) {
+        if (location != null) {
+            lastFix = location;
+        }
+    }
+
+    public static Location peek() {
+        return lastFix;
+    }
+
+    public static String toJson(Location location) {
+        try {
+            JSONObject obj = new JSONObject();
+            obj.put("longitude", location.getLongitude());
+            obj.put("latitude", location.getLatitude());
+            return obj.toString();
+        } catch (Exception e) {
+            return "{}";
+        }
+    }
+
+    public static void fetch(Context context, Callback callback) {
+        if (ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION)
+                != PackageManager.PERMISSION_GRANTED
+                && ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_COARSE_LOCATION)
+                != PackageManager.PERMISSION_GRANTED) {
+            callback.onResult(false, "{\"message\":\"璇峰厛寮�鍚畾浣嶆潈闄怽"}");
+            return;
+        }
+        Context app = context.getApplicationContext();
+        LocationManager lm = (LocationManager) app.getSystemService(Context.LOCATION_SERVICE);
+        Location fresh = newest(readLast(lm), lastFix);
+        if (fresh != null && System.currentTimeMillis() - fresh.getTime() < 120_000) {
+            lastFix = fresh;
+            callback.onResult(true, toJson(fresh));
+            return;
+        }
+        Handler handler = new Handler(Looper.getMainLooper());
+        final boolean[] done = {false};
+        LocationListener listener = new LocationListener() {
+            @Override
+            public void onLocationChanged(Location location) {
+                finish(lm, this, handler, done, callback, location, true);
+            }
+
+            @Override
+            public void onStatusChanged(String provider, int status, Bundle extras) {}
+
+            @Override
+            public void onProviderEnabled(String provider) {}
+
+            @Override
+            public void onProviderDisabled(String provider) {}
+        };
+        try {
+            lm.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0, listener, Looper.getMainLooper());
+        } catch (Exception ignored) {
+        }
+        try {
+            lm.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, listener, Looper.getMainLooper());
+        } catch (Exception ignored) {
+        }
+        handler.postDelayed(() -> {
+            Location fallback = newest(readLast(lm), lastFix, fresh);
+            if (fallback != null) {
+                finish(lm, listener, handler, done, callback, fallback, true);
+            } else {
+                finish(lm, listener, handler, done, callback, null, false);
+            }
+        }, 8000);
+    }
+
+    private static void finish(LocationManager lm, LocationListener listener, Handler handler,
+                               boolean[] done, Callback callback, Location location, boolean ok) {
+        if (done[0]) {
+            return;
+        }
+        done[0] = true;
+        handler.removeCallbacksAndMessages(null);
+        try {
+            lm.removeUpdates(listener);
+        } catch (Exception ignored) {
+        }
+        if (ok && location != null) {
+            lastFix = location;
+            callback.onResult(true, toJson(location));
+        } else {
+            callback.onResult(false, "{\"message\":\"鏃犳硶鑾峰彇瀹氫綅锛岃寮�鍚疓PS鍚庨噸璇昞"}");
+        }
+    }
+
+    private static Location readLast(LocationManager lm) {
+        Location best = null;
+        try {
+            best = newest(best, lm.getLastKnownLocation(LocationManager.NETWORK_PROVIDER));
+        } catch (Exception ignored) {
+        }
+        try {
+            best = newest(best, lm.getLastKnownLocation(LocationManager.GPS_PROVIDER));
+        } catch (Exception ignored) {
+        }
+        return best;
+    }
+
+    private static Location newest(Location... items) {
+        Location best = null;
+        for (Location item : items) {
+            if (item == null) {
+                continue;
+            }
+            if (best == null || item.getTime() > best.getTime()) {
+                best = item;
+            }
+        }
+        return best;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/womp/WompScanActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/womp/WompScanActivity.java
new file mode 100644
index 0000000..83b463b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/womp/WompScanActivity.java
@@ -0,0 +1,149 @@
+package nodomain.freeyourgadget.gadgetbridge.womp;
+
+import android.Manifest;
+import android.annotation.SuppressLint;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.util.Size;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.camera.core.CameraSelector;
+import androidx.camera.core.ImageAnalysis;
+import androidx.camera.core.ImageProxy;
+import androidx.camera.core.Preview;
+import androidx.camera.lifecycle.ProcessCameraProvider;
+import androidx.camera.view.PreviewView;
+import androidx.core.app.ActivityCompat;
+import androidx.core.content.ContextCompat;
+
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.zxing.BinaryBitmap;
+import com.google.zxing.MultiFormatReader;
+import com.google.zxing.NotFoundException;
+import com.google.zxing.PlanarYUVLuminanceSource;
+import com.google.zxing.Result;
+import com.google.zxing.common.HybridBinarizer;
+
+import java.nio.ByteBuffer;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+
+public class WompScanActivity extends AppCompatActivity {
+    public static final String EXTRA_CODE = "code";
+    private PreviewView previewView;
+    private ExecutorService cameraExecutor;
+    private volatile boolean found;
+    private final MultiFormatReader reader = new MultiFormatReader();
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        setTheme(R.style.GadgetbridgeTheme_NoActionBar);
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_womp_scan);
+        previewView = findViewById(R.id.womp_preview);
+        TextView hint = findViewById(R.id.womp_scan_hint);
+        hint.setText("瀵瑰噯浜岀淮鐮佹壂鎻�");
+        cameraExecutor = Executors.newSingleThreadExecutor();
+        findViewById(R.id.womp_scan_cancel).setOnClickListener(v -> {
+            setResult(RESULT_CANCELED);
+            finish();
+        });
+        if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
+            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, 1);
+        } else {
+            startCamera();
+        }
+    }
+
+    @Override
+    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
+        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
+        if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+            startCamera();
+        } else {
+            Toast.makeText(this, "闇�瑕佹憚鍍忓ご鏉冮檺", Toast.LENGTH_SHORT).show();
+            finish();
+        }
+    }
+
+    private void startCamera() {
+        ListenableFuture<ProcessCameraProvider> future = ProcessCameraProvider.getInstance(this);
+        future.addListener(() -> {
+            try {
+                ProcessCameraProvider provider = future.get();
+                Preview preview = new Preview.Builder().build();
+                preview.setSurfaceProvider(previewView.getSurfaceProvider());
+                ImageAnalysis analysis = new ImageAnalysis.Builder()
+                        .setTargetResolution(new Size(1280, 720))
+                        .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
+                        .build();
+                analysis.setAnalyzer(cameraExecutor, this::analyze);
+                provider.unbindAll();
+                provider.bindToLifecycle(this, CameraSelector.DEFAULT_BACK_CAMERA, preview, analysis);
+            } catch (Exception e) {
+                Toast.makeText(this, "鏃犳硶鎵撳紑鎽勫儚澶�", Toast.LENGTH_SHORT).show();
+                finish();
+            }
+        }, ContextCompat.getMainExecutor(this));
+    }
+
+    @SuppressLint("UnsafeOptInUsageError")
+    private void analyze(ImageProxy image) {
+        if (found) {
+            image.close();
+            return;
+        }
+        try {
+            ImageProxy.PlaneProxy plane = image.getPlanes()[0];
+            ByteBuffer buffer = plane.getBuffer();
+            int width = image.getWidth();
+            int height = image.getHeight();
+            int rowStride = plane.getRowStride();
+            byte[] data = new byte[width * height];
+            if (rowStride == width) {
+                buffer.get(data, 0, Math.min(data.length, buffer.remaining()));
+            } else {
+                byte[] row = new byte[rowStride];
+                int pos = 0;
+                for (int i = 0; i < height; i++) {
+                    int toRead = Math.min(rowStride, buffer.remaining());
+                    if (toRead <= 0) break;
+                    buffer.get(row, 0, toRead);
+                    System.arraycopy(row, 0, data, pos, width);
+                    pos += width;
+                }
+            }
+            PlanarYUVLuminanceSource source = new PlanarYUVLuminanceSource(
+                    data, width, height, 0, 0, width, height, false);
+            BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
+            Result result = reader.decodeWithState(bitmap);
+            if (result != null && result.getText() != null) {
+                found = true;
+                Intent dataIntent = new Intent();
+                dataIntent.putExtra(EXTRA_CODE, result.getText().trim());
+                setResult(RESULT_OK, dataIntent);
+                finish();
+            }
+        } catch (NotFoundException ignored) {
+        } catch (Exception ignored) {
+        } finally {
+            reader.reset();
+            image.close();
+        }
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        if (cameraExecutor != null) {
+            cameraExecutor.shutdown();
+        }
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/womp/WompSession.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/womp/WompSession.java
new file mode 100644
index 0000000..a77f524
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/womp/WompSession.java
@@ -0,0 +1,107 @@
+package nodomain.freeyourgadget.gadgetbridge.womp;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+
+import org.json.JSONObject;
+
+/**
+ * 鍒嗘嫞鍛樻湰鍦扮櫥褰曟�侊細淇濆瓨鎵嬫満鍙峰瘑鐮� 7 澶╋紝涓嶄緷璧栨湇鍔$ session銆�
+ */
+public final class WompSession {
+    private WompSession() {}
+
+    public static SharedPreferences prefs(Context context) {
+        return context.getSharedPreferences(WompConfig.PREFS, Context.MODE_PRIVATE);
+    }
+
+    public static boolean isLoggedIn(Context context) {
+        SharedPreferences p = prefs(context);
+        String sorterId = p.getString("sorterId", null);
+        long loginAt = p.getLong("loginAt", 0);
+        return sorterId != null && sorterId.length() > 0
+                && (System.currentTimeMillis() - loginAt) < WompConfig.LOGIN_KEEP_MS;
+    }
+
+    public static void saveCredentials(Context context, String phone, String password) {
+        prefs(context).edit()
+                .putString("phone", phone == null ? "" : phone)
+                .putString("password", password == null ? "" : password)
+                .apply();
+    }
+
+    public static void saveLogin(Context context, String phone, String password, JSONObject sorter) {
+        prefs(context).edit()
+                .putString("phone", phone)
+                .putString("password", password)
+                .putString("sorterId", sorter.optString("sorterId"))
+                .putString("businessName", sorter.optString("businessName"))
+                .putString("sorterJson", sorter.toString())
+                .putLong("loginAt", System.currentTimeMillis())
+                .apply();
+    }
+
+    public static void clear(Context context) {
+        SharedPreferences p = prefs(context);
+        String phone = p.getString("phone", "");
+        String password = p.getString("password", "");
+        boolean checkedIn = p.getBoolean("checkedIn", false);
+        String checkInSorterId = p.getString("checkInSorterId", "");
+        String checkInWorkDate = p.getString("checkInWorkDate", "");
+        p.edit().clear()
+                .putString("phone", phone)
+                .putString("password", password)
+                .putBoolean("checkedIn", checkedIn)
+                .putString("checkInSorterId", checkInSorterId)
+                .putString("checkInWorkDate", checkInWorkDate)
+                .apply();
+    }
+
+    public static void setCheckedIn(Context context, boolean checkedIn) {
+        SharedPreferences.Editor editor = prefs(context).edit()
+                .putBoolean("checkedIn", checkedIn);
+        if (checkedIn) {
+            editor.putString("checkInSorterId", getSorterId(context))
+                    .putString("checkInWorkDate", today());
+        }
+        editor.apply();
+        WompKeepAliveService.onAttendanceChanged(context);
+    }
+
+    public static boolean isCheckedIn(Context context) {
+        SharedPreferences p = prefs(context);
+        if (!p.getBoolean("checkedIn", false)) {
+            return false;
+        }
+        String sorterId = p.getString("sorterId", "");
+        String owner = p.getString("checkInSorterId", "");
+        String workDate = p.getString("checkInWorkDate", "");
+        return sorterId.length() > 0 && sorterId.equals(owner) && today().equals(workDate);
+    }
+
+    public static String today() {
+        return new java.text.SimpleDateFormat("yyyy-MM-dd", java.util.Locale.CHINA)
+                .format(new java.util.Date());
+    }
+
+    public static String getSorterId(Context context) {
+        return prefs(context).getString("sorterId", "");
+    }
+
+    public static String getBusinessName(Context context) {
+        return prefs(context).getString("businessName", "");
+    }
+
+    public static String getPhone(Context context) {
+        return prefs(context).getString("phone", "");
+    }
+
+    public static String getPassword(Context context) {
+        return prefs(context).getString("password", "");
+    }
+
+    public static String getSorterJson(Context context) {
+        String json = prefs(context).getString("sorterJson", "{}");
+        return json == null ? "{}" : json;
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/womp/WompWebActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/womp/WompWebActivity.java
new file mode 100644
index 0000000..00ae503
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/womp/WompWebActivity.java
@@ -0,0 +1,152 @@
+package nodomain.freeyourgadget.gadgetbridge.womp;
+
+import android.Manifest;
+import android.annotation.SuppressLint;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.Build;
+import android.os.Bundle;
+import android.webkit.WebChromeClient;
+import android.webkit.WebSettings;
+import android.webkit.WebView;
+import android.webkit.WebViewClient;
+import android.widget.Toast;
+
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.core.app.ActivityCompat;
+import androidx.core.content.ContextCompat;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+
+public class WompWebActivity extends AppCompatActivity implements WompJsBridge.Host {
+    public static final String EXTRA_PAGE = "page";
+    private static final int REQ_SCAN = 7101;
+    private static final int REQ_PERM = 7102;
+
+    private WebView webView;
+    private String pendingScanCb;
+    private long lastBackPressedAt;
+
+    public static Intent open(android.content.Context ctx, String page) {
+        Intent i = new Intent(ctx, WompWebActivity.class);
+        i.putExtra(EXTRA_PAGE, page);
+        return i;
+    }
+
+    @SuppressLint("SetJavaScriptEnabled")
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        setTheme(R.style.GadgetbridgeTheme_NoActionBar);
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_womp_web);
+        webView = findViewById(R.id.womp_webview);
+        WebSettings s = webView.getSettings();
+        s.setJavaScriptEnabled(true);
+        s.setDomStorageEnabled(true);
+        s.setAllowFileAccess(true);
+        s.setAllowContentAccess(true);
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+            s.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
+        }
+        webView.setWebViewClient(new WebViewClient() {
+            @Override
+            public void onPageFinished(WebView view, String url) {
+                if (url != null && (url.endsWith("home.html") || url.endsWith("login.html"))) {
+                    view.clearHistory();
+                }
+            }
+        });
+        webView.setWebChromeClient(new WebChromeClient());
+        webView.addJavascriptInterface(new WompJsBridge(this, this), "Womp");
+
+        String page = getIntent().getStringExtra(EXTRA_PAGE);
+        if (page == null || page.length() == 0) {
+            page = WompSession.isLoggedIn(this) ? "home.html" : "login.html";
+        }
+        webView.loadUrl("file:///android_asset/womp/" + page);
+        ensureRuntimePermissions();
+    }
+
+    private void ensureRuntimePermissions() {
+        java.util.ArrayList<String> need = new java.util.ArrayList<>();
+        if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
+            need.add(Manifest.permission.CAMERA);
+        }
+        if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
+            need.add(Manifest.permission.ACCESS_FINE_LOCATION);
+        }
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU
+                && ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
+            need.add(Manifest.permission.POST_NOTIFICATIONS);
+        }
+        if (!need.isEmpty()) {
+            ActivityCompat.requestPermissions(this, need.toArray(new String[0]), REQ_PERM);
+        }
+    }
+
+    @Override
+    public void scan(String callbackId) {
+        pendingScanCb = callbackId;
+        startActivityForResult(new Intent(this, WompScanActivity.class), REQ_SCAN);
+    }
+
+    @Override
+    public void closePage() {
+        finish();
+    }
+
+    @Override
+    public void onLoggedIn() {
+        WompKeepAliveService.start(this);
+        if (webView != null) {
+            webView.loadUrl("file:///android_asset/womp/home.html");
+        }
+    }
+
+    @Override
+    public void onLogout() {
+        if (webView != null) {
+            webView.loadUrl("file:///android_asset/womp/login.html");
+        }
+    }
+
+    @Override
+    public void evalJs(String script) {
+        if (webView != null) {
+            webView.evaluateJavascript(script, null);
+        }
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
+        super.onActivityResult(requestCode, resultCode, data);
+        if (requestCode == REQ_SCAN && pendingScanCb != null) {
+            String code = resultCode == RESULT_OK && data != null ? data.getStringExtra(WompScanActivity.EXTRA_CODE) : "";
+            String script = "window.__wompCb && window.__wompCb("
+                    + org.json.JSONObject.quote(pendingScanCb) + ",true,"
+                    + org.json.JSONObject.quote(code == null ? "" : code) + ")";
+            evalJs(script);
+            pendingScanCb = null;
+        }
+    }
+
+    @Override
+    public void onBackPressed() {
+        if (webView != null && webView.canGoBack()) {
+            webView.goBack();
+            return;
+        }
+        if (!isTaskRoot()) {
+            super.onBackPressed();
+            return;
+        }
+        long now = System.currentTimeMillis();
+        if (now - lastBackPressedAt < 2000) {
+            finishAffinity();
+            return;
+        }
+        lastBackPressedAt = now;
+        Toast.makeText(this, R.string.womp_press_again_to_exit, Toast.LENGTH_SHORT).show();
+    }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/womp/WompWorkbenchFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/womp/WompWorkbenchFragment.java
new file mode 100644
index 0000000..56ebc59
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/womp/WompWorkbenchFragment.java
@@ -0,0 +1,189 @@
+package nodomain.freeyourgadget.gadgetbridge.womp;
+
+import android.Manifest;
+import android.annotation.SuppressLint;
+import android.app.Activity;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.Build;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.webkit.WebChromeClient;
+import android.webkit.WebSettings;
+import android.webkit.WebView;
+import android.webkit.WebViewClient;
+
+import androidx.activity.result.ActivityResultLauncher;
+import androidx.activity.result.contract.ActivityResultContracts;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.core.content.ContextCompat;
+import androidx.fragment.app.Fragment;
+
+import org.json.JSONObject;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.ControlCenterv2;
+
+/**
+ * 鍒嗘嫞鍛樺伐浣滃彴锛氫笌鎵嬬幆鍔熻兘鐩镐簰鐙珛锛屾湭鐧诲綍涔熷彲鐣欏湪鏈〉銆�
+ */
+public class WompWorkbenchFragment extends Fragment implements WompJsBridge.Host {
+    private WebView webView;
+    private String pendingScanCb;
+    private ActivityResultLauncher<Intent> scanLauncher;
+
+    @Override
+    public void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        scanLauncher = registerForActivityResult(
+                new ActivityResultContracts.StartActivityForResult(),
+                result -> {
+                    if (pendingScanCb == null) {
+                        return;
+                    }
+                    String code = "";
+                    if (result.getResultCode() == Activity.RESULT_OK && result.getData() != null) {
+                        code = result.getData().getStringExtra(WompScanActivity.EXTRA_CODE);
+                    }
+                    String script = "window.__wompCb && window.__wompCb("
+                            + JSONObject.quote(pendingScanCb) + ",true,"
+                            + JSONObject.quote(code == null ? "" : code) + ")";
+                    evalJs(script);
+                    pendingScanCb = null;
+                });
+    }
+
+    @SuppressLint("SetJavaScriptEnabled")
+    @Nullable
+    @Override
+    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
+                             @Nullable Bundle savedInstanceState) {
+        View root = inflater.inflate(R.layout.fragment_womp_workbench, container, false);
+        webView = root.findViewById(R.id.womp_webview);
+        WebSettings s = webView.getSettings();
+        s.setJavaScriptEnabled(true);
+        s.setDomStorageEnabled(true);
+        s.setAllowFileAccess(true);
+        s.setAllowContentAccess(true);
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+            s.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
+        }
+        webView.setWebViewClient(new WebViewClient() {
+            @Override
+            public void onPageFinished(WebView view, String url) {
+                if (url != null && (url.endsWith("home.html") || url.endsWith("login.html"))) {
+                    view.clearHistory();
+                }
+            }
+        });
+        webView.setWebChromeClient(new WebChromeClient());
+        webView.addJavascriptInterface(new WompJsBridge(requireActivity(), this), "Womp");
+        String page = WompSession.isLoggedIn(requireContext()) ? "home.html" : "login.html";
+        webView.loadUrl("file:///android_asset/womp/" + page);
+        ensureLocationPermission();
+        return root;
+    }
+
+    private void ensureLocationPermission() {
+        if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.ACCESS_FINE_LOCATION)
+                != PackageManager.PERMISSION_GRANTED) {
+            requestPermissions(new String[]{
+                    Manifest.permission.ACCESS_FINE_LOCATION,
+                    Manifest.permission.ACCESS_COARSE_LOCATION
+            }, 7103);
+        }
+    }
+
+    public boolean goBack() {
+        if (webView != null && webView.canGoBack()) {
+            webView.goBack();
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public void scan(String callbackId) {
+        pendingScanCb = callbackId;
+        if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.CAMERA)
+                != PackageManager.PERMISSION_GRANTED) {
+            requestPermissions(new String[]{Manifest.permission.CAMERA}, 7102);
+            return;
+        }
+        scanLauncher.launch(new Intent(requireContext(), WompScanActivity.class));
+    }
+
+    @Override
+    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
+        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
+        if (requestCode == 7102 && pendingScanCb != null
+                && grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+            scanLauncher.launch(new Intent(requireContext(), WompScanActivity.class));
+        } else if (requestCode == 7102 && pendingScanCb != null) {
+            evalJs("window.__wompCb && window.__wompCb("
+                    + JSONObject.quote(pendingScanCb) + ",true,\"\")");
+            pendingScanCb = null;
+        }
+    }
+
+    @Override
+    public void closePage() {
+        if (getActivity() instanceof ControlCenterv2) {
+            ((ControlCenterv2) getActivity()).showBandTab();
+        }
+    }
+
+    @Override
+    public void onLoggedIn() {
+        WompKeepAliveService.start(requireContext());
+        if (webView != null) {
+            webView.loadUrl("file:///android_asset/womp/home.html");
+        }
+    }
+
+    @Override
+    public void onLogout() {
+        if (webView != null) {
+            webView.loadUrl("file:///android_asset/womp/login.html");
+        }
+    }
+
+    @Override
+    public void evalJs(String script) {
+        if (webView != null && getActivity() != null) {
+            getActivity().runOnUiThread(() -> {
+                if (webView != null) {
+                    webView.evaluateJavascript(script, null);
+                }
+            });
+        }
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        if (webView != null) {
+            webView.onResume();
+        }
+    }
+
+    @Override
+    public void onPause() {
+        if (webView != null) {
+            webView.onPause();
+        }
+        super.onPause();
+    }
+
+    @Override
+    public void onDestroyView() {
+        if (webView != null) {
+            webView.destroy();
+            webView = null;
+        }
+        super.onDestroyView();
+    }
+}
diff --git a/app/src/main/java/org/bouncycastle/shaded/crypto/BlockCipher.java b/app/src/main/java/org/bouncycastle/shaded/crypto/BlockCipher.java
new file mode 100644
index 0000000..e8ba9ea
--- /dev/null
+++ b/app/src/main/java/org/bouncycastle/shaded/crypto/BlockCipher.java
@@ -0,0 +1,56 @@
+package org.bouncycastle.shaded.crypto;
+
+
+/**
+ * Block cipher engines are expected to conform to this interface.
+ */
+public interface BlockCipher
+{
+    /**
+     * Initialise the cipher.
+     *
+     * @param forEncryption if true the cipher is initialised for
+     *  encryption, if false for decryption.
+     * @param params the key and other data required by the cipher.
+     * @exception IllegalArgumentException if the params argument is
+     * inappropriate.
+     */
+    public void init(boolean forEncryption, CipherParameters params)
+        throws IllegalArgumentException;
+
+    /**
+     * Return the name of the algorithm the cipher implements.
+     *
+     * @return the name of the algorithm the cipher implements.
+     */
+    public String getAlgorithmName();
+
+    /**
+     * Return the block size for this cipher (in bytes).
+     *
+     * @return the block size for this cipher in bytes.
+     */
+    public int getBlockSize();
+
+    /**
+     * Process one block of input from the array in and write it to
+     * the out array.
+     *
+     * @param input the array containing the input data.
+     * @param inOff offset into the in array the data starts at.
+     * @param output the array the output data will be copied into.
+     * @param outOff the offset into the out array the output will start at.
+     * @exception DataLengthException if there isn't enough data in input , or
+     * space in out.
+     * @exception IllegalStateException if the cipher isn't initialised.
+     * @return the number of bytes processed and produced.
+     */
+    public int processBlock(byte[] input, int inOff, byte[] output, int outOff)
+        throws DataLengthException, IllegalStateException;
+
+    /**
+     * Reset the cipher. After resetting the cipher is in the same state
+     * as it was after the last init (if there was one).
+     */
+    public void reset();
+}
diff --git a/app/src/main/java/org/bouncycastle/shaded/crypto/CipherParameters.java b/app/src/main/java/org/bouncycastle/shaded/crypto/CipherParameters.java
new file mode 100644
index 0000000..d1a1ff6
--- /dev/null
+++ b/app/src/main/java/org/bouncycastle/shaded/crypto/CipherParameters.java
@@ -0,0 +1,8 @@
+package org.bouncycastle.shaded.crypto;
+
+/**
+ * all parameter classes implement this.
+ */
+public interface CipherParameters
+{
+}
diff --git a/app/src/main/java/org/bouncycastle/shaded/crypto/CryptoException.java b/app/src/main/java/org/bouncycastle/shaded/crypto/CryptoException.java
new file mode 100644
index 0000000..9d8c3ae
--- /dev/null
+++ b/app/src/main/java/org/bouncycastle/shaded/crypto/CryptoException.java
@@ -0,0 +1,48 @@
+package org.bouncycastle.shaded.crypto;
+
+/**
+ * the foundation class for the hard exceptions thrown by the crypto packages.
+ */
+public class CryptoException 
+    extends Exception
+{
+    private Throwable cause;
+
+    /**
+     * base constructor.
+     */
+    public CryptoException()
+    {
+    }
+
+    /**
+     * create a CryptoException with the given message.
+     *
+     * @param message the message to be carried with the exception.
+     */
+    public CryptoException(
+        String  message)
+    {
+        super(message);
+    }
+
+    /**
+     * Create a CryptoException with the given message and underlying cause.
+     *
+     * @param message message describing exception.
+     * @param cause the throwable that was the underlying cause.
+     */
+    public CryptoException(
+        String  message,
+        Throwable cause)
+    {
+        super(message);
+
+        this.cause = cause;
+    }
+
+    public Throwable getCause()
+    {
+        return cause;
+    }
+}
diff --git a/app/src/main/java/org/bouncycastle/shaded/crypto/DataLengthException.java b/app/src/main/java/org/bouncycastle/shaded/crypto/DataLengthException.java
new file mode 100644
index 0000000..69994df
--- /dev/null
+++ b/app/src/main/java/org/bouncycastle/shaded/crypto/DataLengthException.java
@@ -0,0 +1,29 @@
+package org.bouncycastle.shaded.crypto;
+
+/**
+ * this exception is thrown if a buffer that is meant to have output
+ * copied into it turns out to be too short, or if we've been given 
+ * insufficient input. In general this exception will get thrown rather
+ * than an ArrayOutOfBounds exception.
+ */
+public class DataLengthException 
+    extends RuntimeCryptoException
+{
+    /**
+     * base constructor.
+     */
+    public DataLengthException()
+    {
+    }
+
+    /**
+     * create a DataLengthException with the given message.
+     *
+     * @param message the message to be carried with the exception.
+     */
+    public DataLengthException(
+        String  message)
+    {
+        super(message);
+    }
+}
diff --git a/app/src/main/java/org/bouncycastle/shaded/crypto/DefaultMultiBlockCipher.java b/app/src/main/java/org/bouncycastle/shaded/crypto/DefaultMultiBlockCipher.java
new file mode 100644
index 0000000..af3a1e0
--- /dev/null
+++ b/app/src/main/java/org/bouncycastle/shaded/crypto/DefaultMultiBlockCipher.java
@@ -0,0 +1,33 @@
+package org.bouncycastle.shaded.crypto;
+
+public abstract class DefaultMultiBlockCipher
+    implements MultiBlockCipher
+{
+    protected DefaultMultiBlockCipher()
+    {
+    }
+
+    public int getMultiBlockSize()
+    {
+        return this.getBlockSize();
+    }
+
+    public int processBlocks(byte[] in, int inOff, int blockCount, byte[] out, int outOff)
+        throws DataLengthException, IllegalStateException
+    {
+
+        // TODO check if the underlying cipher supports the multiblock interface and call it directly?
+
+        int resultLen = 0;
+        int blockSize = this.getMultiBlockSize();
+        
+        for (int i = 0; i != blockCount; i++)
+        {
+            resultLen += this.processBlock(in, inOff, out, outOff + resultLen);
+
+            inOff += blockSize;
+        }
+
+        return resultLen;
+    }
+}
diff --git a/app/src/main/java/org/bouncycastle/shaded/crypto/InvalidCipherTextException.java b/app/src/main/java/org/bouncycastle/shaded/crypto/InvalidCipherTextException.java
new file mode 100644
index 0000000..536f60e
--- /dev/null
+++ b/app/src/main/java/org/bouncycastle/shaded/crypto/InvalidCipherTextException.java
@@ -0,0 +1,40 @@
+package org.bouncycastle.shaded.crypto;
+
+/**
+ * this exception is thrown whenever we find something we don't expect in a
+ * message.
+ */
+public class InvalidCipherTextException 
+    extends CryptoException
+{
+    /**
+     * base constructor.
+     */
+    public InvalidCipherTextException()
+    {
+    }
+
+    /**
+     * create a InvalidCipherTextException with the given message.
+     *
+     * @param message the message to be carried with the exception.
+     */
+    public InvalidCipherTextException(
+        String  message)
+    {
+        super(message);
+    }
+
+    /**
+     * create a InvalidCipherTextException with the given message.
+     *
+     * @param message the message to be carried with the exception.
+     * @param cause the root cause of the exception.
+     */
+    public InvalidCipherTextException(
+        String  message,
+        Throwable cause)
+    {
+        super(message, cause);
+    }
+}
diff --git a/app/src/main/java/org/bouncycastle/shaded/crypto/Mac.java b/app/src/main/java/org/bouncycastle/shaded/crypto/Mac.java
new file mode 100644
index 0000000..7923ff1
--- /dev/null
+++ b/app/src/main/java/org/bouncycastle/shaded/crypto/Mac.java
@@ -0,0 +1,71 @@
+package org.bouncycastle.shaded.crypto;
+
+
+/**
+ * The base interface for implementations of message authentication codes (MACs).
+ */
+public interface Mac
+{
+    /**
+     * Initialise the MAC.
+     *
+     * @param params the key and other data required by the MAC.
+     * @exception IllegalArgumentException if the params argument is
+     * inappropriate.
+     */
+    public void init(CipherParameters params)
+        throws IllegalArgumentException;
+
+    /**
+     * Return the name of the algorithm the MAC implements.
+     *
+     * @return the name of the algorithm the MAC implements.
+     */
+    public String getAlgorithmName();
+
+    /**
+     * Return the block size for this MAC (in bytes).
+     *
+     * @return the block size for this MAC in bytes.
+     */
+    public int getMacSize();
+
+    /**
+     * add a single byte to the mac for processing.
+     *
+     * @param in the byte to be processed.
+     * @exception IllegalStateException if the MAC is not initialised.
+     */
+    public void update(byte in)
+        throws IllegalStateException;
+
+    /**
+     * @param in the array containing the input.
+     * @param inOff the index in the array the data begins at.
+     * @param len the length of the input starting at inOff.
+     * @exception IllegalStateException if the MAC is not initialised.
+     * @exception DataLengthException if there isn't enough data in in.
+     */
+    public void update(byte[] in, int inOff, int len)
+        throws DataLengthException, IllegalStateException;
+
+    /**
+     * Compute the final stage of the MAC writing the output to the out
+     * parameter.
+     * <p>
+     * doFinal leaves the MAC in the same state it was after the last init.
+     *
+     * @param out the array the MAC is to be output to.
+     * @param outOff the offset into the out buffer the output is to start at.
+     * @exception DataLengthException if there isn't enough space in out.
+     * @exception IllegalStateException if the MAC is not initialised.
+     */
+    public int doFinal(byte[] out, int outOff)
+        throws DataLengthException, IllegalStateException;
+
+    /**
+     * Reset the MAC. At the end of resetting the MAC should be in the
+     * in the same state it was after the last init (if there was one).
+     */
+    public void reset();
+}
diff --git a/app/src/main/java/org/bouncycastle/shaded/crypto/MultiBlockCipher.java b/app/src/main/java/org/bouncycastle/shaded/crypto/MultiBlockCipher.java
new file mode 100644
index 0000000..ef6b5a0
--- /dev/null
+++ b/app/src/main/java/org/bouncycastle/shaded/crypto/MultiBlockCipher.java
@@ -0,0 +1,31 @@
+package org.bouncycastle.shaded.crypto;
+
+/**
+ * Base interface for a cipher engine capable of processing multiple blocks at a time.
+ */
+public interface MultiBlockCipher
+    extends BlockCipher
+{
+    /**
+     * Return the multi-block size for this cipher (in bytes).
+     *
+     * @return the multi-block size for this cipher in bytes.
+     */
+    int getMultiBlockSize();
+
+    /**
+     * Process blockCount blocks from input in offset inOff and place the output in
+     * out from offset outOff.
+     *
+     * @param in input data array.
+     * @param inOff start of input data in in.
+     * @param blockCount number of blocks to be processed.
+     * @param out output data array.
+     * @param outOff start position for output data.
+     * @return number of bytes written to out.
+     * @throws DataLengthException
+     * @throws IllegalStateException
+     */
+    int processBlocks(byte[] in, int inOff, int blockCount, byte[] out, int outOff)
+        throws DataLengthException, IllegalStateException;
+}
diff --git a/app/src/main/java/org/bouncycastle/shaded/crypto/OutputLengthException.java b/app/src/main/java/org/bouncycastle/shaded/crypto/OutputLengthException.java
new file mode 100644
index 0000000..0eccf66
--- /dev/null
+++ b/app/src/main/java/org/bouncycastle/shaded/crypto/OutputLengthException.java
@@ -0,0 +1,10 @@
+package org.bouncycastle.shaded.crypto;
+
+public class OutputLengthException
+    extends DataLengthException
+{
+    public OutputLengthException(String msg)
+    {
+        super(msg);
+    }
+}
diff --git a/app/src/main/java/org/bouncycastle/shaded/crypto/RuntimeCryptoException.java b/app/src/main/java/org/bouncycastle/shaded/crypto/RuntimeCryptoException.java
new file mode 100644
index 0000000..8470e34
--- /dev/null
+++ b/app/src/main/java/org/bouncycastle/shaded/crypto/RuntimeCryptoException.java
@@ -0,0 +1,26 @@
+package org.bouncycastle.shaded.crypto;
+
+/**
+ * the foundation class for the exceptions thrown by the crypto packages.
+ */
+public class RuntimeCryptoException 
+    extends RuntimeException
+{
+    /**
+     * base constructor.
+     */
+    public RuntimeCryptoException()
+    {
+    }
+
+    /**
+     * create a RuntimeCryptoException with the given message.
+     *
+     * @param message the message to be carried with the exception.
+     */
+    public RuntimeCryptoException(
+        String  message)
+    {
+        super(message);
+    }
+}
diff --git a/app/src/main/java/org/bouncycastle/shaded/crypto/SkippingCipher.java b/app/src/main/java/org/bouncycastle/shaded/crypto/SkippingCipher.java
new file mode 100644
index 0000000..a8b4ce5
--- /dev/null
+++ b/app/src/main/java/org/bouncycastle/shaded/crypto/SkippingCipher.java
@@ -0,0 +1,31 @@
+package org.bouncycastle.shaded.crypto;
+
+/**
+ * Ciphers producing a key stream which can be reset to particular points in the stream implement this.
+ */
+public interface SkippingCipher
+{
+    /**
+     * Skip numberOfBytes forwards, or backwards.
+     *
+     * @param numberOfBytes the number of bytes to skip (positive forward, negative backwards).
+     * @return the number of bytes actually skipped.
+     * @throws java.lang.IllegalArgumentException if numberOfBytes is an invalid value.
+     */
+    long skip(long numberOfBytes);
+
+    /**
+     * Reset the cipher and then skip forward to a given position.
+     *
+     * @param position the number of bytes in to set the cipher state to.
+     * @return the byte position moved to.
+     */
+    long seekTo(long position);
+
+    /**
+     * Return the current "position" of the cipher
+     *
+     * @return the current byte position.
+     */
+    long getPosition();
+}
diff --git a/app/src/main/java/org/bouncycastle/shaded/crypto/SkippingStreamCipher.java b/app/src/main/java/org/bouncycastle/shaded/crypto/SkippingStreamCipher.java
new file mode 100644
index 0000000..9fa88b9
--- /dev/null
+++ b/app/src/main/java/org/bouncycastle/shaded/crypto/SkippingStreamCipher.java
@@ -0,0 +1,9 @@
+package org.bouncycastle.shaded.crypto;
+
+/**
+ * General interface for a stream cipher that supports skipping.
+ */
+public interface SkippingStreamCipher
+    extends StreamCipher, SkippingCipher
+{
+}
diff --git a/app/src/main/java/org/bouncycastle/shaded/crypto/StreamBlockCipher.java b/app/src/main/java/org/bouncycastle/shaded/crypto/StreamBlockCipher.java
new file mode 100644
index 0000000..7871951
--- /dev/null
+++ b/app/src/main/java/org/bouncycastle/shaded/crypto/StreamBlockCipher.java
@@ -0,0 +1,58 @@
+package org.bouncycastle.shaded.crypto;
+
+/**
+ * A parent class for block cipher modes that do not require block aligned data to be processed, but can function in
+ * a streaming mode.
+ */
+public abstract class StreamBlockCipher
+    extends DefaultMultiBlockCipher
+    implements StreamCipher
+{
+    private final BlockCipher cipher;
+
+    protected StreamBlockCipher(BlockCipher cipher)
+    {
+        this.cipher = cipher;
+    }
+
+    /**
+     * return the underlying block cipher that we are wrapping.
+     *
+     * @return the underlying block cipher that we are wrapping.
+     */
+    public BlockCipher getUnderlyingCipher()
+    {
+        return cipher;
+    }
+
+    public final byte returnByte(byte in)
+    {
+        return calculateByte(in);
+    }
+
+    public int processBytes(byte[] in, int inOff, int len, byte[] out, int outOff)
+        throws DataLengthException
+    {
+        if (inOff + len > in.length)
+        {
+            throw new DataLengthException("input buffer too small");
+        }
+        if (outOff + len > out.length)
+        {
+            throw new OutputLengthException("output buffer too short");
+        }
+
+        int inStart = inOff;
+        int inEnd = inOff + len;
+        int outStart = outOff;
+
+        while (inStart < inEnd)
+        {
+             out[outStart++] = calculateByte(in[inStart++]);
+        }
+
+        return len;
+    }
+
+    protected abstract byte calculateByte(byte b);
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/bouncycastle/shaded/crypto/StreamCipher.java b/app/src/main/java/org/bouncycastle/shaded/crypto/StreamCipher.java
new file mode 100644
index 0000000..06fdb6e
--- /dev/null
+++ b/app/src/main/java/org/bouncycastle/shaded/crypto/StreamCipher.java
@@ -0,0 +1,54 @@
+package org.bouncycastle.shaded.crypto;
+
+/**
+ * the interface stream ciphers conform to.
+ */
+public interface StreamCipher
+{
+    /**
+     * Initialise the cipher.
+     *
+     * @param forEncryption if true the cipher is initialised for
+     *  encryption, if false for decryption.
+     * @param params the key and other data required by the cipher.
+     * @exception IllegalArgumentException if the params argument is
+     * inappropriate.
+     */
+    public void init(boolean forEncryption, CipherParameters params)
+        throws IllegalArgumentException;
+
+    /**
+     * Return the name of the algorithm the cipher implements.
+     *
+     * @return the name of the algorithm the cipher implements.
+     */
+    public String getAlgorithmName();
+
+    /**
+     * encrypt/decrypt a single byte returning the result.
+     *
+     * @param in the byte to be processed.
+     * @return the result of processing the input byte.
+     */
+    public byte returnByte(byte in);
+
+    /**
+     * process a block of bytes from in putting the result into out.
+     *
+     * @param in the input byte array.
+     * @param inOff the offset into the in array where the data to be processed starts.
+     * @param len the number of bytes to be processed.
+     * @param out the output buffer the processed bytes go into.
+     * @param outOff the offset into the output byte array the processed data starts at.
+     * @return the number of bytes produced - should always be len.
+     * @exception DataLengthException if the output buffer is too small.
+     */
+    public int processBytes(byte[] in, int inOff, int len, byte[] out, int outOff)
+        throws DataLengthException;
+
+    /**
+     * reset the cipher. This leaves it in the same state
+     * it was at after the last init (if there was one).
+     */
+    public void reset();
+}
diff --git a/app/src/main/java/org/bouncycastle/shaded/crypto/engines/AESEngine.java b/app/src/main/java/org/bouncycastle/shaded/crypto/engines/AESEngine.java
new file mode 100644
index 0000000..af24dd0
--- /dev/null
+++ b/app/src/main/java/org/bouncycastle/shaded/crypto/engines/AESEngine.java
@@ -0,0 +1,609 @@
+package org.bouncycastle.shaded.crypto.engines;
+
+import org.bouncycastle.shaded.crypto.CipherParameters;
+import org.bouncycastle.shaded.crypto.DataLengthException;
+import org.bouncycastle.shaded.crypto.DefaultMultiBlockCipher;
+import org.bouncycastle.shaded.crypto.MultiBlockCipher;
+import org.bouncycastle.shaded.crypto.OutputLengthException;
+import org.bouncycastle.shaded.crypto.params.KeyParameter;
+import org.bouncycastle.shaded.util.Arrays;
+import org.bouncycastle.shaded.util.Pack;
+
+/**
+ * an implementation of the AES (Rijndael), from FIPS-197.
+ * <p>
+ * For further details see: <a href="https://csrc.nist.gov/encryption/aes/">https://csrc.nist.gov/encryption/aes/</a>.
+ *
+ * This implementation is based on optimizations from Dr. Brian Gladman's paper and C code at
+ * <a href="https://fp.gladman.plus.com/cryptography_technology/rijndael/">https://fp.gladman.plus.com/cryptography_technology/rijndael/</a>
+ *
+ * There are three levels of tradeoff of speed vs memory
+ * Because java has no preprocessor, they are written as three separate classes from which to choose
+ *
+ * The fastest uses 8Kbytes of static tables to precompute round calculations, 4 256 word tables for encryption
+ * and 4 for decryption.
+ *
+ * The middle performance version uses only one 256 word table for each, for a total of 2Kbytes,
+ * adding 12 rotate operations per round to compute the values contained in the other tables from
+ * the contents of the first.
+ *
+ * The slowest version uses no static tables at all and computes the values in each round.
+ * <p>
+ * This file contains the middle performance version with 2Kbytes of static tables for round precomputation.
+ *
+ */
+public class AESEngine
+    extends DefaultMultiBlockCipher
+{
+    // The S box
+    private static final byte[] S = {
+        (byte)99, (byte)124, (byte)119, (byte)123, (byte)242, (byte)107, (byte)111, (byte)197,
+        (byte)48,   (byte)1, (byte)103,  (byte)43, (byte)254, (byte)215, (byte)171, (byte)118,
+        (byte)202, (byte)130, (byte)201, (byte)125, (byte)250,  (byte)89,  (byte)71, (byte)240,
+        (byte)173, (byte)212, (byte)162, (byte)175, (byte)156, (byte)164, (byte)114, (byte)192,
+        (byte)183, (byte)253, (byte)147,  (byte)38,  (byte)54,  (byte)63, (byte)247, (byte)204,
+        (byte)52, (byte)165, (byte)229, (byte)241, (byte)113, (byte)216,  (byte)49,  (byte)21,
+        (byte)4, (byte)199,  (byte)35, (byte)195,  (byte)24, (byte)150,   (byte)5, (byte)154,
+        (byte)7,  (byte)18, (byte)128, (byte)226, (byte)235,  (byte)39, (byte)178, (byte)117,
+        (byte)9, (byte)131,  (byte)44,  (byte)26,  (byte)27, (byte)110,  (byte)90, (byte)160,
+        (byte)82,  (byte)59, (byte)214, (byte)179,  (byte)41, (byte)227,  (byte)47, (byte)132,
+        (byte)83, (byte)209,   (byte)0, (byte)237,  (byte)32, (byte)252, (byte)177,  (byte)91,
+        (byte)106, (byte)203, (byte)190,  (byte)57,  (byte)74,  (byte)76,  (byte)88, (byte)207,
+        (byte)208, (byte)239, (byte)170, (byte)251,  (byte)67,  (byte)77,  (byte)51, (byte)133,
+        (byte)69, (byte)249,   (byte)2, (byte)127,  (byte)80,  (byte)60, (byte)159, (byte)168,
+        (byte)81, (byte)163,  (byte)64, (byte)143, (byte)146, (byte)157,  (byte)56, (byte)245,
+        (byte)188, (byte)182, (byte)218,  (byte)33,  (byte)16, (byte)255, (byte)243, (byte)210,
+        (byte)205,  (byte)12,  (byte)19, (byte)236,  (byte)95, (byte)151,  (byte)68,  (byte)23,
+        (byte)196, (byte)167, (byte)126,  (byte)61, (byte)100,  (byte)93,  (byte)25, (byte)115,
+        (byte)96, (byte)129,  (byte)79, (byte)220,  (byte)34,  (byte)42, (byte)144, (byte)136,
+        (byte)70, (byte)238, (byte)184,  (byte)20, (byte)222,  (byte)94,  (byte)11, (byte)219,
+        (byte)224,  (byte)50,  (byte)58,  (byte)10,  (byte)73,   (byte)6,  (byte)36,  (byte)92,
+        (byte)194, (byte)211, (byte)172,  (byte)98, (byte)145, (byte)149, (byte)228, (byte)121,
+        (byte)231, (byte)200,  (byte)55, (byte)109, (byte)141, (byte)213,  (byte)78, (byte)169,
+        (byte)108,  (byte)86, (byte)244, (byte)234, (byte)101, (byte)122, (byte)174,   (byte)8,
+        (byte)186, (byte)120,  (byte)37,  (byte)46,  (byte)28, (byte)166, (byte)180, (byte)198,
+        (byte)232, (byte)221, (byte)116,  (byte)31,  (byte)75, (byte)189, (byte)139, (byte)138,
+        (byte)112,  (byte)62, (byte)181, (byte)102,  (byte)72,   (byte)3, (byte)246,  (byte)14,
+        (byte)97,  (byte)53,  (byte)87, (byte)185, (byte)134, (byte)193,  (byte)29, (byte)158,
+        (byte)225, (byte)248, (byte)152,  (byte)17, (byte)105, (byte)217, (byte)142, (byte)148,
+        (byte)155,  (byte)30, (byte)135, (byte)233, (byte)206,  (byte)85,  (byte)40, (byte)223,
+        (byte)140, (byte)161, (byte)137,  (byte)13, (byte)191, (byte)230,  (byte)66, (byte)104,
+        (byte)65, (byte)153,  (byte)45,  (byte)15, (byte)176,  (byte)84, (byte)187,  (byte)22,
+    };
+
+    // The inverse S-box
+    private static final byte[] Si = {
+        (byte)82,   (byte)9, (byte)106, (byte)213,  (byte)48,  (byte)54, (byte)165,  (byte)56,
+        (byte)191,  (byte)64, (byte)163, (byte)158, (byte)129, (byte)243, (byte)215, (byte)251,
+        (byte)124, (byte)227,  (byte)57, (byte)130, (byte)155,  (byte)47, (byte)255, (byte)135,
+        (byte)52, (byte)142,  (byte)67,  (byte)68, (byte)196, (byte)222, (byte)233, (byte)203,
+        (byte)84, (byte)123, (byte)148,  (byte)50, (byte)166, (byte)194,  (byte)35,  (byte)61,
+        (byte)238,  (byte)76, (byte)149,  (byte)11,  (byte)66, (byte)250, (byte)195,  (byte)78,
+        (byte)8,  (byte)46, (byte)161, (byte)102,  (byte)40, (byte)217,  (byte)36, (byte)178,
+        (byte)118,  (byte)91, (byte)162,  (byte)73, (byte)109, (byte)139, (byte)209,  (byte)37,
+        (byte)114, (byte)248, (byte)246, (byte)100, (byte)134, (byte)104, (byte)152,  (byte)22,
+        (byte)212, (byte)164,  (byte)92, (byte)204,  (byte)93, (byte)101, (byte)182, (byte)146,
+        (byte)108, (byte)112,  (byte)72,  (byte)80, (byte)253, (byte)237, (byte)185, (byte)218,
+        (byte)94,  (byte)21,  (byte)70,  (byte)87, (byte)167, (byte)141, (byte)157, (byte)132,
+        (byte)144, (byte)216, (byte)171,   (byte)0, (byte)140, (byte)188, (byte)211,  (byte)10,
+        (byte)247, (byte)228,  (byte)88,   (byte)5, (byte)184, (byte)179,  (byte)69,   (byte)6,
+        (byte)208,  (byte)44,  (byte)30, (byte)143, (byte)202,  (byte)63,  (byte)15,   (byte)2,
+        (byte)193, (byte)175, (byte)189,   (byte)3,   (byte)1,  (byte)19, (byte)138, (byte)107,
+        (byte)58, (byte)145,  (byte)17,  (byte)65,  (byte)79, (byte)103, (byte)220, (byte)234,
+        (byte)151, (byte)242, (byte)207, (byte)206, (byte)240, (byte)180, (byte)230, (byte)115,
+        (byte)150, (byte)172, (byte)116,  (byte)34, (byte)231, (byte)173,  (byte)53, (byte)133,
+        (byte)226, (byte)249,  (byte)55, (byte)232,  (byte)28, (byte)117, (byte)223, (byte)110,
+        (byte)71, (byte)241,  (byte)26, (byte)113,  (byte)29,  (byte)41, (byte)197, (byte)137,
+        (byte)111, (byte)183,  (byte)98,  (byte)14, (byte)170,  (byte)24, (byte)190,  (byte)27,
+        (byte)252,  (byte)86,  (byte)62,  (byte)75, (byte)198, (byte)210, (byte)121,  (byte)32,
+        (byte)154, (byte)219, (byte)192, (byte)254, (byte)120, (byte)205,  (byte)90, (byte)244,
+        (byte)31, (byte)221, (byte)168,  (byte)51, (byte)136,   (byte)7, (byte)199,  (byte)49,
+        (byte)177,  (byte)18,  (byte)16,  (byte)89,  (byte)39, (byte)128, (byte)236,  (byte)95,
+        (byte)96,  (byte)81, (byte)127, (byte)169,  (byte)25, (byte)181,  (byte)74,  (byte)13,
+        (byte)45, (byte)229, (byte)122, (byte)159, (byte)147, (byte)201, (byte)156, (byte)239,
+        (byte)160, (byte)224,  (byte)59,  (byte)77, (byte)174,  (byte)42, (byte)245, (byte)176,
+        (byte)200, (byte)235, (byte)187,  (byte)60, (byte)131,  (byte)83, (byte)153,  (byte)97,
+        (byte)23,  (byte)43,   (byte)4, (byte)126, (byte)186, (byte)119, (byte)214,  (byte)38,
+        (byte)225, (byte)105,  (byte)20,  (byte)99,  (byte)85,  (byte)33,  (byte)12, (byte)125,
+        };
+
+    // vector used in calculating key schedule (powers of x in GF(256))
+    private static final int[] rcon = {
+         0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a,
+         0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91 };
+
+    // precomputation tables of calculations for rounds
+    private static final int[] T0 =
+    {
+     0xa56363c6, 0x847c7cf8, 0x997777ee, 0x8d7b7bf6, 0x0df2f2ff, 
+     0xbd6b6bd6, 0xb16f6fde, 0x54c5c591, 0x50303060, 0x03010102, 
+     0xa96767ce, 0x7d2b2b56, 0x19fefee7, 0x62d7d7b5, 0xe6abab4d, 
+     0x9a7676ec, 0x45caca8f, 0x9d82821f, 0x40c9c989, 0x877d7dfa, 
+     0x15fafaef, 0xeb5959b2, 0xc947478e, 0x0bf0f0fb, 0xecadad41, 
+     0x67d4d4b3, 0xfda2a25f, 0xeaafaf45, 0xbf9c9c23, 0xf7a4a453, 
+     0x967272e4, 0x5bc0c09b, 0xc2b7b775, 0x1cfdfde1, 0xae93933d, 
+     0x6a26264c, 0x5a36366c, 0x413f3f7e, 0x02f7f7f5, 0x4fcccc83, 
+     0x5c343468, 0xf4a5a551, 0x34e5e5d1, 0x08f1f1f9, 0x937171e2, 
+     0x73d8d8ab, 0x53313162, 0x3f15152a, 0x0c040408, 0x52c7c795, 
+     0x65232346, 0x5ec3c39d, 0x28181830, 0xa1969637, 0x0f05050a, 
+     0xb59a9a2f, 0x0907070e, 0x36121224, 0x9b80801b, 0x3de2e2df, 
+     0x26ebebcd, 0x6927274e, 0xcdb2b27f, 0x9f7575ea, 0x1b090912, 
+     0x9e83831d, 0x742c2c58, 0x2e1a1a34, 0x2d1b1b36, 0xb26e6edc, 
+     0xee5a5ab4, 0xfba0a05b, 0xf65252a4, 0x4d3b3b76, 0x61d6d6b7, 
+     0xceb3b37d, 0x7b292952, 0x3ee3e3dd, 0x712f2f5e, 0x97848413, 
+     0xf55353a6, 0x68d1d1b9, 0x00000000, 0x2cededc1, 0x60202040, 
+     0x1ffcfce3, 0xc8b1b179, 0xed5b5bb6, 0xbe6a6ad4, 0x46cbcb8d, 
+     0xd9bebe67, 0x4b393972, 0xde4a4a94, 0xd44c4c98, 0xe85858b0, 
+     0x4acfcf85, 0x6bd0d0bb, 0x2aefefc5, 0xe5aaaa4f, 0x16fbfbed, 
+     0xc5434386, 0xd74d4d9a, 0x55333366, 0x94858511, 0xcf45458a, 
+     0x10f9f9e9, 0x06020204, 0x817f7ffe, 0xf05050a0, 0x443c3c78, 
+     0xba9f9f25, 0xe3a8a84b, 0xf35151a2, 0xfea3a35d, 0xc0404080, 
+     0x8a8f8f05, 0xad92923f, 0xbc9d9d21, 0x48383870, 0x04f5f5f1, 
+     0xdfbcbc63, 0xc1b6b677, 0x75dadaaf, 0x63212142, 0x30101020, 
+     0x1affffe5, 0x0ef3f3fd, 0x6dd2d2bf, 0x4ccdcd81, 0x140c0c18, 
+     0x35131326, 0x2fececc3, 0xe15f5fbe, 0xa2979735, 0xcc444488, 
+     0x3917172e, 0x57c4c493, 0xf2a7a755, 0x827e7efc, 0x473d3d7a, 
+     0xac6464c8, 0xe75d5dba, 0x2b191932, 0x957373e6, 0xa06060c0, 
+     0x98818119, 0xd14f4f9e, 0x7fdcdca3, 0x66222244, 0x7e2a2a54, 
+     0xab90903b, 0x8388880b, 0xca46468c, 0x29eeeec7, 0xd3b8b86b, 
+     0x3c141428, 0x79dedea7, 0xe25e5ebc, 0x1d0b0b16, 0x76dbdbad, 
+     0x3be0e0db, 0x56323264, 0x4e3a3a74, 0x1e0a0a14, 0xdb494992, 
+     0x0a06060c, 0x6c242448, 0xe45c5cb8, 0x5dc2c29f, 0x6ed3d3bd, 
+     0xefacac43, 0xa66262c4, 0xa8919139, 0xa4959531, 0x37e4e4d3, 
+     0x8b7979f2, 0x32e7e7d5, 0x43c8c88b, 0x5937376e, 0xb76d6dda, 
+     0x8c8d8d01, 0x64d5d5b1, 0xd24e4e9c, 0xe0a9a949, 0xb46c6cd8, 
+     0xfa5656ac, 0x07f4f4f3, 0x25eaeacf, 0xaf6565ca, 0x8e7a7af4, 
+     0xe9aeae47, 0x18080810, 0xd5baba6f, 0x887878f0, 0x6f25254a, 
+     0x722e2e5c, 0x241c1c38, 0xf1a6a657, 0xc7b4b473, 0x51c6c697, 
+     0x23e8e8cb, 0x7cdddda1, 0x9c7474e8, 0x211f1f3e, 0xdd4b4b96, 
+     0xdcbdbd61, 0x868b8b0d, 0x858a8a0f, 0x907070e0, 0x423e3e7c, 
+     0xc4b5b571, 0xaa6666cc, 0xd8484890, 0x05030306, 0x01f6f6f7, 
+     0x120e0e1c, 0xa36161c2, 0x5f35356a, 0xf95757ae, 0xd0b9b969, 
+     0x91868617, 0x58c1c199, 0x271d1d3a, 0xb99e9e27, 0x38e1e1d9, 
+     0x13f8f8eb, 0xb398982b, 0x33111122, 0xbb6969d2, 0x70d9d9a9, 
+     0x898e8e07, 0xa7949433, 0xb69b9b2d, 0x221e1e3c, 0x92878715, 
+     0x20e9e9c9, 0x49cece87, 0xff5555aa, 0x78282850, 0x7adfdfa5, 
+     0x8f8c8c03, 0xf8a1a159, 0x80898909, 0x170d0d1a, 0xdabfbf65, 
+     0x31e6e6d7, 0xc6424284, 0xb86868d0, 0xc3414182, 0xb0999929, 
+     0x772d2d5a, 0x110f0f1e, 0xcbb0b07b, 0xfc5454a8, 0xd6bbbb6d, 
+     0x3a16162c};
+
+private static final int[] Tinv0 =
+    {
+     0x50a7f451, 0x5365417e, 0xc3a4171a, 0x965e273a, 0xcb6bab3b, 
+     0xf1459d1f, 0xab58faac, 0x9303e34b, 0x55fa3020, 0xf66d76ad, 
+     0x9176cc88, 0x254c02f5, 0xfcd7e54f, 0xd7cb2ac5, 0x80443526, 
+     0x8fa362b5, 0x495ab1de, 0x671bba25, 0x980eea45, 0xe1c0fe5d, 
+     0x02752fc3, 0x12f04c81, 0xa397468d, 0xc6f9d36b, 0xe75f8f03, 
+     0x959c9215, 0xeb7a6dbf, 0xda595295, 0x2d83bed4, 0xd3217458, 
+     0x2969e049, 0x44c8c98e, 0x6a89c275, 0x78798ef4, 0x6b3e5899, 
+     0xdd71b927, 0xb64fe1be, 0x17ad88f0, 0x66ac20c9, 0xb43ace7d, 
+     0x184adf63, 0x82311ae5, 0x60335197, 0x457f5362, 0xe07764b1, 
+     0x84ae6bbb, 0x1ca081fe, 0x942b08f9, 0x58684870, 0x19fd458f, 
+     0x876cde94, 0xb7f87b52, 0x23d373ab, 0xe2024b72, 0x578f1fe3, 
+     0x2aab5566, 0x0728ebb2, 0x03c2b52f, 0x9a7bc586, 0xa50837d3, 
+     0xf2872830, 0xb2a5bf23, 0xba6a0302, 0x5c8216ed, 0x2b1ccf8a, 
+     0x92b479a7, 0xf0f207f3, 0xa1e2694e, 0xcdf4da65, 0xd5be0506, 
+     0x1f6234d1, 0x8afea6c4, 0x9d532e34, 0xa055f3a2, 0x32e18a05, 
+     0x75ebf6a4, 0x39ec830b, 0xaaef6040, 0x069f715e, 0x51106ebd, 
+     0xf98a213e, 0x3d06dd96, 0xae053edd, 0x46bde64d, 0xb58d5491, 
+     0x055dc471, 0x6fd40604, 0xff155060, 0x24fb9819, 0x97e9bdd6, 
+     0xcc434089, 0x779ed967, 0xbd42e8b0, 0x888b8907, 0x385b19e7, 
+     0xdbeec879, 0x470a7ca1, 0xe90f427c, 0xc91e84f8, 0x00000000, 
+     0x83868009, 0x48ed2b32, 0xac70111e, 0x4e725a6c, 0xfbff0efd, 
+     0x5638850f, 0x1ed5ae3d, 0x27392d36, 0x64d90f0a, 0x21a65c68, 
+     0xd1545b9b, 0x3a2e3624, 0xb1670a0c, 0x0fe75793, 0xd296eeb4, 
+     0x9e919b1b, 0x4fc5c080, 0xa220dc61, 0x694b775a, 0x161a121c, 
+     0x0aba93e2, 0xe52aa0c0, 0x43e0223c, 0x1d171b12, 0x0b0d090e, 
+     0xadc78bf2, 0xb9a8b62d, 0xc8a91e14, 0x8519f157, 0x4c0775af, 
+     0xbbdd99ee, 0xfd607fa3, 0x9f2601f7, 0xbcf5725c, 0xc53b6644, 
+     0x347efb5b, 0x7629438b, 0xdcc623cb, 0x68fcedb6, 0x63f1e4b8, 
+     0xcadc31d7, 0x10856342, 0x40229713, 0x2011c684, 0x7d244a85, 
+     0xf83dbbd2, 0x1132f9ae, 0x6da129c7, 0x4b2f9e1d, 0xf330b2dc, 
+     0xec52860d, 0xd0e3c177, 0x6c16b32b, 0x99b970a9, 0xfa489411, 
+     0x2264e947, 0xc48cfca8, 0x1a3ff0a0, 0xd82c7d56, 0xef903322, 
+     0xc74e4987, 0xc1d138d9, 0xfea2ca8c, 0x360bd498, 0xcf81f5a6, 
+     0x28de7aa5, 0x268eb7da, 0xa4bfad3f, 0xe49d3a2c, 0x0d927850, 
+     0x9bcc5f6a, 0x62467e54, 0xc2138df6, 0xe8b8d890, 0x5ef7392e, 
+     0xf5afc382, 0xbe805d9f, 0x7c93d069, 0xa92dd56f, 0xb31225cf, 
+     0x3b99acc8, 0xa77d1810, 0x6e639ce8, 0x7bbb3bdb, 0x097826cd, 
+     0xf418596e, 0x01b79aec, 0xa89a4f83, 0x656e95e6, 0x7ee6ffaa, 
+     0x08cfbc21, 0xe6e815ef, 0xd99be7ba, 0xce366f4a, 0xd4099fea, 
+     0xd67cb029, 0xafb2a431, 0x31233f2a, 0x3094a5c6, 0xc066a235, 
+     0x37bc4e74, 0xa6ca82fc, 0xb0d090e0, 0x15d8a733, 0x4a9804f1, 
+     0xf7daec41, 0x0e50cd7f, 0x2ff69117, 0x8dd64d76, 0x4db0ef43, 
+     0x544daacc, 0xdf0496e4, 0xe3b5d19e, 0x1b886a4c, 0xb81f2cc1, 
+     0x7f516546, 0x04ea5e9d, 0x5d358c01, 0x737487fa, 0x2e410bfb, 
+     0x5a1d67b3, 0x52d2db92, 0x335610e9, 0x1347d66d, 0x8c61d79a, 
+     0x7a0ca137, 0x8e14f859, 0x893c13eb, 0xee27a9ce, 0x35c961b7, 
+     0xede51ce1, 0x3cb1477a, 0x59dfd29c, 0x3f73f255, 0x79ce1418, 
+     0xbf37c773, 0xeacdf753, 0x5baafd5f, 0x146f3ddf, 0x86db4478, 
+     0x81f3afca, 0x3ec468b9, 0x2c342438, 0x5f40a3c2, 0x72c31d16, 
+     0x0c25e2bc, 0x8b493c28, 0x41950dff, 0x7101a839, 0xdeb30c08, 
+     0x9ce4b4d8, 0x90c15664, 0x6184cb7b, 0x70b632d5, 0x745c6c48, 
+     0x4257b8d0};
+
+    private static int shift(int r, int shift)
+    {
+        return (r >>> shift) | (r << -shift);
+    }
+
+    /* multiply four bytes in GF(2^8) by 'x' {02} in parallel */
+
+    private static final int m1 = 0x80808080;
+    private static final int m2 = 0x7f7f7f7f;
+    private static final int m3 = 0x0000001b;
+    private static final int m4 = 0xC0C0C0C0;
+    private static final int m5 = 0x3f3f3f3f;
+
+    private static int FFmulX(int x)
+    {
+        return (((x & m2) << 1) ^ (((x & m1) >>> 7) * m3));
+    }
+
+    private static int FFmulX2(int x)
+    {
+        int t0  = (x & m5) << 2;
+        int t1  = (x & m4);
+            t1 ^= (t1 >>> 1);
+        return t0 ^ (t1 >>> 2) ^ (t1 >>> 5);
+    }
+
+    /* 
+       The following defines provide alternative definitions of FFmulX that might
+       give improved performance if a fast 32-bit multiply is not available.
+       
+       private int FFmulX(int x) { int u = x & m1; u |= (u >> 1); return ((x & m2) << 1) ^ ((u >>> 3) | (u >>> 6)); } 
+       private static final int  m4 = 0x1b1b1b1b;
+       private int FFmulX(int x) { int u = x & m1; return ((x & m2) << 1) ^ ((u - (u >>> 7)) & m4); } 
+
+    */
+
+    private static int inv_mcol(int x)
+    {
+        int t0, t1;
+        t0  = x;
+        t1  = t0 ^ shift(t0, 8);
+        t0 ^= FFmulX(t1);
+        t1 ^= FFmulX2(t0);
+        t0 ^= t1 ^ shift(t1, 16);
+        return t0;
+    }
+
+    private static int subWord(int x)
+    {
+        return (S[x&255]&255 | ((S[(x>>8)&255]&255)<<8) | ((S[(x>>16)&255]&255)<<16) | S[(x>>24)&255]<<24);
+    }
+
+    /**
+     * Calculate the necessary round keys
+     * The number of calculations depends on key size and block size
+     * AES specified a fixed block size of 128 bits and key sizes 128/192/256 bits
+     * This code is written assuming those are the only possible values
+     */
+    private int[][] generateWorkingKey(byte[] key, boolean forEncryption)
+    {
+        int keyLen = key.length;
+        if (keyLen < 16 || keyLen > 32 || (keyLen & 7) != 0)
+        {
+            throw new IllegalArgumentException("Key length not 128/192/256 bits.");
+        }
+
+        int KC = keyLen >>> 2;
+        ROUNDS = KC + 6;  // This is not always true for the generalized Rijndael that allows larger block sizes
+        int[][] W = new int[ROUNDS+1][4];   // 4 words in a block
+
+        switch (KC)
+        {
+        case 4:
+        {
+            int col0 = Pack.littleEndianToInt(key,  0);     W[0][0] = col0;
+            int col1 = Pack.littleEndianToInt(key,  4);     W[0][1] = col1;
+            int col2 = Pack.littleEndianToInt(key,  8);     W[0][2] = col2;
+            int col3 = Pack.littleEndianToInt(key, 12);     W[0][3] = col3;
+
+            for (int i = 1; i <= 10; ++i)
+            {
+                int colx = subWord(shift(col3, 8)) ^ rcon[i - 1];
+                col0 ^= colx;       W[i][0] = col0;
+                col1 ^= col0;       W[i][1] = col1;
+                col2 ^= col1;       W[i][2] = col2;
+                col3 ^= col2;       W[i][3] = col3;
+            }
+
+            break;
+        }
+        case 6:
+        {
+            int col0 = Pack.littleEndianToInt(key,  0);     W[0][0] = col0;
+            int col1 = Pack.littleEndianToInt(key,  4);     W[0][1] = col1;
+            int col2 = Pack.littleEndianToInt(key,  8);     W[0][2] = col2;
+            int col3 = Pack.littleEndianToInt(key, 12);     W[0][3] = col3;
+
+            int col4 = Pack.littleEndianToInt(key, 16);
+            int col5 = Pack.littleEndianToInt(key, 20);
+
+            int i = 1, rcon = 1, colx;
+            for (;;)
+            {
+                                    W[i    ][0] = col4;
+                                    W[i    ][1] = col5;
+                colx = subWord(shift(col5, 8)) ^ rcon; rcon <<= 1;
+                col0 ^= colx;       W[i    ][2] = col0;
+                col1 ^= col0;       W[i    ][3] = col1;
+
+                col2 ^= col1;       W[i + 1][0] = col2;
+                col3 ^= col2;       W[i + 1][1] = col3;
+                col4 ^= col3;       W[i + 1][2] = col4;
+                col5 ^= col4;       W[i + 1][3] = col5;
+
+                colx = subWord(shift(col5, 8)) ^ rcon; rcon <<= 1;
+                col0 ^= colx;       W[i + 2][0] = col0;
+                col1 ^= col0;       W[i + 2][1] = col1;
+                col2 ^= col1;       W[i + 2][2] = col2;
+                col3 ^= col2;       W[i + 2][3] = col3;
+
+                if ((i += 3) >= 13)
+                {
+                    break;
+                }
+
+                col4 ^= col3;
+                col5 ^= col4;
+            }
+
+            break;
+        }
+        case 8:
+        {
+            int col0 = Pack.littleEndianToInt(key,  0);     W[0][0] = col0;
+            int col1 = Pack.littleEndianToInt(key,  4);     W[0][1] = col1;
+            int col2 = Pack.littleEndianToInt(key,  8);     W[0][2] = col2;
+            int col3 = Pack.littleEndianToInt(key, 12);     W[0][3] = col3;
+
+            int col4 = Pack.littleEndianToInt(key, 16);     W[1][0] = col4;
+            int col5 = Pack.littleEndianToInt(key, 20);     W[1][1] = col5;
+            int col6 = Pack.littleEndianToInt(key, 24);     W[1][2] = col6;
+            int col7 = Pack.littleEndianToInt(key, 28);     W[1][3] = col7;
+
+            int i = 2, rcon = 1, colx;
+            for (;;)
+            {
+                colx = subWord(shift(col7, 8)) ^ rcon; rcon <<= 1;
+                col0 ^= colx;       W[i][0] = col0;
+                col1 ^= col0;       W[i][1] = col1;
+                col2 ^= col1;       W[i][2] = col2;
+                col3 ^= col2;       W[i][3] = col3;
+                ++i;
+
+                if (i >= 15)
+                {
+                    break;
+                }
+
+                colx = subWord(col3);
+                col4 ^= colx;       W[i][0] = col4;
+                col5 ^= col4;       W[i][1] = col5;
+                col6 ^= col5;       W[i][2] = col6;
+                col7 ^= col6;       W[i][3] = col7;
+                ++i;
+            }
+
+            break;
+        }
+        default:
+        {
+            throw new IllegalStateException("Should never get here");
+        }
+        }
+
+        if (!forEncryption)
+        {
+            for (int j = 1; j < ROUNDS; j++)
+            {
+                for (int i = 0; i < 4; i++)
+                {
+                    W[j][i] = inv_mcol(W[j][i]);
+                }
+            }
+        }
+
+        return W;
+    }
+
+    private int         ROUNDS;
+    private int[][]     WorkingKey = null;
+    private boolean     forEncryption;
+
+    private byte[]      s;
+
+    private static final int BLOCK_SIZE = 16;
+
+    /**
+      * Return an AESEngine.
+      *
+      * @return an AES ECB mode cipher.
+      */
+     public static MultiBlockCipher newInstance()
+     {
+         return new AESEngine();
+     }
+
+    /**
+     * default constructor - 128 bit block size.
+     * @deprecated use AESEngine.newInstance()
+     */
+    public AESEngine()
+    {
+        // CryptoServicesRegistrar.checkConstraints(new DefaultServiceProperties(getAlgorithmName(), 256));
+    }
+
+    /**
+     * initialise an AES cipher.
+     *
+     * @param forEncryption whether or not we are for encryption.
+     * @param params the parameters required to set up the cipher.
+     * @exception IllegalArgumentException if the params argument is
+     * inappropriate.
+     */
+    public void init(
+        boolean           forEncryption,
+        CipherParameters  params)
+    {
+        if (params instanceof KeyParameter)
+        {
+            WorkingKey = generateWorkingKey(((KeyParameter)params).getKey(), forEncryption);
+            this.forEncryption = forEncryption;
+            if (forEncryption)
+            {
+                s = Arrays.clone(S);
+            }
+            else
+            {
+                s = Arrays.clone(Si);
+            }
+
+            //CryptoServicesRegistrar.checkConstraints(new DefaultServiceProperties(getAlgorithmName(), bitsOfSecurity(), params, Utils.getPurpose(forEncryption)));
+
+            return;
+        }
+
+        throw new IllegalArgumentException("invalid parameter passed to AES init - " + params.getClass().getName());
+    }
+
+    public String getAlgorithmName()
+    {
+        return "AES";
+    }
+
+    public int getBlockSize()
+    {
+        return BLOCK_SIZE;
+    }
+
+    public int processBlock(byte[] in, int inOff, byte[] out, int outOff)
+    {
+        if (WorkingKey == null)
+        {
+            throw new IllegalStateException("AES engine not initialised");
+        }
+
+        if (inOff > (in.length - BLOCK_SIZE))
+        {
+            throw new DataLengthException("input buffer too short");
+        }
+
+        if (outOff > (out.length - BLOCK_SIZE))
+        {
+            throw new OutputLengthException("output buffer too short");
+        }
+
+        if (forEncryption)
+        {
+            encryptBlock(in, inOff, out, outOff, WorkingKey);
+        }
+        else
+        {
+            decryptBlock(in, inOff, out, outOff, WorkingKey);
+        }
+
+        return BLOCK_SIZE;
+    }
+
+    public void reset()
+    {
+    }
+
+    private void encryptBlock(byte[] in, int inOff, byte[] out, int outOff, int[][] KW)
+    {
+        int C0 = Pack.littleEndianToInt(in, inOff +  0);
+        int C1 = Pack.littleEndianToInt(in, inOff +  4);
+        int C2 = Pack.littleEndianToInt(in, inOff +  8);
+        int C3 = Pack.littleEndianToInt(in, inOff + 12);
+
+        int t0 = C0 ^ KW[0][0];
+        int t1 = C1 ^ KW[0][1];
+        int t2 = C2 ^ KW[0][2];
+
+        int r = 1, r0, r1, r2, r3 = C3 ^ KW[0][3];
+        while (r < ROUNDS - 1)
+        {
+            r0 = T0[t0&255] ^ shift(T0[(t1>>8)&255], 24) ^ shift(T0[(t2>>16)&255], 16) ^ shift(T0[(r3>>24)&255], 8) ^ KW[r][0];
+            r1 = T0[t1&255] ^ shift(T0[(t2>>8)&255], 24) ^ shift(T0[(r3>>16)&255], 16) ^ shift(T0[(t0>>24)&255], 8) ^ KW[r][1];
+            r2 = T0[t2&255] ^ shift(T0[(r3>>8)&255], 24) ^ shift(T0[(t0>>16)&255], 16) ^ shift(T0[(t1>>24)&255], 8) ^ KW[r][2];
+            r3 = T0[r3&255] ^ shift(T0[(t0>>8)&255], 24) ^ shift(T0[(t1>>16)&255], 16) ^ shift(T0[(t2>>24)&255], 8) ^ KW[r++][3];
+            t0 = T0[r0&255] ^ shift(T0[(r1>>8)&255], 24) ^ shift(T0[(r2>>16)&255], 16) ^ shift(T0[(r3>>24)&255], 8) ^ KW[r][0];
+            t1 = T0[r1&255] ^ shift(T0[(r2>>8)&255], 24) ^ shift(T0[(r3>>16)&255], 16) ^ shift(T0[(r0>>24)&255], 8) ^ KW[r][1];
+            t2 = T0[r2&255] ^ shift(T0[(r3>>8)&255], 24) ^ shift(T0[(r0>>16)&255], 16) ^ shift(T0[(r1>>24)&255], 8) ^ KW[r][2];
+            r3 = T0[r3&255] ^ shift(T0[(r0>>8)&255], 24) ^ shift(T0[(r1>>16)&255], 16) ^ shift(T0[(r2>>24)&255], 8) ^ KW[r++][3];
+        }
+
+        r0 = T0[t0&255] ^ shift(T0[(t1>>8)&255], 24) ^ shift(T0[(t2>>16)&255], 16) ^ shift(T0[(r3>>24)&255], 8) ^ KW[r][0];
+        r1 = T0[t1&255] ^ shift(T0[(t2>>8)&255], 24) ^ shift(T0[(r3>>16)&255], 16) ^ shift(T0[(t0>>24)&255], 8) ^ KW[r][1];
+        r2 = T0[t2&255] ^ shift(T0[(r3>>8)&255], 24) ^ shift(T0[(t0>>16)&255], 16) ^ shift(T0[(t1>>24)&255], 8) ^ KW[r][2];
+        r3 = T0[r3&255] ^ shift(T0[(t0>>8)&255], 24) ^ shift(T0[(t1>>16)&255], 16) ^ shift(T0[(t2>>24)&255], 8) ^ KW[r++][3];
+
+        // the final round's table is a simple function of S so we don't use a whole other four tables for it
+
+        C0 = (S[r0&255]&255) ^ ((S[(r1>>8)&255]&255)<<8) ^ ((s[(r2>>16)&255]&255)<<16) ^ (s[(r3>>24)&255]<<24) ^ KW[r][0];
+        C1 = (s[r1&255]&255) ^ ((S[(r2>>8)&255]&255)<<8) ^ ((S[(r3>>16)&255]&255)<<16) ^ (s[(r0>>24)&255]<<24) ^ KW[r][1];
+        C2 = (s[r2&255]&255) ^ ((S[(r3>>8)&255]&255)<<8) ^ ((S[(r0>>16)&255]&255)<<16) ^ (S[(r1>>24)&255]<<24) ^ KW[r][2];
+        C3 = (s[r3&255]&255) ^ ((s[(r0>>8)&255]&255)<<8) ^ ((s[(r1>>16)&255]&255)<<16) ^ (S[(r2>>24)&255]<<24) ^ KW[r][3];
+
+        Pack.intToLittleEndian(C0, out, outOff +  0);
+        Pack.intToLittleEndian(C1, out, outOff +  4);
+        Pack.intToLittleEndian(C2, out, outOff +  8);
+        Pack.intToLittleEndian(C3, out, outOff + 12);
+    }
+
+    private void decryptBlock(byte[] in, int inOff, byte[] out, int outOff, int[][] KW)
+    {
+        int C0 = Pack.littleEndianToInt(in, inOff +  0);
+        int C1 = Pack.littleEndianToInt(in, inOff +  4);
+        int C2 = Pack.littleEndianToInt(in, inOff +  8);
+        int C3 = Pack.littleEndianToInt(in, inOff + 12);
+
+        int t0 = C0 ^ KW[ROUNDS][0];
+        int t1 = C1 ^ KW[ROUNDS][1];
+        int t2 = C2 ^ KW[ROUNDS][2];
+
+        int r = ROUNDS - 1, r0, r1, r2, r3 = C3 ^ KW[ROUNDS][3];
+        while (r > 1)
+        {
+            r0 = Tinv0[t0&255] ^ shift(Tinv0[(r3>>8)&255], 24) ^ shift(Tinv0[(t2>>16)&255], 16) ^ shift(Tinv0[(t1>>24)&255], 8) ^ KW[r][0];
+            r1 = Tinv0[t1&255] ^ shift(Tinv0[(t0>>8)&255], 24) ^ shift(Tinv0[(r3>>16)&255], 16) ^ shift(Tinv0[(t2>>24)&255], 8) ^ KW[r][1];
+            r2 = Tinv0[t2&255] ^ shift(Tinv0[(t1>>8)&255], 24) ^ shift(Tinv0[(t0>>16)&255], 16) ^ shift(Tinv0[(r3>>24)&255], 8) ^ KW[r][2];
+            r3 = Tinv0[r3&255] ^ shift(Tinv0[(t2>>8)&255], 24) ^ shift(Tinv0[(t1>>16)&255], 16) ^ shift(Tinv0[(t0>>24)&255], 8) ^ KW[r--][3];
+            t0 = Tinv0[r0&255] ^ shift(Tinv0[(r3>>8)&255], 24) ^ shift(Tinv0[(r2>>16)&255], 16) ^ shift(Tinv0[(r1>>24)&255], 8) ^ KW[r][0];
+            t1 = Tinv0[r1&255] ^ shift(Tinv0[(r0>>8)&255], 24) ^ shift(Tinv0[(r3>>16)&255], 16) ^ shift(Tinv0[(r2>>24)&255], 8) ^ KW[r][1];
+            t2 = Tinv0[r2&255] ^ shift(Tinv0[(r1>>8)&255], 24) ^ shift(Tinv0[(r0>>16)&255], 16) ^ shift(Tinv0[(r3>>24)&255], 8) ^ KW[r][2];
+            r3 = Tinv0[r3&255] ^ shift(Tinv0[(r2>>8)&255], 24) ^ shift(Tinv0[(r1>>16)&255], 16) ^ shift(Tinv0[(r0>>24)&255], 8) ^ KW[r--][3];
+        }
+
+        r0 = Tinv0[t0&255] ^ shift(Tinv0[(r3>>8)&255], 24) ^ shift(Tinv0[(t2>>16)&255], 16) ^ shift(Tinv0[(t1>>24)&255], 8) ^ KW[r][0];
+        r1 = Tinv0[t1&255] ^ shift(Tinv0[(t0>>8)&255], 24) ^ shift(Tinv0[(r3>>16)&255], 16) ^ shift(Tinv0[(t2>>24)&255], 8) ^ KW[r][1];
+        r2 = Tinv0[t2&255] ^ shift(Tinv0[(t1>>8)&255], 24) ^ shift(Tinv0[(t0>>16)&255], 16) ^ shift(Tinv0[(r3>>24)&255], 8) ^ KW[r][2];
+        r3 = Tinv0[r3&255] ^ shift(Tinv0[(t2>>8)&255], 24) ^ shift(Tinv0[(t1>>16)&255], 16) ^ shift(Tinv0[(t0>>24)&255], 8) ^ KW[r][3];
+        
+        // the final round's table is a simple function of Si so we don't use a whole other four tables for it
+
+        C0 = (Si[r0&255]&255) ^ ((s[(r3>>8)&255]&255)<<8) ^ ((s[(r2>>16)&255]&255)<<16) ^ (Si[(r1>>24)&255]<<24) ^ KW[0][0];
+        C1 = (s[r1&255]&255) ^ ((s[(r0>>8)&255]&255)<<8) ^ ((Si[(r3>>16)&255]&255)<<16) ^ (s[(r2>>24)&255]<<24) ^ KW[0][1];
+        C2 = (s[r2&255]&255) ^ ((Si[(r1>>8)&255]&255)<<8) ^ ((Si[(r0>>16)&255]&255)<<16) ^ (s[(r3>>24)&255]<<24) ^ KW[0][2];
+        C3 = (Si[r3&255]&255) ^ ((s[(r2>>8)&255]&255)<<8) ^ ((s[(r1>>16)&255]&255)<<16) ^ (s[(r0>>24)&255]<<24) ^ KW[0][3];
+
+        Pack.intToLittleEndian(C0, out, outOff +  0);
+        Pack.intToLittleEndian(C1, out, outOff +  4);
+        Pack.intToLittleEndian(C2, out, outOff +  8);
+        Pack.intToLittleEndian(C3, out, outOff + 12);
+    }
+
+    private int bitsOfSecurity()
+    {
+        if (WorkingKey == null)
+        {
+            return 256;
+        }
+        return (WorkingKey.length - 7) << 5;
+    }
+}
diff --git a/app/src/main/java/org/bouncycastle/shaded/crypto/macs/CBCBlockCipherMac.java b/app/src/main/java/org/bouncycastle/shaded/crypto/macs/CBCBlockCipherMac.java
new file mode 100644
index 0000000..b86ec14
--- /dev/null
+++ b/app/src/main/java/org/bouncycastle/shaded/crypto/macs/CBCBlockCipherMac.java
@@ -0,0 +1,229 @@
+package org.bouncycastle.shaded.crypto.macs;
+
+import org.bouncycastle.shaded.crypto.BlockCipher;
+import org.bouncycastle.shaded.crypto.CipherParameters;
+import org.bouncycastle.shaded.crypto.Mac;
+import org.bouncycastle.shaded.crypto.modes.CBCBlockCipher;
+import org.bouncycastle.shaded.crypto.paddings.BlockCipherPadding;
+
+/**
+ * standard CBC Block Cipher MAC - if no padding is specified the default of
+ * pad of zeroes is used.
+ */
+public class CBCBlockCipherMac
+    implements Mac
+{
+    private byte[]              mac;
+
+    private byte[]              buf;
+    private int                 bufOff;
+    private BlockCipher         cipher;
+    private BlockCipherPadding  padding;
+
+    private int                 macSize;
+
+    /**
+     * create a standard MAC based on a CBC block cipher. This will produce an
+     * authentication code half the length of the block size of the cipher.
+     *
+     * @param cipher the cipher to be used as the basis of the MAC generation.
+     */
+    public CBCBlockCipherMac(
+        BlockCipher     cipher)
+    {
+        this(cipher, (cipher.getBlockSize() * 8) / 2, null);
+    }
+
+    /**
+     * create a standard MAC based on a CBC block cipher. This will produce an
+     * authentication code half the length of the block size of the cipher.
+     *
+     * @param cipher the cipher to be used as the basis of the MAC generation.
+     * @param padding the padding to be used to complete the last block.
+     */
+    public CBCBlockCipherMac(
+        BlockCipher         cipher,
+        org.bouncycastle.shaded.crypto.paddings.BlockCipherPadding padding)
+    {
+        this(cipher, (cipher.getBlockSize() * 8) / 2, padding);
+    }
+
+    /**
+     * create a standard MAC based on a block cipher with the size of the
+     * MAC been given in bits. This class uses CBC mode as the basis for the
+     * MAC generation.
+     * <p>
+     * Note: the size of the MAC must be at least 24 bits (FIPS Publication 81),
+     * or 16 bits if being used as a data authenticator (FIPS Publication 113),
+     * and in general should be less than the size of the block cipher as it reduces
+     * the chance of an exhaustive attack (see Handbook of Applied Cryptography).
+     *
+     * @param cipher the cipher to be used as the basis of the MAC generation.
+     * @param macSizeInBits the size of the MAC in bits, must be a multiple of 8.
+     */
+    public CBCBlockCipherMac(
+        BlockCipher     cipher,
+        int             macSizeInBits)
+    {
+        this(cipher, macSizeInBits, null);
+    }
+
+    /**
+     * create a standard MAC based on a block cipher with the size of the
+     * MAC been given in bits. This class uses CBC mode as the basis for the
+     * MAC generation.
+     * <p>
+     * Note: the size of the MAC must be at least 24 bits (FIPS Publication 81),
+     * or 16 bits if being used as a data authenticator (FIPS Publication 113),
+     * and in general should be less than the size of the block cipher as it reduces
+     * the chance of an exhaustive attack (see Handbook of Applied Cryptography).
+     *
+     * @param cipher the cipher to be used as the basis of the MAC generation.
+     * @param macSizeInBits the size of the MAC in bits, must be a multiple of 8.
+     * @param padding the padding to be used to complete the last block.
+     */
+    public CBCBlockCipherMac(
+        BlockCipher         cipher,
+        int                 macSizeInBits,
+        BlockCipherPadding padding)
+    {
+        if ((macSizeInBits % 8) != 0)
+        {
+            throw new IllegalArgumentException("MAC size must be multiple of 8");
+        }
+
+        this.cipher = CBCBlockCipher.newInstance(cipher);
+        this.padding = padding;
+        this.macSize = macSizeInBits / 8;
+
+        mac = new byte[cipher.getBlockSize()];
+
+        buf = new byte[cipher.getBlockSize()];
+        bufOff = 0;
+    }
+
+    public String getAlgorithmName()
+    {
+        return cipher.getAlgorithmName();
+    }
+
+    public void init(
+        CipherParameters    params)
+    {
+        reset();
+
+        cipher.init(true, params);
+    }
+
+    public int getMacSize()
+    {
+        return macSize;
+    }
+
+    public void update(
+        byte        in)
+    {
+        if (bufOff == buf.length)
+        {
+            cipher.processBlock(buf, 0, mac, 0);
+            bufOff = 0;
+        }
+
+        buf[bufOff++] = in;
+    }
+
+    public void update(
+        byte[]      in,
+        int         inOff,
+        int         len)
+    {
+        if (len < 0)
+        {
+            throw new IllegalArgumentException("Can't have a negative input length!");
+        }
+
+        int blockSize = cipher.getBlockSize();
+        int gapLen = blockSize - bufOff;
+
+        if (len > gapLen)
+        {
+            System.arraycopy(in, inOff, buf, bufOff, gapLen);
+
+            cipher.processBlock(buf, 0, mac, 0);
+
+            bufOff = 0;
+            len -= gapLen;
+            inOff += gapLen;
+
+            while (len > blockSize)
+            {
+                cipher.processBlock(in, inOff, mac, 0);
+
+                len -= blockSize;
+                inOff += blockSize;
+            }
+        }
+
+        System.arraycopy(in, inOff, buf, bufOff, len);
+
+        bufOff += len;
+    }
+
+    public int doFinal(
+        byte[]  out,
+        int     outOff)
+    {
+        int blockSize = cipher.getBlockSize();
+
+        if (padding == null)
+        {
+            //
+            // pad with zeroes
+            //
+            while (bufOff < blockSize)
+            {
+                buf[bufOff] = 0;
+                bufOff++;
+            }
+        }
+        else
+        {
+            if (bufOff == blockSize)
+            {
+                cipher.processBlock(buf, 0, mac, 0);
+                bufOff = 0;
+            }
+
+            padding.addPadding(buf, bufOff);
+        }
+
+        cipher.processBlock(buf, 0, mac, 0);
+
+        System.arraycopy(mac, 0, out, outOff, macSize);
+
+        reset();
+
+        return macSize;
+    }
+
+    /**
+     * Reset the mac generator.
+     */
+    public void reset()
+    {
+        /*
+         * clean the buffer.
+         */
+        for (int i = 0; i < buf.length; i++)
+        {
+            buf[i] = 0;
+        }
+
+        bufOff = 0;
+
+        /*
+         * reset the underlying cipher.
+         */
+        cipher.reset();
+    }
+}
diff --git a/app/src/main/java/org/bouncycastle/shaded/crypto/modes/AEADBlockCipher.java b/app/src/main/java/org/bouncycastle/shaded/crypto/modes/AEADBlockCipher.java
new file mode 100644
index 0000000..27241d6
--- /dev/null
+++ b/app/src/main/java/org/bouncycastle/shaded/crypto/modes/AEADBlockCipher.java
@@ -0,0 +1,17 @@
+package org.bouncycastle.shaded.crypto.modes;
+
+import org.bouncycastle.shaded.crypto.BlockCipher;
+
+/**
+ * An {@link AEADCipher} based on a {@link BlockCipher}.
+ */
+public interface AEADBlockCipher
+    extends AEADCipher
+{
+    /**
+     * return the {@link BlockCipher} this object wraps.
+     *
+     * @return the {@link BlockCipher} this object wraps.
+     */
+    public BlockCipher getUnderlyingCipher();
+}
diff --git a/app/src/main/java/org/bouncycastle/shaded/crypto/modes/AEADCipher.java b/app/src/main/java/org/bouncycastle/shaded/crypto/modes/AEADCipher.java
new file mode 100644
index 0000000..2b40f10
--- /dev/null
+++ b/app/src/main/java/org/bouncycastle/shaded/crypto/modes/AEADCipher.java
@@ -0,0 +1,139 @@
+package org.bouncycastle.shaded.crypto.modes;
+
+import org.bouncycastle.shaded.crypto.CipherParameters;
+import org.bouncycastle.shaded.crypto.DataLengthException;
+import org.bouncycastle.shaded.crypto.InvalidCipherTextException;
+import org.bouncycastle.shaded.crypto.params.AEADParameters;
+
+/**
+ * A cipher mode that includes authenticated encryption with a streaming mode and optional associated data.
+ * <p>
+ * Implementations of this interface may operate in a packet mode (where all input data is buffered and 
+ * processed during the call to {@link #doFinal(byte[], int)}), or in a streaming mode (where output data is
+ * incrementally produced with each call to {@link #processByte(byte, byte[], int)} or 
+ * {@link #processBytes(byte[], int, int, byte[], int)}.
+ * </p>
+ * This is important to consider during decryption: in a streaming mode, unauthenticated plaintext data
+ * may be output prior to the call to {@link #doFinal(byte[], int)} that results in an authentication
+ * failure. The higher level protocol utilising this cipher must ensure the plaintext data is handled 
+ * appropriately until the end of data is reached and the entire ciphertext is authenticated.
+ * @see AEADParameters
+ */
+public interface AEADCipher
+{
+    /**
+     * initialise the underlying cipher. Parameter can either be an AEADParameters or a ParametersWithIV object.
+     *
+     * @param forEncryption true if we are setting up for encryption, false otherwise.
+     * @param params the necessary parameters for the underlying cipher to be initialised.
+     * @exception IllegalArgumentException if the params argument is inappropriate.
+     */
+    public void init(boolean forEncryption, CipherParameters params)
+        throws IllegalArgumentException;
+
+    /**
+     * Return the name of the algorithm.
+     * 
+     * @return the algorithm name.
+     */
+    public String getAlgorithmName();
+
+    /**
+     * Add a single byte to the associated data check.
+     * <br>If the implementation supports it, this will be an online operation and will not retain the associated data.
+     *
+     * @param in the byte to be processed.
+     */
+    public void processAADByte(byte in);
+
+    /**
+     * Add a sequence of bytes to the associated data check.
+     * <br>If the implementation supports it, this will be an online operation and will not retain the associated data.
+     *
+     * @param in the input byte array.
+     * @param inOff the offset into the in array where the data to be processed starts.
+     * @param len the number of bytes to be processed.
+     */
+    public void processAADBytes(byte[] in, int inOff, int len);
+
+    /**
+     * encrypt/decrypt a single byte.
+     *
+     * @param in the byte to be processed.
+     * @param out the output buffer the processed byte goes into.
+     * @param outOff the offset into the output byte array the processed data starts at.
+     * @return the number of bytes written to out.
+     * @exception DataLengthException if the output buffer is too small.
+     */
+    public int processByte(byte in, byte[] out, int outOff)
+        throws DataLengthException;
+
+    /**
+     * process a block of bytes from in putting the result into out.
+     *
+     * @param in the input byte array.
+     * @param inOff the offset into the in array where the data to be processed starts.
+     * @param len the number of bytes to be processed.
+     * @param out the output buffer the processed bytes go into.
+     * @param outOff the offset into the output byte array the processed data starts at.
+     * @return the number of bytes written to out.
+     * @exception DataLengthException if the output buffer is too small.
+     */
+    public int processBytes(byte[] in, int inOff, int len, byte[] out, int outOff)
+        throws DataLengthException;
+
+    /**
+     * Finish the operation either appending or verifying the MAC at the end of the data.
+     *
+     * @param out space for any resulting output data.
+     * @param outOff offset into out to start copying the data at.
+     * @return number of bytes written into out.
+     * @throws IllegalStateException if the cipher is in an inappropriate state.
+     * @throws InvalidCipherTextException if the MAC fails to match.
+     */
+    public int doFinal(byte[] out, int outOff)
+        throws IllegalStateException, InvalidCipherTextException;
+
+    /**
+     * Return the value of the MAC associated with the last stream processed.
+     *
+     * @return MAC for plaintext data.
+     */
+    public byte[] getMac();
+
+    /**
+     * return the size of the output buffer required for a processBytes
+     * an input of len bytes.
+     * <p>
+     * The returned size may be dependent on the initialisation of this cipher
+     * and may not be accurate once subsequent input data is processed - this method
+     * should be invoked immediately prior to input data being processed.
+     * </p>
+     *
+     * @param len the length of the input.
+     * @return the space required to accommodate a call to processBytes
+     * with len bytes of input.
+     */
+    public int getUpdateOutputSize(int len);
+
+    /**
+     * return the size of the output buffer required for a processBytes plus a
+     * doFinal with an input of len bytes.
+     * <p>
+     * The returned size may be dependent on the initialisation of this cipher
+     * and may not be accurate once subsequent input data is processed - this method
+     * should be invoked immediately prior to a call to final processing of input data
+     * and a call to {@link #doFinal(byte[], int)}.
+     * </p>
+     * @param len the length of the input.
+     * @return the space required to accommodate a call to processBytes and doFinal
+     * with len bytes of input.
+     */
+    public int getOutputSize(int len);
+
+    /**
+     * Reset the cipher. After resetting the cipher is in the same state
+     * as it was after the last init (if there was one).
+     */
+    public void reset();
+}
diff --git a/app/src/main/java/org/bouncycastle/shaded/crypto/modes/CBCBlockCipher.java b/app/src/main/java/org/bouncycastle/shaded/crypto/modes/CBCBlockCipher.java
new file mode 100644
index 0000000..67dee82
--- /dev/null
+++ b/app/src/main/java/org/bouncycastle/shaded/crypto/modes/CBCBlockCipher.java
@@ -0,0 +1,266 @@
+package org.bouncycastle.shaded.crypto.modes;
+
+import org.bouncycastle.shaded.crypto.BlockCipher;
+import org.bouncycastle.shaded.crypto.CipherParameters;
+import org.bouncycastle.shaded.crypto.DataLengthException;
+import org.bouncycastle.shaded.crypto.DefaultMultiBlockCipher;
+import org.bouncycastle.shaded.crypto.params.ParametersWithIV;
+import org.bouncycastle.shaded.util.Arrays;
+
+/**
+ * implements Cipher-Block-Chaining (CBC) mode on top of a simple cipher.
+ */
+public class CBCBlockCipher
+    extends DefaultMultiBlockCipher
+    implements CBCModeCipher
+{
+    private byte[]          IV;
+    private byte[]          cbcV;
+    private byte[]          cbcNextV;
+
+    private int             blockSize;
+    private BlockCipher     cipher = null;
+    private boolean         encrypting;
+
+    /**
+     * Return a new CBC mode cipher based on the passed in base cipher
+     *
+     * @param cipher the base cipher for the CBC mode.
+     */
+    public static CBCModeCipher newInstance(BlockCipher cipher)
+    {
+        return new CBCBlockCipher(cipher);
+    }
+
+    /**
+     * Basic constructor.
+     *
+     * @param cipher the block cipher to be used as the basis of chaining.
+     * @deprecated use the CBCBlockCipher.newInstance() static method.
+     */
+    public CBCBlockCipher(
+        BlockCipher cipher)
+    {
+        this.cipher = cipher;
+        this.blockSize = cipher.getBlockSize();
+
+        this.IV = new byte[blockSize];
+        this.cbcV = new byte[blockSize];
+        this.cbcNextV = new byte[blockSize];
+    }
+
+    /**
+     * return the underlying block cipher that we are wrapping.
+     *
+     * @return the underlying block cipher that we are wrapping.
+     */
+    public BlockCipher getUnderlyingCipher()
+    {
+        return cipher;
+    }
+
+    /**
+     * Initialise the cipher and, possibly, the initialisation vector (IV).
+     * If an IV isn't passed as part of the parameter, the IV will be all zeros.
+     *
+     * @param encrypting if true the cipher is initialised for
+     *  encryption, if false for decryption.
+     * @param params the key and other data required by the cipher.
+     * @exception IllegalArgumentException if the params argument is
+     * inappropriate.
+     */
+    public void init(
+        boolean             encrypting,
+        CipherParameters    params)
+        throws IllegalArgumentException
+    {
+        boolean oldEncrypting = this.encrypting;
+
+        this.encrypting = encrypting;
+
+        if (params instanceof ParametersWithIV)
+        {
+            ParametersWithIV ivParam = (ParametersWithIV)params;
+            byte[] iv = ivParam.getIV();
+
+            if (iv.length != blockSize)
+            {
+                throw new IllegalArgumentException("initialisation vector must be the same length as block size");
+            }
+
+            System.arraycopy(iv, 0, IV, 0, iv.length);
+
+            reset();
+
+            // if null it's an IV changed only.
+            if (ivParam.getParameters() != null)
+            {
+                cipher.init(encrypting, ivParam.getParameters());
+            }
+            else if (oldEncrypting != encrypting)
+            {
+                throw new IllegalArgumentException("cannot change encrypting state without providing key.");
+            }
+        }
+        else
+        {
+            reset();
+
+            // if it's null, key is to be reused.
+            if (params != null)
+            {
+                cipher.init(encrypting, params);
+            }
+            else if (oldEncrypting != encrypting)
+            {
+                throw new IllegalArgumentException("cannot change encrypting state without providing key.");
+            }
+        }
+    }
+
+    /**
+     * return the algorithm name and mode.
+     *
+     * @return the name of the underlying algorithm followed by "/CBC".
+     */
+    public String getAlgorithmName()
+    {
+        return cipher.getAlgorithmName() + "/CBC";
+    }
+
+    /**
+     * return the block size of the underlying cipher.
+     *
+     * @return the block size of the underlying cipher.
+     */
+    public int getBlockSize()
+    {
+        return cipher.getBlockSize();
+    }
+
+    /**
+     * Process one block of input from the array in and write it to
+     * the out array.
+     *
+     * @param in the array containing the input data.
+     * @param inOff offset into the in array the data starts at.
+     * @param out the array the output data will be copied into.
+     * @param outOff the offset into the out array the output will start at.
+     * @exception DataLengthException if there isn't enough data in in, or
+     * space in out.
+     * @exception IllegalStateException if the cipher isn't initialised.
+     * @return the number of bytes processed and produced.
+     */
+    public int processBlock(
+        byte[]      in,
+        int         inOff,
+        byte[]      out,
+        int         outOff)
+        throws DataLengthException, IllegalStateException
+    {
+        return (encrypting) ? encryptBlock(in, inOff, out, outOff) : decryptBlock(in, inOff, out, outOff);
+    }
+
+    /**
+     * reset the chaining vector back to the IV and reset the underlying
+     * cipher.
+     */
+    public void reset()
+    {
+        System.arraycopy(IV, 0, cbcV, 0, IV.length);
+        Arrays.fill(cbcNextV, (byte)0);
+
+        cipher.reset();
+    }
+
+    /**
+     * Do the appropriate chaining step for CBC mode encryption.
+     *
+     * @param in the array containing the data to be encrypted.
+     * @param inOff offset into the in array the data starts at.
+     * @param out the array the encrypted data will be copied into.
+     * @param outOff the offset into the out array the output will start at.
+     * @exception DataLengthException if there isn't enough data in in, or
+     * space in out.
+     * @exception IllegalStateException if the cipher isn't initialised.
+     * @return the number of bytes processed and produced.
+     */
+    private int encryptBlock(
+        byte[]      in,
+        int         inOff,
+        byte[]      out,
+        int         outOff)
+        throws DataLengthException, IllegalStateException
+    {
+        if ((inOff + blockSize) > in.length)
+        {
+            throw new DataLengthException("input buffer too short");
+        }
+
+        /*
+         * XOR the cbcV and the input,
+         * then encrypt the cbcV
+         */
+        for (int i = 0; i < blockSize; i++)
+        {
+            cbcV[i] ^= in[inOff + i];
+        }
+
+        int length = cipher.processBlock(cbcV, 0, out, outOff);
+
+        /*
+         * copy ciphertext to cbcV
+         */
+        System.arraycopy(out, outOff, cbcV, 0, cbcV.length);
+
+        return length;
+    }
+
+    /**
+     * Do the appropriate chaining step for CBC mode decryption.
+     *
+     * @param in the array containing the data to be decrypted.
+     * @param inOff offset into the in array the data starts at.
+     * @param out the array the decrypted data will be copied into.
+     * @param outOff the offset into the out array the output will start at.
+     * @exception DataLengthException if there isn't enough data in in, or
+     * space in out.
+     * @exception IllegalStateException if the cipher isn't initialised.
+     * @return the number of bytes processed and produced.
+     */
+    private int decryptBlock(
+        byte[]      in,
+        int         inOff,
+        byte[]      out,
+        int         outOff)
+        throws DataLengthException, IllegalStateException
+    {
+        if ((inOff + blockSize) > in.length)
+        {
+            throw new DataLengthException("input buffer too short");
+        }
+
+        System.arraycopy(in, inOff, cbcNextV, 0, blockSize);
+
+        int length = cipher.processBlock(in, inOff, out, outOff);
+
+        /*
+         * XOR the cbcV and the output
+         */
+        for (int i = 0; i < blockSize; i++)
+        {
+            out[outOff + i] ^= cbcV[i];
+        }
+
+        /*
+         * swap the back up buffer into next position
+         */
+        byte[]  tmp;
+
+        tmp = cbcV;
+        cbcV = cbcNextV;
+        cbcNextV = tmp;
+
+        return length;
+    }
+}
diff --git a/app/src/main/java/org/bouncycastle/shaded/crypto/modes/CBCModeCipher.java b/app/src/main/java/org/bouncycastle/shaded/crypto/modes/CBCModeCipher.java
new file mode 100644
index 0000000..09187d2
--- /dev/null
+++ b/app/src/main/java/org/bouncycastle/shaded/crypto/modes/CBCModeCipher.java
@@ -0,0 +1,15 @@
+package org.bouncycastle.shaded.crypto.modes;
+
+import org.bouncycastle.shaded.crypto.BlockCipher;
+import org.bouncycastle.shaded.crypto.MultiBlockCipher;
+
+public interface CBCModeCipher
+    extends MultiBlockCipher
+{
+    /**
+     * return the underlying block cipher that we are wrapping.
+     *
+     * @return the underlying block cipher that we are wrapping.
+     */
+    BlockCipher getUnderlyingCipher();
+}
diff --git a/app/src/main/java/org/bouncycastle/shaded/crypto/modes/CCMBlockCipher.java b/app/src/main/java/org/bouncycastle/shaded/crypto/modes/CCMBlockCipher.java
new file mode 100644
index 0000000..7370c02
--- /dev/null
+++ b/app/src/main/java/org/bouncycastle/shaded/crypto/modes/CCMBlockCipher.java
@@ -0,0 +1,480 @@
+package org.bouncycastle.shaded.crypto.modes;
+
+import java.io.ByteArrayOutputStream;
+
+import org.bouncycastle.shaded.crypto.BlockCipher;
+import org.bouncycastle.shaded.crypto.CipherParameters;
+import org.bouncycastle.shaded.crypto.DataLengthException;
+import org.bouncycastle.shaded.crypto.InvalidCipherTextException;
+import org.bouncycastle.shaded.crypto.Mac;
+import org.bouncycastle.shaded.crypto.OutputLengthException;
+import org.bouncycastle.shaded.crypto.macs.CBCBlockCipherMac;
+import org.bouncycastle.shaded.crypto.params.AEADParameters;
+import org.bouncycastle.shaded.crypto.params.ParametersWithIV;
+import org.bouncycastle.shaded.util.Arrays;
+
+/**
+ * Implements the Counter with Cipher Block Chaining mode (CCM) detailed in
+ * NIST Special Publication 800-38C.
+ * <p>
+ * <b>Note</b>: this mode is a packet mode - it needs all the data up front.
+ */
+public class CCMBlockCipher
+    implements CCMModeCipher
+{
+    private BlockCipher           cipher;
+    private int                   blockSize;
+    private boolean               forEncryption;
+    private byte[]                nonce;
+    private byte[]                initialAssociatedText;
+    private int                   macSize;
+    private CipherParameters      keyParam;
+    private byte[]                macBlock;
+    private ExposedByteArrayOutputStream associatedText = new ExposedByteArrayOutputStream();
+    private ExposedByteArrayOutputStream data = new ExposedByteArrayOutputStream();
+
+    /**
+     * Return a new CCM mode cipher based on the passed in base cipher
+     *
+     * @param cipher the base cipher for the CCM mode.
+     */
+    public static CCMModeCipher newInstance(BlockCipher cipher)
+    {
+        return new CCMBlockCipher(cipher);
+    }
+
+    /**
+     * Basic constructor.
+     *
+     * @param c the block cipher to be used.
+     * @deprecated use the CCMBlockCipher.newInstance() static method.
+     */
+    public CCMBlockCipher(BlockCipher c)
+    {
+        this.cipher = c;
+        this.blockSize = c.getBlockSize();
+        this.macBlock = new byte[blockSize];
+
+        if (blockSize != 16)
+        {
+            throw new IllegalArgumentException("cipher required with a block size of 16.");
+        }
+    }
+
+    /**
+     * return the underlying block cipher that we are wrapping.
+     *
+     * @return the underlying block cipher that we are wrapping.
+     */
+    public BlockCipher getUnderlyingCipher()
+    {
+        return cipher;
+    }
+
+
+    public void init(boolean forEncryption, CipherParameters params)
+          throws IllegalArgumentException
+    {
+        this.forEncryption = forEncryption;
+
+        CipherParameters cipherParameters;
+        if (params instanceof AEADParameters)
+        {
+            AEADParameters param = (AEADParameters)params;
+
+            nonce = param.getNonce();
+            initialAssociatedText = param.getAssociatedText();
+            macSize = getMacSize(forEncryption, param.getMacSize());
+            cipherParameters = param.getKey();
+        }
+        else if (params instanceof ParametersWithIV)
+        {
+            ParametersWithIV param = (ParametersWithIV)params;
+
+            nonce = param.getIV();
+            initialAssociatedText = null;
+            macSize = getMacSize(forEncryption, 64);
+            cipherParameters = param.getParameters();
+        }
+        else
+        {
+            throw new IllegalArgumentException("invalid parameters passed to CCM: " + params.getClass().getName());
+        }
+
+        // NOTE: Very basic support for key re-use, but no performance gain from it
+        if (cipherParameters != null)
+        {
+            keyParam = cipherParameters;
+        }
+
+        if (nonce == null || nonce.length < 7 || nonce.length > 13)
+        {
+            throw new IllegalArgumentException("nonce must have length from 7 to 13 octets");
+        }
+
+        reset();
+    }
+
+    public String getAlgorithmName()
+    {
+        return cipher.getAlgorithmName() + "/CCM";
+    }
+
+    public void processAADByte(byte in)
+    {
+        associatedText.write(in);
+    }
+
+    public void processAADBytes(byte[] in, int inOff, int len)
+    {
+        // TODO: Process AAD online
+        associatedText.write(in, inOff, len);
+    }
+
+    public int processByte(byte in, byte[] out, int outOff)
+        throws DataLengthException, IllegalStateException
+    {
+        data.write(in);
+
+        return 0;
+    }
+
+    public int processBytes(byte[] in, int inOff, int inLen, byte[] out, int outOff)
+        throws DataLengthException, IllegalStateException
+    {
+        if (in.length < (inOff + inLen))
+        {
+            throw new DataLengthException("Input buffer too short");
+        }
+        data.write(in, inOff, inLen);
+
+        return 0;
+    }
+
+    public int doFinal(byte[] out, int outOff)
+        throws IllegalStateException, InvalidCipherTextException
+    {
+        int len = processPacket(data.getBuffer(), 0, data.size(), out, outOff);
+
+        reset();
+
+        return len;
+    }
+
+    public void reset()
+    {
+        cipher.reset();
+        associatedText.reset();
+        data.reset();
+    }
+
+    /**
+     * Returns a byte array containing the mac calculated as part of the
+     * last encrypt or decrypt operation.
+     *
+     * @return the last mac calculated.
+     */
+    public byte[] getMac()
+    {
+        byte[] mac = new byte[macSize];
+
+        System.arraycopy(macBlock, 0, mac, 0, mac.length);
+
+        return mac;
+    }
+
+    public int getUpdateOutputSize(int len)
+    {
+        return 0;
+    }
+
+    public int getOutputSize(int len)
+    {
+        int totalData = len + data.size();
+
+        if (forEncryption)
+        {
+             return totalData + macSize;
+        }
+
+        return totalData < macSize ? 0 : totalData - macSize;
+    }
+
+    /**
+     * Process a packet of data for either CCM decryption or encryption.
+     *
+     * @param in data for processing.
+     * @param inOff offset at which data starts in the input array.
+     * @param inLen length of the data in the input array.
+     * @return a byte array containing the processed input..
+     * @throws IllegalStateException if the cipher is not appropriately set up.
+     * @throws InvalidCipherTextException if the input data is truncated or the mac check fails.
+     */
+    public byte[] processPacket(byte[] in, int inOff, int inLen)
+        throws IllegalStateException, InvalidCipherTextException
+    {
+        byte[] output;
+
+        if (forEncryption)
+        {
+            output = new byte[inLen + macSize];
+        }
+        else
+        {
+            if (inLen < macSize)
+            {
+                throw new InvalidCipherTextException("data too short");
+            }
+            output = new byte[inLen - macSize];
+        }
+
+        processPacket(in, inOff, inLen, output, 0);
+
+        return output;
+    }
+
+    /**
+     * Process a packet of data for either CCM decryption or encryption.
+     *
+     * @param in data for processing.
+     * @param inOff offset at which data starts in the input array.
+     * @param inLen length of the data in the input array.
+     * @param output output array.
+     * @param outOff offset into output array to start putting processed bytes.
+     * @return the number of bytes added to output.
+     * @throws IllegalStateException if the cipher is not appropriately set up.
+     * @throws InvalidCipherTextException if the input data is truncated or the mac check fails.
+     * @throws DataLengthException if output buffer too short.
+     */
+    public int processPacket(byte[] in, int inOff, int inLen, byte[] output, int outOff)
+        throws IllegalStateException, InvalidCipherTextException, DataLengthException
+    {
+        // TODO: handle null keyParam (e.g. via RepeatedKeySpec)
+        // Need to keep the CTR and CBC Mac parts around and reset
+        if (keyParam == null)
+        {
+            throw new IllegalStateException("CCM cipher unitialized.");
+        }
+
+        int n = nonce.length;
+        int q = 15 - n;
+        if (q < 4)
+        {
+            int limitLen = 1 << (8 * q);
+            if (inLen >= limitLen)
+            {
+                throw new IllegalStateException("CCM packet too large for choice of q.");
+            }
+        }
+
+        byte[] iv = new byte[blockSize];
+        iv[0] = (byte)((q - 1) & 0x7);
+        System.arraycopy(nonce, 0, iv, 1, nonce.length);
+
+        BlockCipher ctrCipher = SICBlockCipher.newInstance(cipher);
+        ctrCipher.init(forEncryption, new ParametersWithIV(keyParam, iv));
+
+        int outputLen;
+        int inIndex = inOff;
+        int outIndex = outOff;
+
+        if (forEncryption)
+        {
+            outputLen = inLen + macSize;
+            if (output.length < (outputLen + outOff))
+            {
+                throw new OutputLengthException("Output buffer too short.");
+            }
+
+            calculateMac(in, inOff, inLen, macBlock);
+
+            byte[] encMac = new byte[blockSize];
+
+            ctrCipher.processBlock(macBlock, 0, encMac, 0);   // S0
+
+            while (inIndex < (inOff + inLen - blockSize))                 // S1...
+            {
+                ctrCipher.processBlock(in, inIndex, output, outIndex);
+                outIndex += blockSize;
+                inIndex += blockSize;
+            }
+
+            byte[] block = new byte[blockSize];
+
+            System.arraycopy(in, inIndex, block, 0, inLen + inOff - inIndex);
+
+            ctrCipher.processBlock(block, 0, block, 0);
+
+            System.arraycopy(block, 0, output, outIndex, inLen + inOff - inIndex);
+
+            System.arraycopy(encMac, 0, output, outOff + inLen, macSize);
+        }
+        else
+        {
+            if (inLen < macSize)
+            {
+                throw new InvalidCipherTextException("data too short");
+            }
+            outputLen = inLen - macSize;
+            if (output.length < (outputLen + outOff))
+            {
+                throw new OutputLengthException("Output buffer too short.");
+            }
+
+            System.arraycopy(in, inOff + outputLen, macBlock, 0, macSize);
+
+            ctrCipher.processBlock(macBlock, 0, macBlock, 0);
+
+            for (int i = macSize; i != macBlock.length; i++)
+            {
+                macBlock[i] = 0;
+            }
+
+            while (inIndex < (inOff + outputLen - blockSize))
+            {
+                ctrCipher.processBlock(in, inIndex, output, outIndex);
+                outIndex += blockSize;
+                inIndex += blockSize;
+            }
+
+            byte[] block = new byte[blockSize];
+
+            System.arraycopy(in, inIndex, block, 0, outputLen - (inIndex - inOff));
+
+            ctrCipher.processBlock(block, 0, block, 0);
+
+            System.arraycopy(block, 0, output, outIndex, outputLen - (inIndex - inOff));
+
+            byte[] calculatedMacBlock = new byte[blockSize];
+
+            calculateMac(output, outOff, outputLen, calculatedMacBlock);
+
+            if (!Arrays.constantTimeAreEqual(macBlock, calculatedMacBlock))
+            {
+                throw new InvalidCipherTextException("mac check in CCM failed");
+            }
+        }
+
+        return outputLen;
+    }
+
+    private int calculateMac(byte[] data, int dataOff, int dataLen, byte[] macBlock)
+    {
+        Mac cMac = new CBCBlockCipherMac(cipher, macSize * 8);
+
+        cMac.init(keyParam);
+
+        //
+        // build b0
+        //
+        byte[] b0 = new byte[16];
+
+        if (hasAssociatedText())
+        {
+            b0[0] |= 0x40;
+        }
+
+        b0[0] |= (((cMac.getMacSize() - 2) / 2) & 0x7) << 3;
+
+        b0[0] |= ((15 - nonce.length) - 1) & 0x7;
+
+        System.arraycopy(nonce, 0, b0, 1, nonce.length);
+
+        int q = dataLen;
+        int count = 1;
+        while (q > 0)
+        {
+            b0[b0.length - count] = (byte)(q & 0xff);
+            q >>>= 8;
+            count++;
+        }
+
+        cMac.update(b0, 0, b0.length);
+
+        //
+        // process associated text
+        //
+        if (hasAssociatedText())
+        {
+            int extra;
+
+            int textLength = getAssociatedTextLength();
+            if (textLength < ((1 << 16) - (1 << 8)))
+            {
+                cMac.update((byte)(textLength >> 8));
+                cMac.update((byte)textLength);
+
+                extra = 2;
+            }
+            else // can't go any higher than 2^32
+            {
+                cMac.update((byte)0xff);
+                cMac.update((byte)0xfe);
+                cMac.update((byte)(textLength >> 24));
+                cMac.update((byte)(textLength >> 16));
+                cMac.update((byte)(textLength >> 8));
+                cMac.update((byte)textLength);
+
+                extra = 6;
+            }
+
+            if (initialAssociatedText != null)
+            {
+                cMac.update(initialAssociatedText, 0, initialAssociatedText.length);
+            }
+            if (associatedText.size() > 0)
+            {
+                cMac.update(associatedText.getBuffer(), 0, associatedText.size());
+            }
+
+            extra = (extra + textLength) % 16;
+            if (extra != 0)
+            {
+                for (int i = extra; i != 16; i++)
+                {
+                    cMac.update((byte)0x00);
+                }
+            }
+        }
+
+        //
+        // add the text
+        //
+        cMac.update(data, dataOff, dataLen);
+
+        return cMac.doFinal(macBlock, 0);
+    }
+
+    private int getMacSize(boolean forEncryption, int requestedMacBits)
+    {
+        if (forEncryption && (requestedMacBits < 32 || requestedMacBits > 128 || 0 != (requestedMacBits & 15)))
+        {
+            throw new IllegalArgumentException("tag length in octets must be one of {4,6,8,10,12,14,16}");
+        }
+
+        return requestedMacBits >>> 3;
+    }
+
+    private int getAssociatedTextLength()
+    {
+        return associatedText.size() + ((initialAssociatedText == null) ? 0 : initialAssociatedText.length);
+    }
+
+    private boolean hasAssociatedText()
+    {
+        return getAssociatedTextLength() > 0;
+    }
+
+    private static class ExposedByteArrayOutputStream
+        extends ByteArrayOutputStream
+    {
+        public ExposedByteArrayOutputStream()
+        {
+        }
+
+        public byte[] getBuffer()
+        {
+            return this.buf;
+        }
+    }
+}
diff --git a/app/src/main/java/org/bouncycastle/shaded/crypto/modes/CCMModeCipher.java b/app/src/main/java/org/bouncycastle/shaded/crypto/modes/CCMModeCipher.java
new file mode 100644
index 0000000..869e524
--- /dev/null
+++ b/app/src/main/java/org/bouncycastle/shaded/crypto/modes/CCMModeCipher.java
@@ -0,0 +1,6 @@
+package org.bouncycastle.shaded.crypto.modes;
+
+public interface CCMModeCipher
+    extends AEADBlockCipher
+{
+}
diff --git a/app/src/main/java/org/bouncycastle/shaded/crypto/modes/CTRModeCipher.java b/app/src/main/java/org/bouncycastle/shaded/crypto/modes/CTRModeCipher.java
new file mode 100644
index 0000000..babecfa
--- /dev/null
+++ b/app/src/main/java/org/bouncycastle/shaded/crypto/modes/CTRModeCipher.java
@@ -0,0 +1,16 @@
+package org.bouncycastle.shaded.crypto.modes;
+
+import org.bouncycastle.shaded.crypto.BlockCipher;
+import org.bouncycastle.shaded.crypto.MultiBlockCipher;
+import org.bouncycastle.shaded.crypto.SkippingStreamCipher;
+
+public interface CTRModeCipher
+    extends MultiBlockCipher, SkippingStreamCipher
+{
+    /**
+     * return the underlying block cipher that we are wrapping.
+     *
+     * @return the underlying block cipher that we are wrapping.
+     */
+    BlockCipher getUnderlyingCipher();
+}
diff --git a/app/src/main/java/org/bouncycastle/shaded/crypto/modes/SICBlockCipher.java b/app/src/main/java/org/bouncycastle/shaded/crypto/modes/SICBlockCipher.java
new file mode 100644
index 0000000..fee0e55
--- /dev/null
+++ b/app/src/main/java/org/bouncycastle/shaded/crypto/modes/SICBlockCipher.java
@@ -0,0 +1,381 @@
+package org.bouncycastle.shaded.crypto.modes;
+
+import org.bouncycastle.shaded.crypto.BlockCipher;
+import org.bouncycastle.shaded.crypto.CipherParameters;
+import org.bouncycastle.shaded.crypto.DataLengthException;
+import org.bouncycastle.shaded.crypto.OutputLengthException;
+import org.bouncycastle.shaded.crypto.StreamBlockCipher;
+import org.bouncycastle.shaded.crypto.params.ParametersWithIV;
+import org.bouncycastle.shaded.util.Arrays;
+import org.bouncycastle.shaded.util.Pack;
+
+/**
+ * Implements the Segmented Integer Counter (SIC) mode on top of a simple
+ * block cipher. This mode is also known as CTR mode.
+ */
+public class SICBlockCipher
+    extends StreamBlockCipher
+    implements CTRModeCipher
+{
+    private final BlockCipher     cipher;
+    private final int             blockSize;
+
+    private byte[]          IV;
+    private byte[]          counter;
+    private byte[]          counterOut;
+    private int             byteCount;
+
+    /**
+     * Return a new SIC/CTR mode cipher based on the passed in base cipher
+     *
+     * @param cipher the base cipher for the SIC/CTR mode.
+     */
+    public static CTRModeCipher newInstance(BlockCipher cipher)
+    {
+        return new SICBlockCipher(cipher);
+    }
+
+    /**
+     * Basic constructor.
+     *
+     * @param c the block cipher to be used.
+     * @deprecated use newInstance() method.
+     */
+    public SICBlockCipher(BlockCipher c)
+    {
+        super(c);
+
+        this.cipher = c;
+        this.blockSize = cipher.getBlockSize();
+        this.IV = new byte[blockSize];
+        this.counter = new byte[blockSize];
+        this.counterOut = new byte[blockSize];
+        this.byteCount = 0;
+    }
+
+    public void init(
+        boolean             forEncryption, //ignored by this CTR mode
+        CipherParameters    params)
+        throws IllegalArgumentException
+    {
+        if (params instanceof ParametersWithIV)
+        {
+            ParametersWithIV ivParam = (ParametersWithIV)params;
+            this.IV = Arrays.clone(ivParam.getIV());
+
+            if (blockSize < IV.length)
+            {
+                throw new IllegalArgumentException("CTR/SIC mode requires IV no greater than: " + blockSize + " bytes.");
+            }
+
+            int maxCounterSize = (8 > blockSize / 2) ? blockSize / 2 : 8;
+
+            if (blockSize - IV.length > maxCounterSize)
+            {
+                throw new IllegalArgumentException("CTR/SIC mode requires IV of at least: " + (blockSize - maxCounterSize) + " bytes.");
+            }
+
+            // if null it's an IV changed only.
+            if (ivParam.getParameters() != null)
+            {
+                cipher.init(true, ivParam.getParameters());
+            }
+
+            reset();
+        }
+        else
+        {
+            throw new IllegalArgumentException("CTR/SIC mode requires ParametersWithIV");
+        }
+    }
+
+    public String getAlgorithmName()
+    {
+        return cipher.getAlgorithmName() + "/SIC";
+    }
+
+    public int getBlockSize()
+    {
+        return cipher.getBlockSize();
+    }
+
+    public int processBlock(byte[] in, int inOff, byte[] out, int outOff)
+          throws DataLengthException, IllegalStateException
+    {
+        if (byteCount != 0)
+        {
+            processBytes(in, inOff, blockSize, out, outOff);
+            return blockSize;
+        }
+
+        if (inOff + blockSize > in.length)
+        {
+            throw new DataLengthException("input buffer too small");
+        }
+        if (outOff + blockSize > out.length)
+        {
+            throw new OutputLengthException("output buffer too short");
+        }
+
+        cipher.processBlock(counter, 0, counterOut, 0);
+        for (int i = 0; i < blockSize; ++i)
+        {
+            out[outOff + i] = (byte)(in[inOff + i] ^ counterOut[i]);
+        }
+        incrementCounter();
+        return blockSize;
+    }
+
+    public int processBytes(byte[] in, int inOff, int len, byte[] out, int outOff)
+        throws DataLengthException
+    {
+        if (inOff + len > in.length)
+        {
+            throw new DataLengthException("input buffer too small");
+        }
+        if (outOff + len > out.length)
+        {
+            throw new OutputLengthException("output buffer too short");
+        }
+
+        for (int i = 0; i < len; ++i)
+        {
+            byte next;
+
+            if (byteCount == 0)
+            {
+                checkLastIncrement();
+
+                cipher.processBlock(counter, 0, counterOut, 0);
+                next = (byte)(in[inOff + i] ^ counterOut[byteCount++]);
+            }
+            else
+            {
+                next = (byte)(in[inOff + i] ^ counterOut[byteCount++]);
+                if (byteCount == counter.length)
+                {
+                    byteCount = 0;
+                    incrementCounter();
+                }
+            }
+            out[outOff + i] = next;
+        }
+
+        return len;
+    }
+
+    protected byte calculateByte(byte in)
+          throws DataLengthException, IllegalStateException
+    {
+        if (byteCount == 0)
+        {
+            checkLastIncrement();
+
+            cipher.processBlock(counter, 0, counterOut, 0);
+
+            return (byte)(counterOut[byteCount++] ^ in);
+        }
+
+        byte rv = (byte)(counterOut[byteCount++] ^ in);
+
+        if (byteCount == counter.length)
+        {
+            byteCount = 0;
+            incrementCounter();
+        }
+
+        return rv;
+    }
+
+    private void checkCounter()
+    {
+        // if the IV is the same as the blocksize we assume the user knows what they are doing
+        if (IV.length < blockSize)
+        {
+            for (int i = IV.length - 1; i >= 0; i--)
+            {
+                if (counter[i] != IV[i])
+                {
+                    throw new IllegalStateException("Counter in CTR/SIC mode out of range.");
+                }
+            }
+        }
+    }
+
+    private void checkLastIncrement()
+    {
+        // if the IV is the same as the blocksize we assume the user knows what they are doing
+        if (IV.length < blockSize)
+        {
+            if (counter[IV.length - 1] != IV[IV.length - 1])
+            {
+                throw new IllegalStateException("Counter in CTR/SIC mode out of range.");
+            }
+        }
+    }
+
+    private void incrementCounter()
+    {
+        int i = counter.length;
+        while (--i >= 0)
+        {
+            if (++counter[i] != 0)
+            {
+                break;
+            }
+        }
+    }
+
+    private void incrementCounterAt(int pos)
+    {
+        int i = counter.length - pos;
+        while (--i >= 0)
+        {
+            if (++counter[i] != 0)
+            {
+                break;
+            }
+        }
+    }
+
+    private void incrementCounter(int offSet)
+    {
+        byte old = counter[counter.length - 1];
+
+        counter[counter.length - 1] += offSet;
+
+        if (old != 0 && counter[counter.length - 1] < old)
+        {
+            incrementCounterAt(1);
+        }
+    }
+
+    private void decrementCounterAt(int pos)
+    {
+        int i = counter.length - pos;
+        while (--i >= 0)
+        {
+            if (--counter[i] != -1)
+            {
+                return;
+            }
+        }
+    }
+
+    private void adjustCounter(long n)
+    {
+        if (n >= 0)
+        {
+            long numBlocks = (n + byteCount) / blockSize;
+
+            long rem = numBlocks;
+            if (rem > 255)
+            {
+                for (int i = 5; i >= 1; i--)
+                {
+                    long diff = 1L << (8 * i);
+                    while (rem >= diff)
+                    {
+                        incrementCounterAt(i);
+                        rem -= diff;
+                    }
+                }
+            }
+
+            incrementCounter((int)rem);
+
+            byteCount = (int)((n + byteCount) - (blockSize * numBlocks));
+        }
+        else
+        {
+            long numBlocks = (-n - byteCount) / blockSize;
+
+            long rem = numBlocks;
+            if (rem > 255)
+            {
+                for (int i = 5; i >= 1; i--)
+                {
+                    long diff = 1L << (8 * i);
+                    while (rem > diff)
+                    {
+                        decrementCounterAt(i);
+                        rem -= diff;
+                    }
+                }
+            }
+
+            for (long i = 0; i != rem; i++)
+            {
+                decrementCounterAt(0);
+            }
+
+            int gap = (int)(byteCount + n + (blockSize * numBlocks));
+
+            if (gap >= 0)
+            {
+                byteCount = 0;
+            }
+            else
+            {
+                decrementCounterAt(0);
+                byteCount =  blockSize + gap;
+            }
+        }
+    }
+
+    public void reset()
+    {
+        Arrays.fill(counter, (byte)0);
+        System.arraycopy(IV, 0, counter, 0, IV.length);
+        cipher.reset();
+        this.byteCount = 0;
+    }
+
+    public long skip(long numberOfBytes)
+    {
+        adjustCounter(numberOfBytes);
+
+        checkCounter();
+
+        cipher.processBlock(counter, 0, counterOut, 0);
+
+        return numberOfBytes;
+    }
+
+    public long seekTo(long position)
+    {
+        reset();
+
+        return skip(position);
+    }
+
+    public long getPosition()
+    {
+        byte[] res = new byte[counter.length];
+
+        System.arraycopy(counter, 0, res, 0, res.length);
+
+        for (int i = res.length - 1; i >= 1; i--)
+        {
+            int v;
+            if (i < IV.length)
+            {
+                v = (res[i] & 0xff) - (IV[i] & 0xff);
+            }
+            else
+            {
+                v = (res[i] & 0xff);
+            }
+
+            if (v < 0)
+            {
+               res[i - 1]--;
+               v += 256;
+            }
+
+            res[i] = (byte)v;
+        }
+
+        return Pack.bigEndianToLong(res, res.length - 8) * blockSize + byteCount;
+    }
+}
diff --git a/app/src/main/java/org/bouncycastle/shaded/crypto/paddings/BlockCipherPadding.java b/app/src/main/java/org/bouncycastle/shaded/crypto/paddings/BlockCipherPadding.java
new file mode 100644
index 0000000..402bf17
--- /dev/null
+++ b/app/src/main/java/org/bouncycastle/shaded/crypto/paddings/BlockCipherPadding.java
@@ -0,0 +1,48 @@
+package org.bouncycastle.shaded.crypto.paddings;
+
+import java.security.SecureRandom;
+
+import org.bouncycastle.shaded.crypto.InvalidCipherTextException;
+
+/**
+ * Block cipher padders are expected to conform to this interface
+ */
+public interface BlockCipherPadding
+{
+    /**
+     * Initialise the padder.
+     *
+     * @param random the source of randomness for the padding, if required.
+     */
+    public void init(SecureRandom random)
+        throws IllegalArgumentException;
+
+    /**
+     * Return the name of the algorithm the cipher implements.
+     *
+     * @return the name of the algorithm the cipher implements.
+     */
+    public String getPaddingName();
+
+    /**
+     * add the pad bytes to the passed in block, returning the
+     * number of bytes added.
+     * <p>
+     * Note: this assumes that the last block of plain text is always 
+     * passed to it inside in. i.e. if inOff is zero, indicating the
+     * entire block is to be overwritten with padding the value of in
+     * should be the same as the last block of plain text. The reason
+     * for this is that some modes such as "trailing bit compliment"
+     * base the padding on the last byte of plain text.
+     * </p>
+     */
+    public int addPadding(byte[] in, int inOff);
+
+    /**
+     * return the number of pad bytes present in the block.
+     * @exception InvalidCipherTextException if the padding is badly formed
+     * or invalid.
+     */
+    public int padCount(byte[] in)
+        throws InvalidCipherTextException;
+}
diff --git a/app/src/main/java/org/bouncycastle/shaded/crypto/params/AEADParameters.java b/app/src/main/java/org/bouncycastle/shaded/crypto/params/AEADParameters.java
new file mode 100644
index 0000000..c39ffd2
--- /dev/null
+++ b/app/src/main/java/org/bouncycastle/shaded/crypto/params/AEADParameters.java
@@ -0,0 +1,61 @@
+package org.bouncycastle.shaded.crypto.params;
+
+import org.bouncycastle.shaded.crypto.CipherParameters;
+import org.bouncycastle.shaded.util.Arrays;
+
+public class AEADParameters
+    implements CipherParameters
+{
+    private byte[] associatedText;
+    private byte[] nonce;
+    private KeyParameter key;
+    private int macSize;
+
+    /**
+     * Base constructor.
+     *
+     * @param key key to be used by underlying cipher
+     * @param macSize macSize in bits
+     * @param nonce nonce to be used
+     */
+   public AEADParameters(KeyParameter key, int macSize, byte[] nonce)
+    {
+       this(key, macSize, nonce, null);
+    }
+
+    /**
+     * Base constructor.
+     *
+     * @param key key to be used by underlying cipher
+     * @param macSize macSize in bits
+     * @param nonce nonce to be used
+     * @param associatedText initial associated text, if any
+     */
+    public AEADParameters(KeyParameter key, int macSize, byte[] nonce, byte[] associatedText)
+    {
+        this.key = key;
+        this.nonce = Arrays.clone(nonce);
+        this.macSize = macSize;
+        this.associatedText = Arrays.clone(associatedText);
+    }
+
+    public KeyParameter getKey()
+    {
+        return key;
+    }
+
+    public int getMacSize()
+    {
+        return macSize;
+    }
+
+    public byte[] getAssociatedText()
+    {
+        return Arrays.clone(associatedText);
+    }
+
+    public byte[] getNonce()
+    {
+        return Arrays.clone(nonce);
+    }
+}
diff --git a/app/src/main/java/org/bouncycastle/shaded/crypto/params/KeyParameter.java b/app/src/main/java/org/bouncycastle/shaded/crypto/params/KeyParameter.java
new file mode 100644
index 0000000..06224f5
--- /dev/null
+++ b/app/src/main/java/org/bouncycastle/shaded/crypto/params/KeyParameter.java
@@ -0,0 +1,56 @@
+package org.bouncycastle.shaded.crypto.params;
+
+import org.bouncycastle.shaded.crypto.CipherParameters;
+import org.bouncycastle.shaded.util.Arrays;
+
+public class KeyParameter
+    implements CipherParameters
+{
+    private byte[]  key;
+
+    public KeyParameter(
+        byte[]  key)
+    {
+        this(key, 0, key.length);
+    }
+
+    public KeyParameter(
+        byte[]  key,
+        int     keyOff,
+        int     keyLen)
+    {
+        this(keyLen);
+
+        System.arraycopy(key, keyOff, this.key, 0, keyLen);
+    }
+
+    private KeyParameter(int length)
+    {
+        this.key = new byte[length];
+    }
+
+    public void copyTo(byte[] buf, int off, int len)
+    {
+        if (key.length != len)
+            throw new IllegalArgumentException("len");
+
+        System.arraycopy(key, 0, buf, off, len);
+    }
+
+    public byte[] getKey()
+    {
+        return key;
+    }
+
+    public int getKeyLength()
+    {
+        return key.length;
+    }
+
+    public KeyParameter reverse()
+    {
+        KeyParameter reversed = new KeyParameter(key.length);
+        Arrays.reverse(this.key, reversed.key);
+        return reversed;
+    }
+}
diff --git a/app/src/main/java/org/bouncycastle/shaded/crypto/params/ParametersWithIV.java b/app/src/main/java/org/bouncycastle/shaded/crypto/params/ParametersWithIV.java
new file mode 100644
index 0000000..114324c
--- /dev/null
+++ b/app/src/main/java/org/bouncycastle/shaded/crypto/params/ParametersWithIV.java
@@ -0,0 +1,39 @@
+package org.bouncycastle.shaded.crypto.params;
+
+import org.bouncycastle.shaded.crypto.CipherParameters;
+
+public class ParametersWithIV
+    implements CipherParameters
+{
+    private byte[]              iv;
+    private CipherParameters    parameters;
+
+    public ParametersWithIV(
+        CipherParameters    parameters,
+        byte[]              iv)
+    {
+        this(parameters, iv, 0, iv.length);
+    }
+
+    public ParametersWithIV(
+        CipherParameters    parameters,
+        byte[]              iv,
+        int                 ivOff,
+        int                 ivLen)
+    {
+        this.iv = new byte[ivLen];
+        this.parameters = parameters;
+
+        System.arraycopy(iv, ivOff, this.iv, 0, ivLen);
+    }
+
+    public byte[] getIV()
+    {
+        return iv;
+    }
+
+    public CipherParameters getParameters()
+    {
+        return parameters;
+    }
+}
diff --git a/app/src/main/java/org/bouncycastle/shaded/util/Arrays.java b/app/src/main/java/org/bouncycastle/shaded/util/Arrays.java
new file mode 100644
index 0000000..b44ec37
--- /dev/null
+++ b/app/src/main/java/org/bouncycastle/shaded/util/Arrays.java
@@ -0,0 +1,1227 @@
+package org.bouncycastle.shaded.util;
+
+import java.math.BigInteger;
+import java.util.NoSuchElementException;
+
+/**
+ * General array utilities.
+ */
+public final class Arrays
+{
+    private Arrays()
+    {
+        // static class, hide constructor
+    }
+
+    public static boolean areAllZeroes(byte[] buf, int off, int len)
+    {
+        int bits = 0;
+        for (int i = 0; i < len; ++i)
+        {
+            bits |= buf[off + i];
+        }
+        return bits == 0;
+    }
+
+    public static boolean areEqual(boolean[] a, boolean[] b)
+    {
+        return java.util.Arrays.equals(a, b);
+    }
+
+    public static boolean areEqual(byte[] a, byte[] b)
+    {
+        return java.util.Arrays.equals(a, b);
+    }
+
+    public static boolean areEqual(byte[] a, int aFromIndex, int aToIndex, byte[] b, int bFromIndex, int bToIndex)
+    {
+        int aLength = aToIndex - aFromIndex;
+        int bLength = bToIndex - bFromIndex;
+
+        if (aLength != bLength)
+        {
+            return false;
+        }
+
+        for (int i = 0; i < aLength; ++i)
+        {
+            if (a[aFromIndex + i] != b[bFromIndex + i])
+            {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    public static boolean areEqual(char[] a, char[] b)
+    {
+        return java.util.Arrays.equals(a, b);
+    }
+
+    public static boolean areEqual(int[] a, int[] b)
+    {
+        return java.util.Arrays.equals(a, b);
+    }
+
+    public static boolean areEqual(long[] a, long[] b)
+    {
+        return java.util.Arrays.equals(a, b);
+    }
+
+    public static boolean areEqual(Object[] a, Object[] b)
+    {
+        return java.util.Arrays.equals(a, b);
+    }
+
+    public static boolean areEqual(short[] a, short[] b)
+    {
+        return java.util.Arrays.equals(a, b);
+    }
+
+    /**
+     * A constant time equals comparison - does not terminate early if
+     * test will fail. For best results always pass the expected value
+     * as the first parameter.
+     *
+     * @param expected first array
+     * @param supplied second array
+     * @return true if arrays equal, false otherwise.
+     */
+    public static boolean constantTimeAreEqual(
+            byte[]  expected,
+            byte[]  supplied)
+    {
+        if (expected == null || supplied == null)
+        {
+            return false;
+        }
+
+        if (expected == supplied)
+        {
+            return true;
+        }
+
+        int len = (expected.length < supplied.length) ? expected.length : supplied.length;
+
+        int nonEqual = expected.length ^ supplied.length;
+
+        for (int i = 0; i != len; i++)
+        {
+            nonEqual |= (expected[i] ^ supplied[i]);
+        }
+        for (int i = len; i < supplied.length; i++)
+        {
+            nonEqual |= (supplied[i] ^ ~supplied[i]);
+        }
+
+        return nonEqual == 0;
+    }
+
+    public static boolean constantTimeAreEqual(int len, byte[] a, int aOff, byte[] b, int bOff)
+    {
+        if (null == a)
+        {
+            throw new NullPointerException("'a' cannot be null");
+        }
+        if (null == b)
+        {
+            throw new NullPointerException("'b' cannot be null");
+        }
+        if (len < 0)
+        {
+            throw new IllegalArgumentException("'len' cannot be negative");
+        }
+        if (aOff > (a.length - len))
+        {
+            throw new IndexOutOfBoundsException("'aOff' value invalid for specified length");
+        }
+        if (bOff > (b.length - len))
+        {
+            throw new IndexOutOfBoundsException("'bOff' value invalid for specified length");
+        }
+
+        int d = 0;
+        for (int i = 0; i < len; ++i)
+        {
+            d |= (a[aOff + i] ^ b[bOff + i]);
+        }
+        return 0 == d;
+    }
+
+    /**
+     * A constant time equals comparison - does not terminate early if
+     * comparison fails. For best results always pass the expected value
+     * as the first parameter.
+     *
+     * @param expected first array
+     * @param supplied second array
+     * @return true if arrays equal, false otherwise.
+     */
+    public static boolean constantTimeAreEqual(
+            char[] expected,
+            char[] supplied)
+    {
+        if (expected == null || supplied == null)
+        {
+            return false;
+        }
+
+        if (expected == supplied)
+        {
+            return true;
+        }
+
+        int len = Math.min(expected.length, supplied.length);
+
+        int nonEqual = expected.length ^ supplied.length;
+
+        // do the char-wise comparison
+        for (int i = 0; i != len; i++)
+        {
+            nonEqual |= (expected[i] ^ supplied[i]);
+        }
+        // If supplied is longer than expected, iterate over rest of supplied with NOPs
+        for (int i = len; i < supplied.length; i++)
+        {
+            nonEqual |= ((byte)supplied[i] ^ (byte)~supplied[i]);
+        }
+
+        return nonEqual == 0;
+    }
+
+    public static int compareUnsigned(byte[] a, byte[] b)
+    {
+        if (a == b)
+        {
+            return 0;
+        }
+        if (a == null)
+        {
+            return -1;
+        }
+        if (b == null)
+        {
+            return 1;
+        }
+        int minLen = Math.min(a.length, b.length);
+        for (int i = 0; i < minLen; ++i)
+        {
+            int aVal = a[i] & 0xFF, bVal = b[i] & 0xFF;
+            if (aVal < bVal)
+            {
+                return -1;
+            }
+            if (aVal > bVal)
+            {
+                return 1;
+            }
+        }
+        if (a.length < b.length)
+        {
+            return -1;
+        }
+        if (a.length > b.length)
+        {
+            return 1;
+        }
+        return 0;
+    }
+
+    public static boolean contains(boolean[] a, boolean val)
+    {
+        for (int i = 0; i < a.length; ++i)
+        {
+            if (a[i] == val)
+            {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public static boolean contains(byte[] a, byte val)
+    {
+        for (int i = 0; i < a.length; ++i)
+        {
+            if (a[i] == val)
+            {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public static boolean contains(char[] a, char val)
+    {
+        for (int i = 0; i < a.length; ++i)
+        {
+            if (a[i] == val)
+            {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public static boolean contains(int[] a, int val)
+    {
+        for (int i = 0; i < a.length; ++i)
+        {
+            if (a[i] == val)
+            {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public static boolean contains(long[] a, long val)
+    {
+        for (int i = 0; i < a.length; ++i)
+        {
+            if (a[i] == val)
+            {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public static boolean contains(short[] a, short val)
+    {
+        for (int i = 0; i < a.length; ++i)
+        {
+            if (a[i] == val)
+            {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public static void fill(boolean[] a, boolean val)
+    {
+        java.util.Arrays.fill(a, val);
+    }
+
+    public static void fill(boolean[] a, int fromIndex, int toIndex, boolean val)
+    {
+        java.util.Arrays.fill(a, fromIndex, toIndex, val);
+    }
+
+    public static void fill(byte[] a, byte val)
+    {
+        java.util.Arrays.fill(a, val);
+    }
+
+    public static void fill(byte[] a, int fromIndex, int toIndex, byte val)
+    {
+        java.util.Arrays.fill(a, fromIndex, toIndex, val);
+    }
+
+    public static void fill(char[] a, char val)
+    {
+        java.util.Arrays.fill(a, val);
+    }
+
+    public static void fill(char[] a, int fromIndex, int toIndex, char val)
+    {
+        java.util.Arrays.fill(a, fromIndex, toIndex, val);
+    }
+
+    public static void fill(int[] a, int val)
+    {
+        java.util.Arrays.fill(a, val);
+    }
+
+    public static void fill(int[] a, int fromIndex, int toIndex, int val)
+    {
+        java.util.Arrays.fill(a, fromIndex, toIndex, val);
+    }
+
+    public static void fill(long[] a, long val)
+    {
+        java.util.Arrays.fill(a, val);
+    }
+
+    public static void fill(long[] a, int fromIndex, int toIndex, long val)
+    {
+        java.util.Arrays.fill(a, fromIndex, toIndex, val);
+    }
+
+    public static void fill(Object[] a, Object val)
+    {
+        java.util.Arrays.fill(a, val);
+    }
+
+    public static void fill(Object[] a, int fromIndex, int toIndex, Object val)
+    {
+        java.util.Arrays.fill(a, fromIndex, toIndex, val);
+    }
+
+    public static void fill(short[] a, short val)
+    {
+        java.util.Arrays.fill(a, val);
+    }
+
+    public static void fill(short[] a, int fromIndex, int toIndex, short val)
+    {
+        java.util.Arrays.fill(a, fromIndex, toIndex, val);
+    }
+
+    public static int hashCode(byte[] data)
+    {
+        if (data == null)
+        {
+            return 0;
+        }
+
+        int i = data.length;
+        int hc = i + 1;
+
+        while (--i >= 0)
+        {
+            hc *= 257;
+            hc ^= data[i];
+        }
+
+        return hc;
+    }
+
+    public static int hashCode(byte[] data, int off, int len)
+    {
+        if (data == null)
+        {
+            return 0;
+        }
+
+        int i = len;
+        int hc = i + 1;
+
+        while (--i >= 0)
+        {
+            hc *= 257;
+            hc ^= data[off + i];
+        }
+
+        return hc;
+    }
+
+    public static int hashCode(char[] data)
+    {
+        if (data == null)
+        {
+            return 0;
+        }
+
+        int i = data.length;
+        int hc = i + 1;
+
+        while (--i >= 0)
+        {
+            hc *= 257;
+            hc ^= data[i];
+        }
+
+        return hc;
+    }
+
+    public static int hashCode(int[][] ints)
+    {
+        int hc = 0;
+
+        for (int i = 0; i != ints.length; i++)
+        {
+            hc = hc * 257 + hashCode(ints[i]);
+        }
+
+        return hc;
+    }
+
+    public static int hashCode(int[] data)
+    {
+        if (data == null)
+        {
+            return 0;
+        }
+
+        int i = data.length;
+        int hc = i + 1;
+
+        while (--i >= 0)
+        {
+            hc *= 257;
+            hc ^= data[i];
+        }
+
+        return hc;
+    }
+
+    public static int hashCode(int[] data, int off, int len)
+    {
+        if (data == null)
+        {
+            return 0;
+        }
+
+        int i = len;
+        int hc = i + 1;
+
+        while (--i >= 0)
+        {
+            hc *= 257;
+            hc ^= data[off + i];
+        }
+
+        return hc;
+    }
+
+    public static int hashCode(long[] data)
+    {
+        if (data == null)
+        {
+            return 0;
+        }
+
+        int i = data.length;
+        int hc = i + 1;
+
+        while (--i >= 0)
+        {
+            long di = data[i];
+            hc *= 257;
+            hc ^= (int)di;
+            hc *= 257;
+            hc ^= (int)(di >>> 32);
+        }
+
+        return hc;
+    }
+
+    public static int hashCode(long[] data, int off, int len)
+    {
+        if (data == null)
+        {
+            return 0;
+        }
+
+        int i = len;
+        int hc = i + 1;
+
+        while (--i >= 0)
+        {
+            long di = data[off + i];
+            hc *= 257;
+            hc ^= (int)di;
+            hc *= 257;
+            hc ^= (int)(di >>> 32);
+        }
+
+        return hc;
+    }
+
+    public static int hashCode(short[][][] shorts)
+    {
+        int hc = 0;
+
+        for (int i = 0; i != shorts.length; i++)
+        {
+            hc = hc * 257 + hashCode(shorts[i]);
+        }
+
+        return hc;
+    }
+
+    public static int hashCode(short[][] shorts)
+    {
+        int hc = 0;
+
+        for (int i = 0; i != shorts.length; i++)
+        {
+            hc = hc * 257 + hashCode(shorts[i]);
+        }
+
+        return hc;
+    }
+
+    public static int hashCode(short[] data)
+    {
+        if (data == null)
+        {
+            return 0;
+        }
+
+        int i = data.length;
+        int hc = i + 1;
+
+        while (--i >= 0)
+        {
+            hc *= 257;
+            hc ^= (data[i] & 0xff);
+        }
+
+        return hc;
+    }
+
+    public static int hashCode(Object[] data)
+    {
+        if (data == null)
+        {
+            return 0;
+        }
+
+        int i = data.length;
+        int hc = i + 1;
+
+        while (--i >= 0)
+        {
+            hc *= 257;
+            hc ^= Objects.hashCode(data[i]);
+        }
+
+        return hc;
+    }
+
+    public static boolean[] clone(boolean[] data)
+    {
+        return null == data ? null : data.clone();
+    }
+
+    public static byte[] clone(byte[] data)
+    {
+        return null == data ? null : data.clone();
+    }
+
+    public static char[] clone(char[] data)
+    {
+        return null == data ? null : data.clone();
+    }
+
+    public static int[] clone(int[] data)
+    {
+        return null == data ? null : data.clone();
+    }
+
+    public static long[] clone(long[] data)
+    {
+        return null == data ? null : data.clone();
+    }
+
+    public static short[] clone(short[] data)
+    {
+        return null == data ? null : data.clone();
+    }
+
+    public static BigInteger[] clone(BigInteger[] data)
+    {
+        return null == data ? null : data.clone();
+    }
+
+    public static byte[] clone(byte[] data, byte[] existing)
+    {
+        if (data == null)
+        {
+            return null;
+        }
+        if ((existing == null) || (existing.length != data.length))
+        {
+            return clone(data);
+        }
+        System.arraycopy(data, 0, existing, 0, existing.length);
+        return existing;
+    }
+
+    public static long[] clone(long[] data, long[] existing)
+    {
+        if (data == null)
+        {
+            return null;
+        }
+        if ((existing == null) || (existing.length != data.length))
+        {
+            return clone(data);
+        }
+        System.arraycopy(data, 0, existing, 0, existing.length);
+        return existing;
+    }
+
+    public static byte[][] clone(byte[][] data)
+    {
+        if (data == null)
+        {
+            return null;
+        }
+
+        byte[][] copy = new byte[data.length][];
+
+        for (int i = 0; i != copy.length; i++)
+        {
+            copy[i] = clone(data[i]);
+        }
+
+        return copy;
+    }
+
+    public static byte[][][] clone(byte[][][] data)
+    {
+        if (data == null)
+        {
+            return null;
+        }
+
+        byte[][][] copy = new byte[data.length][][];
+
+        for (int i = 0; i != copy.length; i++)
+        {
+            copy[i] = clone(data[i]);
+        }
+
+        return copy;
+    }
+
+    public static boolean[] copyOf(boolean[] original, int newLength)
+    {
+        boolean[] copy = new boolean[newLength];
+        System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength));
+        return copy;
+    }
+
+    public static byte[] copyOf(byte[] original, int newLength)
+    {
+        byte[] copy = new byte[newLength];
+        System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength));
+        return copy;
+    }
+
+    public static char[] copyOf(char[] original, int newLength)
+    {
+        char[] copy = new char[newLength];
+        System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength));
+        return copy;
+    }
+
+    public static int[] copyOf(int[] original, int newLength)
+    {
+        int[] copy = new int[newLength];
+        System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength));
+        return copy;
+    }
+
+    public static long[] copyOf(long[] original, int newLength)
+    {
+        long[] copy = new long[newLength];
+        System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength));
+        return copy;
+    }
+
+    public static short[] copyOf(short[] original, int newLength)
+    {
+        short[] copy = new short[newLength];
+        System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength));
+        return copy;
+    }
+
+    public static BigInteger[] copyOf(BigInteger[] original, int newLength)
+    {
+        BigInteger[] copy = new BigInteger[newLength];
+        System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength));
+        return copy;
+    }
+
+    public static boolean[] copyOfRange(boolean[] original, int from, int to)
+    {
+        int newLength = getLength(from, to);
+        boolean[] copy = new boolean[newLength];
+        System.arraycopy(original, from, copy, 0, Math.min(original.length - from, newLength));
+        return copy;
+    }
+
+    /**
+     * Make a copy of a range of bytes from the passed in array. The range can extend beyond the end
+     * of the input array, in which case the returned array will be padded with zeroes.
+     *
+     * @param original
+     *            the array from which the data is to be copied.
+     * @param from
+     *            the start index at which the copying should take place.
+     * @param to
+     *            the final index of the range (exclusive).
+     *
+     * @return a new byte array containing the range given.
+     */
+    public static byte[] copyOfRange(byte[] original, int from, int to)
+    {
+        int newLength = getLength(from, to);
+        byte[] copy = new byte[newLength];
+        System.arraycopy(original, from, copy, 0, Math.min(original.length - from, newLength));
+        return copy;
+    }
+
+    public static char[] copyOfRange(char[] original, int from, int to)
+    {
+        int newLength = getLength(from, to);
+        char[] copy = new char[newLength];
+        System.arraycopy(original, from, copy, 0, Math.min(original.length - from, newLength));
+        return copy;
+    }
+
+    public static int[] copyOfRange(int[] original, int from, int to)
+    {
+        int newLength = getLength(from, to);
+        int[] copy = new int[newLength];
+        System.arraycopy(original, from, copy, 0, Math.min(original.length - from, newLength));
+        return copy;
+    }
+
+    public static long[] copyOfRange(long[] original, int from, int to)
+    {
+        int newLength = getLength(from, to);
+        long[] copy = new long[newLength];
+        System.arraycopy(original, from, copy, 0, Math.min(original.length - from, newLength));
+        return copy;
+    }
+
+    public static short[] copyOfRange(short[] original, int from, int to)
+    {
+        int newLength = getLength(from, to);
+        short[] copy = new short[newLength];
+        System.arraycopy(original, from, copy, 0, Math.min(original.length - from, newLength));
+        return copy;
+    }
+
+    public static BigInteger[] copyOfRange(BigInteger[] original, int from, int to)
+    {
+        int newLength = getLength(from, to);
+        BigInteger[] copy = new BigInteger[newLength];
+        System.arraycopy(original, from, copy, 0, Math.min(original.length - from, newLength));
+        return copy;
+    }
+
+    private static int getLength(int from, int to)
+    {
+        int newLength = to - from;
+        if (newLength < 0)
+        {
+            StringBuffer sb = new StringBuffer(from);
+            sb.append(" > ").append(to);
+            throw new IllegalArgumentException(sb.toString());
+        }
+        return newLength;
+    }
+
+    public static byte[] append(byte[] a, byte b)
+    {
+        if (a == null)
+        {
+            return new byte[]{ b };
+        }
+
+        int length = a.length;
+        byte[] result = new byte[length + 1];
+        System.arraycopy(a, 0, result, 0, length);
+        result[length] = b;
+        return result;
+    }
+
+    public static short[] append(short[] a, short b)
+    {
+        if (a == null)
+        {
+            return new short[]{ b };
+        }
+
+        int length = a.length;
+        short[] result = new short[length + 1];
+        System.arraycopy(a, 0, result, 0, length);
+        result[length] = b;
+        return result;
+    }
+
+    public static int[] append(int[] a, int b)
+    {
+        if (a == null)
+        {
+            return new int[]{ b };
+        }
+
+        int length = a.length;
+        int[] result = new int[length + 1];
+        System.arraycopy(a, 0, result, 0, length);
+        result[length] = b;
+        return result;
+    }
+
+    public static String[] append(String[] a, String b)
+    {
+        if (a == null)
+        {
+            return new String[]{ b };
+        }
+
+        int length = a.length;
+        String[] result = new String[length + 1];
+        System.arraycopy(a, 0, result, 0, length);
+        result[length] = b;
+        return result;
+    }
+
+    public static byte[] concatenate(byte[] a, byte[] b)
+    {
+        if (null == a)
+        {
+            // b might also be null
+            return clone(b);
+        }
+        if (null == b)
+        {
+            // a might also be null
+            return clone(a);
+        }
+
+        byte[] r = new byte[a.length + b.length];
+        System.arraycopy(a, 0, r, 0, a.length);
+        System.arraycopy(b, 0, r, a.length, b.length);
+        return r;
+    }
+
+    public static short[] concatenate(short[] a, short[] b)
+    {
+        if (null == a)
+        {
+            // b might also be null
+            return clone(b);
+        }
+        if (null == b)
+        {
+            // a might also be null
+            return clone(a);
+        }
+
+        short[] r = new short[a.length + b.length];
+        System.arraycopy(a, 0, r, 0, a.length);
+        System.arraycopy(b, 0, r, a.length, b.length);
+        return r;
+    }
+
+    public static byte[] concatenate(byte[] a, byte[] b, byte[] c)
+    {
+        if (null == a)
+        {
+            return concatenate(b, c);
+        }
+        if (null == b)
+        {
+            return concatenate(a, c);
+        }
+        if (null == c)
+        {
+            return concatenate(a, b);
+        }
+
+        byte[] r = new byte[a.length + b.length + c.length];
+        int pos = 0;
+        System.arraycopy(a, 0, r, pos, a.length);       pos += a.length;
+        System.arraycopy(b, 0, r, pos, b.length);       pos += b.length;
+        System.arraycopy(c, 0, r, pos, c.length);
+        return r;
+    }
+
+    public static byte[] concatenate(byte[] a, byte[] b, byte[] c, byte[] d)
+    {
+        if (null == a)
+        {
+            return concatenate(b, c, d);
+        }
+        if (null == b)
+        {
+            return concatenate(a, c, d);
+        }
+        if (null == c)
+        {
+            return concatenate(a, b, d);
+        }
+        if (null == d)
+        {
+            return concatenate(a, b, c);
+        }
+
+        byte[] r = new byte[a.length + b.length + c.length + d.length];
+        int pos = 0;
+        System.arraycopy(a, 0, r, pos, a.length);       pos += a.length;
+        System.arraycopy(b, 0, r, pos, b.length);       pos += b.length;
+        System.arraycopy(c, 0, r, pos, c.length);       pos += c.length;
+        System.arraycopy(d, 0, r, pos, d.length);
+        return r;
+    }
+
+    public static byte[] concatenate(byte[][] arrays)
+    {
+        int size = 0;
+        for (int i = 0; i != arrays.length; i++)
+        {
+            size += arrays[i].length;
+        }
+
+        byte[] rv = new byte[size];
+
+        int offSet = 0;
+        for (int i = 0; i != arrays.length; i++)
+        {
+            System.arraycopy(arrays[i], 0, rv, offSet, arrays[i].length);
+            offSet += arrays[i].length;
+        }
+
+        return rv;
+    }
+
+    public static int[] concatenate(int[] a, int[] b)
+    {
+        if (null == a)
+        {
+            // b might also be null
+            return clone(b);
+        }
+        if (null == b)
+        {
+            // a might also be null
+            return clone(a);
+        }
+
+        int[] r = new int[a.length + b.length];
+        System.arraycopy(a, 0, r, 0, a.length);
+        System.arraycopy(b, 0, r, a.length, b.length);
+        return r;
+    }
+
+    public static byte[] prepend(byte[] a, byte b)
+    {
+        if (a == null)
+        {
+            return new byte[]{ b };
+        }
+
+        int length = a.length;
+        byte[] result = new byte[length + 1];
+        System.arraycopy(a, 0, result, 1, length);
+        result[0] = b;
+        return result;
+    }
+
+    public static short[] prepend(short[] a, short b)
+    {
+        if (a == null)
+        {
+            return new short[]{ b };
+        }
+
+        int length = a.length;
+        short[] result = new short[length + 1];
+        System.arraycopy(a, 0, result, 1, length);
+        result[0] = b;
+        return result;
+    }
+
+    public static int[] prepend(int[] a, int b)
+    {
+        if (a == null)
+        {
+            return new int[]{ b };
+        }
+
+        int length = a.length;
+        int[] result = new int[length + 1];
+        System.arraycopy(a, 0, result, 1, length);
+        result[0] = b;
+        return result;
+    }
+
+    public static byte[] reverse(byte[] a)
+    {
+        if (a == null)
+        {
+            return null;
+        }
+
+        int p1 = 0, p2 = a.length;
+        byte[] result = new byte[p2];
+
+        while (--p2 >= 0)
+        {
+            result[p2] = a[p1++];
+        }
+
+        return result;
+    }
+
+    public static int[] reverse(int[] a)
+    {
+        if (a == null)
+        {
+            return null;
+        }
+
+        int p1 = 0, p2 = a.length;
+        int[] result = new int[p2];
+
+        while (--p2 >= 0)
+        {
+            result[p2] = a[p1++];
+        }
+
+        return result;
+    }
+
+    public static void reverse(byte[] input, byte[] output)
+    {
+        int last = input.length - 1;
+        for (int i = 0; i <= last; ++i)
+        {
+            output[i] = input[last - i];
+        }
+    }
+
+    public static byte[] reverseInPlace(byte[] a)
+    {
+        if (null == a)
+        {
+            return null;
+        }
+
+        int p1 = 0, p2 = a.length - 1;
+        while (p1 < p2)
+        {
+            byte t1 = a[p1], t2 = a[p2];
+            a[p1++] = t2;
+            a[p2--] = t1;
+        }
+
+        return a;
+    }
+
+    public static void reverseInPlace(byte[] a, int aOff, int aLen)
+    {
+        int p1 = aOff, p2 = aOff + aLen - 1;
+        while (p1 < p2)
+        {
+            byte t1 = a[p1], t2 = a[p2];
+            a[p1++] = t2;
+            a[p2--] = t1;
+        }
+    }
+
+    public static int[] reverseInPlace(int[] a)
+    {
+        if (null == a)
+        {
+            return null;
+        }
+
+        int p1 = 0, p2 = a.length - 1;
+        while (p1 < p2)
+        {
+            int t1 = a[p1], t2 = a[p2];
+            a[p1++] = t2;
+            a[p2--] = t1;
+        }
+
+        return a;
+    }
+
+    /**
+     * Iterator backed by a specific array.
+     */
+    public static class Iterator<T>
+            implements java.util.Iterator<T>
+    {
+        private final T[] dataArray;
+
+        private int position = 0;
+
+        /**
+         * Base constructor.
+         * <p>
+         * Note: the array is not cloned, changes to it will affect the values returned by next().
+         * </p>
+         *
+         * @param dataArray array backing the iterator.
+         */
+        public Iterator(T[] dataArray)
+        {
+            this.dataArray = dataArray;
+        }
+
+        public boolean hasNext()
+        {
+            return position < dataArray.length;
+        }
+
+        public T next()
+        {
+            if (position == dataArray.length)
+            {
+                throw new NoSuchElementException("Out of elements: " + position);
+            }
+
+            return dataArray[position++];
+        }
+
+        public void remove()
+        {
+            throw new UnsupportedOperationException("Cannot remove element from an Array.");
+        }
+    }
+
+    /**
+     * Fill input array by zeros
+     *
+     * @param data input array
+     */
+    public static void clear(byte[] data)
+    {
+        if (null != data)
+        {
+            java.util.Arrays.fill(data, (byte)0x00);
+        }
+    }
+
+    public static void clear(int[] data)
+    {
+        if (null != data)
+        {
+            java.util.Arrays.fill(data, 0);
+        }
+    }
+
+    public static boolean isNullOrContainsNull(Object[] array)
+    {
+        if (null == array)
+        {
+            return true;
+        }
+        int count = array.length;
+        for (int i = 0; i < count; ++i)
+        {
+            if (null == array[i])
+            {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public static boolean isNullOrEmpty(byte[] array)
+    {
+        return null == array || array.length < 1;
+    }
+
+    public static boolean isNullOrEmpty(int[] array)
+    {
+        return null == array || array.length < 1;
+    }
+
+    public static boolean isNullOrEmpty(Object[] array)
+    {
+        return null == array || array.length < 1;
+    }
+}
diff --git a/app/src/main/java/org/bouncycastle/shaded/util/Objects.java b/app/src/main/java/org/bouncycastle/shaded/util/Objects.java
new file mode 100644
index 0000000..65a09e7
--- /dev/null
+++ b/app/src/main/java/org/bouncycastle/shaded/util/Objects.java
@@ -0,0 +1,14 @@
+package org.bouncycastle.shaded.util;
+
+public class Objects
+{
+    public static boolean areEqual(Object a, Object b)
+    {
+        return a == b || (null != a && null != b && a.equals(b));
+    }
+
+    public static int hashCode(Object obj)
+    {
+        return null == obj ? 0 : obj.hashCode();
+    }
+}
diff --git a/app/src/main/java/org/bouncycastle/shaded/util/Pack.java b/app/src/main/java/org/bouncycastle/shaded/util/Pack.java
new file mode 100644
index 0000000..328aca8
--- /dev/null
+++ b/app/src/main/java/org/bouncycastle/shaded/util/Pack.java
@@ -0,0 +1,40 @@
+package org.bouncycastle.shaded.util;
+
+/**
+ * Utility methods for converting byte arrays into ints and longs, and back again.
+ */
+public abstract class Pack
+{
+    public static int bigEndianToInt(byte[] bs, int off)
+    {
+        int n = bs[  off] << 24;
+        n |= (bs[++off] & 0xff) << 16;
+        n |= (bs[++off] & 0xff) << 8;
+        n |= (bs[++off] & 0xff);
+        return n;
+    }
+
+    public static long bigEndianToLong(byte[] bs, int off)
+    {
+        int hi = bigEndianToInt(bs, off);
+        int lo = bigEndianToInt(bs, off + 4);
+        return ((long)(hi & 0xffffffffL) << 32) | (long)(lo & 0xffffffffL);
+    }
+
+    public static int littleEndianToInt(byte[] bs, int off)
+    {
+        int n = bs[off] & 0xff;
+        n |= (bs[++off] & 0xff) << 8;
+        n |= (bs[++off] & 0xff) << 16;
+        n |= bs[++off] << 24;
+        return n;
+    }
+
+    public static void intToLittleEndian(int n, byte[] bs, int off)
+    {
+        bs[off] = (byte)(n);
+        bs[++off] = (byte)(n >>> 8);
+        bs[++off] = (byte)(n >>> 16);
+        bs[++off] = (byte)(n >>> 24);
+    }
+}
diff --git a/app/src/main/java/org/bouncycastle/shaded/util/Strings.java b/app/src/main/java/org/bouncycastle/shaded/util/Strings.java
new file mode 100644
index 0000000..27a001f
--- /dev/null
+++ b/app/src/main/java/org/bouncycastle/shaded/util/Strings.java
@@ -0,0 +1,38 @@
+package org.bouncycastle.shaded.util;
+
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+/* loaded from: classes.dex */
+public final class Strings {
+    private static String LINE_SEPARATOR;
+
+    static {
+        try {
+            LINE_SEPARATOR = (String) AccessController.doPrivileged(new PrivilegedAction<String>() { // from class: org.bouncycastle.util.Strings.1
+                @Override // java.security.PrivilegedAction
+                public String run() {
+                    return System.getProperty("line.separator");
+                }
+            });
+        } catch (Exception e) {
+            try {
+                LINE_SEPARATOR = String.format("%n", new Object[0]);
+            } catch (Exception e2) {
+                LINE_SEPARATOR = "\n";
+            }
+        }
+    }
+
+    public static String toLowerCase(String str) {
+        char[] charArray = str.toCharArray();
+        boolean z = false;
+        for (int i = 0; i != charArray.length; i++) {
+            char c = charArray[i];
+            if ('A' <= c && 'Z' >= c) {
+                charArray[i] = (char) ((c - 'A') + 97);
+                z = true;
+            }
+        }
+        return z ? new String(charArray) : str;
+    }
+}
diff --git a/app/src/main/java/ru/gelin/android/weather/notification/ParcelableWeather2.java b/app/src/main/java/ru/gelin/android/weather/notification/ParcelableWeather2.java
new file mode 100644
index 0000000..b7d0963
--- /dev/null
+++ b/app/src/main/java/ru/gelin/android/weather/notification/ParcelableWeather2.java
@@ -0,0 +1,291 @@
+/*  Copyright (C) 2015-2024 Andreas Shimokawa, Carsten Pfeiffer, Daniele
+    Gobbetti, Jos茅 Rebelo, Petr Van臎k, Taavi Eom盲e
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package ru.gelin.android.weather.notification;
+
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import nodomain.freeyourgadget.gadgetbridge.model.Weather;
+import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
+
+public class ParcelableWeather2 implements Parcelable {
+    private static final Logger LOG = LoggerFactory.getLogger(ParcelableWeather2.class);
+
+    // getters and setters suck ;)
+    public WeatherSpec weatherSpec = new WeatherSpec();
+
+    public JSONObject reconstructedOWMForecast = null;
+
+    private ParcelableWeather2(Parcel in) {
+        int version = in.readInt();
+        if (version != 2) {
+            return;
+        }
+        Bundle bundle = in.readBundle(getClass().getClassLoader());
+
+        weatherSpec.location = bundle.getString("weather_location");
+        long time = bundle.getLong("weather_time");
+        long queryTime = bundle.getLong("weather_query_time");
+        weatherSpec.timestamp = (int) (queryTime / 1000);
+        bundle.getString("weather_forecast_url");
+        int conditions = bundle.getInt("weather_conditions");
+        if (conditions > 0) {
+            Bundle conditionBundle = in.readBundle(getClass().getClassLoader());
+            weatherSpec.currentCondition = conditionBundle.getString("weather_condition_text");
+            conditionBundle.getStringArray("weather_condition_types");
+            weatherSpec.currentTemp = conditionBundle.getInt("weather_current_temp");
+
+            weatherSpec.windDirection = mapDirToDeg(conditionBundle.getString("weather_wind_direction"));
+            weatherSpec.windSpeed = getSpeedInKMH(conditionBundle.getInt("weather_wind_speed"),
+                    conditionBundle.getString("weather_wind_speed_unit"));
+
+            String[] currentConditionType = conditionBundle.getStringArray("weather_condition_types");
+            if (currentConditionType != null) {
+                weatherSpec.currentConditionCode = weatherConditionTypesToOpenWeatherMapIds(currentConditionType[0]);
+            }
+            weatherSpec.todayMinTemp = conditionBundle.getInt("weather_low_temp");
+            weatherSpec.todayMaxTemp = conditionBundle.getInt("weather_high_temp");
+            weatherSpec.currentHumidity = conditionBundle.getInt("weather_humidity_value");
+
+            //fetch forecasts
+            int timeOffset = 0;
+
+            JSONArray list = new JSONArray();
+            JSONObject city = new JSONObject();
+            while (--conditions > 0) {
+                timeOffset += 86400; //manually determined
+                JSONObject item = new JSONObject();
+                JSONObject condition = new JSONObject();
+                JSONObject main = new JSONObject();
+                JSONArray weather = new JSONArray();
+                Bundle forecastBundle = in.readBundle(getClass().getClassLoader());
+                String[] forecastConditionType = forecastBundle.getStringArray("weather_condition_types");
+                int forecastConditionCode = 0;
+                if (forecastConditionType != null && forecastConditionType.length > 0) {
+                    forecastConditionCode = weatherConditionTypesToOpenWeatherMapIds(forecastConditionType[0]);
+                }
+                int forecastLowTemp = forecastBundle.getInt("weather_low_temp");
+                int forecastHighTemp = forecastBundle.getInt("weather_high_temp");
+                int forecastHumidity = forecastBundle.getInt("weather_humidity_value");
+                WeatherSpec.Daily daily = new WeatherSpec.Daily();
+                daily.minTemp = forecastLowTemp;
+                daily.maxTemp = forecastHighTemp;
+                daily.conditionCode = forecastConditionCode;
+                daily.humidity = forecastHumidity;
+                weatherSpec.forecasts.add(daily);
+                try {
+                    condition.put("id", forecastConditionCode);
+                    condition.put("main", forecastBundle.getString("weather_condition_text"));
+                    condition.put("description", forecastBundle.getString("weather_condition_text"));
+                    condition.put("icon", Weather.mapToOpenWeatherMapIcon(forecastConditionCode));
+                    weather.put(condition);
+
+                    main.put("temp", forecastBundle.getInt("weather_current_temp"));
+                    main.put("humidity", forecastHumidity);
+                    main.put("temp_min", forecastLowTemp);
+                    main.put("temp_max", forecastHighTemp);
+
+                    //forecast
+
+                    item.put("dt", (time / 1000) + timeOffset);
+                    item.put("main", main);
+                    item.put("weather", weather);
+                    list.put(item);
+                } catch (JSONException e) {
+                    LOG.error("error while construction JSON", e);
+                }
+            }
+            try {
+                //"city":{"id":3181913,"name":"Bolzano","coord":{"lat":46.4927,"lon":11.3336},"country":"IT"}
+                city.put("name", weatherSpec.location);
+                city.put("country", "World");
+
+                reconstructedOWMForecast = new JSONObject();
+                reconstructedOWMForecast.put("city", city);
+                reconstructedOWMForecast.put("cnt", list.length());
+                reconstructedOWMForecast.put("list", list);
+
+            } catch (JSONException e) {
+                LOG.error("error while construction JSON", e);
+            }
+            LOG.debug("Forecast JSON for Webview: " + reconstructedOWMForecast);
+        }
+    }
+
+    public static final Creator<ParcelableWeather2> CREATOR = new Creator<ParcelableWeather2>() {
+        @Override
+        public ParcelableWeather2 createFromParcel(Parcel in) {
+            return new ParcelableWeather2(in);
+        }
+
+        @Override
+        public ParcelableWeather2[] newArray(int size) {
+            return new ParcelableWeather2[size];
+        }
+    };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        // we do not really want to use this at all
+    }
+
+    private int weatherConditionTypesToOpenWeatherMapIds(String weather_condition_type) {
+        switch (weather_condition_type) {
+            case "THUNDERSTORM_RAIN_LIGHT":
+                return 200;
+            case "THUNDERSTORM_RAIN":
+                return 201;
+            case "THUNDERSTORM_RAIN_HEAVY":
+                return 202;
+            case "THUNDERSTORM_LIGHT":
+                return 210;
+            case "THUNDERSTORM":
+                return 211;
+            case "THUNDERSTORM_HEAVY":
+                return 212;
+            case "THUNDERSTORM_RAGGED":
+                return 221;
+            case "THUNDERSTORM_DRIZZLE_LIGHT":
+                return 230;
+            case "THUNDERSTORM_DRIZZLE":
+                return 231;
+            case "THUNDERSTORM_DRIZZLE_HEAVY":
+                return 232;
+
+            case "DRIZZLE_LIGHT":
+                return 300;
+            case "DRIZZLE":
+                return 301;
+            case "DRIZZLE_HEAVY":
+                return 302;
+            case "DRIZZLE_RAIN_LIGHT":
+                return 310;
+            case "DRIZZLE_RAIN":
+                return 311;
+            case "DRIZZLE_RAIN_HEAVY":
+                return 312;
+            case "DRIZZLE_SHOWER":
+                return 321;
+
+            case "RAIN_LIGHT":
+                return 500;
+            case "RAIN":
+                return 501;
+            case "RAIN_HEAVY":
+                return 502;
+            case "RAIN_VERY_HEAVY":
+                return 503;
+            case "RAIN_EXTREME":
+                return 504;
+            case "RAIN_FREEZING":
+                return 511;
+            case "RAIN_SHOWER_LIGHT":
+                return 520;
+            case "RAIN_SHOWER":
+                return 521;
+            case "RAIN_SHOWER_HEAVY":
+                return 522;
+
+            case "SNOW_LIGHT":
+                return 600;
+            case "SNOW":
+                return 601;
+            case "SNOW_HEAVY":
+                return 602;
+            case "SLEET":
+                return 611;
+            case "SNOW_SHOWER":
+                return 621;
+
+            case "MIST":
+                return 701;
+            case "SMOKE":
+                return 711;
+            case "HAZE":
+                return 721;
+            case "SAND_WHIRLS":
+                return 731;
+            case "FOG":
+                return 741;
+
+            case "CLOUDS_CLEAR":
+                return 800;
+            case "CLOUDS_FEW":
+                return 801;
+            case "CLOUDS_SCATTERED":
+                return 802;
+            case "CLOUDS_BROKEN":
+                return 803;
+            case "CLOUDS_OVERCAST":
+                return 804;
+
+            case "TORNADO":
+                return 900;
+            case "TROPICAL_STORM":
+                return 901;
+            case "HURRICANE":
+                return 902;
+            case "COLD":
+                return 903;
+            case "HOT":
+                return 904;
+            case "WINDY":
+                return 905;
+            case "HAIL":
+                return 906;
+        }
+        return 3200;
+    }
+
+    private int getSpeedInKMH(int speed, String incomingUnit) {
+        float kmhSpeed = 0;
+        switch (incomingUnit) {
+            case "MPS":
+                kmhSpeed = speed * 3.6f;
+                break;
+            case "MPH":
+                kmhSpeed = speed * 1.6093f;
+                break;
+            case "KPH":
+                kmhSpeed = speed;
+                break;
+        }
+        return Math.round(kmhSpeed);
+    }
+
+    private int mapDirToDeg(String dir) {
+        return Math.round(WindDirection.valueOf(dir).ordinal() * 22.5f);
+    }
+
+    private enum WindDirection { // see upstream code, we can't be more precise than getting the quadrant
+        N, NNE, NE, ENE, E, ESE, SE, SSE, S, SSW, SW, WSW, W, WNW, NW, NNW
+    }
+
+}
\ No newline at end of file
diff --git a/app/src/main/lint.xml b/app/src/main/lint.xml
new file mode 100644
index 0000000..3cff4a1
--- /dev/null
+++ b/app/src/main/lint.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<lint>
+    <!-- Ignore missing and extra translations - they are fetched by weblate -->
+    <issue id="ExtraTranslation" severity="ignore" />
+    <issue id="MissingTranslation" severity="ignore" />
+</lint>
diff --git a/app/src/main/project.properties b/app/src/main/project.properties
new file mode 100644
index 0000000..7ad4d39
--- /dev/null
+++ b/app/src/main/project.properties
@@ -0,0 +1,15 @@
+# This file is automatically generated by Android Tools.
+# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
+#
+# This file must be checked in Version Control Systems.
+#
+# To customize properties used by the Ant build system edit
+# "ant.properties", and override values to adapt the script to your
+# project structure.
+#
+# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
+#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
+
+# Project target.
+target=android-21
+android.library.reference.1=../../../../android-sdk/extras/android/support/v7/appcompat
diff --git a/app/src/main/proto/garmin/garmin_contacts.proto b/app/src/main/proto/garmin/garmin_contacts.proto
new file mode 100644
index 0000000..6f1e79a
--- /dev/null
+++ b/app/src/main/proto/garmin/garmin_contacts.proto
@@ -0,0 +1,46 @@
+syntax = "proto2";
+
+package garmin;
+
+option java_package = "nodomain.freeyourgadget.gadgetbridge.proto.garmin";
+option java_outer_classname = "GarminContacts";
+
+message Response {
+  repeated Contact contact = 1;
+  optional Contact self = 2;
+}
+
+message Contact {
+  optional string id = 1; // "SELF" on self
+  optional string fullName = 2;
+  optional string firstName = 3;
+  optional string lastName = 4;
+  repeated Phone phone = 5;
+  optional string unk7 = 7; // ""
+  optional uint32 unk8 = 8; // 0
+  optional uint32 unk9 = 9; // 0
+  optional uint32 unk10 = 10; // 0
+  optional uint64 updateTime = 11;
+  optional uint32 unk12 = 12; // 0
+  optional uint32 unk21 = 21; // 0
+}
+
+message Phone {
+  optional string number = 1;
+  optional uint32 unk2 = 2; // 1
+  optional uint32 unk3 = 3; // 1
+  optional uint32 unk4 = 4; // 0
+  optional uint32 unk5 = 5; // random number
+  optional uint32 unk6 = 6; // 0
+  optional uint32 unk7 = 7; // 0
+  optional string unk8 = 8; // ""
+  optional uint32 unk9 = 9; // 1
+  optional string id = 10; // uuid
+  optional uint32 unk11 = 11; // 1
+  repeated Unk12 unk12 = 12; // 1/5, 2/5
+}
+
+message Unk12 {
+  optional uint32 unk1 = 1; // 1/2
+  optional uint32 unk2 = 2; // 5
+}
diff --git a/app/src/main/proto/garmin/gdi_calendar_service.proto b/app/src/main/proto/garmin/gdi_calendar_service.proto
new file mode 100644
index 0000000..24eb497
--- /dev/null
+++ b/app/src/main/proto/garmin/gdi_calendar_service.proto
@@ -0,0 +1,48 @@
+syntax = "proto2";
+
+package garmin_vivomovehr;
+
+option java_package = "nodomain.freeyourgadget.gadgetbridge.proto.garmin";
+
+message CalendarService {
+  optional CalendarServiceRequest calendar_request = 1;
+  optional CalendarServiceResponse calendar_response = 2;
+
+  message CalendarServiceRequest {
+    optional uint32 begin = 1;
+    optional uint32 end = 2;
+    optional bool include_organizer = 3 [default = false];
+    optional bool include_title = 4 [default = true];
+    optional bool include_location = 5 [default = true];
+    optional bool include_description = 6 [default = false];
+    optional bool include_start_date = 7 [default = true];
+    optional bool include_end_date = 8 [default = false];
+    optional bool include_all_day = 9 [default = false];
+    optional uint32 max_organizer_length = 10;
+    optional uint32 max_title_length = 11;
+    optional uint32 max_location_length = 12;
+    optional uint32 max_description_length = 13;
+    optional uint32 max_events = 14;
+  }
+
+  message CalendarServiceResponse {
+    enum ResponseStatus {
+      UNKNOWN_RESPONSE_STATUS = 0;
+      OK = 1;
+      INVALID_DATE_RANGE = 2;
+    }
+    optional ResponseStatus status = 1;
+    repeated CalendarEvent calendar_event = 2;
+  }
+
+  message CalendarEvent {
+    optional string organizer = 1;
+    optional string title = 2;
+    optional string location = 3 [default = ""];
+    optional string description = 4 [default = ""];
+    optional uint32 start_date = 5;
+    optional uint32 end_date = 6;
+    optional bool all_day = 7;
+    repeated uint32 reminder_time_in_secs = 8;
+  }
+}
\ No newline at end of file
diff --git a/app/src/main/proto/garmin/gdi_core.proto b/app/src/main/proto/garmin/gdi_core.proto
new file mode 100644
index 0000000..1deac9f
--- /dev/null
+++ b/app/src/main/proto/garmin/gdi_core.proto
@@ -0,0 +1,112 @@
+syntax = "proto2";
+
+package garmin_vivomovehr;
+
+option java_package = "nodomain.freeyourgadget.gadgetbridge.proto.garmin";
+
+message CoreService {
+    optional SyncRequest sync_request = 1;
+    optional SyncResponse sync_response = 2;
+    optional GetLocationRequest get_location_request = 3;
+    optional GetLocationResponse get_location_response = 4;
+    optional LocationUpdatedSetEnabledRequest location_updated_set_enabled_request = 5;
+    optional LocationUpdatedSetEnabledResponse location_updated_set_enabled_response = 6;
+    optional LocationUpdatedNotification location_updated_notification = 7;
+
+    message SyncRequest {
+    }
+
+    message SyncResponse {
+        optional ResponseStatus status = 1;
+
+        enum ResponseStatus {
+            UNKNOWN_RESPONSE_STATUS = 0;
+            OK = 1;
+        }
+    }
+
+    message GetLocationRequest {
+        optional RequestType request_type = 1;
+
+        enum RequestType {
+            STANDARD = 0;
+            EMERGENCY = 1;
+        }
+    }
+
+    message GetLocationResponse {
+        optional Status status = 1;
+        optional LocationData location_data = 2;
+
+        enum Status {
+            OK = 1;
+            NO_VALID_LOCATION = 2;
+            LOCATION_SERVICES_UNAVAILABLE = 3;
+            LOCATION_SERVICES_DISABLED = 4;
+            TRY_AGAIN_LATER = 5;
+        }
+    }
+
+    enum DataType {
+        SIGNIFICANT_LOCATION = 0;
+        GENERAL_LOCATION = 1;
+        REALTIME_TRACKING = 2;
+        INREACH_TRACKING = 3;
+        TRACKING_EVENT = 4;
+    }
+
+    message LocationUpdatedSetEnabledRequest {
+        optional bool enabled = 1;
+        repeated Request requests = 2;
+    }
+
+    message Request {
+        optional DataType requested = 1;
+        optional float min_update_threshold = 2;
+        optional float distance_threshold = 3;
+    }
+
+    message LocationUpdatedSetEnabledResponse {
+        optional Status status = 1;
+        repeated Requested requests = 2;
+
+        enum Status {
+            OK = 1;
+            UNAVAILABLE = 2;
+            UNKNOWN3 = 3;
+            UNKNOWN4 = 4;
+        }
+
+        message Requested {
+                optional DataType requested = 1;
+                optional RequestedStatus status = 2;
+
+                enum RequestedStatus {
+                    OK = 1;
+                    KO = 2;
+                }
+
+        }
+    }
+
+    message LocationUpdatedNotification {
+        repeated LocationData location_data = 1;
+    }
+
+    message LocationData {
+        required LatLon position=1;
+        required float altitude=2;
+        required uint32 timestamp=3;
+        required float h_accuracy=4;
+        required float v_accuracy=5;
+        required DataType position_type=6;
+        required float bearing=9;
+        required float speed=10;
+    }
+
+    message LatLon {
+        required sint32 lat = 1;
+        required sint32 lon = 2;
+    }
+
+}
diff --git a/app/src/main/proto/garmin/gdi_data_transfer_service.proto b/app/src/main/proto/garmin/gdi_data_transfer_service.proto
new file mode 100644
index 0000000..1cab324
--- /dev/null
+++ b/app/src/main/proto/garmin/gdi_data_transfer_service.proto
@@ -0,0 +1,30 @@
+syntax = "proto2";
+
+package garmin_vivomovehr;
+
+option java_package = "nodomain.freeyourgadget.gadgetbridge.proto.garmin";
+
+message DataTransferService {
+  enum Status {
+    UNKNOWN = 0;
+    SUCCESS = 1;
+    INVALID_ID = 2;
+    INVALID_OFFSET = 3;
+  }
+
+  optional DataDownloadRequest dataDownloadRequest = 1;
+  optional DataDownloadResponse dataDownloadResponse = 2;
+
+  message DataDownloadRequest {
+    required uint32 id = 1;
+    required uint32 offset = 2;
+    optional uint32 maxChunkSize = 3;
+  }
+
+  message DataDownloadResponse {
+    required Status status = 1;
+    required uint32 id = 2;
+    required uint32 offset = 3;
+    optional bytes payload = 4;
+  }
+}
\ No newline at end of file
diff --git a/app/src/main/proto/garmin/gdi_device_status.proto b/app/src/main/proto/garmin/gdi_device_status.proto
new file mode 100644
index 0000000..1059487
--- /dev/null
+++ b/app/src/main/proto/garmin/gdi_device_status.proto
@@ -0,0 +1,44 @@
+syntax = "proto2";
+
+package garmin_vivomovehr;
+
+option java_package = "nodomain.freeyourgadget.gadgetbridge.proto.garmin";
+
+message DeviceStatusService {
+    optional RemoteDeviceBatteryStatusChangedNotification remote_device_battery_status_changed_notification = 1;
+    optional RemoteDeviceBatteryStatusRequest remote_device_battery_status_request = 2;
+    optional RemoteDeviceBatteryStatusResponse remote_device_battery_status_response = 3;
+    optional ActivityStatusRequest activity_status_request = 4;
+    optional ActivityStatusResponse activity_status_response = 5;
+
+    message RemoteDeviceBatteryStatusChangedNotification {
+    }
+
+    message RemoteDeviceBatteryStatusRequest {
+    }
+
+    message RemoteDeviceBatteryStatusResponse {
+        optional ResponseStatus status = 1;
+        optional int32 current_battery_level = 2;
+    }
+
+    message ActivityStatusRequest {
+    }
+
+    message ActivityStatusResponse {
+        optional ActivityStatus status = 1;
+
+        enum ActivityStatus {
+            OFF = 0;
+            STOPPED = 1;
+            PAUSED = 2;
+            ON = 3;
+        }
+    }
+
+    enum ResponseStatus {
+        UNKNOWN_RESPONSE_STATUS = 0;
+        OK = 1;
+        NO_REMOTE_DEVICE = 2;
+    }
+}
diff --git a/app/src/main/proto/garmin/gdi_find_my_watch.proto b/app/src/main/proto/garmin/gdi_find_my_watch.proto
new file mode 100644
index 0000000..b6d6885
--- /dev/null
+++ b/app/src/main/proto/garmin/gdi_find_my_watch.proto
@@ -0,0 +1,33 @@
+syntax = "proto2";
+
+package garmin_vivomovehr;
+
+option java_package = "nodomain.freeyourgadget.gadgetbridge.proto.garmin";
+
+message FindMyWatchService {
+    optional FindMyWatchRequest find_request = 1;
+    optional FindMyWatchResponse find_response = 2;
+    optional FindMyWatchCancelRequest cancel_request = 3;
+    optional FindMyWatchCancelResponse cancel_response = 4;
+
+    message FindMyWatchRequest {
+        required int32 timeout = 1;
+    }
+
+    message FindMyWatchResponse {
+        optional ResponseStatus status = 1;
+    }
+
+    message FindMyWatchCancelRequest {
+    }
+
+    message FindMyWatchCancelResponse {
+        optional ResponseStatus status = 1;
+    }
+
+    enum ResponseStatus {
+        UNKNOWN_RESPONSE_STATUS = 0;
+        OK = 100;
+        ERROR = 200;
+    }
+}
diff --git a/app/src/main/proto/garmin/gdi_http_service.proto b/app/src/main/proto/garmin/gdi_http_service.proto
new file mode 100644
index 0000000..63f1b01
--- /dev/null
+++ b/app/src/main/proto/garmin/gdi_http_service.proto
@@ -0,0 +1,53 @@
+syntax = "proto2";
+
+package garmin_vivomovehr;
+
+option java_package = "nodomain.freeyourgadget.gadgetbridge.proto.garmin";
+
+message HttpService {
+  enum Method {
+    UNKNOWN_METHOD = 0;
+    GET = 1;
+    PUT = 2;
+    POST = 3;
+    DELETE = 4;
+    PATCH = 5;
+    HEAD = 6;
+  }
+
+  enum Status {
+    UNKNOWN_STATUS = 0;
+    OK = 100;
+    NETWORK_REQUEST_TIMED_OUT = 200;
+    FILE_TOO_LARGE = 300;
+    DATA_TRANSFER_ITEM_FAILURE = 400;
+  }
+
+  optional RawRequest rawRequest = 5;
+  optional RawResponse rawResponse = 6;
+
+  message RawRequest {
+    required string url = 1;
+    optional Method method = 3;
+    repeated Header header = 5;
+    optional bool useDataXfer = 6;
+  }
+
+  message RawResponse {
+    optional Status status = 1;
+    optional uint32 httpStatus = 2;
+    optional bytes body = 3;
+    optional DataTransferItem xferData = 4;
+    repeated Header header = 5;
+  }
+
+  message DataTransferItem {
+    required uint32 id = 1;
+    required uint32 size = 2;
+  }
+
+  message Header {
+    required string key = 1;
+    required string value = 2;
+  }
+}
diff --git a/app/src/main/proto/garmin/gdi_notifications_service.proto b/app/src/main/proto/garmin/gdi_notifications_service.proto
new file mode 100644
index 0000000..03235cf
--- /dev/null
+++ b/app/src/main/proto/garmin/gdi_notifications_service.proto
@@ -0,0 +1,37 @@
+syntax = "proto2";
+
+package garmin_vivomovehr;
+
+option java_package = "nodomain.freeyourgadget.gadgetbridge.proto.garmin";
+
+message NotificationsService {
+  optional PictureRequest pictureRequest = 1;
+  optional PictureResponse pictureResponse = 2;
+}
+
+message PictureRequest {
+  optional uint32 notification_id = 1;
+  optional uint32 unk2 = 2; // 0
+  optional PictureParameters parameters = 3;
+  // unk4 "\001"
+}
+
+message PictureParameters {
+  optional uint32 width = 1;
+  optional uint32 height = 2;
+  optional uint32 unk3 = 3; // 204800 max size?
+  optional uint32 quality = 4; // 80
+}
+
+message PictureResponse {
+  optional uint32 unk1 = 1; // 1
+  optional uint32 notification_id = 2;
+  optional uint32 unk3 = 3; // 0
+  optional uint32 unk4 = 4; // 1
+  optional DataTransferItem dataTransferItem = 5;
+}
+
+message DataTransferItem {
+  optional uint32 id = 1;
+  optional uint32 size = 2;
+}
diff --git a/app/src/main/proto/garmin/gdi_settings_service.proto b/app/src/main/proto/garmin/gdi_settings_service.proto
new file mode 100644
index 0000000..4ebbb08
--- /dev/null
+++ b/app/src/main/proto/garmin/gdi_settings_service.proto
@@ -0,0 +1,228 @@
+syntax = "proto2";
+
+package garmin_vivomovehr;
+
+option java_package = "nodomain.freeyourgadget.gadgetbridge.proto.garmin";
+
+message SettingsService {
+  optional ScreenDefinitionRequest definitionRequest = 1;
+  optional ScreenDefinitionResponse definitionResponse = 2;
+
+  optional ScreenStateRequest stateRequest = 3;
+  optional ScreenStateResponse stateResponse = 4;
+
+  optional ChangeRequest changeRequest = 5;
+  optional ChangeResponse changeResponse = 6;
+
+  optional InitRequest initRequest = 8;
+  optional InitResponse initResponse = 9;
+}
+
+message ScreenDefinitionRequest {
+  optional uint32 screenId = 1;
+  optional uint32 unk2 = 2; // 0
+  optional string language = 3; // en_US
+}
+
+message ScreenDefinitionResponse {
+  optional uint32 unk1 = 1; // 0, status?
+  optional ScreenDefinition definition = 2;
+}
+
+message ScreenDefinition {
+  optional uint32 screenId = 1;
+  optional uint32 unk2 = 2; // 0
+  optional uint32 unk3 = 3; // 928002
+  optional Label title = 4;
+  repeated ScreenEntry entry = 5;
+}
+
+message Label {
+  optional string id = 1;
+  optional string text = 2;
+}
+
+message ScreenEntry {
+  optional uint32 id = 1;
+  optional uint32 type = 2;
+  optional Label title = 3;
+  optional uint32 icon = 5;
+  optional Target target = 9;
+  optional SortOptions sortOptions = 12;
+  optional TextOption textOption = 13;
+}
+
+message SortOptions {
+  optional uint32 unk3 = 3; // 1
+  repeated SortEntry entries = 5;
+}
+
+message SortEntry {
+  optional uint32 id = 1;
+  optional Label title = 2;
+  optional uint32 unk5 = 5; // 1
+}
+
+message TextOption {
+  optional TextLimits limits = 1;
+  optional uint32 unk2 = 2; // 0
+}
+
+message TextLimits {
+  optional uint32 maxLength = 1;
+}
+
+message Target {
+  optional uint32 type = 1; // 0 subscreen, 1 list preference, 6 other activity, 7 hidden, 9 subscreen with options
+  optional uint32 subscreen = 2; // when 0
+  optional uint32 activity = 3; // when 6
+  optional TargetOptions options = 4; // when 1
+  optional TargetNumberPicker numberPicker = 8;
+}
+
+message ScreenStateRequest {
+  optional uint32 screenId = 1;
+}
+
+message TargetOptions {
+  repeated TargetOptionEntry option = 1;
+}
+
+message TargetOptionEntry {
+  optional Label title = 3;
+}
+
+message TargetNumberPicker {
+  optional uint32 min = 1;
+  optional uint32 max = 2;
+  optional uint32 step = 3; // maybe? 1 on weight
+}
+
+message ScreenStateResponse {
+  optional uint32 unk1 = 1; // 0
+  optional ScreenState state = 2;
+}
+
+message ScreenState {
+  optional uint32 screenId = 1;
+  optional uint32 unk2 = 2; // 0
+  optional uint32 unk3 = 3; // 928002
+  repeated EntryState state = 4;
+}
+
+message EntryState {
+  optional uint32 id = 1;
+  optional uint32 state = 2;
+  optional Switch switch = 3;
+  optional Summary summary = 4;
+}
+
+message Switch {
+  optional bool enabled = 1;
+  optional Label title = 2;
+}
+
+message Summary {
+  optional Label title = 1;
+  optional ValueList valueList = 2;
+  optional ValueTime valueTime = 4;
+  optional ValueNumber valueNumber = 6;
+  optional ValueDate valueDate = 8;
+  optional ValueHeight valueHeight = 10;
+}
+
+message ValueList {
+  optional uint32 index = 1;
+}
+
+message ValueTime {
+  optional uint32 seconds = 1;
+  optional uint32 timeFormat = 3; // 0 12h, 1 24h
+}
+
+message ValueNumber {
+  optional uint32 value = 1;
+  optional Label subtitle = 2;
+  optional Label title = 3;
+  optional Label unit = 4;
+}
+
+message ValueDate {
+  optional Label subtitle = 1;
+  optional Date currentDate = 2;
+  optional Date minDate = 3;
+  optional Date maxDate = 4;
+}
+
+message ValueHeight {
+  optional Label subtitle = 1;
+  optional uint32 value = 2;
+  optional uint32 unit = 3; // 0 cm
+}
+
+message Date {
+  optional uint32 month = 1;
+  optional uint32 day = 2;
+  optional uint32 year = 3;
+}
+
+message ChangeRequest {
+  optional uint32 screenId = 1;
+  optional uint32 entryId = 2;
+  optional Switch switch = 3;
+  optional Option option = 4;
+  optional Time time = 6;
+  optional Number number = 8;
+  optional Position position = 11;
+  optional NewDate newDate = 12;
+  optional Text text = 14;
+  optional Height height = 15;
+
+  message Switch {
+    required bool value = 1;
+  }
+  message Option {
+    optional uint32 index = 1;
+  }
+  message Time {
+    optional uint32 seconds = 1;
+  }
+  message Number {
+    optional uint32 value = 1;
+  }
+  message Position {
+    optional uint32 index = 1;
+    optional bool delete = 2;
+  }
+  message NewDate {
+    optional Date value = 1;
+  }
+  message Text {
+    optional string value = 1;
+  }
+  message Height {
+    optional uint32 value = 1;
+    optional uint32 unit = 2;
+  }
+}
+
+message ChangeResponse {
+  optional ResponseStatus status = 1;
+  optional ScreenState state = 3;
+  optional bool shouldReturn = 5;
+}
+
+message InitRequest {
+  optional string language = 1; // en_US
+  optional string region = 2; // us
+}
+
+message InitResponse {
+  optional string unk1 = 1;
+  optional string unk2 = 2;
+}
+
+enum ResponseStatus {
+  SUCCESS = 0;
+  GENERIC_ERROR = 1;
+}
diff --git a/app/src/main/proto/garmin/gdi_smart_proto.proto b/app/src/main/proto/garmin/gdi_smart_proto.proto
new file mode 100644
index 0000000..4d823a6
--- /dev/null
+++ b/app/src/main/proto/garmin/gdi_smart_proto.proto
@@ -0,0 +1,63 @@
+syntax = "proto2";
+
+package garmin_vivomovehr;
+
+option java_package = "nodomain.freeyourgadget.gadgetbridge.proto.garmin";
+
+import "garmin/gdi_device_status.proto";
+import "garmin/gdi_find_my_watch.proto";
+import "garmin/gdi_core.proto";
+import "garmin/gdi_http_service.proto";
+import "garmin/gdi_data_transfer_service.proto";
+import "garmin/gdi_sms_notification.proto";
+import "garmin/gdi_calendar_service.proto";
+import "garmin/gdi_settings_service.proto";
+import "garmin/gdi_notifications_service.proto";
+
+message Smart {
+    optional CalendarService calendar_service = 1;
+    optional HttpService http_service = 2;
+    optional DataTransferService data_transfer_service = 7;
+    optional DeviceStatusService device_status_service = 8;
+    optional FindMyWatchService find_my_watch_service = 12;
+    optional CoreService core_service = 13;
+    optional SmsNotificationService sms_notification_service = 16;
+    optional SettingsService settings_service = 42;
+    optional NotificationsService notifications_service = 49;
+}
+
+/*
+1:  CALENDAR_EVENTS_SERVICE_FIELD_NUMBER
+2:  CONNECT_IQ_HTTP_SERVICE_FIELD_NUMBER
+3:  CONNECT_IQ_INSTALLED_APPS_SERVICE_FIELD_NUMBER
+4:  CONNECT_IQ_APP_SETTINGS_SERVICE_FIELD_NUMBER
+5:  INTERNATIONAL_GOLF_SERVICE_FIELD_NUMBER
+6:  SWING_SENSOR_SERVICE_FIELD_NUMBER
+7:  DATA_TRANSFER_SERVICE_FIELD_NUMBER
+8:  DEVICE_STATUS_SERVICE_FIELD_NUMBER
+9:  INCIDENT_DETECTION_SERVICE_FIELD_NUMBER
+10: AUDIO_PROMPTS_SERVICE_FIELD_NUMBER
+11: WIFI_SETUP_SERVICE_FIELD_NUMBER
+12: FIND_MY_WATCH_SERVICE_FIELD_NUMBER
+13: CORE_SERVICE_FIELD_NUMBER
+14: GROUP_LIVE_TRACK_SERVICE_FIELD_NUMBER
+15: EXPRESSPAY_COMMAND_SERVICE_FIELD_NUMBER
+16: SMS_NOTIFICATION_SERVICE_FIELD_NUMBER
+17: LIVE_TRACK_MESSAGING_SERVICE_FIELD_NUMBER
+18: INSTANT_INPUT_SERVICE_FIELD_NUMBER
+19: SPORT_PROFILE_SETUP_SERVICE_FIELD_NUMBER
+20: HSA_DATA_SERVICE_FIELD_NUMBER
+21: LIVE_TRACK_SERVICE_FIELD_NUMBER
+22: EXPLORE_SYNC_SERVICE_FIELD_NUMBER
+23: WAY_POINT_TRANSFER_SERVICE_FIELD_NUMBER
+24: DEVICE_MESSAGE_SERVICE_FIELD_NUMBER
+25: LTE_SERVICE_FIELD_NUMBER
+26: ANTI_THEFT_ALARM_SERVICE_FIELD_NUMBER
+27: CREDENTIALS_SERVICE_FIELD_NUMBER
+28: INREACH_TRACKING_SERVICE_FIELD_NUMBER
+29: INREACH_MESSAGING_SERVICE_FIELD_NUMBER
+30: EVENT_SHARING_FIELD_NUMBER
+31: GENERIC_ITEM_TRANSFER_SERVICE_FIELD_NUMBER
+32: INREACH_CONTACT_SYNC_SERVICE_FIELD_NUMBER
+33: HAND_CALIBRATION_SERVICE_FIELD_NUMBER
+*/
diff --git a/app/src/main/proto/garmin/gdi_sms_notification.proto b/app/src/main/proto/garmin/gdi_sms_notification.proto
new file mode 100644
index 0000000..bc525a6
--- /dev/null
+++ b/app/src/main/proto/garmin/gdi_sms_notification.proto
@@ -0,0 +1,50 @@
+syntax = "proto2";
+
+package garmin_vivomovehr;
+
+option java_package = "nodomain.freeyourgadget.gadgetbridge.proto.garmin";
+
+message SmsNotificationService {
+    optional SmsSendMessageRequest sms_send_message_request = 1;
+    optional SmsSendMessageResponse sms_send_message_response = 2;
+    optional SmsCannedListChangedNotification sms_canned_list_changed_notification = 3;
+    optional SmsCannedListRequest sms_canned_list_request = 4;
+    optional SmsCannedListResponse sms_canned_list_response = 5;
+
+    message SmsSendMessageRequest {
+        optional string receiver_number = 1;
+        optional string message = 2;
+    }
+
+    message SmsSendMessageResponse {
+        optional ResponseStatus status = 1;
+    }
+
+    message SmsCannedListChangedNotification {
+        repeated CannedListType changed_type = 1;
+    }
+
+    message SmsCannedListRequest {
+        repeated CannedListType requested_types = 1;
+    }
+
+    message SmsCannedListResponse {
+        optional ResponseStatus status = 1;
+        repeated SmsCannedList lists = 2;
+    }
+
+    message SmsCannedList {
+        optional CannedListType type = 1;
+        repeated string response = 2;
+    }
+
+    enum CannedListType {
+        PHONE_CALL_RESPONSE = 0;
+        SMS_MESSAGE_RESPONSE = 1;
+    }
+
+    enum ResponseStatus {
+        SUCCESS = 0;
+        GENERIC_ERROR = 1;
+    }
+}
diff --git a/app/src/main/proto/huami.proto b/app/src/main/proto/huami.proto
new file mode 100644
index 0000000..2666473
--- /dev/null
+++ b/app/src/main/proto/huami.proto
@@ -0,0 +1,111 @@
+syntax = "proto3";
+
+option java_package = "nodomain.freeyourgadget.gadgetbridge.proto";
+option java_outer_classname = "HuamiProtos";
+
+message WorkoutSummary {
+    string version = 1;
+    Location location = 2;
+    Type type = 3;
+    Distance distance = 4;
+    Steps steps = 11;
+    Time time = 7;
+    SwimmingData swimmingData = 9;
+    Pace pace = 10;
+    Altitude altitude = 13;
+    HeartRate heartRate = 19;
+    Calories calories = 16;
+    TrainingEffect trainingEffect = 21;
+    HeartRateZones heartRateZones = 22;
+    Elevation elevation = 23;
+}
+
+message Location {
+    // TODO 2, 3
+    uint32 startTimestamp = 1; // unix epoch, seconds
+    int32 baseLatitude = 5; // /6000000 -> coords
+    int32 baseLongitude = 6; // /-6000000 -> coords
+    int32 baseAltitude = 7; // /2 -> meters
+    int32 maxLatitude = 8; // /3000000 -> coords
+    int32 minLatitude = 9; // /3000000 -> coords
+    int32 maxLongitude = 10; // /3000000 -> coords
+    int32 minLongitude = 11; // /3000000 -> coords
+}
+
+message HeartRate {
+    int32 avg = 1; // bpm
+    int32 max = 2; // bpm
+    int32 min = 3; // bpm
+}
+
+message Steps {
+    float avgCadence = 1; // steps/sec
+    float maxCadence = 2; // steps/sec
+    int32 avgStride = 3; // cm
+    int32 steps = 4; // count
+}
+
+message Type {
+    int32 type = 1; // 1 = running, 4 = bike, 3 = walk
+    int32 ai = 2; // 0 = normal, 1 = ai/automatic
+}
+
+message Distance {
+    float distance = 1; // meters
+}
+
+message Time {
+    int32 totalDuration = 1; // seconds
+    int32 workoutDuration = 2; // seconds
+    int32 pauseDuration = 3; // seconds
+}
+
+message Pace {
+    float avg = 1; // val * 1000 / 60 -> min/km
+    float best = 2; // val * 1000 / 60 -> min/km
+}
+
+message Altitude {
+    int32 maxAltitude = 1; // /200 -> meters
+    int32 minAltitude = 2; // /200 -> meters
+    int32 avgAltitude = 3; // /200 -> meters
+    int32 totalClimbing = 4; // cm
+    int32 elevationGain = 5; // cm
+    int32 elevationLoss = 6; // cm
+}
+
+message Calories {
+    int32 calories = 1; // kcal
+}
+
+message HeartRateZones {
+    // TODO 1, is always = 1?
+    // Zones: N/A, Warm-up, Fat-burn time, Aerobic, Anaerobic, Extreme
+    repeated int32 zoneMax = 2; // bpm
+    repeated int32 zoneTime = 3; // seconds
+}
+
+message Elevation {
+    // TODO 1, 2?
+    uint32 uphillTime = 3; // sec
+    uint32 downhillTime = 4; // sec
+}
+
+message TrainingEffect {
+    float aerobicTrainingEffect = 4;
+    float anaerobicTrainingEffect = 5;
+    int32 currentWorkoutLoad = 6;
+    int32 maximumOxygenUptake = 7; // ml/kg/min
+}
+
+message SwimmingData {
+    uint32 style = 1; // 1: breaststroke, 2: freestyle
+    uint32 laps = 2;
+    uint32 strokes = 3;
+    uint32 avgDps = 4; // cm
+    uint32 swolf = 5;
+    uint32 avgStrokeRate = 6;  // stroke/min
+    uint32 maxStrokeRate = 7;  // stroke/min
+    uint32 laneLength = 8;
+    uint32 laneLengthUnit = 9; // 0: meter, 1: yard
+}
diff --git a/app/src/main/proto/smaq2oss.proto b/app/src/main/proto/smaq2oss.proto
new file mode 100644
index 0000000..aca8879
--- /dev/null
+++ b/app/src/main/proto/smaq2oss.proto
@@ -0,0 +1,60 @@
+syntax = "proto3";
+
+option java_package = "nodomain.freeyourgadget.gadgetbridge.proto";
+option java_outer_classname = "SMAQ2OSSProtos";
+
+message SetTime
+{
+    int32 timestamp = 1;
+}
+
+message MusicControl
+{
+    int32 music_event = 1;
+}
+
+message CallControl
+{
+    int32 call_event = 1;
+}
+
+message Forecast
+{
+    int32 condition = 1;
+    int32 temperature_min = 2;
+    int32 temperature_max = 3;
+}
+
+message SetWeather
+{
+    int32 timestamp = 1;
+    int32 condition = 2;
+    int32 temperature = 3;
+    int32 temperature_min = 4;
+    int32 temperature_max = 5;
+    int32 humidity = 6;
+    repeated Forecast forecasts = 7;
+}
+
+message MessageNotification
+{
+    uint32 timestamp = 1;
+    int32 type = 2;
+    string sender = 3;
+    string subject = 4;
+    string body = 5;
+}
+
+message CallNotification
+{
+    string number = 1;
+    string name = 2;
+    int32 command = 3;
+}
+
+message MusicInfo
+{
+    string artist = 1;
+    string album = 2;
+    string track = 3;
+}
diff --git a/app/src/main/proto/xiaomi.proto b/app/src/main/proto/xiaomi.proto
new file mode 100644
index 0000000..1664adf
--- /dev/null
+++ b/app/src/main/proto/xiaomi.proto
@@ -0,0 +1,974 @@
+syntax = "proto2"; // we must use proto2 to serialize default values on the wire
+
+package xiaomi;
+
+// FIXME: The generated class is very large, so Android Studio stops recognizing it
+// set idea.max.intellisense.filesize=5000 in idea.properties
+// by clicking Help -> Edit Custom Properties
+
+option java_package = "nodomain.freeyourgadget.gadgetbridge.proto.xiaomi";
+option java_outer_classname = "XiaomiProto";
+
+message Command {
+  required uint32 type = 1;
+  optional uint32 subtype = 2;
+
+  optional Auth auth = 3;
+  optional System system = 4;
+  optional Watchface watchface = 6;
+  optional Health health = 10;
+  optional Calendar calendar = 14;
+  optional Music music = 20;
+  optional Notification notification = 9;
+  optional Weather weather = 12;
+  optional Schedule schedule = 19;
+
+  // command type 21
+  optional Phonebook phonebook = 23;
+
+  // type 22
+  optional DataUpload dataUpload = 24;
+
+  optional uint32 status = 100; // 0 on success on some
+}
+
+//
+// Auth
+//
+
+message Auth {
+  optional string userId = 7;
+  optional uint32 status = 8;
+  // 1, 26
+  optional PhoneNonce phoneNonce = 30;
+  optional WatchNonce watchNonce = 31;
+  // 1, 27
+  optional AuthStep3 authStep3 = 32;
+  optional AuthStep4 authStep4 = 33;
+}
+
+message PhoneNonce {
+  required bytes nonce = 1;
+}
+
+message WatchNonce {
+  required bytes nonce = 1;
+  required bytes hmac = 2;
+}
+
+message AuthStep3 {
+  required bytes encryptedNonces = 1;
+  required bytes encryptedDeviceInfo = 2;  // AuthDeviceInfo
+}
+
+message AuthStep4 {
+  required uint32 unknown1 = 1;
+  optional uint32 unknown2 = 2;
+}
+
+message AuthDeviceInfo {
+  required uint32 unknown1 = 1; // 0 - needs to be serialized explicitly
+  required float phoneApiLevel = 2;
+  required string phoneName = 3; // phone model
+  required uint32 unknown3 = 4; // 224
+  required string region = 5; // 2-letter, upper case
+}
+
+//
+// System
+//
+
+message System {
+  // 2, 1
+  optional Power power = 2;
+  // 2, 2
+  optional DeviceInfo deviceInfo = 3;
+  // 2, 3
+  optional Clock clock = 4;
+
+  // 2, 18
+  optional uint32 findDevice = 5; // 0, 1 == "found it" pressed on device
+
+  // 2, 29 get | 2, 39 set
+  optional DisplayItems displayItems = 10;
+
+  // 2, 34
+  optional DoNotDisturb dndStatus = 11;
+
+  // 2, 39
+  optional WorkoutTypes workoutTypes = 14;
+
+  // 2, 5
+  optional FirmwareInstallRequest firmwareInstallRequest = 16;
+  optional FirmwareInstallResponse firmwareInstallResponse = 17;
+
+  // 2, 9 get | 2, 21 set
+  optional Password password = 19;
+
+  // 2, 7 get | 2, 8 set
+  optional Camera camera = 15;
+
+  // 2, 6
+  optional Language language = 20;
+
+  // 2, 51 get | 2, 52 create
+  optional WidgetScreens widgetScreens = 28;
+  // 2, 53
+  optional WidgetParts widgetParts = 29;
+
+  // 2, 14
+  optional MiscSettingGet miscSettingGet = 34;
+  // 2, 15
+  optional MiscSettingSet miscSettingSet = 35;
+
+  // 2, 43
+  optional PhoneSilentModeGet phoneSilentModeGet = 36;
+
+  // 2, 44 returning to watch, 2, 45 setting from watch
+  optional PhoneSilentModeSet phoneSilentModeSet = 37;
+
+  // 2, 46
+  optional VibrationPatterns vibrationPatterns = 38;
+
+  // 2, 47
+  optional VibrationNotificationType vibrationSetPreset = 39;
+
+  // 2, 58
+  optional CustomVibrationPattern vibrationPatternCreate = 40;
+
+  // 2, 59
+  optional VibrationTest vibrationTestCustom = 41;
+
+  // 2, 47
+  optional VibrationPatternAck vibrationPatternAck = 43;
+
+  // 2, 78
+  optional BasicDeviceState basicDeviceState = 48;
+
+  // 2, 79
+  optional DeviceState deviceState = 49;
+}
+
+message Power {
+  optional Battery battery = 1;
+}
+
+message Battery {
+  optional uint32 level = 1;
+  optional uint32 state = 2;
+  optional LastCharge lastCharge = 3;
+}
+
+message LastCharge {
+  optional uint32 state = 1; // 2
+  optional uint32 timestampSeconds = 2;
+}
+
+message DeviceInfo {
+  required string serialNumber = 1;
+  required string firmware = 2;
+  optional string unknown3 = 3; // "" ?
+  required string model = 4;
+}
+
+message Clock {
+  required Date date = 1;
+  required Time time = 2;
+  required TimeZone timezone = 3;
+  optional bool isNot24hour = 4;
+}
+
+message Date {
+  required uint32 year = 1;
+  required uint32 month = 2;
+  required uint32 day = 3;
+}
+
+message Time {
+  required uint32 hour = 1;
+  required uint32 minute = 2;
+  optional uint32 second = 3;
+  optional uint32 millisecond = 4;
+}
+
+message TimeZone {
+  // offsets are in blocks of 15 min
+  optional sint32 zoneOffset = 1;
+  optional sint32 dstOffset = 2;
+  required string name = 3;
+}
+
+message DisplayItems {
+  repeated DisplayItem displayItem = 1;
+}
+
+message DisplayItem {
+  optional string code = 1;
+  optional string name = 2;
+  optional bool disabled = 3;
+  optional uint32 isSettings = 4;
+  optional uint32 unknown5 = 5; // 1
+  optional bool inMoreSection = 6; // rarely used in official app
+}
+
+message Camera {
+  required bool enabled = 1;
+}
+
+message Language {
+  optional string code = 1; // pt_pt, en_us
+}
+
+message WorkoutTypes {
+  repeated WorkoutType workoutType = 1;
+  optional uint32 unknown2 = 2; // 1
+}
+
+message WorkoutType {
+  optional uint32 type = 1;
+  optional uint32 unknown2 = 2; // 1
+}
+
+message WidgetScreens {
+  repeated WidgetScreen widgetScreen = 1;
+  optional uint32 isFullList = 2; // 1 to overwrite the full list
+  optional WidgetsCapabilities widgetsCapabilities = 3; // only in response
+}
+
+message WidgetsCapabilities {
+  optional uint32 minWidgets = 1; // 1
+  optional uint32 maxWidgets = 2; // 7
+
+  // bitmap:
+  // - 0b0000_0011_0000_0000 (768) on bands
+  // - 0b0000_0000_0000_0111 (7) on some square/round devices (Watch S1 Active)
+  // - 0b0000_0000_1000_0111 (135) on some square/round devices (Redmi Watch 4)
+  // - 0b0111_1100_0000_0000 (31744) on portrait devices (Band 8 Pro)
+  optional uint32 supportedLayoutStyles = 3;
+}
+
+message WidgetScreen {
+  optional uint32 id = 1; // starts at 1
+  optional uint32 layout = 2; // 256 for split, 512 for tall
+  repeated WidgetPart widgetPart = 3;
+}
+
+message WidgetParts {
+  repeated WidgetPart widgetPart = 1;
+}
+
+message WidgetPart {
+  optional uint32 type = 1; // 1 for small 1x1, 2 for wide 2x1, 3 for tall 1x2
+  optional uint32 function = 2; // matches command type
+  optional uint32 id = 3; // they all seem unique
+  optional string title = 4; // not set on create
+  optional uint32 subType = 5; // usually 0 if no subtype
+  optional string appId = 6; // "" on get
+  optional string unknown7 = 7; // "" on get
+}
+
+message DoNotDisturb {
+  optional uint32 status = 1; // 0 enabled, 2 disabled
+}
+
+message MiscSettingGet {
+  optional uint32 setting = 1; // 2 dndSync
+}
+
+message MiscSettingSet {
+  optional MiscNotificationSettings miscNotificationSettings = 1;
+  optional DndSync dndSync = 2;
+  optional WearingMode wearingMode = 3;
+}
+
+message MiscNotificationSettings {
+  optional uint32 wakeScreen = 1; // 0 ignore 1 enable 2 disable
+  optional uint32 onlyWhenPhoneLocked = 2; // 0 ignore 1 enable 2 disable
+}
+
+message DndSync {
+  optional uint32 enabled = 1; // 0/1
+}
+
+message WearingMode {
+  // 0 Band Mode (wristband)
+  // 1 Pebble Mode (show buckle)
+  // 2 Necklace mode (neck strap)
+  optional uint32 mode = 1;
+}
+
+message FirmwareInstallRequest {
+  optional uint32 unknown1 = 1; // 0
+  optional uint32 unknown2 = 2; // 0
+  optional string version = 3;
+  optional string md5 = 4;
+}
+
+message FirmwareInstallResponse {
+  optional uint32 status = 1; // 0
+}
+
+message Password {
+  optional uint32 state = 1; // 1 disabled, 2 enabled
+  optional string password = 2;
+  optional uint32 unknown3 = 3; // 0 when set on ret
+}
+
+message PhoneSilentModeGet {
+  optional uint32 unknown1 = 1; // 1
+}
+
+message PhoneSilentModeSet {
+  optional PhoneSilentMode phoneSilentMode = 1;
+}
+
+message PhoneSilentMode {
+  optional bool silent = 1;
+}
+
+message VibrationPatterns {
+  repeated VibrationNotificationType notificationType = 1;
+  optional uint32 unknown2 = 2; // 50, max patterns?
+  repeated CustomVibrationPattern customVibrationPattern = 3;
+}
+
+message CustomVibrationPattern {
+  optional uint32 id = 1;
+  optional string name = 2;
+  repeated Vibration vibration = 3;
+  optional uint32 unknown4 = 4; // 1 on creation
+}
+
+message VibrationNotificationType {
+  // 1 incoming calls
+  // 2 events // TODO confirm which one is events, which one is schedule
+  // 3 alarms
+  // 4 notifications
+  // 5 standing reminder
+  // 6 sms
+  // 7 goal
+  // 8 events // TODO confirm which one is events, which one is schedule
+  optional uint32 notificationType = 1;
+  optional uint32 preset = 2;
+}
+
+message VibrationTest {
+  repeated Vibration vibration = 1;
+}
+
+message VibrationPatternAck {
+  optional uint32 status = 1; // 0
+}
+
+message Vibration {
+  optional uint32 vibrate = 1; // 0/1
+  optional uint32 ms = 2;
+}
+
+message DeviceActivityState {
+  optional uint32 activityType = 1;
+  optional uint32 currentActivityState = 2;
+}
+
+message BasicDeviceState {
+  required bool isCharging = 1; // true when connected to charger
+  optional uint32 batteryLevel = 2;
+  required bool isWorn = 3; // true when the device detects it's being worn
+  required bool isUserAsleep = 4; // true when the device detected its user is asleep
+  optional DeviceActivityState activityState = 5;
+}
+
+message DeviceState {
+  optional uint32 chargingState = 1; // 1 charging, 2 not charging
+  optional uint32 wearingState = 2; // 1 wearing, 2 not wearing
+  optional uint32 sleepState = 3; // 1 sleep detected, 2 no sleep detected
+  optional uint32 warningState = 4; // ?
+  optional DeviceActivityState activityState = 5;
+}
+
+//
+// Watchface
+//
+
+message Watchface {
+  optional WatchfaceList watchfaceList = 1;
+
+  // 4, 2 delete | 4, 1 set
+  optional string watchfaceId = 2;
+  optional uint32 ack = 4; // 1
+
+  // 4, 4
+  optional uint32 installStatus = 5; // 0 not installed, 2 already installed
+  optional WatchfaceInstallStart watchfaceInstallStart = 6;
+  optional WatchfaceInstallFinish watchfaceInstallFinish = 7;
+}
+
+message WatchfaceList {
+  repeated WatchfaceInfo watchface = 1;
+}
+
+message WatchfaceInfo {
+  optional string id = 1;
+  optional string name = 2;
+  optional bool active = 3;
+  optional bool canDelete = 4;
+  optional uint32 unknown5 = 5; // 0
+  optional uint32 unknown6 = 6; // 0
+  optional uint32 unknown11 = 11; // 0
+}
+
+message WatchfaceInstallStart {
+  optional string id = 1;
+  optional uint32 size = 2;
+}
+
+message WatchfaceInstallFinish {
+  optional string id = 1;
+  optional uint32 unknown2 = 2; // 2
+  optional uint32 unknown3 = 3; // 0
+  optional uint32 unknown4 = 4; // 0
+}
+
+//
+// Health
+//
+
+message Health {
+  optional UserInfo userInfo = 1;
+
+  // 8, 2 get today | 8, 3 get past
+  optional bytes activityRequestFileIds = 2;
+  //
+  optional bytes activitySyncAckFileIds = 3;
+  optional ActivitySyncRequestToday activitySyncRequestToday = 5;
+
+  optional SpO2 spo2 = 7;
+  optional HeartRate heartRate = 8;
+  // 8, 12 get | 8, 13 set
+  optional StandingReminder standingReminder = 9;
+  optional Stress stress = 10;
+  optional GoalNotification goalNotification = 13;
+
+  // 8, 35 get | 8, 36 set
+  optional VitalityScore vitalityScore = 14;
+
+  // 8, 26
+  optional WorkoutStatusWatch workoutStatusWatch = 20;
+
+  // 8, 30
+  optional WorkoutOpenWatch workoutOpenWatch = 25;
+  optional WorkoutOpenReply workoutOpenReply = 26;
+
+  // 7, 43
+  optional GoalsConfig goalsConfig = 38;
+
+  // 7, 48
+  optional WorkoutLocation workoutLocation = 40;
+
+  // 8,45 enable | 8, 46 disable | 8, 47 periodic
+  optional RealTimeStats realTimeStats = 39;
+}
+
+message UserInfo {
+  optional uint32 height = 1; // cm
+  optional float weight = 2; // kg
+  optional uint32 birthday = 3; // YYYYMMDD
+  optional uint32 gender = 4; // 1 male, 2 female
+  optional uint32 maxHeartRate = 5;
+  optional uint32 goalCalories = 6;
+  optional uint32 goalSteps = 7;
+  optional uint32 goalStanding = 9; // hours
+  optional uint32 goalMoving = 11; // minutes
+}
+
+message ActivitySyncRequestToday {
+  optional uint32 unknown1 = 1; // 0 most of the time, sometimes 1
+}
+
+message SpO2 {
+  optional uint32 unknown1 = 1; // 1
+  optional bool allDayTracking = 2;
+  optional Spo2AlarmLow alarmLow = 4;
+}
+
+message Spo2AlarmLow {
+  optional bool alarmLowEnabled = 1;
+  optional uint32 alarmLowThreshold = 2; // 90, 85, 80
+}
+
+message HeartRate {
+  optional bool disabled = 1; // 0 enabled 1 disabled
+  optional uint32 interval = 2; // 0 smart 1 10 30
+  optional bool alarmHighEnabled = 3;
+  optional uint32 alarmHighThreshold = 4; // 100, 110, ... 150
+  optional AdvancedMonitoring advancedMonitoring = 5;
+  optional uint32 unknown7 = 7; // 1
+  optional HeartRateAlarmLow heartRateAlarmLow = 8;
+  optional uint32 breathingScore = 9; // 1 on, 2 off
+}
+
+message AdvancedMonitoring {
+  required bool enabled = 1;
+}
+
+message HeartRateAlarmLow {
+  optional bool alarmLowEnabled = 1;
+  optional uint32 alarmLowThreshold = 2; // 40, 45, 50
+}
+
+message StandingReminder {
+  optional bool enabled = 1;
+  optional HourMinute start = 2;
+  optional HourMinute end = 3;
+  optional bool dnd = 4;
+  optional HourMinute dndStart = 6;
+  optional HourMinute dndEnd = 7;
+}
+
+message Stress {
+  optional bool allDayTracking = 1;
+  optional RelaxReminder relaxReminder = 2;
+}
+
+message GoalNotification {
+  optional bool enabled = 1;
+  optional uint32 unknown2 = 2; // 1
+}
+
+message RelaxReminder {
+  optional bool enabled = 1;
+  optional uint32 unknown2 = 2; // 0
+}
+
+message VitalityScore {
+  optional bool sevenDay = 1;
+  optional bool dailyProgress = 2;
+}
+
+message WorkoutStatusWatch {
+  optional uint32 timestamp = 1; // seconds
+  optional uint32 sport = 3;
+  optional uint32 status = 4; // 0 started, 1 resumed, 2 paused, 3 finished
+  optional bytes activityFileIds = 5;
+  optional uint32 unknown6 = 6; // 2
+  optional uint32 unknown10 = 10; // 0
+}
+
+message WorkoutOpenWatch {
+  // This is only called when gps is needed?
+  // 1 outdoor running, 2 walking, 3 hiking, 4 trekking, 5 trail run, 6 outdoor cycling
+  optional uint32 sport = 1;
+  optional uint32 unknown2 = 2; // 2
+}
+
+message WorkoutOpenReply {
+  // 3 2 10 when no gps permissions at all
+  // 5 2 10 when no all time gps permission
+  // ...
+  // 0 * * when phone gps is working fine
+  // 0 2 10
+  // 0 2 2
+  optional uint32 unknown1 = 1;
+  optional uint32 unknown2 = 2; // always 2?
+  optional uint32 unknown3 = 3;
+}
+
+message GoalsConfig {
+  repeated Goal currentGoals = 1;
+  repeated Goal supportedGoals = 2;
+}
+
+message Goal {
+  // 1 steps?
+  // 2 calories?
+  // 3 moving time
+  // 4 standing time
+  optional uint32 id = 1;
+}
+
+message WorkoutLocation {
+  optional uint32 unknown1 = 1; // 10, sometimes 2?
+  optional uint32 timestamp = 2; // seconds
+  optional double longitude = 3;
+  optional double latitude = 4;
+  optional double altitude = 5;
+  optional float speed = 6;
+  optional float bearing = 7;
+  optional float horizontalAccuracy = 8;
+  optional float verticalAccuracy = 9;
+}
+
+message RealTimeStats {
+  optional uint32 steps = 1;
+  optional uint32 calories = 2;
+  optional uint32 unknown3 = 3; // increases during activity
+  optional uint32 heartRate = 4;
+  optional uint32 unknown5 = 5; // 0 probably moving time
+  optional uint32 standingHours = 6;
+}
+
+//
+// Calendar
+//
+
+message Calendar {
+  optional CalendarSync calendarSync = 2;
+}
+
+message CalendarSync {
+  repeated CalendarEvent event = 1;
+  optional bool disabled = 2;
+}
+
+message CalendarEvent {
+  optional string title = 1;
+  optional string description = 2;
+  optional string location = 3;
+  optional uint32 start = 4; // unix epoch sec
+  optional uint32 end = 5; // unix epoch sec
+  optional bool allDay = 6;
+  optional uint32 notifyMinutesBefore = 7;
+}
+
+//
+// Music
+//
+
+message Music {
+  // 18, 1
+  optional MusicInfo musicInfo = 1;
+  // 18, 2
+  optional MediaKey mediaKey = 2;
+}
+
+message MusicInfo {
+  required uint32 state = 1; // 0 not playing, 1 playing, 2 paused
+  optional uint32 volume = 2;
+  optional string track = 4;
+  optional string artist = 5;
+  optional uint32 position = 6;
+  optional uint32 duration = 7;
+}
+
+message MediaKey {
+  required uint32 key = 1; // 0 play, 1 pause, 3 prev, 4 next, 5 vol
+  optional uint32 volume = 2; // 100 vol+, 0 vol-
+}
+
+//
+// Notification
+//
+
+message Notification {
+  // 7, 8
+  optional NotificationId openOnPhone = 2;
+  optional Notification2 notification2 = 3;
+  optional NotificationDismiss notificationDismiss = 4;
+
+  optional bool screenOnOnNotifications = 7;
+  optional uint32 unknown8 = 8; // 1 on canned replies request?
+  // 7, 9 get | 7, 12 set
+  optional CannedMessages cannedMessages = 9;
+
+  // 7, 13
+  optional NotificationReply notificationReply = 12;
+  // 7, 14
+  optional uint32 notificationReplyStatus = 13; // 0 on success, 1 on failure
+
+  // 7, 15
+  optional NotificationIconPackage notificationIconReply = 14;
+  // 7, 15
+  optional NotificationIconRequest notificationIconRequest = 15;
+  // 7, 16
+  optional NotificationIconPackage notificationIconQuery = 16;
+}
+
+message Notification2 {
+  optional Notification3 notification3 = 1;
+}
+
+message Notification3 {
+  optional string package = 1;
+  optional string appName = 2;
+  optional string title = 3;
+  optional string unknown4 = 4;
+  optional string body = 5;
+  optional string timestamp = 6;
+  optional uint32 id = 7;
+  optional bool isCall = 8;
+  optional bool repliesAllowed = 11; // only for calls?
+  optional string key = 12; // "0|<package>|<id 2 complement>|null|12345"
+  optional bool openOnPhone = 13; // 1 to show "Open on phone", needs key
+}
+
+message NotificationDismiss {
+  repeated NotificationId notificationId = 1;
+}
+
+message NotificationId {
+  optional uint32 id = 1;
+  optional string package = 2; // truncated
+  optional string key = 4; // sometimes ""
+}
+
+message CannedMessages {
+  optional uint32 minReplies = 1;
+  repeated string reply = 2;
+  optional uint32 maxReplies = 3;
+}
+
+message NotificationIconRequest {
+  optional uint32 status = 1; // 0
+  optional uint32 pixelFormat = 2; // see XiaomiBitmapUtils
+  optional uint32 size = 3;
+}
+
+message NotificationReply {
+  optional uint32 unknown1 = 1; // 1
+  optional string message = 2;
+  optional uint32 unknown3 = 3; // 1
+  optional string number = 4;
+}
+
+message NotificationIconPackage {
+  optional string package = 1;
+}
+
+//
+// Weather
+//
+
+message Weather {
+  optional WeatherCurrent current = 1;
+  optional WeatherForecast forecast = 2;
+
+  // response to 10, 5 (get location list) | payload of 10, 6 (set, update location list) | payload of 10, 8 (remove)
+  optional WeatherLocations locations = 4;
+
+  // indication payload of 10, 3 (requested update) | payload of 10, 7 (set current, add to list)
+  optional WeatherLocation location = 5;
+
+  // 10, 10
+  optional WeatherPrefs prefs = 6;
+}
+
+message WeatherCurrent {
+  optional WeatherMetadata metadata = 1;
+  required uint32 weatherCondition = 2;
+  optional WeatherUnitValue temperature = 3;
+  optional WeatherUnitValue humidity = 4;
+  optional WeatherUnitValue wind = 5;
+  optional WeatherUnitValue uv = 6;
+  optional WeatherUnitValue aqi = 7;
+  optional WeatherWarnings warning = 8; // Seems to be an array?
+  optional float pressure = 9;
+}
+
+message WeatherMetadata {
+  required string publicationTimestamp = 1;
+  required string cityName = 2;
+  required string locationName = 3;
+  optional string locationKey = 4;
+  optional bool isCurrentLocation = 5; // default location?
+}
+
+message WeatherUnitValue {
+  required string unit = 1;
+  required sint32 value = 2;
+}
+
+message WeatherWarnings {
+  repeated WeatherWarning warning = 1;
+}
+
+message WeatherWarning {
+  required string type = 1;
+  required string level = 2;
+  optional string title = 3;
+  optional string description = 4;
+  optional string id = 5;
+}
+
+message WeatherLocations {
+  repeated WeatherLocation location = 1;
+}
+
+message WeatherForecast {
+  required WeatherMetadata metadata = 1;
+  required ForecastEntries entries = 2;
+}
+
+message ForecastEntries {
+  repeated ForecastEntry entry = 1;
+}
+
+message ForecastEntry {
+  optional WeatherUnitValue aqi = 1;
+  optional WeatherRange conditionRange = 2;
+  optional WeatherRange temperatureRange = 3;
+  optional string temperatureSymbol = 4;
+  optional WeatherSunriseSunset sunriseSunset = 5;
+  optional WeatherUnitValue wind = 6;
+}
+
+message WeatherRange {
+  required sint32 from = 1;
+  required sint32 to = 2;
+}
+
+message WeatherSunriseSunset {
+  required string sunrise = 1;
+  required string sunset = 2;
+}
+
+message WeatherLocation {
+  required string code = 1;
+  optional string name = 2;
+}
+
+message WeatherPrefs {
+  optional uint32 temperatureScale = 1; // 1 celsius 2 fahrenheit
+  optional uint32 weatherWarningsEnabled = 2; // 0 = unsupported, 1 = enabled, 2 = disabled
+}
+
+//
+// Schedule
+//
+
+message Schedule {
+  // 17, 0 get
+  optional Alarms alarms = 1;
+  // 17, 1
+  optional AlarmDetails createAlarm = 2;
+  // 17, 3 -> returns 17, 5
+  optional Alarm editAlarm = 3;
+
+  optional uint32 ackId = 4; // id of created or edited alarm and reminder
+
+  // 17, 4
+  optional AlarmDelete deleteAlarm = 5;
+
+  // 17, 8 get | 17, 9 set
+  optional SleepMode sleepMode = 9;
+
+  // 17, 14 get: 10 -> 2: 50 // max reminders?
+  optional Reminders reminders = 10;
+
+  // 17,10 get/ret | 17,11 create | 17,13 delete
+  optional WorldClocks worldClocks = 11;
+
+  optional uint32 worldClockStatus = 13; // 0 on edit and create
+
+  // 17, 15
+  optional ReminderDetails createReminder = 14;
+
+  // 17, 17
+  optional Reminder editReminder = 15;
+
+  // 17, 18
+  optional ReminderDelete deleteReminder = 17;
+}
+
+message Alarms {
+  optional uint32 maxAlarms = 2; // 10
+  optional uint32 unknown3 = 3; // 0
+  optional uint32 unknown4 = 4; // 1
+  repeated Alarm alarm = 1;
+}
+
+message Alarm {
+  optional uint32 id = 1; // starts at 1
+  optional AlarmDetails alarmDetails = 2;
+}
+
+message AlarmDetails {
+  optional HourMinute time = 2;
+  optional uint32 repeatMode = 3; // 0 once, 1 daily, 5 weekly
+  optional uint32 repeatFlags = 4; // only if weekly: 31 during week, 1 monday, 2 tuesday, 3 mon tue
+  optional bool enabled = 5;
+  optional uint32 smart = 7; // 1 smart, 2 normal
+}
+
+message AlarmDelete {
+  repeated uint32 id = 1;
+}
+
+message SleepMode {
+  required bool enabled = 1;
+  optional SleepModeSchedule schedule = 2;
+}
+
+message SleepModeSchedule {
+  optional HourMinute start = 1;
+  optional HourMinute end = 2;
+  optional uint32 unknown3 = 3; // 0
+}
+
+message Reminders {
+  repeated Reminder reminder = 1;
+  optional uint32 maxReminders = 2;
+}
+
+message Reminder {
+  optional uint32 id = 1;
+  optional ReminderDetails reminderDetails = 2;
+}
+
+message ReminderDetails {
+  optional Date date = 1;
+  optional Time time = 2;
+  optional uint32 repeatMode = 3; // 0 once, 1 daily, weekly (every monday), 7 monthly, 8 yearly
+  optional uint32 repeatFlags = 4; // 64 for unset, day flags on weekly
+  optional string title = 5;
+}
+
+message ReminderDelete {
+  repeated uint32 id = 1;
+}
+
+message WorldClocks {
+  repeated string worldClock = 1;
+}
+
+message HourMinute {
+  required uint32 hour = 1;
+  required uint32 minute = 2;
+}
+
+//
+// Data Upload (watchface, notification icons, firmware)
+//
+
+message DataUpload {
+  // 22, 0
+  optional DataUploadRequest dataUploadRequest = 1;
+  optional DataUploadAck dataUploadAck = 2;
+}
+
+message DataUploadRequest {
+  optional uint32 type = 1; // 16 for watchface, 50 for notification icons, 32 for firmware?
+  optional bytes md5sum = 2;
+  optional uint32 size = 3;
+}
+
+message DataUploadAck {
+  optional bytes md5sum = 1;
+  optional uint32 unknown2 = 2; // 0
+  optional uint32 resumePosition = 4;
+  optional uint32 chunkSize = 5; // 4096 on Redmi Watch 3 Active, Nonexistent on Mi Band 8
+}
+
+message ContactInfo {
+  optional string displayName = 1;
+  optional string phoneNumber = 2;
+}
+
+message ContactList {
+  repeated ContactInfo contactInfo = 1;
+}
+
+message Phonebook {
+  optional string requestedPhoneNumber = 2;
+  optional ContactInfo contactInfo = 3;
+  optional ContactList contactList = 4;
+}
diff --git a/app/src/main/res/.gitignore b/app/src/main/res/.gitignore
new file mode 100644
index 0000000..ad287f3
--- /dev/null
+++ b/app/src/main/res/.gitignore
@@ -0,0 +1 @@
+values-en
diff --git a/app/src/main/res/android-logger.properties b/app/src/main/res/android-logger.properties
new file mode 100644
index 0000000..a266cc6
--- /dev/null
+++ b/app/src/main/res/android-logger.properties
@@ -0,0 +1,28 @@
+# Android Logger configuration
+
+# use VERBOSE, DEBUG, INFO, WARN, ERROR, ASSERT
+# By default logger will print only WARN(and higher) messages
+# with "Gadgetbridge" tag
+root=WARN:Gadgetbridge
+
+# DEBUG (and higher) messages from classes of com.example.database
+# will be logged with "MyApplication-Database" tag
+#logger.com.example.database=DEBUG:MyApplication-Database
+
+# All messages from classes of com.example.ui will be logged with
+# "MyApplication-UI" tag
+#logger.com.example.ui=MyApplication-UI
+#The configuration manages which log tag will be used to print messages and which logging level filter will be applied.
+
+#logger.<package/classname>=<level>:<tag>:<message head>
+# or
+#logger.<package/classname>=<level>:<tag>
+# or
+#logger.<package/classname>=<tag>
+#The rest of messages will be managed by root logger:
+
+#root=<level>:<tag>:<message head>
+# or
+#root=<level>:<tag>
+# or
+#root=<tag>
diff --git a/app/src/main/res/anim/bounceleft.xml b/app/src/main/res/anim/bounceleft.xml
new file mode 100644
index 0000000..ecf953c
--- /dev/null
+++ b/app/src/main/res/anim/bounceleft.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+    android:fillAfter="true"
+    android:interpolator="@android:anim/decelerate_interpolator">
+
+    <translate
+        android:fromXDelta="-10%" android:toXDelta="0%"
+        android:fromYDelta="0%" android:toYDelta="0%"
+        android:duration="200" />
+
+</set>
\ No newline at end of file
diff --git a/app/src/main/res/anim/bounceright.xml b/app/src/main/res/anim/bounceright.xml
new file mode 100644
index 0000000..412aef2
--- /dev/null
+++ b/app/src/main/res/anim/bounceright.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+    android:fillAfter="true"
+    android:interpolator="@android:anim/decelerate_interpolator">
+    <translate
+        android:fromXDelta="10%" android:toXDelta="0%"
+        android:fromYDelta="0%" android:toYDelta="0%"
+        android:duration="200" />
+
+</set>
\ No newline at end of file
diff --git a/app/src/main/res/anim/flyleft.xml b/app/src/main/res/anim/flyleft.xml
new file mode 100644
index 0000000..80f8e28
--- /dev/null
+++ b/app/src/main/res/anim/flyleft.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shareInterpolator="false">
+    <translate
+        android:fromXDelta="100%" android:toXDelta="0%"
+        android:fromYDelta="0%" android:toYDelta="0%"
+        android:duration="150" />
+</set>
\ No newline at end of file
diff --git a/app/src/main/res/anim/flyright.xml b/app/src/main/res/anim/flyright.xml
new file mode 100644
index 0000000..63523f3
--- /dev/null
+++ b/app/src/main/res/anim/flyright.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shareInterpolator="false">
+    <translate
+        android:fromXDelta="-100%" android:toXDelta="0%"
+        android:fromYDelta="0%" android:toYDelta="0%"
+        android:duration="150" />
+</set>
\ No newline at end of file
diff --git a/app/src/main/res/anim/slidefromtop.xml b/app/src/main/res/anim/slidefromtop.xml
new file mode 100644
index 0000000..ee6056c
--- /dev/null
+++ b/app/src/main/res/anim/slidefromtop.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+    android:fillAfter="true"
+    android:interpolator="@android:anim/decelerate_interpolator">
+
+    <translate
+        android:fromXDelta="0%" android:toXDelta="0%"
+        android:fromYDelta="-200%" android:toYDelta="0%"
+        android:duration="100" />
+
+</set>
\ No newline at end of file
diff --git a/app/src/main/res/color/alarm_dow.xml b/app/src/main/res/color/alarm_dow.xml
new file mode 100644
index 0000000..3bb1f0c
--- /dev/null
+++ b/app/src/main/res/color/alarm_dow.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:color="?attr/colorPrimary" android:state_checked="true" />
+    <item android:color="?attr/textColorTertiary" />
+</selector>
\ No newline at end of file
diff --git a/app/src/main/res/color/blacklist_checkboxes.xml b/app/src/main/res/color/blacklist_checkboxes.xml
new file mode 100644
index 0000000..df27b11
--- /dev/null
+++ b/app/src/main/res/color/blacklist_checkboxes.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_checked="true" android:color="?attr/colorAccent"/> <!-- checked -->
+    <item android:color="?attr/textColorPrimary"/> <!-- anything else -->
+</selector>
\ No newline at end of file
diff --git a/app/src/main/res/color/btn_dark.xml b/app/src/main/res/color/btn_dark.xml
new file mode 100644
index 0000000..0e4a3f3
--- /dev/null
+++ b/app/src/main/res/color/btn_dark.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_enabled="false" android:color="@color/tertiarytext_dark" />
+    <item android:color="@color/primarytext_dark" />
+</selector>
\ No newline at end of file
diff --git a/app/src/main/res/drawable-hdpi/ic_languagepack.png b/app/src/main/res/drawable-hdpi/ic_languagepack.png
new file mode 100644
index 0000000..215ff1d
--- /dev/null
+++ b/app/src/main/res/drawable-hdpi/ic_languagepack.png
Binary files differ
diff --git a/app/src/main/res/drawable-hdpi/ic_launcher.png b/app/src/main/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..2f00d13
--- /dev/null
+++ b/app/src/main/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/app/src/main/res/drawable-hdpi/ic_notification.png b/app/src/main/res/drawable-hdpi/ic_notification.png
new file mode 100644
index 0000000..4e60fa9
--- /dev/null
+++ b/app/src/main/res/drawable-hdpi/ic_notification.png
Binary files differ
diff --git a/app/src/main/res/drawable-hdpi/ic_notification_disconnected.png b/app/src/main/res/drawable-hdpi/ic_notification_disconnected.png
new file mode 100644
index 0000000..5d510a7
--- /dev/null
+++ b/app/src/main/res/drawable-hdpi/ic_notification_disconnected.png
Binary files differ
diff --git a/app/src/main/res/drawable-hdpi/ic_notification_disconnected_qhybrid.png b/app/src/main/res/drawable-hdpi/ic_notification_disconnected_qhybrid.png
new file mode 100644
index 0000000..0d28357
--- /dev/null
+++ b/app/src/main/res/drawable-hdpi/ic_notification_disconnected_qhybrid.png
Binary files differ
diff --git a/app/src/main/res/drawable-hdpi/ic_notification_full_battery.png b/app/src/main/res/drawable-hdpi/ic_notification_full_battery.png
new file mode 100644
index 0000000..6a5d25c
--- /dev/null
+++ b/app/src/main/res/drawable-hdpi/ic_notification_full_battery.png
Binary files differ
diff --git a/app/src/main/res/drawable-hdpi/ic_notification_low_battery.png b/app/src/main/res/drawable-hdpi/ic_notification_low_battery.png
new file mode 100644
index 0000000..dad13ba
--- /dev/null
+++ b/app/src/main/res/drawable-hdpi/ic_notification_low_battery.png
Binary files differ
diff --git a/app/src/main/res/drawable-hdpi/ic_notification_qhybrid.png b/app/src/main/res/drawable-hdpi/ic_notification_qhybrid.png
new file mode 100644
index 0000000..af7684c
--- /dev/null
+++ b/app/src/main/res/drawable-hdpi/ic_notification_qhybrid.png
Binary files differ
diff --git a/app/src/main/res/drawable-hdpi/ic_systemapp.png b/app/src/main/res/drawable-hdpi/ic_systemapp.png
new file mode 100644
index 0000000..aa1f5ee
--- /dev/null
+++ b/app/src/main/res/drawable-hdpi/ic_systemapp.png
Binary files differ
diff --git a/app/src/main/res/drawable-hdpi/ic_watchapp.png b/app/src/main/res/drawable-hdpi/ic_watchapp.png
new file mode 100644
index 0000000..46dea36
--- /dev/null
+++ b/app/src/main/res/drawable-hdpi/ic_watchapp.png
Binary files differ
diff --git a/app/src/main/res/drawable-hdpi/ic_watchface.png b/app/src/main/res/drawable-hdpi/ic_watchface.png
new file mode 100644
index 0000000..95fe885
--- /dev/null
+++ b/app/src/main/res/drawable-hdpi/ic_watchface.png
Binary files differ
diff --git a/app/src/main/res/drawable-mdpi/ic_languagepack.png b/app/src/main/res/drawable-mdpi/ic_languagepack.png
new file mode 100644
index 0000000..9b77bc5
--- /dev/null
+++ b/app/src/main/res/drawable-mdpi/ic_languagepack.png
Binary files differ
diff --git a/app/src/main/res/drawable-mdpi/ic_launcher.png b/app/src/main/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..fc54488
--- /dev/null
+++ b/app/src/main/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/app/src/main/res/drawable-mdpi/ic_notification.png b/app/src/main/res/drawable-mdpi/ic_notification.png
new file mode 100644
index 0000000..b244763
--- /dev/null
+++ b/app/src/main/res/drawable-mdpi/ic_notification.png
Binary files differ
diff --git a/app/src/main/res/drawable-mdpi/ic_notification_disconnected.png b/app/src/main/res/drawable-mdpi/ic_notification_disconnected.png
new file mode 100644
index 0000000..316bc30
--- /dev/null
+++ b/app/src/main/res/drawable-mdpi/ic_notification_disconnected.png
Binary files differ
diff --git a/app/src/main/res/drawable-mdpi/ic_notification_disconnected_qhybrid.png b/app/src/main/res/drawable-mdpi/ic_notification_disconnected_qhybrid.png
new file mode 100644
index 0000000..27fee12
--- /dev/null
+++ b/app/src/main/res/drawable-mdpi/ic_notification_disconnected_qhybrid.png
Binary files differ
diff --git a/app/src/main/res/drawable-mdpi/ic_notification_full_battery.png b/app/src/main/res/drawable-mdpi/ic_notification_full_battery.png
new file mode 100644
index 0000000..b8270c7
--- /dev/null
+++ b/app/src/main/res/drawable-mdpi/ic_notification_full_battery.png
Binary files differ
diff --git a/app/src/main/res/drawable-mdpi/ic_notification_low_battery.png b/app/src/main/res/drawable-mdpi/ic_notification_low_battery.png
new file mode 100644
index 0000000..32c4393
--- /dev/null
+++ b/app/src/main/res/drawable-mdpi/ic_notification_low_battery.png
Binary files differ
diff --git a/app/src/main/res/drawable-mdpi/ic_notification_qhybrid.png b/app/src/main/res/drawable-mdpi/ic_notification_qhybrid.png
new file mode 100644
index 0000000..1dfbbd7
--- /dev/null
+++ b/app/src/main/res/drawable-mdpi/ic_notification_qhybrid.png
Binary files differ
diff --git a/app/src/main/res/drawable-mdpi/ic_systemapp.png b/app/src/main/res/drawable-mdpi/ic_systemapp.png
new file mode 100644
index 0000000..245ef3b
--- /dev/null
+++ b/app/src/main/res/drawable-mdpi/ic_systemapp.png
Binary files differ
diff --git a/app/src/main/res/drawable-mdpi/ic_watchapp.png b/app/src/main/res/drawable-mdpi/ic_watchapp.png
new file mode 100644
index 0000000..be99a31
--- /dev/null
+++ b/app/src/main/res/drawable-mdpi/ic_watchapp.png
Binary files differ
diff --git a/app/src/main/res/drawable-mdpi/ic_watchface.png b/app/src/main/res/drawable-mdpi/ic_watchface.png
new file mode 100644
index 0000000..8fc3931
--- /dev/null
+++ b/app/src/main/res/drawable-mdpi/ic_watchface.png
Binary files differ
diff --git a/app/src/main/res/drawable-v21/splash.xml b/app/src/main/res/drawable-v21/splash.xml
new file mode 100644
index 0000000..10bcca0
--- /dev/null
+++ b/app/src/main/res/drawable-v21/splash.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <item
+        android:drawable="@color/primary_dark"/>
+
+    <item
+        android:gravity="center"
+        android:drawable="@drawable/gadgetbridge_img"/>
+
+</layer-list>
diff --git a/app/src/main/res/drawable-xhdpi/ic_languagepack.png b/app/src/main/res/drawable-xhdpi/ic_languagepack.png
new file mode 100644
index 0000000..9e4d7d0
--- /dev/null
+++ b/app/src/main/res/drawable-xhdpi/ic_languagepack.png
Binary files differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_launcher.png b/app/src/main/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..3c1c88f
--- /dev/null
+++ b/app/src/main/res/drawable-xhdpi/ic_launcher.png
Binary files differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_notification.png b/app/src/main/res/drawable-xhdpi/ic_notification.png
new file mode 100644
index 0000000..68d8146
--- /dev/null
+++ b/app/src/main/res/drawable-xhdpi/ic_notification.png
Binary files differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_notification_disconnected.png b/app/src/main/res/drawable-xhdpi/ic_notification_disconnected.png
new file mode 100644
index 0000000..b382a8f
--- /dev/null
+++ b/app/src/main/res/drawable-xhdpi/ic_notification_disconnected.png
Binary files differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_notification_disconnected_qhybrid.png b/app/src/main/res/drawable-xhdpi/ic_notification_disconnected_qhybrid.png
new file mode 100644
index 0000000..29c49e2
--- /dev/null
+++ b/app/src/main/res/drawable-xhdpi/ic_notification_disconnected_qhybrid.png
Binary files differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_notification_full_battery.png b/app/src/main/res/drawable-xhdpi/ic_notification_full_battery.png
new file mode 100644
index 0000000..3c5c84d
--- /dev/null
+++ b/app/src/main/res/drawable-xhdpi/ic_notification_full_battery.png
Binary files differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_notification_low_battery.png b/app/src/main/res/drawable-xhdpi/ic_notification_low_battery.png
new file mode 100644
index 0000000..f1aff36
--- /dev/null
+++ b/app/src/main/res/drawable-xhdpi/ic_notification_low_battery.png
Binary files differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_notification_qhybrid.png b/app/src/main/res/drawable-xhdpi/ic_notification_qhybrid.png
new file mode 100644
index 0000000..3a57df5
--- /dev/null
+++ b/app/src/main/res/drawable-xhdpi/ic_notification_qhybrid.png
Binary files differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_systemapp.png b/app/src/main/res/drawable-xhdpi/ic_systemapp.png
new file mode 100644
index 0000000..125740d
--- /dev/null
+++ b/app/src/main/res/drawable-xhdpi/ic_systemapp.png
Binary files differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_watchapp.png b/app/src/main/res/drawable-xhdpi/ic_watchapp.png
new file mode 100644
index 0000000..f34b272
--- /dev/null
+++ b/app/src/main/res/drawable-xhdpi/ic_watchapp.png
Binary files differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_watchface.png b/app/src/main/res/drawable-xhdpi/ic_watchface.png
new file mode 100644
index 0000000..43e2ebe
--- /dev/null
+++ b/app/src/main/res/drawable-xhdpi/ic_watchface.png
Binary files differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_languagepack.png b/app/src/main/res/drawable-xxhdpi/ic_languagepack.png
new file mode 100644
index 0000000..3d2ff44
--- /dev/null
+++ b/app/src/main/res/drawable-xxhdpi/ic_languagepack.png
Binary files differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_launcher.png b/app/src/main/res/drawable-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..63e7098
--- /dev/null
+++ b/app/src/main/res/drawable-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_notification.png b/app/src/main/res/drawable-xxhdpi/ic_notification.png
new file mode 100644
index 0000000..aa9a5ce
--- /dev/null
+++ b/app/src/main/res/drawable-xxhdpi/ic_notification.png
Binary files differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_notification_disconnected.png b/app/src/main/res/drawable-xxhdpi/ic_notification_disconnected.png
new file mode 100644
index 0000000..cf509b1
--- /dev/null
+++ b/app/src/main/res/drawable-xxhdpi/ic_notification_disconnected.png
Binary files differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_notification_disconnected_qhybrid.png b/app/src/main/res/drawable-xxhdpi/ic_notification_disconnected_qhybrid.png
new file mode 100644
index 0000000..dd8dc63
--- /dev/null
+++ b/app/src/main/res/drawable-xxhdpi/ic_notification_disconnected_qhybrid.png
Binary files differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_notification_full_battery.png b/app/src/main/res/drawable-xxhdpi/ic_notification_full_battery.png
new file mode 100644
index 0000000..b10c3ca
--- /dev/null
+++ b/app/src/main/res/drawable-xxhdpi/ic_notification_full_battery.png
Binary files differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_notification_low_battery.png b/app/src/main/res/drawable-xxhdpi/ic_notification_low_battery.png
new file mode 100644
index 0000000..60dacdf
--- /dev/null
+++ b/app/src/main/res/drawable-xxhdpi/ic_notification_low_battery.png
Binary files differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_notification_qhybrid.png b/app/src/main/res/drawable-xxhdpi/ic_notification_qhybrid.png
new file mode 100644
index 0000000..b069b23
--- /dev/null
+++ b/app/src/main/res/drawable-xxhdpi/ic_notification_qhybrid.png
Binary files differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_systemapp.png b/app/src/main/res/drawable-xxhdpi/ic_systemapp.png
new file mode 100644
index 0000000..0686d74
--- /dev/null
+++ b/app/src/main/res/drawable-xxhdpi/ic_systemapp.png
Binary files differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_watchapp.png b/app/src/main/res/drawable-xxhdpi/ic_watchapp.png
new file mode 100644
index 0000000..dd2373a
--- /dev/null
+++ b/app/src/main/res/drawable-xxhdpi/ic_watchapp.png
Binary files differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_watchface.png b/app/src/main/res/drawable-xxhdpi/ic_watchface.png
new file mode 100644
index 0000000..ef53407
--- /dev/null
+++ b/app/src/main/res/drawable-xxhdpi/ic_watchface.png
Binary files differ
diff --git a/app/src/main/res/drawable/arrow_autofit_height.xml b/app/src/main/res/drawable/arrow_autofit_height.xml
new file mode 100644
index 0000000..aa09643
--- /dev/null
+++ b/app/src/main/res/drawable/arrow_autofit_height.xml
@@ -0,0 +1,41 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:pathData="M12,20h-6a2,2 0,0 1,-2 -2v-12a2,2 0,0 1,2 -2h6"
+      android:strokeLineJoin="round"
+      android:strokeWidth="2"
+      android:fillColor="#00000000"
+      android:strokeColor="#FFFFFFFF"
+      android:strokeLineCap="round"/>
+  <path
+      android:pathData="M18,14v7"
+      android:strokeLineJoin="round"
+      android:strokeWidth="2"
+      android:fillColor="#00000000"
+      android:strokeColor="#FFFFFFFF"
+      android:strokeLineCap="round"/>
+  <path
+      android:pathData="M18,3v7"
+      android:strokeLineJoin="round"
+      android:strokeWidth="2"
+      android:fillColor="#00000000"
+      android:strokeColor="#FFFFFFFF"
+      android:strokeLineCap="round"/>
+  <path
+      android:pathData="M15,18l3,3l3,-3"
+      android:strokeLineJoin="round"
+      android:strokeWidth="2"
+      android:fillColor="#00000000"
+      android:strokeColor="#FFFFFFFF"
+      android:strokeLineCap="round"/>
+  <path
+      android:pathData="M15,6l3,-3l3,3"
+      android:strokeLineJoin="round"
+      android:strokeWidth="2"
+      android:fillColor="#00000000"
+      android:strokeColor="#FFFFFFFF"
+      android:strokeLineCap="round"/>
+</vector>
diff --git a/app/src/main/res/drawable/baseline_bloodtype_24.xml b/app/src/main/res/drawable/baseline_bloodtype_24.xml
new file mode 100644
index 0000000..1c78a42
--- /dev/null
+++ b/app/src/main/res/drawable/baseline_bloodtype_24.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="#7E7E7E"
+    android:viewportHeight="24"
+    android:viewportWidth="24">
+
+    <path android:fillColor="#000000" android:pathData="M12,2c-5.33,4.55 -8,8.48 -8,11.8c0,4.98 3.8,8.2 8,8.2s8,-3.22 8,-8.2C20,10.48 17.33,6.55 12,2zM15,18H9v-2h6V18zM15,13h-2v2h-2v-2H9v-2h2V9h2v2h2V13z"/>
+</vector>
diff --git a/app/src/main/res/drawable/baseline_merge_24.xml b/app/src/main/res/drawable/baseline_merge_24.xml
new file mode 100644
index 0000000..29fb1bf
--- /dev/null
+++ b/app/src/main/res/drawable/baseline_merge_24.xml
@@ -0,0 +1,8 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="#7E7E7E"
+    android:viewportHeight="24"
+    android:viewportWidth="24">
+    <path android:fillColor="#000000" android:pathData="M6.41,21L5,19.59l4.83,-4.83c0.75,-0.75 1.17,-1.77 1.17,-2.83v-5.1L9.41,8.41L8,7l4,-4l4,4l-1.41,1.41L13,6.83v5.1c0,1.06 0.42,2.08 1.17,2.83L19,19.59L17.59,21L12,15.41L6.41,21z"/>
+</vector>
diff --git a/app/src/main/res/drawable/brand_speedtest.xml b/app/src/main/res/drawable/brand_speedtest.xml
new file mode 100644
index 0000000..e18f1e7
--- /dev/null
+++ b/app/src/main/res/drawable/brand_speedtest.xml
@@ -0,0 +1,20 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:pathData="M5.636,19.364a9,9 0,1 1,12.728 0"
+      android:strokeLineJoin="round"
+      android:strokeWidth="2"
+      android:fillColor="#00000000"
+      android:strokeColor="#FFFFFFFF"
+      android:strokeLineCap="round"/>
+  <path
+      android:pathData="M16,9l-4,4"
+      android:strokeLineJoin="round"
+      android:strokeWidth="2"
+      android:fillColor="#00000000"
+      android:strokeColor="#FFFFFFFF"
+      android:strokeLineCap="round"/>
+</vector>
diff --git a/app/src/main/res/drawable/gadgetbridge_img.xml b/app/src/main/res/drawable/gadgetbridge_img.xml
new file mode 100644
index 0000000..757c33e
--- /dev/null
+++ b/app/src/main/res/drawable/gadgetbridge_img.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="200dp"
+    android:height="300dp"
+    android:tint="#A07E7E7E"
+    android:viewportWidth="950"
+    android:viewportHeight="1400">
+    <path
+        android:pathData="M391.439 251.5c-16.53-0.137-32.726-5.052-49-7.576-31.323-4.857-62.605-10.178-94-14.554-11.34-1.581-22.657-3.315-34-4.87-3.544-0.486-9.985-0.271-11.683-4.148-2.661-6.074 3.215-15.79 5.989-20.852 9.17-16.735 19.424-32.9 29.64-49 26.59-41.901 56.624-85.123 95.054-117.08 25.306-21.043 54.596-33.544 87-20.467 13.073 5.276 24.874 14.036 34.155 24.548 7.445 8.431 14.372 18.176 17.816 29 4.549 14.293 11.03 39.431 4.977 53.999-5.502 13.241-20.4 23.207-29.662 34-15.78 18.385-28.437 38.888-40.147 60-5.488 9.893-15.517 24.17-14.96 35.981 0.352 7.455 22.806 13.067 28.82 16.328 21.17 11.476 40.696 28.364 50.689 50.691 18.396 41.106-0.666 100.512-34.69 128.535-10.353 8.529-23.217 13.179-35.998 16.72-35.687 9.887-74.001 0.29-110-1.255-1.397 17.704-4.591 36.596 0.774 54 9.793 31.77 49.168 55.388 82.226 47.402 32.92-7.953 54.022-37.177 77.089-59.294 4.322-4.143 10.976-4.135 15.068-8.032 5.05-4.808 6.756-15.863 10.027-22.076 5.03-9.552 12.956-18.21 19.237-27 38.268-53.553 71.679-110.568 103.719-168 8.826-15.82 17.512-31.797 25.613-48 2.81-5.62 4.46-16.206 8.76-20.486 4.169-4.15 13.16-3.887 18.487-6.306 37.106-16.857 75.799-10.058 105 18.796 6.41 6.338 13.31 12.628 17.1 20.996 16.27 36.011-0.86 79.314-13.68 113-22.504 59.17-57.511 114.154-93.395 166-5.988 8.652-6.82 20.868-12.868 28.725-3.998 5.193-12.115 6-16.718 11.29-10.773 12.382-20.651 27.824-34.44 37.135-26.993 18.228-73.226 17.881-101 2.538-26.892-14.858-32.244-45.823-33.624-73.688-0.5-10.078 3.588-22.7-1.375-32m12-379c17.823-3.679 32.752-13.717 51-16.56 38.387-5.984 78.04 16.747 92.547 52.56 7.349 18.142 5.142 37.032 10.453 55m21 344c8.3 11.537 3.86 33.142 7.746 47 11.464 40.89 48.099 57.862 88.254 49.331 56.57-12.018 97.41-63.857 127.67-109.33 6.99-10.508 12.44-24.387 20.96-33.61 2.88-3.113 10.21-7.579 14.05-4.03 5.94 5.48 3.32 26.012 3.32 33.64 0 32.984 0.29 66.192-2.83 99-5.99 62.92-7.24 134.76-42.22 190-11.86 18.727-29.51 34.486-45.95 49.165-32.43 28.959-66.94 55.856-103 80.164-15.844 10.68-34.304 20-48.891 32.28-5.808 4.89-4.11 13.58-4.11 20.39v54c0 5.29 2.608 20.69-2.317 23.97-4.938 3.3-16.896 1.03-22.682 1.03h-342c-6.208 0-19.404 2.49-24.682-1.03-5.401-3.6-2.318-23.03-2.318-28.97 0-21.69 5.279-56.45-8.804-74-71.457-89.076-142.18-180.028-199.62-279-24.185-41.671-53.67-88.059-58.406-137-9.294-96.034 42.012-191.579 91.777-270 12.162-19.166 25.178-38.033 39.064-56 4.758-6.156 9.917-16.188 17.004-19.821 11.35-5.818 31.627 1.813 43.985 1.821m576 99c44.15-9.108 86.54-30.774 128 0.375 7.04 5.292 12.9 11.496 18.08 18.625 6.29 8.669 10.42 17.756 13.34 28 3.2 11.222 6.24 23.209 5.49 35-1.67 26.166-15.3 46.19-24.44 70-3.25 8.446-3.75 18.156-5.47 27m-746-75c27.09 12.008 57.317 19.456 86 26.627 12.377 3.094 29.002 4.03 40 10.373m-16 622c-12.048 6.43-35.402 2-49 2-3.938 0-14.254-1.83-16.972 1.6-1.8 2.28-1.028 6.71-1.028 9.4v149c0 2.88-0.841 7.84 2.318 9.4 9.22 4.53 29.38 0.6 39.682 0.6h458c11.273 0 27.059 2.89 37.852-0.32 4.03-1.19 3.147-6.39 3.148-9.68v-149c0-2.69 0.772-7.12-1.028-9.4-3.055-3.85-16.446-1.6-20.972-1.6-17.678 0-41.071 4-58-1m-324 40.42c20.064-2.63 41.76-0.42 62-0.42h185c12.293 0 26.03-1.26 38 2.06 8.262 2.29 11.04 9.28 12.385 16.94 2.832 16.13 2.326 37.74 0.329 54-0.937 7.63-2.603 15.93-10.714 18.97-8.04 3.01-17.597 2.03-26 2.03h-251c-8.672 0-18.87 1.13-26.985-2.65-11.262-5.25-9.015-23.28-9.015-33.35 0-13.97-5.538-43.55 7.105-53.2 5.35-4.08 12.57-3.54 18.895-4.38m-63 131.58v127l1.028 10.4 20.972 1.6h343l20.397-1.6 1.603-10.4v-127"
+        android:strokeWidth="15"
+        android:strokeColor="#FFFFFF" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_access_alarms.xml b/app/src/main/res/drawable/ic_access_alarms.xml
new file mode 100644
index 0000000..a3b8e3b
--- /dev/null
+++ b/app/src/main/res/drawable/ic_access_alarms.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="#000000"
+        android:pathData="M15.87 15.25l-3.37-2V8.72c0-0.4-0.32-0.72-0.72-0.72h-0.06C11.32 8 11 8.32 11 8.72v4.72c0 0.35 0.18 0.68 0.49 0.86l3.65 2.19c0.34 0.2 0.78 0.1 0.98-0.24 0.21-0.35 0.1-0.8-0.25-1zm5.31-10.24L18.1 2.45c-0.42-0.35-1.05-0.3-1.41 0.13C16.34 3 16.4 3.63 16.82 3.99l3.07 2.56c0.42 0.35 1.05 0.3 1.41-0.13 0.36-0.42 0.3-1.05-0.12-1.41zM4.1 6.55l3.07-2.56C7.6 3.63 7.66 3 7.3 2.58 6.95 2.15 6.32 2.1 5.9 2.45L2.82 5.01C2.4 5.37 2.34 6 2.7 6.42 3.05 6.85 3.68 6.9 4.1 6.55zM12 4c-4.97 0-9 4.03-9 9s4.03 9 9 9 9-4.03 9-9-4.03-9-9-9zm0 16c-3.86 0-7-3.14-7-7s3.14-7 7-7 7 3.14 7 7-3.14 7-7 7z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_access_time.xml b/app/src/main/res/drawable/ic_access_time.xml
new file mode 100644
index 0000000..cfe3228
--- /dev/null
+++ b/app/src/main/res/drawable/ic_access_time.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="#000000"
+        android:pathData="M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8zM11.78 7h-0.06C11.32 7 11 7.32 11 7.72v4.72c0 0.35 0.18 0.68 0.49 0.86l4.15 2.49c0.34 0.2 0.78 0.1 0.98-0.24 0.21-0.34 0.1-0.79-0.25-0.99l-3.87-2.3V7.72c0-0.4-0.32-0.72-0.72-0.72z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_action_find_lost_device.xml b/app/src/main/res/drawable/ic_action_find_lost_device.xml
new file mode 100644
index 0000000..d13a11d
--- /dev/null
+++ b/app/src/main/res/drawable/ic_action_find_lost_device.xml
@@ -0,0 +1,10 @@
+<!--drawable/watch-vibrate.xml-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24.0"
+    android:viewportHeight="24.0">
+    <path
+        android:fillColor="#7E7E7E"
+        android:pathData="M3 17V7h2v10H3m16 0V7h2v10h-2m3-8h2v6h-2V9M0 15V9h2v6H0m17.96-3.03c0 1.9-0.89 3.6-2.28 4.7l-0.71 4.28H9l-0.73-4.28C6.88 15.57 6 13.87 6 11.97c0-1.9 0.88-3.6 2.27-4.69L9 3h5.97l0.71 4.28c1.39 1.09 2.28 2.79 2.28 4.69m-10.46 0c0 2.48 2 4.49 4.47 4.49a4.5 4.5 0 0 0 4.49-4.49c0-2.47-2.01-4.47-4.49-4.47a4.47 4.47 0 0 0-4.47 4.47z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_action_manage_apps.xml b/app/src/main/res/drawable/ic_action_manage_apps.xml
new file mode 100644
index 0000000..37e9cdf
--- /dev/null
+++ b/app/src/main/res/drawable/ic_action_manage_apps.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="45sp"
+    android:height="45sp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="25"
+    android:viewportHeight="25">
+    <path
+        android:fillColor="#000000"
+        android:pathData="M4 8h4V4H4v4zm6 12h4v-4h-4v4zm-6 0h4v-4H4v4zm0-6h4v-4H4v4zm6 0h4v-4h-4v4zm6-10v4h4V4h-4zm-6 4h4V4h-4v4zm6 6h4v-4h-4v4zm0 6h4v-4h-4v4z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_activity_archery.xml b/app/src/main/res/drawable/ic_activity_archery.xml
new file mode 100644
index 0000000..d23dff0
--- /dev/null
+++ b/app/src/main/res/drawable/ic_activity_archery.xml
@@ -0,0 +1,15 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="25dp"
+    android:height="25dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="25"
+    android:viewportHeight="25">
+  <path
+      android:pathData="M12.5,2.75A9.75,9.75 0,0 0,2.75 12.5A9.75,9.75 0,0 0,12.5 22.25A9.75,9.75 0,0 0,22.25 12.5A9.75,9.75 0,0 0,19.963 6.229L19.43,6.762A9,9 0,0 1,21.5 12.5A9,9 0,0 1,12.5 21.5A9,9 0,0 1,3.5 12.5A9,9 0,0 1,12.5 3.5A9,9 0,0 1,18.381 5.689L18.912,5.158A9.75,9.75 0,0 0,12.5 2.75zM12.5,5.5A7,7 0,0 0,5.5 12.5A7,7 0,0 0,12.5 19.5A7,7 0,0 0,19.5 12.5A7,7 0,0 0,18.008 8.184L17.475,8.717A6.25,6.25 0,0 1,18.75 12.5A6.25,6.25 0,0 1,12.5 18.75A6.25,6.25 0,0 1,6.25 12.5A6.25,6.25 0,0 1,12.5 6.25A6.25,6.25 0,0 1,16.43 7.641L16.963,7.107A7,7 0,0 0,12.5 5.5zM12.5,8.25A4.25,4.25 0,0 0,8.25 12.5A4.25,4.25 0,0 0,12.5 16.75A4.25,4.25 0,0 0,16.75 12.5A4.25,4.25 0,0 0,16.041 10.15L15.496,10.695A3.5,3.5 0,0 1,16 12.5A3.5,3.5 0,0 1,12.5 16A3.5,3.5 0,0 1,9 12.5A3.5,3.5 0,0 1,12.5 9A3.5,3.5 0,0 1,14.465 9.605L15.002,9.068A4.25,4.25 0,0 0,12.5 8.25z"
+      android:strokeWidth="1.0045"
+      android:fillColor="#000000"/>
+  <path
+      android:pathData="m20.275,2.754 l0.239,1.175 -8.365,8.365 0.707,0.707 8.365,-8.365 1.175,0.239L24.169,3.102 22.994,2.863 23.108,2.749 22.401,2.042 22.287,2.156 22.048,0.981Z"
+      android:strokeWidth="1.04456"
+      android:fillColor="#000000"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_activity_badmington.xml b/app/src/main/res/drawable/ic_activity_badmington.xml
new file mode 100644
index 0000000..58f839e
--- /dev/null
+++ b/app/src/main/res/drawable/ic_activity_badmington.xml
@@ -0,0 +1,32 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:pathData="m19.9502,3.6501c-2.34,-2.34 -6.62,-1.87 -9.55,1.06 -1.6,1.6 -2.52,3.87 -2.54,5.46 -0.02,1.58 0.26,3.89 -1.35,5.5l-4.24,4.24 1.42,1.42 4.24,-4.24c1.61,-1.61 3.92,-1.33 5.5,-1.35 1.58,-0.02 3.86,-0.94 5.46,-2.54 2.92,-2.93 3.4,-7.21 1.06,-9.55zM10.7502,12.8401c-1.53,-1.53 -1.05,-4.61 1.06,-6.72 2.11,-2.11 5.18,-2.59 6.72,-1.06 1.53,1.53 1.05,4.61 -1.06,6.72 -2.11,2.11 -5.18,2.59 -6.72,1.06z"
+      android:strokeWidth="1"
+      android:fillColor="#00000000"
+      android:strokeColor="@color/secondarytext"/>
+  <path
+      android:pathData="m21.6116,20.9658c0.093,0 0.1825,0.0367 0.2475,0.1032 0.1369,0.1365 0.1369,0.3587 0,0.4952 -0.0649,0.0648 -0.1544,0.1015 -0.2475,0.1015 -0.093,0 -0.1825,-0.0367 -0.2475,-0.1032 -0.1369,-0.1365 -0.1369,-0.3587 0,-0.4952 0.0649,-0.0648 0.1544,-0.1015 0.2475,-0.1015m0,-0.35c-0.179,0 -0.3598,0.0683 -0.4967,0.2048 -0.2738,0.273 -0.2738,0.7157 0,0.9905 0.1369,0.1365 0.3177,0.2048 0.4967,0.2048 0.179,0 0.3598,-0.0683 0.4967,-0.2048 0.2738,-0.273 0.2738,-0.7157 0,-0.9905 -0.1369,-0.1365 -0.3177,-0.2048 -0.4967,-0.2048z"
+      android:strokeWidth="1"
+      android:fillColor="#00000000"
+      android:strokeColor="@color/secondarytext"/>
+  <path
+      android:pathData="M21.1785,21.867 L15.9561,19.1533"
+      android:strokeLineJoin="miter"
+      android:strokeWidth="1"
+      android:fillColor="#00000000"
+      android:strokeColor="@color/secondarytext"
+      android:fillType="evenOdd"
+      android:strokeLineCap="butt"/>
+  <path
+      android:pathData="M22.2386,21.0049 L18.9929,15.9373"
+      android:strokeLineJoin="miter"
+      android:strokeWidth="1"
+      android:fillColor="#00000000"
+      android:strokeColor="@color/secondarytext"
+      android:fillType="evenOdd"
+      android:strokeLineCap="butt"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_activity_basketball.xml b/app/src/main/res/drawable/ic_activity_basketball.xml
new file mode 100644
index 0000000..bdef5e8
--- /dev/null
+++ b/app/src/main/res/drawable/ic_activity_basketball.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="@color/secondarytext">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M12,2C6.48,2 2,6.48 2,12c0,5.52 4.48,10 10,10s10,-4.48 10,-10C22,6.48 17.52,2 12,2zM5.23,7.75C6.1,8.62 6.7,9.74 6.91,11H4.07C4.22,9.82 4.63,8.72 5.23,7.75zM4.07,13h2.84c-0.21,1.26 -0.81,2.38 -1.68,3.25C4.63,15.28 4.22,14.18 4.07,13zM11,19.93c-1.73,-0.22 -3.29,-1 -4.49,-2.14c1.3,-1.24 2.19,-2.91 2.42,-4.79H11V19.93zM11,11H8.93C8.69,9.12 7.81,7.44 6.5,6.2C7.71,5.06 9.27,4.29 11,4.07V11zM19.93,11h-2.84c0.21,-1.26 0.81,-2.38 1.68,-3.25C19.37,8.72 19.78,9.82 19.93,11zM13,4.07c1.73,0.22 3.29,0.99 4.5,2.13c-1.31,1.24 -2.19,2.92 -2.43,4.8H13V4.07zM13,19.93V13h2.07c0.24,1.88 1.12,3.55 2.42,4.79C16.29,18.93 14.73,19.71 13,19.93zM18.77,16.25c-0.87,-0.86 -1.46,-1.99 -1.68,-3.25h2.84C19.78,14.18 19.37,15.28 18.77,16.25z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_activity_bike_trainer.xml b/app/src/main/res/drawable/ic_activity_bike_trainer.xml
new file mode 100644
index 0000000..9af5caf
--- /dev/null
+++ b/app/src/main/res/drawable/ic_activity_bike_trainer.xml
@@ -0,0 +1,34 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="45sp"
+    android:height="45sp"
+    android:viewportWidth="25"
+    android:viewportHeight="25">
+    <path
+        android:fillColor="@color/secondarytext"
+        android:pathData="M13.748 10.512h4.2v-1.8h-3.2l-1.94-3.27c-0.29-0.5-0.86-0.83-1.46-0.83-0.47 0-0.9 0.19-1.2 0.5l-3.7 3.69c-0.31 0.31-0.5 0.71-0.5 1.21 0 0.63 0.33 1.16 0.85 1.47l3.35 2.03 0.082 2.923h1.8l-0.082-4.423-2.25-1.65 2.32-2.35m2.93-2.7c1 0 1.8-0.8 1.8-1.8s-0.8-1.8-1.8-1.8-1.8 0.8-1.8 1.8 0.8 1.8 1.8 1.8z" />
+    <path
+        android:fillType="evenOdd"
+        android:pathData="M1.866 21.697H21.65L15.206 11.66l3.447-0.064 1.618-1.992M4.463 21.697l12.24-7.672"
+        android:strokeWidth="1"
+        android:strokeColor="@color/secondarytext" />
+    <path
+        android:fillColor="@color/secondarytext"
+        android:pathData="M16.65 17.81a2.5 2.5 0 1 1 5 0 2.5 2.5 0 1 1-5 0"
+        android:strokeWidth="1"
+        android:strokeColor="@color/secondarytext"
+        android:strokeLineCap="square"
+        android:strokeLineJoin="round" />
+    <path
+        android:fillColor="@color/secondarytext"
+        android:fillType="evenOdd"
+        android:pathData="M5.832 11.993l2.74 0.106"
+        android:strokeWidth="1"
+        android:strokeColor="@color/secondarytext" />
+    <path
+        android:fillColor="@color/secondarytext"
+        android:pathData="M8.778 18.293a1 1 0 1 1 2 0 1 1 0 1 1-2 0"
+        android:strokeWidth="1"
+        android:strokeColor="@color/secondarytext"
+        android:strokeLineCap="square"
+        android:strokeLineJoin="round" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_activity_biking.xml b/app/src/main/res/drawable/ic_activity_biking.xml
new file mode 100644
index 0000000..dc5b9a7
--- /dev/null
+++ b/app/src/main/res/drawable/ic_activity_biking.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="45sp"
+    android:height="45sp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="25"
+    android:viewportHeight="25">
+    <path
+        android:fillColor="#000000"
+        android:pathData="M5 20.5A3.5 3.5 0 0 1 1.5 17 3.5 3.5 0 0 1 5 13.5 3.5 3.5 0 0 1 8.5 17 3.5 3.5 0 0 1 5 20.5M5 12a5 5 0 0 0-5 5 5 5 0 0 0 5 5 5 5 0 0 0 5-5 5 5 0 0 0-5-5m9.8-2H19V8.2h-3.2l-1.94-3.27C13.57 4.43 13 4.1 12.4 4.1c-0.47 0-0.9 0.19-1.2 0.5L7.5 8.29C7.19 8.6 7 9 7 9.5c0 0.63 0.33 1.16 0.85 1.47L11.2 13v5H13v-6.5l-2.25-1.65 2.32-2.35m5.93 13a3.5 3.5 0 0 1-3.5-3.5 3.5 3.5 0 0 1 3.5-3.5 3.5 3.5 0 0 1 3.5 3.5 3.5 3.5 0 0 1-3.5 3.5m0-8.5a5 5 0 0 0-5 5 5 5 0 0 0 5 5 5 5 0 0 0 5-5 5 5 0 0 0-5-5m-3-7.2c1 0 1.8-0.8 1.8-1.8S17 1.2 16 1.2 14.2 2 14.2 3 15 4.8 16 4.8z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_activity_billiard_pool.xml b/app/src/main/res/drawable/ic_activity_billiard_pool.xml
new file mode 100644
index 0000000..e2afb76
--- /dev/null
+++ b/app/src/main/res/drawable/ic_activity_billiard_pool.xml
@@ -0,0 +1,15 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="25dp"
+    android:height="25dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="25"
+    android:viewportHeight="25">
+  <path
+      android:pathData="M12.5,4A8.5,8.5 0,0 0,4 12.5A8.5,8.5 0,0 0,12.5 21A8.5,8.5 0,0 0,21 12.5A8.5,8.5 0,0 0,12.5 4zM11,7A4,4 0,0 1,15 11A4,4 0,0 1,11 15A4,4 0,0 1,7 11A4,4 0,0 1,11 7z"
+      android:strokeWidth="1.02129"
+      android:fillColor="#000000"/>
+  <path
+      android:fillColor="#FF000000"
+      android:pathData="m8.846,8.891q0.49,-0.49 1.046,-0.56 0.556,-0.07 1.015,0.389 0.238,0.238 0.315,0.513 0.083,0.271 0.06,0.57 -0.029,0.294 -0.117,0.592 0.345,-0.113 0.682,-0.152 0.331,-0.044 0.634,0.04 0.308,0.079 0.579,0.35 0.497,0.497 0.457,1.124 -0.041,0.616 -0.599,1.173 -0.599,0.599 -1.199,0.646 -0.605,0.041 -1.108,-0.462 -0.271,-0.271 -0.365,-0.574 -0.089,-0.308 -0.056,-0.629 0.034,-0.321 0.137,-0.624 -0.437,0.105 -0.862,0.066 -0.425,-0.05 -0.79,-0.415 -0.304,-0.304 -0.362,-0.654 -0.064,-0.356 0.076,-0.717 0.14,-0.361 0.458,-0.679zM9.233,9.289q-0.287,0.287 -0.325,0.624 -0.039,0.326 0.238,0.602 0.204,0.204 0.431,0.254 0.232,0.044 0.488,-0.013 0.25,-0.062 0.526,-0.162 0.149,-0.393 0.161,-0.746 0.016,-0.359 -0.282,-0.657 -0.276,-0.276 -0.602,-0.238 -0.332,0.033 -0.634,0.336zM11.064,12.674q0.287,0.287 0.674,0.288 0.381,-0.005 0.782,-0.406 0.381,-0.381 0.391,-0.766 0.01,-0.397 -0.294,-0.701 -0.287,-0.287 -0.689,-0.262 -0.407,0.02 -0.905,0.231l-0.117,0.05q-0.189,0.499 -0.167,0.886 0.022,0.376 0.326,0.68z"
+      android:strokeWidth="0.633"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_activity_boating.xml b/app/src/main/res/drawable/ic_activity_boating.xml
new file mode 100644
index 0000000..d78e5c3
--- /dev/null
+++ b/app/src/main/res/drawable/ic_activity_boating.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="292"
+    android:viewportHeight="292">
+    <path
+        android:fillColor="#000000"
+        android:pathData="M267.75,114.63c-14.17,0 -34.1,1.55 -59.22,4.62l-0.82,0.1l-0.62,-0.57L183.53,96.82L80.27,96.58l1.42,5.28l17.91,0.12l4.23,33.72l-8.09,1.52l-58.18,14.88l-18.92,28.64L0,180.36l6.64,14.63H242.06c5.87,-6.48 49.61,-55.49 49.52,-75.5C291.57,118.68 290.4,114.63 267.75,114.63zM132.77,130.55c-8.6,1.47 -16.54,2.88 -22.95,4.06l-1.85,0.34l-0.09,-0.62l-4.25,-32.48h26.55l0.08,0.5l4.24,27.9L132.77,130.55zM169.46,124.57c-9.06,1.39 -18.55,2.91 -29.04,4.67l-1.78,0.3l-0.09,-0.59l-4.34,-27.09h31.71l0.11,0.45l5.36,21.98L169.46,124.57zM198.01,119.83c-4.59,0.59 -7.79,1.21 -11.18,1.87c-2.93,0.57 -5.85,1.14 -9.77,1.72l-1.59,0.24l-5.61,-21.8h12.66l0.17,0.15l19.16,17.33L198.01,119.83z" />
+</vector>
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_activity_bowling.xml b/app/src/main/res/drawable/ic_activity_bowling.xml
new file mode 100644
index 0000000..5220264
--- /dev/null
+++ b/app/src/main/res/drawable/ic_activity_bowling.xml
@@ -0,0 +1,11 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="25dp"
+    android:height="25dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="25"
+    android:viewportHeight="25">
+  <path
+      android:pathData="M12.5,4C7.806,4 4,7.806 4,12.5C4,17.194 7.806,21 12.5,21C17.194,21 21,17.194 21,12.5C21,7.806 17.194,4 12.5,4zM15.871,6.604A1.5,1.5 0,0 1,17.371 8.104A1.5,1.5 0,0 1,15.871 9.604A1.5,1.5 0,0 1,14.371 8.104A1.5,1.5 0,0 1,15.871 6.604zM18.018,10.33A1.5,1.5 0,0 1,19.518 11.83A1.5,1.5 0,0 1,18.018 13.33A1.5,1.5 0,0 1,16.518 11.83A1.5,1.5 0,0 1,18.018 10.33zM14.051,10.365A1.5,1.5 0,0 1,15.551 11.865A1.5,1.5 0,0 1,14.051 13.365A1.5,1.5 0,0 1,12.551 11.865A1.5,1.5 0,0 1,14.051 10.365z"
+      android:strokeWidth="1.02129"
+      android:fillColor="#000000"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_activity_climbing.xml b/app/src/main/res/drawable/ic_activity_climbing.xml
new file mode 100644
index 0000000..6ea1d5e
--- /dev/null
+++ b/app/src/main/res/drawable/ic_activity_climbing.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="45sp"
+    android:height="45sp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="45"
+    android:viewportHeight="45">
+    <path
+        android:fillColor="#000000"
+        android:pathData="m21.9184,3.7795c-1.7312,0 -3.1175,1.3863 -3.1175,3.1175 0,1.7312 1.3863,3.1175 3.1175,3.1175 1.7312,0 3.1175,-1.3863 3.1175,-3.1175 0,-1.7312 -1.3863,-3.1175 -3.1175,-3.1175zM11.3487,9.7812C10.5363,9.8097 9.8221,10.1506 9.5939,10.9207L6.5508,21.9333c-0.613,2.3209 4.2012,3.7931 4.9501,1.4641L14.3851,12.2833C14.8084,10.8183 12.8998,9.7267 11.3487,9.7812ZM18.3174,10.4101c-0.3116,0 -0.5886,0.0521 -0.8656,0.1386 -1.82,0.2388 -2.6137,4.4328 -2.6137,4.4328L8.069,41.5207h3.1175l4.967,-14.0388 4.6627,0.8622 1.7075,7.8072 2.9518,-0.6221 -1.5452,-9.6263 -4.9366,-3.3373 1.2612,-4.9704 1.8698,3.1513 6.6948,-2.0761 3.415,15.1411 0.7134,-0.1454 -3.5165,-15.1851 0.4666,-0.1454 -0.6695,-2.8774 -0.4632,0.142 -0.186,-0.798 -0.6019,0.142 0.1893,0.8419 -4.1285,1.2713 -3.1919,-5.1936c-0.5194,-0.8656 -1.4558,-1.4539 -2.5292,-1.4539zM38.8583,29.9603 L33.5194,34.5419 21.462,37.0981 15.8931,41.5748 38.8583,41.5207z" />
+</vector>
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_activity_cricket.xml b/app/src/main/res/drawable/ic_activity_cricket.xml
new file mode 100644
index 0000000..9e70750
--- /dev/null
+++ b/app/src/main/res/drawable/ic_activity_cricket.xml
@@ -0,0 +1,16 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="@color/secondarytext">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M15.04,12.79l-8.5,-8.5C6.35,4.1 6.09,4 5.83,4S5.32,4.1 5.13,4.29L2.29,7.13c-0.39,0.39 -0.39,1.03 0,1.42l8.5,8.5c0.2,0.2 0.45,0.29 0.71,0.29c0.26,0 0.51,-0.1 0.71,-0.29l2.83,-2.83C15.43,13.82 15.43,13.18 15.04,12.79zM11.5,14.92L4.41,7.83l1.42,-1.42l7.09,7.09L11.5,14.92z"/>
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M14.3412,17.7562l1.4142,-1.4142l4.2426,4.2426l-1.4142,1.4142z"/>
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M18.5,2C16.57,2 15,3.57 15,5.5C15,7.43 16.57,9 18.5,9S22,7.43 22,5.5C22,3.57 20.43,2 18.5,2zM18.5,7C17.67,7 17,6.33 17,5.5S17.67,4 18.5,4S20,4.67 20,5.5S19.33,7 18.5,7z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_activity_curling.xml b/app/src/main/res/drawable/ic_activity_curling.xml
new file mode 100644
index 0000000..15243ce
--- /dev/null
+++ b/app/src/main/res/drawable/ic_activity_curling.xml
@@ -0,0 +1,15 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="25dp"
+    android:height="25dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="25"
+    android:viewportHeight="25">
+  <path
+      android:pathData="M7,12A4,4 0,0 0,3 16A4,4 0,0 0,7 20L17,20A4,4 0,0 0,21 16A4,4 0,0 0,17 12L7,12zM3.5,15L20.5,15L20.5,17L3.5,17L3.5,15z"
+      android:strokeWidth="1.03322"
+      android:fillColor="#000000"/>
+  <path
+      android:pathData="M9.202,7.542 L7.286,11.542h1.5L9.993,9.022v0.02H18.167v-1.5H10.702,9.993Z"
+      android:strokeWidth="1.00035"
+      android:fillColor="#000000"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_activity_dumbbell.xml b/app/src/main/res/drawable/ic_activity_dumbbell.xml
new file mode 100644
index 0000000..2c8b0b4
--- /dev/null
+++ b/app/src/main/res/drawable/ic_activity_dumbbell.xml
@@ -0,0 +1,33 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="512"
+    android:viewportHeight="512">
+
+    <!--
+        Licensed under public domain
+        https://www.svgrepo.com/svg/480564/dumbbell-8
+    -->
+
+    <path
+        android:fillColor="#000000"
+        android:pathData="M141.7,141.55H79.08c-8.49,0 -15.37,6.88 -15.37,15.36v198.18c0,8.48 6.88,15.36 15.37,15.36h62.62c8.49,0 15.37,-6.88 15.37,-15.36V156.91C157.07,148.43 150.19,141.55 141.7,141.55z" />
+
+    <path
+        android:fillColor="#000000"
+        android:pathData="M32.37,189.73H12.8C5.74,189.73 0,195.47 0,202.54v106.93c0,7.07 5.74,12.8 12.8,12.8h19.57c7.07,0 12.8,-5.74 12.8,-12.8V202.54C45.18,195.47 39.44,189.73 32.37,189.73z" />
+
+    <path
+        android:fillColor="#000000"
+        android:pathData="M177.69,228.89h156.62v54.21h-156.62z" />
+
+    <path
+        android:fillColor="#000000"
+        android:pathData="M432.92,141.55h-62.62c-8.49,0 -15.37,6.88 -15.37,15.36v198.18c0,8.48 6.88,15.36 15.37,15.36h62.62c8.49,0 15.37,-6.88 15.37,-15.36V156.91C448.29,148.43 441.41,141.55 432.92,141.55z" />
+
+    <path
+        android:fillColor="#000000"
+        android:pathData="M499.2,189.73h-19.57c-7.07,0 -12.8,5.74 -12.8,12.8v106.93c0,7.07 5.74,12.8 12.8,12.8h19.57c7.07,0 12.8,-5.74 12.8,-12.8V202.54C512,195.47 506.26,189.73 499.2,189.73z" />
+
+</vector>
diff --git a/app/src/main/res/drawable/ic_activity_eliptical.xml b/app/src/main/res/drawable/ic_activity_eliptical.xml
new file mode 100644
index 0000000..d41fe38
--- /dev/null
+++ b/app/src/main/res/drawable/ic_activity_eliptical.xml
@@ -0,0 +1,20 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="45sp"
+    android:height="45sp"
+    android:viewportWidth="25"
+    android:viewportHeight="25">
+    <path
+        android:fillColor="@color/secondarytext"
+        android:pathData="M12.567 5.212c0.97 0.171 1.926-0.505 2.095-1.467 0.176-0.997-0.496-1.923-1.467-2.095-0.988-0.174-1.918 0.47-2.094 1.467-0.17 0.962 0.478 1.92 1.466 2.095M7.174 17.003l1.578-3.742 1.565 2.112-0.942 5.342 1.78 0.314 1.178-6.678-1.565-2.112 1.014-2.575c0.913 1.538 4.426 0.383 6.36-0.39L18.14 7.571c-1.747 0.484-4.498 1.96-4.997 0.57l-0.642-1.565c-0.259-0.614-0.733-1.047-1.348-1.155-0.276-0.049-0.457-0.008-0.733-0.056-1.488 0.34-3.147 6.31-4.386 9.252l-3.093 7.077 1.967 0.144z"
+        android:strokeWidth="0.999999" />
+    <path
+        android:fillType="evenOdd"
+        android:pathData="M21.16 22.869l-3.846-11.3 4.018-4.706"
+        android:strokeWidth="1"
+        android:strokeColor="@color/secondarytext" />
+    <path
+        android:fillType="evenOdd"
+        android:pathData="M22.604 23.183l-6.76-11.358 3.476-6.061m3.284 17.419L7.173 20.181m15.431 3.002L2.612 22.12"
+        android:strokeWidth="1"
+        android:strokeColor="@color/secondarytext" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_activity_exercise.xml b/app/src/main/res/drawable/ic_activity_exercise.xml
new file mode 100644
index 0000000..aeeb811
--- /dev/null
+++ b/app/src/main/res/drawable/ic_activity_exercise.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="45sp"
+    android:height="45sp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="25"
+    android:viewportHeight="25">
+    <path
+        android:fillColor="#000000"
+        android:pathData="M12 2c1.1 0 2 0.9 2 2s-0.9 2-2 2-2-0.9-2-2 0.9-2 2-2zm9 7h-6v13h-2v-6h-2v6H9V9H3V7h18v2z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_activity_free_training.xml b/app/src/main/res/drawable/ic_activity_free_training.xml
new file mode 100644
index 0000000..2cb6b97
--- /dev/null
+++ b/app/src/main/res/drawable/ic_activity_free_training.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="25dp"
+    android:height="25dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="25"
+    android:viewportHeight="25">
+    <path
+        android:fillColor="#000000"
+        android:pathData="M 12 2 C 13.1 2 14 2.9 14 4 C 14 5.1 13.1 6 12 6 C 10.9 6 10 5.1 10 4 C 10 2.9 10.9 2 12 2 Z M 20.644 6.167 L 15.028 7.781 L 15 22 L 13 22 L 13 16 L 11 16 L 11 22 L 9 22 L 9.01 9.26 L 3.94 10.56 L 3.536 8.999 L 20.104 4.573 Z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_activity_frisbee.xml b/app/src/main/res/drawable/ic_activity_frisbee.xml
new file mode 100644
index 0000000..1517045
--- /dev/null
+++ b/app/src/main/res/drawable/ic_activity_frisbee.xml
@@ -0,0 +1,14 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="25dp"
+    android:height="25dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="25"
+    android:viewportHeight="25">
+  <path
+      android:pathData="M12.756,8.485 L16.727,6.553 16.121,5.094 12.88,6.495 10.388,6.12c-0.3,-0.5 -0.84,-0.84 -1.46,-0.84 -0.18,0 -0.34,0.03 -0.5,0.08l-5.42,1.69v5.2h1.8V8.58l2.11,-0.66 -3.91,15.33h1.8l2.87,-8.11 2.33,3.11v5h1.8V16.84L9.318,12.3 10.351,8.294M11.008,5.05c1,0 1.8,-0.8 1.8,-1.8 0,-1 -0.8,-1.8 -1.8,-1.8 -1,0 -1.8,0.8 -1.8,1.8 0,1 0.8,1.8 1.8,1.8z"
+      android:fillColor="#000000"/>
+  <path
+      android:pathData="M17.902,5.203a0.909,3.182 78.761,1 0,6.242 -1.24a0.909,3.182 78.761,1 0,-6.242 1.24z"
+      android:strokeWidth="0.816497"
+      android:fillColor="#000000"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_activity_graphs.xml b/app/src/main/res/drawable/ic_activity_graphs.xml
new file mode 100644
index 0000000..ffd8548
--- /dev/null
+++ b/app/src/main/res/drawable/ic_activity_graphs.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="45sp"
+    android:height="45sp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="25"
+    android:viewportHeight="25">
+    <path
+        android:fillColor="#000000"
+        android:pathData="M10 20h4V4h-4v16zm-6 0h4v-8H4v8zM16 9v11h4V9h-4z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_activity_hiking.xml b/app/src/main/res/drawable/ic_activity_hiking.xml
new file mode 100644
index 0000000..c77e9d8
--- /dev/null
+++ b/app/src/main/res/drawable/ic_activity_hiking.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="45sp"
+    android:height="45sp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="45"
+    android:viewportHeight="45">
+    <path
+        android:fillColor="#000000"
+        android:pathData="m26.0655,3.7795c-1.7337,0 -3.122,1.3883 -3.122,3.122 0,1.7337 1.3883,3.122 3.122,3.122 1.7337,0 3.122,-1.3883 3.122,-3.122 0,-1.7337 -1.3883,-3.122 -3.122,-3.122zM15.4773,9.7018c-0.8139,0.0286 -1.5288,0.3697 -1.7574,1.1411l-3.0475,11.0318c-0.6141,2.3249 4.207,3.7992 4.9572,1.4662l2.8917,-11.1334C18.9051,10.7288 17.0311,9.6472 15.4773,9.7018ZM22.4594,10.4196c-0.3121,0 -0.5894,0.0521 -0.8668,0.1388 -1.7216,0.3323 -2.6174,4.4391 -2.6174,4.4391l-6.7789,26.5772h3.122l4.9741,-14.0589 4.0396,5.3906v8.6683h3.122L27.4538,30.4617l-4.3172,-7.8692 1.263,-4.9775 1.8725,3.1558h7.1886v20.804h0.6535L34.1142,20.7708h0.6197v-3.122h-0.6197v-1.0124h-0.6535v1.0124h-5.0012l-3.4673,-5.7732c-0.5201,-0.8668 -1.4579,-1.456 -2.5328,-1.456z" />
+</vector>
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_activity_hula_hoop.xml b/app/src/main/res/drawable/ic_activity_hula_hoop.xml
new file mode 100644
index 0000000..2bd45f2
--- /dev/null
+++ b/app/src/main/res/drawable/ic_activity_hula_hoop.xml
@@ -0,0 +1,14 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="25dp"
+    android:height="25dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="25"
+    android:viewportHeight="25">
+  <path
+      android:fillColor="#FF000000"
+      android:pathData="m12,2c-1.1,0 -2,0.9 -2,2 0,1.1 0.9,2 2,2 1.1,0 2,-0.9 2,-2C14,2.9 13.1,2 12,2ZM3,7v2h6v4.571c1.563,0.297 3.731,0.466 6,0.467L15,9h6L21,7ZM9,14.488L9,22h2v-6h2v6h2v-7c-2.215,-0.001 -4.352,-0.183 -6,-0.512z"/>
+  <path
+      android:pathData="m15.302,10.783v0.5a8.3,1.5 0,0 1,8.035 1.498,8.3 1.5,0 0,1 -8.301,1.5 8.3,1.5 0,0 1,-8.301 -1.5,8.3 1.5,0 0,1 1.975,-0.967v-0.449a9,2 0,0 0,-2.674 1.416,9 2,0 0,0 9,2 9,2 0,0 0,9 -2,9 2,0 0,0 -8.734,-1.998z"
+      android:strokeWidth="1.28533"
+      android:fillColor="#000000"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_activity_ice_skating.xml b/app/src/main/res/drawable/ic_activity_ice_skating.xml
new file mode 100644
index 0000000..21125a2
--- /dev/null
+++ b/app/src/main/res/drawable/ic_activity_ice_skating.xml
@@ -0,0 +1,18 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="25dp"
+    android:height="25dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="25"
+    android:viewportHeight="25">
+  <path
+      android:pathData="M13.5,5.5c1.09,0 2,-0.92 2,-2 0,-1.12 -0.91,-2 -2,-2 -1.11,0 -2,0.88 -2,2 0,1.08 0.89,2 2,2M9.89,19.38l1,-4.38L13,17v6h2v-7.5l-2.11,-2 0.61,-3C14.79,12 16.79,13 19,13v-2c-1.91,0 -3.5,-1 -4.31,-2.42l-1,-1.58c-0.4,-0.62 -1,-1 -1.69,-1 -0.31,0 -0.5,0.08 -0.81,0.08L6,8.28V13h2V9.58l1.79,-0.7L8.19,17l-4.9,-1 -0.4,2 7,1.38z"
+      android:fillColor="#000000"/>
+  <path
+      android:pathData="m16.248,22.074v0.977c-0.008,0.16 -0.028,0.183 -0.116,0.27 -0.081,0.071 -0.185,0.084 -0.328,0.078h-4.578v0.699H15.828c0.355,-0.003 0.665,-0.036 0.889,-0.246 0.199,-0.241 0.239,-0.411 0.231,-0.793l0.002,-0.984z"
+      android:strokeWidth="1.07471"
+      android:fillColor="#000000"/>
+  <path
+      android:pathData="M3.346,19.328 L2.393,19.115c-0.155,-0.043 -0.172,-0.067 -0.238,-0.171 -0.051,-0.094 -0.042,-0.199 -0.005,-0.337L3.146,14.139 2.463,13.986 1.462,18.478c-0.074,0.348 -0.11,0.656 0.047,0.921 0.192,0.246 0.35,0.322 0.724,0.398l0.96,0.216z"
+      android:strokeWidth="1.07471"
+      android:fillColor="#000000"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_activity_indoor_running.xml b/app/src/main/res/drawable/ic_activity_indoor_running.xml
new file mode 100644
index 0000000..e6aeb49
--- /dev/null
+++ b/app/src/main/res/drawable/ic_activity_indoor_running.xml
@@ -0,0 +1,14 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="25dp"
+    android:height="25dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="25"
+    android:viewportHeight="25">
+  <path
+      android:pathData="M13.5,5.5c1.09,0 2,-0.92 2,-2 0,-1.12 -0.91,-2 -2,-2 -1.11,0 -2,0.88 -2,2 0,1.08 0.89,2 2,2M9.89,19.38l1,-4.38L13,17v6h2v-7.5l-2.11,-2 0.61,-3C14.79,12 16.79,13 19,13v-2c-1.91,0 -3.5,-1 -4.31,-2.42l-1,-1.58c-0.4,-0.62 -1,-1 -1.69,-1 -0.31,0 -0.5,0.08 -0.81,0.08L6,8.28V13h2V9.58l1.79,-0.7L8.19,17l-4.9,-1 -0.4,2 7,1.38z"
+      android:fillColor="#000000"/>
+  <path
+      android:pathData="M21.363,7.109L21.363,8.34L17.529,8.896L17.619,9.615L19.59,9.328L21.332,20.801L19.814,20.807L16.957,22.387L2.035,22.387L2.035,23.674L23.371,23.674L23.371,22.436L23.375,22.436L23.371,22.428L23.371,22.387L23.352,22.387L22.576,20.795L22.268,20.797L20.508,9.195L21.855,9L21.973,9L21.973,7.109L21.363,7.109z"
+      android:strokeWidth="1.07548"
+      android:fillColor="#000000"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_activity_kitesurfing.xml b/app/src/main/res/drawable/ic_activity_kitesurfing.xml
new file mode 100644
index 0000000..6e04c33
--- /dev/null
+++ b/app/src/main/res/drawable/ic_activity_kitesurfing.xml
@@ -0,0 +1,23 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="25sp"
+    android:height="25sp"
+    android:tint="#7E7E7E"
+    android:viewportHeight="568.82"
+    android:viewportWidth="568.82">
+    <path
+        android:fillColor="#000000"
+        android:pathData="M482.6,292.64c55.21,-67.94 49.51,-155.85 43.68,-193.76c-1.61,-10.44 -9.37,-25.13 -16.76,-32.67C471.67,27.7 442.04,11.3 424.35,4.34c-9.83,-3.86 -11.5,0.2 -5.15,8.65c11.62,15.44 27.12,45.55 30.1,98.19c3.37,59.43 -20.72,132 -33.13,164.93c-3.73,9.89 -0.51,23.47 9.02,28.04C461.35,321.48 482.6,292.64 482.6,292.64z"/>
+
+    <path
+        android:fillColor="#000000"
+        android:pathData="M349.79,1.75c-53.17,10.91 -80.2,49.88 -84.01,78.21c-0.52,3.91 0.61,10.14 2.3,13.69c4.48,9.42 14.4,25.89 29.99,27.9c3.89,0.52 9.35,-2.37 12.16,-5.1c15.54,-15.01 64.22,-59.38 111.31,-74.26C421.54,42.2 407.58,-10.1 349.79,1.75z" />
+
+    <path
+        android:fillColor="#000000"
+        android:pathData="M137.16,211.18m-38.31,0a38.31,38.31 0,1 1,76.62 0a38.31,38.31 0,1 1,-76.62 0"/>
+
+    <path
+        android:fillColor="#000000"
+        android:pathData="M93.64,257.84l-50.9,91.48c-7.88,13.96 -2.97,31.75 10.83,39.6l102.88,62.07c3.62,2.06 8.86,6.9 11.22,10.38l7.76,11.43h-0.06c-63.28,4.78 -90.19,16.43 -101.61,25.34c-4.78,3.74 -7.37,10.9 -7.37,15.74v11.81c0,4.85 2.59,12.02 7.37,15.74c11.78,9.2 45.68,24.98 140.21,27.24c4.28,0.1 8.47,0.15 12.56,0.15c96.26,0 139.49,-27.71 153.59,-39.62c2.74,-2.31 4.31,-5.75 4.31,-9.41c0,-3.68 -1.57,-7.11 -4.31,-9.41c-13.41,-11.32 -54.35,-37.82 -144.55,-39.49l-6.61,-0.13l-15.99,-31.81c-2.63,-5.28 -8.3,-12.33 -12.89,-16.05l-75.74,-61.56c-1.54,-1.25 -1.87,-3.67 -0.72,-5.3l5.39,-7.66c3.55,3.63 8.73,6.07 13.59,6.07h112.66c0.74,0 2.15,-0.05 3.55,-0.39l37.33,47.7c1.88,2.41 4.7,3.67 7.53,3.67c2.06,0 4.14,-0.66 5.88,-2.04c4.16,-3.25 4.9,-9.27 1.64,-13.42l-86.06,-109.97c-3.26,-4.16 -9.27,-4.92 -13.42,-1.63c-4.16,3.25 -4.9,9.27 -1.63,13.42l27.23,34.79c-3.11,-0.67 -6.56,-1.03 -10.35,-1.03h-61.95c-7.67,0 -17.57,-2.07 -18.48,-3.7l-19.75,-44.19c-0.03,-0.07 -2.04,-3.7 -5.67,-6.83l11.83,4.66c0.83,1.21 1.29,2.04 1.31,2.07l15.14,33.8l3.37,-4.79c2.31,-3.25 2.73,-7.76 1.21,-11.62l43.44,-20.24c4.82,-2.54 9.95,-8.59 11.68,-13.71l28.43,-76.67c3.67,-9.9 0.49,-20.2 -7.12,-22.99c-7.6,-2.8 -16.79,2.94 -20.52,12.82l-26.3,69.67c-1.4,4.4 -6.43,8.75 -11.13,9.55l-39.55,12.28l-18.77,-10.64C105.78,249.47 97.89,250.32 93.64,257.84z"/>
+
+</vector>
diff --git a/app/src/main/res/drawable/ic_activity_not_measured.xml b/app/src/main/res/drawable/ic_activity_not_measured.xml
new file mode 100644
index 0000000..165c853
--- /dev/null
+++ b/app/src/main/res/drawable/ic_activity_not_measured.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="45sp"
+    android:height="45sp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="25"
+    android:viewportHeight="25">
+    <path
+        android:fillColor="#000000"
+        android:pathData="M12 20a7 7 0 0 1-7-7c0-1.28 0.35-2.5 0.95-3.5l9.55 9.54C14.5 19.65 13.28 20 12 20M3 4L1.75 5.27 4.5 8.03C3.55 9.45 3 11.16 3 13a9 9 0 0 0 9 9c1.84 0 3.55-0.55 5-1.5l2.5 2.5 1.25-1.27L13.04 14 3 4m8 5.44l2 2V8h-2m4-7H9v2h6m4.04 1.55l-1.42 1.42C16.07 4.74 14.12 4 12 4c-1.83 0-3.53 0.55-4.95 1.5L8.5 6.94C9.53 6.35 10.73 6 12 6a7 7 0 0 1 7 7c0 1.27-0.35 2.47-0.94 3.5l1.44 1.44C20.45 16.53 21 14.83 21 13c0-2.12-0.74-4.07-1.97-5.61l1.42-1.42-1.41-1.42z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_activity_obstacle_race.xml b/app/src/main/res/drawable/ic_activity_obstacle_race.xml
new file mode 100644
index 0000000..1b32d04
--- /dev/null
+++ b/app/src/main/res/drawable/ic_activity_obstacle_race.xml
@@ -0,0 +1,17 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="25dp"
+    android:height="25dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="25"
+    android:viewportHeight="25">
+  <path
+      android:pathData="m13.5,5.5c1.09,0 2,-0.92 2,-2 0,-1.12 -0.91,-2 -2,-2 -1.11,0 -2,0.88 -2,2 0,1.08 0.89,2 2,2m-3.61,13.88 l1,-4.38 3.246,1.091 3.258,2.97 1.242,-1.477L15.417,14.553 12.89,13.5 13.5,10.5C14.79,12 16.79,13 19,13V11C17.09,11 15.5,10 14.69,8.58L13.69,7C13.29,6.38 12.69,6 12,6 11.69,6 11.5,6.08 11.19,6.08L6,8.28V13H8V9.58L9.79,8.88 8.19,17 3.29,16 2.89,18Z"
+      android:fillColor="#000000"/>
+  <path
+      android:pathData="M12.045,17.311h1.856v5.985h-1.856z"
+      android:fillColor="#000000"/>
+  <path
+      android:pathData="M6.629,22.689h7.879v1.174h-7.879z"
+      android:strokeWidth="1.06034"
+      android:fillColor="#000000"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_activity_outdoor_walking.xml b/app/src/main/res/drawable/ic_activity_outdoor_walking.xml
new file mode 100644
index 0000000..c45b081
--- /dev/null
+++ b/app/src/main/res/drawable/ic_activity_outdoor_walking.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="45sp"
+    android:height="45sp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="45"
+    android:viewportHeight="45">
+    <path
+        android:fillColor="#000000"
+        android:pathData="M 22.651 3.58 C 20.926 3.761 19.691 5.286 19.872 7.011 C 20.054 8.734 21.579 9.971 23.304 9.789 C 25.027 9.608 26.263 8.082 26.082 6.358 C 25.901 4.634 24.374 3.398 22.651 3.58 Z M 21.173 10.628 C 20.863 10.66 20.593 10.741 20.327 10.855 C 18.649 11.367 18.187 15.545 18.187 15.545 L 14.6 41.032 L 17.527 41.026 L 20.805 27.857 L 25.387 32.795 L 26.158 40.986 L 29.398 41.09 L 28.236 30.038 L 23.12 22.663 L 23.856 17.58 L 26.048 20.523 L 33.197 19.772 L 35.372 40.462 L 35.892 40.373 L 33.847 19.704 L 34.463 19.639 L 34.137 16.534 L 33.52 16.599 L 33.414 15.591 L 32.765 15.66 L 32.871 16.667 L 27.896 17.19 L 23.845 11.811 C 23.237 11.003 22.243 10.515 21.173 10.628 Z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_activity_pilates.xml b/app/src/main/res/drawable/ic_activity_pilates.xml
new file mode 100644
index 0000000..7f89263
--- /dev/null
+++ b/app/src/main/res/drawable/ic_activity_pilates.xml
@@ -0,0 +1,14 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="25dp"
+    android:height="25dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="25"
+    android:viewportHeight="25">
+  <path
+      android:pathData="M14.356,9.471 L19.887,7.805 19.032,6.592 12.327,8.432 7.26,10.363c-0.595,0.345 -0.988,1.024 -0.988,1.738 0,0.56 0.226,1.071 0.595,1.429l4.393,4.405c0.369,0.369 0.845,0.595 1.44,0.595 0.75,0 1.381,-0.393 1.75,-1.012l3.136,-3.723 5.271,-3.03 -0.72,-1.764 -6.337,2.652 -2.684,2.413 -2.798,-2.762M7.105,7.815c0,-1.19 -0.952,-2.143 -2.143,-2.143 -1.19,0 -2.143,0.952 -2.143,2.143 0,1.19 0.952,2.143 2.143,2.143 1.19,0 2.143,-0.952 2.143,-2.143z"
+      android:strokeWidth="1.19048"
+      android:fillColor="#000000"/>
+  <path
+      android:pathData="M1.023,18.977h23.75v1.174h-23.75z"
+      android:fillColor="#000000"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_activity_pingpong.xml b/app/src/main/res/drawable/ic_activity_pingpong.xml
new file mode 100644
index 0000000..b67c64f
--- /dev/null
+++ b/app/src/main/res/drawable/ic_activity_pingpong.xml
@@ -0,0 +1,90 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:pathData="M8.9746,3.6387A4.9498,4.9497 70,0 0,7.3242 3.9336A4.9498,4.9497 70,0 0,4.0684 8.875A4.9498,4.9497 70,0 0,7.877 13.4063L12.9648,11.5645A4.9498,4.9497 70,0 0,12.9902 5.6445A4.9498,4.9497 70,0 0,8.9746 3.6387zM6.5664,6.8301A1.5,1.5 0,0 1,8.0664 8.3301A1.5,1.5 0,0 1,6.5664 9.8301A1.5,1.5 0,0 1,5.0664 8.3301A1.5,1.5 0,0 1,6.5664 6.8301z"
+      android:strokeWidth="1"
+      android:fillColor="@color/secondarytext"
+      android:strokeColor="@color/secondarytext"
+      android:strokeLineCap="square"/>
+  <path
+      android:pathData="M9.9193,14.1876 L11.414,18.0784 13.2716,17.4023 11.8968,13.4432"
+      android:strokeLineJoin="miter"
+      android:strokeWidth="1"
+      android:fillColor="#00000000"
+      android:strokeColor="@color/secondarytext"
+      android:fillType="evenOdd"
+      android:strokeLineCap="butt"/>
+  <path
+      android:pathData="m12.968,11.5527c-1.3829,1.738 -0.963,1.9777 -1.03,2.1856"
+      android:strokeLineJoin="miter"
+      android:strokeWidth="1"
+      android:fillColor="#00000000"
+      android:strokeColor="@color/secondarytext"
+      android:fillType="evenOdd"
+      android:strokeLineCap="butt"/>
+  <path
+      android:pathData="m7.8825,13.4037c2.1539,0.4508 2.0287,0.9325 2.2119,1.0492"
+      android:strokeLineJoin="miter"
+      android:strokeWidth="1"
+      android:fillColor="#00000000"
+      android:strokeColor="@color/secondarytext"
+      android:fillType="evenOdd"
+      android:strokeLineCap="butt"/>
+  <path
+      android:pathData="m12.968,11.5527 l-5.0856,1.851"
+      android:strokeLineJoin="miter"
+      android:strokeWidth="1"
+      android:fillColor="#00000000"
+      android:strokeColor="@color/secondarytext"
+      android:fillType="evenOdd"
+      android:strokeLineCap="butt"/>
+  <path
+      android:pathData="M13.5724,10.489 L6.7542,12.9706"
+      android:strokeLineJoin="miter"
+      android:strokeWidth="1"
+      android:fillColor="#00000000"
+      android:strokeColor="@color/secondarytext"
+      android:fillType="evenOdd"
+      android:strokeLineCap="butt"/>
+  <path
+      android:pathData="m12.592,11.587a4.9498,4.9497 110.0081,0 1,-0.0058 -5.9186,4.9498 4.9497,110.0081 0,1 5.6712,-1.6931 4.9498,4.9497 110.0081,0 1,3.2397 4.9532,4.9498 4.9497,110.0081 0,1 -3.8239,4.5176"
+      android:strokeWidth="1"
+      android:fillColor="#00000000"
+      android:strokeColor="@color/secondarytext"
+      android:strokeLineCap="square"/>
+  <path
+      android:pathData="m13.6568,13.5029 l-1.3593,3.9365 1.8576,0.6761 1.5002,-3.9281"
+      android:strokeLineJoin="miter"
+      android:strokeWidth="1"
+      android:fillColor="#00000000"
+      android:strokeColor="@color/secondarytext"
+      android:fillType="evenOdd"
+      android:strokeLineCap="butt"/>
+  <path
+      android:pathData="m17.6826,13.4392c-2.1765,0.4425 -2.009,0.896 -2.1939,1.0122"
+      android:strokeLineJoin="miter"
+      android:strokeWidth="1"
+      android:fillColor="#00000000"
+      android:strokeColor="@color/secondarytext"
+      android:fillType="evenOdd"
+      android:strokeLineCap="butt"/>
+  <path
+      android:pathData="m12.5971,11.5883c1.3602,1.7298 0.9547,2.0183 1.02,2.2256"
+      android:strokeLineJoin="miter"
+      android:strokeWidth="1"
+      android:fillColor="#00000000"
+      android:strokeColor="@color/secondarytext"
+      android:fillType="evenOdd"
+      android:strokeLineCap="butt"/>
+  <path
+      android:pathData="M17.6826,13.4392 L12.5971,11.5883"
+      android:strokeLineJoin="miter"
+      android:strokeWidth="1"
+      android:fillColor="#00000000"
+      android:strokeColor="@color/secondarytext"
+      android:fillType="evenOdd"
+      android:strokeLineCap="butt"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_activity_rock_climbing.xml b/app/src/main/res/drawable/ic_activity_rock_climbing.xml
new file mode 100644
index 0000000..f499e1d
--- /dev/null
+++ b/app/src/main/res/drawable/ic_activity_rock_climbing.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="45dp"
+    android:height="45dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="45"
+    android:viewportHeight="45">
+  <path
+      android:pathData="m21.918,3.78c-1.731,0 -3.118,1.386 -3.118,3.118 0,1.731 1.386,3.118 3.118,3.118 1.731,0 3.118,-1.386 3.118,-3.118 0,-1.731 -1.386,-3.118 -3.118,-3.118zM11.349,9.781C10.536,9.81 9.822,10.151 9.594,10.921L6.551,21.933c-0.613,2.321 4.201,3.793 4.95,1.464l2.884,-11.114c0.423,-1.465 -1.485,-2.557 -3.036,-2.502zM18.317,10.41c-0.312,0 -0.589,0.052 -0.866,0.139 -1.82,0.239 -2.614,4.433 -2.614,4.433l-2.715,13.063 4.052,12.985 3.199,-1.228 -3.221,-11.5 4.826,-0.775 2.363,7.234 2.624,-1.113 -2.446,-9.626 -5.018,-0.471 1.752,-5.953 3.999,2.332 4.812,-2.895 -1.226,-2.639 -3.146,1.844L20.846,11.864c-0.519,-0.866 -1.456,-1.454 -2.529,-1.454zM38.858,29.96 L38.818,4.143 30.079,4.223 27.439,10.934 30.943,18.349 29.482,20.019 29.317,24.488 26.055,28.006 26.603,34.446 21.053,37.344 20.478,41.411L38.858,41.521Z"
+      android:fillColor="#000000"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_activity_rope_jump.xml b/app/src/main/res/drawable/ic_activity_rope_jump.xml
new file mode 100644
index 0000000..d1182c2
--- /dev/null
+++ b/app/src/main/res/drawable/ic_activity_rope_jump.xml
@@ -0,0 +1,14 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="45sp"
+    android:height="45sp"
+    android:viewportWidth="25"
+    android:viewportHeight="25">
+    <path
+        android:fillColor="@color/secondarytext"
+        android:pathData="M18.985 12.41l0.753-1.361-2.788-1.605-2.837-4.116c-0.3-0.5-1.912-0.49-2.532-0.501-0.64-0.013-1.508 0.007-2.009 0.5l-2.16 3.928-3.255 2.236 1.033 0.983 3.52-1.928 0.862-1.9-0.031 13.46 1.785 0.013V13.8h0.956l0.055 5.49h1.8l-0.055-5.49 0.031-5.156 1.324 1.737m-3.749-5.986c1 0 1.8-0.8 1.8-1.8s-0.8-1.8-1.8-1.8-1.8 0.8-1.8 1.8 0.8 1.8 1.8 1.8z" />
+    <path
+        android:fillType="evenOdd"
+        android:pathData="M4.484 12.56s3.907 7.967 7.73 8.362c3.824 0.396 7.637-8.779 7.637-8.779"
+        android:strokeWidth="0.5"
+        android:strokeColor="@color/secondarytext" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_activity_rowing.xml b/app/src/main/res/drawable/ic_activity_rowing.xml
new file mode 100644
index 0000000..f08b485
--- /dev/null
+++ b/app/src/main/res/drawable/ic_activity_rowing.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="@color/secondarytext">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M8.5,14.5L4,19l1.5,1.5L9,17h2L8.5,14.5zM15,1c-1.1,0 -2,0.9 -2,2s0.9,2 2,2s2,-0.9 2,-2S16.1,1 15,1zM21,21.01L18,24l-2.99,-3.01V19.5l-7.1,-7.09C7.6,12.46 7.3,12.48 7,12.48v-2.16c1.66,0.03 3.61,-0.87 4.67,-2.04l1.4,-1.55C13.42,6.34 14.06,6 14.72,6h0.03C15.99,6.01 17,7.02 17,8.26v5.75c0,0.84 -0.35,1.61 -0.92,2.16l-3.58,-3.58v-2.27c-0.63,0.52 -1.43,1.02 -2.29,1.39L16.5,18H18L21,21.01z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_activity_running.xml b/app/src/main/res/drawable/ic_activity_running.xml
new file mode 100644
index 0000000..d97b249
--- /dev/null
+++ b/app/src/main/res/drawable/ic_activity_running.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="45sp"
+    android:height="45sp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="25"
+    android:viewportHeight="25">
+    <path
+        android:fillColor="#000000"
+        android:pathData="M13.5 5.5c1.09 0 2-0.92 2-2 0-1.12-0.91-2-2-2-1.11 0-2 0.88-2 2 0 1.08 0.89 2 2 2M9.89 19.38l1-4.38L13 17v6h2v-7.5l-2.11-2 0.61-3C14.79 12 16.79 13 19 13v-2c-1.91 0-3.5-1-4.31-2.42l-1-1.58c-0.4-0.62-1-1-1.69-1-0.31 0-0.5 0.08-0.81 0.08L6 8.28V13h2V9.58l1.79-0.7L8.19 17l-4.9-1-0.4 2 7 1.38z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_activity_sailing.xml b/app/src/main/res/drawable/ic_activity_sailing.xml
new file mode 100644
index 0000000..fe8a1ff
--- /dev/null
+++ b/app/src/main/res/drawable/ic_activity_sailing.xml
@@ -0,0 +1,12 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="377.5"
+    android:viewportHeight="377.5">
+      
+    <path
+        android:fillColor="#000000"
+        android:pathData="M239.19,1.5c-4.57,-2.22 -8.38,0.35 -8.38,5.42v267.38c53.07,-23.04 92.96,-75.99 92.96,-137.75C323.76,77.12 289.17,25.79 239.19,1.5z M206.53,62.06L74.52,274.31h136.97l-0.09,-210.86C211.39,58.36 209.22,57.74 206.53,62.06z M2.07,316.4l15.04,35.07c5.99,14 23.2,25.34 38.42,25.34h266.45c15.23,0 32.42,-11.35 38.42,-25.34l15.04,-35.07c5.99,-14.01 -1.48,-25.34 -16.71,-25.34H18.79C3.55,291.07 -3.92,302.4 2.07,316.4z" />
+    
+</vector>
diff --git a/app/src/main/res/drawable/ic_activity_sleep.xml b/app/src/main/res/drawable/ic_activity_sleep.xml
new file mode 100644
index 0000000..aa65ac6
--- /dev/null
+++ b/app/src/main/res/drawable/ic_activity_sleep.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24sp"
+    android:height="24sp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="25"
+    android:viewportHeight="25">
+    <path
+        android:fillColor="#000"
+        android:pathData="M23 12h-6v-2l3.39-4H17V4h6v2l-3.38 4H23v2m-8 4H9v-2l3.39-4H9V8h6v2l-3.38 4H15v2m-8 4H1v-2l3.39-4H1v-2h6v2l-3.38 4H7v2z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_activity_snorkeling.xml b/app/src/main/res/drawable/ic_activity_snorkeling.xml
new file mode 100644
index 0000000..8c2eae6
--- /dev/null
+++ b/app/src/main/res/drawable/ic_activity_snorkeling.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="512"
+    android:viewportHeight="512">
+    <path
+        android:fillColor="#000000"
+        android:pathData="M475.74,217.6c-2.79,-6.38 -6.07,-12.48 -9.82,-18.27V0h-38.4v161.05c-19.94,-12.81 -43.66,-20.25 -69.12,-20.25H153.6c-52.49,0 -97.59,31.6 -117.34,76.8H0V320h36.26c19.75,45.2 64.85,76.8 117.34,76.8h51.2l51.2,-64l51.2,64h51.2c25.46,0 49.18,-7.43 69.12,-20.25v20.89c0,41.99 -34.17,76.16 -76.16,76.16s-76.16,-34.16 -76.16,-76.16v-0.64h-38.4v0.64c0,63.17 51.39,114.56 114.56,114.56s114.56,-51.39 114.56,-114.56v-59.17c3.74,-5.78 7.03,-11.89 9.82,-18.27H512V217.6H475.74zM64,268.8c0,-49.41 40.19,-89.6 89.6,-89.6h48.09L73,307.9C67.24,296.08 64,282.81 64,268.8zM256,271.33L186.34,358.4H153.6c-21.45,0 -41.16,-7.58 -56.6,-20.2L256,179.2h102.4c14.01,0 27.28,3.24 39.1,9L281.94,303.75L256,271.33zM358.4,358.4h-32.74l-19.58,-24.48L427.8,212.2C440.42,227.64 448,247.35 448,268.8C448,318.21 407.81,358.4 358.4,358.4z" />
+</vector>
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_activity_soccer.xml b/app/src/main/res/drawable/ic_activity_soccer.xml
new file mode 100644
index 0000000..c6dc62e
--- /dev/null
+++ b/app/src/main/res/drawable/ic_activity_soccer.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="@color/secondarytext">
+  <path
+      android:fillColor="@color/secondarytext"
+      android:pathData="M12,2C6.48,2 2,6.48 2,12c0,5.52 4.48,10 10,10s10,-4.48 10,-10C22,6.48 17.52,2 12,2zM13,5.3l1.35,-0.95c1.82,0.56 3.37,1.76 4.38,3.34l-0.39,1.34l-1.35,0.46L13,6.7V5.3zM9.65,4.35L11,5.3v1.4L7.01,9.49L5.66,9.03L5.27,7.69C6.28,6.12 7.83,4.92 9.65,4.35zM7.08,17.11l-1.14,0.1C4.73,15.81 4,13.99 4,12c0,-0.12 0.01,-0.23 0.02,-0.35l1,-0.73L6.4,11.4l1.46,4.34L7.08,17.11zM14.5,19.59C13.71,19.85 12.87,20 12,20s-1.71,-0.15 -2.5,-0.41l-0.69,-1.49L9.45,17h5.11l0.64,1.11L14.5,19.59zM14.27,15H9.73l-1.35,-4.02L12,8.44l3.63,2.54L14.27,15zM18.06,17.21l-1.14,-0.1l-0.79,-1.37l1.46,-4.34l1.39,-0.47l1,0.73C19.99,11.77 20,11.88 20,12C20,13.99 19.27,15.81 18.06,17.21z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_activity_stacked.xml b/app/src/main/res/drawable/ic_activity_stacked.xml
new file mode 100644
index 0000000..c878177
--- /dev/null
+++ b/app/src/main/res/drawable/ic_activity_stacked.xml
@@ -0,0 +1,6 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="26dp" android:height="26dp" android:tint="#7E7E7E" android:viewportWidth="26" android:viewportHeight="26">
+    <path android:pathData="M11.711 16.328a4.854 7.133 75.802 0 1-5.603-5.02 4.854 7.133 75.802 0 1 8.19-5.021 4.854 7.133 75.802 0 1 5.603 5.02 4.854 7.133 75.802 0 1-8.19 5.02m9.775-2.25l4.519-2.77-3.091-2.77 1.011-3.924h-5.39l-3.091-2.77-4.519 2.77h-5.39L4.523 8.537l-4.518 2.77 3.09 2.77L2.086 18h5.39l3.091 2.77L15.085 18h5.391z" android:strokeWidth="1.01966" android:fillColor="#000000"/>
+    <path android:pathData="M4.273 11.632h4.094l3.48-4.207 1.037 4.593 2.684-2.282h7.458l-0.86 1.16-6.05 0.001-4.35 3.773-0.86-3.804-1.612 2.011h-4.03z" android:strokeWidth="1.01966" android:fillColor="#000000"/>
+    <path android:pathData="M1.87 18.747l-0.314 1.221H6.9l3.13 2.807 4.573-2.807h5.345l0.315-1.22h-5.438l-4.462 2.733-3.056-2.734z" android:strokeWidth="0.591009" android:fillColor="#000000" android:fillType="evenOdd"/>
+    <path android:pathData="M1.367 20.737l-0.315 1.22h5.346l3.126 2.808 4.574-2.807h5.345l0.315-1.221H14.32L9.858 23.47l-3.054-2.734z" android:strokeWidth="0.591009" android:fillColor="#000000" android:fillType="evenOdd"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_activity_stair_stepper.xml b/app/src/main/res/drawable/ic_activity_stair_stepper.xml
new file mode 100644
index 0000000..a2c84ee
--- /dev/null
+++ b/app/src/main/res/drawable/ic_activity_stair_stepper.xml
@@ -0,0 +1,21 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="25dp"
+    android:height="25dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="25"
+    android:viewportHeight="25">
+  <path
+      android:pathData="m12.112,11.742h4.88V9.942h-3.62l-2,-3.33c-0.3,-0.5 -0.84,-0.84 -1.46,-0.84 -0.18,0 -0.34,0.03 -0.5,0.08l-5.42,1.69v5.2h1.8V9.072l2.11,-0.66 -3.635,14.469h1.8l2.595,-7.249 2.444,0.042 0,3.899h1.8l-0,-5.309 -2.604,-1.472 0.73,-2.87m0.96,-4.38c1,0 1.8,-0.8 1.8,-1.8 0,-1 -0.8,-1.8 -1.8,-1.8 -1,0 -1.8,0.8 -1.8,1.8 0,1 0.8,1.8 1.8,1.8z"
+      android:fillColor="#000000"/>
+  <path
+      android:pathData="m17.479,16.2 l0.042,1.796 -3.415,0.009 -0.011,1.803 -3.414,-0.015 0.038,1.807L7.3,21.6v1.4l-3.4,-0v1.047h6.412v0.014H20.9v-1.06H11.502L20.9,17.508v-1.308z"
+      android:strokeWidth="0.863186"
+      android:fillColor="#000000"/>
+  <path
+      android:pathData="M19.553,7.871h0.91v15.311h-0.91z"
+      android:strokeWidth="1.1438"
+      android:fillColor="#000000"/>
+  <path
+      android:pathData="M17.489,7.5l3.74,-0.805l0.232,1.076l-3.74,0.805z"
+      android:fillColor="#000000"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_activity_stairs.xml b/app/src/main/res/drawable/ic_activity_stairs.xml
new file mode 100644
index 0000000..caf514f
--- /dev/null
+++ b/app/src/main/res/drawable/ic_activity_stairs.xml
@@ -0,0 +1,14 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="25dp"
+    android:height="25dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="25"
+    android:viewportHeight="25">
+  <path
+      android:pathData="m12.112,11.742h4.88V9.942h-3.62l-2,-3.33c-0.3,-0.5 -0.84,-0.84 -1.46,-0.84 -0.18,0 -0.34,0.03 -0.5,0.08l-5.42,1.69v5.2h1.8V9.072l2.11,-0.66 -3.91,15.33h1.8l2.87,-8.11 2.444,0.042 -0.038,5.038h1.8l0.038,-6.448 -2.604,-1.472 0.73,-2.87m0.96,-4.38c1,0 1.8,-0.8 1.8,-1.8 0,-1 -0.8,-1.8 -1.8,-1.8 -1,0 -1.8,0.8 -1.8,1.8 0,1 0.8,1.8 1.8,1.8z"
+      android:fillColor="#000000"/>
+  <path
+      android:pathData="M18.313,15.061L18.313,18.061L14.313,18.061L14.313,21.061L10.313,21.061L10.313,24.061L21.313,24.061L21.313,21.061L21.313,18.061L21.313,15.061L18.313,15.061z"
+      android:strokeWidth="0.918472"
+      android:fillColor="#000000"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_activity_stretching.xml b/app/src/main/res/drawable/ic_activity_stretching.xml
new file mode 100644
index 0000000..713cc99
--- /dev/null
+++ b/app/src/main/res/drawable/ic_activity_stretching.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="25dp"
+    android:height="25dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="25"
+    android:viewportHeight="25">
+    <path
+        android:fillColor="#000000"
+        android:pathData="M 15.559 4.564 C 14.611 4.247 14.106 3.234 14.423 2.286 C 14.741 1.338 15.753 0.833 16.701 1.15 C 17.65 1.467 18.155 2.48 17.837 3.428 C 17.52 4.377 16.507 4.881 15.559 4.564 Z M 11.591 10.906 L 10.643 12.717 L 9.668 11.293 L 10.616 9.593 L 12.93 5.315 C 13.373 4.936 14.72 4.686 15.308 4.883 C 15.479 4.94 16.231 5.067 16.227 5.927 C 16.227 5.927 16.435 6.518 16.357 8.319 L 15.071 23.485 L 13.131 23.465 L 13.79 14.998 L 11.482 17.71 L 7.882 12.015 L 9.125 10.89 L 11.588 14.411 L 12.691 11.712 L 12.91 8.758" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_activity_sup.xml b/app/src/main/res/drawable/ic_activity_sup.xml
new file mode 100644
index 0000000..ce8215e
--- /dev/null
+++ b/app/src/main/res/drawable/ic_activity_sup.xml
@@ -0,0 +1,26 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="25sp"
+    android:height="25sp"
+    android:tint="#7E7E7E"
+    android:viewportHeight="1024"
+    android:viewportWidth="1024">
+      
+    <path android:fillColor="#000000" android:pathData="M699.5,448.9l-102.3,-52.2 -128.4,-102.3 -43.5,65.3 128.4,89.2c2.2,2.2 4.4,4.4 8.7,6.5l128.4,63.1 8.7,-69.7zM251.1,882c-4.4,0 -8.7,0 -15.2,-2.2 -21.8,-8.7 -34.8,-32.7 -26.1,-56.6l50.1,-141.5L290.2,518.5l132.8,-37 -130.6,370.1c-6.5,19.6 -23.9,30.5 -41.4,30.5z" android:strokeColor="#000000" android:strokeWidth="1"/>
+      
+    <path android:fillColor="#000000" android:pathData="M366.4,642.6l56.6,-161.1 -132.8,37 -19.6,102.3z" android:strokeColor="#000000" android:strokeWidth="1"/>
+      
+    <path android:fillColor="#000000" android:pathData="M479.6,76.6c41.4,0 76.2,34.8 76.2,76.2s-34.8,76.2 -76.2,76.2 -76.2,-34.8 -76.2,-76.2 34.8,-76.2 76.2,-76.2z" android:strokeColor="#000000" android:strokeWidth="1"/>
+      
+    <path android:fillColor="#000000" android:pathData="M773.3,210.5l-86.9,695.5 -43.2,-5.4 86.9,-695.5z" android:strokeColor="#000000" android:strokeWidth="1"/>
+      
+    <path android:fillColor="#000000" android:pathData="M736.5,947.4H584.1c13.1,-117.5 69.7,-126.3 78.4,-195.9h43.5c-4.4,69.7 37,71.8 30.5,195.9z" android:strokeColor="#000000" android:strokeWidth="1"/>
+      
+    <path android:fillColor="#000000" android:pathData="M925.9,816.7s-65.3,65.3 -413.6,65.3H98.7c-13.1,0 -21.8,8.7 -21.8,21.8 0,21.8 87.1,43.5 174.1,43.5h522.4c87.1,0 174.1,-87.1 174.1,-108.8 0,-13.1 -8.7,-21.8 -21.8,-21.8z" android:strokeColor="#000000" android:strokeWidth="1"/>
+      
+    <path android:fillColor="#000000" android:pathData="M771.3,194.2c-8.7,-15.2 -28.3,-21.8 -43.5,-13.1l-124.1,67.5 -119.7,23.9h-60.9c-30.5,0 -56.6,19.6 -67.5,47.9L292.4,512c-8.7,26.1 -2.2,52.2 15.2,69.7l117.5,119.7v137.1c0,23.9 19.6,43.5 43.5,43.5s43.5,-19.6 43.5,-43.5V686.1c0,-10.9 -4.4,-19.6 -10.9,-28.3l-76.2,-100.1v-2.2l74,-189.4c0,-2.2 2.2,-4.4 2.2,-6.5l117.5,-47.9c4.4,0 6.5,-2.2 8.7,-4.4l128.4,-71.8c17.4,-6.5 23.9,-26.1 15.2,-41.4z" android:strokeColor="#000000" android:strokeWidth="1"/>
+      
+    <path android:fillColor="#000000" android:pathData="M447,316.1c0,-43.5 37,-43.5 37,-43.5h-60.9c-30.5,0 -56.6,19.6 -67.5,47.9L292.4,512c-8.7,26.1 -2.2,52.2 15.2,69.7l60.9,60.9 82.7,-52.2s-26.1,-32.7 -26.1,-34.8l74,-189.4c0,-2.2 2.2,-4.4 2.2,-6.5 0,0 -54.4,8.7 -54.4,-43.5z" android:strokeColor="#000000" android:strokeWidth="1"/>
+      
+    <path android:fillColor="#000000" android:pathData="M501.4,359.6c-2.2,0 -21.8,2.2 -37,-6.5l-67.5,169.8 28.3,32.7 74,-189.4c0,-2.2 2.2,-4.4 2.2,-6.5z" android:strokeColor="#000000" android:strokeWidth="1"/>
+    
+</vector>
diff --git a/app/src/main/res/drawable/ic_activity_surfing.xml b/app/src/main/res/drawable/ic_activity_surfing.xml
new file mode 100644
index 0000000..97929c1
--- /dev/null
+++ b/app/src/main/res/drawable/ic_activity_surfing.xml
@@ -0,0 +1,14 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="25sp"
+    android:height="25sp"
+    android:tint="#7E7E7E"
+    android:viewportHeight="128"
+    android:viewportWidth="128">
+      
+    <path android:fillColor="#000000" android:pathData="M120.4,120.9C64.2,103.2 20.1,83.7 10,79.1c-1,-0.4 -2.2,0 -2.6,1.1c-0.4,1 0,2.2 1.1,2.5c2.5,1.1 5,2.2 7.4,3.3l-7.9,6c-0.9,1.3 -0.5,3 0.8,3.9c1,0.7 2.4,0.6 3.4,0l13.6,-5.7c33.3,14 58.7,22.2 94.3,33.4c0.7,0.1 1.4,-0.4 1.5,-1.2C121.6,121.8 121.1,121.1 120.4,120.9z"/>
+      
+    <path android:fillColor="#000000" android:pathData="M100,32c5.3,0 9.5,-4.3 9.5,-9.5c0,-5.3 -4.2,-9.5 -9.5,-9.5s-9.5,4.3 -9.5,9.5C90.4,27.7 94.7,32 100,32z"/>
+      
+    <path android:fillColor="#000000" android:pathData="M34.3,84.1c1,0.1 2,-0.2 2.8,-0.6l20.9,-9.4c1.5,-0.8 2.6,-2.2 3,-4v-10.5l14.5,13l-2.7,22.1c0,0.1 0,0.3 0,0.4c-0.1,3.3 2.4,6 5.7,6.1c3.1,0.1 5.7,-2.1 6.1,-5.1l3.1,-25.2c0,-0.1 0,-0.2 0,-0.3c0.1,-1.8 -0.6,-3.4 -1.8,-4.5l-13.4,-12l20.2,-9.9l18.8,17.3c0.8,0.8 2,1.3 3.2,1.3c2.5,0 4.6,-2.1 4.5,-4.6c0,-1.4 -0.6,-2.6 -1.6,-3.4L90.9,30.2c-0.9,-0.9 -1.9,-1.6 -3.1,-2.1L66.9,20.7l-9.5,-13.8c-0.7,-1 -1.9,-1.7 -3.2,-1.9c-2.5,-0.3 -4.7,1.5 -5,4c-0.1,1 0.1,2.1 0.6,2.9L59.5,26c0.4,0.6 1,1 1.7,1.4l11.2,4.4L54,41.2c-2.6,1.5 -4.4,4 -4.9,7.1l0.1,16.9l-17.1,7.7c-1.9,0.9 -3.3,2.8 -3.4,5.1C28.6,81.2 31.1,83.9 34.3,84.1z"/>
+    
+</vector>
diff --git a/app/src/main/res/drawable/ic_activity_swimming.xml b/app/src/main/res/drawable/ic_activity_swimming.xml
new file mode 100644
index 0000000..7e136fe
--- /dev/null
+++ b/app/src/main/res/drawable/ic_activity_swimming.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="45sp"
+    android:height="45sp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="25"
+    android:viewportHeight="25">
+    <path
+        android:fillColor="#000000"
+        android:pathData="M22 21c-1.11 0-1.73-0.37-2.18-0.64-0.37-0.22-0.6-0.36-1.15-0.36-0.56 0-0.78 0.13-1.15 0.36-0.46 0.27-1.07 0.64-2.18 0.64s-1.73-0.37-2.18-0.64c-0.37-0.22-0.6-0.36-1.15-0.36-0.56 0-0.78 0.13-1.15 0.36C10.4 20.63 9.78 21 8.67 21c-1.11 0-1.73-0.37-2.18-0.64C6.12 20.13 5.89 20 5.34 20s-0.78 0.13-1.15 0.36C3.73 20.63 3.11 21 2 21v-2c0.56 0 0.78-0.13 1.15-0.36C3.61 18.37 4.23 18 5.34 18s1.73 0.37 2.18 0.64C7.89 18.87 8.11 19 8.67 19c0.56 0 0.78-0.13 1.15-0.36 0.46-0.27 1.08-0.64 2.19-0.64 1.11 0 1.73 0.37 2.18 0.64 0.37 0.22 0.6 0.36 1.15 0.36s0.78-0.13 1.15-0.36c0.45-0.27 1.07-0.64 2.18-0.64s1.73 0.37 2.18 0.64C21.22 18.87 21.44 19 22 19v2zm0-4.5c-1.11 0-1.73-0.37-2.18-0.64-0.37-0.22-0.6-0.36-1.15-0.36-0.56 0-0.78 0.13-1.15 0.36-0.45 0.27-1.07 0.64-2.18 0.64s-1.73-0.37-2.18-0.64c-0.37-0.22-0.6-0.36-1.15-0.36-0.56 0-0.78 0.13-1.15 0.36-0.45 0.27-1.07 0.64-2.18 0.64s-1.73-0.37-2.18-0.64c-0.37-0.22-0.6-0.36-1.15-0.36s-0.78 0.13-1.15 0.36C3.73 16.13 3.11 16.5 2 16.5v-2c0.56 0 0.78-0.13 1.15-0.36 0.45-0.27 1.07-0.64 2.18-0.64s1.73 0.37 2.18 0.64c0.37 0.22 0.6 0.36 1.15 0.36 0.56 0 0.78-0.13 1.15-0.36 0.45-0.27 1.07-0.64 2.18-0.64s1.73 0.37 2.18 0.64c0.37 0.22 0.6 0.36 1.15 0.36s0.78-0.13 1.15-0.36c0.45-0.27 1.07-0.64 2.18-0.64s1.73 0.37 2.18 0.64c0.37 0.22 0.6 0.36 1.15 0.36v2zM8.67 12c0.56 0 0.78-0.13 1.15-0.36 0.46-0.27 1.08-0.64 2.19-0.64 1.11 0 1.73 0.37 2.18 0.64 0.37 0.22 0.6 0.36 1.15 0.36s0.78-0.13 1.15-0.36c0.12-0.07 0.26-0.15 0.41-0.23L10.48 5C8.93 3.45 7.5 2.99 5 3v2.5C6.82 5.49 7.89 5.89 9 7l1 1-3.25 3.25c0.31 0.12 0.56 0.27 0.77 0.39C7.89 11.87 8.11 12 8.67 12zM14 5.5a2.5 2.5 0 1 1 5 0 2.5 2.5 0 1 1-5 0" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_activity_threadmill.xml b/app/src/main/res/drawable/ic_activity_threadmill.xml
new file mode 100644
index 0000000..dfabc9d
--- /dev/null
+++ b/app/src/main/res/drawable/ic_activity_threadmill.xml
@@ -0,0 +1,20 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="45sp"
+    android:height="45sp"
+    android:viewportWidth="25"
+    android:viewportHeight="25">
+    <path
+        android:fillColor="@color/secondarytext"
+        android:pathData="M12.698 4.563c0.97 0.171 1.925-0.505 2.095-1.467 0.176-0.997-0.496-1.923-1.467-2.094-0.988-0.174-1.919 0.47-2.095 1.467-0.169 0.961 0.479 1.92 1.467 2.094M7.305 16.355l1.578-3.743 1.565 2.112-0.942 5.342 1.78 0.314 1.178-6.678-1.565-2.112 1.014-2.575c0.913 1.538 4.426 0.384 6.36-0.39l-0.002-1.702c-1.747 0.484-4.498 1.96-4.997 0.57l-0.642-1.565c-0.259-0.614-0.733-1.047-1.348-1.155-0.276-0.049-0.458-0.007-0.734-0.056L5.584 5.86l-0.74 4.202 1.78 0.314 0.537-3.045L8.864 6.99l-2.699 6.98-4.206-1.66-0.67 1.718z"
+        android:strokeWidth="1" />
+    <path
+        android:fillColor="@color/secondarytext"
+        android:fillType="evenOdd"
+        android:pathData="M0.485 22.642l20.42-2.94 2.479 3.76h-22.9z"
+        android:strokeWidth="1" />
+    <path
+        android:fillType="evenOdd"
+        android:pathData="M22.604 23.183l-6.253-9.47 3.452-1.4"
+        android:strokeWidth="1"
+        android:strokeColor="@color/secondarytext" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_activity_tracks.xml b/app/src/main/res/drawable/ic_activity_tracks.xml
new file mode 100644
index 0000000..b1c1e67
--- /dev/null
+++ b/app/src/main/res/drawable/ic_activity_tracks.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="45sp"
+    android:height="45sp"
+    android:viewportWidth="25"
+    android:viewportHeight="25">
+    <path
+        android:fillColor="#000000"
+        android:pathData="M13.5 5.5c1.09 0 2-0.92 2-2 0-1.12-0.91-2-2-2-1.11 0-2 0.88-2 2 0 1.08 0.89 2 2 2M9.89 19.38l1-4.38L13 17v6h2v-7.5l-2.11-2 0.61-3C14.79 12 16.79 13 19 13v-2c-1.91 0-3.5-1-4.31-2.42l-1-1.58c-0.4-0.62-1-1-1.69-1-0.31 0-0.5 0.08-0.81 0.08L6 8.28V13h2V9.58l1.79-0.7L8.19 17l-4.9-1-0.4 2 7 1.38z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_activity_trail_run.xml b/app/src/main/res/drawable/ic_activity_trail_run.xml
new file mode 100644
index 0000000..0ff3c0e
--- /dev/null
+++ b/app/src/main/res/drawable/ic_activity_trail_run.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="45sp"
+    android:height="45sp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="25"
+    android:viewportHeight="25">
+    <path
+        android:fillColor="#000000"
+        android:pathData="M 13.5 5.5 C 14.59 5.5 15.5 4.443 15.5 3.5 C 15.5 2.557 14.443 1.5 13.5 1.5 C 12.557 1.5 11.5 2.557 11.5 3.5 C 11.5 4.443 12.39 5.5 13.5 5.5 M 9.89 19.38 L 10.89 15 L 13 17 L 13 23 L 15 23 L 15 15.5 L 12.89 13.5 L 13.5 10.5 C 14.79 12 16.79 13 19 13 L 19 11 C 17.09 11 15.5 10 14.69 8.58 L 13.69 7 C 13.29 6.38 12.69 6 12 6 C 11.69 6 11.5 6.08 11.19 6.08 L 6 8.28 L 6 13 L 8 13 L 8 9.58 L 9.79 8.88 L 8.19 17 L 3.29 16 L 2.89 18 Z M 10.121 22.68 C 10.679 21.598 10.148 20.821 9.205 20.821 C 8.262 20.821 7.225 20.743 7.225 21.685 C 7.225 22.628 7.397 22.953 8.472 22.668 M 6.249 22.353 C 7.119 21.965 6.587 21.527 5.47 21.595 C 4.529 21.654 4.235 21.501 4.235 22.444 C 4.235 23.387 5.296 22.477 6.072 22.567 M 18.484 21.79 C 17.794 20.797 17.672 21.166 17.226 21.256 C 16.684 21.366 15.865 21.934 16.073 22.446 C 16.346 23.121 17.05 22.661 18.258 22.513 M 12.556 21.791 C 12.41 21.227 12.961 21.223 11.798 21.442 C 10.945 21.603 10.928 21.399 11.221 22.208 C 11.637 23.356 11.691 22.546 12.432 22.366" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_activity_unknown.xml b/app/src/main/res/drawable/ic_activity_unknown.xml
new file mode 100644
index 0000000..bbe8bf8
--- /dev/null
+++ b/app/src/main/res/drawable/ic_activity_unknown.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="45sp"
+    android:height="45sp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="25"
+    android:viewportHeight="25">
+  <path
+      android:fillColor="#000000"
+      android:pathData="M12 18a6 6 0 0 1-6-6 6 6 0 0 1 6-6 6 6 0 0 1 6 6 6 6 0 0 1-6 6m8-2.69L23.31 12 20 8.69V4h-4.69L12 0.69 8.69 4H4v4.69L0.69 12 4 15.31V20h4.69L12 23.31 15.31 20H20v-4.69z" />
+</vector>
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_activity_unknown_small.xml b/app/src/main/res/drawable/ic_activity_unknown_small.xml
new file mode 100644
index 0000000..87505b5
--- /dev/null
+++ b/app/src/main/res/drawable/ic_activity_unknown_small.xml
@@ -0,0 +1,4 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:tint="#7E7E7E" android:viewportHeight="24">
+    <path android:pathData="M12 17.305A5.305 5.305 0 0 1 6.695 12 5.305 5.305 0 0 1 12 6.695 5.305 5.305 0 0 1 17.305 12 5.305 5.305 0 0 1 12 17.305m7.073-2.378L22 12l-2.927-2.927V4.927h-4.146L12 2 9.073 4.927H4.927v4.146L2 12l2.927 2.927v4.146h4.146L12 22l2.927-2.927h4.146z" android:strokeWidth="1.95392" android:fillColor="#000000"/>
+    <path android:pathData="M5.348 12.344h3.149l1.842-4.446 1.709 4.853 1.612-2.411h5.737l-0.431 1.227h-4.654l-2.598 3.986-1.417-4.019-0.84 2.125h-3.1z" android:strokeWidth="1.131" android:fillColor="#000000"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_activity_wakeboarding.xml b/app/src/main/res/drawable/ic_activity_wakeboarding.xml
new file mode 100644
index 0000000..aff20f9
--- /dev/null
+++ b/app/src/main/res/drawable/ic_activity_wakeboarding.xml
@@ -0,0 +1,16 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="25sp"
+    android:height="25sp"
+    android:tint="#7E7E7E"
+    android:viewportHeight="310.64"
+    android:viewportWidth="310.64">
+      
+    <path android:fillColor="#000000" android:pathData="m195.65,257c2.78,18.54 -8.98,30.61 -22.42,32.63 -14.94,2.23 -28.54,-8.32 -30.69,-22.7 -0.8,-5.33 -1.14,-7.65 -1.94,-12.97 -5.31,18.91 -4.37,15.58 -5.85,20.83 -3.97,14.13 -18.79,22.77 -33.29,18.7 -8.09,-2.27 -14.6,-8.07 -17.77,-15.98 -22.34,7.52 -36.01,15.71 -34.98,22.35 1.85,11.96 50.67,14.34 109.05,5.32s104.21,-26.03 102.36,-37.99c-1.35,-8.72 -27.67,-12.35 -64.46,-10.18z"/>
+      
+    <path android:fillColor="#000000" android:pathData="m65.05,194.39l45.68,10.55 -16.42,58.49c-2.24,7.98 2.41,16.26 10.39,18.5 7.98,2.24 16.26,-2.42 18.5,-10.39l20.67,-73.62c2.31,-8.24 -2.74,-16.75 -11.07,-18.67l-59.72,-14.11c0,0 60.84,2.06 62.42,2.42 7.16,1.65 13.41,6.22 17.15,12.54 3.74,6.32 4.75,14 2.77,21.07l-6.91,24.61 5.9,39.38c1.23,8.18 8.84,13.84 17.06,12.61 8.19,-1.23 13.84,-8.86 12.61,-17.06l-13.58,-90.67c-0.88,-5.91 -5.18,-10.68 -10.87,-12.24l-44.46,-12.19 -2.57,-7.09 -4.58,2.27c-10.52,5.21 -21.37,1.36 -25.36,-2.39l-37.23,-35.02 45.45,26.27c3.5,2.03 8.02,2.26 11.81,0.38l44.75,-22.18c6.18,-3.06 8.72,-10.56 5.65,-16.75 -0,-0 -0,-0 -0,-0 -3.1,-6.25 -10.65,-8.67 -16.75,-5.65l-12.84,6.36 -25.9,12.84 -41.16,-23.79c2.27,0.63 38.12,10.51 39.93,11.01l11.41,-5.66 -24.95,-23.96c-3.59,-3.44 -11.63,-5.97 -18.84,-3.36l-31.26,11.3c-9.68,3.5 -14.69,14.18 -11.19,23.86 3.01,8.31 29.23,80.83 32.37,89.53 1.37,5.22 5.51,9.52 11.13,10.81z"/>
+      
+    <path android:fillColor="#000000" android:pathData="m289.24,136.02c-46.08,-5.14 -84.47,-16.25 -110.52,-25.64l1.95,-0.61c6.59,-2.07 10.25,-9.09 8.18,-15.67 -2.07,-6.59 -9.09,-10.25 -15.67,-8.18l-8.2,2.58c3.09,8.74 1,18.6 -5.61,25.32 -1.85,1.88 -3.98,3.43 -6.34,4.63 -0.09,0.05 -0.17,0.1 -0.26,0.14l-3.57,1.77c24.34,10.43 73.53,28.35 137.82,35.53 0.38,0.04 0.75,0.06 1.12,0.06 5.03,0 9.35,-3.78 9.93,-8.89 0.61,-5.49 -3.34,-10.43 -8.83,-11.05z"/>
+      
+    <path android:fillColor="#000000" android:pathData="M40.1,27.59m-27.59,0a27.59,27.59 0,1 1,55.18 0a27.59,27.59 0,1 1,-55.18 0"/>
+    
+</vector>
diff --git a/app/src/main/res/drawable/ic_activity_walking.xml b/app/src/main/res/drawable/ic_activity_walking.xml
new file mode 100644
index 0000000..2793483
--- /dev/null
+++ b/app/src/main/res/drawable/ic_activity_walking.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="45sp"
+    android:height="45sp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="25"
+    android:viewportHeight="25">
+    <path
+        android:fillColor="#000000"
+        android:pathData="M14.12 10H19V8.2h-3.62l-2-3.33c-0.3-0.5-0.84-0.84-1.46-0.84-0.18 0-0.34 0.03-0.5 0.08L6 5.8V11h1.8V7.33l2.11-0.66L6 22h1.8l2.87-8.11L13 17v5h1.8v-6.41l-2.49-4.54 0.73-2.87M14 3.8c1 0 1.8-0.8 1.8-1.8S15 0.2 14 0.2 12.2 1 12.2 2 13 3.8 14 3.8z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_activity_waterskiing.xml b/app/src/main/res/drawable/ic_activity_waterskiing.xml
new file mode 100644
index 0000000..bf9773b
--- /dev/null
+++ b/app/src/main/res/drawable/ic_activity_waterskiing.xml
@@ -0,0 +1,16 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:height="25sp"
+    android:width="25sp"
+    android:tint="#7E7E7E"
+    android:viewportHeight="128"
+    android:viewportWidth="128">
+      
+    <path android:fillColor="#000000" android:pathData="M75.9,107.4c0.8,-0.1 1.6,-0.2 2.4,-0.2c2.5,0 4.9,0.6 7,1.6c2.1,0.9 4.4,1.5 6.8,1.5c2.4,0 4.8,-0.6 6.9,-1.5c2.1,-1 4.5,-1.6 7,-1.6c2.5,0 4.9,0.6 7,1.6c2.1,0.9 4.4,1.5 6.8,1.5v10.3c-2.4,0 -4.8,-0.6 -6.8,-1.5c-2.1,-1 -4.5,-1.5 -7,-1.5c-2.5,0 -4.9,0.6 -7,1.5c-2.1,0.9 -4.4,1.5 -6.9,1.5c-2.4,0 -4.8,-0.6 -6.8,-1.5c-2.1,-1 -4.5,-1.5 -7,-1.5c-2.5,0 -4.9,0.6 -7,1.5c-2.1,0.9 -4.4,1.5 -6.8,1.5c-2.4,0 -4.7,-0.6 -6.8,-1.5c-2.1,-1 -4.5,-1.5 -7,-1.5c-2.5,0 -4.9,0.6 -7,1.5c-2.1,0.9 -4.4,1.5 -6.8,1.5c-2.4,0 -4.7,-0.6 -6.8,-1.5c-2.2,-1 -4.6,-1.5 -7,-1.5c-2.5,0 -4.9,0.6 -7,1.5c-2.1,0.9 -4.4,1.5 -6.8,1.5v-10.3c2.4,0 4.7,-0.6 6.8,-1.5c2.1,-1 4.5,-1.6 7,-1.6c2.5,0 4.9,0.6 7,1.6c2.1,0.9 4.4,1.5 6.8,1.5c2.4,0 4.7,-0.6 6.8,-1.5c1.2,-0.6 2.5,-1 3.8,-1.2l64.7,-11.5c2.1,-0.5 4.9,-2 5.9,-4.5c0.2,-0.5 0.9,-0.6 1.5,-0.6c1,0.1 1.8,1 1.8,2c0,1.7 -1.3,6.3 -8.3,7.7L75.9,107.4z"/>
+      
+    <path android:fillColor="#000000" android:pathData="M19.8,26.2c5.2,0 9.4,-4.2 9.4,-9.4S25,7.4 19.8,7.4c-5.2,0 -9.3,4.2 -9.3,9.3S14.6,26.2 19.8,26.2"/>
+      
+    <path android:fillColor="#000000" android:pathData="M47,101.8c0.5,2.1 3,4.1 5.6,4.1l0,0c3.3,0 6,-2.6 6,-6l-0.1,-1l-7.1,-30.6c-0.6,-2 -2.5,-3.8 -4.4,-4.6l-10.5,-6.3l-4.8,-20.6l29.5,-0.1c2.4,0 4.3,-1.9 4.3,-4.3c0,-2.4 -1.9,-4.4 -4.3,-4.4H18.6c-4.3,0 -6.2,3 -5.7,5.7l6,27.3c0.5,2.6 2.2,4.7 4.4,5.9l0.1,0.1L41,77.3L47,101.8z"/>
+      
+    <path android:fillColor="#000000" android:pathData="M68,33.6h52.1v3.3h-52.1z"/>
+    
+</vector>
diff --git a/app/src/main/res/drawable/ic_activity_watertubing.xml b/app/src/main/res/drawable/ic_activity_watertubing.xml
new file mode 100644
index 0000000..66c8c5e
--- /dev/null
+++ b/app/src/main/res/drawable/ic_activity_watertubing.xml
@@ -0,0 +1,24 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:height="25sp"
+    android:width="25sp"
+    android:tint="#7E7E7E"
+    android:viewportHeight="285"
+    android:viewportWidth="285">
+      
+    <path android:fillColor="#000000" android:pathData="m195.45,103.55h31.74c8.28,0 15.89,2.98 21.8,7.91l6.68,0.84c8.23,1.04 15.72,-4.79 16.76,-13.01 1.03,-8.22 -4.79,-15.72 -13.01,-16.76l-63.54,-8c-4.73,-0.59 -9.45,1.09 -12.73,4.54l-15.61,16.38 5.69,-0.76c8.58,-1.14 16.9,2.32 22.22,8.85z"/>
+      
+    <path android:fillColor="#000000" android:pathData="m59.64,98.99l2.83,4.55h25.43l-4.92,-9.27 34.56,39.51c2.76,3.15 7.06,4.71 11.06,4.16l49.51,-6.58c7.32,-0.97 12.2,-8.06 10.52,-15.25 -1.48,-6.32 -7.55,-10.36 -13.81,-9.53 -0,0 -0,0 -0,0l-42.86,5.7 -31.3,-35.78 34.26,23.28 2.92,-0.39 -21.92,-35.32c-5.1,-8.21 -15.89,-10.74 -24.1,-5.64l-26.52,16.46c-8.21,5.1 -10.74,15.89 -5.64,24.1z"/>
+      
+    <path android:fillColor="#000000" android:pathData="M60.91,37.99m-25.91,0a25.91,25.91 0,1 1,51.81 0a25.91,25.91 0,1 1,-51.81 0"/>
+      
+    <path android:fillColor="#000000" android:pathData="m53.31,175.72h113.34c-0.24,-12.4 9.78,-22.43 21.99,-22.43h60.61v-15.68c0,-12.18 -9.88,-22.06 -22.06,-22.06h-26.49c1.92,13.56 -7.61,25.94 -21.02,27.72l-49.51,6.58c-11.05,1.47 -20.39,-5.06 -23.24,-10.43l-12.68,-23.88h-40.96c-12.18,0 -22.06,9.88 -22.06,22.06v16.05c-0,12.18 9.88,22.06 22.06,22.06z"/>
+      
+    <path android:fillColor="#000000" android:pathData="m111,199.42c0,-5.52 -4.48,-10 -10,-10h-91c-5.52,0 -10,4.48 -10,10 0,5.52 4.48,10 10,10h91c5.52,0 10,-4.48 10,-10z"/>
+      
+    <path android:fillColor="#000000" android:pathData="m76.82,231.42c0,5.52 4.48,10 10,10h91.18c5.52,0 10,-4.48 10,-10 0,-5.52 -4.48,-10 -10,-10h-91.18c-5.52,0 -10,4.48 -10,10z"/>
+      
+    <path android:fillColor="#000000" android:pathData="m264,252.92h-229c-5.52,0 -10,4.48 -10,10 0,5.52 4.48,10 10,10h229c5.52,0 10,-4.48 10,-10 0,-5.52 -4.48,-10 -10,-10z"/>
+      
+    <path android:fillColor="#000000" android:pathData="m275,165.29h-86.36c-5.52,0 -10,4.48 -10,10 0,5.52 4.48,10 10,10h86.36c5.52,0 10,-4.48 10,-10s-4.48,-10 -10,-10z"/>
+    
+</vector>
diff --git a/app/src/main/res/drawable/ic_activity_windsurfing.xml b/app/src/main/res/drawable/ic_activity_windsurfing.xml
new file mode 100644
index 0000000..599c31b
--- /dev/null
+++ b/app/src/main/res/drawable/ic_activity_windsurfing.xml
@@ -0,0 +1,16 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="25sp"
+    android:height="25sp"
+    android:tint="#7E7E7E"
+    android:viewportHeight="128"
+    android:viewportWidth="128">
+      
+    <path android:fillColor="#000000" android:pathData="M119.9,124c-2.5,0 -4.8,-0.6 -6.9,-1.5c-2.2,-1 -4.6,-1.6 -7.1,-1.6c-2.5,0 -5,0.6 -7.1,1.6c-2.1,0.9 -4.5,1.5 -7,1.5c-2.5,0 -4.8,-0.6 -6.9,-1.5c-2.2,-1 -4.6,-1.6 -7.2,-1.6c-2.5,0 -5,0.6 -7.1,1.6c-2.1,0.9 -4.5,1.5 -7,1.5c-2.5,0 -4.8,-0.6 -6.9,-1.5c-2.2,-1 -4.6,-1.6 -7.2,-1.6s-5,0.6 -7.1,1.6c-2.1,0.9 -4.5,1.5 -7,1.5c-2.5,0 -4.8,-0.6 -6.9,-1.5c-2.2,-1 -4.6,-1.6 -7.1,-1.6c-2.6,0 -5,0.6 -7.1,1.6c-2.1,0.9 -4.5,1.5 -7,1.5v-10.4c2.5,0 4.8,-0.6 7,-1.5c2.2,-1 4.6,-1.6 7.1,-1.6c2.5,0 5,0.6 7.1,1.6c2.1,0.9 4.5,1.5 6.9,1.5c2.5,0 4.8,-0.6 7,-1.5c2.2,-1 4.6,-1.6 7.1,-1.6s5,0.6 7.2,1.6c2.1,0.9 4.5,1.5 6.9,1.5c2.5,0 4.8,-0.6 7,-1.5c2.2,-1 4.6,-1.6 7.1,-1.6c2.5,0 5,0.6 7.2,1.6c2.1,0.9 4.5,1.5 6.9,1.5c2.5,0 4.8,-0.6 7,-1.5c2.2,-1 4.6,-1.6 7.1,-1.6c2.5,0 5,0.6 7.1,1.6c2.1,0.9 4.5,1.5 6.9,1.5V124z"/>
+      
+    <path android:fillColor="#000000" android:pathData="M19.2,63.8c3.8,0 6.8,-3.1 6.8,-6.8c0,-3.8 -3.1,-6.8 -6.8,-6.8c-3.8,0 -6.8,3.1 -6.8,6.8C12.4,60.7 15.5,63.8 19.2,63.8"/>
+      
+    <path android:fillColor="#000000" android:pathData="M49.3,94.7H29.7c-2.9,0 -5.4,-1.7 -6.5,-4.2L16,74.1c-0.1,-0.6 -0.2,-1.1 -0.2,-1.7c0,-4 3.2,-7.2 7.1,-7.2l15.9,0l10.9,-4.9c0.2,-0.1 0.6,-0.2 1.1,-0.2c1.9,0 3.4,1.5 3.4,3.4c0,1.1 -0.6,2.2 -1.5,2.8l-11.8,5.3c-0.8,0.6 -2.3,0.7 -2.3,0.7h-7.1l6,13.3h13.2c1.3,0 2.2,0.5 3,1.1L66.6,99c1.8,1.7 1.9,4.5 0.2,6.3c-1.7,1.8 -4.5,1.9 -6.4,0.2L49.3,94.7z"/>
+      
+    <path android:fillColor="#000000" android:pathData="M110.1,72.4c0.3,0.4 0.9,0.6 1.4,0.6c1.1,0 2,-0.9 2,-2c0,-0.5 -0.3,-1 -0.5,-1.5c-12.2,-23.3 -45.2,-16.4 -49.2,-14.8c-0.7,0.3 -1.1,1 -1.1,1.8c0,1.1 0.9,2 2,2c0.3,0 0.5,0 0.8,-0.1C97.9,51 108.4,69.4 110.1,72.4M115.8,74.5l-41.4,26.1L36.8,4.4C70.5,8.9 113.3,35.1 115.8,74.5"/>
+    
+</vector>
diff --git a/app/src/main/res/drawable/ic_activity_yoga.xml b/app/src/main/res/drawable/ic_activity_yoga.xml
new file mode 100644
index 0000000..cc2bbcd
--- /dev/null
+++ b/app/src/main/res/drawable/ic_activity_yoga.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="45sp"
+    android:height="45sp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="25"
+    android:viewportHeight="25">
+    <path
+        android:fillColor="#000000"
+        android:pathData="M10 6a2 2 0 1 1 4 0 2 2 0 1 1-4 0m11 10v-2c-2.24 0-4.16-0.96-5.6-2.68l-1.34-1.6C13.68 9.26 13.12 9 12.53 9h-1.05c-0.59 0-1.15 0.26-1.53 0.72l-1.34 1.6C7.16 13.04 5.24 14 3 14v2c2.77 0 5.19-1.17 7-3.25V15l-3.88 1.55C5.45 16.82 5 17.48 5 18.21 5 19.2 5.8 20 6.79 20H9v-0.5c0-1.38 1.12-2.5 2.5-2.5h3c0.28 0 0.5 0.22 0.5 0.5S14.78 18 14.5 18h-3c-0.83 0-1.5 0.67-1.5 1.5V20h7.21C18.2 20 19 19.2 19 18.21c0-0.73-0.45-1.39-1.12-1.66L14 15v-2.25c1.81 2.08 4.23 3.25 7 3.25z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_activitytracker.xml b/app/src/main/res/drawable/ic_activitytracker.xml
new file mode 100644
index 0000000..e07a191
--- /dev/null
+++ b/app/src/main/res/drawable/ic_activitytracker.xml
@@ -0,0 +1,34 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="45sp"
+    android:height="45sp"
+    android:viewportWidth="30"
+    android:viewportHeight="30">
+    <path
+        android:fillColor="#1f7fdb"
+        android:pathData="M3.871 3.877h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.824a0.947 0.947 0 0 1 0.947-0.947z"
+        android:strokeWidth="3.57115173" />
+    <path
+        android:fillColor="#4dabf5"
+        android:pathData="M3.879 3.035h20.925a0.947 0.947 0 0 1 0.947 0.947v20.01a0.947 0.947 0 0 1-0.947 0.948H3.88a0.947 0.947 0 0 1-0.947-0.948V3.982A0.947 0.947 0 0 1 3.88 3.035z"
+        android:strokeWidth="3.57115173" />
+    <path
+        android:fillColor="#2196f3"
+        android:pathData="M3.871 3.413h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.36A0.947 0.947 0 0 1 3.87 3.413z"
+        android:strokeWidth="3.57115173" />
+    <path
+        android:fillColor="#ffffff"
+        android:pathData="M7.91 8.764h2.255v12.154H7.91z"
+        android:strokeWidth="3.46860933" />
+    <path
+        android:fillColor="#ffffff"
+        android:pathData="M11.292 15.88h2.254v5.038h-2.254z"
+        android:strokeWidth="2.23327112" />
+    <path
+        android:fillColor="#ffffff"
+        android:pathData="M14.783 11.725h2.254v9.193h-2.254z"
+        android:strokeWidth="3.01662064" />
+    <path
+        android:fillColor="#ffffff"
+        android:pathData="M18.54 7.88h2.253v13.038H18.54z"
+        android:strokeWidth="3.59252691" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_add.xml b/app/src/main/res/drawable/ic_add.xml
new file mode 100644
index 0000000..70c233a
--- /dev/null
+++ b/app/src/main/res/drawable/ic_add.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="#FFFFFF"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="#000000"
+        android:pathData="M18 13h-5v5c0 0.55-0.45 1-1 1s-1-0.45-1-1v-5H6c-0.55 0-1-0.45-1-1s0.45-1 1-1h5V6c0-0.55 0.45-1 1-1s1 0.45 1 1v5h5c0.55 0 1 0.45 1 1s-0.45 1-1 1z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_add_gray.xml b/app/src/main/res/drawable/ic_add_gray.xml
new file mode 100644
index 0000000..3732fe4
--- /dev/null
+++ b/app/src/main/res/drawable/ic_add_gray.xml
@@ -0,0 +1,12 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z" />
+
+</vector>
diff --git a/app/src/main/res/drawable/ic_alert_circle_outline.xml b/app/src/main/res/drawable/ic_alert_circle_outline.xml
new file mode 100644
index 0000000..7558b60
--- /dev/null
+++ b/app/src/main/res/drawable/ic_alert_circle_outline.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:fillColor="#FFFFFFFF"
+      android:pathData="M11,15H13V17H11V15M11,7H13V13H11V7M12,2C6.47,2 2,6.5 2,12A10,10 0,0 0,12 22A10,10 0,0 0,22 12A10,10 0,0 0,12 2M12,20A8,8 0,0 1,4 12A8,8 0,0 1,12 4A8,8 0,0 1,20 12A8,8 0,0 1,12 20Z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_always_on_display.xml b/app/src/main/res/drawable/ic_always_on_display.xml
new file mode 100644
index 0000000..975e958
--- /dev/null
+++ b/app/src/main/res/drawable/ic_always_on_display.xml
@@ -0,0 +1,5 @@
+<vector android:height="24dp" android:tint="#7E7E7E"
+    android:viewportHeight="24" android:viewportWidth="24"
+    android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
+    <path android:fillColor="@android:color/white" android:pathData="M17,1.01L7,1C5.9,1 5,1.9 5,3v18c0,1.1 0.9,2 2,2h10c1.1,0 2,-0.9 2,-2V3C19,1.9 18.1,1.01 17,1.01zM17,18H7V6h10V18zM8,10h8v1.5H8V10zM9,13h6v1.5H9V13z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_archive.xml b/app/src/main/res/drawable/ic_archive.xml
new file mode 100644
index 0000000..ace6dc2
--- /dev/null
+++ b/app/src/main/res/drawable/ic_archive.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M20.54,5.23l-1.39,-1.68C18.88,3.21 18.47,3 18,3H6c-0.47,0 -0.88,0.21 -1.16,0.55L3.46,5.23C3.17,5.57 3,6.02 3,6.5V19c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V6.5c0,-0.48 -0.17,-0.93 -0.46,-1.27zM12,17.5L6.5,12H10v-2h4v2h3.5L12,17.5zM5.12,5l0.81,-1h12l0.94,1H5.12z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_arrow_circle_left.xml b/app/src/main/res/drawable/ic_arrow_circle_left.xml
new file mode 100644
index 0000000..50e8c1b
--- /dev/null
+++ b/app/src/main/res/drawable/ic_arrow_circle_left.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="?attr/colorControlNormal"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="#000000"
+        android:pathData="M6,13c0,-1.65 0.67,-3.15 1.76,-4.24L6.34,7.34C4.9,8.79 4,10.79 4,13c0,4.08 3.05,7.44 7,7.93v-2.02C8.17,18.43 6,15.97 6,13zM20,13c0,-4.42 -3.58,-8 -8,-8c-0.06,0 -0.12,0.01 -0.18,0.01l1.09,-1.09L11.5,2.5L8,6l3.5,3.5l1.41,-1.41l-1.08,-1.08C11.89,7.01 11.95,7 12,7c3.31,0 6,2.69 6,6c0,2.97 -2.17,5.43 -5,5.91v2.02C16.95,20.44 20,17.08 20,13z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_arrow_circle_right.xml b/app/src/main/res/drawable/ic_arrow_circle_right.xml
new file mode 100644
index 0000000..091889a
--- /dev/null
+++ b/app/src/main/res/drawable/ic_arrow_circle_right.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="?attr/colorControlNormal"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="#000000"
+        android:pathData="M18,13C18,11.35 17.33,9.85 16.24,8.76L17.66,7.34C19.1,8.79 20,10.79 20,13c-0,4.08 -3.05,7.44 -7,7.93L13,18.91C15.83,18.43 18,15.97 18,13ZM4,13C4,8.58 7.58,5 12,5 12.06,5 12.12,5.01 12.18,5.01L11.09,3.92 12.5,2.5 16,6 12.5,9.5 11.09,8.09 12.17,7.01C12.11,7.01 12.05,7 12,7c-3.31,0 -6,2.69 -6,6 -0,2.97 2.17,5.43 5,5.91l-0,2.02c-3.95,-0.49 -7,-3.85 -7,-7.93z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_arrow_upward.xml b/app/src/main/res/drawable/ic_arrow_upward.xml
new file mode 100644
index 0000000..ed082cc
--- /dev/null
+++ b/app/src/main/res/drawable/ic_arrow_upward.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="#000000"
+        android:pathData="M13 19V7.83l4.88 4.88c0.39 0.39 1.03 0.39 1.42 0 0.39-0.39 0.39-1.02 0-1.41l-6.59-6.59c-0.39-0.39-1.02-0.39-1.41 0l-6.6 6.58c-0.39 0.39-0.39 1.02 0 1.41 0.39 0.39 1.02 0.39 1.41 0L11 7.83V19c0 0.55 0.45 1 1 1s1-0.45 1-1z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_auto_awesome.xml b/app/src/main/res/drawable/ic_auto_awesome.xml
new file mode 100644
index 0000000..d900f58
--- /dev/null
+++ b/app/src/main/res/drawable/ic_auto_awesome.xml
@@ -0,0 +1,5 @@
+<vector android:height="24dp" android:tint="#7E7E7E"
+    android:viewportHeight="24" android:viewportWidth="24"
+    android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
+    <path android:fillColor="@android:color/white" android:pathData="M19,9l1.25,-2.75L23,5l-2.75,-1.25L19,1l-1.25,2.75L15,5l2.75,1.25L19,9zM11.5,9.5L9,4 6.5,9.5 1,12l5.5,2.5L9,20l2.5,-5.5L17,12l-5.5,-2.5zM19,15l-1.25,2.75L15,19l2.75,1.25L19,23l1.25,-2.75L23,19l-2.75,-1.25L19,15z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_battery.xml b/app/src/main/res/drawable/ic_battery.xml
new file mode 100644
index 0000000..37c6193
--- /dev/null
+++ b/app/src/main/res/drawable/ic_battery.xml
@@ -0,0 +1,12 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M15.67,4H14V2h-4v2H8.33C7.6,4 7,4.6 7,5.33v15.33C7,21.4 7.6,22 8.33,22h7.33c0.74,0 1.34,-0.6 1.34,-1.33V5.33C17,4.6 16.4,4 15.67,4z" />
+
+</vector>
diff --git a/app/src/main/res/drawable/ic_battery_20.xml b/app/src/main/res/drawable/ic_battery_20.xml
new file mode 100644
index 0000000..57aee1e
--- /dev/null
+++ b/app/src/main/res/drawable/ic_battery_20.xml
@@ -0,0 +1,13 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="45sp"
+    android:height="45sp"
+    android:viewportWidth="25"
+    android:viewportHeight="25">
+    <path
+        android:fillColor="#000000"
+        android:pathData="M7 17v3.67C7 21.4 7.6 22 8.33 22h7.33C16.4 22 17 21.4 17 20.67V17H7z" />
+    <path
+        android:fillAlpha=".3"
+        android:fillColor="#000000"
+        android:pathData="M17 5.33C17 4.6 16.4 4 15.67 4H14V2h-4v2H8.33C7.6 4 7 4.6 7 5.33V17h10V5.33z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_battery_50.xml b/app/src/main/res/drawable/ic_battery_50.xml
new file mode 100644
index 0000000..cc41ff3
--- /dev/null
+++ b/app/src/main/res/drawable/ic_battery_50.xml
@@ -0,0 +1,13 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="45sp"
+    android:height="45sp"
+    android:viewportWidth="25"
+    android:viewportHeight="25">
+    <path
+        android:fillAlpha=".3"
+        android:fillColor="#000000"
+        android:pathData="M17 5.33C17 4.6 16.4 4 15.67 4H14V2h-4v2H8.33C7.6 4 7 4.6 7 5.33V13h10V5.33z" />
+    <path
+        android:fillColor="#000000"
+        android:pathData="M7 13v7.67C7 21.4 7.6 22 8.33 22h7.33C16.4 22 17 21.4 17 20.67V13H7z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_battery_80.xml b/app/src/main/res/drawable/ic_battery_80.xml
new file mode 100644
index 0000000..7cba9a1
--- /dev/null
+++ b/app/src/main/res/drawable/ic_battery_80.xml
@@ -0,0 +1,13 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="45sp"
+    android:height="45sp"
+    android:viewportWidth="25"
+    android:viewportHeight="25">
+    <path
+        android:fillAlpha=".3"
+        android:fillColor="#000000"
+        android:pathData="M17 5.33C17 4.6 16.4 4 15.67 4H14V2h-4v2H8.33C7.6 4 7 4.6 7 5.33V9h10V5.33z" />
+    <path
+        android:fillColor="#000000"
+        android:pathData="M7 9v11.67C7 21.4 7.6 22 8.33 22h7.33C16.4 22 17 21.4 17 20.67V9H7z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_battery_charging_20.xml b/app/src/main/res/drawable/ic_battery_charging_20.xml
new file mode 100644
index 0000000..e15a88a
--- /dev/null
+++ b/app/src/main/res/drawable/ic_battery_charging_20.xml
@@ -0,0 +1,13 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="45sp"
+    android:height="45sp"
+    android:viewportWidth="25"
+    android:viewportHeight="25">
+    <path
+        android:fillColor="#000000"
+        android:pathData="M11 20v-3H7v3.67C7 21.4 7.6 22 8.33 22h7.33C16.4 22 17 21.4 17 20.67V17h-4.4L11 20z" />
+    <path
+        android:fillAlpha=".3"
+        android:fillColor="#000000"
+        android:pathData="M15.67 4H14V2h-4v2H8.33C7.6 4 7 4.6 7 5.33V17h4v-2.5H9L13 7v5.5h2L12.6 17H17V5.33C17 4.6 16.4 4 15.67 4z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_battery_charging_50.xml b/app/src/main/res/drawable/ic_battery_charging_50.xml
new file mode 100644
index 0000000..9972c40
--- /dev/null
+++ b/app/src/main/res/drawable/ic_battery_charging_50.xml
@@ -0,0 +1,13 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="45sp"
+    android:height="45sp"
+    android:viewportWidth="30"
+    android:viewportHeight="30">
+    <path
+        android:fillColor="#000000"
+        android:pathData="M14.47 13.5L11 20v-5.5H9l0.53-1H7v7.17C7 21.4 7.6 22 8.33 22h7.33C16.4 22 17 21.4 17 20.67V13.5h-2.53z" />
+    <path
+        android:fillAlpha=".3"
+        android:fillColor="#000000"
+        android:pathData="M15.67 4H14V2h-4v2H8.33C7.6 4 7 4.6 7 5.33v8.17h2.53L13 7v5.5h2l-0.53 1H17V5.33C17 4.6 16.4 4 15.67 4z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_battery_charging_80.xml b/app/src/main/res/drawable/ic_battery_charging_80.xml
new file mode 100644
index 0000000..6156db8
--- /dev/null
+++ b/app/src/main/res/drawable/ic_battery_charging_80.xml
@@ -0,0 +1,13 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="45sp"
+    android:height="45sp"
+    android:viewportWidth="25"
+    android:viewportHeight="25">
+    <path
+        android:fillAlpha=".3"
+        android:fillColor="#000000"
+        android:pathData="M15.67 4H14V2h-4v2H8.33C7.6 4 7 4.6 7 5.33V9h4.93L13 7v2h4V5.33C17 4.6 16.4 4 15.67 4z" />
+    <path
+        android:fillColor="#000000"
+        android:pathData="M13 12.5h2L11 20v-5.5H9L11.93 9H7v11.67C7 21.4 7.6 22 8.33 22h7.33C16.4 22 17 21.4 17 20.67V9h-4v3.5z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_battery_charging_full.xml b/app/src/main/res/drawable/ic_battery_charging_full.xml
new file mode 100644
index 0000000..4a10b67
--- /dev/null
+++ b/app/src/main/res/drawable/ic_battery_charging_full.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="45sp"
+    android:height="45sp"
+    android:viewportWidth="25"
+    android:viewportHeight="25">
+    <path
+        android:fillColor="#000000"
+        android:pathData="M15.67 4H14V2h-4v2H8.33C7.6 4 7 4.6 7 5.33v15.33C7 21.4 7.6 22 8.33 22h7.33C16.4 22 17 21.4 17 20.67V5.33C17 4.6 16.4 4 15.67 4zM11 20v-5.5H9L13 7v5.5h2L11 20z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_battery_full.xml b/app/src/main/res/drawable/ic_battery_full.xml
new file mode 100644
index 0000000..b3c9a56
--- /dev/null
+++ b/app/src/main/res/drawable/ic_battery_full.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="45sp"
+    android:height="45sp"
+    android:viewportWidth="25"
+    android:viewportHeight="25">
+    <path
+        android:fillColor="#000000"
+        android:pathData="M15.67 4H14V2h-4v2H8.33C7.6 4 7 4.6 7 5.33v15.33C7 21.4 7.6 22 8.33 22h7.33C16.4 22 17 21.4 17 20.67V5.33C17 4.6 16.4 4 15.67 4z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_battery_unknown.xml b/app/src/main/res/drawable/ic_battery_unknown.xml
new file mode 100644
index 0000000..0d80f56
--- /dev/null
+++ b/app/src/main/res/drawable/ic_battery_unknown.xml
@@ -0,0 +1,4 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="45sp" android:height="45sp" android:viewportWidth="25" android:viewportHeight="25">
+    <path android:fillAlpha=".3" android:fillColor="#000000" android:pathData="M10 2v2H8.33C7.6 4 7 4.6 7 5.33v15.34C7 21.4 7.6 22 8.33 22h7.33C16.4 22 17 21.4 17 20.67V5.33C17 4.6 16.4 4 15.67 4H14V2h-4z"/>
+    <path android:fillAlpha=".7" android:fillColor="#000000" android:pathData="M15.456 10.279c0-1.872-1.328-3.072-3.408-3.072-2.304 0-3.504 1.28-3.504 3.744h1.36c0-1.744 0.64-2.496 2.064-2.496 1.232 0 2.048 0.736 2.048 1.824 0 0.736-0.352 1.36-1.2 2.112-0.944 0.848-0.944 0.848-1.216 1.216-0.336 0.448-0.448 0.816-0.448 1.392v0.88h1.44v-0.752c0-0.688 0.176-0.976 1.264-1.984 1.2-1.088 1.6-1.808 1.6-2.864zm-2.864 8.784v-1.664h-1.44v1.664z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_beep.xml b/app/src/main/res/drawable/ic_beep.xml
new file mode 100644
index 0000000..b470554
--- /dev/null
+++ b/app/src/main/res/drawable/ic_beep.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="#7E7E7E">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M12,3v10.55c-0.59,-0.34 -1.27,-0.55 -2,-0.55 -2.21,0 -4,1.79 -4,4s1.79,4 4,4 4,-1.79 4,-4L14,7h4L18,3h-6zM10,19c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2 2,0.9 2,2 -0.9,2 -2,2z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_block.xml b/app/src/main/res/drawable/ic_block.xml
new file mode 100644
index 0000000..c0443c9
--- /dev/null
+++ b/app/src/main/res/drawable/ic_block.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="#000000"
+        android:pathData="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zM4 12c0-4.42 3.58-8 8-8 1.85 0 3.55 0.63 4.9 1.69L5.69 16.9C4.63 15.55 4 13.85 4 12zm8 8c-1.85 0-3.55-0.63-4.9-1.69L18.31 7.1C19.37 8.45 20 10.15 20 12c0 4.42-3.58 8-8 8z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_bluetooth.xml b/app/src/main/res/drawable/ic_bluetooth.xml
new file mode 100644
index 0000000..2410519
--- /dev/null
+++ b/app/src/main/res/drawable/ic_bluetooth.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="#7E7E7E">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M17.71,7.71L12,2h-1v7.59L6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 11,14.41L11,22h1l5.71,-5.71 -4.3,-4.29 4.3,-4.29zM13,5.83l1.88,1.88L13,9.59L13,5.83zM14.88,16.29L13,18.17v-3.76l1.88,1.88z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_bluetooth_searching.xml b/app/src/main/res/drawable/ic_bluetooth_searching.xml
new file mode 100644
index 0000000..78b9d08
--- /dev/null
+++ b/app/src/main/res/drawable/ic_bluetooth_searching.xml
@@ -0,0 +1,11 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="#7E7E7E"
+    android:autoMirrored="true">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M14.24,12.01l2.32,2.32c0.28,-0.72 0.44,-1.51 0.44,-2.33s-0.16,-1.59 -0.43,-2.31l-2.33,2.32zM19.53,6.71l-1.26,1.26c0.63,1.21 0.98,2.57 0.98,4.02s-0.36,2.82 -0.98,4.02l1.2,1.2c0.97,-1.54 1.54,-3.36 1.54,-5.31 -0.01,-1.89 -0.55,-3.67 -1.48,-5.19zM15.71,7.71L10,2L9,2v7.59L4.41,5 3,6.41 8.59,12 3,17.59 4.41,19 9,14.41L9,22h1l5.71,-5.71 -4.3,-4.29 4.3,-4.29zM11,5.83l1.88,1.88L11,9.59L11,5.83zM12.88,16.29L11,18.17v-3.76l1.88,1.88z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_bolt.xml b/app/src/main/res/drawable/ic_bolt.xml
new file mode 100644
index 0000000..6aed899
--- /dev/null
+++ b/app/src/main/res/drawable/ic_bolt.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="#7E7E7E">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M11,21h-1l1,-7H7.5c-0.88,0 -0.33,-0.75 -0.31,-0.78C8.48,10.94 10.42,7.54 13.01,3h1l-1,7h3.51c0.4,0 0.62,0.19 0.4,0.66C12.97,17.55 11,21 11,21z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_brightness_2.xml b/app/src/main/res/drawable/ic_brightness_2.xml
new file mode 100644
index 0000000..cab8d92
--- /dev/null
+++ b/app/src/main/res/drawable/ic_brightness_2.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="#000000"
+        android:pathData="M12.43 2.3C10.05 1.71 7.75 2.03 5.8 2.94 5.45 3.1 5.39 3.58 5.7 3.8 8.3 5.6 10 8.6 10 12c0 3.4-1.7 6.4-4.3 8.2-0.32 0.22-0.26 0.7 0.09 0.86C7.07 21.66 8.5 22 10 22c6.05 0 10.85-5.38 9.87-11.6-0.61-3.92-3.59-7.16-7.44-8.1z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_buds_pro_case.xml b/app/src/main/res/drawable/ic_buds_pro_case.xml
new file mode 100644
index 0000000..769645b
--- /dev/null
+++ b/app/src/main/res/drawable/ic_buds_pro_case.xml
@@ -0,0 +1,3 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="10.399478dp" android:height="10.338884dp" android:tint="#7E7E7E" android:viewportWidth="10.399477" android:viewportHeight="10.338885">
+    <path android:fillColor="#000000" android:pathData="M2.873 9.063C0.861 8.728-0.35 6.263 0.452 4.14c0.698-1.85 1.22-2.386 2.716-2.793C3.791 1.177 6.401 1.146 7.05 1.3c1.278 0.302 1.887 0.728 2.37 1.655 0.707 1.36 0.896 2.223 0.719 3.275C9.88 7.76 8.846 8.841 7.416 9.075 6.858 9.166 3.437 9.157 2.872 9.063zm4.644-0.155c0.604-0.11 1.305-0.487 1.655-0.887 0.307-0.353 0.686-1.108 0.768-1.532l0.053-0.274-0.229 0.244c-0.332 0.355-0.774 0.623-1.34 0.81-1.136 0.378-5.214 0.387-6.41 0.015C1.456 7.11 0.82 6.728 0.58 6.422L0.417 6.215l0.047 0.274c0.07 0.417 0.462 1.196 0.773 1.54 0.331 0.367 0.958 0.724 1.5 0.856 0.506 0.123 4.14 0.14 4.78 0.023zM8.082 7.24c0.917-0.232 1.683-0.775 1.9-1.348 0.209-0.547-0.02-1.558-0.622-2.756-0.47-0.93-1.06-1.376-2.2-1.665-0.677-0.172-3.41-0.148-4.066 0.036-1.07 0.3-1.649 0.765-2.081 1.677-0.995 2.096-0.885 3.04 0.434 3.73 0.83 0.434 1.469 0.509 4.07 0.476C7.078 7.37 7.711 7.333 8.08 7.24z" android:strokeWidth="0.4" android:strokeColor="#000000"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_buds_pro_left.xml b/app/src/main/res/drawable/ic_buds_pro_left.xml
new file mode 100644
index 0000000..12af00c
--- /dev/null
+++ b/app/src/main/res/drawable/ic_buds_pro_left.xml
@@ -0,0 +1,17 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="10.999999dp"
+    android:height="10.999998dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="11"
+    android:viewportHeight="10.999998">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M4.654 2.265h0.368v2.423h1.324V5H4.654zM4.97 9.917c0.357-0.15 0.823-0.536 0.969-0.805C6.01 8.978 5.964 8.977 5.463 9.1 4.48 9.34 3.784 9.109 3.157 8.33c-0.26-0.324-0.296-0.445-0.34-1.178l-0.05-0.817L2.512 6.64C1.69 7.62 2.051 9.288 3.213 9.862c0.489 0.243 1.251 0.266 1.757 0.055zm-1.33 0.329c-0.542-0.13-0.867-0.328-1.226-0.747-0.606-0.708-0.727-1.776-0.3-2.636C2.25 6.592 2.438 6.329 2.53 6.279c0.158-0.085 0.16-0.12 0.026-0.496-0.352-0.992-0.21-2.516 0.328-3.504 0.33-0.604 1.043-1.243 1.604-1.434 1.115-0.38 2.68 0.042 3.461 0.935 1.212 1.384 1.528 3.218 0.817 4.731C8.528 7.02 7.551 8.008 6.84 8.461c-0.343 0.22-0.564 0.444-0.677 0.69-0.372 0.803-1.569 1.322-2.523 1.095zm2.199-1.497c0.978-0.372 2.47-1.672 2.795-2.435 0.783-1.84-0.119-4.304-1.87-5.11-0.807-0.37-1.79-0.385-2.471-0.037C2.842 1.907 2.187 4.02 2.829 5.89c0.096 0.281 0.176 0.771 0.177 1.088 0.005 1.232 0.672 1.948 1.818 1.949 0.305 0 0.75-0.078 1.015-0.179z"
+        android:strokeWidth="0.265"
+        android:strokeColor="#000000" />
+    <path
+        android:fillColor="#000000"
+        android:pathData="M5.705 6.158c0.032-0.082 0.4-0.286 0.819-0.455 0.849-0.343 1.068-0.31 1.068 0.157 0 0.246-0.062 0.297-0.71 0.574-0.864 0.37-0.952 0.56-1.06 0.266-0.107-0.294-0.148-0.461-0.117-0.542z"
+        android:strokeWidth="0.4"
+        android:strokeColor="#000000" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_buds_pro_right.xml b/app/src/main/res/drawable/ic_buds_pro_right.xml
new file mode 100644
index 0000000..0304040
--- /dev/null
+++ b/app/src/main/res/drawable/ic_buds_pro_right.xml
@@ -0,0 +1,17 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="10.999999dp"
+    android:height="10.999999dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="11"
+    android:viewportHeight="10.999999">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M5.69 3.701q0.117 0.04 0.228 0.172 0.113 0.132 0.226 0.363l0.373 0.748H6.122L5.775 4.282Q5.64 4.007 5.513 3.917q-0.126-0.09-0.344-0.09h-0.4v1.157H4.4V2.248h0.83q0.466 0 0.695 0.196 0.229 0.196 0.229 0.592 0 0.258-0.12 0.429Q5.917 3.635 5.689 3.7zM4.768 2.552v0.971H5.23q0.265 0 0.4-0.122 0.137-0.125 0.137-0.365T5.63 2.676q-0.135-0.124-0.4-0.124zm1.26 7.365C5.672 9.767 5.206 9.381 5.06 9.112 4.99 8.978 5.036 8.977 5.537 9.1c0.984 0.24 1.68 0.009 2.306-0.77 0.26-0.324 0.296-0.445 0.34-1.178l0.05-0.817 0.256 0.306c0.822 0.98 0.46 2.647-0.702 3.221-0.489 0.243-1.251 0.266-1.757 0.055zM5.162 8.749c-0.978-0.372-2.47-1.672-2.795-2.435-0.783-1.84 0.119-4.304 1.87-5.11 0.807-0.37 1.79-0.385 2.471-0.037 1.45 0.74 2.105 2.853 1.463 4.723-0.096 0.282-0.176 0.77-0.177 1.089-0.005 1.232-0.672 1.947-1.818 1.949-0.305 0-0.75-0.078-1.015-0.18zm2.2 1.497c0.541-0.13 0.866-0.328 1.225-0.747 0.606-0.708 0.727-1.776 0.3-2.636C8.75 6.592 8.562 6.329 8.47 6.279c-0.158-0.085-0.16-0.12-0.026-0.496 0.352-0.992 0.21-2.516-0.328-3.504-0.33-0.604-1.043-1.243-1.604-1.434-1.115-0.38-2.68 0.042-3.461 0.935-1.212 1.384-1.528 3.218-0.817 4.731 0.238 0.508 1.215 1.497 1.926 1.95 0.343 0.22 0.564 0.444 0.677 0.69 0.372 0.803 1.569 1.322 2.523 1.095z"
+        android:strokeWidth="0.299542"
+        android:strokeColor="#000000" />
+    <path
+        android:fillColor="#000000"
+        android:pathData="M5.295 6.158c-0.031-0.082-0.4-0.286-0.818-0.455-0.85-0.343-1.069-0.31-1.069 0.157 0 0.246 0.063 0.297 0.71 0.574 0.864 0.37 0.947 0.408 1.058 0.19 0.11-0.218 0.15-0.385 0.119-0.466z"
+        android:strokeWidth="0.4"
+        android:strokeColor="#000000" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_calculate.xml b/app/src/main/res/drawable/ic_calculate.xml
new file mode 100644
index 0000000..8be4fa2
--- /dev/null
+++ b/app/src/main/res/drawable/ic_calculate.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="#000000"
+        android:pathData="M19 3H5C3.9 3 3 3.9 3 5v14c0 1.1 0.9 2 2 2h14c1.1 0 2-0.9 2-2V5c0-1.1-0.9-2-2-2zm-5.44 3.53c0.29-0.29 0.77-0.29 1.06 0l0.88 0.88 0.88-0.88c0.29-0.29 0.77-0.29 1.06 0 0.29 0.29 0.29 0.77 0 1.06l-0.88 0.88 0.88 0.88c0.29 0.29 0.29 0.77 0 1.06-0.29 0.29-0.77 0.29-1.06 0L15.5 9.54l-0.88 0.88c-0.29 0.29-0.77 0.29-1.06 0-0.29-0.29-0.29-0.77 0-1.06l0.88-0.88-0.88-0.88c-0.3-0.3-0.3-0.78 0-1.07zM7 7.72h3.5c0.41 0 0.75 0.34 0.75 0.75s-0.34 0.75-0.75 0.75H7c-0.41 0-0.75-0.34-0.75-0.75S6.59 7.72 7 7.72zM10.75 16H9.5v1.25C9.5 17.66 9.16 18 8.75 18S8 17.66 8 17.25V16H6.75C6.34 16 6 15.66 6 15.25s0.34-0.75 0.75-0.75H8v-1.25c0-0.41 0.34-0.75 0.75-0.75s0.75 0.34 0.75 0.75v1.25h1.25c0.41 0 0.75 0.34 0.75 0.75S11.16 16 10.75 16zm6.5 1.25h-3.5c-0.41 0-0.75-0.34-0.75-0.75s0.34-0.75 0.75-0.75h3.5c0.41 0 0.75 0.34 0.75 0.75s-0.34 0.75-0.75 0.75zm0-2.5h-3.5C13.34 14.75 13 14.41 13 14s0.34-0.75 0.75-0.75h3.5c0.41 0 0.75 0.34 0.75 0.75s-0.34 0.75-0.75 0.75z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_calendar_from.xml b/app/src/main/res/drawable/ic_calendar_from.xml
new file mode 100644
index 0000000..29973f9
--- /dev/null
+++ b/app/src/main/res/drawable/ic_calendar_from.xml
@@ -0,0 +1,3 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:tint="#7E7E7E" android:viewportWidth="24" android:viewportHeight="24">
+    <path android:fillColor="@android:color/white" android:pathData="M19 3h-1V1h-2v2H8V1H6v2H5C3.89 3 3 3.9 3 5v14c0 1.1 0.89 2 2 2h14c1.1 0 2-0.9 2-2V5c0-1.1-0.9-2-2-2zm0 16H5V9h14v10zm0-12H5V5h14v2zM7 11h5v5H7z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_calendar_sync.xml b/app/src/main/res/drawable/ic_calendar_sync.xml
new file mode 100644
index 0000000..f25dd4d
--- /dev/null
+++ b/app/src/main/res/drawable/ic_calendar_sync.xml
@@ -0,0 +1,14 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="#000000"
+        android:pathData="M20.019 3h-1V1h-2v2h-10V1h-2v2h-1c-1.1 0-2 0.9-2 2v16c0 1.1 0.9 2 2 2h16c1.1 0 2-0.9 2-2V5c0-1.1-0.9-2-2-2zm0 18h-16V10h16zm0-13h-16V5h16z" />
+    <path
+        android:fillColor="#000000"
+        android:pathData="M12.019 11.63v-1.467l-1.955 1.955 1.955 1.955v-1.466c1.617 0 2.932 1.314 2.932 2.932 0 0.493-0.123 0.962-0.342 1.368l0.713 0.713c0.381-0.6 0.606-1.314 0.606-2.081 0-2.16-1.75-3.91-3.91-3.91zm0 6.84c-1.618 0-2.932-1.314-2.932-2.932 0-0.493 0.122-0.962 0.342-1.368l-0.714-0.713c-0.38 0.6-0.605 1.314-0.605 2.081 0 2.16 1.749 3.91 3.909 3.91v1.465l1.954-1.954-1.954-1.955z"
+        android:strokeWidth="1" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_calendar_to.xml b/app/src/main/res/drawable/ic_calendar_to.xml
new file mode 100644
index 0000000..65dbc28
--- /dev/null
+++ b/app/src/main/res/drawable/ic_calendar_to.xml
@@ -0,0 +1,3 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:tint="#7E7E7E" android:viewportWidth="24" android:viewportHeight="24">
+    <path android:fillColor="@android:color/white" android:pathData="M19 4h-1V2h-2v2H8V2H6v2H5C3.89 4 3.01 4.9 3.01 6L3 20c0 1.1 0.89 2 2 2h14c1.1 0 2-0.9 2-2V6c0-1.1-0.9-2-2-2zm0 16H5V10h14v10zm0-12H5V6h14v2zm-7 5h5v5h-5z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_calendar_today.xml b/app/src/main/res/drawable/ic_calendar_today.xml
new file mode 100644
index 0000000..197339a
--- /dev/null
+++ b/app/src/main/res/drawable/ic_calendar_today.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="#000000"
+        android:pathData="M20 3h-1V2c0-0.55-0.45-1-1-1s-1 0.45-1 1v1H7V2c0-0.55-0.45-1-1-1S5 1.45 5 2v1H4C2.9 3 2 3.9 2 5v16c0 1.1 0.9 2 2 2h16c1.1 0 2-0.9 2-2V5c0-1.1-0.9-2-2-2zm-1 18H5c-0.55 0-1-0.45-1-1V8h16v12c0 0.55-0.45 1-1 1z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_calories.xml b/app/src/main/res/drawable/ic_calories.xml
new file mode 100644
index 0000000..31ab082
--- /dev/null
+++ b/app/src/main/res/drawable/ic_calories.xml
@@ -0,0 +1,3 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24" android:tint="#7E7E7E">
+    <path android:fillColor="@android:color/white" android:pathData="M19.48 12.35c-1.57-4.08-7.16-4.3-5.81-10.23 0.1-0.44-0.37-0.78-0.75-0.55C9.29 3.71 6.68 8 8.87 13.62c0.18 0.46-0.36 0.89-0.75 0.59-1.81-1.37-2-3.34-1.84-4.75 0.06-0.52-0.62-0.77-0.91-0.34C4.69 10.16 4 11.84 4 14.37c0.38 5.6 5.11 7.32 6.81 7.54 2.43 0.31 5.06-0.14 6.95-1.87 2.08-1.93 2.84-5.01 1.72-7.69zm-9.28 5.03c1.44-0.35 2.18-1.39 2.38-2.31 0.33-1.43-0.96-2.83-0.09-5.09 0.33 1.87 3.27 3.04 3.27 5.08 0.08 2.53-2.66 4.7-5.56 2.32z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_camera_remote.xml b/app/src/main/res/drawable/ic_camera_remote.xml
new file mode 100644
index 0000000..dc668b6
--- /dev/null
+++ b/app/src/main/res/drawable/ic_camera_remote.xml
@@ -0,0 +1,16 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M12,14m-3.2,0a3.2,3.2 0,1 1,6.4 0a3.2,3.2 0,1 1,-6.4 0" />
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M16,3.33c2.58,0 4.67,2.09 4.67,4.67H22c0,-3.31 -2.69,-6 -6,-6v1.33M16,6c1.11,0 2,0.89 2,2h1.33c0,-1.84 -1.49,-3.33 -3.33,-3.33V6" />
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M17,9c0,-1.11 -0.89,-2 -2,-2L15,4L9,4L7.17,6L4,6c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,9h-5zM12,19c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5 -2.24,5 -5,5z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_chair.xml b/app/src/main/res/drawable/ic_chair.xml
new file mode 100644
index 0000000..abe9e95
--- /dev/null
+++ b/app/src/main/res/drawable/ic_chair.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="#7E7E7E">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M20,8V6c0,-1.65 -1.35,-3 -3,-3H7C5.35,3 4,4.35 4,6v2c-1.65,0 -3,1.35 -3,3v5c0,1.65 1.35,3 3,3v1c0,0.55 0.45,1 1,1c0.55,0 1,-0.45 1,-1v-1h12v1c0,0.55 0.45,1 1,1c0.55,0 1,-0.45 1,-1v-1c1.65,0 3,-1.35 3,-3v-5C23,9.35 21.65,8 20,8zM6,6c0,-0.55 0.45,-1 1,-1h10c0.55,0 1,0.45 1,1v2.78c-0.61,0.55 -1,1.34 -1,2.22v2H7v-2c0,-0.88 -0.39,-1.67 -1,-2.22V6zM21,16c0,0.55 -0.45,1 -1,1H4c-0.55,0 -1,-0.45 -1,-1v-5c0,-0.55 0.45,-1 1,-1s1,0.45 1,1v4h14v-4c0,-0.55 0.45,-1 1,-1s1,0.45 1,1V16z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_checklist.xml b/app/src/main/res/drawable/ic_checklist.xml
new file mode 100644
index 0000000..ad49fce
--- /dev/null
+++ b/app/src/main/res/drawable/ic_checklist.xml
@@ -0,0 +1,5 @@
+<vector android:height="24dp" android:tint="#7E7E7E"
+    android:viewportHeight="24" android:viewportWidth="24"
+    android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
+    <path android:fillColor="@android:color/white" android:pathData="M22,7h-9v2h9V7zM22,15h-9v2h9V15zM5.54,11L2,7.46l1.41,-1.41l2.12,2.12l4.24,-4.24l1.41,1.41L5.54,11zM5.54,19L2,15.46l1.41,-1.41l2.12,2.12l4.24,-4.24l1.41,1.41L5.54,19z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_circle.xml b/app/src/main/res/drawable/ic_circle.xml
new file mode 100644
index 0000000..f33abf1
--- /dev/null
+++ b/app/src/main/res/drawable/ic_circle.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="#7E7E7E">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M12,2C6.47,2 2,6.47 2,12c0,5.53 4.47,10 10,10s10,-4.47 10,-10C22,6.47 17.53,2 12,2zM12,20c-4.42,0 -8,-3.58 -8,-8c0,-4.42 3.58,-8 8,-8s8,3.58 8,8C20,16.42 16.42,20 12,20z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_create.xml b/app/src/main/res/drawable/ic_create.xml
new file mode 100644
index 0000000..9224b25
--- /dev/null
+++ b/app/src/main/res/drawable/ic_create.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="#000000"
+        android:pathData="M3 17.46v3.04C3 20.78 3.22 21 3.5 21h3.04c0.13 0 0.26-0.05 0.35-0.15L17.81 9.94l-3.75-3.75L3.15 17.1C3.05 17.2 3 17.32 3 17.46zM20.71 7.04c0.39-0.39 0.39-1.02 0-1.41l-2.34-2.34c-0.39-0.39-1.02-0.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_create_new_folder.xml b/app/src/main/res/drawable/ic_create_new_folder.xml
new file mode 100644
index 0000000..3175baa
--- /dev/null
+++ b/app/src/main/res/drawable/ic_create_new_folder.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M20,6h-8l-2,-2L4,4c-1.11,0 -1.99,0.89 -1.99,2L2,18c0,1.11 0.89,2 2,2h16c1.11,0 2,-0.89 2,-2L22,8c0,-1.11 -0.89,-2 -2,-2zM20,18L4,18L4,6h5.17l2,2L20,8v10zM12,14h2v2h2v-2h2v-2h-2v-2h-2v2h-2z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_dashboard.xml b/app/src/main/res/drawable/ic_dashboard.xml
new file mode 100644
index 0000000..be3329b
--- /dev/null
+++ b/app/src/main/res/drawable/ic_dashboard.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="24.0"
+    android:viewportHeight="24.0">
+    <path
+        android:fillColor="#000000"
+        android:pathData="M3,13h8L11,3L3,3v10zM3,21h8v-6L3,15v6zM13,21h8L21,11h-8v10zM13,3v6h8L21,3h-8z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_date_range.xml b/app/src/main/res/drawable/ic_date_range.xml
new file mode 100644
index 0000000..087fdf9
--- /dev/null
+++ b/app/src/main/res/drawable/ic_date_range.xml
@@ -0,0 +1,3 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24" android:tint="#7E7E7E">
+    <path android:fillColor="@android:color/white" android:pathData="M7 11h2v2H7v-2zm14-5v14c0 1.1-0.9 2-2 2H5c-1.11 0-2-0.9-2-2L3.01 6c0-1.1 0.88-2 1.99-2h1V2h2v2h8V2h2v2h1c1.1 0 2 0.9 2 2zM5 8h14V6H5v2zm14 12V10H5v10h14zm-4-7h2v-2h-2v2zm-4 0h2v-2h-2v2z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_delete.xml b/app/src/main/res/drawable/ic_delete.xml
new file mode 100644
index 0000000..20ead7e
--- /dev/null
+++ b/app/src/main/res/drawable/ic_delete.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="#000000"
+        android:pathData="M6 19c0 1.1 0.9 2 2 2h8c1.1 0 2-0.9 2-2V9c0-1.1-0.9-2-2-2H8C6.9 7 6 7.9 6 9v10zM18 4h-2.5l-0.71-0.71C14.61 3.11 14.35 3 14.09 3H9.91c-0.26 0-0.52 0.11-0.7 0.29L8.5 4H6C5.45 4 5 4.45 5 5s0.45 1 1 1h12c0.55 0 1-0.45 1-1s-0.45-1-1-1z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_delete_forever.xml b/app/src/main/res/drawable/ic_delete_forever.xml
new file mode 100644
index 0000000..cfcbaa7
--- /dev/null
+++ b/app/src/main/res/drawable/ic_delete_forever.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="#000000"
+        android:pathData="M6 19c0 1.1 0.9 2 2 2h8c1.1 0 2-0.9 2-2V9c0-1.1-0.9-2-2-2H8C6.9 7 6 7.9 6 9v10zm3.17-7.83c0.39-0.39 1.02-0.39 1.41 0L12 12.59l1.42-1.42c0.39-0.39 1.02-0.39 1.41 0 0.39 0.39 0.39 1.02 0 1.41L13.41 14l1.42 1.42c0.39 0.39 0.39 1.02 0 1.41-0.39 0.39-1.02 0.39-1.41 0L12 15.41l-1.42 1.42c-0.39 0.39-1.02 0.39-1.41 0-0.39-0.39-0.39-1.02 0-1.41L10.59 14l-1.42-1.42c-0.39-0.38-0.39-1.02 0-1.41zM15.5 4l-0.71-0.71C14.61 3.11 14.35 3 14.09 3H9.91c-0.26 0-0.52 0.11-0.7 0.29L8.5 4H6C5.45 4 5 4.45 5 5s0.45 1 1 1h12c0.55 0 1-0.45 1-1s-0.45-1-1-1h-2.5z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_developer_mode.xml b/app/src/main/res/drawable/ic_developer_mode.xml
new file mode 100644
index 0000000..b1dc197
--- /dev/null
+++ b/app/src/main/res/drawable/ic_developer_mode.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="#7E7E7E">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M7,5h10v2h2L19,3c0,-1.1 -0.9,-1.99 -2,-1.99L7,1c-1.1,0 -2,0.9 -2,2v4h2L7,5zM15.41,16.59L20,12l-4.59,-4.59L14,8.83 17.17,12 14,15.17l1.41,1.42zM10,15.17L6.83,12 10,8.83 8.59,7.41 4,12l4.59,4.59L10,15.17zM17,19L7,19v-2L5,17v4c0,1.1 0.9,2 2,2h10c1.1,0 2,-0.9 2,-2v-4h-2v2z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_device_amazfit_bip.xml b/app/src/main/res/drawable/ic_device_amazfit_bip.xml
new file mode 100644
index 0000000..bb2f89a
--- /dev/null
+++ b/app/src/main/res/drawable/ic_device_amazfit_bip.xml
@@ -0,0 +1,26 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="45sp"
+    android:height="45sp"
+    android:viewportWidth="30"
+    android:viewportHeight="30">
+    <path
+        android:fillColor="?attr/deviceIconLight"
+        android:pathData="M3.871 3.877h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.824a0.947 0.947 0 0 1 0.947-0.947z"
+        android:strokeWidth="3.57115173" />
+    <path
+        android:fillColor="?attr/deviceIconDark"
+        android:pathData="M3.879 3.035h20.925a0.947 0.947 0 0 1 0.947 0.947v20.01a0.947 0.947 0 0 1-0.947 0.948H3.88a0.947 0.947 0 0 1-0.947-0.948V3.982A0.947 0.947 0 0 1 3.88 3.035z"
+        android:strokeWidth="3.57115173" />
+    <path
+        android:fillColor="?attr/deviceIconPrimary"
+        android:pathData="M3.871 3.413h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.36A0.947 0.947 0 0 1 3.87 3.413z"
+        android:strokeWidth="3.57115173" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M11.407 7.52c-0.814-0.016-0.908 0.156-0.885 0.967-0.72 0.126-1.342 1.159-1.325 2.124l0.005 7.825c0.093 0.883 0.625 1.487 1.32 1.867 0 0.813 0.077 0.99 0.885 0.99h5.81c0.847 0 0.826-0.236 0.805-0.99 0.872-0.416 1.21-0.892 1.295-1.867l0.002-3.437c0.123-0.205 0.15-0.028 0.219 0.05 0.094 0.11 0.214 0.063 0.267-0.09l0.003-1.14c-0.055-0.114-0.1-0.213-0.25-0.117-0.078 0.049-0.11 0.22-0.24 0.085v-3.444c-0.087-0.903-0.473-1.51-1.295-1.838 0.007-0.945-0.06-0.991-0.806-0.98zm0.05 1.428L17.234 9c0.805 0.007 1.47 0.828 1.47 1.343l-0.028 7.942c-0.003 0.832-0.717 1.512-1.443 1.512h-5.925c-0.536 0-1.498-0.65-1.492-1.426l0.062-7.878c0.008-0.957 0.76-1.554 1.58-1.546z" />
+    <path
+        android:fillAlpha="0.731"
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M11.457 8.948c-0.82-0.007-1.572 0.59-1.58 1.547l-0.062 7.877c-0.006 0.776 0.956 1.425 1.492 1.425h5.924c0.725 0 1.44-0.68 1.443-1.512l0.027-7.942C18.703 9.828 18.037 9.007 17.23 9zm-0.482 1.812h6.567v6.696h-6.568z"
+        android:strokeAlpha="0.731" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_device_amazfit_bip_disabled.xml b/app/src/main/res/drawable/ic_device_amazfit_bip_disabled.xml
new file mode 100644
index 0000000..84730f7
--- /dev/null
+++ b/app/src/main/res/drawable/ic_device_amazfit_bip_disabled.xml
@@ -0,0 +1,26 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="45sp"
+    android:height="45sp"
+    android:viewportWidth="30"
+    android:viewportHeight="30">
+    <path
+        android:fillColor="#7a7a7a"
+        android:pathData="M3.871 3.877h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.824a0.947 0.947 0 0 1 0.947-0.947z"
+        android:strokeWidth="3.57115173" />
+    <path
+        android:fillColor="#9f9f9f"
+        android:pathData="M3.879 3.035h20.925a0.947 0.947 0 0 1 0.947 0.947v20.01a0.947 0.947 0 0 1-0.947 0.948H3.88a0.947 0.947 0 0 1-0.947-0.948V3.982A0.947 0.947 0 0 1 3.88 3.035z"
+        android:strokeWidth="3.57115173" />
+    <path
+        android:fillColor="#8a8a8a"
+        android:pathData="M3.871 3.413h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.36A0.947 0.947 0 0 1 3.87 3.413z"
+        android:strokeWidth="3.57115173" />
+    <path
+        android:fillColor="#ffffff"
+        android:pathData="M11.342 7.48c-0.814-0.017-0.908 0.155-0.885 0.966-0.72 0.126-1.342 1.159-1.325 2.124l0.005 7.825c0.093 0.883 0.625 1.487 1.32 1.867 0 0.813 0.077 0.99 0.885 0.99h5.81c0.847 0 0.826-0.236 0.805-0.99 0.872-0.416 1.21-0.892 1.295-1.867l0.002-3.437c0.123-0.205 0.151-0.028 0.22 0.05 0.093 0.11 0.213 0.063 0.266-0.09l0.003-1.14c-0.055-0.114-0.1-0.213-0.25-0.117-0.078 0.049-0.11 0.22-0.24 0.085v-3.444c-0.087-0.903-0.473-1.51-1.295-1.838 0.007-0.945-0.06-0.991-0.806-0.98zm0.051 1.427l5.775 0.052c0.805 0.007 1.471 0.828 1.47 1.343l-0.028 7.942c-0.003 0.832-0.717 1.512-1.443 1.512h-5.925c-0.536 0-1.498-0.65-1.492-1.426l0.062-7.878c0.008-0.957 0.761-1.554 1.58-1.546z" />
+    <path
+        android:fillAlpha="0.731"
+        android:fillColor="#ffffff"
+        android:pathData="M11.392 8.907c-0.819-0.007-1.572 0.59-1.58 1.547L9.75 18.33c-0.006 0.776 0.956 1.425 1.492 1.425h5.924c0.725 0 1.44-0.68 1.443-1.512l0.027-7.942c0.002-0.515-0.664-1.336-1.469-1.343zm-0.482 1.812h6.567v6.696H10.91z"
+        android:strokeAlpha="0.731" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_device_banglejs.xml b/app/src/main/res/drawable/ic_device_banglejs.xml
new file mode 100644
index 0000000..54f7cde
--- /dev/null
+++ b/app/src/main/res/drawable/ic_device_banglejs.xml
@@ -0,0 +1,34 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="45sp"
+    android:height="45sp"
+    android:viewportWidth="30"
+    android:viewportHeight="30">
+    <path
+        android:fillColor="?attr/deviceIconLight"
+        android:pathData="M3.871 3.877h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.824a0.947 0.947 0 0 1 0.947-0.947z"
+        android:strokeWidth="3.6" />
+    <path
+        android:fillColor="?attr/deviceIconDark"
+        android:pathData="M3.879 3.035h20.925a0.947 0.947 0 0 1 0.947 0.947v20.01a0.947 0.947 0 0 1-0.947 0.948H3.88a0.947 0.947 0 0 1-0.947-0.948V3.982A0.947 0.947 0 0 1 3.88 3.035z"
+        android:strokeWidth="3.6" />
+    <path
+        android:fillColor="?attr/deviceIconPrimary"
+        android:pathData="M3.871 3.413h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.36A0.947 0.947 0 0 1 3.87 3.413z"
+        android:strokeWidth="3.6" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M12.27 6.708h4.54a0.425 0.425 0 0 1 0.426 0.425v15.04a0.425 0.425 0 0 1-0.425 0.424h-4.542a0.425 0.425 0 0 1-0.425-0.425V7.132a0.425 0.425 0 0 1 0.425-0.424z"
+        android:strokeWidth="2.4" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M10.915 9.692h7.294a1.104 1.104 0 0 1 1.104 1.105v7.293a1.104 1.104 0 0 1-1.104 1.104h-7.294a1.104 1.104 0 0 1-1.104-1.104v-7.293a1.104 1.104 0 0 1 1.104-1.105z"
+        android:strokeWidth="3.5" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M19 13.485h0.407a0.26 0.26 0 0 1 0.26 0.261v1.556a0.26 0.26 0 0 1-0.26 0.26H19a0.26 0.26 0 0 1-0.26-0.26v-1.556a0.26 0.26 0 0 1 0.26-0.26z"
+        android:strokeWidth="3.5" />
+    <path
+        android:fillColor="?attr/deviceIconPrimary"
+        android:pathData="M11.716 10.731h5.706a0.866 0.838 90 0 1 0.838 0.866v5.008a0.866 0.838 90 0 1-0.838 0.866h-5.707a0.866 0.838 90 0 1-0.837-0.866v-5.008a0.866 0.838 90 0 1 0.838-0.866z"
+        android:strokeWidth="5.1" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_device_banglejs_disabled.xml b/app/src/main/res/drawable/ic_device_banglejs_disabled.xml
new file mode 100644
index 0000000..d7a69c7
--- /dev/null
+++ b/app/src/main/res/drawable/ic_device_banglejs_disabled.xml
@@ -0,0 +1,35 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="45sp"
+    android:height="45sp"
+    android:viewportWidth="30"
+    android:viewportHeight="30">
+    <path
+        android:fillColor="#7a7a7a"
+        android:pathData="M3.871 3.877h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.824a0.947 0.947 0 0 1 0.947-0.947z"
+        android:strokeWidth="3.57115173" />
+    <path
+        android:fillAlpha="0.9411765"
+        android:fillColor="#9f9f9f"
+        android:pathData="M3.879 3.035h20.925a0.947 0.947 0 0 1 0.947 0.947v20.01a0.947 0.947 0 0 1-0.947 0.948H3.88a0.947 0.947 0 0 1-0.947-0.948V3.982A0.947 0.947 0 0 1 3.88 3.035z"
+        android:strokeWidth="3.57115173" />
+    <path
+        android:fillColor="#8a8a8a"
+        android:pathData="M3.871 3.413h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.36A0.947 0.947 0 0 1 3.87 3.413z"
+        android:strokeWidth="3.57115173" />
+    <path
+        android:fillColor="#ffffff"
+        android:pathData="M12.278 6.399h4.542a0.425 0.425 0 0 1 0.425 0.425v15.04a0.425 0.425 0 0 1-0.425 0.424h-4.542a0.425 0.425 0 0 1-0.425-0.425V6.824a0.425 0.425 0 0 1 0.425-0.425z"
+        android:strokeWidth="2.43489814" />
+    <path
+        android:fillColor="#ffffff"
+        android:pathData="M10.925 9.383h7.293a1.104 1.104 0 0 1 1.104 1.105v7.293a1.104 1.104 0 0 1-1.104 1.104h-7.293a1.104 1.104 0 0 1-1.105-1.104v-7.293a1.104 1.104 0 0 1 1.105-1.104z"
+        android:strokeWidth="3.5" />
+    <path
+        android:fillColor="#ffffff"
+        android:pathData="M19 13.485h0.407a0.26 0.26 0 0 1 0.26 0.261v1.556a0.26 0.26 0 0 1-0.26 0.26H19a0.26 0.26 0 0 1-0.26-0.26v-1.556a0.26 0.26 0 0 1 0.26-0.26z"
+        android:strokeWidth="3.5" />
+    <path
+        android:fillColor="#8a8a8a"
+        android:pathData="M11.725 10.422h5.706a0.866 0.838 90 0 1 0.838 0.866v5.008a0.866 0.838 90 0 1-0.838 0.866h-5.706a0.866 0.838 90 0 1-0.838-0.866v-5.008a0.866 0.838 90 0 1 0.838-0.866z"
+        android:strokeWidth="5.10916853" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_device_default.xml b/app/src/main/res/drawable/ic_device_default.xml
new file mode 100644
index 0000000..c0839ba
--- /dev/null
+++ b/app/src/main/res/drawable/ic_device_default.xml
@@ -0,0 +1,24 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="45sp"
+    android:height="45sp"
+    android:viewportWidth="30"
+    android:viewportHeight="30">
+    <path
+        android:fillColor="?attr/deviceIconLight"
+        android:pathData="M3.871 3.877h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.824a0.947 0.947 0 0 1 0.947-0.947z"
+        android:strokeWidth="3.57" />
+    <path
+        android:fillColor="?attr/deviceIconDark"
+        android:pathData="M3.879 3.035h20.925a0.947 0.947 0 0 1 0.947 0.947v20.01a0.947 0.947 0 0 1-0.947 0.948H3.88a0.947 0.947 0 0 1-0.947-0.948V3.982A0.947 0.947 0 0 1 3.88 3.035z"
+        android:strokeWidth="3.57" />
+    <path
+        android:fillColor="?attr/deviceIconPrimary"
+        android:pathData="M3.871 3.413h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.36A0.947 0.947 0 0 1 3.87 3.413z"
+        android:strokeWidth="3.57" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M12 9.955h4.575a0.507 0.567 0 0 1 0.507 0.567v7.836a0.507 0.567 0 0 1-0.507 0.567h-4.577a0.507 0.567 0 0 1-0.507-0.567v-7.836a0.507 0.567 0 0 1 0.507-0.567zm0.581 9.735h3.382v1.45h-3.382zM12.554 7.693h3.382v1.45h-3.382z" />
+    <path
+        android:fillColor="?attr/deviceIconPrimary"
+        android:pathData="M12.363 10.948h3.907v6.978h-3.907z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_device_default_disabled.xml b/app/src/main/res/drawable/ic_device_default_disabled.xml
new file mode 100644
index 0000000..d1f118e
--- /dev/null
+++ b/app/src/main/res/drawable/ic_device_default_disabled.xml
@@ -0,0 +1,24 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="45sp"
+    android:height="45sp"
+    android:viewportWidth="30"
+    android:viewportHeight="30">
+    <path
+        android:fillColor="#7a7a7a"
+        android:pathData="M3.871 3.877h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.824a0.947 0.947 0 0 1 0.947-0.947z"
+        android:strokeWidth="3.57" />
+    <path
+        android:fillColor="#9f9f9f"
+        android:pathData="M3.879 3.035h20.925a0.947 0.947 0 0 1 0.947 0.947v20.01a0.947 0.947 0 0 1-0.947 0.948H3.88a0.947 0.947 0 0 1-0.947-0.948V3.982A0.947 0.947 0 0 1 3.88 3.035z"
+        android:strokeWidth="3.57" />
+    <path
+        android:fillColor="#8a8a8a"
+        android:pathData="M3.871 3.413h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.36A0.947 0.947 0 0 1 3.87 3.413z"
+        android:strokeWidth="3.57" />
+    <path
+        android:fillColor="#ffffff"
+        android:pathData="M12.15 9.904h4.576a0.507 0.567 0 0 1 0.507 0.567v7.836a0.507 0.567 0 0 1-0.507 0.567h-4.577a0.507 0.567 0 0 1-0.507-0.567V10.47a0.507 0.567 0 0 1 0.507-0.567zm0.582 9.735h3.382v1.45h-3.382zM12.705 7.642h3.382v1.45h-3.382z" />
+    <path
+        android:fillColor="#8a8a8a"
+        android:pathData="M12.483 10.865h3.907v6.978h-3.907z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_device_flipper.xml b/app/src/main/res/drawable/ic_device_flipper.xml
new file mode 100644
index 0000000..2340501
--- /dev/null
+++ b/app/src/main/res/drawable/ic_device_flipper.xml
@@ -0,0 +1,238 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="45sp"
+    android:height="45sp"
+    android:viewportWidth="30"
+    android:viewportHeight="30">
+    <path
+        android:fillColor="?attr/deviceIconLight"
+        android:pathData="M3.871 3.877h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.824a0.947 0.947 0 0 1 0.947-0.947z"
+        android:strokeWidth="3.57115" />
+    <path
+        android:fillColor="?attr/deviceIconDark"
+        android:pathData="M3.879 3.035h20.925a0.947 0.947 0 0 1 0.947 0.947v20.01a0.947 0.947 0 0 1-0.947 0.948H3.88a0.947 0.947 0 0 1-0.947-0.948V3.982A0.947 0.947 0 0 1 3.88 3.035z"
+        android:strokeWidth="3.57115" />
+    <path
+        android:fillColor="?attr/deviceIconPrimary"
+        android:pathData="M3.871 3.413h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.36A0.947 0.947 0 0 1 3.87 3.413z"
+        android:strokeWidth="3.57115" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M19.807 17.496h0.249v-0.249h-1.161v0.249H19.6zm-7.339-0.042h0.456v-0.207h-0.456z"
+        android:strokeWidth="0.99999803" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M8.096 17.415l0.001-0.249 0.249 0.002-0.001 0.249zM7.638 17.45l0.002-0.248 0.248 0.001-0.001 0.249zM7.18 17.445l0.002-0.25 0.249 0.002-0.001 0.249z"
+        android:strokeWidth="1.0000112" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M20.056 17.04v0.207h0.207l0.249 0.042h0.456v-0.25h-0.706v-0.248h-0.455v0.249z"
+        android:strokeWidth="0.99999803" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M12.236 17.241l0.002-0.249 0.249 0.002-0.002 0.249zM8.307 17.21l0.001-0.249 0.249 0.002-0.001 0.249zM7.85 17.204v-0.249l0.25 0.002-0.002 0.249zm-0.458-0.006l0.001-0.249 0.249 0.002-0.001 0.248zm-0.458-0.006l0.002-0.249 0.249 0.002-0.002 0.248z"
+        android:strokeWidth="1.0000112" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M21.424 17.04h0.249v-0.207h-0.456l-0.25-0.042v0.249h0.25z"
+        android:strokeWidth="0.99999803" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M8.56 16.964v-0.249l0.25 0.001-0.002 0.25zm-0.458-0.006l0.001-0.249 0.249 0.001-0.001 0.25zm-0.458-0.006l0.002-0.249 0.249 0.001-0.002 0.25zm-0.457 0.035l0.001-0.249 0.249 0.002-0.001 0.249zM6.73 16.981v-0.249l0.25 0.002-0.002 0.249z"
+        android:strokeWidth="1.0000112" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M21.88 16.832h0.249v-0.248h-0.456v0.249zm-2.073-0.248H19.35v0.207h0.458z"
+        android:strokeWidth="0.99999803" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M8.313 16.753l0.001-0.249 0.249 0.001-0.001 0.25zm-0.458-0.006l0.002-0.249 0.248 0.001v0.25zM7.398 16.74l0.001-0.248 0.25 0.001-0.002 0.249zM6.94 16.735l0.002-0.25 0.249 0.002-0.002 0.249zm-0.457-0.006l0.001-0.25 0.25 0.002-0.002 0.249zM8.108 16.5l0.001-0.248 0.25 0.001-0.002 0.249zM7.65 16.494l0.002-0.248H7.9l-0.002 0.25z"
+        android:strokeWidth="1.0000112" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M7.193 16.53l0.001-0.249 0.249 0.001-0.001 0.25zm-0.457-0.006v-0.25l0.25 0.002-0.002 0.249z"
+        android:strokeWidth="1.0000112" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M18.895 15.63l0.248 0.042v-0.25h0.208l0.041-0.248H19.6v-0.207h0.25v-0.25h0.207v-0.206h0.248v-0.25h0.208v-0.207h0.248v-0.248h0.25v-0.208h0.207V13.35h0.248v-0.208h0.208v-0.249h0.248v-0.207h0.208v-0.249h0.249v-0.912H22.17v-0.207h-0.249l-0.248-0.042h-0.208V11.07h-2.073v0.207h-0.456v0.25H18.48v0.206h-0.456v-0.207h-0.249v-0.497h-0.207V10.57h-0.249v-0.207h-0.207v-0.249h-0.25V9.908h-0.206V9.659H16.2V9.41h-0.457V9.203H15.04V8.954h-1.161V8.747L13.67 8.706h-3.234v0.248H9.524v0.208H8.818V9.41H8.613v0.208H8.156L8.114 9.867H7.907l-0.25-0.042v0.249H7.452v0.249H7.2v0.207H6.996v0.249h-0.25v0.207H6.539l-0.041 0.249v0.207H6.29v0.456H6.04v0.456H5.835v0.706H6.04v-0.458h0.25v-0.455h0.207v-0.456h0.249v-0.456h0.249v-0.249h0.207v-0.207H7.45v-0.25h0.207v-0.206h0.249v-0.25h0.457V9.868h0.456v-0.25h2.778V9.41H9.731V9.162h0.954V8.954h2.986v0.25h0.207V9.41h0.248V9.203h0.208V9.41h1.161v0.457h0.207l0.249 0.041h0.456v0.207h0.249v0.25h0.207v0.206h0.249v0.25h0.207v0.662h-0.207l-0.042 0.248h0.705v0.459h0.456v-0.21h0.456v-0.249h0.456v-0.207h0.498v-0.249h1.576v0.25h0.704v0.248h0.208v0.207h-0.912v0.249h-0.664v0.207h-0.497v0.25h-0.208L19.6 12.644v0.248h-0.207v0.25h-0.249v0.207h-0.207v0.248h-0.249v0.208H18.48l-0.042 0.248h-0.207v0.208h-0.249v0.249h-0.207v0.207h-0.249v0.249H17.32v0.207h-0.25v0.249h-0.249v0.207h-0.207v0.249h-0.249v0.207h-0.207v0.249h-0.249v0.207h-0.207v0.249h-0.456l-0.042 0.207h-0.456v0.25h-0.663v0.206h-1.162v0.25h5.972v-0.208h-2.737v-0.498h0.207V16.79h0.25v-0.249h0.206v-0.207h1.618v0.249h0.911v-0.25h2.529l0.249 0.042v0.208h0.457v0.705h-0.249v0.207H22.13v0.249h-0.25v0.207h-0.207V18.2h-0.456l-0.042 0.207h-0.663v0.249h-0.706v0.207h-0.662l-0.041 0.249h-0.914v0.207h-0.91v0.25H16.82l-0.248-0.042v0.248h-0.208v0.25h1.617v-0.25h0.459V19.32h0.91v-0.207h0.705v-0.249h0.664v-0.207h0.705v-0.249h0.456v-0.207h0.249v-0.249h0.207v-0.207h0.249v-0.25h0.207v-0.662l0.042-0.248v-0.208h-0.25v-0.248h-3.896v-0.25h0.208z"
+        android:strokeWidth="0.99999803" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M7.862 16.29v-0.25l0.25 0.002L8.11 16.29zm-0.458-0.007l0.001-0.249 0.25 0.002-0.002 0.249zm-0.457-0.006v-0.249l0.25 0.002-0.002 0.249zM6.49 16.271v-0.249l0.25 0.002-0.002 0.248zm1.167-0.234v-0.249l0.25 0.001-0.002 0.25zM7.2 16.03v-0.248l0.25 0.001-0.002 0.25z"
+        android:strokeWidth="1.0000112" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M6.742 16.066v-0.249l0.25 0.002-0.002 0.249zm2.04-0.222l0.002-0.249 0.249 0.001-0.002 0.25z"
+        android:strokeWidth="1.0000112" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M9.026 15.837v0.456H8.778v-0.248H8.57v-0.208H8.322v-0.456H8.114v0.207H7.865v0.25h0.25v0.207h0.207v0.248H8.57v0.208h0.208v0.248h0.248V17h0.208v-0.706h0.249v-0.457h-0.25z"
+        android:strokeWidth="0.99999803" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M7.41 15.826l0.001-0.249 0.25 0.001-0.002 0.25zM6.953 15.82v-0.25l0.25 0.001-0.002 0.25zm-0.458-0.006l0.001-0.25 0.25 0.002-0.002 0.249zm1.168-0.234l0.001-0.25 0.249 0.002-0.002 0.249zm-0.458-0.007l0.001-0.248 0.25 0.001-0.002 0.249z"
+        android:strokeWidth="1.0000112" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M6.706 15.567l0.002-0.249 0.248 0.002v0.249z"
+        android:strokeWidth="1.0000112" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M8.57 15.381v0.207h0.208v-0.455H8.57z"
+        android:strokeWidth="0.99999803" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M7.416 15.368l0.001-0.248h0.25l-0.002 0.25zm-0.457-0.006l0.001-0.248h0.249l-0.001 0.25z"
+        android:strokeWidth="1.0000112" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M12.012 14.925v-1.244h-0.25v-0.498h-0.248v-0.248h-0.497v-0.249H9.772v0.249H9.276v0.249h-0.25v0.497H8.778v1.245h0.248v0.497h0.25v0.497h0.248v-0.248h0.497v-0.25h0.747v-0.248h1.492v-0.249zm-2.073-0.912v-0.249h0.498v0.498H9.938z"
+        android:strokeWidth="0.99999803" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M7.17 15.116l0.001-0.249 0.249 0.001-0.001 0.25z"
+        android:strokeWidth="1.0000112" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M13.173 14.718v-1.41h-0.25v1.617h0.706v-0.207H13.38zM8.57 13.764v-0.249H8.363v0.457L8.321 14.22v0.912h0.25v-1.161z"
+        android:strokeWidth="0.99999803" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M13.836 14.718h0.249v-0.249h-0.457v0.249zm0.498-0.249h0.207v-0.207h-0.457v0.207zm0.456-0.207h0.207v-0.25h-0.456v0.25zm0.456-0.249h0.207v-0.207h-0.456v0.207zm-2.778 0v1.161H12.26v0.207h0.456v-1.824h-0.25v0.207z"
+        android:strokeWidth="0.99999803" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M15.463 13.811l0.002-0.248h0.249l-0.002 0.25z"
+        android:strokeWidth="1.0000112" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M15.95 13.557h0.208V13.35h-0.457v0.207zm-3.482-0.249v-0.207H12.26v0.456h0.207zM8.57 13.06v0.454h0.25V13.06zm-0.456 0.248H7.907l-0.042 0.207v1.369H7.658v0.248h0.207v0.25h0.25v-1.867z"
+        android:strokeWidth="0.99999803" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M16.156 13.342l0.001-0.249 0.249 0.002-0.001 0.249z"
+        android:strokeWidth="1.0000112" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M16.614 13.1h0.249v-0.207h-0.456v0.208zm-1.824-0.248h-0.456v0.249h0.912v-0.208l-0.249-0.041zM12.717 13.1v0.208h0.207l0.041-0.207v-0.249h-0.248z"
+        android:strokeWidth="0.99999803" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M12.021 13.1l0.001-0.248h0.25l-0.002 0.25zm-3.202-0.042l0.001-0.249 0.249 0.002-0.001 0.248z"
+        android:strokeWidth="1.0000112" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M8.363 13.308v-0.456H8.114v0.457z"
+        android:strokeWidth="0.99999803" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M16.848 12.873l0.001-0.249 0.25 0.002-0.002 0.249z"
+        android:strokeWidth="1.0000112" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M15.495 12.893h0.207v-0.248h-0.457v0.248z"
+        android:strokeWidth="0.99999803" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M14.103 12.878l0.002-0.249 0.248 0.002v0.249zm-1.601 0l0.001-0.249 0.25 0.001-0.002 0.25zm-0.686-0.03l0.001-0.249 0.25 0.002-0.002 0.248zM9.03 12.853l0.001-0.249 0.249 0.001-0.001 0.25zm-0.686-0.03l0.001-0.249 0.249 0.002-0.001 0.248z"
+        android:strokeWidth="1.0000112" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M17.319 12.645h0.207l0.042-0.208H17.07v0.208zm-1.369 0h0.208v-0.208l-0.207-0.041H15.7v0.249z"
+        android:strokeWidth="0.99999803" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M13.857 12.626l0.001-0.25 0.25 0.002-0.002 0.249zm-1.601-0.001l0.001-0.249 0.249 0.002-0.001 0.249z"
+        android:strokeWidth="1.0000112" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M11.804 12.396h-0.456v0.207l0.208 0.042h0.248zm-2.322 0H9.275v0.207h0.457v-0.207z"
+        android:strokeWidth="0.99999803" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M8.596 12.618l0.001-0.249 0.25 0.002-0.002 0.249zm8.944-0.214l0.002-0.249 0.249 0.002-0.002 0.248zm-1.372 0.023l0.001-0.248h0.25l-0.002 0.25z"
+        android:strokeWidth="1.0000112" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M11.1 12.396h0.248v-0.249H9.731v0.249h1.162z"
+        android:strokeWidth="0.99999803" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M8.807 12.372l0.002-0.25 0.248 0.002v0.249z"
+        android:strokeWidth="1.0000112" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M16.614 12.189h0.249V11.98l-0.249-0.041h-0.207v0.249z"
+        android:strokeWidth="0.99999803" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M15.235 12.186l0.002-0.248h0.249l-0.002 0.25z"
+        android:strokeWidth="1.0000112" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M13.629 12.189v0.207h0.249v-0.664h-0.25v0.208zM9.482 11.94h2.074v0.207h0.456v0.249h0.249v-0.249l0.248 0.042V11.69h-0.248v0.249h-0.25l0.042-0.249h-0.249v-0.207h-0.248v0.207H9.524z"
+        android:strokeWidth="0.99999803" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M9.275 12.147h0.207V11.94H9.026v0.207z"
+        android:strokeWidth="0.99999803" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M16.86 11.958l0.002-0.249 0.248 0.002v0.249zm-1.83-0.024l0.002-0.249 0.249 0.002-0.002 0.248zm0.898-0.217l0.001-0.248h0.249l-0.001 0.25z"
+        android:strokeWidth="1.0000112" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M14.784 11.723l0.001-0.249 0.25 0.002-0.002 0.248z"
+        android:strokeWidth="1.0000112" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M13.878 11.276v0.457h0.207v-0.458z"
+        android:strokeWidth="0.99999803" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M12.04 11.687v-0.25l0.25 0.002-0.002 0.249zm3.683-0.18l0.001-0.25 0.249 0.002-0.002 0.249z"
+        android:strokeWidth="1.0000112" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M11.793 11.476l0.001-0.25 0.249 0.002-0.001 0.249zm4.827-0.186l0.001-0.249 0.249 0.001-0.001 0.25zm-1.144-0.036l0.002-0.249 0.248 0.001v0.25zm-1.372 0.023l0.001-0.249 0.249 0.002-0.001 0.249zm-2.058-0.047v-0.25l0.25 0.002-0.002 0.249zm4.369-0.193l0.001-0.248h0.25l-0.002 0.25z"
+        android:strokeWidth="1.0000112" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M15.271 11.043l0.002-0.249 0.248 0.001v0.25zm-0.956-0.013l0.001-0.248 0.25 0.001-0.002 0.249z"
+        android:strokeWidth="1.0000112" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M12.51 11.691h0.207v-0.457h0.248v-0.455h-0.248v0.25h-0.208v0.207H12.26v0.249h0.25z"
+        android:strokeWidth="0.99999803" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M12.256 11.024l0.002-0.249 0.249 0.002-0.002 0.248zm-0.457-0.006L11.8 10.77l0.249 0.002-0.001 0.248zm4.37-0.192v-0.249l0.25 0.002-0.002 0.249zm-1.601-0.042v-0.249l0.25 0.002-0.002 0.249zm-1.602 0l0.002-0.249 0.249 0.001-0.002 0.25zm-0.457-0.006l0.001-0.249 0.25 0.001-0.002 0.25zm-0.457-0.006v-0.25l0.25 0.002-0.002 0.249zm3.912-0.198v-0.249l0.25 0.001-0.002 0.25z"
+        android:strokeWidth="1.0000112" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M14.778 10.58l0.002-0.25 0.249 0.002-0.002 0.249zm-2.058-0.007l0.001-0.25 0.249 0.002-0.001 0.249zm-0.458-0.006l0.002-0.25 0.249 0.002-0.002 0.249zm2.769-0.234l0.001-0.249 0.25 0.002-0.002 0.248z"
+        android:strokeWidth="1.0000112" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M12.717 10.115h0.248V9.867H12.26v0.248h0.25v0.208h0.207z"
+        android:strokeWidth="0.99999803" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M15.242 10.128l0.001-0.249 0.249 0.001-0.001 0.25zm-1.601-0.042l0.001-0.249 0.25 0.002-0.002 0.248z"
+        android:strokeWidth="1.0000112" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M13.421 10.323h0.208l0.041-0.208h-0.249V9.867h-0.248v0.248h-0.208v0.208h0.208v0.248h0.249z"
+        android:strokeWidth="0.99999803" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M13.893 9.881l0.002-0.249 0.249 0.002-0.002 0.248zm-0.457-0.006l0.001-0.249 0.249 0.002-0.001 0.248z"
+        android:strokeWidth="1.0000112" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M11.804 9.867h0.456V9.659l-0.207-0.041h-0.456v0.249z"
+        android:strokeWidth="0.99999803" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M14.104 9.635l0.002-0.25 0.249 0.002-0.002 0.249zm-0.457-0.006l0.001-0.25 0.25 0.002-0.002 0.249z"
+        android:strokeWidth="1.0000112" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_device_flipper_disabled.xml b/app/src/main/res/drawable/ic_device_flipper_disabled.xml
new file mode 100644
index 0000000..e8cbd61
--- /dev/null
+++ b/app/src/main/res/drawable/ic_device_flipper_disabled.xml
@@ -0,0 +1,243 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="45sp"
+    android:height="45sp"
+    android:viewportWidth="30"
+    android:viewportHeight="30">
+    <path
+        android:fillColor="#7a7a7a"
+        android:pathData="M3.871 3.877h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.824a0.947 0.947 0 0 1 0.947-0.947z"
+        android:strokeWidth="3.57115" />
+    <path
+        android:fillAlpha="0.941176"
+        android:fillColor="#9f9f9f"
+        android:pathData="M3.879 3.035h20.925a0.947 0.947 0 0 1 0.947 0.947v20.01a0.947 0.947 0 0 1-0.947 0.948H3.88a0.947 0.947 0 0 1-0.947-0.948V3.982A0.947 0.947 0 0 1 3.88 3.035z"
+        android:strokeWidth="3.57115" />
+    <path
+        android:fillColor="#8a8a8a"
+        android:pathData="M3.871 3.413h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.36A0.947 0.947 0 0 1 3.87 3.413z"
+        android:strokeWidth="3.57115" />
+    <path
+        android:fillAlpha="0.9411765"
+        android:fillColor="#8a8a8a"
+        android:pathData="M12.483 10.865h3.907v6.978h-3.907z" />
+    <path
+        android:fillColor="#fcfeff"
+        android:pathData="M19.143 17.496h0.913v-0.249h-1.161v0.249zm-6.219-0.249h-0.456v0.207h0.456z"
+        android:strokeWidth="0.99999803" />
+    <path
+        android:fillColor="#fcfeff"
+        android:pathData="M8.096 17.415l0.001-0.249 0.249 0.002-0.001 0.249zM7.638 17.45l0.002-0.248 0.248 0.001-0.001 0.249zM7.18 17.445l0.002-0.25 0.249 0.002-0.001 0.249z"
+        android:strokeWidth="1.0000112" />
+    <path
+        android:fillColor="#fcfeff"
+        android:pathData="M20.263 17.04v-0.25h-0.457v0.249h0.25v0.207h0.207l0.249 0.042h0.456v-0.25h-0.456z"
+        android:strokeWidth="0.99999803" />
+    <path
+        android:fillColor="#fcfeff"
+        android:pathData="M12.236 17.241l0.002-0.249 0.249 0.002-0.002 0.249zM8.307 17.21l0.001-0.249 0.249 0.002-0.001 0.249zM7.85 17.204v-0.249l0.25 0.002-0.002 0.249zm-0.458-0.006l0.001-0.249 0.249 0.002-0.001 0.248zm-0.458-0.006l0.002-0.249 0.249 0.002-0.002 0.248z"
+        android:strokeWidth="1.0000112" />
+    <path
+        android:fillColor="#fcfeff"
+        android:pathData="M21.673 17.04v-0.207h-0.456l-0.25-0.042v0.249h0.458z"
+        android:strokeWidth="0.99999803" />
+    <path
+        android:fillColor="#fcfeff"
+        android:pathData="M8.56 16.964v-0.249l0.25 0.001-0.002 0.25zm-0.458-0.006l0.001-0.249 0.249 0.001-0.001 0.25zm-0.458-0.006l0.002-0.249 0.249 0.001-0.002 0.25zm-0.457 0.035l0.001-0.249 0.249 0.002-0.001 0.249zM6.73 16.981v-0.249l0.25 0.002-0.002 0.249z"
+        android:strokeWidth="1.0000112" />
+    <path
+        android:fillColor="#fcfeff"
+        android:pathData="M21.88 16.584h-0.207v0.249h0.456v-0.25zm-2.53 0.207h0.458v-0.207H19.35z"
+        android:strokeWidth="0.99999803" />
+    <path
+        android:fillColor="#fcfeff"
+        android:pathData="M8.313 16.753l0.001-0.249 0.249 0.001-0.001 0.25zm-0.458-0.006l0.002-0.249 0.248 0.001v0.25zM7.398 16.74l0.001-0.248 0.25 0.001-0.002 0.249zM6.94 16.735l0.002-0.25 0.249 0.002-0.002 0.249zm-0.457-0.006l0.001-0.25 0.25 0.002-0.002 0.249zM8.108 16.5l0.001-0.248 0.25 0.001-0.002 0.249zM7.65 16.494l0.002-0.248H7.9l-0.002 0.25z"
+        android:strokeWidth="1.0000112" />
+    <path
+        android:fillColor="#fcfeff"
+        android:pathData="M7.193 16.53l0.001-0.249 0.249 0.001-0.001 0.25zm-0.457-0.006v-0.25l0.25 0.002-0.002 0.249z"
+        android:strokeWidth="1.0000112" />
+    <path
+        android:fillColor="#fcfeff"
+        android:pathData="M20.056 14.718V14.51h0.248v-0.25h0.208v-0.207h0.248v-0.248h0.25v-0.208h0.207V13.35h0.248v-0.208h0.208v-0.249h0.248v-0.207h0.208v-0.249h0.249v-0.912H22.17v-0.207h-0.249l-0.248-0.042h-0.208V11.07h-2.073v0.207h-0.456v0.25H18.48v0.206h-0.456v-0.207h-0.249v-0.497h-0.207V10.57h-0.249v-0.207h-0.207v-0.249h-0.25V9.908h-0.206V9.659H16.2V9.41h-0.457V9.203H15.04V8.954h-1.161V8.747L13.67 8.706h-3.234v0.248H9.524v0.208H8.818V9.41H8.613v0.208H8.156L8.114 9.867H7.907l-0.25-0.042v0.249H7.452v0.249H7.2v0.207H6.996v0.249h-0.25v0.207H6.539l-0.041 0.249v0.207H6.29v0.456H6.04v0.456H5.835v0.706H6.04v-0.458h0.25v-0.455h0.207v-0.456h0.249v-0.456h0.249v-0.249h0.207v-0.207H7.45v-0.25h0.207v-0.206h0.249v-0.25h0.457V9.868h0.456v-0.25h2.778V9.41H9.731V9.162h0.954V8.954h2.986v0.25h0.207V9.41h0.248V9.203h0.208V9.41h1.161v0.457h0.207l0.249 0.041h0.456v0.207h0.249v0.25h0.207v0.206h0.249v0.25h0.207v0.662h-0.207l-0.042 0.248h0.705v0.459h0.456v-0.21h0.456v-0.249h0.456v-0.207h0.498v-0.249h1.576v0.25h0.704v0.248h0.208v0.207h-0.912v0.249h-0.664v0.207h-0.497v0.25h-0.208L19.6 12.644v0.248h-0.207v0.25h-0.249v0.207h-0.207v0.248h-0.249v0.208H18.48l-0.042 0.248h-0.207v0.208h-0.249v0.249h-0.207v0.207h-0.249v0.249H17.32v0.207h-0.25v0.249h-0.249v0.207h-0.207v0.249h-0.249v0.207h-0.207v0.249h-0.249v0.207h-0.207v0.249h-0.456l-0.042 0.207h-0.456v0.25h-0.663v0.206h-1.162v0.25h5.972v-0.208h-2.737v-0.498h0.207V16.79h0.25v-0.249h0.206v-0.207h1.618v0.249h0.911v-0.25h2.529l0.249 0.042v0.208h0.457v0.705h-0.249v0.207H22.13v0.249h-0.25v0.207h-0.207V18.2h-0.456l-0.042 0.207h-0.663v0.249h-0.706v0.207h-0.662l-0.041 0.249h-0.914v0.207h-0.91v0.25H16.82l-0.248-0.042v0.248h-0.208v0.25h1.617v-0.25h0.459V19.32h0.91v-0.207h0.705v-0.249h0.664v-0.207h0.705v-0.249h0.456v-0.207h0.249v-0.249h0.207v-0.207h0.249v-0.25h0.207v-0.662l0.042-0.248v-0.208h-0.25v-0.248h-3.896v-0.25h0.208V15.63l0.248 0.041v-0.248h0.208l0.041-0.25H19.6v-0.206h0.25v-0.25z"
+        android:strokeWidth="0.99999803" />
+    <path
+        android:fillColor="#fcfeff"
+        android:pathData="M7.862 16.29v-0.25l0.25 0.002L8.11 16.29zm-0.458-0.007l0.001-0.249 0.25 0.002-0.002 0.249zm-0.457-0.006v-0.249l0.25 0.002-0.002 0.249zM6.49 16.271v-0.249l0.25 0.002-0.002 0.248zm1.167-0.234v-0.249l0.25 0.001-0.002 0.25zM7.2 16.03v-0.248l0.25 0.001-0.002 0.25z"
+        android:strokeWidth="1.0000112" />
+    <path
+        android:fillColor="#fcfeff"
+        android:pathData="M6.742 16.066v-0.249l0.25 0.002-0.002 0.249zm2.04-0.222l0.002-0.249 0.249 0.001-0.002 0.25z"
+        android:strokeWidth="1.0000112" />
+    <path
+        android:fillColor="#fcfeff"
+        android:pathData="M8.322 15.381H8.114v0.207H7.865v0.25h0.25v0.207h0.207v0.248H8.57v0.208h0.208v0.248h0.248V17h0.208v-0.706h0.248v-0.457H9.026v0.456H8.778v-0.248H8.57v-0.208H8.321v-0.249z"
+        android:strokeWidth="0.99999803" />
+    <path
+        android:fillColor="#fcfeff"
+        android:pathData="M7.41 15.826l0.001-0.249 0.25 0.001-0.002 0.25zM6.953 15.82v-0.25l0.25 0.001-0.002 0.25zm-0.458-0.006l0.001-0.25 0.25 0.002-0.002 0.249zm1.168-0.234l0.001-0.25 0.249 0.002-0.002 0.249zm-0.458-0.007l0.001-0.248 0.25 0.001-0.002 0.249z"
+        android:strokeWidth="1.0000112" />
+    <path
+        android:fillColor="#fcfeff"
+        android:pathData="M6.706 15.567l0.002-0.249 0.248 0.002v0.249z"
+        android:strokeWidth="1.0000112" />
+    <path
+        android:fillColor="#fcfeff"
+        android:pathData="M8.778 15.381v-0.249H8.57v0.458h0.208z"
+        android:strokeWidth="0.99999803" />
+    <path
+        android:fillColor="#fcfeff"
+        android:pathData="M7.416 15.368l0.001-0.248h0.25l-0.002 0.25zm-0.457-0.006l0.001-0.248h0.249l-0.001 0.25z"
+        android:strokeWidth="1.0000112" />
+    <path
+        android:fillColor="#fcfeff"
+        android:pathData="M12.012 14.925v-1.244h-0.25v-0.498h-0.248v-0.248h-0.497v-0.249H9.772v0.249H9.276v0.249h-0.25v0.497H8.778v1.245h0.248v0.497h0.25v0.497h0.248v-0.248h0.497v-0.25h0.747v-0.248h1.492v-0.249zm-2.073-0.912v-0.249h0.498v0.498H9.938z"
+        android:strokeWidth="0.99999803" />
+    <path
+        android:fillColor="#fcfeff"
+        android:pathData="M7.17 15.116l0.001-0.249 0.249 0.001-0.001 0.25z"
+        android:strokeWidth="1.0000112" />
+    <path
+        android:fillColor="#fcfeff"
+        android:pathData="M12.924 13.764v1.161h0.705v-0.207h-0.456v-1.41h-0.25v0.249zm-4.602 1.369H8.57v-1.619H8.363v0.458L8.321 14.22v0.706z"
+        android:strokeWidth="0.99999803" />
+    <path
+        android:fillColor="#fcfeff"
+        android:pathData="M13.878 14.469h-0.25v0.249h0.458v-0.249z"
+        android:strokeWidth="0.99999803" />
+    <path
+        android:fillColor="#fcfeff"
+        android:pathData="M14.334 14.262h-0.25v0.207h0.458v-0.207zm0.456-0.249h-0.25v0.249h0.457v-0.249zm0.456-0.207h-0.249v0.207h0.456v-0.207zm-2.529 1.575v-1.824h-0.25v1.617H12.26v0.207h0.208z"
+        android:strokeWidth="0.99999803" />
+    <path
+        android:fillColor="#fcfeff"
+        android:pathData="M15.463 13.811l0.002-0.248h0.249l-0.002 0.25z"
+        android:strokeWidth="1.0000112" />
+    <path
+        android:fillColor="#fcfeff"
+        android:pathData="M15.95 13.35h-0.248v0.207h0.456V13.35zm-3.69-0.042v0.249h0.208V13.1H12.26zm-3.441 0.207V13.06H8.57v0.455zm-1.161 1.369v0.248h0.207v0.25h0.25v-2.074H7.906l-0.042 0.207v1.369z"
+        android:strokeWidth="0.99999803" />
+    <path
+        android:fillColor="#fcfeff"
+        android:pathData="M16.156 13.342l0.001-0.249 0.249 0.002-0.001 0.249z"
+        android:strokeWidth="1.0000112" />
+    <path
+        android:fillColor="#fcfeff"
+        android:pathData="M16.614 12.893h-0.207v0.208h0.456v-0.208zm-1.617-0.041h-0.663v0.249h0.912v-0.208zM12.965 13.1v-0.248h-0.248v0.456h0.207z"
+        android:strokeWidth="0.99999803" />
+    <path
+        android:fillColor="#fcfeff"
+        android:pathData="M12.021 13.1l0.001-0.248h0.25l-0.002 0.25zm-3.202-0.042l0.001-0.249 0.249 0.002-0.001 0.248z"
+        android:strokeWidth="1.0000112" />
+    <path
+        android:fillColor="#fcfeff"
+        android:pathData="M8.114 12.852v0.457h0.249v-0.458z"
+        android:strokeWidth="0.99999803" />
+    <path
+        android:fillColor="#fcfeff"
+        android:pathData="M16.848 12.873l0.001-0.249 0.25 0.002-0.002 0.249z"
+        android:strokeWidth="1.0000112" />
+    <path
+        android:fillColor="#fcfeff"
+        android:pathData="M15.495 12.645h-0.25v0.249h0.458v-0.25z"
+        android:strokeWidth="0.99999803" />
+    <path
+        android:fillColor="#fcfeff"
+        android:pathData="M14.103 12.878l0.002-0.249 0.248 0.002v0.249zm-1.601 0l0.001-0.249 0.25 0.001-0.002 0.25zm-0.686-0.03l0.001-0.249 0.25 0.002-0.002 0.248zM9.03 12.853l0.001-0.249 0.249 0.001-0.001 0.25zm-0.686-0.03l0.001-0.249 0.249 0.002-0.001 0.248z"
+        android:strokeWidth="1.0000112" />
+    <path
+        android:fillColor="#fcfeff"
+        android:pathData="M17.319 12.437H17.07v0.208h0.456l0.042-0.208zm-1.369-0.041h-0.248v0.249h0.456v-0.208z"
+        android:strokeWidth="0.99999803" />
+    <path
+        android:fillColor="#fcfeff"
+        android:pathData="M13.857 12.626l0.001-0.25 0.25 0.002-0.002 0.249zm-1.601-0.001l0.001-0.249 0.249 0.002-0.001 0.249z"
+        android:strokeWidth="1.0000112" />
+    <path
+        android:fillColor="#fcfeff"
+        android:pathData="M11.348 12.603l0.208 0.042h0.248v-0.25h-0.456zm-1.866 0h0.25v-0.207H9.274v0.207z"
+        android:strokeWidth="0.99999803" />
+    <path
+        android:fillColor="#fcfeff"
+        android:pathData="M8.596 12.618l0.001-0.249 0.25 0.002-0.002 0.249zm8.944-0.214l0.002-0.249 0.249 0.002-0.002 0.248zm-1.372 0.023l0.001-0.248h0.25l-0.002 0.25z"
+        android:strokeWidth="1.0000112" />
+    <path
+        android:fillColor="#fcfeff"
+        android:pathData="M9.731 12.147v0.249h1.619v-0.249H9.98z"
+        android:strokeWidth="0.99999803" />
+    <path
+        android:fillColor="#fcfeff"
+        android:pathData="M8.807 12.372l0.002-0.25 0.248 0.002v0.249z"
+        android:strokeWidth="1.0000112" />
+    <path
+        android:fillColor="#fcfeff"
+        android:pathData="M16.614 11.94h-0.207v0.249h0.456V11.98z"
+        android:strokeWidth="0.99999803" />
+    <path
+        android:fillColor="#fcfeff"
+        android:pathData="M15.235 12.186l0.002-0.248h0.249l-0.002 0.25z"
+        android:strokeWidth="1.0000112" />
+    <path
+        android:fillColor="#fcfeff"
+        android:pathData="M13.629 12.396h0.249v-0.664h-0.25v0.458zm-2.073-0.456v0.207h0.456v0.249h0.248v-0.249l0.25 0.042V11.69h-0.25v0.249h-0.248l0.041-0.249h-0.249v-0.207h-0.248v0.207H9.524l-0.042 0.25h1.866zm-2.281 0H9.026v0.207h0.456V11.94z"
+        android:strokeWidth="0.99999803" />
+    <path
+        android:fillColor="#fcfeff"
+        android:pathData="M16.86 11.958l0.002-0.249 0.248 0.002v0.249zm-1.83-0.024l0.002-0.249 0.249 0.002-0.002 0.248zm0.898-0.217l0.001-0.248h0.249l-0.001 0.25z"
+        android:strokeWidth="1.0000112" />
+    <path
+        android:fillColor="#fcfeff"
+        android:pathData="M14.784 11.723l0.001-0.249 0.25 0.002-0.002 0.248z"
+        android:strokeWidth="1.0000112" />
+    <path
+        android:fillColor="#fcfeff"
+        android:pathData="M14.085 11.732v-0.455h-0.207v0.456z"
+        android:strokeWidth="0.99999803" />
+    <path
+        android:fillColor="#fcfeff"
+        android:pathData="M12.04 11.687v-0.25l0.25 0.002-0.002 0.249zm3.683-0.18l0.001-0.25 0.249 0.002-0.002 0.249z"
+        android:strokeWidth="1.0000112" />
+    <path
+        android:fillColor="#fcfeff"
+        android:pathData="M11.793 11.476l0.001-0.25 0.249 0.002-0.001 0.249zm4.827-0.186l0.001-0.249 0.249 0.001-0.001 0.25zm-1.144-0.036l0.002-0.249 0.248 0.001v0.25zm-1.372 0.023l0.001-0.249 0.249 0.002-0.001 0.249zm-2.058-0.047v-0.25l0.25 0.002-0.002 0.249zm4.369-0.193l0.001-0.248h0.25l-0.002 0.25z"
+        android:strokeWidth="1.0000112" />
+    <path
+        android:fillColor="#fcfeff"
+        android:pathData="M15.271 11.043l0.002-0.249 0.248 0.001v0.25zm-0.956-0.013l0.001-0.248 0.25 0.001-0.002 0.249z"
+        android:strokeWidth="1.0000112" />
+    <path
+        android:fillColor="#fcfeff"
+        android:pathData="M12.51 11.028v0.207h-0.25v0.249h0.25v0.207h0.207v-0.457h0.248v-0.455h-0.248v0.25z"
+        android:strokeWidth="0.99999803" />
+    <path
+        android:fillColor="#fcfeff"
+        android:pathData="M12.256 11.024l0.002-0.249 0.249 0.002-0.002 0.248zm-0.457-0.006L11.8 10.77l0.249 0.002-0.001 0.248zm4.37-0.192v-0.249l0.25 0.002-0.002 0.249zm-1.601-0.042v-0.249l0.25 0.002-0.002 0.249zm-1.602 0l0.002-0.249 0.249 0.001-0.002 0.25zm-0.457-0.006l0.001-0.249 0.25 0.001-0.002 0.25zm-0.457-0.006v-0.25l0.25 0.002-0.002 0.249zm3.912-0.198v-0.249l0.25 0.001-0.002 0.25z"
+        android:strokeWidth="1.0000112" />
+    <path
+        android:fillColor="#fcfeff"
+        android:pathData="M14.778 10.58l0.002-0.25 0.249 0.002-0.002 0.249zm-2.058-0.007l0.001-0.25 0.249 0.002-0.001 0.249zm-0.458-0.006l0.002-0.25 0.249 0.002-0.002 0.249zm2.769-0.234l0.001-0.249 0.25 0.002-0.002 0.248z"
+        android:strokeWidth="1.0000112" />
+    <path
+        android:fillColor="#fcfeff"
+        android:pathData="M12.717 10.323v-0.208h0.248V9.867H12.26v0.248h0.25v0.208z"
+        android:strokeWidth="0.99999803" />
+    <path
+        android:fillColor="#fcfeff"
+        android:pathData="M15.242 10.128l0.001-0.249 0.249 0.001-0.001 0.25zm-1.601-0.042l0.001-0.249 0.25 0.002-0.002 0.248z"
+        android:strokeWidth="1.0000112" />
+    <path
+        android:fillColor="#fcfeff"
+        android:pathData="M13.173 10.323v0.248h0.249v-0.248h0.207l0.041-0.208h-0.249V9.867h-0.248v0.248h-0.208v0.208z"
+        android:strokeWidth="0.99999803" />
+    <path
+        android:fillColor="#fcfeff"
+        android:pathData="M13.893 9.881l0.002-0.249 0.249 0.002-0.002 0.248zm-0.457-0.006l0.001-0.249 0.249 0.002-0.001 0.248z"
+        android:strokeWidth="1.0000112" />
+    <path
+        android:fillColor="#fcfeff"
+        android:pathData="M12.053 9.867h0.207V9.659l-0.207-0.041h-0.456v0.249h0.207z"
+        android:strokeWidth="0.99999803" />
+    <path
+        android:fillColor="#fcfeff"
+        android:pathData="M14.104 9.635l0.002-0.25 0.249 0.002-0.002 0.249zm-0.457-0.006l0.001-0.25 0.25 0.002-0.002 0.249z"
+        android:strokeWidth="1.0000112" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_device_folder.xml b/app/src/main/res/drawable/ic_device_folder.xml
new file mode 100644
index 0000000..b7a2f86
--- /dev/null
+++ b/app/src/main/res/drawable/ic_device_folder.xml
@@ -0,0 +1,23 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="45sp"
+    android:height="45sp"
+    android:viewportWidth="30"
+    android:viewportHeight="30">
+
+    <path
+        android:fillColor="?attr/deviceIconLight"
+        android:pathData="M3.871 3.877h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.824a0.947 0.947 0 0 1 0.947-0.947z"
+        android:strokeWidth="3.5" />
+    <path
+        android:fillColor="?attr/deviceIconDark"
+        android:pathData="M3.879 3.035h20.925a0.947 0.947 0 0 1 0.947 0.947v20.01a0.947 0.947 0 0 1-0.947 0.948H3.88a0.947 0.947 0 0 1-0.947-0.948V3.982A0.947 0.947 0 0 1 3.88 3.035z"
+        android:strokeWidth="3.5" />
+    <path
+        android:fillColor="?attr/deviceIconPrimary"
+        android:pathData="M3.871 3.413h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.36A0.947 0.947 0 0 1 3.87 3.413z"
+        android:strokeWidth="3.5" />
+
+    <path
+        android:pathData="M6.8,6.95C6.22,6.95 5.75,7.42 5.75,8L5.75,20.92C5.73,21 5.74,21.08 5.77,21.16C5.87,21.63 6.3,22 6.8,22L20.45,22C20.97,22 21.4,21.61 21.48,21.1C21.48,21.08 21.49,21.06 21.49,21.04C21.49,21.03 21.49,21.02 21.49,21.02L21.5,20.96C21.5,20.96 21.5,20.95 21.5,20.95L23.24,11.57L23.25,11.53L23.25,11.5C23.25,10.92 22.78,10.45 22.2,10.45L22.2,9.4C22.2,8.82 21.73,8.35 21.15,8.35L12.06,8.35C12.06,8.35 12.06,8.35 12.05,8.35C12.04,8.34 12.01,8.32 11.95,8.25C11.87,8.15 11.77,7.99 11.66,7.81C11.55,7.64 11.43,7.45 11.28,7.29C11.14,7.13 10.94,6.95 10.65,6.95ZM6.8,7.65L10.65,7.65C10.63,7.65 10.67,7.65 10.76,7.75C10.85,7.85 10.95,8.01 11.05,8.19C11.16,8.36 11.28,8.55 11.43,8.71C11.57,8.88 11.76,9.05 12.05,9.05L21.15,9.05C21.35,9.05 21.5,9.2 21.5,9.4L21.5,10.45L8.55,10.45C8,10.45 7.56,10.89 7.52,11.43L7.51,11.43L7.5,11.5L6.45,17.17L6.45,8C6.45,7.8 6.6,7.65 6.8,7.65ZM8.55,11.15L22.2,11.15C22.4,11.15 22.55,11.3 22.55,11.5L20.83,20.79L20.82,20.81C20.82,20.82 20.81,20.84 20.81,20.85C20.81,20.87 20.8,20.88 20.8,20.9C20.8,20.9 20.8,20.91 20.8,20.92C20.8,20.92 20.8,20.92 20.8,20.93C20.79,20.95 20.79,20.96 20.79,20.98C20.79,20.99 20.79,21 20.79,21.02C20.79,21.03 20.79,21.04 20.79,21.06C20.75,21.2 20.61,21.3 20.45,21.3L6.8,21.3C6.6,21.3 6.45,21.15 6.45,20.95L8.19,11.57L8.2,11.53L8.2,11.5C8.2,11.3 8.35,11.15 8.55,11.15Z"
+        android:fillColor="?attr/deviceIconOnPrimary"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_device_folder_disabled.xml b/app/src/main/res/drawable/ic_device_folder_disabled.xml
new file mode 100644
index 0000000..5fc4ee0
--- /dev/null
+++ b/app/src/main/res/drawable/ic_device_folder_disabled.xml
@@ -0,0 +1,32 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="45sp"
+    android:height="45sp"
+    android:viewportWidth="30"
+    android:viewportHeight="30">
+
+
+  <path
+      android:fillColor="#7a7a7a"
+      android:pathData="M3.8712,3.8769L24.7963,3.8769A0.9472,0.9472 0,0 1,25.7435 4.8241L25.7435,24.8343A0.9472,0.9472 0,0 1,24.7963 25.7815L3.8712,25.7815A0.9472,0.9472 0,0 1,2.924 24.8343L2.924,4.8241A0.9472,0.9472 0,0 1,3.8712 3.8769z"
+      android:strokeWidth="3.57115173"
+      android:strokeColor="#00000000" />
+  <path
+      android:fillAlpha="0.9411765"
+      android:fillColor="#9f9f9f"
+      android:pathData="M3.879,3.0349L24.8041,3.0349A0.9472,0.9472 0,0 1,25.7514 3.9822L25.7514,23.9924A0.9472,0.9472 0,0 1,24.8041 24.9396L3.879,24.9396A0.9472,0.9472 0,0 1,2.9318 23.9924L2.9318,3.9822A0.9472,0.9472 0,0 1,3.879 3.0349z"
+      android:strokeWidth="3.57115173"
+      android:strokeColor="#00000000" />
+  <path
+      android:fillColor="#8a8a8a"
+      android:pathData="M3.8711,3.4131L24.7962,3.4131A0.9472,0.9472 0,0 1,25.7435 4.3603L25.7435,24.3705A0.9472,0.9472 0,0 1,24.7962 25.3177L3.8711,25.3177A0.9472,0.9472 0,0 1,2.9239 24.3705L2.9239,4.3603A0.9472,0.9472 0,0 1,3.8711 3.4131z"
+      android:strokeWidth="3.57115173"
+      android:strokeColor="#00000000" />
+  <path
+      android:fillAlpha="0.9411765"
+      android:fillColor="#8a8a8a"
+      android:pathData="m12.4827,10.8654h3.907v6.978h-3.907z" />
+
+  <path
+      android:pathData="M6.8,6.95C6.22,6.95 5.75,7.42 5.75,8L5.75,20.92C5.73,21 5.74,21.08 5.77,21.16C5.87,21.63 6.3,22 6.8,22L20.45,22C20.97,22 21.4,21.61 21.48,21.1C21.48,21.08 21.49,21.06 21.49,21.04C21.49,21.03 21.49,21.02 21.49,21.02L21.5,20.96C21.5,20.96 21.5,20.95 21.5,20.95L23.24,11.57L23.25,11.53L23.25,11.5C23.25,10.92 22.78,10.45 22.2,10.45L22.2,9.4C22.2,8.82 21.73,8.35 21.15,8.35L12.06,8.35C12.06,8.35 12.06,8.35 12.05,8.35C12.04,8.34 12.01,8.32 11.95,8.25C11.87,8.15 11.77,7.99 11.66,7.81C11.55,7.64 11.43,7.45 11.28,7.29C11.14,7.13 10.94,6.95 10.65,6.95ZM6.8,7.65L10.65,7.65C10.63,7.65 10.67,7.65 10.76,7.75C10.85,7.85 10.95,8.01 11.05,8.19C11.16,8.36 11.28,8.55 11.43,8.71C11.57,8.88 11.76,9.05 12.05,9.05L21.15,9.05C21.35,9.05 21.5,9.2 21.5,9.4L21.5,10.45L8.55,10.45C8,10.45 7.56,10.89 7.52,11.43L7.51,11.43L7.5,11.5L6.45,17.17L6.45,8C6.45,7.8 6.6,7.65 6.8,7.65ZM8.55,11.15L22.2,11.15C22.4,11.15 22.55,11.3 22.55,11.5L20.83,20.79L20.82,20.81C20.82,20.82 20.81,20.84 20.81,20.85C20.81,20.87 20.8,20.88 20.8,20.9C20.8,20.9 20.8,20.91 20.8,20.92C20.8,20.92 20.8,20.92 20.8,20.93C20.79,20.95 20.79,20.96 20.79,20.98C20.79,20.99 20.79,21 20.79,21.02C20.79,21.03 20.79,21.04 20.79,21.06C20.75,21.2 20.61,21.3 20.45,21.3L6.8,21.3C6.6,21.3 6.45,21.15 6.45,20.95L8.19,11.57L8.2,11.53L8.2,11.5C8.2,11.3 8.35,11.15 8.55,11.15Z"
+      android:fillColor="#ffffff"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_device_galaxy_buds.xml b/app/src/main/res/drawable/ic_device_galaxy_buds.xml
new file mode 100644
index 0000000..dc23436
--- /dev/null
+++ b/app/src/main/res/drawable/ic_device_galaxy_buds.xml
@@ -0,0 +1,40 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="45sp"
+    android:height="45sp"
+    android:viewportWidth="30"
+    android:viewportHeight="30">
+    <path
+        android:fillColor="?attr/deviceIconLight"
+        android:pathData="M3.871 3.877h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.824a0.947 0.947 0 0 1 0.947-0.947z"
+        android:strokeWidth="3.5" />
+    <path
+        android:fillColor="?attr/deviceIconDark"
+        android:pathData="M3.879 3.035h20.925a0.947 0.947 0 0 1 0.947 0.947v20.01a0.947 0.947 0 0 1-0.947 0.948H3.88a0.947 0.947 0 0 1-0.947-0.948V3.982A0.947 0.947 0 0 1 3.88 3.035z"
+        android:strokeWidth="3.5" />
+    <path
+        android:fillColor="?attr/deviceIconPrimary"
+        android:pathData="M3.871 3.413h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.36A0.947 0.947 0 0 1 3.87 3.413z"
+        android:strokeWidth="3.5" />
+    <path
+        android:fillType="evenOdd"
+        android:pathData="M21.522 19.793c0.616 0.866 0.173 2.2-1.533 2.201m-0.035-5.877c-1.155-0.468-1.978-0.451-3.04-0.143-0.498 0.145-0.888 0.736-0.928 1.257-0.186 2.404 1.257 3.576 2.712 3.47 0.892-0.065 1.79-1.2 2.005-2.193 0.18-0.835 0.036-2.073-0.75-2.391z"
+        android:strokeWidth="0.5"
+        android:strokeColor="?attr/deviceIconOnPrimary" />
+    <path
+        android:pathData="M18.948 15.002c-1.53-0.223-3.098 0.434-3.735 1.91-0.855 1.98 0.377 5.192 2.652 5.532 4.187 0.625 5.547-6.793 1.083-7.442"
+        android:strokeWidth="0.5"
+        android:strokeColor="?attr/deviceIconOnPrimary" />
+    <path
+        android:pathData="M18.032 13.173c0.662 0.176 1.074 1.094 1.583 1.523 0.681 0.574 1.442 0.888 1.89 1.711 0.342 0.627 0.249 1.521 0.611 2.072 0.19 0.289 0.567 0.422 0.782 0.708 0.667 0.89 0.593 2.232-0.208 3.014-0.528 0.515-1.36 0.727-2.075 0.56-0.304-0.07-0.598-0.326-0.916-0.3-0.745 0.062-1.207 0.429-2 0.22-2.802-0.736-3.851-4.044-3.23-6.61 0.327-1.358 1.966-3.322 3.563-2.898M11.07 6.31c-0.663 0.176-1.075 1.094-1.584 1.523-0.681 0.573-1.442 0.888-1.89 1.711-0.342 0.627-0.249 1.521-0.611 2.072-0.19 0.288-0.567 0.422-0.781 0.708-0.668 0.89-0.594 2.232 0.207 3.014 0.528 0.515 1.36 0.727 2.075 0.56 0.304-0.07 0.598-0.326 0.917-0.3 0.744 0.062 1.206 0.429 2 0.22 2.8-0.736 3.85-4.044 3.23-6.61-0.328-1.358-1.967-3.322-3.564-2.898"
+        android:strokeWidth="0.5"
+        android:strokeColor="?attr/deviceIconOnPrimary" />
+    <path
+        android:fillType="evenOdd"
+        android:pathData="M7.58 12.93c-0.617 0.865-0.174 2.2 1.532 2.201m0.035-5.877c1.155-0.468 1.978-0.451 3.04-0.143 0.498 0.144 0.888 0.736 0.928 1.257 0.186 2.404-1.257 3.576-2.712 3.47-0.892-0.065-1.79-1.2-2.005-2.193-0.18-0.835-0.036-2.073 0.75-2.391z"
+        android:strokeWidth="0.5"
+        android:strokeColor="?attr/deviceIconOnPrimary" />
+    <path
+        android:pathData="M10.153 8.138c1.53-0.223 3.098 0.435 3.735 1.911 0.855 1.979-0.377 5.192-2.652 5.532-4.187 0.624-5.547-6.793-1.084-7.443"
+        android:strokeWidth="0.5"
+        android:strokeColor="?attr/deviceIconOnPrimary" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_device_galaxy_buds_disabled.xml b/app/src/main/res/drawable/ic_device_galaxy_buds_disabled.xml
new file mode 100644
index 0000000..72280c8
--- /dev/null
+++ b/app/src/main/res/drawable/ic_device_galaxy_buds_disabled.xml
@@ -0,0 +1,45 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="45sp"
+    android:height="45sp"
+    android:viewportWidth="30"
+    android:viewportHeight="30">
+    <path
+        android:fillColor="#7a7a7a"
+        android:pathData="M3.871 3.877h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.824a0.947 0.947 0 0 1 0.947-0.947z"
+        android:strokeWidth="3.5" />
+    <path
+        android:fillAlpha="0.9411765"
+        android:fillColor="#9f9f9f"
+        android:pathData="M3.879 3.035h20.925a0.947 0.947 0 0 1 0.947 0.947v20.01a0.947 0.947 0 0 1-0.947 0.948H3.88a0.947 0.947 0 0 1-0.947-0.948V3.982A0.947 0.947 0 0 1 3.88 3.035z"
+        android:strokeWidth="3.5" />
+    <path
+        android:fillColor="#8a8a8a"
+        android:pathData="M3.871 3.413h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.36A0.947 0.947 0 0 1 3.87 3.413z"
+        android:strokeWidth="3.5" />
+    <path
+        android:fillAlpha="0.9411765"
+        android:fillColor="#8a8a8a"
+        android:pathData="M12.483 10.865h3.907v6.978h-3.907z" />
+    <path
+        android:fillType="evenOdd"
+        android:pathData="M21.522 19.793c0.616 0.866 0.173 2.2-1.533 2.201m-0.035-5.877c-1.155-0.468-1.978-0.451-3.04-0.143-0.498 0.145-0.888 0.736-0.928 1.257-0.186 2.404 1.257 3.576 2.712 3.47 0.892-0.065 1.79-1.2 2.005-2.193 0.18-0.835 0.036-2.073-0.75-2.391z"
+        android:strokeWidth="0.5"
+        android:strokeColor="#fcffff" />
+    <path
+        android:pathData="M18.948 15.002c-1.53-0.223-3.098 0.434-3.735 1.91-0.855 1.98 0.377 5.192 2.652 5.532 4.187 0.625 5.547-6.793 1.083-7.442"
+        android:strokeWidth="0.49999797"
+        android:strokeColor="#fcffff" />
+    <path
+        android:pathData="M18.032 13.173c0.662 0.176 1.074 1.094 1.583 1.523 0.681 0.574 1.442 0.888 1.89 1.711 0.342 0.627 0.249 1.521 0.611 2.072 0.19 0.289 0.567 0.422 0.782 0.708 0.667 0.89 0.593 2.232-0.208 3.014-0.528 0.515-1.36 0.727-2.075 0.56-0.304-0.07-0.598-0.326-0.916-0.3-0.745 0.062-1.207 0.429-2 0.22-2.802-0.736-3.851-4.044-3.23-6.61 0.327-1.358 1.966-3.322 3.563-2.898M11.07 6.31c-0.663 0.176-1.075 1.094-1.584 1.523-0.681 0.573-1.442 0.888-1.89 1.711-0.342 0.627-0.249 1.521-0.611 2.072-0.19 0.288-0.567 0.422-0.781 0.708-0.668 0.89-0.594 2.232 0.207 3.014 0.528 0.515 1.36 0.727 2.075 0.56 0.304-0.07 0.598-0.326 0.917-0.3 0.744 0.062 1.206 0.429 2 0.22 2.8-0.736 3.85-4.044 3.23-6.61-0.328-1.358-1.967-3.322-3.564-2.898"
+        android:strokeWidth="0.5"
+        android:strokeColor="#fcffff" />
+    <path
+        android:fillType="evenOdd"
+        android:pathData="M7.58 12.93c-0.617 0.865-0.174 2.2 1.532 2.201m0.035-5.877c1.155-0.468 1.978-0.451 3.04-0.143 0.498 0.144 0.888 0.736 0.928 1.257 0.186 2.404-1.257 3.576-2.712 3.47-0.892-0.065-1.79-1.2-2.005-2.193-0.18-0.835-0.036-2.073 0.75-2.391z"
+        android:strokeWidth="0.5"
+        android:strokeColor="#fcffff" />
+    <path
+        android:pathData="M10.153 8.138c1.53-0.223 3.098 0.435 3.735 1.911 0.855 1.979-0.377 5.192-2.652 5.532-4.187 0.624-5.547-6.793-1.084-7.443"
+        android:strokeWidth="0.5"
+        android:strokeColor="#fcffff" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_device_galaxy_buds_live.xml b/app/src/main/res/drawable/ic_device_galaxy_buds_live.xml
new file mode 100644
index 0000000..cef683b
--- /dev/null
+++ b/app/src/main/res/drawable/ic_device_galaxy_buds_live.xml
@@ -0,0 +1,36 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="45sp"
+    android:height="45sp"
+    android:viewportWidth="30"
+    android:viewportHeight="30">
+    <path
+        android:fillColor="?attr/deviceIconLight"
+        android:pathData="M3.871 3.877h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.824a0.947 0.947 0 0 1 0.947-0.947z"
+        android:strokeWidth="3.57115" />
+    <path
+        android:fillColor="?attr/deviceIconDark"
+        android:pathData="M3.879 3.035h20.925a0.947 0.947 0 0 1 0.947 0.947v20.01a0.947 0.947 0 0 1-0.947 0.948H3.88a0.947 0.947 0 0 1-0.947-0.948V3.982A0.947 0.947 0 0 1 3.88 3.035z"
+        android:strokeWidth="3.57115" />
+    <path
+        android:fillColor="?attr/deviceIconPrimary"
+        android:pathData="M3.871 3.413h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.36A0.947 0.947 0 0 1 3.87 3.413z"
+        android:strokeWidth="3.57115" />
+    <path
+        android:pathData="M11.564 17.14C10.61 17.978 9.3 18.237 8.29 17.787 7.214 17.31 6.474 16.17 6.46 14.97c-0.008-0.698 0.155-1.202 0.607-1.884 0.388-0.584 0.482-0.871 0.468-1.426-0.012-0.476-0.13-0.776-0.53-1.344-0.381-0.54-0.54-0.935-0.608-1.5-0.122-1.023 0.267-2.012 1.079-2.743 1.027-0.925 2.55-0.932 3.775-0.018 1.422 1.06 2.27 3.096 2.306 5.539 0.023 1.492-0.24 2.81-0.775 3.903-0.205 0.42-0.353 0.656-0.635 1.02-0.259 0.332-0.284 0.358-0.584 0.622z"
+        android:strokeWidth="0.7559051"
+        android:strokeColor="?attr/deviceIconOnPrimary" />
+    <path
+        android:fillType="evenOdd"
+        android:pathData="M11.771 10.559s0.45 0.443 0.416 1.494c-0.028 0.888-0.539 1.302-0.539 1.302"
+        android:strokeWidth="0.7559051"
+        android:strokeColor="?attr/deviceIconOnPrimary" />
+    <path
+        android:pathData="M17.69 22.996c0.955 0.84 2.264 1.1 3.275 0.65 1.075-0.48 1.814-1.62 1.828-2.82 0.008-0.697-0.155-1.2-0.607-1.883-0.388-0.584-0.482-0.872-0.468-1.426 0.012-0.476 0.13-0.776 0.53-1.344 0.381-0.541 0.54-0.935 0.608-1.501 0.122-1.022-0.267-2.01-1.08-2.742-1.026-0.925-2.549-0.932-3.775-0.018-1.421 1.06-2.269 3.096-2.306 5.539-0.022 1.492 0.24 2.81 0.776 3.903 0.205 0.42 0.353 0.656 0.635 1.02 0.258 0.331 0.283 0.358 0.584 0.622z"
+        android:strokeWidth="0.7559051"
+        android:strokeColor="?attr/deviceIconOnPrimary" />
+    <path
+        android:fillType="evenOdd"
+        android:pathData="M17.482 16.416s-0.45 0.443-0.415 1.494c0.028 0.888 0.539 1.302 0.539 1.302"
+        android:strokeWidth="0.7559051"
+        android:strokeColor="?attr/deviceIconOnPrimary" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_device_galaxy_buds_live_disabled.xml b/app/src/main/res/drawable/ic_device_galaxy_buds_live_disabled.xml
new file mode 100644
index 0000000..26e25a9
--- /dev/null
+++ b/app/src/main/res/drawable/ic_device_galaxy_buds_live_disabled.xml
@@ -0,0 +1,41 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="45sp"
+    android:height="45sp"
+    android:viewportWidth="30"
+    android:viewportHeight="30">
+    <path
+        android:fillColor="#7a7a7a"
+        android:pathData="M3.871 3.877h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.824a0.947 0.947 0 0 1 0.947-0.947z"
+        android:strokeWidth="3.57115173" />
+    <path
+        android:fillAlpha="0.9411765"
+        android:fillColor="#9f9f9f"
+        android:pathData="M3.879 3.035h20.925a0.947 0.947 0 0 1 0.947 0.947v20.01a0.947 0.947 0 0 1-0.947 0.948H3.88a0.947 0.947 0 0 1-0.947-0.948V3.982A0.947 0.947 0 0 1 3.88 3.035z"
+        android:strokeWidth="3.57115173" />
+    <path
+        android:fillColor="#8a8a8a"
+        android:pathData="M3.871 3.413h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.36A0.947 0.947 0 0 1 3.87 3.413z"
+        android:strokeWidth="3.57115173" />
+    <path
+        android:fillAlpha="0.9411765"
+        android:fillColor="#8a8a8a"
+        android:pathData="M12.483 10.865h3.907v6.978h-3.907z" />
+    <path
+        android:pathData="M11.564 17.14C10.61 17.978 9.3 18.237 8.29 17.787 7.214 17.31 6.474 16.17 6.46 14.97c-0.008-0.698 0.155-1.202 0.607-1.884 0.388-0.584 0.482-0.871 0.468-1.426-0.012-0.476-0.13-0.776-0.53-1.344-0.381-0.54-0.54-0.935-0.608-1.5-0.122-1.023 0.267-2.012 1.079-2.743 1.027-0.925 2.55-0.932 3.775-0.018 1.422 1.06 2.27 3.096 2.306 5.539 0.023 1.492-0.24 2.81-0.775 3.903-0.205 0.42-0.353 0.656-0.635 1.02-0.259 0.332-0.284 0.358-0.584 0.622z"
+        android:strokeWidth="0.7559051"
+        android:strokeColor="#fffffd" />
+    <path
+        android:fillType="evenOdd"
+        android:pathData="M11.771 10.559s0.45 0.443 0.416 1.494c-0.028 0.888-0.539 1.302-0.539 1.302"
+        android:strokeWidth="0.7559051"
+        android:strokeColor="#fffffd" />
+    <path
+        android:pathData="M17.69 22.996c0.955 0.84 2.264 1.1 3.275 0.65 1.075-0.48 1.814-1.62 1.828-2.82 0.008-0.697-0.155-1.2-0.607-1.883-0.388-0.584-0.482-0.872-0.468-1.426 0.012-0.476 0.13-0.776 0.53-1.344 0.381-0.541 0.54-0.935 0.608-1.501 0.122-1.022-0.267-2.01-1.08-2.742-1.026-0.925-2.549-0.932-3.775-0.018-1.421 1.06-2.269 3.096-2.306 5.539-0.022 1.492 0.24 2.81 0.776 3.903 0.205 0.42 0.353 0.656 0.635 1.02 0.258 0.331 0.283 0.358 0.584 0.622z"
+        android:strokeWidth="0.7559051"
+        android:strokeColor="#fffffd" />
+    <path
+        android:fillType="evenOdd"
+        android:pathData="M17.482 16.416s-0.45 0.443-0.415 1.494c0.028 0.888 0.539 1.302 0.539 1.302"
+        android:strokeWidth="0.7559051"
+        android:strokeColor="#fffffd" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_device_galaxy_buds_pro.xml b/app/src/main/res/drawable/ic_device_galaxy_buds_pro.xml
new file mode 100644
index 0000000..4b825f0
--- /dev/null
+++ b/app/src/main/res/drawable/ic_device_galaxy_buds_pro.xml
@@ -0,0 +1,31 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="45sp"
+    android:height="45sp"
+    android:viewportWidth="30"
+    android:viewportHeight="30">
+    <path
+        android:fillColor="?attr/deviceIconLight"
+        android:pathData="M3.8712,3.8769L24.7963,3.8769A0.9472,0.9472 0,0 1,25.7435 4.8241L25.7435,24.8343A0.9472,0.9472 0,0 1,24.7963 25.7815L3.8712,25.7815A0.9472,0.9472 0,0 1,2.924 24.8343L2.924,4.8241A0.9472,0.9472 0,0 1,3.8712 3.8769z"
+        android:strokeWidth="3.57115"
+        android:strokeColor="#00000000" />
+    <path
+        android:fillColor="?attr/deviceIconDark"
+        android:pathData="M3.879,3.0349L24.8041,3.0349A0.9472,0.9472 0,0 1,25.7514 3.9822L25.7514,23.9924A0.9472,0.9472 0,0 1,24.8041 24.9396L3.879,24.9396A0.9472,0.9472 0,0 1,2.9318 23.9924L2.9318,3.9822A0.9472,0.9472 0,0 1,3.879 3.0349z"
+        android:strokeWidth="3.57115"
+        android:strokeColor="#00000000" />
+    <path
+        android:fillColor="?attr/deviceIconPrimary"
+        android:pathData="M3.8711,3.4131L24.7962,3.4131A0.9472,0.9472 0,0 1,25.7435 4.3603L25.7435,24.3705A0.9472,0.9472 0,0 1,24.7962 25.3177L3.8711,25.3177A0.9472,0.9472 0,0 1,2.9239 24.3705L2.9239,4.3603A0.9472,0.9472 0,0 1,3.8711 3.4131z"
+        android:strokeWidth="3.57115"
+        android:strokeColor="#00000000" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="m21.2919,23.2015c0.6342,-0.1548 1.0149,-0.392 1.4344,-0.894 0.7094,-0.8486 0.8512,-2.1283 0.3502,-3.158 -0.1582,-0.3251 -0.377,-0.6401 -0.4861,-0.6999 -0.1847,-0.1012 -0.1868,-0.1424 -0.0305,-0.5933 0.4121,-1.1887 0.2451,-3.014 -0.384,-4.1977 -0.3848,-0.724 -1.2208,-1.4895 -1.8766,-1.7183 -1.3054,-0.4555 -3.136,0.051 -4.0509,1.1204 -1.418,1.6577 -1.7887,3.8542 -0.9565,5.6672 0.2793,0.6085 1.4226,1.7934 2.2541,2.3362 0.402,0.2624 0.6604,0.5313 0.7933,0.8255 0.4347,0.9623 1.8352,1.5845 2.9527,1.3118zM19.7347,22.8078c-0.4177,-0.1787 -0.9625,-0.6424 -1.1335,-0.9646 -0.0846,-0.1596 -0.0299,-0.161 0.5561,-0.0144 1.1521,0.2882 1.9675,0.0109 2.6995,-0.9215 0.3051,-0.3883 0.346,-0.5332 0.3978,-1.4115l0.0577,-0.9789 0.3002,0.3663c0.9623,1.1744 0.5377,3.1707 -0.821,3.8594 -0.5724,0.2901 -1.4649,0.3184 -2.0568,0.0651zM18.7182,21.4086c-1.1454,-0.4457 -2.8916,-2.0023 -3.2716,-2.9163 -0.9165,-2.2045 0.1396,-5.1562 2.19,-6.1208 0.9436,-0.4439 2.0937,-0.462 2.8908,-0.0454 1.6982,0.8873 2.465,3.4189 1.7138,5.6589 -0.1131,0.3371 -0.2067,0.9238 -0.208,1.3038 -0.0056,1.476 -0.786,2.3326 -2.1276,2.3341 -0.3572,0.0004 -0.8771,-0.0933 -1.1874,-0.2141zM18.7696,18.7483c0.0941,-0.1471 0.1413,-0.347 0.1049,-0.4442 -0.0365,-0.0973 -0.4674,-0.3424 -0.9577,-0.5448 -0.994,-0.4103 -1.2506,-0.3717 -1.2506,0.1881 0,0.2954 0.0735,0.3564 0.8303,0.6882 1.0117,0.4436 1.0587,0.4478 1.2731,0.1126z"
+        android:strokeWidth="0.499999"
+        android:strokeColor="?attr/deviceIconOnPrimary" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="m7.8637,17.6849c-0.6433,-0.1548 -1.0294,-0.392 -1.455,-0.894 -0.7195,-0.8486 -0.8635,-2.1283 -0.3552,-3.158 0.1605,-0.3251 0.3824,-0.6401 0.4931,-0.6999 0.1874,-0.1012 0.1895,-0.1424 0.031,-0.5932 -0.418,-1.1887 -0.2486,-3.014 0.3895,-4.1977 0.3903,-0.724 1.2383,-1.4895 1.9035,-1.7183 1.3241,-0.4555 3.181,0.0509 4.1091,1.1204 1.4383,1.6578 1.8143,3.8542 0.9702,5.6672 -0.2833,0.6085 -1.443,1.7934 -2.2864,2.3362 -0.4078,0.2624 -0.6699,0.5313 -0.8047,0.8255 -0.4409,0.9623 -1.8616,1.5845 -2.995,1.3118zM9.4431,17.2912c0.4237,-0.1787 0.9763,-0.6423 1.1497,-0.9646 0.0858,-0.1596 0.0303,-0.161 -0.5641,-0.0144 -1.1687,0.2882 -1.9957,0.0108 -2.7382,-0.9215 -0.3095,-0.3882 -0.3509,-0.5332 -0.4035,-1.4115l-0.0586,-0.9789 -0.3045,0.3663c-0.9761,1.1744 -0.5454,3.1707 0.8327,3.8594 0.5806,0.2901 1.4859,0.3184 2.0864,0.0652zM10.4743,15.892c1.1618,-0.4457 2.9331,-2.0023 3.3186,-2.9163C14.7225,10.7711 13.6513,7.8195 11.5715,6.8549 10.6144,6.411 9.4477,6.3929 8.6392,6.8094c-1.7226,0.8873 -2.5003,3.419 -1.7384,5.6589 0.1147,0.3371 0.2096,0.9238 0.211,1.3038 0.0052,1.4759 0.7973,2.3326 2.1581,2.3341 0.3623,0.0004 0.8897,-0.0933 1.2044,-0.2141zM10.4222,13.2317c-0.0955,-0.1471 -0.1434,-0.347 -0.1064,-0.4442 0.037,-0.0973 0.4741,-0.3424 0.9715,-0.5448 1.0082,-0.4103 1.2686,-0.3717 1.2686,0.1881 0,0.2954 -0.0746,0.3564 -0.8422,0.6882 -1.0262,0.4436 -1.0739,0.4478 -1.2914,0.1126z"
+        android:strokeWidth="0.5"
+        android:strokeColor="?attr/deviceIconOnPrimary" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_device_galaxy_buds_pro_disabled.xml b/app/src/main/res/drawable/ic_device_galaxy_buds_pro_disabled.xml
new file mode 100644
index 0000000..9d9584b
--- /dev/null
+++ b/app/src/main/res/drawable/ic_device_galaxy_buds_pro_disabled.xml
@@ -0,0 +1,36 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="45sp"
+    android:height="45sp"
+    android:viewportWidth="30"
+    android:viewportHeight="30">
+    <path
+        android:fillColor="#7a7a7a"
+        android:pathData="M3.8712,3.8769L24.7963,3.8769A0.9472,0.9472 0,0 1,25.7435 4.8241L25.7435,24.8343A0.9472,0.9472 0,0 1,24.7963 25.7815L3.8712,25.7815A0.9472,0.9472 0,0 1,2.924 24.8343L2.924,4.8241A0.9472,0.9472 0,0 1,3.8712 3.8769z"
+        android:strokeWidth="3.57115173"
+        android:strokeColor="#00000000" />
+    <path
+        android:fillAlpha="0.9411765"
+        android:fillColor="#9f9f9f"
+        android:pathData="M3.879,3.0349L24.8041,3.0349A0.9472,0.9472 0,0 1,25.7514 3.9822L25.7514,23.9924A0.9472,0.9472 0,0 1,24.8041 24.9396L3.879,24.9396A0.9472,0.9472 0,0 1,2.9318 23.9924L2.9318,3.9822A0.9472,0.9472 0,0 1,3.879 3.0349z"
+        android:strokeWidth="3.57115173"
+        android:strokeColor="#00000000" />
+    <path
+        android:fillColor="#8a8a8a"
+        android:pathData="M3.8711,3.4131L24.7962,3.4131A0.9472,0.9472 0,0 1,25.7435 4.3603L25.7435,24.3705A0.9472,0.9472 0,0 1,24.7962 25.3177L3.8711,25.3177A0.9472,0.9472 0,0 1,2.9239 24.3705L2.9239,4.3603A0.9472,0.9472 0,0 1,3.8711 3.4131z"
+        android:strokeWidth="3.57115173"
+        android:strokeColor="#00000000" />
+    <path
+        android:fillAlpha="0.9411765"
+        android:fillColor="#8a8a8a"
+        android:pathData="m12.4827,10.8654h3.907v6.978h-3.907z" />
+    <path
+        android:fillColor="#ffffff"
+        android:pathData="m21.2919,23.2015c0.6342,-0.1548 1.0149,-0.392 1.4344,-0.894 0.7094,-0.8486 0.8512,-2.1283 0.3502,-3.158 -0.1582,-0.3251 -0.377,-0.6401 -0.4861,-0.6999 -0.1847,-0.1012 -0.1868,-0.1424 -0.0305,-0.5933 0.4121,-1.1887 0.2451,-3.014 -0.384,-4.1977 -0.3848,-0.724 -1.2208,-1.4895 -1.8766,-1.7183 -1.3054,-0.4555 -3.136,0.051 -4.0509,1.1204 -1.418,1.6577 -1.7887,3.8542 -0.9565,5.6672 0.2793,0.6085 1.4226,1.7934 2.2541,2.3362 0.402,0.2624 0.6604,0.5313 0.7933,0.8255 0.4347,0.9623 1.8352,1.5845 2.9527,1.3118zM19.7347,22.8078c-0.4177,-0.1787 -0.9625,-0.6424 -1.1335,-0.9646 -0.0846,-0.1596 -0.0299,-0.161 0.5561,-0.0144 1.1521,0.2882 1.9675,0.0109 2.6995,-0.9215 0.3051,-0.3883 0.346,-0.5332 0.3978,-1.4115l0.0577,-0.9789 0.3002,0.3663c0.9623,1.1744 0.5377,3.1707 -0.821,3.8594 -0.5724,0.2901 -1.4649,0.3184 -2.0568,0.0651zM18.7182,21.4086c-1.1454,-0.4457 -2.8916,-2.0023 -3.2716,-2.9163 -0.9165,-2.2045 0.1396,-5.1562 2.19,-6.1208 0.9436,-0.4439 2.0937,-0.462 2.8908,-0.0454 1.6982,0.8873 2.465,3.4189 1.7138,5.6589 -0.1131,0.3371 -0.2067,0.9238 -0.208,1.3038 -0.0056,1.476 -0.786,2.3326 -2.1276,2.3341 -0.3572,0.0004 -0.8771,-0.0933 -1.1874,-0.2141zM18.7696,18.7483c0.0941,-0.1471 0.1413,-0.347 0.1049,-0.4442 -0.0365,-0.0973 -0.4674,-0.3424 -0.9577,-0.5448 -0.994,-0.4103 -1.2506,-0.3717 -1.2506,0.1881 0,0.2954 0.0735,0.3564 0.8303,0.6882 1.0117,0.4436 1.0587,0.4478 1.2731,0.1126z"
+        android:strokeWidth="0.499999"
+        android:strokeColor="#ffffff" />
+    <path
+        android:fillColor="#ffffff"
+        android:pathData="m7.8637,17.6849c-0.6433,-0.1548 -1.0294,-0.392 -1.455,-0.894 -0.7195,-0.8486 -0.8635,-2.1283 -0.3552,-3.158 0.1605,-0.3251 0.3824,-0.6401 0.4931,-0.6999 0.1874,-0.1012 0.1895,-0.1424 0.031,-0.5932 -0.418,-1.1887 -0.2486,-3.014 0.3895,-4.1977 0.3903,-0.724 1.2383,-1.4895 1.9035,-1.7183 1.3241,-0.4555 3.181,0.0509 4.1091,1.1204 1.4383,1.6578 1.8143,3.8542 0.9702,5.6672 -0.2833,0.6085 -1.443,1.7934 -2.2864,2.3362 -0.4078,0.2624 -0.6699,0.5313 -0.8047,0.8255 -0.4409,0.9623 -1.8616,1.5845 -2.995,1.3118zM9.4431,17.2912c0.4237,-0.1787 0.9763,-0.6423 1.1497,-0.9646 0.0858,-0.1596 0.0303,-0.161 -0.5641,-0.0144 -1.1687,0.2882 -1.9957,0.0108 -2.7382,-0.9215 -0.3095,-0.3882 -0.3509,-0.5332 -0.4035,-1.4115l-0.0586,-0.9789 -0.3045,0.3663c-0.9761,1.1744 -0.5454,3.1707 0.8327,3.8594 0.5806,0.2901 1.4859,0.3184 2.0864,0.0652zM10.4743,15.892c1.1618,-0.4457 2.9331,-2.0023 3.3186,-2.9163C14.7225,10.7711 13.6513,7.8195 11.5715,6.8549 10.6144,6.411 9.4477,6.3929 8.6392,6.8094c-1.7226,0.8873 -2.5003,3.419 -1.7384,5.6589 0.1147,0.3371 0.2096,0.9238 0.211,1.3038 0.0052,1.4759 0.7973,2.3326 2.1581,2.3341 0.3623,0.0004 0.8897,-0.0933 1.2044,-0.2141zM10.4222,13.2317c-0.0955,-0.1471 -0.1434,-0.347 -0.1064,-0.4442 0.037,-0.0973 0.4741,-0.3424 0.9715,-0.5448 1.0082,-0.4103 1.2686,-0.3717 1.2686,0.1881 0,0.2954 -0.0746,0.3564 -0.8422,0.6882 -1.0262,0.4436 -1.0739,0.4478 -1.2914,0.1126z"
+        android:strokeWidth="0.5"
+        android:strokeColor="#fffffd" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_device_h30_h10.xml b/app/src/main/res/drawable/ic_device_h30_h10.xml
new file mode 100644
index 0000000..bacaab9
--- /dev/null
+++ b/app/src/main/res/drawable/ic_device_h30_h10.xml
@@ -0,0 +1,32 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="45sp"
+    android:height="45sp"
+    android:viewportWidth="30"
+    android:viewportHeight="30">
+    <path
+        android:fillColor="#6cb8f7"
+        android:pathData="M3.612 2.619h19.7a0.854 0.854 0 0 1 0.854 0.854v18.94a0.854 0.854 0 0 1-0.855 0.855h-19.7a0.854 0.854 0 0 1-0.854-0.854V3.473a0.854 0.854 0 0 1 0.855-0.854z"
+        android:strokeWidth="0.99516809" />
+    <path
+        android:fillColor="#0c84e3"
+        android:pathData="M3.61 3.393h19.7a0.854 0.854 0 0 1 0.855 0.854v18.941a0.854 0.854 0 0 1-0.854 0.854H3.61a0.854 0.854 0 0 1-0.854-0.854V4.248A0.854 0.854 0 0 1 3.61 3.392z"
+        android:strokeWidth="0.99516809" />
+    <path
+        android:fillColor="?attr/deviceIconPrimary"
+        android:pathData="M3.61 3.024h19.7a0.854 0.854 0 0 1 0.854 0.854v18.94a0.854 0.854 0 0 1-0.854 0.854H3.61a0.854 0.854 0 0 1-0.854-0.854V3.878A0.854 0.854 0 0 1 3.61 3.024z"
+        android:strokeWidth="0.99516809" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M6.569 11.48h13.878a0.603 0.553 0 0 1 0.603 0.553v2.34a0.603 0.553 0 0 1-0.603 0.554H6.57a0.603 0.553 0 0 1-0.603-0.554v-2.34a0.603 0.553 0 0 1 0.603-0.554z"
+        android:strokeWidth="0.95660609" />
+    <path
+        android:fillColor="?attr/deviceIconPrimary"
+        android:pathData="M7.84 10.065h11.436a0.591 0.553 0 0 1 0.591 0.554v5.522a0.591 0.553 0 0 1-0.591 0.553H7.839a0.591 0.553 0 0 1-0.591-0.553v-5.522a0.591 0.553 0 0 1 0.591-0.554z"
+        android:strokeWidth="0.98218656" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M8.572 11.17H18.66a0.613 0.553 0 0 1 0.613 0.553v3.048a0.613 0.553 0 0 1-0.613 0.553H8.572a0.613 0.553 0 0 1-0.613-0.553v-3.048a0.613 0.553 0 0 1 0.614-0.553z" />
+    <path
+        android:fillColor="?attr/deviceIconPrimary"
+        android:pathData="M16.788 13.25a0.738 0.751 0 1 0 1.477 0 0.738 0.751 0 1 0-1.477 0z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_device_h30_h10_disabled.xml b/app/src/main/res/drawable/ic_device_h30_h10_disabled.xml
new file mode 100644
index 0000000..5e898b0
--- /dev/null
+++ b/app/src/main/res/drawable/ic_device_h30_h10_disabled.xml
@@ -0,0 +1,32 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="45sp"
+    android:height="45sp"
+    android:viewportWidth="30"
+    android:viewportHeight="30">
+    <path
+        android:fillColor="#a1a1a1"
+        android:pathData="M4.036 2.923h19.7a0.854 0.854 0 0 1 0.854 0.854v18.94a0.854 0.854 0 0 1-0.854 0.855h-19.7a0.854 0.854 0 0 1-0.854-0.854V3.778a0.854 0.854 0 0 1 0.854-0.855z"
+        android:strokeWidth="0.99516809" />
+    <path
+        android:fillColor="#7d7d7d"
+        android:pathData="M4.035 3.698h19.7a0.854 0.854 0 0 1 0.854 0.854v18.94a0.854 0.854 0 0 1-0.854 0.854h-19.7a0.854 0.854 0 0 1-0.854-0.854V4.552a0.854 0.854 0 0 1 0.854-0.854z"
+        android:strokeWidth="0.99516809" />
+    <path
+        android:fillColor="#8a8a8a"
+        android:pathData="M4.034 3.328h19.7a0.854 0.854 0 0 1 0.854 0.854v18.94a0.854 0.854 0 0 1-0.854 0.855h-19.7a0.854 0.854 0 0 1-0.854-0.854V4.182a0.854 0.854 0 0 1 0.854-0.854z"
+        android:strokeWidth="0.99516809" />
+    <path
+        android:fillColor="#ffffff"
+        android:pathData="M7.303 11.99h13.962a0.606 0.553 0 0 1 0.606 0.553v2.34a0.606 0.553 0 0 1-0.606 0.554H7.303a0.606 0.553 0 0 1-0.606-0.554v-2.34a0.606 0.553 0 0 1 0.606-0.553z"
+        android:strokeWidth="0.95947295" />
+    <path
+        android:fillColor="#8a8a8a"
+        android:pathData="M8.66 10.575h11.437a0.591 0.553 0 0 1 0.591 0.554v5.522a0.591 0.553 0 0 1-0.59 0.553H8.66a0.591 0.553 0 0 1-0.591-0.553V11.13a0.591 0.553 0 0 1 0.591-0.554z"
+        android:strokeWidth="0.98218656" />
+    <path
+        android:fillColor="#ffffff"
+        android:pathData="M9.393 11.68h10.088a0.613 0.553 0 0 1 0.613 0.554v3.047a0.613 0.553 0 0 1-0.613 0.553H9.393a0.613 0.553 0 0 1-0.613-0.553v-3.047a0.613 0.553 0 0 1 0.614-0.554z" />
+    <path
+        android:fillColor="#8a8a8a"
+        android:pathData="M17.609 13.76a0.738 0.751 0 1 0 1.477 0 0.738 0.751 0 1 0-1.477 0z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_device_headphones.xml b/app/src/main/res/drawable/ic_device_headphones.xml
new file mode 100644
index 0000000..5d6aa42
--- /dev/null
+++ b/app/src/main/res/drawable/ic_device_headphones.xml
@@ -0,0 +1,28 @@
+<vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:name="vector"
+    android:width="28dp"
+    android:height="28dp"
+    android:viewportWidth="28"
+    android:viewportHeight="28">
+    <path
+        android:name="path_1"
+        android:pathData="M 3.871 3.877 L 24.796 3.877 C 25.047 3.877 25.289 3.977 25.466 4.154 C 25.644 4.332 25.744 4.573 25.744 4.824 L 25.744 24.834 C 25.744 25.085 25.644 25.327 25.466 25.504 C 25.289 25.682 25.047 25.782 24.796 25.782 L 3.871 25.782 C 3.62 25.782 3.379 25.682 3.201 25.504 C 3.024 25.327 2.924 25.085 2.924 24.834 L 2.924 4.824 C 2.924 4.573 3.024 4.332 3.201 4.154 C 3.379 3.977 3.62 3.877 3.871 3.877"
+        android:fillColor="?attr/deviceIconLight"
+        android:strokeWidth="1"/>
+    <path
+        android:name="path_5"
+        android:pathData="M 3.879 3.035 L 24.804 3.035 C 25.055 3.035 25.296 3.135 25.474 3.312 C 25.651 3.49 25.751 3.731 25.751 3.982 L 25.751 23.992 C 25.751 24.243 25.651 24.485 25.474 24.662 C 25.296 24.84 25.055 24.94 24.804 24.94 L 3.879 24.94 C 3.628 24.94 3.387 24.84 3.209 24.662 C 3.032 24.485 2.932 24.243 2.932 23.992 L 2.932 3.982 C 2.932 3.731 3.032 3.49 3.209 3.312 C 3.387 3.135 3.628 3.035 3.879 3.035"
+        android:fillColor="?attr/deviceIconDark"
+        android:strokeWidth="1"/>
+    <path
+        android:name="path_6"
+        android:pathData="M 3.879 3.035 L 24.804 3.035 C 25.055 3.035 25.296 3.135 25.474 3.313 C 25.652 3.49 25.751 3.731 25.751 3.982 L 25.751 23.992 C 25.751 24.244 25.652 24.485 25.474 24.662 C 25.296 24.84 25.055 24.94 24.804 24.94 L 3.879 24.94 C 3.628 24.94 3.387 24.84 3.209 24.662 C 3.032 24.485 2.932 24.244 2.932 23.992 L 2.932 3.982 C 2.932 3.731 3.032 3.49 3.209 3.313 C 3.387 3.135 3.628 3.035 3.879 3.035"
+        android:fillColor="?attr/deviceIconPrimary"
+        android:strokeWidth="1"/>
+    <path
+        android:name="path_7"
+        android:pathData="M 14.362 8.693 C 11.231 8.693 8.693 11.231 8.693 14.362 L 8.693 18.772 C 8.693 19.465 9.26 20.031 9.953 20.031 L 12.472 20.031 L 12.472 14.992 L 9.953 14.992 L 9.953 14.362 C 9.953 11.924 11.924 9.953 14.362 9.953 C 16.8 9.953 18.772 11.924 18.772 14.362 L 18.772 14.992 L 16.252 14.992 L 16.252 20.031 L 18.772 20.031 C 19.465 20.031 20.031 19.465 20.031 18.772 L 20.031 14.362 C 20.031 11.231 17.493 8.693 14.362 8.693 Z"
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:strokeWidth="1"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_device_headphones_disabled.xml b/app/src/main/res/drawable/ic_device_headphones_disabled.xml
new file mode 100644
index 0000000..15026d7
--- /dev/null
+++ b/app/src/main/res/drawable/ic_device_headphones_disabled.xml
@@ -0,0 +1,28 @@
+<vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:name="vector"
+    android:width="28dp"
+    android:height="28dp"
+    android:viewportWidth="28"
+    android:viewportHeight="28">
+    <path
+        android:name="path_1"
+        android:pathData="M 3.871 3.877 L 24.796 3.877 C 25.047 3.877 25.289 3.977 25.466 4.154 C 25.644 4.332 25.744 4.573 25.744 4.824 L 25.744 24.834 C 25.744 25.085 25.644 25.327 25.466 25.504 C 25.289 25.682 25.047 25.782 24.796 25.782 L 3.871 25.782 C 3.62 25.782 3.379 25.682 3.201 25.504 C 3.024 25.327 2.924 25.085 2.924 24.834 L 2.924 4.824 C 2.924 4.573 3.024 4.332 3.201 4.154 C 3.379 3.977 3.62 3.877 3.871 3.877"
+        android:fillColor="#7a7a7a"
+        android:strokeWidth="1"/>
+    <path
+        android:name="path_5"
+        android:pathData="M 3.879 3.035 L 24.804 3.035 C 25.055 3.035 25.296 3.135 25.474 3.312 C 25.651 3.49 25.751 3.731 25.751 3.982 L 25.751 23.992 C 25.751 24.243 25.651 24.485 25.474 24.662 C 25.296 24.84 25.055 24.94 24.804 24.94 L 3.879 24.94 C 3.628 24.94 3.387 24.84 3.209 24.662 C 3.032 24.485 2.932 24.243 2.932 23.992 L 2.932 3.982 C 2.932 3.731 3.032 3.49 3.209 3.312 C 3.387 3.135 3.628 3.035 3.879 3.035"
+        android:fillColor="#9f9f9f"
+        android:strokeWidth="1"/>
+    <path
+        android:name="path_6"
+        android:pathData="M 3.879 3.035 L 24.804 3.035 C 25.055 3.035 25.296 3.135 25.474 3.313 C 25.652 3.49 25.751 3.731 25.751 3.982 L 25.751 23.992 C 25.751 24.244 25.652 24.485 25.474 24.662 C 25.296 24.84 25.055 24.94 24.804 24.94 L 3.879 24.94 C 3.628 24.94 3.387 24.84 3.209 24.662 C 3.032 24.485 2.932 24.244 2.932 23.992 L 2.932 3.982 C 2.932 3.731 3.032 3.49 3.209 3.313 C 3.387 3.135 3.628 3.035 3.879 3.035"
+        android:fillColor="#8a8a8a"
+        android:strokeWidth="1"/>
+    <path
+        android:name="path_7"
+        android:pathData="M 14.362 8.693 C 11.231 8.693 8.693 11.231 8.693 14.362 L 8.693 18.772 C 8.693 19.465 9.26 20.031 9.953 20.031 L 12.472 20.031 L 12.472 14.992 L 9.953 14.992 L 9.953 14.362 C 9.953 11.924 11.924 9.953 14.362 9.953 C 16.8 9.953 18.772 11.924 18.772 14.362 L 18.772 14.992 L 16.252 14.992 L 16.252 20.031 L 18.772 20.031 C 19.465 20.031 20.031 19.465 20.031 18.772 L 20.031 14.362 C 20.031 11.231 17.493 8.693 14.362 8.693 Z"
+        android:fillColor="#ffffff"
+        android:strokeWidth="1"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_device_hplus.xml b/app/src/main/res/drawable/ic_device_hplus.xml
new file mode 100644
index 0000000..a8efd94
--- /dev/null
+++ b/app/src/main/res/drawable/ic_device_hplus.xml
@@ -0,0 +1,34 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="45sp"
+    android:height="45sp"
+    android:viewportWidth="30"
+    android:viewportHeight="30">
+    <path
+        android:fillColor="?attr/deviceIconLight"
+        android:pathData="M3.871 3.877h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.824a0.947 0.947 0 0 1 0.947-0.947z"
+        android:strokeWidth="3.57115173" />
+    <path
+        android:fillColor="?attr/deviceIconDark"
+        android:pathData="M3.879 3.035h20.925a0.947 0.947 0 0 1 0.947 0.947v20.01a0.947 0.947 0 0 1-0.947 0.948H3.88a0.947 0.947 0 0 1-0.947-0.948V3.982A0.947 0.947 0 0 1 3.88 3.035z"
+        android:strokeWidth="3.57115173" />
+    <path
+        android:fillColor="?attr/deviceIconPrimary"
+        android:pathData="M3.871 3.413h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.36A0.947 0.947 0 0 1 3.87 3.413z"
+        android:strokeWidth="3.57115173" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M19.932 9.383h0.397a0.53 0.53 0 0 1 0.53 0.53v0.53a0.53 0.53 0 0 1-0.53 0.531h-0.397a0.53 0.53 0 0 1-0.53-0.53v-0.53a0.53 0.53 0 0 1 0.53-0.53z"
+        android:strokeWidth="3.5" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M9.848 7.218h8.986a1.142 1.142 0 0 1 1.142 1.142v12.123a1.142 1.142 0 0 1-1.142 1.142H9.848a1.142 1.142 0 0 1-1.142-1.142V8.36a1.142 1.142 0 0 1 1.142-1.142z"
+        android:strokeWidth="3.46226358" />
+    <path
+        android:fillColor="?attr/deviceIconPrimary"
+        android:pathData="M10.076 9.295h8.441V19.37h-8.44z"
+        android:strokeWidth="3.5" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M11.225 10.62h6.276v7.514h-6.276z"
+        android:strokeWidth="3.5" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_device_hplus_disabled.xml b/app/src/main/res/drawable/ic_device_hplus_disabled.xml
new file mode 100644
index 0000000..15e6a09
--- /dev/null
+++ b/app/src/main/res/drawable/ic_device_hplus_disabled.xml
@@ -0,0 +1,35 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="45sp"
+    android:height="45sp"
+    android:viewportWidth="30"
+    android:viewportHeight="30">
+    <path
+        android:fillColor="#7a7a7a"
+        android:pathData="M3.871 3.877h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.824a0.947 0.947 0 0 1 0.947-0.947z"
+        android:strokeWidth="3.57115173" />
+    <path
+        android:fillAlpha="0.9411765"
+        android:fillColor="#9f9f9f"
+        android:pathData="M3.879 3.035h20.925a0.947 0.947 0 0 1 0.947 0.947v20.01a0.947 0.947 0 0 1-0.947 0.948H3.88a0.947 0.947 0 0 1-0.947-0.948V3.982A0.947 0.947 0 0 1 3.88 3.035z"
+        android:strokeWidth="3.57115173" />
+    <path
+        android:fillColor="#8a8a8a"
+        android:pathData="M3.871 3.413h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.36A0.947 0.947 0 0 1 3.87 3.413z"
+        android:strokeWidth="3.57115173" />
+    <path
+        android:fillColor="#ffffff"
+        android:pathData="M19.932 9.383h0.397a0.53 0.53 0 0 1 0.53 0.53v0.53a0.53 0.53 0 0 1-0.53 0.531h-0.397a0.53 0.53 0 0 1-0.53-0.53v-0.53a0.53 0.53 0 0 1 0.53-0.53z"
+        android:strokeWidth="3.5" />
+    <path
+        android:fillColor="#ffffff"
+        android:pathData="M9.848 7.218h8.986a1.142 1.142 0 0 1 1.142 1.142v12.123a1.142 1.142 0 0 1-1.142 1.142H9.848a1.142 1.142 0 0 1-1.142-1.142V8.36a1.142 1.142 0 0 1 1.142-1.142z"
+        android:strokeWidth="3.46226358" />
+    <path
+        android:fillColor="#8a8a8a"
+        android:pathData="M10.076 9.295h8.441V19.37h-8.44z"
+        android:strokeWidth="3.5" />
+    <path
+        android:fillColor="#ffffff"
+        android:pathData="M11.181 10.62h6.276v7.514H11.18z"
+        android:strokeWidth="3.5" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_device_itag.xml b/app/src/main/res/drawable/ic_device_itag.xml
new file mode 100644
index 0000000..2dcfdd1
--- /dev/null
+++ b/app/src/main/res/drawable/ic_device_itag.xml
@@ -0,0 +1,40 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="45sp"
+    android:height="45sp"
+    android:viewportWidth="30"
+    android:viewportHeight="30">
+    <path
+        android:fillColor="#6cb8f7"
+        android:pathData="M3.612 2.619h19.7a0.854 0.854 0 0 1 0.854 0.854v18.94a0.854 0.854 0 0 1-0.855 0.855h-19.7a0.854 0.854 0 0 1-0.854-0.854V3.473a0.854 0.854 0 0 1 0.855-0.854z"
+        android:strokeWidth="0.99516809" />
+    <path
+        android:fillColor="#0c84e3"
+        android:pathData="M3.61 3.393h19.7a0.854 0.854 0 0 1 0.855 0.854v18.941a0.854 0.854 0 0 1-0.854 0.854H3.61a0.854 0.854 0 0 1-0.854-0.854V4.248A0.854 0.854 0 0 1 3.61 3.392z"
+        android:strokeWidth="0.99516809" />
+    <path
+        android:fillColor="?attr/deviceIconPrimary"
+        android:pathData="M3.61 3.024h19.7a0.854 0.854 0 0 1 0.854 0.854v18.94a0.854 0.854 0 0 1-0.854 0.854H3.61a0.854 0.854 0 0 1-0.854-0.854V3.878A0.854 0.854 0 0 1 3.61 3.024z"
+        android:strokeWidth="0.99516809" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M13.514 11.518c0.727-0.005 2.73-0.032 3.09-0.659 0.36-0.627-0.642-2.351-1.01-2.974-0.367-0.622-1.391-2.332-2.118-2.328-0.727 0.005-1.73 1.728-2.089 2.355-0.36 0.628-1.338 2.364-0.971 2.987 0.367 0.623 2.371 0.623 3.098 0.619z"
+        android:strokeWidth="0.75212771" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M8.401 13.429c0.879-0.025 1.488 1.191 1.947 2.027 0.458 0.836 1.152 1.994 0.732 2.854-0.42 0.86-1.669 0.84-2.548 0.866-0.879 0.024-2.125 0.115-2.584-0.72-0.459-0.836 0.181-2.032 0.602-2.893 0.42-0.86 0.972-2.11 1.851-2.134z"
+        android:strokeWidth="0.64926457" />
+    <path
+        android:fillColor="?attr/deviceIconPrimary"
+        android:pathData="M13.485 6.99c-0.262 0-0.566 0.013-0.698-0.129-0.132-0.142 0.035-0.302 0.165-0.445 0.13-0.143 0.267-0.315 0.529-0.316 0.262 0 0.4 0.17 0.532 0.311 0.132 0.142 0.3 0.301 0.17 0.444-0.131 0.143-0.435 0.134-0.698 0.135zm-5.054 7.782c-0.237 0-0.58-0.026-0.698-0.154-0.12-0.128 0.012-0.328 0.13-0.458 0.118-0.13 0.326-0.302 0.563-0.303 0.238-0.001 0.448 0.17 0.567 0.298 0.12 0.129 0.253 0.328 0.135 0.457-0.118 0.13-0.46 0.159-0.697 0.16z" />
+    <path
+        android:fillColor="#fefefe"
+        android:pathData="M5.779 18.004a2.74 2.784 0 1 0 5.48 0 2.74 2.784 0 1 0-5.48 0z"
+        android:strokeWidth="1.24393165" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M16.202 14.348h3.905a0.854 0.854 0 0 1 0.854 0.854v3.905a0.854 0.854 0 0 1-0.854 0.854h-3.905a0.854 0.854 0 0 1-0.854-0.854v-3.905a0.854 0.854 0 0 1 0.854-0.854z" />
+    <path
+        android:fillColor="?attr/deviceIconPrimary"
+        android:pathData="M15.866 15.375a0.553 0.536 0 1 0 1.107 0 0.553 0.536 0 1 0-1.107 0z"
+        android:strokeWidth="1.06593406" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_device_itag_disabled.xml b/app/src/main/res/drawable/ic_device_itag_disabled.xml
new file mode 100644
index 0000000..c6072c7
--- /dev/null
+++ b/app/src/main/res/drawable/ic_device_itag_disabled.xml
@@ -0,0 +1,40 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="45sp"
+    android:height="45sp"
+    android:viewportWidth="30"
+    android:viewportHeight="30">
+    <path
+        android:fillColor="#a1a1a1"
+        android:pathData="M4.036 2.923h19.7a0.854 0.854 0 0 1 0.854 0.854v18.94a0.854 0.854 0 0 1-0.854 0.855h-19.7a0.854 0.854 0 0 1-0.854-0.854V3.778a0.854 0.854 0 0 1 0.854-0.855z"
+        android:strokeWidth="0.99516809" />
+    <path
+        android:fillColor="#7d7d7d"
+        android:pathData="M4.035 3.698h19.7a0.854 0.854 0 0 1 0.854 0.854v18.94a0.854 0.854 0 0 1-0.854 0.854h-19.7a0.854 0.854 0 0 1-0.854-0.854V4.552a0.854 0.854 0 0 1 0.854-0.854z"
+        android:strokeWidth="0.99516809" />
+    <path
+        android:fillColor="#8a8a8a"
+        android:pathData="M4.034 3.328h19.7a0.854 0.854 0 0 1 0.854 0.854v18.94a0.854 0.854 0 0 1-0.854 0.855h-19.7a0.854 0.854 0 0 1-0.854-0.854V4.182a0.854 0.854 0 0 1 0.854-0.854z"
+        android:strokeWidth="0.99516809" />
+    <path
+        android:fillColor="#ffffff"
+        android:pathData="M13.939 11.822c0.726-0.005 2.73-0.031 3.09-0.659 0.359-0.627-0.642-2.35-1.01-2.974-0.367-0.622-1.392-2.332-2.118-2.328-0.727 0.005-1.73 1.728-2.09 2.356-0.359 0.627-1.338 2.364-0.97 2.986 0.367 0.623 2.37 0.624 3.098 0.619z"
+        android:strokeWidth="0.75212771" />
+    <path
+        android:fillColor="#ffffff"
+        android:pathData="M8.826 13.733c0.878-0.025 1.487 1.191 1.946 2.027 0.459 0.836 1.153 1.994 0.733 2.854-0.42 0.861-1.67 0.84-2.548 0.866-0.879 0.025-2.126 0.116-2.585-0.72-0.458-0.836 0.182-2.032 0.602-2.892 0.42-0.861 0.973-2.11 1.852-2.135z"
+        android:strokeWidth="0.64926457" />
+    <path
+        android:fillColor="#8a8a8a"
+        android:pathData="M13.91 7.294c-0.262 0.001-0.567 0.013-0.699-0.128-0.132-0.142 0.035-0.303 0.166-0.446 0.13-0.143 0.266-0.314 0.528-0.316 0.263 0 0.4 0.17 0.532 0.311 0.132 0.142 0.3 0.301 0.17 0.444-0.13 0.144-0.435 0.134-0.697 0.135zm-5.054 7.782c-0.238 0.001-0.58-0.025-0.699-0.154-0.12-0.128 0.012-0.328 0.13-0.458 0.118-0.129 0.327-0.302 0.564-0.303 0.237 0 0.447 0.17 0.567 0.299 0.119 0.128 0.253 0.327 0.135 0.456-0.118 0.13-0.46 0.159-0.697 0.16z" />
+    <path
+        android:fillColor="#fefefe"
+        android:pathData="M6.203 18.308a2.74 2.784 0 1 0 5.48 0 2.74 2.784 0 1 0-5.48 0z"
+        android:strokeWidth="1.24393165" />
+    <path
+        android:fillColor="#ffffff"
+        android:pathData="M16.627 14.652h3.905a0.854 0.854 0 0 1 0.854 0.854v3.905a0.854 0.854 0 0 1-0.854 0.854h-3.905a0.854 0.854 0 0 1-0.854-0.854v-3.905a0.854 0.854 0 0 1 0.854-0.854z" />
+    <path
+        android:fillColor="#8a8a8a"
+        android:pathData="M16.29 15.679a0.553 0.536 0 1 0 1.107 0 0.553 0.536 0 1 0-1.107 0z"
+        android:strokeWidth="1.06593406" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_device_lovetoy.xml b/app/src/main/res/drawable/ic_device_lovetoy.xml
new file mode 100644
index 0000000..16efed4
--- /dev/null
+++ b/app/src/main/res/drawable/ic_device_lovetoy.xml
@@ -0,0 +1,35 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="45sp"
+    android:height="45sp"
+    android:viewportWidth="30"
+    android:viewportHeight="30">
+    <path
+        android:fillColor="?attr/deviceIconLight"
+        android:pathData="M3.871 3.877h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.824a0.947 0.947 0 0 1 0.947-0.947z"
+        android:strokeWidth="3.57115173" />
+    <path
+        android:fillColor="?attr/deviceIconDark"
+        android:pathData="M3.879 3.035h20.925a0.947 0.947 0 0 1 0.947 0.947v20.01a0.947 0.947 0 0 1-0.947 0.948H3.88a0.947 0.947 0 0 1-0.947-0.948V3.982A0.947 0.947 0 0 1 3.88 3.035z"
+        android:strokeWidth="3.57115173" />
+    <path
+        android:fillColor="?attr/deviceIconPrimary"
+        android:pathData="M3.871 3.413h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.36A0.947 0.947 0 0 1 3.87 3.413z"
+        android:strokeWidth="3.57115173" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M7.18 11.825a4.008 4.008 0 1 1 8.015 0 4.008 4.008 0 1 1-8.015 0"
+        android:strokeWidth="3.69501472" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M13.493 11.814a4.008 4.008 0 1 1 8.016 0 4.008 4.008 0 1 1-8.016 0"
+        android:strokeWidth="3.69501472" />
+    <path
+        android:pathData="M8.002 13.302c1.955 3.307 6.759 7.152 6.759 7.152m6.035-7.436c-1.171 2.85-6.66 7.434-6.66 7.434"
+        android:strokeWidth="1"
+        android:strokeColor="?attr/deviceIconOnPrimary" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M14.056 19.212c-0.932-0.797-1.796-1.595-2.573-2.373-0.682-0.683-1.056-1.083-0.995-1.065 0.232 0.07 1.014 0.073 1.378 0.006 0.656-0.12 1.237-0.374 1.76-0.772 0.183-0.14 0.529-0.47 0.645-0.617l0.067-0.084 0.318 0.316c0.362 0.362 0.608 0.545 1.014 0.755 0.647 0.335 1.409 0.491 2.12 0.434 0.158-0.013 0.334-0.032 0.392-0.043 0.059-0.011 0.11-0.016 0.114-0.012 0.023 0.023-1.055 1.148-1.698 1.77-0.732 0.708-1.728 1.623-2.145 1.968-0.03 0.024-0.086-0.016-0.397-0.283z"
+        android:strokeWidth="0.07733981"
+        android:strokeColor="?attr/deviceIconOnPrimary" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_device_lovetoy_disabled.xml b/app/src/main/res/drawable/ic_device_lovetoy_disabled.xml
new file mode 100644
index 0000000..207d62f
--- /dev/null
+++ b/app/src/main/res/drawable/ic_device_lovetoy_disabled.xml
@@ -0,0 +1,36 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="45sp"
+    android:height="45sp"
+    android:viewportWidth="30"
+    android:viewportHeight="30">
+    <path
+        android:fillColor="#7a7a7a"
+        android:pathData="M3.871 3.877h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.824a0.947 0.947 0 0 1 0.947-0.947z"
+        android:strokeWidth="3.57115173" />
+    <path
+        android:fillAlpha="0.9411765"
+        android:fillColor="#9f9f9f"
+        android:pathData="M3.879 3.035h20.925a0.947 0.947 0 0 1 0.947 0.947v20.01a0.947 0.947 0 0 1-0.947 0.948H3.88a0.947 0.947 0 0 1-0.947-0.948V3.982A0.947 0.947 0 0 1 3.88 3.035z"
+        android:strokeWidth="3.57115173" />
+    <path
+        android:fillColor="#8a8a8a"
+        android:pathData="M3.871 3.413h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.36A0.947 0.947 0 0 1 3.87 3.413z"
+        android:strokeWidth="3.57115173" />
+    <path
+        android:fillColor="#ffffff"
+        android:pathData="M7.279 12.118a4.008 4.008 0 1 1 8.015 0 4.008 4.008 0 1 1-8.015 0"
+        android:strokeWidth="3.69501472" />
+    <path
+        android:fillColor="#ffffff"
+        android:pathData="M13.592 12.106a4.008 4.008 0 1 1 8.015 0 4.008 4.008 0 1 1-8.015 0"
+        android:strokeWidth="3.69501472" />
+    <path
+        android:pathData="M8.1 13.594c1.956 3.307 6.76 7.152 6.76 7.152m6.035-7.436c-1.171 2.85-6.66 7.434-6.66 7.434"
+        android:strokeWidth="1"
+        android:strokeColor="#ffffff" />
+    <path
+        android:fillColor="#ffffff"
+        android:pathData="M14.154 19.504c-0.931-0.797-1.795-1.595-2.573-2.373-0.682-0.683-1.055-1.083-0.994-1.065 0.231 0.07 1.014 0.073 1.378 0.007 0.655-0.12 1.237-0.375 1.76-0.772 0.183-0.14 0.529-0.47 0.645-0.618l0.067-0.084 0.318 0.317c0.362 0.36 0.608 0.544 1.014 0.754 0.647 0.336 1.409 0.492 2.12 0.434 0.157-0.013 0.334-0.032 0.392-0.043 0.058-0.01 0.11-0.016 0.114-0.012 0.023 0.023-1.056 1.148-1.699 1.77-0.731 0.709-1.727 1.623-2.144 1.968-0.03 0.024-0.086-0.016-0.398-0.283z"
+        android:strokeWidth="0.07733981"
+        android:strokeColor="#ffffff" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_device_miband.xml b/app/src/main/res/drawable/ic_device_miband.xml
new file mode 100644
index 0000000..6032bfd
--- /dev/null
+++ b/app/src/main/res/drawable/ic_device_miband.xml
@@ -0,0 +1,27 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="45sp"
+    android:height="45sp"
+    android:viewportWidth="30"
+    android:viewportHeight="30">
+    <path
+        android:fillColor="?attr/deviceIconLight"
+        android:pathData="M3.871 3.877h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.824a0.947 0.947 0 0 1 0.947-0.947z"
+        android:strokeWidth="3.57115173" />
+    <path
+        android:fillColor="?attr/deviceIconDark"
+        android:pathData="M3.879 3.035h20.925a0.947 0.947 0 0 1 0.947 0.947v20.01a0.947 0.947 0 0 1-0.947 0.948H3.88a0.947 0.947 0 0 1-0.947-0.948V3.982A0.947 0.947 0 0 1 3.88 3.035z"
+        android:strokeWidth="3.57115173" />
+    <path
+        android:fillColor="?attr/deviceIconPrimary"
+        android:pathData="M3.871 3.413h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.36A0.947 0.947 0 0 1 3.87 3.413z"
+        android:strokeWidth="3.57115173" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M9.388 13.204h9.841a1.094 1.094 0 0 1 1.094 1.094v0.228a1.094 1.094 0 0 1-1.094 1.094H9.39a1.094 1.094 0 0 1-1.095-1.094v-0.228a1.094 1.094 0 0 1 1.094-1.094z"
+        android:strokeWidth="2.22514033"
+        android:strokeColor="?attr/deviceIconOnPrimary" />
+    <path
+        android:fillColor="?attr/deviceIconPrimary"
+        android:pathData="M11.468 14.392a0.458 0.458 0 1 1 0.916 0 0.458 0.458 0 1 1-0.916 0m2.437 0.008a0.458 0.458 0 1 1 0.916 0 0.458 0.458 0 1 1-0.916 0m2.385-0.01a0.458 0.458 0 1 1 0.916 0 0.458 0.458 0 1 1-0.916 0"
+        android:strokeWidth="1.34653521" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_device_miband2.xml b/app/src/main/res/drawable/ic_device_miband2.xml
new file mode 100644
index 0000000..6cdcdd0
--- /dev/null
+++ b/app/src/main/res/drawable/ic_device_miband2.xml
@@ -0,0 +1,31 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="45sp"
+    android:height="45sp"
+    android:viewportWidth="30"
+    android:viewportHeight="30">
+    <path
+        android:fillColor="?attr/deviceIconLight"
+        android:pathData="M3.871 3.877h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.824a0.947 0.947 0 0 1 0.947-0.947z"
+        android:strokeWidth="3.57115173" />
+    <path
+        android:fillColor="?attr/deviceIconDark"
+        android:pathData="M3.879 3.035h20.925a0.947 0.947 0 0 1 0.947 0.947v20.01a0.947 0.947 0 0 1-0.947 0.948H3.88a0.947 0.947 0 0 1-0.947-0.948V3.982A0.947 0.947 0 0 1 3.88 3.035z"
+        android:strokeWidth="3.57115173" />
+    <path
+        android:fillColor="?attr/deviceIconPrimary"
+        android:pathData="M3.871 3.413h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.36A0.947 0.947 0 0 1 3.87 3.413z"
+        android:strokeWidth="3.57115173" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M9.388 13.204h9.841a1.094 1.094 0 0 1 1.094 1.094v0.228a1.094 1.094 0 0 1-1.094 1.094H9.39a1.094 1.094 0 0 1-1.095-1.094v-0.228a1.094 1.094 0 0 1 1.094-1.094z"
+        android:strokeWidth="2.22514033"
+        android:strokeColor="?attr/deviceIconOnPrimary" />
+    <path
+        android:fillColor="?attr/deviceIconPrimary"
+        android:pathData="M17.298 14.405a1.405 1.405 0 1 1 2.81 0 1.405 1.405 0 1 1-2.81 0"
+        android:strokeWidth="3.82923341" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M17.675 14.406a1.03 1.03 0 1 1 2.06 0 1.03 1.03 0 1 1-2.06 0"
+        android:strokeWidth="2.80542302" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_device_miband2_disabled.xml b/app/src/main/res/drawable/ic_device_miband2_disabled.xml
new file mode 100644
index 0000000..0044c1e
--- /dev/null
+++ b/app/src/main/res/drawable/ic_device_miband2_disabled.xml
@@ -0,0 +1,32 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="45sp"
+    android:height="45sp"
+    android:viewportWidth="30"
+    android:viewportHeight="30">
+    <path
+        android:fillColor="#7a7a7a"
+        android:pathData="M3.871 3.877h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.824a0.947 0.947 0 0 1 0.947-0.947z"
+        android:strokeWidth="3.57115173" />
+    <path
+        android:fillAlpha="0.9411765"
+        android:fillColor="#9f9f9f"
+        android:pathData="M3.879 3.035h20.925a0.947 0.947 0 0 1 0.947 0.947v20.01a0.947 0.947 0 0 1-0.947 0.948H3.88a0.947 0.947 0 0 1-0.947-0.948V3.982A0.947 0.947 0 0 1 3.88 3.035z"
+        android:strokeWidth="3.57115173" />
+    <path
+        android:fillColor="#8a8a8a"
+        android:pathData="M3.871 3.413h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.36A0.947 0.947 0 0 1 3.87 3.413z"
+        android:strokeWidth="3.57115173" />
+    <path
+        android:fillColor="#ffffff"
+        android:pathData="M9.388 13.204h9.841a1.094 1.094 0 0 1 1.094 1.094v0.228a1.094 1.094 0 0 1-1.094 1.094H9.39a1.094 1.094 0 0 1-1.095-1.094v-0.228a1.094 1.094 0 0 1 1.094-1.094z"
+        android:strokeWidth="2.22514033"
+        android:strokeColor="#ffffff" />
+    <path
+        android:fillColor="#8a8a8a"
+        android:pathData="M17.313 14.401a1.405 1.405 0 1 1 2.81 0 1.405 1.405 0 1 1-2.81 0"
+        android:strokeWidth="3.82923341" />
+    <path
+        android:fillColor="#ffffff"
+        android:pathData="M17.69 14.403a1.03 1.03 0 1 1 2.06 0 1.03 1.03 0 1 1-2.06 0"
+        android:strokeWidth="2.80542302" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_device_miband6.xml b/app/src/main/res/drawable/ic_device_miband6.xml
new file mode 100644
index 0000000..82e2148
--- /dev/null
+++ b/app/src/main/res/drawable/ic_device_miband6.xml
@@ -0,0 +1,27 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="45sp"
+    android:height="45sp"
+    android:viewportWidth="30"
+    android:viewportHeight="30">
+    <path
+        android:fillColor="?attr/deviceIconLight"
+        android:pathData="M3.871 3.877h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.824a0.947 0.947 0 0 1 0.947-0.947z"
+        android:strokeWidth="3.57115" />
+    <path
+        android:fillColor="?attr/deviceIconDark"
+        android:pathData="M3.879 3.035h20.925a0.947 0.947 0 0 1 0.947 0.947v20.01a0.947 0.947 0 0 1-0.947 0.948H3.88a0.947 0.947 0 0 1-0.947-0.948V3.982A0.947 0.947 0 0 1 3.88 3.035z"
+        android:strokeWidth="3.57115" />
+    <path
+        android:fillColor="?attr/deviceIconPrimary"
+        android:pathData="M3.871 3.413h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.36A0.947 0.947 0 0 1 3.87 3.413z"
+        android:strokeWidth="3.57115" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M5.762 14.02l0.023-0.226c0.052-0.503 0.266-1.351 0.391-1.55 0.025-0.038 0.077-0.09 0.116-0.115 0.138-0.089 0.468-0.16 1.016-0.223 0.155-0.018 0.304-0.04 0.331-0.05 0.07-0.028 0.27-0.068 0.33-0.068 0.03 0 0.153-0.05 0.29-0.117 0.465-0.23 0.971-0.357 1.597-0.403 0.32-0.024 8.726-0.024 9.05 0 0.652 0.047 1.177 0.187 1.687 0.45l0.244 0.125 0.31 0.03c0.597 0.06 1.044 0.138 1.217 0.215 0.098 0.043 0.19 0.128 0.235 0.219 0.086 0.17 0.226 0.687 0.304 1.121 0.153 0.855 0.104 1.63-0.163 2.59-0.084 0.303-0.14 0.436-0.219 0.52-0.138 0.147-0.523 0.238-1.349 0.32l-0.335 0.032-0.277 0.14c-0.497 0.252-0.966 0.378-1.612 0.436-0.35 0.032-8.734 0.032-9.118 0-0.66-0.053-1.201-0.2-1.68-0.453C8.052 16.96 8 16.945 7.906 16.936c-0.066-0.006-0.168-0.025-0.227-0.043-0.059-0.017-0.265-0.051-0.46-0.075-0.767-0.095-0.967-0.168-1.08-0.392-0.052-0.103-0.187-0.563-0.247-0.844L5.851 15.39c-0.075-0.336-0.121-1.042-0.09-1.368z"
+        android:strokeWidth="3.77953" />
+    <path
+        android:fillColor="?attr/deviceIconDark"
+        android:pathData="M9.45 13.162h9.84c0.29 0 0.568 0.115 0.774 0.32 0.205 0.206 0.32 0.484 0.32 0.774v0.228c0 0.29-0.115 0.568-0.32 0.774-0.206 0.205-0.484 0.32-0.774 0.32H9.451c-0.192 0-0.38-0.05-0.547-0.146-0.167-0.096-0.305-0.234-0.4-0.4-0.097-0.167-0.148-0.356-0.148-0.548v-0.228c0-0.29 0.115-0.568 0.32-0.774 0.206-0.205 0.484-0.32 0.774-0.32z"
+        android:strokeWidth="2.22514"
+        android:strokeColor="?attr/deviceIconDark" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_device_miband6_disabled.xml b/app/src/main/res/drawable/ic_device_miband6_disabled.xml
new file mode 100644
index 0000000..81fc75e
--- /dev/null
+++ b/app/src/main/res/drawable/ic_device_miband6_disabled.xml
@@ -0,0 +1,32 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="45sp"
+    android:height="45sp"
+    android:viewportWidth="30"
+    android:viewportHeight="30">
+    <path
+        android:fillColor="#7a7a7a"
+        android:pathData="M3.871 3.877h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.824a0.947 0.947 0 0 1 0.947-0.947z"
+        android:strokeWidth="3.57115173" />
+    <path
+        android:fillAlpha="0.9411765"
+        android:fillColor="#9f9f9f"
+        android:pathData="M3.879 3.035h20.925a0.947 0.947 0 0 1 0.947 0.947v20.01a0.947 0.947 0 0 1-0.947 0.948H3.88a0.947 0.947 0 0 1-0.947-0.948V3.982A0.947 0.947 0 0 1 3.88 3.035z"
+        android:strokeWidth="3.57115173" />
+    <path
+        android:fillColor="#8a8a8a"
+        android:pathData="M3.871 3.413h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.36A0.947 0.947 0 0 1 3.87 3.413z"
+        android:strokeWidth="3.57115173" />
+    <path
+        android:fillAlpha="0.9411765"
+        android:fillColor="#8a8a8a"
+        android:pathData="M12.483 10.865h3.907v6.978h-3.907z" />
+    <path
+        android:fillColor="#ffffff"
+        android:pathData="M5.762 14.02l0.023-0.226c0.052-0.503 0.266-1.351 0.391-1.55 0.025-0.038 0.077-0.09 0.116-0.115 0.138-0.089 0.468-0.16 1.016-0.223 0.155-0.018 0.304-0.04 0.331-0.05 0.07-0.028 0.27-0.068 0.33-0.068 0.03 0 0.153-0.05 0.29-0.117 0.465-0.23 0.971-0.357 1.597-0.403 0.32-0.024 8.726-0.024 9.05 0 0.652 0.047 1.177 0.187 1.687 0.45l0.244 0.125 0.31 0.03c0.597 0.06 1.044 0.138 1.217 0.215 0.098 0.043 0.19 0.128 0.235 0.219 0.086 0.17 0.226 0.687 0.304 1.121 0.153 0.855 0.104 1.63-0.163 2.59-0.084 0.303-0.14 0.436-0.219 0.52-0.138 0.147-0.523 0.238-1.349 0.32l-0.335 0.032-0.277 0.14c-0.497 0.252-0.966 0.378-1.612 0.436-0.35 0.032-8.734 0.032-9.118 0-0.66-0.053-1.201-0.2-1.68-0.453C8.052 16.96 8 16.945 7.906 16.936c-0.066-0.006-0.168-0.025-0.227-0.043-0.059-0.017-0.266-0.051-0.46-0.075-0.767-0.095-0.967-0.168-1.08-0.392-0.052-0.103-0.187-0.563-0.247-0.844L5.851 15.39c-0.075-0.336-0.121-1.042-0.09-1.368z"
+        android:strokeWidth="3.77953" />
+    <path
+        android:fillColor="#9f9f9f"
+        android:pathData="M9.45 13.162h9.84c0.29 0 0.568 0.115 0.774 0.32 0.205 0.206 0.32 0.484 0.32 0.774v0.228c0 0.29-0.115 0.568-0.32 0.774-0.206 0.205-0.484 0.32-0.774 0.32H9.451c-0.192 0-0.38-0.05-0.547-0.146-0.167-0.096-0.305-0.234-0.4-0.4-0.097-0.167-0.148-0.356-0.148-0.548v-0.228c0-0.29 0.115-0.568 0.32-0.774 0.206-0.205 0.484-0.32 0.774-0.32z"
+        android:strokeWidth="2.22514"
+        android:strokeColor="#9f9f9f" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_device_miband_disabled.xml b/app/src/main/res/drawable/ic_device_miband_disabled.xml
new file mode 100644
index 0000000..e1e6a1d
--- /dev/null
+++ b/app/src/main/res/drawable/ic_device_miband_disabled.xml
@@ -0,0 +1,28 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="45sp"
+    android:height="45sp"
+    android:viewportWidth="30"
+    android:viewportHeight="30">
+    <path
+        android:fillColor="#7a7a7a"
+        android:pathData="M3.871 3.877h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.824a0.947 0.947 0 0 1 0.947-0.947z"
+        android:strokeWidth="3.57115173" />
+    <path
+        android:fillAlpha="0.9411765"
+        android:fillColor="#9f9f9f"
+        android:pathData="M3.879 3.035h20.925a0.947 0.947 0 0 1 0.947 0.947v20.01a0.947 0.947 0 0 1-0.947 0.948H3.88a0.947 0.947 0 0 1-0.947-0.948V3.982A0.947 0.947 0 0 1 3.88 3.035z"
+        android:strokeWidth="3.57115173" />
+    <path
+        android:fillColor="#8a8a8a"
+        android:pathData="M3.871 3.413h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.36A0.947 0.947 0 0 1 3.87 3.413z"
+        android:strokeWidth="3.57115173" />
+    <path
+        android:fillColor="#ffffff"
+        android:pathData="M9.388 13.204h9.841a1.094 1.094 0 0 1 1.094 1.094v0.228a1.094 1.094 0 0 1-1.094 1.094H9.39a1.094 1.094 0 0 1-1.095-1.094v-0.228a1.094 1.094 0 0 1 1.094-1.094z"
+        android:strokeWidth="2.22514033"
+        android:strokeColor="#ffffff" />
+    <path
+        android:fillColor="#8a8a8a"
+        android:pathData="M11.468 14.392a0.458 0.458 0 1 1 0.916 0 0.458 0.458 0 1 1-0.916 0m2.437 0.008a0.458 0.458 0 1 1 0.916 0 0.458 0.458 0 1 1-0.916 0m2.385-0.01a0.458 0.458 0 1 1 0.916 0 0.458 0.458 0 1 1-0.916 0"
+        android:strokeWidth="1.34653521" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_device_miscale.xml b/app/src/main/res/drawable/ic_device_miscale.xml
new file mode 100644
index 0000000..dc37ee2
--- /dev/null
+++ b/app/src/main/res/drawable/ic_device_miscale.xml
@@ -0,0 +1,41 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="45sp"
+    android:height="45sp"
+    android:viewportWidth="30"
+    android:viewportHeight="30">
+    <path
+        android:fillColor="#6cb8f7"
+        android:pathData="M3.612 2.619h19.7a0.854 0.854 0 0 1 0.854 0.854v18.94a0.854 0.854 0 0 1-0.855 0.855h-19.7a0.854 0.854 0 0 1-0.854-0.854V3.473a0.854 0.854 0 0 1 0.855-0.854z"
+        android:strokeWidth="0.99516809" />
+    <path
+        android:fillColor="#0c84e3"
+        android:pathData="M3.61 3.393h19.7a0.854 0.854 0 0 1 0.855 0.854v18.941a0.854 0.854 0 0 1-0.854 0.854H3.61a0.854 0.854 0 0 1-0.854-0.854V4.248A0.854 0.854 0 0 1 3.61 3.392z"
+        android:strokeWidth="0.99516809" />
+    <path
+        android:fillColor="?attr/deviceIconPrimary"
+        android:pathData="M3.61 3.024h19.7a0.854 0.854 0 0 1 0.854 0.854v18.94a0.854 0.854 0 0 1-0.854 0.854H3.61a0.854 0.854 0 0 1-0.854-0.854V3.878A0.854 0.854 0 0 1 3.61 3.024z"
+        android:strokeWidth="0.99516809" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M8.906 7.822H18a0.688 0.688 0 0 1 0.688 0.688v9.75A0.688 0.688 0 0 1 18 18.947H8.906A0.688 0.688 0 0 1 8.22 18.26V8.51a0.688 0.688 0 0 1 0.687-0.688z" />
+    <path
+        android:fillColor="?attr/deviceIconPrimary"
+        android:pathData="M10.896 10.323a3.66 3.265 92.127 0 1 2.63-1.47 3.66 3.265 92.127 0 1 2.606 1.476l-2.636 2.184z"
+        android:strokeWidth="0.93131608" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M11.688 11.472a2.982 2.265 90.871 0 1 1.824-1.197 2.982 2.265 90.871 0 1 1.807 1.201l-1.829 1.78z"
+        android:strokeWidth="0.70008069" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M12.29 9.79l0.254 0.365a0.09 0.09 100.007 0 1-0.022 0.125 0.09 0.09 100.007 0 1-0.124-0.022l-0.254-0.365a0.09 0.09 100.007 0 1 0.022-0.124 0.09 0.09 100.007 0 1 0.124 0.021z"
+        android:strokeWidth="0.8715741" />
+    <path
+        android:fillColor="?attr/deviceIconPrimary"
+        android:pathData="M16.11 10.453l-0.85 0.71a0.098 0.108 92.456 0 1-0.153-0.007 0.098 0.108 92.456 0 1 0.006-0.139l0.85-0.71a0.098 0.108 92.456 0 1 0.153 0.007 0.098 0.108 92.456 0 1-0.006 0.139z"
+        android:strokeWidth="0.43048036" />
+    <path
+        android:fillColor="?attr/deviceIconPrimary"
+        android:pathData="M11.028 10.31l0.858 0.692a0.107 0.097 81.04 0 1 0.024 0.15 0.107 0.097 81.04 0 1-0.136 0.022l-0.858-0.692a0.107 0.097 81.04 0 1-0.024-0.15 0.107 0.097 81.04 0 1 0.136-0.021z"
+        android:strokeWidth="0.42741153" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_device_miscale_disabled.xml b/app/src/main/res/drawable/ic_device_miscale_disabled.xml
new file mode 100644
index 0000000..5c56740
--- /dev/null
+++ b/app/src/main/res/drawable/ic_device_miscale_disabled.xml
@@ -0,0 +1,41 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="45sp"
+    android:height="45sp"
+    android:viewportWidth="30"
+    android:viewportHeight="30">
+    <path
+        android:fillColor="#8a8a8a"
+        android:pathData="M4.034 3.328h19.7a0.854 0.854 0 0 1 0.854 0.854v18.94a0.854 0.854 0 0 1-0.854 0.855h-19.7a0.854 0.854 0 0 1-0.854-0.854V4.182a0.854 0.854 0 0 1 0.854-0.854z"
+        android:strokeWidth="0.99516809" />
+    <path
+        android:fillColor="#7d7d7d"
+        android:pathData="M4.035 3.698h19.7a0.854 0.854 0 0 1 0.854 0.854v18.94a0.854 0.854 0 0 1-0.854 0.854h-19.7a0.854 0.854 0 0 1-0.854-0.854V4.552a0.854 0.854 0 0 1 0.854-0.854z"
+        android:strokeWidth="0.99516809" />
+    <path
+        android:fillColor="#a1a1a1"
+        android:pathData="M4.036 2.923h19.7a0.854 0.854 0 0 1 0.854 0.854v18.94a0.854 0.854 0 0 1-0.854 0.855h-19.7a0.854 0.854 0 0 1-0.854-0.854V3.778a0.854 0.854 0 0 1 0.854-0.855z"
+        android:strokeWidth="0.99516809" />
+    <path
+        android:fillColor="#ffffff"
+        android:pathData="M9.184 7.552h9.093a0.688 0.688 0 0 1 0.688 0.688v9.75a0.688 0.688 0 0 1-0.688 0.687H9.184a0.688 0.688 0 0 1-0.688-0.687V8.24a0.688 0.688 0 0 1 0.688-0.688z" />
+    <path
+        android:fillColor="#a1a1a1"
+        android:pathData="M11.173 10.053a3.66 3.265 92.127 0 1 2.63-1.47 3.66 3.265 92.127 0 1 2.607 1.475l-2.637 2.184z"
+        android:strokeWidth="0.93131608" />
+    <path
+        android:fillColor="#ffffff"
+        android:pathData="M11.965 11.201a2.982 2.265 90.871 0 1 1.824-1.197 2.982 2.265 90.871 0 1 1.807 1.202l-1.828 1.78z"
+        android:strokeWidth="0.70008069" />
+    <path
+        android:fillColor="#ffffff"
+        android:pathData="M12.568 9.52l0.254 0.365a0.09 0.09 100.007 0 1-0.022 0.124 0.09 0.09 100.007 0 1-0.124-0.021l-0.254-0.365a0.09 0.09 100.007 0 1 0.022-0.125 0.09 0.09 100.007 0 1 0.124 0.022z"
+        android:strokeWidth="0.8715741" />
+    <path
+        android:fillColor="#a1a1a1"
+        android:pathData="M16.387 10.182l-0.85 0.71a0.098 0.108 92.456 0 1-0.153-0.006 0.098 0.108 92.456 0 1 0.006-0.14l0.85-0.71a0.098 0.108 92.456 0 1 0.153 0.008 0.098 0.108 92.456 0 1-0.006 0.138z"
+        android:strokeWidth="0.43048036" />
+    <path
+        android:fillColor="#a1a1a1"
+        android:pathData="M11.305 10.04l0.858 0.692a0.107 0.097 81.04 0 1 0.024 0.15 0.107 0.097 81.04 0 1-0.136 0.021l-0.858-0.691a0.107 0.097 81.04 0 1-0.024-0.15 0.107 0.097 81.04 0 1 0.136-0.021z"
+        android:strokeWidth="0.42741153" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_device_miwatch.xml b/app/src/main/res/drawable/ic_device_miwatch.xml
new file mode 100644
index 0000000..29452d1
--- /dev/null
+++ b/app/src/main/res/drawable/ic_device_miwatch.xml
@@ -0,0 +1,52 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="45sp"
+    android:height="45sp"
+    android:viewportWidth="30"
+    android:viewportHeight="30">
+    <path
+        android:fillColor="?attr/deviceIconLight"
+        android:pathData="M3.871 3.877h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.824a0.947 0.947 0 0 1 0.947-0.947z"
+        android:strokeWidth="3.57115173" />
+    <path
+        android:fillColor="?attr/deviceIconDark"
+        android:pathData="M3.879 3.035h20.925a0.947 0.947 0 0 1 0.947 0.947v20.01a0.947 0.947 0 0 1-0.947 0.948H3.88a0.947 0.947 0 0 1-0.947-0.948V3.982A0.947 0.947 0 0 1 3.88 3.035z"
+        android:strokeWidth="3.57115173" />
+    <path
+        android:fillColor="?attr/deviceIconPrimary"
+        android:pathData="M3.871 3.413h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.36A0.947 0.947 0 0 1 3.87 3.413z"
+        android:strokeWidth="3.57115173" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M17.39 6.396h0.637a0.499 0.499 0 0 1 0.499 0.5v14.377a0.499 0.499 0 0 1-0.499 0.498H17.39a0.499 0.499 0 0 1-0.499-0.498V6.895a0.499 0.499 0 0 1 0.499-0.499z"
+        android:strokeWidth="3.87336898" />
+    <group
+        android:translateX="20"
+        android:translateY="16"
+        android:rotation="30">
+        <path
+            android:fillColor="?attr/deviceIconOnPrimary"
+            android:pathData="M0 0h0.36a0.499 0.499 0 0 1 0.5 0.5v1.465a0.499 0.499 0 0 1-0.5 0.5h-0.36z"
+            android:strokeWidth="3.5" />
+    </group>
+    <group
+        android:translateX="18.75"
+        android:translateY="10"
+        android:rotation="-30">
+        <path
+            android:fillColor="?attr/deviceIconOnPrimary"
+            android:pathData="M0 0h0.36a0.499 0.499 0 0 1 0.5 0.5v1.465a0.499 0.499 0 0 1-0.5 0.5h-0.36z"
+            android:strokeWidth="3.5" />
+    </group>
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M10.667 6.396h0.638a0.499 0.499 0 0 1 0.498 0.5v14.377a0.499 0.499 0 0 1-0.498 0.498h-0.638a0.499 0.499 0 0 1-0.498-0.498V6.895a0.499 0.499 0 0 1 0.498-0.499z"
+        android:strokeWidth="3.87336898" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M8.456 14.053a5.923 6.099 0 1 0 11.846 0 5.923 6.099 0 1 0-11.846 0z"
+        android:strokeWidth="9.28846169" />
+    <path
+        android:fillColor="?attr/deviceIconPrimary"
+        android:pathData="M9.95 14.014a4.464 4.597 0 1 0 8.929 0 4.464 4.597 0 1 0-8.928 0z"
+        android:strokeWidth="7.00060892" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_device_miwatch_disabled.xml b/app/src/main/res/drawable/ic_device_miwatch_disabled.xml
new file mode 100644
index 0000000..667483f
--- /dev/null
+++ b/app/src/main/res/drawable/ic_device_miwatch_disabled.xml
@@ -0,0 +1,53 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="45sp"
+    android:height="45sp"
+    android:viewportWidth="30"
+    android:viewportHeight="30">
+    <path
+        android:fillColor="#7a7a7a"
+        android:pathData="M3.871 3.877h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.824a0.947 0.947 0 0 1 0.947-0.947z"
+        android:strokeWidth="3.57115173" />
+    <path
+        android:fillAlpha="0.9411765"
+        android:fillColor="#9f9f9f"
+        android:pathData="M3.879 3.035h20.925a0.947 0.947 0 0 1 0.947 0.947v20.01a0.947 0.947 0 0 1-0.947 0.948H3.88a0.947 0.947 0 0 1-0.947-0.948V3.982A0.947 0.947 0 0 1 3.88 3.035z"
+        android:strokeWidth="3.57115173" />
+    <path
+        android:fillColor="#8a8a8a"
+        android:pathData="M3.871 3.413h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.36A0.947 0.947 0 0 1 3.87 3.413z"
+        android:strokeWidth="3.57115173" />
+    <path
+        android:fillColor="#ffffff"
+        android:pathData="M17.39 6.396h0.637a0.499 0.499 0 0 1 0.499 0.5v14.377a0.499 0.499 0 0 1-0.499 0.498H17.39a0.499 0.499 0 0 1-0.499-0.498V6.895a0.499 0.499 0 0 1 0.499-0.499z"
+        android:strokeWidth="3.87336898" />
+    <group
+        android:translateX="20"
+        android:translateY="16"
+        android:rotation="30">
+        <path
+            android:fillColor="#ffffff"
+            android:pathData="M0 0h0.36a0.499 0.499 0 0 1 0.5 0.5v1.465a0.499 0.499 0 0 1-0.5 0.5h-0.36z"
+            android:strokeWidth="3.5" />
+    </group>
+    <group
+        android:translateX="18.75"
+        android:translateY="10"
+        android:rotation="-30">
+        <path
+            android:fillColor="#ffffff"
+            android:pathData="M0 0h0.36a0.499 0.499 0 0 1 0.5 0.5v1.465a0.499 0.499 0 0 1-0.5 0.5h-0.36z"
+            android:strokeWidth="3.5" />
+    </group>
+    <path
+        android:fillColor="#ffffff"
+        android:pathData="M10.667 6.396h0.638a0.499 0.499 0 0 1 0.498 0.5v14.377a0.499 0.499 0 0 1-0.498 0.498h-0.638a0.499 0.499 0 0 1-0.498-0.498V6.895a0.499 0.499 0 0 1 0.498-0.499z"
+        android:strokeWidth="3.87336898" />
+    <path
+        android:fillColor="#ffffff"
+        android:pathData="M8.456 14.053a5.923 6.099 0 1 0 11.846 0 5.923 6.099 0 1 0-11.846 0z"
+        android:strokeWidth="9.28846169" />
+    <path
+        android:fillColor="#8a8a8a"
+        android:pathData="M9.95 14.014a4.464 4.597 0 1 0 8.929 0 4.464 4.597 0 1 0-8.928 0z"
+        android:strokeWidth="7.00060892" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_device_nothingear.xml b/app/src/main/res/drawable/ic_device_nothingear.xml
new file mode 100644
index 0000000..2ae97ef
--- /dev/null
+++ b/app/src/main/res/drawable/ic_device_nothingear.xml
@@ -0,0 +1,34 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="45sp"
+    android:height="45sp"
+    android:viewportWidth="30"
+    android:viewportHeight="30">
+    <path
+        android:fillColor="?attr/deviceIconLight"
+        android:pathData="M3.871 3.877h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.824a0.947 0.947 0 0 1 0.947-0.947z"
+        android:strokeWidth="3.5" />
+    <path
+        android:fillColor="?attr/deviceIconDark"
+        android:pathData="M3.879 3.035h20.925a0.947 0.947 0 0 1 0.947 0.947v20.01a0.947 0.947 0 0 1-0.947 0.948H3.88a0.947 0.947 0 0 1-0.947-0.948V3.982A0.947 0.947 0 0 1 3.88 3.035z"
+        android:strokeWidth="3.5" />
+    <path
+        android:fillColor="?attr/deviceIconPrimary"
+        android:pathData="M3.871 3.413h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.36A0.947 0.947 0 0 1 3.87 3.413z"
+        android:strokeWidth="3.5" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="m11.248,18.9974a2.6708,2.6529 90,0 1,-2.6529 -2.6708l0,-3.6234a5.5464,5.5093 89.9997,0 1,-3.3073 -1.6559,0.8903 0.8843,89.9998 0,1 -0.2299,-0.6054l0,-3.3652a0.8903,0.8843 89.9998,0 1,0.2299 -0.6054,5.6977 5.6596,90.0002 0,1 4.1916,-1.7182l0.1945,0a4.4513,4.4216 89.9998,0 1,4.227 4.4513l0,7.1222a2.6708,2.6529 90,0 1,-2.6529 2.6708zM6.8264,10.0947a4.113,4.0855 90.0003,0 0,2.6529 0.8903,0.8903 0.8843,89.9998 0,1 0.8843,0.8903l0,4.4513a0.8903,0.8843 90,0 0,1.7686 0L12.1323,9.2044a2.6708,2.6529 90,0 0,-2.5114 -2.6708l-0.1415,0a4.113,4.0855 90.0003,0 0,-2.6529 0.8903z"
+        android:strokeWidth="0.8872858" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M18.3225,24.339A2.6708,2.6529 90,0 1,15.6695 21.6682L15.6695,14.546a4.4513,4.4216 89.9998,0 1,4.227 -4.4513l0.1945,0a5.6977,5.6596 90.0002,0 1,4.1916 1.7182,0.8903 0.8843,89.9998 0,1 0.2299,0.6054l0,3.3652a0.8903,0.8843 89.9998,0 1,-0.2299 0.6054,5.5464 5.5093,89.9997 0,1 -3.3073,1.6559l0,3.6234a2.6708,2.6529 90,0 1,-2.6529 2.6708zM20.0911,11.8752l-0.1415,0a2.6708,2.6529 90,0 0,-2.5114 2.6708l0,7.1222a0.8903,0.8843 90.0003,0 0,1.7686 0l0,-4.4513a0.8903,0.8843 89.9998,0 1,0.8843 -0.8903,4.113 4.0855,90.0003 0,0 2.6529,-0.8903L22.744,12.7655a4.113,4.0855 90.0003,0 0,-2.6529 -0.8903z"
+        android:strokeWidth="0.8872858" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M8.595,8.7593m-0.8843,0a0.8903,0.8843 90.0003,1 1,1.7686 0a0.8903,0.8843 90.0003,1 1,-1.7686 0"
+        android:strokeWidth="0.8872858" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M20.9754,14.1009m-0.8843,0a0.8903,0.8843 90.0003,1 1,1.7686 0a0.8903,0.8843 90.0003,1 1,-1.7686 0"
+        android:strokeWidth="0.8872858" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_device_nothingear_disabled.xml b/app/src/main/res/drawable/ic_device_nothingear_disabled.xml
new file mode 100644
index 0000000..799842b
--- /dev/null
+++ b/app/src/main/res/drawable/ic_device_nothingear_disabled.xml
@@ -0,0 +1,34 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="45sp"
+    android:height="45sp"
+    android:viewportWidth="30"
+    android:viewportHeight="30">
+    <path
+        android:fillColor="#7a7a7a"
+        android:pathData="M3.871 3.877h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.824a0.947 0.947 0 0 1 0.947-0.947z"
+        android:strokeWidth="3.57" />
+    <path
+        android:fillColor="#9f9f9f"
+        android:pathData="M3.879 3.035h20.925a0.947 0.947 0 0 1 0.947 0.947v20.01a0.947 0.947 0 0 1-0.947 0.948H3.88a0.947 0.947 0 0 1-0.947-0.948V3.982A0.947 0.947 0 0 1 3.88 3.035z"
+        android:strokeWidth="3.57" />
+    <path
+        android:fillColor="#8a8a8a"
+        android:pathData="M3.871 3.413h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.36A0.947 0.947 0 0 1 3.87 3.413z"
+        android:strokeWidth="3.57" />
+    <path
+        android:fillColor="#ffffff"
+        android:pathData="m11.248,18.9974a2.6708,2.6529 90,0 1,-2.6529 -2.6708l0,-3.6234a5.5464,5.5093 89.9997,0 1,-3.3073 -1.6559,0.8903 0.8843,89.9998 0,1 -0.2299,-0.6054l0,-3.3652a0.8903,0.8843 89.9998,0 1,0.2299 -0.6054,5.6977 5.6596,90.0002 0,1 4.1916,-1.7182l0.1945,0a4.4513,4.4216 89.9998,0 1,4.227 4.4513l0,7.1222a2.6708,2.6529 90,0 1,-2.6529 2.6708zM6.8264,10.0947a4.113,4.0855 90.0003,0 0,2.6529 0.8903,0.8903 0.8843,89.9998 0,1 0.8843,0.8903l0,4.4513a0.8903,0.8843 90,0 0,1.7686 0L12.1323,9.2044a2.6708,2.6529 90,0 0,-2.5114 -2.6708l-0.1415,0a4.113,4.0855 90.0003,0 0,-2.6529 0.8903z"
+        android:strokeWidth="0.8872858" />
+    <path
+        android:fillColor="#ffffff"
+        android:pathData="M18.3225,24.339A2.6708,2.6529 90,0 1,15.6695 21.6682L15.6695,14.546a4.4513,4.4216 89.9998,0 1,4.227 -4.4513l0.1945,0a5.6977,5.6596 90.0002,0 1,4.1916 1.7182,0.8903 0.8843,89.9998 0,1 0.2299,0.6054l0,3.3652a0.8903,0.8843 89.9998,0 1,-0.2299 0.6054,5.5464 5.5093,89.9997 0,1 -3.3073,1.6559l0,3.6234a2.6708,2.6529 90,0 1,-2.6529 2.6708zM20.0911,11.8752l-0.1415,0a2.6708,2.6529 90,0 0,-2.5114 2.6708l0,7.1222a0.8903,0.8843 90.0003,0 0,1.7686 0l0,-4.4513a0.8903,0.8843 89.9998,0 1,0.8843 -0.8903,4.113 4.0855,90.0003 0,0 2.6529,-0.8903L22.744,12.7655a4.113,4.0855 90.0003,0 0,-2.6529 -0.8903z"
+        android:strokeWidth="0.8872858" />
+    <path
+        android:fillColor="#ffffff"
+        android:pathData="M8.595,8.7593m-0.8843,0a0.8903,0.8843 90.0003,1 1,1.7686 0a0.8903,0.8843 90.0003,1 1,-1.7686 0"
+        android:strokeWidth="0.8872858" />
+    <path
+        android:fillColor="#ffffff"
+        android:pathData="M20.9754,14.1009m-0.8843,0a0.8903,0.8843 90.0003,1 1,1.7686 0a0.8903,0.8843 90.0003,1 1,-1.7686 0"
+        android:strokeWidth="0.8872858" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_device_pebble.xml b/app/src/main/res/drawable/ic_device_pebble.xml
new file mode 100644
index 0000000..2949fd5
--- /dev/null
+++ b/app/src/main/res/drawable/ic_device_pebble.xml
@@ -0,0 +1,42 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="45sp"
+    android:height="45sp"
+    android:viewportWidth="30"
+    android:viewportHeight="30">
+    <path
+        android:fillColor="?attr/deviceIconLight"
+        android:pathData="M3.871 3.877h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.824a0.947 0.947 0 0 1 0.947-0.947z"
+        android:strokeWidth="3.4" />
+    <path
+        android:fillColor="?attr/deviceIconDark"
+        android:pathData="M3.879 3.035h20.925a0.947 0.947 0 0 1 0.947 0.947v20.01a0.947 0.947 0 0 1-0.947 0.948H3.88a0.947 0.947 0 0 1-0.947-0.948V3.982A0.947 0.947 0 0 1 3.88 3.035z"
+        android:strokeWidth="3.4" />
+    <path
+        android:fillColor="?attr/deviceIconPrimary"
+        android:pathData="M3.871 3.413h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.36A0.947 0.947 0 0 1 3.87 3.413z"
+        android:strokeWidth="3.4" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M8.656 8.627h11.408v11.532H8.656z"
+        android:strokeWidth="3.4" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M8.656 7.178h1.585v14.437H8.656zm9.782 0h1.626v14.468h-1.627z"
+        android:strokeWidth="3.4" />
+    <path
+        android:fillColor="?attr/deviceIconPrimary"
+        android:pathData="M10.231 10.178h8.207v8.5H10.23z"
+        android:strokeWidth="3.4" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M11.316 11.347h6v6.125h-6z"
+        android:strokeWidth="3.4" />
+    <path
+        android:fillColor="?attr/deviceIconPrimary"
+        android:pathData="M13.954 12.481h0.781v2.281h-0.78z"
+        android:strokeWidth="3.4" />
+    <path
+        android:fillColor="?attr/deviceIconPrimary"
+        android:pathData="M13.954 14.043h2.188v0.72h-2.188z"
+        android:strokeWidth="3.4" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_device_pebble_disabled.xml b/app/src/main/res/drawable/ic_device_pebble_disabled.xml
new file mode 100644
index 0000000..c7bad54
--- /dev/null
+++ b/app/src/main/res/drawable/ic_device_pebble_disabled.xml
@@ -0,0 +1,43 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="45sp"
+    android:height="45sp"
+    android:viewportWidth="30"
+    android:viewportHeight="30">
+    <path
+        android:fillColor="#7a7a7a"
+        android:pathData="M3.871 3.877h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.824a0.947 0.947 0 0 1 0.947-0.947z"
+        android:strokeWidth="3.4" />
+    <path
+        android:fillAlpha="0.9411765"
+        android:fillColor="#9f9f9f"
+        android:pathData="M3.879 3.035h20.925a0.947 0.947 0 0 1 0.947 0.947v20.01a0.947 0.947 0 0 1-0.947 0.948H3.88a0.947 0.947 0 0 1-0.947-0.948V3.982A0.947 0.947 0 0 1 3.88 3.035z"
+        android:strokeWidth="3.4" />
+    <path
+        android:fillColor="#8a8a8a"
+        android:pathData="M3.871 3.413h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.36A0.947 0.947 0 0 1 3.87 3.413z"
+        android:strokeWidth="3.4" />
+    <path
+        android:fillColor="#ffffff"
+        android:pathData="M8.723 8.517H20.13V20.05H8.723z"
+        android:strokeWidth="3.4" />
+    <path
+        android:fillColor="#ffffff"
+        android:pathData="M8.723 7.068h1.585v14.438H8.723zm9.781 0h1.627v14.47h-1.627z"
+        android:strokeWidth="3.4" />
+    <path
+        android:fillColor="#8a8a8a"
+        android:pathData="M10.297 10.068h8.207v8.5h-8.207z"
+        android:strokeWidth="3.4" />
+    <path
+        android:fillColor="#ffffff"
+        android:pathData="M11.383 11.237h6v6.125h-6z"
+        android:strokeWidth="3.4" />
+    <path
+        android:fillColor="#8a8a8a"
+        android:pathData="M14.02 12.372h0.782v2.28h-0.781z"
+        android:strokeWidth="3.4" />
+    <path
+        android:fillColor="#8a8a8a"
+        android:pathData="M14.02 13.934h2.188v0.719h-2.187z"
+        android:strokeWidth="3.4" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_device_pinetime.xml b/app/src/main/res/drawable/ic_device_pinetime.xml
new file mode 100644
index 0000000..8627862
--- /dev/null
+++ b/app/src/main/res/drawable/ic_device_pinetime.xml
@@ -0,0 +1,66 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="45sp"
+    android:height="45sp"
+    android:viewportWidth="30"
+    android:viewportHeight="30">
+    <path
+        android:fillColor="?attr/deviceIconLight"
+        android:pathData="M3.871 3.877h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.824a0.947 0.947 0 0 1 0.947-0.947z"
+        android:strokeWidth="3.6" />
+    <path
+        android:fillColor="?attr/deviceIconDark"
+        android:pathData="M3.879 3.035h20.925a0.947 0.947 0 0 1 0.947 0.947v20.01a0.947 0.947 0 0 1-0.947 0.948H3.88a0.947 0.947 0 0 1-0.947-0.948V3.982A0.947 0.947 0 0 1 3.88 3.035z"
+        android:strokeWidth="3.6" />
+    <path
+        android:fillColor="?attr/deviceIconPrimary"
+        android:pathData="M3.871 3.413h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.36A0.947 0.947 0 0 1 3.87 3.413z"
+        android:strokeWidth="3.6" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M12.27 6.708h4.54a0.425 0.425 0 0 1 0.426 0.425v15.04a0.425 0.425 0 0 1-0.425 0.424h-4.542a0.425 0.425 0 0 1-0.425-0.425V7.132a0.425 0.425 0 0 1 0.425-0.424z"
+        android:strokeWidth="2.4" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M10.915 9.692h7.294a1.104 1.104 0 0 1 1.104 1.105v7.293a1.104 1.104 0 0 1-1.104 1.104h-7.294a1.104 1.104 0 0 1-1.104-1.104v-7.293a1.104 1.104 0 0 1 1.104-1.105z"
+        android:strokeWidth="3.5" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M9.817 13.485h0.407a0.26 0.26 0 0 1 0.26 0.261v1.556a0.26 0.26 0 0 1-0.26 0.26H9.817a0.26 0.26 0 0 1-0.26-0.26v-1.556a0.26 0.26 0 0 1 0.26-0.26z"
+        android:strokeWidth="3.5" />
+    <path
+        android:fillColor="?attr/deviceIconPrimary"
+        android:pathData="M11.716 10.731h5.706a0.866 0.838 90 0 1 0.838 0.866v5.008a0.866 0.838 90 0 1-0.838 0.866h-5.707a0.866 0.838 90 0 1-0.837-0.866v-5.008a0.866 0.838 90 0 1 0.838-0.866z"
+        android:strokeWidth="5.1" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M14.689 15.174l1.185 0.431a0.097 0.267 90 0 1 0 0.137l-1.185 0.432a0.097 0.267 90 0 1-0.378 0l-1.185-0.431a0.097 0.267 90 0 1 0-0.138l1.185-0.431a0.097 0.267 90 0 1 0.378 0z"
+        android:strokeWidth="3.1" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M14.681 13.926l1.037 0.421a0.104 0.256 90 0 1 0 0.148l-1.037 0.421a0.104 0.256 90 0 1-0.362 0l-1.037-0.421a0.104 0.256 90 0 1 0-0.148l1.037-0.421a0.104 0.256 90 0 1 0.362 0z"
+        android:strokeWidth="2.8" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M14.649 12.7l0.96 0.434a0.095 0.21 90 0 1 0 0.134l-0.96 0.435a0.095 0.21 90 0 1-0.298 0l-0.96-0.434a0.095 0.21 90 0 1 0-0.135l0.96-0.435a0.095 0.21 90 0 1 0.298 0z"
+        android:strokeWidth="2.6" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M14.68 11.556l0.646 0.351a0.138 0.254 90 0 1 0 0.195l-0.647 0.351a0.138 0.254 90 0 1-0.358 0l-0.646-0.35a0.138 0.254 90 0 1 0-0.196l0.646-0.35a0.138 0.254 90 0 1 0.358 0z"
+        android:strokeWidth="2" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M14.395 16.023h0.21a0.094 0.094 0 0 1 0.094 0.094v0.376a0.094 0.094 0 0 1-0.094 0.094h-0.21a0.094 0.094 0 0 1-0.094-0.094v-0.376a0.094 0.094 0 0 1 0.094-0.094z"
+        android:strokeWidth="3.5" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M13.63 13.437c0.881 0.416 0.87 0.35 0.119 0.658-0.75 0.31-0.67 0.347-0.801-0.378-0.131-0.724-0.2-0.696 0.682-0.28zm-0.08 1.239c0.983 0.416 0.97 0.35 0.132 0.658-0.837 0.31-0.747 0.347-0.893-0.377-0.146-0.725-0.222-0.697 0.76-0.281z"
+        android:strokeWidth="1" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M15.448 14.679c-0.983 0.416-0.97 0.35-0.132 0.658 0.837 0.31 0.747 0.347 0.893-0.377 0.146-0.725 0.222-0.697-0.76-0.281z"
+        android:strokeWidth="1" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M15.397 13.438c-0.881 0.415-0.87 0.35-0.119 0.658 0.75 0.309 0.67 0.347 0.801-0.378 0.13-0.724 0.199-0.696-0.682-0.28zm-0.122-1.235c-0.757 0.43-0.748 0.365-0.09 0.66 0.657 0.297 0.589 0.336 0.688-0.39 0.1-0.727 0.16-0.7-0.598-0.27zM13.723 12.2c0.758 0.431 0.749 0.365 0.09 0.661-0.657 0.296-0.588 0.335-0.688-0.391-0.1-0.727-0.159-0.7 0.598-0.27z"
+        android:strokeWidth="1" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_device_pinetime_disabled.xml b/app/src/main/res/drawable/ic_device_pinetime_disabled.xml
new file mode 100644
index 0000000..3bd626f
--- /dev/null
+++ b/app/src/main/res/drawable/ic_device_pinetime_disabled.xml
@@ -0,0 +1,67 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="45sp"
+    android:height="45sp"
+    android:viewportWidth="30"
+    android:viewportHeight="30">
+    <path
+        android:fillColor="#7a7a7a"
+        android:pathData="M3.871 3.877h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.824a0.947 0.947 0 0 1 0.947-0.947z"
+        android:strokeWidth="3.57115173" />
+    <path
+        android:fillAlpha="0.9411765"
+        android:fillColor="#9f9f9f"
+        android:pathData="M3.879 3.035h20.925a0.947 0.947 0 0 1 0.947 0.947v20.01a0.947 0.947 0 0 1-0.947 0.948H3.88a0.947 0.947 0 0 1-0.947-0.948V3.982A0.947 0.947 0 0 1 3.88 3.035z"
+        android:strokeWidth="3.57115173" />
+    <path
+        android:fillColor="#8a8a8a"
+        android:pathData="M3.871 3.413h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.36A0.947 0.947 0 0 1 3.87 3.413z"
+        android:strokeWidth="3.57115173" />
+    <path
+        android:fillColor="#ffffff"
+        android:pathData="M12.278 6.399h4.542a0.425 0.425 0 0 1 0.425 0.425v15.04a0.425 0.425 0 0 1-0.425 0.424h-4.542a0.425 0.425 0 0 1-0.425-0.425V6.824a0.425 0.425 0 0 1 0.425-0.425z"
+        android:strokeWidth="2.43489814" />
+    <path
+        android:fillColor="#ffffff"
+        android:pathData="M10.925 9.383h7.293a1.104 1.104 0 0 1 1.104 1.105v7.293a1.104 1.104 0 0 1-1.104 1.104h-7.293a1.104 1.104 0 0 1-1.105-1.104v-7.293a1.104 1.104 0 0 1 1.105-1.104z"
+        android:strokeWidth="3.5" />
+    <path
+        android:fillColor="#ffffff"
+        android:pathData="M9.827 13.176h0.406a0.26 0.26 0 0 1 0.26 0.261v1.556a0.26 0.26 0 0 1-0.26 0.26H9.827a0.26 0.26 0 0 1-0.261-0.26v-1.556a0.26 0.26 0 0 1 0.26-0.26z"
+        android:strokeWidth="3.5" />
+    <path
+        android:fillColor="#8a8a8a"
+        android:pathData="M11.725 10.422h5.706a0.866 0.838 90 0 1 0.838 0.866v5.008a0.866 0.838 90 0 1-0.838 0.866h-5.706a0.866 0.838 90 0 1-0.838-0.866v-5.008a0.866 0.838 90 0 1 0.838-0.866z"
+        android:strokeWidth="5.10916853" />
+    <path
+        android:fillColor="#ffffff"
+        android:pathData="M14.767 14.932l1.185 0.432a0.097 0.267 90 0 1 0 0.137l-1.185 0.432a0.097 0.267 90 0 1-0.378 0l-1.185-0.431a0.097 0.267 90 0 1 0-0.138l1.185-0.432a0.097 0.267 90 0 1 0.378 0z"
+        android:strokeWidth="3.12518311" />
+    <path
+        android:fillColor="#ffffff"
+        android:pathData="M14.759 13.685l1.037 0.421a0.104 0.256 90 0 1 0 0.148l-1.037 0.421a0.104 0.256 90 0 1-0.362 0l-1.037-0.421a0.104 0.256 90 0 1 0-0.148l1.037-0.421a0.104 0.256 90 0 1 0.362 0z"
+        android:strokeWidth="2.83754849" />
+    <path
+        android:fillColor="#ffffff"
+        android:pathData="M14.727 12.458l0.96 0.435a0.095 0.21 90 0 1 0 0.134l-0.96 0.435a0.095 0.21 90 0 1-0.298 0l-0.96-0.434a0.095 0.21 90 0 1 0-0.135l0.96-0.435a0.095 0.21 90 0 1 0.298 0z"
+        android:strokeWidth="2.59477019" />
+    <path
+        android:fillColor="#ffffff"
+        android:pathData="M14.757 11.315l0.646 0.351a0.138 0.254 90 0 1 0 0.195l-0.646 0.351a0.138 0.254 90 0 1-0.358 0l-0.647-0.35a0.138 0.254 90 0 1 0-0.196l0.646-0.35a0.138 0.254 90 0 1 0.36 0z"
+        android:strokeWidth="2.1484313" />
+    <path
+        android:fillColor="#ffffff"
+        android:pathData="M14.472 15.782h0.211a0.094 0.094 0 0 1 0.094 0.094v0.376a0.094 0.094 0 0 1-0.094 0.094h-0.21a0.094 0.094 0 0 1-0.094-0.094v-0.376a0.094 0.094 0 0 1 0.093-0.094z"
+        android:strokeWidth="3.5" />
+    <path
+        android:fillColor="#ffffff"
+        android:pathData="M13.708 13.196c0.88 0.416 0.869 0.35 0.119 0.658-0.75 0.31-0.67 0.347-0.801-0.377-0.131-0.725-0.2-0.697 0.682-0.281zm-0.08 1.239c0.983 0.415 0.97 0.35 0.132 0.658-0.837 0.31-0.747 0.347-0.893-0.378-0.146-0.724-0.222-0.696 0.76-0.28z"
+        android:strokeWidth="1.08117461" />
+    <path
+        android:fillColor="#ffffff"
+        android:pathData="M15.526 14.438c-0.983 0.415-0.97 0.35-0.132 0.658 0.837 0.31 0.747 0.347 0.893-0.378 0.146-0.724 0.222-0.696-0.76-0.28z"
+        android:strokeWidth="1.08117461" />
+    <path
+        android:fillColor="#ffffff"
+        android:pathData="M15.475 13.196c-0.881 0.416-0.87 0.35-0.12 0.659 0.751 0.309 0.671 0.347 0.802-0.378 0.13-0.724 0.199-0.696-0.682-0.28zm-0.122-1.234c-0.757 0.43-0.748 0.365-0.09 0.66 0.657 0.297 0.588 0.336 0.688-0.39 0.1-0.727 0.16-0.7-0.598-0.27zm-1.552-0.002c0.757 0.43 0.748 0.364 0.09 0.66-0.657 0.296-0.588 0.335-0.688-0.391-0.1-0.727-0.159-0.7 0.598-0.27z"
+        android:strokeWidth="1.08117461" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_device_roidmi.xml b/app/src/main/res/drawable/ic_device_roidmi.xml
new file mode 100644
index 0000000..ff47f62
--- /dev/null
+++ b/app/src/main/res/drawable/ic_device_roidmi.xml
@@ -0,0 +1,38 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="45sp"
+    android:height="45sp"
+    android:viewportWidth="30"
+    android:viewportHeight="30">
+    <path
+        android:fillColor="?attr/deviceIconLight"
+        android:pathData="M3.871 3.877h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.824a0.947 0.947 0 0 1 0.947-0.947z"
+        android:strokeWidth="3.57115173" />
+    <path
+        android:fillColor="?attr/deviceIconDark"
+        android:pathData="M3.879 3.035h20.925a0.947 0.947 0 0 1 0.947 0.947v20.01a0.947 0.947 0 0 1-0.947 0.948H3.88a0.947 0.947 0 0 1-0.947-0.948V3.982A0.947 0.947 0 0 1 3.88 3.035z"
+        android:strokeWidth="3.57115173" />
+    <path
+        android:fillColor="?attr/deviceIconPrimary"
+        android:pathData="M3.871 3.413h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.36A0.947 0.947 0 0 1 3.87 3.413z"
+        android:strokeWidth="3.57115173" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M10.328 7.779h7.864a0.686 0.686 0 0 1 0.686 0.686v4.923a0.686 0.686 0 0 1-0.686 0.687h-7.864a0.686 0.686 0 0 1-0.686-0.687V8.465a0.686 0.686 0 0 1 0.686-0.686z"
+        android:strokeWidth="3.28026915" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M11.362 13.505h5.757a0.638 0.638 0 0 1 0.638 0.637v4.893a0.638 0.638 0 0 1-0.638 0.638h-5.757a0.638 0.638 0 0 1-0.637-0.638v-4.893a0.638 0.638 0 0 1 0.637-0.637z"
+        android:strokeWidth="2.83302426" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M12.729 19.419h3.042a0.342 0.342 0 0 1 0.342 0.341v1.856a0.342 0.342 0 0 1-0.342 0.342h-3.042a0.342 0.342 0 0 1-0.342-0.342v-1.855a0.342 0.342 0 0 1 0.342-0.342z"
+        android:strokeWidth="1.32303238" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M14.063 21.518h0.405a0.55 0.55 0 0 1 0.549 0.55v0.166a0.55 0.55 0 0 1-0.55 0.55h-0.404a0.55 0.55 0 0 1-0.549-0.55v-0.167a0.55 0.55 0 0 1 0.55-0.549z"
+        android:strokeWidth="0.5930993" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M10.262 7.56h7.996a0.214 0.214 0 0 1 0.213 0.214v5.073a0.214 0.214 0 0 1-0.213 0.213h-7.996a0.214 0.214 0 0 1-0.214-0.213V7.774a0.214 0.214 0 0 1 0.214-0.214z"
+        android:strokeWidth="2.9279027" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_device_roidmi_disabled.xml b/app/src/main/res/drawable/ic_device_roidmi_disabled.xml
new file mode 100644
index 0000000..9c7155f
--- /dev/null
+++ b/app/src/main/res/drawable/ic_device_roidmi_disabled.xml
@@ -0,0 +1,39 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="45sp"
+    android:height="45sp"
+    android:viewportWidth="30"
+    android:viewportHeight="30">
+    <path
+        android:fillColor="#7a7a7a"
+        android:pathData="M3.871 3.877h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.824a0.947 0.947 0 0 1 0.947-0.947z"
+        android:strokeWidth="3.57115173" />
+    <path
+        android:fillAlpha="0.9411765"
+        android:fillColor="#9f9f9f"
+        android:pathData="M3.879 3.035h20.925a0.947 0.947 0 0 1 0.947 0.947v20.01a0.947 0.947 0 0 1-0.947 0.948H3.88a0.947 0.947 0 0 1-0.947-0.948V3.982A0.947 0.947 0 0 1 3.88 3.035z"
+        android:strokeWidth="3.57115173" />
+    <path
+        android:fillColor="#8a8a8a"
+        android:pathData="M3.871 3.413h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.36A0.947 0.947 0 0 1 3.87 3.413z"
+        android:strokeWidth="3.57115173" />
+    <path
+        android:fillColor="#ffffff"
+        android:pathData="M10.443 7.535h7.864a0.686 0.686 0 0 1 0.686 0.686v4.923a0.686 0.686 0 0 1-0.686 0.687h-7.864a0.686 0.686 0 0 1-0.686-0.687V8.221a0.686 0.686 0 0 1 0.686-0.686z"
+        android:strokeWidth="3.28026915" />
+    <path
+        android:fillColor="#ffffff"
+        android:pathData="M11.477 13.26h5.757a0.638 0.638 0 0 1 0.638 0.638v4.893a0.638 0.638 0 0 1-0.638 0.638h-5.757a0.638 0.638 0 0 1-0.638-0.638v-4.893a0.638 0.638 0 0 1 0.638-0.637z"
+        android:strokeWidth="2.83302426" />
+    <path
+        android:fillColor="#ffffff"
+        android:pathData="M12.844 19.175h3.042a0.342 0.342 0 0 1 0.341 0.341v1.856a0.342 0.342 0 0 1-0.341 0.342h-3.042a0.342 0.342 0 0 1-0.342-0.342v-1.855a0.342 0.342 0 0 1 0.342-0.342z"
+        android:strokeWidth="1.32303238" />
+    <path
+        android:fillColor="#ffffff"
+        android:pathData="M14.178 21.274h0.404a0.55 0.55 0 0 1 0.55 0.55v0.166a0.55 0.55 0 0 1-0.55 0.55h-0.404a0.55 0.55 0 0 1-0.549-0.55v-0.167a0.55 0.55 0 0 1 0.55-0.549z"
+        android:strokeWidth="0.5930993" />
+    <path
+        android:fillColor="#ffffff"
+        android:pathData="M10.377 7.316h7.995a0.214 0.214 0 0 1 0.214 0.214v5.073a0.214 0.214 0 0 1-0.214 0.213h-7.995a0.214 0.214 0 0 1-0.214-0.213V7.53a0.214 0.214 0 0 1 0.214-0.214z"
+        android:strokeWidth="2.9279027" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_device_scannable.xml b/app/src/main/res/drawable/ic_device_scannable.xml
new file mode 100644
index 0000000..a4b4852
--- /dev/null
+++ b/app/src/main/res/drawable/ic_device_scannable.xml
@@ -0,0 +1,28 @@
+<vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:name="vector"
+    android:width="30dp"
+    android:height="30dp"
+    android:viewportWidth="30"
+    android:viewportHeight="30">
+    <path
+        android:name="path"
+        android:pathData="M 3.871 3.877 L 24.796 3.877 C 24.962 3.877 25.126 3.92 25.27 4.004 C 25.414 4.087 25.534 4.206 25.617 4.35 C 25.7 4.494 25.744 4.658 25.744 4.824 L 25.744 24.834 C 25.744 25 25.7 25.164 25.617 25.308 C 25.534 25.452 25.414 25.572 25.27 25.655 C 25.126 25.738 24.962 25.782 24.796 25.782 L 3.871 25.782 C 3.705 25.782 3.541 25.738 3.397 25.655 C 3.253 25.572 3.134 25.452 3.051 25.308 C 2.967 25.164 2.924 25 2.924 24.834 L 2.924 4.824 C 2.924 4.573 3.024 4.332 3.201 4.154 C 3.379 3.977 3.62 3.877 3.871 3.877 Z"
+        android:fillColor="?attr/deviceIconLight"
+        android:strokeWidth="1"/>
+    <path
+        android:name="path_1"
+        android:pathData="M 3.879 3.035 L 24.804 3.035 C 25.055 3.035 25.296 3.135 25.474 3.312 C 25.651 3.49 25.751 3.731 25.751 3.982 L 25.751 23.992 C 25.751 24.158 25.708 24.322 25.624 24.466 C 25.541 24.61 25.422 24.73 25.278 24.813 C 25.134 24.896 24.97 24.94 24.804 24.94 L 3.88 24.94 C 3.714 24.94 3.55 24.896 3.406 24.813 C 3.262 24.73 3.143 24.61 3.06 24.466 C 2.976 24.322 2.933 24.158 2.933 23.992 L 2.933 3.982 C 2.933 3.731 3.033 3.49 3.21 3.312 C 3.388 3.135 3.629 3.035 3.88 3.035 Z"
+        android:fillColor="?attr/deviceIconDark"
+        android:strokeWidth="1"/>
+    <path
+        android:name="path_2"
+        android:pathData="M 3.871 3.413 L 24.796 3.413 C 24.962 3.413 25.126 3.456 25.27 3.54 C 25.414 3.623 25.534 3.742 25.617 3.886 C 25.7 4.03 25.744 4.194 25.744 4.36 L 25.744 24.37 C 25.744 24.536 25.7 24.7 25.617 24.844 C 25.534 24.988 25.414 25.108 25.27 25.191 C 25.126 25.274 24.962 25.318 24.796 25.318 L 3.871 25.318 C 3.705 25.318 3.541 25.274 3.397 25.191 C 3.253 25.108 3.134 24.988 3.051 24.844 C 2.967 24.7 2.924 24.536 2.924 24.37 L 2.924 4.36 C 2.924 4.109 3.024 3.868 3.201 3.691 C 3.378 3.513 3.619 3.413 3.87 3.413 Z"
+        android:fillColor="?attr/deviceIconPrimary"
+        android:strokeWidth="1"/>
+    <path
+        android:name="path_3"
+        android:pathData="M 16.122 14.46 L 17.825 16.245 C 18.03 15.691 18.148 15.083 18.148 14.452 C 18.148 13.821 18.03 13.229 17.832 12.675 Z M 20.005 10.382 L 19.08 11.351 C 19.543 12.282 19.8 13.329 19.8 14.444 C 19.8 15.56 19.535 16.614 19.08 17.538 L 19.961 18.461 C 20.673 17.276 21.092 15.875 21.092 14.375 C 21.084 12.921 20.688 11.551 20.005 10.382 Z M 17.201 11.151 L 13.009 6.758 L 12.275 6.758 L 12.275 12.598 L 8.906 9.066 L 7.871 10.151 L 11.974 14.452 L 7.871 18.753 L 8.906 19.838 L 12.275 16.306 L 12.275 22.147 L 13.009 22.147 L 17.201 17.753 L 14.044 14.452 Z M 13.743 9.705 L 15.123 11.151 L 13.743 12.598 Z M 15.123 17.753 L 13.743 19.2 L 13.743 16.306 Z"
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:strokeWidth="1"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_device_scannable_disabled.xml b/app/src/main/res/drawable/ic_device_scannable_disabled.xml
new file mode 100644
index 0000000..3518e3c
--- /dev/null
+++ b/app/src/main/res/drawable/ic_device_scannable_disabled.xml
@@ -0,0 +1,26 @@
+<vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:name="vector"
+    android:width="30dp"
+    android:height="30dp"
+    android:viewportWidth="30"
+    android:viewportHeight="30">
+    <path
+        android:fillColor="#7a7a7a"
+        android:pathData="M3.871 3.877h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.824a0.947 0.947 0 0 1 0.947-0.947z"
+        android:strokeWidth="3.57115173" />
+    <path
+        android:fillAlpha="0.9411765"
+        android:fillColor="#9f9f9f"
+        android:pathData="M3.879 3.035h20.925a0.947 0.947 0 0 1 0.947 0.947v20.01a0.947 0.947 0 0 1-0.947 0.948H3.88a0.947 0.947 0 0 1-0.947-0.948V3.982A0.947 0.947 0 0 1 3.88 3.035z"
+        android:strokeWidth="3.57115173" />
+    <path
+        android:fillColor="#8a8a8a"
+        android:pathData="M3.871 3.413h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.36A0.947 0.947 0 0 1 3.87 3.413z"
+        android:strokeWidth="3.57115173" />
+    <path
+        android:name="path_3"
+        android:pathData="M 16.122 14.46 L 17.825 16.245 C 18.03 15.691 18.148 15.083 18.148 14.452 C 18.148 13.821 18.03 13.229 17.832 12.675 Z M 20.005 10.382 L 19.08 11.351 C 19.543 12.282 19.8 13.329 19.8 14.444 C 19.8 15.56 19.535 16.614 19.08 17.538 L 19.961 18.461 C 20.673 17.276 21.092 15.875 21.092 14.375 C 21.084 12.921 20.688 11.551 20.005 10.382 Z M 17.201 11.151 L 13.009 6.758 L 12.275 6.758 L 12.275 12.598 L 8.906 9.066 L 7.871 10.151 L 11.974 14.452 L 7.871 18.753 L 8.906 19.838 L 12.275 16.306 L 12.275 22.147 L 13.009 22.147 L 17.201 17.753 L 14.044 14.452 Z M 13.743 9.705 L 15.123 11.151 L 13.743 12.598 Z M 15.123 17.753 L 13.743 19.2 L 13.743 16.306 Z"
+        android:fillColor="#ffffff"
+        android:strokeWidth="1"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_device_set_reminders.xml b/app/src/main/res/drawable/ic_device_set_reminders.xml
new file mode 100644
index 0000000..aa25afa
--- /dev/null
+++ b/app/src/main/res/drawable/ic_device_set_reminders.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#7E7E7E"
+        android:pathData="M12,22c1.1,0 2,-0.9 2,-2h-4c0,1.1 0.89,2 2,2zM18,16v-5c0,-3.07 -1.64,-5.64 -4.5,-6.32L13.5,4c0,-0.83 -0.67,-1.5 -1.5,-1.5s-1.5,0.67 -1.5,1.5v0.68C7.63,5.36 6,7.92 6,11v5l-2,2v1h16v-1l-2,-2z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_device_smartring.xml b/app/src/main/res/drawable/ic_device_smartring.xml
new file mode 100644
index 0000000..8810158
--- /dev/null
+++ b/app/src/main/res/drawable/ic_device_smartring.xml
@@ -0,0 +1,22 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="30dp"
+    android:height="30dp"
+    android:viewportWidth="30"
+    android:viewportHeight="30">
+  <path
+      android:fillColor="?attr/deviceIconLight"
+      android:pathData="M3.871 3.877h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.824a0.947 0.947 0 0 1 0.947-0.947z"
+      android:strokeWidth="3.57115" />
+  <path
+      android:fillColor="?attr/deviceIconDark"
+      android:pathData="M3.879 3.035h20.925a0.947 0.947 0 0 1 0.947 0.947v20.01a0.947 0.947 0 0 1-0.947 0.948H3.88a0.947 0.947 0 0 1-0.947-0.948V3.982A0.947 0.947 0 0 1 3.88 3.035z"
+      android:strokeWidth="3.57115" />
+  <path
+      android:fillColor="?attr/deviceIconPrimary"
+      android:pathData="M3.871 3.413h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.36A0.947 0.947 0 0 1 3.87 3.413z"
+      android:strokeWidth="3.57115" />
+  <path
+      android:fillColor="?attr/deviceIconOnPrimary"
+      android:pathData="m16.822,6.557c-1.802,0.093 -3.48,1.006 -4.864,2.151 -2.034,1.673 -3.661,3.931 -4.462,6.492 -0.373,1.411 -0.603,3.061 0.246,4.345 0.832,1.273 2.524,1.476 3.854,1.052 2.34,-0.623 4.276,-2.305 5.808,-4.17 1.25,-1.606 2.256,-3.504 2.551,-5.56C20.091,9.587 19.974,8.058 18.895,7.217 18.311,6.74 17.557,6.552 16.822,6.557ZM16.562,6.807c1.111,-0.028 2.247,0.608 2.64,1.718 0.608,1.847 -0.088,3.815 -0.94,5.454 -1.266,2.356 -3.202,4.374 -5.555,5.571 -1.208,0.523 -2.658,1.011 -3.903,0.327 -1.192,-0.602 -1.611,-2.118 -1.402,-3.39 0.235,-2.183 1.391,-4.139 2.723,-5.802 1.574,-1.834 3.591,-3.481 6.005,-3.852 0.143,-0.018 0.287,-0.027 0.431,-0.027zM16.478,7.151c-2.089,0.145 -3.864,1.488 -5.336,2.921 -1.703,1.735 -3.072,3.953 -3.408,6.43 -0.124,1.064 0.068,2.31 1.003,2.943 0.056,-0.444 0.021,-1.19 0.144,-1.742 0.506,-2.767 2.122,-5.23 4.145,-7.077 1.549,-1.347 3.391,-2.532 5.467,-2.662 -0.48,-0.614 -1.28,-0.819 -2.015,-0.813zM20.07,8.265c0.22,0.778 0.373,1.666 0.23,2.502 -0.314,2.48 -1.628,4.709 -3.215,6.551 -1.628,1.831 -3.733,3.37 -6.157,3.76 -0.484,0.125 -1.407,-0.058 -1.619,0.002 0.68,0.587 1.331,1.29 2.243,1.466 1.36,0.301 2.764,-0.19 3.983,-0.789 3.143,-1.75 5.634,-4.861 6.431,-8.476C22.234,11.795 22.109,9.979 20.867,8.974 20.601,8.74 20.348,8.479 20.07,8.265Z"
+      android:strokeWidth="0.25"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_device_smartring_disabled.xml b/app/src/main/res/drawable/ic_device_smartring_disabled.xml
new file mode 100644
index 0000000..be74420
--- /dev/null
+++ b/app/src/main/res/drawable/ic_device_smartring_disabled.xml
@@ -0,0 +1,22 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="30dp"
+    android:height="30dp"
+    android:viewportWidth="30"
+    android:viewportHeight="30">
+  <path
+      android:fillColor="#7a7a7a"
+      android:pathData="M3.871 3.877h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.824a0.947 0.947 0 0 1 0.947-0.947z"
+      android:strokeWidth="3.57115" />
+  <path
+      android:fillColor="#9f9f9f"
+      android:pathData="M3.879 3.035h20.925a0.947 0.947 0 0 1 0.947 0.947v20.01a0.947 0.947 0 0 1-0.947 0.948H3.88a0.947 0.947 0 0 1-0.947-0.948V3.982A0.947 0.947 0 0 1 3.88 3.035z"
+      android:strokeWidth="3.57115" />
+  <path
+      android:fillColor="#8a8a8a"
+      android:pathData="M3.871 3.413h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.36A0.947 0.947 0 0 1 3.87 3.413z"
+      android:strokeWidth="3.57115" />
+  <path
+      android:fillColor="#ffffff"
+      android:pathData="m16.822,6.557c-1.802,0.093 -3.48,1.006 -4.864,2.151 -2.034,1.673 -3.661,3.931 -4.462,6.492 -0.373,1.411 -0.603,3.061 0.246,4.345 0.832,1.273 2.524,1.476 3.854,1.052 2.34,-0.623 4.276,-2.305 5.808,-4.17 1.25,-1.606 2.256,-3.504 2.551,-5.56C20.091,9.587 19.974,8.058 18.895,7.217 18.311,6.74 17.557,6.552 16.822,6.557ZM16.562,6.807c1.111,-0.028 2.247,0.608 2.64,1.718 0.608,1.847 -0.088,3.815 -0.94,5.454 -1.266,2.356 -3.202,4.374 -5.555,5.571 -1.208,0.523 -2.658,1.011 -3.903,0.327 -1.192,-0.602 -1.611,-2.118 -1.402,-3.39 0.235,-2.183 1.391,-4.139 2.723,-5.802 1.574,-1.834 3.591,-3.481 6.005,-3.852 0.143,-0.018 0.287,-0.027 0.431,-0.027zM16.478,7.151c-2.089,0.145 -3.864,1.488 -5.336,2.921 -1.703,1.735 -3.072,3.953 -3.408,6.43 -0.124,1.064 0.068,2.31 1.003,2.943 0.056,-0.444 0.021,-1.19 0.144,-1.742 0.506,-2.767 2.122,-5.23 4.145,-7.077 1.549,-1.347 3.391,-2.532 5.467,-2.662 -0.48,-0.614 -1.28,-0.819 -2.015,-0.813zM20.07,8.265c0.22,0.778 0.373,1.666 0.23,2.502 -0.314,2.48 -1.628,4.709 -3.215,6.551 -1.628,1.831 -3.733,3.37 -6.157,3.76 -0.484,0.125 -1.407,-0.058 -1.619,0.002 0.68,0.587 1.331,1.29 2.243,1.466 1.36,0.301 2.764,-0.19 3.983,-0.789 3.143,-1.75 5.634,-4.861 6.431,-8.476C22.234,11.795 22.109,9.979 20.867,8.974 20.601,8.74 20.348,8.479 20.07,8.265Z"
+      android:strokeWidth="0.25"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_device_sony_overhead.xml b/app/src/main/res/drawable/ic_device_sony_overhead.xml
new file mode 100644
index 0000000..6e67fed
--- /dev/null
+++ b/app/src/main/res/drawable/ic_device_sony_overhead.xml
@@ -0,0 +1,23 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="45sp"
+    android:height="45sp"
+    android:viewportWidth="30"
+    android:viewportHeight="30">
+    <path
+        android:fillColor="?attr/deviceIconLight"
+        android:pathData="M3.871 3.877h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.824a0.947 0.947 0 0 1 0.947-0.947z"
+        android:strokeWidth="3.57115" />
+    <path
+        android:fillColor="?attr/deviceIconDark"
+        android:pathData="M3.879 3.035h20.925a0.947 0.947 0 0 1 0.947 0.947v20.01a0.947 0.947 0 0 1-0.947 0.948H3.88a0.947 0.947 0 0 1-0.947-0.948V3.982A0.947 0.947 0 0 1 3.88 3.035z"
+        android:strokeWidth="3.57115" />
+    <path
+        android:fillColor="?attr/deviceIconPrimary"
+        android:pathData="M3.871 3.413h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.36A0.947 0.947 0 0 1 3.87 3.413z"
+        android:strokeWidth="3.57115" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M11.788 22.12s-2.091-0.292-2.658-1.225c-0.538-0.824-0.974-1.714-1.498-2.547-0.523-0.78-0.034-1.59 0.057-2.376-0.008-1.014-0.285-2.034-0.029-3.04 0.356-2.063 1.501-3.997 3.158-5.246 2.688-1.872 6.803-1.214 8.663 1.554 1.347 1.783 2.027 4.133 1.554 6.36-0.087 0.924 0.697 1.928 0.061 2.79-0.596 0.924-1.055 1.949-1.712 2.824-0.756 0.595-2.691 0.773-2.691 0.773s-1.515-0.619-1.65-1.432c0.042-0.63 0.5-1.137 0.746-1.7 0.841-1.55 1.744-3.067 2.737-4.522 0.49-0.444 1.602 0.74 1.564-0.407 0.076-1.294-0.51-2.515-1.13-3.606-0.52-0.856-1.367-1.458-2.317-1.715-2.033-0.69-4.431-0.473-6.166 0.867-1.16 1.257-1.893 2.975-1.84 4.71 0.301 0.86 1.157-0.488 1.686 0.353 0.606 0.62 0.904 1.47 1.406 2.171 0.729 1.2 1.437 2.42 1.999 3.712-0.01 0.972-1.94 1.702-1.94 1.702z"
+        android:strokeWidth="0.5"
+        android:strokeColor="?attr/deviceIconOnPrimary" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_device_sony_overhead_disabled.xml b/app/src/main/res/drawable/ic_device_sony_overhead_disabled.xml
new file mode 100644
index 0000000..1f22932
--- /dev/null
+++ b/app/src/main/res/drawable/ic_device_sony_overhead_disabled.xml
@@ -0,0 +1,28 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="45sp"
+    android:height="45sp"
+    android:viewportWidth="30"
+    android:viewportHeight="30">
+    <path
+        android:fillColor="#7a7a7a"
+        android:pathData="M3.871 3.877h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.824a0.947 0.947 0 0 1 0.947-0.947z"
+        android:strokeWidth="3.57115173" />
+    <path
+        android:fillAlpha="0.9411765"
+        android:fillColor="#9f9f9f"
+        android:pathData="M3.879 3.035h20.925a0.947 0.947 0 0 1 0.947 0.947v20.01a0.947 0.947 0 0 1-0.947 0.948H3.88a0.947 0.947 0 0 1-0.947-0.948V3.982A0.947 0.947 0 0 1 3.88 3.035z"
+        android:strokeWidth="3.57115173" />
+    <path
+        android:fillColor="#8a8a8a"
+        android:pathData="M3.871 3.413h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.36A0.947 0.947 0 0 1 3.87 3.413z"
+        android:strokeWidth="3.57115173" />
+    <path
+        android:fillAlpha="0.9411765"
+        android:fillColor="#8a8a8a"
+        android:pathData="M12.483 10.865h3.907v6.978h-3.907z" />
+    <path
+        android:fillColor="#fffffe"
+        android:pathData="M11.788 22.12s-2.091-0.292-2.658-1.225c-0.538-0.824-0.974-1.714-1.498-2.547-0.523-0.78-0.034-1.59 0.057-2.376-0.008-1.014-0.284-2.034-0.029-3.04 0.356-2.063 1.501-3.997 3.158-5.246 2.688-1.872 6.803-1.214 8.663 1.554 1.347 1.783 2.027 4.133 1.554 6.36-0.087 0.924 0.697 1.928 0.061 2.791-0.596 0.923-1.055 1.948-1.712 2.823-0.756 0.595-2.691 0.773-2.691 0.773s-1.515-0.619-1.65-1.431c0.042-0.632 0.5-1.138 0.746-1.702 0.841-1.549 1.744-3.066 2.737-4.52 0.49-0.445 1.602 0.74 1.564-0.408 0.076-1.294-0.51-2.515-1.13-3.606-0.52-0.856-1.367-1.458-2.317-1.715-2.033-0.69-4.431-0.473-6.166 0.868-1.16 1.256-1.893 2.974-1.84 4.71 0.301 0.858 1.157-0.49 1.686 0.352 0.606 0.62 0.904 1.47 1.406 2.171 0.729 1.2 1.437 2.42 1.999 3.712-0.01 0.972-1.94 1.702-1.94 1.702z"
+        android:strokeWidth="0.5"
+        android:strokeColor="#ffffff" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_device_sony_wf_800n.xml b/app/src/main/res/drawable/ic_device_sony_wf_800n.xml
new file mode 100644
index 0000000..bf6611e
--- /dev/null
+++ b/app/src/main/res/drawable/ic_device_sony_wf_800n.xml
@@ -0,0 +1,64 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="45sp"
+    android:height="45sp"
+    android:viewportWidth="30"
+    android:viewportHeight="30">
+    <path
+        android:fillColor="?attr/deviceIconLight"
+        android:pathData="M3.871 3.877h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.824a0.947 0.947 0 0 1 0.947-0.947z"
+        android:strokeWidth="3.57115" />
+    <path
+        android:fillColor="?attr/deviceIconDark"
+        android:pathData="M3.879 3.035h20.925a0.947 0.947 0 0 1 0.947 0.947v20.01a0.947 0.947 0 0 1-0.947 0.948H3.88a0.947 0.947 0 0 1-0.947-0.948V3.982A0.947 0.947 0 0 1 3.88 3.035z"
+        android:strokeWidth="3.57115" />
+    <path
+        android:fillColor="?attr/deviceIconPrimary"
+        android:pathData="M3.871 3.413h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.36A0.947 0.947 0 0 1 3.87 3.413z"
+        android:strokeWidth="3.57115" />
+    <path
+        android:pathData="M7.291 6.092L7.194 6.145C7.162 6.163 7.13 6.183 7.099 6.203l-0.1 0.067L6.89 6.35 6.815 6.405 6.74 6.466C6.691 6.507 6.642 6.55 6.595 6.595 6.547 6.64 6.5 6.685 6.455 6.734 6.41 6.78 6.365 6.83 6.323 6.88 6.28 6.93 6.24 6.983 6.2 7.035c-0.04 0.053-0.078 0.106-0.114 0.16C6.05 7.25 6.015 7.304 5.982 7.36L5.935 7.442C5.92 7.47 5.905 7.498 5.892 7.526c-0.037 0.071-0.07 0.14-0.1 0.206-0.03 0.065-0.058 0.128-0.083 0.19-0.025 0.061-0.047 0.12-0.067 0.18-0.01 0.03-0.02 0.059-0.028 0.088-0.01 0.03-0.017 0.058-0.025 0.088L5.567 8.366l-0.02 0.09c-0.011 0.06-0.021 0.12-0.03 0.184-0.008 0.063-0.015 0.13-0.02 0.198-0.007 0.069-0.01 0.14-0.014 0.217L5.477 9.219v0.157C5.478 9.428 5.48 9.478 5.483 9.527c0.003 0.05 0.007 0.099 0.012 0.147C5.5 9.722 5.505 9.77 5.513 9.818 5.52 9.866 5.529 9.914 5.538 9.961l0.033 0.144 0.042 0.146c0.015 0.05 0.031 0.1 0.05 0.15 0.018 0.051 0.037 0.103 0.059 0.156 0.02 0.053 0.044 0.108 0.068 0.163l0.079 0.173c0.028 0.059 0.058 0.12 0.09 0.182 0.031 0.063 0.065 0.128 0.1 0.196l0.113 0.209 0.125 0.224 0.19 0.325 0.188 0.312 0.184 0.3 0.182 0.287 0.18 0.275c0.059 0.09 0.117 0.177 0.176 0.262 0.058 0.086 0.116 0.17 0.173 0.251l0.17 0.24 0.169 0.227c0.055 0.074 0.11 0.146 0.165 0.217 0.054 0.07 0.108 0.138 0.162 0.205l0.008 0.01C8.02 14.582 7.132 13.792 6.67 13.21c-1.06-1.33-1.604-2.907-1.441-4.18 0.148-1.165 0.664-2.116 1.624-2.991C7.09 5.821 7.117 5.85 7.187 5.871 7.424 6.016 7.452 6.003 7.29 6.092z"
+        android:strokeWidth="0.4"
+        android:strokeColor="?attr/deviceIconOnPrimary" />
+    <path
+        android:pathData="M11.589 11.343c0.322-0.014 0.357 0.04 0.594-0.181 0.222-0.24 0.274-0.352 0.276-0.57 0.003-0.224-0.007-0.25-0.197-0.71-0.408-0.979-0.896-1.961-1.124-2.262-0.342-0.45-0.831-0.779-1.38-0.948-0.413-0.127-1.121-0.108-1.564 0.041-1.404 0.475-2.155 1.989-1.691 3.408 0.271 0.832 0.705 1.282 1.214 1.282z"
+        android:strokeWidth="0.3"
+        android:strokeColor="?attr/deviceIconOnPrimary" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M11.858 12.337a0.198 0.194 90 0 0 0.268 0.058 0.198 0.194 90 0 0 0.059-0.273 0.198 0.194 90 0 0-0.267-0.063 0.198 0.194 90 0 0-0.063 0.272"
+        android:strokeWidth="0.3"
+        android:strokeColor="?attr/deviceIconOnPrimary"
+        android:strokeLineCap="round"
+        android:strokeLineJoin="round" />
+    <path
+        android:pathData="M11.87 10.803L8.042 10.87c-0.145 0.003-0.261 0.12-0.261 0.268 0 0.148 0.116 0.27 0.261 0.267l3.826-0.068c0.145-0.003 0.262-0.12 0.262-0.268 0-0.148-0.117-0.27-0.262-0.267z"
+        android:strokeWidth="0.3"
+        android:strokeColor="?attr/deviceIconOnPrimary"
+        android:strokeLineCap="round"
+        android:strokeLineJoin="round" />
+    <path
+        android:pathData="M12.193 17.066c0.625-0.132 1.334-0.604 1.68-1.12 0.53-0.787 0.661-1.615 0.413-2.61-0.445-1.784-1.737-5.003-2.422-6.035-0.49-0.738-1.147-1.226-1.985-1.474-0.284-0.084-0.55-0.118-0.97-0.127-0.244-0.005-0.653 0.037-1.041 0.174-0.486 0.169-0.64 0.23-0.95 0.458-0.406 0.296-0.789 0.73-1.013 1.175-0.29 0.576-0.392 0.938-0.419 1.547-0.04 0.9 0.118 1.412 0.816 2.655 1.003 1.681 1.225 2.02 2.372 3.434 0.232 0.3 0.63 0.757 0.883 1.016 0.373 0.381 0.524 0.502 0.787 0.633 0.599 0.296 1.271 0.396 1.85 0.274zm9.319-4.474c0.034 0.017 0.066 0.034 0.097 0.053 0.032 0.018 0.064 0.037 0.096 0.058 0.032 0.02 0.064 0.043 0.099 0.067l0.109 0.079 0.075 0.057 0.075 0.06c0.05 0.041 0.098 0.084 0.146 0.129 0.047 0.044 0.094 0.09 0.14 0.138 0.045 0.048 0.089 0.097 0.131 0.148 0.043 0.05 0.084 0.102 0.124 0.154 0.04 0.052 0.078 0.106 0.114 0.16 0.036 0.054 0.07 0.109 0.103 0.164l0.047 0.083 0.044 0.083c0.036 0.072 0.07 0.14 0.1 0.206 0.03 0.066 0.057 0.129 0.082 0.19 0.025 0.062 0.047 0.121 0.067 0.18l0.029 0.088 0.025 0.088 0.022 0.089 0.019 0.089c0.012 0.06 0.022 0.121 0.03 0.185 0.009 0.063 0.016 0.129 0.021 0.198 0.006 0.068 0.01 0.14 0.014 0.217l0.005 0.164v0.157c0 0.051-0.003 0.102-0.006 0.151-0.002 0.05-0.006 0.098-0.011 0.147-0.005 0.048-0.011 0.096-0.019 0.144-0.007 0.048-0.015 0.095-0.025 0.143-0.01 0.048-0.02 0.096-0.033 0.144-0.012 0.048-0.026 0.097-0.041 0.146-0.015 0.05-0.032 0.1-0.05 0.15-0.018 0.051-0.038 0.103-0.06 0.156-0.02 0.053-0.043 0.107-0.068 0.163-0.024 0.056-0.05 0.113-0.079 0.172-0.028 0.06-0.058 0.12-0.09 0.183-0.03 0.063-0.064 0.128-0.1 0.195l-0.113 0.21-0.124 0.224-0.19 0.325-0.188 0.312-0.185 0.3-0.182 0.286-0.179 0.275-0.176 0.263c-0.059 0.086-0.116 0.17-0.174 0.251l-0.17 0.24-0.168 0.227c-0.056 0.074-0.11 0.146-0.165 0.216-0.055 0.07-0.109 0.139-0.163 0.205l-0.008 0.01c0.224-0.032 1.112-0.822 1.575-1.403 1.06-1.332 1.603-2.909 1.44-4.182-0.148-1.165-0.664-2.116-1.623-2.991-0.239-0.218-0.265-0.188-0.334-0.167-0.238 0.144-0.265 0.132-0.105 0.22z"
+        android:strokeWidth="0.4"
+        android:strokeColor="?attr/deviceIconOnPrimary" />
+    <path
+        android:pathData="M17.215 17.843c-0.323-0.014-0.358 0.039-0.594-0.182-0.222-0.24-0.274-0.352-0.277-0.569-0.002-0.225 0.007-0.251 0.198-0.71 0.407-0.98 0.895-1.962 1.123-2.262 0.342-0.45 0.832-0.779 1.38-0.948 0.413-0.128 1.122-0.109 1.565 0.041 1.403 0.475 2.154 1.989 1.69 3.408-0.271 0.832-0.705 1.282-1.214 1.282z"
+        android:strokeWidth="0.3"
+        android:strokeColor="?attr/deviceIconOnPrimary" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M16.945 18.836a0.194 0.198 0 0 1-0.267 0.058 0.194 0.198 0 0 1-0.06-0.272 0.194 0.198 0 0 1 0.267-0.063 0.194 0.198 0 0 1 0.063 0.272"
+        android:strokeWidth="0.3"
+        android:strokeColor="?attr/deviceIconOnPrimary"
+        android:strokeLineCap="round"
+        android:strokeLineJoin="round" />
+    <path
+        android:pathData="M16.934 17.302l3.826 0.069c0.145 0.002 0.262 0.119 0.262 0.267 0 0.148-0.117 0.27-0.262 0.268l-3.826-0.069c-0.145-0.002-0.261-0.119-0.261-0.267 0-0.148 0.116-0.27 0.261-0.268z"
+        android:strokeWidth="0.3"
+        android:strokeColor="?attr/deviceIconOnPrimary"
+        android:strokeLineCap="round"
+        android:strokeLineJoin="round" />
+    <path
+        android:pathData="M16.61 23.566c-0.624-0.132-1.333-0.604-1.68-1.12-0.53-0.788-0.661-1.615-0.413-2.611 0.446-1.784 1.738-5.003 2.422-6.035 0.49-0.737 1.147-1.225 1.985-1.473 0.285-0.085 0.55-0.119 0.97-0.127 0.244-0.005 0.653 0.037 1.041 0.174 0.486 0.169 0.64 0.23 0.95 0.458 0.406 0.296 0.789 0.73 1.013 1.175 0.29 0.575 0.392 0.938 0.42 1.547 0.04 0.9-0.118 1.411-0.817 2.654-1.003 1.682-1.225 2.02-2.372 3.434-0.232 0.3-0.63 0.757-0.883 1.017-0.373 0.38-0.523 0.502-0.787 0.633-0.598 0.296-1.27 0.395-1.849 0.274z"
+        android:strokeWidth="0.4"
+        android:strokeColor="?attr/deviceIconOnPrimary" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_device_sony_wf_800n_disabled.xml b/app/src/main/res/drawable/ic_device_sony_wf_800n_disabled.xml
new file mode 100644
index 0000000..d8992d8
--- /dev/null
+++ b/app/src/main/res/drawable/ic_device_sony_wf_800n_disabled.xml
@@ -0,0 +1,69 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="45sp"
+    android:height="45sp"
+    android:viewportWidth="30"
+    android:viewportHeight="30">
+    <path
+        android:fillColor="#7a7a7a"
+        android:pathData="M3.871 3.877h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.824a0.947 0.947 0 0 1 0.947-0.947z"
+        android:strokeWidth="3.57115173" />
+    <path
+        android:fillAlpha="0.9411765"
+        android:fillColor="#9f9f9f"
+        android:pathData="M3.879 3.035h20.925a0.947 0.947 0 0 1 0.947 0.947v20.01a0.947 0.947 0 0 1-0.947 0.948H3.88a0.947 0.947 0 0 1-0.947-0.948V3.982A0.947 0.947 0 0 1 3.88 3.035z"
+        android:strokeWidth="3.57115173" />
+    <path
+        android:fillColor="#8a8a8a"
+        android:pathData="M3.871 3.413h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.36A0.947 0.947 0 0 1 3.87 3.413z"
+        android:strokeWidth="3.57115173" />
+    <path
+        android:fillAlpha="0.9411765"
+        android:fillColor="#8a8a8a"
+        android:pathData="M12.483 10.865h3.907v6.978h-3.907z" />
+    <path
+        android:pathData="M7.291 6.092L7.194 6.145C7.162 6.163 7.13 6.183 7.099 6.203l-0.1 0.067L6.89 6.35 6.815 6.405 6.74 6.466C6.691 6.507 6.642 6.55 6.595 6.595 6.547 6.64 6.5 6.685 6.455 6.734 6.41 6.78 6.365 6.83 6.323 6.88 6.28 6.93 6.24 6.983 6.2 7.035c-0.04 0.053-0.078 0.106-0.114 0.16C6.05 7.25 6.015 7.304 5.982 7.36L5.935 7.442C5.92 7.47 5.905 7.498 5.892 7.526c-0.037 0.071-0.07 0.14-0.1 0.206-0.03 0.065-0.058 0.128-0.083 0.19-0.025 0.061-0.047 0.12-0.067 0.18-0.01 0.03-0.02 0.059-0.028 0.088-0.01 0.03-0.017 0.058-0.025 0.088L5.567 8.366l-0.02 0.09c-0.011 0.06-0.021 0.12-0.03 0.184-0.008 0.063-0.015 0.13-0.02 0.198-0.007 0.069-0.01 0.14-0.014 0.217L5.477 9.219v0.157C5.478 9.428 5.48 9.478 5.483 9.527c0.003 0.05 0.007 0.099 0.012 0.147C5.5 9.722 5.505 9.77 5.513 9.818 5.52 9.866 5.529 9.914 5.538 9.961l0.033 0.144 0.042 0.146c0.015 0.05 0.031 0.1 0.05 0.15 0.018 0.051 0.037 0.103 0.059 0.156 0.02 0.053 0.044 0.108 0.068 0.163l0.079 0.173c0.028 0.059 0.058 0.12 0.09 0.182 0.031 0.063 0.065 0.128 0.1 0.196l0.113 0.209 0.125 0.224 0.19 0.325 0.188 0.312 0.184 0.3 0.182 0.287 0.18 0.275c0.059 0.09 0.117 0.177 0.176 0.262 0.058 0.086 0.116 0.17 0.173 0.251l0.17 0.24 0.169 0.227c0.055 0.074 0.11 0.146 0.165 0.217 0.054 0.07 0.108 0.138 0.162 0.205l0.008 0.01C8.02 14.582 7.132 13.792 6.67 13.21c-1.06-1.33-1.604-2.907-1.441-4.18 0.148-1.165 0.664-2.116 1.624-2.991C7.09 5.821 7.117 5.85 7.187 5.871 7.424 6.016 7.452 6.003 7.29 6.092z"
+        android:strokeWidth="0.4"
+        android:strokeColor="#ffffff" />
+    <path
+        android:pathData="M11.589 11.343c0.322-0.014 0.357 0.04 0.594-0.181 0.222-0.24 0.274-0.352 0.276-0.57 0.003-0.224-0.007-0.25-0.197-0.71-0.408-0.979-0.896-1.961-1.124-2.262-0.342-0.45-0.831-0.779-1.38-0.948-0.413-0.127-1.121-0.108-1.564 0.041-1.404 0.475-2.155 1.989-1.691 3.408 0.271 0.832 0.705 1.282 1.214 1.282z"
+        android:strokeWidth="0.3"
+        android:strokeColor="#ffffff" />
+    <path
+        android:fillColor="#ffffff"
+        android:pathData="M11.858 12.337a0.198 0.194 90 0 0 0.268 0.058 0.198 0.194 90 0 0 0.059-0.273 0.198 0.194 90 0 0-0.267-0.063 0.198 0.194 90 0 0-0.063 0.272"
+        android:strokeWidth="0.3"
+        android:strokeColor="#ffffff"
+        android:strokeLineCap="round"
+        android:strokeLineJoin="round" />
+    <path
+        android:pathData="M11.87 10.803L8.042 10.87c-0.145 0.003-0.261 0.12-0.261 0.268 0 0.148 0.116 0.27 0.261 0.267l3.826-0.068c0.145-0.003 0.262-0.12 0.262-0.268 0-0.148-0.117-0.27-0.262-0.267z"
+        android:strokeWidth="0.3"
+        android:strokeColor="#ffffff"
+        android:strokeLineCap="round"
+        android:strokeLineJoin="round" />
+    <path
+        android:pathData="M12.193 17.066c0.625-0.132 1.334-0.604 1.68-1.12 0.53-0.787 0.661-1.615 0.413-2.61-0.445-1.784-1.737-5.003-2.422-6.035-0.49-0.738-1.147-1.226-1.985-1.474-0.284-0.084-0.55-0.118-0.97-0.127-0.244-0.005-0.653 0.037-1.041 0.174-0.486 0.169-0.64 0.23-0.95 0.458-0.406 0.296-0.789 0.73-1.013 1.175-0.29 0.576-0.392 0.938-0.419 1.547-0.04 0.9 0.118 1.412 0.816 2.655 1.003 1.681 1.225 2.02 2.372 3.434 0.232 0.3 0.63 0.757 0.883 1.016 0.373 0.381 0.524 0.502 0.787 0.633 0.599 0.296 1.271 0.396 1.85 0.274zm9.319-4.474c0.034 0.017 0.066 0.034 0.097 0.053 0.032 0.018 0.064 0.037 0.096 0.058 0.032 0.02 0.064 0.043 0.099 0.067l0.109 0.079 0.075 0.057 0.075 0.06c0.05 0.041 0.098 0.084 0.146 0.129 0.047 0.044 0.094 0.09 0.14 0.138 0.045 0.048 0.089 0.097 0.131 0.148 0.043 0.05 0.084 0.102 0.124 0.154 0.04 0.052 0.078 0.106 0.114 0.16 0.036 0.054 0.07 0.109 0.103 0.164l0.047 0.083 0.044 0.083c0.036 0.072 0.07 0.14 0.1 0.206 0.03 0.066 0.057 0.129 0.082 0.19 0.025 0.062 0.047 0.121 0.067 0.18l0.029 0.088 0.025 0.088 0.022 0.089 0.019 0.089c0.012 0.06 0.022 0.121 0.03 0.185 0.009 0.063 0.016 0.129 0.021 0.198 0.006 0.068 0.01 0.14 0.014 0.217l0.005 0.164v0.157c0 0.051-0.003 0.102-0.006 0.151-0.002 0.05-0.006 0.098-0.011 0.147-0.005 0.048-0.011 0.096-0.019 0.144-0.007 0.048-0.015 0.095-0.025 0.143-0.01 0.048-0.02 0.096-0.033 0.144-0.012 0.048-0.026 0.097-0.041 0.146-0.015 0.05-0.032 0.1-0.05 0.15-0.018 0.051-0.038 0.103-0.06 0.156-0.02 0.053-0.043 0.107-0.068 0.163-0.024 0.056-0.05 0.113-0.079 0.172-0.028 0.06-0.058 0.12-0.09 0.183-0.03 0.063-0.064 0.128-0.1 0.195l-0.113 0.21-0.124 0.224-0.19 0.325-0.188 0.312-0.185 0.3-0.182 0.286-0.179 0.275-0.176 0.263c-0.059 0.086-0.116 0.17-0.174 0.251l-0.17 0.24-0.168 0.227c-0.056 0.074-0.11 0.146-0.165 0.216-0.055 0.07-0.109 0.139-0.163 0.205l-0.008 0.01c0.224-0.032 1.112-0.822 1.575-1.403 1.06-1.332 1.603-2.909 1.44-4.182-0.148-1.165-0.664-2.116-1.623-2.991-0.239-0.218-0.265-0.188-0.334-0.167-0.238 0.144-0.265 0.132-0.105 0.22z"
+        android:strokeWidth="0.4"
+        android:strokeColor="#ffffff" />
+    <path
+        android:pathData="M17.215 17.843c-0.323-0.014-0.358 0.039-0.594-0.182-0.222-0.24-0.274-0.352-0.277-0.569-0.002-0.225 0.007-0.251 0.198-0.71 0.407-0.98 0.895-1.962 1.123-2.262 0.342-0.45 0.832-0.779 1.38-0.948 0.413-0.128 1.122-0.109 1.565 0.041 1.403 0.475 2.154 1.989 1.69 3.408-0.271 0.832-0.705 1.282-1.214 1.282z"
+        android:strokeWidth="0.3"
+        android:strokeColor="#ffffff" />
+    <path
+        android:fillColor="#ffffff"
+        android:pathData="M16.945 18.836a0.194 0.198 0 0 1-0.267 0.058 0.194 0.198 0 0 1-0.06-0.272 0.194 0.198 0 0 1 0.267-0.063 0.194 0.198 0 0 1 0.063 0.272"
+        android:strokeWidth="0.3"
+        android:strokeColor="#ffffff"
+        android:strokeLineCap="round"
+        android:strokeLineJoin="round" />
+    <path
+        android:pathData="M16.934 17.302l3.826 0.069c0.145 0.002 0.262 0.119 0.262 0.267 0 0.148-0.117 0.27-0.262 0.268l-3.826-0.069c-0.145-0.002-0.261-0.119-0.261-0.267 0-0.148 0.116-0.27 0.261-0.268z"
+        android:strokeWidth="0.3"
+        android:strokeColor="#ffffff"
+        android:strokeLineCap="round"
+        android:strokeLineJoin="round" />
+    <path
+        android:pathData="M16.61 23.566c-0.624-0.132-1.333-0.604-1.68-1.12-0.53-0.788-0.661-1.615-0.413-2.611 0.446-1.784 1.738-5.003 2.422-6.035 0.49-0.737 1.147-1.225 1.985-1.473 0.285-0.085 0.55-0.119 0.97-0.127 0.244-0.005 0.653 0.037 1.041 0.174 0.486 0.169 0.64 0.23 0.95 0.458 0.406 0.296 0.789 0.73 1.013 1.175 0.29 0.575 0.392 0.938 0.42 1.547 0.04 0.9-0.118 1.411-0.817 2.654-1.003 1.682-1.225 2.02-2.372 3.434-0.232 0.3-0.63 0.757-0.883 1.017-0.373 0.38-0.523 0.502-0.787 0.633-0.598 0.296-1.27 0.395-1.849 0.274z"
+        android:strokeWidth="0.4"
+        android:strokeColor="#ffffff" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_device_supercars.xml b/app/src/main/res/drawable/ic_device_supercars.xml
new file mode 100644
index 0000000..3f4c57b
--- /dev/null
+++ b/app/src/main/res/drawable/ic_device_supercars.xml
@@ -0,0 +1,22 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="45sp"
+    android:height="45sp"
+    android:viewportWidth="30"
+    android:viewportHeight="30">
+    <path
+        android:fillColor="?attr/deviceIconLight"
+        android:pathData="M3.871 3.877h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.824a0.947 0.947 0 0 1 0.947-0.947z"
+        android:strokeWidth="3.57115" />
+    <path
+        android:fillColor="?attr/deviceIconDark"
+        android:pathData="M3.879 3.035h20.925a0.947 0.947 0 0 1 0.947 0.947v20.01a0.947 0.947 0 0 1-0.947 0.948H3.88a0.947 0.947 0 0 1-0.947-0.948V3.982A0.947 0.947 0 0 1 3.88 3.035z"
+        android:strokeWidth="3.57115" />
+    <path
+        android:fillColor="?attr/deviceIconPrimary"
+        android:pathData="M3.871 3.413h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.36A0.947 0.947 0 0 1 3.87 3.413z"
+        android:strokeWidth="3.57115" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M21.841 13.785c-0.292-3.157-3.15-5.415-6.3-5.415-0.142 0-0.285 0.008-0.427 0.015C6.87 8.925 6.87 18.27 6.87 18.27v0.6c0 0.825 0.675 1.5 1.5 1.5h7.5c3.503 0 6.308-3.007 5.97-6.585zM9.316 14.04c0.428-0.967 0.96-1.762 1.605-2.392l2.715 1.147c0.45 0.188 0.735 0.623 0.735 1.11 0 0.668-0.54 1.208-1.207 1.208H8.91c0.113-0.345 0.24-0.705 0.405-1.073zm9.885 3.36c-0.855 0.938-2.07 1.47-3.33 1.47h-7.5v-0.6c0-0.015 0.008-0.69 0.18-1.65h4.613c1.492 0 2.707-1.215 2.707-2.707 0-1.088-0.652-2.07-1.65-2.49l-1.875-0.795c0.825-0.428 1.778-0.675 2.865-0.743 0.113-0.015 0.225-0.015 0.33-0.015 2.483 0 4.598 1.778 4.808 4.058 0.12 1.29-0.285 2.52-1.148 3.472z"
+        android:strokeWidth="0.9999975" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_device_supercars_disabled.xml b/app/src/main/res/drawable/ic_device_supercars_disabled.xml
new file mode 100644
index 0000000..f40c2fe
--- /dev/null
+++ b/app/src/main/res/drawable/ic_device_supercars_disabled.xml
@@ -0,0 +1,27 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="45sp"
+    android:height="45sp"
+    android:viewportWidth="30"
+    android:viewportHeight="30">
+    <path
+        android:fillColor="#7a7a7a"
+        android:pathData="M3.871 3.877h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.824a0.947 0.947 0 0 1 0.947-0.947z"
+        android:strokeWidth="3.57115" />
+    <path
+        android:fillAlpha="0.941176"
+        android:fillColor="#9f9f9f"
+        android:pathData="M3.879 3.035h20.925a0.947 0.947 0 0 1 0.947 0.947v20.01a0.947 0.947 0 0 1-0.947 0.948H3.88a0.947 0.947 0 0 1-0.947-0.948V3.982A0.947 0.947 0 0 1 3.88 3.035z"
+        android:strokeWidth="3.57115" />
+    <path
+        android:fillColor="#8a8a8a"
+        android:pathData="M3.871 3.413h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.36A0.947 0.947 0 0 1 3.87 3.413z"
+        android:strokeWidth="3.57115" />
+    <path
+        android:fillAlpha="0.9411765"
+        android:fillColor="#8a8a8a"
+        android:pathData="M12.483 10.865h3.907v6.978h-3.907z" />
+    <path
+        android:fillColor="#ffffff"
+        android:pathData="M21.841 13.785c-0.292-3.157-3.15-5.415-6.3-5.415-0.142 0-0.285 0.008-0.427 0.015C6.87 8.925 6.87 18.27 6.87 18.27v0.6c0 0.825 0.675 1.5 1.5 1.5h7.5c3.503 0 6.308-3.007 5.97-6.585zM9.316 14.04c0.428-0.967 0.96-1.762 1.605-2.392l2.715 1.147c0.45 0.188 0.735 0.623 0.735 1.11 0 0.668-0.54 1.208-1.207 1.208H8.91c0.113-0.345 0.24-0.705 0.405-1.073zm9.885 3.36c-0.855 0.938-2.07 1.47-3.33 1.47h-7.5v-0.6c0-0.015 0.008-0.69 0.18-1.65h4.613c1.492 0 2.707-1.215 2.707-2.707 0-1.088-0.652-2.07-1.65-2.49l-1.875-0.795c0.825-0.428 1.778-0.675 2.865-0.743 0.113-0.015 0.225-0.015 0.33-0.015 2.483 0 4.598 1.778 4.808 4.058 0.12 1.29-0.285 2.52-1.148 3.472z"
+        android:strokeWidth="0.9999975" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_device_thermometer.xml b/app/src/main/res/drawable/ic_device_thermometer.xml
new file mode 100644
index 0000000..fbef54b
--- /dev/null
+++ b/app/src/main/res/drawable/ic_device_thermometer.xml
@@ -0,0 +1,6 @@
+<vector android:height="45sp" android:viewportHeight="30" android:viewportWidth="30" android:width="45sp" xmlns:android="http://schemas.android.com/apk/res/android">
+    <path android:fillColor="?attr/deviceIconLight" android:pathData="M3.871 3.877h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.824a0.947 0.947 0 0 1 0.947-0.947z" android:strokeWidth="3.57115"/>
+    <path android:fillColor="?attr/deviceIconDark" android:pathData="M3.879 3.035h20.925a0.947 0.947 0 0 1 0.947 0.947v20.01a0.947 0.947 0 0 1-0.947 0.948H3.879a0.947 0.947 0 0 1-0.947-0.948V3.982a0.947 0.947 0 0 1 0.947-0.947z" android:strokeWidth="3.57115"/>
+    <path android:fillColor="?attr/deviceIconPrimary" android:pathData="M3.871 3.413h20.925a0.947 0.947 0 0 1 0.947 0.947v20.01a0.947 0.947 0 0 1-0.947 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.36a0.947 0.947 0 0 1 0.947-0.947z" android:strokeWidth="3.57115"/>
+    <path android:fillColor="#FFFFFF" android:pathData="M14.37 22.271c-1.093 0-2.024-0.385-2.794-1.156-0.771-0.77-1.156-1.702-1.156-2.795 0-0.632 0.138-1.221 0.415-1.767 0.276-0.547 0.665-1.011 1.165-1.393V8.839c0-0.658 0.231-1.218 0.691-1.679 0.462-0.461 1.021-0.691 1.679-0.691 0.659 0 1.218 0.23 1.68 0.691 0.46 0.461 0.691 1.021 0.691 1.679v6.321c0.5 0.382 0.889 0.846 1.165 1.393 0.277 0.546 0.415 1.135 0.415 1.767 0 1.093-0.385 2.025-1.156 2.795-0.77 0.771-1.702 1.156-2.795 1.156zm-0.79-8.691h1.581v-0.79H14.37V12h0.791v-1.581H14.37v-0.79h0.791v-0.79c0-0.224-0.076-0.411-0.228-0.563-0.151-0.151-0.339-0.227-0.563-0.227-0.223 0-0.411 0.076-0.563 0.227-0.151 0.152-0.227 0.339-0.227 0.563v4.741z" android:strokeWidth="0.55"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_device_thermometer_disabled.xml b/app/src/main/res/drawable/ic_device_thermometer_disabled.xml
new file mode 100644
index 0000000..b97f878
--- /dev/null
+++ b/app/src/main/res/drawable/ic_device_thermometer_disabled.xml
@@ -0,0 +1,6 @@
+<vector android:height="45sp" android:viewportHeight="30" android:viewportWidth="30" android:width="45sp" xmlns:android="http://schemas.android.com/apk/res/android">
+    <path android:fillColor="#7a7a7a" android:pathData="M3.871 3.877h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.824a0.947 0.947 0 0 1 0.947-0.947z" android:strokeWidth="3.57115173"/>
+    <path android:fillAlpha="0.9411765" android:fillColor="#9f9f9f" android:pathData="M3.879 3.035h20.925a0.947 0.947 0 0 1 0.947 0.947v20.01a0.947 0.947 0 0 1-0.947 0.948H3.879a0.947 0.947 0 0 1-0.947-0.948V3.982a0.947 0.947 0 0 1 0.947-0.947z" android:strokeWidth="3.57115173"/>
+    <path android:fillColor="#8a8a8a" android:pathData="M3.871 3.413h20.925a0.947 0.947 0 0 1 0.947 0.947v20.01a0.947 0.947 0 0 1-0.947 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.36a0.947 0.947 0 0 1 0.947-0.947z" android:strokeWidth="3.57115173"/>
+    <path android:fillColor="#FFFFFF" android:pathData="M14.37 22.271c-1.093 0-2.024-0.385-2.794-1.156-0.771-0.77-1.156-1.702-1.156-2.795 0-0.632 0.138-1.221 0.415-1.767 0.276-0.547 0.665-1.011 1.165-1.393V8.839c0-0.658 0.231-1.218 0.691-1.679 0.462-0.461 1.021-0.691 1.679-0.691 0.659 0 1.218 0.23 1.68 0.691 0.46 0.461 0.691 1.021 0.691 1.679v6.321c0.5 0.382 0.889 0.846 1.165 1.393 0.277 0.546 0.415 1.135 0.415 1.767 0 1.093-0.385 2.025-1.156 2.795-0.77 0.771-1.702 1.156-2.795 1.156zm-0.79-8.691h1.581v-0.79H14.37V12h0.791v-1.581H14.37v-0.79h0.791v-0.79c0-0.224-0.076-0.411-0.228-0.563-0.151-0.151-0.339-0.227-0.563-0.227-0.223 0-0.411 0.076-0.563 0.227-0.151 0.152-0.227 0.339-0.227 0.563v4.741z" android:strokeWidth="0.55"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_device_unknown.xml b/app/src/main/res/drawable/ic_device_unknown.xml
new file mode 100644
index 0000000..9158160
--- /dev/null
+++ b/app/src/main/res/drawable/ic_device_unknown.xml
@@ -0,0 +1,22 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="45sp"
+    android:height="45sp"
+    android:viewportWidth="30"
+    android:viewportHeight="30">
+    <path
+        android:fillColor="?attr/deviceIconLight"
+        android:pathData="M3.871 3.877h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.824a0.947 0.947 0 0 1 0.947-0.947z"
+        android:strokeWidth="3.5" />
+    <path
+        android:fillColor="?attr/deviceIconDark"
+        android:pathData="M3.879 3.035h20.925a0.947 0.947 0 0 1 0.947 0.947v20.01a0.947 0.947 0 0 1-0.947 0.948H3.88a0.947 0.947 0 0 1-0.947-0.948V3.982A0.947 0.947 0 0 1 3.88 3.035z"
+        android:strokeWidth="3.5" />
+    <path
+        android:fillColor="?attr/deviceIconPrimary"
+        android:pathData="M3.871 3.413h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.36A0.947 0.947 0 0 1 3.87 3.413z"
+        android:strokeWidth="3.5" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M15.634 17.365h-4.23v-0.574q0-0.96 0.387-1.699 0.386-0.75 1.629-1.898l0.75-0.68q0.668-0.61 0.972-1.148 0.317-0.54 0.317-1.079 0-0.82-0.563-1.277-0.562-0.469-1.57-0.469-0.95 0-2.05 0.399-1.103 0.386-2.298 1.16V6.42q1.418-0.492 2.59-0.727Q12.74 5.46 13.83 5.46q2.86 0 4.36 1.172 1.5 1.16 1.5 3.398 0 1.149-0.458 2.063-0.457 0.902-1.559 1.945l-0.75 0.668q-0.796 0.727-1.042 1.172-0.247 0.434-0.247 0.961zm-4.23 1.735h4.23v4.172h-4.23z"
+        android:strokeWidth="1" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_device_unknown_disabled.xml b/app/src/main/res/drawable/ic_device_unknown_disabled.xml
new file mode 100644
index 0000000..330f03e
--- /dev/null
+++ b/app/src/main/res/drawable/ic_device_unknown_disabled.xml
@@ -0,0 +1,27 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="45sp"
+    android:height="45sp"
+    android:viewportWidth="30"
+    android:viewportHeight="30">
+    <path
+        android:fillColor="#7a7a7a"
+        android:pathData="M3.871 3.877h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.824a0.947 0.947 0 0 1 0.947-0.947z"
+        android:strokeWidth="3.5" />
+    <path
+        android:fillAlpha="0.941176"
+        android:fillColor="#9f9f9f"
+        android:pathData="M3.879 3.035h20.925a0.947 0.947 0 0 1 0.947 0.947v20.01a0.947 0.947 0 0 1-0.947 0.948H3.88a0.947 0.947 0 0 1-0.947-0.948V3.982A0.947 0.947 0 0 1 3.88 3.035z"
+        android:strokeWidth="3.5" />
+    <path
+        android:fillColor="#8a8a8a"
+        android:pathData="M3.871 3.413h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.36A0.947 0.947 0 0 1 3.87 3.413z"
+        android:strokeWidth="3.5" />
+    <path
+        android:fillAlpha="0.9411765"
+        android:fillColor="#8a8a8a"
+        android:pathData="M12.483 10.865h3.907v6.978h-3.907z" />
+    <path
+        android:fillColor="#ffffff"
+        android:pathData="M15.634 17.365h-4.23v-0.574q0-0.96 0.387-1.699 0.386-0.75 1.629-1.898l0.75-0.68q0.668-0.61 0.972-1.148 0.317-0.54 0.317-1.079 0-0.82-0.563-1.277-0.562-0.469-1.57-0.469-0.95 0-2.05 0.399-1.103 0.386-2.298 1.16V6.42q1.418-0.492 2.59-0.727Q12.74 5.46 13.83 5.46q2.86 0 4.36 1.172 1.5 1.16 1.5 3.398 0 1.149-0.458 2.063-0.457 0.902-1.559 1.945l-0.75 0.668q-0.796 0.727-1.042 1.172-0.247 0.434-0.247 0.961zm-4.23 1.735h4.23v4.172h-4.23z"
+        android:strokeWidth="1" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_device_vesc.xml b/app/src/main/res/drawable/ic_device_vesc.xml
new file mode 100644
index 0000000..ddfba0a
--- /dev/null
+++ b/app/src/main/res/drawable/ic_device_vesc.xml
@@ -0,0 +1,26 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="45sp"
+    android:height="45sp"
+    android:viewportWidth="30"
+    android:viewportHeight="30">
+    <path
+        android:fillColor="?attr/deviceIconLight"
+        android:pathData="M3.871 3.877h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.824a0.947 0.947 0 0 1 0.947-0.947z"
+        android:strokeWidth="3.57115" />
+    <path
+        android:fillColor="?attr/deviceIconDark"
+        android:pathData="M3.879 3.035h20.925a0.947 0.947 0 0 1 0.947 0.947v20.01a0.947 0.947 0 0 1-0.947 0.948H3.88a0.947 0.947 0 0 1-0.947-0.948V3.982A0.947 0.947 0 0 1 3.88 3.035z"
+        android:strokeWidth="3.5" />
+    <path
+        android:fillColor="?attr/deviceIconPrimary"
+        android:pathData="M3.871 3.413h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.36A0.947 0.947 0 0 1 3.87 3.413z"
+        android:strokeWidth="3.5" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M21.455 11.429l-1.047 1.575a6.811 6.811 55.425 0 1-0.188 6.454H8.42a6.811 6.811 65.813 0 1 8.948-9.493l1.575-1.048A8.514 8.514 107.27 0 0 6.956 20.31a1.703 1.703 0 0 0 1.464 0.85h11.792a1.703 1.703 0 0 0 1.481-0.851 8.514 8.514 51.806 0 0-0.23-8.889z"
+        android:strokeWidth="1" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M13.12 17.253a1.703 1.703 0 0 0 2.41 0l4.818-7.229-7.228 4.82a1.703 1.703 0 0 0 0 2.409z"
+        android:strokeWidth="1" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_device_vesc_disabled.xml b/app/src/main/res/drawable/ic_device_vesc_disabled.xml
new file mode 100644
index 0000000..b10e3e0
--- /dev/null
+++ b/app/src/main/res/drawable/ic_device_vesc_disabled.xml
@@ -0,0 +1,31 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="45sp"
+    android:height="45sp"
+    android:viewportWidth="30"
+    android:viewportHeight="30">
+    <path
+        android:fillColor="#7a7a7a"
+        android:pathData="M3.871 3.877h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.824a0.947 0.947 0 0 1 0.947-0.947z"
+        android:strokeWidth="3.5" />
+    <path
+        android:fillAlpha="0.941176"
+        android:fillColor="#9f9f9f"
+        android:pathData="M3.879 3.035h20.925a0.947 0.947 0 0 1 0.947 0.947v20.01a0.947 0.947 0 0 1-0.947 0.948H3.88a0.947 0.947 0 0 1-0.947-0.948V3.982A0.947 0.947 0 0 1 3.88 3.035z"
+        android:strokeWidth="3.5" />
+    <path
+        android:fillColor="#8a8a8a"
+        android:pathData="M3.871 3.413h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.36A0.947 0.947 0 0 1 3.87 3.413z"
+        android:strokeWidth="3.5" />
+    <path
+        android:fillAlpha="0.9411765"
+        android:fillColor="#8a8a8a"
+        android:pathData="M12.483 10.865h3.907v6.978h-3.907z" />
+    <path
+        android:fillColor="#fffffe"
+        android:pathData="M21.455 11.429l-1.047 1.575a6.811 6.811 55.425 0 1-0.188 6.454H8.42a6.811 6.811 65.813 0 1 8.948-9.493l1.575-1.048A8.514 8.514 107.27 0 0 6.956 20.31a1.703 1.703 0 0 0 1.464 0.85h11.792a1.703 1.703 0 0 0 1.481-0.851 8.514 8.514 51.806 0 0-0.23-8.889z"
+        android:strokeWidth="1" />
+    <path
+        android:fillColor="#fffffe"
+        android:pathData="M13.12 17.253a1.703 1.703 0 0 0 2.41 0l4.818-7.229-7.228 4.82a1.703 1.703 0 0 0 0 2.409z"
+        android:strokeWidth="1" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_device_watchxplus.xml b/app/src/main/res/drawable/ic_device_watchxplus.xml
new file mode 100644
index 0000000..bfbd6a7
--- /dev/null
+++ b/app/src/main/res/drawable/ic_device_watchxplus.xml
@@ -0,0 +1,38 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="45sp"
+    android:height="45sp"
+    android:viewportWidth="30"
+    android:viewportHeight="30">
+    <path
+        android:fillColor="?attr/deviceIconLight"
+        android:pathData="M3.871 3.877h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.824a0.947 0.947 0 0 1 0.947-0.947z"
+        android:strokeWidth="3.57115173" />
+    <path
+        android:fillColor="?attr/deviceIconDark"
+        android:pathData="M3.879 3.035h20.925a0.947 0.947 0 0 1 0.947 0.947v20.01a0.947 0.947 0 0 1-0.947 0.948H3.88a0.947 0.947 0 0 1-0.947-0.948V3.982A0.947 0.947 0 0 1 3.88 3.035z"
+        android:strokeWidth="3.57115173" />
+    <path
+        android:fillColor="?attr/deviceIconPrimary"
+        android:pathData="M3.871 3.413h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.36A0.947 0.947 0 0 1 3.87 3.413z"
+        android:strokeWidth="3.57115173" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M17.478 6.646h0.637a0.499 0.499 0 0 1 0.5 0.5v14.377a0.499 0.499 0 0 1-0.5 0.498h-0.637a0.499 0.499 0 0 1-0.499-0.498V7.145a0.499 0.499 0 0 1 0.5-0.499z"
+        android:strokeWidth="3.87336898" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M20.002 13.067h0.36a0.499 0.499 0 0 1 0.5 0.5v1.465a0.499 0.499 0 0 1-0.5 0.5h-0.36a0.499 0.499 0 0 1-0.5-0.5v-1.466a0.499 0.499 0 0 1 0.5-0.499z"
+        android:strokeWidth="3.5" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M10.6 6.678h0.637a0.499 0.499 0 0 1 0.498 0.498v14.378a0.499 0.499 0 0 1-0.498 0.499h-0.638a0.499 0.499 0 0 1-0.498-0.5V7.177a0.499 0.499 0 0 1 0.498-0.498z"
+        android:strokeWidth="3.87336898" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M8.388 14.334a5.923 6.099 0 1 0 11.846 0 5.923 6.099 0 1 0-11.846 0z"
+        android:strokeWidth="9.28846169" />
+    <path
+        android:fillColor="?attr/deviceIconPrimary"
+        android:pathData="M9.883 14.295a4.464 4.597 0 1 0 8.928 0 4.464 4.597 0 1 0-8.928 0z"
+        android:strokeWidth="7.00060892" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_device_watchxplus_disabled.xml b/app/src/main/res/drawable/ic_device_watchxplus_disabled.xml
new file mode 100644
index 0000000..71bbfa5
--- /dev/null
+++ b/app/src/main/res/drawable/ic_device_watchxplus_disabled.xml
@@ -0,0 +1,39 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="45sp"
+    android:height="45sp"
+    android:viewportWidth="30"
+    android:viewportHeight="30">
+    <path
+        android:fillColor="#7a7a7a"
+        android:pathData="M3.871 3.877h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.824a0.947 0.947 0 0 1 0.947-0.947z"
+        android:strokeWidth="3.57115173" />
+    <path
+        android:fillAlpha="0.9411765"
+        android:fillColor="#9f9f9f"
+        android:pathData="M3.879 3.035h20.925a0.947 0.947 0 0 1 0.947 0.947v20.01a0.947 0.947 0 0 1-0.947 0.948H3.88a0.947 0.947 0 0 1-0.947-0.948V3.982A0.947 0.947 0 0 1 3.88 3.035z"
+        android:strokeWidth="3.57115173" />
+    <path
+        android:fillColor="#8a8a8a"
+        android:pathData="M3.871 3.413h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.36A0.947 0.947 0 0 1 3.87 3.413z"
+        android:strokeWidth="3.57115173" />
+    <path
+        android:fillColor="#ffffff"
+        android:pathData="M17.39 6.396h0.637a0.499 0.499 0 0 1 0.499 0.5v14.377a0.499 0.499 0 0 1-0.499 0.498H17.39a0.499 0.499 0 0 1-0.499-0.498V6.895a0.499 0.499 0 0 1 0.499-0.499z"
+        android:strokeWidth="3.87336898" />
+    <path
+        android:fillColor="#ffffff"
+        android:pathData="M20.07 12.786h0.36a0.499 0.499 0 0 1 0.5 0.499v1.466a0.499 0.499 0 0 1-0.5 0.499h-0.36a0.499 0.499 0 0 1-0.5-0.499v-1.466a0.499 0.499 0 0 1 0.5-0.499z"
+        android:strokeWidth="3.5" />
+    <path
+        android:fillColor="#ffffff"
+        android:pathData="M10.667 6.396h0.638a0.499 0.499 0 0 1 0.498 0.5v14.377a0.499 0.499 0 0 1-0.498 0.498h-0.638a0.499 0.499 0 0 1-0.498-0.498V6.895a0.499 0.499 0 0 1 0.498-0.499z"
+        android:strokeWidth="3.87336898" />
+    <path
+        android:fillColor="#ffffff"
+        android:pathData="M8.456 14.053a5.923 6.099 0 1 0 11.846 0 5.923 6.099 0 1 0-11.846 0z"
+        android:strokeWidth="9.28846169" />
+    <path
+        android:fillColor="#8a8a8a"
+        android:pathData="M9.95 14.014a4.464 4.597 0 1 0 8.929 0 4.464 4.597 0 1 0-8.928 0z"
+        android:strokeWidth="7.00060892" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_device_zetime.xml b/app/src/main/res/drawable/ic_device_zetime.xml
new file mode 100644
index 0000000..0ea8e75
--- /dev/null
+++ b/app/src/main/res/drawable/ic_device_zetime.xml
@@ -0,0 +1,50 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="45sp"
+    android:height="45sp"
+    android:viewportWidth="30"
+    android:viewportHeight="30">
+    <path
+        android:fillColor="?attr/deviceIconLight"
+        android:pathData="M3.871 3.877h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.824a0.947 0.947 0 0 1 0.947-0.947z"
+        android:strokeWidth="3.57115173" />
+    <path
+        android:fillColor="?attr/deviceIconDark"
+        android:pathData="M3.879 3.035h20.925a0.947 0.947 0 0 1 0.947 0.947v20.01a0.947 0.947 0 0 1-0.947 0.948H3.88a0.947 0.947 0 0 1-0.947-0.948V3.982A0.947 0.947 0 0 1 3.88 3.035z"
+        android:strokeWidth="3.57115173" />
+    <path
+        android:fillColor="?attr/deviceIconPrimary"
+        android:pathData="M3.871 3.413h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.36A0.947 0.947 0 0 1 3.87 3.413z"
+        android:strokeWidth="3.57115173" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M11.982 5.98h4.515a0.55 0.55 0 0 1 0.549 0.55v14.9a0.55 0.55 0 0 1-0.55 0.548h-4.513a0.55 0.55 0 0 1-0.55-0.549V6.53a0.55 0.55 0 0 1 0.55-0.549z"
+        android:strokeWidth="3.5" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M14.219 21.052c-4.122-0.06-4.413-0.53-4.375-7.033 0.038-6.503 0.335-6.963 4.457-6.903 4.121 0.06 4.413 0.529 4.374 7.032-0.038 6.504-0.335 6.964-4.456 6.904z"
+        android:strokeWidth="3.5" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M8.531 14.1a5.703 5.703 0 1 1 11.407 0 5.703 5.703 0 1 1-11.407 0"
+        android:strokeWidth="5.37907696" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M19.14 10.906a0.422 0.422 118.059 0 1 0.548 0.236l0.44 1.103a0.422 0.422 106.141 0 1-0.237 0.548 0.422 0.422 106.141 0 1-0.548-0.236l-0.439-1.103a0.422 0.422 118.059 0 1 0.236-0.548zm0.826 4.371a0.422 0.422 93.114 0 1 0.22 0.555l-0.472 1.09a0.422 0.422 120.657 0 1-0.555 0.219 0.422 0.422 120.657 0 1-0.22-0.555l0.472-1.09a0.422 0.422 93.114 0 1 0.555-0.22z"
+        android:strokeWidth="3.5" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M19.516 13.209h0.625a0.422 0.422 0 0 1 0.422 0.422v0.687a0.422 0.422 0 0 1-0.422 0.422h-0.625a0.422 0.422 0 0 1-0.422-0.422v-0.687a0.422 0.422 0 0 1 0.422-0.422z"
+        android:strokeWidth="3.29593801" />
+    <path
+        android:fillColor="?attr/deviceIconPrimary"
+        android:pathData="M9.937 14.021a4.312 4.312 0 1 1 8.625 0 4.312 4.312 0 1 1-8.625 0"
+        android:strokeWidth="9.89868832" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M12.437 11.79l2.21 2.21a0.344 0.344 0 0 1 0 0.485 0.344 0.344 0 0 1-0.486 0l-2.21-2.21a0.344 0.344 0 0 1 0-0.486 0.344 0.344 0 0 1 0.486 0z"
+        android:strokeWidth="2.75432277" />
+    <path
+        android:fillColor="?attr/deviceIconOnPrimary"
+        android:pathData="M14.516 13.92h1.906a0.344 0.344 0 0 1 0.344 0.343 0.344 0.344 0 0 1-0.344 0.344h-1.906a0.344 0.344 0 0 1-0.344-0.344 0.344 0.344 0 0 1 0.344-0.344z"
+        android:strokeWidth="2.27182031" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_device_zetime_disabled.xml b/app/src/main/res/drawable/ic_device_zetime_disabled.xml
new file mode 100644
index 0000000..9c2589d
--- /dev/null
+++ b/app/src/main/res/drawable/ic_device_zetime_disabled.xml
@@ -0,0 +1,51 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="45sp"
+    android:height="45sp"
+    android:viewportWidth="30"
+    android:viewportHeight="30">
+    <path
+        android:fillColor="#7a7a7a"
+        android:pathData="M3.871 3.877h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.824a0.947 0.947 0 0 1 0.947-0.947z"
+        android:strokeWidth="3.57115173" />
+    <path
+        android:fillAlpha="0.9411765"
+        android:fillColor="#9f9f9f"
+        android:pathData="M3.879 3.035h20.925a0.947 0.947 0 0 1 0.947 0.947v20.01a0.947 0.947 0 0 1-0.947 0.948H3.88a0.947 0.947 0 0 1-0.947-0.948V3.982A0.947 0.947 0 0 1 3.88 3.035z"
+        android:strokeWidth="3.57115173" />
+    <path
+        android:fillColor="#8a8a8a"
+        android:pathData="M3.871 3.413h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.36A0.947 0.947 0 0 1 3.87 3.413z"
+        android:strokeWidth="3.57115173" />
+    <path
+        android:fillColor="#ffffff"
+        android:pathData="M11.81 6.366h4.515a0.55 0.55 0 0 1 0.55 0.55v14.9a0.55 0.55 0 0 1-0.55 0.548h-4.514a0.55 0.55 0 0 1-0.55-0.549v-14.9a0.55 0.55 0 0 1 0.55-0.549z"
+        android:strokeWidth="3.5" />
+    <path
+        android:fillColor="#ffffff"
+        android:pathData="M14.047 21.438c-4.122-0.06-4.413-0.53-4.375-7.033 0.038-6.503 0.335-6.963 4.457-6.903 4.121 0.06 4.413 0.529 4.374 7.032-0.038 6.504-0.335 6.964-4.456 6.903z"
+        android:strokeWidth="3.5" />
+    <path
+        android:fillColor="#ffffff"
+        android:pathData="M8.359 14.485a5.703 5.703 0 1 1 11.407 0 5.703 5.703 0 1 1-11.407 0"
+        android:strokeWidth="5.37907696" />
+    <path
+        android:fillColor="#ffffff"
+        android:pathData="M18.968 11.292a0.422 0.422 49.05 0 1 0.548 0.236l0.44 1.103a0.422 0.422 106.141 0 1-0.237 0.548 0.422 0.422 106.141 0 1-0.548-0.236l-0.439-1.103a0.422 0.422 49.05 0 1 0.236-0.548zm0.826 4.371a0.422 0.422 68.194 0 1 0.22 0.555l-0.472 1.09a0.422 0.422 60.234 0 1-0.555 0.219 0.422 0.422 60.234 0 1-0.22-0.555l0.472-1.09a0.422 0.422 68.194 0 1 0.555-0.22z"
+        android:strokeWidth="3.5" />
+    <path
+        android:fillColor="#ffffff"
+        android:pathData="M19.344 13.595h0.625a0.422 0.422 0 0 1 0.422 0.422v0.687a0.422 0.422 0 0 1-0.422 0.422h-0.625a0.422 0.422 0 0 1-0.422-0.422v-0.687a0.422 0.422 0 0 1 0.422-0.422z"
+        android:strokeWidth="3.29593801" />
+    <path
+        android:fillColor="#8a8a8a"
+        android:pathData="M9.766 14.407a4.312 4.312 0 1 1 8.624 0 4.312 4.312 0 1 1-8.624 0"
+        android:strokeWidth="9.89868832" />
+    <path
+        android:fillColor="#ffffff"
+        android:pathData="M12.265 12.175l2.21 2.21a0.344 0.344 0 0 1 0 0.486 0.344 0.344 0 0 1-0.486 0l-2.21-2.21a0.344 0.344 109.54 0 1 0-0.486 0.344 0.344 109.54 0 1 0.486 0z"
+        android:strokeWidth="2.75432277" />
+    <path
+        android:fillColor="#ffffff"
+        android:pathData="M14.344 14.305h1.906a0.344 0.344 0 0 1 0.344 0.344 0.344 0.344 0 0 1-0.344 0.344h-1.906A0.344 0.344 0 0 1 14 14.649a0.344 0.344 0 0 1 0.344-0.344z"
+        android:strokeWidth="2.27182031" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_devices_other.xml b/app/src/main/res/drawable/ic_devices_other.xml
new file mode 100644
index 0000000..46b697e
--- /dev/null
+++ b/app/src/main/res/drawable/ic_devices_other.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="#000000"
+        android:pathData="M3 7c0-0.55 0.45-1 1-1h16c0.55 0 1-0.45 1-1s-0.45-1-1-1H3C1.9 4 1 4.9 1 6v12c0 1.1 0.9 2 2 2h3c0.55 0 1-0.45 1-1s-0.45-1-1-1H4c-0.55 0-1-0.45-1-1V7zm9 5h-2c-0.55 0-1 0.45-1 1v0.78C8.39 14.33 8 15.11 8 16c0 0.89 0.39 1.67 1 2.22V19c0 0.55 0.45 1 1 1h2c0.55 0 1-0.45 1-1v-0.78c0.61-0.55 1-1.34 1-2.22s-0.39-1.67-1-2.22V13c0-0.55-0.45-1-1-1zm-1 5.5c-0.83 0-1.5-0.67-1.5-1.5s0.67-1.5 1.5-1.5 1.5 0.67 1.5 1.5-0.67 1.5-1.5 1.5zM22 8h-6c-0.5 0-1 0.5-1 1v10c0 0.5 0.5 1 1 1h6c0.5 0 1-0.5 1-1V9c0-0.5-0.5-1-1-1zm-1 10h-4v-8h4v8z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_distance.xml b/app/src/main/res/drawable/ic_distance.xml
new file mode 100644
index 0000000..ae3406c
--- /dev/null
+++ b/app/src/main/res/drawable/ic_distance.xml
@@ -0,0 +1,20 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24.170443dp"
+    android:height="24dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="24.170443"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M8.58 13.798c-0.836 0-1.52-0.693-1.52-1.54 0-0.848 0.684-1.54 1.52-1.54s1.52 0.692 1.52 1.54c0 0.847-0.684 1.54-1.52 1.54zm4.56-1.386c0-2.796-2.013-4.776-4.56-4.776-2.546 0-4.56 1.98-4.56 4.776 0 1.802 1.482 4.19 4.56 7.04 3.079-2.85 4.56-5.238 4.56-7.04zM8.58 6.096c3.193 0 6.08 2.48 6.08 6.316 0 2.557-2.029 5.583-6.08 9.088-4.05-3.504-6.08-6.531-6.08-9.088 0-3.836 2.888-6.316 6.08-6.316z"
+        android:strokeWidth="0.993391" />
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M18.606 6.382c-0.422 0-0.766-0.35-0.766-0.777 0-0.427 0.345-0.776 0.766-0.776 0.421 0 0.766 0.35 0.766 0.776 0 0.427-0.345 0.777-0.766 0.777zm2.298-0.699c0-1.409-1.015-2.407-2.298-2.407-1.283 0-2.299 0.998-2.299 2.407 0 0.908 0.747 2.112 2.299 3.548 1.551-1.436 2.298-2.64 2.298-3.548zM18.606 2.5c1.609 0 3.064 1.25 3.064 3.183 0 1.289-1.022 2.814-3.064 4.58-2.042-1.766-3.065-3.291-3.065-4.58 0-1.933 1.456-3.183 3.065-3.183z"
+        android:strokeWidth="0.99339" />
+    <path
+        android:fillType="evenOdd"
+        android:pathData="M10.9 20.385c17.527-4.9-0.051-6.886 6.62-10.398"
+        android:strokeWidth="0.993391"
+        android:strokeColor="#000000" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_distance_total.xml b/app/src/main/res/drawable/ic_distance_total.xml
new file mode 100644
index 0000000..d251a51
--- /dev/null
+++ b/app/src/main/res/drawable/ic_distance_total.xml
@@ -0,0 +1,6 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24.170443dp" android:height="24dp" android:tint="#7E7E7E" android:viewportWidth="24.170443" android:viewportHeight="24">
+    <path android:fillColor="#FF000000" android:pathData="M11.239 10.769c-0.837 0-1.52-0.693-1.52-1.54 0-0.848 0.683-1.54 1.52-1.54 0.836 0 1.52 0.692 1.52 1.54 0 0.847-0.684 1.54-1.52 1.54zm4.56-1.386c0-2.796-2.014-4.776-4.56-4.776-2.547 0-4.56 1.98-4.56 4.776 0 1.802 1.481 4.19 4.56 7.04 3.078-2.85 4.56-5.238 4.56-7.04zm-4.56-6.316c3.192 0 6.08 2.48 6.08 6.316 0 2.557-2.03 5.583-6.08 9.088-4.052-3.504-6.08-6.531-6.08-9.088 0-3.836 2.887-6.316 6.08-6.316z" android:strokeWidth="1"/>
+    <path android:fillColor="#FF000000" android:pathData="M20.108 4.784c-0.422 0-0.766-0.35-0.766-0.776 0-0.427 0.344-0.777 0.766-0.777 0.421 0 0.766 0.35 0.766 0.777 0 0.427-0.345 0.776-0.766 0.776zm2.298-0.699c0-1.409-1.015-2.406-2.298-2.406-1.284 0-2.299 0.997-2.299 2.406 0 0.909 0.747 2.112 2.299 3.548 1.551-1.436 2.298-2.64 2.298-3.548zm-2.298-3.183c1.609 0 3.064 1.25 3.064 3.184 0 1.288-1.023 2.814-3.064 4.58-2.042-1.766-3.065-3.292-3.065-4.58 0-1.933 1.456-3.183 3.065-3.183z" android:strokeWidth="0.999999"/>
+    <path android:pathData="M6.246 21.452c18.536-0.72 4.63-9.508 16.083-12.305" android:strokeWidth="1" android:strokeColor="#000000" android:fillType="evenOdd"/>
+    <path android:fillColor="#FF000000" android:pathData="M4.003 17.725c-0.422 0-0.767-0.35-0.767-0.776 0-0.427 0.345-0.777 0.767-0.777 0.42 0 0.766 0.35 0.766 0.777 0 0.427-0.345 0.776-0.766 0.776zM6.3 17.026c0-1.409-1.015-2.406-2.298-2.406-1.284 0-2.299 0.997-2.299 2.406 0 0.909 0.747 2.112 2.299 3.548 1.55-1.436 2.298-2.64 2.298-3.548zm-2.298-3.183c1.608 0 3.064 1.25 3.064 3.184 0 1.288-1.023 2.814-3.064 4.58-2.042-1.766-3.065-3.292-3.065-4.58 0-1.934 1.456-3.184 3.065-3.184z" android:strokeWidth="0.999999"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_donate.xml b/app/src/main/res/drawable/ic_donate.xml
new file mode 100644
index 0000000..00f29ed
--- /dev/null
+++ b/app/src/main/res/drawable/ic_donate.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="45sp"
+    android:height="45sp"
+    android:viewportWidth="45"
+    android:viewportHeight="45">
+    <path
+        android:fillColor="#000000"
+        android:pathData="M17.266 33.844c0-2.401 1.482-4.458 3.581-5.31l-0.098 0.005c-2.717-2.464-2.5-3.827-2.547-4.84v-0.511c-4.804 2.836-10.196 6.529-11.886 4.839L5.892 27.01c5.654-3.903 11.31-7.806 15.96-11.714 0.397-0.434 1.05-0.699 2.122-0.681 4.933 0.049 9.609 0.436 14.096 0.934l5.518-2.378-0.171 7.047c-4.53 4.225-11.256 6.388-16.326 9.616 1.014 1.034 1.641 2.451 1.641 4.01 0 3.164-2.568 5.733-5.734 5.733-3.163 0-5.732-2.569-5.732-5.733zm9.98 0c0-2.34-1.905-4.243-4.248-4.243s-4.246 1.903-4.246 4.243c0 2.343 1.903 4.245 4.246 4.245 2.343 0 4.248-1.902 4.248-4.245zm-6.049 2.308c-0.355-0.375-0.589-0.841-0.7-1.401h-0.612l0.133-0.639h0.405c-0.003-0.066-0.008-0.139-0.008-0.213 0-0.128 0.005-0.237 0.011-0.333h-0.541l0.133-0.641h0.491c0.116-0.55 0.345-1.009 0.689-1.377 0.523-0.557 1.205-0.833 2.055-0.833 0.463 0 0.865 0.083 1.205 0.249l-0.242 1.147c-0.236-0.235-0.576-0.351-1.026-0.351-0.45 0-0.812 0.161-1.089 0.484-0.158 0.18-0.269 0.409-0.335 0.681h2.274l-0.135 0.641h-2.222c-0.003 0.061-0.004 0.143-0.004 0.242 0 0.096 0.001 0.198 0.007 0.304h2.105l-0.132 0.639h-1.887c0.07 0.296 0.174 0.528 0.321 0.693 0.274 0.326 0.635 0.489 1.073 0.489 0.528 0 0.951-0.163 1.265-0.489v1.266c-0.358 0.18-0.775 0.269-1.252 0.269-0.804 0-1.466-0.277-1.982-0.827zm8.297-11.86c0.709-1.712 0.594-4.211-4.755-2.971-2.961 1.242-3.613 2.611-2.547 4.076 0.595 0.911 0.932 1.816 1.137 2.725 0.602 0.035 1.178 0.162 1.718 0.37 1.429-1.572 3.837-2.733 4.447-4.2z"
+        android:strokeWidth="0.75"
+        android:strokeLineCap="round"
+        android:strokeMiterLimit="80" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_done.xml b/app/src/main/res/drawable/ic_done.xml
new file mode 100644
index 0000000..899cbb6
--- /dev/null
+++ b/app/src/main/res/drawable/ic_done.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="?attr/colorControlNormal">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M9,16.2L4.8,12l-1.4,1.4L9,19 21,7l-1.4,-1.4L9,16.2z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_done_all.xml b/app/src/main/res/drawable/ic_done_all.xml
new file mode 100644
index 0000000..b2f2f57
--- /dev/null
+++ b/app/src/main/res/drawable/ic_done_all.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="#7E7E7E">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M18,7l-1.41,-1.41 -6.34,6.34 1.41,1.41L18,7zM22.24,5.59L11.66,16.17 7.48,12l-1.41,1.41L11.66,19l12,-12 -1.42,-1.41zM0.41,13.41L6,19l1.41,-1.41L1.83,12 0.41,13.41z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_drag_handle.xml b/app/src/main/res/drawable/ic_drag_handle.xml
new file mode 100644
index 0000000..1177cb3
--- /dev/null
+++ b/app/src/main/res/drawable/ic_drag_handle.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="#000000"
+        android:pathData="M19 9H5c-0.55 0-1 0.45-1 1s0.45 1 1 1h14c0.55 0 1-0.45 1-1s-0.45-1-1-1zM5 15h14c0.55 0 1-0.45 1-1s-0.45-1-1-1H5c-0.55 0-1 0.45-1 1s0.45 1 1 1z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_drink.xml b/app/src/main/res/drawable/ic_drink.xml
new file mode 100644
index 0000000..0fefc5e
--- /dev/null
+++ b/app/src/main/res/drawable/ic_drink.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="#7E7E7E">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M3,2l2.01,18.23C5.13,21.23 5.97,22 7,22h10c1.03,0 1.87,-0.77 1.99,-1.77L21,2L3,2zM17,20l-10,0.01L5.89,10L18.1,10L17,20zM18.33,8L5.67,8l-0.44,-4h13.53l-0.43,4zM12,19c1.66,0 3,-1.34 3,-3 0,-2 -3,-5.4 -3,-5.4S9,14 9,16c0,1.66 1.34,3 3,3zM12,13.91c0.59,0.91 1,1.73 1,2.09 0,0.55 -0.45,1 -1,1s-1,-0.45 -1,-1c0,-0.37 0.41,-1.19 1,-2.09z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_earbuds_battery.xml b/app/src/main/res/drawable/ic_earbuds_battery.xml
new file mode 100644
index 0000000..1137346
--- /dev/null
+++ b/app/src/main/res/drawable/ic_earbuds_battery.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="?attr/colorControlNormal">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M21,7h-1V6h-2v1h-1c-0.55,0 -1,0.45 -1,1v9c0,0.55 0.45,1 1,1h4c0.55,0 1,-0.45 1,-1V8C22,7.45 21.55,7 21,7zM20,16h-2V9h2V16zM14,9.38C14,7.51 12.49,6 10.62,6S7.25,7.51 7.25,9.38v5.25c0,1.04 -0.84,1.88 -1.88,1.88S3.5,15.66 3.5,14.62v-4.7C3.66,9.97 3.83,10 4,10c1.1,0 2,-0.9 2,-2S5.1,6 4,6S2,6.9 2,8c0,0.04 0,6.62 0,6.62C2,16.49 3.51,18 5.38,18s3.38,-1.51 3.38,-3.38V9.38c0,-1.04 0.84,-1.88 1.88,-1.88s1.88,0.84 1.88,1.88v4.7C12.34,14.03 12.17,14 12,14c-1.1,0 -2,0.9 -2,2s0.9,2 2,2s2,-0.9 2,-2C14,15.96 14,9.38 14,9.38z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_edit.xml b/app/src/main/res/drawable/ic_edit.xml
new file mode 100644
index 0000000..7a2303c
--- /dev/null
+++ b/app/src/main/res/drawable/ic_edit.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:tint="#7E7E7E"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M14.06,9.02l0.92,0.92L5.92,19L5,19v-0.92l9.06,-9.06M17.66,3c-0.25,0 -0.51,0.1 -0.7,0.29l-1.83,1.83 3.75,3.75 1.83,-1.83c0.39,-0.39 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.2,-0.2 -0.45,-0.29 -0.71,-0.29zM14.06,6.19L3,17.25L3,21h3.75L17.81,9.94l-3.75,-3.75z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_email.xml b/app/src/main/res/drawable/ic_email.xml
new file mode 100644
index 0000000..7e6e248
--- /dev/null
+++ b/app/src/main/res/drawable/ic_email.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M20,4L4,4c-1.1,0 -1.99,0.9 -1.99,2L2,18c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,6c0,-1.1 -0.9,-2 -2,-2zM20,8l-8,5 -8,-5L4,6l8,5 8,-5v2z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_emergency.xml b/app/src/main/res/drawable/ic_emergency.xml
new file mode 100644
index 0000000..025ea08
--- /dev/null
+++ b/app/src/main/res/drawable/ic_emergency.xml
@@ -0,0 +1,12 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M20.79,9.23l-2,-3.46l-4.79,2.77l0,-5.54l-4,0l0,5.54l-4.79,-2.77l-2,3.46l4.79,2.77l-4.79,2.77l2,3.46l4.79,-2.77l0,5.54l4,0l0,-5.54l4.79,2.77l2,-3.46l-4.79,-2.77z" />
+
+</vector>
diff --git a/app/src/main/res/drawable/ic_engineering.xml b/app/src/main/res/drawable/ic_engineering.xml
new file mode 100644
index 0000000..3aede2a
--- /dev/null
+++ b/app/src/main/res/drawable/ic_engineering.xml
@@ -0,0 +1,19 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M9,15c-2.67,0 -8,1.34 -8,4v2h16v-2C17,16.34 11.67,15 9,15zM3,19c0.22,-0.72 3.31,-2 6,-2c2.7,0 5.8,1.29 6,2H3z" />
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M4.74,9H5c0,2.21 1.79,4 4,4s4,-1.79 4,-4h0.26c0.27,0 0.49,-0.22 0.49,-0.49V8.49c0,-0.27 -0.22,-0.49 -0.49,-0.49H13c0,-1.48 -0.81,-2.75 -2,-3.45V5.5C11,5.78 10.78,6 10.5,6S10,5.78 10,5.5V4.14C9.68,4.06 9.35,4 9,4S8.32,4.06 8,4.14V5.5C8,5.78 7.78,6 7.5,6S7,5.78 7,5.5V4.55C5.81,5.25 5,6.52 5,8H4.74C4.47,8 4.25,8.22 4.25,8.49v0.03C4.25,8.78 4.47,9 4.74,9zM11,9c0,1.1 -0.9,2 -2,2s-2,-0.9 -2,-2H11z" />
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M21.98,6.23l0.93,-0.83l-0.75,-1.3l-1.19,0.39c-0.14,-0.11 -0.3,-0.2 -0.47,-0.27L20.25,3h-1.5L18.5,4.22c-0.17,0.07 -0.33,0.16 -0.48,0.27L16.84,4.1l-0.75,1.3l0.93,0.83C17,6.4 17,6.58 17.02,6.75L16.09,7.6l0.75,1.3l1.2,-0.38c0.13,0.1 0.28,0.18 0.43,0.25L18.75,10h1.5l0.27,-1.22c0.16,-0.07 0.3,-0.15 0.44,-0.25l1.19,0.38l0.75,-1.3l-0.93,-0.85C22,6.57 21.99,6.4 21.98,6.23zM19.5,7.75c-0.69,0 -1.25,-0.56 -1.25,-1.25s0.56,-1.25 1.25,-1.25s1.25,0.56 1.25,1.25S20.19,7.75 19.5,7.75z" />
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M19.4,10.79l-0.85,0.28c-0.1,-0.08 -0.21,-0.14 -0.33,-0.19L18.04,10h-1.07l-0.18,0.87c-0.12,0.05 -0.24,0.12 -0.34,0.19l-0.84,-0.28l-0.54,0.93l0.66,0.59c-0.01,0.13 -0.01,0.25 0,0.37l-0.66,0.61l0.54,0.93l0.86,-0.27c0.1,0.07 0.2,0.13 0.31,0.18L16.96,15h1.07l0.19,-0.87c0.11,-0.05 0.22,-0.11 0.32,-0.18l0.85,0.27l0.54,-0.93l-0.66,-0.61c0.01,-0.13 0.01,-0.25 0,-0.37l0.66,-0.59L19.4,10.79zM17.5,13.39c-0.49,0 -0.89,-0.4 -0.89,-0.89c0,-0.49 0.4,-0.89 0.89,-0.89s0.89,0.4 0.89,0.89C18.39,12.99 17.99,13.39 17.5,13.39z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_equalizer.xml b/app/src/main/res/drawable/ic_equalizer.xml
new file mode 100644
index 0000000..5ff5204
--- /dev/null
+++ b/app/src/main/res/drawable/ic_equalizer.xml
@@ -0,0 +1,3 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:tint="#7E7E7E" android:viewportWidth="24" android:viewportHeight="24">
+    <path android:fillColor="@android:color/white" android:pathData="M10 20h4V4h-4v16zm-6 0h4v-8H4v8zM16 9v11h4V9h-4z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_events.xml b/app/src/main/res/drawable/ic_events.xml
new file mode 100644
index 0000000..1e085de
--- /dev/null
+++ b/app/src/main/res/drawable/ic_events.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M19,5h-2V3H7v2H5C3.9,5 3,5.9 3,7v1c0,2.55 1.92,4.63 4.39,4.94c0.63,1.5 1.98,2.63 3.61,2.96V19H7v2h10v-2h-4v-3.1c1.63,-0.33 2.98,-1.46 3.61,-2.96C19.08,12.63 21,10.55 21,8V7C21,5.9 20.1,5 19,5zM5,8V7h2v3.82C5.84,10.4 5,9.3 5,8zM12,14c-1.65,0 -3,-1.35 -3,-3V5h6v6C15,12.65 13.65,14 12,14zM19,8c0,1.3 -0.84,2.4 -2,2.82V7h2V8z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_events_gold.xml b/app/src/main/res/drawable/ic_events_gold.xml
new file mode 100644
index 0000000..7db7a59
--- /dev/null
+++ b/app/src/main/res/drawable/ic_events_gold.xml
@@ -0,0 +1,21 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="#FFC107"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="#000000"
+        android:pathData="M5.7484,0C4.909,0.4048 3.9699,0.7895 3.4635,1.8877 3.1075,2.4815 3.0984,3.2545 2.8474,3.9012 3.3122,3.359 4.0318,2.8038 4.4418,2.1731 4.8992,1.3809 5.1318,0.6712 5.7484,0ZM18.2516,0c0.6166,0.6712 0.8493,1.3809 1.3067,2.1731 0.41,0.6308 1.1294,1.186 1.5943,1.7281C20.9016,3.2545 20.8925,2.4815 20.5365,1.8877 20.0301,0.7895 19.091,0.4048 18.2516,0ZM3.4844,1.4146C3.0566,1.5498 2.6763,1.8713 2.3764,2.3345 1.6854,3.2346 1.5669,4.4154 1.8533,5.8391 1.9518,6.2156 1.8117,6.5683 1.8316,6.9198 2.0346,6.4032 2.2645,5.9282 2.7115,5.5956 3.7081,4.8955 3.9823,4.5248 4.2373,3.8647 4.5371,3.0886 4.5929,2.9733 4.5995,2.2768 4.3103,3.2786 3.4374,3.6991 2.8474,4.3755 2.5442,4.7446 2.3013,5.1724 2.1631,5.6254L2.1419,5.6046C2.2145,5.2038 2.3604,4.8436 2.4444,4.4526 2.7078,3.3954 2.6537,2.1228 3.4844,1.4146ZM20.4876,1.4146c0.8307,0.7082 0.7766,1.9808 1.04,3.038 0.084,0.391 0.2299,0.7511 0.3025,1.1519l-0.0212,0.0208C21.6706,5.1724 21.4277,4.7446 21.1245,4.3755 20.5345,3.6991 19.6616,3.2786 19.3725,2.2768c0.0066,0.6965 0.0625,0.8118 0.3623,1.5879 0.255,0.66 0.529,1.0308 1.5257,1.7309 0.447,0.3326 0.6769,0.8075 0.8798,1.3242C22.1602,6.5683 22.0201,6.2156 22.1186,5.8391 22.405,4.4154 22.2865,3.2346 21.5955,2.3345 21.2956,1.8713 20.9154,1.5498 20.4876,1.4146ZM1.7114,3.3085c-0.0217,-0.0051 -0.0335,0.0158 -0.0503,0.0262C1.0818,4.2517 0.5761,4.9453 0.6011,6.1777 0.5231,7.6942 1.4002,9.0766 1.4882,10.5069 1.6212,9.412 2.3095,8.6385 2.879,7.7899 2.804,8.1525 2.7168,8.5038 2.5663,8.8193 1.9424,9.9924 1.3214,11.385 1.7515,12.886 1.7415,12.9018 1.7275,12.9227 1.7114,12.9119 1.5386,12.4159 1.509,11.8485 1.3881,11.3201 1.0992,9.9073 0.2186,8.7316 0.474,7.1031 0.3392,7.4564 0.2846,7.8552 0.2132,8.2405 0.0625,9.2361 -0.0178,9.8503 0.2035,10.7649 0.4725,11.912 1.6056,13.1775 2.0631,14.228l0.0625,0.0622C2.0589,13.7202 2.0249,13.1067 2.1547,12.5632 2.3518,11.6934 2.8936,10.8433 3.0453,9.9468 3.1244,9.2437 2.9481,8.4642 2.8814,7.7863 3.087,7.4797 3.2774,7.1634 3.4198,6.8182 3.6078,6.2973 3.529,5.5561 3.5853,4.9925 3.5096,5.2325 3.3793,5.44 3.2319,5.6443 2.5417,6.4252 1.7515,7.2268 1.6013,8.3586 1.5761,8.5194 1.568,8.6866 1.5303,8.8429 1.5007,8.8591 1.5007,8.817 1.4882,8.8068L1.5723,7.4034C1.6014,6.0326 1.0737,4.5128 1.7114,3.3085ZM22.2886,3.3085c0.6377,1.2042 0.1102,2.7241 0.1392,4.0948l0.084,1.4034c-0.0125,0.0102 -0.0124,0.0523 -0.042,0.0362 -0.0377,-0.1563 -0.0459,-0.3235 -0.0711,-0.4844C22.2485,7.2268 21.4583,6.4252 20.7681,5.6443 20.6207,5.44 20.4904,5.2325 20.4147,4.9925c0.0564,0.5636 -0.0223,1.3048 0.1657,1.8257 0.1424,0.3452 0.3327,0.6614 0.5382,0.9681 -0.0667,0.678 -0.2428,1.4574 -0.1637,2.1605 0.1517,0.8965 0.6935,1.7466 0.8906,2.6164 0.1297,0.5435 0.0956,1.1571 0.029,1.727l0.0625,-0.0622C22.3944,13.1775 23.5276,11.912 23.7966,10.7649 24.018,9.8503 23.9375,9.2361 23.7868,8.2405 23.7154,7.8552 23.6608,7.4564 23.526,7.1031c0.2554,1.6286 -0.6252,2.8043 -0.9141,4.217 -0.1209,0.5284 -0.1505,1.0958 -0.3233,1.5918 -0.0161,0.0109 -0.0301,-0.01 -0.0401,-0.0259 0.4301,-1.501 -0.1907,-2.8935 -0.8146,-4.0667 -0.1505,-0.3155 -0.2378,-0.6668 -0.3129,-1.0295 0.5695,0.8487 1.2578,1.6221 1.3909,2.717 0.088,-1.4303 0.9651,-2.8126 0.8871,-4.3292 0.025,-1.2324 -0.4806,-1.926 -1.0599,-2.8429 -0.0168,-0.0104 -0.0288,-0.0313 -0.0505,-0.0262zM2.7953,11.0137c0.1056,1.1638 -0.3008,2.1508 -0.2722,3.3188 0.0285,0.7055 0.2136,1.3308 0.5114,1.8936l-0.0505,0.0002C2.5817,15.5698 2.2675,14.8712 1.7974,14.246 1.0675,13.372 0.1364,12.4758 0.0406,11.2609 -0.0834,12.2755 0.0833,13.2266 0.4503,14.0674 0.8172,14.9082 1.3843,15.6386 2.061,16.2113l0.3153,0.2422c0.5579,0.4531 0.9973,0.6309 1.5334,1.1462v-0.0633C3.4991,16.6883 3.5518,15.6808 3.4863,14.652 3.4208,13.6233 3.2369,12.573 2.984,11.6393ZM21.2047,11.0137 L21.016,11.6393c-0.253,0.9337 -0.4366,1.984 -0.5022,3.0128 -0.0655,1.0288 -0.0128,2.0362 -0.4234,2.8843v0.0633c0.5361,-0.5153 0.9754,-0.6931 1.5332,-1.1462l0.3153,-0.2422c0.6767,-0.5727 1.2438,-1.3031 1.6107,-2.1439 0.3669,-0.8408 0.5336,-1.7919 0.4097,-2.8064 -0.0958,1.2149 -1.027,2.1111 -1.7568,2.9851 -0.4702,0.6252 -0.7843,1.3237 -1.1866,1.9802l-0.0505,-0.0002c0.2978,-0.5628 0.483,-1.1881 0.5115,-1.8936 0.0285,-1.168 -0.378,-2.155 -0.2724,-3.3188zM3.5004,14.1213c0.5128,1.2745 0.272,2.9326 0.9564,4.1181 0.188,0.3919 0.4985,0.7364 0.767,1.0391l-0.0125,0.0158C4.9684,19.1529 4.743,18.9508 4.5278,18.713 3.3584,17.5169 1.6144,17.3507 0.918,15.3892 1.0125,16.1089 1.1273,16.8312 1.5024,17.4358 2.7623,19.4588 4.1024,19.494 6.2178,20.0338 5.2496,19.0609 5.0897,17.61 4.6713,16.3248 4.39,15.5375 3.9663,14.8158 3.5004,14.1213ZM20.4996,14.1213c-0.4659,0.6945 -0.8896,1.4162 -1.1709,2.2035 -0.4184,1.2852 -0.5783,2.7361 -1.5465,3.709 2.188,-0.5087 3.4283,-0.5189 4.7154,-2.598 0.3751,-0.6046 0.4899,-1.327 0.5844,-2.0467 -0.6964,1.9616 -2.4403,2.1278 -3.6096,3.3239 -0.2152,0.2378 -0.4408,0.4399 -0.6837,0.5813l-0.0125,-0.0158c0.2685,-0.3026 0.579,-0.6472 0.767,-1.0391 0.6844,-1.1855 0.4436,-2.8436 0.9564,-4.1181zM5.1744,17.229c0.6329,1.0603 0.804,2.6309 1.9698,3.1431 0.1935,0.1273 0.4395,0.1128 0.6168,0.2528 -0.2227,0.042 -0.4149,-0.0632 -0.6249,-0.0993 -1.4873,-0.4168 -3.3117,0.1065 -4.4283,-1.4759 0.4238,1.0333 1.2928,1.8637 2.135,2.1955 1.2955,0.5497 2.6527,-0.0582 3.8743,-0.4293 0.1754,-0.0568 0.3897,-0.0359 0.5531,-0.1098C8.5902,20.5326 7.9449,20.2615 7.4087,19.676 6.6628,18.8013 5.9829,17.8988 5.1744,17.229ZM18.8256,17.229c-0.8085,0.6698 -1.4883,1.5723 -2.2342,2.447 -0.5361,0.5855 -1.1816,0.8566 -1.8616,1.0301 0.1634,0.0739 0.3777,0.053 0.5531,0.1098 1.2217,0.3711 2.5789,0.979 3.8745,0.4293 0.8423,-0.3318 1.7113,-1.1622 2.135,-2.1955 -1.1165,1.5824 -2.941,1.0591 -4.4283,1.4759 -0.2101,0.0361 -0.4024,0.1414 -0.6251,0.0993 0.1773,-0.14 0.4234,-0.1255 0.6169,-0.2528 1.1658,-0.5121 1.3368,-2.0828 1.9697,-3.1431zM13.3061,20.6618c-0.3586,-0.0002 -0.7166,0.0516 -1.0603,0.1619 -0.2182,0.2432 -0.4427,-0.0435 -0.6736,-0.049 -1.48,-0.4048 -2.7556,0.4321 -3.9833,1.092 -0.9149,0.4459 -2.1131,0.371 -2.9176,-0.3203 0.3981,0.6219 0.9306,1.0051 1.4804,1.2658 1.0222,0.5092 2.2388,0.1667 3.0846,-0.5663 0.7259,-0.5054 1.3501,-1.5007 2.3063,-1.1218 -0.9731,0.5607 -1.8627,1.3468 -2.6669,2.3573l0.5038,0.5187c0.5864,-0.9759 1.3664,-1.7724 2.1552,-2.3911 0.1711,-0.114 0.3515,-0.2556 0.5412,-0.2858 0.9689,0.591 1.8343,1.6013 2.6157,2.6506 0.1667,-0.1671 0.3437,-0.3483 0.4804,-0.5422 -0.8108,-0.9763 -1.6977,-1.7404 -2.6523,-2.2952 0.1586,-0.0567 0.3387,-0.0677 0.5253,-0.0629 0.5146,-0.0389 0.8652,0.4149 1.2631,0.7304 0.9827,0.9293 2.2958,1.5431 3.554,0.9617 0.564,-0.2501 1.1103,-0.6925 1.5101,-1.3109 -0.7777,0.6861 -1.9507,0.799 -2.8579,0.3796L15.5382,21.3453c-0.6511,-0.4315 -1.4432,-0.6831 -2.2321,-0.6835z"
+        android:strokeWidth="0.939045"
+        android:strokeColor="#00000000" />
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M17.8333,6.1667L16.1667,6.1667L16.1667,4.5L7.8333,4.5L7.8333,6.1667L6.1667,6.1667c-0.9167,0 -1.6667,0.75 -1.6667,1.6667v0.8333c0,2.125 1.6,3.8583 3.6583,4.1167 0.525,1.25 1.65,2.1917 3.0083,2.4667v2.5833L7.8333,17.8333L7.8333,19.5L16.1667,19.5L16.1667,17.8333L12.8333,17.8333L12.8333,15.25c1.3583,-0.275 2.4833,-1.2167 3.0083,-2.4667C17.9,12.525 19.5,10.7917 19.5,8.6667L19.5,7.8333C19.5,6.9167 18.75,6.1667 17.8333,6.1667ZM6.1667,8.6667L6.1667,7.8333L7.8333,7.8333L7.8333,11.0167C6.8667,10.6667 6.1667,9.75 6.1667,8.6667ZM12,13.6667c-1.375,0 -2.5,-1.125 -2.5,-2.5L9.5,6.1667L14.5,6.1667v5c0,1.375 -1.125,2.5 -2.5,2.5zM17.8333,8.6667c0,1.0833 -0.7,2 -1.6667,2.35L16.1667,7.8333h1.6667z"
+        android:strokeWidth="1" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="m11.7472,8.175c-0.0503,-0.0004 -0.0996,-0.0157 -0.1492,-0.0235 -0.0954,-0.0151 -0.1906,-0.0315 -0.2862,-0.0451 -0.0345,-0.0049 -0.069,-0.0102 -0.1035,-0.0151 -0.0108,-0.0015 -0.0304,-0.0008 -0.0356,-0.0128 -0.0082,-0.0188 0.0099,-0.0489 0.0182,-0.0646 0.0279,-0.0519 0.0591,-0.102 0.0903,-0.1519 0.081,-0.1299 0.1724,-0.2638 0.2894,-0.3629 0.077,-0.0652 0.1662,-0.104 0.2649,-0.0634 0.0398,0.0163 0.0757,0.0435 0.104,0.0761 0.0227,0.0261 0.0438,0.0563 0.0542,0.0899 0.0139,0.0443 0.0336,0.1222 0.0151,0.1674 -0.0168,0.041 -0.0621,0.0719 -0.0903,0.1054 -0.0481,0.057 -0.0866,0.1205 -0.1222,0.1859 -0.0167,0.0307 -0.0472,0.0749 -0.0455,0.1115 0.0011,0.0231 0.0694,0.0405 0.0878,0.0506 0.0645,0.0356 0.1239,0.0879 0.1543,0.1571 0.056,0.1274 -0.002,0.3115 -0.1056,0.3984 -0.0315,0.0264 -0.0707,0.0408 -0.1096,0.0518 -0.1087,0.0306 -0.2253,0.0009 -0.3349,-0.0038 -0.0042,0.0549 -0.014,0.1134 0.0023,0.1674 0.0298,0.0985 0.1497,0.1717 0.2504,0.1469 0.1002,-0.0246 0.1645,-0.1152 0.2347,-0.1838 0.0131,-0.0128 0.0334,-0.0128 0.0459,-0.0249 0.0154,-0.015 0.0206,-0.0492 0.0305,-0.0684 0.0153,-0.0296 0.0394,-0.0564 0.0586,-0.0837 0.1165,-0.166 0.2182,-0.3427 0.3158,-0.5207 0.0269,-0.049 0.0533,-0.0985 0.078,-0.1488 0.0085,-0.0174 0.0136,-0.0502 0.0267,-0.0635 0.0126,-0.0128 0.0401,-0.0121 0.0563,-0.0195 0.113,-0.0522 0.2308,-0.0312 0.3197,0.0582 0.0195,0.0196 0.0405,0.0391 0.0521,0.0651 0.0495,0.1116 -0.0026,0.2458 -0.0417,0.3502 -0.0685,0.1834 -0.1751,0.3538 -0.2844,0.5145 -0.0182,0.0268 -0.0208,0.0647 -0.0392,0.089 -0.0122,0.0161 -0.0369,0.0186 -0.0509,0.035 -0.0328,0.0384 -0.0629,0.0862 -0.1049,0.1151 -0.0822,0.0565 -0.223,0.0554 -0.3075,0.0079 -0.0819,-0.0461 -0.0982,-0.142 -0.1024,-0.2284 -0.0015,-0.0312 0.011,-0.0703 -0.0042,-0.0992m0.0365,-1.1746c0.0543,-0.0115 0.0997,-0.0425 0.1553,-0.0513 0.1169,-0.0185 0.2376,0.0519 0.2818,0.1629 0.0224,0.0562 0.0157,0.1148 0.0318,0.1705m0.0639,1.0661c0.0253,0.0358 0.0117,0.1027 0.0236,0.1457 0.0349,0.1267 0.1465,0.1793 0.2687,0.1529 0.1722,-0.0372 0.2966,-0.1979 0.3887,-0.3388 0.0213,-0.0326 0.0379,-0.0756 0.0638,-0.1042 0.0088,-0.0096 0.0311,-0.0235 0.0428,-0.0125 0.0181,0.017 0.0102,0.0806 0.0102,0.1043 0,0.1022 0.0009,0.2051 -0.0086,0.3068 -0.0182,0.195 -0.022,0.4176 -0.1285,0.5888 -0.0361,0.058 -0.0899,0.1069 -0.1399,0.1524 -0.0987,0.0897 -0.2038,0.1731 -0.3136,0.2484 -0.0483,0.0331 -0.1045,0.062 -0.1489,0.1 -0.0177,0.0151 -0.0125,0.0421 -0.0125,0.0632v0.1673c0,0.0164 0.0079,0.0641 -0.0071,0.0743 -0.015,0.0102 -0.0514,0.0032 -0.069,0.0032h-1.0413c-0.0189,0 -0.0591,0.0076 -0.0751,-0.0032 -0.0164,-0.0111 -0.0071,-0.0714 -0.0071,-0.0898 0,-0.0672 0.0161,-0.1749 -0.0268,-0.2293C11.1496,10.1614 10.9343,9.8796 10.7594,9.5728 10.6858,9.4437 10.596,9.2999 10.5816,9.1482 10.5533,8.8506 10.7095,8.5545 10.861,8.3115c0.037,-0.0594 0.0767,-0.1179 0.1189,-0.1736 0.0145,-0.0191 0.0302,-0.0502 0.0518,-0.0614 0.0345,-0.018 0.0963,0.0057 0.1339,0.0057m1.7538,0.3068c0.1344,-0.0282 0.2635,-0.0954 0.3897,0.0012 0.0214,0.0164 0.0393,0.0356 0.0551,0.0577 0.0192,0.0269 0.0317,0.055 0.0406,0.0868 0.0097,0.0348 0.019,0.0719 0.0167,0.1085 -0.0051,0.0811 -0.0466,0.1431 -0.0744,0.2169 -0.0099,0.0262 -0.0114,0.0563 -0.0167,0.0837M11.0591,8.7113c0.0825,0.0372 0.1745,0.0603 0.2618,0.0825 0.0377,0.0096 0.0883,0.0125 0.1218,0.0321m-0.0487,1.9277c-0.0367,0.0199 -0.1078,0.0063 -0.1492,0.0063 -0.012,0 -0.0434,-0.0057 -0.0517,0.005 -0.0054,0.007 -0.0031,0.0208 -0.0031,0.0291v0.4618c0,0.0089 -0.0026,0.0243 0.0071,0.0291 0.0281,0.0141 0.0895,0.0018 0.1208,0.0018h1.3945c0.0343,0 0.0824,0.009 0.1152,-0.001 0.0123,-0.0037 0.0096,-0.0198 0.0096,-0.03v-0.4618c0,-0.0084 0.0023,-0.0221 -0.0031,-0.0291 -0.0093,-0.0119 -0.0501,-0.0049 -0.0639,-0.0049 -0.0538,0 -0.125,0.0124 -0.1766,-0.0031m-0.9865,0.1253c0.0611,-0.0081 0.1271,-0.0013 0.1888,-0.0013h0.5632c0.0374,0 0.0793,-0.004 0.1157,0.0064 0.0252,0.007 0.0336,0.0288 0.0377,0.0525 0.0086,0.05 0.0071,0.117 0.001,0.1674 -0.0029,0.0236 -0.0079,0.0494 -0.0326,0.0588 -0.0245,0.0093 -0.0536,0.0063 -0.0792,0.0063H11.6376c-0.0264,0 -0.0575,0.0035 -0.0822,-0.0082 -0.0343,-0.0163 -0.0274,-0.0721 -0.0274,-0.1034 0,-0.0433 -0.0169,-0.135 0.0216,-0.1649 0.0163,-0.0127 0.0383,-0.011 0.0575,-0.0136m-0.1918,0.4078v0.3936l0.0031,0.0322 0.0639,0.0049h1.0443l0.0621,-0.0049 0.0049,-0.0322v-0.3936"
+        android:strokeWidth="0.15625"
+        android:strokeColor="#000000" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_extension.xml b/app/src/main/res/drawable/ic_extension.xml
new file mode 100644
index 0000000..2839222
--- /dev/null
+++ b/app/src/main/res/drawable/ic_extension.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="#000000"
+        android:pathData="M20.5 11H19V7c0-1.1-0.9-2-2-2h-4V3.5C13 2.12 11.88 1 10.5 1S8 2.12 8 3.5V5H4C2.9 5 2.01 5.9 2.01 7v3.8H3.5c1.49 0 2.7 1.21 2.7 2.7s-1.21 2.7-2.7 2.7H2V20c0 1.1 0.9 2 2 2h3.8v-1.5c0-1.49 1.21-2.7 2.7-2.7s2.7 1.21 2.7 2.7V22H17c1.1 0 2-0.9 2-2v-4h1.5c1.38 0 2.5-1.12 2.5-2.5S21.88 11 20.5 11z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_file_open.xml b/app/src/main/res/drawable/ic_file_open.xml
new file mode 100644
index 0000000..bd1899c
--- /dev/null
+++ b/app/src/main/res/drawable/ic_file_open.xml
@@ -0,0 +1,12 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M14,2H6C4.9,2 4,2.9 4,4v16c0,1.1 0.89,2 1.99,2H15v-8h5V8L14,2zM13,9V3.5L18.5,9H13zM17,21.66V16h5.66v2h-2.24l2.95,2.95l-1.41,1.41L19,19.41l0,2.24H17z" />
+
+</vector>
diff --git a/app/src/main/res/drawable/ic_file_upload.xml b/app/src/main/res/drawable/ic_file_upload.xml
new file mode 100644
index 0000000..4692e38
--- /dev/null
+++ b/app/src/main/res/drawable/ic_file_upload.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M9,16h6v-6h4l-7,-7 -7,7h4zM5,18h14v2L5,20z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_filter_1.xml b/app/src/main/res/drawable/ic_filter_1.xml
new file mode 100644
index 0000000..edb92ac
--- /dev/null
+++ b/app/src/main/res/drawable/ic_filter_1.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="#7E7E7E">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M3,5L1,5v16c0,1.1 0.9,2 2,2h16v-2L3,21L3,5zM14,15h2L16,5h-4v2h2v8zM21,1L7,1c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2L23,3c0,-1.1 -0.9,-2 -2,-2zM21,17L7,17L7,3h14v14z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_filter_2.xml b/app/src/main/res/drawable/ic_filter_2.xml
new file mode 100644
index 0000000..e2166a4
--- /dev/null
+++ b/app/src/main/res/drawable/ic_filter_2.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="#7E7E7E">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M3,5L1,5v16c0,1.1 0.9,2 2,2h16v-2L3,21L3,5zM21,1L7,1c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2L23,3c0,-1.1 -0.9,-2 -2,-2zM21,17L7,17L7,3h14v14zM17,13h-4v-2h2c1.1,0 2,-0.89 2,-2L17,7c0,-1.11 -0.9,-2 -2,-2h-4v2h4v2h-2c-1.1,0 -2,0.89 -2,2v4h6v-2z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_filter_3.xml b/app/src/main/res/drawable/ic_filter_3.xml
new file mode 100644
index 0000000..d18f833
--- /dev/null
+++ b/app/src/main/res/drawable/ic_filter_3.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="#7E7E7E">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M21,1L7,1c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2L23,3c0,-1.1 -0.9,-2 -2,-2zM21,17L7,17L7,3h14v14zM3,5L1,5v16c0,1.1 0.9,2 2,2h16v-2L3,21L3,5zM17,13v-1.5c0,-0.83 -0.67,-1.5 -1.5,-1.5 0.83,0 1.5,-0.67 1.5,-1.5L17,7c0,-1.11 -0.9,-2 -2,-2h-4v2h4v2h-2v2h2v2h-4v2h4c1.1,0 2,-0.89 2,-2z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_filter_9_plus.xml b/app/src/main/res/drawable/ic_filter_9_plus.xml
new file mode 100644
index 0000000..7f6b178
--- /dev/null
+++ b/app/src/main/res/drawable/ic_filter_9_plus.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="#000000"
+        android:pathData="M2 5C1.45 5 1 5.45 1 6v15c0 1.1 0.9 2 2 2h15c0.55 0 1-0.45 1-1s-0.45-1-1-1H4c-0.55 0-1-0.45-1-1V6c0-0.55-0.45-1-1-1zm12 7V8c0-1.1-0.9-2-2-2h-1C9.9 6 9 6.9 9 8v1c0 1.1 0.9 2 2 2h1v1h-2c-0.55 0-1 0.45-1 1s0.45 1 1 1h2c1.1 0 2-0.9 2-2zm-3-3V8h1v1h-1zm10-8H7C5.9 1 5 1.9 5 3v14c0 1.1 0.9 2 2 2h14c1.1 0 2-0.9 2-2V3c0-1.1-0.9-2-2-2zm-1 16H8c-0.55 0-1-0.45-1-1V4c0-0.55 0.45-1 1-1h12c0.55 0 1 0.45 1 1v12c0 0.55-0.45 1-1 1zm1-7c0-0.55-0.45-1-1-1h-1V8c0-0.55-0.45-1-1-1s-1 0.45-1 1v1h-1c-0.55 0-1 0.45-1 1s0.45 1 1 1h1v1c0 0.55 0.45 1 1 1s1-0.45 1-1v-1h1c0.55 0 1-0.45 1-1z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_filter_alt.xml b/app/src/main/res/drawable/ic_filter_alt.xml
new file mode 100644
index 0000000..4337957
--- /dev/null
+++ b/app/src/main/res/drawable/ic_filter_alt.xml
@@ -0,0 +1,3 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:tint="#7E7E7E" android:viewportWidth="24" android:viewportHeight="24">
+    <path android:fillColor="#000000" android:pathData="M4.25 5.61C6.57 8.59 10 13 10 13v5c0 1.1 0.9 2 2 2s2-0.9 2-2v-5s3.43-4.41 5.75-7.39C20.26 4.95 19.79 4 18.95 4H5.04c-0.83 0-1.3 0.95-0.79 1.61z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_filter_alt_plus.xml b/app/src/main/res/drawable/ic_filter_alt_plus.xml
new file mode 100644
index 0000000..ac5f393
--- /dev/null
+++ b/app/src/main/res/drawable/ic_filter_alt_plus.xml
@@ -0,0 +1,4 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
+    <path android:fillColor="@color/secondarytext" android:pathData="M5.04 4c-0.83 0-1.3 0.95-0.79 1.61C6.27 8.2 10 13 10 13v6c0 0.55 0.45 1 1 1h2c0.55 0 1-0.45 1-1v-6l0.648-0.836a6.098 6.098 0 0 1-1.472-1.354L11.99 12.3 7 6h4.979a6.098 6.098 0 0 1 0.728-2H5.039z"/>
+    <path android:pathData="M16.728 3.019v4.122h4.128V8.4h-4.128v4.123h-1.246V8.399h-4.128V7.141h4.128V3.02z" android:strokeWidth="0.420925" android:fillColor="@color/secondarytext"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_filter_b_and_w.xml b/app/src/main/res/drawable/ic_filter_b_and_w.xml
new file mode 100644
index 0000000..c0f83e1
--- /dev/null
+++ b/app/src/main/res/drawable/ic_filter_b_and_w.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="#7E7E7E">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M19,3L5,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2zM19,19l-7,-8v8L5,19l7,-8L12,5h7v14z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_firmware.xml b/app/src/main/res/drawable/ic_firmware.xml
new file mode 100644
index 0000000..a81445f
--- /dev/null
+++ b/app/src/main/res/drawable/ic_firmware.xml
@@ -0,0 +1,42 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="45sp"
+    android:height="45sp"
+    android:viewportWidth="30"
+    android:viewportHeight="30">
+    <path
+        android:fillColor="#1f7fdb"
+        android:pathData="M3.871 3.877h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.824a0.947 0.947 0 0 1 0.947-0.947z"
+        android:strokeWidth="3.57115173" />
+    <path
+        android:fillColor="#4dabf5"
+        android:pathData="M3.879 3.035h20.925a0.947 0.947 0 0 1 0.947 0.947v20.01a0.947 0.947 0 0 1-0.947 0.948H3.88a0.947 0.947 0 0 1-0.947-0.948V3.982A0.947 0.947 0 0 1 3.88 3.035z"
+        android:strokeWidth="3.57115173" />
+    <path
+        android:fillColor="#2196f3"
+        android:pathData="M3.871 3.413h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.36A0.947 0.947 0 0 1 3.87 3.413z"
+        android:strokeWidth="3.57115173" />
+    <path
+        android:fillColor="#ffffff"
+        android:pathData="M9.869 7.173h8.989a1.383 1.383 0 0 1 1.383 1.384V20.24a1.383 1.383 0 0 1-1.383 1.384h-8.99a1.383 1.383 0 0 1-1.383-1.384V8.557A1.383 1.383 0 0 1 9.87 7.173z"
+        android:strokeWidth="4.20063162" />
+    <path
+        android:fillColor="#2196f3"
+        android:pathData="M12.288 13.938h4.148a0.844 0.844 0 0 1 0.844 0.843v4.136a0.844 0.844 0 0 1-0.844 0.844h-4.148a0.844 0.844 0 0 1-0.844-0.844V14.78a0.844 0.844 0 0 1 0.844-0.843z"
+        android:strokeWidth="3.38216949" />
+    <path
+        android:fillColor="#ffffff"
+        android:pathData="M12.281 14.756h4.188v4.187H12.28z"
+        android:strokeWidth="5.90702915" />
+    <path
+        android:fillColor="#2196f3"
+        android:pathData="M13.063 15.553h2.59v2.703h-2.59z"
+        android:strokeWidth="3.58284044" />
+    <path
+        android:fillColor="#ffffff"
+        android:pathData="M13.945 16.49h0.814v0.844h-0.814z"
+        android:strokeWidth="3.37599993" />
+    <path
+        android:fillColor="#2196f3"
+        android:pathData="M10.734 15.568h0.672a0.172 0.172 0 0 1 0.172 0.172v0.625a0.172 0.172 0 0 1-0.172 0.172h-0.672a0.172 0.172 0 0 1-0.171-0.172V15.74a0.172 0.172 0 0 1 0.171-0.172zm0 1.719h0.672a0.172 0.172 0 0 1 0.172 0.172v0.625a0.172 0.172 0 0 1-0.172 0.172h-0.672a0.172 0.172 0 0 1-0.171-0.172v-0.625a0.172 0.172 0 0 1 0.171-0.172zm6.579 0h0.671a0.172 0.172 0 0 1 0.172 0.172v0.625a0.172 0.172 0 0 1-0.172 0.172h-0.672a0.172 0.172 0 0 1-0.171-0.172v-0.625a0.172 0.172 0 0 1 0.172-0.172zm0-1.719h0.671a0.172 0.172 0 0 1 0.172 0.172v0.625a0.172 0.172 0 0 1-0.172 0.172h-0.672a0.172 0.172 0 0 1-0.171-0.172V15.74a0.172 0.172 0 0 1 0.172-0.172zm-2.485-2.453H15.5a0.172 0.172 0 0 1 0.172 0.172v0.625a0.172 0.172 0 0 1-0.172 0.172h-0.672a0.172 0.172 0 0 1-0.172-0.172v-0.625a0.172 0.172 0 0 1 0.172-0.172zm-1.601 0h0.671a0.172 0.172 0 0 1 0.172 0.172v0.625a0.172 0.172 0 0 1-0.172 0.172h-0.671a0.172 0.172 0 0 1-0.172-0.172v-0.625a0.172 0.172 0 0 1 0.172-0.172zm0 6.504h0.671a0.172 0.172 0 0 1 0.172 0.172v0.625a0.172 0.172 0 0 1-0.172 0.172h-0.671a0.172 0.172 0 0 1-0.172-0.172V19.79a0.172 0.172 0 0 1 0.172-0.172zm1.589 0h0.672a0.172 0.172 0 0 1 0.172 0.172v0.625a0.172 0.172 0 0 1-0.172 0.172h-0.672a0.172 0.172 0 0 1-0.171-0.172V19.79a0.172 0.172 0 0 1 0.171-0.172zm5.451-7.929c-5.543-5.552 0.98 0.98-4.564-4.571l4.571 0.002-0.007 4.569zm-5.187-3.5l4.033 4.066-4.026 0.012-0.007-4.077z"
+        android:strokeWidth="3.5" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_focus.xml b/app/src/main/res/drawable/ic_focus.xml
new file mode 100644
index 0000000..02d1080
--- /dev/null
+++ b/app/src/main/res/drawable/ic_focus.xml
@@ -0,0 +1,12 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M5,15L3,15v4c0,1.1 0.9,2 2,2h4v-2L5,19v-4zM5,5h4L9,3L5,3c-1.1,0 -2,0.9 -2,2v4h2L5,5zM19,3h-4v2h4v4h2L21,5c0,-1.1 -0.9,-2 -2,-2zM19,19h-4v2h4c1.1,0 2,-0.9 2,-2v-4h-2v4zM12,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3 3,-1.34 3,-3 -1.34,-3 -3,-3z" />
+
+</vector>
diff --git a/app/src/main/res/drawable/ic_folder.xml b/app/src/main/res/drawable/ic_folder.xml
new file mode 100644
index 0000000..d77293c
--- /dev/null
+++ b/app/src/main/res/drawable/ic_folder.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M9.17,6l2,2H20v10H4V6h5.17M10,4H4c-1.1,0 -1.99,0.9 -1.99,2L2,18c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V8c0,-1.1 -0.9,-2 -2,-2h-8l-2,-2z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_folder_delete.xml b/app/src/main/res/drawable/ic_folder_delete.xml
new file mode 100644
index 0000000..96a8fda
--- /dev/null
+++ b/app/src/main/res/drawable/ic_folder_delete.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M16.5,10V9h-2v1H12v1.5h1v4c0,0.83 0.67,1.5 1.5,1.5h2c0.83,0 1.5,-0.67 1.5,-1.5v-4h1V10H16.5zM16.5,15.5h-2v-4h2V15.5zM20,6h-8l-2,-2H4C2.89,4 2.01,4.89 2.01,6L2,18c0,1.11 0.89,2 2,2h16c1.11,0 2,-0.89 2,-2V8C22,6.89 21.11,6 20,6zM20,18H4V6h5.17l2,2H20V18z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_font_download.xml b/app/src/main/res/drawable/ic_font_download.xml
new file mode 100644
index 0000000..9151be9
--- /dev/null
+++ b/app/src/main/res/drawable/ic_font_download.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="#7E7E7E">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M9.17,15.5h5.64l1.14,3h2.09l-5.11,-13h-1.86l-5.11,13h2.09l1.12,-3zM12,7.98l2.07,5.52L9.93,13.5L12,7.98zM20,2L4,2c-1.1,0 -2,0.9 -2,2v16c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2zM20,20L4,20L4,4h16v16z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_font_size.xml b/app/src/main/res/drawable/ic_font_size.xml
new file mode 100644
index 0000000..d8bce6c
--- /dev/null
+++ b/app/src/main/res/drawable/ic_font_size.xml
@@ -0,0 +1,5 @@
+<vector android:height="24dp" android:tint="#7E7E7E"
+    android:viewportHeight="24" android:viewportWidth="24"
+    android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
+    <path android:fillColor="@android:color/white" android:pathData="M9,4v3h5v12h3L17,7h5L22,4L9,4zM3,12h3v7h3v-7h3L12,9L3,9v3z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_galaxy_buds_l.xml b/app/src/main/res/drawable/ic_galaxy_buds_l.xml
new file mode 100644
index 0000000..627b7d3
--- /dev/null
+++ b/app/src/main/res/drawable/ic_galaxy_buds_l.xml
@@ -0,0 +1,25 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="22.722658dp"
+    android:height="24.000002dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="22.722658"
+    android:viewportHeight="24.000002">
+    <path
+        android:pathData="M12.986 2.187c-1.36 0.36-2.206 2.24-3.252 3.118-1.398 1.174-2.96 1.818-3.882 3.504-0.7 1.282-0.509 3.114-1.253 4.242-0.39 0.59-1.165 0.864-1.604 1.45-1.37 1.82-1.22 4.57 0.426 6.17 1.085 1.056 2.793 1.489 4.26 1.148 0.625-0.145 1.228-0.668 1.882-0.614 1.53 0.126 2.478 0.878 4.107 0.45 5.752-1.508 7.906-8.281 6.633-13.536-0.674-2.779-4.038-6.8-7.317-5.932"
+        android:strokeWidth="1"
+        android:strokeColor="#000000" />
+    <path
+        android:fillType="evenOdd"
+        android:pathData="M5.819 15.742c-1.265 1.771-0.356 4.505 3.148 4.507M9.04 8.214c2.37-0.96 4.06-0.924 6.241-0.293 1.021 0.296 1.823 1.507 1.905 2.575 0.382 4.922-2.582 7.322-5.57 7.105-1.83-0.133-3.673-2.46-4.115-4.491C7.13 11.4 7.427 8.865 9.04 8.214z"
+        android:strokeWidth="1"
+        android:strokeColor="#000000" />
+    <path
+        android:pathData="M11.103 5.93c3.143-0.456 6.362 0.89 7.67 3.913 1.755 4.051-0.774 10.63-5.445 11.326C4.73 22.448 1.938 7.26 11.103 5.929"
+        android:strokeWidth="1"
+        android:strokeColor="#000000" />
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M10.908 9.638h0.696v4.558h2.505v0.586h-3.2z"
+        android:strokeWidth="0.264583"
+        android:strokeColor="#000000" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_galaxy_buds_live_l.xml b/app/src/main/res/drawable/ic_galaxy_buds_live_l.xml
new file mode 100644
index 0000000..388a6f5
--- /dev/null
+++ b/app/src/main/res/drawable/ic_galaxy_buds_live_l.xml
@@ -0,0 +1,16 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M11.25 9.428h0.696v4.558h2.505v0.586H11.25z"
+        android:strokeWidth="0.5"
+        android:strokeColor="#000000" />
+    <path
+        android:pathData="M14.534 20.549c-1.517 1.318-3.595 1.726-5.2 1.02-1.705-0.752-2.88-2.544-2.9-4.428-0.013-1.096 0.245-1.888 0.963-2.959 0.616-0.918 0.765-1.37 0.742-2.24-0.019-0.748-0.207-1.219-0.842-2.112C6.694 8.98 6.44 8.362 6.333 7.473 6.14 5.867 6.757 4.313 8.046 3.165c1.63-1.452 4.047-1.464 5.993-0.028 2.256 1.665 3.601 4.863 3.66 8.7 0.035 2.344-0.38 4.416-1.23 6.132-0.327 0.658-0.561 1.03-1.01 1.601-0.41 0.522-0.449 0.564-0.926 0.978z"
+        android:strokeWidth="2"
+        android:strokeColor="#000000" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_galaxy_buds_live_r.xml b/app/src/main/res/drawable/ic_galaxy_buds_live_r.xml
new file mode 100644
index 0000000..aa42550
--- /dev/null
+++ b/app/src/main/res/drawable/ic_galaxy_buds_live_r.xml
@@ -0,0 +1,16 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M12.19 12.16q0.223 0.076 0.433 0.324 0.214 0.248 0.427 0.682l0.707 1.406h-0.748l-0.658-1.32q-0.255-0.517-0.496-0.685-0.238-0.17-0.651-0.17h-0.758v2.175H9.75V9.428h1.571q0.882 0 1.316 0.369 0.434 0.368 0.434 1.113 0 0.485-0.227 0.806-0.224 0.32-0.655 0.444zM10.445 10v1.826h0.875q0.503 0 0.758-0.23 0.258-0.235 0.258-0.686 0-0.452-0.258-0.68Q11.824 10 11.32 10z"
+        android:strokeWidth="0.5"
+        android:strokeColor="#000000" />
+    <path
+        android:pathData="M9.466 20.549c1.517 1.318 3.595 1.726 5.2 1.02 1.705-0.752 2.88-2.544 2.9-4.428 0.013-1.096-0.245-1.888-0.963-2.959-0.616-0.918-0.765-1.37-0.743-2.24 0.02-0.748 0.208-1.219 0.843-2.112 0.604-0.85 0.857-1.468 0.964-2.357 0.193-1.606-0.424-3.16-1.713-4.308-1.63-1.452-4.047-1.464-5.993-0.028C7.705 4.802 6.36 8 6.301 11.837c-0.035 2.344 0.38 4.416 1.23 6.132 0.327 0.658 0.561 1.03 1.01 1.601 0.41 0.522 0.449 0.564 0.925 0.978z"
+        android:strokeWidth="2"
+        android:strokeColor="#000000" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_galaxy_buds_r.xml b/app/src/main/res/drawable/ic_galaxy_buds_r.xml
new file mode 100644
index 0000000..5901fa8
--- /dev/null
+++ b/app/src/main/res/drawable/ic_galaxy_buds_r.xml
@@ -0,0 +1,25 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="22.722673dp"
+    android:height="24.000002dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="22.722673"
+    android:viewportHeight="24.000002">
+    <path
+        android:fillType="evenOdd"
+        android:pathData="M16.904 15.742c1.265 1.771 0.356 4.505-3.148 4.507M13.684 8.214C11.312 7.254 9.622 7.29 7.442 7.92 6.42 8.217 5.619 9.428 5.536 10.496c-0.381 4.922 2.582 7.322 5.57 7.105 1.831-0.133 3.673-2.46 4.115-4.491 0.372-1.71 0.075-4.245-1.537-4.896z"
+        android:strokeWidth="1"
+        android:strokeColor="#000000" />
+    <path
+        android:pathData="M11.62 5.93C8.475 5.474 5.257 6.82 3.948 9.843c-1.755 4.051 0.775 10.63 5.446 11.326 8.597 1.279 11.389-13.909 2.224-15.24"
+        android:strokeWidth="1"
+        android:strokeColor="#000000" />
+    <path
+        android:pathData="M9.737 2.187c1.36 0.36 2.205 2.24 3.251 3.118 1.399 1.174 2.96 1.818 3.882 3.504 0.701 1.282 0.51 3.114 1.254 4.242 0.39 0.59 1.164 0.864 1.604 1.449 1.37 1.822 1.219 4.571-0.426 6.171-1.085 1.055-2.794 1.489-4.26 1.148-0.625-0.145-1.228-0.668-1.883-0.614-1.529 0.126-2.478 0.877-4.106 0.45C3.3 20.146 1.146 13.374 2.42 8.12c0.673-2.78 4.038-6.8 7.317-5.932"
+        android:strokeWidth="1"
+        android:strokeColor="#000000" />
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M11.056 12.459q0.224 0.075 0.434 0.323 0.214 0.248 0.428 0.683l0.706 1.405h-0.748l-0.658-1.32q-0.255-0.516-0.496-0.685-0.238-0.169-0.651-0.169H9.313v2.174H8.617V9.727h1.571q0.882 0 1.316 0.368 0.434 0.369 0.434 1.113 0 0.486-0.227 0.806-0.224 0.32-0.655 0.445zm-1.743-2.16v1.825h0.875q0.503 0 0.758-0.23 0.258-0.235 0.258-0.686 0-0.451-0.258-0.679-0.255-0.23-0.758-0.23z"
+        android:strokeWidth="0.264583"
+        android:strokeColor="#000000" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_gps_edit.xml b/app/src/main/res/drawable/ic_gps_edit.xml
new file mode 100644
index 0000000..6dcd2c8
--- /dev/null
+++ b/app/src/main/res/drawable/ic_gps_edit.xml
@@ -0,0 +1,13 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="23.999998"
+    android:viewportHeight="24">
+  <path
+      android:pathData="M11,1L11,3.0605C6.83,3.5205 3.5205,6.83 3.0605,11L1,11L1,13L3.0605,13C3.1662,13.9581 3.4281,14.8678 3.8105,15.7109L5.3477,14.1719C5.125,13.488 5,12.7588 5,12C5,8.13 8.13,5 12,5C12.758,5 13.4867,5.1235 14.1699,5.3457L15.707,3.8086C14.8649,3.4271 13.9566,3.1661 13,3.0605L13,1L11,1zM20.1875,8.2852L18.6504,9.8203C18.8747,10.5064 19,11.2383 19,12C19,15.87 15.87,19 12,19C11.2355,19 10.5008,18.8743 9.8125,18.6484L8.2754,20.1836C9.1223,20.5695 10.0367,20.8332 11,20.9395L11,23L13,23L13,20.9395C17.17,20.4795 20.4795,17.17 20.9395,13L23,13L23,11L20.9395,11C20.8336,10.0404 20.571,9.1294 20.1875,8.2852z"
+      android:fillColor="#000000"/>
+  <path
+      android:pathData="m14.0588,9.02 l0.92,0.92 -9.06,9.06h-0.92v-0.92L14.0588,9.02m3.6,-6.02c-0.25,0 -0.51,0.1 -0.7,0.29l-1.83,1.83 3.75,3.75 1.83,-1.83c0.39,-0.39 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.2,-0.2 -0.45,-0.29 -0.71,-0.29zM14.0588,6.19L2.9987,17.25v3.75h3.75L17.8088,9.94Z"
+      android:fillColor="#000000"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_gps_location.xml b/app/src/main/res/drawable/ic_gps_location.xml
new file mode 100644
index 0000000..8173d3d
--- /dev/null
+++ b/app/src/main/res/drawable/ic_gps_location.xml
@@ -0,0 +1,5 @@
+<vector android:height="24dp" android:tint="#7E7E7E"
+    android:viewportHeight="24" android:viewportWidth="24"
+    android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
+    <path android:fillColor="@android:color/white" android:pathData="M12,8c-2.21,0 -4,1.79 -4,4s1.79,4 4,4 4,-1.79 4,-4 -1.79,-4 -4,-4zM20.94,11c-0.46,-4.17 -3.77,-7.48 -7.94,-7.94L13,1h-2v2.06C6.83,3.52 3.52,6.83 3.06,11L1,11v2h2.06c0.46,4.17 3.77,7.48 7.94,7.94L11,23h2v-2.06c4.17,-0.46 7.48,-3.77 7.94,-7.94L23,13v-2h-2.06zM12,19c-3.87,0 -7,-3.13 -7,-7s3.13,-7 7,-7 7,3.13 7,7 -3.13,7 -7,7z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_graphic_eq.xml b/app/src/main/res/drawable/ic_graphic_eq.xml
new file mode 100644
index 0000000..c650a08
--- /dev/null
+++ b/app/src/main/res/drawable/ic_graphic_eq.xml
@@ -0,0 +1,3 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:tint="#7E7E7E" android:viewportWidth="24" android:viewportHeight="24">
+    <path android:fillColor="@android:color/white" android:pathData="M7 18h2V6H7v12zm4 4h2V2h-2v20zm-8-8h2v-4H3v4zm12 4h2V6h-2v12zm4-8v4h2v-4h-2z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_health.xml b/app/src/main/res/drawable/ic_health.xml
new file mode 100644
index 0000000..1282ee3
--- /dev/null
+++ b/app/src/main/res/drawable/ic_health.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M10.5,13H8v-3h2.5V7.5h3V10H16v3h-2.5v2.5h-3V13zM12,2L4,5v6.09c0,5.05 3.41,9.76 8,10.91c4.59,-1.15 8,-5.86 8,-10.91V5L12,2z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_hearing.xml b/app/src/main/res/drawable/ic_hearing.xml
new file mode 100644
index 0000000..7a01672
--- /dev/null
+++ b/app/src/main/res/drawable/ic_hearing.xml
@@ -0,0 +1,3 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:tint="#7E7E7E" android:viewportWidth="24" android:viewportHeight="24">
+    <path android:fillColor="@android:color/white" android:pathData="M17 20c-0.29 0-0.56-0.06-0.76-0.15-0.71-0.37-1.21-0.88-1.71-2.38-0.51-1.56-1.47-2.29-2.39-3-0.79-0.61-1.61-1.24-2.32-2.53C9.29 10.98 9 9.93 9 9c0-2.8 2.2-5 5-5s5 2.2 5 5h2c0-3.93-3.07-7-7-7S7 5.07 7 9c0 1.26 0.38 2.65 1.07 3.9 0.91 1.65 1.98 2.48 2.85 3.15 0.81 0.62 1.39 1.07 1.71 2.05 0.6 1.82 1.37 2.84 2.73 3.55C15.87 21.88 16.43 22 17 22c2.21 0 4-1.79 4-4h-2c0 1.1-0.9 2-2 2zM7.64 2.64L6.22 1.22C4.23 3.21 3 5.96 3 9s1.23 5.79 3.22 7.78l1.41-1.41C6.01 13.74 5 11.49 5 9s1.01-4.74 2.64-6.36zM11.5 9c0 1.38 1.12 2.5 2.5 2.5s2.5-1.12 2.5-2.5-1.12-2.5-2.5-2.5-2.5 1.12-2.5 2.5z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_heart.xml b/app/src/main/res/drawable/ic_heart.xml
new file mode 100644
index 0000000..aa23797
--- /dev/null
+++ b/app/src/main/res/drawable/ic_heart.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:tint="#7E7E7E"
+    android:viewportHeight="24">
+  <path
+      android:fillColor="#FF000000"
+      android:pathData="M16.5,3c-1.74,0 -3.41,0.81 -4.5,2.09C10.91,3.81 9.24,3 7.5,3 4.42,3 2,5.42 2,8.5c0,3.78 3.4,6.86 8.55,11.54L12,21.35l1.45,-1.32C18.6,15.36 22,12.28 22,8.5 22,5.42 19.58,3 16.5,3zM12.1,18.55l-0.1,0.1 -0.1,-0.1C7.14,14.24 4,11.39 4,8.5 4,6.5 5.5,5 7.5,5c1.54,0 3.04,0.99 3.57,2.36h1.87C13.46,5.99 14.96,5 16.5,5c2,0 3.5,1.5 3.5,3.5 0,2.89 -3.14,5.74 -7.9,10.05z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_heartrate.xml b/app/src/main/res/drawable/ic_heartrate.xml
new file mode 100644
index 0000000..74a29cf
--- /dev/null
+++ b/app/src/main/res/drawable/ic_heartrate.xml
@@ -0,0 +1,4 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:tint="#7E7E7E" android:viewportHeight="24">
+    <path android:fillColor="#FF000000" android:pathData="M16.5 3c-1.74 0-3.41 0.81-4.5 2.09C10.91 3.81 9.24 3 7.5 3 4.42 3 2 5.42 2 8.5c0 3.78 3.4 6.86 8.55 11.54L12 21.35l1.45-1.32C18.6 15.36 22 12.28 22 8.5 22 5.42 19.58 3 16.5 3zm-4.4 15.55l-0.1 0.1-0.1-0.1C7.14 14.24 4 11.39 4 8.5 4 6.5 5.5 5 7.5 5c1.54 0 3.04 0.99 3.57 2.36h1.87C13.46 5.99 14.96 5 16.5 5c2 0 3.5 1.5 3.5 3.5 0 2.89-3.14 5.74-7.9 10.05z"/>
+    <path android:pathData="M4.41 12.148h3.56l2.084-5.03 1.932 5.49 1.823-2.727h6.49L19.81 11.27h-5.263l-2.939 4.509-1.602-4.546-0.95 2.403H5.55z" android:strokeWidth="1" android:fillColor="#FF000000"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_help_outline.xml b/app/src/main/res/drawable/ic_help_outline.xml
new file mode 100644
index 0000000..62950e4
--- /dev/null
+++ b/app/src/main/res/drawable/ic_help_outline.xml
@@ -0,0 +1,11 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:autoMirrored="true"
+    android:tint="#7E7E7E"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M11,18h2v-2h-2v2zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM12,6c-2.21,0 -4,1.79 -4,4h2c0,-1.1 0.9,-2 2,-2s2,0.9 2,2c0,2 -3,1.75 -3,5h2c0,-2.25 3,-2.5 3,-5 0,-2.21 -1.79,-4 -4,-4z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_history.xml b/app/src/main/res/drawable/ic_history.xml
new file mode 100644
index 0000000..3f533c0
--- /dev/null
+++ b/app/src/main/res/drawable/ic_history.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="#000000"
+        android:pathData="M13.26 3C8.17 2.86 4 6.95 4 12H2.21c-0.45 0-0.67 0.54-0.35 0.85l2.79 2.8c0.2 0.2 0.51 0.2 0.71 0l2.79-2.8C8.46 12.54 8.24 12 7.79 12H6c0-3.9 3.18-7.05 7.1-7 3.72 0.05 6.85 3.18 6.9 6.9 0.05 3.91-3.1 7.1-7 7.1-1.61 0-3.1-0.55-4.28-1.48-0.4-0.31-0.96-0.28-1.32 0.08-0.42 0.42-0.39 1.13 0.08 1.49C9 20.29 10.91 21 13 21c5.05 0 9.14-4.17 9-9.26-0.13-4.69-4.05-8.61-8.74-8.74zm-0.51 5C12.34 8 12 8.34 12 8.75v3.68c0 0.35 0.19 0.68 0.49 0.86l3.12 1.85c0.36 0.21 0.82 0.09 1.03-0.26 0.21-0.36 0.09-0.82-0.26-1.03l-2.88-1.71v-3.4c0-0.4-0.34-0.74-0.75-0.74z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_horizontal_rule.xml b/app/src/main/res/drawable/ic_horizontal_rule.xml
new file mode 100644
index 0000000..8986510
--- /dev/null
+++ b/app/src/main/res/drawable/ic_horizontal_rule.xml
@@ -0,0 +1,11 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="@android:color/white"
+        android:fillType="evenOdd"
+        android:pathData="M4,11h16v2h-16z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_hourglass_empty.xml b/app/src/main/res/drawable/ic_hourglass_empty.xml
new file mode 100644
index 0000000..465fcb8
--- /dev/null
+++ b/app/src/main/res/drawable/ic_hourglass_empty.xml
@@ -0,0 +1,5 @@
+<vector android:height="24dp" android:tint="#7E7E7E"
+    android:viewportHeight="24" android:viewportWidth="24"
+    android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
+    <path android:fillColor="@android:color/white" android:pathData="M6,2v6h0.01L6,8.01 10,12l-4,4 0.01,0.01L6,16.01L6,22h12v-5.99h-0.01L18,16l-4,-4 4,-3.99 -0.01,-0.01L18,8L18,2L6,2zM16,16.5L16,20L8,20v-3.5l4,-4 4,4zM12,11.5l-4,-4L8,4h8v3.5l-4,4z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_humidity_mid.xml b/app/src/main/res/drawable/ic_humidity_mid.xml
new file mode 100644
index 0000000..fcfd698
--- /dev/null
+++ b/app/src/main/res/drawable/ic_humidity_mid.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="960"
+    android:viewportHeight="960">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M480,860Q347,860 253.5,768Q160,676 160,544Q160,481 184.5,423.5Q209,366 254,322L480,100L706,322Q751,366 775.5,423.5Q800,481 800,544Q800,676 706.5,768Q613,860 480,860ZM240,544L720,544Q720,497 702,454.5Q684,412 650,380L480,212L310,380Q276,412 258,454.5Q240,497 240,544Z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_info.xml b/app/src/main/res/drawable/ic_info.xml
new file mode 100644
index 0000000..7cc3a6a
--- /dev/null
+++ b/app/src/main/res/drawable/ic_info.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="#000000"
+        android:pathData="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 15c-0.55 0-1-0.45-1-1v-4c0-0.55 0.45-1 1-1s1 0.45 1 1v4c0 0.55-0.45 1-1 1zm1-8h-2V7h2v2z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_insights.xml b/app/src/main/res/drawable/ic_insights.xml
new file mode 100644
index 0000000..698f809
--- /dev/null
+++ b/app/src/main/res/drawable/ic_insights.xml
@@ -0,0 +1,4 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24" android:tint="?attr/colorControlNormal">
+    <path android:fillColor="@android:color/white" android:pathData="M21 8c-1.45 0-2.26 1.44-1.93 2.51l-3.55 3.56c-0.3-0.09-0.74-0.09-1.04 0l-2.55-2.55C12.27 10.45 11.46 9 10 9c-1.45 0-2.27 1.44-1.93 2.52l-4.56 4.55C2.44 15.74 1 16.55 1 18c0 1.1 0.9 2 2 2 1.45 0 2.26-1.44 1.93-2.51l4.55-4.56c0.3 0.09 0.74 0.09 1.04 0l2.55 2.55C12.73 16.55 13.54 18 15 18c1.45 0 2.27-1.44 1.93-2.52l3.56-3.55C21.56 12.26 23 11.45 23 10c0-1.1-0.9-2-2-2z"/>
+    <path android:fillColor="@android:color/white" android:pathData="M15 9l0.94-2.07L18 6l-2.06-0.93L15 3l-0.92 2.07L12 6l2.08 0.93zM3.5 11L4 9l2-0.5L4 8 3.5 6 3 8 1 8.5 3 9z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_intensity.xml b/app/src/main/res/drawable/ic_intensity.xml
new file mode 100644
index 0000000..39bc7d9
--- /dev/null
+++ b/app/src/main/res/drawable/ic_intensity.xml
@@ -0,0 +1,3 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:tint="#7E7E7E" android:viewportWidth="24" android:viewportHeight="24">
+    <path android:fillColor="@android:color/white" android:pathData="M11.57 13.16c-1.36 0.28-2.17 1.16-2.17 2.41 0 1.34 1.11 2.42 2.49 2.42 2.05 0 3.71-1.66 3.71-3.71 0-1.07-0.15-2.12-0.46-3.12-0.79 1.07-2.2 1.72-3.57 2zM13.5 0.67s0.74 2.65 0.74 4.8c0 2.06-1.35 3.73-3.41 3.73-2.07 0-3.63-1.67-3.63-3.73l0.03-0.36C5.21 7.51 4 10.62 4 14c0 4.42 3.58 8 8 8s8-3.58 8-8c0-5.39-2.59-10.2-6.5-13.33zM12 20c-3.31 0-6-2.69-6-6 0-1.53 0.3-3.04 0.86-4.43 1.01 1.01 2.41 1.63 3.97 1.63 2.66 0 4.75-1.83 5.28-4.43C17.34 8.97 18 11.44 18 14c0 3.31-2.69 6-6 6z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_intensity_hollow.xml b/app/src/main/res/drawable/ic_intensity_hollow.xml
new file mode 100644
index 0000000..4789bf0
--- /dev/null
+++ b/app/src/main/res/drawable/ic_intensity_hollow.xml
@@ -0,0 +1,3 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:tint="#7E7E7E" android:viewportHeight="24">
+    <path android:fillColor="#FF000000" android:pathData="M13.5 0.67s0.74 2.65 0.74 4.8c0 2.06-1.35 3.73-3.41 3.73-2.07 0-3.63-1.67-3.63-3.73l0.03-0.36C5.21 7.51 4 10.62 4 14c0 4.42 3.58 8 8 8s8-3.58 8-8c0-5.39-2.59-10.2-6.5-13.33zM12 20c-3.31 0-6-2.69-6-6 0-1.53 0.3-3.04 0.86-4.43 1.01 1.01 2.41 1.63 3.97 1.63 2.66 0 4.75-1.83 5.28-4.43C17.34 8.97 18 11.44 18 14c0 3.31-2.69 6-6 6z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_invert_colors.xml b/app/src/main/res/drawable/ic_invert_colors.xml
new file mode 100644
index 0000000..c446a46
--- /dev/null
+++ b/app/src/main/res/drawable/ic_invert_colors.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="#7E7E7E">
+  <path
+      android:fillColor="#FF000000"
+      android:pathData="M17.66,7.93L12,2.27 6.34,7.93c-3.12,3.12 -3.12,8.19 0,11.31C7.9,20.8 9.95,21.58 12,21.58c2.05,0 4.1,-0.78 5.66,-2.34 3.12,-3.12 3.12,-8.19 0,-11.31zM12,19.59c-1.6,0 -3.11,-0.62 -4.24,-1.76C6.62,16.69 6,15.19 6,13.59s0.62,-3.11 1.76,-4.24L12,5.1v14.49z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_language.xml b/app/src/main/res/drawable/ic_language.xml
new file mode 100644
index 0000000..e9a732e
--- /dev/null
+++ b/app/src/main/res/drawable/ic_language.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="#000000"
+        android:pathData="M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zm6.93 6h-2.95c-0.32-1.25-0.78-2.45-1.38-3.56 1.84 0.63 3.37 1.91 4.33 3.56zM12 4.04c0.83 1.2 1.48 2.53 1.91 3.96h-3.82c0.43-1.43 1.08-2.76 1.91-3.96zM4.26 14C4.1 13.36 4 12.69 4 12s0.1-1.36 0.26-2h3.38c-0.08 0.66-0.14 1.32-0.14 2s0.06 1.34 0.14 2H4.26zm0.82 2h2.95c0.32 1.25 0.78 2.45 1.38 3.56-1.84-0.63-3.37-1.9-4.33-3.56zm2.95-8H5.08c0.96-1.66 2.49-2.93 4.33-3.56C8.81 5.55 8.35 6.75 8.03 8zM12 19.96c-0.83-1.2-1.48-2.53-1.91-3.96h3.82c-0.43 1.43-1.08 2.76-1.91 3.96zM14.34 14H9.66c-0.09-0.66-0.16-1.32-0.16-2s0.07-1.35 0.16-2h4.68c0.09 0.65 0.16 1.32 0.16 2s-0.07 1.34-0.16 2zm0.25 5.56c0.6-1.11 1.06-2.31 1.38-3.56h2.95c-0.96 1.65-2.49 2.93-4.33 3.56zM16.36 14c0.08-0.66 0.14-1.32 0.14-2s-0.06-1.34-0.14-2h3.38C19.9 10.64 20 11.31 20 12s-0.1 1.36-0.26 2h-3.38z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml
new file mode 100644
index 0000000..1688a25
--- /dev/null
+++ b/app/src/main/res/drawable/ic_launcher_foreground.xml
@@ -0,0 +1,21 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="108dp"
+    android:height="108dp"
+    android:viewportWidth="125"
+    android:viewportHeight="125">
+    <path
+        android:fillColor="#ffffff"
+        android:pathData="M72.12 87.5l-0.43-10.38c-0.15-7.29 6-13.48 7.64-18.93-1.74 1.66-5.35 9.52-9.1 8.86-2.09-0.7-2.75-1.69-3.6-3.47-2.81 0.55-5.04-1.11-5.85-3.11l0.16 8.09-3.66-10.5c-2.86-0.93-5.48-5.32-2.85-4.81l4.72 0.9c3.23 0.68 5.05-3.69 2.16-5.96-0.62-0.44-6.79-3.32-7.95-3.64-1.04-0.29-2.28 0.12-3.08 1.02l-4.81 8.93c-0.5 0.69-0.5 1.41-0.21 2.04l8.8 17.65c0.28 0.54 0.14 1.81 0.15 2.25l-1.05 11.07zm7.35-31.33c1.59-1.81-1.36-6.84-2.58-5.38l-7.88 10.32c-2.31 2.87 1.17 5.71 3.65 3.94 0.22-0.2 0.34-0.31 0.5-0.52zm-3.85-7.83c-1.17-1.22-3.57-2.98-3.89-2.86l-8.79 11.86c-2.37 2.31 0.3 5.07 2.88 4.8 0.36-0.1 0.48-0.17 0.7-0.38l9.16-11.84c0.4-0.52 0.27-1.16-0.07-1.58zm-5.22-3.82c-0.09-0.61-3.96-3.73-4.26-3.34l-4.46 5.77c5.64 6.14-0.94 9.96-4.85 7.73 0.49 1.57 3.42 3.2 4.33 2.01zm-5.38-4.3c-0.85-1.8-4.22-3.17-5.74-2.59l-4.83 5.95 6.06 2.51z"
+        android:strokeWidth="0.26458332"
+        android:strokeAlpha="0.5098038" />
+    <path
+        android:fillColor="#ff5000"
+        android:pathData="M54.78 75.59H70.9a2.67 2.67 0 0 1 2.67 2.67v4.34a2.67 2.67 0 0 1-2.67 2.68H54.78a2.67 2.67 0 0 1-2.68-2.68v-4.34a2.67 2.67 0 0 1 2.68-2.67z"
+        android:strokeWidth="16.03646088"
+        android:strokeAlpha="0.5098038" />
+    <path
+        android:fillColor="#1359ff"
+        android:pathData="M58.95 77.48h7.77a1.63 1.63 0 0 1 1.63 1.63v2.65a1.63 1.63 0 0 1-1.63 1.63h-7.77a1.63 1.63 0 0 1-1.63-1.63v-2.65a1.63 1.63 0 0 1 1.63-1.63z"
+        android:strokeWidth="8.97837925"
+        android:strokeAlpha="0.5098038" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_led_color.xml b/app/src/main/res/drawable/ic_led_color.xml
new file mode 100644
index 0000000..bc61dcb
--- /dev/null
+++ b/app/src/main/res/drawable/ic_led_color.xml
@@ -0,0 +1,10 @@
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:innerRadius="0dp"
+    android:shape="ring"
+    android:thicknessRatio="2"
+    android:useLevel="false">
+    <solid android:color="@android:color/transparent" />
+    <stroke
+        android:width="2dp"
+        android:color="@color/secondarytext" />
+</shape>
diff --git a/app/src/main/res/drawable/ic_link.xml b/app/src/main/res/drawable/ic_link.xml
new file mode 100644
index 0000000..0c9cf24
--- /dev/null
+++ b/app/src/main/res/drawable/ic_link.xml
@@ -0,0 +1,5 @@
+<vector android:height="24dp" android:tint="#7E7E7E"
+    android:viewportHeight="24" android:viewportWidth="24"
+    android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
+    <path android:fillColor="@android:color/white" android:pathData="M3.9,12c0,-1.71 1.39,-3.1 3.1,-3.1h4L11,7L7,7c-2.76,0 -5,2.24 -5,5s2.24,5 5,5h4v-1.9L7,15.1c-1.71,0 -3.1,-1.39 -3.1,-3.1zM8,13h8v-2L8,11v2zM17,7h-4v1.9h4c1.71,0 3.1,1.39 3.1,3.1s-1.39,3.1 -3.1,3.1h-4L13,17h4c2.76,0 5,-2.24 5,-5s-2.24,-5 -5,-5z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_list_numbered.xml b/app/src/main/res/drawable/ic_list_numbered.xml
new file mode 100644
index 0000000..5756c42
--- /dev/null
+++ b/app/src/main/res/drawable/ic_list_numbered.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="#7E7E7E">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M2,17h2v0.5L3,17.5v1h1v0.5L2,19v1h3v-4L2,16v1zM3,8h1L4,4L2,4v1h1v3zM2,11h1.8L2,13.1v0.9h3v-1L3.2,13L5,10.9L5,10L2,10v1zM7,5v2h14L21,5L7,5zM7,19h14v-2L7,17v2zM7,13h14v-2L7,11v2z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_lock_open.xml b/app/src/main/res/drawable/ic_lock_open.xml
new file mode 100644
index 0000000..917091a
--- /dev/null
+++ b/app/src/main/res/drawable/ic_lock_open.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="#000000"
+        android:pathData="M12 13c-1.1 0-2 0.9-2 2s0.9 2 2 2 2-0.9 2-2-0.9-2-2-2zm6-5h-1V6c0-2.76-2.24-5-5-5-2.28 0-4.27 1.54-4.84 3.75-0.14 0.54 0.18 1.08 0.72 1.22 0.53 0.14 1.08-0.18 1.22-0.72C9.44 3.93 10.63 3 12 3c1.65 0 3 1.35 3 3v2H6c-1.1 0-2 0.9-2 2v10c0 1.1 0.9 2 2 2h12c1.1 0 2-0.9 2-2V10c0-1.1-0.9-2-2-2zm0 11c0 0.55-0.45 1-1 1H7c-0.55 0-1-0.45-1-1v-8c0-0.55 0.45-1 1-1h10c0.55 0 1 0.45 1 1v8z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_loyalty.xml b/app/src/main/res/drawable/ic_loyalty.xml
new file mode 100644
index 0000000..c0e3ff2
--- /dev/null
+++ b/app/src/main/res/drawable/ic_loyalty.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M21.41,11.58l-9,-9C12.05,2.22 11.55,2 11,2L4,2c-1.1,0 -2,0.9 -2,2v7c0,0.55 0.22,1.05 0.59,1.42l9,9c0.36,0.36 0.86,0.58 1.41,0.58 0.55,0 1.05,-0.22 1.41,-0.59l7,-7c0.37,-0.36 0.59,-0.86 0.59,-1.41 0,-0.55 -0.23,-1.06 -0.59,-1.42zM5.5,7C4.67,7 4,6.33 4,5.5S4.67,4 5.5,4 7,4.67 7,5.5 6.33,7 5.5,7zM17.27,15.27L13,19.54l-4.27,-4.27C8.28,14.81 8,14.19 8,13.5c0,-1.38 1.12,-2.5 2.5,-2.5 0.69,0 1.32,0.28 1.77,0.74l0.73,0.72 0.73,-0.73c0.45,-0.45 1.08,-0.73 1.77,-0.73 1.38,0 2.5,1.12 2.5,2.5 0,0.69 -0.28,1.32 -0.73,1.77z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_map.xml b/app/src/main/res/drawable/ic_map.xml
new file mode 100644
index 0000000..b17843c
--- /dev/null
+++ b/app/src/main/res/drawable/ic_map.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:fillColor="@color/secondarytext"
+      android:pathData="M20.5,3l-0.16,0.03L15,5.1 9,3 3.36,4.9c-0.21,0.07 -0.36,0.25 -0.36,0.48L3,20.5c0,0.28 0.22,0.5 0.5,0.5l0.16,-0.03L9,18.9l6,2.1 5.64,-1.9c0.21,-0.07 0.36,-0.25 0.36,-0.48L21,3.5c0,-0.28 -0.22,-0.5 -0.5,-0.5zM10,5.47l4,1.4v11.66l-4,-1.4L10,5.47zM5,6.46l3,-1.01v11.7l-3,1.16L5,6.46zM19,17.54l-3,1.01L16,6.86l3,-1.16v11.84z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_menu.xml b/app/src/main/res/drawable/ic_menu.xml
new file mode 100644
index 0000000..66c96a6
--- /dev/null
+++ b/app/src/main/res/drawable/ic_menu.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="#000000"
+        android:pathData="M4 18h16c0.55 0 1-0.45 1-1s-0.45-1-1-1H4c-0.55 0-1 0.45-1 1s0.45 1 1 1zm0-5h16c0.55 0 1-0.45 1-1s-0.45-1-1-1H4c-0.55 0-1 0.45-1 1s0.45 1 1 1zM3 7c0 0.55 0.45 1 1 1h16c0.55 0 1-0.45 1-1s-0.45-1-1-1H4C3.45 6 3 6.45 3 7z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_message_outline.xml b/app/src/main/res/drawable/ic_message_outline.xml
new file mode 100644
index 0000000..30c13a3
--- /dev/null
+++ b/app/src/main/res/drawable/ic_message_outline.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:fillColor="#FFFFFF"
+      android:pathData="M20,2H4C2.9,2 2,2.9 2,4V22L6,18H20C21.1,18 22,17.1 22,16V4C22,2.9 21.1,2 20,2M20,16H5.2L4,17.2V4H20V16Z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_monochrome_photos.xml b/app/src/main/res/drawable/ic_monochrome_photos.xml
new file mode 100644
index 0000000..85fb57d
--- /dev/null
+++ b/app/src/main/res/drawable/ic_monochrome_photos.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="#7E7E7E">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M20,5h-3.2L15,3L9,3L7.2,5L4,5c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,7c0,-1.1 -0.9,-2 -2,-2zM20,19h-8v-1c-2.8,0 -5,-2.2 -5,-5s2.2,-5 5,-5L12,7h8v12zM17,13c0,-2.8 -2.2,-5 -5,-5v1.8c1.8,0 3.2,1.4 3.2,3.2s-1.4,3.2 -3.2,3.2L12,18c2.8,0 5,-2.2 5,-5zM8.8,13c0,1.8 1.4,3.2 3.2,3.2L12,9.8c-1.8,0 -3.2,1.4 -3.2,3.2z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_mood_bad.xml b/app/src/main/res/drawable/ic_mood_bad.xml
new file mode 100644
index 0000000..7548a58
--- /dev/null
+++ b/app/src/main/res/drawable/ic_mood_bad.xml
@@ -0,0 +1,5 @@
+<vector android:height="24dp" android:tint="#7E7E7E"
+    android:viewportHeight="24" android:viewportWidth="24"
+    android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
+    <path android:fillColor="@android:color/white" android:pathData="M11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8zM15.5,11c0.83,0 1.5,-0.67 1.5,-1.5S16.33,8 15.5,8 14,8.67 14,9.5s0.67,1.5 1.5,1.5zM8.5,11c0.83,0 1.5,-0.67 1.5,-1.5S9.33,8 8.5,8 7,8.67 7,9.5 7.67,11 8.5,11zM12,14c-2.33,0 -4.31,1.46 -5.11,3.5h10.22c-0.8,-2.04 -2.78,-3.5 -5.11,-3.5z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_more_vert.xml b/app/src/main/res/drawable/ic_more_vert.xml
new file mode 100644
index 0000000..d348540
--- /dev/null
+++ b/app/src/main/res/drawable/ic_more_vert.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="#000000"
+        android:pathData="M12 8c1.1 0 2-0.9 2-2s-0.9-2-2-2-2 0.9-2 2 0.9 2 2 2zm0 2c-1.1 0-2 0.9-2 2s0.9 2 2 2 2-0.9 2-2-0.9-2-2-2zm0 6c-1.1 0-2 0.9-2 2s0.9 2 2 2 2-0.9 2-2-0.9-2-2-2z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_mtu.xml b/app/src/main/res/drawable/ic_mtu.xml
new file mode 100644
index 0000000..1e9bda2
--- /dev/null
+++ b/app/src/main/res/drawable/ic_mtu.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="#7E7E7E">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M7.77,6.76L6.23,5.48 0.82,12l5.41,6.52 1.54,-1.28L3.42,12l4.35,-5.24zM7,13h2v-2L7,11v2zM17,11h-2v2h2v-2zM11,13h2v-2h-2v2zM17.77,5.48l-1.54,1.28L20.58,12l-4.35,5.24 1.54,1.28L23.18,12l-5.41,-6.52z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_music.xml b/app/src/main/res/drawable/ic_music.xml
new file mode 100644
index 0000000..b896050
--- /dev/null
+++ b/app/src/main/res/drawable/ic_music.xml
@@ -0,0 +1,21 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="45sp"
+    android:height="45sp"
+    android:viewportWidth="30"
+    android:viewportHeight="30">
+  <path
+      android:fillColor="?attr/deviceIconLight"
+      android:pathData="m3.871,3.877h20.925a0.947,0.947 0,0 1,0.948 0.947v20.01a0.947,0.947 0,0 1,-0.948 0.948H3.871A0.947,0.947 0,0 1,2.924 24.834V4.824A0.947,0.947 0,0 1,3.871 3.877Z"
+      android:strokeWidth="3.5"/>
+  <path
+      android:fillColor="?attr/deviceIconDark"
+      android:pathData="m3.879,3.035h20.925a0.947,0.947 0,0 1,0.947 0.947v20.01A0.947,0.947 0,0 1,24.804 24.94H3.88A0.947,0.947 0,0 1,2.933 23.992V3.982A0.947,0.947 0,0 1,3.88 3.035Z"
+      android:strokeWidth="3.5"/>
+  <path
+      android:fillColor="?attr/deviceIconPrimary"
+      android:pathData="m3.871,3.413h20.925a0.947,0.947 0,0 1,0.948 0.947v20.01a0.947,0.947 0,0 1,-0.948 0.948H3.871A0.947,0.947 0,0 1,2.924 24.37V4.36A0.947,0.947 0,0 1,3.87 3.413Z"
+      android:strokeWidth="3.5"/>
+  <path
+      android:pathData="m9.96,22.998c1.459,-0.675 2.415,-1.894 2.427,-2.958h0.003v-8.276l9.868,-3.619v7.181c-0.791,-0.585 -2.187,-0.616 -3.548,0.014 -1.763,0.816 -2.797,2.427 -2.309,3.598 0.488,1.172 2.313,1.46 4.076,0.645 1.459,-0.675 2.415,-1.894 2.427,-2.958h0.003V5.269C19.188,6.702 15.18,8.025 11.741,9.242v9.5c-0.791,-0.585 -2.187,-0.617 -3.548,0.013 -1.763,0.816 -2.797,2.428 -2.309,3.599 0.488,1.172 2.313,1.46 4.076,0.645"
+      android:fillColor="?attr/deviceIconOnPrimary"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_music_item.xml b/app/src/main/res/drawable/ic_music_item.xml
new file mode 100644
index 0000000..14e99a6
--- /dev/null
+++ b/app/src/main/res/drawable/ic_music_item.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_selected="true" android:drawable="@drawable/ic_edit" />
+    <item android:drawable="@drawable/ic_music" />
+</selector>
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_music_note.xml b/app/src/main/res/drawable/ic_music_note.xml
new file mode 100644
index 0000000..0e1b03e
--- /dev/null
+++ b/app/src/main/res/drawable/ic_music_note.xml
@@ -0,0 +1,3 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:tint="#7E7E7E" android:viewportWidth="24" android:viewportHeight="24">
+    <path android:fillColor="@android:color/white" android:pathData="M12 3l0.01 10.55c-0.59-0.34-1.27-0.55-2-0.55C7.79 13 6 14.79 6 17s1.79 4 4.01 4S14 19.21 14 17V7h4V3h-6zm-1.99 16c-1.1 0-2-0.9-2-2s0.9-2 2-2 2 0.9 2 2-0.9 2-2 2z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_navigation.xml b/app/src/main/res/drawable/ic_navigation.xml
new file mode 100644
index 0000000..47548d8
--- /dev/null
+++ b/app/src/main/res/drawable/ic_navigation.xml
@@ -0,0 +1,12 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M12,2L4.5,20.29l0.71,0.71L12,18l6.79,3 0.71,-0.71z" />
+
+</vector>
diff --git a/app/src/main/res/drawable/ic_nights_stay.xml b/app/src/main/res/drawable/ic_nights_stay.xml
new file mode 100644
index 0000000..fdb1bfe
--- /dev/null
+++ b/app/src/main/res/drawable/ic_nights_stay.xml
@@ -0,0 +1,13 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="#000000"
+        android:pathData="M11.1 12.08c-2-3.88-0.92-7.36 0.07-9.27 0.19-0.36-0.12-0.77-0.53-0.72-5.02 0.68-8.86 5.07-8.65 10.32 0.01 0 0.01 0 0.01 0.01C2.62 12.15 3.29 12 4 12c1.66 0 3.18 0.83 4.1 2.15C9.77 14.63 11 16.17 11 18c0 1.52-0.87 2.83-2.12 3.51 0.98 0.32 2.03 0.5 3.11 0.5 3.13 0 5.92-1.44 7.76-3.69 0.26-0.32 0.04-0.79-0.37-0.82-2.49-0.13-6.28-1.53-8.28-5.42z" />
+    <path
+        android:fillColor="#000000"
+        android:pathData="M7 16H6.82C6.4 14.84 5.3 14 4 14c-1.66 0-3 1.34-3 3s1.34 3 3 3h3c1.1 0 2-0.9 2-2s-0.9-2-2-2z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_no_watch.xml b/app/src/main/res/drawable/ic_no_watch.xml
new file mode 100644
index 0000000..2d3efea
--- /dev/null
+++ b/app/src/main/res/drawable/ic_no_watch.xml
@@ -0,0 +1,15 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="#000000"
+        android:pathData="M8 0L7.05 5.73C6.828 5.907 6.626 6.106 6.425 6.306L8.13 7.438C8.505 7.12 8.913 6.84 9.357 6.62 10.156 6.227 11.051 6 12 6c1.174 0 2.266 0.343 3.191 0.928C16.876 7.99 18 9.864 18 12c0 0.628-0.098 1.233-0.277 1.803l1.709 1.132C19.792 14.025 20 13.037 20 12c0-1.851-0.638-3.555-1.697-4.91-0.394-0.504-0.84-0.964-1.342-1.36L16 0H8zm1.7 2h4.61l0.41 2.48C13.87 4.17 12.96 4 12 4c-0.95 0-1.87 0.17-2.71 0.47L9.7 2zM5.386 7.525C4.515 8.8 4 10.335 4 12c0 2.55 1.19 4.81 3.05 6.27L8 24h8l0.96-5.73c0.667-0.526 1.244-1.16 1.714-1.87l-1.662-1.109C15.938 16.921 14.095 18 12 18c-3.31 0-6-2.69-6-6 0-1.25 0.392-2.405 1.049-3.365l-1.662-1.11zM14.72 19.52L14.31 22H9.698l-0.41-2.47C10.13 19.83 11.05 20 12 20c0.96 0 1.87-0.17 2.72-0.48z" />
+    <path
+        android:fillType="evenOdd"
+        android:pathData="M2.033 5.303l20.152 13.612"
+        android:strokeWidth="2"
+        android:strokeColor="#000000" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_nothing_ear_l.xml b/app/src/main/res/drawable/ic_nothing_ear_l.xml
new file mode 100644
index 0000000..968d053
--- /dev/null
+++ b/app/src/main/res/drawable/ic_nothing_ear_l.xml
@@ -0,0 +1,11 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="45sp"
+    android:height="45sp"
+    android:viewportWidth="25"
+    android:viewportHeight="25"
+    android:tint="#7E7E7E">
+  <path
+      android:pathData="m12.0927,19.3298c-0.241,3.181 4.3579,3.724 4.2615,-0.8403l-0.054,-11.0757c-0.0096,-1.9588 -0.9269,-4.7246 -4.0681,-4.6895 -2.4888,0.0278 -5.0364,0.643 -5.0461,1.8758l-0.0329,4.1767c-0.0152,1.9307 3.7442,3.6762 4.8677,2.9943 1.0099,-0.6129 0.0595,7.6762 0.0595,7.6762"
+      android:strokeWidth="2"
+      android:strokeColor="#000000"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_nothing_ear_r.xml b/app/src/main/res/drawable/ic_nothing_ear_r.xml
new file mode 100644
index 0000000..7483004
--- /dev/null
+++ b/app/src/main/res/drawable/ic_nothing_ear_r.xml
@@ -0,0 +1,11 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="45sp"
+    android:height="45sp"
+    android:viewportWidth="25"
+    android:viewportHeight="25"
+    android:tint="#7E7E7E">
+  <path
+      android:pathData="m11.7614,19.3298c0.241,3.1811 -4.3579,3.7241 -4.2615,-0.8403l0.054,-11.0761c0.0096,-1.9589 0.9269,-4.7247 4.0681,-4.6896 2.4888,0.0278 5.0364,0.643 5.0461,1.8758l0.0329,4.1769c0.0152,1.9308 -3.7442,3.6763 -4.8677,2.9944 -1.0099,-0.6129 -0.0595,7.6764 -0.0595,7.6764"
+      android:strokeWidth="2"
+      android:strokeColor="#000000"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_notifications.xml b/app/src/main/res/drawable/ic_notifications.xml
new file mode 100644
index 0000000..dd3d1f3
--- /dev/null
+++ b/app/src/main/res/drawable/ic_notifications.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M12,22c1.1,0 2,-0.9 2,-2h-4c0,1.1 0.9,2 2,2zM18,16v-5c0,-3.07 -1.63,-5.64 -4.5,-6.32L13.5,4c0,-0.83 -0.67,-1.5 -1.5,-1.5s-1.5,0.67 -1.5,1.5v0.68C7.64,5.36 6,7.92 6,11v5l-2,2v1h16v-1l-2,-2zM16,17L8,17v-6c0,-2.48 1.51,-4.5 4,-4.5s4,2.02 4,4.5v6z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_open_in_browser.xml b/app/src/main/res/drawable/ic_open_in_browser.xml
new file mode 100644
index 0000000..35f49b3
--- /dev/null
+++ b/app/src/main/res/drawable/ic_open_in_browser.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="#7E7E7E">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M19,4L5,4c-1.11,0 -2,0.9 -2,2v12c0,1.1 0.89,2 2,2h4v-2L5,18L5,8h14v10h-4v2h4c1.1,0 2,-0.9 2,-2L21,6c0,-1.1 -0.89,-2 -2,-2zM12,10l-4,4h3v6h2v-6h3l-4,-4z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_pageview.xml b/app/src/main/res/drawable/ic_pageview.xml
new file mode 100644
index 0000000..d0a505c
--- /dev/null
+++ b/app/src/main/res/drawable/ic_pageview.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="#000000"
+        android:pathData="M11.5 9C10.12 9 9 10.12 9 11.5s1.12 2.5 2.5 2.5 2.5-1.12 2.5-2.5S12.88 9 11.5 9zM20 4H4C2.9 4 2 4.9 2 6v12c0 1.1 0.9 2 2 2h16c1.1 0 2-0.9 2-2V6c0-1.1-0.9-2-2-2zm-3.92 13.5l-2.2-2.2c-0.9 0.58-2.03 0.84-3.22 0.62-1.88-0.35-3.38-1.93-3.62-3.83-0.38-3.01 2.18-5.52 5.21-5.04 1.88 0.3 3.39 1.84 3.7 3.71 0.19 1.16-0.08 2.23-0.64 3.12l2.2 2.19c0.39 0.39 0.39 1.03 0 1.42-0.4 0.4-1.04 0.4-1.43 0.01z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_paint.xml b/app/src/main/res/drawable/ic_paint.xml
new file mode 100644
index 0000000..b5d7741
--- /dev/null
+++ b/app/src/main/res/drawable/ic_paint.xml
@@ -0,0 +1,12 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M18,4V3c0,-0.55 -0.45,-1 -1,-1H5c-0.55,0 -1,0.45 -1,1v4c0,0.55 0.45,1 1,1h12c0.55,0 1,-0.45 1,-1V6h1v4H9v11c0,0.55 0.45,1 1,1h2c0.55,0 1,-0.45 1,-1v-9h8V4h-3z" />
+
+</vector>
diff --git a/app/src/main/res/drawable/ic_password.xml b/app/src/main/res/drawable/ic_password.xml
new file mode 100644
index 0000000..072f0ee
--- /dev/null
+++ b/app/src/main/res/drawable/ic_password.xml
@@ -0,0 +1,5 @@
+<vector android:height="24dp" android:tint="#7E7E7E"
+    android:viewportHeight="24" android:viewportWidth="24"
+    android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
+    <path android:fillColor="@android:color/white" android:pathData="M2,17h20v2H2V17zM3.15,12.95L4,11.47l0.85,1.48l1.3,-0.75L5.3,10.72H7v-1.5H5.3l0.85,-1.47L4.85,7L4,8.47L3.15,7l-1.3,0.75L2.7,9.22H1v1.5h1.7L1.85,12.2L3.15,12.95zM9.85,12.2l1.3,0.75L12,11.47l0.85,1.48l1.3,-0.75l-0.85,-1.48H15v-1.5h-1.7l0.85,-1.47L12.85,7L12,8.47L11.15,7l-1.3,0.75l0.85,1.47H9v1.5h1.7L9.85,12.2zM23,9.22h-1.7l0.85,-1.47L20.85,7L20,8.47L19.15,7l-1.3,0.75l0.85,1.47H17v1.5h1.7l-0.85,1.48l1.3,0.75L20,11.47l0.85,1.48l1.3,-0.75l-0.85,-1.48H23V9.22z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_pause.xml b/app/src/main/res/drawable/ic_pause.xml
new file mode 100644
index 0000000..a932d88
--- /dev/null
+++ b/app/src/main/res/drawable/ic_pause.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M6,19h4L10,5L6,5v14zM14,5v14h4L18,5h-4z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_pending_actions.xml b/app/src/main/res/drawable/ic_pending_actions.xml
new file mode 100644
index 0000000..46732b5
--- /dev/null
+++ b/app/src/main/res/drawable/ic_pending_actions.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="#7E7E7E">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M17,12c-2.76,0 -5,2.24 -5,5s2.24,5 5,5c2.76,0 5,-2.24 5,-5S19.76,12 17,12zM18.65,19.35l-2.15,-2.15V14h1v2.79l1.85,1.85L18.65,19.35zM18,3h-3.18C14.4,1.84 13.3,1 12,1S9.6,1.84 9.18,3H6C4.9,3 4,3.9 4,5v15c0,1.1 0.9,2 2,2h6.11c-0.59,-0.57 -1.07,-1.25 -1.42,-2H6V5h2v3h8V5h2v5.08c0.71,0.1 1.38,0.31 2,0.6V5C20,3.9 19.1,3 18,3zM12,5c-0.55,0 -1,-0.45 -1,-1c0,-0.55 0.45,-1 1,-1c0.55,0 1,0.45 1,1C13,4.55 12.55,5 12,5z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_person.xml b/app/src/main/res/drawable/ic_person.xml
new file mode 100644
index 0000000..cee7071
--- /dev/null
+++ b/app/src/main/res/drawable/ic_person.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M12,12c2.21,0 4,-1.79 4,-4s-1.79,-4 -4,-4 -4,1.79 -4,4 1.79,4 4,4zM12,14c-2.67,0 -8,1.34 -8,4v2h16v-2c0,-2.66 -5.33,-4 -8,-4z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_phone.xml b/app/src/main/res/drawable/ic_phone.xml
new file mode 100644
index 0000000..08b0fc2
--- /dev/null
+++ b/app/src/main/res/drawable/ic_phone.xml
@@ -0,0 +1,5 @@
+<vector android:height="24dp" android:tint="#7E7E7E"
+    android:viewportHeight="24" android:viewportWidth="24"
+    android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
+    <path android:fillColor="@android:color/white" android:pathData="M6.62,10.79c1.44,2.83 3.76,5.14 6.59,6.59l2.2,-2.2c0.27,-0.27 0.67,-0.36 1.02,-0.24 1.12,0.37 2.33,0.57 3.57,0.57 0.55,0 1,0.45 1,1V20c0,0.55 -0.45,1 -1,1 -9.39,0 -17,-7.61 -17,-17 0,-0.55 0.45,-1 1,-1h3.5c0.55,0 1,0.45 1,1 0,1.25 0.2,2.45 0.57,3.57 0.11,0.35 0.03,0.74 -0.25,1.02l-2.2,2.2z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_phone_missed_outline.xml b/app/src/main/res/drawable/ic_phone_missed_outline.xml
new file mode 100644
index 0000000..1be9e11
--- /dev/null
+++ b/app/src/main/res/drawable/ic_phone_missed_outline.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="#7E7E7E">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M18.6,15.5v1.8c0.7,0.4 1.3,0.8 1.9,1.3l1.1,-1.1c-0.9,-0.9 -1.9,-1.5 -3,-2m-13.2,0c-1,0.5 -2,1.1 -2.9,1.9l1.1,1.1c0.6,-0.5 1.2,-0.9 1.9,-1.3v-1.7M12,12c4.5,0 8.7,1.7 11.7,4.7 0.2,0.2 0.3,0.4 0.3,0.7 0,0.3 -0.1,0.5 -0.3,0.7l-2.5,2.5c-0.2,0.2 -0.4,0.3 -0.7,0.3 -0.2,0 -0.5,-0.1 -0.7,-0.3 -0.8,-0.7 -1.7,-1.4 -2.7,-1.8 -0.3,-0.2 -0.6,-0.5 -0.6,-0.9v-3.1c-1.5,-0.5 -3,-0.7 -4.6,-0.7 -1.6,0 -3.1,0.2 -4.6,0.7v3.1c0,0.4 -0.2,0.7 -0.6,0.9 -1,0.5 -1.9,1.1 -2.7,1.8 -0.2,0.2 -0.4,0.3 -0.7,0.3 -0.3,0 -0.5,-0.1 -0.7,-0.3L0.1,18.1c0,-0.2 -0.1,-0.5 -0.1,-0.7 0,-0.3 0.1,-0.5 0.3,-0.7C3.3,13.8 7.5,12 12,12zM6.5,5.5V9H5V3h6v1.5H7.5L12,9l6,-6 1,1 -7,7 -5.5,-5.5z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_phone_msg.xml b/app/src/main/res/drawable/ic_phone_msg.xml
new file mode 100644
index 0000000..70167ff
--- /dev/null
+++ b/app/src/main/res/drawable/ic_phone_msg.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="#7E7E7E">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M20,15.5c-1.25,0 -2.45,-0.2 -3.57,-0.57 -0.1,-0.03 -0.21,-0.05 -0.31,-0.05 -0.26,0 -0.51,0.1 -0.71,0.29l-2.2,2.2c-2.83,-1.44 -5.15,-3.75 -6.59,-6.58l2.2,-2.21c0.28,-0.27 0.36,-0.66 0.25,-1.01C8.7,6.45 8.5,5.25 8.5,4c0,-0.55 -0.45,-1 -1,-1L4,3c-0.55,0 -1,0.45 -1,1 0,9.39 7.61,17 17,17 0.55,0 1,-0.45 1,-1v-3.5c0,-0.55 -0.45,-1 -1,-1zM5.03,5h1.5c0.07,0.88 0.22,1.75 0.46,2.59L5.79,8.8c-0.41,-1.21 -0.67,-2.48 -0.76,-3.8zM19,18.97c-1.32,-0.09 -2.6,-0.35 -3.8,-0.76l1.2,-1.2c0.85,0.24 1.72,0.39 2.6,0.45v1.51zM12,3v10l3,-3h6L21,3h-9zM19,8h-5L14,5h5v3z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_phone_outline.xml b/app/src/main/res/drawable/ic_phone_outline.xml
new file mode 100644
index 0000000..bd3d2fd
--- /dev/null
+++ b/app/src/main/res/drawable/ic_phone_outline.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:fillColor="#FFFFFFFF"
+      android:pathData="M20,15.5C18.8,15.5 17.5,15.3 16.4,14.9C16.3,14.9 16.2,14.9 16.1,14.9C15.8,14.9 15.6,15 15.4,15.2L13.2,17.4C10.4,15.9 8,13.6 6.6,10.8L8.8,8.6C9.1,8.3 9.2,7.9 9,7.6C8.7,6.5 8.5,5.2 8.5,4C8.5,3.5 8,3 7.5,3H4C3.5,3 3,3.5 3,4C3,13.4 10.6,21 20,21C20.5,21 21,20.5 21,20V16.5C21,16 20.5,15.5 20,15.5M5,5H6.5C6.6,5.9 6.8,6.8 7,7.6L5.8,8.8C5.4,7.6 5.1,6.3 5,5M19,19C17.7,18.9 16.4,18.6 15.2,18.2L16.4,17C17.2,17.2 18.1,17.4 19,17.4V19Z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_play.xml b/app/src/main/res/drawable/ic_play.xml
new file mode 100644
index 0000000..e623676
--- /dev/null
+++ b/app/src/main/res/drawable/ic_play.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M8,5v14l11,-7z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_power_settings_new.xml b/app/src/main/res/drawable/ic_power_settings_new.xml
new file mode 100644
index 0000000..af6eb5e
--- /dev/null
+++ b/app/src/main/res/drawable/ic_power_settings_new.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="#000000"
+        android:pathData="M12 3c-0.55 0-1 0.45-1 1v8c0 0.55 0.45 1 1 1s1-0.45 1-1V4c0-0.55-0.45-1-1-1zm5.14 2.86c-0.39 0.39-0.38 1-0.01 1.39 1.13 1.2 1.83 2.8 1.87 4.57 0.09 3.83-3.08 7.13-6.91 7.17C8.18 19.05 5 15.9 5 12c0-1.84 0.71-3.51 1.87-4.76 0.37-0.39 0.37-1-0.01-1.38-0.4-0.4-1.05-0.39-1.43 0.02C3.98 7.42 3.07 9.47 3 11.74c-0.14 4.88 3.83 9.1 8.71 9.25 5.1 0.16 9.29-3.93 9.29-9 0-2.37-0.92-4.51-2.42-6.11-0.38-0.41-1.04-0.42-1.44-0.02z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_pressure.xml b/app/src/main/res/drawable/ic_pressure.xml
new file mode 100644
index 0000000..0b293a8
--- /dev/null
+++ b/app/src/main/res/drawable/ic_pressure.xml
@@ -0,0 +1,5 @@
+<vector android:height="24dp" android:tint="#7E7E7E"
+    android:viewportHeight="24" android:viewportWidth="24"
+    android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
+    <path android:fillColor="@android:color/white" android:pathData="M20.38,8.57l-1.23,1.85a8,8 0,0 1,-0.22 7.58L5.07,18A8,8 0,0 1,15.58 6.85l1.85,-1.23A10,10 0,0 0,3.35 19a2,2 0,0 0,1.72 1h13.85a2,2 0,0 0,1.74 -1,10 10,0 0,0 -0.27,-10.44zM10.59,15.41a2,2 0,0 0,2.83 0l5.66,-8.49 -8.49,5.66a2,2 0,0 0,0 2.83z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_radio.xml b/app/src/main/res/drawable/ic_radio.xml
new file mode 100644
index 0000000..228588f
--- /dev/null
+++ b/app/src/main/res/drawable/ic_radio.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="#000000"
+        android:pathData="M3.24 6.15C2.51 6.43 2 7.17 2 8v12c0 1.1 0.9 2 2 2h16c1.11 0 2-0.9 2-2V8c0-1.1-0.9-2-2-2H8.3l7.43-3c0.46-0.19 0.68-0.71 0.49-1.17-0.19-0.46-0.71-0.68-1.17-0.49L3.24 6.15zM7 20c-1.66 0-3-1.34-3-3s1.34-3 3-3 3 1.34 3 3-1.34 3-3 3zm13-8h-2v-1c0-0.55-0.45-1-1-1s-1 0.45-1 1v1H4V9c0-0.55 0.45-1 1-1h14c0.55 0 1 0.45 1 1v3z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_refresh.xml b/app/src/main/res/drawable/ic_refresh.xml
new file mode 100644
index 0000000..0a5320a
--- /dev/null
+++ b/app/src/main/res/drawable/ic_refresh.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="#000000"
+        android:pathData="M17.65 6.35c-1.63-1.63-3.94-2.57-6.48-2.31-3.67 0.37-6.69 3.35-7.1 7.02C3.52 15.91 7.27 20 12 20c3.19 0 5.93-1.87 7.21-4.56 0.32-0.67-0.16-1.44-0.9-1.44-0.37 0-0.72 0.2-0.88 0.53-1.13 2.43-3.84 3.97-6.8 3.31-2.22-0.49-4.01-2.3-4.48-4.52C5.31 9.44 8.26 6 12 6c1.66 0 3.14 0.69 4.22 1.78l-1.51 1.51C14.08 9.92 14.52 11 15.41 11H19c0.55 0 1-0.45 1-1V6.41c0-0.89-1.08-1.34-1.71-0.71l-0.64 0.65z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_remove_image.xml b/app/src/main/res/drawable/ic_remove_image.xml
new file mode 100644
index 0000000..dd1b1ed
--- /dev/null
+++ b/app/src/main/res/drawable/ic_remove_image.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="#7E7E7E">
+  <path
+      android:fillColor="#000000"
+      android:pathData="M21.9,21.9l-8.49,-8.49l0,0L3.59,3.59l0,0L2.1,2.1L0.69,3.51L3,5.83V19c0,1.1 0.9,2 2,2h13.17l2.31,2.31L21.9,21.9zM5,18l3.5,-4.5l2.5,3.01L12.17,15l3,3H5zM21,18.17L5.83,3H19c1.1,0 2,0.9 2,2V18.17z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_reply.xml b/app/src/main/res/drawable/ic_reply.xml
new file mode 100644
index 0000000..8d3246c
--- /dev/null
+++ b/app/src/main/res/drawable/ic_reply.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="#7E7E7E">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M10,9V5l-7,7 7,7v-4.1c5,0 8.5,1.6 11,5.1 -1,-5 -4,-10 -11,-11z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_report.xml b/app/src/main/res/drawable/ic_report.xml
new file mode 100644
index 0000000..6b84b9f
--- /dev/null
+++ b/app/src/main/res/drawable/ic_report.xml
@@ -0,0 +1,12 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M15.73,3L8.27,3L3,8.27v7.46L8.27,21h7.46L21,15.73L21,8.27L15.73,3zM12,17.3c-0.72,0 -1.3,-0.58 -1.3,-1.3 0,-0.72 0.58,-1.3 1.3,-1.3 0.72,0 1.3,0.58 1.3,1.3 0,0.72 -0.58,1.3 -1.3,1.3zM13,13h-2L11,7h2v6z" />
+
+</vector>
diff --git a/app/src/main/res/drawable/ic_rotate_left.xml b/app/src/main/res/drawable/ic_rotate_left.xml
new file mode 100644
index 0000000..f7104e0
--- /dev/null
+++ b/app/src/main/res/drawable/ic_rotate_left.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="#000000"
+        android:pathData="M6.56 7.98C6.1 7.52 5.31 7.6 5 8.17 4.72 8.68 4.5 9.2 4.33 9.75 4.14 10.38 4.64 11 5.29 11H5.3c0.43 0 0.82-0.28 0.94-0.7 0.12-0.4 0.28-0.79 0.48-1.17 0.22-0.37 0.15-0.84-0.16-1.15zM5.31 13H5.29c-0.65 0-1.15 0.62-0.96 1.25 0.16 0.54 0.38 1.07 0.66 1.58 0.31 0.57 1.11 0.66 1.57 0.2 0.3-0.31 0.38-0.77 0.17-1.15-0.2-0.37-0.36-0.76-0.48-1.16C6.13 13.28 5.74 13 5.31 13zm2.85 6.02c0.51 0.28 1.04 0.5 1.59 0.66 0.62 0.18 1.24-0.32 1.24-0.96v-0.03c0-0.43-0.28-0.82-0.7-0.94-0.4-0.12-0.78-0.28-1.15-0.48-0.38-0.21-0.86-0.14-1.16 0.17l-0.03 0.03c-0.45 0.45-0.36 1.24 0.21 1.55zM13 4.07V3.41c0-0.89-1.08-1.34-1.71-0.71L9.17 4.83c-0.4 0.4-0.4 1.04 0 1.43l2.13 2.08C11.93 8.96 13 8.51 13 7.62V6.09c2.84 0.48 5 2.94 5 5.91 0 2.73-1.82 5.02-4.32 5.75-0.41 0.12-0.68 0.51-0.68 0.94v0.02c0 0.65 0.61 1.14 1.23 0.96C17.57 18.71 20 15.64 20 12c0-4.08-3.05-7.44-7-7.93z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_save.xml b/app/src/main/res/drawable/ic_save.xml
new file mode 100644
index 0000000..a7a81a2
--- /dev/null
+++ b/app/src/main/res/drawable/ic_save.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FFFFFFFF"
+        android:pathData="M17,3L5,3c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2L21,7l-4,-4zM12,19c-1.66,0 -3,-1.34 -3,-3s1.34,-3 3,-3 3,1.34 3,3 -1.34,3 -3,3zM15,9L5,9L5,5h10v4z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_screen_rotation.xml b/app/src/main/res/drawable/ic_screen_rotation.xml
new file mode 100644
index 0000000..cd55d0c
--- /dev/null
+++ b/app/src/main/res/drawable/ic_screen_rotation.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="#7E7E7E">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M16.48,2.52c3.27,1.55 5.61,4.72 5.97,8.48h1.5C23.44,4.84 18.29,0 12,0l-0.66,0.03 3.81,3.81 1.33,-1.32zM10.23,1.75c-0.59,-0.59 -1.54,-0.59 -2.12,0L1.75,8.11c-0.59,0.59 -0.59,1.54 0,2.12l12.02,12.02c0.59,0.59 1.54,0.59 2.12,0l6.36,-6.36c0.59,-0.59 0.59,-1.54 0,-2.12L10.23,1.75zM14.83,21.19L2.81,9.17l6.36,-6.36 12.02,12.02 -6.36,6.36zM7.52,21.48C4.25,19.94 1.91,16.76 1.55,13L0.05,13C0.56,19.16 5.71,24 12,24l0.66,-0.03 -3.81,-3.81 -1.33,1.32z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_screenshot.xml b/app/src/main/res/drawable/ic_screenshot.xml
new file mode 100644
index 0000000..d3b0902
--- /dev/null
+++ b/app/src/main/res/drawable/ic_screenshot.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="#000000"
+        android:pathData="M4 4h7V2H4C2.9 2 2 2.9 2 4v7h2V4zm6 9l-4 5h12l-3-4-2.03 2.71L10 13zm7-4.5C17 7.67 16.33 7 15.5 7S14 7.67 14 8.5s0.67 1.5 1.5 1.5S17 9.33 17 8.5zM20 2h-7v2h7v7h2V4c0-1.1-0.9-2-2-2zm0 18h-7v2h7c1.1 0 2-0.9 2-2v-7h-2v7zM4 13H2v7c0 1.1 0.9 2 2 2h7v-2H4v-7z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_search.xml b/app/src/main/res/drawable/ic_search.xml
new file mode 100644
index 0000000..0aedebe
--- /dev/null
+++ b/app/src/main/res/drawable/ic_search.xml
@@ -0,0 +1,12 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M15.5,14h-0.79l-0.28,-0.27C15.41,12.59 16,11.11 16,9.5 16,5.91 13.09,3 9.5,3S3,5.91 3,9.5 5.91,16 9.5,16c1.61,0 3.09,-0.59 4.23,-1.57l0.27,0.28v0.79l5,4.99L20.49,19l-4.99,-5zM9.5,14C7.01,14 5,11.99 5,9.5S7.01,5 9.5,5 14,7.01 14,9.5 11.99,14 9.5,14z" />
+
+</vector>
diff --git a/app/src/main/res/drawable/ic_select_all.xml b/app/src/main/res/drawable/ic_select_all.xml
new file mode 100644
index 0000000..d9e5e28
--- /dev/null
+++ b/app/src/main/res/drawable/ic_select_all.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="#000000"
+        android:pathData="M3 5h2V3C3.9 3 3 3.9 3 5zm0 8h2v-2H3v2zm4 8h2v-2H7v2zM3 9h2V7H3v2zm10-6h-2v2h2V3zm6 0v2h2c0-1.1-0.9-2-2-2zM5 21v-2H3c0 1.1 0.9 2 2 2zm-2-4h2v-2H3v2zM9 3H7v2h2V3zm2 18h2v-2h-2v2zm8-8h2v-2h-2v2zm0 8c1.1 0 2-0.9 2-2h-2v2zm0-12h2V7h-2v2zm0 8h2v-2h-2v2zm-4 4h2v-2h-2v2zm0-16h2V3h-2v2zM8 17h8c0.55 0 1-0.45 1-1V8c0-0.55-0.45-1-1-1H8C7.45 7 7 7.45 7 8v8c0 0.55 0.45 1 1 1zm1-8h6v6H9V9z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_sensor_calibration.xml b/app/src/main/res/drawable/ic_sensor_calibration.xml
new file mode 100644
index 0000000..d273e89
--- /dev/null
+++ b/app/src/main/res/drawable/ic_sensor_calibration.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="#7E7E7E">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M12,12c-2.76,0 -5,2.24 -5,5s2.24,5 5,5 5,-2.24 5,-5 -2.24,-5 -5,-5zM12,20c-1.65,0 -3,-1.35 -3,-3s1.35,-3 3,-3 3,1.35 3,3 -1.35,3 -3,3zM12,3C8.1,3 4.56,4.59 2,7.15l5,5c1.28,-1.28 3.05,-2.08 5,-2.08s3.72,0.79 5,2.07l5,-5C19.44,4.59 15.9,3 12,3zM16.84,9.47c-1.44,-0.91 -3.1,-1.4 -4.84,-1.4 -1.74,0 -3.41,0.49 -4.85,1.41L4.94,7.26C6.99,5.79 9.44,5 12,5c2.56,0 5,0.79 7.05,2.26l-2.21,2.21z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_settings.xml b/app/src/main/res/drawable/ic_settings.xml
new file mode 100644
index 0000000..3731428
--- /dev/null
+++ b/app/src/main/res/drawable/ic_settings.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="#000000"
+        android:pathData="M19.43 12.98c0.04-0.32 0.07-0.64 0.07-0.98s-0.03-0.66-0.07-0.98l2.11-1.65c0.19-0.15 0.24-0.42 0.12-0.64l-2-3.46c-0.12-0.22-0.39-0.3-0.61-0.22l-2.49 1c-0.52-0.4-1.08-0.73-1.69-0.98l-0.38-2.65C14.46 2.18 14.25 2 14 2h-4C9.75 2 9.54 2.18 9.51 2.42L9.13 5.07C8.52 5.32 7.96 5.66 7.44 6.05l-2.49-1c-0.23-0.09-0.49 0-0.61 0.22l-2 3.46C2.21 8.95 2.27 9.22 2.46 9.37l2.11 1.65C4.53 11.34 4.5 11.67 4.5 12s0.03 0.66 0.07 0.98l-2.11 1.65c-0.19 0.15-0.24 0.42-0.12 0.64l2 3.46c0.12 0.22 0.39 0.3 0.61 0.22l2.49-1c0.52 0.4 1.08 0.73 1.69 0.98l0.38 2.65C9.54 21.82 9.75 22 10 22h4c0.25 0 0.46-0.18 0.49-0.42l0.38-2.65c0.61-0.25 1.17-0.59 1.69-0.98l2.49 1c0.23 0.09 0.49 0 0.61-0.22l2-3.46c0.12-0.22 0.07-0.49-0.12-0.64l-2.11-1.65zM12 15.5c-1.93 0-3.5-1.57-3.5-3.5s1.57-3.5 3.5-3.5 3.5 1.57 3.5 3.5-1.57 3.5-3.5 3.5z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_settings_applications.xml b/app/src/main/res/drawable/ic_settings_applications.xml
new file mode 100644
index 0000000..82ed4fe
--- /dev/null
+++ b/app/src/main/res/drawable/ic_settings_applications.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="#7E7E7E">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M6.21,13.97l1.2,2.07c0.08,0.13 0.23,0.18 0.37,0.13l1.49,-0.6c0.31,0.24 0.64,0.44 1.01,0.59l0.22,1.59c0.03,0.14 0.15,0.25 0.3,0.25h2.4c0.15,0 0.27,-0.11 0.3,-0.26l0.22,-1.59c0.36,-0.15 0.7,-0.35 1.01,-0.59l1.49,0.6c0.14,0.05 0.29,0 0.37,-0.13l1.2,-2.07c0.08,-0.13 0.04,-0.29 -0.07,-0.39l-1.27,-0.99c0.03,-0.19 0.04,-0.39 0.04,-0.58 0,-0.2 -0.02,-0.39 -0.04,-0.59l1.27,-0.99c0.11,-0.09 0.15,-0.26 0.07,-0.39l-1.2,-2.07c-0.08,-0.13 -0.23,-0.18 -0.37,-0.13l-1.49,0.6c-0.31,-0.24 -0.64,-0.44 -1.01,-0.59l-0.22,-1.59c-0.03,-0.14 -0.15,-0.25 -0.3,-0.25h-2.4c-0.15,0 -0.27,0.11 -0.3,0.26l-0.22,1.59c-0.36,0.15 -0.71,0.34 -1.01,0.58l-1.49,-0.6c-0.14,-0.05 -0.29,0 -0.37,0.13l-1.2,2.07c-0.08,0.13 -0.04,0.29 0.07,0.39l1.27,0.99c-0.03,0.2 -0.05,0.39 -0.05,0.59 0,0.2 0.02,0.39 0.04,0.59l-1.27,0.99c-0.11,0.1 -0.14,0.26 -0.06,0.39zM12,10.29c0.94,0 1.71,0.77 1.71,1.71s-0.77,1.71 -1.71,1.71 -1.71,-0.77 -1.71,-1.71 0.77,-1.71 1.71,-1.71zM19,3L5,3c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h14c1.11,0 2,-0.9 2,-2L21,5c0,-1.1 -0.89,-2 -2,-2zM19,19L5,19L5,5h14v14z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_settings_remote.xml b/app/src/main/res/drawable/ic_settings_remote.xml
new file mode 100644
index 0000000..44587f8
--- /dev/null
+++ b/app/src/main/res/drawable/ic_settings_remote.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="#000000"
+        android:pathData="M15 9H9c-0.55 0-1 0.45-1 1v12c0 0.55 0.45 1 1 1h6c0.55 0 1-0.45 1-1V10c0-0.55-0.45-1-1-1zm-3 6c-1.1 0-2-0.9-2-2s0.9-2 2-2 2 0.9 2 2-0.9 2-2 2zM7.82 6.82c0.35 0.35 0.9 0.38 1.3 0.1C9.93 6.34 10.93 6 12 6s2.07 0.34 2.88 0.91c0.4 0.28 0.95 0.26 1.3-0.09 0.43-0.43 0.39-1.14-0.09-1.5C14.94 4.49 13.53 4 12 4S9.06 4.49 7.91 5.32c-0.49 0.35-0.52 1.07-0.09 1.5zM12 0C9.36 0 6.94 0.93 5.05 2.47c-0.46 0.38-0.5 1.07-0.08 1.49 0.36 0.36 0.93 0.39 1.32 0.07C7.84 2.77 9.83 2 12 2s4.16 0.77 5.7 2.04c0.39 0.32 0.96 0.29 1.32-0.07 0.42-0.42 0.38-1.11-0.08-1.49C17.06 0.93 14.64 0 12 0z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_share.xml b/app/src/main/res/drawable/ic_share.xml
new file mode 100644
index 0000000..7c8369a
--- /dev/null
+++ b/app/src/main/res/drawable/ic_share.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="#000000"
+        android:pathData="M18 16.08c-0.76 0-1.44 0.3-1.96 0.77L8.91 12.7C8.96 12.47 9 12.24 9 12s-0.04-0.47-0.09-0.7l7.05-4.11C16.5 7.69 17.21 8 18 8c1.66 0 3-1.34 3-3s-1.34-3-3-3-3 1.34-3 3c0 0.24 0.04 0.47 0.09 0.7L8.04 9.81C7.5 9.31 6.79 9 6 9c-1.66 0-3 1.34-3 3s1.34 3 3 3c0.79 0 1.5-0.31 2.04-0.81l7.12 4.16c-0.05 0.21-0.08 0.43-0.08 0.65 0 1.61 1.31 2.92 2.92 2.92s2.92-1.31 2.92-2.92-1.31-2.92-2.92-2.92z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_shoe.xml b/app/src/main/res/drawable/ic_shoe.xml
new file mode 100644
index 0000000..d0a11e6
--- /dev/null
+++ b/app/src/main/res/drawable/ic_shoe.xml
@@ -0,0 +1,11 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="27dp"
+    android:height="24dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="27"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M20.01,16.94 L18.6,15.4517 23.08,10.7228 24.5,12.2111ZM15.6852,14.1733 L19.55,9.9733 15.3,5.4767 11.4252,9.6766 10.0052,8.1778 15.3,2.5v0l7.08,7.4733 -5.2748,5.6883zM16.74,16.4967 L15.33,17.985v0l-3.33,3.515L2.5,21.5L2.5,18.7239c0,-0.8867 0.52,-1.6572 1.3,-1.9844 0.58,-0.2428 1.28,-0.5911 1.97,-1.0767l1.38,1.4567c0.09,0.1056 0.22,0.1583 0.35,0.1583 0.13,0 0.26,-0.0528 0.36,-0.1583 0.2,-0.2111 0.2,-0.5383 0,-0.7494L6.58,15.0189c0.27,-0.2533 0.53,-0.5383 0.77,-0.8444l1.27,1.3406c0.09,0.1056 0.23,0.1583 0.35,0.1583 0.12,0 0.25,-0.0528 0.35,-0.1583 0.2,-0.2111 0.2,-0.5383 0,-0.7494l-1.4,-1.4778c0.19,-0.3589 0.34,-0.76 0.45,-1.1822l1.71,1.8156c0.09,0.1056 0.23,0.1583 0.35,0.1583 0.12,0 0.25,-0.0528 0.35,-0.1583 0.19,-0.2111 0.19,-0.5278 0.01,-0.7389l-2.3711,-2.5044 1.42,-1.4883zM13.92,16.4967 L12.25,14.7233 8.92,18.2278C8.14,19.0511 6.87,19.0511 6.09,18.2172L5.9,18.0378 5.43,18.2911c-0.29,0.1478 -0.59,0.285 -0.89,0.4117L4.53,19.3889h6.64z"
+        android:strokeWidth="1" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_shoe_print.xml b/app/src/main/res/drawable/ic_shoe_print.xml
new file mode 100644
index 0000000..b2d530c
--- /dev/null
+++ b/app/src/main/res/drawable/ic_shoe_print.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="#000"
+        android:pathData="M10.74 11.72c0.47 1.23 0.42 2.51-0.99 3.02-2.9 1.07-3.55-1.74-3.59-1.88l4.58-1.14m-5.03-0.81l4.32-1.07c-0.19-1.05 0.1-2.1 0.1-3.34 0-1.68-1.33-4.97-3.45-4.44C4.26 2.66 3.91 5.35 4 6.65c0.12 1.3 1.64 4.08 1.71 4.26m12.14 8.94c-0.03 0.15-0.69 2.95-3.59 1.89-1.4-0.52-1.46-1.8-0.99-3.03l4.58 1.14m2.15-6.2c0.1-1.3-0.24-4-2.67-4.6-2.11-0.55-3.44 2.76-3.44 4.45 0 1.23 0.28 2.28 0.11 3.33l4.3 1.07c0.08-0.18 1.59-2.96 1.7-4.25z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_shoe_prints_many.xml b/app/src/main/res/drawable/ic_shoe_prints_many.xml
new file mode 100644
index 0000000..0d3a77a
--- /dev/null
+++ b/app/src/main/res/drawable/ic_shoe_prints_many.xml
@@ -0,0 +1,4 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:tint="#7E7E7E" android:viewportHeight="24">
+    <path android:fillColor="#FF000000" android:pathData="M6.48 14.38c0.382 1.03 0.342 2.103-0.805 2.53-2.36 0.898-2.888-1.458-2.92-1.575l3.725-0.956M2.39 13.7l3.514-0.897c-0.155-0.88 0.08-1.76 0.08-2.8 0-1.408-1.081-4.166-2.805-3.721-1.969 0.503-2.254 2.758-2.18 3.847 0.097 1.09 1.334 3.42 1.39 3.571m9.875 7.494c-0.024 0.126-0.56 2.473-2.92 1.585-1.138-0.436-1.187-1.51-0.805-2.54l3.726 0.955m1.749-5.197c0.08-1.09-0.196-3.353-2.172-3.856-1.717-0.46-2.799 2.314-2.799 3.73 0 1.032 0.228 1.912 0.09 2.792l3.498 0.897c0.065-0.151 1.293-2.482 1.382-3.563z" android:strokeWidth="0.999999"/>
+    <path android:fillColor="#FF000000" android:pathData="M14.73 7.49c0.256 0.745 0.229 1.52-0.537 1.828-1.574 0.648-1.927-1.053-1.949-1.138l2.487-0.69M12 7l2.345-0.648C14.242 5.717 14.4 5.082 14.4 4.331c0-1.017-0.722-3.009-1.873-2.688-1.314 0.363-1.504 1.991-1.455 2.778C11.137 5.208 11.962 6.891 12 7m6.59 5.411c-0.015 0.091-0.374 1.786-1.948 1.144-0.76-0.314-0.793-1.09-0.538-1.834l2.487 0.69m1.167-3.752c0.054-0.787-0.13-2.422-1.45-2.785-1.145-0.333-1.867 1.67-1.867 2.694 0 0.744 0.152 1.38 0.06 2.015l2.334 0.648c0.044-0.109 0.863-1.792 0.923-2.572z" android:strokeWidth="0.999998"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_shortcut.xml b/app/src/main/res/drawable/ic_shortcut.xml
new file mode 100644
index 0000000..e8a2dad
--- /dev/null
+++ b/app/src/main/res/drawable/ic_shortcut.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:pathData="M15,5l-1.41,1.41L15,7.83L17.17,10H8c-2.76,0 -5,2.24 -5,5v4h2v-4c0,-1.65 1.35,-3 3,-3h9.17L15,14.17l-1.41,1.41L15,17l6,-6L15,5z"
+      android:fillColor="#000000"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_show_chart.xml b/app/src/main/res/drawable/ic_show_chart.xml
new file mode 100644
index 0000000..de73084
--- /dev/null
+++ b/app/src/main/res/drawable/ic_show_chart.xml
@@ -0,0 +1,11 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="#7E7E7E"
+    >
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M3.5,18.49l6,-6.01 4,4L22,6.92l-1.41,-1.41 -7.09,7.97 -4,-4L2,16.99l1.5,1.5z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_signal_cellular_connected_no_internet_4_bar.xml b/app/src/main/res/drawable/ic_signal_cellular_connected_no_internet_4_bar.xml
new file mode 100644
index 0000000..ca121b7
--- /dev/null
+++ b/app/src/main/res/drawable/ic_signal_cellular_connected_no_internet_4_bar.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="#000000"
+        android:pathData="M21 18c0.55 0 1-0.45 1-1v-6c0-0.55-0.45-1-1-1s-1 0.45-1 1v6c0 0.55 0.45 1 1 1zm-1 4h2v-2h-2v2zM4.41 22H18V11c0-1.66 1.34-3 3-3h1V4.41c0-0.89-1.08-1.34-1.71-0.71L3.71 20.29C3.08 20.92 3.52 22 4.41 22z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_smart_button.xml b/app/src/main/res/drawable/ic_smart_button.xml
new file mode 100644
index 0000000..6de309b
--- /dev/null
+++ b/app/src/main/res/drawable/ic_smart_button.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="#000000"
+        android:pathData="M22 9v6c0 1.1-0.9 2-2 2h-1v-2h1V9H4v6h6v2H4c-1.1 0-2-0.9-2-2V9c0-1.1 0.9-2 2-2h16c1.1 0 2 0.9 2 2zm-7.96 8.99c0.18 0.39 0.73 0.39 0.91 0l0.63-1.4 1.4-0.63c0.39-0.18 0.39-0.73 0-0.91l-1.4-0.63-0.63-1.4c-0.18-0.39-0.73-0.39-0.91 0l-0.63 1.4-1.4 0.63c-0.39 0.18-0.39 0.73 0 0.91l1.4 0.63 0.63 1.4zm2.7-4.56c0.1 0.22 0.42 0.22 0.52 0l0.36-0.8 0.8-0.36c0.22-0.1 0.22-0.42 0-0.52l-0.8-0.36-0.36-0.8c-0.1-0.22-0.42-0.22-0.52 0l-0.36 0.8-0.8 0.36c-0.22 0.1-0.22 0.42 0 0.52l0.8 0.36 0.36 0.8z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_sony_wf_800n_case.xml b/app/src/main/res/drawable/ic_sony_wf_800n_case.xml
new file mode 100644
index 0000000..d03918e
--- /dev/null
+++ b/app/src/main/res/drawable/ic_sony_wf_800n_case.xml
@@ -0,0 +1,17 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="12.321217dp"
+    android:height="12.3dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="12.321217"
+    android:viewportHeight="12.3">
+  <path
+      android:pathData="m0.9714,5.2359c0.0231,0.0565 0.2325,0.1658 0.4451,0.2324 0.2193,0.0687 0.5432,0.1306 0.8643,0.1653 0.2564,0.0277 3.8385,0.1078 3.8385,0.1078l0.3317,0c0,0 3.5822,-0.0801 3.8385,-0.1078 0.321,-0.0346 0.645,-0.0966 0.8643,-0.1653 0.2126,-0.0666 0.422,-0.1759 0.4451,-0.2324"
+      android:strokeWidth="0.49999905"
+      android:fillColor="#00000000"
+      android:strokeColor="#000000"/>
+  <path
+      android:pathData="m5.5182,9.0707c-1.2484,-0.0547 -1.9786,-0.1803 -2.5078,-0.4316 -0.9808,-0.4655 -1.657,-1.3807 -1.9166,-2.5937 -0.1031,-0.4818 -0.1853,-1.3119 -0.1408,-1.4218 0.1036,-0.2557 0.8173,-0.69 1.6414,-0.9986 0.5403,-0.2023 1.3346,-0.3697 2.235,-0.4709 0.5768,-0.0648 2.3313,-0.0648 2.908,0 0.8876,0.0997 1.6741,0.2642 2.2172,0.4635 0.8141,0.2988 1.5552,0.749 1.6604,1.0088 0.0282,0.0697 -0.0115,0.639 -0.073,1.047 -0.1549,1.0274 -0.5317,1.8131 -1.1444,2.3859 -0.7633,0.7136 -1.4895,0.9288 -3.4172,1.0129 -0.634,0.0276 -0.8013,0.0275 -1.4622,-0.0015z"
+      android:strokeWidth="0.49999905"
+      android:fillColor="#00000000"
+      android:strokeColor="#000000"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_sony_wf_800n_left.xml b/app/src/main/res/drawable/ic_sony_wf_800n_left.xml
new file mode 100644
index 0000000..c654008
--- /dev/null
+++ b/app/src/main/res/drawable/ic_sony_wf_800n_left.xml
@@ -0,0 +1,39 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="12.321217dp"
+    android:height="12.3dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="12.321217"
+    android:viewportHeight="12.3">
+  <path
+      android:pathData="m8.3489,1.4897c0.0293,0.0131 0.0571,0.0295 0.0848,0.0455 0.0277,0.0164 0.055,0.0328 0.0829,0.0508 0.028,0.0164 0.0564,0.0373 0.0864,0.0583 0.03,0.0196 0.0614,0.0436 0.0952,0.0681 0.0221,0.0164 0.044,0.0328 0.0658,0.0495 0.0217,0.0164 0.0435,0.0344 0.065,0.0521 0.043,0.0357 0.0853,0.0727 0.1268,0.1114 0.0414,0.0386 0.082,0.0789 0.1216,0.1202 0.0395,0.0413 0.078,0.0839 0.1152,0.1274 0.0373,0.0436 0.0733,0.0881 0.1079,0.1336 0.0346,0.0455 0.0678,0.0917 0.0993,0.1385 0.0316,0.0468 0.0615,0.0943 0.0897,0.1421 0.0142,0.0229 0.0277,0.0478 0.041,0.0721 0.0132,0.0229 0.026,0.0482 0.0381,0.0724 0.0316,0.0622 0.0604,0.1215 0.0868,0.1785 0.0264,0.057 0.0503,0.1117 0.072,0.1647 0.0217,0.0531 0.0411,0.1048 0.0586,0.1562 0.0086,0.0262 0.0168,0.0511 0.0247,0.0763 0.0079,0.0262 0.0152,0.0508 0.0217,0.076 0.0069,0.0262 0.0132,0.0508 0.0191,0.0763 0.0059,0.0262 0.0115,0.0514 0.0168,0.0773 0.0102,0.0521 0.0191,0.1051 0.0264,0.1602 0.0076,0.055 0.0135,0.112 0.0184,0.1716 0.0049,0.0596 0.0089,0.1222 0.0119,0.188 0.0022,0.0488 0.0036,0.0963 0.0046,0.1425 0.0008,0.0465 0.0008,0.0917 0.0001,0.1359 -0.0008,0.0442 -0.0024,0.0878 -0.0049,0.1307 -0.0025,0.0429 -0.0059,0.0852 -0.0102,0.1271 -0.0043,0.0419 -0.0096,0.0835 -0.0158,0.1248 -0.0063,0.0413 -0.0135,0.0825 -0.0221,0.1241 -0.0086,0.0413 -0.0181,0.0829 -0.029,0.1248 -0.0109,0.0419 -0.0227,0.0839 -0.0359,0.1268 -0.0132,0.0429 -0.0277,0.0861 -0.0434,0.1304 -0.0158,0.0442 -0.0329,0.0891 -0.0514,0.1349 -0.0184,0.0459 -0.0385,0.093 -0.0599,0.1415 -0.0214,0.0485 -0.0443,0.0979 -0.0687,0.1494 -0.0244,0.0511 -0.0505,0.1038 -0.0781,0.1585 -0.0277,0.0544 -0.0568,0.1107 -0.0878,0.169 -0.031,0.0583 -0.0636,0.1186 -0.098,0.1811 -0.0344,0.0626 -0.0706,0.1274 -0.1087,0.1949 -0.0557,0.0957 -0.1111,0.1893 -0.166,0.2814 -0.0549,0.092 -0.1094,0.1821 -0.1635,0.2702 -0.0541,0.0884 -0.1077,0.1749 -0.161,0.2594 -0.0532,0.0848 -0.1061,0.1677 -0.1585,0.2489 -0.0524,0.0812 -0.1045,0.1605 -0.1561,0.2381 -0.0516,0.0776 -0.1028,0.1536 -0.1536,0.2276 -0.0508,0.074 -0.1012,0.1467 -0.1511,0.2175 -0.05,0.0708 -0.0996,0.1399 -0.1487,0.2073 -0.0491,0.0675 -0.0979,0.1333 -0.1462,0.1972 -0.0483,0.0642 -0.0963,0.1268 -0.1438,0.1874 -0.0475,0.0609 -0.0946,0.1199 -0.1413,0.1775 -0.0024,0.003 -0.0046,0.007 -0.0069,0.01 0.195,-0.0262 0.9686,-0.712 1.3717,-1.2158 0.9235,-1.154 1.3969,-2.5207 1.2556,-3.6246 -0.1292,-1.0093 -0.5792,-1.8334 -1.4153,-2.592 -0.2076,-0.1883 -0.2305,-0.1631 -0.2909,-0.1444 -0.2071,0.1251 -0.2308,0.1146 -0.0911,0.1916z"
+      android:strokeWidth="0.5"
+      android:fillColor="#00000000"
+      android:strokeColor="#000000"/>
+  <path
+      android:pathData="m4.604,6.04c-0.2809,-0.0131 -0.3113,0.0341 -0.5172,-0.1569 -0.1934,-0.2083 -0.239,-0.3049 -0.2411,-0.4931 -0.0022,-0.1952 0.0063,-0.2181 0.1724,-0.6153 0.355,-0.8488 0.7801,-1.7003 0.9789,-1.9606 0.2979,-0.3901 0.7242,-0.6751 1.2023,-0.8217 0.3598,-0.1104 0.9771,-0.0943 1.363,0.0357 1.2229,0.4116 1.8774,1.7234 1.4735,2.9532 -0.2367,0.7207 -0.6147,1.111 -1.0581,1.111z"
+      android:strokeWidth="0.2"
+      android:fillColor="#00000000"
+      android:strokeColor="#000000"/>
+  <path
+      android:pathData="m4.3599,5.5719 l3.3337,0.0593c0.1263,0.002 0.228,0.1035 0.228,0.2319 0,0.1284 -0.1017,0.2342 -0.228,0.2319l-3.3337,-0.0593c-0.1263,-0.002 -0.228,-0.1035 -0.228,-0.2319 0,-0.1284 0.1017,-0.2342 0.228,-0.2319z"
+      android:strokeLineJoin="round"
+      android:strokeWidth="0.2"
+      android:fillColor="#00000000"
+      android:strokeColor="#000000"
+      android:strokeLineCap="round"/>
+  <path
+      android:pathData="m4.0774,11c-0.544,-0.114 -1.1618,-0.5236 -1.4644,-0.9707 -0.4617,-0.6823 -0.5756,-1.3996 -0.359,-2.2625 0.3879,-1.5456 1.5136,-4.3354 2.1103,-5.2296 0.4265,-0.6392 0.9992,-1.0621 1.7298,-1.2773 0.2478,-0.073 0.4791,-0.1025 0.8453,-0.1097 0.2124,-0.003 0.5687,0.0328 0.9069,0.1507 0.4231,0.1461 0.5579,0.1995 0.8281,0.3972 0.3533,0.2561 0.6869,0.6318 0.8824,1.0183 0.2522,0.4986 0.3412,0.8125 0.3648,1.3402 0.035,0.7806 -0.1024,1.2234 -0.7111,2.3006 -0.8738,1.4571 -1.0675,1.7511 -2.0667,2.9761 -0.2024,0.2597 -0.5486,0.656 -0.7695,0.8808 -0.3247,0.3304 -0.456,0.4354 -0.6859,0.5486 -0.5213,0.2565 -1.1074,0.343 -1.6111,0.2375z"
+      android:strokeWidth="0.5"
+      android:fillColor="#00000000"
+      android:strokeColor="#000000"/>
+  <path
+      android:pathData="m3.9326,7.1171h0.3132v2.051h1.1271v0.2636h-1.4402z"
+      android:strokeWidth="0.264583"
+      android:fillColor="#000000"
+      android:strokeColor="#00000000"/>
+  <path
+      android:pathData="m3.8005,6.9839v0.1328,2.4473h1.7051v-0.5273h-1.127v-2.0527zM4.0642,7.2496h0.0488v2.0508h-0.0488z"
+      android:strokeWidth="0.264583"
+      android:fillColor="#000000"
+      android:strokeColor="#00000000"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_sony_wf_800n_right.xml b/app/src/main/res/drawable/ic_sony_wf_800n_right.xml
new file mode 100644
index 0000000..e2a1d41
--- /dev/null
+++ b/app/src/main/res/drawable/ic_sony_wf_800n_right.xml
@@ -0,0 +1,39 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="12.321217dp"
+    android:height="12.3dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="12.321217"
+    android:viewportHeight="12.3">
+  <path
+      android:pathData="m7.9485,8.3399q0.1008,0.0341 0.1953,0.1457 0.0961,0.1116 0.1922,0.307l0.3178,0.6325h-0.3364l-0.2961,-0.5938q-0.1147,-0.2325 -0.2232,-0.3085 -0.107,-0.076 -0.293,-0.076L7.1641,8.4468v0.9782h-0.3132v-2.3146h0.7069q0.3969,0 0.5922,0.1659 0.1953,0.1659 0.1953,0.5007 0,0.2186 -0.1023,0.3628 -0.1008,0.1442 -0.2946,0.2zM7.1641,7.3679v0.8217h0.3938q0.2263,0 0.3411,-0.1039 0.1163,-0.1054 0.1163,-0.3085 0,-0.2031 -0.1163,-0.3054 -0.1147,-0.1039 -0.3411,-0.1039z"
+      android:strokeWidth="0.264583"
+      android:fillColor="#000000"
+      android:strokeColor="#00000000"/>
+  <path
+      android:pathData="m6.7195,6.9789v2.5781h0.5762v-0.9785h0.209c0.106,0 0.175,0.023 0.2168,0.0527 0.0383,0.0268 0.11,0.1125 0.1816,0.2578v0.002l0.332,0.666h0.6328l-0.4141,-0.8223v-0.002c-0.0668,-0.1356 -0.1349,-0.2457 -0.209,-0.332 -0.0229,-0.027 -0.0533,-0.036 -0.0781,-0.0586 0.0619,-0.0418 0.1407,-0.0643 0.1836,-0.125 0.0865,-0.1218 0.127,-0.2731 0.127,-0.4394 0,-0.249 -0.0785,-0.4626 -0.2422,-0.6016 -0.1639,-0.1392 -0.3947,-0.1973 -0.6777,-0.1973zM6.9832,7.2426h0.0488v0.125,0.9473 0.008,0.9707h-0.0488zM7.6941,7.2797c0.1482,0.0194 0.3006,0.0378 0.3711,0.0977 0.0967,0.0822 0.1484,0.2029 0.1484,0.4004 0,0.1251 -0.0282,0.2148 -0.0781,0.2852L8.1355,8.0649c-0.0301,0.0431 -0.0878,0.0643 -0.1367,0.0918 0.0941,-0.098 0.1484,-0.2292 0.1484,-0.3789 0,-0.16 -0.051,-0.3074 -0.1602,-0.4043 -0.074,-0.067 -0.1869,-0.0752 -0.293,-0.0937zM7.2957,7.5004h0.2617c0.1307,0 0.2076,0.0301 0.252,0.0703h0.002c0.0449,0.0395 0.0723,0.0969 0.0723,0.207 0,0.1102 -0.028,0.1671 -0.0742,0.209 -0.0444,0.0402 -0.1213,0.0703 -0.252,0.0703h-0.2617zM7.9285,8.2035c-0.0069,0.002 -0.0083,0.008 -0.0156,0.01l-0.0957,0.0273v0.1582c-0.083,-0.0446 -0.1767,-0.0726 -0.2832,-0.0762h0.0234c0.1477,0 0.2679,-0.0478 0.3711,-0.1191zM7.9168,8.4731c0.0361,0.0144 0.0784,0.0403 0.127,0.0977v0.002c0.0534,0.062 0.1131,0.1558 0.1738,0.2793l0.2227,0.4414h-0.041l-0.2598,-0.5215c-0.068,-0.1379 -0.1406,-0.2243 -0.2227,-0.2988z"
+      android:strokeWidth="0.264583"
+      android:fillColor="#000000"
+      android:strokeColor="#00000000"/>
+  <path
+      android:pathData="m3.9723,1.4897c-0.0293,0.0131 -0.0571,0.0295 -0.0848,0.0455 -0.0277,0.0164 -0.055,0.0328 -0.0829,0.0508 -0.028,0.0164 -0.0564,0.0373 -0.0864,0.0583 -0.03,0.0196 -0.0614,0.0436 -0.0952,0.0681 -0.0221,0.0164 -0.044,0.0328 -0.0658,0.0495 -0.0217,0.0164 -0.0435,0.0344 -0.065,0.0521 -0.043,0.0357 -0.0853,0.0727 -0.1268,0.1114 -0.0414,0.0386 -0.082,0.0789 -0.1216,0.1202 -0.0395,0.0413 -0.078,0.0839 -0.1152,0.1274 -0.0373,0.0436 -0.0733,0.0881 -0.1079,0.1336 -0.0346,0.0455 -0.0678,0.0917 -0.0993,0.1385 -0.0316,0.0468 -0.0615,0.0943 -0.0897,0.1421 -0.0142,0.0229 -0.0277,0.0478 -0.041,0.0721 -0.0132,0.0229 -0.026,0.0482 -0.0381,0.0724 -0.0316,0.0622 -0.0604,0.1215 -0.0868,0.1785 -0.0264,0.057 -0.0503,0.1117 -0.072,0.1647 -0.0217,0.0531 -0.0411,0.1048 -0.0586,0.1562 -0.0086,0.0262 -0.0168,0.0511 -0.0247,0.0763 -0.0079,0.0262 -0.0152,0.0508 -0.0217,0.076 -0.0069,0.0262 -0.0132,0.0508 -0.0191,0.0763 -0.0059,0.0262 -0.0115,0.0514 -0.0168,0.0773 -0.0102,0.0521 -0.0191,0.1051 -0.0264,0.1602 -0.0076,0.055 -0.0135,0.112 -0.0184,0.1716 -0.0049,0.0596 -0.0089,0.1222 -0.0119,0.188 -0.0022,0.0488 -0.0036,0.0963 -0.0046,0.1425 -0.0008,0.0465 -0.0008,0.0917 -0.0001,0.1359 0.0008,0.0442 0.0024,0.0878 0.0049,0.1307 0.0025,0.0429 0.0059,0.0852 0.0102,0.1271 0.0043,0.0419 0.0096,0.0835 0.0158,0.1248 0.0063,0.0413 0.0135,0.0825 0.0221,0.1241 0.0086,0.0413 0.0181,0.0829 0.029,0.1248 0.0109,0.0419 0.0227,0.0839 0.0359,0.1268 0.0132,0.0429 0.0277,0.0861 0.0434,0.1304 0.0158,0.0442 0.0329,0.0891 0.0514,0.1349 0.0184,0.0459 0.0385,0.093 0.0599,0.1415 0.0214,0.0485 0.0443,0.0979 0.0687,0.1494 0.0244,0.0511 0.0505,0.1038 0.0781,0.1585 0.0277,0.0544 0.0568,0.1107 0.0878,0.169 0.031,0.0583 0.0636,0.1186 0.098,0.1811 0.0344,0.0626 0.0706,0.1274 0.1087,0.1949 0.0557,0.0957 0.1111,0.1893 0.166,0.2814 0.0549,0.092 0.1094,0.1821 0.1635,0.2702 0.0541,0.0884 0.1077,0.1749 0.161,0.2594 0.0532,0.0848 0.1061,0.1677 0.1585,0.2489 0.0524,0.0812 0.1045,0.1605 0.1561,0.2381 0.0516,0.0776 0.1028,0.1536 0.1536,0.2276 0.0508,0.074 0.1012,0.1467 0.1511,0.2175 0.05,0.0708 0.0996,0.1399 0.1487,0.2073 0.0491,0.0675 0.0979,0.1333 0.1462,0.1972 0.0483,0.0642 0.0963,0.1268 0.1438,0.1874 0.0475,0.0609 0.0946,0.1199 0.1413,0.1775 0.0024,0.003 0.0046,0.007 0.0069,0.01 -0.195,-0.0262 -0.9686,-0.712 -1.3717,-1.2158 -0.9235,-1.154 -1.3969,-2.5207 -1.2556,-3.6246 0.1292,-1.0093 0.5792,-1.8334 1.4153,-2.592 0.2076,-0.1883 0.2305,-0.1631 0.2909,-0.1444 0.2071,0.1251 0.2308,0.1146 0.0911,0.1916z"
+      android:strokeWidth="0.5"
+      android:fillColor="#00000000"
+      android:strokeColor="#000000"/>
+  <path
+      android:pathData="m7.7172,6.04c0.2809,-0.0131 0.3113,0.0341 0.5172,-0.1569 0.1934,-0.2083 0.239,-0.3049 0.2411,-0.4931 0.0022,-0.1952 -0.0063,-0.2181 -0.1724,-0.6153 -0.355,-0.8488 -0.7801,-1.7003 -0.9789,-1.9606 -0.2979,-0.3901 -0.7242,-0.6751 -1.2023,-0.8217 -0.3598,-0.1104 -0.9771,-0.0943 -1.363,0.0357 -1.2229,0.4116 -1.8774,1.7234 -1.4735,2.9532 0.2367,0.7207 0.6147,1.111 1.0581,1.111z"
+      android:strokeWidth="0.2"
+      android:fillColor="#00000000"
+      android:strokeColor="#000000"/>
+  <path
+      android:pathData="m7.9613,5.5719 l-3.3337,0.0593c-0.1263,0.002 -0.228,0.1035 -0.228,0.2319 0,0.1284 0.1017,0.2342 0.228,0.2319l3.3337,-0.0593c0.1263,-0.002 0.228,-0.1035 0.228,-0.2319 0,-0.1284 -0.1017,-0.2342 -0.228,-0.2319z"
+      android:strokeLineJoin="round"
+      android:strokeWidth="0.2"
+      android:fillColor="#00000000"
+      android:strokeColor="#000000"
+      android:strokeLineCap="round"/>
+  <path
+      android:pathData="m8.2438,11c0.544,-0.114 1.1618,-0.5236 1.4644,-0.9707 0.4617,-0.6823 0.5756,-1.3996 0.359,-2.2625 -0.3879,-1.5456 -1.5136,-4.3354 -2.1103,-5.2296 -0.4265,-0.6392 -0.9992,-1.0621 -1.7298,-1.2773 -0.2478,-0.073 -0.4791,-0.1025 -0.8453,-0.1097 -0.2124,-0.003 -0.5687,0.0328 -0.9069,0.1507 -0.4231,0.1461 -0.5579,0.1995 -0.8281,0.3972 -0.3533,0.2561 -0.6869,0.6318 -0.8824,1.0183 -0.2522,0.4986 -0.3412,0.8125 -0.3648,1.3402 -0.035,0.7806 0.1024,1.2234 0.7111,2.3006 0.8738,1.4571 1.0675,1.7511 2.0667,2.9761 0.2024,0.2597 0.5486,0.656 0.7695,0.8808 0.3247,0.3304 0.456,0.4354 0.6859,0.5486 0.5213,0.2565 1.1074,0.343 1.6111,0.2375z"
+      android:strokeWidth="0.5"
+      android:fillColor="#00000000"
+      android:strokeColor="#000000"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_speaker.xml b/app/src/main/res/drawable/ic_speaker.xml
new file mode 100644
index 0000000..c5fd13d
--- /dev/null
+++ b/app/src/main/res/drawable/ic_speaker.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M17,2L7,2c-1.1,0 -2,0.9 -2,2v16c0,1.1 0.9,1.99 2,1.99L17,22c1.1,0 2,-0.9 2,-2L19,4c0,-1.1 -0.9,-2 -2,-2zM12,4c1.1,0 2,0.9 2,2s-0.9,2 -2,2c-1.11,0 -2,-0.9 -2,-2s0.89,-2 2,-2zM12,20c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5 -2.24,5 -5,5zM12,12c-1.66,0 -3,1.34 -3,3s1.34,3 3,3 3,-1.34 3,-3 -1.34,-3 -3,-3z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_star_gold.xml b/app/src/main/res/drawable/ic_star_gold.xml
new file mode 100644
index 0000000..021b800
--- /dev/null
+++ b/app/src/main/res/drawable/ic_star_gold.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="@android:color/holo_orange_light"
+        android:pathData="M12,17.27L18.18,21l-1.64,-7.03L22,9.24l-7.19,-0.61L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_star_gray.xml b/app/src/main/res/drawable/ic_star_gray.xml
new file mode 100644
index 0000000..24d9e79
--- /dev/null
+++ b/app/src/main/res/drawable/ic_star_gray.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="#7E7E7E"
+        android:pathData="M12,17.27L18.18,21l-1.64,-7.03L22,9.24l-7.19,-0.61L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_stop.xml b/app/src/main/res/drawable/ic_stop.xml
new file mode 100644
index 0000000..aac111d
--- /dev/null
+++ b/app/src/main/res/drawable/ic_stop.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M6,6h12v12H6z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_surround.xml b/app/src/main/res/drawable/ic_surround.xml
new file mode 100644
index 0000000..7b89bba
--- /dev/null
+++ b/app/src/main/res/drawable/ic_surround.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="#000000"
+        android:pathData="M20,4L4,4c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,6c0,-1.1 -0.9,-2 -2,-2zM7.76,16.24l-1.41,1.41C4.78,16.1 4,14.05 4,12c0,-2.05 0.78,-4.1 2.34,-5.66l1.41,1.41C6.59,8.93 6,10.46 6,12s0.59,3.07 1.76,4.24zM12,16c-2.21,0 -4,-1.79 -4,-4s1.79,-4 4,-4 4,1.79 4,4 -1.79,4 -4,4zM17.66,17.66l-1.41,-1.41C17.41,15.07 18,13.54 18,12s-0.59,-3.07 -1.76,-4.24l1.41,-1.41C19.22,7.9 20,9.95 20,12c0,2.05 -0.78,4.1 -2.34,5.66zM12,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_switch_left.xml b/app/src/main/res/drawable/ic_switch_left.xml
new file mode 100644
index 0000000..095c9aa
--- /dev/null
+++ b/app/src/main/res/drawable/ic_switch_left.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="#000000"
+        android:pathData="M8.5 8.62v6.76L5.12 12 8.5 8.62m-4.79 2.67c-0.39 0.39-0.39 1.02 0 1.41l4.59 4.59c0.62 0.63 1.7 0.19 1.7-0.7V7.41c0-0.89-1.08-1.34-1.71-0.71l-4.58 4.59zM14 7.41v9.17c0 0.89 1.08 1.34 1.71 0.71l4.59-4.59c0.39-0.39 0.39-1.02 0-1.41L15.71 6.7C15.08 6.08 14 6.52 14 7.41z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_switch_right.xml b/app/src/main/res/drawable/ic_switch_right.xml
new file mode 100644
index 0000000..3fbf746
--- /dev/null
+++ b/app/src/main/res/drawable/ic_switch_right.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M15.5,15.38V8.62L18.88,12L15.5,15.38M20.29,12.71c0.39,-0.39 0.39,-1.02 0,-1.41l-4.59,-4.59C15.08,6.08 14,6.52 14,7.41v9.17c0,0.89 1.08,1.34 1.71,0.71L20.29,12.71zM10,16.59V7.41c0,-0.89 -1.08,-1.34 -1.71,-0.71l-4.59,4.59c-0.39,0.39 -0.39,1.02 0,1.41l4.59,4.59C8.92,17.92 10,17.48 10,16.59z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_sync.xml b/app/src/main/res/drawable/ic_sync.xml
new file mode 100644
index 0000000..ffce01f
--- /dev/null
+++ b/app/src/main/res/drawable/ic_sync.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="#000000"
+        android:pathData="M12 4.002233V1L7.998977 5.00093 12 9.00186V6.001674c3.309286 0 6.000512 2.689117 6.000512 6.000373 0 1.00893 -0.251727 1.968744 -0.699924 2.799627l1.459197 1.459163C19.539524 15.03293 20 13.571721 20 12.002047 20 7.581581 16.418521 4.000186 11.997953 4.000186Zm0 13.998139c-3.311333 0 -6.000512 -2.689116 -6.000512 -6.000372 0 -1.00893 0.249681 -1.968744 0.699924 -2.799628L5.2381683 7.741209C4.4604758 8.969116 4 10.430326 4 12c0 4.420465 3.579432 8.00186 8 8.00186V23L15.998977 19.001116 12 15.000186Z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_tabs.xml b/app/src/main/res/drawable/ic_tabs.xml
new file mode 100644
index 0000000..21d9a1a
--- /dev/null
+++ b/app/src/main/res/drawable/ic_tabs.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="#7E7E7E">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M21,3L3,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h18c1.1,0 2,-0.9 2,-2L23,5c0,-1.1 -0.9,-2 -2,-2zM21,19L3,19L3,5h10v4h8v10z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_temperature.xml b/app/src/main/res/drawable/ic_temperature.xml
new file mode 100644
index 0000000..1ad3252
--- /dev/null
+++ b/app/src/main/res/drawable/ic_temperature.xml
@@ -0,0 +1,12 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:pathData="m11.182,1.149c-1.543,0 -2.786,1.452 -2.786,3.255v8.775a5.82,5.163 0,0 0,-3.034 4.53,5.82 5.163,0 0,0 5.82,5.163 5.82,5.163 0,0 0,5.82 -5.163,5.82 5.163,0 0,0 -3.034,-4.533L13.968,4.404c0,-1.803 -1.242,-3.255 -2.786,-3.255zM8.475,4.763h1.93c0.04,0 0.073,0.032 0.073,0.072v0.873c0,0.04 -0.033,0.072 -0.073,0.072L8.475,5.781c-0.04,0 -0.073,-0.032 -0.073,-0.072L8.402,4.835c0,-0.04 0.033,-0.072 0.073,-0.072zM8.5,7.779h2.593c0.054,0 0.098,0.032 0.098,0.072v0.873c0,0.04 -0.044,0.072 -0.098,0.072L8.5,8.798c-0.054,0 -0.098,-0.032 -0.098,-0.072L8.402,7.852c0,-0.04 0.044,-0.072 0.098,-0.072zM8.475,10.796h1.93c0.04,0 0.073,0.032 0.073,0.072v0.873c0,0.04 -0.033,0.072 -0.073,0.072L8.475,11.814c-0.04,0 -0.073,-0.032 -0.073,-0.072v-0.873c0,-0.04 0.033,-0.072 0.073,-0.072z"
+      android:strokeWidth="1.5"
+      android:fillColor="#00000000"
+      android:strokeColor="#7e7e7e"
+      android:fillAlpha="0"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_thermometer.xml b/app/src/main/res/drawable/ic_thermometer.xml
new file mode 100644
index 0000000..cd618db
--- /dev/null
+++ b/app/src/main/res/drawable/ic_thermometer.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="960"
+    android:viewportHeight="960">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M480,840Q397,840 338.5,781.5Q280,723 280,640Q280,592 301,550.5Q322,509 360,480L360,240Q360,190 395,155Q430,120 480,120Q530,120 565,155Q600,190 600,240L600,480Q638,509 659,550.5Q680,592 680,640Q680,723 621.5,781.5Q563,840 480,840ZM480,760Q530,760 565,725Q600,690 600,640Q600,611 587.5,586Q575,561 552,544L520,520L520,400L520,240Q520,223 508.5,211.5Q497,200 480,200Q463,200 451.5,211.5Q440,223 440,240L440,400L440,520L408,544Q385,561 372.5,586Q360,611 360,640Q360,690 395,725Q430,760 480,760ZM480,640Q480,640 480,640Q480,640 480,640Q480,640 480,640Q480,640 480,640L480,640L480,640L480,640L480,640L480,640Q480,640 480,640Q480,640 480,640Q480,640 480,640Q480,640 480,640Z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_timer.xml b/app/src/main/res/drawable/ic_timer.xml
new file mode 100644
index 0000000..6a88fb6
--- /dev/null
+++ b/app/src/main/res/drawable/ic_timer.xml
@@ -0,0 +1,3 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24" android:tint="#7E7E7E">
+    <path android:fillColor="@android:color/white" android:pathData="M15.07 1.01h-6v2h6v-2zm-4 13h2v-6h-2v6zm8.03-6.62l1.42-1.42c-0.43-0.51-0.9-0.99-1.41-1.41l-1.42 1.42C16.14 4.74 14.19 4 12.07 4c-4.97 0-9 4.03-9 9s4.02 9 9 9 9-4.03 9-9c0-2.11-0.74-4.06-1.97-5.61zm-7.03 12.62c-3.87 0-7-3.13-7-7s3.13-7 7-7 7 3.13 7 7-3.13 7-7 7z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_touch.xml b/app/src/main/res/drawable/ic_touch.xml
new file mode 100644
index 0000000..e93d7f9
--- /dev/null
+++ b/app/src/main/res/drawable/ic_touch.xml
@@ -0,0 +1,3 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:tint="#7E7E7E" android:viewportWidth="24" android:viewportHeight="24">
+    <path android:fillColor="@android:color/white" android:pathData="M18.19 12.44l-3.24-1.62c1.29-1 2.12-2.56 2.12-4.32 0-3.03-2.47-5.5-5.5-5.5s-5.5 2.47-5.5 5.5c0 2.13 1.22 3.98 3 4.89v3.26c-2.15-0.46-2.02-0.44-2.26-0.44-0.53 0-1.03 0.21-1.41 0.59L4 16.22l5.09 5.09C9.52 21.75 10.12 22 10.74 22h6.3c0.98 0 1.81-0.7 1.97-1.67l0.8-4.71c0.22-1.3-0.43-2.58-1.62-3.18zm-0.35 2.85L17.04 20h-6.3c-0.09 0-0.17-0.04-0.24-0.1l-3.68-3.68 4.25 0.89V6.5c0-0.28 0.22-0.5 0.5-0.5s0.5 0.22 0.5 0.5v6h1.76l3.46 1.73c0.4 0.2 0.62 0.63 0.55 1.06zM8.07 6.5c0-1.93 1.57-3.5 3.5-3.5s3.5 1.57 3.5 3.5c0 0.95-0.38 1.81-1 2.44V6.5c0-1.38-1.12-2.5-2.5-2.5s-2.5 1.12-2.5 2.5v2.44c-0.62-0.63-1-1.49-1-2.44z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_translate.xml b/app/src/main/res/drawable/ic_translate.xml
new file mode 100644
index 0000000..9529ce2
--- /dev/null
+++ b/app/src/main/res/drawable/ic_translate.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M12.65 15.67c0.14-0.36 0.05-0.77-0.23-1.05l-2.09-2.06 0.03-0.03c1.74-1.94 2.98-4.17 3.71-6.53h1.94C16.55 6 17 5.55 17 5.01V4.99C17 4.45 16.55 4 16.01 4H10V3c0-0.55-0.45-1-1-1S8 2.45 8 3v1H1.99C1.45 4 1 4.45 1 4.99c0 0.55 0.45 0.99 0.99 0.99h10.18C11.5 7.92 10.44 9.75 9 11.35c-0.81-0.89-1.49-1.86-2.06-2.88C6.78 8.18 6.49 8 6.16 8 5.47 8 5.03 8.75 5.37 9.35c0.63 1.13 1.4 2.21 2.3 3.21L3.3 16.87c-0.4 0.39-0.4 1.03 0 1.42 0.39 0.39 1.02 0.39 1.42 0L9 14l2.02 2.02c0.51 0.51 1.38 0.32 1.63-0.35zM17.5 10c-0.6 0-1.14 0.37-1.35 0.94l-3.67 9.8C12.24 21.35 12.7 22 13.35 22c0.39 0 0.74-0.24 0.88-0.61L15.12 19h4.75l0.9 2.39c0.14 0.36 0.49 0.61 0.88 0.61 0.65 0 1.11-0.65 0.88-1.26l-3.67-9.8C18.64 10.37 18.1 10 17.5 10zm-1.62 7l1.62-4.33L19.12 17h-3.24z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_tws_case.xml b/app/src/main/res/drawable/ic_tws_case.xml
new file mode 100644
index 0000000..3186591
--- /dev/null
+++ b/app/src/main/res/drawable/ic_tws_case.xml
@@ -0,0 +1,15 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="45sp"
+    android:height="45sp"
+    android:viewportWidth="25"
+    android:viewportHeight="25"
+    android:tint="#7E7E7E">
+  <path
+      android:pathData="M8.1975,3L16.8025,3A5.7287,5.7287 0,0 1,22.5312 8.7287L22.5312,16.2713A5.7287,5.7287 0,0 1,16.8025 22L8.1975,22A5.7287,5.7287 0,0 1,2.4688 16.2713L2.4688,8.7287A5.7287,5.7287 0,0 1,8.1975 3z"
+      android:strokeWidth="2"
+      android:strokeColor="#000000"/>
+  <path
+      android:pathData="M2.4138,8.6137L22.5862,8.6137"
+      android:strokeWidth="2"
+      android:strokeColor="#000000"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_u_turn_left.xml b/app/src/main/res/drawable/ic_u_turn_left.xml
new file mode 100644
index 0000000..abec9ce
--- /dev/null
+++ b/app/src/main/res/drawable/ic_u_turn_left.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="?attr/colorControlNormal">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M18,9v12h-2V9c0,-2.21 -1.79,-4 -4,-4S8,6.79 8,9v4.17l1.59,-1.59L11,13l-4,4l-4,-4l1.41,-1.41L6,13.17V9c0,-3.31 2.69,-6 6,-6S18,5.69 18,9z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_u_turn_right.xml b/app/src/main/res/drawable/ic_u_turn_right.xml
new file mode 100644
index 0000000..a6ee472
--- /dev/null
+++ b/app/src/main/res/drawable/ic_u_turn_right.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="?attr/colorControlNormal">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M6,9v12h2V9c0,-2.21 1.79,-4 4,-4s4,1.79 4,4v4.17l-1.59,-1.59L13,13l4,4l4,-4l-1.41,-1.41L18,13.17V9c0,-3.31 -2.69,-6 -6,-6S6,5.69 6,9z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_verified.xml b/app/src/main/res/drawable/ic_verified.xml
new file mode 100644
index 0000000..b7d0aeb
--- /dev/null
+++ b/app/src/main/res/drawable/ic_verified.xml
@@ -0,0 +1,13 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="#7E7E7E">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M23,11.99l-2.44,-2.79l0.34,-3.69l-3.61,-0.82L15.4,1.5L12,2.96L8.6,1.5L6.71,4.69L3.1,5.5L3.44,9.2L1,11.99l2.44,2.79l-0.34,3.7l3.61,0.82L8.6,22.5l3.4,-1.47l3.4,1.46l1.89,-3.19l3.61,-0.82l-0.34,-3.69L23,11.99zM19.05,13.47l-0.56,0.65l0.08,0.85l0.18,1.95l-1.9,0.43l-0.84,0.19l-0.44,0.74l-0.99,1.68l-1.78,-0.77L12,18.85l-0.79,0.34l-1.78,0.77l-0.99,-1.67l-0.44,-0.74l-0.84,-0.19l-1.9,-0.43l0.18,-1.96l0.08,-0.85l-0.56,-0.65l-1.29,-1.47l1.29,-1.48l0.56,-0.65L5.43,9.01L5.25,7.07l1.9,-0.43l0.84,-0.19l0.44,-0.74l0.99,-1.68l1.78,0.77L12,5.14l0.79,-0.34l1.78,-0.77l0.99,1.68l0.44,0.74l0.84,0.19l1.9,0.43l-0.18,1.95l-0.08,0.85l0.56,0.65l1.29,1.47L19.05,13.47z"/>
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M10.09,13.75l-2.32,-2.33l-1.48,1.49l3.8,3.81l7.34,-7.36l-1.48,-1.49z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_vibration.xml b/app/src/main/res/drawable/ic_vibration.xml
new file mode 100644
index 0000000..eab11f4
--- /dev/null
+++ b/app/src/main/res/drawable/ic_vibration.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="#000000"
+        android:pathData="M1 15c0.55 0 1-0.45 1-1v-4c0-0.55-0.45-1-1-1s-1 0.45-1 1v4c0 0.55 0.45 1 1 1zm3 2c0.55 0 1-0.45 1-1V8c0-0.55-0.45-1-1-1S3 7.45 3 8v8c0 0.55 0.45 1 1 1zm18-7v4c0 0.55 0.45 1 1 1s1-0.45 1-1v-4c0-0.55-0.45-1-1-1s-1 0.45-1 1zm-2 7c0.55 0 1-0.45 1-1V8c0-0.55-0.45-1-1-1s-1 0.45-1 1v8c0 0.55 0.45 1 1 1zM16.5 3h-9C6.67 3 6 3.67 6 4.5v15C6 20.33 6.67 21 7.5 21h9c0.83 0 1.5-0.67 1.5-1.5v-15C18 3.67 17.33 3 16.5 3zM16 19H8V5h8v14z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_videogame.xml b/app/src/main/res/drawable/ic_videogame.xml
new file mode 100644
index 0000000..c50c195
--- /dev/null
+++ b/app/src/main/res/drawable/ic_videogame.xml
@@ -0,0 +1,4 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:tint="#7E7E7E" android:viewportWidth="24" android:viewportHeight="24">
+    <path android:fillColor="@android:color/white" android:pathData="M21 6H3C1.9 6 1 6.9 1 8v8c0 1.1 0.9 2 2 2h18c1.1 0 2-0.9 2-2V8c0-1.1-0.9-2-2-2zm0 10H3V8h18v8zM6 15h2v-2h2v-2H8V9H6v2H4v2h2z"/>
+    <path android:fillColor="@android:color/white" android:pathData="M13 13.5a1.5 1.5 0 1 1 3 0 1.5 1.5 0 1 1-3 0m4-3a1.5 1.5 0 1 1 3 0 1.5 1.5 0 1 1-3 0"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_voice.xml b/app/src/main/res/drawable/ic_voice.xml
new file mode 100644
index 0000000..85151bc
--- /dev/null
+++ b/app/src/main/res/drawable/ic_voice.xml
@@ -0,0 +1,3 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:tint="#7E7E7E" android:viewportWidth="24" android:viewportHeight="24">
+    <path android:fillColor="@android:color/white" android:pathData="M9 13c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0-6c1.1 0 2 0.9 2 2s-0.9 2-2 2-2-0.9-2-2 0.9-2 2-2zm0 8c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4zm-6 4c0.22-0.72 3.31-2 6-2 2.7 0 5.8 1.29 6 2H3zM15.08 7.05c0.84 1.18 0.84 2.71 0 3.89l1.68 1.69c2.02-2.02 2.02-5.07 0-7.27l-1.68 1.69zM20.07 2l-1.63 1.63c2.77 3.02 2.77 7.56 0 10.74L20.07 16c3.9-3.89 3.91-9.95 0-14z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_volume_off.xml b/app/src/main/res/drawable/ic_volume_off.xml
new file mode 100644
index 0000000..4e5fae4
--- /dev/null
+++ b/app/src/main/res/drawable/ic_volume_off.xml
@@ -0,0 +1,5 @@
+<vector android:height="24dp" android:tint="#7E7E7E"
+    android:viewportHeight="24" android:viewportWidth="24"
+    android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
+    <path android:fillColor="@android:color/white" android:pathData="M16.5,12c0,-1.77 -1.02,-3.29 -2.5,-4.03v2.21l2.45,2.45c0.03,-0.2 0.05,-0.41 0.05,-0.63zM19,12c0,0.94 -0.2,1.82 -0.54,2.64l1.51,1.51C20.63,14.91 21,13.5 21,12c0,-4.28 -2.99,-7.86 -7,-8.77v2.06c2.89,0.86 5,3.54 5,6.71zM4.27,3L3,4.27 7.73,9L3,9v6h4l5,5v-6.73l4.25,4.25c-0.67,0.52 -1.42,0.93 -2.25,1.18v2.06c1.38,-0.31 2.63,-0.95 3.69,-1.81L19.73,21 21,19.73l-9,-9L4.27,3zM12,4L9.91,6.09 12,8.18L12,4z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_volume_up.xml b/app/src/main/res/drawable/ic_volume_up.xml
new file mode 100644
index 0000000..88bd805
--- /dev/null
+++ b/app/src/main/res/drawable/ic_volume_up.xml
@@ -0,0 +1,3 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:autoMirrored="true" android:tint="#7E7E7E" android:viewportWidth="24" android:viewportHeight="24">
+    <path android:fillColor="@android:color/white" android:pathData="M3 9v6h4l5 5V4L7 9H3zm7-0.17v6.34L7.83 13H5v-2h2.83L10 8.83zM16.5 12c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-0.73 2.5-2.25 2.5-4.02zM14 3.23v2.06c2.89 0.86 5 3.54 5 6.71s-2.11 5.85-5 6.71v2.06c4.01-0.91 7-4.49 7-8.77 0-4.28-2.99-7.86-7-8.77z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_vpn_key.xml b/app/src/main/res/drawable/ic_vpn_key.xml
new file mode 100644
index 0000000..6afa96c
--- /dev/null
+++ b/app/src/main/res/drawable/ic_vpn_key.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="#000000"
+        android:pathData="M12.65 10C11.7 7.31 8.9 5.5 5.77 6.12 3.48 6.58 1.62 8.41 1.14 10.7 0.32 14.57 3.26 18 7 18c2.61 0 4.83-1.67 5.65-4H17v2c0 1.1 0.9 2 2 2s2-0.9 2-2v-2c1.1 0 2-0.9 2-2s-0.9-2-2-2h-8.35zM7 14c-1.1 0-2-0.9-2-2s0.9-2 2-2 2 0.9 2 2-0.9 2-2 2z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_warning.xml b/app/src/main/res/drawable/ic_warning.xml
new file mode 100644
index 0000000..506d058
--- /dev/null
+++ b/app/src/main/res/drawable/ic_warning.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="?attr/colorControlNormal">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M4.47,21h15.06c1.54,0 2.5,-1.67 1.73,-3L13.73,4.99c-0.77,-1.33 -2.69,-1.33 -3.46,0L2.74,18c-0.77,1.33 0.19,3 1.73,3zM12,14c-0.55,0 -1,-0.45 -1,-1v-2c0,-0.55 0.45,-1 1,-1s1,0.45 1,1v2c0,0.55 -0.45,1 -1,1zM13,18h-2v-2h2v2z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_warning_gray.xml b/app/src/main/res/drawable/ic_warning_gray.xml
new file mode 100644
index 0000000..98ce891
--- /dev/null
+++ b/app/src/main/res/drawable/ic_warning_gray.xml
@@ -0,0 +1,5 @@
+<vector android:height="24dp" android:tint="#7E7E7E"
+    android:viewportHeight="24" android:viewportWidth="24"
+    android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
+    <path android:fillColor="@android:color/white" android:pathData="M1,21h22L12,2 1,21zM13,18h-2v-2h2v2zM13,14h-2v-4h2v4z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_watch.xml b/app/src/main/res/drawable/ic_watch.xml
new file mode 100644
index 0000000..aaf26a3
--- /dev/null
+++ b/app/src/main/res/drawable/ic_watch.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="#7E7E7E">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M14.31,2l0.41,2.48C13.87,4.17 12.96,4 12,4c-0.95,0 -1.87,0.17 -2.71,0.47L9.7,2h4.61m0.41,17.52L14.31,22L9.7,22l-0.41,-2.47c0.84,0.3 1.76,0.47 2.71,0.47 0.96,0 1.87,-0.17 2.72,-0.48M16,0L8,0l-0.95,5.73C5.19,7.19 4,9.45 4,12s1.19,4.81 3.05,6.27L8,24h8l0.96,-5.73C18.81,16.81 20,14.54 20,12s-1.19,-4.81 -3.04,-6.27L16,0zM12,18c-3.31,0 -6,-2.69 -6,-6s2.69,-6 6,-6 6,2.69 6,6 -2.69,6 -6,6z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_watch_bw.xml b/app/src/main/res/drawable/ic_watch_bw.xml
new file mode 100644
index 0000000..e242792
--- /dev/null
+++ b/app/src/main/res/drawable/ic_watch_bw.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="?attr/colorControlNormal">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M20,12c0,-2.54 -1.19,-4.81 -3.04,-6.27L16,0H8l-0.95,5.73C5.19,7.19 4,9.45 4,12s1.19,4.81 3.05,6.27L8,24h8l0.96,-5.73C18.81,16.81 20,14.54 20,12zM6,12c0,-3.31 2.69,-6 6,-6s6,2.69 6,6 -2.69,6 -6,6 -6,-2.69 -6,-6z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_wb_sunny.xml b/app/src/main/res/drawable/ic_wb_sunny.xml
new file mode 100644
index 0000000..74e9085
--- /dev/null
+++ b/app/src/main/res/drawable/ic_wb_sunny.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="#000000"
+        android:pathData="M6.05 4.14L5.66 3.75c-0.39-0.39-1.02-0.38-1.4 0L4.25 3.76c-0.39 0.39-0.39 1.02 0 1.4l0.39 0.39c0.39 0.39 1.01 0.39 1.4 0l0.01-0.01c0.39-0.38 0.39-1.02 0-1.4zM3.01 10.5H1.99C1.44 10.5 1 10.94 1 11.49v0.01c0 0.55 0.44 0.99 0.99 0.99H3c0.56 0.01 1-0.43 1-0.98V11.5c0-0.56-0.44-1-0.99-1zm9-9.95H12c-0.56 0-1 0.44-1 0.99V2.5c0 0.55 0.44 0.99 0.99 0.99H12c0.56 0.01 1-0.43 1-0.98V1.54c0-0.55-0.44-0.99-0.99-0.99zm7.74 3.21c-0.39-0.39-1.02-0.39-1.41-0.01l-0.39 0.39c-0.39 0.39-0.39 1.02 0 1.4l0.01 0.01c0.39 0.39 1.02 0.39 1.4 0l0.39-0.39c0.39-0.39 0.39-1.01 0-1.4zm-1.81 15.1l0.39 0.39c0.39 0.39 1.02 0.39 1.41 0 0.39-0.39 0.39-1.02 0-1.41l-0.39-0.39c-0.39-0.39-1.02-0.38-1.4 0-0.4 0.4-0.4 1.02-0.01 1.41zM20 11.49v0.01c0 0.55 0.44 0.99 0.99 0.99H22c0.55 0 0.99-0.44 0.99-0.99v-0.01c0-0.55-0.44-0.99-0.99-0.99h-1.01c-0.55 0-0.99 0.44-0.99 0.99zM12 5.5c-3.31 0-6 2.69-6 6s2.69 6 6 6 6-2.69 6-6-2.69-6-6-6zm-0.01 16.95H12c0.55 0 0.99-0.44 0.99-0.99V20.5c0-0.55-0.44-0.99-0.99-0.99h-0.01c-0.55 0-0.99 0.44-0.99 0.99v0.96c0 0.55 0.44 0.99 0.99 0.99zm-7.74-3.21c0.39 0.39 1.02 0.39 1.41 0l0.39-0.39c0.39-0.39 0.38-1.02 0-1.4l-0.01-0.01c-0.39-0.39-1.02-0.39-1.41 0l-0.39 0.39c-0.38 0.4-0.38 1.02 0.01 1.41z" />
+</vector>
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 0000000..e9d4482
--- /dev/null
+++ b/app/src/main/res/drawable/ic_wb_twilight.xml
@@ -0,0 +1,9 @@
+<vector android:height="24dp" android:tint="#7E7E7E"
+    android:viewportHeight="24" android:viewportWidth="24"
+    android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
+    <path android:fillColor="@android:color/white" android:pathData="M16.9552,8.662l2.1207,-2.1222l1.4148,1.4138l-2.1207,2.1222z"/>
+    <path android:fillColor="@android:color/white" android:pathData="M2,18h20v2h-20z"/>
+    <path android:fillColor="@android:color/white" android:pathData="M11,4h2v3h-2z"/>
+    <path android:fillColor="@android:color/white" android:pathData="M3.5425,7.9248l1.4142,-1.4142l2.1213,2.1213l-1.4142,1.4142z"/>
+    <path android:fillColor="@android:color/white" android:pathData="M5,16h14c0,-3.87 -3.13,-7 -7,-7S5,12.13 5,16z"/>
+</vector>
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 0000000..9a66b9a
--- /dev/null
+++ b/app/src/main/res/drawable/ic_wena_icon_center.xml
@@ -0,0 +1,24 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="48dp"
+    android:height="48dp"
+    android:viewportWidth="12.7"
+    android:viewportHeight="12.7">
+  <path
+      android:pathData="M5.041,4.975h2.619v2.75h-2.619z"
+      android:strokeLineJoin="round"
+      android:strokeWidth="0.0891348"
+      android:fillColor="#616161"
+      android:strokeColor="#626262"/>
+  <path
+      android:pathData="M8.436,4.975h2.619v2.75h-2.619z"
+      android:strokeLineJoin="round"
+      android:strokeWidth="0.0891348"
+      android:fillColor="#00000000"
+      android:strokeColor="#626262"/>
+  <path
+      android:pathData="M1.645,4.975h2.619v2.75h-2.619z"
+      android:strokeLineJoin="round"
+      android:strokeWidth="0.0891348"
+      android:fillColor="#00000000"
+      android:strokeColor="#626262"/>
+</vector>
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 0000000..d5383e5
--- /dev/null
+++ b/app/src/main/res/drawable/ic_wena_icon_left.xml
@@ -0,0 +1,24 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="48dp"
+    android:height="48dp"
+    android:viewportWidth="12.7"
+    android:viewportHeight="12.7">
+  <path
+      android:pathData="M5.041,4.975h2.619v2.75h-2.619z"
+      android:strokeLineJoin="round"
+      android:strokeWidth="0.0891348"
+      android:fillColor="#00000000"
+      android:strokeColor="#626262"/>
+  <path
+      android:pathData="M8.436,4.975h2.619v2.75h-2.619z"
+      android:strokeLineJoin="round"
+      android:strokeWidth="0.0891348"
+      android:fillColor="#00000000"
+      android:strokeColor="#626262"/>
+  <path
+      android:pathData="M1.645,4.975h2.619v2.75h-2.619z"
+      android:strokeLineJoin="round"
+      android:strokeWidth="0.0891348"
+      android:fillColor="#616161"
+      android:strokeColor="#626262"/>
+</vector>
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 0000000..7125886
--- /dev/null
+++ b/app/src/main/res/drawable/ic_wena_icon_right.xml
@@ -0,0 +1,24 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="48dp"
+    android:height="48dp"
+    android:viewportWidth="12.7"
+    android:viewportHeight="12.7">
+  <path
+      android:pathData="M5.041,4.975h2.619v2.75h-2.619z"
+      android:strokeLineJoin="round"
+      android:strokeWidth="0.0891348"
+      android:fillColor="#00000000"
+      android:strokeColor="#626262"/>
+  <path
+      android:pathData="M8.436,4.975h2.619v2.75h-2.619z"
+      android:strokeLineJoin="round"
+      android:strokeWidth="0.0891348"
+      android:fillColor="#616161"
+      android:strokeColor="#626262"/>
+  <path
+      android:pathData="M1.645,4.975h2.619v2.75h-2.619z"
+      android:strokeLineJoin="round"
+      android:strokeWidth="0.0891348"
+      android:fillColor="#00000000"
+      android:strokeColor="#626262"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_widgets.xml b/app/src/main/res/drawable/ic_widgets.xml
new file mode 100644
index 0000000..de857d6
--- /dev/null
+++ b/app/src/main/res/drawable/ic_widgets.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="#000000"
+        android:pathData="M13 14v6c0 0.55 0.45 1 1 1h6c0.55 0 1-0.45 1-1v-6c0-0.55-0.45-1-1-1h-6c-0.55 0-1 0.45-1 1zm-9 7h6c0.55 0 1-0.45 1-1v-6c0-0.55-0.45-1-1-1H4c-0.55 0-1 0.45-1 1v6c0 0.55 0.45 1 1 1zM3 4v6c0 0.55 0.45 1 1 1h6c0.55 0 1-0.45 1-1V4c0-0.55-0.45-1-1-1H4C3.45 3 3 3.45 3 4zm12.95-1.6L11.7 6.64c-0.39 0.39-0.39 1.02 0 1.41l4.25 4.25c0.39 0.39 1.02 0.39 1.41 0l4.25-4.25c0.39-0.39 0.39-1.02 0-1.41L17.37 2.4c-0.39-0.39-1.03-0.39-1.42 0z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_wifi.xml b/app/src/main/res/drawable/ic_wifi.xml
new file mode 100644
index 0000000..7fd8bdc
--- /dev/null
+++ b/app/src/main/res/drawable/ic_wifi.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M1,9l2,2c4.97,-4.97 13.03,-4.97 18,0l2,-2C16.93,2.93 7.08,2.93 1,9zM9,17l3,3 3,-3c-1.65,-1.66 -4.34,-1.66 -6,0zM5,13l2,2c2.76,-2.76 7.24,-2.76 10,0l2,-2C15.14,9.14 8.87,9.14 5,13z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_wifi_tethering.xml b/app/src/main/res/drawable/ic_wifi_tethering.xml
new file mode 100644
index 0000000..ab78ed0
--- /dev/null
+++ b/app/src/main/res/drawable/ic_wifi_tethering.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="#7E7E7E"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M12,11c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM18,13c0,-3.31 -2.69,-6 -6,-6s-6,2.69 -6,6c0,2.22 1.21,4.15 3,5.19l1,-1.74c-1.19,-0.7 -2,-1.97 -2,-3.45 0,-2.21 1.79,-4 4,-4s4,1.79 4,4c0,1.48 -0.81,2.75 -2,3.45l1,1.74c1.79,-1.04 3,-2.97 3,-5.19zM12,3C6.48,3 2,7.48 2,13c0,3.7 2.01,6.92 4.99,8.65l1,-1.73C5.61,18.53 4,15.96 4,13c0,-4.42 3.58,-8 8,-8s8,3.58 8,8c0,2.96 -1.61,5.53 -4,6.92l1,1.73c2.99,-1.73 5,-4.95 5,-8.65 0,-5.52 -4.48,-10 -10,-10z" />
+</vector>
diff --git a/app/src/main/res/drawable/level_list_battery.xml b/app/src/main/res/drawable/level_list_battery.xml
new file mode 100644
index 0000000..1baf370
--- /dev/null
+++ b/app/src/main/res/drawable/level_list_battery.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<level-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item
+        android:drawable="@drawable/ic_battery_20"
+        android:maxLevel="20" />
+    <item
+        android:drawable="@drawable/ic_battery_50"
+        android:maxLevel="50" />
+    <item
+        android:drawable="@drawable/ic_battery_80"
+        android:maxLevel="80" />
+    <item
+        android:drawable="@drawable/ic_battery_full"
+        android:maxLevel="100" />
+    <item
+        android:drawable="@drawable/ic_battery_charging_20"
+        android:maxLevel="120" />
+    <item
+        android:drawable="@drawable/ic_battery_charging_50"
+        android:maxLevel="150" />
+    <item
+        android:drawable="@drawable/ic_battery_charging_80"
+        android:maxLevel="180" />
+    <item
+        android:drawable="@drawable/ic_battery_charging_full"
+        android:maxLevel="200" />
+    <item
+        android:drawable="@drawable/ic_battery_unknown"
+        android:maxLevel="300" />
+</level-list>
diff --git a/app/src/main/res/drawable/list_selector.xml b/app/src/main/res/drawable/list_selector.xml
new file mode 100644
index 0000000..e6d7f87
--- /dev/null
+++ b/app/src/main/res/drawable/list_selector.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:drawable="@drawable/selected" android:state_activated="true" />
+</selector>
\ No newline at end of file
diff --git a/app/src/main/res/drawable/side_nav_bar.xml b/app/src/main/res/drawable/side_nav_bar.xml
new file mode 100644
index 0000000..a695cc9
--- /dev/null
+++ b/app/src/main/res/drawable/side_nav_bar.xml
@@ -0,0 +1,9 @@
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+    <gradient
+        android:angle="135"
+        android:centerColor="@color/primary_light"
+        android:endColor="@color/primary_dark"
+        android:startColor="@color/primary_light"
+        android:type="linear" />
+</shape>
diff --git a/app/src/main/res/drawable/splash.xml b/app/src/main/res/drawable/splash.xml
new file mode 100644
index 0000000..a003d8f
--- /dev/null
+++ b/app/src/main/res/drawable/splash.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:drawable="@color/primary_dark" />
+</layer-list>
diff --git a/app/src/main/res/drawable/widget_preview.png b/app/src/main/res/drawable/widget_preview.png
new file mode 100644
index 0000000..999d45f
--- /dev/null
+++ b/app/src/main/res/drawable/widget_preview.png
Binary files differ
diff --git a/app/src/main/res/layout-land/activity_notification_filter.xml b/app/src/main/res/layout-land/activity_notification_filter.xml
new file mode 100644
index 0000000..6c6a737
--- /dev/null
+++ b/app/src/main/res/layout-land/activity_notification_filter.xml
@@ -0,0 +1,96 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context=".activities.NotificationFilterActivity"
+    tools:layout_editor_absoluteY="81dp">
+
+    <TextView
+        android:id="@+id/textView2"
+        android:layout_width="0dp"
+        android:layout_height="19dp"
+        android:layout_marginEnd="8dp"
+        android:layout_marginStart="16dp"
+        android:layout_marginTop="16dp"
+        android:text="@string/filter_mode"
+        app:layout_constraintEnd_toStartOf="@+id/guideline"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <Spinner
+        android:id="@+id/spinnerFilterMode"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginEnd="8dp"
+        android:layout_marginStart="16dp"
+        android:layout_marginTop="16dp"
+        android:entries="@array/notification_filter_modes_entries"
+        app:layout_constraintEnd_toStartOf="@+id/guideline"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/textView2" />
+
+    <TextView
+        android:id="@+id/textView3"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginEnd="8dp"
+        android:layout_marginStart="16dp"
+        android:layout_marginTop="16dp"
+        android:text="@string/mode_configuration"
+        app:layout_constraintEnd_toStartOf="@+id/guideline"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/spinnerFilterMode" />
+
+    <Spinner
+        android:id="@+id/spinnerSubMode"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginEnd="8dp"
+        android:layout_marginStart="16dp"
+        android:layout_marginTop="16dp"
+        android:entries="@array/notification_filter_submodes_entries"
+        app:layout_constraintEnd_toStartOf="@+id/guideline"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/textView3" />
+
+    <EditText
+        android:id="@+id/editTextWords"
+        style="@style/Widget.AppCompat.EditText"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:layout_marginBottom="16dp"
+        android:layout_marginEnd="16dp"
+        android:layout_marginTop="8dp"
+        android:ems="10"
+        android:gravity="start"
+        android:hint="@string/edittext_notification_filter_words_hint"
+        android:inputType="textMultiLine"
+        android:maxLines="25"
+        android:minLines="10"
+        android:scrollbars="vertical"
+        app:layout_constraintBottom_toTopOf="@+id/buttonSaveFilter"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="@+id/guideline"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <Button
+        android:id="@+id/buttonSaveFilter"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginBottom="16dp"
+        android:layout_marginEnd="16dp"
+        android:layout_marginStart="16dp"
+        android:text="@string/save_configuration"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent" />
+
+    <androidx.constraintlayout.widget.Guideline
+        android:id="@+id/guideline"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:orientation="vertical"
+        app:layout_constraintGuide_percent="0.5" />
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout-land/fragment_weeksteps_chart.xml b/app/src/main/res/layout-land/fragment_weeksteps_chart.xml
new file mode 100644
index 0000000..3958bda
--- /dev/null
+++ b/app/src/main/res/layout-land/fragment_weeksteps_chart.xml
@@ -0,0 +1,58 @@
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:orientation="horizontal"
+        tools:context="nodomain.freeyourgadget.gadgetbridge.activities.charts.ActivityChartsActivity$PlaceholderFragment">
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_weight="40"
+            android:orientation="vertical">
+
+            <TextView
+                android:id="@+id/balance"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content" />
+
+            <com.github.mikephil.charting.charts.PieChart
+                android:id="@+id/todaystepschart"
+                android:layout_width="fill_parent"
+                android:layout_height="fill_parent"
+                android:layout_weight="40" />
+
+        </LinearLayout>
+
+        <com.github.mikephil.charting.charts.BarChart
+            android:id="@+id/weekstepschart"
+            android:layout_width="fill_parent"
+            android:layout_height="fill_parent"
+            android:layout_weight="20" />
+
+        <!--<TextView-->
+        <!--android:text="Test"-->
+        <!--android:layout_width="fill_parent"-->
+        <!--android:layout_height="fill_parent"-->
+        <!--android:layout_weight="20" />-->
+
+    </LinearLayout>
+
+    <ImageView
+        android:id="@+id/steps_streaks_button"
+        android:layout_width="50dp"
+        android:layout_height="50dp"
+        android:layout_alignParentStart="true"
+        android:layout_alignParentBottom="true"
+        android:layout_marginStart="5dp"
+        android:layout_marginEnd="0dp"
+        android:visibility="visible"
+        app:srcCompat="@drawable/ic_events" />
+
+</RelativeLayout>
+
diff --git a/app/src/main/res/layout/activity_about.xml b/app/src/main/res/layout/activity_about.xml
new file mode 100644
index 0000000..769ce31
--- /dev/null
+++ b/app/src/main/res/layout/activity_about.xml
@@ -0,0 +1,175 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <ImageView
+        android:id="@+id/no_items_bg"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_centerInParent="true"
+        android:alpha="0.1"
+        android:contentDescription="@string/icon_placeholder"
+        app:tint="?attr/textColorPrimary"
+        app:srcCompat="@drawable/gadgetbridge_img" />
+
+    <ScrollView
+        android:id="@+id/about_background"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:paddingLeft="@dimen/about_margin"
+        android:paddingRight="@dimen/about_margin"
+        tools:context="nodomain.freeyourgadget.gadgetbridge.activities.AboutActivity">
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="vertical">
+
+            <TextView
+                android:id="@+id/about_title"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/app_name"
+                android:textAppearance="@style/TextAppearance.AppCompat.Large"
+                android:textColor="@color/accent" />
+
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:orientation="vertical"
+                android:paddingLeft="@dimen/about_margin"
+                android:paddingRight="@dimen/about_margin">
+
+                <TextView
+                    android:id="@+id/about_description"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:text="@string/about_description" />
+
+                <TextView
+                    android:id="@+id/about_version"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content" />
+
+                <TextView
+                    android:id="@+id/about_hash"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content" />
+
+            </LinearLayout>
+
+            <TextView
+                android:id="@+id/links_title"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/about_links"
+                android:textAppearance="@style/TextAppearance.AppCompat.Large"
+                android:textColor="@color/accent" />
+
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:orientation="vertical"
+                android:paddingLeft="@dimen/about_margin"
+                android:paddingRight="@dimen/about_margin">
+
+                <TextView
+                    android:id="@+id/links1"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:text="@string/homepage_url" />
+
+                <TextView
+                    android:id="@+id/links2"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:text="@string/codeberg_url" />
+
+                <TextView
+                    android:id="@+id/links3"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:text="@string/fdroid_url" />
+            </LinearLayout>
+
+            <TextView
+                android:id="@+id/contrib_title"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/about_contributors"
+                android:textAppearance="@style/TextAppearance.AppCompat.Large"
+                android:textColor="@color/accent" />
+
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:orientation="vertical"
+                android:paddingLeft="@dimen/about_margin"
+                android:paddingRight="@dimen/about_margin">
+
+                <TextView
+                    android:id="@+id/contrib_core_title"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:text="@string/about_core_team_title"
+                    android:textColor="@color/accent" />
+
+                <LinearLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="match_parent"
+                    android:orientation="vertical"
+                    android:paddingLeft="@dimen/about_margin"
+                    android:paddingRight="@dimen/about_margin">
+
+                    <TextView
+                        android:id="@+id/contrib_core_members"
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:text="@string/about_core_team_members" />
+                </LinearLayout>
+
+            </LinearLayout>
+
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:orientation="vertical"
+                android:paddingStart="@dimen/about_margin"
+                android:paddingEnd="@dimen/about_margin">
+
+                <TextView
+                    android:id="@+id/contrib_add_title"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:text="@string/about_additional_device_support"
+                    android:textColor="@color/accent" />
+
+                <LinearLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="match_parent"
+                    android:orientation="vertical"
+                    android:paddingLeft="@dimen/about_margin"
+                    android:paddingRight="@dimen/about_margin">
+
+                    <TextView
+                        android:id="@+id/contrib_add_members"
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:text="@string/about_additional_contributors" />
+
+                </LinearLayout>
+
+                <TextView
+                    android:id="@+id/contrib_add_title2"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:text="@string/about_additional_contributions"
+                    android:textColor="@color/accent" />
+
+            </LinearLayout>
+        </LinearLayout>
+    </ScrollView>
+</RelativeLayout>
diff --git a/app/src/main/res/layout/activity_alarm_details.xml b/app/src/main/res/layout/activity_alarm_details.xml
new file mode 100644
index 0000000..8f9b1a8
--- /dev/null
+++ b/app/src/main/res/layout/activity_alarm_details.xml
@@ -0,0 +1,170 @@
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    tools:context="nodomain.freeyourgadget.gadgetbridge.activities.AlarmDetails">
+
+    <ScrollView
+        android:id="@+id/scrollView"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentStart="true"
+        android:layout_alignParentTop="true"
+        android:layout_alignParentEnd="true"
+        android:layout_alignParentBottom="true">
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="vertical"
+            android:paddingLeft="@dimen/activity_horizontal_margin"
+            android:paddingTop="@dimen/activity_vertical_margin"
+            android:paddingRight="@dimen/activity_horizontal_margin"
+            android:paddingBottom="@dimen/activity_vertical_margin">
+
+            <EditText
+                android:id="@+id/alarm_title"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:ems="10"
+                android:hint="@string/title"
+                android:inputType="text"
+                android:maxLength="32" />
+
+            <EditText
+                android:id="@+id/alarm_description"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:ems="10"
+                android:gravity="start|top"
+                android:hint="@string/description"
+                android:inputType="textMultiLine"
+                android:maxLength="256"
+                android:singleLine="false" />
+
+            <androidx.appcompat.widget.AppCompatCheckedTextView
+                android:id="@+id/alarm_cb_snooze"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginStart="4dp"
+                android:drawableStart="?android:attr/listChoiceIndicatorMultiple"
+                android:gravity="center"
+                android:text="@string/alarm_snooze"
+                android:textAppearance="?android:attr/textAppearanceSmall" />
+
+            <androidx.appcompat.widget.AppCompatCheckedTextView
+                android:id="@+id/alarm_cb_smart_wakeup"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginStart="4dp"
+                android:drawableStart="?android:attr/listChoiceIndicatorMultiple"
+                android:gravity="center"
+                android:text="@string/alarm_smart_wakeup"
+                android:textAppearance="?android:attr/textAppearanceSmall" />
+
+            <EditText
+                android:id="@+id/alarm_cb_smart_wakeup_interval"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:ems="10"
+                android:inputType="number"
+                android:hint="@string/alarm_smart_wakeup_interval_default" />
+
+            <TimePicker
+                android:id="@+id/alarm_time_picker"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginBottom="20dp"
+                android:scaleX="1"
+                android:scaleY="1" />
+
+            <LinearLayout
+                android:id="@+id/dowSelector"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                android:baselineAligned="false"
+                android:orientation="horizontal">
+
+                <androidx.appcompat.widget.AppCompatCheckedTextView
+                    android:id="@+id/alarm_cb_monday"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_marginStart="4dp"
+                    android:layout_weight="1"
+                    android:drawableTop="?android:attr/listChoiceIndicatorMultiple"
+                    android:gravity="center"
+                    android:text="@string/alarm_mon_short"
+                    android:textAppearance="?android:attr/textAppearanceSmall" />
+
+                <androidx.appcompat.widget.AppCompatCheckedTextView
+                    android:id="@+id/alarm_cb_tuesday"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_marginStart="4dp"
+                    android:layout_weight="1"
+                    android:drawableTop="?android:attr/listChoiceIndicatorMultiple"
+                    android:gravity="center"
+                    android:text="@string/alarm_tue_short"
+                    android:textAppearance="?android:attr/textAppearanceSmall" />
+
+                <androidx.appcompat.widget.AppCompatCheckedTextView
+                    android:id="@+id/alarm_cb_wednesday"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_marginStart="4dp"
+                    android:layout_weight="1"
+                    android:drawableTop="?android:attr/listChoiceIndicatorMultiple"
+                    android:gravity="center"
+                    android:text="@string/alarm_wed_short"
+                    android:textAppearance="?android:attr/textAppearanceSmall" />
+
+                <androidx.appcompat.widget.AppCompatCheckedTextView
+                    android:id="@+id/alarm_cb_thursday"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_marginStart="4dp"
+                    android:layout_weight="1"
+                    android:drawableTop="?android:attr/listChoiceIndicatorMultiple"
+                    android:gravity="center"
+                    android:text="@string/alarm_thu_short"
+                    android:textAppearance="?android:attr/textAppearanceSmall" />
+
+                <androidx.appcompat.widget.AppCompatCheckedTextView
+                    android:id="@+id/alarm_cb_friday"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_marginStart="4dp"
+                    android:layout_weight="1"
+                    android:drawableTop="?android:attr/listChoiceIndicatorMultiple"
+                    android:gravity="center"
+                    android:text="@string/alarm_fri_short"
+                    android:textAppearance="?android:attr/textAppearanceSmall" />
+
+                <androidx.appcompat.widget.AppCompatCheckedTextView
+                    android:id="@+id/alarm_cb_saturday"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_marginStart="4dp"
+                    android:layout_weight="1"
+                    android:drawableTop="?android:attr/listChoiceIndicatorMultiple"
+                    android:gravity="center"
+                    android:text="@string/alarm_sat_short"
+                    android:textAppearance="?android:attr/textAppearanceSmall" />
+
+                <androidx.appcompat.widget.AppCompatCheckedTextView
+                    android:id="@+id/alarm_cb_sunday"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_marginStart="4dp"
+                    android:layout_weight="1"
+                    android:drawableTop="?android:attr/listChoiceIndicatorMultiple"
+                    android:gravity="center"
+                    android:text="@string/alarm_sun_short"
+                    android:textAppearance="?android:attr/textAppearanceSmall" />
+
+            </LinearLayout>
+
+        </LinearLayout>
+    </ScrollView>
+</RelativeLayout>
diff --git a/app/src/main/res/layout/activity_android_pairing.xml b/app/src/main/res/layout/activity_android_pairing.xml
new file mode 100644
index 0000000..566f808
--- /dev/null
+++ b/app/src/main/res/layout/activity_android_pairing.xml
@@ -0,0 +1,16 @@
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:paddingLeft="@dimen/activity_horizontal_margin"
+    android:paddingRight="@dimen/activity_horizontal_margin"
+    android:paddingTop="@dimen/activity_vertical_margin"
+    android:paddingBottom="@dimen/activity_vertical_margin"
+    tools:context="nodomain.freeyourgadget.gadgetbridge.activities.AndroidPairingActivity">
+
+    <TextView
+        android:text="@string/android_pairing_hint"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content" />
+
+</RelativeLayout>
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 0000000..64955fb
--- /dev/null
+++ b/app/src/main/res/layout/activity_app_specific_notification_settings.xml
@@ -0,0 +1,25 @@
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context=".activities.app_specific_notifications.AppSpecificNotificationSettingsActivity">
+
+    <androidx.appcompat.widget.SearchView
+        android:id="@+id/appListViewSearch"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_alignParentTop="true"
+        android:paddingEnd="16dp"
+        android:paddingStart="16dp">
+
+    </androidx.appcompat.widget.SearchView>
+
+    <androidx.recyclerview.widget.RecyclerView
+        android:id="@+id/appListView"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_below="@id/appListViewSearch"
+        android:layout_centerHorizontal="true"
+        android:divider="@null" />
+
+</RelativeLayout>
diff --git a/app/src/main/res/layout/activity_appblacklist.xml b/app/src/main/res/layout/activity_appblacklist.xml
new file mode 100644
index 0000000..8725800
--- /dev/null
+++ b/app/src/main/res/layout/activity_appblacklist.xml
@@ -0,0 +1,25 @@
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context="nodomain.freeyourgadget.gadgetbridge.activities.AppBlacklistActivity">
+
+    <androidx.appcompat.widget.SearchView
+        android:id="@+id/appListViewSearch"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_alignParentTop="true"
+        android:paddingEnd="16dp"
+        android:paddingStart="16dp">
+
+    </androidx.appcompat.widget.SearchView>
+
+    <androidx.recyclerview.widget.RecyclerView
+        android:id="@+id/appListView"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_below="@id/appListViewSearch"
+        android:layout_centerHorizontal="true"
+        android:divider="@null" />
+
+</RelativeLayout>
diff --git a/app/src/main/res/layout/activity_appinstaller.xml b/app/src/main/res/layout/activity_appinstaller.xml
new file mode 100644
index 0000000..c115c44
--- /dev/null
+++ b/app/src/main/res/layout/activity_appinstaller.xml
@@ -0,0 +1,102 @@
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent">
+
+    <RelativeLayout
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent"
+        android:animateLayoutChanges="true"
+        android:orientation="vertical"
+        android:paddingBottom="@dimen/activity_vertical_margin"
+        android:paddingLeft="@dimen/activity_horizontal_margin"
+        android:paddingRight="@dimen/activity_horizontal_margin"
+        android:paddingTop="@dimen/activity_vertical_margin"
+        tools:context="nodomain.freeyourgadget.gadgetbridge.activities.FwAppInstallerActivity"
+        tools:ignore="ScrollViewSize">
+
+        <ImageView
+            android:id="@+id/previewImage"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginBottom="8dp"
+            android:contentDescription="@string/preview_image"
+            android:visibility="gone" />
+
+        <ListView
+            android:id="@+id/itemListView"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_below="@+id/previewImage"
+            android:layout_alignParentEnd="false" />
+
+        <TextView
+            android:id="@+id/infoTextView"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:layout_alignParentEnd="false"
+            android:layout_alignParentStart="false"
+            android:layout_alignParentTop="false"
+            android:layout_below="@+id/itemListView"
+            android:layout_gravity="center_horizontal" />
+
+        <ImageView
+            android:id="@+id/fwappStatusIcon"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_alignParentStart="false"
+            android:layout_alignParentTop="false"
+            android:layout_below="@+id/infoTextView"
+            android:layout_centerHorizontal="true"
+            android:contentDescription="@string/status_icon" />
+
+        <ProgressBar
+            style="@android:style/Widget.DeviceDefault.ProgressBar.Horizontal"
+            android:id="@+id/installProgressBar"
+            android:layout_width="match_parent"
+            android:layout_height="40dp"
+            android:layout_below="@+id/fwappStatusIcon"
+            android:layout_centerHorizontal="true"
+            android:layout_gravity="center_horizontal"
+            android:indeterminate="true"
+            android:visibility="gone" />
+
+        <Button
+            android:id="@+id/installButton"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_alignParentBottom="false"
+            android:layout_alignParentStart="false"
+            android:layout_alignParentTop="false"
+            android:layout_alignWithParentIfMissing="false"
+            android:layout_below="@+id/installProgressBar"
+            android:layout_centerHorizontal="true"
+            android:layout_gravity="center_horizontal"
+            android:layout_marginTop="10dp"
+            android:enabled="false"
+            android:text="@string/appinstaller_install" />
+
+        <TextView
+            android:id="@+id/installProgressText"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_below="@+id/installProgressBar"
+            android:layout_gravity="start"
+            android:visibility="gone" />
+
+        <ListView
+            android:id="@+id/detailsListView"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_alignParentEnd="false"
+            android:layout_below="@+id/installProgressText" />
+
+        <android.widget.Space
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_alignParentBottom="true"
+            android:layout_centerHorizontal="true" />
+
+    </RelativeLayout>
+
+</ScrollView>
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_appmanager.xml b/app/src/main/res/layout/activity_appmanager.xml
new file mode 100644
index 0000000..057ee11
--- /dev/null
+++ b/app/src/main/res/layout/activity_appmanager.xml
@@ -0,0 +1,14 @@
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    xmlns:tools="http://schemas.android.com/tools"
+    tools:context="nodomain.freeyourgadget.gadgetbridge.activities.appmanager.AbstractAppManagerFragment">
+
+    <androidx.recyclerview.widget.RecyclerView
+        android:id="@+id/appListView"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_alignParentTop="true"
+        android:layout_centerHorizontal="true"
+        android:divider="@null" />
+</RelativeLayout>
diff --git a/app/src/main/res/layout/activity_backup_restore_progress.xml b/app/src/main/res/layout/activity_backup_restore_progress.xml
new file mode 100644
index 0000000..f021950
--- /dev/null
+++ b/app/src/main/res/layout/activity_backup_restore_progress.xml
@@ -0,0 +1,62 @@
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    tools:context="nodomain.freeyourgadget.gadgetbridge.activities.BackupRestoreProgressActivity">
+
+    <RelativeLayout
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent"
+        android:animateLayoutChanges="true"
+        android:orientation="vertical"
+        android:paddingLeft="@dimen/activity_horizontal_margin"
+        android:paddingTop="@dimen/activity_vertical_margin"
+        android:paddingRight="@dimen/activity_horizontal_margin"
+        android:paddingBottom="@dimen/activity_vertical_margin"
+        tools:ignore="ScrollViewSize">
+
+        <TextView
+            android:id="@+id/backupRestoreHint"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_alignParentStart="false"
+            android:layout_alignParentEnd="false"
+            android:layout_gravity="center_horizontal"
+            android:text="@string/loading" />
+
+        <ProgressBar
+            android:id="@+id/backupRestoreProgressBar"
+            style="?android:attr/progressBarStyleHorizontal"
+            android:layout_width="match_parent"
+            android:layout_height="50dp"
+            android:layout_below="@+id/backupRestoreHint"
+            android:layout_centerHorizontal="true"
+            android:layout_gravity="center"
+            android:indeterminate="true" />
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_below="@+id/backupRestoreProgressBar"
+            android:orientation="horizontal">
+
+            <TextView
+                android:id="@+id/backupRestoreProgressText"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_horizontal"
+                android:gravity="start"
+                android:text="@string/loading" />
+
+            <TextView
+                android:id="@+id/backupRestoreProgressPercentage"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:gravity="end"
+                android:text="@string/stats_empty_value"
+                android:textAlignment="gravity" />
+        </LinearLayout>
+
+    </RelativeLayout>
+
+</ScrollView>
diff --git a/app/src/main/res/layout/activity_banglejs_apps_management.xml b/app/src/main/res/layout/activity_banglejs_apps_management.xml
new file mode 100644
index 0000000..08188a9
--- /dev/null
+++ b/app/src/main/res/layout/activity_banglejs_apps_management.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout
+    xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+
+        <WebView
+            android:id="@+id/webview"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent" />
+    </LinearLayout>
+
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_battery_info.xml b/app/src/main/res/layout/activity_battery_info.xml
new file mode 100644
index 0000000..4ec3d57
--- /dev/null
+++ b/app/src/main/res/layout/activity_battery_info.xml
@@ -0,0 +1,95 @@
+<?xml version="1.0" encoding="utf-8"?>
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/activity_battery_info_layout"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:scrollbars="vertical">
+
+
+    <LinearLayout
+        android:id="@+id/activity_battery_info_master_layout"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="0dp"
+        android:layout_marginEnd="0dp"
+        android:orientation="vertical">
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_marginStart="14dp"
+            android:layout_marginEnd="14dp"
+            android:orientation="horizontal">
+
+            <ImageView
+                android:id="@+id/battery_status_device_icon"
+                android:layout_width="80dp"
+                android:layout_height="80dp"
+                android:contentDescription="@string/icon_placeholder"
+                app:srcCompat="@drawable/ic_devices_other" />
+
+            <LinearLayout
+                android:id="@+id/activity_battery_info_top_layout"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginStart="5dp"
+                android:layout_marginEnd="5dp"
+                android:orientation="vertical">
+
+                <TextView
+                    android:id="@+id/battery_status_device_name"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_weight="1"
+                    android:textSize="24sp"
+                    tools:text="Device name" />
+
+                <TextView
+                    android:id="@+id/battery_status_battery_level"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_weight="1"
+                    android:textAlignment="textEnd"
+                    android:textSize="48sp"
+                    tools:text="90%" />
+
+                <LinearLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="match_parent"
+                    android:layout_weight="1"
+                    android:orientation="horizontal">
+
+                    <TextView
+                        android:id="@+id/battery_status_extra_name"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content" />
+
+                    <TextView
+                        android:id="@+id/battery_status_battery_voltage"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content" />
+                </LinearLayout>
+
+            </LinearLayout>
+
+        </LinearLayout>
+
+
+        <LinearLayout
+            android:id="@+id/activity_battery_info_bottom_layout"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:orientation="vertical">
+
+            <FrameLayout
+                android:id="@+id/batteryChartFragmentHolder"
+                android:layout_width="match_parent"
+                android:layout_height="350dp" />
+        </LinearLayout>
+
+        <include layout="@layout/layout_widget_duration_selector" />
+
+    </LinearLayout>
+</ScrollView>
diff --git a/app/src/main/res/layout/activity_binary_sensor_data.xml b/app/src/main/res/layout/activity_binary_sensor_data.xml
new file mode 100644
index 0000000..afddc5d
--- /dev/null
+++ b/app/src/main/res/layout/activity_binary_sensor_data.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:gravity="center">
+
+    <LinearLayout
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:orientation="vertical"
+        android:gravity="center_horizontal">
+
+        <TextView
+            android:id="@+id/text_sensor_state"
+            android:layout_width="250dp"
+            android:layout_height="250dp"
+            android:background="@android:color/holo_red_light"
+            android:text="@string/unknown"
+            android:textSize="50sp"
+            android:gravity="center"/>
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/unknown"
+            android:textSize="50sp"
+            android:id="@+id/text_sensor_count"/>
+
+    </LinearLayout>
+
+</RelativeLayout>
diff --git a/app/src/main/res/layout/activity_calblacklist.xml b/app/src/main/res/layout/activity_calblacklist.xml
new file mode 100644
index 0000000..60788e9
--- /dev/null
+++ b/app/src/main/res/layout/activity_calblacklist.xml
@@ -0,0 +1,14 @@
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context="nodomain.freeyourgadget.gadgetbridge.activities.CalBlacklistActivity">
+
+    <ListView
+        android:id="@+id/calListView"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_centerHorizontal="true"
+        android:divider="@null" />
+
+</RelativeLayout>
diff --git a/app/src/main/res/layout/activity_camera.xml b/app/src/main/res/layout/activity_camera.xml
new file mode 100644
index 0000000..d529a06
--- /dev/null
+++ b/app/src/main/res/layout/activity_camera.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <androidx.camera.view.PreviewView
+        android:id="@+id/preview"
+        android:layout_height="match_parent"
+        android:layout_width="match_parent" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_charts.xml b/app/src/main/res/layout/activity_charts.xml
new file mode 100644
index 0000000..f5769ab
--- /dev/null
+++ b/app/src/main/res/layout/activity_charts.xml
@@ -0,0 +1,115 @@
+<androidx.swiperefreshlayout.widget.SwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/activity_swipe_layout"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context="nodomain.freeyourgadget.gadgetbridge.activities.charts.ActivityChartsActivity">
+
+    <LinearLayout
+        android:id="@+id/charts_main_layout"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:orientation="vertical">
+
+        <nodomain.freeyourgadget.gadgetbridge.activities.charts.NonSwipeableViewPager
+            android:id="@+id/charts_pager"
+            android:layout_width="match_parent"
+            android:layout_height="0dp"
+            android:layout_weight="1"
+            tools:context="nodomain.freeyourgadget.gadgetbridge.activities.charts.ActivityChartsActivity">
+
+            <com.google.android.material.tabs.TabLayout
+                android:id="@+id/charts_pagerTabStrip"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_gravity="top"
+                app:tabMode="scrollable" />
+        </nodomain.freeyourgadget.gadgetbridge.activities.charts.NonSwipeableViewPager>
+
+    <LinearLayout
+        android:id="@+id/charts_date_bar"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:gravity="fill_horizontal"
+        android:orientation="horizontal">
+
+        <TextView
+            android:id="@+id/charts_text_date"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:gravity="center_horizontal"
+            android:layout_weight="1"
+            android:text="@string/stats_empty_value" />
+
+    </LinearLayout>
+
+        <LinearLayout
+            android:id="@+id/charts_date_button_bar"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:gravity="fill_horizontal"
+            android:orientation="horizontal">
+
+            <Button
+                android:id="@+id/charts_previous_month"
+                android:layout_width="wrap_content"
+                android:layout_weight="1"
+                android:layout_height="wrap_content"
+                android:padding="0dp"
+                android:layout_marginHorizontal="2dp"
+                android:text="&lt;&lt;&lt;"
+                tools:ignore="HardcodedText"/>
+
+            <Button
+                android:id="@+id/charts_previous_week"
+                android:layout_width="wrap_content"
+                android:layout_weight="1"
+                android:layout_height="wrap_content"
+                android:padding="0dp"
+                android:layout_marginHorizontal="2dp"
+                android:text="&lt;&lt;"
+                tools:ignore="HardcodedText" />
+
+            <Button
+                android:id="@+id/charts_previous_day"
+                android:layout_width="wrap_content"
+                android:layout_weight="1"
+                android:layout_height="wrap_content"
+                android:padding="0dp"
+                android:layout_marginHorizontal="2dp"
+                android:text="&lt;"
+                tools:ignore="HardcodedText"/>
+            
+            <Button
+                android:id="@+id/charts_next_day"
+                android:layout_width="wrap_content"
+                android:layout_weight="1"
+                android:layout_height="wrap_content"
+                android:padding="0dp"
+                android:layout_marginHorizontal="2dp"
+                android:text=">"
+                tools:ignore="HardcodedText"/>
+            <Button
+                android:id="@+id/charts_next_week"
+                android:layout_width="wrap_content"
+                android:layout_weight="1"
+                android:layout_height="wrap_content"
+                android:padding="0dp"
+                android:layout_marginHorizontal="2dp"
+                android:text=">>"
+                tools:ignore="HardcodedText"/>
+
+            <Button
+                android:id="@+id/charts_next_month"
+                android:layout_width="wrap_content"
+                android:layout_weight="1"
+                android:layout_height="wrap_content"
+                android:padding="0dp"
+                android:layout_marginHorizontal="2dp"
+                android:text=">>>"
+                tools:ignore="HardcodedText"/>
+
+        </LinearLayout>
+    </LinearLayout>
+</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
diff --git a/app/src/main/res/layout/activity_charts_durationdialog.xml b/app/src/main/res/layout/activity_charts_durationdialog.xml
new file mode 100644
index 0000000..6f1080f
--- /dev/null
+++ b/app/src/main/res/layout/activity_charts_durationdialog.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+
+    <TextView
+        android:id="@+id/charts_duration_label"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:padding="5dp" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_commute_actions.xml b/app/src/main/res/layout/activity_commute_actions.xml
new file mode 100644
index 0000000..2e8243a
--- /dev/null
+++ b/app/src/main/res/layout/activity_commute_actions.xml
@@ -0,0 +1,36 @@
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    xmlns:tools="http://schemas.android.com/tools"
+    tools:context=".devices.qhybrid.CommuteActionsActivity">
+
+    <TextView
+        android:id="@+id/fossil_hr_commute_actions_explanation"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_alignParentTop="true"
+        android:layout_marginLeft="10dp"
+        android:layout_marginTop="10dp"
+        android:layout_marginRight="10dp"
+        android:layout_marginBottom="10dp"
+        android:layout_weight="1"
+        android:text="@string/fossil_hr_commute_actions_explanation" />
+
+    <androidx.recyclerview.widget.RecyclerView
+        android:id="@+id/actionsListView"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_below="@id/fossil_hr_commute_actions_explanation"
+        android:divider="@null" />
+
+    <com.google.android.material.floatingactionbutton.FloatingActionButton
+        android:id="@+id/actionAddFab"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentBottom="true"
+        android:layout_alignParentEnd="true"
+        android:layout_gravity="bottom|end"
+        app:srcCompat="@drawable/ic_add"
+        android:layout_margin="16dp" />
+</RelativeLayout>
diff --git a/app/src/main/res/layout/activity_configure_alarms.xml b/app/src/main/res/layout/activity_configure_alarms.xml
new file mode 100644
index 0000000..e94f1cc
--- /dev/null
+++ b/app/src/main/res/layout/activity_configure_alarms.xml
@@ -0,0 +1,17 @@
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    app:layout_behavior="@string/appbar_scrolling_view_behavior"
+    android:fitsSystemWindows="true"
+    tools:context="nodomain.freeyourgadget.gadgetbridge.activities.ConfigureAlarms">
+
+    <androidx.recyclerview.widget.RecyclerView
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_alignParentTop="true"
+        android:layout_centerHorizontal="true"
+        android:divider="@null"
+        android:id="@+id/alarm_list" />
+</RelativeLayout>
diff --git a/app/src/main/res/layout/activity_configure_contacts.xml b/app/src/main/res/layout/activity_configure_contacts.xml
new file mode 100644
index 0000000..740bed3
--- /dev/null
+++ b/app/src/main/res/layout/activity_configure_contacts.xml
@@ -0,0 +1,27 @@
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:fitsSystemWindows="true"
+    app:layout_behavior="@string/appbar_scrolling_view_behavior"
+    tools:context="nodomain.freeyourgadget.gadgetbridge.activities.ConfigureContacts">
+
+    <androidx.recyclerview.widget.RecyclerView
+        android:id="@+id/contact_list"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_alignParentTop="true"
+        android:layout_centerHorizontal="true"
+        android:divider="@null" />
+
+    <com.google.android.material.floatingactionbutton.FloatingActionButton
+        android:id="@+id/fab"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentEnd="true"
+        android:layout_alignParentBottom="true"
+        android:layout_gravity="bottom|end"
+        android:layout_margin="16dp"
+        app:srcCompat="@drawable/ic_add" />
+</RelativeLayout>
diff --git a/app/src/main/res/layout/activity_configure_reminders.xml b/app/src/main/res/layout/activity_configure_reminders.xml
new file mode 100644
index 0000000..fbbc4cf
--- /dev/null
+++ b/app/src/main/res/layout/activity_configure_reminders.xml
@@ -0,0 +1,27 @@
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    app:layout_behavior="@string/appbar_scrolling_view_behavior"
+    android:fitsSystemWindows="true"
+    tools:context="nodomain.freeyourgadget.gadgetbridge.activities.ConfigureReminders">
+
+    <androidx.recyclerview.widget.RecyclerView
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_alignParentTop="true"
+        android:layout_centerHorizontal="true"
+        android:divider="@null"
+        android:id="@+id/reminder_list" />
+
+    <com.google.android.material.floatingactionbutton.FloatingActionButton
+        android:id="@+id/fab"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentBottom="true"
+        android:layout_alignParentEnd="true"
+        android:layout_gravity="bottom|end"
+        app:srcCompat="@drawable/ic_add"
+        android:layout_margin="16dp" />
+</RelativeLayout>
diff --git a/app/src/main/res/layout/activity_configure_world_clocks.xml b/app/src/main/res/layout/activity_configure_world_clocks.xml
new file mode 100644
index 0000000..727f8c4
--- /dev/null
+++ b/app/src/main/res/layout/activity_configure_world_clocks.xml
@@ -0,0 +1,27 @@
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    app:layout_behavior="@string/appbar_scrolling_view_behavior"
+    android:fitsSystemWindows="true"
+    tools:context="nodomain.freeyourgadget.gadgetbridge.activities.ConfigureWorldClocks">
+
+    <androidx.recyclerview.widget.RecyclerView
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_alignParentTop="true"
+        android:layout_centerHorizontal="true"
+        android:divider="@null"
+        android:id="@+id/world_clock_list" />
+
+    <com.google.android.material.floatingactionbutton.FloatingActionButton
+        android:id="@+id/fab"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentBottom="true"
+        android:layout_alignParentEnd="true"
+        android:layout_gravity="bottom|end"
+        app:srcCompat="@drawable/ic_add"
+        android:layout_margin="16dp" />
+</RelativeLayout>
diff --git a/app/src/main/res/layout/activity_contact_details.xml b/app/src/main/res/layout/activity_contact_details.xml
new file mode 100644
index 0000000..733cc11
--- /dev/null
+++ b/app/src/main/res/layout/activity_contact_details.xml
@@ -0,0 +1,96 @@
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:card_view="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/constraintLayout"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    tools:context="nodomain.freeyourgadget.gadgetbridge.activities.ContactDetails">
+
+    <com.google.android.material.card.MaterialCardView
+        android:id="@+id/card_contact_name"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_margin="8dp"
+        android:foreground="?android:attr/selectableItemBackground"
+        app:cardBackgroundColor="?attr/cardview_background_color"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        card_view:cardElevation="3dp"
+        card_view:contentPadding="4dp">
+
+        <androidx.constraintlayout.widget.ConstraintLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_margin="8dp">
+
+            <TextView
+                android:id="@+id/label_contact_name"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/contact_name"
+                android:textAppearance="?android:attr/textAppearance"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toTopOf="parent" />
+
+            <EditText
+                android:id="@+id/contact_name"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="2dp"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toBottomOf="@id/label_contact_name" />
+        </androidx.constraintlayout.widget.ConstraintLayout>
+    </com.google.android.material.card.MaterialCardView>
+
+    <com.google.android.material.card.MaterialCardView
+        android:id="@+id/card_contact_number"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_margin="8dp"
+        android:foreground="?android:attr/selectableItemBackground"
+        app:cardBackgroundColor="?attr/cardview_background_color"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/card_contact_name"
+        card_view:cardElevation="3dp"
+        card_view:contentPadding="4dp">
+
+        <androidx.constraintlayout.widget.ConstraintLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_margin="8dp">
+
+            <TextView
+                android:id="@+id/label_contact_number"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/contact_phone_number"
+                android:textAppearance="?android:attr/textAppearance"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toTopOf="parent" />
+
+            <EditText
+                android:id="@+id/contact_number"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="2dp"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toBottomOf="@id/label_contact_number" />
+        </androidx.constraintlayout.widget.ConstraintLayout>
+    </com.google.android.material.card.MaterialCardView>
+
+    <com.google.android.material.floatingactionbutton.FloatingActionButton
+        android:id="@+id/fab_save"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="bottom|end"
+        android:layout_margin="16dp"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:srcCompat="@drawable/ic_save" />
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/app/src/main/res/layout/activity_cycling_live_data.xml b/app/src/main/res/layout/activity_cycling_live_data.xml
new file mode 100644
index 0000000..b06560b
--- /dev/null
+++ b/app/src/main/res/layout/activity_cycling_live_data.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:orientation="vertical" >
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:id="@+id/cycling_data_speed"
+            android:textSize="@dimen/um25_value_text_size"
+            android:layout_marginTop="100dp"
+            android:gravity="center"
+            android:textColor="@color/primary_dark"
+            android:text="-"/>
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:id="@+id/cycling_data_trip_distance"
+            android:textSize="@dimen/um25_value_text_size"
+            android:layout_marginTop="100dp"
+            android:gravity="center"
+            android:textColor="@color/primary_dark"
+            android:text="-"/>
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:textSize="@dimen/um25_value_text_size_small"
+            android:gravity="center"
+            android:textColor="@color/secondarytext"
+            android:text="Long press to zero"/>
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:id="@+id/cycling_data_total_distance"
+            android:textSize="@dimen/um25_value_text_size_small"
+            android:layout_marginTop="100dp"
+            android:gravity="center"
+            android:textColor="@color/secondarytext"
+            android:text="-"/>
+
+    </LinearLayout>
+
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_dashboard_calendar.xml b/app/src/main/res/layout/activity_dashboard_calendar.xml
new file mode 100644
index 0000000..06c74fb
--- /dev/null
+++ b/app/src/main/res/layout/activity_dashboard_calendar.xml
@@ -0,0 +1,78 @@
+<ScrollView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context=".activities.dashboard.DashboardCalendarActivity">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical">
+
+        <RelativeLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_margin="8dp"
+            android:orientation="horizontal">
+            <Button
+                android:id="@+id/arrow_left"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:minWidth="0dp"
+                android:text="\u003C"
+                android:textStyle="bold"
+                android:textSize="20sp"
+                android:layout_alignParentLeft="true" />
+            <TextView
+                android:id="@+id/calendar_month"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/calendar_month"
+                android:textStyle="bold"
+                android:textSize="25sp"
+                android:layout_centerInParent="true" />
+            <Button
+                android:id="@+id/arrow_right"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:minWidth="0dp"
+                android:text="\u003E"
+                android:textStyle="bold"
+                android:textSize="20sp"
+                android:layout_alignParentRight="true" />
+        </RelativeLayout>
+
+        <androidx.gridlayout.widget.GridLayout
+            android:id="@+id/dashboard_calendar_grid"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="15dp"
+            android:layout_marginBottom="15dp"
+            app:columnCount="7" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_margin="8dp"
+            android:textSize="20sp"
+            android:text="@string/dashboard_calendar_month_goals_reached_title" />
+
+        <ImageView
+            android:id="@+id/dashboard_calendar_month_goals_chart"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_margin="8dp"
+            android:scaleType="fitCenter"
+            android:adjustViewBounds="true" />
+
+        <TextView
+            android:id="@+id/dashboard_calendar_month_goals_text"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_margin="8dp" />
+
+    </LinearLayout>
+
+</ScrollView>
diff --git a/app/src/main/res/layout/activity_data_management.xml b/app/src/main/res/layout/activity_data_management.xml
new file mode 100644
index 0000000..1d92049
--- /dev/null
+++ b/app/src/main/res/layout/activity_data_management.xml
@@ -0,0 +1,252 @@
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:grid="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context="nodomain.freeyourgadget.gadgetbridge.activities.DataManagementActivity">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical"
+        android:paddingLeft="@dimen/activity_horizontal_margin"
+        android:paddingTop="@dimen/activity_vertical_margin"
+        android:paddingRight="@dimen/activity_horizontal_margin"
+        android:paddingBottom="@dimen/activity_vertical_margin">
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="vertical"
+            android:paddingLeft="@dimen/activity_horizontal_margin"
+            android:paddingTop="@dimen/activity_vertical_margin"
+            android:paddingRight="@dimen/activity_horizontal_margin"
+            android:paddingBottom="@dimen/activity_vertical_margin">
+            <TextView
+                android:id="@+id/backupRestoreLabel"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/activity_db_management_backup_restore_label"
+                android:textAppearance="?android:attr/textAppearanceMedium"
+                android:textColor="@color/accent" />
+
+            <TextView
+                android:id="@+id/backup_restore_intro"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/activity_db_management_backup_restore_explanation"
+                android:textAppearance="?android:attr/textAppearanceSmall" />
+
+            <LinearLayout
+                style="?android:attr/buttonBarStyle"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="10dp"
+                android:gravity="center_vertical"
+                android:orientation="horizontal">
+
+                <Button
+                    android:id="@+id/backupToZipButton"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_weight="1"
+                    android:layout_marginEnd="2dp"
+                    android:text="@string/activity_db_management_export_to_zip" />
+
+                <Button
+                    android:id="@+id/restoreFromZipButton"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_weight="1"
+                    android:layout_marginStart="2dp"
+                    android:text="@string/activity_db_management_import_from_zip" />
+            </LinearLayout>
+        </LinearLayout>
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="vertical"
+            android:paddingLeft="@dimen/activity_horizontal_margin"
+            android:paddingTop="@dimen/activity_vertical_margin"
+            android:paddingRight="@dimen/activity_horizontal_margin"
+            android:paddingBottom="@dimen/activity_vertical_margin">
+
+
+            <TextView
+                android:id="@+id/exportImportLabel"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/activity_db_management_exportimport_label"
+                android:textAppearance="?android:attr/textAppearanceMedium"
+                android:textColor="@color/accent" />
+
+            <TextView
+                android:id="@+id/db_management_intro"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/activity_db_management_import_export_explanation"
+                android:textAppearance="?android:attr/textAppearanceSmall" />
+
+            <TextView
+                android:id="@+id/activity_data_management_path"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:textAppearance="?android:attr/textAppearanceSmall"
+                android:textStyle="bold" />
+
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:gravity="center_vertical"
+                android:orientation="horizontal">
+
+                <Button
+                    android:id="@+id/exportDataButton"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_weight="1"
+                    android:layout_marginEnd="2dp"
+                    android:text="@string/activity_DB_ExportButton" />
+
+                <Button
+                    android:id="@+id/importDataButton"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_weight="1"
+                    android:layout_marginStart="2dp"
+                    android:text="@string/activity_DB_import_button" />
+            </LinearLayout>
+
+            <Button
+                android:id="@+id/showContentDataButton"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:text="@string/activity_DB_ShowContentButton" />
+
+            <TextView
+                android:id="@+id/cleanExportDirectory_label"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="20dp"
+                android:text="@string/activity_db_management_clean_export_directory_label"
+                android:textAppearance="?android:attr/textAppearanceMedium"
+                android:textColor="@color/accent" />
+
+            <TextView
+                android:id="@+id/cleanExportDirectory_text"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/activity_db_management_clean_export_directory_text" />
+
+            <TextView
+                android:id="@+id/activity_data_management_path2"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:textAppearance="?android:attr/textAppearanceSmall"
+                android:textStyle="bold" />
+
+
+            <Button
+                android:id="@+id/cleanExportDirectoryButton"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:text="@string/activity_db_management_clean_export_directory_label" />
+
+            <TextView
+                android:id="@+id/autoExportTitleLabel"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="20dp"
+                android:text="@string/activity_db_management_autoexport_label"
+                android:textAppearance="?android:attr/textAppearanceMedium"
+                android:textColor="@color/accent" />
+
+            <TextView
+                android:id="@+id/autoExportEnabled"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text=""
+                android:textAppearance="?android:attr/textAppearanceSmall" />
+
+            <TextView
+                android:id="@+id/autoExportScheduled"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/activity_db_management_autoexport_scheduled_no"
+                android:textAppearance="?android:attr/textAppearanceSmall" />
+
+
+            <TextView
+                android:id="@+id/autoExport_lastTime_label"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/autoExport_lastTime_label"
+                android:textAppearance="?android:attr/textAppearanceSmall" />
+
+            <TextView
+                android:id="@+id/autoExportLocation_label"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/activity_db_management_autoexport_explanation"
+                android:textAppearance="?android:attr/textAppearanceSmall" />
+
+            <TextView
+                android:id="@+id/autoExportLocation_path"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:textAppearance="?android:attr/textAppearanceSmall"
+                android:textStyle="bold" />
+
+            <Button
+                android:id="@+id/testExportDBButton"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/activity_DB_test_export_button"
+                grid:layout_gravity="center" />
+
+            <TextView
+                android:id="@+id/mergeOldActivityDataTitle"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="20dp"
+                android:text="@string/activity_db_management_merge_old_title"
+                android:textAppearance="?android:attr/textAppearanceMedium"
+                android:textColor="@color/accent" />
+
+
+            <Button
+                android:id="@+id/deleteOldActivityDB"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/activity_DB_delete_legacy_button" />
+
+            <TextView
+                android:id="@+id/emptyActivityDataTitle"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="20dp"
+                android:text="@string/activity_db_management_empty_DB"
+                android:textAppearance="?android:attr/textAppearanceMedium"
+                android:textColor="@color/accent" />
+
+            <TextView
+                android:id="@+id/emptyDBText"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/activity_db_management_empty_db_warning"
+                android:textAppearance="?android:attr/textAppearanceSmall" />
+
+            <Button
+                android:id="@+id/emptyDBButton"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/activity_DB_empty_button" />
+
+        </LinearLayout>
+
+    </LinearLayout>
+
+</ScrollView>
diff --git a/app/src/main/res/layout/activity_debug.xml b/app/src/main/res/layout/activity_debug.xml
new file mode 100644
index 0000000..acd9ebd
--- /dev/null
+++ b/app/src/main/res/layout/activity_debug.xml
@@ -0,0 +1,332 @@
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:grid="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context="nodomain.freeyourgadget.gadgetbridge.activities.ControlCenterv2">
+
+    <ScrollView
+        android:id="@+id/scrollView"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentBottom="true"
+        android:layout_alignParentEnd="true"
+        android:layout_alignParentStart="true"
+        android:layout_alignParentTop="true">
+
+        <androidx.gridlayout.widget.GridLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:paddingBottom="@dimen/activity_vertical_margin"
+            android:paddingLeft="@dimen/activity_horizontal_margin"
+            android:paddingRight="@dimen/activity_horizontal_margin"
+            android:paddingTop="@dimen/activity_vertical_margin"
+            grid:alignmentMode="alignMargins"
+            grid:columnCount="2">
+
+            <TextView
+                android:id="@+id/textView"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                grid:layout_columnSpan="2"
+                grid:layout_gravity="fill_horizontal"
+                android:text="Message / Caller"
+                android:textAppearance="?android:attr/textAppearanceLarge" />
+
+            <EditText
+                android:id="@+id/editContent"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                grid:layout_columnSpan="2"
+                grid:layout_gravity="fill_horizontal"
+                android:ems="10"
+                android:inputType="textMultiLine"
+                android:text="Test" />
+
+            <Spinner
+                android:id="@+id/sendTypeSpinner"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                grid:layout_gravity="fill_horizontal"
+                grid:layout_columnSpan="2"
+                android:text="send as SMS" />
+
+            <Button
+                android:id="@+id/sendButton"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                grid:layout_columnSpan="2"
+                grid:layout_gravity="fill_horizontal"
+                android:text="Send" />
+
+            <Button
+                android:id="@+id/incomingCallButton"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginEnd="2dp"
+                grid:layout_gravity="fill_horizontal"
+                android:text="incoming call" />
+
+            <Button
+                android:id="@+id/outgoingCallButton"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginStart="2dp"
+                grid:layout_gravity="fill_horizontal"
+                android:text="outgoing call" />
+
+            <Button
+                android:id="@+id/startCallButton"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginEnd="2dp"
+                grid:layout_gravity="fill_horizontal"
+                android:text="start call" />
+
+            <Button
+                android:id="@+id/endCallButton"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginStart="2dp"
+                grid:layout_gravity="fill_horizontal"
+                android:text="end call" />
+
+            <Button
+                android:id="@+id/setTimeButton"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginEnd="2dp"
+                grid:layout_gravity="fill_horizontal"
+                android:text="set time" />
+
+            <Button
+                android:id="@+id/setMusicInfoButton"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginStart="2dp"
+                grid:layout_gravity="fill_horizontal"
+                android:text="set music info" />
+
+            <Button
+                android:id="@+id/HeartRateButton"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginEnd="2dp"
+                grid:layout_gravity="fill_horizontal"
+                android:text="Heart Rate Test" />
+
+            <Button
+                android:id="@+id/rebootButton"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginStart="2dp"
+                grid:layout_gravity="fill_horizontal"
+                android:text="reboot" />
+
+            <Button
+                android:id="@+id/SetFetchTimeButton"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                grid:layout_columnSpan="2"
+                grid:layout_gravity="fill_horizontal"
+                android:text="Set Activity Fetch Time" />
+
+            <Button
+                android:id="@+id/setWeatherButton"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginEnd="2dp"
+                grid:layout_gravity="fill_horizontal"
+                android:text="Set Weather" />
+
+            <Button
+                android:id="@+id/showCachedWeatherButton"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginStart="2dp"
+                grid:layout_gravity="fill_horizontal"
+                android:text="Show Cached Weather" />
+
+            <Button
+                android:id="@+id/factoryResetButton"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                grid:layout_columnSpan="2"
+                grid:layout_gravity="fill_horizontal"
+                android:text="factory reset" />
+
+            <Button
+                android:id="@+id/testNotificationButton"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                grid:layout_columnSpan="2"
+                grid:layout_gravity="fill_horizontal"
+                android:text="create test notification" />
+            <Button
+                android:id="@+id/testPebbleKitNotificationButton"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                grid:layout_columnSpan="2"
+                grid:layout_gravity="fill_horizontal"
+                android:text="create PebbleKit test notification" />
+            <Button
+                android:id="@+id/fetchDebugLogsButton"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                grid:layout_columnSpan="2"
+                grid:layout_gravity="fill_horizontal"
+                android:text="Fetch device Debug Logs" />
+            <Button
+                android:id="@+id/testNewFunctionality"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                grid:layout_columnSpan="2"
+                grid:layout_gravity="fill_horizontal"
+                android:text="Test New Functionality" />
+            <Button
+                android:id="@+id/shareLog"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                grid:layout_columnSpan="2"
+                grid:layout_gravity="fill_horizontal"
+                android:text="@string/share_log" />
+            <Button
+                android:id="@+id/showWidgetsButton"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                grid:layout_columnSpan="2"
+                grid:layout_gravity="fill_horizontal"
+                android:text="Show all registered app widgets IDs" />
+            <Button
+                android:id="@+id/deleteWidgets"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                grid:layout_columnSpan="2"
+                grid:layout_gravity="fill_horizontal"
+                android:text="Delete all registered app widgets IDs" />
+            <Button
+                android:id="@+id/showWidgetsPrefs"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                grid:layout_columnSpan="2"
+                grid:layout_gravity="fill_horizontal"
+                android:text="Show app Widgets Preferences" />
+            <Button
+                android:id="@+id/deleteWidgetsPrefs"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                grid:layout_columnSpan="2"
+                grid:layout_gravity="fill_horizontal"
+                android:text="Delete app Widgets Preferences" />
+
+            <CheckBox
+                android:id="@+id/activity_list_debug_extra_time_range"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_margin="4dp"
+                android:layout_marginTop="7dp"
+                android:padding="4dp"
+                android:paddingLeft="10dp"
+                android:text="Activity summary extra date range (takes long to generate when used)"
+                android:textStyle="bold"
+                grid:layout_columnSpan="2" />
+            <Button
+                android:id="@+id/addDeviceButtonDebug"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                grid:layout_columnSpan="2"
+                grid:layout_gravity="fill_horizontal"
+                android:text="Add test device manually" />
+
+            <Button
+                android:id="@+id/removeDevicePreferences"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="Remove device preferences"
+                grid:layout_columnSpan="2"
+                grid:layout_gravity="fill_horizontal" />
+
+            <Button
+                android:id="@+id/runDebugFunction"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="Run Debug Function"
+                grid:layout_columnSpan="2"
+                grid:layout_gravity="fill_horizontal" />
+
+            <Button
+                android:id="@+id/startFitnessAppTracking"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/pref_device_action_fitness_app_control_start"
+                grid:layout_columnSpan="2"
+                grid:layout_gravity="fill_horizontal" />
+
+            <Button
+                android:id="@+id/stopFitnessAppTracking"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/pref_device_action_fitness_app_control_stop"
+                grid:layout_columnSpan="2"
+                grid:layout_gravity="fill_horizontal" />
+
+            <Button
+                android:id="@+id/showStatusFitnessAppTracking"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="Show Fit.App.Track. Status"
+                grid:layout_columnSpan="2"
+                grid:layout_gravity="fill_horizontal" />
+
+            <Button
+                android:id="@+id/stopPhoneGpsLocationListener"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/pref_device_action_phone_gps_location_listener_stop"
+                grid:layout_columnSpan="2"
+                grid:layout_gravity="fill_horizontal" />
+
+            <Button
+                android:id="@+id/showCompanionDevices"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                grid:layout_columnSpan="2"
+                grid:layout_gravity="fill_horizontal"
+                android:text="@string/debug_companion_show_associated" />
+
+            <Button
+                android:id="@+id/pairAsCompanion"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                grid:layout_columnSpan="2"
+                grid:layout_gravity="fill_horizontal"
+                android:text="@string/debug_companion_pair_current" />
+
+            <Button
+                android:id="@+id/cameraOpen"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                grid:layout_columnSpan="2"
+                grid:layout_gravity="fill_horizontal"
+                android:text="@string/open_camera" />
+
+            <Button
+                android:id="@+id/startWelcomeActivity"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginEnd="2dp"
+                grid:layout_gravity="fill_horizontal"
+                android:text="Welcome Activity" />
+
+            <Button
+                android:id="@+id/startPermissionsActivity"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginEnd="2dp"
+                grid:layout_gravity="fill_horizontal"
+                android:text="Permissions Screen" />
+        </androidx.gridlayout.widget.GridLayout>
+
+    </ScrollView>
+
+</RelativeLayout>
diff --git a/app/src/main/res/layout/activity_device_settings.xml b/app/src/main/res/layout/activity_device_settings.xml
new file mode 100644
index 0000000..c2e4426
--- /dev/null
+++ b/app/src/main/res/layout/activity_device_settings.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:layout_margin="10dp">
+    <FrameLayout
+        android:id="@+id/settings_container"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"/>
+</LinearLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_discovery.xml b/app/src/main/res/layout/activity_discovery.xml
new file mode 100644
index 0000000..cf7177f
--- /dev/null
+++ b/app/src/main/res/layout/activity_discovery.xml
@@ -0,0 +1,68 @@
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:paddingLeft="@dimen/activity_horizontal_margin"
+    android:paddingRight="@dimen/activity_horizontal_margin"
+    android:paddingTop="@dimen/activity_vertical_margin"
+    android:paddingBottom="@dimen/activity_vertical_margin"
+    tools:context="nodomain.freeyourgadget.gadgetbridge.activities.discovery.DiscoveryActivityV2">
+
+    <LinearLayout
+        android:orientation="vertical"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:weightSum="1">
+
+        <Button
+            android:id="@+id/discovery_start"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_horizontal"
+            android:text="@string/discovery_start_scanning" />
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginBottom="5dp"
+            android:gravity="center_vertical">
+
+            <ProgressBar
+                android:id="@+id/discovery_progressbar"
+                style="?android:attr/progressBarStyleHorizontal"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_horizontal"
+                android:indeterminate="true"
+                android:indeterminateOnly="true"
+                android:visibility="gone" />
+
+        </LinearLayout>
+
+        <ListView
+            android:layout_width="wrap_content"
+            android:layout_height="0dp"
+            android:id="@+id/discovery_device_candidates_list"
+            android:layout_gravity="bottom|top"
+            android:layout_weight="1" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:textAppearance="?android:attr/textAppearanceSmall"
+            android:text="@string/discovery_note"
+            android:id="@+id/discovery_note"
+            android:textStyle="bold"
+            android:textColor="@color/secondarytext"
+            android:gravity="center_horizontal|bottom" />
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textAppearance="?android:attr/textAppearanceSmall"
+            android:text="@string/discovery_connected_devices_hint"
+            android:id="@+id/discovery_hint"
+            android:textColor="@color/secondarytext"
+            android:textIsSelectable="true" />
+    </LinearLayout>
+</LinearLayout>
diff --git a/app/src/main/res/layout/activity_external_pebble_js.xml b/app/src/main/res/layout/activity_external_pebble_js.xml
new file mode 100644
index 0000000..638624a
--- /dev/null
+++ b/app/src/main/res/layout/activity_external_pebble_js.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<FrameLayout android:id="@+id/webview_placeholder"
+    android:layout_height="match_parent"
+    android:layout_width="match_parent"
+    xmlns:android="http://schemas.android.com/apk/res/android" />
diff --git a/app/src/main/res/layout/activity_file_manager.xml b/app/src/main/res/layout/activity_file_manager.xml
new file mode 100644
index 0000000..c40ba3e
--- /dev/null
+++ b/app/src/main/res/layout/activity_file_manager.xml
@@ -0,0 +1,25 @@
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context=".activities.files.FileManagerActivity">
+
+    <androidx.appcompat.widget.SearchView
+        android:id="@+id/fileListSearchView"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_alignParentTop="true"
+        android:paddingStart="8dp"
+        android:paddingEnd="8dp" />
+
+    <androidx.recyclerview.widget.RecyclerView
+        android:id="@+id/fileListView"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_below="@id/fileListSearchView"
+        android:layout_centerHorizontal="true"
+        android:divider="@null"
+        android:scrollbarSize="5dp"
+        android:scrollbars="vertical" />
+
+</RelativeLayout>
diff --git a/app/src/main/res/layout/activity_find_phone.xml b/app/src/main/res/layout/activity_find_phone.xml
new file mode 100644
index 0000000..e200d03
--- /dev/null
+++ b/app/src/main/res/layout/activity_find_phone.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:layout_margin="10dp"
+    android:gravity="center">
+
+    <Button
+        android:id="@+id/foundbutton"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="@string/find_my_phone_found_it" />
+</LinearLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_fragmentappmanager.xml b/app/src/main/res/layout/activity_fragmentappmanager.xml
new file mode 100644
index 0000000..3fd8e2d
--- /dev/null
+++ b/app/src/main/res/layout/activity_fragmentappmanager.xml
@@ -0,0 +1,52 @@
+<android.widget.RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    tools:context=".activities.appmanager.AppManagerActivity">
+
+    <LinearLayout
+        android:id="@+id/appmanager_main_layout"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:orientation="vertical">
+
+        <androidx.viewpager.widget.ViewPager
+            android:id="@+id/appmanager_pager"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            tools:context=".activities.appmanager.AppManagerActivity">
+
+            <com.google.android.material.tabs.TabLayout
+                android:id="@+id/charts_pagerTabStrip"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_gravity="top" />
+        </androidx.viewpager.widget.ViewPager>
+
+    </LinearLayout>
+
+    <com.google.android.material.floatingactionbutton.FloatingActionButton
+        android:id="@+id/fab"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentBottom="true"
+        android:layout_alignParentEnd="true"
+        android:layout_gravity="bottom|end"
+        app:srcCompat="@android:drawable/stat_sys_upload"
+        android:layout_margin="16dp" />
+
+    <com.google.android.material.floatingactionbutton.FloatingActionButton
+        android:id="@+id/fab_new"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentEnd="true"
+        android:layout_alignParentBottom="true"
+        android:layout_gravity="bottom|end"
+        android:layout_marginEnd="16dp"
+        android:layout_marginBottom="90dp"
+        app:srcCompat="@drawable/ic_add"
+        android:visibility="invisible"
+        />
+
+</android.widget.RelativeLayout>
diff --git a/app/src/main/res/layout/activity_gpx_receiver.xml b/app/src/main/res/layout/activity_gpx_receiver.xml
new file mode 100644
index 0000000..b6f2a6a
--- /dev/null
+++ b/app/src/main/res/layout/activity_gpx_receiver.xml
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+
+    <TextView
+        android:id="@+id/gpx_receiver_received_label"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginLeft="@dimen/about_margin"
+        android:layout_marginTop="20sp"
+        android:layout_marginRight="@dimen/about_margin"
+        android:layout_marginBottom="20sp"
+        android:fontFamily="sans-serif-black"
+        android:textSize="20sp" />
+
+    <ScrollView
+        android:layout_width="match_parent"
+        android:layout_height="0dip"
+        android:layout_marginLeft="@dimen/about_margin"
+        android:layout_marginRight="@dimen/about_margin"
+        android:layout_weight="1">
+
+        <TextView
+            android:id="@+id/gpx_receiver_files_listing"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="20sp"
+            android:layout_marginBottom="20sp"
+            android:textSize="16sp" />
+    </ScrollView>
+
+    <LinearLayout
+        android:id="@+id/gpx_receiver_overwrite_layout"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical">
+
+        <TextView
+            android:id="@+id/gpx_receiver_overwrite_label"
+            android:layout_width="match_parent"
+            android:layout_height="0dp"
+            android:layout_weight="1"
+            android:text="@string/gpx_receiver_overwrite_some_files"
+            android:textAlignment="center"
+            android:textSize="20sp" />
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal">
+
+            <Button
+                android:id="@+id/gpx_receiver_cancel"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:text="@string/Cancel" />
+
+            <Button
+                android:id="@+id/gpx_receiver_ok"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:text="@string/ok" />
+        </LinearLayout>
+    </LinearLayout>
+</LinearLayout>
diff --git a/app/src/main/res/layout/activity_hybridhr_watchface_designer.xml b/app/src/main/res/layout/activity_hybridhr_watchface_designer.xml
new file mode 100644
index 0000000..69d0679
--- /dev/null
+++ b/app/src/main/res/layout/activity_hybridhr_watchface_designer.xml
@@ -0,0 +1,164 @@
+<?xml version="1.0" encoding="utf-8"?>
+<ScrollView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content">
+<LinearLayout
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical"
+    tools:context=".devices.qhybrid.HybridHRWatchfaceDesignerActivity">
+
+    <ProgressBar
+        style="@android:style/Widget.DeviceDefault.ProgressBar.Horizontal"
+        android:id="@+id/watchface_upload_progress_bar"
+        android:layout_width="match_parent"
+        android:layout_height="10dp"
+        android:scaleY="3"
+        android:indeterminate="true"
+        android:visibility="gone" />
+
+    <RelativeLayout
+        android:id="@+id/watchface_name_layout"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+
+        <TextView
+            android:id="@+id/watchface_name"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_centerVertical="true"
+            android:layout_marginStart="10dp"
+            android:text="NewWatchface"
+            android:textSize="16sp" />
+
+        <Button
+            android:id="@+id/button_edit_name"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_alignParentEnd="true"
+            android:text="@string/button_watchface_edit_name" />
+    </RelativeLayout>
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:orientation="horizontal">
+
+        <ImageView
+            android:id="@+id/watchface_invert_colors"
+            android:layout_width="40dp"
+            android:layout_height="40dp"
+            app:srcCompat="@drawable/ic_invert_colors" />
+
+        <ImageView
+            android:id="@+id/watchface_rotate_left"
+            android:layout_width="40dp"
+            android:layout_height="40dp"
+            android:layout_alignStart="@+id/watchface_invert_colors"
+            app:srcCompat="@drawable/ic_rotate_left" />
+
+        <ImageView
+            android:id="@+id/watchface_rotate_right"
+            android:layout_width="40dp"
+            android:layout_height="40dp"
+            android:layout_alignStart="@+id/watchface_rotate_left"
+            android:rotationY="180"
+            app:srcCompat="@drawable/ic_rotate_left" />
+
+        <ImageView
+            android:id="@+id/watchface_remove_image"
+            android:layout_width="40dp"
+            android:layout_height="40dp"
+            android:layout_alignStart="@+id/watchface_rotate_right"
+            app:srcCompat="@drawable/ic_remove_image" />
+    </LinearLayout>
+
+    <RelativeLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+
+        <RelativeLayout
+            android:id="@+id/watchface_preview_layout"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="20dp"
+            android:layout_marginBottom="20dp">
+
+            <ImageView
+                android:id="@+id/hybridhr_background_image"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_centerHorizontal="true" />
+        </RelativeLayout>
+    </RelativeLayout>
+
+    <RelativeLayout
+        android:id="@+id/watchface_widget_delete_layout"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginBottom="20dp"
+        android:visibility="gone">
+
+        <ImageView
+            android:id="@+id/watchface_widget_delete_droparea"
+            android:layout_width="60dp"
+            android:layout_height="60dp"
+            android:layout_centerHorizontal="true"
+            app:srcCompat="@drawable/ic_delete_forever" />
+    </RelativeLayout>
+
+    <Button
+        android:id="@+id/button_set_background"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="@string/button_watchface_select_image" />
+
+    <Button
+        android:id="@+id/button_add_widget"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="@string/button_watchface_add_widget" />
+
+    <Button
+        android:id="@+id/button_watchface_settings"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="@string/button_watchface_settings" />
+
+    <Button
+        android:id="@+id/button_watchface_open_menu_companion"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="@string/button_open_menu_companion" />
+
+    <Button
+        android:id="@+id/button_watchface_reset_menu_structure"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="@string/button_reset_menu_structure" />
+
+    <com.google.android.material.card.MaterialCardView
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        xmlns:app="http://schemas.android.com/apk/res-auto"
+        android:id="@+id/fossil_menu_structure_hint_container"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_margin="8dp"
+        android:foreground="?android:attr/selectableItemBackground"
+        app:cardBackgroundColor="?attr/cardview_background_color"
+        app:cardElevation="3dp"
+        app:contentPadding="8dp">
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:id="@+id/text_watchface_menu_structure"
+            android:lines="1"
+            android:ellipsize="end" />
+    </com.google.android.material.card.MaterialCardView>
+
+
+</LinearLayout>
+</ScrollView>
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_idasen_control.xml b/app/src/main/res/layout/activity_idasen_control.xml
new file mode 100644
index 0000000..7fa5999
--- /dev/null
+++ b/app/src/main/res/layout/activity_idasen_control.xml
@@ -0,0 +1,122 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:gravity="center"
+    android:orientation="vertical">
+
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:gravity="center"
+        android:orientation="horizontal">
+
+        <Button
+            android:id="@+id/idasen_control_button_sit"
+            android:layout_width="wrap_content"
+            android:layout_height="100dp"
+            android:layout_weight="0.5"
+            android:text="@string/idasen_control_button_sit"
+            app:icon="@drawable/ic_activity_yoga"
+            app:iconPadding="0dp"
+            app:iconSize="35dp"
+            app:iconTint="#FFFFFF" />
+
+        <Button
+            android:id="@+id/idasen_control_button_mid"
+            android:layout_width="wrap_content"
+            android:layout_height="100dp"
+            android:layout_weight="0.5"
+            android:text="@string/idasen_control_button_mid"
+            app:icon="@drawable/ic_activity_bike_trainer"
+            app:iconSize="35dp" />
+
+        <Button
+            android:id="@+id/idasen_control_button_stand"
+            android:layout_width="wrap_content"
+            android:layout_height="100dp"
+            android:layout_weight="0.5"
+            android:text="@string/idasen_control_button_stand"
+            app:icon="@drawable/ic_activity_exercise"
+            app:iconSize="35dp"
+            app:iconTint="#FFFFFF" />
+
+    </LinearLayout>
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:gravity="center"
+        android:orientation="vertical">
+
+        <Button
+            android:id="@+id/idasen_control_button_up"
+            android:layout_width="wrap_content"
+            android:layout_height="100dp"
+            android:layout_weight="0.5"
+            android:background="@drawable/ic_arrow_upward" />
+
+        <Button
+            android:id="@+id/idasen_control_button_down"
+            android:layout_width="wrap_content"
+            android:layout_height="100dp"
+            android:layout_weight="0.5"
+            android:background="@drawable/ic_arrow_upward"
+            android:drawable="@drawable/ic_arrow_upward"
+            android:rotation="180" />
+
+    </LinearLayout>
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:gravity="center"
+        android:orientation="horizontal">
+
+        <ImageView
+            android:id="@+id/idasen_desk_height_icon"
+            android:layout_width="40dp"
+            android:layout_height="40dp"
+            android:layout_gravity="start|center_vertical"
+            android:background="@drawable/arrow_autofit_height"
+            android:backgroundTint="#FF0000"
+            android:contentDescription="@string/icon_placeholder"
+            android:padding="0dp" />
+
+        <TextView
+            android:id="@+id/idasen_desk_height"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_weight="0.5"
+            android:textIsSelectable="true"
+            android:textAlignment="center"
+            android:textColor="#FFFFFF"
+            android:textSize="24sp" />
+
+        <ImageView
+            android:id="@+id/idasen_desk_speed_icon"
+            android:layout_width="40dp"
+            android:layout_height="40dp"
+            android:layout_gravity="start|center_vertical"
+            android:background="@drawable/brand_speedtest"
+            android:backgroundTint="#FF0000"
+            android:contentDescription="@string/icon_placeholder"
+            android:padding="0dp" />
+
+        <TextView
+            android:id="@+id/idasen_desk_speed"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_weight="0.5"
+            android:textAlignment="center"
+            android:textColor="#FFFFFF"
+            android:textSize="24sp"
+            android:textIsSelectable="true"
+            android:visibility="visible" />
+
+    </LinearLayout>
+
+
+</LinearLayout>
diff --git a/app/src/main/res/layout/activity_legacy_external_pebble_js.xml b/app/src/main/res/layout/activity_legacy_external_pebble_js.xml
new file mode 100644
index 0000000..551fb92
--- /dev/null
+++ b/app/src/main/res/layout/activity_legacy_external_pebble_js.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<WebView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/configureWebview"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent" />
diff --git a/app/src/main/res/layout/activity_list.xml b/app/src/main/res/layout/activity_list.xml
new file mode 100644
index 0000000..af58074
--- /dev/null
+++ b/app/src/main/res/layout/activity_list.xml
@@ -0,0 +1,46 @@
+<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:gravity="center_horizontal">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:orientation="vertical">
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:orientation="horizontal">
+
+
+        </LinearLayout>
+
+        <androidx.swiperefreshlayout.widget.SwipeRefreshLayout
+            android:id="@+id/list_activity_swipe_layout"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent">
+
+            <androidx.recyclerview.widget.RecyclerView
+                android:id="@+id/itemListView"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:scrollbars="vertical" />
+
+        </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
+    </LinearLayout>
+
+    <com.google.android.material.floatingactionbutton.FloatingActionButton
+        android:id="@+id/fab"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentEnd="true"
+        android:layout_alignParentBottom="true"
+        android:layout_gravity="bottom|end"
+        android:layout_margin="16dp"
+        android:tint="@android:color/white"
+        app:srcCompat="@drawable/ic_refresh" />
+
+</androidx.coordinatorlayout.widget.CoordinatorLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_list_dashboard_item.xml b/app/src/main/res/layout/activity_list_dashboard_item.xml
new file mode 100644
index 0000000..0efb543
--- /dev/null
+++ b/app/src/main/res/layout/activity_list_dashboard_item.xml
@@ -0,0 +1,132 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/list_item_parent_layout"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content">
+    <!--
+    android:background="?android:attr/activatedBackgroundIndicator"
+    -->
+
+    <LinearLayout
+        android:id="@+id/activity_list_summary_dashboard"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginBottom="10dp"
+        android:orientation="vertical">
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="center"
+            android:orientation="horizontal">
+
+            <com.github.mikephil.charting.charts.PieChart
+                android:id="@+id/activity_dashboard_piechart1"
+                android:layout_width="110dp"
+                android:layout_height="110dp"
+                android:layout_gravity="bottom|center"
+                android:layout_marginBottom="0dp"
+                android:layout_weight="1" />
+
+            <com.github.mikephil.charting.charts.PieChart
+                android:id="@+id/activity_dashboard_piechart2"
+                android:layout_width="110dp"
+                android:layout_height="110dp"
+                android:layout_gravity="bottom|center"
+                android:layout_marginBottom="0dp"
+                android:layout_weight="1" />
+
+            <com.github.mikephil.charting.charts.PieChart
+                android:id="@+id/activity_dashboard_piechart3"
+                android:layout_width="110dp"
+                android:layout_height="110dp"
+                android:layout_gravity="bottom|center"
+                android:layout_marginBottom="0dp"
+                android:layout_weight="1" />
+            <!--
+                                <com.github.mikephil.charting.charts.PieChart
+                                    android:id="@+id/activity_dashboard_piechart4"
+                                    android:layout_width="90dp"
+                                    android:layout_height="90dp"
+                                    android:layout_gravity="bottom|center"
+                                    android:layout_marginBottom="0dp" />
+                                    -->
+        </LinearLayout>
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_gravity="center"
+            android:layout_marginBottom="0dp"
+            android:gravity="center"
+            android:orientation="horizontal">
+
+            <View
+                android:layout_width="25dp"
+                android:layout_height="0dp"
+                android:layout_weight="0.5" />
+
+            <include
+                layout="@layout/layout_widget_heartrate_average"
+                android:layout_width="50dp"
+                android:layout_height="50dp"
+                android:layout_gravity="bottom|center"
+                android:layout_weight="1" />
+
+            <include
+                layout="@layout/layout_widget_intensity_total"
+                android:layout_width="50dp"
+                android:layout_height="50dp"
+                android:layout_gravity="bottom|center"
+                android:layout_marginTop="0dp"
+                android:layout_marginEnd="0dp"
+                android:layout_weight="1" />
+
+            <View
+                android:layout_width="25dp"
+                android:layout_height="0dp"
+                android:layout_weight="0.5" />
+        </LinearLayout>
+
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="0dp"
+            android:layout_marginBottom="10dp"
+            android:baselineAligned="false"
+            android:gravity="bottom|center"
+            android:orientation="horizontal">
+
+            <include layout="@layout/layout_widget_active_steps" />
+
+            <include layout="@layout/layout_widget_distance" />
+
+            <include layout="@layout/layout_widget_duration" />
+
+        </LinearLayout>
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="5dp"
+            android:layout_marginBottom="5dp"
+            android:baselineAligned="false"
+            android:gravity="center"
+            android:orientation="horizontal">
+
+            <include layout="@layout/layout_widget_total_steps" />
+
+            <include layout="@layout/layout_widget_activities_count" />
+
+            <include layout="@layout/layout_widget_intensity_total2" />
+
+        </LinearLayout>
+    </LinearLayout>
+    <View
+        android:layout_below="@id/activity_list_summary_dashboard"
+        android:layout_width="match_parent"
+        android:layout_height="1dp"
+        android:background="?attr/row_separator"
+        />
+</RelativeLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_list_detail.xml b/app/src/main/res/layout/activity_list_detail.xml
new file mode 100644
index 0000000..8a88622
--- /dev/null
+++ b/app/src/main/res/layout/activity_list_detail.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <LinearLayout
+        android:id="@+id/activity_battery_info_master_layout"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="0dp"
+        android:layout_marginTop="10dp"
+        android:layout_marginBottom="10dp"
+        android:gravity="center|center_vertical"
+        android:minWidth="1000dp"
+        android:orientation="vertical">
+
+        <include
+            android:id="@+id/activityItemHolder"
+            layout="@layout/activity_list_item"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content" />
+
+        <FrameLayout
+            android:id="@+id/chartsFragmentHolder"
+            android:layout_width="match_parent"
+            android:layout_height="300dp" />
+    </LinearLayout>
+</ScrollView>
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_list_item.xml b/app/src/main/res/layout/activity_list_item.xml
new file mode 100644
index 0000000..b5374cb
--- /dev/null
+++ b/app/src/main/res/layout/activity_list_item.xml
@@ -0,0 +1,289 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/list_item_parent_layout"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content">
+
+    <LinearLayout
+        android:gravity="start"
+        android:layout_gravity="start"
+        android:id="@+id/list_item_subparent_layout"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:paddingStart="10dp"
+        android:paddingTop="10dp"
+        android:paddingEnd="10dp"
+        android:paddingBottom="10dp"
+        android:background="?attr/selectableItemBackground"
+        android:orientation="horizontal">
+
+            <LinearLayout
+                android:id="@+id/line_layout_time"
+                android:layout_width="wrap_content"
+                android:layout_height="match_parent"
+                android:layout_marginEnd="15dp"
+                android:orientation="vertical">
+
+                <TextView
+                    android:id="@+id/line_layout_time_from"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_gravity="end"
+                    android:maxLines="1"
+                    android:scrollHorizontally="false"
+                    android:text="@string/time_empty_value" />
+
+                <TextView
+                    android:id="@+id/line_layout_time_to"
+                    android:layout_width="wrap_content"
+                    android:layout_height="0dp"
+                    android:layout_gravity="end"
+                    android:layout_weight="1"
+                    android:gravity="bottom"
+                    android:maxLines="1"
+                    android:scrollHorizontally="false"
+                    android:text="@string/time_empty_value" />
+
+            </LinearLayout>
+
+            <LinearLayout
+                android:layout_width="wrap_content"
+                android:layout_height="match_parent"
+                android:orientation="vertical"
+                android:gravity="center_horizontal|center_vertical"
+                >
+
+                <ImageView
+                    android:id="@+id/line_layout_activity_icon"
+                    android:layout_width="50dp"
+                    android:layout_height="50dp"
+                    android:layout_marginEnd="5dp"
+                    android:contentDescription="@string/candidate_item_device_image"
+                    android:padding="8dp"
+                    app:srcCompat="@drawable/ic_activity_unknown" />
+
+            </LinearLayout>
+
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:orientation="vertical"
+                android:minHeight="50dp"
+                >
+
+                <TableLayout
+                    android:gravity="top"
+                    android:layout_weight="1"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:shrinkColumns="0"
+                    >
+                    <TableRow
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:gravity="center_vertical">
+                        <TextView
+                            android:id="@+id/line_layout_activity_name"
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content"
+                            android:layout_weight="0"
+                            android:scrollHorizontally="false"
+                            android:text="@string/unknown"
+                            android:textStyle="bold"
+                            android:textSize="16sp"
+                            android:singleLine="true"
+                            android:ellipsize="end"
+                            />
+                        <TextView
+                            android:gravity="end|bottom"
+                            android:layout_gravity="top"
+                            android:id="@+id/line_layout_date_label"
+                            android:layout_width="wrap_content"
+                            android:layout_height="match_parent"
+                            android:layout_weight="1"
+                            android:scrollHorizontally="false"
+                            android:text="@string/stats_empty_value"
+                            android:singleLine="true"
+                            android:ellipsize="none"
+                            />
+                    </TableRow>
+                </TableLayout>
+                <TextView
+                    android:id="@+id/line_layout_activity_label"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_weight="0"
+                    />
+                <LinearLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:orientation="horizontal"
+                    android:gravity="bottom"
+                    >
+                    <TextView
+                        android:id="@+id/line_layout_duration_label"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:layout_weight="1"
+                        android:gravity="start"
+                        android:maxLines="1"
+                        android:scrollHorizontally="false"
+                        android:text="@string/stats_empty_value" />
+                    <LinearLayout
+                        android:id="@+id/line_layout_duration_gps"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:gravity="end"
+                        android:orientation="horizontal">
+
+                        <ImageView
+                            android:id="@+id/line_layout_gps_icon"
+                            android:layout_width="20dp"
+                            android:layout_marginStart="2dp"
+                            android:layout_height="19dp"
+                            android:contentDescription="@string/candidate_item_device_image"
+                            app:srcCompat="@android:drawable/ic_menu_mylocation" />
+
+                    </LinearLayout>
+                </LinearLayout>
+
+
+                <LinearLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:gravity="bottom"
+                    android:orientation="horizontal"
+                    >
+
+                    <LinearLayout
+                        android:id="@+id/line_layout_step"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:layout_gravity="start"
+                        android:gravity="bottom|start"
+                        android:orientation="horizontal">
+
+                        <ImageView
+                            android:id="@+id/line_layout_step_icon"
+                            android:layout_width="19dp"
+                            android:layout_height="19dp"
+                            android:layout_gravity="bottom|start"
+                            android:contentDescription="@string/candidate_item_device_image"
+                            app:srcCompat="@drawable/ic_shoe_print" />
+
+                        <TextView
+                            android:id="@+id/line_layout_step_label"
+                            android:layout_width="0dp"
+                            android:layout_height="wrap_content"
+                            android:layout_gravity="start"
+                            android:layout_marginEnd="4dp"
+                            android:layout_weight="1"
+                            android:gravity="start"
+                            android:maxLines="1"
+                            android:scrollHorizontally="false"
+                            android:text="@string/stats_empty_value" />
+
+                    </LinearLayout>
+
+                    <LinearLayout
+                        android:id="@+id/line_layout_intensity"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:layout_gravity="start"
+                        android:gravity="start"
+                        android:orientation="horizontal">
+
+                        <ImageView
+                            android:id="@+id/line_layout_intensity_icon"
+                            android:layout_width="19dp"
+                            android:layout_height="19dp"
+                            android:layout_gravity="start"
+                            android:contentDescription="@string/candidate_item_device_image"
+                            app:srcCompat="@drawable/ic_intensity" />
+
+                        <TextView
+                            android:id="@+id/line_layout_intensity_label"
+                            android:layout_width="0dp"
+                            android:layout_height="wrap_content"
+                            android:layout_gravity="start"
+                            android:layout_marginEnd="4dp"
+                            android:layout_weight="1"
+                            android:gravity="start"
+                            android:maxLines="1"
+                            android:scrollHorizontally="false"
+                            android:text="@string/stats_empty_value" />
+
+                    </LinearLayout>
+
+                    <LinearLayout
+                        android:id="@+id/line_layout_distance"
+                        android:layout_width="wrap_content"
+                        android:layout_height="match_parent"
+                        android:layout_gravity="start"
+                        android:gravity="start"
+                        android:orientation="horizontal">
+
+                        <ImageView
+                            android:id="@+id/line_layout_distance_icon"
+                            android:layout_width="19dp"
+                            android:layout_height="19dp"
+                            android:layout_gravity="start"
+                            android:contentDescription="@string/candidate_item_device_image"
+                            app:srcCompat="@drawable/ic_distance" />
+
+                        <TextView
+                            android:id="@+id/line_layout_distance_label"
+                            android:layout_width="0dp"
+                            android:layout_height="wrap_content"
+                            android:layout_gravity="start"
+                            android:layout_marginEnd="4dp"
+                            android:layout_weight="1"
+                            android:gravity="start"
+                            android:maxLines="1"
+                            android:scrollHorizontally="false"
+                            android:text="@string/stats_empty_value" />
+
+                    </LinearLayout>
+
+                    <LinearLayout
+                        android:id="@+id/line_layout_hr"
+                        android:layout_width="wrap_content"
+                        android:layout_height="match_parent"
+                        android:layout_gravity="start"
+                        android:gravity="start"
+                        android:orientation="horizontal">
+
+                        <ImageView
+                            android:id="@+id/line_layout_hr_icon"
+                            android:layout_width="19dp"
+                            android:layout_height="19dp"
+                            android:layout_gravity="start"
+                            android:contentDescription="@string/candidate_item_device_image"
+                            app:srcCompat="@drawable/ic_heartrate" />
+
+                        <TextView
+                            android:id="@+id/line_layout_hr_label"
+                            android:layout_width="0dp"
+                            android:layout_height="wrap_content"
+                            android:layout_gravity="start"
+                            android:layout_marginEnd="2dp"
+                            android:layout_weight="1"
+                            android:gravity="start"
+                            android:maxLines="1"
+                            android:scrollHorizontally="false"
+                            android:text="@string/stats_empty_value" />
+
+                    </LinearLayout>
+
+                </LinearLayout>
+
+            </LinearLayout>
+        </LinearLayout>
+    <View
+        android:layout_below="@id/list_item_subparent_layout"
+        android:layout_width="match_parent"
+        android:layout_height="1dp"
+        android:background="?attr/row_separator"
+        />
+</RelativeLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_list_total_dashboard.xml b/app/src/main/res/layout/activity_list_total_dashboard.xml
new file mode 100644
index 0000000..2ce1edb
--- /dev/null
+++ b/app/src/main/res/layout/activity_list_total_dashboard.xml
@@ -0,0 +1,87 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/activity_battery_info_master_layout"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:layout_marginStart="0dp"
+    android:layout_marginTop="10dp"
+    android:layout_marginBottom="10dp"
+    android:gravity="center|center_vertical"
+    android:minWidth="1000dp"
+    android:orientation="vertical">
+
+    <RelativeLayout
+        android:id="@+id/activity_list_dashboard_loading_layout"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_marginTop="10dp"
+        android:layout_marginBottom="10dp"
+        android:gravity="center"
+        android:orientation="vertical">
+
+        <ImageView
+            android:layout_width="100dp"
+            android:layout_height="100dp"
+            android:layout_marginStart="35dp"
+            android:layout_marginTop="35dp"
+            app:srcCompat="@drawable/gadgetbridge_img" />
+
+        <ProgressBar
+            android:layout_width="171dp"
+            android:layout_height="171dp"
+            android:indeterminate="true" />
+
+    </RelativeLayout>
+
+    <LinearLayout
+        android:id="@+id/activity_list_dashboard_results_layout"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="10dp"
+        android:layout_marginBottom="30dp"
+        android:orientation="vertical">
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:baselineAligned="false"
+            android:gravity="bottom|center"
+            android:orientation="horizontal">
+
+            <include layout="@layout/layout_widget_active_steps" />
+
+            <include layout="@layout/layout_widget_distance" />
+
+            <include layout="@layout/layout_widget_duration" />
+
+        </LinearLayout>
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="5dp"
+            android:layout_marginBottom="5dp"
+            android:baselineAligned="false"
+            android:gravity="center"
+            android:orientation="horizontal">
+
+            <include layout="@layout/layout_widget_total_steps" />
+
+            <ImageView
+                android:layout_width="60dp"
+                android:layout_height="60dp"
+                app:srcCompat="@drawable/gadgetbridge_img" />
+
+            <include layout="@layout/layout_widget_activities_count" />
+
+        </LinearLayout>
+
+    </LinearLayout>
+
+    <include
+        layout="@layout/layout_widget_duration_selector"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..451698f
--- /dev/null
+++ b/app/src/main/res/layout/activity_main.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/drawer_layout"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:fitsSystemWindows="true"
+    tools:openDrawer="start"
+    tools:context=".activities.ControlCenterv2">
+
+    <include
+        layout="@layout/activity_main_app_bar"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+
+    <com.google.android.material.navigation.NavigationView
+        android:id="@+id/nav_view"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:layout_gravity="start"
+        android:fitsSystemWindows="true"
+        app:headerLayout="@layout/main_drawer_header"
+        app:menu="@menu/activity_main_drawer" />
+
+</androidx.drawerlayout.widget.DrawerLayout>
diff --git a/app/src/main/res/layout/activity_main_app_bar.xml b/app/src/main/res/layout/activity_main_app_bar.xml
new file mode 100644
index 0000000..2cd1a15
--- /dev/null
+++ b/app/src/main/res/layout/activity_main_app_bar.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:fitsSystemWindows="true"
+    tools:context=".activities.ControlCenterv2">
+
+    <com.google.android.material.appbar.AppBarLayout
+        android:id="@+id/appbarlayout"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        app:layout_constraintTop_toTopOf="parent">
+
+        <com.google.android.material.appbar.MaterialToolbar
+            android:id="@+id/toolbar"
+            android:layout_width="match_parent"
+            android:layout_height="?attr/actionBarSize"
+            android:background="?attr/colorPrimaryDark" />
+
+        <com.google.android.material.tabs.TabLayout
+            android:id="@+id/band_tabs"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content" />
+
+    </com.google.android.material.appbar.AppBarLayout>
+
+    <androidx.swiperefreshlayout.widget.SwipeRefreshLayout
+        android:id="@+id/dashboard_swipe_layout"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        app:layout_constraintTop_toBottomOf="@id/appbarlayout"
+        app:layout_constraintBottom_toTopOf="@id/bottom_nav_bar">
+
+        <androidx.viewpager2.widget.ViewPager2
+            android:id="@+id/dashboard_viewpager"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent" />
+    </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
+
+    <FrameLayout
+        android:id="@+id/workbench_container"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:visibility="gone"
+        app:layout_constraintTop_toBottomOf="@id/appbarlayout"
+        app:layout_constraintBottom_toTopOf="@id/bottom_nav_bar" />
+
+    <com.google.android.material.bottomnavigation.BottomNavigationView
+        android:id="@+id/bottom_nav_bar"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="0dp"
+        android:layout_marginEnd="0dp"
+        app:labelVisibilityMode="labeled"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:menu="@menu/bottom_nav_menu" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/app/src/main/res/layout/activity_mi_band_pairing.xml b/app/src/main/res/layout/activity_mi_band_pairing.xml
new file mode 100644
index 0000000..0327351
--- /dev/null
+++ b/app/src/main/res/layout/activity_mi_band_pairing.xml
@@ -0,0 +1,33 @@
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:paddingLeft="@dimen/activity_horizontal_margin"
+    android:paddingRight="@dimen/activity_horizontal_margin"
+    android:paddingTop="@dimen/activity_vertical_margin"
+    android:paddingBottom="@dimen/activity_vertical_margin"
+    tools:context="nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandPairingActivity">
+
+    <TextView
+        android:text="@string/pairing"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:id="@+id/miband_pair_message" />
+
+    <ProgressBar
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:id="@+id/progressBar"
+        android:layout_marginTop="25dp"
+        android:layout_below="@+id/miband_pair_message"
+        android:layout_centerHorizontal="true" />
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/miband_pairing_tap_hint"
+        android:id="@+id/miband_pair_hint"
+        android:layout_centerVertical="true"
+        android:layout_alignParentStart="true" />
+
+</RelativeLayout>
diff --git a/app/src/main/res/layout/activity_musicmanager.xml b/app/src/main/res/layout/activity_musicmanager.xml
new file mode 100644
index 0000000..3749a27
--- /dev/null
+++ b/app/src/main/res/layout/activity_musicmanager.xml
@@ -0,0 +1,151 @@
+<?xml version="1.0" encoding="utf-8"?>
+<android.widget.RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context=".activities.musicmanager.MusicManagerActivity">
+
+    <TextView
+        android:id="@+id/music_device_info"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_margin="10dp"
+        android:lineSpacingMultiplier="1.1"
+        android:text="" />
+
+    <LinearLayout
+        android:id="@+id/music_playlists_layout"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_below="@id/music_device_info"
+        android:orientation="horizontal">
+
+        <Spinner
+            android:id="@+id/music_playlists"
+            android:layout_width="wrap_content"
+            android:layout_height="48dp"
+            android:layout_weight="20" />
+
+        <ImageButton
+            android:id="@+id/music_playlist_rename"
+            android:layout_width="48dp"
+            android:layout_height="48dp"
+            android:layout_weight="1"
+            android:background="@null"
+            android:contentDescription="@string/music_rename_playlist"
+            app:srcCompat="@drawable/ic_edit" />
+
+        <ImageButton
+            android:id="@+id/music_playlist_delete"
+            android:layout_width="48dp"
+            android:layout_height="48dp"
+            android:layout_weight="1"
+            android:background="@null"
+            android:contentDescription="@string/music_delete"
+            app:srcCompat="@drawable/ic_delete" />
+    </LinearLayout>
+
+
+    <androidx.recyclerview.widget.RecyclerView
+        android:id="@+id/music_songs_list"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_below="@id/music_playlists_layout"
+        android:layout_centerHorizontal="true"
+        android:divider="@null" />
+
+
+    <com.google.android.material.floatingactionbutton.FloatingActionButton
+        android:id="@+id/fab_music_upload"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentEnd="true"
+        android:layout_alignParentBottom="true"
+        android:layout_gravity="bottom|end"
+        android:layout_margin="16dp"
+        app:srcCompat="@android:drawable/stat_sys_upload" />
+
+    <com.google.android.material.floatingactionbutton.FloatingActionButton
+        android:id="@+id/fab_music_playlist_add"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentEnd="true"
+        android:layout_alignParentBottom="true"
+        android:layout_gravity="bottom|end"
+        android:layout_marginEnd="16dp"
+        android:layout_marginBottom="90dp"
+        android:contentDescription="@string/music_new_playlist"
+        app:srcCompat="@drawable/ic_add" />
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+
+        <com.google.android.material.appbar.MaterialToolbar
+            android:id="@+id/bottom_toolbar"
+            android:layout_width="match_parent"
+            android:layout_height="54dp"
+            android:layout_gravity="bottom"
+            android:background="@color/primary_dark">
+
+            <ImageButton
+                android:id="@+id/music_cancel_selection"
+                android:layout_width="48dp"
+                android:layout_height="48dp"
+                android:layout_gravity="start"
+                android:layout_weight="1"
+                android:background="@null"
+                android:contentDescription="@string/music_rename_playlist"
+                app:srcCompat="@drawable/ic_action_notify_cancel" />
+
+            <TextView
+                android:id="@+id/music_selection_info"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_margin="10dp"
+                android:layout_weight="1"
+                android:text="" />
+
+            <LinearLayout
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="end">
+
+                <ImageButton
+                    android:id="@+id/music_multiselect_add_to_playlist"
+                    android:layout_width="48dp"
+                    android:layout_height="48dp"
+                    android:layout_toStartOf="@id/music_multiselect_delete"
+                    android:layout_weight="1"
+                    android:background="@null"
+                    android:contentDescription="@string/music_add_to_playlist"
+                    app:srcCompat="@drawable/ic_checklist" />
+
+                <ImageButton
+                    android:id="@+id/music_multiselect_delete"
+                    android:layout_width="48dp"
+                    android:layout_height="48dp"
+                    android:layout_weight="1"
+                    android:background="@null"
+                    android:contentDescription="@string/music_delete"
+                    app:srcCompat="@drawable/ic_delete" />
+            </LinearLayout>
+        </com.google.android.material.appbar.MaterialToolbar>
+
+    </LinearLayout>
+
+    <RelativeLayout
+        android:id="@+id/music_loading"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:background="#95000000"
+        android:gravity="center">
+
+        <ProgressBar
+            style="?android:attr/progressBarStyleLarge"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content" />
+    </RelativeLayout>
+
+</android.widget.RelativeLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_notification_filter.xml b/app/src/main/res/layout/activity_notification_filter.xml
new file mode 100644
index 0000000..1fde275
--- /dev/null
+++ b/app/src/main/res/layout/activity_notification_filter.xml
@@ -0,0 +1,86 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context=".activities.NotificationFilterActivity"
+    tools:layout_editor_absoluteY="81dp">
+
+    <TextView
+        android:id="@+id/textView2"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="16dp"
+        android:layout_marginTop="16dp"
+        android:text="@string/filter_mode"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <Spinner
+        android:id="@+id/spinnerFilterMode"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginEnd="16dp"
+        android:layout_marginStart="16dp"
+        android:layout_marginTop="16dp"
+        android:entries="@array/notification_filter_modes_entries"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/textView2" />
+
+    <TextView
+        android:id="@+id/textView3"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="16dp"
+        android:layout_marginTop="16dp"
+        android:text="@string/mode_configuration"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/spinnerFilterMode" />
+
+    <Spinner
+        android:id="@+id/spinnerSubMode"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginEnd="16dp"
+        android:layout_marginStart="16dp"
+        android:layout_marginTop="16dp"
+        android:entries="@array/notification_filter_submodes_entries"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/textView3" />
+
+    <EditText
+        android:id="@+id/editTextWords"
+        style="@style/Widget.AppCompat.EditText"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_marginBottom="16dp"
+        android:layout_marginEnd="16dp"
+        android:layout_marginStart="16dp"
+        android:layout_marginTop="16dp"
+        android:ems="10"
+        android:gravity="start"
+        android:hint="@string/edittext_notification_filter_words_hint"
+        android:inputType="textMultiLine"
+        android:maxLines="25"
+        android:minLines="10"
+        android:scrollbars="vertical"
+        app:layout_constraintBottom_toTopOf="@+id/buttonSaveFilter"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/spinnerSubMode" />
+
+    <Button
+        android:id="@+id/buttonSaveFilter"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginBottom="16dp"
+        android:layout_marginEnd="16dp"
+        android:layout_marginStart="16dp"
+        android:text="@string/save_configuration"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent" />
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
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 0000000..875644d
--- /dev/null
+++ b/app/src/main/res/layout/activity_notification_per_app_setting_detail.xml
@@ -0,0 +1,86 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context=".activities.NotificationFilterActivity"
+    tools:layout_editor_absoluteY="81dp">
+
+    <TextView
+        android:id="@+id/textView2"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="16dp"
+        android:layout_marginTop="16dp"
+        android:text="@string/prefs_wena3_notification_default_led"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <Spinner
+        android:id="@+id/spinnerLedType"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginEnd="16dp"
+        android:layout_marginStart="16dp"
+        android:layout_marginTop="16dp"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/textView2" />
+
+    <TextView
+        android:id="@+id/textView3"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="16dp"
+        android:layout_marginTop="16dp"
+        android:text="@string/prefs_wena3_notification_default_vibration"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/spinnerLedType" />
+
+    <Spinner
+        android:id="@+id/spinnerVibraType"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginEnd="16dp"
+        android:layout_marginStart="16dp"
+        android:layout_marginTop="16dp"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/textView3" />
+
+    <Spinner
+        android:id="@+id/spinnerVibraCount"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginEnd="16dp"
+        android:layout_marginStart="16dp"
+        android:layout_marginTop="16dp"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/spinnerVibraType" />
+
+    <Button
+        android:id="@+id/buttonSaveSettings"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginBottom="16dp"
+        android:layout_marginEnd="16dp"
+        android:layout_marginStart="16dp"
+        android:text="@string/save_configuration"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintBottom_toTopOf="@+id/buttonDeleteSettings"/>
+    <Button
+        android:id="@+id/buttonDeleteSettings"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginBottom="16dp"
+        android:layout_marginEnd="16dp"
+        android:layout_marginStart="16dp"
+        android:text="@string/appmananger_app_delete"
+        android:backgroundTint="@color/secondarytext"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent" />
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_open_fw_app_installer.xml b/app/src/main/res/layout/activity_open_fw_app_installer.xml
new file mode 100644
index 0000000..b9468ec
--- /dev/null
+++ b/app/src/main/res/layout/activity_open_fw_app_installer.xml
@@ -0,0 +1,88 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <ScrollView
+        android:id="@+id/about_background"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:paddingLeft="@dimen/about_margin"
+        android:paddingRight="@dimen/about_margin"
+        tools:context="nodomain.freeyourgadget.gadgetbridge.activities.OpenFwAppInstallerActivity">
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="vertical">
+
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginBottom="10dp"
+                android:text="@string/open_fw_installer_info_text_title"
+                android:textAppearance="@style/TextAppearance.AppCompat.Large"
+                android:textColor="@color/accent" />
+
+            <TextView
+                android:id="@+id/open_fw_desc_with_link"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginBottom="20dp"
+                android:autoLink="web"
+                android:text="@string/open_fw_installer_info_text" />
+
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginBottom="10dp"
+                android:text="@string/open_fw_installer_warning_title"
+                android:textAppearance="@style/TextAppearance.AppCompat.Medium"
+                android:textColor="@color/accent" />
+
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginBottom="20dp"
+                android:text="@string/open_fw_installer_warning_text" />
+
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginBottom="10dp"
+                android:text="@string/open_fw_installer_getting_files_title"
+                android:textAppearance="@style/TextAppearance.AppCompat.Medium"
+                android:textColor="@color/accent" />
+
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginBottom="20dp"
+                android:text="@string/open_fw_installer_getting_files_text" />
+
+            <TextView
+                android:id="@+id/open_fw_installer_no_device"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="" />
+
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:orientation="horizontal"
+                android:paddingLeft="@dimen/about_margin"
+                android:paddingRight="@dimen/about_margin">
+
+                <Button
+                    android:id="@+id/open_fw_installer_pick_button"
+                    android:layout_width="0dp"
+                    android:layout_height="wrap_content"
+                    android:layout_weight="1"
+                    android:text="@string/open_fw_installer_pick_file" />
+
+            </LinearLayout>
+        </LinearLayout>
+    </ScrollView>
+</RelativeLayout>
diff --git a/app/src/main/res/layout/activity_pebble_pairing.xml b/app/src/main/res/layout/activity_pebble_pairing.xml
new file mode 100644
index 0000000..661f538
--- /dev/null
+++ b/app/src/main/res/layout/activity_pebble_pairing.xml
@@ -0,0 +1,33 @@
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:paddingLeft="@dimen/activity_horizontal_margin"
+    android:paddingRight="@dimen/activity_horizontal_margin"
+    android:paddingTop="@dimen/activity_vertical_margin"
+    android:paddingBottom="@dimen/activity_vertical_margin"
+    tools:context="nodomain.freeyourgadget.gadgetbridge.devices.pebble.PebblePairingActivity">
+
+    <TextView
+        android:text="@string/pairing"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:id="@+id/pebble_pair_message" />
+
+    <ProgressBar
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:id="@+id/progressBar"
+        android:layout_marginTop="25dp"
+        android:layout_below="@+id/pebble_pair_message"
+        android:layout_centerHorizontal="true" />
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/pebble_pairing_hint"
+        android:id="@+id/pebble_pair_hint"
+        android:layout_centerVertical="true"
+        android:layout_alignParentStart="true" />
+
+</RelativeLayout>
diff --git a/app/src/main/res/layout/activity_permissions.xml b/app/src/main/res/layout/activity_permissions.xml
new file mode 100644
index 0000000..4aead3b
--- /dev/null
+++ b/app/src/main/res/layout/activity_permissions.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <FrameLayout
+        android:id="@+id/fragment_container"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+</RelativeLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_qhybrid_app_choser.xml b/app/src/main/res/layout/activity_qhybrid_app_choser.xml
new file mode 100644
index 0000000..d9c02ff
--- /dev/null
+++ b/app/src/main/res/layout/activity_qhybrid_app_choser.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context=".devices.qhybrid.QHybridAppChoserActivity">
+
+    <RelativeLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+
+    <ProgressBar
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_centerHorizontal="true"
+        android:paddingTop="40dp"
+        android:id="@+id/qhybrid_packageChooserLoading"/>
+
+    <ListView
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:id="@+id/qhybrid_appChooserList"/>
+
+    </RelativeLayout>
+
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_qhybrid_apps_management.xml b/app/src/main/res/layout/activity_qhybrid_apps_management.xml
new file mode 100644
index 0000000..058e135
--- /dev/null
+++ b/app/src/main/res/layout/activity_qhybrid_apps_management.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout
+    xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+
+        <ListView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:id="@+id/qhybrid_apps_list" />
+
+    </LinearLayout>
+
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_qhybrid_calibration.xml b/app/src/main/res/layout/activity_qhybrid_calibration.xml
new file mode 100644
index 0000000..93fa46d
--- /dev/null
+++ b/app/src/main/res/layout/activity_qhybrid_calibration.xml
@@ -0,0 +1,96 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:orientation="vertical">
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:padding="10dp"
+            android:text="@string/qhybrid_calibration_align_hint" />
+
+        <Spinner
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:id="@+id/qhybrid_calibration_hand_spinner" />
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal"
+            android:weightSum="2">
+
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:orientation="vertical"
+                android:gravity="center_horizontal">
+
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/qhybrid_calibration_counterclockwise"/>
+
+                <Button
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:id="@+id/qhybrid_calibration_counter_clockwise_1"
+                    android:text="@string/qhybrid_calibration_1_step" />
+
+                <Button
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:id="@+id/qhybrid_calibration_counter_clockwise_10"
+                    android:text="@string/qhybrid_calibration_10_steps" />
+
+                <Button
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:id="@+id/qhybrid_calibration_counter_clockwise_100"
+                    android:text="@string/qhybrid_calibration_100_steps" />
+
+            </LinearLayout>
+
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:orientation="vertical"
+                android:gravity="center_horizontal">
+
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/qhybrid_calibration_clockwise"/>
+
+                <Button
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:id="@+id/qhybrid_calibration_clockwise_1"
+                    android:text="@string/qhybrid_calibration_1_step" />
+
+                <Button
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:id="@+id/qhybrid_calibration_clockwise_10"
+                    android:text="@string/qhybrid_calibration_10_steps" />
+
+                <Button
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:id="@+id/qhybrid_calibration_clockwise_100"
+                    android:text="@string/qhybrid_calibration_100_steps" />
+
+            </LinearLayout>
+
+        </LinearLayout>
+
+    </LinearLayout>
+
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_qhybrid_file_management.xml b/app/src/main/res/layout/activity_qhybrid_file_management.xml
new file mode 100644
index 0000000..cf12fa3
--- /dev/null
+++ b/app/src/main/res/layout/activity_qhybrid_file_management.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout
+    xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <RelativeLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" >
+
+        <Spinner
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:id="@+id/qhybrid_file_types"
+            android:alpha="0.2"
+            android:clickable="false"/>
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_alignParentBottom="true"
+            android:orientation="vertical">
+
+            <com.google.android.material.materialswitch.MaterialSwitch
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="encrypted file"
+                android:id="@+id/qhybrid_switch_encrypted_file" />
+
+            <com.google.android.material.materialswitch.MaterialSwitch
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="generate file header"
+                android:id="@+id/sqhybrid_switch_generate_file_header" />
+
+            <Button
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="download file"
+                android:id="@+id/qhybrid_button_download_file" />
+
+            <Button
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="upload file"
+                android:id="@+id/qhybrid_button_upload_file" />
+
+        </LinearLayout>
+
+    </RelativeLayout>
+
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_qhybrid_hr_settings.xml b/app/src/main/res/layout/activity_qhybrid_hr_settings.xml
new file mode 100644
index 0000000..3292e68
--- /dev/null
+++ b/app/src/main/res/layout/activity_qhybrid_hr_settings.xml
@@ -0,0 +1,109 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    android:weightSum="1">
+
+    <LinearLayout
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:orientation="vertical"
+        android:visibility="gone">
+
+        <TextView
+            android:id="@+id/qhybrid_button_top_single_press"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="top button single press"
+            android:textSize="20dp" />
+
+        <TextView
+            android:id="@+id/qhybrid_button_middle_single_press"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="middle button single press"
+            android:textSize="20dp" />
+
+        <TextView
+            android:id="@+id/qhybrid_button_bottom_single_press"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="bottom button single press"
+            android:textSize="20dp" />
+
+    </LinearLayout>
+
+    <android.widget.Space
+        android:layout_width="match_parent"
+        android:layout_height="20dp" />
+
+    <TextView
+        android:id="@+id/qhybrid_button_widget_top"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="top widget"
+        android:textSize="20dp" />
+
+    <TextView
+        android:id="@+id/qhybrid_button_widget_right"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="right widget"
+        android:textSize="20dp" />
+
+    <TextView
+        android:id="@+id/qhybrid_button_widget_bottom"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="bottom widget"
+        android:textSize="20dp" />
+
+    <TextView
+        android:id="@+id/qhybrid_button_widget_left"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="left widget"
+        android:textSize="20dp" />
+
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="custom widgets" />
+
+    <ListView
+        android:id="@+id/qhybrid_widget_list"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="0.4" />
+
+    <Button
+        android:id="@+id/qhybrid_widget_add"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="add widget" />
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:weightSum="2"
+        android:orientation="horizontal">
+
+        <Button
+            android:id="@+id/qhybrid_unset_background"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="delete background"
+            android:layout_weight="1"/>
+
+        <Button
+            android:id="@+id/qhybrid_set_background"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="set background"
+            android:layout_weight="1" />
+
+    </LinearLayout>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_qhybrid_image_edit.xml b/app/src/main/res/layout/activity_qhybrid_image_edit.xml
new file mode 100644
index 0000000..709638d
--- /dev/null
+++ b/app/src/main/res/layout/activity_qhybrid_image_edit.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    android:weightSum="1">
+
+    <RelativeLayout
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:id="@+id/qhybrid_image_edit_container"
+        android:layout_above="@id/qhybrid_image_edit_okay"
+        android:layout_alignParentTop="true">
+
+        <ImageView
+            android:id="@+id/qhybrid_image_edit_image"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:background="@android:color/holo_red_dark"/>
+
+        <ImageView
+            android:id="@+id/qhybrid_image_edit_image_overlay"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"/>
+
+    </RelativeLayout>
+
+    <Button
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="ok"
+        android:id="@+id/qhybrid_image_edit_okay"
+        android:layout_alignParentBottom="true"/>
+
+</RelativeLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_qhybrid_settings.xml b/app/src/main/res/layout/activity_qhybrid_settings.xml
new file mode 100644
index 0000000..5ad9385
--- /dev/null
+++ b/app/src/main/res/layout/activity_qhybrid_settings.xml
@@ -0,0 +1,132 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/qhybridMain"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:layout_margin="5dp">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_alignParentTop="true"
+        android:orientation="vertical"
+        android:id="@+id/settingsLayout"
+        android:focusableInTouchMode="true" >
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:id="@+id/vibrationStrengthLayout"
+            android:orientation="vertical">
+
+            <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/qhybrid_vibration_strength" />
+
+
+            <SeekBar
+                android:id="@+id/vibrationStrengthBar"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:max="2"
+                android:min="0" />
+
+        </LinearLayout>
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/qhybrid_goal_in_steps" />
+
+        <EditText
+            android:id="@+id/stepGoalEt"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:inputType="numberDecimal" />
+
+        <RelativeLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content">
+
+            <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_alignParentStart="true"
+                android:text="@string/qhybrid_time_shift" />
+
+            <TextView
+                android:id="@+id/qhybridTimeOffset"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_alignParentEnd="true" />
+
+        </RelativeLayout>
+
+        <RelativeLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content">
+
+            <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_alignParentStart="true"
+                android:text="@string/qhybrid_second_timezone_offset_relative_to_utc" />
+
+            <TextView
+                android:id="@+id/timezoneOffset"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_alignParentEnd="true" />
+
+        </RelativeLayout>
+
+        <Button
+            android:id="@+id/buttonOverwriteButtons"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="@string/qhybrid_overwrite_buttons" />
+
+
+        <CheckBox
+            android:id="@+id/checkBoxUserActivityHand"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="@string/qhybrid_use_activity_hand_as_notification_counter" />
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:id="@+id/buttonConfigLayout"
+            android:orientation="vertical"/>
+
+    </LinearLayout>
+
+    <!-- <ProgressBar
+        android:id="@+id/vibrationSettingProgressBar"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignBottom="@id/settingsLayout"
+        android:layout_alignTop="@id/settingsLayout"
+        android:layout_centerHorizontal="true"
+        android:gravity="center" /> -->
+
+    <TextView
+        android:id="@+id/settingsErrorText"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignTop="@id/settingsLayout"
+        android:layout_alignBottom="@id/settingsLayout"
+        android:layout_centerHorizontal="true"
+        android:visibility="gone"
+        android:gravity="center" />
+    <ListView
+        android:id="@+id/qhybrid_appList"
+        android:layout_marginTop="50dp"
+        android:layout_width="wrap_content"
+        android:layout_height="0dp"
+        android:layout_below="@id/settingsLayout"
+        android:layout_alignParentBottom="true"/>
+
+</RelativeLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_reminder_details.xml b/app/src/main/res/layout/activity_reminder_details.xml
new file mode 100644
index 0000000..e53bfb2
--- /dev/null
+++ b/app/src/main/res/layout/activity_reminder_details.xml
@@ -0,0 +1,176 @@
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:card_view="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/constraintLayout"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    tools:context="nodomain.freeyourgadget.gadgetbridge.activities.ReminderDetails">
+
+    <com.google.android.material.card.MaterialCardView
+        android:id="@+id/card_repeat"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_margin="8dp"
+        android:foreground="?android:attr/selectableItemBackground"
+        app:cardBackgroundColor="?attr/cardview_background_color"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        card_view:cardElevation="3dp"
+        card_view:contentPadding="4dp">
+
+        <androidx.constraintlayout.widget.ConstraintLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_margin="8dp">
+
+            <TextView
+                android:id="@+id/label_repeat"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/reminder_repeat"
+                android:textAppearance="?android:attr/textAppearance"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toTopOf="parent" />
+
+            <TextView
+                android:id="@+id/reminder_repeat"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="2dp"
+                android:text="Once"
+                android:textAppearance="?android:attr/textAppearanceLarge"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toBottomOf="@id/label_repeat" />
+        </androidx.constraintlayout.widget.ConstraintLayout>
+    </com.google.android.material.card.MaterialCardView>
+
+    <com.google.android.material.card.MaterialCardView
+        android:id="@+id/card_date"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_margin="8dp"
+        android:foreground="?android:attr/selectableItemBackground"
+        app:cardBackgroundColor="?attr/cardview_background_color"
+        app:layout_constraintEnd_toStartOf="@id/card_time"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/card_repeat"
+        card_view:cardElevation="3dp"
+        card_view:contentPadding="4dp">
+
+        <androidx.constraintlayout.widget.ConstraintLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_margin="8dp">
+
+            <TextView
+                android:id="@+id/label_date"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/reminder_date"
+                android:textAppearance="?android:attr/textAppearance"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toTopOf="parent" />
+
+            <TextView
+                android:id="@+id/reminder_date"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="2dp"
+                android:text="2019-08-17"
+                android:textAppearance="?android:attr/textAppearanceLarge"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toBottomOf="@id/label_date" />
+        </androidx.constraintlayout.widget.ConstraintLayout>
+    </com.google.android.material.card.MaterialCardView>
+
+    <com.google.android.material.card.MaterialCardView
+        android:id="@+id/card_time"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_margin="8dp"
+        android:foreground="?android:attr/selectableItemBackground"
+        android:visibility="visible"
+        app:cardBackgroundColor="?attr/cardview_background_color"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toEndOf="@id/card_date"
+        app:layout_constraintTop_toBottomOf="@id/card_repeat"
+        card_view:cardElevation="3dp"
+        card_view:contentPadding="4dp">
+
+        <androidx.constraintlayout.widget.ConstraintLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_margin="8dp">
+
+            <TextView
+                android:id="@+id/label_time"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/reminder_time"
+                android:textAppearance="?android:attr/textAppearance"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toTopOf="parent" />
+
+            <TextView
+                android:id="@+id/reminder_time"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="2dp"
+                android:text="16:35"
+                android:textAppearance="?android:attr/textAppearanceLarge"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toBottomOf="@id/label_time" />
+        </androidx.constraintlayout.widget.ConstraintLayout>
+    </com.google.android.material.card.MaterialCardView>
+
+    <com.google.android.material.card.MaterialCardView
+        android:id="@+id/card_view4"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_margin="8dp"
+        android:foreground="?android:attr/selectableItemBackground"
+        app:cardBackgroundColor="?attr/cardview_background_color"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/card_date"
+        card_view:cardElevation="3dp"
+        card_view:contentPadding="4dp">
+
+        <androidx.constraintlayout.widget.ConstraintLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_margin="8dp">
+
+            <TextView
+                android:id="@+id/label_message"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/reminder_message"
+                android:textAppearance="?android:attr/textAppearance"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toTopOf="parent" />
+
+            <EditText
+                android:id="@+id/reminder_message"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="2dp"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toBottomOf="@id/label_message" />
+        </androidx.constraintlayout.widget.ConstraintLayout>
+    </com.google.android.material.card.MaterialCardView>
+
+    <com.google.android.material.floatingactionbutton.FloatingActionButton
+        android:id="@+id/fab_save"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="bottom|end"
+        android:layout_margin="16dp"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:srcCompat="@drawable/ic_save" />
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/app/src/main/res/layout/activity_solar_equipment_status.xml b/app/src/main/res/layout/activity_solar_equipment_status.xml
new file mode 100644
index 0000000..a5f0719
--- /dev/null
+++ b/app/src/main/res/layout/activity_solar_equipment_status.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.swiperefreshlayout.widget.SwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/solarequipmentview_swipe_layout"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    tools:context=".devices.marstek.SolarEquipmentStatusActivity">
+
+    <androidx.core.widget.NestedScrollView
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+
+        <androidx.gridlayout.widget.GridLayout
+            android:id="@+id/solarequipmentview_gridlayout"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            app:columnCount="2" />
+    </androidx.core.widget.NestedScrollView>
+</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
diff --git a/app/src/main/res/layout/activity_summary_dashboard_item.xml b/app/src/main/res/layout/activity_summary_dashboard_item.xml
new file mode 100644
index 0000000..fccb048
--- /dev/null
+++ b/app/src/main/res/layout/activity_summary_dashboard_item.xml
@@ -0,0 +1,476 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/list_item_parent_layout"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content">
+    <!--
+    android:background="?android:attr/activatedBackgroundIndicator"
+    -->
+
+    <LinearLayout
+        android:id="@+id/summary_dashboard"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="10dp"
+        android:layout_marginBottom="10dp"
+        android:orientation="vertical">
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="5dp"
+            android:layout_marginBottom="0dp"
+            android:baselineAligned="false"
+            android:gravity="center"
+            android:orientation="horizontal">
+
+            <LinearLayout
+                android:id="@+id/summary_dashboard_layout_big_activity_layout"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center|top"
+                android:layout_marginStart="1dp"
+                android:layout_marginEnd="1dp"
+                android:layout_weight="1"
+                android:gravity="center"
+                android:orientation="vertical">
+
+                <ImageView
+                    android:id="@+id/summary_dashboard_layout_big_activity_icon"
+                    android:layout_width="100dp"
+                    android:layout_height="100dp"
+                    android:layout_gravity="center"
+                    android:contentDescription="@string/candidate_item_device_image"
+                    app:srcCompat="@drawable/ic_activity_unknown_small" />
+
+
+            </LinearLayout>
+
+
+        </LinearLayout>
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="0dp"
+            android:layout_marginBottom="5dp"
+            android:baselineAligned="false"
+            android:gravity="center"
+            android:orientation="horizontal">
+
+            <LinearLayout
+                android:id="@+id/summary_dashboard_layout_distance"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center|top"
+                android:layout_marginStart="1dp"
+                android:layout_marginEnd="1dp"
+                android:layout_weight="1"
+                android:gravity="center"
+                android:orientation="vertical">
+
+                <ImageView
+                    android:id="@+id/summary_dashboard_layout_distance_icon"
+                    android:layout_width="40dp"
+                    android:layout_height="40dp"
+                    android:layout_gravity="center"
+                    android:contentDescription="@string/candidate_item_device_image"
+                    app:srcCompat="@drawable/ic_distance" />
+
+                <TextView
+                    android:id="@+id/summary_dashboard_layout_distance_title"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_gravity="center"
+                    android:gravity="center"
+                    android:maxLines="2"
+                    android:scrollHorizontally="false"
+                    android:text="@string/distance"
+                    android:textAllCaps="true"
+                    android:textColor="@color/accent"
+                    android:textStyle="bold" />
+
+                <TextView
+                    android:id="@+id/summary_dashboard_layout_distance_label"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_gravity="center"
+                    android:fontFamily="sans-serif-black"
+                    android:gravity="center"
+                    android:maxLines="1"
+                    android:scrollHorizontally="false"
+                    android:text="15000"
+                    android:textSize="18sp" />
+
+            </LinearLayout>
+
+
+            <LinearLayout
+                android:id="@+id/summary_dashboard_layout_calories"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center|top"
+                android:layout_marginStart="1dp"
+                android:layout_marginEnd="1dp"
+                android:layout_weight="1"
+                android:gravity="bottom|center"
+                android:orientation="vertical">
+
+                <ImageView
+                    android:id="@+id/summary_dashboard_layout_calories_icon"
+                    android:layout_width="40dp"
+                    android:layout_height="40dp"
+                    android:layout_gravity="center"
+                    android:contentDescription="@string/candidate_item_device_image"
+                    app:srcCompat="@drawable/ic_calories" />
+
+                <TextView
+                    android:id="@+id/summary_dashboard_layout_calories_title"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_gravity="center"
+                    android:gravity="center"
+                    android:maxLines="2"
+                    android:scrollHorizontally="false"
+                    android:text="@string/caloriesBurnt"
+                    android:textAllCaps="true"
+                    android:textColor="@color/accent"
+                    android:textStyle="bold" />
+
+                <TextView
+                    android:id="@+id/summary_dashboard_layout_calories_label"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_gravity="center"
+                    android:fontFamily="sans-serif-black"
+                    android:gravity="center"
+                    android:maxLines="1"
+                    android:scrollHorizontally="false"
+                    android:text="30000"
+                    android:textSize="18sp" />
+
+            </LinearLayout>
+
+
+        </LinearLayout>
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="5dp"
+            android:layout_marginBottom="5dp"
+            android:baselineAligned="false"
+            android:gravity="bottom|center"
+            android:orientation="horizontal">
+
+
+
+            <LinearLayout
+                android:id="@+id/summary_dashboard_layout_duration"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center|top"
+                android:layout_marginStart="1dp"
+                android:layout_marginEnd="1dp"
+                android:layout_weight="1"
+                android:gravity="center|top"
+                android:orientation="vertical">
+
+                <ImageView
+                    android:id="@+id/summary_dashboard_layout_duration_icon"
+                    android:layout_width="19dp"
+                    android:layout_height="19dp"
+                    android:layout_gravity="center"
+                    android:contentDescription="@string/candidate_item_device_image"
+                    app:srcCompat="@drawable/ic_timer" />
+
+                <TextView
+                    android:id="@+id/summary_dashboard_layout_duration_title"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_gravity="center"
+                    android:gravity="center"
+                    android:maxLines="2"
+                    android:scrollHorizontally="false"
+                    android:text="@string/activity_detail_duration_label"
+                    android:textAllCaps="true"
+                    android:textColor="@color/accent"
+                    android:textStyle="bold" />
+
+                <TextView
+                    android:id="@+id/summary_dashboard_layout_duration_label"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_gravity="center"
+                    android:fontFamily="sans-serif-black"
+                    android:gravity="center"
+                    android:maxLines="1"
+                    android:scrollHorizontally="false"
+                    android:text="1d 20h 35m"
+                    android:textSize="18sp" />
+
+            </LinearLayout>
+
+            <LinearLayout
+                android:id="@+id/summary_dashboard_layout_active_duration"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center|top"
+                android:layout_marginStart="1dp"
+                android:layout_marginEnd="1dp"
+                android:layout_weight="1"
+                android:gravity="center|top"
+                android:orientation="vertical">
+
+                <ImageView
+                    android:id="@+id/summary_dashboard_layout_active_duration_icon"
+                    android:layout_width="19dp"
+                    android:layout_height="19dp"
+                    android:layout_gravity="center"
+                    android:contentDescription="@string/candidate_item_device_image"
+                    app:srcCompat="@drawable/ic_timer" />
+
+                <TextView
+                    android:id="@+id/summary_dashboard_layout_active_duration_title"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_gravity="center"
+                    android:gravity="center"
+                    android:maxLines="2"
+                    android:scrollHorizontally="false"
+                    android:text="@string/activity_list_summary_active_time"
+                    android:textAllCaps="true"
+                    android:textColor="@color/accent"
+                    android:textStyle="bold" />
+
+                <TextView
+                    android:id="@+id/summary_dashboard_layout_active_duration_label"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_gravity="center"
+                    android:fontFamily="sans-serif-black"
+                    android:gravity="center"
+                    android:maxLines="1"
+                    android:scrollHorizontally="false"
+                    android:text="1d 13h 30m"
+                    android:textSize="18sp" />
+
+            </LinearLayout>
+
+
+
+
+        </LinearLayout>
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="5dp"
+            android:layout_marginBottom="5dp"
+            android:baselineAligned="false"
+            android:gravity="center"
+            android:orientation="horizontal">
+
+            <LinearLayout
+                android:id="@+id/summary_dashboard_layout_activity"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center|top"
+                android:layout_marginStart="1dp"
+                android:layout_marginEnd="1dp"
+                android:layout_weight="1"
+                android:gravity="center"
+                android:orientation="vertical">
+
+                <ImageView
+                    android:id="@+id/summary_dashboard_layout_activity_icon"
+                    android:layout_width="19dp"
+                    android:layout_height="19dp"
+                    android:layout_gravity="center"
+                    android:contentDescription="@string/candidate_item_device_image"
+                    app:srcCompat="@drawable/ic_activity_unknown_small" />
+
+                <TextView
+                    android:id="@+id/summary_dashboard_layout_activity_title"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_gravity="center"
+                    android:gravity="center"
+                    android:maxLines="2"
+                    android:scrollHorizontally="false"
+                    android:text="@string/Activity"
+                    android:textAllCaps="true"
+                    android:textColor="@color/accent"
+                    android:textStyle="bold" />
+
+                <TextView
+                    android:id="@+id/summary_dashboard_layout_activity_label"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_gravity="center"
+                    android:fontFamily="sans-serif-black"
+                    android:gravity="center"
+                    android:maxLines="1"
+                    android:scrollHorizontally="false"
+                    android:text="All Activities"
+                    android:textSize="18sp" />
+
+            </LinearLayout>
+
+
+            <LinearLayout
+                android:id="@+id/summary_dashboard_layout_count"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center|top"
+                android:layout_marginStart="1dp"
+                android:layout_marginEnd="1dp"
+                android:layout_weight="1"
+                android:gravity="center"
+                android:orientation="vertical">
+
+                <ImageView
+                    android:id="@+id/summary_dashboard_layout_count_icon"
+                    android:layout_width="19dp"
+                    android:layout_height="19dp"
+                    android:layout_gravity="center"
+                    android:contentDescription="@string/candidate_item_device_image"
+                    app:srcCompat="@drawable/ic_activity_stacked" />
+
+                <TextView
+                    android:id="@+id/summary_dashboard_layout_count_title"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_gravity="center"
+                    android:gravity="center"
+                    android:maxLines="2"
+                    android:scrollHorizontally="false"
+                    android:text="@string/activity_list_summary_activities"
+                    android:textAllCaps="true"
+                    android:textColor="@color/accent"
+                    android:textStyle="bold" />
+
+                <TextView
+                    android:id="@+id/summary_dashboard_layout_count_label"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_gravity="center"
+                    android:fontFamily="sans-serif-black"
+                    android:gravity="center"
+                    android:maxLines="1"
+                    android:scrollHorizontally="false"
+                    android:text="15"
+                    android:textSize="18sp" />
+
+            </LinearLayout>
+
+
+        </LinearLayout>
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="5dp"
+            android:layout_marginBottom="5dp"
+            android:baselineAligned="false"
+            android:gravity="bottom|center"
+            android:orientation="horizontal">
+
+            <LinearLayout
+                android:id="@+id/summary_dashboard_layout_from"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center|top"
+                android:layout_marginStart="1dp"
+                android:layout_marginEnd="1dp"
+                android:layout_weight="1"
+                android:gravity="center|top"
+                android:orientation="vertical">
+
+                <ImageView
+                    android:id="@+id/summary_dashboard_layout_from_icon"
+                    android:layout_width="19dp"
+                    android:layout_height="19dp"
+                    android:layout_gravity="center"
+                    android:contentDescription="@string/candidate_item_device_image"
+                    app:srcCompat="@drawable/ic_date_range" />
+
+                <TextView
+                    android:id="@+id/summary_dashboard_layout_from_title"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_gravity="center"
+                    android:gravity="center"
+                    android:maxLines="2"
+                    android:scrollHorizontally="false"
+                    android:text="@string/activity_filter_date_from"
+                    android:textAllCaps="true"
+                    android:textColor="@color/accent"
+                    android:textStyle="bold" />
+
+                <TextView
+                    android:id="@+id/summary_dashboard_layout_from_label"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_gravity="center"
+                    android:fontFamily="sans-serif-black"
+                    android:gravity="center"
+                    android:maxLines="1"
+                    android:scrollHorizontally="false"
+                    android:text="January 1, 1970"
+                    android:textSize="18sp" />
+
+            </LinearLayout>
+
+            <LinearLayout
+                android:id="@+id/summary_dashboard_layout_to"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center|top"
+                android:layout_marginStart="1dp"
+                android:layout_marginEnd="1dp"
+                android:layout_weight="1"
+                android:gravity="center|top"
+                android:orientation="vertical">
+
+                <ImageView
+                    android:id="@+id/summary_dashboard_layout_to_icon"
+                    android:layout_width="19dp"
+                    android:layout_height="19dp"
+                    android:layout_gravity="center"
+                    android:contentDescription="@string/candidate_item_device_image"
+                    app:srcCompat="@drawable/ic_date_range" />
+
+                <TextView
+                    android:id="@+id/summary_dashboard_layout_to_title"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_gravity="center"
+                    android:gravity="center"
+                    android:maxLines="2"
+                    android:scrollHorizontally="false"
+                    android:text="@string/activity_filter_date_to"
+                    android:textAllCaps="true"
+                    android:textColor="@color/accent"
+                    android:textStyle="bold" />
+
+                <TextView
+                    android:id="@+id/summary_dashboard_layout_to_label"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_gravity="center"
+                    android:fontFamily="sans-serif-black"
+                    android:gravity="center"
+                    android:maxLines="1"
+                    android:scrollHorizontally="false"
+                    android:text="December 1, 2020"
+                    android:textSize="18sp" />
+
+            </LinearLayout>
+
+
+        </LinearLayout>
+    </LinearLayout>
+    <View
+        android:layout_below="@id/summary_dashboard"
+        android:layout_width="match_parent"
+        android:layout_height="1dp"
+        android:background="?attr/row_separator"
+        />
+</RelativeLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_summary_details.xml b/app/src/main/res/layout/activity_summary_details.xml
new file mode 100644
index 0000000..4f7106b
--- /dev/null
+++ b/app/src/main/res/layout/activity_summary_details.xml
@@ -0,0 +1,111 @@
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/activity_summary_detail_scroll_layout"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:scrollbars="vertical">
+
+    <LinearLayout
+        android:id="@+id/activity_summary_detail_relative_layout"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical">
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_margin="8dp"
+            android:layout_marginBottom="8dp"
+            android:orientation="horizontal">
+
+            <LinearLayout
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:orientation="vertical"
+                android:paddingStart="8dp"
+                android:paddingEnd="8dp">
+
+                <TextView
+                    android:id="@+id/activityname"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_marginBottom="2dp"
+                    android:fontFamily="sans-serif-black"
+                    android:scrollHorizontally="false"
+                    android:text="Activity Name"
+                    android:textSize="16sp"
+                    tools:ignore="HardcodedText" />
+
+                <TextView
+                    android:id="@+id/activitydate"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_marginBottom="2dp"
+                    android:layout_weight="1"
+                    android:gravity="start"
+                    android:text="Today, 10:00 - 12:00"
+                    tools:ignore="HardcodedText" />
+
+                <TextView
+                    android:id="@+id/activityduration"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_weight="1"
+                    android:gravity="end"
+                    android:text="30min"
+                    tools:ignore="HardcodedText" />
+            </LinearLayout>
+
+            <LinearLayout
+                android:id="@+id/top_layout2"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:foregroundGravity="top"
+                android:gravity="end"
+                android:orientation="horizontal"
+                android:paddingStart="8dp"
+                android:paddingEnd="8dp">
+
+                <ImageView
+                    android:id="@+id/item_image"
+                    android:layout_width="48dp"
+                    android:layout_height="48dp"
+                    android:layout_gravity="center_vertical"
+                    android:layout_marginStart="10dp"
+                    android:layout_marginEnd="10dp"
+                    android:contentDescription="@string/icon_placeholder"
+                    app:srcCompat="@drawable/ic_activity_unknown" />
+            </LinearLayout>
+
+        </LinearLayout>
+
+        <LinearLayout
+            android:id="@+id/bottom_layout"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:orientation="vertical">
+
+            <View
+                android:layout_width="match_parent"
+                android:layout_height="2dp"
+                android:background="@color/gauge_line_color" />
+
+            <LinearLayout
+                android:id="@+id/summaryDetails"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                android:orientation="vertical" />
+
+            <FrameLayout
+                android:id="@+id/chartsFragmentHolder"
+                android:layout_width="match_parent"
+                android:layout_height="300dp" />
+
+            <FrameLayout
+                android:id="@+id/gpsFragmentHolder"
+                android:layout_width="match_parent"
+                android:layout_height="300dp" />
+        </LinearLayout>
+    </LinearLayout>
+</ScrollView>
diff --git a/app/src/main/res/layout/activity_supercars_control.xml b/app/src/main/res/layout/activity_supercars_control.xml
new file mode 100644
index 0000000..3b6c066
--- /dev/null
+++ b/app/src/main/res/layout/activity_supercars_control.xml
@@ -0,0 +1,142 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical"
+        tools:ignore="MissingConstraints">
+
+        <TextView
+            android:id="@+id/battery_percentage_label"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_horizontal"
+            android:layout_weight="1"
+            android:foregroundGravity="center_horizontal"
+            android:gravity="center_horizontal"
+            android:text=""
+            android:textSize="48sp" />
+
+        <nodomain.freeyourgadget.gadgetbridge.devices.supercars.JoystickView
+            android:id="@+id/joystickLeft"
+            android:layout_width="wrap_content"
+            android:layout_height="300dp"
+            android:layout_weight="1" />
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal">
+
+            <CheckBox
+                android:id="@+id/turboMode"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:height="48dp"
+                android:text="@string/supercars_turbo_speed_label"
+                tools:ignore="TouchTargetSizeCheck" />
+
+            <CheckBox
+                android:id="@+id/lightsOn"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:height="48dp"
+                android:text="@string/supercars_lights_label"
+                tools:ignore="TouchTargetSizeCheck" />
+
+            <CheckBox
+                android:id="@+id/lightsBlinking"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:height="48dp"
+                android:text="@string/supercars_lights_blinking_label"
+                tools:ignore="TouchTargetSizeCheck" />
+
+        </LinearLayout>
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal">
+
+            <ImageButton
+                android:id="@+id/trick1"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_margin="1dp"
+                android:layout_marginStart="1dp"
+                android:layout_marginLeft="1dp"
+                android:layout_marginTop="1dp"
+                android:layout_marginEnd="1dp"
+                android:layout_marginRight="1dp"
+                android:layout_marginBottom="1dp"
+                android:layout_weight="1"
+                android:background="@color/accent"
+                android:minHeight="64dp"
+                app:srcCompat="@drawable/ic_arrow_circle_left" />
+
+            <ImageButton
+                android:id="@+id/trick2"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_margin="1dp"
+                android:layout_marginStart="1dp"
+                android:layout_marginLeft="1dp"
+                android:layout_marginTop="1dp"
+                android:layout_marginEnd="1dp"
+                android:layout_marginRight="1dp"
+                android:layout_marginBottom="1dp"
+                android:layout_weight="1"
+                android:background="@color/accent"
+                android:minHeight="64dp"
+                app:srcCompat="@drawable/ic_arrow_circle_right" />
+
+        </LinearLayout>
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal">
+
+            <ImageButton
+                android:id="@+id/trick3"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_margin="1dp"
+                android:layout_marginStart="1dp"
+                android:layout_marginLeft="1dp"
+                android:layout_marginTop="1dp"
+                android:layout_marginEnd="1dp"
+                android:layout_marginRight="1dp"
+                android:layout_marginBottom="1dp"
+                android:layout_weight="1"
+                android:background="@color/accent"
+                android:minHeight="64dp"
+                app:srcCompat="@drawable/ic_u_turn_left" />
+
+            <ImageButton
+                android:id="@+id/trick4"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_margin="1dp"
+                android:layout_marginStart="1dp"
+                android:layout_marginLeft="1dp"
+                android:layout_marginTop="1dp"
+                android:layout_marginEnd="1dp"
+                android:layout_marginRight="1dp"
+                android:layout_marginBottom="1dp"
+                android:layout_weight="1"
+                android:background="@color/accent"
+                android:minHeight="64dp"
+                app:srcCompat="@drawable/ic_u_turn_right" />
+
+        </LinearLayout>
+    </LinearLayout>
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_um25_data.xml b/app/src/main/res/layout/activity_um25_data.xml
new file mode 100644
index 0000000..e9b67ff
--- /dev/null
+++ b/app/src/main/res/layout/activity_um25_data.xml
@@ -0,0 +1,101 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:gravity="center_horizontal"
+        android:orientation="vertical">
+
+        <TextView
+            android:id="@+id/um25_text_voltage"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_margin="10dp"
+            android:textColor="@android:color/holo_green_dark"
+            android:textSize="@dimen/um25_value_text_size" />
+
+        <LinearLayout
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_margin="10dp"
+            android:gravity="center_horizontal"
+            android:orientation="vertical">
+
+            <TextView
+                android:id="@+id/um25_text_current"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:textColor="@android:color/holo_red_dark"
+                android:textSize="@dimen/um25_value_text_size" />
+
+            <TextView
+                android:id="@+id/um25_text_current_sum"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:textColor="@android:color/holo_red_dark"
+                android:textSize="@dimen/um25_value_text_size_small" />
+
+        </LinearLayout>
+
+        <LinearLayout
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_margin="10dp"
+            android:orientation="vertical"
+            android:gravity="center_horizontal">
+
+            <TextView
+                android:id="@+id/um25_text_wattage"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_margin="10dp"
+                android:textColor="@android:color/white"
+                android:textSize="@dimen/um25_value_text_size" />
+
+            <TextView
+                android:id="@+id/um25_text_wattage_sum"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:textColor="@android:color/white"
+                android:textSize="@dimen/um25_value_text_size_small" />
+
+        </LinearLayout>
+
+
+        <TextView
+            android:id="@+id/um25_text_charge_duration"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_margin="10dp"
+            android:textColor="@android:color/white"
+            android:textSize="@dimen/um25_value_text_size" />
+
+        <LinearLayout
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_margin="10dp"
+            android:orientation="horizontal">
+
+            <TextView
+                android:id="@+id/um25_text_temperature"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:textColor="@android:color/white"
+                android:textSize="@dimen/um25_value_text_size_small"
+                android:layout_marginEnd="20dp"/>
+
+            <TextView
+                android:id="@+id/um25_cable_resistance"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:textColor="@android:color/white"
+                android:textSize="@dimen/um25_value_text_size_small" />
+
+        </LinearLayout>
+
+    </LinearLayout>
+
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_vesc_control.xml b/app/src/main/res/layout/activity_vesc_control.xml
new file mode 100644
index 0000000..dba721b
--- /dev/null
+++ b/app/src/main/res/layout/activity_vesc_control.xml
@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical">
+
+    <LinearLayout
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal">
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="RPM: "
+            android:labelFor="@+id/vesc_control_input_rpm"/>
+
+        <EditText
+            android:layout_width="100dp"
+            android:layout_height="wrap_content"
+            android:inputType="numberDecimal"
+            android:id="@+id/vesc_control_input_rpm"/>
+
+    </LinearLayout>
+
+    <LinearLayout
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal">
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Break current (A): " />
+
+        <EditText
+            android:layout_width="100dp"
+            android:layout_height="wrap_content"
+            android:inputType="numberDecimal"
+            android:id="@+id/vesc_control_input_break_current"/>
+
+    </LinearLayout>
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+
+        <Button
+            android:layout_width="wrap_content"
+            android:layout_height="100dp"
+            android:layout_weight="0.5"
+            android:text="break"
+            android:id="@+id/vesc_control_button_break"/>
+
+        <Button
+            android:layout_width="wrap_content"
+            android:layout_height="100dp"
+            android:layout_weight="0.5"
+            android:text="fwd"
+            android:id="@+id/vesc_control_button_fwd" />
+
+    </LinearLayout>
+
+    <CheckBox
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="Volume keys control"
+        android:id="@+id/vesc_control_checkbox_volume_keys" />
+
+</LinearLayout>
diff --git a/app/src/main/res/layout/activity_vibration.xml b/app/src/main/res/layout/activity_vibration.xml
new file mode 100644
index 0000000..4a2a5ac
--- /dev/null
+++ b/app/src/main/res/layout/activity_vibration.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:layout_centerHorizontal="false"
+    android:layout_centerInParent="false"
+    android:layout_centerVertical="false"
+    android:weightSum="1">
+
+    <SeekBar
+        android:layout_width="match_parent"
+        android:id="@+id/vibration_seekbar"
+        android:max="16"
+        android:scaleY="4.0"
+        android:layout_height="wrap_content" />
+</LinearLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_watch9_calibration.xml b/app/src/main/res/layout/activity_watch9_calibration.xml
new file mode 100644
index 0000000..dc5efc5
--- /dev/null
+++ b/app/src/main/res/layout/activity_watch9_calibration.xml
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <TextView
+        android:id="@+id/tv_note"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentStart="true"
+        android:layout_alignParentTop="true"
+        android:layout_marginStart="16dp"
+        android:layout_marginTop="16dp"
+        android:text="@string/discovery_note"
+        android:textColor="@color/accent"
+        android:textStyle="bold" />
+
+    <TextView
+        android:id="@+id/tv_hint"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignStart="@+id/tv_note"
+        android:layout_below="@+id/tv_note"
+        android:layout_marginTop="16dp"
+        android:text="@string/watch9_calibration_hint" />
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_above="@id/watch9_bt_calibrate"
+        android:layout_below="@id/tv_hint"
+        android:orientation="horizontal"
+        android:paddingLeft="8dp"
+        android:paddingRight="8dp"
+        android:baselineAligned="false">
+
+        <LinearLayout
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:layout_marginLeft="8dp"
+            android:layout_marginRight="8dp"
+            android:layout_weight="1"
+            android:orientation="vertical">
+
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/watch9_time_hours"
+                android:textAllCaps="true"
+                android:textColor="@color/accent" />
+
+            <NumberPicker
+                android:id="@+id/np_hour"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center"
+                android:descendantFocusability="blocksDescendants"  />
+        </LinearLayout>
+
+        <LinearLayout
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:layout_marginLeft="8dp"
+            android:layout_marginRight="8dp"
+            android:layout_weight="1"
+            android:orientation="vertical">
+
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/watch9_time_minutes"
+                android:textAllCaps="true"
+                android:textColor="@color/accent" />
+
+            <NumberPicker
+                android:id="@+id/np_minute"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center"
+                android:descendantFocusability="blocksDescendants"  />
+        </LinearLayout>
+
+        <LinearLayout
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:layout_marginLeft="8dp"
+            android:layout_marginRight="8dp"
+            android:layout_weight="1"
+            android:orientation="vertical">
+
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/watch9_time_seconds"
+                android:textAllCaps="true"
+                android:textColor="@color/accent" />
+
+            <NumberPicker
+                android:id="@+id/np_second"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center"
+                android:descendantFocusability="blocksDescendants"  />
+        </LinearLayout>
+
+
+
+    </LinearLayout>
+
+    <Button
+        android:id="@+id/watch9_bt_calibrate"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentBottom="true"
+        android:layout_centerHorizontal="true"
+        android:layout_marginBottom="16dp"
+        android:padding="16dp"
+        android:text="@string/watch9_calibration_button" />
+
+</RelativeLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_watch9_pairing.xml b/app/src/main/res/layout/activity_watch9_pairing.xml
new file mode 100644
index 0000000..2add84a
--- /dev/null
+++ b/app/src/main/res/layout/activity_watch9_pairing.xml
@@ -0,0 +1,33 @@
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:paddingLeft="@dimen/activity_horizontal_margin"
+    android:paddingRight="@dimen/activity_horizontal_margin"
+    android:paddingTop="@dimen/activity_vertical_margin"
+    android:paddingBottom="@dimen/activity_vertical_margin"
+    tools:context="nodomain.freeyourgadget.gadgetbridge.devices.watch9.Watch9PairingActivity">
+
+    <TextView
+        android:text="@string/pairing"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:id="@+id/watch9_pair_message" />
+
+    <ProgressBar
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:id="@+id/progressBar"
+        android:layout_marginTop="25dp"
+        android:layout_below="@+id/watch9_pair_message"
+        android:layout_centerHorizontal="true" />
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/watch9_pairing_tap_hint"
+        android:id="@+id/watch9_pair_hint"
+        android:layout_centerVertical="true"
+        android:layout_alignParentStart="true" />
+
+</RelativeLayout>
diff --git a/app/src/main/res/layout/activity_watchxplus_calibration.xml b/app/src/main/res/layout/activity_watchxplus_calibration.xml
new file mode 100644
index 0000000..dc5efc5
--- /dev/null
+++ b/app/src/main/res/layout/activity_watchxplus_calibration.xml
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <TextView
+        android:id="@+id/tv_note"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentStart="true"
+        android:layout_alignParentTop="true"
+        android:layout_marginStart="16dp"
+        android:layout_marginTop="16dp"
+        android:text="@string/discovery_note"
+        android:textColor="@color/accent"
+        android:textStyle="bold" />
+
+    <TextView
+        android:id="@+id/tv_hint"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignStart="@+id/tv_note"
+        android:layout_below="@+id/tv_note"
+        android:layout_marginTop="16dp"
+        android:text="@string/watch9_calibration_hint" />
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_above="@id/watch9_bt_calibrate"
+        android:layout_below="@id/tv_hint"
+        android:orientation="horizontal"
+        android:paddingLeft="8dp"
+        android:paddingRight="8dp"
+        android:baselineAligned="false">
+
+        <LinearLayout
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:layout_marginLeft="8dp"
+            android:layout_marginRight="8dp"
+            android:layout_weight="1"
+            android:orientation="vertical">
+
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/watch9_time_hours"
+                android:textAllCaps="true"
+                android:textColor="@color/accent" />
+
+            <NumberPicker
+                android:id="@+id/np_hour"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center"
+                android:descendantFocusability="blocksDescendants"  />
+        </LinearLayout>
+
+        <LinearLayout
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:layout_marginLeft="8dp"
+            android:layout_marginRight="8dp"
+            android:layout_weight="1"
+            android:orientation="vertical">
+
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/watch9_time_minutes"
+                android:textAllCaps="true"
+                android:textColor="@color/accent" />
+
+            <NumberPicker
+                android:id="@+id/np_minute"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center"
+                android:descendantFocusability="blocksDescendants"  />
+        </LinearLayout>
+
+        <LinearLayout
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:layout_marginLeft="8dp"
+            android:layout_marginRight="8dp"
+            android:layout_weight="1"
+            android:orientation="vertical">
+
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/watch9_time_seconds"
+                android:textAllCaps="true"
+                android:textColor="@color/accent" />
+
+            <NumberPicker
+                android:id="@+id/np_second"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center"
+                android:descendantFocusability="blocksDescendants"  />
+        </LinearLayout>
+
+
+
+    </LinearLayout>
+
+    <Button
+        android:id="@+id/watch9_bt_calibrate"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentBottom="true"
+        android:layout_centerHorizontal="true"
+        android:layout_marginBottom="16dp"
+        android:padding="16dp"
+        android:text="@string/watch9_calibration_button" />
+
+</RelativeLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_weather_notification.xml b/app/src/main/res/layout/activity_weather_notification.xml
new file mode 100644
index 0000000..754328f
--- /dev/null
+++ b/app/src/main/res/layout/activity_weather_notification.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:layout_margin="10dp">
+
+    <TextView
+        android:text="Weather on your Pebble"
+        android:textAlignment="center"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:textStyle="bold"
+        android:textSize="20dp" />
+
+    <androidx.legacy.widget.Space
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/activity_vertical_margin" />
+
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:id="@+id/weather_notification_label"
+        android:text="@string/weather_notification_label" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_welcome.xml b/app/src/main/res/layout/activity_welcome.xml
new file mode 100644
index 0000000..e7ba028
--- /dev/null
+++ b/app/src/main/res/layout/activity_welcome.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:fitsSystemWindows="true"
+    android:orientation="vertical"
+    tools:context=".activities.welcome.WelcomeActivity">
+
+    <androidx.viewpager2.widget.ViewPager2
+        android:id="@+id/welcome_viewpager"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintBottom_toTopOf="@id/welcome_page_indicator" />
+
+    <nodomain.freeyourgadget.gadgetbridge.activities.welcome.WelcomePageIndicator
+        android:id="@+id/welcome_page_indicator"
+        android:layout_width="match_parent"
+        android:layout_height="30dp"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:page_indicator_color="?attr/colorPrimary" />
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_widget_screen_details.xml b/app/src/main/res/layout/activity_widget_screen_details.xml
new file mode 100644
index 0000000..3d56ac5
--- /dev/null
+++ b/app/src/main/res/layout/activity_widget_screen_details.xml
@@ -0,0 +1,254 @@
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:card_view="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/constraintLayout"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    tools:context="nodomain.freeyourgadget.gadgetbridge.activities.widgets.WidgetScreenDetailsActivity">
+
+    <com.google.android.material.card.MaterialCardView
+        android:id="@+id/card_layout"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_margin="8dp"
+        android:foreground="?android:attr/selectableItemBackground"
+        app:cardBackgroundColor="?attr/cardview_background_color"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        card_view:cardElevation="3dp"
+        card_view:contentPadding="4dp">
+
+        <androidx.constraintlayout.widget.ConstraintLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_margin="8dp">
+
+            <TextView
+                android:id="@+id/label_layout"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/widget_layout"
+                android:textAppearance="?android:attr/textAppearance"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toTopOf="parent" />
+
+            <TextView
+                android:id="@+id/widget_screen_layout"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="2dp"
+                android:text="2x1"
+                android:textAppearance="?android:attr/textAppearanceMedium"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toBottomOf="@id/label_layout" />
+        </androidx.constraintlayout.widget.ConstraintLayout>
+    </com.google.android.material.card.MaterialCardView>
+
+    <com.google.android.material.card.MaterialCardView
+        android:id="@+id/card_widget_top_left"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_margin="8dp"
+        android:foreground="?android:attr/selectableItemBackground"
+        app:cardBackgroundColor="?attr/cardview_background_color"
+        app:layout_constraintEnd_toStartOf="@id/card_widget_top_right"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/card_layout"
+        card_view:cardElevation="3dp"
+        card_view:contentPadding="4dp">
+
+        <androidx.constraintlayout.widget.ConstraintLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_margin="8dp">
+
+            <TextView
+                android:id="@+id/label_widget_top_left"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/widget"
+                android:textAppearance="?android:attr/textAppearance"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toTopOf="parent" />
+
+            <TextView
+                android:id="@+id/widget_top_left"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="2dp"
+                android:text="TOP LEFT"
+                android:textAppearance="?android:attr/textAppearanceMedium"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toBottomOf="@id/label_widget_top_left" />
+        </androidx.constraintlayout.widget.ConstraintLayout>
+    </com.google.android.material.card.MaterialCardView>
+
+    <com.google.android.material.card.MaterialCardView
+        android:id="@+id/card_widget_top_right"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_margin="8dp"
+        android:foreground="?android:attr/selectableItemBackground"
+        app:cardBackgroundColor="?attr/cardview_background_color"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toEndOf="@id/card_widget_top_left"
+        app:layout_constraintTop_toBottomOf="@id/card_layout"
+        card_view:cardElevation="3dp"
+        card_view:contentPadding="4dp">
+
+        <androidx.constraintlayout.widget.ConstraintLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_margin="8dp">
+
+            <TextView
+                android:id="@+id/label_widget_top_right"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/widget"
+                android:textAppearance="?android:attr/textAppearance"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toTopOf="parent" />
+
+            <TextView
+                android:id="@+id/widget_top_right"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="2dp"
+                android:text="TOP RIGHT"
+                android:textAppearance="?android:attr/textAppearanceMedium"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toBottomOf="@id/label_widget_top_right" />
+        </androidx.constraintlayout.widget.ConstraintLayout>
+    </com.google.android.material.card.MaterialCardView>
+
+    <com.google.android.material.card.MaterialCardView
+        android:id="@+id/card_widget_center"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_margin="8dp"
+        android:foreground="?android:attr/selectableItemBackground"
+        app:cardBackgroundColor="?attr/cardview_background_color"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/card_widget_top_right"
+        card_view:cardElevation="3dp"
+        card_view:contentPadding="4dp">
+
+        <androidx.constraintlayout.widget.ConstraintLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_margin="8dp">
+
+            <TextView
+                android:id="@+id/label_widget_center"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/widget"
+                android:textAppearance="?android:attr/textAppearance"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toTopOf="parent" />
+
+            <TextView
+                android:id="@+id/widget_center"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="2dp"
+                android:text="CENTER"
+                android:textAppearance="?android:attr/textAppearanceMedium"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toBottomOf="@id/label_widget_center" />
+        </androidx.constraintlayout.widget.ConstraintLayout>
+    </com.google.android.material.card.MaterialCardView>
+
+    <com.google.android.material.card.MaterialCardView
+        android:id="@+id/card_widget_bottom_left"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_margin="8dp"
+        android:foreground="?android:attr/selectableItemBackground"
+        app:cardBackgroundColor="?attr/cardview_background_color"
+        app:layout_constraintEnd_toStartOf="@id/card_widget_bottom_right"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/card_widget_center"
+        card_view:cardElevation="3dp"
+        card_view:contentPadding="4dp">
+
+        <androidx.constraintlayout.widget.ConstraintLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_margin="8dp">
+
+            <TextView
+                android:id="@+id/label_widget_bottom_left"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/widget"
+                android:textAppearance="?android:attr/textAppearance"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toTopOf="parent" />
+
+            <TextView
+                android:id="@+id/widget_bottom_left"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="2dp"
+                android:text="BOT LEFT"
+                android:textAppearance="?android:attr/textAppearanceMedium"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toBottomOf="@id/label_widget_bottom_left" />
+        </androidx.constraintlayout.widget.ConstraintLayout>
+    </com.google.android.material.card.MaterialCardView>
+
+    <com.google.android.material.card.MaterialCardView
+        android:id="@+id/card_widget_bottom_right"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_margin="8dp"
+        android:foreground="?android:attr/selectableItemBackground"
+        app:cardBackgroundColor="?attr/cardview_background_color"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toEndOf="@id/card_widget_bottom_left"
+        app:layout_constraintTop_toBottomOf="@id/card_widget_center"
+        card_view:cardElevation="3dp"
+        card_view:contentPadding="4dp">
+
+        <androidx.constraintlayout.widget.ConstraintLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_margin="8dp">
+
+            <TextView
+                android:id="@+id/label_widget_bottom_right"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/widget"
+                android:textAppearance="?android:attr/textAppearance"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toTopOf="parent" />
+
+            <TextView
+                android:id="@+id/widget_bottom_right"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="2dp"
+                android:text="BOT RIGHT"
+                android:textAppearance="?android:attr/textAppearanceMedium"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toBottomOf="@id/label_widget_bottom_right" />
+        </androidx.constraintlayout.widget.ConstraintLayout>
+    </com.google.android.material.card.MaterialCardView>
+
+    <com.google.android.material.floatingactionbutton.FloatingActionButton
+        android:id="@+id/fab_save"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="bottom|end"
+        android:layout_margin="16dp"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:srcCompat="@drawable/ic_save" />
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/app/src/main/res/layout/activity_widget_screens_list.xml b/app/src/main/res/layout/activity_widget_screens_list.xml
new file mode 100644
index 0000000..0c642fb
--- /dev/null
+++ b/app/src/main/res/layout/activity_widget_screens_list.xml
@@ -0,0 +1,27 @@
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:fitsSystemWindows="true"
+    app:layout_behavior="@string/appbar_scrolling_view_behavior"
+    tools:context="nodomain.freeyourgadget.gadgetbridge.activities.widgets.WidgetScreensListActivity">
+
+    <androidx.recyclerview.widget.RecyclerView
+        android:id="@+id/widget_screens_list"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_alignParentTop="true"
+        android:layout_centerHorizontal="true"
+        android:divider="@null" />
+
+    <com.google.android.material.floatingactionbutton.FloatingActionButton
+        android:id="@+id/fab"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentEnd="true"
+        android:layout_alignParentBottom="true"
+        android:layout_gravity="bottom|end"
+        android:layout_margin="16dp"
+        app:srcCompat="@drawable/ic_add" />
+</RelativeLayout>
diff --git a/app/src/main/res/layout/activity_withings_calibration.xml b/app/src/main/res/layout/activity_withings_calibration.xml
new file mode 100644
index 0000000..c6fa346
--- /dev/null
+++ b/app/src/main/res/layout/activity_withings_calibration.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:rotaryControl="http://schemas.android.com/apk/res-auto"
+    android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <TextView
+        android:id="@+id/withings_calibration_textview"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:padding="30dp"
+        android:text="@string/withings_calibration_text_hours"
+        android:textAppearance="?android:attr/textAppearanceLarge" />
+
+    <nodomain.freeyourgadget.gadgetbridge.devices.withingssteelhr.RotaryControl
+        android:id="@+id/rotary_control"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:padding="30dp"
+        rotaryControl:controlpoint_color="@color/accent"
+        rotaryControl:controlpoint_size="20dp"
+        rotaryControl:line_color="@color/design_default_color_on_secondary"
+        rotaryControl:line_thickness="10dp" />
+
+    <Button
+        android:id="@+id/withings_calibration_button_previous"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentStart="true"
+        android:layout_alignParentBottom="true"
+        android:layout_marginBottom="30dp"
+        android:layout_marginStart="50dp"
+        android:padding="16dp"
+        android:text="@string/withings_bt_calibration_previous" />
+
+    <Button
+        android:id="@+id/withings_calibration_button_next"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentBottom="true"
+        android:layout_alignParentEnd="true"
+        android:layout_marginBottom="30dp"
+        android:layout_marginEnd="50dp"
+        android:padding="16dp"
+        android:text="@string/withings_bt_calibration_next" />
+
+    <Button
+        android:id="@+id/withings_calibration_button_ok"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentBottom="true"
+        android:layout_centerHorizontal="true"
+        android:layout_marginBottom="30dp"
+        android:padding="16dp"
+        android:text="@string/ok" />
+
+</RelativeLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_womp_scan.xml b/app/src/main/res/layout/activity_womp_scan.xml
new file mode 100644
index 0000000..283c7f5
--- /dev/null
+++ b/app/src/main/res/layout/activity_womp_scan.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="#000000">
+
+    <androidx.camera.view.PreviewView
+        android:id="@+id/womp_preview"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_gravity="bottom"
+        android:background="#CC000000"
+        android:orientation="vertical"
+        android:padding="16dp">
+
+        <TextView
+            android:id="@+id/womp_scan_hint"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:textColor="#FFFFFF"
+            android:textSize="16sp"
+            android:text="瀵瑰噯浜岀淮鐮佹壂鎻�" />
+
+        <Button
+            android:id="@+id/womp_scan_cancel"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="12dp"
+            android:text="鍋滄 / 杩斿洖" />
+    </LinearLayout>
+</FrameLayout>
diff --git a/app/src/main/res/layout/activity_womp_web.xml b/app/src/main/res/layout/activity_womp_web.xml
new file mode 100644
index 0000000..89282cf
--- /dev/null
+++ b/app/src/main/res/layout/activity_womp_web.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <WebView
+        android:id="@+id/womp_webview"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+</FrameLayout>
diff --git a/app/src/main/res/layout/activity_world_clock_details.xml b/app/src/main/res/layout/activity_world_clock_details.xml
new file mode 100644
index 0000000..bbf1aa7
--- /dev/null
+++ b/app/src/main/res/layout/activity_world_clock_details.xml
@@ -0,0 +1,161 @@
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:card_view="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/constraintLayout"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    tools:context="nodomain.freeyourgadget.gadgetbridge.activities.WorldClockDetails">
+
+    <com.google.android.material.card.MaterialCardView
+        android:id="@+id/card_enabled"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_margin="8dp"
+        android:foreground="?android:attr/selectableItemBackground"
+        app:cardBackgroundColor="?attr/cardview_background_color"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        card_view:cardElevation="3dp"
+        card_view:contentPadding="4dp">
+
+        <androidx.constraintlayout.widget.ConstraintLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_margin="8dp">
+
+            <CheckBox
+                android:id="@+id/world_clock_enabled"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:text="@string/function_enabled" />
+        </androidx.constraintlayout.widget.ConstraintLayout>
+    </com.google.android.material.card.MaterialCardView>
+
+    <com.google.android.material.card.MaterialCardView
+        android:id="@+id/card_timezone"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_margin="8dp"
+        android:foreground="?android:attr/selectableItemBackground"
+        app:cardBackgroundColor="?attr/cardview_background_color"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/card_enabled"
+        card_view:cardElevation="3dp"
+        card_view:contentPadding="4dp">
+
+        <androidx.constraintlayout.widget.ConstraintLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_margin="8dp">
+
+            <TextView
+                android:id="@+id/label_timezone"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/world_clock_timezone"
+                android:textAppearance="?android:attr/textAppearance"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toTopOf="parent" />
+
+            <TextView
+                android:id="@+id/world_clock_timezone"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="2dp"
+                android:text=""
+                android:textAppearance="?android:attr/textAppearanceLarge"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toBottomOf="@id/label_timezone" />
+        </androidx.constraintlayout.widget.ConstraintLayout>
+    </com.google.android.material.card.MaterialCardView>
+
+    <com.google.android.material.card.MaterialCardView
+        android:id="@+id/card_label"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_margin="8dp"
+        android:foreground="?android:attr/selectableItemBackground"
+        app:cardBackgroundColor="?attr/cardview_background_color"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/card_timezone"
+        card_view:cardElevation="3dp"
+        card_view:contentPadding="4dp">
+
+        <androidx.constraintlayout.widget.ConstraintLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_margin="8dp">
+
+            <TextView
+                android:id="@+id/label_message"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/world_clock_label"
+                android:textAppearance="?android:attr/textAppearance"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toTopOf="parent" />
+
+            <EditText
+                android:id="@+id/world_clock_label"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="2dp"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toBottomOf="@id/label_message" />
+        </androidx.constraintlayout.widget.ConstraintLayout>
+    </com.google.android.material.card.MaterialCardView>
+
+    <com.google.android.material.card.MaterialCardView
+        android:id="@+id/card_code"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_margin="8dp"
+        android:foreground="?android:attr/selectableItemBackground"
+        app:cardBackgroundColor="?attr/cardview_background_color"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/card_label"
+        card_view:cardElevation="3dp"
+        card_view:contentPadding="4dp">
+
+        <androidx.constraintlayout.widget.ConstraintLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_margin="8dp">
+
+            <TextView
+                android:id="@+id/label_code"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/world_clock_code"
+                android:textAppearance="?android:attr/textAppearance"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toTopOf="parent" />
+
+            <EditText
+                android:id="@+id/world_clock_code"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="2dp"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toBottomOf="@id/label_code" />
+        </androidx.constraintlayout.widget.ConstraintLayout>
+    </com.google.android.material.card.MaterialCardView>
+
+    <com.google.android.material.floatingactionbutton.FloatingActionButton
+        android:id="@+id/fab_save"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="bottom|end"
+        android:layout_margin="16dp"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:srcCompat="@drawable/ic_save" />
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/app/src/main/res/layout/custom_chart_marker.xml b/app/src/main/res/layout/custom_chart_marker.xml
new file mode 100644
index 0000000..18b27d4
--- /dev/null
+++ b/app/src/main/res/layout/custom_chart_marker.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <TextView
+        android:id="@+id/chart_marker_item_top"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:textAlignment="center"
+        app:layout_constraintBottom_toTopOf="@+id/chart_marker_item_bottom"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintHorizontal_bias="0.52"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <TextView
+        android:id="@+id/chart_marker_item_bottom"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:textAlignment="center"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/chart_marker_item_top" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/app/src/main/res/layout/dashboard_widget_generic_gauge.xml b/app/src/main/res/layout/dashboard_widget_generic_gauge.xml
new file mode 100644
index 0000000..d8039fa
--- /dev/null
+++ b/app/src/main/res/layout/dashboard_widget_generic_gauge.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:gravity="center_vertical"
+    tools:context=".activities.dashboard.AbstractDashboardWidget">
+
+    <RelativeLayout
+        android:id="@+id/card_layout"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_margin="8dp"
+        android:orientation="vertical"
+        tools:ignore="UselessParent">
+
+        <ImageView
+            android:id="@+id/gauge_bar"
+            android:layout_width="150dp"
+            android:layout_height="75dp"
+            android:layout_centerHorizontal="true"
+            android:scaleType="fitStart" />
+
+        <TextView
+            android:id="@+id/gauge_value"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_centerHorizontal="true"
+            android:layout_marginTop="28dp"
+            android:text="@string/stats_empty_value"
+            android:textSize="30sp" />
+
+        <TextView
+            android:id="@+id/gauge_label"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_below="@+id/gauge_value"
+            android:layout_centerHorizontal="true"
+            android:text="@string/no_data" />
+
+    </RelativeLayout>
+
+</LinearLayout>
diff --git a/app/src/main/res/layout/dashboard_widget_goals.xml b/app/src/main/res/layout/dashboard_widget_goals.xml
new file mode 100644
index 0000000..dade05f
--- /dev/null
+++ b/app/src/main/res/layout/dashboard_widget_goals.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    android:gravity="center_vertical"
+    tools:context=".activities.dashboard.DashboardGoalsWidget">
+
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+        <ImageView
+            android:id="@+id/dashboard_goals_chart"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:layout_marginHorizontal="15dp"
+            android:layout_marginVertical="5dp"
+            android:gravity="center"
+            app:layout_constraintDimensionRatio="H,1:1"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent"
+            android:scaleType="fitXY" />
+    </androidx.constraintlayout.widget.ConstraintLayout>
+
+    <TextView
+        android:id="@+id/dashboard_goals_legend"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginHorizontal="8dp"
+        android:textAlignment="center" />
+</LinearLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/dashboard_widget_today.xml b/app/src/main/res/layout/dashboard_widget_today.xml
new file mode 100644
index 0000000..0e942aa
--- /dev/null
+++ b/app/src/main/res/layout/dashboard_widget_today.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    android:gravity="center_vertical"
+    tools:context=".activities.dashboard.DashboardTodayWidget">
+
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+        <ImageView
+            android:id="@+id/dashboard_today_chart"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:layout_marginHorizontal="15dp"
+            android:layout_marginVertical="5dp"
+            android:gravity="center"
+            app:layout_constraintDimensionRatio="H,1:1"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent"
+            android:scaleType="fitXY" />
+    </androidx.constraintlayout.widget.ConstraintLayout>
+
+    <TextView
+        android:id="@+id/dashboard_piechart_legend"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginHorizontal="8dp"
+        android:textAlignment="center" />
+</LinearLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/device_itemv2.xml b/app/src/main/res/layout/device_itemv2.xml
new file mode 100644
index 0000000..cbde32e
--- /dev/null
+++ b/app/src/main/res/layout/device_itemv2.xml
@@ -0,0 +1,546 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.coordinatorlayout.widget.CoordinatorLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:card_view="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:gravity="center_horizontal">
+
+    <com.google.android.material.card.MaterialCardView
+        android:id="@+id/card_view"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginEnd="8dp"
+        android:layout_marginTop="8dp"
+        android:layout_marginBottom="8dp"
+        android:foreground="?android:attr/selectableItemBackground"
+        app:cardBackgroundColor="?attr/cardview_background_color"
+        card_view:cardElevation="3dp"
+        card_view:contentPadding="8dp">
+
+        <RelativeLayout
+            android:layout_width="match_parent"
+            android:layout_height="match_parent">
+
+            <RelativeLayout
+                android:id="@+id/device_item_infos_box"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_alignParentStart="true"
+                android:layout_alignParentTop="true"
+                android:layout_marginBottom="8dp"
+                android:focusable="false"
+                android:visibility="gone">
+
+                <ListView
+                    android:id="@+id/device_item_infos"
+                    android:layout_width="wrap_content"
+                    android:layout_height="match_parent"
+                    android:layout_alignParentStart="true"
+                    android:focusable="false"
+                    android:scrollbars="none">
+                </ListView>
+            </RelativeLayout>
+
+            <RelativeLayout
+                android:layout_width="match_parent"
+                android:id="@+id/device_name_layout"
+                android:layout_below="@id/device_item_infos_box"
+                android:layout_height="wrap_content">
+
+                <ImageView
+                    android:id="@+id/device_image"
+                    android:layout_width="48dp"
+                    android:layout_height="48dp"
+                    android:layout_alignParentStart="true"
+                    android:layout_marginTop="2dp"
+                    android:background="?android:attr/selectableItemBackground"
+                    android:clickable="true"
+                    android:contentDescription="@string/candidate_item_device_image"
+                    android:tooltipText="@string/candidate_item_device_image"
+                    android:focusable="true"
+                    android:longClickable="true"
+                    card_view:srcCompat="@drawable/ic_device_pebble" />
+
+                <TextView
+                    android:id="@+id/device_name"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_toEndOf="@id/device_image"
+                    android:layout_alignParentEnd="true"
+                    android:layout_marginEnd="48dp"
+                    android:gravity="center"
+                    android:maxLines="1"
+                    android:textAppearance="@style/TextAppearance.AppCompat.Large"
+                    tools:text="My Pebble Watch" />
+
+                <TextView
+                    android:id="@+id/device_status"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_below="@id/device_name"
+                    android:layout_toEndOf="@id/device_image"
+                    android:layout_alignParentEnd="true"
+                    android:layout_marginEnd="48dp"
+                    android:gravity="center"
+                    android:textAppearance="@style/TextAppearance.AppCompat.Subhead"
+                    tools:text="@string/connecting" />
+
+                <ImageView
+                    android:id="@+id/device_info_image"
+                    android:layout_width="36dp"
+                    android:layout_height="36dp"
+                    android:layout_alignParentEnd="true"
+                    android:layout_marginBottom="8dp"
+                    android:layout_marginStart="8dp"
+                    android:layout_marginEnd="0dp"
+                    android:layout_marginTop="8dp"
+                    android:clickable="true"
+                    android:contentDescription="@string/candidate_item_device_image"
+                    android:tooltipText="@string/candidate_item_device_image"
+                    android:background="?android:attr/selectableItemBackground"
+                    card_view:srcCompat="@drawable/ic_more_vert"
+                    android:focusable="true"
+                    card_view:tint="@color/secondarytext" />
+            </RelativeLayout>
+
+            <com.google.android.flexbox.FlexboxLayout
+                android:id="@+id/device_info_icons"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_below="@id/device_name_layout"
+                card_view:flexWrap="wrap">
+
+                <LinearLayout
+                    android:id="@+id/device_battery_status_box"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:contentDescription="@string/battery_detail_activity_title"
+                    android:tooltipText="@string/battery_detail_activity_title"
+                    android:layout_alignParentStart="true"
+                    android:layout_margin="3dp"
+                    android:orientation="vertical">
+
+                    <ImageView
+                        android:id="@+id/device_battery_status"
+                        android:layout_width="40dp"
+                        android:layout_height="40dp"
+                        android:padding="3dp"
+                        android:scaleType="fitXY"
+                        card_view:srcCompat="@drawable/level_list_battery"
+                        card_view:tint="@color/secondarytext" />
+
+                    <TextView
+                        android:id="@+id/battery_status"
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:gravity="center_horizontal"
+                        android:minWidth="36dp"
+                        android:textColor="@color/secondarytext"
+                        android:textStyle="bold"
+                        tools:text="100%" />
+
+                </LinearLayout>
+
+                <LinearLayout
+                    android:id="@+id/device_battery_status_box1"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+
+                    android:layout_alignParentStart="true"
+                    android:layout_margin="3dp"
+                    android:orientation="vertical">
+
+                    <ImageView
+                        android:id="@+id/device_battery_status1"
+                        android:layout_width="40dp"
+                        android:layout_height="40dp"
+                        android:padding="3dp"
+                        android:scaleType="fitXY"
+                        card_view:srcCompat="@drawable/level_list_battery"
+                        card_view:tint="@color/secondarytext" />
+
+                    <TextView
+                        android:id="@+id/battery_status1"
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:gravity="center_horizontal"
+                        android:minWidth="36dp"
+                        android:textColor="@color/secondarytext"
+                        android:textStyle="bold"
+                        tools:text="100%" />
+
+                </LinearLayout>
+
+                <LinearLayout
+                    android:id="@+id/device_battery_status_box2"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+
+                    android:layout_alignParentStart="true"
+                    android:layout_margin="3dp"
+                    android:orientation="vertical">
+
+                    <ImageView
+                        android:id="@+id/device_battery_status2"
+                        android:layout_width="40dp"
+                        android:layout_height="40dp"
+                        android:padding="3dp"
+                        android:scaleType="fitXY"
+                        card_view:srcCompat="@drawable/level_list_battery"
+                        card_view:tint="@color/secondarytext" />
+
+                    <TextView
+                        android:id="@+id/battery_status2"
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:gravity="center_horizontal"
+                        android:minWidth="36dp"
+                        android:textColor="@color/secondarytext"
+                        android:textStyle="bold"
+                        tools:text="100%" />
+
+                </LinearLayout>
+
+
+                <ImageView
+                    android:id="@+id/device_specific_settings"
+                    android:layout_width="40dp"
+                    android:layout_height="40dp"
+                    android:layout_below="@id/device_image"
+                    android:layout_marginStart="3dp"
+                    android:layout_marginTop="3dp"
+                    android:layout_marginEnd="3dp"
+                    android:layout_marginBottom="3dp"
+                    android:layout_toEndOf="@id/device_battery_status_box"
+                    android:background="?android:attr/selectableItemBackground"
+                    android:clickable="true"
+                    android:focusable="true"
+                    android:padding="3dp"
+                    android:contentDescription="@string/title_activity_device_specific_settings"
+                    android:tooltipText="@string/title_activity_device_specific_settings"
+                    android:scaleType="fitXY"
+                    card_view:srcCompat="@drawable/ic_settings"
+                    card_view:tint="@color/secondarytext" />
+
+
+                <LinearLayout
+                    android:id="@+id/device_fm_frequency_box"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_below="@id/device_image"
+                    android:contentDescription="@string/controlcenter_change_fm_frequency"
+                    android:tooltipText="@string/controlcenter_change_fm_frequency"
+                    android:layout_margin="3dp"
+                    android:layout_toEndOf="@id/device_specific_settings"
+                    android:orientation="vertical">
+
+                    <ImageView
+                        android:id="@+id/device_fm_frequency"
+                        android:layout_width="40dp"
+                        android:layout_height="40dp"
+                        android:padding="3dp"
+                        android:scaleType="fitXY"
+                        card_view:srcCompat="@drawable/ic_radio"
+                        card_view:tint="@color/secondarytext" />
+
+                    <TextView
+                        android:id="@+id/fm_frequency"
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:gravity="center_horizontal"
+                        android:minWidth="36dp"
+                        android:textColor="@color/secondarytext"
+                        android:textStyle="bold"
+                        tools:text="107.8" />
+
+                </LinearLayout>
+
+                <ImageView
+                    android:id="@+id/device_led_color"
+                    android:layout_width="40dp"
+                    android:layout_height="40dp"
+                    android:layout_below="@id/device_image"
+                    android:layout_margin="3dp"
+                    android:layout_toEndOf="@id/device_fm_frequency_box"
+                    android:background="?android:attr/selectableItemBackground"
+                    android:clickable="true"
+                    android:contentDescription="@string/controlcenter_change_led_color"
+                    android:tooltipText="@string/controlcenter_change_led_color"
+                    android:focusable="true"
+                    android:padding="3dp"
+                    android:scaleType="fitXY"
+                    card_view:srcCompat="@drawable/ic_led_color" />
+
+                <LinearLayout
+                    android:id="@+id/device_action_fetch_activity_box"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_below="@id/device_image"
+                    android:layout_margin="3dp"
+                    android:layout_toEndOf="@id/device_led_color"
+                    android:gravity="center_vertical"
+                    android:minWidth="36dp"
+                    android:orientation="vertical">
+
+                    <ImageView
+                        android:id="@+id/device_action_fetch_activity"
+                        android:layout_width="40dp"
+                        android:layout_height="40dp"
+                        android:background="?android:attr/selectableItemBackground"
+                        android:clickable="true"
+                        android:contentDescription="@string/controlcenter_fetch_activity_data"
+                        android:tooltipText="@string/controlcenter_fetch_activity_data"
+                        android:focusable="true"
+                        android:padding="3dp"
+                        android:scaleType="fitXY"
+                        card_view:srcCompat="@drawable/ic_refresh"
+                        card_view:tint="@color/secondarytext" />
+
+                    <ProgressBar
+                        android:id="@+id/device_busy_indicator"
+                        style="@android:style/Widget.ProgressBar.Horizontal"
+                        android:layout_width="match_parent"
+                        android:layout_height="3dp"
+                        android:indeterminate="true"
+                        android:visibility="visible"
+                        tools:visibility="visible" />
+
+                </LinearLayout>
+
+                <ImageView
+                    android:id="@+id/device_action_take_screenshot"
+                    android:layout_width="40dp"
+                    android:layout_height="40dp"
+                    android:layout_below="@id/device_image"
+                    android:layout_margin="3dp"
+                    android:layout_toEndOf="@id/device_action_fetch_activity_box"
+                    android:background="?android:attr/selectableItemBackground"
+                    android:clickable="true"
+                    android:contentDescription="@string/controlcenter_take_screenshot"
+                    android:tooltipText="@string/controlcenter_take_screenshot"
+                    android:focusable="true"
+                    android:padding="3dp"
+                    android:scaleType="fitXY"
+                    card_view:srcCompat="@drawable/ic_screenshot"
+                    card_view:tint="@color/secondarytext" />
+
+                <ImageView
+                    android:id="@+id/device_action_manage_apps"
+                    android:layout_width="40dp"
+                    android:layout_height="40dp"
+                    android:layout_below="@id/device_image"
+                    android:layout_margin="3dp"
+                    android:layout_toEndOf="@id/device_action_take_screenshot"
+                    android:background="?android:attr/selectableItemBackground"
+                    android:clickable="true"
+                    android:contentDescription="@string/title_activity_appmanager"
+                    android:tooltipText="@string/title_activity_appmanager"
+                    android:focusable="true"
+                    android:padding="3dp"
+                    android:scaleType="fitXY"
+                    card_view:srcCompat="@drawable/ic_action_manage_apps"
+                    card_view:tint="@color/secondarytext" />
+
+                <ImageView
+                    android:id="@+id/device_action_set_alarms"
+                    android:layout_width="40dp"
+                    android:layout_height="40dp"
+                    android:layout_below="@id/device_image"
+                    android:layout_margin="3dp"
+                    android:layout_toEndOf="@id/device_action_manage_apps"
+                    android:background="?android:attr/selectableItemBackground"
+                    android:clickable="true"
+                    android:contentDescription="@string/controlcenter_start_configure_alarms"
+                    android:tooltipText="@string/controlcenter_start_configure_alarms"
+                    android:focusable="true"
+                    android:padding="3dp"
+                    android:scaleType="fitXY"
+                    card_view:srcCompat="@drawable/ic_access_alarms"
+                    card_view:tint="@color/secondarytext" />
+
+                <ImageView
+                    android:id="@+id/device_action_set_reminders"
+                    android:layout_width="40dp"
+                    android:layout_height="40dp"
+                    android:layout_below="@id/device_image"
+                    android:layout_margin="3dp"
+                    android:layout_toEndOf="@id/device_action_set_alarms"
+                    android:background="?android:attr/selectableItemBackground"
+                    android:clickable="true"
+                    android:contentDescription="@string/controlcenter_start_configure_reminders"
+                    android:tooltipText="@string/controlcenter_start_configure_reminders"
+                    android:focusable="true"
+                    android:padding="4dp"
+                    android:scaleType="fitXY"
+                    card_view:srcCompat="@drawable/ic_device_set_reminders"
+                    app:tint="@color/secondarytext" />
+
+                <ImageView
+                    android:id="@+id/device_action_show_activity_graphs"
+                    android:layout_width="40dp"
+                    android:layout_height="40dp"
+                    android:layout_below="@id/device_image"
+                    android:layout_margin="3dp"
+                    android:layout_toEndOf="@id/device_action_set_reminders"
+                    android:background="?android:attr/selectableItemBackground"
+                    android:clickable="true"
+                    android:contentDescription="@string/controlcenter_start_activitymonitor"
+                    android:tooltipText="@string/controlcenter_start_activitymonitor"
+                    android:focusable="true"
+                    android:padding="3dp"
+                    android:scaleType="fitXY"
+                    card_view:srcCompat="@drawable/ic_activity_graphs"
+                    card_view:tint="@color/secondarytext" />
+
+                <ImageView
+                    android:id="@+id/device_action_show_activity_tracks"
+                    android:layout_width="40dp"
+                    android:layout_height="40dp"
+                    android:layout_below="@id/device_image"
+                    android:layout_margin="3dp"
+                    android:layout_toEndOf="@id/device_action_show_activity_graphs"
+                    android:background="?android:attr/selectableItemBackground"
+                    android:clickable="true"
+                    android:contentDescription="@string/controlcenter_start_activity_tracks"
+                    android:tooltipText="@string/controlcenter_start_activity_tracks"
+                    android:focusable="true"
+                    android:padding="3dp"
+                    android:scaleType="fitXY"
+                    card_view:srcCompat="@drawable/ic_activity_tracks"
+                    card_view:tint="@color/secondarytext" />
+
+                <ImageView
+                    android:id="@+id/device_action_find"
+                    android:layout_width="40dp"
+                    android:layout_height="40dp"
+                    android:layout_below="@id/device_image"
+                    android:layout_margin="3dp"
+                    android:layout_toEndOf="@id/device_action_show_activity_tracks"
+                    android:clickable="true"
+                    android:contentDescription="@string/controlcenter_find_device"
+                    android:tooltipText="@string/controlcenter_find_device"
+                    android:focusable="true"
+                    android:padding="3dp"
+                    android:scaleType="fitXY"
+                    card_view:srcCompat="@drawable/ic_action_find_lost_device"
+                    card_view:tint="@color/secondarytext" />
+
+                <ImageView
+                    android:id="@+id/device_action_calibrate"
+                    android:layout_width="40dp"
+                    android:layout_height="40dp"
+                    android:layout_below="@id/device_image"
+                    android:layout_margin="3dp"
+                    android:layout_toEndOf="@id/device_action_find"
+                    android:clickable="true"
+                    android:contentDescription="@string/controlcenter_calibrate_device"
+                    android:tooltipText="@string/controlcenter_calibrate_device"
+                    android:focusable="true"
+                    android:padding="3dp"
+                    android:scaleType="fitXY"
+                    card_view:srcCompat="@drawable/ic_activity_unknown"
+                    card_view:tint="@color/secondarytext" />
+
+                <LinearLayout
+                    android:id="@+id/device_heart_rate_status_box"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:contentDescription="@string/controlcenter_get_heartrate_measurement"
+                    android:tooltipText="@string/controlcenter_get_heartrate_measurement"
+                    android:layout_alignParentStart="true"
+                    android:layout_margin="3dp"
+                    android:orientation="vertical">
+
+                    <ImageView
+                        android:id="@+id/device_heart_rate_status"
+                        android:layout_width="40dp"
+                        android:layout_height="40dp"
+                        android:padding="3dp"
+                        android:scaleType="fitXY"
+                        card_view:srcCompat="@drawable/ic_heart"
+                        card_view:tint="@color/secondarytext" />
+
+                    <TextView
+                        android:id="@+id/heart_rate_status"
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:gravity="center_horizontal"
+                        android:minWidth="36dp"
+                        android:textColor="@color/secondarytext"
+                        android:textStyle="bold"
+                        tools:text="100%" />
+
+                </LinearLayout>
+
+                <ImageView
+                    android:id="@+id/device_action_power_off"
+                    android:layout_width="40dp"
+                    android:layout_height="40dp"
+                    android:layout_margin="3dp"
+                    android:layout_toEndOf="@id/device_heart_rate_status_box"
+                    android:background="?android:attr/selectableItemBackground"
+                    android:clickable="true"
+                    android:contentDescription="@string/controlcenter_power_off"
+                    android:tooltipText="@string/controlcenter_power_off"
+                    android:focusable="true"
+                    android:padding="3dp"
+                    android:scaleType="fitXY"
+                    card_view:srcCompat="@drawable/ic_power_settings_new"
+                    card_view:tint="@color/secondarytext" />
+
+            </com.google.android.flexbox.FlexboxLayout>
+
+            <LinearLayout
+                android:id="@+id/card_view_activity_card_layout"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_below="@id/device_info_icons"
+                android:layout_marginBottom="10dp"
+                android:orientation="vertical">
+
+                <LinearLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:gravity="center"
+                    android:orientation="horizontal">
+
+                    <com.github.mikephil.charting.charts.PieChart
+                        android:id="@+id/activity_dashboard_piechart1"
+                        android:layout_width="110dp"
+                        android:layout_height="110dp"
+                        android:layout_gravity="bottom|center"
+                        android:layout_marginBottom="0dp"
+                        android:contentDescription="@string/live_activity_total_steps"
+                        android:tooltipText="@string/live_activity_total_steps"
+                        android:layout_weight="1" />
+
+                    <com.github.mikephil.charting.charts.PieChart
+                        android:id="@+id/activity_dashboard_piechart2"
+                        android:layout_width="110dp"
+                        android:layout_height="110dp"
+                        android:layout_gravity="bottom|center"
+                        android:contentDescription="@string/activity_list_summary_distance"
+                        android:tooltipText="@string/activity_list_summary_distance"
+                        android:layout_marginBottom="0dp"
+                        android:layout_weight="1" />
+
+                    <com.github.mikephil.charting.charts.PieChart
+                        android:id="@+id/activity_dashboard_piechart3"
+                        android:layout_width="110dp"
+                        android:layout_height="110dp"
+                        android:layout_gravity="bottom|center"
+                        android:contentDescription="@string/sleepchart_your_sleep"
+                        android:tooltipText="@string/sleepchart_your_sleep"
+                        android:layout_marginBottom="0dp"
+                        android:layout_weight="1" />
+                </LinearLayout>
+            </LinearLayout>
+        </RelativeLayout>
+
+    </com.google.android.material.card.MaterialCardView>
+
+
+</androidx.coordinatorlayout.widget.CoordinatorLayout>
diff --git a/app/src/main/res/layout/dialog_frequency_picker.xml b/app/src/main/res/layout/dialog_frequency_picker.xml
new file mode 100644
index 0000000..bde348e
--- /dev/null
+++ b/app/src/main/res/layout/dialog_frequency_picker.xml
@@ -0,0 +1,79 @@
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+
+    <LinearLayout
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:gravity="center">
+
+        <NumberPicker
+            android:id="@+id/frequency_dec"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center" />
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:padding="5dp"
+            android:text="."
+            android:textSize="20sp" />
+
+        <NumberPicker
+            android:id="@+id/frequency_fraction"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center" />
+
+    </LinearLayout>
+
+    <TextView
+        android:id="@+id/textView6"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="1"
+        android:gravity="center_horizontal"
+        android:text="@string/prefs_fm_presets_presets" />
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal">
+
+        <Button
+            android:id="@+id/frequency_preset1"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginHorizontal="8dp"
+            android:layout_weight="1"
+            android:text="" />
+
+        <Button
+            android:id="@+id/frequency_preset2"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:text="" />
+
+        <Button
+            android:id="@+id/frequency_preset3"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginHorizontal="8dp"
+            android:layout_weight="1"
+            android:text="" />
+
+    </LinearLayout>
+
+    <TextView
+        android:id="@+id/textView5"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="1"
+        android:gravity="center_horizontal"
+        android:text="@string/prefs_fm_preset_instructions" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/fossil_hr_row_commute_action.xml b/app/src/main/res/layout/fossil_hr_row_commute_action.xml
new file mode 100644
index 0000000..475ecf5
--- /dev/null
+++ b/app/src/main/res/layout/fossil_hr_row_commute_action.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="horizontal"
+    android:padding="5dp">
+
+    <ImageView
+        android:id="@+id/drag_handle"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentStart="true"
+        android:layout_centerVertical="true"
+        app:srcCompat="@drawable/ic_drag_handle" />
+
+    <TextView
+        android:id="@+id/fossil_hr_row_commute_action"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="10dp"
+        android:layout_toEndOf="@id/drag_handle"
+        android:textSize="25sp" />
+
+</RelativeLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/fossil_hr_row_installed_app.xml b/app/src/main/res/layout/fossil_hr_row_installed_app.xml
new file mode 100644
index 0000000..1992ce2
--- /dev/null
+++ b/app/src/main/res/layout/fossil_hr_row_installed_app.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textSize="25dp"
+        android:id="@+id/fossil_hr_row_app_name"
+        android:padding="5dp"
+        android:layout_alignParentStart="true"/>
+
+    <ImageView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:src="@android:drawable/ic_menu_delete"
+        android:layout_alignParentEnd="true" />
+
+</RelativeLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_body_energy.xml b/app/src/main/res/layout/fragment_body_energy.xml
new file mode 100644
index 0000000..54cdcd0
--- /dev/null
+++ b/app/src/main/res/layout/fragment_body_energy.xml
@@ -0,0 +1,92 @@
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context="nodomain.freeyourgadget.gadgetbridge.activities.BodyEnergy">
+
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:orientation="vertical">
+
+        <TextView
+            android:id="@+id/body_energy_date_view"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="15dp"
+            android:layout_marginBottom="20dp"
+            android:gravity="center"
+            android:textSize="20sp" />
+
+        <ImageView
+            android:id="@+id/body_energy_gauge"
+            android:layout_width="180dp"
+            android:layout_height="180dp"
+            android:layout_centerHorizontal="true"
+            android:layout_gravity="center"
+            android:scaleType="fitStart" />
+
+
+        <GridLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="15dp"
+            android:layout_marginBottom="15dp"
+            android:background="@color/gauge_line_color"
+            android:columnCount="2">
+
+            <LinearLayout
+                style="@style/GridTile"
+                android:layout_marginTop="2dp"
+                android:layout_marginEnd="1dp">
+
+                <TextView
+                    android:id="@+id/body_energy_gained"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="+ 0"
+                    android:textSize="20sp" />
+
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/body_energy_gained"
+                    android:textSize="12sp" />
+            </LinearLayout>
+
+            <LinearLayout
+                style="@style/GridTile"
+                android:layout_marginTop="2dp"
+                android:layout_marginStart="1dp">
+
+                <TextView
+                    android:id="@+id/body_energy_lost"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="- 0"
+                    android:textSize="20sp" />
+
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/body_energy_lost"
+                    android:textSize="12sp" />
+            </LinearLayout>
+
+        </GridLayout>
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="250sp">
+
+            <com.github.mikephil.charting.charts.LineChart
+                android:id="@+id/body_energy_chart"
+                android:layout_width="fill_parent"
+                android:layout_height="fill_parent"
+                android:layout_weight="2" />
+        </LinearLayout>
+
+    </LinearLayout>
+
+</ScrollView>
diff --git a/app/src/main/res/layout/fragment_calories.xml b/app/src/main/res/layout/fragment_calories.xml
new file mode 100644
index 0000000..20f15ca
--- /dev/null
+++ b/app/src/main/res/layout/fragment_calories.xml
@@ -0,0 +1,102 @@
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical">
+
+        <TextView
+            android:id="@+id/date_view"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="15dp"
+            android:layout_marginBottom="20dp"
+            android:gravity="center"
+            android:textSize="20sp" />
+
+        <ImageView
+            android:id="@+id/calories_gauge"
+            android:layout_width="180dp"
+            android:layout_height="180dp"
+            android:layout_gravity="center"
+            android:scaleType="fitStart" />
+
+        <GridLayout
+            android:id="@+id/calories_types_wrapper"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="15dp"
+            android:background="@color/gauge_line_color"
+            android:columnCount="2">
+
+            <LinearLayout
+                android:id="@+id/calories_active_wrapper"
+                style="@style/GridTile"
+                android:layout_marginTop="2dp"
+                android:layout_marginEnd="1dp">
+
+                <TextView
+                    android:id="@+id/calories_active"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/stats_empty_value"
+                    android:textSize="20sp" />
+
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/active_calories_short"
+                    android:textSize="12sp" />
+            </LinearLayout>
+
+            <LinearLayout
+                android:id="@+id/calories_resting_wrapper"
+                style="@style/GridTile"
+                android:layout_marginStart="1dp"
+                android:layout_marginTop="2dp">
+
+                <TextView
+                    android:id="@+id/calories_resting"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/stats_empty_value"
+                    android:textSize="20sp" />
+
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/hr_resting"
+                    android:textSize="12sp" />
+            </LinearLayout>
+
+            <LinearLayout
+                android:id="@+id/calories_active_goal_wrapper"
+                style="@style/GridTile"
+                android:layout_marginEnd="1dp">
+
+                <TextView
+                    android:id="@+id/calories_active_goal"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/stats_empty_value"
+                    android:textSize="20sp" />
+
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/active_calories_goal"
+                    android:textSize="12sp" />
+            </LinearLayout>
+
+            <!-- empty tile -->
+            <LinearLayout
+                style="@style/GridTile"
+                android:layout_marginStart="1dp" />
+        </GridLayout>
+    </LinearLayout>
+
+</ScrollView>
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_charts.xml b/app/src/main/res/layout/fragment_charts.xml
new file mode 100644
index 0000000..f715e4b
--- /dev/null
+++ b/app/src/main/res/layout/fragment_charts.xml
@@ -0,0 +1,13 @@
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context="nodomain.freeyourgadget.gadgetbridge.activities.charts.ActivityChartsActivity$PlaceholderFragment">
+
+    <com.github.mikephil.charting.charts.LineChart
+        android:id="@+id/activitysleepchart"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_marginTop="@dimen/activity_vertical_margin" />
+
+</RelativeLayout>
diff --git a/app/src/main/res/layout/fragment_cycling.xml b/app/src/main/res/layout/fragment_cycling.xml
new file mode 100644
index 0000000..9eaf93c
--- /dev/null
+++ b/app/src/main/res/layout/fragment_cycling.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.gridlayout.widget.GridLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:grid="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    grid:columnCount="2">
+
+    <com.github.mikephil.charting.charts.LineChart
+        android:id="@+id/chart_cycling_history"
+        grid:layout_columnSpan="2"
+        grid:layout_columnWeight="1"
+        grid:layout_rowWeight="1">
+    </com.github.mikephil.charting.charts.LineChart>
+
+
+</androidx.gridlayout.widget.GridLayout>
diff --git a/app/src/main/res/layout/fragment_dashboard.xml b/app/src/main/res/layout/fragment_dashboard.xml
new file mode 100644
index 0000000..200cb45
--- /dev/null
+++ b/app/src/main/res/layout/fragment_dashboard.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    tools:context=".activities.DashboardFragment">
+
+    <RelativeLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_margin="8dp"
+        android:orientation="horizontal">
+        <Button
+            android:id="@+id/arrow_left"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:minWidth="0dp"
+            android:text="\u003C"
+            android:textStyle="bold"
+            android:textSize="20sp"
+            android:layout_alignParentLeft="true" />
+        <TextView
+            android:id="@+id/dashboard_date"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/activity_summary_today"
+            android:textStyle="bold"
+            android:textSize="25sp"
+            android:layout_centerInParent="true"/>
+        <Button
+            android:id="@+id/arrow_right"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:minWidth="0dp"
+            android:text="\u003E"
+            android:textStyle="bold"
+            android:textSize="20sp"
+            android:layout_alignParentRight="true" />
+    </RelativeLayout>
+
+    <androidx.core.widget.NestedScrollView
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+
+        <androidx.gridlayout.widget.GridLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:id="@+id/dashboard_gridlayout"
+            app:columnCount="2" />
+    </androidx.core.widget.NestedScrollView>
+</LinearLayout>
diff --git a/app/src/main/res/layout/fragment_devices.xml b/app/src/main/res/layout/fragment_devices.xml
new file mode 100644
index 0000000..286bee7
--- /dev/null
+++ b/app/src/main/res/layout/fragment_devices.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/content_main"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    app:layout_behavior="@string/appbar_scrolling_view_behavior"
+    tools:context=".activities.DevicesFragment">
+
+    <ImageView
+        android:id="@+id/no_items_bg"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_centerInParent="true"
+        android:alpha="0.1"
+        app:srcCompat="@drawable/gadgetbridge_img" />
+
+    <androidx.recyclerview.widget.RecyclerView
+        android:id="@+id/deviceListView"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_alignParentTop="true"
+        android:layout_centerHorizontal="true"
+        android:divider="@null" />
+
+    <com.google.android.material.floatingactionbutton.FloatingActionButton
+        android:id="@+id/fab"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentBottom="true"
+        android:layout_alignParentEnd="true"
+        app:srcCompat="@drawable/ic_add"
+        android:layout_margin="16dp" />
+
+</RelativeLayout>
diff --git a/app/src/main/res/layout/fragment_gps.xml b/app/src/main/res/layout/fragment_gps.xml
new file mode 100644
index 0000000..40109ee
--- /dev/null
+++ b/app/src/main/res/layout/fragment_gps.xml
@@ -0,0 +1,28 @@
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context="nodomain.freeyourgadget.gadgetbridge.activities.charts.ActivityChartsActivity$PlaceholderFragment">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:orientation="vertical">
+
+        <TextView
+            android:id="@+id/textView4"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:fontFamily="sans-serif-black"
+            android:gravity="center"
+            android:text="@string/gps_track"
+            android:textSize="16sp" />
+
+        <ImageView
+            android:id="@+id/activitygpsview"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            tools:src="@tools:sample/avatars" />
+    </LinearLayout>
+
+</RelativeLayout>
diff --git a/app/src/main/res/layout/fragment_heart_rate.xml b/app/src/main/res/layout/fragment_heart_rate.xml
new file mode 100644
index 0000000..b46910e
--- /dev/null
+++ b/app/src/main/res/layout/fragment_heart_rate.xml
@@ -0,0 +1,132 @@
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical">
+
+        <TextView
+            android:id="@+id/hr_date_view"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="15dp"
+            android:layout_marginBottom="20dp"
+            android:gravity="center"
+            android:textSize="20sp" />
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="300sp">
+
+            <com.github.mikephil.charting.charts.LineChart
+                android:id="@+id/heart_rate_line_chart"
+                android:layout_width="fill_parent"
+                android:layout_height="fill_parent"
+                android:layout_weight="2" />
+        </LinearLayout>
+
+        <GridLayout
+            android:background="@color/gauge_line_color"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:columnCount="2"
+            android:layout_marginTop="15dp"
+            >
+            <LinearLayout
+                style="@style/GridTile"
+                android:layout_marginEnd="1dp"
+                android:layout_marginTop="2dp"
+                >
+                <TextView
+                    android:id="@+id/hr_minimum"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/stats_empty_value"
+                    android:textSize="20sp" />
+
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/hr_minimum"
+                    android:textSize="12sp" />
+            </LinearLayout>
+
+
+            <LinearLayout
+                style="@style/GridTile"
+                android:layout_marginStart="1dp"
+                android:layout_marginTop="2dp"
+                >
+                <TextView
+                    android:id="@+id/hr_maximum"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/stats_empty_value"
+                    android:textSize="20sp" />
+
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/hr_maximum"
+                    android:textSize="12sp" />
+            </LinearLayout>
+
+            <LinearLayout
+                style="@style/GridTile"
+                android:layout_marginEnd="1dp"
+                >
+                <TextView
+                    android:id="@+id/hr_average"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/stats_empty_value"
+                    android:textSize="20sp" />
+
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/hr_average"
+                    android:textSize="12sp" />
+            </LinearLayout>
+            <LinearLayout
+                style="@style/GridTile"
+                android:layout_marginStart="1dp"
+                >
+                <LinearLayout
+                    android:id="@+id/hr_resting_wrapper"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content"
+                    android:gravity="center"
+                    android:orientation="vertical"
+                    >
+                    <TextView
+                        android:id="@+id/hr_resting"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:text="@string/stats_empty_value"
+                        android:textSize="20sp" />
+
+                    <TextView
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:text="@string/hr_resting"
+                        android:textSize="12sp" />
+                </LinearLayout>
+            </LinearLayout>
+        </GridLayout>
+
+        <TextView
+            android:id="@+id/steps_chart_title"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="left"
+            android:paddingLeft="20dip"
+            android:text=""
+            android:textSize="20sp" />
+    </LinearLayout>
+
+</ScrollView>
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_hrv_status.xml b/app/src/main/res/layout/fragment_hrv_status.xml
new file mode 100644
index 0000000..225f2c6
--- /dev/null
+++ b/app/src/main/res/layout/fragment_hrv_status.xml
@@ -0,0 +1,183 @@
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context="nodomain.freeyourgadget.gadgetbridge.activities.HRVStatus$PlaceholderFragment">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical">
+
+        <TextView
+            android:id="@+id/hrv_status_date_view"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="center"
+            android:textSize="20sp"
+            android:layout_marginTop="15dp"
+            />
+
+        <RelativeLayout
+            android:id="@+id/hrv_gauge"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_margin="8dp"
+            android:orientation="vertical"
+            >
+
+            <ImageView
+                android:id="@+id/hrv_status_gauge_bar"
+                android:layout_width="150dp"
+                android:layout_height="75dp"
+                android:layout_centerHorizontal="true"
+                android:scaleType="fitStart" />
+
+            <TextView
+                android:id="@+id/hrv_gauge_value"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_centerHorizontal="true"
+                android:layout_marginTop="28dp"
+                android:text="@string/stats_empty_value"
+                android:textSize="30sp" />
+
+            <TextView
+                android:id="@+id/hrv_gauge_status"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_below="@+id/hrv_gauge_value"
+                android:layout_centerHorizontal="true"
+                android:text="" />
+
+        </RelativeLayout>
+
+        <GridLayout
+            android:background="@color/gauge_line_color"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:columnCount="2"
+            android:layout_marginTop="5dp"
+            >
+            <LinearLayout
+                style="@style/GridTile"
+                android:layout_marginEnd="1dp"
+                android:layout_marginTop="2dp"
+                >
+                <TextView
+                    android:id="@+id/hrv_status_seven_days_avg"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="-"
+                    android:textSize="20sp" />
+
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/hrv_status_seven_days_avg"
+                    android:textSize="12sp" />
+            </LinearLayout>
+            <LinearLayout
+                style="@style/GridTile"
+                android:layout_marginStart="1dp"
+                android:layout_marginTop="2dp"
+                >
+                <TextView
+                    android:id="@+id/hrv_status_seven_days_avg_rate"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="-"
+                    android:textSize="20sp" />
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/hrv_status_seven_days_avg_status"
+                    android:textSize="12sp" />
+            </LinearLayout>
+            <LinearLayout
+                android:id="@+id/hrv_status_day_avg_wrapper"
+                style="@style/GridTile"
+                android:layout_marginEnd="1dp"
+                >
+                <TextView
+                    android:id="@+id/hrv_status_day_avg"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="-"
+                    android:textSize="20sp" />
+
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/hrv_status_day_avg"
+                    android:textSize="12sp" />
+            </LinearLayout>
+
+            <LinearLayout
+                android:id="@+id/hrv_status_baseline_wrapper"
+                style="@style/GridTile"
+                android:layout_marginStart="1dp"
+                >
+                <TextView
+                    android:id="@+id/hrv_status_baseline"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="-"
+                    android:textSize="20sp" />
+
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/hrv_status_baseline_label"
+                    android:textSize="12sp" />
+            </LinearLayout>
+            <LinearLayout
+                android:id="@+id/sleep_chart_legend_rem_time_wrapper"
+                style="@style/GridTile"
+                android:layout_marginEnd="1dp"
+                >
+                <TextView
+                    android:id="@+id/hrv_status_last_night"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:textSize="20sp" />
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/hrv_status_last_night"
+                    android:textSize="12sp" />
+            </LinearLayout>
+
+            <LinearLayout
+                android:id="@+id/hrv_status_last_night_highest_5_wrapper"
+                style="@style/GridTile"
+                android:layout_marginStart="1dp"
+                >
+                <TextView
+                    android:id="@+id/hrv_status_last_night_highest_5"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="-"
+                    android:textSize="20sp" />
+
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/hrv_status_last_night_highest_5"
+                    android:textSize="12sp" />
+            </LinearLayout>
+        </GridLayout>
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="250sp">
+
+            <com.github.mikephil.charting.charts.LineChart
+                android:id="@+id/hrv_weekly_line_chart"
+                android:layout_width="fill_parent"
+                android:layout_height="fill_parent"
+                android:layout_weight="2" />
+        </LinearLayout>
+
+    </LinearLayout>
+</ScrollView>
diff --git a/app/src/main/res/layout/fragment_live_activity.xml b/app/src/main/res/layout/fragment_live_activity.xml
new file mode 100644
index 0000000..0a05ff2
--- /dev/null
+++ b/app/src/main/res/layout/fragment_live_activity.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.gridlayout.widget.GridLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:grid="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    grid:columnCount="2">
+
+    <nodomain.freeyourgadget.gadgetbridge.activities.charts.CustomBarChart
+        android:id="@+id/livechart_steps_per_minute_current"
+        grid:layout_columnWeight="1"
+        grid:layout_rowWeight="1"></nodomain.freeyourgadget.gadgetbridge.activities.charts.CustomBarChart>
+
+    <nodomain.freeyourgadget.gadgetbridge.activities.charts.CustomBarChart
+        android:id="@+id/livechart_steps_total"
+        grid:layout_columnWeight="1"
+        grid:layout_rowWeight="1"></nodomain.freeyourgadget.gadgetbridge.activities.charts.CustomBarChart>
+
+    <!--
+        <com.github.mikephil.charting.charts.PieChart
+            android:id="@+id/livechart_steps_total"
+            android:layout_width="fill_parent"
+            android:layout_height="fill_parent"
+            android:layout_weight="20">
+        </com.github.mikephil.charting.charts.PieChart>
+
+        <com.github.mikephil.charting.charts.PieChart
+            android:id="@+id/livechart_steps_per_minute_current"
+            android:layout_width="fill_parent"
+            android:layout_height="fill_parent"
+            android:layout_weight="20">
+        </com.github.mikephil.charting.charts.PieChart>
+-->
+    <TextView
+        android:id="@+id/livechart_max_heart_rate"
+        grid:layout_columnSpan="2"
+        grid:layout_columnWeight="1" />
+
+    <com.github.mikephil.charting.charts.LineChart
+        android:id="@+id/livechart_steps_per_minute_history"
+        grid:layout_columnSpan="2"
+        grid:layout_columnWeight="1"
+        grid:layout_rowWeight="1"></com.github.mikephil.charting.charts.LineChart>
+
+
+</androidx.gridlayout.widget.GridLayout>
diff --git a/app/src/main/res/layout/fragment_nested_tabs.xml b/app/src/main/res/layout/fragment_nested_tabs.xml
new file mode 100644
index 0000000..a735b8b
--- /dev/null
+++ b/app/src/main/res/layout/fragment_nested_tabs.xml
@@ -0,0 +1,17 @@
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+
+    <com.google.android.material.tabs.TabLayout
+        android:id="@+id/tab_layout"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content" />
+
+    <androidx.viewpager2.widget.ViewPager2
+        android:id="@+id/pager"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="1" />
+
+</LinearLayout>
diff --git a/app/src/main/res/layout/fragment_pai_chart.xml b/app/src/main/res/layout/fragment_pai_chart.xml
new file mode 100644
index 0000000..7b55cce
--- /dev/null
+++ b/app/src/main/res/layout/fragment_pai_chart.xml
@@ -0,0 +1,192 @@
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical"
+        tools:context="nodomain.freeyourgadget.gadgetbridge.activities.charts.ActivityChartsActivity$PlaceholderFragment"
+        tools:ignore="UselessParent">
+
+        <TextView
+            android:id="@+id/pai_date_view"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="15dp"
+            android:layout_marginBottom="10dp"
+            android:gravity="center"
+            android:textSize="20sp" />
+
+        <ImageView
+            android:layout_weight="2"
+            android:layout_marginTop="15dp"
+            android:layout_marginBottom="10dp"
+            android:id="@+id/goal_minutes_gauge"
+            android:layout_width="180dp"
+            android:layout_height="180dp"
+            android:layout_gravity="center"
+            android:scaleType="fitStart" />
+
+        <GridLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:background="@color/gauge_line_color"
+            android:columnCount="2">
+
+            <LinearLayout
+                style="@style/GridTile"
+                android:layout_marginBottom="0dp">
+
+                <View
+                    android:layout_width="fill_parent"
+                    android:layout_height="5dp"
+                    android:layout_marginBottom="10dp"
+                    android:background="@color/chart_pai_today" />
+
+                <TextView
+                    android:id="@+id/pai_line_today"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/stats_empty_value"
+                    android:textSize="20sp" />
+
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/activity_summary_today"
+                    android:textSize="12sp" />
+            </LinearLayout>
+
+            <LinearLayout
+                style="@style/GridTile"
+                android:layout_marginBottom="0dp">
+
+                <View
+                    android:layout_width="fill_parent"
+                    android:layout_height="5dp"
+                    android:layout_marginBottom="10dp"
+                    android:background="@color/chart_pai_weekly" />
+
+                <TextView
+                    android:id="@+id/pai_line_total"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/stats_empty_value"
+                    android:textSize="20sp" />
+
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/weekly_total"
+                    android:textSize="12sp" />
+            </LinearLayout>
+
+        </GridLayout>
+
+        <GridLayout
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_marginBottom="15dp"
+            android:background="@color/gauge_line_color"
+            android:columnCount="3"
+            android:rowCount="1">
+
+            <LinearLayout
+                style="@style/GridTile"
+                android:id="@+id/pai_tile_low"
+                android:layout_marginTop="2dp"
+                android:layout_marginEnd="1dp">
+
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/sony_speak_to_chat_sensitivity_low"
+                    android:textSize="12sp" />
+
+                <TextView
+                    android:id="@+id/pai_line_low_inc"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:fontFamily="sans-serif-black"
+                    android:text="@string/stats_empty_value"
+                    android:textSize="20sp" />
+
+                <TextView
+                    android:id="@+id/pai_line_low_time"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/stats_empty_value"
+                    android:textSize="12sp" />
+            </LinearLayout>
+
+            <LinearLayout
+                style="@style/GridTile"
+                android:id="@+id/pai_tile_moderate"
+                android:layout_marginStart="1dp"
+                android:layout_marginTop="2dp"
+                android:layout_marginEnd="1dp">
+
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/stress_moderate"
+                    android:textSize="12sp" />
+
+                <TextView
+                    android:id="@+id/pai_line_moderate_inc"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:fontFamily="sans-serif-black"
+                    android:text="@string/stats_empty_value"
+                    android:textSize="20sp" />
+
+                <TextView
+                    android:id="@+id/pai_line_moderate_time"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/stats_empty_value"
+                    android:textSize="12sp" />
+            </LinearLayout>
+
+            <LinearLayout
+                style="@style/GridTile"
+                android:id="@+id/pai_tile_high"
+                android:layout_marginStart="1dp"
+                android:layout_marginTop="2dp"
+                android:layout_marginBottom="2dp">
+
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/stress_high"
+                    android:textSize="12sp" />
+
+                <TextView
+                    android:id="@+id/pai_line_high_inc"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:fontFamily="sans-serif-black"
+                    android:text="@string/stats_empty_value"
+                    android:textSize="20sp" />
+
+                <TextView
+                    android:id="@+id/pai_line_high_time"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/stats_empty_value"
+                    android:textSize="12sp" />
+            </LinearLayout>
+        </GridLayout>
+
+        <com.github.mikephil.charting.charts.BarChart
+            android:id="@+id/pai_chart_week"
+            android:layout_width="fill_parent"
+            android:layout_height="300dp"
+            android:layout_marginBottom="25dp"
+            android:layout_weight="2" />
+
+    </LinearLayout>
+
+</ScrollView>
diff --git a/app/src/main/res/layout/fragment_respiratory_rate.xml b/app/src/main/res/layout/fragment_respiratory_rate.xml
new file mode 100644
index 0000000..5b25e5a
--- /dev/null
+++ b/app/src/main/res/layout/fragment_respiratory_rate.xml
@@ -0,0 +1,114 @@
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical">
+
+        <TextView
+            android:id="@+id/rr_date_view"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="15dp"
+            android:layout_marginBottom="20dp"
+            android:gravity="center"
+            android:textSize="20sp" />
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="300sp">
+
+            <com.github.mikephil.charting.charts.LineChart
+                android:id="@+id/respiratory_rate_line_chart"
+                android:layout_width="fill_parent"
+                android:layout_height="fill_parent"
+                android:layout_weight="2" />
+        </LinearLayout>
+
+        <GridLayout
+            android:background="@color/gauge_line_color"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:columnCount="2"
+            android:layout_marginTop="15dp"
+            >
+            <LinearLayout
+                style="@style/GridTile"
+                android:layout_marginEnd="1dp"
+                android:layout_marginTop="2dp"
+                >
+                <TextView
+                    android:id="@+id/awake_avg"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/stats_empty_value"
+                    android:textSize="20sp" />
+
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/sleep_colored_stats_awake_avg"
+                    android:textSize="12sp" />
+            </LinearLayout>
+
+
+            <LinearLayout
+                style="@style/GridTile"
+                android:layout_marginStart="1dp"
+                android:layout_marginTop="2dp"
+                >
+                <TextView
+                    android:id="@+id/sleep_avg"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/stats_empty_value"
+                    android:textSize="20sp" />
+
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/sleep_avg"
+                    android:textSize="12sp" />
+            </LinearLayout>
+            <LinearLayout
+                style="@style/GridTile"
+                android:layout_marginStart="1dp"
+                >
+                <TextView
+                    android:id="@+id/day_lowest"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/stats_empty_value"
+                    android:textSize="20sp" />
+
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/lowest"
+                    android:textSize="12sp" />
+            </LinearLayout>
+            <LinearLayout
+                style="@style/GridTile"
+                android:layout_marginStart="1dp"
+                >
+                <TextView
+                    android:id="@+id/day_highest"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/stats_empty_value"
+                    android:textSize="20sp" />
+
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/highest"
+                    android:textSize="12sp" />
+            </LinearLayout>
+        </GridLayout>
+    </LinearLayout>
+
+</ScrollView>
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_respiratory_rate_period.xml b/app/src/main/res/layout/fragment_respiratory_rate_period.xml
new file mode 100644
index 0000000..a085d28
--- /dev/null
+++ b/app/src/main/res/layout/fragment_respiratory_rate_period.xml
@@ -0,0 +1,78 @@
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical">
+
+        <TextView
+            android:id="@+id/rr_date_view"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="15dp"
+            android:layout_marginBottom="20dp"
+            android:gravity="center"
+            android:textSize="20sp" />
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="300sp">
+
+            <com.github.mikephil.charting.charts.LineChart
+                android:id="@+id/respiratory_rate_line_chart"
+                android:layout_width="fill_parent"
+                android:layout_height="fill_parent"
+                android:layout_weight="2" />
+        </LinearLayout>
+
+        <GridLayout
+            android:background="@color/gauge_line_color"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:columnCount="2"
+            android:layout_marginTop="15dp"
+            >
+            <LinearLayout
+                style="@style/GridTile"
+                android:layout_marginEnd="1dp"
+                android:layout_marginTop="2dp"
+                >
+                <TextView
+                    android:id="@+id/awake_avg"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/stats_empty_value"
+                    android:textSize="20sp" />
+
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/sleep_colored_stats_awake_avg"
+                    android:textSize="12sp" />
+            </LinearLayout>
+            <LinearLayout
+                style="@style/GridTile"
+                android:layout_marginStart="1dp"
+                android:layout_marginTop="2dp"
+                >
+                <TextView
+                    android:id="@+id/sleep_avg"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/stats_empty_value"
+                    android:textSize="20sp" />
+
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/sleep_avg"
+                    android:textSize="12sp" />
+            </LinearLayout>
+        </GridLayout>
+    </LinearLayout>
+
+</ScrollView>
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_sleepchart.xml b/app/src/main/res/layout/fragment_sleepchart.xml
new file mode 100644
index 0000000..cfd4020
--- /dev/null
+++ b/app/src/main/res/layout/fragment_sleepchart.xml
@@ -0,0 +1,250 @@
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical">
+
+        <TextView
+            android:id="@+id/sleep_date"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="15dp"
+            android:gravity="center"
+            android:textSize="20sp" />
+
+        <TextView
+            android:id="@+id/sleepchart_info"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="center"
+            android:text="@string/stats_empty_value"
+            android:textSize="12sp" />
+
+        <ImageView
+            android:layout_weight="2"
+            android:layout_marginTop="15dp"
+            android:layout_marginBottom="10dp"
+            android:id="@+id/sleep_stages_gauge"
+            android:layout_width="180dp"
+            android:layout_height="180dp"
+            android:layout_gravity="center"
+            android:scaleType="fitStart" />
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content">
+
+            <GridLayout
+                android:background="@color/gauge_line_color"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:columnCount="2"
+                >
+                <LinearLayout
+                    style="@style/GridTile"
+                    android:layout_marginBottom="0dp"
+                    >
+                    <View
+                        android:layout_width="fill_parent"
+                        android:layout_height="5dp"
+                        android:background="@color/chart_deep_sleep_dark"
+                        android:layout_marginBottom="10dp"
+                        />
+                    <TextView
+                        android:id="@+id/sleep_chart_legend_deep_time"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:text="@string/stats_empty_value"
+                        android:textSize="20sp" />
+
+                    <TextView
+                        android:id="@+id/sleep_chart_legend_deep_legend"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:text="@string/sleep_colored_stats_deep"
+                        android:textSize="12sp" />
+                </LinearLayout>
+                <LinearLayout
+                    style="@style/GridTile"
+                    android:layout_marginBottom="0dp"
+                    >
+                    <View
+                        android:layout_width="fill_parent"
+                        android:layout_height="5dp"
+                        android:background="@color/chart_light_sleep_dark"
+                        android:layout_marginBottom="10dp"
+                        />
+
+                    <TextView
+                        android:id="@+id/sleep_chart_legend_light_time"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:text="@string/stats_empty_value"
+                        android:textSize="20sp" />
+
+                    <TextView
+                        android:id="@+id/sleep_chart_legend_light_legend"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:text="@string/sleep_colored_stats_light"
+                        android:textSize="12sp" />
+                </LinearLayout>
+                <LinearLayout
+                    style="@style/GridTile"
+                    android:layout_height="fill_parent"
+                    android:layout_marginBottom="0dp"
+                    >
+                    <LinearLayout
+                        android:id="@+id/sleep_chart_legend_rem_time_wrapper"
+                        android:layout_width="fill_parent"
+                        android:layout_height="wrap_content"
+                        android:gravity="center"
+                        android:orientation="vertical"
+                        >
+                        <View
+                            android:layout_width="fill_parent"
+                            android:layout_height="5dp"
+                            android:background="@color/chart_rem_sleep_dark"
+                            android:layout_marginBottom="10dp"
+                            />
+
+                        <TextView
+                            android:id="@+id/sleep_chart_legend_rem_time"
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content"
+                            android:gravity="center"
+                            android:text="@string/stats_empty_value"
+                            android:textSize="20sp" />
+
+                        <TextView
+                            android:id="@+id/sleep_chart_legend_rem_legend"
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content"
+                            android:gravity="center"
+                            android:text="@string/sleep_colored_stats_rem"
+                            android:textSize="12sp" />
+                    </LinearLayout>
+                </LinearLayout>
+
+                <LinearLayout
+                    style="@style/GridTile"
+                    android:layout_marginBottom="0dp"
+                    android:layout_height="fill_parent"
+                    >
+                    <LinearLayout
+                        android:id="@+id/sleep_chart_legend_awake_time_wrapper"
+                        android:layout_width="fill_parent"
+                        android:layout_height="wrap_content"
+                        android:gravity="center"
+                        android:orientation="vertical"
+                        >
+                        <View
+                            android:layout_width="fill_parent"
+                            android:layout_height="5dp"
+                            android:background="@color/chart_awake_sleep_dark"
+                            android:layout_marginBottom="10dp"
+                            />
+                        <TextView
+                            android:id="@+id/sleep_chart_legend_awake_time"
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content"
+                            android:text="@string/stats_empty_value"
+                            android:textSize="20sp" />
+
+                        <TextView
+                            android:id="@+id/sleep_chart_legend_awake_legend"
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content"
+                            android:text="@string/abstract_chart_fragment_kind_awake_sleep"
+                            android:textSize="12sp" />
+                    </LinearLayout>
+                </LinearLayout>
+            </GridLayout>
+        </LinearLayout>
+
+        <com.github.mikephil.charting.charts.LineChart
+            android:id="@+id/sleepchart"
+            android:layout_width="fill_parent"
+            android:layout_height="300dp"
+            android:layout_marginTop="15dp"
+            android:layout_marginBottom="25dp"
+            android:layout_weight="4" />
+
+        <GridLayout
+            android:background="@color/gauge_line_color"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:columnCount="2"
+            android:rowCount="2"
+            android:layout_marginBottom="15dp"
+            >
+            <LinearLayout
+                android:layout_marginTop="2dp"
+                android:layout_marginEnd="1dp"
+                style="@style/GridTile"
+                >
+                <TextView
+                    android:id="@+id/sleep_hr_lowest"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="0"
+                    android:textSize="20sp" />
+
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/stats_lowest_hr"
+                    android:textSize="12sp" />
+            </LinearLayout>
+            <LinearLayout
+                android:layout_marginTop="2dp"
+                android:layout_marginStart="1dp"
+                style="@style/GridTile"
+                >
+                <TextView
+                    android:id="@+id/sleep_hr_highest"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="0"
+                    android:textSize="20sp" />
+
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/stats_highest_hr"
+                    android:textSize="12sp" />
+            </LinearLayout>
+            <LinearLayout
+                android:id="@+id/sleep_chart_legend_movement_intensity_wrapper"
+                android:layout_marginBottom="2dp"
+                android:layout_marginEnd="1dp"
+                style="@style/GridTile"
+                >
+
+                <TextView
+                    android:id="@+id/sleep_movement_intensity"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="0"
+                    android:textSize="20sp" />
+
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/movement_intensity"
+                    android:textSize="12sp" />
+            </LinearLayout>
+            <LinearLayout
+                android:id="@+id/sleep_chart_legend_dummy_tile"
+                android:layout_marginBottom="2dp"
+                android:layout_marginStart="1dp"
+                style="@style/GridTile"
+                />
+        </GridLayout>
+
+
+    </LinearLayout>
+</ScrollView>
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_statschart.xml b/app/src/main/res/layout/fragment_statschart.xml
new file mode 100644
index 0000000..8df152b
--- /dev/null
+++ b/app/src/main/res/layout/fragment_statschart.xml
@@ -0,0 +1,39 @@
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context="nodomain.freeyourgadget.gadgetbridge.activities.charts.ActivityChartsActivity$PlaceholderFragment">
+
+    <com.github.mikephil.charting.charts.HorizontalBarChart
+        android:id="@+id/statschart"
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent"
+        android:layout_weight="20"
+        android:layout_below="@+id/statsXAxisText"
+        android:layout_alignParentStart="true"
+        android:layout_toStartOf="@+id/statsYAxisText"></com.github.mikephil.charting.charts.HorizontalBarChart>
+
+    <TextView
+        android:id="@+id/statsXAxisText"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentEnd="true"
+        android:layout_alignParentStart="true"
+        android:layout_alignParentTop="true"
+        android:paddingLeft="10dp"
+        android:text="@string/stats_x_axis_label" />
+
+    <TextView
+        android:id="@+id/statsYAxisText"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentEnd="true"
+        android:layout_centerVertical="true"
+        android:layout_marginStart="-95dp"
+        android:elegantTextHeight="false"
+        android:rotation="90"
+        android:singleLine="true"
+        android:text="@string/stats_y_axis_label"
+        android:translationX="40dp" />
+
+</RelativeLayout>
diff --git a/app/src/main/res/layout/fragment_steps.xml b/app/src/main/res/layout/fragment_steps.xml
new file mode 100644
index 0000000..f921926
--- /dev/null
+++ b/app/src/main/res/layout/fragment_steps.xml
@@ -0,0 +1,101 @@
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical"
+        >
+
+        <RelativeLayout
+            android:layout_width="match_parent"
+            android:layout_height="match_parent">
+            <TextView
+                android:id="@+id/steps_date_view"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:gravity="center"
+                android:textSize="20sp"
+                android:layout_marginTop="15dp"
+                android:layout_marginBottom="20dp"
+                />
+            <ImageView
+                android:layout_width="50dp"
+                android:layout_height="50dp"
+                android:id="@+id/steps_streaks_button"
+                android:layout_alignParentEnd="true"
+                android:layout_alignParentTop="true"
+                android:layout_marginTop="0dp"
+                android:layout_marginEnd="0dp"
+                android:visibility="visible"
+                app:srcCompat="@drawable/ic_events" />
+        </RelativeLayout>
+
+        <ImageView
+            android:layout_width="180dp"
+            android:layout_height="180dp"
+            android:layout_gravity="center"
+            android:layout_centerHorizontal="true"
+            android:scaleType="fitStart"
+            android:id="@+id/steps_gauge" />
+
+        <GridLayout
+            android:background="@color/gauge_line_color"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:columnCount="2"
+            android:layout_marginTop="15dp"
+            android:layout_marginBottom="15dp"
+            >
+            <LinearLayout
+                style="@style/GridTile"
+                android:layout_marginEnd="1dp"
+                android:layout_marginTop="2dp"
+                >
+                <TextView
+                    android:id="@+id/steps_count"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="0"
+                    android:textSize="20sp" />
+
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/steps"
+                    android:textSize="12sp" />
+            </LinearLayout>
+            <LinearLayout
+                style="@style/GridTile"
+                android:layout_marginStart="1dp"
+                android:layout_marginTop="2dp"
+                >
+                <TextView
+                    android:id="@+id/steps_distance"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="0"
+                    android:textSize="20sp" />
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/distance"
+                    android:textSize="12sp" />
+            </LinearLayout>
+        </GridLayout>
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="250sp">
+            <com.github.mikephil.charting.charts.LineChart
+                android:id="@+id/steps_daily_chart"
+                android:layout_width="0dp"
+                android:layout_height="fill_parent"
+                android:layout_weight="2" />
+        </LinearLayout>
+    </LinearLayout>
+
+</ScrollView>
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_steps_list.xml b/app/src/main/res/layout/fragment_steps_list.xml
new file mode 100644
index 0000000..6d869c5
--- /dev/null
+++ b/app/src/main/res/layout/fragment_steps_list.xml
@@ -0,0 +1,44 @@
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    tools:context="nodomain.freeyourgadget.gadgetbridge.activities.charts.ActivityChartsActivity$PlaceholderFragment">
+
+    <TextView
+        android:id="@+id/stepsDateView"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:textAlignment="center"
+        android:textAllCaps="false"
+        android:textAppearance="@style/TextAppearance.AppCompat.Medium"
+        android:textStyle="bold" />
+
+
+    <androidx.recyclerview.widget.RecyclerView
+        android:id="@+id/itemListView"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_below="@id/stepsDateView"
+        android:layout_alignParentBottom="false"
+        android:layout_marginBottom="0dp"
+        android:layout_marginTop="0dp">
+    </androidx.recyclerview.widget.RecyclerView>
+
+    <com.google.android.material.floatingactionbutton.FloatingActionButton
+        android:id="@+id/fab"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentEnd="true"
+        android:layout_alignParentBottom="true"
+        android:layout_gravity="bottom|end"
+        android:layout_marginStart="16dp"
+        android:layout_marginTop="16dp"
+        android:layout_marginEnd="16dp"
+        android:layout_marginBottom="9dp"
+        app:srcCompat="@drawable/ic_insights" />
+
+
+
+</RelativeLayout>
diff --git a/app/src/main/res/layout/fragment_steps_period.xml b/app/src/main/res/layout/fragment_steps_period.xml
new file mode 100644
index 0000000..9fe156e
--- /dev/null
+++ b/app/src/main/res/layout/fragment_steps_period.xml
@@ -0,0 +1,120 @@
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical"
+        >
+
+        <RelativeLayout
+            android:layout_width="match_parent"
+            android:layout_height="match_parent">
+            <TextView
+                android:id="@+id/steps_date_view"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:gravity="center"
+                android:textSize="20sp"
+                android:layout_marginTop="15dp"
+                android:layout_marginBottom="20dp"
+                />
+        </RelativeLayout>
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="250sp"
+            >
+
+            <com.github.mikephil.charting.charts.BarChart
+                android:id="@+id/steps_chart"
+                android:layout_width="0dp"
+                android:layout_height="fill_parent"
+                android:layout_weight="2" />
+
+        </LinearLayout>
+
+        <GridLayout
+            android:background="@color/gauge_line_color"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:columnCount="2"
+            android:layout_marginTop="15dp"
+            >
+            <LinearLayout
+                style="@style/GridTile"
+                android:layout_marginEnd="1dp"
+                android:layout_marginTop="2dp"
+                >
+                <TextView
+                    android:id="@+id/steps_avg"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="0"
+                    android:textSize="20sp" />
+
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/steps_avg"
+                    android:textSize="12sp" />
+            </LinearLayout>
+            <LinearLayout
+                style="@style/GridTile"
+                android:layout_marginStart="1dp"
+                android:layout_marginTop="2dp"
+                >
+
+                <TextView
+                    android:id="@+id/steps_total"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="0"
+                    android:textSize="20sp" />
+
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/steps_total"
+                    android:textSize="12sp" />
+            </LinearLayout>
+            <LinearLayout
+                style="@style/GridTile"
+                android:layout_marginEnd="1dp"
+                >
+                <TextView
+                    android:id="@+id/distance_avg"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="0"
+                    android:textSize="20sp" />
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/distance_avg"
+                    android:textSize="12sp" />
+            </LinearLayout>
+            <LinearLayout
+                style="@style/GridTile"
+                android:layout_marginStart="1dp"
+                >
+                <TextView
+                    android:id="@+id/distance_total"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="0"
+                    android:textSize="20sp" />
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/distance_total"
+                    android:textSize="12sp" />
+            </LinearLayout>
+        </GridLayout>
+
+    </LinearLayout>
+
+</ScrollView>
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_stresschart.xml b/app/src/main/res/layout/fragment_stresschart.xml
new file mode 100644
index 0000000..28dffee
--- /dev/null
+++ b/app/src/main/res/layout/fragment_stresschart.xml
@@ -0,0 +1,148 @@
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:orientation="vertical">
+
+        <TextView
+            android:id="@+id/stress_date"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="15dp"
+            android:gravity="center"
+            android:textSize="20sp" />
+
+        <com.github.mikephil.charting.charts.PieChart
+            android:id="@+id/stress_pie_chart"
+            android:layout_width="fill_parent"
+            android:layout_height="200dp"
+            android:layout_marginTop="15dp"
+            android:layout_weight="2" />
+
+        <GridLayout
+            android:background="@color/gauge_line_color"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:columnCount="2"
+            android:layout_marginTop="15dp"
+            >
+            <LinearLayout
+                style="@style/GridTile"
+                android:layout_marginBottom="0dp"
+                >
+
+                <View
+                    android:layout_width="fill_parent"
+                    android:layout_height="5dp"
+                    android:background="@color/chart_stress_relaxed"
+                    android:layout_marginBottom="10dp"
+                    />
+
+                <TextView
+                    android:id="@+id/stress_chart_relaxed_time"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/stats_empty_value"
+                    android:textSize="20sp" />
+
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/stress_relaxed"
+                    android:textSize="12sp" />
+            </LinearLayout>
+
+
+            <LinearLayout
+                style="@style/GridTile"
+                android:layout_marginBottom="0dp"
+                >
+
+                <View
+                    android:layout_width="fill_parent"
+                    android:layout_height="5dp"
+                    android:background="@color/chart_stress_mild"
+                    android:layout_marginBottom="10dp"
+                    />
+
+                <TextView
+                    android:id="@+id/stress_chart_mild_time"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/stats_empty_value"
+                    android:textSize="20sp" />
+
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/stress_mild"
+                    android:textSize="12sp" />
+            </LinearLayout>
+            <LinearLayout
+                style="@style/GridTile"
+                android:layout_marginBottom="0dp"
+                >
+
+                <View
+                    android:layout_width="fill_parent"
+                    android:layout_height="5dp"
+                    android:background="@color/chart_stress_moderate"
+                    android:layout_marginBottom="10dp"
+                    />
+
+                <TextView
+                    android:id="@+id/stress_chart_moderate_time"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:gravity="center"
+                    android:text="@string/stats_empty_value"
+                    android:textSize="20sp" />
+
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:gravity="center"
+                    android:text="@string/stress_moderate"
+                    android:textSize="12sp" />
+            </LinearLayout>
+
+            <LinearLayout
+                style="@style/GridTile"
+                android:layout_marginBottom="0dp"
+                >
+
+                <View
+                    android:layout_width="fill_parent"
+                    android:layout_height="5dp"
+                    android:background="@color/chart_stress_high"
+                    android:layout_marginBottom="10dp"
+                    />
+
+                <TextView
+                    android:id="@+id/stress_chart_high_time"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:gravity="center"
+                    android:text="@string/stats_empty_value"
+                    android:textSize="20sp" />
+
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:gravity="center"
+                    android:text="@string/stress_high"
+                    android:textSize="12sp" />
+            </LinearLayout>
+        </GridLayout>
+
+        <com.github.mikephil.charting.charts.LineChart
+            android:id="@+id/stress_line_chart"
+            android:layout_width="fill_parent"
+            android:layout_height="300dp"
+            android:layout_marginTop="15dp"
+            android:layout_marginBottom="25dp"
+            android:layout_weight="2" />
+    </LinearLayout>
+</ScrollView>
diff --git a/app/src/main/res/layout/fragment_temperature.xml b/app/src/main/res/layout/fragment_temperature.xml
new file mode 100644
index 0000000..16e9f4a
--- /dev/null
+++ b/app/src/main/res/layout/fragment_temperature.xml
@@ -0,0 +1,101 @@
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical">
+
+        <TextView
+            android:id="@+id/temp_date_view"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="15dp"
+            android:layout_marginBottom="20dp"
+            android:gravity="center"
+            android:textSize="20sp" />
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="300sp">
+
+            <com.github.mikephil.charting.charts.LineChart
+                android:id="@+id/temp_line_chart"
+                android:layout_width="fill_parent"
+                android:layout_height="fill_parent"
+                android:layout_weight="2" />
+        </LinearLayout>
+
+        <GridLayout
+            android:background="@color/gauge_line_color"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:columnCount="2"
+            android:layout_marginTop="15dp"
+            >
+            <LinearLayout
+                style="@style/GridTile"
+                android:layout_marginEnd="1dp"
+                android:layout_marginTop="2dp"
+                >
+                <TextView
+                    android:id="@+id/temp_minimum"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/stats_empty_value"
+                    android:textSize="20sp" />
+
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/hr_minimum"
+                    android:textSize="12sp" />
+            </LinearLayout>
+
+
+            <LinearLayout
+                style="@style/GridTile"
+                android:layout_marginStart="1dp"
+                android:layout_marginTop="2dp"
+                >
+                <TextView
+                    android:id="@+id/temp_maximum"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/stats_empty_value"
+                    android:textSize="20sp" />
+
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/hr_maximum"
+                    android:textSize="12sp" />
+            </LinearLayout>
+
+            <LinearLayout
+                style="@style/GridTile"
+                android:layout_marginEnd="1dp"
+                >
+                <TextView
+                    android:id="@+id/temp_average"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/stats_empty_value"
+                    android:textSize="20sp" />
+
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/hr_average"
+                    android:textSize="12sp" />
+            </LinearLayout>
+
+            <LinearLayout
+                style="@style/GridTile"
+                android:layout_marginStart="1dp" />
+        </GridLayout>
+
+    </LinearLayout>
+
+</ScrollView>
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_temperaturechart.xml b/app/src/main/res/layout/fragment_temperaturechart.xml
new file mode 100644
index 0000000..d243074
--- /dev/null
+++ b/app/src/main/res/layout/fragment_temperaturechart.xml
@@ -0,0 +1,19 @@
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    tools:context="nodomain.freeyourgadget.gadgetbridge.activities.charts.ActivityChartsActivity$PlaceholderFragment">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:orientation="vertical">
+
+        <com.github.mikephil.charting.charts.LineChart
+            android:id="@+id/temperature_line_chart"
+            android:layout_width="fill_parent"
+            android:layout_height="fill_parent"
+            android:layout_weight="2" />
+    </LinearLayout>
+</RelativeLayout>
diff --git a/app/src/main/res/layout/fragment_vo2max.xml b/app/src/main/res/layout/fragment_vo2max.xml
new file mode 100644
index 0000000..f08b98e
--- /dev/null
+++ b/app/src/main/res/layout/fragment_vo2max.xml
@@ -0,0 +1,122 @@
+   <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context="nodomain.freeyourgadget.gadgetbridge.activities.VO2Max">
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:orientation="vertical">
+        <TextView
+            android:id="@+id/vo2max_date_view"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="center"
+            android:textSize="20sp"
+            android:layout_marginTop="15dp"
+            android:layout_marginBottom="20dp"
+            />
+
+        <GridLayout
+            android:id="@+id/tiles_grid_wrapper"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:columnCount="2"
+            >
+            <RelativeLayout
+                android:id="@+id/vo2max_running_card_layout"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_margin="8dp"
+                android:orientation="vertical"
+                tools:ignore="UselessParent"
+                android:layout_gravity="fill"
+                android:layout_columnWeight="1"
+                >
+
+                <ImageView
+                    android:id="@+id/vo2max_running_gauge"
+                    android:layout_width="150dp"
+                    android:layout_height="75dp"
+                    android:layout_centerHorizontal="true"
+                    android:scaleType="fitStart" />
+
+                <TextView
+                    android:id="@+id/vo2max_running_gauge_value"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_centerHorizontal="true"
+                    android:layout_marginTop="28dp"
+                    android:text="@string/stats_empty_value"
+                    android:textSize="30sp" />
+
+                <TextView
+                    android:id="@+id/vo2max_running_gauge_label"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_below="@+id/vo2max_running_gauge_value"
+                    android:layout_centerHorizontal="true"
+                    android:text="@string/activity_type_running" />
+
+            </RelativeLayout>
+            <RelativeLayout
+                android:id="@+id/vo2max_cycling_card_layout"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_margin="8dp"
+                android:orientation="vertical"
+                tools:ignore="UselessParent"
+                android:layout_gravity="fill"
+                android:layout_columnWeight="1"
+                >
+
+                <ImageView
+                    android:id="@+id/vo2max_cycling_gauge"
+                    android:layout_width="150dp"
+                    android:layout_height="75dp"
+                    android:layout_centerHorizontal="true"
+                    android:scaleType="fitStart" />
+
+                <TextView
+                    android:id="@+id/vo2max_cycling_gauge_value"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_centerHorizontal="true"
+                    android:layout_marginTop="28dp"
+                    android:text="@string/stats_empty_value"
+                    android:textSize="30sp" />
+
+                <TextView
+                    android:id="@+id/vo2max_cycling_gauge_label"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_below="@+id/vo2max_cycling_gauge_value"
+                    android:layout_centerHorizontal="true"
+                    android:text="@string/title_cycling" />
+
+            </RelativeLayout>
+
+        </GridLayout>
+        <TextView
+            android:layout_marginTop="30dp"
+            android:layout_marginStart="25dp"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="start"
+            android:textSize="20sp"
+            android:text="@string/thirty_days_timeline"
+            />
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="250sp"
+            >
+            <com.github.mikephil.charting.charts.LineChart
+                android:id="@+id/vo2max_chart"
+                android:layout_width="fill_parent"
+                android:layout_height="fill_parent"
+                android:layout_weight="2" />
+        </LinearLayout>
+
+    </LinearLayout>
+
+</RelativeLayout>
diff --git a/app/src/main/res/layout/fragment_weeksleep_chart.xml b/app/src/main/res/layout/fragment_weeksleep_chart.xml
new file mode 100644
index 0000000..4a485d3
--- /dev/null
+++ b/app/src/main/res/layout/fragment_weeksleep_chart.xml
@@ -0,0 +1,190 @@
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical"
+        tools:context="nodomain.freeyourgadget.gadgetbridge.activities.charts.ActivityChartsActivity$PlaceholderFragment">
+
+        <TextView
+                android:id="@+id/sleep_dates"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:gravity="center"
+                android:textSize="20sp"
+                android:layout_marginTop="15dp"
+                android:layout_marginBottom="10dp"
+        />
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginBottom="5dp"
+            android:gravity="center">
+            <TextView
+                android:id="@+id/balance"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content" />
+        </LinearLayout>
+
+        <GridLayout
+            android:background="@color/gauge_line_color"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:columnCount="2"
+            >
+            <LinearLayout
+                style="@style/GridTile"
+                android:layout_marginBottom="0dp"
+                >
+                <View
+                    android:layout_width="fill_parent"
+                    android:layout_height="5dp"
+                    android:background="@color/chart_deep_sleep_dark"
+                    android:layout_marginBottom="10dp"
+                    />
+                <TextView
+                    android:id="@+id/sleep_chart_legend_deep_time"
+                    android:text="@string/stats_empty_value"
+                    android:textSize="20sp"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    />
+                <TextView
+                    android:id="@+id/sleep_chart_legend_deep_legend"
+                    android:text="@string/sleep_colored_stats_deep_avg"
+                    android:textSize="12sp"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    />
+            </LinearLayout>
+            <LinearLayout
+                style="@style/GridTile"
+                android:layout_marginBottom="0dp"
+                >
+                <View
+                    android:layout_width="fill_parent"
+                    android:layout_height="5dp"
+                    android:background="@color/chart_light_sleep_dark"
+                    android:layout_marginBottom="10dp"
+                    />
+                <TextView
+                    android:id="@+id/sleep_chart_legend_light_time"
+                    android:text="@string/stats_empty_value"
+                    android:textSize="20sp"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    />
+                <TextView
+                    android:id="@+id/sleep_chart_legend_light_legend"
+                    android:text="@string/sleep_colored_stats_light_avg"
+                    android:textSize="12sp"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    />
+            </LinearLayout>
+            <LinearLayout
+                style="@style/GridTile"
+                android:layout_height="fill_parent"
+                android:layout_marginBottom="0dp"
+                >
+                <LinearLayout
+                    android:id="@+id/sleep_chart_legend_rem_time_wrapper"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content"
+                    android:gravity="center"
+                    android:orientation="vertical"
+                    >
+                    <View
+                        android:layout_width="fill_parent"
+                        android:layout_height="5dp"
+                        android:background="@color/chart_rem_sleep_dark"
+                        android:layout_marginBottom="10dp"
+                        />
+                    <TextView
+                        android:id="@+id/sleep_chart_legend_rem_time"
+                        android:text="@string/stats_empty_value"
+                        android:textSize="20sp"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:gravity="center"/>
+                    <TextView
+                        android:id="@+id/sleep_chart_legend_rem_legend"
+                        android:text="@string/sleep_colored_stats_rem_avg"
+                        android:textSize="12sp"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:gravity="center"/>
+                </LinearLayout>
+            </LinearLayout>
+            <LinearLayout
+                style="@style/GridTile"
+                android:layout_height="fill_parent"
+                android:layout_marginBottom="0dp"
+                >
+                <LinearLayout
+                    android:id="@+id/sleep_chart_legend_awake_time_wrapper"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content"
+                    android:gravity="center"
+                    android:orientation="vertical"
+                    >
+                    <View
+                        android:layout_width="fill_parent"
+                        android:layout_height="5dp"
+                        android:background="@color/chart_awake_sleep_dark"
+                        android:layout_marginBottom="10dp"
+                        />
+                    <TextView
+                        android:id="@+id/sleep_chart_legend_awake_time"
+                        android:text="@string/stats_empty_value"
+                        android:textSize="20sp"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:gravity="center"/>
+                    <TextView
+                        android:id="@+id/sleep_chart_legend_awake_legend"
+                        android:text="@string/sleep_colored_stats_awake_avg"
+                        android:textSize="12sp"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:gravity="center"/>
+                </LinearLayout>
+            </LinearLayout>
+        </GridLayout>
+
+        <com.github.mikephil.charting.charts.BarChart
+                android:id="@+id/weekstepschart"
+                android:layout_width="fill_parent"
+                android:layout_height="350dp"
+        />
+    <LinearLayout
+        android:id="@+id/sleep_score_wrapper"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:gravity="center"
+        android:orientation="vertical"
+        >
+        <TextView
+            android:layout_marginStart="18dp"
+            android:layout_marginTop="15dp"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="start"
+            android:textSize="20sp"
+            android:text="@string/sleep_score"
+            />
+        <com.github.mikephil.charting.charts.LineChart
+            android:id="@+id/sleep_score_chart"
+            android:layout_width="fill_parent"
+            android:layout_height="350dp"
+            />
+    </LinearLayout>
+
+    </LinearLayout>
+
+</ScrollView>
+
diff --git a/app/src/main/res/layout/fragment_weeksteps_chart.xml b/app/src/main/res/layout/fragment_weeksteps_chart.xml
new file mode 100644
index 0000000..aa97943
--- /dev/null
+++ b/app/src/main/res/layout/fragment_weeksteps_chart.xml
@@ -0,0 +1,44 @@
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:orientation="vertical"
+        tools:context="nodomain.freeyourgadget.gadgetbridge.activities.charts.ActivityChartsActivity$PlaceholderFragment">
+
+        <TextView
+            android:id="@+id/balance"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content" />
+
+        <com.github.mikephil.charting.charts.PieChart
+            android:id="@+id/todaystepschart"
+            android:layout_width="fill_parent"
+            android:layout_height="fill_parent"
+            android:layout_weight="20" />
+
+        <com.github.mikephil.charting.charts.BarChart
+            android:id="@+id/weekstepschart"
+            android:layout_width="fill_parent"
+            android:layout_height="fill_parent"
+            android:layout_weight="20" />
+
+    </LinearLayout>
+
+    <ImageView
+        android:layout_width="50dp"
+        android:layout_height="50dp"
+        android:id="@+id/steps_streaks_button"
+        android:layout_alignParentEnd="true"
+        android:layout_alignParentTop="true"
+        android:layout_marginTop="0dp"
+        android:layout_marginEnd="0dp"
+        android:visibility="visible"
+        app:srcCompat="@drawable/ic_events" />
+
+</RelativeLayout>
+
diff --git a/app/src/main/res/layout/fragment_weightchart.xml b/app/src/main/res/layout/fragment_weightchart.xml
new file mode 100644
index 0000000..3bb535d
--- /dev/null
+++ b/app/src/main/res/layout/fragment_weightchart.xml
@@ -0,0 +1,78 @@
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+    <TextView
+        android:id="@+id/weight_time_span_text"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:gravity="center"
+        android:textSize="20sp"
+        android:layout_marginTop="15dp"
+        android:layout_marginBottom="20dp" />
+    <com.github.mikephil.charting.charts.LineChart
+        android:id="@+id/weight_chart"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_weight="1" />
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="5dp"
+        android:layout_marginBottom="30dp">
+        <LinearLayout
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:orientation="vertical"
+            android:paddingLeft="20dp"
+            android:paddingTop="20dp"
+            android:paddingRight="20dp">
+            <View
+                android:layout_width="match_parent"
+                android:layout_height="5px"
+                android:background="@color/value_line_color" />
+            <TextView
+                android:id="@+id/weight_latest_text"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="left"
+                android:layout_marginTop="20dp"
+                android:text="@string/stats_empty_value"
+                android:textSize="20sp" />
+            <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="left"
+                android:text="@string/menuitem_weight"
+                android:textSize="12sp" />
+        </LinearLayout>
+        <LinearLayout
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:orientation="vertical"
+            android:paddingLeft="20dp"
+            android:paddingTop="20dp"
+            android:paddingRight="20dp">
+            <View
+                android:layout_width="match_parent"
+                android:layout_height="5px"
+                android:background="@color/value_line_color" />
+            <TextView
+                android:id="@+id/weight_target_text"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="left"
+                android:layout_marginTop="20dp"
+                android:text="@string/stats_empty_value"
+                android:textSize="20sp" />
+            <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="left"
+                android:text="@string/target"
+                android:textSize="12sp" />
+        </LinearLayout>
+    </LinearLayout>
+</LinearLayout>
diff --git a/app/src/main/res/layout/fragment_welcome_docs_source.xml b/app/src/main/res/layout/fragment_welcome_docs_source.xml
new file mode 100644
index 0000000..e772214
--- /dev/null
+++ b/app/src/main/res/layout/fragment_welcome_docs_source.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <ScrollView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_centerInParent="true">
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginHorizontal="50dp"
+            android:orientation="vertical">
+
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:textAlignment="center"
+                android:text="@string/first_start_open_source_title"
+                android:textSize="30sp"/>
+
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="20dp"
+                android:textSize="20sp"
+                android:textAlignment="center"
+                android:text="@string/first_start_open_source_text" />
+
+            <ImageView
+                android:layout_width="match_parent"
+                android:layout_height="60dp"
+                android:layout_marginTop="30dp"
+                android:src="@drawable/ic_engineering"
+                app:tint="?attr/colorPrimary" />
+
+        </LinearLayout>
+    </ScrollView>
+</RelativeLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_welcome_get_started.xml b/app/src/main/res/layout/fragment_welcome_get_started.xml
new file mode 100644
index 0000000..d0a7329
--- /dev/null
+++ b/app/src/main/res/layout/fragment_welcome_get_started.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_centerInParent="true"
+        android:layout_marginHorizontal="50dp"
+        android:orientation="vertical">
+
+        <TextView
+            android:id="@+id/intro_gadgetbridge"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:textAlignment="center"
+            android:text="@string/first_start_get_started_title"
+            android:textSize="25sp"/>
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="20dp"
+            android:textSize="20sp"
+            android:textAlignment="center"
+            android:text="@string/first_start_get_started_desc" />
+
+        <Button
+            android:id="@+id/welcome_button_add_device"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="30dp"
+            android:text="@string/first_start_get_started_add_first_device_button" />
+
+        <Button
+            android:id="@+id/welcome_button_restore"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="15dp"
+            android:text="@string/first_start_get_started_restore_button" />
+
+        <Button
+            android:id="@+id/welcome_button_to_app"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="15dp"
+            android:text="@string/first_start_get_started_go_to_app_button" />
+
+    </LinearLayout>
+</RelativeLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_welcome_intro.xml b/app/src/main/res/layout/fragment_welcome_intro.xml
new file mode 100644
index 0000000..188d627
--- /dev/null
+++ b/app/src/main/res/layout/fragment_welcome_intro.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginHorizontal="50dp"
+        android:layout_centerInParent="true"
+        android:gravity="center_horizontal"
+        android:orientation="vertical">
+
+        <ImageView
+            android:layout_width="200dp"
+            android:layout_height="200dp"
+            android:background="@color/accent"
+            android:src="@drawable/ic_launcher_foreground" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="30dp"
+            android:textAlignment="center"
+            android:text="@string/first_start_intro_welcome_to"
+            android:textSize="30sp" />
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:textAlignment="center"
+            android:text="@string/app_name"
+            android:textSize="30sp" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="20dp"
+            android:textSize="20sp"
+            android:textAlignment="center"
+            android:text="@string/first_start_intro_tag_line" />
+
+        <com.google.android.material.textfield.TextInputLayout
+            style="@style/Widget.Material3.TextInputLayout.OutlinedBox.ExposedDropdownMenu"
+            android:id="@+id/app_theme_dropdown_input"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="30dp"
+            android:hint="@string/pref_title_theme">
+
+            <AutoCompleteTextView
+                android:id="@+id/app_theme_dropdown_menu"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:inputType="none"
+                app:simpleItems="@array/pref_theme_options" />
+
+        </com.google.android.material.textfield.TextInputLayout>
+    </LinearLayout>
+</RelativeLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_welcome_overview.xml b/app/src/main/res/layout/fragment_welcome_overview.xml
new file mode 100644
index 0000000..2bdcc2b
--- /dev/null
+++ b/app/src/main/res/layout/fragment_welcome_overview.xml
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <ScrollView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_centerInParent="true">
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginHorizontal="50dp"
+            android:orientation="vertical">
+
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:textAlignment="center"
+                android:text="@string/first_start_overview_title"
+                android:textSize="30sp"/>
+
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="20dp"
+                android:textSize="20sp"
+                android:textAlignment="center"
+                android:text="@string/first_start_overview_desc" />
+
+            <ImageView
+                android:layout_width="match_parent"
+                android:layout_height="60dp"
+                android:layout_marginTop="30dp"
+                android:src="@drawable/ic_dashboard"
+                app:tint="?attr/colorPrimary" />
+
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:textSize="20sp"
+                android:textAlignment="center"
+                android:text="@string/first_start_overview_dashboard" />
+
+            <ImageView
+                android:layout_width="match_parent"
+                android:layout_height="60dp"
+                android:layout_marginTop="20dp"
+                android:src="@drawable/ic_devices_other"
+                app:tint="?attr/colorPrimary" />
+
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:textSize="20sp"
+                android:textAlignment="center"
+                android:text="@string/first_start_overview_devices" />
+
+        </LinearLayout>
+    </ScrollView>
+</RelativeLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_welcome_permission_row.xml b/app/src/main/res/layout/fragment_welcome_permission_row.xml
new file mode 100644
index 0000000..8e24103
--- /dev/null
+++ b/app/src/main/res/layout/fragment_welcome_permission_row.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:minHeight="50dp">
+    <LinearLayout
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="10dp"
+        android:layout_marginEnd="10dp"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintEnd_toStartOf="@id/permission_request"
+        android:orientation="vertical">
+        <TextView
+            android:id="@+id/permission_title"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textStyle="bold"
+            android:text="Placeholder: Permission name" />
+        <TextView
+            android:id="@+id/permission_summary"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Placeholder: Permission summary" />
+    </LinearLayout>
+    <Button
+        android:id="@+id/permission_request"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        android:text="@string/first_start_permissions_request_button" />
+    <ImageView
+        android:id="@+id/permission_check"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginEnd="20dp"
+        android:src="@drawable/cpv_preset_checked"
+        android:visibility="gone"
+        app:tint="@android:color/holo_green_dark"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent" />
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/app/src/main/res/layout/fragment_welcome_permissions.xml b/app/src/main/res/layout/fragment_welcome_permissions.xml
new file mode 100644
index 0000000..8a9b34e
--- /dev/null
+++ b/app/src/main/res/layout/fragment_welcome_permissions.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    android:gravity="center_horizontal"
+    android:padding="8dp">
+
+    <TextView
+        android:id="@+id/permissions_title"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="30dp"
+        android:textAlignment="center"
+        android:text="@string/first_start_permissions_title"
+        android:textSize="30sp"/>
+
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="20dp"
+        android:textSize="20sp"
+        android:textAlignment="center"
+        android:text="@string/first_start_permissions_desc" />
+
+    <Button
+        android:id="@+id/button_request_all"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="20dp"
+        android:text="@string/first_start_permissions_request_all_button"/>
+
+    <androidx.recyclerview.widget.RecyclerView
+        android:id="@+id/permissions_list"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="20dp" />
+</LinearLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_womp_workbench.xml b/app/src/main/res/layout/fragment_womp_workbench.xml
new file mode 100644
index 0000000..89282cf
--- /dev/null
+++ b/app/src/main/res/layout/fragment_womp_workbench.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <WebView
+        android:id="@+id/womp_webview"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+</FrameLayout>
diff --git a/app/src/main/res/layout/heart_rate_dialog.xml b/app/src/main/res/layout/heart_rate_dialog.xml
new file mode 100644
index 0000000..048d45b
--- /dev/null
+++ b/app/src/main/res/layout/heart_rate_dialog.xml
@@ -0,0 +1,84 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/activity_battery_info_master_layout"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:layout_marginStart="0dp"
+    android:layout_marginTop="10dp"
+    android:layout_marginBottom="10dp"
+    android:gravity="center|center_vertical"
+    android:minWidth="1000dp"
+    android:orientation="vertical">
+
+    <TextView
+        android:id="@+id/heart_rate_dialog_title"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:gravity="center"
+        android:maxLines="2"
+        android:scrollHorizontally="false"
+        android:text="@string/getting_heart_rate"
+        android:textAllCaps="true"
+        android:textColor="@color/accent"
+        android:textStyle="bold" />
+
+    <RelativeLayout
+        android:id="@+id/heart_rate_dialog_loading_layout"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_marginTop="10dp"
+        android:layout_marginBottom="10dp"
+        android:gravity="center"
+        android:orientation="vertical">
+
+        <ImageView
+            android:layout_width="100dp"
+            android:layout_height="100dp"
+            android:layout_marginStart="35dp"
+            android:layout_marginTop="35dp"
+            android:contentDescription="@string/app_name"
+            app:srcCompat="@drawable/gadgetbridge_img" />
+
+        <ProgressBar
+            android:layout_width="171dp"
+            android:layout_height="171dp"
+            android:indeterminate="true" />
+
+    </RelativeLayout>
+
+    <LinearLayout
+        android:id="@+id/heart_rate_dialog_results_layout"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="10dp"
+        android:layout_marginBottom="30dp"
+        android:orientation="vertical">
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="0dp"
+            android:layout_marginBottom="10dp"
+            android:baselineAligned="false"
+            android:gravity="bottom|center"
+            android:orientation="horizontal">
+
+            <include
+                android:id="@+id/heart_rate_measurements1"
+                layout="@layout/layout_widget_generic" />
+
+            <include
+                android:id="@+id/heart_rate_measurements2"
+                layout="@layout/layout_widget_generic" />
+
+            <include
+                android:id="@+id/heart_rate_measurements3"
+                layout="@layout/layout_widget_generic" />
+
+        </LinearLayout>
+
+    </LinearLayout>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_alarm.xml b/app/src/main/res/layout/item_alarm.xml
new file mode 100644
index 0000000..c19961f
--- /dev/null
+++ b/app/src/main/res/layout/item_alarm.xml
@@ -0,0 +1,143 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.coordinatorlayout.widget.CoordinatorLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content">
+
+
+    <com.google.android.material.card.MaterialCardView
+        android:id="@+id/card_view"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_margin="8dp"
+        android:foreground="?android:attr/selectableItemBackground"
+        app:cardBackgroundColor="?attr/cardview_background_color"
+        app:cardElevation="3dp">
+
+        <RelativeLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_margin="16dp">
+
+            <TextView
+                android:id="@+id/alarm_item_time"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_alignParentStart="true"
+                android:layout_alignParentTop="true"
+                android:layout_marginStart="8dp"
+                android:layout_marginTop="8dp"
+                android:layout_marginBottom="8dp"
+                android:layout_marginEnd="0dp"
+                android:text="00:00"
+                android:textAppearance="?android:attr/textAppearanceLarge" />
+
+            <TextView
+                android:id="@+id/alarm_smart_wakeup"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_alignBaseline="@+id/alarm_item_time"
+                android:layout_toEndOf="@+id/alarm_item_time"
+                android:text="(Smart)"
+                android:visibility="invisible" />
+
+            <com.google.android.material.materialswitch.MaterialSwitch
+                android:id="@+id/alarm_item_toggle"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_alignParentEnd="true"
+                android:layout_alignParentTop="true"
+                android:layout_marginEnd="8dp" />
+
+            <LinearLayout
+                android:id="@+id/dowSelector"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                android:layout_below="@+id/alarm_item_time"
+                android:baselineAligned="false"
+                android:orientation="horizontal">
+
+                <androidx.appcompat.widget.AppCompatCheckedTextView
+                    android:id="@+id/alarm_item_monday"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_marginStart="4dp"
+                    android:layout_weight="1"
+                    android:textColor="@color/alarm_dow"
+                    android:gravity="center"
+                    android:text="@string/alarm_mon_short"
+                    android:textAppearance="?android:attr/textAppearanceSmall" />
+
+                <androidx.appcompat.widget.AppCompatCheckedTextView
+                    android:id="@+id/alarm_item_tuesday"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_marginStart="4dp"
+                    android:layout_weight="1"
+                    android:textColor="@color/alarm_dow"
+                    android:gravity="center"
+                    android:text="@string/alarm_tue_short"
+                    android:textAppearance="?android:attr/textAppearanceSmall" />
+
+                <androidx.appcompat.widget.AppCompatCheckedTextView
+                    android:id="@+id/alarm_item_wednesday"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_marginStart="4dp"
+                    android:layout_weight="1"
+                    android:textColor="@color/alarm_dow"
+                    android:gravity="center"
+                    android:text="@string/alarm_wed_short"
+                    android:textAppearance="?android:attr/textAppearanceSmall" />
+
+                <androidx.appcompat.widget.AppCompatCheckedTextView
+                    android:id="@+id/alarm_item_thursday"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_marginStart="4dp"
+                    android:layout_weight="1"
+                    android:textColor="@color/alarm_dow"
+                    android:gravity="center"
+                    android:text="@string/alarm_thu_short"
+                    android:textAppearance="?android:attr/textAppearanceSmall" />
+
+                <androidx.appcompat.widget.AppCompatCheckedTextView
+                    android:id="@+id/alarm_item_friday"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_marginStart="4dp"
+                    android:layout_weight="1"
+                    android:textColor="@color/alarm_dow"
+                    android:gravity="center"
+                    android:text="@string/alarm_fri_short"
+                    android:textAppearance="?android:attr/textAppearanceSmall" />
+
+                <androidx.appcompat.widget.AppCompatCheckedTextView
+                    android:id="@+id/alarm_item_saturday"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_marginStart="4dp"
+                    android:layout_weight="1"
+                    android:textColor="@color/alarm_dow"
+                    android:gravity="center"
+                    android:text="@string/alarm_sat_short"
+                    android:textAppearance="?android:attr/textAppearanceSmall" />
+
+                <androidx.appcompat.widget.AppCompatCheckedTextView
+                    android:id="@+id/alarm_item_sunday"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_marginStart="4dp"
+                    android:layout_weight="1"
+                    android:textColor="@color/alarm_dow"
+                    android:gravity="center"
+                    android:text="@string/alarm_sun_short"
+                    android:textAppearance="?android:attr/textAppearanceSmall" />
+
+            </LinearLayout>
+
+        </RelativeLayout>
+    </com.google.android.material.card.MaterialCardView>
+
+</androidx.coordinatorlayout.widget.CoordinatorLayout>
diff --git a/app/src/main/res/layout/item_app_blacklist.xml b/app/src/main/res/layout/item_app_blacklist.xml
new file mode 100644
index 0000000..a0df797
--- /dev/null
+++ b/app/src/main/res/layout/item_app_blacklist.xml
@@ -0,0 +1,83 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:background="?android:attr/activatedBackgroundIndicator"
+    android:minHeight="60dp">
+
+
+    <androidx.appcompat.widget.AppCompatCheckedTextView
+        android:id="@+id/item_checkbox"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentStart="true"
+        android:layout_centerVertical="true"
+        android:layout_marginStart="8dp"
+        android:layout_marginTop="8dp"
+        android:drawableTop="?android:attr/listChoiceIndicatorMultiple"
+        android:text="NOTIF"
+        android:textAppearance="?android:attr/textAppearanceSmall" />
+
+    <androidx.appcompat.widget.AppCompatCheckedTextView
+        android:id="@+id/item_pebble_checkbox"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_centerVertical="true"
+        android:layout_marginStart="8dp"
+        android:layout_marginTop="8dp"
+        android:layout_toEndOf="@+id/item_checkbox"
+        android:drawableTop="?android:attr/listChoiceIndicatorMultiple"
+        android:text="PEBBLE"
+        android:textAppearance="?android:attr/textAppearanceSmall" />
+
+    <ImageView
+        android:id="@+id/item_image"
+        android:layout_width="48dp"
+        android:layout_height="48dp"
+        android:layout_centerVertical="true"
+        android:layout_marginStart="8dp"
+        android:layout_marginTop="8dp"
+        android:layout_toEndOf="@+id/item_pebble_checkbox"
+        android:paddingBottom="8dp"
+        android:paddingTop="8dp" />
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_centerVertical="true"
+        android:layout_toEndOf="@+id/item_image"
+        android:layout_toStartOf="@+id/btn_configureApp"
+        android:orientation="vertical"
+        android:padding="8dp">
+
+        <TextView
+            android:id="@+id/item_name"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:maxLines="1"
+            android:scrollHorizontally="false"
+            android:text="Item Name"
+            android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
+
+        <TextView
+            android:id="@+id/item_details"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="Item Description"
+            android:maxLines="2"
+            android:ellipsize="end"
+            android:textAppearance="@style/TextAppearance.AppCompat.Small" />
+    </LinearLayout>
+
+    <ImageView
+        android:id="@+id/btn_configureApp"
+        android:layout_width="wrap_content"
+        android:layout_height="48dp"
+        android:layout_alignParentEnd="true"
+        android:layout_marginEnd="8dp"
+        android:layout_marginStart="8dp"
+        android:layout_marginTop="8dp"
+        app:srcCompat="@drawable/ic_settings" />
+
+</RelativeLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_app_specific_notification_app_list.xml b/app/src/main/res/layout/item_app_specific_notification_app_list.xml
new file mode 100644
index 0000000..73ad649
--- /dev/null
+++ b/app/src/main/res/layout/item_app_specific_notification_app_list.xml
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:background="?android:attr/activatedBackgroundIndicator"
+    android:minHeight="60dp">
+
+
+    <ImageView
+        android:id="@+id/item_image"
+        android:layout_width="48dp"
+        android:layout_height="48dp"
+        android:layout_centerVertical="true"
+        android:layout_marginStart="8dp"
+        android:layout_marginTop="8dp"
+        android:layout_alignParentStart="true"
+        android:paddingBottom="8dp"
+        android:paddingTop="8dp" />
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_centerVertical="true"
+        android:layout_toEndOf="@+id/item_image"
+        android:layout_toStartOf="@+id/btn_configureApp"
+        android:orientation="vertical"
+        android:padding="8dp">
+
+        <TextView
+            android:id="@+id/item_name"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:maxLines="1"
+            android:scrollHorizontally="false"
+            android:text="Item Name"
+            android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
+
+        <TextView
+            android:id="@+id/item_details"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="Item Description"
+            android:maxLines="2"
+            android:ellipsize="end"
+            android:textAppearance="@style/TextAppearance.AppCompat.Small" />
+    </LinearLayout>
+
+    <ImageView
+        android:id="@+id/btn_configureApp"
+        android:layout_width="wrap_content"
+        android:layout_height="48dp"
+        android:layout_alignParentEnd="true"
+        android:layout_marginEnd="8dp"
+        android:layout_marginStart="8dp"
+        android:layout_marginTop="8dp"
+        app:srcCompat="@drawable/ic_settings" />
+
+</RelativeLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_appmanager_watchapp.xml b/app/src/main/res/layout/item_appmanager_watchapp.xml
new file mode 100644
index 0000000..1d3e03b
--- /dev/null
+++ b/app/src/main/res/layout/item_appmanager_watchapp.xml
@@ -0,0 +1,101 @@
+<?xml version="1.0" encoding="utf-8"?>
+<com.google.android.material.card.MaterialCardView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/appmanager_item_card_view"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:layout_margin="8dp"
+    android:foreground="?android:attr/selectableItemBackground"
+    app:cardBackgroundColor="?attr/cardview_background_color"
+    app:cardElevation="3dp"
+    app:contentPadding="8dp">
+
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:orientation="vertical">
+
+        <ImageView
+            android:id="@+id/item_preview_image"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content" />
+
+        <RelativeLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:background="?android:attr/activatedBackgroundIndicator"
+            android:minHeight="60dp">
+
+            <ImageView
+                android:id="@+id/item_image"
+                android:layout_width="40dp"
+                android:layout_height="40dp"
+                android:layout_alignParentStart="true"
+                android:layout_marginStart="16dp"
+                android:layout_marginTop="8dp"
+                android:layout_centerVertical="true"
+                android:contentDescription="@string/candidate_item_device_image" />
+
+            <LinearLayout
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_alignWithParentIfMissing="true"
+                android:layout_centerVertical="true"
+                android:layout_marginStart="16dp"
+                android:layout_marginTop="8dp"
+                android:layout_marginEnd="16dp"
+                android:layout_toEndOf="@+id/item_image"
+                android:layout_toStartOf="@+id/drag_handle"
+                android:orientation="vertical"
+                android:paddingBottom="8dp"
+                android:paddingTop="8dp">
+
+                <TextView
+                    android:id="@+id/item_name"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:maxLines="1"
+                    android:scrollHorizontally="false"
+                    android:text="Item Name"
+                    android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
+
+                <LinearLayout
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:orientation="horizontal">
+                    <TextView
+                        android:id="@+id/item_details"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:text="Item Description"
+                        android:textAppearance="@style/TextAppearance.AppCompat.Body1" />
+                    <TextView
+                        android:id="@+id/item_watchapp_outdated"
+                        android:visibility="gone"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:layout_marginStart="5dp"
+                        android:text="@string/appmanager_item_outdated"
+                        android:textAppearance="@style/TextAppearance.AppCompat.Body1"
+                        android:textColor="@android:color/holo_red_dark" />
+                </LinearLayout>
+            </LinearLayout>
+
+
+            <ImageView
+                android:id="@+id/drag_handle"
+                android:layout_width="36dp"
+                android:layout_height="36dp"
+                android:layout_alignParentEnd="true"
+                android:layout_marginEnd="16dp"
+                android:layout_marginTop="8dp"
+                android:layout_centerVertical="true"
+                android:contentDescription="drag handle"
+                app:tint="@color/secondarytext"
+                app:srcCompat="@drawable/ic_drag_handle" />
+
+        </RelativeLayout>
+    </LinearLayout>
+</com.google.android.material.card.MaterialCardView>
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_cal_blacklist.xml b/app/src/main/res/layout/item_cal_blacklist.xml
new file mode 100644
index 0000000..e26fcca
--- /dev/null
+++ b/app/src/main/res/layout/item_cal_blacklist.xml
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:background="?android:attr/activatedBackgroundIndicator"
+    android:minHeight="60dp">
+
+
+    <CheckBox
+        android:id="@+id/item_checkbox"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentStart="true"
+        android:layout_centerVertical="true"
+        android:layout_marginStart="16dp"
+        android:layout_marginTop="8dp"
+        android:clickable="false"
+        android:focusable="false" />
+
+    <View
+        android:id="@+id/calendar_color"
+        android:layout_width="48dp"
+        android:layout_height="48dp"
+        android:layout_centerVertical="true"
+        android:layout_marginStart="16dp"
+        android:layout_marginTop="8dp"
+        android:layout_toEndOf="@+id/item_checkbox"
+        android:paddingTop="8dp"
+        android:paddingBottom="8dp" />
+
+    <LinearLayout
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:layout_centerVertical="true"
+        android:layout_toEndOf="@+id/calendar_color"
+        android:orientation="vertical"
+        android:padding="8dp">
+
+        <TextView
+            android:id="@+id/calendar_owner_account"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:scrollHorizontally="false"
+            android:maxLines="1"
+            android:text="TextView"
+            android:textAppearance="@style/TextAppearance.AppCompat.Subhead"
+            android:textSize="12sp" />
+
+        <TextView
+            android:id="@+id/calendar_name"
+            android:textAppearance="@style/TextAppearance.AppCompat.Subhead"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:scrollHorizontally="false"
+            android:maxLines="1"
+            android:text="Item Name" />
+
+    </LinearLayout>
+
+</RelativeLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_contact.xml b/app/src/main/res/layout/item_contact.xml
new file mode 100644
index 0000000..25f876c
--- /dev/null
+++ b/app/src/main/res/layout/item_contact.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.coordinatorlayout.widget.CoordinatorLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:card_view="http://schemas.android.com/apk/res-auto"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content">
+
+    <com.google.android.material.card.MaterialCardView
+        android:id="@+id/card_contact"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_margin="8dp"
+        android:foreground="?android:attr/selectableItemBackground"
+        app:cardBackgroundColor="?attr/cardview_background_color"
+        card_view:cardElevation="3dp"
+        card_view:contentPadding="4dp">
+
+        <RelativeLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_margin="8dp">
+
+            <TextView
+                android:id="@+id/contact_item_name"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_alignParentStart="true"
+                android:layout_alignParentTop="true"
+                android:layout_marginStart="3dp"
+                android:layout_marginTop="0dp"
+                android:layout_marginEnd="0dp"
+                android:layout_marginBottom="4dp"
+                android:text="John Smith"
+                android:textAppearance="?android:attr/textAppearance" />
+
+            <TextView
+                android:id="@+id/contact_item_phone_number"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_alignParentStart="true"
+                android:layout_alignParentTop="true"
+                android:layout_marginStart="3dp"
+                android:layout_marginTop="25dp"
+                android:layout_marginEnd="0dp"
+                android:layout_marginBottom="0dp"
+                android:text="+1 000 000 000"
+                android:textAppearance="?android:attr/textAppearanceSmall" />
+
+        </RelativeLayout>
+    </com.google.android.material.card.MaterialCardView>
+
+</androidx.coordinatorlayout.widget.CoordinatorLayout>
diff --git a/app/src/main/res/layout/item_file_manager.xml b/app/src/main/res/layout/item_file_manager.xml
new file mode 100644
index 0000000..36c5761
--- /dev/null
+++ b/app/src/main/res/layout/item_file_manager.xml
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:background="?android:attr/selectableItemBackground"
+    android:clickable="true"
+    android:focusable="true"
+    android:minHeight="60dp">
+
+    <ImageView
+        android:id="@+id/file_icon"
+        android:layout_width="48dp"
+        android:layout_height="48dp"
+        android:layout_alignParentStart="true"
+        android:layout_centerVertical="true"
+        android:layout_marginStart="8dp"
+        android:layout_marginTop="8dp"
+        android:paddingTop="8dp"
+        android:paddingBottom="8dp" />
+
+    <LinearLayout
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_centerVertical="true"
+        android:layout_toStartOf="@+id/file_menu"
+        android:layout_toEndOf="@+id/file_icon"
+        android:orientation="vertical"
+        android:padding="8dp">
+
+        <TextView
+            android:id="@+id/file_name"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:scrollHorizontally="false"
+            android:text="-"
+            android:textAppearance="@style/TextAppearance.AppCompat.Subhead"
+            tools:ignore="HardcodedText" />
+
+        <TextView
+            android:id="@+id/file_description"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:ellipsize="end"
+            android:maxLines="1"
+            android:text=""
+            android:textAppearance="@style/TextAppearance.AppCompat.Small" />
+    </LinearLayout>
+
+    <ImageView
+        android:id="@+id/file_menu"
+        android:layout_width="32dp"
+        android:layout_height="48dp"
+        android:layout_alignParentEnd="true"
+        android:layout_marginStart="8dp"
+        android:layout_marginTop="8dp"
+        android:layout_marginEnd="8dp"
+        android:background="?android:attr/selectableItemBackground"
+        android:clickable="true"
+        android:focusable="true"
+        app:srcCompat="@drawable/ic_more_vert" />
+
+</RelativeLayout>
diff --git a/app/src/main/res/layout/item_musicmanager_song.xml b/app/src/main/res/layout/item_musicmanager_song.xml
new file mode 100644
index 0000000..77aea0a
--- /dev/null
+++ b/app/src/main/res/layout/item_musicmanager_song.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="utf-8"?>
+<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/appmanager_item_card_view"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:layout_margin="8dp"
+    android:foreground="?android:attr/selectableItemBackground"
+    app:cardBackgroundColor="?attr/cardview_background_color"
+    app:cardElevation="3dp"
+    app:contentPadding="8dp">
+
+
+    <RelativeLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:background="?android:attr/activatedBackgroundIndicator"
+        android:minHeight="60dp">
+
+        <ImageView
+            android:id="@+id/item_image"
+            android:layout_width="40dp"
+            android:layout_height="40dp"
+            android:layout_alignParentStart="true"
+            android:layout_centerVertical="true"
+            android:layout_marginStart="16dp"
+            android:layout_marginTop="8dp"
+            android:contentDescription="@string/candidate_item_device_image"
+            android:src="@drawable/ic_music_item"/>
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_alignWithParentIfMissing="true"
+            android:layout_centerVertical="true"
+            android:layout_marginStart="16dp"
+            android:layout_marginTop="8dp"
+            android:layout_marginEnd="16dp"
+            android:layout_toEndOf="@+id/item_image"
+            android:orientation="vertical"
+            android:paddingTop="8dp"
+            android:paddingBottom="8dp">
+
+            <TextView
+                android:id="@+id/item_name"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:maxLines="1"
+                android:scrollHorizontally="false"
+                android:text="Item Name"
+                android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
+
+            <TextView
+                android:id="@+id/item_details"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="Item Description"
+                android:textAppearance="@style/TextAppearance.AppCompat.Body1" />
+
+        </LinearLayout>
+
+    </RelativeLayout>
+</com.google.android.material.card.MaterialCardView>
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_reminder.xml b/app/src/main/res/layout/item_reminder.xml
new file mode 100644
index 0000000..4115e18
--- /dev/null
+++ b/app/src/main/res/layout/item_reminder.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.coordinatorlayout.widget.CoordinatorLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:card_view="http://schemas.android.com/apk/res-auto"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content">
+
+    <com.google.android.material.card.MaterialCardView
+        android:id="@+id/card_view"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_margin="8dp"
+        android:foreground="?android:attr/selectableItemBackground"
+        app:cardBackgroundColor="?attr/cardview_background_color"
+        card_view:cardElevation="3dp"
+        card_view:contentPadding="4dp">
+
+        <RelativeLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_margin="8dp">
+
+            <TextView
+                android:id="@+id/reminder_item_time"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_alignParentStart="true"
+                android:layout_alignParentTop="true"
+                android:layout_marginStart="3dp"
+                android:layout_marginTop="4dp"
+                android:layout_marginEnd="0dp"
+                android:layout_marginBottom="8dp"
+                android:text="Every ?, at ??:??"
+                android:textAppearance="?android:attr/textAppearance" />
+
+            <TextView
+                android:id="@+id/reminder_item_message"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_alignParentStart="true"
+                android:layout_alignParentTop="true"
+                android:layout_marginStart="3dp"
+                android:layout_marginTop="25dp"
+                android:layout_marginEnd="0dp"
+                android:layout_marginBottom="8dp"
+                android:text="?"
+                android:textAppearance="?android:attr/textAppearanceLarge" />
+
+        </RelativeLayout>
+    </com.google.android.material.card.MaterialCardView>
+
+</androidx.coordinatorlayout.widget.CoordinatorLayout>
diff --git a/app/src/main/res/layout/item_widget_screen.xml b/app/src/main/res/layout/item_widget_screen.xml
new file mode 100644
index 0000000..0adc57d
--- /dev/null
+++ b/app/src/main/res/layout/item_widget_screen.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:card_view="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content">
+
+    <com.google.android.material.card.MaterialCardView
+        android:id="@+id/card_widget_screen"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_margin="8dp"
+        android:foreground="?android:attr/selectableItemBackground"
+        app:cardBackgroundColor="?attr/cardview_background_color"
+        card_view:cardElevation="3dp"
+        card_view:contentPadding="4dp">
+
+        <RelativeLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_margin="8dp">
+
+            <TextView
+                android:id="@+id/widget_screen_name"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_alignParentStart="true"
+                android:layout_alignParentTop="true"
+                android:layout_marginStart="3dp"
+                android:layout_marginTop="0dp"
+                android:layout_marginEnd="0dp"
+                android:layout_marginBottom="4dp"
+                android:text="Screen 1"
+                android:textAppearance="?android:attr/textAppearance" />
+
+            <TextView
+                android:id="@+id/widget_screen_description"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_alignParentStart="true"
+                android:layout_alignParentTop="true"
+                android:layout_marginStart="3dp"
+                android:layout_marginTop="25dp"
+                android:layout_marginEnd="0dp"
+                android:layout_marginBottom="0dp"
+                android:text="x widgets"
+                android:textAppearance="?android:attr/textAppearanceSmall" />
+
+        </RelativeLayout>
+    </com.google.android.material.card.MaterialCardView>
+
+</androidx.coordinatorlayout.widget.CoordinatorLayout>
diff --git a/app/src/main/res/layout/item_with_details.xml b/app/src/main/res/layout/item_with_details.xml
new file mode 100644
index 0000000..213dbb2
--- /dev/null
+++ b/app/src/main/res/layout/item_with_details.xml
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:background="?android:attr/activatedBackgroundIndicator">
+
+    <ImageView
+        android:id="@+id/item_preview"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentStart="false"
+        android:layout_alignParentTop="true"
+        android:layout_centerHorizontal="true"
+        android:contentDescription="Item preview" />
+
+    <ImageView
+        android:id="@+id/item_image"
+        android:layout_width="56dp"
+        android:layout_height="56dp"
+        android:layout_below="@+id/item_preview"
+        android:contentDescription="@string/candidate_item_device_image"
+        android:padding="8dp" />
+
+    <LinearLayout
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:layout_centerVertical="true"
+        android:layout_toEndOf="@+id/item_image"
+        android:layout_below="@+id/item_preview"
+        android:orientation="vertical"
+        android:paddingStart="8dp"
+        android:paddingEnd="8dp">
+
+        <TextView
+            android:id="@+id/item_name"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:maxLines="1"
+            android:scrollHorizontally="false"
+            android:text="Item Name"
+            android:textStyle="bold" />
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal">
+
+            <TextView
+                android:id="@+id/item_details"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:gravity="start"
+                android:text="Item Description" />
+
+            <TextView
+                android:id="@+id/item_status"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:gravity="end"
+                android:text=""
+                android:textColor="@color/design_default_color_error" />
+        </LinearLayout>
+    </LinearLayout>
+
+</RelativeLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_with_details_horizontal.xml b/app/src/main/res/layout/item_with_details_horizontal.xml
new file mode 100644
index 0000000..ac2d537
--- /dev/null
+++ b/app/src/main/res/layout/item_with_details_horizontal.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:background="?android:attr/activatedBackgroundIndicator"
+    android:paddingStart="1dp">
+
+    <ImageView
+        android:id="@+id/item_preview"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentStart="true"
+        android:layout_centerHorizontal="true"
+        android:contentDescription="Item preview" />
+
+    <ImageView
+        android:id="@+id/item_image"
+        android:layout_width="8dp"
+        android:layout_height="8dp"
+        android:layout_toEndOf="@+id/item_preview"
+        android:contentDescription="@string/candidate_item_device_image" />
+
+    <LinearLayout
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:layout_centerVertical="true"
+        android:layout_toEndOf="@+id/item_image"
+        android:orientation="horizontal"
+        android:paddingStart="2dp"
+        android:paddingEnd="2dp">
+
+        <TextView
+            android:id="@+id/item_name"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:scrollHorizontally="false"
+            android:textColor="@color/secondarytext"
+            android:textSize="12sp"
+            android:text="Item Name"
+            android:singleLine="true" />
+
+        <TextView
+            android:id="@+id/item_details"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textColor="@color/secondarytext"
+            android:textSize="12sp"
+            android:text="Item Description" />
+    </LinearLayout>
+
+</RelativeLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_with_details_small.xml b/app/src/main/res/layout/item_with_details_small.xml
new file mode 100644
index 0000000..02146ab
--- /dev/null
+++ b/app/src/main/res/layout/item_with_details_small.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:background="?android:attr/activatedBackgroundIndicator"
+    android:padding="4dp">
+
+    <ImageView
+        android:id="@+id/item_preview"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentStart="false"
+        android:layout_alignParentTop="true"
+        android:layout_centerHorizontal="true"
+        android:contentDescription="Item preview" />
+
+    <ImageView
+        android:id="@+id/item_image"
+        android:layout_width="24dp"
+        android:layout_height="24dp"
+        android:layout_below="@+id/item_preview"
+        android:contentDescription="@string/candidate_item_device_image" />
+
+    <LinearLayout
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:layout_centerVertical="true"
+        android:layout_below="@+id/item_preview"
+        android:orientation="vertical"
+        android:paddingStart="4dp"
+        android:paddingEnd="4dp">
+
+        <TextView
+            android:id="@+id/item_name"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:scrollHorizontally="false"
+
+            style="@style/Base.TextAppearance.AppCompat.Body1"
+            android:text="Item Name" />
+
+        <TextView
+            android:id="@+id/item_details"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            style="@style/Base.TextAppearance.AppCompat.Body2"
+            android:text="Item Description" />
+    </LinearLayout>
+
+</RelativeLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_world_clock.xml b/app/src/main/res/layout/item_world_clock.xml
new file mode 100644
index 0000000..a6c13a6
--- /dev/null
+++ b/app/src/main/res/layout/item_world_clock.xml
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.coordinatorlayout.widget.CoordinatorLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:card_view="http://schemas.android.com/apk/res-auto"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content">
+
+    <com.google.android.material.card.MaterialCardView
+        android:id="@+id/card_view"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_margin="8dp"
+        android:foreground="?android:attr/selectableItemBackground"
+        app:cardBackgroundColor="?attr/cardview_background_color"
+        card_view:cardElevation="3dp"
+        card_view:contentPadding="4dp">
+
+        <RelativeLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_margin="8dp">
+
+            <TextView
+                android:id="@+id/world_clock_item_timezone"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_alignParentStart="true"
+                android:layout_alignParentTop="true"
+                android:layout_marginStart="3dp"
+                android:layout_marginTop="4dp"
+                android:layout_marginEnd="0dp"
+                android:layout_marginBottom="8dp"
+                android:text="Middle-earth/Gondor"
+                android:textAppearance="?android:attr/textAppearance" />
+
+            <TextView
+                android:id="@+id/world_clock_item_label"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_alignParentStart="true"
+                android:layout_alignParentTop="true"
+                android:layout_marginStart="3dp"
+                android:layout_marginTop="25dp"
+                android:layout_marginEnd="0dp"
+                android:layout_marginBottom="8dp"
+                android:text="?"
+                android:textAppearance="?android:attr/textAppearanceLarge" />
+
+            <TextView
+                android:id="@+id/world_clock_current_time"
+                android:layout_width="48dp"
+                android:layout_height="36dp"
+                android:layout_alignParentEnd="true"
+                android:layout_marginStart="8dp"
+                android:layout_marginTop="8dp"
+                android:gravity="center"
+                android:layout_marginEnd="0dp"
+                android:layout_marginBottom="8dp"
+                android:text="00:00"
+                card_view:tint="@color/secondarytext" />
+
+        </RelativeLayout>
+    </com.google.android.material.card.MaterialCardView>
+
+</androidx.coordinatorlayout.widget.CoordinatorLayout>
diff --git a/app/src/main/res/layout/layout_widget_active_steps.xml b/app/src/main/res/layout/layout_widget_active_steps.xml
new file mode 100644
index 0000000..4d59839
--- /dev/null
+++ b/app/src/main/res/layout/layout_widget_active_steps.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout android:id="@+id/line_layout_step"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:layout_gravity="center|top"
+    android:layout_marginStart="1dp"
+    android:layout_marginEnd="1dp"
+    android:layout_weight="1"
+    android:gravity="center|top"
+    android:orientation="vertical"
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+
+    <ImageView
+        android:id="@+id/line_layout_step_icon"
+        android:layout_width="19dp"
+        android:layout_height="19dp"
+        android:layout_gravity="center"
+        android:contentDescription="@string/candidate_item_device_image"
+        app:srcCompat="@drawable/ic_shoe_print" />
+
+    <TextView
+        android:id="@+id/line_layout_step_title"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:gravity="center"
+        android:maxLines="2"
+        android:scrollHorizontally="false"
+        android:text="@string/activity_list_summary_active_steps"
+        android:textAllCaps="true"
+        android:textColor="@color/accent"
+        android:textStyle="bold" />
+
+    <TextView
+        android:id="@+id/line_layout_step_label"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:fontFamily="sans-serif-black"
+        android:gravity="center"
+        android:maxLines="1"
+        android:scrollHorizontally="false"
+        android:text="@string/stats_empty_value"
+        android:textSize="24sp" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/layout_widget_activities_count.xml b/app/src/main/res/layout/layout_widget_activities_count.xml
new file mode 100644
index 0000000..96ecc10
--- /dev/null
+++ b/app/src/main/res/layout/layout_widget_activities_count.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/line_layout_count"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:layout_gravity="center|top"
+    android:layout_marginStart="1dp"
+    android:layout_marginEnd="1dp"
+    android:layout_weight="1"
+    android:gravity="center"
+    android:orientation="vertical">
+
+    <ImageView
+        android:id="@+id/line_layout_count_icon"
+        app:srcCompat="@drawable/ic_activity_stacked"
+        android:layout_width="19dp"
+        android:layout_height="19dp"
+        android:layout_gravity="center"
+        android:contentDescription="@string/candidate_item_device_image" />
+
+    <TextView
+        android:id="@+id/line_layout_count_title"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:gravity="center"
+        android:maxLines="2"
+        android:scrollHorizontally="false"
+        android:text="@string/activity_list_summary_activities"
+        android:textAllCaps="true"
+        android:textColor="@color/accent"
+        android:textStyle="bold" />
+
+    <TextView
+        android:id="@+id/line_layout_count_label"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:fontFamily="sans-serif-black"
+        android:gravity="center"
+        android:maxLines="1"
+        android:scrollHorizontally="false"
+        android:text="@string/stats_empty_value"
+        android:textSize="24sp" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/layout_widget_distance.xml b/app/src/main/res/layout/layout_widget_distance.xml
new file mode 100644
index 0000000..131855c
--- /dev/null
+++ b/app/src/main/res/layout/layout_widget_distance.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/line_layout_distance"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:layout_gravity="center|top"
+    android:layout_marginStart="1dp"
+    android:layout_marginEnd="1dp"
+    android:layout_weight="1"
+    android:gravity="center|top"
+    android:orientation="vertical">
+
+    <ImageView
+        android:id="@+id/line_layout_distance_icon"
+        android:layout_width="19dp"
+        android:layout_height="19dp"
+        android:layout_gravity="center"
+        android:contentDescription="@string/candidate_item_device_image"
+        app:srcCompat="@drawable/ic_distance" />
+
+    <TextView
+        android:id="@+id/line_layout_distance_title"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:gravity="center"
+        android:maxLines="2"
+        android:scrollHorizontally="false"
+        android:text="@string/distance"
+        android:textAllCaps="true"
+        android:textColor="@color/accent"
+        android:textStyle="bold" />
+
+    <TextView
+        android:id="@+id/line_layout_distance_label"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:fontFamily="sans-serif-black"
+        android:gravity="center"
+        android:maxLines="1"
+        android:scrollHorizontally="false"
+        android:text="@string/stats_empty_value"
+        android:textSize="24sp" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/layout_widget_duration.xml b/app/src/main/res/layout/layout_widget_duration.xml
new file mode 100644
index 0000000..c17cca0
--- /dev/null
+++ b/app/src/main/res/layout/layout_widget_duration.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/line_layout_duration"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:layout_gravity="center|top"
+    android:layout_marginStart="1dp"
+    android:layout_marginEnd="1dp"
+    android:layout_weight="1"
+    android:gravity="center|top"
+    android:orientation="vertical">
+
+    <ImageView
+        android:id="@+id/line_layout_duration_icon"
+        android:layout_width="19dp"
+        android:layout_height="19dp"
+        android:layout_gravity="center"
+        android:contentDescription="@string/candidate_item_device_image"
+        app:srcCompat="@drawable/ic_access_time" />
+
+    <TextView
+        android:id="@+id/line_layout_duration_title"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:gravity="center"
+        android:maxLines="2"
+        android:scrollHorizontally="false"
+        android:text="@string/activity_list_summary_active_time"
+        android:textAllCaps="true"
+        android:textColor="@color/accent"
+        android:textStyle="bold" />
+
+    <TextView
+        android:id="@+id/line_layout_duration_label"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:fontFamily="sans-serif-black"
+        android:gravity="center"
+        android:maxLines="1"
+        android:scrollHorizontally="false"
+        android:text="@string/stats_empty_value"
+        android:textSize="24sp" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/layout_widget_duration_selector.xml b/app/src/main/res/layout/layout_widget_duration_selector.xml
new file mode 100644
index 0000000..ac69705
--- /dev/null
+++ b/app/src/main/res/layout/layout_widget_duration_selector.xml
@@ -0,0 +1,161 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+
+    <SeekBar
+        android:id="@+id/battery_status_time_span_seekbar"
+        style="@style/Widget.AppCompat.SeekBar.Discrete"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layoutDirection="rtl"
+        android:max="6"
+        android:progress="1" />
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="0dp"
+        android:layout_marginBottom="10dp"
+        android:baselineAligned="false"
+        android:gravity="bottom|center"
+        android:orientation="horizontal">
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center|top"
+            android:layout_marginStart="1dp"
+            android:layout_marginEnd="1dp"
+            android:layout_weight="1"
+            android:gravity="center|top"
+            android:orientation="vertical">
+
+            <ImageView
+                android:layout_width="19dp"
+                android:layout_height="19dp"
+                android:layout_gravity="center"
+                android:contentDescription="@string/icon_placeholder"
+                android:scaleX="-1"
+                app:srcCompat="@drawable/ic_calendar_to" />
+
+            <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center"
+                android:gravity="center"
+                android:maxLines="2"
+                android:scrollHorizontally="false"
+                android:text="@string/activity_filter_date_from"
+                android:textAllCaps="true"
+                android:textColor="@color/accent"
+                android:textStyle="bold" />
+
+            <TextView
+                android:id="@+id/battery_status_date_from_text"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center"
+                android:fontFamily="sans-serif-black"
+                android:gravity="center"
+                android:maxLines="1"
+                android:scrollHorizontally="false"
+                android:text="@string/lorem_ipsum"
+                android:textSize="12sp" />
+
+        </LinearLayout>
+
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center|top"
+            android:layout_marginStart="1dp"
+            android:layout_marginEnd="1dp"
+            android:layout_weight="1"
+            android:gravity="center|top"
+            android:orientation="vertical">
+
+            <ImageView
+                android:layout_width="19dp"
+                android:layout_height="19dp"
+                android:layout_gravity="center"
+                android:contentDescription="@string/candidate_item_device_image"
+                app:srcCompat="@drawable/ic_date_range" />
+
+            <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center"
+                android:gravity="center"
+                android:maxLines="2"
+                android:scrollHorizontally="false"
+                android:text="@string/activity_detail_duration_label"
+                android:textAllCaps="true"
+                android:textColor="@color/accent"
+                android:textStyle="bold" />
+
+            <TextView
+                android:id="@+id/battery_status_time_span_text"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center"
+                android:fontFamily="sans-serif-black"
+                android:gravity="center"
+                android:maxLines="1"
+                android:scrollHorizontally="false"
+                android:text="@string/lorem_ipsum"
+                android:textSize="18sp" />
+
+        </LinearLayout>
+
+
+        <LinearLayout
+            android:id="@+id/battery_status_date_to_layout"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center|top"
+            android:layout_marginStart="1dp"
+            android:layout_marginEnd="1dp"
+            android:layout_weight="1"
+            android:gravity="center|top"
+            android:orientation="vertical">
+
+            <ImageView
+                android:layout_width="19dp"
+                android:layout_height="19dp"
+                android:layout_gravity="center"
+                android:contentDescription="@string/candidate_item_device_image"
+                app:srcCompat="@drawable/ic_calendar_to" />
+
+            <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center"
+                android:gravity="center"
+                android:maxLines="2"
+                android:scrollHorizontally="false"
+                android:text="@string/activity_filter_date_to"
+                android:textAllCaps="true"
+                android:textColor="@color/accent"
+                android:textStyle="bold" />
+
+            <TextView
+                android:id="@+id/battery_status_date_to_text"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center"
+                android:fontFamily="sans-serif-black"
+                android:gravity="center"
+                android:maxLines="1"
+                android:scrollHorizontally="false"
+                android:text="@string/lorem_ipsum"
+                android:textColor="@color/accent"
+                android:textSize="12sp" />
+        </LinearLayout>
+
+    </LinearLayout>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/layout_widget_generic.xml b/app/src/main/res/layout/layout_widget_generic.xml
new file mode 100644
index 0000000..d731658
--- /dev/null
+++ b/app/src/main/res/layout/layout_widget_generic.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/line_layout_step"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:layout_gravity="center|top"
+    android:layout_marginStart="1dp"
+    android:layout_marginEnd="1dp"
+    android:layout_weight="1"
+    android:gravity="center|top"
+    android:orientation="vertical">
+
+    <ImageView
+        android:id="@+id/generic_widget_icon"
+        android:layout_width="19dp"
+        android:layout_height="19dp"
+        android:layout_gravity="center"
+        android:contentDescription="@string/candidate_item_device_image"
+        app:srcCompat="@drawable/ic_shoe_print" />
+
+    <TextView
+        android:id="@+id/generic_widget_title"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:gravity="center"
+        android:maxLines="2"
+        android:scrollHorizontally="false"
+        android:textAllCaps="true"
+        android:textColor="@color/accent"
+        android:textStyle="bold" />
+
+    <TextView
+        android:id="@+id/generic_widget_value"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:fontFamily="sans-serif-black"
+        android:gravity="center"
+        android:maxLines="1"
+        android:scrollHorizontally="false"
+        android:text=""
+        android:textSize="24sp" />
+</LinearLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/layout_widget_heartrate_average.xml b/app/src/main/res/layout/layout_widget_heartrate_average.xml
new file mode 100644
index 0000000..12dfd4f
--- /dev/null
+++ b/app/src/main/res/layout/layout_widget_heartrate_average.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/heartrate_widget_layout"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <ImageView
+        android:id="@+id/heartrate_widget_icon"
+        android:layout_width="50dp"
+        android:layout_height="50dp"
+        android:contentDescription="@string/heart_rate"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        app:srcCompat="@drawable/ic_heart" />
+
+    <TextView
+        android:id="@+id/heartrate_widget_label"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:paddingBottom="6dp"
+        android:text=""
+        android:textAlignment="center"
+        android:textSize="12sp"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/app/src/main/res/layout/layout_widget_intensity_total.xml b/app/src/main/res/layout/layout_widget_intensity_total.xml
new file mode 100644
index 0000000..42ba95e
--- /dev/null
+++ b/app/src/main/res/layout/layout_widget_intensity_total.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/intensity_widget_layout"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <ImageView
+        android:id="@+id/intensity_widget_icon"
+        android:layout_width="50dp"
+        android:layout_height="50dp"
+        android:contentDescription="@string/movement_intensity"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        app:srcCompat="@drawable/ic_intensity_hollow" />
+
+    <TextView
+        android:id="@+id/intensity_widget_label"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:paddingTop="10dp"
+        android:text=""
+        android:textAlignment="center"
+        android:textSize="12sp"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/app/src/main/res/layout/layout_widget_intensity_total2.xml b/app/src/main/res/layout/layout_widget_intensity_total2.xml
new file mode 100644
index 0000000..6857f15
--- /dev/null
+++ b/app/src/main/res/layout/layout_widget_intensity_total2.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/line_layout_intensity2"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:layout_gravity="center|top"
+    android:layout_marginStart="1dp"
+    android:layout_marginEnd="1dp"
+    android:layout_weight="1"
+    android:gravity="bottom|center"
+    android:orientation="vertical">
+
+    <ImageView
+        android:id="@+id/line_layout_intensity2_icon"
+        app:srcCompat="@drawable/ic_intensity"
+        android:layout_width="19dp"
+        android:layout_height="19dp"
+        android:layout_gravity="center"
+        android:contentDescription="@string/candidate_item_device_image" />
+
+    <TextView
+        android:id="@+id/line_layout_intensity2_title"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:gravity="center"
+        android:maxLines="2"
+        android:scrollHorizontally="false"
+        android:text="@string/activity_list_summary_intensity"
+        android:textAllCaps="true"
+        android:textColor="@color/accent"
+        android:textStyle="bold" />
+
+    <TextView
+        android:id="@+id/line_layout_intensity2_label"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:fontFamily="sans-serif-black"
+        android:gravity="center"
+        android:maxLines="1"
+        android:scrollHorizontally="false"
+        android:text="15"
+        android:textSize="24sp" />
+
+</LinearLayout>
diff --git a/app/src/main/res/layout/layout_widget_total_steps.xml b/app/src/main/res/layout/layout_widget_total_steps.xml
new file mode 100644
index 0000000..4bfe011
--- /dev/null
+++ b/app/src/main/res/layout/layout_widget_total_steps.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/line_layout_total_step"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:layout_gravity="center|top"
+    android:layout_marginStart="1dp"
+    android:layout_marginEnd="1dp"
+    android:layout_weight="1"
+    android:gravity="center"
+    android:orientation="vertical">
+
+    <ImageView
+        android:id="@+id/line_layout_total_step_icon"
+        app:srcCompat="@drawable/ic_shoe_prints_many"
+        android:layout_width="19dp"
+        android:layout_height="19dp"
+        android:layout_gravity="center"
+        android:contentDescription="@string/candidate_item_device_image" />
+
+    <TextView
+        android:id="@+id/line_layout_total_step_title"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:gravity="center"
+        android:maxLines="2"
+        android:scrollHorizontally="false"
+        android:text="@string/live_activity_total_steps"
+        android:textAllCaps="true"
+        android:textColor="@color/accent"
+        android:textStyle="bold" />
+
+    <TextView
+        android:id="@+id/line_layout_total_step_label"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:fontFamily="sans-serif-black"
+        android:gravity="center"
+        android:maxLines="1"
+        android:scrollHorizontally="false"
+        android:text="@string/stats_empty_value"
+        android:textSize="24sp" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/list_item_checkable.xml b/app/src/main/res/layout/list_item_checkable.xml
new file mode 100644
index 0000000..8d8a596
--- /dev/null
+++ b/app/src/main/res/layout/list_item_checkable.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<com.mobeta.android.dslv.CheckableLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="fill_parent"
+    android:layout_height="48dp"
+    android:orientation="horizontal">
+
+    <CheckedTextView
+        android:id="@+id/text"
+        android:layout_width="wrap_content"
+        android:layout_height="48dp"
+        android:layout_weight="1"
+        android:drawableStart="?android:attr/listChoiceIndicatorMultiple"
+        android:gravity="center_vertical"
+        android:paddingStart="16dp"
+        android:textAppearance="?android:attr/textAppearanceMedium" />
+
+    <ImageView
+        android:id="@+id/drag_handle"
+        android:layout_width="36dp"
+        android:layout_height="48dp"
+        android:layout_alignParentEnd="true"
+        android:layout_centerVertical="true"
+        android:contentDescription="@string/drag_handle"
+        app:srcCompat="@drawable/ic_drag_handle"
+        app:tint="@color/secondarytext" />
+
+</com.mobeta.android.dslv.CheckableLinearLayout>
diff --git a/app/src/main/res/layout/main_drawer_header.xml b/app/src/main/res/layout/main_drawer_header.xml
new file mode 100644
index 0000000..0a335c1
--- /dev/null
+++ b/app/src/main/res/layout/main_drawer_header.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:background="?attr/drawer_header_color"
+    android:gravity="bottom"
+    android:orientation="vertical"
+    android:layout_marginTop="16dp"
+    android:paddingBottom="@dimen/activity_vertical_margin"
+    android:paddingLeft="@dimen/activity_horizontal_margin"
+    android:paddingRight="@dimen/activity_horizontal_margin"
+    android:paddingTop="@dimen/activity_vertical_margin">
+
+    <ImageView
+        android:id="@+id/imageView"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:paddingTop="@dimen/nav_header_vertical_spacing"
+        app:srcCompat="@drawable/ic_launcher" />
+
+    <!-- TODO: make visible when we support multiple users -->
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:paddingTop="@dimen/nav_header_vertical_spacing"
+        android:textAppearance="@style/TextAppearance.Material3.ActionBar.Title"
+        android:textColor="?attr/drawer_title_color"
+        android:text="@string/app_name" />
+
+</LinearLayout>
diff --git a/app/src/main/res/layout/preference_checkbox.xml b/app/src/main/res/layout/preference_checkbox.xml
new file mode 100644
index 0000000..d1c0873
--- /dev/null
+++ b/app/src/main/res/layout/preference_checkbox.xml
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:background="?selectableItemBackground"
+    android:baselineAligned="false"
+    android:clipToPadding="false"
+    android:gravity="center_vertical"
+    android:minHeight="?listPreferredItemHeightSmall"
+    android:paddingLeft="?listPreferredItemPaddingLeft"
+    android:paddingRight="?listPreferredItemPaddingRight"
+    tools:ignore="RtlHardcoded">
+
+    <FrameLayout
+        android:id="@+id/icon_frame"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginEnd="12dp"
+        android:layout_marginLeft="-4dp"
+        android:layout_marginRight="12dp"
+        android:layout_marginStart="-4dp"
+        android:minWidth="48dp"
+        android:visibility="gone"
+        tools:visibility="visible">
+
+        <ImageView
+            android:id="@android:id/icon"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:maxHeight="48dp"
+            android:maxWidth="48dp"
+            android:scaleType="fitStart"
+            android:visibility="gone"
+            tools:ignore="ContentDescription"
+            tools:visibility="visible" />
+    </FrameLayout>
+
+    <LinearLayout
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_weight="1"
+        android:orientation="vertical"
+        android:paddingBottom="16dp"
+        android:paddingTop="16dp">
+
+        <TextView
+            android:id="@android:id/title"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:ellipsize="marquee"
+            android:maxLines="10"
+            android:singleLine="false"
+            android:textAppearance="?textAppearanceListItem" />
+
+        <TextView
+            android:id="@android:id/summary"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:maxLines="10"
+            android:textColor="?android:textColorSecondary" />
+
+    </LinearLayout>
+
+    <LinearLayout
+        android:id="@android:id/widget_frame"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:layout_marginEnd="-16dp"
+        android:layout_marginRight="-16dp"
+        android:clipToPadding="false"
+        android:gravity="end|center_vertical"
+        android:orientation="vertical"
+        android:paddingLeft="16dp"
+        android:paddingRight="16dp" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/preference_material_switch.xml b/app/src/main/res/layout/preference_material_switch.xml
new file mode 100644
index 0000000..af3272e
--- /dev/null
+++ b/app/src/main/res/layout/preference_material_switch.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Found on https://stackoverflow.com/a/73782598 -->
+<!-- Derived from https://github.com/androidx/androidx/blob/8cb282cc/preference/preference/res/layout/preference_widget_switch_compat.xml -->
+<com.google.android.material.materialswitch.MaterialSwitch xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/switchWidget"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:background="@null"
+    android:clickable="false"
+    android:focusable="false" />
\ No newline at end of file
diff --git a/app/src/main/res/layout/qhybrid_activity_widget_settings.xml b/app/src/main/res/layout/qhybrid_activity_widget_settings.xml
new file mode 100644
index 0000000..a5c2507
--- /dev/null
+++ b/app/src/main/res/layout/qhybrid_activity_widget_settings.xml
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context=".devices.qhybrid.WidgetSettingsActivity">
+
+    <RelativeLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:orientation="vertical"
+            android:weightSum="1">
+
+            <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="widget name" />
+
+            <EditText
+                android:id="@+id/qhybrid_widget_name"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content" />
+
+            <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="elements" />
+
+            <ListView
+                android:id="@+id/qhybrid_widget_elements_list"
+                android:layout_width="match_parent"
+                android:layout_height="0dp"
+                android:layout_weight="0.4" />
+
+            <Button
+                android:id="@+id/qhybrid_widget_elements_add"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="add element" />
+
+        </LinearLayout>
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_alignParentBottom="true"
+            android:weightSum="1">
+
+
+            <Button
+                android:id="@+id/qhybrid_widget_delete"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="delete"
+                android:layout_weight="0.5" />
+
+            <Button
+                android:id="@+id/qhybrid_widget_save"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="save"
+                android:layout_weight="0.5" />
+
+        </LinearLayout>
+
+    </RelativeLayout>
+
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/qhybrid_app_view.xml b/app/src/main/res/layout/qhybrid_app_view.xml
new file mode 100644
index 0000000..1c2f34c
--- /dev/null
+++ b/app/src/main/res/layout/qhybrid_app_view.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:layout_margin="15dp">
+
+    <ImageView
+        android:layout_width="40dp"
+        android:layout_height="40dp"
+        android:scaleType="fitXY"
+        android:layout_alignParentStart="true"
+        android:id="@+id/qhybrid_appChooserItemIcon"/>
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_centerInParent="true"
+        android:textSize="20dp"
+        android:id="@+id/qhybrid_appChooserItemText"/>
+
+</RelativeLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/qhybrid_element_popup_view.xml b/app/src/main/res/layout/qhybrid_element_popup_view.xml
new file mode 100644
index 0000000..4820e0c
--- /dev/null
+++ b/app/src/main/res/layout/qhybrid_element_popup_view.xml
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:padding="10dp">
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="element id" />
+
+    <EditText
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:id="@+id/qhybrid_widget_element_id"/>
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="default value" />
+
+    <EditText
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:id="@+id/qhybrid_widget_element_value"/>
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="type" />
+
+    <RadioGroup
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:id="@+id/qhybrid_widget_element_type"
+        android:checkedButton="@id/qhybrid_widget_elements_type_text">
+
+        <RadioButton
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="text"
+            android:id="@+id/qhybrid_widget_elements_type_text"/>
+
+        <RadioButton
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="background image"
+            android:id="@+id/qhybrid_widget_elements_type_background" />
+
+    </RadioGroup>
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="position" />
+
+    <RadioGroup
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:id="@+id/qhybrid_widget_element_position"
+        android:checkedButton="@id/qhybrid_widget_elements_position_uppper">
+
+        <RadioButton
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="upper half"
+            android:id="@+id/qhybrid_widget_elements_position_uppper"/>
+
+        <RadioButton
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="lower half"
+            android:id="@+id/qhybrid_widget_elements_position_lower" />
+
+    </RadioGroup>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/qhybrid_package_settings_item.xml b/app/src/main/res/layout/qhybrid_package_settings_item.xml
new file mode 100644
index 0000000..ef5f7bb
--- /dev/null
+++ b/app/src/main/res/layout/qhybrid_package_settings_item.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:gravity="center_vertical"
+    android:padding="5dp">
+
+    <ImageView
+        android:layout_width="40dp"
+        android:layout_height="40dp"
+        android:scaleType="fitXY"
+        android:layout_alignParentStart="true"
+        android:id="@+id/packageIcon"/>
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:id="@+id/packageName"
+        android:textSize="20dp"
+        android:layout_centerInParent="true"/>
+
+    <ImageView
+        android:layout_width="40dp"
+        android:layout_height="40dp"
+        android:scaleType="fitXY"
+        android:id="@+id/packageClock"
+        android:layout_alignParentEnd="true"/>
+
+</RelativeLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/simple_spinner_item_themed.xml b/app/src/main/res/layout/simple_spinner_item_themed.xml
new file mode 100644
index 0000000..8f82b03
--- /dev/null
+++ b/app/src/main/res/layout/simple_spinner_item_themed.xml
@@ -0,0 +1,11 @@
+<CheckedTextView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@android:id/text1"
+    style="?android:attr/spinnerDropDownItemStyle"
+    android:layout_width="match_parent"
+    android:layout_height="50dp"
+    android:layout_marginTop="0dp"
+    android:paddingTop="0dp"
+    android:textSize="16sp"
+    android:paddingStart="8dp"
+    android:paddingEnd="8dp"
+    android:textColor="?android:attr/textColorTertiary" />
diff --git a/app/src/main/res/layout/sleep_alarm_widget.xml b/app/src/main/res/layout/sleep_alarm_widget.xml
new file mode 100644
index 0000000..b1b9c8c
--- /dev/null
+++ b/app/src/main/res/layout/sleep_alarm_widget.xml
@@ -0,0 +1,18 @@
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="@color/primary_light"
+    android:padding="@dimen/widget_margin">
+
+    <TextView
+        android:id="@+id/sleepalarmwidget_text"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:contentDescription="@string/appwidget_text"
+        android:text="@string/appwidget_text"
+        android:textColor="#ffffff"
+        android:textSize="24sp"
+        android:gravity="center_vertical|center_horizontal"
+        android:textStyle="bold|italic" />
+
+</RelativeLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/sort_list_array_dialog_preference.xml b/app/src/main/res/layout/sort_list_array_dialog_preference.xml
new file mode 100644
index 0000000..6c4a95a
--- /dev/null
+++ b/app/src/main/res/layout/sort_list_array_dialog_preference.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<com.mobeta.android.dslv.DragSortListView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:dslv="http://schemas.android.com/apk/res-auto"
+    android:id="@android:id/list"
+    android:divider="@null"
+    android:focusable="false"
+    android:focusableInTouchMode="false"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:layout_margin="3dp"
+    android:choiceMode="multipleChoice"
+    android:paddingTop="12dp"
+    dslv:sort_enabled="true"
+    dslv:remove_enabled="false"
+    dslv:float_background_color="@color/accent"
+    dslv:drag_handle_id="@id/drag_handle" />
diff --git a/app/src/main/res/layout/spinner_with_image_layout.xml b/app/src/main/res/layout/spinner_with_image_layout.xml
new file mode 100644
index 0000000..7a25213
--- /dev/null
+++ b/app/src/main/res/layout/spinner_with_image_layout.xml
@@ -0,0 +1,21 @@
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+     android:layout_height="wrap_content"
+    android:orientation="horizontal">
+
+    <ImageView
+        android:id="@+id/spinner_item_icon"
+        android:layout_width="60dp"
+        android:layout_height="60dp"
+        android:layout_gravity="center_horizontal|center_vertical"
+        android:padding="10dp" />
+    <TextView
+        android:id="@+id/spinner_item_text"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:padding="10dp"
+        android:layout_gravity="center_vertical"
+        >
+    </TextView>
+</LinearLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/sport_activity_filter.xml b/app/src/main/res/layout/sport_activity_filter.xml
new file mode 100644
index 0000000..dac3b29
--- /dev/null
+++ b/app/src/main/res/layout/sport_activity_filter.xml
@@ -0,0 +1,244 @@
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/sport_activity_filter_background"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    android:paddingStart="15dp"
+    android:paddingEnd="15dp">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical">
+
+        <Spinner
+            android:id="@+id/select_device"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="start|center_vertical"
+            android:spinnerMode="dropdown"
+            android:textAlignment="gravity" />
+
+        <View
+            android:layout_width="match_parent"
+            android:layout_height="1.5dp"
+            android:background="#000" />
+
+        <android.widget.Space
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginBottom="1dp"
+            android:background="#000" />
+
+        <Spinner
+            android:id="@+id/select_kind"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="start|center_vertical"
+            android:spinnerMode="dropdown"
+            android:textAlignment="gravity" />
+
+        <View
+            android:layout_width="match_parent"
+            android:layout_height="1.5dp"
+            android:background="#000" />
+
+        <android.widget.Space
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginBottom="5dp"
+            android:background="#000" />
+
+        <LinearLayout
+            android:id="@+id/filtername"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:focusable="true"
+            android:focusableInTouchMode="true"
+            android:orientation="horizontal"
+            android:paddingBottom="5dp">
+
+            <EditText
+                android:id="@+id/textViewNameData"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                android:autofillHints="@string/activity_filter_name_contains"
+                android:hint="@string/activity_filter_name_contains"
+                android:inputType="text"
+                android:paddingStart="8dp"
+                android:paddingEnd="8dp"
+                android:textAlignment="textStart"
+                android:textSize="16sp" />
+
+        </LinearLayout>
+
+        <View
+            android:layout_width="match_parent"
+            android:layout_height="1.5dp"
+            android:background="#000" />
+
+        <android.widget.Space
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginBottom="15dp"
+            android:background="#000" />
+
+        <LinearLayout
+            android:id="@+id/filterfrom"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:orientation="horizontal"
+            android:paddingStart="8dp"
+            android:paddingEnd="8dp"
+            android:paddingBottom="15dp">
+
+            <TextView
+                android:id="@+id/textViewFromLabel"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/activity_filter_date_from"
+                android:textAlignment="textStart"
+                android:textSize="16sp" />
+
+            <View
+                android:layout_width="0dp"
+                android:layout_height="0dp"
+                android:layout_weight="1" />
+
+            <TextView
+                android:id="@+id/textViewFromData"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:hint="@string/activity_filter_from_placeholder"
+                android:textAlignment="textStart"
+                android:textSize="16sp" />
+        </LinearLayout>
+
+        <View
+            android:layout_width="match_parent"
+            android:layout_height="1.5dp"
+            android:background="#000" />
+
+        <android.widget.Space
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginBottom="15dp"
+            android:background="#000" />
+
+        <LinearLayout
+            android:id="@+id/filterto"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:orientation="horizontal"
+            android:paddingStart="8dp"
+            android:paddingEnd="8dp"
+            android:paddingBottom="15dp">
+
+            <TextView
+                android:id="@+id/textViewTolabel"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/activity_filter_date_to"
+                android:textAlignment="textStart"
+                android:textSize="16sp" />
+
+            <View
+                android:layout_width="0dp"
+                android:layout_height="0dp"
+                android:layout_weight="1" />
+
+            <TextView
+                android:id="@+id/textViewToData"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:hint="@string/activity_filter_to_placeholder"
+                android:textAlignment="textStart"
+                android:textSize="16sp" />
+
+        </LinearLayout>
+
+        <View
+            android:layout_width="match_parent"
+            android:layout_height="1.5dp"
+            android:background="#000" />
+
+        <Spinner
+            android:id="@+id/quick_filter_period_select"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="start|center_vertical"
+            android:spinnerMode="dropdown"
+            android:textAlignment="gravity" />
+
+        <View
+            android:layout_width="match_parent"
+            android:layout_height="1.5dp"
+            android:background="#000" />
+
+        <android.widget.Space
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginBottom="15dp"
+            android:background="#000" />
+
+        <LinearLayout
+            android:id="@+id/filteritems"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+
+            android:orientation="horizontal"
+            android:paddingStart="8dp"
+            android:paddingEnd="8dp"
+            android:paddingBottom="15dp">
+
+            <TextView
+                android:id="@+id/textViewItemslabel"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/activity_filter_individual_items"
+                android:textAlignment="textStart"
+                android:textSize="16sp" />
+
+            <View
+                android:layout_width="0dp"
+                android:layout_height="0dp"
+                android:layout_weight="1" />
+
+            <TextView
+                android:id="@+id/textViewItemsData"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:textAlignment="textStart"
+                android:textSize="16sp" />
+        </LinearLayout>
+
+        <View
+            android:layout_width="match_parent"
+            android:layout_height="1.5dp"
+            android:background="#000" />
+
+        <android.widget.Space
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginBottom="15dp"
+            android:background="#000" />
+
+        <Button
+            android:id="@+id/reset_filter_button"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="@string/activity_filter_reset_filter" />
+
+        <View
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:layout_weight="1" />
+
+        <Button
+            android:id="@+id/apply_filter_button"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="@string/activity_filter_apply_filter" />
+
+    </LinearLayout>
+</ScrollView>
\ No newline at end of file
diff --git a/app/src/main/res/layout/steps_streak_average.xml b/app/src/main/res/layout/steps_streak_average.xml
new file mode 100644
index 0000000..7270873
--- /dev/null
+++ b/app/src/main/res/layout/steps_streak_average.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:layout_gravity="center|top"
+    android:layout_marginStart="1dp"
+    android:layout_marginEnd="1dp"
+    android:layout_weight="1"
+    android:gravity="center|top"
+    android:orientation="vertical">
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:gravity="center"
+        android:maxLines="2"
+        android:scrollHorizontally="false"
+        android:text="@string/steps_streaks_average_steps"
+        android:textAllCaps="true"
+        android:textColor="@color/accent"
+        android:textStyle="bold"
+        android:tooltipText="@string/step_streak_average_steps_hint" />
+
+    <TextView
+        android:id="@+id/step_streak_average_value"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:fontFamily="sans-serif-black"
+        android:gravity="center"
+        android:maxLines="1"
+        android:scrollHorizontally="false"
+        android:text="15000"
+        android:textSize="24sp"
+        android:tooltipText="@string/step_streak_average_steps_hint" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/steps_streak_current_line_layout.xml b/app/src/main/res/layout/steps_streak_current_line_layout.xml
new file mode 100644
index 0000000..b987520
--- /dev/null
+++ b/app/src/main/res/layout/steps_streak_current_line_layout.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/step_streak_current_layout"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:layout_marginTop="5dp"
+    android:layout_marginBottom="5dp"
+    android:orientation="vertical">
+
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:layout_marginStart="1dp"
+        android:layout_marginEnd="1dp"
+        android:layout_weight="1"
+        android:gravity="center"
+        android:maxLines="2"
+        android:scrollHorizontally="false"
+        android:text="@string/step_streak_ongoing"
+        android:textAllCaps="true"
+        android:textSize="24sp"
+        android:textStyle="bold" />
+
+    <TextView
+        android:id="@+id/step_streak_current_date_value"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:layout_marginStart="1dp"
+        android:layout_marginEnd="1dp"
+        android:layout_weight="1"
+        android:gravity="center"
+        android:maxLines="2"
+        android:scrollHorizontally="false" />
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:baselineAligned="false"
+        android:gravity="bottom|center"
+        android:orientation="horizontal">
+
+        <include layout="@layout/steps_streak_average" />
+
+        <include layout="@layout/steps_streak_days" />
+
+        <include layout="@layout/steps_streak_total" />
+
+    </LinearLayout>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/steps_streak_days.xml b/app/src/main/res/layout/steps_streak_days.xml
new file mode 100644
index 0000000..bb32a5f
--- /dev/null
+++ b/app/src/main/res/layout/steps_streak_days.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/step_streak_days_layout"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:layout_gravity="center|top"
+    android:layout_marginStart="1dp"
+    android:layout_marginEnd="1dp"
+    android:layout_weight="1"
+    android:gravity="center|top"
+    android:orientation="vertical">
+
+    <TextView
+        android:id="@+id/step_streak_days_label"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:gravity="center"
+        android:maxLines="2"
+        android:scrollHorizontally="false"
+        android:text="@string/steps_streaks_streak_days"
+        android:textAllCaps="true"
+        android:textColor="@color/accent"
+        android:textStyle="bold"
+        android:tooltipText="@string/step_streak_days_hint" />
+
+    <TextView
+        android:id="@+id/step_streak_days_value"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:fontFamily="sans-serif-black"
+        android:gravity="center"
+        android:maxLines="1"
+        android:scrollHorizontally="false"
+        android:text="9150000"
+        android:textSize="24sp"
+        android:tooltipText="@string/step_streak_days_hint" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/steps_streak_maximum_line_layout.xml b/app/src/main/res/layout/steps_streak_maximum_line_layout.xml
new file mode 100644
index 0000000..83572b3
--- /dev/null
+++ b/app/src/main/res/layout/steps_streak_maximum_line_layout.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/step_streak_maximum_layout"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:layout_marginTop="5dp"
+    android:layout_marginBottom="5dp"
+    android:orientation="vertical">
+
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:layout_marginStart="1dp"
+        android:layout_marginEnd="1dp"
+        android:layout_weight="1"
+        android:gravity="center"
+        android:maxLines="2"
+        android:scrollHorizontally="false"
+        android:text="@string/step_streak_longest"
+        android:textAllCaps="true"
+        android:textSize="24sp"
+        android:textStyle="bold" />
+
+    <TextView
+        android:id="@+id/step_streak_maximum_date_value"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:layout_marginStart="1dp"
+        android:layout_marginEnd="1dp"
+        android:layout_weight="1"
+        android:gravity="center"
+        android:maxLines="2"
+        android:scrollHorizontally="false" />
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:baselineAligned="false"
+        android:gravity="bottom|center"
+        android:orientation="horizontal">
+
+        <include layout="@layout/steps_streak_average" />
+
+        <include layout="@layout/steps_streak_days" />
+
+        <include layout="@layout/steps_streak_total" />
+
+    </LinearLayout>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/steps_streak_total.xml b/app/src/main/res/layout/steps_streak_total.xml
new file mode 100644
index 0000000..278979a
--- /dev/null
+++ b/app/src/main/res/layout/steps_streak_total.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:layout_gravity="center|top"
+    android:layout_marginStart="1dp"
+    android:layout_marginEnd="1dp"
+    android:layout_weight="1"
+    android:gravity="center|top"
+    android:orientation="vertical">
+
+    <TextView
+        android:id="@+id/step_streak_total_label"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:gravity="center"
+        android:maxLines="2"
+        android:scrollHorizontally="false"
+        android:text="@string/steps_streaks_total_steps"
+        android:textAllCaps="true"
+        android:textColor="@color/accent"
+        android:textStyle="bold"
+        android:tooltipText="@string/steps_streaks_total_steps_hint" />
+
+    <TextView
+        android:id="@+id/step_streak_total_value"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:fontFamily="sans-serif-black"
+        android:gravity="center"
+        android:maxLines="1"
+        android:scrollHorizontally="false"
+        android:text="9150000"
+        android:textSize="24sp"
+        android:tooltipText="@string/steps_streaks_total_steps_hint" />
+
+
+</LinearLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/steps_streak_total_line_layout.xml b/app/src/main/res/layout/steps_streak_total_line_layout.xml
new file mode 100644
index 0000000..59c1d68
--- /dev/null
+++ b/app/src/main/res/layout/steps_streak_total_line_layout.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/step_streak_total_layout"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:layout_marginTop="5dp"
+    android:layout_marginBottom="0dp"
+    android:orientation="vertical">
+
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:layout_marginStart="1dp"
+        android:layout_marginEnd="1dp"
+        android:layout_weight="1"
+        android:gravity="center"
+        android:maxLines="2"
+        android:scrollHorizontally="false"
+        android:text="@string/step_streak_total"
+        android:textAllCaps="true"
+        android:textSize="24sp"
+        android:textStyle="bold" />
+
+    <TextView
+        android:id="@+id/step_streak_total_date_value"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:layout_marginStart="1dp"
+        android:layout_marginEnd="1dp"
+        android:layout_weight="1"
+        android:gravity="center"
+        android:maxLines="2"
+        android:scrollHorizontally="false" />
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:baselineAligned="false"
+        android:gravity="bottom|center"
+        android:orientation="horizontal">
+
+        <include layout="@layout/steps_streak_days" />
+
+        <include layout="@layout/steps_streak_total" />
+
+    </LinearLayout>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/steps_streaks_dashboard.xml b/app/src/main/res/layout/steps_streaks_dashboard.xml
new file mode 100644
index 0000000..0d2e0e5
--- /dev/null
+++ b/app/src/main/res/layout/steps_streaks_dashboard.xml
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="utf-8"?>
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/streaks_dashboard"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:minWidth="1000dp">
+
+    <RelativeLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+
+        <LinearLayout
+            android:id="@+id/streaks_dashboard_inner"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="vertical">
+
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="0dp"
+                android:layout_gravity="center"
+                android:layout_marginStart="1dp"
+                android:layout_marginEnd="1dp"
+                android:layout_weight="1"
+                android:gravity="center"
+                android:maxLines="2"
+                android:scrollHorizontally="false"
+                android:text="@string/steps_streaks"
+                android:textAllCaps="true"
+                android:textSize="24sp"
+                android:textStyle="bold"
+                android:tooltipText="@string/steps_streaks_hint" />
+
+            <include
+                layout="@layout/steps_streak_current_line_layout"
+                android:visibility="gone" />
+
+            <include
+                layout="@layout/steps_streak_maximum_line_layout"
+                android:visibility="gone" />
+
+            <include
+                layout="@layout/steps_streak_total_line_layout"
+                android:visibility="gone" />
+
+            <RelativeLayout
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:layout_marginTop="0dp"
+                android:layout_marginBottom="0dp"
+                android:gravity="center"
+                android:orientation="vertical">
+
+                <ImageView
+                    android:layout_width="100dp"
+                    android:layout_height="100dp"
+                    android:layout_marginStart="35dp"
+                    android:layout_marginTop="39dp"
+                    app:srcCompat="@drawable/ic_events_gold" />
+
+                <ProgressBar
+                    android:id="@+id/step_streak_dashboard_loading_circle"
+                    android:layout_width="171dp"
+                    android:layout_height="171dp"
+                    android:indeterminate="true" />
+            </RelativeLayout>
+        </LinearLayout>
+
+        <ImageButton
+            android:id="@+id/step_streak_share_button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_alignParentRight="true"
+            android:layout_alignParentBottom="true"
+            app:srcCompat="@drawable/ic_share" />
+    </RelativeLayout>
+</ScrollView>
\ No newline at end of file
diff --git a/app/src/main/res/layout/widget.xml b/app/src/main/res/layout/widget.xml
new file mode 100644
index 0000000..b037d0e
--- /dev/null
+++ b/app/src/main/res/layout/widget.xml
@@ -0,0 +1,215 @@
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/alarm_list_top_layout"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+
+    <LinearLayout
+        android:id="@+id/todaywidget_header_container"
+        android:layout_width="match_parent"
+        android:layout_height="34dp"
+        android:background="@color/accent"
+        android:orientation="horizontal">
+
+        <ImageView
+            android:id="@+id/todaywidget_header_icon"
+            android:layout_width="32dp"
+            android:layout_height="match_parent"
+            android:layout_gravity="center_vertical"
+            android:adjustViewBounds="false"
+            android:contentDescription="@string/icon_placeholder"
+            android:cropToPadding="false"
+            android:padding="0dp"
+            android:src="@drawable/ic_notification" />
+
+
+        <TextView
+            android:id="@+id/todaywidget_device_name"
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+            android:layout_marginLeft="8dp"
+            android:layout_marginRight="8dp"
+            android:layout_weight="1"
+            android:drawablePadding="4dp"
+            android:ellipsize="end"
+            android:gravity="center_vertical"
+            android:maxLines="1"
+            android:text="@string/app_name"
+            android:textColor="@color/primarytext_dark"
+            android:textSize="18sp" />
+
+        <ImageView
+            android:id="@+id/todaywidget_battery_icon"
+            android:layout_width="20dp"
+            android:layout_height="20dp"
+            android:layout_gravity="start|center_vertical"
+            android:backgroundTint="#FFFFFF"
+            android:background="@drawable/ic_battery_full"
+            android:contentDescription="@string/icon_placeholder"
+            android:padding="0dp" />
+
+        <TextView
+            android:id="@+id/todaywidget_device_status"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_vertical|end"
+            android:foregroundGravity="right"
+            android:gravity="end"
+            android:text="@string/device_not_connected"
+            android:textColor="@color/primarytext_dark"
+            android:textSize="18sp" />
+
+        <ImageView
+            android:id="@+id/todaywidget_header_alarm_icon"
+            android:layout_width="32dp"
+            android:layout_height="match_parent"
+            android:layout_gravity="center_vertical"
+            android:contentDescription="@string/icon_placeholder"
+            android:padding="0dp"
+            android:src="@drawable/ic_watchface" />
+
+
+    </LinearLayout>
+
+
+    <LinearLayout
+        android:id="@+id/todaywidget_bottom_layout"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:background="#2BE3DBDB"
+        android:baselineAligned="false"
+        android:orientation="horizontal"
+        android:paddingLeft="3dp"
+        android:paddingRight="3dp"
+        android:paddingBottom="5dp">
+
+        <LinearLayout
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:orientation="horizontal">
+
+            <ImageView
+                android:id="@+id/todaywidget_steps_icon"
+                android:layout_width="20dp"
+                android:layout_height="20dp"
+                android:layout_gravity="start|center_vertical"
+                android:backgroundTint="#FFFFFF"
+                android:background="@drawable/ic_shoe_prints_many"
+                android:contentDescription="@string/icon_placeholder"
+                android:padding="0dp" />
+
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:orientation="vertical"
+                android:paddingStart="10dp"
+                android:paddingEnd="10dp">
+
+                <TextView
+                    android:id="@+id/todaywidget_steps"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:gravity="center_vertical|center_horizontal"
+                    android:textColor="@color/primarytext_dark"
+                    android:textSize="18sp" />
+
+                <ProgressBar
+                    android:id="@+id/todaywidget_steps_progress"
+                    style="?android:attr/progressBarStyleHorizontal"
+                    android:layout_width="match_parent"
+                    android:layout_height="3dp"
+                    android:layout_weight="1"
+                    android:background="#FFFFFF" />
+            </LinearLayout>
+
+        </LinearLayout>
+
+        <LinearLayout
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:orientation="horizontal">
+
+            <ImageView
+                android:id="@+id/todaywidget_distance_icon"
+                android:layout_width="20dp"
+                android:layout_height="20dp"
+                android:layout_gravity="start|center_vertical"
+                android:backgroundTint="#FFFFFF"
+                android:background="@drawable/ic_distance_total"
+                android:contentDescription="@string/icon_placeholder"
+                android:padding="0dp" />
+
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:orientation="vertical"
+                android:paddingStart="10dp"
+                android:paddingEnd="10dp">
+
+                <TextView
+                    android:id="@+id/todaywidget_distance"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:gravity="center_vertical|center_horizontal"
+                    android:textColor="@color/primarytext_dark"
+                    android:textSize="18sp" />
+
+                <ProgressBar
+                    android:id="@+id/todaywidget_distance_progress"
+                    style="?android:attr/progressBarStyleHorizontal"
+                    android:layout_width="match_parent"
+                    android:layout_height="3dp"
+                    android:layout_weight="1"
+                    android:background="#FFFFFF" />
+            </LinearLayout>
+        </LinearLayout>
+
+        <LinearLayout
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:orientation="horizontal"
+            android:id="@+id/todaywidget_sleep_layout">
+
+            <ImageView
+                android:id="@+id/todaywidget_sleep_icon"
+                android:layout_width="20dp"
+                android:layout_height="20dp"
+                android:layout_gravity="start|center_vertical"
+                android:backgroundTint="#FFFFFF"
+                android:background="@drawable/ic_activity_sleep"
+                android:contentDescription="@string/icon_placeholder" />
+
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:orientation="vertical"
+                android:paddingStart="10dp"
+                android:paddingEnd="10dp">
+
+                <TextView
+                    android:id="@+id/todaywidget_sleep"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:gravity="center_vertical|center_horizontal"
+                    android:textColor="@color/primarytext_dark"
+                    android:textSize="18sp" />
+
+                <ProgressBar
+                    android:id="@+id/todaywidget_sleep_progress"
+                    style="?android:attr/progressBarStyleHorizontal"
+                    android:layout_width="match_parent"
+                    android:layout_height="3dp"
+                    android:layout_weight="1"
+                    android:background="#FFFFFF" />
+            </LinearLayout>
+        </LinearLayout>
+
+
+    </LinearLayout>
+
+
+</LinearLayout>
diff --git a/app/src/main/res/layout/widget_alarms_activity_list.xml b/app/src/main/res/layout/widget_alarms_activity_list.xml
new file mode 100644
index 0000000..eaabcfc
--- /dev/null
+++ b/app/src/main/res/layout/widget_alarms_activity_list.xml
@@ -0,0 +1,103 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/alarm_list_top_layout"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:background="@color/primary_dark"
+    android:gravity="center"
+    android:orientation="vertical">
+
+    <LinearLayout
+        android:id="@+id/todaywidget_alarm_header_container"
+        android:layout_width="match_parent"
+        android:layout_height="40dp"
+        android:background="@color/accent"
+        android:baselineAligned="false"
+        android:orientation="horizontal">
+
+
+        <LinearLayout
+            android:id="@+id/todaywidget_alarm_header_bar"
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:gravity="start"
+            android:paddingLeft="0dp"
+            android:paddingRight="0dp">
+
+            <TextView
+                android:id="@+id/todaywidget_alarm_list_header"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_vertical"
+                android:layout_marginLeft="8dp"
+                android:layout_marginRight="8dp"
+                android:drawablePadding="4dp"
+                android:ellipsize="end"
+                android:maxLines="1"
+                android:text="@string/widget_set_alarm_after"
+                android:textColor="@color/primarytext_dark"
+                android:textSize="14sp" />
+
+        </LinearLayout>
+
+
+    </LinearLayout>
+
+
+    <LinearLayout
+        android:id="@+id/todaywidget_alarm_list_bottom_layout"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:clickable="true"
+        android:focusable="true"
+        android:orientation="vertical"
+        android:paddingLeft="10dp"
+        android:paddingRight="10dp">
+
+
+        <TextView
+            android:id="@+id/alarm1"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:onClick="onClick"
+            android:text="@string/widget_5_minutes"
+            android:textSize="30sp" />
+
+        <TextView
+            android:id="@+id/alarm2"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:onClick="onClick"
+            android:text="@string/widget_10_minutes"
+            android:textSize="30sp" />
+
+        <TextView
+            android:id="@+id/alarm3"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:onClick="onClick"
+            android:text="@string/widget_20_minutes"
+            android:textSize="30sp" />
+
+        <TextView
+            android:id="@+id/alarm4"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:onClick="onClick"
+            android:text="@string/widget_1_hour"
+            android:textSize="30sp" />
+
+        <TextView
+            android:id="@+id/alarm5"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:onClick="onClick"
+            android:text="@string/miband_prefs_fitness_goal"
+            android:textSize="30sp" />
+    </LinearLayout>
+
+
+</LinearLayout>
+
+
diff --git a/app/src/main/res/menu/activity_hybridhr_watchface_designer_actionbar.xml b/app/src/main/res/menu/activity_hybridhr_watchface_designer_actionbar.xml
new file mode 100644
index 0000000..5f38d16
--- /dev/null
+++ b/app/src/main/res/menu/activity_hybridhr_watchface_designer_actionbar.xml
@@ -0,0 +1,17 @@
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+    <item
+        android:id="@+id/button_preview_watchface"
+        android:title="@string/button_watchface_preview"
+        android:icon="@drawable/ic_watch_bw"
+        app:iconTint="?attr/actionmenu_icon_color"
+        app:showAsAction="always"
+        />
+    <item
+        android:id="@+id/button_save_watchface"
+        android:title="@string/button_watchface_save_apply"
+        android:icon="@drawable/ic_done"
+        app:iconTint="?attr/actionmenu_icon_color"
+        app:showAsAction="always"
+        />
+</menu>
\ No newline at end of file
diff --git a/app/src/main/res/menu/activity_list_context_menu.xml b/app/src/main/res/menu/activity_list_context_menu.xml
new file mode 100644
index 0000000..a1a4144
--- /dev/null
+++ b/app/src/main/res/menu/activity_list_context_menu.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+
+    <item
+        android:id="@+id/activity_action_addto_filter"
+        android:icon="@drawable/ic_filter_alt_plus"
+        android:title="@string/addto_filter"
+        app:showAsAction="ifRoom" />
+
+    <item
+        android:id="@+id/activity_action_select_all"
+        android:icon="@drawable/ic_select_all"
+        android:title="@string/select_all"
+        app:showAsAction="ifRoom" />
+
+    <item
+        android:id="@+id/activity_action_export"
+        android:icon="@drawable/ic_share"
+        android:title="@string/share"
+        app:showAsAction="ifRoom" />
+
+    <item
+        android:id="@+id/activity_action_delete"
+        android:icon="@drawable/ic_delete_forever"
+        android:title="@string/Delete"
+        app:showAsAction="never"/>
+</menu>
\ No newline at end of file
diff --git a/app/src/main/res/menu/activity_list_menu.xml b/app/src/main/res/menu/activity_list_menu.xml
new file mode 100644
index 0000000..db25fcf
--- /dev/null
+++ b/app/src/main/res/menu/activity_list_menu.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+
+    <item
+        android:id="@+id/activity_action_filter"
+        android:icon="@drawable/ic_filter_alt"
+        android:title="@string/pref_header_filter"
+        app:iconTint="?attr/actionmenu_icon_color"
+        app:showAsAction="always" />
+
+    <item
+        android:id="@+id/overflowMenu"
+        android:icon="@drawable/ic_menu"
+        android:title="@string/menuitem_more"
+        app:iconTint="?attr/actionmenu_icon_color"
+        app:showAsAction="always">
+
+        <menu>
+            <item
+                android:id="@+id/activity_action_manage_timestamp"
+                android:icon="@drawable/ic_history"
+                android:title="@string/reset_index"
+                app:showAsAction="never" />
+        </menu>
+    </item>
+
+</menu>
\ No newline at end of file
diff --git a/app/src/main/res/menu/activity_main_drawer.xml b/app/src/main/res/menu/activity_main_drawer.xml
new file mode 100644
index 0000000..6b0f8cb
--- /dev/null
+++ b/app/src/main/res/menu/activity_main_drawer.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <group
+        android:checkableBehavior="single"
+        android:id="@+id/applicaction_options">
+        <item android:id="@+id/action_womp_workbench"
+            android:title="@string/womp_workbench"
+            android:icon="@drawable/ic_add" />
+        <item android:id="@+id/action_settings"
+            android:title="@string/action_settings"
+            android:icon="@drawable/ic_settings" />
+        <!-- 璋冭瘯/鏁版嵁绠$悊瀵瑰垎鎷e憳绔叧闂�
+        <item android:id="@+id/action_debug"
+            android:title="@string/action_debug"
+            android:icon="@drawable/ic_devices_other" />
+        <item android:id="@+id/action_data_management"
+            android:title="@string/action_db_management"
+            android:icon="@drawable/ic_pageview" />
+        -->
+        <item android:id="@+id/device_action_discover"
+            android:title="@string/action_discover"
+            android:iconTint="#7E7E7E"
+            android:icon="@drawable/ic_add" />
+
+        <item android:id="@+id/action_quit"
+            android:title="@string/action_quit"
+            android:icon="@drawable/ic_power_settings_new" />
+    </group>
+
+    <group
+        android:checkableBehavior="single"
+        android:id="@+id/further_options">
+        <!-- 鎹愯禒/鏇存柊鏃ュ織瀵瑰垎鎷e憳绔叧闂�
+        <item
+            android:id="@+id/donation_link"
+            android:title="@string/action_donate"
+            android:icon="@drawable/ic_donate" />
+        <item
+            android:id="@+id/external_changelog"
+            android:title="@string/action_changelog"
+            android:icon="@drawable/ic_history" />
+        -->
+        <item
+            android:id="@+id/about"
+            android:icon="@drawable/ic_info"
+            android:title="@string/about_title" />
+
+    </group>
+</menu>
diff --git a/app/src/main/res/menu/activity_take_screenshot_menu.xml b/app/src/main/res/menu/activity_take_screenshot_menu.xml
new file mode 100644
index 0000000..d87cc95
--- /dev/null
+++ b/app/src/main/res/menu/activity_take_screenshot_menu.xml
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+
+    <item
+        android:id="@+id/activity_detail_overflowMenu2"
+        android:icon="@drawable/ic_edit"
+        android:title="@string/appmanager_app_edit"
+        app:iconTint="?attr/actionmenu_icon_color"
+        app:showAsAction="ifRoom">
+
+        <menu>
+            <item
+                android:id="@+id/activity_summary_detail_action_edit_name"
+                android:icon="@drawable/ic_edit"
+                android:title="@string/activity_summary_edit_name_title"
+                app:showAsAction="never" />
+
+            <item
+                android:id="@+id/activity_summary_detail_action_edit_gps"
+                android:icon="@drawable/ic_gps_edit"
+                android:title="@string/activity_summary_detail_select_gpx_track"
+                app:showAsAction="never" />
+        </menu>
+    </item>
+
+    <item
+        android:id="@+id/activity_detail_overflowMenu"
+        android:icon="@drawable/ic_share"
+        android:title="@string/share"
+        app:iconTint="?attr/actionmenu_icon_color"
+        app:showAsAction="always">
+
+        <menu>
+            <item
+                android:id="@+id/activity_action_take_screenshot"
+                android:icon="@drawable/ic_screenshot"
+                android:title="@string/controlcenter_take_screenshot"
+                app:showAsAction="never" />
+
+            <item
+                android:id="@+id/activity_action_show_gpx"
+                android:icon="@drawable/ic_map"
+                android:title="@string/activity_detail_show_gps_label"
+                app:showAsAction="never" />
+
+            <item
+                android:id="@+id/activity_action_share_gpx"
+                android:icon="@drawable/ic_share"
+                android:title="@string/activity_detail_share_gps_label"
+                app:showAsAction="never" />
+
+            <item
+                android:id="@+id/activity_action_dev_tools"
+                android:icon="@drawable/ic_developer_mode"
+                android:title="@string/dev_tools"
+                app:showAsAction="never">
+                <menu>
+                    <item
+                        android:id="@+id/activity_action_dev_share_raw_summary"
+                        android:title="@string/activity_detail_share_raw_summary"
+                        app:showAsAction="never" />
+
+                    <item
+                        android:id="@+id/activity_action_dev_share_raw_details"
+                        android:title="@string/activity_detail_share_raw_details"
+                        app:showAsAction="never" />
+
+                    <item
+                        android:id="@+id/activity_action_dev_share_json_details"
+                        android:title="@string/activity_detail_share_json_details"
+                        app:showAsAction="never" />
+                </menu>
+            </item>
+
+        </menu>
+    </item>
+</menu>
diff --git a/app/src/main/res/menu/app_blacklist_menu.xml b/app/src/main/res/menu/app_blacklist_menu.xml
new file mode 100644
index 0000000..831d511
--- /dev/null
+++ b/app/src/main/res/menu/app_blacklist_menu.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+        <item
+            android:id="@+id/check_all_applications"
+            android:title="@string/check_all_applications">
+
+        </item>
+        <item
+            android:id="@+id/uncheck_all_applications"
+            android:title="@string/uncheck_all_applications" />
+</menu>
\ No newline at end of file
diff --git a/app/src/main/res/menu/appmanager_context.xml b/app/src/main/res/menu/appmanager_context.xml
new file mode 100644
index 0000000..6186ee2
--- /dev/null
+++ b/app/src/main/res/menu/appmanager_context.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android" >
+    <item
+        android:id="@+id/appmanager_watchface_activate"
+        android:title="@string/appmanager_watchface_activate"/>
+    <item
+        android:id="@+id/appmanager_app_start"
+        android:title="@string/appmanager_app_start"/>
+    <item
+        android:id="@+id/appmanager_app_edit"
+        android:title="@string/appmanager_app_edit"/>
+    <item
+        android:id="@+id/appmanager_app_reinstall"
+        android:title="@string/appmananger_app_reinstall"/>
+    <item
+        android:id="@+id/appmanager_app_share"
+        android:title="@string/appmanager_app_share"/>
+    <item
+        android:id="@+id/appmanager_app_download"
+        android:title="@string/appmanager_app_download"/>
+    <item
+        android:id="@+id/appmanager_app_delete"
+        android:title="@string/appmananger_app_delete"/>
+    <item
+        android:id="@+id/appmanager_app_delete_cache"
+        android:title="@string/appmananger_app_delete_cache"/>
+    <item
+        android:id="@+id/appmanager_health_activate"
+        android:title="@string/appmanager_health_activate"/>
+    <item
+        android:id="@+id/appmanager_health_deactivate"
+        android:title="@string/appmanager_health_deactivate"/>
+    <item
+        android:id="@+id/appmanager_hrm_activate"
+        android:title="@string/appmanager_hrm_activate"/>
+    <item
+        android:id="@+id/appmanager_hrm_deactivate"
+        android:title="@string/appmanager_hrm_deactivate"/>
+    <item
+        android:id="@+id/appmanager_weather_activate"
+        android:title="@string/appmanager_weather_activate" />
+    <item
+        android:id="@+id/appmanager_weather_deactivate"
+        android:title="@string/appmanager_weather_deactivate" />
+    <item
+        android:id="@+id/appmanager_weather_install_provider"
+        android:title="@string/appmanager_weather_install_provider" />
+    <item
+        android:id="@+id/appmanager_app_configure"
+        android:title="@string/app_configure"/>
+    <item
+        android:id="@+id/appmanager_app_openinstore"
+        android:title="@string/appmanager_app_openinstore"/>
+</menu>
\ No newline at end of file
diff --git a/app/src/main/res/menu/bottom_nav_menu.xml b/app/src/main/res/menu/bottom_nav_menu.xml
new file mode 100644
index 0000000..a54fa90
--- /dev/null
+++ b/app/src/main/res/menu/bottom_nav_menu.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <item
+        android:title="@string/bottom_nav_workbench"
+        android:id="@+id/bottom_nav_workbench"
+        android:icon="@drawable/ic_person"/>
+    <item
+        android:title="@string/bottom_nav_band"
+        android:id="@+id/bottom_nav_band"
+        android:icon="@drawable/ic_watch"/>
+
+</menu>
diff --git a/app/src/main/res/menu/dashboard_menu.xml b/app/src/main/res/menu/dashboard_menu.xml
new file mode 100644
index 0000000..2927899
--- /dev/null
+++ b/app/src/main/res/menu/dashboard_menu.xml
@@ -0,0 +1,18 @@
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    tools:context=".activities.DashboardFragment">
+
+    <item
+        android:id="@+id/dashboard_show_calendar"
+        android:icon="@drawable/ic_calendar_from"
+        android:title="@string/menuitem_calendar"
+        app:iconTint="?attr/actionmenu_icon_color"
+        app:showAsAction="ifRoom" />
+    <item
+        android:id="@+id/dashboard_settings"
+        android:icon="@drawable/ic_settings"
+        android:title="@string/dashboard_settings"
+        app:iconTint="?attr/actionmenu_icon_color"
+        app:showAsAction="never" />
+</menu>
diff --git a/app/src/main/res/menu/file_manager_file.xml b/app/src/main/res/menu/file_manager_file.xml
new file mode 100644
index 0000000..ace3644
--- /dev/null
+++ b/app/src/main/res/menu/file_manager_file.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+    <item
+        android:id="@+id/file_manager_file_menu_share"
+        android:title="@string/share" />
+</menu>
diff --git a/app/src/main/res/menu/fragment_devices_device_submenu.xml b/app/src/main/res/menu/fragment_devices_device_submenu.xml
new file mode 100644
index 0000000..766954b
--- /dev/null
+++ b/app/src/main/res/menu/fragment_devices_device_submenu.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+    <item
+        android:id="@+id/controlcenter_device_submenu_connect"
+        android:title="@string/controlcenter_connect" />
+    <item
+        android:id="@+id/controlcenter_device_submenu_disconnect"
+        android:title="@string/controlcenter_disconnect" />
+    <item
+        android:id="@+id/controlcenter_device_submenu_set_parent_folder"
+        android:title="@string/controlcenter_set_parent_folder" />
+    <item
+        android:id="@+id/controlcenter_device_submenu_set_alias"
+        android:title="@string/controlcenter_set_alias" />
+    <item
+        android:id="@+id/controlcenter_device_submenu_show_details"
+        android:title="@string/controlcenter_toggle_details" />
+    <item
+        android:id="@+id/controlcenter_device_submenu_installer"
+        android:title="@string/open_fw_installer_info_text_title" />
+
+    <item
+        android:id="@+id/controlcenter_device_submenu_remove"
+        android:title="@string/controlcenter_delete_device" />
+</menu>
\ No newline at end of file
diff --git a/app/src/main/res/menu/menu_charts.xml b/app/src/main/res/menu/menu_charts.xml
new file mode 100644
index 0000000..f440512
--- /dev/null
+++ b/app/src/main/res/menu/menu_charts.xml
@@ -0,0 +1,23 @@
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    tools:context="nodomain.freeyourgadget.gadgetbridge.activities.charts.ActivityChartsActivity">
+    <item
+        android:id="@+id/charts_fetch_activity_data"
+        android:icon="@drawable/ic_refresh"
+        android:title="@string/controlcenter_fetch_activity_data"
+        app:iconTint="?attr/actionmenu_icon_color"
+        app:showAsAction="ifRoom" />
+    <item
+        android:id="@+id/charts_set_date"
+        android:icon="@drawable/ic_calendar_from"
+        android:title="@string/menuitem_calendar"
+        app:iconTint="?attr/actionmenu_icon_color"
+        app:showAsAction="ifRoom" />
+    <item
+        android:id="@+id/prefs_charts_menu"
+        android:icon="@drawable/ic_settings"
+        android:title="@string/pref_header_charts"
+        app:iconTint="?attr/actionmenu_icon_color"
+        app:showAsAction="ifRoom" />
+</menu>
diff --git a/app/src/main/res/menu/menu_discovery.xml b/app/src/main/res/menu/menu_discovery.xml
new file mode 100644
index 0000000..f221d6f
--- /dev/null
+++ b/app/src/main/res/menu/menu_discovery.xml
@@ -0,0 +1,11 @@
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    tools:context="nodomain.freeyourgadget.gadgetbridge.activities.discovery.DiscoveryActivityV2">
+    <item
+        android:id="@+id/prefs_discovery_pairing"
+        android:icon="@drawable/ic_settings"
+        android:title="@string/activity_prefs_discovery_pairing"
+        app:iconTint="?attr/actionmenu_icon_color"
+        app:showAsAction="always" />
+</menu>
diff --git a/app/src/main/res/menu/menu_file_manager.xml b/app/src/main/res/menu/menu_file_manager.xml
new file mode 100644
index 0000000..e9b06cd
--- /dev/null
+++ b/app/src/main/res/menu/menu_file_manager.xml
@@ -0,0 +1,11 @@
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    tools:context="nodomain.freeyourgadget.gadgetbridge.activities.files.FileManagerActivity">
+    <item
+        android:id="@+id/file_manager_search"
+        android:icon="@drawable/ic_search"
+        android:title="@string/search"
+        app:iconTint="?attr/actionmenu_icon_color"
+        app:showAsAction="always" />
+</menu>
diff --git a/app/src/main/res/menu/menu_garmin_realtime_settings.xml b/app/src/main/res/menu/menu_garmin_realtime_settings.xml
new file mode 100644
index 0000000..7bc6749
--- /dev/null
+++ b/app/src/main/res/menu/menu_garmin_realtime_settings.xml
@@ -0,0 +1,17 @@
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    tools:context="nodomain.freeyourgadget.gadgetbridge.activities.charts.ActivityChartsActivity">
+    <item
+        android:id="@+id/garmin_rt_debug_toggle"
+        android:icon="@drawable/ic_developer_mode"
+        android:title="@string/toggle_debug_mode"
+        app:iconTint="?attr/actionmenu_icon_color"
+        app:showAsAction="never" />
+    <item
+        android:id="@+id/garmin_rt_debug_share"
+        android:icon="@drawable/ic_share"
+        android:title="@string/share_debug_info"
+        app:iconTint="?attr/actionmenu_icon_color"
+        app:showAsAction="never" />
+</menu>
diff --git a/app/src/main/res/menu/musicmanager_context.xml b/app/src/main/res/menu/musicmanager_context.xml
new file mode 100644
index 0000000..82263c5
--- /dev/null
+++ b/app/src/main/res/menu/musicmanager_context.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+    <item
+        android:id="@+id/musicmanager_add_to_playlist"
+        android:title="@string/music_add_to_playlist"/>
+    <item
+        android:id="@+id/musicmanager_delete_from_playlist"
+        android:title="@string/music_delete_from_playlist"/>
+    <item
+        android:id="@+id/musicmanager_delete"
+        android:title="@string/music_delete"/>
+</menu>
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..ef49c99
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@color/ic_launcher_background"/>
+    <foreground android:drawable="@drawable/ic_launcher_foreground"/>
+    <monochrome android:drawable="@drawable/ic_launcher_foreground"/>
+</adaptive-icon>
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 0000000..ef49c99
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@color/ic_launcher_background"/>
+    <foreground android:drawable="@drawable/ic_launcher_foreground"/>
+    <monochrome android:drawable="@drawable/ic_launcher_foreground"/>
+</adaptive-icon>
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..7ece8eb
--- /dev/null
+++ b/app/src/main/res/mipmap-hdpi/ic_launcher.png
Binary files differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
new file mode 100644
index 0000000..f04f9e3
--- /dev/null
+++ b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
Binary files differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..58f879a
--- /dev/null
+++ b/app/src/main/res/mipmap-mdpi/ic_launcher.png
Binary files differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
new file mode 100644
index 0000000..e37a361
--- /dev/null
+++ b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
Binary files differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..9ff6018
--- /dev/null
+++ b/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Binary files differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..16c438a
--- /dev/null
+++ b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
Binary files differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..766f407
--- /dev/null
+++ b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..15eddba
--- /dev/null
+++ b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
Binary files differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..99fc1b4
--- /dev/null
+++ b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Binary files differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..f19a541
--- /dev/null
+++ b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
Binary files differ
diff --git a/app/src/main/res/raw/ping_tone.rtttl b/app/src/main/res/raw/ping_tone.rtttl
new file mode 100644
index 0000000..af619fb
--- /dev/null
+++ b/app/src/main/res/raw/ping_tone.rtttl
@@ -0,0 +1 @@
+ping:d=8,o=5,b=120:d,2p,d,2p,d,2p,d,2p
\ No newline at end of file
diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml
new file mode 100644
index 0000000..525703a
--- /dev/null
+++ b/app/src/main/res/values-ar/strings.xml
@@ -0,0 +1,3548 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources xmlns:tools="http://schemas.android.com/tools">
+    <string name="action_settings">丕賱廿毓丿丕丿丕鬲</string>
+    <string name="action_quit">丕賳賴丕亍</string>
+    <string name="controlcenter_connect">丕鬲氐賱鈥�</string>
+    <string name="battery_level">賲爻鬲賵賶 丕賱亘胤丕乇賷丞</string>
+    <string name="title_activity_settings">廿毓丿丕丿丕鬲</string>
+    <string name="action_donate">鬲亘乇毓</string>
+    <string name="find_lost_device_message">丕亘丨孬 毓賳 %1$s責</string>
+    <string name="controlcenter_delete_device">丨匕賮 丕賱噩賴丕夭</string>
+    <string name="controlcenter_delete_device_name">丨匕賮 %1$s</string>
+    <string name="controlcenter_delete_device_dialogmessage">爻賷丐丿賷 賴匕丕 廿賱賶 丨匕賮 丕賱噩賴丕夭 賵噩賲賷毓 丕賱亘賷丕賳丕鬲 丕賱賲乇鬲亘胤丞 亘賴!</string>
+    <string name="controlcenter_set_alias">鬲毓賷賷賳 丕賱丕爻賲 丕賱賲爻鬲毓丕乇</string>
+    <string name="controlcenter_snackbar_disconnecting">賷鬲賲 賯胤毓 丕賱丕鬲氐丕賱</string>
+    <string name="controlcenter_snackbar_connecting">鬲賵氐賷賱鈥�</string>
+    <string name="controlcenter_snackbar_requested_screenshot">兀禺匕 賱賯胤丞 卮丕卮丞 賱賱噩賴丕夭</string>
+    <string name="controlcenter_calibrate_device">賲毓丕賷乇丞 丕賱噩賴丕夭</string>
+    <string name="battery_detail_activity_title">賲毓賱賵賲丕鬲 丕賱亘胤丕乇賷丞</string>
+    <string name="calendar_six_months">6 丕卮賴乇</string>
+    <string name="calendar_year">爻賳丞</string>
+    <string name="title_activity_appmanager">賲丿賷乇 丕賱鬲胤亘賷賯</string>
+    <string name="appmanager_installed_watchfaces">賵丕噩賴丕鬲 丕賱爻丕毓丞 丕賱賲孬亘鬲丞</string>
+    <string name="appmananger_app_delete">丨匕賮</string>
+    <string name="appmananger_app_delete_cache">丨匕賮 賵廿夭丕賱丞 賲賳 匕丕賰乇丞 丕賱鬲禺夭賷賳 丕賱賲丐賯鬲</string>
+    <string name="appmananger_app_reinstall">兀毓丿 丕賱鬲孬亘賷鬲</string>
+    <string name="appmanager_weather_install_provider">賯賲 亘鬲孬亘賷鬲 鬲胤亘賷賯 廿禺胤丕乇丕鬲 丕賱胤賯爻</string>
+    <string name="app_move_to_top">丕賳賯賱 廿賱賶 丕賱兀毓賱賶</string>
+    <string name="title_activity_notification_management">廿毓丿丕丿丕鬲 丕賱鬲賳亘賷賴</string>
+    <string name="uncheck_all_applications">廿賱睾丕亍 鬲丨丿賷丿 噩賲賷毓 丕賱鬲胤亘賷賯丕鬲</string>
+    <string name="check_all_applications">鬲丨丿賷丿 噩賲賷毓 丕賱鬲胤亘賷賯丕鬲</string>
+    <string name="fw_upgrade_notice">鬲賵卮賰 毓賱賶 鬲孬亘賷鬲 %s.</string>
+    <string name="pref_header_general">丕賱丕毓丿丕丿丕鬲 丕賱毓丕賲丞</string>
+    <string name="pref_default">丕賮鬲乇丕囟賷</string>
+    <string name="pref_header_datetime">丕賱鬲丕乇賷禺 賵 丕賱賵賯鬲</string>
+    <string name="pref_title_datetime_syctimeonconnect">賲夭丕賲賳丞 丕賱鬲賵賯賷鬲</string>
+    <string name="pref_theme_light">賮丕鬲丨</string>
+    <string name="pref_theme_dark">丿丕賰賳</string>
+    <string name="controlcenter_change_led_color">鬲睾賷賷乇 賱賵賳 丕賱 LED</string>
+    <string name="calendar_day">賷賵賲</string>
+    <string name="calendar_week">兀爻亘賵毓</string>
+    <string name="calendar_two_weeks">廿爻亘賵毓賷賳</string>
+    <string name="calendar_month">卮賴乇</string>
+    <string name="controlcenter_snackbar_need_longpress">丕囟睾胤 賱賮鬲乇丞 胤賵賷賱丞 毓賱賶 丕賱亘胤丕賯丞 賱賯胤毓 丕賱丕鬲氐丕賱</string>
+    <string name="debugactivity_really_factoryreset">爻賷丐丿賷 廿噩乇丕亍 廿毓丕丿丞 囟亘胤 丕賱賲氐賳毓 廿賱賶 丨匕賮 噩賲賷毓 丕賱亘賷丕賳丕鬲 賲賳 丕賱噩賴丕夭 丕賱賲鬲氐賱 (廿匕丕 賰丕賳 賲丿毓賵賲賸丕). 鬲賯賵賲 兀噩賴夭丞 Xiaomi / Huami 兀賷囟賸丕 亘鬲睾賷賷乇 Bluetooth MAC address 貙 亘丨賷孬 鬲馗賴乇 賰兀噩賴夭丞 噩丿賷丿丞 賮賷 Gadgetbridge.</string>
+    <string name="controlcenter_take_screenshot">禺丿 賱賯胤丞 賱賱卮丕卮丞</string>
+    <string name="controlcenter_change_fm_frequency">鬲睾賷賷乇 鬲乇丿丿 FM</string>
+    <string name="controlcenter_disconnect">賯胤毓 丕賱丕鬲氐丕賱</string>
+    <string name="calendar_three_months">3 丕卮賴乇</string>
+    <string name="debugactivity_really_factoryreset_title">丨賯丕 廿毓丕丿丞 囟亘胤 丕賱賲氐賳毓責</string>
+    <string name="appmanager_cached_watchapps_watchfaces">鬲胤亘賷賯丕鬲 賮賷 匕丕賰乇丞 丕賱鬲禺夭賷賳 丕賱賲丐賯鬲</string>
+    <string name="appmanager_installed_watchapps">丕賱鬲胤亘賷賯丕鬲 丕賱賲孬亘鬲丞</string>
+    <string name="pref_title_general_autostartonboot">丕亘丿兀 鬲賱賯丕卅賷賸丕</string>
+    <string name="pref_title_general_autoreconnect">兀毓丿 丕賱丕鬲氐丕賱 鬲賱賯丕卅賷賸丕</string>
+    <string name="pref_title_theme">爻賲丞</string>
+    <string name="pref_header_notifications">廿卮毓丕乇丕鬲</string>
+    <string name="pref_title_notifications_repetitions">鬲賰乇丕乇</string>
+    <string name="pref_title_whenscreenon">鈥� 兀賷囟賸丕 毓賳丿 鬲卮睾賷賱 丕賱卮丕卮丞</string>
+    <string name="pref_summary_transliteration">賯賲 亘鬲賲賰賷賳 賴匕丕 廿匕丕 賰丕賳 噩賴丕夭賰 賱丕 賷丿毓賲 禺胤 賱睾鬲賰</string>
+    <string name="pref_rtl_max_line_length">兀賯氐賶 胤賵賱 賱賱禺胤 賲賳 丕賱賷賲賷賳 廿賱賶 丕賱賷爻丕乇</string>
+    <string name="pref_rtl_max_line_length_summary">賷胤賷賱 兀賵 賷賯氐乇 丕賱兀爻胤乇 丕賱鬲賷 賷鬲賲 賮氐賱 丕賱賳氐 賲賳 丕賱賷賲賷賳 廿賱賶 丕賱賷爻丕乇 廿賱賷賴丕</string>
+    <string name="pref_call_privacy_mode_name">廿禺賮丕亍 丕賱丕爻賲 賵賱賰賳 兀毓乇囟 丕賱乇賯賲</string>
+    <string name="pref_title_pebble_activitytracker">賲鬲毓賯亘 丕賱賳卮丕胤 丕賱賲賮囟賱</string>
+    <string name="pref_title_enable_outgoing_call">丿毓賲 丕賱賲賰丕賱賲丕鬲 丕賱氐丕丿乇丞</string>
+    <string name="pref_title_enable_pebblekit">丕賱爻賲丕丨 亘丕賱賵氐賵賱 廿賱賶 鬲胤亘賷賯丕鬲 Android 丕賱鬲丕亘毓丞 賱噩賴丕鬲 禺丕乇噩賷丞</string>
+    <string name="pref_title_sunrise_sunset">卮乇賵賯 丕賱卮賲爻 賵睾乇賵亘賴丕</string>
+    <string name="pref_title_enable_calendar_sync">賲夭丕賲賳丞 丕賱鬲賯賵賷賲</string>
+    <string name="pref_sensors_bp_calibration">賲毓丕賷乇丞 囟睾胤 丕賱丿賲</string>
+    <string name="steps">禺胤賵丕鬲</string>
+    <string name="seconds_km">孬丕賳賷丞/賰賲</string>
+    <string name="seconds_m">孬丕賳賷丞/賲</string>
+    <string name="minutes_km">丿賯賷賯丞/賰賲</string>
+    <string name="bpm">賳亘囟丞 賮賷 丕賱丿賯賷賯丞</string>
+    <string name="Elevation">丕乇鬲賮丕毓</string>
+    <string name="Speed">爻乇毓丞</string>
+    <string name="activity_detail_end_label">賳賴丕賷丞</string>
+    <string name="activity_filter_date_to">廿賱賶</string>
+    <string name="qhybrid_calibration_counterclockwise">毓賰爻 毓賯丕乇亘 丕賱爻丕毓賴</string>
+    <string name="qhybrid_calibration_clockwise">賮賷 丕鬲噩丕賴 毓賯丕乇亘 丕賱爻丕毓丞</string>
+    <string name="watchface_widget_type_date">鬲丕乇賷禺</string>
+    <string name="watchface_dialog_widget_preset_left">賷爻丕乇</string>
+    <string name="watchface_dialog_widget_preset_right">賷賲賷賳</string>
+    <string name="watchface_dialog_widget_color">丕賱賱賵賳</string>
+    <string name="pref_title_language">賱睾丞</string>
+    <string name="pref_title_call_privacy_mode">賵囟毓 禺氐賵氐賷丞 丕賱丕鬲氐丕賱</string>
+    <string name="pref_title_pebble_settings">廿毓丿丕丿丕鬲 Pebble</string>
+    <string name="prefs_title_all_day_heart_rate">賯賷丕爻 賲毓丿賱 賳亘囟丕鬲 丕賱賯賱亘 胤賵丕賱 丕賱賷賵賲</string>
+    <string name="pref_title_notification_use_as">丕爻鬲禺丿賲 賯丕卅賲丞 丕賱鬲胤亘賷賯丕鬲 賱鈥�</string>
+    <string name="pref_title_rtl">賲賳 丕賱賷賲賷賳 廿賱賶 丕賱賷爻丕乇</string>
+    <string name="pref_summary_rtl">賯賲 亘鬲賲賰賷賳 賴匕丕 廿匕丕 賰丕賳 噩賴丕夭賰 賱丕 賷賲賰賳賴 毓乇囟 丕賱賱睾丕鬲 賲賳 丕賱賷賲賷賳 廿賱賶 丕賱賷爻丕乇</string>
+    <string name="pref_theme_system">賳馗丕賲</string>
+    <string name="pref_title_notifications_call">丕賱賲賰丕賱賲丕鬲 丕賱賴丕鬲賮賷丞</string>
+    <string name="pref_title_notification_filter">毓丿賲 丕賱廿夭毓丕噩</string>
+    <string name="pref_applications_settings">賯丕卅賲丞 丕賱鬲胤亘賷賯丕鬲</string>
+    <string name="pref_title_canned_replies">丕賱乇丿賵丿</string>
+    <string name="pref_title_notifications_timeout">丕賱丨丿 丕賱兀丿賳賶 賲賳 丕賱賵賯鬲 亘賷賳 丕賱廿卮毓丕乇丕鬲</string>
+    <string name="pref_header_privacy">禺氐賵氐賷丞</string>
+    <string name="pref_call_privacy_mode_off">毓乇囟 丕賱丕爻賲 賵丕賱乇賯賲</string>
+    <string name="pref_title_weather">丕賱胤賯爻</string>
+    <string name="pref_title_location_keep_uptodate">鬲丨丿賷孬 丕賱賲賵賯毓 亘丕爻鬲賲乇丕乇</string>
+    <string name="toast_aqurired_networklocation">鬲賲 丕賱丨氐賵賱 毓賱賶 丕賱賲賵賯毓</string>
+    <string name="pref_display_add_device_fab_on">賲乇卅賷 丿丕卅賲丕</string>
+    <string name="pref_title_notifications_sms">丕賱乇爻丕卅賱 丕賱賯氐賷乇丞</string>
+    <string name="pref_call_privacy_mode_number">廿禺賮丕亍 丕賱乇賯賲 賵賱賰賳 兀毓乇囟 丕賱丕爻賲</string>
+    <string name="pref_title_weather_location">賲賵賯毓 丕賱胤賯爻 (賱賲夭賵丿 丕賱胤賯爻 賮賷 LineageOS)</string>
+    <string name="watchface_dialog_widget_type">丕賱賳賵毓</string>
+    <string name="pref_title_message_privacy_mode">賵囟毓 禺氐賵氐賷丞 丕賱乇爻丕卅賱</string>
+    <string name="pref_message_privacy_mode_complete">廿禺賮丕亍 賰賱 丕賱賲丨鬲賵賶</string>
+    <string name="pref_title_sync_caldendar">賲夭丕賲賳丞 兀丨丿丕孬 丕賱鬲賯賵賷賲</string>
+    <string name="pref_title_vibration_strength">賯賵丞 丕賱丕賴鬲夭丕夭</string>
+    <string name="pref_display_add_device_fab_off">賲乇卅賷 賮賯胤 賮賷 丨丕賱丞 毓丿賲 廿囟丕賮丞 兀賷 噩賴丕夭</string>
+    <string name="always">丿賵賲丕</string>
+    <string name="pref_title_location_latitude">禺胤 丕賱毓乇囟</string>
+    <string name="pref_title_location_longitude">禺胤 丕賱胤賵賱</string>
+    <string name="pref_title_pebble_forceuntested">鬲賲賰賷賳 丕賱賲賷夭丕鬲 睾賷乇 丕賱賲禺鬲亘乇丞</string>
+    <string name="pref_summary_allow_high_mtu">賷夭賷丿 賲賳 爻乇毓丞 丕賱賳賯賱貙 賵賱賰賳 賯丿 賱丕 賷毓賲賱 毓賱賶 亘毓囟 兀噩賴夭丞 Android.</string>
+    <string name="power_mode_normal">毓丕丿賷</string>
+    <string name="laps">賱賮丕鬲</string>
+    <string name="descentSeconds">鬲賳丕夭賱賷</string>
+    <string name="calories_unit">爻毓乇賴 丨乇丕乇賷賴</string>
+    <string name="when_screen_off">毓賳丿賲丕 鬲賰賵賳 丕賱卮丕卮丞 賲睾賱賯丞</string>
+    <string name="never">兀亘丿丕</string>
+    <string name="pref_call_privacy_mode_complete">廿禺賮丕亍 丕賱丕爻賲 賵丕賱乇賯賲</string>
+    <string name="pref_message_privacy_mode_off">毓乇囟 賰賱 丕賱賲丨鬲賵賶</string>
+    <string name="pref_title_timeformat">鬲賳爻賷賯 丕賱賵賯鬲</string>
+    <string name="watchface_dialog_widget_color_black">兀爻賵丿</string>
+    <string name="ascentSeconds">鬲氐丕毓丿賷</string>
+    <string name="activity_detail_duration_label">丕賱賲丿丞</string>
+    <string name="pref_title_canned_messages_set">丕賱鬲丨丿賷孬 毓賱賶 丕賱噩賴丕夭</string>
+    <string name="pref_header_development">禺賷丕乇丕鬲 丕賱賲胤賵乇</string>
+    <string name="prefs_sensors_button_bp_calibration_sum">丕囟睾胤 賴賳丕 賱亘丿亍 丕賱賲毓丕賷乇丞</string>
+    <string name="pref_title_development_miaddr">毓賳賵丕賳 Mi Band</string>
+    <string name="pref_header_activitytrackers">賲鬲鬲亘毓丕鬲 丕賱賳卮丕胤</string>
+    <string name="prefs_notifications_and_calls_reject">夭乇 鬲噩丕賴賱 / 乇賮囟 丕賱賲賰丕賱賲丞</string>
+    <string name="pref_summary_enable_pebblekit">鬲賲賰賷賳 丕賱丿毓賲 丕賱鬲噩乇賷亘賷 賱鬲胤亘賷賯丕鬲 Android 亘丕爻鬲禺丿丕賲 PebbleKit</string>
+    <string name="zetime_title_settings">廿毓丿丕丿丕鬲 ZeTime</string>
+    <string name="Distance">賲爻丕賮丞</string>
+    <string name="pref_summary_sunrise_sunset">兀乇爻賱 兀賵賯丕鬲 卮乇賵賯 丕賱卮賲爻 賵睾乇賵亘賴丕 亘賳丕亍賸 毓賱賶 丕賱賲賵賯毓 廿賱賶 賲禺胤胤 Pebble 丕賱夭賲賳賷</string>
+    <string name="swimStyle">丕爻賱賵亘 丕賱爻亘丕丨丞</string>
+    <string name="pref_header_location">賲賵賯毓</string>
+    <string name="pref_title_custom_deviceicon">廿馗賴丕乇 乇賲夭 廿毓賱丕賲 禺丕氐 亘丕賱噩賴丕夭</string>
+    <string name="pref_title_pebble_privacy_mode">賵囟毓 丕賱禺氐賵氐賷丞</string>
+    <string name="pref_title_location_aquire">丕賱丨氐賵賱 毓賱賶 丕賱賲賵賯毓</string>
+    <string name="pref_summary_location_keep_uptodate">丨丕賵賱 丕賱丨氐賵賱 毓賱賶 丕賱賲賵賯毓 丕賱丨丕賱賷 賮賷 賵賯鬲 丕賱鬲卮睾賷賱 貙 賵丕爻鬲禺丿賲 丕賱賲賵賯毓 丕賱賲禺夭賳 賰亘丿賷賱</string>
+    <string name="pref_summary_notifications_and_calls_enable_misscall">賷鬲賰乇乇 賮賷 賰賱 丿賯賷賯丞</string>
+    <string name="pref_title_notifications_and_calls_repeat_on_misscall">賰乇乇 賱賲丿丞 X 丿賯丕卅賯</string>
+    <string name="prefs_sensors_button_bp_calibration">賲毓丕賷乇丞</string>
+    <string name="steps_unit">禺胤賵丕鬲</string>
+    <string name="meters_second">賲 / 孬</string>
+    <string name="km_h">賰賲/ 爻丕毓丞</string>
+    <string name="qhybrid_title_apps">鬲胤亘賷賯丕鬲</string>
+    <string name="qhybrid_title_calibration">賲毓丕賷乇丞</string>
+    <string name="fossil_hr_new_action_cancel">廿賱睾丕亍</string>
+    <string name="prefs_left">賷爻丕乇</string>
+    <string name="pref_display_add_device_fab">夭乇 鬲賵氐賷賱 噩賴丕夭 噩丿賷丿</string>
+    <string name="caloriesBurnt">爻毓乇丕鬲 丨乇丕乇賷賴</string>
+    <string name="maxSpeed">兀賯氐賶</string>
+    <string name="averageKMPaceSeconds">賵鬲賷乇賴</string>
+    <string name="minutes_mi">丿賯賷賯丞 / 賲賷賱</string>
+    <string name="Activity">賳卮丕胤</string>
+    <string name="cm">爻賲</string>
+    <string name="pref_title_unit_system">丕賱賵丨丿丕鬲</string>
+    <string name="mi_h">賲賷賱 / 爻丕毓丞</string>
+    <string name="pref_summary_sync_calendar">鬲賲賰賷賳 鬲賳亘賷賴丕鬲 丕賱鬲賯賵賷賲貙 丨鬲賶 毓賳丿 賯胤毓 丕賱丕鬲氐丕賱</string>
+    <string name="pref_header_device_spec_settings">廿毓丿丕丿丕鬲 丕賱噩賴丕夭</string>
+    <string name="pref_title_pebble_reconnect_attempts">賲丨丕賵賱丕鬲 廿毓丕丿丞 丕賱丕鬲氐丕賱</string>
+    <string name="pref_title_huami_force_new_protocol">亘乇賵鬲賵賰賵賱 丕賱賲氐丕丿賯賴 丕賱噩丿賷丿</string>
+    <string name="pref_header_notifications_and_calls">丕賱廿禺胤丕乇丕鬲 賵丕賱賲賰丕賱賲丕鬲</string>
+    <string name="averageHR">賲毓丿賱 賳亘囟丕鬲 丕賱賯賱亘</string>
+    <string name="mi">賲賷賱</string>
+    <string name="activity_filter_to_placeholder">丕賱賷賵賲</string>
+    <string name="watchface_widget_type_weather">胤賯爻</string>
+    <string name="Steps">禺胤賵丕鬲</string>
+    <string name="power_mode_saving">鬲賵賮賷乇 丕賱胤丕賯丞</string>
+    <string name="laps_unit">賱賮丕鬲</string>
+    <string name="zetime_title_heartrate">廿毓丿丕丿丕鬲 賲毓丿賱 賳亘囟丕鬲 丕賱賯賱亘</string>
+    <string name="ft">賯丿賲</string>
+    <string name="yes">賳毓賲</string>
+    <string name="activity_summaries_statistics">廿丨氐丕卅賷丕鬲</string>
+    <string name="breaststroke">爻亘丕丨丞 丕賱氐丿乇</string>
+    <string name="activity_detail_start_label">亘丿亍</string>
+    <string name="activity_filter_date_from">賲賳</string>
+    <string name="no">賱丕</string>
+    <string name="freestyle">丨乇賴</string>
+    <string name="backstroke">馗賴乇</string>
+    <string name="fossil_hr_edit_action_delete">丨匕賮</string>
+    <string name="qhybrid_pref_title_actions">兀噩乇丕亍丕鬲</string>
+    <string name="watchface_widget_type_steps">丕賱禺胤賵丕鬲</string>
+    <string name="appmanager_app_edit">丨乇乇</string>
+    <string name="maxAltitude">丕賱丨丿 丕賱丕賯氐賷</string>
+    <string name="minAltitude">丕賱丨丿 丕賱丕丿賳賷</string>
+    <string name="Swimming">爻亘丕丨丞</string>
+    <string name="km">賰賷賱賵賲鬲乇</string>
+    <string name="watchface_dialog_widget_preset_bottom">賯丕毓</string>
+    <string name="pref_title_equalizer_clear">賵丕囟丨</string>
+    <string name="watchface_widget_type_calories">爻毓乇丕鬲 丨乇丕乇賷賴</string>
+    <string name="watchface_dialog_widget_preset_top">賯賲丞</string>
+    <string name="watchface_dialog_widget_color_white">兀亘賷囟</string>
+    <string name="watchface_widget_type_battery">亘胤丕乇賷丞</string>
+    <string name="prefs_galaxy_buds_experimental">鬲噩乇賷亘賷</string>
+    <string name="prefs_right">賷賲賷賳</string>
+    <string name="controlcenter_fetch_activity_data">丕賱賲夭丕賲賳賴</string>
+    <string name="controlcenter_find_device">丕亘丨孬 毓賳 噩賴丕夭 賲賮賯賵丿</string>
+    <string name="app_configure">鬲賴賷卅丞</string>
+    <string name="title_activity_fw_app_insaller">FW / 賲孬亘鬲 丕賱鬲胤亘賷賯</string>
+    <string name="pref_title_audio_player">賲卮睾賱 丕賱氐賵鬲 丕賱賲賮囟賱</string>
+    <string name="pref_theme_black_background">丕爻鬲禺丿丕賲 禺賱賮賷丞 爻賵丿丕亍 賮賷 丕賱爻賲丞 丕賱丿丕賰賳丞</string>
+    <string name="appmanager_watchface_activate">鬲賮毓賷賱</string>
+    <string name="appmanager_download_started">亘丿兀 鬲賳夭賷賱 丕賱鬲胤亘賷賯</string>
+    <string name="appmanager_downloaded_to_cache">鬲賲 鬲賳夭賷賱 %s 廿賱賶 匕丕賰乇丞 丕賱鬲禺夭賷賳 丕賱賲丐賯鬲</string>
+    <string name="fw_upgrade_notice_amazfitgts">兀賳鬲 毓賱賶 賵卮賰 鬲孬亘賷鬲 丕賱亘乇賳丕賲噩 丕賱孬丕亘鬲 %s 毓賱賶 Amazfit GTS.
+\n
+\n賷乇噩賶 丕賱鬲兀賰丿 賲賳 鬲孬亘賷鬲 賲賱賮 .fw 貙 孬賲 賲賱賮 .res 貙 賵兀禺賷乇丕 賲賱賮 .gps. 爻賷鬲賲 廿毓丕丿丞 鬲卮睾賷賱 爻丕毓鬲賰 亘毓丿 鬲孬亘賷鬲 賲賱賮 .fw.
+\n
+\n賲賱丕丨馗丞: 賱丕 賷賱夭賲 鬲孬亘賷鬲 .res 賵 .gps 廿匕丕 賰丕賳鬲 賴匕賴 丕賱賲賱賮丕鬲 賴賷 賳賮爻賴丕 鬲賲丕賲丕 賲孬賱 丕賱賲賱賮丕鬲 丕賱賲孬亘鬲丞 賲爻亘賯丕.
+\n
+\n丕賱賲囟賷 賯丿賲丕 毓賱賶 賲爻丐賵賱賷鬲賰 丕賱禺丕氐丞!</string>
+    <string name="miband_firmware_known">鬲賲 丕禺鬲亘丕乇 賴匕丕 丕賱亘乇賳丕賲噩 丕賱孬丕亘鬲 賵賲賳 丕賱賲毓乇賵賮 兀賳賴 賲鬲賵丕賮賯 賲毓 Gadgetbridge.</string>
+    <string name="pref_header_other">兀禺乇賶</string>
+    <string name="pref_header_calendar">丕賱鬲賯賵賷賲</string>
+    <string name="fw_upgrade_notice_miband7">兀賳鬲 毓賱賶 賵卮賰 鬲孬亘賷鬲 丕賱亘乇賳丕賲噩 丕賱孬丕亘鬲 %s 毓賱賶 Xiaomi Smart Band 7.
+\n
+\n爻鬲鬲賲 廿毓丕丿丞 鬲卮睾賷賱 丕賱爻賵丕乇 亘毓丿 鬲孬亘賷鬲 賲賱賮 zip.
+\n
+\n丕賱賲囟賷 賯丿賲丕 毓賱賶 賲爻丐賵賱賷鬲賰 丕賱禺丕氐丞!</string>
+    <string name="pref_header_time">丕賱賵賯鬲</string>
+    <string name="pref_header_workout">丕賱鬲賲乇賷賳</string>
+    <string name="single_band">賳胤丕賯 賵丕丨丿</string>
+    <string name="accuracy">丕賱丿賯丞</string>
+    <string name="balanced">賲鬲賵丕夭賳</string>
+    <string name="power_saving">鬲賵賮賷乇 丕賱胤丕賯丞</string>
+    <string name="custom">賲購禺氐賾氐</string>
+    <string name="dual_band">賳胤丕賯 賲夭丿賵噩</string>
+    <string name="low_power_gps">賳馗丕賲 鬲丨丿賷丿 丕賱賲賵丕賯毓 丕賱毓丕賱賲賷 (GPS) 賲賳禺賮囟 丕賱胤丕賯丞</string>
+    <string name="gps">鬲丨丿賷丿 丕賱賲賵丕賯毓</string>
+    <string name="gps_bds">賳馗丕賲 鬲丨丿賷丿 丕賱賲賵丕賯毓 + BDS</string>
+    <string name="gps_gnolass">賳馗丕賲 鬲丨丿賷丿 丕賱賲賵丕賯毓 + 噩賳賵賱丕爻</string>
+    <string name="gps_galileo">賳馗丕賲 鬲丨丿賷丿 丕賱賲賵丕賯毓 + 睾丕賱賷賱賷賵</string>
+    <string name="all_satellites">噩賲賷毓 丕賱兀賯賲丕乇 丕賱氐賳丕毓賷丞</string>
+    <string name="speed_first">丕賱爻乇毓丞 兀賵賱丕</string>
+    <string name="accuracy_first">丕賱丿賯丞 兀賵賱丕</string>
+    <string name="appmanager_hrm_deactivate">鬲毓胤賷賱 HRM</string>
+    <string name="appmanager_weather_activate">鬲賮毓賷賱 鬲胤亘賷賯 胤賯爻 丕賱賳馗丕賲</string>
+    <string name="appmanager_weather_deactivate">賯賲 亘廿賱睾丕亍 鬲賳卮賷胤 鬲胤亘賷賯 胤賯爻 丕賱賳馗丕賲</string>
+    <string name="fw_upgrade_notice_miband4">兀賳鬲 毓賱賶 賵卮賰 鬲孬亘賷鬲 丕賱亘乇賳丕賲噩 丕賱孬丕亘鬲 %s 毓賱賶 Mi Band 4.
+\n
+\n賷乇噩賶 丕賱鬲兀賰丿 賲賳 鬲孬亘賷鬲 賲賱賮 .fw 貙 賵亘毓丿 匕賱賰 賲賱賮 .res. 爻賷鬲賲 廿毓丕丿丞 鬲卮睾賷賱 丕賱賮乇賯丞 丕賱禺丕氐丞 亘賰 亘毓丿 鬲孬亘賷鬲 賲賱賮 .fw.
+\n
+\n賲賱丕丨馗丞: 賱丕 賷鬲毓賷賳 毓賱賷賰 鬲孬亘賷鬲 .res 廿匕丕 賰丕賳 賲胤丕亘賯丕 鬲賲丕賲丕 賱賱鬲孬亘賷鬲 丕賱匕賷 鬲賲 鬲孬亘賷鬲賴 賲爻亘賯丕.
+\n
+\n丕賱賲囟賷 賯丿賲丕 毓賱賶 賲爻丐賵賱賷鬲賰 丕賱禺丕氐丞!</string>
+    <string name="fw_upgrade_notice_amazfit_gtr4">兀賳鬲 毓賱賶 賵卮賰 鬲孬亘賷鬲 丕賱亘乇賳丕賲噩 丕賱孬丕亘鬲 %s 毓賱賶 噩賴丕夭 Amazfit GTR 4.
+\n
+\n爻鬲鬲賲 廿毓丕丿丞 鬲卮睾賷賱 丕賱爻賵丕乇 亘毓丿 鬲孬亘賷鬲 賲賱賮 zip.
+\n
+\n丕賱賲囟賷 賯丿賲丕 毓賱賶 賲爻丐賵賱賷鬲賰 丕賱禺丕氐丞!</string>
+    <string name="about_activity_title_generic">丨賵賱 Gadgetbridge</string>
+    <string name="about_description_generic">丕爻鬲亘丿丕賱 丨乇 賲鬲乇賵賰 丕賱丨賯賵賯 亘丿賵賳 爻丨丕亘丞 賱鬲胤亘賷賯丕鬲 賵 兀丿賵丕鬲 丕賳丿乇賵賷丿 賲睾賱賯丞 丕賱賲氐丿乇 賲賳 丕賱亘丕卅毓賷賳.</string>
+    <string name="gadgetbridge_running_generic">Gadgetbridge 賯賷丿 丕賱鬲卮睾賷賱</string>
+    <string name="about_activity_title_banglejs_main">丨賵賱 Bangle.js Gadgetbridge</string>
+    <string name="about_description_banglejs_main">鬲胤亘賷賯 賲氐丕丨亘 賱賳馗丕賲 Android 賱賭 Bangle.js 賲亘賳賷 毓賱賶 賯賲丞 賲卮乇賵毓 Gadgetbridge 貙 賲毓 賵氐賵賱 廿囟丕賮賷 廿賱賶 丕賱廿賳鬲乇賳鬲.
+\n
+\n賳馗乇賸丕 賱爻賷丕爻丕鬲 賲鬲噩乇 Google Play 貙 賱丕 賷購爻賲丨 賱賳丕 亘丕爻鬲禺丿丕賲 乇丕亘胤 鬲亘乇毓 賮賷 丕賱鬲胤亘賷賯 賳賮爻賴 貙 賵賱賰賳 廿匕丕 兀毓噩亘賰 賴匕丕 丕賱鬲胤亘賷賯 貙 賮賷乇噩賶 丕賱鬲賮賰賷乇 賮賷 丕賱鬲亘乇毓 毓亘乇 氐賮丨丞 Gadgetbridge 丕賱乇卅賷爻賷丞 兀丿賳丕賴.</string>
+    <string name="gadgetbridge_running_banglejs_main">Bangle.js 賯賷丿 丕賱鬲卮睾賷賱</string>
+    <string name="about_description_main_nightly">鬲胤亘賷賯 丨乇 賲賮鬲賵丨 丕賱賲氐丿乇 賲鬲乇賵賰 丕賱丨賯賵賯 亘丿賵賳 爻丨丕亘丞 亘丿賷賱 賱鬲胤亘賷賯丕鬲 兀丿賵丕鬲 丕賳丿乇賵賷丿 賲睾賱賯丞 丕賱賲氐丿乇 賲賳 丕賱賲氐賳毓賷賳. 丕賱廿氐丿丕乇丕鬲 丕賱賱賷賱賷丞 賲賳 Gadgetbridge. 賱丕 賷賲賰賳 鬲孬亘賷鬲賴丕 廿匕丕 賰賳鬲 亘丕賱賮毓賱 賲孬亘鬲丕 Gadgetbridge 兀賵 鬲胤亘賷賯 Pebble 貙 亘爻亘亘 鬲毓丕乇囟 賮賷 賲賵賮乇 Pebble.</string>
+    <string name="application_name_main_nopebble">Gadgetbridge (丕賱賷賱賷, 賱丕賷賵噩丿 賲賵賮乇 Pebble)</string>
+    <string name="about_description_main_nopebble">丕爻鬲亘丿丕賱 丨乇 亘丿賵賳 丨賯賵賯 賲鬲乇賵賰丞 丕賱丨賯賵賯 亘丿賵賳 爻丨丕亘丞 賱鬲胤亘賷賯丕鬲 兀丿賵丕鬲 丕賳丿乇賵賷丿 賲睾賱賯丞 丕賱賲氐丿乇 賲賳 丕賱亘丕卅毓賷賳. 丕賱廿氐丿丕乇丕鬲 丕賱賱賷賱賷丞 賲賳 Gadgetbridge. 賷丨鬲賵賷 賴匕丕 丕賱廿氐丿丕乇 毓賱賶 賲賵賮乇 Pebble 丕賱匕賷 鬲賲鬲 廿毓丕丿丞 鬲爻賲賷鬲賴 賱賲賳毓 丕賱鬲毓丕乇囟丕鬲 貙 賱匕賱賰 賱賳 鬲毓賲賱 亘毓囟 毓賲賱賷丕鬲 丕賱鬲賰丕賲賱 匕丕鬲 丕賱氐賱丞 亘賭 Pebble 貙 賵賱賰賳 賷賲賰賳 鬲孬亘賷鬲賴丕 噩賳亘賸丕 廿賱賶 噩賳亘 賲毓 鬲孬亘賷鬲 Gadgetbridge 丕賱丨丕賱賷.</string>
+    <string name="controlcenter_power_off">廿賷賯丕賮 丕賱鬲卮睾賷賱</string>
+    <string name="controlcenter_power_off_confirm_title">廿賷賯丕賮 丕賱鬲卮睾賷賱</string>
+    <string name="controlcenter_power_off_confirm_description">賴賱 兀賳鬲 賲鬲兀賰丿 賲賳 兀賳賰 鬲乇賷丿 廿賷賯丕賮 鬲卮睾賷賱 丕賱噩賴丕夭責</string>
+    <string name="controlcenter_navigation_drawer_open">賮鬲丨 丿乇噩 丕賱鬲賳賯賱</string>
+    <string name="controlcenter_navigation_drawer_close">廿睾賱丕賯 丿乇噩 丕賱鬲賳賯賱</string>
+    <string name="prefs_activity_in_device_card_steps_title_summary">廿馗賴丕乇 賲噩賲賵毓 丕賱禺胤賵丕鬲</string>
+    <string name="prefs_activity_in_device_card_distance_title_summary">賷鬲賲 丨爻丕亘 丕賱賲爻丕賮丞 賲賳 丕賱禺胤賵丕鬲 賵胤賵賱 丕賱禺胤賵丞 (賯丕亘賱 賱賱鬲毓丿賷賱 賮賷 丕賱廿毓丿丕丿丕鬲 - 丨賵賱賰 兀賳鬲)</string>
+    <string name="blacklist_all_for_notifications">廿丿乇丕噩 丕賱賰賱 賮賷 丕賱賯丕卅賲丞 丕賱爻賵丿丕亍 賱賱廿卮毓丕乇丕鬲</string>
+    <string name="whitelist_all_for_notifications">廿囟丕賮丞 丕賱賰賱 廿賱賶 丕賱賯丕卅賲丞 丕賱亘賷囟丕亍 賱賱廿卮毓丕乇丕鬲</string>
+    <string name="fw_upgrade_notice_amazfitgtr">兀賳鬲 毓賱賶 賵卮賰 鬲孬亘賷鬲 丕賱亘乇賳丕賲噩 丕賱孬丕亘鬲 %s 毓賱賶 Amazfit GTR.
+\n
+\n賷乇噩賶 丕賱鬲兀賰丿 賲賳 鬲孬亘賷鬲 賲賱賮 .fw 貙 孬賲 賲賱賮 .res 貙 賵兀禺賷乇丕 賲賱賮 .gps. 爻賷鬲賲 廿毓丕丿丞 鬲卮睾賷賱 爻丕毓鬲賰 亘毓丿 鬲孬亘賷鬲 賲賱賮 .fw.
+\n
+\n賲賱丕丨馗丞: 賱丕 賷賱夭賲 鬲孬亘賷鬲 .res 賵 .gps 廿匕丕 賰丕賳鬲 賴匕賴 丕賱賲賱賮丕鬲 賴賷 賳賮爻賴丕 鬲賲丕賲丕 賲孬賱 丕賱賲賱賮丕鬲 丕賱賲孬亘鬲丞 賲爻亘賯丕.
+\n
+\n丕賱賲囟賷 賯丿賲丕 毓賱賶 賲爻丐賵賱賷鬲賰 丕賱禺丕氐丞!</string>
+    <string name="pref_summary_datetime_syctimeonconnect">賲夭丕賲賳丞 丕賱賵賯鬲 賲毓 兀噩賴夭丞 Gadgetbridge 毓賳丿 丕賱丕鬲氐丕賱貙 賵毓賳丿賲丕 賷鬲睾賷乇 丕賱賵賯鬲 兀賵 丕賱賲賳胤賯丞 丕賱夭賲賳賷丞 毓賱賶 噩賴丕夭 丕賳丿乇賵賷丿貙 賵亘卮賰賱 丿賵乇賷</string>
+    <string name="fw_upgrade_notice_amazfit_gts4">兀賳鬲 毓賱賶 賵卮賰 鬲孬亘賷鬲 丕賱亘乇賳丕賲噩 丕賱孬丕亘鬲 %s 毓賱賶 Amazfit GTS 4.
+\n
+\n爻鬲鬲賲 廿毓丕丿丞 鬲卮睾賷賱 丕賱爻賵丕乇 亘毓丿 鬲孬亘賷鬲 賲賱賮 zip.
+\n
+\n丕賱賲囟賷 賯丿賲丕 毓賱賶 賲爻丐賵賱賷鬲賰 丕賱禺丕氐丞!</string>
+    <string name="appmanager_item_outdated">(毓賮丕 毓賱賷賴 丕賱夭賲賳)</string>
+    <string name="open_fw_installer_warning_title">鬲丨匕賷乇</string>
+    <string name="open_fw_installer_getting_files_title">丕賱丨氐賵賱 毓賱賶 賲賱賮 丕賱亘乇賳丕賲噩 丕賱孬丕亘鬲 / 丕賱鬲胤亘賷賯</string>
+    <string name="pref_header_display">丕賱卮丕卮丞</string>
+    <string name="prefs_activity_in_device_card_title_summary">廿馗賴丕乇 丕賱禺胤賵丕鬲 丕賱丨丕賱賷丞 兀賵 丕賱賲爻丕賮丞 兀賵 丕賱爻賰賵賳 毓賱賶 亘胤丕賯丞 丕賱噩賴丕夭</string>
+    <string name="prefs_activity_in_device_card_sleep_title_summary">毓乇囟 賲丿丞 丕賱賳賵賲</string>
+    <string name="controlcenter_get_heartrate_measurement">丕賱丨氐賵賱 毓賱賶 賯賷丕爻 賱賲毓丿賱 賳亘囟丕鬲 丕賱賯賱亘</string>
+    <string name="device_card_activity_card_title">賲毓賱賵賲丕鬲 丕賱賳卮丕胤 毓賱賶 亘胤丕賯丞 丕賱噩賴丕夭</string>
+    <string name="title_activity_calblacklist">丕賱鬲賯丕賵賷賲 丕賱賲丿乇噩丞 賮賷 丕賱賯丕卅賲丞 丕賱爻賵丿丕亍</string>
+    <string name="action_debug">丕賱鬲氐丨賷丨</string>
+    <string name="title_activity_debug">鬲氐丨賷丨</string>
+    <string name="open_fw_installer_warning_text">賴匕賴 丕賱賲賷夭丞 賱丿賷賴丕 丕賱賯丿乇丞 毓賱賶 毓胤亘 噩賴丕夭賰. 賵賲毓 匕賱賰 貙 賱賲 賷丨丿孬 賴匕丕 賲胤賱賯賸丕 賱兀賷 賲賳 丕賱賲胤賵乇賷賳 賲賳 禺賱丕賱 丕賱鬲孬亘賷鬲 貙 賵賱賰賳 鬲匕賰乇 兀賳賰 鬲賯賵賲 亘匕賱賰 毓賱賶 賲爻丐賵賱賷鬲賰 丕賱禺丕氐丞.</string>
+    <string name="open_fw_installer_pick_file">丨丿丿 丕賱賲賱賮</string>
+    <string name="open_fw_installer_select_file">丨丿丿 丕賱賲賱賮 丕賱匕賷 鬲乇賷丿 鬲丨賲賷賱賴 毓賱賶 丕賱噩賴丕夭: %s</string>
+    <string name="pref_header_system">丕賱賳馗丕賲</string>
+    <string name="pref_header_health">丕賱氐丨丞</string>
+    <string name="pref_header_equalizer">丕賱賲毓丕丿賱</string>
+    <string name="pref_title_minimize_priority">廿禺賮丕亍 廿卮毓丕乇 Gadgetbridge</string>
+    <string name="about_description_banglejs_nightly">鬲胤亘賷賯 賲氐丕丨亘 賱賳馗丕賲 Android 賱賭 Bangle.js 賲亘賳賷 毓賱賶 賲卮乇賵毓 Gadgetbridge 貙 賲毓 賵氐賵賱 廿囟丕賮賷 廿賱賶 丕賱廿賳鬲乇賳鬲.
+\n
+\n賳馗乇賸丕 賱爻賷丕爻丕鬲 賲鬲噩乇 Google Play 貙 賱丕 賷購爻賲丨 賱賳丕 亘丕爻鬲禺丿丕賲 乇丕亘胤 鬲亘乇毓 賮賷 丕賱鬲胤亘賷賯 賳賮爻賴 貙 賵賱賰賳 廿匕丕 兀毓噩亘賰 賴匕丕 丕賱鬲胤亘賷賯 貙 賮賷乇噩賶 丕賱鬲賮賰賷乇 賮賷 丕賱鬲亘乇毓 毓亘乇 氐賮丨丞 Gadgetbridge 丕賱乇卅賷爻賷丞 兀丿賳丕賴.</string>
+    <string name="fw_multi_upgrade_notice">兀賳鬲 毓賱賶 賵卮賰 鬲孬亘賷鬲 丕賱亘乇丕賲噩 丕賱孬丕亘鬲丞 %1$s 賵 %2$s 貙 亘丿賱丕賸 賲賳 丕賱亘乇丕賲噩 丕賱賲賵噩賵丿丞 丨丕賱賷賸丕 毓賱賶 Mi Band.</string>
+    <string name="open_fw_installer_info_text_title">賲孬亘鬲 丕賱賲賱賮丕鬲</string>
+    <string name="open_fw_installer_getting_files_text">賳馗乇賸丕 賱兀賳賳丕 賯丿 賱丕 賳賵夭毓 賲賱賮丕鬲 丕賱亘乇丕賲噩 丕賱孬丕亘鬲丞 貙 賮爻賷鬲毓賷賳 毓賱賷賰 丕賱丨氐賵賱 毓賱賶 丕賱賲賱賮丕鬲 亘賳賮爻賰. 賴匕丕 賷毓賳賷 兀賳賰 爻鬲丨鬲丕噩 廿賱賶 丕賱亘丨孬 毓賳 丕賱賲賱賮丕鬲 賮賷 賲賱賮丕鬲 apk 貙 毓亘乇 丕賱廿賳鬲乇賳鬲 貙 賮賷 丕賱賲賳鬲丿賷丕鬲 貙 毓賱賶 Amazfitwatchfaces (賱兀噩賴夭丞 Miband / Amazfit) 賵賲丕 廿賱賶 匕賱賰.</string>
+    <string name="fw_upgrade_notice_amazfit_trex2">兀賳鬲 毓賱賶 賵卮賰 鬲孬亘賷鬲 丕賱亘乇賳丕賲噩 丕賱孬丕亘鬲 %s 毓賱賶 Amazfit T-Rex 2.
+\n
+\n爻鬲鬲賲 廿毓丕丿丞 鬲卮睾賷賱 丕賱爻賵丕乇 亘毓丿 鬲孬亘賷鬲 賲賱賮 zip.
+\n
+\n丕賱賲囟賷 賯丿賲丕 毓賱賶 賲爻丐賵賱賷鬲賰 丕賱禺丕氐丞!</string>
+    <string name="fw_upgrade_notice_amazfitneo">兀賳鬲 毓賱賶 賵卮賰 鬲孬亘賷鬲 丕賱亘乇賳丕賲噩 丕賱孬丕亘鬲 %s 毓賱賶 噩賴丕夭 Amazfit Neo 丕賱禺丕氐 亘賰.
+\n
+\n爻鬲鬲賲 廿毓丕丿丞 鬲卮睾賷賱 丕賱爻賵丕乇 亘毓丿 鬲孬亘賷鬲 賲賱賮 .fw.
+\n
+\n丕賱賲囟賷 賯丿賲丕 毓賱賶 賲爻丐賵賱賷鬲賰 丕賱禺丕氐丞!</string>
+    <string name="appmanager_app_share">賲卮丕乇賰丞</string>
+    <string name="fw_upgrade_notice_amazfitbip">兀賳鬲 毓賱賶 賵卮賰 鬲孬亘賷鬲 丕賱亘乇賳丕賲噩 丕賱孬丕亘鬲 %s 毓賱賶 Amazfit Bip.
+\n
+\n賷乇噩賶 丕賱鬲兀賰丿 賲賳 鬲孬亘賷鬲 賲賱賮 .fw 貙 孬賲 賲賱賮 .res 貙 賵兀禺賷乇丕 賲賱賮 .gps. 爻賷鬲賲 廿毓丕丿丞 鬲卮睾賷賱 爻丕毓鬲賰 亘毓丿 鬲孬亘賷鬲 賲賱賮 .fw.
+\n
+\n賲賱丕丨馗丞: 賱丕 賷賱夭賲 鬲孬亘賷鬲 .res 賵 .gps 廿匕丕 賰丕賳鬲 賴匕賴 丕賱賲賱賮丕鬲 賴賷 賳賮爻賴丕 鬲賲丕賲丕 賲孬賱 丕賱賲賱賮丕鬲 丕賱賲孬亘鬲丞 賲爻亘賯丕.
+\n
+\n丕賱賲囟賷 賯丿賲丕 毓賱賶 賲爻丐賵賱賷鬲賰 丕賱禺丕氐丞!</string>
+    <string name="fw_upgrade_notice_amazfitcor2">兀賳鬲 毓賱賶 賵卮賰 鬲孬亘賷鬲 丕賱亘乇賳丕賲噩 丕賱孬丕亘鬲 %s 毓賱賶 Amazfit Cor 2.
+\n
+\n賷乇噩賶 丕賱鬲兀賰丿 賲賳 鬲孬亘賷鬲 賲賱賮 .fw 貙 賵亘毓丿 匕賱賰 賲賱賮 .res. 爻賷鬲賲 廿毓丕丿丞 鬲卮睾賷賱 丕賱賮乇賯丞 丕賱禺丕氐丞 亘賰 亘毓丿 鬲孬亘賷鬲 賲賱賮 .fw.
+\n
+\n賲賱丕丨馗丞: 賱丕 賷鬲毓賷賳 毓賱賷賰 鬲孬亘賷鬲 .res 廿匕丕 賰丕賳 賲胤丕亘賯丕 鬲賲丕賲丕 賱賱鬲孬亘賷鬲 丕賱匕賷 鬲賲 鬲孬亘賷鬲賴 賲爻亘賯丕.
+\n
+\n丕賱賲囟賷 賯丿賲丕 毓賱賶 賲爻丐賵賱賷鬲賰 丕賱禺丕氐丞!
+\n
+\n賱賲 賷鬲賲 丕禺鬲亘丕乇賴 鬲賲丕賲丕 貙 乇亘賲丕 鬲丨鬲丕噩 廿賱賶 賵賲賷囟 亘乇賳丕賲噩 孬丕亘鬲 BEATS_W 廿匕丕 賰丕賳 丕爻賲 噩賴丕夭賰 賴賵 \"Amazfit Band 2\"</string>
+    <string name="open_fw_installer_connect_maximum_one_device">丕賱乇噩丕亍 鬲賵氐賷賱 噩賴丕夭 賵丕丨丿 賮賯胤 丕賱匕賷 鬲乇賷丿 廿乇爻丕賱 丕賱賲賱賮 廿賱賷賴.</string>
+    <string name="open_fw_installer_ensure_device_connected">鬲兀賰丿 賲賳 鬲賵氐賷賱 丕賱噩賴丕夭 %s</string>
+    <string name="appmanager_hrm_activate">鬲賮毓賷賱 HRM</string>
+    <string name="debugactivity_confirm_remove_device_preferences_title">廿夭丕賱丞 鬲賮囟賷賱丕鬲 丕賱噩賴丕夭責</string>
+    <string name="appmanager_app_openinstore">丕賱亘丨孬 賮賷 賲鬲噩乇 鬲胤亘賷賯丕鬲 Pebble</string>
+    <string name="fw_upgrade_notice_amazfit_gts4_mini">兀賳鬲 毓賱賶 賵卮賰 鬲孬亘賷鬲 丕賱亘乇賳丕賲噩 丕賱孬丕亘鬲 %s 毓賱賶 Amazfit GTS 4 Mini.
+\n
+\n爻鬲鬲賲 廿毓丕丿丞 鬲卮睾賷賱 丕賱爻賵丕乇 亘毓丿 鬲孬亘賷鬲 賲賱賮 zip.
+\n
+\n丕賱賲囟賷 賯丿賲丕 毓賱賶 賲爻丐賵賱賷鬲賰 丕賱禺丕氐丞!</string>
+    <string name="fw_upgrade_notice_amazfit_band7">兀賳鬲 毓賱賶 賵卮賰 鬲孬亘賷鬲 丕賱亘乇賳丕賲噩 丕賱孬丕亘鬲 %s 毓賱賶 Amazfit Band 7.
+\n
+\n爻鬲鬲賲 廿毓丕丿丞 鬲卮睾賷賱 丕賱爻賵丕乇 亘毓丿 鬲孬亘賷鬲 賲賱賮 zip.
+\n
+\n丕賱賲囟賷 賯丿賲丕 毓賱賶 賲爻丐賵賱賷鬲賰 丕賱禺丕氐丞!</string>
+    <string name="pref_header_connection">丕賱廿鬲氐丕賱</string>
+    <string name="pref_header_sound_vibration">丕賱氐賵鬲 賵丕賱丕賴鬲夭丕夭</string>
+    <string name="pref_header_offline_voice">氐賵鬲 睾賷乇 賲鬲氐賱</string>
+    <string name="device_card_activity_card_title_summary">丕禺鬲賷丕乇 鬲賮丕氐賷賱 丕賱賳卮丕胤 丕賱鬲賷 賷鬲賲 毓乇囟賴丕 毓賱賶 亘胤丕賯丞 丕賱噩賴丕夭</string>
+    <string name="prefs_activity_in_device_card_title">廿馗賴丕乇 賲毓賱賵賲丕鬲 丕賱賳卮丕胤 毓賱賶 亘胤丕賯丞 丕賱噩賴丕夭</string>
+    <string name="prefs_activity_in_device_card_sleep_title">丕賱賳賵賲</string>
+    <string name="appmanager_app_start">亘丿亍</string>
+    <string name="debugactivity_confirm_remove_device_preferences">爻賷丐丿賷 賴匕丕 廿賱賶 廿毓丕丿丞 鬲毓賷賷賳 鬲賮囟賷賱丕鬲 丕賱噩賴丕夭 賱噩賲賷毓 丕賱兀噩賴夭丞 丕賱賲鬲氐賱丞. 賴賱 兀賳鬲 賲鬲兀賰丿責</string>
+    <string name="appmanager_app_download">鬲賳夭賷賱 廿賱賶 匕丕賰乇丞 丕賱鬲禺夭賷賳 丕賱賲丐賯鬲</string>
+    <string name="fw_upgrade_notice_amazfit_trex">兀賳鬲 毓賱賶 賵卮賰 鬲孬亘賷鬲 丕賱亘乇賳丕賲噩 丕賱孬丕亘鬲 %s 毓賱賶 Amazfit T-Rex.
+\n
+\n賷乇噩賶 丕賱鬲兀賰丿 賲賳 鬲孬亘賷鬲 賲賱賮 .fw 貙 孬賲 賲賱賮 .res 貙 賵兀禺賷乇丕 賲賱賮 .gps. 爻賷鬲賲 廿毓丕丿丞 鬲卮睾賷賱 爻丕毓鬲賰 亘毓丿 鬲孬亘賷鬲 賲賱賮 .fw.
+\n
+\n賲賱丕丨馗丞: 賱丕 賷賱夭賲 鬲孬亘賷鬲 .res 賵 .gps 廿匕丕 賰丕賳鬲 賴匕賴 丕賱賲賱賮丕鬲 賴賷 賳賮爻賴丕 鬲賲丕賲丕 賲孬賱 丕賱賲賱賮丕鬲 丕賱賲孬亘鬲丞 賲爻亘賯丕.
+\n
+\n丕賱賲囟賷 賯丿賲丕 毓賱賶 賲爻丐賵賱賷鬲賰 丕賱禺丕氐丞!</string>
+    <string name="fw_upgrade_notice_amazfitx">兀賳鬲 毓賱賶 賵卮賰 鬲孬亘賷鬲 丕賱亘乇賳丕賲噩 丕賱孬丕亘鬲 %s 毓賱賶 噩賴丕夭 Amazfit X.
+\n
+\n賷乇噩賶 丕賱鬲兀賰丿 賲賳 鬲孬亘賷鬲 賲賱賮 .fw 貙 賵亘毓丿 匕賱賰 賲賱賮 .res. 爻鬲鬲賲 廿毓丕丿丞 鬲卮睾賷賱 丕賱爻賵丕乇 亘毓丿 鬲孬亘賷鬲 賲賱賮 .fw.
+\n
+\n賲賱丕丨馗丞: 賱賷爻 毓賱賷賰 鬲孬亘賷鬲 .res 廿匕丕 賰丕賳 賲胤丕亘賯賸丕 鬲賲丕賲賸丕 賱賲丕 鬲賲 鬲孬亘賷鬲賴 賲爻亘賯賸丕.
+\n
+\n丕賱賲囟賷 賯丿賲丕 毓賱賶 賲爻丐賵賱賷鬲賰 丕賱禺丕氐丞!</string>
+    <string name="appmanager_health_activate">鬲賮毓賷賱</string>
+    <string name="appmanager_health_deactivate">鬲毓胤賷賱</string>
+    <string name="fw_upgrade_notice_amazfitvergel">兀賳鬲 毓賱賶 賵卮賰 鬲孬亘賷鬲 丕賱亘乇賳丕賲噩 丕賱孬丕亘鬲 %s 毓賱賶 Amazfit Verge Lite.
+\n
+\n賷乇噩賶 丕賱鬲兀賰丿 賲賳 鬲孬亘賷鬲 賲賱賮 .fw 貙 孬賲 賲賱賮 .res 貙 賵兀禺賷乇賸丕 賲賱賮 .gps. 爻鬲鬲賲 廿毓丕丿丞 鬲卮睾賷賱 爻丕毓鬲賰 亘毓丿 鬲孬亘賷鬲 賲賱賮 .fw.
+\n
+\n賲賱丕丨馗丞: 賱賷爻 毓賱賷賰 鬲孬亘賷鬲 .res 賵. gps 廿匕丕 賰丕賳鬲 賴匕賴 丕賱賲賱賮丕鬲 賲胤丕亘賯丞 鬲賲丕賲賸丕 賱鬲賱賰 丕賱鬲賷 鬲賲 鬲孬亘賷鬲賴丕 賲爻亘賯賸丕.
+\n
+\n丕賱賲囟賷 賯丿賲丕 毓賱賶 賲爻丐賵賱賷鬲賰 丕賱禺丕氐丞!</string>
+    <string name="appmanager_download_app_error">禺胤兀 賮賷 鬲賳夭賷賱 丕賱鬲胤亘賷賯</string>
+    <string name="fw_upgrade_notice_amazfitbip_lite">兀賳鬲 毓賱賶 賵卮賰 鬲孬亘賷鬲 丕賱亘乇賳丕賲噩 丕賱孬丕亘鬲 %s 毓賱賶 Amazfit Bip Lite.
+\n
+\n賷乇噩賶 丕賱鬲兀賰丿 賲賳 鬲孬亘賷鬲 賲賱賮 .fw 貙 賵亘毓丿 匕賱賰 賲賱賮 .res. 爻鬲鬲賲 廿毓丕丿丞 鬲卮睾賷賱 爻丕毓鬲賰 亘毓丿 鬲孬亘賷鬲 賲賱賮 .fw.
+\n
+\n賲賱丕丨馗丞: 賱賷爻 毓賱賷賰 鬲孬亘賷鬲 .res 廿匕丕 賰丕賳 賲胤丕亘賯賸丕 鬲賲丕賲賸丕 賱賲丕 鬲賲 鬲孬亘賷鬲賴 賲爻亘賯賸丕.
+\n
+\n丕賱賲囟賷 賯丿賲丕 毓賱賶 賲爻丐賵賱賷鬲賰 丕賱禺丕氐丞!</string>
+    <string name="fw_upgrade_notice_amazfitcor">兀賳鬲 毓賱賶 賵卮賰 鬲孬亘賷鬲 丕賱亘乇賳丕賲噩 丕賱孬丕亘鬲 %s 毓賱賶 Amazfit Cor.
+\n
+\n賷乇噩賶 丕賱鬲兀賰丿 賲賳 鬲孬亘賷鬲 賲賱賮 .fw 貙 賵亘毓丿 匕賱賰 賲賱賮 .res. 爻賷鬲賲 廿毓丕丿丞 鬲卮睾賷賱 丕賱賮乇賯丞 丕賱禺丕氐丞 亘賰 亘毓丿 鬲孬亘賷鬲 賲賱賮 .fw.
+\n
+\n賲賱丕丨馗丞: 賱丕 賷鬲毓賷賳 毓賱賷賰 鬲孬亘賷鬲 .res 廿匕丕 賰丕賳 賲胤丕亘賯丕 鬲賲丕賲丕 賱賱鬲孬亘賷鬲 丕賱匕賷 鬲賲 鬲孬亘賷鬲賴 賲爻亘賯丕.
+\n
+\n丕賱賲囟賷 賯丿賲丕 毓賱賶 賲爻丐賵賱賷鬲賰 丕賱禺丕氐丞!</string>
+    <string name="fw_upgrade_notice_amazfit_gtr3">兀賳鬲 毓賱賶 賵卮賰 鬲孬亘賷鬲 丕賱亘乇賳丕賲噩 丕賱孬丕亘鬲 %s 毓賱賶 噩賴丕夭 Amazfit GTR 3.
+\n
+\n爻鬲鬲賲 廿毓丕丿丞 鬲卮睾賷賱 丕賱爻賵丕乇 亘毓丿 鬲孬亘賷鬲 賲賱賮 zip.
+\n
+\n丕賱賲囟賷 賯丿賲丕 毓賱賶 賲爻丐賵賱賷鬲賰 丕賱禺丕氐丞!</string>
+    <string name="fw_upgrade_notice_miband3">兀賳鬲 毓賱賶 賵卮賰 鬲孬亘賷鬲 丕賱亘乇賳丕賲噩 丕賱孬丕亘鬲 %s 毓賱賶 Mi Band 3.
+\n
+\n賷乇噩賶 丕賱鬲兀賰丿 賲賳 鬲孬亘賷鬲 賲賱賮 .fw 貙 賵亘毓丿 匕賱賰 賲賱賮 .res. 爻鬲鬲賲 廿毓丕丿丞 鬲卮睾賷賱 丕賱爻賵丕乇 亘毓丿 鬲孬亘賷鬲 賲賱賮 .fw.
+\n
+\n賲賱丕丨馗丞: 賱賷爻 毓賱賷賰 鬲孬亘賷鬲 .res 廿匕丕 賰丕賳 賲胤丕亘賯賸丕 鬲賲丕賲賸丕 賱賲丕 鬲賲 鬲孬亘賷鬲賴 賲爻亘賯賸丕.
+\n
+\n丕賱賲囟賷 賯丿賲丕 毓賱賶 賲爻丐賵賱賷鬲賰 丕賱禺丕氐丞!</string>
+    <string name="fw_upgrade_notice_miband5">兀賳鬲 毓賱賶 賵卮賰 鬲孬亘賷鬲 丕賱亘乇賳丕賲噩 丕賱孬丕亘鬲 %s 毓賱賶 Mi Band 5.
+\n
+\n賷乇噩賶 丕賱鬲兀賰丿 賲賳 鬲孬亘賷鬲 賲賱賮 .fw 貙 賵亘毓丿 匕賱賰 賲賱賮 .res. 爻賷鬲賲 廿毓丕丿丞 鬲卮睾賷賱 丕賱賮乇賯丞 丕賱禺丕氐丞 亘賰 亘毓丿 鬲孬亘賷鬲 賲賱賮 .fw.
+\n
+\n賲賱丕丨馗丞: 賱丕 賷鬲毓賷賳 毓賱賷賰 鬲孬亘賷鬲 .res 廿匕丕 賰丕賳 賲胤丕亘賯丕 鬲賲丕賲丕 賱賱鬲孬亘賷鬲 丕賱匕賷 鬲賲 鬲孬亘賷鬲賴 賲爻亘賯丕.
+\n
+\n丕賱賲囟賷 賯丿賲丕 毓賱賶 賲爻丐賵賱賷鬲賰 丕賱禺丕氐丞!</string>
+    <string name="fw_upgrade_notice_miband6">兀賳鬲 毓賱賶 賵卮賰 鬲孬亘賷鬲 丕賱亘乇賳丕賲噩 丕賱孬丕亘鬲 %s 毓賱賶 Mi Band 6.
+\n
+\n賷乇噩賶 丕賱鬲兀賰丿 賲賳 鬲孬亘賷鬲 賲賱賮 .fw 貙 賵亘毓丿 匕賱賰 賲賱賮 .res. 爻賷鬲賲 廿毓丕丿丞 鬲卮睾賷賱 丕賱賮乇賯丞 丕賱禺丕氐丞 亘賰 亘毓丿 鬲孬亘賷鬲 賲賱賮 .fw.
+\n
+\n賲賱丕丨馗丞: 賱丕 賷鬲毓賷賳 毓賱賷賰 鬲孬亘賷鬲 .res 廿匕丕 賰丕賳 賲胤丕亘賯丕 鬲賲丕賲丕 賱賱鬲孬亘賷鬲 丕賱匕賷 鬲賲 鬲孬亘賷鬲賴 賲爻亘賯丕.
+\n
+\n丕賱賲囟賷 賯丿賲丕 毓賱賶 賲爻丐賵賱賷鬲賰 丕賱禺丕氐丞!</string>
+    <string name="fw_upgrade_notice_amazfit_gts3">兀賳鬲 毓賱賶 賵卮賰 鬲孬亘賷鬲 丕賱亘乇賳丕賲噩 丕賱孬丕亘鬲 %s 毓賱賶 Amazfit GTS 3.
+\n
+\n爻鬲鬲賲 廿毓丕丿丞 鬲卮睾賷賱 丕賱爻賵丕乇 亘毓丿 鬲孬亘賷鬲 賲賱賮 zip.
+\n
+\n丕賱賲囟賷 賯丿賲丕 毓賱賶 賲爻丐賵賱賷鬲賰 丕賱禺丕氐丞!</string>
+    <string name="fw_upgrade_notice_amazfit_gtr3_pro">兀賳鬲 毓賱賶 賵卮賰 鬲孬亘賷鬲 丕賱亘乇賳丕賲噩 丕賱孬丕亘鬲 %s 毓賱賶 噩賴丕夭 Amazfit GTR 3 Pro.
+\n
+\n爻鬲鬲賲 廿毓丕丿丞 鬲卮睾賷賱 丕賱爻賵丕乇 亘毓丿 鬲孬亘賷鬲 賲賱賮 zip.
+\n
+\n丕賱賲囟賷 賯丿賲丕 毓賱賶 賲爻丐賵賱賷鬲賰 丕賱禺丕氐丞!</string>
+    <string name="miband_firmware_unknown_warning">賱賲 賷鬲賲 丕禺鬲亘丕乇 賴匕丕 丕賱亘乇賳丕賲噩 丕賱孬丕亘鬲 賵賯丿 賱丕 賷賰賵賳 賲鬲賵丕賮賯丕 賲毓 Gadgetbridge.
+\n
+\n兀賳鬲 賲賲賳賵毓 賲賳 鬲孬亘賷鬲賴丕!</string>
+    <string name="miband_firmware_suggest_whitelist">廿匕丕 賰賳鬲 賱丕 鬲夭丕賱 鬲乇睾亘 賮賷 丕賱賲鬲丕亘毓丞 賵丕爻鬲賲乇鬲 丕賱兀賲賵乇 賮賷 丕賱毓賲賱 亘卮賰賱 氐丨賷丨 亘毓丿 匕賱賰 貙 賮丕賱乇噩丕亍 廿禺亘丕乇 賲胤賵乇賷 Gadgetbridge 亘廿丿乇丕噩 廿氐丿丕乇 丕賱亘乇賳丕賲噩 丕賱孬丕亘鬲 %s 賮賷 丕賱賯丕卅賲丞 丕賱亘賷囟丕亍.</string>
+    <string name="open_fw_installer_info_text">賷爻賲丨 賱賰 亘乇賳丕賲噩 Firmware / Watchface / App / File Installer 亘鬲丨賲賷賱 / 鬲孬亘賷鬲 丕賱賲賱賮丕鬲 丕賱賲丿毓賵賲丞 (丕賱亘乇丕賲噩 丕賱孬丕亘鬲丞 貙 賵賵丕噩賴丕鬲 丕賱賲乇丕賯亘丞 貙 賵丕賱鬲胤亘賷賯丕鬲 貙 賵賳馗丕賲 鬲丨丿賷丿 丕賱賲賵丕賯毓 丕賱毓丕賱賲賷 (GPS) 貙 賵丕賱賲賵丕乇丿 貙 賵丕賱禺胤賵胤 ...) 毓賱賶 丕賱噩賴丕夭. 丕胤賱毓 毓賱賶 賲夭賷丿 賲賳 丕賱賲毓賱賵賲丕鬲 賮賷 丕賱賵賷賰賷: https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Firmware-Update</string>
+    <string name="pref_title_general_autoconnectonbluetooth">賯賲 亘丕賱丕鬲氐丕賱 亘丕賱噩賴丕夭/兀噩賴夭丞 Gadgetbridge 毓賳丿 鬲卮睾賷賱 Bluetooth</string>
+    <string name="open_fw_installer_connect_minimum_one_device">丕賱乇噩丕亍 鬲賵氐賷賱 噩賴丕夭 賵丕丨丿 毓賱賶 丕賱兀賯賱 丕賱匕賷 鬲乇賷丿 廿乇爻丕賱 丕賱賲賱賮 廿賱賷賴.</string>
+    <string name="pref_summary_minimize_priority_off">賷鬲賲 毓乇囟 丕賱乇賲夭 丕賱賲賵噩賵丿 賮賷 卮乇賷胤 丕賱丨丕賱丞 賵丕賱廿卮毓丕乇 賮賷 卮丕卮丞 丕賱賯賮賱</string>
+    <string name="ignore_bonded_devices_description">爻賷丐丿賷 鬲賲賰賷賳 賴匕丕 丕賱禺賷丕乇 廿賱賶 鬲噩丕賴賱 丕賱兀噩賴夭丞 丕賱鬲賷 鬲賲 乇亘胤賴丕 / 廿賯乇丕賳賴丕 亘丕賱賮毓賱 毓賳丿 丕賱賲爻丨</string>
+    <string name="pref_title_notification_cache_while_disconnected">丕賱鬲禺夭賷賳 丕賱賲丐賯鬲 禺丕乇噩 丕賱賳胤丕賯</string>
+    <string name="pref_title_notification_use_as_allow">丕賱爻賲丕丨 亘丕賱廿卮毓丕乇丕鬲 賲賳 丕賱鬲胤亘賷賯丕鬲 丕賱賲丨丿丿丞</string>
+    <string name="pref_title_transliteration">丕賱鬲乇噩賲丞 丕賱丨乇賮賷丞</string>
+    <string name="pref_summary_screen_on_on_notifications">賯賲 亘鬲卮睾賷賱 卮丕卮丞 丕賱爻賵丕乇 毓賳丿 賵氐賵賱 廿卮毓丕乇</string>
+    <string name="pref_summary_enable_calendar_sync">廿乇爻丕賱 丕賱鬲賯賵賷賲 丕賱兀丨丿丕孬 廿賱賶 丕賱噩丿賵賱 丕賱夭賲賳賷</string>
+    <string name="zetime_prefs_inactivity_fr">丕賱噩賲毓丞</string>
+    <string name="zetime_prefs_inactivity_su">丕賱兀丨丿</string>
+    <string name="pref_summary_notification_filter">丨馗乇 噩賲賷毓 丕賱廿卮毓丕乇丕鬲 毓賳丿 鬲賲賰賷賳 \"毓丿賲 丕賱廿夭毓丕噩\" 毓賱賶 丕賱賴丕鬲賮</string>
+    <string name="pref_title_notification_media_ignores_application_list">廿卮毓丕乇丕鬲 丕賱賵爻丕卅胤 鬲鬲噩丕賴賱 賯丕卅賲丞 丕賱鬲胤亘賷賯丕鬲</string>
+    <string name="pref_summary_banglejs_webview_url">廿匕丕 賰賳鬲 鬲乇賷丿 兀丿丕丞 鬲丨賲賷賱 鬲胤亘賷賯丕鬲 賲禺氐氐丞 貙 賮囟毓 毓賳賵丕賳 URL https://.../android.html 賴賳丕. 賵廿賱丕 丕鬲乇賰賴 賮丕乇睾丕 賱賭 https://banglejs.com/apps</string>
+    <string name="dateformat_time">丕賱賵賯鬲</string>
+    <string name="zetime_title_alarm_signaling">鬲毓賷賷賳 賳賵毓 丕賱廿卮丕乇丕鬲 賱賱廿賳匕丕乇</string>
+    <string name="male">匕賰乇</string>
+    <string name="vertical">毓賲賵丿賷</string>
+    <string name="miband_pairing_using_dummy_userdata">賱賲 賷鬲賲 鬲賯丿賷賲 亘賷丕賳丕鬲 賲爻鬲禺丿賲 氐丕賱丨丞 貙 亘丕爻鬲禺丿丕賲 亘賷丕賳丕鬲 丕賱賲爻鬲禺丿賲 丕賱賵賴賲賷丞 賮賷 丕賱賵賯鬲 丕賱丨丕賱賷.</string>
+    <string name="watch9_pairing_tap_hint">毓賳丿賲丕 鬲賴鬲夭 爻丕毓鬲賰貙 賴夭 丕賱噩賴丕夭 兀賵 丕囟睾胤 毓賱賶 丕賱夭乇 丕賱禺丕氐 亘賴.</string>
+    <string name="interval_thirty_minutes">賰賱 30 丿賯賷賯丞</string>
+    <string name="spo2_perc_80">80%</string>
+    <string name="alarm_mon_short">丕賱廿孬</string>
+    <string name="alarm_tue_short">丕賱孬</string>
+    <string name="prefs_relaxation_reminder_description">丕賴鬲夭 丕賱爻賵丕乇 賱廿毓賱丕賲賰 廿匕丕 賰丕賳鬲 賯賷賲丞 丕賱囟睾胤 兀毓賱賶 賲賳 80</string>
+    <string name="notif_export_failed_title">賮卮賱 鬲氐丿賷乇 賯丕毓丿丞 丕賱亘賷丕賳丕鬲! 賷乇噩賶 丕賱鬲丨賯賯 賲賳 丕賱廿毓丿丕丿丕鬲 丕賱禺丕氐丞 亘賰.</string>
+    <string name="fwapp_install_device_not_ready">賱丕 賷賲賰賳 鬲孬亘賷鬲 丕賱賲賱賮 貙 丕賱噩賴丕夭 睾賷乇 噩丕賴夭.</string>
+    <string name="french">丕賱賮乇賳爻賷丞</string>
+    <string name="indonesian">丕賱廿賳丿賵賳賷爻賷丞</string>
+    <string name="chart_steps">丕賱禺胤賵丕鬲</string>
+    <string name="calories">丕賱爻毓乇丕鬲</string>
+    <string name="distance">丕賱賲爻丕賮丞</string>
+    <string name="fwinstaller_firmware_not_compatible_to_device">賴匕丕 丕賱亘乇賳丕賲噩 丕賱孬丕亘鬲 睾賷乇 賲鬲賵丕賮賯 賲毓 丕賱噩賴丕夭</string>
+    <string name="dbmanagementactivity_overwrite_database_confirmation">賴賱 鬲乇賷丿 丨賯賸丕 丕賱賰鬲丕亘丞 賮賵賯 丕賱亘賷丕賳丕鬲 丕賱丨丕賱賷丞 責 爻賷鬲賲 丕爻鬲亘丿丕賱 噩賲賷毓 亘賷丕賳丕鬲 賳卮丕胤賰 丕賱丨丕賱賷丞 (廿賳 賵噩丿鬲) 賵丕賱兀噩賴夭丞 賵丕賱鬲賮囟賷賱丕鬲.</string>
+    <string name="activity_error_no_app_for_png">賱賲卮丕乇賰丞 賱賯胤丞 丕賱卮丕卮丞 賴匕賴 貙 賯賲 亘鬲孬亘賷鬲 鬲胤亘賷賯 賷賲賰賳賴 丕賱鬲毓丕賲賱 賲毓 賲賱賮丕鬲 丕賱氐賵乇.</string>
+    <string name="activity_error_no_app_for_gpx">賱毓乇囟 鬲鬲亘毓 丕賱賳卮丕胤 貙 賯賲 亘鬲孬亘賷鬲 丕賱鬲胤亘賷賯 丕賱匕賷 賷賲賰賳賴 丕賱鬲毓丕賲賱 賲毓 賲賱賮丕鬲 GPX.</string>
+    <string name="permission_notification_listener">賷丨鬲丕噩 %1$s 廿賱賶 丕賱賵氐賵賱 廿賱賶 丕賱廿卮毓丕乇丕鬲 賱毓乇囟賴丕 毓賱賶 爻丕毓鬲賰 毓賳丿賲丕 鬲賰賵賳 卮丕卮丞 賴丕鬲賮賰 賲睾賱賯丞.
+\n
+\n賷購乇噩賶 丕賱賳賯乇 毓賱賶 \"%2$s\" 孬賲 \"%1$s\" 賵鬲賲賰賷賳 \"丕賱爻賲丕丨 亘丕賱賵氐賵賱 廿賱賶 丕賱廿卮毓丕乇丕鬲\" 貙 孬賲 丕賱賳賯乇 毓賱賶 \"乇噩賵毓\" 賱賱毓賵丿丞 廿賱賶 %1$s.</string>
+    <string name="watchface_upload_failed">賮卮賱 鬲丨賲賷賱 賵丕噩賴丞 丕賱爻丕毓丞. 賷乇噩賶 丕賱賲丨丕賵賱丞 賲乇丞 兀禺乇賶.</string>
+    <string name="kind_watchface">卮丕卮丞 丕賱爻丕毓丞</string>
+    <string name="watch9_calibration_hint">丕囟亘胤 丕賱賵賯鬲 丕賱匕賷 賷馗賴乇 賮賷賴 噩賴丕夭賰 賱賰 丕賱丌賳.</string>
+    <string name="watchface_cache_confirm_overwrite">鬲賵噩丿 亘丕賱賮毓賱 禺賱賮賷丞 卮丕卮丞 亘賴匕丕 丕賱丕爻賲 賮賷 匕丕賰乇丞 丕賱鬲禺夭賷賳 丕賱賲丐賯鬲. 賴賱 鬲乇賷丿 丕賱賰鬲丕亘丞 毓賱賷賴責</string>
+    <string name="watchface_setting_desc_wrist_flick">(賱賱鬲毓胤賷賱 鬲賲丕賲賸丕 貙 賯賲 亘鬲賲賰賷賳 丕賱丨乇賰丞 丕賱賳爻亘賷丞 賵鬲毓賷賷賳 噩賲賷毓 丕賱賯賷賲 毓賱賶 0)</string>
+    <string name="pressure_relief_summary">賲賳毓 丕賱卮毓賵乇 亘丕賱囟睾胤 賮賷 丕賱兀匕賳賷賳 毓賳丿 毓丿賲 丕爻鬲禺丿丕賲 廿賱睾丕亍 丕賱囟賵囟丕亍 丕賱賳卮胤</string>
+    <string name="qhybrid_summary_on_device_confirmation">賷賲賰賳 兀賳 鬲氐亘丨 鬲兀賰賷丿丕鬲 丕賱丕賯鬲乇丕賳 毓賱賶 丕賱噩賴丕夭 賲夭毓噩丞. 賯丿 賷丐丿賷 鬲毓胤賷賱賴丕 廿賱賶 賮賯丿丕賳 賵馗丕卅賮賰.</string>
+    <string name="pbw_install_handler_hw_revision_mismatch">鬲毓匕乇 鬲孬亘賷鬲 丕賱亘乇賳丕賲噩 丕賱孬丕亘鬲 丕賱賲丨丿丿: 賱丕 賷鬲胤丕亘賯 賲毓 賲乇丕噩毓丞 兀噩賴夭丞 Pebble 丕賱禺丕氐丞 亘賰.</string>
+    <string name="prefs_control_center_summary">丕禺鬲乇 丕賱毓賳丕氐乇 丕賱賲賵噩賵丿丞 賮賷 丕賱賯丕卅賲丞 丕賱賲賳爻丿賱丞 賱賲乇賰夭 丕賱鬲丨賰賲</string>
+    <string name="dbmanagementactivity_import_successful">賲爻鬲賵乇丿.</string>
+    <string name="menuitem_stress">丕賱囟睾胤</string>
+    <string name="menuitem_unknown">睾賷乇 賲毓乇賵賮</string>
+    <string name="pref_camera_remote_summary">賷爻賲丨 賱賱爻丕毓丞 亘鬲卮睾賷賱 賰丕賲賷乇丕 丕賱賴丕鬲賮</string>
+    <string name="pref_workout_detection_categories_title">賮卅丕鬲 丕賱鬲賲乇賷賳</string>
+    <string name="pref_workout_keep_screen_on_summary">爻鬲馗賱 丕賱卮丕卮丞 賯賷丿 丕賱鬲卮睾賷賱 兀孬賳丕亍 丕賱鬲賲乇賷賳 貙 賵爻賷鬲賲 囟亘胤 丕賱爻胤賵毓 賱毓乇囟 亘賷丕賳丕鬲 丕賱鬲賲乇賷賳 賮賷 丕賱賵賯鬲 丕賱賮毓賱賷 亘丕爻鬲賲乇丕乇</string>
+    <string name="connecting">丕賱丕鬲氐丕賱</string>
+    <string name="connected">賲鬲氐賱</string>
+    <string name="discovery_note">賲賱丕丨馗丞:</string>
+    <string name="spo2_perc_90">90%</string>
+    <string name="heartrate_bpm_50">50 賳亘囟丞 賮賷 丕賱丿賯賷賯丞</string>
+    <string name="heartrate_bpm_125">125 賳亘囟丞 賮賷 丕賱丿賯賷賯丞</string>
+    <string name="spo2_perc_85">85%</string>
+    <string name="pbw_install_handler_unable_to_install">鬲毓匕乇 鬲孬亘賷鬲 丕賱賲賱賮 丕賱賲丨丿丿: %1$s</string>
+    <string name="live_activity_max_heart_rate">賲毓丿賱 賳亘囟 丕賱賯賱亘 丕賱丨丕賱賷 / 丕賱兀賯氐賶: %1$d / %2$d</string>
+    <string name="error_creating_directory_for_logfiles">禺胤兀 賮賷 廿賳卮丕亍 丿賱賷賱 賱賲賱賮丕鬲 丕賱爻噩賱: %1$s</string>
+    <string name="activity_type_yoga">丕賱賷賵噩丕</string>
+    <string name="kind_firmware">丕賱亘乇丕賲噩 丕賱孬丕亘鬲丞</string>
+    <string name="menuitem_pai">PAI</string>
+    <string name="_unknown_">(睾賷乇 賲毓乇賵賮)</string>
+    <string name="ok">賳毓賲</string>
+    <string name="activity_type_swimming">丕賱爻亘丕丨賴</string>
+    <string name="share">賲卮丕乇賰丞</string>
+    <string name="error_no_location_access">賷噩亘 賲賳丨 丕賱賵氐賵賱 廿賱賶 丕賱賲賵賯毓 賵鬲賲賰賷賳賴 賱賱賲爻丨 賱賷毓賲賱 亘卮賰賱 氐丨賷丨</string>
+    <string name="error_background_service_reason">賮卮賱 亘丿亍 鬲卮睾賷賱 禺丿賲丞 丕賱禺賱賮賷丞 亘爻亘亘 丕爻鬲孬賳丕亍 - 丕賳賯乇 賴賳丕 賱賲夭賷丿 賲賳 丕賱賲毓賱賵賲丕鬲.\n\n禺胤兀:</string>
+    <string name="pref_summary_pebble_enable_applogs">爻賷丐丿賷 廿賱賶 鬲爻噩賷賱 丕賱爻噩賱丕鬲 賲賳 鬲胤亘賷賯丕鬲 丕賱爻丕毓丞 亘賵丕爻胤丞 Gadgetbridge (賷鬲胤賱亘 廿毓丕丿丞 丕賱丕鬲氐丕賱)</string>
+    <string name="test">丕賱丕禺鬲亘丕乇</string>
+    <string name="mi2_prefs_goal_notification_summary">爻賷賴鬲夭 丕賱爻賵丕乇 毓賳丿 丕賱賵氐賵賱 廿賱賶 賴丿賮 丕賱禺胤賵丕鬲 丕賱賷賵賲賷丞</string>
+    <string name="share_log_not_enabled_message">賷噩亘 毓賱賷賰 兀賵賱丕賸 鬲賲賰賷賳 丕賱鬲爻噩賷賱 賮賷 丕賱廿毓丿丕丿丕鬲 - 賰鬲丕亘丞 賲賱賮丕鬲 丕賱爻噩賱</string>
+    <string name="menuitem_timer">丕賱毓丿 丕賱鬲賳丕夭賱賷</string>
+    <string name="menuitem_compass">丕賱亘賵氐賱丞</string>
+    <string name="menuitem_settings">丕賱廿毓丿丕丿丕鬲</string>
+    <string name="menuitem_more">丕賱賲夭賷丿</string>
+    <string name="menuitem_sleep">丕賱賳賵賲</string>
+    <string name="watch9_time_hours">爻丕毓丕鬲:</string>
+    <string name="watch9_calibration_button">賲毓丕賷乇丞</string>
+    <string name="error_version_check_extreme_caution">鬲賳亘賷賴: 禺胤兀 毓賳丿 丕賱鬲丨賯賯 賲賳 賲毓賱賵賲丕鬲 丕賱廿氐丿丕乇! 賱丕 賷噩亘 兀賳 鬲爻鬲賲乇! 丕賱賲賳卮丕乇 丕爻賲 丕賱廿氐丿丕乇 \"%s\"</string>
+    <string name="permission_notification_policy_access">賷丨鬲丕噩 %1$s 廿賱賶 丕賱賵氐賵賱 廿賱賶 廿毓丿丕丿丕鬲 \"丕賱乇噩丕亍 毓丿賲 丕賱廿夭毓丕噩\" 賱鬲賰乇賷賲賴賲 毓賱賶 爻丕毓鬲賰 毓賳丿賲丕 鬲賰賵賳 卮丕卮丞 賴丕鬲賮賰 賲睾賱賯丞.
+\n
+\n賷購乇噩賶 丕賱賳賯乇 毓賱賶 \"%2$s\" 孬賲 \"%1$s\" 賵鬲賲賰賷賳 \"丕賱爻賲丕丨 亘毓丿賲 丕賱廿夭毓丕噩\" 貙 孬賲 丕賱賳賯乇 毓賱賶 \"乇噩賵毓\" 賱賱毓賵丿丞 廿賱賶 %1$s.</string>
+    <string name="nothing_prefs_inear_summary">賯賲 亘鬲卮睾賷賱 / 廿賷賯丕賮 丕賱賲賵爻賷賯賶 賲丐賯鬲丕 丨爻亘 賲丕 廿匕丕 賰賳鬲 鬲乇鬲丿賷 爻賲丕毓丕鬲 丕賱兀匕賳</string>
+    <string name="prefs_noise_control_with_one_earbud_summary">丕賱爻賲丕丨 亘丕賱鬲丨賰賲 賮賷 丕賱囟賵囟丕亍 毓賳丿 丕爻鬲禺丿丕賲 爻賲丕毓丞 兀匕賳 賵丕丨丿丞 賮賯胤</string>
+    <string name="sony_warn_sbc_codec">鬲丨匕賷乇: 賷毓賲賱 丕賱賲毓丕丿賱 賵賲賵囟毓 丕賱氐賵鬲 賵廿毓丿丕丿丕鬲 丕賱氐賵鬲 丕賱賲丨賷胤 賮賯胤 賲毓 亘乇賳丕賲噩 鬲乇賲賷夭 丕賱氐賵鬲 SBC.</string>
+    <string name="pref_title_pebble_enable_applogs">鬲賲賰賷賳 鬲爻噩賷賱 丕賱兀禺胤丕亍 賱鬲胤亘賷賯 丕賱爻丕毓丞</string>
+    <string name="other">丌禺乇</string>
+    <string name="on">鬲卮睾賷賱</string>
+    <string name="menuitem_alarm">丕賱賲賳亘賴</string>
+    <string name="menuitem_status">丕賱丨丕賱丞</string>
+    <string name="discovery_pair_question">丨丿丿 廿賯乇丕賳 賱廿賯乇丕賳 兀噩賴夭鬲賰. 廿匕丕 賮卮賱 匕賱賰 貙 丨丕賵賱 賲乇丞 兀禺乇賶 亘丿賵賳 丕賱丕賯鬲乇丕賳.</string>
+    <string name="mi2_fw_installhandler_fw53_hint">鬲丨鬲丕噩 廿賱賶 鬲孬亘賷鬲 丕賱廿氐丿丕乇 %1$s 賯亘賱 鬲孬亘賷鬲 賴匕丕 丕賱亘乇賳丕賲噩 丕賱孬丕亘鬲!</string>
+    <string name="activity_DB_clean_export_directory_warning_message">賴賱 鬲乇睾亘 丨賯賸丕 賮賷 丨匕賮 丕賱賲賱賮丕鬲 丕賱賲賵噩賵丿丞 賮賷 丿賱賷賱 丕賱鬲氐丿賷乇 / 丕賱丕爻鬲賷乇丕丿責</string>
+    <string name="StringUtils_sender">(%1$s)</string>
+    <string name="pref_summary_opentracks_packagename">賷爻鬲禺丿賲 賱亘丿亍 / 廿賷賯丕賮 鬲爻噩賷賱 賲爻丕乇 GPS 賮賷 鬲胤亘賷賯 賱賷丕賯丞 禺丕乇噩賷.</string>
+    <string name="pref_title_banglejs_webview_url">毓賳賵丕賳 URL 賱賲丨賲賱 丕賱鬲胤亘賷賯</string>
+    <string name="pref_title_canned_messages_dismisscall">乇賮囟 丕賱丕鬲氐丕賱</string>
+    <string name="interval_one_minute">賲乇丞 賰賱 丿賯賷賯丞</string>
+    <string name="heartrate_bpm_115">115 賳亘囟丞 賮賷 丕賱丿賯賷賯丞</string>
+    <string name="heartrate_bpm_110">110 賳亘囟丞 賮賷 丕賱丿賯賷賯丞</string>
+    <string name="spo2_off">丕賷賯丕賮</string>
+    <string name="Cancel">廿賱睾丕亍</string>
+    <string name="pref_summary_third_party_app_device_settings">丕爻賲丨 賱鬲胤亘賷賯丕鬲 丕賱胤乇賮 丕賱孬丕賱孬 丕賱賲孬亘鬲丞 丕賱兀禺乇賶 亘囟亘胤 廿毓丿丕丿丕鬲 丕賱噩賴丕夭 賲賳 禺賱丕賱 丕賱賳賵丕賷丕.</string>
+    <string name="pref_summary_low_latency_fw_update">賯丿 賷爻丕毓丿 賴匕丕 毓賱賶 丕賱兀噩賴夭丞 丕賱鬲賷 賷賮卮賱 賮賷賴丕 鬲孬亘賷鬲 丕賱亘乇丕賲噩 丕賱孬丕亘鬲丞.</string>
+    <string name="miband_prefs_hr_sleep_detection">丕爻鬲禺丿丕賲 兀噩賴夭丞 丕賱丕爻鬲卮毓丕乇 賱賲毓丿賱丕鬲 丕賱賯賱亘 賱鬲丨爻賷賳 賰卮賮 丕賱賳賵賲</string>
+    <string name="mi2_prefs_button_press_count_summary">毓丿丿 賲乇丕鬲 丕賱囟睾胤 毓賱賶 丕賱夭乇 賱鬲卮睾賷賱 丨丿孬 1. 賷丐丿賷 賳賮爻 丕賱賲賯丿丕乇 賲賳 丕賱囟睾胤丕鬲 丕賱賲鬲賱丕丨賯丞 廿賱賶 廿賳卮丕亍 丕賱丨丿孬 2 賵賲丕 廿賱賶 匕賱賰.</string>
+    <string name="mi2_prefs_button_action_summary">鬲賲賰賷賳 丕賱廿噩乇丕亍 毓賱賶 毓丿丿 賲丨丿丿 賲賳 囟睾胤丕鬲 丕賱兀夭乇丕乇</string>
+    <string name="mi2_prefs_button_press_count_max_delay_summary">丕賱丨丿 丕賱兀賯氐賶 賱賱鬲兀禺賷乇 亘賷賳 囟睾胤丕鬲 丕賱兀夭乇丕乇 亘丕賱賲賱賱賷 孬丕賳賷丞</string>
+    <string name="czesh">丕賱鬲卮賷賰賷丞</string>
+    <string name="pref_title_pebble_sync_health">賲夭丕賲賳丞 Pebble Health</string>
+    <string name="pref_summary_enable_outgoing_call">爻賷丐丿賷 鬲毓胤賷賱 賴匕丕 兀賷囟賸丕 廿賱賶 廿賷賯丕賮 Pebble 2 / LE 賱賷賴鬲夭 毓賳丿 丕賱賲賰丕賱賲丕鬲 丕賱氐丕丿乇丞</string>
+    <string name="pref_summary_custom_deviceicon">廿馗賴丕乇 乇賲夭 廿卮毓丕乇 Android 丕賱禺丕氐 亘噩賴丕夭 賲毓賷賳 亘丿賱丕 賲賳 乇賲夭 Gadgetbridge 毓賳丿 丕賱丕鬲氐丕賱</string>
+    <string name="pref_title_autoremove_notifications">丕賱廿夭丕賱丞 丕賱鬲賱賯丕卅賷丞 賱賱廿卮毓丕乇丕鬲 丕賱賲乇賮賵囟丞</string>
+    <string name="pref_summary_autoremove_notifications">鬲鬲賲 廿夭丕賱丞 丕賱廿卮毓丕乇丕鬲 鬲賱賯丕卅賷賸丕 賲賳 丕賱噩賴丕夭 毓賳丿 乇賮囟賴丕 賲賳 丕賱賴丕鬲賮</string>
+    <string name="pref_pebble_privacy_mode_off">丕賱廿卮毓丕乇丕鬲 丕賱毓丕丿賷丞</string>
+    <string name="pref_pebble_privacy_mode_complete">廿馗賴丕乇 乇賲夭 丕賱廿卮毓丕乇 賮賯胤</string>
+    <string name="toast_enable_networklocationprovider">丕賱乇噩丕亍 鬲賲賰賷賳 賲賵賯毓 丕賱卮亘賰丞</string>
+    <string name="pref_title_pebble_forceprotocol">賮乇囟 亘乇賵鬲賵賰賵賱 丕賱廿卮毓丕乇</string>
+    <string name="pref_summary_pebble_forceuntested">鬲賲賰賷賳 丕賱賲賷夭丕鬲 睾賷乇 丕賱賲禺鬲亘乇丞. 賱丕亘丿 丕賳 鬲毓乇賮 賲丕 鬲賯賵賲 亘賴!</string>
+    <string name="pref_summary_pebble_forcele">丕爻鬲禺丿賲 丿毓賲 Pebble LE 丕賱鬲噩乇賷亘賷 賱噩賲賷毓 Pebbles 貙 亘丿賱丕賸 賲賳 BT 丕賱賰賱丕爻賷賰賷. 賷鬲胤賱亘 賴匕丕 丕賱丕賯鬲乇丕賳 亘睾賷乇 LE 兀賵賱丕賸 貙 孬賲 Pebble LE</string>
+    <string name="pref_summary_pebble_mtu_limit">廿匕丕 賱賲 賷毓賲賱 Pebble 2 / Pebble LE 賰賲丕 賴賵 賲鬲賵賯毓 貙 賮噩乇賾亘 賴匕丕 丕賱廿毓丿丕丿 賱鬲賯賷賷丿 MTU (丕賱賳胤丕賯 丕賱氐丕賱丨 20-512)</string>
+    <string name="pref_summary_device_spec_settings_title_force_time">賮乇囟 賵賯鬲 丕賱賲夭丕賲賳丞 丕賱鬲賱賯丕卅賷丞 毓賳丿 廿毓丕丿丞 丕賱丕鬲氐丕賱. 賯丿 鬲馗賴乇 丕賱毓賯丕乇亘 丕賱鬲賳丕馗乇賷丞 賵賯鬲賸丕 睾賷乇 氐丨賷丨!</string>
+    <string name="zetime_prefs_inactivity_repetitions">丕賱鬲賰乇丕乇</string>
+    <string name="zetime_prefs_inactivity_mo">丕賱丕孬賳賷賳</string>
+    <string name="pref_workout_send_gps_title">廿乇爻丕賱 GPS 兀孬賳丕亍 丕賱鬲賲乇賷賳</string>
+    <string name="pref_summary_disable_new_ble_scanning">鬲丨賯賯 賲賳 賴匕丕 丕賱禺賷丕乇 廿匕丕 賰丕賳 噩賴丕夭賰 賱丕 賷賲賰賳 丕賱毓孬賵乇 毓賱賷賴 禺賱丕賱 丕賱丕賰鬲卮丕賮</string>
+    <string name="discovery_entered_invalid_authkey">賲賮鬲丕丨 丕賱賲氐丕丿賯丞 丕賱爻乇賷 丕賱匕賷 兀丿禺賱鬲賴 睾賷乇 氐丕賱丨! 丕囟睾胤 賱賮鬲乇丞 胤賵賷賱丞 毓賱賶 丕賱噩賴丕夭 賱賱鬲毓丿賷賱.</string>
+    <string name="pref_cache_weather_summary">爻賷鬲賲 鬲禺夭賷賳 賲毓賱賵賲丕鬲 丕賱胤賯爻 賲丐賯鬲賸丕 毓亘乇 毓賲賱賷丕鬲 廿毓丕丿丞 鬲卮睾賷賱 丕賱鬲胤亘賷賯.</string>
+    <string name="pref_write_logfiles_not_available">賮卮賱鬲 鬲賴賷卅丞 鬲爻噩賷賱 丕賱賲賱賮 貙 賰鬲丕亘丞 賲賱賮丕鬲 丕賱爻噩賱 睾賷乇 賲鬲賵賮乇丞 丨丕賱賷賸丕. 兀毓丿 鬲卮睾賷賱 丕賱鬲胤亘賷賯 賱賲丨丕賵賱丞 鬲賴賷卅丞 賲賱賮丕鬲 丕賱爻噩賱 賲乇丞 兀禺乇賶.</string>
+    <string name="interval_ten_minutes">賰賱 10 丿賯丕卅賯</string>
+    <string name="interval_forty_five_minutes">賰賱 45 丿賯賷賯丞</string>
+    <string name="interval_one_hour">賲乇丞 賵丕丨丿丞 賮賷 丕賱爻丕毓丞</string>
+    <string name="reminder_delete_confirm_description">賴賱 兀賳鬲 賲鬲兀賰丿 兀賳賰 鬲乇賷丿 丨匕賮 丕賱鬲匕賰賷乇責</string>
+    <string name="pref_summary_dont_ack_transfers">廿匕丕 賱賲 鬲鬲賲 丕賱賲賵丕賮賯丞 毓賱賶 丕賱賳胤丕賯 貙 賮賱賳 賷鬲賲 賲爻丨 亘賷丕賳丕鬲 丕賱賳卮丕胤. 賲賮賷丿 廿匕丕 鬲賲 丕爻鬲禺丿丕賲 GB 賲毓 鬲胤亘賷賯丕鬲 兀禺乇賶.</string>
+    <string name="installation_successful">丕賱賲孬亘鬲丞</string>
+    <string name="swedish">丕賱爻賵賷丿賷丞</string>
+    <string name="dbmanagementactivity_overwrite">丕賱賰鬲丕亘丞 賮賵賯</string>
+    <string name="watch9_time_minutes">丿賯丕卅賯:</string>
+    <string name="toast_app_must_not_be_selected">賷噩亘 毓丿賲 鬲丨丿賷丿 丕賱鬲胤亘賷賯 賱賷鬲賲 鬲賰賵賷賳賴</string>
+    <string name="contact_delete_confirm_description">賴賱 兀賳鬲 賲鬲兀賰丿 賲賳 兀賳賰 鬲乇賷丿 丨匕賮 \"%1$s\"責</string>
+    <string name="alarm_wed_short">丕賱丕乇</string>
+    <string name="alarm_thu_short">丕賱禺</string>
+    <string name="alarm_fri_short">丕賱噩</string>
+    <string name="prefs_spo2_monitoring_description">賲乇丕賯亘丞 賲爻鬲賵賷丕鬲 丕賱兀賰爻噩賷賳 賮賷 丕賱丿賲 鬲賱賯丕卅賷賸丕 毓賱賶 賲丿丕乇 丕賱賷賵賲</string>
+    <string name="installer_activity_unable_to_find_handler">睾賷乇 賯丕丿乇 毓賱賶 丕賱毓孬賵乇 毓賱賶 賲毓丕賱噩 賱鬲孬亘賷鬲 賴匕丕 丕賱賲賱賮.</string>
+    <string name="miband_prefs_device_time_offset_hours">廿夭丕丨丞 賵賯鬲 丕賱噩賴丕夭 亘丕賱爻丕毓丕鬲 (賱丕賰鬲卮丕賮 爻亘丕鬲 毓賲丕賱 丕賱賲賳丕賵亘丞)</string>
+    <string name="mi2_prefs_button_press_broadcast_summary">廿乇爻丕賱 乇爻丕賱丞 丕賱亘孬 賲毓 丕賱丨丿孬. 鬲鬲賲 廿囟丕賮丞 丕賱賲毓賱賲丞 \"button_id\" 鬲賱賯丕卅賷賸丕 廿賱賶 賰賱 乇爻丕賱丞.</string>
+    <string name="mi2_prefs_display_items_summary">丕禺鬲乇 丕賱毓賳丕氐乇 丕賱賲毓乇賵囟丞 毓賱賶 卮丕卮丞 丕賱爻賵丕乇</string>
+    <string name="mi2_prefs_button_action_vibrate_summary">鬲賲賰賷賳 丕賴鬲夭丕夭 丕賱賳胤丕賯 毓賳丿 鬲卮睾賷賱 丕賱夭乇</string>
+    <string name="mi2_prefs_inactivity_warnings_summary">爻賷賴鬲夭 丕賱爻賵丕乇 毓賳丿賲丕 鬲賰賵賳 睾賷乇 賳卮胤 賱賮鬲乇丞 賲賳 丕賱賵賯鬲</string>
+    <string name="mi2_prefs_do_not_disturb_summary">賱賳 賷鬲賱賯賶 丕賱爻賵丕乇 廿卮毓丕乇丕鬲 兀孬賳丕亍 鬲賳卮賷胤賴</string>
+    <string name="mi2_prefs_inactivity_warnings_dnd_summary">鬲毓胤賷賱 鬲丨匕賷乇丕鬲 毓丿賲 丕賱賳卮丕胤 賱賮鬲乇丞 夭賲賳賷丞</string>
+    <string name="prefs_password_summary">賯賮賱 丕賱爻賵丕乇 亘賰賱賲丞 賲乇賵乇 毓賳丿 廿夭丕賱鬲賴 賲賳 丕賱賲毓氐賲</string>
+    <string name="mi2_prefs_do_not_disturb_lift_wrist">賯賲 亘鬲賳卮賷胤 丕賱卮丕卮丞 毓賳丿 丕賱乇賮毓 兀孬賳丕亍 賵囟毓 \"毓丿賲 丕賱廿夭毓丕噩\"</string>
+    <string name="bip_prefs_shortcuts">丕賱丕禺鬲氐丕乇丕鬲</string>
+    <string name="mi3_prefs_band_screen_unlock_summary">丕爻丨亘 賱兀毓賱賶 賱廿賱睾丕亍 賯賮賱 卮丕卮丞 丕賱爻賵丕乇</string>
+    <string name="mi3_prefs_night_mode_summary">丕賳禺賮丕囟 爻胤賵毓 卮丕卮丞 丕賱爻賵丕乇 鬲賱賯丕卅賷賸丕 賮賷 丕賱賱賷賱</string>
+    <string name="prefs_shortcut_cards_summary">鬲馗賴乇 亘胤丕賯丕鬲 丕賱丕禺鬲氐丕乇 毓賳丿 丕賱鬲賲乇賷乇 賷賲賷賳賸丕 毓賱賶 禺賱賮賷丞 卮丕卮丞 丕賱爻丕毓丞. 毓賳丿 鬲卮睾賷賱 兀丨丿 丕賱鬲胤亘賷賯丕鬲 貙 賱丕 鬲鬲兀孬乇 丕賱亘胤丕賯丕鬲 丕賱鬲賷 鬲賲 廿賳卮丕丐賴丕 鬲賱賯丕卅賷賸丕 亘賴匕丕 丕賱廿毓丿丕丿.</string>
+    <string name="mi5_prefs_workout_activity_types_summary">丕禺鬲乇 兀賳賵丕毓 丕賱兀賳卮胤丞 丕賱鬲賷 鬲乇賷丿 毓乇囟賴丕 毓賱賶 卮丕卮丞 丕賱鬲賲丕乇賷賳</string>
+    <string name="authenticating">丕賱賲氐丕丿賯丞</string>
+    <string name="dbmanagementactivity_export_confirmation">賴賱 鬲乇睾亘 丨賯賸丕 賮賷 鬲氐丿賷乇 丕賱亘賷丕賳丕鬲責 爻鬲鬲賲 丕賱賰鬲丕亘丞 賮賵賯 亘賷丕賳丕鬲 丕賱兀賳卮胤丞 丕賱鬲賷 鬲賲 鬲氐丿賷乇賴丕 賲爻亘賯賸丕 (廿賳 賵噩丿鬲) 賵丕賱鬲賮囟賷賱丕鬲.</string>
+    <string name="dbmanagementactivity_error_cleaning_export_directory">禺胤兀 賮賷 賲丨賵 丕賱賲賱賮丕鬲 賲賳 丿賱賷賱 丕賱鬲氐丿賷乇 %1$s</string>
+    <string name="dbmanagementactivity_really_delete_entire_db">賴賱 鬲乇睾亘 丨賯賸丕 賮賷 丨匕賮 賯丕毓丿丞 丕賱亘賷丕賳丕鬲 亘兀賰賲賱賴丕責 爻鬲賮賯丿 噩賲賷毓 亘賷丕賳丕鬲 賳卮丕胤賰 賵賲毓賱賵賲丕鬲 丨賵賱 兀噩賴夭鬲賰.</string>
+    <string name="activity_db_management_autoexport_explanation">鬲賲 鬲毓賷賷賳 賲賵賯毓 丕賱鬲氐丿賷乇 丕賱鬲賱賯丕卅賷 賱賯丕毓丿丞 丕賱亘賷丕賳丕鬲 廿賱賶:</string>
+    <string name="activity_db_management_autoexport_scheduled_yes">鬲賲鬲 噩丿賵賱丞 丕賱鬲氐丿賷乇 丕賱鬲賱賯丕卅賷 (賮賷 丕賱兀氐賱) 賱賭 %1$s</string>
+    <string name="activity_db_management_autoexport_location">賱丕 賷賲賰賳 賮賴賲 丕賱賲賵賯毓. 賲賳 丕賱賲丨鬲賲賱 賵噩賵丿 賲卮賰賱丞 賮賷 賳馗丕賲 兀匕賵賳丕鬲 Android 丕賱兀丨丿孬. 毓賱賶 丕賱兀乇噩丨 貙 賱丕 賷毓賲賱 Autoexport 丕賱丌賳.</string>
+    <string name="activity_db_management_empty_db_warning">鬲丨匕賷乇! 亘丕賱囟睾胤 毓賱賶 賴匕丕 丕賱夭乇 貙 爻賵賮 鬲賲爻丨 賯丕毓丿丞 丕賱亘賷丕賳丕鬲 丕賱禺丕氐丞 亘賰 賵鬲亘丿兀 賲賳 丕賱氐賮乇.</string>
+    <string name="title_activity_vibration">丕賴鬲夭丕夭</string>
+    <string name="pebble_pairing_hint">爻賷馗賴乇 賲乇亘毓 丨賵丕乇 丕賱丕賯鬲乇丕賳 毓賱賶 噩賴丕夭 Android 丕賱禺丕氐 亘賰. 廿匕丕 賱賲 賷賰賳 賰匕賱賰 貙 賮丕亘丨孬 賮賷 丿乇噩 丕賱廿卮毓丕乇丕鬲 賵丕賯亘賱 胤賱亘 丕賱丕賯鬲乇丕賳. 鬲賯亘賱賴 兀賷囟賸丕 毓賱賶 Pebble 亘毓丿 匕賱賰.</string>
+    <string name="weather_notification_label">鬲兀賰丿 賲賳 鬲賲賰賷賳 賴匕丕 丕賱卮賰賱 賮賷 鬲胤亘賷賯 Weather Notification 賱賱丨氐賵賱 毓賱賶 賲毓賱賵賲丕鬲 丕賱胤賯爻 毓賱賶 Pebble.
+\n
+\n賱丕 賷賵噩丿 鬲賰賵賷賳 賲胤賱賵亘 賴賳丕.
+\n
+\n賷賲賰賳賰 鬲賲賰賷賳 鬲胤亘賷賯 丕賱胤賯爻 賱賱賳馗丕賲 丕賱禺丕氐 亘賭 Pebble 賲賳 廿丿丕乇丞 丕賱鬲胤亘賷賯.
+\n
+\n爻鬲馗賴乇 賵丕噩賴丕鬲 丕賱賲乇丕賯亘丞 丕賱賲丿毓賵賲丞 丕賱胤賯爻 鬲賱賯丕卅賷賸丕.</string>
+    <string name="pref_summary_setup_bt_pairing">賯賲 亘廿賱睾丕亍 鬲賳卮賷胤 賴匕丕 廿匕丕 賰賳鬲 鬲賵丕噩賴 賲卮賰賱丞 賮賷 丕賱丕鬲氐丕賱</string>
+    <string name="unit_metric">賲鬲乇賷</string>
+    <string name="timeformat_24h">24 爻丕毓丞</string>
+    <string name="mi2_dnd_off">丕賷賯丕賮</string>
+    <string name="maximum_duration">丕賱賲丿丞</string>
+    <string name="_pebble_watch_mute">賰鬲賲</string>
+    <string name="_pebble_watch_reply">乇丿</string>
+    <string name="activity_type_activity">賳卮丕胤</string>
+    <string name="activity_type_walking">丕賱賲卮賷</string>
+    <string name="activity_type_soccer">賰乇丞 丕賱賯丿賲</string>
+    <string name="activity_type_basketball">賰乇丞 爻賱丞</string>
+    <string name="activity_type_badminton">鬲賳爻 丕賱乇賷卮丞</string>
+    <string name="notification_channel_name">毓丕賲</string>
+    <string name="menuitem_notifications">丕賱廿卮毓丕乇丕鬲</string>
+    <string name="menuitem_weather">丕賱胤賯爻</string>
+    <string name="menuitem_breathing">丕賱鬲賳賮爻</string>
+    <string name="menuitem_alipay">毓賱賷 亘丕賷</string>
+    <string name="menuitem_music">丕賱賲賵爻賷賯賶</string>
+    <string name="menuitem_workout">鬲賲乇賷賳</string>
+    <string name="menuitem_alexa">丕賱賷賰爻丕</string>
+    <string name="menuitem_dnd">丿賷 廿賳 丿賷</string>
+    <string name="menuitem_events">丕賱兀丨丿丕孬</string>
+    <string name="watch9_time_seconds">丕賱孬賵丕賳賷:</string>
+    <string name="hr_widget_calories">丕賱爻毓乇丕鬲 丕賱丨乇丕乇賷賴</string>
+    <string name="hr_widget_battery">丕賱亘胤丕乇賷丞</string>
+    <string name="hr_widget_weather">丕賱胤賯爻</string>
+    <string name="hr_widget_nothing">賱丕 卮賷亍</string>
+    <string name="hr_appname_wellness">丕賱氐丨丞</string>
+    <string name="hr_appname_workout">鬲賲乇賷賳</string>
+    <string name="hr_appname_stopwatch">爻丕毓丞 丕賱鬲賵賯賷賮</string>
+    <string name="about_core_team_title">丕賱賮乇賷賯 丕賱兀爻丕爻賷 (亘鬲乇鬲賷亘 賲爻丕賴賲丞 丕賱賰賵丿 丕賱兀賵賱)</string>
+    <string name="permission_granting_mandatory">賰賱 賴匕賴 丕賱兀匕賵賳丕鬲 賲胤賱賵亘丞 賵賯丿 賷丨丿孬 毓丿賲 丕爻鬲賯乇丕乇 廿匕丕 賱賲 賷鬲賲 賲賳丨賴丕</string>
+    <string name="permission_display_over_other_apps">賷丨鬲丕噩 %1$s 廿賱賶 廿匕賳 賱賱毓乇囟 賮賵賯 丕賱鬲胤亘賷賯丕鬲 丕賱兀禺乇賶 賱賱爻賲丕丨 賱爻丕毓丕鬲 Bangle.js 亘亘丿亍 丕賱兀賳卮胤丞 毓亘乇 丕賱賳賵丕賷丕 毓賳丿賲丕 賷賰賵賳 %1$s 賮賷 丕賱禺賱賮賷丞.
+\n
+\n賷賲賰賳 丕爻鬲禺丿丕賲 賴匕丕 賱亘丿亍 鬲卮睾賷賱 鬲胤亘賷賯 丕賱賲賵爻賷賯賶 賵鬲卮睾賷賱 兀睾賳賷丞 賵兀卮賷丕亍 兀禺乇賶 賰孬賷乇丞.
+\n
+\n賷購乇噩賶 丕賱賳賯乇 毓賱賶 \"%2$s\" 孬賲 \"%1$s\" 賵鬲賲賰賷賳 \"丕賱爻賲丕丨 亘丕賱毓乇囟 賮賵賯 丕賱鬲胤亘賷賯丕鬲 丕賱兀禺乇賶\" 貙 孬賲 丕賱賳賯乇 毓賱賶 \"乇噩賵毓\" 賱賱毓賵丿丞 廿賱賶 %1$s.
+\n
+\n賱廿賷賯丕賮 胤賱亘 %1$s 賱賱丨氐賵賱 毓賱賶 兀匕賵賳丕鬲 貙 丕賳鬲賯賱 廿賱賶 \"丕賱廿毓丿丕丿丕鬲\" 賵賯賲 亘廿賱睾丕亍 鬲丨丿賷丿 \"丕賱鬲丨賯賯 賲賳 丨丕賱丞 丕賱廿匕賳\".
+\n
+\n鬲兀賰丿 賲賳 賲賳丨 %1$s 丕賱兀匕賵賳丕鬲 丕賱賱丕夭賲丞 賱賱毓賲賱 賰賲丕 鬲鬲賵賯毓.</string>
+    <string name="companiondevice_pairing_details">賷購賲賰賾賳 丿毓賲 賵丕噩賴丞 亘乇賲噩丞 丕賱鬲胤亘賷賯丕鬲 CompanionDevice 丕賱噩丿賷丿丞 (賱賴 鬲兀孬賷乇 賮賯胤 毓賱賶 Android 8 兀賵 兀毓賱賶) 賲賲丕 爻賷夭賷丿 賲賳 丕賱賲賵孬賵賯賷丞 廿匕丕 賰丕賳鬲 丕賱禺丿賲丞 亘丨丕噩丞 廿賱賶 廿毓丕丿丞 丕賱鬲卮睾賷賱 賮賷 丕賱禺賱賮賷丞 貙 賵賷鬲胤賱亘 廿毓丕丿丞 丕賱丕賯鬲乇丕賳 亘丕爻鬲禺丿丕賲 Gadgetbridge 賱賷賰賵賳 賱賴丕 鬲兀孬賷乇</string>
+    <string name="discover_unsupported_devices_description">爻賷丐丿賷 鬲賲賰賷賳 賴匕丕 丕賱禺賷丕乇 廿賱賶 毓乇囟 噩賲賷毓 兀噩賴夭丞 丕賱亘賱賵鬲賵孬 丕賱賲賰鬲卮賮丞 毓賳丿 丕賱賲爻丨. 爻賷丐丿賷 丕賱賳賯乇 亘丕禺鬲氐丕乇 廿賱賶 賳爻禺 丕爻賲 丕賱噩賴丕夭 賵毓賳賵丕賳 mac 廿賱賶 丕賱丨丕賮馗丞. 丕賱囟睾胤 賱賮鬲乇丞 胤賵賷賱丞 爻賷丐丿賷 廿賱賶 鬲卮睾賷賱 賲乇亘毓 丕賱丨賵丕乇 \"廿囟丕賮丞 噩賴丕夭 丕禺鬲亘丕乇\". 賷賲賰賳 兀賳 賷鬲爻亘亘 賮賷 丨丿賵孬 賲卮賰賱丕鬲 賲丨鬲賲賱丞 賮賷 鬲噩賲賷丿 丕賱鬲胤亘賷賯.</string>
+    <string name="fossil_hr_warning_firmware_too_new">鬲賲 鬲毓胤賷賱 亘毓囟 丕賱賵馗丕卅賮 賱兀賳 丕賱亘乇賳丕賲噩 丕賱孬丕亘鬲 賱賱爻丕毓丞 噩丿賷丿 噩丿賸丕</string>
+    <string name="pref_summary_physical_buttons">鬲賰賵賷賳 賵馗丕卅賮 丕賱兀夭乇丕乇 丕賱賲丕丿賷丞 毓賱賶 丕賱爻丕毓丞</string>
+    <string name="fossil_hr_commute_actions_explanation">爻鬲馗賴乇 丕賱廿噩乇丕亍丕鬲 丕賱鬲賷 鬲賲 鬲賰賵賷賳賴丕 賴賳丕 賮賷 鬲胤亘賷賯 Commute 毓賱賶 爻丕毓鬲賰. 丕賯乇兀 丕賱賵賷賰賷 賱賱丨氐賵賱 毓賱賶 賲毓賱賵賲丕鬲 丨賵賱 賰賷賮賷丞 丕賱鬲毓丕賲賱 賲毓 丕賱賲賯丕氐丿 丕賱賳丕鬲噩丞 毓賳 賴匕賴 丕賱廿噩乇丕亍丕鬲.</string>
+    <string name="sony_anc_optimize_description">丕賳賯乇 賱亘丿亍 賲丨爻賳 廿賱睾丕亍 丕賱囟賵囟丕亍.</string>
+    <string name="steps_streaks_hint">爻賱爻賱丞 賲賳 丕賱兀賷丕賲 丕賱賲鬲鬲丕賱賷丞 丿賵賳 丕賳賯胤丕毓 賲毓 丕賱賵氐賵賱 廿賱賶 賴丿賮 丕賱禺胤賵丕鬲</string>
+    <string name="step_streak_days_hint">毓丿丿 丕賱兀賷丕賲 丕賱賲鬲鬲丕賱賷丞 賲毓 丕賱賵氐賵賱 廿賱賶 賴丿賮 丕賱禺胤賵丕鬲</string>
+    <string name="prefs_hourly_chime_summary">爻鬲氐丿乇 丕賱爻丕毓丞 氐賵鬲丕 賲乇丞 賵丕丨丿丞 賮賷 丕賱爻丕毓丞</string>
+    <string name="contact_missing_number">乇賯賲 丕賱丕鬲氐丕賱 賮丕乇睾</string>
+    <string name="contact_missing_name">丕爻賲 噩賴丞 丕賱丕鬲氐丕賱 賮丕乇睾</string>
+    <string name="contact_phone_number">乇賯賲 丕賱賴丕鬲賮</string>
+    <string name="unit_imperial">廿賲亘乇丕胤賵乇賷</string>
+    <string name="timeformat_am_pm">氐亘丕丨丕 / 賲爻丕亍</string>
+    <string name="kind_font">丕賱禺胤</string>
+    <string name="prefs_fm_preset_instructions">丕囟睾胤 賱賮鬲乇丞 胤賵賷賱丞 毓賱賶 丕賱夭乇 賱鬲禺夭賷賳 丕賱廿毓丿丕丿 丕賱賲爻亘賯</string>
+    <string name="prefs_autoheartrate_summary">賯賷丕爻丕鬲 丿賵乇賷丞 賱賲毓丿賱 賳亘囟丕鬲 丕賱賯賱亘 兀孬賳丕亍 丕賱賳賴丕乇 賵兀賷囟賸丕 兀孬賳丕亍 丕賱賳賵賲</string>
+    <string name="android_pairing_hint">丕爻鬲禺丿賲 賲乇亘毓 丨賵丕乇 Android Bluetooth 丕賱丕賯鬲乇丕賳 賱廿賯乇丕賳 丕賱噩賴丕夭.</string>
+    <string name="miband_prefs_vibration">丕賴鬲夭丕夭</string>
+    <string name="prefs_activity_monitoring_description">賯賲 亘夭賷丕丿丞 鬲乇丿丿 丕賰鬲卮丕賮 賲毓丿賱 賳亘囟丕鬲 丕賱賯賱亘 鬲賱賯丕卅賷丕 毓賳丿賲丕 賷賰鬲卮賮 丕賱爻賵丕乇 賲賲丕乇爻丞 丕賱乇賷丕囟丞 丕賱亘丿賳賷丞 貙 賱夭賷丕丿丞 丿賯丞 丕賱鬲賯丕胤 賲毓丿賱 囟乇亘丕鬲 丕賱賯賱亘.</string>
+    <string name="prefs_password_6_digits_0_to_9_summary">賷噩亘 兀賳 鬲鬲賰賵賳 賰賱賲丞 丕賱賲乇賵乇 賲賳 6 兀乇賯丕賲 貙 亘丕爻鬲禺丿丕賲 兀乇賯丕賲 賮賯胤</string>
+    <string name="mi2_prefs_heart_rate_monitoring_alerts_summary">鬲賰賵賷賳 賲乇丕賯亘丞 賲毓丿賱 賳亘囟丕鬲 丕賱賯賱亘 賵毓鬲亘丕鬲 丕賱鬲賳亘賷賴</string>
+    <string name="arabic">丕賱毓乇亘賷丞</string>
+    <string name="vietnamese">丕賱賮賷鬲賳丕賲賷丞</string>
+    <string name="toast_app_must_be_selected">賷噩亘 鬲丨丿賷丿 丕賱鬲胤亘賷賯 賱賷鬲賲 鬲賰賵賷賳賴</string>
+    <string name="zetime_title_heart_rate_alarm_summary">丕賱爻丕毓丞 爻鬲丨匕乇賰 毓賳丿賲丕 賷鬲噩丕賵夭 賲毓丿賱 賯賱亘賰 丕賱丨丿賵丿.</string>
+    <string name="zetime_signaling_none">氐丕賲鬲</string>
+    <string name="female">兀賳孬賶</string>
+    <string name="pref_screen_auto_brightness_summary">囟亘胤 爻胤賵毓 丕賱卮丕卮丞 賵賮賯丕 賱賱廿囟丕亍丞 丕賱賲丨賷胤丞</string>
+    <string name="auto_reconnect_ble_summary">丨丕賵賱 亘卮賰賱 丕爻鬲亘丕賯賷 丕賱丕鬲氐丕賱 亘丕賱噩賴丕夭 亘卮賰賱 丿賵乇賷</string>
+    <string name="steps_streaks_total_steps_hint">廿噩賲丕賱賷 毓丿丿 丕賱禺胤賵丕鬲 賮賷 丕賱禺胤 亘兀賰賲賱賴</string>
+    <string name="steps_streaks_total_days_hint_totals">丕賱賳爻亘丞 丕賱賲卅賵賷丞 賱賱兀賷丕賲 匕丕鬲 丕賱賴丿賮 丕賱賲丨賯賯 亘丕賱賳爻亘丞 賱噩賲賷毓 丕賱兀賷丕賲 丕賱鬲賷 亘賴丕 禺胤賵丕鬲</string>
+    <string name="activity_prefs_summary_allow_bluetooth_intent_api">丕賱爻賲丕丨 亘丕賱鬲丨賰賲 賮賷 丕鬲氐丕賱 Bluetooth 毓亘乇 賵丕噩賴丞 亘乇賲噩丞 鬲胤亘賷賯丕鬲 Intent</string>
+    <string name="interval_fifteen_minutes">賰賱 15 丿賯賷賯丞</string>
+    <string name="heartrate_bpm_145">145 賳亘囟丞 賮賷 丕賱丿賯賷賯丞</string>
+    <string name="heartrate_bpm_150">150 賳亘囟丞 賮賷 丕賱丿賯賷賯丞</string>
+    <string name="prefs_find_phone_summary">丕爻鬲禺丿賲 爻賵丕乇賰 賱鬲卮睾賷賱 賳睾賲丞 乇賳賷賳 賴丕鬲賮賰.</string>
+    <string name="mi2_enable_text_notifications_summary"><![CDATA[Needs firmware >= 1.0.1.28 and Mili_pro.ft* installed.]]></string>
+    <string name="warning">鬲丨匕賷乇!</string>
+    <string name="pref_chart_heartrate_color_red">兀丨賲乇</string>
+    <string name="pref_charts_range_on">鬲賲 鬲毓賷賷賳 賳胤丕賯 丕賱乇爻賵賲 丕賱亘賷丕賳賷丞 廿賱賶 卮賴乇</string>
+    <string name="notification_channel_high_priority_name">兀賵賱賵賷丞 毓丕賱賷丞</string>
+    <string name="sonyswr12_settings_low_vibration_summary">鬲賮毓賷賱 卮丿丞 丕賴鬲夭丕夭 賲賳禺賮囟丞 毓賱賶 爻賵丕乇 丕賱賲毓氐賲</string>
+    <string name="autoconnect_from_device_summary">賯賲 亘鬲兀爻賷爻 丕鬲氐丕賱 毓賳丿賲丕 賷亘丿兀 丕賱丕鬲氐丕賱 亘賵丕爻胤丞 丕賱噩賴丕夭 貙 賲孬賱 爻賲丕毓丕鬲 丕賱乇兀爻</string>
+    <string name="pref_workout_detection_time_summary">賲賯丿丕乇 丕賱丿賯丕卅賯 丕賱鬲賷 賷噩亘 兀賳 賷爻鬲賲乇 賮賷賴丕 丕賱鬲賲乇賷賳 賯亘賱 丕賰鬲卮丕賮賴</string>
+    <string name="app_install_info">兀賳鬲 毓賱賶 賵卮賰 鬲孬亘賷鬲 丕賱鬲胤亘賷賯 丕賱鬲丕賱賷:
+\n
+\n%1$s
+\n廿氐丿丕乇 %2$s 亘賵丕爻胤丞 %3$s
+\n</string>
+    <string name="discovery_need_to_enter_authkey">賷丨鬲丕噩 賴匕丕 丕賱噩賴丕夭 廿賱賶 賲賮鬲丕丨 賲氐丕丿賯丞 爻乇賷 貙 丕囟睾胤 賲胤賵賱丕賸 毓賱賶 丕賱噩賴丕夭 賱廿丿禺丕賱賴. 丕賯乇兀 丕賱賵賷賰賷.</string>
+    <string name="vibration_profile_medium">賲鬲賵爻胤</string>
+    <string name="vibration_profile_long">胤賵賷賱</string>
+    <string name="vibration_profile_ring">噩乇爻</string>
+    <string name="vibration_try">丨丕賵賱</string>
+    <string name="pref_screen_notification_profile_generic_chat">賲丨丕丿孬丞</string>
+    <string name="world_clock_delete_confirm_description">賴賱 兀賳鬲 賲鬲兀賰丿 兀賳賰 鬲乇賷丿 丨匕賮 丕賱爻丕毓丞 丕賱毓丕賱賲賷丞責</string>
+    <string name="world_clock_no_free_slots_description">賱丕 賷丨鬲賵賷 丕賱噩賴丕夭 毓賱賶 氐賮丨丕鬲 丨乇丞 賱賱爻丕毓丕鬲 丕賱毓丕賱賲賷丞 (廿噩賲丕賱賷 丕賱氐賮丨丕鬲: %1$s)</string>
+    <string name="alarm_sun_short">丕賱丕丨</string>
+    <string name="alarm_sat_short">丕賱爻</string>
+    <string name="alarm_snooze">睾賮賵丞</string>
+    <string name="firmware_update_progress">丕賱鬲丨賲賷賱 賯賷丿 丕賱鬲賯丿賲
+\n%1d 侏侏 亘賲毓丿賱 %.2f 賰賷賱賵亘鬲 賮賷 丕賱孬丕賳賷丞 (賲鬲賵爻胤 %.2f 賰賷賱賵亘鬲 賮賷 丕賱孬丕賳賷丞)
+\n丕賱噩夭亍 %1d 賲賳 %1d</string>
+    <string name="pref_huami_truncate_fetch_operation_timestamps_summary">丕賯鬲胤毓 丕賱胤賵丕亘毓 丕賱夭賲賳賷丞 賱毓賲賱賷丞 丕賱噩賱亘 廿賱賶 丿賯丕卅賯. 賯賲 亘鬲毓胤賷賱 賴匕丕 丕賱廿毓丿丕丿 賱賱丕丨鬲賮丕馗 亘丕賱胤賵丕亘毓 丕賱夭賲賳賷丞 賮賷 孬賵丕賳賺 貙 廿匕丕 賵丕噩賴鬲 賲卮賰賱丕鬲 兀孬賳丕亍 噩賱亘 丕賱鬲丿乇賷亘丕鬲 丕賱賯氐賷乇丞 噩丿賸丕.</string>
+    <string name="pref_summary_keep_data_on_device">爻賷鬲賲 丕賱丕丨鬲賮丕馗 亘亘賷丕賳丕鬲 丕賱賳卮丕胤 毓賱賶 丕賱噩賴丕夭 丨鬲賶 亘毓丿 丕賱賲夭丕賲賳丞. 賷賰賵賳 賲賮賷丿賸丕 廿匕丕 鬲賲 丕爻鬲禺丿丕賲 GB 賲毓 鬲胤亘賷賯丕鬲 兀禺乇賶. 賯丿 賷鬲爻亘亘 賴匕丕 賮賷 賳賮丕丿 匕丕賰乇丞 丕賱爻丕毓丞 賵/兀賵 鬲賵賯賮 丕賱賲夭丕賲賳丞 亘卮賰賱 賯胤毓賷.</string>
+    <string name="portuguese">丕賱亘乇鬲睾丕賱賷丞</string>
+    <string name="pref_chart_heartrate_color_orange">亘乇鬲賯丕賱賷</string>
+    <string name="activity_summary_today">丕賱賷賵賲</string>
+    <string name="activity_type_treadmill">噩賴丕夭 丕賱賲卮賷</string>
+    <string name="activity_type_exercise">鬲賲乇賷賳</string>
+    <string name="error_location_enabled_mandatory">賷噩亘 鬲卮睾賷賱 丕賱賲賵賯毓 賱賱亘丨孬 毓賳 丕賱兀噩賴夭丞</string>
+    <string name="clock">丕賱爻丕毓丞</string>
+    <string name="german">丕賱兀賱賲丕賳賷丞</string>
+    <string name="pref_pair_bluetooth_calls_help_summary">賱鬲賱賯賷 賲賰丕賱賲丕鬲 丕賱亘賱賵鬲賵孬 貙 鬲丨鬲丕噩 廿賱賶 廿賯乇丕賳 賴丕鬲賮賰 亘賲孬賷賱 孬丕賳賺 賱賱爻丕毓丞.</string>
+    <string name="pref_pair_bluetooth_calls_help_1">1. 丕囟睾胤 毓賱賶 丕賱夭乇 兀丿賳丕賴 賱亘丿亍 毓賲賱賷丞 丕賱丕賯鬲乇丕賳.</string>
+    <string name="pref_title_support_voip_calls">鬲賮毓賷賱 賲賰丕賱賲丕鬲 鬲胤亘賷賯 VoIP</string>
+    <string name="pref_title_notifications_pebblemsg">乇爻丕卅賱 Pebble</string>
+    <string name="pref_summary_notifications_pebblemsg">丿毓賲 賱賱鬲胤亘賷賯丕鬲 丕賱鬲賷 鬲乇爻賱 廿卮毓丕乇丕鬲 廿賱賶 Pebble 毓亘乇 PebbleKit.</string>
+    <string name="pref_title_notifications_generic">丿毓賲 丕賱廿卮毓丕乇丕鬲 丕賱毓丕賲丞</string>
+    <string name="pref_summary_minimize_priority_on">賷鬲賲 廿禺賮丕亍 丕賱乇賲夭 丕賱賲賵噩賵丿 賮賷 卮乇賷胤 丕賱丨丕賱丞 賵丕賱廿卮毓丕乇 賮賷 卮丕卮丞 丕賱賯賮賱</string>
+    <string name="pref_summary_notification_delay_calls">鬲兀禺賷乇 賯亘賱 廿乇爻丕賱 廿卮毓丕乇丕鬲 丕賱賲賰丕賱賲丕鬲 丕賱賵丕乇丿丞 廿賱賶 丕賱噩賴丕夭 貙 賮賷 孬賵丕賳.</string>
+    <string name="pref_title_notifications_generic_settings">廿毓丿丕丿丕鬲 廿卮毓丕乇丕鬲 丕賳丿乇賵賷丿</string>
+    <string name="pref_title_notification_prefer_long_text">鬲賮囟賷賱 賳氐 胤賵賷賱 賱賱廿卮毓丕乇</string>
+    <string name="pref_summary_notification_cache_while_disconnected">廿乇爻丕賱 廿卮毓丕乇丕鬲 賮丕卅鬲丞 毓賳丿 廿毓丕丿丞 丕鬲氐丕賱 丕賱噩賴丕夭 亘毓丿 禺乇賵噩賴 毓賳 丕賱賳胤丕賯</string>
+    <string name="pref_header_notification_application_settings">廿毓丿丕丿丕鬲 賱賰賱 鬲胤亘賷賯</string>
+    <string name="pref_title_notification_use_as_deny">乇賮囟 丕賱廿卮毓丕乇丕鬲 賲賳 丕賱鬲胤亘賷賯丕鬲 丕賱賲丨丿丿丞</string>
+    <string name="pref_summary_banglejs_text_bitmap">廿匕丕 鬲毓匕乇 毓乇囟 賰賱賲丞 亘禺胤 丕賱爻丕毓丞貙 賮賯賲 亘鬲丨賵賷賱賴丕 廿賱賶 氐賵乇丞 賳賯胤賷丞 賮賷 Gadgetbridge 賵丕毓乇囟 丕賱氐賵乇丞 丕賱賳賯胤賷丞 毓賱賶 丕賱爻丕毓丞</string>
+    <string name="pref_title_weather_summary">鬲爻鬲禺丿賲 廿氐丿丕乇丕鬲 Android 丕賱兀禺乇賶 貙 丕賱賲爻鬲禺丿賲丞 賱賲夭賵丿 禺丿賲丞 丕賱胤賯爻 LineageOS 貙 鬲胤亘賷賯賸丕 賲孬賱 \"廿卮毓丕乇 丕賱胤賯爻\". 丕毓孬乇 毓賱賶 賲夭賷丿 賲賳 丕賱賲毓賱賵賲丕鬲 賮賷 Gadgetbridge wiki.</string>
+    <string name="pref_blacklist_calendars">鬲賯賵賷賲丕鬲 丕賱賯丕卅賲丞 丕賱爻賵丿丕亍</string>
+    <string name="pref_blacklist_calendars_summary">賱賳 鬲鬲賲 賲夭丕賲賳丞 丕賱鬲賯賵賷賲丕鬲 丕賱賲丿乇噩丞 賮賷 丕賱賯丕卅賲丞 丕賱爻賵丿丕亍 賲毓 丕賱噩賴丕夭</string>
+    <string name="pref_title_canned_reply_suffix">賱丕丨賯丞 賲卮鬲乇賰丞</string>
+    <string name="pref_header_pebble_timeline">Pebble 丕賱噩丿賵賱 丕賱夭賲賳賷</string>
+    <string name="pref_title_screen_on_on_notifications">丕賱卮丕卮丞 賯賷丿 丕賱鬲卮睾賷賱 毓賳丿 丕賱廿卮毓丕乇丕鬲</string>
+    <string name="pref_title_pebble_forcele">鬲賮囟賷賱 BLE 丿丕卅賲賸丕</string>
+    <string name="pref_summary_expose_hr">賷爻賲丨 賱賱鬲胤亘賷賯丕鬲 丕賱兀禺乇賶 亘丕賱賵氐賵賱 廿賱賶 亘賷丕賳丕鬲 丕賱賲賵丕乇丿 賮賷 丕賱賵賯鬲 丕賱賮毓賱賷 兀孬賳丕亍 丕鬲氐丕賱 Gadgetbridge</string>
+    <string name="pref_summary_device_internet_access">丕爻賲丨 賱賱鬲胤亘賷賯丕鬲 丕賱賲賵噩賵丿丞 毓賱賶 賴匕丕 丕賱噩賴丕夭 亘丕賱賵氐賵賱 廿賱賶 丕賱廿賳鬲乇賳鬲</string>
+    <string name="pref_explanation_authkey">鬲鬲胤賱亘 亘毓囟 丕賱兀噩賴夭丞 賲賮鬲丕丨 廿賯乇丕賳 禺丕氐 賲賳 兀噩賱 丕賱鬲賴賷卅丞 丕賱兀賵賱賶 賱賱噩賴丕夭. 丕賳賯乇 賴賳丕 賱賲夭賷丿 賲賳 丕賱鬲賮丕氐賷賱 賮賷 丕賱賵賷賰賷.</string>
+    <string name="pref_morning_updates_title">鬲丨丿賷孬丕鬲 丕賱氐亘丕丨</string>
+    <string name="right">賷賲賷賳</string>
+    <string name="horizontal">兀賮賯賷</string>
+    <string name="appinstaller_install">鬲孬亘賷鬲</string>
+    <string name="discovery_connected_devices_hint">丕噩毓賱 噩賴丕夭賰 賯丕亘賱丕賸 賱賱丕賰鬲卮丕賮. 賲賳 丕賱賲丨鬲賲賱 兀賱丕 賷鬲賲 丕賰鬲卮丕賮 丕賱兀噩賴夭丞 丕賱賲鬲氐賱丞 丨丕賱賷賸丕. 賯賲 亘鬲賳卮賷胤 丕賱賲賵賯毓 (賲孬賱 GPS) 毓賱賶 Android 6+. 鬲毓胤賷賱 Privacy Guard 賱賭 Gadgetbridge 貙 賱兀賳賴 賯丿 賷鬲毓胤賱 賵賷毓賷丿 鬲卮睾賷賱 賴丕鬲賮賰. 廿匕丕 賱賲 賷鬲賲 丕賱毓孬賵乇 毓賱賶 兀賷 噩賴丕夭 亘毓丿 亘囟毓 丿賯丕卅賯 貙 賮丨丕賵賱 賲乇丞 兀禺乇賶 亘毓丿 廿毓丕丿丞 鬲卮睾賷賱 噩賴丕夭賰 丕賱賲丨賲賵賱.</string>
+    <string name="pref_screen_vibration_patterns_summary">鬲賰賵賷賳 兀賳賲丕胤 丕賱丕賴鬲夭丕夭 賱賱廿卮毓丕乇丕鬲 丕賱賲禺鬲賱賮丞</string>
+    <string name="miband_prefs_alias">丕賱丕爻賲/ 丕賱賲爻鬲毓丕乇</string>
+    <string name="initializing">噩丕乇 鬲賴賷卅丞</string>
+    <string name="heartrate_bpm_40">40 賳亘囟丞 賮賷 丕賱丿賯賷賯丞</string>
+    <string name="heartrate_bpm_45">45 賳亘囟丞 賮賷 丕賱丿賯賷賯丞</string>
+    <string name="stats_title">賲賳丕胤賯 丕賱爻乇毓丞</string>
+    <string name="battery">丕賱亘胤丕乇賷丞</string>
+    <string name="pref_enable_unsupported_settings_summary">爻賷丐丿賷 賴匕丕 廿賱賶 鬲賲賰賷賳 丕賱賵氐賵賱 廿賱賶 噩賲賷毓 丕賱廿毓丿丕丿丕鬲 丕賱賲鬲丕丨丞 貙 丨鬲賶 廿匕丕 賰丕賳 丕賱噩賴丕夭 睾賷乇 賲丿毓賵賲. 賴匕丕 賷賲賰賳 兀賳 賷爻亘亘 毓丿賲 丕賱丕爻鬲賯乇丕乇 賵鬲毓胤賱 丕賱噩賴丕夭.</string>
+    <string name="lefun_prefs_hydration_reminder_summary">爻賷賴鬲夭 丕賱爻賵丕乇 賱鬲匕賰賷乇賰 亘卮乇亘 丕賱賲丕亍</string>
+    <string name="pref_summary_force_white_color_scheme">賲賮賷丿 廿匕丕 賰丕賳鬲 爻丕毓鬲賰 匕丕鬲 毓賯丕乇亘 丿丕賰賳丞</string>
+    <string name="russian">丕賱乇賵爻賷丞</string>
+    <string name="thai">丕賱鬲丕賷賱賳丿賷丞</string>
+    <string name="greek">丕賱賷賵賳丕賳賷丞</string>
+    <string name="activity_prefs_chart_min_steps_per_minute_for_run">丕賱丨丿 丕賱兀丿賳賶 賲賳 丕賱禺胤賵丕鬲 賮賷 丕賱丿賯賷賯丞 賱賱賰卮賮 毓賳 丕賱噩乇賷</string>
+    <string name="pref_summary_pebble_health_store_raw">賷禺夭賳 丕賱亘賷丕賳丕鬲 \"賰賲丕 賴賷\" 貙 賲賲丕 賷夭賷丿 賲賳 丕爻鬲禺丿丕賲 賯丕毓丿丞 丕賱亘賷丕賳丕鬲 賱賱爻賲丕丨 亘鬲賮爻賷乇賴丕 賱丕丨賯賸丕.</string>
+    <string name="activity_type_biking">乇賰賵亘 丕賱丿乇丕噩丞</string>
+    <string name="activity_summary_yesterday">兀賲爻</string>
+    <string name="edittext_notification_filter_words_hint">兀丿禺賱 丕賱賰賱賲丕鬲 丕賱賲胤賱賵亘丞 貙 爻胤乇 噩丿賷丿 賱賰賱 賲賳賴丕</string>
+    <string name="qhybrid_prompt_million_steps">賷乇噩賶 鬲毓賷賷賳 毓丿丿 丕賱禺胤賵丕鬲 廿賱賶 賲賱賷賵賳 賱鬲賮毓賷賱 匕賱賰.</string>
+    <string name="sports_activity_confirm_delete_description">賴賱 兀賳鬲 賲鬲兀賰丿 兀賳賰 鬲乇賷丿 丨匕賮 兀賳卮胤丞 %d責</string>
+    <string name="qhybrid_pref_summary_external_intents">賷爻賲丨 賱鬲胤亘賷賯丕鬲 Android 丕賱兀禺乇賶 亘鬲丨賲賷賱 / 丕賱賰鬲丕亘丞 賮賵賯 丕賱賲賱賮丕鬲</string>
+    <string name="prefs_game_mode_summary">賮賯胤 廿匕丕 賰丕賳 賴丕鬲賮賰 賷丿毓賲 賵囟毓 丕賱賱毓亘丞</string>
+    <string name="pref_explanation_authkey_new_protocol">廿匕丕 鬲賱賯賷鬲 乇爻丕賱丞 \"鬲丨丿賷孬 丕賱鬲胤亘賷賯 廿賱賶 兀丨丿孬 廿氐丿丕乇\" 毓賱賶 丕賱爻賵丕乇 貙 賮鬲兀賰丿 賲賳 丕賱鬲丨賯賯 賲賳 \"亘乇賵鬲賵賰賵賱 賲氐丕丿賯丞 噩丿賷丿\" 兀毓賱丕賴. 丕賳賯乇 賴賳丕 賱賲夭賷丿 賲賳 丕賱鬲賮丕氐賷賱 賮賷 丕賱賵賷賰賷.</string>
+    <string name="pref_workout_detection_title">賰卮賮 丕賱鬲賲乇賷賳</string>
+    <string name="prefs_sounds">丕賱丕氐賵丕鬲</string>
+    <string name="user_feedback_miband_set_alarms_failed">丨丿孬 禺胤兀 兀孬賳丕亍 囟亘胤 丕賱賲賳亘賴丕鬲 貙 賷乇噩賶 丕賱賲丨丕賵賱丞 賲乇丞 兀禺乇賶.</string>
+    <string name="heartrate_bpm_120">120 賳亘囟丞 賮賷 丕賱丿賯賷賯丞</string>
+    <string name="heartrate_bpm_130">130 賳亘囟丞 賮賷 丕賱丿賯賷賯丞</string>
+    <string name="abstract_chart_fragment_kind_activity">丕賱賳卮丕胤</string>
+    <string name="bip_prefs_shotcuts_summary">丕禺鬲乇 丕賱丕禺鬲氐丕乇丕鬲 毓賱賶 卮丕卮丞 丕賱爻賵丕乇</string>
+    <string name="english">丕賱廿賳噩賱賷夭賷丞</string>
+    <string name="spanish">丕賱廿爻亘丕賳賷丞</string>
+    <string name="polish">丕賱亘賵賱賳丿賷丞</string>
+    <string name="korean">丕賱賰賵乇賷丞</string>
+    <string name="japanese">丕賱賷丕亘丕賳賷丞</string>
+    <string name="dutch">丕賱賴賵賱賳丿賷丞</string>
+    <string name="turkish">丕賱鬲乇賰賷丞</string>
+    <string name="activity_prefs_goal_fat_burn_time_minutes">丕賱賴丿賮 丕賱賷賵賲賷: 賵賯鬲 丨乇賯 丕賱丿賴賵賳 賮賷 丿賯丕卅賯</string>
+    <string name="activity_db_management_autoexport_label">丕賱鬲氐丿賷乇 丕賱鬲賱賯丕卅賷</string>
+    <string name="Delete">丨匕賮</string>
+    <string name="set">鬲毓賷賷賳</string>
+    <string name="off">丕賷賯丕賮</string>
+    <string name="activity_type_running">丕賱噩乇賷</string>
+    <string name="devicetype_pebble">Pebble</string>
+    <string name="hr_widget_steps">丕賱禺胤賵丕鬲</string>
+    <string name="hr_widget_date">丕賱鬲丕乇賷禺</string>
+    <string name="prefs_voice_detect_summary">鬲賲賰賷賳 丕賱氐賵鬲 丕賱賲丨賷胤 賵禺賮囟 賲丕賷鬲賲 鬲卮睾賷賱賴 丨丕賱賷丕 亘卮賰賱 鬲賱賯丕卅賷 亘毓丿 丕賰鬲卮丕賮 丕賱氐賵鬲</string>
+    <string name="pref_header_intent_api">賵丕噩賴丞 亘乇賲噩丞 鬲胤亘賷賯丕鬲 Intent</string>
+    <string name="pref_header_authentication">丕賱賲氐丕丿賯丞</string>
+    <string name="pref_sleep_mode_sleep_screen_summary">兀馗賴乇 卮丕卮丞 丕賱爻賰賵賳 毓賳丿 廿賷賯丕馗 丕賱卮丕卮丞 兀孬賳丕亍 賵囟毓 丕賱爻賰賵賳 賱鬲賯賱賷賱 賲氐丕丿乇 丕賱鬲卮鬲賷鬲</string>
+    <string name="vibration_profile_staccato">賲鬲賯胤毓</string>
+    <string name="vibration_profile_short">賯氐賷乇</string>
+    <string name="pref_screen_notification_profile_generic_navigation">丕賱賲賱丕丨丞</string>
+    <string name="activity_list_summary_distance">丕賱賲爻丕賮丞</string>
+    <string name="dialog_hide">丕禺賮丕亍</string>
+    <string name="ukrainian">丕賱兀賵賰乇丕賳賷丞</string>
+    <string name="hungarian">丕賱賲噩乇賷丞</string>
+    <string name="activity_prefs_gender">噩賳爻</string>
+    <string name="pref_title_charts_swipe">鬲賲賰賷賳 丕賱鬲賲乇賷乇 賱賱賷爻丕乇 / 賱賱賷賲賷賳 賮賷 賳卮丕胤 丕賱乇爻賵賲 丕賱亘賷丕賳賷丞</string>
+    <string name="activity_type_cricket">賰乇賷賰賷鬲</string>
+    <string name="menuitem_stopwatch">爻丕毓丞 鬲賵賯賷鬲</string>
+    <string name="menuitem_flashlight">賲氐亘丕丨 賷丿賵賷</string>
+    <string name="sony_anc_optimize_confirmation_description">丕爻鬲禺丿賲 爻賲丕毓丕鬲 丕賱乇兀爻 賰賲丕 鬲賮毓賱 毓丕丿丞. 廿匕丕 鬲睾賷乇鬲 丨丕賱丞 丕賱鬲丌賰賱 兀賵 丕賱囟睾胤 丕賱噩賵賷 貙 賮賯賲 亘鬲卮睾賷賱 丕賱賲丨爻賳 賲乇丞 兀禺乇賶.</string>
+    <string name="intent_api_allow_activity_sync_summary">丕賱爻賲丕丨 亘賲夭丕賲賳丞 丕賱賳卮丕胤 丕賱賲卮睾賱 毓亘乇 Intent API</string>
+    <string name="romanian">丕賱乇賵賲丕賳賷丞</string>
+    <string name="hebrew">丕賱毓亘乇賷丞</string>
+    <string name="activity_prefs_chart_min_steps_per_minute">丕賱丨丿 丕賱兀丿賳賶 賲賳 丕賱禺胤賵丕鬲 賮賷 丕賱丿賯賷賯丞 賱賱賰卮賮 毓賳 丕賱賳卮丕胤</string>
+    <string name="italian">丕賱廿賷胤丕賱賷丞</string>
+    <string name="pref_sleep_mode_smart_enable_summary">賯賲 亘鬲賲賰賷賳 賵囟毓 丕賱爻賰賵賳 鬲賱賯丕卅賷賸丕 毓賳丿 丕乇鬲丿丕亍 丕賱爻賵丕乇 兀孬賳丕亍 丕賱賳賵賲</string>
+    <string name="zetime_prefs_inactivity_tu">丕賱孬賱丕孬丕亍</string>
+    <string name="prefs_reserve_reminder_calendar_summary">毓丿丿 兀丨丿丕孬 丕賱鬲賯賵賷賲 丕賱鬲賷 爻鬲鬲賲 賲夭丕賲賳鬲賴丕</string>
+    <string name="zetime_handmove_display_summary">賯賲 亘鬲丿賵賷乇 賲毓氐賲賰 賱鬲賳卮賷胤 丕賱卮丕卮丞 兀賵 廿賱睾丕亍 鬲賳卮賷胤賴丕.</string>
+    <string name="pref_title_banglejs_txt_bitmap_size">丨噩賲 丕賱氐賵乇 丕賱賳賯胤賷丞 賱賱賳氐</string>
+    <string name="menuitem_nfc">廿賳 廿賮 爻賷</string>
+    <string name="menuitem_spo2">SpO2</string>
+    <string name="sonyswr12_settings_stamina_summary">賷賯賵賲 賵囟毓 鬲賵賮賷乇 丕賱胤丕賯丞 亘廿賷賯丕賮 鬲卮睾賷賱 丕賱賯賷丕爻 丕賱鬲賱賯丕卅賷 丕賱丿賵乇賷 賱賲毓丿賱 賳亘囟丕鬲 丕賱賯賱亘 賵亘丕賱鬲丕賱賷 賷夭賷丿 賲賳 賵賯鬲 丕賱毓賲賱</string>
+    <string name="prefs_double_tap_edge_summary">丕賰鬲卮賮 丕賱賳賯乇 丕賱賲夭丿賵噩 丨鬲賶 毓賳丿 毓丿賲 丕賱賳賯乇 毓賱賶 賱賵丨丞 丕賱賱賲爻</string>
+    <string name="fossil_hr_connection_not_confirmed">賱賲 賷鬲賲 鬲兀賰賷丿 丕賱丕鬲氐丕賱 毓賱賶 丕賱爻丕毓丞 貙 亘丕爻鬲禺丿丕賲 賵囟毓 睾賷乇 賲氐丕丿賯 毓賱賷賴</string>
+    <string name="automatic">鬲賱賯丕卅賷</string>
+    <string name="bluetooth_calls">賲賰丕賱賲丕鬲 丕賱亘賱賵鬲賵孬</string>
+    <string name="bluetooth_calls_pairing">廿賯乇丕賳 賲賰丕賱賲丕鬲 亘賱賵鬲賵孬</string>
+    <string name="bluetooth_calls_settings">廿毓丿丕丿丕鬲 賲賰丕賱賲丕鬲 丕賱亘賱賵鬲賵孬</string>
+    <string name="pref_display_caller_title">廿馗賴丕乇 賲毓賱賵賲丕鬲 丕賱丕鬲氐丕賱</string>
+    <string name="pref_display_caller_summary">毓乇囟 乇賯賲 丕賱賴丕鬲賮 兀賵 丕賱丕爻賲 賱賱賲賰丕賱賲丕鬲 丕賱賵丕乇丿丞</string>
+    <string name="pref_pair_bluetooth_calls_title">廿賯乇丕賳 賱賲賰丕賱賲丕鬲 丕賱亘賱賵鬲賵孬</string>
+    <string name="pref_pair_bluetooth_calls_summary">丕賳賯乇 賴賳丕 賱亘丿亍 毓賲賱賷丞 丕賱丕賯鬲乇丕賳</string>
+    <string name="pref_pair_bluetooth_calls_help_title">賰賷賮賷丞 丕爻鬲賯亘丕賱 賲賰丕賱賲丕鬲 丕賱亘賱賵鬲賵孬</string>
+    <string name="pref_summary_receive_calls_watch">賯賲 亘廿噩乇丕亍 賵丕爻鬲賯亘丕賱 丕賱賲賰丕賱賲丕鬲 賲亘丕卮乇丞 毓賱賶 丕賱爻丕毓丞</string>
+    <string name="pref_title_banglejs_text_bitmap">丕賱賳氐 賰氐賵乇 賳賯胤賷丞</string>
+    <string name="pref_summary_banglejs_txt_bitmap_size">丕賱丨噩賲 丕賱賲胤賱賵亘 丕爻鬲禺丿丕賲賴 賱毓乇囟 丕賱賳氐 丕賱賳賯胤賷</string>
+    <string name="pref_summary_overwrite_settings_on_connection">毓賳丿 丕賱丕鬲氐丕賱 亘丕賱爻賵丕乇 貙 丕賰鬲亘 噩賲賷毓 丕賱廿毓丿丕丿丕鬲 丕賱賲賵噩賵丿丞 毓賱賷賴.</string>
+    <string name="left">賷爻丕乇</string>
+    <string name="user_feedback_miband_activity_data_transfer">毓賱賶 賵卮賰 賳賯賱 %1$s 賲賳 丕賱亘賷丕賳丕鬲 亘丿亍丕 賲賳 %2$s</string>
+    <string name="heartrate_bpm_105">105 賳亘囟丞 賮賷 丕賱丿賯賷賯丞</string>
+    <string name="stats_x_axis_label">廿噩賲丕賱賷 丕賱丿賯丕卅賯</string>
+    <string name="prefs_password_4_digits_1_to_4_summary">賷噩亘 兀賳 鬲鬲賰賵賳 賰賱賲丞 丕賱賲乇賵乇 賲賳 4 兀乇賯丕賲 貙 亘丕爻鬲禺丿丕賲 丕賱兀乇賯丕賲 賲賳 1 廿賱賶 4</string>
+    <string name="pref_chart_sleep_lines_limit">賰賲 毓丿丿 禺胤賵胤 噩賱爻丞 丕賱賳賵賲 丕賱鬲賷 賷噩亘 廿馗賴丕乇賴丕 賯亘賱 丕賱鬲賲乇賷乇</string>
+    <string name="pref_charts_range_off">鬲賲 鬲毓賷賷賳 賳胤丕賯 丕賱乇爻賵賲 丕賱亘賷丕賳賷丞 毓賱賶 兀爻亘賵毓</string>
+    <string name="dbmanagementactivity_delete_old_activitydb_confirmation">賴賱 鬲乇睾亘 丨賯賸丕 賮賷 丨匕賮 賯丕毓丿丞 亘賷丕賳丕鬲 丕賱賳卮丕胤 丕賱賯丿賷賲丞責 爻鬲賮賯丿 亘賷丕賳丕鬲 丕賱賳卮丕胤 丕賱鬲賷 賱賲 賷鬲賲 丕爻鬲賷乇丕丿賴丕.</string>
+    <string name="menuitem_temperature">丿乇噩丞 丕賱丨乇丕乇丞</string>
+    <string name="menuitem_barometer">丕賱亘丕乇賵賲鬲乇</string>
+    <string name="pref_summary_canned_messages_set">廿乇爻丕賱 丕賱乇爻丕卅賱 丕賱鬲賷 鬲賲 鬲賰賵賷賳賴丕 兀丿賳丕賴 廿賱賶 噩賴丕夭賰</string>
+    <string name="enable_vibrations_summary">丕賱丕賴鬲夭丕夭丕鬲 賱賱賲賰丕賱賲丕鬲 賵丕賱乇爻丕卅賱 賵丕賱廿卮毓丕乇丕鬲 賵丕賱賲夭賷丿</string>
+    <string name="share_log_warning">賷乇噩賶 賲乇丕毓丕丞 賲賱賮丕鬲 爻噩賱丕鬲 Gadgetbridge 丕賱鬲賷 賯丿 鬲丨鬲賵賷 毓賱賶 丕賱賰孬賷乇 賲賳 丕賱賲毓賱賵賲丕鬲 丕賱卮禺氐賷丞 貙 亘賲丕 賮賷 匕賱賰 毓賱賶 爻亘賷賱 丕賱賲孬丕賱 賱丕 丕賱丨氐乇 丕賱亘賷丕賳丕鬲 丕賱氐丨賷丞 賵丕賱賲毓乇賮丕鬲 丕賱賮乇賷丿丞 (賲孬賱 毓賳賵丕賳 MAC 賱賱噩賴丕夭) 賵鬲賮囟賷賱丕鬲 丕賱賲賵爻賷賯賶 賵賲丕 廿賱賶 匕賱賰. 囟毓 賮賷 丕毓鬲亘丕乇賰 鬲丨乇賷乇 丕賱賲賱賮 賵廿夭丕賱丞 賴匕賴 丕賱賲毓賱賵賲丕鬲 賯亘賱 廿乇爻丕賱 賲賱賮 廿賱賶 鬲賯乇賷乇 賯囟賷丞 毓丕賲丞.</string>
+    <string name="pref_pair_bluetooth_calls_help_2">2. 丕賳鬲賯賱 廿賱賶 廿毓丿丕丿丕鬲 丕賱亘賱賵鬲賵孬 亘賴丕鬲賮賰 貙 賵賯賲 亘丕賱丕賯鬲乇丕賳 亘丕賱噩賴丕夭 丕賱噩丿賷丿 丕賱匕賷 爻賷馗賴乇 (丕爻賲 賲卮丕亘賴 賱爻丕毓鬲賰 丕賱丨丕賱賷丞 貙 賵賱賰賳 賲毓 賱丕丨賯丞 貙 毓賱賶 爻亘賷賱 丕賱賲孬丕賱 \"Amazfit GTR 4 - AFC8\".</string>
+    <string name="pref_pair_bluetooth_calls_help_3">3. 賯賲 亘鬲賲賰賷賳 廿毓丿丕丿 賲賰丕賱賲丕鬲 丕賱亘賱賵鬲賵孬 兀丿賳丕賴.</string>
+    <string name="pref_title_notifications_ignore_low_priority">鬲噩丕賴賱 丕賱廿卮毓丕乇丕鬲 匕丕鬲 丕賱兀賵賱賵賷丞 丕賱賲賳禺賮囟丞</string>
+    <string name="pref_pair_bluetooth_calls_help_warning">鬲丨匕賷乇: 廿匕丕 賯賲鬲 亘鬲賲賰賷賳 賲賰丕賱賲丕鬲 丕賱亘賱賵鬲賵孬 丿賵賳 丕賱丕賯鬲乇丕賳 亘丕賱賲孬賷賱 丕賱孬丕賳賷 貙 賮賯丿 賱丕 鬲毓賲賱 廿卮毓丕乇丕鬲 丕賱賲賰丕賱賲丕鬲 賰賲丕 賴賵 賲鬲賵賯毓.</string>
+    <string name="pref_summary_notifications_ignore_low_priority">賱丕 鬲乇爻賱 廿卮毓丕乇丕鬲 匕丕鬲 兀賵賱賵賷丞 賲賳禺賮囟丞 賵丿賳賷丕 廿賱賶 丕賱爻丕毓丞</string>
+    <string name="pref_summary_notification_prefer_long_text">廿匕丕 賰丕賳 匕賱賰 賲鬲丕丨丕 貙 賮兀乇爻賱 賳氐 丕賱廿卮毓丕乇 丕賱胤賵賷賱 廿賱賶 丕賱噩賴丕夭</string>
+    <string name="pref_summary_notification_media_ignores_application_list">賲毓丕賱噩丞 廿卮毓丕乇丕鬲 丕賱賵爻丕卅胤 賯亘賱 賯丕卅賲丞 丕賱鬲胤亘賷賯丕鬲. 廿匕丕 賱賲 賷鬲賲 鬲丨丿賷丿 賴匕丕 丕賱鬲賮囟賷賱貙 賮賷噩亘 丕賱爻賲丕丨 亘鬲胤亘賷賯丕鬲 丕賱賵爻丕卅胤 賮賷 賯丕卅賲丞 丕賱鬲胤亘賷賯丕鬲 丨鬲賶 鬲毓賲賱 毓賳丕氐乇 丕賱鬲丨賰賲 賮賷 丕賱賵爻丕卅胤 毓賱賶 丕賱噩賴丕夭.</string>
+    <string name="pref_header_cannned_messages">丕賱乇爻丕卅賱 丕賱噩丕賴夭丞</string>
+    <string name="pref_summary_canned_replies">丕賱乇丿 賲賳 丕賱爻丕毓丞 亘丕爻鬲禺丿丕賲 丕賱乇爻丕卅賱 丕賱賲毓丿丞 賲爻亘賯丕</string>
+    <string name="pref_title_pebble_sync_morpheuz">賲夭丕賲賳丞 Morpheuz</string>
+    <string name="pref_pebble_privacy_mode_content">廿夭丕丨丞 賳氐 丕賱廿卮毓丕乇 禺丕乇噩 丕賱卮丕卮丞</string>
+    <string name="activity_db_management_clean_export_directory_text">賷賲賰賳 丕賱賵氐賵賱 廿賱賶 丕賱賲賱賮丕鬲 丕賱賲氐丿乇丞 賮賷 丿賱賷賱 丕賱鬲氐丿賷乇 / 丕賱丕爻鬲賷乇丕丿 亘賵丕爻胤丞 兀賷 鬲胤亘賷賯 毓賱賶 噩賴丕夭賰. 賯丿 鬲乇睾亘 賮賷 廿夭丕賱丞 賴匕賴 丕賱賲賱賮丕鬲 亘毓丿 丕賱賲夭丕賲賳丞 兀賵 丕賱賳爻禺 丕賱丕丨鬲賷丕胤賷. 鬲兀賰丿 賲賳 兀賳 賱丿賷賰 賳爻禺丞 丕丨鬲賷丕胤賷丞 賯亘賱 丨匕賮賴丕. 賱賳 賷鬲賲 丨匕賮 賲賱賮丕鬲 GPX 賵丕賱兀丿賱丞 丕賱賮乇毓賷丞 賵賲賱賮 賯丕毓丿丞 丕賱亘賷丕賳丕鬲 丕賱匕賷 鬲賲 鬲氐丿賷乇賴 鬲賱賯丕卅賷賸丕 (廿賳 賵噩丿). 丕賱賲爻丕乇 廿賱賶 丿賱賷賱 丕賱鬲氐丿賷乇 / 丕賱丕爻鬲賷乇丕丿 賴賵:</string>
+    <string name="pref_summary_pebble_forceprotocol">賷賮乇囟 賴匕丕 丕賱禺賷丕乇 丕爻鬲禺丿丕賲 兀丨丿孬 亘乇賵鬲賵賰賵賱 丕卮毓丕乇 亘賳丕亍賸 毓賱賶 廿氐丿丕乇 丕賱亘乇賳丕賲噩 丕賱孬丕亘鬲. 賱丕亘丿 丕賳 鬲毓乇賮 賲丕 鬲賯賵賲 亘賴!</string>
+    <string name="pref_summary_pebble_gatt_clientonly">賴匕丕 賱賭 Pebble 2 賮賯胤 賵 鬲噩乇賷亘賷 貙 噩乇亘 賴匕丕 廿匕丕 賰丕賳鬲 賱丿賷賰 賲卮丕賰賱 賮賷 丕賱丕鬲氐丕賱</string>
+    <string name="pref_summary_pebble_enable_bgjs">毓賳丿 丕賱鬲賲賰賷賳 貙 賷爻賲丨 賱賵丕噩賴丕鬲 丕賱爻丕毓丞 亘廿馗賴丕乇 丕賱胤賯爻 賵賲毓賱賵賲丕鬲 丕賱亘胤丕乇賷丞 賵賲丕 廿賱賶 匕賱賰.</string>
+    <string name="activity_db_management_import_export_explanation">鬲爻鬲禺丿賲 毓賲賱賷丕鬲 丕賱鬲氐丿賷乇 / 丕賱丕爻鬲賷乇丕丿 丕賱賲爻丕乇 丕賱鬲丕賱賷 (丕賳馗乇 兀丿賳丕賴) 廿賱賶 丿賱賷賱 毓賱賶 噩賴丕夭賰. 賴匕丕 丕賱丿賱賷賱 賲鬲丕丨 賱鬲胤亘賷賯丕鬲 Android 丕賱兀禺乇賶 賵噩賴丕夭 丕賱賰賲亘賷賵鬲乇 丕賱禺丕氐 亘賰. 賱丕丨馗 兀賳 賴匕丕 丕賱丿賱賷賱 賵噩賲賷毓 丕賱賲賱賮丕鬲 丕賱鬲賷 鬲丨鬲賵賷 毓賱賷賴丕 賷鬲賲 丨匕賮賴丕 廿匕丕 賯賲鬲 亘廿賱睾丕亍 鬲孬亘賷鬲 Gadgetbridge. 鬲卮賲賱 丕賱亘賷丕賳丕鬲:
+\n Export_preference - 廿毓丿丕丿丕鬲 毓丕賲丞
+\n Export_preference_MAC - 廿毓丿丕丿丕鬲 禺丕氐丞 亘丕賱噩賴丕夭
+\n Gadgetbridge - 賯丕毓丿丞 亘賷丕賳丕鬲 丕賱噩賴丕夭 賵丕賱賳卮丕胤
+\n Gadgetbridge_date - 鬲賲 鬲氐丿賷乇 賯丕毓丿丞 丕賱亘賷丕賳丕鬲 賮賷 鬲丕乇賷禺
+\n * .gpx - 鬲爻噩賷賱丕鬲 GPS
+\n * .log - 賲賱賮丕鬲 丕賱爻噩賱
+\n鬲賵賯毓 丕賱毓孬賵乇 毓賱賶 丕賱賲賱賮丕鬲 丕賱鬲賷 鬲賲 鬲氐丿賷乇賴丕 (兀賵 囟毓 丕賱賲賱賮丕鬲 丕賱鬲賷 鬲乇賷丿 丕爻鬲賷乇丕丿賴丕) 賴賳丕賰:</string>
+    <string name="fossil_hr_button_config_info">賱丕 賷賲賰賳 鬲賰賵賷賳 亘毓囟 丕賱兀夭乇丕乇 賱兀賳 賵馗丕卅賮賴丕 賲卮賮乇丞 亘卮賰賱 孬丕亘鬲 賮賷 丕賱亘乇賳丕賲噩 丕賱孬丕亘鬲 賱賱爻丕毓丞.
+\n
+\n鬲丨匕賷乇: 爻賷丐丿賷 丕賱囟睾胤 賱賮鬲乇丞 胤賵賷賱丞 毓賱賶 丕賱夭乇 丕賱毓賱賵賷 毓賳丿 鬲孬亘賷鬲 禺賱賮賷丞 卮丕卮丞 賲賳 鬲胤亘賷賯 Fossil 丕賱乇爻賲賷 廿賱賶 丕賱鬲亘丿賷賱 亘賷賳 廿馗賴丕乇 / 廿禺賮丕亍 丕賱兀丿賵丕鬲.</string>
+    <string name="sonyswr12_settings_alarm_interval_summary">丕賱賮丕氐賱 丕賱夭賲賳賷 賱賱廿賳匕丕乇 丕賱匕賰賷 賴賵 賮丕氐賱 夭賲賳賷 賯亘賱 丕賱鬲賳亘賷賴 丕賱賲孬亘鬲. 賮賷 賴匕丕 丕賱賮丕氐賱 丕賱夭賲賳賷 貙 賷丨丕賵賱 丕賱噩賴丕夭 丕賰鬲卮丕賮 兀禺賮 賲乇丨賱丞 賲賳 丕賱賳賵賲 賱廿賷賯丕馗 丕賱賲爻鬲禺丿賲</string>
+    <string name="pref_summary_device_intents">丕爻賲丨 賱鬲胤亘賷賯丕鬲 Bangle.js watch 亘廿乇爻丕賱 兀賴丿丕賮 Android 貙 賵丕賱爻賲丕丨 賱賱鬲胤亘賷賯丕鬲 丕賱兀禺乇賶 毓賱賶 Android (賲孬賱 Tasker) 亘廿乇爻丕賱 丕賱亘賷丕賳丕鬲 廿賱賶 Bangle.js 亘丕爻鬲禺丿丕賲 com.banglejs.uart.tx Intent. 賷丨鬲丕噩 廿賱賶 廿匕賳 賱賱毓乇囟 賮賵賯 丕賱鬲胤亘賷賯丕鬲 丕賱兀禺乇賶 賱賱毓賲賱 賮賷 丕賱禺賱賮賷丞.</string>
+    <string name="pref_summary_authkey">賯賲 亘鬲睾賷賷乇 賲賮鬲丕丨 丕賱賲氐丕丿賯丞 廿賱賶 賲賮鬲丕丨 賲卮鬲乇賰 毓賱賶 噩賲賷毓 兀噩賴夭丞 Android 丕賱鬲賷 鬲乇睾亘 賮賷 丕賱丕鬲氐丕賱 賲賳賴丕. 丕賱賲賮鬲丕丨 丕賱丕賮鬲乇丕囟賷 丕賱爻丕亘賯 賱噩賲賷毓 丕賱兀噩賴夭丞 賴賵 0123456789 @ ABCDE</string>
+    <string name="pref_check_permission_status_summary">鬲丨賯賯 賵丕胤賱亘 丕賱兀匕賵賳丕鬲 丕賱賲賮賯賵丿丞 丨鬲賶 毓賳丿賲丕 賱丕 鬲賰賵賳 賴賳丕賰 丨丕噩丞 廿賱賷賴丕 毓賱賶 丕賱賮賵乇. 賯賲 亘鬲毓胤賷賱 賴匕丕 賮賯胤 廿匕丕 賰丕賳鬲 兀噩賴夭鬲賰 賱丕 鬲丿毓賲 亘丕賱賮毓賱 兀賷賸丕 賲賳 賴匕賴 丕賱賲賷夭丕鬲. 賯丿 賷丐丿賷 毓丿賲 賲賳丨 丕賱廿匕賳 廿賱賶 丨丿賵孬 賲卮賰賱丕鬲!</string>
+    <string name="prefs_heartrate_alert_experimental_description">丕賴鬲夭丕夭 丕賱賳胤丕賯 毓賳丿賲丕 賷鬲噩丕賵夭 賲毓丿賱 賳亘囟丕鬲 丕賱賯賱亘 毓鬲亘丞 貙 丿賵賳 兀賷 賳卮丕胤 亘丿賳賷 賵丕囟丨 賮賷 丕賱丿賯丕卅賯 丕賱毓卮乇 丕賱賲丕囟賷丞. 賴匕賴 丕賱賲賷夭丞 鬲噩乇賷亘賷丞 賵賱賲 賷鬲賲 丕禺鬲亘丕乇賴丕 毓賱賶 賳胤丕賯 賵丕爻毓.</string>
+    <string name="discovery_scanning_intensity_warning">廿匕丕 賵丕噩賴鬲 鬲噩賲賷丿賸丕 兀賵 毓丿賲 丕爻鬲噩丕亘丞 貙 賮丨丕賵賱 囟亘胤 賰孬丕賮丞 丕賱賲爻丨 毓賱賶 賲爻鬲賵賶 兀賯賱. 廿匕丕 賱賲 賷鬲賲 丕賰鬲卮丕賮 噩賴丕夭賰 貙 賮丨丕賵賱 囟亘胤 賰孬丕賮丞 丕賱賲爻丨 毓賱賶 賲爻鬲賵賶 兀毓賱賶.</string>
+    <string name="permission_location">賷丨鬲丕噩 %1$s 廿賱賶 丕賱賵氐賵賱 廿賱賶 賲賵賯毓賰 賮賷 丕賱禺賱賮賷丞 賱賱爻賲丕丨 賱賴 亘丕賱亘賯丕亍 毓賱賶 丕鬲氐丕賱 亘爻丕毓鬲賰 丨鬲賶 毓賳丿賲丕 鬲賰賵賳 丕賱卮丕卮丞 賲睾賱賯丞. \n \n丕賱乇噩丕亍 丕賱賳賯乇 毓賱賶 \"%2$s\" 賱賱賲賵丕賮賯丞.</string>
+    <string name="firmware_install_warning">兀賳鬲 鬲丨丕賵賱 鬲孬亘賷鬲 亘乇賳丕賲噩 孬丕亘鬲 貙 鬲丕亘毓 毓賱賶 賲爻丐賵賱賷鬲賰 丕賱禺丕氐丞.
+\n
+\n
+\n賴匕丕 丕賱亘乇賳丕賲噩 丕賱孬丕亘鬲 賲禺氐氐 賱賲乇丕噩毓丞 HW: %s</string>
+    <string name="pref_title_relax_firmware_checks">賯賲 亘鬲賲賰賷賳 賴匕丕 廿匕丕 賰賳鬲 鬲乇賷丿 鬲丨丿賷孬 亘乇賳丕賲噩 孬丕亘鬲 睾賷乇 賲禺氐氐 賱噩賴丕夭賰 (毓賱賶 賲爻丐賵賱賷鬲賰 丕賱禺丕氐丞)</string>
+    <string name="miband_pairing_tap_hint">毓賳丿賲丕 賷賴鬲夭 Mi Band 賵賷賵賲囟 貙 丕賳賯乇 毓賱賷賴 毓丿丞 賲乇丕鬲 賲鬲鬲丕賱賷丞.</string>
+    <string name="about_additional_contributions">卮賰乇丕賸 噩夭賷賱丕賸 賱噩賲賷毓 丕賱賲爻丕賴賲賷賳 睾賷乇 丕賱賲丿乇噩賷賳 賮賷 丕賱賯丕卅賲丞 賱賱賲爻丕賴賲丞 賮賷 丕賱鬲毓賱賷賲丕鬲 丕賱亘乇賲噩賷丞 賵丕賱鬲乇噩賲丕鬲 賵丕賱丿毓賲 賵丕賱兀賮賰丕乇 賵丕賱鬲丨賮賷夭 賵鬲賯丕乇賷乇 丕賱兀禺胤丕亍 賵丕賱賲丕賱 ... 鉁�</string>
+    <string name="pref_auto_fetch_summary">賷丨丿孬 丕賱噩賱亘 毓賳丿 賮鬲丨 丕賱卮丕卮丞. 賷毓賲賱 賮賯胤 廿匕丕 鬲賲 囟亘胤 丌賱賷丞 丕賱賯賮賱!</string>
+    <string name="pref_summary_use_custom_font">賯賲 亘鬲賲賰賷賳 賴匕丕 廿匕丕 賰丕賳 噩賴丕夭賰 賷丨鬲賵賷 毓賱賶 亘乇賳丕賲噩 孬丕亘鬲 賲禺氐氐 賱賱禺胤 賱丿毓賲 丕賱乇賲賵夭 丕賱鬲毓亘賷乇賷丞</string>
+    <string name="watchface_install_info">兀賳鬲 毓賱賶 賵卮賰 鬲孬亘賷鬲 禺賱賮賷丞 卮丕卮丞 丕賱爻丕毓丞 丕賱鬲丕賱賷丞:
+\n
+\n%1$s
+\n廿氐丿丕乇 %2$s 亘賵丕爻胤丞 %3$s
+\n</string>
+    <string name="zetime_activity_tracking_summary">鬲睾賷賷乇 鬲鬲亘毓 丕賱賳卮丕胤貙 爻賷丨爻亘 禺胤賵丕鬲賰 賵賲丕 廿賱賶 匕賱賰.</string>
+    <string name="pref_summary_huami_force_new_protocol">賯賲 亘鬲賲賰賷賳賴 廿匕丕 賱賲 賷毓丿 噩賴丕夭賰 賷鬲氐賱 亘毓丿 鬲乇賯賷丞 丕賱亘乇賳丕賲噩 丕賱孬丕亘鬲</string>
+    <string name="updatefirmwareoperation_updateproblem_do_not_reboot">賲卮賰賱丞 賮賷 賳賯賱 丕賱亘乇丕賲噩 丕賱孬丕亘鬲丞. 賱丕 鬲賯賲 亘廿毓丕丿丞 鬲卮睾賷賱 Mi Band 丕賱禺丕氐 亘賰!</string>
+    <string name="charts_min_max_heartrate_popup">兀丿賳賶 賲毓丿賱 賱賳亘囟丕鬲 丕賱賯賱亘: %1$d
+\n兀毓賱賶 賲毓丿賱 賱賳亘囟丕鬲 丕賱賯賱亘: %2$d
+\n卮丿丞 丕賱丨乇賰丞: %3$s</string>
+    <string name="lefun_prefs_antilost_summary">爻賷賴鬲夭 丕賱爻賵丕乇 廿匕丕 丕賳賯胤毓 丕鬲氐丕賱 賴丕鬲賮賰 亘丕賱爻賵丕乇</string>
+    <string name="qhybrid_calibration_align_hint">丕爻鬲禺丿賲 丕賱兀夭乇丕乇 兀丿賳丕賴 賱賲丨丕匕丕丞 毓賯丕乇亘 丕賱爻丕毓丞 廿賱賶 丕賱爻丕毓丞 12:00.</string>
+    <string name="qhybrid_watchface_configuration_old_firmware">卮丕卮丞 鬲賰賵賷賳 賵丕噩賴丞 丕賱爻丕毓丞 賱賱爻丕毓丕鬲 丕賱鬲賷 鬲丨鬲賵賷 毓賱賶 廿氐丿丕乇 丕賱亘乇賳丕賲噩 丕賱孬丕亘鬲 DN1.0.2.19r 賵丕賱廿氐丿丕乇丕鬲 丕賱兀賯丿賲</string>
+    <string name="prefs_in_ear_detection_summary">賯賲 亘鬲卮睾賷賱 丕賱賲賰丕賱賲丕鬲 賲賳 禺賱丕賱 爻賲丕毓丕鬲 丕賱兀匕賳 丕賱禺丕氐丞 亘賰 毓賳丿賲丕 鬲賰賵賳 賮賷 兀匕賳賷賰</string>
+    <string name="pref_summary_connected_advertisement">賷噩毓賱 丕賱噩賴丕夭 賯丕亘賱丕賸 賱賱丕賰鬲卮丕賮 毓亘乇 丕賱亘賱賵鬲賵孬 丨鬲賶 毓賳丿 丕賱丕鬲氐丕賱</string>
+    <string name="sony_speak_to_chat_summary">賯賲 亘廿賷賯丕賮 鬲卮睾賷賱 廿賱睾丕亍 丕賱囟賵囟丕亍 鬲賱賯丕卅賷丕 毓賳丿 亘丿亍 丕賱鬲丨丿孬.</string>
+    <string name="this_is_a_test_notification_from_gadgetbridge">賴匕丕 廿卮毓丕乇 丕禺鬲亘丕乇 賲賳 Gadgetbridge</string>
+    <string name="installer_activity_wait_while_determining_status">賷乇噩賶 丕賱丕賳鬲馗丕乇 兀孬賳丕亍 鬲丨丿賷丿 丨丕賱丞 丕賱鬲孬亘賷鬲 鈥�</string>
+    <string name="prefs_events_forwarding_summary">丕爻鬲禺丿丕賲 兀丨丿丕孬 丕賱噩賴丕夭 賱鬲卮睾賷賱 丕賱廿噩乇丕亍丕鬲 賵毓賲賱賷丕鬲 丕賱亘孬 毓賱賶 丕賳丿乇賵賷丿</string>
+    <string name="pref_summary_canned_messages_dismisscall">鬲噩丕賴賱 丕賱賲賰丕賱賲丕鬲 賲賳 丕賱爻丕毓丞 亘乇爻丕賱丞 SMS</string>
+    <string name="dbmanagementactivvity_cannot_access_export_path">賱丕 賷賲賰賳 丕賱賵氐賵賱 廿賱賶 賲爻丕乇 丕賱鬲氐丿賷乇. 賷乇噩賶 丕賱丕鬲氐丕賱 亘丕賱賲胤賵乇賷賳.</string>
+    <string name="pref_invalid_frequency_message">丕賱乇噩丕亍 廿丿禺丕賱 鬲乇丿丿 亘賷賳 87.5 賵 108.0</string>
+    <string name="prefs_seamless_connection_switch_summary">賷賯賵賲 亘鬲亘丿賷賱 丕賱爻賲丕毓丕鬲 亘賷賳 丕賱兀噩賴夭丞 丕賱賲賯鬲乇賳丞 鬲賱賯丕卅賷賸丕</string>
+    <string name="step_streak_average_steps_hint">賲鬲賵爻胤 丕賱禺胤賵丕鬲 賮賷 丕賱賷賵賲 賲賳 丕賱禺胤</string>
+    <string name="zetime_prefs_inactivity_sa">丕賱爻亘鬲</string>
+    <string name="vibration_profile_waterdrop">賯胤乇丞 賲丕亍</string>
+    <string name="pref_title_third_party_app_device_settings">丕賱爻賲丕丨 賱鬲胤亘賷賯丕鬲 丕賱胤乇賮 丕賱孬丕賱孬 亘鬲睾賷賷乇 丕賱廿毓丿丕丿丕鬲</string>
+    <string name="zetime_prefs_inactivity_we">丕賱兀乇亘毓丕亍</string>
+    <string name="zetime_prefs_inactivity_th">丕賱禺賲賷爻</string>
+    <string name="sleepchart_your_sleep">丕賱賳賵賲</string>
+    <string name="activity_sleepchart_activity_and_sleep">丕賱賳卮丕胤</string>
+    <string name="activity_list_summary_activities">丕賱兀賳卮胤丞</string>
+    <string name="getting_heart_rate">丕賱賯賷丕爻</string>
+    <string name="pref_workout_send_gps_summary">兀乇爻賱 賲賵賯毓 GPS 丕賱丨丕賱賷 廿賱賶 丕賱賳胤丕賯 兀孬賳丕亍 丕賱鬲賲乇賷賳</string>
+    <string name="pref_workout_start_on_phone_summary">亘丿亍 / 廿賷賯丕賮 鬲鬲亘毓 鬲胤亘賷賯 丕賱賱賷丕賯丞 丕賱亘丿賳賷丞 毓賱賶 丕賱賴丕鬲賮 毓賳丿 亘丿亍 鬲賲乇賷賳 GPS 毓賱賶 丕賱爻賵丕乇</string>
+    <string name="pref_workout_start_on_phone_title">鬲鬲亘毓 鬲胤亘賷賯 丕賱賱賷丕賯丞 丕賱亘丿賳賷丞</string>
+    <string name="heartrate_bpm_100">100 賳亘囟丞 賮賷 丕賱丿賯賷賯丞</string>
+    <string name="heartrate_bpm_135">135 賳亘囟丞 賮賷 丕賱丿賯賷賯丞</string>
+    <string name="heartrate_bpm_140">140 賳亘囟丞 賮賷 丕賱丿賯賷賯丞</string>
+    <string name="pref_morning_updates_summary">毓乇囟 丕賱鬲丨丿賷孬丕鬲 賰賱 氐亘丕丨</string>
+    <string name="pref_morning_updates_categories_title">賮卅丕鬲 鬲丨丿賷孬丕鬲 丕賱氐亘丕丨</string>
+    <string name="pref_morning_updates_categories_summary">賯丕卅賲丞 丕賱賮卅丕鬲 丕賱鬲賷 爻賷鬲賲 毓乇囟賴丕 賰賱 氐亘丕丨</string>
+    <string name="pref_workout_detection_summary">丕賰鬲卮丕賮 丕賱鬲賲乇賷賳 鬲賱賯丕卅賷丕</string>
+    <string name="pref_workout_keep_screen_on_title">廿亘賯丕亍 丕賱卮丕卮丞 賯賷丿 丕賱鬲卮睾賷賱 兀孬賳丕亍 丕賱鬲賲乇賷賳</string>
+    <string name="kind_resources">丕賱賲賵丕乇丿</string>
+    <string name="zetime_analog_mode">丕賱賵囟毓 丕賱鬲賳丕馗乇賷</string>
+    <string name="pref_world_clocks_summary">鬲賰賵賷賳 丕賱爻丕毓丕鬲 賱賲賳丕胤賯 夭賲賳賷丞 兀禺乇賶</string>
+    <string name="prefs_always_on_display_summary">丨丕賮馗 毓賱賶 卮丕卮丞 丕賱爻賵丕乇 賯賷丿 丕賱鬲卮睾賷賱 丿丕卅賲丕</string>
+    <string name="device_requires_key">丕賱賲賮鬲丕丨 賲胤賱賵亘貙 丕囟睾胤 賱賮鬲乇丞 胤賵賷賱丞 賱賱丿禺賵賱</string>
+    <string name="pref_summary_relax_firmware_checks">丕賱丕爻鬲乇禺丕亍 賮賷 毓賲賱賷丕鬲 賮丨氐 丕賱亘乇丕賲噩 丕賱孬丕亘鬲丞</string>
+    <string name="pairing_unable_to_pair_with">鬲毓匕乇 丕賱廿賯乇丕賳 亘賭 %1$s (%2$s)</string>
+    <string name="about_contributors">丕賱賲爻丕賴賲賵賳</string>
+    <string name="preferences_miband_settings">廿毓丿丕丿丕鬲 Mi Band / Amazfit</string>
+    <string name="sony_pause_when_taken_off">廿賷賯丕賮 賲丐賯鬲 毓賳丿 丕夭丕賱丞 爻賲丕毓丕鬲 丕賱乇兀爻</string>
+    <string name="activity_db_management_empty_DB">賯丕毓丿丞 亘賷丕賳丕鬲 賮丕乇睾丞</string>
+    <string name="activity_data_management_directory_content_title">鬲氐丿賷乇/丕爻鬲賷乇丕丿 賲丨鬲賵賶 丕賱賲爻丕乇</string>
+    <string name="gpx_receiver_overwrite_some_files">亘毓囟 丕賱賲賱賮丕鬲 賲賵噩賵丿丞 亘丕賱賮毓賱. 丕賱賰鬲丕亘丞 賮賵賯賴丕責</string>
+    <string name="error_retrieving_devices_database">禺胤兀 賮賷 丕爻鬲乇丿丕丿 丕賱兀噩賴夭丞 賲賳 賯丕毓丿丞 丕賱亘賷丕賳丕鬲</string>
+    <string name="watchface_setting_wrist_flick_move_relative">鬲丨乇賷賰 丕賱賷丿賷賳 亘丕賱賳爻亘丞 賱賱賵賯鬲</string>
+    <string name="watchface_setting_wrist_flick_hour">毓賯乇亘 丕賱爻丕毓丕鬲 (-360 廿賱賶 360)</string>
+    <string name="lefun_prefs_hydration_reminder_interval_title">丕賱賮丕氐賱 丕賱夭賲賳賷 賱賱鬲匕賰賷乇 亘丕賱鬲乇胤賷亘 (亘丕賱丿賯丕卅賯)</string>
+    <string name="pref_workout_detection_categories_summary">賮卅丕鬲 丕賱鬲丿乇賷亘丕鬲 賱丕賰鬲卮丕賮賴丕 鬲賱賯丕卅賷賸丕</string>
+    <string name="reminder_no_free_slots_description">賱丕 賷丨鬲賵賷 丕賱噩賴丕夭 毓賱賶 氐賮丨丕鬲 賮丕乇睾丞 賱賱鬲匕賰賷乇丕鬲 (廿噩賲丕賱賷 丕賱氐賮丨丕鬲: %1$s)</string>
+    <string name="pref_switch_controls_anc_ambient_off">廿賱睾丕亍 丕賱囟賵囟丕亍 鈫愨啋 廿賷賯丕賮 鈫愨啋 丕賱賲丨賷胤</string>
+    <string name="autoconnect_from_device_title">丕賱丕鬲氐丕賱 毓賳丿 丕賱丕鬲氐丕賱 賲賳 丕賱噩賴丕夭</string>
+    <string name="updatefirmwareoperation_metadata_updateproblem">賲卮賰賱丞 賮賷 賳賯賱 丕賱亘賷丕賳丕鬲 丕賱賵氐賮賷丞 賱賱亘乇賳丕賲噩 丕賱孬丕亘鬲</string>
+    <string name="pref_title_keep_data_on_device">丕賱丕丨鬲賮丕馗 亘亘賷丕賳丕鬲 丕賱賳卮丕胤 毓賱賶 丕賱噩賴丕夭</string>
+    <string name="prefs_sleep_time_summary">賷丨丿丿 丕賱兀賵賯丕鬲 丕賱鬲賷 賷鬲賲 賮賷賴丕 鬲爻噩賷賱 丕賱賳賵賲</string>
+    <string name="dbmanagementactivity_db_deletion_failed">賮卮賱 丨匕賮 賯丕毓丿丞 丕賱亘賷丕賳丕鬲.</string>
+    <string name="qhybrid_second_timezone_offset_relative_to_utc">廿夭丕丨丞 丕賱賲賳胤賯丞 丕賱夭賲賳賷丞 丕賱孬丕賳賷丞 亘丕賱賳爻亘丞 廿賱賶 丕賱鬲賵賯賷鬲 丕賱毓丕賱賲賷 丕賱賲賳爻賯 (UTC)</string>
+    <string name="pref_summary_contextual_arabic">鬲賲賰賷賳 賴匕丕 賱丿毓賲 丕賱爻賷丕賯 賮賷 丕賱賱睾丞 丕賱毓乇亘賷丞</string>
+    <string name="filter_mode_whitelist">兀馗賴乇 毓賳丿賲丕 賷鬲賲 丕丨鬲賵丕亍 丕賱賰賱賲丕鬲</string>
+    <string name="error_background_service_reason_truncated">賮卮賱 亘丿亍 禺丿賲丞 丕賱禺賱賮賷丞 亘爻亘亘 鈥�</string>
+    <string name="fossil_hr_unavailable_unauthed">睾賷乇 賲鬲丕丨 賮賷 賵囟毓 毓丿賲 丕賱賲氐丕丿賯丞</string>
+    <string name="prefs_noise_control_with_one_earbud">丕賱鬲丨賰賲 賮賷 丕賱囟賵囟丕亍 賲毓 爻賲丕毓丞 兀匕賳 賵丕丨丿丞</string>
+    <string name="about_hash">丕賱丕賱鬲夭丕賲 %s</string>
+    <string name="about_version">丕賱廿氐丿丕乇 %s</string>
+    <string name="pref_crown_vibration">丕賴鬲夭丕夭 丕賱鬲丕噩</string>
+    <string name="activity_prefs_chart_max_idle_phase_length">胤賵賱 丕賱賲丿丞 丕賱賲禺氐氐丞 賱兀賳卮胤丞 賲賳賮氐賱丞 (亘丕賱丿賯丕卅賯)</string>
+    <string name="activity_prefs_distance_meters">丕賱賴丿賮 丕賱賷賵賲賷: 丕賱賲爻丕賮丞 亘丕賱兀賲鬲丕乇</string>
+    <string name="pref_title_allow_high_mtu">丕賱爻賲丕丨 亘丕乇鬲賮丕毓 MTU</string>
+    <string name="pref_title_device_intents">丕賱爻賲丕丨 亘丕賱賳賵丕賷丕</string>
+    <string name="prefs_notifications_and_calls_shake_reject">賴夭 丕賱賲毓氐賲 鬲噩丕賴賱 / 乇賮囟 丕賱賲賰丕賱賲丞</string>
+    <string name="pref_title_device_spec_settings_force_time">賮乇囟 賲夭丕賲賳丞 丕賱賵賯鬲</string>
+    <string name="pref_title_device_spec_settings_show_raw_graph">廿馗賴丕乇 亘賷丕賳丕鬲 RAW 毓賱賶 丕賱乇爻賲 丕賱亘賷丕賳賷 賱賱賳卮丕胤</string>
+    <string name="zetime_date_format_3">卮賴乇 / 賷賵賲 / 爻賳丞</string>
+    <string name="application_name_generic">Gadgetbridge</string>
+    <string name="title_activity_controlcenter_generic">Gadgetbridge</string>
+    <string name="application_name_banglejs_main">Bangle.js Gadgetbridge</string>
+    <string name="title_activity_controlcenter_banglejs_main">Bangle.js Gadgetbridge</string>
+    <string name="application_name_main_nightly">Gadgetbridge (丕賱賷賱賷)</string>
+    <string name="about_activity_title_main_nightly">丨賵賱 Gadgetbridge Nightly</string>
+    <string name="title_activity_controlcenter_main_nightly">Gadgetbridge 丕賱賷賱賷</string>
+    <string name="about_activity_title_main_nopebble">丨賵賱 Gadgetbridge Nightly No Pebble</string>
+    <string name="zetime_calories_type_all">丕賱爻毓乇丕鬲 丕賱丨乇丕乇賷丞 丕賱賲丨乇賵賯丞 丕賱賳卮胤丞 賵睾賷乇 丕賱賳卮胤丞</string>
+    <string name="zetime_calories_type">賳賵毓 丕賱爻毓乇丕鬲 丕賱丨乇丕乇賷丞</string>
+    <string name="zetime_calories_type_active">丨乇賯 丕賱爻毓乇丕鬲 丕賱丨乇丕乇賷丞 丕賱賳卮胤丞 賮賯胤</string>
+    <string name="zetime_date_format">鬲賳爻賷賯 丕賱鬲丕乇賷禺</string>
+    <string name="zetime_signaling_vibrate">丕賱丕賴鬲夭丕夭 丕賱賲爻鬲賲乇</string>
+    <string name="tap_connected_device_for_app_mananger">丕賳賯乇 賮賵賯 丕賱噩賴丕夭 丕賱賲鬲氐賱 賱賲丿賷乇 丕賱鬲胤亘賷賯丕鬲</string>
+    <string name="about_links">丕賱乇賵丕亘胤</string>
+    <string name="prefs_stress_monitoring_description">賲乇丕賯亘丞 賲爻鬲賵賶 丕賱鬲賵鬲乇 兀孬賳丕亍 丕賱乇丕丨丞</string>
+    <string name="you_slept">%1$s - %2$s</string>
+    <string name="miband_prefs_reserve_reminder_calendar">鬲匕賰賷乇丕鬲 丕賱丨噩夭 賱賱兀丨丿丕孬 丕賱賯丕丿賲丞</string>
+    <string name="pref_title_force_white_color_scheme">賮乇囟 丕賱兀爻賵丿 毓賱賶 賳馗丕賲 丕賱賱賵賳 丕賱兀亘賷囟</string>
+    <string name="dbmanagementactivity_delete_old_activity_db">丨匕賮 賯丕毓丿丞 亘賷丕賳丕鬲 丕賱賳卮丕胤 丕賱賯丿賷賲丞責</string>
+    <string name="dbmanagementactivity_old_activity_db_deletion_failed">賮卮賱 丨匕賮 賯丕毓丿丞 亘賷丕賳丕鬲 丕賱賳卮丕胤 丕賱賯丿賷賲.</string>
+    <string name="dbmanagementactivity_old_activity_db_successfully_deleted">鬲賲 丨匕賮 亘賷丕賳丕鬲 丕賱賳卮丕胤 丕賱賯丿賷賲丞.</string>
+    <string name="activity_db_management_clean_export_directory_label">丨匕賮 丕賱賲賱賮丕鬲 賮賷 丿賱賷賱 丕賱鬲氐丿賷乇 / 丕賱丕爻鬲賷乇丕丿</string>
+    <string name="discovery_attempting_to_pair">賲丨丕賵賱丞 丕賱丕賯鬲乇丕賳 亘賭 %1$s</string>
+    <string name="discovery_bonding_failed_immediately">賮卮賱 丕賱丕乇鬲亘丕胤 亘賭 %1$s 毓賱賶 丕賱賮賵乇.</string>
+    <string name="discovery_trying_to_connect_to">賲丨丕賵賱丞 丕賱丕鬲氐丕賱 亘賭: %1$s</string>
+    <string name="discovery_yes_pair">丕賯鬲乇丕賳</string>
+    <string name="about_title">丨賵賱 丕賱鬲胤亘賷賯</string>
+    <string name="about_additional_device_support">丿毓賲 廿囟丕賮賷 賱賱噩賴丕夭</string>
+    <string name="error_background_service">賮卮賱 亘丿亍 鬲卮睾賷賱 禺丿賲丞 丕賱禺賱賮賷丞</string>
+    <string name="error_exporting_device_preferences">禺胤兀 賮賷 鬲氐丿賷乇 丕賱鬲賮囟賷賱丕鬲 丕賱禺丕氐丞 亘丕賱噩賴丕夭</string>
+    <string name="watchface_setting_power_saving_display">鬲毓胤賷賱 毓乇囟 丕賱鬲丨丿賷孬丕鬲 兀孬賳丕亍 丕夭丕賱鬲賴丕 賲賳 丕賱賲毓氐賲</string>
+    <string name="prefs_ambient_sound_during_call_summary">爻賲丕毓 氐賵鬲賰 亘賵囟賵丨 兀孬賳丕亍 丕賱賲賰丕賱賲丕鬲</string>
+    <string name="prefs_pressure_relief">鬲禺賮賷賮 丕賱囟睾胤 賲毓 丕賱氐賵鬲 丕賱賲丨賷胤</string>
+    <string name="watchface_setting_light_up_on_notification">丕賱廿囟丕卅丞 毓賳丿 廿卮毓丕乇 噩丿賷丿</string>
+    <string name="steps_streaks_total_steps_hint_totals">廿噩賲丕賱賷 毓丿丿 丕賱禺胤賵丕鬲 丕賱賰丕賲賱丞 丕賱賲爻噩賱丞</string>
+    <string name="intent_api_broadcast_export_summary">亘孬 丕賱賳賷丞 毓賳丿 丕賱丕賳鬲賴丕亍 賲賳 鬲氐丿賷乇 賯丕毓丿丞 丕賱亘賷丕賳丕鬲</string>
+    <string name="wifi_hotspot_summary">丕賱鬲丨賰賲 賮賷 賳賯胤丞 丕鬲氐丕賱 Wi-Fi 毓賱賶 丕賱爻丕毓丞</string>
+    <string name="contact_name">丕賱丕爻賲</string>
+    <string name="debug_companion_show_associated">廿馗賴丕乇 丕賱兀噩賴夭丞 丕賱賲氐丕丨亘丞 丕賱賲賯鬲乇賳丞</string>
+    <string name="debug_companion_pair_current">廿賯乇丕賳 丕賱噩賴丕夭 丕賱丨丕賱賷 賰乇賮賷賯</string>
+    <string name="steps_streaks_total_steps_average_hint">廿噩賲丕賱賷 賲鬲賵爻胤 丕賱禺胤賵丕鬲 %d 賮賷 丕賱賷賵賲</string>
+    <string name="appwidget_not_connected">睾賷乇 賲鬲氐賱 貙 賱賲 賷鬲賲 囟亘胤 丕賱賲賳亘賴.</string>
+    <string name="watchface_setting_power_saving_hands">鬲毓胤賷賱 丨乇賰丞 丕賱賷丿賷賳 兀孬賳丕亍 丕夭丕賱鬲賴丕 賲賳 丕賱賲毓氐賲</string>
+    <string name="intent_api_allow_trigger_export_summary">丕賱爻賲丕丨 亘鬲卮睾賷賱 鬲氐丿賷乇 賯丕毓丿丞 丕賱亘賷丕賳丕鬲 毓亘乇 Intent API</string>
+    <string name="pref_title_dont_ack_transfer">賱丕 鬲賯賲 亘賳賯賱 亘賷丕賳丕鬲 賳卮丕胤 ACK</string>
+    <string name="zetime_signaling_beep_twice">氐賮丕乇丞 賲乇鬲賷賳</string>
+    <string name="sony_connect_two_devices">丕賱丕鬲氐丕賱 亘噩賴丕夭賷賳 賮賷 賵賯鬲 賵丕丨丿</string>
+    <string name="tap_a_device_to_connect">丕賳賯乇 賮賵賯 噩賴丕夭 賱賱丕鬲氐丕賱</string>
+    <string name="pairing_in_progress">丕賱乇亘胤 賯賷丿 丕賱鬲賯丿賲: %1$s (%2$s)</string>
+    <string name="chart_get_active_and_synchronize">賯賲 亘亘毓囟 丕賱賳卮丕胤 賵賯賲 亘賲夭丕賲賳丞 丕賱噩賴丕夭.</string>
+    <string name="filter_submode_at_least_one">賵丕丨丿丞 毓賱賶 丕賱兀賯賱 賲賳 丕賱賰賱賲丕鬲</string>
+    <string name="ftp_server_summary">丕賱鬲丨賰賲 賮賷 禺丕丿賲 FTP 毓賱賶 丕賱爻丕毓丞</string>
+    <string name="ftp_server_start_summary">亘丿亍 鬲卮睾賷賱 禺丕丿賲 FTP 毓賱賶 丕賱爻丕毓丞</string>
+    <string name="ftp_server_stop_summary">兀賵賯賮 禺丕丿賲 FTP 毓賱賶 丕賱爻丕毓丞</string>
+    <string name="discovery_enable_bluetooth">賯賲 亘鬲賲賰賷賳 丕賱亘賱賵鬲賵孬 賱丕賰鬲卮丕賮 丕賱兀噩賴夭丞.</string>
+    <string name="pref_workout_detection_ask_first_summary">胤賱亘 丕賱鬲兀賰賷丿 兀孬賳丕亍 丕賱賲卮丕賴丿丞 毓賳丿 丕賰鬲卮丕賮 賴匕丕 丕賱鬲賲乇賷賳</string>
+    <string name="pref_workout_detection_enabled_summary">鬲賲賰賷賳 丕賱丕賰鬲卮丕賮 丕賱鬲賱賯丕卅賷 賱賴匕丕 丕賱鬲賲乇賷賳</string>
+    <string name="cannot_connect_bt_address_invalid_">賱丕 賷賲賰賳 丕賱丕鬲氐丕賱. 毓賳賵丕賳 亘賱賵鬲賵孬 睾賷乇 氐丕賱丨責</string>
+    <string name="pairing_creating_bond_with">廿賳卮丕亍 丕乇鬲亘丕胤 賲毓 %1$s (%2$s)</string>
+    <string name="activity_prefs_about_you">賲毓賱賵賲丕鬲 毓賳賰</string>
+    <string name="activity_DB_clean_export_directory_warning_title">丨匕賮 丕賱賲賱賮丕鬲 賮賷 丿賱賷賱 丕賱鬲氐丿賷乇 / 丕賱丕爻鬲賷乇丕丿責</string>
+    <string name="pref_title_use_custom_font">丕爻鬲禺丿丕賲 禺胤 賲禺氐氐</string>
+    <string name="preferences_hplus_settings">廿毓丿丕丿丕鬲 HPlus/Makibes</string>
+    <string name="pref_header_notifications_and_calls_callhandling">丕賱鬲毓丕賲賱 賲毓 丕賱賲賰丕賱賲丕鬲</string>
+    <string name="pref_sensors_bp_calibration_high">囟睾胤 丕賱丿賲 丕賱丕賳賯亘丕囟賷 (賲乇鬲賮毓)</string>
+    <string name="power_mode_watch">丕賱爻丕毓丞 賮賯胤</string>
+    <string name="preferences_makibes_hr3_settings">廿毓丿丕丿丕鬲 Makibes HR3</string>
+    <string name="zetime_title_heart_rate_alarm">廿賳匕丕乇 賲毓丿賱 賳亘囟丕鬲 丕賱賯賱亘</string>
+    <string name="zetime_signaling_beep">氐賮賷乇 賲爻鬲賲乇</string>
+    <string name="pref_title_authkey">賲賮鬲丕丨 丕賱賲氐丕丿賯丞</string>
+    <string name="prefs_hr_alarm_activity">廿賳匕丕乇 賲毓丿賱 賳亘囟丕鬲 丕賱賯賱亘 兀孬賳丕亍 丕賱賳卮丕胤 丕賱乇賷丕囟賷</string>
+    <string name="pref_workout_detection_alert_summary">廿卮毓丕乇 毓賳丿 丕賰鬲卮丕賮 鬲賲乇賷賳</string>
+    <string name="n_a">睾賷乇 賲鬲賵賮乇</string>
+    <string name="tap_connected_device_for_activity">丕賳賯乇 賮賵賯 丕賱噩賴丕夭 丕賱賲鬲氐賱 賱賱賳卮丕胤</string>
+    <string name="action_discover">鬲賵氐賷賱 噩賴丕夭 噩丿賷丿</string>
+    <string name="pairing_already_bonded">賲乇鬲亘胤 亘丕賱賮毓賱 亘賭 %1$s (%2$s) 貙 噩丕乇賺 丕賱丕鬲氐丕賱 鈥�</string>
+    <string name="pref_title_charts_average">廿馗賴丕乇 丕賱賲鬲賵爻胤丕鬲 賮賷 丕賱乇爻賵賲 丕賱亘賷丕賳賷丞</string>
+    <string name="activity_prefs_sleep_duration">賲丿丞 丕賱賳賵賲 丕賱賲賮囟賱丞 亘丕賱爻丕毓丕鬲</string>
+    <string name="DEVINFO_HR_VER">"HR: "</string>
+    <string name="pref_title_pebble_health_store_raw">鬲禺夭賷賳 丕賱爻噩賱 丕賱禺丕賲 賮賷 賯丕毓丿丞 丕賱亘賷丕賳丕鬲</string>
+    <string name="action_db_management">廿丿丕乇丞 丕賱亘賷丕賳丕鬲</string>
+    <string name="activity_db_management_merge_old_title">丨匕賮 賯丕毓丿丞 丕賱亘賷丕賳丕鬲 丕賱賯丿賷賲丞</string>
+    <string name="notification_gps_text">廿乇爻丕賱 賲賵賯毓 GPS 廿賱賶 兀噩賴夭丞 %1$d</string>
+    <string name="toast_notification_filter_words_empty_hint">丕賱乇噩丕亍 廿丿禺丕賱 賰賱賲丞 賵丕丨丿丞 毓賱賶 丕賱兀賯賱</string>
+    <string name="filter_mode_blacklist">丨馗乇 毓賳丿 丕丨鬲賵丕亍 丕賱賰賱賲丕鬲</string>
+    <string name="qhybrid_use_activity_hand_as_notification_counter">丕爻鬲禺丿丕賲 賷丿 丕賱賳卮丕胤 賰毓丿丕丿 廿毓賱丕賲</string>
+    <string name="pref_summary_developer_settings">丕賱廿毓丿丕丿丕鬲 賵丕賱賵馗丕卅賮 丕賱賲爻鬲禺丿賲丞 賲賳 賯亘賱 丕賱賲胤賵乇賷賳</string>
+    <string name="qhybrid_pref_summary_actions">廿噩乇丕亍丕鬲 賱鬲胤亘賷賯 丕賱鬲賳賯賱</string>
+    <string name="error_setting_parent_folder">禺胤兀 賮賷 廿毓丿丕丿 丕賱賲噩賱丿 丕賱兀氐賱: %s</string>
+    <string name="application_name_banglejs_nightly">Bangle.js Gadgetbridge (丕賱賷賱賷)</string>
+    <string name="title_activity_controlcenter_banglejs_nightly">Bangle.js Gadgetbridge (丕賱賷賱賷)</string>
+    <string name="about_activity_title_banglejs_nightly">丨賵賱 Bangle.js Gadgetbridge (丕賱賷賱賷)</string>
+    <string name="gadgetbridge_running_banglejs_nightly">丕賱賷賱賷 Bangle.js 賷毓賲賱</string>
+    <string name="pref_notifications_and_calls_enable_misscall">丕賱廿卮毓丕乇 毓賳 丕賱賲賰丕賱賲丕鬲 丕賱賮丕卅鬲丞</string>
+    <string name="pref_summary_notifications_and_calls_title_reject">廿賷賯丕賮 - 鬲噩丕賴賱 貙 鬲卮睾賷賱 - 乇賮囟</string>
+    <string name="zetime_analog_mode_handsandsteps">丕賱賷丿賷賳 賵丕賱禺胤賵丕鬲</string>
+    <string name="zetime_activity_tracking">鬲鬲亘毓 丕賱賳卮丕胤</string>
+    <string name="zetime_analog_mode_hands">丕賱兀賷丿賷 賮賯胤</string>
+    <string name="zetime_date_format_2">丕賱賷賵賲 / 丕賱卮賴乇 / 丕賱爻賳丞</string>
+    <string name="zetime_handmove_display">丨乇賰丞 丕賱賷丿</string>
+    <string name="zetime_signaling_vibrate_beep">丕賱丕賴鬲夭丕夭 丕賱賲爻鬲賲乇 賵丕賱氐賮賷乇</string>
+    <string name="zetime_signaling_vibrate_once">丕賴鬲夭丕夭 賲乇丞 賵丕丨丿丞</string>
+    <string name="zetime_date_format_1">爻賳丞/卮賴乇/賷賵賲</string>
+    <string name="title_activity_device_specific_settings">丕賱廿毓丿丕丿丕鬲 丕賱禺丕氐丞 亘丕賱噩賴丕夭</string>
+    <string name="zetime_signaling_vibrate_twice">丕賴鬲夭丕夭 賲乇鬲賷賳</string>
+    <string name="prefs_hr_alarm_high">丕賱丨丿 丕賱兀賯氐賶</string>
+    <string name="pref_alert_tone">賳睾賲丞 丕賱鬲賳亘賷賴</string>
+    <string name="pref_cover_to_mute">鬲睾胤賷丞 賱賰鬲賲 丕賱氐賵鬲</string>
+    <string name="pref_text_to_speech">丕賱賳氐 廿賱賶 丕賱賰賱丕賲</string>
+    <string name="offline_voice_response_during_screen_lighting">丕賱丕爻鬲噩丕亘丞 兀孬賳丕亍 廿囟丕亍丞 丕賱卮丕卮丞</string>
+    <string name="offline_voice_respond_turn_wrist">丕賱丕爻鬲噩丕亘丞 毓賳丿 鬲丿賵賷乇 丕賱賲毓氐賲</string>
+    <string name="offline_voice_respond_screen_on">丕賱丕爻鬲噩丕亘丞 毓賳丿 鬲卮睾賷賱 丕賱卮丕卮丞</string>
+    <string name="message_cannot_pair_no_mac">賱賲 賷鬲賲 鬲賲乇賷乇 兀賷 毓賳賵丕賳 MAC 貙 賱丕 賷賲賰賳 丕賱廿賯乇丕賳.</string>
+    <string name="activity_prefs_goal_standing_time_minutes">丕賱賴丿賮 丕賱賷賵賲賷: 賵賯鬲 丕賱賵賯賵賮 亘丕賱丿賯丕卅賯</string>
+    <string name="activity_prefs_activetime_minutes">丕賱賴丿賮 丕賱賷賵賲賷: 丕賱賵賯鬲 丕賱賳卮胤 亘丕賱丿賯丕卅賯</string>
+    <string name="dbmanagementactivity_delete_activity_data_title">丨匕賮 亘賷丕賳丕鬲 丕賱賳卮丕胤責</string>
+    <string name="dbmanagementactivity_database_successfully_deleted">鬲賲 丨匕賮 丕賱亘賷丕賳丕鬲.</string>
+    <string name="activity_db_management_autoexport_scheduled_no">賱賲 賷鬲賲 噩丿賵賱丞 丕賱鬲氐丿賷乇 丕賱鬲賱賯丕卅賷.</string>
+    <string name="dbmanagementactivity_import_data_title">丕爻鬲賷乇丕丿 丕賱亘賷丕賳丕鬲責</string>
+    <string name="dbmanagementactivity_export_data_title">鬲氐丿賷乇 丕賱亘賷丕賳丕鬲責</string>
+    <string name="zetime_signaling_beep_once">氐賮丕乇丞 賲乇丞 賵丕丨丿丞</string>
+    <string name="pref_gps_header">鬲丨丿賷丿 丕賱賲賵丕賯毓</string>
+    <string name="pref_agps_header">AGPS</string>
+    <string name="pref_gps_mode_preset">賵囟毓 GPS</string>
+    <string name="pref_gps_band">賳胤丕賯 GPS</string>
+    <string name="pref_gps_combination">賲噩賲賵毓丞 GPS</string>
+    <string name="pref_gps_satellite_search">丕賱亘丨孬 毓亘乇 丕賱兀賯賲丕乇 丕賱氐賳丕毓賷丞</string>
+    <string name="sonyswr12_settings_alarm_interval">丕賱賮丕氐賱 丕賱夭賲賳賷 賱賱廿賳匕丕乇 丕賱匕賰賷 亘丕賱丿賯丕卅賯</string>
+    <string name="pref_title_overwrite_settings_on_connection">丕賱賰鬲丕亘丞 賮賵賯 丕賱廿毓丿丕丿丕鬲 毓賳丿 丕賱丕鬲氐丕賱</string>
+    <string name="sony_button_mode_help_summary">賵氐賮 賱賰賱 賵囟毓 夭乇</string>
+    <string name="controlcenter_set_folder_title">賯賲 亘鬲毓賷賷賳 兀賵 廿賳卮丕亍 賲噩賱丿 噩丿賷丿</string>
+    <string name="mi2_prefs_rotate_wrist_to_switch_info">鬲丿賵賷乇 丕賱賲毓氐賲 賱鬲亘丿賷賱 丕賱賲毓賱賵賲丕鬲</string>
+    <string name="pref_title_ping_tone">賳睾賲丞 亘賷賳睾</string>
+    <string name="pref_title_connected_advertisement">賲乇卅賷 兀孬賳丕亍 丕賱丕鬲氐丕賱</string>
+    <string name="title_activity_db_management">廿丿丕乇丞 丕賱亘賷丕賳丕鬲</string>
+    <string name="qhybrid_changes_delay_prompt">賯丿 賷爻鬲睾乇賯 丕賱鬲睾賷賷乇 亘囟毓 孬賵丕賳 鈥�</string>
+    <string name="watchface_setting_wrist_flick_minute">毓賯乇亘 丕賱丿賯丕卅賯 (-360 廿賱賶 360)</string>
+    <string name="enable_notifications_summary">廿卮毓丕乇丕鬲 賱賱賲賰丕賱賲丕鬲 賵丕賱乇爻丕卅賱 賵丕賱賲夭賷丿</string>
+    <string name="wifi_hotspot_start_summary">丕亘丿兀 鬲卮睾賷賱 賳賯胤丞 丕鬲氐丕賱 Wi-Fi 毓賱賶 丕賱爻丕毓丞</string>
+    <string name="wifi_hotspot_stop_summary">兀賵賯賮 賳賯胤丞 丕鬲氐丕賱 Wi-Fi 毓賱賶 丕賱爻丕毓丞</string>
+    <string name="pref_workout_detection_alert_title">鬲賳亘賷賴</string>
+    <string name="pref_agps_update_time">賵賯鬲 鬲丨丿賷孬 AGPS</string>
+    <string name="pref_agps_expire_time">賵賯鬲 丕賳鬲賴丕亍 AGPS</string>
+    <string name="pref_title_pebble_sync_misfit">賲夭丕賲賳丞 睾賷乇 賲賱丕卅賲丞</string>
+    <string name="prefs_sounds_summary">賯賲 亘鬲賰賵賷賳 丕賱賵賯鬲 丕賱匕賷 爻賷氐丿乇 賮賷賴 丕賱噩賴丕夭 氐賵鬲 鬲賳亘賷賴</string>
+    <string name="pref_title_low_latency_fw_update">丕爻鬲禺丿丕賲 賵囟毓 夭賲賳 丕賱賵氐賵賱 丕賱賲賳禺賮囟 賱鬲孬亘賷鬲 丕賱亘乇丕賲噩 丕賱孬丕亘鬲丞</string>
+    <string name="miband_prefs_reserve_alarm_calendar">鬲賳亘賷賴丕鬲 賱賱丨噩夭 賱賱兀丨丿丕孬 丕賱賯丕丿賲丞</string>
+    <string name="FetchActivityOperation_about_to_transfer_since">毓賱賶 賵卮賰 賳賯賱 丕賱亘賷丕賳丕鬲 賲賳匕 %1$s</string>
+    <string name="zetime_title_screentime">賲丿丞 丕賱卮丕卮丞 亘丕賱孬賵丕賳賷</string>
+    <string name="zetime_heart_rate_alarm_enable">鬲賮毓賷賱 賲賳亘賴 賲毓丿賱 賳亘囟丕鬲 丕賱賯賱亘</string>
+    <string name="tap_connected_device_for_vibration">丕賳賯乇 賮賵賯 丕賱噩賴丕夭 丕賱賲鬲氐賱 賱賱丕賴鬲夭丕夭</string>
+    <string name="updatefirmwareoperation_update_complete_rebooting">丕賰鬲賲賱 鬲孬亘賷鬲 丕賱亘乇賳丕賲噩 丕賱孬丕亘鬲 貙 噩丕乇賺 廿毓丕丿丞 鬲卮睾賷賱 丕賱噩賴丕夭 鈥�</string>
+    <string name="prefs_active_noise_cancelling_summary">丨噩亘 丕賱囟賵囟丕亍 丕賱賲丨賷胤丞</string>
+    <string name="pref_title_pebble_gatt_clientonly">GATT 毓賲賷賱 賮賯胤</string>
+    <string name="pref_title_device_internet_access">丕賱爻賲丕丨 亘丕賱賵氐賵賱 廿賱賶 丕賱廿賳鬲乇賳鬲</string>
+    <string name="prefs_screen_orientation">丕鬲噩丕賴 丕賱卮丕卮丞</string>
+    <string name="power_mode_title">丕賱爻丕毓丞 賵囟毓 丕賱胤丕賯丞</string>
+    <string name="pref_agps_expiry_reminder_time">賵賯鬲 鬲匕賰賷乇 丕賳鬲賴丕亍 氐賱丕丨賷丞 AGPS</string>
+    <string name="zetime_signaling_vibrate_beep_once">丕賱丕賴鬲夭丕夭 賵丕賱氐賮賷乇 賲乇丞 賵丕丨丿丞</string>
+    <string name="prefs_hr_alarm_low">丕賱丨丿 丕賱兀丿賳賶</string>
+    <string name="pref_agps_expiry_reminder_enabled">鬲匕賰賷乇 丕賳鬲賴丕亍 氐賱丕丨賷丞 AGPS</string>
+    <string name="contact_no_free_slots_description">賱丕賷丨鬲賵賷 丕賱噩賴丕夭 毓賱賶 兀賷 氐賮丨丕鬲 賮丕乇睾丞 賱噩賴丕鬲 丕賱丕鬲氐丕賱 (賲噩賲賵毓 丕賱氐賮丨丕鬲: %1$s</string>
+    <string name="pref_summary_pebble_always_ack_pebblekit">爻賷丐丿賷 廿賱賶 丕賱丕毓鬲乇丕賮 亘丕賱乇爻丕卅賱 丕賱鬲賷 賷鬲賲 廿乇爻丕賱賴丕 廿賱賶 鬲胤亘賷賯丕鬲 丕賱胤乇賮 3rd 丕賱禺丕乇噩賷丞 丿丕卅賲丕 賵毓賱賶 丕賱賮賵乇</string>
+    <string name="menuitem_takephoto">噩賴丕夭 丕賱鬲丨賰賲 毓賳 亘毓丿 賱賱賰丕賲賷乇丕</string>
+    <string name="menuitem_membership_cards">亘胤丕賯丕鬲 丕賱毓囟賵賷丞</string>
+    <string name="menuitem_findphone">丕賱亘丨孬 毓賳 丕賱賴丕鬲賮</string>
+    <string name="menuitem_worldclock">丕賱爻丕毓丞 丕賱毓丕賱賲賷丞</string>
+    <string name="menuitem_goal">賴丿賮 丕賱賳卮丕胤</string>
+    <string name="menuitem_cycles">鬲鬲亘毓 丕賱丿賵乇丞</string>
+    <string name="menuitem_wechat_pay">丿賮毓 WeChat</string>
+    <string name="menuitem_email">丕賱亘乇賷丿 丕賱廿賱賰鬲乇賵賳賷</string>
+    <string name="menuitem_phone">丕賱賴丕鬲賮</string>
+    <string name="pref_title_pebble_mtu_limit">丨丿 Pebble 2 / LE GATT MTU</string>
+    <string name="gadgetbridge_running_main_nightly">丕賱賷賱賷 GB 賷毓賲賱</string>
+    <string name="title_activity_controlcenter_main_nopebble">Gadgetbridge 丕賱賷賱賷 賱丕 Pebble</string>
+    <string name="gadgetbridge_running_main_nopebble">賱賷賱丕賸 NoPebble GB 賯賷丿 丕賱鬲卮睾賷賱</string>
+    <string name="preferences_qhybrid_settings">廿毓丿丕丿丕鬲 Q 丕賱賴噩賷賳丞</string>
+    <string name="menuitem_hr">賲毓丿賱 賳亘囟丕鬲 丕賱賯賱亘</string>
+    <string name="menuitem_pomodoro">Pomodoro 賲鬲鬲亘毓</string>
+    <string name="menuitem_voice_memos">丕賱賲匕賰乇丕鬲 丕賱氐賵鬲賷丞</string>
+    <string name="menuitem_sun_moon">丕賱卮賲爻 賵丕賱賯賲乇</string>
+    <string name="pref_title_pebble_always_ack_pebblekit">賯亘賱 丕賱兀賵丕賳 ACK PebbleKit</string>
+    <string name="menuitem_eventreminder">鬲匕賰賷乇 丕賱丨丿孬</string>
+    <string name="menuitem_widgets">丕賱賵賷丿噩賷鬲</string>
+    <string name="menuitem_one_tap_measuring">丕賱賯賷丕爻 亘賳賯乇丞 賵丕丨丿丞</string>
+    <string name="pref_title_notification_delay_calls">鬲兀禺賷乇 廿卮毓丕乇 丕賱賲賰丕賱賲丞</string>
+    <string name="menuitem_last_workout">丌禺乇 鬲賲乇賷賳</string>
+    <string name="menuitem_recommendation">丕賱鬲賵氐賷賴</string>
+    <string name="menuitem_todo">賲丕 賷噩亘 丕賱賯賷丕賲 亘賴</string>
+    <string name="menuitem_mutephone">賰鬲賲 氐賵鬲 丕賱賴丕鬲賮</string>
+    <string name="menuitem_calendar">丕賱鬲賯賵賷賲</string>
+    <string name="menuitem_offline_voice">氐賵鬲 睾賷乇 賲鬲氐賱</string>
+    <string name="menuitem_countdown">丕賱毓丿 丕賱鬲賳丕夭賱賷</string>
+    <string name="menuitem_female_health">氐丨丞 丕賱賲乇兀丞</string>
+    <string name="pref_title_pebble_enable_bgjs">鬲賲賰賷賳 JS 丕賱禺賱賮賷丞</string>
+    <string name="menuitem_cards">丕賱亘胤丕賯丕鬲</string>
+    <string name="menuitem_mi_ai">MI 丕賱匕賰丕亍 丕賱丕氐胤賳丕毓賷</string>
+    <string name="fossil_hr_confirm_connection">賷乇噩賶 丕賱鬲兀賰賷丿 毓賱賶 爻丕毓鬲賰</string>
+    <string name="fossil_hr_confirmation_timeout">賲賴賱丞 丕賱鬲兀賰賷丿 貙 噩丕乇賷 丕賱賲鬲丕亘毓丞</string>
+    <string name="fossil_hr_confirmation_skipped">鬲禺胤賷 丕賱鬲兀賰賷丿 毓賱賶 丕賱噩賴丕夭</string>
+    <string name="fossil_hr_pairing_failed">賮卮賱 丕賱丕賯鬲乇丕賳 亘丕賱爻丕毓丞</string>
+    <string name="fossil_hr_pairing_successful">鬲賲 丕賱丕賯鬲乇丕賳 賲毓 丕賱爻丕毓丞 亘賳噩丕丨</string>
+    <string name="menuitem_personal_activity_intelligence">匕賰丕亍 丕賱賳卮丕胤 丕賱卮禺氐賷</string>
+    <string name="menuitem_workout_history">鬲丕乇賷禺 丕賱鬲賲乇賷賳</string>
+    <string name="menuitem_workout_status">丨丕賱丞 丕賱鬲賲乇賷賳</string>
+    <string name="zepp_os_watchface_red_fantasy">丕賱禺賷丕賱 丕賱兀丨賲乇</string>
+    <string name="device_is_currently_bonded">賲乇亘賵胤 賲爻亘賯丕</string>
+    <string name="strokes_second">str/s</string>
+    <string name="Strokes">爻鬲乇賵賰爻</string>
+    <string name="prefs_events_forwarding_broadcast_title">乇爻丕賱丞 丕賱亘孬</string>
+    <string name="prefs_ambient_mode">丕賱賵囟毓 丕賱賲丨賷胤賷</string>
+    <string name="devicetype_bose_qc35">Bose QC35</string>
+    <string name="menuitem_menu">丕賱賯丕卅賲丞</string>
+    <string name="activity_prefs_alarm_min_heart_rate">丕賱丨丿 丕賱兀丿賳賶 賱賲毓丿賱 賳亘囟丕鬲 丕賱賯賱亘</string>
+    <string name="activity_prefs_alarm_max_heart_rate">兀賯氐賶 賲毓丿賱 賱賳亘囟丕鬲 丕賱賯賱亘</string>
+    <string name="pref_header_vibration_settings">廿毓丿丕丿丕鬲 丕賱丕賴鬲夭丕夭</string>
+    <string name="pref_screen_notification_profile_generic">丕賱廿卮毓丕乇 丕賱毓丕賲</string>
+    <string name="reminder_delete_confirm_title">丨匕賮 丕賱鬲匕賰賷乇</string>
+    <string name="updatefirmwareoperation_write_failed">賮卮賱 鬲孬亘賷鬲 丕賱亘乇賳丕賲噩 丕賱孬丕亘鬲</string>
+    <string name="prefs_events_forwarding_startnonwear">毓賳丿 毓丿賲 丕賱廿乇鬲丿丕亍</string>
+    <string name="devicetype_miband4">賲賷 亘丕賳丿 4</string>
+    <string name="devicetype_amazfit_pop">Amazfit Pop</string>
+    <string name="pref_title_lower_button_function_long">丕賱夭乇 丕賱爻賮賱賷 胤賵賷賱</string>
+    <string name="sony_speak_to_chat_sensitivity">丨爻丕爻賷丞 賰卮賮 丕賱氐賵鬲</string>
+    <string name="sony_equalizer_preset_bass_boost">鬲毓夭賷夭 丕賱噩賴賷乇</string>
+    <string name="sony_equalizer_preset_speech">賰賱丕賲</string>
+    <string name="buttons_on_right">丕賱兀夭乇丕乇 丕賱賲賵噩賵丿丞 毓賱賶 丕賱賷賲賷賳</string>
+    <string name="preferences_category_device_specific_settings">丕賱廿毓丿丕丿丕鬲 丕賱禺丕氐丞 亘丕賱噩賴丕夭</string>
+    <string name="buttons_on_left">丕賱兀夭乇丕乇 丕賱賲賵噩賵丿丞 毓賱賶 丕賱賷爻丕乇</string>
+    <string name="notification_channel_low_battery_name">丕賱亘胤丕乇賷丞 囟毓賷賮丞</string>
+    <string name="notification_channel_gps">鬲鬲亘毓 賳馗丕賲 鬲丨丿賷丿 丕賱賲賵丕賯毓 丕賱毓丕賱賲賷 (GPS</string>
+    <string name="averageAltitude">丕賱賲鬲賵爻胤</string>
+    <string name="activeSeconds">丕賱賳卮丕胤</string>
+    <string name="watchface_widget_type_heart_rate">賲毓丿賱 賳亘囟丕鬲 丕賱賯賱亘</string>
+    <string name="pref_header_spo2">兀賰爻噩賷賳 丕賱丿賲</string>
+    <string name="pref_header_sony_ambient_sound_control">丕賱鬲丨賰賲 賮賷 丕賱氐賵鬲 丕賱賲丨賷胤賷</string>
+    <string name="pref_header_sony_device_info">賲毓賱賵賲丕鬲 丕賱噩賴丕夭</string>
+    <string name="pref_header_sony_anc_optimizer">賲丨爻賳 廿賱睾丕亍 丕賱囟賵囟丕亍</string>
+    <string name="pref_anc_optimizer_state_pressure">丕賱囟睾胤 丕賱噩賵賷</string>
+    <string name="sony_sound_position_front">兀賲丕賲</string>
+    <string name="sony_sound_position_rear_left">丕賱賷爻丕乇 丕賱禺賱賮賷</string>
+    <string name="activity_prefs_allow_bluetooth_intent_api">亘賱賵鬲賵孬 賳賷丞 API</string>
+    <string name="prefs_weardirection">丕鬲噩丕賴 丕賱丕乇鬲丿丕亍</string>
+    <string name="pref_screen_vibration_profile">賲賱賮 鬲毓乇賷賮 丕賱丕賴鬲夭丕夭</string>
+    <string name="prefs_title_heartrate_measurement_interval">賯賷丕爻 賳亘囟丕鬲 丕賱賯賱亘 胤賵丕賱 丕賱賷賵賲</string>
+    <string name="pref_screen_notification_profile_anti_loss">鬲丨匕賷乇 囟丿 丕賱禺爻丕乇丞</string>
+    <string name="pref_screen_notification_profile_event_reminder">丨丿孬 丕賱鬲匕賰賷乇</string>
+    <string name="pref_screen_vibration_patterns_title">兀賳賲丕胤 丕賱丕賴鬲夭丕夭</string>
+    <string name="world_clock_label">鬲爻賲賷賴</string>
+    <string name="dbaccess_error_executing">禺胤兀 賮賷 鬲賳賮賷匕 \"%1$s\"</string>
+    <string name="controlcenter_start_activitymonitor">賳卮丕胤賰</string>
+    <string name="notif_battery_low">%1$s 丕賱亘胤丕乇賷丞 賲賳禺賮囟丞</string>
+    <string name="devicestatus_connecting">丕賱噩賴丕夭 賯賷丿 丕賱丕鬲氐丕賱</string>
+    <string name="devicestatus_upload_starting">亘丿兀 丕賱鬲丨賲賷賱</string>
+    <string name="devicestatus_upload_started">亘丿兀 丕賱乇賮毓</string>
+    <string name="installhandler_firmware_name">%1$s: %2$s %3$s</string>
+    <string name="updatefirmwareoperation_update_complete">丕賰鬲賲丕賱 鬲孬亘賷鬲 丕賱亘乇賳丕賲噩 丕賱孬丕亘鬲</string>
+    <string name="watchface_dialog_widget_background">丕賱禺賱賮賷丞</string>
+    <string name="gpx_route_upload_failed">賮卮賱 乇賮毓 賲爻丕乇 Gpx</string>
+    <string name="gpx_route_upload_complete">丕賰鬲賲賱 乇賮毓 賲爻丕乇 Gpx</string>
+    <string name="gpx_route_upload_in_progress">乇賮毓 賲爻丕乇 gpx</string>
+    <string name="seconds_7">7 孬賵丕賳</string>
+    <string name="minutes_5">5 丿賯丕卅賯</string>
+    <string name="user_feedback_all_alarms_disabled">鬲賲 鬲毓胤賷賱 噩賲賷毓 丕賱鬲亘賷賴丕鬲</string>
+    <string name="autoExport_lastTime_label">丌禺乇 鬲氐丿賷乇 鬲賱賯丕卅賷: %1$s</string>
+    <string name="activity_DB_ExportButton">鬲氐丿賷乇 丕賱亘賷丕賳丕鬲</string>
+    <string name="activity_db_management_exportimport_label">丕賱鬲氐丿賷乇 賵丕賱丕爻鬲賷乇丕丿</string>
+    <string name="dismiss">乇賮囟</string>
+    <string name="sensitive">丨爻丕爻</string>
+    <string name="activity_type_indoor_walking">丕賱賲卮賷 賮賷 丕賱兀賲丕賰賳 丕賱賲睾賱賯丞</string>
+    <string name="activity_type_stepper">丕賱禺胤賵丞</string>
+    <string name="activity_type_gymnastics">丕賱噩賲亘丕夭</string>
+    <string name="activity_type_hiit">鬲丿乇賷亘 賲鬲賯胤毓 毓丕賱賷 丕賱賰孬丕賮丞</string>
+    <string name="activity_type_core_training">丕賱鬲丿乇賷亘 丕賱兀爻丕爻賷</string>
+    <string name="menuitem_screen_always_lit">丕賱卮丕卮丞 賲囟丕亍丞 丿丕卅賲丕</string>
+    <string name="menuitem_wifi">賵丕賷 賮丕賷</string>
+    <string name="zepp_os_watchface_multiple_data">亘賷丕賳丕鬲 賲鬲毓丿丿丞</string>
+    <string name="zepp_os_watchface_rush">丕賽賳賿丿賻賮賻毓賻</string>
+    <string name="zepp_os_watchface_minimalist">丕賱丨丿 丕賱兀丿賳賶</string>
+    <string name="zepp_os_watchface_vibrant">賳丕亘囟 亘丕賱丨賷丕丞</string>
+    <string name="zepp_os_watchface_emerald_moonlight">囟賵亍 丕賱賯賲乇 丕賱夭賲乇丿賷</string>
+    <string name="zepp_os_watchface_rotating_earth">丕賱兀乇囟 丕賱丿賵丕乇丞</string>
+    <string name="require_location_provider">賷噩亘 鬲賲賰賷賳 丕賱賲賵賯毓</string>
+    <string name="ascentDistance">賲爻丕賮丞 卮丕賯丞</string>
+    <string name="watchface_widget_type_2nd_tz">丕賱賲賳胤賯丞 丕賱夭賲賳賷丞 2</string>
+    <string name="watchface_widget_type_chance_rain">丕丨鬲賲丕賱 賴胤賵賱 丕賱兀賲胤丕乇</string>
+    <string name="prefs_ambient_voice_focus">丕賱鬲乇賰賷夭 丕賱氐賵鬲賷</string>
+    <string name="devicetype_nothingear1">Nothing Ear (1)</string>
+    <string name="nothing_prefs_audiomode_title">賵囟毓 丕賱氐賵鬲</string>
+    <string name="pref_balance">賲鬲賵丕夭賳</string>
+    <string name="intent_api_broadcast_export_title">丕賱亘孬 毓賱賶 鬲氐丿賷乇 賯丕毓丿丞 丕賱亘賷丕賳丕鬲</string>
+    <string name="activity_error_share_failed">賮卮賱鬲 賲卮丕乇賰丞 丕賱賲賱賮.</string>
+    <string name="kind_gpx_route">胤乇賷賯 GPX</string>
+    <string name="devicetype_amazfit_trex">Amazfit T-Rex</string>
+    <string name="devicetype_amazfit_bips_lite">Amazfit Bip S 賱丕賷鬲</string>
+    <string name="devicetype_qhybrid">Fossil Q Hybrid</string>
+    <string name="devicetype_sony_wh_1000xm5">Sony WH-1000XM5</string>
+    <string name="pref_title_contextual_arabic">丕賱爻賷丕賯 丕賱毓乇亘賷</string>
+    <string name="pref_qhybrid_title_widget_draw_circles">乇爻賲 丿賵丕卅乇 毓賳氐乇 賵丕噩賴丞 丕賱賲爻鬲禺丿賲</string>
+    <string name="companiondevice_pairing">廿賯乇丕賳 丕賱噩賴丕夭 丕賱賲乇丕賮賯</string>
+    <string name="maxHR">兀賯氐賶 賲毓丿賱 賱賳亘囟丕鬲 丕賱賯賱亘</string>
+    <string name="averageStrokesPerSecond">賲鬲賵爻胤 丕賱爻鬲乇賵賰</string>
+    <string name="watchface_dialog_title_widget">廿毓丿丕丿丕鬲 丕賱賵賷丿噩賷鬲</string>
+    <string name="watchface_setting_button_move_hands">鬲丨乇賷賰 丕賱賷丿賷賳</string>
+    <string name="watchface_setting_display_refresh_partial">鬲丨丿賷孬 噩夭卅賷 (亘丕賱丿賯丕卅賯)</string>
+    <string name="watchface_setting_wrist_flick_duration">丕賱賲丿丞 (亘丕賱賲賱賱賷 孬丕賳賷丞)</string>
+    <string name="prefs_ambient_voice_summary">丕噩毓賱 丕賱氐賵鬲 亘丕乇夭丕</string>
+    <string name="prefs_active_noise_cancelling_level_high">毓丕賱</string>
+    <string name="prefs_autoheartrate_sleep">兀禺匕 丕賱賯賷丕爻丕鬲 兀孬賳丕亍 丕賱賳賵賲</string>
+    <string name="prefs_switch_control_right">鬲亘丿賷賱 丕賱鬲丨賰賲 丕賱賶 丕賱賷賲賷賳</string>
+    <string name="prefs_switch_control_left">鬲亘丿賷賱 丕賱鬲丨賰賲 廿賱賶 丕賱賷爻丕乇</string>
+    <string name="pref_header_heartrate_allday">賲乇丕賯亘丞 胤賵丕賱 丕賱賷賵賲</string>
+    <string name="pref_title_touch_quick_ambient">氐賵鬲 賲丨賷胤賷 爻乇賷毓</string>
+    <string name="sony_ambient_sound_focus_voice">丕賱鬲乇賰賷夭 毓賱賶 丕賱氐賵鬲</string>
+    <string name="pref_switch_controls_anc_ambient">廿賱睾丕亍 丕賱囟賵囟丕亍 鈫愨啋 丕賱賲丨賷胤賷</string>
+    <string name="pref_switch_controls_ambient_off">賲丨賷胤賷 鈫愨啋 廿賷賯丕賮</string>
+    <string name="prefs_voice_detect_duration">丕賳鬲賴賶 亘毓丿 丕賱賴丿賵亍 賲賳 兀噩賱:</string>
+    <string name="pref_voice_detect_duration_5">5 孬賵丕賳</string>
+    <string name="pref_voice_detect_duration_15">15 孬丕賳賷丞</string>
+    <string name="sony_anc_optimizer_status_analyzing">噩丕乇賺 丕賱鬲丨賱賷賱鈥�</string>
+    <string name="sony_equalizer_band_6300">6.3k</string>
+    <string name="sony_speak_to_chat_timeout_standard">賯賷丕爻賷 (30 孬丕賳賷丞)</string>
+    <string name="sony_sound_position">賲賵囟毓 丕賱氐賵鬲</string>
+    <string name="sony_surround_mode_outdoor_stage">丕賱賲爻乇丨 丕賱禺丕乇噩賷</string>
+    <string name="sony_anc_optimizer_status_atmospheric_pressure">賯賷丕爻 丕賱囟睾胤 丕賱噩賵賷鈥�</string>
+    <string name="sony_ambient_sound_control_button_mode_nc_as_off">廿賱睾丕亍 丕賱囟賵囟丕亍貙 丕賱氐賵鬲 丕賱賲丨賷胤賷貙 廿賷賯丕賮</string>
+    <string name="sony_quick_access_double_tap">丕賱賵氐賵賱 丕賱爻乇賷毓 (賳賯乇丞 賲夭丿賵噩丞)</string>
+    <string name="sony_quick_access_triple_tap">丕賱賵氐賵賱 丕賱爻乇賷毓 (賳賯乇丞 孬賱丕孬賷丞)</string>
+    <string name="sony_button_mode_help_title">兀賵囟丕毓 丕賱兀夭乇丕乇 - 鬲毓賱賷賲丕鬲</string>
+    <string name="sony_button_mode_playback_control">丕賱鬲丨賰賲 賮賷 丕賱鬲卮睾賷賱</string>
+    <string name="qhybrid_title_on_device_confirmation">鬲賲賰賷賳 鬲兀賰賷丿 丕賱廿賯乇丕賳 毓賱賶 丕賱噩賴丕夭</string>
+    <string name="pref_screen_brightness">爻胤賵毓 丕賱卮丕卮丞</string>
+    <string name="watchface_dialog_widget_timezone">丕賱賲賳胤賯丞 丕賱夭賲賳賷丞</string>
+    <string name="watchface_dialog_widget_timezone_duration">賲丿丞 乇丐賷丞 丕賱爻丕毓丞 (亘丕賱孬賵丕賳賷)</string>
+    <string name="pref_device_action_phone_gps_location_listener_stop">丕賷賯丕賮 賲爻鬲賲毓 賲賵賯毓 GPS</string>
+    <string name="distance_format_meters">### 賲</string>
+    <string name="steps_streaks_streak_days">禺胤
+\n兀賷丕賲</string>
+    <string name="steps_streaks_average_steps">賲鬲賵爻胤
+\n丕賱禺胤賵丕鬲</string>
+    <string name="ftp_server_root_dir">丿賱賷賱 丕賱噩匕乇</string>
+    <string name="address">丕賱毓賳丕賵賷賳</string>
+    <string name="username">丕爻賲 丕賱賲爻鬲禺丿賲</string>
+    <string name="notif_battery_low_bigtext_last_charge_time">丌禺乇 卮丨賳丞: %s
+\n</string>
+    <string name="activity_type_boxing">丕賱賲賱丕賰賲丞</string>
+    <string name="activity_type_zumba">夭賵賲亘丕</string>
+    <string name="activity_type_indoor_ice_skating">丕賱鬲夭賱噩 毓賱賶 丕賱噩賱賷丿 賮賷 丕賱兀賲丕賰賳 丕賱賲睾賱賯丞</string>
+    <string name="devicetype_zepp_e">Zepp E</string>
+    <string name="devicetype_amazfit_gtr3">Amazfit GTR 3</string>
+    <string name="filter_submode_all">賰賱 丕賱賰賱賲丕鬲</string>
+    <string name="pref_title_upper_button_function_long">丕賱夭乇 丕賱毓賱賵賷 胤賵賷賱</string>
+    <string name="pref_title_upper_button_function_short">丕賱夭乇 丕賱毓賱賵賷 丕賱賯氐賷乇</string>
+    <string name="averageLapPace">賲鬲賵爻胤 爻乇毓丞 丕賱賱賮丞</string>
+    <string name="pref_camera_remote_title">噩賴丕夭 鬲丨賰賲 毓賳 亘毓丿 亘丕賱賰丕賲賷乇丕</string>
+    <string name="pref_header_auto_export">鬲氐丿賷乇 鬲賱賯丕卅賷</string>
+    <string name="pref_sleep_mode_title">賵囟毓 丕賱賳賵賲</string>
+    <string name="pref_sleep_mode_sleep_screen_title">卮丕卮丞 丕賱賳賵賲</string>
+    <string name="initialized">賲賴賷兀</string>
+    <string name="title_activity_discovery">丕賰鬲卮丕賮 丕賱噩賴丕夭</string>
+    <string name="busy_task_fetch_activity_data">噩賱亘 亘賷丕賳丕鬲 丕賱賳卮丕胤</string>
+    <string name="control_center_cancel_to_stop_vibration">廿賱睾丕亍 賱廿賷賯丕賮 丕賱丕賴鬲夭丕夭.</string>
+    <string name="title_activity_set_alarm">鬲賰賵賷賳 丕賱賲賳亘賴丕鬲</string>
+    <string name="title_activity_set_reminders">鬲賰賵賷賳 丕賱鬲匕賰賷乇丕鬲</string>
+    <string name="reminder_time_every_month">%1$s貙 賰賱 卮賴乇</string>
+    <string name="world_clock_no_free_slots_title">賱丕 鬲賵噩丿 氐賮丨丕鬲 賮丕乇睾丞</string>
+    <string name="devicestatus_disconnecting">丕賱噩賴丕夭 賲賮氐賵賱!</string>
+    <string name="pref_title_charts_range">賳胤丕賯 丕賱乇爻賵賲 丕賱亘賷丕賳賷丞</string>
+    <string name="pref_title_chart_heartrate_color">賱賵賳 賲毓丿賱 賳亘囟丕鬲 丕賱賯賱亘</string>
+    <string name="activity_db_management_autoexport_enabled_yes">鬲賲 鬲賲賰賷賳 丕賱鬲氐丿賷乇 丕賱鬲賱賯丕卅賷.</string>
+    <string name="activity_DB_import_button">丕爻鬲賷乇丕丿 丕賱亘賷丕賳丕鬲</string>
+    <string name="kind_gps">丕賱亘乇丕賲噩 丕賱孬丕亘鬲丞 賱賳馗丕賲 鬲丨丿賷丿 丕賱賲賵丕賯毓 丕賱毓丕賱賲賷 (GPS)</string>
+    <string name="note">賲賱丕丨馗丞</string>
+    <string name="widget_20_minutes">20 丿賯賷賯丞</string>
+    <string name="widget_1_hour">1 爻丕毓丞</string>
+    <string name="pref_title_touch_anc">廿賱睾丕亍 丕賱囟賵囟丕亍 丕賱賳卮胤</string>
+    <string name="continue_pressing">丕爻鬲賲乇 賮賷 丕賱囟睾胤</string>
+    <string name="quick_attention">丕賴鬲賲丕賲 爻乇賷毓</string>
+    <string name="function_enabled">賲賲賰賳</string>
+    <string name="controlcenter_start_activity_tracks">賲爻丕乇丕鬲 賳卮丕胤賰</string>
+    <string name="devicetype_hplus">HPlus</string>
+    <string name="save_configuration">丨賮馗 丕賱鬲賰賵賷賳</string>
+    <string name="add_widget">廿囟丕賮丞 丕賱賵賷丿噩賷鬲</string>
+    <string name="qhybrid_time_shift">鬲丨賵賱 丕賱賵賯鬲</string>
+    <string name="gpx_receiver_files_received">鬲賲 丕爻鬲賱丕賲 賲賱賮 (賲賱賮丕鬲) GPX:</string>
+    <string name="ignore_bonded_devices">鬲噩丕賴賱 丕賱兀噩賴夭丞 丕賱賲乇鬲亘胤丞</string>
+    <string name="sonyswr12_settings_title">廿毓丿丕丿丕鬲 爻賵賳賷 SWR12</string>
+    <string name="watchface_dialog_title_set_name">鬲毓賷賷賳 丕爻賲 賵丕噩賴丞 丕賱爻丕毓丞</string>
+    <string name="button_watchface_save_apply">丨賮馗 賵鬲胤亘賷賯</string>
+    <string name="hybridhr_widget_bg_dashed_circle">丿丕卅乇丞 賲鬲賯胤毓丞</string>
+    <string name="sony_audio_upsampling">乇賮毓 毓賷賳丕鬲 丕賱氐賵鬲</string>
+    <string name="devicetype_sony_wh_1000xm2">Sony WH-1000XM2</string>
+    <string name="averageCadence">賲鬲賵爻胤 丕賱廿賷賯丕毓</string>
+    <string name="sony_touch_sensor">丕賱鬲丨賰賲 賮賷 賲爻鬲卮毓乇 丕賱賱賲爻</string>
+    <string name="sony_notification_voice_guide">丕賱廿卮毓丕乇丕鬲 賵丕賱丿賱賷賱 丕賱氐賵鬲賷</string>
+    <string name="sony_button_mode_left">賵囟毓 丕賱夭乇 (賷爻丕乇)</string>
+    <string name="world_clock_timezone">丕賱賲賳胤賯丞 丕賱夭賲賳賷丞</string>
+    <string name="prefs_relaxation_reminder_title">鬲匕賰賷乇 丕賱丕爻鬲乇禺丕亍</string>
+    <string name="mi2_prefs_heart_rate_monitoring">賲乇丕賯亘丞 賲毓丿賱 賳亘囟丕鬲 丕賱賯賱亘</string>
+    <string name="prefs_always_on_display">丿丕卅賲丕 毓賱賶 丕賱卮丕卮丞</string>
+    <string name="mi2_prefs_heart_rate_monitoring_summary">鬲賰賵賷賳 賲乇丕賯亘丞 賲毓丿賱 賳亘囟丕鬲 丕賱賯賱亘</string>
+    <string name="estonian">丕賱廿爻鬲賵賳賷丞</string>
+    <string name="find_lost_device_you_found_it">賵噩丿鬲賴!</string>
+    <string name="mi2_enable_text_notifications">丕賱廿卮毓丕乇丕鬲 丕賱賳氐賷丞</string>
+    <string name="sonyswr12_settings_low_vibration">鬲賲賰賷賳 丕賱丕賴鬲夭丕夭 丕賱賲賳禺賮囟</string>
+    <string name="minCadence">丕賱丨丿 丕賱兀丿賳賶 賱賱廿賷賯丕毓</string>
+    <string name="sony_sound_position_front_left">丕賱賷爻丕乇 丕賱兀賲丕賲賷</string>
+    <string name="sony_button_mode_right">賵囟毓 丕賱夭乇 (賷賲賷賳)</string>
+    <string name="sony_button_mode_volume_control">丕賱鬲丨賰賲 賮賷 賲爻鬲賵賶 丕賱氐賵鬲</string>
+    <string name="controlcenter_connected_fraction">賲鬲氐賱: %d/%d</string>
+    <string name="pref_device_action_fitness_app_control_start">亘丿亍 鬲鬲亘毓 鬲胤亘賷賯 丕賱賱賷丕賯丞 丕賱亘丿賳賷丞</string>
+    <string name="activity_prefs_height_cm">丕賱丕乇鬲賮丕毓 亘丕賱爻賳鬲賷賲鬲乇</string>
+    <string name="activity_web_view">賳卮丕胤 毓乇囟 丕賱賵賷亘</string>
+    <string name="normal">毓丕丿賷</string>
+    <string name="activity_type_hiking">丕賱賲卮賷</string>
+    <string name="kind_invalid">亘賷丕賳丕鬲 睾賷乇 氐丕賱丨丞</string>
+    <string name="pref_header_statistics">廿丨氐丕卅賷丕鬲 丕賱兀賳卮胤丞 丕賱乇賷丕囟賷丞</string>
+    <string name="watchface_setting_button_toggle_widgets">鬲亘丿賷賱 丕賱賵賷丿噩賷鬲</string>
+    <string name="prefs_sleep_time">兀賵賯丕鬲 丕賱賳賵賲</string>
+    <string name="sony_anc_optimizer_status_not_running">賱丕 賷毓賲賱</string>
+    <string name="sony_equalizer_preset_vocal">氐賵鬲賷</string>
+    <string name="sony_equalizer_preset_excited">賲鬲丨賲爻</string>
+    <string name="menuitem_aqi">賲丐卮乇 噩賵丿丞 丕賱賴賵丕亍</string>
+    <string name="menuitem_lockscreen">賯賮賱 丕賱卮丕卮丞</string>
+    <string name="menuitem_eject_water">廿禺乇丕噩 丕賱賲丕亍</string>
+    <string name="menuitem_unknown_app">睾賷乇 賲毓乇賵賮 (%s)</string>
+    <string name="zepp_os_watchface_simplicity_data">亘賷丕賳丕鬲 丕賱亘爻丕胤丞</string>
+    <string name="title_activity_watch9_pairing">Watch 9 丕賯鬲乇丕賳</string>
+    <string name="prefs_fm_presets_presets">丕賱廿毓丿丕丿丕鬲 丕賱賲爻亘賯丞</string>
+    <string name="title_activity_notification_filter">賲乇卮丨 丕賱廿卮毓丕乇丕鬲</string>
+    <string name="swim_style">兀爻賱賵亘 丕賱爻亘丕丨丞</string>
+    <string name="strokes_unit">str</string>
+    <string name="seconds">孬丕賳賷丞</string>
+    <string name="swolf_index">swolf index</string>
+    <string name="activity_filter_reset_filter">廿毓丕丿丞 鬲毓賷賷賳 丕賱賲乇卮丨</string>
+    <string name="qhybrid_title_apps_management">廿丿丕乇丞 丕賱鬲胤亘賷賯丕鬲</string>
+    <string name="pref_title_physical_buttons">丕賱兀夭乇丕乇 丕賱賲丕丿賷丞</string>
+    <string name="qhybrid_title_watchface_designer">賲氐賲賲 賵丕噩賴丞 丕賱爻丕毓丞</string>
+    <string name="qhybrid_summary_file_management">乇賮毓 賵鬲賳夭賷賱 丕賱賲賱賮丕鬲</string>
+    <string name="pref_title_equalizer_trebble">鬲毓夭賷夭 孬賱丕孬賷</string>
+    <string name="pref_title_touch_spotify_galaxy_app">爻亘賵鬲賷賮賷 (鬲胤亘賷賯 Galaxy 丕賱賯丕亘賱 賱賱丕乇鬲丿丕亍 賮賯胤)</string>
+    <string name="sony_equalizer_preset_custom_1">賲禺氐氐 1</string>
+    <string name="sony_surround_mode_off">丕賷賯丕賮</string>
+    <string name="sony_equalizer">丕賱賲毓丕丿賱</string>
+    <string name="pref_media_pause">廿賷賯丕賮 丕賱賵爻丕卅胤 賲丐賯鬲丕</string>
+    <string name="pref_media_playpause">鬲亘丿賷賱 丕賱鬲卮睾賷賱</string>
+    <string name="pref_media_previous">丕賱賲爻丕乇 丕賱爻丕亘賯</string>
+    <string name="distance_format_kilometers">###.#km</string>
+    <string name="pref_title_opentracks_packagename">丕爻賲 丨夭賲丞 OpenTracks</string>
+    <string name="supercars_lights_blinking_label">賵丕賲囟</string>
+    <string name="devicetype_casiogb6900">Casio GB-6900</string>
+    <string name="devicetype_casiogbx100">Casio GBX-100</string>
+    <string name="qhybrid_goal_in_steps">丕賱賴丿賮 賮賷 丕賱禺胤賵丕鬲</string>
+    <string name="status">丕賱丨丕賱丞</string>
+    <string name="stop">鬲賵賯賮</string>
+    <string name="activity_DB_ShowContentButton">廿馗賴丕乇 賲丨鬲賵賶 丿賱賷賱 丕賱鬲氐丿賷乇/丕賱丕爻鬲賷乇丕丿</string>
+    <string name="pref_screen_notification_profile_alarm_clock">爻丕毓丞 丕賱賲賳亘賴</string>
+    <string name="find_device_you_found_it">賱賯丿 賵噩丿鬲賴!</string>
+    <string name="miband2_prefs_timeformat">Mi2: 鬲賳爻賷賯 丕賱賵賯鬲</string>
+    <string name="hr_widget_heart_rate">賲毓丿賱 賳亘囟丕鬲 丕賱賯賱亘</string>
+    <string name="prefs_button_double_press_action_selection_title">廿噩乇丕亍 丕賱丨丿孬 2</string>
+    <string name="prefs_button_variable_actions">廿毓丿丕丿丕鬲 賲賮氐賱丞 賱賱囟睾胤 毓賱賶 丕賱夭乇</string>
+    <string name="gpx_receiver_activity_title">噩賴丕夭 丕爻鬲賯亘丕賱 GPX Gadgetbridge</string>
+    <string name="prefs_button_single_press_action_selection_title">廿噩乇丕亍 丕賱丨丿孬 1</string>
+    <string name="pref_title_equalizer_dynamic">丿賷賳丕賲賷賰賷</string>
+    <string name="pref_device_action_broadcast">廿乇爻丕賱 丕賱亘孬</string>
+    <string name="distance_format_miles">###.#mi</string>
+    <string name="sony_equalizer_preset_manual">賷丿賵賷</string>
+    <string name="pref_header_sony_equalizer_preset_custom_2">廿毓丿丕丿 賲爻亘賯 賲禺氐氐 2</string>
+    <string name="sony_speak_to_chat_sensitivity_auto">鬲賱賯丕卅賷</string>
+    <string name="sony_speak_to_chat_sensitivity_high">毓丕賱賷丞</string>
+    <string name="sony_speak_to_chat_timeout_off">丕賷賯丕賮</string>
+    <string name="ftp_server_configuration">鬲賰賵賷賳 禺丕丿賲 FTP</string>
+    <string name="qhybrid_calibration_10_steps">10 禺胤賵丕鬲</string>
+    <string name="pref_screen_notification_profile_email">廿卮毓丕乇 丕賱亘乇賷丿 丕賱廿賱賰鬲乇賵賳賷</string>
+    <string name="cannot_connect">賱丕 賷賲賰賳 丕賱丕鬲氐丕賱: %1$s</string>
+    <string name="mi2_prefs_button_actions_summary">鬲丨丿賷丿 廿噩乇丕亍丕鬲 丕賱囟睾胤 毓賱賶 丕賱夭乇</string>
+    <string name="mi2_prefs_button_press_count_max_delay">丕賱丨丿 丕賱兀賯氐賶 賱賱鬲兀禺賷乇 亘賷賳 丕賱囟睾胤丕鬲</string>
+    <string name="choose_auto_export_location">丕禺鬲乇 賲賵賯毓 丕賱鬲氐丿賷乇</string>
+    <string name="notification_channel_transfer_name">賳賯賱 丕賱亘賷丕賳丕鬲</string>
+    <string name="hrZoneFatBurn">丨乇賯 丕賱丿賴賵賳</string>
+    <string name="hrZoneAerobic">丕賱兀賷乇賵亘賷</string>
+    <string name="hrZoneAnaerobic">丕賱賱丕賴賵丕卅賷賴</string>
+    <string name="hrZoneWarmUp">丕賱丕丨賲丕亍</string>
+    <string name="watchface_setting_title_custom_events">兀丨丿丕孬 賲禺氐氐丞</string>
+    <string name="prefs_active_noise_cancelling_level_low">賲賳禺賮囟</string>
+    <string name="prefs_double_tap_edge">丨丕賮丞 丕賱賳賯乇 丕賱賲夭丿賵噩</string>
+    <string name="sony_speak_to_chat_focus_on_voice">乇賰夭 毓賱賶 丕賱氐賵鬲</string>
+    <string name="sony_speak_to_chat_timeout">賳賮匕 丕賱賵賯鬲</string>
+    <string name="info_connected_count">%d 丕賱兀噩賴夭丞 丕賱賲鬲氐賱丞</string>
+    <string name="controlcenter_set_parent_folder">鬲毓賷賷賳 賲噩賱丿 丕賱兀氐賱</string>
+    <string name="supercars_turbo_speed_label">爻乇毓丞 鬲賵乇亘賵</string>
+    <string name="live_activity_current_steps_per_minute">丕賱禺胤賵丕鬲 丕賱丨丕賱賷丞 / 丿賯賷賯丞</string>
+    <string name="device_not_connected">睾賷乇 賲鬲氐賱.</string>
+    <string name="prefs_find_phone_duration">賲丿丞 丕賱乇賳賷賳 亘丕賱孬賵丕賳賷</string>
+    <string name="dateformat_date_time"><![CDATA[Time & date]]></string>
+    <string name="prefs_disconnect_notification">廿卮毓丕乇 賯胤毓 丕賱丕鬲氐丕賱</string>
+    <string name="mi2_prefs_button_press_count">毓丿丿 賲乇丕鬲 丕賱囟睾胤 毓賱賶 丕賱夭乇</string>
+    <string name="mi2_prefs_button_action">鬲賲賰賷賳 廿噩乇丕亍 丕賱夭乇</string>
+    <string name="mi2_prefs_goal_notification">廿卮毓丕乇 丕賱賴丿賮</string>
+    <string name="mi2_prefs_do_not_disturb_start">賵賯鬲 丕賱亘丿亍</string>
+    <string name="mi3_prefs_band_screen_unlock">賮鬲丨 卮丕卮丞 丕賱爻賵丕乇</string>
+    <string name="portuguese_pt">丕賱亘乇鬲睾丕賱賷丞 (丕賱亘乇鬲睾丕賱)</string>
+    <string name="activity_prefs_step_length_cm">胤賵賱 丕賱禺胤賵丞 亘丕賱爻賳鬲賷賲鬲乇</string>
+    <string name="device_hw">賲乇丕噩毓丞 丕賱兀噩賴夭丞: %1$s</string>
+    <string name="pref_title_auto_export_enabled">鬲賲賰賷賳 丕賱鬲氐丿賷乇 丕賱鬲賱賯丕卅賷</string>
+    <string name="pref_auto_fetch_limit_fetches">丕賱丨丿 丕賱兀丿賳賶 賱賱賵賯鬲 亘賷賳 毓賲賱賷丕鬲 丕賱噩賱亘</string>
+    <string name="pref_title_auto_export_location">鬲氐丿賷乇 丕賱賲賵賯毓</string>
+    <string name="unknown">睾賷乇 賲毓乇賵賮</string>
+    <string name="sleep_activity_date_range">賲賳 %1$s 廿賱賶 %2$s</string>
+    <string name="controlcenter_start_configure_reminders">鬲賰賵賷賳 丕賱鬲匕賰賷乇丕鬲</string>
+    <string name="reminder_repeat">鬲賰乇丕乇</string>
+    <string name="reminder_date">丕賱鬲丕乇賷禺</string>
+    <string name="reminder_message">丕賱乇爻丕賱丞</string>
+    <string name="reminder_every_year">賰賱 爻賳丞</string>
+    <string name="reminder_every_week">賰賱 兀爻亘賵毓</string>
+    <string name="reminder_every_month">賰賱 卮賴乇</string>
+    <string name="world_clock_delete_confirm_title">丨匕賮 \"%1$s\"</string>
+    <string name="title_activity_world_clock_details">鬲賮丕氐賷賱 丕賱爻丕毓丞 丕賱毓丕賱賲賷丞</string>
+    <string name="prefs_stress_monitoring_title">賲乇丕賯亘丞 丕賱廿噩賴丕丿</string>
+    <string name="weeksleepchart_sleep_a_week">丕賱賳賵賲 賮賷 丕賱兀爻亘賵毓</string>
+    <string name="activity_type_freestyle">賳賲胤 丨乇</string>
+    <string name="sports_activity_quick_filter_this_month">賴匕丕 丕賱卮賴乇</string>
+    <string name="steps_streaks_total_steps">賲噩賲賵毓
+\n丕賱禺胤賵丕鬲</string>
+    <string name="pref_title_screentime">賲丿丞 鬲卮睾賷賱 丕賱卮丕卮丞</string>
+    <string name="pref_auto_fetch_limit_fetches_summary">賷噩賱亘 賰賱 %d 丿賯賷賯丞</string>
+    <string name="bluetooth_is_not_supported_">丕賱亘賱賵鬲賵孬 睾賷乇 賲丿毓賵賲.</string>
+    <string name="bluetooth_is_disabled_">鬲賲 鬲毓胤賷賱 丕賱亘賱賵鬲賵孬.</string>
+    <string name="activity_summary_detail_clear_gpx_track">賲爻丨 賲爻丕乇 GPX</string>
+    <string name="interval_five_minutes">賰賱 5 丿賯丕卅賯</string>
+    <string name="live_activity_start_your_activity">丕亘丿兀 賳卮丕胤賰</string>
+    <string name="activity_type_deep_sleep">賳賵賲 毓賲賷賯</string>
+    <string name="qhybrid_buttons_overwrite_error">禺胤兀 賮賷 丕賱賰鬲丕亘丞 賮賵賯 丕賱兀夭乇丕乇</string>
+    <string name="prefs_vibration_enable">鬲賲賰賷賳 丕賱丕賴鬲夭丕夭丕鬲</string>
+    <string name="pref_title_equalizer_normal">毓丕丿賷</string>
+    <string name="pref_title_equalizer_soft">賳丕毓賲</string>
+    <string name="reminder_time_once">%1$s貙 賲乇丞 賵丕丨丿丞</string>
+    <string name="reminder_time_every_day">%1$s貙 賰賱 賷賵賲</string>
+    <string name="reminder_every_day">賰賱 賷賵賲</string>
+    <string name="reminder_no_free_slots_title">賱丕 鬲賵噩丿 氐賮丨丕鬲 賮丕乇睾丞</string>
+    <string name="contact_delete_confirm_title">丨匕賮 噩賴丞 丕賱丕鬲氐丕賱</string>
+    <string name="reminder_time_every_week">%1$s貙 賰賱 兀爻亘賵毓</string>
+    <string name="reminder_time_every_year">%1$s貙 賰賱 爻賳丞</string>
+    <string name="title_activity_alarm_details">鬲賮丕氐賷賱 丕賱賲賳亘賴</string>
+    <string name="title_activity_contact_details">鬲賮丕氐賷賱 噩賴丞 丕賱丕鬲氐丕賱</string>
+    <string name="chart_no_data_synchronize">賱丕 鬲賵噩丿 亘賷丕賳丕鬲. 賲夭丕賲賳丞 丕賱噩賴丕夭責</string>
+    <string name="chart_no_active_data">賱賲 賷鬲賲 丕賱賰卮賮 毓賳 兀賷 兀賳卮胤丞.</string>
+    <string name="prefs_heartrate_alert_experimental_title">鬲賳亘賷賴 賲毓丿賱 賳亘囟丕鬲 丕賱賯賱亘 (丕賱禺丕乇噩賷)</string>
+    <string name="prefs_heartrate_alert_low_threshold">毓鬲亘丞 鬲賳亘賷賴 丕賳禺賮丕囟 賲毓丿賱 賳亘囟丕鬲 丕賱賯賱亘</string>
+    <string name="prefs_spo2_monitoring_title">賲乇丕賯亘丞 丕賱兀賰爻噩賷賳 賮賷 丕賱丿賲</string>
+    <string name="prefs_spo2_alert_threshold">丨丿 鬲賳亘賷賴 SPO2</string>
+    <string name="prefs_activity_monitoring_title">賲乇丕賯亘丞 丕賱賳卮丕胤</string>
+    <string name="notif_battery_low_title">亘胤丕乇賷丞 丕賱兀丿丕丞 賲賳禺賮囟丞!</string>
+    <string name="notif_battery_low_bigtext_number_of_charges">毓丿丿 丕賱卮丨賳丕鬲: %s</string>
+    <string name="prefs_charts_tabs">毓賱丕賲丕鬲 鬲亘賵賷亘 丕賱賲禺胤胤丕鬲</string>
+    <string name="devicestatus_disconnected">鬲賲 賯胤毓 丕鬲氐丕賱 丕賱噩賴丕夭!</string>
+    <string name="devicestatus_upload_completed">丕賰鬲賲賱 丕賱乇賮毓</string>
+    <string name="pbwinstallhandler_correct_hw_revision">鬲氐丨賷丨 賲乇丕噩毓丞 丕賱兀噩賴夭丞</string>
+    <string name="pref_huami_truncate_fetch_operation_timestamps_title">丕賯鬲胤丕毓 丕賱胤賵丕亘毓 丕賱夭賲賳賷丞 賱毓賲賱賷丞 丕賱噩賱亘</string>
+    <string name="live_activity_total_steps">廿噩賲丕賱賷 丕賱禺胤賵丕鬲</string>
+    <string name="live_activity_steps_per_minute_history">爻噩賱 丕賱禺胤賵丕鬲 賮賷 丕賱丿賯賷賯丞</string>
+    <string name="abstract_chart_fragment_kind_not_worn">賱賲 賷鬲賲 丕乇鬲丿丕卅賴丕</string>
+    <string name="pref_sleep_breathing_quality_monitoring">賲乇丕賯亘丞 噩賵丿丞 丕賱鬲賳賮爻 兀孬賳丕亍 丕賱賳賵賲</string>
+    <string name="prefs_enable_find_phone">鬲賮毓賷賱 \"丕賱毓孬賵乇 毓賱賶 丕賱賴丕鬲賮\"</string>
+    <string name="mi2_prefs_button_actions">廿噩乇丕亍丕鬲 丕賱夭乇</string>
+    <string name="mi2_prefs_button_press_broadcast">乇爻丕賱丞 丕賱亘孬 賱廿乇爻丕賱賴丕</string>
+    <string name="mi2_prefs_button_action_vibrate">鬲賲賰賷賳 丕賴鬲夭丕夭 丕賱賳胤丕賯</string>
+    <string name="mi2_prefs_inactivity_warnings">鬲丨匕賷乇丕鬲 毓丿賲 丕賱賳卮丕胤</string>
+    <string name="prefs_always_on_display_follow_watchface">丕賱賳賲胤 賷鬲亘毓 賵丕噩賴丞 丕賱爻丕毓丞</string>
+    <string name="prefs_password">賰賱賲丞 丕賱賲乇賵乇</string>
+    <string name="prefs_password_enabled">鬲賲 鬲賲賰賷賳 賰賱賲丞 丕賱賲乇賵乇</string>
+    <string name="mi2_prefs_do_not_disturb_end">賵賯鬲 丕賱丕賳鬲賴丕亍</string>
+    <string name="mi3_prefs_night_mode">丕賱賵囟毓 丕賱賱賷賱賷</string>
+    <string name="prefs_shortcut_cards">亘胤丕賯丕鬲 丕賱丕禺鬲氐丕乇</string>
+    <string name="mi5_prefs_workout_activity_types">兀賳賵丕毓 賳卮丕胤 丕賱鬲賲乇賷賳</string>
+    <string name="icelandic">丕賱兀賷爻賱賳丿賷丞</string>
+    <string name="lithuanian">丕賱賱賷鬲賵丕賳賷丞</string>
+    <string name="persian">丕賱賮丕乇爻賷丞</string>
+    <string name="weeksleepchart_sleep_a_month">丕賱賳賵賲 賮賷 丕賱卮賴乇</string>
+    <string name="updatefirmwareoperation_update_in_progress">鬲孬亘賷鬲 丕賱亘乇丕賲噩 丕賱孬丕亘鬲丞</string>
+    <string name="updatefirmwareoperation_firmware_not_sent">賱賲 賷鬲賲 廿乇爻丕賱 丕賱亘乇丕賲噩 丕賱孬丕亘鬲丞</string>
+    <string name="activity_prefs_calories_burnt">丕賱賴丿賮 丕賱賷賵賲賷: 丨乇賯 丕賱爻毓乇丕鬲 丕賱丨乇丕乇賷丞</string>
+    <string name="charts_legend_heartrate">賲毓丿賱 賳亘囟丕鬲 丕賱賯賱亘</string>
+    <string name="live_activity_heart_rate">賲毓丿賱 賳亘囟丕鬲 丕賱賯賱亘</string>
+    <string name="dbmanagementactivity_error_importing_db">禺胤兀 賮賷 丕爻鬲賷乇丕丿 賯丕毓丿丞 丕賱亘賷丕賳丕鬲: %1$s</string>
+    <string name="activity_db_management_autoexport_enabled_no">賱賲 賷鬲賲 鬲賲賰賷賳 丕賱鬲氐丿賷乇 丕賱鬲賱賯丕卅賷.</string>
+    <string name="activity_DB_test_export_button">賯賲 亘鬲卮睾賷賱 丕賱鬲氐丿賷乇 丕賱鬲賱賯丕卅賷 丕賱丌賳</string>
+    <string name="dbmanagementactivity_export_finished">丕賳鬲賴賶 丕賱丨匕賮</string>
+    <string name="title_activity_pebble_pairing">Pebble 丕賯鬲乇丕賳</string>
+    <string name="pref_title_setup_bt_pairing">鬲賲賰賷賳 廿賯乇丕賳 丕賱亘賱賵鬲賵孬</string>
+    <string name="mi3_night_mode_sunset">毓賳丿 睾乇賵亘 丕賱卮賲爻</string>
+    <string name="discovery_successfully_bonded">賲乇鬲亘胤 亘賭 %1$s.</string>
+    <string name="discovery_dont_pair">賱丕 鬲賯鬲乇賳</string>
+    <string name="_pebble_watch_open_on_phone">丕賮鬲丨 毓賱賶 噩賴丕夭 兀賳丿乇賵賷丿</string>
+    <string name="activity_type_not_measured">賱賲 賷鬲賲 賯賷丕爻賴丕</string>
+    <string name="activity_type_light_sleep">賳賵賲 禺賮賷賮</string>
+    <string name="activity_type_outdoor_running">丕賱噩乇賷 賮賷 丕賱賴賵丕亍 丕賱胤賱賯</string>
+    <string name="activity_type_climbing">丕賱鬲爻賱賯</string>
+    <string name="activity_type_rowing_machine">丌賱丞 丕賱鬲噩丿賷賮</string>
+    <string name="activity_type_rowing">鬲噩丿賷賮</string>
+    <string name="activity_type_indoor_fitness">丕賱賱賷丕賯丞 丕賱亘丿賳賷丞 丕賱丿丕禺賱賷丞</string>
+    <string name="activity_type_pilates">亘賷賱丕鬲賷爻</string>
+    <string name="devicetype_test">丕禺鬲亘丕乇 丕賱噩賴丕夭</string>
+    <string name="devicetype_miband3">賲賷 亘丕賳丿 3</string>
+    <string name="devicetype_miband5">賲賷 亘丕賳丿 5</string>
+    <string name="devicetype_amazfit_gts4_mini">Amazfit GTS 4 賲賷賳賷</string>
+    <string name="devicetype_amazfit_gtr3_pro">Amazfit GTR 3 亘乇賵</string>
+    <string name="devicetype_amazfit_gts2e">Amazfit GTS 2e</string>
+    <string name="devicetype_exrizu_k8">Exrizu K8</string>
+    <string name="devicetype_no1_f1">No.1 F1</string>
+    <string name="devicetype_sony_wf_sp800n">Sony WF-SP800N</string>
+    <string name="notification_gps_title">Gadgetbridge GPS</string>
+    <string name="devicetype_amazfit_vergel">Amazfit Verge Lite</string>
+    <string name="devicetype_sg2">Lemfo SG2</string>
+    <string name="menuitem_nothing">賱丕 卮賷亍</string>
+    <string name="menuitem_activity">爻噩賱 丕賱鬲賲乇賷賳</string>
+    <string name="menuitem_forecast">鬲賳亘丐 亘丕賱賲賳丕禺</string>
+    <string name="menuitem_unsupported">[睾賷乇 賲丿毓賵賲] %s</string>
+    <string name="zepp_os_watchface_business_style">兀爻賱賵亘 丕賱毓賲賱</string>
+    <string name="zepp_os_watchface_superposition">丕賱鬲乇丕賰亘</string>
+    <string name="preferences_rtl_settings">丿毓賲 賲賳 丕賱賷賲賷賳 廿賱賶 丕賱賷爻丕乇</string>
+    <string name="appwidget_sleep_alarm_widget_label">賲賳亘賴 丕賱賳賵賲</string>
+    <string name="qhybrid_buttons_overwrite_success">丕賱賰鬲丕亘丞 賮賵賯 丕賱兀夭乇丕乇</string>
+    <string name="hr_widget_active_minutes">丕賱丿賯丕卅賯 丕賱賳卮胤丞</string>
+    <string name="appwidget_text">Zzz</string>
+    <string name="hr_appname_commute">丕賱鬲賳賯賱</string>
+    <string name="pref_title_upper_button_long_press_action">丕噩乇丕亍 丕賱囟睾胤 毓賱賶 丕賱夭乇 丕賱毓賱賵賷 賱賮鬲乇丞 胤賵賷賱丞</string>
+    <string name="pref_title_lower_button_short_press_action">丕噩乇丕亍 丕賱囟睾胤 丕賱賯氐賷乇 毓賱賶 丕賱夭乇 丕賱爻賮賱賷</string>
+    <string name="pref_title_upper_button_function_double">丕賱夭乇 丕賱毓賱賵賷 賲夭丿賵噩</string>
+    <string name="pref_title_middle_button_function_double">丕賱夭乇 丕賱兀賵爻胤 賲夭丿賵噩</string>
+    <string name="pref_title_lower_button_function_double">丕賱夭乇 丕賱爻賮賱賷 賲夭丿賵噩</string>
+    <string name="prefs_button_long_press_action_selection_title">廿噩乇丕亍 丕賱囟睾胤 賱賮鬲乇丞 胤賵賷賱丞 毓賱賶 丕賱夭乇</string>
+    <string name="pref_qhybrid_save_raw_activity_files">丨賮馗 賲賱賮丕鬲 丕賱賳卮丕胤 丕賱禺丕賲</string>
+    <string name="hr_widget_last_notification">丕賱廿卮毓丕乇 丕賱兀禺賷乇</string>
+    <string name="pref_check_permission_status">丕賱鬲丨賯賯 賲賳 丨丕賱丞 丕賱廿匕賳</string>
+    <string name="minSpeed">丕賱丨丿 丕賱兀丿賳賶</string>
+    <string name="minPace">兀亘胤兀 賵鬲賷乇丞</string>
+    <string name="maxPace">兀爻乇毓 賵鬲賷乇丞</string>
+    <string name="activity_detail_show_gps_label">毓乇囟 賲爻丕乇 GPS</string>
+    <string name="activity_detail_share_gps_label">賲卮丕乇賰丞 賲爻丕乇 GPS</string>
+    <string name="activity_detail_share_raw_summary">賲卮丕乇賰丞 丕賱賲賱禺氐 丕賱禺丕賲</string>
+    <string name="activity_detail_share_raw_details">賲卮丕乇賰丞 丕賱鬲賮丕氐賷賱 丕賱禺丕賲丞</string>
+    <string name="dev_tools">兀丿賵丕鬲 丕賱鬲胤賵賷乇</string>
+    <string name="prefs_events_forwarding_title">廿噩乇丕亍丕鬲 丕賱噩賴丕夭</string>
+    <string name="activity_filter_individual_items">丕賱毓賳丕氐乇 丕賱賲丨丿丿丞 亘卮賰賱 賮乇丿賷</string>
+    <string name="sports_activity_quick_filter_last_month">丕賱卮賴乇 丕賱爻丕亘賯</string>
+    <string name="sports_activity_quick_filter_30days">30 賷賵賲丕</string>
+    <string name="sports_activity_quick_filter_select">丕賱賮鬲乇丞 丕賱夭賲賳賷丞</string>
+    <string name="fossil_hr_synced_activity_data">亘賷丕賳丕鬲 丕賱兀賳卮胤丞 丕賱賲鬲夭丕賲賳丞</string>
+    <string name="fossil_hr_auth_failed">賮卮賱 丕賱賲氐丕丿賯丞 貙 賵馗丕卅賮 賲丨丿賵丿丞</string>
+    <string name="qhybrid_title_watchface">鬲賰賵賷賳 賵丕噩賴丞 丕賱爻丕毓丞</string>
+    <string name="qhybrid_title_file_management">廿丿丕乇丞 丕賱賲賱賮丕鬲</string>
+    <string name="qhybrid_summary_calibration">賲毓丕賷乇丞 毓賯丕乇亘 丕賱爻丕毓丞</string>
+    <string name="qhybrid_calibration_1_step">1 禺胤賵丞</string>
+    <string name="watchface_dialog_widget_x_coordinate">廿丨丿丕孬賷丕鬲 X (亘丨丿 兀賯氐賶 240)</string>
+    <string name="button_watchface_settings">廿毓丿丕丿丕鬲 賵丕噩賴丞 丕賱爻丕毓丞</string>
+    <string name="button_watchface_preview">賲毓丕賷賳丞 毓賱賶 丕賱爻丕毓丞</string>
+    <string name="watchface_dialog_widget_cat_2nd_tz_widget">賵賷丿噩賷鬲 丕賱賲賳胤賯丞 丕賱夭賲賳賷丞 丕賱孬丕賳賷丞</string>
+    <string name="watchface_dialog_widget_presets">丕賱廿毓丿丕丿丕鬲 丕賱賲爻亘賯丞 賱賱賲賵囟毓</string>
+    <string name="watchface_dialog_widget_width">毓乇囟 丕賱賵賷丿噩賷鬲 (亘丕賱亘賰爻賱)</string>
+    <string name="watchface_setting_title_display_refresh_timeout">賲賴賱丞 鬲丨丿賷孬 丕賱卮丕卮丞</string>
+    <string name="watchface_setting_title_wrist_flick">賳賮囟丞 丕賱賲毓氐賲</string>
+    <string name="watchface_setting_button_toggle_backlight">鬲卮睾賷賱 丕賱廿囟丕亍丞 丕賱禺賱賮賷丞</string>
+    <string name="watchface_setting_title_power_saving">鬲賵賮賷乇 丕賱胤丕賯丞</string>
+    <string name="hybridhr_widget_bg_double_circle">丿丕卅乇丞 賲夭丿賵噩丞</string>
+    <string name="prefs_sleep_time_label">鬲丨丿賷丿 爻丕毓丕鬲 丕賱賳賵賲</string>
+    <string name="prefs_touch_lock">賯賮賱 丕賱賱賲爻</string>
+    <string name="battery_case">亘胤丕乇賷丞 丕賱毓賱亘丞</string>
+    <string name="audio_codec">亘乇賳丕賲噩 鬲乇賲賷夭 丕賱氐賵鬲</string>
+    <string name="pref_title_touch_voice_assistant">丕賱賲爻丕毓丿 丕賱氐賵鬲賷</string>
+    <string name="pref_ambient_sound_tone_summary">賲賳 丕賱賳丕毓賲 廿賱賶 丕賱賵丕囟丨</string>
+    <string name="prefs_noise_control">丕賱鬲丨賰賲 賮賷 丕賱囟賵囟丕亍</string>
+    <string name="sony_ambient_sound_level">賲爻鬲賵賶 丕賱氐賵鬲 丕賱賲丨賷胤賷</string>
+    <string name="sony_anc_optimize_confirmation_title">賲丨爻賳 廿賱睾丕亍 丕賱囟賵囟丕亍</string>
+    <string name="sony_surround_mode">丕賱賵囟毓 丕賱賲丨賷胤賷</string>
+    <string name="sony_equalizer_preset_custom_2">賲禺氐氐 2</string>
+    <string name="sony_equalizer_band_400">400</string>
+    <string name="sony_automatic_power_off">廿賷賯丕賮 丕賱鬲卮睾賷賱 丕賱鬲賱賯丕卅賷</string>
+    <string name="sony_automatic_power_off_30_min">30 丿賯賷賯丞</string>
+    <string name="pref_media_play">鬲卮睾賷賱 丕賱賵爻丕卅胤</string>
+    <string name="pref_media_volumedown">禺賮囟 賲爻鬲賵賶 丕賱氐賵鬲</string>
+    <string name="pref_media_forward">鬲禺胤賷 廿賱賶 丕賱兀賲丕賲</string>
+    <string name="pref_device_action_fitness_app_control_stop">廿賷賯丕賮 鬲鬲亘毓 鬲胤亘賷賯 丕賱賱賷丕賯丞 丕賱亘丿賳賷丞</string>
+    <string name="watchface_dialog_pre_setting_position">賵囟毓 丕賱廿毓丿丕丿 丕賱賲爻亘賯 毓賱賶 %s</string>
+    <string name="auto_reconnect_ble_title">廿毓丕丿丞 丕賱丕鬲氐丕賱 丕賱鬲賱賯丕卅賷 亘丕賱噩賴丕夭</string>
+    <string name="steps_streaks">禺胤賵胤 丕賱禺胤賵丕鬲</string>
+    <string name="step_streak_total">丕賱賲噩賲賵毓</string>
+    <string name="steps_streaks_achievement_rate">丕賱廿賳噩丕夭
+\n丕賱賲毓丿賱</string>
+    <string name="step_streaks_achievements_sharing_title">廿賳噩丕夭丕鬲 丕賱禺胤賵丕鬲</string>
+    <string name="devicetype_flipper_zero">Flipper zero</string>
+    <string name="intent_api_allow_activity_sync_title">丕賱爻賲丕丨 亘鬲卮睾賷賱 賲夭丕賲賳丞 丕賱賳卮丕胤</string>
+    <string name="intent_api_allow_trigger_export_title">丕賱爻賲丕丨 亘鬲氐丿賷乇 賯丕毓丿丞 丕賱亘賷丕賳丕鬲</string>
+    <string name="devicetype_super_cars">爻亘丕賯 丕賱氐丿賮丞</string>
+    <string name="pref_title_lock_unlock">賯賮賱</string>
+    <string name="wifi_hotspot">賳賯胤丞 丕鬲氐丕賱 Wi-Fi</string>
+    <string name="wifi_hotspot_configuration">鬲賰賵賷賳 賳賯胤丞 丕鬲氐丕賱 Wi-Fi</string>
+    <string name="wifi_ssid">SSID</string>
+    <string name="prefs_notifications_enable">鬲賲賰賷賳 丕賱廿卮毓丕乇丕鬲</string>
+    <string name="prefs_equalizer_summary">鬲賲賰賷賳 兀賵 鬲毓胤賷賱 丕賱賲毓丕丿賱</string>
+    <string name="sony_equalizer_preset_bright">爻丕胤毓</string>
+    <string name="controlcenter_folder_name">丕爻賲 丕賱賲噩賱丿:</string>
+    <string name="step_streak_ongoing">噩丕乇賷丞</string>
+    <string name="devicetype_amazfit_bips">丕賲丕夭賮賷鬲 Bip S</string>
+    <string name="pref_title_chart_sleep_rolling_24_hour">賳胤丕賯 丕賱賳賵賲</string>
+    <string name="pref_chart_sleep_rolling_24_on">丕賱 24 爻丕毓丞 丕賱賲丕囟賷丞</string>
+    <string name="kind_gps_almanac">鬲賯賵賷賲 GPS</string>
+    <string name="devicetype_amazfit_gts2_mini">Amazfit GTS 2 賲賷賳賷</string>
+    <string name="devicetype_vibratissimo">Vibratissimo</string>
+    <string name="preferences_led_color">賱賵賳 LED</string>
+    <string name="preferences_fm_frequency">鬲乇丿丿 FM</string>
+    <string name="appwidget_setting_alarm">囟亘胤 丕賱賲賳亘賴 賱賭 %1$02d: %2$02d</string>
+    <string name="mode_configuration">鬲賰賵賷賳 丕賱賵囟毓</string>
+    <string name="widget_listing_label">丕賱丨丕賱丞 賵丕賱賲賳亘賴丕鬲</string>
+    <string name="currentWorkoutLoad">鬲丨賲賷賱 丕賱鬲賲乇賷賳</string>
+    <string name="maxCadence">丕賯氐賶 廿賷賯丕毓</string>
+    <string name="prefs_events_forwarding_action_title">鈥簇嘿娰� 丕噩乇丕亍</string>
+    <string name="sports_activity_quick_filter_7days">7 兀賷丕賲</string>
+    <string name="prefs_key_vibration">丕賴鬲夭丕夭 丕賱賲賮鬲丕丨</string>
+    <string name="prefs_operating_sounds">兀氐賵丕鬲 丕賱鬲卮睾賷賱</string>
+    <string name="prefs_fake_ring_duration">乇賳賷賳 賲爻鬲賲乇 賲夭賷賮</string>
+    <string name="prefs_autoremove_message">廿夭丕賱丞 廿卮毓丕乇丕鬲 丕賱乇爻丕卅賱 丕賱賯氐賷乇丞 鬲賱賯丕卅賷丕</string>
+    <string name="pref_title_equalizer_bass_boost">鬲毓夭賷夭 丕賱噩賴賷乇</string>
+    <string name="prefs_equalizer">丕賱賲毓丕丿賱</string>
+    <string name="prefs_autoheartrate_interval">鬲乇丿丿 丕賱賯賷丕爻丕鬲</string>
+    <string name="prefs_dolby_summary">廿毓丿丕丿 丿賵賱亘賷 丕賱賲爻亘賯 賱賲毓丕丿賱丞 丕賱氐賵鬲</string>
+    <string name="sony_anc_optimize_title">鬲丨爻賷賳</string>
+    <string name="sony_sound_position_off">丕賷賯丕賮</string>
+    <string name="sony_sound_position_front_right">丕賱賷賲賷賳 丕賱丕賲丕賲賷</string>
+    <string name="sony_surround_mode_arena">丕賱爻丕丨賴</string>
+    <string name="sony_surround_mode_club">賳丕丿</string>
+    <string name="sony_equalizer_preset_mellow">賲鬲毓丕丿賱</string>
+    <string name="sony_anc_optimizer_status_starting">亘丿亍鈥�</string>
+    <string name="error_deleting_device">禺胤兀 賮賷 丨匕賮 丕賱噩賴丕夭: %s</string>
+    <string name="controlcenter_add_new_folder">廿囟丕賮丞 賲噩賱丿 噩丿賷丿</string>
+    <string name="controlcenter_unset_folder">賲噩賱丿 睾賷乇 賲丨丿丿</string>
+    <string name="devicetype_asteroidos">AsteroidOS</string>
+    <string name="pref_screen_notification_profile_low_power">鬲丨匕賷乇 丕賳禺賮丕囟 丕賱胤丕賯丞</string>
+    <string name="title_activity_charts">丕賱賳卮丕胤 賵丕賱賳賵賲</string>
+    <string name="reminder_once">賲乇丞</string>
+    <string name="average">丕賱賲鬲賵爻胤: %1$s</string>
+    <string name="simplified_chinese">丕賱氐賷賳賷丞 丕賱賲亘爻胤丞</string>
+    <string name="title_activity_watch9_calibration">Watch 9 賲毓賷丕乇丞</string>
+    <string name="language_and_region_prefs">廿毓丿丕丿丕鬲 丕賱賱睾丞 賵丕賱賲賳胤賯丞</string>
+    <string name="pref_title_middle_button_function_short">丕賱夭乇 丕賱兀賵爻胤 丕賱賯氐賷乇</string>
+    <string name="prefs_events_forwarding_wokeup">毓賳丿 丕賱丕爻鬲賷賯丕馗</string>
+    <string name="pref_header_auto_fetch">丕賱噩賱亘 丕賱鬲賱賯丕卅賷</string>
+    <string name="notif_battery_low_percent">%1$s 丕賱亘胤丕乇賷丞 丕賱賲鬲亘賯賷丞: %2$s侏侏</string>
+    <string name="overslept">丕賱賳賵賲 丕賱夭丕卅丿: %1$s</string>
+    <string name="you_did_not_sleep">兀賳鬲 賱賲 鬲賳賲</string>
+    <string name="devicetype_miband">Mi 爻賵丕乇</string>
+    <string name="discover_unsupported_devices">丕賰鬲卮丕賮 丕賱兀噩賴夭丞 睾賷乇 丕賱賲丿毓賵賲丞</string>
+    <string name="watchface_dialog_widget_y_coordinate">廿丨丿丕孬賷丕鬲 Y (亘丨丿 兀賯氐賶 240)</string>
+    <string name="prefs_autoheartrate_measurement">賯賷丕爻丕鬲 賲毓丿賱 賳亘囟丕鬲 丕賱賯賱亘 丕賱鬲賱賯丕卅賷丞</string>
+    <string name="pref_title_touch_spotify">爻亘賵鬲賷賮賷</string>
+    <string name="pref_switch_noise_control">鬲亘丿賷賱 丕賱鬲丨賰賲 賮賷 丕賱囟賵囟丕亍</string>
+    <string name="sony_anc_optimizer_status_wearing_condition">賯賷丕爻 丨丕賱丞 丕賱丕乇鬲丿丕亍鈥�</string>
+    <string name="sony_speak_to_chat_timeout_long">胤賵賷賱 (1 丿)</string>
+    <string name="watchface_dialog_widget_update_timeout">賲賴賱丞 丕賱鬲丨丿賷孬 亘丕賱丿賯丕卅賯</string>
+    <string name="devicetype_galaxybuds_2">Galaxy Buds2</string>
+    <string name="prefs_galaxy_touch_options">禺賷丕乇丕鬲 丕賱賱賲爻</string>
+    <string name="left_earbud">爻賲丕毓丞 丕賱兀匕賳 丕賱賷爻乇賶</string>
+    <string name="right_earbud">爻賲丕毓丞 丕賱兀匕賳 丕賱賷賲賳賶</string>
+    <string name="prefs_voice_detect">賰卮賮 丕賱氐賵鬲</string>
+    <string name="pref_switch_controls_anc_off">廿賱睾丕亍 丕賱囟賵囟丕亍 鈫� 鈫� 廿賷賯丕賮</string>
+    <string name="sony_equalizer_preset_relaxed">丕爻鬲乇禺丕亍</string>
+    <string name="sony_equalizer_preset_off">丕賷賯丕賮</string>
+    <string name="sony_speak_to_chat_sensitivity_standard">毓丕丿賷</string>
+    <string name="sony_equalizer_clear_bass">噩賴賷乇 賵丕囟丨</string>
+    <string name="sony_ambient_sound_control_button_mode_as_off">丕賱氐賵鬲 丕賱賲丨賷胤賷貙 廿賷賯丕賮</string>
+    <string name="sony_ambient_sound_control_button_mode_nc_off">廿賱睾丕亍 丕賱囟賵囟丕亍貙 廿賷賯丕賮</string>
+    <string name="pref_screen_auto_brightness_title">爻胤賵毓 鬲賱賯丕卅賷</string>
+    <string name="devicetype_vesc">VESC</string>
+    <string name="pref_button_action_disabled">賲毓胤賱</string>
+    <string name="watchface_dialog_widget_timeout_show_circle">廿馗賴丕乇 丕賱丿丕卅乇丞 毓賳丿 丕賳鬲賴丕亍 丕賱賲賴賱丞</string>
+    <string name="pref_media_volumeup">乇賮毓 賲爻鬲賵賶 丕賱氐賵鬲</string>
+    <string name="pref_device_action_fitness_app_control_toggle">鬲亘丿賷賱 鬲鬲亘毓 鬲胤亘賷賯 丕賱賱賷丕賯丞 丕賱亘丿賳賷丞</string>
+    <string name="steps_streaks_since_date">賲賳匕 %s</string>
+    <string name="step_streaks_achievements_sharing_message">廿賳噩丕夭丕鬲賷 丕賱賷賵賲賷丞 賱賱禺胤賵丕鬲!</string>
+    <string name="prefs_hourly_chime">乇賳賷賳 賰賱 爻丕毓丞</string>
+    <string name="supercars_lights_label">丕囟賵丕亍</string>
+    <string name="pref_header_sony_equalizer_bands">丕賱賮乇賯丞</string>
+    <string name="installing_binary_d_d">鬲孬亘賷鬲 孬賳丕卅賷 %1$d/%2$d</string>
+    <string name="reminder_time">丕賱賵賯鬲</string>
+    <string name="miband_prefs_fitness_goal">賴丿賮 丕賱禺胤賵丞 丕賱賷賵賲賷</string>
+    <string name="activity_list_summary_active_steps">禺胤賵丕鬲 賳卮胤丞</string>
+    <string name="lack_of_sleep">賯賱丞 丕賱賳賵賲: %1$s</string>
+    <string name="devicestatus_connected">丕賱噩賴丕夭 賲鬲氐賱</string>
+    <string name="pbwinstallhandler_incorrect_hw_revision">賲乇丕噩毓丞 丕賱兀噩賴夭丞 睾賷乇 賲鬲胤丕亘賯丞!</string>
+    <string name="miband_fwinstaller_incompatible_version">丕賱亘乇丕賲噩 丕賱孬丕亘鬲丞 睾賷乇 賲鬲賵丕賮賯賴</string>
+    <string name="lefun_prefs_antilost_title">賲賰丕賮丨丞 丕賱禺爻丕乇丞</string>
+    <string name="activity_type_elliptical_trainer">賲丿乇亘 亘賷囟丕賵賷 丕賱卮賰賱</string>
+    <string name="kind_gps_cep">鬲氐丨賷丨 禺胤兀 GPS</string>
+    <string name="devicetype_amazfit_cor">Amazfit Cor</string>
+    <string name="devicetype_y5">Y5</string>
+    <string name="meters">賲</string>
+    <string name="sony_automatic_power_off_1_hour">1 爻丕毓丞</string>
+    <string name="devicetype_lefun">Lefun</string>
+    <string name="no_data">賱丕 鬲賵噩丿 亘賷丕賳丕鬲</string>
+    <string name="ftp_server_status">丨丕賱丞 禺丕丿賲 FTP</string>
+    <string name="sony_automatic_power_off_off">賱丕 鬲賯賲 亘廿賷賯丕賮 丕賱鬲卮睾賷賱</string>
+    <string name="georgian">丕賱噩賵乇噩賷丞</string>
+    <string name="activity_type_pingpong">亘賷賳噩 亘賵賳噩</string>
+    <string name="devicetype_amazfit_x">Amazfit X</string>
+    <string name="devicetype_amazfit_bip_lite">丕賲丕夭賮賷鬲 Bip 賱丕賷鬲</string>
+    <string name="devicetype_xwatch">XWatch</string>
+    <string name="devicetype_itag">iTag</string>
+    <string name="devicetype_amazfit_gts">Amazfit GTS</string>
+    <string name="share_log">賲卮丕乇賰丞 丕賱爻噩賱</string>
+    <string name="quick_alarm">賲賳亘賴 爻乇賷毓</string>
+    <string name="error_setting_alias">禺胤兀 賮賷 鬲毓賷賷賳 丕賱丕爻賲 丕賱賲爻鬲毓丕乇:</string>
+    <string name="device_unsupported">睾賷乇 賲丿毓賵賲</string>
+    <plurals name="widget_alarm_target_hours">
+        <item quantity="zero">%d 爻丕毓丞</item>
+        <item quantity="one">%d 爻丕毓丞</item>
+        <item quantity="two">%d爻丕毓鬲丕賳</item>
+        <item quantity="few">%d爻丕毓丕鬲</item>
+        <item quantity="many">%d爻丕毓丕鬲</item>
+        <item quantity="other">%d 爻丕毓丕鬲</item>
+    </plurals>
+    <string name="watchface_setting_display_refresh_full">鬲丨丿賷孬 賰丕賲賱 (亘丕賱丿賯丕卅賯)</string>
+    <string name="sony_ambient_sound_noise_cancelling">廿賱睾丕亍 丕賱囟賵囟丕亍</string>
+    <string name="controlcenter_set_preferences">鬲毓賷賷賳 丕賱鬲賮囟賷賱丕鬲</string>
+    <string name="pref_workout_detection_enabled">鬲賲 鬲賲賰賷賳 丕賱賰卮賮</string>
+    <string name="pref_workout_detection_ask_first">丕爻兀賱賳賷 兀賵賱丕</string>
+    <string name="pref_workout_detection_time">丕賱丿賯丕卅賯 丕賱賮毓賱賷丞 賯亘賱 丕賱賰卮賮</string>
+    <string name="pref_sleep_mode_smart_enable_title">丕賱鬲賲賰賷賳 丕賱匕賰賷</string>
+    <string name="pref_title_auto_export_interval">丕賱賮丕氐賱 丕賱夭賲賳賷 賱賱鬲氐丿賷乇</string>
+    <string name="discovery_bluetooth_scan">賮丨氐 亘賱賵鬲賵孬:</string>
+    <string name="discovery_bluetooth_le_scan">賮丨氐 亘賱賵鬲賵孬 LE:</string>
+    <string name="pairing">丕賱丕賯鬲乇丕賳 賲毓 %s鈥�</string>
+    <string name="vibration_profile_alarm_clock">爻丕毓丞 丕賱賲賳亘賴</string>
+    <string name="pref_screen_notification_profile_sms">廿卮毓丕乇 丕賱乇爻丕卅賱 丕賱賯氐賷乇丞</string>
+    <string name="pref_screen_notification_profile_find_device">丕賱毓孬賵乇 毓賱賶 噩賴丕夭</string>
+    <string name="pref_screen_notification_profile_incoming_call">廿卮毓丕乇 丕賱賲賰丕賱賲丕鬲 丕賱賵丕乇丿丞</string>
+    <string name="pref_screen_notification_profile_schedule">丕賱噩丿賵賱</string>
+    <string name="pref_screen_notification_idle_alerts">鬲賳亘賷賴丕鬲 丕賱禺賲賵賱</string>
+    <string name="miband_fwinstaller_compatible_version">賳爻禺丞 賲鬲賵丕賮賯丞</string>
+    <string name="miband_fwinstaller_untested_version">賳爻禺丞 睾賷乇 賲禺鬲亘乇丞!</string>
+    <string name="pbwinstallhandler_app_item">%1$s (%2$s)</string>
+    <string name="overstep">鬲噩丕賵夭: %1$d</string>
+    <string name="weeksteps_today_steps_description">禺胤賵丕鬲 丕賱賷賵賲 貙 丕賱賴丿賮: %1$s</string>
+    <string name="miband2_prefs_dateformat">鬲賳爻賷賯 丕賱鬲丕乇賷禺</string>
+    <string name="mi2_dnd_automatic">鬲賱賯丕卅賷 (賰卮賮 丕賱賳賵賲)</string>
+    <string name="discovery_pair_title">丕賱丕賯鬲乇丕賳 賲毓 %1$s責</string>
+    <string name="activity_type_strength_training">鬲丿乇賷亘 丕賱賯賵丞</string>
+    <string name="activity_type_dance">丕賱乇賯氐</string>
+    <string name="activity_type_unknown">賳卮丕胤 睾賷乇 賲毓乇賵賮</string>
+    <string name="activity_summary_detail">鬲賮丕氐賷賱 丕賱賳卮丕胤 丕賱乇賷丕囟賷</string>
+    <string name="devicetype_amazfit_gts4">Amazfit GTS 4</string>
+    <string name="devicetype_unknown">噩賴丕夭 睾賷乇 賲毓乇賵賮</string>
+    <string name="devicetype_amazfit_band5">丕賲丕夭賮賷鬲 Band 5</string>
+    <string name="devicetype_amazfit_band7">Amazfit Band 7</string>
+    <string name="devicetype_amazfit_neo">Amazfit Neo</string>
+    <string name="devicetype_watchxplus">Watch X Plus</string>
+    <string name="devicetype_mykronoz_zetime">MyKronoz ZeTime</string>
+    <string name="devicetype_id115">ID115</string>
+    <string name="devicetype_watch9">Watch 9</string>
+    <string name="devicetype_watchx">Watch X</string>
+    <string name="devicetype_mijia_lywsd02">Mijia Smart Clock</string>
+    <string name="menuitem_bluetooth">丕賱亘賱賵鬲賵孬</string>
+    <string name="pref_title_lower_button_function_short">丕賱夭乇 丕賱爻賮賱賷 賯氐賷乇</string>
+    <string name="button_watchface_add_widget">廿囟丕賮丞 丕賱賵賷丿噩賷鬲</string>
+    <string name="button_watchface_select_image">鬲睾賷賷乇 氐賵乇丞 丕賱禺賱賮賷丞</string>
+    <string name="activity_prefs_year_birth">爻賳丞 丕賱賲賷賱丕丿</string>
+    <string name="maxStride">賲丕賰爻 爻鬲乇丕賷丿</string>
+    <string name="averageStride">賲鬲賵爻胤 丕賱禺胤賵丞</string>
+    <string name="activity_filter_filter_title">丕賱賲乇卮丨</string>
+    <string name="activity_filter_name_contains">鬲爻賲賷賴</string>
+    <string name="activity_filter_apply_filter">鬲胤亘賷賯 毓丕賲賱 丕賱鬲氐賮賷丞</string>
+    <string name="activity_summaries_all_activities">噩賲賷毓 丕賱兀賳卮胤丞</string>
+    <string name="sports_activity_quick_filter_this_week">賴匕丕 丕賱丕爻亘賵毓</string>
+    <string name="sports_activity_quick_filter_last_week">丕賱兀爻亘賵毓 丕賱爻丕亘賯</string>
+    <string name="watchface_dialog_widget_cat_position">丕賱賲賵囟毓</string>
+    <string name="watchface_dialog_widget_cat_generic">賳賵毓賷</string>
+    <string name="watchface_dialog_widget_cat_custom_widget">丕賱賵賷丿噩賷鬲 丕賱賲禺氐氐丞</string>
+    <string name="devicetype_galaxybuds_live">Galaxy Buds Live</string>
+    <string name="prefs_dolby_mode">賵囟毓 丿賵賱亘賷</string>
+    <string name="prefs_active_noise_cancelling">廿賱睾丕亍 丕賱囟賵囟丕亍 丕賱賳卮胤</string>
+    <string name="sony_sound_position_rear_right">禺賱賮 賷賲賷賳</string>
+    <string name="sony_surround_mode_concert_hall">賯丕毓丞 丕賱丨賮賱丕鬲 丕賱賲賵爻賷賯賷丞</string>
+    <string name="devicetype_binary_sensor">Binary sensor</string>
+    <string name="pref_title_expose_hr">賵氐賵賱 賱賲毓丿賱 丕賱賳亘囟 賮賷 丕賱賵賯鬲 丕賱賮毓賱賷 賱胤乇賮 孬丕賱孬</string>
+    <string name="title_activity_LenovoWatch_calibration">賲毓丕賷乇丞 Watch X Plus</string>
+    <string name="pref_title_sensors_altitude">賲毓丕賷乇丞 丕賱丕乇鬲賮丕毓</string>
+    <string name="pref_workout_detection_sensitivity">丕賱丨爻丕爻賷丞</string>
+    <string name="pref_summary_auto_export_interval">鬲氐丿賷乇 賰賱 %d 爻丕毓丞</string>
+    <string name="pref_auto_fetch">丕賱噩賱亘 丕賱鬲賱賯丕卅賷 賱亘賷丕賳丕鬲 丕賱賳卮丕胤</string>
+    <string name="discovery_scanning_intensity">賰孬丕賮丞 丕賱賮丨氐</string>
+    <string name="pref_disable_new_ble_scanning">鬲毓胤賷賱 賮丨氐 BLE 丕賱噩丿賷丿</string>
+    <string name="not_connected">睾賷乇 賲鬲氐賱</string>
+    <string name="test_notification">廿卮毓丕乇 丕賱丕禺鬲亘丕乇</string>
+    <string name="discovery_stop_scanning">廿賷賯丕賮 丕賱賮丨氐</string>
+    <string name="discovery_start_scanning">丕亘丿兀 丕賱丕賰鬲卮丕賮</string>
+    <string name="device_with_rssi">%1$s (%2$s)</string>
+    <string name="title_activity_android_pairing">廿賯乇丕賳 丕賱噩賴丕夭</string>
+    <string name="title_activity_mi_band_pairing">賯賲 亘廿賯乇丕賳 Mi Band 丕賱禺丕氐 亘賰</string>
+    <string name="candidate_item_device_image">氐賵乇丞 丕賱噩賴丕夭</string>
+    <string name="pref_header_vibration_count">毓丿丿 丕賱丕賴鬲夭丕夭丕鬲</string>
+    <string name="title_activity_sleepmonitor">賲乇丕賯亘丞 丕賱賳賵賲</string>
+    <string name="pref_write_logfiles">賰鬲丕亘丞 賲賱賮丕鬲 丕賱爻噩賱</string>
+    <string name="control_center_find_lost_device">丕賱亘丨孬 毓賳 丕賱噩賴丕夭 丕賱賲賮賯賵丿</string>
+    <string name="pref_world_clocks_title">丕賱爻丕毓丕鬲 丕賱毓丕賱賲賷丞</string>
+    <string name="controlcenter_start_configure_alarms">鬲賰賵賷賳 丕賱賲賳亘賴丕鬲</string>
+    <string name="devicestatus_upload_validating">噩丕乇賺 丕賱鬲丨賯賯 賲賳 氐丨丞 丕賱乇賮毓</string>
+    <string name="devicestatus_upload_aborted">鬲賲 廿丨亘丕胤 丕賱乇賮毓!</string>
+    <string name="devicestatus_upload_failed">賮卮賱 丕賱乇賮毓</string>
+    <string name="pbw_installhandler_pebble_firmware">Pebble 賳馗丕賲 鬲卮睾賷賱 %1$s</string>
+    <string name="heart_rate">賲毓丿賱 賳亘囟丕鬲 丕賱賯賱亘</string>
+    <string name="fwappinstaller_connection_state">丕賱丕鬲氐丕賱 亘丕賱噩賴丕夭: %1$s</string>
+    <string name="no_limit">亘賱丕 丨丿賵丿</string>
+    <string name="seconds_5">5 孬賵丕賳</string>
+    <string name="seconds_6">6 孬賵丕賳</string>
+    <string name="prefs_find_phone">丕賱毓孬賵乇 毓賱賶 丕賱賴丕鬲賮</string>
+    <string name="lefun_prefs_hydration_reminder_title">鬲匕賰賷乇 亘丕賱鬲乇胤賷亘</string>
+    <string name="lefun_prefs_interface_language_title">賱睾丞 丕賱賵丕噩賴丞</string>
+    <string name="croatian">丕賱賰乇賵丕鬲賷丞</string>
+    <string name="scandinavian">丕賱丕爻賰賳丿賳丕賮賷丞</string>
+    <string name="weekstepschart_steps_a_month">毓丿丿 丕賱禺胤賵丕鬲 賮賷 丕賱卮賴乇</string>
+    <string name="pref_chart_sleep_rolling_24_off">賲賳 丕賱馗賴乇 廿賱賶 丕賱馗賴乇</string>
+    <string name="waiting_for_reconnect">賮賷 丕賳鬲馗丕乇 廿毓丕丿丞 丕賱丕鬲氐丕賱</string>
+    <string name="activity_prefs_chart_min_session_length">兀賯賱 賲丿丞 賱賱賳卮丕胤 (亘丕賱丿賯丕卅賯)</string>
+    <string name="authentication_required">丕賱賲氐丕丿賯丞 賲胤賱賵亘丞</string>
+    <string name="activity_type_pool_swimming">丕賱爻亘丕丨丞 賮賷 丕賱賲爻亘丨</string>
+    <string name="activity_type_indoor_cycling">乇賰賵亘 丕賱丿乇丕噩丕鬲 賮賷 丕賱兀賲丕賰賳 丕賱賲睾賱賯丞</string>
+    <string name="activity_type_volleyball">丕賱賰乇丞 丕賱胤丕卅乇丞</string>
+    <string name="activity_summary_detail_select_gpx_track">丕禺鬲賷丕乇 賲爻丕乇 GPX</string>
+    <string name="activity_summary_detail_editing_gpx_track">鬲丨乇賷乇 賲爻丕乇 GPX 丕賱賲乇鬲亘胤</string>
+    <string name="kind_agps_bundle">丨夭賲丞 AGPS</string>
+    <string name="devicetype_miscale2">Mi Scale 2</string>
+    <string name="devicetype_roidmi3">Roidmi 3</string>
+    <string name="devicetype_sony_wh_1000xm4">Sony WH-1000XM4</string>
+    <string name="devicetype_pinetime_jf">PineTime (JF Firmware)</string>
+    <string name="devicetype_sonyswr12">Sony SWR12</string>
+    <string name="devicetype_smaq2oss">SMA-Q2 OSS</string>
+    <string name="devicetype_fitpro">FitPro</string>
+    <string name="devicetype_domyos_t540">Domyos T540</string>
+    <string name="devicetype_sony_wh_1000xm3">Sony WH-1000XM3</string>
+    <string name="menuitem_vo2_max">VO鈧� Max</string>
+    <string name="menuitem_total_workout">廿噩賲丕賱賷 丕賱鬲賲乇賷賳</string>
+    <string name="pref_invalid_frequency_title">鬲乇丿丿 睾賷乇 氐丕賱丨</string>
+    <string name="toast_notification_filter_saved_successfully">鬲賲 丨賮馗 毓丕賲賱 鬲氐賮賷丞 丕賱廿卮毓丕乇丕鬲</string>
+    <string name="filter_mode">賵囟毓 丕賱鬲氐賮賷丞</string>
+    <string name="qhybrid_vibration_strength">賯賵丞 丕賱丕賴鬲夭丕夭:</string>
+    <string name="qhybrid_overwrite_buttons">丕賱賰鬲丕亘丞 賮賵賯 丕賱夭乇</string>
+    <string name="widget_set_alarm_after">囟亘胤 丕賱賲賳亘賴 亘毓丿:</string>
+    <string name="pref_title_middle_button_function_long">丕賱夭乇 丕賱兀賵爻胤 胤賵賷賱</string>
+    <string name="qhybrid_offset_timezone">廿夭丕丨丞 丕賱賲賳胤賯丞 丕賱夭賲賳賷丞 亘賲賯丿丕乇</string>
+    <string name="sonyswr12_settings_stamina">賵囟毓 鬲賵賮賷乇 丕賱胤丕賯丞 賯賷丿 丕賱鬲卮睾賷賱</string>
+    <string name="flatSeconds">賲爻胤丨</string>
+    <string name="swolfIndex">SWOLF</string>
+    <string name="averageStrokeDistance">賲鬲賵爻胤 賲爻丕賮丞 丕賱爻鬲乇賵賰</string>
+    <string name="baseAltitude">丕乇鬲賮丕毓 丕賱賯丕毓丿丞</string>
+    <string name="spm">禺胤賵丕鬲 / 丿賯賷賯丞</string>
+    <string name="addto_filter">廿囟丕賮丞 廿賱賶 丕賱鬲氐賮賷丞</string>
+    <string name="sports_activity_confirm_delete_title">丨匕賮 兀賳卮胤丞 %d</string>
+    <string name="prefs_autolight">囟賵亍 鬲賱賯丕卅賷</string>
+    <string name="medley">賲賷丿賱賷</string>
+    <string name="devicetype_nut_mini">Nut mini</string>
+    <string name="qhybrid_calibration_100_steps">100 禺胤賵丞</string>
+    <string name="pref_title_developer_settings">廿毓丿丕丿丕鬲 丕賱賲胤賵乇</string>
+    <string name="qhybrid_pref_title_external_intents">丕賱爻賲丕丨 亘賳賵丕賷丕 禺丕乇噩賷丞 禺胤賷乇丞</string>
+    <string name="devicetype_galaxybuds">Galaxy Buds</string>
+    <string name="prefs_game_mode">賵囟毓 丕賱賱毓亘丞</string>
+    <string name="hybridhr_widget_bg_thin_circle">丿丕卅乇丞 乇賯賷賯丞</string>
+    <string name="prefs_ambient_volume">丕賱氐賵鬲 丕賱賲丨賷胤賷</string>
+    <string name="prefs_touch_lock_buds2">毓賳丕氐乇 丕賱鬲丨賰賲 亘丕賱賱賲爻</string>
+    <string name="prefs_touch_lock_summary">鬲毓胤賷賱 兀丨丿丕孬 丕賱賱賲爻</string>
+    <string name="prefs_seamless_connection_switch_title">丕賱鬲亘丿賷賱 丕賱爻賱爻 賱賱廿鬲氐丕賱</string>
+    <string name="pref_title_touch_volume">丕賱賲爻鬲賵賶</string>
+    <string name="pref_title_touch_spotify_official_app">爻亘賵鬲賷賮丕賷 (丕賱鬲胤亘賷賯 丕賱乇爻賲賷 賮賯胤)</string>
+    <string name="pref_title_touch_ambient">丕賱氐賵鬲 丕賱賲丨賷胤賷</string>
+    <string name="pref_header_heartrate_alerts">鬲賳亘賷賴丕鬲 賲毓丿賱 賳亘囟丕鬲 丕賱賯賱亘</string>
+    <string name="sony_ambient_sound_off">丕賷賯丕賮</string>
+    <string name="sony_ambient_sound_wind_noise_reduction">丕賱丨丿 賲賳 囟賵囟丕亍 丕賱乇賷丕丨</string>
+    <string name="sony_ambient_sound_ambient_sound">丕賱氐賵鬲 丕賱賲丨賷胤賷</string>
+    <string name="sony_equalizer_band_1000">1k</string>
+    <string name="sony_speak_to_chat_sensitivity_low">賲賳禺賮囟</string>
+    <string name="sony_anc_optimizer_status_finished">噩丕乇賷 丕賱廿賳賴丕亍 鈥�</string>
+    <string name="pref_media_next">丕賱賲爻丕乇 丕賱鬲丕賱賷</string>
+    <string name="pref_media_rewind">鬲禺胤賷 賱賱禺賱賮</string>
+    <string name="single_tap">賳賯乇丞 賵丕丨丿丞</string>
+    <string name="connection_over_ble">丕鬲氐丕賱 毓亘乇 BLE</string>
+    <string name="activity_type_swimming_openwater">丕賱爻亘丕丨丞 (丕賱賲賷丕賴 丕賱賲賮鬲賵丨丞)</string>
+    <string name="sony_automatic_power_off_5_min">5 丿賯丕卅賯</string>
+    <string name="sony_speak_to_chat">丕賱鬲丨丿孬 廿賱賶 丕賱丿乇丿卮丞</string>
+    <string name="prefs_notifications_and_calls_continious_ring">廿卮毓丕乇 兀孬賳丕亍 乇賳賷賳 丕賱賴丕鬲賮</string>
+    <string name="pref_summary_notifications_and_calls_title_shake_reject">鬲賰乇丕乇 毓賲賱 夭乇 丕賱爻丕毓丞</string>
+    <string name="pref_header_sensors_calibration">賲毓丕賷乇丞 丕賱賲爻鬲卮毓乇</string>
+    <string name="pref_vibrate_for_alert">丕賱丕賴鬲夭丕夭 賱賱鬲賳亘賷賴</string>
+    <string name="pref_screen_notification_profile_missed_call">廿卮毓丕乇 丕賱賲賰丕賱賲丕鬲 丕賱賮丕卅鬲丞</string>
+    <string name="pref_cache_weather">賲毓賱賵賲丕鬲 丕賱胤賯爻 賮賷 匕丕賰乇丞 丕賱鬲禺夭賷賳 丕賱賲丐賯鬲</string>
+    <string name="pref_screen_notification_profile_generic_social">丕賱卮亘賰丞 丕賱廿噩鬲賲丕毓賷丞</string>
+    <string name="prefs_wearside">賷乇鬲丿賷 丕賱賷爻丕乇 兀賵 丕賱賷賲賷賳責</string>
+    <string name="pref_screen_notification_profile_calendar">廿卮毓丕乇 丕賱鬲賯賵賷賲</string>
+    <string name="user_feedback_miband_set_alarms_ok">鬲賲 廿乇爻丕賱 丕賱賲賳亘賴丕鬲 廿賱賶 丕賱噩賴丕夭.</string>
+    <string name="movement_intensity">卮丿丞 丕賱丨乇賰丞</string>
+    <string name="seconds_8">8 孬賵丕賳</string>
+    <string name="seconds_10">10 孬賵丕賳</string>
+    <string name="abstract_chart_fragment_kind_light_sleep">賳賵賲 禺賮賷賮</string>
+    <string name="abstract_chart_fragment_kind_deep_sleep">賳賵賲 毓賲賷賯</string>
+    <string name="abstract_chart_fragment_kind_rem_sleep">賳賵賲 丨乇賰丞 丕賱毓賷賳 丕賱爻乇賷毓丞</string>
+    <string name="prefs_control_center">賲乇賰夭 丕賱鬲丨賰賲</string>
+    <string name="prefs_activate_display_on_lift_sensitivity">丕賱丨爻丕爻賷丞</string>
+    <string name="prefs_screen_timeout">賲賴賱丞 丕賱卮丕卮丞</string>
+    <string name="traditional_chinese">丕賱氐賷賳賷丞 丕賱鬲賯賱賷丿賷丞</string>
+    <string name="norwegian_bokmal">丕賱賳乇賵賷噩賷丞 亘賵賰賲丕賱</string>
+    <string name="bengali">丕賱亘賳睾丕賱賷丞</string>
+    <string name="czech">丕賱鬲卮賷賰賷丞</string>
+    <string name="dbmanagementactivity_error_exporting_shared">禺胤兀 賮賷 鬲氐丿賷乇 丕賱鬲賮囟賷賱: %1$s</string>
+    <string name="activity_DB_test_export_message">鬲氐丿賷乇 賯丕毓丿丞 丕賱亘賷丕賳丕鬲鈥�</string>
+    <string name="activity_DB_delete_legacy_button">丨匕賮 賯丕毓丿丞 丕賱亘賷丕賳丕鬲 丕賱賯丿賷賲丞</string>
+    <string name="activity_DB_empty_button">賯丕毓丿丞 亘賷丕賳丕鬲 賮丕乇睾丞</string>
+    <string name="start">亘丿亍</string>
+    <string name="smart">匕賰賷</string>
+    <string name="mi2_dnd_scheduled">賲噩丿賵賱 (賮丕氐賱 夭賲賳賷)</string>
+    <string name="activity_type_not_worn">丕賱噩賴丕夭 丕賲 賷鬲賲 廿乇鬲丿丕亍賴</string>
+    <string name="devicetype_amazfit_trex_2">Amazfit T-Rex 2</string>
+    <string name="devicetype_amazfit_cor2">丕賲丕夭賮賷鬲 Cor 2</string>
+    <string name="devicetype_amazfit_gtr_lite">Amazfit GTR Lite</string>
+    <string name="devicetype_um25">UM-25</string>
+    <string name="devicetype_teclast_h30">Teclast H30</string>
+    <string name="filter_mode_none">賱丕 鬲賯賲 亘丕賱鬲氐賮賷丞</string>
+    <string name="prefs_events_forwarding_fellsleep">毓賳丿 丕賱丿禺賵賱 賮賷 丕賱賳賵賲</string>
+    <string name="pref_header_heartrate_sleep">丕賱賳賵賲</string>
+    <string name="sony_equalizer_band_16000">16k</string>
+    <string name="sony_speak_to_chat_timeout_short">賯氐賷乇 (15 孬丕賳賷丞)</string>
+    <string name="sony_button_mode_off">丕賷賯丕賮</string>
+    <string name="sony_button_mode_ambient_sound_control">丕賱鬲丨賰賲 賮賷 丕賱氐賵鬲 丕賱賲丨賷胤</string>
+    <string name="sony_ambient_sound_control_button_modes">兀賵囟丕毓 夭乇 丕賱鬲丨賰賲 賮賷 丕賱氐賵鬲 丕賱賲丨賷胤賷</string>
+    <string name="sony_ambient_sound_control_button_mode_nc_as">廿賱睾丕亍 丕賱囟賵囟丕亍, 丕賱氐賵鬲 丕賱賲丨賷胤賷</string>
+    <string name="unknown_state">丨丕賱丞 睾賷乇 賲毓乇賵賮丞</string>
+    <string name="vibration_profile_default">丕賮鬲乇丕囟賷</string>
+    <string name="pref_screen_notification_profile_todo_list">賯丕卅賲丞 丕賱賲賴丕賲</string>
+    <string name="charts_activity_list">賯丕卅賲丞 丕賱兀賳卮胤丞</string>
+    <string name="portuguese_br">丕賱亘乇鬲睾丕賱賷丞 (丕賱亘乇丕夭賷賱)</string>
+    <string name="activity_prefs_weight_kg">丕賱賵夭賳 亘丕賱賰賷賱賵 噩乇丕賲</string>
+    <string name="activity_prefs_target_weight_kg">丕賱賵夭賳 丕賱賲爻鬲賴丿賮 亘丕賱賰賷賱賵 噩乇丕賲</string>
+    <string name="pref_header_charts">廿毓丿丕丿丕鬲 丕賱乇爻賵賲 丕賱亘賷丕賳賷丞</string>
+    <string name="dbmanagementactivity_exported_to">丕賱賲氐丿乇丞 廿賱賶: %1$s</string>
+    <string name="mi2_dnd_always">丿丕卅賲丕賸</string>
+    <string name="activity_type_stretching">丕賱鬲賲丿丿</string>
+    <string name="activity_type_table_tennis">鬲賳爻 胤丕賵賱丞</string>
+    <string name="activity_type_kickboxing">丕賱賰賷賰 亘賵賰爻賷賳睾</string>
+    <string name="activity_type_street_dance">乇賯氐 丕賱卮賵丕乇毓</string>
+    <string name="devicetype_liveview">LiveView</string>
+    <string name="devicetype_amazfit_gts2">Amazfit GTS 2</string>
+    <string name="devicetype_makibes_f68">Makibes F68</string>
+    <string name="devicetype_q8">Q8</string>
+    <string name="menuitem_theater_mode">賵囟毓 丕賱賲爻乇丨</string>
+    <string name="ukranian">丕賱兀賵賰乇丕賳賷丞</string>
+    <string name="charts_legend_heartrate_average">賲鬲賵爻胤 賲毓丿賱 賳亘囟丕鬲 丕賱賯賱亘</string>
+    <string name="widget_settings_select_device_title">丕禺鬲乇 噩賴丕夭</string>
+    <string name="watchface_widget_type_active_mins">丕賱丿賯丕卅賯 丕賱賳卮胤丞</string>
+    <string name="watchface_widget_type_custom">賵賷丿噩賷鬲 賲禺氐氐丞</string>
+    <string name="activity_summaries">丕賱兀賳卮胤丞 丕賱乇賷丕囟賷丞</string>
+    <string name="mi2_prefs_inactivity_warnings_threshold">丨丿 毓丿賲 丕賱賳卮丕胤 (亘丕賱丿賯丕卅賯)</string>
+    <string name="prefs_always_on_display_style">鈥庁з勜X迟勝堌�</string>
+    <string name="seconds_20">20 孬丕賳賷丞</string>
+    <string name="seconds_25">25 孬丕賳賷丞</string>
+    <string name="seconds_30">30 孬丕賳賷丞</string>
+    <string name="minutes_1">1 丿賯賷賯丞</string>
+    <string name="pref_ambient_sound_tone">賳睾賲丞 丕賱氐賵鬲 丕賱賲丨賷胤丞</string>
+    <string name="watchface_dialog_widget_timeout_hide_text">廿禺賮丕亍 丕賱賳氐 毓賳丿 丕賳鬲賴丕亍 丕賱賲賴賱丞</string>
+    <string name="distanceMeters">丕賱賲爻丕賮丞</string>
+    <string name="elevationGain">夭賷丕丿丞 丕賱丕乇鬲賮丕毓</string>
+    <string name="elevationLoss">丕賳禺賮丕囟 丕賱丕乇鬲賮丕毓</string>
+    <string name="descentDistance">賲爻丕賮丞 丕賳丨丿丕乇</string>
+    <string name="seconds_9">9 孬賵丕賳</string>
+    <string name="seconds_11">11 孬丕賳賷丞</string>
+    <string name="seconds_12">12 孬丕賳賷丞</string>
+    <string name="seconds_13">13 孬丕賳賷丞</string>
+    <string name="seconds_14">14 孬丕賳賷丞</string>
+    <string name="gps_track">賲爻丕乇 GPS</string>
+    <string name="menuitem_volume">賲爻鬲賵賶 丕賱氐賵鬲</string>
+    <string name="totalStride">禺胤賵丞 賰丕賲賱丞</string>
+    <string name="anaerobicTrainingEffect">丕賱鬲兀孬賷乇 丕賱賱丕賴賵丕卅賷</string>
+    <string name="HeartRateZones">賲賳丕胤賯 賲毓丿賱 賳亘囟丕鬲 丕賱賯賱亘</string>
+    <string name="maximumOxygenUptake">丕賱丨丿 丕賱兀賯氐賶 賱丕賲鬲氐丕氐 丕賱兀賰爻噩賷賳</string>
+    <string name="hrZoneNa">睾賷乇 賲鬲丕丨</string>
+    <string name="hrZoneExtreme">丕賱兀賯氐賶</string>
+    <string name="TrainingEffect">鬲兀孬賷乇 丕賱鬲丿乇賷亘</string>
+    <string name="aerobicTrainingEffect">丕賱鬲兀孬賷乇 丕賱賴賵丕卅賷</string>
+    <string name="minStride">丕賯賱 爻鬲乇丕賷丿</string>
+    <string name="minHR">丕賱丨丿 丕賱兀丿賳賶 賱賲毓丿賱 賳亘囟丕鬲 丕賱賯賱亘</string>
+    <string name="sony_equalizer_preset_treble_boost">鬲毓夭賷夭 丕賱鬲乇賷亘賱</string>
+    <string name="pref_header_sony_equalizer_preset_custom_1">廿毓丿丕丿 賲爻亘賯 賲禺氐氐 1</string>
+    <string name="pref_header_stress">丕賱廿噩賴丕丿</string>
+    <string name="sony_ambient_sound">丕賱賵囟毓</string>
+    <string name="double_tap">丕賳賯乇 賳賯乇丕 賲夭丿賵噩丕</string>
+    <string name="triple_tap">賳賯乇丞 孬賱丕孬賷丞</string>
+    <string name="step_streak_longest">丕賱兀胤賵賱</string>
+    <string name="info_no_devices_connected">賱丕 鬲賵噩丿 兀噩賴夭丞 賲鬲氐賱丞</string>
+    <string name="connection_over_bt_classic">丕賱丕鬲氐丕賱 毓亘乇 丕賱亘賱賵鬲賵孬 丕賱賰賱丕爻賷賰賷</string>
+    <string name="updating_firmware">鬲孬亘賷鬲 丕賱亘乇丕賲噩 丕賱孬丕亘鬲丞鈥�</string>
+    <string name="blood_pressure">囟睾胤 丕賱丿賲</string>
+    <string name="live_activity_steps_history">爻噩賱 丕賱禺胤賵丕鬲</string>
+    <string name="mi2_prefs_activate_display_on_lift">鬲賳卮賷胤 丕賱卮丕卮丞 毓賳丿 丕賱乇賮毓</string>
+    <string name="mi2_prefs_do_not_disturb">毓丿賲 丕賱廿夭毓丕噩</string>
+    <string name="mi2_prefs_display_items">毓乇囟 丕賱毓賳丕氐乇</string>
+    <string name="activity_type_bowling">丕賱亘賵賱賷賳噩</string>
+    <string name="devicetype_waspos">Wasp-os</string>
+    <string name="descentMeters">賲賳丨丿乇</string>
+    <string name="averageSpeed">賲鬲賵爻胤 丕賱爻乇毓丞</string>
+    <string name="devicetype_sony_linkbuds_s">Sony LinkBuds S</string>
+    <string name="ascentMeters">賲乇鬲賯賶</string>
+    <string name="stats_y_axis_label">禺胤賵丞 賮賷 丕賱丿賯賷賯丞</string>
+    <string name="world_clock_code">丕賱乇賲夭</string>
+    <string name="title_activity_reminder_details">鬲賮丕氐賷賱 丕賱鬲匕賰賷乇</string>
+    <string name="alarm_smart_wakeup">丕賱丕爻鬲賷賯丕馗 丕賱匕賰賷</string>
+    <string name="prefs_heartrate_alert_threshold">丨丿 鬲賳亘賷賴 賲毓丿賱 賳亘囟丕鬲 丕賱賯賱亘</string>
+    <string name="activity_prefs_chart_max_heart_rate">兀賯氐賶 賲毓丿賱 賱賳亘囟丕鬲 丕賱賯賱亘</string>
+    <string name="activity_prefs_chart_min_heart_rate">丕賱丨丿 丕賱兀丿賳賶 賱賲毓丿賱 賳亘囟丕鬲 丕賱賯賱亘</string>
+    <string name="dbmanagementactivity_error_exporting_db">禺胤兀 賮賷 鬲氐丿賷乇 賯丕毓丿丞 丕賱亘賷丕賳丕鬲: %1$s</string>
+    <string name="dbmanagementactivity_error_importing_shared">禺胤兀 賮賷 丕爻鬲賷乇丕丿 丕賱鬲賮囟賷賱: %1$s</string>
+    <string name="menuitem_brightness">丕賱爻胤賵毓</string>
+    <string name="yesterdays_activity">賳卮丕胤 丕賱兀賲爻</string>
+    <string name="widget_10_minutes">10 丿賯丕卅賯</string>
+    <string name="prefs_button_triple_press_action_selection_title">廿噩乇丕亍 丕賱丨丿孬 3</string>
+    <string name="quick_alarm_description">鬲賳亘賷賴 賲賳 丕賱賵賷丿噩賷鬲</string>
+    <string name="watch_not_connected">丕賱爻丕毓丞 睾賷乇 賲鬲氐賱丞</string>
+    <string name="qhybrid_offset_time_by">廿夭丕丨丞 丕賱賵賯鬲 亘賲賯丿丕乇</string>
+    <string name="watchface_dialog_title_settings">廿毓丿丕丿丕鬲 賵丕噩賴丞 丕賱爻丕毓丞</string>
+    <string name="qhybrid_title_background_image">氐賵乇丞 丕賱禺賱賮賷丞</string>
+    <string name="fossil_hr_edit_action">鬲丨乇賷乇 丕賱廿噩乇丕亍</string>
+    <string name="fossil_hr_new_action">廿噩乇丕亍 噩丿賷丿</string>
+    <string name="devicetype_amazfit_trex_pro">Amazfit T-Rex Pro</string>
+    <string name="button_watchface_edit_name">鬲毓丿賷賱 丕賱丕爻賲</string>
+    <string name="pref_header_filter">賲乇卮丨 丕賱兀賳卮胤丞 丕賱乇賷丕囟賷丞</string>
+    <string name="prefs_autoheartrate">賲毓丿賱 賳亘囟丕鬲 丕賱賯賱亘 丕賱鬲賱賯丕卅賷</string>
+    <string name="prefs_ambient_volume_left">丕賱氐賵鬲 丕賱賲丨賷胤賷 賱賱賷爻丕乇</string>
+    <string name="prefs_ambient_volume_right">丕賱氐賵鬲 丕賱賲丨賷胤賷 賱賱賷賲賷賳</string>
+    <string name="prefs_ambient_sound">丕賱氐賵鬲 丕賱賲丨賷胤賷</string>
+    <string name="prefs_customize_ambient_sound_summary">鈥屫蒂娯� 丕賱氐賵鬲 丕賱賲丨賷胤賷</string>
+    <string name="prefs_ambient_sound_during_call_title">丕賱氐賵鬲 丕賱賲丨賷胤賷 兀孬賳丕亍 丕賱賲賰丕賱賲丞</string>
+    <string name="prefs_ambient_settings_title">禺賷丕乇丕鬲 丕賱氐賵鬲 丕賱賲丨賷胤賷</string>
+    <string name="prefs_active_noise_cancelling_level">賲爻鬲賵賶 廿賱睾丕亍 丕賱囟賵囟丕亍 丕賱賳卮胤</string>
+    <string name="distance_format_feet">###ft</string>
+    <string name="controlcenter_toggle_details">鬲亘丿賷賱 丕賱鬲賮丕氐賷賱</string>
+    <string name="devicetype_soflow_s06">SoFlow SO6</string>
+    <string name="wifi_hotspot_status">丨丕賱丞 賳賯胤丞 丕鬲氐丕賱 Wi-Fi</string>
+    <string name="ftp_server">禺丕丿賲 亘乇賵鬲賵賰賵賱 賳賯賱 丕賱賲賱賮丕鬲</string>
+    <string name="extended_ascii">ASCII 丕賱賲賵爻毓丞</string>
+    <string name="widget_5_minutes">5 丿賯丕卅賯</string>
+    <string name="pref_screen_notification_profile_inactivity">廿卮毓丕乇 毓丿賲 丕賱賳卮丕胤</string>
+    <string name="pref_title_notifications_and_calls_repeat_on_call">鬲賰乇丕乇 廿卮毓丕乇 丕賱賲賰丕賱賲丞</string>
+    <string name="find_my_phone_notification">丕賱毓孬賵乇 毓賱賶 賴丕鬲賮賷</string>
+    <string name="installation_failed_">賮卮賱 丕賱鬲孬亘賷鬲</string>
+    <string name="title_activity_set_contacts">鬲賰賵賷賳 噩賴丕鬲 丕賱丕鬲氐丕賱</string>
+    <string name="notif_battery_low_extended">%1$s 丕賱亘胤丕乇賷丞 賲賳禺賮囟丞: %2$s</string>
+    <string name="activity_list_summary_intensity">丨乇賰丞
+\n卮丿丞</string>
+    <string name="show_ongoing_activity">廿馗賴丕乇 丕賱賳丕賮匕丞 丕賱賲賳亘孬賯丞 賱賱賳卮丕胤 丕賱賲爻鬲賲乇</string>
+    <string name="prefs_heartrate_alert_high_threshold">毓鬲亘丞 鬲賳亘賷賴 賲毓丿賱 賳亘囟丕鬲 丕賱賯賱亘 丕賱賲乇鬲賮毓賴</string>
+    <string name="pref_sensors_bp_calibration_low">囟睾胤 丕賱丿賲 丕賱丕賳亘爻丕胤賷 (賲賳禺賮囟)</string>
+    <string name="appversion_by_creator">%1$s 亘賵丕爻胤丞 %2$s</string>
+    <string name="prefs_charts_tabs_summary">毓賱丕賲丕鬲 鬲亘賵賷亘 丕賱乇爻賲 丕賱亘賷丕賳賷 丕賱賲乇卅賷丞</string>
+    <string name="weeksleepchart_today_sleep_description">丕賱賳賵賲 丕賱賷賵賲 貙 丕賱賴丿賮: %1$s</string>
+    <string name="activity_list_summary_active_time">丕賱賵賯鬲 丕賱賳卮胤</string>
+    <string name="weekstepschart_steps_a_week">禺胤賵丕鬲 賮賷 丕賱兀爻亘賵毓</string>
+    <string name="pref_contacts_title">噩賴丕鬲 丕賱丕鬲氐丕賱</string>
+    <string name="pref_contacts_summary">鬲賰賵賷賳 噩賴丕鬲 丕賱丕鬲氐丕賱 毓賱賶 丕賱爻丕毓丞</string>
+    <string name="heart_rate_result">賳鬲丕卅噩 丕賱賯賷丕爻</string>
+    <string name="minutes_30">30 丿賯賷賯丞</string>
+    <string name="liveactivity_live_activity">賳卮丕胤 丨賷</string>
+    <string name="pref_enable_unsupported_settings_title">鬲賲賰賷賳 丕賱廿毓丿丕丿丕鬲 睾賷乇 丕賱賲丿毓賵賲丞</string>
+    <string name="activity_prefs_charts">廿毓丿丕丿丕鬲 丕賱乇爻賲 丕賱亘賷丕賳賷</string>
+    <string name="seconds_15">15 孬丕賳賷丞</string>
+    <string name="activity_prefs_discovery_pairing">禺賷丕乇丕鬲 丕賱丕賰鬲卮丕賮 賵丕賱丕賯鬲乇丕賳</string>
+    <string name="minutes_10">10 丿賯丕卅賯</string>
+    <string name="activity_type_jump_roping">丕賱賯賮夭 毓賱賶 丕賱丨亘賱</string>
+    <string name="lack_of_step">丕賱丕賮鬲賯丕乇 廿賱賶 丕賱禺胤賵丕鬲: %1$d</string>
+    <string name="device_fw">廿氐丿丕乇 丕賱亘乇賳丕賲噩 丕賱孬丕亘鬲: %1$s</string>
+    <string name="activity_type_outdoor_cycling">乇賰賵亘 丕賱丿乇丕噩丕鬲 賮賷 丕賱賴賵丕亍 丕賱胤賱賯</string>
+    <string name="activity_type_elliptical">亘賷囟丕賵賷 丕賱卮賰賱</string>
+    <string name="activity_summary_edit_name_title">鬲丨乇賷乇 丕賱鬲爻賲賷丞</string>
+    <string name="nothing_prefs_inear_title">賰卮賮 丿丕禺賱 丕賱兀匕賳</string>
+    <string name="prefs_equalizer_preset">丕賱囟亘胤 丕賱賲爻亘賯 賱賱賲毓丕丿賱</string>
+    <string name="long_press">囟睾胤丞 胤賵賷賱丞</string>
+    <string name="sony_automatic_power_off_3_hour">3 爻丕毓丕鬲</string>
+    <string name="sony_automatic_power_off_when_taken_off">毓賳丿 丕夭丕賱鬲賴丕</string>
+    <string name="activity_summaries_all_devices">噩賲賷毓 丕賱兀噩賴夭丞</string>
+    <string name="activity_filter_from_placeholder">丕賱賲丕囟賷 丕賱亘毓賷丿</string>
+    <string name="fossil_hr_commute_processing">賲毓丕賱噩丞 丕賱胤賱亘鈥�</string>
+    <string name="pref_voice_detect_duration_10">10 孬賵丕賳賷</string>
+    <string name="devicetype_bfh16">BFH-16</string>
+    <string name="devicetype_tlw64">TLW64</string>
+    <string name="sony_equalizer_band_2500">2.5k</string>
+    <string name="devicetype_makibes_hr3">Makibes HR3</string>
+    <string name="devicetype_banglejs">Bangle.js</string>
+    <string name="devicetype_sony_wf_1000xm3">Sony WF-1000XM3</string>
+    <string name="devicetype_galaxybuds_pro">Galaxy Buds Pro</string>
+    <string name="devicetype_galaxybuds_2_pro">Galaxy Buds2 Pro</string>
+    <string name="devicetype_sony_wf_1000xm4">Sony WF-1000XM4</string>
+    <string name="select_all">丕禺鬲乇 丕賱賰賱</string>
+    <string name="reset_index">廿毓丕丿丞 鬲毓賷賷賳 鬲丕乇賷禺 丕賱噩賱亘</string>
+    <string name="kind_app">丕賱鬲胤亘賷賯</string>
+    <string name="add_test_device">廿囟丕賮丞 噩賴丕夭 丕禺鬲亘丕乇</string>
+    <string name="devicetype_miband2">賲賷 亘丕賳丿 2</string>
+    <string name="devicetype_miband6">賲賷 亘丕賳丿 6</string>
+    <string name="devicetype_miband7">爻賵丕乇 卮丕賵賲賷 丕賱匕賰賷 7</string>
+    <string name="devicetype_amazfit_gts3">Amazfit GTS 3</string>
+    <string name="devicetype_amazfit_gtr4">Amazfit GTR 4</string>
+    <string name="devicetype_amazfit_bip">Amazfit Bip</string>
+    <string name="devicetype_amazfit_gtr">Amazfit GTR</string>
+    <string name="devicetype_amazfit_bipu">丕賲丕夭賮賷鬲 Bip U</string>
+    <string name="devicetype_amazfit_bipupro">Amazfit Bip U 亘乇賵</string>
+    <string name="devicetype_amazfit_pop_pro">Amazfit Pop Pro</string>
+    <string name="devicetype_amazfit_gtr2">丕賲丕夭賮賷鬲 GTR 2</string>
+    <string name="devicetype_amazfit_gtr2e">丕賲丕夭賮賷鬲 GTR 2e</string>
+    <string name="devicetype_roidmi">Roidmi</string>
+    <string name="pref_summary_navigation_forward">廿毓丕丿丞 鬲賵噩賷賴 丕賱鬲毓賱賷賲丕鬲 賲賳 鬲胤亘賷賯丕鬲 丕賱賲賱丕丨丞 廿賱賶 丕賱爻丕毓丞</string>
+    <string name="pref_title_navigation_forward">廿乇爻丕賱 丕賱賲賱丕丨丞 賱賱爻丕毓丞</string>
+    <string name="pref_header_navigation">丕賱賲賱丕丨丞</string>
+    <string name="intent_api_allow_debug_commands_title">丕賱爻賲丕丨 亘兀賵丕賲乇 丕賱鬲氐丨賷丨</string>
+    <string name="intent_api_allow_debug_commands_summary">丕賱爻賲丕丨 亘鬲卮睾賷賱 兀賵丕賲乇 賯丕卅賲丞 鬲氐丨賷丨 丕賱兀禺胤丕亍 毓亘乇 賵丕噩賴丞 亘乇賲噩丞 鬲胤亘賷賯丕鬲 丕賱賳賷丞</string>
+    <string name="voice_service_package_summary">丕賱鬲胤亘賷賯 丕賱匕賷 賷丨鬲賵賷 毓賱賶 禺丿賲丞 丕賱鬲毓丕賲賱 賲毓 丕賱兀賵丕賲乇 丕賱氐賵鬲賷丞</string>
+    <string name="voice_service_class_title">賮卅丞 丕賱禺丿賲丞 丕賱氐賵鬲賷丞</string>
+    <string name="voice_service">丕賱禺丿賲丞 丕賱氐賵鬲賷丞</string>
+    <string name="voice_service_package_title">賲噩賲賵毓丞 禺丿賲丕鬲 丕賱氐賵鬲</string>
+    <string name="voice_service_class_summary">賲爻丕乇 丕賱禺丿賲丞 丕賱賰丕賲賱 丕賱匕賷 賷鬲毓丕賲賱 賲毓 丕賱兀賵丕賲乇 丕賱氐賵鬲賷丞</string>
+    <string name="pref_app_logs_summary">鬲賲賰賷賳 丕賱爻噩賱丕鬲 賲賳 鬲胤亘賷賯丕鬲 丕賱爻丕毓丞</string>
+    <string name="pref_app_logs_stop_summary">鬲賵賯賮 毓賳 丕賱鬲爻噩賷賱 賲賳 鬲胤亘賷賯丕鬲 丕賱爻丕毓丞</string>
+    <string name="pref_app_logs_title">爻噩賱 丕賱鬲胤亘賷賯</string>
+    <string name="pref_app_logs_start_summary">亘丿亍 鬲爻噩賷賱 丕賱爻噩賱 賲賳 鬲胤亘賷賯丕鬲 丕賱爻丕毓丞</string>
+    <string name="fwapp_install_device_not_supported">賱丕 賷賲賰賳 鬲孬亘賷鬲 丕賱賲賱賮 貙 丕賱噩賴丕夭 睾賷乇 賲丿毓賵賲.</string>
+    <string name="screenshot_taken">鬲賲 丕賱鬲賯丕胤 賱賯胤丞 丕賱卮丕卮丞</string>
+    <string name="share_screenshot">賲卮丕乇賰丞 賱賯胤丞 丕賱卮丕卮丞</string>
+    <string name="charts_legend_stress_average">賲鬲賵爻胤 丕賱廿噩賴丕丿</string>
+    <string name="stress_moderate">賲毓鬲丿賱</string>
+    <string name="stress_high">毓丕賱</string>
+    <string name="stress_relaxed">賲爻鬲乇禺賷</string>
+    <string name="stress_mild">禺賮賷賮</string>
+    <string name="watchface_widget_type_uv_index">賲丐卮乇 丕賱兀卮毓丞 賮賵賯 丕賱亘賳賮爻噩賷丞</string>
+    <string name="preview_image">賲毓丕賷賳丞 丕賱氐賵乇丞</string>
+    <string name="status_icon">兀賷賯賵賳丞 丕賱丨丕賱丞</string>
+    <string name="changelog_full_title">爻噩賱 丕賱鬲睾賷賷乇</string>
+    <string name="pref_title_banglejs_phone_gps_enbale">丕爻鬲禺丿賲 亘賷丕賳丕鬲 GPS 丕賱禺丕氐丞 亘丕賱賴丕鬲賮</string>
+    <string name="pref_summary_banglejs_phone_gps_enbale">丕爻鬲禺丿賲 亘賷丕賳丕鬲 GPS 丕賱禺丕氐丞 亘丕賱賴丕鬲賮 賱賱賰鬲丕亘丞 賮賵賯 亘賷丕賳丕鬲 GPS 賱噩賴丕夭 丕賱廿爻賵乇丞</string>
+    <string name="pref_title_banglejs_phone_gps_network_only">丕爻鬲禺丿賲 丕賱卮亘賰丞 賮賯胤 賱鬲丨丿賷丿 丕賱賲賵賯毓</string>
+    <string name="pref_summary_banglejs_phone_gps_network_only">丕爻鬲禺丿賲 賲賵賮乇 丕賱卮亘賰丞 賮賯胤 賱鬲丨丿賷丿 丕賱賲賵賯毓. 賴匕丕 賷賯賱賱 賲賳 丕爻鬲賴賱丕賰 丕賱胤丕賯丞 毓賱賶 丨爻丕亘 丕賱丿賯丞. 賴賳丕賰 丨丕噩丞 廿賱賶 廿毓丕丿丞 丕賱丕鬲氐丕賱.</string>
+    <string name="pref_title_banglejs_phone_gps_update_interval">丕賱賮丕氐賱 丕賱夭賲賳賷 賱鬲丨丿賷孬 亘賷丕賳丕鬲 GPS</string>
+    <string name="title">丕賱毓賳賵丕賳</string>
+    <string name="description">丕賱賵氐賮</string>
+    <string name="changelog_ok_button">丨爻賳丕</string>
+    <string name="pref_summary_banglejs_phone_gps_update_interval">丕賱賮丕氐賱 丕賱夭賲賳賷 賱毓丿丿 丕賱賲乇丕鬲 丕賱鬲賷 賷鬲賲 賮賷賴丕 鬲丨丿賷孬 賲賵囟毓 GPS 貙 亘丕賱賲賱賱賷 孬丕賳賷丞</string>
+    <string name="changelog_show_full">兀賰孬乇鈥�</string>
+    <string name="changelog_title">賲丕 丕賱噩丿賷丿</string>
+    <string name="action_changelog">爻噩賱 丕賱鬲睾賷賷乇丕鬲</string>
+    <string name="pai_plus_num">+%d</string>
+    <string name="num_min">%d 丿賯賷賯丞</string>
+    <string name="pai_chart_per_week">PAI 賮賷 丕賱兀爻亘賵毓</string>
+    <string name="pai_chart_per_month">PAI 賮賷 丕賱卮賴乇</string>
+    <string name="pai_total">丕賱賲噩賲賵毓</string>
+    <string name="pai_day">夭賷丕丿丞 丕賱賷賵賲</string>
+    <string name="loyalty_cards_install">鬲孬亘賷鬲 賰丕鬲賷賲丕</string>
+    <string name="loyalty_cards_sync_groups_only">賲夭丕賲賳丞 賲噩賲賵毓丕鬲 賲丨丿丿丞 賮賯胤</string>
+    <string name="loyalty_cards_install_catima_fail">賮卮賱 賮賷 賮鬲丨 賲鬲噩乇 丕賱鬲胤亘賷賯丕鬲 賱鬲孬亘賷鬲 賰丕鬲賷賲丕</string>
+    <string name="loyalty_cards_sync_title">賲夭丕賲賳丞 亘胤丕賯丕鬲 丕賱賵賱丕</string>
+    <string name="loyalty_cards_sync_options">禺賷丕乇丕鬲 丕賱賲夭丕賲賳丞</string>
+    <string name="loyalty_cards_sync">賲夭丕賲賳丞</string>
+    <string name="loyalty_cards_catima_permissions_title">兀匕賵賳丕鬲 賲賮賯賵丿丞</string>
+    <string name="loyalty_cards_syncing">賲夭丕賲賳丞 亘胤丕賯丕鬲 丕賱賵賱丕亍 %d 賲毓 丕賱噩賴丕夭</string>
+    <string name="loyalty_cards_catima_package">丕爻賲 丨夭賲丞 賰丕鬲賷賲丕</string>
+    <string name="loyalty_cards_open_catima">丕賮鬲丨 賰丕鬲賷賲丕</string>
+    <string name="loyalty_cards_sync_groups">丕賱賲噩賲賵毓丕鬲 丕賱賲乇丕丿 賲夭丕賲賳鬲賴丕</string>
+    <string name="loyalty_cards_sync_archived">賲夭丕賲賳丞 丕賱亘胤丕賯丕鬲 丕賱賲丐乇卮賮丞</string>
+    <string name="loyalty_cards_catima_not_compatible">廿氐丿丕乇 賰丕鬲賷賲丕 丕賱賲孬亘鬲 睾賷乇 賲鬲賵丕賮賯 賲毓 Gadgetbridge. 賷乇噩賶 鬲丨丿賷孬 賰丕鬲賷賲丕 賵 Gadgetbridge 廿賱賶 兀丨丿孬 丕氐丿丕乇.</string>
+    <string name="loyalty_cards_sync_starred">賲夭丕賲賳丞 丕賱亘胤丕賯丕鬲 丕賱賲賲賷夭丞 亘賳噩賲丞 賮賯胤</string>
+    <string name="loyalty_cards_sync_summary">丕賳賯乇 賱賲夭丕賲賳丞 丕賱亘胤丕賯丕鬲 賲毓 丕賱爻丕毓丞</string>
+    <string name="loyalty_cards_catima">賰丕鬲賷賲丕</string>
+    <string name="loyalty_cards">亘胤丕賯丕鬲 丕賱賵賱丕亍</string>
+    <string name="loyalty_cards_catima_permissions_summary">賷丨鬲丕噩 Gadgetbridge 廿賱賶 兀匕賵賳丕鬲 賯乇丕亍丞 賱亘胤丕賯丕鬲 賰丕鬲賷賲丕 賱賲夭丕賲賳鬲賴丕. 丕囟睾胤 毓賱賶 賴匕丕 丕賱夭乇 賱賲賳丨賴丕.</string>
+    <string name="loyalty_cards_catima_not_installed">賴賳丕賰 丨丕噩丞 廿賱賶 賰丕鬲賷賲丕 賱廿丿丕乇丞 亘胤丕賯丕鬲 丕賱賵賱丕亍</string>
+    <string name="pref_title_mb_intents">賳賵丕賷丕 夭乇 亘孬 丕賱賵爻丕卅胤 賲亘丕卮乇丞</string>
+    <string name="pref_summary_mb_intents">鬲賲賰賷賳 廿匕丕 賰丕賳 丕賱鬲丨賰賲 賮賷 賵爻丕卅胤 丕賱噩賴丕夭 賱丕 賷毓賲賱 賲毓 鬲胤亘賷賯丕鬲 賲毓賷賳丞</string>
+    <string name="devicetype_bohemic_smart_bracelet">爻賵丕乇 Bohemic 丕賱匕賰賷</string>
+    <string name="devicetype_vivomove_hr">睾丕乇賲賷賳 賮賷賮賵賲賵賮賵賮 HR</string>
+    <string name="devicetype_casiogwb5600">賰丕爻賷賵 GW-B5600</string>
+    <string name="devicetype_casiogmwb5000">賰丕爻賷賵 GMW-B5000</string>
+    <string name="busy_task_fetch_sports_details">噩賱亘 丕賱鬲賮丕氐賷賱 丕賱乇賷丕囟賷丞</string>
+    <string name="busy_task_fetch_hr_data">噩賱亘 亘賷丕賳丕鬲 賲毓丿賱 囟乇亘丕鬲 丕賱賯賱亘</string>
+    <string name="busy_task_fetch_sleep_respiratory_rate_data">噩賱亘 亘賷丕賳丕鬲 賲毓丿賱 丕賱鬲賳賮爻 兀孬賳丕亍 丕賱賳賵賲</string>
+    <string name="busy_task_fetch_sports_summaries">噩賱亘 賲賱禺氐丕鬲 丕賱乇賷丕囟丞</string>
+    <string name="busy_task_fetch_debug_logs">噩賱亘 爻噩賱丕鬲 丕賱鬲氐丨賷丨</string>
+    <string name="busy_task_fetch_stress_data">噩賱亘 亘賷丕賳丕鬲 丕賱廿噩賴丕丿</string>
+    <string name="busy_task_fetch_pai_data">噩賱亘 亘賷丕賳丕鬲 PAI</string>
+    <string name="busy_task_fetch_spo2_data">噩賱亘 亘賷丕賳丕鬲 SpO2</string>
+    <string name="error_no_bluetooth_connect">賷噩亘 賲賳丨 丕賱賵氐賵賱 廿賱賶 丕鬲氐丕賱 Bluetooth 賵鬲賲賰賷賳賴 賱賱賮丨氐 賱賷毓賲賱 亘卮賰賱 氐丨賷丨</string>
+    <string name="activity_prefs_goals">丕賱丕賴丿丕賮</string>
+    <string name="error_no_bluetooth_scan">賷噩亘 賲賳丨 丕賱賵氐賵賱 廿賱賶 丕賱賮丨氐 毓亘乇 Bluetooth 賵鬲賲賰賷賳賴 丨鬲賶 賷毓賲賱 丕賱賮丨氐 亘卮賰賱 氐丨賷丨</string>
+    <string name="permission_request">賷爻賲丨 賱賰 %1$s 亘廿乇爻丕賱 丕賱乇爻丕卅賱 賵丕賱亘賷丕賳丕鬲 丕賱兀禺乇賶 賲賳 Android 廿賱賶 噩賴丕夭賰. 賱賱賯賷丕賲 亘匕賱賰 貙 賷鬲胤賱亘 丕賱兀賲乇 廿匕賳丕 賱賱賵氐賵賱 廿賱賶 鬲賱賰 丕賱亘賷丕賳丕鬲 賵亘丿賵賳賴丕 貙 賯丿 賱丕 鬲毓賲賱 亘卮賰賱 氐丨賷丨.
+\n
+\n爻鬲馗賴乇 賱賰 丕賱丌賳 亘毓囟 賲乇亘毓丕鬲 丨賵丕乇 Android 丕賱鬲賷 鬲胤賱亘 賴匕賴 丕賱兀匕賵賳丕鬲.
+\n
+\n丕賱乇噩丕亍 丕賱賳賯乇 毓賱賶 \"%2$s\" 賱賱賲鬲丕亘毓丞.</string>
+    <string name="activity_type_surfing">乇賰賵亘 丕賱兀賲賵丕噩</string>
+    <string name="activity_type_windsurfing">胤丕卅乇丞 卮乇丕毓賷丞</string>
+    <string name="activity_type_kitesurfing">乇賰賵亘 丕賱兀賲賵丕噩 丕賱卮乇丕毓賷</string>
+    <string name="activity_type_football">賰乇丞 丕賱賯丿賲 丕賱丕賲乇賷賰賷丞</string>
+    <string name="activity_type_rugby">丕賱乇賰亘賷</string>
+    <string name="activity_type_baseball">亘賷爻亘賵賱</string>
+    <string name="activity_type_handball">賰乇丞 丕賱賷丿</string>
+    <string name="activity_type_tennis">鬲賳爻</string>
+    <string name="activity_type_squash">廿爻賰賵丕卮</string>
+    <string name="activity_type_weightlifting">乇賮毓 丕賱丕孬賯丕賱</string>
+    <string name="activity_type_dancing">丕賱乇賯氐</string>
+    <string name="activity_type_skiing">丕賱鬲夭賱噩</string>
+    <string name="activity_type_snowboarding">丕賱鬲夭賱噩 毓賱賶 丕賱噩賱賷丿</string>
+    <string name="activity_type_riding">乇賰賵亘 丕賱禺賷賱</string>
+    <string name="activity_type_hockey">賴賵賰賷</string>
+    <string name="activity_type_golf">丕賱噩賵賱賮</string>
+    <string name="activity_type_other">兀禺乇賶</string>
+    <string name="withings_steel_hr">賵賷孬賷賳噩夭 爻鬲賷賱 廿鬲卮 丌乇</string>
+    <string name="withings_bt_calibration_previous">丕賱爻丕亘賯</string>
+    <string name="activity_type_icehockey">賴賵賰賷 丕賱噩賱賷丿</string>
+    <string name="activity_type_iceskating">丕賱鬲夭賱噩 毓賱賶 丕賱孬賱噩</string>
+    <string name="withings_calibration_text_hours">賷乇噩賶 丕爻鬲禺丿丕賲 丕賱賯乇氐 兀丿賳丕賴 賱賲丨丕匕丕丞 毓賯乇亘 丕賱爻丕毓丞 廿賱賶 12.</string>
+    <string name="withings_calibration_text_minutes">丕賱丌賳 丕爻鬲禺丿賲 丕賱賯乇氐 賱賲丨丕匕丕丞 毓賯乇亘 丕賱丿賯丕卅賯 廿賱賶 12.</string>
+    <string name="withings_calibration_text_activity_target">兀禺賷乇賸丕 賯賲 亘賲丨丕匕丕丞 賷丿 丕賱賳卮丕胤 廿賱賶 100侏. 賷乇噩賶 丕賱丕賳鬲亘丕賴 廿賱賶 兀賳 賴匕丕 丕賱毓賯乇亘 賷鬲丨乇賰 賮賷 丕鬲噩丕賴 毓賯丕乇亘 丕賱爻丕毓丞 賮賯胤.</string>
+    <string name="withings_bt_calibration_next">丕賱鬲丕賱賷</string>
+    <string name="pref_theme_dynamic">兀賱賵丕賳 丿賷賳丕賲賷賰賷丞</string>
+    <string name="find_my_phone_found_it">鬲賲 丕賱毓孬賵乇 毓賱賷賴丕</string>
+    <string name="pref_canned_message">乇爻丕賱丞</string>
+    <string name="drag_handle">賲賯亘囟 丕賱爻丨亘</string>
+    <string name="pref_activity_full_sync_trigger_summary">賯賲 亘鬲卮睾賷賱 賲夭丕賲賳丞 賰丕賲賱丞 賱噩賲賷毓 亘賷丕賳丕鬲 丕賱賳卮丕胤</string>
+    <string name="pref_activity_full_sync_trigger_title">賲夭丕賲賳丞 賰丕賲賱丞</string>
+    <string name="preferences_miband_1_2_settings" tools:ignore="TypographyFractions">Mi Band 1/2 丕毓丿丕丿丕鬲</string>
+    <string name="preferences_miband_1_2_warning">鬲丨匕賷乇: 鬲賳胤亘賯 賴匕賴 丕賱鬲賮囟賷賱丕鬲 賮賯胤 毓賱賶 Mi Bands 1 賵 2.</string>
+    <string name="pref_show_changelog">廿馗賴丕乇 爻噩賱 丕賱鬲睾賷賷乇 毓賳丿 亘丿亍 丕賱鬲卮睾賷賱</string>
+    <string name="pref_show_changelog_summary">丕毓乇囟 爻噩賱 丕賱鬲睾賷賷乇 賲賳匕 丕賱廿氐丿丕乇 丕賱兀禺賷乇 亘毓丿 鬲丨丿賷孬 Gadgetbridge</string>
+    <string name="pref_activity_full_sync_trigger_warning">爻賷丐丿賷 賴匕丕 廿賱賶 鬲卮睾賷賱 賲夭丕賲賳丞 賰丕賲賱丞 賱噩賲賷毓 亘賷丕賳丕鬲 丕賱賳卮丕胤 賲賳 丕賱噩賴丕夭. 賯丿 賷爻鬲睾乇賯 丕賱兀賲乇 亘囟毓 丿賯丕卅賯 丨鬲賶 賷賰鬲賲賱.</string>
+    <string name="menuitem_zepp_coach">賲丿乇亘 夭賷亘</string>
+    <string name="pref_theme_dynamic_colors_not_available_warning">丕賱兀賱賵丕賳 丕賱丿賷賳丕賲賷賰賷丞 賱賷爻鬲 賲鬲丕丨丞 毓賱賶 噩賴丕夭賰 賮賯胤 兀賳丿乇賵賷丿 12+ 賷丿毓賲 賴匕賴 丕賱賵馗賷賮丞 Gadgetbridge 爻賷爻鬲禺丿賲 兀賱賵丕賳 丕賱賲賵丕丿 3.</string>
+    <string name="pref_theme_dynamic_colors_explanation">賲賱丕丨馗丞: 亘丕賱賳爻亘丞 廿賱賶 爻賲丞 丕賱兀賱賵丕賳 丕賱丿賷賳丕賲賷賰賷丞 貙 鬲丨鬲丕噩 廿賱賶 鬲賲賰賷賳 兀賱賵丕賳 丕賱禺賱賮賷丞 兀賵 賱賵丨丞 丕賱兀賱賵丕賳 賮賷 廿毓丿丕丿丕鬲 賲馗賴乇 Android 12+. 廿匕丕 賱賲 鬲賯賲 亘匕賱賰 貙 賮爻賷爻鬲禺丿賲 Gadgetbridge 丕賱兀賱賵丕賳 丕賱丕賮鬲乇丕囟賷丞 賱賱賲賵丕丿 3.</string>
+    <string name="devicetype_amazfit_bip3_pro">Amazfit Bip 3 Pro</string>
+    <string name="fw_upgrade_notice_amazfitbip3pro">兀賳鬲 毓賱賶 賵卮賰 鬲孬亘賷鬲 丕賱亘乇賳丕賲噩%s 丕賱孬丕亘鬲 毓賱賶 Amazfit Bip 3 Pro.
+\n
+\n賷乇噩賶 丕賱鬲兀賰丿 賲賳 鬲孬亘賷鬲 賲賱賮 .fw 貙 賵亘毓丿 匕賱賰 賲賱賮 .res. 爻鬲鬲賲 廿毓丕丿丞 鬲卮睾賷賱 爻丕毓鬲賰 亘毓丿 鬲孬亘賷鬲 賲賱賮 .fw.
+\n
+\n賲賱丕丨馗丞: 賱賷爻 毓賱賷賰 鬲孬亘賷鬲 .res 廿匕丕 賰丕賳 賲胤丕亘賯賸丕 鬲賲丕賲賸丕 賱賲丕 鬲賲 鬲孬亘賷鬲賴 賲爻亘賯賸丕.
+\n
+\n丕賱賲囟賷 賯丿賲丕 毓賱賶 賲爻丐賵賱賷鬲賰 丕賱禺丕氐丞!</string>
+    <string name="fw_upgrade_notice_amazfit_cheetah_pro">兀賳鬲 毓賱賶 賵卮賰 鬲孬亘賷鬲 丕賱亘乇丕賲噩 丕賱孬丕亘鬲丞 %s 毓賱賶 Amazfit Cheetah Pro.
+\n
+\n爻鬲鬲賲 廿毓丕丿丞 鬲卮睾賷賱 丕賱爻賵丕乇 亘毓丿 鬲孬亘賷鬲 賲賱賮 zip.
+\n
+\n兀爻鬲賲乇 毓賱賶 賲爻丐賵賱賷鬲賰 丕賱禺丕氐丞!</string>
+    <string name="devicetype_amazfit_cheetah_pro">Amazfit Cheetah Pro</string>
+    <string name="fw_upgrade_notice_zepp_os">兀賳鬲 毓賱賶 賵卮賰 鬲孬亘賷鬲 丕賱亘乇丕賲噩 丕賱孬丕亘鬲丞 %s 毓賱賶%s.
+\n
+\n爻鬲鬲賲 廿毓丕丿丞 鬲卮睾賷賱 爻丕毓鬲賰 亘毓丿 鬲孬亘賷鬲 賲賱賮 zip.
+\n
+\n丕賱丕爻鬲賲乇丕乇 毓賱賶 賲爻丐賵賱賷鬲賰 丕賱禺丕氐丞!</string>
+    <string name="devicetype_amazfit_bip5">Amazfit Bip 5</string>
+    <string name="devicetype_amazfit_trex_ultra">Amazfit T-Rex Ultra</string>
+    <string name="devicetype_amazfit_cheetah_square">Amazfit Cheetah (丕賱賲乇亘毓丞)</string>
+    <string name="devicetype_amazfit_gtr_mini">Amazfit GTR Mini</string>
+    <string name="devicetype_amazfit_cheetah_round">Amazfit Cheetah (丕賱丿丕卅乇賷丞)</string>
+    <string name="devicetype_amazfit_falcon">Amazfit Falcon</string>
+    <string name="device_experimental">丕禺鬲亘丕乇賷</string>
+    <string name="info_menu_structure_set">亘賳賷丞 丕賱賯丕卅賲丞 JSON 鬲賲 鬲毓賷賷賳賴丕 亘丕賱噩賷噩丕亘丕賷鬲</string>
+    <string name="error_invalid_menu_structure">亘賳賷丞 賯丕卅賲丞 JSON 睾賷乇 氐丕賱丨丞</string>
+    <string name="button_open_menu_companion">丕賮鬲丨 賯丕卅賲丞 丕賱鬲胤亘賷賯 丕賱賲氐丕丨亘</string>
+    <string name="button_reset_menu_structure">廿毓丕丿丞 鬲毓賷賷賳 賴賷賰賱 丕賱賯丕卅賲丞</string>
+    <string name="info_menu_structure_removed">鬲賲鬲 廿夭丕賱丞 亘賳賷丞 丕賱賯丕卅賲丞</string>
+    <string name="error_menu_companion_not_installed">乇亘賲丕 賱賲 賷鬲賲 鬲孬亘賷鬲 \"鬲胤亘賷賯 賳亘囟丕鬲 丕賱賯賱亘 丕賱賲乇丕賮賯\"</string>
+    <string name="info_menu_structure_contents">賴賷賰賱 丕賱賯丕卅賲丞: %s</string>
+    <string name="info_fossil_rebuild_watchface_custom_menu">賷乇噩賶 廿毓丕丿丞 廿賳卮丕亍 禺賱賮賷丞 卮丕卮丞 丕賱爻丕毓丞 賱賱賯丕卅賲丞 丕賱賲禺氐氐丞</string>
+    <string name="english_au">丕賱廿賳噩賱賷夭賷丞 (兀爻鬲乇丕賱賷丕)</string>
+    <string name="english_ca">丕賱廿賳噩賱賷夭賷丞 (賰賳丿丕)</string>
+    <string name="english_gb">丕賱廿賳噩賱賷夭賷丞 (丕賱賲賲賱賰丞 丕賱賲鬲丨丿丞)</string>
+    <string name="english_in">丕賱廿賳噩賱賷夭賷丞 (丕賱賴賳丿)</string>
+    <string name="english_us">丕賱廿賳噩賱賷夭賷丞 (丕賱賵賱丕賷丕鬲 丕賱賲鬲丨丿丞)</string>
+    <string name="spanish_es">丕賱廿爻亘丕賳賷丞 (廿爻亘丕賳賷丕)</string>
+    <string name="spanish_mx">丕賱廿爻亘丕賳賷丞 (丕賱賲賰爻賷賰)</string>
+    <string name="spanish_us">丕賱廿爻亘丕賳賷丞 (丕賱賵賱丕賷丕鬲 丕賱賲鬲丨丿丞)</string>
+    <string name="french_ca">丕賱賮乇賳爻賷丞 (賰賳丿丕)</string>
+    <string name="french_fr">丕賱賮乇賳爻賷丞 (賮乇賳爻丕)</string>
+    <string name="updatefirmwareoperation_updateproblem_free_space">丕賱噩賴丕夭 賱賷爻 賱丿賷賴 賲爻丕丨丞 賰丕賮賷丞</string>
+    <string name="text_receiver_activity_title">兀乇爻賱 乇爻丕賱丞 廿賱賶 丕賱噩賴丕夭</string>
+    <string name="updatefirmwareoperation_failed_low_mtu">賵丨丿丞 MTU 丕氐丿丕乇賴丕 %1$d 賲賳禺賮囟丞 噩丿賸丕貙 賷乇噩賶 鬲賲賰賷賳 賵丨丿丞 MTU 賮賷 廿毓丿丕丿丕鬲 丕賱噩賴丕夭 賵賮氐賱/廿毓丕丿丞 鬲賵氐賷賱 丕賱噩賴丕夭.</string>
+    <string name="devicetype_miband2_hrx">Mi Band HRX</string>
+    <string name="new_discover_activity_description">MTU 丕賱丨丕賱賷 丕賱亘丕賱睾 %1$賯賲 亘鬲賲賰賷賳 賳卮丕胤 丕賱丕賰鬲卮丕賮 丕賱噩丿賷丿貙 賵丕賱匕賷 賲賳 丕賱賲賮鬲乇囟 兀賳 賷丨賱 賲卮賰賱丕鬲 丕賰鬲卮丕賮 丕賱噩賴丕夭. 賯賲 亘鬲毓胤賷賱 賴匕丕 廿匕丕 賵丕噩賴鬲 兀賷 賲卮丕賰賱 兀孬賳丕亍 丕賱亘丨孬 毓賳 噩賴丕夭賰 兀賵 丕賱丕賯鬲乇丕賳 亘賴. 賲爻鬲賵賶 丕賱廿乇爻丕賱 賲賳禺賮囟 噩丿賸丕貙 賷乇噩賶 鬲賲賰賷賳 MTU 丕賱毓丕賱賷 賮賷 廿毓丿丕丿丕鬲 丕賱噩賴丕夭 賵賮氐賱/廿毓丕丿丞 鬲賵氐賷賱 丕賱噩賴丕夭.</string>
+    <string name="new_discover_activity_title">鬲賲賰賷賳 兀賳卮胤丞 丕賱丕賰鬲卮丕賮/胤胤賵噩丿賷丿丞</string>
+    <string name="prefs_wena3_receive_calls_title">廿毓丿丕丿丕鬲 丕賱賲賰丕賱賲丕鬲 丕賱賵丕乇丿丞</string>
+    <string name="prefs_wena3_button_action_name_start_timer">亘丿亍 丕賱賲賵賯鬲 丕賱賳卮胤</string>
+    <string name="prefs_wena3_menu_icon_hint">賷馗賴乇 乇賲夭 丕賱廿毓丿丕丿丕鬲 丿丕卅賲賸丕 賮賷 丕賱賳賴丕賷丞</string>
+    <string name="prefs_wena3_notification_vibration_repetition_3">3 賲乇丕鬲</string>
+    <string name="prefs_wena3_button_action_name_qrio_lock">賯賮賱 Qrio</string>
+    <string name="prefs_wena3_auto_power_off_turn_off_time_item">賵賯鬲 丕賱丕睾賱丕賯</string>
+    <string name="prefs_wena3_vibration_short">賯氐賷乇</string>
+    <string name="prefs_wena3_home_icon_name_calories">丕賱爻毓乇丕鬲</string>
+    <string name="prefs_home_icon_left_item">丕賱賷爻丕乇</string>
+    <string name="prefs_wena3_led_none">賱丕 賷賵噩丿 LED</string>
+    <string name="prefs_wena3_vibration_step_down">鬲賳丨賷 賱賱丕爻賮賱</string>
+    <string name="prefs_wena3_vibration_smart_item">丕賱丕賴鬲夭丕夭 丕賱匕賰賷</string>
+    <string name="prefs_wena3_home_icon_name_riiiver">Riiiver</string>
+    <string name="prefs_wena3_hint_background_sync">丕爻賲丨 賱賭 Wena 亘兀賳 鬲胤賱亘 亘卮賰賱 丿賵乇賷 賲賳 Gadgetbridge 鬲賳夭賷賱 亘賷丕賳丕鬲 丕賱賳卮丕胤 賲賳 丕賱噩賴丕夭</string>
+    <string name="prefs_wena3_home_icon_name_suica">乇氐賷丿 爻賵賷賰丕</string>
+    <string name="prefs_wena3_vibration_strength_item_medium">賲鬲賵爻胤</string>
+    <string name="prefs_wena3_title_activity">廿毓丿丕丿丕鬲 丕賱賳卮丕胤</string>
+    <string name="prefs_wena3_vibration_continuous">賲鬲賵丕氐賱</string>
+    <string name="prefs_wena3_home_icon_name_pedometer">毓丿丿 丕賱禺胤賵丕鬲</string>
+    <string name="prefs_wena3_home_icon_name_qrio">Qrio</string>
+    <string name="white">兀亘賷囟</string>
+    <string name="prefs_wena3_home_icon_name_clock">丕賱賵賯鬲 丕賱丨丕賱賷</string>
+    <string name="prefs_wena3_item_background_sync">鈥呚藏з呝嗀� 亘賷丕賳丕鬲 丕賱賳卮丕胤 賮賷 丕賱禺賱賮賷丞</string>
+    <string name="prefs_wena3_vibration_strength_item_weak">囟毓賷賮</string>
+    <string name="prefs_wena3_home_icon_title">兀賷賯賵賳丕鬲 丕賱卮丕卮丞 丕賱乇卅賷爻賷丞</string>
+    <string name="prefs_wena3_item_weather_statusbar">鈥з勜焚傌� 賮賷 卮乇賷胤 丕賱丨丕賱丞</string>
+    <string name="temperature_scale_celsius">丿乇噩丞 賲卅賵賷丞</string>
+    <string name="prefs_wena3_vibration_step_up">鬲賳丨賷 賱賱丕毓賱賶</string>
+    <string name="prefs_home_icon_right_item">丕賱賷賲賷賳</string>
+    <string name="prefs_wena3_vibration_basic">兀爻丕爻賷</string>
+    <string name="prefs_wena3_title_screen">丕毓丿丕丿丕鬲 丕賱卮丕卮丞</string>
+    <string name="prefs_wena3_item_large_font">丨噩賲 丕賱禺胤 兀賰亘乇</string>
+    <string name="prefs_wena3_home_icon_name_energy">胤丕賯丞 丕賱噩爻賲</string>
+    <string name="prefs_wena3_notification_vibration_repetition_2">賲乇鬲賷賳</string>
+    <string name="prefs_wena3_auto_power_off_hint">爻賷鬲賲 廿賷賯丕賮 鬲卮睾賷賱 丕賱噩賴丕夭 賵鬲卮睾賷賱賴 鬲賱賯丕卅賷賸丕 賵賮賯賸丕 賱賱噩丿賵賱 丕賱夭賲賳賷 丕賱賲丨丿丿</string>
+    <string name="prefs_wena3_hint_lift_wrist">賯賲 亘鬲卮睾賷賱 丕賱卮丕卮丞 毓賳丿賲丕 鬲賳馗乇 廿賱賶 賲毓氐賲賰</string>
+    <string name="prefs_wena3_hint_large_font">鈥操娯ж� 丨噩賲 丕賱禺胤 賮賷 丕賱鬲賯賵賷賲 賵丕賱廿卮毓丕乇丕鬲 賵賲丕 廿賱賶 匕賱賰.</string>
+    <string name="prefs_wena3_button_action_item_double">囟睾胤丞 賲夭丿賵噩丞</string>
+    <string name="prefs_wena3_vibration_rapid">爻乇賷毓</string>
+    <string name="prefs_wena3_button_action_name_qrio_unlock">Qrio 賮鬲丨</string>
+    <string name="temperature_scale_cf">賲賯賷丕爻 丿乇噩丞 丕賱丨乇丕乇丞</string>
+    <string name="prefs_wena3_notification_vibration_repetition_1">賲乇丞</string>
+    <string name="prefs_wena3_receive_calls_item">鬲賱賯賷 廿卮毓丕乇丕鬲 丕賱賲賰丕賱賲丕鬲</string>
+    <string name="prefs_wena3_hint_weather_statusbar">廿馗賴丕乇 兀賷賯賵賳丞 丕賱馗乇賵賮 丕賱丨丕賱賷丞 賮賷 丕賱夭丕賵賷丞 丕賱毓賱賵賷丞 丕賱賷爻乇賶 賲賳 丕賱卮丕卮丞 丕賱乇卅賷爻賷丞</string>
+    <string name="prefs_wena3_home_icon_name_wena_pay">丿賮毓 Wena</string>
+    <string name="prefs_wena3_button_action_item">廿噩乇丕亍 丕賱夭乇</string>
+    <string name="prefs_wena3_auto_power_off_turn_on_time_item">丕爻鬲卅賳丕賮 丕賱賵賯鬲</string>
+    <string name="prefs_wena3_notification_default_call_vibration">丕賴鬲夭丕夭 丕賱賲賰丕賱賲丕鬲 丕賱賵丕乇丿丞</string>
+    <string name="green">兀禺囟乇</string>
+    <string name="yellow">兀氐賮乇</string>
+    <string name="devicetype_sony_wena3">Sony Wena 3</string>
+    <string name="prefs_wena3_notification_default_call_led">賱賵賳 LED 賱賱賲賰丕賱賲丕鬲 丕賱賵丕乇丿丞</string>
+    <string name="red">兀丨賲乇</string>
+    <string name="cyan">丕夭乇賯 爻賲丕賵賷</string>
+    <string name="prefs_wena3_vibration_siren">Siren</string>
+    <string name="temperature_scale_cf_summary">丨丿丿 賲丕 廿匕丕 賰丕賳 丕賱噩賴丕夭 賷爻鬲禺丿賲 賲賯賷丕爻 賲卅賵賷丞 兀賵 賮賴乇賳賴丕賷鬲.</string>
+    <string name="prefs_wena3_menu_icon_title">丕賷賯賵賳丕鬲 丕賱賯丕卅賲丞</string>
+    <string name="prefs_wena3_notification_default_call_vibration_repetition">丕賴鬲夭丕夭 賲鬲賰乇乇 賱賱賲賰丕賱賲丕鬲 丕賱賵丕乇丿丞</string>
+    <string name="prefs_wena3_notification_default_vibration">丕賴鬲夭丕夭 丕賱廿卮毓丕乇丕鬲</string>
+    <string name="prefs_wena3_notification_vibration_repetition_0">睾賷乇 賲丨丿丿</string>
+    <string name="prefs_wena3_notification_vibration_repetition_4">4 賲乇丕鬲</string>
+    <string name="temperature_scale_fahrenheit">賮賴乇賳賴丕賷鬲</string>
+    <string name="prefs_wena3_vibration_strength_item">賯賵丞 丕賱丕賴鬲夭丕夭</string>
+    <string name="prefs_wena3_receive_calls_hint">廿匕丕 鬲賲 廿賷賯丕賮 鬲卮睾賷賱賴貙 賮賱賳 賷鬲賲 廿毓賱丕賲賰 亘丕賱賲賰丕賱賲丕鬲 丕賱賵丕乇丿丞 毓賱賶 Wena</string>
+    <string name="prefs_wena3_button_action_item_long">囟睾胤丞 賲胤賵賱丞</string>
+    <string name="prefs_wena3_menu_icon_name_payment">丕賱賲丿賮賵毓丕鬲</string>
+    <string name="prefs_wena3_status_page_title">鬲乇鬲賷亘 氐賮丨丞 丕賱丨丕賱丞</string>
+    <string name="prefs_wena3_title_alarm">廿毓丿丕丿丕鬲 丕賱鬲賳亘賷賴</string>
+    <string name="prefs_wena3_notification_default_vibration_repetition">鬲賰乇丕乇 丕賴鬲夭丕夭 丕賱廿卮毓丕乇丕鬲</string>
+    <string name="prefs_home_icon_center_item">丕賱賵爻胤</string>
+    <string name="prefs_wena3_hint_rich_design">鈥娯顿娰� 賲爻鬲胤賷賱丕鬲 賲爻鬲丿賷乇丞 丨賵賱 兀賷賯賵賳丕鬲 丕賱卮丕卮丞 丕賱乇卅賷爻賷丞</string>
+    <string name="prefs_wena3_notification_default_led">賱賵賳 LED 賱賱廿卮毓丕乇丕鬲</string>
+    <string name="prefs_wena3_auto_power_off_item">廿賷賯丕賮 丕賱鬲卮睾賷賱 丕賱鬲賱賯丕卅賷</string>
+    <string name="prefs_wena3_home_icon_name_edy">鬲賵丕夭賳 Edy</string>
+    <string name="prefs_wena3_vibration_warning">鬲丨匕賷乇</string>
+    <string name="prefs_wena3_smart_alarm_margin_item">賴丕賲卮 丕賱鬲賳亘賷賴 丕賱匕賰賷</string>
+    <string name="prefs_wena3_item_rich_design">丕爻鬲禺丿賲 丕賱鬲氐賲賷賲 丕賱睾賳賷</string>
+    <string name="prefs_wena3_day_start_hour_item">賷亘丿兀 丕賱賷賵賲 賮賷</string>
+    <string name="prefs_wena3_notification_per_app_settings_title">廿毓丿丕丿丕鬲 丕賱廿卮毓丕乇丕鬲 賱賰賱 鬲胤亘賷賯</string>
+    <string name="prefs_wena3_vibration_none">亘丿賵賳 丕賴鬲夭丕夭</string>
+    <string name="purple">亘賳賮爻噩賷</string>
+    <string name="prefs_wena3_vibration_strength_item_strong">賯賵賷</string>
+    <string name="prefs_wena3_vibration_triple">孬賱丕孬賷丞</string>
+    <string name="blue">兀夭乇賯</string>
+    <string name="prefs_wena3_button_action_name_activity_screen">卮丕卮丞 丕賱賳卮丕胤</string>
+    <string name="prefs_wena3_notification_settings_title">廿毓丿丕丿丕鬲 丕賱廿卮毓丕乇丕鬲</string>
+    <string name="devicetype_sony_wf_1000xm5">爻賵賳賷 WF-1000XM5</string>
+    <string name="pref_message_privacy_mode_bodyonly">廿禺賮丕亍 丕賱噩爻賲 賮賯胤</string>
+    <string name="latvian">丕賱賱丕鬲賮賷丞</string>
+    <string name="common_symbols">丕賱乇賲夭 丕賱賲卮鬲乇賰</string>
+    <string name="pref_title_notifications_ignore_work_profile">鬲噩丕賴賱 廿卮毓丕乇丕鬲 丕賱賲賱賮 丕賱卮禺氐賷 賱賱毓賲賱</string>
+    <string name="pref_summary_notifications_ignore_work_profile">賱丕 鬲乇爻賱 廿卮毓丕乇丕鬲 賲賳 丕賱鬲胤亘賷賯丕鬲 丕賱賲賵噩賵丿丞 賮賷 丕賱賲賱賮 丕賱卮禺氐賷 賱賱毓賲賱 廿賱賶 丕賱爻丕毓丞</string>
+    <string name="zepp_os_watchface_pure_white">兀亘賷囟 賳賯賷</string>
+    <string name="avgStrokeRate">賲鬲賵爻胤 賲毓丿賱 丕賱禺賳賯</string>
+    <string name="zepp_os_watchface_the_ultima">丕賱賳賴丕賷丞</string>
+    <string name="zepp_os_watchface_city_of_speed">賲丿賷賳丞 丕賱爻乇毓丞</string>
+    <string name="menuitem_headphone">爻賲丕毓丞 丕賱乇兀爻</string>
+    <string name="femometer_measurement_mode_normal">丕賱賵囟毓 丕賱毓丕丿賷 (60孬-90孬)</string>
+    <string name="zepp_os_watchface_free_combination">賲夭賷噩 丨乇</string>
+    <string name="menuitem_body_composition">鬲乇賰賷亘 丕賱噩爻賲</string>
+    <string name="menuitem_zepp_pay">丿賮毓 夭賷亘</string>
+    <string name="femometer_measurement_mode_quick">丕賱賵囟毓 丕賱爻乇賷毓 (30 孬丕賳賷丞)</string>
+    <string name="zepp_os_watchface_starry_sky">爻賲丕亍 丕賱賳噩賵賲</string>
+    <string name="devicetype_amazfit_balance">乇氐賷丿 兀賲丕夭賮賷鬲</string>
+    <string name="yard">丕賱賮賳丕亍</string>
+    <string name="strokes_minute">卮丕乇毓 / 丿賯賷賯丞</string>
+    <string name="femometer_measurement_mode_title">胤乇賷賯丞 丕賱賯賷丕爻</string>
+    <string name="femometer_measurement_mode_precise">丕賱賵囟毓 丕賱丿賯賷賯 (3 丿賯丕卅賯)</string>
+    <string name="zepp_os_watchface_vast_sky">丕賱爻賲丕亍 丕賱賵丕爻毓丞</string>
+    <string name="menuitem_workout_shortcuts">丕禺鬲氐丕乇丕鬲 丕賱鬲賲乇賷賳</string>
+    <string name="maxStrokeRate">丕賱丨丿 丕賱兀賯氐賶 賱賲毓丿賱 丕賱囟乇亘</string>
+    <string name="zepp_os_watchface_lightning_flash">囟賵亍 丕賱亘乇賯</string>
+    <string name="laneLength">胤賵賱 丕賱賲爻丕乇</string>
+    <string name="strokes">賲噩賲賵毓 丕賱囟乇亘丕鬲</string>
+    <string name="menuitem_apps_shortcuts">丕禺鬲氐丕乇丕鬲 丕賱鬲胤亘賷賯丕鬲</string>
+    <string name="zepp_os_watchface_guider">賱鬲賵噩賷賴</string>
+    <string name="menuitem_readiness">丕賱賯乇丕亍丞</string>
+    <string name="menuitem_thermometer">賲賯賷丕爻 丕賱丨乇丕乇丞</string>
+    <string name="devicetype_femometer_vinca2">Femometer Vinca II</string>
+    <string name="busy_task_fetch_statistics">鈥ж辟� 噩賱亘 丕賱廿丨氐丕卅賷丕鬲</string>
+    <string name="fossil_hr_nav_app_not_installed_notify_text">亘丿兀 丕賱鬲賳賯賱 賵賱賰賳 鬲胤亘賷賯 丕賱鬲賳賯賱 睾賷乇 賲孬亘鬲 毓賱賶 丕賱爻丕毓丞. 賷乇噩賶 鬲孬亘賷鬲賴 賲賳 賲丿賷乇 丕賱鬲胤亘賷賯丕鬲.</string>
+    <string name="busy_task_fetch_temperature">噩丕乇賺 噩賱亘 亘賷丕賳丕鬲 丿乇噩丞 丕賱丨乇丕乇丞</string>
+    <string name="fossil_hr_nav_app_not_installed_notify_title">鬲胤亘賷賯 賳馗丕賲 丕賱賲賱丕丨丞 賱賲 賷賲 鬲孬亘賷鬲賴 賮賷 丕賱爻丕毓丞</string>
+    <string name="call_rejection_method_ignore">鬲噩丕賴賱 (賰鬲賲)</string>
+    <string name="call_rejection_method_reject">乇賮囟</string>
+    <string name="pref_call_rejection_method_title">胤乇賷賯丞 乇賮囟 丕賱賲賰丕賱賲丕鬲</string>
+    <string name="pref_call_rejection_method_summary">賲丕 賴賵 丕賱廿噩乇丕亍 丕賱匕賷 賷鬲賲 丕鬲禺丕匕賴 毓賳丿 乇賮囟 賲賰丕賱賲丞 賵丕乇丿丞 賲賳 丕賱爻丕毓丞</string>
+    <string name="devicetype_amazfit_active_edge">Amazfit Active Edge</string>
+    <string name="charts_legend_spo2_average">賲鬲賵爻胤 兀賰爻噩賷賳 丕賱丿賲</string>
+    <string name="devicetype_amazfit_active">Amazfit Active</string>
+    <string name="pref_title_navigation_apps">鬲胤亘賷賯丕鬲 丕賱賲賱丕丨丞</string>
+    <string name="pref_title_navigation_prefs">鬲賮囟賷賱丕鬲 丕賱賲賱丕丨丞</string>
+    <string name="pref_navigation_app_gmaps">禺乇丕卅胤 賯賵賯賱</string>
+    <string name="pref_summary_prefix_notification_with_app">毓賳賵丕賳 丕賱廿卮毓丕乇 丕賱賲丿賲噩 賲毓 丕爻賲 丕賱鬲胤亘賷賯 丕賱兀爻丕爻賷</string>
+    <string name="pref_summary_osmand_packagename">賷爻鬲禺丿賲 賱鬲丨丿賷丿 廿氐丿丕乇 OsmAnd 賱賱丕鬲氐丕賱 亘賴</string>
+    <string name="pref_title_prefix_notification_with_app">丕爻賲 丕賱鬲胤亘賷賯 賮賷 丕賱廿卮毓丕乇</string>
+    <string name="pref_title_osmand_packagename">丕爻賲 丕賱丨夭賲丞 OsmAnd</string>
+    <string name="pref_navigation_app_osmand">OsmAnd(+)</string>
+    <string name="pref_title_casio_alert_other">鬲賳亘賷賴 賱賱廿禺胤丕乇丕鬲 丕賱兀禺乇賶</string>
+    <string name="pref_title_casio_alert_calendar">丕賱鬲賳亘賷賴 賱廿卮毓丕乇丕鬲 丕賱鬲賯賵賷賲</string>
+    <string name="pref_summary_casio_alert_call">鬲賳亘賷賴 (丕賴鬲夭丕夭/氐丕賮乇丞) 賱賱賲賰丕賱賲丕鬲 丕賱賵丕乇丿丞</string>
+    <string name="devicetype_miband8">爻賵丕乇 卮丕賵賲賷 丕賱匕賰賷 8</string>
+    <string name="menuitem_running">丕賱噩乇賷</string>
+    <string name="pref_sleep_mode_schedule_title">丕賱噩丿賵賱 丕賱夭賲賳賷 賱賱賳賵賲</string>
+    <string name="pref_title_casio_alert_call">鬲賳亘賷賴 賱賱賲賰丕賱賲丕鬲 丕賱賵丕乇丿丞</string>
+    <string name="bedtime">賵賯鬲 丕賱賳賵賲</string>
+    <string name="pref_title_casio_alert_email">鬲賳亘賷賴 賱廿卮毓丕乇丕鬲 丕賱亘乇賷丿 丕賱廿賱賰鬲乇賵賳賷</string>
+    <string name="pref_summary_casio_alert_email">鬲賳亘賷賴 (丕賴鬲夭丕夭/氐丕賮乇丞) 賱廿卮毓丕乇丕鬲 丕賱亘乇賷丿 丕賱廿賱賰鬲乇賵賳賷</string>
+    <string name="menuitem_focus">丕賱鬲乇賰賷夭</string>
+    <string name="pref_summary_casio_alert_sms">鬲賳亘賷賴 (丕賴鬲夭丕夭/氐丕賮乇丞) 賱廿卮毓丕乇丕鬲 丕賱乇爻丕卅賱 丕賱賯氐賷乇丞 (丕賱乇爻丕卅賱 丕賱賳氐賷丞)</string>
+    <string name="pref_title_preview_message_in_title">廿馗賴丕乇 賲毓丕賷賳丞 賱賱乇爻丕賱丞 賮賷 丕賱毓賳賵丕賳</string>
+    <string name="devicetype_redmiwatch3active">爻丕毓丞 乇賷丿賲賷 3 丕賰鬲賷賮</string>
+    <string name="pref_summary_casio_alert_calendar">鬲賳亘賷賴 (丕賴鬲夭丕夭/氐丕賮乇丞) 賱廿卮毓丕乇丕鬲 丕賱鬲賯賵賷賲</string>
+    <string name="serial_number">丕賱乇賯賲 丕賱鬲爻賱爻賱賷</string>
+    <string name="danish">丕賱丿丕賳賲丕乇賰賷丞</string>
+    <string name="wake_up_time">丕爻鬲賷賯馗</string>
+    <string name="menuitem_stats">丕賱丨丕賱丞</string>
+    <string name="pref_summary_casio_alert_other">鬲賳亘賷賴 (丕賴鬲夭丕夭/氐丕賮乇丞) 賱賱廿卮毓丕乇丕鬲 賮賷 丕賱賮卅丞 丕賱兀禺乇賶</string>
+    <string name="devicetype_xiaomi_watch_lite">爻丕毓丞 卮丕賵賲賷 賱丕賷鬲</string>
+    <string name="pref_summary_preview_message_in_title">賷馗賴乇 賲毓丕賷賳丞 賱賱乇爻丕賱丞 賮賷 毓賳賵丕賳 丕賱廿卮毓丕乇 丨爻亘 賲丕 賷爻賲丨 亘賴 丕賱噩賴丕夭</string>
+    <string name="pref_sleep_mode_schedule_summary">兀乇爻賱 鬲匕賰賷乇賸丕 賵丕丿禺賱 廿賱賶 賵囟毓 丕賱爻賰賵賳 賮賷 賵賯鬲 丕賱賳賵賲. 賮賷 賵賯鬲 丕賱丕爻鬲賷賯丕馗 丕賱賲丨丿丿貙 爻賷氐丿乇 賲賳亘賴 丕賱丕爻鬲賷賯丕馗 氐賵鬲賸丕.</string>
+    <string name="pref_title_casio_alert_sms">鬲賳亘賷賴 賱廿卮毓丕乇丕鬲 丕賱乇爻丕卅賱 丕賱賯氐賷乇丞</string>
+    <string name="menuitem_alerts">鬲賳亘賷賴丕鬲</string>
+    <string name="devicetype_miband7pro">爻賵丕乇 卮丕賵賲賷 丕賱匕賰賷 7 亘乇賵</string>
+    <string name="devicetype_xiaomi_watch_s1_active">爻丕毓丞 卮丕賵賲賷S1 丕賱賳卮胤丞</string>
+    <string name="devicetype_mi_watch_color_sport">爻丕毓丞 賲賷 賰賵賱賵乇 爻亘賵乇鬲</string>
+    <string name="activity_type_trail_run">賲爻丕乇 丕賱噩乇賷</string>
+    <string name="widget_layout">鬲禺胤賷胤 丕賱賯胤毓丞</string>
+    <string name="widget_screen_no_free_slots_description">賱丕 賷丨鬲賵賷 丕賱噩賴丕夭 毓賱賶 賮鬲丨丕鬲 賲噩丕賳賷丞 賱卮丕卮丕鬲 丕賱兀丿賵丕鬲 (廿噩賲丕賱賷 丕賱賮鬲丨丕鬲: %1$s)</string>
+    <string name="pref_device_action_dnd_on">毓丿賲 丕賱廿夭毓丕噩 - 鬲卮睾賷賱</string>
+    <string name="prefs_phone_silent_mode">丕賱賵囟毓 丕賱氐丕賲鬲 賱賱賴丕鬲賮</string>
+    <string name="widget_unknown_workout">鬲賲乇賷賳 睾賷乇 賲毓乇賵賮 - %s</string>
+    <string name="prefs_wearmode">賵囟毓 丕賱丕乇鬲丿丕亍</string>
+    <string name="wearmode_pebble">丨氐丕丞 (賲卮亘賰 丕賱丨匕丕亍)</string>
+    <string name="widget_move_up">鬲丨乇賰 賱賱兀毓賱賶</string>
+    <string name="widget_screen_delete_confirm_description">賴賱 兀賳鬲 賲鬲兀賰丿 兀賳賰 鬲乇賷丿 丨匕賮 \'%1$s\'責</string>
+    <string name="clap_hands_to_wakeup_device">氐賮賯 丕賱賷丿賷賳 賱乇賮毓 丕賱卮丕卮丞</string>"
+    <string name="silent_mode_normal_vibrate">毓丕丿賷 / 丕賴鬲夭丕夭</string>
+    <string name="pref_title_goal_secondary">賴丿賮 孬丕賳賵賷</string>
+    <string name="pref_vitality_score_7_day_summary">丕丨氐賱 毓賱賶 廿卮毓丕乇 毓賳丿賲丕 鬲氐賱 丿乇噩丞 丨賷賵賷鬲賰 廿賱賶 30 兀賵 60 兀賵 100 禺賱丕賱 丕賱兀賷丕賲 丕賱爻亘毓丞 丕賱賲丕囟賷丞</string>
+    <string name="activity_type_trekking">丕賱乇丨賱丕鬲</string>
+    <string name="devicetype_pixoo">Pixoo</string>
+    <string name="pref_vitality_score_title">毓丿丿 丕賱囟丨丕賷丕</string>
+    <string name="widget_layout_two">2 毓賳丕氐乇 賵丕噩賴丞 賲爻鬲禺丿賲</string>
+    <string name="widget_screen_delete_confirm_title">丨匕賮 卮丕卮丞 丕賱賯胤毓丞</string>
+    <string name="pref_device_action_dnd_alarms">毓丿賲 丕賱廿夭毓丕噩 - 丕賱廿賳匕丕乇丕鬲 賮賯胤</string>
+    <string name="clap_hands_to_wakeup_device_summary">丕賱鬲氐賮賷賯 賲乇丞 兀禺乇賶 爻賵賮 賷胤賮卅 丕賱卮丕卮丞</string>"
+    <string name="widget_layout_top_1_bot_2">1 兀毓賱賶貙 2 兀爻賮賱</string>
+    <string name="devicetype_colacao23">ColaCao 2023</string>
+    <string name="widget_layout_top_2_bot_2">2 兀毓賱賶貙 2 兀爻賮賱</string>
+    <string name="widget_screen_min_screens">賷噩亘 兀賳 賷賰賵賳 賴賳丕賰 %1$s 賲賳 丕賱卮丕卮丕鬲 毓賱賶 丕賱兀賯賱</string>
+    <string name="silent_mode_vibrate_silent">賷賴鬲夭 / 氐丕賲鬲</string>
+    <string name="widget">丕賱鬲胤亘賷賯丕鬲 丕賱賲氐睾乇丞</string>
+    <string name="pref_vitality_score_daily_summary">丕丨氐賱 毓賱賶 廿卮毓丕乇 毓賳丿賲丕 鬲氐賱 廿賱賶 丕賱丨丿 丕賱兀賯氐賶 賱毓丿丿 賳賯丕胤 丕賱丨賷賵賷丞 賱賴匕丕 丕賱賷賵賲</string>
+    <string name="devicetype_redmi_smart_band_2">乇賷丿賲賷 爻賲丕乇鬲 亘丕賳丿 2</string>
+    <string name="widget_screen">卮丕卮丞 丕賱賯胤毓丞</string>
+    <string name="pref_summary_send_app_notifications">廿乇爻丕賱 廿卮毓丕乇丕鬲 丕賱鬲胤亘賷賯 廿賱賶 丕賱噩賴丕夭</string>
+    <string name="widget_move_down">鬲丨乇賰 賱兀爻賮賱</string>
+    <string name="widget_layout_single">1 丕賱賯胤毓丞</string>
+    <string name="pref_device_action_dnd_priority">毓丿賲 丕賱廿夭毓丕噩 - 丕賱兀賵賱賵賷丞 賮賯胤</string>
+    <string name="not_set">賱賲 賷鬲賲 鬲毓賷賷賳賴</string>
+    <string name="widget_subtype">丕賱賳賵毓 丕賱賮乇毓賷 賱賱賯胤毓丞</string>
+    <string name="standing_time">丕賱賵賯鬲 丕賱丿丕卅賲</string>
+    <string name="active_time">賵賯鬲 賳卮胤</string>
+    <string name="wearmode_band">丕賱賮乇賯丞 (賲毓氐賲賴)</string>
+    <string name="widget_screen_x">丕賱卮丕卮丞 %s</string>
+    <string name="widget_missing_parts">丕賱乇噩丕亍 鬲丨丿賷丿 賰丕賮丞 丕賱賯胤毓 丕賱賲氐睾乇丞</string>
+    <string name="widget_layout_top_2_bot_1">2 兀毓賱賶貙 1 兀爻賮賱</string>
+    <string name="devicetype_colacao21">ColaCao 2021</string>
+    <string name="wearmode_necklace">賯賱丕丿丞 (丨夭丕賲 丕賱乇賯亘丞)</string>
+    <string name="pref_title_send_app_notifications">廿乇爻丕賱 丕賱廿禺胤丕乇丕鬲</string>
+    <string name="pixoo_power_saving_summary">爻賷鬲賲 廿賷賯丕賮 鬲卮睾賷賱 丕賱卮丕卮丞 亘毓丿 兀賳 賷賰鬲卮賮 丕賱賲賷賰乇賵賮賵賳 丕賱氐賲鬲 賱賮鬲乇丞 賲賳 丕賱賵賯鬲</string>
+    <string name="pref_vitality_score_daily_title">丕賱鬲賯丿賲 丕賱賷賵賲賷</string>
+    <string name="pref_vitality_score_7_day_title">丕賱鬲賯丿賲 賱賲丿丞 7 兀賷丕賲</string>
+    <string name="pref_device_action_dnd_off">毓丿賲 丕賱廿夭毓丕噩 - 廿賷賯丕賮</string>
+    <string name="silent_mode_normal_silent">毓丕丿賷 / 氐丕賲鬲</string>
+    <string name="activity_type_wrestling">丕賱賲氐丕乇毓丞</string>
+    <string name="devicetype_mijia_lywsd03">Mijia 賲爻鬲卮毓乇 丿乇噩丞 丕賱丨乇丕乇丞 賵丕賱乇胤賵亘丞 2</string>
+    <string name="pref_title_fossil_hr_navigation_instructions">鬲毓賱賷賲丕鬲 丕賱賲賱丕丨丞</string>
+    <string name="pref_summary_fossil_hr_navigation_instructions">賯賲 亘鬲賰賵賷賳 爻賱賵賰 丕賱鬲胤亘賷賯 兀孬賳丕亍 丕賱鬲賳賯賱</string>
+    <string name="pref_summary_fossil_hr_nav_foreground">賲丕 廿匕丕 賰丕賳 賷噩亘 兀賳 賷馗賴乇 鬲胤亘賷賯 丕賱鬲賳賯賱 鬲賱賯丕卅賷賸丕 賮賷 丕賱賲賯丿賲丞 毓賳丿賲丕 賷鬲賱賯賶 鬲毓賱賷賲丕鬲 丕賱鬲賳賯賱</string>
+    <string name="pref_title_fossil_hr_nav_vibrate">丕賱丕賴鬲夭丕夭 毓賳丿 丕賱鬲毓賱賷賲丕鬲 丕賱噩丿賷丿丞</string>
+    <string name="pref_title_fossil_hr_nav_foreground">鬲毓丕賱 廿賱賶 丕賱賲賯丿賲丞</string>
+    <string name="prefs_device_name">丕爻賲 丕賱噩賴丕夭</string>
+    <string name="pref_summary_fossil_hr_nav_vibrate">賲丕 廿匕丕 賰丕賳 賷噩亘 兀賳 鬲賴鬲夭 丕賱爻丕毓丞 毓賳丿 賰賱 鬲毓賱賷賲丕鬲 鬲賳賯賱 噩丿賷丿丞 兀賵 鬲賲 鬲睾賷賷乇賴丕 (賮賯胤 毓賳丿賲丕 賷賰賵賳 丕賱鬲胤亘賷賯 賮賷 丕賱賲賯丿賲丞)</string>
+    <string name="devicetype_redmi_watch_2_lite">爻丕毓丞 乇賷丿賲賷 2 賱丕賷鬲</string>
+    <string name="devicetype_redmi_smart_band_pro">爻賵丕乇 乇賷丿賲賷 亘乇賵 丕賱匕賰賷</string>
+    <string name="notification_channel_connection_status_name">丨丕賱丞 丕賱廿鬲氐丕賱</string>
+    <string name="devicetype_nothingear2">賳賵孬賷賳噩 Ear (2)</string>
+    <string name="prefs_active_noise_cancelling_transparency">丕賱卮賮丕賮賷丞</string>
+    <string name="devicetype_nothingearstick">賳賵孬賷賳噩 Ear (Stick)</string>
+    <string name="prefs_active_noise_cancelling_light">廿賱睾丕亍 丕賱囟賵囟丕亍 丕賱賳卮胤丞 丕賱禺賮賷賮丞</string>
+    <string name="devicetype_huawei_watchgt2e">爻丕毓丞 賴賵丕賵賷 噩賷 鬲賷 2 丕賷</string>
+    <string name="pref_heartrate_automatic_enable">鬲賲賰賷賳 賯賷丕爻 賲毓丿賱 囟乇亘丕鬲 丕賱賯賱亘 丕賱鬲賱賯丕卅賷</string>
+    <string name="pref_spo_automatic_enable">鬲賲賰賷賳 賯賷丕爻 賲毓丿賱 丕賱兀賵賰爻噩賷賳 丕賱鬲賱賯丕卅賷</string>
+    <string name="devicetype_xiaomi_watch_s1_pro">爻丕毓丞 卮丕賵賲賷 S1 亘乇賵</string>
+    <string name="devicetype_xiaomi_watch_s3">爻丕毓丞 卮丕賵賲賷 丕爻 伲</string>
+    <string name="uploadwatchfaceoperation_complete">丕賰鬲賲賱 鬲孬亘賷鬲 賵丕噩賴丞 丕賱爻丕毓丞</string>
+    <string name="pref_activity_recognize_biking">丕賱鬲毓乇賮 毓賱賶 乇賰賵亘 丕賱丿乇丕噩丕鬲</string>
+    <string name="pref_activity_recognize_walking">丕賱鬲毓乇賮 毓賱賶 丕賱賲卮賷</string>
+    <string name="flatDistance">賲爻丕賮丞 賲爻胤丨丞</string>
+    <string name="sony_protocol_v1">丕賱廿氐丿丕乇 佟</string>
+    <string name="sony_protocol_v2">丕賱廿氐丿丕乇 佗</string>
+    <string name="sony_protocol_v3">丕賱廿氐丿丕乇 伲</string>
+    <string name="protocol_version">廿氐丿丕乇 丕賱亘乇賵鬲賵賰賵賱</string>
+    <string name="uploadwatchfaceoperation_in_progress">噩丕乇賺 鬲丨賲賷賱 賵丕噩賴丞 丕賱爻丕毓丞</string>
+    <string name="pref_force_connection_type_bt_classic">亘賱賵鬲賵孬 賰賱丕爻賷賰</string>
+    <string name="pref_force_connection_type_description">賷賲賰賳賰 賲丨丕賵賱丞 賮乇囟 賳賵毓 丕賱丕鬲氐丕賱 賮賷 丨丕賱丞 毓丿賲 丕爻鬲噩丕亘丞 噩賴丕夭賰 賱賭 Gadgetbridge</string>
+    <string name="devicetype_honor_band3">爻賵丕乇 丕賵賳乇 伲</string>
+    <string name="devicetype_honor_band4">爻賵丕乇 丕賵賳乇 伽</string>
+    <string name="devicetype_honor_band5">爻賵丕乇 丕賵賳乇 佶</string>
+    <string name="devicetype_honor_band6">爻賵丕乇 丕賵賳乇 佴</string>
+    <string name="devicetype_honor_band7">爻賵丕乇 丕賵賳乇 侑</string>
+    <string name="pref_activity_recognize_rowing">丕賱鬲毓乇賮 毓賱賶 丕賱鬲噩丿賷賮</string>
+    <string name="pref_activity_recognition_mode_none">賱丕卮賷亍</string>
+    <string name="pref_enable_call_accept_summary">鬲賲賰賷賳 賯亘賵賱 丕賱賲賰丕賱賲丕鬲 賲賳 丕賱噩賴丕夭</string>
+    <string name="pref_enable_call_accept">鬲賲賰賷賳 賯亘賵賱 丕賱賲賰丕賱賲丕鬲</string>
+    <string name="pref_force_dnd_support_summary">賮乇囟 丿毓賲 毓丿賲 丕賱廿夭毓丕噩.
+\n丕爻鬲禺丿丕賲 毓賱賶 賲爻丐賵賱賷鬲賰 丕賱禺丕氐丞</string>
+    <string name="huawei_ignore_wakeup_status_end">鬲噩丕賴賱 丨丕賱丞 賳賴丕賷丞 丕賱丕爻鬲賷賯丕馗</string>
+    <string name="pref_title_general_reconnectonlytoconnected">兀毓丿 丕賱丕鬲氐丕賱 亘丕賱兀噩賴夭丞 丕賱賲鬲氐賱丞 賮賯胤</string>
+    <string name="pref_summary_general_reconnectonlytoconnected">兀毓丿 丕賱丕鬲氐丕賱 亘丕賱兀噩賴夭丞 丕賱賲鬲氐賱丞 賮賯胤貙 亘丿賱丕賸 賲賳 廿毓丕丿丞 丕賱丕鬲氐丕賱 亘噩賲賷毓 丕賱兀噩賴夭丞</string>
+    <string name="prefs_disconnect_notification_summary">廿卮毓丕乇 毓賱賶 丕賱噩賴丕夭 毓賳丿 賮氐賱賴 毓賳 BT.</string>
+    <string name="manual">賷丿賵賷</string>
+    <string name="dnd_all_day">賰賱 丕賱賷賵賲</string>
+    <string name="devicetype_huawei_band6">爻賵丕乇 賴賵丕賵賷 佴</string>
+    <string name="pref_enable_call_reject_summary">鬲賲賰賷賳 乇賮囟 丕賱賲賰丕賱賲丕鬲 賲賳 丕賱噩賴丕夭</string>
+    <string name="pref_enable_call_reject">鬲賲賰賷賳 乇賮囟 丕賱賲賰丕賱賲丕鬲</string>
+    <string name="pref_force_options_summary">鬲丿毓賷 亘毓囟 丕賱兀噩賴夭丞 賰匕亘丕賸 兀賳賴丕 賱丕 鬲丿毓賲 亘毓囟 丕賱禺賷丕乇丕鬲. 賷賲賰賳 丕爻鬲禺丿丕賲 賴匕賴 丕賱廿毓丿丕丿丕鬲 賱鬲賲賰賷賳賴丕 毓賱賶 兀賷 丨丕賱.
+\n丕爻鬲禺丿丕賲 毓賱賶 賲爻丐賵賱賷鬲賰 丕賱禺丕氐丞
+\n丕賯乇兀 丕賱賵賷賰賷</string>
+    <string name="huawei_reparse_workout_data">廿毓丕丿丞 鬲丨賱賷賱 亘賷丕賳丕鬲 丕賱鬲賲乇賷賳</string>
+    <string name="huawei_ignore_wakeup_status_start_description">賯丿 賷爻丕毓丿 賮賷 丕賰鬲卮丕賮 丕賱賳賵賲 丕賱賲賳丕爻亘. 賲乇卅賷丞 毓賱賶 丕賱賮賵乇 賮賷 毓乇囟 丕賱兀賳卮胤丞 丕賱賷賵賲賷丞.</string>
+    <string name="pref_summary_debug">兀乇爻賱 胤賱亘 鬲氐丨賷丨 廿賱賶 噩賴丕夭 賴賵丕賵賷</string>
+    <string name="pref_title_debug">胤賱亘 丕賱鬲氐丨賷丨</string>
+    <string name="prefs_password_4_digits_0_to_9_summary">賷噩亘 兀賳 鬲鬲賰賵賳 賰賱賲丞 丕賱賲乇賵乇 賲賳 4 兀乇賯丕賲貙 亘丕爻鬲禺丿丕賲 丕賱兀乇賯丕賲 賮賯胤</string>
+    <string name="prefs_workmode">賵囟毓 丕賱毓賲賱</string>
+    <string name="prefs_activity_recognition">廿毓丿丕丿丕鬲 丕賱鬲毓乇賮 毓賱賶 丕賱賳卮丕胤</string>
+    <string name="pref_activity_recognition_mode_auto">鬲賱賯丕卅賷</string>
+    <string name="updatefirmwareoperation_updateproblem_low_battery">亘胤丕乇賷丞 丕賱噩賴丕夭 賲賳禺賮囟丞 噩丿賸丕</string>
+    <string name="do_not_disturb_lift_wrist_summary">賮賯胤 賮賷 丨丕賱丞 鬲賮毓賷賱 丕賱卮丕卮丞 毓賳丿 丕賱乇賮毓</string>
+    <string name="pref_do_not_disturb_not_wear">賱丕 鬲夭毓噩 毓賳丿賲丕 賱丕 鬲乇鬲丿賷</string>
+    <string name="devicetype_huawei_watchgt2">賴賵丕賵賷 賵丕鬲卮 噩賷 鬲賷 2 (亘乇賵)</string>
+    <string name="devicetype_huawei_band4pro">賴賵丕賵賷 亘丕賳丿 4 (亘乇賵)</string>
+    <string name="devicetype_huawei_band7">爻賵丕乇 賴賵丕賵賷 侑</string>
+    <string name="devicetype_huawei_band_aw70">爻賵丕乇 賴賵丕賵賷 (AW70)</string>
+    <string name="devicetype_huawei_band8">爻賵丕乇 賴賵丕賵賷 侉</string>
+    <string name="devicetype_huawei_watch_gt">爻丕毓丞 賴賵丕賵賷 噩賷 鬲賷</string>
+    <string name="devicetype_huawei_watchgt3">賴賵丕賵賷 賵丕鬲卮 噩賷 鬲賷 3 (亘乇賵)</string>
+    <string name="devicetype_huawei_talk_band_b6">賴賵丕賵賷 鬲賵賰 亘丕賳丿 B6</string>
+    <string name="huawei_alarm_smart_description">賱丕 鬲賯賲 亘廿賱睾丕亍 鬲丨丿賷丿 禺丕賳丞 丕禺鬲賷丕乇 丕賱鬲賳亘賷賴 丕賱匕賰賷.</string>
+    <string name="huawei_alarm_event_description">賱丕 鬲丨丿丿 禺丕賳丞 丕禺鬲賷丕乇 丕賱鬲賳亘賷賴 丕賱匕賰賷.</string>
+    <string name="huawei_trusleep_title">賴賵丕賵賷 鬲乇賵 爻賱賷亘鈩�</string>
+    <string name="huawei_trusleep_summary_light">鬲丨爻賷賳 賲乇丕賯亘丞 丕賱賳賵賲</string>
+    <string name="huawei_trusleep_summary">賲乇丕賯亘丞 賳賵毓賷丞 賳賵賲賰 賵賳賲胤 丕賱鬲賳賮爻 賮賷 丕賱賵賯鬲 丕賱丨賯賷賯賷.
+\n賯賲 亘鬲丨賱賷賱 兀賳賲丕胤 賳賵賲賰 賵鬲卮禺賷氐 6 兀賳賵丕毓 賲賳 賲卮丕賰賱 丕賱賳賵賲 亘丿賯丞.</string>
+    <string name="pref_force_wear_location_summary">賮乇囟 丿毓賲 賲賵賯毓 wear.
+\n丕爻鬲禺丿丕賲 毓賱賶 賲爻丐賵賱賷鬲賰 丕賱禺丕氐丞</string>
+    <string name="huawei_reparse_workout_data_description">賱賳 賷賯丿賲 賴匕丕 卮賷卅丕 廿賱丕 亘毓丿 鬲丨丿賷孬丕鬲 賲毓賷賳丞</string>
+    <string name="pref_force_dnd_support">賮乇囟 丿毓賲 毓丿賲 丕賱廿夭毓丕噩</string>
+    <string name="pref_force_wear_location">賮乇囟 賲賵賯毓 wear</string>
+    <string name="huawei_ignore_wakeup_status_end_description">賯丿 賷爻丕毓丿 賮賷 丕賰鬲卮丕賮 丕賱賳賵賲 丕賱賲賳丕爻亘. 賲乇卅賷丞 毓賱賶 丕賱賮賵乇 賮賷 毓乇囟 丕賱兀賳卮胤丞 丕賱賷賵賲賷丞.</string>
+    <string name="huawei_ignore_wakeup_status_start">鬲噩丕賴賱 丨丕賱丞 亘丿亍 丕賱丕爻鬲賷賯丕馗</string>
+    <string name="pref_force_smart_alarm_summary">賮乇囟 丿毓賲 兀噩賴夭丞 丕賱廿賳匕丕乇 丕賱匕賰賷丞.
+\n丕爻鬲禺丿丕賲 毓賱賶 賲爻丐賵賱賷鬲賰 丕賱禺丕氐丞</string>
+    <string name="devicetype_miband8pro">爻賵丕乇 卮丕賵賲賷 丕賱匕賰賷 8 亘乇賵</string>
+    <string name="devicetype_mijia_mho_c303">賲賷噩賷丕 MHO-C303</string>
+    <string name="menuitem_buzzer_intensity">卮丿丞 丕賱噩乇爻</string>
+    <string name="pref_force_smart_alarm">賮乇囟 丕賱鬲賳亘賷賴 丕賱匕賰賷</string>
+    <string name="pref_activity_recognize_running">丕賱鬲毓乇賮 毓賱賶 丕賱噩乇賷</string>
+    <string name="pref_force_options">禺賷丕乇丕鬲 丕賱賯賵丞</string>
+    <string name="pref_activity_recognition_mode_ask">胤賱亘</string>
+    <string name="pref_disable_find_phone_with_dnd">賯賲 亘鬲毓胤賷賱 丕賱毓孬賵乇 毓賱賶 賴丕鬲賮賷 毓賳丿賲丕 賷賰賵賳 賵囟毓 毓丿賲 丕賱廿夭毓丕噩 賳卮胤賸丕</string>
+    <string name="devicetype_xiaomi_watch_s1">爻丕毓丞 卮丕賵賲賷 丕爻 1</string>
+    <string name="uploadwatchfaceoperation_failed">賮卮賱 鬲孬亘賷鬲 賵丕噩賴丞 丕賱爻丕毓丞</string>
+    <string name="pref_force_connection_type_title">賮乇囟 賳賵毓 丕賱丕鬲氐丕賱</string>
+    <string name="uploading_watchface">噩丕乇賺 鬲丨賲賷賱 賵丕噩賴丞 丕賱爻丕毓丞鈥�</string>
+    <string name="pref_force_connection_type_ble">亘賱賵鬲賵孬 廿賱 廿賷</string>
+    <string name="activity_info">賲毓賱賵賲丕鬲 丕賱賳卮丕胤</string>
+    <string name="pref_force_connection_type_auto">鬲賱賯丕卅賷</string>
+    <string name="devicetype_sony_wi_sp600n">爻賵賳賷 WI-SP600N</string>
+    <string name="prefs_heartrate_alert_active_high_threshold">毓鬲亘丞 鬲賳亘賷賴 賲毓丿賱 囟乇亘丕鬲 丕賱賯賱亘 毓丕賱賷丞 丕賱賳卮丕胤</string>
+    <string name="hydration_dnd_summary">賯賲 亘鬲毓胤賷賱 鬲丨匕賷乇丕鬲 丕賱鬲乇胤賷亘 賱賮鬲乇丞 夭賲賳賷丞</string>
+    <string name="activity_type_indoor_running">丕賱乇賰囟 賮賷 丕賱丿丕禺賱</string>
+    <string name="activity_type_cross_trainer">賲丿乇亘 賲鬲賯丕胤毓</string>
+    <string name="activity_type_stair_stepper">丕賱丿乇噩 丕賱爻丕卅乇</string>
+    <string name="activity_type_functional_training">丕賱鬲丿乇賷亘 丕賱賵馗賷賮賷</string>
+    <string name="activity_type_fitness_exercises">丕賱鬲賲丕乇賷賳 丕賱乇賷丕囟賷丞</string>
+    <string name="activity_type_crossfit">賱賷丕賯丞 亘丿賳賷丞</string>
+    <string name="activity_type_physical_training">丕賱鬲丿乇賷亘 丕賱亘丿賳賷</string>
+    <string name="activity_type_horizontal_bar">毓丕乇囟丞 兀賮賯賷丞</string>
+    <string name="activity_type_taekwondo">鬲賷賰賵丕賳丿賵</string>
+    <string name="activity_type_cross_country_running">丕賱噩乇賷 毓亘乇 丕賱亘賱丕丿</string>
+    <string name="activity_type_parallel_bar">賯囟亘丕賳 賲鬲賵丕夭賷丞</string>
+    <string name="activity_type_karate">賰丕乇賷鬲</string>
+    <string name="activity_type_cooldown">賮鬲乇丞 丕賱鬲賴丿卅丞</string>
+    <string name="activity_type_cross_training">丕賱鬲丿乇賷亘 丕賱賲鬲賯丕胤毓</string>
+    <string name="activity_type_sit_ups">丕賱噩賱賵爻</string>
+    <string name="activity_type_fencing">丕賱鬲賳賯賷亘</string>
+    <string name="activity_type_fitness_gaming">兀賱毓丕亘 丕賱賱賷丕賯丞 丕賱亘丿賳賷丞</string>
+    <string name="activity_type_rolling">丕賱鬲丿丨乇噩</string>
+    <string name="activity_type_kendo">賰賷賳丿賵</string>
+    <string name="activity_type_aerobic_exercise">丕賱鬲賲丕乇賷賳 丕賱賴賵丕卅賷丞</string>
+    <string name="activity_type_snow_sports">乇賷丕囟丞 丕賱孬賱噩</string>
+    <string name="devicetype_honor_magicwatch2">爻丕毓丞 賲丕噩賷賰 丕賵賳乇 2</string>
+    <string name="devicetype_nothing_cmf_watch_pro">爻丕毓丞 爻賷 丕賲 丕賮 亘乇賵</string>
+    <string name="devicetype_redmi_watch_2">爻丕毓丞 乇賷丿賲賷 2</string>
+    <string name="heartrate_bpm_155">155 賳亘囟丞 賮賷 丕賱丿賯賷賯丞</string>
+    <string name="heartrate_bpm_165">165 賳亘囟丞 賮賷 丕賱丿賯賷賯丞</string>
+    <string name="heartrate_bpm_175">175 賳亘囟丞 賮賷 丕賱丿賯賷賯丞</string>
+    <string name="heartrate_bpm_185">185 賳亘囟丞 賮賷 丕賱丿賯賷賯丞</string>
+    <string name="heartrate_bpm_195">195 賳亘囟丞 賮賷 丕賱丿賯賷賯丞</string>
+    <string name="heartrate_bpm_205">205 賳亘囟丞 賮賷 丕賱丿賯賷賯丞</string>
+    <string name="activity_type_mountain_hike">賳夭賴丞 噩亘賱賷丞</string>
+    <string name="activity_type_free_training">丕賱鬲丿乇賷亘 丕賱丨乇</string>
+    <string name="activity_type_rower">鈥з勝呚�</string>
+    <string name="activity_type_dynamic_cycle">丕賱丿賵乇丞 丕賱丿賷賳丕賲賷賰賷丞</string>
+    <string name="activity_type_flexibility">丕賱賲乇賵賳丞</string>
+    <string name="activity_type_track_and_field">丕賱賲爻丕乇 賵丕賱賲賷丿丕賳</string>
+    <string name="activity_type_push_ups">鬲賲乇賷賳 丕賱囟睾胤</string>
+    <string name="activity_type_battle_rope">丨亘賱 賯鬲丕賱</string>
+    <string name="activity_type_smith_machine">丌賱丞 爻賲賷孬</string>
+    <string name="activity_type_plank">丕賱賱賵丨</string>
+    <string name="activity_type_javelin">丕賱乇賲丨</string>
+    <string name="activity_type_long_jump">賯賮夭丞 胤賵賷賱丞</string>
+    <string name="activity_type_high_jump">賯賮夭丞 毓丕賱賷丞</string>
+    <string name="activity_type_trampoline">丕賱鬲乇丕賲亘賵賱賷賳</string>
+    <string name="activity_type_dumbbell">丕賱丿賲亘賱</string>
+    <string name="activity_type_belly_dance">丕賱乇賯氐 丕賱卮乇賯賷</string>
+    <string name="activity_type_jazz_dance">乇賯氐丞 丕賱噩丕夭</string>
+    <string name="activity_type_latin_dance">丕賱乇賯氐 丕賱賱丕鬲賷賳賷</string>
+    <string name="activity_type_ballet">亘丕賱賷賴</string>
+    <string name="activity_type_other_dance">乇賯氐丞 兀禺乇賶</string>
+    <string name="activity_type_roller_skating">鬲夭賱噩</string>
+    <string name="activity_type_martial_arts">丕賱賮賳賵賳 丕賱賯鬲丕賱賷丞</string>
+    <string name="activity_type_tai_chi">鬲丕賷 卮賷</string>
+    <string name="activity_type_hula_hooping">丨賵賱丕 賴賵亘</string>
+    <string name="activity_type_disc_sports">丕賱兀賱毓丕亘 丕賱乇賷丕囟賷丞</string>
+    <string name="activity_type_pull_ups">爻丨亘 丕賱賳賮爻 賱賱兀毓賱賶</string>
+    <string name="activity_type_darts">丕賱爻賴丕賲 丕賱氐睾賷乇丞</string>
+    <string name="activity_type_archery">丕賱賯賵爻 賵丕賱爻賴賲</string>
+    <string name="activity_type_horse_riding">乇賰賵亘 丕賱丨氐丕賳</string>
+    <string name="activity_type_kite_flying">丕賱胤丕卅乇丞 丕賱賵乇賯賷丞</string>
+    <string name="activity_type_swing">丕賱鬲兀乇噩丨</string>
+    <string name="activity_type_stairs">爻賱丕賱賲</string>
+    <string name="activity_type_fishing">氐賷丿 丕賱爻賲賰</string>
+    <string name="activity_type_hand_cycling">丕賱丿乇丕噩丞 丕賱賷丿賵賷丞</string>
+    <string name="activity_type_mind_and_body">丕賱毓賯賱 賵丕賱噩爻賲</string>
+    <string name="activity_type_kabaddi">賰丕亘丕丿賷</string>
+    <string name="activity_type_karting">丕賱賰丕乇鬲賷賳噩</string>
+    <string name="activity_type_billiards">亘賱賷丕乇丿賵</string>
+    <string name="activity_type_shuttlecock">賲賰賵賰</string>
+    <string name="activity_type_softball">賰乇丞 禺賮賷賮丞</string>
+    <string name="activity_type_dodgeball">賰乇丞 鬲噩丕賵夭</string>
+    <string name="activity_type_australian_football">賰乇丞 丕賱賯丿賲 丕賱兀爻鬲乇丕賱賷丞</string>
+    <string name="activity_type_pickleball">亘賷賰亘賵賱</string>
+    <string name="activity_type_lacross">賱丕賰乇賵爻</string>
+    <string name="activity_type_shot">乇賲賷 丕賱噩賱丞</string>
+    <string name="activity_type_sailing">丕賱廿亘丨丕乇</string>
+    <string name="activity_type_jet_skiing">鬲夭賱噩 胤丕卅乇丞</string>
+    <string name="activity_type_skating">丕賱鬲夭賱噩</string>
+    <string name="activity_type_ice_hockey">丕賱賴賵賰賷 丕賱噩賱賷丿賷</string>
+    <string name="activity_type_curling">丕賱卮亘丕賰</string>
+    <string name="activity_type_cross_country_skiing">鬲夭賱噩 毓亘乇 丕賱亘賱丿</string>
+    <string name="activity_type_skateboarding">乇賰賵亘 丕賱鬲夭賱噩</string>
+    <string name="activity_type_rock_climbing">鬲爻賱賯 丕賱氐禺賵乇</string>
+    <string name="activity_type_hunting">丕賱氐賷丿</string>
+    <string name="activity_type_outdoor_walking">丕賱賲卮賷 賮賷 丕賱賴賵丕亍 丕賱胤賱賯</string>
+    <string name="warning_missing_notification_permission">賱丕 賷賲賰賳 賳卮乇 丕賱廿卮毓丕乇 丕賱賲爻鬲賲乇 亘爻亘亘 賮賯丿丕賳 丕賱氐賱丕丨賷丕鬲 丕賱賲胤賱賵亘丞</string>
+    <string name="devicetype_huawei_watchfit">爻丕毓丞 賴賵丕賵賷 賮賷鬲</string>
+    <string name="pref_test_features_title">丕賱賲賲賷夭丕鬲</string>
+    <string name="pref_test_features_summary">丕賱賲賷夭丕鬲 丕賱賲賲賰賳丞 賱賴匕丕 丕賱噩賴丕夭 丕賱丕禺鬲亘丕乇賷</string>
+    <string name="devicetype_redmiwatch3">爻丕毓丞 乇賷丿賲賷 伲</string>
+    <string name="alarm_smart_wakeup_interval_default">5 丿賯丕卅賯</string>
+    <string name="alarm_smart_wakeup_interval">丕賱賮丕氐賱 丕賱夭賲賳賷 賱賱丕爻鬲賷賯丕馗 丕賱匕賰賷:</string>
+    <string name="auto_reconnect_ble_scan_summary">丕賳鬲馗乇 賮丨氐 丕賱噩賴丕夭 亘丿賱丕賸 賲賳 賲丨丕賵賱丕鬲 丕賱丕鬲氐丕賱 丕賱毓賲賷丕亍</string>
+    <string name="scan_scanning_single_device">賮丨氐 噩賴丕夭 賵丕丨丿</string>
+    <string name="notification_channel_scan_service_name">禺丿賲丞 丕賱賮丨氐</string>
+    <string name="device_state_waiting_scan">賮賷 丕賳鬲馗丕乇 賮丨氐 丕賱噩賴丕夭</string>
+    <string name="auto_reconnect_ble_scan_title">兀毓丿 丕賱丕鬲氐丕賱 毓賳 胤乇賷賯 賮丨氐 BLE</string>
+    <string name="prompt_restart_gadgetbridge">賷乇噩賶 廿毓丕丿丞 鬲卮睾賷賱 GB 丨鬲賶 鬲丿禺賱 丕賱鬲睾賷賷乇丕鬲 丨賷夭 丕賱鬲賳賮賷匕.</string>
+    <string name="waiting_for_bluetooth">賮賷 丕賳鬲馗丕乇 丕賱亘賱賵鬲賵孬鈥�</string>
+    <string name="error_scan_failed">賮卮賱 丕賱賮丨氐: %d</string>
+    <string name="scan_not_scanning">賱丕 賷鬲賲 丕賱賮丨氐</string>
+    <string name="scan_scanning_all_devices">賮丨氐 噩賲賷毓 丕賱兀噩賴夭丞</string>
+    <string name="scan_scanning_multiple_devices">噩丕乇賺 賮丨氐 %d 噩賴丕夭</string>
+    <string name="devicetype_miband8active">爻賵丕乇 卮丕賵賲賷 丕賱匕賰賷 8 丕賰鬲賷賮</string>
+    <string name="temperature">丕賱丨乇丕乇丞</string>
+    <string name="pref_header_sony_sound_control">丕賱鬲丨賰賲 賮賷 丕賱氐賵鬲</string>
+    <string name="pref_wide_area_tap_summary">丕賱鬲毓乇賮 毓賱賶 丕賱賳賯乇丕鬲 亘賷賳 丕賱禺丿賷賳 賵丕賱兀匕賳賷賳</string>
+    <string name="devicetype_sony_linkbuds">爻賲丕毓丕鬲 爻賵賳賷 賱賷賳賰 亘賵丿夭</string>
+    <string name="pref_wide_area_tap_title">賲賳胤賯丞 賵丕爻毓丞 賱賱賳賯乇</string>
+    <string name="pref_adaptive_volume_control_title">丕賱鬲丨賰賲 丕賱鬲賰賷賮賷 賮賷 賲爻鬲賵賶 丕賱氐賵鬲</string>
+    <string name="pref_adaptive_volume_control_summary">賯賲 亘夭賷丕丿丞 賲爻鬲賵賶 丕賱氐賵鬲 鬲賱賯丕卅賷賸丕 毓賳丿賲丕 賷賰賵賳 丕賱氐賵鬲 丕賱賲丨賷胤 賲乇鬲賮毓賸丕</string>
+    <string name="devicetype_amazfit_bip3">丕賲丕夭賮賷鬲 亘賷亘 3</string>
+    <string name="fw_upgrade_notice_amazfitbip3">兀賳鬲 毓賱賶 賵卮賰 鬲孬亘賷鬲 丕賱亘乇賳丕賲噩 丕賱孬丕亘鬲 %s 毓賱賶 Amazfit Bip 3.
+\n
+\n丕賱乇噩丕亍 丕賱鬲兀賰丿 賲賳 鬲孬亘賷鬲 賲賱賮 .fw貙 賵亘毓丿 匕賱賰 賲賱賮 .res. 爻賷鬲賲 廿毓丕丿丞 鬲卮睾賷賱 爻丕毓鬲賰 亘毓丿 鬲孬亘賷鬲 賲賱賮 .fw.
+\n
+\n賲賱丕丨馗丞: 賱賷爻 毓賱賷賰 鬲孬亘賷鬲 鈥�.res 廿匕丕 賰丕賳 賴賵 賳賮爻賴 丕賱匕賷 鬲賲 鬲孬亘賷鬲賴 賲爻亘賯賸丕.
+\n
+\n丕賱賲囟賷 賯丿賲丕 毓賱賶 賲爻丐賵賱賷鬲賰 丕賱禺丕氐丞!</string>
+    <string name="weekstepschart_steps_a_week_or_month">丕賱禺胤賵丕鬲 賮賷 丕賱兀爻亘賵毓/丕賱卮賴乇</string>
+    <string name="weeksleepchart_sleep_a_week_or_month">丕賱賳賵賲 賮賷 丕賱兀爻亘賵毓/丕賱卮賴乇</string>
+    <string name="devicetype_huawei_watchgt4">Huawei Watch GT 4</string>
+    <string name="busy_task_fetch_sports_details_interrupted">鬲賲鬲 賲賯丕胤毓丞 噩賱亘 丕賱鬲賮丕氐賷賱 丕賱乇賷丕囟賷丞</string>
+    <string name="devicetype_huawei_watchultimate">Huawei Watch Ultimate</string>
+    <string name="unbind_before_pair_title">賲乇鬲亘胤 亘丕賱賮毓賱</string>
+    <string name="companion_pairing_request_description">廿賯乇丕賳 賴匕丕 丕賱噩賴丕夭 賰賲乇丕賮賯責.
+\n
+\n賷賵氐賶 亘賴匕丕 賱亘毓囟 丕賱賵馗丕卅賮 賲孬賱 丕賱亘丨孬 毓賳 丕賱噩賴丕夭貙 賵賷賵賮乇 丕鬲氐丕賱丕賸 兀賮囟賱.</string>
+    <string name="companion_pairing_request_title">噩賴丕夭 賲乇丕賮賯</string>
+    <string name="unbind_before_pair_message">賴匕丕 丕賱噩賴丕夭 賲乇鬲亘胤 亘丕賱賮毓賱 亘廿毓丿丕丿丕鬲 Android貙 賲賲丕 賯丿 賷丐丿賷 廿賱賶 賮卮賱 丕賱丕賯鬲乇丕賳 賱亘毓囟 丕賱兀噩賴夭丞.
+\n
+\n廿匕丕 賮卮賱鬲 廿囟丕賮丞 丕賱噩賴丕夭貙 賮賷乇噩賶 廿夭丕賱鬲賴 賮賷 廿毓丿丕丿丕鬲 Android 賵丕賱賲丨丕賵賱丞 賲乇丞 兀禺乇賶.</string>
+    <string name="devicetype_scannable">噩賴丕夭 賯丕亘賱 賱賱賲爻丨 丕賱囟賵卅賷</string>
+    <string name="state_scanned">鬲賲 賲爻丨賴丕</string>
+    <string name="devicesetting_scannable_debounce">賲賴賱丞 丕賱丕乇鬲丿丕丿 丕賱賯丕亘賱丞 賱賱賲爻丨 (孬賵丕賳賷)</string>
+    <string name="devicesetting_scannable_rssi_summary">丕賱丨丿 丕賱兀丿賳賶 賱毓鬲亘丞 RSSI 賱賱賰卮賮</string>
+    <string name="devicesetting_scannable_minimum_unseen">丕賱丨丿 丕賱兀丿賳賶 賱賱賵賯鬲 睾賷乇 丕賱賲乇卅賷 (孬賵丕賳賷)</string>
+    <string name="devicesetting_scannable_debounce_summary">亘毓丿 兀賳 賷鬲賲 賮丨氐賴貙 爻賷馗賱 丕賱噩賴丕夭 賲賲爻賵丨賸丕 囟賵卅賷賸丕 賵賷鬲賲 鬲噩丕賴賱賴 賱賮鬲乇丞 夭賲賳賷丞 賲丨丿丿丞</string>
+    <string name="devicesetting_scannable_minimum_unseen_summary">亘毓丿 兀賳 賷鬲賲 賮丨氐賴貙 賷噩亘 兀賳 賷賰賵賳 丕賱噩賴丕夭 睾賷乇 賲乇卅賷 賱賴匕賴 丕賱賮鬲乇丞 賲賳 丕賱賵賯鬲 賯亘賱 兀賳 賷鬲賲 鬲爻噩賷賱賴 賲乇丞 兀禺乇賶</string>
+    <string name="devicesetting_scannable_rssi">丕賱丨丿 丕賱兀丿賳賶 賱毓鬲亘丞 RSSI</string>
+    <string name="stepRateSum">賲噩賲賵毓 賲毓丿賱 丕賱禺胤賵丞</string>
+    <string name="stepRateAvg">賲鬲賵爻胤 賲毓丿賱 丕賱禺胤賵丞</string>
+    <string name="stepLengthAvg">賲鬲賵爻胤 胤賵賱 丕賱禺胤賵丞</string>
+    <string name="groundContactTimeAvg">賲鬲賵爻胤 賵賯鬲 丕賱鬲賱丕賲爻 亘丕賱兀乇囟</string>
+    <string name="impactAvg">賲鬲賵爻胤 丕賱兀孬乇</string>
+    <string name="impactMax">丕賯氐賶 兀孬乇</string>
+    <string name="swingAngleAvg">賲鬲賵爻胤 夭丕賵賷丞 丕賱鬲兀乇噩丨</string>
+    <string name="swolfAvg">賲鬲賵爻胤 丕賱賲匕丐賵亘</string>
+    <string name="swolfMax">丕賱丨丿 丕賱兀賯氐賶 賱賱賲匕丐賵亘</string>
+    <string name="swolfMin">丕賱丨丿 丕賱兀丿賳賶 賱賱賲匕丐賵亘賷賳</string>
+    <string name="foreFootLandings">丕賱賴亘賵胤 丕賱兀賲丕賲賷</string>
+    <string name="midFootLandings">賴亘賵胤 賲賳鬲氐賮 丕賱賯丿賲</string>
+    <string name="backFootLandings">賴亘賵胤 丕賱賯丿賲 丕賱禺賱賮賷丞</string>
+    <string name="eversionAngleAvg">賲鬲賵爻胤 夭丕賵賷丞 丕賱丕賳賯賱丕亘</string>
+    <string name="eversionAngleMax">兀賯氐賶 夭丕賵賷丞 賱賱丕賳賯賱丕亘</string>
+    <string name="fmtPaceDistance">賲爻丕賮丞 丕賱賵鬲賷乇丞 %d</string>
+    <string name="fmtPaceType">賳賵毓 丕賱賵鬲賷乇丞 %d</string>
+    <string name="fmtPacePace">賵鬲賷乇丞 %d 丕賱賵鬲賷乇丞</string>
+    <string name="fmtPaceCorrection">鬲氐丨賷丨 丕賱賵鬲賷乇丞 %d</string>
+    <string name="fmtPaceTypeAverage">賳賵毓 賲鬲賵爻胤 丕賱爻乇毓丞 %d</string>
+    <string name="unknownDataEncountered">鬲賲鬲 賲賵丕噩賴丞 亘賷丕賳丕鬲 睾賷乇 賲毓乇賵賮丞</string>
+    <string name="milliseconds">賲賷賱賷 孬丕賳賷丞</string>
+    <string name="degrees">丿乇噩丞</string>
+    <string name="Pace">賵鬲賷乇賴</string>
+    <string name="RunningForm">賳賲賵匕噩 丕賱噩乇賷</string>
+    <string name="pref_force_enable_spo2_support">丿毓賲 賯賵丞 SpO2</string>
+    <string name="pref_force_enable_heartrate_support">丿毓賲 賯賵丞 賲毓丿賱 囟乇亘丕鬲 丕賱賯賱亘</string>
+    <string name="pref_force_enable_heartrate_support_summary">賯賵丞 鬲賲賰賷賳 丿毓賲 賲毓丿賱 囟乇亘丕鬲 丕賱賯賱亘.
+\n丕爻鬲禺丿丕賲 毓賱賶 賲爻丐賵賱賷鬲賰 丕賱禺丕氐丞</string>
+    <string name="pref_force_enable_spo2_support_summary">賮乇囟 鬲賲賰賷賳 丿毓賲 SpO2.
+\n丕爻鬲禺丿丕賲 毓賱賶 賲爻丐賵賱賷鬲賰 丕賱禺丕氐丞</string>
+    <string name="dashboard_settings">廿毓丿丕丿丕鬲 賱賵丨丞 丕賱賯賷丕丿丞</string>
+    <string name="error_showing_changelog">丨丿孬 禺胤兀 兀孬賳丕亍 毓乇囟 爻噩賱 丕賱鬲睾賷賷乇</string>
+    <string name="activity_type_worn">鬲賲 賱亘爻賴丕</string>
+    <string name="bottom_nav_devices">丕賱兀噩賴夭丞</string>
+    <string name="bottom_nav_dashboard">丕賱賱賵丨丞</string>
+    <string name="pref_dashboard_first_title">丕馗賴乇 丕賱賱賵丨丞 兀賵賱丕</string>
+    <string name="pref_dashboard_first_summary">廿馗賴丕乇 賱賵丨丞 丕賱賲毓賱賵賲丕鬲 毓賳丿 亘丿亍 鬲卮睾賷賱 Gadgetbridge貙 亘丿賱丕賸 賲賳 卮丕卮丞 丕賱兀噩賴夭丞</string>
+    <string name="pref_dashboard_cards_title">毓乇囟 丕賱兀丿賵丕鬲 毓賱賶 丕賱亘胤丕賯丕鬲</string>
+    <string name="pref_dashboard_cards_summary">丕乇爻賲 亘胤丕賯丕鬲 丨賵賱 丕賱兀丿賵丕鬲 毓賱賶 賱賵丨丞 丕賱賲毓賱賵賲丕鬲</string>
+    <string name="pref_dashboard_widget_settings">廿毓丿丕丿丕鬲 丕賱賵賷丿噩賷鬲</string>
+    <string name="pref_dashboard_widget_today_title">禺乇賷胤丞 丕賱兀賳卮胤丞</string>
+    <string name="pref_dashboard_widget_today_24h_title">賵囟毓 24 爻丕毓丞</string>
+    <string name="pref_dashboard_widget_double_size_title">丨噩賲 賲夭丿賵噩</string>
+    <string name="pref_dashboard_widget_show_legend_title">毓乇囟 丕賱兀爻胤賵乇丞</string>
+    <string name="pref_dashboard_widget_goals_chart_title">禺乇賷胤丞 丕賱兀賴丿丕賮</string>
+    <string name="pref_dashboard_all_devices_title">噩賲賷毓 丕賱兀噩賴夭丞</string>
+    <string name="pref_dashboard_select_devices_title">鬲丨丿賷丿 丕賱兀噩賴夭丞...</string>
+    <string name="pref_dashboard_widgets_order_summary">丨丿丿 丕賱兀丿賵丕鬲 丕賱鬲賷 鬲賲 鬲賲賰賷賳賴丕 賵亘兀賷 鬲乇鬲賷亘 賷鬲賲 毓乇囟賴丕 毓賱賶 賱賵丨丞 丕賱賲毓賱賵賲丕鬲</string>
+    <string name="pref_dashboard_widget_today_24h_summary">丕毓乇囟 丕賱賳卮丕胤 賮賷 丿丕卅乇丞 賵丕丨丿丞 賲丿鬲賴丕 24 爻丕毓丞 亘丿賱丕賸 賲賳 丿丕卅乇丞 賲夭丿賵噩丞 賲丿鬲賴丕 12 爻丕毓丞</string>
+    <string name="pref_dashboard_widget_double_size_summary">丕爻賲丨 賱賱兀丿丕丞 亘卮睾賱 毓賲賵丿賷賳 毓賱賶 賱賵丨丞 丕賱賲毓賱賵賲丕鬲</string>
+    <string name="pref_dashboard_all_devices_summary">賯賲 亘丿賲噩 亘賷丕賳丕鬲 丕賱賳卮丕胤 賲賳 賰丕賮丞 丕賱兀噩賴夭丞 丕賱賲囟丕賮丞 賱賱丨氐賵賱 毓賱賶 丕賱廿噩賲丕賱賷丕鬲 毓賱賶 賱賵丨丞 丕賱賲毓賱賵賲丕鬲</string>
+    <string name="pref_dashboard_devices_to_include">丕賱兀噩賴夭丞 丕賱鬲賷 鬲卮賲賱</string>
+    <string name="pref_dashboard_widget_show_legend_summary">廿馗賴丕乇 賵爻賷賱丞 廿賷囟丕丨 兀爻賮賱 丕賱兀丿丕丞 鬲卮乇丨 丕賱兀賱賵丕賳</string>
+    <string name="pref_dashboard_select_devices_summary">賯賲 亘丿賲噩 亘賷丕賳丕鬲 丕賱賳卮丕胤 賲賳 兀噩賴夭丞 賲丨丿丿丞 賱賱丨氐賵賱 毓賱賶 丕賱廿噩賲丕賱賷丕鬲 毓賱賶 賱賵丨丞 丕賱賲毓賱賵賲丕鬲</string>
+    <string name="pref_dashboard_widget_today_hr_interval_title">丕賱賮丕氐賱 丕賱夭賲賳賷 賱賲毓丿賱 囟乇亘丕鬲 丕賱賯賱亘</string>
+    <string name="pref_dashboard_widget_today_hr_interval_summary">賲賯丿丕乇 丕賱丿賯丕卅賯 丕賱鬲賷 賷馗賴乇賴丕 丕賱乇爻賲 丕賱亘賷丕賳賷 \"賲爻鬲賴賱賰丞\" 亘毓丿 賰賱 賯賷丕爻 賳丕噩丨 賱賲毓丿賱 囟乇亘丕鬲 丕賱賯賱亘</string>
+    <string name="pref_header_generic">賳賵毓賷</string>
+    <string name="pref_header_audio">氐賵鬲</string>
+    <string name="pref_auto_reply_calls_summary">爻賵賮 賷賯賵賲 丕賱賴丕鬲賮 鬲賱賯丕卅賷賸丕 亘丕爻鬲賯亘丕賱 丕賱賲賰丕賱賲丕鬲 丕賱賴丕鬲賮賷丞 丕賱賵丕乇丿丞</string>
+    <string name="pref_auto_reply_calls_title">丕賱乇丿 毓賱賶 丕賱賲賰丕賱賲丕鬲 丕賱賴丕鬲賮賷丞 鬲賱賯丕卅賷丕</string>
+    <string name="pref_auto_reply_calls_delay_summary">毓丿丿 丕賱孬賵丕賳賷 丕賱鬲賷 賷鬲賲 亘毓丿賴丕 丕賱乇丿 毓賱賶 丕賱賲賰丕賱賲丞 鬲賱賯丕卅賷賸丕</string>
+    <string name="pref_auto_reply_calls_delay_title">鬲兀禺賷乇 丕賱乇丿 丕賱鬲賱賯丕卅賷</string>
+    <string name="pref_title_notification_wake_on_open">丕賱鬲賳亘賷賴 丕賱鬲賱賯丕卅賷 賵賮鬲丨 丕賱賯賮賱</string>
+    <string name="pref_summary_notification_wake_on_open">賯賲 亘鬲賳亘賷賴 噩賴丕夭 Android 賵廿賱睾丕亍 賯賮賱賴 毓賳丿賲丕 鬲乇爻賱 丕賱兀丿丕丞 乇丿賸丕 賲賮鬲賵丨賸丕. 賷噩亘 兀賳 鬲賰賵賳 賮賷 丨丕賱丞 賲賵孬賵賯丞.</string>
+    <string name="devicetype_redmi_watch_4">爻丕毓丞 乇賷丿賲賷 伽</string>
+    <string name="widget_name_colored_tile">%1$s (廿胤丕乇 賲賱賵賳)</string>
+    <string name="pref_speak_notifications_aloud_summary">爻賷鬲賲 賯乇丕亍丞 丕賱廿禺胤丕乇丕鬲 亘氐賵鬲 毓丕賱 賲賳 禺賱丕賱 爻賲丕毓丕鬲 丕賱乇兀爻</string>
+    <string name="pref_speak_notifications_aloud_title">賳胤賯 丕賱廿禺胤丕乇丕鬲 亘氐賵鬲 毓丕賱</string>
+    <string name="widget_layout_top_wide_bot_large">毓乇賷囟丞 賲賳 丕賱兀毓賱賶貙 賰亘賷乇丞 賲賳 丕賱兀爻賮賱</string>
+    <string name="widget_layout_top_large_bot_wide">賰亘賷乇 賲賳 丕賱丕毓賱賶貙 毓乇賷囟丞 賲賳 丕賱丕爻賮賱</string>
+    <string name="widget_name_untitled">毓賳氐乇 賵丕噩賴丞 丕賱賲爻鬲禺丿賲 亘丿賵賳 毓賳賵丕賳 (%1$s)</string>
+    <string name="pref_header_calls_and_notifications">丕賱賲賰丕賱賲丕鬲 賵丕賱廿禺胤丕乇丕鬲</string>
+    <string name="armenian">丕锘坟辟呝娰嗁娯�</string>
+    <string name="pref_summary_bottom_navigation_bar_off">賯賲 亘丕賱鬲亘丿賷賱 亘賷賳 丕賱卮丕卮丕鬲 丕賱乇卅賷爻賷丞 賮賯胤 亘丕爻鬲禺丿丕賲 丕賱爻丨亘 丕賱兀賮賯賷</string>
+    <string name="pref_dashboard_widget_today_upside_down_summary">賮賷 賵囟毓 24 爻丕毓丞貙 丕乇爻賲 賲賳鬲氐賮 丕賱賱賷賱 賮賷 丕賱兀爻賮賱貙 賵賲賳鬲氐賮 丕賱賳賴丕乇 賮賷 丕賱噩夭亍 丕賱毓賱賵賷 賲賳 丕賱乇爻賲 丕賱亘賷丕賳賷</string>
+    <string name="pref_dashboard_widget_today_upside_down_title">賲賳鬲氐賮 丕賱賱賷賱 賮賷 丕賱兀爻賮賱</string>
+    <string name="pref_title_bottom_navigation_bar">卮乇賷胤 丕賱鬲賳賯賱 丕賱爻賮賱賷</string>
+    <string name="pref_summary_bottom_navigation_bar_on">賯賲 亘丕賱鬲亘丿賷賱 亘賷賳 丕賱卮丕卮丕鬲 丕賱乇卅賷爻賷丞 亘丕爻鬲禺丿丕賲 卮乇賷胤 丕賱鬲賳賯賱 兀賵 丕賱鬲賲乇賷乇 丕賱兀賮賯賷</string>
+    <string name="sleepasandroid_settings">賳賲 賰丕賱兀賳丿乇賵賷丿</string>
+    <string name="pref_sleepasandroid_enable_summary">鬲賲賰賷賳 鬲賰丕賲賱 Sleep As Android</string>
+    <string name="alarm_slot_reset">鬲賲 囟亘胤 賮鬲丨丞 丕賱鬲賳亘賷賴 毓賱賶 丕賱賵囟毓 丕賱丕賮鬲乇丕囟賷</string>
+    <string name="pref_sleepasandroid_feat_heartrate">賲毓丿賱 囟乇亘丕鬲 丕賱賯賱亘</string>
+    <string name="pref_sleepasandroid_device_title">噩賴丕夭 丕賱賲夭賵丿</string>
+    <string name="pref_sleepasandroid_device_summary">丨丿丿 丕賱噩賴丕夭 賰賲夭賵丿 亘賷丕賳丕鬲 Sleep As Android</string>
+    <string name="pref_sleepasandroid_features_title">丕賱賲賲賷夭丕鬲</string>
+    <string name="pref_sleepasandroid_features_summary">賷禺鬲賱賮 丕賱丿毓賲 賲賳 噩賴丕夭 廿賱賶 噩賴丕夭</string>
+    <string name="pref_sleepasandroid_feat_alarms">廿賳匕丕乇</string>
+    <string name="pref_sleepasandroid_slot_title">賮鬲丨丞 丕賱廿卮毓丕乇丕鬲</string>
+    <string name="pref_sleepasandroid_slot_summary">賲丕 賴賷 賮鬲丨丞 丕賱鬲賳亘賷賴 丕賱鬲賷 賷噩亘 丕爻鬲禺丿丕賲賴丕 毓賳丿 囟亘胤 丕賱廿卮毓丕乇丕鬲</string>
+    <string name="pref_sleepasandroid_feat_notifications">丕賱廿卮毓丕乇丕鬲</string>
+    <string name="pref_sleepasandroid_feat_movement">賲賯賷丕爻 丕賱鬲爻丕乇毓</string>
+    <string name="pref_sleepasandroid_feat_oximetry">丕賱賲賯丕賷賷爻</string>
+    <string name="pref_sleepasandroid_feat_spo2">SPO2</string>
+    <string name="pref_title_huawei_account">丨爻丕亘 賴賵丕賵賷</string>
+    <string name="pref_summary_huawei_account">丨爻丕亘 賴賵丕賵賷 丕賱賲爻鬲禺丿賲 賮賷 毓賲賱賷丞 丕賱丕賯鬲乇丕賳. 賷爻賲丨 廿毓丿丕丿賴 亘丕賱丕賯鬲乇丕賳 丿賵賳 廿毓丕丿丞 囟亘胤 丕賱賲氐賳毓.</string>
+    <string name="serbian">丕賱氐乇亘賷丞</string>
+    <string name="watchface_resolution_doesnt_match">丿賯丞 賵噩賴 丕賱爻丕毓丞 賱丕 鬲鬲胤丕亘賯 賲毓 卮丕卮丞 丕賱噩賴丕夭. 賵丕噩賴丞 丕賱爻丕毓丞 賴賷 %1$s 卮丕卮丞 丕賱噩賴丕夭 賴賷 %2$s</string>
+    <string name="cyclingPowerMax">兀賯氐賶 賯賵丞 乇賰賵亘 丕賱丿乇丕噩丞</string>
+    <string name="cyclingPowerAverage">賲鬲賵爻胤 賯賵丞 乇賰賵亘 丕賱丿乇丕噩丞</string>
+    <string name="cyclingPowerMin">丕賱丨丿 丕賱兀丿賳賶 賲賳 賯賵丞 乇賰賵亘 丕賱丿乇丕噩丞</string>
+    <string name="devicetype_huawei_watchfit2">Huawei Watch Fit 2</string>
+    <string name="huawei_trusleep_warning">鬲丨匕賷乇: 爻賷丐丿賷 鬲賲賰賷賳 賴匕丕 廿賱賶 噩毓賱 賰賱 賵賯鬲 丕賱賳賵賲 賷馗賴乇 賰賳賵賲 禺賮賷賮 賮賷 Gadgetbridge! 丕囟睾胤 賴賳丕 廿匕丕 賯亘賱鬲 賴匕丕.</string>
+    <string name="pref_touch_tone">賳睾賲丞 丕賱賱賲爻</string>
+    <string name="pref_touch_tone_summary">賷氐丿乇 賳睾賲丞 毓賳丿 賱賲爻 爻賲丕毓丞 丕賱兀匕賳</string>
+    <string name="pref_wearing_tone">賳睾賲丞 丕賱廿乇鬲丿丕亍</string>
+    <string name="pref_wearing_tone_summary">賷氐丿乇 賳睾賲丞 毓賳丿 鬲乇賰賷亘 爻賲丕毓丞 丕賱兀匕賳</string>
+    <string name="devicetype_soundcore_liberty3_pro">Soundcore Liberty 3 Pro</string>
+    <string name="devicetype_huawei_watchfit3">Huawei Watch Fit 3</string>
+    <string name="devicetype_huawei_watch4pro">(Huawei Watch 4 (Pro</string>
+    <string name="preferences_qhybrid_settings_summary">丕賱廿毓丿丕丿丕鬲 丕賱賯丿賷賲丞 賱爻丕毓丕鬲 Q 丕賱賴噩賷賳丞</string>
+    <string name="pref_adaptive_noise_cancelling_title">毓夭賱 丕賱氐賵鬲 丕賱賲鬲賰賷賮</string>
+    <string name="pref_adaptive_noise_cancelling_summary">丕囟亘胤 賯賵丞 毓夭賱 丕賱氐賵鬲 鬲賱賯丕卅賷賸丕 毓賱賶 賲爻鬲賵賶 丕賱氐賵鬲 丕賱賲丨賷胤</string>
+    <string name="device_name_bicycle_sensor">丨爻丕爻 丕賱丿乇丕噩丞</string>
+    <string name="device_name_cycling_sensor">賲爻鬲卮毓乇 丕賱丿乇丕噩丕鬲</string>
+    <string name="devicetype_cycling_sensor">噩賴丕夭 丕爻鬲卮毓丕乇 爻乇毓丞 丕賱丿乇丕噩丞</string>
+    <string name="title_cycling">丕賱丿乇丕噩丞</string>
+    <string name="pref_summary_wheel_diameter">鈥傌坟� 丕賱毓噩賱丞 亘丕賱亘賵氐丞. 毓丕丿丞 29 貙 27貙5 兀賵 26.</string>
+    <string name="pref_title_wheel_diameter">賯胤乇 丕賱毓噩賱丞</string>
+    <string name="pref_summary_cycling_persistence_interval">丕賱賮丕氐賱 丕賱夭賲賳賷 亘孬賵丕賳賺 毓賳丿賲丕 賷噩亘 賰鬲丕亘丞 亘賷丕賳丕鬲 乇賰賵亘 丕賱丿乇丕噩丕鬲 丕賱丨丕賱賷丞 廿賱賶 賯丕毓丿丞 丕賱亘賷丕賳丕鬲</string>
+    <string name="pref_title_cycling_persistence_interval">賲毓丕丿賱 丕賱賮丕氐賱 丕賱夭賲賳賷</string>
+    <string name="chart_cycling_point_label_distance">丕賱賷賵賲: %.1f km
+\n丕賱廿噩賲丕賱賷: %.1f km</string>
+    <string name="chart_cycling_point_label_speed">%.1f km/h</string>
+    <string name="devicetype_huawei_band9">Huawei Band 9</string>
+    <string name="pref_speak_notifications_focus_exclusive_title">廿賷賯丕賮 丕賱氐賵鬲 賲丐賯鬲賸丕 賱賱鬲胤亘賷賯丕鬲 丕賱兀禺乇賶</string>
+    <string name="pref_speak_notifications_focus_exclusive_summary_on">爻賷鬲賲 廿賷賯丕賮 丕賱鬲卮睾賷賱 賲賳 丕賱鬲胤亘賷賯丕鬲 丕賱兀禺乇賶 賲丐賯鬲賸丕 兀孬賳丕亍 賳胤賯 丕賱廿卮毓丕乇</string>
+    <string name="pref_speak_notifications_focus_exclusive_summary_off">爻賷鬲賲 禺賮囟 賲爻鬲賵賶 鬲卮睾賷賱 丕賱鬲胤亘賷賯丕鬲 丕賱兀禺乇賶 兀孬賳丕亍 賳胤賯 丕賱廿卮毓丕乇</string>
+    <string name="toast_setting_requires_reconnect">爻賷氐亘丨 賴匕丕 丕賱廿毓丿丕丿 爻丕乇賷 丕賱賲賮毓賵賱 亘毓丿 廿毓丕丿丞 丕賱丕鬲氐丕賱</string>
+    <string name="open_camera">丕賮鬲丨 丕賱賰丕賲賷乇丕</string>
+    <string name="toast_camera_permission_required">賲胤賱賵亘 廿匕賳 丕賱賰丕賲賷乇丕 賱賴匕賴 丕賱賵馗賷賮丞.</string>
+    <string name="toast_camera_support_required">賲胤賱賵亘 丿毓賲 丕賱賰丕賲賷乇丕 賱賴匕賴 丕賱賵馗賷賮丞.</string>
+    <string name="toast_camera_photo_taken">鬲賲 丕賱鬲賯丕胤 丕賱氐賵乇丞 賵丨賮馗賴丕 賮賷: %s</string>
+    <string name="devicetype_amazfit_bip5_unity">Amazfit Bip 5 Unity</string>
+    <string name="pref_agps_status">丨丕賱丞 AGPS</string>
+    <string name="agps_status_missing">賲賮賯賵丿丞</string>
+    <string name="agps_status_pending">賯賷丿 丕賱丕賳鬲馗丕乇</string>
+    <string name="agps_status_current">丕賱丨丕賱賷</string>
+    <string name="agps_status_error">禺胤兀</string>
+    <string name="devicetype_garmin_venu_3">噩丕乇賲賷賳 賮賷賳賵 3</string>
+    <string name="devicetype_garmin_instinct_2_solar">睾丕乇賲賷賳 睾乇賷夭丞 2 丕賱卮賲爻賷丞</string>
+    <string name="devicetype_garmin_instinct_2_soltac">睾丕乇賲賷賳 睾乇賷夭丞 2 爻賵賱鬲丕賰</string>
+    <string name="devicetype_garmin_instinct_crossover">睾丕乇賲賷賳 睾乇賷夭丞 賰乇賵爻</string>
+    <string name="devicetype_garmin_vivoactive_4">噩丕乇賲賷賳 賮賷賮賵 兀賰鬲賷賮 4</string>
+    <string name="pref_title_garmin_default_reply_suffix">丕爻鬲禺丿賲 賱丕丨賯丞 丕賱乇丿 丕賱賲丨丿丿丞 賲爻亘賯賸丕</string>
+    <string name="none">亘賱丕</string>
+    <string name="garmin_agps_url_i">AGPS %1$d 乇丕亘胤</string>
+    <string name="no_folder_selected">賱賲 賷鬲賲 鬲丨丿賷丿 兀賷 賲噩賱丿</string>
+    <string name="folder_is_empty">賲噩賱丿 賮丕乇睾</string>
+    <string name="folder">賲噩賱丿</string>
+    <string name="url">乇丕亘胤</string>
+    <string name="garmin_agps_local_file">丕賱賲賱賮 丕賱賲丨賱賷</string>
+    <string name="pref_garmin_agps_help">鬲丨鬲賵賷 丕賱賯丕卅賲丞 兀丿賳丕賴 毓賱賶 噩賲賷毓 毓賳丕賵賷賳 URL 丕賱鬲賷 胤賱亘鬲賴丕 丕賱爻丕毓丞 賱鬲丨丿賷孬丕鬲 AGPS. 賷賲賰賳賰 鬲丨丿賷丿 賲賱賮 賲賳 賲爻丕丨丞 鬲禺夭賷賳 丕賱賴丕鬲賮 賱賷鬲賲 廿乇爻丕賱賴 廿賱賶 丕賱爻丕毓丞 毓賳丿賲丕 鬲胤賱亘 丕賱鬲丨丿賷孬.</string>
+    <string name="copied_to_clipboard">鬲賲 丕賱賳爻禺 廿賱賶 丕賱丨丕賮馗丞</string>
+    <string name="devicetype_garmin_vivomove_hr">噩丕乇賲賷賳 賮賷賮賵賲賵賮 HR</string>
+    <string name="devicetype_garmin_vivomove_style">睾丕乇賲賷賳 賮賷賮賵賲賵賮 爻鬲丕賷賱</string>
+    <string name="devicetype_garmin_instinct_2s">睾丕乇賲賷賳 睾乇賷夭丞 2S</string>
+    <string name="devicetype_garmin_instinct_solar">睾丕乇賲賷賳 睾乇賷夭丞 丕賱胤丕賯丞 丕賱卮賲爻賷丞</string>
+    <string name="devicetype_garmin_forerunner_245">噩丕乇賲賷賳 賮賵乇乇賳乇 245</string>
+    <string name="devicetype_garmin_swim_2">睾丕乇賲賷賳 丕賱爻亘丕丨丞 2</string>
+    <string name="devicetype_garmin_vivoactive_4s">噩丕乇賲賷賳 賮賷賮賵 兀賰鬲賷賮 4S</string>
+    <string name="devicetype_garmin_vivoactive_5">噩丕乇賲賷賳 賮賷賮賵 兀賰鬲賷賮 5</string>
+    <string name="pref_summary_garmin_default_reply_suffix">鬲賲 廿賱丨丕賯賴 亘丕賱廿囟丕賮丞 廿賱賶 丕賱賱丕丨賯丞 丕賱賲毓賷賳丞 賮賷 Gadgetbridge</string>
+    <string name="devicetype_garmin_venu_2_plus">Garmin Venu 2 Plus</string>
+    <string name="devicetype_garmin_vivosmart_5">Garmin V铆vosmart 5</string>
+    <string name="devicetype_garmin_instinct_2s_solar">Garmin Instinct 2S Solar</string>
+    <string name="devicetype_garmin_instinct_2x_solar">Garmin Instinct 2X Solar</string>
+    <string name="devicetype_garmin_epix_pro">Garmin Epix Pro</string>
+    <string name="intent_api_broadcast_activity_sync_title">丕賱亘孬 毓賳丿 丕賳鬲賴丕亍 賲夭丕賲賳丞 丕賱賳卮丕胤</string>
+    <string name="intent_api_broadcast_activity_sync_summary">賯賲 亘亘孬 賳賷丞 毓賳丿賲丕 鬲賳鬲賴賷 賲夭丕賲賳丞 丕賱賳卮丕胤 賱兀賷 噩賴丕夭</string>
+    <string name="activity_prefs_height_inches">丕賱胤賵賱 亘丕賱亘賵氐丞</string>
+    <string name="min_val">丕賱丨丿 丕賱兀丿賳賶: %d</string>
+    <string name="devicetype_garmin_fenix_6_sapphire">Garmin Fenix 6 Sapphire</string>
+    <string name="toggle_debug_mode">鬲亘丿賷賱 賵囟毓 丕賱鬲氐丨賷丨</string>
+    <string name="loading">鬲丨賲賷賱鈥�</string>
+    <string name="share_debug_info">賲卮丕乇賰丞 賲毓賱賵賲丕鬲 丕賱鬲氐丨賷丨</string>
+    <string name="realtime_settings">廿毓丿丕丿丕鬲 丕賱賵賯鬲 丕賱丨賯賷賯賷</string>
+    <string name="unsupported">睾賷乇 賲丿毓賵賲</string>
+    <string name="max_val">丕賱丨丿 丕賱兀賯氐賶: %d</string>
+    <string name="devicetype_garmin_venu_3s">Garmin Venu 3S</string>
+    <string name="devicetype_garmin_fenix_7_pro">Garmin Fenix 7 Pro</string>
+    <string name="notif_battery_full_title">亘胤丕乇賷丞 丕賱兀丿丕丞 賲賲鬲賱卅丞!</string>
+    <string name="notif_battery_full">%1$s 丕賱亘胤丕乇賷丞 賲賲鬲賱卅丞</string>
+    <string name="notification_channel_full_battery_name">丕賱亘胤丕乇賷丞 賰丕賲賱丞</string>
+    <string name="battery_i">丕賱亘胤丕乇賷丞 %d</string>
+    <string name="battery_full_threshold">毓鬲亘丞 丕賱亘胤丕乇賷丞 丕賱賰丕賲賱丞</string>
+    <string name="devicetype_garmin_fenix_7s">Garmin Fenix 7S</string>
+    <string name="authentication_failed_check_key">賮卮賱鬲 丕賱賲氐丕丿賯丞貙 賷乇噩賶 丕賱鬲丨賯賯 賲賳 賲賮鬲丕丨 丕賱賲氐丕丿賯丞</string>
+    <string name="battery_percentage_str">%1$s%%</string>
+    <string name="battery_low_notify_enabled">廿卮毓丕乇 毓賳丿 廿賳禺賮丕囟 丕賱亘胤丕乇賷丞</string>
+    <string name="battery_low_threshold">毓鬲亘丞 丕賱亘胤丕乇賷丞 丕賱賲賳禺賮囟丞</string>
+    <string name="battery_full_notify_enabled">丕賱廿卮毓丕乇 亘丕賰鬲賲丕賱 丕賱亘胤丕乇賷丞</string>
+    <string name="default_percentage">丕賱丕賮鬲乇丕囟賷 (%1$d%%)</string>
+    <string name="show_in_notification">毓乇囟 賮賷 丕賱廿卮毓丕乇</string>
+    <string name="devicetype_garmin_forerunner_255">Garmin Forerunner 255</string>
+    <string name="devicetype_garmin_forerunner_265">Garmin Forerunner 265</string>
+    <string name="devicetype_hama_fit6900">Hama Fit6900</string>
+    <plurals name="amount_of_days">
+        <item quantity="zero">%d 賷賵賲</item>
+        <item quantity="one">%d 賷賵賲</item>
+        <item quantity="two">%d 賷賵賲</item>
+        <item quantity="few">%d 賷賵賲</item>
+        <item quantity="many">%d 丕賷丕賲</item>
+        <item quantity="other">%d 賷賵賲</item>
+    </plurals>
+    <string name="dashboard_calendar_month_goals_reached_title">% 賲賳 丕賱禺胤賵丕鬲 丕賱鬲賷 鬲賲 丕賱賵氐賵賱 廿賱賷賴丕</string>
+    <string name="pref_fetch_unknown_files_title">噩賱亘 賲賱賮丕鬲 睾賷乇 賲毓乇賵賮丞</string>
+    <string name="pref_fetch_unknown_files_summary">噩賱亘 賲賱賮丕鬲 丕賱兀賳卮胤丞 睾賷乇 丕賱賲毓乇賵賮丞 賲賳 丕賱爻丕毓丞. 賱賳 鬲鬲賲 賲毓丕賱噩鬲賴丕貙 賵賱賰賳 爻賷鬲賲 丨賮馗賴丕 賮賷 丕賱賴丕鬲賮.</string>
+    <string name="devicetype_garmin_venu_2">Garmin Venu 2</string>
+    <string name="devicetype_garmin_forerunner_255s">Garmin Forerunner 255S</string>
+    <string name="devicetype_garmin_instinct">Garmin Instinct</string>
+    <string name="moondrop_equalizer_preset_reference">丕賱賲乇噩毓</string>
+    <string name="moondrop_equalizer_preset_basshead">Basshead</string>
+    <string name="moondrop_touch_earbud_both">賰賱丕賴賲丕</string>
+    <string name="moondrop_touch_action_play_pause">鬲卮睾賷賱/廿賷賯丕賮 賲丐賯鬲</string>
+    <string name="moondrop_touch_earbud">爻賲丕毓丞 兀匕賳</string>
+    <string name="moondrop_touch_action_call_pick_hang">丕賱乇丿/賯賮賱 丕賱賲賰丕賱賲丞</string>
+    <string name="moondrop_touch_action_call_start">亘丿丕賷丞 丕賱廿鬲氐丕賱</string>
+    <string name="moondrop_touch_action_assistant">鬲卮睾賷賱 賲爻丕毓丿 丕賱氐賵鬲</string>
+    <string name="moondrop_touch_trigger_long_press_1s">丕賱囟睾胤 賱賮鬲乇丞 胤賵賷賱丞 (1 孬丕賳賷丞)</string>
+    <string name="moondrop_touch_trigger">丕賱賲卮睾賱</string>
+    <string name="moondrop_equalizer_preset_monitor">賲乇丕賯亘丞</string>
+    <string name="devicetype_garmin_fenix_5">Garmin Fenix 5</string>
+    <string name="devicetype_garmin_fenix_6">Garmin Fenix 6</string>
+    <string name="devicetype_moondrop_space_travel">Moondrop Space Travel</string>
+    <string name="moondrop_touch_trigger_long_press_3s">丕賱囟睾胤 賱賮鬲乇丞 胤賵賷賱丞 (3孬)</string>
+    <string name="moondrop_touch_action_anc_mode">賯賲 亘鬲亘丿賷賱 賵囟毓 廿賱睾丕亍 丕賱囟賵囟丕亍 丕賱賳卮胤</string>
+    <string name="devicetype_garmin_vivoactive_3">Garmin V铆voactive 3</string>
+    <string name="battery_polling_failed_start">鬲毓匕乇 亘丿亍 丕爻鬲胤賱丕毓 丕賱亘胤丕乇賷丞</string>
+    <string name="pref_battery_polling_enable">鬲賲賰賷賳 丕爻鬲胤賱丕毓 丕賱亘胤丕乇賷丞</string>
+    <string name="pref_battery_polling_interval">丕賱賮丕氐賱 丕賱夭賲賳賷 賱丕爻鬲賯氐丕亍 丕賱亘胤丕乇賷丞</string>
+    <string name="pref_battery_polling_interval_format">賰賱 %1$s 丿賯賷賯丞</string>
+    <string name="pref_battery_polling_configuration">鬲賰賵賷賳 丕爻鬲賯氐丕亍 丕賱亘胤丕乇賷丞</string>
+    <string name="pref_battery_polling_summary">賴匕丕 賴賵 兀賮囟賱 噩賴丿貙 賵賯丿 賷鬲兀禺乇 賱毓丿丞 兀爻亘丕亘</string>
+    <string name="proprietary_app_warning">鬲鬲胤賱亘 賴匕賴 丕賱賲賷夭丞 鬲孬亘賷鬲 鬲胤亘賷賯 禺丕氐</string>
+    <string name="devicetype_garmin_vivomove_trend">Garmin V铆vomove Trend</string>
+    <string name="devicetype_garmin_vivosport">Garmin V铆vosport</string>
+    <string name="devicetype_nothing_cmf_watch_pro_2">CMF Watch Pro 2</string>
+    <string name="activity_type_gateball">賰乇丞 丕賱亘賵丕亘丞</string>
+    <string name="activity_type_parkour">亘丕乇賰賵乇</string>
+    <string name="activity_type_sepak_takraw">爻賷亘丕賰 鬲丕賰乇賵</string>
+    <string name="activity_type_tae_bo">鬲丕賷 亘賵</string>
+    <string name="activity_type_beach_soccer">賰乇丞 丕賱賯丿賲 丕賱卮丕胤卅賷丞</string>
+    <string name="activity_type_beach_volleyball">賰乇丞 丕賱胤丕卅乇丞 丕賱卮丕胤卅賷丞</string>
+    <string name="activity_type_luge">丕賱夭丨丕賮丕鬲 丕賱孬賱噩賷丞</string>
+    <string name="activity_type_parachuting">丕賱賯賮夭 亘丕賱賲馗賱丕鬲</string>
+    <string name="activity_type_auto_racing">爻亘丕賯 丕賱爻賷丕乇丕鬲</string>
+    <string name="pref_summary_notification_times_enabled">廿乇爻丕賱 丕賱廿卮毓丕乇丕鬲 亘賷賳 兀賵賯丕鬲 賲丨丿丿丞 賮賯胤</string>
+    <string name="pref_title_notification_times_enabled">兀賵賯丕鬲 丕賱廿卮毓丕乇丕鬲</string>
+    <string name="devicetype_huawei_watch3">Huawei Watch 3 (Pro)</string>
+    <string name="devicetype_nothing_cmf_buds_pro_2">CMF Buds Pro 2</string>
+    <string name="devicetype_vivitar_hr_bp_monitor_activity_tracker">噩賴丕夭 鬲鬲亘毓 賳卮丕胤 賲乇丕賯亘丞 賲毓丿賱 囟乇亘丕鬲 丕賱賯賱亘 賵 囟睾胤 丕賱丿賲 Vivitar</string>
+    <string name="devicetype_garmin_forerunner_255s_music">賲賵爻賷賯賶 Garmin Forerunner 255S</string>
+    <string name="devicetype_garmin_forerunner_255_music">睾丕乇賲賷賳 賮賵乇賵賳乇 255 賲賵爻賷賯賶</string>
+    <string name="devicetype_huawei_watchgtrunner">爻丕毓丞 賴賵丕賵賷 噩賷 鬲賷 乇丕賳乇</string>
+    <string name="find_my_phone_companion_warning">賷賱夭賲 賵噩賵丿 廿賯乇丕賳 賲氐丕丨亘 賱賱毓孬賵乇 毓賱賶 丕賱賴丕鬲賮. 丕囟睾胤 賴賳丕 賱賱丨氐賵賱 毓賱賶 丕賱賲夭賷丿 賲賳 丕賱賲毓賱賵賲丕鬲.</string>
+    <string name="hrv_status_balanced">賲鬲賵丕夭賳</string>
+    <string name="hrv_status_unbalanced">睾賷乇 賲鬲賵丕夭賳</string>
+    <string name="hrv_status_low">賲賳禺賮囟</string>
+    <string name="hrv_status_poor">囟毓賷賮</string>
+    <string name="hrv_status_last_night">丕賱賱賷賱丞 丕賱賲丕囟賷丞</string>
+    <string name="hrv_status_unit">%1$d 賲賱賷 孬丕賳賷丞</string>
+    <string name="hrv_status_baseline">%1$d-%2$d 賲賱賷 孬丕賳賷丞</string>
+    <string name="hrv_status_last_night_highest_5">賲鬲賵爻胤 丕賱賱賷賱丞 丕賱賲丕囟賷丞 5 丿賯丕卅賯 賰丨丿 兀賯氐賶</string>
+    <string name="sleep_colored_stats_deep_avg">賲鬲賵爻胤 毓賲賷賯</string>
+    <string name="sleep_colored_stats_rem_avg">賲鬲賵爻胤 REM</string>
+    <string name="hrv_status">丨丕賱丞 HRV</string>
+    <string name="sleep_colored_stats_deep">丕賱毓賲賷賯</string>
+    <string name="abstract_chart_fragment_kind_awake_sleep">丕賱丕爻鬲賷賯丕馗</string>
+    <string name="sleep_colored_stats_light">丕賱禺賮賷賮</string>
+    <string name="sleep_colored_stats_rem">REM</string>
+    <string name="sleep_colored_stats_light_avg">賲鬲賵爻胤 丕賱禺賮賷賮</string>
+    <string name="stats_highest_hr">兀毓賱賶 賲毓丿賱 賱賳亘囟丕鬲 丕賱賯賱亘</string>
+    <string name="stats_lowest_hr">兀丿賳賶 賲毓丿賱 賱賳亘囟丕鬲 丕賱賯賱亘</string>
+    <string name="hrv_status_day_avg">賲鬲賵爻胤 丕賱賷賵賲</string>
+    <string name="hrv_status_day_avg_legend">丕賱賲鬲賵爻胤 丕賱賷賵賲賷 (賲賱賱賷 孬丕賳賷丞)</string>
+    <string name="hrv_status_seven_days_avg">賲鬲賵爻胤 7 兀賷丕賲</string>
+    <string name="hrv_status_seven_days_avg_status">丕賱丨丕賱丞</string>
+    <string name="hrv_status_seven_days_avg_long">賲鬲賵爻胤 7 兀賷丕賲</string>
+    <string name="hrv_status_baseline_label">禺胤 丕賱兀爻丕爻</string>
+    <string name="pref_header_hrv_status">丨丕賱丞 HRV</string>
+    <string name="stats_empty_value">-</string>
+    <string name="devicetype_garmin_forerunner_965">Garmin Forerunner 965</string>
+    <string name="gps_glonass">GPS + GLONASS</string>
+    <string name="devicetype_garmin_fenix_5_plus">Garmin Fenix 5 Plus</string>
+    <string name="minutes_20">20 丿賯賷賯丞</string>
+    <string name="body_energy_lost">賲賮賯賵丿</string>
+    <string name="soundcore_voice_prompts">丕賱賲胤丕賱亘丕鬲 丕賱氐賵鬲賷丞</string>
+    <string name="soundcore_button_brightness">爻胤賵毓 丕賱夭乇</string>
+    <string name="soundcore_button_brightness_medium">賲鬲賵爻胤</string>
+    <string name="soundcore_button_brightness_low">賲賳禺賮囟</string>
+    <string name="soundcore_button_brightness_high">毓丕賱</string>
+    <string name="soundcore_equalizer_preset_xtra_bass">毓丕賱 丕賱噩賴賷乇</string>
+    <string name="soundcore_equalizer_preset_signature">soundcore Signature</string>
+    <string name="soundcore_equalizer_preset_voice">丕賱氐賵鬲</string>
+    <string name="soundcore_equalizer_custom_summary">鬲賰賵賷賳 廿毓丿丕丿丕鬲 丕賱賲毓丕丿賱 丕賱亘丕乇丕賲鬲乇賷丞</string>
+    <string name="soundcore_equalizer_direction_lying">賲爻鬲賱賯賷</string>
+    <string name="soundcore_equalizer_reset_summary">鬲毓賷賷賳 噩賲賷毓 賳胤丕賯丕鬲 丕賱賲毓丕丿賱 賲乇丞 兀禺乇賶 廿賱賶 丕賱廿毓丿丕丿丕鬲 丕賱丕賮鬲乇丕囟賷丞</string>
+    <string name="soundcore_equalizer_band2">丕賱賮乇賯丞 2</string>
+    <string name="soundcore_equalizer_band4">丕賱賮乇賯丞 4</string>
+    <string name="soundcore_equalizer_band5">丕賱賮乇賯丞 5</string>
+    <string name="soundcore_equalizer_band6">丕賱賮乇賯丞 6</string>
+    <string name="soundcore_equalizer_band3">丕賱賮乇賯丞 3</string>
+    <string name="soundcore_equalizer_value">丕賱賯賷賲丞</string>
+    <string name="soundcore_equalizer_preset_balanced">賲鬲賵丕夭賳</string>
+    <string name="soundcore_equalizer_custom_title">鬲禺氐賷氐鈥�</string>
+    <string name="minutes_60">60 丿賯賷賯丞</string>
+    <string name="body_energy_legend_level">賲爻鬲賵賶 胤丕賯丞 丕賱噩爻賲</string>
+    <string name="body_energy_gained">丕賱賲丨氐賵賱</string>
+    <string name="devicetype_soundcore_motion300">Soundcore Motion 300</string>
+    <string name="body_energy">胤丕賯丞 丕賱噩爻賲</string>
+    <string name="soundcore_ldac_mode_title">賵囟毓 LDAC</string>
+    <string name="soundcore_ldac_mode_summary">爻賷丐丿賷 鬲賲賰賷賳 LDAC 廿賱賶 鬲賯賱賷賱 毓賲乇 丕賱亘胤丕乇賷丞 賵賯丿 賷丐丿賷 廿賱賶 毓丿賲 丕爻鬲賯乇丕乇 丕賱丕鬲氐丕賱</string>
+    <string name="soundcore_adaptive_direction_title">丕賱鬲賵噩賷賴 丕賱廿賷噩丕亘賷</string>
+    <string name="soundcore_adaptive_direction_summary">丕囟亘胤 丕賱廿毓丿丕丿 丕賱賲爻亘賯 賱賱賲毓丕丿賱 鬲賱賯丕卅賷賸丕 丕爻鬲賳丕丿賸丕 廿賱賶 丕鬲噩丕賴 丕賱噩賴丕夭</string>
+    <string name="soundcore_equalizer_preset">鈥з勜ヘ关ж� 丕賱賲爻亘賯</string>
+    <string name="soundcore_equalizer_direction">丕鬲噩丕賴 丕賱噩賴丕夭</string>
+    <string name="soundcore_equalizer_direction_standing">賮賷 賵囟毓賷丞 丕賱賵賯賵賮</string>
+    <string name="soundcore_equalizer_direction_hanging">賲毓賱賯</string>
+    <string name="soundcore_equalizer_reset_title">廿毓丕丿丞 鬲毓賷賷賳 賱賱廿賮鬲乇丕囟賷</string>
+    <string name="soundcore_equalizer_band1">丕賱賮乇賯丞 1</string>
+    <string name="soundcore_equalizer_band7">丕賱賮乇賯丞 7</string>
+    <string name="soundcore_equalizer_band8">丕賱賮乇賯丞 8</string>
+    <string name="soundcore_equalizer_band9">丕賱賮乇賯丞 9</string>
+    <string name="soundcore_equalizer_frequency">丕賱鬲乇丿丿</string>
+    <string name="transition">丕賱丕賳鬲賯丕賱</string>
+    <string name="activity_type_navigate">丕賱賲賱丕丨丞</string>
+    <string name="activity_type_indoor_track">丕賱賲爻丕乇 丕賱丿丕禺賱賷</string>
+    <string name="activity_type_handcycling">丕賱鬲丿賵賷乇 丕賱賷丿賵賷</string>
+    <string name="activity_type_bike_commute">鬲賳賯賾購賱 亘丕賱丿乇丕噩丞</string>
+    <string name="activity_type_handcycling_indoor">乇賰賵亘 丕賱丿乇丕噩丕鬲 丕賱賷丿賵賷丞 賮賷 丕賱兀賲丕賰賳 丕賱賲睾賱賯丞</string>
+    <string name="activity_type_transition">丕賱丕賳鬲賯丕賱</string>
+    <string name="activity_type_fitness_equipment">賲毓丿丕鬲 丕賱賱賷丕賯丞</string>
+    <string name="activity_type_platform_tennis">鬲賳爻 丕賱賲賳氐丞</string>
+    <string name="activity_type_american_football">賰乇丞 丕賱賯丿賲 丕賱兀賲乇賷賰賷丞</string>
+    <string name="activity_type_training">丕賱鬲丿乇賷亘</string>
+    <string name="activity_type_cardio">丕賱賯賱亘</string>
+    <string name="activity_type_xc_classic_ski">XC 丕賱鬲夭賱噩 丕賱賰賱丕爻賷賰賷</string>
+    <string name="activity_type_e_bike">丕賱丿乇丕噩丞 丕賱賰賴乇亘丕卅賷丞</string>
+    <string name="activity_type_breathwork">丕賱兀毓賲丕賱 丕賱鬲賳賮爻賷丞</string>
+    <string name="activity_type_driving">丕賱賯賷丕丿丞</string>
+    <string name="activity_type_hang_gliding">胤賷乇丕賳 卮乇丕毓賷 賲毓賱賯</string>
+    <string name="activity_type_inline_skating">丕賱鬲夭賱噩 丕賱賲囟賲賳</string>
+    <string name="activity_type_flying">丕賱胤賷乇丕賳</string>
+    <string name="activity_type_boating">乇賰賵亘 丕賱賯賵丕乇亘</string>
+    <string name="activity_type_mountaineering">鬲爻賱賯 噩亘丕賱</string>
+    <string name="activity_type_multisport">丕賱乇賷丕囟丕鬲 丕賱賲鬲毓丿丿丞</string>
+    <string name="activity_type_paddling">鬲噩丿賷賮</string>
+    <string name="activity_type_motorcycling">乇賰賵亘 丕賱丿乇丕噩丕鬲 丕賱賳丕乇賷丞</string>
+    <string name="pref_header_sound">丕賱氐賵鬲</string>
+    <string name="activity_type_sail_race">爻亘丕賯 丕賱卮乇丕毓</string>
+    <string name="authentication_failed_negotiation">賮卮賱 丕賱鬲賮丕賵囟 毓賱賶 賲賮鬲丕丨 丕賱賲氐丕丿賯丞</string>
+    <string name="hrv">HRV</string>
+    <string name="pref_time_sync">丕賱賲夭丕賲賳丞 丕賱鬲賱賯丕卅賷丞 賱賱賵賯鬲</string>
+    <string name="minutes_8">8 丿賯丕卅賯</string>
+    <string name="minutes_9">9 丿賯丕卅賯</string>
+    <string name="minutes_15">15 丿賯賷賯丞</string>
+    <string name="minutes_255">255 丿賯賷賯丞</string>
+    <string name="activity_type_climb_indoor">丕賱鬲爻賱賯 賮賷 丕賱兀賲丕賰賳 丕賱賲睾賱賯丞</string>
+    <string name="busy_task_fetch_sleep_data">噩賱亘 亘賷丕賳丕鬲 丕賱賳賵賲</string>
+    <string name="minutes_7">7 丿賯丕卅賯</string>
+    <string name="minutes_45">45 丿賯賷賯丞</string>
+    <string name="minutes_180">180 丿賯賷賯丞</string>
+    <string name="minutes_210">210 丿賯丕卅賯</string>
+    <string name="minutes_240">240 丿賯賷賯丞</string>
+    <string name="dateformat_day_month">丕賱賷賵賲貙 丕賱卮賴乇</string>
+    <string name="dateformat_month_day">丕賱卮賴乇貙 丕賱賷賵賲</string>
+    <string name="activity_type_bouldering">丕賱亘賵賱丿乇</string>
+    <string name="stress_average">丕賱賲鬲賵爻胤</string>
+    <string name="sleep_colored_stats_awake_avg">賲鬲賵爻胤 丕賱丕爻鬲賷賯丕馗</string>
+    <string name="activity_type_rem_sleep">賳賵賲 丨乇賰丞 丕賱毓賷賳 丕賱爻乇賷毓丞</string>
+    <string name="user_feedback_set_settings_ok">鬲賲 廿乇爻丕賱 丕賱廿毓丿丕丿丕鬲 廿賱賶 丕賱噩賴丕夭.</string>
+    <string name="minutes_2">丿賯賷賯鬲丕賳</string>
+    <string name="minutes_3">3 丿賯丕卅賯</string>
+    <string name="minutes_4">4 丿賯丕卅賯</string>
+    <string name="minutes_6">6 丿賯丕卅賯</string>
+    <string name="minutes_75">75 丿賯賷賯丞</string>
+    <string name="minutes_120">120 丿賯賷賯丞</string>
+    <string name="minutes_90">90 丿賯賷賯丞</string>
+    <string name="minutes_150">150 丿賯賷賯丞</string>
+    <string name="idasen_pref_value_warning">賵賷噩亘 兀賳 鬲鬲乇丕賵丨 丕賱賯賷賲丞 亘賷賳 62 賵126 爻賲</string>
+    <string name="prefs_hrv_monitoring_description">乇丕賯亘 鬲賯賱亘 賲毓丿賱 囟乇亘丕鬲 丕賱賯賱亘 鬲賱賯丕卅賷賸丕 毓賱賶 賲丿丕乇 丕賱賷賵賲</string>
+    <string name="pref_continuous_skin_temperature_measurement_description">丕爻鬲乇丿丕丿 丿乇噩丞 丕賱丨乇丕乇丞 睾賷乇 賲丿毓賵賲 丨丕賱賷賸丕. 賷鬲賷丨 賴匕丕 丕賱廿毓丿丕丿 丕賱賯賷丕爻 丕賱賲爻鬲賲乇 毓賱賶 丕賱噩賴丕夭 賮賯胤</string>
+    <string name="backup_restore_do_not_exit">%s 賷乇噩賶 廿亘賯丕亍 賴匕賴 丕賱卮丕卮丞 賲賮鬲賵丨丞 丨鬲賶 鬲賳鬲賴賷 丕賱毓賲賱賷丞.</string>
+    <string name="date_placeholders__start_time__end_time">%1s - %2s</string>
+    <string name="activity_type_ice_skating">丕賱鬲夭賱噩 毓賱賶 丕賱孬賱噩</string>
+    <string name="prefs_title_gatt_client_notification_intents">廿匕丕毓丞 賳賵丕賷丕 廿禺胤丕乇 丕賱睾丕鬲 賲賳 禺賱丕賱 賵丕噩賴丞 亘乇賲噩丞 鬲胤亘賷賯丕鬲 賳賵丕賷丕 BLE</string>
+    <string name="cannot_upload_watchface_too_many_watchfaces_installed">賱丕 賷賲賰賳 鬲丨賲賷賱 賵丕噩賴丞 丕賱爻丕毓丞貙 鬲賲 鬲孬亘賷鬲 丕賱毓丿賷丿 賲賳 賵丕噩賴丕鬲 丕賱爻丕毓丞</string>
+    <string name="prefs_summary_gatt_client_allow_gatt_interactions">丕賱爻賲丕丨 亘廿乇爻丕賱 兀賵丕賲乇 丕賱賯乇丕亍丞/丕賱賰鬲丕亘丞 丕賱禺丕氐丞 亘賭 BLE 賵鬲賵氐賷賱賴丕</string>
+    <string name="pref_title_sync_birthdays">賲夭丕賲賳丞 丕賷丕賲 丕賱賲賷賱丕丿</string>
+    <string name="activity_type_sky_diving">丕賱賯賮夭 賮賷 丕賱爻賲丕亍</string>
+    <string name="activity_type_snowshoe">丨匕丕亍 噩賱賷丿賷</string>
+    <string name="busy_task_fetch_hrv_data">噩賱亘 亘賷丕賳丕鬲 丕賱賭 HRV</string>
+    <string name="date_placeholders__date__time">%1s, %1s</string>
+    <string name="miscale_small_objects_summary">賯賲 亘鬲禺夭賷賳 賵夭賳 丕賱兀卮賷丕亍 丕賱鬲賷 賷賯賱 賵夭賳賴丕 毓賳 10 賰噩賲</string>
+    <string name="prefs_summary_gatt_client_api_package">鬲賯賷賷丿 丕鬲氐丕賱 賵丕噩賴丞 亘乇賲噩丞 丕賱鬲胤亘賷賯丕鬲 賳賷丞 BLE 亘賴匕賴 丕賱丨夭賲丞</string>
+    <string name="inactivity_warnings_minimum_steps_summary">丕賱丨丿 丕賱兀丿賳賶 賲賳 丕賱禺胤賵丕鬲 丕賱鬲賷 賷噩亘 丕鬲禺丕匕賴丕 禺賱丕賱 丿賯丕卅賯 丕賱毓鬲亘丞</string>
+    <string name="pref_summary_sync_birthdays">賲夭丕賲賳丞 丕賷丕賲 丕賱賲賷賱丕丿 賱噩賴丕鬲 丕賱丕鬲氐丕賱 賲毓 兀丨丿丕孬 丕賱鬲賯賵賷賲</string>
+    <string name="activity_db_management_backup_restore_explanation">鬲爻賲丨 賱賰 毓賲賱賷丕鬲 丕賱丕爻鬲賷乇丕丿/丕賱鬲氐丿賷乇 亘鬲乇丨賷賱 兀賵 賳爻禺 噩賲賷毓 廿毓丿丕丿丕鬲 Gadgetbridge 賵丕賱兀噩賴夭丞 賵丕賱亘賷丕賳丕鬲 賲賳 賵廿賱賶 賲賱賮 賲囟睾賵胤.\n\n爻賷丐丿賷 丕爻鬲賷乇丕丿 賲賱賮 廿賱賶 廿夭丕賱丞 噩賲賷毓 丕賱亘賷丕賳丕鬲 賵丕賱兀噩賴夭丞 賵丕賱鬲賮囟賷賱丕鬲 丕賱賲賵噩賵丿丞貙 賵丕爻鬲亘丿丕賱賴丕 亘丕賱賰丕賲賱 亘丕賱賳爻禺丞 丕賱丕丨鬲賷丕胤賷丞.</string>
+    <string name="pref_dnd_follow_phone_summary">毓賳丿 鬲賲賰賷賳 賵囟毓 毓丿賲 丕賱廿夭毓丕噩 兀賵 鬲毓胤賷賱賴 毓賱賶 丕賱賴丕鬲賮貙 賯賲 亘鬲亘丿賷賱賴 鬲賱賯丕卅賷賸丕 毓賱賶 丕賱噩賴丕夭 兀賷囟賸丕</string>
+    <string name="pref_title_calendar_lookahead">毓丿丿 丕賱兀賷丕賲 丕賱賲賯亘賱丞</string>
+    <string name="pref_summary_calendar_lookahead">賲夭丕賲賳丞 賲丕 賷氐賱 廿賱賶 %1s 兀賷丕賲 賲賳 兀丨丿丕孬 丕賱鬲賯賵賷賲</string>
+    <string name="pref_crash_notification_summary">毓賳丿 鬲毓胤賱 丕賱鬲胤亘賷賯貙 丕毓乇囟 廿卮毓丕乇賸丕 賷丨鬲賵賷 毓賱賶 丕賱禺胤兀</string>
+    <string name="devicetype_mijia_xmwsdj04">賲爻鬲卮毓乇 丿乇噩丞 丕賱丨乇丕乇丞 賵丕賱乇胤賵亘丞 Mijia 2 (丕賱丨亘乇 丕賱廿賱賰鬲乇賵賳賷)</string>
+    <string name="miscale_weight_unit_summary">鬲毓賷賷賳 賵丨丿丞 丕賱賵夭賳 賱賱賯賷丕爻丕鬲 丕賱賲毓乇賵囟丞</string>
+    <string name="pref_header_deprecated_functionalities_warning">鬲賲 廿賴賲丕賱 丕賱賵馗丕卅賮 丕賱鬲丕賱賷丞 賵爻賷鬲賲 廿夭丕賱鬲賴丕 賯乇賷亘賸丕 賲賳 丕賱亘乇賳丕賲噩.\n廿匕丕 賰賳鬲 亘丨丕噩丞 廿賱賶 鬲賲賰賷賳 兀丨丿 丕賱廿毓丿丕丿丕鬲 丕賱鬲丕賱賷丞貙 賮鬲兀賰丿 賲賳 丕賱鬲賵丕氐賱 賲毓 賮乇賷賯 丕賱賲卮乇賵毓.</string>
+    <string name="mijia_lywsd_comfort_level_summary">鬲賰賵賷賳 丨丿賵丿 丿乇噩丞 丕賱丨乇丕乇丞 賵丕賱乇胤賵亘丞 賱賱乇賲賵夭 丕賱鬲毓亘賷乇賷丞 丕賱賲毓乇賵囟丞</string>
+    <string name="backup_restore_abort_import_confirmation">賴賱 鬲乇賷丿 廿賱睾丕亍 丕賱丕爻鬲賷乇丕丿 責 賯丿 賷丐丿賷 匕賱賰 廿賱賶 鬲賱賮 賯丕毓丿丞 丕賱亘賷丕賳丕鬲 兀賵 毓丿賲 丕鬲爻丕賯賴丕.</string>
+    <string name="backup_restore_abort_export_confirmation">賴賱 鬲乇賷丿 廿賱睾丕亍 丕賱鬲氐丿賷乇 責 爻賷鬲賲 丨匕賮 丕賱賲賱賮 丕賱賲囟睾賵胤 丕賱噩夭卅賷.</string>
+    <string name="pref_dashboard_widget_today_time_indicator_summary">毓乇囟 賲丐卮乇 賮賷 丕賱賵賯鬲 丕賱丨丕賱賷貙 賱賮氐賱 丕賱亘賷丕賳丕鬲 亘氐乇賷賸丕 毓賳 丕賱兀賲爻 賵丕賱賷賵賲</string>
+    <string name="pref_dashboard_widget_today_yesterday_data_summary">毓乇囟 丕賱亘賷丕賳丕鬲 賲賳 丕賱兀賲爻 禺丕賮鬲丞 亘賷賳 丕賱賵賯鬲 丕賱丨丕賱賷 賵賲賳鬲氐賮 丕賱賱賷賱</string>
+    <string name="smart_ring_measurement_error_worn_incorrectly">禺胤兀 賮賷 丕賱賯賷丕爻. 賴賱 賲爻鬲卮毓乇丕鬲 丕賱丨賱賯丞 賲賵噩賴丞 亘卮賰賱 氐丨賷丨 責</string>
+    <string name="choose_device">丕禺鬲乇 噩賴丕夭丕</string>
+    <string name="no_supported_devices_found">賱賲 賷鬲賲 丕賱毓孬賵乇 毓賱賶 丕賱兀噩賴夭丞 丕賱賲丿毓賵賲丞</string>
+    <string name="search">亘丨孬</string>
+    <string name="pref_deprecated_media_control_summary">廿乇爻丕賱 兀賵丕賲乇 丕賱鬲丨賰賲 賮賷 丕賱賵爻丕卅胤 賰兀丨丿丕孬 乇卅賷爻賷丞 亘丿賱丕賸 賲賳 賵丨丿丞 丕賱鬲丨賰賲 賮賷 丕賱賵爻丕卅胤.</string>
+    <string name="smart_ring_measurement_error_unknown">禺胤兀 賯賷丕爻 睾賷乇 賲毓乇賵賮 %d 鬲賲 丕爻鬲賱丕賲賴 賲賳 丕賱丨賱賯丞</string>
+    <string name="hr_resting">丕賱乇丕丨丞</string>
+    <string name="hr_maximum">兀賯氐賶</string>
+    <string name="hr_minimum">兀丿賳賶</string>
+    <string name="hr_average">丕賱賲鬲賵爻胤</string>
+    <string name="pref_title_nagivation_apps">鬲胤亘賷賯丕鬲 丕賱賲賱丕丨丞</string>
+    <string name="pref_header_external_integrations">丕賱丿賲噩 丕賱禺丕乇噩賷</string>
+    <string name="pref_header_automations">丕賱丕鬲賲鬲賴</string>
+    <string name="pref_description_general">丕賱亘丿丕賷丞貙 丕賱賱睾丞貙 丕賱賲賳胤賯丞貙 丕賱賲賵賯毓</string>
+    <string name="pref_description_about_you">鬲丕乇賷禺 丕賱賲賷賱丕丿 賵賳賵毓 丕賱噩賳爻 賵丕賱胤賵賱 賵丕賱賵夭賳 賵丕賱兀賴丿丕賮</string>
+    <string name="pref_description_notifications">廿卮毓丕乇丕鬲 丕賱鬲胤亘賷賯貙 丕賱賯丕卅賲丞 丕賱亘賷囟丕亍/丕賱賯丕卅賲丞 丕賱爻賵丿丕亍</string>
+    <string name="pref_description_user_interface">丕賱爻賲丞貙 丕賱卮丕卮丞 丕賱乇卅賷爻賷丞</string>
+    <string name="pref_description_dashboard">丕賱兀丿賵丕鬲 賵丕賱兀噩賴夭丞 丕賱賲乇丕丿 鬲囟賲賷賳賴丕</string>
+    <string name="pref_description_automations">鬲氐丿賷乇 鬲賱賯丕卅賷貙 噩賱亘 鬲賱賯丕卅賷</string>
+    <string name="pref_description_developer_options">丕賱爻噩賱丕鬲貙 賵丕噩賴丞 亘乇賲噩丞 鬲胤亘賷賯丕鬲 丕賱賳賵丕賷丕</string>
+    <string name="pref_description_deprecated_functionalities">丕賱廿毓丿丕丿丕鬲 丕賱鬲賷 爻鬲鬲賲 廿夭丕賱鬲賴丕 賮賷 廿氐丿丕乇 賲爻鬲賯亘賱賷</string>
+    <string name="pref_header_main_screen">丕賱卮丕卮丞 丕賱乇卅賷爻賷丞</string>
+    <string name="pref_title_user_interface">賵丕噩賴丞 丕賱賲爻鬲禺丿賲</string>
+    <string name="activity_prefs_date_birth">鬲丕乇賷禺 丕賱賲賷賱丕丿</string>
+    <string name="devicetype_redmi_watch_5_active">Redmi Watch 5 Active</string>
+    <string name="max_respiration_rate">賲毓丿賱 丕賱鬲賳賮爻 丕賱兀賯氐賶</string>
+    <string name="min_respiration_rate">賲毓丿賱 丕賱鬲賳賮爻 丕賱兀丿賳賶</string>
+    <string name="average_respiration_rate">賲毓丿賱 丕賱鬲賳賮爻</string>
+    <string name="hrv_sdrr">HRV SDRR</string>
+    <string name="hrZoneMaximum">丕賱丨丿 丕賱兀賯氐賶</string>
+    <string name="activity_type_plaza_dancing">乇賯氐 丕賱亘賱丕夭丕</string>
+    <string name="miscale_weight_unit_imperial">丕賱丕賲亘乇丕胤賵乇賷丞 (丕賱胤賳)</string>
+    <string name="miscale_weight_unit_metric">賲鬲乇賷 (賰睾)</string>
+    <string name="backup_restore_importing_database">丕爻鬲賷乇丕丿 賯丕毓丿丞 丕賱亘賷丕賳丕鬲鈥�</string>
+    <string name="devicetype_sony_wf_c500">Sony WF-C500</string>
+    <string name="devicetype_colmi_r10">Colmi R10</string>
+    <string name="activity_type_orienteering">爻亘丕賯 賲賵噩賴</string>
+    <string name="idasen_control_button_stand">賵賯賵賮</string>
+    <string name="activity_detail_share_json_details">賲卮丕乇賰丞 鬲賮丕氐賷賱 JSON</string>
+    <string name="devicetype_miband9">Xiaomi Smart Band 9</string>
+    <string name="devicetype_amazfit_trex_3">Amazfit T-Rex 3</string>
+    <string name="devicetype_idasen">IKEA Idasen Desk</string>
+    <string name="devicetype_huawei_watchd2">Huawei Watch D2</string>
+    <string name="menuitem_heart_rate_push">賲毓丿賱 賳亘囟丕鬲 丕賱賯賱亘</string>
+    <string name="hrv_rmssd">HRV RMSSD</string>
+    <string name="workout_set_reps">丕賱鬲賰乇丕乇</string>
+    <string name="breaths_per_min">丕賳賮丕爻/丿賯賷賯丞</string>
+    <string name="milliseconds_ms">賲 孬</string>
+    <string name="idasen_control_button_mid">MID</string>
+    <string name="idasen_control_button_sit">SIT</string>
+    <string name="idasen_pref_mid_height">丕乇鬲賮丕毓 丕賱賲賵囟毓 丕賱兀賵爻胤 (亘丕賱爻賳鬲賷賲鬲乇)</string>
+    <string name="idasen_pref_sit_height">丕乇鬲賮丕毓 賲賵囟毓 丕賱噩賱賵爻 (亘丕賱爻賳鬲賷賲鬲乇)</string>
+    <string name="activity_db_management_import_from_zip">丕爻鬲賷乇丕丿 丕賱賲賱賮 丕賱賲囟睾賵胤</string>
+    <string name="prefs_title_ble_intent_api">BLE 丕賱賳賵丕賷丕 API</string>
+    <string name="backup_restore_exporting_preferences">鬲賮囟賷賱丕鬲 丕賱鬲氐丿賷乇鈥�</string>
+    <string name="backup_restore_importing_preferences">兀賮囟賱賷丕鬲 丕賱丕爻鬲賷乇丕丿鈥�</string>
+    <string name="prefs_hrv_monitoring_title">HRV 賲乇丕賯亘丞</string>
+    <string name="backup_restore_error_import">賮卮賱 丕賱丕爻鬲賷乇丕丿 賲賳 丕賱賲賱賮 丕賱賲囟睾賵胤</string>
+    <string name="pref_dnd_follow_phone_title">丕鬲亘毓 廿毓丿丕丿 賵囟毓 毓丿賲 丕賱廿夭毓丕噩 (DND) 賱賱賴丕鬲賮</string>
+    <string name="label_distance_total_mph">丕賱廿噩賲丕賱賷: %.1f 賲賷賱</string>
+    <string name="menuitem_stress_segmented">丕賱廿噩賴丕丿 (丕賱賲噩夭兀)</string>
+    <string name="insufficient_space_for_upload">賲爻丕丨丞 睾賷乇 賰丕賮賷丞 賱賱鬲丨賲賷賱</string>
+    <string name="backup_restore_exporting">丕賱鬲氐丿賷乇 廿賱賶 賲賱賮 賲囟睾賵胤鈥�</string>
+    <string name="mijia_lywsd_comfort_lower">丕賱丨丿 丕賱兀丿賳賶</string>
+    <string name="activity_type_bocce">亘賵鬲卮賷</string>
+    <string name="activity_type_sail_expedition">乇丨賱丞 丕爻鬲賰卮丕賮賷丞</string>
+    <string name="activity_type_aerobics">丕賱賲賱丕丨丞 丕賱噩賵賷丞</string>
+    <string name="activity_type_spinning">丕賱丿賵乇丕賳</string>
+    <string name="activity_type_free_sparring">賲賱丕賰賲丞 丨乇丞</string>
+    <string name="devicetype_garmin_forerunner_955">Garmin Forerunner 955</string>
+    <string name="prefs_summary_gatt_client_notification_intents">鬲賱賯賷 丕賱鬲睾賷賷乇丕鬲 丕賱賲賲賷夭丞 賱賭 BLE 賲賳 禺賱丕賱 丕賱賳賵丕賷丕</string>
+    <string name="prefs_title_gatt_client_allow_gatt_interactions">丕賱爻賲丕丨 亘鬲賮丕毓賱 GATT 賲賳 禺賱丕賱 賵丕噩賴丞 亘乇賲噩丞 丕賱鬲胤亘賷賯丕鬲 BLE Intent API</string>
+    <string name="label_distance_total">丕賱廿噩賲丕賱賷: %.1f 賰賲</string>
+    <string name="activity_type_water_skiing">丕賱鬲夭賱噩 毓賱賶 丕賱賲賷丕賴</string>
+    <string name="activity_type_kayaking">鬲噩丿賷賮 亘丕賱賰丕賷丕賰</string>
+    <string name="activity_type_racket">丕賱鬲賳爻</string>
+    <string name="activity_type_meditation">丕賱鬲兀賲賱</string>
+    <string name="activity_type_para_sport">乇賷丕囟丞 亘丕乇丕</string>
+    <string name="activity_type_board_game">賱毓亘丞 賱賵丨</string>
+    <string name="activity_type_finswimming">丕賱爻亘丕丨丞 亘丕賱夭毓賳賮丞</string>
+    <string name="activity_type_flowriding">爻賷乇賸丕 毓賱賶 丕賱兀賯丿丕賲</string>
+    <string name="activity_type_hip_hop">賴賷亘-賴賵亘</string>
+    <string name="activity_type_modern_dance">丕賱乇賯氐 丕賱丨丿賷孬</string>
+    <string name="activity_type_sledding">丕賱鬲夭賱噩</string>
+    <string name="activity_type_laser_tag">毓賱丕賲丕鬲 丕賱賱賷夭乇</string>
+    <string name="bpm_value_unit">%1$d 賳亘囟丞 賮賷 丕賱丿賯賷賯丞</string>
+    <string name="menuitem_stress_simple">丕賱廿噩賴丕丿 (亘爻賷胤)</string>
+    <string name="devicetype_sony_wf_c700n">Sony WF-C700N</string>
+    <string name="devicetype_huawei_watchgt5">Huawei Watch GT 5 (Pro)</string>
+    <string name="menuitem_stress_breakdown">丕賱廿噩賴丕丿 (丕賱賰爻乇)</string>
+    <string name="estimatedSweatLoss">丕賱禺爻丕乇丞 丕賱賲賯丿乇丞 賱賱毓乇賯</string>
+    <string name="activity_type_marine">丕賱亘丨乇賷丞</string>
+    <string name="activity_type_indoor_push_walk_speed">丿賮毓 丿丕禺賱賷 - 爻乇毓丞 丕賱賲卮賷</string>
+    <string name="activity_type_indoor_push_run_speed">丿賮毓 丿丕禺賱賷 - 爻乇毓丞 丕賱噩乇賷</string>
+    <string name="activity_type_push_run_speed">丿賮毓 - 爻乇毓丞 丕賱噩乇賷</string>
+    <string name="activity_type_disc_golf">賯賵賱賮 丕賱賯乇氐</string>
+    <string name="activity_type_pole_dance">乇賯氐丞 丕賱毓賲賵丿</string>
+    <string name="activity_type_ballroom_dance">乇賯氐丞 賯丕毓丞 丕賱乇賯氐</string>
+    <string name="activity_type_race_walking">爻亘丕賯 丕賱賲卮賷</string>
+    <string name="activity_type_somatosensory_game">賱毓亘丞 丨爻賷丞 噩爻丿賷丞</string>
+    <string name="activity_type_square_dance">爻丕丨丞 丕賱乇賯氐</string>
+    <string name="activity_type_shuffleboard">卮丕賮賱亘賵乇丿 (賱毓亘丞 丿賮毓 丕賱兀賯乇丕氐)</string>
+    <string name="steps_distance_unit">%1$,.2f 賰賲</string>
+    <string name="activity_type_weiqi">賵賷鬲卮賷</string>
+    <string name="devicetype_garmin_venu">Garmin Venu</string>
+    <string name="devicetype_garmin_venu_sq_2">Garmin Venu Sq 2</string>
+    <string name="devicetype_garmin_forerunner_165">Garmin Forerunner 165</string>
+    <string name="devicetype_garmin_forerunner_245_music">Garmin Forerunner 245 Music</string>
+    <string name="hrZoneThreshold">丕賱丨丿 丕賱兀丿賳賶</string>
+    <string name="miscale_small_objects_title">丕賱兀噩爻丕賲 丕賱氐睾賷乇丞</string>
+    <string name="mijia_lywsd_comfort_humidity_title">丕賱乇胤賵亘丞 (%)</string>
+    <string name="backup_restore_exporting_database">鬲氐丿賷乇 賯丕毓丿丞 丕賱亘賷丕賳丕鬲鈥�</string>
+    <string name="backup_restore_exporting_finishing">丕賱丕賳鬲賴丕亍 賲賳 丕賱鬲氐丿賷乇鈥�</string>
+    <string name="backup_restore_exporting_files_i_of_n">鬲氐丿賷乇 丕賱賲賱賮丕鬲... %1d 賲賳 %2d</string>
+    <string name="activity_type_stand_up_paddleboarding">丕賱鬲噩丿賷賮 毓賱賶 丕賱兀賱賵丕丨 賮賷 賵囟毓 丕賱賵賯賵賮</string>
+    <string name="activity_type_jumpmaster">丕賱賯賮夭</string>
+    <string name="activity_type_wakesurfing">乇賰賵亘 丕賱兀賲賵丕噩</string>
+    <string name="activity_type_aerobic_combo">丕賱賲乇賰亘 丕賱兀賷乇賵亘賷</string>
+    <string name="activity_type_mixed_martial_arts">賮賳賵賳 丕賱賯鬲丕賱 丕賱賲禺鬲賱胤丞</string>
+    <string name="activity_type_artistic_swimming">丕賱爻亘丕丨丞 丕賱賮賳賷丞</string>
+    <string name="activity_type_breaking">賰爻乇</string>
+    <string name="activity_type_futsal">賰乇丞 丕賱氐丕賱丕鬲</string>
+    <string name="activity_type_frisbee">氐丨賳 胤丕卅乇</string>
+    <string name="activity_type_jai_alai">噩丕賷 毓賱賱丕賷</string>
+    <string name="activity_type_parallel_bars">噩賴丕夭 丕賱賲鬲賵丕夭賷</string>
+    <string name="app_crash_share_stacktrace">禺胤兀 賮賷 丕賱賲卮丕乇賰丞</string>
+    <string name="contact_birthday">%1s 賷賵賲 賲賷賱丕丿</string>
+    <string name="backup_restore_importing_loading">鬲丨賲賷賱 丕賱賲賱賮鈥�</string>
+    <string name="backup_restore_importing_validating">丕賱鬲丨賯賯 賲賳 丕賱賲賱賮 鈥�</string>
+    <string name="backup_restore_importing_files_i_of_n">丕爻鬲賷乇丕丿 丕賱賲賱賮丕鬲... %1d 賲賳 %2d</string>
+    <string name="fmtPaceAverage">賲毓丿賱 丕賱禺胤賶</string>
+    <string name="backup_restore_abort_title">鬲噩丕賴賱</string>
+    <string name="mijia_lywsd_comfort_upper">丕賱丨丿 丕賱兀賯氐賶</string>
+    <string name="label_distance_trip_mph">丕賱乇丨賱丞: %.1f 賲賷賱</string>
+    <string name="backup_restore_restart_summary">爻賷鬲賲 丕賱丌賳 廿毓丕丿丞 鬲卮睾賷賱 %1s.</string>
+    <string name="label_distance_trip">丕賱乇丨賱丞: %.1f 賰賲</string>
+    <string name="inactivity_warnings_minimum_steps_title">丕賱丨丿 丕賱兀丿賳賶 賱賱禺胤賵丕鬲</string>
+    <string name="pref_dashboard_widget_today_yesterday_data_title">亘賷丕賳丕鬲 兀賲爻</string>
+    <string name="activity_type_esports">乇賷丕囟丞 廿賱賰鬲乇賵賳賷丞</string>
+    <string name="activity_type_checkers">丕賱卮賷賰丕鬲</string>
+    <string name="activity_type_chess">卮胤乇賳噩</string>
+    <string name="activity_type_dragon_boat">賯丕乇亘 丕賱鬲賳賷賳</string>
+    <string name="activity_type_muay_thai">賲賵 鬲丕賷</string>
+    <string name="activity_type_snorkeling">丕賱睾賵氐</string>
+    <string name="activity_type_mass_gymnastics">噩賲亘丕夭 噩賲丕毓賷</string>
+    <string name="devicetype_garmin_forerunner_265s">Garmin Forerunner 265S</string>
+    <string name="pref_crash_notification_title">丕賱廿禺胤丕乇 毓賳丿 丕賱鬲丨胤賲</string>
+    <string name="birthdays">兀賷丕賲 丕賱賲賷賱丕丿</string>
+    <string name="devicetype_garmin_fenix_6s_sapphire">Garmin Fenix 6S Sapphire</string>
+    <string name="devicetype_soundcore_liberty4_nc">Soundcore Liberty 4 NC</string>
+    <string name="vo2max_running">鬲卮睾賷賱 VO鈧� Max</string>
+    <string name="vo2max_cycling">賯賷丕丿丞 丕賱丿乇丕噩丞 VO鈧� 丕賱丨丿 丕賱丕賯氐賶</string>
+    <string name="thirty_days_timeline">丕賱噩丿賵賱 丕賱夭賲賳賷 賱賲丿丞 30 賷賵賲賸丕</string>
+    <string name="target">丕賱賴丿賮</string>
+    <string name="miscale_weight_unit_title">賵丨丿丞 丕賱賵夭賳</string>
+    <string name="error_no_cycling_sensor_found">賱賲 賷鬲賲 丕賱毓孬賵乇 毓賱賶 賲爻鬲卮毓乇 賱賱丿乇丕噩丕鬲</string>
+    <string name="hrZoneEasy">爻賴賱</string>
+    <string name="mijia_lywsd_comfort_level_title">賲爻鬲賵賶 丕賱乇丕丨丞</string>
+    <string name="mijia_lywsd_comfort_temperature_title">丿乇噩丞 丕賱丨乇丕乇丞 (丿乇噩丞 賲卅賵賷丞)</string>
+    <string name="mijia_lywsd_comfort_temperature_summary">丕賱賳胤丕賯 丕賱賲賵氐賶 亘賴: 19 - 27</string>
+    <string name="mijia_lywsd_comfort_humidity_summary">丕賱賳胤丕賯 丕賱賲賵氐賶 亘賴: 20 - 85</string>
+    <string name="activity_db_management_backup_restore_label">丕賱賳爻禺 丕賱丕丨鬲賷丕胤賷 賵丕賱丕爻鬲毓丕丿丞</string>
+    <string name="activity_db_management_export_to_zip">鬲氐丿賷乇 丕賱賲賱賮 丕賱賲囟睾賵胤</string>
+    <string name="backup_restore_exporting_files">鬲氐丿賷乇 丕賱賲賱賮丕鬲鈥�</string>
+    <string name="backup_restore_export_complete">鬲賲 丕賱鬲氐丿賷乇</string>
+    <string name="backup_restore_import_complete">鬲賲 丕賱丕爻鬲賷乇丕丿</string>
+    <string name="backup_restore_warning_files">鈥忊�佖促勜� 丕爻鬲毓丕丿丞 %1d 賲賳 丕賱賲賱賮丕鬲: \n%2s</string>
+    <string name="backup_restore_error_export">賮卮賱 丕賱鬲氐丿賷乇 賱賱賲賱賮 丕賱賲囟睾賵胤</string>
+    <string name="backup_restore_restart_title">廿毓丕丿丞 鬲卮睾賷賱</string>
+    <string name="devicetype_garmin_venu_2s">Garmin Venu 2S</string>
+    <string name="prefs_title_gatt_client_api_package">丨夭賲丞 BLE API</string>
+    <string name="activity_type_wall_ball">丕賱賰乇丞 丕賱噩丿丕乇賷丞</string>
+    <string name="activity_type_water_polo">賰乇丞 丕賱賲丕亍</string>
+    <string name="activity_type_body_combat">丕賱賯鬲丕賱 丕賱噩爻丿賷</string>
+    <string name="activity_type_obstacle_race">爻亘丕賯 丕賱毓賯亘丕鬲</string>
+    <string name="activity_type_water_scooter">爻賰賵鬲乇 丕賱賲賷丕賴</string>
+    <string name="activity_type_bungee_jumping">丕賱賯賮夭 亘丕賱丨亘丕賱</string>
+    <string name="activity_type_billiard_pool">丨賲丕賲 丕賱爻亘丕丨丞</string>
+    <string name="activity_type_canoeing">夭賵乇賯</string>
+    <string name="activity_type_bobsleigh">亘賵亘爻賱賷</string>
+    <string name="activity_type_biathlon">亘賷丕孬賱賵賳</string>
+    <string name="activity_type_tug_of_war">卮丿 丕賱丨亘賱</string>
+    <string name="prefs_summary_gatt_client_device_state_updates">鬲賱賯賷 鬲睾賷賷乇丕鬲 丨丕賱丞 丕鬲氐丕賱 BLE 毓亘乇 丕賱賳賵丕賷丕</string>
+    <string name="activity_type_racquetball">賰乇丞 丕賱乇丕丨</string>
+    <string name="activity_type_push_walk_speed">丿賮毓 - 爻乇毓丞 丕賱賲卮賷</string>
+    <string name="activity_type_lacrosse">賱賰乇賵爻</string>
+    <string name="activity_type_ultimate_disc">兀賱鬲賷賲賷鬲 丿賷爻賰</string>
+    <string name="activity_type_team_sport">乇賷丕囟丞 丕賱賮乇賷賯</string>
+    <string name="activity_type_water_tubing">兀賳丕亘賷亘 丕賱賲賷丕賴</string>
+    <string name="activity_type_grinding">丕賱氐賯賱</string>
+    <string name="activity_type_shooting">廿胤賱丕賯 丕賱賳丕乇</string>
+    <string name="activity_type_padel">亘丕丿賱</string>
+    <string name="activity_type_bridge">丕賱噩爻乇</string>
+    <string name="activity_type_softball_slow_pitch">丕賱賰乇丞 丕賱賱賷賳丞 乇賲賷賴 亘胤賷卅丞</string>
+    <string name="activity_type_floor_climbing">鬲爻賱賯 丕賱兀乇囟賷丕鬲</string>
+    <string name="activity_type_winter_sport">丕賱乇賷丕囟丞 丕賱卮鬲賵賷丞</string>
+    <string name="activity_type_health_snapshot">賱賲丨丞 毓賳 丕賱氐丨丞</string>
+    <string name="activity_type_air_walker">賲卮丕賷丞 賴賵丕卅賷丞</string>
+    <string name="activity_type_cardio_combat">賲賰丕賮丨丞 兀賲乇丕囟 丕賱賯賱亘</string>
+    <string name="devicetype_colmi_r02">Colmi R02</string>
+    <string name="steps_avg">賲鬲賵爻胤 丕賱禺胤賵丕鬲</string>
+    <string name="devicetype_colmi_r06">Colmi R06</string>
+    <string name="devicetype_huawei_watchgtcyber">Huawei Watch GT Cyber</string>
+    <string name="menuitem_weight">丕賱賵夭賳</string>
+    <string name="app_crash_notification_title">鬲毓胤賱 %1s</string>
+    <string name="prefs_light_duration_longer">賲丿丞 廿囟丕亍丞 兀胤賵賱</string>
+    <string name="pref_dashboard_widget_today_time_indicator_title">賲丐卮乇 丕賱賵賯鬲 丕賱丨丕賱賷</string>
+    <string name="paceCorrection">鬲氐丨賷丨</string>
+    <string name="activity_type_wakeboarding">乇賰賵亘 賲賷丕賴 丕賱亘丨賷乇賴</string>
+    <string name="devicetype_colmi_r03">Colmi R03</string>
+    <string name="steps_total">丕噩賲丕賱賷 丕賱禺胤賵丕鬲</string>
+    <string name="pref_app_connection_duration">賲丿丞 丕鬲氐丕賱 丕賱鬲胤亘賷賯</string>
+    <string name="weight_kg">%1$.2f 賰睾</string>
+    <string name="activity_type_snowmobiling">丕賱鬲夭丨賱賯 毓賱賶 丕賱噩賱賷丿</string>
+    <string name="activity_type_rafting">丕賱鬲噩丿賷賮 丕賱賳賴乇賷</string>
+    <string name="activity_type_tactical">鬲賰鬲賷賰賷丞</string>
+    <string name="idasen_pref_stand_height">丕乇鬲賮丕毓 賲賵囟毓 丕賱賵賯賵賮 (亘丕賱爻賳鬲賷賲鬲乇)</string>
+    <string name="backup_restore_importing">丕爻鬲賷乇丕丿 賲賳 賲賱賮 賲囟睾賵胤鈥�</string>
+    <string name="activity_type_video_gaming">兀賱毓丕亘 丕賱賮賷丿賷賵</string>
+    <string name="activity_type_bmx">丕賱丿乇丕噩丞</string>
+    <string name="activity_type_judo">丕賱噩賵丿賵</string>
+    <string name="activity_type_folk_dance">乇賯氐丞 丕賱賳丕爻</string>
+    <string name="activity_type_hacky_sack">賰賷爻 賴丕賰賷</string>
+    <string name="activity_type_hula_hoop">胤賵賯 賴賵賱丕 賴賵亘</string>
+    <string name="activity_type_jujitsu">噩賵噩賵鬲爻賵</string>
+    <string name="activity_type_stair_climber">賲鬲爻賱賯 丕賱爻賱丕賱賲</string>
+    <string name="activity_type_table_football">賰乇丞 賯丿賲 丕賱胤丕賵賱丞</string>
+    <string name="devicetype_micompositionscale">Mi Body Composition Scale 2</string>
+    <string name="pref_continuous_skin_temperature_measurement_title">賯賷丕爻 賲爻鬲賲乇 賱丿乇噩丞 丨乇丕乇丞 丕賱噩賱丿</string>
+    <string name="number_selected_items">鬲賲 鬲丨丿賷丿 %1d</string>
+    <string name="pref_header_deprecated_functionalities">丕賱賵馗丕卅賮 丕賱賲賴賲賱丞</string>
+    <string name="devicetype_garmin_fenix_5x_plus">Garmin Fenix 5X Plus</string>
+    <string name="devicetype_garmin_enduro_3">Garmin Enduro 3</string>
+    <string name="devicetype_mismartscale">Mi Smart Scale 2</string>
+    <string name="devicetype_honor_watchgspro">Honor Watch GS Pro</string>
+    <string name="devicetype_honor_watchgs3">Honor Watch GS 3</string>
+    <string name="menuitem_map">禺乇賷胤丞</string>
+    <string name="menuitem_night_display">丕賱卮丕卮丞 丕賱賷賱賷丞</string>
+    <string name="distance_avg">賲鬲賵爻胤 丕賱賲爻丕賮丞</string>
+    <string name="distance_total">丕噩賲丕賱賷 丕賱賲丿賶</string>
+    <string name="lactateThresholdHeartRate">賲毓丿賱 囟乇亘丕鬲 賯賱亘 毓鬲亘丞 丕賱賱丕賰鬲丕鬲</string>
+    <string name="recoveryTime">賲丿丞 丕賱卮賮丕亍</string>
+    <string name="workoutSets">丕賱賲噩賲賵毓丞</string>
+    <string name="workout_set_repetitions_weight_kg">%1d x %2$.2f kg</string>
+    <string name="workout_set_repetitions_weight_lbs">%1d x %2$.2f lbs</string>
+    <string name="workout_set_i">丕賱賲噩賲賵毓丞 %1d</string>
+    <string name="workout_set_repetitions">%1d x</string>
+    <string name="weight_lbs">%1$.2f 胤賳</string>
+    <string name="pref_deprecated_media_control_title">丕賱賲賷夭丞 丕賱賲賴賲賱丞 賱賱鬲丨賰賲 賮賷 丕賱賵爻丕卅胤</string>
+    <string name="devicetype_garmin_fenix_8">Garmin Fenix 8</string>
+    <string name="devicetype_garmin_venu_sq">Garmin Venu Sq</string>
+    <string name="miscale_weight_unit_chinese">丕賱氐賷賳賷丞 (噩賷賳)</string>
+    <string name="devicetype_ble_gatt_client">毓賲賷賱 毓丕賲 賱賭 BLE GATT</string>
+    <string name="music_upload_info">兀賳鬲 毓賱賶 賵卮賰 鬲丨賲賷賱 賲賱賮 丕賱賲賵爻賷賯賶 丕賱鬲丕賱賷:\n\n%1$s\n毓賳賵丕賳 丕賱兀睾賳賷丞: %2$s\n丕賱兀賱亘賵賲: %3$s\n</string>
+    <string name="file_already_exists">丕賱賲賱賮 賲賵噩賵丿 亘丕賱賮毓賱</string>
+    <string name="devicetype_garmin_fenix_7">Garmin Fenix 7</string>
+    <string name="devicetype_garmin_forerunner_235">Garmin Forerunner 235</string>
+    <string name="devicetype_sony_wi_c100">Sony WI-C100</string>
+    <string name="devicetype_bandw_pseries">Bowers and Wilkins P series</string>
+    <string name="devicetype_garmin_fenix_6s_pro">Garmin Fenix 6S Pro</string>
+    <string name="devicetype_huawei_band3pro">Huawei Band 3 (Pro)</string>
+    <string name="devicetype_garmin_forerunner_55">Garmin Forerunner 55</string>
+    <string name="devicetype_garmin_forerunner_620">Garmin Forerunner 620</string>
+    <string name="devicetype_oppo_enco_air">Oppo Enco Air</string>
+    <string name="title_activity_musicmanager">賲丿賷乇 丕賱賲賵爻賷賯賶</string>
+    <string name="devicetype_realme_buds_t110">Realme Buds T110</string>
+    <string name="pref_voice_passthrough_enabled_summary">丕爻賲丨 賱賱兀氐賵丕鬲 丕賱禺丕乇噩賷丞 亘丕賱賲乇賵乇 廿賱賶 兀匕賳賷賰</string>
+    <string name="pref_wear_sensor_summary">丕賰鬲卮賮 賲鬲賶 賱丕 賷鬲賲 丕乇鬲丿丕亍 丕賱噩賴丕夭</string>
+    <string name="pref_wear_sensor_title">丨爻丕爻 丕賱丕乇鬲丿丕亍</string>
+    <string name="pref_music_management_title">廿丿丕乇丞 丕賱賲賵爻賷賯賶</string>
+    <string name="music_add_to_playlist">丕囟賮 丕賱賶 賯丕卅賲丞 丕賱鬲卮睾賷賱</string>
+    <string name="music_delete_from_playlist">丨匕賮 賲賳 賯丕卅賲丞 丕賱鬲卮睾賷賱</string>
+    <string name="music_delete">丨匕賮 丕賱兀睾賳賷丞</string>
+    <string name="music_delete_confirm_description">賴賱 兀賳鬲 賲鬲兀賰丿 兀賳賰 鬲乇賷丿 丨匕賮 \'%1$s \'責</string>
+    <string name="music_all_songs">賰賱 丕賱兀睾丕賳賷</string>
+    <string name="music_new_playlist">賯丕卅賲丞 鬲卮睾賷賱 噩丿賷丿丞</string>
+    <string name="music_rename_playlist">廿毓丕丿丞 鬲爻賲賷丞 賯丕卅賲丞 丕賱鬲卮睾賷賱</string>
+    <string name="music_error">丨丿孬 禺胤兀</string>
+    <string name="pref_music_management_summary">廿丿丕乇丞 丕賱賲賵爻賷賯賶 毓賱賶 丕賱爻丕毓丞</string>
+    <string name="devicetype_garmin_instinct_2">Garmin Instinct 2</string>
+    <string name="music_huawei_device_info">丕賱鬲賳爻賷賯丕鬲 丕賱賲丿毓賵賲丞: %1$s\n賵丨丿丞 鬲禺夭賷賳 丕賱爻丕毓丞: %2$d 賲賷噩丕亘丕賷鬲</string>
+    <string name="pref_voice_passthrough_enabled">鬲賲乇賷乇 丕賱氐賵鬲</string>
+    <string name="pref_voice_passthrough_level">賲爻鬲賵賶 鬲賲乇賷乇 丕賱氐賵鬲</string>
+    <string name="pref_developer_add_test_activities_summary">賲賱亍 賯丕毓丿丞 丕賱亘賷丕賳丕鬲 亘兀賳卮胤丞 丕禺鬲亘丕乇 賵賴賲賷丞</string>
+    <string name="pref_developer_add_test_activities_title">廿囟丕賮丞 兀賳卮胤丞 丕賱丕禺鬲亘丕乇</string>
+</resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-bg/strings.xml b/app/src/main/res/values-bg/strings.xml
new file mode 100644
index 0000000..27dfeff
--- /dev/null
+++ b/app/src/main/res/values-bg/strings.xml
@@ -0,0 +1,601 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <string name="pref_display_add_device_fab">袘褍褌芯薪 蟹邪 薪芯胁芯 褍褋褌褉芯泄褋褌胁芯</string>
+    <string name="pref_display_add_device_fab_on">袙懈薪邪谐懈 胁懈写懈屑</string>
+    <string name="pref_display_add_device_fab_off">袙懈写懈屑 褋邪屑芯 邪泻芯 薪褟屑邪 褋胁褗褉蟹邪薪芯 褍褋褌褉芯泄褋褌胁芯</string>
+    <string name="language_and_region_prefs">袝蟹懈泻 懈 褉械谐懈芯薪</string>
+    <string name="activity_prefs_about_you">袟邪 袙邪褋</string>
+    <string name="activity_prefs_year_birth">袚芯写懈薪邪 薪邪 褉邪卸写邪薪械</string>
+    <string name="activity_prefs_gender">袩芯谢</string>
+    <string name="activity_prefs_height_cm">袙懈褋芯褔懈薪邪 胁 cm</string>
+    <string name="activity_prefs_weight_kg">孝械谐谢芯 胁 kg</string>
+    <string name="pref_header_charts">袧邪褋褌褉芯泄泻懈 薪邪 袚褉邪褎懈泻邪褌邪</string>
+    <string name="pref_title_charts_average">袩芯泻邪蟹胁邪泄 褋褉械写薪懈 褋褌芯泄薪芯褋褌懈</string>
+    <string name="activity_prefs_charts">袧邪褋褌褉芯泄泻懈 薪邪 谐褉邪褎懈泻邪</string>
+    <string name="activity_prefs_chart_max_heart_rate">Max 褋褗褉写械褔械薪 褉懈褌褗屑</string>
+    <string name="activity_prefs_chart_min_heart_rate">Min 褋褗褉写械褔械薪 褉懈褌褗屑</string>
+    <string name="pref_title_charts_range">袨斜褏胁邪褌 薪邪 袚褉邪褎懈泻邪褌邪</string>
+    <string name="pref_charts_range_on">袨斜褏胁邪褌邪 械 袦械褋械褑</string>
+    <string name="pref_charts_range_off">袨斜褏胁邪褌邪 械 小械写屑懈褑邪</string>
+    <string name="application_name_generic">Gadgetbridge</string>
+    <string name="title_activity_controlcenter_generic">Gadgetbridge</string>
+    <string name="action_settings">袧邪褋褌褉芯泄泻懈</string>
+    <string name="action_debug">袛械斜褗谐胁邪薪械</string>
+    <string name="action_quit">袠蟹褏芯写</string>
+    <string name="action_donate">袛邪褉械薪懈械</string>
+    <string name="controlcenter_fetch_activity_data">小懈薪褏褉芯薪懈蟹懈褉邪薪械</string>
+    <string name="controlcenter_find_device">袧邪屑械褉懈 懈蟹谐褍斜械薪芯褌芯 褍褋褌褉芯泄褋褌胁芯</string>
+    <string name="controlcenter_take_screenshot">小薪懈屑泻邪 薪邪 械泻褉邪薪邪</string>
+    <string name="controlcenter_change_led_color">袩褉芯屑褟薪邪 薪邪 LED 褑胁械褌邪</string>
+    <string name="controlcenter_change_fm_frequency">袩褉芯屑褟薪邪 薪邪 FM 褔械褋褌芯褌邪</string>
+    <string name="controlcenter_connect">小胁褗褉蟹胁邪薪械鈥�</string>
+    <string name="controlcenter_disconnect">袩褉械泻褗褋胁邪薪械 薪邪 胁褉褗蟹泻邪褌邪</string>
+    <string name="controlcenter_delete_device">袩褉械屑邪褏胁邪薪械 薪邪 褍褋褌褉芯泄褋褌胁芯褌芯</string>
+    <string name="controlcenter_delete_device_name">袠蟹褌褉懈胁邪薪械 %1$s</string>
+    <string name="controlcenter_delete_device_dialogmessage">袠蟹褌褉懈胁邪薪械 薪邪 褍褋褌褉芯泄褋褌胁芯褌芯!</string>
+    <string name="controlcenter_navigation_drawer_open">袨褌胁芯褉懈 屑械薪褞褌芯 蟹邪 薪邪胁懈谐邪褑懈褟</string>
+    <string name="controlcenter_navigation_drawer_close">袟邪褌胁芯褉懈 屑械薪褞褌芯 蟹邪 薪邪胁懈谐邪褑懈褟</string>
+    <string name="controlcenter_snackbar_need_longpress">袧邪褌懈褋薪械褌械 锌褉芯写褗谢卸懈褌械谢薪芯 胁褗褉褏褍 泻邪褉褌邪褌邪 蟹邪 懈蟹泻谢褞褔胁邪薪械</string>
+    <string name="controlcenter_snackbar_disconnecting">袩褉械泻褗褋胁邪薪械 薪邪 胁褉褗蟹泻邪褌邪</string>
+    <string name="controlcenter_snackbar_connecting">小胁褗褉蟹胁邪薪械鈥�</string>
+    <string name="controlcenter_snackbar_requested_screenshot">小薪懈屑泻邪 薪邪 械泻褉邪薪邪</string>
+    <string name="controlcenter_calibrate_device">袣邪谢懈斜褉懈褉邪薪械 薪邪 褍褋褌褉芯泄褋褌胁芯褌芯</string>
+    <string name="title_activity_debug">袛械斜褗谐胁邪薪械</string>
+    <string name="debugactivity_really_factoryreset_title">袙褉褗褖邪薪械 泻褗屑 褎邪斜褉懈褔薪懈褌械 薪邪褋褌褉芯泄泻懈\?</string>
+    <string name="debugactivity_really_factoryreset">袩褉懈 胁褉褗褖邪薪械 泻褗屑 褎邪斜褉懈褔薪懈褌械 薪邪褋褌褉芯泄泻懈 褖械 褋械 懈蟹褌褉懈褟褌 胁褋懈褔泻懈 写邪薪薪懈 芯褌 褋胁褗褉蟹邪薪芯褌芯 褍褋褌褉芯泄褋褌胁芯 (邪泻芯 褋械 锌芯写写褗褉卸邪). 袩褉懈 Xiaomi/Huami 褍褋褌褉芯泄褋褌胁邪褌邪 褖械 褋械 锌褉芯屑械薪懈 Bluetooth MAC 邪写褉械褋邪 懈 褌械 褖械 褋械 锌芯褟胁褟褌 泻邪褌芯 薪芯胁懈 褍褋褌褉芯泄褋褌胁邪 胁 Gadgetbrige.</string>
+    <string name="title_activity_appmanager">校锌褉邪胁谢械薪懈械 薪邪 锌褉懈谢芯卸械薪懈褟褌邪</string>
+    <string name="appmanager_cached_watchapps_watchfaces">袩褉懈谢芯卸械薪懈褟 胁 泻械褕邪</string>
+    <string name="appmanager_installed_watchapps">袠薪褋褌邪谢懈褉邪薪懈 锌褉懈谢芯卸械薪懈褟</string>
+    <string name="appmanager_installed_watchfaces">袠薪褋褌邪谢懈褉邪薪懈 褑懈褎械褉斜谢邪褌懈</string>
+    <string name="mi2_prefs_activate_display_on_lift">袙泻谢褞褔懈 械泻褉邪薪邪 锌褉懈 胁写懈谐邪薪械</string>
+    <string name="prefs_disconnect_notification">袠蟹胁械褋褌懈械 锌褉懈 锌褉械泻褗褋胁邪薪械</string>
+    <string name="prefs_find_phone">袧邪屑械褉懈 褌械谢械褎芯薪邪</string>
+    <string name="prefs_enable_find_phone">袙泻谢褞褔懈 薪邪屑懈褉邪薪械 薪邪 褌械谢械褎芯薪邪</string>
+    <string name="prefs_find_phone_summary">袠蟹锌芯谢蟹胁邪泄褌械 褍褋褌褉芯泄褋褌胁芯褌芯, 蟹邪 写邪 薪邪泻邪褉邪褌械 褌械谢械褎芯薪邪 写邪 蟹胁褗薪懈.</string>
+    <string name="prefs_find_phone_duration">袟胁褗薪械薪械 - 褋械泻褍薪写懈</string>
+    <string name="appmananger_app_delete">袠蟹褌褉懈胁邪薪械</string>
+    <string name="appmananger_app_delete_cache">袠蟹褌褉懈胁邪薪械 懈 锌褉械屑邪褏胁邪薪械 芯褌 泻械褕邪</string>
+    <string name="appmananger_app_reinstall">袩褉械懈薪褋褌邪谢懈褉邪薪械</string>
+    <string name="appmanager_app_openinstore">孝褗褉褋械薪械 胁 Pebble appstore</string>
+    <string name="appmanager_health_activate">袗泻褌懈胁懈褉邪薪械</string>
+    <string name="appmanager_health_deactivate">袛械邪泻褌懈胁懈褉邪薪械</string>
+    <string name="appmanager_hrm_activate">袗泻褌懈胁懈褉邪薪械 薪邪 HRM</string>
+    <string name="appmanager_hrm_deactivate">袛械邪泻褌懈胁懈褉邪薪械 薪邪 HRM</string>
+    <string name="appmanager_weather_activate">袗泻褌懈胁懈褉邪薪械 薪邪 褋懈褋褌械屑薪芯褌芯 锌褉懈谢芯卸械薪懈械 蟹邪 锌褉芯谐薪芯蟹邪 薪邪 胁褉械屑械褌芯</string>
+    <string name="appmanager_weather_deactivate">袛械泻褌懈胁懈褉邪薪械 薪邪 褋懈褋褌械屑薪芯褌芯 锌褉懈谢芯卸械薪懈械 蟹邪 锌褉芯谐薪芯蟹邪 薪邪 胁褉械屑械褌芯</string>
+    <string name="appmanager_weather_install_provider">袠薪褋褌邪谢懈褉邪薪械 薪邪 锌褉懈谢芯卸械薪懈械褌芯 蟹邪 锌褉芯谐薪芯蟹邪 薪邪 胁褉械屑械褌芯</string>
+    <string name="app_configure">袣芯薪褎懈谐褍褉懈褉邪薪械</string>
+    <string name="app_move_to_top">袩褉械屑械褋褌胁邪薪械 谐芯褉械</string>
+    <string name="blacklist_all_for_notifications">袘谢芯泻懈褉邪薪械 薪邪 胁褋懈褔泻懈 懈蟹胁械褋褌懈褟</string>
+    <string name="whitelist_all_for_notifications">袪邪蟹褉械褕邪胁邪薪械 薪邪 胁褋懈褔泻懈 懈蟹胁械褋褌懈褟</string>
+    <string name="title_activity_calblacklist">袘谢芯泻懈褉邪薪懈 袣邪谢械薪写邪褉懈</string>
+    <string name="title_activity_fw_app_insaller">FW/App 懈薪褋褌邪谢邪褌芯褉</string>
+    <string name="fw_upgrade_notice">些械 褋械 懈薪褋褌邪谢懈褉邪 %s.</string>
+    <string name="fw_upgrade_notice_amazfitbip">些械 褋械 懈薪褋褌邪谢懈褉邪 %s firmware 薪邪 胁邪褕懈褟 Amazfit Bip.
+\n
+\n袩褗褉胁芯 褌褉褟斜胁邪 写邪 褋械 懈薪褋褌邪谢懈褉邪 .fw file, 褋谢械写 褌芯胁邪 .res file 懈 薪邪泻褉邪褟 .gps file. 效邪褋芯胁薪懈泻褗褌 褖械 褋械 褉械褋褌邪褉褌懈褉邪 褋谢械写 懈薪褋褌邪谢懈褉邪薪械褌芯 薪邪 .fw file.
+\n
+\n袧械 械 薪械芯斜褏芯写懈屑芯 写邪 褋械 懈薪褋褌邪谢懈褉邪褌 .res 懈 .gps 褎邪泄谢芯胁械褌械 邪泻芯 褌械 褋邪 褋褗褖懈褌械 泻邪褌芯 懈薪褋褌邪谢懈褉邪薪懈褌械 锌芯-褉邪薪芯.
+\n
+\n袩袪袨袛歇袥袞袗袙袗孝袝 袧袗 小袙袨携 袨孝袚袨袙袨袪袧袨小孝!</string>
+    <string name="fw_upgrade_notice_amazfitcor">些械 褋械 懈薪褋褌邪谢懈褉邪 %s 褎褗褉屑褍械褗褉褗褌 薪邪 胁邪褕懈褟 Amazfit Cor.
+\n
+\n袩褗褉胁芯 褌褉褟斜胁邪 写邪 懈薪褋褌邪谢懈褉邪褌械 .fw 褎邪泄谢邪, 邪 褋谢械写 褌芯胁邪 .res 褎邪泄谢邪. 袚褉懈胁薪邪褌邪 袙懈 褖械 褋械 褉械褋褌邪褉褌懈褉邪 褋谢械写 懈薪褋褌邪谢懈褉邪薪械褌芯 薪邪 .fw 褎邪泄谢邪.
+\n
+\n袙薪懈屑邪薪懈械: 袧械 械 薪械芯斜褏芯写懈屑芯 写邪 褋械 懈薪褋褌邪谢懈褉邪 .res 褎邪泄谢邪, 邪泻芯 褋褌械 懈薪褋褌邪谢懈褉邪谢懈 褋褗褖懈褟 锌褉械写懈.
+\n
+\n袠袧小孝袗袥袠袪袗袡孝袝 袧袗 小袙袨携 袨孝袚袨袙袨袪袧袨小孝!</string>
+    <string name="fw_upgrade_notice_miband3">些械 褋械 懈薪褋褌邪谢懈褉邪 %s firmware 薪邪 胁邪褕懈褟 Mi Band 3.
+\n
+\n袩褗褉胁芯 褌褉褟斜胁邪 写邪 褋械 懈薪褋褌邪谢懈褉邪 .fw file 懈 褋谢械写 褌芯胁邪 .res file. 校褋褌褉芯泄褋褌胁芯褌芯 褖械 褋械 褉械褋褌邪褉褌懈褉邪 褋谢械写 懈薪褋褌邪谢懈褉邪薪械褌芯 薪邪 .fw file.
+\n
+\n袧械 械 薪械芯斜褏芯写懈屑芯 写邪 褋械 懈薪褋褌邪谢懈褉邪 .res 褎邪泄谢, 邪泻芯 械 褋褗褖懈褟 泻邪褌芯 懈薪褋褌邪谢懈褉邪薪懈褟 锌芯-褉邪薪芯.
+\n
+\n袩袪袨袛歇袥袞袗袙袗孝袝 袧袗 小袙袨携 袨孝袚袨袙袨袪袧袨小孝!</string>
+    <string name="fw_multi_upgrade_notice">些械 褋械 懈薪褋褌邪谢懈褉邪 %1$s 懈 %2$s firmware, 胁屑械褋褌芯 褌械泻褍褖懈褟 薪邪 胁邪褕懈褟 Mi Band.</string>
+    <string name="miband_firmware_known">孝芯蟹懈 firmware 械 褌械褋褌胁邪薪 懈 械 褋褗胁屑械褋褌懈屑 褋 Gadgetbridge.</string>
+    <string name="miband_firmware_unknown_warning">孝芯蟹懈 firmware 薪械 械 褌械褋褌胁邪薪 懈 屑芯卸械 写械 薪械 械 褋褗胁屑械褋褌懈屑 褋 Gadgetbridge. 
+\n 
+\n袧袝 小袝 袩袪袝袩袨袪歇效袙袗 写邪 谐芯 懈薪褋褌邪谢懈褉邪褌械!</string>
+    <string name="miband_firmware_suggest_whitelist">袗泻芯 胁褋械 锌邪泻 卸械谢邪械褌械 写邪 锌褉芯写褗谢卸懈褌械 懈 锌芯-泻褗褋薪芯 褍褋褌邪薪芯胁懈褌械, 褔械 胁褋懈褔泻芯 械 薪邪褉械写, 屑芯谢褟 褍胁械写芯屑械褌械 Gadgetbridge 锌褉芯谐褉邪屑懈褋褌懈褌械 写邪 写芯斜邪胁褟褌 胁 whitelist %s firmware 胁械褉褋懈褟褌邪.</string>
+    <string name="title_activity_settings">袧邪褋褌褉芯泄泻懈</string>
+    <string name="pref_header_general">袨斜褖懈 薪邪褋褌褉芯泄泻懈</string>
+    <string name="pref_title_general_autoconnectonbluetooth">小胁褗褉蟹胁邪薪械 褋 Gadgetbridge 褍褋褌褉芯泄褋褌胁芯褌芯 锌褉懈 胁泻谢褞褔胁邪薪械 薪邪 Bluetoooth</string>
+    <string name="pref_title_general_autostartonboot">袗胁褌芯屑邪褌懈褔薪芯 褋褌邪褉褌懈褉邪薪械</string>
+    <string name="pref_title_general_autoreconnect">袗胁褌芯屑邪褌懈褔薪芯 胁褗蟹褋褌邪薪芯胁褟胁邪薪械 薪邪 胁褉褗蟹泻邪褌邪</string>
+    <string name="pref_title_audio_player">袩褉械写锌芯褔懈褌邪薪 屑褍蟹懈泻邪谢械薪 player</string>
+    <string name="pref_default">袩芯 锌芯写褉邪蟹斜懈褉邪薪械</string>
+    <string name="pref_title_charts_swipe">袙泻谢褞褔胁邪薪械 薪邪 锌谢褗蟹谐邪薪械 胁 谢褟胁芯/写褟褋薪芯 胁 谐褉邪褎懈泻邪褌邪 蟹邪 邪泻褌懈胁薪芯褋褌懈</string>
+    <string name="pref_header_datetime">袛邪褌邪 懈 褔邪褋</string>
+    <string name="pref_title_datetime_syctimeonconnect">小懈薪褏褉芯薪懈蟹邪褑懈褟 薪邪 褔邪褋</string>
+    <string name="pref_summary_datetime_syctimeonconnect">小懈薪褏褉芯薪懈蟹懈褉邪薪械 薪邪 褔邪褋邪 锌褉懈 胁泻褞褔胁邪薪械 泻褗屑 褍褋褌褉芯泄褋褌胁芯褌芯, 褋褗褖芯 懈 锌褉懈 褋屑褟薪邪 薪邪 褔邪褋邪 懈谢懈 褔邪褋芯胁邪褌邪 蟹芯薪邪 薪邪 袗薪写褉芯懈写 褍褋褌褉芯泄褋褌胁芯褌芯</string>
+    <string name="pref_title_theme">孝械屑邪</string>
+    <string name="pref_theme_light">小胁械褌谢邪</string>
+    <string name="pref_theme_dark">孝褗屑薪邪</string>
+    <string name="pref_title_language">袝蟹懈泻</string>
+    <string name="pref_title_minimize_priority">小泻褉懈泄 Gadgetbridge 懈蟹胁械褋褌懈械褌芯</string>
+    <string name="pref_summary_minimize_priority_off">袩芯蟹邪泻胁邪薪械 薪邪 懈泻芯薪邪褌邪 胁 褋褌邪褌褍褋 斜邪褉邪 懈 懈蟹胁械褋褌懈械褌芯 锌褉懈 蟹邪泻谢褞褔械薪 械泻褉邪薪</string>
+    <string name="pref_summary_minimize_priority_on">小泻褉懈胁邪薪械 薪邪 懈泻芯薪邪褌邪 胁 褋褌邪褌褍褋 斜邪褉邪 懈 懈蟹胁械褋褌懈械褌芯 锌褉懈 蟹邪泻谢褞褔械薪 械泻褉邪薪</string>
+    <string name="pref_header_notifications">袠蟹胁械褋褌懈褟</string>
+    <string name="pref_title_notifications_repetitions">袩芯胁褌芯褉械薪懈褟</string>
+    <string name="pref_title_notifications_call">孝械谢械褎芯薪薪懈 芯斜邪卸写邪薪懈褟</string>
+    <string name="pref_title_notifications_sms">SMS</string>
+    <string name="pref_title_notifications_timeout">袦懈薪懈屑邪谢械薪 懈薪褌械褉胁邪谢 屑械卸写褍 懈蟹胁械褋褌懈褟褌邪</string>
+    <string name="pref_title_notifications_pebblemsg">Pebble 褋褗芯斜褖械薪懈褟</string>
+    <string name="pref_summary_notifications_pebblemsg">袩芯写写褉褗卸泻邪 薪邪 锌褉懈谢芯卸械薪懈褟, 泻芯懈褌芯 懈蟹锌褉邪褖邪褌 懈蟹胁械褋褌懈褟 写芯 Pebble 褔褉械蟹 PebbleKit.</string>
+    <string name="pref_title_notifications_generic">袨斜褖邪 锌芯写写褉褗卸泻邪 薪邪 懈蟹胁械褋褌懈褟</string>
+    <string name="pref_title_whenscreenon">鈥ρ佈娧壭� 懈 锌褉懈 胁泻谢褞褔械薪 械泻褉邪薪</string>
+    <string name="pref_title_notification_filter">袧械 斜械蟹锌芯泻芯泄褌械</string>
+    <string name="pref_summary_notification_filter">袧械卸械谢邪薪懈褌械 懈蟹胁械褋褌懈褟 褋械 褋锌懈褉邪褌 胁 褌芯蟹懈 褉械卸懈屑</string>
+    <string name="pref_title_transliteration">孝褉邪薪褋谢懈褌械褉邪褑懈褟</string>
+    <string name="pref_summary_transliteration">袗泻褌懈胁懈褉邪泄褌械, 邪泻芯 褍褋褌褉芯泄褋褌胁芯褌芯 薪械 锌芯写写褗褉卸邪 褕褉懈褎褌邪 薪邪 胁邪褕懈褟 械蟹懈泻</string>
+    <string name="pref_title_rtl">袨褌写褟褋薪芯 薪邪谢褟胁芯</string>
+    <string name="pref_summary_rtl">袗泻褌懈胁懈褉邪泄褌械, 邪泻芯 褍褋褌褉芯泄褋褌胁芯褌芯 薪械 屑芯卸械 写邪 锌芯泻邪蟹胁邪 械蟹懈褑懈 芯褌写褟褋薪芯 薪邪谢褟胁芯</string>
+    <string name="pref_rtl_max_line_length">袦邪泻褋懈屑邪谢薪邪 写褗卸懈薪邪 薪邪 谢懈薪懈褟褌邪 芯褌写褟褋薪芯 薪邪谢褟胁芯</string>
+    <string name="pref_rtl_max_line_length_summary">校写褗谢卸邪胁邪薪械/褋泻褗褋褟胁邪薪械 褉械写芯胁械褌械 薪邪 褌械泻褋褌 芯褌写褟褋薪芯 薪邪谢褟胁芯</string>
+    <string name="always">袙懈薪邪谐懈</string>
+    <string name="when_screen_off">袣芯谐邪褌芯 械泻褉邪薪褗褌 械 懈蟹泻谢褞褔械薪</string>
+    <string name="never">袧懈泻芯谐邪</string>
+    <string name="pref_header_privacy">袩芯胁械褉懈褌械谢薪芯褋褌</string>
+    <string name="pref_title_call_privacy_mode">袩芯胁械褉懈褌械谢薪芯褋褌 锌褉懈 芯斜邪卸写邪薪懈褟</string>
+    <string name="pref_call_privacy_mode_off">袩芯泻邪蟹胁邪薪械 薪邪 懈屑械褌芯 懈 薪芯屑械褉邪</string>
+    <string name="pref_call_privacy_mode_name">小泻褉懈胁邪薪械 薪邪 懈屑械褌芯, 薪芯 锌芯泻邪蟹胁邪薪械 薪邪 薪芯屑械褉邪</string>
+    <string name="pref_call_privacy_mode_number">小泻褉懈胁邪薪械 薪邪 薪芯屑械褉邪, 薪芯 锌芯泻邪蟹胁邪薪械 薪邪 懈屑械褌芯</string>
+    <string name="pref_call_privacy_mode_complete">小泻褉懈胁邪薪械 薪邪 懈屑械褌芯 懈 薪芯屑械褉邪</string>
+    <string name="pref_title_weather">袩褉芯谐薪芯蟹邪 蟹邪 胁褉械屑械褌芯</string>
+    <string name="pref_title_weather_location">袦械褌械芯褉芯谢芯谐懈褔薪芯 屑械褋褌芯锌芯谢芯卸械薪懈械 (CM/LOS)</string>
+    <string name="pref_blacklist_calendars">袘谢芯泻懈褉邪薪懈 袣邪谢械薪写邪褉懈</string>
+    <string name="pref_header_cannned_messages">袚芯褌芯胁懈 褋褗芯斜褖械薪懈褟</string>
+    <string name="pref_title_canned_replies">袨褌谐芯胁芯褉懈</string>
+    <string name="pref_title_canned_reply_suffix">袨斜褖邪 薪邪褋褌邪胁泻邪</string>
+    <string name="pref_title_canned_messages_dismisscall">袨褌褏胁褗褉谢褟薪械 薪邪 芯斜邪卸写邪薪械</string>
+    <string name="pref_title_canned_messages_set">袨斜薪芯胁懈 薪邪 Pebble</string>
+    <string name="pref_header_development">袧邪褋褌褉芯泄泻懈 蟹邪 褉邪蟹褉邪斜芯褌褔懈褑懈</string>
+    <string name="pref_title_development_miaddr">Mi Band 邪写褉械褋</string>
+    <string name="pref_title_pebble_settings">Pebble 薪邪褋褌褉芯泄泻懈</string>
+    <string name="pref_header_activitytrackers">袗泻褌懈胁懈褌懈 褌褉邪泻械褉懈</string>
+    <string name="pref_title_pebble_activitytracker">袩褉械写锌芯褔懈褌邪薪 邪泻褌懈胁懈褌懈 tracker</string>
+    <string name="pref_title_pebble_sync_health">小懈薪褏褉芯薪懈蟹懈褉邪薪械 褋 Pebble Health</string>
+    <string name="pref_title_pebble_sync_misfit">小懈薪褏褉芯薪懈蟹懈褉邪薪械 褋 Misfit</string>
+    <string name="pref_title_pebble_sync_morpheuz">小懈薪褏褉芯薪懈蟹懈褉邪薪械 褋 Morpheuz</string>
+    <string name="pref_title_enable_outgoing_call">袩芯写写褉褗卸泻邪 薪邪 懈蟹褏芯写褟褖懈 芯斜邪卸写邪薪懈褟</string>
+    <string name="pref_summary_enable_outgoing_call">袗泻芯 薪械 械 邪泻褌懈胁懈褉邪薪芯, Pebble 2/LE 薪褟屑邪 写邪 胁懈斜褉懈褉邪褌 锌褉懈 懈蟹褏芯写褟褖懈 芯斜邪卸写邪薪懈褟</string>
+    <string name="pref_title_enable_pebblekit">袪邪蟹褉械褕械薪 写芯褋褌褗锌 薪邪 胁褗薪褕薪懈 邪薪写褉芯懈写 锌褉懈谢芯卸械薪懈褟</string>
+    <string name="pref_summary_enable_pebblekit">袗泻褌懈胁懈褉邪薪械 薪邪 械泻褋锌械褉懈屑械薪褌邪谢薪邪 锌芯写写褉褗卸泻邪 薪邪 邪薪写褉芯懈写 锌褉懈谢芯卸械薪懈褟, 懈蟹锌芯谢蟹胁邪褖懈 PebbleKit</string>
+    <string name="pref_header_pebble_timeline">Pebble 褏褉芯薪芯谢芯谐懈褟 /timeline/</string>
+    <string name="pref_title_sunrise_sunset">袠蟹谐褉械胁 懈 蟹邪谢械蟹</string>
+    <string name="pref_summary_sunrise_sunset">袩芯泻邪蟹胁邪薪械 薪邪 懈蟹谐褉械胁/蟹邪谢械蟹 褔邪褋芯胁械褌械 胁 褏褉芯薪芯谢芯谐懈褟褌邪 薪邪 Pebble timeline, 褋锌芯褉械写 屑械褋褌芯锌芯谢芯卸械薪懈械褌芯</string>
+    <string name="pref_title_enable_calendar_sync">小懈薪褏褉芯薪懈蟹懈褉邪薪械 薪邪 泻邪谢械薪写邪褉邪</string>
+    <string name="pref_summary_enable_calendar_sync">袩芯泻邪蟹胁邪薪械 薪邪 褋褗斜懈褌懈褟 芯褌 泻邪谢械薪写邪褉邪 胁 褏褉芯薪芯谢芯谐懈褟褌邪</string>
+    <string name="pref_title_autoremove_notifications">袗胁褌芯屑邪褌懈褔薪芯 锌褉械屑邪褏胁邪薪械 薪邪 芯褌褏胁褗褉谢械薪懈褌械 懈蟹胁械褋褌懈褟</string>
+    <string name="pref_summary_autoremove_notifications">袠蟹胁械褋褌懈褟褌邪 邪胁褌芯屑邪褌懈褔薪芯 褋械 锌褉械屑邪褏胁邪褌 芯褌 Pebble, 邪泻芯 褋邪 芯褌褏胁褗褉谢械薪懈 芯褌 邪薪写褉芯懈写 褍褋褌褉芯泄褋褌胁芯褌芯</string>
+    <string name="pref_title_pebble_privacy_mode">袪械卸懈屑 薪邪 锌芯胁械褉懈褌械谢薪芯褋褌</string>
+    <string name="pref_pebble_privacy_mode_off">袧芯褉屑邪谢薪芯 懈蟹胁械褋褌褟胁邪薪械</string>
+    <string name="pref_pebble_privacy_mode_content">袩褉械薪邪褋褟薪械 薪邪 懈蟹胁械褋褌懈械褌芯, 泻芯谐邪褌芯 褌械泻褋褌邪 懈蟹谢懈蟹邪 懈蟹胁褗薪 械泻褉邪薪邪</string>
+    <string name="pref_pebble_privacy_mode_complete">袩芯泻邪蟹胁邪薪械 褋邪屑芯 懈泻芯薪邪褌邪 薪邪 懈蟹胁械褋褌懈械褌芯</string>
+    <string name="pref_header_location">袦械褋褌芯锌芯谢芯卸械薪懈械</string>
+    <string name="pref_title_location_aquire">袩芯谢褍褔邪胁邪薪械 薪邪 屑械褋褌芯锌芯谢芯卸械薪懈械</string>
+    <string name="pref_title_location_latitude">楔懈褉懈薪邪</string>
+    <string name="pref_title_location_longitude">袛褗谢卸懈薪邪</string>
+    <string name="pref_title_location_keep_uptodate">袨斜薪芯胁褟胁邪薪械 薪邪 屑械褋褌芯锌芯谢芯卸械薪懈械褌芯</string>
+    <string name="pref_summary_location_keep_uptodate">袩芯谢褍褔邪胁邪薪械 薪邪 褌械泻褍褖芯褌芯 屑械褋褌芯锌芯谢芯卸械薪懈械 胁 褉械邪谢薪芯 胁褉械屑械, 锌褉懈 薪械褍褋锌械褏 写邪 褋械 懈蟹锌芯谢蟹胁邪 褋褗褏褉邪薪械薪芯褌芯 屑械褋褌芯锌芯谢芯卸械薪懈械</string>
+    <string name="toast_enable_networklocationprovider">袦芯谢褟, 邪泻褌懈胁懈褉邪泄褌械 屑械褋褌芯锌芯谢芯卸械薪懈械褌芯 褋锌芯褉械写 屑褉械卸邪褌邪</string>
+    <string name="toast_aqurired_networklocation">袦械褋褌芯锌芯谢芯卸械薪懈械褌芯 械 芯锌褉械写械谢械薪芯</string>
+    <string name="pref_title_pebble_forceprotocol">袟邪写褗谢卸懈褌械谢械薪 锌褉芯褌芯泻芯谢 蟹邪 懈蟹胁械褋褌懈褟</string>
+    <string name="pref_summary_pebble_forceprotocol">孝邪蟹懈 芯锌褑懈褟 锌褉懈薪褍卸写邪胁邪 写邪 褋械 懈蟹锌芯谢蟹胁邪 锌芯褋谢械写薪懈褟褌 锌褉芯褌芯泻芯谢 蟹邪 懈蟹胁械褋褌懈褟 褋锌芯褉械写 firmware 胁械褉褋懈褟褌邪. 袗袣孝袠袙袠袪袗袡孝袝, 袗袣袨 袟袧袗袝孝袝 袣袗袣袙袨 袩袪袗袙袠孝袝!</string>
+    <string name="pref_title_pebble_forceuntested">袗泻褌懈胁懈褉邪薪械 薪邪 褎褍薪泻褑懈芯薪邪谢薪芯褋褌懈, 泻芯懈褌芯 薪械 褋邪 褌械褋褌胁邪薪懈</string>
+    <string name="pref_summary_pebble_forceuntested">袗泻褌懈胁懈褉邪薪械 薪邪 褎褍薪泻褑懈芯薪邪谢薪芯褋褌懈, 泻芯懈褌芯 薪械 褋邪 褌械褋褌胁邪薪懈. 袟袧袗袝孝袝 袥袠 袣袗袣袙袨 袩袪袗袙袠孝袝\?!</string>
+    <string name="pref_title_pebble_forcele">袙懈薪邪谐懈 写邪 褋械 锌褉械写锌芯褔懈褌邪 BLE /Bluetooth Low Energy/</string>
+    <string name="pref_summary_pebble_forcele">袠蟹锌芯谢蟹胁邪薪械 薪邪 械泻褋锌械褉懈屑械薪褌邪谢薪邪褌邪 Pebble LE 锌芯写写褉褗卸泻邪, 胁屑械褋褌芯 泻谢邪褋懈褔械褋泻懈褟 BT. 孝芯胁邪 懈蟹懈褋泻胁邪 锌褗褉胁芯 褋写胁芯褟胁邪薪械 褋 泻谢邪褋懈褔械褋泻懈褟 BT, 邪 褋谢械写 褌芯胁邪 褋 Pebble LE</string>
+    <string name="pref_title_pebble_mtu_limit">Pebble 2/LE GATT MTU 芯谐褉邪薪懈褔械薪懈械</string>
+    <string name="pref_summary_pebble_mtu_limit">袗泻芯 胁邪褕懈褟褌 Pebble 2/Pebble LE 薪械 褉邪斜芯褌懈 褋锌芯褉械写 芯褔邪泻胁邪薪懈褟褌邪, 芯锌懈褌邪泄褌械 褌械蟹懈 薪邪褋褌褉芯泄泻懈 蟹邪 芯谐褉邪薪懈褔邪胁邪薪械 薪邪 MTU (胁邪谢懈写械薪 写懈邪锌邪蟹芯薪 20鈥�512)</string>
+    <string name="pref_title_pebble_enable_applogs">袗泻褌懈胁懈褉邪泄 watch app logging</string>
+    <string name="pref_title_pebble_gatt_clientonly">小邪屑芯 GATT 泻谢懈械薪褌</string>
+    <string name="pref_summary_pebble_gatt_clientonly">小邪屑芯 蟹邪 Pebble 2, 械泻褋锌械褉懈屑械薪褌邪谢薪芯, 锌褉芯斜胁邪泄褌械 邪泻芯 懈屑邪褌械 锌褉芯斜谢械屑懈 褋 胁褉褗蟹泻邪褌邪</string>
+    <string name="pref_summary_pebble_enable_applogs">袥芯谐芯胁械褌械 芯褌 锌褉懈谢芯卸械薪懈褟褌邪 薪邪 褔邪褋芯胁薪懈泻邪 褖械 褋械 蟹邪锌懈褋胁邪褌 芯褌 Gadgetbridge (懈蟹懈褋泻胁邪 褋械 锌芯胁褌芯褉薪芯 褋胁褗褉蟹胁邪薪械)</string>
+    <string name="pref_title_pebble_always_ack_pebblekit">袩褉械卸写械胁褉械屑械薪薪芯 ACK PebbleKit</string>
+    <string name="pref_summary_pebble_always_ack_pebblekit">小褗芯斜褖械薪懈褟褌邪 芯褌 胁褗薪褕薪懈 锌褉懈谢芯卸械薪懈褟 写邪 褋械 锌芯褌胁褗褉卸写邪胁邪褌 胁械写薪邪谐邪 懈 薪械蟹邪斜邪胁薪芯</string>
+    <string name="pref_title_pebble_enable_bgjs">袙泻谢褞褔胁邪薪械 薪邪 JS 胁褗胁 褎芯薪芯胁 褉械卸懈屑</string>
+    <string name="pref_summary_pebble_enable_bgjs">袣芯谐邪褌芯 械 邪泻褌懈胁懈褉邪薪芯, 褑懈褎械褉斜谢邪褌懈褌械 褖械 锌芯泻邪蟹胁邪褌 胁褉械屑械褌芯, 懈薪褎芯褉屑邪褑懈褟 蟹邪 斜邪褌械褉懈褟褌邪 懈 写褉.</string>
+    <string name="pref_title_pebble_reconnect_attempts">袨锌懈褌懈 蟹邪 锌芯胁褌芯褉薪芯 褋胁褗褉蟹胁邪薪械</string>
+    <string name="pref_title_unit_system">袝写懈薪懈褑懈</string>
+    <string name="pref_title_timeformat">效邪褋芯胁懈 褎芯褉屑邪褌</string>
+    <string name="pref_title_screentime">袩褉芯写褗谢卸懈褌械谢薪芯褋褌 薪邪 胁泻谢褞褔械薪 械泻褉邪薪</string>
+    <string name="prefs_title_all_day_heart_rate">袠蟹屑械褉胁邪薪械 薪邪 褋褗褉写械褔薪懈褟 褉懈褌褗屑 锌褉械蟹 褑械谢懈褟 写械薪</string>
+    <string name="preferences_hplus_settings">HPlus/Makibes 薪邪褋褌褉芯泄泻懈</string>
+    <string name="prefs_screen_orientation">袨褉懈械薪褌邪褑懈褟 薪邪 械泻褉邪薪邪</string>
+    <string name="pref_header_auto_export">袗胁褌芯屑邪褌懈褔械薪 械泻褋锌芯褉褌</string>
+    <string name="pref_title_auto_export_enabled">袙泻谢褞褔胁邪薪械 薪邪 邪胁褌芯屑邪褌懈褔械薪 械泻褋锌芯褉褌</string>
+    <string name="pref_title_auto_export_location">袩褗褌 蟹邪 褋褗褏褉邪薪械薪懈械 薪邪 械泻褋锌芯褉褌邪</string>
+    <string name="pref_title_auto_export_interval">袠薪褌械褉胁邪谢</string>
+    <string name="pref_summary_auto_export_interval">袝泻褋锌芯褉褌 薪邪 胁褋械泻懈 %d 褔邪褋</string>
+    <string name="pref_auto_fetch">袗胁褌芯屑邪褌懈褔薪芯 懈蟹胁谢懈褔邪薪械 薪邪 写邪薪薪懈褌械 蟹邪 邪泻褌懈胁薪芯褋褌</string>
+    <string name="not_connected">袧褟屑邪 胁褉褗蟹泻邪</string>
+    <string name="pref_auto_fetch_limit_fetches_summary">小懈薪褏褉芯薪懈蟹懈褉邪薪械 薪邪 胁褋械泻懈 %d 屑懈薪褍褌懈</string>
+    <string name="connecting">小胁褗褉蟹胁邪薪械</string>
+    <string name="connected">小胁褗褉蟹邪薪</string>
+    <string name="unknown_state">袧械懈蟹胁械褋褌薪芯 褋褗褋褌芯褟薪懈械</string>
+    <string name="_unknown_">(薪械懈蟹胁械褋褌薪芯)</string>
+    <string name="test">孝械褋褌</string>
+    <string name="test_notification">孝械褋褌芯胁芯 懈蟹胁械褋褌懈械</string>
+    <string name="this_is_a_test_notification_from_gadgetbridge">孝芯胁邪 械 褌械褋褌芯胁芯 懈蟹胁械褋褌懈械 芯褌 Gadgetbridge</string>
+    <string name="bluetooth_is_not_supported_">Bluetooth 薪械 褋械 锌芯写写褗褉卸邪.</string>
+    <string name="bluetooth_is_disabled_">Bluetooth 械 蟹邪斜褉邪薪械薪.</string>
+    <string name="tap_connected_device_for_app_mananger">袛芯泻芯褋薪械褌械 褋胁褗褉蟹邪薪芯褌芯 褍褋褌褉芯泄褋褌胁芯, 蟹邪 写邪 褋械 褋褌邪褉褌懈褉邪 褍锌褉邪胁谢械薪懈械褌芯 薪邪 锌褉懈谢芯卸械薪懈褟</string>
+    <string name="tap_connected_device_for_activity">袛芯泻芯褋薪械褌械 褋胁褗褉蟹邪薪芯褌芯 褍褋褌褉芯泄褋褌胁芯 蟹邪 锌芯泻邪蟹胁邪薪械 薪邪 activity</string>
+    <string name="tap_connected_device_for_vibration">袛芯泻芯褋薪械褌械 褋胁褗褉蟹邪薪芯褌芯 褍褋褌褉芯泄褋褌胁芯 蟹邪 胁懈斜褉邪褑懈懈</string>
+    <string name="tap_a_device_to_connect">袛芯泻芯褋薪械褌械 褍褋褌褉芯泄褋褌胁芯 蟹邪 褋胁褗褉蟹胁邪薪械</string>
+    <string name="cannot_connect_bt_address_invalid_">袧械 屑芯卸械 写邪 褋械 褍褋褌邪薪芯胁懈 胁褉褗蟹泻邪. 袚褉械褕械薪 Bluetooth 邪写褉械褋\?</string>
+    <string name="gadgetbridge_running_generic">Gadgetbridge 褉邪斜芯褌懈</string>
+    <string name="installing_binary_d_d">袠薪褋褌邪谢懈褉邪薪械 薪邪 写胁芯懈褔械薪 褎邪泄谢 %1$d/%2$d</string>
+    <string name="installation_failed_">袠薪褋褌邪谢邪褑懈褟褌邪 锌褉芯锌邪写薪邪</string>
+    <string name="installation_successful">袠薪褋褌邪谢懈褉邪薪芯</string>
+    <string name="firmware_install_warning">袨袩袠孝袙袗孝袝 小袝 袛袗 袠袧小孝袗袥袠袪袗孝袝 FIRMWARE, 袩袪袨袛歇袥袞袝孝袝 袧袗 小袙袨携 袨孝袚袨袙袨袪袧袨小孝. 
+\n 
+\n 
+\n 孝芯蟹懈 firmware 械 蟹邪 HW Revision: %s</string>
+    <string name="app_install_info">些械 褋械 懈薪褋褌邪谢懈褉邪 褋谢械写薪芯褌芯 锌褉懈谢芯卸械薪懈械:
+\n
+\n
+\n%1$s 袙械褉褋懈褟 %2$s 芯褌 %3$s
+\n</string>
+    <string name="n_a">N/A</string>
+    <string name="initialized">懈薪懈褑懈邪谢懈蟹懈褉邪薪芯</string>
+    <string name="appversion_by_creator">%1$s 芯褌 %2$s</string>
+    <string name="title_activity_discovery">孝褗褉褋械薪械 薪邪 褍褋褌褉芯泄褋褌胁芯</string>
+    <string name="discovery_stop_scanning">小锌褉懈 褌褗褉褋械薪械褌芯</string>
+    <string name="discovery_start_scanning">小褌邪褉褌懈褉邪泄 褌褗褉褋械薪械</string>
+    <string name="action_discover">小胁褗褉蟹胁邪薪械 褋 薪芯胁芯 褍褋褌褉芯泄褋褌胁芯</string>
+    <string name="device_with_rssi">%1$s (%2$s)</string>
+    <string name="title_activity_android_pairing">小写胁芯褟胁邪薪械 薪邪 褍褋褌褉芯泄褋褌胁芯褌芯</string>
+    <string name="android_pairing_hint">袠蟹锌芯谢蟹胁邪薪械 薪邪 袗薪写褉芯懈写 Bluetooth 写懈邪谢芯谐邪 蟹邪 褋写胁芯褟胁邪薪械 薪邪 褍褋褌褉芯泄褋褌胁芯褌芯.</string>
+    <string name="title_activity_mi_band_pairing">小写胁芯械褌械 胁邪褕懈褟 Mi Band</string>
+    <string name="pairing">小写胁芯褟胁邪薪械 褋/褋褗褋 %s鈥�</string>
+    <string name="pairing_creating_bond_with">小褗蟹写邪胁邪薪械 薪邪 胁褉褗蟹泻邪 褋/褋褗褋 %1$s (%2$s)</string>
+    <string name="pairing_unable_to_pair_with">袧械 屑芯卸械 写邪 褋械 褋写胁芯懈 褋/褋褗褋 %1$s (%2$s)</string>
+    <string name="pairing_in_progress">校褋褌邪薪芯胁褟胁邪 褋械 胁褉褗蟹泻邪: %1$s (%2$s)</string>
+    <string name="pairing_already_bonded">校褋褌邪薪芯胁械薪邪 械 胁褉褗蟹泻邪 褋/褋褗褋 %1$s (%2$s), 褋胁褗褉蟹胁邪薪械鈥�</string>
+    <string name="message_cannot_pair_no_mac">袥懈锌褋胁邪 MAC 邪写褉械褋, 薪械 屑芯卸械 写邪 褋械 褍褋褌邪薪芯胁懈 胁褉褗蟹泻邪.</string>
+    <string name="preferences_category_device_specific_settings">小锌械褑懈褎懈褔薪懈 薪邪褋褌褉芯泄泻懈 蟹邪 褍褋褌褉芯泄褋褌胁邪褌邪</string>
+    <string name="preferences_miband_settings">Mi Band / Amazfit 薪邪褋褌褉芯泄泻懈</string>
+    <string name="male">袦褗卸</string>
+    <string name="female">袞械薪邪</string>
+    <string name="other">袛褉褍谐</string>
+    <string name="left">袥褟胁邪</string>
+    <string name="right">袛褟褋薪邪</string>
+    <string name="horizontal">啸芯褉懈蟹芯薪褌邪谢薪芯</string>
+    <string name="vertical">袙械褉褌懈泻邪谢薪芯</string>
+    <string name="miband_pairing_using_dummy_userdata">袧械 褋邪 锌褉械写芯褋褌邪胁械薪懈 褉械邪谢薪懈 锌芯褌褉械斜懈褌械谢褋泻懈 写邪薪薪懈, 褖械 褋械 懈蟹锌芯谢蟹胁邪褌 褋谢褍褔邪泄薪懈 写邪薪薪懈.</string>
+    <string name="miband_pairing_tap_hint">袣芯谐邪褌芯 胁邪褕懈褟褌 Mi Band 胁懈斜褉懈褉邪 懈 屑懈谐邪, 写芯泻芯褋薪械褌械 谐芯 薪褟泻芯谢泻芯 锌褗褌懈.</string>
+    <string name="appinstaller_install">袠薪褋褌邪谢懈褉邪泄</string>
+    <string name="discovery_connected_devices_hint">袧邪锌褉邪胁械褌械 褍褋褌褉芯泄褋褌胁芯褌芯 褋懈 芯褌泻褉懈胁邪械屑芯. 袙 屑芯屑械薪褌邪 褋胁褗褉蟹邪薪懈褌械 褍褋褌褉芯泄褋褌胁邪 胁械褉芯褟褌薪芯 薪褟屑邪 写邪 斜褗写邪褌 芯褌泻褉懈褌懈. 袗泻褌懈胁懈褉邪泄褌械 屑械褋褌芯锌芯谢芯卸械薪懈械褌芯 (e.g. GPS) 薪邪 Android 6+. 袠蟹泻谢褞褔械褌械 Disable Privacy Guard for Gadgetbridge, 蟹邪褖芯褌芯 褌芯泄 屑芯卸械 写邪 斜谢芯泻懈褉邪 懈谢懈 褉械褋褌邪褉褌懈褉邪 褌械谢械褎芯薪邪. 袗泻芯 写芯 薪褟泻芯谢泻芯 屑懈薪褍褌懈 薪械 斜褗写械 芯褌泻褉懈褌芯 褍褋褌褉芯泄褋褌胁芯, 褉械褋褌邪褉褌懈褉邪泄褌械 褌械谢械褎芯薪邪 懈 芯锌懈褌邪泄褌械 芯褌薪芯胁芯.</string>
+    <string name="discovery_note">袘械谢械卸泻邪:</string>
+    <string name="candidate_item_device_image">袠蟹芯斜褉邪卸械薪懈械 薪邪 褍褋褌褉芯泄褋褌胁芯褌芯</string>
+    <string name="miband_prefs_alias">袠屑械/袩褋械胁写芯薪懈屑</string>
+    <string name="pref_header_vibration_count">袘褉芯泄 薪邪 胁懈斜褉邪褑懈懈褌械</string>
+    <string name="watch9_pairing_tap_hint">袣芯谐邪褌芯 褔邪褋芯胁薪懈泻褗褌 蟹邪胁懈斜褉懈褉邪, 褉邪蟹泻谢邪褌械褌械 褍褋褌褉芯泄褋褌胁芯褌芯 懈谢懈 薪邪褌懈褋薪械褌械 斜褍褌芯薪邪</string>
+    <string name="watch9_time_minutes">袦懈薪褍褌懈:</string>
+    <string name="watch9_time_hours">效邪褋芯胁械:</string>
+    <string name="watch9_time_seconds">小械泻褍薪写懈:</string>
+    <string name="watch9_calibration_hint">袟邪写邪泄褌械 胁褉械屑械褌芯, 泻芯械褌芯 锌芯泻邪蟹胁邪 褍褋褌褉芯泄褋褌胁芯褌芯 袙懈.</string>
+    <string name="watch9_calibration_button">小胁械褉褟胁邪薪械</string>
+    <string name="title_activity_watch9_pairing">Watch 9 褋胁褗褉蟹胁邪薪械</string>
+    <string name="title_activity_watch9_calibration">Watch 9 褋胁械褉褟胁邪薪械</string>
+    <string name="mi2_prefs_do_not_disturb_summary">袧褟屑邪 写邪 褋械 锌芯谢褍褔邪胁邪褌 懈蟹胁械褋褌懈褟 写芯泻邪褌芯 械 邪泻褌懈胁薪芯</string>
+    <!-- WatchXPlus Preferences -->
+    <string name="title_activity_LenovoWatch_calibration">Watch X Plus 褋胁械褉褟胁邪薪械</string>
+    <!-- Device Settings - Notifications and Calls - Used in devicesettings_watchxplus.xml -->
+    <string name="pref_header_notifications_and_calls">袠蟹胁械褋褌懈褟 懈 袨斜邪卸写邪薪懈褟</string>
+    <string name="pref_title_notifications_and_calls_repeat_on_call">袩芯胁褌邪褉褟泄 懈蟹胁械褋褌懈械 蟹邪 锌芯蟹胁褗薪褟胁邪薪械</string>
+    <string name="prefs_notifications_and_calls_continious_ring">袠蟹胁械褋褌褟胁邪泄 写芯泻邪褌芯 褌械谢械褎芯薪邪 蟹胁褗薪懈</string>
+    <string name="pref_notifications_and_calls_enable_misscall">袠蟹胁械褋褌褟胁邪泄 蟹邪 锌褉芯锌褍褋薪邪褌芯 锌芯胁懈泻胁邪薪械</string>
+    <string name="pref_summary_notifications_and_calls_enable_misscall">袩芯胁褌邪褉褟 褋械 薪邪 胁褋褟泻邪 屑懈薪褍褌邪</string>
+    <string name="pref_title_notifications_and_calls_repeat_on_misscall">袩芯胁褌邪褉褟泄 蟹邪 啸 屑懈薪褍褌懈</string>
+    <string name="pref_header_notifications_and_calls_callhandling">校锌褉邪胁谢械薪懈械 薪邪 芯斜邪卸写邪薪懈褟褌邪</string>
+    <string name="prefs_notifications_and_calls_reject">袘褍褌芯薪邪 蟹邪谐谢褍褕邪胁邪/芯褌泻邪蟹胁邪 锌芯胁懈泻胁邪薪械</string>
+    <string name="pref_summary_notifications_and_calls_title_reject">袠蟹泻谢. - 蟹邪谐谢褍褕懈, 袙泻谢. - 芯褌泻邪卸懈</string>
+    <string name="prefs_notifications_and_calls_shake_reject">袪邪蟹泻谢邪褌懈 蟹邪 蟹邪谐谢褍褕邪胁邪薪械/芯褌泻邪蟹胁邪薪械 薪邪 锌芯胁懈泻胁邪薪械褌芯</string>
+    <string name="pref_summary_notifications_and_calls_title_shake_reject">袩芯胁褌邪褉褟 写械泄褋褌胁懈械褌芯 薪邪 斜褍褌芯薪邪</string>
+    <!-- Device Settings - Device Settings - Used in devicesettings_watchxplus.xml -->
+    <string name="pref_header_device_spec_settings">袧邪褋褌褉芯泄泻懈 薪邪 褍褋褌褉芯泄褋褌胁芯褌芯</string>
+    <string name="pref_title_device_spec_settings_force_time">袩褉懈薪褍写懈褌械谢薪芯 褋懈薪褏褉芯薪懈蟹懈褉邪泄 褔邪褋邪</string>
+    <string name="pref_summary_device_spec_settings_title_force_time">袩褉懈薪褍写懈褌械谢薪芯 褋懈薪褏褉芯薪懈蟹懈褉邪泄 写邪褌邪 懈 褔邪褋 锌褉懈 褋胁褗褉蟹胁邪薪械. 小褌褉械谢泻懈褌械 屑芯卸械 写邪 锌芯泻邪蟹胁邪褌 谐褉械褕薪芯 胁褉械屑械!</string>
+    <string name="pref_title_device_spec_settings_show_raw_graph">袩芯泻邪卸懈 谐褉邪褎懈泻邪 胁 褋褍褉芯胁 胁懈写</string>
+    <!-- Device Settings - Calibration - Used in devicesettings_watchxplus.xml -->
+    <string name="pref_header_sensors_calibration">袣邪谢懈斜褉懈褉邪薪械 薪邪 褋械薪蟹芯褉懈褌械</string>
+    <string name="pref_title_sensors_altitude">袣邪谢懈斜褉懈褉邪薪械 薪邪 胁懈褋芯褔懈薪邪褌邪</string>
+    <string name="pref_sensors_bp_calibration">袣邪谢懈斜褉懈褉邪薪械 薪邪 泻褉褗胁薪芯 薪邪谢褟谐邪薪械</string>
+    <string name="pref_sensors_bp_calibration_low">袣褉褗胁薪芯 薪邪谢褟谐邪薪械 DIASTOLIC (薪懈褋泻邪)</string>
+    <string name="pref_sensors_bp_calibration_high">袣褉褗胁薪芯 薪邪谢褟谐邪薪械 SYSTOLIC (胁懈褋芯泻邪)</string>
+    <string name="prefs_sensors_button_bp_calibration">袣邪谢懈斜褉懈褉邪薪械</string>
+    <string name="prefs_sensors_button_bp_calibration_sum">袧邪褌懈褋薪懈 褌褍泻 蟹邪 泻邪谢懈斜褉懈褉邪薪械</string>
+    <!-- Device Settings - Power Mode -->
+    <string name="power_mode_title">袪械卸懈屑 薪邪 褔邪褋芯胁薪懈泻邪</string>
+    <string name="power_mode_normal">袧芯褉屑邪谢械薪</string>
+    <string name="power_mode_saving">袠泻芯薪芯屑懈褔械薪</string>
+    <string name="power_mode_watch">小邪屑芯 褔邪褋芯胁薪懈泻</string>
+    <string name="title_activity_sleepmonitor">袧邪斜谢褞写械薪懈械/邪薪邪谢懈蟹 薪邪 褋褗薪褟</string>
+    <string name="pref_write_logfiles">小褗褏褉邪薪褟胁邪薪械 薪邪 log 褎邪泄谢芯胁械</string>
+    <string name="initializing">袠薪懈褑懈邪谢懈蟹懈褉邪薪械</string>
+    <string name="busy_task_fetch_activity_data">袠蟹胁谢懈褔邪薪械 薪邪 写邪薪薪懈褌械 蟹邪 邪泻褌懈胁薪芯褋褌</string>
+    <string name="sleep_activity_date_range">袨褌 %1$s 袛芯 %2$s</string>
+    <string name="prefs_wearside">袧邪 泻芯褟 褉褗泻邪 - 谢褟胁邪 懈谢懈 写褟褋薪邪\?</string>
+    <string name="pref_screen_vibration_profile">袩褉芯褎懈谢 薪邪 胁懈斜褉邪褑懈懈褌械</string>
+    <string name="vibration_profile_staccato">小褌邪泻邪褌芯</string>
+    <string name="vibration_profile_short">袣褉邪褌泻芯</string>
+    <string name="vibration_profile_medium">小褉械写薪芯</string>
+    <string name="vibration_profile_long">袛褗谢谐芯</string>
+    <string name="vibration_profile_waterdrop">袙芯写薪邪 泻邪锌泻邪</string>
+    <string name="vibration_profile_ring">袟胁褗薪械褑</string>
+    <string name="vibration_profile_alarm_clock">袘褍写懈谢薪懈泻</string>
+    <string name="miband_prefs_vibration">袙懈斜褉邪褑懈褟</string>
+    <string name="vibration_try">袩褉芯斜邪</string>
+    <string name="pref_screen_notification_profile_sms">SMS 懈蟹胁械褋褌懈械</string>
+    <string name="pref_header_vibration_settings">袧邪褋褌褉芯泄泻懈 薪邪 胁懈斜褉邪褑懈褟褌邪</string>
+    <string name="pref_screen_notification_profile_generic">袨斜褖邪 锌芯写写褉褗卸泻邪 薪邪 懈蟹胁械褋褌懈褟</string>
+    <string name="pref_screen_notification_profile_email">袠屑械泄谢 懈蟹胁械褋褌褟胁邪薪械</string>
+    <string name="pref_screen_notification_profile_incoming_call">袠蟹胁械褋褌褟胁邪薪械 蟹邪 胁褏芯写褟褖芯 芯斜邪卸写邪薪械</string>
+    <string name="pref_screen_notification_profile_generic_chat">效邪褌</string>
+    <string name="pref_screen_notification_profile_generic_navigation">袧邪胁懈谐邪褑懈褟</string>
+    <string name="pref_screen_notification_profile_generic_social">小芯褑懈邪谢薪懈 屑褉械卸懈</string>
+    <string name="prefs_title_heartrate_measurement_interval">袠蟹屑械褉胁邪薪械 薪邪 褋褗褉写械褔薪懈褟 褉懈褌褗屑 锌褉械蟹 褑械谢懈褟 写械薪</string>
+    <string name="interval_one_minute">薪邪 胁褋褟泻邪 屑懈薪褍褌邪</string>
+    <string name="interval_five_minutes">薪邪 胁褋械泻懈 5 屑懈薪褍褌懈</string>
+    <string name="interval_ten_minutes">薪邪 胁褋械泻懈 10 屑懈薪褍褌懈</string>
+    <string name="interval_thirty_minutes">薪邪 胁褋械泻懈 30 屑懈薪褍褌懈</string>
+    <string name="interval_one_hour">胁械写薪褗卸 薪邪 褔邪褋</string>
+    <string name="zetime_title_settings">ZeTime 薪邪褋褌褉芯泄泻懈</string>
+    <string name="zetime_title_heartrate">小褗褉写械褔械薪 褉懈褌褗屑 薪邪褋褌褉芯泄泻懈</string>
+    <string name="zetime_title_screentime">袩褉芯写褗谢卸懈褌械谢薪芯褋褌 薪邪 胁泻谢褞褔械薪 械泻褉邪薪 胁 褋械泻褍薪写懈</string>
+    <string name="zetime_title_heart_rate_alarm">小褗褉写械褔械薪 褉懈褌褗屑 邪谢邪褉屑邪</string>
+    <string name="zetime_title_heart_rate_alarm_summary">效邪褋芯胁薪懈泻褗褌 褖械 胁懈 锌褉械写褍锌褉械写懈, 泻芯谐邪褌芯 褋褗褉写械褔薪懈褟褌 褉懈褌褗屑 锌褉械屑懈薪械 谐褉邪薪懈褑懈褌械.</string>
+    <string name="zetime_heart_rate_alarm_enable">袙泻谢褞褔胁邪薪械 薪邪 邪谢邪褉屑邪褌邪 蟹邪 褋褗褉写械褔薪懈褟 褉懈褌褗屑</string>
+    <string name="activity_prefs_alarm_max_heart_rate">袦邪泻褋懈屑邪谢械薪 褋褗褉写械褔械薪 褉懈褌褗屑</string>
+    <string name="activity_prefs_alarm_min_heart_rate">袦懈薪懈屑邪谢械薪 褋褗褉写械褔械薪 褉懈褌褗屑</string>
+    <string name="zetime_analog_mode">袗薪邪谢芯谐芯胁 褉械卸懈屑</string>
+    <string name="zetime_analog_mode_hands">小邪屑芯 褉褗褑械</string>
+    <string name="zetime_analog_mode_handsandsteps">袪褗褑械 懈 泻褉邪褔泻懈</string>
+    <string name="zetime_activity_tracking">袩褉芯褋谢械写褟胁邪薪械 薪邪 邪泻褌懈胁薪芯褋褌</string>
+    <string name="zetime_handmove_display">袛胁懈卸械薪懈械 薪邪 褉褗泻邪褌邪</string>
+    <string name="zetime_handmove_display_summary">袟邪胁褗褉褌械褌械 泻懈褌泻邪褌邪, 蟹邪 邪泻褌懈胁懈褉邪薪械 懈谢懈 写械邪泻褌懈胁懈褉邪薪械 薪邪 械泻褉邪薪邪.</string>
+    <string name="zetime_calories_type">袙懈写 泻邪谢芯褉懈懈</string>
+    <string name="zetime_calories_type_active">小邪屑芯 邪泻褌懈胁薪芯 懈蟹谐芯褉械薪懈 泻邪谢芯褉懈懈</string>
+    <string name="zetime_calories_type_all">袗泻褌懈胁薪芯 懈 薪械邪泻褌懈胁薪芯 懈蟹谐芯褉械薪懈 泻邪谢芯褉懈懈</string>
+    <string name="zetime_date_format">肖芯褉屑邪褌 薪邪 写邪褌邪褌邪</string>
+    <string name="zetime_date_format_1">袚袚/袦袦/袛袛</string>
+    <string name="zetime_date_format_2">袛袛/袦袦/袚袚</string>
+    <string name="zetime_date_format_3">袦袦/袛袛/袚袚</string>
+    <string name="zetime_prefs_inactivity_repetitions">袩芯胁褌芯褉械薪懈褟</string>
+    <string name="zetime_prefs_inactivity_mo">袩芯薪械写械谢薪懈泻</string>
+    <string name="zetime_prefs_inactivity_tu">袙褌芯褉薪懈泻</string>
+    <string name="zetime_prefs_inactivity_we">小褉褟写邪</string>
+    <string name="zetime_prefs_inactivity_th">效械褌胁褗褉褌褗泻</string>
+    <string name="zetime_prefs_inactivity_fr">袩械褌褗泻</string>
+    <string name="zetime_prefs_inactivity_sa">小褗斜芯褌邪</string>
+    <string name="zetime_prefs_inactivity_su">袧械写械谢褟</string>
+    <string name="zetime_signaling_none">孝懈褏芯</string>
+    <string name="zetime_signaling_vibrate">袩褉芯写褗谢卸懈褌械谢薪芯 胁懈斜褉懈褉邪薪械</string>
+    <string name="zetime_signaling_beep">袩褉芯写褗谢卸懈褌械谢械薪 蟹胁褍泻</string>
+    <string name="zetime_signaling_vibrate_beep">袩褉芯写褗谢卸懈褌械谢薪芯 胁懈斜褉懈褉邪薪械 懈 蟹胁褍泻</string>
+    <string name="zetime_signaling_vibrate_once">袝写薪芯泻褉邪褌薪芯 胁懈斜褉懈褉邪薪械</string>
+    <string name="zetime_signaling_vibrate_twice">袛胁褍泻褉邪褌薪芯 胁懈斜褉懈褉邪薪械</string>
+    <string name="zetime_signaling_beep_once">袝写薪芯泻褉邪褌械薪 蟹胁褍泻</string>
+    <string name="zetime_signaling_beep_twice">袛胁褍泻褉邪褌械薪 蟹胁褍泻</string>
+    <string name="zetime_signaling_vibrate_beep_once">袙懈斜褉懈褉邪薪械 懈 蟹胁褍泻 械写薪芯泻褉邪褌薪芯</string>
+    <string name="pref_auto_fetch_summary">小懈薪褏褉芯薪懈蟹邪褑懈褟 褋谢械写 芯褌泻谢褞褔胁邪薪械 薪邪 械泻褉邪薪邪. 袪邪斜芯褌懈 褋邪屑芯 锌褉懈 蟹邪写邪写械薪邪 薪邪褋褌褉芯泄泻邪 蟹邪 蟹邪泻谢褞褔胁邪薪械 薪邪 械泻褉邪薪邪!</string>
+    <string name="pref_auto_fetch_limit_fetches">袦懈薪懈屑邪谢薪芯 胁褉械屑械 屑械卸写褍 褋懈薪褏褉芯薪懈蟹邪褑懈懈褌械</string>
+    <string name="pref_screen_notification_profile_missed_call">袠蟹胁械褋褌褟胁邪薪械 蟹邪 锌褉芯锌褍褋薪邪褌芯 芯斜邪卸写邪薪械</string>
+    <string name="pref_screen_notification_profile_calendar">袣邪谢械薪写邪褉 懈蟹胁械褋褌褟胁邪薪械</string>
+    <string name="pref_screen_notification_profile_inactivity">袠蟹胁械褋褌褟胁邪薪械 蟹邪 薪械邪泻褌懈胁薪芯褋褌</string>
+    <string name="pref_screen_notification_profile_low_power">袩褉械写褍锌褉械卸写械薪懈械 蟹邪 薪懈褋褗泻 蟹邪褉褟写 薪邪 斜邪褌械褉懈褟褌邪</string>
+    <string name="pref_screen_notification_profile_anti_loss">袩褉械写褍锌褉械卸写械薪懈械 锌褉懈 懈蟹谐褍斜胁邪薪械</string>
+    <string name="interval_fifteen_minutes">胁褋械泻懈 15 屑懈薪褍褌懈</string>
+    <string name="interval_forty_five_minutes">胁褋械泻懈 45 屑懈薪褍褌懈</string>
+    <string name="stats_title">袟芯薪懈 蟹邪 褋泻芯褉芯褋褌</string>
+    <string name="stats_x_axis_label">袨斜褖芯 屑懈薪褍褌懈</string>
+    <string name="stats_y_axis_label">袣褉邪褔泻懈 胁 屑懈薪褍褌邪</string>
+    <string name="control_center_find_lost_device">孝褗褉褋械薪械 薪邪 蟹邪谐褍斜械薪芯 褍褋褌褉芯泄褋褌胁芯</string>
+    <string name="control_center_cancel_to_stop_vibration">袨褌屑械薪械褌械 蟹邪 褋锌懈褉邪薪械 薪邪 胁懈斜褉邪褑懈褟褌邪.</string>
+    <string name="title_activity_charts">袙邪褕邪褌邪 邪泻褌懈胁薪芯褋褌</string>
+    <string name="title_activity_set_alarm">袧邪褋褌褉芯泄泻邪 薪邪 邪谢邪褉屑懈褌械</string>
+    <string name="controlcenter_start_configure_alarms">袧邪褋褌褉芯泄泻邪 薪邪 邪谢邪褉屑懈褌械</string>
+    <string name="title_activity_alarm_details">袛械褌邪泄谢懈 薪邪 邪谢邪褉屑邪褌邪</string>
+    <string name="alarm_sun_short">袧械写</string>
+    <string name="alarm_mon_short">袩芯薪</string>
+    <string name="alarm_tue_short">袙褌芯</string>
+    <string name="alarm_wed_short">小褉褟</string>
+    <string name="alarm_thu_short">效械褌</string>
+    <string name="alarm_fri_short">袩械褌</string>
+    <string name="alarm_sat_short">小褗斜</string>
+    <string name="alarm_smart_wakeup">Smart 褋褗斜褍卸写邪薪械</string>
+    <string name="user_feedback_miband_set_alarms_failed">袙褗蟹薪懈泻薪邪 谐褉械褕泻邪 锌褉懈 薪邪褋褌褉芯泄泻邪 薪邪 邪谢邪褉屑懈褌械, 屑芯谢褟 芯锌懈褌邪泄褌械 芯褌薪芯胁芯.</string>
+    <string name="user_feedback_miband_set_alarms_ok">袗谢邪褉屑懈褌械 褋邪 懈蟹锌褉邪褌械薪懈 薪邪 褍褋褌褉芯泄褋褌胁芯褌芯.</string>
+    <string name="chart_no_data_synchronize">袧褟屑邪 写邪薪薪懈. 小懈薪褏褉芯薪懈蟹懈褉邪薪械 薪邪 褍褋褌褉芯泄褋褌胁芯\?</string>
+    <string name="user_feedback_miband_activity_data_transfer">些械 褋械 锌褉械褏胁褗褉谢褟褌 %1$s 写邪薪薪懈, 泻邪褌芯 褋械 蟹邪锌芯褔薪械 芯褌 %2$s</string>
+    <string name="miband_prefs_fitness_goal">袩谢邪薪懈褉邪薪懈 泻褉邪褔泻懈 蟹邪 写械薪</string>
+    <string name="dbaccess_error_executing">袚褉械褕泻邪 锌褉懈 懈蟹锌褗谢薪械薪懈械 薪邪 \'%1$s\'</string>
+    <string name="controlcenter_start_activitymonitor">袙邪褕邪褌邪 邪泻褌懈胁薪芯褋褌 (ALPHA)</string>
+    <string name="cannot_connect">袧械 屑芯卸械 写邪 褋械 褍褋褌邪薪芯胁懈 胁褉褗蟹泻邪: %1$s</string>
+    <string name="installer_activity_unable_to_find_handler">袧械 屑芯卸械 写邪 褋械 薪邪屑械褉懈 屑邪薪懈锌褍谢邪褌芯褉 蟹邪 懈薪褋褌邪谢懈褉邪薪械 薪邪 褌芯蟹懈 褎邪泄谢.</string>
+    <string name="pbw_install_handler_unable_to_install">袧械 屑芯卸械 写邪 褋械 懈薪褋褌邪谢懈褉邪 褎邪泄谢褗褌: %1$s</string>
+    <string name="pbw_install_handler_hw_revision_mismatch">袧械 屑芯卸械 写邪 褋械 懈薪褋褌邪谢懈褉邪 写邪写械薪懈褟褌 firmware: 袧械 褋褗芯褌胁械褋褌胁邪 薪邪 胁械褉褋懈褟褌邪 薪邪 胁邪褕懈褟 Pebble.</string>
+    <string name="installer_activity_wait_while_determining_status">袦芯谢褟, 懈蟹褔邪泻邪泄褌械 写芯泻邪褌芯 褋械 锌褉芯胁械褉褟胁邪 褋褌邪褌褍褋邪 薪邪 懈薪褋褌邪谢邪褑懈褟褌邪鈥�</string>
+    <string name="notif_battery_low_title">袘邪褌械褉懈褟褌邪 薪邪 褍褋褌褉芯泄褋褌胁芯褌芯 械 褋 薪懈褋褗泻 蟹邪褉褟写!</string>
+    <string name="notif_battery_low_percent">%1$s 蟹邪褉褟写 薪邪 斜邪褌械褉懈褟: %2$s%%</string>
+    <string name="notif_battery_low_bigtext_last_charge_time">袩芯褋谢械写薪芯 蟹邪褉械写械薪: %s
+\n</string>
+    <string name="notif_battery_low_bigtext_number_of_charges">袘褉芯泄 蟹邪褉械卸写邪薪懈褟: %s</string>
+    <string name="notif_battery_low">%1$s 斜邪褌械褉懈褟褌邪 械 褉邪蟹褉械写械薪邪</string>
+    <string name="notif_battery_low_extended">%1$s 斜邪褌械褉懈褟褌邪 械 褉邪蟹褉械写械薪邪: %2$s</string>
+    <string name="pref_title_support_voip_calls">袗泻褌懈胁懈褉邪薪械 薪邪 芯斜邪卸写邪薪械 芯褌 VoIP 锌褉懈谢芯卸械薪懈褟</string>
+    <string name="notif_export_failed_title">袝泻褋锌芯褉褌褗褌 薪邪 斜邪蟹邪褌邪 写邪薪薪懈 锌褉芯锌邪写薪邪! 袦芯谢褟, 锌褉芯胁械褉械褌械 褋懈 薪邪褋褌褉芯泄泻懈褌械.</string>
+    <string name="sleepchart_your_sleep">袙邪褕懈褟褌 褋褗薪</string>
+    <string name="weekstepschart_steps_a_week">袣褉邪褔泻懈 蟹邪 褋械写屑懈褑邪</string>
+    <string name="activity_sleepchart_activity_and_sleep">袙邪褕懈褌械 邪泻褌懈胁薪芯褋褌 懈 褋褗薪</string>
+    <string name="lack_of_sleep">袧械写芯褋锌懈胁邪薪械: %1$s</string>
+    <string name="updating_firmware">袨斜薪芯胁褟胁邪薪械 薪邪 firmware鈥�</string>
+    <string name="fwapp_install_device_not_ready">肖邪泄谢褗褌 薪械 屑芯卸械 写邪 斜褗写械 懈薪褋褌邪谢懈褉邪薪, 褍褋褌褉芯泄褋褌胁芯褌芯 薪械 械 谐芯褌芯胁芯.</string>
+    <string name="installhandler_firmware_name">%1$s: %2$s %3$s</string>
+    <string name="miband_fwinstaller_compatible_version">小褗胁屑械褋褌懈屑邪 胁械褉褋懈褟</string>
+    <string name="miband_fwinstaller_untested_version">袧械褌械褋褌胁邪薪邪 胁械褉褋懈褟!</string>
+    <string name="fwappinstaller_connection_state">小胁褗褉蟹胁邪薪械 褋 褍褋褌褉芯泄褋褌胁芯: %1$s</string>
+    <string name="pbw_installhandler_pebble_firmware">Pebble Firmware %1$s</string>
+    <string name="pbwinstallhandler_correct_hw_revision">袣芯褉械泻褌薪邪 褏邪褉写褍械褉薪邪 褉械胁懈蟹懈褟</string>
+    <string name="pbwinstallhandler_incorrect_hw_revision">袧械褋褗芯褌胁械褌褋褌胁懈械 薪邪 褏邪褉写褍械褉薪邪褌邪 褉械胁懈蟹懈褟!</string>
+    <string name="pbwinstallhandler_app_item">%1$s (%2$s)</string>
+    <string name="updatefirmwareoperation_updateproblem_do_not_reboot">袩褉芯斜谢械屑 锌褉懈 蟹邪褉械卸写邪薪械褌芯 薪邪 firmware. 袧袝 袪袝小孝袗袪孝袠袪袗袡孝袝 胁邪褕懈褟 Mi Band!</string>
+    <string name="updatefirmwareoperation_metadata_updateproblem">袩褉芯斜谢械屑 锌褉懈 蟹邪褉械卸写邪薪械褌芯 薪邪 屑械褌邪写邪薪薪懈褌械 薪邪 褎褗褉屑褍械褉邪</string>
+    <string name="updatefirmwareoperation_update_complete">袠薪褋褌邪谢懈褉邪薪械褌芯 薪邪 firmware 蟹邪胁褗褉褕懈</string>
+    <string name="updatefirmwareoperation_update_complete_rebooting">袠薪褋褌邪谢懈褉邪薪械褌芯 薪邪 firmware 蟹邪胁褗褉褕懈, 褍褋褌褉芯泄褋褌胁芯褌芯 褋械 褉械褋褌邪褉褌懈褉邪鈥�</string>
+    <string name="updatefirmwareoperation_write_failed">Firmware 芯斜薪芯胁褟胁邪薪械褌芯 械 薪械褍褋锌械褕薪芯</string>
+    <string name="chart_steps">袣褉邪褔泻懈</string>
+    <string name="calories">袣邪谢芯褉懈懈</string>
+    <string name="distance">袪邪蟹褋褌芯褟薪懈械</string>
+    <string name="clock">效邪褋芯胁薪懈泻</string>
+    <string name="heart_rate">小褗褉写械褔械薪 褉懈褌褗屑</string>
+    <string name="battery">袘邪褌械褉懈褟</string>
+    <string name="no_limit">袘械蟹 芯谐褉邪薪懈褔械薪懈械</string>
+    <string name="seconds_5">5 褋械泻褍薪写懈</string>
+    <string name="seconds_10">10 褋械泻褍薪写懈</string>
+    <string name="seconds_20">20 褋械泻褍薪写懈</string>
+    <string name="seconds_30">30 褋械泻褍薪写懈</string>
+    <string name="minutes_1">1 屑懈薪褍褌邪</string>
+    <string name="minutes_5">5 屑懈薪褍褌懈</string>
+    <string name="minutes_10">10 屑懈薪褍褌懈</string>
+    <string name="minutes_30">30 屑懈薪褍褌懈</string>
+    <string name="liveactivity_live_activity">袗泻褌懈胁薪芯褋褌 胁 褉械邪谢薪芯 胁褉械屑械</string>
+    <string name="weeksteps_today_steps_description">袣褉邪褔泻懈 写薪械褋, 褑械谢: %1$s</string>
+    <string name="lack_of_step">袧械写芯褋褌懈谐 薪邪 泻褉邪褔泻懈: %1$d</string>
+    <string name="overstep">袣褉邪褔泻懈 胁 锌芯胁械褔械: %1$d</string>
+    <string name="pref_summary_dont_ack_transfers">袗泻芯 写邪薪薪懈褌械 蟹邪 邪泻褌懈胁薪芯褋褌 薪械 褋邪 锌芯褌胁褗褉械薪懈 芯褌 褍褋褌褉芯泄褋褌胁芯褌芯, 褌械 薪褟屑邪 写邪 褋械 懈蟹褔懈褋褌褟褌. 袩芯谢械蟹薪芯 械, 泻芯谐邪褌芯 GB 褋械 懈蟹锌芯谢蟹胁邪 蟹邪械写薪芯 褋 写褉褍谐懈 锌褉懈谢芯卸械薪懈褟.</string>
+    <string name="pref_summary_keep_data_on_device">袛邪薪薪懈褌械 蟹邪 邪泻褌懈胁薪芯褋褌 褖械 褋械 锌邪蟹褟褌 薪邪 Mi Band 写芯褉懈 褋谢械写 褋懈薪褏褉芯薪懈蟹邪褑懈褟. 袩芯谢械蟹薪芯 械, 泻芯谐邪褌芯 GB 褋械 懈蟹锌芯谢蟹胁邪 蟹邪械写薪芯 褋 写褉褍谐懈 锌褉懈谢芯卸械薪懈褟.</string>
+    <string name="weeksleepchart_sleep_a_week">小械写屑懈褔械薪 褋褗薪</string>
+    <string name="weeksleepchart_today_sleep_description">袛薪械胁械薪 褋褗薪, 褑械谢: %1$s</string>
+    <string name="overslept">袩褉械褋锌懈胁邪薪械: %1$s</string>
+    <string name="pref_summary_low_latency_fw_update">孝芯胁邪 屑芯卸械 写邪 锌芯屑芯谐薪械 薪邪 褍褋褌褉芯泄褋褌胁邪, 薪邪 泻芯懈褌芯 芯斜薪芯胁褟胁邪薪械褌芯 薪邪 firmware 械 薪械褍褋锌械褕薪芯</string>
+    <string name="live_activity_steps_history">袠褋褌芯褉懈褟 泻褉邪褔泻懈</string>
+    <string name="live_activity_current_steps_per_minute">孝械泻褍褖懈 泻褉邪褔泻懈 胁 屑懈薪褍褌邪</string>
+    <string name="live_activity_total_steps">袨斜褖芯 泻褉邪褔泻懈</string>
+    <string name="live_activity_steps_per_minute_history">袠褋褌芯褉懈褟 薪邪 泻褉邪褔泻懈 胁 屑懈薪褍褌邪</string>
+    <string name="live_activity_start_your_activity">小褌邪褉褌懈褉邪泄褌械 邪泻褌懈胁薪芯褋褌</string>
+    <string name="title_activity_device_specific_settings">小锌械褑懈褎懈褔薪懈 薪邪褋褌褉芯泄泻懈 蟹邪 褍褋褌褉芯泄褋褌胁芯褌芯</string>
+    <string name="average">小褉械写薪芯: %1$s</string>
+    <string name="mi2_prefs_button_press_broadcast_default_value" translatable="false">nodomain.freeyourgadget.gadgetbridge.ButtonPressed</string>
+    <string name="Cancel">袨褌泻邪蟹</string>
+    <string name="Delete">袠蟹褌褉懈泄</string>
+    <string name="abstract_chart_fragment_kind_deep_sleep">袛褗谢斜芯泻 褋褗薪</string>
+    <string name="abstract_chart_fragment_kind_light_sleep">袥械泻 褋褗薪</string>
+    <string name="abstract_chart_fragment_kind_not_worn">袧械 械 薪芯褋械薪</string>
+    <string name="abstract_chart_fragment_kind_activity">袗泻褌懈胁薪芯褋褌</string>
+    <string name="_pebble_watch_reply">袨褌谐芯胁芯褉懈</string>
+    <string name="action_db_management">校锌褉邪胁谢械薪懈械 薪邪 斜邪蟹邪褌邪</string>
+    <string name="activity_DB_empty_button">袠蟹褔懈褋褌懈 袘袛</string>
+    <string name="activity_DB_import_button">袠屑锌芯褉褌 袘袛</string>
+    <string name="activity_DB_delete_legacy_button">袠蟹褌褉懈泄 褋褌邪褉邪 袘袛</string>
+    <string name="activity_DB_ExportButton">袝泻褋锌芯褉褌 薪邪 袘袛</string>
+    <string name="activity_DB_test_export_message">袝泻褋锌芯褉褌懈褉邪薪械 薪邪 袘袛鈥�</string>
+    <string name="activity_DB_test_export_button">小褌邪褉褌懈褉邪泄 邪胁褌芯 械泻褋锌芯褉褌懈褉邪薪械</string>
+    <string name="activity_db_management_autoexport_explanation">袥芯泻邪褑懈褟褌邪 蟹邪 械泻褋锌芯褉褌 薪邪 袘袛 械:</string>
+    <string name="activity_db_management_autoexport_label">袗胁褌芯袝泻褋锌芯褉褌</string>
+    <string name="activity_db_management_empty_DB">袠蟹锌褉邪蟹薪懈袘袛</string>
+    <string name="activity_db_management_empty_db_warning">袙薪懈屑邪薪懈械! 袗泻芯 薪邪褌懈褋薪械褌械 褌芯蟹懈 斜褍褌芯薪 袘袛 褖械 褋械 懈蟹褌褉懈械.</string>
+    <string name="activity_db_management_exportimport_label">袝泻褋锌芯褉褌 懈 袠屑锌芯褉褌</string>
+    <string name="activity_prefs_sleep_duration">袙褉械屑械 蟹邪 褋褗薪 胁 褔邪褋芯胁械</string>
+    <string name="activity_summaries">袗泻褌懈胁薪芯褋褌懈</string>
+    <string name="activity_type_activity">袗泻褌懈胁薪芯褋褌</string>
+    <string name="activity_type_biking">袣芯谢芯械蟹写械薪械</string>
+    <string name="activity_type_deep_sleep">袛褗谢斜芯泻 褋褗薪</string>
+    <string name="activity_type_light_sleep">袥械泻 褋褗薪</string>
+    <string name="activity_type_exercise">校锌褉邪卸薪械薪懈械</string>
+    <string name="activity_type_not_measured">袧械 械 懈蟹屑械褉械薪芯</string>
+    <string name="activity_type_not_worn">袧械 械 薪芯褋械薪芯</string>
+    <string name="activity_type_running">袘褟谐邪薪械</string>
+    <string name="activity_type_swimming">袩谢褍胁邪薪械</string>
+    <string name="activity_type_unknown">袧械懈蟹胁械褋褌薪邪 邪泻褌懈胁薪芯褋褌</string>
+    <string name="activity_type_walking">啸芯写械薪械</string>
+    <string name="appwidget_not_connected">袧械 械 褋胁褗褉蟹邪薪, 邪谢邪褉屑邪褌邪 薪械 械 薪邪褋褌褉芯械薪邪.</string>
+    <string name="appwidget_setting_alarm">袗谢邪褉屑邪 蟹邪 %1$02d:%2$02d</string>
+    <string name="automatic">袗胁褌芯屑邪褌懈褔薪芯</string>
+    <string name="authenticating">校写芯褋褌芯胁械褉褟胁邪薪械</string>
+    <string name="authentication_required">袠蟹懈褋泻胁邪 褍写芯褋褌芯胁械褉褟胁邪薪械</string>
+    <string name="charts_legend_heartrate">小褗褉写械褔薪邪 褔械褋褌芯褌邪</string>
+    <string name="choose_auto_export_location">袠蟹斜械褉懈 谢芯泻邪褑懈褟 蟹邪 械泻褋锌芯褉褌</string>
+    <string name="dateformat_time">效邪褋</string>
+    <string name="dbmanagementactivity_overwrite">袟邪屑械薪懈</string>
+    <string name="device_fw">Firmware 胁械褉褋懈褟: %1$s</string>
+    <string name="device_hw">Hardware 胁械褉褋懈褟: %1$s</string>
+    <string name="device_not_connected">袧械 械 褋胁褗褉蟹邪薪.</string>
+    <string name="devicetype_unknown">袧械懈蟹胁械褋褌薪芯 褍褋褌褉芯泄褋褌胁芯</string>
+    <string name="discovery_attempting_to_pair">袨锌懈褌 蟹邪 褋胁褗褉蟹胁邪薪械 褋 %1$s</string>
+    <string name="discovery_dont_pair">袧械 褋械 褋胁褗褉蟹胁邪泄</string>
+    <string name="discovery_enable_bluetooth">袙泻谢褞褔懈 bluetooth 蟹邪 薪邪屑懈褉邪薪械 薪邪 褍褋褌褉芯泄褋褌胁邪.</string>
+    <string name="discovery_pair_title">小胁褗褉蟹胁邪薪械 褋 %1$s?</string>
+    <string name="discovery_trying_to_connect_to">袨锌懈褌 蟹邪 褋胁褗褉蟹胁邪薪械 褋: %1$s</string>
+    <string name="discovery_yes_pair">小胁褗褉卸懈</string>
+    <string name="filter_mode_none">袧械 褎懈谢褌褉懈褉邪泄</string>
+    <string name="find_device_you_found_it">袧邪屑械褉懈 屑械!</string>
+    <string name="kind_font">楔褉懈褎褌</string>
+    <string name="kind_invalid">袧械胁邪谢懈写薪懈 写邪薪薪懈</string>
+    <string name="live_activity_heart_rate">小褗褉写械褔械薪 褉懈褌褗屑</string>
+    <string name="maximum_duration">袩褉芯写褗谢卸懈褌械谢薪芯褋褌</string>
+    <string name="menuitem_alarm">袗谢邪褉屑邪</string>
+    <string name="menuitem_activity">袗泻褌懈胁薪芯褋褌</string>
+    <string name="menuitem_compass">袣芯屑锌邪褋</string>
+    <string name="menuitem_music">袦褍蟹懈泻邪</string>
+    <string name="menuitem_settings">袧邪褋褌褉芯泄泻懈</string>
+    <string name="menuitem_notifications">袠蟹胁械褋褌懈褟</string>
+    <string name="menuitem_weather">袩褉芯谐薪芯蟹邪 蟹邪 胁褉械屑械</string>
+    <string name="menuitem_timer">孝邪泄屑械褉</string>
+    <string name="mi2_dnd_off">袠蟹泻谢.</string>
+    <string name="mi2_dnd_scheduled">袪邪蟹锌懈褋邪薪懈械</string>
+    <string name="mi2_prefs_do_not_disturb_end">袣褉邪泄</string>
+    <string name="mi2_prefs_do_not_disturb_start">袧邪褔邪谢芯</string>
+    <string name="mi2_prefs_do_not_disturb">袧械 斜械蟹锌芯泻芯泄</string>
+    <string name="mi2_prefs_inactivity_warnings">袩褉械写褍锌褉械卸写械薪懈械 蟹邪 斜械蟹写械泄褋褌胁懈械</string>
+    <string name="mi2_prefs_inactivity_warnings_summary">校褋褌褉芯泄褋褌胁芯褌芯 褖械 胁懈斜褉懈褉邪 邪泻芯 斜械蟹写械泄褋褌胁邪褌械 蟹邪 懈蟹胁械褋褌薪芯 胁褉械屑械</string>
+    <string name="mi2_prefs_inactivity_warnings_threshold">袩械褉懈芯写 薪邪 斜械蟹写械泄褋褌胁懈械 (胁 屑懈薪褍褌懈)</string>
+    <string name="you_slept">袙懈械 褋锌邪褏褌械 芯褌 %1$s 写芯 %2$s</string>
+    <string name="widget_5_minutes">5 屑懈薪褍褌懈</string>
+    <string name="widget_20_minutes">20 屑懈薪褍褌懈</string>
+    <string name="widget_1_hour">1 褔邪褋</string>
+    <string name="widget_10_minutes">10 屑懈薪褍褌懈</string>
+    <string name="weekstepschart_steps_a_month">袣褉邪褔泻懈 蟹邪 屑械褋械褑</string>
+    <string name="weeksleepchart_sleep_a_month">小褗薪 蟹邪 屑械褋械褑</string>
+    <string name="warning">袙薪懈屑邪薪懈械!</string>
+    <string name="waiting_for_reconnect">袠蟹褔邪泻胁邪 褋胁褗褉蟹胁邪薪械</string>
+    <string name="user_feedback_all_alarms_disabled">袙褋懈褔泻懈 邪谢邪褉屑懈 褋邪 懈蟹泻谢褞褔械薪懈</string>
+    <string name="unit_metric">袦械褌褉懈褔薪邪</string>
+    <string name="unit_imperial">袠薪褔芯胁邪</string>
+    <string name="toast_notification_filter_words_empty_hint">袙褗胁械写械褌械 锌芯薪械 械写薪邪 写褍屑邪</string>
+    <string name="toast_notification_filter_saved_successfully">肖懈谢褌褗褉邪 蟹邪 懈蟹胁械褋褌懈褟 械 蟹邪锌邪蟹械薪</string>
+    <string name="title_activity_vibration">袙懈斜褉邪褑懈褟</string>
+    <string name="title_activity_notification_filter">肖懈谢褌褗褉 蟹邪 懈蟹胁械褋褌懈褟</string>
+    <string name="title_activity_db_management">校锌褉邪胁谢械薪懈械 薪邪 袘袛</string>
+    <string name="select_all">袠蟹斜械褉懈 胁褋懈褔泻懈</string>
+    <string name="share">小锌芯写械谢懈</string>
+    <string name="save_configuration">袟邪锌邪蟹懈 薪邪褋褌褉芯泄泻懈褌械</string>
+    <string name="prefs_hr_alarm_high">袚芯褉薪邪 谐褉邪薪懈褑邪</string>
+    <string name="prefs_hr_alarm_low">袛芯谢薪邪 谐褉邪薪懈褑邪</string>
+    <string name="preferences_rtl_settings">袨褌 谢褟胁芯 薪邪 写褟褋薪芯</string>
+    <string name="pref_screen_notification_profile_alarm_clock">袗谢邪褉屑懈</string>
+    <string name="p_unit_metric">屑械褌褉懈褔薪懈</string>
+    <string name="p_unit_imperial">懈薪褔芯胁懈</string>
+    <string name="p_steps">泻褉邪褔泻懈</string>
+    <string name="p_scheduled">褉邪蟹锌懈褋邪薪懈械</string>
+    <string name="p_pebble_privacy_mode_off">懈蟹泻谢.</string>
+    <string name="p_on">胁泻谢.</string>
+    <string name="p_off">懈蟹泻谢.</string>
+    <string name="p_menuitem_weather">锌褉芯谐薪芯蟹邪</string>
+    <string name="p_menuitem_settings">薪邪褋褌褉芯泄泻懈</string>
+    <string name="p_menuitem_notifications">懈蟹胁械褋褌懈褟</string>
+    <string name="off">袠蟹泻谢.</string>
+    <string name="on">袙泻谢.</string>
+    <string name="no_data">袧褟屑邪 写邪薪薪懈</string>
+    <string name="sonyswr12_settings_title">袧邪褋褌褉芯泄泻懈 Sony SWR12</string>
+    <string name="sonyswr12_settings_low_vibration">袗泻褌懈胁懈褉邪薪邪 薪懈褋泻邪 胁懈斜褉邪褑懈褟</string>
+    <string name="sonyswr12_settings_stamina">袪械卸懈屑褗褌 蟹邪 锌械褋褌械薪械 薪邪 械薪械褉谐懈褟 械 胁泻谢褞褔械薪</string>
+    <string name="sonyswr12_settings_alarm_interval">袠薪褌械谢懈谐械薪褌械薪 邪谢邪褉屑械薪 懈薪褌械褉胁邪谢 胁 屑懈薪褍褌懈</string>
+    <string name="sonyswr12_settings_low_vibration_summary">袗泻褌懈胁懈褉邪泄褌械 薪懈褋泻邪褌邪 懈薪褌械薪蟹懈胁薪芯褋褌 薪邪 胁懈斜褉邪褑懈懈褌械 薪邪 屑邪薪褕械褌邪</string>
+    <string name="sonyswr12_settings_stamina_summary">袪械卸懈屑褗褌 蟹邪 锌械褋褌械薪械 薪邪 械薪械褉谐懈褟 懈蟹泻谢褞褔胁邪 锌械褉懈芯写懈褔薪芯褌芯 邪胁褌芯屑邪褌懈褔薪芯 懈蟹屑械褉胁邪薪械 薪邪 褋褗褉写械褔薪邪褌邪 褔械褋褌芯褌邪, 泻邪褌芯 锌芯 褌芯蟹懈 薪邪褔懈薪 褍胁械谢懈褔邪胁邪 褉邪斜芯褌薪芯褌芯 胁褉械屑械</string>
+    <string name="sonyswr12_settings_alarm_interval_summary">袠薪褌械谢懈谐械薪褌薪懈褟褌 邪谢邪褉屑械薪 懈薪褌械褉胁邪谢 械 懈薪褌械褉胁邪谢 锌褉械写懈 懈薪褋褌邪谢懈褉邪薪邪褌邪 邪谢邪褉屑邪. 袙 褌芯蟹懈 懈薪褌械褉胁邪谢 褍褋褌褉芯泄褋褌胁芯褌芯 褋械 芯锌懈褌胁邪 写邪 芯褌泻褉懈械 薪邪泄-谢械泻邪褌邪 褎邪蟹邪 薪邪 褋褗薪 蟹邪 褋褗斜褍卸写邪薪械 薪邪 锌芯褌褉械斜懈褌械谢褟</string>
+</resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml
new file mode 100644
index 0000000..57cb2f4
--- /dev/null
+++ b/app/src/main/res/values-ca/strings.xml
@@ -0,0 +1,1527 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <string name="application_name_generic">Gadgetbridge</string>
+    <string name="title_activity_controlcenter_generic">Gadgetbridge</string>
+    <string name="action_settings">Configuraci贸</string>
+    <string name="action_debug">Depuraci贸</string>
+    <string name="action_quit">Surt</string>
+    <string name="action_donate">Donaci贸</string>
+    <string name="controlcenter_fetch_activity_data">Sincronitza</string>
+    <string name="controlcenter_find_device">Cerca un aparell perdut</string>
+    <string name="controlcenter_take_screenshot">Fes una captura de pantalla</string>
+    <string name="controlcenter_connect">Connecta鈥�</string>
+    <string name="controlcenter_disconnect">Desconnecta</string>
+    <string name="controlcenter_delete_device">Esborra l\'aparell</string>
+    <string name="controlcenter_delete_device_name">Esborra %1$s</string>
+    <string name="controlcenter_delete_device_dialogmessage">Aquesta acci贸 esborrar脿 l\'aparell i tota la seva informaci贸 associada!</string>
+    <string name="controlcenter_navigation_drawer_open">Obre el calaix de navegaci贸</string>
+    <string name="controlcenter_navigation_drawer_close">Tanca el calaix de navegaci贸</string>
+    <string name="controlcenter_snackbar_need_longpress">Mantingueu premut l\'element per desconnectar</string>
+    <string name="controlcenter_snackbar_disconnecting">S\'est脿 desconnectant</string>
+    <string name="controlcenter_snackbar_connecting">S\'est脿 connectant鈥�</string>
+    <string name="controlcenter_snackbar_requested_screenshot">S\'est脿 fent una captura de pantalla de l\'aparell</string>
+    <string name="title_activity_debug">Depuraci贸</string>
+    <string name="title_activity_appmanager">Gestor d\'aplicacions</string>
+    <string name="appmanager_cached_watchapps_watchfaces">Aplicacions a la mem貌ria cau</string>
+    <string name="appmanager_installed_watchapps">Aplicacions instal路lades</string>
+    <string name="appmanager_installed_watchfaces">Car脿tules instal路lades</string>
+    <string name="appmananger_app_delete">Esborra</string>
+    <string name="appmananger_app_delete_cache">Esborra i suprimeix de la mem貌ria cau</string>
+    <string name="appmananger_app_reinstall">Reinstal路la</string>
+    <string name="appmanager_app_openinstore">Cerca a la botiga d\'aplicacions de Pebble</string>
+    <string name="appmanager_health_activate">Activa</string>
+    <string name="appmanager_health_deactivate">Desactiva</string>
+    <string name="appmanager_hrm_activate">Activa el seguiment de ritme card铆ac</string>
+    <string name="appmanager_hrm_deactivate">Desactiva el seguiment de ritme card铆ac</string>
+    <string name="appmanager_weather_activate">Activa l\'aplicaci贸 del temps del sistema</string>
+    <string name="appmanager_weather_deactivate">Desactiva l\'aplicaci贸 del temps del sistema</string>
+    <string name="appmanager_weather_install_provider">Instal路la l\'aplicaci贸 del temps del sistema</string>
+    <string name="app_configure">Configura</string>
+    <string name="app_move_to_top">Mou a la part superior</string>
+    <string name="title_activity_calblacklist">Calendaris bloquejats</string>
+    <string name="title_activity_fw_app_insaller">Instal路lador de microprogramari i aplicacions</string>
+    <string name="fw_upgrade_notice">Sou a punt d\'instal路lar el %s.</string>
+    <string name="fw_upgrade_notice_amazfitbip">Esteu a punt d\'instal路lar el microprogramari %s en el vostre Amazfit Bip.
+\n
+\nSi us plau, assegureu-vos d\'instal路lar el fitxer .fw, el fitxer .res i, finalment, el fitxer .gps. El vostre rellotge es reiniciar脿 despr茅s d\'instal路lar l\'arxiu .fw.
+\n
+\nNota: no heu d\'instal路lar els fitxers .res i .gps si aquests fitxers s贸n exactament els mateixos que els pr猫viament instal路lats.
+\n
+\nPROCEDIU SOTA LA VOSTRA PR脪PIA RESPONSABILITAT!</string>
+    <string name="fw_upgrade_notice_amazfitcor">Sou a punt d\'instal路lar el microprogramari %s en la vostra Amazfit Cor.
+\n
+\nSi us plau, assegureu-vos d\'instal路lar el fitxer .fw, i despr茅s el fitxer .res. La vostra polsera es reiniciar脿 despr茅s d\'instal路lar el fitxer .fw.
+\n
+\nNota: no heu d\'instal路lar el fitxer .res si 茅s exactament el mateix que el pr猫viament instal路lat.
+\n
+\nPROCEDIU SOTA LA VOSTRA PR脪PIA RESPONSABILITAT!</string>
+    <string name="fw_multi_upgrade_notice">Sou a punt d\'instal路lar els microprogramaris %1$s i %2$s en lloc dels que hi ha actualment a la vostra Mi Band.</string>
+    <string name="miband_firmware_known">Aquest microprogramari ha estat provat i se sap que 茅s compatible amb el Gadgetbridge.</string>
+    <string name="miband_firmware_unknown_warning">Aquest microprogramari no ha estat provat i pot ser que no sigui compatible amb el Gadgetbridge. 
+\n 
+\nNO es recomana instal路lar-lo!</string>
+    <string name="miband_firmware_suggest_whitelist">Si tot i aix铆 voleu continuar i les coses continuen funcionant correctament, si us plau informeu els desenvolupadors del Gadgetbridge que la versi贸 %s del microprogramari funciona b茅.</string>
+    <string name="title_activity_settings">Configuraci贸</string>
+    <string name="pref_header_general">Configuraci贸 general</string>
+    <string name="pref_title_general_autoconnectonbluetooth">Connecta\'t a l\'aparell Gadgetbridge quan el Bluetooth estigui activat</string>
+    <string name="pref_title_general_autostartonboot">Inicia autom脿ticament</string>
+    <string name="pref_title_general_autoreconnect">Reconnecta autom脿ticament</string>
+    <string name="pref_title_audio_player">Reproductor d\'脿udio preferit</string>
+    <string name="pref_default">Per defecte</string>
+    <string name="pref_title_charts_swipe">Permet lliscar cap a l\'esquerra i la dreta en els gr脿fics d\'activitat</string>
+    <string name="pref_header_datetime">Data i Hora</string>
+    <string name="pref_title_datetime_syctimeonconnect">Sincronitza l\'hora</string>
+    <string name="pref_summary_datetime_syctimeonconnect">Sincronitza l\'hora en l\'aparell Gadgetbridge quan es connecti o quan es canvi茂 l\'hora o zona hor脿ria en l\'aparell Android</string>
+    <string name="pref_title_theme">Aparen莽a</string>
+    <string name="pref_theme_light">Clar</string>
+    <string name="pref_theme_dark">Fosc</string>
+    <string name="pref_title_language">Llengua</string>
+    <string name="pref_title_minimize_priority">Amaga la notificaci贸 del Gadgetbridge</string>
+    <string name="pref_summary_minimize_priority_off">La icona a la barra d\'estat i la notificaci贸 a la pantalla de bloqueig es mostren</string>
+    <string name="pref_summary_minimize_priority_on">La icona a la barra d\'estat i la notificaci贸 a la pantalla de bloqueig no es mostren</string>
+    <string name="pref_header_notifications">Notificacions</string>
+    <string name="pref_title_notifications_repetitions">Repeticions</string>
+    <string name="pref_title_notifications_call">Trucades telef貌niques</string>
+    <string name="pref_title_notifications_sms">SMS</string>
+    <string name="pref_title_notifications_pebblemsg">Missatges de Pebble</string>
+    <string name="pref_summary_notifications_pebblemsg">Suport per a aplicacions que envien notificacions a Pebble a trav茅s de PebbleKit.</string>
+    <string name="pref_title_notifications_generic">Suport per a notificacions gen猫riques</string>
+    <string name="pref_title_whenscreenon">鈥� tamb茅 amb la pantalla encesa</string>
+    <string name="pref_title_notification_filter">No molesteu</string>
+    <string name="pref_summary_notification_filter">Blocar totes les notificacions quan el \"No molesteu\" estigui activat al tel猫fon</string>
+    <string name="pref_title_transliteration">Transliteraci贸</string>
+    <string name="pref_summary_transliteration">Activeu aquesta opci贸 si el vostre aparell no pot mostrar els car脿cters de la vostra llengua</string>
+    <string name="always">Sempre</string>
+    <string name="when_screen_off">Quan la pantalla est脿 apagada</string>
+    <string name="never">Mai</string>
+    <string name="pref_header_privacy">Privacitat</string>
+    <string name="pref_title_call_privacy_mode">Mode de privacitat de trucada</string>
+    <string name="pref_call_privacy_mode_off">Mostra el nom i el n煤mero</string>
+    <string name="pref_call_privacy_mode_name">Amaga el nom per貌 mostra el n煤mero</string>
+    <string name="pref_call_privacy_mode_number">Amaga el n煤mero per貌 mostra el nom</string>
+    <string name="pref_call_privacy_mode_complete">Amaga el nom i el n煤mero</string>
+    <string name="pref_blacklist_calendars">Bloqueig de calendaris</string>
+    <string name="pref_header_cannned_messages">Missatges predefinits</string>
+    <string name="pref_title_canned_replies">Respostes</string>
+    <string name="pref_title_canned_reply_suffix">Sufix autom脿tic</string>
+    <string name="pref_title_canned_messages_dismisscall">Rebuig de trucada</string>
+    <string name="pref_title_canned_messages_set">Actualitza a l\'aparell</string>
+    <string name="pref_header_development">Opcions per a desenvolupadors</string>
+    <string name="pref_title_development_miaddr">Adre莽a de Mi Band</string>
+    <string name="pref_title_pebble_settings">Configuraci贸 del Pebble</string>
+    <string name="pref_header_activitytrackers">Sistemes de seguiment d\'activitat</string>
+    <string name="pref_title_pebble_activitytracker">Sistema preferit per al seguiment d\'activitat</string>
+    <string name="pref_title_pebble_sync_health">Sincronitza amb Pebble Health</string>
+    <string name="pref_title_pebble_sync_misfit">Sincronitza amb Misfit</string>
+    <string name="pref_title_pebble_sync_morpheuz">Sincronitza amb Morpheuz</string>
+    <string name="pref_title_enable_outgoing_call">Suport per a trucades sortints</string>
+    <string name="pref_summary_enable_outgoing_call">Desactivar aix貌 evitar脿 que Pebble 2/LE vibri en trucades sortints</string>
+    <string name="pref_title_enable_pebblekit">Permet l\'acc茅s a aplicacions Android de tercers</string>
+    <string name="pref_summary_enable_pebblekit">Permet aplicacions d\'Android experimentals que fan servir PebbleKit</string>
+    <string name="pref_header_pebble_timeline">Cronologia del Pebble</string>
+    <string name="pref_title_sunrise_sunset">Sortida i posta de sol</string>
+    <string name="pref_summary_sunrise_sunset">Envia les hores de sortida i posta de sol basades en la ubicaci贸 a la cronologia del Pebble</string>
+    <string name="pref_title_enable_calendar_sync">Sincronitza el calendari</string>
+    <string name="pref_summary_enable_calendar_sync">Envia els esdeveniments del calendari a la cronologia</string>
+    <string name="pref_title_autoremove_notifications">Suprimeix autom脿ticament les notificacions rebutjades</string>
+    <string name="pref_summary_autoremove_notifications">Les notificacions se suprimeixen autom脿ticament de l\'aparell quan es rebutgen al tel猫fon</string>
+    <string name="pref_title_pebble_privacy_mode">Mode de privacitat</string>
+    <string name="pref_pebble_privacy_mode_off">Notificacions normals</string>
+    <string name="pref_pebble_privacy_mode_content">Despla莽a la notificaci贸 fora de la pantalla</string>
+    <string name="pref_pebble_privacy_mode_complete">Mostra nom茅s la icona de la notificaci贸</string>
+    <string name="pref_header_location">Ubicaci贸</string>
+    <string name="pref_title_location_aquire">Obt茅 la localitzaci贸</string>
+    <string name="pref_title_location_latitude">Latitud</string>
+    <string name="pref_title_location_longitude">Longitud</string>
+    <string name="pref_title_location_keep_uptodate">Mantingues la ubicaci贸 actualitzada</string>
+    <string name="pref_summary_location_keep_uptodate">Intenta aconseguir la ubicaci贸 constantment, i si aix貌 no 茅s possible fes servir la ubicaci贸 desada pr猫viament</string>
+    <string name="toast_enable_networklocationprovider">Si us plau, activeu la ubicaci贸 per xarxa</string>
+    <string name="toast_aqurired_networklocation">S\'ha trobat la ubicaci贸</string>
+    <string name="pref_title_pebble_forceprotocol">For莽a el protocol de notificaci贸</string>
+    <string name="pref_summary_pebble_forceprotocol">Aquesta opci贸 for莽a l\'煤s del darrer protocol de notificaci贸 en funci贸 de la versi贸 del microprogramari. VIGILEU AMB EL QUE FEU!</string>
+    <string name="pref_title_pebble_forceuntested">Activa caracter铆stiques no provades</string>
+    <string name="pref_summary_pebble_forceuntested">Activa les caracter铆stiques que no han estat provades. VIGILEU AMB EL QUE FEU!</string>
+    <string name="pref_title_pebble_forcele">Prefereix sempre BLE</string>
+    <string name="pref_summary_pebble_forcele">Utilitza el suport experimental de Pebble LE a tots els Pebble, enlloc del Bluetooth cl脿ssic. Primer cal aparellar primer un Pebble sense LE i despr茅s un Pebble LE</string>
+    <string name="pref_title_pebble_mtu_limit">L铆mit de l\'MTU del GATT del Pebble 2/LE</string>
+    <string name="pref_summary_pebble_mtu_limit">Si el teu Pebble 2/Pebble LE no funciona correctament, prova aquesta opci贸 per limitar l\'MTU (rang v脿lid: 20鈥�512)</string>
+    <string name="pref_title_pebble_enable_applogs">Activa els registres de les aplicacions del rellotge</string>
+    <string name="pref_summary_pebble_enable_applogs">Produir脿 registres de les apps del rellotge que el Gadgetbridge guardar脿 (necessita reconnexi贸)</string>
+    <string name="pref_title_pebble_always_ack_pebblekit">Envia l\'ACK al PebbleKit abans d\'hora</string>
+    <string name="pref_summary_pebble_always_ack_pebblekit">Permetr脿 que missatges enviats a aplicacions de tercers siguin reconeguts sempre i immediatament</string>
+    <string name="pref_title_pebble_enable_bgjs">Activa JS en segon pla</string>
+    <string name="pref_summary_pebble_enable_bgjs">Permet a les car脿tules mostrar el temps, informaci贸 de bateria, etc.</string>
+    <string name="pref_title_pebble_reconnect_attempts">Intents de reconnexi贸</string>
+    <string name="pref_title_unit_system">Unitats</string>
+    <string name="pref_title_timeformat">Format d\'hora</string>
+    <string name="pref_title_screentime">Durada de pantalla encesa</string>
+    <string name="prefs_title_all_day_heart_rate">Mesura el ritme card铆ac tot el dia</string>
+    <string name="preferences_hplus_settings">Configuraci贸 de HPlus/Makibes</string>
+    <string name="not_connected">No connectat</string>
+    <string name="connecting">S\'est脿 connectant</string>
+    <string name="connected">Connectat</string>
+    <string name="unknown_state">Estat desconegut</string>
+    <string name="_unknown_">(desconegut)</string>
+    <string name="test">Prova</string>
+    <string name="test_notification">Notificaci贸 de prova</string>
+    <string name="this_is_a_test_notification_from_gadgetbridge">Aix貌 茅s una notificaci贸 de prova des del Gadgetbridge</string>
+    <string name="bluetooth_is_not_supported_">El Bluetooth no 茅s compatible o no est脿 implementat.</string>
+    <string name="bluetooth_is_disabled_">El Bluetooth est脿 desactivat.</string>
+    <string name="tap_connected_device_for_app_mananger">Toca l\'aparell connectat per obrir el gestor d\'aplicacions</string>
+    <string name="tap_connected_device_for_activity">Toca l\'aparell connectat per gestionar-ne l\'activitat</string>
+    <string name="tap_connected_device_for_vibration">Toca l\'aparell per fer-lo vibrar</string>
+    <string name="tap_a_device_to_connect">Toca un aparell per connectar-hi</string>
+    <string name="cannot_connect_bt_address_invalid_">No es pot connectar. Potser la direcci贸 Bluetooth 茅s incorrecta\?</string>
+    <string name="gadgetbridge_running_generic">El Gadgetbridge est脿 funcionant</string>
+    <string name="installing_binary_d_d">S\'est脿 instal路lant el binari %1$d/%2$d</string>
+    <string name="installation_failed_">La instal路laci贸 ha fallat</string>
+    <string name="installation_successful">Instal路lat</string>
+    <string name="pref_title_weather">El temps</string>
+    <string name="pref_title_weather_location">Ubicaci贸 del temps (per al prove茂dor de temps de LineageOS)</string>
+    <string name="pref_header_auto_export">Exportaci贸 autom脿tica</string>
+    <string name="pref_title_auto_export_enabled">Exportaci贸 autom脿tica activada</string>
+    <string name="pref_title_auto_export_location">Ubicaci贸 de l\'exportaci贸</string>
+    <string name="pref_title_auto_export_interval">Interval de l\'exportaci贸</string>
+    <string name="pref_summary_auto_export_interval">Exporta cada %d hores</string>
+    <string name="app_install_info">Sou a punt d\'instal路lar la seg眉ent aplicaci贸:
+\n
+\n%1$s
+\nVersi贸 %2$s per %3$s
+\n</string>
+    <string name="n_a">NO DISPONIBLE</string>
+    <string name="initialized">inicialitzat</string>
+    <string name="appversion_by_creator">%1$s per %2$s</string>
+    <string name="title_activity_discovery">Detecci贸 d\'aparells</string>
+    <string name="discovery_stop_scanning">Atura l\'escaneig</string>
+    <string name="discovery_start_scanning">Inicia la detecci贸</string>
+    <string name="action_discover">Connecta un aparell nou</string>
+    <string name="device_with_rssi">%1$s (%2$s)</string>
+    <string name="title_activity_android_pairing">Aparella l\'aparell</string>
+    <string name="android_pairing_hint">Utilitzeu el di脿leg d\'aparellament Bluetooth de l\'Android per aparellar l\'aparell.</string>
+    <string name="title_activity_mi_band_pairing">Aparelleu la vostra Mi Band</string>
+    <string name="pairing">S\'est脿 aparellant amb %s鈥�</string>
+    <string name="pairing_creating_bond_with">S\'est脿 creant un vincle amb %1$s (%2$s)</string>
+    <string name="pairing_unable_to_pair_with">No s\'ha pogut aparellar amb %1$s (%2$s)</string>
+    <string name="pairing_in_progress">Aparellament en curs: %1$s (%2$s)</string>
+    <string name="pairing_already_bonded">Ja s\'ha aparellat amb %1$s (%2$s). Connectant鈥�</string>
+    <string name="message_cannot_pair_no_mac">No s\'ha proporcionat cap adre莽a MAC. No es pot emparellar.</string>
+    <string name="preferences_category_device_specific_settings">Configuraci贸 d\'aparells espec铆fics</string>
+    <string name="preferences_miband_settings">Configuraci贸 de Mi Band / Amazfit</string>
+    <string name="male">Home</string>
+    <string name="female">Dona</string>
+    <string name="other">Altre</string>
+    <string name="left">Esquerra</string>
+    <string name="right">Dreta</string>
+    <string name="appinstaller_install">Instal路la</string>
+    <string name="discovery_note">Nota:</string>
+    <string name="candidate_item_device_image">Imatge de l\'aparell</string>
+    <string name="miband_prefs_alias">Nom/脌lies</string>
+    <string name="pref_header_vibration_count">Recompte de vibracions</string>
+    <string name="title_activity_sleepmonitor">Monitor de son</string>
+    <string name="pref_write_logfiles">Escriu fitxers de registre</string>
+    <string name="initializing">S\'est脿 inicialitzant</string>
+    <string name="busy_task_fetch_activity_data">S\'est脿n recollint les dades d\'activitat</string>
+    <string name="sleep_activity_date_range">De %1$s a %2$s</string>
+    <string name="prefs_wearside">A quina m脿 porteu l\'aparell\?</string>
+    <string name="pref_screen_vibration_profile">Perfil de vibraci贸</string>
+    <string name="vibration_profile_staccato">Molt curta</string>
+    <string name="vibration_profile_short">Curta</string>
+    <string name="vibration_profile_medium">Mitjana</string>
+    <string name="vibration_profile_long">Llarga</string>
+    <string name="vibration_profile_waterdrop">Gota d\'aigua</string>
+    <string name="vibration_profile_ring">Timbre</string>
+    <string name="vibration_profile_alarm_clock">Despertador</string>
+    <string name="miband_prefs_vibration">Vibraci贸</string>
+    <string name="vibration_try">Prova-ho</string>
+    <string name="pref_screen_notification_profile_sms">Notificaci贸 de SMS</string>
+    <string name="pref_header_vibration_settings">Configuraci贸 de vibraci贸</string>
+    <string name="pref_screen_notification_profile_generic">Notificaci贸 gen猫rica</string>
+    <string name="pref_screen_notification_profile_email">Notificaci贸 de correu electr貌nic</string>
+    <string name="pref_screen_notification_profile_incoming_call">Notificaci贸 de trucada entrant</string>
+    <string name="pref_screen_notification_profile_generic_chat">Xat</string>
+    <string name="pref_screen_notification_profile_generic_navigation">Navegaci贸</string>
+    <string name="pref_screen_notification_profile_generic_social">Xarxa social</string>
+    <string name="prefs_title_heartrate_measurement_interval">Mesura el ritme card铆ac tot el dia</string>
+    <string name="interval_one_minute">una vegada per minut</string>
+    <string name="interval_five_minutes">cada 5 minuts</string>
+    <string name="interval_ten_minutes">cada 10 minuts</string>
+    <string name="interval_thirty_minutes">cada 30 minuts</string>
+    <string name="interval_one_hour">cada hora</string>
+    <string name="stats_title">Zones de velocitat</string>
+    <string name="stats_x_axis_label">Minuts totals</string>
+    <string name="stats_y_axis_label">Passes per minut</string>
+    <string name="control_center_find_lost_device">Troba l\'aparell perdut</string>
+    <string name="control_center_cancel_to_stop_vibration">Cancel路leu per aturar la vibraci贸.</string>
+    <string name="title_activity_charts">Activitat i son</string>
+    <string name="title_activity_set_alarm">Configura les alarmes</string>
+    <string name="controlcenter_start_configure_alarms">Configura les alarmes</string>
+    <string name="title_activity_alarm_details">Detalls d\'alarma</string>
+    <string name="alarm_sun_short">Dg.</string>
+    <string name="alarm_mon_short">Dl.</string>
+    <string name="alarm_tue_short">Dt.</string>
+    <string name="alarm_wed_short">Dc.</string>
+    <string name="alarm_thu_short">Dj.</string>
+    <string name="alarm_fri_short">Dv.</string>
+    <string name="alarm_sat_short">Ds.</string>
+    <string name="alarm_smart_wakeup">Reactivaci贸 intel路ligent</string>
+    <string name="user_feedback_miband_set_alarms_failed">Hi ha hagut un error en definir les alarmes. Si us plau, intenteu-ho de nou.</string>
+    <string name="user_feedback_miband_set_alarms_ok">S\'han enviat les alarmes a l\'aparell.</string>
+    <string name="chart_no_data_synchronize">No hi ha dades. Sincronitzo l\'aparell\?</string>
+    <string name="user_feedback_miband_activity_data_transfer">A punt de transferir %1$s de dades des de %2$s</string>
+    <string name="miband_prefs_fitness_goal">Objectiu de passes di脿ries</string>
+    <string name="dbaccess_error_executing">Error en executar \'%1$s\'</string>
+    <string name="controlcenter_start_activitymonitor">La vostra activitat (ALPHA)</string>
+    <string name="cannot_connect">No es pot connectar: %1$s</string>
+    <string name="installer_activity_unable_to_find_handler">No s\'ha trobat cap controlador per instal路lar aquest fitxer.</string>
+    <string name="pbw_install_handler_unable_to_install">No s\'ha pogut instal路lar el fitxer: %1$s</string>
+    <string name="pbw_install_handler_hw_revision_mismatch">No s\'ha pogut instal路lar el microprogramari: no coincideix amb la revisi贸 de maquinari del vostre Pebble.</string>
+    <string name="installer_activity_wait_while_determining_status">Si us plau, espereu mentre s\'esbrina l\'estat de la instal路laci贸鈥�</string>
+    <string name="notif_battery_low_title">L\'aparell t茅 poca bateria!</string>
+    <string name="notif_battery_low_percent">A %1$s li roman un %2$s%% de bateria</string>
+    <string name="notif_battery_low_bigtext_last_charge_time">Darrera c脿rrega: %s
+\n</string>
+    <string name="notif_battery_low_bigtext_number_of_charges">Nombre de c脿rregues: %s</string>
+    <string name="notif_export_failed_title">L\'exportaci贸 de la base de dades ha fallat! Si us plau, comproveu la configuraci贸.</string>
+    <string name="sleepchart_your_sleep">Son</string>
+    <string name="weeksleepchart_sleep_a_week">Son per setmana</string>
+    <string name="weeksleepchart_today_sleep_description">Son avui, objectiu: %1$s</string>
+    <string name="weekstepschart_steps_a_week">Passes per setmana</string>
+    <string name="activity_sleepchart_activity_and_sleep">Activitat</string>
+    <string name="updating_firmware">El microprogramari s\'est脿 instal路lant鈥�</string>
+    <string name="fwapp_install_device_not_ready">No es pot instal路lar el fitxer. L\'aparell no est脿 preparat.</string>
+    <string name="installhandler_firmware_name">%1$s: %2$s %3$s</string>
+    <string name="miband_fwinstaller_compatible_version">Versi贸 compatible</string>
+    <string name="miband_fwinstaller_untested_version">Versi贸 no provada!</string>
+    <string name="fwappinstaller_connection_state">Connexi贸 a l\'aparell: %1$s</string>
+    <string name="pbw_installhandler_pebble_firmware">Microprogramari Pebble %1$s</string>
+    <string name="pbwinstallhandler_correct_hw_revision">Revisi贸 del maquinari correcta</string>
+    <string name="pbwinstallhandler_incorrect_hw_revision">La revisi贸 del maquinari no coincideix!</string>
+    <string name="pbwinstallhandler_app_item">%1$s (%2$s)</string>
+    <string name="updatefirmwareoperation_updateproblem_do_not_reboot">Hi ha hagut un problema amb la transfer猫ncia del microprogramari. NO REINICIEU la vostra Mi Band!</string>
+    <string name="updatefirmwareoperation_metadata_updateproblem">Hi ha hagut un problema amb la transfer猫ncia de metadades del microprogramari</string>
+    <string name="updatefirmwareoperation_update_complete">S\'ha completat la instal路laci贸 del microprogramari</string>
+    <string name="updatefirmwareoperation_update_complete_rebooting">S\'ha completat la instal路laci贸 del microprogramari. S\'est脿 reiniciant l\'aparell鈥�</string>
+    <string name="updatefirmwareoperation_write_failed">La instal路laci贸 del microprogramari ha fallat</string>
+    <string name="chart_steps">Passes</string>
+    <string name="calories">Calories</string>
+    <string name="distance">Dist脿ncia</string>
+    <string name="clock">Rellotge</string>
+    <string name="heart_rate">Ritme card铆ac</string>
+    <string name="battery">Bateria</string>
+    <string name="liveactivity_live_activity">Activitat en temps real</string>
+    <string name="weeksteps_today_steps_description">Passes avui, objectiu: %1$s</string>
+    <string name="pref_title_low_latency_fw_update">Utilitza el mode de baixa lat猫ncia per actualitzar el microprogramari</string>
+    <string name="pref_summary_low_latency_fw_update">Aix貌 podria ajudar en aparells on l\'actualitzaci贸 del microprogramari falla.</string>
+    <string name="live_activity_steps_history">Historial de passes</string>
+    <string name="live_activity_current_steps_per_minute">Passes actuals / min</string>
+    <string name="live_activity_total_steps">Passes totals</string>
+    <string name="live_activity_steps_per_minute_history">Historial de passes per minut</string>
+    <string name="live_activity_start_your_activity">Inicieu la vostra activitat</string>
+    <string name="abstract_chart_fragment_kind_activity">Activitat</string>
+    <string name="abstract_chart_fragment_kind_light_sleep">Son lleuger</string>
+    <string name="abstract_chart_fragment_kind_deep_sleep">Son profund</string>
+    <string name="abstract_chart_fragment_kind_not_worn">No portat</string>
+    <string name="device_not_connected">No connectat.</string>
+    <string name="user_feedback_all_alarms_disabled">Totes les alarmes estan desactivades</string>
+    <string name="pref_title_keep_data_on_device">Mantingues les dades d\'activitat a l\'aparell</string>
+    <string name="miband_fwinstaller_incompatible_version">Microprogramari incompatible</string>
+    <string name="fwinstaller_firmware_not_compatible_to_device">Aquest microprogramari no 茅s compatible amb l\'aparell</string>
+    <string name="miband_prefs_reserve_alarm_calendar">Alarmes a reservar per a esdeveniments futurs</string>
+    <string name="miband_prefs_hr_sleep_detection">Utilitza el sensor de ritme card铆ac per millorar la detecci贸 del son</string>
+    <string name="miband_prefs_device_time_offset_hours">Despla莽ament de temps de l\'aparell, en hores (per detectar el son de treballadors/es de torns)</string>
+    <string name="miband2_prefs_dateformat">Format de data</string>
+    <string name="dateformat_time">Temps</string>
+    <string name="dateformat_date_time">Hora i data</string>
+    <string name="mi2_prefs_button_actions">Accions del bot贸</string>
+    <string name="mi2_prefs_button_actions_summary">Especifiqueu les accions en pr茅mer el bot贸</string>
+    <string name="mi2_prefs_button_press_count">Nombre de pulsacions del bot贸</string>
+    <string name="mi2_prefs_button_press_count_summary">Nombre de vegades que cal pr茅mer el bot贸 per activar un Esdeveniment 1. Despr茅s, pr茅mer el mateix nombre de vegades crear脿 un Esdeveniment 2, i successivament.</string>
+    <string name="mi2_prefs_button_press_broadcast">Missatge de difusi贸 a enviar</string>
+    <string name="mi2_prefs_button_action">Activa el bot贸 d\'acci贸</string>
+    <string name="mi2_prefs_button_action_summary">Activa un acci贸 en pr茅mer un bot贸 un cert nombre de vegades</string>
+    <string name="mi2_prefs_button_action_vibrate">Activa la vibraci贸 de la polsera</string>
+    <string name="mi2_prefs_button_action_vibrate_summary">Activa la vibraci贸 de la pulsera en pr茅mer el bot贸 d\'acci贸</string>
+    <string name="mi2_prefs_button_press_count_max_delay">Retard m脿xim entre pulsacions</string>
+    <string name="mi2_prefs_button_press_count_max_delay_summary">Retard m脿xim entre pulsacions del bot贸 en mil路lisegons</string>
+    <string name="mi2_prefs_goal_notification">Notificaci贸 d\'objectiu</string>
+    <string name="mi2_prefs_goal_notification_summary">La polsera vibrar脿 quan l\'objectiu diari de passes siga assolit</string>
+    <string name="mi2_prefs_display_items">Elements a mostrar</string>
+    <string name="mi2_prefs_display_items_summary">Escolliu els elements a mostrar a la pantalla de la polsera</string>
+    <string name="mi2_prefs_activate_display_on_lift">Activa la pantalla en al莽ar-la</string>
+    <string name="mi2_prefs_rotate_wrist_to_switch_info">Gira el canell per canviar la informaci贸</string>
+    <string name="mi2_prefs_do_not_disturb">No molesteu</string>
+    <string name="mi2_prefs_do_not_disturb_summary">La polsera no rebr脿 notificacions mentre aquesta opci贸 estigui activada</string>
+    <string name="mi2_prefs_inactivity_warnings">Avisos d\'inactivitat</string>
+    <string name="mi2_prefs_inactivity_warnings_summary">La polsera vibrar脿 quan h脿giu estat inactius durant una estona</string>
+    <string name="mi2_prefs_inactivity_warnings_threshold">Llindar d\'inactivitat (en minuts)</string>
+    <string name="mi2_prefs_inactivity_warnings_dnd_summary">Desactiva els avisos d\'inactivitat durant un interval de temps</string>
+    <string name="mi2_prefs_do_not_disturb_start">Hora d\'inici</string>
+    <string name="mi2_prefs_do_not_disturb_end">Hora de finalitzaci贸</string>
+    <string name="automatic">Autom脿tic</string>
+    <string name="simplified_chinese">Xin猫s simplificat</string>
+    <string name="traditional_chinese">Xin猫s tradicional</string>
+    <string name="english">Angl猫s</string>
+    <string name="spanish">Castell脿</string>
+    <string name="FetchActivityOperation_about_to_transfer_since">A punt de transferir dades des de %1$s</string>
+    <string name="waiting_for_reconnect">S\'est脿 esperant per reconectar</string>
+    <string name="activity_prefs_about_you">Quant a tu</string>
+    <string name="activity_prefs_year_birth">Any de naixement</string>
+    <string name="activity_prefs_gender">G猫nere</string>
+    <string name="activity_prefs_height_cm">Al莽ada en cm</string>
+    <string name="activity_prefs_weight_kg">Pes en kg</string>
+    <string name="authenticating">S\'est脿 autenticant</string>
+    <string name="authentication_required">Es requereix autenticaci贸</string>
+    <string name="appwidget_text">Zzz</string>
+    <string name="add_widget">Afegeix un giny</string>
+    <string name="activity_prefs_sleep_duration">Durada preferida del son en hores</string>
+    <string name="appwidget_setting_alarm">S\'ha fixat una alarma a les %1$02d:%2$02d</string>
+    <string name="device_hw">Revisi贸 del maquinari: %1$s</string>
+    <string name="device_fw">Versi贸 del microprogramari: %1$s</string>
+    <string name="error_creating_directory_for_logfiles">S\'ha produ茂t un error en crear un directori per als fitxers de registre: %1$s</string>
+    <string name="DEVINFO_HR_VER">"Ritme card铆ac: "</string>
+    <string name="updatefirmwareoperation_update_in_progress">S\'est脿 instal路lant el microprogramari</string>
+    <string name="updatefirmwareoperation_firmware_not_sent">No s\'ha enviat el microprogramari</string>
+    <string name="charts_legend_heartrate">Ritme card铆ac</string>
+    <string name="live_activity_heart_rate">Ritme card铆ac</string>
+    <string name="pref_title_pebble_gatt_clientonly">Nom茅s client GATT</string>
+    <string name="pref_summary_pebble_gatt_clientonly">Aix貌 nom茅s 茅s per a Pebble 2 i 茅s experimental. Proveu-ho si teniu problemes de connexi贸</string>
+    <string name="fw_upgrade_notice_miband3">Sou a punt d\'instal路lar el microprogramari %s en la vostra Mi Band 3.
+\n
+\nSi us plau, assegureu-vos d\'instal路lar el fitxer .fw, i despr茅s el fitxer .res. La vostra polsera es reiniciar脿 despr茅s d\'instal路lar el fitxer .fw.
+\n
+\nObservaci贸: No cal que instal路leu el fitxer .res si 茅s exactament el mateix que ja estava instal路lat.
+\n
+\nPROCEDIU SOTA LA VOSTRA PR脪PIA RESPONSABILITAT!</string>
+    <string name="blacklist_all_for_notifications">Bloqueja totes les notifications</string>
+    <string name="whitelist_all_for_notifications">Permet totes les notificacions</string>
+    <string name="prefs_screen_orientation">Orientaci贸 de la pantalla</string>
+    <string name="controlcenter_calibrate_device">Calibra l\'aparell</string>
+    <string name="pref_title_notifications_timeout">Temps m铆nim entre notificacions</string>
+    <string name="controlcenter_change_led_color">Canvia el color del LED</string>
+    <string name="controlcenter_change_fm_frequency">Canvia la freq眉猫ncia FM</string>
+    <string name="pref_title_rtl">De dreta a esquerra</string>
+    <string name="pref_summary_rtl">Activeu aix貌 si el vostre aparell no pot mostrar lleng眉es que s\'escriuen de dreta esquerra</string>
+    <string name="pref_rtl_max_line_length">Llargada m脿xima de l铆nia de dreta a esquerra</string>
+    <string name="pref_rtl_max_line_length_summary">Allarga o escur莽a les l铆nies en qu猫 se separa el text escrit de dreta a esquerra</string>
+    <string name="debugactivity_really_factoryreset_title">De deb貌 voleu restablir als par脿metres de f脿brica\?</string>
+    <string name="debugactivity_really_factoryreset">Restablir als par脿metres de f脿brica esborrar脿 totes les dades de l\'aparell connectat (si 茅s compatible). En el cas dels aparells Xiaomi o Huami, tamb茅 canvia la seva adre莽a MAC, de manera que per al Gadgetbridge apareixeran com a un aparell nou.</string>
+    <string name="zetime_title_settings">Configuraci贸 de ZeTime</string>
+    <string name="zetime_title_heartrate">Configuraci贸 del r铆tme card铆ac</string>
+    <string name="zetime_title_screentime">Durada de la pantalla encesa en segons</string>
+    <string name="zetime_title_heart_rate_alarm">Alarma del ritme card铆ac</string>
+    <string name="zetime_title_heart_rate_alarm_summary">El rellotge us avisar脿 quan el vostre ritme card铆ac superi els l铆mits.</string>
+    <string name="zetime_heart_rate_alarm_enable">Activa l\'alarma del ritme card铆ac</string>
+    <string name="activity_prefs_alarm_max_heart_rate">Ritme card铆ac m脿xim</string>
+    <string name="activity_prefs_alarm_min_heart_rate">Ritme card铆ac m铆nim</string>
+    <string name="zetime_analog_mode">Mode anal貌gic</string>
+    <string name="zetime_analog_mode_hands">Nom茅s mans</string>
+    <string name="zetime_analog_mode_handsandsteps">Mans i passes</string>
+    <string name="pref_title_support_voip_calls">Activa les trucades de VoIP</string>
+    <string name="fw_upgrade_notice_amazfitcor2">Sou a punt d\'instal路lar el microprogramari %s en la vostra Amazfit Cor 2.
+\n
+\nSi us plau, assegureu-vos d\'instal路lar el fitxer .fw, i despr茅s el fitxer .res. La vostra polsera es reiniciar脿 despr茅s d\'instal路lar el fitxer .fw.
+\n
+\nObservaci贸: No cal que instal路leu el fitxer .res si 茅s exactament el mateix que ja estava instal路lat.
+\n
+\nPROCEDIU SOTA LA VOSTRA PR脪PIA RESPONSABILITAT!
+\n
+\nAIX脪 NO HA ESTAT PROVAT ABANS. PROBABLEMENT CAL QUE INSTAL路LEU UN MICROPROGRAMARI BEATS_W SI EL NOM DEL VOSTRE APARELL 脡S \"Amazfit Band 2\"</string>
+    <string name="firmware_install_warning">ESTEU PROVANT D\'INSTAL路LAR UN MICROPROGRAMARI. CONTINUEU SOTA LA VOSTRA RESPONSABILITAT.
+\n
+\n
+\nAquest microprogramari 茅s per a la revisi贸 del maquinari: %s</string>
+    <string name="miband_pairing_using_dummy_userdata">No s\'han introdu茂t dades v脿lides. De moment es fan servir dades fict铆cies.</string>
+    <string name="miband_pairing_tap_hint">Quan la vostra Mi Band vibri i parpallegi, toqueu-la v脿ries vegades seguides.</string>
+    <string name="discovery_connected_devices_hint">Feu que el vostre aparell es pugui detectar. Els aparells que ara estan connectats probablement no seran detectats. Activeu la localitzaci贸 (per exemple, el GPS) a Android 6+. Desactiveu el Privacy Guard per al Gadgetbridge, perqu猫 podria penjar-se i fer que el tel猫fon es renici茅s. Si despr茅s d\'uns quants minuts no s\'ha trobat cap aparell, torneu-ho a provar despr茅s de reiniciar el tel猫fon.</string>
+    <string name="pref_title_dont_ack_transfer">No envi茂s l\'ACK de les dades d\'activitats</string>
+    <string name="pref_summary_dont_ack_transfers">Si no s贸n notificades a la polsera, les dades d\'activitat no s\'eliminaran. Pot ser 煤til si feu servir el Gadgebridge junt amb altres aplicacions.</string>
+    <string name="pref_summary_keep_data_on_device">Les dades d\'activitats de la Mi Band es guardaran fins i tot despr茅s de sincronitzar. Aquesta opci贸 pot ser 煤til si feu servir el Gadgebridge tamb茅 amb altres aplicacions.</string>
+    <string name="mi2_prefs_button_press_broadcast_summary">Dif贸n el missatge enviat amb l\'esdeveniment. El par脿metre `button_id` 茅s afegit autom脿ticament a cada missatge.</string>
+    <string name="pref_title_pebble_health_store_raw">Desa dades sense processar a la base de dades</string>
+    <string name="pref_summary_pebble_health_store_raw">Desa les dades \"tal qual\", tot augmentant l\'煤s de la base de dades per tal de permetre possibles interpretacions m茅s tard.</string>
+    <string name="action_db_management">Gesti贸 de dades</string>
+    <string name="title_activity_db_management">Gesti贸 de dades</string>
+    <string name="activity_db_management_import_export_explanation">Les operacions d\'exportaci贸/importaci贸 fan servir el seg眉ent cam铆 (vegeu a sota) cap a un directori al vostre aparell. Aquest directori 茅s accessible des d\'altres aparells Android i des del vostre ordinador. Tingueu en compte que aquest directori i tots els seus fitxers seran esborrats si desinstal路leu Gadgetbridge. Les dades inclouen:
+\n Export_preference - configuraci贸 global
+\n Export_preference_MAC - configuraci贸 espec铆fica de l\'aparell
+\n Gadgetbridge - base de dades d\'activitat i aparell
+\n Gadgetbridge_date - base de dades exportada en una data
+\n *.gpx - gravacions GPS
+\n *.log - fitxers de registre
+\nTrobeu els vostres fitxers exportats (o deseu els fitxers que volgueu importar) aqu铆:</string>
+    <string name="activity_db_management_merge_old_title">Esborra la base de dades antiga</string>
+    <string name="dbmanagementactivvity_cannot_access_export_path">No es pot accedir al cam铆 d\'exportaci贸. Si us plau, contacteu els desenvolupadors.</string>
+    <string name="dbmanagementactivity_exported_to">S\'ha exportat a: %1$s</string>
+    <string name="dbmanagementactivity_error_exporting_db">Hi ha hagut un error en esborrar la base de dades: %1$s</string>
+    <string name="dbmanagementactivity_error_exporting_shared">Hi ha hagut un error en exportar la prefer猫ncia: %1$s</string>
+    <string name="dbmanagementactivity_import_data_title">Voleu importar les dades\?</string>
+    <string name="dbmanagementactivity_overwrite_database_confirmation">De deb貌 voleu sobreescriure les dades actuals? Totes les vostres dades actuals d\'activitats (si en teniu) i les prefer猫ncies es sobreescriuran.</string>
+    <string name="dbmanagementactivity_import_successful">S\'ha importat.</string>
+    <string name="dbmanagementactivity_error_importing_db">Hi ha hagut un error en importar la base de dades: %1$s</string>
+    <string name="dbmanagementactivity_error_importing_shared">Hi ha hagut un error en importar la prefer猫ncia: %1$s</string>
+    <string name="dbmanagementactivity_delete_activity_data_title">Voleu esborrar les dades d\'activitats\?</string>
+    <string name="dbmanagementactivity_really_delete_entire_db">De deb貌 voleu esborrar tota la base de dades\? Totes les vostres dades d\'activitats i tota la informaci贸 sobre els vostres aparells es perdran.</string>
+    <string name="dbmanagementactivity_database_successfully_deleted">S\'han esborrat les dades.</string>
+    <string name="dbmanagementactivity_db_deletion_failed">Ha fallat la supressi贸 de la base de dades.</string>
+    <string name="dbmanagementactivity_delete_old_activity_db">Voleu esborrar la base de dades antiga d\'activitats\?</string>
+    <string name="dbmanagementactivity_delete_old_activitydb_confirmation">De deb貌 voleu esborrar la base de dades antiga d\'activitats\? Les dades d\'activitats que no haguessin estat importades es perdran.</string>
+    <string name="dbmanagementactivity_old_activity_db_successfully_deleted">Les dades antigues d\'activitats s\'han esborrat.</string>
+    <string name="dbmanagementactivity_old_activity_db_deletion_failed">Ha fallat la supressi贸 de la base de dades antiga d\'activitats.</string>
+    <string name="dbmanagementactivity_overwrite">Sobreescriu</string>
+    <string name="Cancel">Cancel路la</string>
+    <string name="Delete">Esborra</string>
+    <string name="title_activity_vibration">Vibraci贸</string>
+    <string name="title_activity_pebble_pairing">Aparellament amb Pebble</string>
+    <string name="pebble_pairing_hint">Apareixer脿 un di脿leg d\'aparellament al vostre aparell Android. Si no, cerqueu-lo entre les notificacions i accepteu la petici贸 d\'aparellament. Despr茅s accepteu l\'aparellament tamb茅 al Pebble.</string>
+    <string name="weather_notification_label">Assegureu-vos que aquesta aparen莽a est脿 activada en l\'aplicaci贸 de notificaci贸 del temps per obtenir informaci贸 del temps al vostre Pebble.
+\n
+\nAqu铆 no cal configurar res.
+\n
+\nPodeu activar l\'aplicaci贸 del temps del sistema del vostre Pebble des de la gesti贸 d\'aplicacions.
+\n
+\nLes car脿tules compatibles mostraran el temps autom脿ticament.</string>
+    <string name="pref_title_setup_bt_pairing">Activa l\'aparellament per Bluetooth</string>
+    <string name="pref_summary_setup_bt_pairing">Desactiveu aix貌 si teniu problemes de connexi贸</string>
+    <string name="unit_metric">Sistema m猫tric</string>
+    <string name="unit_imperial">Sistema imperial</string>
+    <string name="timeformat_24h">24h</string>
+    <string name="timeformat_am_pm">AM/PM</string>
+    <string name="pref_screen_notification_profile_alarm_clock">Despertador</string>
+    <string name="activity_web_view">Activitat Web View</string>
+    <string name="StringUtils_sender">(%1$s)</string>
+    <string name="find_device_you_found_it">L\'heu trobat!</string>
+    <string name="miband2_prefs_timeformat">Mi2: format d\'hora</string>
+    <string name="mi2_fw_installhandler_fw53_hint">Heu d\'instal路lar la versi贸 %1$s abans d\'instal路lar aquest microprogramari!</string>
+    <string name="mi2_enable_text_notifications">Notifications de text</string>
+    <string name="mi2_enable_text_notifications_summary">Necessita tenir instal路lats el microrprogramari &gt;= 1.10.1.28 i Mili_pro.ft*.</string>
+    <string name="off">Desactivat</string>
+    <string name="mi2_dnd_off">Desactivat</string>
+    <string name="mi2_dnd_automatic">Autom脿tic (detecci贸 de son)</string>
+    <string name="mi2_dnd_scheduled">Programat (interval de temps)</string>
+    <string name="discovery_attempting_to_pair">S\'est脿 intentant aparellar amb %1$s</string>
+    <string name="discovery_bonding_failed_immediately">El vincle amb %1$s ha fallat immediatament.</string>
+    <string name="discovery_trying_to_connect_to">S\'est脿 intentant connectar amb: %1$s</string>
+    <string name="discovery_enable_bluetooth">Activa el Bluetooth per detectar aparells.</string>
+    <string name="discovery_successfully_bonded">Enlla莽at amb %1$s.</string>
+    <string name="discovery_pair_title">Voleu aparellar amb %1$s\?</string>
+    <string name="discovery_pair_question">Seleccioneu \"Aparella\" per aparellar els vostres aparells. Si no funciona, torneu-ho a provar sense aparellar.</string>
+    <string name="discovery_yes_pair">Aparella</string>
+    <string name="discovery_dont_pair">No aparellis</string>
+    <string name="_pebble_watch_open_on_phone">Obre a l\'aparell Android</string>
+    <string name="_pebble_watch_mute">Silencia</string>
+    <string name="_pebble_watch_reply">Respon</string>
+    <string name="kind_firmware">Microprogramari</string>
+    <string name="kind_invalid">Les dades no s贸n v脿lides</string>
+    <string name="kind_font">Tipus de lletra</string>
+    <string name="kind_gps">Microprogramari del GPS</string>
+    <string name="kind_gps_almanac">Almanac GPS</string>
+    <string name="kind_gps_cep">Correcci贸 d\'errors del GPS</string>
+    <string name="kind_resources">Recursos</string>
+    <string name="kind_watchface">Car脿tula</string>
+    <string name="devicetype_unknown">Aparell desconegut</string>
+    <string name="devicetype_test">Aparell de prova</string>
+    <string name="devicetype_pebble">Pebble</string>
+    <string name="devicetype_miband">Mi Band</string>
+    <string name="devicetype_miband2">Mi Band 2</string>
+    <string name="devicetype_amazfit_bip">Amazfit Bip</string>
+    <string name="devicetype_amazfit_cor">Amazfit Cor</string>
+    <string name="devicetype_vibratissimo">Vibratissimo</string>
+    <string name="devicetype_liveview">LiveView</string>
+    <string name="devicetype_hplus">HPlus</string>
+    <string name="devicetype_makibes_f68">Makibes F68</string>
+    <string name="devicetype_exrizu_k8">Exrizu K8</string>
+    <string name="devicetype_no1_f1">No.1 F1</string>
+    <string name="devicetype_teclast_h30">Teclast H30</string>
+    <string name="choose_auto_export_location">Tria la ubicaci贸 de l\'exportaci贸</string>
+    <string name="notification_channel_name">General</string>
+    <string name="devicetype_xwatch">XWatch</string>
+    <string name="on">Activat</string>
+    <string name="controlcenter_start_activity_tracks">Les vostres pistes d\'activitat</string>
+    <string name="activity_type_not_measured">No mesurat</string>
+    <string name="activity_type_activity">Activitat</string>
+    <string name="activity_type_light_sleep">Son lleuger</string>
+    <string name="activity_type_deep_sleep">Son profund</string>
+    <string name="activity_type_not_worn">Aparell no usat</string>
+    <string name="activity_type_running">C贸rrer</string>
+    <string name="activity_type_walking">Caminar</string>
+    <string name="activity_type_swimming">Nedar</string>
+    <string name="activity_type_unknown">Activitat desconeguda</string>
+    <string name="activity_summaries">Activitats esportives</string>
+    <string name="activity_type_biking">Ciclisme</string>
+    <string name="activity_type_treadmill">Cinta de c贸rrer</string>
+    <string name="select_all">Selecciona-ho tot</string>
+    <string name="share">Comparteix</string>
+    <string name="reset_index">Reinicialitza la dada d\'extracci贸</string>
+    <string name="menuitem_status">Estat</string>
+    <string name="menuitem_activity">Historial d\'entrenaments</string>
+    <string name="menuitem_weather">El Temps</string>
+    <string name="menuitem_alarm">Alarma</string>
+    <string name="menuitem_timer">Compte enrere</string>
+    <string name="menuitem_compass">Br煤ixola</string>
+    <string name="menuitem_settings">Configuraci贸</string>
+    <string name="menuitem_alipay">Alipay</string>
+    <string name="devicetype_q8">Q8</string>
+    <string name="devicetype_miband3">Mi Band 3</string>
+    <string name="pref_auto_fetch">Recull autom脿ticament les dades d\'activitats</string>
+    <string name="pref_auto_fetch_summary">Es recullen dades quan es bloqueja la pantalla. Nom茅s funciona si hi ha un sistema de bloqueig establert!</string>
+    <string name="pref_auto_fetch_limit_fetches">Temps m铆nim entre extraccions</string>
+    <string name="pref_auto_fetch_limit_fetches_summary">Extraccions cada %d minuts</string>
+    <string name="devicetype_mykronoz_zetime">MyKronoz ZeTime</string>
+    <string name="russian">Rus</string>
+    <string name="horizontal">Horitzontal</string>
+    <string name="vertical">Vertical</string>
+    <string name="devicetype_id115">ID115</string>
+    <string name="menuitem_notifications">Notificacions</string>
+    <string name="menuitem_more">M茅s</string>
+    <string name="menuitem_music">M煤sica</string>
+    <string name="watch9_pairing_tap_hint">Quan el vostre aparell vibri, sacsegeu-lo o premeu-ne el bot贸.</string>
+    <string name="devicetype_watch9">Watch 9</string>
+    <string name="watch9_time_minutes">Minuts:</string>
+    <string name="watch9_time_hours">Hores:</string>
+    <string name="watch9_time_seconds">Segons:</string>
+    <string name="watch9_calibration_hint">Establiu l\'hora que l\'aparell us mostra ara mateix.</string>
+    <string name="watch9_calibration_button">Calibra</string>
+    <string name="title_activity_watch9_pairing">Aparellament del Watch 9</string>
+    <string name="title_activity_watch9_calibration">Calibratge del Watch 9</string>
+    <string name="mi3_prefs_band_screen_unlock">Desbloqueig de la pantalla de la polsera</string>
+    <string name="mi3_prefs_band_screen_unlock_summary">Passa el dit cap amunt per desbloquejar la pantalla de la polsera</string>
+    <string name="german">Alemany</string>
+    <string name="italian">Itali脿</string>
+    <string name="french">Franc猫s</string>
+    <string name="polish">Polon猫s</string>
+    <string name="korean">Core脿</string>
+    <string name="japanese">Japon猫s</string>
+    <string name="you_slept">Heu dormit de %1$s a %2$s</string>
+    <string name="you_did_not_sleep">No heu dormit</string>
+    <string name="norwegian_bokmal">Noruec bokm氓l</string>
+    <string name="no_limit">Sense l铆mit</string>
+    <string name="seconds_5">5 segons</string>
+    <string name="seconds_10">10 segons</string>
+    <string name="seconds_20">20 segons</string>
+    <string name="seconds_30">30 segons</string>
+    <string name="minutes_1">1 minut</string>
+    <string name="minutes_5">5 minuts</string>
+    <string name="minutes_10">10 minuts</string>
+    <string name="minutes_30">30 minuts</string>
+    <string name="mi3_prefs_night_mode">Mode nocturn</string>
+    <string name="mi3_prefs_night_mode_summary">Rebaixa la brillantor de la pantalla de la polsera autom脿ticament a la nit</string>
+    <string name="mi3_night_mode_sunset">Al capvespre</string>
+    <string name="ok">D\'acord</string>
+    <string name="share_log">Comparteix el registre</string>
+    <string name="share_log_warning">Si us plau, tingueu en compte que els fitxers de registre del Gadgetbridge poden contenir molta informaci贸 personal, incloent-hi, entre d\'altres, dades relacionades amb la salut, identificadors 煤nics (com ara adreces MAC), prefer猫ncies musicals, etc. Considereu editar el fitxer i suprimir aquesta informaci贸 abans d\'enviar-lo a un informe p煤blic d\'error.</string>
+    <string name="warning">Atenci贸!</string>
+    <string name="devicetype_roidmi">Roidmi</string>
+    <string name="devicetype_roidmi3">Roidmi 3</string>
+    <string name="preferences_led_color">Color del LED</string>
+    <string name="preferences_fm_frequency">Freq眉猫ncia de FM</string>
+    <string name="pref_invalid_frequency_title">La freq眉猫ncia no 茅s v脿lida</string>
+    <string name="pref_invalid_frequency_message">Si us plau, introdu茂u una freq眉猫ncia entre 87,5 i 108,0</string>
+    <string name="activity_prefs_charts">Configuraci贸 dels gr脿fics</string>
+    <string name="activity_prefs_chart_max_heart_rate">Ritme card铆ac m脿xim</string>
+    <string name="activity_prefs_chart_min_heart_rate">Ritme card铆ac m铆nim</string>
+    <string name="notif_battery_low">%1$s nivell de bateria baix</string>
+    <string name="notif_battery_low_extended">%1$s nivell de bateria baix: %2$s</string>
+    <string name="pref_title_contextual_arabic">脌rab contextual</string>
+    <string name="pref_summary_contextual_arabic">Activeu aix貌 per a compatibilitat amb l\'脿rab contextual</string>
+    <string name="preferences_rtl_settings">Compatibilitat amb escriptura de dreta a esquerra</string>
+    <string name="language_and_region_prefs">Configuraci贸 de llengua i regi贸</string>
+    <string name="lack_of_sleep">Manca de son: %1$s</string>
+    <string name="overslept">Son de m茅s: %1$s</string>
+    <string name="lack_of_step">Manca de passes: %1$d</string>
+    <string name="overstep">Passes de m茅s: %1$d</string>
+    <string name="no_data">No hi ha dades</string>
+    <string name="live_activity_max_heart_rate">Ritme card铆ac actual / m脿xim: %1$d / %2$d</string>
+    <string name="devicetype_casiogb6900">Casio GB-6900</string>
+    <string name="title_activity_notification_filter">Filtre de notificacions</string>
+    <string name="edittext_notification_filter_words_hint">Introdu茂u les paraules desitjades, cadascuna en una l铆nia nova</string>
+    <string name="toast_notification_filter_saved_successfully">S\'ha desat el filtre de notificacions</string>
+    <string name="filter_mode_none">No filtris</string>
+    <string name="filter_mode_whitelist">Mostra quan es contenen les paraules</string>
+    <string name="filter_mode_blacklist">Bloca quan es contenen les paraules</string>
+    <string name="filter_submode_at_least_one">Almenys una de les paraules</string>
+    <string name="filter_submode_all">Totes les paraules</string>
+    <string name="toast_notification_filter_words_empty_hint">Si us plau, introdu茂u com a m铆nim una paraula</string>
+    <string name="filter_mode">Mode de filtratge</string>
+    <string name="mode_configuration">Configuraci贸 del mode</string>
+    <string name="save_configuration">Desa la configuraci贸</string>
+    <string name="appwidget_not_connected">No est脿 connectat, no s\'ha establert l\'alarma.</string>
+    <string name="activity_type_exercise">Exercici</string>
+    <string name="prefs_disconnect_notification">Notificaci贸 de desconnexi贸</string>
+    <string name="zetime_activity_tracking">Seguiment d\'activitats</string>
+    <string name="zetime_activity_tracking_summary">Activeu aquesta opci贸 per comptar les vostres passes i altres activitats.</string>
+    <string name="zetime_handmove_display">Moviment de la m脿</string>
+    <string name="zetime_handmove_display_summary">Moveu el canell per activar o desactivar la pantalla.</string>
+    <string name="zetime_calories_type">Tipus de calories</string>
+    <string name="zetime_calories_type_active">Nom茅s les calories cremades de forma activa</string>
+    <string name="zetime_calories_type_all">Calories cremades de forma activa i en rep貌s</string>
+    <string name="zetime_date_format">Format de data</string>
+    <string name="zetime_date_format_1">AA/MM/DD</string>
+    <string name="zetime_date_format_2">DD/MM/AA</string>
+    <string name="zetime_date_format_3">MM/DD/AA</string>
+    <string name="zetime_prefs_inactivity_repetitions">Repeticions</string>
+    <string name="zetime_prefs_inactivity_mo">Dilluns</string>
+    <string name="zetime_prefs_inactivity_tu">Dimarts</string>
+    <string name="zetime_prefs_inactivity_we">Dimecres</string>
+    <string name="zetime_prefs_inactivity_th">Dijous</string>
+    <string name="zetime_prefs_inactivity_fr">Divendres</string>
+    <string name="zetime_prefs_inactivity_sa">Dissabte</string>
+    <string name="zetime_prefs_inactivity_su">Diumenge</string>
+    <string name="zetime_title_alarm_signaling">Defineix el tipus de senyal per a l\'alarma</string>
+    <string name="zetime_signaling_none">Silenci贸s</string>
+    <string name="zetime_signaling_vibrate">Vibraci贸 cont铆nua</string>
+    <string name="zetime_signaling_beep">Timbre continu</string>
+    <string name="zetime_signaling_vibrate_beep">Vibraci贸 i timbre continus</string>
+    <string name="zetime_signaling_vibrate_once">Vibra una vegada</string>
+    <string name="zetime_signaling_vibrate_twice">Vibra dues vegades</string>
+    <string name="zetime_signaling_beep_once">Fes sonar un timbre una vegada</string>
+    <string name="zetime_signaling_beep_twice">Fes sonar un timbre dues vegades</string>
+    <string name="zetime_signaling_vibrate_beep_once">Vibra i fes sonar un timbre una vegada</string>
+    <string name="pref_screen_notification_profile_missed_call">Notificaci贸 de trucada perduda</string>
+    <string name="pref_screen_notification_profile_calendar">Notificaci贸 de calendari</string>
+    <string name="pref_screen_notification_profile_inactivity">Notificaci贸 d\'inactivitat</string>
+    <string name="pref_screen_notification_profile_low_power">Av铆s de bateria baixa</string>
+    <string name="pref_screen_notification_profile_anti_loss">Av铆s anti-p猫rdua</string>
+    <string name="interval_fifteen_minutes">cada 15 minuts</string>
+    <string name="interval_forty_five_minutes">cada 45 minuts</string>
+    <string name="activity_prefs_calories_burnt">Objectiu diari: calories cremades</string>
+    <string name="activity_prefs_distance_meters">Objectiu diari: dist脿ncia en metres</string>
+    <string name="activity_prefs_activetime_minutes">Objectiu diari: temps d\'activitat en minuts</string>
+    <string name="devicetype_miscale2">Mi Scale 2</string>
+    <string name="title_activity_device_specific_settings">Configuraci贸 espec铆fica de l\'aparell</string>
+    <string name="pref_title_authkey">Clau d\'autenticaci贸</string>
+    <string name="pref_summary_authkey">Canvia la clau d\'autenticaci贸 en tots els aparells Android des dels quals et vulguis connectar. La clau anterior per defecte per a tots els aparells 茅s 0123456789@ABCDE</string>
+    <string name="devicetype_bfh16">BFH-16</string>
+    <string name="dutch">Neerland猫s</string>
+    <string name="turkish">Turc</string>
+    <string name="ukrainian">Ucra茂n猫s</string>
+    <string name="arabic">脌rab</string>
+    <string name="indonesian">Indonesi</string>
+    <string name="thai">Tailand猫s</string>
+    <string name="vietnamese">Vietnamita</string>
+    <string name="portuguese">Portugu猫s</string>
+    <string name="devicetype_amazfit_cor2">Amazfit Cor 2</string>
+    <string name="devicetype_miband4">Mi Band 4</string>
+    <string name="mi2_prefs_button_press_broadcast_default_value" translatable="false">nodomain.freeyourgadget.gadgetbridge.ButtonPressed</string>
+    <string name="fw_upgrade_notice_miband4">Esteu a punt d\'instal路lar el microprogramari %s en la vostra Mi Band 4.
+\n
+\nSi us plau, assegureu-vos d\'instal路lar el fitxer .fw, i despr茅s el fitxer .res. La vostra polsera es reiniciar脿 despr茅s d\'instal路lar l\'arxiu .fw.
+\n
+\nNota: no heu d\'instal路lar el fitxer .res si 茅s exactament el mateix que ja estava instal路lat.
+\n
+\nPROCEDIU SOTA LA VOSTRA PR脪PIA RESPONSABILITAT!</string>
+    <string name="prefs_hr_alarm_low">L铆mit baix</string>
+    <string name="prefs_hr_alarm_high">L铆mit alt</string>
+    <string name="average">Mitjana: %1$s</string>
+    <string name="pref_header_charts">Configuraci贸 dels gr脿fics</string>
+    <string name="pref_title_charts_average">Mostra mitjanes als gr脿fics</string>
+    <string name="pref_title_charts_range">Rang dels gr脿fics</string>
+    <string name="weekstepschart_steps_a_month">Passes per mes</string>
+    <string name="weeksleepchart_sleep_a_month">Son per mes</string>
+    <string name="devicetype_mijia_lywsd02">Mijia Smart Clock</string>
+    <string name="menuitem_nfc">NFC</string>
+    <string name="pref_summary_expose_hr">Permet altres aplicacions accedir a les dades de freq眉猫ncia card铆aca en temps real mentre Gadgetbridge estigui connectat</string>
+    <string name="pref_title_expose_hr">Acc茅s a tercers a freq眉猫ncia card铆aca en temps real</string>
+    <string name="pref_title_use_custom_font">Utilitza un tipus de lletra personalitzat</string>
+    <string name="pref_summary_use_custom_font">Activa-ho si el vostre dispositiu inclou microprogramari de tipus de lletra personalitzat amb suport per a emojis</string>
+    <string name="activity_db_management_autoexport_label">Exportaci贸 autom脿tica</string>
+    <string name="activity_DB_ExportButton">Exporta les dades</string>
+    <string name="activity_DB_import_button">Importa les dades</string>
+    <string name="activity_DB_test_export_button">Executa l\'exportaci贸 autom脿tica ara</string>
+    <string name="activity_DB_test_export_message">Exportant base de dades鈥�</string>
+    <string name="activity_DB_delete_legacy_button">Esborra BD antiga</string>
+    <string name="activity_DB_empty_button">Buida la BD</string>
+    <string name="appwidget_sleep_alarm_widget_label">Alarma de son</string>
+    <string name="widget_listing_label">Estat i alarmes</string>
+    <string name="widget_5_minutes">5 minuts</string>
+    <string name="widget_10_minutes">10 minuts</string>
+    <string name="widget_20_minutes">20 minuts</string>
+    <string name="widget_1_hour">1 hora</string>
+    <plurals name="widget_alarm_target_hours">
+        <item quantity="one">%d hora</item>
+        <item quantity="other">%d hores</item>
+    </plurals>
+    <string name="pref_display_add_device_fab">Bot贸 d\'afegir nou dispositiu</string>
+    <string name="pref_display_add_device_fab_on">Sempre visible</string>
+    <string name="pref_display_add_device_fab_off">Visible nom茅s si no hi ha cap dispositiu afegit</string>
+    <string name="preferences_makibes_hr3_settings">Configuraci贸 de Makibes HR3</string>
+    <string name="devicetype_makibes_hr3">Makibes HR3</string>
+    <string name="devicetype_amazfit_bip_lite">Amazfit Bip Lite</string>
+    <string name="prefs_find_phone">Troba el tel猫fon</string>
+    <string name="prefs_enable_find_phone">Activa 芦Troba el tel猫fon禄</string>
+    <string name="prefs_find_phone_duration">Durada del to en segons</string>
+    <string name="maximum_duration">Durada</string>
+    <string name="fw_upgrade_notice_amazfitbip_lite">Esteu a punt d\'instal路lar el microprogramari %s en el vostre Amazfit Bip Lite.
+\n
+\nSi us plau, assegureu-vos d\'instal路lar el fitxer .fw, i despr茅s el fitxer .res. El vostre rellotge es reiniciar脿 despr茅s d\'instal路lar l\'arxiu .fw.
+\n
+\nNota: no heu d\'instal路lar el fitxer .res si 茅s exactament el mateix que ja estava instal路lat.
+\n
+\nPROCEDIU SOTA LA VOSTRA PR脪PIA RESPONSABILITAT!</string>
+    <string name="devicetype_amazfit_gtr">Amazfit GTR</string>
+    <string name="fw_upgrade_notice_amazfitgtr">Esteu a punt d\'instal路lar el microprogramari %s en el vostre Amazfit GTR.
+\n
+\nSi us plau, assegureu-vos d\'instal路lar el fitxer .fw, el fitxer .res i, finalment, el fitxer .gps. El vostre rellotge es reiniciar脿 despr茅s d\'instal路lar l\'arxiu .fw.
+\n
+\nNota: no heu d\'instal路lar els fitxers .res i .gps si aquests fitxers s贸n exactament els mateixos que els pr猫viament instal路lats.
+\n
+\nPROCEDIU SOTA LA VOSTRA PR脪PIA RESPONSABILITAT!</string>
+    <string name="pref_chart_heartrate_color_red">Vermell</string>
+    <string name="pref_chart_heartrate_color_orange">Taronja</string>
+    <string name="pref_title_chart_heartrate_color">Color de freq眉猫ncia card铆aca</string>
+    <string name="pref_title_chart_sleep_rolling_24_hour">Interval de son</string>
+    <string name="pref_chart_sleep_rolling_24_on">脷ltimes 24 hores</string>
+    <string name="pref_chart_sleep_rolling_24_off">De migdia a migdia</string>
+    <string name="devicetype_amazfit_gts">Amazfit GTS</string>
+    <string name="fw_upgrade_notice_amazfitgts">Esteu a punt d\'instal路lar el microprogramari %s en el vostre Amazfit GTS.
+\n
+\nSi us plau, assegureu-vos d\'instal路lar el fitxer .fw, el fitxer .res i, finalment, el fitxer .gps. El vostre rellotge es reiniciar脿 despr茅s d\'instal路lar l\'arxiu .fw.
+\n
+\nNota: no heu d\'instal路lar els fitxers .res i .gps si aquests fitxers s贸n exactament els mateixos que els pr猫viament instal路lats.
+\n
+\nPROCEDIU SOTA LA VOSTRA PR脪PIA RESPONSABILITAT!</string>
+    <string name="prefs_hr_alarm_activity">Alarma de freq眉猫ncia card铆aca durant activitat esportiva</string>
+    <string name="pref_charts_range_on">El rang dels gr脿fics est脿 configurat a un mes</string>
+    <string name="pref_charts_range_off">El rang dels gr脿fics est脿 configurat a una setmana</string>
+    <string name="activity_db_management_autoexport_explanation">La ubicaci贸 de l\'exportaci贸 autom脿tica s\'ha definit a:</string>
+    <string name="activity_db_management_empty_DB">Buida la base de dades</string>
+    <string name="activity_db_management_exportimport_label">Exportar i importar</string>
+    <string name="activity_db_management_empty_db_warning">Atenci贸! En pr茅mer aquest bot贸 esborrareu tota la base de dades i comen莽areu de zero.</string>
+    <string name="widget_set_alarm_after">Fixa una alarma al cap de:</string>
+    <string name="activity_error_no_app_for_gpx">Per a veure la tra莽a d\'activitat, instal路leu una app que pugui manejar fitxers GPX.</string>
+    <string name="prefs_find_phone_summary">Fes servir la teva banda per a reproduir el to del tel猫fon.</string>
+    <string name="discovery_need_to_enter_authkey">Aquest aparell requereix una clau d\'autenticaci贸 secreta, mantingueu premut sobre l\'aparell per a introduir-la. Consulteu la wiki.</string>
+    <string name="pref_summary_allow_high_mtu">Augmenta la velocitat de transfer猫ncia, per貌 pot ser que no funcioni en alguns aparells Android.</string>
+    <string name="devicetype_qhybrid">Fossil Q Hybrid</string>
+    <string name="preferences_qhybrid_settings">Configuraci贸 de Q Hybrid</string>
+    <string name="watch_not_connected">Rellotge no connectat</string>
+    <string name="qhybrid_vibration_strength">intensitat de vibraci贸:</string>
+    <string name="qhybrid_goal_in_steps">Objectiu en passes</string>
+    <string name="qhybrid_time_shift">decalat horari</string>
+    <string name="qhybrid_second_timezone_offset_relative_to_utc">decalat del segon fus horari relatiu a UTC</string>
+    <string name="qhybrid_overwrite_buttons">sobreescriu els botons</string>
+    <string name="qhybrid_use_activity_hand_as_notification_counter">utilitza la maneta d\'activitat com a comptador de notificacions</string>
+    <string name="qhybrid_prompt_million_steps">Si us plau, estableix el comptador de passes a un mili贸 per a activar-ho.</string>
+    <string name="qhybrid_buttons_overwrite_success">Botons sobreescrits</string>
+    <string name="qhybrid_buttons_overwrite_error">Error al sobreescriure botons</string>
+    <string name="qhybrid_offset_timezone">decala el fus horari en</string>
+    <string name="qhybrid_changes_delay_prompt">el canvi pot trigar uns segons鈥�</string>
+    <string name="qhybrid_offset_time_by">temps decalat en</string>
+    <string name="pref_disable_new_ble_scanning">Desactiva el nou escaneig BLE</string>
+    <string name="pref_summary_disable_new_ble_scanning">Marca aquesta opci贸 si el teu aparell no pot ser trobat durant la detecci贸</string>
+    <string name="devicetype_banglejs">Bangle.js</string>
+    <string name="devicetype_y5">Y5</string>
+    <string name="prefs_button_single_press_action_selection_title">Acci贸 d\'esdeveniment 1</string>
+    <string name="prefs_button_double_press_action_selection_title">Acci贸 d\'esdeveniment 2</string>
+    <string name="prefs_button_triple_press_action_selection_title">Acci贸 d\'esdeveniment 3</string>
+    <string name="prefs_button_variable_actions">Configuraci贸 detallada de premuda de bot贸</string>
+    <string name="prefs_button_long_press_action_selection_title">Acci贸 de premuda llarga de bot贸</string>
+    <string name="alarm_snooze">Posposa</string>
+    <string name="error_no_location_access">S\'ha de permetre i activar l\'acc茅s a la localitztaci贸 per al correcte funcionament de l\'escaneig</string>
+    <string name="devicetype_itag">iTag</string>
+    <string name="pref_title_allow_high_mtu">Permet una alta MTU</string>
+    <string name="pref_summary_sync_calendar">Activa les alertes de calendari, fins i tot quan estigui desconnectat</string>
+    <string name="pref_title_sync_caldendar">Sincronitza els esdeveniments del calendari</string>
+    <string name="hr_widget_heart_rate">Ritme card铆ac</string>
+    <string name="hr_widget_steps">Passes</string>
+    <string name="hr_widget_date">Data</string>
+    <string name="hr_widget_active_minutes">Minuts actius</string>
+    <string name="hr_widget_calories">Calories</string>
+    <string name="hr_widget_battery">Bateria</string>
+    <string name="hr_widget_weather">El temps</string>
+    <string name="hr_widget_nothing">Res</string>
+    <string name="find_lost_device_you_found_it">Trobat!</string>
+    <string name="pref_title_force_white_color_scheme">For莽a l\'esquema de color blanc sobre negre</string>
+    <string name="pref_summary_force_white_color_scheme">脷til si el teu rellotge t茅 les manetes negres</string>
+    <string name="find_my_phone_notification">Troba el meu tel猫fon</string>
+    <string name="notification_channel_high_priority_name">Alta prioritat</string>
+    <string name="pref_title_custom_deviceicon">Mostra l\'icona de notificaci贸 espec铆fica de l\'aparell</string>
+    <string name="pref_summary_custom_deviceicon">Mostra l\'icona de notificaci贸 d\'Android espec铆fica de l\'aparell enlloc de l\'icona de Gadgetbridge quan estigui connectat</string>
+    <string name="hr_appname_wellness">Benestar</string>
+    <string name="hr_appname_workout">Entrenament</string>
+    <string name="hr_appname_stopwatch">Cron貌metre</string>
+    <string name="hr_appname_commute">Despl莽ament</string>
+    <string name="pref_title_relax_firmware_checks">Activeu-ho si voleu instal路lar microprogramari no destinat al vostre aparell (sota la vostra responsabilitat)</string>
+    <string name="pref_summary_relax_firmware_checks">Relaxa les comprovacions de microprogramari</string>
+    <string name="devicetype_amazfit_bips">Amazfit Bip S</string>
+    <string name="pref_title_vibration_strength">Intensitat de vibraci贸</string>
+    <string name="pref_header_auto_fetch">Recollida autom脿tica</string>
+    <string name="menuitem_workout">Entrenament</string>
+    <string name="menuitem_eventreminder">Recordatori d\'esdeveniment</string>
+    <string name="menuitem_hr">Ritme card铆ac</string>
+    <string name="devicetype_watchxplus">Watch X Plus</string>
+    <string name="devicetype_watchx">Watch X</string>
+    <string name="power_mode_watch">Nom茅s rellotge</string>
+    <string name="power_mode_saving">Estalvi d\'energia</string>
+    <string name="power_mode_normal">Normal</string>
+    <string name="prefs_sensors_button_bp_calibration_sum">Prem aqu铆 per comen莽ar la calibraci贸</string>
+    <string name="prefs_sensors_button_bp_calibration">Calibraci贸</string>
+    <string name="pref_sensors_bp_calibration">Calibraci贸 de la pressi贸 sangu铆nia</string>
+    <string name="pref_title_sensors_altitude">Calibraci贸 d\'altitud</string>
+    <string name="pref_header_sensors_calibration">Calibraci贸 de sensor</string>
+    <string name="pref_header_device_spec_settings">Configuraci贸 de l\'aparell</string>
+    <string name="pref_summary_notifications_and_calls_title_shake_reject">Duplica l\'acci贸 del bot贸 del rellotge</string>
+    <string name="prefs_notifications_and_calls_shake_reject">Sacseja el canell per a ignorar/rebutjar la trucada</string>
+    <string name="pref_summary_notifications_and_calls_title_reject">Off - ignora, On - rebutja</string>
+    <string name="prefs_notifications_and_calls_reject">Bot贸 d\'ignorar/rebutjar trucada</string>
+    <string name="pref_header_notifications_and_calls_callhandling">Gesti贸 de trucades</string>
+    <string name="pref_title_notifications_and_calls_repeat_on_misscall">Repetex durant X minuts</string>
+    <string name="pref_summary_notifications_and_calls_enable_misscall">Es repeteix a cada minut</string>
+    <string name="pref_notifications_and_calls_enable_misscall">Notifica les trucades perdudes</string>
+    <string name="prefs_notifications_and_calls_continious_ring">Notificaci贸 durant el to de trucada</string>
+    <string name="pref_title_notifications_and_calls_repeat_on_call">Repeteix la notificaci贸 de trucada</string>
+    <string name="pref_header_notifications_and_calls">Notificacions i trucades</string>
+    <string name="title_activity_LenovoWatch_calibration">Calibraci贸 del Watch X Plus</string>
+    <string name="sonyswr12_settings_title">Configuraci贸 de Sony SWR12</string>
+    <string name="sonyswr12_settings_low_vibration">Vibraci贸 baixa activada</string>
+    <string name="sonyswr12_settings_alarm_interval">Interval d\'alarma intel路ligent en minuts</string>
+    <string name="sonyswr12_settings_low_vibration_summary">Activa la baixa intensitat de vibraci贸 a la banda</string>
+    <string name="sonyswr12_settings_stamina_summary">El mode d鈥檈stalvi d鈥檈nergia desactiva la mesura autom脿tica peri貌dica de la freq眉猫ncia card铆aca i, per tant, augmenta el temps de treball</string>
+    <string name="sonyswr12_settings_alarm_interval_summary">L\'interval d\'alarma intel路ligent 茅s l\'interval abans de l\'alarma instal路lada. En aquest interval, el dispositiu intenta detectar la fase m茅s lleugera del son per despertar l\'usuari</string>
+    <string name="sonyswr12_settings_stamina">Mode d\'estalvi d\'energia activat</string>
+    <string name="qhybrid_calibration_align_hint">Utilitza els botons de sota per a alinear les manetes del rellotge a les 12:00.</string>
+    <string name="devicetype_nut_mini">Nut mini</string>
+    <string name="backstroke">Esquena</string>
+    <string name="freestyle">Estil lliure</string>
+    <string name="breaststroke">Bra莽a</string>
+    <string name="activity_filter_apply_filter">Aplica el filtre</string>
+    <string name="activity_filter_reset_filter">Restableix filtre</string>
+    <string name="baseAltitude">Elevaci贸 base</string>
+    <string name="descentSeconds">Descendent</string>
+    <string name="ascentSeconds">Ascendent</string>
+    <string name="averageLapPace">Ritme de volta mitj脿</string>
+    <string name="averageHR">Ritme card铆ac</string>
+    <string name="error_location_enabled_mandatory">La ubicaci贸 ha d\'estar activada per cercar aparells</string>
+    <string name="error_retrieving_devices_database">Error al recuperar els aparells de la base de dades</string>
+    <string name="error_setting_alias">Error a l\'establir pseud貌nim:</string>
+    <string name="error_exporting_device_preferences">Error a l\'exportar les prefer猫ncies espec铆fiques d\'aparell</string>
+    <string name="pref_check_permission_status">Comprova l\'estat dels permisos</string>
+    <string name="error_background_service_reason">L\'inici del servei en segon pla ha fallat per una excepci贸. Error:</string>
+    <string name="error_background_service_reason_truncated">L\'inici del servei en segon pla ha fallat perqu猫鈥�</string>
+    <string name="error_background_service">Ha fallat l\'inici del servei en segon pla</string>
+    <string name="permission_granting_mandatory">Tots aquests permisos s贸n requerits i pot haver-hi inestabilitat si no s\'atorguen</string>
+    <string name="about_additional_contributions">Moltes gr脿cies a tots els col路laboradors no mencionats per contribuir-hi codi, traduccions, suport, idees, motivaci贸, informes d\'error, diners鈥� 鉁�</string>
+    <string name="about_additional_device_support">Support d\'aparell addicional</string>
+    <string name="about_contributors">Col路laboradors</string>
+    <string name="about_core_team_title">Equip principal (en ordre de primera contribuci贸 de codi)</string>
+    <string name="about_description_generic">Substitut lliure, copyleft i sense n煤vol per a les aplicacions Android de codi tancat dels fabricants dels aparells.</string>
+    <string name="menuitem_dnd">No molestis</string>
+    <string name="menuitem_spo2">SpO2</string>
+    <string name="menuitem_pai">PAI</string>
+    <string name="activity_error_no_app_for_png">Per compartir aquesta captura de pantalla, instal路la una app que pugui manegar fitxers d\'imatge.</string>
+    <string name="bip_prefs_shotcuts_summary">Tria les dreceres a la pantalla de la polsera</string>
+    <string name="charts_min_max_heartrate_popup">Ritme card铆ac m铆nim: %1$d
+\nRitme card铆ac m脿xim: %2$d
+\nIntensitat de moviment: %3$s</string>
+    <string name="devicestatus_upload_aborted">S\'ha avortat la c脿rrega!</string>
+    <string name="activity_list_summary_active_steps">Passes actives</string>
+    <string name="chart_get_active_and_synchronize">Fes alguna activitat i sincronitza l\'aparell.</string>
+    <string name="pref_sensors_bp_calibration_high">Pressi贸 arterial sist貌lica (alta)</string>
+    <string name="pref_sensors_bp_calibration_low">Pressi贸 arterial diast貌lica (baixa)</string>
+    <string name="pref_summary_device_spec_settings_title_force_time">For莽a la sincronitzaci贸 autom脿tica de l\'hora al reconnectar. Les manetes anal貌giques poden mostrar una hora incorrecta!</string>
+    <string name="pref_title_device_spec_settings_force_time">For莽a la sincronitzaci贸 de l\'hora</string>
+    <string name="pref_title_weather_summary">Usat per el prove茂dor de temps de LineageOS, altres versions d\'Android requereixen fer servir una app com \"Weather notification\". Troba\'n m茅s informaci贸 a la wiki de Gadgetbridge.</string>
+    <string name="controlcenter_set_alias">Establir pseud貌nim</string>
+    <string name="find_lost_device_message">Cercar %1$s\?</string>
+    <string name="activity_filter_to_placeholder">avui</string>
+    <string name="activity_filter_from_placeholder">passat lluny脿</string>
+    <string name="activity_summaries_all_devices">Tots els aparells</string>
+    <string name="sports_activity_quick_filter_select">Per铆ode de temps</string>
+    <string name="sports_activity_quick_filter_30days">30 dies</string>
+    <string name="sports_activity_quick_filter_7days">7 dies</string>
+    <string name="sports_activity_quick_filter_last_month">El mes passat</string>
+    <string name="sports_activity_quick_filter_this_month">Aquest mes</string>
+    <string name="sports_activity_quick_filter_last_week">La setmana passada</string>
+    <string name="sports_activity_quick_filter_this_week">Aquesta setmana</string>
+    <string name="activity_summaries_all_activities">Totes les activitats</string>
+    <string name="activity_summaries_statistics">Estad铆stiques</string>
+    <string name="no">No</string>
+    <string name="yes">S铆</string>
+    <string name="addto_filter">Afegeix al filtre</string>
+    <string name="activity_filter_name_contains">Etiqueta</string>
+    <string name="activity_filter_filter_title">Filtre</string>
+    <string name="activity_filter_date_to">A</string>
+    <string name="activity_filter_date_from">Des de</string>
+    <string name="pref_header_statistics">Estad铆stiques d\'activitats esportives</string>
+    <string name="pref_header_filter">Filtre d\'activitats esportives</string>
+    <string name="prefs_events_forwarding_action_title">Executa acci贸</string>
+    <string name="prefs_events_forwarding_broadcast_title">Difon missatge</string>
+    <string name="prefs_events_forwarding_startnonwear">Quan no s\'esta portant</string>
+    <string name="prefs_events_forwarding_wokeup">Al despertar-te</string>
+    <string name="prefs_events_forwarding_fellsleep">A l\'adormir-te</string>
+    <string name="prefs_events_forwarding_title">Accions de l\'aparell</string>
+    <string name="gps_track">Pista GPS</string>
+    <string name="activity_detail_show_gps_label">Mostra la pista GPS</string>
+    <string name="activity_detail_duration_label">Durada</string>
+    <string name="activity_detail_end_label">Final</string>
+    <string name="activity_detail_start_label">Inici</string>
+    <string name="Steps">Passes</string>
+    <string name="Activity">Activitat</string>
+    <string name="Speed">Velocitat</string>
+    <string name="Elevation">Elevaci贸</string>
+    <string name="Distance">Dist脿ncia</string>
+    <string name="Swimming">Nataci贸</string>
+    <string name="km">km</string>
+    <string name="minutes_km">min/km</string>
+    <string name="seconds_m">seg/m</string>
+    <string name="seconds_km">seg/km</string>
+    <string name="calories_unit">kcal</string>
+    <string name="laps_unit">voltes</string>
+    <string name="seconds">seg</string>
+    <string name="km_h">km/h</string>
+    <string name="meters_second">m/s</string>
+    <string name="steps_unit">passes</string>
+    <string name="cm">cm</string>
+    <string name="meters">m</string>
+    <string name="averageSpeed">Velocitat mitjana</string>
+    <string name="flatSeconds">Pla</string>
+    <string name="laps">Voltes</string>
+    <string name="averageKMPaceSeconds">Ritme</string>
+    <string name="maxPace">Ritme m茅s r脿pid</string>
+    <string name="minPace">Ritme m茅s lent</string>
+    <string name="maxSpeed">M脿xim</string>
+    <string name="caloriesBurnt">Calories</string>
+    <string name="activeSeconds">Actiu</string>
+    <string name="steps">Passes</string>
+    <string name="minAltitude">M铆nim</string>
+    <string name="maxAltitude">M脿xim</string>
+    <string name="descentMeters">En baixada</string>
+    <string name="ascentMeters">En pujada</string>
+    <string name="distanceMeters">Dist脿ncia</string>
+    <string name="device_requires_key">ES REQUEREIX CLAU</string>
+    <string name="require_location_provider">La ubicaci贸 ha d\'estar activada</string>
+    <string name="about_links">Enlla莽os</string>
+    <string name="about_activity_title_generic">Quant a Gadgetbridge</string>
+    <string name="about_version">Versi贸 %s</string>
+    <string name="about_title">Quant a</string>
+    <string name="hr_widget_last_notification">脷ltima notificaci贸</string>
+    <string name="menuitem_stopwatch">Cron貌metre</string>
+    <string name="menuitem_alexa">Alexa</string>
+    <string name="menuitem_takephoto">Comandament de la c脿mera</string>
+    <string name="menuitem_mutephone">Silencia el tel猫fon</string>
+    <string name="menuitem_findphone">Troba el tel猫fon</string>
+    <string name="menuitem_worldclock">Rellotge mundial</string>
+    <string name="menuitem_unknown">Desconegut</string>
+    <string name="menuitem_stress">Estr猫s</string>
+    <string name="menuitem_cycles">Seguiment del cicle</string>
+    <string name="menuitem_breathing">Respiraci贸</string>
+    <string name="devicetype_lefun">Lefun</string>
+    <string name="devicetype_sg2">Lemfo SG2</string>
+    <string name="devicetype_pinetime_jf">PineTime (JF Firmware)</string>
+    <string name="devicetype_tlw64">TLW64</string>
+    <string name="devicetype_amazfit_gts2">Amazfit GTS 2</string>
+    <string name="devicetype_amazfit_gtr2">Amazfit GTR 2</string>
+    <string name="devicetype_amazfit_bips_lite">Amazfit Bip S Lite</string>
+    <string name="devicetype_amazfit_trex">Amazfit T-Rex</string>
+    <string name="devicetype_amazfit_band5">Amazfit Band 5</string>
+    <string name="devicetype_miband5">Mi Band 5</string>
+    <string name="activity_summary_yesterday">Ahir</string>
+    <string name="activity_summary_today">Avui</string>
+    <string name="activity_summary_edit_name_title">Edita l\'etiqueta</string>
+    <string name="activity_summary_detail">Detall d\'activitats esportives</string>
+    <string name="activity_type_badminton">B脿dminton</string>
+    <string name="activity_type_pingpong">Tennis de taula</string>
+    <string name="activity_type_basketball">B脿squet</string>
+    <string name="activity_type_cricket">Criquet</string>
+    <string name="activity_type_rowing_machine">M脿quina de rem</string>
+    <string name="activity_type_soccer">Futbol</string>
+    <string name="activity_type_yoga">Ioga</string>
+    <string name="activity_type_jump_roping">Salt de corda</string>
+    <string name="activity_type_elliptical_trainer">M脿quina el路l铆ptica</string>
+    <string name="activity_type_indoor_cycling">Ciclisme d\'interior</string>
+    <string name="activity_type_swimming_openwater">Nataci贸 (Aig眉es obertes)</string>
+    <string name="charts_legend_heartrate_average">Ritme card铆ac mitj脿</string>
+    <string name="activity_prefs_chart_min_session_length">Durada m铆nima d\'activitat</string>
+    <string name="activity_prefs_chart_max_idle_phase_length">Duraci贸 de la pausa per a separar activitats (minuts)</string>
+    <string name="activity_prefs_chart_min_steps_per_minute_for_run">Passes per minut m铆nimes per a detectar correguda</string>
+    <string name="activity_prefs_chart_min_steps_per_minute">Passes per minut m铆nimes per a detectar activitat</string>
+    <string name="activity_prefs_step_length_cm">Llargada de passa en cm</string>
+    <string name="czesh">Txec</string>
+    <string name="swedish">Suec</string>
+    <string name="hebrew">Hebreu</string>
+    <string name="greek">Grec</string>
+    <string name="hungarian">Hongar猫s</string>
+    <string name="romanian">Roman猫s</string>
+    <string name="lefun_prefs_interface_language_title">Idioma de la interf铆cie</string>
+    <string name="lefun_prefs_antilost_summary">La polsera vibrar脿 si el teu m貌bil es desconnecta de la polsera</string>
+    <string name="lefun_prefs_antilost_title">Antip猫rdua</string>
+    <string name="lefun_prefs_hydration_reminder_interval_title">Interval de recordatori d\'hidrataci贸 (en minuts)</string>
+    <string name="lefun_prefs_hydration_reminder_summary">La polsera vibrar脿 per a recordar-te que beguis aigua</string>
+    <string name="lefun_prefs_hydration_reminder_title">Recordatori d\'hidrataci贸</string>
+    <string name="bip_prefs_shortcuts">Dreceres</string>
+    <string name="movement_intensity">Intensitat de moviment</string>
+    <string name="firmware_update_progress">La c脿rrega 茅s en proc茅s
+\n%1d%% a %.2fkbps (mitjana %.2fkbps)
+\nPart %1d de %1d</string>
+    <string name="devicestatus_upload_failed">Ha fallat la c脿rrega</string>
+    <string name="devicestatus_upload_started">Ha comen莽at la c脿rrega</string>
+    <string name="devicestatus_upload_completed">S\'ha completat la c脿rrega</string>
+    <string name="devicestatus_upload_validating">S\'est脿 validant la c脿rrega</string>
+    <string name="devicestatus_connecting">S\'est脿 connectant l\'aparell</string>
+    <string name="devicestatus_connected">L\'aparell est脿 connectat</string>
+    <string name="devicestatus_disconnecting">L\'aparell s\'est脿 desconnectant!</string>
+    <string name="devicestatus_disconnected">L\'aparell s\'ha desconnectat!</string>
+    <string name="devicestatus_upload_starting">La c脿rrega est脿 comen莽ant</string>
+    <string name="activity_list_summary_activities">Activitats</string>
+    <string name="activity_list_summary_intensity">Moviment
+\nIntensitat</string>
+    <string name="activity_list_summary_active_time">Temps actiu</string>
+    <string name="activity_list_summary_distance">Dist脿ncia</string>
+    <string name="charts_activity_list">Llista d\'activitats</string>
+    <string name="chart_no_active_data">No s\'han detectat activitats.</string>
+    <string name="pref_theme_system">Sistema</string>
+    <string name="fw_upgrade_notice_amazfitx">Sou a punt d\'instal路lar el microprogramari %s en la vostra Amazfit X.
+\n
+\nSi us plau, assegureu-vos d\'instal路lar el fitxer .fw, i despr茅s el fitxer .res. La vostra polsera es reiniciar脿 despr茅s d\'instal路lar el fitxer .fw.
+\n
+\nNota: no heu d\'instal路lar el fitxer .res si 茅s exactament el mateix que el pr猫viament instal路lat.
+\n
+\nPROCEDIU SOTA LA VOSTRA PR脪PIA RESPONSABILITAT!</string>
+    <string name="fw_upgrade_notice_miband5">Sou a punt d\'instal路lar el microprogramari %s en la vostra Mi Band 5.
+\n
+\nSi us plau, assegureu-vos d\'instal路lar el fitxer .fw, i despr茅s el fitxer .res. La vostra polsera es reiniciar脿 despr茅s d\'instal路lar el fitxer .fw.
+\n
+\nNota: no heu d\'instal路lar el fitxer .res si 茅s exactament el mateix que el pr猫viament instal路lat.
+\n
+\nPROCEDIU SOTA LA VOSTRA PR脪PIA RESPONSABILITAT!</string>
+    <string name="fw_upgrade_notice_amazfitvergel">Sou a punt d\'instal路lar el microprogramari %s en la vostra Amazfit Verge Lite.
+\n
+\nSi us plau, assegureu-vos d\'instal路lar el fitxer .fw, i despr茅s el fitxer .res. La vostra polsera es reiniciar脿 despr茅s d\'instal路lar el fitxer .fw.
+\n
+\nNota: no heu d\'instal路lar el fitxer .res si 茅s exactament el mateix que el pr猫viament instal路lat.
+\n
+\nPROCEDIU SOTA LA VOSTRA PR脪PIA RESPONSABILITAT!</string>
+    <string name="fw_upgrade_notice_amazfit_trex">Sou a punt d\'instal路lar el microprogramari %s en la vostra Amazfit T-Rex.
+\n
+\nSi us plau, assegureu-vos d\'instal路lar el fitxer .fw, i despr茅s el fitxer .res. La vostra polsera es reiniciar脿 despr茅s d\'instal路lar el fitxer .fw.
+\n
+\nNota: no heu d\'instal路lar el fitxer .res si 茅s exactament el mateix que el pr猫viament instal路lat.
+\n
+\nPROCEDIU SOTA LA VOSTRA PR脪PIA RESPONSABILITAT!</string>
+    <string name="prefs_autoremove_message">Suprimeix autom脿ticament les notificacions d\'SMS</string>
+    <string name="prefs_key_vibration">Vibraci贸 de les tecles</string>
+    <string name="prefs_autolight">Llum autom脿tica</string>
+    <string name="mi">mi</string>
+    <string name="minutes_mi">min/mi</string>
+    <string name="mi_h">mi/h</string>
+    <string name="ft">ft</string>
+    <string name="device_is_currently_bonded">JA EST脌 APARELLAT</string>
+    <string name="pref_qhybrid_save_raw_activity_files">Guarda els arxius d\'activitat sense processar</string>
+    <string name="widget_settings_select_device_title">Seleccionar aparell</string>
+    <string name="menuitem_sleep">Dormir</string>
+    <string name="menuitem_goal">Objectiu d\'activitat</string>
+    <string name="devicetype_amazfit_vergel">Amazfit Verge Lite</string>
+    <string name="devicetype_waspos">Wasp-os</string>
+    <string name="devicetype_casiogbx100">Casio GBX-100</string>
+    <string name="devicetype_zepp_e">Zepp E</string>
+    <string name="devicetype_amazfit_x">Amazfit X</string>
+    <string name="devicetype_amazfit_gts2e">Amazfit GTS 2e</string>
+    <string name="devicetype_amazfit_gts2_mini">Amazfit GTS 2 Mini</string>
+    <string name="devicetype_amazfit_gtr2e">Amazfit GTR 2e</string>
+    <string name="devicetype_amazfit_bipupro">Amazfit Bip U Pro</string>
+    <string name="devicetype_amazfit_bipu">Amazfit Bip U</string>
+    <string name="devicetype_amazfit_neo">Amazfit Neo</string>
+    <string name="activity_type_strength_training">Entrenament de for莽a</string>
+    <string name="dbmanagementactivity_export_finished">Supressi贸 finalitzada</string>
+    <string name="activity_DB_clean_export_directory_warning_message">De deb貌 voleu esborrar els fitxers del directori d\'exportaci贸/importaci贸\?</string>
+    <string name="activity_DB_clean_export_directory_warning_title">Voleu esborrar els fitxers del directori d\'exportaci贸/importaci贸\?</string>
+    <string name="activity_db_management_clean_export_directory_label">Esborra els fitxers del directori d\'exportaci贸/importaci贸</string>
+    <string name="activity_DB_ShowContentButton">Mostra el contingut del directori d\'exportaci贸/importaci贸</string>
+    <string name="activity_data_management_directory_content_title">Exporta/Importa el contingut del directori</string>
+    <string name="dbmanagementactivity_export_data_title">Voleu exportar les dades\?</string>
+    <string name="dbmanagementactivity_error_cleaning_export_directory">Hi ha hagut un error en esborrar fitxers del directori d\'exportaci贸 %1$s</string>
+    <string name="prefs_charts_tabs_summary">Pestanyes de gr脿fics visibles</string>
+    <string name="prefs_charts_tabs">Pestanyes de gr脿fics</string>
+    <string name="discovery_entered_invalid_authkey">La clau secreta d\'autenticaci贸 que heu introdu茂t no 茅s v脿lida! Mantingueu premut sobre l\'aparell per a editar-la.</string>
+    <string name="power_mode_title">Mode d\'energia del rellotge</string>
+    <string name="pref_title_device_spec_settings_show_raw_graph">Mostra les dades RAW al gr脿fic d\'activitat</string>
+    <string name="pref_summary_connected_advertisement">Fes que l\'aparell sigui visible per Bluetooth incl煤s quan estigui connectat</string>
+    <string name="pref_title_connected_advertisement">Visible mentre estigui connectat</string>
+    <string name="calendar_year">Any</string>
+    <string name="calendar_six_months">6 mesos</string>
+    <string name="calendar_month">Mes</string>
+    <string name="calendar_two_weeks">Dues setmanes</string>
+    <string name="calendar_week">Setmana</string>
+    <string name="calendar_day">Dia</string>
+    <string name="battery_level">Nivell de bateria</string>
+    <string name="battery_detail_activity_title">Informaci贸 de la bateria</string>
+    <string name="prefs_sounds">Sons</string>
+    <string name="watchface_widget_type_date">Data</string>
+    <string name="watchface_widget_type_weather">Temps</string>
+    <string name="watchface_dialog_title_add_widget">Afegeix un giny</string>
+    <string name="fw_upgrade_notice_amazfitneo">Sou a punt d鈥檌nstal路lar el microprogramari %s a la vostra Amazfit Neo.
+\n
+\nLa vostra polsera es reiniciar脿 despr茅s d鈥檌nstal路lar el fitxer .fw.
+\n
+\nPROCEDIU SOTA LA VOSTRA PR脪PIA RESPONSABILITAT!</string>
+    <string name="qhybrid_pref_title_actions">Accions</string>
+    <string name="watchface_widget_type_chance_rain">Possibilitat de pluja</string>
+    <string name="dialog_hide">Amaga</string>
+    <string name="fossil_hr_commute_processing">S\'est脿 processant la sol路licitud鈥�</string>
+    <string name="fossil_hr_synced_activity_data">Dades d\'activitat sincronitzades</string>
+    <string name="watchface_setting_title_wrist_flick">Moviment de canell</string>
+    <string name="prefs_autoheartrate">Freq眉猫ncia card铆aca autom脿tica</string>
+    <string name="blood_pressure">Pressi贸 sangu铆nea</string>
+    <string name="menuitem_temperature">Temperatura</string>
+    <string name="qhybrid_title_file_management">Gesti贸 de fitxers</string>
+    <string name="qhybrid_calibration_counterclockwise">En sentit antihorari</string>
+    <string name="qhybrid_calibration_clockwise">En sentit horari</string>
+    <string name="appmanager_app_edit">Edita</string>
+    <string name="watchface_widget_type_heart_rate">Ritme card铆ac</string>
+    <string name="prefs_sleep_time">Temps de son</string>
+    <string name="button_watchface_edit_name">Edita el nom</string>
+    <string name="button_watchface_add_widget">Afegeix un giny</string>
+    <string name="prefs_activity_in_device_card_sleep_title">Son</string>
+    <string name="prefs_activity_in_device_card_sleep_title_summary">Mostra la durada del son</string>
+    <string name="prefs_activity_in_device_card_steps_title_summary">Mostra les passes totals</string>
+    <string name="calendar_three_months">3 mesos</string>
+    <string name="pref_header_other">Altres</string>
+    <string name="pref_header_system">Sistema</string>
+    <string name="pref_header_equalizer">Equalitzador</string>
+    <string name="pref_message_privacy_mode_off">Mostra el contingut</string>
+    <string name="pref_message_privacy_mode_complete">Amaga el contingut</string>
+    <string name="pref_applications_settings">Llistat d\'aplicacions</string>
+    <string name="pref_title_huami_force_new_protocol">Nou protocol d\'autenticaci贸</string>
+    <string name="show_ongoing_activity">Mostra una finestra emergent d\'activitat en curs</string>
+    <string name="getting_heart_rate">Mesurant</string>
+    <string name="devicetype_miband6">Mi Band 6</string>
+    <string name="devicetype_um25">UM-25</string>
+    <string name="devicetype_fitpro">FitPro</string>
+    <string name="menuitem_events">Esdeveniments</string>
+    <string name="menuitem_widgets">Ginys</string>
+    <string name="menuitem_flashlight">Llanterna</string>
+    <string name="swim_style">estil de nataci贸</string>
+    <string name="fossil_hr_edit_action_delete">esborra</string>
+    <string name="watchface_dialog_widget_preset_left">Esquerra</string>
+    <string name="watchface_dialog_widget_preset_right">Dreta</string>
+    <string name="watchface_dialog_widget_color">Color:</string>
+    <string name="watchface_dialog_widget_color_white">Blanc</string>
+    <string name="watchface_widget_type_battery">Bateria</string>
+    <string name="prefs_autoheartrate_measurement">Mesures autom脿tiques del ritme card铆ac</string>
+    <string name="sony_equalizer">Equalitzador</string>
+    <string name="sony_equalizer_preset_off">Apagat</string>
+    <string name="qhybrid_title_apps">Aplicacions</string>
+    <string name="fossil_hr_new_action_cancel">cancel路la</string>
+    <string name="fossil_hr_new_action">Nova acci贸</string>
+    <string name="pref_title_equalizer_soft">Suau</string>
+    <string name="pref_title_equalizer_dynamic">Din脿mic</string>
+    <string name="pref_title_equalizer_clear">Clar</string>
+    <string name="prefs_equalizer">Equalitzador</string>
+    <string name="prefs_galaxy_buds_experimental">Experimental</string>
+    <string name="prefs_left">Esquerra</string>
+    <string name="prefs_right">Dreta</string>
+    <string name="activity_filter_individual_items">Elements seleccionats individualment</string>
+    <string name="strokes_second">b/s</string>
+    <string name="swimStyle">estil de nataci贸</string>
+    <string name="swolfIndex">铆ndex swolf</string>
+    <string name="set">Establir</string>
+    <string name="menuitem_barometer">Bar貌metre</string>
+    <string name="strokes_unit">b</string>
+    <string name="Strokes">Bra莽ades</string>
+    <string name="qhybrid_title_calibration">Calibratge</string>
+    <string name="qhybrid_title_background_image">Imatge de fons</string>
+    <string name="watchface_dialog_widget_preset_top">Superior</string>
+    <string name="watchface_dialog_widget_preset_bottom">Inferior</string>
+    <string name="watchface_dialog_widget_color_black">Negre</string>
+    <string name="watchface_widget_type_calories">Calories</string>
+    <string name="pref_title_equalizer_normal">Normal</string>
+    <string name="sony_ambient_sound">Mode</string>
+    <string name="sony_ambient_sound_off">Apagat</string>
+    <string name="watchface_setting_title_power_saving">Estalvi d\'energia</string>
+    <string name="prefs_notifications_enable">Activa les notificacions</string>
+    <string name="devicetype_amazfit_trex_pro">Amazfit T-Rex Pro</string>
+    <string name="button_watchface_select_image">Canvia la imatge de fons</string>
+    <string name="watchface_setting_wrist_flick_duration">Durada (en ms):</string>
+    <string name="watchface_dialog_widget_y_coordinate">Coordenada Y (m脿xim 240):</string>
+    <string name="watchface_setting_wrist_flick_move_relative">Mou les mans en relaci贸 amb el temps:</string>
+    <string name="sony_sound_position_off">Apagat</string>
+    <string name="sony_sound_position_front">Frontal</string>
+    <string name="sony_surround_mode_off">Apagat</string>
+    <string name="sony_surround_mode_arena">Arena</string>
+    <string name="sony_surround_mode_club">Club</string>
+    <string name="sony_equalizer_preset_manual">Manual</string>
+    <string name="sony_equalizer_band_400">400</string>
+    <string name="sony_equalizer_band_1000">1k</string>
+    <string name="sony_equalizer_band_2500">2.5k</string>
+    <string name="sony_equalizer_band_6300">6.3k</string>
+    <string name="sony_equalizer_band_16000">16k</string>
+    <string name="title_activity_notification_management">Configuraci贸 de les notificacions</string>
+    <string name="fw_upgrade_notice_miband6">Sou a punt d\'instal路lar el microprogramari %s en la vostra Mi Band 6.
+\n
+\nSi us plau, assegureu-vos d\'instal路lar el fitxer .fw, i despr茅s el fitxer .res. La vostra polsera es reiniciar脿 despr茅s d\'instal路lar el fitxer .fw.
+\n
+\nNota: no heu d\'instal路lar el fitxer .res si 茅s exactament el mateix que el pr猫viament instal路lat.
+\n
+\nPROCEDIU SOTA LA VOSTRA PR脪PIA RESPONSABILITAT!</string>
+    <string name="watchface_widget_type_steps">Passes</string>
+    <string name="watchface_dialog_widget_type">Tipus:</string>
+    <string name="watchface_dialog_widget_x_coordinate">Coordenada X (m脿xim 240):</string>
+    <string name="title_activity_set_reminders">Configureu els recordatoris</string>
+    <string name="controlcenter_start_configure_reminders">Configureu els recordatoris</string>
+    <string name="reminder_time">Hora</string>
+    <string name="reminder_time_once">%1$s, una vegada</string>
+    <string name="reminder_time_every_week">%1$s, cada setmana</string>
+    <string name="reminder_once">una vegada</string>
+    <string name="reminder_every_year">Cada any</string>
+    <string name="reminder_delete_confirm_title">Esborrar recordatori</string>
+    <string name="reminder_delete_confirm_description">Esteu segur que voleu esborrar el recordatori\?</string>
+    <string name="reminder_no_free_slots_title">No hi ha espais lliures</string>
+    <string name="reminder_no_free_slots_description">El dispositiu no t茅 espais lliures per a recordatoris (espais totals: %1$s)</string>
+    <string name="title_activity_reminder_details">Detalls del recordatori</string>
+    <string name="heart_rate_result">Resultats de la mesura</string>
+    <string name="miband_prefs_reserve_reminder_calendar">Recordatoris a reservar per esdeveniments futurs</string>
+    <string name="prefs_reserve_reminder_calendar_summary">Nombre d\'esdeveniments del calendari que seran sincronitzats</string>
+    <string name="prefs_sounds_summary">Configureu quan l\'aparell pitar脿</string>
+    <string name="prefs_fm_presets_presets">Preconfiguracions</string>
+    <string name="device_card_activity_card_title">Informaci贸 d\'activitat a la targeta de l\'aparell</string>
+    <string name="pref_header_notification_application_settings">Configuraci贸 per aplicaci贸</string>
+    <string name="pref_title_notification_use_as">Useu la llista d\'aplicacions per a 鈥�</string>
+    <string name="pref_title_notification_use_as_deny">Rebutgeu les notificacions de les aplicacions triades</string>
+    <string name="pref_title_notification_use_as_allow">Permeteu les notificacions de les aplicacions triades</string>
+    <string name="notification_channel_transfer_name">Transfer猫ncia de dades</string>
+    <string name="notification_channel_low_battery_name">Bateria baixa</string>
+    <string name="reminder_every_day">Cada dia</string>
+    <string name="reminder_message">Missatge</string>
+    <string name="reminder_time_every_day">%1$s, cada dia</string>
+    <string name="prefs_activity_in_device_card_distance_title_summary">La dist脿ncia es calcula a partir de les passes i la llargada de la passa (configurable a 鈥淐onfiguraci贸 - Sobre tu鈥�)</string>
+    <string name="pref_theme_black_background">Useu fons negre amb el Tema Fosc</string>
+    <string name="pref_summary_huami_force_new_protocol">Activeu en cas que l\'aparell no es connecti despr茅s d\'una actualitzaci贸 de microprogramari</string>
+    <string name="reminder_repeat">Repetir</string>
+    <string name="reminder_date">Data</string>
+    <string name="reminder_time_every_month">%1$s, cada mes</string>
+    <string name="reminder_time_every_year">%1$s, cada any</string>
+    <string name="reminder_every_month">Cada mes</string>
+    <string name="reminder_every_week">Cada setmana</string>
+    <string name="menuitem_pomodoro">Seguiment Pomodoro</string>
+    <string name="dbmanagementactivity_export_confirmation">Voleu exportar dades\? Les dades d鈥檃ctivitat exportades anteriorment (si n鈥檋i ha) i les prefer猫ncies se sobreescriuran.</string>
+    <string name="devicetype_sony_wh_1000xm3">Sony WH-1000XM3</string>
+    <string name="prefs_fm_preset_instructions">Premeu una estona el bot贸 per desar la preconfiguraci贸</string>
+    <string name="check_all_applications">Activar totes les aplicacions</string>
+    <string name="uncheck_all_applications">Desactivar totes les aplicacions</string>
+    <string name="averageStrokeDistance">Llargada mitjana d\'una bra莽ada</string>
+    <string name="toast_app_must_not_be_selected">L\'aplicaci贸 no ha d\'estar seleccionar per a ser configurada</string>
+    <string name="toast_app_must_be_selected">L\'aplicaci贸 ha d\'estar seleccionar per a ser configurada</string>
+    <string name="qhybrid_calibration_10_steps">10 passes</string>
+    <string name="qhybrid_calibration_100_steps">100 passes</string>
+    <string name="qhybrid_pref_summary_actions">Accions per a l\'aplicaci贸 Commute</string>
+    <string name="pref_title_developer_settings">Configuraci贸 per a programadors</string>
+    <string name="qhybrid_summary_file_management">Envieu i descarregueu fitxers</string>
+    <string name="pref_summary_physical_buttons">Configureu la funcionalitat dels botons f铆sics del rellotge</string>
+    <string name="bpm">bpm</string>
+    <string name="qhybrid_title_apps_management">Gesti贸 d\'apps</string>
+    <string name="fossil_hr_edit_action">Acci贸 d\'editar</string>
+    <string name="pref_summary_canned_messages_set">Envieu els missatges configurats m茅s avall al vostre aparell</string>
+    <string name="pref_summary_developer_settings">Configuraci贸 i funcionalitat usada per programadors</string>
+    <string name="qhybrid_pref_title_external_intents">Permetre el intents externs perillosos</string>
+    <string name="devicetype_smaq2oss">SMA-Q2 OSS</string>
+    <string name="gpx_receiver_overwrite_some_files">Algun(s) fitxer(s) ja existeixen. Sobreescriure\'ls\?</string>
+    <string name="ignore_bonded_devices_description">Activant aquesta opci贸 far脿 que s\'ignorin els aparells que ja han estat vinculats/aparellats a l\'escanejar</string>
+    <string name="gpx_receiver_files_received">Fitxer(s) GPX rebut(s):</string>
+    <string name="fossil_hr_auth_failed">Autenticaci贸 fallada, funcionalitat limitada</string>
+    <string name="fossil_hr_unavailable_unauthed">No disponible en mode no autenticat</string>
+    <string name="qhybrid_calibration_1_step">1 passa</string>
+    <string name="pref_check_permission_status_summary">Comproveu i demaneu els permisos que faltin tot i que poden no ser necessitats en aquest moment. Desactiveu aix貌 nom茅s si el vostre aparell realment no suporta cap d\'aquestes caracter铆stiques. No donar un perm铆s pot causar problemes!</string>
+    <string name="fossil_hr_warning_firmware_too_new">Algunes funcions estan desactivades perqu猫 el microprogramari del rellotge 茅s massa nou</string>
+    <string name="companiondevice_pairing_details">Activa el suport de la nova API de CompanionDevice (nom茅s t茅 efecte en Android 8 o superior) que incrementar脿 la fiabilitat si el servei s\'ha de reiniciar en segon pla; requereix re-aparellar amb Gadgetbridge per tenir efecte</string>
+    <string name="error_version_check_extreme_caution">COMPTE: Error al comprovar la informaci贸 de versi贸! No haur铆eu de continuar! Nom de versi贸 vist: \"%s\"</string>
+    <string name="averageStrokesPerSecond">Mitjana de bra莽ades</string>
+    <string name="fossil_hr_commute_actions_explanation">Les accions configurades aqu铆 apareixeran a l\'aplicaci贸 Commute del rellotge. Llegiu la wiki per obtenir informaci贸 sobre com gestionar els intents produ茂ts per aquestes accions.</string>
+    <string name="pref_summary_canned_messages_dismisscall">Ignoreu les trucades del rellotge amb un missatge SMS</string>
+    <string name="pref_title_physical_buttons">Botons f铆sics</string>
+    <string name="averageAltitude">Mitjana</string>
+    <string name="minSpeed">M铆nim</string>
+    <string name="maxHR">Ritme card铆ac m脿xim</string>
+    <string name="averageCadence">Cad猫ncia mitjana</string>
+    <string name="maxCadence">Cad猫ncia m脿xima</string>
+    <string name="minCadence">Cad猫ncia m铆nima</string>
+    <string name="spm">passes/min</string>
+    <string name="watchface_dialog_widget_presets">Preconfiguracions de posici贸:</string>
+    <string name="ignore_bonded_devices">Ignora els dispositius vinculats</string>
+    <string name="minHR">Ritme card铆ac m铆nim</string>
+    <string name="prefs_sleep_time_label">Definiu les hores de son</string>
+    <string name="nothing_prefs_inear_summary">Reprodueix / pausa la m煤sica segons si porteu els auriculars</string>
+    <string name="enable_vibrations_summary">Vibracions per a trucades, missatges, notificacions i molt m茅s</string>
+    <string name="button_watchface_save_apply">Deseu i apliqueu</string>
+    <string name="prefs_sleep_time_summary">Especifica els moments en qu猫 es registra el son</string>
+    <string name="enable_notifications_summary">Notificacions de trucades, missatges i m茅s</string>
+    <string name="prefs_autoheartrate_summary">Mesures peri貌diques del ritme card铆ac durant el dia i tamb茅 durant el son</string>
+    <string name="prefs_autoheartrate_sleep">Prendre mesures durant el son</string>
+    <string name="watchface_setting_desc_wrist_flick">(per desactivar-lo completament, habiliteu el moviment relatiu i establiu tots els valors a 0)</string>
+    <string name="button_watchface_preview">Previsualitzaci贸 del rellotge</string>
+    <string name="unknown">Desconegut</string>
+    <string name="start">Inicia</string>
+    <string name="activity_summary_detail_editing_gpx_track">Edici贸 de la pista GPX enlla莽ada</string>
+    <string name="nothing_prefs_inear_title">Detecci贸 intraorella</string>
+    <string name="prefs_ambient_sound">So ambiental</string>
+    <string name="pref_anc_optimizer_state_pressure">Pressi贸 atmosf猫rica</string>
+    <string name="distance_format_feet">### ft</string>
+    <string name="distance_format_miles">###.# mi</string>
+    <string name="distance_format_kilometers">###.# km</string>
+    <string name="discover_unsupported_devices">Detecta aparells no suportats</string>
+    <string name="watchface_toast_settings_incomplete">Configuraci贸 incompleta, giny no afegit</string>
+    <string name="prefs_vibration_enable">Activa la vibraci贸</string>
+    <string name="prefs_autoheartrate_interval">Freq眉猫ncia de mesures</string>
+    <string name="left_earbud">Auricular esquerre</string>
+    <string name="right_earbud">Auricular dret</string>
+    <string name="sony_sound_position_front_left">Frontal esquerra</string>
+    <string name="distance_format_meters">### m</string>
+    <string name="watchface_dialog_widget_width">Ample:</string>
+    <string name="menuitem_menu">Men煤</string>
+    <string name="pref_activity_recognition_mode_auto">autom脿tic</string>
+    <string name="pref_activity_recognition_mode_ask">pregunta</string>
+    <string name="add_test_device">Afegeix aparell de prova</string>
+    <string name="controlcenter_power_off_confirm_description">Est脿s segur que vols apagar l\'aparell\?</string>
+    <string name="pref_title_message_privacy_mode">Mode de privacitat de missatges</string>
+    <string name="devicetype_amazfit_pop">Amazfit Pop</string>
+    <string name="devicetype_amazfit_pop_pro">Amazfit Pop Pro</string>
+    <string name="device_unsupported">NO SUPORTAT</string>
+    <string name="watchface_widget_type_2nd_tz">2n fus horari</string>
+    <string name="pref_header_sony_device_info">Informaci贸 de l\'aparell</string>
+    <string name="sony_ambient_sound_wind_noise_reduction">Reducci贸 de soroll de vent</string>
+    <string name="sony_anc_optimize_title">Optimitza</string>
+    <string name="watchface_widget_type_active_mins">Minuts actiu</string>
+    <string name="activity_type_hiking">Senderisme</string>
+    <string name="pref_title_ping_tone">To de ping</string>
+    <string name="device_card_activity_card_title_summary">Escull quins detalls d\'activitat es mostren a la targeta de l\'aparell</string>
+    <string name="prefs_activity_in_device_card_title">Mostra informaci贸 d\'activitat a la targeta de l\'aparell</string>
+    <string name="prefs_activity_in_device_card_title_summary">Mostra les passes, la dist脿ncia o el son actuals a la targeta de l\'aparell</string>
+    <string name="devicetype_sonyswr12">Sony SWR12</string>
+    <string name="swolf_index">铆ndex swolf</string>
+    <string name="discovery_bluetooth_scan">Escaneig Bluetooth:</string>
+    <string name="discovery_bluetooth_le_scan">Escaneig Bluetooth LE:</string>
+    <string name="activity_prefs_discovery_pairing">Opcions de detecci贸 i emparellament</string>
+    <string name="prefs_galaxy_touch_options">Opcions t脿ctils</string>
+    <string name="pref_activity_recognition_mode_none">cap</string>
+    <string name="prefs_ambient_volume">Volum ambiental</string>
+    <string name="activity_db_management_autoexport_enabled_yes">L\'exportaci贸 autom脿tica est脿 activada.</string>
+    <string name="autoExport_lastTime_label">脷ltima exportaci贸 autom脿tica: %1$s</string>
+    <string name="activity_db_management_autoexport_enabled_no">L\'exportaci贸 autom脿tica no est脿 activada.</string>
+    <string name="activity_db_management_autoexport_scheduled_yes">L\'exportaci贸 autom脿tica est脿 programada (originalment) per a %1$s</string>
+    <string name="activity_db_management_autoexport_scheduled_no">L\'exportaci贸 autom脿tica no ha sigut programada.</string>
+    <string name="menuitem_nothing">Res</string>
+    <string name="qhybrid_summary_calibration">Calibra les manetes del rellotge</string>
+    <string name="watchface_setting_display_refresh_partial">Actualitzaci贸 parcial (en minuts):</string>
+    <string name="devicetype_galaxybuds">Galaxy Buds</string>
+    <string name="devicetype_galaxybuds_live">Galaxy Buds Live</string>
+    <string name="devicetype_nothingear1">Nothing Ear (1)</string>
+    <string name="devicetype_bose_qc35">Bose QC35</string>
+    <string name="devicetype_vesc">VESC</string>
+    <string name="controlcenter_power_off_confirm_title">Apaga</string>
+    <string name="pref_title_notifications_generic_settings">Configuraci贸 de notificacions Android</string>
+    <string name="controlcenter_power_off">Apaga</string>
+    <string name="activity_summary_detail_clear_gpx_track">Neteja la pista GPX</string>
+    <string name="devicetype_sony_wf_sp800n">Sony WF-SP800N</string>
+    <string name="menuitem_email">Correu electr貌nic</string>
+    <string name="watchface_install_info">Sou a punt d\'instal路lar la seg眉ent car脿tula:
+\n
+\n%1$s
+\nVersi贸 %2$s per %3$s
+\n</string>
+    <string name="devicetype_domyos_t540">Domyos T540</string>
+    <string name="activity_type_climbing">Escalada</string>
+    <string name="activity_summary_detail_select_gpx_track">Selecciona la pista GPX</string>
+    <string name="devicetype_sony_wh_1000xm4">Sony WH-1000XM4</string>
+    <string name="about_hash">Confirma %s</string>
+    <string name="watchface_setting_display_refresh_full">Actualitzaci贸 completa (en minuts):</string>
+    <string name="sony_ambient_sound_noise_cancelling">Cancel路laci贸 de soroll</string>
+    <string name="sony_sound_position_front_right">Frontal dreta</string>
+    <string name="gpx_receiver_activity_title">Receptor GPX Gadgetbridge</string>
+    <string name="prefs_active_noise_cancelling">Cancel路laci贸 de soroll activa</string>
+    <string name="pref_title_upper_button_function_double">Bot贸 superior doble</string>
+    <string name="pref_title_middle_button_function_double">Bot贸 central doble</string>
+    <string name="prefs_events_forwarding_summary">Fes servir events del dispositiu per executar accions i emissions d\'Android</string>
+    <string name="prefs_operating_sounds">Sons de funcionament</string>
+    <string name="pref_title_middle_button_function_long">Bot贸 central llarg</string>
+    <string name="mi2_prefs_do_not_disturb_lift_wrist">Encendre la pantalla al agafar el dispositiu amb \"No molestar\"</string>
+    <string name="pref_title_upper_button_function_long">Bot贸 superior llarg</string>
+    <string name="pref_title_lower_button_function_double">Bot贸 inferior doble</string>
+    <string name="maxStride">Gambada m脿xima</string>
+    <string name="totalStride">Total recorregut</string>
+    <string name="prefs_fake_ring_duration">Timbre continu fals</string>
+    <string name="pref_qhybrid_title_widget_draw_circles">Dibuixar cercles de widget</string>
+    <string name="minStride">Gambada m铆nima</string>
+    <string name="pref_title_middle_button_function_short">Bot贸 central curt</string>
+    <string name="pref_title_lower_button_function_short">Bot贸 inferior curt</string>
+    <string name="pref_title_upper_button_function_short">Bot贸 superior curt</string>
+    <string name="qhybrid_title_watchface">Configuraci贸 de Watchface</string>
+    <string name="kind_agps_bundle">Paquet AGPS</string>
+    <string name="pref_title_lower_button_function_long">Bot贸 inferior llarg</string>
+    <string name="activity_db_management_autoexport_location">No s\'ha ent茅s la ubicaci贸. 脡s possible que sigui un problema de permissos del nou sistema Android. El m茅s probable 茅s que l\'autoexportaci贸 de dades no funcioni ara.</string>
+    <string name="activity_db_management_clean_export_directory_text">Els fitxers exportats a la carpeta d\'Exportar/Importar poden ser llegits per qualsevol aplicaci贸 al teu dispositiu. 脡s possible que vulguis suprimir aquests fixters despr茅s de sincronitzar o fer una copia de seguretat. Assegura\'t de fer una copia abans d\'esborrar-los. Els fitxers GPX, les subcarpetes i la base de dades auto-exportada (si existeix) no s\'esborrar脿. La carpeta d\'Exportar/Importar es troba a:</string>
+    <string name="companiondevice_pairing">Emparellament de dispositius de companyia</string>
+    <string name="qhybrid_pref_summary_external_intents">Permetre que altres aplicacions d\'Android puguin sobre/escriure fitxers</string>
+    <string name="averageStride">Mitjana d\'avan莽ada</string>
+    <string name="about_activity_title_banglejs_main">Sobre Bangle.js Gadgetbridge</string>
+    <string name="about_description_banglejs_main">L\'aplicaci贸 complement脿ria d\'Android per a Bangle.js est脿 constru茂da fonamentant-se en projecte Gadgetbridge, amb l\'afegit d\'acc茅s a Internet .
+\n
+\nA causa de les pol铆tiques de la botiga del Google Play, no se\'ns permet incloure un enlla莽 per fer donatius a la mateixa aplicaci贸, per貌 si us agrada aquesta aplicaci贸 considereu fer una donaci贸 a trav茅s de la p脿gina d\'inici de Gadgetbridge a continuaci贸.</string>
+    <string name="gadgetbridge_running_banglejs_main">Bangle.js funcionant</string>
+    <string name="application_name_banglejs_nopebble">Bangle.js Gadgetbridge</string>
+    <string name="title_activity_controlcenter_banglejs_nopebble">Bangle.js Gadgetbridge</string>
+    <string name="about_activity_title_banglejs_nopebble">Sobre Bangle.js Gadgetbridge</string>
+    <string name="application_name_banglejs_main">Bangle.js Gadgetbridge</string>
+    <string name="title_activity_controlcenter_banglejs_main">Bangle.js Gadgetbridge</string>
+    <string name="appmanager_watchface_activate">Activa</string>
+    <string name="custom">Personalitzat</string>
+    <string name="accuracy">Precisi贸</string>
+    <string name="gps_bds">GPS + BDS</string>
+    <string name="gps">GPS</string>
+    <string name="application_name_main_nightly">Gadgetbridge (Nightly)</string>
+    <string name="gadgetbridge_running_main_nightly">GB Nightly funcionant</string>
+    <string name="application_name_main_nopebble">Gadgetbridge (Nightly, Sense prove茂dor de Pebble)</string>
+    <string name="title_activity_controlcenter_main_nopebble">Gadgetbridge Nightly Sense Pebble</string>
+    <string name="about_activity_title_main_nopebble">Sobre Gadgetbridge Nightly Sense Pebble</string>
+    <string name="gadgetbridge_running_main_nopebble">GB Nightly Sense Pebble funcionant</string>
+    <string name="about_activity_title_banglejs_nightly">Sobre Bangle.js Gadgetbridge (Nightly)</string>
+    <string name="title_activity_controlcenter_banglejs_nightly">Bangle.js Gadgetbridge (Nightly)</string>
+    <string name="balanced">Equilibrat</string>
+    <string name="power_saving">Estalvi d\'energia</string>
+    <string name="about_description_banglejs_nightly">L\'aplicaci贸 complement脿ria d\'Android per a Bangle.js est脿 constru茂da fonamentant-se en projecte Gadgetbridge, amb l\'afegit d\'acc茅s a Internet .
+\n
+\nA causa de les pol铆tiques de la botiga del Google Play, no se\'ns permet incloure un enlla莽 per fer donatius a la mateixa aplicaci贸, per貌 si us agrada aquesta aplicaci贸 considereu fer una donaci贸 a trav茅s de la p脿gina d\'inici de Gadgetbridge a continuaci贸.</string>
+    <string name="about_activity_title_main_nightly">Sobre Gadgetbridge Nightly</string>
+    <string name="gps_gnolass">GPS + GNOLASS</string>
+    <string name="appmanager_app_share">Comparteix</string>
+    <string name="fw_upgrade_notice_amazfit_gts3">Est脿 a punt d\'instal路lar %s microprogramari al seu Amazfit GTS 3.
+\n
+\nEl seu aparell es reiniciar脿 despr茅s d\'instal路lar l\'arxiu .zip.
+\n
+\nPROCEDEIXI SOTA LA SEVA RESPONSABILITAT</string>
+    <string name="fw_upgrade_notice_amazfit_gts4">Sou a punt d鈥檌nstal路lar el microprogramari %s a la vostra Amazfit GTS 4.
+\n
+\nLa vostra polsera es reiniciar脿 despr茅s d鈥檌nstal路lar el fitxer .zip.
+\n
+\nPROCEDIU SOTA LA VOSTRA PR脪PIA RESPONSABILITAT!</string>
+    <string name="gps_galileo">GPS + GALILEO</string>
+    <string name="appmanager_app_start">Inicia</string>
+    <string name="fw_upgrade_notice_amazfit_gts4_mini">Sou a punt d鈥檌nstal路lar el microprogramari %s a la vostra Amazfit GTS 4 Mini.
+\n
+\nLa vostra polsera es reiniciar脿 despr茅s d鈥檌nstal路lar el fitxer .zip.
+\n
+\nPROCEDIU SOTA LA VOSTRA PR脪PIA RESPONSABILITAT!</string>
+    <string name="application_name_banglejs_nightly">Bangle.js Gadgetbridge (Nightly)</string>
+    <string name="gadgetbridge_running_banglejs_nightly">Bangle.js Nightly funcionant</string>
+    <string name="title_activity_controlcenter_main_nightly">Gadgetbridge Nightly</string>
+    <string name="action_changelog">Registre de canvis</string>
+    <string name="appmanager_download_started">Desc脿rrega de la App iniciada</string>
+    <string name="appmanager_item_outdated">(Obsolet)</string>
+    <string name="fw_upgrade_notice_miband7">Est脿 a punt d\'instal路lar %s microprogramari al seu Xiaomi Smart Band 7.
+\n
+\nEl seu Band 7 es reiniciar脿 despr茅s d\'instal路lar l\'arxiu .zip.
+\n
+\nPROCEDEIXI SOTA LA SEVA RESPONSABILITAT</string>
+    <string name="all_satellites">Tots els sat猫l路lits</string>
+    <string name="accuracy_first">Precisi贸 primer</string>
+    <string name="fw_upgrade_notice_amazfitbip3pro">Est脿 a punt d\'instal路lar %s microprogramari al seu dispositiu Amazfit Bip 3 Pro.
+\n
+\nSi us plau, asseguri\'s d\'instal路lar l\'arxiu. fw i seguidament l\'arxiu. res. El seu rellotge es reiniciar脿 despr茅s de l\'instal路laci贸 de l\'arxiu. fw.
+\n
+\nNota: No 茅s necessari instal路lar .res si 茅s exactament igual que l\'instal路lat pr猫viament.
+\n
+\nPROCEDEIXI SOTA LA SEVA RESPONSABILITAT.</string>
+    <string name="appmanager_app_download">Descarregar a cache</string>
+    <string name="debugactivity_confirm_remove_device_preferences">Aix貌 reestablir脿 les prefer猫ncies del dispositiu per a tots els dispositius, n\'est脿 segur\?</string>
+    <string name="debugactivity_confirm_remove_device_preferences_title">Eliminar prefer猫ncies del dispositiu\?</string>
+    <string name="appmanager_downloaded_to_cache">Descarregat %s a cache</string>
+    <string name="appmanager_download_app_error">Error descarregant la app</string>
+    <string name="speed_first">Velocitat primer</string>
+</resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml
new file mode 100644
index 0000000..74d05ac
--- /dev/null
+++ b/app/src/main/res/values-cs/strings.xml
@@ -0,0 +1,3699 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources xmlns:tools="http://schemas.android.com/tools">
+    <string name="application_name_generic">Gadgetbridge</string>
+    <string name="title_activity_controlcenter_generic">Gadgetbridge</string>
+    <string name="action_settings">Nastaven铆</string>
+    <string name="action_debug">Lad臎n铆</string>
+    <string name="action_quit">Ukon膷it</string>
+    <string name="controlcenter_fetch_activity_data">Synchronizovat</string>
+    <string name="controlcenter_find_device">Naj铆t za艡铆zen铆</string>
+    <string name="controlcenter_take_screenshot">Po艡铆dit sn铆mek obrazovky</string>
+    <string name="controlcenter_disconnect">Odpojit</string>
+    <string name="controlcenter_delete_device">Odstranit za艡铆zen铆</string>
+    <string name="controlcenter_delete_device_name">Odstranit %1$s</string>
+    <string name="controlcenter_delete_device_dialogmessage">Odstran铆 za艡铆zen铆 a vyma啪e v拧echna data!</string>
+    <string name="controlcenter_navigation_drawer_open">Otev艡铆t naviga膷n铆 li拧tu</string>
+    <string name="controlcenter_navigation_drawer_close">Zav艡铆t naviga膷n铆 li拧tu</string>
+    <string name="controlcenter_snackbar_need_longpress">Podr啪te d茅le pro odpojen铆</string>
+    <string name="controlcenter_snackbar_disconnecting">Odpojuji</string>
+    <string name="controlcenter_snackbar_connecting">P艡ipojuji鈥�</string>
+    <string name="controlcenter_snackbar_requested_screenshot">Po艡izov谩n铆 sn铆mku obrazovky za艡铆zen铆</string>
+    <string name="title_activity_debug">Lad臎n铆</string>
+    <!--Strings related to AppManager-->
+    <string name="title_activity_appmanager">Spr谩va aplikac铆</string>
+    <string name="appmanager_cached_watchapps_watchfaces">Aplikace v cache</string>
+    <string name="appmanager_installed_watchapps">Nainstalovan茅 aplikace</string>
+    <string name="appmanager_installed_watchfaces">Nainstalovan茅 cifern铆ky</string>
+    <string name="appmananger_app_delete">Odstranit</string>
+    <string name="appmananger_app_delete_cache">Odstranit z cache</string>
+    <string name="appmananger_app_reinstall">P艡einstalovat</string>
+    <string name="appmanager_app_openinstore">Hledat v Pebble appstore</string>
+    <string name="appmanager_health_activate">Aktivovat</string>
+    <string name="appmanager_health_deactivate">Deaktivovat</string>
+    <string name="appmanager_hrm_activate">Zapnout m臎艡en铆 pulzu</string>
+    <string name="appmanager_hrm_deactivate">Vypnout m臎艡en铆 pulzu</string>
+    <string name="appmanager_weather_activate">Aktivovat syst茅movou aplikaci pro po膷as铆</string>
+    <string name="appmanager_weather_deactivate">Deaktivovat syst茅movou aplikaci pro po膷as铆</string>
+    <string name="appmanager_weather_install_provider">Nainstalujte aplikaci Weather Notifications</string>
+    <string name="app_configure">Nastavit</string>
+    <string name="app_move_to_top">Posunout nahoru</string>
+    <!--Strings related to FwAppInstaller-->
+    <string name="title_activity_fw_app_insaller">Instal谩tor FW/aplikac铆</string>
+    <string name="fw_upgrade_notice">Chyst谩te se nainstalovat %s.</string>
+    <string name="fw_multi_upgrade_notice">Chyst谩te se nainstalovat firmware %1$s a %2$s, nam铆sto firmwaru, kter媒 se aktu谩ln臎 nach谩z铆 na va拧em Mi Band.</string>
+    <string name="miband_firmware_known">Tento firmware byl otestov谩n a je kompatibiln铆 s aplikac铆 Gadgetbridge.</string>
+    <string name="miband_firmware_unknown_warning">Tento firmware nebyl otestov谩n a nemus铆 b媒t kompatibiln铆 s aplikac铆 Gadgetbridge.
+\n
+\nNEDOPORU膶UJEME jej flashovat!</string>
+    <string name="miband_firmware_suggest_whitelist">Pokud p艡esto chcete pokra膷ovat a bude-li v拧e spr谩vn臎 fungovat i po aktualizaci, oznamte pros铆m v媒voj谩艡暖m Gadgetbridge, aby ozna膷ili verzi firmwaru %s jako funk膷n铆.</string>
+    <!--Strings related to Settings-->
+    <string name="title_activity_settings">Nastaven铆</string>
+    <string name="pref_header_general">Obecn谩 nastaven铆</string>
+    <string name="pref_title_general_autoconnectonbluetooth">P艡ipojit Gadgetbridge k za艡铆zen铆m p艡i zapnut铆 Bluetooth</string>
+    <string name="pref_title_general_autostartonboot">Spou拧t臎t automaticky</string>
+    <string name="pref_title_general_autoreconnect">Automaticky znovu p艡ipojit</string>
+    <string name="pref_title_audio_player">Preferovan媒 p艡ehr谩va膷 hudby</string>
+    <string name="pref_default">V媒choz铆</string>
+    <string name="pref_header_datetime">Datum a 膷as</string>
+    <string name="pref_title_datetime_syctimeonconnect">Synchronizovat 膷as</string>
+    <string name="pref_summary_datetime_syctimeonconnect">Synchronizovat 膷as b臎hem p艡ipojen铆 k za艡铆zen铆m, p艡i zm臎n臎 膷asu v Androidu a periodicky</string>
+    <string name="pref_title_theme">Motiv</string>
+    <string name="pref_theme_light">Sv臎tl媒</string>
+    <string name="pref_theme_dark">Tmav媒</string>
+    <string name="pref_title_language">Jazyk</string>
+    <string name="pref_title_minimize_priority">Skr媒t ozn谩men铆 Gadgetbridge</string>
+    <string name="pref_summary_minimize_priority_off">Ikona ve stavov茅 li拧t臎 a ozn谩men铆 na zam膷en茅 obrazovce budou zobrazeny</string>
+    <string name="pref_summary_minimize_priority_on">Ikona ve stavov茅 li拧t臎 a ozn谩men铆 na zam膷en茅 obrazovce budou skryty</string>
+    <string name="pref_header_notifications">Ozn谩men铆</string>
+    <string name="pref_title_notifications_repetitions">Opakov谩n铆</string>
+    <string name="pref_title_notifications_call">Vol谩n铆</string>
+    <string name="pref_title_notifications_sms">SMS</string>
+    <string name="pref_title_notifications_pebblemsg">Zpr谩vy Pebble</string>
+    <string name="pref_summary_notifications_pebblemsg">Podpora pro aplikace, kter茅 pos铆laj铆 ozn谩men铆 do Pebble p艡es PebbleKit.</string>
+    <string name="pref_title_notifications_generic">P艡铆stup k ozn谩men铆m</string>
+    <string name="pref_title_whenscreenon">Upozorn臎n铆 i p艡i zapnut茅 obrazovce</string>
+    <string name="pref_title_notification_filter">Neru拧it</string>
+    <string name="pref_summary_notification_filter">Zablokovat v拧echna ozn谩men铆, kdy啪 je v telefonu povolen re啪im Neru拧it</string>
+    <string name="pref_title_transliteration">P艡eps谩n铆 diakritiky</string>
+    <string name="pref_summary_transliteration">Zapn臎te, pokud toto za艡铆zen铆 nepodporuje p铆smo va拧eho jazyka</string>
+    <string name="always">V啪dy</string>
+    <string name="when_screen_off">Pokud je vypnut媒 displej</string>
+    <string name="never">Nikdy</string>
+    <string name="pref_header_privacy">Soukrom铆</string>
+    <string name="pref_title_call_privacy_mode">Nastaven铆 soukrom铆 p艡i p艡铆choz铆m vol谩n铆</string>
+    <string name="pref_call_privacy_mode_off">Zobrazit jm茅no a 膷铆slo</string>
+    <string name="pref_call_privacy_mode_name">Skr媒t jm茅no, ale zobrazit 膷铆slo</string>
+    <string name="pref_call_privacy_mode_complete">Skr媒t jm茅no i 膷铆slo</string>
+    <string name="pref_header_cannned_messages">P艡edvolen茅 zpr谩vy</string>
+    <string name="pref_title_canned_replies">Odpov臎di</string>
+    <string name="pref_title_canned_reply_suffix">B臎啪n谩 p艡铆pona</string>
+    <string name="pref_title_canned_messages_dismisscall">Zam铆tnut铆 vol谩n铆</string>
+    <string name="pref_title_canned_messages_set">Aktualizovat na za艡铆zen铆</string>
+    <string name="pref_header_development">V媒voj谩艡sk谩 nastaven铆</string>
+    <string name="pref_title_development_miaddr">Adresa Mi Band</string>
+    <string name="pref_title_pebble_settings">Nastaven铆 Pebble</string>
+    <string name="pref_header_activitytrackers">Sledova膷e aktivity</string>
+    <string name="pref_title_pebble_activitytracker">Preferovan媒 sledova膷 aktivity</string>
+    <string name="pref_title_pebble_sync_health">Synchronizovat Pebble Health</string>
+    <string name="pref_title_pebble_sync_misfit">Synchronizovat Misfit</string>
+    <string name="pref_title_pebble_sync_morpheuz">Synchronizovat  Morpheuz</string>
+    <string name="pref_title_enable_outgoing_call">Podpora odchoz铆ch vol谩n铆</string>
+    <string name="pref_summary_enable_outgoing_call">Vypnut铆m tak茅 p艡estane Pebble 2/LE vibrovat p艡i odchoz铆ch vol谩n铆ch</string>
+    <string name="pref_title_enable_pebblekit">Povolit p艡铆stup Android aplikac铆 t艡et铆ch stran</string>
+    <string name="pref_summary_enable_pebblekit">Povolit experiment谩ln铆 podporu pro aplikace Androidu p艡es PebbleKit</string>
+    <string name="pref_title_sunrise_sunset">V媒chod a z谩pad slunce</string>
+    <string name="pref_summary_sunrise_sunset">Zas铆lat 膷asy v媒chodu a z谩padu slunce dle aktu谩ln铆 polohy do 膷asov茅 osy Pebble</string>
+    <string name="pref_title_autoremove_notifications">Automaticky odstra艌ovat zav艡en谩 ozn谩men铆</string>
+    <string name="pref_summary_autoremove_notifications">Ozn谩men铆 budou po ozna膷en铆 jako p艡e膷ten茅 na telefonu ze za艡铆zen铆 automaticky odstran臎na</string>
+    <string name="pref_title_pebble_privacy_mode">Soukrom媒 re啪im</string>
+    <string name="pref_pebble_privacy_mode_off">B臎啪n谩 ozn谩men铆</string>
+    <string name="pref_pebble_privacy_mode_content">Posunout text ozn谩men铆 mimo displej</string>
+    <string name="pref_pebble_privacy_mode_complete">Zobrazit pouze ikonu ozn谩men铆</string>
+    <string name="pref_header_location">Poloha</string>
+    <string name="pref_title_location_aquire">Zjistit polohu</string>
+    <string name="pref_title_location_latitude">Z. 拧铆艡ka</string>
+    <string name="pref_title_location_longitude">Z. d茅lka</string>
+    <string name="pref_title_location_keep_uptodate">Aktualizovat polohu</string>
+    <string name="pref_summary_location_keep_uptodate">Zjistit polohu p艡i spu拧t臎n铆, ulo啪enou polohu pou啪铆t jako z谩lo啪n铆</string>
+    <string name="toast_enable_networklocationprovider">Povolte zji拧t臎n铆 polohy v s铆ti</string>
+    <string name="toast_aqurired_networklocation">poloha zji拧t臎na</string>
+    <string name="pref_title_pebble_forceprotocol">Vynutit protokol ozn谩men铆</string>
+    <string name="pref_summary_pebble_forceprotocol">Tato volba vynut铆 pou啪it铆 posledn铆ho protokolu ozn谩men铆 podle verze firmware. POVOLTE, JEN POKUD V脥TE, CO D臍L脕TE!</string>
+    <string name="pref_title_pebble_forceuntested">Povolit netestovan茅 funkce</string>
+    <string name="pref_summary_pebble_forceuntested">Povol铆 funkce, kter茅 nebyl testov谩ny. POVOLTE, JEN POKUD V脥TE, CO D臍L脕TE!</string>
+    <string name="pref_title_pebble_forcele">V啪dy preferovat BLE</string>
+    <string name="pref_summary_pebble_forcele">Pou啪铆t experiment谩ln铆 podporu Pebble LE nam铆sto BT classic pro v拧echna za艡铆zen铆 Pebble. Vy啪aduje nejprve sp谩rovat s ne-LE a pot茅 s Pebble LE</string>
+    <string name="pref_title_pebble_mtu_limit">Pebble 2/LE GATT MTU limity</string>
+    <string name="pref_summary_pebble_mtu_limit">Pokud va拧e Pebble 2/Pebble LE nepracuje jak m谩, zkuste toto nastaven铆 pro omezen铆 MTU (povolen媒 rozsah 20鈥�512)</string>
+    <string name="pref_title_pebble_enable_applogs">Zapnout logov谩n铆 aplikac铆 hodinek</string>
+    <string name="pref_summary_pebble_enable_applogs">Protokoly aplikac铆 hodinek budou zapisov谩ny aplikac铆 Gadgetbridge (vy啪aduje op臎tovn茅 p艡ipojen铆)</string>
+    <string name="pref_title_pebble_always_ack_pebblekit">P艡ed膷asn茅 potvrzen铆 (ACK) pro PebbleKit</string>
+    <string name="pref_summary_pebble_always_ack_pebblekit">Zp暖sob铆, 啪e zpr谩vy zaslan茅 extern铆m aplikac铆m 3. stran budou potvrzeny v啪dy a okam啪it臎</string>
+    <string name="pref_title_pebble_reconnect_attempts">Pokus暖 o znovup艡ipojen铆</string>
+    <string name="pref_title_unit_system">Jednotky</string>
+    <string name="pref_title_timeformat">Form谩t 膷asu</string>
+    <string name="pref_title_screentime">Doba zapnut铆 displeje</string>
+    <string name="prefs_title_all_day_heart_rate">Celodenn铆 m臎艡en铆 tepu</string>
+    <string name="preferences_hplus_settings">Nastaven铆 HPlus/Makibes</string>
+    <string name="not_connected">Nep艡ipojeno</string>
+    <string name="connecting">P艡ipojov谩n铆</string>
+    <string name="connected">P艡ipojeno</string>
+    <string name="unknown_state">Nezn谩m媒 stav</string>
+    <string name="_unknown_">(nezn谩m茅)</string>
+    <string name="test">Test</string>
+    <string name="test_notification">Test ozn谩men铆</string>
+    <string name="this_is_a_test_notification_from_gadgetbridge">Toto je testovac铆 ozn谩men铆 z Gadgetbridge</string>
+    <string name="bluetooth_is_not_supported_">Bluetooth nen铆 podporov谩n.</string>
+    <string name="bluetooth_is_disabled_">Bluetooth je vypnut媒.</string>
+    <string name="tap_connected_device_for_app_mananger">Dotkn臎te se za艡铆zen铆 pro App Manager</string>
+    <string name="tap_connected_device_for_activity">Dotkn臎te se za艡铆zen铆 pro aktivitu</string>
+    <string name="tap_connected_device_for_vibration">Dotkn臎te se za艡铆zen铆 pro vibrace</string>
+    <string name="tap_a_device_to_connect">Dotkn臎te se za艡铆zen铆 pro p艡ipojen铆</string>
+    <string name="cannot_connect_bt_address_invalid_">Nelze p艡ipojit. Je BT adresa v po艡谩dku?</string>
+    <string name="gadgetbridge_running_generic">Gadgetbridge b臎啪铆</string>
+    <string name="installing_binary_d_d">Instaluji soubor %1$d/%2$d</string>
+    <string name="installation_failed_">Instalace selhala</string>
+    <string name="installation_successful">Instalace 煤sp臎拧n谩</string>
+    <string name="firmware_install_warning">CHYST脕TE SE NAINSTALOVAT FIRMWARE, POKRA膶UJTE NA VLASTN脥 NEBEZPE膶脥.
+\n
+\n
+\nTento firmware je pro revizi HW: %s</string>
+    <string name="app_install_info">Instalujete aplikaci:
+\n
+\n%1$s
+\nVerze %2$s od %3$s
+\n</string>
+    <string name="n_a">Nedostupn茅</string>
+    <string name="initialized">Spu拧t臎no</string>
+    <string name="appversion_by_creator">%1$s od %2$s</string>
+    <string name="title_activity_discovery">Hled谩n铆 za艡铆zen铆</string>
+    <string name="discovery_stop_scanning">Zastavit hled谩n铆</string>
+    <string name="discovery_start_scanning">Spustit hled谩n铆</string>
+    <string name="action_discover">P艡ipojit nov茅 za艡铆zen铆</string>
+    <string name="device_with_rssi">%1$s (%2$s)</string>
+    <string name="title_activity_android_pairing">P谩rovat za艡铆zen铆</string>
+    <string name="android_pairing_hint">Pou啪ijte p谩rov谩n铆 BT Androidu pro sp谩rov谩n铆 se za艡铆zen铆m. </string>
+    <string name="title_activity_mi_band_pairing">P谩rovat Mi Band</string>
+    <string name="pairing">P谩ruji s %s鈥�</string>
+    <string name="pairing_creating_bond_with">Vytv谩艡铆m vazbu s %1$s (%2$s)</string>
+    <string name="pairing_unable_to_pair_with">Nelze sp谩rovat s %1$s (%2$s)</string>
+    <string name="pairing_in_progress">Vytv谩艡铆m vazbu: %1$s (%2$s)</string>
+    <string name="pairing_already_bonded">Vazba s %1$s (%2$s) ji啪 existuje, propojuji鈥�</string>
+    <string name="message_cannot_pair_no_mac">Adresa MAC nep艡i拧la, nemohu p谩rovat.</string>
+    <string name="preferences_category_device_specific_settings">Nastaven铆 za艡铆zen铆</string>
+    <string name="preferences_miband_settings">Nastaven铆 Mi Band / Amazfit</string>
+    <string name="male">Mu啪</string>
+    <string name="female">沤ena</string>
+    <string name="other">Jin茅</string>
+    <string name="left">Lev茅</string>
+    <string name="right">Prav茅</string>
+    <string name="miband_pairing_using_dummy_userdata">Data u啪ivatele nejsou platn谩, nyn铆 pou啪铆v谩m vzorov谩.</string>
+    <string name="miband_pairing_tap_hint">Kdy啪 Mi Band zavibruje a blikne, dotkn臎te se jej n臎kolikr谩t po sob臎.</string>
+    <string name="appinstaller_install">Instalovat</string>
+    <string name="discovery_connected_devices_hint">Nastavte sv茅 za艡铆zen铆 jako viditeln茅. Ji啪 p艡ipojen谩 za艡铆zen铆 nejsp铆拧e nebudou nalezena. Povolte polohu (GPS) pro mobily se syst茅mem Android 6 a nov臎j拧铆m. Vypn臎te hl铆d谩n铆 soukrom铆 (Privacy Guard) pro Gadgetbridge, proto啪e m暖啪e zp暖sobit nestabilitu telefonu. Pokud nebude za艡铆zen铆 nalezeno b臎hem n臎kolika minut, zkuste to znovu po restartu telefonu.</string>
+    <string name="discovery_note">Pozn谩mka:</string>
+    <string name="candidate_item_device_image">Obr谩zek za艡铆zen铆</string>
+    <string name="miband_prefs_alias">Jm茅no/p艡ezd铆vka</string>
+    <string name="pref_header_vibration_count">Po膷et vibrac铆</string>
+    <string name="title_activity_sleepmonitor">Sledov谩n铆 sp谩nku</string>
+    <string name="pref_write_logfiles">Zapisovat protokoly</string>
+    <string name="initializing">Spou拧t铆m</string>
+    <string name="busy_task_fetch_activity_data">Stahuji data o aktivit臎</string>
+    <string name="sleep_activity_date_range">Od %1$s do %2$s</string>
+    <string name="prefs_wearside">Nos铆te vlevo nebo vpravo?</string>
+    <string name="pref_screen_vibration_profile">Profil vibrac铆</string>
+    <string name="vibration_profile_staccato">Staccato</string>
+    <string name="vibration_profile_short">Kr谩tk茅</string>
+    <string name="vibration_profile_medium">St艡edn铆</string>
+    <string name="vibration_profile_long">Dlouh茅</string>
+    <string name="vibration_profile_waterdrop">Kapka</string>
+    <string name="vibration_profile_ring">Zvon臎n铆</string>
+    <string name="vibration_profile_alarm_clock">Bud铆k</string>
+    <string name="miband_prefs_vibration">Vibrace</string>
+    <string name="vibration_try">Zkusit</string>
+    <string name="pref_screen_notification_profile_sms">Ozn谩men铆 SMS</string>
+    <string name="pref_header_vibration_settings">Nastaven铆 vibrac铆</string>
+    <string name="pref_screen_notification_profile_generic">Obecn茅 ozn谩men铆</string>
+    <string name="pref_screen_notification_profile_email">Upozorn臎n铆 emailu</string>
+    <string name="pref_screen_notification_profile_incoming_call">Ozn谩men铆 o p艡铆choz铆m hovoru</string>
+    <string name="pref_screen_notification_profile_generic_chat">Konverzace</string>
+    <string name="pref_screen_notification_profile_generic_navigation">Navigace</string>
+    <string name="pref_screen_notification_profile_generic_social">Soci谩ln铆 s铆t臎</string>
+    <string name="control_center_find_lost_device">Naj铆t za艡铆zen铆</string>
+    <string name="control_center_cancel_to_stop_vibration">Zastavit vibrac铆.</string>
+    <string name="title_activity_charts">Aktivita a sp谩nek</string>
+    <string name="title_activity_set_alarm">Nastavit bud铆ky</string>
+    <string name="controlcenter_start_configure_alarms">Nastavit bud铆ky</string>
+    <string name="title_activity_alarm_details">Podrobnosti bud铆ku</string>
+    <string name="alarm_sun_short">Ne</string>
+    <string name="alarm_mon_short">Po</string>
+    <string name="alarm_tue_short">脷t</string>
+    <string name="alarm_wed_short">St</string>
+    <string name="alarm_thu_short">膶t</string>
+    <string name="alarm_fri_short">P谩</string>
+    <string name="alarm_sat_short">So</string>
+    <string name="alarm_smart_wakeup">Inteligentn铆 probuzen铆</string>
+    <string name="user_feedback_miband_set_alarms_failed">Do拧lo k chyb臎 p艡i nastavov谩n铆 bud铆ku, zkuste to znovu.</string>
+    <string name="user_feedback_miband_set_alarms_ok">Bud铆ky odesl谩ny do za艡铆zen铆.</string>
+    <string name="chart_no_data_synchronize">Chyb铆 data, prov茅st sta啪en铆?</string>
+    <string name="user_feedback_miband_activity_data_transfer">Stahnout %1$s dat z %2$s</string>
+    <string name="miband_prefs_fitness_goal">C铆lov媒 po膷et krok暖 na den</string>
+    <string name="dbaccess_error_executing">Chyba spou拧t臎n铆 \'%1$s\'</string>
+    <string name="controlcenter_start_activitymonitor">Va拧e aktivita</string>
+    <string name="cannot_connect">Nelze se p艡ipojit k: %1$s</string>
+    <string name="installer_activity_unable_to_find_handler">Nemohu naj铆t program k instalaci tohoto souboru.</string>
+    <string name="pbw_install_handler_unable_to_install">Nelze nainstalovat vybran媒 soubor: %1$s</string>
+    <string name="pbw_install_handler_hw_revision_mismatch">Nepoda艡ilo se nainstalovat vybran媒 firmware: neshoduje se s reviz铆 hardware va拧eho Pebble.</string>
+    <string name="installer_activity_wait_while_determining_status">Vy膷kejte na zji拧t臎n铆 stavu instalace鈥�</string>
+    <string name="notif_battery_low_title">N铆zk谩 煤rove艌 baterie v za艡铆zen铆!</string>
+    <string name="notif_battery_low_percent">%1$s zb媒v谩 v baterii: %2$s%%</string>
+    <string name="notif_battery_low_bigtext_last_charge_time">Posledn铆 nabit铆: %s \n</string>
+    <string name="notif_battery_low_bigtext_number_of_charges">Po膷et nabit铆: %s</string>
+    <string name="sleepchart_your_sleep">Sp谩nek</string>
+    <string name="weeksleepchart_sleep_a_week">Sp谩nek za t媒den</string>
+    <string name="weeksleepchart_today_sleep_description">Sp谩nek dnes, c铆l: %1$s</string>
+    <string name="weekstepschart_steps_a_week">Kroky za t媒den</string>
+    <string name="activity_sleepchart_activity_and_sleep">Aktivita</string>
+    <string name="updating_firmware">Nahr谩v谩m firmware鈥�</string>
+    <string name="fwapp_install_device_not_ready">Soubor nelze nainstalovat, za艡铆zen铆 nen铆 p艡ipraveno.</string>
+    <string name="installhandler_firmware_name">%1$s: %2$s %3$s</string>
+    <string name="miband_fwinstaller_compatible_version">Kompatibiln铆 verze</string>
+    <string name="miband_fwinstaller_untested_version">Netestovan谩 verze!</string>
+    <string name="fwappinstaller_connection_state">P艡ipojen铆 k za艡铆zen铆: %1$s</string>
+    <string name="pbw_installhandler_pebble_firmware">Firmware Pebble %1$s</string>
+    <string name="pbwinstallhandler_correct_hw_revision">Spr谩vn谩 revize HW</string>
+    <string name="pbwinstallhandler_incorrect_hw_revision">Revize HW nen铆 spr谩vn谩!</string>
+    <string name="pbwinstallhandler_app_item">%1$s (%2$s)</string>
+    <string name="updatefirmwareoperation_updateproblem_do_not_reboot">P艡i p艡enosu firmwaru nastaly pot铆啪e. Nerestartujte sv暖j Mi Band!</string>
+    <string name="updatefirmwareoperation_metadata_updateproblem">Probl茅m p艡i p艡enosu metadat firmware</string>
+    <string name="updatefirmwareoperation_update_complete">Instalace firmware byla dokon膷ena</string>
+    <string name="updatefirmwareoperation_update_complete_rebooting">Instalace firmware byla dokon膷ena, restartuji za艡铆zen铆鈥�</string>
+    <string name="updatefirmwareoperation_write_failed">Nahr谩n铆 firmware selhalo</string>
+    <string name="chart_steps">Krok暖</string>
+    <string name="liveactivity_live_activity">Aktu谩ln铆 aktivita</string>
+    <string name="weeksteps_today_steps_description">Krok暖 dnes, c铆l: %1$s</string>
+    <string name="pref_title_dont_ack_transfer">Nepotvrzovat p艡enos dat o aktivit臎</string>
+    <string name="pref_summary_dont_ack_transfers">Pokud nen铆 p艡enos dat potvrzen n谩ramku, potom nebudou smaz谩na. U啪ite膷n茅 pokud je GB pou啪铆v谩n dohromady s jinou aplikac铆.</string>
+    <string name="pref_summary_keep_data_on_device">Zachov谩 data o aktivit臎 v za艡铆zen铆 i po synchronizaci. U啪ite膷n茅, pokud je GB pou啪铆v谩n dohromady s jin媒mi aplikacemi. Tato funkce m暖啪e zp暖sobit, 啪e na za艡铆zen铆 dojde m铆sto a/nebo se p艡estane spr谩vn臎 synchronizovat.</string>
+    <string name="pref_title_low_latency_fw_update">Pou啪铆t re啪im z n铆zk媒m zpo啪d臎n铆m pro aktualizace firmwaru</string>
+    <string name="pref_summary_low_latency_fw_update">M暖啪e to pomoci u za艡铆zen铆, kde aktualizace firmwaru selhala.</string>
+    <string name="live_activity_steps_history">Historie krok暖</string>
+    <string name="live_activity_current_steps_per_minute">Aktu谩ln臎 krok暖/min</string>
+    <string name="live_activity_total_steps">Celkem krok暖</string>
+    <string name="live_activity_steps_per_minute_history">Historie krok暖 za minutu</string>
+    <string name="live_activity_start_your_activity">Zahajte svou aktivitu</string>
+    <string name="abstract_chart_fragment_kind_activity">Aktivita</string>
+    <string name="abstract_chart_fragment_kind_light_sleep">Lehk媒 sp谩nek</string>
+    <string name="abstract_chart_fragment_kind_deep_sleep">Hlubok媒 sp谩nek</string>
+    <string name="abstract_chart_fragment_kind_not_worn">Neno拧eno</string>
+    <string name="device_not_connected">Nep艡ipojeno.</string>
+    <string name="user_feedback_all_alarms_disabled">V拧echny bud铆ky vypnuty</string>
+    <string name="pref_title_keep_data_on_device">Zachovat data o aktivit臎 v za艡铆zen铆</string>
+    <string name="miband_fwinstaller_incompatible_version">Nekompatibiln铆 firmware</string>
+    <string name="fwinstaller_firmware_not_compatible_to_device">Tento firmware nen铆 kompatibiln铆 s t铆mto za艡铆zen铆m</string>
+    <string name="miband_prefs_reserve_alarm_calendar">Bud铆ky pro nadch谩zej铆c铆 ud谩losti</string>
+    <string name="miband_prefs_hr_sleep_detection">Pou啪铆t senzor tepu pro zlep拧en铆 detekce sp谩nku</string>
+    <string name="miband_prefs_device_time_offset_hours">膶asov媒 posun za艡铆zen铆 v hodin谩ch (pro zji拧钮ov谩n铆 sp谩nku sm臎na艡暖)</string>
+    <string name="miband2_prefs_dateformat">Form谩t data</string>
+    <string name="dateformat_time">膶as</string>
+    <string name="dateformat_date_time"><![CDATA[膶as a datum]]></string>
+    <string name="mi2_prefs_activate_display_on_lift">Zapnout displej p艡i zvednut铆</string>
+    <string name="FetchActivityOperation_about_to_transfer_since">P艡en谩拧铆m data od %1$s</string>
+    <string name="waiting_for_reconnect">膶ek谩n铆 na znovup艡ipojen铆</string>
+    <string name="activity_prefs_about_you">Va拧e 煤daje</string>
+    <string name="activity_prefs_year_birth">Rok narozen铆</string>
+    <string name="activity_prefs_gender">Pohlav铆</string>
+    <string name="activity_prefs_height_cm">V媒拧ka v cm</string>
+    <string name="activity_prefs_weight_kg">V谩ha v kg</string>
+    <string name="authenticating">Ov臎艡ov谩n铆</string>
+    <string name="authentication_required">Ov臎艡ov谩n铆 vy啪adov谩no</string>
+    <string name="appwidget_text">Sp铆</string>
+    <string name="add_widget">P艡idat widget</string>
+    <string name="activity_prefs_sleep_duration">Preferovan谩 doba sp谩nku v hodin谩ch</string>
+    <string name="appwidget_setting_alarm">Bud铆k nastaven na %1$02d:%2$02d</string>
+    <string name="device_hw">Revize HW: %1$s</string>
+    <string name="device_fw">Verze FW: %1$s</string>
+    <string name="error_creating_directory_for_logfiles">Chyba p艡i vytv谩艡en铆 adres谩艡e pro protokoly: %1$s</string>
+    <string name="DEVINFO_HR_VER">"Tep: "</string>
+    <string name="updatefirmwareoperation_update_in_progress">Aktualizace firmware</string>
+    <string name="updatefirmwareoperation_firmware_not_sent">Firmware nebyl odesl谩n</string>
+    <string name="charts_legend_heartrate">Srde膷n铆 tep</string>
+    <string name="live_activity_heart_rate">Srde膷n铆 tep</string>
+    <string name="pref_title_pebble_health_store_raw">Ulo啪it hrub媒 z谩znam do datab谩ze</string>
+    <string name="pref_summary_pebble_health_store_raw">Ukl谩d谩 data v p暖vodn铆 podob臎, tak啪e je datab谩ze v臎t拧铆, ale umo啪艌uje to pozd臎j拧铆 vyhodnocen铆.</string>
+    <string name="action_db_management">Spr谩va dat</string>
+    <string name="title_activity_db_management">Spr谩va dat</string>
+    <string name="activity_db_management_import_export_explanation">Operace s datab谩z铆 pou啪ij铆 n谩sleduj铆c铆 cestu (viz n铆啪e) ke slo啪ce v za艡铆zen铆. Tato slo啪ka je dostupn谩 pro ostatn铆 aplikace Androidu a v谩拧 po膷铆ta膷. Nezapome艌te, 啪e tato slo啪ka a jej铆 obsah jsou vymaz谩ny p艡i odinstalaci Gadgetbridge. Soubory obsahuj铆:
+\n Export_preference - glob谩ln铆 nastaven铆
+\n Export_preference_MAC - nastaven铆 specifick谩 pro za艡铆zen铆
+\n Gadgetbridge - datab谩ze za艡铆zen铆 a aktivit
+\n Gadgetbridge_date - datab谩ze exportovan铆 ke datu
+\n *.gpx - GPS z谩znamy
+\n *.log - log soubory
+\nExportovan谩 data (nebo m铆sto pro import dat) naleznete zde:</string>
+    <string name="activity_db_management_merge_old_title">Smazat p暖vodn铆 datab谩zi</string>
+    <string name="dbmanagementactivvity_cannot_access_export_path">Nelze p艡istoupit na zadanou cestu. Kontaktujte v媒voj谩艡e.</string>
+    <string name="dbmanagementactivity_exported_to">Exportov谩no do: %1$s</string>
+    <string name="dbmanagementactivity_error_exporting_db">Chyba p艡i exportu DB: %1$s</string>
+    <string name="dbmanagementactivity_import_data_title">Importovat data?</string>
+    <string name="dbmanagementactivity_overwrite_database_confirmation">Opravdu chcete p艡epsat aktu谩ln铆 data? V拧echna data aktivit (pokud existuj铆), za艡铆zen铆 a nastaven铆 budou p艡eps谩na.</string>
+    <string name="dbmanagementactivity_import_successful">Importov谩no.</string>
+    <string name="dbmanagementactivity_error_importing_db">Chyba p艡i importu DB: %1$s</string>
+    <string name="dbmanagementactivity_delete_activity_data_title">Smazat data o aktivit谩ch?</string>
+    <string name="dbmanagementactivity_really_delete_entire_db">Opravdu chcete smazat celou datab谩zi? V拧echna data o aktivit谩ch a informace o za艡铆zen铆ch se ztrat铆.</string>
+    <string name="dbmanagementactivity_database_successfully_deleted">Data smaz谩na.</string>
+    <string name="dbmanagementactivity_db_deletion_failed">Maz谩n铆 dat se nezda艡ilo.</string>
+    <string name="dbmanagementactivity_delete_old_activity_db">Smazat p暖vodn铆 datab谩zi?</string>
+    <string name="dbmanagementactivity_delete_old_activitydb_confirmation">Opravdu chcete smazat p暖vodn铆 datab谩zi? Data o aktivit谩ch, kter谩 nebyla importov谩na budou ztracena.</string>
+    <string name="dbmanagementactivity_old_activity_db_successfully_deleted">P暖vodn铆 datab谩ze byla vymaz谩na.</string>
+    <string name="dbmanagementactivity_old_activity_db_deletion_failed">Maz谩n铆 p暖vodn铆 datab谩ze selhalo.</string>
+    <string name="dbmanagementactivity_overwrite">P艡epsat</string>
+    <string name="Cancel">Zru拧it</string>
+    <string name="Delete">Odstranit</string>
+    <!--Strings related to Vibration Activity-->
+    <string name="title_activity_vibration">Vibrace</string>
+    <!--Strings related to Pebble Pairing Activity-->
+    <string name="title_activity_pebble_pairing">P谩rov谩n铆 Pebble</string>
+    <string name="pebble_pairing_hint">Na va拧em za艡铆zen铆 Android se objev铆 dialog p谩rov谩n铆. Pokud se tak nestane, zkontrolujte ozn谩men铆 a potvr膹te 啪谩dost o p谩rov谩n铆. Pot茅 potvr膹te p谩rov谩n铆 na Pebble.</string>
+    <string name="weather_notification_label">Ujist臎te se, 啪e tento skin je povolen v aplikaci pro ozn谩men铆 po膷as铆 pro z铆sk谩v谩n铆 informac铆 v Pebble.\n\nNen铆 t艡eba nic nastavovat.\n\nAplikaci pro po膷as铆 Pebble je mo啪n茅 povolit ve spr谩v臎 aplikac铆.\n\nPodporovan茅 cifern铆ky uk谩啪铆 po膷as铆 automaticky.</string>
+    <string name="pref_title_setup_bt_pairing">Zapnout p谩rov谩n铆 BT</string>
+    <string name="pref_summary_setup_bt_pairing">Toto vypn臎te v p艡铆pad臎 probl茅m暖 s p艡ipojen铆m</string>
+    <string name="unit_metric">Metrick茅</string>
+    <string name="unit_imperial">Imperi谩ln铆</string>
+    <string name="timeformat_24h">24h</string>
+    <string name="timeformat_am_pm">Dop./Odp.</string>
+    <string name="pref_screen_notification_profile_alarm_clock">Bud铆k</string>
+    <string name="StringUtils_sender">(%1$s)</string>
+    <string name="find_device_you_found_it">Nalezeno!</string>
+    <string name="miband2_prefs_timeformat">Form谩t 膷asu Mi2</string>
+    <string name="action_donate">Podpo艡it</string>
+    <string name="title_activity_calblacklist">Zak谩zan茅 kalend谩艡e</string>
+    <string name="pref_call_privacy_mode_number">Skr媒t 膷铆slo, ale zobrazit jm茅no</string>
+    <string name="pref_blacklist_calendars">Zak谩zan茅 kalend谩艡e</string>
+    <string name="pref_header_pebble_timeline">膶asov谩 osa Pebble</string>
+    <string name="pref_title_enable_calendar_sync">Synchronizace kalend谩艡e</string>
+    <string name="pref_summary_enable_calendar_sync">Odes铆lat ud谩losti kalend谩艡e na 膷asovou osu</string>
+    <string name="stats_title">Z贸ny rychlosti</string>
+    <string name="stats_x_axis_label">Minut celkem</string>
+    <string name="stats_y_axis_label">Krok暖 za minutu</string>
+    <string name="calories">Kalori铆</string>
+    <string name="distance">Vzd谩lenost</string>
+    <string name="clock">M臎艡en铆 膷asu</string>
+    <string name="heart_rate">Tep</string>
+    <string name="battery">Baterie</string>
+    <string name="mi2_prefs_goal_notification">Upozorn臎n铆 na c铆l</string>
+    <string name="mi2_prefs_goal_notification_summary">N谩ramek zavibruje p艡i dosa啪en铆 c铆le denn铆ho po膷tu krok暖</string>
+    <string name="mi2_prefs_display_items">Zobrazen铆 polo啪ek</string>
+    <string name="mi2_prefs_display_items_summary">Vyberte polo啪ky zobrazen茅 na obrazovce n谩ramku</string>
+    <string name="mi2_prefs_rotate_wrist_to_switch_info">Oto膷en铆m z谩p臎st铆 p艡epnout menu</string>
+    <string name="mi2_prefs_do_not_disturb">Neru拧it</string>
+    <string name="mi2_prefs_do_not_disturb_summary">N谩ramek nebude p艡ij铆mat upozorn臎n铆, pokud je aktivn铆 re啪im Neru拧it</string>
+    <string name="mi2_prefs_inactivity_warnings">Upozorn臎n铆 na ne膷innost</string>
+    <string name="mi2_prefs_inactivity_warnings_summary">N谩ramek zavibruje po ur膷it茅 dob臎 neaktivity</string>
+    <string name="mi2_prefs_inactivity_warnings_threshold">Limit 膷asu bez pohybu (v minut谩ch)</string>
+    <string name="mi2_prefs_inactivity_warnings_dnd_summary">Vypnut铆 upozorn臎n铆 na ne膷innost b臎hem zadan茅ho 膷asov茅ho intervalu</string>
+    <string name="mi2_prefs_do_not_disturb_start">Od</string>
+    <string name="mi2_prefs_do_not_disturb_end">Konec</string>
+    <string name="dbmanagementactivity_error_exporting_shared">Chyba p艡i exportu preferenc铆: %1$s</string>
+    <string name="dbmanagementactivity_error_importing_shared">Chyba p艡i importu preferenc铆: %1$s</string>
+    <string name="mi2_fw_installhandler_fw53_hint">P艡ed instalac铆 tohoto firmwaru mus铆te nainstalovat verzi %1$s!</string>
+    <string name="mi2_enable_text_notifications">Textov茅 upozorn臎n铆</string>
+    <string name="mi2_enable_text_notifications_summary"><![CDATA[Vy啪aduje nainstalovan媒 firmware >= 1.0.1.28 and Mili_pro.ft*]]></string>
+    <string name="off">Vypnuto</string>
+    <string name="mi2_dnd_off">Vypnuto</string>
+    <string name="mi2_dnd_automatic">Automaticky (detekce sp谩nku)</string>
+    <string name="mi2_dnd_scheduled">Napl谩novano (膷asov媒 interval)</string>
+    <string name="discovery_attempting_to_pair">Pokus o sp谩rov谩n铆 %1$s</string>
+    <string name="discovery_bonding_failed_immediately">Spojen铆 s %1$s ihned selhalo.</string>
+    <string name="discovery_trying_to_connect_to">Pokou拧铆m sa spojit s: %1$s</string>
+    <string name="discovery_enable_bluetooth">Chcete-li naj铆t za艡铆zen铆, povolte funkci Bluetooth.</string>
+    <string name="discovery_successfully_bonded">Spojeno s %1$s.</string>
+    <string name="discovery_pair_title">P谩rovat s %1$s?</string>
+    <string name="discovery_pair_question">Chcete-li sp谩rovat sv谩 za艡铆zen铆, klepn臎te na P谩rovat. Pokud to sel啪e, zkuste to znovu bez p谩rov谩n铆.</string>
+    <string name="discovery_yes_pair">P谩rovat</string>
+    <string name="discovery_dont_pair">Nep谩rovat</string>
+    <string name="mi2_prefs_button_actions">Akce tla膷铆tka</string>
+    <string name="mi2_prefs_button_actions_summary">Nastaven铆 akc铆 po stisknut铆 tla膷铆tka</string>
+    <string name="mi2_prefs_button_press_count">Po膷et stisknut铆 tla膷铆tka</string>
+    <string name="mi2_prefs_button_press_count_summary">Po膷et stisknut铆 tla膷铆tka pro vyvol谩n铆 Ud谩losti 1. Dal拧铆 stejn茅 mno啪stv铆 stisk暖 vyvol谩 Ud谩lost 2 atd.</string>
+    <string name="mi2_prefs_button_press_broadcast">Vys铆l谩n铆 zpr谩v</string>
+    <string name="mi2_prefs_button_press_broadcast_summary">Vys铆l谩n铆 zpr谩vy odeslan茅 s ud谩lost铆. Parametr `button_id` je dopln臎n do ka啪d茅 zpr谩vy automaticky.</string>
+    <string name="mi2_prefs_button_action">Zapnout akci tla膷铆tka</string>
+    <string name="mi2_prefs_button_action_summary">Zapnout akci na zadan媒 po膷et stisknut铆 tla膷铆tka</string>
+    <string name="mi2_prefs_button_action_vibrate">Zapnout vibrov谩n铆 n谩ramku</string>
+    <string name="mi2_prefs_button_action_vibrate_summary">Zapnout vibrace n谩ramku p艡i stisknut铆 tla膷铆tka</string>
+    <string name="mi2_prefs_button_press_count_max_delay">Maxim谩ln铆 zpo啪d臎n铆 mezi stisky</string>
+    <string name="mi2_prefs_button_press_count_max_delay_summary">Maxim谩ln铆 zpo啪d臎n铆 mezi stisknut铆m tla膷铆tka v milisekund谩ch</string>
+    <string name="fw_upgrade_notice_amazfitbip">Chyst谩te se nainstalovat firmware %s na v谩拧 Amazfit Bip.
+\n
+\nNainstalujte nejprve soubor .fw, pot茅 soubor .res a nakonec soubor .gps. Va拧e hodinky se po instalaci souboru .fw restartuj铆.
+\n
+\nPozn谩mka: soubory .res a .gps nemus铆te instalovat, pokud jsou toto啪n茅 s ji啪 d艡铆ve nainstalovan媒mi.
+\n
+\nPOKRA膶UJTE NA VLASTN脥 NEBEZPE膶脥!</string>
+    <string name="controlcenter_connect">P艡ipojit鈥�</string>
+    <string name="fw_upgrade_notice_amazfitcor">Chyst谩te se nainstalovat firmware %s na v谩拧 Amazfit Cor.
+\n
+\nNainstalujte nejprve soubor .fw a pot茅 soubor .res. V谩拧 n谩ramek se po instalaci souboru .fw restartuje.
+\n
+\nPozn谩mka: soubor .res nemus铆te instalovat, pokud je toto啪n媒 s ji啪 d艡铆ve nainstalovan媒m.
+\n
+\nPOKRA膶UJTE NA VLASTN脥 NEBEZPE膶脥!</string>
+    <string name="pref_title_charts_swipe">Zapnout mo啪nost pota啪en铆 mezi grafy aktivit</string>
+    <string name="pref_title_weather">Po膷as铆</string>
+    <string name="pref_title_weather_location">Poloha pro po膷as铆 (poskytovatel v LineageOS)</string>
+    <string name="pref_title_pebble_enable_bgjs">Zapnout JS na pozad铆</string>
+    <string name="pref_summary_pebble_enable_bgjs">Pokud je zapnuto, umo啪艌uje hodin谩m zobrazovat po膷as铆, 煤daje o baterii atd.</string>
+    <string name="pref_header_auto_export">Automatick媒 export</string>
+    <string name="pref_title_auto_export_enabled">Zapnout automatick媒 export</string>
+    <string name="pref_title_auto_export_location">Slo啪ka pro ulo啪en铆</string>
+    <string name="pref_title_auto_export_interval">膶asov媒 interval</string>
+    <string name="pref_summary_auto_export_interval">Exportovat ka啪dou %d. hodinu</string>
+    <string name="prefs_title_heartrate_measurement_interval">Celodenn铆 m臎艡en铆 tepu</string>
+    <string name="interval_one_minute">Jednou za minutu</string>
+    <string name="interval_five_minutes">Ka啪d媒ch 5 minut</string>
+    <string name="interval_ten_minutes">Ka啪d媒ch 10 minut</string>
+    <string name="interval_thirty_minutes">Ka啪d媒ch 30 minut</string>
+    <string name="interval_one_hour">Jednou za hodinu</string>
+    <string name="notif_export_failed_title">Export datab谩ze selhal! Zkontrolujte nastaven铆.</string>
+    <string name="automatic">Automaticky</string>
+    <string name="simplified_chinese">Zjednodu拧en谩 膷铆n拧tina</string>
+    <string name="traditional_chinese">Tradi膷n铆 膷铆n拧tina</string>
+    <string name="english">Angli膷tina</string>
+    <string name="spanish">艩pan臎l拧tina</string>
+    <string name="activity_web_view">Webov茅 zobrazen铆 aktivit</string>
+    <string name="_pebble_watch_open_on_phone">Otev艡铆t na Android za艡铆zen铆</string>
+    <string name="_pebble_watch_mute">Ztlumit</string>
+    <string name="_pebble_watch_reply">Znova</string>
+    <string name="kind_firmware">Firmware</string>
+    <string name="kind_invalid">Neplatn谩 data</string>
+    <string name="kind_font">P铆smo</string>
+    <string name="kind_gps">Firmware GPS</string>
+    <string name="kind_gps_almanac">Almanach GPS</string>
+    <string name="kind_gps_cep">Korekce chyb GPS</string>
+    <string name="kind_resources">Zdroje</string>
+    <string name="kind_watchface">Cifern铆k</string>
+    <string name="devicetype_unknown">Nezn谩m茅 za艡铆zen铆</string>
+    <string name="devicetype_test">Test za艡铆zen铆</string>
+    <string name="devicetype_pebble">Pebble</string>
+    <string name="devicetype_miband">Mi Band</string>
+    <string name="devicetype_miband2">Mi Band 2</string>
+    <string name="devicetype_amazfit_bip">Amazfit Bip</string>
+    <string name="devicetype_amazfit_cor">Amazfit Cor</string>
+    <string name="devicetype_vibratissimo">Vibratissimo</string>
+    <string name="devicetype_liveview">LiveView</string>
+    <string name="devicetype_hplus">HPlus</string>
+    <string name="devicetype_makibes_f68">Makibes F68</string>
+    <string name="devicetype_exrizu_k8">Exrizu K8</string>
+    <string name="devicetype_no1_f1">No.1 F1</string>
+    <string name="devicetype_teclast_h30">Teclast H30</string>
+    <string name="devicetype_xwatch">XWatch</string>
+    <string name="choose_auto_export_location">Vyberte um铆st臎n铆 exportu</string>
+    <string name="notification_channel_name">Obecn茅</string>
+    <string name="pref_title_pebble_gatt_clientonly">pouze GATT klient</string>
+    <string name="pref_summary_pebble_gatt_clientonly">Pebble 2 experiment谩ln铆 nastaven铆, zkuste m谩te-li probl茅my se spojen铆m</string>
+    <string name="on">Zapnuto</string>
+    <string name="activity_type_not_measured">Nem臎艡eno</string>
+    <string name="activity_type_activity">Aktivita</string>
+    <string name="activity_type_light_sleep">Lehk媒 sp谩nek</string>
+    <string name="activity_type_deep_sleep">Hlubok媒 sp谩nek</string>
+    <string name="activity_type_not_worn">Za艡铆zen铆 nepou啪ito</string>
+    <string name="activity_type_running">B臎h</string>
+    <string name="activity_type_walking">Ch暖ze</string>
+    <string name="activity_type_swimming">Plav谩n铆</string>
+    <string name="activity_type_unknown">Nezn谩m谩 膷innost</string>
+    <string name="activity_summaries">Sportovn铆 aktivity</string>
+    <string name="activity_type_biking">Cyklistika</string>
+    <string name="select_all">Vybrat v拧e</string>
+    <string name="share">Sd铆let</string>
+    <string name="reset_index">Resetovat datum sta啪en铆</string>
+    <string name="menuitem_status">Stav</string>
+    <string name="menuitem_activity">Historie Aktivit</string>
+    <string name="menuitem_weather">Po膷as铆</string>
+    <string name="menuitem_alarm">Bud铆k</string>
+    <string name="menuitem_timer">膶asova膷</string>
+    <string name="menuitem_compass">Kompas</string>
+    <string name="menuitem_settings">Nastaven铆</string>
+    <string name="menuitem_alipay">Alipay</string>
+    <string name="controlcenter_change_led_color">Zm臎nit barvu LED</string>
+    <string name="controlcenter_change_fm_frequency">Zm臎nit FM frekvenci</string>
+    <string name="controlcenter_calibrate_device">Kalibrovat za艡铆zen铆</string>
+    <string name="blacklist_all_for_notifications">Zak谩zat ozn谩men铆 ze v拧ech aplikac铆</string>
+    <string name="whitelist_all_for_notifications">Povolit ozn谩men铆 ze v拧ech aplikac铆</string>
+    <string name="fw_upgrade_notice_miband3">Chyst谩te se nainstalovat firmware %s na v谩拧 Mi Band 3.
+\n
+\nNainstalujte nejprve soubor .fw a pot茅 soubor .res. V谩拧 n谩ramek se po instalaci souboru .fw restartuje.
+\n
+\nPozn谩mka: soubor .res nemus铆te instalovat, pokud je toto啪n媒 s ji啪 d艡铆ve nainstalovan媒m.
+\n
+\nPOKRA膶UJTE NA VLASTN脥 NEBEZPE膶脥!</string>
+    <string name="pref_title_notifications_timeout">Minim谩ln铆 doba mezi ozn谩men铆mi</string>
+    <string name="pref_title_rtl">Zprava doleva</string>
+    <string name="pref_summary_rtl">Povolte, nepodporuje-li va拧e za艡铆zen铆 jazyky zprava doleva</string>
+    <string name="pref_rtl_max_line_length">Maxim谩ln铆 d茅lka 艡谩dku pro jazyky zprava doleva</string>
+    <string name="pref_rtl_max_line_length_summary">Prodlu啪uje/zkracuje text 艡谩dk暖 jazyk暖 zprava doleva</string>
+    <string name="prefs_screen_orientation">Orientace displeje</string>
+    <string name="pref_auto_fetch">Synchronizovat automaticky</string>
+    <string name="pref_auto_fetch_summary">Synchronizovat p艡i odem膷en铆 obrazovky. Mus铆 b媒t nastaven z谩mek obrazovky!</string>
+    <string name="pref_auto_fetch_limit_fetches">Minim谩ln铆 doba mezi synchronizacemi</string>
+    <string name="pref_auto_fetch_limit_fetches_summary">Synchronizovat ka啪d媒ch %d minut</string>
+    <string name="horizontal">Na 拧铆艡ku</string>
+    <string name="vertical">Na v媒拧ku</string>
+    <string name="watch9_pairing_tap_hint">Po zavibrov谩n铆 stiskn臎te tla膷铆tko, nebo se za艡铆zen铆m zat艡este.</string>
+    <string name="notif_battery_low">N铆zk媒 stav baterie %1$s</string>
+    <string name="notif_battery_low_extended">Baterie %1$s je vybit谩: %2$s</string>
+    <string name="lack_of_sleep">Nedostatek sp谩nku: %1$s</string>
+    <string name="overslept">Sp谩nku nad pl谩n: %1$s</string>
+    <string name="no_limit">Bez omezen铆</string>
+    <string name="seconds_5">5 vte艡in</string>
+    <string name="seconds_10">10 vte艡in</string>
+    <string name="seconds_20">20 vte艡in</string>
+    <string name="seconds_30">30 vte艡in</string>
+    <string name="minutes_1">1 minuta</string>
+    <string name="minutes_5">5 minut</string>
+    <string name="minutes_10">10 minut</string>
+    <string name="minutes_30">30 minut</string>
+    <string name="lack_of_step">P艡铆li拧 m谩lo krok暖: %1$d</string>
+    <string name="overstep">Krok暖 nad pl谩n: %1$d</string>
+    <string name="live_activity_max_heart_rate">Aktu谩ln铆 / Maxim谩ln铆 tepov谩 frekvence: %1$d / %2$d</string>
+    <string name="you_slept">%1$s - %2$s</string>
+    <string name="you_did_not_sleep">Bez sp谩nku</string>
+    <string name="mi3_prefs_band_screen_unlock">Odemknut铆 obrazovky za艡铆zen铆</string>
+    <string name="mi3_prefs_band_screen_unlock_summary">P艡eje膹te prstem pro odem膷en铆 obrazovky za艡铆zen铆</string>
+    <string name="mi3_prefs_night_mode">No膷n铆 re啪im</string>
+    <string name="mi3_prefs_night_mode_summary">Ni啪拧铆 automatick谩 intenzita displeje v noci</string>
+    <string name="norwegian_bokmal">Norsk媒 Bokm氓l</string>
+    <string name="russian">Ru拧tina</string>
+    <string name="german">N臎m膷ina</string>
+    <string name="italian">Ital拧tina</string>
+    <string name="french">Francouz拧tina</string>
+    <string name="polish">Pol拧tina</string>
+    <string name="korean">Korej拧tina</string>
+    <string name="japanese">Japon拧tina</string>
+    <string name="activity_prefs_charts">Nastaven铆 graf暖</string>
+    <string name="activity_prefs_chart_max_heart_rate">Maxim谩ln铆 tepov谩 frekvence</string>
+    <string name="activity_prefs_chart_min_heart_rate">Minim谩ln铆 tepov谩 frekvence</string>
+    <string name="ok">Ok</string>
+    <string name="mi3_night_mode_sunset">P艡i z谩padu slunce</string>
+    <string name="controlcenter_start_activity_tracks">Z谩znamy aktivit</string>
+    <string name="activity_type_treadmill">B臎啪eck媒 p谩s</string>
+    <string name="devicetype_miband3">Mi Band 3</string>
+    <string name="devicetype_q8">Q8</string>
+    <string name="devicetype_mykronoz_zetime">MyKronoz ZeTime</string>
+    <string name="devicetype_id115">ID115</string>
+    <string name="devicetype_watch9">Watch 9</string>
+    <string name="devicetype_roidmi">Roidmi</string>
+    <string name="devicetype_roidmi3">Roidmi 3</string>
+    <string name="menuitem_notifications">Upozorn臎n铆</string>
+    <string name="menuitem_music">Hudba</string>
+    <string name="menuitem_more">V铆ce</string>
+    <string name="watch9_time_minutes">Minuty:</string>
+    <string name="watch9_time_hours">Hodiny:</string>
+    <string name="watch9_time_seconds">Vte艡iny:</string>
+    <string name="watch9_calibration_hint">Nastavit 膷as aktu谩ln臎 zobrazen媒 na za艡铆zen铆.</string>
+    <string name="watch9_calibration_button">Kalibrovat</string>
+    <string name="title_activity_watch9_pairing">P谩rov谩n铆 Watch 9</string>
+    <string name="title_activity_watch9_calibration">Kalibrace Watch 9</string>
+    <string name="pref_title_contextual_arabic">Kontextu谩ln铆 arab拧tina</string>
+    <string name="pref_summary_contextual_arabic">Zapnout podporu pro kontextu谩ln铆 arab拧tinu</string>
+    <string name="preferences_rtl_settings">Podpora jazyk暖 zprava doleva</string>
+    <string name="share_log">Sd铆let protokol</string>
+    <string name="share_log_warning">Nezapome艌te pros铆m, 啪e ladic铆 z谩znamy Gadgetbridge mohou obsahovat osobn铆 informace, zahrnuj铆c铆 nap艡铆klad zdravotn铆 data, identifik谩tory (nap艡铆klad adresu MAC), hudebn铆 preference a podobn臎. Tyto informace m暖啪ete vymazat p艡ed odesl谩n铆m souboru do ve艡ejn茅ho hl谩拧en铆 o chyb臎.</string>
+    <string name="warning">Upozorn臎n铆!</string>
+    <string name="no_data">沤谩dn谩 data</string>
+    <string name="preferences_led_color">Barva LED</string>
+    <string name="preferences_fm_frequency">Frekvence FM</string>
+    <string name="pref_invalid_frequency_title">Neplatn谩 frekvence</string>
+    <string name="pref_invalid_frequency_message">Nastavte frekvenci mezi 87.5 a 108.0</string>
+    <string name="language_and_region_prefs">Jazyk a m铆stn铆 nastaven铆</string>
+    <string name="debugactivity_really_factoryreset_title">Opravdu uv茅st do tov谩rn铆ho nastaven铆\?</string>
+    <string name="debugactivity_really_factoryreset">Uveden铆 do tov谩rn铆ho nastaven铆 sma啪e z p艡ipojen茅ho za艡铆zen铆 v拧echna data (je-li toto podporov谩no). Xiaomi/Huami tak茅 provedou zm臎nu Bluetooth MAC adresy a budou se pro Gadgetbridge tv谩艡it jako nov谩 za艡铆zen铆.</string>
+    <string name="devicetype_casiogb6900">Casio GB-6900</string>
+    <string name="activity_type_exercise">Cvi膷en铆</string>
+    <string name="title_activity_notification_filter">Filtr ozn谩men铆</string>
+    <string name="edittext_notification_filter_words_hint">Zadejte po啪adovan谩 slova, jedno na 艡谩dek</string>
+    <string name="toast_notification_filter_saved_successfully">Filtr ozn谩men铆 ulo啪en</string>
+    <string name="filter_mode_none">Nefiltrovat</string>
+    <string name="filter_mode_whitelist">Zobrazit p艡i v媒skytu slov</string>
+    <string name="filter_mode_blacklist">Nezobrazit p艡i v媒skytu slov</string>
+    <string name="filter_submode_at_least_one">Alespo艌 jedno ze slov</string>
+    <string name="filter_submode_all">V拧echna slova</string>
+    <string name="toast_notification_filter_words_empty_hint">Zadejte pros铆m alespo艌 jedno slovo</string>
+    <string name="filter_mode">Re啪im filtrov谩n铆</string>
+    <string name="mode_configuration">Konfigurace re啪imu</string>
+    <string name="save_configuration">Ulo啪it konfiguraci</string>
+    <string name="appwidget_not_connected">Nep艡ipojeno, bud铆ky neodesl谩ny.</string>
+    <string name="prefs_disconnect_notification">Upozorn臎n铆 p艡i odpojen铆</string>
+    <string name="zetime_title_settings">Nastaven铆 ZeTime</string>
+    <string name="zetime_title_heartrate">Nastaven铆 me艡en铆 tepu</string>
+    <string name="zetime_title_screentime">Doba (vte艡in) zapnut铆 obrazovky</string>
+    <string name="zetime_title_heart_rate_alarm">Poplach m臎艡en铆 tepu</string>
+    <string name="zetime_title_heart_rate_alarm_summary">Hodinky spust铆 varov谩n铆 p艡i p艡ekro膷en铆 limitu tepov茅 frekvence.</string>
+    <string name="zetime_heart_rate_alarm_enable">Povolit poplach m臎艡en铆 tepu</string>
+    <string name="activity_prefs_alarm_max_heart_rate">Maxim谩ln铆 tepov谩 frekvence</string>
+    <string name="activity_prefs_alarm_min_heart_rate">Minim谩ln铆 tepov谩 frekvence</string>
+    <string name="zetime_analog_mode">Analogov媒 m贸d</string>
+    <string name="zetime_analog_mode_hands">Pouze ru膷i膷ky</string>
+    <string name="zetime_analog_mode_handsandsteps">Ru膷i膷ky a kroky</string>
+    <string name="zetime_activity_tracking">Sledov谩n铆 aktivity</string>
+    <string name="zetime_activity_tracking_summary">Zap铆n谩m sledov谩n铆 aktivity - po膷铆t谩n铆 krok暖 a podobn臎.</string>
+    <string name="zetime_handmove_display">Pohyb ruky</string>
+    <string name="zetime_handmove_display_summary">Zarotujte z谩p臎st铆m pro aktivaci/deaktivaci displeje.</string>
+    <string name="zetime_calories_type">Nastaven铆 kalori铆</string>
+    <string name="zetime_calories_type_active">Pouze sp谩len茅 kalorie</string>
+    <string name="zetime_calories_type_all">Aktivn铆 a neaktivn铆 sp谩len茅 kalorie</string>
+    <string name="zetime_date_format">Form谩t data</string>
+    <string name="zetime_date_format_1">YY/MM/DD</string>
+    <string name="zetime_date_format_2">DD/MM/YY</string>
+    <string name="zetime_date_format_3">MM/DD/YY</string>
+    <string name="zetime_prefs_inactivity_repetitions">Opakov谩n铆</string>
+    <string name="zetime_prefs_inactivity_mo">Pond臎l铆</string>
+    <string name="zetime_prefs_inactivity_tu">脷ter媒</string>
+    <string name="zetime_prefs_inactivity_we">St艡eda</string>
+    <string name="zetime_prefs_inactivity_th">膶tvrtek</string>
+    <string name="zetime_prefs_inactivity_fr">P谩tek</string>
+    <string name="zetime_prefs_inactivity_sa">Sobota</string>
+    <string name="zetime_prefs_inactivity_su">Ned臎le</string>
+    <string name="zetime_title_alarm_signaling">Nastavte druh sign谩lu poplachu</string>
+    <string name="zetime_signaling_none">Tich媒</string>
+    <string name="zetime_signaling_vibrate">Opakovan茅 vibrace</string>
+    <string name="zetime_signaling_beep">Opakovan茅 p铆p谩n铆</string>
+    <string name="zetime_signaling_vibrate_beep">Opakovan茅 p铆p谩n铆 a vibrace</string>
+    <string name="zetime_signaling_vibrate_once">Vibrovat jednou</string>
+    <string name="zetime_signaling_vibrate_twice">Vibrovat dvakr谩t</string>
+    <string name="zetime_signaling_beep_once">P铆pnout jednou</string>
+    <string name="zetime_signaling_beep_twice">P铆pnout dvakr谩t</string>
+    <string name="zetime_signaling_vibrate_beep_once">Vibrovat a p铆pnout jednou</string>
+    <string name="pref_screen_notification_profile_missed_call">Ozn谩men铆 zme拧kan媒ch vol谩n铆</string>
+    <string name="pref_screen_notification_profile_calendar">Ozn谩men铆 kalend谩艡e</string>
+    <string name="pref_screen_notification_profile_inactivity">Ozn谩men铆 o neaktivit臎</string>
+    <string name="pref_screen_notification_profile_low_power">Varov谩n铆 p艡i n铆zk茅m stavu baterie</string>
+    <string name="pref_screen_notification_profile_anti_loss">Varov谩n铆 p艡i ztr谩t臎</string>
+    <string name="interval_fifteen_minutes">ka啪d媒ch 15 minut</string>
+    <string name="interval_forty_five_minutes">ka啪d媒ch 45 minut</string>
+    <string name="activity_prefs_calories_burnt">Denn铆 c铆l: sp谩len茅 kalorie</string>
+    <string name="activity_prefs_distance_meters">Denn铆 c铆l: vzd谩lenost v metrech</string>
+    <string name="activity_prefs_activetime_minutes">Denn铆 c铆l: aktivita v minut谩ch</string>
+    <string name="devicetype_miscale2">Mi Scale 2</string>
+    <string name="pref_title_support_voip_calls">Povolit upozorn臎n铆 VOIP hovor暖</string>
+    <string name="title_activity_device_specific_settings">Nastaven铆 pro jednotliv谩 za艡铆zen铆</string>
+    <string name="pref_title_authkey">Autentiza膷n铆 kl铆膷</string>
+    <string name="pref_summary_authkey">Nastavte stejn媒 kl铆膷 na v拧ech Va拧ich za艡铆zen铆ch s Androidem, ze kter媒ch se budete p艡ipojovat. V媒choz铆 kl铆膷 pro v拧echna za艡铆zen铆 je 0123456789@ABCDE</string>
+    <string name="devicetype_bfh16">BFH-16</string>
+    <string name="fw_upgrade_notice_amazfitcor2">Chyst谩te se nainstalovat firmware %s na v谩拧 Amazfit Cor 2. \n \nNainstalujte nejprve soubor .fw a pot茅 soubor .res. V谩拧 n谩ramek se po instalaci souboru .fw restartuje. \n \nPozn谩mka: soubor .res nemus铆te instalovat, pokud je toto啪n媒 s ji啪 d艡铆ve nainstalovan媒m. \n \nPOKRA膶UJTE NA VLASTN脥 NEBEZPE膶脥! \n \nNEN脥 V女BEC OTESTOV脕NO, POKUD JE N脕ZEV VA艩EHO ZA艠脥ZEN脥 鈥濧mazfit Band 2鈥�, NEJSP脥艩E MUS脥TE FLASHNOUT FIRMWAER BEATS_W</string>
+    <string name="dutch">Nizozem拧tina</string>
+    <string name="turkish">Ture膷tina</string>
+    <string name="ukrainian">Ukrajin拧tina</string>
+    <string name="arabic">Arab拧tina</string>
+    <string name="indonesian">Indon茅拧tina</string>
+    <string name="thai">Thaj拧tina</string>
+    <string name="vietnamese">Vietnam拧tina</string>
+    <string name="portuguese">Portugal拧tina</string>
+    <string name="devicetype_amazfit_cor2">Amazfit Cor 2</string>
+    <string name="devicetype_miband4">Mi Band 4</string>
+    <string name="fw_upgrade_notice_miband4">Chyst谩te se nainstalovat firmware %s na v谩拧 Mi Band 4.
+\n
+\nNainstalujte nejprve soubor .fw a pot茅 soubor .res. V谩拧 n谩ramek se po instalaci souboru .fw restartuje.
+\n
+\nPozn谩mka: soubor .res nemus铆te instalovat, pokud je toto啪n媒 s ji啪 d艡铆ve nainstalovan媒m.
+\n
+\nPOKRA膶UJTE NA VLASTN脥 NEBEZPE膶脥!</string>
+    <string name="prefs_hr_alarm_activity">Alarm m臎艡en铆 tepu p艡i sportovn铆 aktivit臎</string>
+    <string name="prefs_hr_alarm_low">Spodn铆 hodnota</string>
+    <string name="prefs_hr_alarm_high">Horn铆 limit</string>
+    <string name="average">Pr暖m臎r: %1$s</string>
+    <string name="pref_header_charts">Grafy</string>
+    <string name="pref_title_charts_average">Zobrazovat v grafech pr暖m臎ry</string>
+    <string name="pref_title_charts_range">膶asov茅 obdob铆 graf暖</string>
+    <string name="pref_charts_range_on">Rozsah nastaven na m臎s铆c</string>
+    <string name="pref_charts_range_off">Rozsah nastaven na t媒den</string>
+    <string name="weekstepschart_steps_a_month">Kroky za m臎s铆c</string>
+    <string name="weeksleepchart_sleep_a_month">Sp谩nek za m臎s铆c</string>
+    <string name="devicetype_mijia_lywsd02">Mijia Smart Clock</string>
+    <string name="menuitem_nfc">NFC</string>
+    <string name="pref_summary_expose_hr">Umo啪n铆 ostatn铆m aplikac铆m p艡istupovat k dat暖m 膷idla srde膷n铆ho tepu dokud Gadgetbridge b臎啪铆</string>
+    <string name="pref_title_expose_hr">P艡铆stup ciz铆ch aplikac铆 k 膷idlu tepu</string>
+    <string name="pref_title_use_custom_font">Vyu啪铆t vlastn铆 font</string>
+    <string name="pref_summary_use_custom_font">Va拧e za艡铆zen铆 m谩 vlastn铆 font s podporou emoji</string>
+    <string name="activity_db_management_autoexport_explanation">Um铆st臎n铆 automatick茅ho exportu datab谩ze bylo nastaveno na:</string>
+    <string name="activity_db_management_autoexport_label">Automatick媒 export</string>
+    <string name="activity_DB_ExportButton">Exportovat data</string>
+    <string name="activity_DB_import_button">Importovat data</string>
+    <string name="activity_DB_test_export_button">Spustit automatick媒 export nyn铆</string>
+    <string name="activity_DB_test_export_message">Exportuji datab谩zi鈥�</string>
+    <string name="activity_DB_delete_legacy_button">Vymazat starou datab谩zi</string>
+    <string name="activity_DB_empty_button">Vymazat datab谩zi</string>
+    <string name="activity_db_management_empty_DB">Vymazat datab谩zi</string>
+    <string name="activity_db_management_exportimport_label">Export a import</string>
+    <string name="activity_db_management_empty_db_warning">Varov谩n铆! Stisknut铆m tohoto tla膷铆tka vyma啪ete v拧echna data a za膷nete od za膷谩tku.</string>
+    <string name="appwidget_sleep_alarm_widget_label">Bud铆k sp谩nku</string>
+    <string name="widget_listing_label">Stav a bud铆ky</string>
+    <string name="widget_set_alarm_after">Nastavit zvon臎n铆 za:</string>
+    <string name="widget_5_minutes">5 minut</string>
+    <string name="widget_10_minutes">10 minut</string>
+    <string name="widget_20_minutes">20 minut</string>
+    <string name="widget_1_hour">1 hodinu</string>
+    <string name="pref_display_add_device_fab">Tla膷铆tko P艡ipojit nov茅 za艡铆zen铆</string>
+    <string name="pref_display_add_device_fab_on">V啪dy viditeln茅</string>
+    <string name="pref_display_add_device_fab_off">Viditeln茅 pouze pokud nen铆 p艡id谩no 啪谩dn茅 za艡铆zen铆</string>
+    <plurals name="widget_alarm_target_hours" tools:ignore="MissingQuantity">
+        <item quantity="one">%d hodina</item>
+        <item quantity="few">%d hodiny</item>
+        <item quantity="other">%d hodin</item>
+    </plurals>
+    <string name="activity_error_no_app_for_gpx">Pro zobrazen铆 trasy aktivity nainstalujte aplikaci, kter谩 um铆 zobrazit GPX soubory.</string>
+    <string name="preferences_makibes_hr3_settings">Nastaven铆 Makibes HR3</string>
+    <string name="devicetype_makibes_hr3">Makibes HR3</string>
+    <string name="devicetype_amazfit_bip_lite">Amazfit Bip Lite</string>
+    <string name="prefs_find_phone">Vyhledat telefon</string>
+    <string name="prefs_enable_find_phone">Zapnout \'Vyhled谩n铆 telefonu\'</string>
+    <string name="prefs_find_phone_summary">Pou啪ijte n谩ramek/hodinky k prozvon臎n铆 telefonu.</string>
+    <string name="prefs_find_phone_duration">D茅lka zvon臎n铆 ve vte艡in谩ch</string>
+    <string name="maximum_duration">D茅lka</string>
+    <string name="discovery_need_to_enter_authkey">Toto za艡铆zen铆 vy啪aduje tajn媒 kl铆膷, podr啪te dlouze na jm茅nu za艡铆zen铆 pro jeho vlo啪en铆. Pro v铆ce detail暖 膷t臎te wiki.</string>
+    <string name="fw_upgrade_notice_amazfitbip_lite">Chyst谩te se nainstalovat firmware %s na v谩拧 Amazfit Bip Lite.
+\n
+\nNainstalujte nejprve soubor .fw a pot茅 soubor .res. Va拧e hodinky se po instalaci souboru .fw restartuj铆.
+\n
+\nPozn谩mka: soubor .res nemus铆te instalovat, pokud je toto啪n媒 s ji啪 d艡铆ve nainstalovan媒m.
+\n
+\nPOKRA膶UJTE NA VLASTN脥 NEBEZPE膶脥!</string>
+    <string name="devicetype_amazfit_gtr">Amazfit GTR</string>
+    <string name="fw_upgrade_notice_amazfitgtr">Chyst谩te se nainstalovat firmware %s na v谩拧 Amazfit GTR.
+\n
+\nNainstalujte nejprve soubor .fw, pot茅 soubor .res a nakonec soubor .gps. Va拧e hodinky se po instalaci souboru .fw restartuj铆.
+\n
+\nPozn谩mka: soubory .res a .gps nemus铆te instalovat, pokud jsou toto啪n茅 s ji啪 d艡铆ve nainstalovan媒mi.
+\n
+\nPOKRA膶UJTE NA VLASTN脥 NEBEZPE膶脥!</string>
+    <string name="pref_chart_heartrate_color_red">膶erven谩</string>
+    <string name="pref_chart_heartrate_color_orange">Oran啪ov谩</string>
+    <string name="pref_title_chart_heartrate_color">Barva tepov茅 frekvence</string>
+    <string name="pref_title_chart_sleep_rolling_24_hour">Rozsah pro sp谩nek</string>
+    <string name="pref_chart_sleep_rolling_24_on">P艡edchoz铆ch 24 hodin</string>
+    <string name="pref_chart_sleep_rolling_24_off">Od poledne k poledni</string>
+    <string name="devicetype_amazfit_gts">Amazfit GTS</string>
+    <string name="fw_upgrade_notice_amazfitgts">Chyst谩te se nainstalovat firmware %s na v谩拧 Amazfit GTS.
+\n
+\nNainstalujte nejprve soubor .fw, pot茅 soubor .res a nakonec soubor .gps. Va拧e hodinky se po instalaci souboru .fw restartuj铆.
+\n
+\nPozn谩mka: soubory .res a .gps nemus铆te instalovat, pokud jsou toto啪n茅 s ji啪 d艡铆ve nainstalovan媒mi.
+\n
+\nPOKRA膶UJTE NA VLASTN脥 NEBEZPE膶脥!</string>
+    <string name="mi2_prefs_button_press_broadcast_default_value" translatable="false">nodomain.freeyourgadget.gadgetbridge.ButtonPressed</string>
+    <string name="devicetype_qhybrid">Fossil Q Hybrid</string>
+    <string name="preferences_qhybrid_settings">Nastaven铆 Q Hybrid</string>
+    <string name="watch_not_connected">Hodinky nejsou p艡ipojeny</string>
+    <string name="qhybrid_vibration_strength">s铆la vibrace:</string>
+    <string name="qhybrid_goal_in_steps">C铆l v kroc铆ch</string>
+    <string name="qhybrid_time_shift">膷asov媒 posun</string>
+    <string name="qhybrid_second_timezone_offset_relative_to_utc">posun dal拧铆ho 膷asov茅ho p谩sma vzhledem k UTC</string>
+    <string name="qhybrid_overwrite_buttons">p艡epsat tla膷铆tka</string>
+    <string name="qhybrid_use_activity_hand_as_notification_counter">pou啪铆t ruku aktivity jako 膷铆ta膷 ozn谩men铆</string>
+    <string name="qhybrid_prompt_million_steps">Chcete-li aktivovat po膷铆tadlo, nastavte po膷et krok暖 na milion.</string>
+    <string name="qhybrid_buttons_overwrite_success">Tla膷铆tka p艡eps谩na</string>
+    <string name="qhybrid_buttons_overwrite_error">Chyba p艡i p艡episov谩n铆 tla膷铆tek</string>
+    <string name="qhybrid_offset_timezone">posun 膷asov茅ho p谩sma podle</string>
+    <string name="qhybrid_changes_delay_prompt">zm臎na m暖啪e trvat n臎kolik sekund鈥�</string>
+    <string name="qhybrid_offset_time_by">posun 膷asu o</string>
+    <string name="pref_disable_new_ble_scanning">Zak谩zat nov茅 BLE skenov谩n铆</string>
+    <string name="pref_summary_disable_new_ble_scanning">Tuto mo啪nost za拧krtn臎te pokud Va拧e za艡铆zen铆 nelze naj铆t b臎hem vyhled谩v谩n铆</string>
+    <string name="devicetype_banglejs">Bangle.js</string>
+    <string name="devicetype_y5">Y5</string>
+    <string name="prefs_button_single_press_action_selection_title">Akce ud谩losti 1</string>
+    <string name="prefs_button_double_press_action_selection_title">Akce ud谩losti 2</string>
+    <string name="prefs_button_triple_press_action_selection_title">Akce ud谩losti 3</string>
+    <string name="prefs_button_variable_actions">Detailn铆 nastaven铆 stisku tla膷铆tka</string>
+    <string name="prefs_button_long_press_action_selection_title">Akce pro dlouh媒 stisk</string>
+    <string name="alarm_snooze">Odlo啪it</string>
+    <string name="error_no_location_access">P艡铆stup k poloze mus铆 b媒t d铆ky syst茅mu Android p艡铆stup暖 povolen a zapnut, aby bylo mo啪no spr谩vn臎 naskenovat za艡铆zen铆</string>
+    <string name="devicetype_itag">iTag</string>
+    <string name="pref_title_allow_high_mtu">Povolit vy拧拧铆 MTU</string>
+    <string name="pref_summary_allow_high_mtu">Zrychluje p艡enosovou rychlost, ale na n臎kter媒ch Android za艡铆zen铆 nemus铆 pracovat spr谩vn臎.</string>
+    <string name="pref_summary_sync_calendar">Umo啪艌uje upozorn臎n铆 kalend谩艡e, i p艡i odpojen茅m za艡铆zen铆</string>
+    <string name="pref_title_sync_caldendar">Synchronizace ud谩losti kalend谩艡e</string>
+    <string name="hr_widget_heart_rate">Tepov谩 frekvence</string>
+    <string name="hr_widget_steps">Kroky</string>
+    <string name="hr_widget_date">Datum</string>
+    <string name="hr_widget_active_minutes">Minuty aktivity</string>
+    <string name="hr_widget_calories">Kalorie</string>
+    <string name="hr_widget_battery">Baterie</string>
+    <string name="hr_widget_weather">Po膷as铆</string>
+    <string name="hr_widget_nothing">Nic</string>
+    <string name="find_lost_device_you_found_it">Nalezeno!</string>
+    <string name="pref_title_force_white_color_scheme">Vynutit barevn茅 sch茅ma 膶ern谩 na b铆l茅</string>
+    <string name="pref_summary_force_white_color_scheme">U啪ite膷n茅, pokud m谩te hodinky s tmav媒mi ru膷i膷kami</string>
+    <string name="find_my_phone_notification">Naj铆t telefon</string>
+    <string name="notification_channel_high_priority_name">Vysok谩 priorita</string>
+    <string name="pref_title_custom_deviceicon">V notifika膷n铆 li拧t臎 pou啪铆t ikonu za艡铆zen铆</string>
+    <string name="pref_summary_custom_deviceicon">V notifika膷n铆 li拧t臎 Androidu zobrazit ikonu konkr茅tn铆ho p艡ipojen茅ho za艡铆zen铆 nam铆sto ikony Gadgetbridge</string>
+    <string name="hr_appname_wellness">Wellness</string>
+    <string name="hr_appname_workout">Cvi膷en铆</string>
+    <string name="hr_appname_stopwatch">Stopky</string>
+    <string name="hr_appname_commute">Doj铆啪d臎n铆</string>
+    <string name="pref_title_vibration_strength">S铆la vibrac铆</string>
+    <string name="devicetype_amazfit_bips">Amazfit Bip S</string>
+    <string name="pref_title_relax_firmware_checks">Aktivujte tuto mo啪nost, pokud chcete flashovat firmware, kter媒 nen铆 ur膷en pro va拧e za艡铆zen铆 (na vlastn铆 nebezpe膷铆)</string>
+    <string name="pref_summary_relax_firmware_checks">Uvoln臎te kontroly firmwaru</string>
+    <string name="pref_qhybrid_title_widget_draw_circles">Vykreslit kruhy widgetu</string>
+    <string name="pref_header_auto_fetch">Stahovat automaticky</string>
+    <string name="menuitem_pai">PAI</string>
+    <string name="pref_qhybrid_save_raw_activity_files">Ulo啪it soubory nezpracovan媒ch dat aktivit</string>
+    <string name="menuitem_workout">Cvi膷en铆</string>
+    <string name="menuitem_eventreminder">P艡ipomenut铆 ud谩losti</string>
+    <string name="menuitem_hr">Tepov谩 frekvence</string>
+    <string name="devicetype_watchxplus">Watch X Plus</string>
+    <string name="devicetype_watchx">Watch X</string>
+    <string name="power_mode_watch">Pouze hodinky</string>
+    <string name="power_mode_saving">脷spora energie</string>
+    <string name="power_mode_normal">Norm谩ln铆</string>
+    <string name="power_mode_title">Re啪im nap谩jen铆 hodinek</string>
+    <string name="prefs_sensors_button_bp_calibration_sum">Stisknut铆m zde zah谩j铆te kalibraci</string>
+    <string name="prefs_sensors_button_bp_calibration">Kalibrace</string>
+    <string name="pref_sensors_bp_calibration_high">Krevn铆 tlak SYSTOLIC (vysok媒)</string>
+    <string name="pref_sensors_bp_calibration_low">Krevn铆 tlak DIASTOLIC (n铆zk媒)</string>
+    <string name="pref_sensors_bp_calibration">Kalibrace krevn铆ho tlaku</string>
+    <string name="pref_title_sensors_altitude">Kalibrace nadmo艡sk茅 v媒拧ky</string>
+    <string name="pref_header_sensors_calibration">Kalibrace senzoru</string>
+    <string name="pref_title_device_spec_settings_show_raw_graph">Zobrazit nezpracovan谩 data v grafu aktivity</string>
+    <string name="pref_summary_device_spec_settings_title_force_time">Vynutit automatickou synchronizaci 膷asu p艡i op臎tovn茅m p艡ipojen铆. Analogov茅 ru膷i膷ky mohou ukazovat nespr谩vn媒 膷as!</string>
+    <string name="pref_title_device_spec_settings_force_time">Vynutit synchronizaci 膷asu</string>
+    <string name="pref_header_device_spec_settings">Nastaven铆 za艡铆zen铆</string>
+    <string name="pref_summary_notifications_and_calls_title_shake_reject">Duplikuje akce tla膷铆tka hodinek</string>
+    <string name="prefs_notifications_and_calls_shake_reject">Pot艡谩st z谩p臎st铆m pro ignorov谩n铆/odm铆tnut铆 hovoru</string>
+    <string name="pref_summary_notifications_and_calls_title_reject">Vypnuto - ignorovat, Zapnuto - odm铆tnout</string>
+    <string name="prefs_notifications_and_calls_reject">Tla膷铆tko ignorovat / odm铆tnout hovor</string>
+    <string name="pref_header_notifications_and_calls_callhandling">Zpracov谩n铆 hovor暖</string>
+    <string name="pref_title_notifications_and_calls_repeat_on_misscall">Opakuje se po dobu X minut</string>
+    <string name="pref_summary_notifications_and_calls_enable_misscall">Opakuje se ka啪dou minutu</string>
+    <string name="pref_notifications_and_calls_enable_misscall">Upozornit na zme拧kan媒 hovor</string>
+    <string name="prefs_notifications_and_calls_continious_ring">Ozn谩men铆 b臎hem vyzv谩n臎n铆 telefonu</string>
+    <string name="pref_title_notifications_and_calls_repeat_on_call">Opakov谩n铆 ozn谩men铆 o vol谩n铆</string>
+    <string name="pref_header_notifications_and_calls">Ozn谩men铆 a vol谩n铆</string>
+    <string name="title_activity_LenovoWatch_calibration">Kalibrace Watch X Plus</string>
+    <string name="czesh">膶e拧tina</string>
+    <string name="swedish">艩v茅d拧tina</string>
+    <string name="hebrew">Hebrej拧tina</string>
+    <string name="greek">艠e膷tina</string>
+    <string name="hungarian">Ma膹ar拧tina</string>
+    <string name="romanian">Rumun拧tina</string>
+    <string name="pref_theme_system">Syst茅mov媒</string>
+    <string name="fw_upgrade_notice_amazfit_trex">Chyst谩te se nainstalovat firmware %s na v谩拧 Amazfit T-Rex.
+\n
+\nNainstalujte nejprve soubor .fw, pot茅 soubor .res a nakonec soubor .gps. Va拧e hodinky se po instalaci souboru .fw restartuj铆.
+\n
+\nPozn谩mka: soubory .res a .gps nemus铆te instalovat, pokud jsou toto啪n茅 s ji啪 d艡铆ve nainstalovan媒mi.
+\n
+\nPOKRA膶UJTE NA VLASTN脥 NEBEZPE膶脥!</string>
+    <string name="hr_widget_last_notification">Posledn铆 ozn谩men铆</string>
+    <string name="menuitem_unknown">Nezn谩m茅</string>
+    <string name="devicetype_amazfit_trex">Amazfit T-Rex</string>
+    <string name="bip_prefs_shotcuts_summary">Vyberte odkazy na displeji</string>
+    <string name="bip_prefs_shortcuts">Zkratky</string>
+    <string name="controlcenter_set_alias">Nastavit p艡ezd铆vku</string>
+    <string name="about_additional_contributions">Mnohokr谩t d臎kujeme v拧em v媒拧e nezm铆n臎n媒m p艡isp臎vatel暖m za k贸d, p艡eklady, podporu, n谩pady, motivaci, hl谩拧en铆 o chyb谩ch, pen铆ze鈥� 鉁�</string>
+    <string name="about_additional_device_support">Podpora dal拧铆ch za艡铆zen铆</string>
+    <string name="about_contributors">P艡isp臎vatel茅</string>
+    <string name="about_core_team_title">Centr谩ln铆 t媒m (v po艡ad铆 p艡铆sp臎vku prvn铆ho k贸du)</string>
+    <string name="about_description_generic">Svobodn谩 a lok谩ln铆 n谩hrada za uzav艡enou aplikaci va拧ich hodinek/n谩ramku.</string>
+    <string name="about_activity_title_generic">O aplikaci Gadgetbridge</string>
+    <string name="about_title">O aplikaci</string>
+    <string name="menuitem_worldclock">Sv臎tov媒 膶as</string>
+    <string name="pref_title_weather_summary">Pou啪ito pro poskytovatele po膷as铆 v syst茅mu LineageOS. Ostatn铆 verze Androidu vy啪aduj铆 aplikaci jako nap艡铆klad 鈥濿eather Notification鈥�. V铆ce informac铆 naleznete na wiki Gadgetbridge.</string>
+    <string name="error_version_check_extreme_caution">POZOR: Chyba p艡i kontrole verze! Nedoporu膷ujeme pokra膷ovat! Verze m谩 n谩zev 鈥�%s鈥�</string>
+    <string name="permission_granting_mandatory">V拧echna tato opr谩vn臎n铆 jsou vy啪adov谩na a pokud nebudou ud臎lena, m暖啪e doj铆t k nestabilit臎</string>
+    <string name="activity_detail_show_gps_label">Zobrazit trasu GPS</string>
+    <string name="activity_detail_duration_label">Doba trv谩n铆</string>
+    <string name="activity_detail_end_label">Konec</string>
+    <string name="activity_detail_start_label">Za膷谩tek</string>
+    <string name="Steps">Kroky</string>
+    <string name="Activity">Aktivita</string>
+    <string name="Speed">Rychlost</string>
+    <string name="Elevation">Elevace</string>
+    <string name="Distance">Vzd谩lenost</string>
+    <string name="Swimming">Plav谩n铆</string>
+    <string name="Strokes">Z谩b臎ry</string>
+    <string name="bpm">tep暖/min</string>
+    <string name="minutes_km">min/km</string>
+    <string name="seconds_km">s/km</string>
+    <string name="calories_unit">kcal</string>
+    <string name="laps_unit">baz茅n暖</string>
+    <string name="swim_style">plaveck媒 styl</string>
+    <string name="swolf_index">swolf index</string>
+    <string name="seconds">sek</string>
+    <string name="strokes_unit">z谩b</string>
+    <string name="strokes_second">z谩b/s</string>
+    <string name="km_h">km/h</string>
+    <string name="meters_second">m/s</string>
+    <string name="steps_unit">krok暖</string>
+    <string name="cm">cm</string>
+    <string name="meters">m</string>
+    <string name="flatSeconds">V rovin臎</string>
+    <string name="ascentSeconds">Stoupaj铆c铆</string>
+    <string name="laps">Baz茅n暖</string>
+    <string name="swimStyle">Plaveck媒 styl</string>
+    <string name="swolfIndex">SWOLF</string>
+    <string name="averageLapPace">Pr暖m臎rn茅 tempo kola</string>
+    <string name="averageStrokesPerSecond">Pr暖m臎rn茅 Z谩b臎ry</string>
+    <string name="averageStrokeDistance">Pr暖m臎rn谩 D茅lka Z谩b臎ru</string>
+    <string name="averageStride">Pr暖m臎rn媒 Krok</string>
+    <string name="averageKMPaceSeconds">Tempo</string>
+    <string name="averageHR">Tepov谩 frekvence</string>
+    <string name="totalStride">Celkov臎 ujito</string>
+    <string name="maxPace">Nejrychlej拧铆 Tempo</string>
+    <string name="minPace">Nejpomalej拧铆 Tempo</string>
+    <string name="maxSpeed">Maxim谩ln铆</string>
+    <string name="caloriesBurnt">Kalorie</string>
+    <string name="activeSeconds">Aktivn铆</string>
+    <string name="steps">Kroky</string>
+    <string name="minAltitude">Nejn铆啪e</string>
+    <string name="maxAltitude">Nejv媒拧e</string>
+    <string name="descentMeters">Z kopce</string>
+    <string name="ascentMeters">Do kopce</string>
+    <string name="distanceMeters">Vzd谩lenost</string>
+    <string name="error_location_enabled_mandatory">Chcete-li vyhledat za艡铆zen铆, Poloha mus铆 b媒t zapnut谩</string>
+    <string name="ignore_bonded_devices_description">Povolen铆m t茅to mo啪nosti se budou ignorovat za艡铆zen铆, kter谩 byla p艡i skenov谩n铆 ji啪 spojena/sp谩rov谩na</string>
+    <string name="ignore_bonded_devices">Ignorovat p艡ipojen谩 za艡铆zen铆</string>
+    <string name="error_retrieving_devices_database">P艡i na膷铆t谩n铆 za艡铆zen铆 z datab谩ze do拧lo k chyb臎</string>
+    <string name="error_setting_alias">Chyba p艡i nastavov谩n铆 p艡ezd铆vky:</string>
+    <string name="error_exporting_device_preferences">P艡i exportu p艡edvoleb specifick媒ch pro za艡铆zen铆 do拧lo k chyb臎</string>
+    <string name="pref_check_permission_status_summary">Kontroluje chyb臎j铆c铆 opr谩vn臎n铆 a 啪谩d谩 o n臎, i kdy啪 nemus铆 b媒t okam啪it臎 pot艡eba. Tuto funkci zaka啪te pouze v p艡铆pad臎, 啪e va拧e za艡铆zen铆 n臎kterou z t臎chto funkc铆 skute膷n臎 nepodporuje. Neud臎len铆 opr谩vn臎n铆 m暖啪e zp暖sobit probl茅my!</string>
+    <string name="pref_check_permission_status">Kontrolovat stav opr谩vn臎n铆</string>
+    <string name="error_background_service_reason">Spu拧t臎n铆 slu啪by na pozad铆 selhalo z d暖vodu chyby 鈥� klepn臎te sem pro v铆ce informac铆.\n\nChyba:</string>
+    <string name="device_requires_key">VY沤ADOV脕N KL脥膶, ZADEJTE PODR沤EN脥M</string>
+    <string name="device_is_currently_bonded">JI沤 SP脕ROV脕NO</string>
+    <string name="error_background_service_reason_truncated">Slu啪bu na pozad铆 se nepoda艡ilo spustit z d暖vodu鈥�</string>
+    <string name="error_background_service">Nepoda艡ilo se spustit slu啪bu na pozad铆</string>
+    <string name="companiondevice_pairing_details">Povoluje nov茅 rozhran铆 Doprovodn茅ho Za艡铆zen铆 (CompanionDevice API, toto existuje pouze v Android 8 a nov臎j拧铆m), kter茅 zlep拧uje spolehlivost p艡ipojen铆. Pro vyu啪it铆 tohoto rozhran铆 je nutn茅 znovu-nap谩rov谩n铆 za pou啪it铆 Gadgetbridge</string>
+    <string name="companiondevice_pairing">P谩rov谩n铆 syst茅mem doprovodn茅ho za艡铆zen铆</string>
+    <string name="require_location_provider">Poloha mus铆 b媒t povolena</string>
+    <string name="menuitem_stress">Stres</string>
+    <string name="menuitem_cycles">Menstrua膷n铆 kalend谩艡</string>
+    <string name="menuitem_breathing">D媒ch谩n铆</string>
+    <string name="devicetype_pinetime_jf">PineTime (JF Firmware)</string>
+    <string name="devicetype_tlw64">TLW64</string>
+    <string name="devicetype_miband5">Mi Band 5</string>
+    <string name="activity_summary_detail">Podrobnosti o sportovn铆ch aktivit谩ch</string>
+    <string name="fw_upgrade_notice_miband5">Chyst谩te se nainstalovat firmware %s na v谩拧 Mi Band 5.
+\n
+\nNainstalujte nejprve soubor .fw a pot茅 soubor .res. V谩拧 n谩ramek se po instalaci souboru .fw restartuje.
+\n
+\nPozn谩mka: soubor .res nemus铆te instalovat, pokud je toto啪n媒 s ji啪 d艡铆ve nainstalovan媒m.
+\n
+\nPOKRA膶UJTE NA VLASTN脥 NEBEZPE膶脥!</string>
+    <string name="medley">Polohov媒 z谩vod</string>
+    <string name="backstroke">Znak</string>
+    <string name="freestyle">Voln媒 styl</string>
+    <string name="breaststroke">Prsa</string>
+    <string name="activity_summaries_statistics">Statistiky</string>
+    <string name="activity_filter_apply_filter">Pou啪铆t filtr</string>
+    <string name="activity_filter_filter_title">Filtr</string>
+    <string name="activity_filter_reset_filter">Resetovat filtr</string>
+    <string name="activity_filter_date_to">Do</string>
+    <string name="activity_filter_date_from">Od</string>
+    <string name="pref_header_statistics">Statistiky sportovn铆ch aktivit</string>
+    <string name="pref_header_filter">Filtr sportovn铆ch aktivit</string>
+    <string name="prefs_events_forwarding_action_title">Spustit akci</string>
+    <string name="prefs_events_forwarding_broadcast_title">Vys铆lac铆 broadcast zpr谩va</string>
+    <string name="prefs_events_forwarding_startnonwear">P艡i neno拧en铆</string>
+    <string name="prefs_events_forwarding_wokeup">P艡i probuzen铆</string>
+    <string name="prefs_events_forwarding_fellsleep">P艡i usnut铆</string>
+    <string name="prefs_events_forwarding_title">Akce za艡铆zen铆</string>
+    <string name="prefs_events_forwarding_summary">Pou啪ijte ud谩losti za艡铆zen铆 pro spou拧t臎n铆 akc铆 a vys铆l谩n铆 syst茅mu Android</string>
+    <string name="km">km</string>
+    <string name="seconds_m">sec/m</string>
+    <string name="activity_type_yoga">J贸ga</string>
+    <string name="activity_type_jump_roping">艩vihadlo</string>
+    <string name="activity_type_elliptical_trainer">Eliptick媒 trena啪茅r</string>
+    <string name="activity_type_indoor_cycling">S谩lov谩 Cyklistika</string>
+    <string name="activity_type_swimming_openwater">Plav谩n铆 (otev艡en谩 voda)</string>
+    <string name="activity_filter_name_contains">艩t铆tek</string>
+    <string name="activity_summary_edit_name_title">Upravit 拧t铆tek</string>
+    <string name="activity_summaries_all_activities">V拧echny aktivity</string>
+    <string name="activity_summary_yesterday">V膷era</string>
+    <string name="activity_summary_today">Dnes</string>
+    <string name="devicetype_sg2">Lemfo SG2</string>
+    <string name="activity_filter_to_placeholder">dnes</string>
+    <string name="activity_filter_from_placeholder">vzd谩len谩 minulost</string>
+    <string name="activity_summaries_all_devices">V拧echna za艡铆zen铆</string>
+    <string name="sports_activity_quick_filter_select">膶asov茅 obdob铆</string>
+    <string name="sports_activity_quick_filter_30days">30 dn铆</string>
+    <string name="sports_activity_quick_filter_7days">7 dn铆</string>
+    <string name="sports_activity_quick_filter_last_month">Minul媒 m臎s铆c</string>
+    <string name="sports_activity_quick_filter_this_month">Tento m臎s铆c</string>
+    <string name="sports_activity_quick_filter_last_week">Minul媒 t媒den</string>
+    <string name="sports_activity_quick_filter_this_week">Tento t媒den</string>
+    <string name="no">Ne</string>
+    <string name="yes">Ano</string>
+    <string name="activity_filter_individual_items">Jednotliv臎 vybran茅 polo啪ky</string>
+    <string name="addto_filter">P艡idat do filtru</string>
+    <string name="averageSpeed">Pr暖m臎rn谩 rychlost</string>
+    <string name="baseAltitude">Z谩kladn铆 nadmo艡sk谩 v媒拧ka</string>
+    <string name="about_links">Odkazy</string>
+    <string name="activity_type_badminton">Badminton</string>
+    <string name="activity_type_pingpong">Stoln铆 tenis</string>
+    <string name="activity_type_basketball">Basketbal</string>
+    <string name="activity_type_cricket">Kriket</string>
+    <string name="activity_type_rowing_machine">Veslovac铆 trena啪茅r</string>
+    <string name="activity_type_soccer">Fotbal</string>
+    <string name="sonyswr12_settings_title">Sony SWR12 nastaven铆</string>
+    <string name="sonyswr12_settings_low_vibration">N铆zk茅 vibrace povoleny</string>
+    <string name="sonyswr12_settings_stamina">Re啪im 煤spory energie je zapnut媒</string>
+    <string name="sonyswr12_settings_alarm_interval">Interval inteligentn铆ho alarmu v minut谩ch</string>
+    <string name="sonyswr12_settings_low_vibration_summary">Povolit n铆zkou intenzitu vibrac铆 na p谩smu</string>
+    <string name="sonyswr12_settings_stamina_summary">Re啪im 煤spory energie vyp铆n谩 periodick茅 automatick茅 m臎艡en铆 srde膷n铆 frekvence a t铆m prodlu啪uje pracovn铆 dobu</string>
+    <string name="sonyswr12_settings_alarm_interval_summary">Interval inteligentn铆ho alarmu je interval p艡ed nainstalovan媒m alarmem. V tomto intervalu se za艡铆zen铆 pokou拧铆 detekovat nejleh膷铆 f谩zi sp谩nku, aby probudil u啪ivatele</string>
+    <string name="devicetype_nut_mini">Nut mini</string>
+    <string name="chart_get_active_and_synchronize">Vykonejte n臎jakou aktivitu a synchronizujte za艡铆zen铆.</string>
+    <string name="chart_no_active_data">Nedetekov谩ny 啪谩dn茅 aktivity.</string>
+    <string name="descentSeconds">Doba sestupu</string>
+    <string name="devicetype_lefun">Lefun</string>
+    <string name="activity_error_no_app_for_png">Chcete-li sd铆let tento sn铆mek obrazovky, nainstalujte si aplikaci, kter谩 dok谩啪e zpracov谩vat nebo p艡ij铆mat obr谩zky.</string>
+    <string name="activity_prefs_chart_min_session_length">Minim谩ln铆 d茅lka aktivity (minut)</string>
+    <string name="activity_prefs_chart_max_idle_phase_length">D茅lka pauzy k odd臎len铆 aktivit (minut)</string>
+    <string name="activity_prefs_chart_min_steps_per_minute">Minim谩ln铆 po膷et krok暖 za minutu k detekci aktivity</string>
+    <string name="activity_prefs_chart_min_steps_per_minute_for_run">Minim谩ln铆 po膷et krok暖 za minutu k detekci b臎hu</string>
+    <string name="lefun_prefs_interface_language_title">Jazyk rozhran铆</string>
+    <string name="lefun_prefs_antilost_summary">艠em铆nek zavibruje p艡i odpojen铆 od telefonu</string>
+    <string name="lefun_prefs_antilost_title">Ochrana proti ztr谩t臎</string>
+    <string name="lefun_prefs_hydration_reminder_interval_title">Interval p艡ipomenut铆 hydratace (v minut谩ch)</string>
+    <string name="lefun_prefs_hydration_reminder_summary">N谩ramek v谩m vibracemi p艡ipomene, 啪e se m谩te nap铆t</string>
+    <string name="lefun_prefs_hydration_reminder_title">P艡ipomenut铆 hydratace</string>
+    <string name="firmware_update_progress">Pr暖b臎h nahr谩v谩n铆
+\n%1d%% p艡i %.2fkbps (pr暖m臎r %.2fkbps)
+\n膶谩st %1d z %1d</string>
+    <string name="devicestatus_upload_failed">Do拧lo k chyb臎 nahr谩v谩n铆</string>
+    <string name="devicestatus_upload_aborted">Nahr谩v谩n铆 p艡eru拧eno!</string>
+    <string name="devicestatus_upload_validating">Kontrola nahran媒ch dat</string>
+    <string name="devicestatus_upload_completed">Nahr谩v谩n铆 dokon膷eno</string>
+    <string name="devicestatus_disconnected">Za艡铆zen铆 odpojeno!</string>
+    <string name="devicestatus_disconnecting">Za艡铆zen铆 se odpojuje!</string>
+    <string name="devicestatus_upload_started">Nahr谩v谩n铆 za膷alo</string>
+    <string name="devicestatus_upload_starting">Po膷谩tek nahr谩v谩n铆</string>
+    <string name="devicestatus_connected">Za艡铆zen铆 p艡ipojeno</string>
+    <string name="devicestatus_connecting">P艡ipojov谩n铆 za艡铆zen铆</string>
+    <string name="charts_activity_list">Seznam aktivit</string>
+    <string name="devicetype_amazfit_band5">Amazfit Band 5</string>
+    <string name="charts_legend_heartrate_average">Pr暖m臎rn媒 tep</string>
+    <string name="activity_prefs_step_length_cm">D茅lka kroku v cm</string>
+    <string name="charts_min_max_heartrate_popup">Nejni啪拧铆 tep: %1$d
+\nNejvy拧拧铆 tep: %2$d
+\nIntenzita pohybu: %3$s</string>
+    <string name="find_lost_device_message">Vyhledat %1$s\?</string>
+    <string name="qhybrid_calibration_align_hint">Pomoc铆 tla膷铆tek n铆啪e srovnejte ru膷i膷ky na 12:00.</string>
+    <string name="gps_track">GPS z谩znam</string>
+    <string name="about_version">Verze %s</string>
+    <string name="pref_title_lower_button_function_double">Spodn铆 tla膷铆tko: dvojstisk</string>
+    <string name="pref_title_middle_button_function_double">Prost艡edn铆 tla膷铆tko: dvojstisk</string>
+    <string name="pref_title_upper_button_function_double">Horn铆 tla膷铆tko: dvojstisk</string>
+    <string name="pref_title_lower_button_function_long">Spodn铆 tla膷铆tko: dlouh媒 stisk</string>
+    <string name="pref_title_middle_button_function_long">Prost艡edn铆 tla膷铆tko: dlouh媒 stisk</string>
+    <string name="pref_title_upper_button_function_long">Horn铆 tla膷铆tko: dlouh媒 stisk</string>
+    <string name="pref_title_lower_button_function_short">Spodn铆 tla膷铆tko: kr谩tk媒 stisk</string>
+    <string name="pref_title_middle_button_function_short">Prost艡edn铆 tla膷铆tko: kr谩tk媒 stisk</string>
+    <string name="pref_title_upper_button_function_short">Horn铆 tla膷铆tko: kr谩tk媒 stisk</string>
+    <string name="menuitem_stopwatch">Stopky</string>
+    <string name="menuitem_dnd">Neru拧it</string>
+    <string name="menuitem_alexa">Alexa</string>
+    <string name="menuitem_takephoto">Po艡铆zen铆 fotografie</string>
+    <string name="menuitem_mutephone">Zti拧it telefon</string>
+    <string name="menuitem_findphone">Naj铆t telefon</string>
+    <string name="menuitem_spo2">SpO2</string>
+    <string name="devicetype_amazfit_gtr2">Amazfit GTR 2</string>
+    <string name="devicetype_amazfit_bips_lite">Amazfit Bip S Lite</string>
+    <string name="movement_intensity">Intenzita pohybu</string>
+    <string name="activity_list_summary_activities">Aktivity</string>
+    <string name="activity_list_summary_intensity">Intenzita
+\npohybu</string>
+    <string name="activity_list_summary_active_time">Aktivn铆 膷as</string>
+    <string name="activity_list_summary_distance">Vzd谩lenost</string>
+    <string name="activity_list_summary_active_steps">Aktivn铆 kroky</string>
+    <string name="devicetype_casiogbx100">Casio GBX-100</string>
+    <string name="devicetype_amazfit_gts2">Amazfit GTS 2</string>
+    <string name="devicetype_amazfit_bipu">Amazfit Bip U</string>
+    <string name="prefs_charts_tabs_summary">Viditeln茅 karty graf暖</string>
+    <string name="prefs_charts_tabs">Karty graf暖</string>
+    <string name="prefs_operating_sounds">Zvuky akc铆</string>
+    <string name="menuitem_pomodoro">Pomodoro 膶asova膷</string>
+    <string name="pref_summary_connected_advertisement">Za艡铆zen铆 bude viditeln茅 p艡es Bluetooth i kdy啪 je p艡ipojeno</string>
+    <string name="prefs_autoremove_message">Automaticky odstra艌ovat SMS ozn谩men铆</string>
+    <string name="prefs_fake_ring_duration">Fale拧n茅 nep艡etr啪it茅 zvon臎n铆</string>
+    <string name="prefs_key_vibration">Vibrace kl谩ves</string>
+    <string name="prefs_autolight">Automatick茅 podsv铆cen铆</string>
+    <string name="menuitem_sleep">Sp谩nek</string>
+    <string name="menuitem_goal">C铆l aktivit</string>
+    <string name="devicetype_amazfit_vergel">Amazfit Verge Lite</string>
+    <string name="discovery_entered_invalid_authkey">Zadan媒 autoriza膷n铆 kl铆膷 je neplatn媒! Podr啪te dlouze na jm茅nu za艡铆zen铆 pro jeho editaci.</string>
+    <string name="pref_title_connected_advertisement">Viditeln茅 kdy啪 p艡ipojeno</string>
+    <string name="fw_upgrade_notice_amazfitvergel">Chyst谩te se nainstalovat firmware %s na v谩拧 Amazfit Verge Lite.
+\n
+\nNainstalujte nejprve soubor .fw, pot茅 soubor .res a nakonec soubor .gps. Va拧e hodinky se po instalaci souboru .fw restartuj铆.
+\n
+\nPozn谩mka: soubory .res a .gps nemus铆te instalovat, pokud jsou toto啪n茅 s ji啪 d艡铆ve nainstalovan媒mi.
+\n
+\nPOKRA膶UJTE NA VLASTN脥 NEBEZPE膶脥!</string>
+    <string name="activity_DB_ShowContentButton">Zobrazit obsah adres谩艡e exportu/importu</string>
+    <string name="activity_data_management_directory_content_title">Obsah adres谩艡e exportu/importu</string>
+    <string name="widget_settings_select_device_title">Vyberte za艡铆zen铆</string>
+    <string name="devicetype_amazfit_bipupro">Amazfit Bip U Pro</string>
+    <string name="dbmanagementactivity_export_confirmation">Opravdu chcete exportovat data? D艡铆ve exportovan谩 data aktivit a nastaven铆 budou p艡eps谩na (pokud existuj铆).</string>
+    <string name="dbmanagementactivity_export_data_title">Exportovat data?</string>
+    <string name="dbmanagementactivity_export_finished">Maz谩n铆 dokon膷eno</string>
+    <string name="activity_db_management_clean_export_directory_text">Soubory exportovan茅 do Export/Import slo啪ky jsou dostupn茅 kter茅koli aplikaci va拧eho za艡铆zen铆. Pravd臎podobn臎 je po exportu a n谩sledn茅 z谩loze nebo synchronizaci budete cht铆t vymazat. P艡ed vymaz谩n铆m se ujist臎te, 啪e m谩te z谩lo啪n铆 kopii. GPX z谩znamy, pod-slo啪ky a soubor auto-exportu datab谩ze (existuj铆-li) nebudou vymaz谩ny. Cesta k Export/Import slo啪ce je:</string>
+    <string name="activity_DB_clean_export_directory_warning_message">Opravdu odstranit soubory z Export/Import slo啪ky\?</string>
+    <string name="activity_DB_clean_export_directory_warning_title">Chcete odstranit soubory z Export/Import slo啪ky\?</string>
+    <string name="activity_db_management_clean_export_directory_label">Odstranit soubory v adres谩艡i exportu/importu</string>
+    <string name="dbmanagementactivity_error_cleaning_export_directory">Do拧lo k chyb臎 p艡i maz谩n铆 soubor暖 ze slo啪ky exportu %1$s</string>
+    <string name="fw_upgrade_notice_amazfitx">Chyst谩te se nainstalovat firmware %s na v谩拧 Amazfit X.
+\n
+\nNainstalujte nejprve soubor .fw a pot茅 soubor .res. V谩拧 n谩ramek se po instalaci souboru .fw restartuje.
+\n
+\nPozn谩mka: soubor .res nemus铆te instalovat, pokud je toto啪n媒 s ji啪 d艡铆ve nainstalovan媒m.
+\n
+\nPOKRA膶UJTE NA VLASTN脥 NEBEZPE膶脥!</string>
+    <string name="fossil_hr_auth_failed">Ov臎艡en铆 selhalo</string>
+    <string name="fossil_hr_unavailable_unauthed">Nen铆 dostupn茅 v neov臎艡en茅m m贸du</string>
+    <string name="fossil_hr_synced_activity_data">Data aktivit synchronizov谩na</string>
+    <string name="fossil_hr_commute_processing">Zpracov谩v谩n铆 po啪adavku鈥�</string>
+    <string name="mi">mi</string>
+    <string name="minutes_mi">min/mi</string>
+    <string name="mi_h">mi/h</string>
+    <string name="ft">st</string>
+    <string name="devicetype_waspos">Wasp-os</string>
+    <string name="devicetype_zepp_e">Zepp E</string>
+    <string name="devicetype_amazfit_x">Amazfit X</string>
+    <string name="devicetype_amazfit_gts2e">Amazfit GTS 2e</string>
+    <string name="devicetype_amazfit_gts2_mini">Amazfit GTS 2 Mini</string>
+    <string name="devicetype_amazfit_gtr2e">Amazfit GTR 2e</string>
+    <string name="devicetype_amazfit_neo">Amazfit Neo</string>
+    <string name="activity_type_strength_training">Posilov谩n铆</string>
+    <string name="show_ongoing_activity">Zobrazovat informaci o prob铆haj铆c铆 aktivit臎</string>
+    <string name="dialog_hide">Skr媒t</string>
+    <string name="calendar_year">Rok</string>
+    <string name="calendar_six_months">P暖l roku</string>
+    <string name="calendar_three_months">3 m臎s铆ce</string>
+    <string name="calendar_month">M臎s铆c</string>
+    <string name="calendar_two_weeks">Dva t媒dny</string>
+    <string name="calendar_week">T媒den</string>
+    <string name="calendar_day">Den</string>
+    <string name="battery_level">脷rove艌 nabit铆</string>
+    <string name="battery_detail_activity_title">Informace o baterii</string>
+    <string name="qhybrid_title_calibration">Kalibrace</string>
+    <string name="qhybrid_title_apps_management">Spr谩va aplikac铆</string>
+    <string name="qhybrid_title_file_management">Spr谩va soubor暖</string>
+    <string name="qhybrid_title_background_image">Obr谩zek na pozad铆</string>
+    <string name="qhybrid_title_apps">Aplikace</string>
+    <string name="devicetype_um25">UM-25</string>
+    <string name="fw_upgrade_notice_amazfitneo">Chyst谩te se nainstalovat firmware %s na v谩拧 Amazfit Neo.
+\n
+\nV谩拧 n谩ramek se po instalaci souboru .fw restartuje.
+\n
+\nPOKRA膶UJTE NA VLASTN脥 NEBEZPE膶脥!</string>
+    <string name="fossil_hr_warning_firmware_too_new">N臎kter茅 funkce jsou vypnut茅, proto啪e firmware hodinek je p艡铆li拧 nov媒</string>
+    <string name="prefs_sounds_summary">Nakonfigurujte, kdy bude za艡铆zen铆 p铆pat</string>
+    <string name="prefs_sounds">Zvuky</string>
+    <string name="menuitem_temperature">Teplota</string>
+    <string name="menuitem_widgets">Widgety</string>
+    <string name="menuitem_events">Ud谩losti</string>
+    <string name="qhybrid_watchface_configuration_old_firmware">Konfigura膷n铆 obrazovka cifern铆ku pro hodinky s firmwarem verze DN1.0.2.19r a ni啪拧铆m</string>
+    <string name="qhybrid_calibration_100_steps">100 krok暖</string>
+    <string name="qhybrid_calibration_10_steps">10 krok暖</string>
+    <string name="qhybrid_calibration_1_step">1 krok</string>
+    <string name="qhybrid_calibration_clockwise">Po sm臎ru hodinov媒ch ru膷i膷ek</string>
+    <string name="qhybrid_calibration_counterclockwise">Proti sm臎ru hodinov媒ch ru膷i膷ek</string>
+    <string name="fossil_hr_new_action">Nov谩 akce</string>
+    <string name="fossil_hr_new_action_cancel">zru拧it</string>
+    <string name="fossil_hr_edit_action">Upravit akci</string>
+    <string name="fossil_hr_edit_action_delete">smazat</string>
+    <string name="fossil_hr_commute_actions_explanation">Zde nakonfigurovan茅 akce se zobraz铆 v aplikaci Commute na hodink谩ch. Informace o tom, jak zach谩zet se z谩m臎ry vytvo艡en媒mi t臎mito akcemi, najdete na wiki.</string>
+    <string name="pref_summary_canned_messages_dismisscall">Odm铆tn臎te hovory z hodinek pomoc铆 SMS zpr谩vy</string>
+    <string name="pref_summary_canned_messages_set">Odesl谩n铆 n铆啪e nakonfigurovan媒ch zpr谩v do za艡铆zen铆</string>
+    <string name="qhybrid_summary_calibration">Kalibrace ru膷i膷ek hodinek</string>
+    <string name="qhybrid_summary_file_management">Nahr谩v谩n铆 a stahov谩n铆 soubor暖</string>
+    <string name="pref_summary_physical_buttons">Nastavte funkce fyzick媒ch tla膷铆tek na hodink谩ch</string>
+    <string name="pref_title_physical_buttons">Fyzick谩 tla膷铆tka</string>
+    <string name="qhybrid_title_watchface">Nastaven铆 cifern铆ku</string>
+    <string name="devicetype_miband6">Mi Band 6</string>
+    <string name="kind_agps_bundle">AGPS Bal铆膷ek</string>
+    <string name="watchface_install_info">Instalujete cifern铆k:
+\n
+\n%1$s
+\nVerze %2$s by %3$s
+\n</string>
+    <string name="fw_upgrade_notice_miband6">Chyst谩te se nainstalovat firmware %s na v谩拧 Mi Band 6.
+\n
+\nNainstalujte nejprve soubor .fw a pot茅 soubor .res. V谩拧 n谩ramek se po instalaci souboru .fw restartuje.
+\n
+\nPozn谩mka: soubor .res nemus铆te instalovat, pokud je toto啪n媒 s ji啪 d艡铆ve nainstalovan媒m.
+\n
+\nPOKRA膶UJTE NA VLASTN脥 NEBEZPE膶脥!</string>
+    <string name="devicetype_amazfit_trex_pro">Amazfit T-Rex Pro</string>
+    <string name="qhybrid_pref_summary_external_intents">Povolit ostatn铆m Android aplikac铆m nahr谩t/p艡epsat soubory</string>
+    <string name="qhybrid_pref_title_external_intents">Povolit nebezpe膷n茅 extern铆 intenty</string>
+    <string name="pref_summary_developer_settings">Nastaven铆 a funkce pro v媒voj谩艡e</string>
+    <string name="pref_title_developer_settings">V媒voj谩艡sk谩 nastaven铆</string>
+    <string name="qhybrid_pref_summary_actions">Akce pro aplikaci Commute</string>
+    <string name="qhybrid_pref_title_actions">Akce</string>
+    <string name="menuitem_barometer">Barometr</string>
+    <string name="notification_channel_low_battery_name">N铆zk媒 stav baterie</string>
+    <string name="notification_channel_transfer_name">P艡enos dat</string>
+    <string name="pref_theme_black_background">Ve tmav茅m motivu pou啪铆t 膷ern茅 pozad铆</string>
+    <string name="gpx_receiver_overwrite_some_files">N臎kter茅 soubory ji啪 existuj铆. P艡epsat\?</string>
+    <string name="gpx_receiver_files_received">GPX soubor(暖) p艡ijato:</string>
+    <string name="gpx_receiver_activity_title">GPX p艡ij铆ma膷 Gadgetbridge</string>
+    <string name="activity_summary_detail_editing_gpx_track">脷prava propojen茅 trasy GPX</string>
+    <string name="activity_summary_detail_clear_gpx_track">Vymazat trasu GPX</string>
+    <string name="activity_summary_detail_select_gpx_track">Vybrat trasu GPX</string>
+    <string name="set">Nastavit</string>
+    <string name="watchface_setting_wrist_flick_duration">Doba trv谩n铆 (v ms)</string>
+    <string name="watchface_setting_wrist_flick_minute">Minutov谩 ru膷i膷ka (-360 a啪 360)</string>
+    <string name="watchface_setting_wrist_flick_hour">Hodinov谩 ru膷i膷ka (-360 a啪 360)</string>
+    <string name="watchface_setting_wrist_flick_move_relative">Pohyb ru膷i膷ek vzhledem k 膷asu</string>
+    <string name="watchface_setting_desc_wrist_flick">(pro 煤pln茅 vypnut铆 povolte relativn铆 pohyb a nastavte v拧echny hodnoty na 0)</string>
+    <string name="watchface_setting_title_wrist_flick">艩vihnut铆 z谩p臎st铆m</string>
+    <string name="watchface_setting_display_refresh_partial">膶谩ste膷n茅 obnoven铆 (v minut谩ch)</string>
+    <string name="watchface_setting_display_refresh_full">脷pln茅 obnoven铆 (v minut谩ch)</string>
+    <string name="watchface_setting_title_display_refresh_timeout">膶asov媒 limit obnoven铆 displeje</string>
+    <string name="watchface_dialog_title_settings">Nastaven铆 cifern铆ku</string>
+    <string name="watchface_dialog_widget_color_black">膶ern谩</string>
+    <string name="watchface_dialog_widget_color_white">B铆l谩</string>
+    <string name="watchface_dialog_widget_color">Barva</string>
+    <string name="qhybrid_title_watchface_designer">N谩vrh谩艡 cifern铆ku</string>
+    <string name="watchface_dialog_widget_preset_right">Vpravo</string>
+    <string name="watchface_dialog_widget_preset_left">Vlevo</string>
+    <string name="watchface_dialog_widget_preset_bottom">Dole</string>
+    <string name="watchface_dialog_widget_preset_top">Naho艡e</string>
+    <string name="watchface_dialog_widget_presets">P艡edvolby pozice</string>
+    <string name="watchface_dialog_widget_y_coordinate">Sou艡adnice Y (max 240)</string>
+    <string name="watchface_dialog_widget_x_coordinate">Sou艡adnice X (max 240)</string>
+    <string name="watchface_dialog_widget_type">Typ</string>
+    <string name="appmanager_app_edit">Upravit</string>
+    <string name="button_watchface_save_apply">Ulo啪it a pou啪铆t</string>
+    <string name="button_watchface_preview">N谩hled na hodink谩ch</string>
+    <string name="watchface_widget_type_heart_rate">Srde膷n铆 tep</string>
+    <string name="watchface_widget_type_steps">Kroky</string>
+    <string name="watchface_widget_type_weather">Po膷as铆</string>
+    <string name="watchface_widget_type_date">Datum</string>
+    <string name="watchface_dialog_title_add_widget">P艡idat widget</string>
+    <string name="watchface_toast_settings_incomplete">Nastaven铆 nen铆 kompletn铆, widget nebyl p艡id谩n</string>
+    <string name="watchface_dialog_title_set_name">Jm茅no cifern铆ku</string>
+    <string name="button_watchface_settings">Nastaven铆 cifern铆ku</string>
+    <string name="button_watchface_add_widget">P艡idat widget</string>
+    <string name="button_watchface_select_image">Zm臎nit obr谩zek pozad铆</string>
+    <string name="button_watchface_edit_name">Upravit n谩zev</string>
+    <string name="watchface_setting_power_saving_hands">Zak谩zat pohyb ru膷i膷ek p艡i sejmut铆 z ruky</string>
+    <string name="watchface_setting_power_saving_display">Zak谩zat aktualizace displeje p艡i sejmut铆 z ruky</string>
+    <string name="watchface_setting_title_power_saving">脷sporn媒 m贸d</string>
+    <string name="watchface_widget_type_chance_rain">艩ance de拧t臎</string>
+    <string name="watchface_widget_type_active_mins">Aktivn铆 minuty</string>
+    <string name="watchface_widget_type_2nd_tz">Druh谩 膷asov谩 z贸na</string>
+    <string name="watchface_widget_type_calories">Kalorie</string>
+    <string name="watchface_widget_type_battery">Baterie</string>
+    <string name="watchface_upload_failed">Nahr谩n铆 cifern铆ku se nezda艡ilo. Pros铆m zkuste to znovu.</string>
+    <string name="watchface_cache_confirm_overwrite">Ve vyrovn谩vac铆 pam臎ti ji啪 existuje cifern铆k s t铆mto n谩zvem. Chcete jej p艡epsat\?</string>
+    <string name="devicetype_smaq2oss">SMA-Q2 OSS</string>
+    <string name="heart_rate_result">V媒sledky m臎艡en铆</string>
+    <string name="getting_heart_rate">Prob铆h谩 m臎艡en铆</string>
+    <string name="blood_pressure">Krevn铆 tlak</string>
+    <string name="menuitem_flashlight">Sv铆tilna</string>
+    <string name="pref_summary_huami_force_new_protocol">Zapn臎te, pokud se za艡铆zen铆 po aktualizaci firmwaru nep艡ipojuje</string>
+    <string name="pref_title_huami_force_new_protocol">Nov媒 Protokol Ov臎艡ov谩n铆</string>
+    <string name="pref_title_ping_tone">Melodie p艡i hled谩n铆 telefonu</string>
+    <string name="prefs_autoheartrate_interval">膶etnost m臎艡en铆</string>
+    <string name="prefs_autoheartrate_sleep">M臎艡en铆 b臎hem sp谩nku</string>
+    <string name="prefs_autoheartrate_measurement">Automatick茅 m臎艡en铆 srde膷n铆ho tepu</string>
+    <string name="prefs_autoheartrate_summary">Pravideln茅 m臎艡en铆 srde膷n铆 frekvence b臎hem dne a tak茅 ve sp谩nku</string>
+    <string name="prefs_autoheartrate">Automatick谩 m臎艡en铆 srde膷n铆 frekvence</string>
+    <string name="enable_notifications_summary">Upozorn臎n铆 na hovory, zpr谩vy a dal拧铆</string>
+    <string name="enable_vibrations_summary">Vibrace pro hovory, zpr谩vy, ozn谩men铆 a dal拧铆 funkce</string>
+    <string name="prefs_notifications_enable">Povolit ozn谩men铆</string>
+    <string name="prefs_vibration_enable">Povolit vibrace</string>
+    <string name="prefs_sleep_time_summary">Ur膷uje 膷asy, kdy se registruje sp谩nek</string>
+    <string name="prefs_sleep_time_label">Definujte hodiny sp谩nku</string>
+    <string name="prefs_sleep_time">膶as sp谩nku</string>
+    <string name="devicetype_fitpro">FitPro</string>
+    <string name="devicetype_nothingear1">Nothing Ear (1)</string>
+    <string name="nothing_prefs_inear_summary">P艡ehr谩vat/Pauzovat dle detekce no拧en铆</string>
+    <string name="nothing_prefs_audiomode_title">Audio m贸d</string>
+    <string name="nothing_prefs_inear_title">Detekce no拧en铆</string>
+    <string name="pref_title_notification_use_as">Pou啪铆t seznam aplikac铆 k鈥�</string>
+    <string name="pref_title_notification_use_as_deny">Zak谩z谩n铆 ozn谩men铆 z vybran媒ch aplikac铆</string>
+    <string name="title_activity_notification_management">Nastaven铆 ozn谩men铆</string>
+    <string name="check_all_applications">Ozna膷it v拧echny aplikace</string>
+    <string name="uncheck_all_applications">Zru拧it ozna膷en铆 v拧ech aplikac铆</string>
+    <string name="pref_header_notification_application_settings">Nastaven铆 jednotliv媒ch aplikac铆</string>
+    <string name="pref_title_notification_use_as_allow">Povolen铆 ozn谩men铆 z vybran媒ch aplikac铆</string>
+    <string name="pref_title_message_privacy_mode">Nastaven铆 soukrom铆 pro ozn谩men铆</string>
+    <string name="pref_message_privacy_mode_off">Zobrazit v拧echen text</string>
+    <string name="pref_message_privacy_mode_complete">Skr媒t v拧echen text</string>
+    <string name="pref_applications_settings">Seznam aplikac铆</string>
+    <string name="toast_app_must_be_selected">Aplikace mus铆 b媒t ozna膷ena, aby ji bylo mo啪no konfigurovat</string>
+    <string name="toast_app_must_not_be_selected">Aplikace nesm铆 b媒t ozna膷ena, aby ji bylo mo啪no konfigurovat</string>
+    <string name="prefs_equalizer_preset">P艡edvolba ekvaliz茅ru</string>
+    <string name="pref_title_equalizer_bass_boost">Zes铆len铆 bas暖</string>
+    <string name="pref_title_equalizer_soft">M臎kk媒</string>
+    <string name="pref_title_equalizer_trebble">Zes铆len铆 v媒拧ek</string>
+    <string name="devicetype_galaxybuds">Galaxy Buds</string>
+    <string name="prefs_dolby_summary">P艡edvolba Dolby pro ekvaliz茅r</string>
+    <string name="prefs_game_mode">Hern铆 m贸d</string>
+    <string name="prefs_game_mode_summary">Pouze pokud v谩拧 telefon podporuje hern铆 re啪im</string>
+    <string name="prefs_touch_lock">Z谩mek Dotyku</string>
+    <string name="prefs_touch_lock_summary">Zak谩z谩n铆 dotykov媒ch ud谩lost铆</string>
+    <string name="prefs_ambient_voice_summary">Aby hlas vynikl</string>
+    <string name="pref_title_equalizer_clear">Jasn媒</string>
+    <string name="prefs_ambient_sound">Okoln铆 zvuk</string>
+    <string name="prefs_left">Lev茅</string>
+    <string name="prefs_galaxy_touch_options">Mo啪nosti dotykov茅ho ovl谩d谩n铆</string>
+    <string name="pref_title_equalizer_dynamic">Dynamick媒</string>
+    <string name="prefs_dolby_mode">Re啪im Dolby</string>
+    <string name="prefs_right">Prav茅</string>
+    <string name="prefs_equalizer">Ekvaliz茅r</string>
+    <string name="prefs_equalizer_summary">Povolen铆 nebo zak谩z谩n铆 ekvaliz茅ru</string>
+    <string name="prefs_ambient_volume">Hlasitost okol铆</string>
+    <string name="prefs_ambient_voice_focus">Zam臎艡en铆 hlasu</string>
+    <string name="prefs_ambient_mode">Re啪im zvuku okol铆</string>
+    <string name="prefs_galaxy_buds_experimental">Experiment谩ln铆</string>
+    <string name="battery_case">Bateriov茅 pouzdro</string>
+    <string name="left_earbud">Lev茅 sluch谩tko</string>
+    <string name="right_earbud">Prav茅 sluch谩tko</string>
+    <string name="sony_sound_position_off">Vypnuto</string>
+    <string name="sony_sound_position_front">Vp艡edu</string>
+    <string name="sony_surround_mode">Re啪im Surround</string>
+    <string name="pref_header_other">Ostatn铆</string>
+    <string name="device_card_activity_card_title_summary">Zvolte kter茅 informace budou zobrazeny na kart臎 za艡铆zen铆</string>
+    <string name="prefs_activity_in_device_card_title_summary">Zobrazit aktu谩ln铆 kroky, vzd谩lenost nebo sp谩nek na kart臎 za艡铆zen铆</string>
+    <string name="prefs_activity_in_device_card_sleep_title_summary">Zobrazit dobu sp谩nku</string>
+    <string name="prefs_activity_in_device_card_steps_title_summary">Zobrazovat celkov茅 kroky</string>
+    <string name="devicetype_sony_wh_1000xm3">Sony WH-1000XM3</string>
+    <string name="devicetype_galaxybuds_live">Galaxy Buds Live</string>
+    <string name="pref_title_equalizer_normal">Norm谩ln铆</string>
+    <string name="prefs_active_noise_cancelling">Aktivn铆 potla膷en铆 hluku</string>
+    <string name="prefs_active_noise_cancelling_summary">Blokov谩n铆 zvuk暖 okol铆</string>
+    <string name="prefs_pressure_relief">Odleh膷en铆 tlaku se zvukem okol铆</string>
+    <string name="pressure_relief_summary">Zabra艌te pocitu tlaku v u拧铆ch p艡i nepou啪铆v谩n铆 aktivn铆ho potla膷en铆 hluku</string>
+    <string name="pref_header_sony_ambient_sound_control">Nastaven铆 zvuku okol铆</string>
+    <string name="sony_ambient_sound">Re啪im</string>
+    <string name="sony_ambient_sound_off">Vypnuto</string>
+    <string name="sony_ambient_sound_noise_cancelling">Potla膷en铆 hluku</string>
+    <string name="sony_ambient_sound_wind_noise_reduction">Redukce hluku v臎tru</string>
+    <string name="sony_ambient_sound_ambient_sound">Zvuky okol铆</string>
+    <string name="sony_ambient_sound_focus_voice">Up艡ednostn臎n铆 hlasu</string>
+    <string name="sony_ambient_sound_level">Hladina okoln铆ho zvuku</string>
+    <string name="sony_sound_position_front_left">Vp艡edu vlevo</string>
+    <string name="sony_sound_position_front_right">Vp艡edu vpravo</string>
+    <string name="sony_sound_position">Pozice zvuku</string>
+    <string name="sony_sound_position_rear_left">Vzadu vlevo</string>
+    <string name="sony_sound_position_rear_right">Vzadu vpravo</string>
+    <string name="sony_surround_mode_off">Vypnuto</string>
+    <string name="sony_surround_mode_arena">Ar茅na</string>
+    <string name="sony_surround_mode_club">Klub</string>
+    <string name="sony_surround_mode_outdoor_stage">Venkovn铆 p贸dium</string>
+    <string name="sony_surround_mode_concert_hall">Koncertn铆 s铆艌</string>
+    <string name="sony_warn_sbc_codec">Varov谩n铆: Tato nastaven铆 funguj铆 pouze pro zvukov媒 kodek SBC.</string>
+    <string name="sony_equalizer">Ekvaliz茅r</string>
+    <string name="sony_equalizer_preset_off">Vypnuto</string>
+    <string name="sony_equalizer_preset_bright">Jasn媒</string>
+    <string name="sony_equalizer_preset_excited">Vzru拧en媒</string>
+    <string name="sony_equalizer_preset_mellow">M臎kk媒</string>
+    <string name="sony_equalizer_preset_relaxed">Uvoln臎n媒</string>
+    <string name="sony_equalizer_preset_vocal">Vok谩ln铆</string>
+    <string name="sony_equalizer_preset_treble_boost">Zes铆len铆 v媒拧ek</string>
+    <string name="sony_equalizer_preset_bass_boost">Zes铆len铆 bas暖</string>
+    <string name="sony_equalizer_preset_speech">艠e膷</string>
+    <string name="sony_equalizer_preset_manual">Manu谩ln铆</string>
+    <string name="sony_equalizer_band_2500">2.5k</string>
+    <string name="sony_equalizer_band_6300">6.3k</string>
+    <string name="sony_equalizer_band_16000">16k</string>
+    <string name="sony_equalizer_clear_bass">Jasn茅 basy</string>
+    <string name="sony_touch_sensor">Nastaven铆 dotykov茅ho ovl谩d谩n铆</string>
+    <string name="sony_notification_voice_guide">Ozn谩men铆 a hlasov媒 pr暖vodce</string>
+    <string name="sony_automatic_power_off">Automatick茅 vypnut铆</string>
+    <string name="sony_automatic_power_off_off">Nevyp铆nat</string>
+    <string name="sony_automatic_power_off_5_min">5 minut</string>
+    <string name="sony_automatic_power_off_30_min">30 minut</string>
+    <string name="sony_automatic_power_off_1_hour">1 hodina</string>
+    <string name="sony_automatic_power_off_3_hour">3 hodiny</string>
+    <string name="watchface_widget_type_custom">Vlastn铆 widget</string>
+    <string name="pref_header_equalizer">Equaliz茅r</string>
+    <string name="device_card_activity_card_title">Informace o aktivit臎 na kart臎 za艡铆zen铆</string>
+    <string name="prefs_activity_in_device_card_sleep_title">Sp谩nek</string>
+    <string name="prefs_activity_in_device_card_distance_title_summary">Vzd谩lenost je vypo膷铆t谩na z krok暖 a d茅lky kroku (nastavte v Nastaven铆 - Va拧e 煤daje)</string>
+    <string name="pref_header_system">Syst茅m</string>
+    <string name="prefs_activity_in_device_card_title">Zobrazit informace o aktivit臎 na kart臎 za艡铆zen铆</string>
+    <string name="sony_equalizer_band_1000">1k</string>
+    <string name="sony_equalizer_preset_custom_1">Vlastn铆 1</string>
+    <string name="sony_equalizer_preset_custom_2">Vlastn铆 2</string>
+    <string name="pref_header_sony_equalizer_preset_custom_1">Vlastn铆 p艡edvolba 1</string>
+    <string name="sony_equalizer_band_400">400</string>
+    <string name="pref_header_sony_equalizer_preset_custom_2">Vlastn铆 p艡edvolba 2</string>
+    <string name="sony_automatic_power_off_when_taken_off">P艡i sund谩n铆</string>
+    <string name="title_activity_set_reminders">Nastavit upozorn臎n铆</string>
+    <string name="controlcenter_start_configure_reminders">Nastavit upozorn臎n铆</string>
+    <string name="reminder_time_once">%1$s, Jednou</string>
+    <string name="reminder_time_every_week">%1$s, T媒dn臎</string>
+    <string name="reminder_time_every_day">%1$s, Denn臎</string>
+    <string name="reminder_time_every_month">%1$s, M臎s铆膷n臎</string>
+    <string name="reminder_every_month">M臎s铆膷n臎</string>
+    <string name="reminder_every_year">Ro膷n臎</string>
+    <string name="reminder_delete_confirm_title">Odstranit upozorn臎n铆</string>
+    <string name="reminder_delete_confirm_description">Opravdu chcete odstranit toto upozorn臎n铆?</string>
+    <string name="reminder_no_free_slots_title">Nejsou voln谩 m铆sta</string>
+    <string name="reminder_no_free_slots_description">Za艡铆zen铆 nem谩 voln谩 m铆sta pro dal拧铆 upozorn臎n铆 (celkem m铆st: %1$s)</string>
+    <string name="title_activity_reminder_details">Podrobnosti o upozorn臎n铆</string>
+    <string name="miband_prefs_reserve_reminder_calendar">Upozorn臎n铆 k rezervaci pro nadch谩zej铆c铆 ud谩losti</string>
+    <string name="qhybrid_title_on_device_confirmation">Povolit potvrzen铆 p谩rov谩n铆 na za艡铆zen铆</string>
+    <string name="prefs_fm_preset_instructions">Dlouh媒m podr啪en铆m tla膷铆tka ulo啪te p艡edvolbu</string>
+    <string name="reminder_every_day">Denn臎</string>
+    <string name="reminder_message">Text</string>
+    <string name="reminder_repeat">Opakov谩n铆</string>
+    <string name="reminder_time">膶as</string>
+    <string name="reminder_date">Datum</string>
+    <string name="reminder_time_every_year">%1$s, Ro膷n臎</string>
+    <string name="reminder_once">Jednou</string>
+    <string name="reminder_every_week">T媒dn臎</string>
+    <string name="prefs_reserve_reminder_calendar_summary">Po膷et kalend谩艡ov媒ch ud谩lost铆 k synchronizaci</string>
+    <string name="watchface_dialog_widget_timeout_show_circle">Zobrazit or谩mov谩n铆 po 膷asov茅m limitu</string>
+    <string name="qhybrid_summary_on_device_confirmation">Potvrzen铆 p谩rov谩n铆 na za艡铆zen铆 m暖啪e b媒t otravn茅. Jejich vypnut铆m m暖啪ete p艡ij铆t o funk膷nost.</string>
+    <string name="prefs_fm_presets_presets">P艡edvolby</string>
+    <string name="watchface_dialog_widget_timezone">膶asov谩 z贸na</string>
+    <string name="watchface_dialog_widget_update_timeout">膶asov媒 limit aktualizace v minut谩ch</string>
+    <string name="watchface_dialog_widget_timeout_hide_text">Skryt铆 textu p艡i vypr拧en铆 膷asov茅ho limitu</string>
+    <string name="averageAltitude">Pr暖m臎r</string>
+    <string name="minSpeed">Minimum</string>
+    <string name="maxStride">Max krok</string>
+    <string name="minStride">Min krok</string>
+    <string name="averageCadence">Pr暖m臎rn媒 rytmus</string>
+    <string name="maxCadence">Max rytmus</string>
+    <string name="spm">kroky/min</string>
+    <string name="mi2_prefs_do_not_disturb_lift_wrist">Zapnout displej p艡i zvednut铆 b臎hem re啪imu Neru拧it</string>
+    <string name="minHR">Min. tepov谩 frekvence</string>
+    <string name="maxHR">Max. tepov谩 frekvence</string>
+    <string name="minCadence">Min rytmus</string>
+    <string name="pref_header_sony_equalizer_bands">Rozsahy</string>
+    <string name="sony_audio_upsampling">P艡evzorkov谩n铆 zvuku</string>
+    <string name="activity_prefs_discovery_pairing">Mo啪nosti vyhled谩v谩n铆 a p谩rov谩n铆</string>
+    <string name="sony_button_mode_ambient_sound_control">Nastaven铆 zvuku okol铆</string>
+    <string name="sony_button_mode_playback_control">Nastaven铆 p艡ehr谩v谩n铆</string>
+    <string name="sony_button_mode_volume_control">Ovl谩d谩n铆 hlasitosti</string>
+    <string name="controlcenter_power_off_confirm_title">Vypnout</string>
+    <string name="controlcenter_power_off">Vypnut铆</string>
+    <string name="controlcenter_power_off_confirm_description">Opravdu chcete za艡铆zen铆 vypnout\?</string>
+    <string name="add_test_device">P艡idat testovac铆 za艡铆zen铆</string>
+    <string name="devicetype_sony_wf_sp800n">Sony WF-SP800N</string>
+    <string name="discover_unsupported_devices">Vyhled谩vat nepodporovan谩 za艡铆zen铆</string>
+    <string name="discover_unsupported_devices_description">Povolen铆m t茅to volby budou zobrazena i nepodporovan谩 objeven谩 za艡铆zen铆. Klepnut铆m zkop铆rujete n谩zev a adresu MAC za艡铆zen铆 do schr谩nky. Podr啪en铆m otev艡ete nab铆dku 鈥濸艡idat testovac铆 za艡铆zen铆鈥�. M暖啪e zp暖sobit zamrznut铆 aplikace.</string>
+    <string name="sony_pause_when_taken_off">Pozastaven铆 p艡ehr谩v谩n铆 p艡i sund谩n铆 sluch谩tek</string>
+    <string name="sony_button_mode_left">Re啪im tla膷铆tka (vlevo)</string>
+    <string name="sony_button_mode_off">Vypnout</string>
+    <string name="sony_button_mode_right">Re啪im tla膷铆tka (vpravo)</string>
+    <string name="devicetype_bose_qc35">Bose QC35</string>
+    <string name="devicetype_vesc">VESC</string>
+    <string name="activity_type_hiking">Turistika</string>
+    <string name="ascentDistance">Vzd谩lenost do kopce</string>
+    <string name="descentDistance">Vzd谩lenost z kopce</string>
+    <string name="activity_type_climbing">Lezen铆</string>
+    <string name="devicetype_domyos_t540">Domyos T540</string>
+    <string name="pref_media_rewind">P艡esko膷it zp臎t</string>
+    <string name="about_hash">Z谩pis (commit) %s</string>
+    <string name="pref_media_pause">Zapauzov谩n铆</string>
+    <string name="pref_media_playpause">Zapnout/vypnout p艡ehr谩v谩n铆</string>
+    <string name="pref_device_action_broadcast">Odeslat vys铆l谩n铆 (broadcast)</string>
+    <string name="pref_button_action_disabled">Deaktivov谩no</string>
+    <string name="pref_media_previous">P艡edchoz铆 skladba</string>
+    <string name="distance_format_kilometers">###.#km</string>
+    <string name="pref_media_next">Dal拧铆 skladba</string>
+    <string name="distance_format_miles">###.#mi</string>
+    <string name="distance_format_feet">###st</string>
+    <string name="pref_media_play">P艡ehr谩v谩n铆 m茅di铆</string>
+    <string name="pref_media_forward">P艡esko膷it vp艡ed</string>
+    <string name="distance_format_meters">###m</string>
+    <string name="pref_media_volumeup">Zv媒拧it hlasitost</string>
+    <string name="pref_media_volumedown">Sn铆啪it hlasitost</string>
+    <string name="prefs_activity_recognition">Nastaven铆 rozpozn谩v谩n铆 aktivit</string>
+    <string name="fossil_hr_button_config_info">N臎kter谩 tla膷铆tka nelze konfigurovat, proto啪e jejich funkce jsou pevn臎 zak贸dov谩ny ve firmwaru hodinek.
+\n
+\nUpozorn臎n铆: Dlouh媒m stisknut铆m horn铆ho tla膷铆tka p艡i nainstalovan茅m cifern铆ku hodinek z ofici谩ln铆 aplikace Fossil se p艡ep铆n谩 i zobrazen铆/skryt铆 widget暖.</string>
+    <string name="menuitem_menu">Nab铆dka</string>
+    <string name="devicetype_sony_wh_1000xm4">Sony WH-1000XM4</string>
+    <string name="discovery_bluetooth_scan">Skenov谩n铆 Bluetooth:</string>
+    <string name="sony_speak_to_chat_sensitivity">Citlivost detekce hlasu</string>
+    <string name="sony_speak_to_chat_sensitivity_auto">Automatick谩</string>
+    <string name="sony_speak_to_chat_sensitivity_high">Vysok谩</string>
+    <string name="sony_speak_to_chat_sensitivity_low">N铆zk谩</string>
+    <string name="sony_speak_to_chat_focus_on_voice">Zam臎艡en铆 na hlas</string>
+    <string name="sony_speak_to_chat_timeout">膶asov媒 limit</string>
+    <string name="sony_speak_to_chat_timeout_off">Vypnuto</string>
+    <string name="sony_speak_to_chat_timeout_short">Kr谩tk媒 (15s)</string>
+    <string name="sony_speak_to_chat_timeout_standard">Standardn铆 (30s)</string>
+    <string name="sony_speak_to_chat_timeout_long">Dlouh媒 (1m)</string>
+    <string name="sony_connect_two_devices">P艡ipojen铆 ke 2 za艡铆zen铆m sou膷asn臎</string>
+    <string name="pref_activity_recognize_running">rozpoznat b臎h</string>
+    <string name="pref_activity_recognize_biking">rozpoznat cyklistiku</string>
+    <string name="pref_activity_recognize_walking">rozpoznat ch暖zi</string>
+    <string name="pref_activity_recognition_mode_none">nic</string>
+    <string name="pref_activity_recognition_mode_auto">automaticky</string>
+    <string name="watchface_dialog_widget_width">艩铆艡ka widgetu (v pixelech)</string>
+    <string name="device_unsupported">NEPODPOROV脕NO</string>
+    <string name="discovery_bluetooth_le_scan">Skenov谩n铆 Bluetooth LE:</string>
+    <string name="sony_speak_to_chat">Mluven铆 s lidmi</string>
+    <string name="pref_activity_recognize_rowing">rozpoznat veslov谩n铆</string>
+    <string name="pref_activity_recognition_mode_ask">dot谩zat se</string>
+    <string name="devicetype_sonyswr12">Sony SWR12</string>
+    <string name="audio_codec">Zvukov媒 kodek</string>
+    <string name="pref_header_sony_device_info">Informace o za艡铆zen铆</string>
+    <string name="unknown">Nezn谩m茅</string>
+    <string name="start">Start</string>
+    <string name="devicetype_amazfit_pop">Amazfit Pop</string>
+    <string name="devicetype_amazfit_pop_pro">Amazfit Pop Pro</string>
+    <string name="pref_header_sony_anc_optimizer">Optimaliz谩tor potla膷en铆 hluku</string>
+    <string name="sony_anc_optimize_description">Kliknut铆m spust铆te optimaliz谩tor pro potla膷en铆 拧umu.</string>
+    <string name="sony_anc_optimize_confirmation_description">Pou啪铆vejte sluch谩tka jako obvykle. Pokud se zm臎n铆 podm铆nky no拧en铆 nebo atmosf茅rick媒 tlak, spus钮te optimaliz谩tor znovu.</string>
+    <string name="sony_anc_optimize_title">Optimalizovat</string>
+    <string name="sony_anc_optimizer_status_starting">Za膷铆n谩me鈥�</string>
+    <string name="sony_anc_optimizer_status_not_running">Neprob铆h谩</string>
+    <string name="sony_anc_optimizer_status_atmospheric_pressure">M臎艡en铆 atmostf茅rick茅ho tlaku鈥�</string>
+    <string name="sony_anc_optimizer_status_analyzing">Anal媒za鈥�</string>
+    <string name="sony_anc_optimizer_status_finished">Dokon膷ov谩n铆鈥�</string>
+    <string name="sony_anc_optimize_confirmation_title">Optimaliz谩tor potla膷en铆 拧umu</string>
+    <string name="pref_anc_optimizer_state_pressure">Atmosf茅rick媒 tlak</string>
+    <string name="sony_anc_optimizer_status_wearing_condition">M臎艡en铆 stavu no拧en铆鈥�</string>
+    <string name="menuitem_nothing">Nic</string>
+    <string name="pref_device_action_fitness_app_control_start">Start Sledov谩n铆 Sportovn铆 aplikac铆</string>
+    <string name="pref_device_action_fitness_app_control_stop">Stop Sledov谩n铆 Sportovn铆 aplikac铆</string>
+    <string name="pref_title_opentracks_packagename">N谩zev bal铆膷ku OpenTracks</string>
+    <string name="pref_title_notifications_generic_settings">Syst茅mov茅 nastaven铆 ozn谩men铆</string>
+    <string name="pref_summary_opentracks_packagename">Pou啪ito pro ovl谩d谩n铆 nahr谩v谩n铆 GPS trasy pomoc铆 extern铆 fitness aplikace.</string>
+    <string name="autoExport_lastTime_label">Posledn铆 automatick媒 export: %1$s</string>
+    <string name="activity_db_management_autoexport_enabled_yes">Automatick媒 export je aktivn铆.</string>
+    <string name="activity_db_management_autoexport_scheduled_yes">Automatick媒 export byl (p暖vodn臎) napl谩nov谩n na %1$s</string>
+    <string name="activity_db_management_autoexport_location">Um铆st臎n铆 nebylo pochopeno. Jedn谩 se pravd臎podobn臎 o probl茅m nov臎j拧铆ho syst茅mu opr谩vn臎n铆 Android. Automatick媒 export s nejv臎t拧铆 pravd臎podobnost铆 nyn铆 nefunguje.</string>
+    <string name="watchface_dialog_pre_setting_position">p艡ednastavuji pozici na %s</string>
+    <string name="activity_db_management_autoexport_enabled_no">Automatick媒 export nen铆 aktivn铆.</string>
+    <string name="activity_db_management_autoexport_scheduled_no">Automatick媒 export zat铆m nebyl napl谩nov谩n.</string>
+    <string name="menuitem_email">E-mail</string>
+    <string name="gadgetbridge_running_banglejs_main">Bangle.js b臎啪铆</string>
+    <string name="application_name_banglejs_nopebble">Bangle.js Gadgetbridge</string>
+    <string name="about_description_banglejs_nopebble">Doprovodn谩 aplikace pro Bangle.js pro Android vytvo艡en谩 na z谩klad臎 projektu Gadgetbridge s p艡idan媒m p艡铆stupem k internetu.
+\n
+\nVzhledem k pravidl暖m obchodu Google Play nem暖啪eme v samotn茅 aplikaci uv茅st odkaz na d谩rcovstv铆, pokud se v谩m tato aplikace ale l铆b铆, zva啪te pros铆m mo啪nost p艡isp臎t prost艡ednictv铆m n铆啪e uveden茅 domovsk茅 str谩nky Gadgetbridge.</string>
+    <string name="title_activity_controlcenter_main_nightly">Gadgetbridge Nightly</string>
+    <string name="title_activity_controlcenter_banglejs_main">Bangle.js Gadgetbridge</string>
+    <string name="about_activity_title_banglejs_nopebble">O Bangle.js Gadgetbridge</string>
+    <string name="application_name_banglejs_main">Bangle.js Gadgetbridge</string>
+    <string name="about_activity_title_banglejs_main">O Bangle.js Gadgetbridge</string>
+    <string name="about_description_banglejs_main">Doprovodn谩 aplikace pro Bangle.js pro Android vytvo艡en谩 na z谩klad臎 projektu Gadgetbridge s p艡idan媒m p艡铆stupem k internetu.
+\n
+\nVzhledem k pravidl暖m obchodu Google Play nem暖啪eme v samotn茅 aplikaci uv茅st odkaz na d谩rcovstv铆, pokud se v谩m tato aplikace ale l铆b铆, zva啪te pros铆m mo啪nost p艡isp臎t prost艡ednictv铆m n铆啪e uveden茅 domovsk茅 str谩nky Gadgetbridge.</string>
+    <string name="application_name_main_nightly">Gadgetbridge (Nightly)</string>
+    <string name="title_activity_controlcenter_banglejs_nopebble">Bangle.js Gadgetbridge</string>
+    <string name="gadgetbridge_running_banglejs_nopebble">Bangle.js b臎啪铆</string>
+    <string name="about_activity_title_main_nightly">O aplikaci Gadgetbridge Nightly</string>
+    <string name="gadgetbridge_running_main_nightly">Nightly GB b臎啪铆</string>
+    <string name="application_name_main_nopebble">Gadgetbridge (Nightly, bez poskytovatele slu啪eb Pebble)</string>
+    <string name="about_activity_title_main_nopebble">O aplikaci Gadgetbridge Nightly, bez Pebble</string>
+    <string name="watchface_setting_light_up_on_notification">Rozsv铆tit obrazovku p艡i ozn谩men铆</string>
+    <string name="about_description_main_nightly">Svobodn谩 a lok谩ln铆 n谩hrada za uzav艡enou aplikaci va拧eho gadgetu. Nightly vyd谩n铆 Gadgetbridge. Tato verze nelze nainstalovat, m谩te-li ji啪 nainstalovanou jinou verzi aplikace Gadgetbridge nebo Pebble, z d暖vodu konfliktu v poskytovali slu啪eb Pebble.</string>
+    <string name="title_activity_controlcenter_main_nopebble">Gadgetbridge Nightly, bez Pebble</string>
+    <string name="about_description_main_nopebble">Svobodn谩 a lok谩ln铆 n谩hrada za uzav艡enou aplikaci va拧eho gadgetu. Nightly vyd谩n铆 Gadgetbridge. Tato verze m谩 p艡ejmenovanou slu啪bu poskytovatele Pebble pro zabr谩n臎n铆 konflikt暖, tak啪e n臎kter茅 integrace Pebble nebudou fungovat, ale tuto verzi je mo啪no nainstalovat paraleln臎 k ji啪 nainstalovan茅 jin茅 verzi Gadgetbridge.</string>
+    <string name="gadgetbridge_running_main_nopebble">Nightly NoPebble GB b臎啪铆</string>
+    <string name="pref_screen_notification_idle_alerts">Upozorn臎n铆 na ne膷innost</string>
+    <string name="pref_screen_vibration_patterns_title">Vzory vibrac铆</string>
+    <string name="activity_type_elliptical">Eliptick媒 tren茅r</string>
+    <string name="pref_screen_notification_profile_event_reminder">Ud谩losti</string>
+    <string name="mi5_prefs_workout_activity_types">Typy tr茅ninkov媒ch aktivit</string>
+    <string name="pref_screen_notification_profile_find_device">Naj铆t za艡铆zen铆</string>
+    <string name="prefs_activate_display_on_lift_sensitivity">Citlivost</string>
+    <string name="mi5_prefs_workout_activity_types_summary">V媒b臎r typ暖 aktivit, kter茅 se maj铆 zobrazit na obrazovce tr茅nink暖</string>
+    <string name="sensitive">Citliv茅</string>
+    <string name="normal">Norm谩ln铆</string>
+    <string name="activity_type_outdoor_running">Venkovn铆 b臎h</string>
+    <string name="activity_type_outdoor_cycling">Venkovn铆 cyklistika</string>
+    <string name="pref_screen_vibration_patterns_summary">Konfigurace vibra膷n铆ch vzor暖 pro r暖zn谩 ozn谩men铆</string>
+    <string name="pref_device_action_fitness_app_control_toggle">Zapnout/vypnout Sledov谩n铆 Sportovn铆 aplikac铆</string>
+    <string name="activity_type_freestyle">Voln媒 styl</string>
+    <string name="world_clock_delete_confirm_description">Opravdu chcete smazat polo啪ku sv臎tov媒ch hodin\?</string>
+    <string name="world_clock_no_free_slots_description">Za艡铆zen铆 nem谩 啪谩dn谩 voln谩 m铆sta pro sv臎tov茅 hodiny (celkov媒 po膷et slot暖: %1$s)</string>
+    <string name="world_clock_timezone">膶asov茅 p谩smo</string>
+    <string name="world_clock_label">Titulek</string>
+    <string name="title_activity_world_clock_details">Podrobnosti sv臎tov媒ch hodin</string>
+    <string name="pref_world_clocks_title">Sv臎tov茅 hodiny</string>
+    <string name="pref_world_clocks_summary">Konfigurace hodin pro jin谩 膷asov谩 p谩sma</string>
+    <string name="world_clock_delete_confirm_title">Odstranit \'%1$s\'</string>
+    <string name="world_clock_no_free_slots_title">沤谩dn茅 voln茅 m铆sto</string>
+    <string name="devicetype_sony_wf_1000xm3">Sony WF-1000XM3</string>
+    <string name="devicetype_galaxybuds_pro">Galaxy Buds Pro</string>
+    <string name="pref_title_touch_ambient">Okoln铆 zvuk</string>
+    <string name="pref_title_device_internet_access">Povolit p艡铆stup k internetu</string>
+    <string name="pref_summary_device_internet_access">Povolit v拧em aplikac铆m na hodink谩ch p艡铆stup k internetu</string>
+    <string name="pref_title_device_intents">Povolit Intenty</string>
+    <string name="heartrate_bpm_105">105 tep暖/min</string>
+    <string name="heartrate_bpm_100">100 tep暖/min</string>
+    <string name="heartrate_bpm_110">110 tep暖/min</string>
+    <string name="heartrate_bpm_112">112 tep暖/min</string>
+    <string name="prefs_switch_control_right">Ovl谩d谩n铆 p艡ep铆n谩n铆 Vpravo</string>
+    <string name="pref_title_touch_volume">Hlasitost</string>
+    <string name="pref_title_touch_spotify">Spotify</string>
+    <string name="pref_switch_noise_control">P艡ep铆n谩n铆 regulace hluku</string>
+    <string name="permission_notification_listener">Aplikace %1$s pot艡ebuje p艡铆stup k ozn谩men铆m, aby mohla zobrazovat ozn谩men铆 na p艡ipojen媒ch hodink谩ch p艡i vypnut茅 obrazovce telefonu.
+\n
+\nKlepn臎te na 鈥�%2$s鈥�, pot茅 na 鈥�%1$s鈥� a zapn臎te 鈥濸ovolit p艡铆stup k ozn谩men铆m鈥�, pot茅 klepn臎te na 鈥瀂p臎t鈥� pro n谩vrat do aplikace %1$s.</string>
+    <string name="permission_notification_policy_access">Aplikace %1$s pot艡ebuje p艡铆stup k nastaven铆 re啪imu Neru拧it, aby jej mohla nastavit na p艡ipojen媒ch hodink谩ch p艡i vypnut茅 obrazovce telefonu.
+\n
+\nKlepn臎te na 鈥�%2$s鈥�, pot茅 na 鈥�%1$s鈥� a zapn臎te 鈥濸ovolit re啪im Neru拧it鈥�, pot茅 klepn臎te na 鈥瀂p臎t鈥� pro n谩vrat do aplikace %1$s.</string>
+    <string name="prefs_in_ear_detection_summary">P艡ehr谩v谩n铆 hovor暖 do sluch谩tek, jsou li v u拧铆ch</string>
+    <string name="heartrate_bpm_130">130 tep暖/min</string>
+    <string name="pref_title_banglejs_text_bitmap">Text jako Bitmapa</string>
+    <string name="pref_summary_banglejs_text_bitmap">Pokud nen铆 mo啪no zobrazit dan茅 slovo pomoc铆 fontu hodinek, Gadgetbridge vykresl铆 obr谩zek, kter媒 se na hodink谩ch zobraz铆</string>
+    <string name="heartrate_bpm_135">135 tep暖/min</string>
+    <string name="heartrate_bpm_150">150 tep暖/min</string>
+    <string name="prefs_stress_monitoring_title">Sledov谩n铆 stresu</string>
+    <string name="prefs_voice_detect_summary">Automaticky povolit okoln铆 zvuk a sn铆啪it p艡ehr谩v谩n铆 po zji拧t臎n铆 hlasu</string>
+    <string name="pref_summary_device_intents">Umo啪n铆 aplikac铆m Bangle.js hodinek pos铆lat Android Intenty a povol铆 ostatn铆m Android aplikac铆m (Tasker) aby pos铆laly data do Bangle.js pomoc铆 com.banglejs.uart.tx Intentu. Pot艡ebuje povolen铆 k zobrazen铆 nad ostatn铆mi aplikacemi, aby mohl pracovat na pozad铆.</string>
+    <string name="prefs_heartrate_alert_experimental_description">N谩ramek zavibruje, pokud v谩拧 tep p艡ekro膷铆 prahovou hodnotu bez zjevn茅 fyzick茅 aktivity v posledn铆ch 10 minut谩ch. Tato funkce je experiment谩ln铆 a nebyla d暖kladn臎 testov谩na.</string>
+    <string name="prefs_activity_monitoring_description">Automaticky zv媒拧铆 膷etnost detekce srde膷n铆 frekvence p艡i fyzick茅 aktivit臎.</string>
+    <string name="mi2_prefs_heart_rate_monitoring">Monitorov谩n铆 srde膷n铆 frekvence</string>
+    <string name="prefs_ambient_sound_during_call_title">Okoln铆 zvuk b臎hem hovoru</string>
+    <string name="prefs_switch_control_left">Ovl谩d谩n铆 p艡ep铆n谩n铆 Vlevo</string>
+    <string name="pref_switch_controls_ambient_off">Okoln铆 zvuk 鈫愨啋 Vypnuto</string>
+    <string name="heartrate_bpm_120">120 tep暖/min</string>
+    <string name="heartrate_bpm_140">140 tep暖/min</string>
+    <string name="heartrate_bpm_125">125 tep暖/min</string>
+    <string name="prefs_heartrate_alert_experimental_title">Upozorn臎n铆 na srde膷n铆 frekvenci (experiment谩ln铆)</string>
+    <string name="prefs_stress_monitoring_description">Sledov谩n铆 煤rovn臎 stresu p艡i odpo膷inku</string>
+    <string name="heartrate_bpm_145">145 tep暖/min</string>
+    <string name="prefs_heartrate_alert_threshold">Prahov谩 hodnota upozorn臎n铆 na srde膷n铆 frekvenci</string>
+    <string name="prefs_activity_monitoring_title">Sledov谩n铆 aktivity</string>
+    <string name="mi2_prefs_heart_rate_monitoring_summary">Konfigurace monitorov谩n铆 srde膷n铆ho tepu</string>
+    <string name="mi2_prefs_heart_rate_monitoring_alerts_summary">Konfigurace monitorov谩n铆 srde膷n铆ho tepu a prahov媒ch hodnot v媒strah</string>
+    <string name="prefs_seamless_connection_switch_title">Zjednodu拧en茅 p艡ep铆n谩n铆 p艡ipojen铆</string>
+    <string name="prefs_seamless_connection_switch_summary">P艡ep铆n谩 sluch谩tka automaticky mezi p艡ipojen媒mi za艡铆zen铆mi</string>
+    <string name="prefs_ambient_volume_left">Okoln铆 hlasitost Vlevo</string>
+    <string name="prefs_ambient_volume_right">Okoln铆 hlasitost Vpravo</string>
+    <string name="prefs_customize_ambient_sound_summary">P艡izp暖soben铆 nastaven铆 okoln铆ho zvuku</string>
+    <string name="prefs_ambient_sound_during_call_summary">B臎hem hovoru sly拧et jasn臎 vlastn铆 hlas</string>
+    <string name="prefs_ambient_settings_title">Mo啪nosti okoln铆ho zvuku</string>
+    <string name="prefs_active_noise_cancelling_level">脷rove艌 aktivn铆ho potla膷en铆 hluku</string>
+    <string name="prefs_active_noise_cancelling_level_high">Vysok谩</string>
+    <string name="pref_title_touch_anc">Aktivn铆 potla膷en铆 hluku</string>
+    <string name="pref_switch_controls_anc_off">Potla膷en铆 hluku 鈫愨啋 Vypnuto</string>
+    <string name="prefs_voice_detect_duration">Konec po klidu za:</string>
+    <string name="prefs_active_noise_cancelling_level_low">N铆zk谩</string>
+    <string name="pref_title_touch_quick_ambient">Rychl媒 okoln铆 zvuk</string>
+    <string name="prefs_noise_control_with_one_earbud">Regulace hluku s jedn铆m sluch谩tkem</string>
+    <string name="pref_ambient_sound_tone_summary">Od M臎kk茅ho po Jasn媒</string>
+    <string name="pref_balance">Vyrovn谩n铆 zvuku</string>
+    <string name="pref_switch_controls_anc_ambient">Potla膷en铆 hluku 鈫愨啋 Okoln铆 zvuk</string>
+    <string name="prefs_noise_control">Regulace hluku</string>
+    <string name="prefs_voice_detect">Detekce hlasu</string>
+    <string name="pref_title_touch_voice_assistant">Hlasov媒 asistent</string>
+    <string name="prefs_noise_control_with_one_earbud_summary">Povolit regulaci hluku i p艡i pou啪it铆 pouze s jednoho sluch谩tka</string>
+    <string name="pref_ambient_sound_tone">T贸n okoln铆ho zvuku</string>
+    <string name="prefs_double_tap_edge">Dvojit茅 klepnut铆 na okraj</string>
+    <string name="prefs_double_tap_edge_summary">Detekce dvojit茅ho klepnut铆, i kdy啪 nen铆 klepnuto na dotykov媒 panel</string>
+    <string name="pref_voice_detect_duration_5">5 sekund</string>
+    <string name="pref_voice_detect_duration_15">15 sekund</string>
+    <string name="pref_voice_detect_duration_10">10 sekund</string>
+    <string name="pref_explanation_authkey_new_protocol">Pokud se na za艡铆zen铆 zobraz铆 zpr谩va \"Aktualizujte aplikaci na nejnov臎j拧铆 verzi/Update the app to latest version\", za拧krtn臎te volbu \"Nov媒 protokol ov臎艡ov谩n铆\" v媒拧e. Klepn臎te zde pro dal拧铆 podrobnosti ve wiki.</string>
+    <string name="pref_explanation_authkey">N臎kter谩 za艡铆zen铆 vy啪aduj铆 speci谩ln铆 p谩rovac铆 kl铆膷 pro prvn铆 inicializaci za艡铆zen铆. Klepn臎te zde pro dal拧铆 podrobnosti ve wiki.</string>
+    <string name="ukranian">Ukrajin拧tina</string>
+    <string name="notification_channel_gps">Sledov谩n铆 pomoc铆 GPS</string>
+    <string name="controlcenter_set_parent_folder">Nastavit nad艡azenou slo啪ku</string>
+    <string name="error_setting_parent_folder">Chyba p艡i nastavov谩n铆 nad艡azen茅 slo啪ky: %s</string>
+    <string name="controlcenter_folder_name">N谩zev slo啪ky:</string>
+    <string name="controlcenter_toggle_details">P艡epnout podrobnosti</string>
+    <string name="pref_workout_start_on_phone_title">Sledov谩n铆 pomoc铆 fitness aplikac铆</string>
+    <string name="pref_workout_start_on_phone_summary">Spu拧t臎n铆/zastaven铆 sledov谩n铆 fitness aplikac铆 v telefonu p艡i spu拧t臎n铆 tr茅ninku na n谩ramku/hodink谩ch</string>
+    <string name="pref_workout_send_gps_title">B臎hem cvi膷en铆 pos铆lat GPS do n谩ramku</string>
+    <string name="pref_workout_send_gps_summary">B臎hem tr茅ninku odes铆lat do n谩ramku aktu谩ln铆 GPS polohu</string>
+    <string name="discovery_scanning_intensity">Intenzita skenov谩n铆</string>
+    <string name="notification_gps_text">Odes铆l谩n铆 polohy GPS do %1$d za艡铆zen铆</string>
+    <string name="notification_gps_title">Gadgetbridge GPS</string>
+    <string name="quick_alarm">Rychl媒 bud铆k</string>
+    <string name="pref_title_banglejs_webview_url">Adresa URL zavad臎膷e aplikac铆</string>
+    <string name="pref_summary_banglejs_webview_url">Pokud chcete vlastn铆 zavad臎膷 aplikac铆, vlo啪te adresu ve form谩tu URL https://.../android.html. V opa膷n茅m p艡铆pad臎 zanechte pr谩zdn茅 pro https://banglejs.com/apps</string>
+    <string name="portuguese_br">Portugal拧tina (Braz铆lie)</string>
+    <string name="portuguese_pt">Portugal拧tina (Portugalsko)</string>
+    <string name="permission_location">Aplikace %1$s pot艡ebuje p艡铆stup k poloze na pozad铆 z d暖vodu zaji拧t臎n铆 udr啪en铆 spojen铆 z va拧imi hodinkami i v dob臎, kdy je obrazovka telefonu zhasnut谩. \n \nNa n谩sleduj铆c铆 obrazovce pros铆m vyberte 鈥�%2$s鈥� a pot茅 klepn臎te na 鈥瀂p臎t鈥� pro n谩vrat do aplikace %1$s.</string>
+    <string name="info_no_devices_connected">Nejsou p艡ipojena 啪谩dn谩 za艡铆zen铆</string>
+    <string name="info_connected_count">P艡ipojen谩 za艡铆zen铆: %d</string>
+    <string name="czech">膶e拧tina</string>
+    <string name="pref_device_action_phone_gps_location_listener_stop">Ukon膷it slu啪bu GPS polohy</string>
+    <string name="prefs_password">Heslo</string>
+    <string name="prefs_password_summary">Zamknut铆 n谩ramku heslem p艡i sejmut铆 ze z谩p臎st铆</string>
+    <string name="prefs_password_enabled">Heslo povoleno</string>
+    <string name="bengali">Beng谩l拧tina</string>
+    <string name="estonian">Eston拧tina</string>
+    <string name="extended_ascii">Roz拧铆艡en茅 ASCII</string>
+    <string name="icelandic">Island拧tina</string>
+    <string name="lithuanian">Litev拧tina</string>
+    <string name="controlcenter_add_new_folder">P艡idat novou slo啪ku</string>
+    <string name="controlcenter_unset_folder">Zru拧it nastaven铆 slo啪ky</string>
+    <string name="pref_blacklist_calendars_summary">Kalend谩艡e na 膷ern茅 listin臎 nebudou synchronizov谩ny se za艡铆zen铆m</string>
+    <string name="pref_title_notification_delay_calls">Zpo啪d臎n铆 upozorn臎n铆 na hovor</string>
+    <string name="pref_summary_notification_delay_calls">Zpo啪d臎n铆 p艡ed odesl谩n铆m ozn谩men铆 o p艡铆choz铆m hovoru do za艡铆zen铆, v sekund谩ch.</string>
+    <string name="discovery_scanning_intensity_warning">Pokud doch谩z铆 k zamrznut铆 nebo nereagov谩n铆 aplikace p艡i skenov谩n铆, zkuste nastavit intenzitu skenov谩n铆 na ni啪拧铆 煤rove艌. Pokud va拧e bluetooth za艡铆zen铆 nen铆 vyhled谩no, zkuste nastavit intenzitu skenov谩n铆 na vy拧拧铆 煤rove艌.</string>
+    <string name="prefs_password_4_digits_1_to_4_summary">Heslo mus铆 m铆t 4 znaky a mus铆 obsahovat 膷铆sla 1 a啪 4</string>
+    <string name="prefs_password_6_digits_0_to_9_summary">Heslo mus铆 m铆t 6 znak暖 a m暖啪e obsahovat pouze 膷铆sla</string>
+    <string name="persian">Per拧tina</string>
+    <string name="scandinavian">Skandin谩v拧tina</string>
+    <string name="quick_alarm_description">Bud铆k z widgetu</string>
+    <string name="controlcenter_set_preferences">Nastaven铆 preferenc铆</string>
+    <string name="controlcenter_connected_fraction">P艡ipojeno: %d/%d</string>
+    <string name="error_deleting_device">Chyba p艡i maz谩n铆 za艡铆zen铆: %s</string>
+    <string name="controlcenter_set_folder_title">Nastaven铆 nebo vytvo艡en铆 nov茅 slo啪ky</string>
+    <string name="auto_reconnect_ble_title">Automatick茅 op臎tovn茅 p艡ipojen铆 k za艡铆zen铆</string>
+    <string name="auto_reconnect_ble_summary">Pravideln臎 se aktivn臎 pokou拧et p艡ipojit k za艡铆zen铆</string>
+    <string name="connection_over_ble">P艡ipojen铆 p艡es BLE</string>
+    <string name="connection_over_bt_classic">P艡ipojen铆 p艡es Bluetooth classic</string>
+    <string name="autoconnect_from_device_title">P艡ipojen铆 p艡i po啪adavku o p艡ipojen铆 ze za艡铆zen铆</string>
+    <string name="autoconnect_from_device_summary">Nav谩z谩n铆 p艡ipojen铆 p艡i iniciov谩n铆 p艡ipojen铆 za艡铆zen铆m, nap艡铆klad sluch谩tky</string>
+    <string name="watchface_dialog_widget_timezone_duration">Doba viditelnosti hodin (v sekund谩ch)</string>
+    <string name="appmanager_item_outdated">(zastaral谩 verze)</string>
+    <string name="open_fw_installer_warning_title">Varov谩n铆</string>
+    <string name="open_fw_installer_getting_files_title">Z铆sk谩v谩n铆 souboru firmwaru/aplikace</string>
+    <string name="open_fw_installer_ensure_device_connected">Ujist臎te se, 啪e za艡铆zen铆 %s je p艡ipojeno</string>
+    <string name="open_fw_installer_connect_minimum_one_device">P艡ipojte pros铆m alespo艌 JEDNO za艡铆zen铆, na kter茅 chcete soubor odeslat.</string>
+    <string name="open_fw_installer_connect_maximum_one_device">P艡ipojte POUZE JEDNO za艡铆zen铆, na kter茅 chcete soubor odeslat.</string>
+    <string name="appmanager_app_share">Sd铆let</string>
+    <string name="open_fw_installer_warning_text">Tato funkce m暖啪e zp暖sobit zablokov谩n铆 za艡铆zen铆. Nikdy se to 啪谩dn茅mu z v媒voj谩艡暖 nestalo, ale nezapome艌te, 啪e to d臎l谩te na vlastn铆 nebezpe膷铆.</string>
+    <string name="open_fw_installer_pick_file">Vybrat soubor</string>
+    <string name="open_fw_installer_info_text_title">Instal谩tor soubor暖</string>
+    <string name="open_fw_installer_select_file">Vyberte soubor, kter媒 chcete nahr谩t do za艡铆zen铆: %s</string>
+    <string name="open_fw_installer_getting_files_text">Vzhledem k tomu, 啪e soubory firmwaru nem暖啪eme distribuovat, budete si je muset obstarat sami. To znamen谩, 啪e budete muset hledat soubory v souborech apk, na internetu, na f贸rech, na Amazfitwatchfaces (pro za艡铆zen铆 Miband/Amazfit) atd.</string>
+    <string name="open_fw_installer_info_text">Instal谩tor firmware/cifern铆k暖/aplikac铆/soubor暖 umo啪艌uje nahr谩t/instalovat do za艡铆zen铆 podporovan茅 soubory (firmware, cifern铆ky, aplikace, GPS, zdroje, fonty...). Dal拧铆 informace naleznete na wiki: https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Firmware-Update</string>
+    <string name="steps_streaks_streak_days">Dny
+\nS茅rie</string>
+    <string name="steps_streaks_average_steps">Pr暖m臎r
+\nkrok暖</string>
+    <string name="steps_streaks_achievement_rate">M铆ra
+\n煤sp臎拧nosti</string>
+    <string name="step_streak_longest">Nejdel拧铆</string>
+    <string name="watchface_setting_title_button_toggle_widgets">P艡ep铆n谩n铆 viditelnosti widget暖</string>
+    <string name="watchface_dialog_widget_background">Pozad铆</string>
+    <string name="watchface_setting_button_toggle_widgets">P艡ep铆n谩n铆 zobrazen铆 widget暖</string>
+    <string name="hybridhr_widget_bg_double_circle">Dvojit媒 kruh</string>
+    <string name="hybridhr_widget_bg_dashed_circle">P艡eru拧ovan媒 kruh</string>
+    <string name="steps_streaks_total_steps">Celkov茅
+\nkroky</string>
+    <string name="step_streak_total">Celkem</string>
+    <string name="steps_streaks">S茅rie 煤sp臎ch暖</string>
+    <string name="steps_streaks_hint">S茅rie po sob臎 jdouc铆ch dn暖 bez p艡eru拧en铆 s dosa啪en铆m c铆le krok暖</string>
+    <string name="step_streak_ongoing">Prob铆haj铆c铆</string>
+    <string name="steps_streaks_since_date">Od %s</string>
+    <string name="step_streak_average_steps_hint">Pr暖m臎rn茅 kroky na den v s茅rii</string>
+    <string name="steps_streaks_total_steps_average_hint">Celkov媒 pr暖m臎r %d krok暖 za den</string>
+    <string name="steps_streaks_total_steps_hint_totals">Celkov媒 po膷et v拧ech zaznamenan媒ch krok暖</string>
+    <string name="step_streak_days_hint">Po膷et po sob臎 jdouc铆ch dn暖 s dosa啪en媒m c铆lem krok暖</string>
+    <string name="steps_streaks_total_days_hint_totals">Procento dn铆 s dosa啪en媒m c铆lem ve vztahu ke v拧em dn暖m s kroky</string>
+    <string name="steps_streaks_total_steps_hint">Celkov媒 po膷et krok暖 v s茅rii</string>
+    <string name="hybridhr_widget_bg_thin_circle">Tenk媒 kruh</string>
+    <string name="pref_title_banglejs_txt_bitmap_size">Velikost Textu Bitmapy</string>
+    <string name="pref_summary_banglejs_txt_bitmap_size">Velikost fontu p艡i vykreslov谩n铆 textu</string>
+    <string name="watchface_dialog_widget_cat_generic">Obecn媒</string>
+    <string name="watchface_dialog_widget_cat_position">Pozice</string>
+    <string name="watchface_dialog_widget_cat_2nd_tz_widget">Widget 2. 膷asov茅ho p谩sma</string>
+    <string name="watchface_dialog_widget_cat_custom_widget">Vlastn铆 widget</string>
+    <string name="watchface_dialog_title_widget">Nastaven铆 widgetu</string>
+    <string name="share_log_not_enabled_message">Nejprve mus铆te povolit protokolov谩n铆 v Nastaven铆 - Zapisovat protokoly</string>
+    <string name="note">Pozn谩mka</string>
+    <string name="controlcenter_get_heartrate_measurement">Zm臎艡it tep</string>
+    <string name="watchface_setting_title_custom_events">Vlastn铆 ud谩losti</string>
+    <string name="watchface_setting_button_toggle_backlight">Zapnut铆 podsv铆cen铆</string>
+    <string name="pref_write_logfiles_not_available">Inicializace zapisov谩n铆 log暖 se nezda艡ila, z谩pis do souboru nen铆 dostupn媒. Restartujte aplikaci pro pokus o novou inicializaci.</string>
+    <string name="devicetype_binary_sensor">Bin谩rn铆 senzor</string>
+    <string name="title_activity_controlcenter_banglejs_nightly">Bangle.js Gadgetbridge (Nightly)</string>
+    <string name="about_description_banglejs_nightly">Doprovodn谩 aplikace pro Bangle.js pro Android vytvo艡en谩 na z谩klad臎 projektu Gadgetbridge s p艡idan媒m p艡铆stupem k internetu.
+\n
+\nVzhledem k pravidl暖m Obchodu Google Play nem暖啪eme v samotn茅 aplikaci uv茅st odkaz na finan膷n铆 dary, pokud se v谩m ale tato aplikace l铆b铆, zva啪te pros铆m mo啪nost p艡isp臎t prost艡ednictv铆m n铆啪e uveden茅 domovsk茅 str谩nky Gadgetbridge.</string>
+    <string name="gadgetbridge_running_banglejs_nightly">Nightly Bangle.js b臎啪铆</string>
+    <string name="prefs_hourly_chime">Hodinov媒 sign谩l</string>
+    <string name="prefs_hourly_chime_summary">Hodinky p铆pnou jednou za hodinu</string>
+    <string name="step_streaks_achievements_sharing_title">脷sp臎chy v kroc铆ch</string>
+    <string name="application_name_banglejs_nightly">Bangle.js Gadgetbridge (Nightly)</string>
+    <string name="step_streaks_achievements_sharing_message">Moje 煤sp臎chy denn铆ch c铆l暖 krok暖!</string>
+    <string name="about_activity_title_banglejs_nightly">O aplikace Bangle.js Gadgetbridge (Nightly)</string>
+    <string name="pref_header_time">膶as</string>
+    <string name="pref_header_workout">Cvi膷en铆</string>
+    <string name="pref_summary_screen_on_on_notifications">Zapnout podsv铆cen铆 displeje p艡i obdr啪en铆 ozn谩men铆</string>
+    <string name="pref_cache_weather">Ukl谩d谩n铆 informac铆 o po膷as铆 do mezipam臎ti</string>
+    <string name="pref_cache_weather_summary">Informace o po膷as铆 budou ukl谩d谩ny do mezipam臎ti nap艡铆膷 restarty aplikace.</string>
+    <string name="heartrate_bpm_40">40 tep暖/min</string>
+    <string name="heartrate_bpm_45">45 tep暖/min</string>
+    <string name="heartrate_bpm_50">50 tep暖/min</string>
+    <string name="heartrate_bpm_115">115 tep暖/min</string>
+    <string name="prefs_heartrate_alert_low_threshold">Varovn媒 pr谩h n铆zk茅 srde膷n铆 frekvence</string>
+    <string name="prefs_relaxation_reminder_title">P艡ipomenut铆 relaxace</string>
+    <string name="prefs_relaxation_reminder_description">Vibrov谩n铆 n谩ramku v谩s upozorn铆, pokud je hodnota stresu vy拧拧铆 ne啪 80</string>
+    <string name="prefs_spo2_monitoring_title">Monitorov谩n铆 kysl铆ku v krvi</string>
+    <string name="seconds_6">6 sekund</string>
+    <string name="seconds_7">7 sekund</string>
+    <string name="seconds_8">8 sekund</string>
+    <string name="seconds_13">13 sekund</string>
+    <string name="seconds_14">14 sekund</string>
+    <string name="pref_sleep_breathing_quality_monitoring">Sledovat kvalitu d媒ch谩n铆 ve sp谩nku</string>
+    <string name="prefs_always_on_display">St谩le zapnut媒 displej</string>
+    <string name="prefs_always_on_display_summary">Displej v啪dy zapnut媒</string>
+    <string name="prefs_screen_timeout">膶asov媒 limit obrazovky</string>
+    <string name="smart">Inteligentn铆</string>
+    <string name="mi2_dnd_always">V啪dy</string>
+    <string name="devicetype_miband7">Xiaomi Smart Band 7</string>
+    <string name="menuitem_countdown">Odpo膷铆t谩v谩n铆</string>
+    <string name="pref_screen_brightness">Jas obrazovky</string>
+    <string name="pref_header_connection">Spojen铆</string>
+    <string name="fw_upgrade_notice_miband7">Chyst谩te se nainstalovat firmware %s na v谩拧 Xiaomi Smart Band 7.
+\n
+\nV谩拧 n谩ramek se po instalaci souboru .zip restartuje.
+\n
+\nPOKRA膶UJTE NA VLASTN脥 NEBEZPE膶脥!</string>
+    <string name="pref_header_display">Displej</string>
+    <string name="pref_title_screen_on_on_notifications">Rozsv铆tit displej p艡i ozn谩men铆ch</string>
+    <string name="spo2_off">Vypnuto</string>
+    <string name="pref_header_calendar">Kalend谩艡</string>
+    <string name="pref_header_health">Zdrav铆</string>
+    <string name="pref_summary_canned_replies">Odpov臎di z hodinek pomoc铆 p艡ednastaven媒ch zpr谩v</string>
+    <string name="prefs_heartrate_alert_high_threshold">Varovn媒 pr谩h vysok茅 srde膷n铆 frekvence</string>
+    <string name="prefs_spo2_alert_threshold">Prahov谩 hodnota v媒strahy SpO2</string>
+    <string name="spo2_perc_80">80 %</string>
+    <string name="spo2_perc_85">85 %</string>
+    <string name="spo2_perc_90">90 %</string>
+    <string name="pref_header_heartrate_sleep">Sp谩nek</string>
+    <string name="pref_header_heartrate_allday">Celodenn铆 sledov谩n铆</string>
+    <string name="seconds_12">12 sekund</string>
+    <string name="menuitem_personal_activity_intelligence">Inteligence osobn铆ch aktivit</string>
+    <string name="prefs_spo2_monitoring_description">Automatick茅 sledov谩n铆 hladiny kysl铆ku v krvi b臎hem dne</string>
+    <string name="seconds_9">9 sekund</string>
+    <string name="seconds_11">11 sekund</string>
+    <string name="seconds_15">15 sekund</string>
+    <string name="menuitem_workout_history">Historie cvi膷en铆</string>
+    <string name="menuitem_female_health">沤ensk茅 zdrav铆</string>
+    <string name="menuitem_workout_status">Stav cvi膷en铆</string>
+    <string name="permission_display_over_other_apps">Aplikace %1$s pot艡ebuje opr谩vn臎n铆 k zobrazen铆 nad ostatn铆mi aplikacemi, aby hodinky Bangle.js mohly spou拧t臎t aktivity prost艡ednictv铆m z谩m臎r暖 (intents) p艡i b臎hu aplikace %1$s na pozad铆.
+\n
+\nToho lze vyu啪铆t ke spu拧t臎n铆 hudebn铆 aplikace pro p艡ehr谩n铆 skladby a ke spoust臎 dal拧铆ch v臎c铆.
+\n
+\nKlepn臎te na 鈥�%2$s鈥�, pot茅 na 鈥�%1$s鈥� a zapn臎te 鈥濸ovolit zobrazen铆 p艡es jin茅 aplikace鈥�, pot茅 klepn臎te na 鈥瀂p臎t鈥� pro n谩vrat do aplikace %1$s.
+\n
+\nPokud chcete, aby aplikace %1$s p艡estala 啪谩dat o opr谩vn臎n铆, p艡ejd臎te do 鈥濶astaven铆鈥� a vypn臎te 鈥濳ontrolovat stav opr谩vn臎n铆鈥�.
+\n
+\nUjist臎te se, 啪e jste aplikaci %1$s poskytli po啪adovan谩 opr谩vn臎n铆 pro funkci dle va拧ich o膷ek谩v谩n铆.</string>
+    <string name="pref_header_heartrate_alerts">Upozorn臎n铆 na tepovou frekvenci</string>
+    <string name="pref_header_stress">Stres</string>
+    <string name="pref_header_spo2">Kysl铆k v krvi</string>
+    <string name="devicetype_flipper_zero">Flipper zero</string>
+    <string name="activity_prefs_allow_bluetooth_intent_api">Rozhran铆 Bluetooth Intent API</string>
+    <string name="fw_upgrade_notice_amazfit_gts3">Chyst谩te se nainstalovat firmware %s na v谩拧 Amazfit GTS 3.
+\n
+\nV谩拧 n谩ramek se po instalaci souboru .zip restartuje.
+\n
+\nPOKRA膶UJTE NA VLASTN脥 NEBEZPE膶脥!</string>
+    <string name="pref_title_notifications_ignore_low_priority">Ignorovat ozn谩men铆 s n铆zkou prioritou</string>
+    <string name="pref_summary_notifications_ignore_low_priority">Nepos铆lat hodink谩m ozn谩men铆 s n铆zkou a minim谩ln铆 prioritou</string>
+    <string name="pref_title_notification_prefer_long_text">Up艡ednost艌ovat dlouh媒 text ozn谩men铆</string>
+    <string name="pref_summary_notification_prefer_long_text">Pokud je k dispozici, odeslat do za艡铆zen铆 ozn谩men铆 v pln茅 d茅lce</string>
+    <string name="kind_app">Aplikace</string>
+    <string name="devicetype_amazfit_gts3">Amazfit GTS 3</string>
+    <string name="menuitem_unknown_app">Nezn谩m谩 (%s)</string>
+    <string name="activity_prefs_summary_allow_bluetooth_intent_api">Povolit ovl谩d谩n铆 p艡ipojen铆 Bluetooth pomoc铆 intent API</string>
+    <string name="pref_header_sound_vibration">Zvuk a vibrace</string>
+    <string name="pref_gps_satellite_search">Hled谩n铆 satelit暖</string>
+    <string name="pref_camera_remote_title">Ovl谩d谩n铆 fotoapar谩tu</string>
+    <string name="pref_gps_band">P谩smo GPS</string>
+    <string name="pref_gps_mode_preset">Re啪im GPS</string>
+    <string name="pref_crown_vibration">Vibrace korunky</string>
+    <string name="pref_display_caller_summary">Zobrazit jm茅no nebo telefonn铆 膷铆slo p艡铆choz铆ch hovor暖</string>
+    <string name="pref_pair_bluetooth_calls_summary">Klepn臎te sem pro zah谩jen铆 p谩rovac铆ho procesu</string>
+    <string name="accuracy">P艡esnost</string>
+    <string name="balanced">Vyv谩啪en媒</string>
+    <string name="power_saving">艩et艡en铆 energie</string>
+    <string name="custom">Vlastn铆</string>
+    <string name="activity_prefs_target_weight_kg">C铆lov谩 v谩ha v kg</string>
+    <string name="bluetooth_calls">Bluetooth vol谩n铆</string>
+    <string name="bluetooth_calls_settings">Nastaven铆 Bluetooth vol谩n铆</string>
+    <string name="title_activity_contact_details">Podrobnosti kontaktu</string>
+    <string name="activity_type_dance">Tanec</string>
+    <string name="activity_type_volleyball">Volejbal</string>
+    <string name="activity_type_zumba">Zumba</string>
+    <string name="menuitem_recommendation">Doru膷en铆</string>
+    <string name="menuitem_calendar">Kalend谩艡</string>
+    <string name="appmanager_watchface_activate">Aktivovat</string>
+    <string name="appmanager_app_download">St谩hnout do mezipam臎ti</string>
+    <string name="appmanager_download_started">Stahov谩n铆 aplikace za膷alo</string>
+    <string name="appmanager_downloaded_to_cache">Sta啪eno %s do mezipam臎ti</string>
+    <string name="appmanager_download_app_error">Chyba p艡i stahov谩n铆 aplikace</string>
+    <string name="pref_title_overwrite_settings_on_connection">P艡epsat nastaven铆 p艡i p艡ipojen铆</string>
+    <string name="pref_summary_overwrite_settings_on_connection">P艡epsat v拧echna nastaven铆 na n谩ramku p艡i jeho p艡ipojen铆.</string>
+    <string name="pref_workout_detection_title">Rozpozn谩n铆 cvi膷en铆</string>
+    <string name="function_enabled">Povoleno</string>
+    <string name="charts_legend_stress_average">Pr暖m臎rn媒 stres</string>
+    <string name="devicetype_amazfit_gts4_mini">Amazfit GTS 4 Mini</string>
+    <string name="pref_screen_auto_brightness_summary">Automaticky nastavit jas obrazovky v z谩vislosti na okoln铆m osv臎tlen铆</string>
+    <string name="activity_prefs_goal_standing_time_minutes">Denn铆 c铆l: 膷as vestoje v minut谩ch</string>
+    <string name="menuitem_wechat_pay">WeChat Pay</string>
+    <string name="ftp_server">FTP server</string>
+    <string name="activity_error_share_failed">Sd铆len铆 souboru selhalo.</string>
+    <string name="devicetype_amazfit_gtr_lite">Amazfit GTR Lite</string>
+    <string name="menuitem_bluetooth">Bluetooth</string>
+    <string name="menuitem_wifi">Wi-Fi</string>
+    <string name="pref_screen_auto_brightness_title">Automatick媒 jas</string>
+    <string name="pref_workout_detection_categories_title">Kategorie cvi膷en铆</string>
+    <string name="pref_workout_keep_screen_on_title">P艡i cvi膷en铆 ponechat zapnutou obrazovku</string>
+    <string name="long_press">Dlouh茅 podr啪en铆</string>
+    <string name="devicetype_galaxybuds_2">Galaxy Buds2</string>
+    <string name="single_tap">Jedno klepnut铆</string>
+    <string name="double_tap">Dvojit茅 klepnut铆</string>
+    <string name="triple_tap">Trojit茅 klepnut铆</string>
+    <string name="gps_galileo">GPS + GALILEO</string>
+    <string name="pref_workout_detection_sensitivity">Citlivost</string>
+    <string name="seconds_25">25 sekund</string>
+    <string name="pref_enable_unsupported_settings_title">Povolit nepodporovan谩 nastaven铆</string>
+    <string name="activity_prefs_goal_fat_burn_time_minutes">Denn铆 c铆l: 膷as spalov谩n铆 tuk暖 v minut谩ch</string>
+    <string name="devicetype_sony_wf_1000xm4">Sony WF-1000XM4</string>
+    <string name="pref_contacts_title">Kontakty</string>
+    <string name="activity_type_gymnastics">Gymnastika</string>
+    <string name="activity_type_bowling">Bowling</string>
+    <string name="devicetype_sony_wh_1000xm5">Sony WH-1000XM5</string>
+    <string name="menuitem_mi_ai">MI AI</string>
+    <string name="devicetype_asteroidos">AsteroidOS</string>
+    <string name="devicetype_soflow_s06">SoFlow SO6</string>
+    <string name="gps">GPS</string>
+    <string name="gps_gnolass">GPS + GNOLASS</string>
+    <string name="bluetooth_calls_pairing">P谩rov谩n铆 Bluetooth vol谩n铆</string>
+    <string name="all_satellites">V拧echny satelity</string>
+    <string name="pref_display_caller_title">Zobrazit informace o kontaktu</string>
+    <string name="pref_title_banglejs_phone_gps_enbale">Pou啪铆vat data GPS z telefonu</string>
+    <string name="pref_title_banglejs_phone_gps_update_interval">Interval aktualizace GPS dat</string>
+    <string name="pref_camera_remote_summary">Umo啪n铆 hodink谩m ovl谩dat fotoapar谩t telefonu</string>
+    <string name="pref_workout_keep_screen_on_summary">Obrazovka z暖stane p艡i cvi膷en铆 zapnut谩 a jas bude automaticky upravov谩n pro zobrazen铆</string>
+    <string name="menuitem_vo2_max">VO鈧� Max</string>
+    <string name="pref_contacts_summary">Nastavit kontakty na hodink谩ch</string>
+    <string name="abstract_chart_fragment_kind_rem_sleep">Sp谩nek REM</string>
+    <string name="activity_type_table_tennis">Ping pong</string>
+    <string name="devicetype_amazfit_gtr3">Amazfit GTR 3</string>
+    <string name="pref_summary_third_party_app_device_settings">Povolit ostatn铆m nainstalovan媒m aplikac铆m t艡et铆ch stran m臎nit nastaven铆 p艡es intenty.</string>
+    <string name="pref_title_third_party_app_device_settings">Povolit aplikac铆m t艡et铆ch stran m臎nit nastaven铆</string>
+    <string name="share_screenshot">Sd铆let sn铆mek obrazovky</string>
+    <string name="devicetype_amazfit_trex_2">Amazfit T-Rex 2</string>
+    <string name="menuitem_last_workout">Posledn铆 cvi膷en铆</string>
+    <string name="devicetype_amazfit_band7">Amazfit Band 7</string>
+    <string name="devicetype_sony_linkbuds_s">Sony LinkBuds S</string>
+    <string name="dev_tools">V媒voj谩艡sk茅 n谩stroje</string>
+    <string name="devicetype_galaxybuds_2_pro">Galaxy Buds2 Pro</string>
+    <string name="prefs_touch_lock_buds2">Dotykov茅 ovl谩d谩n铆</string>
+    <string name="activity_type_boxing">Boxov谩n铆</string>
+    <string name="devicetype_sony_wh_1000xm2">Sony WH-1000XM2</string>
+    <string name="menuitem_membership_cards">V臎rnostn铆 karty</string>
+    <string name="status">Stav</string>
+    <string name="devicetype_amazfit_gtr3_pro">Amazfit GTR 3 Pro</string>
+    <string name="menuitem_aqi">Index kvality vzduchu</string>
+    <string name="devicetype_amazfit_gts4">Amazfit GTS 4</string>
+    <string name="devicetype_amazfit_gtr4">Amazfit GTR 4</string>
+    <string name="menuitem_forecast">P艡edpov臎膹</string>
+    <string name="menuitem_sun_moon">Slunce a M臎s铆c</string>
+    <string name="menuitem_phone">Telefon</string>
+    <string name="menuitem_theater_mode">Re啪im kina</string>
+    <string name="menuitem_brightness">Jas</string>
+    <string name="menuitem_lockscreen">Zamykac铆 obrazovka</string>
+    <string name="yesterdays_activity">V膷erej拧铆 aktivita</string>
+    <string name="pref_title_touch_spotify_official_app">Spotify (pouze ofici谩ln铆 aplikace)</string>
+    <string name="wifi_hotspot">Wi-Fi hotspot</string>
+    <string name="wifi_ssid">SSID</string>
+    <string name="menuitem_cards">Karty</string>
+    <string name="activity_type_pilates">Pilates</string>
+    <string name="sports_activity_confirm_delete_title">Odstranit %d aktivit</string>
+    <string name="fwapp_install_device_not_supported">Soubor nem暖啪e b媒t nainstalov谩n, za艡铆zen铆 nen铆 podporov谩no.</string>
+    <string name="menuitem_volume">Hlasitost</string>
+    <string name="action_changelog">Seznam zm臎n</string>
+    <string name="gps_bds">GPS + BDS</string>
+    <string name="pref_title_banglejs_phone_gps_network_only">Pou啪铆t pouze s铆t臎 pro ur膷en铆 polohy</string>
+    <string name="pref_workout_detection_summary">Automaticky rozpozn谩vat cvi膷en铆</string>
+    <string name="contact_delete_confirm_title">Odstranit kontakt</string>
+    <string name="pref_pair_bluetooth_calls_help_title">Jak p艡ij铆mat hovory p艡es Bluetooth</string>
+    <string name="pref_pair_bluetooth_calls_help_3">3. Povolte n铆啪e uveden茅 nastaven铆 vol谩n铆 p艡es Bluetooth.</string>
+    <string name="fw_upgrade_notice_amazfit_gtr3_pro">Chyst谩te se nainstalovat firmware %s na v谩拧 Amazfit GTR 3 Pro.
+\n
+\nV谩拧 n谩ramek se po instalaci souboru .zip restartuje.
+\n
+\nPOKRA膶UJTE NA VLASTN脥 NEBEZPE膶脥!</string>
+    <string name="pref_header_offline_voice">Hlasov茅 ovl谩d谩n铆 offline</string>
+    <string name="pref_pair_bluetooth_calls_help_1">1. Klepnut铆m na tla膷铆tko n铆啪e zah谩j铆te proces p谩rov谩n铆.</string>
+    <string name="pref_title_notification_cache_while_disconnected">Mezipam臎钮, kdy啪 je telefon mimo dosah</string>
+    <string name="appmanager_app_start">Za膷铆t</string>
+    <string name="fw_upgrade_notice_amazfit_trex2">Chyst谩te se nainstalovat firmware %s na v谩拧 Amazfit T-Rex 2.
+\n
+\nV谩拧 n谩ramek se po instalaci souboru .zip restartuje.
+\n
+\nPOKRA膶UJTE NA VLASTN脥 NEBEZPE膶脥!</string>
+    <string name="fw_upgrade_notice_amazfit_gts4">Chyst谩te se nainstalovat firmware %s na v谩拧 Amazfit GTS 4.
+\n
+\nV谩拧 n谩ramek se po instalaci souboru .zip restartuje.
+\n
+\nPOKRA膶UJTE NA VLASTN脥 NEBEZPE膶脥!</string>
+    <string name="fw_upgrade_notice_amazfit_gts4_mini">Chyst谩te se nainstalovat firmware %s na v谩拧 Amazfit GTS 4 Mini.
+\n
+\nV谩拧 n谩ramek se po instalaci souboru .zip restartuje.
+\n
+\nPOKRA膶UJTE NA VLASTN脥 NEBEZPE膶脥!</string>
+    <string name="pref_pair_bluetooth_calls_help_2">2. P艡ejd臎te do nastaven铆 bluetooth telefonu a sp谩rujte se s nov媒m za艡铆zen铆m, kter茅 se zobraz铆 (podobn媒 n谩zev jako va拧e aktu谩ln铆 hodinky, ale s p艡铆ponou, nap艡. \"Amazfit GTR 4 - AFC8\".</string>
+    <string name="pref_summary_notification_cache_while_disconnected">Odes铆l谩n铆 zme拧kan媒ch ozn谩men铆, kdy啪 se za艡铆zen铆 znovu p艡ipoj铆 pot茅, co bylo mimo dosah telefonu</string>
+    <string name="pref_summary_notification_media_ignores_application_list">Zpracov谩vat ozn谩men铆 multimedi谩ln铆ch aplikac铆 p艡ed b臎啪n媒m seznamem aplikac铆. Pokud nen铆 tato mo啪nost povolena, mus铆 b媒t medi谩ln铆 aplikace povoleny v seznamu aplikac铆, aby fungovalo ovl谩d谩n铆 m茅di铆 ze za艡铆zen铆.</string>
+    <string name="pref_title_notification_media_ignores_application_list">Ignorovat seznam aplikac铆 u multimedi谩ln铆ch ozn谩men铆</string>
+    <string name="debugactivity_confirm_remove_device_preferences_title">Odebrat p艡edvolby za艡铆zen铆\?</string>
+    <string name="single_band">Jednoduch茅 P谩smo</string>
+    <string name="dual_band">Du谩ln铆 P谩smo</string>
+    <string name="low_power_gps">GPS s n铆zkou spot艡ebou energie</string>
+    <string name="accuracy_first">Up艡ednostnit p艡esnost</string>
+    <string name="speed_first">Up艡ednostnit rychlost</string>
+    <string name="debugactivity_confirm_remove_device_preferences">T铆m se resetuj铆 p艡edvolby za艡铆zen铆 pro v拧echna p艡ipojen谩 za艡铆zen铆. Jste si jisti\?</string>
+    <string name="fw_upgrade_notice_amazfit_gtr3">Chyst谩te se nainstalovat firmware %s na v谩拧 Amazfit GTR 3.
+\n
+\nV谩拧 n谩ramek se po instalaci souboru .zip restartuje.
+\n
+\nPOKRA膶UJTE NA VLASTN脥 NEBEZPE膶脥!</string>
+    <string name="fw_upgrade_notice_amazfit_gtr4">Chyst谩te se nainstalovat firmware %s na v谩拧 Amazfit GTR 4.
+\n
+\nV谩拧 n谩ramek se po instalaci souboru .zip restartuje.
+\n
+\nPOKRA膶UJTE NA VLASTN脥 NEBEZPE膶脥!</string>
+    <string name="fw_upgrade_notice_amazfit_band7">Chyst谩te se nainstalovat firmware %s na v谩拧 Amazfit Band 7.
+\n
+\nV谩拧 n谩ramek se po instalaci souboru .zip restartuje.
+\n
+\nPOKRA膶UJTE NA VLASTN脥 NEBEZPE膶脥!</string>
+    <string name="pref_summary_receive_calls_watch">Prov谩d臎n铆 a p艡ij铆m谩n铆 hovor暖 p艡铆mo na hodink谩ch</string>
+    <string name="pref_pair_bluetooth_calls_title">P谩rov谩n铆 pro hovory p艡es Bluetooth</string>
+    <string name="pref_pair_bluetooth_calls_help_summary">Chcete-li p艡ij铆mat hovory p艡es Bluetooth, mus铆te telefon sp谩rovat s druhou instanc铆 hodinek.</string>
+    <string name="pref_pair_bluetooth_calls_help_warning">UPOZORN臍N脥: Pokud povol铆te vol谩n铆 Bluetooth bez sp谩rov谩n铆 s druhou instanc铆 hodinek, ozn谩men铆 o hovorech nemus铆 fungovat podle o膷ek谩v谩n铆.</string>
+    <string name="pref_summary_banglejs_phone_gps_enbale">Vyu啪铆t gps telefonu k p艡eps谩n铆 gps dat bangle hodinek</string>
+    <string name="activity_type_stretching">Protahov谩n铆</string>
+    <string name="activity_type_street_dance">Pouli膷n铆 tanec</string>
+    <string name="activity_type_pool_swimming">Baz茅nov茅 plav谩n铆</string>
+    <string name="activity_type_hiit">HIIT</string>
+    <string name="activity_type_indoor_walking">Ch暖ze vevnit艡</string>
+    <string name="activity_type_kickboxing">Kickbox</string>
+    <string name="activity_type_indoor_ice_skating">Bruslen铆 na led臎 vevnit艡</string>
+    <string name="activity_type_rowing">Veslov谩n铆</string>
+    <string name="buttons_on_right">Tla膷铆tka vpravo</string>
+    <string name="pref_header_intent_api">Intent API</string>
+    <string name="offline_voice_respond_turn_wrist">Odpov臎d臎t p艡i pooto膷en铆 z谩p臎st铆m</string>
+    <string name="pref_theme_dynamic">Dynamick茅 barvy</string>
+    <string name="pref_summary_banglejs_phone_gps_network_only">Pou啪铆vat pouze poskytovatele s铆t臎 pro ur膷en铆 polohy. To sni啪uje spot艡ebu energie za cenu p艡esnosti. Je pot艡eba znovup艡ipojen铆.</string>
+    <string name="pref_header_authentication">Ov臎艡en铆</string>
+    <string name="pref_cover_to_mute">Ztlumit zakryt铆m</string>
+    <string name="pref_morning_updates_categories_title">Kategorie rann铆ch aktualizac铆</string>
+    <string name="pref_workout_detection_alert_title">Upozorn臎n铆</string>
+    <string name="pref_workout_detection_alert_summary">Upozornit p艡i detekov谩n铆 cvi膷en铆</string>
+    <string name="busy_task_fetch_stress_data">Stahov谩n铆 dat stresu</string>
+    <string name="busy_task_fetch_pai_data">Stahov谩n铆 dat PAI</string>
+    <string name="pref_screen_notification_profile_schedule">Pl谩n</string>
+    <string name="pref_screen_notification_profile_todo_list">Seznam 煤kol暖</string>
+    <string name="pai_chart_per_week">PAI za t媒den</string>
+    <string name="preferences_miband_1_2_settings" tools:ignore="TypographyFractions">Nastaven铆 Mi Band 1/2</string>
+    <string name="preferences_miband_1_2_warning">Upozorn臎n铆: Tato nastaven铆 se vztahuj铆 pouze na Mi Band 1 a 2.</string>
+    <string name="buttons_on_left">Tla膷铆tka vlevo</string>
+    <string name="busy_task_fetch_debug_logs">Stahov谩n铆 protokolu lad臎n铆</string>
+    <string name="pref_workout_detection_enabled_summary">Zapnout automatickou detekci tohoto cvi膷en铆</string>
+    <string name="pref_workout_detection_time">Doba aktivity p艡ed detekc铆</string>
+    <string name="pref_gps_header">GPS</string>
+    <string name="pref_alert_tone">T贸n upozorn臎n铆</string>
+    <string name="pref_agps_header">AGPS</string>
+    <string name="pref_agps_expiry_reminder_enabled">P艡ipomenut铆 o vypr拧en铆 AGPS</string>
+    <string name="pref_agps_expiry_reminder_time">Doba p艡ipomenut铆 o vypr拧en铆 dat AGPS</string>
+    <string name="prefs_weardirection">Sm臎r no拧en铆</string>
+    <string name="world_clock_code">K贸d</string>
+    <string name="pref_summary_banglejs_phone_gps_update_interval">Interval, jak 膷asto je gps pozice aktualizov谩na, v ms</string>
+    <string name="pref_vibrate_for_alert">Vibrovat pro upozorn臎n铆</string>
+    <string name="offline_voice_response_during_screen_lighting">Odpov臎d臎t p艡i osv臎tlen茅 obrazovce</string>
+    <string name="pref_agps_update_time">膶as aktualizace AGPS</string>
+    <string name="pref_workout_detection_enabled">Detekce aktivn铆</string>
+    <string name="vibration_profile_default">V媒choz铆</string>
+    <string name="pref_canned_message">Zpr谩va</string>
+    <string name="pref_text_to_speech">Text na 艡e膷</string>
+    <string name="pref_agps_expire_time">膶as vypr拧en铆 dat AGPS</string>
+    <string name="pref_morning_updates_title">Rann铆 aktualizace</string>
+    <string name="pref_morning_updates_summary">Zobrazit aktualizace ka啪d茅 r谩no</string>
+    <string name="pref_morning_updates_categories_summary">Seznam kategori铆 k zobrazen铆 ka啪d茅 r谩no</string>
+    <string name="pref_workout_detection_ask_first">Nejprve se zeptat</string>
+    <string name="pref_workout_detection_categories_summary">Detekovat kategorie aktivit automaticky</string>
+    <string name="pref_workout_detection_ask_first_summary">P艡i detekci tohoto cvi膷en铆 vy啪adovat potvrzen铆 na hodink谩ch</string>
+    <string name="busy_task_fetch_sleep_respiratory_rate_data">Stahov谩n铆 dat dechov茅 frekvence sp谩nku</string>
+    <string name="pref_workout_detection_time_summary">Kolik minut cvi膷en铆 je pot艡eba pro automatickou detekci</string>
+    <string name="pref_sleep_mode_title">Re啪im sp谩nku</string>
+    <string name="pref_sleep_mode_sleep_screen_title">Sp谩nkov谩 Obrazovka</string>
+    <string name="contact_delete_confirm_description">Opravdu chcete odstranit \'%1$s\'\?</string>
+    <string name="contact_no_free_slots_description">Za艡铆zen铆 nem谩 啪谩dn茅 voln茅 m铆sto pro kontakty (celkem m铆st: %1$s)</string>
+    <string name="pref_sleep_mode_smart_enable_title">Chytr茅 Zapnut铆</string>
+    <string name="pref_sleep_mode_smart_enable_summary">Povolit re啪im sp谩nku automaticky b臎hem sp谩nku</string>
+    <string name="busy_task_fetch_sports_summaries">Stahov谩n铆 data sportovn铆ch aktivit</string>
+    <string name="busy_task_fetch_sports_details">Stahov谩n铆 detail暖 sportovn铆ch aktivit</string>
+    <string name="busy_task_fetch_spo2_data">Stahov谩n铆 dat SpO2</string>
+    <string name="busy_task_fetch_hr_data">Stahov谩n铆 dat srde膷n铆ho tepu</string>
+    <string name="pref_gps_combination">GPS kombinace</string>
+    <string name="offline_voice_respond_screen_on">Odpov臎d臎t p艡i aktivn铆 obrazovce</string>
+    <string name="pref_sleep_mode_sleep_screen_summary">Zobrazit Sp谩nkovou Obrazovku p艡i probuzen铆 obrazovky b臎hem re啪imu sp谩nku, ke sn铆啪en铆 rozptyl暖</string>
+    <string name="pref_title_mb_intents">P艡铆mo odes铆lat intenty tla膷铆tek m茅di铆</string>
+    <string name="pref_summary_mb_intents">Zapn臎te, pokud ovl谩d谩n铆 m茅di铆 nefunguje pro ur膷it茅 aplikace</string>
+    <string name="title_activity_set_contacts">Konfigurace kontakt暖</string>
+    <string name="pref_header_navigation">Navigace</string>
+    <string name="pai_plus_num">+%d</string>
+    <string name="num_min">%d min</string>
+    <string name="pai_chart_per_month">PAI za m臎s铆c</string>
+    <string name="pref_huami_truncate_fetch_operation_timestamps_title">O艡铆znout 膷asy p艡i stahov谩n铆</string>
+    <string name="gpx_route_upload_complete">Nahr谩v谩n铆 GPX traxy hotovo</string>
+    <string name="pref_huami_truncate_fetch_operation_timestamps_summary">O艡铆zne 膷asy sta啪en媒ch data na cel茅 minuty. Vypn臎te, chcete-li zachovat 膷as ve vte艡in谩ch, p艡i pot铆啪铆ch p艡i stahov谩n铆 velmi kr谩tk媒ch aktivit.</string>
+    <string name="gpx_route_upload_failed">Chyba p艡i nahr谩v谩n铆 GPX trasy</string>
+    <string name="gpx_route_upload_in_progress">Nahr谩v谩n铆 GPX trasy</string>
+    <string name="pref_app_logs_stop_summary">Vypnout zaznamen谩v谩n铆 lad铆c铆ch informac铆 aplikaci</string>
+    <string name="prefs_control_center">Ovl谩dac铆 Centrum</string>
+    <string name="dismiss">Zam铆tnout</string>
+    <string name="contact_missing_number">Kontaktn铆 膷铆slo nen铆 vypln臎no</string>
+    <string name="prefs_always_on_display_style">Styl</string>
+    <string name="prefs_shortcut_cards_summary">Karty zobrazen茅 p艡i tahu (swipe) vpravo p艡es cifern铆k. Pokud b臎啪铆 aplikace, auto-generovan茅 karty nejsou t铆mto nastaven铆m ovlivn臎ny.</string>
+    <string name="activity_prefs_goals">C铆le</string>
+    <string name="activity_type_surfing">Surfov谩n铆</string>
+    <string name="activity_type_windsurfing">Windsurfing</string>
+    <string name="activity_type_kitesurfing">Kitesurfing</string>
+    <string name="activity_type_icehockey">Ledn铆 hokej</string>
+    <string name="activity_type_iceskating">Bruslen铆</string>
+    <string name="error_no_bluetooth_scan">Mus铆 b媒t povoleno Bluetooth skenov谩n铆 pro spr谩vnou funkci</string>
+    <string name="error_no_bluetooth_connect">Mus铆 b媒t povoleno Bluetooth p艡ipojen铆 pro spr谩vnou funkci</string>
+    <string name="permission_request">Aplikace %1$s umo啪艌uje pos铆l谩n铆 zpr谩v a dal拧铆ch dat ze syst茅mu Android do va拧eho za艡铆zen铆. K tomu pot艡ebuje opr谩vn臎n铆 k p艡铆stupu k t臎mto dat暖m, bez kter茅ho nemus铆 spr谩vn臎 fungovat.
+\n
+\nNyn铆 se v谩m zobraz铆 n臎kolik dialogov媒ch oken syst茅mu Android, kter谩 si tato opr谩vn臎n铆 vy啪谩daj铆.
+\n
+\nPro pokra膷ov谩n铆 klepn臎te na 鈥�%2$s鈥�.</string>
+    <string name="hrZoneAerobic">Aerobn铆</string>
+    <string name="hrZoneNa">Nen铆 k dispozici</string>
+    <string name="sony_speak_to_chat_sensitivity_standard">Standardn铆</string>
+    <string name="withings_bt_calibration_previous">P艡edchoz铆</string>
+    <string name="activity_type_core_training">Cvi膷en铆 b艡icha a core</string>
+    <string name="zepp_os_watchface_red_fantasy">Red Fantasy</string>
+    <string name="pref_title_upper_button_long_press_action">Akce dlouh茅ho dr啪en铆 Horn铆ho Tla膷铆tka</string>
+    <string name="elevationLoss">Ztr谩ta nadmo艡sk茅 v媒拧ky</string>
+    <string name="HeartRateZones">Z贸ny srde膷n铆ho tepu</string>
+    <string name="activity_detail_share_gps_label">Sd铆let trasu GPS</string>
+    <string name="intent_api_allow_trigger_export_title">Povolit export datab谩ze</string>
+    <string name="address">Adresa</string>
+    <string name="fossil_hr_confirm_connection">Pros铆me o potvrzen铆 na hodink谩ch</string>
+    <string name="debug_companion_show_associated">Zobrazit p艡idru啪en谩 doprovodn谩 za艡铆zen铆</string>
+    <string name="contact_missing_name">Jm茅no nen铆 vypln臎no</string>
+    <string name="voice_service_package_title">Bal铆膷ek hlasov茅 slu啪by</string>
+    <string name="title">N谩zev</string>
+    <string name="description">Popis</string>
+    <string name="preview_image">N谩hled</string>
+    <string name="activity_type_baseball">Baseball</string>
+    <string name="activity_type_handball">H谩zen谩</string>
+    <string name="activity_type_tennis">Tenis</string>
+    <string name="activity_type_dancing">Tanec</string>
+    <string name="activity_type_skiing">Ly啪ov谩n铆</string>
+    <string name="activity_type_snowboarding">Snowboarding</string>
+    <string name="activity_type_riding">J铆zda na koni</string>
+    <string name="activity_type_hockey">Hokej</string>
+    <string name="voice_service_package_summary">Aplikace obsluhuj铆c铆 hlasov茅 p艡铆kazy</string>
+    <string name="loyalty_cards_sync">Synchronizace</string>
+    <string name="loyalty_cards_catima_permissions_title">Sch谩z铆 opr谩vn臎n铆</string>
+    <string name="withings_calibration_text_hours">Pou啪ijte cifern铆k n铆啪e pro zarovn谩n铆 hodinov茅 ru膷i膷ky na 12.</string>
+    <string name="withings_calibration_text_minutes">Nyn铆 zarovnejte minutovou ru膷i膷ku na 12.</string>
+    <string name="withings_calibration_text_activity_target">Zarovnejte ru膷i膷ku aktivity na 100 %. Tato ru膷i膷ka se pohybuje pouze po sm臎ru hod. ru膷i膷ek.</string>
+    <string name="withings_bt_calibration_next">N谩sleduj铆c铆</string>
+    <string name="drag_handle">posunout</string>
+    <string name="find_my_phone_found_it">NALEZENO</string>
+    <string name="aerobicTrainingEffect">Aerobick媒 煤膷inek</string>
+    <string name="TrainingEffect">Tr茅ninkov媒 efekt</string>
+    <string name="ftp_server_start_summary">Spustit FTP server na hodink谩ch</string>
+    <string name="fossil_hr_pairing_failed">P谩rov谩n铆 selhalo</string>
+    <string name="pref_activity_full_sync_trigger_warning">Vyvol谩 plnou synchronizaci v拧ech dat aktivit ze za艡铆zen铆. M暖啪e trvat n臎kolik minut.</string>
+    <string name="elevationGain">P艡ev媒拧en铆</string>
+    <string name="devicetype_vivomove_hr">Garmin Vivomove HR</string>
+    <string name="menuitem_todo">脷koly</string>
+    <string name="zepp_os_watchface_minimalist">Minimalista</string>
+    <string name="zepp_os_watchface_simplicity_data">Simplicity Data</string>
+    <string name="sports_activity_confirm_delete_description">Opravdu chcete odstranit %d aktivit?</string>
+    <string name="watchface_widget_type_uv_index">UV Index</string>
+    <string name="pref_switch_controls_anc_ambient_off">Potla膷en铆 Hluku 鈫愨啋 Zvuky Okol铆 鈫愨啋 Vypnuto</string>
+    <string name="sony_quick_access_double_tap">Rychl媒 P艡铆stup (dvojit茅 钮uknut铆)</string>
+    <string name="continue_pressing">Dlouh媒 Stisk</string>
+    <string name="quick_attention">Rychl谩 pozornost</string>
+    <string name="changelog_show_full">V铆ce鈥�</string>
+    <string name="pref_show_changelog">Zobrazit seznam zm臎n p艡i spu拧t臎n铆 aplikace</string>
+    <string name="pref_show_changelog_summary">Zobraz铆 seznam zm臎n od posledn铆 aktualizace Gadgetbridge</string>
+    <string name="activity_detail_share_raw_summary">Sd铆let nezpracovan媒 souhrn dat</string>
+    <string name="activity_detail_share_raw_details">Sd铆let nezpracovan茅 podrobnosti</string>
+    <string name="stress_relaxed">Uvoln臎n媒</string>
+    <string name="stress_mild">M铆rn媒</string>
+    <string name="ftp_server_root_dir">Ko艡enov媒 adres谩艡</string>
+    <string name="username">U啪ivatelsk茅 jm茅no</string>
+    <string name="fossil_hr_connection_not_confirmed">P艡ipojen铆 na hodink谩ch nepotvrzeno, vyu啪it neov臎艡en媒 m贸d</string>
+    <string name="contact_name">Jm茅no</string>
+    <string name="voice_service_class_title">T艡铆da hlasov茅 slu啪by</string>
+    <string name="voice_service_class_summary">Pln谩 servisn铆 cesta k obslou啪en铆 hlasov媒ch p艡铆kaz暖</string>
+    <string name="voice_service">Hlasov谩 slu啪ba</string>
+    <string name="loyalty_cards">V臎rnostn铆 Karty</string>
+    <string name="pref_activity_full_sync_trigger_summary">Vyvolat plnou synchronizaci dat aktivit</string>
+    <string name="pref_activity_full_sync_trigger_title">Pln谩 synchronizace</string>
+    <string name="loyalty_cards_catima_package">N谩zev bal铆膷ku aplikace Catima</string>
+    <string name="loyalty_cards_install_catima_fail">P艡i instalaci Catima do拧lo k chyb臎</string>
+    <string name="loyalty_cards_sync_starred">Synchronizovat pouze obl铆ben铆 karty</string>
+    <string name="loyalty_cards_sync_title">Synchronizace V臎rnostn铆ch Karet</string>
+    <string name="loyalty_cards_open_catima">Otev艡铆t Catima</string>
+    <string name="loyalty_cards_catima">Catima</string>
+    <string name="loyalty_cards_syncing">Synchronizace %d v臎rnostn铆ch karet do hodinek</string>
+    <string name="prefs_always_on_display_follow_watchface">Styl podle cifern铆ku</string>
+    <string name="menuitem_screen_always_lit">Obrazovka V啪dy Rozsv铆cena</string>
+    <string name="menuitem_eject_water">Odstranit vodu</string>
+    <string name="stress_moderate">St艡edn铆</string>
+    <string name="loyalty_cards_sync_summary">Klepn臎te pro synchronizaci karet do hodinek</string>
+    <string name="pref_title_lower_button_short_press_action">Akce stisku Doln铆ho Tla膷铆tka</string>
+    <string name="wifi_hotspot_stop_summary">Zastavit Wi-Fi hotspot na hodink谩ch</string>
+    <string name="contact_phone_number">Telefonn铆 膷铆slo</string>
+    <string name="pref_enable_unsupported_settings_summary">To umo啪n铆 p艡铆stup ke v拧em dostupn媒m nastaven铆m, i kdy啪 tato nejsou za艡铆zen铆m podporov谩na. To m暖啪e zp暖sobit nestabilitu a restary za艡铆zen铆.</string>
+    <string name="devicetype_casiogwb5600">Casio GW-B5600</string>
+    <string name="pai_day">Denn铆 p艡铆r暖stek</string>
+    <string name="loyalty_cards_install">Instalace Catima</string>
+    <string name="croatian">Chorvat拧tina</string>
+    <string name="pref_title_touch_spotify_galaxy_app">Spotify (pouze s Galaxy Wearable aplikac铆)</string>
+    <string name="supercars_lights_blinking_label">Blik谩n铆</string>
+    <string name="zepp_os_watchface_superposition">superposition</string>
+    <string name="intent_api_allow_debug_commands_title">Povolit p艡铆kazy lad臎n铆</string>
+    <string name="intent_api_allow_debug_commands_summary">Povolit vyvol谩n铆 p艡铆kaz暖 menu Lad臎n铆 prost艡ednictv铆m Intent API</string>
+    <string name="ftp_server_stop_summary">Zastavit FTP server na hodink谩ch</string>
+    <string name="ftp_server_status">Status FTP serveru</string>
+    <string name="ftp_server_configuration">Nastaven铆 FTP serveru</string>
+    <string name="pref_app_logs_start_summary">Zapnout zaznamen谩v谩n铆 lad铆c铆ch informac铆 aplikac铆</string>
+    <string name="changelog_title">Co je nov茅ho</string>
+    <string name="loyalty_cards_sync_groups">Skupiny k synchronizaci</string>
+    <string name="loyalty_cards_catima_not_compatible">Instalovan谩 verze Catima nen铆 kompatibiln铆 s Gadgetbridge. Aktualizujte Catima a Gadgetbridge na posledn铆 verzi.</string>
+    <string name="pref_theme_dynamic_colors_not_available_warning">Dynamick茅 barvy nejsou ve va拧em za艡铆zen铆 k dispozici , tato funkce je dostupn谩 pouze od Androidu 12+. Gadgetbridge pou啪ije v媒choz铆 barvy Material 3.</string>
+    <string name="pref_theme_dynamic_colors_explanation">Pozn谩mka: pro vyu啪it铆 barev dynamick茅ho t茅matu je nutno povolit barvy tapety nebo barevn茅 palety v nastaven铆 stylu Androidu 12+. V opa膷n茅m p艡铆pad臎 pou啪ije Gadgetbridge v媒choz铆 barvy Material 3.</string>
+    <string name="georgian">Gruz铆j拧tina</string>
+    <string name="menuitem_one_tap_measuring">M臎艡en铆 jednotap暖</string>
+    <string name="activity_type_weightlifting">Vzp铆r谩n铆</string>
+    <string name="stress_high">Vysok媒</string>
+    <string name="pai_total">Celkov媒</string>
+    <string name="sony_speak_to_chat_summary">Automaticky vypnut potla膷en铆 hluku p艡i promluven铆.</string>
+    <string name="screenshot_taken">Screenshot po艡铆zen</string>
+    <string name="menuitem_total_workout">Celkov媒 tr茅nink</string>
+    <string name="menuitem_voice_memos">Hlasov茅 pozn谩mky</string>
+    <string name="sony_button_mode_help_title">M贸dy Tla膷铆tek - N谩pov臎da</string>
+    <string name="sony_ambient_sound_control_button_mode_nc_as">Potla膷en铆 Hluku, Zvuky Okol铆</string>
+    <string name="sony_ambient_sound_control_button_mode_nc_off">Potla膷en铆 Hluku, Vypnuto</string>
+    <string name="intent_api_allow_activity_sync_title">Povolit vyvol谩n铆 synchronizace aktivit</string>
+    <string name="pref_title_lock_unlock">Z谩mek</string>
+    <string name="wifi_hotspot_configuration">Nastaven铆 Wi-Fi hotspotu</string>
+    <string name="wifi_hotspot_status">Status Wi-Fi hotspotu</string>
+    <string name="fossil_hr_pairing_successful">P谩rov谩n铆 煤sp臎拧n茅</string>
+    <string name="devicetype_super_cars">Shell Racing</string>
+    <string name="menuitem_unsupported">[NEPODPOROV脕NO] %s</string>
+    <string name="currentWorkoutLoad">Cvi膷铆c铆 z谩t臎啪</string>
+    <string name="fw_upgrade_notice_amazfitbip3pro">Chyst谩te se nainstalovat firmware %s na v谩拧 Amazfit Bip 3 Pro.
+\n
+\nNainstalujte nejprve soubor .fw a pot茅 soubor .res. Va拧e hodinky se po instalaci souboru .fw restartuj铆.
+\n
+\nPozn谩mka: soubor .res nemus铆te instalovat, pokud je toto啪n媒 s ji啪 d艡铆ve nainstalovan媒m.
+\n
+\nPOKRA膶UJTE NA VLASTN脥 NEBEZPE膶脥!</string>
+    <string name="zepp_os_watchface_rotating_earth">Rotating Earth</string>
+    <string name="intent_api_allow_activity_sync_summary">Povolit vyvol谩n铆 synchronizace aktivit prost艡ednictv铆m Intent API</string>
+    <string name="intent_api_allow_trigger_export_summary">Povolit vyvol谩n铆 exportu datab谩ze prost艡ednictv铆m Intent API</string>
+    <string name="intent_api_broadcast_export_title">Vys铆lat broadcast zpr谩vu p艡i exportu datab谩ze</string>
+    <string name="intent_api_broadcast_export_summary">Vyslat broadcastov媒 intent po dokon膷en铆 exportu datab谩ze</string>
+    <string name="supercars_turbo_speed_label">Turbo Rychlost</string>
+    <string name="supercars_lights_label">Reflektory</string>
+    <string name="wifi_hotspot_summary">Nastaven铆 Wi-Fi hotspotu na hodink谩ch</string>
+    <string name="wifi_hotspot_start_summary">Spustit Wi-Fi hotspot na hodink谩ch</string>
+    <string name="loyalty_cards_sync_groups_only">Synchronizovat pouze specifick茅 skupiny</string>
+    <string name="loyalty_cards_sync_archived">Synchronizovat archivovan茅 karty</string>
+    <string name="pref_chart_sleep_lines_limit">Kolik detekovan媒ch skupin sp谩nku zobrazit p艡ed skrolov谩n铆m</string>
+    <string name="hrZoneWarmUp">Zah艡铆v谩n铆</string>
+    <string name="hrZoneFatBurn">P谩len铆 tuku</string>
+    <string name="hrZoneAnaerobic">Anaerobn铆</string>
+    <string name="hrZoneExtreme">Extr茅mn铆</string>
+    <string name="anaerobicTrainingEffect">Anaerobick媒 煤膷inek</string>
+    <string name="maximumOxygenUptake">Maxim谩ln铆 p艡铆jem kysl铆ku</string>
+    <string name="status_icon">Ikona stavu</string>
+    <string name="fw_upgrade_notice_amazfit_cheetah_pro">Chyst谩te se nainstalovat firmware %s na v谩拧 Amazfit Cheetah Pro.
+\n
+\nV谩拧 n谩ramek se po instalaci souboru .zip restartuje.
+\n
+\nPOKRA膶UJTE NA VLASTN脥 NEBEZPE膶脥!</string>
+    <string name="kind_gpx_route">GPX Trasa</string>
+    <string name="zepp_os_watchface_multiple_data">V铆cen谩sobn谩 data</string>
+    <string name="zepp_os_watchface_rush">Rush</string>
+    <string name="zepp_os_watchface_vibrant">Vibrant</string>
+    <string name="ftp_server_summary">Nastaven铆 FTP serveru na hodink谩ch</string>
+    <string name="changelog_full_title">Seznam zm臎n</string>
+    <string name="fw_upgrade_notice_zepp_os">Chyst谩te se nainstalovat firmware %s na v谩拧 %s.
+\n
+\nVa拧e hodinky se po instalaci souboru .zip restartuje.
+\n
+\nPOKRA膶UJTE NA VLASTN脥 NEBEZPE膶脥!</string>
+    <string name="stop">Stop</string>
+    <string name="prefs_shortcut_cards">P艡铆stupn茅 Karty</string>
+    <string name="prefs_control_center_summary">Vyberte polo啪ky v rozbalovac铆m menu ovl谩dac铆ho centra</string>
+    <string name="activity_type_football">Fotbal</string>
+    <string name="activity_type_rugby">Ragby</string>
+    <string name="activity_type_squash">Squash</string>
+    <string name="activity_type_indoor_fitness">Posilovna</string>
+    <string name="activity_type_stepper">Chod铆c铆 trena啪茅r</string>
+    <string name="activity_type_other">Ostatn铆</string>
+    <string name="devicetype_amazfit_trex_ultra">Amazfit T-Rex Ultra</string>
+    <string name="activity_type_golf">Golf</string>
+    <string name="devicetype_amazfit_cheetah_square">Amazfit Cheetah (hranat媒)</string>
+    <string name="devicetype_amazfit_cheetah_round">Amazfit Cheetah (kulat媒)</string>
+    <string name="devicetype_amazfit_cheetah_pro">Amazfit Cheetah Pro</string>
+    <string name="devicetype_amazfit_bip3_pro">Amazfit Bip 3 Pro</string>
+    <string name="devicetype_amazfit_bip5">Amazfit Bip 5</string>
+    <string name="devicetype_amazfit_gtr_mini">Amazfit GTR Mini</string>
+    <string name="devicetype_amazfit_falcon">Amazfit Falcon</string>
+    <string name="devicetype_casiogmwb5000">Casio GMW-B5000</string>
+    <string name="devicetype_bohemic_smart_bracelet">Bohemic Smart Bracelet</string>
+    <string name="menuitem_zepp_coach">Zepp Coach</string>
+    <string name="zepp_os_watchface_business_style">Business Style</string>
+    <string name="device_experimental">EXPERIMENT脕LN脥</string>
+    <string name="zepp_os_watchface_emerald_moonlight">Emerald Moonlight</string>
+    <string name="menuitem_offline_voice">Hlasov茅 ovl谩d谩n铆 offline</string>
+    <string name="watchface_setting_button_move_hands">Nastavit ru膷i膷ky</string>
+    <string name="sony_ambient_sound_control_button_mode_nc_as_off">Potla膷en铆 Hluku, Zvuky Okol铆, Vypnuto</string>
+    <string name="sony_ambient_sound_control_button_mode_as_off">Zvuky Okol铆, Vypnuto</string>
+    <string name="pref_summary_navigation_forward">P艡ed谩vat instrukce z naviga膷n铆ch aplikac铆 do hodinek</string>
+    <string name="sony_quick_access_triple_tap">Rychl媒 P艡铆stup (trojt茅 钮uknut铆)</string>
+    <string name="pref_title_navigation_forward">Odes铆lat navigaci do hodinek</string>
+    <string name="withings_steel_hr">Withings Steel HR</string>
+    <string name="sony_button_mode_help_summary">Popis m贸d暖 tla膷铆tek</string>
+    <string name="sony_ambient_sound_control_button_modes">Re啪imy ovl谩d谩n铆 okoln铆ho zvuku</string>
+    <string name="fossil_hr_confirmation_skipped">P艡esko膷it potvrzen铆 na hodink谩ch</string>
+    <string name="fossil_hr_confirmation_timeout">膶as vypr拧en铆 potvrzen铆, pokra膷ov谩n铆</string>
+    <string name="debug_companion_pair_current">Sp谩rovat aktu谩ln铆 za艡铆zen铆 jako doprovodn茅</string>
+    <string name="pref_app_logs_title">Lad铆c铆 z谩znamy</string>
+    <string name="pref_app_logs_summary">Povolit z谩znamy lad臎n铆 aplikac铆 hodinek</string>
+    <string name="changelog_ok_button">OK</string>
+    <string name="loyalty_cards_catima_not_installed">Program Catima je vy啪adov谩n pro spr谩vu v臎rnostn铆ch karet</string>
+    <string name="loyalty_cards_sync_options">Mo啪nosti synchronizace</string>
+    <string name="loyalty_cards_catima_permissions_summary">Pro synchronizaci karet pot艡ebuje Gadgetbridge opr谩vn臎n铆 pro 膷ten铆 karet z Catima. Klikn臎te pro povolen铆 p艡铆stupu.</string>
+    <string name="updatefirmwareoperation_updateproblem_free_space">V za艡铆zen铆 nen铆 k dispozici dostatek pam臎ti</string>
+    <string name="updatefirmwareoperation_failed_low_mtu">Aktu谩ln铆 MTU %1$d je p艡铆li拧 n铆zk茅, povolte, pros铆m, v臎t拧铆 MTU v nastaven铆 za艡铆zen铆. N谩sledn臎 se odpojte a znovu p艡ipojte.</string>
+    <string name="english_au">Angli膷tina (Austr谩lie)</string>
+    <string name="english_ca">Angli膷tina (Kanada)</string>
+    <string name="pref_title_notifications_ignore_work_profile">Ignorovat ozn谩men铆 z aplikac铆 v pracovn铆m profilu</string>
+    <string name="pref_message_privacy_mode_bodyonly">Skr媒t pouze obsah zpr谩vy</string>
+    <string name="pref_title_preview_message_in_title">Uk谩zat n谩hled zpr谩vy v n谩zvu</string>
+    <string name="pref_summary_notifications_ignore_work_profile">Nepos铆lat ozn谩men铆 do hodinek z aplikac铆 v pracovn铆m profilu</string>
+    <string name="pref_title_casio_alert_call">Upozorn臎n铆 na p艡铆choz铆 hovory</string>
+    <string name="pref_summary_casio_alert_call">Upozornit (vibrovat/p铆pnout) na p艡铆choz铆 hovory</string>
+    <string name="pref_summary_casio_alert_email">Upozornit (vibrovat/p铆pnout) na ozn谩men铆 e-mail暖</string>
+    <string name="pref_summary_casio_alert_sms">Upozornit (vibrovat/p铆pnout) na ozn谩men铆 SMS</string>
+    <string name="pref_summary_casio_alert_other">Upozornit (vibrovat/p铆pnout) na ozn谩men铆 ostatn铆ch kategori铆</string>
+    <string name="pref_title_casio_alert_email">Upozorn臎n铆 na ozn谩men铆 e-mail暖</string>
+    <string name="pref_title_casio_alert_sms">Upozorn臎n铆 na ozn谩men铆 SMS</string>
+    <string name="pref_title_casio_alert_other">Upozorn臎n铆 na ostatn铆 ozn谩men铆</string>
+    <string name="info_menu_structure_removed">Struktura menu odstran臎na</string>
+    <string name="pref_title_casio_alert_calendar">Upozorn臎n铆 pro ozn谩men铆 kalend谩艡e</string>
+    <string name="pref_summary_casio_alert_calendar">Upozornit (vibrovat/p铆pnout) na ozn谩men铆 kalend谩艡e</string>
+    <string name="pref_title_send_app_notifications">Pos铆lat ozn谩men铆</string>
+    <string name="pref_summary_send_app_notifications">Pos铆lat ozn谩men铆 aplikac铆 do za艡铆zen铆</string>
+    <string name="spanish_mx">艩pan臎l拧tina (Mexiko)</string>
+    <string name="spanish_us">艩pan臎l拧tina (Spojen茅 st谩ty)</string>
+    <string name="french_fr">Francouz拧tina (Francie)</string>
+    <string name="french_ca">Francouz拧tina (Kanada)</string>
+    <string name="info_menu_structure_contents">Struktura menu: %s</string>
+    <string name="femometer_measurement_mode_quick">Rychl媒 re啪im (30s)</string>
+    <string name="femometer_measurement_mode_normal">Norm谩ln铆 re啪im (60 s-90 s)</string>
+    <string name="femometer_measurement_mode_precise">Precizn铆 re啪im (3 min)</string>
+    <string name="femometer_measurement_mode_title">Re啪im m臎艡en铆</string>
+    <string name="devicetype_femometer_vinca2">Femometer Vinca II</string>
+    <string name="zepp_os_watchface_free_combination">Free combination</string>
+    <string name="pref_device_action_dnd_priority">Neru拧it - pouze prioritn铆</string>
+    <string name="devicetype_redmi_smart_band_pro">Redmi Smart Band Pro</string>
+    <string name="devicetype_xiaomi_watch_lite">Xiaomi Watch Lite</string>
+    <string name="menuitem_thermometer">Teplom臎r</string>
+    <string name="pref_device_action_dnd_on">Neru拧it - zapnuto</string>
+    <string name="pref_enable_call_reject">Povolit odm铆t谩n铆 hovor暖</string>
+    <string name="pref_enable_call_reject_summary">Povolit odm铆t谩n铆 hovor暖 ze za艡铆zen铆</string>
+    <string name="pref_disable_find_phone_with_dnd">Zak谩zat naj铆t m暖j telefon p艡i re啪imu neru拧it</string>
+    <string name="pref_heartrate_automatic_enable">Povolit automatick茅 m臎艡en铆 srde膷n铆ho tepu</string>
+    <string name="pref_spo_automatic_enable">Povolit automatick茅 m臎艡en铆 SpO2</string>
+    <string name="pref_force_options">Vynucen茅 volby</string>
+    <string name="info_menu_structure_set">JSON struktura menu nastavena v GB</string>
+    <string name="updatefirmwareoperation_updateproblem_low_battery">脷rove艌 baterie za艡铆zen铆 je p艡铆li拧 n铆zk谩</string>
+    <string name="temperature">Teplota</string>
+    <string name="prefs_phone_silent_mode">Tich媒 re啪im telefonu</string>
+    <string name="silent_mode_normal_vibrate">Norm谩ln铆/vibrovat</string>
+    <string name="silent_mode_vibrate_silent">Vibrovat/tich媒</string>
+    <string name="activity_type_ballet">Balet</string>
+    <string name="activity_type_other_dance">Jin媒 tanec</string>
+    <string name="activity_type_ice_hockey">Ledn铆 hokej</string>
+    <string name="activity_type_trampoline">Trampol铆na</string>
+    <string name="activity_type_snow_sports">Zimn铆 sporty</string>
+    <string name="activity_type_hunting">Lov</string>
+    <string name="activity_type_outdoor_walking">Venkovn铆 turistika</string>
+    <string name="devicetype_miband8active">Xiaomi Smart Band 8 Active</string>
+    <string name="devicetype_colacao21">ColaCao 2021</string>
+    <string name="devicetype_honor_band4">Honor Band 4</string>
+    <string name="devicetype_huawei_watchgt2">Huawei Watch GT 2 (Pro)</string>
+    <string name="devicetype_redmiwatch3active">Redmi Watch 3 Active</string>
+    <string name="zepp_os_watchface_pure_white">Pure white</string>
+    <string name="devicetype_nothing_cmf_watch_pro">CMF Watch Pro</string>
+    <string name="prefs_active_noise_cancelling_light">Lehk茅 aktivn铆 potla膷en铆 hluku</string>
+    <string name="prefs_active_noise_cancelling_transparency">Pr暖hledn茅</string>
+    <string name="devicetype_sony_wena3">Sony Wena 3</string>
+    <string name="devicetype_sony_wf_1000xm5">Sony WF-1000XM5</string>
+    <string name="dnd_all_day">Celodenn铆</string>
+    <string name="devicetype_honor_band6">Honor Band 6</string>
+    <string name="devicetype_huawei_watchgt2e">Huawei Watch GT 2e</string>
+    <string name="pref_device_action_dnd_off">Neru拧it - vypnuto</string>
+    <string name="menuitem_headphone">Sluch谩tko</string>
+    <string name="devicetype_huawei_band6">Huawei Band 6</string>
+    <string name="huawei_trusleep_title">HUAWEI TruSleep 鈩�</string>
+    <string name="pref_enable_call_accept">Povolit p艡ij铆m谩n铆 hovor暖</string>
+    <string name="busy_task_fetch_temperature">Z铆sk谩v谩n铆 teplotn铆ch dat</string>
+    <string name="devicetype_sony_wi_sp600n">Sony WI-SP600N</string>
+    <string name="protocol_version">Verze protokolu</string>
+    <string name="huawei_trusleep_summary_light">Zlep拧en茅 sledov谩n铆 sp谩nku</string>
+    <string name="common_symbols">B臎啪n茅 znaky</string>
+    <string name="latvian">Loty拧tina</string>
+    <string name="busy_task_fetch_statistics">Z铆sk谩v谩n铆 statistik</string>
+    <string name="devicetype_miband7pro">Xiaomi Smart Band 7 Pro</string>
+    <string name="devicetype_miband8">Xiaomi Smart Band 8</string>
+    <string name="devicetype_huawei_talk_band_b6">Huawei Talk Band B6</string>
+    <string name="devicetype_huawei_watchgt3">Huawei Watch GT 3 (Pro)</string>
+    <string name="devicetype_redmiwatch3">Redmi Watch 3</string>
+    <string name="devicetype_redmi_smart_band_2">Redmi Smart Band 2</string>
+    <string name="devicetype_redmi_watch_2">Redmi Watch 2</string>
+    <string name="alarm_smart_wakeup_interval">Interval inteligentn铆ho probuzen铆:</string>
+    <string name="alarm_smart_wakeup_interval_default">5 minut</string>
+    <string name="prefs_workmode">Pracovn铆 re啪im</string>
+    <string name="devicetype_honor_magicwatch2">Honor MagicWatch 2</string>
+    <string name="devicetype_mijia_mho_c303">Mijia MHO-C303</string>
+    <string name="devicetype_miband8pro">Xiaomi Smart Band 8 Pro</string>
+    <string name="devicetype_amazfit_balance">Amazfit Balance</string>
+    <string name="charts_legend_spo2_average">Pr暖m臎rn茅 okysli膷en铆 krve</string>
+    <string name="text_receiver_activity_title">Poslat text do za艡铆zen铆</string>
+    <string name="pref_title_goal_secondary">Druhotn媒 c铆l</string>
+    <string name="devicetype_huawei_watchfit">Huawei Watch Fit</string>
+    <string name="silent_mode_normal_silent">Norm谩ln铆/tich媒</string>
+    <string name="prefs_device_name">N谩zev za艡铆zen铆</string>
+    <string name="manual">Ru膷n铆</string>
+    <string name="english_gb">Angli膷tina (Spojen茅 kr谩lovstv铆)</string>
+    <string name="english_in">Angli膷tina (Indie)</string>
+    <string name="english_us">Angli膷tina (Spojen茅 st谩ty)</string>
+    <string name="spanish_es">艩pan臎l拧tina (艩pan臎lsko)</string>
+    <string name="danish">D谩n拧tina</string>
+    <string name="active_time">Aktivn铆 膷as</string>
+    <string name="standing_time">膶as st谩n铆</string>
+    <string name="activity_type_indoor_running">B臎h vevnit艡</string>
+    <string name="activity_type_taekwondo">Taekwondo</string>
+    <string name="activity_type_karate">Karate</string>
+    <string name="activity_type_martial_arts">Bojov谩 um臎n铆</string>
+    <string name="activity_type_darts">艩ipky</string>
+    <string name="activity_type_archery">Lukost艡elba</string>
+    <string name="activity_type_horse_riding">J铆zda na koni</string>
+    <string name="activity_type_stairs">Schody</string>
+    <string name="activity_type_fishing">Ryba艡en铆</string>
+    <string name="activity_type_rock_climbing">Horolezectv铆</string>
+    <string name="devicetype_amazfit_active">Amazfit Active</string>
+    <string name="devicetype_amazfit_active_edge">Amazfit Active Edge</string>
+    <string name="devicetype_colacao23">ColaCao 2023</string>
+    <string name="devicetype_honor_band3">Honor Band 3</string>
+    <string name="devicetype_honor_band5">Honor Band 5</string>
+    <string name="devicetype_honor_band7">Honor Band 7</string>
+    <string name="devicetype_huawei_band_aw70">Huawei Band (AW70)</string>
+    <string name="devicetype_huawei_band7">Huawei Band 7</string>
+    <string name="devicetype_huawei_band8">Huawei Band 8</string>
+    <string name="devicetype_huawei_watch_gt">Huawei Watch GT</string>
+    <string name="devicetype_huawei_band4pro">Huawei Band 4 (Pro)</string>
+    <string name="devicetype_redmi_watch_2_lite">Redmi Watch 2 Lite</string>
+    <string name="menuitem_zepp_pay">Zepp Pay</string>
+    <string name="menuitem_apps_shortcuts">Zkratky aplikac铆</string>
+    <string name="zepp_os_watchface_city_of_speed">City of speed</string>
+    <string name="yard">stopy</string>
+    <string name="sony_protocol_v1">Verze 1</string>
+    <string name="sony_protocol_v2">Verze 2</string>
+    <string name="sony_protocol_v3">Verze 3</string>
+    <string name="pref_enable_call_accept_summary">Povolit p艡ij铆m谩n铆 hovor暖 ze za艡铆zen铆</string>
+    <string name="error_invalid_menu_structure">Chybn谩 JSON struktura menu</string>
+    <string name="menuitem_body_composition">Slo啪en铆 t臎la</string>
+    <string name="weekstepschart_steps_a_week_or_month">Kroky za t媒den/m臎s铆c</string>
+    <string name="weeksleepchart_sleep_a_week_or_month">Sp谩nek za t媒den/m臎s铆c</string>
+    <string name="activity_type_high_jump">Skok do v媒拧ky</string>
+    <string name="menuitem_readiness">P艡ipravenost</string>
+    <string name="prefs_wena3_button_action_name_start_timer">Spustit aktivn铆 膷asova膷</string>
+    <string name="pref_adaptive_volume_control_title">Adaptivn铆 ovl谩d谩n铆 hlasitosti</string>
+    <string name="prefs_wearmode">Re啪im no拧en铆</string>
+    <string name="activity_type_push_ups">Kliky</string>
+    <string name="activity_type_pull_ups">P艡铆tahy</string>
+    <string name="activity_type_javelin">O拧t臎p</string>
+    <string name="activity_type_long_jump">Skok do d谩li</string>
+    <string name="activity_type_mind_and_body">Mysl a t臎lo</string>
+    <string name="activity_type_dodgeball">Vyb铆jen谩</string>
+    <string name="activity_type_australian_football">Australsk媒 fotbal</string>
+    <string name="notification_channel_scan_service_name">Skenovac铆 slu啪ba</string>
+    <string name="do_not_disturb_lift_wrist_summary">Pouze pokud je povoleno probozen铆 obrazovky p艡i pozvednut铆</string>
+    <string name="activity_type_plank">Prkno (Plank)</string>
+    <string name="activity_type_dumbbell">膶inka</string>
+    <string name="activity_type_belly_dance">B艡i拧n铆 tanec</string>
+    <string name="menuitem_buzzer_intensity">Intenzita bzu膷谩ku</string>
+    <string name="pref_header_sony_sound_control">Ovl谩d谩n铆 zvuku</string>
+    <string name="prefs_wena3_receive_calls_title">Nastaven铆 p艡铆choz铆ch hovor暖</string>
+    <string name="prefs_wena3_notification_settings_title">Nastaven铆 upozorn臎n铆</string>
+    <string name="clap_hands_to_wakeup_device_summary">Op臎tovn茅 zatlesk谩n铆 zhashne obrazovku</string>
+    <string name="pixoo_power_saving_summary">Obrazovka zhasne, pokud mikrofon nic nezaznamen谩 del拧铆 dobu</string>
+    <string name="busy_task_fetch_sports_details_interrupted">Na膷铆t谩n铆 sportovn铆ch detail暖 bylo p艡eru拧eno</string>
+    <string name="pref_do_not_disturb_not_wear">Neru拧it, pokud za艡铆zen铆 nen铆 no拧eno</string>
+    <string name="activity_type_fencing">艩erm</string>
+    <string name="activity_type_flexibility">Flexibilita</string>
+    <string name="devicetype_huawei_watchgt4">Huawei Watch GT 4</string>
+    <string name="devicetype_huawei_watchultimate">Huawei Watch Ultimate</string>
+    <string name="pref_adaptive_volume_control_summary">Automatick茅 zv媒拧en铆 hlasitosti v hlu膷n茅m prost艡ed铆</string>
+    <string name="activity_type_softball">Softball</string>
+    <string name="activity_type_wrestling">Z谩pas</string>
+    <string name="prefs_wena3_day_start_hour_item">Den za膷铆n谩 v</string>
+    <string name="prefs_wena3_button_action_name_activity_screen">Obrazovka aktivit</string>
+    <string name="activity_type_latin_dance">Latinsk媒 tanec</string>
+    <string name="activity_type_roller_skating">Kole膷kov茅 bruslen铆</string>
+    <string name="activity_type_trail_run">B臎h ter茅nem</string>
+    <string name="devicetype_amazfit_bip3">Amazfit Bip 3</string>
+    <string name="prefs_password_4_digits_0_to_9_summary">Heslo se mus铆 skl谩dat z 膷ty艡 膷铆sel</string>
+    <string name="prefs_disconnect_notification_summary">Upozornit, pokud dojde k odpojen铆 za艡铆zen铆.</string>
+    <string name="clap_hands_to_wakeup_device">Zatlesk谩n铆m probud铆te obrazovku</string>
+    <string name="pref_device_action_dnd_alarms">Neru拧it - pouze bud铆ky</string>
+    <string name="prefs_wena3_button_action_item_double">Dvojit茅 stisknut铆</string>
+    <string name="wearmode_band">N谩ramek</string>
+    <string name="wearmode_necklace">N谩hrdeln铆k</string>
+    <string name="prefs_wena3_menu_icon_name_payment">Platba</string>
+    <string name="prefs_wena3_menu_icon_title">Ikony menu</string>
+    <string name="prefs_wena3_auto_power_off_item">Automatick茅 vypnut铆</string>
+    <string name="prefs_wena3_menu_icon_hint">Ikona nastaven铆 je v啪dy posledn铆</string>
+    <string name="prefs_wena3_auto_power_off_turn_off_time_item">膶as vypnut铆</string>
+    <string name="prefs_wena3_button_action_item_long">Dlouh茅 stisknut铆</string>
+    <string name="devicetype_mijia_lywsd03">Xiaomi Mi Temperature and Humidity Monitor 2</string>
+    <string name="prefs_wena3_home_icon_name_calories">Kalorie</string>
+    <string name="activity_type_mountain_hike">Horsk谩 t暖ra</string>
+    <string name="activity_type_kendo">Kend贸</string>
+    <string name="activity_type_sit_ups">Sedy-lehy</string>
+    <string name="activity_type_cross_country_skiing">B臎啪ky</string>
+    <string name="devicetype_sony_linkbuds">Sony LinkBuds</string>
+    <string name="serbian">Srb拧tina</string>
+    <string name="armenian">Arm茅n拧tina</string>
+    <string name="pref_title_debug">沤谩dost o lad臎n铆</string>
+    <string name="prefs_wena3_button_action_item">Akce tla膷铆tka</string>
+    <string name="devicetype_garmin_epix_pro">Garmin Epix Pro</string>
+    <string name="prefs_wena3_notification_vibration_repetition_1">Jednou</string>
+    <string name="avgStrokeRate">Pr暖m臎rn谩 rychlost zdvihu</string>
+    <string name="strokes">Celkem zdvih暖</string>
+    <string name="maxStrokeRate">Maxim谩ln铆 rychlost zdvihu</string>
+    <string name="prefs_wena3_home_icon_name_wena_pay">Wena Pay</string>
+    <string name="pref_header_audio">Zvuk</string>
+    <string name="pref_header_generic">Obecn茅</string>
+    <string name="pref_title_general_reconnectonlytoconnected">Znovu se p艡ipojit pouze k p艡ipojen媒m za艡铆zen铆m</string>
+    <string name="pref_summary_general_reconnectonlytoconnected">Znovu se p艡ipojit pouze k p艡ipojen媒m za艡铆zen铆m, nam铆sto p艡ipojen铆 se ke v拧em za艡铆zen铆m</string>
+    <string name="button_reset_menu_structure">Resetovat strukturu menu</string>
+    <string name="call_rejection_method_ignore">Ignorovat (ticho)</string>
+    <string name="pref_force_connection_type_ble">Bluetooth LE</string>
+    <string name="temperature_scale_fahrenheit">Fahrenheit</string>
+    <string name="pref_test_features_summary">Povolen茅 funkce pro toto zku拧ebn铆 za艡铆zen铆</string>
+    <string name="activity_type_physical_training">Fyzick媒 tr茅nink</string>
+    <string name="activity_type_fitness_gaming">Fitness hran铆</string>
+    <string name="activity_type_aerobic_exercise">Aerobik</string>
+    <string name="widget_move_up">Posunout nahoru</string>
+    <string name="devicetype_nothingear2">Nothing Ear (2)</string>
+    <string name="prefs_wena3_button_action_name_qrio_lock">Qrio Lock</string>
+    <string name="swingAngleAvg">Pr暖m臎rn媒 煤hel 拧vihu</string>
+    <string name="prefs_wena3_vibration_strength_item_strong">Siln茅</string>
+    <string name="prefs_wena3_home_icon_name_clock">Aktu谩ln铆 膷as</string>
+    <string name="prefs_wena3_home_icon_name_qrio">Qrio</string>
+    <string name="prefs_wena3_home_icon_name_pedometer">Po膷et krok暖</string>
+    <string name="scan_scanning_multiple_devices">Skenov谩n铆 %d za艡铆zen铆</string>
+    <string name="preferences_qhybrid_settings_summary">Star谩 nastaven铆 pro hodinky Q Hybrid</string>
+    <string name="degrees">stup艌暖</string>
+    <string name="button_open_menu_companion">Otev艡铆t nab铆dku doprovodn茅 aplikace</string>
+    <string name="yellow">沤lut谩</string>
+    <string name="blue">Modr谩</string>
+    <string name="pref_summary_prefix_notification_with_app">P艡idat n谩zev aplikace do n谩zvu ozn谩men铆</string>
+    <string name="pref_vitality_score_title">Sk贸re vitality</string>
+    <string name="pref_vitality_score_daily_title">Denn铆 pokrok</string>
+    <string name="widget">Widget</string>
+    <string name="widget_screen">Obrazovka widgetu</string>
+    <string name="pref_summary_fossil_hr_navigation_instructions">Nastavit chov谩n铆 naviga膷n铆 aplikace na hodink谩ch</string>
+    <string name="uploadwatchfaceoperation_failed">Instalace cifern铆ku selhala</string>
+    <string name="scan_not_scanning">Neskenuji</string>
+    <string name="prefs_home_icon_right_item">Vpravo</string>
+    <string name="prefs_wena3_notification_vibration_repetition_3">T艡ikr谩t</string>
+    <string name="pref_title_fossil_hr_navigation_instructions">Naviga膷n铆 pokyny</string>
+    <string name="pref_title_notification_wake_on_open">Automatick茅 probuzen铆 a odem膷en铆</string>
+    <string name="pref_summary_notification_wake_on_open">Probudit a odemknout za艡铆zen铆 Android, kdy啪 gadget vr谩t铆 odpov臎膹 OPEN. Mus铆 b媒t v d暖v臎ryhodn茅m re啪imu.</string>
+    <string name="pref_force_smart_alarm_summary">Vynutit podporu chytr媒ch bud铆k暖.
+\nPOU沤IJTE NA VLASTN脥 NEBEZPE膶脥</string>
+    <string name="prefs_wena3_vibration_strength_item_medium">St艡edn铆</string>
+    <string name="devicetype_redmi_watch_4">Redmi Watch 4</string>
+    <string name="impactAvg">Pr暖m臎rn媒 dopad</string>
+    <string name="impactMax">Maxim谩ln铆 dopad</string>
+    <string name="huawei_ignore_wakeup_status_start">Ignorovat stav za膷谩tku probuzen铆</string>
+    <string name="widget_layout_top_1_bot_2">1 naho艡e, 2 dole</string>
+    <string name="prefs_wena3_hint_lift_wrist">Zapnout displej, kdy啪 se pod铆v谩te na sv茅 z谩p臎st铆</string>
+    <string name="activity_prefs_height_inches">V媒拧ka v palc铆ch</string>
+    <string name="swolfMax">Maxim谩ln铆 swolf</string>
+    <string name="prefs_wena3_title_activity">Nastaven铆 aktivit</string>
+    <string name="widget_screen_x">Obrazovka %s</string>
+    <string name="pref_title_fossil_hr_nav_vibrate">Vibrovat p艡i nov茅m pokynu</string>
+    <string name="widget_screen_delete_confirm_description">Opravdu chcete odstranit 鈥�%1$s鈥�?</string>
+    <string name="pref_force_wear_location_summary">Vynutit podporu um铆st臎n铆 gadgetu.
+\nPOU沤IJTE NA VLASTN脥 NEBEZPE膶脥</string>
+    <string name="devicetype_miband2_hrx">Mi Band HRX</string>
+    <string name="devicetype_garmin_forerunner_265">Garmin Forerunner 265</string>
+    <string name="devicetype_huawei_watchfit2">Huawei Watch Fit 2</string>
+    <string name="devicetype_garmin_venu_3s">Garmin Venu 3S</string>
+    <string name="swolfMin">Minim谩ln铆 swolf</string>
+    <string name="strokes_minute">z谩b/min</string>
+    <string name="prefs_wena3_vibration_strength_item">S铆la vibrac铆</string>
+    <string name="widget_missing_parts">Vyberte pros铆m v拧echny widgety</string>
+    <string name="warning_missing_notification_permission">Nepoda艡ilo se odeslat trval茅 ozn谩men铆 z d暖vodu chyb臎j铆c铆ho opr谩vn臎n铆</string>
+    <string name="devicetype_garmin_forerunner_255s">Garmin Forerunner 255S</string>
+    <string name="devicetype_huawei_watch4pro">Huawei Watch 4 (Pro)</string>
+    <string name="notification_channel_full_battery_name">Pln谩 baterie</string>
+    <string name="devicetype_hama_fit6900">Hama Fit6900</string>
+    <string name="menuitem_workout_shortcuts">Zkratky cvi膷en铆</string>
+    <string name="zepp_os_watchface_vast_sky">Vast Sky</string>
+    <string name="zepp_os_watchface_lightning_flash">Lightning flash</string>
+    <string name="zepp_os_watchface_guider">Guider</string>
+    <string name="laneLength">D茅lka pruhu</string>
+    <string name="unknownDataEncountered">Vyskytla se nezn谩m谩 data</string>
+    <string name="Pace">Tempo</string>
+    <string name="RunningForm">B臎啪eck谩 forma</string>
+    <string name="devicetype_nothingearstick">Nothing Ear (Stick)</string>
+    <string name="huawei_reparse_workout_data_description">Toto nastaven铆 bude fungovat pouze po ur膷it媒ch aktualizac铆ch</string>
+    <string name="intent_api_broadcast_activity_sync_summary">Vys铆lat intent p艡i dokon膷en铆 synchronizace aktivit u kter茅hokoli za艡铆zen铆</string>
+    <string name="error_menu_companion_not_installed">Aplikace 鈥濰R Menu Companion鈥� nejsp铆拧e nen铆 nainstalovan谩</string>
+    <string name="info_fossil_rebuild_watchface_custom_menu">Znovu pros铆m sestavte sv暖j cifern铆k pro vlastn铆 nab铆dku</string>
+    <string name="cyan">Azurov谩</string>
+    <string name="prefs_wena3_title_alarm">Nastaven铆 bud铆k暖</string>
+    <string name="prefs_wena3_vibration_smart_item">Chytr茅 vibrace</string>
+    <string name="prefs_wena3_vibration_strength_item_weak">Slab茅</string>
+    <string name="prefs_wena3_auto_power_off_turn_on_time_item">膶as obnoven铆</string>
+    <string name="prefs_wena3_notification_default_vibration">Vibrace ozn谩men铆</string>
+    <string name="fossil_hr_nav_app_not_installed_notify_title">Naviga膷n铆 aplikace nen铆 na hodink谩ch nainstalov谩na</string>
+    <string name="widget_layout_top_2_bot_2">2 naho艡e, 2 dole</string>
+    <string name="widget_move_down">Posunout dol暖</string>
+    <string name="notification_channel_connection_status_name">Stav spojen铆</string>
+    <string name="waiting_for_bluetooth">膶ek谩n铆 na Bluetooth鈥�</string>
+    <string name="prefs_wena3_home_icon_name_edy">Z暖statek Edy</string>
+    <string name="temperature_scale_cf">Teplotn铆 stupnice</string>
+    <string name="devicetype_huawei_band9">Huawei Band 9</string>
+    <string name="devicetype_huawei_watchfit3">Huawei Watch Fit 3</string>
+    <string name="zepp_os_watchface_starry_sky">Starry sky</string>
+    <string name="zepp_os_watchface_the_ultima">The ultima</string>
+    <string name="stepRateSum">Sou膷et rychlosti krok暖</string>
+    <string name="stepRateAvg">Pr暖m臎rn谩 rychlost krok暖</string>
+    <string name="stepLengthAvg">Pr暖m臎rn谩 d茅lka kroku</string>
+    <string name="groundContactTimeAvg">Pr暖m臎rn媒 膷as kontaktu se zem铆</string>
+    <string name="pref_title_navigation_prefs">Nastaven铆 navigace</string>
+    <string name="pref_title_navigation_apps">Naviga膷n铆 aplikace</string>
+    <string name="menuitem_stats">Statistiky</string>
+    <string name="devicetype_xiaomi_watch_s1">Xiaomi Watch S1</string>
+    <string name="devicetype_xiaomi_watch_s1_pro">Xiaomi Watch S1 Pro</string>
+    <string name="devicetype_pixoo">Pixoo</string>
+    <string name="widget_screen_delete_confirm_title">Odstranit obrazovku widgetu</string>
+    <string name="widget_layout_top_2_bot_1">2 naho艡e, 1 dole</string>
+    <string name="pref_title_fossil_hr_nav_foreground">P艡ej铆t do pop艡ed铆</string>
+    <string name="pref_summary_fossil_hr_nav_vibrate">Zda maj铆 hodinky vibrovat p艡i ka啪d茅m nov茅m nebo zm臎n臎n茅m naviga膷n铆m pokynu (pouze, pokud je aplikace v pop艡ed铆)</string>
+    <string name="uploading_watchface">Nahr谩v谩m cifern铆k鈥�</string>
+    <string name="pref_force_connection_type_title">Vynutit typ spojen铆</string>
+    <string name="pref_force_connection_type_description">M暖啪ete zkusit vynutit typ spojen铆, pokud va拧e za艡铆zen铆 neodpov铆d谩 aplikaci Gadgetbridge</string>
+    <string name="pref_force_connection_type_auto">Automatick茅</string>
+    <string name="pref_force_connection_type_bt_classic">Bluetooth klasick媒</string>
+    <string name="prefs_wena3_hint_weather_statusbar">Zobrazen铆 ikony aktu谩ln铆ch podm铆nek v lev茅m horn铆m rohu domovsk茅 obrazovky</string>
+    <string name="call_rejection_method_reject">Odm铆tnout</string>
+    <string name="pref_call_rejection_method_title">Zp暖sob odm铆tnut铆 hovoru</string>
+    <string name="widget_layout_top_wide_bot_large">艩irok媒 naho艡e, velk媒 dole</string>
+    <string name="widget_layout_top_large_bot_wide">Velk媒 naho艡e, 拧irok媒 dole</string>
+    <string name="widget_layout_two">2 widgety</string>
+    <string name="wearmode_pebble">Pebble (svorka na botu)</string>
+    <string name="green">Zelen谩</string>
+    <string name="devicetype_garmin_instinct_2s_solar">Garmin Instinct 2S Solar</string>
+    <string name="prefs_wena3_notification_default_vibration_repetition">Opakov谩n铆 vibrac铆 ozn谩men铆</string>
+    <string name="wake_up_time">Probuzen铆</string>
+    <string name="notif_battery_full_title">Baterie gadgetu je pln谩!</string>
+    <string name="notif_battery_full">Baterie %1$s je pln谩</string>
+    <string name="activity_type_cross_trainer">Crossov媒 trena啪茅r</string>
+    <string name="devicetype_garmin_vivomove_hr">Garmin V铆vomove HR</string>
+    <string name="devicetype_garmin_vivomove_style">Garmin V铆vomove Style</string>
+    <string name="devicetype_garmin_fenix_6_sapphire">Garmin Fenix 6 Sapphire</string>
+    <string name="flatDistance">Ploch谩 vzd谩lenost</string>
+    <string name="prefs_wena3_status_page_title">艠azen铆 stavov茅 str谩nky</string>
+    <string name="activity_info">Informace o aktivit臎</string>
+    <string name="pref_test_features_title">Funkce</string>
+    <string name="prefs_wena3_notification_vibration_repetition_2">Dvakr谩t</string>
+    <string name="temperature_scale_cf_summary">Vyberte, zda za艡铆zen铆 pou啪铆v谩 Celsiovu nebo Fahrenheitovu stupnici.</string>
+    <string name="temperature_scale_celsius">Celsius</string>
+    <string name="pref_title_prefix_notification_with_app">N谩zev aplikace v ozn谩men铆</string>
+    <string name="pref_touch_tone">T贸n dotyku</string>
+    <string name="pref_touch_tone_summary">P艡ehraje t贸n, kdy啪 se dotknete sluch谩tka</string>
+    <string name="pref_navigation_app_osmand">OsmAnd(+)</string>
+    <string name="devicetype_xiaomi_watch_s3">Xiaomi Watch S3</string>
+    <string name="devicetype_mi_watch_color_sport">Mi Watch Color Sport</string>
+    <string name="devicetype_garmin_fenix_7s">Garmin Fenix 7S</string>
+    <string name="battery_i">Baterie %d</string>
+    <string name="pref_force_options_summary">N臎kter谩 za艡铆zen铆 nespr谩vn臎 tvrd铆, 啪e podporuj铆 n臎kter茅 mo啪nosti. T臎mito nastaven铆mi je p艡esto povol铆te.
+\nPOU沤脥VEJTE NA VLASTN脥 NEBEZPE膶脥
+\nP艡e膷t臎te si wiki</string>
+    <string name="pref_force_smart_alarm">Vynutit chytr媒 bud铆k</string>
+    <string name="pref_force_wear_location">Vynutit um铆st臎n铆 gadgetu</string>
+    <string name="pref_force_enable_heartrate_support_summary">Vynutit povolen铆 podpory srde膷n铆ho tepu.
+\nPOU沤IJTE NA VLASTN脥 NEBEZPE膶脥</string>
+    <string name="pref_force_enable_spo2_support_summary">Vynutit povolen铆 podpory SpO2.
+\nPOU沤IJTE NA VLASTN脥 NEBEZPE膶脥</string>
+    <string name="authentication_failed_check_key">Ov臎艡en铆 se nezda艡ilo, zkontrolujte pros铆m autentiza膷n铆 kl铆膷</string>
+    <string name="activity_type_stair_stepper">Stepper</string>
+    <string name="activity_type_crossfit">Crossfit</string>
+    <string name="devicetype_amazfit_bip5_unity">Amazfit Bip 5 Unity</string>
+    <string name="huawei_trusleep_summary">Sledujte kvalitu sv茅ho sp谩nku a zp暖sob d媒ch谩n铆 v re谩ln茅m 膷ase.
+\nAnalyzujte sv茅 sp谩nkov茅 vzorce a p艡esn臎 diagnostikujte 6 typ暖 probl茅m暖 se sp谩nkem.</string>
+    <string name="huawei_ignore_wakeup_status_end_description">M暖啪e pomoci se spr谩vnou detekc铆 sp谩nku. Viditeln茅 ihned v denn铆m p艡ehledu aktivit.</string>
+    <string name="prefs_wena3_receive_calls_hint">Po vypnut铆 nebudete dost谩vat ozn谩men铆 o p艡铆choz铆ch hovorech na Wena</string>
+    <string name="prefs_wena3_notification_vibration_repetition_0">Nekone膷n臎</string>
+    <string name="prefs_wena3_notification_vibration_repetition_4">膶ty艡ikr谩t</string>
+    <string name="pref_call_rejection_method_summary">Akce, kter谩 m谩 b媒t provedena po odm铆tnut铆 p艡铆choz铆ho hovoru na hodink谩ch</string>
+    <string name="pref_summary_osmand_packagename">Pou啪ito pro v媒b臎r verze OsmAnd, ke kter茅 se m谩 aplikace p艡ipojit</string>
+    <string name="pref_sleep_mode_schedule_summary">Poslat p艡ipom铆nku a vstoupit do re啪imu sp谩nku p艡i ve膷erce. P艡i nastaven茅m 膷ase pobuzen铆 zazvon铆 bud铆k.</string>
+    <string name="not_set">Nenastaveno</string>
+    <string name="pref_vitality_score_7_day_title">7denn铆 pokrok</string>
+    <string name="pref_vitality_score_daily_summary">Z铆skejte ozn谩men铆 po dosa啪en铆 maxim谩ln铆ho mno啪stv铆 bod暖 vitality za den</string>
+    <string name="widget_screen_no_free_slots_description">Za艡铆zen铆 nem谩 啪谩dn谩 voln谩 m铆sta pro obrazovky widget暖 (celkem m铆st: %1$s)</string>
+    <string name="widget_layout_single">1 widget</string>
+    <string name="pref_summary_fossil_hr_nav_foreground">Zda m谩 naviga膷n铆 aplikace automaticky p艡ej铆t do pop艡ed铆, jakmile obdr啪铆 naviga膷n铆 pokyny</string>
+    <string name="uploadwatchfaceoperation_in_progress">Nahr谩v谩m cifern铆k</string>
+    <string name="uploadwatchfaceoperation_complete">Instalace cifern铆ku dokon膷ena</string>
+    <string name="prefs_home_icon_center_item">Uprost艡ed</string>
+    <string name="menuitem_focus">Soust艡ed臎n铆</string>
+    <string name="prefs_wena3_home_icon_name_suica">Z暖statek Suica</string>
+    <string name="heartrate_bpm_155">155 tep暖/min</string>
+    <string name="heartrate_bpm_165">165 tep暖/min</string>
+    <string name="heartrate_bpm_175">175 tep暖/min</string>
+    <string name="devicetype_soundcore_liberty3_pro">Soundcore Liberty 3 Pro</string>
+    <string name="huawei_alarm_event_description">Neza拧krt谩vejte za拧krt谩vac铆 pol铆膷ko inteligentn铆 probuzen铆.</string>
+    <string name="menuitem_alerts">Upozorn臎n铆</string>
+    <string name="devicetype_xiaomi_watch_s1_active">Xiaomi Watch S1 Active</string>
+    <string name="swolfAvg">Pr暖m臎rn媒 swolf</string>
+    <string name="pref_agps_status">Stav AGPS</string>
+    <string name="agps_status_missing">Chyb臎j铆c铆</string>
+    <string name="agps_status_pending">膶ekaj铆c铆</string>
+    <string name="agps_status_current">Aktu谩ln铆</string>
+    <string name="agps_status_error">Chyba</string>
+    <string name="prefs_heartrate_alert_active_high_threshold">Pr谩h upozorn臎n铆 na vysokou aktivitu srde膷n铆 frekvence</string>
+    <string name="hydration_dnd_summary">Zak谩zat varov谩n铆 o hydrataci v 膷asov茅m 煤seku</string>
+    <string name="activity_type_free_training">Voln媒 tr茅nink</string>
+    <string name="activity_type_rower">Vesla艡sk媒 trena啪茅r</string>
+    <string name="activity_type_dynamic_cycle">Dynamick媒 cyklus</string>
+    <string name="activity_type_fitness_exercises">Fitness cvi膷en铆</string>
+    <string name="activity_type_cross_country_running">P艡espoln铆 b臎h</string>
+    <string name="activity_type_horizontal_bar">Vodorovn谩 ty膷</string>
+    <string name="activity_type_parallel_bar">Paraleln铆 ty膷</string>
+    <string name="activity_type_cooldown">Vychladnut铆</string>
+    <string name="activity_type_cross_training">K艡铆啪ov媒 tr茅nink</string>
+    <string name="devicetype_garmin_swim_2">Garmin Swim 2</string>
+    <string name="devicetype_garmin_vivoactive_4">Garmin V铆voactive 4</string>
+    <string name="milliseconds">milisekund</string>
+    <string name="pref_wide_area_tap_title">艩irokoploch媒 dotek</string>
+    <string name="pref_adaptive_noise_cancelling_title">Adaptivn铆 potla膷en铆 hluku</string>
+    <string name="pref_force_dnd_support">Vynutit podporu re啪imu Neru拧it</string>
+    <string name="pref_force_dnd_support_summary">Vynutit podporu re啪imu Neru拧it.
+\nPOU沤IJTE NA VLASTN脥 NEBEZPE膶脥</string>
+    <string name="pref_force_enable_heartrate_support">Vynutit podporu srde膷n铆ho tepu</string>
+    <string name="huawei_ignore_wakeup_status_end">Ignorovat stav konce probuzen铆</string>
+    <string name="prefs_home_icon_left_item">Vlevo</string>
+    <string name="red">膶erven谩</string>
+    <string name="prefs_wena3_auto_power_off_hint">Za艡铆zen铆 se automaticky vypne a zapne v nastaven茅m 膷ase</string>
+    <string name="prefs_wena3_button_action_name_qrio_unlock">Qrio Unlock</string>
+    <string name="prefs_wena3_receive_calls_item">Z铆sk谩vat ozn谩men铆 o hovorech</string>
+    <string name="device_state_waiting_scan">膶ek谩n铆 na sken za艡铆zen铆</string>
+    <string name="auto_reconnect_ble_scan_title">Znovu se p艡ipojit skenem BLE</string>
+    <string name="auto_reconnect_ble_scan_summary">Po膷kat na sken za艡铆zen铆 nam铆sto slep媒ch pokus暖 o spojen铆</string>
+    <string name="error_scan_failed">Sken selhal: %d</string>
+    <string name="fw_upgrade_notice_amazfitbip3">Chyst谩te se nainstalovat firmware %s na v谩拧 Amazfit Bip 3.
+\n
+\nNainstalujte nejprve soubor .fw a pot茅 soubor .res. Va拧e hodinky se po instalaci souboru .fw restartuj铆.
+\n
+\nPozn谩mka: soubor .res nemus铆te instalovat, pokud je toto啪n媒 s ji啪 d艡铆ve nainstalovan媒m.
+\n
+\nPOKRA膶UJTE NA VLASTN脥 NEBEZPE膶脥!</string>
+    <string name="pref_wearing_tone_summary">P艡ehraje t贸n p艡i vlo啪en铆 sluch谩tka</string>
+    <string name="heartrate_bpm_185">185 tep暖/min</string>
+    <string name="heartrate_bpm_195">195 tep暖/min</string>
+    <string name="heartrate_bpm_205">205 tep暖/min</string>
+    <string name="pref_summary_preview_message_in_title">Zobraz铆 n谩hled zpr谩vy v n谩zvu ozn谩men铆, podle povolen铆 za艡铆zen铆m</string>
+    <string name="pref_wearing_tone">T贸n no拧en铆</string>
+    <string name="activity_type_functional_training">Funk膷n铆 tr茅nink</string>
+    <string name="devicetype_garmin_fenix_7_pro">Garmin Fenix 7 Pro</string>
+    <string name="devicetype_garmin_instinct_2x_solar">Garmin Instinct 2X Solar</string>
+    <string name="pref_adaptive_noise_cancelling_summary">Automaticky nastavovat s铆lu potla膷en铆 hluku v z谩vislosti na 煤rovni hladin臎 okoln铆ho zvuku</string>
+    <string name="devicetype_garmin_forerunner_255">Garmin Forerunner 255</string>
+    <string name="devicetype_garmin_vivoactive_4s">Garmin V铆voactive 4S</string>
+    <string name="devicetype_garmin_vivoactive_5">Garmin V铆voactive 5</string>
+    <string name="huawei_alarm_smart_description">Neod拧krt谩vejte za拧krt谩vac铆 pol铆膷ko inteligentn铆 probuzen铆.</string>
+    <string name="huawei_trusleep_warning">Varov谩n铆: po povolen铆 t茅to funkce se v aplikaci Gadgetbridge bude ve拧ker媒 sp谩nek zobrazovat jako lehk媒 sp谩nek! Klepn臎te sem, pokud toto p艡ij铆m谩te.</string>
+    <string name="pref_force_enable_spo2_support">Vynutit podporu SpO2</string>
+    <string name="purple">Fialov谩</string>
+    <string name="prefs_wena3_item_background_sync">Synchronizace dat aktivit na pozad铆</string>
+    <string name="prefs_wena3_item_large_font">V臎t拧铆 velikost p铆sma</string>
+    <string name="prefs_wena3_hint_large_font">Zv臎t拧it velikost p铆sma v kalend谩艡i, ozn谩men铆ch atd.</string>
+    <string name="widget_layout">Rozvr啪en铆 widgetu</string>
+    <string name="huawei_reparse_workout_data">Znovu zpracovat data o cvi膷en铆</string>
+    <string name="white">B铆l谩</string>
+    <string name="prefs_wena3_title_screen">Nastaven铆 displeje</string>
+    <string name="prefs_wena3_hint_background_sync">Povolit Wena pravideln臎 啪谩dat Gadgetbridge o sta啪en铆 dat aktivit ze za艡铆zen铆</string>
+    <string name="prefs_wena3_item_rich_design">Pou啪铆t p臎kn媒 vzhled</string>
+    <string name="huawei_ignore_wakeup_status_start_description">M暖啪e pomoci se spr谩vnou detekc铆 sp谩nku. Viditeln茅 ihned v p艡ehledu denn铆ch aktivit.</string>
+    <string name="prefs_wena3_hint_rich_design">P艡id谩 zaoblen茅 膷tverce kolem ikon na domovsk茅 obrazovce</string>
+    <string name="menuitem_running">B臎h</string>
+    <string name="pref_sleep_mode_schedule_title">Pl谩n re啪imu sp谩nku</string>
+    <string name="pref_vitality_score_7_day_summary">Z铆skejte ozn谩men铆, kdy啪 va拧e sk贸re vitality dos谩hne 30, 60 nebo 100 v posledn铆ch 7 dnech</string>
+    <string name="prefs_wena3_item_weather_statusbar">Po膷as铆 ve stavov茅 li拧t臎</string>
+    <string name="pref_title_osmand_packagename">N谩zev bal铆膷ku OsmAnd</string>
+    <string name="serial_number">S茅riov茅 膷铆slo</string>
+    <string name="bedtime">Ve膷erka</string>
+    <string name="fossil_hr_nav_app_not_installed_notify_text">Navigace byla spu拧t臎na, ale na hodink谩ch nen铆 nainstalov谩na navigationApp. Nainstalujte ji pros铆m ze Spr谩vce aplikac铆.</string>
+    <string name="pref_navigation_app_gmaps">Mapy Google</string>
+    <string name="widget_screen_min_screens">Minim谩ln铆 po膷et obrazovek je %1$s</string>
+    <string name="widget_subtype">Podtyp widgetu</string>
+    <string name="widget_unknown_workout">Nezn谩m茅 cvi膷en铆 - %s</string>
+    <string name="widget_name_colored_tile">%1$s (barevn谩 dla啪dice)</string>
+    <string name="widget_name_untitled">Nepojmenovan媒 widget (%1$s)</string>
+    <string name="prompt_restart_gadgetbridge">Pro projeven铆 zm臎ny pros铆m restartujte GB.</string>
+    <string name="scan_scanning_all_devices">Skenov谩n铆 v拧ech za艡铆zen铆</string>
+    <string name="scan_scanning_single_device">Skenov谩n铆 1 za艡铆zen铆</string>
+    <string name="prefs_wena3_home_icon_title">Ikony na domovsk茅 obrazovce</string>
+    <string name="prefs_wena3_home_icon_name_energy">T臎lesn谩 energie</string>
+    <string name="prefs_wena3_home_icon_name_riiiver">Riiiver</string>
+    <string name="devicetype_garmin_instinct_solar">Garmin Instinct Solar</string>
+    <string name="devicetype_garmin_instinct_2s">Garmin Instinct 2S</string>
+    <string name="devicetype_garmin_instinct_2_solar">Garmin Instinct 2 Solar</string>
+    <string name="devicetype_garmin_instinct_2_soltac">Garmin Instinct 2 SolTac</string>
+    <string name="devicetype_garmin_venu_2">Garmin Venu 2</string>
+    <string name="devicetype_garmin_venu_3">Garmin Venu 3</string>
+    <string name="devicetype_garmin_instinct_crossover">Garmin Instinct Crossover</string>
+    <string name="devicetype_garmin_forerunner_245">Garmin Forerunner 245</string>
+    <string name="devicetype_garmin_vivosmart_5">Garmin V铆vosmart 5</string>
+    <string name="pref_wide_area_tap_summary">Rozpozn谩vat poklepy mezi tv谩艡emi a u拧ima</string>
+    <string name="intent_api_broadcast_activity_sync_title">Vys铆lat p艡i dokon膷en铆 synchronizace aktivit</string>
+    <string name="pref_summary_debug">Poslat 啪谩dost o lad臎n铆 do za艡铆zen铆 Huawei</string>
+    <string name="devicetype_garmin_venu_2_plus">Garmin Venu 2 Plus</string>
+    <string name="folder_is_empty">Slo啪ka je pr谩zdn谩</string>
+    <string name="dashboard_calendar_month_goals_reached_title">Dosa啪en茅 %% c铆le krok暖</string>
+    <string name="alarm_slot_reset">Slot bud铆k暖 byl nastaven na v媒choz铆</string>
+    <string name="pref_dashboard_widget_today_upside_down_summary">Ve 24h re啪imu vykreslit p暖lnoc dole a poledne na vrchu grafu</string>
+    <string name="pref_sleepasandroid_feat_notifications">Ozn谩men铆</string>
+    <string name="chart_cycling_point_label_distance">Dnes: %.1f km
+\nCelkem: %.1f km</string>
+    <string name="pref_fetch_unknown_files_summary">Na膷铆tat nezn谩m茅 soubory aktivit z hodinek. Nebudou zpracov谩ny, ale budou ulo啪eny do telefonu.</string>
+    <string name="pref_dashboard_all_devices_title">V拧echna za艡铆zen铆</string>
+    <string name="pref_dashboard_widget_double_size_title">Dvojit谩 velikost</string>
+    <string name="pref_dashboard_select_devices_title">Vybrat za艡铆zen铆...</string>
+    <string name="pref_dashboard_first_title">Zobrazit nejprve n谩st臎nku</string>
+    <string name="pref_sleepasandroid_feat_alarms">Bud铆ky</string>
+    <string name="pref_dashboard_widget_show_legend_title">Zobrazit legendu</string>
+    <string name="pref_title_huawei_account">脷膷et Huawei</string>
+    <string name="companion_pairing_request_title">Doprovodn茅 za艡铆zen铆</string>
+    <string name="prefs_wena3_notification_default_led">Barva oznamovac铆 LED</string>
+    <string name="pref_auto_reply_calls_delay_title">Zpo啪d臎n铆 automatick茅 odpov臎di</string>
+    <string name="activity_type_billiards">Kule膷n铆k</string>
+    <string name="activity_type_kabaddi">Kabadi</string>
+    <string name="activity_type_karting">Motok谩ry</string>
+    <string name="devicetype_scannable">Skenovateln茅 za艡铆zen铆</string>
+    <string name="unbind_before_pair_title">Ji啪 sp谩rov谩no</string>
+    <string name="pref_sleepasandroid_feat_heartrate">Srde膷n铆 tep</string>
+    <string name="pref_sleepasandroid_feat_oximetry">Oxymetrie</string>
+    <string name="pref_sleepasandroid_feat_spo2">SpO2</string>
+    <string name="device_name_cycling_sensor">Cyklistick媒 senzor</string>
+    <string name="devicetype_cycling_sensor">Cyklistick媒 senzor rychlosti</string>
+    <string name="bottom_nav_dashboard">N谩st臎nka</string>
+    <string name="pref_dashboard_widget_goals_chart_title">Graf c铆l暖</string>
+    <string name="activity_type_battle_rope">Posilovac铆 lano</string>
+    <string name="activity_type_swing">Houpa膷ka</string>
+    <string name="activity_type_hand_cycling">Ru膷n铆 cyklistika</string>
+    <string name="activity_type_shot">St艡elba</string>
+    <string name="prefs_wena3_notification_default_call_vibration">Vibrace p艡铆choz铆ho hovoru</string>
+    <string name="prefs_wena3_vibration_continuous">Pr暖b臎啪n谩</string>
+    <string name="loading">Na膷铆t谩n铆鈥�</string>
+    <string name="prefs_wena3_notification_per_app_settings_title">Nastaven铆 ozn谩men铆 pro ka啪dou aplikaci</string>
+    <string name="cyclingPowerMin">Minim谩ln铆 cyklistick媒 v媒kon</string>
+    <string name="cyclingPowerMax">Maxim谩ln铆 cyklistick媒 v媒kon</string>
+    <string name="pref_sleepasandroid_slot_title">Slot pro bud铆ky</string>
+    <string name="min_val">Minimum: %d</string>
+    <string name="foreFootLandings">Dopady na p艡edn铆 膷谩st nohy</string>
+    <string name="midFootLandings">Dopady na st艡edn铆 膷谩st nohy</string>
+    <string name="backFootLandings">Dopady na zadn铆 膷谩st nohy</string>
+    <string name="cyclingPowerAverage">Pr暖m臎rn媒 cyklistick媒 v媒kon</string>
+    <string name="prefs_wena3_smart_alarm_margin_item">Odsazen铆 chytr茅ho bud铆ku</string>
+    <string name="prefs_wena3_vibration_basic">Z谩kladn铆</string>
+    <string name="prefs_wena3_vibration_rapid">Rychl谩</string>
+    <string name="prefs_wena3_vibration_triple">Trojit谩</string>
+    <string name="prefs_wena3_vibration_step_up">Zvy拧ov谩n铆</string>
+    <string name="prefs_wena3_vibration_step_down">Sni啪ov谩n铆</string>
+    <string name="prefs_wena3_vibration_short">Kr谩tk谩</string>
+    <string name="state_scanned">Naskenov谩no</string>
+    <string name="pref_dashboard_devices_to_include">Zahrnut谩 za艡铆zen铆</string>
+    <string name="error_showing_changelog">Chyba p艡i zobrazov谩n铆 seznamu zm臎n</string>
+    <plurals name="amount_of_days" tools:ignore="MissingQuantity">
+        <item quantity="one">%d den</item>
+        <item quantity="few">%d dny</item>
+        <item quantity="other">%d dn铆</item>
+    </plurals>
+    <string name="pref_speak_notifications_focus_exclusive_summary_off">B臎hem 膷ten铆 ozn谩men铆 bude hlasitost p艡ehr谩v谩n铆 z ostatn铆ch aplikac铆 sn铆啪ena</string>
+    <string name="pref_title_garmin_default_reply_suffix">Pou啪铆t p艡edem nastavenou p艡铆ponu odpov臎di</string>
+    <string name="pref_sleepasandroid_feat_movement">Akcelerometr</string>
+    <string name="watchface_resolution_doesnt_match">Rozli拧en铆 cifern铆ku se neshoduje s obrazovkou za艡铆zen铆. Cifern铆k m谩 %1$s, obrazovka za艡铆zen铆 m谩 %2$s</string>
+    <string name="device_name_bicycle_sensor">Senzor na kolo</string>
+    <string name="pref_title_wheel_diameter">Pr暖m臎r kola</string>
+    <string name="pref_summary_cycling_persistence_interval">Interval v sekund谩ch, ve kter茅m budou aktu谩ln铆 cyklistick谩 data zapisov谩na do datab谩ze</string>
+    <string name="pref_title_cycling_persistence_interval">Interval ukl谩d谩n铆</string>
+    <string name="toast_setting_requires_reconnect">Toto nastaven铆 se projev铆 po op臎tovn茅m p艡ipojen铆</string>
+    <string name="toast_camera_support_required">Pro tuto funkci je vy啪adov谩na podpora fotoapar谩tu.</string>
+    <string name="toast_camera_photo_taken">Fotka byla po艡铆zena a ulo啪ena do: %s</string>
+    <string name="none">沤谩dn茅</string>
+    <string name="garmin_agps_url_i">AGPS %1$d URL</string>
+    <string name="no_folder_selected">Nen铆 vybr谩na 啪谩dn谩 slo啪ka</string>
+    <string name="default_percentage">V媒choz铆 (%1$d%%)</string>
+    <string name="battery_percentage_str">%1$s%%</string>
+    <string name="pref_fetch_unknown_files_title">Na膷铆tat nezn谩m茅 soubory</string>
+    <string name="eversionAngleAvg">Pr暖m臎rn媒 煤hel vybo膷en铆</string>
+    <string name="eversionAngleMax">Maxim谩ln铆 煤hel vybo膷en铆</string>
+    <string name="prefs_wena3_vibration_none">Bez vibrace</string>
+    <string name="pref_dashboard_widget_today_hr_interval_title">Interval srde膷n铆ho rytmu</string>
+    <string name="unsupported">Nepodporov谩no</string>
+    <string name="max_val">Maximum: %d</string>
+    <string name="pref_dashboard_cards_title">Zobrazit widgety na kart谩ch</string>
+    <string name="pref_dashboard_widget_settings">Nastaven铆 widget暖</string>
+    <string name="pref_auto_reply_calls_title">Automaticky p艡ij铆mat hovory</string>
+    <string name="pref_sleepasandroid_device_summary">Vyberte za艡铆zen铆 jako poskytovatele dat pro Sleep As Android</string>
+    <string name="pref_sleepasandroid_features_title">Funkce</string>
+    <string name="activity_type_worn">No拧eno</string>
+    <string name="prefs_wena3_led_none">Bez LED</string>
+    <string name="pref_dashboard_widget_today_hr_interval_summary">Po膷et minut, kdy graf uk谩啪e 鈥瀗o拧en铆鈥� po ka啪d茅m 煤sp臎拧n茅m m臎艡en铆 srde膷n铆ho rytmu</string>
+    <string name="activity_type_pickleball">Pickleball</string>
+    <string name="activity_type_trekking">Treking</string>
+    <string name="prefs_wena3_notification_default_call_vibration_repetition">Opakov谩n铆 vybrac铆 p艡铆choz铆ho hovoru</string>
+    <string name="prefs_wena3_vibration_warning">Varov谩n铆</string>
+    <string name="prefs_wena3_notification_default_call_led">Barva LED p艡铆choz铆ho hovoru</string>
+    <string name="prefs_wena3_vibration_siren">Sir茅na</string>
+    <string name="bottom_nav_devices">Za艡铆zen铆</string>
+    <string name="pref_auto_reply_calls_delay_summary">Po膷et sekund, po kter媒ch bude hovor automaticky p艡ijat</string>
+    <string name="pref_sleepasandroid_slot_summary">Kter媒 slot pro bud铆ky pou啪铆t p艡i nastavov谩n铆 bud铆k暖</string>
+    <string name="toggle_debug_mode">P艡epnout re啪im lad臎n铆</string>
+    <string name="share_debug_info">Sd铆let ladic铆 informace</string>
+    <string name="folder">Slo啪ka</string>
+    <string name="battery_low_notify_enabled">Ozn谩men铆 p艡i n铆zk茅m nabit铆</string>
+    <string name="url">URL</string>
+    <string name="garmin_agps_local_file">M铆stn铆 soubor</string>
+    <string name="pref_garmin_agps_help">N铆啪e uveden媒 seznam obsahuje v拧echny adresy URL, kter茅 si hodinky vy啪谩daly pro aktualizace syst茅mu AGPS. M暖啪ete vybrat soubor z 煤lo啪i拧t臎 telefonu, kter媒 bude odesl谩n do hodinek, kdy啪 si vy啪谩daj铆 aktualizaci.</string>
+    <string name="copied_to_clipboard">Zkop铆rov谩no do schr谩nky</string>
+    <string name="realtime_settings">Nastaven铆 v re谩ln茅m 膷ase</string>
+    <string name="show_in_notification">Zobrazit v ozn谩men铆</string>
+    <string name="battery_low_threshold">Hranice n铆zk茅ho nabit铆</string>
+    <string name="battery_full_notify_enabled">Ozn谩men铆 p艡i pln茅m nabit铆</string>
+    <string name="battery_full_threshold">Hranice pln茅ho nabit铆</string>
+    <string name="activity_type_rolling">Kole膷kov茅 sporty</string>
+    <string name="activity_type_track_and_field">Lehk谩 atletika</string>
+    <string name="activity_type_smith_machine">Multipress</string>
+    <string name="activity_type_jazz_dance">Jazzov媒 tanec</string>
+    <string name="activity_type_sailing">Jachting</string>
+    <string name="activity_type_skateboarding">Skateboarding</string>
+    <string name="companion_pairing_request_description">Sp谩rovat toto za艡铆zen铆 jako doprovodn茅?
+\n
+\nTato akce je doporu膷ena pro n臎kter茅 funkce jako nalezen铆 za艡铆zen铆 a poskytuje lep拧铆 spojen铆.</string>
+    <string name="devicesetting_scannable_rssi_summary">Minim谩ln铆 hranice RSSI pro detekci</string>
+    <string name="dashboard_settings">Nastaven铆 n谩st臎nky</string>
+    <string name="sleepasandroid_settings">Sleep As Android</string>
+    <string name="pref_dashboard_first_summary">Po spu拧t臎n铆 aplikace Gadgetbridge zobrazit n谩st臎nku nam铆sto obrazovky za艡铆zen铆</string>
+    <string name="pref_dashboard_cards_summary">Vykreslit karty kolem widget暖 na n谩st臎nce</string>
+    <string name="pref_auto_reply_calls_summary">Mobil automaticky p艡ijme p艡铆choz铆 hovory</string>
+    <string name="pref_dashboard_widget_today_upside_down_title">P暖lnoc dole</string>
+    <string name="pref_sleepasandroid_enable_summary">Integrace s aplikac铆 Sleep As Android</string>
+    <string name="pref_sleepasandroid_device_title">Poskytovatel</string>
+    <string name="activity_type_skating">Bruslen铆</string>
+    <string name="activity_type_curling">Curling</string>
+    <string name="activity_type_tai_chi">Tchaj-钮i</string>
+    <string name="activity_type_hula_hooping">Hula hooping</string>
+    <string name="activity_type_disc_sports">Diskov茅 sporty</string>
+    <string name="activity_type_kite_flying">Pou拧t臎n铆 drak暖</string>
+    <string name="activity_type_shuttlecock">Shuttlecock</string>
+    <string name="activity_type_lacross">Lakros</string>
+    <string name="activity_type_jet_skiing">Jet skiing</string>
+    <string name="pref_dashboard_widget_today_title">Graf aktivit</string>
+    <string name="pref_speak_notifications_aloud_title">Nahlas p艡ed膷铆tat ozn谩men铆</string>
+    <string name="pref_speak_notifications_focus_exclusive_title">Pozastavit zvuk ostatn铆ch aplikac铆</string>
+    <string name="pref_speak_notifications_focus_exclusive_summary_on">B臎hem 膷ten铆 ozn谩men铆 bude p艡ehr谩v谩n铆 z ostatn铆ch aplikac铆 pozastaveno</string>
+    <string name="pref_header_calls_and_notifications">Hovory a ozn谩men铆</string>
+    <string name="pref_title_bottom_navigation_bar">Spodn铆 naviga膷n铆 li拧ta</string>
+    <string name="pref_summary_bottom_navigation_bar_on">P艡ep铆n谩n铆 mezi hlavn铆mi obrazovkami pomoc铆 naviga膷n铆 li拧ty nebo horizont谩ln铆ch gest</string>
+    <string name="pref_summary_bottom_navigation_bar_off">P艡ep铆n谩n铆 mezi hlavn铆mi obrazovkami pouze pomoc铆 horizont谩ln铆ch gest</string>
+    <string name="open_camera">Otev艡铆t fotoapar谩t</string>
+    <string name="title_cycling">Cyklistika</string>
+    <string name="pref_sleepasandroid_features_summary">Podpora se u jednotliv媒ch za艡铆zen铆 li拧铆</string>
+    <string name="chart_cycling_point_label_speed">%.1f km/h</string>
+    <string name="unbind_before_pair_message">Toto za艡铆zen铆 je ji啪 sp谩rov谩no v nastaven铆 syst茅mu Android, kv暖li 膷emu啪 m暖啪e p谩rov谩n铆 n臎kter媒ch za艡铆zen铆 selhat.
+\n
+\nPokud se v谩m nepovede za艡铆zen铆 p艡idat, odstra艌te jej pros铆m v nastaven铆 syst茅mu Android a zkuste to znovu.</string>
+    <string name="pref_dashboard_widget_today_24h_title">24h re啪im</string>
+    <string name="devicesetting_scannable_rssi">Minim谩ln铆 hranice RSSI</string>
+    <string name="pref_speak_notifications_aloud_summary">Ozn谩men铆 budou nahlas p艡e膷tena skrze sluch谩tka</string>
+    <string name="pref_summary_garmin_default_reply_suffix">P艡id谩no nav铆c k p艡铆pon臎 nastaven茅 v Gadgetbridge</string>
+    <string name="pref_summary_huawei_account">脷膷et Huawei pou啪铆van媒 v procesu p谩rov谩n铆. Jeho nastaven铆 umo啪艌uje p谩rov谩n铆 bez tov谩rn铆ho nastaven铆.</string>
+    <string name="pref_summary_wheel_diameter">Pr暖m臎r kola v palc铆ch. Obvykle 29, 27,5 nebo 26.</string>
+    <string name="toast_camera_permission_required">Pro tuto funkci je vy啪adov谩no opr谩vn臎n铆 k fotoapar谩tu.</string>
+    <string name="pref_dashboard_select_devices_summary">Zkombinovat data aktivit z ur膷it媒ch za艡铆zen铆 pro celkov茅 hodnoty na n谩st臎nce</string>
+    <string name="fmtPaceTypeAverage">Typ tempa %d pr暖m臎rn茅</string>
+    <string name="fmtPaceDistance">Tempo %d vzd谩lenost</string>
+    <string name="fmtPacePace">Tempo %d tempo</string>
+    <string name="fmtPaceCorrection">Tempo %d oprava</string>
+    <string name="devicesetting_scannable_debounce_summary">Po naskenov谩n铆 bude za艡铆zen铆 ozna膷eno jako naskenovan茅 a bude ignorov谩no po ur膷it媒 膷as</string>
+    <string name="pref_dashboard_all_devices_summary">Zkombinovat data aktivit ze v拧ech p艡idan媒ch za艡铆zen铆 pro celkov茅 hodnoty na n谩st臎nce</string>
+    <string name="pref_dashboard_widget_show_legend_summary">Zobrazit legendu pod widgetem vysv臎tluj铆c铆 barvy</string>
+    <string name="devicesetting_scannable_minimum_unseen">Minim谩ln铆 膷as nevid臎n铆 (sekundy)</string>
+    <string name="devicesetting_scannable_debounce">膶asov媒 limit odezvy skenov谩n铆 (sekundy)</string>
+    <string name="devicesetting_scannable_minimum_unseen_summary">Po naskenov谩n铆 nesm铆 b媒t za艡铆zen铆 vid臎n茅 po toto mno啪stv铆 膷asu, ne啪 bude op臎t registrov谩no</string>
+    <string name="pref_dashboard_widget_today_24h_summary">Zobrazit aktivitu v jednom 24h kruhu nam铆sto dvojit茅ho 12h kruhu</string>
+    <string name="fmtPaceType">Tempo %d typ</string>
+    <string name="pref_dashboard_widgets_order_summary">Vyberte, kter茅 widgety jsou povoleny a v jak茅m po艡ad铆 budou zobrazeny na n谩st臎nce</string>
+    <string name="pref_dashboard_widget_double_size_summary">Povolit widgetu zabrat dva sloupce na n谩st臎nce</string>
+    <string name="devicetype_garmin_instinct">Garmin Instinct</string>
+    <string name="devicetype_moondrop_space_travel">Moondrop Space Travel</string>
+    <string name="moondrop_equalizer_preset_monitor">Monitor</string>
+    <string name="moondrop_touch_earbud">Sluch谩tko</string>
+    <string name="moondrop_touch_earbud_both">Ob臎</string>
+    <string name="moondrop_touch_trigger">Spou拧t臎膷</string>
+    <string name="moondrop_touch_action_play_pause">P艡ehr谩t/pozastavit</string>
+    <string name="moondrop_touch_action_call_pick_hang">Zvednout/zav臎sit hovor</string>
+    <string name="moondrop_touch_action_call_start">Za膷铆t hovor</string>
+    <string name="moondrop_touch_action_assistant">Spustit hlasov茅ho asistenta</string>
+    <string name="moondrop_touch_trigger_long_press_3s">Dlouh茅 stisknut铆 (3s)</string>
+    <string name="moondrop_touch_trigger_long_press_1s">Dlouh茅 stisknut铆 (1s)</string>
+    <string name="moondrop_equalizer_preset_reference">Reference</string>
+    <string name="moondrop_equalizer_preset_basshead">Basshead</string>
+    <string name="moondrop_touch_action_anc_mode">P艡epnout re啪im aktivn铆ho potla膷en铆 hluku</string>
+    <string name="devicetype_garmin_fenix_6">Garmin Fenix 6</string>
+    <string name="devicetype_garmin_fenix_5">Garmin Fenix 5</string>
+    <string name="devicetype_garmin_vivoactive_3">Garmin V铆voactive 3</string>
+    <string name="pref_battery_polling_summary">Jedn谩 se o maxim谩ln铆 snahu, kter谩 m暖啪e b媒t zpo啪d臎na z n臎kolika d暖vod暖</string>
+    <string name="pref_battery_polling_enable">Povolit dotazov谩n铆 baterie</string>
+    <string name="pref_battery_polling_interval_format">ka啪d媒ch %1$s minut</string>
+    <string name="battery_polling_failed_start">Nepoda艡ilo se spustit dotazov谩n铆 baterie</string>
+    <string name="proprietary_app_warning">Tato funkce vy啪aduje instalaci propriet谩rn铆 aplikace</string>
+    <string name="devicetype_garmin_vivomove_trend">Garmin V铆vomove Trend</string>
+    <string name="pref_battery_polling_interval">Interval dotazov谩n铆 baterie</string>
+    <string name="pref_battery_polling_configuration">Nastaven铆 dotazov谩n铆 baterie</string>
+    <string name="devicetype_nothing_cmf_watch_pro_2">CMF Watch Pro 2</string>
+    <string name="devicetype_garmin_vivosport">Garmin V铆vosport</string>
+    <string name="activity_type_gateball">Gateball</string>
+    <string name="activity_type_luge">San臎</string>
+    <string name="activity_type_parachuting">Seskok pad谩kem</string>
+    <string name="activity_type_tae_bo">Tae Bo</string>
+    <string name="activity_type_beach_soccer">Pl谩啪ov媒 fotbal</string>
+    <string name="activity_type_beach_volleyball">Pl谩啪ov媒 volejbal</string>
+    <string name="activity_type_sepak_takraw">Sepak takraw</string>
+    <string name="activity_type_auto_racing">Automobilov茅 z谩vody</string>
+    <string name="activity_type_parkour">Parkour</string>
+    <string name="pref_summary_notification_times_enabled">Pos铆lat ozn谩men铆 pouze v ur膷it茅m 膷asov茅m rozmez铆</string>
+    <string name="pref_title_notification_times_enabled">膶asy ozn谩men铆</string>
+    <string name="devicetype_huawei_watch3">Huawei Watch 3 (Pro)</string>
+    <string name="devicetype_nothing_cmf_buds_pro_2">CMF Buds Pro 2</string>
+    <string name="devicetype_vivitar_hr_bp_monitor_activity_tracker">Vivitar HR &amp; BP Monitor Activity Tracker</string>
+    <string name="devicetype_garmin_forerunner_255s_music">Garmin Forerunner 255S Music</string>
+    <string name="devicetype_garmin_forerunner_255_music">Garmin Forerunner 255 Music</string>
+    <string name="devicetype_huawei_watchgtrunner">Huawei Watch GT Runner</string>
+    <string name="find_my_phone_companion_warning">Pro nalezen铆 telefonu je vy啪adov谩na doprovodn谩 aplikace. Klepn臎te sem pro v铆ce informac铆.</string>
+    <string name="abstract_chart_fragment_kind_awake_sleep">Vzh暖ru</string>
+    <string name="sleep_colored_stats_deep">Hlubok媒</string>
+    <string name="sleep_colored_stats_light">Lehk媒</string>
+    <string name="sleep_colored_stats_rem">REM</string>
+    <string name="sleep_colored_stats_deep_avg">Pr暖m. hlubok茅ho</string>
+    <string name="sleep_colored_stats_light_avg">Pr暖m. lehk茅ho</string>
+    <string name="sleep_colored_stats_rem_avg">Pr暖m. REM</string>
+    <string name="stats_highest_hr">Nejvy拧拧铆 tep</string>
+    <string name="stats_empty_value">-</string>
+    <string name="stats_lowest_hr">Nejni啪拧铆 tep</string>
+    <string name="minutes_20">20 minut</string>
+    <string name="hrv_status_balanced">Vyv谩啪en媒</string>
+    <string name="hrv_status_unbalanced">Nevyv谩啪en媒</string>
+    <string name="hrv_status_baseline">%1$d-%2$d ms</string>
+    <string name="devicetype_garmin_forerunner_965">Garmin Forerunner 965</string>
+    <string name="soundcore_voice_prompts">Hlasov茅 povely</string>
+    <string name="soundcore_equalizer_preset">P艡edvolba</string>
+    <string name="soundcore_equalizer_direction">Sm臎r za艡铆zen铆</string>
+    <string name="soundcore_equalizer_reset_summary">Nastavit v拧echna p谩sma ekvaliz茅ru zp臎t na v媒choz铆 nastaven铆</string>
+    <string name="soundcore_equalizer_band1">P谩smo 1</string>
+    <string name="soundcore_equalizer_band6">P谩smo 6</string>
+    <string name="hrv_status_low">N铆zk媒</string>
+    <string name="minutes_60">60 minut</string>
+    <string name="hrv_status_poor">艩patn媒</string>
+    <string name="hrv_status_unit">%1$d ms</string>
+    <string name="soundcore_equalizer_value">Hodnota</string>
+    <string name="body_energy_gained">Z铆skan谩</string>
+    <string name="body_energy_legend_level">脷rove艌 t臎lesn茅 energie</string>
+    <string name="soundcore_button_brightness">Jas tla膷铆tka</string>
+    <string name="soundcore_button_brightness_medium">St艡edn铆</string>
+    <string name="body_energy_lost">Ztracen谩</string>
+    <string name="soundcore_button_brightness_low">N铆zk媒</string>
+    <string name="soundcore_button_brightness_high">Vysok媒</string>
+    <string name="soundcore_equalizer_custom_summary">Upravit nastaven铆 parametrick茅ho ekvaliz茅ru</string>
+    <string name="soundcore_equalizer_reset_title">Obnovit na v媒choz铆</string>
+    <string name="devicetype_garmin_fenix_5_plus">Garmin Fenix 5 Plus</string>
+    <string name="devicetype_soundcore_motion300">Soundcore Motion 300</string>
+    <string name="pref_header_hrv_status">Stav HRV</string>
+    <string name="body_energy">T臎lesn谩 energie</string>
+    <string name="soundcore_ldac_mode_title">Re啪im LDAC</string>
+    <string name="soundcore_ldac_mode_summary">Povolen铆m LDAC se sn铆啪铆 v媒dr啪 baterie a spojen铆 se m暖啪e st谩t nestabiln铆m</string>
+    <string name="soundcore_adaptive_direction_title">Adaptivn铆 sm臎r</string>
+    <string name="soundcore_adaptive_direction_summary">Automaticky upravit p艡edvolbu ekvaliz茅ru v z谩vislosti na sm臎ru za艡铆zen铆</string>
+    <string name="soundcore_equalizer_preset_signature">Podpis soundcore</string>
+    <string name="soundcore_equalizer_preset_xtra_bass">Xtra Bass</string>
+    <string name="soundcore_equalizer_preset_voice">Hlas</string>
+    <string name="soundcore_equalizer_preset_balanced">Vyv谩啪en媒</string>
+    <string name="soundcore_equalizer_custom_title">P艡izp暖sobit鈥�</string>
+    <string name="soundcore_equalizer_direction_standing">Stoj铆c铆</string>
+    <string name="soundcore_equalizer_direction_lying">Le啪铆c铆</string>
+    <string name="soundcore_equalizer_direction_hanging">Vis铆c铆</string>
+    <string name="soundcore_equalizer_band2">P谩smo 2</string>
+    <string name="soundcore_equalizer_band3">P谩smo 3</string>
+    <string name="soundcore_equalizer_band4">P谩smo 4</string>
+    <string name="soundcore_equalizer_band5">P谩smo 5</string>
+    <string name="soundcore_equalizer_band7">P谩smo 7</string>
+    <string name="soundcore_equalizer_band8">P谩smo 8</string>
+    <string name="soundcore_equalizer_band9">P谩smo 9</string>
+    <string name="soundcore_equalizer_frequency">Frekvence</string>
+    <string name="hrv_status">Stav HRV</string>
+    <string name="hrv_status_day_avg">Denn铆 pr暖m臎r</string>
+    <string name="hrv_status_day_avg_legend">Denn铆 pr暖m臎r (ms)</string>
+    <string name="hrv_status_seven_days_avg">7denn铆 pr暖m臎r</string>
+    <string name="hrv_status_seven_days_avg_status">Stav</string>
+    <string name="hrv_status_last_night">Posledn铆 noc</string>
+    <string name="hrv_status_last_night_highest_5">Posledn铆 noc 5-min maxim谩ln铆 pr暖m臎r</string>
+    <string name="hrv_status_seven_days_avg_long">7denn铆 pr暖m臎r</string>
+    <string name="hrv_status_baseline_label">Z谩klad</string>
+    <string name="gps_glonass">GPS + GLONASS</string>
+    <string name="transition">P艡echod</string>
+    <string name="activity_type_navigate">Navigovat</string>
+    <string name="activity_type_indoor_track">Halov谩 dr谩ha</string>
+    <string name="activity_type_handcycling">Handcycling</string>
+    <string name="activity_type_e_bike">Elektrokolo</string>
+    <string name="activity_type_bike_commute">Doj铆啪d臎n铆 na kole</string>
+    <string name="activity_type_handcycling_indoor">Vnit艡n铆 handcycling</string>
+    <string name="activity_type_transition">P艡echod</string>
+    <string name="activity_type_fitness_equipment">Fitness vybaven铆</string>
+    <string name="activity_type_cardio">Kardio</string>
+    <string name="activity_type_breathwork">Dechov茅 cvi膷en铆</string>
+    <string name="activity_type_xc_classic_ski">Klasick媒 b臎h na ly啪铆ch</string>
+    <string name="activity_type_mountaineering">Alpinismus</string>
+    <string name="activity_type_multisport">Multisport</string>
+    <string name="activity_type_paddling">P谩dlov谩n铆</string>
+    <string name="activity_type_flying">L茅t谩n铆</string>
+    <string name="activity_type_motorcycling">J铆zda na motocyklu</string>
+    <string name="activity_type_boating">J铆zda na lodi</string>
+    <string name="activity_type_driving">艠铆zen铆</string>
+    <string name="activity_type_inline_skating">Inline bruslen铆</string>
+    <string name="activity_type_climb_indoor">Lezen铆 na um臎l茅 st臎n臎</string>
+    <string name="activity_type_bouldering">Bouldering</string>
+    <string name="activity_type_sail_race">Z谩vod plachetnic</string>
+    <string name="activity_type_platform_tennis">Platformov媒 tenis</string>
+    <string name="activity_type_american_football">Americk媒 fotbal</string>
+    <string name="activity_type_training">Tr茅nink</string>
+    <string name="activity_type_hang_gliding">Z谩v臎sn茅 l茅t谩n铆</string>
+    <string name="activity_type_ice_skating">Bruslen铆</string>
+    <string name="activity_type_sail_expedition">V媒let na plachetnici</string>
+    <string name="activity_type_sky_diving">Skydiving</string>
+    <string name="activity_type_snowshoe">Sn臎啪nice</string>
+    <string name="activity_type_kayaking">J铆zda na kajaku</string>
+    <string name="activity_type_rafting">Rafting</string>
+    <string name="activity_type_wakeboarding">Wakeboarding</string>
+    <string name="activity_type_water_skiing">Vodn铆 ly啪ov谩n铆</string>
+    <string name="cannot_upload_watchface_too_many_watchfaces_installed">Nepoda艡ilo se nahr谩t cifern铆k, je nainstalov谩no p艡铆li拧 mnoho cifern铆k暖</string>
+    <string name="insufficient_space_for_upload">Nedostatek m铆sta pro nahr谩n铆</string>
+    <string name="activity_type_snowmobiling">J铆zda na sn臎啪n茅m sk煤tru</string>
+    <string name="activity_type_stand_up_paddleboarding">Paddleboarding ve stoje</string>
+    <string name="activity_type_tactical">Tactical</string>
+    <string name="activity_type_jumpmaster">Jumpmaster</string>
+    <string name="activity_type_floor_climbing">艩plh谩n铆 po podlaze</string>
+    <string name="activity_type_softball_slow_pitch">Slow Pitch Softball</string>
+    <string name="activity_type_winter_sport">Zimn铆 sport</string>
+    <string name="activity_type_grinding">Grinding</string>
+    <string name="activity_type_health_snapshot">Zdravotn铆 sn铆mek</string>
+    <string name="activity_type_marine">Plavba po mo艡i</string>
+    <string name="activity_type_racquetball">Raketbal</string>
+    <string name="activity_type_push_walk_speed">Push - Walk Speed</string>
+    <string name="activity_type_disc_golf">Discgolf</string>
+    <string name="activity_type_ultimate_disc">Ultimate Frisbee</string>
+    <string name="activity_type_team_sport">T媒mov媒 sport</string>
+    <string name="activity_type_lacrosse">Lakros</string>
+    <string name="activity_type_water_tubing">Tubing</string>
+    <string name="activity_type_wakesurfing">Wakesurfing</string>
+    <string name="activity_type_mixed_martial_arts">Sm铆拧en谩 bojov谩 um臎n铆</string>
+    <string name="activity_type_aerobic_combo">Aerobn铆 kombinace</string>
+    <string name="activity_type_aerobics">Aerobika</string>
+    <string name="activity_type_air_walker">Air walker</string>
+    <string name="activity_type_board_game">Deskov谩 hra</string>
+    <string name="activity_type_bocce">Bocce</string>
+    <string name="activity_type_breaking">Breakdance</string>
+    <string name="activity_type_bridge">Most</string>
+    <string name="activity_type_checkers">D谩ma</string>
+    <string name="activity_type_chess">艩achy</string>
+    <string name="activity_type_dragon_boat">Dra膷铆 lo膹</string>
+    <string name="activity_type_flowriding">Flowriding</string>
+    <string name="activity_type_folk_dance">Folkov媒 tanec</string>
+    <string name="activity_type_shooting">St艡elba</string>
+    <string name="activity_type_video_gaming">Videohry</string>
+    <string name="activity_type_racket">Raketa</string>
+    <string name="activity_type_indoor_push_walk_speed">Push - Walk Speed v interi茅ru</string>
+    <string name="activity_type_meditation">Meditace</string>
+    <string name="activity_type_padel">P谩dlo</string>
+    <string name="activity_type_artistic_swimming">Synchronizovan茅 plav谩n铆</string>
+    <string name="activity_type_bmx">BMX</string>
+    <string name="activity_type_push_run_speed">Push - Run Speed</string>
+    <string name="activity_type_indoor_push_run_speed">Push - Run Speed v interi茅ru</string>
+    <string name="activity_type_para_sport">Para Sport</string>
+    <string name="activity_type_ballroom_dance">Spole膷ensk媒 tanec</string>
+    <string name="activity_type_cardio_combat">Kardio z谩pas</string>
+    <string name="activity_type_esports">Esporty</string>
+    <string name="activity_type_finswimming">Ploutvov茅 plav谩n铆</string>
+    <string name="activity_type_frisbee">Frisbee</string>
+    <string name="activity_type_futsal">Futsal</string>
+    <string name="activity_type_hacky_sack">Hacky sack</string>
+    <string name="activity_type_snorkeling">艩norchlov谩n铆</string>
+    <string name="activity_type_wall_ball">Medicinbal</string>
+    <string name="activity_type_water_polo">Vodn铆 p贸lo</string>
+    <string name="activity_type_hip_hop">Hip-hop</string>
+    <string name="activity_type_parallel_bars">Bradla</string>
+    <string name="activity_type_race_walking">Sportovn铆 ch暖ze</string>
+    <string name="activity_type_hula_hoop">Hula hoop</string>
+    <string name="activity_type_jai_alai">Jai alai</string>
+    <string name="activity_type_jujitsu">Ju-jitsu</string>
+    <string name="activity_type_modern_dance">Modern铆 tanec</string>
+    <string name="activity_type_judo">Judo</string>
+    <string name="activity_type_muay_thai">Muaythai</string>
+    <string name="activity_type_mass_gymnastics">Hromadn谩 gymnastika</string>
+    <string name="activity_type_shuffleboard">Shuffleboarding</string>
+    <string name="activity_type_pole_dance">Pole dance</string>
+    <string name="activity_type_square_dance">Square dance</string>
+    <string name="activity_type_weiqi">Weiqi</string>
+    <string name="activity_type_stair_climber">Stairclimber</string>
+    <string name="activity_type_somatosensory_game">Somatosenzorick谩 hra</string>
+    <string name="activity_type_spinning">Spinning</string>
+    <string name="activity_type_tug_of_war">P艡etahov谩n铆 lanem</string>
+    <string name="activity_type_table_football">Stoln铆 fotbal</string>
+    <string name="activity_type_free_sparring">Voln媒 sparing</string>
+    <string name="activity_type_body_combat">Body combat</string>
+    <string name="activity_type_plaza_dancing">Tanec na n谩m臎st铆</string>
+    <string name="activity_type_laser_tag">Laser Tag</string>
+    <string name="activity_type_obstacle_race">P艡ek谩啪kov媒 b臎h</string>
+    <string name="activity_type_billiard_pool">Pool</string>
+    <string name="activity_type_canoeing">Kanoistika</string>
+    <string name="activity_type_water_scooter">Vodn铆 sk煤tr</string>
+    <string name="activity_type_bobsleigh">Boby</string>
+    <string name="activity_type_sledding">S谩艌kov谩n铆</string>
+    <string name="activity_type_biathlon">Biatlon</string>
+    <string name="activity_type_bungee_jumping">Bungee jumping</string>
+    <string name="activity_type_orienteering">Orienta膷n铆 b臎h</string>
+    <string name="devicetype_garmin_venu">Garmin Venu</string>
+    <string name="authentication_failed_negotiation">Vyjedn谩v谩n铆 ov臎艡ovac铆ho kl铆膷e se nezda艡ilo</string>
+    <string name="devicetype_garmin_venu_2s">Garmin Venu 2S</string>
+    <string name="hrv">HRV</string>
+    <string name="estimatedSweatLoss">Odhadovan谩 ztr谩ta potu</string>
+    <string name="pref_time_sync">Automatick谩 synchronizace 膷asu</string>
+    <string name="user_feedback_set_settings_ok">Nastaven铆 odesl谩na do za艡铆zen铆.</string>
+    <string name="minutes_2">2 minuty</string>
+    <string name="minutes_3">3 minuty</string>
+    <string name="minutes_4">4 minuty</string>
+    <string name="minutes_6">6 minut</string>
+    <string name="minutes_7">7 minut</string>
+    <string name="minutes_8">8 minut</string>
+    <string name="minutes_9">9 minut</string>
+    <string name="minutes_15">15 minut</string>
+    <string name="minutes_45">45 minut</string>
+    <string name="minutes_75">75 minut</string>
+    <string name="minutes_90">90 minut</string>
+    <string name="minutes_120">120 minut</string>
+    <string name="minutes_150">150 minut</string>
+    <string name="minutes_180">180 minut</string>
+    <string name="minutes_210">210 minut</string>
+    <string name="dateformat_day_month">Den, m臎s铆c</string>
+    <string name="dateformat_month_day">M臎s铆c, den</string>
+    <string name="steps_distance_unit">%1$,.2f km</string>
+    <string name="devicetype_micompositionscale">Mi Body Composition Scale 2</string>
+    <string name="steps_total">Celkem krok暖</string>
+    <string name="steps_avg">Pr暖m臎rn臎 krok暖</string>
+    <string name="distance_avg">Pr暖m臎rn谩 vzd谩lenost</string>
+    <string name="distance_total">Celkov谩 vzd谩lenost</string>
+    <string name="prefs_light_duration_longer">Del拧铆 trv谩n铆 podsv铆cen铆</string>
+    <string name="miscale_weight_unit_title">Jednotka v谩hy</string>
+    <string name="miscale_weight_unit_summary">Nastavte jednotku v谩hy pro zobrazen谩 m臎艡en铆</string>
+    <string name="pref_app_connection_duration">D茅lka spojen铆 s aplikac铆</string>
+    <string name="miscale_weight_unit_metric">Metrick谩 (kg)</string>
+    <string name="minutes_240">240 minut</string>
+    <string name="miscale_weight_unit_imperial">Imperi谩ln铆 (lb)</string>
+    <string name="minutes_255">255 minut</string>
+    <string name="miscale_weight_unit_chinese">膶铆nsk谩 (jin)</string>
+    <string name="devicetype_mismartscale">Mi Smart Scale 2</string>
+    <string name="miscale_small_objects_title">Mal茅 objekty</string>
+    <string name="miscale_small_objects_summary">Ukl谩dat v谩hu objekt暖 leh膷铆ch ne啪 10 kg</string>
+    <string name="busy_task_fetch_sleep_data">Stahov谩n铆 dat o sp谩nku</string>
+    <string name="devicetype_colmi_r02">Colmi R02</string>
+    <string name="devicetype_colmi_r03">Colmi R03</string>
+    <string name="devicetype_colmi_r06">Colmi R06</string>
+    <string name="smart_ring_measurement_error_worn_incorrectly">Chyba m臎艡en铆. Jsou senzory prstenu spr谩vn臎 orientov谩ny?</string>
+    <string name="smart_ring_measurement_error_unknown">Nezn谩m谩 chyba m臎艡en铆 %d z铆skan谩 z prstenu</string>
+    <string name="stress_average">Pr暖m臎r</string>
+    <string name="devicetype_honor_watchgs3">Honor Watch GS 3</string>
+    <string name="devicetype_mijia_xmwsdj04">Xiaomi Mi Temperature and Humidity Monitor 2 (E-ink)</string>
+    <string name="menuitem_weight">V谩ha</string>
+    <string name="sleep_colored_stats_awake_avg">Pr暖m. vzh暖ru</string>
+    <string name="activity_type_rem_sleep">Sp谩nek REM</string>
+    <string name="lactateThresholdHeartRate">Lakt谩tov媒 pr谩h srde膷n铆 frekvence</string>
+    <string name="recoveryTime">Doba na zotaven铆</string>
+    <string name="target">C铆l</string>
+    <string name="devicetype_garmin_fenix_5x_plus">Garmin Fenix 5X Plus</string>
+    <string name="weight_lbs">%1$.2f lb</string>
+    <string name="weight_kg">%1$.2f kg</string>
+    <string name="pref_header_sound">Zvuk</string>
+    <string name="workoutSets">Sady</string>
+    <string name="workout_set_repetitions">%1d x</string>
+    <string name="workout_set_repetitions_weight_kg">%1d x %2$.2f kg</string>
+    <string name="workout_set_i">Sada %1d</string>
+    <string name="workout_set_repetitions_weight_lbs">%1d x %2$.2f lb</string>
+    <string name="menuitem_stress_breakdown">Stres (podrobn臎)</string>
+    <string name="menuitem_stress_simple">Stres (jednoduch媒)</string>
+    <string name="menuitem_stress_segmented">Stres (rozd臎len媒)</string>
+    <string name="choose_device">Vyberte za艡铆zen铆</string>
+    <string name="no_supported_devices_found">Nenalezena 啪谩dn谩 podporovan谩 za艡铆zen铆</string>
+    <string name="search">Hledat</string>
+    <string name="devicetype_soundcore_liberty4_nc">Soundcore Liberty 4 NC</string>
+    <string name="pref_deprecated_media_control_title">Zastaral茅 ovl谩d谩n铆 m茅di铆</string>
+    <string name="pref_deprecated_media_control_summary">Pos铆lat p艡铆kazy ovl谩d谩n铆 m茅di铆 jako ud谩losti, nam铆sto ovlada膷e m茅di铆.</string>
+    <string name="pref_header_deprecated_functionalities">Zastaral茅 funkce</string>
+    <string name="pref_header_deprecated_functionalities_warning">N谩sleduj铆c铆 funkce jsou zastaral茅 a budou z tohoto software brzy odstran臎ny.
+\nPokud pot艡ebujete jedno z n谩sleduj铆c铆ch nastaven铆 povolit, kontaktujte pros铆m projektov媒 t媒m.</string>
+    <string name="pref_title_nagivation_apps">Naviga膷n铆 aplikace</string>
+    <string name="pref_header_external_integrations">Extern铆 integrace</string>
+    <string name="pref_header_automations">Automatizace</string>
+    <string name="pref_description_general">Spou拧t臎n铆, jazyk, oblast, um铆st臎n铆</string>
+    <string name="pref_description_about_you">Datum narozen铆, pohlav铆, v媒拧ka, v谩ha, c铆le</string>
+    <string name="pref_description_notifications">Ozn谩men铆 aplikac铆, seznam povolen媒ch/zak谩zan媒ch</string>
+    <string name="pref_description_user_interface">Motiv, hlavn铆 obrazovka</string>
+    <string name="pref_description_dashboard">Widgety, za艡铆zen铆 k zahrnut铆</string>
+    <string name="pref_description_automations">Automatick媒 export, automatick茅 stahov谩n铆</string>
+    <string name="pref_title_user_interface">U啪ivatelsk茅 rozhran铆</string>
+    <string name="pref_description_developer_options">Protokoly, intent API</string>
+    <string name="pref_description_deprecated_functionalities">Nastaven铆, kter谩 budou odstran臎na v budouc铆ch verz铆ch</string>
+    <string name="pref_header_main_screen">Hlavn铆 obrazovka</string>
+    <string name="activity_prefs_date_birth">Datum narozen铆</string>
+    <string name="devicetype_garmin_forerunner_165">Garmin Forerunner 165</string>
+    <string name="prefs_title_gatt_client_allow_gatt_interactions">Povolit interakci GATT skrze BLE Intent API</string>
+    <string name="prefs_summary_gatt_client_notification_intents">Z铆sk谩vat zm臎ny charakteristiky BLE skrze Intenty</string>
+    <string name="prefs_summary_gatt_client_allow_gatt_interactions">Povolit pos铆l谩n铆 charakteristiky 膷ten铆/zapisov谩n铆 BLE a p艡铆kaz暖 p艡ipojov谩n铆</string>
+    <string name="prefs_summary_gatt_client_device_state_updates">Z铆sk谩vat zm臎ny stavu p艡ipojen铆 BLE skrze Intenty</string>
+    <string name="prefs_title_gatt_client_api_package">Bal铆膷ek API BLE</string>
+    <string name="prefs_summary_gatt_client_api_package">Omezit komunikaci BLE Intent API na tento bal铆膷ek</string>
+    <string name="prefs_title_ble_intent_api">BLE Intent API</string>
+    <string name="pref_continuous_skin_temperature_measurement_title">Pr暖b臎啪n茅 m臎艡en铆 teploty poko啪ky</string>
+    <string name="devicetype_ble_gatt_client">Generick媒 klient BLE GATT</string>
+    <string name="pref_continuous_skin_temperature_measurement_description">Z铆sk谩v谩n铆 teploty moment谩ln臎 nen铆 podporov谩no. Toto nastaven铆 pouze povol铆 pr暖b臎啪n茅 m臎艡en铆 na za艡铆zen铆</string>
+    <string name="prefs_title_gatt_client_notification_intents">Vys铆lat oznamovac铆 IntentyGATT skrze BLE Intent API</string>
+    <string name="activity_db_management_backup_restore_label">Z谩loha a obnoven铆</string>
+    <string name="activity_db_management_export_to_zip">Exportovat zip</string>
+    <string name="backup_restore_exporting_finishing">Dokon膷ov谩n铆 exportu鈥�</string>
+    <string name="backup_restore_importing">Importov谩n铆 ze souboru zip鈥�</string>
+    <string name="backup_restore_importing_files_i_of_n">Importov谩n铆 soubor暖鈥� %1d z %2d</string>
+    <string name="devicetype_honor_watchgspro">Honor Watch GS Pro</string>
+    <string name="activity_db_management_import_from_zip">Importovat zip</string>
+    <string name="activity_db_management_backup_restore_explanation">Operace importu/exportu umo啪艌uj铆 migrovat nebo z谩lohovat v拧echna nastaven铆 Gadgetbridge, za艡铆zen铆 a data do a ze souboru zip.
+\n
+\nImportov谩n铆m souboru odstran铆te v拧echna existuj铆c铆 data, za艡铆zen铆 a p艡edvolby a kompletn臎 je nahrad铆te z谩lohou.</string>
+    <string name="backup_restore_exporting">Exportov谩n铆 do souboru zip鈥�</string>
+    <string name="backup_restore_exporting_preferences">Exportov谩n铆 p艡edvoleb鈥�</string>
+    <string name="backup_restore_exporting_database">Exportov谩n铆 datab谩ze鈥�</string>
+    <string name="backup_restore_exporting_files">Exportov谩n铆 soubor暖鈥�</string>
+    <string name="backup_restore_exporting_files_i_of_n">Exportov谩n铆 soubor暖鈥� %1d z %2d</string>
+    <string name="backup_restore_do_not_exit">%s Ponechte tuto obrazovku otev艡enou a啪 do dokon膷en铆 operace.</string>
+    <string name="backup_restore_importing_loading">Na膷铆t谩n铆 souboru鈥�</string>
+    <string name="backup_restore_export_complete">Export dokon膷en</string>
+    <string name="backup_restore_import_complete">Import dokon膷en</string>
+    <string name="backup_restore_error_export">Export do zip selhal</string>
+    <string name="backup_restore_error_import">Import ze zip selhal</string>
+    <string name="backup_restore_abort_title">P艡eru拧it</string>
+    <string name="backup_restore_abort_export_confirmation">P艡eru拧it export? 膶谩ste膷n媒 soubor zip bude odstran臎n.</string>
+    <string name="backup_restore_abort_import_confirmation">P艡eru拧it import? M暖啪e to zp暖sobit po拧kozenou nebo nekonzistentn铆 datab谩zi.</string>
+    <string name="backup_restore_restart_summary">%1s se nyn铆 restartuje.</string>
+    <string name="backup_restore_importing_validating">Ov臎艡ov谩n铆 souboru鈥�</string>
+    <string name="backup_restore_importing_preferences">Importov谩n铆 p艡edvoleb鈥�</string>
+    <string name="backup_restore_restart_title">Restartovat</string>
+    <string name="backup_restore_importing_database">Importov谩n铆 datab谩ze鈥�</string>
+    <string name="backup_restore_warning_files">Nepoda艡ilo se obnovit %1d soubor暖:
+\n%2s</string>
+    <string name="devicetype_amazfit_trex_3">Amazfit T-Rex 3</string>
+    <string name="menuitem_map">Mapa</string>
+    <string name="menuitem_night_display">No膷n铆 zobrazen铆</string>
+    <string name="menuitem_heart_rate_push">Odeslat srde膷n铆 tep</string>
+    <string name="label_distance_trip">V媒let: %.1f km</string>
+    <string name="label_distance_total">Celkem: %.1f km</string>
+    <string name="label_distance_trip_mph">V媒let: %.1f mi</string>
+    <string name="error_no_cycling_sensor_found">nebyl nalezen 啪谩dn媒 cyklistick媒 senzor</string>
+    <string name="label_distance_total_mph">Celkem: %.1f mi</string>
+    <string name="devicetype_garmin_forerunner_245_music">Garmin Forerunner 245 Music</string>
+    <string name="devicetype_garmin_enduro_3">Garmin Enduro 3</string>
+    <string name="bpm_value_unit">%1$d tep暖/min</string>
+    <string name="devicetype_sony_wf_c500">Sony WF-C500</string>
+    <string name="hr_resting">Odpo膷inek</string>
+    <string name="hr_maximum">Maximum</string>
+    <string name="hr_minimum">Minimum</string>
+    <string name="hr_average">Pr暖m臎r</string>
+    <string name="devicetype_garmin_forerunner_955">Garmin Forerunner 955</string>
+    <string name="vo2max_running">VO鈧� Max p艡i b臎hu</string>
+    <string name="vo2max_cycling">VO鈧� Max p艡i cyklistice</string>
+    <string name="thirty_days_timeline">30denn铆 膷asov谩 osa</string>
+    <string name="devicetype_garmin_forerunner_265s">Garmin Forerunner 265S</string>
+    <string name="devicetype_miband9">Xiaomi Smart Band 9</string>
+    <string name="devicetype_redmi_watch_5_active">Redmi Watch 5 Active</string>
+    <string name="devicetype_huawei_watchgt5">Huawei Watch GT 5 (Pro)</string>
+    <string name="pref_summary_sync_birthdays">Synchronizovat narozeniny kontakt暖 spole膷n臎 s ud谩lostmi kalend谩艡e</string>
+    <string name="contact_birthday">Narozeniny kontaktu %1s</string>
+    <string name="birthdays">Narozeniny</string>
+    <string name="pref_title_sync_birthdays">Synchronizovat narozeniny</string>
+    <string name="devicetype_sony_wf_c700n">Sony WF-C700N</string>
+    <string name="pref_crash_notification_title">Ozn谩men铆 p艡i p谩du</string>
+    <string name="pref_crash_notification_summary">P艡i p谩du aplikace zobrazit ozn谩men铆 s chybou</string>
+    <string name="app_crash_share_stacktrace">Sd铆let chybu</string>
+    <string name="app_crash_notification_title">Aplikace %1s havarovala</string>
+    <string name="pref_dashboard_widget_today_yesterday_data_summary">Zobrazit tmav臎 data ze v膷erej拧ka mezi aktu谩ln铆m 膷asem a p暖lnoc铆</string>
+    <string name="pref_dashboard_widget_today_yesterday_data_title">脷daje ze v膷erej拧ka</string>
+    <string name="pref_dashboard_widget_today_time_indicator_title">Ukazatel aktu谩ln铆ho 膷asu</string>
+    <string name="pref_dashboard_widget_today_time_indicator_summary">Zobrazit ukazatel aktu谩ln铆ho 膷asu pro vizu谩ln铆 odd臎len铆 煤daj暖 ze v膷erej拧ka a dne拧ka</string>
+    <string name="pref_title_calendar_lookahead">Po膷et dn暖 vp艡ed</string>
+    <string name="pref_summary_calendar_lookahead">Synchronizovat do %1s dn铆 ud谩lost铆 v kalend谩艡i</string>
+    <string name="workout_set_reps">Opakov谩n铆</string>
+    <string name="hrZoneEasy">N铆zk谩</string>
+    <string name="hrZoneThreshold">Prahov谩 hodnota</string>
+    <string name="hrZoneMaximum">Maxim谩ln铆</string>
+    <string name="devicetype_huawei_watchgtcyber">Huawei Watch GT Cyber</string>
+    <string name="paceCorrection">Oprava</string>
+    <string name="activity_detail_share_json_details">Sd铆let podrobnosti v JSON</string>
+    <string name="number_selected_items">Vybr谩no %1d</string>
+    <string name="devicetype_garmin_venu_sq">Garmin Venu Sq</string>
+    <string name="devicetype_garmin_fenix_8">Garmin Fenix 8</string>
+    <string name="mijia_lywsd_comfort_temperature_title">Teplota (掳C)</string>
+    <string name="mijia_lywsd_comfort_temperature_summary">Doporu膷en媒 rozsah: 19 - 27</string>
+    <string name="mijia_lywsd_comfort_humidity_title">Vlhkost (%)</string>
+    <string name="mijia_lywsd_comfort_humidity_summary">Doporu膷en媒 rozsah: 20 - 85</string>
+    <string name="mijia_lywsd_comfort_lower">Spodn铆 limit</string>
+    <string name="date_placeholders__date__time">%1s, %1s</string>
+    <string name="mijia_lywsd_comfort_level_title">脷rove艌 pohodl铆</string>
+    <string name="date_placeholders__start_time__end_time">%1s - %2s</string>
+    <string name="devicetype_garmin_venu_sq_2">Garmin Venu Sq 2</string>
+    <string name="devicetype_garmin_fenix_6s_sapphire">Garmin Fenix 6S Sapphire</string>
+    <string name="mijia_lywsd_comfort_level_summary">Nastavte si omezen铆 teploty a vlhkosti pro nastaven茅 emotikony</string>
+    <string name="mijia_lywsd_comfort_upper">Horn铆 limit</string>
+    <string name="busy_task_fetch_hrv_data">Z铆sk谩v谩n铆 dat HRV</string>
+    <string name="pref_dnd_follow_phone_title">Podle nastaven铆 re啪imu Neru拧it telefonu</string>
+    <string name="pref_dnd_follow_phone_summary">Pokud je na telefonu povolen nebo zak谩z谩n re啪im Neru拧it, automaticky jej p艡epnout i na za艡铆zen铆</string>
+    <string name="inactivity_warnings_minimum_steps_title">Minim谩ln铆 po膷et krok暖</string>
+    <string name="min_respiration_rate">Min. rychlost d媒ch谩n铆</string>
+    <string name="breaths_per_min">n谩dech暖/min</string>
+    <string name="fmtPaceAverage">Pr暖m臎rn茅 tempo</string>
+    <string name="milliseconds_ms">ms</string>
+    <string name="average_respiration_rate">Rychlost d媒ch谩n铆</string>
+    <string name="max_respiration_rate">Max. rychlost d媒ch谩n铆</string>
+    <string name="hrv_sdrr">HRV SDRR</string>
+    <string name="hrv_rmssd">HRV RMSSD</string>
+    <string name="inactivity_warnings_minimum_steps_summary">Minim谩ln铆 po膷et krok暖, kter茅 je t艡eba ud臎lat b臎hem prahov媒ch minut</string>
+    <string name="prefs_hrv_monitoring_title">Sledov谩n铆 HRV</string>
+    <string name="prefs_hrv_monitoring_description">Automaticky sledovat variabilitu srde膷n铆 frekvence b臎hem dne</string>
+    <string name="devicetype_huawei_watchd2">Huawei Watch D2</string>
+    <string name="idasen_control_button_mid">MID</string>
+    <string name="devicetype_idasen">St暖l IKEA Idasen</string>
+    <string name="devicetype_colmi_r10">Colmi R10</string>
+    <string name="idasen_pref_stand_height">V媒拧ka stoj铆c铆 pozice (v centimetrech)</string>
+    <string name="idasen_control_button_sit">SIT</string>
+    <string name="idasen_control_button_stand">STAND</string>
+    <string name="idasen_pref_sit_height">V媒拧ka sed铆c铆 pozice (v centimetrech)</string>
+    <string name="idasen_pref_value_warning">Hodnota mus铆 b媒t mezi 62 a 126 cm</string>
+    <string name="idasen_pref_mid_height">V媒拧ka prost艡edn铆 pozice (v centimetrech)</string>
+    <string name="music_upload_info">Chyst谩te se nahr谩t n谩sleduj铆c铆 hudebn铆 soubor:\n\n%1$s\nN谩zev skladby: %2$s\nAlbum: %3$s\n</string>
+    <string name="devicetype_garmin_fenix_7">Garmin Fenix 7</string>
+    <string name="file_already_exists">Soubor ji啪 existuje</string>
+    <string name="devicetype_sony_wi_c100">Sony WI-C100</string>
+    <string name="devicetype_garmin_forerunner_235">Garmin Forerunner 235</string>
+    <string name="devicetype_bandw_pseries">S茅rie Bowers and Wilkins P</string>
+    <string name="devicetype_garmin_fenix_6s_pro">Garmin Fenix 6S Pro</string>
+    <string name="devicetype_huawei_band3pro">Huawei Band 3 (Pro)</string>
+    <string name="pref_voice_passthrough_level">脷rove艌 pr暖chodu</string>
+    <string name="pref_voice_passthrough_enabled">Pr暖choz铆</string>
+    <string name="pref_voice_passthrough_enabled_summary">Umo啪nit pr暖chod extern铆ch zvuk暖 k va拧emu uchu</string>
+    <string name="devicetype_garmin_forerunner_55">Garmin Forerunner 55</string>
+    <string name="devicetype_garmin_forerunner_620">Garmin Forerunner 620</string>
+    <string name="devicetype_oppo_enco_air">Oppo Enco Air</string>
+    <string name="pref_wear_sensor_summary">Rozpoznat, kdy啪 za艡铆zen铆 nen铆 no拧eno</string>
+    <string name="pref_wear_sensor_title">Senzor no拧en铆</string>
+    <string name="pref_developer_add_test_activities_title">P艡idat zku拧ebn铆 aktivity</string>
+    <string name="pref_developer_add_test_activities_summary">Naplnit datab谩zi fiktivn铆mi zku拧ebn铆mi aktivitami</string>
+    <string name="pref_music_management_title">Spravovat hudbu</string>
+    <string name="pref_music_management_summary">Spravovat hudbu na hodink谩ch</string>
+    <string name="music_delete_confirm_description">Opravdu chcete odstranit 鈥�%1$s鈥�?</string>
+    <string name="music_add_to_playlist">P艡idat do playlistu</string>
+    <string name="music_all_songs">V拧echny skladby</string>
+    <string name="title_activity_musicmanager">Spr谩vce hudby</string>
+    <string name="music_error">Do拧lo k chyb臎</string>
+    <string name="music_delete_from_playlist">Odstranit z playlistu</string>
+    <string name="music_huawei_device_info">Podporovan茅 form谩ty: %1$s\n脷lo啪i拧t臎 hodinek: %2$d MB</string>
+    <string name="music_delete">Odstranit skladbu</string>
+    <string name="music_new_playlist">Nov媒 playlist</string>
+    <string name="music_rename_playlist">P艡ejmenovat playlist</string>
+    <string name="devicetype_realme_buds_t110">Realme Buds T110</string>
+    <string name="devicetype_garmin_instinct_2">Garmin Instinct 2</string>
+    <string name="active_calories_short">Aktivn铆</string>
+    <string name="active_calories_goal">Aktivn铆 c铆l</string>
+    <string name="total_calories_burnt">Celkem sp谩leno</string>
+    <string name="permission_bluetooth_scan_title">Bluetooth sken</string>
+    <string name="permission_contacts_title">Kontakty</string>
+    <string name="menuitem_calories_segmented">Kalorie (膷len臎n茅)</string>
+    <string name="first_start_get_started_add_first_device_button">P艡idat prvn铆 za艡铆zen铆</string>
+    <string name="first_start_get_started_restore_button">Obnovit z谩lohu</string>
+    <string name="permission_manage_dnd_summary">Zm臎na nastaven铆 ozn谩men铆 v re啪imu Neru拧it</string>
+    <string name="menuitem_calories_active_goal">C铆l kalori铆 (aktivn铆)</string>
+    <string name="first_start_overview_devices">Zobrazen铆 za艡铆zen铆 zobrazuje v拧echna nastaven谩 za艡铆zen铆 a jejich stav a umo啪艌uje p艡铆stup ke specifick媒m funkc铆m za艡铆zen铆, jako jsou podrobn茅 grafy, nastaven铆, aplikace a bud铆ky.</string>
+    <string name="first_start_overview_desc">Gadgetbridge m谩 dva hlavn铆 pohledy, ka啪d媒 s vlastn铆m 煤膷elem.</string>
+    <string name="first_start_open_source_text">Gadgetbridge je aplikace s otev艡en媒m zdrojov媒m k贸dem. Je vyv铆jena komunitou pro komunitu.\n\nKa啪d媒 m暖啪e p艡isp臎t prost艡ednictv铆m k贸du, dokumentace, testov谩n铆 a dar暖.\n\nGadgetbridge neobsahuje 啪谩dn茅 reklamy ani sledov谩n铆. Va拧e data uchov谩v谩 lok谩ln臎 ve va拧em za艡铆zen铆, tak啪e je 100% p艡铆v臎tiv媒 k va拧emu soukrom铆.\n\nNav拧tivte na拧e webov茅 str谩nky, kde najdete dal拧铆 informace, dokumentaci a odkazy na na拧e komunika膷n铆 kan谩ly.</string>
+    <string name="permission_bluetooth_admin_title">Spr谩va Bluetooth</string>
+    <string name="permission_bluetooth_title">Bluetooth</string>
+    <string name="permission_bluetooth_summary">P艡ipojen铆 k Bluetooth za艡铆zen铆m</string>
+    <string name="permission_bluetooth_admin_summary">Objevov谩n铆 a p谩rov谩n铆 Bluetooth za艡铆zen铆</string>
+    <string name="permission_bluetooth_scan_summary">Vyhled谩v谩n铆 nov媒ch Bluetooth za艡铆zen铆</string>
+    <string name="permission_bluetooth_connect_title">P艡ipojen铆 k Bluetooth</string>
+    <string name="permission_internet_access_title">P艡铆stup k internetu</string>
+    <string name="permission_contacts_summary">Odes铆l谩n铆 kontakt暖 do gadget暖</string>
+    <string name="permission_read_phone_state_summary">Zji拧钮ov谩n铆 stavu odchoz铆ch hovor暖</string>
+    <string name="permission_external_storage_title">Extern铆 煤lo啪i拧t臎</string>
+    <string name="permission_external_storage_summary">Pou啪铆v谩n铆 obr谩zk暖, vyzv谩n臎n铆, soubor暖 aplikac铆 a dal拧铆ch</string>
+    <string name="first_start_welcome_title">V铆tejte</string>
+    <string name="first_start_intro_welcome_to">V铆tejte v</string>
+    <string name="first_start_intro_tag_line">Osvobo膹te se od propriet谩rn铆ch aplikac铆 a cloudov媒ch slu啪eb dodavatel暖 gadget暖.</string>
+    <string name="first_start_overview_title">P艡ehled</string>
+    <string name="first_start_overview_dashboard">Pomoc铆 n谩st臎nky z铆sk谩te rychl媒 p艡ehled o tom, jak se v谩m dnes da艡铆. Zobrazen铆 kalend谩艡e ukazuje stav va拧ich c铆l暖 za cel媒 m臎s铆c.</string>
+    <string name="first_start_open_source_title">Open Source</string>
+    <string name="first_start_permissions_desc">Gadgetbridge pot艡ebuje k prov谩d臎n铆 v拧ech sv媒ch funkc铆 velk茅 mno啪stv铆 opr谩vn臎n铆. N铆啪e si prohl茅dn臎te jednotliv谩 opr谩vn臎n铆 a jejich 煤膷el.</string>
+    <string name="first_start_permissions_request_all_button">Vy啪谩dat v拧echna opr谩vn臎n铆</string>
+    <string name="first_start_permissions_request_button">Vy啪谩dat</string>
+    <string name="first_start_get_started_title">Za膷铆n谩me</string>
+    <string name="first_start_get_started_desc">Chcete-li za膷铆t, p艡idejte prvn铆 za艡铆zen铆 p艡铆mo z t茅to obrazovky, obnovte z谩lohu nebo za膷n臎te s 膷istou datab谩z铆.</string>
+    <string name="permission_notifications_summary">P艡ed谩v谩n铆 ozn谩men铆 p艡ipojen媒m gadget暖m</string>
+    <string name="permission_manage_dnd_title">Spr谩va re啪imu Neru拧it</string>
+    <string name="permission_displayover_summary">Pou啪铆v谩no gadgetem Bangle.js ke spou拧t臎n铆 aplikac铆 a dal拧铆 funkcionality na va拧em za艡铆zen铆</string>
+    <string name="permission_fine_location_title">P艡esn谩 poloha</string>
+    <string name="permission_fine_location_summary">Vyhled谩v谩n铆 Bluetooth za艡铆zen铆</string>
+    <string name="permission_background_location_summary">Vyhled谩v谩n铆 Bluetooth za艡铆zen铆 na pozad铆 a odes铆l谩n铆 polohy do ur膷it媒ch gadget暖</string>
+    <string name="permission_bluetooth_connect_summary">P艡ipojen铆 k ji啪 sp谩rovan媒m Bluetooth za艡铆zen铆m</string>
+    <string name="permission_post_notification_title">Odes铆lat ozn谩men铆</string>
+    <string name="permission_post_notification_summary">Zas铆l谩n铆 pr暖b臎啪n媒ch ozn谩men铆, kter谩 udr啪uj铆 slu啪bu v chodu</string>
+    <string name="permission_internet_access_summary">Synchronizace s online zdroji</string>
+    <string name="permission_calendar_title">Kalend谩艡</string>
+    <string name="permission_calendar_summary">Odes铆l谩n铆 kalend谩艡e do gadget暖</string>
+    <string name="permission_receive_sms_title">Z铆sk谩vat SMS</string>
+    <string name="permission_receive_sms_summary">P艡epos铆l谩n铆 zpr谩v SMS do gadget暖</string>
+    <string name="permission_send_sms_title">Pos铆lat SMS</string>
+    <string name="permission_read_call_log_title">膶铆st seznamy hovor暖</string>
+    <string name="permission_read_call_log_summary">P艡ed谩v谩n铆 seznamu hovor暖 do gadget暖</string>
+    <string name="permission_read_phone_state_title">Zjistit stav telefonu</string>
+    <string name="permission_call_phone_title">Volat</string>
+    <string name="permission_process_outgoing_calls_title">Zpracov谩vat odchoz铆 hovory</string>
+    <string name="permission_send_sms_summary">Pos铆l谩n铆 zpr谩v SMS (p艡edem nastaven媒ch) z gadget暖</string>
+    <string name="permission_call_phone_summary">Prov谩d臎n铆 hovor暖 z gadget暖</string>
+    <string name="permission_process_outgoing_calls_summary">Zji拧钮ov谩n铆 膷铆sla odchoz铆ho hovoru k zobrazen铆 na gadgetu</string>
+    <string name="permission_answer_phone_calls_title">P艡ij铆mat hovory</string>
+    <string name="permission_answer_phone_calls_summary">P艡ij铆m谩n铆 telefonn铆ch hovor暖 z gadget暖</string>
+    <string name="permission_query_all_packages_title">Z铆skat seznam v拧ech bal铆膷k暖</string>
+    <string name="permission_query_all_packages_summary">Zji拧钮ov谩n铆 n谩zv暖 a ikon nainstalovan媒ch aplikac铆</string>
+    <string name="active_calories">Aktivn铆 kalorie</string>
+    <string name="activity_prefs_goal_active_calories_burnt">Denn铆 c铆l: sp谩leno aktivn铆ch kalori铆</string>
+    <string name="first_start_permissions_title">Opr谩vn臎n铆</string>
+    <string name="permission_displayover_title">Zobrazovat p艡es ostatn铆 aplikace</string>
+    <string name="first_start_get_started_go_to_app_button">P艡ej铆t do aplikace</string>
+    <string name="permission_background_location_title">Poloha na pozad铆</string>
+    <string name="devicetype_oppo_enco_air2">Oppo Enco Air2</string>
+    <string name="redmi_buds_5_pro_combo_transparency_off">Pr暖chodnost / vypnuta</string>
+    <string name="redmi_buds_5_pro_anc_balanced">Vyv谩啪en茅</string>
+    <string name="devicetype_redmi_buds_5_pro">Redmi Buds 5 Pro</string>
+    <string name="redmi_buds_5_pro_anc_light">Lehk茅</string>
+    <string name="redmi_buds_5_pro_transparency_ambient">Zv媒raznit ambientn铆 zvuky</string>
+    <string name="redmi_buds_5_pro_combo_anc_transparency">ANC / pr暖chodnost</string>
+    <string name="redmi_buds_5_pro_combo_all">ANC / pr暖chodnost / vypnuta</string>
+    <string name="redmi_buds_5_pro_double_connection_description">Povolit sluch谩tk谩m p艡ipojit se ke dv臎ma za艡铆zen铆m z谩rove艌</string>
+    <string name="redmi_buds_5_pro_anc_deep">Hlubok茅</string>
+    <string name="redmi_buds_5_pro_transparency_strength">S铆la pr暖chodnosti</string>
+    <string name="redmi_buds_5_pro_transparency_regular">B臎啪n谩</string>
+    <string name="redmi_buds_5_pro_combo_anc_off">ANC / vypnuto</string>
+    <string name="redmi_buds_5_pro_transparency_voice">Zv媒raznit hlasy</string>
+    <string name="redmi_buds_5_pro_double_connection">Dvojit茅 p艡ipojen铆</string>
+    <string name="redmi_buds_5_pro_adaptive_sound">Adaptivn铆 zvuk</string>
+    <string name="redmi_buds_5_pro_equalizer_preset_standard">Standardn铆</string>
+    <string name="redmi_buds_5_pro_equalizer_preset_treble">Zv媒raznit v媒拧ky</string>
+    <string name="redmi_buds_5_pro_equalizer_preset_bass">Zv媒raznit basy</string>
+    <string name="redmi_buds_5_pro_equalizer_preset_custom">Vlastn铆</string>
+    <string name="redmi_buds_5_pro_equalizer_band_62">62 Hz</string>
+    <string name="redmi_buds_5_pro_equalizer_band_500">500 Hz</string>
+    <string name="redmi_buds_5_pro_equalizer_band_1k">1 kHz</string>
+    <string name="redmi_buds_5_pro_equalizer_band_2k">2 kHz</string>
+    <string name="redmi_buds_5_pro_equalizer_band_12k">12 kHz</string>
+    <string name="redmi_buds_5_pro_equalizer_band_16k">16 kHz</string>
+    <string name="redmi_buds_5_pro_equalizer_neg5">-5 dB</string>
+    <string name="redmi_buds_5_pro_equalizer_neg4">-4 dB</string>
+    <string name="redmi_buds_5_pro_equalizer_neg2">-2 dB</string>
+    <string name="redmi_buds_5_pro_equalizer_neg1">-1 dB</string>
+    <string name="redmi_buds_5_pro_equalizer_zero">0 dB</string>
+    <string name="redmi_buds_5_pro_equalizer_2">2 dB</string>
+    <string name="redmi_buds_5_pro_equalizer_5">5 dB</string>
+    <string name="redmi_buds_5_pro_equalizer_6">6 dB</string>
+    <string name="redmi_buds_5_pro_adaptive_sound_description">P艡izp暖sob铆 zvuk podle tvaru ucha a prost艡ed铆</string>
+    <string name="devicetype_miband9pro">Xiaomi Smart Band 9 Pro</string>
+    <string name="redmi_buds_5_pro_equalizer_preset_voice">Zv媒raznit hlas</string>
+    <string name="redmi_buds_5_pro_equalizer_neg3">-3 dB</string>
+    <string name="redmi_buds_5_pro_equalizer_band_125">125 Hz</string>
+    <string name="redmi_buds_5_pro_equalizer_neg6">-6 dB</string>
+    <string name="redmi_buds_5_pro_equalizer_band_250">250 Hz</string>
+    <string name="redmi_buds_5_pro_equalizer_band_4k">4 kHz</string>
+    <string name="redmi_buds_5_pro_equalizer_band_8k">8 kHz</string>
+    <string name="redmi_buds_5_pro_equalizer_1">1 dB</string>
+    <string name="redmi_buds_5_pro_equalizer_4">4 dB</string>
+    <string name="redmi_buds_5_pro_equalizer_3">3 dB</string>
+    <string name="music_multiselect_limit">Dosa啪en limit pro v媒b臎r v铆ce ne啪 jedn茅 polo啪ky</string>
+    <string name="respiratoryrate">Rychlost d媒ch谩n铆</string>
+    <string name="lowest">Nejni啪拧铆</string>
+    <string name="sleep_avg">Pr暖m. sp谩nku</string>
+    <string name="music_delete_multiple_confirm_description">Opravdu chcete odstranit skladby 鈥�%1$d鈥�?</string>
+    <string name="highest">Nejvy拧拧铆</string>
+    <string name="sleep_score">Sp谩nkov茅 sk贸re</string>
+    <string name="sleep_score_value">Sk贸re: %1d</string>
+    <string name="devicetype_colmi_r09">Colmi R09</string>
+    <string name="watchface_widget_type_sp02">SpO2</string>
+    <string name="devicetype_huawei_band2pro">Huawei Band 2 (Pro)</string>
+    <string name="devicetype_huawei_freebuds_5i">Huawei FreeBuds 5i</string>
+    <string name="Jumps">V媒skoky</string>
+    <string name="jumps_minute">v媒skok暖/min</string>
+    <string name="jumps_unit">v媒skok暖</string>
+    <string name="totalJumps">Celkem v媒skok暖</string>
+    <string name="avgJumpRate">Pr暖m臎rn谩 frekvence v媒skok暖</string>
+    <string name="maxJumpRate">Maxim谩ln铆 frekvence v媒skok暖</string>
+    <string name="devicetype_garmin_fenix_7x">Garmin Fenix 7X</string>
+    <string name="devicetype_redmi_watch_5_lite">Redmi Watch 5 Lite</string>
+    <string name="garmin_intensity_minutes">Intenzivn铆 minuty</string>
+    <string name="stepRateMax">Maxim谩ln铆 rychlost krok暖</string>
+    <string name="weekly_total">T媒denn铆 souhrn</string>
+    <string name="devicetype_casioecbs100">Casio ECB-S100</string>
+    <string name="battery_discharge_intervals">Intervaly vyb铆jen铆 baterie</string>
+    <string name="discharge_interval_1">Interval vyb铆jen铆 1</string>
+    <string name="discharge_interval_2">Interval vyb铆jen铆 2</string>
+    <string name="discharge_interval_3">Interval vyb铆jen铆 3</string>
+    <string name="discharge_interval_4">Interval vyb铆jen铆 4</string>
+    <string name="power_w">V媒kon ve W</string>
+    <string name="manual_discharge">Ru膷n铆 intervaly vyb铆jen铆</string>
+    <string name="summary_battery_discharge_intervals_set">Odeslat konfiguraci n铆啪e do za艡铆zen铆</string>
+    <string name="battery_minimum_charge">Minim谩ln铆 nabit铆 baterie v %</string>
+    <string name="battery_allow_pass_though_summary">Pokud je zapnuto, lze baterii nab铆jet a z谩rove艌 vyb铆jet</string>
+    <string name="battery_allow_pass_through">Umo啪nit pr暖chod bateri铆</string>
+    <string name="devicetype_marstek_b2500">Marstek B2500</string>
+    <string name="about_build_details_copied_to_clipboard">Podrobnosti o sestaven铆 zkop铆rov谩ny do schr谩nky</string>
+    <string name="discharge_interval_5">Interval vyb铆jen铆 5</string>
+    <string name="manual_discharge_summary">pokud je vypnuto, p艡edpokl谩d谩 se inteligentn铆 vyb铆jen铆 艡铆zen茅 extern铆m m臎艡i膷em spot艡eby (nen铆 podporov谩no aplikac铆 Gadgetbridge)</string>
+</resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
new file mode 100644
index 0000000..1775cbc
--- /dev/null
+++ b/app/src/main/res/values-de/strings.xml
@@ -0,0 +1,3414 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources xmlns:tools="http://schemas.android.com/tools">
+    <string name="application_name_generic">Gadgetbridge</string>
+    <string name="title_activity_controlcenter_generic">Gadgetbridge</string>
+    <string name="action_settings">Einstellungen</string>
+    <string name="action_debug">Debug</string>
+    <string name="action_quit">Beenden</string>
+    <string name="action_donate">Spenden</string>
+    <string name="controlcenter_fetch_activity_data">Synchronisieren</string>
+    <string name="controlcenter_find_device">Verlorenes Ger盲t finden</string>
+    <string name="controlcenter_take_screenshot">Screenshot erstellen</string>
+    <string name="controlcenter_disconnect">Trennen</string>
+    <string name="controlcenter_delete_device">Ger盲t l枚schen</string>
+    <string name="controlcenter_delete_device_name">%1$s l枚schen</string>
+    <string name="controlcenter_delete_device_dialogmessage">Dadurch werden das Ger盲t und alle zugeh枚rigen Daten gel枚scht!</string>
+    <string name="controlcenter_navigation_drawer_open">Navigationsmen眉 枚ffnen</string>
+    <string name="controlcenter_navigation_drawer_close">Navigationsmen眉 schlie脽en</string>
+    <string name="controlcenter_snackbar_need_longpress">Langes Dr眉cken der Karte trennt die Verbindung</string>
+    <string name="controlcenter_snackbar_disconnecting">Trennen</string>
+    <string name="controlcenter_snackbar_connecting">Verbinden鈥�</string>
+    <string name="controlcenter_snackbar_requested_screenshot">Einen Screenshot des Ger盲tes erstellen</string>
+    <string name="title_activity_debug">Debug</string>
+    <!--Strings related to AppManager-->
+    <string name="title_activity_appmanager">App-Manager</string>
+    <string name="appmanager_cached_watchapps_watchfaces">Apps im Zwischenspeicher</string>
+    <string name="appmanager_installed_watchapps">Installierte Apps</string>
+    <string name="appmanager_installed_watchfaces">Installierte Zifferbl盲tter</string>
+    <string name="appmananger_app_delete">L枚schen</string>
+    <string name="appmananger_app_delete_cache">L枚schen und aus dem Zwischenspeicher entfernen</string>
+    <string name="appmananger_app_reinstall">Erneut installieren</string>
+    <string name="appmanager_app_openinstore">Im Pebble App Store suchen</string>
+    <string name="appmanager_health_activate">Aktivieren</string>
+    <string name="appmanager_health_deactivate">Deaktivieren</string>
+    <string name="appmanager_hrm_activate">HRM aktivieren</string>
+    <string name="appmanager_hrm_deactivate">HRM deaktivieren</string>
+    <string name="appmanager_weather_activate">Aktiviere die Wetter-App des Systems</string>
+    <string name="appmanager_weather_deactivate">Deaktiviere die Wetter-App des Systems</string>
+    <string name="appmanager_weather_install_provider">App \"Weather Notification\" installieren</string>
+    <string name="app_configure">Konfigurieren</string>
+    <string name="app_move_to_top">Nach oben</string>
+    <!--Strings related to CalBlacklist-->
+    <string name="title_activity_calblacklist">Kalender auf Blacklist</string>
+    <!--Strings related to FwAppInstaller-->
+    <string name="title_activity_fw_app_insaller">FW/App-Installer</string>
+    <string name="fw_upgrade_notice">Du bist dabei, die %s zu installieren.</string>
+    <string name="fw_multi_upgrade_notice">Du bist dabei anstelle der aktuell auf dem Mi Band vorhandenen Firmware, die Firmware %1$s und %2$s zu installieren.</string>
+    <string name="miband_firmware_known">Diese Firmware wurde getestet und ist bekanntlich mit Gadgetbridge kompatibel.</string>
+    <string name="miband_firmware_unknown_warning">Diese Firmware ist nicht getestet und ist m枚glicherweise nicht mit Gadgetbridge kompatibel.
+\n
+\nDu wirst GEBETEN, diese nicht zu flashen!</string>
+    <string name="miband_firmware_suggest_whitelist">Wenn du dennoch fortfahren m枚chtest und das Ger盲t anschlie脽end korrekt funktioniert, melde bitte den Gadgetbridge-Entwicklern, das sie die Firmwareversion %s auf die Whitelist setzen sollen.</string>
+    <!--Strings related to Settings-->
+    <string name="title_activity_settings">Einstellungen</string>
+    <string name="pref_header_general">Allgemeine Einstellungen</string>
+    <string name="pref_title_general_autoconnectonbluetooth">Verbindung mit Gadgetbridge-Ger盲t(en) herstellen, wenn Bluetooth eingeschaltet ist</string>
+    <string name="pref_title_general_autostartonboot">Automatisch starten</string>
+    <string name="pref_title_general_autoreconnect">Verbindung automatisch wiederherstellen</string>
+    <string name="pref_title_audio_player">Bevorzugter Audioplayer</string>
+    <string name="pref_default">Standard</string>
+    <string name="pref_header_datetime">Datum und Uhrzeit</string>
+    <string name="pref_title_datetime_syctimeonconnect">Uhrzeit synchronisieren</string>
+    <string name="pref_summary_datetime_syctimeonconnect">Synchronisierung der Zeit mit dem/den Gadgetbridge-Ger盲t(en), wenn eine Verbindung hergestellt wird, und wenn sich die Zeit oder die Zeitzone auf dem Android-Ger盲t 盲ndert, sowie in regelm盲脽igen Abst盲nden</string>
+    <string name="pref_title_theme">Farbschema</string>
+    <string name="pref_theme_light">Hell</string>
+    <string name="pref_theme_dark">Dunkel</string>
+    <string name="pref_title_language">Sprache</string>
+    <string name="pref_title_minimize_priority">Gadgetbridge-Benachrichtigung ausblenden</string>
+    <string name="pref_summary_minimize_priority_off">Das Symbol in der Statusleiste und die Benachrichtigung auf dem Sperrbildschirm werden angezeigt</string>
+    <string name="pref_summary_minimize_priority_on">Das Symbol in der Statusleiste und die Benachrichtigung auf dem Sperrbildschirm werden nicht angezeigt</string>
+    <string name="pref_header_notifications">Benachrichtigungen</string>
+    <string name="pref_title_notifications_repetitions">Wiederholungen</string>
+    <string name="pref_title_notifications_call">Telefonanrufe</string>
+    <string name="pref_title_notifications_sms">SMS</string>
+    <string name="pref_title_notifications_pebblemsg">Pebble-Nachrichten</string>
+    <string name="pref_summary_notifications_pebblemsg">Unterst眉tzung f眉r Apps, die Benachrichtigungen an die Pebble via PebbleKit senden.</string>
+    <string name="pref_title_notifications_generic">Unterst眉tzung allgemeine Benachrichtigung</string>
+    <string name="pref_title_whenscreenon">鈥uch wenn der Bildschirm an ist</string>
+    <string name="pref_title_notification_filter">Bitte nicht st枚ren</string>
+    <string name="pref_summary_notification_filter">Alle Benachrichtigungen blockieren, wenn \"Nicht st枚ren\" auf dem Telefon aktiviert ist</string>
+    <string name="pref_title_transliteration">Sonderzeichen umschreiben</string>
+    <string name="pref_summary_transliteration">Aktiviere diese Option, wenn dein Ger盲t die Schriftart f眉r deine Sprache nicht unterst眉tzt</string>
+    <string name="always">Immer</string>
+    <string name="when_screen_off">Wenn der Bildschirm aus ist</string>
+    <string name="never">Niemals</string>
+    <string name="pref_header_privacy">Privatsph盲re</string>
+    <string name="pref_title_call_privacy_mode">Anzeige bei Anrufen</string>
+    <string name="pref_call_privacy_mode_off">Name und Telefonnummer anzeigen</string>
+    <string name="pref_call_privacy_mode_name">Name ausblenden, aber Nummer anzeigen</string>
+    <string name="pref_call_privacy_mode_number">Nummer ausblenden, aber Name anzeigen</string>
+    <string name="pref_call_privacy_mode_complete">Name und Telefonnummer ausblenden</string>
+    <string name="pref_blacklist_calendars">Blockierte Kalender</string>
+    <string name="pref_header_cannned_messages">Gespeicherte Nachrichten</string>
+    <string name="pref_title_canned_replies">Antworten</string>
+    <string name="pref_title_canned_reply_suffix">Gemeinsame Endung</string>
+    <string name="pref_title_canned_messages_dismisscall">Anrufabweisung</string>
+    <string name="pref_title_canned_messages_set">Aktualisierung auf dem Ger盲t</string>
+    <string name="pref_header_development">Entwickleroptionen</string>
+    <string name="pref_title_development_miaddr">Mi Band-Adresse</string>
+    <string name="pref_title_pebble_settings">Pebble-Einstellungen</string>
+    <string name="pref_header_activitytrackers">Aktivit盲ts-Tracker</string>
+    <string name="pref_title_pebble_activitytracker">Bevorzugter Aktivit盲ts-Tracker</string>
+    <string name="pref_title_pebble_sync_health">Pebble Gesundheit synchronisieren</string>
+    <string name="pref_title_pebble_sync_misfit">Misfit synchronisieren</string>
+    <string name="pref_title_pebble_sync_morpheuz">Morpheuz synchronisieren</string>
+    <string name="pref_title_enable_outgoing_call">Unterst眉tzung f眉r ausgehende Anrufe</string>
+    <string name="pref_summary_enable_outgoing_call">Deaktivieren hindert auch die Pebble 2/LE daran, bei ausgehenden Anrufen zu vibrieren</string>
+    <string name="pref_title_enable_pebblekit">Zugriff auf Android Drittanbieter-Apps zulassen</string>
+    <string name="pref_summary_enable_pebblekit">Experimentelle Unterst眉tzung f眉r Android-Apps mit PebbleKit aktivieren</string>
+    <string name="pref_header_pebble_timeline">Pebble-Zeitleiste</string>
+    <string name="pref_title_sunrise_sunset">Sonnenauf- und -untergang</string>
+    <string name="pref_summary_sunrise_sunset">Sonnenauf- und -untergangszeiten abh盲ngig vom Standort auf die Pebble-Zeitleiste senden</string>
+    <string name="pref_title_enable_calendar_sync">Kalender synchronisieren</string>
+    <string name="pref_summary_enable_calendar_sync">Kalenderereignisse an die Zeitleiste senden</string>
+    <string name="pref_title_autoremove_notifications">Verpasste Benachrichtigungen automatisch entfernen</string>
+    <string name="pref_summary_autoremove_notifications">Benachrichtigungen werden automatisch vom Ger盲t entfernt, wenn sie vom Telefon entfernt werden</string>
+    <string name="pref_title_pebble_privacy_mode">Datenschutzmodus</string>
+    <string name="pref_pebble_privacy_mode_off">Normale Benachrichtigungen</string>
+    <string name="pref_pebble_privacy_mode_content">Benachrichtigungstext au脽erhalb des Bildschirms verschieben</string>
+    <string name="pref_pebble_privacy_mode_complete">Nur das Benachrichtigungssymbol anzeigen</string>
+    <string name="pref_header_location">Standort</string>
+    <string name="pref_title_location_aquire">Standort ermitteln</string>
+    <string name="pref_title_location_latitude">Breitengrad</string>
+    <string name="pref_title_location_longitude">L盲ngengrad</string>
+    <string name="pref_title_location_keep_uptodate">Standort aktuell halten</string>
+    <string name="pref_summary_location_keep_uptodate">Versuche, den aktuellen Standort zu ermitteln und nutze den gespeicherten Standort, falls das fehlschl盲gt</string>
+    <string name="toast_enable_networklocationprovider">Bitte Netzwerkstandort aktivieren</string>
+    <string name="toast_aqurired_networklocation">Standort wurde ermittelt</string>
+    <string name="pref_title_pebble_forceprotocol">Benachrichtigungsprotokoll erzwingen</string>
+    <string name="pref_summary_pebble_forceprotocol">Diese Option erzwingt das neuste Benachrichtigungsprotokoll abh盲ngig von der Firmwareversion. NUR EINSCHALTEN, WENN DU WEI岷濼, WAS DU TUST!</string>
+    <string name="pref_title_pebble_forceuntested">Ungetestete Features aktivieren</string>
+    <string name="pref_summary_pebble_forceuntested">Aktiviere ungetestete Features. NUR EINSCHALTEN, WENN DU WEI岷濼, WAS DU TUST!</string>
+    <string name="pref_title_pebble_forcele">BLE immer bevorzugen</string>
+    <string name="pref_summary_pebble_forcele">Nutze die experimentelle LE-Unterst眉tzung f眉r alle Pebbles anstelle von BT-Classic. Dies erfordert zuerst eine Kopplung mit Nicht-LE und dann Pebble LE</string>
+    <string name="pref_title_pebble_mtu_limit">Pebble 2/LE GATT MTU Limit</string>
+    <string name="pref_summary_pebble_mtu_limit">Wenn deine Pebble 2/Pebble LE nicht so wie erwartet funktioniert, versuche die MTU zu begrenzen (erlaubte Werte 20 bis 512)</string>
+    <string name="pref_title_pebble_enable_applogs">Anwendungsprotokollierung der Armbanduhr aktivieren</string>
+    <string name="pref_summary_pebble_enable_applogs">Schreibt Protokolle von Armbanduhranwendungen in Gadgetbridge-Protokolle (erfordert erneute Verbindung)</string>
+    <string name="pref_title_pebble_always_ack_pebblekit">PebbleKit Nachrichten direkt best盲tigen</string>
+    <string name="pref_summary_pebble_always_ack_pebblekit">Nachrichten, die an externe Drittanbieteranwendungen geschickt werden, immer und sofort best盲tigen</string>
+    <string name="pref_title_pebble_reconnect_attempts">Wiederholte Verbindungsversuche</string>
+    <string name="pref_title_unit_system">Einheiten</string>
+    <string name="pref_title_timeformat">Zeitformat</string>
+    <string name="pref_title_screentime">Zeit der aktiven Bildschirmanzeige</string>
+    <string name="prefs_title_all_day_heart_rate">Ganzt盲gige Herzfrequenzmessung</string>
+    <string name="preferences_hplus_settings">HPlus/Makibes-Einstellungen</string>
+    <string name="not_connected">Nicht verbunden</string>
+    <string name="connecting">Verbinde</string>
+    <string name="connected">Verbunden</string>
+    <string name="unknown_state">Unbekannter Zustand</string>
+    <string name="_unknown_">(unbekannt)</string>
+    <string name="test">Test</string>
+    <string name="test_notification">Testbenachrichtigung</string>
+    <string name="this_is_a_test_notification_from_gadgetbridge">Dies ist eine Testbenachrichtigung von Gadgetbridge</string>
+    <string name="bluetooth_is_not_supported_">Bluetooth wird nicht unterst眉tzt.</string>
+    <string name="bluetooth_is_disabled_">Bluetooth ist deaktiviert.</string>
+    <string name="tap_connected_device_for_app_mananger">Tippe auf das verbundene Ger盲t, um den App-Manager zu starten</string>
+    <string name="tap_connected_device_for_activity">Tippe auf das verbundene Ger盲t, um die Aktivit盲tsdaten anzuzeigen</string>
+    <string name="tap_connected_device_for_vibration">Tippe auf das verbundene Ger盲t, um es vibrieren zu lassen</string>
+    <string name="tap_a_device_to_connect">Tippe auf ein Ger盲t, um eine Verbindung herzustellen</string>
+    <string name="cannot_connect_bt_address_invalid_">Keine Verbindung m枚glich. Bluetooth-Adresse ung眉ltig\?</string>
+    <string name="gadgetbridge_running_generic">Gadgetbridge l盲uft</string>
+    <string name="installing_binary_d_d">Bin盲rdatei %1$d/%2$d wird installiert</string>
+    <string name="installation_failed_">Installation fehlgeschlagen</string>
+    <string name="installation_successful">Installiert</string>
+    <string name="firmware_install_warning">DU VERSUCHST EINE FIRMWARE ZU INSTALLIEREN , FAHRE AUF EIGENES RISIKO FORT.
+\n
+\n
+\nDiese Firmware ist f眉r HW Revision: %s</string>
+    <string name="app_install_info">Du bist dabei, die folgende App zu installieren:
+\n
+\n%1$s
+\nVersion %2$s von %3$s
+\n</string>
+    <string name="n_a">N/A</string>
+    <string name="initialized">initialisiert</string>
+    <string name="appversion_by_creator">%1$s von %2$s</string>
+    <string name="title_activity_discovery">Ger盲teerkennung</string>
+    <string name="discovery_stop_scanning">Scannen anhalten</string>
+    <string name="discovery_start_scanning">Entdeckung starten</string>
+    <string name="action_discover">Neues Ger盲t verbinden</string>
+    <string name="device_with_rssi">%1$s (%2$s)</string>
+    <string name="title_activity_android_pairing">Ger盲t koppeln</string>
+    <string name="android_pairing_hint">Verwende den Android-Bluetooth-Kopplungsdialog, um das Ger盲t zu koppeln.</string>
+    <string name="title_activity_mi_band_pairing">Kopple dein Mi Band</string>
+    <string name="pairing">Koppeln mit %s鈥�</string>
+    <string name="pairing_creating_bond_with">Verbindung mit %1$s (%2$s) herstellen</string>
+    <string name="pairing_unable_to_pair_with">Kein Koppeln mit %1$s (%2$s) m枚glich</string>
+    <string name="pairing_in_progress">Verbindung wird hergestellt: %1$s (%2$s)</string>
+    <string name="pairing_already_bonded">Bereits an %1$s (%2$s) gebunden, verbinden鈥�</string>
+    <string name="message_cannot_pair_no_mac">Keine MAC-Adresse erhalten, koppeln nicht m枚glich.</string>
+    <string name="preferences_category_device_specific_settings">Ger盲tespezifische Einstellungen</string>
+    <string name="preferences_miband_settings">Mi Band- / Amazfit-Einstellungen</string>
+    <string name="male">M盲nnlich</string>
+    <string name="female">Weiblich</string>
+    <string name="other">Andere</string>
+    <string name="left">Links</string>
+    <string name="right">Rechts</string>
+    <string name="miband_pairing_using_dummy_userdata">Keine g眉ltigen Benutzerdaten angegeben, es werden jetzt Dummy-Benutzerdaten verwendet.</string>
+    <string name="miband_pairing_tap_hint">Wenn dein Mi Band vibriert und blinkt, tippe es ein paar Mal hintereinander an.</string>
+    <string name="appinstaller_install">Installieren</string>
+    <string name="discovery_connected_devices_hint">Mache dein Ger盲t auffindbar. Derzeit verbundene Ger盲te werden wahrscheinlich nicht erkannt. Aktiviere die Standortbestimmung (zum Beispiel GPS) in Android 6+. Deaktiviere Privacy Guard f眉r Gadgetbridge, da er zu Abst眉rzen und Neustarts deines Telefons f眉hren kann. Wenn nach einigen Minuten kein Ger盲t erkannt wird, versuche es nach dem Neustart deines mobilen Ger盲tes erneut.</string>
+    <string name="discovery_note">Hinweis:</string>
+    <string name="candidate_item_device_image">Ger盲tesymbol</string>
+    <string name="miband_prefs_alias">Name/Alias</string>
+    <string name="pref_header_vibration_count">Vibrationsz盲hler</string>
+    <string name="title_activity_sleepmonitor">Schlaf眉berwachung</string>
+    <string name="pref_write_logfiles">Protokolldateien schreiben</string>
+    <string name="initializing">Initialisieren</string>
+    <string name="busy_task_fetch_activity_data">Aktivit盲tsdaten abrufen</string>
+    <string name="sleep_activity_date_range">Von %1$s bis %2$s</string>
+    <string name="prefs_wearside">Wird links oder rechts getragen\?</string>
+    <string name="pref_screen_vibration_profile">Vibrationsprofile</string>
+    <string name="vibration_profile_staccato">Stakkato</string>
+    <string name="vibration_profile_short">Kurz</string>
+    <string name="vibration_profile_medium">Mittel</string>
+    <string name="vibration_profile_long">Lang</string>
+    <string name="vibration_profile_waterdrop">Wassertropfen</string>
+    <string name="vibration_profile_ring">Klingel</string>
+    <string name="vibration_profile_alarm_clock">Wecker</string>
+    <string name="miband_prefs_vibration">Vibrieren</string>
+    <string name="vibration_try">Versuchen</string>
+    <string name="pref_screen_notification_profile_sms">SMS-Benachrichtigung</string>
+    <string name="pref_header_vibration_settings">Vibrationseinstellungen</string>
+    <string name="pref_screen_notification_profile_generic">Allgemeine Benachrichtigung</string>
+    <string name="pref_screen_notification_profile_email">E-Mail-Benachrichtigung</string>
+    <string name="pref_screen_notification_profile_incoming_call">Benachrichtigung bei eingehenden Anrufen</string>
+    <string name="pref_screen_notification_profile_generic_chat">Chat</string>
+    <string name="pref_screen_notification_profile_generic_navigation">Navigation</string>
+    <string name="pref_screen_notification_profile_generic_social">Soziale Netzwerke</string>
+    <string name="stats_x_axis_label">Minuten insgesamt</string>
+    <string name="stats_y_axis_label">Schritte pro Minute</string>
+    <string name="control_center_find_lost_device">Verlorenes Ger盲t finden</string>
+    <string name="control_center_cancel_to_stop_vibration">Abbrechen, um die Vibration zu stoppen.</string>
+    <string name="title_activity_charts">Aktivit盲t und Schlaf</string>
+    <string name="title_activity_set_alarm">Wecker konfigurieren</string>
+    <string name="controlcenter_start_configure_alarms">Wecker konfigurieren</string>
+    <string name="title_activity_alarm_details">Wecker-Details</string>
+    <string name="alarm_sun_short">So</string>
+    <string name="alarm_mon_short">Mo</string>
+    <string name="alarm_tue_short">Di</string>
+    <string name="alarm_wed_short">Mi</string>
+    <string name="alarm_thu_short">Do</string>
+    <string name="alarm_fri_short">Fr</string>
+    <string name="alarm_sat_short">Sa</string>
+    <string name="alarm_smart_wakeup">Intelligentes Aufwecken</string>
+    <string name="user_feedback_miband_set_alarms_failed">Beim Einstellen der Wecker ist ein Fehler aufgetreten. Bitte erneut versuchen.</string>
+    <string name="user_feedback_miband_set_alarms_ok">Wecker ans Ger盲t gesendet.</string>
+    <string name="chart_no_data_synchronize">Keine Daten. Ger盲t synchronisieren?</string>
+    <string name="user_feedback_miband_activity_data_transfer">Im Begriff %1$s Daten zu 眉bertragen, beginnend mit %2$s</string>
+    <string name="miband_prefs_fitness_goal">T盲gliches Schrittziel</string>
+    <string name="dbaccess_error_executing">Fehler beim Ausf眉hren von \'%1$s\'</string>
+    <string name="controlcenter_start_activitymonitor">Deine Aktivit盲t</string>
+    <string name="cannot_connect">Keine Verbindung m枚glich: %1$s</string>
+    <string name="installer_activity_unable_to_find_handler">Es konnte kein Programm gefunden werden, das diese Datei installiert.</string>
+    <string name="pbw_install_handler_unable_to_install">Angegebene Datei kann nicht installiert werden: %1$s</string>
+    <string name="pbw_install_handler_hw_revision_mismatch">Angegebene Firmware kann nicht installiert werden: Sie passt nicht zur Hardware-Revision deiner Pebble.</string>
+    <string name="installer_activity_wait_while_determining_status">Bitte warten, Installationsstatus wird ermittelt鈥�</string>
+    <string name="notif_battery_low_title">Gadget-Akku schwach!</string>
+    <string name="notif_battery_low_percent">%1$s Akku 眉brig: %2$s%%</string>
+    <string name="notif_battery_low_bigtext_last_charge_time">Letzter Ladevorgang: %s
+\n</string>
+    <string name="notif_battery_low_bigtext_number_of_charges">Anzahl der Ladungen: %s</string>
+    <string name="sleepchart_your_sleep">Schlaf</string>
+    <string name="weeksleepchart_sleep_a_week">Schlaf pro Woche</string>
+    <string name="weeksleepchart_today_sleep_description">Schlaf heute, Ziel: %1$s</string>
+    <string name="weekstepschart_steps_a_week">Schritte pro Woche</string>
+    <string name="activity_sleepchart_activity_and_sleep">Aktivit盲t</string>
+    <string name="updating_firmware">Aktualisiere Firmware鈥�</string>
+    <string name="fwapp_install_device_not_ready">Datei kann nicht installiert werden, Ger盲t nicht bereit.</string>
+    <string name="installhandler_firmware_name">%1$s: %2$s %3$s</string>
+    <string name="miband_fwinstaller_compatible_version">Kompatible Version</string>
+    <string name="miband_fwinstaller_untested_version">Ungetestete Version!</string>
+    <string name="fwappinstaller_connection_state">Verbindung zum Ger盲t: %1$s</string>
+    <string name="pbw_installhandler_pebble_firmware">Pebble Firmware %1$s</string>
+    <string name="pbwinstallhandler_correct_hw_revision">Korrekte Hardwarerevision</string>
+    <string name="pbwinstallhandler_incorrect_hw_revision">Falsche Hardwarerevision!</string>
+    <string name="pbwinstallhandler_app_item">%1$s (%2$s)</string>
+    <string name="updatefirmwareoperation_updateproblem_do_not_reboot">Problem mit der Firmware-脺bertragung. DEIN MI BAND NICHT NEU STARTEN!</string>
+    <string name="updatefirmwareoperation_metadata_updateproblem">Problem bei der Firmware-Metadaten眉bertragung</string>
+    <string name="updatefirmwareoperation_update_complete">Firmwareinstallation abgeschlossen</string>
+    <string name="updatefirmwareoperation_update_complete_rebooting">Firmwareinstallation erfolgreich beendet, Ger盲t wird neu gestartet鈥�</string>
+    <string name="updatefirmwareoperation_write_failed">Firmware aktualisieren fehlgeschlagen</string>
+    <string name="chart_steps">Schritte</string>
+    <string name="calories">Kalorien</string>
+    <string name="distance">Strecke</string>
+    <string name="clock">Uhr</string>
+    <string name="heart_rate">Herzfrequenz</string>
+    <string name="battery">Akku</string>
+    <string name="liveactivity_live_activity">Jetzige Aktivit盲t (live)</string>
+    <string name="weeksteps_today_steps_description">Schritte heute, Ziel: %1$s</string>
+    <string name="pref_title_dont_ack_transfer">脺bertragung der Aktivit盲tsdaten nicht best盲tigen (ACK)</string>
+    <string name="pref_summary_dont_ack_transfers">Ohne ACK (Best盲tigung), l枚scht das Band die Aktivit盲tsdaten nicht. N眉tzlich, wenn GB zusammen mit anderen Apps verwendet wird.</string>
+    <string name="pref_summary_keep_data_on_device">Beh盲lt die Aktivit盲tsdaten auch nach der Synchronisierung auf dem Ger盲t. N眉tzlich, wenn GB zusammen mit anderen Apps verwendet wird. Dies kann dazu f眉hren, dass die Uhr keinen Platz mehr hat und/oder die Synchronisierung nicht mehr richtig funktioniert.</string>
+    <string name="pref_title_low_latency_fw_update">Verwende den Low-Latency-Modus f眉r Firmwareaktualisierung</string>
+    <string name="pref_summary_low_latency_fw_update">Dies kann bei Ger盲ten helfen, bei denen Firmwareupdates fehlschlagen.</string>
+    <string name="live_activity_steps_history">Schrittverlauf</string>
+    <string name="live_activity_current_steps_per_minute">Aktuelle Schritte/min</string>
+    <string name="live_activity_total_steps">Schritte insgesamt</string>
+    <string name="live_activity_steps_per_minute_history">Schrittverlauf pro Minute</string>
+    <string name="live_activity_start_your_activity">Starte deine Aktivit盲t</string>
+    <string name="abstract_chart_fragment_kind_activity">Aktivit盲t</string>
+    <string name="abstract_chart_fragment_kind_light_sleep">Leichter Schlaf</string>
+    <string name="abstract_chart_fragment_kind_deep_sleep">Tiefschlaf</string>
+    <string name="abstract_chart_fragment_kind_not_worn">Nicht getragen</string>
+    <string name="device_not_connected">Nicht verbunden.</string>
+    <string name="user_feedback_all_alarms_disabled">Alle Wecker deaktiviert</string>
+    <string name="pref_title_keep_data_on_device">Aktivit盲tsdaten auf dem Ger盲t lassen</string>
+    <string name="miband_fwinstaller_incompatible_version">Inkompatible Firmware</string>
+    <string name="fwinstaller_firmware_not_compatible_to_device">Diese Firmware ist nicht mit dem Ger盲t kompatibel</string>
+    <string name="miband_prefs_reserve_alarm_calendar">Wecker f眉r zuk眉nftige Ereignisse vormerken</string>
+    <string name="miband_prefs_hr_sleep_detection">Verwende den Herzfrequenzsensor, um die Schlaferkennung zu verbessern</string>
+    <string name="miband_prefs_device_time_offset_hours">Ger盲tezeitversatz in Stunden (Erkennung des Schlafes von Schichtarbeitern)</string>
+    <string name="miband2_prefs_dateformat">Datumsformat</string>
+    <string name="dateformat_time">Zeit</string>
+    <string name="dateformat_date_time"><![CDATA[Zeit & Datum]]></string>
+    <string name="mi2_prefs_goal_notification">Zielbenachrichtigung</string>
+    <string name="mi2_prefs_goal_notification_summary">Das Band vibriert, wenn das Tagesschrittziel erreicht ist</string>
+    <string name="mi2_prefs_display_items">Anzeigeelemente</string>
+    <string name="mi2_prefs_display_items_summary">Die auf der Bandanzeige anzuzeigenden Elemente ausw盲hlen</string>
+    <string name="mi2_prefs_activate_display_on_lift">Anzeige beim Anheben aktivieren</string>
+    <string name="mi2_prefs_rotate_wrist_to_switch_info">Handgelenk drehen, um die Information zu wechseln</string>
+    <string name="mi2_prefs_do_not_disturb">Bitte nicht st枚ren</string>
+    <string name="mi2_prefs_do_not_disturb_summary">Wenn das Band aktiv ist, erh盲lt es keine Benachrichtigungen</string>
+    <string name="mi2_prefs_inactivity_warnings">Inaktivit盲tswarnungen</string>
+    <string name="mi2_prefs_inactivity_warnings_summary">Das Band vibriert, wenn du eine Weile nicht aktiv warst</string>
+    <string name="mi2_prefs_inactivity_warnings_threshold">Grenzwert f眉r Inaktivit盲t (in Minuten)</string>
+    <string name="mi2_prefs_inactivity_warnings_dnd_summary">Deaktivieren von Inaktivit盲tswarnungen f眉r eine bestimmte Zeit</string>
+    <string name="mi2_prefs_do_not_disturb_start">Startzeit</string>
+    <string name="mi2_prefs_do_not_disturb_end">Endzeit</string>
+    <string name="FetchActivityOperation_about_to_transfer_since">脺bertrage Daten ab %1$s</string>
+    <string name="waiting_for_reconnect">Warten auf Wiederverbindung</string>
+    <string name="activity_prefs_about_you">脺ber Dich</string>
+    <string name="activity_prefs_year_birth">Geburtsjahr</string>
+    <string name="activity_prefs_gender">Geschlecht</string>
+    <string name="activity_prefs_height_cm">Gr枚脽e in cm</string>
+    <string name="activity_prefs_weight_kg">Gewicht in kg</string>
+    <string name="authenticating">Authentifizieren</string>
+    <string name="authentication_required">Authentifizierung erforderlich</string>
+    <string name="appwidget_text">Zzz</string>
+    <string name="add_widget">Widget hinzuf眉gen</string>
+    <string name="activity_prefs_sleep_duration">Gew眉nschte Schlafdauer in Stunden</string>
+    <string name="appwidget_setting_alarm">Wecker wurde auf %1$02d:%2$02d gestellt</string>
+    <string name="device_hw">Hardwarerevision: %1$s</string>
+    <string name="device_fw">Firmwareversion: %1$s</string>
+    <string name="error_creating_directory_for_logfiles">Fehler beim Erstellen des Verzeichnisses f眉r Protokolldateien: %1$s</string>
+    <string name="DEVINFO_HR_VER">"HF: "</string>
+    <string name="updatefirmwareoperation_update_in_progress">Firmwareupdate wird durchgef眉hrt</string>
+    <string name="updatefirmwareoperation_firmware_not_sent">Firmware nicht gesendet</string>
+    <string name="charts_legend_heartrate">Herzfrequenz</string>
+    <string name="live_activity_heart_rate">Herzfrequenz</string>
+    <string name="pref_title_pebble_health_store_raw">Rohdaten in der Datenbank speichern</string>
+    <string name="pref_summary_pebble_health_store_raw">Speichert Daten 鈥瀢ie sie sind鈥� und erh枚ht die Datenbanknutzung, um eine sp盲tere Auswertung zu erm枚glichen.</string>
+    <string name="action_db_management">Datenmanagement</string>
+    <string name="title_activity_db_management">Datenmanagement</string>
+    <string name="activity_db_management_import_export_explanation">Die Export-/Importvorg盲nge verwenden den folgenden Pfad (siehe unten) zu einem Verzeichnis auf deinem Ger盲t. Auf dieses Verzeichnis k枚nnen andere Android-Apps und dein Computer zugreifen. Beachte, dass dieses Verzeichnis und alle enthaltenen Dateien gel枚scht werden, wenn du Gadgetbridge deinstallierst. Die Daten umfassen:
+\n Export_preference - globale Einstellungen
+\n Export_preference_MAC - ger盲tespezifische Einstellungen
+\n Gadgetbridge - Ger盲te- und Aktivit盲tsdatenbank
+\n Gadgetbridge_date - Datenbank, die an einem Datum exportiert wurde
+\n * .gpx - GPS-Aufzeichnungen
+\n * .log - Protokolldateien
+\nDie exportierten Dateien (oder die Dateien, die du importieren m枚chtest) findest du dort:</string>
+    <string name="activity_db_management_merge_old_title">Legacy-Datenbank l枚schen</string>
+    <string name="dbmanagementactivvity_cannot_access_export_path">Kann nicht auf den Exportpfad zugreifen. Bitte die Entwickler kontaktieren.</string>
+    <string name="dbmanagementactivity_exported_to">Exportiert nach: %1$s</string>
+    <string name="dbmanagementactivity_error_exporting_db">Fehler beim Exportieren der DB: %1$s</string>
+    <string name="dbmanagementactivity_error_exporting_shared">Fehler beim Exportieren der Einstellungen: %1$s</string>
+    <string name="dbmanagementactivity_import_data_title">Daten importieren?</string>
+    <string name="dbmanagementactivity_overwrite_database_confirmation">Die aktuellen Daten wirklich 眉berschreiben? Alle aktuellen Aktivit盲tsdaten (falls vorhanden), Ger盲te und Einstellungen werden 眉berschrieben.</string>
+    <string name="dbmanagementactivity_import_successful">Importiert.</string>
+    <string name="dbmanagementactivity_error_importing_db">Fehler beim Importieren der DB: %1$s</string>
+    <string name="dbmanagementactivity_error_importing_shared">Fehler beim Importieren der Einstellungen: %1$s</string>
+    <string name="dbmanagementactivity_delete_activity_data_title">Aktivit盲tsdaten l枚schen?</string>
+    <string name="dbmanagementactivity_really_delete_entire_db">Die gesamte Datenbank wirklich l枚schen\? Alle Aktivit盲tsdaten und Informationen 眉ber deine Ger盲te gehen verloren.</string>
+    <string name="dbmanagementactivity_database_successfully_deleted">Daten gel枚scht.</string>
+    <string name="dbmanagementactivity_db_deletion_failed">Datenbank l枚schen fehlgeschlagen.</string>
+    <string name="dbmanagementactivity_delete_old_activity_db">Alte Aktivit盲tsdatenbank l枚schen?</string>
+    <string name="dbmanagementactivity_delete_old_activitydb_confirmation">Die alte Aktivit盲tsdatenbank wirklich l枚schen\? Nicht importierte Aktivit盲tsdaten gehen verloren.</string>
+    <string name="dbmanagementactivity_old_activity_db_successfully_deleted">Alte Aktivit盲tsdaten gel枚scht.</string>
+    <string name="dbmanagementactivity_old_activity_db_deletion_failed">L枚schen der alten Aktivit盲tsdatenbank fehlgeschlagen.</string>
+    <string name="dbmanagementactivity_overwrite">脺berschreiben</string>
+    <string name="Cancel">Abbrechen</string>
+    <string name="Delete">L枚schen</string>
+    <!--Strings related to Vibration Activity-->
+    <string name="title_activity_vibration">Vibration</string>
+    <!--Strings related to Pebble Pairing Activity-->
+    <string name="title_activity_pebble_pairing">Pebble koppeln</string>
+    <string name="pebble_pairing_hint">Auf deinem Android-Ger盲t erscheint ein Koppeln-Dialog. Wenn nicht, schaue in die Benachrichtigungs眉bersicht und akzeptiere die Kopplungsanforderung. Akzeptiere es auch danach auf deiner Pebble.</string>
+    <string name="weather_notification_label">Stelle sicher, dass dieser Skin in der App \"Weather Notification\" aktiviert ist, damit du Wetterdaten auf deiner Pebble erh盲ltst.
+\n
+\nHier ist keine Konfiguration n枚tig.
+\n
+\nDu kannst die Systemwetter-App deiner Pebble in App-Verwaltung aktivieren.
+\n
+\nUnterst眉tzte Zifferbl盲tter zeigen das Wetter automatisch an.</string>
+    <string name="pref_title_setup_bt_pairing">Bluetooth-Kopplung aktivieren</string>
+    <string name="pref_summary_setup_bt_pairing">Deaktivierbar, falls du Probleme beim Verbinden hast</string>
+    <string name="unit_metric">Metrisch</string>
+    <string name="unit_imperial">Imperial (US/UK)</string>
+    <string name="timeformat_24h">24h</string>
+    <string name="timeformat_am_pm">AM/PM</string>
+    <string name="pref_screen_notification_profile_alarm_clock">Wecker</string>
+    <string name="StringUtils_sender"> (%1$s)</string>
+    <string name="find_device_you_found_it">Gefunden!</string>
+    <string name="miband2_prefs_timeformat">Mi2: Zeitformat</string>
+    <string name="mi2_fw_installhandler_fw53_hint">Du musst Version %1$s installieren, um diese Firmware installierbar zu machen!</string>
+    <string name="mi2_enable_text_notifications">Textbenachrichtigungen</string>
+    <string name="mi2_enable_text_notifications_summary"><![CDATA[Ben枚tigt Firmware >= 1.0.1.28 und Mili_pro.ft* Installiert.]]></string>
+    <string name="off">Aus</string>
+    <string name="mi2_dnd_off">Aus</string>
+    <string name="mi2_dnd_automatic">Automatisch (Schlaferkennung)</string>
+    <string name="mi2_dnd_scheduled">Geplant (Zeitintervall)</string>
+    <string name="discovery_attempting_to_pair">Versuche mit %1$s zu koppeln</string>
+    <string name="discovery_bonding_failed_immediately">Bindung mit %1$s sofort fehlgeschlagen.</string>
+    <string name="discovery_trying_to_connect_to">Verbindungsversuch mit: %1$s</string>
+    <string name="discovery_enable_bluetooth">Aktiviere Bluetooth um Ger盲te zu finden.</string>
+    <string name="discovery_successfully_bonded">Gebunden an %1$s.</string>
+    <string name="discovery_pair_title">Koppeln mit %1$s?</string>
+    <string name="discovery_pair_question">W盲hle Koppeln, um dein Ger盲t zu verbinden. Falls dies fehlschl盲gt, versuche es erneut ohne koppeln.</string>
+    <string name="discovery_yes_pair">Koppeln</string>
+    <string name="discovery_dont_pair">Nicht koppeln</string>
+    <string name="stats_title">Geschwindigkeitszonen</string>
+    <string name="fw_upgrade_notice_amazfitbip">Du bist dabei, die Firmware %s auf deine Amazfit Bip zu installieren.
+\n
+\nBitte stelle sicher, dass du die .fw-Datei, dann die .res-Datei und schlie脽lich die .gps-Datei installierst. Deine Uhr wird nach der Installation der .fw-Datei neu gestartet.
+\n
+\nHinweis: Du musst die .res und .gps nicht installieren, wenn diese Dateien genau die gleichen sind, wie die zuvor installierten.
+\n
+\nINSTALLATION AUF EIGENE GEFAHR!</string>
+    <string name="mi2_prefs_button_actions">Tastenaktionen</string>
+    <string name="mi2_prefs_button_actions_summary">Tastendruckaktionen bestimmen</string>
+    <string name="mi2_prefs_button_press_count_summary">Anzahl der Tastendr眉cke, um Aktion 1 auszul枚sen. Wiederholen der gleichen Anzahl an Tastendr眉cken f眉hrt Aktion 2 aus, und so weiter.</string>
+    <string name="mi2_prefs_button_press_broadcast">Zu sendende Nachricht</string>
+    <string name="mi2_prefs_button_action">Tastenaktion aktivieren</string>
+    <string name="mi2_prefs_button_action_summary">Aktion bei einer bestimmten Anzahl an Tastendr眉cken aktivieren</string>
+    <string name="mi2_prefs_button_action_vibrate">Bandvibration aktivieren</string>
+    <string name="mi2_prefs_button_action_vibrate_summary">Bandvibration aktivieren, wenn die Tastenaktion ausgel枚st wurde</string>
+    <string name="mi2_prefs_button_press_count_max_delay">Maximale Verz枚gerung zwischen den Tastendr眉cken</string>
+    <string name="mi2_prefs_button_press_count_max_delay_summary">Maximale Verz枚gerung zwischen den Tastendr眉cken in Millisekunden</string>
+    <string name="_pebble_watch_open_on_phone">Auf Android-Ger盲t 枚ffnen</string>
+    <string name="_pebble_watch_mute">Lautlos</string>
+    <string name="_pebble_watch_reply">Antworten</string>
+    <string name="controlcenter_connect">Verbinden鈥�</string>
+    <string name="pref_title_pebble_enable_bgjs">Hintergrund-JS aktivieren</string>
+    <string name="simplified_chinese">Vereinfachtes Chinesisch</string>
+    <string name="traditional_chinese">Traditionelles Chinesisch</string>
+    <string name="english">Englisch</string>
+    <string name="fw_upgrade_notice_amazfitcor">Du bist dabei, die Firmware %s auf deine Amazfit Cor zu installieren.
+\n
+\nBitte stelle sicher, dass du die .fw Datei und danach die .res Datei installierst. Dein Band wird nach der Installation der .fw Datei neu gestartet.
+\n
+\nHinweis: Du musst .res nicht installieren, wenn es genau das gleiche ist, wie die zuvor installierte.
+\n
+\nINSTALLATION AUF EIGENE GEFAHR!</string>
+    <string name="mi2_prefs_button_press_count">Anzahl der Tastendr眉cke</string>
+    <string name="pref_title_charts_swipe">Streichen nach links/rechts im Diagrammbetrieb aktivieren</string>
+    <string name="pref_summary_pebble_enable_bgjs">Wenn aktiviert, kann Zifferblatt Wetter, Akkuinfos, usw. anzeigen.</string>
+    <string name="prefs_title_heartrate_measurement_interval">Ganzt盲gige Messung der Herzfrequenz</string>
+    <string name="interval_one_minute">einmal pro Minute</string>
+    <string name="interval_five_minutes">alle 5 Minuten</string>
+    <string name="interval_ten_minutes">alle 10 Minuten</string>
+    <string name="interval_thirty_minutes">alle 30 Minuten</string>
+    <string name="interval_one_hour">St眉ndlich</string>
+    <string name="automatic">Automatisch</string>
+    <string name="activity_web_view">Webansicht Aktivit盲t</string>
+    <string name="pref_title_weather">Wetter</string>
+    <string name="kind_firmware">Firmware</string>
+    <string name="kind_invalid">Ung眉ltige Daten</string>
+    <string name="kind_gps">GPS-Firmware</string>
+    <string name="devicetype_unknown">Unbekanntes Ger盲t</string>
+    <string name="devicetype_test">Testger盲t</string>
+    <string name="devicetype_pebble">Pebble</string>
+    <string name="devicetype_miband">Mi Band</string>
+    <string name="devicetype_miband2">Mi Band 2</string>
+    <string name="devicetype_amazfit_bip">Amazfit Bip</string>
+    <string name="devicetype_amazfit_cor">Amazfit Cor</string>
+    <string name="devicetype_vibratissimo">Vibratissimo</string>
+    <string name="devicetype_liveview">LiveView</string>
+    <string name="devicetype_hplus">HPlus</string>
+    <string name="devicetype_makibes_f68">Makibes F68</string>
+    <string name="devicetype_exrizu_k8">Exrizu K8</string>
+    <string name="devicetype_no1_f1">No.1 F1</string>
+    <string name="devicetype_teclast_h30">Teclast H30</string>
+    <string name="pref_title_weather_location">Wetterstandort (f眉r den LineageOS Wetterdienst)</string>
+    <string name="pref_header_auto_export">Automatisch exportieren</string>
+    <string name="pref_title_auto_export_enabled">Automatisch exportieren aktiviert</string>
+    <string name="pref_title_auto_export_location">Speicherort f眉r den Export</string>
+    <string name="pref_title_auto_export_interval">Intervall des Exports</string>
+    <string name="pref_summary_auto_export_interval">Alle %d Stunden exportieren</string>
+    <string name="notif_export_failed_title">Datenbankexport fehlgeschlagen! Bitte 眉berpr眉fe deine Einstellungen.</string>
+    <string name="mi2_prefs_button_press_broadcast_summary">Broadcast Nachricht, die mit der Aktion gesendet wird. Der Parameter 鈥瀊utton_id鈥� wird automatisch zu jeder Nachricht hinzugef眉gt.</string>
+    <string name="spanish">Spanisch</string>
+    <string name="on">Ein</string>
+    <string name="controlcenter_start_activity_tracks">Deine Aktivit盲tsverl盲ufe</string>
+    <string name="activity_type_not_measured">Nicht gemessen</string>
+    <string name="activity_type_activity">Aktivit盲t</string>
+    <string name="activity_type_light_sleep">Leichter Schlaf</string>
+    <string name="activity_type_deep_sleep">Tiefschlaf</string>
+    <string name="activity_type_not_worn">Ger盲t nicht getragen</string>
+    <string name="activity_type_running">Laufen</string>
+    <string name="activity_type_walking">Gehen</string>
+    <string name="activity_type_swimming">Schwimmen</string>
+    <string name="activity_type_unknown">Unbekannte Aktivit盲t</string>
+    <string name="activity_summaries">Sportliche Aktivit盲ten</string>
+    <string name="activity_type_biking">Radfahren</string>
+    <string name="activity_type_treadmill">Laufband</string>
+    <string name="activity_error_no_app_for_gpx">Um Aktivit盲ten zu betrachten ist eine App zu installieren, die GPX-Dateien verarbeiten kann.</string>
+    <string name="select_all">Alle ausw盲hlen</string>
+    <string name="share">Teilen</string>
+    <string name="reset_index">Abrufdatum zur眉cksetzen</string>
+    <string name="kind_font">Schriftart</string>
+    <string name="kind_gps_almanac">GPS-Kalender</string>
+    <string name="kind_gps_cep">GPS-Fehlerkorrektur</string>
+    <string name="kind_resources">Quellen</string>
+    <string name="kind_watchface">Ziffernblatt</string>
+    <string name="devicetype_xwatch">XWatch</string>
+    <string name="choose_auto_export_location">Speicherort f眉r Export ausw盲hlen</string>
+    <string name="notification_channel_name">Allgemein</string>
+    <string name="menuitem_nothing">Nichts</string>
+    <string name="menuitem_status">Status</string>
+    <string name="menuitem_activity">Trainingsverlauf</string>
+    <string name="menuitem_weather">Wetter</string>
+    <string name="menuitem_alarm">Alarm</string>
+    <string name="menuitem_timer">Countdown</string>
+    <string name="menuitem_compass">Kompass</string>
+    <string name="menuitem_settings">Einstellungen</string>
+    <string name="menuitem_alipay">Alipay</string>
+    <string name="fw_upgrade_notice_miband3">Du bist dabei, die Firmware %s auf dein Mi Band 3 zu installieren.
+\n
+\nBitte stelle sicher, dass du die .fw Datei und die .res Datei installierst. Dein Band wird nach der Installation der .fw Datei neu gestartet.
+\n
+\nHinweis: Du musst .res nicht installieren, wenn es genau das gleiche ist, wie die zuvor installierte.
+\n
+\nINSTALLATION AUF EIGENE GEFAHR!</string>
+    <string name="pref_summary_pebble_gatt_clientonly">Dies ist nur f眉r Pebble 2 und experimentell, versuche dies, wenn du Verbindungsprobleme hast</string>
+    <string name="pref_auto_fetch">Aktivit盲tsdaten automatisch abrufen</string>
+    <string name="devicetype_miband3">Mi Band 3</string>
+    <string name="devicetype_q8">Q8</string>
+    <string name="blacklist_all_for_notifications">Blacklist f眉r alle Benachrichtigungen</string>
+    <string name="whitelist_all_for_notifications">Whitelist f眉r alle Benachrichtigungen</string>
+    <string name="pref_auto_fetch_summary">Abruf erfolgt beim Entsperren des Bildschirms. Funktioniert nur, wenn ein Sperrmechanismus eingestellt ist!</string>
+    <string name="watch9_pairing_tap_hint">Wenn deine Uhr vibriert, sch眉ttel das Ger盲t oder dr眉cke die Taste.</string>
+    <string name="preferences_qhybrid_settings">Q Hybrid-Einstellungen</string>
+    <string name="watch9_calibration_button">Kalibrieren</string>
+    <string name="title_activity_watch9_pairing">Watch 9 koppeln</string>
+    <string name="watch9_time_minutes">Minuten:</string>
+    <string name="watch9_time_hours">Stunden:</string>
+    <string name="watch9_time_seconds">Sekunden:</string>
+    <string name="title_activity_watch9_calibration">Watch 9 kalibrieren</string>
+    <string name="watch9_calibration_hint">Stelle die Zeit ein, die dir dein Ger盲t gerade anzeigt.</string>
+    <string name="controlcenter_calibrate_device">Ger盲t kalibrieren</string>
+    <string name="horizontal">Horizontal</string>
+    <string name="vertical">Vertikal</string>
+    <string name="you_slept">%1$s - %2$s</string>
+    <string name="you_did_not_sleep">Du hast nicht geschlafen</string>
+    <string name="norwegian_bokmal">Norwegisch (Bokm氓l)</string>
+    <string name="russian">Russisch</string>
+    <string name="german">Deutsch</string>
+    <string name="italian">Italienisch</string>
+    <string name="french">Franz枚sisch</string>
+    <string name="polish">Polnisch</string>
+    <string name="korean">Koreanisch</string>
+    <string name="japanese">Japanisch</string>
+    <string name="controlcenter_change_led_color">LED-Farbe 盲ndern</string>
+    <string name="controlcenter_change_fm_frequency">Radio-Frequenz 盲ndern</string>
+    <string name="pref_title_notifications_timeout">Mindestzeit zwischen Benachrichtigungen</string>
+    <string name="pref_title_rtl">Linksl盲ufiger Text (Rechts nach links)</string>
+    <string name="pref_summary_rtl">Aktivieren, wenn dein Ger盲t keine linksl盲ufigen Texte anzeigen kann</string>
+    <string name="pref_title_pebble_gatt_clientonly">Nur f眉r GATT</string>
+    <string name="prefs_screen_orientation">Bildschirmausrichtung</string>
+    <string name="pref_auto_fetch_limit_fetches">Minimale Zeit zwischen Abfragen</string>
+    <string name="pref_auto_fetch_limit_fetches_summary">Hole neue Informationen alle %d Minuten</string>
+    <string name="notif_battery_low">%1$s Akku schwach</string>
+    <string name="no_limit">Kein Limit</string>
+    <string name="seconds_5">5 Sekunden</string>
+    <string name="seconds_10">10 Sekunden</string>
+    <string name="seconds_20">20 Sekunden</string>
+    <string name="seconds_30">30 Sekunden</string>
+    <string name="minutes_1">1 Minute</string>
+    <string name="minutes_5">5 Minuten</string>
+    <string name="minutes_10">10 Minuten</string>
+    <string name="minutes_30">30 Minuten</string>
+    <string name="lack_of_step">Zu wenig Schritte: %1$d</string>
+    <string name="overstep">Schritte 眉ber Tagesziel: %1$d</string>
+    <string name="live_activity_max_heart_rate">Aktuelle / Maximale Herzfrequenz: %1$d / %2$d</string>
+    <string name="mi3_prefs_band_screen_unlock">Band-Bildschirm entsperren</string>
+    <string name="mi3_prefs_band_screen_unlock_summary">Nach oben wischen, um den Band-Bildschirm zu entsperren</string>
+    <string name="mi3_prefs_night_mode">Nachtmodus</string>
+    <string name="mi3_prefs_night_mode_summary">Bandhelligkeit nachts automatisch verringern</string>
+    <string name="activity_prefs_charts">Diagrammeinstellungen</string>
+    <string name="activity_prefs_chart_max_heart_rate">Maximale Herzfrequenz</string>
+    <string name="activity_prefs_chart_min_heart_rate">Minimale Herzfrequenz</string>
+    <string name="ok">OK</string>
+    <string name="mi3_night_mode_sunset">Bei Sonnenuntergang</string>
+    <string name="pref_rtl_max_line_length">Maximale Zeilenl盲nge linksl盲ufiger Text</string>
+    <string name="overslept">Schlaf眉berschuss: %1$s</string>
+    <string name="menuitem_notifications">Benachrichtigungen</string>
+    <string name="menuitem_music">Musik</string>
+    <string name="menuitem_more">Mehr</string>
+    <string name="warning">Warnung!</string>
+    <string name="no_data">Keine Daten</string>
+    <string name="preferences_led_color">LED-Farbe</string>
+    <string name="preferences_fm_frequency">FM-Frequenz</string>
+    <string name="pref_invalid_frequency_title">Ung眉ltige Frequenz</string>
+    <string name="pref_invalid_frequency_message">Bitte eine Frequenz zwischen 87,5 und 108,0 eingeben</string>
+    <string name="language_and_region_prefs">Sprach- und Regionseinstellungen</string>
+    <string name="debugactivity_really_factoryreset_title">Wirklich auf Werkseinstellungen zur眉cksetzen\?</string>
+    <string name="debugactivity_really_factoryreset">Beim Zur眉cksetzen auf die Werkseinstellungen werden alle Daten vom angeschlossenen Ger盲t gel枚scht (falls unterst眉tzt). Xiaomi/Huami-Ger盲te wechseln dabei auch die Bluetooth-MAC-Adresse, so dass sie als neue Ger盲te bei Gadgetbridge erscheinen.</string>
+    <string name="pref_rtl_max_line_length_summary">Verl盲ngert oder verk眉rzt die Zeilen, in die linksl盲ufiger Text geteilt wird</string>
+    <string name="notif_battery_low_extended">%1$s Akku fast leer: %2$s</string>
+    <string name="lack_of_sleep">Zu wenig geschlafen: %1$s</string>
+    <string name="activity_type_exercise">脺bung</string>
+    <string name="devicetype_mykronoz_zetime">MyKronoz ZeTime</string>
+    <string name="devicetype_id115">ID115</string>
+    <string name="devicetype_watch9">Watch 9</string>
+    <string name="devicetype_roidmi">Roidmi</string>
+    <string name="devicetype_roidmi3">Roidmi 3</string>
+    <string name="devicetype_casiogb6900">Casio GB-6900</string>
+    <string name="pref_title_contextual_arabic">Kontextabh盲ngiges Arabisch</string>
+    <string name="pref_summary_contextual_arabic">Aktivieren, um kontextabh盲ngiges Arabisch zu unterst眉tzen</string>
+    <string name="preferences_rtl_settings">Unterst眉tzung f眉r linksl盲ufigen Text (RTL)</string>
+    <string name="share_log">Protokoll teilen</string>
+    <string name="share_log_warning">Bitte beachte, in den Protokolldateien von Gadgetbridge k枚nnen personenbezogene Informationen enthalten sein. Dies k枚nnen Gesundheitsdaten, eindeutige Identifikatoren (wie die MAC-Adresse eines Ger盲ts), Musikpr盲ferenzen und mehr sein. Erw盲ge die Datei zu bearbeiten und diese Informationen zu entfernen, bevor du die Datei in einem Fehlerbericht 枚ffentlich zur Verf眉gung stellst.</string>
+    <string name="title_activity_notification_filter">Benachrichtigungsfilter</string>
+    <string name="edittext_notification_filter_words_hint">Gew眉nschte W枚rter eingeben, neue Zeile f眉r jedes Wort</string>
+    <string name="toast_notification_filter_saved_successfully">Benachrichtigungsfilter gespeichert</string>
+    <string name="filter_mode_none">Nicht filtern</string>
+    <string name="filter_mode_whitelist">Wenn W枚rter enthalten sind anzeigen</string>
+    <string name="filter_mode_blacklist">Wenn W枚rter enthalten sind blockieren</string>
+    <string name="filter_submode_at_least_one">Mindestens eines der W枚rter</string>
+    <string name="filter_submode_all">Alle W枚rter</string>
+    <string name="toast_notification_filter_words_empty_hint">Bitte mindestens ein Wort eingeben</string>
+    <string name="filter_mode">Filtermodus</string>
+    <string name="mode_configuration">Modus-Konfiguration</string>
+    <string name="save_configuration">Konfiguration speichern</string>
+    <string name="appwidget_not_connected">Nicht verbunden, Alarm nicht eingestellt.</string>
+    <string name="prefs_disconnect_notification">Trennungsbenachrichtigung</string>
+    <string name="activity_prefs_calories_burnt">T盲gliches Ziel: verbrannte Kalorien</string>
+    <string name="activity_prefs_distance_meters">T盲gliches Ziel: zur眉ckgelegte Strecke in Meter</string>
+    <string name="activity_prefs_activetime_minutes">T盲gliches Ziel: aktive Zeit in Minuten</string>
+    <!-- ZeTime Preferences -->
+    <string name="zetime_title_settings">ZeTime-Einstellungen</string>
+    <string name="zetime_title_screentime">Display-Aktivierungsdauer in Sekunden</string>
+    <string name="activity_prefs_alarm_max_heart_rate">Hohe Herzfrequenzschwelle</string>
+    <string name="activity_prefs_alarm_min_heart_rate">Niedrige Herzfrequenzschwelle</string>
+    <string name="zetime_title_heartrate">Herzfrequenz-Men眉</string>
+    <string name="zetime_title_heart_rate_alarm">Herzfrequenz Alarm</string>
+    <string name="zetime_heart_rate_alarm_enable">Herzfrequenzalarm aktivieren</string>
+    <string name="zetime_date_format">Datumsformat</string>
+    <string name="zetime_date_format_1">JJ/MM/TT</string>
+    <string name="zetime_date_format_2">TT/MM/JJ</string>
+    <string name="zetime_date_format_3">MM/TT/JJ</string>
+    <string name="zetime_prefs_inactivity_mo">Montag</string>
+    <string name="zetime_prefs_inactivity_tu">Dienstag</string>
+    <string name="zetime_prefs_inactivity_we">Mittwoch</string>
+    <string name="zetime_prefs_inactivity_th">Donnerstag</string>
+    <string name="zetime_prefs_inactivity_fr">Freitag</string>
+    <string name="zetime_prefs_inactivity_sa">Samstag</string>
+    <string name="zetime_prefs_inactivity_su">Sonntag</string>
+    <string name="zetime_signaling_none">Lautlos</string>
+    <string name="devicetype_miscale2">Mi Scale 2</string>
+    <string name="zetime_title_heart_rate_alarm_summary">L枚st eine Warnung aus, wenn deine Herzfrequenz die Grenzwerte 眉berschreitet.</string>
+    <string name="zetime_analog_mode">Analoger Modus</string>
+    <string name="zetime_analog_mode_hands">Nur Zeiger</string>
+    <string name="zetime_analog_mode_handsandsteps">Zeiger und Schrittanzahl</string>
+    <string name="zetime_activity_tracking">Aktivit盲tsverfolgung</string>
+    <string name="zetime_activity_tracking_summary">Bei eingeschalteter Aktivit盲tenverfolgung werden unter anderem deine Schritte gez盲hlt.</string>
+    <string name="zetime_handmove_display">Handbewegung</string>
+    <string name="zetime_handmove_display_summary">Drehe dein Handgelenk, um die Anzeige zu aktivieren oder zu deaktivieren.</string>
+    <string name="zetime_calories_type">Kalorientyp</string>
+    <string name="zetime_calories_type_active">Nur aktiv verbrannte Kalorien</string>
+    <string name="zetime_calories_type_all">Aktiv und inaktiv verbrannte Kalorien</string>
+    <string name="zetime_prefs_inactivity_repetitions">Wiederholungen</string>
+    <string name="zetime_title_alarm_signaling">Signalisierungart f眉r den Alarm einstellen</string>
+    <string name="zetime_signaling_vibrate">Kontinuierliche Vibration</string>
+    <string name="zetime_signaling_beep">Kontinuierliches Piepen</string>
+    <string name="zetime_signaling_vibrate_beep">Kontinuierliche Vibration und Piepen</string>
+    <string name="zetime_signaling_vibrate_once">Einmal vibrieren</string>
+    <string name="zetime_signaling_vibrate_twice">Zweimal vibrieren</string>
+    <string name="zetime_signaling_beep_once">Einmaliges Piepen</string>
+    <string name="zetime_signaling_beep_twice">Zweimal piepen</string>
+    <string name="zetime_signaling_vibrate_beep_once">Einmal vibrieren und piepen</string>
+    <string name="pref_screen_notification_profile_missed_call">Benachrichtigung bei verpassten Anrufen</string>
+    <string name="pref_screen_notification_profile_calendar">Kalenderbenachrichtigung</string>
+    <string name="pref_screen_notification_profile_inactivity">Inaktivit盲tsbenachrichtigung</string>
+    <string name="pref_screen_notification_profile_low_power">Warnung bei niedrigem Akku</string>
+    <string name="pref_screen_notification_profile_anti_loss">Warnung bei Kontaktverlust</string>
+    <string name="interval_fifteen_minutes">alle 15 Minuten</string>
+    <string name="interval_forty_five_minutes">alle 45 Minuten</string>
+    <string name="pref_title_support_voip_calls">VoIP App-Anrufe aktivieren</string>
+    <string name="title_activity_device_specific_settings">Ger盲tespezifische Einstellungen</string>
+    <string name="pref_title_authkey">Authentifizierungsschl眉ssel</string>
+    <string name="pref_summary_authkey">脛ndere den Authentifizierungsschl眉ssel in einen gemeinsamen Schl眉ssel auf all deinen Android-Ger盲ten, von denen aus du eine Verbindung herstellen m枚chtet. Der bisherige Standardschl眉ssel f眉r alle Ger盲te ist 0123456789@ABCDE</string>
+    <string name="devicetype_bfh16">BFH-16</string>
+    <string name="fw_upgrade_notice_amazfitcor2">Du bist dabei, die %s-Firmware auf deine Amazfit Cor 2 zu installieren.
+\n
+\nBitte stelle sicher, dass du die .fw-Datei und danach die .res-Datei installierst. Dein Band wird nach der Installation der .fw-Datei neu gestartet.
+\n
+\nHinweis: Du musst .res nicht installieren, wenn es genau das gleiche ist, wie die zuvor installierte.
+\n
+\nAUF EIGENE GEFAHR!
+\n
+\nVOLLST脛NDIG UNGETESTET, WAHRSCHEINLICH MUSST DU EINE BEATS_W FIRMWARE FLASHEN, WENN DEIN GER脛TENAME \"Amazfit Band 2\" IST</string>
+    <string name="dutch">Niederl盲ndisch</string>
+    <string name="turkish">T眉rkisch</string>
+    <string name="ukrainian">Ukrainisch</string>
+    <string name="arabic">Arabisch</string>
+    <string name="indonesian">Indonesisch</string>
+    <string name="thai">Thail盲ndisch</string>
+    <string name="vietnamese">Vietnamesisch</string>
+    <string name="portuguese">Portugiesisch</string>
+    <string name="devicetype_amazfit_cor2">Amazfit Cor 2</string>
+    <string name="devicetype_miband4">Mi Band 4</string>
+    <string name="fw_upgrade_notice_miband4">Du bist dabei, die Firmware %s auf dein Mi Band 4 zu installieren.
+\n
+\nBitte stelle sicher, dass du die .fw Datei und die .res Datei installierst. Dein Band wird nach der Installation der .fw Datei neu gestartet.
+\n
+\nHinweis: Du musst .res nicht installieren, wenn es genau das gleiche ist, wie die zuvor installierte.
+\n
+\nINSTALLATION AUF EIGENE GEFAHR!</string>
+    <string name="prefs_hr_alarm_activity">Herzfrequenzalarm bei sportlicher Aktivit盲t</string>
+    <string name="prefs_hr_alarm_low">Untere Grenze</string>
+    <string name="prefs_hr_alarm_high">Obere Grenze</string>
+    <string name="average">Mittelwert: %1$s</string>
+    <string name="pref_header_charts">Diagrammeinstellungen</string>
+    <string name="pref_title_charts_average">Durchschnittswerte im Diagramm anzeigen</string>
+    <string name="pref_title_charts_range">Diagrammbereich</string>
+    <string name="pref_charts_range_on">Diagrammbereich ist auf einen Monat eingestellt</string>
+    <string name="pref_charts_range_off">Diagrammbereich ist auf eine Woche eingestellt</string>
+    <string name="weekstepschart_steps_a_month">Schritte pro Monat</string>
+    <string name="weeksleepchart_sleep_a_month">Schlaf pro Monat</string>
+    <string name="devicetype_mijia_lywsd02">Mijia Smart Uhr</string>
+    <string name="menuitem_nfc">NFC</string>
+    <string name="pref_summary_expose_hr">Erm枚glicht anderen Anwendungen den Zugriff auf HR-Daten in Echtzeit, w盲hrend Gadgetbridge verbunden ist</string>
+    <string name="pref_title_expose_hr">Echtzeit-HR-Zugang von Drittanbietern</string>
+    <string name="pref_title_use_custom_font">Benutzerdefinierte Schriftart verwenden</string>
+    <string name="pref_summary_use_custom_font">Aktiviere das, wenn dein Ger盲t 眉ber eine benutzerdefinierte Schriftarten-Firmware f眉r die Emoji-Unterst眉tzung verf眉gt</string>
+    <string name="activity_DB_ExportButton">Daten exportieren</string>
+    <string name="activity_DB_import_button">Daten importieren</string>
+    <string name="activity_DB_delete_legacy_button">Alte Datenbank l枚schen</string>
+    <string name="activity_DB_empty_button">Leere Datenbank</string>
+    <string name="activity_db_management_empty_DB">Leere Datenbank</string>
+    <string name="activity_db_management_exportimport_label">Exportieren und Importieren</string>
+    <string name="widget_5_minutes">5 Minuten</string>
+    <string name="widget_10_minutes">10 Minuten</string>
+    <string name="widget_20_minutes">20 Minuten</string>
+    <string name="widget_1_hour">1 Stunde</string>
+    <string name="pref_display_add_device_fab">Neues Ger盲t koppeln</string>
+    <string name="pref_display_add_device_fab_on">Immer sichtbar</string>
+    <string name="pref_display_add_device_fab_off">Nur sichtbar, wenn noch kein Ger盲t hinzugef眉gt wurde</string>
+    <string name="activity_db_management_autoexport_explanation">Speicherort f眉r automatischen Datenbank-Export auf:</string>
+    <string name="activity_db_management_autoexport_label">Automatischer Export</string>
+    <string name="activity_DB_test_export_button">Automatischen Export jetzt starten</string>
+    <string name="activity_DB_test_export_message">Datenbank exportieren鈥�</string>
+    <string name="activity_db_management_empty_db_warning">Achtung! Wenn du auf diese Schaltfl盲che klickst, l枚schst du deine Datenbank und beginnst von Grund auf neu.</string>
+    <string name="appwidget_sleep_alarm_widget_label">Schlafalarm</string>
+    <string name="widget_listing_label">Status und Wecker</string>
+    <string name="widget_set_alarm_after">Wecker einstellen nach:</string>
+    <plurals name="widget_alarm_target_hours">
+        <item quantity="one">%d Stunde</item>
+        <item quantity="other">%d Stunden</item>
+    </plurals>
+    <string name="preferences_makibes_hr3_settings">Makibes HR3-Einstellungen</string>
+    <string name="devicetype_makibes_hr3">Makibes HR3</string>
+    <string name="devicetype_amazfit_bip_lite">Amazfit Bip Lite</string>
+    <string name="prefs_find_phone">Telefon finden</string>
+    <string name="prefs_enable_find_phone">\'Telefon finden\' einschalten</string>
+    <string name="prefs_find_phone_summary">Verwende dein Band, um den Klingelton deines Telefons wiederzugeben.</string>
+    <string name="prefs_find_phone_duration">Klingeldauer in Sekunden</string>
+    <string name="maximum_duration">Dauer</string>
+    <string name="discovery_need_to_enter_authkey">Dieses Ger盲t ben枚tigt einen geheimen Authentifikationsschl眉ssel, den du durch langes Dr眉cken auf den Ger盲tnamen eingeben kannst. Mehr Informationen gibt es auf der Gadgetbride-Webseite.</string>
+    <string name="fw_upgrade_notice_amazfitbip_lite">Du bist dabei, die Firmware %s auf deine Amazfit Bip Lite zu installieren.
+\n
+\nBitte stelle sicher, dass du die .fw Datei und danach die .res Datei installierst. Deine Uhr wird nach der Installation der .fw Datei neu gestartet.
+\n
+\nHinweis: Du musst .res nicht installieren, wenn es genau das gleiche ist, wie die zuvor installierte.
+\n
+\nINSTALLATION AUF EIGENE GEFAHR!</string>
+    <string name="devicetype_amazfit_gtr">Amazfit GTR</string>
+    <string name="fw_upgrade_notice_amazfitgtr">Du bist dabei, die Firmware %s auf deine Amazfit GTR zu installieren.
+\n
+\nBitte stelle sicher, dass du die .fw Datei, dann die .res Datei und schlie脽lich die .gps Datei installierst. Deine Uhr wird nach der Installation der .fw Datei neu gestartet.
+\n
+\nHinweis: Du musst die .res und .gps nicht installieren, wenn diese Dateien genau die gleichen sind wie die zuvor installierten.
+\n
+\nINSTALLATION AUF EIGENE GEFAHR!</string>
+    <string name="pref_chart_heartrate_color_red">Rot</string>
+    <string name="pref_chart_heartrate_color_orange">Orange</string>
+    <string name="pref_title_chart_heartrate_color">Farbe der Herzfrequenz</string>
+    <string name="pref_title_chart_sleep_rolling_24_hour">Schlafbereich</string>
+    <string name="pref_chart_sleep_rolling_24_on">Letzte 24 Stunden</string>
+    <string name="pref_chart_sleep_rolling_24_off">Mittag bis Mittag</string>
+    <string name="devicetype_amazfit_gts">Amazfit GTS</string>
+    <string name="fw_upgrade_notice_amazfitgts">Du bist dabei, die Firmware %s auf deine Amazfit GTS zu installieren.
+\n
+\nBitte stelle sicher, dass du die .fw Datei, dann die .res Datei und schlie脽lich die .gps Datei installierst. Deine Uhr wird nach der Installation der .fw Datei neu gestartet.
+\n
+\nHinweis: Du musst die .res und .gps nicht installieren, wenn diese Dateien genau die gleichen sind wie die zuvor installierten.
+\n 
+\nINSTALLATION AUF EIGENE GEFAHR!</string>
+    <string name="mi2_prefs_button_press_broadcast_default_value" translatable="false">nodomain.freeyourgadget.gadgetbridge.ButtonPressed</string>
+    <string name="devicetype_qhybrid">Fossil Q Hybrid</string>
+    <string name="watch_not_connected">Uhr nicht verbunden</string>
+    <string name="qhybrid_vibration_strength">Vibrationsst盲rke:</string>
+    <string name="qhybrid_goal_in_steps">Ziel in Schritten</string>
+    <string name="qhybrid_time_shift">Zeitverschiebung</string>
+    <string name="qhybrid_second_timezone_offset_relative_to_utc">Verschiebung der zweiten Zeitzone relativ zu UTC</string>
+    <string name="qhybrid_overwrite_buttons">Tasten 眉berschreiben</string>
+    <string name="qhybrid_use_activity_hand_as_notification_counter">Verwende Aktivit盲tsfortschrittzeiger als Benachrichtigungsz盲hler</string>
+    <string name="qhybrid_prompt_million_steps">Daf眉r bitte die Schrittzahl auf eine Million einstellen.</string>
+    <string name="qhybrid_buttons_overwrite_success">Tasten 眉berschrieben</string>
+    <string name="qhybrid_buttons_overwrite_error">Fehler beim 脺berschreiben der Tasten</string>
+    <string name="qhybrid_offset_timezone">Verschiebung der Zeitzone um</string>
+    <string name="qhybrid_changes_delay_prompt">脛nderung kann einige Sekunden dauern鈥�</string>
+    <string name="qhybrid_offset_time_by">Zeitverschiebung um</string>
+    <string name="pref_disable_new_ble_scanning">Deaktivieren des neuen BLE-Scans</string>
+    <string name="pref_summary_disable_new_ble_scanning">Aktiviere diese Option, wenn dein Ger盲t bei der Erkennung nicht gefunden wird</string>
+    <string name="devicetype_banglejs">Bangle.js</string>
+    <string name="devicetype_y5">Y5</string>
+    <string name="prefs_button_variable_actions">Detaillierte Einstellungen zu Tastendr眉cken</string>
+    <string name="prefs_button_long_press_action_selection_title">Lange Tastendruckaktion</string>
+    <string name="alarm_snooze">Schlummern</string>
+    <string name="error_no_location_access">Der Standortzugriff muss gew盲hrt werden und aktiviert sein, damit das Scannen ordnungsgem盲脽 funktioniert</string>
+    <string name="devicetype_itag">iTag</string>
+    <string name="pref_summary_allow_high_mtu">Erh枚ht die 脺bertragungsgeschwindigkeit, funktioniert aber m枚glicherweise nicht auf einigen Android-Ger盲ten.</string>
+    <string name="prefs_button_single_press_action_selection_title">Ausf眉hren bei Aktion 1</string>
+    <string name="prefs_button_double_press_action_selection_title">Ausf眉hren bei Aktion 2</string>
+    <string name="prefs_button_triple_press_action_selection_title">Ausf眉hren bei Aktion 3</string>
+    <string name="pref_title_allow_high_mtu">Gr枚脽ere MTU erlauben</string>
+    <string name="pref_summary_sync_calendar">Aktiviert Kalendererinnerungen, auch wenn die Verbindung getrennt ist</string>
+    <string name="pref_title_sync_caldendar">Kalenderereignisse synchronisieren</string>
+    <string name="hr_widget_heart_rate">Herzfrequenz</string>
+    <string name="hr_widget_steps">Schritte</string>
+    <string name="hr_widget_date">Datum</string>
+    <string name="hr_widget_active_minutes">Aktive Minuten</string>
+    <string name="hr_widget_calories">Kalorien</string>
+    <string name="hr_widget_battery">Akku</string>
+    <string name="hr_widget_weather">Wetter</string>
+    <string name="hr_widget_nothing">Nichts</string>
+    <string name="find_lost_device_you_found_it">Gefunden!</string>
+    <string name="pref_title_force_white_color_scheme">Schwarz-Wei脽-Farbschema erzwingen</string>
+    <string name="pref_summary_force_white_color_scheme">N眉tzlich, wenn deine Uhr dunkle Zeiger hat</string>
+    <string name="find_my_phone_notification">Mein Telefon finden</string>
+    <string name="notification_channel_high_priority_name">Hohe Priorit盲t</string>
+    <string name="pref_title_custom_deviceicon">Ger盲tespezifisches Benachrichtigungssymbol anzeigen</string>
+    <string name="pref_summary_custom_deviceicon">Zeige ein ger盲tespezifisches Android-Benachrichtigungssymbol anstelle des Gadgetbridge-Symbols, wenn eine Verbindung besteht</string>
+    <string name="hr_appname_wellness">Wellness</string>
+    <string name="hr_appname_workout">Training</string>
+    <string name="hr_appname_stopwatch">Stoppuhr</string>
+    <string name="pref_title_vibration_strength">Vibrationsst盲rke</string>
+    <string name="hr_appname_commute">Pendeln</string>
+    <string name="devicetype_amazfit_bips">Amazfit Bip S</string>
+    <string name="pref_summary_relax_firmware_checks">Firmware眉berpr眉fung deaktivieren</string>
+    <string name="pref_title_relax_firmware_checks">Aktiviere diese Option, wenn du eine Firmware flashen m枚chten, die nicht f眉r dein Ger盲t bestimmt ist (auf eigene Gefahr)</string>
+    <string name="pref_qhybrid_title_widget_draw_circles">Widget-Kreise zeichnen</string>
+    <string name="pref_header_auto_fetch">Automatischer Abruf</string>
+    <string name="pref_qhybrid_save_raw_activity_files">Raw Activity Dateien speichern</string>
+    <string name="menuitem_workout">Training</string>
+    <string name="menuitem_hr">Puls</string>
+    <string name="devicetype_watchxplus">Watch X Plus</string>
+    <string name="devicetype_watchx">Watch X</string>
+    <string name="prefs_sensors_button_bp_calibration">Kalibrierung</string>
+    <string name="pref_sensors_bp_calibration_high">Blutdruck SYSTOLISCH (hoch)</string>
+    <string name="pref_sensors_bp_calibration_low">Blutdruck DIASTOLISCH (niedrig)</string>
+    <string name="pref_sensors_bp_calibration">Blutdruckkalibrierung</string>
+    <string name="pref_title_sensors_altitude">H枚henkalibrierung</string>
+    <string name="pref_header_sensors_calibration">Sensorkalibrierung</string>
+    <string name="pref_title_device_spec_settings_force_time">Synchronisierungszeit erzwingen</string>
+    <string name="pref_header_device_spec_settings">Ger盲teeinstellungen</string>
+    <string name="pref_header_notifications_and_calls">Benachrichtigungen und Anrufe</string>
+    <string name="power_mode_watch">Nur Uhr</string>
+    <string name="power_mode_saving">Energiesparmodus</string>
+    <string name="power_mode_normal">Normal</string>
+    <string name="prefs_sensors_button_bp_calibration_sum">Dr眉cke hier, um die Kalibrierung zu starten</string>
+    <string name="pref_title_device_spec_settings_show_raw_graph">RAW-Daten im Aktivit盲tsdiagramm anzeigen</string>
+    <string name="power_mode_title">Uhr-Energiesparmodus</string>
+    <string name="menuitem_eventreminder">Ereigniserinnerung</string>
+    <string name="menuitem_pai">PAI</string>
+    <string name="pref_summary_device_spec_settings_title_force_time">Erzwinge automatische Zeitsynchronisation beim erneuten Verbinden. Analoge Uhrzeiger k枚nnen dabei die falsche Uhrzeit anzeigen!</string>
+    <string name="pref_summary_notifications_and_calls_title_shake_reject">Kopiere Aktion vom Uhr-Knopf</string>
+    <string name="prefs_notifications_and_calls_shake_reject">Sch眉ttelgeste Anruf ignorieren/zur眉ckzuweisen</string>
+    <string name="pref_summary_notifications_and_calls_title_reject">Aus - ignorieren, An - zur眉ckweisen</string>
+    <string name="prefs_notifications_and_calls_reject">Taste Anruf ignorieren/zur眉ckweisen</string>
+    <string name="pref_header_notifications_and_calls_callhandling">Anruf-Steuerung</string>
+    <string name="pref_title_notifications_and_calls_repeat_on_misscall">Wiederhole f眉r X Minuten</string>
+    <string name="pref_summary_notifications_and_calls_enable_misscall">Wiederhole min眉tlich</string>
+    <string name="pref_notifications_and_calls_enable_misscall">Benachrichtige verpasste Anrufe</string>
+    <string name="prefs_notifications_and_calls_continious_ring">Benachrichtigung wh盲rend dem Klingeln</string>
+    <string name="pref_title_notifications_and_calls_repeat_on_call">Anrufbenachrichtigung wiederholen</string>
+    <string name="title_activity_LenovoWatch_calibration">Watch X Plus-Kalibrierung</string>
+    <string name="hungarian">Ungarisch</string>
+    <string name="romanian">Rum盲nisch</string>
+    <string name="pref_theme_system">System</string>
+    <string name="czesh">Tschechisch</string>
+    <string name="swedish">Schwedisch</string>
+    <string name="hebrew">Hebr盲isch</string>
+    <string name="greek">Griechisch</string>
+    <string name="bip_prefs_shotcuts_summary">W盲hle die Verkn眉pfungen auf dem Band-Bildschirm</string>
+    <string name="menuitem_unknown">Unbekannt</string>
+    <string name="bip_prefs_shortcuts">Verkn眉pfungen</string>
+    <string name="hr_widget_last_notification">Letzte Benachrichtigung</string>
+    <string name="controlcenter_set_alias">Alias setzen</string>
+    <string name="fw_upgrade_notice_amazfit_trex">Du bist dabei, die Firmware %s auf deine Amazfit T-Rex zu installieren.
+\n
+\nBitte stelle sicher, dass du die .fw Datei, dann die .res Datei und schlie脽lich die .gps Datei installierst. Deine Uhr wird nach der Installation der .fw Datei neu gestartet.
+\n
+\nHinweis: Du musst die .res und .gps nicht installieren, wenn diese Dateien genau die gleichen sind wie die zuvor installierten. 
+\n
+\nINSTALLATION AUF EIGENE GEFAHR!</string>
+    <string name="devicetype_amazfit_trex">Amazfit T-Rex</string>
+    <string name="about_additional_contributions">Wir danken allen nicht gelisteten Mitwirkenden daf眉r uns zu unterst眉tzen mit Code, 脺bersetzung, Unterst眉tzung, Ideen, Motivation, Fehlermeldungen, Geld鈥� 鉁�</string>
+    <string name="about_additional_device_support">Unterst眉tzung zus盲tzlicher Ger盲te</string>
+    <string name="about_contributors">Beitragende</string>
+    <string name="about_core_team_title">Kernteam (Reihenfolge der ersten Mitwirkung am Code)</string>
+    <string name="about_description_generic">Cloudloser freier Ersatz f眉r propriet盲re Android-Gadget-Apps der Hersteller.</string>
+    <string name="about_activity_title_generic">脺ber Gadgetbridge</string>
+    <string name="about_title">脺ber</string>
+    <string name="pref_title_weather_summary">Wird f眉r den LineageOS Wetterdienst genutzt, andere Android-Versionen m眉ssen eine Anwendung (wie Breezy Weather) nutzen. Mehr Informationen gibt es auf der Gadgetbride-Webseite.</string>
+    <string name="menuitem_worldclock">Weltzeituhr</string>
+    <string name="error_version_check_extreme_caution">ACHTUNG: Fehler beim Pr眉fen der Information zur Version! Du solltest nicht fortfahren! Versionsname \"%s\" gefunden</string>
+    <string name="permission_granting_mandatory">Alle Berechtigungen sind notwendig und es kann zu Problemen f眉hren, wenn sie nicht genehmigt werden</string>
+    <string name="menuitem_stress">Stress</string>
+    <string name="menuitem_cycles">Zyklus-Tracking</string>
+    <string name="error_background_service_reason">Start des Hintergrunddienstes fehlgeschlagen wegen einem Fehler. Fehlermeldung:</string>
+    <string name="error_background_service_reason_truncated">Hintergrunddienst konnte nicht gestartet werden, wegen鈥�</string>
+    <string name="error_background_service">Start des Hintergrunddienstes fehlgeschlagen</string>
+    <string name="menuitem_breathing">Atem眉bungen</string>
+    <string name="devicetype_miband5">Mi Band 5</string>
+    <string name="fw_upgrade_notice_miband5">Du bist dabei, die Firmware %s auf dein Mi Band 5 zu installieren.
+\n
+\nBitte stelle sicher, dass du zuerst die .fw Datei und dann die .res Datei installierst. Dein Band wird nach der Installation der .fw Datei neu starten.
+\n
+\nHinweis: Die .res Datei muss nicht installiert werden, wenn es die gleiche Datei ist, die schon zuvor installiert wurde.
+\n
+\nINSTALLATION AUF EIGENE GEFAHR!</string>
+    <string name="companiondevice_pairing_details">Aktiviert die neue CompanionDevice-API-Unterst眉tzung (funktioniert nur bei Android 8 und neuer). Erh枚ht die Zuverl盲ssigkeit der Verbindung, wenn der Dienst im Hintergrund neu gestartet werden muss. Ben枚tigt eine erneute Kopplung mit Gadgetbridge, um zu funktionieren</string>
+    <string name="error_location_enabled_mandatory">Standort muss f眉r den Ger盲tescan aktiviert sein</string>
+    <string name="ignore_bonded_devices_description">Bei aktiver Option werden Ger盲te ignoriert, die beim Scan schon verbunden/gepaart sind</string>
+    <string name="ignore_bonded_devices">Ignoriere verbundene Ger盲te</string>
+    <string name="error_retrieving_devices_database">Fehler beim laden der Ger盲te aus der Datenbank</string>
+    <string name="error_setting_alias">Fehler beim setzen des Alias:</string>
+    <string name="error_exporting_device_preferences">Fehler beim Export der ger盲tespezifischen Einstellungen</string>
+    <string name="pref_check_permission_status_summary">Pr眉fen und nachfragen von fehlenden Berechtigungen, auch wenn sie nicht direkt ben枚tigt werden. Nur deaktivieren, wenn dieses Ger盲t eine der Features nicht unterst眉tzen soll. Eine Berechtigung nicht zu erteilen kann zu Problemen f眉hren!</string>
+    <string name="pref_check_permission_status">Berechtigungsstatus pr眉fen</string>
+    <string name="device_requires_key">SCHL脺SSEL NOTWENDIG, LANGES DR脺CKEN ZUR EINGABE</string>
+    <string name="device_is_currently_bonded">BEREITS VERBUNDEN</string>
+    <string name="companiondevice_pairing">CompanionDevice Ger盲tepaarung</string>
+    <string name="require_location_provider">Standort muss aktiviert sein</string>
+    <string name="devicetype_tlw64">TLW64</string>
+    <string name="devicetype_pinetime_jf">PimeTime (JF Firmware)</string>
+    <string name="activity_detail_show_gps_label">GPS-Track anzeigen</string>
+    <string name="activity_detail_duration_label">Dauer</string>
+    <string name="activity_detail_end_label">Ende</string>
+    <string name="activity_detail_start_label">Start</string>
+    <string name="Steps">Schritte</string>
+    <string name="Activity">Aktivit盲t</string>
+    <string name="Speed">Geschwindigkeit</string>
+    <string name="Distance">Strecke</string>
+    <string name="Swimming">Schwimmen</string>
+    <string name="laps_unit">Runden</string>
+    <string name="swim_style">Schwimmstil</string>
+    <string name="km_h">km/h</string>
+    <string name="meters_second">m/s</string>
+    <string name="steps_unit">Schritte</string>
+    <string name="cm">cm</string>
+    <string name="meters">m</string>
+    <string name="laps">Runden</string>
+    <string name="averageLapPace">Durchschnittliches Rundentempo</string>
+    <string name="averageKMPaceSeconds">Tempo</string>
+    <string name="averageHR">Herzfrequenz</string>
+    <string name="maxPace">Schnellstes Tempo</string>
+    <string name="minPace">Langsamstes Tempo</string>
+    <string name="caloriesBurnt">Kalorien</string>
+    <string name="activeSeconds">Aktiv</string>
+    <string name="steps">Schritte</string>
+    <string name="minAltitude">Minimale H枚he</string>
+    <string name="maxAltitude">Maximale H枚he</string>
+    <string name="descentMeters">H枚henverlust</string>
+    <string name="ascentMeters">Bergauf</string>
+    <string name="distanceMeters">Strecke</string>
+    <string name="activity_summary_detail">Detail der Sportaktivit盲t</string>
+    <string name="maxSpeed">Max Geschwindigkeit</string>
+    <string name="prefs_events_forwarding_action_title">Aktion ausf眉hren</string>
+    <string name="prefs_events_forwarding_startnonwear">Beim nicht tragend</string>
+    <string name="prefs_events_forwarding_wokeup">Beim Aufwachen</string>
+    <string name="prefs_events_forwarding_fellsleep">Beim Einschlafen</string>
+    <string name="activity_type_yoga">Yoga</string>
+    <string name="activity_type_swimming_openwater">Schwimmen (Freiwasser)</string>
+    <string name="Strokes">Z眉ge</string>
+    <string name="calories_unit">kcal</string>
+    <string name="activity_type_jump_roping">Seilspringen</string>
+    <string name="activity_type_elliptical_trainer">Crosstrainer</string>
+    <string name="activity_type_indoor_cycling">Spinning</string>
+    <string name="strokes_second">Z眉ge/s</string>
+    <string name="swimStyle">Schwimmstil</string>
+    <string name="seconds">sek</string>
+    <string name="minutes_km">min/km</string>
+    <string name="seconds_m">sek/m</string>
+    <string name="ascentSeconds">Aufsteigend</string>
+    <string name="backstroke">R眉ckenschwimmen</string>
+    <string name="freestyle">Freistil</string>
+    <string name="breaststroke">Brustschwimmen</string>
+    <string name="activity_summaries_all_activities">Alle Aktivit盲ten</string>
+    <string name="activity_summaries_statistics">Statistiken</string>
+    <string name="activity_filter_date_to">Bis</string>
+    <string name="activity_filter_date_from">Von</string>
+    <string name="km">km</string>
+    <string name="devicetype_sg2">Lemfo SG2</string>
+    <string name="activity_summary_yesterday">Gestern</string>
+    <string name="activity_summary_today">Heute</string>
+    <string name="medley">Gemisch</string>
+    <string name="activity_filter_apply_filter">Filter anwenden</string>
+    <string name="activity_filter_name_contains">Beschriftung</string>
+    <string name="activity_filter_filter_title">Filter</string>
+    <string name="activity_filter_reset_filter">Filter zur眉cksetzen</string>
+    <string name="pref_header_statistics">Statistiken der Sportaktivit盲ten</string>
+    <string name="pref_header_filter">Filter f眉r Sportaktivit盲ten</string>
+    <string name="prefs_events_forwarding_broadcast_title">Broadcast Nachricht</string>
+    <string name="prefs_events_forwarding_title">Ger盲teaktionen</string>
+    <string name="prefs_events_forwarding_summary">Nutze Ger盲teereignisse um Aktionen und Android broadcasts auszul枚sen</string>
+    <string name="Elevation">H枚he</string>
+    <string name="bpm">bpm</string>
+    <string name="seconds_km">sek/km</string>
+    <string name="strokes_unit">Z眉ge</string>
+    <string name="flatSeconds">Flach</string>
+    <string name="averageStrokesPerSecond">Durchschnittliche Schl盲ge</string>
+    <string name="averageStrokeDistance">Durchschnittliche Schlagdistanz</string>
+    <string name="averageStride">Mittlere Schrittl盲nge</string>
+    <string name="totalStride">Gesamtfortschritt</string>
+    <string name="activity_summary_edit_name_title">Beschriftung bearbeiten</string>
+    <string name="swolf_index">Swolf-Index</string>
+    <string name="baseAltitude">Basish枚he</string>
+    <string name="swolfIndex">SWOLF</string>
+    <string name="activity_filter_individual_items">Individuell ausgew盲hlte Elemente</string>
+    <string name="addto_filter">Zu Filter hinzuf眉gen</string>
+    <string name="activity_summaries_all_devices">Alle Ger盲te</string>
+    <string name="sports_activity_quick_filter_select">Zeitraum</string>
+    <string name="sports_activity_quick_filter_30days">30 Tage</string>
+    <string name="sports_activity_quick_filter_7days">7 Tage</string>
+    <string name="sports_activity_quick_filter_last_week">Vorherige Woche</string>
+    <string name="sports_activity_quick_filter_last_month">Vorheriger Monat</string>
+    <string name="sports_activity_quick_filter_this_month">Dieser Monat</string>
+    <string name="sports_activity_quick_filter_this_week">Diese Woche</string>
+    <string name="no">Nein</string>
+    <string name="yes">Ja</string>
+    <string name="averageSpeed">Durchschnittliche Geschwindigkeit</string>
+    <string name="about_links">Links</string>
+    <string name="activity_type_badminton">Badminton</string>
+    <string name="activity_type_pingpong">Tischtennis</string>
+    <string name="activity_type_basketball">Basketball</string>
+    <string name="activity_type_cricket">Kricket</string>
+    <string name="activity_type_rowing_machine">Ruderger盲t</string>
+    <string name="activity_type_soccer">Fu脽ball</string>
+    <string name="activity_filter_to_placeholder">Heute</string>
+    <string name="activity_filter_from_placeholder">Vergangenheit</string>
+    <string name="chart_no_active_data">Keine Aktivit盲ten festgestellt.</string>
+    <string name="devicetype_sonyswr12">Sony-SWR12</string>
+    <string name="sonyswr12_settings_title">Sony SWR12 Einstellungen</string>
+    <string name="sonyswr12_settings_low_vibration">Geringe Vibration aktiviert</string>
+    <string name="sonyswr12_settings_stamina">Der Energiesparmodus ist aktiviert</string>
+    <string name="sonyswr12_settings_alarm_interval">Intelligentes Alarmintervall in Minuten</string>
+    <string name="sonyswr12_settings_low_vibration_summary">Aktiviere eine geringe Vibrationsintensit盲t auf dem Band</string>
+    <string name="sonyswr12_settings_stamina_summary">Der Energiesparmodus schaltet die regelm盲脽ige automatische Messung der Herzfrequenz aus und verl盲ngert so die Arbeitszeit</string>
+    <string name="sonyswr12_settings_alarm_interval_summary">Das intelligente Alarmintervall ist das Intervall vor dem installierten Alarm. In diesem Intervall versucht das Ger盲t, die leichteste Schlafphase zu erkennen, um den Benutzer zu wecken</string>
+    <string name="chart_get_active_and_synchronize">F眉hre einige Aktivit盲ten durch und synchronisiere das Ger盲t.</string>
+    <string name="devicetype_nut_mini">Nut mini</string>
+    <string name="descentSeconds">Absteigend</string>
+    <string name="devicetype_lefun">Lefun</string>
+    <string name="activity_error_no_app_for_png">Um diesen Screenshot zu teilen installiere eine App die Bilder verarbeiten kann.</string>
+    <string name="activity_prefs_chart_min_session_length">Minimale Aktivit盲tsdauer (Minuten)</string>
+    <string name="activity_prefs_chart_max_idle_phase_length">Pausenl盲nge zum Trennen von Aktivit盲ten (Minuten)</string>
+    <string name="activity_prefs_chart_min_steps_per_minute">Minimale Schritte pro Minute zur Erkennung einer Aktivit盲t (Gehen)</string>
+    <string name="activity_prefs_chart_min_steps_per_minute_for_run">Minimale Schritte pro Minute zur Erkennung einer Laufaktivit盲t</string>
+    <string name="lefun_prefs_interface_language_title">Sprache der Benutzeroberfl盲che</string>
+    <string name="lefun_prefs_antilost_summary">Das Band wird vibrieren, wenn dein Telefon die Verbindung zum Band unterbricht</string>
+    <string name="lefun_prefs_antilost_title">Anti-Verlust</string>
+    <string name="lefun_prefs_hydration_reminder_interval_title">Intervall der Trinkerinnerung (in Minuten)</string>
+    <string name="lefun_prefs_hydration_reminder_summary">Das Band wird vibrieren, um dich daran zu erinnern, Wasser zu trinken</string>
+    <string name="lefun_prefs_hydration_reminder_title">Trinkerinnerung</string>
+    <string name="firmware_update_progress">Upload wird ausgef眉hrt
+\n%1d%% bei %.2fkbps (durchschnittlich %.2fkbps)
+\nTeil %1d von %1d</string>
+    <string name="devicestatus_upload_failed">Upload ist fehlgeschlagen</string>
+    <string name="devicestatus_upload_aborted">Upload wurde abgebrochen!</string>
+    <string name="devicestatus_upload_validating">Upload wird 眉berpr眉ft</string>
+    <string name="devicestatus_upload_completed">Upload abgeschlossen</string>
+    <string name="devicestatus_disconnected">Ger盲t getrennt!</string>
+    <string name="devicestatus_disconnecting">Ger盲t trennt die Verbindung!</string>
+    <string name="devicestatus_upload_started">Upload hat begonnen</string>
+    <string name="devicestatus_upload_starting">Upload beginnt</string>
+    <string name="devicestatus_connected">Ger盲t ist verbunden</string>
+    <string name="devicestatus_connecting">Ger盲t stellt eine Verbindung her</string>
+    <string name="charts_activity_list">Aktivit盲tsliste</string>
+    <string name="pref_title_lower_button_function_double">Untere Taste doppelt</string>
+    <string name="pref_title_middle_button_function_double">Mittlere Taste doppelt</string>
+    <string name="pref_title_upper_button_function_double">Obere Taste doppelt</string>
+    <string name="pref_title_lower_button_function_long">Untere Taste lang</string>
+    <string name="pref_title_middle_button_function_long">Mittlere Taste lang</string>
+    <string name="pref_title_upper_button_function_long">Obere Taste lang</string>
+    <string name="pref_title_lower_button_function_short">Untere Taste kurz</string>
+    <string name="pref_title_middle_button_function_short">Mittlere Taste kurz</string>
+    <string name="pref_title_upper_button_function_short">Obere Taste kurz</string>
+    <string name="activity_prefs_step_length_cm">Schrittl盲nge in cm</string>
+    <string name="qhybrid_calibration_align_hint">Verwende die Tasten unten, um die Uhrzeiger auf 12:00 Uhr auszurichten.</string>
+    <string name="devicetype_amazfit_band5">Amazfit Band 5</string>
+    <string name="charts_min_max_heartrate_popup">Niedrigste Herzfrequenz: %1$d
+\nH枚chste Herzfrequenz: %2$d
+\nBewegungsintensit盲t: %3$s</string>
+    <string name="gps_track">GPS-Track</string>
+    <string name="about_version">Version %s</string>
+    <string name="menuitem_spo2">SpO2</string>
+    <string name="devicetype_amazfit_bips_lite">Amazfit Bip S Lite</string>
+    <string name="charts_legend_heartrate_average">Durchschnittliche Herzfrequenz</string>
+    <string name="find_lost_device_message">Suche nach %1$s\?</string>
+    <string name="menuitem_stopwatch">Stoppuhr</string>
+    <string name="menuitem_dnd">Nicht st枚ren</string>
+    <string name="menuitem_alexa">Alexa</string>
+    <string name="menuitem_takephoto">Kamera-Fernausl枚sung</string>
+    <string name="menuitem_mutephone">Telefon stummschalten</string>
+    <string name="menuitem_findphone">Telefon suchen</string>
+    <string name="devicetype_amazfit_gtr2">Amazfit GTR 2</string>
+    <string name="devicetype_amazfit_gts2">Amazfit GTS 2</string>
+    <string name="movement_intensity">Bewegungsintensit盲t</string>
+    <string name="activity_list_summary_activities">Aktivit盲ten</string>
+    <string name="activity_list_summary_intensity">Bewegungs-
+\nintensit盲t</string>
+    <string name="activity_list_summary_active_time">Aktive Zeit</string>
+    <string name="activity_list_summary_distance">Strecke</string>
+    <string name="activity_list_summary_active_steps">Aktive Schritte</string>
+    <string name="devicetype_casiogbx100">Casio GBX-100</string>
+    <string name="prefs_charts_tabs_summary">Sichtbare Chart-Tabs</string>
+    <string name="prefs_charts_tabs">Chart-Tabs</string>
+    <string name="devicetype_amazfit_bipu">Amazfit Bip U</string>
+    <string name="menuitem_pomodoro">Pomodoro Tracker</string>
+    <string name="menuitem_sleep">Schlaf</string>
+    <string name="menuitem_goal">Aktivit盲tsziel</string>
+    <string name="prefs_autoremove_message">Automatisches Entfernen von SMS-Benachrichtigungen</string>
+    <string name="prefs_fake_ring_duration">Falsches Dauerklingeln</string>
+    <string name="prefs_operating_sounds">Tastent枚ne</string>
+    <string name="prefs_key_vibration">Tastenvibration</string>
+    <string name="prefs_autolight">Automatisches Licht</string>
+    <string name="devicetype_amazfit_vergel">Amazfit Verge Lite</string>
+    <string name="discovery_entered_invalid_authkey">Der eingegebene geheime Authentifikationsschl眉ssel ist ung眉ltig! Dr眉cke zum Bearbeiten lange auf das Ger盲t.</string>
+    <string name="pref_summary_connected_advertisement">Macht das Ger盲t trotz bestehender Bluetoothverbindung auffindbar</string>
+    <string name="pref_title_connected_advertisement">Sichtbar, w盲hrend eine Verbindung besteht</string>
+    <string name="fw_upgrade_notice_amazfitvergel">Du bist dabei, die Firmware %s auf deine Amazfit Verge Lite zu installieren.
+\n
+\nBitte stelle sicher, dass du die .fw Datei, dann die .res Datei und schlie脽lich die .gps Datei installierst. Deine Uhr wird nach der Installation der .fw Datei neu gestartet.
+\n
+\nHinweis: Du musst die .res und .gps nicht installieren, wenn diese Dateien genau die gleichen sind wie die zuvor installierten.
+\n
+\nINSTALLATION AUF EIGENE GEFAHR!</string>
+    <string name="widget_settings_select_device_title">Ger盲t ausw盲hlen</string>
+    <string name="dbmanagementactivity_export_confirmation">Die Daten wirklich exportieren\? Zuvor exportierte Aktivit盲tsdaten (falls vorhanden) und Einstellungen werden 眉berschrieben.</string>
+    <string name="dbmanagementactivity_export_data_title">Daten exportieren\?</string>
+    <string name="activity_DB_ShowContentButton">Export-/Importverzeichnisinhalt anzeigen</string>
+    <string name="activity_data_management_directory_content_title">Verzeichnisinhalt exportieren/importieren</string>
+    <string name="devicetype_amazfit_bipupro">Amazfit Bip U Pro</string>
+    <string name="activity_db_management_clean_export_directory_text">Auf exportierte Dateien im Export/Import-Verzeichnis kann jede App auf deinem Ger盲t zugreifen. M枚glicherweise m枚chtest du diese Dateien nach der Synchronisierung oder Sicherung entfernen. Stelle sicher, dass du ein Backup hast, bevor du sie l枚schst. GPX-Dateien, Unterverzeichnisse und automatisch exportierte Datenbankdateien (falls vorhanden) werden nicht gel枚scht. Der Pfad zum Export/Import-Verzeichnis lautet:</string>
+    <string name="dbmanagementactivity_error_cleaning_export_directory">Fehler beim L枚schen der Dateien aus dem Exportverzeichnis %1$s</string>
+    <string name="dbmanagementactivity_export_finished">L枚schen abgeschlossen</string>
+    <string name="activity_DB_clean_export_directory_warning_message">Dateien im Export/Import-Verzeichnis wirklich l枚schen\?</string>
+    <string name="activity_DB_clean_export_directory_warning_title">Dateien im Export/Import-Verzeichnis l枚schen\?</string>
+    <string name="activity_db_management_clean_export_directory_label">Dateien im Export/Import-Verzeichnis l枚schen</string>
+    <string name="devicetype_amazfit_neo">Amazfit Neo</string>
+    <string name="activity_type_strength_training">Krafttraining</string>
+    <string name="mi">mi</string>
+    <string name="minutes_mi">min/mi</string>
+    <string name="mi_h">mi/h</string>
+    <string name="ft">ft</string>
+    <string name="devicetype_amazfit_gts2_mini">Amazfit GTS 2 Mini</string>
+    <string name="devicetype_zepp_e">Zepp E</string>
+    <string name="devicetype_amazfit_gtr2e">Amazfit GTR 2e</string>
+    <string name="devicetype_waspos">Wasp-os</string>
+    <string name="battery_detail_activity_title">Akku-Information</string>
+    <string name="devicetype_amazfit_x">Amazfit X</string>
+    <string name="devicetype_amazfit_gts2e">Amazfit GTS 2e</string>
+    <string name="fw_upgrade_notice_amazfitx">Du bist dabei, die Firmware %s auf dein Amazfit X zu installieren.
+\n
+\nBitte stelle sicher, dass du zuerst die .fw Datei und dann die .res Datei installierst. Dein Band wird nach der Installation der .fw Datei neu starten.
+\n
+\nHinweis: Die .res Datei muss nicht installiert werden, wenn es die gleiche Datei ist, die schon zuvor installiert wurde.
+\n
+\nINSTALLATION AUF EIGENE GEFAHR!</string>
+    <string name="calendar_year">Jahr</string>
+    <string name="calendar_six_months">6 Monate</string>
+    <string name="calendar_month">Monat</string>
+    <string name="calendar_two_weeks">Zwei Wochen</string>
+    <string name="calendar_week">Woche</string>
+    <string name="calendar_day">Tag</string>
+    <string name="battery_level">Akkustand</string>
+    <string name="show_ongoing_activity">Popup f眉r laufende Aktivit盲ten anzeigen</string>
+    <string name="dialog_hide">Ausblenden</string>
+    <string name="calendar_three_months">3 Monate</string>
+    <string name="fossil_hr_auth_failed">Authentifizierung fehlgeschlagen, eingeschr盲nkte Funktionalit盲t</string>
+    <string name="fossil_hr_unavailable_unauthed">Im unauthentifizierten Modus nicht verf眉gbar</string>
+    <string name="fossil_hr_synced_activity_data">Synchronisierte Aktivit盲tsdaten</string>
+    <string name="fossil_hr_commute_processing">Anfrage wird verarbeitet鈥�</string>
+    <string name="fossil_hr_warning_firmware_too_new">Einige Funktionen sind deaktiviert, weil die Firmware der Uhr zu neu ist</string>
+    <string name="qhybrid_title_calibration">Kalibrierung</string>
+    <string name="qhybrid_title_apps_management">App-Verwaltung</string>
+    <string name="qhybrid_title_file_management">Dateiverwaltung</string>
+    <string name="qhybrid_title_background_image">Hintergrundbild</string>
+    <string name="qhybrid_title_apps">Apps</string>
+    <string name="devicetype_um25">UM-25</string>
+    <string name="prefs_sounds_summary">Festlegen, wann das Ger盲t einen Signalton abgibt</string>
+    <string name="prefs_sounds">T枚ne</string>
+    <string name="fw_upgrade_notice_amazfitneo">Du bist dabei, die Firmware %s auf dein Amazfit Neo zu installieren.
+\n
+\nDein Band wird nach der Installation der .fw Datei neu starten.
+\n
+\nINSTALLATION AUF EIGENE GEFAHR!</string>
+    <string name="menuitem_temperature">Temperatur</string>
+    <string name="menuitem_widgets">Widgets</string>
+    <string name="menuitem_events">Ereignisse</string>
+    <string name="devicetype_miband6">Mi Band 6</string>
+    <string name="fw_upgrade_notice_miband6">Du bist dabei, die Firmware %s auf dein Mi Band 6 zu installieren.
+\n
+\nBitte stelle sicher, dass du die .fw Datei und die .res Datei installierst. Dein Band wird nach der Installation der .fw Datei neu gestartet.
+\n
+\nHinweis: Du musst .res nicht installieren, wenn es genau die gleiche ist, wie die zuvor installierte.
+\n
+\nINSTALLATION AUF EIGENE GEFAHR!</string>
+    <string name="fossil_hr_commute_actions_explanation">Die hier konfigurierten Aktionen werden in der Commute-App auf deiner Uhr angezeigt. Mehr Informationen gibt es auf der Gadgetbride-Webseite.</string>
+    <string name="kind_agps_bundle">AGPS-Bundle</string>
+    <string name="qhybrid_calibration_100_steps">100 Schritte</string>
+    <string name="qhybrid_calibration_10_steps">10 Schritte</string>
+    <string name="qhybrid_calibration_1_step">1 Schritt</string>
+    <string name="qhybrid_calibration_clockwise">Im Uhrzeigersinn</string>
+    <string name="qhybrid_calibration_counterclockwise">Gegen den Uhrzeigersinn</string>
+    <string name="fossil_hr_new_action">Neue Aktion</string>
+    <string name="fossil_hr_new_action_cancel">abbrechen</string>
+    <string name="fossil_hr_edit_action">Aktion bearbeiten</string>
+    <string name="fossil_hr_edit_action_delete">l枚schen</string>
+    <string name="pref_summary_canned_messages_dismisscall">Abgewiesene Anrufe von der Uhr mit einer SMS-Nachricht beantworten</string>
+    <string name="pref_summary_canned_messages_set">Sende die unten konfigurierten Nachrichten an dein Ger盲t</string>
+    <string name="qhybrid_summary_calibration">Kalibrieren der Uhrzeiger</string>
+    <string name="qhybrid_summary_file_management">Hochladen und Herunterladen von Dateien</string>
+    <string name="pref_summary_physical_buttons">Konfiguriere die Funktionalit盲t der physischen Tasten an der Uhr</string>
+    <string name="pref_title_physical_buttons">Physische Tasten</string>
+    <string name="watchface_install_info">Du bist dabei, das folgende Zifferblatt zu installieren:
+\n
+\n%1$s
+\nVersion %2$s von %3$s
+\n</string>
+    <string name="qhybrid_watchface_configuration_old_firmware">Zifferblattkonfiguration f眉r Uhren mit Firmware-Version DN1.0.2.19r und niedriger</string>
+    <string name="qhybrid_title_watchface">Zifferblattkonfiguration</string>
+    <string name="qhybrid_pref_title_external_intents">Gef盲hrliche externe Eingriffe zulassen</string>
+    <string name="qhybrid_pref_summary_external_intents">Erlaubt anderen Android-Apps das Hochladen/脺berschreiben von Dateien</string>
+    <string name="pref_summary_developer_settings">Von Entwicklern verwendete Einstellungen und Funktionen</string>
+    <string name="pref_title_developer_settings">Entwicklereinstellungen</string>
+    <string name="qhybrid_pref_summary_actions">Aktionen f眉r die Commute-App</string>
+    <string name="qhybrid_pref_title_actions">Aktionen</string>
+    <string name="notification_channel_low_battery_name">Geringer Akkustand</string>
+    <string name="notification_channel_transfer_name">Datentransfer</string>
+    <string name="devicetype_amazfit_trex_pro">Amazfit T-Rex Pro</string>
+    <string name="menuitem_barometer">Barometer</string>
+    <string name="pref_theme_black_background">Schwarzen Hintergrund im schwarzen Farbschema verwenden</string>
+    <string name="watchface_setting_wrist_flick_duration">Dauer (in ms)</string>
+    <string name="watchface_setting_wrist_flick_minute">Minutenzeiger (-360 bis 360)</string>
+    <string name="watchface_setting_wrist_flick_hour">Stundenzeiger (-360 bis 360)</string>
+    <string name="watchface_setting_wrist_flick_move_relative">Zeiger relativ zur Zeit bewegen</string>
+    <string name="watchface_setting_desc_wrist_flick">(zum vollst盲ndigen Deaktivieren aktiviere die relative Bewegung und setze alle Werte auf 0)</string>
+    <string name="watchface_setting_title_wrist_flick">Handgelenkbewegung</string>
+    <string name="watchface_setting_display_refresh_partial">Teilaktualisierung (in Minuten)</string>
+    <string name="watchface_setting_display_refresh_full">Vollst盲ndige Aktualisierung (in Minuten)</string>
+    <string name="watchface_setting_title_display_refresh_timeout">Zeit眉berschreitung bei der Anzeigeaktualisierung</string>
+    <string name="watchface_dialog_title_settings">Zifferblatteinstellungen</string>
+    <string name="watchface_dialog_widget_color_black">Schwarz</string>
+    <string name="watchface_dialog_widget_color_white">Wei脽</string>
+    <string name="watchface_dialog_widget_color">Farbe</string>
+    <string name="qhybrid_title_watchface_designer">Zifferblattdesigner</string>
+    <string name="watchface_dialog_widget_preset_right">Rechts</string>
+    <string name="watchface_dialog_widget_preset_left">Links</string>
+    <string name="watchface_dialog_widget_preset_bottom">Unten</string>
+    <string name="watchface_dialog_widget_preset_top">Oben</string>
+    <string name="watchface_dialog_widget_presets">Positionsvorgaben</string>
+    <string name="watchface_dialog_widget_y_coordinate">Y-Koordinate (max. 240)</string>
+    <string name="watchface_dialog_widget_x_coordinate">X-Koordinate (max. 240)</string>
+    <string name="watchface_dialog_widget_type">Art</string>
+    <string name="appmanager_app_edit">Bearbeiten</string>
+    <string name="button_watchface_save_apply">Speichern und anwenden</string>
+    <string name="button_watchface_preview">Vorschau auf der Uhr</string>
+    <string name="watchface_widget_type_heart_rate">Herzfrequenz</string>
+    <string name="watchface_widget_type_steps">Schritte</string>
+    <string name="watchface_widget_type_weather">Wetter</string>
+    <string name="watchface_widget_type_date">Datum</string>
+    <string name="watchface_dialog_title_add_widget">Widget hinzuf眉gen</string>
+    <string name="watchface_toast_settings_incomplete">Einstellungen unvollst盲ndig, Widget nicht hinzugef眉gt</string>
+    <string name="watchface_dialog_title_set_name">Zifferblattnamen festlegen</string>
+    <string name="button_watchface_settings">Zifferblatteinstellungen</string>
+    <string name="button_watchface_add_widget">Widget hinzuf眉gen</string>
+    <string name="button_watchface_select_image">Hintergrundbild 盲ndern</string>
+    <string name="button_watchface_edit_name">Name bearbeiten</string>
+    <string name="gpx_receiver_overwrite_some_files">Einige Datei(en) sind bereits vorhanden. 脺berschreiben\?</string>
+    <string name="gpx_receiver_files_received">GPX-Datei(en) empfangen:</string>
+    <string name="gpx_receiver_activity_title">GPX-Empf盲nger Gadgetbridge</string>
+    <string name="devicetype_smaq2oss">SMA-Q2 OSS</string>
+    <string name="activity_summary_detail_editing_gpx_track">Verkn眉pften GPX-Track bearbeiten</string>
+    <string name="activity_summary_detail_clear_gpx_track">GPX-Track l枚schen</string>
+    <string name="activity_summary_detail_select_gpx_track">GPX-Track ausw盲hlen</string>
+    <string name="set">Festlegen</string>
+    <string name="watchface_upload_failed">Hochladen des Zifferblatts ist fehlgeschlagen. Bitte versuche es erneut.</string>
+    <string name="watchface_cache_confirm_overwrite">Ein Zifferblatt mit diesem Namen existiert bereits im Cache. Willst du es 眉berschreiben\?</string>
+    <string name="getting_heart_rate">Messung</string>
+    <string name="heart_rate_result">Messergebnisse</string>
+    <string name="blood_pressure">Blutdruck</string>
+    <string name="watchface_setting_title_power_saving">Energiesparmodus</string>
+    <string name="watchface_widget_type_chance_rain">Regenwahrscheinlichkeit</string>
+    <string name="watchface_widget_type_active_mins">Aktive Minuten</string>
+    <string name="watchface_widget_type_2nd_tz">Zweite Zeitzone</string>
+    <string name="watchface_widget_type_calories">Kalorien</string>
+    <string name="watchface_widget_type_battery">Akku</string>
+    <string name="watchface_setting_power_saving_hands">Zeigerbewegung deaktivieren, wenn Uhr nicht getragen wird</string>
+    <string name="watchface_setting_power_saving_display">Bildschirmaktualisierungen deaktivieren, wenn Uhr nicht getragen wird</string>
+    <string name="pref_summary_huami_force_new_protocol">Aktivieren, wenn das Ger盲t nach einem Firmware-Upgrade keine Verbindung mehr herstellt</string>
+    <string name="pref_title_huami_force_new_protocol">Neues Auth-Protokoll</string>
+    <string name="menuitem_flashlight">Taschenlampe</string>
+    <string name="prefs_autoheartrate_interval">H盲ufigkeit der Messungen</string>
+    <string name="prefs_autoheartrate_sleep">Im Schlaf Messungen vornehmen</string>
+    <string name="prefs_autoheartrate_measurement">Automatische Herzfrequenzmessungen</string>
+    <string name="prefs_autoheartrate_summary">Periodische Herzfrequenzmessungen w盲hrend des Tages und auch w盲hrend des Schlafens</string>
+    <string name="prefs_autoheartrate">Automatische Herzfrequenz</string>
+    <string name="enable_notifications_summary">Benachrichtigungen f眉r Anrufe, Nachrichten und mehr</string>
+    <string name="enable_vibrations_summary">Vibrationen f眉r Anrufe, Nachrichten, Benachrichtigungen und mehr</string>
+    <string name="prefs_notifications_enable">Benachrichtigungen aktivieren</string>
+    <string name="prefs_vibration_enable">Vibrationen aktivieren</string>
+    <string name="prefs_sleep_time_summary">Gibt Zeiten an, zu denen Schlaf registriert wird</string>
+    <string name="prefs_sleep_time_label">Schlafstunden definieren</string>
+    <string name="prefs_sleep_time">Schlafzeiten</string>
+    <string name="devicetype_fitpro">FitPro</string>
+    <string name="pref_title_ping_tone">Klingelton</string>
+    <string name="devicetype_nothingear1">Nothing Ear (1)</string>
+    <string name="nothing_prefs_inear_summary">Wiedergabe/Pause der Musik, je nachdem, ob du die Ohrh枚rer tr盲gst</string>
+    <string name="nothing_prefs_inear_title">In-Ear-Erkennung</string>
+    <string name="nothing_prefs_audiomode_title">Audiomodus</string>
+    <string name="title_activity_notification_management">Benachrichtigungseinstellungen</string>
+    <string name="pref_header_notification_application_settings">Pro App einstellen</string>
+    <string name="uncheck_all_applications">Alle Apps abw盲hlen</string>
+    <string name="pref_message_privacy_mode_complete">Alle Inhalte ausblenden</string>
+    <string name="pref_title_notification_use_as">Verwende die App-Liste, um鈥�</string>
+    <string name="pref_title_notification_use_as_deny">Benachrichtigungen von ausgew盲hlten Apps ablehnen</string>
+    <string name="pref_title_notification_use_as_allow">Benachrichtigungen von ausgew盲hlten Apps zulassen</string>
+    <string name="pref_title_message_privacy_mode">Datenschutzmodus f眉r Nachrichten</string>
+    <string name="pref_message_privacy_mode_off">Alle Inhalte anzeigen</string>
+    <string name="check_all_applications">Alle Apps ausw盲hlen</string>
+    <string name="pref_applications_settings">Liste der Apps</string>
+    <string name="pref_header_other">Andere</string>
+    <string name="devicetype_sony_wh_1000xm3">Sony WH-1000XM3</string>
+    <string name="devicetype_galaxybuds_live">Galaxy Buds Live</string>
+    <string name="pref_title_equalizer_normal">Normal</string>
+    <string name="pref_header_equalizer">Equalizer</string>
+    <string name="pref_header_system">System</string>
+    <string name="devicetype_galaxybuds">Galaxy Buds</string>
+    <string name="prefs_equalizer_preset">Equalizer-Voreinstellungen</string>
+    <string name="pref_title_equalizer_dynamic">Dynamisch</string>
+    <string name="prefs_equalizer">Equalizer</string>
+    <string name="prefs_equalizer_summary">Equalizer aktivieren oder deaktivieren</string>
+    <string name="pref_title_equalizer_soft">Soft</string>
+    <string name="pref_title_equalizer_clear">Klar</string>
+    <string name="prefs_dolby_mode">Dolby-Modus</string>
+    <string name="prefs_dolby_summary">Dolby-Voreinstellung f眉r Equalizer</string>
+    <string name="prefs_galaxy_buds_experimental">Experimentell</string>
+    <string name="sony_surround_mode_off">Aus</string>
+    <string name="sony_automatic_power_off_off">Nicht ausschalten</string>
+    <string name="sony_automatic_power_off_5_min">5 Minuten</string>
+    <string name="sony_automatic_power_off_1_hour">1 Stunde</string>
+    <string name="prefs_fm_presets_presets">Voreinstellungen</string>
+    <string name="sony_automatic_power_off_30_min">30 Minuten</string>
+    <string name="sony_automatic_power_off_3_hour">3 Stunden</string>
+    <string name="prefs_activity_in_device_card_sleep_title">Schlaf</string>
+    <string name="prefs_activity_in_device_card_sleep_title_summary">Schlafdauer anzeigen</string>
+    <string name="sony_ambient_sound_off">Aus</string>
+    <string name="sony_sound_position_off">Aus</string>
+    <string name="sony_equalizer_preset_off">Aus</string>
+    <string name="prefs_left">Links</string>
+    <string name="prefs_right">Rechts</string>
+    <string name="sony_automatic_power_off">Automatisches Ausschalten</string>
+    <string name="sony_ambient_sound_wind_noise_reduction">Windger盲uschreduzierung</string>
+    <string name="sony_sound_position_front">Vorn</string>
+    <string name="sony_sound_position_front_left">Vorn links</string>
+    <string name="sony_sound_position_front_right">Vorn rechts</string>
+    <string name="sony_sound_position_rear_right">Hinten rechts</string>
+    <string name="sony_surround_mode">Surround-Modus</string>
+    <string name="sony_surround_mode_arena">Arena</string>
+    <string name="sony_surround_mode_club">Club</string>
+    <string name="sony_surround_mode_outdoor_stage">Au脽enb眉hne</string>
+    <string name="sony_equalizer_preset_custom_1">Benutzerdefiniert 1</string>
+    <string name="pref_header_sony_equalizer_preset_custom_1">Benutzerdefinierte Voreinstellung 1</string>
+    <string name="sony_touch_sensor">Ber眉hrungssensorsteuerung</string>
+    <string name="sony_notification_voice_guide">Benachrichtigungen &amp; Sprachf眉hrung</string>
+    <string name="sony_automatic_power_off_when_taken_off">Wenn sie abgenommen werden</string>
+    <string name="watchface_widget_type_custom">Benutzerdefiniertes Widget</string>
+    <string name="prefs_active_noise_cancelling">Aktive Ger盲uschunterdr眉ckung</string>
+    <string name="prefs_active_noise_cancelling_summary">Ger盲usche der Umgebung blockieren</string>
+    <string name="watchface_dialog_widget_timezone">Zeitzone</string>
+    <string name="watchface_dialog_widget_timeout_hide_text">Text bei Zeit眉berschreitung ausblenden</string>
+    <string name="watchface_dialog_widget_timeout_show_circle">Kreis bei Zeit眉berschreitung anzeigen</string>
+    <string name="sony_sound_position">Klangposition</string>
+    <string name="sony_equalizer">Equalizer</string>
+    <string name="sony_equalizer_preset_bright">Hell</string>
+    <string name="sony_equalizer_preset_mellow">Weich</string>
+    <string name="sony_equalizer_preset_vocal">Gesang</string>
+    <string name="sony_equalizer_preset_bass_boost">Bassverst盲rkung</string>
+    <string name="sony_equalizer_preset_speech">Sprache</string>
+    <string name="sony_equalizer_preset_relaxed">Entspannt</string>
+    <string name="sony_equalizer_preset_treble_boost">H枚henverst盲rkung</string>
+    <string name="sony_equalizer_band_6300">6.3k</string>
+    <string name="prefs_activity_in_device_card_steps_title_summary">Gesamte Schritte anzeigen</string>
+    <string name="prefs_fm_preset_instructions">Langes Dr眉cken der Taste zum Speichern der Voreinstellung</string>
+    <string name="pref_header_sony_ambient_sound_control">Umgebungsger盲uschsteuerung</string>
+    <string name="sony_ambient_sound">Modus</string>
+    <string name="sony_ambient_sound_noise_cancelling">Ger盲uschunterdr眉ckung</string>
+    <string name="sony_ambient_sound_ambient_sound">Umgebungsger盲usche</string>
+    <string name="sony_ambient_sound_focus_voice">Fokus auf Stimme</string>
+    <string name="sony_ambient_sound_level">Umgebungsger盲uschpegel</string>
+    <string name="sony_sound_position_rear_left">Hinten links</string>
+    <string name="sony_surround_mode_concert_hall">Konzertsaal</string>
+    <string name="sony_warn_sbc_codec">Achtung! Die Einstellungen f眉r Equalizer, Audioposition und Surround funktionieren nur f眉r den SBC-Audiocodec.</string>
+    <string name="sony_equalizer_band_16000">16k</string>
+    <string name="sony_equalizer_preset_manual">Manuell</string>
+    <string name="pref_header_sony_equalizer_preset_custom_2">Benutzerdefinierte Voreinstellung 2</string>
+    <string name="sony_equalizer_band_400">400</string>
+    <string name="sony_equalizer_band_2500">2.5k</string>
+    <string name="sony_equalizer_clear_bass">Klarer Bass</string>
+    <string name="sony_equalizer_band_1000">1k</string>
+    <string name="pref_title_equalizer_bass_boost">Bassverst盲rkung</string>
+    <string name="pref_title_equalizer_trebble">H枚henverst盲rkung</string>
+    <string name="prefs_game_mode">Spielmodus</string>
+    <string name="prefs_game_mode_summary">Nur wenn Ihr Telefon den Spielmodus unterst眉tzt</string>
+    <string name="prefs_touch_lock">Ber眉hrungssperre</string>
+    <string name="prefs_touch_lock_summary">Ber眉hrungsereignisse deaktivieren</string>
+    <string name="prefs_ambient_volume">Umgebungslautst盲rke</string>
+    <string name="prefs_ambient_voice_focus">Sprachfokus</string>
+    <string name="prefs_ambient_voice_summary">Sprache hervorheben</string>
+    <string name="prefs_galaxy_touch_options">Ber眉hrungsoptionen</string>
+    <string name="prefs_ambient_sound">Umgebungsger盲usche</string>
+    <string name="prefs_ambient_mode">Umgebungsmodus</string>
+    <string name="sony_equalizer_preset_custom_2">Benutzerdefiniert 2</string>
+    <string name="left_earbud">Linker Ohrh枚rer</string>
+    <string name="right_earbud">Rechter Ohrh枚rer</string>
+    <string name="title_activity_set_reminders">Erinnerungen konfigurieren</string>
+    <string name="controlcenter_start_configure_reminders">Erinnerungen konfigurieren</string>
+    <string name="reminder_repeat">Wiederholen</string>
+    <string name="reminder_date">Datum</string>
+    <string name="reminder_time">Zeit</string>
+    <string name="reminder_message">Nachricht</string>
+    <string name="reminder_time_once">%1$s, einmal</string>
+    <string name="reminder_every_day">T盲glich</string>
+    <string name="reminder_every_week">W枚chentlich</string>
+    <string name="reminder_every_month">Monatlich</string>
+    <string name="reminder_every_year">J盲hrlich</string>
+    <string name="reminder_delete_confirm_title">Erinnerung l枚schen</string>
+    <string name="reminder_delete_confirm_description">M枚chtest du die Erinnerung wirklich l枚schen\?</string>
+    <string name="reminder_no_free_slots_title">Keine freien Speicherpl盲tze</string>
+    <string name="reminder_no_free_slots_description">Das Ger盲t hat keine freien Speicherpl盲tze f眉r Erinnerungen (Gesamtspeicherplatz: %1$s)</string>
+    <string name="title_activity_reminder_details">Erinnerungsdetails</string>
+    <string name="miband_prefs_reserve_reminder_calendar">Erinnerungen f眉r bevorstehende Veranstaltungen reservieren</string>
+    <string name="qhybrid_summary_on_device_confirmation">Kopplungsbest盲tigungen auf dem Ger盲t k枚nnen l盲stig werden. Wenn du sie deaktivierst, verlierst du m枚glicherweise Funktionen.</string>
+    <string name="reminder_time_every_day">%1$s, jeden Tag</string>
+    <string name="reminder_time_every_month">%1$s, jeden Monat</string>
+    <string name="reminder_time_every_week">%1$s, jede Woche</string>
+    <string name="reminder_once">Einmal</string>
+    <string name="prefs_reserve_reminder_calendar_summary">Anzahl der Kalenderereignisse, die synchronisiert werden sollen</string>
+    <string name="toast_app_must_not_be_selected">App darf zum Konfigurieren nicht ausgew盲hlt sein</string>
+    <string name="qhybrid_title_on_device_confirmation">Best盲tigung der Kopplung auf dem Ger盲t aktivieren</string>
+    <string name="reminder_time_every_year">%1$s, jedes Jahr</string>
+    <string name="toast_app_must_be_selected">App muss zum Konfigurieren ausgew盲hlt sein</string>
+    <string name="prefs_pressure_relief">Druckentlastung mit Umgebungsger盲uschen</string>
+    <string name="pressure_relief_summary">Verhindert Druckgef眉hl in den Ohren, wenn die aktive Ger盲uschunterdr眉ckung nicht verwendet wird</string>
+    <string name="mi2_prefs_do_not_disturb_lift_wrist">Anzeige beim Anheben auch w盲hrend 鈥濨itte nicht st枚ren鈥� aktivieren</string>
+    <string name="watchface_dialog_widget_update_timeout">Aktualisierungszeit in Minuten</string>
+    <string name="device_card_activity_card_title">Aktivit盲tsinfo in der Ger盲te眉bersicht</string>
+    <string name="device_card_activity_card_title_summary">Auswahl der Aktivit盲tsinfos in der Ger盲te眉bersicht</string>
+    <string name="prefs_activity_in_device_card_title">Aktivit盲tsinfos in der Ger盲te眉bersicht anzeigen</string>
+    <string name="prefs_activity_in_device_card_title_summary">Zeige Schritte, Distanz oder Schlaf in der Ger盲te眉bersicht</string>
+    <string name="prefs_activity_in_device_card_distance_title_summary">Strecke basiert auf Schrittzahl und Schrittl盲nge (siehe Einstellungen - 脺ber Dich)</string>
+    <string name="sony_equalizer_preset_excited">Angeregt</string>
+    <string name="battery_case">Batteriefach</string>
+    <string name="sony_button_mode_ambient_sound_control">Umgebungsger盲usch-Steuerung</string>
+    <string name="minSpeed">Min Geschwindigkeit</string>
+    <string name="maxCadence">Max Kadenz</string>
+    <string name="minCadence">Min Kadenz</string>
+    <string name="sony_button_mode_off">Aus</string>
+    <string name="activity_prefs_discovery_pairing">Auffindungs- und Kopplungsoptionen</string>
+    <string name="add_test_device">Testger盲t hinzuf眉gen</string>
+    <string name="devicetype_sony_wf_sp800n">Sony WF-SP800N</string>
+    <string name="controlcenter_power_off_confirm_title">Ausschalten</string>
+    <string name="controlcenter_power_off">Ausschalten</string>
+    <string name="controlcenter_power_off_confirm_description">Bist du sicher, dass du das Ger盲t abschalten willst?</string>
+    <string name="discover_unsupported_devices">Nicht unterst眉tzte Ger盲te finden</string>
+    <string name="minStride">Min Schrittl盲nge</string>
+    <string name="discover_unsupported_devices_description">Wenn du diese Option aktivierst, werden beim Scannen alle erkannten Bluetooth-Ger盲te angezeigt. Durch kurzes Antippen werden Ger盲tename und MAC-Adresse in die Zwischenablage kopiert. Langes Dr眉cken startet den Dialog \"Testger盲t hinzuf眉gen\". Kann zu Problemen mit einfrierender App f眉hren.</string>
+    <string name="averageAltitude">Mittlere H枚he</string>
+    <string name="maxHR">Max Herzfrequenz</string>
+    <string name="minHR">Min Herzfrequenz</string>
+    <string name="sony_pause_when_taken_off">Pausieren wenn Kopfh枚rer abgenommen werden</string>
+    <string name="maxStride">Max Schrittl盲nge</string>
+    <string name="averageCadence">Mittlere Kadenz</string>
+    <string name="spm">Schritte/Min</string>
+    <string name="sony_button_mode_left">Schalter Modus (Links)</string>
+    <string name="sony_button_mode_playback_control">Wiedergabesteuerung</string>
+    <string name="sony_button_mode_right">Schalter Modus (Rechts)</string>
+    <string name="sony_button_mode_volume_control">Lautst盲rkeregelung</string>
+    <string name="pref_header_sony_equalizer_bands">B盲nder</string>
+    <string name="sony_audio_upsampling">Audio Upsampling</string>
+    <string name="devicetype_bose_qc35">Bose QC35</string>
+    <string name="descentDistance">Bergab Distanz</string>
+    <string name="ascentDistance">Bergauf Distanz</string>
+    <string name="devicetype_vesc">VESC</string>
+    <string name="activity_type_hiking">Wandern</string>
+    <string name="activity_type_climbing">Klettern</string>
+    <string name="pref_button_action_disabled">Deaktiviert</string>
+    <string name="pref_media_play">Medienwiedergabe</string>
+    <string name="pref_media_next">N盲chster Titel</string>
+    <string name="pref_media_previous">Vorheriger Titel</string>
+    <string name="pref_media_volumeup">Lauter</string>
+    <string name="pref_media_pause">Medienpause</string>
+    <string name="pref_media_playpause">Wiedergabe ein-/ausschalten</string>
+    <string name="pref_media_rewind">Zur眉ck springen</string>
+    <string name="distance_format_meters">###m</string>
+    <string name="distance_format_miles">###.#mi</string>
+    <string name="distance_format_feet">###ft</string>
+    <string name="pref_device_action_broadcast">Broadcast senden</string>
+    <string name="pref_media_volumedown">Leiser</string>
+    <string name="pref_media_forward">Vorw盲rts springen</string>
+    <string name="distance_format_kilometers">###.#km</string>
+    <string name="about_hash">Commit %s</string>
+    <string name="prefs_activity_recognition">Einstellungen f眉r die Aktivit盲tserkennung</string>
+    <string name="pref_activity_recognize_running">Laufen erkennen</string>
+    <string name="pref_activity_recognize_biking">Radfahren erkennen</string>
+    <string name="pref_activity_recognize_walking">Gehen erkennen</string>
+    <string name="pref_activity_recognize_rowing">Rudern erkennen</string>
+    <string name="pref_activity_recognition_mode_none">keine</string>
+    <string name="pref_activity_recognition_mode_ask">fragen</string>
+    <string name="pref_activity_recognition_mode_auto">automatisch</string>
+    <string name="fossil_hr_button_config_info">Einige Tasten k枚nnen nicht konfiguriert werden, da ihre Funktionen in der Firmware der Uhr fest programmiert sind.
+\n
+\nAchtung: Ein langer Druck auf den oberen Knopf, wenn ein Zifferblatt aus der offiziellen Fossil-App installiert ist, schaltet auch zwischen dem Ein- und Ausblenden von Widgets um.</string>
+    <string name="menuitem_menu">Men眉</string>
+    <string name="sony_speak_to_chat_sensitivity_auto">Automatisch</string>
+    <string name="sony_speak_to_chat_sensitivity_low">Niedrig</string>
+    <string name="sony_speak_to_chat_focus_on_voice">Fokus auf Stimme</string>
+    <string name="sony_speak_to_chat_timeout">Zeitbeschr盲nkung</string>
+    <string name="sony_speak_to_chat_timeout_short">Kurz (15 Sek.)</string>
+    <string name="sony_speak_to_chat_timeout_standard">Normal (30 Sek.)</string>
+    <string name="sony_speak_to_chat_timeout_long">Lang (1 Min.)</string>
+    <string name="sony_connect_two_devices">Gleichzeitige Verbindung mit 2 Ger盲ten</string>
+    <string name="sony_speak_to_chat">Speak-To-Chat</string>
+    <string name="sony_speak_to_chat_sensitivity_high">Hoch</string>
+    <string name="sony_speak_to_chat_timeout_off">Aus</string>
+    <string name="devicetype_sony_wh_1000xm4">Sony WH-1000XM4</string>
+    <string name="sony_speak_to_chat_sensitivity">Spracherkennungsempfindlichkeit</string>
+    <string name="watchface_dialog_widget_width">Widget-Breite (in Pixel)</string>
+    <string name="discovery_bluetooth_scan">Bluetooth-Scan:</string>
+    <string name="discovery_bluetooth_le_scan">Bluetooth-LE-Scan:</string>
+    <string name="device_unsupported">NICHT UNTERST脺TZT</string>
+    <string name="devicetype_domyos_t540">Domyos-T540</string>
+    <string name="unknown">Unbekannt</string>
+    <string name="pref_header_sony_anc_optimizer">Optimierer zur Ger盲uschunterdr眉ckung</string>
+    <string name="pref_anc_optimizer_state_pressure">Luftdruck</string>
+    <string name="sony_anc_optimizer_status_atmospheric_pressure">Luftdruck messen鈥�</string>
+    <string name="sony_anc_optimize_confirmation_title">Optimierer zur Ger盲uschunterdr眉ckung</string>
+    <string name="sony_anc_optimize_title">Optimieren</string>
+    <string name="start">Start</string>
+    <string name="audio_codec">Audio-Codec</string>
+    <string name="sony_anc_optimizer_status_wearing_condition">Tragezustand messen鈥�</string>
+    <string name="sony_anc_optimizer_status_finished">Beende鈥�</string>
+    <string name="pref_header_sony_device_info">Ger盲teinformation</string>
+    <string name="sony_anc_optimizer_status_starting">Starte鈥�</string>
+    <string name="sony_anc_optimizer_status_not_running">L盲uft nicht</string>
+    <string name="sony_anc_optimizer_status_analyzing">Analysiere鈥�</string>
+    <string name="sony_anc_optimize_description">Klicke, um die Optimierung der Ger盲uschunterdr眉ckung zu starten.</string>
+    <string name="sony_anc_optimize_confirmation_description">Verwende den Kopfh枚rer wie gewohnt. Wenn sich die Tragebedingungen oder der Luftdruck 盲ndern, f眉hre den Optimierer erneut aus.</string>
+    <string name="devicetype_amazfit_pop">Amazfit Pop</string>
+    <string name="devicetype_amazfit_pop_pro">Amazfit Pop Pro</string>
+    <string name="pref_title_opentracks_packagename">OpenTracks Paketname</string>
+    <string name="pref_summary_opentracks_packagename">Dient zum Starten/Stoppen der GPS-Track-Aufzeichnung in der externen Fitness-App.</string>
+    <string name="pref_title_notifications_generic_settings">Android-Benachrichtigungseinstellungen</string>
+    <string name="watchface_dialog_pre_setting_position">Voreinstellung der Position auf %s</string>
+    <string name="pref_device_action_fitness_app_control_stop">Fitness-App Tracking Stopp</string>
+    <string name="pref_device_action_fitness_app_control_start">Fitness-App Tracking Start</string>
+    <string name="activity_db_management_autoexport_enabled_yes">Autom. Export ist aktiviert.</string>
+    <string name="activity_db_management_autoexport_enabled_no">Autom. Export ist deaktiviert.</string>
+    <string name="activity_db_management_autoexport_scheduled_yes">Autom. Export wurde (urspr眉nglich) geplant f眉r %1$s</string>
+    <string name="activity_db_management_autoexport_scheduled_no">Autom. Export wurde nicht geplant.</string>
+    <string name="watchface_setting_light_up_on_notification">Aufleuchten bei neuer Benachrichtigung</string>
+    <string name="autoExport_lastTime_label">Letzter autom. Export: %1$s</string>
+    <string name="activity_db_management_autoexport_location">Standort ermittelt nicht m枚glich. Wahrscheinlich ein Problem des neueren Android-Berechtigungssystems. H枚chstwahrscheinlich funktioniert der autom. Export jetzt nicht.</string>
+    <string name="menuitem_email">E-Mail</string>
+    <string name="application_name_banglejs_main">Bangle.js Gadgetbridge</string>
+    <string name="title_activity_controlcenter_banglejs_main">Bangle.js Gadgetbridge</string>
+    <string name="about_description_banglejs_main">Android-Begleit-App f眉r Bangle.js, die auf dem Gadgetbridge-Projekt aufbaut, mit zus盲tzlichem Internetzugang.
+\n
+\nAufgrund der Richtlinien des Google Play Stores ist es uns nicht erlaubt, einen Spendenlink in der App selbst einzubauen, aber wenn dir diese App gef盲llt, ziehe bitte eine Spende 眉ber die Gadgetbridge-Homepage unten in Erw盲gung.</string>
+    <string name="gadgetbridge_running_banglejs_main">Bangle.js l盲uft</string>
+    <string name="about_description_banglejs_nopebble">Android-Begleit-App f眉r Bangle.js, die auf dem Gadgetbridge-Projekt aufbaut, mit zus盲tzlichem Internetzugang.
+\n
+\nAufgrund der Richtlinien des Google Play Stores ist es uns nicht erlaubt, einen Spendenlink in der App selbst einzubauen, aber wenn dir diese App gef盲llt, ziehe bitte eine Spende 眉ber die Gadgetbridge-Homepage unten in Erw盲gung.</string>
+    <string name="application_name_main_nightly">Gadgetbridge (Nightly)</string>
+    <string name="title_activity_controlcenter_main_nightly">Gadgetbridge (Nightly)</string>
+    <string name="about_activity_title_main_nightly">脺ber Gadgetbridge (Nightly)</string>
+    <string name="about_description_main_nightly">Cloudloser und freier quelloffener Ersatz f眉r Closed-Source Android-Gadget-Apps von Anbietern. N盲chtliche Ver枚ffentlichungen von Gadgetbridge. Es kann nicht installiert werden, wenn du bereits die Gadgetbridge- oder die Pebble-App installiert hast, da es einen Konflikt mit dem Pebble-Anbieter gibt.</string>
+    <string name="gadgetbridge_running_main_nightly">Nightly GB l盲uft</string>
+    <string name="application_name_main_nopebble">Gadgetbridge (Nightly, kein Pebble-Anbieter)</string>
+    <string name="title_activity_controlcenter_main_nopebble">Gadgetbridge (Nightly, kein Pebble)</string>
+    <string name="about_activity_title_main_nopebble">脺ber Gadgetbridge (Nightly, kein Pebble)</string>
+    <string name="gadgetbridge_running_main_nopebble">Nightly kein Pebble GB l盲uft</string>
+    <string name="prefs_activate_display_on_lift_sensitivity">Empfindlichkeit</string>
+    <string name="normal">Normal</string>
+    <string name="sensitive">Empfindlich</string>
+    <string name="application_name_banglejs_nopebble">Bangle.js Gadgetbridge</string>
+    <string name="about_activity_title_banglejs_main">脺ber Bangle.js Gadgetbridge</string>
+    <string name="about_description_main_nopebble">Cloudloser und freier quelloffener Ersatz f眉r Closed-Source Android-Gadget-Apps von Anbietern. N盲chtliche Ver枚ffentlichungen von Gadgetbridge. Bei dieser Version wurde der Pebble-Provider umbenannt, um Konflikte zu vermeiden. Daher werden einige Pebble-bezogene Integrationen nicht funktionieren, aber sie kann neben einer bestehenden Gadgetbridge-Installation installiert werden.</string>
+    <string name="title_activity_controlcenter_banglejs_nopebble">Bangle.js Gadgetbridge</string>
+    <string name="gadgetbridge_running_banglejs_nopebble">Bangle.js l盲uft</string>
+    <string name="about_activity_title_banglejs_nopebble">脺ber Bangle.js Gadgetbridge</string>
+    <string name="pref_device_action_fitness_app_control_toggle">Fitness-App Tracking umschalten</string>
+    <string name="pref_title_banglejs_text_bitmap">Text als Bitmaps</string>
+    <string name="pref_screen_notification_profile_event_reminder">Terminerinnerung</string>
+    <string name="pref_screen_notification_profile_find_device">Ger盲t finden</string>
+    <string name="pref_screen_notification_idle_alerts">Leerlaufwarnungen</string>
+    <string name="mi5_prefs_workout_activity_types">Arten von Trainingsaktivit盲ten</string>
+    <string name="mi5_prefs_workout_activity_types_summary">Aktivit盲tsarten w盲hlen, die auf dem Trainingsbildschirm angezeigt werden sollen</string>
+    <string name="activity_type_freestyle">Freistil</string>
+    <string name="devicetype_sony_wf_1000xm3">Sony WF-1000XM3</string>
+    <string name="devicetype_galaxybuds_pro">Galaxy Buds Pro</string>
+    <string name="prefs_in_ear_detection_summary">Wiedergabe von Anrufen 眉ber Ohrst枚psel, wenn diese im Ohr sind</string>
+    <string name="pref_title_touch_volume">Lautst盲rke</string>
+    <string name="pref_title_touch_quick_ambient">Schnelle Umgebungsger盲usche</string>
+    <string name="prefs_voice_detect_summary">Umgebungsger盲usche aktivieren und Wiedergabe automatisch absenken, nachdem eine Stimme erkannt wurde</string>
+    <string name="heartrate_bpm_145">145 BPM</string>
+    <string name="heartrate_bpm_150">150 BPM</string>
+    <string name="world_clock_label">Beschriftung</string>
+    <string name="world_clock_no_free_slots_description">Das Ger盲t hat keine freien Speicherpl盲tze f眉r Weltzeituhren (Speicherpl盲tze insgesamt: %1$s)</string>
+    <string name="activity_type_elliptical">Elliptisch</string>
+    <string name="pref_summary_banglejs_text_bitmap">Wenn ein Wort nicht mit der Schriftart der Uhr gerendert werden kann, in Gadgetbridge eine Bitmap rendern und die Bitmap auf der Uhr anzeigen</string>
+    <string name="pref_title_device_internet_access">Internetzugang zulassen</string>
+    <string name="pref_summary_device_internet_access">Apps auf diesem Ger盲t Internetzugriff erlauben</string>
+    <string name="pref_title_device_intents">Eingriffe erlauben</string>
+    <string name="pref_summary_device_intents">Bangle.js-Watch-Apps erlauben, Android-Intents zu senden und anderen Apps auf Android (wie Tasker) erlauben, Daten mit dem com.banglejs.uart.tx-Intent an Bangle.js zu senden. Ben枚tigt die Berechtigung zum Anzeigen 眉ber andere Apps, um im Hintergrund zu arbeiten.</string>
+    <string name="pref_screen_vibration_patterns_title">Vibrationsmuster</string>
+    <string name="pref_screen_vibration_patterns_summary">Vibrationsmuster f眉r verschiedene Benachrichtigungen konfigurieren</string>
+    <string name="heartrate_bpm_100">100 BPM</string>
+    <string name="heartrate_bpm_105">105 BPM</string>
+    <string name="heartrate_bpm_110">110 BPM</string>
+    <string name="heartrate_bpm_112">112 BPM</string>
+    <string name="heartrate_bpm_120">120 BPM</string>
+    <string name="heartrate_bpm_125">125 BPM</string>
+    <string name="heartrate_bpm_130">130 BPM</string>
+    <string name="heartrate_bpm_135">135 BPM</string>
+    <string name="heartrate_bpm_140">140 BPM</string>
+    <string name="title_activity_world_clock_details">Weltzeituhrdetails</string>
+    <string name="prefs_heartrate_alert_experimental_title">Pulsalarm (experimentell)</string>
+    <string name="prefs_heartrate_alert_experimental_description">Band vibriert, wenn der Puls 眉ber einem Schwellenwert liegt, ohne dass Sie in den letzten 10 Minuten eine offensichtliche k枚rperliche Aktivit盲t hatten. Diese Funktion ist experimentell und wurde nicht ausf眉hrlich getestet.</string>
+    <string name="prefs_heartrate_alert_threshold">Alarmschwelle Herzfrequenz</string>
+    <string name="prefs_stress_monitoring_title">Stress眉berwachung</string>
+    <string name="prefs_stress_monitoring_description">Stresslevel w盲hrend des Ausruhens 眉berwachen</string>
+    <string name="prefs_activity_monitoring_title">Aktivit盲ts眉berwachung</string>
+    <string name="mi2_prefs_heart_rate_monitoring">Herzfrequenz 眉berwachen</string>
+    <string name="mi2_prefs_heart_rate_monitoring_summary">Puls眉berwachung konfigurieren</string>
+    <string name="mi2_prefs_heart_rate_monitoring_alerts_summary">Puls眉berwachung und Warnschwellen konfigurieren</string>
+    <string name="activity_type_outdoor_running">Laufen im Freien</string>
+    <string name="activity_type_outdoor_cycling">Radfahren im Freien</string>
+    <string name="prefs_noise_control">Ger盲uschkontrolle</string>
+    <string name="pref_switch_controls_anc_off">Ger盲uschunterdr眉ckung 鈫愨啋 Aus</string>
+    <string name="pref_switch_controls_ambient_off">Umgebung 鈫愨啋 Aus</string>
+    <string name="prefs_double_tap_edge_summary">Doppeltippen erkennen, auch wenn nicht auf dem Touchpad getippt wird</string>
+    <string name="prefs_voice_detect_duration">Ende nach Ruhe f眉r:</string>
+    <string name="pref_voice_detect_duration_5">5 Sekunden</string>
+    <string name="pref_voice_detect_duration_10">10 Sekunden</string>
+    <string name="pref_voice_detect_duration_15">15 Sekunden</string>
+    <string name="prefs_voice_detect">Spracherkennung</string>
+    <string name="prefs_double_tap_edge">Kante doppelt antippen</string>
+    <string name="permission_notification_policy_access">%1$s ben枚tigt Zugriff auf die Einstellungen von \'DND\', um sie auf deiner Uhr zu ber眉cksichtigen, wenn der Bildschirm des Telefons ausgeschaltet ist.
+\n
+\nBitte auf \'%2$s\', dann auf \'%1$s tippen und \'DND\' aktiveren und dann auf \'Zur眉ck\' tippen, um zu %1$s zur眉ckzukehren.</string>
+    <string name="prefs_seamless_connection_switch_title">Nahtloser Verbindungswechsel</string>
+    <string name="prefs_seamless_connection_switch_summary">Wechselt die St枚psel automatisch zwischen den gekoppelten Ger盲ten</string>
+    <string name="prefs_ambient_volume_left">Umgebungslautst盲rke links</string>
+    <string name="prefs_ambient_volume_right">Umgebungslautst盲rke rechts</string>
+    <string name="prefs_customize_ambient_sound_summary">An Umgebungsger盲usche anpassen</string>
+    <string name="prefs_ambient_sound_during_call_summary">Eigene Stimme bei Anrufen deutlich h枚ren</string>
+    <string name="prefs_ambient_settings_title">Optionen Umgebungsger盲usche</string>
+    <string name="prefs_active_noise_cancelling_level">Stufe der aktiven Ger盲uschunterdr眉ckung</string>
+    <string name="prefs_active_noise_cancelling_level_high">Hoch</string>
+    <string name="prefs_active_noise_cancelling_level_low">Niedrig</string>
+    <string name="prefs_switch_control_right">Schaltersteuerung rechts</string>
+    <string name="pref_title_touch_voice_assistant">Sprachassistent</string>
+    <string name="pref_title_touch_anc">Aktive Ger盲uschunterdr眉ckung</string>
+    <string name="pref_title_touch_ambient">Umgebungsger盲usche</string>
+    <string name="pref_title_touch_spotify">Spotify</string>
+    <string name="pref_switch_noise_control">L盲rmschutz umschalten</string>
+    <string name="prefs_noise_control_with_one_earbud_summary">Erm枚glicht Ger盲uschkontrolle bei Verwendung nur eines Ohrh枚rers</string>
+    <string name="prefs_noise_control_with_one_earbud">Ger盲uschkontrolle mit einem Ohrh枚rer</string>
+    <string name="pref_ambient_sound_tone">Umgebungsger盲usche</string>
+    <string name="pref_ambient_sound_tone_summary">Von sanft bis klar</string>
+    <string name="pref_balance">Ausgeglichen</string>
+    <string name="pref_world_clocks_summary">Uhren f眉r andere Zeitzonen konfigurieren</string>
+    <string name="world_clock_delete_confirm_title">\'%1$s\' l枚schen</string>
+    <string name="world_clock_timezone">Zeitzone</string>
+    <string name="pref_world_clocks_title">Weltuhren</string>
+    <string name="world_clock_delete_confirm_description">M枚chtest du die Weltzeituhr wirklich l枚schen\?</string>
+    <string name="world_clock_no_free_slots_title">Keine freien Speicherpl盲tze</string>
+    <string name="prefs_activity_monitoring_description">Erh枚ht automatisch die Frequenz der Pulserfassung, wenn das Band k枚rperliche Bewegung erkennt, um die Genauigkeit der Pulserfassung zu erh枚hen.</string>
+    <string name="prefs_ambient_sound_during_call_title">Umgebungsger盲usche w盲hrend des Anrufs</string>
+    <string name="permission_notification_listener">%1$s ben枚tigt Zugriff auf Benachrichtigungen, um sie auf deiner Uhr anzeigen zu k枚nnen, wenn der Bildschirm des Telefons ausgeschaltet ist.
+\n
+\nBitte auf \'%2$s\' und dann auf \'%1$s\' tippen, \'Zugriff auf Benachrichtigungen zulassen\' aktivieren und auf \'Zur眉ck\' tippen, um zu %1$s zur眉ckzukehren.</string>
+    <string name="prefs_switch_control_left">Schaltersteuerung links</string>
+    <string name="pref_switch_controls_anc_ambient">Ger盲uschunterdr眉ckung 鈫愨啋 Umgebung</string>
+    <string name="pref_workout_start_on_phone_summary">Startet/stoppt das Fitness-App-Tracking auf dem Telefon, wenn ein GPS-Training auf dem Band gestartet wird</string>
+    <string name="pref_workout_send_gps_title">GPS w盲hrend des Trainings senden</string>
+    <string name="pref_workout_send_gps_summary">Senden der aktuellen GPS-Position an das Band w盲hrend des Trainings</string>
+    <string name="pref_workout_start_on_phone_title">Fitness-App-Tracking</string>
+    <string name="discovery_scanning_intensity_warning">Wenn dein Ger盲t einfriert oder nicht reagiert, versuche, die Scanning-Intensit盲t auf eine niedrigere Stufe einzustellen. Wenn dein Ger盲t nicht erkannt wird, stelle die Scanning-Intensit盲t auf eine h枚here Stufe.</string>
+    <string name="discovery_scanning_intensity">Scanning-Intensit盲t</string>
+    <string name="portuguese_br">Portugiesisch (Brasilien)</string>
+    <string name="quick_alarm">Schneller Alarm</string>
+    <string name="quick_alarm_description">Alarm vom Widget</string>
+    <string name="pref_device_action_phone_gps_location_listener_stop">GPS-Standort Stopp</string>
+    <string name="notification_channel_gps">GPS-Ortung</string>
+    <string name="notification_gps_title">Gadgetbridge GPS</string>
+    <string name="portuguese_pt">Portugiesisch (Portugal)</string>
+    <string name="notification_gps_text">Senden des GPS-Standorts an %1$d Ger盲te</string>
+    <string name="controlcenter_set_parent_folder">脺berordner setzen</string>
+    <string name="controlcenter_toggle_details">Details anzeigen</string>
+    <string name="controlcenter_connected_fraction">Verbunden: %d/%d</string>
+    <string name="ukranian">Ukrainisch</string>
+    <string name="info_no_devices_connected">Keine Ger盲te verbunden</string>
+    <string name="info_connected_count">%d Ger盲te verbunden</string>
+    <string name="estonian">Estnisch</string>
+    <string name="extended_ascii">Erweitertes ASCII</string>
+    <string name="czech">Tschechisch</string>
+    <string name="pref_title_banglejs_webview_url">URL des App-Loaders</string>
+    <string name="icelandic">Isl盲ndisch</string>
+    <string name="permission_location">%1$s ben枚tigt im Hintergrund Zugriff auf deinen Standort, um mit deiner Uhr verbunden zu bleiben, auch wenn dein Bildschirm ausgeschaltet ist. \n \nBitte tippe auf \'%2$s\', um zuzustimmen.</string>
+    <string name="persian">Persisch</string>
+    <string name="pref_summary_banglejs_webview_url">Wenn du einen benutzerdefinierten App-Loader m枚chtest, trage hier deine URL https://鈥�/android.html ein. Andernfalls lasse https://banglejs.com/apps leer</string>
+    <string name="bengali">Bengalisch</string>
+    <string name="lithuanian">Litauisch</string>
+    <string name="scandinavian">Skandinavisch</string>
+    <string name="pref_title_notification_delay_calls">Verz枚gerung f眉r Anrufbenachrichtigungen</string>
+    <string name="pref_summary_notification_delay_calls">Verz枚gerung in Sekunden, bis Anrufbenachrichtigungen zum Ger盲t gesendet werden.</string>
+    <string name="pref_blacklist_calendars_summary">Blockierte Kalender werden nicht mit dem Ger盲t synchronisiert</string>
+    <string name="autoconnect_from_device_summary">Herstellen einer Verbindung, wenn die Verbindung durch ein Ger盲t initiiert wird, z. B. Kopfh枚rer</string>
+    <string name="autoconnect_from_device_title">Herstellen einer Verbindung vom Ger盲t aus</string>
+    <string name="watchface_dialog_widget_timezone_duration">Sichtbarkeitsdauer der Uhr (in Sekunden)</string>
+    <string name="connection_over_ble">Verbindung 眉ber BLE</string>
+    <string name="connection_over_bt_classic">Verbindung 眉ber Bluetooth classic</string>
+    <string name="error_setting_parent_folder">Fehler beim Festlegen des 眉bergeordneten Ordners: %s</string>
+    <string name="error_deleting_device">Fehler beim L枚schen des Ger盲ts: %s</string>
+    <string name="controlcenter_folder_name">Ordnername:</string>
+    <string name="controlcenter_add_new_folder">Neuen Ordner hinzuf眉gen</string>
+    <string name="controlcenter_unset_folder">Ordner aufheben</string>
+    <string name="pref_explanation_authkey">Einige Ger盲te erfordern einen speziellen Pairing-Schl眉ssel f眉r die erste Initialisierung des Ger盲ts. Tippe hier f眉r weitere Details im Wiki.</string>
+    <string name="pref_explanation_authkey_new_protocol">Wenn du die Meldung \"Aktualisiere die App auf die neueste Version\" auf dem Band erh盲ltst, 眉berpr眉fe bitte das \"Neue Auth-Protokoll\" oben. Tippe hier f眉r weitere Details im Wiki.</string>
+    <string name="prefs_password">Passwort</string>
+    <string name="prefs_password_summary">Sperre das Band mit einem Passwort, wenn du es vom Handgelenk abnimmst</string>
+    <string name="prefs_password_enabled">Passwort aktiviert</string>
+    <string name="prefs_password_4_digits_1_to_4_summary">Das Passwort muss 4 Ziffern haben, wobei die Zahlen 1 bis 4 verwendet werden m眉ssen</string>
+    <string name="prefs_password_6_digits_0_to_9_summary">Das Passwort muss 6 Ziffern haben und darf nur Zahlen enthalten</string>
+    <string name="controlcenter_set_preferences">Einstellungen festlegen</string>
+    <string name="controlcenter_set_folder_title">Ordner festlegen oder neu erstellen</string>
+    <string name="auto_reconnect_ble_title">Automatische Wiederverbindung mit dem Ger盲t</string>
+    <string name="auto_reconnect_ble_summary">Regelm盲脽ig proaktiv versuchen, eine Verbindung zum Ger盲t herzustellen</string>
+    <string name="open_fw_installer_warning_title">Warnung</string>
+    <string name="open_fw_installer_warning_text">Diese Funktion hat das Potenzial, dein Ger盲t zu besch盲digen. Das hei脽t, dies ist noch nie einem der Entwickler durch Flashen passiert, aber denke daran, dass du dies auf eigene Gefahr tust.</string>
+    <string name="open_fw_installer_getting_files_title">Abrufen der Firmware-/App-Datei</string>
+    <string name="open_fw_installer_pick_file">Datei ausw盲hlen</string>
+    <string name="open_fw_installer_info_text_title">Datei Installer</string>
+    <string name="open_fw_installer_select_file">W盲hle eine Datei aus, die du auf das Ger盲t hochladen m枚chtest: %s</string>
+    <string name="open_fw_installer_connect_minimum_one_device">Bitte verbinde MINDESTENS EIN Ger盲t, an das du die Datei senden m枚chtest.</string>
+    <string name="open_fw_installer_connect_maximum_one_device">Bitte verbinde NUR EIN Ger盲t, an das du die Datei senden m枚chtest.</string>
+    <string name="open_fw_installer_ensure_device_connected">Stelle sicher, dass das Ger盲t %s angeschlossen ist</string>
+    <string name="open_fw_installer_info_text">Mit dem Firmware/Watchface/App/File Installer kannst du unterst眉tzte Dateien (Firmware, Zifferbl盲tter, Anwendungen, GPS, Ressourcen, Schriftarten...) auf das Ger盲t hochladen/installieren. Mehr Informationen dazu gibt es im Wiki: https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Firmware-Update</string>
+    <string name="open_fw_installer_getting_files_text">Da wir die Firmware-Dateien nicht verteilen d眉rfen, musst du dir die Dateien selbst beschaffen. Das bedeutet, dass du nach den Dateien in apk-Dateien, online, in Foren, auf Amazfitwatchfaces (f眉r Miband/Amazfit-Ger盲te) und so weiter suchen musst.</string>
+    <string name="appmanager_item_outdated">(veraltet)</string>
+    <string name="pref_title_banglejs_txt_bitmap_size">Gr枚脽e der Text-Bitmaps</string>
+    <string name="pref_summary_banglejs_txt_bitmap_size">Gr枚脽e, die f眉r das Rendern von Text als Bitmap verwendet werden soll</string>
+    <string name="watchface_dialog_widget_cat_generic">Allgemein</string>
+    <string name="watchface_dialog_widget_cat_custom_widget">Benutzerdefiniertes Widget</string>
+    <string name="watchface_dialog_widget_cat_2nd_tz_widget">2. Zeitzonen-Widget</string>
+    <string name="watchface_dialog_widget_cat_position">Position</string>
+    <string name="watchface_dialog_widget_background">Hintergrund</string>
+    <string name="hybridhr_widget_bg_thin_circle">D眉nner Kreis</string>
+    <string name="hybridhr_widget_bg_double_circle">Doppelkreis</string>
+    <string name="hybridhr_widget_bg_dashed_circle">Gestrichelter Kreis</string>
+    <string name="appmanager_app_share">Teilen</string>
+    <string name="watchface_dialog_title_widget">Widget-Einstellungen</string>
+    <string name="steps_streaks">Schrittserie</string>
+    <string name="share_log_not_enabled_message">Du musst zuerst die Protokollierung in den Einstellungen - Protokolldateien schreiben aktivieren</string>
+    <string name="note">Hinweis</string>
+    <string name="step_streak_total">Insgesamt</string>
+    <string name="steps_streaks_since_date">Seit %s</string>
+    <string name="step_streak_average_steps_hint">Durchschnittliche Schritte pro Tag der Serie</string>
+    <string name="steps_streaks_total_days_hint_totals">Prozentualer Anteil der Tage mit erreichtem Ziel im Verh盲ltnis zu allen Tagen mit Schritten</string>
+    <string name="steps_streaks_total_steps">Insgesamte
+\nSchritte</string>
+    <string name="steps_streaks_streak_days">Serie
+\nTage</string>
+    <string name="watchface_setting_title_custom_events">Benutzerdefinierte Ereignisse</string>
+    <string name="watchface_setting_button_toggle_backlight">Hintergrundbeleuchtung einschalten</string>
+    <string name="steps_streaks_hint">Serie von aufeinanderfolgenden Tagen ohne Unterbrechung, an denen das Schrittziel erreicht wird</string>
+    <string name="step_streak_ongoing">Laufend</string>
+    <string name="step_streak_longest">L盲ngste</string>
+    <string name="steps_streaks_total_steps_average_hint">Durchschnittliche %d Schritte pro Tag</string>
+    <string name="step_streak_days_hint">Anzahl der aufeinanderfolgenden Tage, an denen das Schrittziel erreicht wurde</string>
+    <string name="steps_streaks_total_steps_hint">Gesamtzahl der Schritte in der gesamten Serie</string>
+    <string name="steps_streaks_total_steps_hint_totals">Gesamtzahl der jemals aufgezeichneten Schritte</string>
+    <string name="steps_streaks_average_steps">Durchschnittliche
+\nSchritte</string>
+    <string name="steps_streaks_achievement_rate">Leistung
+\nBewertung</string>
+    <string name="watchface_setting_button_toggle_widgets">Widgets umschalten</string>
+    <string name="controlcenter_get_heartrate_measurement">Herzfrequenzmessung</string>
+    <string name="pref_write_logfiles_not_available">Die Initialisierung der Dateiprotokollierung ist fehlgeschlagen, das Schreiben von Protokolldateien ist derzeit nicht m枚glich. Starte die App neu, um zu versuchen, die Protokolldateien erneut zu initialisieren.</string>
+    <string name="devicetype_binary_sensor">Bin盲rer Sensor</string>
+    <string name="application_name_banglejs_nightly">Bangle.js Gadgetbridge (Nightly)</string>
+    <string name="title_activity_controlcenter_banglejs_nightly">Bangle.js Gadgetbridge (Nightly)</string>
+    <string name="about_activity_title_banglejs_nightly">脺ber Bangle.js Gadgetbridge (Nightly)</string>
+    <string name="about_description_banglejs_nightly">Android-Begleit-App f眉r Bangle.js, die auf dem Gadgetbridge-Projekt aufbaut, mit zus盲tzlichem Internetzugang.
+\n
+\nAufgrund der Richtlinien des Google Play Stores ist es uns nicht erlaubt, einen Spendenlink in der App selbst einzubauen, aber wenn dir diese App gef盲llt, ziehe bitte eine Spende 眉ber die Gadgetbridge-Homepage unten in Erw盲gung.</string>
+    <string name="gadgetbridge_running_banglejs_nightly">Bangle.js (Nightly) wird ausgef眉hrt</string>
+    <string name="prefs_hourly_chime">St眉ndlicher Klang</string>
+    <string name="prefs_hourly_chime_summary">Die Uhr piept einmal pro Stunde</string>
+    <string name="step_streaks_achievements_sharing_message">Meine t盲glichen Schritterfolge!</string>
+    <string name="step_streaks_achievements_sharing_title">Schritterfolge</string>
+    <string name="permission_display_over_other_apps">%1$s ben枚tigt die Berechtigung, 眉ber andere Apps angezeigt zu werden, damit Bangle.js-Watch Aktivit盲ten mit Intents starten kann, wenn %1$s im Hintergrund ist.
+\n
+\nDies kann verwendet werden, um eine Musik-App zu starten und einen Song abzuspielen und viele andere Dinge.
+\n
+\nTippe auf \'%2$s\' und dann auf \'%1$s\', aktiviere \'Anzeige 眉ber andere Apps zulassen\' und tippe dann auf \'Zur眉ck\', um zu %1$s zur眉ckzukehren.
+\n
+\nUm zu verhindern, dass %1$s nach Berechtigungen fragt, gehe zu \"Einstellungen\" und entferne das H盲kchen bei \"Berechtigungsstatus pr眉fen\".
+\n
+\nVergewissere dich, dass du %1$s die erforderlichen Berechtigungen erteilst, damit es so funktioniert, wie du es erwartest.</string>
+    <string name="fw_upgrade_notice_miband7">Du bist dabei, die Firmware %s auf dein Xiaomi Smart Band 7 zu installieren.
+\n
+\nDein Band wird nach der Installation der .zip-Datei neu starten.
+\n
+\nINSTALLATION AUF EIGENE GEFAHR!</string>
+    <string name="pref_header_calendar">Kalender</string>
+    <string name="pref_header_connection">Verbindung</string>
+    <string name="pref_title_screen_on_on_notifications">Bildschirm ein bei Benachrichtigungen</string>
+    <string name="pref_summary_screen_on_on_notifications">Schaltet den Bildschirm des Bands ein, wenn eine Benachrichtigung eingeht</string>
+    <string name="spo2_perc_80">80 %</string>
+    <string name="spo2_perc_85">85 %</string>
+    <string name="spo2_perc_90">90 %</string>
+    <string name="spo2_off">Aus</string>
+    <string name="prefs_heartrate_alert_high_threshold">Alarmschwelle f眉r hohe Herzfrequenz</string>
+    <string name="prefs_spo2_monitoring_title">脺berwachung des Blutsauerstoffs</string>
+    <string name="seconds_6">6 Sekunden</string>
+    <string name="seconds_11">11 Sekunden</string>
+    <string name="seconds_12">12 Sekunden</string>
+    <string name="seconds_13">13 Sekunden</string>
+    <string name="seconds_14">14 Sekunden</string>
+    <string name="seconds_15">15 Sekunden</string>
+    <string name="prefs_always_on_display">Display immer eingeschaltet</string>
+    <string name="smart">Smart</string>
+    <string name="mi2_dnd_always">Immer</string>
+    <string name="devicetype_miband7">Xiaomi Smart Band 7</string>
+    <string name="menuitem_countdown">Countdown</string>
+    <string name="menuitem_personal_activity_intelligence">Pers枚nliche Aktivit盲tsintelligenz</string>
+    <string name="menuitem_female_health">Weibliche Gesundheit</string>
+    <string name="menuitem_workout_status">Trainingsstatus</string>
+    <string name="pref_header_heartrate_sleep">Schlaf</string>
+    <string name="pref_header_heartrate_allday">Ganzt盲gige 脺berwachung</string>
+    <string name="pref_header_heartrate_alerts">Herzfrequenzwarnungen</string>
+    <string name="pref_header_stress">Stress</string>
+    <string name="pref_header_spo2">Blutsauerstoff</string>
+    <string name="pref_screen_brightness">Bildschirmhelligkeit</string>
+    <string name="pref_header_workout">Training</string>
+    <string name="pref_header_health">Gesundheit</string>
+    <string name="pref_header_time">Zeit</string>
+    <string name="heartrate_bpm_50">50 BPM</string>
+    <string name="prefs_spo2_monitoring_description">Automatische 脺berwachung des Sauerstoffgehalts im Blut w盲hrend des gesamten Tages</string>
+    <string name="heartrate_bpm_115">115 BPM</string>
+    <string name="prefs_relaxation_reminder_description">Band vibriert, um dich zu benachrichtigen, wenn der Stresswert h枚her als 80 ist</string>
+    <string name="pref_header_display">Anzeige</string>
+    <string name="pref_summary_canned_replies">Antworten von der Uhr mit voreingestellten Nachrichten</string>
+    <string name="pref_cache_weather">Wetterinformationen zwischenspeichern</string>
+    <string name="prefs_heartrate_alert_low_threshold">Alarmschwelle f眉r niedrige Herzfrequenz</string>
+    <string name="pref_sleep_breathing_quality_monitoring">脺berwachung der Qualit盲t der Schlafatmung</string>
+    <string name="pref_cache_weather_summary">Wetterinformationen werden 眉ber Anwendungsneustarts hinweg zwischengespeichert.</string>
+    <string name="heartrate_bpm_40">40 BPM</string>
+    <string name="heartrate_bpm_45">45 BPM</string>
+    <string name="prefs_relaxation_reminder_title">Erinnerung zur Entspannung</string>
+    <string name="prefs_spo2_alert_threshold">SpO2-Alarmschwelle</string>
+    <string name="seconds_7">7 Sekunden</string>
+    <string name="seconds_8">8 Sekunden</string>
+    <string name="seconds_9">9 Sekunden</string>
+    <string name="prefs_always_on_display_summary">Display des Bandes immer eingeschaltet lassen</string>
+    <string name="menuitem_workout_history">Trainingsverlauf</string>
+    <string name="prefs_screen_timeout">Bildschirmzeitlimit</string>
+    <string name="pref_title_notifications_ignore_low_priority">Benachrichtigungen mit niedriger Priorit盲t ignorieren</string>
+    <string name="pref_summary_notifications_ignore_low_priority">Keine Benachrichtigungen mit niedriger und minimaler Priorit盲t an die Uhr senden</string>
+    <string name="devicetype_flipper_zero">Flipper zero</string>
+    <string name="activity_prefs_summary_allow_bluetooth_intent_api">Steuerung der Bluetooth-Verbindung 眉ber Intent-API</string>
+    <string name="activity_prefs_allow_bluetooth_intent_api">Bluetooth Intent-API</string>
+    <string name="fw_upgrade_notice_amazfit_gts3">Du bist dabei, die Firmware %s auf deine Amazfit GTS 3 zu installieren.
+\n
+\nDeine Uhr wird nach der Installation der .zip Datei neu starten.
+\n
+\nINSTALLATION AUF EIGENE GEFAHR!</string>
+    <string name="pref_title_notification_prefer_long_text">Langen Benachrichtigungstext bevorzugen</string>
+    <string name="pref_summary_notification_prefer_long_text">Falls verf眉gbar, langen Benachrichtigungstext an das Ger盲t senden</string>
+    <string name="devicetype_amazfit_gts3">Amazfit GTS 3</string>
+    <string name="menuitem_unknown_app">Unbekannt (%s)</string>
+    <string name="pref_summary_overwrite_settings_on_connection">Beim Verbinden mit dem Band 眉berschreibst du alle Einstellungen des Bandes.</string>
+    <string name="pref_title_overwrite_settings_on_connection">脺berschreiben von Einstellungen bei der Verbindung</string>
+    <string name="kind_app">App</string>
+    <string name="dismiss">Ablehnen</string>
+    <string name="fw_upgrade_notice_amazfit_gtr3">Du bist dabei, die Firmware %s auf deine Amazfit GTR 3 zu installieren.
+\n
+\nDeine Uhr wird nach der Installation der .zip Datei neu starten.
+\n
+\nINSTALLATION AUF EIGENE GEFAHR!</string>
+    <string name="devicetype_amazfit_gtr3">Amazfit GTR 3</string>
+    <string name="pref_summary_third_party_app_device_settings">Erlaube anderen installierten Apps von Drittanbietern, Ger盲teeinstellungen 眉ber Intents festzulegen.</string>
+    <string name="pref_title_third_party_app_device_settings">脛ndern von Einstellungen durch App von Drittanbietern zulassen</string>
+    <string name="activity_type_dance">Tanzen</string>
+    <string name="activity_type_indoor_fitness">Indoor-Fitness</string>
+    <string name="activity_type_gymnastics">Gymnastik</string>
+    <string name="activity_type_hiit">Hochintensives Intervalltraining</string>
+    <string name="activity_type_core_training">Core-Training</string>
+    <string name="activity_type_stretching">Dehnen</string>
+    <string name="activity_type_stepper">Stepper</string>
+    <string name="activity_type_pilates">Pilates</string>
+    <string name="activity_type_volleyball">Volleyball</string>
+    <string name="activity_type_table_tennis">Tischtennis</string>
+    <string name="activity_type_bowling">Bowling</string>
+    <string name="activity_type_zumba">Zumba</string>
+    <string name="activity_type_indoor_ice_skating">Indoor-Eislaufen</string>
+    <string name="devicetype_amazfit_gtr_lite">Amazfit GTR Lite</string>
+    <string name="activity_type_street_dance">Street Dance</string>
+    <string name="activity_type_kickboxing">Kickboxen</string>
+    <string name="activity_type_boxing">Boxen</string>
+    <string name="abstract_chart_fragment_kind_rem_sleep">REM-Schlaf</string>
+    <string name="supercars_lights_label">Lichter</string>
+    <string name="supercars_lights_blinking_label">Blinken</string>
+    <string name="sports_activity_confirm_delete_title">%d Aktivit盲ten l枚schen</string>
+    <string name="supercars_turbo_speed_label">Turbo-Geschwindigkeit</string>
+    <string name="pref_header_authentication">Authentifizierung</string>
+    <string name="sports_activity_confirm_delete_description">Bist du sicher, dass du %d Aktivit盲ten l枚schen m枚chtest\?</string>
+    <string name="devicetype_super_cars">Shell Racing</string>
+    <string name="watchface_setting_button_move_hands">Zeiger bewegen</string>
+    <string name="aerobicTrainingEffect">Aerober Effekt</string>
+    <string name="HeartRateZones">Herzfrequenzzonen</string>
+    <string name="hrZoneNa">K.A.</string>
+    <string name="hrZoneAerobic">Aerob</string>
+    <string name="hrZoneWarmUp">Aufw盲rmen</string>
+    <string name="hrZoneAnaerobic">Anaerob</string>
+    <string name="hrZoneExtreme">Extrem</string>
+    <string name="currentWorkoutLoad">Trainingsbelastung</string>
+    <string name="hrZoneFatBurn">Fettverbrennung</string>
+    <string name="anaerobicTrainingEffect">Anaerober Effekt</string>
+    <string name="TrainingEffect">Trainingseffekt</string>
+    <string name="maximumOxygenUptake">Maximale Sauerstoffaufnahme</string>
+    <string name="pref_chart_sleep_lines_limit">Anzahl der Schlafeinheiten bis gebl盲ttert werden muss</string>
+    <string name="elevationLoss">H枚henverlust</string>
+    <string name="elevationGain">H枚hengewinn</string>
+    <string name="devicetype_sony_wh_1000xm2">Sony WH-1000XM2</string>
+    <string name="devicetype_sony_wf_1000xm4">Sony WF-1000XM4</string>
+    <string name="balanced">Ausgewogen</string>
+    <string name="dual_band">Doppelband</string>
+    <string name="low_power_gps">Stromsparendes GPS</string>
+    <string name="gps">GPS</string>
+    <string name="gps_bds">GPS + BDS</string>
+    <string name="gps_gnolass">GPS + GLONASS</string>
+    <string name="speed_first">Priorit盲t auf Geschwindigkeit</string>
+    <string name="pref_cover_to_mute">Abdecken zum Stummschalten</string>
+    <string name="pref_workout_detection_title">Trainingserkennung</string>
+    <string name="pref_workout_detection_summary">Training automatisch erkennen</string>
+    <string name="pref_workout_detection_categories_summary">Trainingskategorien, die automatisch erkannt werden sollen</string>
+    <string name="pref_workout_detection_alert_summary">Benachrichtigen, wenn ein Training erkannt wird</string>
+    <string name="pref_workout_detection_alert_title">Hinweis</string>
+    <string name="pref_sleep_mode_smart_enable_summary">Schlafmodus automatisch aktivieren, wenn du das Ger盲t im Schlaf tr盲gst</string>
+    <string name="prefs_weardirection">Tragerichtung</string>
+    <string name="prefs_always_on_display_follow_watchface">Stil folgt Zifferblatt</string>
+    <string name="prefs_control_center">Kontrollzentrum</string>
+    <string name="prefs_control_center_summary">W盲hle die Elemente im Dropdown-Men眉 des Kontrollzentrums</string>
+    <string name="activity_prefs_goal_fat_burn_time_minutes">Tagesziel: Fettverbrennungszeit in Minuten</string>
+    <string name="activity_type_indoor_walking">Indoor-Gehen</string>
+    <string name="devicetype_amazfit_gtr4">Amazfit GTR 4</string>
+    <string name="menuitem_calendar">Kalender</string>
+    <string name="menuitem_todo">Aufgaben</string>
+    <string name="menuitem_voice_memos">Sprachnotizen</string>
+    <string name="menuitem_sun_moon">Sonne &amp; Mond</string>
+    <string name="menuitem_one_tap_measuring">Messen mit einem Tastendruck</string>
+    <string name="menuitem_phone">Telefon</string>
+    <string name="menuitem_theater_mode">Theatermodus</string>
+    <string name="menuitem_volume">Lautst盲rke</string>
+    <string name="menuitem_brightness">Helligkeit</string>
+    <string name="pref_screen_auto_brightness_summary">Anpassung der Bildschirmhelligkeit an das Umgebungslicht</string>
+    <string name="pref_gps_band">GPS-Band</string>
+    <string name="power_saving">Energiesparmodus</string>
+    <string name="custom">Benutzerdefiniert</string>
+    <string name="pref_gps_satellite_search">Satellitensuche</string>
+    <string name="pref_vibrate_for_alert">Vibration f眉r Warnung</string>
+    <string name="prefs_always_on_display_style">Stil</string>
+    <string name="pref_gps_header">GPS</string>
+    <string name="pref_crown_vibration">Vibration der Krone</string>
+    <string name="accuracy">Genauigkeit</string>
+    <string name="pref_sleep_mode_sleep_screen_title">Schlafbildschirm</string>
+    <string name="buttons_on_right">Tasten auf der rechten Seite</string>
+    <string name="menuitem_offline_voice">Offline Stimme</string>
+    <string name="menuitem_screen_always_lit">Bildschirm leuchtet immer</string>
+    <string name="pref_header_sound_vibration">Ton &amp; Vibration</string>
+    <string name="single_band">Einzelband</string>
+    <string name="gps_galileo">GPS + GALILEO</string>
+    <string name="pref_text_to_speech">Text in Sprache</string>
+    <string name="offline_voice_respond_turn_wrist">Reagiert beim Drehen des Handgelenks</string>
+    <string name="offline_voice_respond_screen_on">Reagieren bei eingeschaltetem Bildschirm</string>
+    <string name="offline_voice_response_during_screen_lighting">Reagieren bei Bildschirmbeleuchtung</string>
+    <string name="pref_agps_expiry_reminder_enabled">AGPS-Ablauferinnerung</string>
+    <string name="pref_agps_expiry_reminder_time">AGPS-Ablauferinnerungszeit</string>
+    <string name="all_satellites">Alle Satelliten</string>
+    <string name="fw_upgrade_notice_amazfit_gtr4">Du bist dabei, die Firmware %s auf deine Amazfit GTR 4 zu installieren.
+\n
+\nDeine Uhr wird nach der Installation der .zip Datei neu starten.
+\n
+\nINSTALLATION AUF EIGENE GEFAHR!</string>
+    <string name="pref_gps_mode_preset">GPS-Modus</string>
+    <string name="pref_workout_detection_sensitivity">Empfindlichkeit</string>
+    <string name="pref_enable_unsupported_settings_title">Nicht unterst眉tzte Einstellungen aktivieren</string>
+    <string name="menuitem_bluetooth">Bluetooth</string>
+    <string name="pref_header_offline_voice">Offline Stimme</string>
+    <string name="pref_agps_header">AGPS</string>
+    <string name="pref_sleep_mode_title">Schlafmodus</string>
+    <string name="vibration_profile_default">Standard</string>
+    <string name="pref_screen_notification_profile_schedule">Zeitplan</string>
+    <string name="seconds_25">25 Sekunden</string>
+    <string name="pref_enable_unsupported_settings_summary">Dadurch wird der Zugriff auf alle verf眉gbaren Einstellungen erm枚glicht, auch wenn diese vom Ger盲t nicht unterst眉tzt werden. Dies kann zu Instabilit盲t und Abst眉rzen auf dem Ger盲t f眉hren.</string>
+    <string name="pref_gps_combination">GPS-Kombination</string>
+    <string name="activity_prefs_goal_standing_time_minutes">Tagesziel: Stehzeit in Minuten</string>
+    <string name="pref_alert_tone">Warnton</string>
+    <string name="pref_workout_detection_categories_title">Trainingskategorien</string>
+    <string name="buttons_on_left">Tasten auf der linken Seite</string>
+    <string name="pref_screen_notification_profile_todo_list">Aufgabenliste</string>
+    <string name="activity_type_pool_swimming">Schwimmen im Pool</string>
+    <string name="menuitem_wifi">WLAN</string>
+    <string name="menuitem_lockscreen">Sperrbildschirm</string>
+    <string name="sony_speak_to_chat_sensitivity_standard">Standard</string>
+    <string name="pref_screen_auto_brightness_title">Automatische Helligkeit</string>
+    <string name="pref_sleep_mode_smart_enable_title">Intelligent aktivieren</string>
+    <string name="pref_sleep_mode_sleep_screen_summary">Schlafbildschirm anzeigen, wenn der Bildschirm w盲hrend des Ruhezustands aufgeweckt wird, um Ablenkungen zu verringern</string>
+    <string name="activity_prefs_target_weight_kg">Zielgewicht in kg</string>
+    <string name="menuitem_membership_cards">Mitgliedskarten</string>
+    <string name="menuitem_eject_water">Wasser auswerfen</string>
+    <string name="menuitem_unsupported">[NICHT UNTERST脺TZT] %s</string>
+    <string name="pref_title_lower_button_short_press_action">Untere Taste kurzes Dr眉cken</string>
+    <string name="pref_title_upper_button_long_press_action">Obere Taste langes Dr眉cken</string>
+    <string name="accuracy_first">Priorit盲t auf Genauigkeit</string>
+    <string name="pref_huami_truncate_fetch_operation_timestamps_title">Zeitstempel des Abrufvorgangs k眉rzen</string>
+    <string name="pref_huami_truncate_fetch_operation_timestamps_summary">K眉rze die Zeitstempel der Abrufvorg盲nge auf Minuten. Deaktiviere diese Einstellung, um die Zeitstempel in Sekunden beizubehalten, wenn du Probleme beim Abrufen sehr kurzer Workouts hast.</string>
+    <string name="debugactivity_confirm_remove_device_preferences_title">Ger盲teeinstellungen entfernen\?</string>
+    <string name="debugactivity_confirm_remove_device_preferences">Dadurch werden die Ger盲teeinstellungen f眉r alle angeschlossenen Ger盲te zur眉ckgesetzt. Bist du sicher\?</string>
+    <string name="pref_summary_notification_media_ignores_application_list">Medienbenachrichtigungen vor der App-Liste verarbeiten. Wenn diese Voreinstellung nicht aktiviert ist, m眉ssen Medien-Apps in der App-Liste zugelassen sein, damit die Mediensteuerung auf dem Ger盲t funktioniert.</string>
+    <string name="pref_title_notification_media_ignores_application_list">Medienbenachrichtigungen ignorieren die App-Liste</string>
+    <string name="fw_upgrade_notice_amazfit_band7">Du bist dabei, die Firmware %s auf dein Amazfit Band 7 zu installieren.
+\n
+\nDein Band wird nach der Installation der .zip-Datei neu starten.
+\n
+\nINSTALLATION AUF EIGENE GEFAHR!</string>
+    <string name="devicetype_amazfit_band7">Amazfit Band 7</string>
+    <string name="function_enabled">Aktiviert</string>
+    <string name="world_clock_code">Code</string>
+    <string name="devicetype_amazfit_gts4">Amazfit GTS 4</string>
+    <string name="pref_title_touch_spotify_official_app">Spotify (nur offizielle App)</string>
+    <string name="pref_title_touch_spotify_galaxy_app">Spotify (nur Galaxy Wearable App)</string>
+    <string name="fw_upgrade_notice_amazfit_gts4">Du bist dabei, die Firmware %s auf deine Amazfit GTS 4 zu installieren.
+\n
+\nDeine Uhr wird nach der Installation der .zip Datei neu starten.
+\n
+\nINSTALLATION AUF EIGENE GEFAHR!</string>
+    <string name="devicetype_galaxybuds_2">Galaxy Buds2</string>
+    <string name="prefs_touch_lock_buds2">Ber眉hrungssteuerung</string>
+    <string name="fw_upgrade_notice_amazfit_gts4_mini">Du bist dabei, die Firmware %s auf deine Amazfit GTS 4 Mini zu installieren.
+\n
+\nDeine Uhr wird nach der Installation der .zip Datei neu starten.
+\n
+\nINSTALLATION AUF EIGENE GEFAHR!</string>
+    <string name="devicetype_amazfit_gts4_mini">Amazfit GTS 4 Mini</string>
+    <string name="devicetype_sony_linkbuds_s">Sony LinkBuds S</string>
+    <string name="intent_api_allow_trigger_export_title">Datenbankexport zulassen</string>
+    <string name="menuitem_wechat_pay">WeChat Pay</string>
+    <string name="sony_ambient_sound_control_button_mode_nc_off">Ger盲uschunterdr眉ckung, Aus</string>
+    <string name="sony_ambient_sound_control_button_mode_as_off">Umgebungsger盲usche, Aus</string>
+    <string name="sony_quick_access_triple_tap">Schnellzugriff (Dreifachtippen)</string>
+    <string name="sony_ambient_sound_control_button_mode_nc_as_off">Ger盲uschunterdr眉ckung, Umgebungsger盲usche, Aus</string>
+    <string name="sony_quick_access_double_tap">Schnellzugriff (Doppeltippen)</string>
+    <string name="intent_api_broadcast_export_title">脺bertragung beim Datenbankexport</string>
+    <string name="sony_ambient_sound_control_button_mode_nc_as">Ger盲uschunterdr眉ckung, Umgebungsger盲usche</string>
+    <string name="appmanager_watchface_activate">Aktivieren</string>
+    <string name="appmanager_app_start">Start</string>
+    <string name="appmanager_downloaded_to_cache">%s in den Cache heruntergeladen</string>
+    <string name="appmanager_download_app_error">Fehler beim Herunterladen der App</string>
+    <string name="appmanager_app_download">In den Cache herunterladen</string>
+    <string name="appmanager_download_started">App-Download gestartet</string>
+    <string name="devicetype_asteroidos">AsteroidOS</string>
+    <string name="fw_upgrade_notice_amazfit_trex2">Du bist dabei, die Firmware %s auf deine Amazfit T-Rex 2 zu installieren.
+\n
+\nDeine Uhr wird nach der Installation der .zip Datei neu starten.
+\n
+\nINSTALLATION AUF EIGENE GEFAHR!</string>
+    <string name="devicetype_amazfit_trex_2">Amazfit T-Rex 2</string>
+    <string name="activity_type_rowing">Rudern</string>
+    <string name="georgian">Georgisch</string>
+    <string name="pref_workout_detection_enabled">Erkennung aktiviert</string>
+    <string name="pref_workout_detection_enabled_summary">Automatische Erkennung dieses Training aktivieren</string>
+    <string name="pref_workout_detection_ask_first">Frage mich zuerst</string>
+    <string name="pref_workout_detection_ask_first_summary">Best盲tigung auf der Uhr erforderlich, wenn dieses Training erkannt wird</string>
+    <string name="pref_workout_detection_time">Aktive Minuten vor der Erkennung</string>
+    <string name="pref_workout_detection_time_summary">Die Anzahl der Minuten, die das Training andauern muss, bevor es erkannt wird</string>
+    <string name="devicetype_galaxybuds_2_pro">Galaxy Buds2 Pro</string>
+    <string name="devicetype_soflow_s06">SoFlow SO6</string>
+    <string name="pref_title_lock_unlock">Sperren</string>
+    <string name="pref_header_intent_api">Intent-API</string>
+    <string name="pref_agps_update_time">AGPS Aktualisierungszeit</string>
+    <string name="pref_agps_expire_time">AGPS Ablaufzeit</string>
+    <string name="intent_api_allow_activity_sync_summary">Ausl枚sen der Aktivit盲tssynchronisierung 眉ber die Intent-API erlauben</string>
+    <string name="intent_api_allow_activity_sync_title">Aktivit盲tssynchronisierungsausl枚ser zulassen</string>
+    <string name="sony_ambient_sound_control_button_modes">Tastenmodi f眉r die Steuerung von Umgebungsger盲uschen</string>
+    <string name="intent_api_allow_trigger_export_summary">Datenbankexport 眉ber die Intent-API erlauben</string>
+    <string name="intent_api_broadcast_export_summary">Einen Intent via Broadcast verschicken, wenn der Datenbankexport abgeschlossen ist</string>
+    <string name="pref_workout_keep_screen_on_title">Bildschirm w盲hrend des Trainings eingeschaltet lassen</string>
+    <string name="pref_workout_keep_screen_on_summary">Der Bildschirm bleibt w盲hrend des Trainings eingeschaltet und die Helligkeit wird angepasst, um die Trainingsdaten in Echtzeit anzuzeigen</string>
+    <string name="pref_camera_remote_summary">Erm枚glicht es der Uhr, die Kamera des Telefons auszul枚sen</string>
+    <string name="pref_camera_remote_title">Kamera-Fernausl枚sung</string>
+    <string name="stop">Stop</string>
+    <string name="status">Status</string>
+    <string name="wifi_hotspot">WLAN Hotspot</string>
+    <string name="wifi_hotspot_summary">Kontrolliere den WLAN-Hotspot auf der Uhr</string>
+    <string name="wifi_hotspot_configuration">WLAN-Hotspot-Konfiguration</string>
+    <string name="wifi_ssid">SSID</string>
+    <string name="croatian">Kroatisch</string>
+    <string name="address">Adresse</string>
+    <string name="username">Nutzername</string>
+    <string name="sony_speak_to_chat_summary">Deaktiviere Ger盲uschunterdr眉ckung automatisch, wenn du anf盲ngst zu reden.</string>
+    <string name="ftp_server">FTP-Server</string>
+    <string name="ftp_server_stop_summary">Beende den FTP-Server auf der Uhr</string>
+    <string name="wifi_hotspot_status">Status des WLAN-Hotspots</string>
+    <string name="wifi_hotspot_start_summary">Starte den WLAN-Hotspot auf der Uhr</string>
+    <string name="wifi_hotspot_stop_summary">Beende den WLAN-Hotspot auf der Uhr</string>
+    <string name="ftp_server_summary">Steuern des FTP-Servers auf der Uhr</string>
+    <string name="ftp_server_start_summary">Starte den FTP-Server auf der Uhr</string>
+    <string name="ftp_server_status">Status des FTP-Servers</string>
+    <string name="ftp_server_configuration">FTP-Serverkonfiguration</string>
+    <string name="ftp_server_root_dir">Stammverzeichnis</string>
+    <string name="pref_morning_updates_title">Morgendliche Updates</string>
+    <string name="pref_morning_updates_summary">Zeigt jeden Morgen Updates an</string>
+    <string name="pref_morning_updates_categories_title">Morgendliche Kategorie-Updates</string>
+    <string name="yesterdays_activity">Gestrige Aktivit盲t</string>
+    <string name="pref_morning_updates_categories_summary">Liste der Kategorien, die jeden Morgen angezeigt werden</string>
+    <string name="pref_pair_bluetooth_calls_help_warning">WARNUNG: Wenn du Bluetooth-Anrufe aktivierst, ohne dich mit der zweiten Instanz zu koppeln, funktionieren die Anrufbenachrichtigungen m枚glicherweise nicht wie erwartet.</string>
+    <string name="pref_summary_receive_calls_watch">F眉hren und empfangen von Anrufen direkt auf der Uhr</string>
+    <string name="bluetooth_calls">Bluetooth-Anrufe</string>
+    <string name="bluetooth_calls_settings">Einstellungen f眉r Bluetooth-Anrufe</string>
+    <string name="pref_pair_bluetooth_calls_summary">Klicke hier, um den Kopplungsprozess zu starten</string>
+    <string name="pref_pair_bluetooth_calls_help_summary">Um Bluetooth-Anrufe empfangen zu k枚nnen, musst du dein Telefon mit einer zweiten Instanz der Uhr koppeln.</string>
+    <string name="pref_pair_bluetooth_calls_help_1">1. Tippe auf die Schaltfl盲che unten, um den Kopplungsprozess zu starten.</string>
+    <string name="bluetooth_calls_pairing">Bluetooth-Anrufe koppeln</string>
+    <string name="pref_display_caller_summary">Telefonnummer oder Name bei eingehenden Anrufen anzeigen</string>
+    <string name="pref_display_caller_title">Kontaktinformationen anzeigen</string>
+    <string name="pref_pair_bluetooth_calls_title">Koppeln f眉r Bluetooth-Anrufe</string>
+    <string name="pref_pair_bluetooth_calls_help_title">Wie man Bluetooth-Anrufe empf盲ngt</string>
+    <string name="pref_pair_bluetooth_calls_help_2">2. Gehe zu den Bluetooth-Einstellungen deines Telefons und verbinde es mit dem neuen Ger盲t, das angezeigt wird (盲hnlicher Name wie deine aktuelle Uhr, aber mit einem Suffix, z. B. \"Amazfit GTR 4 - AFC8\".</string>
+    <string name="pref_pair_bluetooth_calls_help_3">3. Aktiviere die nachstehende Einstellung f眉r Bluetooth-Anrufe.</string>
+    <string name="pref_title_notification_cache_while_disconnected">Cache au脽erhalb der Reichweite</string>
+    <string name="pref_summary_notification_cache_while_disconnected">Verpasste Benachrichtigungen senden, wenn sich ein Ger盲t wieder verbindet, nachdem es sich au脽erhalb der Reichweite befand</string>
+    <string name="zepp_os_watchface_business_style">Business-Stil</string>
+    <string name="triple_tap">Dreifaches Antippen</string>
+    <string name="long_press">Langes Dr眉cken</string>
+    <string name="sony_button_mode_help_title">Tastenmodi - Hilfe</string>
+    <string name="zepp_os_watchface_vibrant">Lebendig</string>
+    <string name="zepp_os_watchface_emerald_moonlight">Smaragdgr眉nes Mondlicht</string>
+    <string name="zepp_os_watchface_rotating_earth">Rotierende Erde</string>
+    <string name="zepp_os_watchface_superposition">脺berlagerung</string>
+    <string name="quick_attention">Schnelle Aufmerksamkeit</string>
+    <string name="double_tap">Doppeltes Antippen</string>
+    <string name="continue_pressing">Gedr眉ckt halten</string>
+    <string name="devicetype_amazfit_gtr3_pro">Amazfit GTR 3 Pro</string>
+    <string name="menuitem_vo2_max">VO鈧� Max</string>
+    <string name="menuitem_mi_ai">MI AI</string>
+    <string name="zepp_os_watchface_simplicity_data">Einfache Daten</string>
+    <string name="zepp_os_watchface_rush">Eile</string>
+    <string name="zepp_os_watchface_minimalist">Minimalistisch</string>
+    <string name="sony_button_mode_help_summary">Eine Beschreibung der einzelnen Tastenmodi</string>
+    <string name="menuitem_aqi">Luftqualit盲tsindex</string>
+    <string name="menuitem_total_workout">Training insgesamt</string>
+    <string name="menuitem_recommendation">Empfehlung</string>
+    <string name="zepp_os_watchface_red_fantasy">Rote Fantasie</string>
+    <string name="zepp_os_watchface_multiple_data">Mehrere Daten</string>
+    <string name="menuitem_cards">Karten</string>
+    <string name="fw_upgrade_notice_amazfit_gtr3_pro">Du bist dabei, die Firmware %s auf deine Amazfit GTR 3 Pro zu installieren.
+\n
+\nDeine Uhr wird nach der Installation der .zip Datei neu starten.
+\n
+\nINSTALLATION AUF EIGENE GEFAHR!</string>
+    <string name="devicetype_sony_wh_1000xm5">Sony WH-1000XM5</string>
+    <string name="menuitem_forecast">Vorhersage</string>
+    <string name="menuitem_last_workout">Letztes Training</string>
+    <string name="pref_switch_controls_anc_ambient_off">Ger盲uschunterdr眉ckung 鈫愨啋 Umgebungsger盲usche 鈫愨啋 Aus</string>
+    <string name="single_tap">Einmaliges Antippen</string>
+    <string name="prefs_shortcut_cards_summary">Verkn眉pfungskarten, die beim Wischen nach rechts auf dem Zifferblatt angezeigt werden. Wenn eine App ausgef眉hrt wird, sind automatisch generierte Karten von dieser Einstellung nicht betroffen.</string>
+    <string name="prefs_shortcut_cards">Verkn眉pfungskarten</string>
+    <string name="debug_companion_pair_current">Aktuelles Ger盲t als Begleiter koppeln</string>
+    <string name="debug_companion_show_associated">Zugeh枚rige Begleitger盲te anzeigen</string>
+    <string name="gpx_route_upload_failed">Fehler beim Hochladen der GPX-Route</string>
+    <string name="gpx_route_upload_in_progress">Hochladen der GPX-Route</string>
+    <string name="gpx_route_upload_complete">Hochladen der GPX-Route abgeschlossen</string>
+    <string name="activity_detail_share_gps_label">GPS-Track teilen</string>
+    <string name="kind_gpx_route">GPX-Route</string>
+    <string name="activity_error_share_failed">Teilen der Datei ist fehlgeschlagen.</string>
+    <string name="activity_detail_share_raw_details">Rohdaten teilen</string>
+    <string name="activity_detail_share_raw_summary">Rohdatenzusammenfassung teilen</string>
+    <string name="dev_tools">Entwicklungswerkzeuge</string>
+    <string name="fossil_hr_pairing_successful">Kopplung mit Uhr erfolgreich</string>
+    <string name="fossil_hr_confirm_connection">Bitte auf deiner Uhr best盲tigen</string>
+    <string name="fossil_hr_connection_not_confirmed">Verbindung auf der Uhr nicht best盲tigt, nutze unauthentifizierten Modus</string>
+    <string name="fossil_hr_pairing_failed">Kopplung mit Uhr fehlgeschlagen</string>
+    <string name="fossil_hr_confirmation_skipped">脺berspringen der Best盲tigung auf dem Ger盲t</string>
+    <string name="fossil_hr_confirmation_timeout">Zeit眉berschreitung bei der Best盲tigung, wird fortgesetzt</string>
+    <string name="pref_contacts_title">Kontakte</string>
+    <string name="contact_no_free_slots_description">Das Ger盲t hat keine freien Speicherpl盲tze f眉r Kontakte (Gesamtspeicherplatz: %1$s)</string>
+    <string name="title_activity_contact_details">Kontaktdetails</string>
+    <string name="contact_delete_confirm_description">Bist du sicher, dass du \'%1$s\' l枚schen m枚chtest\?</string>
+    <string name="contact_delete_confirm_title">Kontakt l枚schen</string>
+    <string name="contact_name">Name</string>
+    <string name="contact_phone_number">Telefonnummer</string>
+    <string name="title_activity_set_contacts">Kontakte konfigurieren</string>
+    <string name="contact_missing_name">Kontaktname ist leer</string>
+    <string name="pref_contacts_summary">Konfigurieren der Kontakte auf der Uhr</string>
+    <string name="contact_missing_number">Kontaktnummer ist leer</string>
+    <string name="pref_app_logs_stop_summary">Beenden der Protokollierung 眉ber Watch-Apps</string>
+    <string name="stress_relaxed">Entspannt</string>
+    <string name="stress_mild">Leicht</string>
+    <string name="stress_moderate">M盲脽ig</string>
+    <string name="stress_high">Hoch</string>
+    <string name="charts_legend_stress_average">Durchschnittliche Belastung</string>
+    <string name="intent_api_allow_debug_commands_summary">Ausl枚sen von Debug-Men眉befehlen 眉ber Intent-API erm枚glichen</string>
+    <string name="intent_api_allow_debug_commands_title">Debug-Befehle zulassen</string>
+    <string name="screenshot_taken">Screenshot aufgenommen</string>
+    <string name="share_screenshot">Screenshot teilen</string>
+    <string name="pref_summary_navigation_forward">Anweisungen von Navigations-Apps an die Uhr weiterleiten</string>
+    <string name="pref_title_navigation_forward">Navigation an Uhr senden</string>
+    <string name="voice_service_package_summary">Anwendung, die den Dienst zur Bearbeitung von Sprachbefehlen enth盲lt</string>
+    <string name="voice_service_class_title">Sprachdienstklasse</string>
+    <string name="voice_service_class_summary">Vollst盲ndiger Servicepfad zur Verarbeitung von Sprachbefehlen</string>
+    <string name="voice_service">Sprachdienst</string>
+    <string name="voice_service_package_title">Sprachdienstpaket</string>
+    <string name="pref_app_logs_title">App-Protokolle</string>
+    <string name="pref_app_logs_summary">Protokolle von Uhr-Apps aktivieren</string>
+    <string name="pref_app_logs_start_summary">Start der Protokollierung 眉ber Watch-Apps</string>
+    <string name="pref_header_navigation">Navigation</string>
+    <string name="fwapp_install_device_not_supported">Datei kann nicht installiert werden, das Ger盲t wird nicht unterst眉tzt.</string>
+    <string name="pref_title_banglejs_phone_gps_network_only">Standortbestimmung nur 眉ber das Netzwerk</string>
+    <string name="action_changelog">脛nderungsprotokoll</string>
+    <string name="pref_title_banglejs_phone_gps_enbale">GPS-Daten des Telefons verwenden</string>
+    <string name="title">Titel</string>
+    <string name="description">Beschreibung</string>
+    <string name="status_icon">Statussymbol</string>
+    <string name="pref_summary_banglejs_phone_gps_enbale">Verwende die GPS-Daten des Telefons, um die GPS-Daten des Armbands zu 眉berschreiben</string>
+    <string name="pref_summary_banglejs_phone_gps_network_only">Verwende nur den Netzbetreiber, um den Standort zu bestimmen. Dies reduziert den Stromverbrauch auf Kosten der Genauigkeit. Eine erneute Verbindung ist erforderlich.</string>
+    <string name="watchface_widget_type_uv_index">UV-Index</string>
+    <string name="pref_summary_banglejs_phone_gps_update_interval">Das Intervall in ms, in dem die GPS-Position aktualisiert wird</string>
+    <string name="preview_image">Vorschaubild</string>
+    <string name="changelog_full_title">脛nderungsprotokoll</string>
+    <string name="changelog_show_full">Mehr鈥�</string>
+    <string name="pref_title_banglejs_phone_gps_update_interval">Aktualisierungsintervall der GPS-Daten</string>
+    <string name="changelog_ok_button">OK</string>
+    <string name="changelog_title">Was gibt\'s Neues</string>
+    <string name="num_min">%d min</string>
+    <string name="pai_chart_per_week">PAI pro Woche</string>
+    <string name="pai_chart_per_month">PAI pro Monat</string>
+    <string name="pai_plus_num">+%d</string>
+    <string name="pai_total">PAI Gesamt</string>
+    <string name="loyalty_cards_sync_groups">Zu synchronisierende Gruppen</string>
+    <string name="loyalty_cards_sync_archived">Archivierte Karten synchronisieren</string>
+    <string name="loyalty_cards_catima_package">Catima Paketname</string>
+    <string name="loyalty_cards_install">Catima installieren</string>
+    <string name="loyalty_cards_sync_groups_only">Nur bestimmte Gruppen synchronisieren</string>
+    <string name="loyalty_cards_sync_summary">Tippe, um die Karten mit der Uhr zu synchronisieren</string>
+    <string name="loyalty_cards_sync_title">Kundenkarten synchronisieren</string>
+    <string name="loyalty_cards_catima_not_compatible">Die installierte Catima-Version ist nicht mit Gadgetbridge kompatibel. Bitte aktualisiere Catima und Gadgetbridge auf die neuesten Versionen.</string>
+    <string name="loyalty_cards_catima_permissions_summary">Gadgetbridge ben枚tigt Leseberechtigungen f眉r Catima-Karten, um diese zu synchronisieren. Tippe auf diese Schaltfl盲che, um sie zu erteilen.</string>
+    <string name="pref_title_mb_intents">Direktes 脺bertragen von Medienschaltfl盲chen-Intents</string>
+    <string name="pref_summary_mb_intents">Aktivieren, wenn die Mediensteuerung des Ger盲ts f眉r bestimmte Anwendungen nicht funktioniert</string>
+    <string name="pai_day">Erh枚hung des Tages-PAI</string>
+    <string name="loyalty_cards_install_catima_fail">App Store konnte nicht ge枚ffnet werden, um Catima zu installieren</string>
+    <string name="loyalty_cards_sync_starred">Nur markierte Karten synchronisieren</string>
+    <string name="loyalty_cards_catima_not_installed">Catima wird f眉r die Verwaltung der Kundenkarten ben枚tigt</string>
+    <string name="loyalty_cards_sync_options">Synchronisationsoptionen</string>
+    <string name="loyalty_cards_sync">Synchronisieren</string>
+    <string name="loyalty_cards_open_catima">Catima 枚ffnen</string>
+    <string name="loyalty_cards_catima_permissions_title">Fehlende Berechtigungen</string>
+    <string name="loyalty_cards">Kundenkarten</string>
+    <string name="loyalty_cards_syncing">Synchronisieren der %d Kundenkarten mit dem Ger盲t</string>
+    <string name="loyalty_cards_catima">Catima</string>
+    <string name="devicetype_bohemic_smart_bracelet">Bohemic Smart Bracelet</string>
+    <string name="devicetype_vivomove_hr">Garmin Vivomove HR</string>
+    <string name="devicetype_casiogmwb5000">Casio GMW-B5000</string>
+    <string name="devicetype_casiogwb5600">Casio GW-B5600</string>
+    <string name="busy_task_fetch_sports_details">Abrufen von Sportdaten</string>
+    <string name="busy_task_fetch_sports_summaries">Abrufen von Sport眉bersichten</string>
+    <string name="busy_task_fetch_debug_logs">Abrufen von Debug-Protokollen</string>
+    <string name="busy_task_fetch_stress_data">Abrufen von Stressdaten</string>
+    <string name="busy_task_fetch_pai_data">Abrufen von PAI-Daten</string>
+    <string name="busy_task_fetch_spo2_data">Abrufen von SpO2-Daten</string>
+    <string name="busy_task_fetch_sleep_respiratory_rate_data">Abruf von Daten zur Atemfrequenz im Schlaf</string>
+    <string name="error_no_bluetooth_scan">Zugriff auf den Bluetooth-Scan muss gew盲hrt und aktiviert sein, damit der Scanvorgang ordnungsgem盲脽 funktioniert</string>
+    <string name="error_no_bluetooth_connect">Zugriff auf die Bluetooth-Verbindung muss gew盲hrt und aktiviert sein, damit der Scanvorgang ordnungsgem盲脽 funktioniert</string>
+    <string name="permission_request">Mit %1$s kannst du Nachrichten und andere Daten von Android an dein Ger盲t senden. Dazu ist die Berechtigung f眉r den Zugriff auf diese Daten erforderlich, und ohne diese Berechtigung funktioniert sie m枚glicherweise nicht richtig.
+\n
+\nEs werden nun einige Android-Dialogfelder angezeigt, in denen diese Berechtigungen angefordert werden.
+\n
+\nBitte tippe auf \'%2$s\', um fortzufahren.</string>
+    <string name="busy_task_fetch_hr_data">Abrufen von Herzfrequenzdaten</string>
+    <string name="activity_prefs_goals">Ziele</string>
+    <string name="activity_type_surfing">Surfen</string>
+    <string name="activity_type_windsurfing">Windsurfen</string>
+    <string name="activity_type_football">Fu脽ball</string>
+    <string name="activity_type_rugby">Rugby</string>
+    <string name="activity_type_baseball">Baseball</string>
+    <string name="activity_type_handball">Handball</string>
+    <string name="activity_type_tennis">Tennis</string>
+    <string name="activity_type_squash">Squash</string>
+    <string name="activity_type_weightlifting">Gewichtheben</string>
+    <string name="activity_type_skiing">Skifahren</string>
+    <string name="activity_type_hockey">Hockey</string>
+    <string name="activity_type_icehockey">Eishockey</string>
+    <string name="activity_type_iceskating">Schlittschuhlaufen</string>
+    <string name="activity_type_golf">Golfen</string>
+    <string name="withings_calibration_text_hours">Bitte verwende das Zifferblatt unten, um den Stundenzeiger auf die 12 auszurichten.</string>
+    <string name="withings_calibration_text_minutes">Verwende nun das Zifferblatt, um den Minutenzeiger auf die 12 auszurichten.</string>
+    <string name="withings_calibration_text_activity_target">Zum Schluss richte den Aktivit盲tszeiger auf 100% aus. Bitte beachte, dass sich dieser Zeiger nur im Uhrzeigersinn bewegt.</string>
+    <string name="withings_bt_calibration_previous">Vorherige</string>
+    <string name="withings_bt_calibration_next">N盲chste</string>
+    <string name="activity_type_kitesurfing">Kitesurfen</string>
+    <string name="activity_type_snowboarding">Snowboarden</string>
+    <string name="activity_type_dancing">Tanzen</string>
+    <string name="activity_type_riding">Reiten</string>
+    <string name="activity_type_other">Andere</string>
+    <string name="withings_steel_hr">Withings Steel HR</string>
+    <string name="pref_canned_message">Nachricht</string>
+    <string name="find_my_phone_found_it">GEFUNDEN</string>
+    <string name="pref_theme_dynamic">Dynamische Farben</string>
+    <string name="preferences_miband_1_2_settings" tools:ignore="TypographyFractions">Mi Band 1/2 Einstellungen</string>
+    <string name="preferences_miband_1_2_warning">Warnung: Diese Einstellungen gelten nur f眉r die Mi Bands 1 und 2.</string>
+    <string name="pref_activity_full_sync_trigger_summary">Vollst盲ndige Synchronisation aller Aktivit盲tsdaten durchf眉hren</string>
+    <string name="pref_show_changelog">脛nderungsprotokoll beim Starten anzeigen</string>
+    <string name="pref_show_changelog_summary">Anzeige des 脛nderungsprotokolls seit der letzten Version, nachdem Gadgetbridge aktualisiert wurde</string>
+    <string name="pref_activity_full_sync_trigger_title">Vollst盲ndige Synchronisation</string>
+    <string name="pref_activity_full_sync_trigger_warning">Dadurch wird eine vollst盲ndige Synchronisation aller Aktivit盲tsdaten des Ger盲ts ausgel枚st. Es kann einige Minuten dauern, bis der Vorgang abgeschlossen ist.</string>
+    <string name="pref_theme_dynamic_colors_not_available_warning">Dynamische Farben sind auf deinem Ger盲t nicht verf眉gbar, nur Android 12+ unterst眉tzt diese Funktion. Gadgetbridge verwendet die Standardfarben von Material 3.</string>
+    <string name="pref_theme_dynamic_colors_explanation">Hinweis: F眉r das Design mit dynamischen Farben musst du Hintergrundbildfarben oder Farbpalette in deinen Android 12+ Darstellungseinstellungen aktivieren. Wenn du das nicht tust, wird Gadgetbridge die Standardfarben von Material 3 verwenden.</string>
+    <string name="menuitem_zepp_coach">Zepp Coach</string>
+    <string name="devicetype_amazfit_bip3_pro">Amazfit Bip 3 Pro</string>
+    <string name="fw_upgrade_notice_amazfit_cheetah_pro">Du bist dabei, die Firmware %s auf deine Amazfit Cheetah Pro zu installieren.
+\n
+\nDeine Uhr wird nach der Installation der .zip-Datei neu starten.
+\n
+\nINSTALLATION AUF EIGENE GEFAHR!</string>
+    <string name="fw_upgrade_notice_zepp_os">Du bist dabei, die Firmware %s auf deine %s zu installieren.
+\n
+\nDeine Uhr wird nach der Installation der .zip-Datei neu starten.
+\n
+\nINSTALLATION AUF EIGENE GEFAHR!</string>
+    <string name="devicetype_amazfit_cheetah_square">Amazfit Cheetah (Quadratisch)</string>
+    <string name="device_experimental">EXPERIMENTELL</string>
+    <string name="fw_upgrade_notice_amazfitbip3pro">Du bist dabei, die Firmware %s auf deine Amazfit Bip 3 Pro zu installieren.
+\n
+\nBitte stelle sicher, dass du die .fw Datei und danach die .res Datei installierst. Deine Uhr wird nach der Installation der .fw Datei neu gestartet.
+\n
+\nHinweis: Du musst .res nicht installieren, wenn es genau das gleiche ist, wie die zuvor installierte.
+\n
+\nINSTALLATION AUF EIGENE GEFAHR!</string>
+    <string name="devicetype_amazfit_cheetah_round">Amazfit Cheetah (Rund)</string>
+    <string name="devicetype_amazfit_cheetah_pro">Amazfit Cheetah Pro</string>
+    <string name="devicetype_amazfit_bip5">Amazfit Bip 5</string>
+    <string name="devicetype_amazfit_trex_ultra">Amazfit T-Rex Ultra</string>
+    <string name="devicetype_amazfit_falcon">Amazfit Falcon</string>
+    <string name="devicetype_amazfit_gtr_mini">Amazfit GTR Mini</string>
+    <string name="french_ca">Franz枚sisch (Kanada)</string>
+    <string name="updatefirmwareoperation_updateproblem_free_space">Das Ger盲t hat nicht gen眉gend freien Speicherplatz</string>
+    <string name="spanish_es">Spanisch (Spanien)</string>
+    <string name="english_in">Englisch (Indien)</string>
+    <string name="english_au">Englisch (Australien)</string>
+    <string name="text_receiver_activity_title">Text an das Ger盲t senden</string>
+    <string name="french_fr">Franz枚sisch (Frankreich)</string>
+    <string name="spanish_mx">Spanisch (Mexiko)</string>
+    <string name="english_gb">Englisch (Vereinigtes K枚nigreich)</string>
+    <string name="english_us">Englisch (USA)</string>
+    <string name="spanish_us">Spanisch (USA)</string>
+    <string name="info_fossil_rebuild_watchface_custom_menu">Bitte 眉berarbeite dein Zifferblatt f眉r das benutzerdefinierte Men眉</string>
+    <string name="english_ca">Englisch (Kanada)</string>
+    <string name="prefs_wena3_menu_icon_hint">Das Einstellungssymbol wird immer am Ende angezeigt</string>
+    <string name="prefs_wena3_notification_vibration_repetition_3">3 Mal</string>
+    <string name="prefs_wena3_button_action_name_qrio_lock">Qrio abschlie脽en</string>
+    <string name="prefs_wena3_vibration_short">Kurz</string>
+    <string name="info_menu_structure_contents">Men眉struktur: %s</string>
+    <string name="prefs_wena3_home_icon_name_calories">Kalorien</string>
+    <string name="prefs_home_icon_left_item">Links</string>
+    <string name="prefs_wena3_led_none">Keine LED</string>
+    <string name="updatefirmwareoperation_failed_low_mtu">Die aktuelle MTU von %1$d ist zu niedrig,bitte aktiviere eine hohe MTU in den Ger盲teeinstellungen und trenne/schlie脽e das Ger盲t neu an.</string>
+    <string name="prefs_wena3_home_icon_name_riiiver">Riiiver</string>
+    <string name="pref_message_privacy_mode_bodyonly">Nur Nachrichtentext ausblenden</string>
+    <string name="prefs_wena3_home_icon_name_suica">Suica Kontostand</string>
+    <string name="prefs_wena3_vibration_strength_item_medium">Mittel</string>
+    <string name="prefs_wena3_title_activity">Aktivit盲tseinstellungen</string>
+    <string name="prefs_wena3_vibration_continuous">Durchgehend</string>
+    <string name="prefs_wena3_home_icon_name_pedometer">Schrittz盲hler</string>
+    <string name="prefs_wena3_home_icon_name_qrio">Qrio</string>
+    <string name="devicetype_miband2_hrx">Mi Band HRX</string>
+    <string name="new_discover_activity_description">Aktiviere die neue Erkennungsaktivit盲t, die Probleme bei der Ger盲teerkennung beheben sollte. Deaktiviere diese Funktion, wenn du bei der Suche nach deinem Ger盲t oder der Kopplung mit diesem Probleme hast.</string>
+    <string name="white">Wei脽</string>
+    <string name="prefs_wena3_home_icon_name_clock">Aktuelle Zeit</string>
+    <string name="prefs_wena3_vibration_strength_item_weak">Schwach</string>
+    <string name="temperature_scale_celsius">Celsius</string>
+    <string name="prefs_home_icon_right_item">Rechts</string>
+    <string name="prefs_wena3_vibration_basic">Einfach</string>
+    <string name="prefs_wena3_title_screen">Anzeigeeinstellungen</string>
+    <string name="prefs_wena3_home_icon_name_energy">K枚rperenergie</string>
+    <string name="prefs_wena3_notification_vibration_repetition_2">Zweimal</string>
+    <string name="prefs_wena3_auto_power_off_hint">Das Ger盲t schaltet sich automatisch nach dem festgelegten Zeitplan aus und wieder ein</string>
+    <string name="prefs_wena3_hint_lift_wrist">Display einschalten, wenn du auf dein Handgelenk schaust</string>
+    <string name="prefs_wena3_vibration_rapid">Schnell</string>
+    <string name="prefs_wena3_button_action_name_qrio_unlock">Qrio 枚ffnen</string>
+    <string name="temperature_scale_cf">Temperaturskala</string>
+    <string name="prefs_wena3_notification_vibration_repetition_1">Einmal</string>
+    <string name="prefs_wena3_home_icon_name_wena_pay">Wena Pay</string>
+    <string name="new_discover_activity_title">Neue Entdeckungsaktivit盲t aktivieren</string>
+    <string name="green">Gr眉n</string>
+    <string name="yellow">Gelb</string>
+    <string name="devicetype_sony_wena3">Sony Wena 3</string>
+    <string name="red">Rot</string>
+    <string name="cyan">T眉rkis</string>
+    <string name="prefs_wena3_vibration_siren">Sirene</string>
+    <string name="temperature_scale_cf_summary">W盲hle aus, ob das Ger盲t die Celsius- oder die Fahrenheitskala verwendet.</string>
+    <string name="prefs_wena3_menu_icon_title">Men眉-Symbole</string>
+    <string name="prefs_wena3_notification_default_vibration">Vibration bei Benachrichtigung</string>
+    <string name="prefs_wena3_notification_vibration_repetition_0">Unbegrenzt</string>
+    <string name="prefs_wena3_notification_vibration_repetition_4">4 Mal</string>
+    <string name="temperature_scale_fahrenheit">Fahrenheit</string>
+    <string name="prefs_wena3_vibration_strength_item">Vibrationsst盲rke</string>
+    <string name="prefs_wena3_menu_icon_name_payment">Bezahlen</string>
+    <string name="prefs_wena3_notification_default_vibration_repetition">Wiederholung der Vibration bei Benachrichtigung</string>
+    <string name="prefs_home_icon_center_item">Mitte</string>
+    <string name="prefs_wena3_notification_default_led">Farbe der Benachrichtigungs-LED</string>
+    <string name="prefs_wena3_home_icon_name_edy">Edy Kontostand</string>
+    <string name="prefs_wena3_vibration_warning">Warnung</string>
+    <string name="prefs_wena3_vibration_none">Keine Vibration</string>
+    <string name="purple">Lila</string>
+    <string name="prefs_wena3_vibration_strength_item_strong">Stark</string>
+    <string name="prefs_wena3_vibration_triple">Dreifach</string>
+    <string name="blue">Blau</string>
+    <string name="prefs_wena3_notification_settings_title">Benachrichtigungseinstellungen</string>
+    <string name="devicetype_sony_wf_1000xm5">Sony WF-1000XM5</string>
+    <string name="prefs_wena3_receive_calls_title">Einstellungen f眉r eingehende Anrufe</string>
+    <string name="error_invalid_menu_structure">Ung眉ltiges JSON f眉r Men眉struktur</string>
+    <string name="prefs_wena3_item_large_font">Gr枚脽ere Schriftgr枚脽e</string>
+    <string name="button_reset_menu_structure">Men眉struktur zur眉cksetzen</string>
+    <string name="prefs_wena3_hint_large_font">Schriftgr枚脽e f眉r Kalender, Benachrichtigungen usw. vergr枚脽ern</string>
+    <string name="prefs_wena3_receive_calls_item">Anrufbenachrichtigungen empfangen</string>
+    <string name="prefs_wena3_notification_default_call_vibration">Eingehende Anrufvibration</string>
+    <string name="prefs_wena3_notification_default_call_led">LED-Farbe bei eingehendem Anruf</string>
+    <string name="prefs_wena3_notification_default_call_vibration_repetition">Wiederholung von eingehender Anrufvibration</string>
+    <string name="prefs_wena3_receive_calls_hint">Falls abgeschaltet, wirst du nicht 眉ber eingehende Anrufe auf der Wena benachrichtigt</string>
+    <string name="latvian">Lettisch</string>
+    <string name="info_menu_structure_removed">Men眉struktur entfernt</string>
+    <string name="prefs_wena3_home_icon_title">Startbildschirm-Symbole</string>
+    <string name="prefs_wena3_item_weather_statusbar">Wetter in Statusleiste</string>
+    <string name="avgStrokeRate">Durchschnittliche Schlagrate</string>
+    <string name="menuitem_headphone">Kopfh枚rer</string>
+    <string name="femometer_measurement_mode_normal">Normaler Modus (60s-90s)</string>
+    <string name="menuitem_zepp_pay">Zepp Pay</string>
+    <string name="femometer_measurement_mode_quick">Schneller Modus (30s)</string>
+    <string name="pref_title_notifications_ignore_work_profile">Ignoriere Arbeitsprofilbenachrichtigungen</string>
+    <string name="devicetype_amazfit_balance">Amazfit Balance</string>
+    <string name="yard">yd</string>
+    <string name="strokes_minute">Z眉ge/min</string>
+    <string name="femometer_measurement_mode_title">Messmodus</string>
+    <string name="femometer_measurement_mode_precise">Pr盲ziser Modus (3min)</string>
+    <string name="menuitem_workout_shortcuts">Trainingsverkn眉pfungen</string>
+    <string name="maxStrokeRate">Maximale Schlagrate</string>
+    <string name="laneLength">Bahnl盲nge</string>
+    <string name="strokes">Anzahl Z眉ge</string>
+    <string name="menuitem_apps_shortcuts">Apps-Verkn眉pfungen</string>
+    <string name="pref_summary_notifications_ignore_work_profile">Benachrichtigungen von Apps im Arbeitsprofil nicht an die Uhr senden</string>
+    <string name="menuitem_thermometer">Thermometer</string>
+    <string name="common_symbols">H盲ufige Symbole</string>
+    <string name="devicetype_femometer_vinca2">Femometer Vinca II</string>
+    <string name="activity_type_trekking">Wanderung</string>
+    <string name="activity_type_wrestling">Wrestling</string>
+    <string name="devicetype_redmi_smart_band_pro">Redmi Smart Band Pro</string>
+    <string name="menuitem_readiness">Bereitschaft</string>
+    <string name="devicetype_xiaomi_watch_lite">Xiaomi Watch Lite</string>
+    <string name="devicetype_amazfit_active_edge">Amazfit Active Edge</string>
+    <string name="pref_summary_casio_alert_call">Klingeln (Vibration/Ton) bei eingehenden Anrufen</string>
+    <string name="pref_title_casio_alert_email">Alarm f眉r Mail-Benachrichtigungen</string>
+    <string name="devicetype_miband7pro">Xiaomi Smart Band 7 Pro</string>
+    <string name="clap_hands_to_wakeup_device">Klatsche in die H盲nde, um den Bildschirm zu aktivieren</string>"
+    <string name="devicetype_colacao21">ColaCao 2021</string>
+    <string name="devicetype_colacao23">ColaCao 2023</string>
+    <string name="devicetype_redmiwatch3active">Redmi Watch 3 Active</string>
+    <string name="devicetype_redmi_smart_band_2">Redmi Smart Band 2</string>
+    <string name="standing_time">Steh-Zeit</string>
+    <string name="active_time">Aktiv-Zeit</string>
+    <string name="pref_title_send_app_notifications">Sende Benachrichtigungen</string>
+    <string name="pref_summary_send_app_notifications">Sende App-Benachrichtigungen an das Ger盲t</string>
+    <string name="pref_title_preview_message_in_title">Nachrichtenvorschau in Titelleiste anzeigen</string>
+    <string name="pref_summary_preview_message_in_title">Zeigt vom Ger盲t zugelassene Vorschau der Nachricht im Benachrichtigungstitel an</string>
+    <string name="pref_title_casio_alert_calendar">Alarmton f眉r Kalender-Erinnerungen</string>
+    <string name="pref_summary_casio_alert_calendar">Alarm (Vibration/Ton) f眉r Kalender-Erinnerungen</string>
+    <string name="pref_title_casio_alert_call">Klingelton bei eingehenden Anrufen</string>
+    <string name="pref_summary_casio_alert_email">Alarm (Vibration/Ton) f眉r Mail-Benachrichtigungen</string>
+    <string name="pref_title_casio_alert_sms">Alarm f眉r SMS-Benachrichtigungen</string>
+    <string name="pref_title_casio_alert_other">Alarm f眉r andere Benachrichtigungen</string>
+    <string name="pref_summary_casio_alert_other">Alarm (Vibration/Ton) f眉r Benachrichtigungen in anderen Kategorien</string>
+    <string name="devicetype_miband8">Xiaomi Smart Band 8</string>
+    <string name="devicetype_mijia_lywsd03">Mijia Temperatur- und Feuchtigkeitssensor 2</string>
+    <string name="menuitem_body_composition">K枚rperliche Zusammensetzung</string>
+    <string name="devicetype_amazfit_active">Amazfit Active</string>
+    <string name="pref_title_goal_secondary">Zus盲tzliches Ziel</string>
+    <string name="devicetype_redmi_watch_2_lite">Redmi Watch 2 Lite</string>
+    <string name="wake_up_time">Aufwachen</string>
+    <string name="pixoo_power_saving_summary">Der Bildschirm schaltet sich aus, wenn das Mikrofon eine Zeit lang Stille erkannt hat</string>
+    <string name="activity_info">Informationen zur Aktivit盲t</string>
+    <string name="prefs_password_4_digits_0_to_9_summary">Das Passwort muss 4-stellig sein und darf nur Zahlen enthalten</string>
+    <string name="activity_type_trail_run">Gel盲ndelauf</string>
+    <string name="pref_force_connection_type_title">Verbindungstyp erzwingen</string>
+    <string name="uploadwatchfaceoperation_complete">Installation des Zifferblatts abgeschlossen</string>
+    <string name="uploadwatchfaceoperation_failed">Installation des Zifferblatts fehlgeschlagen</string>
+    <string name="pref_heartrate_automatic_enable">Automatische Pulsmessung aktivieren</string>
+    <string name="zepp_os_watchface_vast_sky">Weitl盲ufiger Himmel</string>
+    <string name="zepp_os_watchface_pure_white">Reines Wei脽</string>
+    <string name="prefs_wena3_hint_rich_design">F眉gt abgerundete Rechtecke um Startbildschirmsymbole hinzu</string>
+    <string name="error_menu_companion_not_installed">\'HR Menu Companion\" ist wahrscheinlich nicht installiert</string>
+    <string name="pref_vitality_score_7_day_title">7-Tage-Fortschritt</string>
+    <string name="pref_vitality_score_daily_title">T盲glicher Fortschritt</string>
+    <string name="pref_vitality_score_title">Vitalit盲tswert</string>
+    <string name="zepp_os_watchface_guider">Reisef眉hrer</string>
+    <string name="prefs_phone_silent_mode">Telefonstummschaltung</string>
+    <string name="silent_mode_normal_vibrate">Normal / Vibration</string>
+    <string name="prefs_wena3_vibration_smart_item">Intelligente Vibration</string>
+    <string name="bedtime">Bettzeit</string>
+    <string name="prefs_wena3_auto_power_off_item">Automatisches Ausschalten</string>
+    <string name="prefs_disconnect_notification_summary">Benachrichtigung auf dem Ger盲t, wenn die Verbindung zu Bluetooth getrennt wird.</string>
+    <string name="dnd_all_day">Ganzt盲gig</string>
+    <string name="pref_force_options">Optionen erzwingen</string>
+    <string name="pref_force_smart_alarm">Intelligenten Alarm erzwingen</string>
+    <string name="notification_channel_connection_status_name">Status der Verbindung</string>
+    <string name="pref_force_dnd_support">Unterst眉tzung von \"Bitte-Nicht-St枚ren\" erzwingen</string>
+    <string name="charts_legend_spo2_average">Durchschnittlicher Blutsauerstoff</string>
+    <string name="pref_disable_find_phone_with_dnd">Telefon suchen deaktivieren, wenn Bitte-Nicht-St枚ren aktiv ist</string>
+    <string name="pref_enable_call_reject">Ablehnen von Anrufen aktivieren</string>
+    <string name="pref_spo_automatic_enable">Automatische SpO2-Messung aktivieren</string>
+    <string name="prefs_wena3_item_background_sync">Datensynchronisierung im Hintergrund</string>
+    <string name="prefs_wena3_title_alarm">Alarm Einstellungen</string>
+    <string name="updatefirmwareoperation_updateproblem_low_battery">Der Akku des Ger盲ts ist zu schwach</string>
+    <string name="silent_mode_normal_silent">Normal / Lautlos</string>
+    <string name="silent_mode_vibrate_silent">Vibration / Lautlos</string>
+    <string name="clap_hands_to_wakeup_device_summary">Erneutes Klatschen schaltet den Bildschirm aus</string>"
+    <string name="busy_task_fetch_statistics">Statistiken abrufen</string>
+    <string name="busy_task_fetch_temperature">Temperaturdaten abrufen</string>
+    <string name="prefs_wearmode">Tragemodus</string>
+    <string name="zepp_os_watchface_starry_sky">Sternenhimmel</string>
+    <string name="prefs_wena3_hint_background_sync">Erlaube Wena, Gadgetbridge in regelm盲脽igen Abst盲nden aufzufordern, Aktivit盲tsdaten vom Ger盲t herunterzuladen</string>
+    <string name="fossil_hr_nav_app_not_installed_notify_title">Navigations-App nicht auf der Uhr installiert</string>
+    <string name="call_rejection_method_reject">Ablehnen</string>
+    <string name="pref_sleep_mode_schedule_title">Zeitplan f眉r Schlafmodus</string>
+    <string name="pref_sleep_mode_schedule_summary">Sendet eine Erinnerung und schaltet zur Bettzeit in den Schlafmodus. Zur geplanten Aufwachzeit ert枚nt der Weckalarm.</string>
+    <string name="pref_vitality_score_7_day_summary">Erhalte eine Benachrichtigung, wenn dein Vitalit盲tswert in den letzten 7 Tagen 30, 60 oder 100 erreicht hat</string>
+    <string name="pref_vitality_score_daily_summary">Erhalte eine Benachrichtigung, wenn du die maximale Anzahl an Vitalit盲tspunkten f眉r den Tag erreicht hast</string>
+    <string name="wearmode_band">Armband</string>
+    <string name="wearmode_necklace">Halskette (Halsband)</string>
+    <string name="wearmode_pebble">Kieselsteine (Schuhschnalle)</string>
+    <string name="danish">D盲nisch</string>
+    <string name="pref_summary_casio_alert_sms">Alarm (Vibration/Ton) f眉r SMS (Textnachrichten)</string>
+    <string name="prefs_wena3_button_action_item_long">Lange dr眉cken</string>
+    <string name="menuitem_buzzer_intensity">Summer-Intensit盲t</string>
+    <string name="zepp_os_watchface_lightning_flash">Blitzschlag</string>
+    <string name="uploading_watchface">Zifferblatt hochladen鈥�</string>
+    <string name="uploadwatchfaceoperation_in_progress">Zifferblatt hochladen</string>
+    <string name="pref_title_fossil_hr_nav_vibrate">Vibrieren bei neuer Anweisung</string>
+    <string name="pref_title_fossil_hr_nav_foreground">In den Vordergrund r眉cken</string>
+    <string name="prefs_device_name">Ger盲tebezeichnung</string>
+    <string name="not_set">Nicht festgelegt</string>
+    <string name="widget_screen_delete_confirm_description">Bist du sicher, dass du \'%1$s\' l枚schen m枚chtest?</string>
+    <string name="widget_move_down">Nach unten verschieben</string>
+    <string name="protocol_version">Protokoll-Version</string>
+    <string name="flatDistance">Flache Strecke</string>
+    <string name="pref_enable_call_accept">Annehmen von Anrufen aktivieren</string>
+    <string name="fossil_hr_nav_app_not_installed_notify_text">Navigation gestartet, aber die Navigation-App ist nicht auf der Uhr installiert. Bitte installiere sie 眉ber den App-Manager.</string>
+    <string name="pref_summary_fossil_hr_nav_foreground">Ob die Navigations-App automatisch in den Vordergrund kommen soll, wenn sie eine Navigationsanweisung erh盲lt</string>
+    <string name="pref_force_connection_type_description">Du kannst versuchen, den Verbindungstyp zu erzwingen, falls dein Ger盲t nicht auf Gadgetbridge reagiert</string>
+    <string name="pref_force_connection_type_auto">Automatisch</string>
+    <string name="pref_force_connection_type_ble">Bluetooth-LE</string>
+    <string name="pref_device_action_dnd_alarms">Bitte-Nicht-St枚ren - nur Alarme</string>
+    <string name="pref_title_navigation_apps">Navigations-Apps</string>
+    <string name="pref_summary_osmand_packagename">Dient zur Auswahl der Version von OsmAnd, mit der eine Verbindung hergestellt werden soll</string>
+    <string name="pref_title_navigation_prefs">Einstellungen zur Navigation</string>
+    <string name="pref_title_osmand_packagename">OsmAnd Paketname</string>
+    <string name="widget_missing_parts">Bitte alle Widgets ausw盲hlen</string>
+    <string name="widget_layout_top_1_bot_2">1 oben, 2 unten</string>
+    <string name="widget_unknown_workout">Unbekanntes Workout - %s</string>
+    <string name="prefs_wena3_auto_power_off_turn_off_time_item">Ausschaltzeit</string>
+    <string name="pref_device_action_dnd_off">Bitte-Nicht-St枚ren - Aus</string>
+    <string name="zepp_os_watchface_free_combination">Freie Kombination</string>
+    <string name="pref_title_debug">Debug-Anfrage</string>
+    <string name="prefs_wena3_button_action_name_activity_screen">Aktivit盲tsbildschirm</string>
+    <string name="pref_title_general_reconnectonlytoconnected">Verbindung nur mit verbundenen Ger盲ten wiederherstellen</string>
+    <string name="pref_summary_general_reconnectonlytoconnected">Verbindung nur mit verbundenen Ger盲ten wiederherstellen, statt mit allen Ger盲ten neu zu verbinden</string>
+    <string name="prefs_active_noise_cancelling_transparency">Transparenz</string>
+    <string name="prefs_workmode">Arbeitsmodus</string>
+    <string name="pref_summary_debug">Eine Debug-Anfrage an das Huawei-Ger盲t senden</string>
+    <string name="prefs_wena3_hint_weather_statusbar">Symbol f眉r aktuelle Wetterbedingungen in der oberen linken Ecke des Startbildschirms anzeigen</string>
+    <string name="pref_title_prefix_notification_with_app">App-Name in der Benachrichtigung</string>
+    <string name="pref_device_action_dnd_on">Bitte-Nicht-St枚ren - Ein</string>
+    <string name="pref_force_dnd_support_summary">Unterst眉tzung von \"Bitte-Nicht-St枚ren\" erzwingen
+\nVERWENDUNG AUF EIGENE GEFAHR</string>
+    <string name="prefs_wena3_notification_per_app_settings_title">App-spezifische Einstellungen f眉r Benachrichtigungen</string>
+    <string name="serial_number">Seriennummer</string>
+    <string name="menuitem_stats">Statistiken</string>
+    <string name="menuitem_alerts">Meldungen</string>
+    <string name="widget_layout_top_2_bot_1">2 oben, 1 unten</string>
+    <string name="widget_layout_top_2_bot_2">2 oben, 2 unten</string>
+    <string name="widget_layout">Widget-Anordnung</string>
+    <string name="widget_layout_single">1 Widget</string>
+    <string name="widget_move_up">Nach oben verschieben</string>
+    <string name="widget_layout_two">2 Widgets</string>
+    <string name="call_rejection_method_ignore">Ignorieren (Stille)</string>
+    <string name="huawei_trusleep_summary_light">Verbesserte Schlaf眉berwachung</string>
+    <string name="pref_force_smart_alarm_summary">Unterst眉tzung f眉r intelligente Alarme erzwingen
+\nVERWENDUNG AUF EIGENE GEFAHR</string>
+    <string name="prefs_wena3_button_action_name_start_timer">Starte Aktivit盲tstimer</string>
+    <string name="pref_title_fossil_hr_navigation_instructions">Anweisungen f眉r die Navigation</string>
+    <string name="pref_summary_fossil_hr_nav_vibrate">Ob die Uhr bei jeder neuen oder ge盲nderten Navigationsanweisung vibrieren soll (nur wenn die App im Vordergrund ist)</string>
+    <string name="pref_summary_fossil_hr_navigation_instructions">Konfiguriere das Verhalten der Navigations-App</string>
+    <string name="manual">Manuell</string>
+    <string name="pref_do_not_disturb_not_wear">Bitte-Nicht-St枚ren wenn nicht getragen</string>
+    <string name="widget">Widget</string>
+    <string name="sony_protocol_v2">Version 2</string>
+    <string name="sony_protocol_v3">Version 3</string>
+    <string name="pref_navigation_app_gmaps">Google Maps</string>
+    <string name="devicetype_nothingear2">Nothing Ear (2)</string>
+    <string name="prefs_active_noise_cancelling_light">Leichte aktive Ger盲uschunterdr眉ckung</string>
+    <string name="huawei_trusleep_title">HUAWEI TruSleep 鈩�</string>
+    <string name="pref_device_action_dnd_priority">Bitte-Nicht-St枚ren - nur Priorit盲ten</string>
+    <string name="devicetype_honor_band4">Honor Band 4</string>
+    <string name="devicetype_honor_band5">Honor Band 5</string>
+    <string name="devicetype_honor_band6">Honor Band 6</string>
+    <string name="devicetype_honor_band7">Honor Band 7</string>
+    <string name="devicetype_huawei_band_aw70">Huawei Band (AW70)</string>
+    <string name="devicetype_huawei_band6">Huawei Band 6</string>
+    <string name="devicetype_huawei_band7">Huawei Band 7</string>
+    <string name="devicetype_huawei_band8">Huawei Band 8</string>
+    <string name="devicetype_huawei_watch_gt">Huawei Watch GT</string>
+    <string name="devicetype_huawei_band4pro">Huawei Band 4 (Pro)</string>
+    <string name="devicetype_huawei_watchgt2">Huawei Watch GT 2 (Pro)</string>
+    <string name="devicetype_huawei_watchgt2e">Huawei Watch GT 2e</string>
+    <string name="devicetype_huawei_talk_band_b6">Huawei Talk Band B6</string>
+    <string name="devicetype_huawei_watchgt3">Huawei Watch GT 3 (Pro)</string>
+    <string name="menuitem_running">Laufen</string>
+    <string name="devicetype_xiaomi_watch_s1_active">Xiaomi Watch S1 Active</string>
+    <string name="devicetype_mi_watch_color_sport">Mi Watch Color Sport</string>
+    <string name="devicetype_pixoo">Pixoo</string>
+    <string name="sony_protocol_v1">Version 1</string>
+    <string name="devicetype_xiaomi_watch_s1">Xiaomi Watch S1</string>
+    <string name="devicetype_xiaomi_watch_s3">Xiaomi Watch S3</string>
+    <string name="devicetype_xiaomi_watch_s1_pro">Xiaomi Watch S1 Pro</string>
+    <string name="devicetype_miband8pro">Xiaomi Smart Band 8 Pro</string>
+    <string name="devicetype_mijia_mho_c303">Mijia MHO-C303</string>
+    <string name="devicetype_honor_band3">Honor Band 3</string>
+    <string name="devicetype_nothingearstick">Nothing Ear (Stick)</string>
+    <string name="info_menu_structure_set">JSON-Men眉struktur in Gadgetbridge gesetzt</string>
+    <string name="zepp_os_watchface_the_ultima">The Ultima</string>
+    <string name="zepp_os_watchface_city_of_speed">City of Speed</string>
+    <string name="widget_screen">Widget-Bildschirm</string>
+    <string name="widget_screen_min_screens">Es m眉ssen mindestens %1$s Widget-Bildschirme vorhanden sein</string>
+    <string name="widget_screen_no_free_slots_description">Das Ger盲t hat keine freien Pl盲tze f眉r Widget-Bildschirme (insgesamt: %1$s)</string>
+    <string name="huawei_trusleep_summary">脺berwache deine Schlafqualit盲t und dein Atemmuster in Echtzeit.
+\nAnalysiere dein Schlafmuster und diagnostiziere genau 6 Arten von Schlafproblemen.</string>
+    <string name="prefs_wena3_button_action_item_double">Doppelt Dr眉cken</string>
+    <string name="prefs_wena3_day_start_hour_item">Der Tag beginnt um</string>
+    <string name="prefs_wena3_vibration_step_up">Schritt hoch</string>
+    <string name="prefs_wena3_vibration_step_down">Schritt runter</string>
+    <string name="huawei_ignore_wakeup_status_start_description">Kann bei der korrekten Schlaferkennung helfen. Sofort sichtbar in der Ansicht der t盲glichen Aktivit盲ten.</string>
+    <string name="huawei_alarm_smart_description">Deaktiviere nicht das Kontrollk盲stchen f眉r das intelligente Aufwecken.</string>
+    <string name="pref_force_options_summary">Einige Ger盲te behaupten f盲lschlicherweise, dass sie einige Optionen nicht unterst眉tzen. Diese Einstellungen k枚nnen verwendet werden, um sie trotzdem zu aktivieren.
+\nVERWENDUNG AUF EIGENE GEFAHR
+\nLese auf der Webseite</string>
+    <string name="pref_enable_call_reject_summary">Aktiviere das Abweisen von Anrufen vom Ger盲t</string>
+    <string name="pref_enable_call_accept_summary">Aktiviere die Annahme von Anrufen vom Ger盲t</string>
+    <string name="huawei_reparse_workout_data">Reparieren der Trainingsdaten</string>
+    <string name="huawei_alarm_event_description">Aktiviere nicht das Kontrollk盲stchen f眉r das intelligente Aufwecken.</string>
+    <string name="huawei_ignore_wakeup_status_end_description">Kann bei der korrekten Schlaferkennung helfen. Sofort sichtbar in der Ansicht der t盲glichen Aktivit盲ten.</string>
+    <string name="widget_screen_delete_confirm_title">Widget-Bildschirm l枚schen</string>
+    <string name="pref_force_wear_location">Erzwinge Standort der Uhr</string>
+    <string name="huawei_reparse_workout_data_description">Dies wird nur nach bestimmten Aktualisierungen geschehen</string>
+    <string name="prefs_wena3_button_action_item">Schaltfl盲chenaktion</string>
+    <string name="pref_call_rejection_method_summary">Welche Aktion wird ausgef眉hrt wenn ein eingehender Anruf von der Uhr abgelehnt wird</string>
+    <string name="do_not_disturb_lift_wrist_summary">Nur falls beim \"Hochheben Display aktivieren\" aktiviert ist</string>
+    <string name="menuitem_focus">Fokus</string>
+    <string name="pref_call_rejection_method_title">Methode zur Ablehnung von Anrufen</string>
+    <string name="prefs_wena3_auto_power_off_turn_on_time_item">Zeit fortsetzen</string>
+    <string name="heartrate_bpm_155">155 Schl盲ge/min</string>
+    <string name="heartrate_bpm_165">165 Schl盲ge/min</string>
+    <string name="heartrate_bpm_175">175 Schl盲ge/min</string>
+    <string name="heartrate_bpm_185">185 Schl盲ge/min</string>
+    <string name="heartrate_bpm_195">195 Schl盲ge/min</string>
+    <string name="heartrate_bpm_205">205 Schl盲ge/min</string>
+    <string name="devicetype_sony_wi_sp600n">Sony WI-SP600N</string>
+    <string name="pref_navigation_app_osmand">OsmAnd(+)</string>
+    <string name="pref_force_connection_type_bt_classic">Bluetooth-Classic</string>
+    <string name="prefs_heartrate_alert_active_high_threshold">Alarmschwelle f眉r hohe Aktivit盲t der Herzfrequenz</string>
+    <string name="hydration_dnd_summary">Deaktivieren von Hydrationswarnungen f眉r ein bestimmtes Zeitintervall</string>
+    <string name="activity_type_karate">Karate</string>
+    <string name="activity_type_kendo">Kendo</string>
+    <string name="activity_type_long_jump">Weitsprung</string>
+    <string name="activity_type_high_jump">Hochsprung</string>
+    <string name="activity_type_ice_hockey">Eishockey</string>
+    <string name="devicetype_honor_magicwatch2">Honor MagicWatch 2</string>
+    <string name="activity_type_trampoline">Trampolin</string>
+    <string name="activity_type_javelin">Speerwurf</string>
+    <string name="devicetype_nothing_cmf_watch_pro">CMF Watch Pro</string>
+    <string name="activity_type_belly_dance">Bauchtanz</string>
+    <string name="activity_type_hunting">Jagen</string>
+    <string name="pref_force_wear_location_summary">Erzwinge Unterst眉tzung f眉r Standort der Uhr
+\nBENUTZUNG AUF EIGENE GEFAHR</string>
+    <string name="activity_type_outdoor_walking">Outdoor-Wandern</string>
+    <string name="activity_type_rock_climbing">Bergsteigen</string>
+    <string name="activity_type_skateboarding">Skateboarden</string>
+    <string name="activity_type_snow_sports">Wintersport</string>
+    <string name="activity_type_cross_country_skiing">Skilanglauf</string>
+    <string name="activity_type_curling">Curling</string>
+    <string name="activity_type_jet_skiing">Jetski fahren</string>
+    <string name="activity_type_sailing">Segeln</string>
+    <string name="activity_type_mountain_hike">Bergwandern</string>
+    <string name="activity_type_indoor_running">Indoor-Laufen</string>
+    <string name="activity_type_cross_trainer">Crosstrainer</string>
+    <string name="activity_type_rower">Ruderer</string>
+    <string name="activity_type_fitness_exercises">Fitness眉bungen</string>
+    <string name="activity_type_functional_training">Funktionales Training</string>
+    <string name="activity_type_physical_training">K枚rperliches Training</string>
+    <string name="activity_type_taekwondo">Taekwondo</string>
+    <string name="activity_type_fencing">Fechten</string>
+    <string name="activity_type_cooldown">Abk眉hlung</string>
+    <string name="activity_type_dodgeball">V枚lkerball</string>
+    <string name="activity_type_softball">Softball</string>
+    <string name="activity_type_shuttlecock">Federball</string>
+    <string name="activity_type_fishing">Angeln</string>
+    <string name="activity_type_stairs">Treppen</string>
+    <string name="activity_type_jazz_dance">Jazz-Tanz</string>
+    <string name="activity_type_latin_dance">Lateinischer Tanz</string>
+    <string name="activity_type_ballet">Ballett</string>
+    <string name="activity_type_roller_skating">Rollschuhlaufen</string>
+    <string name="activity_type_martial_arts">Martial Arts</string>
+    <string name="activity_type_tai_chi">Tai-Chi</string>
+    <string name="activity_type_hula_hooping">Hula Hoop</string>
+    <string name="activity_type_darts">Darten</string>
+    <string name="activity_type_archery">Bogenschie脽en</string>
+    <string name="activity_type_horse_riding">Pferdereiten</string>
+    <string name="activity_type_swing">Schaukeln</string>
+    <string name="activity_type_kite_flying">Drachensteigen</string>
+    <string name="activity_type_lacross">Lacross</string>
+    <string name="activity_type_free_training">Freies Training</string>
+    <string name="activity_type_track_and_field">Leichtathletik</string>
+    <string name="activity_type_aerobic_exercise">Aerobic-脺bung</string>
+    <string name="activity_type_push_ups">Liegest眉tze</string>
+    <string name="activity_type_skating">Eislaufen</string>
+    <string name="activity_type_mind_and_body">K枚rper und Geist</string>
+    <string name="activity_type_hand_cycling">Handrad fahren</string>
+    <string name="activity_type_other_dance">Andere T盲nze</string>
+    <string name="activity_type_cross_country_running">Querfeldeinlauf</string>
+    <string name="activity_type_pull_ups">Klimmz眉ge</string>
+    <string name="activity_type_billiards">Billard</string>
+    <string name="devicetype_redmi_watch_2">Redmi Watch 2</string>
+    <string name="drag_handle">Ziehgriff</string>
+    <string name="prefs_wena3_item_rich_design">Rich-Design verwenden</string>
+    <string name="pref_summary_prefix_notification_with_app">Pr盲fix des Benachrichtigungstitels mit dem Namen der Quellanwendung</string>
+    <string name="huawei_ignore_wakeup_status_end">Endstatus des Weckens ignorieren</string>
+    <string name="huawei_ignore_wakeup_status_start">Startstatus beim Aufwachen ignorieren</string>
+    <string name="prefs_wena3_smart_alarm_margin_item">Intelligente Alarmspanne</string>
+    <string name="widget_subtype">Widget-Untertyp</string>
+    <string name="widget_screen_x">Widget-Bildschirm %s</string>
+    <string name="button_open_menu_companion">Begleit-App Men眉 枚ffnen</string>
+    <string name="activity_type_stair_stepper">Stepper</string>
+    <string name="activity_type_sit_ups">Sit-ups</string>
+    <string name="activity_type_fitness_gaming">Fitness-Spiele</string>
+    <string name="activity_type_flexibility">Beweglichkeit</string>
+    <string name="activity_type_dumbbell">Kurzhantel</string>
+    <string name="activity_type_crossfit">CrossFit</string>
+    <string name="activity_type_horizontal_bar">Horizontale Stange</string>
+    <string name="activity_type_parallel_bar">Barren</string>
+    <string name="activity_type_cross_training">Cross-Training</string>
+    <string name="activity_type_plank">Plank</string>
+    <string name="activity_type_rolling">Rollen</string>
+    <string name="activity_type_battle_rope">Seiltraining</string>
+    <string name="activity_type_smith_machine">Smith-Ger盲t</string>
+    <string name="activity_type_dynamic_cycle">Dynamischer Zyklus</string>
+    <string name="activity_type_pickleball">Pickleball</string>
+    <string name="activity_type_karting">Kartsport</string>
+    <string name="activity_type_disc_sports">Diskus-Sport</string>
+    <string name="activity_type_kabaddi">Kabaddi</string>
+    <string name="activity_type_australian_football">Australisches Football</string>
+    <string name="activity_type_shot">Shot</string>
+    <string name="prefs_wena3_status_page_title">Statusseite bereitstellen</string>
+    <string name="warning_missing_notification_permission">Benachrichtigungen konnten wegen fehlender Berechtigung nicht ver枚ffentlicht werden</string>
+    <string name="devicetype_huawei_watchfit">Huawei Watch Fit</string>
+    <string name="pref_test_features_title">Funktionen</string>
+    <string name="pref_test_features_summary">Aktivierte Funktionen f眉r dieses Testger盲t</string>
+    <string name="devicetype_redmiwatch3">Redmi Watch 3</string>
+    <string name="alarm_smart_wakeup_interval">Intelligentes Weckintervall:</string>
+    <string name="alarm_smart_wakeup_interval_default">5 Minuten</string>
+    <string name="device_state_waiting_scan">Warten auf Ger盲te-Scan</string>
+    <string name="auto_reconnect_ble_scan_summary">Warten auf Ger盲te-Scan anstelle von blinden Verbindungsversuchen</string>
+    <string name="waiting_for_bluetooth">warten auf Bluetooth鈥�</string>
+    <string name="scan_scanning_all_devices">Scannen aller Ger盲te</string>
+    <string name="scan_not_scanning">Nicht scannen</string>
+    <string name="error_scan_failed">Scan fehlgeschlagen: %d</string>
+    <string name="scan_scanning_single_device">Scannen von 1 Ger盲t</string>
+    <string name="scan_scanning_multiple_devices">Scannen von %d Ger盲ten</string>
+    <string name="notification_channel_scan_service_name">Scan-Dienst</string>
+    <string name="auto_reconnect_ble_scan_title">Wiederherstellung der Verbindung durch BLE-Scan</string>
+    <string name="prompt_restart_gadgetbridge">Bitte Gadgetbridge neu starten, damit es wirksam wird.</string>
+    <string name="devicetype_miband8active">Xiaomi Smart Band 8 Active</string>
+    <string name="temperature">Temperatur</string>
+    <string name="devicetype_sony_linkbuds">Sony LinkBuds</string>
+    <string name="pref_header_sony_sound_control">Klangkontrolle</string>
+    <string name="pref_wide_area_tap_summary">Klopfen zwischen Wangen und Ohren erkennen</string>
+    <string name="pref_adaptive_volume_control_summary">Automatische Erh枚hung der Lautst盲rke bei lauten Umgebungsger盲uschen</string>
+    <string name="pref_adaptive_volume_control_title">Adaptive Lautst盲rkeregelung</string>
+    <string name="devicetype_amazfit_bip3">Amazfit Bip 3</string>
+    <string name="pref_wide_area_tap_title">Weitr盲umiges Abgreifen</string>
+    <string name="fw_upgrade_notice_amazfitbip3">Du bist dabei, die %s-Firmware auf deiner Amazfit Bip 3 zu installieren.
+\n
+\nBitte stelle sicher, dass die .fw-Datei und danach die .res-Datei installiert wird. Die Uhr wird nach der Installation der .fw-Datei neu starten.
+\n
+\nHinweis: Du brauchst die .res-Datei nicht installieren, wenn sie mit der zuvor installierten Datei identisch ist.
+\n
+\nVORGEHEN AUF EIGENE GEFAHR!</string>
+    <string name="weeksleepchart_sleep_a_week_or_month">Schlaf pro Woche/Monat</string>
+    <string name="weekstepschart_steps_a_week_or_month">Schritte pro Woche/Monat</string>
+    <string name="devicetype_huawei_watchultimate">Huawei Watch Ultimate</string>
+    <string name="devicetype_huawei_watchgt4">Huawei Watch GT 4</string>
+    <string name="busy_task_fetch_sports_details_interrupted">Das Abrufen von Sportdaten wurde unterbrochen</string>
+    <string name="companion_pairing_request_title">Begleitendes Ger盲t</string>
+    <string name="unbind_before_pair_title">Bereits gebunden</string>
+    <string name="companion_pairing_request_description">Koppelst du dieses Ger盲t als Begleiter?
+\n
+\nDies wird f眉r einige Funktionen empfohlen, z. B. f眉r die Ger盲tesuche, und bietet eine bessere Verbindung.</string>
+    <string name="unbind_before_pair_message">Dieses Ger盲t ist bereits in den Android-Einstellungen gebunden, was dazu f眉hren kann, dass die Kopplung bei einigen Ger盲ten fehlschl盲gt.
+\n
+\nWenn das Hinzuf眉gen des Ger盲ts fehlschl盲gt, entferne es bitte in den Android-Einstellungen und versuche es erneut.</string>
+    <string name="devicetype_scannable">Scannbares Ger盲t</string>
+    <string name="state_scanned">Gescannt</string>
+    <string name="devicesetting_scannable_minimum_unseen">Minimale ungesehene Zeit (Sekunden)</string>
+    <string name="devicesetting_scannable_rssi">Minimaler RSSI-Schwellenwert</string>
+    <string name="devicesetting_scannable_debounce_summary">Nach dem Scannen bleibt das Ger盲t als gescannt und wird f眉r die angegebene Zeitspanne ignoriert</string>
+    <string name="devicesetting_scannable_minimum_unseen_summary">Nach dem Scannen muss das Ger盲t f眉r diese Zeitspanne unsichtbar sein, bevor es erneut registriert wird</string>
+    <string name="devicesetting_scannable_rssi_summary">Der minimale RSSI-Schwellenwert f眉r die Erkennung</string>
+    <string name="devicesetting_scannable_debounce">Zeit眉berschreitung der abtastbaren Entprellung (Sekunden)</string>
+    <string name="stepRateSum">Summe der Schrittgeschwindigkeit</string>
+    <string name="stepRateAvg">Mittlere Schrittfrequenz</string>
+    <string name="stepLengthAvg">Durchschnittliche Schrittl盲nge</string>
+    <string name="groundContactTimeAvg">Durchschnittliche Bodenkontaktzeit</string>
+    <string name="impactAvg">Durchschnittliche Auswirkungen</string>
+    <string name="impactMax">Maximale Wirkung</string>
+    <string name="swingAngleAvg">Durchschnittlicher Schwenkwinkel</string>
+    <string name="foreFootLandings">Landungen mit dem Vorderfu脽</string>
+    <string name="eversionAngleAvg">Durchschnittlicher Eversionswinkel</string>
+    <string name="eversionAngleMax">Maximaler Eversionswinkel</string>
+    <string name="unknownDataEncountered">Unbekannte Daten gefunden</string>
+    <string name="milliseconds">Millisekunden</string>
+    <string name="degrees">Grad</string>
+    <string name="swolfAvg">Mittlerer Swolf</string>
+    <string name="swolfMax">Maximaler Swolf</string>
+    <string name="swolfMin">Minimaler Swolf</string>
+    <string name="backFootLandings">Landungen mit dem Fu脽r眉cken</string>
+    <string name="midFootLandings">Landungen mit dem Mittelfu脽</string>
+    <string name="pref_force_enable_spo2_support">SpO2-Unterst眉tzung erzwingen</string>
+    <string name="pref_force_enable_heartrate_support">Unterst眉tzung der Herzfrequenz erzwingen</string>
+    <string name="pref_force_enable_spo2_support_summary">Erzwinge die Aktivierung der SpO2-Unterst眉tzung.
+\nVERWENDUNG AUF EIGENE GEFAHR</string>
+    <string name="pref_force_enable_heartrate_support_summary">Erzwinge die Herzfrequenzunterst眉tzung.
+\nVERWENDUNG AUF EIGENE GEFAHR</string>
+    <string name="error_showing_changelog">Fehler bei der Anzeige des Changelogs</string>
+    <string name="pref_dashboard_widget_goals_chart_title">Zieltabelle</string>
+    <string name="pref_dashboard_all_devices_title">Alle Ger盲te</string>
+    <string name="pref_dashboard_widgets_order_summary">W盲hle, welche Widgets aktiviert sind und in welcher Reihenfolge sie auf dem Dashboard angezeigt werden</string>
+    <string name="pref_dashboard_widget_today_24h_summary">Zeige die Aktivit盲t in einem einzelnen 24-Stunden-Kreis anstelle eines doppelten 12-Stunden-Kreis</string>
+    <string name="pref_dashboard_widget_double_size_summary">Erlaube dem Widget, zwei Spalten auf dem Dashboard einzunehmen</string>
+    <string name="pref_dashboard_widget_show_legend_summary">Eine Legende unterhalb des Widgets anzeigen, welche die Farben erkl盲rt</string>
+    <string name="pref_dashboard_widget_today_hr_interval_title">Herzfrequenz-Intervall</string>
+    <string name="activity_type_worn">Getragen</string>
+    <string name="dashboard_settings">Dashboard-Einstellungen</string>
+    <string name="bottom_nav_dashboard">Dashboard</string>
+    <string name="bottom_nav_devices">Ger盲te</string>
+    <string name="pref_dashboard_first_title">Dashboard zuerst anzeigen</string>
+    <string name="pref_dashboard_first_summary">Zeige beim Start von Gadgetbridge das Dashboard anstelle des Ger盲tebildschirms an</string>
+    <string name="pref_dashboard_cards_title">Widgets auf Karten anzeigen</string>
+    <string name="pref_dashboard_widget_today_hr_interval_summary">Die Anzahl der Minuten, die das Diagramm nach jeder erfolgreichen Herzfrequenzmessung \"Getragen\" anzeigt</string>
+    <string name="pref_dashboard_cards_summary">Zeichne Karten um die Widgets auf dem Dashboard</string>
+    <string name="pref_dashboard_widget_settings">Widget-Einstellungen</string>
+    <string name="pref_dashboard_widget_today_title">T盲tigkeitstabelle</string>
+    <string name="pref_dashboard_widget_today_24h_title">24h-Modus</string>
+    <string name="pref_dashboard_widget_double_size_title">Doppelte Gr枚脽e</string>
+    <string name="pref_dashboard_widget_show_legend_title">Legende anzeigen</string>
+    <string name="pref_dashboard_select_devices_title">Ger盲te ausw盲hlen...</string>
+    <string name="pref_dashboard_all_devices_summary">Kombiniere Aktivit盲tsdaten von allen hinzugef眉gten Ger盲ten f眉r die Gesamtzahlen auf dem Dashboard</string>
+    <string name="pref_dashboard_select_devices_summary">Kombiniere Aktivit盲tsdaten von bestimmten Ger盲ten f眉r die Gesamtzahlen auf dem Dashboard</string>
+    <string name="fmtPaceDistance">Tempo %d Entfernung</string>
+    <string name="pref_auto_reply_calls_delay_title">Automatische Antwortverz枚gerung</string>
+    <string name="pref_auto_reply_calls_delay_summary">Anzahl der Sekunden, nach denen der Anruf automatisch entgegengenommen wird</string>
+    <string name="pref_auto_reply_calls_title">Telefonanrufe automatisch annehmen</string>
+    <string name="pref_auto_reply_calls_summary">Das Telefon nimmt eingehende Telefonanrufe automatisch entgegen</string>
+    <string name="pref_dashboard_devices_to_include">Einzubeziehende Ger盲te</string>
+    <string name="RunningForm">Art der Durchf眉hrung</string>
+    <string name="pref_header_audio">Audio</string>
+    <string name="pref_header_generic">Allgemein</string>
+    <string name="pref_title_notification_wake_on_open">Automatisches Aufwachen und Entsperren</string>
+    <string name="pref_summary_notification_wake_on_open">Wecken und Entsperren des Android-Ger盲ts, wenn das Gadget eine OPEN-Antwort zur眉cksendet. Es muss sich in einem vertrauensw眉rdigen Zustand befinden.</string>
+    <string name="fmtPaceType">Tempo %d Typ</string>
+    <string name="fmtPacePace">Tempo %d Tempo</string>
+    <string name="fmtPaceCorrection">Tempo %d Korrektur</string>
+    <string name="fmtPaceTypeAverage">Tempo-Typ %d Durchschnitt</string>
+    <string name="Pace">Tempo</string>
+    <string name="widget_name_colored_tile">%1$s (farbige Kachel)</string>
+    <string name="armenian">Armenisch</string>
+    <string name="widget_layout_top_wide_bot_large">Oben weit, unten gro脽</string>
+    <string name="widget_name_untitled">Unbenanntes Widget (%1$s)</string>
+    <string name="pref_speak_notifications_aloud_summary">Benachrichtigungen werden 眉ber die Kopfh枚rer vorgelesen</string>
+    <string name="pref_speak_notifications_aloud_title">Lautes Sprechen von Benachrichtigungen</string>
+    <string name="pref_header_calls_and_notifications">Anrufe und Benachrichtigungen</string>
+    <string name="devicetype_redmi_watch_4">Redmi Watch 4</string>
+    <string name="widget_layout_top_large_bot_wide">Oben gro脽, unten weit</string>
+    <string name="pref_summary_bottom_navigation_bar_off">Wechseln zwischen den Hauptbildschirmen nur durch horizontales Wischen</string>
+    <string name="pref_title_bottom_navigation_bar">Untere Navigationsleiste</string>
+    <string name="pref_summary_bottom_navigation_bar_on">Wechsle zwischen den Hauptbildschirmen mit der Navigationsleiste oder durch horizontales Wischen</string>
+    <string name="pref_dashboard_widget_today_upside_down_title">Mitternacht ist unten</string>
+    <string name="pref_dashboard_widget_today_upside_down_summary">Im 24-Stunden-Modus zeichne Mitternacht unten und Mittag oben im Diagramm</string>
+    <string name="pref_sleepasandroid_device_title">Auszuw盲hlendes Ger盲t</string>
+    <string name="pref_sleepasandroid_device_summary">Ger盲t als Sleep as Android-Datenanbieter ausw盲hlen</string>
+    <string name="pref_sleepasandroid_features_summary">Die Unterst眉tzung ist von Ger盲t zu Ger盲t unterschiedlich</string>
+    <string name="pref_sleepasandroid_feat_alarms">Alarme</string>
+    <string name="alarm_slot_reset">Der Alarm-Platz wurde auf Standard eingestellt</string>
+    <string name="pref_sleepasandroid_feat_notifications">Benachrichtigungen</string>
+    <string name="pref_sleepasandroid_feat_movement">Beschleunigungssensor</string>
+    <string name="pref_sleepasandroid_feat_heartrate">Herzfrequenz</string>
+    <string name="pref_sleepasandroid_feat_spo2">SpO2</string>
+    <string name="sleepasandroid_settings">Sleep as Android</string>
+    <string name="pref_sleepasandroid_enable_summary">Aktiviere die Integration von Sleep as Android</string>
+    <string name="pref_sleepasandroid_features_title">Eigenschaften</string>
+    <string name="pref_sleepasandroid_slot_title">Alarm-Platz</string>
+    <string name="pref_sleepasandroid_slot_summary">Welcher Alarm-Platz beim Einstellen von Alarmen zu verwenden ist</string>
+    <string name="pref_sleepasandroid_feat_oximetry">Oximetrie</string>
+    <string name="pref_title_huawei_account">Huawei Account</string>
+    <string name="pref_summary_huawei_account">Huawei-Account, welcher f眉r den Kopplungsprozess verwendet wird. Die Einstellung erm枚glicht die Kopplung ohne Werksreset.</string>
+    <string name="serbian">Serbisch</string>
+    <string name="watchface_resolution_doesnt_match">Die Aufl枚sung des Watchface stimmt nicht mit dem Bildschirm des Ger盲ts 眉berein. Watchface ist %1$s Ger盲tebildschirm ist %2$s</string>
+    <string name="cyclingPowerMin">Minimale Leistung beim Radfahren</string>
+    <string name="cyclingPowerMax">Maximale Leistung beim Radfahren</string>
+    <string name="cyclingPowerAverage">Durchschnittliche Leistung beim Radfahren</string>
+    <string name="devicetype_huawei_watchfit2">Huawei Watch Fit 2</string>
+    <string name="huawei_trusleep_warning">Warnung: Wenn du diese Option aktivierst, wird jeder Schlaf in Gadgetbridge als leichter Schlaf angezeigt! Klicke hier, wenn du dies akzeptierst.</string>
+    <string name="pref_touch_tone_summary">Spielt einen Ton, wenn der Ohrh枚rer ber眉hrt wird</string>
+    <string name="pref_wearing_tone">Trageton</string>
+    <string name="devicetype_soundcore_liberty3_pro">Soundcore Liberty 3 Pro</string>
+    <string name="pref_touch_tone">Tastenton</string>
+    <string name="pref_wearing_tone_summary">Spielt einen Ton, wenn der Ohrh枚rer eingef眉hrt wird</string>
+    <string name="pref_adaptive_noise_cancelling_title">Adaptives ANC</string>
+    <string name="pref_adaptive_noise_cancelling_summary">Automatische Einstellung der ANC-St盲rke in Abh盲ngigkeit vom Umgebungsger盲uschpegel</string>
+    <string name="devicetype_huawei_watchfit3">Huawei Watch Fit 3</string>
+    <string name="preferences_qhybrid_settings_summary">脛ltere Einstellungen f眉r Q Hybrid-Uhren</string>
+    <string name="devicetype_huawei_band9">Huawei Band 9</string>
+    <string name="devicetype_huawei_watch4pro">Huawei Watch 4 Pro</string>
+    <string name="devicetype_cycling_sensor">Sensor f眉r Radfahrgeschwindigkeit</string>
+    <string name="pref_summary_wheel_diameter">Raddurchmesser in Zoll. In der Regel 29, 27.5 oder 26.</string>
+    <string name="pref_title_wheel_diameter">Raddurchmesser</string>
+    <string name="device_name_bicycle_sensor">Fahrradsensor</string>
+    <string name="title_cycling">Radfahren</string>
+    <string name="pref_summary_cycling_persistence_interval">Intervall in Sekunden, in dem die aktuellen Fahrraddaten in die Datenbank geschrieben werden sollen</string>
+    <string name="device_name_cycling_sensor">Fahrradsensor</string>
+    <string name="chart_cycling_point_label_distance">Heute: %.1f km
+\nGesamt: %.1f km</string>
+    <string name="chart_cycling_point_label_speed">%.1f km/h</string>
+    <string name="pref_title_cycling_persistence_interval">Persistenzintervall</string>
+    <string name="pref_speak_notifications_focus_exclusive_title">Audio pausieren von anderen Anwendungen</string>
+    <string name="toast_setting_requires_reconnect">Diese Einstellung wird nach einer Neuverbindung wirksam</string>
+    <string name="open_camera">Open Camera</string>
+    <string name="toast_camera_permission_required">F眉r diese Funktion ist eine Kameraerlaubnis erforderlich.</string>
+    <string name="toast_camera_support_required">Kameraunterst眉tzung ist f眉r diese Funktion erforderlich.</string>
+    <string name="toast_camera_photo_taken">Foto wurde aufgenommen und gespeichert unter: %s</string>
+    <string name="pref_speak_notifications_focus_exclusive_summary_on">Die Wiedergabe aus anderen Anwendungen wird angehalten, w盲hrend die Benachrichtigung gesprochen wird</string>
+    <string name="pref_speak_notifications_focus_exclusive_summary_off">Wiedergabelautst盲rke anderer Anwendungen wird gesenkt, w盲hrend die Benachrichtigung gesprochen wird</string>
+    <string name="devicetype_amazfit_bip5_unity">Amazfit Bip 5 Unity</string>
+    <string name="agps_status_missing">Fehlt</string>
+    <string name="agps_status_error">Fehler</string>
+    <string name="devicetype_garmin_instinct_solar">Garmin Instinct Solar</string>
+    <string name="devicetype_garmin_instinct_2_solar">Garmin Instinct 2 Solar</string>
+    <string name="devicetype_garmin_instinct_crossover">Garmin Instinct Crossover</string>
+    <string name="devicetype_garmin_forerunner_245">Garmin Forerunner 245</string>
+    <string name="devicetype_garmin_vivoactive_4">Garmin V铆voactive 4</string>
+    <string name="devicetype_garmin_vivoactive_4s">Garmin V铆voactive 4S</string>
+    <string name="devicetype_garmin_vivoactive_5">Garmin V铆voactive 5</string>
+    <string name="pref_title_garmin_default_reply_suffix">Vordefiniertes Antwortsuffix verwenden</string>
+    <string name="no_folder_selected">Kein Ordner ausgew盲hlt</string>
+    <string name="folder_is_empty">Ordner ist leer</string>
+    <string name="folder">Ordner</string>
+    <string name="url">URL</string>
+    <string name="garmin_agps_local_file">Lokale Datei</string>
+    <string name="copied_to_clipboard">In die Zwischenablage kopiert</string>
+    <string name="pref_agps_status">AGPS Status</string>
+    <string name="agps_status_pending">Anh盲ngig</string>
+    <string name="agps_status_current">Aktuell</string>
+    <string name="devicetype_garmin_vivomove_hr">Garmin V铆vomove HR</string>
+    <string name="devicetype_garmin_vivomove_style">Garmin V铆vomove Style</string>
+    <string name="devicetype_garmin_venu_3">Garmin Venu 3</string>
+    <string name="devicetype_garmin_instinct_2s">Garmin Instinct 2S</string>
+    <string name="devicetype_garmin_instinct_2_soltac">Garmin Instinct 2 SolTac</string>
+    <string name="devicetype_garmin_swim_2">Garmin Swim 2</string>
+    <string name="pref_summary_garmin_default_reply_suffix">Zus盲tzlich zu dem in Gadgetbridge eingestellten Suffix angef眉gt</string>
+    <string name="none">Keine</string>
+    <string name="garmin_agps_url_i">AGPS %1$d URL</string>
+    <string name="pref_garmin_agps_help">Die folgende Liste enth盲lt alle URLs, die von der Uhr f眉r AGPS-Aktualisierungen angefordert werden. Du kannst eine Datei aus dem Speicher des Telefons ausw盲hlen, die an die Uhr gesendet wird, wenn sie eine Aktualisierung anfordert.</string>
+    <string name="devicetype_garmin_venu_2_plus">Garmin Venu 2 Plus</string>
+    <string name="devicetype_garmin_vivosmart_5">Garmin V铆vosmart 5</string>
+    <string name="devicetype_garmin_instinct_2s_solar">Garmin Instinct 2S Solar</string>
+    <string name="devicetype_garmin_epix_pro">Garmin Epix Pro</string>
+    <string name="devicetype_garmin_instinct_2x_solar">Garmin Instinct 2X Solar</string>
+    <string name="intent_api_broadcast_activity_sync_title">脺bertragung bei Beendigung der Aktivit盲tssynchronisation</string>
+    <string name="intent_api_broadcast_activity_sync_summary">Senden einer Absichtserkl盲rung, wenn die Aktivit盲tssynchronisierung f眉r ein beliebiges Ger盲t abgeschlossen ist</string>
+    <string name="devicetype_garmin_fenix_6_sapphire">Garmin Fenix 6 Sapphire</string>
+    <string name="min_val">Minimum: %d</string>
+    <string name="activity_prefs_height_inches">H枚he in Zoll</string>
+    <string name="toggle_debug_mode">Umschalten des Debug-Modus</string>
+    <string name="loading">Laden鈥�</string>
+    <string name="share_debug_info">Debug-Informationen teilen</string>
+    <string name="realtime_settings">Einstellungen in Echtzeit</string>
+    <string name="unsupported">Nicht unterst眉tzt</string>
+    <string name="max_val">Maximum: %d</string>
+    <string name="devicetype_garmin_venu_3s">Garmin Venu 3S</string>
+    <string name="devicetype_garmin_fenix_7_pro">Garmin Fenix 7 Pro</string>
+    <string name="notif_battery_full_title">Gadget-Akku voll!</string>
+    <string name="notif_battery_full">%1$s Akku voll</string>
+    <string name="show_in_notification">In den Benachrichtigungen anzeigen</string>
+    <string name="battery_i">Akku %d</string>
+    <string name="battery_percentage_str">%1$s%%</string>
+    <string name="authentication_failed_check_key">Authentifizierung fehlgeschlagen, bitte 眉berpr眉fe den Authentifizierungsschl眉ssel</string>
+    <string name="devicetype_garmin_fenix_7s">Garmin Fenix 7S</string>
+    <string name="notification_channel_full_battery_name">Voller Akku</string>
+    <string name="battery_low_notify_enabled">Benachrichtigung bei schwachem Akku</string>
+    <string name="battery_low_threshold">Schwellenwert f眉r schwachen Akku</string>
+    <string name="battery_full_notify_enabled">Benachrichtigung bei vollem Akku</string>
+    <string name="battery_full_threshold">Schwellenwert f眉r vollen Akku</string>
+    <string name="default_percentage">Standard (%1$d%%)</string>
+    <string name="devicetype_garmin_forerunner_255">Garmin Forerunner 255</string>
+    <string name="dashboard_calendar_month_goals_reached_title">% des Schrittziels erreicht</string>
+    <string name="pref_fetch_unknown_files_summary">Holt unbekannte Aktivit盲tsdateien von der Uhr. Sie werden nicht verarbeitet, sondern im Telefon gespeichert.</string>
+    <plurals name="amount_of_days">
+        <item quantity="one">%d Tag</item>
+        <item quantity="other">%d Tage</item>
+    </plurals>
+    <string name="pref_fetch_unknown_files_title">Unbekannte Dateien abrufen</string>
+    <string name="devicetype_garmin_forerunner_265">Garmin Forerunner 265</string>
+    <string name="devicetype_garmin_venu_2">Garmin Venu 2</string>
+    <string name="devicetype_hama_fit6900">Hama Fit Watch 6900</string>
+    <string name="devicetype_garmin_forerunner_255s">Garmin Forerunner 255S</string>
+    <string name="devicetype_garmin_instinct">Garmin Instinct</string>
+    <string name="moondrop_equalizer_preset_basshead">Basskopf</string>
+    <string name="moondrop_touch_earbud">Ohrh枚rer</string>
+    <string name="moondrop_touch_earbud_both">Beide</string>
+    <string name="moondrop_touch_action_anc_mode">Aktive Ger盲uschunterdr眉ckung umschalten</string>
+    <string name="moondrop_touch_trigger_long_press_1s">Langer Druck (1s)</string>
+    <string name="moondrop_touch_action_play_pause">Wiedergabe/Pause</string>
+    <string name="devicetype_moondrop_space_travel">Moondrop Space Travel</string>
+    <string name="moondrop_equalizer_preset_reference">Referenz</string>
+    <string name="moondrop_equalizer_preset_monitor">Monitor</string>
+    <string name="moondrop_touch_action_call_pick_hang">W盲hlen/H盲ngen Anruf</string>
+    <string name="moondrop_touch_trigger">Ausl枚ser</string>
+    <string name="moondrop_touch_action_call_start">Anruf starten</string>
+    <string name="moondrop_touch_action_assistant">Sprachassistent ausl枚sen</string>
+    <string name="moondrop_touch_trigger_long_press_3s">Langer Druck (3s)</string>
+    <string name="devicetype_garmin_fenix_5">Garmin Fenix 5</string>
+    <string name="devicetype_garmin_fenix_6">Garmin Fenix 6</string>
+    <string name="devicetype_garmin_vivoactive_3">Garmin V铆voactive 3</string>
+    <string name="proprietary_app_warning">Diese Funktion erfordert die Installation einer propriet盲ren App</string>
+    <string name="battery_polling_failed_start">Die Batterieabfrage konnte nicht gestartet werden</string>
+    <string name="devicetype_garmin_vivomove_trend">Garmin V铆vomove Trend</string>
+    <string name="pref_battery_polling_configuration">Konfiguration der Batterieabfrage</string>
+    <string name="pref_battery_polling_summary">Dies geschieht nach bestem Bem眉hen und kann sich aus verschiedenen Gr眉nden verz枚gern</string>
+    <string name="pref_battery_polling_enable">Aktivieren der Batterieabfrage</string>
+    <string name="pref_battery_polling_interval">Intervall der Batterieabfrage</string>
+    <string name="pref_battery_polling_interval_format">alle %1$s Minuten</string>
+    <string name="devicetype_nothing_cmf_watch_pro_2">CMF Watch Pro 2</string>
+    <string name="devicetype_garmin_vivosport">Garmin V铆vosport</string>
+    <string name="pref_title_notification_times_enabled">Benachrichtigungszeiten</string>
+    <string name="activity_type_tae_bo">Tae Bo</string>
+    <string name="activity_type_beach_soccer">Strandfu脽ball</string>
+    <string name="activity_type_beach_volleyball">Strandvolleyball</string>
+    <string name="activity_type_gateball">Gateball</string>
+    <string name="activity_type_sepak_takraw">Sepak Takraw</string>
+    <string name="activity_type_luge">Rodeln</string>
+    <string name="activity_type_parachuting">Fallschirmspringen</string>
+    <string name="activity_type_auto_racing">Autorennen</string>
+    <string name="activity_type_parkour">Parkour</string>
+    <string name="pref_summary_notification_times_enabled">Benachrichtigungen nur zwischen bestimmten Zeiten senden</string>
+    <string name="devicetype_huawei_watch3">Huawei Watch 3 (Pro)</string>
+    <string name="devicetype_vivitar_hr_bp_monitor_activity_tracker">Vivitar HR &amp; BP Monitor Aktivit盲ts-Tracker</string>
+    <string name="devicetype_garmin_forerunner_255s_music">Garmin Forerunner 255S Music</string>
+    <string name="devicetype_nothing_cmf_buds_pro_2">CMF Buds Pro 2</string>
+    <string name="devicetype_garmin_forerunner_255_music">Garmin Forerunner 255 Music</string>
+    <string name="find_my_phone_companion_warning">Kopplung mit der Begleit-App ist f眉r das Suchhandy erforderlich. Klicke hier f眉r weitere Informationen.</string>
+    <string name="devicetype_huawei_watchgtrunner">Huawei Watch GT Runner</string>
+    <string name="hrv_status">HRV-Status</string>
+    <string name="abstract_chart_fragment_kind_awake_sleep">Wach</string>
+    <string name="sleep_colored_stats_deep">Tief</string>
+    <string name="sleep_colored_stats_light">Leicht</string>
+    <string name="sleep_colored_stats_rem">REM</string>
+    <string name="sleep_colored_stats_deep_avg">Tief AVG</string>
+    <string name="sleep_colored_stats_light_avg">Leicht AVG</string>
+    <string name="sleep_colored_stats_rem_avg">REM AVG</string>
+    <string name="hrv_status_day_avg">Tagesdurchschnitt</string>
+    <string name="hrv_status_balanced">Ausgewogen</string>
+    <string name="hrv_status_unbalanced">Unausgewogen</string>
+    <string name="hrv_status_low">Niedrig</string>
+    <string name="hrv_status_poor">Schlecht</string>
+    <string name="hrv_status_unit">%1$d ms</string>
+    <string name="hrv_status_baseline">%1$d-%2$d ms</string>
+    <string name="pref_header_hrv_status">HRV Status</string>
+    <string name="hrv_status_day_avg_legend">Tagesdurchschnitt (ms)</string>
+    <string name="hrv_status_seven_days_avg">7-Tage-Durchschnitt</string>
+    <string name="hrv_status_seven_days_avg_status">Status</string>
+    <string name="hrv_status_last_night">Letzte Nacht</string>
+    <string name="hrv_status_last_night_highest_5">Letzte Nacht 5-Min.-Max.-Durchschnitt</string>
+    <string name="hrv_status_seven_days_avg_long">7-Tage-Durchschnitt</string>
+    <string name="hrv_status_baseline_label">Grundlinie</string>
+    <string name="stats_empty_value">-</string>
+    <string name="stats_lowest_hr">Niedrigste HR</string>
+    <string name="stats_highest_hr">H枚chste HR</string>
+    <string name="devicetype_garmin_forerunner_965">Garmin Forerunner 965</string>
+    <string name="soundcore_equalizer_direction">Ger盲teausrichtung</string>
+    <string name="soundcore_equalizer_direction_lying">Liegend</string>
+    <string name="soundcore_equalizer_direction_hanging">H盲ngend</string>
+    <string name="soundcore_equalizer_reset_title">Auf Standard zur眉cksetzen</string>
+    <string name="soundcore_equalizer_custom_title">Anpassen鈥�</string>
+    <string name="soundcore_equalizer_custom_summary">Parametrische Klangeinstellungen konfigurieren</string>
+    <string name="soundcore_equalizer_preset_xtra_bass">Xtra Bass</string>
+    <string name="soundcore_equalizer_preset_voice">Stimme</string>
+    <string name="soundcore_equalizer_preset_balanced">Ausbalanciert</string>
+    <string name="soundcore_equalizer_direction_standing">Stehend</string>
+    <string name="soundcore_equalizer_reset_summary">Alle Equalizer auf Standard zur眉ck setzen</string>
+    <string name="soundcore_equalizer_band1">Band 1</string>
+    <string name="soundcore_equalizer_frequency">Frequenz</string>
+    <string name="soundcore_equalizer_value">Wert</string>
+    <string name="minutes_60">60 Minuten</string>
+    <string name="devicetype_soundcore_motion300">Soundcore Motion 300</string>
+    <string name="soundcore_voice_prompts">Sprachansagen</string>
+    <string name="soundcore_button_brightness">Schaltfl盲che Helligkeit</string>
+    <string name="soundcore_button_brightness_medium">Mittel</string>
+    <string name="soundcore_ldac_mode_title">LDAC-Modus</string>
+    <string name="soundcore_equalizer_preset">Voreinstellung</string>
+    <string name="soundcore_equalizer_preset_signature">Soundcore Signatur</string>
+    <string name="minutes_20">20 Minuten</string>
+    <string name="devicetype_garmin_fenix_5_plus">Garmin Fenix 5 Plus</string>
+    <string name="soundcore_button_brightness_low">Niedrig</string>
+    <string name="soundcore_button_brightness_high">Hoch</string>
+    <string name="soundcore_ldac_mode_summary">Die Aktivierung von LDAC verk眉rzt die Batterielebensdauer und kann zu einer instabilen Verbindung f眉hren</string>
+    <string name="soundcore_adaptive_direction_title">Adaptive Richtung</string>
+    <string name="soundcore_adaptive_direction_summary">Automatische Anpassung der Equalizer-Voreinstellung je nach Ausrichtung des Ger盲ts</string>
+    <string name="gps_glonass">GPS + GLONASS</string>
+    <string name="soundcore_equalizer_band2">Band 2</string>
+    <string name="soundcore_equalizer_band9">Band 9</string>
+    <string name="soundcore_equalizer_band3">Band 3</string>
+    <string name="soundcore_equalizer_band5">Band 5</string>
+    <string name="soundcore_equalizer_band4">Band 4</string>
+    <string name="soundcore_equalizer_band7">Band 7</string>
+    <string name="soundcore_equalizer_band6">Band 6</string>
+    <string name="soundcore_equalizer_band8">Band 8</string>
+    <string name="body_energy_lost">Verloren</string>
+    <string name="body_energy_legend_level">K枚rperliches Energieniveau</string>
+    <string name="body_energy_gained">Gewonnen</string>
+    <string name="body_energy">K枚rperenergie</string>
+    <string name="transition">脺berleitung</string>
+    <string name="minutes_3">3 Minuten</string>
+    <string name="minutes_15">15 Minuten</string>
+    <string name="minutes_6">6 Minuten</string>
+    <string name="minutes_9">9 Minuten</string>
+    <string name="minutes_45">45 Minuten</string>
+    <string name="minutes_2">2 Minuten</string>
+    <string name="minutes_4">4 Minuten</string>
+    <string name="minutes_8">8 Minuten</string>
+    <string name="minutes_7">7 Minuten</string>
+    <string name="sleep_colored_stats_awake_avg">Durchschnitt Wach</string>
+    <string name="minutes_150">150 Minuten</string>
+    <string name="minutes_210">210 Minuten</string>
+    <string name="minutes_240">240 Minuten</string>
+    <string name="minutes_255">255 Minuten</string>
+    <string name="authentication_failed_negotiation">Aushandlung des Authentifizierungsschl眉ssels gescheitert</string>
+    <string name="activity_type_handcycling_indoor">Handbikefahren Indoor</string>
+    <string name="activity_type_paddling">Paddeln</string>
+    <string name="activity_type_flying">Fliegen</string>
+    <string name="activity_type_bouldering">Bouldern</string>
+    <string name="activity_type_water_skiing">Wasserskifahren</string>
+    <string name="activity_type_kayaking">Kajakfahren</string>
+    <string name="activity_type_rafting">Rafting</string>
+    <string name="activity_type_navigate">Navigieren</string>
+    <string name="activity_type_indoor_track">Indoor Strecke</string>
+    <string name="activity_type_handcycling">Handbikefahren</string>
+    <string name="activity_type_e_bike">E-Bike</string>
+    <string name="activity_type_bike_commute">Pendeln zur Arbeit mit dem Fahrrad</string>
+    <string name="activity_type_platform_tennis">Plattformtennis</string>
+    <string name="activity_type_training">Training</string>
+    <string name="activity_type_xc_classic_ski">Langlauf</string>
+    <string name="activity_type_mountaineering">Bergsteigen</string>
+    <string name="activity_type_motorcycling">Motorradfahren</string>
+    <string name="activity_type_boating">Bootfahren</string>
+    <string name="activity_type_driving">Fahren</string>
+    <string name="activity_type_sail_race">Regattasegeln</string>
+    <string name="activity_type_sail_expedition">Segelt枚rn</string>
+    <string name="activity_type_floor_climbing">Treppensteigen</string>
+    <string name="activity_type_breathwork">Atem眉bungen</string>
+    <string name="activity_type_inline_skating">Inlineskating</string>
+    <string name="pref_time_sync">Automatische Zeitsynchronisation</string>
+    <string name="dateformat_day_month">Tag, Monat</string>
+    <string name="dateformat_month_day">Monat, Tag</string>
+    <string name="activity_type_cardio">Kardiotraining</string>
+    <string name="busy_task_fetch_sleep_data">Schlaf-Daten werden abgerufen</string>
+    <string name="pref_header_sound">Audio</string>
+    <string name="activity_type_rem_sleep">REM-Schlaf</string>
+    <string name="choose_device">Ger盲t ausw盲hlen</string>
+    <string name="no_supported_devices_found">Keine unterst眉tzten Ger盲te gefunden</string>
+    <string name="search">Suche</string>
+    <string name="hr_resting">Erholung</string>
+    <string name="hr_maximum">Maximum</string>
+    <string name="hr_minimum">Minimum</string>
+    <string name="hr_average">Durchschnitt</string>
+    <string name="minutes_75">75 Minuten</string>
+    <string name="minutes_90">90 Minuten</string>
+    <string name="minutes_120">120 Minuten</string>
+    <string name="minutes_180">180 Minuten</string>
+    <string name="stress_average">Durchschnitt</string>
+    <string name="activity_type_transition">脺bergang</string>
+    <string name="activity_type_fitness_equipment">Fitnessger盲te</string>
+    <string name="activity_type_multisport">Multisport</string>
+    <string name="activity_type_hang_gliding">Drachenfliegen</string>
+    <string name="activity_type_climb_indoor">Hallenklettern</string>
+    <string name="activity_type_ice_skating">Schlittschuhlaufen</string>
+    <string name="activity_type_sky_diving">Fallschirmspringen</string>
+    <string name="activity_type_snowshoe">Schneeschuhwandern</string>
+    <string name="activity_type_snowmobiling">Schneemobilfahren</string>
+    <string name="activity_type_stand_up_paddleboarding">Stand-up-Paddling</string>
+    <string name="activity_type_jumpmaster">Absetzer (Fallschirmspringen)</string>
+    <string name="pref_description_user_interface">Theme, Hauptbildschirm</string>
+    <string name="pref_title_nagivation_apps">Navigations-Apps</string>
+    <string name="pref_header_external_integrations">Externe Integrationen</string>
+    <string name="pref_header_automations">Automatisierungen</string>
+    <string name="pref_description_general">Startup, Sprache, Region, Standort</string>
+    <string name="pref_description_about_you">Geburtsdatum, Geschlecht, K枚rpergr枚脽e, K枚rpergewicht, Ziele</string>
+    <string name="pref_description_notifications">App-Benachrichtigungen, Whitelist/Blacklist</string>
+    <string name="pref_description_dashboard">Widget, einzuschlie脽enden Ger盲te</string>
+    <string name="pref_description_automations">Automatischer Export, automatischer Abruf</string>
+    <string name="pref_description_developer_options">Protokolle, Intent API</string>
+    <string name="pref_description_deprecated_functionalities">Einstellungen, welche in einer zuk眉nftigen Version entfernt werden</string>
+    <string name="pref_header_main_screen">Hauptbildschirm</string>
+    <string name="pref_title_user_interface">Benutzeroberfl盲che</string>
+    <string name="user_feedback_set_settings_ok">Einstellungen wurden an das Ger盲t gesendet.</string>
+    <string name="activity_prefs_date_birth">Geburtsdatum</string>
+    <string name="activity_type_shooting">Schie脽en</string>
+    <string name="hrv">HFV</string>
+    <string name="activity_type_winter_sport">Wintersport</string>
+    <string name="activity_type_video_gaming">Videospiel</string>
+    <string name="devicetype_miband9">Xiaomi Smart Band 9</string>
+    <string name="pref_header_deprecated_functionalities">Veraltete Funktionalit盲ten</string>
+    <string name="mijia_lywsd_comfort_temperature_title">Temperatur (掳C)</string>
+    <string name="activity_type_breaking">Breaking</string>
+    <string name="target">Ziel</string>
+    <string name="pref_title_sync_birthdays">Geburtstage synchronisieren</string>
+    <string name="activity_type_spinning">Spinning</string>
+    <string name="devicetype_huawei_watchgt5">Huawei Watch GT 5 (Pro)</string>
+    <string name="busy_task_fetch_hrv_data">Abrufen von HRV-Daten</string>
+    <string name="activity_type_softball_slow_pitch">Slowpitch-Softball</string>
+    <string name="activity_type_disc_golf">Discgolf</string>
+    <string name="activity_type_snorkeling">Schnorcheln</string>
+    <string name="activity_type_ultimate_disc">Ultimate Frisbee</string>
+    <string name="activity_type_bmx">BMX</string>
+    <string name="activity_type_bocce">Boccia</string>
+    <string name="activity_type_esports">E-Sport</string>
+    <string name="activity_type_checkers">Dame (Brettspiel)</string>
+    <string name="mijia_lywsd_comfort_level_title">Komfort-Level</string>
+    <string name="mijia_lywsd_comfort_humidity_title">Luftfeuchtigkeit (%)</string>
+    <string name="average_respiration_rate">Atemfrequenz durchschnittlich</string>
+    <string name="breaths_per_min">Atemz眉ge/min</string>
+    <string name="fmtPaceAverage">Durchschnittliches Tempo</string>
+    <string name="milliseconds_ms">ms</string>
+    <string name="pref_summary_sync_birthdays">Geburtstage von Kontakten zusammen mit Kalender-Events synchronisieren</string>
+    <string name="activity_type_board_game">Brettspiel</string>
+    <string name="menuitem_weight">Gewicht</string>
+    <string name="pref_crash_notification_title">Absturzbenachrichtigung</string>
+    <string name="pref_crash_notification_summary">Zeige eine Benachrichtigung mit dem Fehler, wenn die App abst眉rzt</string>
+    <string name="steps_avg">Schritte durchschnittlich</string>
+    <string name="miscale_small_objects_title">Leichte Gegenst盲nde</string>
+    <string name="pref_continuous_skin_temperature_measurement_title">Kontinuierliche Messung der Hauttemperatur</string>
+    <string name="activity_type_mixed_martial_arts">Mixed Martial Arts</string>
+    <string name="activity_type_chess">Schach</string>
+    <string name="miscale_weight_unit_metric">Metrisch (kg)</string>
+    <string name="miscale_weight_unit_title">Gewichtseinheit</string>
+    <string name="miscale_weight_unit_summary">Gewichtseinheit f眉r angezeigte Messungen festlegen</string>
+    <string name="insufficient_space_for_upload">Zu geringer Speicherplatz f眉r Upload</string>
+    <string name="cannot_upload_watchface_too_many_watchfaces_installed">Zu viele Zifferbl盲tter installiert. Upload nicht m枚glich</string>
+    <string name="miscale_small_objects_summary">Gewicht von Gegenst盲nden unter 10 kg speichern</string>
+    <string name="miscale_weight_unit_chinese">Chinesisch (jin)</string>
+    <string name="mijia_lywsd_comfort_level_summary">Stelle die Temperatur- und Luftfeuchtigkeitsgrenzen f眉r das angezeigte Emoji ein</string>
+    <string name="mijia_lywsd_comfort_lower">Untergrenze</string>
+    <string name="mijia_lywsd_comfort_upper">Obergrenze</string>
+    <string name="activity_type_laser_tag">Laser Tag</string>
+    <string name="activity_type_billiard_pool">Poolbillard</string>
+    <string name="activity_type_biathlon">Biathlon</string>
+    <string name="activity_type_bungee_jumping">Bungeejumping</string>
+    <string name="activity_type_wakeboarding">Wakeboarden</string>
+    <string name="activity_type_meditation">Meditation</string>
+    <string name="activity_type_lacrosse">Lacrosse</string>
+    <string name="activity_type_aerobics">Aerobic</string>
+    <string name="activity_type_american_football">American Football</string>
+    <string name="pref_continuous_skin_temperature_measurement_description">Temperaturabfrage derzeit nicht unterst眉tzt. Diese Einstellung erm枚glicht nur die kontinuierliche Messung auf dem Ger盲t</string>
+    <string name="paceCorrection">Korrektur</string>
+    <string name="miscale_weight_unit_imperial">Imperial (lbs)</string>
+    <string name="min_respiration_rate">Atemfrequenz min</string>
+    <string name="recoveryTime">Erholungszeit</string>
+    <string name="activity_type_frisbee">Frisbee</string>
+    <string name="activity_type_judo">Judo</string>
+    <string name="activity_type_table_football">Tischfu脽ball</string>
+    <string name="distance_avg">Strecke durchschnittlich</string>
+    <string name="distance_total">Strecke insgesamt</string>
+    <string name="estimatedSweatLoss">Gesch盲tzter Schwei脽verlust</string>
+    <string name="workout_set_reps">Wiederholungen</string>
+    <string name="activity_detail_share_json_details">JSON-Details teilen</string>
+    <string name="max_respiration_rate">Atemfrequenz max</string>
+    <string name="date_placeholders__date__time">%1s, %1s</string>
+    <string name="date_placeholders__start_time__end_time">%1s - %2s</string>
+    <string name="pref_title_calendar_lookahead">Anzahl der Tage voraus</string>
+    <string name="pref_summary_calendar_lookahead">Kalenderereignisse bis zu %1s Tagen synchronisieren</string>
+    <string name="activity_type_racquetball">Racquetball</string>
+    <string name="music_upload_info">Du bist gerade dabei vollende Musik Dateien hochzuladen:\n\n%1$s\nTitel: %2$s\nAlbum: %3$s\n</string>
+    <string name="activity_type_racket">Schl盲ger</string>
+    <string name="activity_type_tactical">Taktisch</string>
+    <string name="menuitem_stress_simple">Stress (einfach)</string>
+    <string name="inactivity_warnings_minimum_steps_title">Minimale Anzahl Schritte</string>
+    <string name="activity_db_management_backup_restore_label">Sichern und Wiederherstellen</string>
+    <string name="backup_restore_error_import">Import von zip fehlgeschlagen</string>
+    <string name="backup_restore_error_export">Export als zip fehlgeschlagen</string>
+    <string name="pref_voice_passthrough_enabled">Durchreichen</string>
+    <string name="menuitem_stress_breakdown">Stress (detailliert)</string>
+    <string name="file_already_exists">Die Datei existiert bereits</string>
+    <string name="backup_restore_exporting">Wird als zip exportiert鈥�</string>
+    <string name="backup_restore_exporting_preferences">Einstellungen werden exportiert鈥�</string>
+    <string name="backup_restore_exporting_files">Dateien werden exportiert鈥�</string>
+    <string name="backup_restore_exporting_database">Datenbank wird exportiert鈥�</string>
+    <string name="backup_restore_importing_database">Datenbank wird importiert鈥�</string>
+    <string name="backup_restore_importing_preferences">Einstellungen werden importiert鈥�</string>
+    <string name="backup_restore_do_not_exit">%s Bitte lasse diese Ansicht ge枚ffnet, bis der Vorgang abgeschlossen ist.</string>
+    <string name="pref_dnd_follow_phone_title">Bitte-nicht-st枚ren Einstellung des Smartphones 眉bernehmen</string>
+    <string name="activity_type_team_sport">Team-Sport</string>
+    <string name="activity_type_dragon_boat">Drachenboot</string>
+    <string name="pref_dashboard_widget_today_yesterday_data_title">Daten von gestern</string>
+    <string name="pref_dashboard_widget_today_time_indicator_title">Indikator f眉r den aktuellen Zeitpunkt</string>
+    <string name="app_crash_notification_title">%1s ist abgest眉rzt</string>
+    <string name="app_crash_share_stacktrace">Fehler beim Teilen</string>
+    <string name="backup_restore_importing_validating">Datei wird gepr眉ft鈥�</string>
+    <string name="contact_birthday">Geburtstag von %1s</string>
+    <string name="backup_restore_export_complete">Export abgeschlossen</string>
+    <string name="backup_restore_importing_loading">Datei wird geladen鈥�</string>
+    <string name="menuitem_stress_segmented">Stress (segmentiert)</string>
+    <string name="pref_deprecated_media_control_title">Veraltete Mediensteuerung</string>
+    <string name="activity_type_canoeing">Kanufahren</string>
+    <string name="backup_restore_exporting_files_i_of_n">%1d von %2d Dateien werden exportiert鈥�</string>
+    <string name="backup_restore_exporting_finishing">Export wird abgeschlossen鈥�</string>
+    <string name="backup_restore_import_complete">Import abgeschlossen</string>
+    <string name="backup_restore_abort_title">Abbrechen</string>
+    <string name="birthdays">Geburtstage</string>
+    <string name="pref_dashboard_widget_today_time_indicator_summary">Zeigt einen Indikator f眉r den aktuellen Zeitpunkt an, um die Daten von gestern und heute optisch zu trennen</string>
+    <string name="activity_type_grinding">Grinding</string>
+    <string name="menuitem_map">Karte</string>
+    <string name="menuitem_night_display">Nacht-Ansicht</string>
+    <string name="steps_total">Schritte Total</string>
+    <string name="number_selected_items">%1d ausgew盲hlt</string>
+    <string name="active_calories_short">Aktivit盲t</string>
+    <string name="active_calories">Aktivit盲tskalorien</string>
+    <string name="active_calories_goal">Aktivit盲tsziel</string>
+    <string name="activity_prefs_goal_active_calories_burnt">"Tagesziel:  Aktiver Verbrauch"</string>
+    <string name="activity_type_health_snapshot">Gesundheit aktuell</string>
+    <string name="total_calories_burnt">Insgesamt verbraucht</string>
+    <string name="title_activity_musicmanager">Musikmanager</string>
+    <string name="activity_db_management_export_to_zip">ZIP exportieren</string>
+    <string name="backup_restore_abort_import_confirmation">Import abbrechen? Das kann zu einer kaputten Datenbank f眉hren.</string>
+    <string name="backup_restore_restart_title">Neustart</string>
+    <string name="backup_restore_restart_summary">%1s startet jetzt neu.</string>
+    <string name="first_start_welcome_title">Willkommen</string>
+    <string name="first_start_intro_welcome_to">WIllkommen zu</string>
+    <string name="activity_db_management_import_from_zip">ZIP importieren</string>
+    <string name="activity_db_management_backup_restore_explanation">Das Sichern und Wiederherstellen erm枚glicht es, alle Gadgetbridge-Einstellungen, -Ger盲te und -Daten mittels ZIP-Datei zu migrieren oder zu sichern.\n\nBeim Wiederherstellen werden alle bestehenden Daten, Ger盲te und Einstellungen entfernt und vollst盲ndig durch die Sicherung ersetzt.</string>
+    <string name="backup_restore_abort_export_confirmation">Export abbrechen? Die unvollst盲ndige ZIP-Datei wird gel枚scht.</string>
+    <string name="totalJumps">Anzahl Spr眉nge</string>
+    <string name="jumps_minute">Spr眉nge/min</string>
+    <string name="jumps_unit">Spr眉nge</string>
+    <string name="Jumps">Spr眉nge</string>
+    <string name="avgJumpRate">Mittlere Sprungrate</string>
+    <string name="maxJumpRate">Max Sprungrate</string>
+    <string name="stepRateMax">Max Schrittfrequenz</string>
+    <string name="hrZoneEasy">Leicht</string>
+</resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml
new file mode 100644
index 0000000..252961d
--- /dev/null
+++ b/app/src/main/res/values-el/strings.xml
@@ -0,0 +1,1004 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <string name="application_name_generic">Gadgetbridge</string>
+    <string name="title_activity_controlcenter_generic">Gadgetbridge</string>
+    <string name="action_settings">巍蠀胃渭委蟽蔚喂蟼</string>
+    <string name="action_debug">螒蟺慰蟽蠁伪位渭维蟿蠅蟽畏</string>
+    <string name="action_quit">螆尉慰未慰蟼</string>
+    <string name="action_donate">螖蠅蟻蔚维</string>
+    <string name="controlcenter_fetch_activity_data">危蠀纬蠂蟻慰谓喂蟽渭蠈蟼</string>
+    <string name="controlcenter_find_device">螔蟻蔚委蟿蔚 蟿畏 蠂伪渭苇谓畏 蟽蠀蟽魏蔚蠀萎</string>
+    <string name="controlcenter_take_screenshot">危蟿喂纬渭喂蠈蟿蠀蟺慰 慰胃蠈谓畏蟼</string>
+    <string name="controlcenter_connect">危蠉谓未蔚蟽畏鈥�</string>
+    <string name="controlcenter_disconnect">螒蟺慰蟽蠉谓未蔚蟽畏</string>
+    <string name="controlcenter_delete_device">螖喂伪纬蟻伪蠁萎 蟽蠀蟽魏蔚蠀萎蟼</string>
+    <string name="controlcenter_delete_device_name">螖喂伪纬蟻伪蠁萎 %1$s</string>
+    <string name="controlcenter_delete_device_dialogmessage">螒蠀蟿蠈 胃伪 未喂伪纬蟻维蠄蔚喂 蟿畏谓 蟽蠀蟽魏蔚蠀萎 魏伪喂 蠈位伪 蟿伪 未蔚未慰渭苇谓伪 蟽蠂蔚蟿喂魏维 渭蔚 伪蠀蟿萎!</string>
+    <string name="controlcenter_snackbar_need_longpress">螤伪蟿萎蟽蟿蔚 蟽蠀谓蔚蠂蠈渭蔚谓伪 蟽蟿畏 魏伪蟻蟿苇位伪 蟿畏蟼 蟽蠀蟽魏蔚蠀萎蟼 纬喂伪 伪蟺慰蟽蠉谓未蔚蟽畏</string>
+    <string name="controlcenter_snackbar_disconnecting">螕委谓蔚蟿伪喂 伪蟺慰蟽蠉谓未蔚蟽畏</string>
+    <string name="controlcenter_snackbar_connecting">危蠀谓未苇蟽蟿蔚鈥�</string>
+    <string name="controlcenter_snackbar_requested_screenshot">螞萎蠄畏 蟽蟿喂纬渭喂蠈蟿蠀蟺慰蠀 伪蟺蠈 蟿畏 蟽蠀蟽魏蔚蠀萎</string>
+    <string name="title_activity_debug">螒蟺慰蟽蠁伪位渭维蟿蠅蟽畏</string>
+    <string name="controlcenter_navigation_drawer_open">螁谓慰喂纬渭伪 蟽蠀蟻蟿伪蟻喂慰蠉 蟺位慰萎纬畏蟽畏蟼</string>
+    <string name="controlcenter_navigation_drawer_close">螝位蔚委蟽喂渭慰 蟽蠀蟻蟿伪蟻喂慰蠉 蟺位慰萎纬畏蟽畏蟼</string>
+    <string name="appmanager_cached_watchapps_watchfaces">螘蠁伪蟻渭慰纬苇蟼 蟽蟿畏谓 蟺蟻慰蟽蠅蟻喂谓萎 渭谓萎渭畏</string>
+    <string name="appmanager_installed_watchapps">螘纬魏伪蟿蔚蟽蟿畏渭苇谓蔚蟼 蔚蠁伪蟻渭慰纬苇蟼</string>
+    <string name="appmanager_installed_watchfaces">螘纬魏伪蟿蔚蟽蟿畏渭苇谓伪 watchfaces</string>
+    <string name="appmananger_app_delete">螖喂伪纬蟻伪蠁萎</string>
+    <string name="appmananger_app_delete_cache">螖喂伪纬蟻伪蠁萎 魏伪喂 伪蟺慰渭维魏蟻蠀谓蟽畏 伪蟺蠈 蟿畏 蟺蟻慰蟽蠅蟻喂谓萎 渭谓萎渭畏</string>
+    <string name="appmananger_app_reinstall">螘蟺伪谓蔚纬魏伪蟿维蟽蟿伪蟽畏</string>
+    <string name="appmanager_app_openinstore">螒谓伪味萎蟿畏蟽畏 蟽蟿慰 魏伪蟿维蟽蟿畏渭伪 蔚蠁伪蟻渭慰纬蠋谓 蟿慰蠀 Pebble</string>
+    <string name="appmanager_health_activate">螘谓蔚蟻纬慰蟺慰委畏蟽畏</string>
+    <string name="appmanager_health_deactivate">螒蟺蔚谓蔚蟻纬慰蟺慰委畏蟽畏</string>
+    <string name="appmanager_hrm_activate">螘谓蔚蟻纬慰蟺慰委畏蟽畏 渭苇蟿蟻畏蟽畏蟼 蠀纬蔚委伪蟼</string>
+    <string name="appmanager_hrm_deactivate">螒蟺蔚谓蔚蟻纬慰蟺慰委畏蟽畏 渭苇蟿蟻畏蟽畏蟼 蠀纬蔚委伪蟼</string>
+    <string name="appmanager_weather_activate">螘谓蔚蟻纬慰蟺慰委畏蟽畏 螘蠁伪蟻渭慰纬萎蟼 螝伪喂蟻慰蠉</string>
+    <string name="appmanager_weather_deactivate">螒蟺蔚谓蔚蟻纬慰蟺慰委畏蟽畏 螘蠁伪蟻渭慰纬萎蟼 螝伪喂蟻慰蠉</string>
+    <string name="appmanager_weather_install_provider">螘纬魏伪蟿伪蟽蟿蔚委蟽蟿蔚 蟿畏谓 蔚蠁伪蟻渭慰纬萎 蔚喂未慰蟺慰委畏蟽畏蟼 螝伪喂蟻慰蠉</string>
+    <string name="app_configure">螘蟺喂位慰纬苇蟼</string>
+    <string name="app_move_to_top">螠蔚蟿伪魏委谓畏蟽畏 蟽蟿畏 魏慰蟻蠀蠁萎</string>
+    <string name="title_activity_calblacklist">螒蟺慰魏位蔚喂蟽渭苇谓伪 畏渭蔚蟻慰位蠈纬喂伪</string>
+    <string name="title_activity_fw_app_insaller">螘纬魏伪蟿伪蟽蟿维蟿畏蟼 蔚蠁伪蟻渭慰纬蠋谓/FW</string>
+    <string name="fw_upgrade_notice">螤蟻蠈魏蔚喂蟿伪喂 谓伪 蔚纬魏伪蟿伪蟽蟿萎蟽蔚蟿蔚 蟿畏谓 苇魏未慰蟽畏 位慰纬喂蟽渭喂魏慰蠉 %s.</string>
+    <string name="fw_upgrade_notice_amazfitbip">螤蟻蠈魏蔚喂蟿伪喂 谓伪 蔚纬魏伪蟿伪蟽蟿萎蟽蟿蔚 蟿慰 位慰纬喂蟽渭喂魏蠈 苇魏未慰蟽畏蟼 %s 蟽蟿慰 Amazfit Bip 蟽伪蟼.
+\n
+\n螤伪蟻伪魏伪位蠋 谓伪 蔚委蟽蟿蔚 蟽委纬慰蠀蟻慰喂 蠈蟿喂 胃伪 蔚纬魏伪蟿伪蟽蟿萎蟽蔚蟿蔚 蟿慰 伪蟻蠂蔚委慰 .fw, 渭蔚蟿维 蟿慰 伪蟻蠂蔚委慰 .res 魏伪喂 蟿苇位慰蟼 蟿慰 伪蟻蠂蔚委慰 .gps. 韦慰 smartwatch 胃伪 魏维谓蔚喂 蔚蟺伪谓蔚魏魏委谓畏蟽畏 渭蔚蟿维 蟿畏谓 蔚纬魏伪蟿维蟽蟿伪蟽畏 蟿慰蠀 .fw 伪蟻蠂蔚委慰蠀.
+\n
+\n危畏渭蔚委蠅蟽畏: 螖蔚谓 蠂蟻蔚喂维味蔚蟿伪喂 畏 蔚纬魏伪蟿维蟽蟿伪蟽畏 蟿蠅谓 伪蟻蠂蔚委蠅谓 .res 魏伪喂 .gps 伪谓 伪蠀蟿维 蔚委谓伪喂 伪魏蟻喂尾蠋蟼 蟿伪 委未喂伪 渭蔚 伪蠀蟿维 蟺慰蠀 苇蠂慰蠀谓 蔚纬魏伪蟿伪蟽蟿伪胃蔚委 蟺蟻慰畏纬慰蠀渭苇谓蠅蟼.
+\n
+\n螤巍螣围惟巍螚危韦螘 螠螘 螖螜螝螚 危螒危 螘违螛违螡螚!</string>
+    <string name="fw_upgrade_notice_amazfitcor">鈥斘犗佅屛何滴瓜勎蔽� 谓伪 蔚纬魏伪蟿伪蟽蟿萎蟽蔚蟿蔚 蟿慰 位慰纬喂蟽渭喂魏蠈 %s 纬喂伪 蟿慰 Amazfit Cor.
+\n
+\n螤伪蟻伪魏伪位蠋 尾蔚尾伪喂蠅胃蔚委蟿蔚 蠈蟿喂 胃伪 蔚纬魏伪蟿伪蟽蟿萎蟽蔚蟿蔚 蟿慰 伪蟻蠂蔚委慰 .fw 魏伪喂 渭蔚蟿维 蟿慰 伪蟻蠂蔚委慰 .res. 韦慰 smartwatch 胃伪 蔚蟺伪谓蔚魏魏喂谓萎蟽蔚喂 渭蔚蟿维 蟿畏谓 蔚纬魏伪蟿维蟽蟿伪蟽畏 蟿慰蠀 .fw 伪蟻蠂蔚委慰蠀.
+\n
+\n危畏渭蔚委蠅蟽畏: 韦慰 伪蟻蠂蔚委慰 .res 未蔚谓 蠂蟻蔚喂维味蔚蟿伪喂 蔚纬魏伪蟿维蟽蟿伪蟽畏 伪谓 蔚委谓伪喂 伪魏蟻喂尾蠋蟼 蟿慰 委未喂慰 渭蔚 伪蠀蟿蠈 蟺慰蠀 苇蠂蔚喂 蔚纬魏伪蟿伪蟽蟿伪胃蔚委 蟺蟻慰畏纬慰蠉渭蔚谓伪.
+\n
+\n螤巍螣围惟巍螚危韦螘 螠螘 螖螜螝螚 危螒危 螘违螛违螡螚!</string>
+    <string name="fw_multi_upgrade_notice">螤蟻蠈魏蔚喂蟿伪喂 谓伪 蔚纬魏伪蟿伪蟽蟿萎蟽蔚蟿蔚 蟿伪 位慰纬喂蟽渭喂魏维 %1$s 魏伪喂 %2$s 伪谓蟿\'伪蠀蟿蠋谓 蟺慰蠀 蠀蟺维蟻蠂慰蠀谓 蟽蟿慰 Mi Band 蟽伪蟼.</string>
+    <string name="miband_firmware_known">螒蠀蟿蠈 蟿慰 位慰纬喂蟽渭喂魏蠈 苇蠂蔚喂 未慰魏喂渭伪蟽蟿蔚委 魏伪喂 蔚委谓伪喂 蟽蠀渭尾伪蟿蠈 渭蔚 蟿慰 Gadgetbridge.</string>
+    <string name="miband_firmware_unknown_warning">螒蠀蟿蠈 蟿慰 位慰纬喂蟽渭喂魏蠈 未蔚谓 苇蠂蔚喂 未慰魏喂渭伪蟽蟿蔚委 魏伪喂 蔚谓未苇蠂蔚蟿伪喂 谓伪 渭畏谓 蔚委谓伪喂 蟽蠀渭尾伪蟿蠈 渭蔚 蟿慰 Gadgetbridge. 
+\n 
+\n螖螘螡 蟽蠀谓委蟽蟿伪蟿伪喂 畏 蔚纬魏伪蟿维蟽蟿伪蟽畏 蟿慰蠀!</string>
+    <string name="title_activity_appmanager">螖喂伪蠂蔚喂蟻喂蟽蟿萎蟼 蔚蠁伪蟻渭慰纬蠋谓</string>
+    <string name="miband_firmware_suggest_whitelist">螒谓 伪魏蠈渭畏 胃苇位蔚蟿蔚 谓伪 蟺蟻慰蠂蠅蟻萎蟽蔚蟿蔚 魏伪喂 渭蔚蟿维 蠈位伪 位蔚喂蟿慰蠀蟻纬慰蠉谓 魏伪谓慰谓喂魏维, 蟺伪蟻伪魏伪位慰蠉渭蔚 蔚谓畏渭蔚蟻蠋蟽蟿蔚 蟿慰蠀蟼 未畏渭喂慰蠀蟻纬慰蠉蟼 蟿慰蠀 Gadgetbridge 蟽蠂蔚蟿喂魏维 渭蔚 蟿畏谓 %s 苇魏未慰蟽畏.</string>
+    <string name="title_activity_settings">巍蠀胃渭委蟽蔚喂蟼</string>
+    <string name="pref_header_general">螕蔚谓喂魏苇蟼 蟻蠀胃渭委蟽蔚喂蟼</string>
+    <string name="pref_title_general_autoconnectonbluetooth">危蠉谓未蔚蟽畏 渭蔚 蟿畏谓 蠁慰蟻蔚蟿萎 蟽蠀蟽魏蔚蠀萎 蠈蟿伪谓 蟿慰 Bluetooth 蟽蟿慰 魏喂谓畏蟿蠈 蔚谓蔚蟻纬慰蟺慰喂蔚委蟿伪喂</string>
+    <string name="pref_title_general_autostartonboot">螒蠀蟿蠈渭伪蟿畏 苇谓伪蟻尉畏</string>
+    <string name="pref_title_general_autoreconnect">螒蠀蟿蠈渭伪蟿畏 蔚蟺伪谓伪蟽蠉谓未蔚蟽畏</string>
+    <string name="pref_title_audio_player">螤蟻慰蟿喂渭蠋渭蔚谓慰 蟺蟻蠈纬蟻伪渭渭伪 萎蠂慰蠀</string>
+    <string name="pref_default">螤蟻慰蔚蟺喂位慰纬萎</string>
+    <string name="pref_title_charts_swipe">危蠉蟻蔚蟿蔚 伪蟻喂蟽蟿蔚蟻维/未蔚尉喂维 纬喂伪 蟿伪 未喂伪纬蟻维渭渭伪蟿伪 蟿畏蟼 未蟻伪蟽蟿畏蟻喂蠈蟿畏蟿伪蟼</string>
+    <string name="pref_header_datetime">螚渭蔚蟻慰渭畏谓委伪 魏伪喂 蠋蟻伪</string>
+    <string name="pref_title_datetime_syctimeonconnect">危蠀纬蠂蟻慰谓喂蟽渭蠈蟼 蠋蟻伪蟼</string>
+    <string name="pref_summary_datetime_syctimeonconnect">危蠀纬蠂蟻慰谓喂蟽渭蠈蟼 蠋蟻伪蟼 蟽蟿畏 蠁慰蟻蔚蟿萎 蟽蠀蟽魏蔚蠀萎 蠈蟿伪谓 伪蠀蟿萎 蟽蠀谓未苇蔚蟿伪喂 渭蔚 蟿慰 魏喂谓畏蟿蠈 萎 蠈蟿伪谓 伪位位维味蔚蟿蔚 味蠋谓畏 蠋蟻伪蟼</string>
+    <string name="pref_title_theme">螛苇渭伪</string>
+    <string name="pref_theme_light">桅蠅蟿蔚喂谓蠈</string>
+    <string name="pref_theme_dark">危魏慰蟿蔚喂谓蠈</string>
+    <string name="pref_title_language">螕位蠋蟽蟽伪</string>
+    <string name="pref_title_minimize_priority">螒蟺蠈魏蟻蠀蠄畏 蟿慰蠀 蔚喂魏慰谓喂未委慰蠀 蟿畏蟼 蔚蠁伪蟻渭慰纬萎蟼 Gadgetbridge 伪蟺蠈 蟿畏 渭蟺维蟻伪 蔚喂未慰蟺慰喂萎蟽蔚蠅谓</string>
+    <string name="pref_summary_minimize_priority_off">韦慰 蔚喂魏慰谓委未喂慰 胃伪 蠁伪委谓蔚蟿伪喂 蟽蟿畏 纬蟻伪渭渭萎 魏伪蟿维蟽蟿伪蟽畏蟼 魏伪喂 慰喂 蔚喂未慰蟺慰喂萎蟽蔚喂蟼 蟽蟿畏谓 慰胃蠈谓畏 魏位蔚喂未蠋渭伪蟿慰蟼</string>
+    <string name="pref_summary_minimize_priority_on">螒蟺蠈魏蟻蠀蠄畏 蟿慰蠀 蔚喂魏慰谓委未喂慰蠀 伪蟺蠈 蟿畏 纬蟻伪渭渭萎 魏伪蟿维蟽蟿伪蟽畏蟼 魏伪喂 蟿蠅谓 蔚喂未慰蟺慰喂萎蟽蔚蠅谓 伪蟺蠈 蟿畏谓 慰胃蠈谓畏 魏位蔚喂未蠋渭伪蟿慰蟼</string>
+    <string name="pref_header_notifications">螘喂未慰蟺慰喂萎蟽蔚喂蟼</string>
+    <string name="pref_title_notifications_repetitions">螘蟺伪谓伪位萎蠄蔚喂蟼</string>
+    <string name="pref_title_notifications_call">韦畏位蔚蠁蠅谓喂魏苇蟼 魏位萎蟽蔚喂蟼</string>
+    <string name="pref_title_notifications_sms">SMS</string>
+    <string name="pref_title_notifications_pebblemsg">螠畏谓蠉渭伪蟿伪 Pebble</string>
+    <string name="pref_summary_notifications_pebblemsg">违蟺慰蟽蟿萎蟻喂尉畏 蟿蠅谓 蔚蠁伪蟻渭慰纬蠋谓 蟺慰蠀 蟽蟿苇位谓慰蠀谓 蔚喂未慰蟺慰喂萎蟽蔚喂蟼 蟽蟿慰 Pebble 渭苇蟽蠅 蟿慰蠀 PebbleKit.</string>
+    <string name="pref_title_notifications_generic">违蟺慰蟽蟿萎蟻喂尉畏 纬蔚谓喂魏蠋谓 蔚喂未慰蟺慰喂萎蟽蔚蠅谓</string>
+    <string name="pref_title_whenscreenon">鈥ξ迪�委蟽畏蟼 蠈蟿伪谓 蔚委谓伪喂 伪谓慰喂蠂蟿萎 畏 慰胃蠈谓畏</string>
+    <string name="pref_title_notification_filter">螠畏谓 螘谓慰蠂位蔚委蟿蔚</string>
+    <string name="pref_summary_notification_filter">危蔚 伪蠀蟿萎 蟿畏 位蔚喂蟿慰蠀蟻纬委伪 蟽蟿伪渭伪蟿慰蠉谓 慰喂 伪谓蔚蟺喂胃蠉渭畏蟿蔚蟼 蔚喂未慰蟺慰喂萎蟽蔚喂蟼</string>
+    <string name="pref_title_transliteration">螒位位伪纬萎 蠂伪蟻伪魏蟿萎蟻蠅谓</string>
+    <string name="pref_summary_transliteration">螘谓蔚蟻纬慰蟺慰喂萎蟽蟿蔚 伪蠀蟿萎 蟿畏谓 蔚蟺喂位慰纬萎 伪谓 畏 蟽蠀蟽魏蔚蠀萎 蟽伪蟼 未蔚谓 蠀蟺慰蟽蟿畏蟻委味蔚喂 蟿喂蟼 纬蟻伪渭渭伪蟿慰蟽蔚喂蟻苇蟼 蟿畏蟼 纬位蠋蟽蟽伪蟼 蟽伪蟼</string>
+    <string name="always">螤维谓蟿伪</string>
+    <string name="when_screen_off">螌蟿伪谓 畏 慰胃蠈谓畏 蔚委谓伪喂 蟽尾畏蟽蟿萎</string>
+    <string name="never">螤慰蟿苇</string>
+    <string name="pref_header_privacy">螜未喂蠅蟿喂魏蠈蟿畏蟿伪</string>
+    <string name="pref_title_call_privacy_mode">螞蔚喂蟿慰蠀蟻纬委伪 喂未喂蠅蟿喂魏蠈蟿畏蟿伪蟼 蟽蟿畏 未喂维蟻魏蔚喂伪 蟿蠅谓 魏位萎蟽蔚蠅谓</string>
+    <string name="pref_call_privacy_mode_off">螡伪 蠁伪委谓蔚蟿伪喂 蠈谓慰渭伪 魏喂 伪蟻喂胃渭蠈蟼</string>
+    <string name="pref_call_privacy_mode_name">螡伪 蠁伪委谓蔚蟿伪喂 渭蠈谓慰 慰 伪蟻喂胃渭蠈蟼</string>
+    <string name="pref_call_privacy_mode_number">螡伪 蠁伪委谓蔚蟿伪喂 渭蠈谓慰 蟿慰 蠈谓慰渭伪</string>
+    <string name="pref_call_privacy_mode_complete">螒蟺蠈魏蟻蠀蠄畏 慰谓蠈渭伪蟿慰蟼 魏伪喂 伪蟻喂胃渭慰蠉</string>
+    <string name="pref_blacklist_calendars">螒蟺慰魏位蔚喂蟽渭苇谓伪 畏渭蔚蟻慰位蠈纬喂伪</string>
+    <string name="pref_header_cannned_messages">螤蟻慰魏伪胃慰蟻喂蟽渭苇谓伪 渭畏谓蠉渭伪蟿伪</string>
+    <string name="pref_title_canned_replies">螒蟺伪谓蟿萎蟽蔚喂蟼</string>
+    <string name="pref_title_canned_reply_suffix">螝慰喂谓萎 魏伪蟿维位畏尉畏</string>
+    <string name="pref_title_canned_messages_dismisscall">螒蟺蠈蟻蟻喂蠄畏 魏位萎蟽畏蟼</string>
+    <string name="pref_title_canned_messages_set">螒谓伪尾维胃渭喂蟽畏 蟽蟿慰 Pebble</string>
+    <string name="pref_header_development">螘蟺喂位慰纬苇蟼 蟺蟻慰纬蟻伪渭渭伪蟿喂蟽蟿萎</string>
+    <string name="pref_title_development_miaddr">螖喂蔚蠉胃蠀谓蟽畏 Bluetooth 蟿慰蠀 Mi Band</string>
+    <string name="pref_title_pebble_settings">巍蠀胃渭委蟽蔚喂蟼 纬喂伪 Pebble</string>
+    <string name="pref_header_activitytrackers">螒谓喂蠂谓蔚蠀蟿苇蟼 未蟻伪蟽蟿畏蟻喂蠈蟿畏蟿伪蟼</string>
+    <string name="pref_title_pebble_activitytracker">螤蟻慰蟿喂渭蠋渭蔚谓慰喂 伪谓喂蠂谓蔚蠀蟿苇蟼 未蟻伪蟽蟿畏蟻喂蠈蟿畏蟿伪蟼</string>
+    <string name="pref_title_pebble_sync_health">危蠀纬蠂蟻慰谓喂蟽渭蠈蟼 Pebble Heath</string>
+    <string name="pref_title_pebble_sync_misfit">危蠀纬蠂蟻慰谓喂蟽渭蠈蟼 Misfit</string>
+    <string name="pref_title_pebble_sync_morpheuz">危蠀纬蠂蟻慰谓喂蟽渭蠈蟼 Morpheuz</string>
+    <string name="pref_title_enable_outgoing_call">违蟺慰蟽蟿萎蟻喂尉畏 蔚尉蔚蟻蠂慰渭苇谓蠅谓 魏位萎蟽蔚蠅谓</string>
+    <string name="pref_summary_enable_outgoing_call">螒蟺蔚谓蔚蟻纬慰蟺慰喂蠋谓蟿伪蟼 伪蠀蟿蠈 胃伪 伪蟺蔚谓蔚蟻纬慰蟺慰喂畏胃蔚委 魏伪喂 畏 未蠈谓畏蟽畏 蟽蟿慰 Pebble2/LE 蟽蟿喂蟼 蔚尉蔚蟻蠂蠈渭蔚谓蔚蟼 魏位萎蟽蔚喂蟼</string>
+    <string name="pref_title_enable_pebblekit">螡伪 蔚蟺喂蟿蟻伪蟺蔚委 畏 蟺蟻蠈蟽尾伪蟽畏 蟽蔚 蔚蠁伪蟻渭慰纬苇蟼 蟿蟻委蟿蠅谓</string>
+    <string name="pref_summary_enable_pebblekit">螡伪 蔚蟺喂蟿蟻伪蟺蔚委 畏 未慰魏喂渭伪蟽蟿喂魏萎 蠀蟺慰蟽蟿萎蟻喂尉畏 纬喂伪 蟿喂蟼 蔚蠁伪蟻渭慰纬苇蟼 Android 蠂蟻畏蟽喂渭慰蟺慰喂蠋谓蟿伪蟼 蟿慰 PebbleKit</string>
+    <string name="pref_header_pebble_timeline">围蟻慰谓慰位蠈纬喂慰 Pebble</string>
+    <string name="pref_title_sunrise_sunset">螒谓伪蟿慰位萎 魏伪喂 螖蠉蟽畏 蟿慰蠀 萎位喂慰蠀</string>
+    <string name="pref_summary_sunrise_sunset">螒蟺慰蟽蟿慰位萎 蟿畏蟼 蠋蟻伪蟼 伪谓伪蟿慰位萎蟼 魏伪喂 未蠉蟽畏蟼, 尾伪蟽喂蟽渭苇谓畏 蟽蟿畏 蟿慰蟺慰胃蔚蟽委伪, 蟽蟿慰 蠂蟻慰谓慰位蠈纬喂慰 Pebble</string>
+    <string name="pref_title_enable_calendar_sync">危蠀纬蠂蟻慰谓喂蟽渭蠈蟼 畏渭蔚蟻慰位慰纬委慰蠀</string>
+    <string name="pref_summary_enable_calendar_sync">螒蟺慰蟽蟿慰位萎 蟿蠅谓 纬蔚纬慰谓蠈蟿蠅谓 蟿慰蠀 畏渭蔚蟻慰位慰纬委慰蠀 蟽蟿慰 蠂蟻慰谓慰位蠈纬喂慰</string>
+    <string name="pref_title_autoremove_notifications">螒蠀蟿蠈渭伪蟿畏 伪蠁伪委蟻蔚蟽畏 蔚喂未慰蟺慰喂萎蟽蔚蠅谓 蟺慰蠀 苇蠂慰蠀谓 伪蟺慰蟻蟻喂蠁胃蔚委</string>
+    <string name="pref_summary_autoremove_notifications">螣喂 蔚喂未慰蟺慰喂萎蟽蔚喂蟼 谓伪 伪蠁伪喂蟻慰蠉谓蟿伪喂 伪蟺蠈 蟿慰 Pebble 伪蠀蟿蠈渭伪蟿伪, 蠈蟿伪谓 伪蟺慰蟻蟻委蟺蟿慰谓蟿伪喂 蟽蟿慰 蟿畏位苇蠁蠅谓慰</string>
+    <string name="pref_title_pebble_privacy_mode">螞蔚喂蟿慰蠀蟻纬委伪 喂未喂蠅蟿喂魏蠈蟿畏蟿伪蟼</string>
+    <string name="pref_pebble_privacy_mode_off">螘喂未慰蟺慰喂萎蟽蔚喂蟼</string>
+    <string name="pref_pebble_privacy_mode_content">螠蔚蟿伪蟿蠈蟺喂蟽畏 蟿畏蟼 蔚喂未慰蟺慰委畏蟽畏蟼 蔚魏蟿蠈蟼 慰胃蠈谓畏蟼</string>
+    <string name="pref_pebble_privacy_mode_complete">螡伪 蠁伪委谓蔚蟿伪喂 渭蠈谓慰 蟿慰 蔚喂魏慰谓委未喂慰 蟿畏蟼 蔚喂未慰蟺慰委畏蟽畏蟼</string>
+    <string name="pref_header_location">韦慰蟺慰胃蔚蟽委伪</string>
+    <string name="pref_title_location_aquire">螒谓维魏蟿畏蟽畏 蟿慰蟺慰胃蔚蟽委伪蟼</string>
+    <string name="pref_title_location_latitude">螕蔚蠅纬蟻伪蠁喂魏蠈 蟺位维蟿慰蟼</string>
+    <string name="pref_title_location_longitude">螕蔚蠅纬蟻伪蠁喂魏蠈 渭萎魏慰蟼</string>
+    <string name="pref_title_location_keep_uptodate">螡伪 魏蟻伪蟿蔚委蟿蔚 畏 蟿慰蟺慰胃蔚蟽委伪 蔚谓畏渭蔚蟻蠅渭苇谓畏</string>
+    <string name="pref_summary_location_keep_uptodate">螤蟻慰蟽蟺维胃蔚喂伪 蠂蟻萎蟽畏蟼 蟿畏蟼 蟺伪蟻慰蠉蟽伪蟼 蟿慰蟺慰胃蔚蟽委伪蟼 魏伪蟿维 蟿慰谓 蠂蟻蠈谓慰 蔚魏蟿苇位蔚蟽畏蟼, 蔚谓伪位位伪魏蟿喂魏维 蠂蟻萎蟽畏 蟿畏蟼 伪蟺慰胃畏魏蔚蠀渭苇谓畏蟼 蟿慰蟺慰胃蔚蟽委伪蟼</string>
+    <string name="toast_enable_networklocationprovider">螤伪蟻伪魏伪位蠋 蔚谓蔚蟻纬慰蟺慰喂萎蟽蟿蔚 蟿喂蟼 蠀蟺畏蟻蔚蟽委蔚蟼 蟿慰蟺慰胃蔚蟽委伪蟼</string>
+    <string name="toast_aqurired_networklocation">螚 蟿慰蟺慰胃蔚蟽委伪 尾蟻苇胃畏魏蔚</string>
+    <string name="pref_title_pebble_forceprotocol">螤蟻蠅蟿蠈魏慰位位慰 蠀蟺慰蠂蟻蔚蠅蟿喂魏蠋谓 蔚喂未慰蟺慰喂萎蟽蔚蠅谓</string>
+    <string name="pref_summary_pebble_forceprotocol">螒蠀蟿萎 畏 蔚蟺喂位慰纬萎 蠀蟺慰蠂蟻蔚蠋谓蔚喂 蟿畏谓 蠂蟻萎蟽畏 蟿慰蠀 蟺喂慰 蟺蟻蠈蟽蠁伪蟿慰蠀 蟺蟻蠅蟿慰魏蠈位位慰蠀 蔚喂未慰蟺慰喂萎蟽蔚蠅谓 伪谓维位慰纬伪 渭蔚 蟿畏谓 苇魏未慰蟽畏 蟿慰蠀 位慰纬喂蟽渭喂魏慰蠉. 螘螡螘巍螕螣螤螣螜螚危韦螘 螠螣螡螣 螒螡 螢螘巍螘韦螘 韦螜 螝螒螡螘韦螘!</string>
+    <string name="pref_title_pebble_forceuntested">螡伪 蔚蟺喂蟿蟻苇蟺慰谓蟿伪喂 蟿伪 渭畏 未慰魏喂渭伪蟽渭苇谓伪 蠂伪蟻伪魏蟿畏蟻喂蟽蟿喂魏维</string>
+    <string name="pref_summary_pebble_forceuntested">围蟻萎蟽畏 渭畏 未慰魏喂渭伪蟽渭苇谓蠅谓 蠂伪蟻伪魏蟿畏蟻喂蟽蟿喂魏蠋谓. 螘螡螘巍螕螣螤螣螜螚危韦螘 螠螣螡螣 螒螡 螢螘巍螘韦螘 韦螜 螝螒螡螘韦螘!</string>
+    <string name="pref_title_pebble_forcele">螤维谓蟿伪 谓伪 蟺蟻慰蟿喂渭维蟿伪喂 蟿慰 Bluetooth 蠂伪渭畏位萎蟼 蔚谓苇蟻纬蔚喂伪蟼 (BLE)</string>
+    <string name="pref_summary_pebble_forcele">围蟻萎蟽畏 蟿畏蟼 未慰魏喂渭伪蟽蟿喂魏萎蟼 蠀蟺慰蟽蟿萎蟻喂尉畏蟼 蟿慰蠀 Pebble LE 纬喂伪 蠈位伪 蟿伪 Pebble 伪谓蟿委 蟿畏蟼 魏位伪蟽喂魏萎蟼 蠀蟺慰蟽蟿萎蟻喂尉畏蟼 BT. 螒蠀蟿蠈 伪蟺伪喂蟿蔚委 蟿畏 蟽蠉味蔚蠀尉畏 渭畏 LE 蟺蟻蠋蟿伪 魏伪喂 渭蔚蟿维 蟽蠉谓未蔚蟽畏 Pebble LE</string>
+    <string name="pref_title_pebble_mtu_limit">螌蟻喂慰 Pebble 2/LE GATT MTU</string>
+    <string name="pref_summary_pebble_mtu_limit">螒谓 蟿慰 Pebble 2/Pebble LE 未蔚谓 位蔚喂蟿慰蠀蟻纬蔚委 伪谓伪渭蔚谓蠈渭蔚谓伪, 蟺蟻慰蟽蟺伪胃萎蟽蟿蔚 谓伪 尾维位蔚蟿蔚 蠈蟻喂慰 蟽蟿慰 MTU (伪蟺蠈 20 苇蠅蟼 512)</string>
+    <string name="pref_title_pebble_enable_applogs">螡伪 蔚蟺喂蟿蟻苇蟺蔚蟿伪喂 畏 魏伪蟿伪纬蟻伪蠁萎 未蔚未慰渭苇谓蠅谓</string>
+    <string name="pref_summary_pebble_enable_applogs">螛伪 伪谓伪纬魏维蟽蔚喂 蟿喂蟼 魏伪蟿伪纬蟻伪蠁苇蟼 伪蟺蠈 蟿喂蟼 蔚蠁伪蟻渭慰纬苇蟼 蟿慰蠀 smartwatch 谓伪 魏伪蟿伪纬蟻维蠁慰谓蟿伪喂 伪蟺蠈 蟿慰 Gadgetbridge (伪蟺伪喂蟿蔚委 蔚蟺伪谓伪蟽蠉谓未蔚蟽畏)</string>
+    <string name="pref_title_pebble_always_ack_pebblekit">螤蟻蠈蠅蟻慰 ACK PebbleKit</string>
+    <string name="pref_summary_pebble_always_ack_pebblekit">螛伪 伪谓伪纬魏维蟽蔚喂 蟿伪 渭畏谓蠉渭伪蟿伪 蟺慰蠀 蟽蟿苇位谓慰谓蟿伪喂 伪蟺蠈 蔚蠁伪蟻渭慰纬苇蟼 蟿蟻委蟿蠅谓 谓伪 纬谓蠅蟽蟿慰蟺慰喂慰蠉谓蟿伪喂 蟺维谓蟿伪 魏伪喂 维渭蔚蟽伪</string>
+    <string name="pref_title_pebble_enable_bgjs">围蟻萎蟽畏 JS 蟽蟿慰 蟺伪蟻伪蟽魏萎谓喂慰</string>
+    <string name="pref_summary_pebble_enable_bgjs">螒谓 蔚蟺喂位蔚蠂胃蔚委, 胃伪 蔚蟺喂蟿蟻苇蟺蔚喂 蟽蟿伪 watchfaces 谓伪 未蔚委蠂谓慰蠀谓 蟿慰 魏伪喂蟻蠈, 蟿畏谓 渭蟺伪蟿伪蟻委伪 魏位蟺.</string>
+    <string name="pref_title_pebble_reconnect_attempts">螤蟻慰蟽蟺维胃蔚喂蔚蟼 蔚蟺伪谓伪蟽蠉谓未蔚蟽畏蟼</string>
+    <string name="pref_title_unit_system">螠慰谓维未蔚蟼</string>
+    <string name="pref_title_timeformat">螠慰蟻蠁萎 蠋蟻伪蟼</string>
+    <string name="pref_title_screentime">螖喂维蟻魏蔚喂伪 伪谓维渭渭伪蟿慰蟼 慰胃蠈谓畏蟼</string>
+    <string name="prefs_title_all_day_heart_rate">螠苇蟿蟻畏蟽畏 魏伪蟻未喂伪魏慰蠉 蟽蠁蠀纬渭慰蠉 蠈位畏 蟿畏 渭苇蟻伪</string>
+    <string name="preferences_hplus_settings">巍蠀胃渭委蟽蔚喂蟼 纬喂伪 HPlus/Makibes</string>
+    <string name="not_connected">螠畏 蟽蠀谓未蔚未蔚渭苇谓慰</string>
+    <string name="connecting">螕委谓蔚蟿伪喂 蟽蠉谓未蔚蟽畏</string>
+    <string name="connected">危蠀谓未蔚未蔚渭苇谓慰</string>
+    <string name="unknown_state">螁纬谓蠅蟽蟿畏 魏伪蟿维蟽蟿伪蟽畏</string>
+    <string name="_unknown_">(维纬谓蠅蟽蟿慰)</string>
+    <string name="test">韦蔚蟽蟿</string>
+    <string name="test_notification">螖慰魏喂渭伪蟽蟿喂魏萎 蔚喂未慰蟺慰委畏蟽畏</string>
+    <string name="this_is_a_test_notification_from_gadgetbridge">螒蠀蟿萎 蔚委谓伪喂 渭委伪 未慰魏喂渭伪蟽蟿喂魏萎 蔚喂未慰蟺慰委畏蟽畏 伪蟺蠈 蟿慰 Gadgetbridge</string>
+    <string name="bluetooth_is_not_supported_">韦慰 Bluetooth 未蔚谓 蠀蟺慰蟽蟿畏蟻委味蔚蟿伪喂.</string>
+    <string name="bluetooth_is_disabled_">韦慰 Bluetooth 蔚委谓伪喂 伪蟺蔚谓蔚蟻纬慰蟺慰喂畏渭苇谓慰.</string>
+    <string name="tap_connected_device_for_app_mananger">螤伪蟿萎蟽蟿蔚 蟿畏 蟽蠀谓未蔚未蔚渭苇谓畏 蟽蠀蟽魏蔚蠀萎 纬喂伪 蟿慰谓 未喂伪蠂蔚喂蟻喂蟽蟿萎 蔚蠁伪蟻渭慰纬蠋谓</string>
+    <string name="tap_connected_device_for_activity">螤伪蟿萎蟽蟿蔚 蟿畏 蟽蠀谓未蔚未蔚渭苇谓畏 蟽蠀蟽魏蔚蠀萎 纬喂伪 未蟻伪蟽蟿畏蟻喂蠈蟿畏蟿伪</string>
+    <string name="tap_connected_device_for_vibration">螤伪蟿萎蟽蟿蔚 蟿畏 蟽蠀谓未蔚未蔚渭苇谓畏 蟽蠀蟽魏蔚蠀萎 纬喂伪 未蠈谓畏蟽畏</string>
+    <string name="tap_a_device_to_connect">螤伪蟿萎蟽蟿蔚 渭委伪 蟽蠀蟽魏蔚蠀萎 纬喂伪 谓伪 蟽蠀谓未蔚胃蔚委</string>
+    <string name="cannot_connect_bt_address_invalid_">螖蔚谓 渭蟺慰蟻蔚委 谓伪 纬委谓蔚喂 蟽蠉谓未蔚蟽畏. 螠萎蟺蠅蟼 未蔚谓 蔚委谓伪喂 苇纬魏蠀蟻畏 畏 未喂蔚蠉胃蠀谓蟽畏 Bluetooth;</string>
+    <string name="gadgetbridge_running_generic">韦慰 Gadgetbridge 蔚魏蟿蔚位蔚委蟿伪喂</string>
+    <string name="installing_binary_d_d">螕委谓蔚蟿伪喂 蔚纬魏伪蟿维蟽蟿伪蟽畏 蟿蠅谓 尾喂尾位喂慰胃畏魏蠋谓 %1$d/%2$d</string>
+    <string name="installation_failed_">螚 蔚纬魏伪蟿维蟽蟿伪蟽畏 伪蟺苇蟿蠀蠂蔚</string>
+    <string name="installation_successful">螘纬魏伪蟿蔚蟽蟿畏渭苇谓慰</string>
+    <string name="firmware_install_warning">螤巍螣危螤螒螛螘螜韦螘 螡螒 螘螕螝螒韦螒危韦螚危螘韦螘 螞螣螕螜危螠螜螝螣, 螤巍螣围惟巍螚危韦螘 螠螘 螖螜螝螚 危螒危 螘违螛违螡螚. 
+\n 
+\n 
+\n螒蠀蟿蠈 蟿慰 位慰纬喂蟽渭喂魏蠈 蔚委谓伪喂 纬喂伪 蟿畏谓 苇魏未慰蟽畏 蠀位喂魏慰蠉: %s</string>
+    <string name="app_install_info">螛伪 蔚纬魏伪蟿伪蟽蟿萎蟽蔚蟿蔚 蟿畏谓 伪魏蠈位慰蠀胃畏 蔚蠁伪蟻渭慰纬萎:
+\n
+\n
+\n%1$s 苇魏未慰蟽畏 %2$s 伪蟺蠈 %3$s
+\n</string>
+    <string name="n_a">螠畏 未喂伪胃苇蟽喂渭慰</string>
+    <string name="initialized">螒蟻蠂喂魏慰蟺慰委畏蟽畏</string>
+    <string name="appversion_by_creator">%1$s 伪蟺蠈 %2$s</string>
+    <string name="title_activity_discovery">螘蠉蟻蔚蟽畏 蟽蠀蟽魏蔚蠀萎蟼</string>
+    <string name="discovery_stop_scanning">危蟿伪渭维蟿畏渭伪 蟽维蟻蠅蟽畏蟼</string>
+    <string name="discovery_start_scanning">螆谓伪蟻尉畏 伪谓蔚蠉蟻蔚蟽畏蟼</string>
+    <string name="action_discover">危蠉谓未蔚蟽畏 谓苇伪蟼 蟽蠀蟽魏蔚蠀萎蟼</string>
+    <string name="device_with_rssi">%1$s (%2$s)</string>
+    <string name="title_activity_android_pairing">危蠉味蔚蠀尉畏 蟽蠀蟽魏蔚蠀萎蟼</string>
+    <string name="android_pairing_hint">围蟻萎蟽畏 蟿慰蠀 未喂伪位蠈纬慰蠀 蟽蠉味蔚蠀尉畏蟼 Bluetooth 蟿慰蠀 Android 纬喂伪 蟽蠉味蔚蠀尉畏 渭蔚 蟿畏 蟽蠀蟽魏蔚蠀萎.</string>
+    <string name="title_activity_mi_band_pairing">危蠉味蔚蠀尉畏 渭蔚 蟿慰 Mi Band</string>
+    <string name="pairing">螕委谓蔚蟿伪喂 蟽蠉味蔚蠀尉畏 渭蔚 蟿慰 %s鈥�</string>
+    <string name="pairing_creating_bond_with">螖畏渭喂慰蠀蟻纬委伪 未蔚蟽渭慰蠉 渭蔚 蟿慰 %1$s (%2$s)</string>
+    <string name="pairing_unable_to_pair_with">螖蔚谓 渭蟺慰蟻蔚委 谓伪 纬委谓蔚喂 蟽蠉味蔚蠀尉畏 渭蔚 蟿慰 %1$s (%2$s)</string>
+    <string name="pairing_in_progress">螖畏渭喂慰蠀蟻纬委伪 未蔚蟽渭慰蠉: %1$s (%2$s)</string>
+    <string name="pairing_already_bonded">螆蠂蔚喂 萎未畏 纬委谓蔚喂 未蔚蟽渭蠈蟼 渭蔚 %1$s (%2$s), 蟽蠉谓未蔚蟽畏鈥�</string>
+    <string name="message_cannot_pair_no_mac">螖蔚谓 蟺苇蟻伪蟽蔚 魏伪渭委伪 未喂蔚蠉胃蠀谓蟽畏 MAC, 未蔚谓 蔚委谓伪喂 未蠀谓伪蟿萎 畏 蟽蠉味蔚蠀尉畏.</string>
+    <string name="preferences_category_device_specific_settings">巍蠀胃渭委蟽蔚喂蟼 伪谓维位慰纬伪 渭蔚 蟿慰谓 蟿蠉蟺慰 蟽蠀蟽魏蔚蠀萎蟼</string>
+    <string name="preferences_miband_settings">巍蠀胃渭委蟽蔚喂蟼 纬喂伪 Mi Band / Amazfit</string>
+    <string name="male">螁谓未蟻伪蟼</string>
+    <string name="female">螕蠀谓伪委魏伪</string>
+    <string name="other">螁位位慰</string>
+    <string name="left">螒蟻喂蟽蟿蔚蟻蠈</string>
+    <string name="right">螖蔚尉委</string>
+    <string name="miband_pairing_using_dummy_userdata">螖蔚谓 未蠈胃畏魏伪谓 伪蟻魏蔚蟿维 未蔚未慰渭苇谓伪 纬喂伪 蟿慰谓 蠂蟻萎蟽蟿畏, 胃伪 蠂蟻畏蟽喂渭慰蟺慰喂畏胃慰蠉谓 蟺蟻慰魏伪胃慰蟻喂蟽渭苇谓伪 未蔚未慰渭苇谓伪 纬喂伪 蟿畏谓 蠋蟻伪.</string>
+    <string name="miband_pairing_tap_hint">螌蟿伪谓 蟿慰 Mi Band 蟽伪蟼 未慰谓蔚委蟿伪喂 魏伪喂 伪谓伪尾慰蟽尾萎谓蔚喂, 蟺喂苇蟽蟿蔚 蔚位伪蠁蟻维 蟿慰 魏慰蠀渭蟺委 位委纬蔚蟼 蠁慰蟻苇蟼 蟽蠀谓蔚蠂蠈渭蔚谓伪.</string>
+    <string name="appinstaller_install">螘纬魏伪蟿维蟽蟿伪蟽畏</string>
+    <string name="discovery_connected_devices_hint">螘谓蔚蟻纬慰蟺慰喂萎蟽蟿蔚 蟿畏谓 蔚蟺喂位慰纬萎 \"蔚谓蟿慰蟺喂蟽渭慰蠉 bluetooth\". 螣喂 蟽蠀谓未蔚未蔚渭苇谓蔚蟼 蟽蠀蟽魏蔚蠀苇蟼 蔚委谓伪喂 蟺喂胃伪谓蠈谓 谓伪 渭畏谓 蔚谓蟿慰蟺喂蟽蟿慰蠉谓. 危蔚 蔚魏未蠈蟽蔚喂蟼 蟿慰蠀 Android 伪蟺蠈 蟿慰 6 魏伪喂 谓蔚蠈蟿蔚蟻蔚蟼 蔚谓蔚蟻纬慰蟺慰喂萎蟽蟿蔚 蟿喂蟼 蠀蟺畏蟻蔚蟽委蔚蟼 蟿慰蟺慰胃蔚蟽委伪蟼 蟿慰蠀 魏喂谓畏蟿慰蠉 (蟺.蠂. GPS). 螒蟺蔚谓蔚蟻纬慰蟺慰喂萎蟽蟿蔚 蟿慰 Gadgetbridge 伪蟺蠈 蟿慰 Privacy Guard, 蔚蟺蔚喂未萎 渭蟺慰蟻蔚委 谓伪 魏维谓蔚喂 蟿慰 魏喂谓畏蟿蠈 蟽伪蟼 蟿蠀蠂伪委蔚蟼 蔚蟺伪谓蔚魏魏喂谓萎蟽蔚喂蟼. 螒谓, 渭蔚蟿维 伪蟺蠈 位委纬伪 位蔚蟺蟿维, 未蔚谓 尾蟻蔚胃蔚委 魏伪渭委伪 蟽蠀蟽魏蔚蠀萎, 尉伪谓伪未慰魏喂渭维蟽蟿蔚 伪蠁慰蠉 蟺蟻蠋蟿伪 蔚蟺伪谓蔚魏魏喂谓萎蟽蔚蟿蔚 蟿慰 蟿畏位苇蠁蠅谓蠈 蟽伪蟼.</string>
+    <string name="discovery_note">危畏渭蔚委蠅蟽畏:</string>
+    <string name="candidate_item_device_image">螘喂魏蠈谓伪 蟿畏蟼 蟽蠀蟽魏蔚蠀萎蟼</string>
+    <string name="miband_prefs_alias">螌谓慰渭伪/唯蔚蠀未蠋谓蠀渭慰</string>
+    <string name="pref_header_vibration_count">螒蟻喂胃渭蠈蟼 未慰谓萎蟽蔚蠅谓</string>
+    <string name="title_activity_sleepmonitor">螝伪蟿伪纬蟻伪蠁萎 蠉蟺谓慰蠀</string>
+    <string name="pref_write_logfiles">螘谓蔚蟻纬慰蟺慰委畏蟽畏 伪蟻蠂蔚委蠅谓 魏伪蟿伪纬蟻伪蠁萎蟼</string>
+    <string name="initializing">螒蟻蠂喂魏慰蟺慰委畏蟽畏</string>
+    <string name="busy_task_fetch_activity_data">螞萎蠄畏 未蔚未慰渭苇谓蠅谓 未蟻伪蟽蟿畏蟻喂蠈蟿畏蟿伪蟼</string>
+    <string name="sleep_activity_date_range">螒蟺蠈 %1$s 苇蠅蟼 %2$s</string>
+    <string name="prefs_wearside">危蔚 蟺慰喂慰 蠂苇蟻喂 蟿慰 蠁慰蟻维蟿蔚;</string>
+    <string name="pref_screen_vibration_profile">螤蟻慰蠁委位 未蠈谓畏蟽畏蟼</string>
+    <string name="vibration_profile_staccato">螝慰蠁蟿萎</string>
+    <string name="vibration_profile_short">危蠉谓蟿慰渭畏</string>
+    <string name="vibration_profile_medium">螠苇蟿蟻喂伪</string>
+    <string name="vibration_profile_long">螠伪魏蟻蠈蟽蠀蟻蟿畏</string>
+    <string name="vibration_profile_waterdrop">危蟿伪纬蠈谓伪</string>
+    <string name="vibration_profile_ring">螝慰蠀未慰蠉谓喂蟽渭伪</string>
+    <string name="vibration_profile_alarm_clock">螢蠀蟺谓畏蟿萎蟻喂</string>
+    <string name="miband_prefs_vibration">螖蠈谓畏蟽畏</string>
+    <string name="vibration_try">螖慰魏喂渭萎</string>
+    <string name="pref_screen_notification_profile_sms">螘喂未慰蟺慰喂萎蟽蔚喂蟼 SMS</string>
+    <string name="pref_header_vibration_settings">巍蠀胃渭委蟽蔚喂蟼 未蠈谓畏蟽畏蟼</string>
+    <string name="pref_screen_notification_profile_generic">螕蔚谓喂魏苇蟼 蔚喂未慰蟺慰喂萎蟽蔚喂蟼</string>
+    <string name="pref_screen_notification_profile_email">螘喂未慰蟺慰喂萎蟽蔚喂蟼 email</string>
+    <string name="pref_title_weather_location">韦慰蟺慰胃蔚蟽委伪 魏伪喂蟻慰蠉 (纬喂伪 蟿畏谓 蠀蟺畏蟻蔚蟽委伪 魏伪喂蟻慰蠉 蟿慰蠀 LineageOS)</string>
+    <string name="pref_screen_notification_profile_incoming_call">螘喂未慰蟺慰喂萎蟽蔚喂蟼 蔚喂蟽蔚蟻蠂慰渭苇谓蠅谓 魏位萎蟽蔚蠅谓</string>
+    <string name="pref_screen_notification_profile_generic_chat">危蠀谓慰渭喂位委伪</string>
+    <string name="pref_screen_notification_profile_generic_navigation">螤位慰萎纬畏蟽畏</string>
+    <string name="pref_screen_notification_profile_generic_social">螝慰喂谓蠅谓喂魏维 未委魏蟿蠀伪</string>
+    <string name="prefs_title_heartrate_measurement_interval">螠苇蟿蟻畏蟽畏 蠀纬蔚委伪蟼 蠈位畏 蟿畏 渭苇蟻伪</string>
+    <string name="interval_one_minute">魏维胃蔚 位蔚蟺蟿蠈</string>
+    <string name="interval_five_minutes">魏维胃蔚 5 位蔚蟺蟿维</string>
+    <string name="interval_ten_minutes">魏维胃蔚 10 位蔚蟺蟿维</string>
+    <string name="interval_thirty_minutes">魏维胃蔚 30 位蔚蟺蟿维</string>
+    <string name="interval_one_hour">魏维胃蔚 蠋蟻伪</string>
+    <string name="stats_title">螙蠋谓蔚蟼 蟿伪蠂蠉蟿畏蟿伪蟼</string>
+    <string name="stats_x_axis_label">螞蔚蟺蟿维 蟽蔚 蟽蠉谓慰位慰</string>
+    <string name="stats_y_axis_label">螔萎渭伪蟿伪 伪谓维 位蔚蟺蟿蠈</string>
+    <string name="control_center_find_lost_device">螘谓蟿慰蟺喂蟽渭蠈蟼 蟽蠀蟽魏蔚蠀萎蟼</string>
+    <string name="control_center_cancel_to_stop_vibration">螒魏蠀蟻蠋蟽蟿蔚 纬喂伪 谓伪 蟽蟿伪渭伪蟿萎蟽蔚喂 畏 未蠈谓畏蟽畏.</string>
+    <string name="title_activity_charts">螖蟻伪蟽蟿畏蟻喂蠈蟿畏蟿伪 魏伪喂 蠉蟺谓慰蟼</string>
+    <string name="title_activity_set_alarm">巍蠀胃渭委蟽蔚喂蟼 尉蠀蟺谓畏蟿畏蟻喂蠋谓</string>
+    <string name="controlcenter_start_configure_alarms">巍蠀胃渭委蟽蔚喂蟼 尉蠀蟺谓畏蟿畏蟻喂蠋谓</string>
+    <string name="title_activity_alarm_details">螞蔚蟺蟿慰渭苇蟻蔚喂蔚蟼 尉蠀蟺谓畏蟿畏蟻喂慰蠉</string>
+    <string name="alarm_sun_short">螝蠀蟻</string>
+    <string name="alarm_mon_short">螖蔚蠀</string>
+    <string name="alarm_tue_short">韦蟻委</string>
+    <string name="alarm_wed_short">韦蔚蟿</string>
+    <string name="alarm_thu_short">螤苇渭</string>
+    <string name="alarm_fri_short">螤伪蟻</string>
+    <string name="alarm_sat_short">危维尾</string>
+    <string name="alarm_smart_wakeup">螆尉蠀蟺谓慰 尉蠉蟺谓畏渭伪</string>
+    <string name="user_feedback_miband_set_alarms_failed">违蟺萎蟻尉蔚 位维胃慰蟼 蟽蟿畏 蟻蠉胃渭喂蟽畏 蟿蠅谓 尉蠀蟺谓畏蟿畏蟻喂蠋谓, 蟺伪蟻伪魏伪位蠋 蟺蟻慰蟽蟺伪胃萎蟽蟿蔚 尉伪谓维.</string>
+    <string name="user_feedback_miband_set_alarms_ok">韦伪 尉蠀蟺谓畏蟿萎蟻喂伪 蟽蟿维位胃畏魏伪谓 蟽蟿畏 蟽蠀蟽魏蔚蠀萎.</string>
+    <string name="chart_no_data_synchronize">围蠅蟻委蟼 未蔚未慰渭苇谓伪. 危蠀纬蠂蟻慰谓喂蟽渭蠈蟼 渭蔚 蟿畏 蟽蠀蟽魏蔚蠀萎;</string>
+    <string name="user_feedback_miband_activity_data_transfer">螛伪 纬委谓蔚喂 渭蔚蟿伪蠁慰蟻维 %1$s 未蔚未慰渭苇谓蠅谓 尉蔚魏喂谓蠋谓蟿伪蟼 伪蟺蠈 %2$s</string>
+    <string name="miband_prefs_fitness_goal">危蟿蠈蠂慰蟼 尾畏渭维蟿蠅谓 伪谓维 渭苇蟻伪</string>
+    <string name="dbaccess_error_executing">危蠁维位渭伪 魏伪蟿维 蟿畏谓 蔚魏蟿苇位蔚蟽畏 \"%1$s\"</string>
+    <string name="controlcenter_start_activitymonitor">螚 未蟻伪蟽蟿畏蟻喂蠈蟿畏蟿维 蟽伪蟼 (违螤螣 螖螣螝螜螠螚)</string>
+    <string name="cannot_connect">螖蔚谓 渭蟺慰蟻蔚委 谓伪 蟽蠀谓未蔚胃蔚委: %1$s</string>
+    <string name="installer_activity_unable_to_find_handler">螖蔚谓 尾蟻苇胃畏魏蔚 蟺蟻蠈纬蟻伪渭渭伪 蟺慰蠀 渭蟺慰蟻蔚委 谓伪 伪谓慰委尉蔚喂 伪蠀蟿蠈 蟿慰 伪蟻蠂蔚委慰.</string>
+    <string name="pbw_install_handler_unable_to_install">螖蔚谓 蔚委谓伪喂 未蠀谓伪蟿萎 畏 蔚纬魏伪蟿维蟽蟿伪蟽畏 伪蠀蟿慰蠉 蟿慰蠀 伪蟻蠂蔚委慰蠀: %1$s</string>
+    <string name="pbw_install_handler_hw_revision_mismatch">螖蔚谓 蔚委谓伪喂 未蠀谓伪蟿萎 畏 蔚纬魏伪蟿维蟽蟿伪蟽畏 伪蠀蟿慰蠉 蟿慰蠀 位慰纬喂蟽渭喂魏慰蠉: 螖蔚谓 蟿伪喂蟻喂维味蔚喂 渭蔚 伪蠀蟿萎 蟿畏谓 苇魏未慰蟽畏 蠀位喂魏慰蠉 蟿慰蠀 Pebble.</string>
+    <string name="installer_activity_wait_while_determining_status">螤伪蟻伪魏伪位蠋 蟺蔚蟻喂渭苇谓蔚蟿蔚 谓伪 魏伪胃慰蟻喂蟽蟿蔚委 畏 魏伪蟿维蟽蟿伪蟽畏 蟿畏蟼 蔚纬魏伪蟿维蟽蟿伪蟽畏蟼鈥�</string>
+    <string name="notif_battery_low_title">围伪渭畏位萎 渭蟺伪蟿伪蟻委伪 蟿畏蟼 蠁慰蟻蔚蟿萎蟼 蟽蠀蟽魏蔚蠀萎蟼!</string>
+    <string name="notif_battery_low_percent">%1$s 螠蟺伪蟿伪蟻委伪 蟺慰蠀 伪蟺慰渭苇谓蔚喂: %2$s%%</string>
+    <string name="notif_battery_low_bigtext_last_charge_time">韦蔚位蔚蠀蟿伪委伪 蠁蠈蟻蟿喂蟽畏: %s 
+\n</string>
+    <string name="notif_battery_low_bigtext_number_of_charges">螒蟻喂胃渭蠈蟼 蠁慰蟻蟿委蟽蔚蠅谓: %s</string>
+    <string name="sleepchart_your_sleep">螏蟺谓慰蟼</string>
+    <string name="weeksleepchart_sleep_a_week">螏蟺谓慰蟼 伪谓维 蔚尾未慰渭维未伪</string>
+    <string name="weeksleepchart_today_sleep_description">螏蟺谓慰蟼 蟽萎渭蔚蟻伪, 蟽蟿蠈蠂慰蟼: %1$s</string>
+    <string name="weekstepschart_steps_a_week">螔萎渭伪蟿伪 蟿畏谓 蔚尾未慰渭维未伪</string>
+    <string name="activity_sleepchart_activity_and_sleep">螖蟻伪蟽蟿畏蟻喂蠈蟿畏蟿伪</string>
+    <string name="updating_firmware">螒谓伪尾维胃渭喂蟽畏 位慰纬喂蟽渭喂魏慰蠉鈥�</string>
+    <string name="fwapp_install_device_not_ready">韦慰 伪蟻蠂蔚委慰 未蔚谓 渭蟺慰蟻蔚委 谓伪 蔚纬魏伪蟿伪蟽蟿伪胃蔚委, 畏 蟽蠀蟽魏蔚蠀萎 未蔚谓 蔚委谓伪喂 苇蟿慰喂渭畏.</string>
+    <string name="miband_fwinstaller_compatible_version">危蠀渭尾伪蟿萎 苇魏未慰蟽畏</string>
+    <string name="miband_fwinstaller_untested_version">螠畏 未慰魏喂渭伪蟽渭苇谓畏 苇魏未慰蟽畏!</string>
+    <string name="fwappinstaller_connection_state">危蠉谓未蔚蟽畏 蟽蟿畏 蟽蠀蟽魏蔚蠀萎: %1$s</string>
+    <string name="pbw_installhandler_pebble_firmware">螞慰纬喂蟽渭喂魏蠈 Pebble %1$s</string>
+    <string name="pbwinstallhandler_correct_hw_revision">螖喂慰蟻胃蠋蟽蟿蔚 蟿畏谓 苇魏未慰蟽畏 伪谓伪胃蔚蠋蟻畏蟽畏蟼 蟿慰蠀 蠀位喂魏慰蠉</string>
+    <string name="pbwinstallhandler_incorrect_hw_revision">螚 苇魏未慰蟽畏 蠀位喂魏慰蠉 未蔚谓 蟿伪喂蟻喂维味蔚喂!</string>
+    <string name="pbwinstallhandler_app_item">%1$s (%2$s)</string>
+    <string name="updatefirmwareoperation_updateproblem_do_not_reboot">螤蟻蠈尾位畏渭伪 渭蔚 蟿畏谓 渭蔚蟿伪蠁慰蟻维 蟿慰蠀 位慰纬喂蟽渭喂魏慰蠉 蟽蟿畏 蟽蠀蟽魏蔚蠀萎. 螠螚螡 螘螤螒螡螘螝螝螜螡螚危螘韦螘 蟿慰 Mi Band 蟽伪蟼!</string>
+    <string name="updatefirmwareoperation_metadata_updateproblem">螤蟻蠈尾位畏渭伪 渭蔚 蟿畏 渭蔚蟿伪蠁慰蟻维 蟿蠅谓 渭蔚蟿伪未蔚未慰渭苇谓蠅谓 蟿慰蠀 位慰纬喂蟽渭喂魏慰蠉 蟽蟿畏 蟽蠀蟽魏蔚蠀萎</string>
+    <string name="updatefirmwareoperation_update_complete">螚 蔚纬魏伪蟿维蟽蟿伪蟽畏 蟿慰蠀 位慰纬喂蟽渭喂魏慰蠉 慰位慰魏位畏蟻蠋胃畏魏蔚</string>
+    <string name="updatefirmwareoperation_update_complete_rebooting">螚 蔚纬魏伪蟿维蟽蟿伪蟽畏 蟿慰蠀 位慰纬喂蟽渭喂魏慰蠉 慰位慰魏位畏蟻蠋胃畏魏蔚, 畏 蟽蠀蟽魏蔚蠀萎 蔚蟺伪谓蔚魏魏喂谓蔚委鈥�</string>
+    <string name="updatefirmwareoperation_write_failed">螚 蔚纬魏伪蟿维蟽蟿伪蟽畏 蟿慰蠀 位慰纬喂蟽渭喂魏慰蠉 伪蟺苇蟿蠀蠂蔚</string>
+    <string name="chart_steps">螔萎渭伪蟿伪</string>
+    <string name="calories">螛蔚蟻渭委未蔚蟼</string>
+    <string name="distance">螒蟺蠈蟽蟿伪蟽畏</string>
+    <string name="clock">巍慰位蠈喂</string>
+    <string name="heart_rate">危蠁蠀纬渭慰委</string>
+    <string name="battery">螠蟺伪蟿伪蟻委伪</string>
+    <string name="liveactivity_live_activity">螚 未蟻伪蟽蟿畏蟻喂蠈蟿畏蟿伪 live</string>
+    <string name="weeksteps_today_steps_description">螔萎渭伪蟿伪 蟽萎渭蔚蟻伪, 蟽蟿蠈蠂慰蟼: %1$s</string>
+    <string name="pref_title_dont_ack_transfer">螠畏谓 蔚蟺喂尾蔚尾伪喂蠋蟽蔚蟿蔚 蟿畏 渭蔚蟿伪蠁慰蟻维 未蔚未慰渭苇谓蠅谓 未蟻伪蟽蟿畏蟻喂蠈蟿畏蟿伪蟼</string>
+    <string name="pref_summary_dont_ack_transfers">螘维谓 畏 渭蔚蟿伪蠁慰蟻维 蟿蠅谓 未蔚未慰渭苇谓蠅谓 未蟻伪蟽蟿畏蟻喂蠈蟿畏蟿伪蟼 未蔚谓 蔚蟺喂尾蔚尾伪喂蠅胃蔚委, 蟿伪 未蔚未慰渭苇谓伪 未蔚谓 胃伪 未喂伪纬蟻伪蠁慰蠉谓. 围蟻萎蟽喂渭慰 伪谓 蟿慰 Gadgetbridge 蠂蟻畏蟽喂渭慰蟺慰喂蔚委蟿伪喂 蟺伪蟻维位位畏位伪 渭蔚 维位位蔚蟼 蔚蠁伪蟻渭慰纬苇蟼.</string>
+    <string name="pref_summary_keep_data_on_device">螛伪 魏蟻伪蟿畏胃慰蠉谓 蟿伪 未蔚未慰渭苇谓伪 蟿畏蟼 未蟻伪蟽蟿畏蟻喂蠈蟿畏蟿伪蟼 蟽蟿慰 Mi Band 魏伪喂 渭蔚蟿维 蟿慰谓 蟽蠀纬蠂蟻慰谓喂蟽渭蠈. 围蟻萎蟽喂渭慰 伪谓 蟿慰 Gadgetbridge 蠂蟻畏蟽喂渭慰蟺慰喂蔚委蟿伪喂 蟺伪蟻维位位畏位伪 渭蔚 维位位蔚蟼 蔚蠁伪蟻渭慰纬苇蟼.</string>
+    <string name="installhandler_firmware_name">%1$s: %2$s %3$s</string>
+    <string name="pref_title_low_latency_fw_update">围蟻萎蟽畏 蟿畏蟼 位蔚喂蟿慰蠀蟻纬委伪蟼 蠂伪渭畏位萎蟼 魏伪胃蠀蟽蟿苇蟻畏蟽畏蟼 纬喂伪 蟿喂蟼 伪谓伪尾伪胃渭委蟽蔚喂蟼 位慰纬喂蟽渭喂魏慰蠉</string>
+    <string name="pref_summary_low_latency_fw_update">螒蠀蟿蠈 渭蟺慰蟻蔚委 谓伪 尾慰畏胃萎蟽蔚喂 纬喂伪 蟽蠀蟽魏蔚蠀苇蟼 蟺慰蠀 畏 伪谓伪尾维胃渭喂蟽畏 位慰纬喂蟽渭喂魏慰蠉 伪蟺慰蟿蠀纬蠂维谓蔚喂.</string>
+    <string name="live_activity_steps_history">螜蟽蟿慰蟻喂魏蠈 尾畏渭维蟿蠅谓</string>
+    <string name="live_activity_current_steps_per_minute">韦蟻苇蠂蠅谓 蟻蠀胃渭蠈蟼 尾畏渭维蟿蠅谓/位蔚蟺蟿蠈</string>
+    <string name="live_activity_total_steps">危蠀谓慰位喂魏蠈蟼 伪蟻喂胃渭蠈蟼 尾畏渭维蟿蠅谓</string>
+    <string name="live_activity_steps_per_minute_history">螜蟽蟿慰蟻喂魏蠈 蟻蠀胃渭慰蠉 尾畏渭维蟿蠅谓/位蔚蟺蟿蠈</string>
+    <string name="live_activity_start_your_activity">螢蔚魏喂谓萎蟽蟿蔚 蟿畏 未蟻伪蟽蟿畏蟻喂蠈蟿畏蟿伪 蟽伪蟼</string>
+    <string name="abstract_chart_fragment_kind_activity">螖蟻伪蟽蟿畏蟻喂蠈蟿畏蟿伪</string>
+    <string name="abstract_chart_fragment_kind_light_sleep">螘位伪蠁蟻蠉蟼 蠉蟺谓慰蟼</string>
+    <string name="abstract_chart_fragment_kind_deep_sleep">螔伪胃蠉蟼 蠉蟺谓慰蟼</string>
+    <string name="abstract_chart_fragment_kind_not_worn">螒蠁蠈蟻蔚蟿慰</string>
+    <string name="device_not_connected">螠畏 蟽蠀谓未蔚未蔚渭苇谓慰.</string>
+    <string name="user_feedback_all_alarms_disabled">螌位伪 蟿伪 尉蠀蟺谓畏蟿萎蟻喂伪 伪蟺蔚谓蔚蟻纬慰蟺慰喂萎胃畏魏伪谓</string>
+    <string name="pref_title_keep_data_on_device">螖喂伪蟿萎蟻畏蟽畏 蟿蠅谓 未蔚未慰渭苇谓蠅谓 未蟻伪蟽蟿畏蟻喂蠈蟿畏蟿伪蟼 蟽蟿畏 蟽蠀蟽魏蔚蠀萎</string>
+    <string name="miband_fwinstaller_incompatible_version">螠畏 蟽蠀渭尾伪蟿蠈 位慰纬喂蟽渭喂魏蠈</string>
+    <string name="fwinstaller_firmware_not_compatible_to_device">螒蠀蟿蠈 蟿慰 位慰纬喂蟽渭喂魏蠈 未蔚谓 蔚委谓伪喂 蟽蠀渭尾伪蟿蠈 渭蔚 蟿畏 蟽蠀蟽魏蔚蠀萎</string>
+    <string name="miband_prefs_reserve_alarm_calendar">螢蠀蟺谓畏蟿萎蟻喂伪 蟺慰蠀 胃伪 \"魏蟻伪蟿畏胃慰蠉谓\" 纬喂伪 蟿伪 蔚蟺蔚蟻蠂蠈渭蔚谓伪 蟽蠀渭尾维谓蟿伪 蟿慰蠀 畏渭蔚蟻慰位慰纬委慰蠀</string>
+    <string name="miband_prefs_hr_sleep_detection">围蟻萎蟽畏 蟿慰蠀 伪喂蟽胃畏蟿萎蟻伪 蟺伪位渭蠋谓 纬喂伪 谓伪 尾蔚位蟿喂蠅胃蔚委 畏 伪谓委蠂谓蔚蠀蟽畏 蟿慰蠀 蠉蟺谓慰蠀</string>
+    <string name="miband_prefs_device_time_offset_hours">螒谓蟿喂蟽蟿维胃渭喂蟽畏 蠂蟻蠈谓慰蠀 蟽蔚 蠋蟻蔚蟼 (纬喂伪 谓伪 伪谓喂蠂谓蔚蠀胃蔚委 慰 蠉蟺谓慰蟼, 蟽蔚 蔚蟻纬伪味蠈渭蔚谓慰蠀蟼 蟺慰蠀 未慰蠀位蔚蠉慰蠀谓 尾蟻维未蠀)</string>
+    <string name="miband2_prefs_dateformat">螠慰蟻蠁萎 畏渭蔚蟻慰渭畏谓委伪蟼</string>
+    <string name="dateformat_time">螐蟻伪</string>
+    <string name="dateformat_date_time">螐蟻伪 魏伪喂 畏渭蔚蟻慰渭畏谓委伪</string>
+    <string name="mi2_prefs_button_actions">螞蔚喂蟿慰蠀蟻纬委蔚蟼 魏慰蠀渭蟺喂慰蠉</string>
+    <string name="mi2_prefs_button_actions_summary">螝伪胃慰蟻喂蟽渭蠈蟼 蔚谓蔚蟻纬蔚喂蠋谓 纬喂伪 蟿慰 蟺维蟿畏渭伪 蟿慰蠀 魏慰蠀渭蟺喂慰蠉</string>
+    <string name="mi2_prefs_button_press_count">螒蟻喂胃渭蠈蟼 蟺伪蟿畏渭维蟿蠅谓 蟿慰蠀 魏慰蠀渭蟺喂慰蠉</string>
+    <string name="mi2_prefs_button_press_count_summary">螒蟻喂胃渭蠈蟼 蟺伪蟿畏渭维蟿蠅谓 蟿慰蠀 魏慰蠀渭蟺喂慰蠉 纬喂伪 谓伪 蟽蟿伪位胃蔚委 渭萎谓蠀渭伪 蔚谓蔚蟻纬慰蟺慰委畏蟽畏蟼 纬喂伪 蟿慰 \"纬蔚纬慰谓蠈蟼1\", 伪魏慰位慰蠉胃蠅蟼 纬喂伪 蟿慰 \"纬蔚纬慰谓蠈蟼2\" 魏位蟺.</string>
+    <string name="mi2_prefs_button_press_broadcast">螠萎谓蠀渭伪 蟺慰蠀 胃伪 蟽蟿伪位蔚委</string>
+    <string name="mi2_prefs_button_press_broadcast_summary">韦慰 渭萎谓蠀渭伪 蟺慰蠀 胃伪 蟽蟿伪位蔚委 渭蔚 蔚谓蔚蟻纬慰蟺慰委畏蟽畏 蟿慰蠀 纬蔚纬慰谓蠈蟿慰蟼. 螚 蟺伪蟻维渭蔚蟿蟻慰蟼 \"button id\" 胃伪 蟺蟻慰蟽蟿蔚胃蔚委 伪蠀蟿蠈渭伪蟿伪 蟽蔚 魏维胃蔚 渭萎谓蠀渭伪.</string>
+    <string name="mi2_prefs_button_action">螘谓蔚蟻纬慰蟺慰委畏蟽畏 位蔚喂蟿慰蠀蟻纬委伪蟼 魏慰蠀渭蟺喂慰蠉</string>
+    <string name="mi2_prefs_button_action_summary">螘谓蔚蟻纬慰蟺慰委畏蟽畏 蟿畏蟼 蔚谓苇蟻纬蔚喂伪蟼 蟽蟿伪 蟺蟻慰魏伪胃慰蟻喂蟽渭苇谓伪 蟺伪蟿萎渭伪蟿伪 蟿慰蠀 魏慰蠀渭蟺喂慰蠉</string>
+    <string name="mi2_prefs_button_action_vibrate">螘谓蔚蟻纬慰蟺慰委畏蟽畏 未蠈谓畏蟽畏蟼</string>
+    <string name="mi2_prefs_button_action_vibrate_summary">螘谓蔚蟻纬慰蟺慰委畏蟽畏 未蠈谓畏蟽畏蟼 蠈蟿伪谓 蟺伪蟿喂苇蟿伪喂 蟿慰 魏慰蠀渭蟺委</string>
+    <string name="mi2_prefs_button_press_count_max_delay">螠苇纬喂蟽蟿慰蟼 蠂蟻蠈谓慰蟼 渭蔚蟿伪尉蠉 蟿蠅谓 蠂蟿蠀蟺畏渭维蟿蠅谓 蟿慰蠀 魏慰蠀渭蟺喂慰蠉</string>
+    <string name="mi2_prefs_button_press_count_max_delay_summary">螠苇纬喂蟽蟿慰蟼 蠂蟻蠈谓慰蟼 渭蔚蟿伪尉蠉 蟿蠅谓 蠂蟿蠀蟺畏渭维蟿蠅谓 蟿慰蠀 魏慰蠀渭蟺喂慰蠉 (蟽蔚 蠂喂位喂慰蟽蟿维 蟿慰蠀 未蔚蠀蟿蔚蟻慰位苇蟺蟿慰蠀)</string>
+    <string name="mi2_prefs_goal_notification">螘喂未慰蟺慰委畏蟽畏 蔚蟺委蟿蔚蠀尉畏蟼 蟽蟿蠈蠂慰蠀</string>
+    <string name="mi2_prefs_goal_notification_summary">韦慰 Mi Band 胃伪 魏维谓蔚喂 未蠈谓畏蟽畏 蠈蟿伪谓 慰 畏渭蔚蟻萎蟽喂慰蟼 蟽蟿蠈蠂慰蟼 纬喂伪 蟿伪 尾萎渭伪蟿伪 蔚蟺喂蟿蔚蠀蠂胃蔚委</string>
+    <string name="mi2_prefs_display_items">螤位畏蟻慰蠁慰蟻委蔚蟼 蟺慰蠀 胃伪 蔚渭蠁伪谓委味慰谓蟿伪喂</string>
+    <string name="mi2_prefs_display_items_summary">螖喂伪位苇尉蟿蔚 蟿委 胃苇位蔚蟿蔚 谓伪 蔚渭蠁伪谓委味蔚蟿伪喂 蟽蟿畏谓 慰胃蠈谓畏 蟿慰蠀 Mi Band</string>
+    <string name="mi2_prefs_activate_display_on_lift">螘谓蔚蟻纬慰蟺慰委畏蟽畏 慰胃蠈谓畏蟼 渭蔚 蟿慰 蟽萎魏蠅渭伪 蟿慰蠀 蠂蔚蟻喂慰蠉</string>
+    <string name="mi2_prefs_rotate_wrist_to_switch_info">螕蠀蟻委蟽蟿蔚 蟿慰 魏伪蟻蟺蠈 蟽伪蟼 纬喂伪 谓伪 蔚渭蠁伪谓喂蟽蟿蔚委 畏 蔚蟺蠈渭蔚谓畏 蟺位畏蟻慰蠁慰蟻委伪</string>
+    <string name="mi2_prefs_do_not_disturb">螠畏谓 螘谓慰蠂位蔚委蟿蔚</string>
+    <string name="mi2_prefs_do_not_disturb_summary">韦慰 Mi Band 未蔚谓 胃伪 位伪渭尾维谓蔚喂 蔚喂未慰蟺慰喂萎蟽蔚喂蟼 蠈蟿伪谓 蔚委谓伪喂 蔚谓蔚蟻纬慰蟺慰喂畏渭苇谓慰</string>
+    <string name="mi2_prefs_inactivity_warnings">螤蟻慰蔚喂未慰蟺慰委畏蟽畏 蠈蟿伪谓 蔚委蟽蟿蔚 伪未蟻伪谓萎蟼</string>
+    <string name="mi2_prefs_inactivity_warnings_summary">韦慰 Mi Band 胃伪 未慰谓蔚委蟿伪喂 蠈蟿伪谓 蔚委蟽蟿蔚 伪未蟻伪谓萎蟼 纬喂伪 蠋蟻伪</string>
+    <string name="mi2_prefs_inactivity_warnings_threshold">螞蔚蟺蟿维 伪未蟻维谓蔚喂伪蟼</string>
+    <string name="mi2_prefs_inactivity_warnings_dnd_summary">螒蟺蔚谓蔚蟻纬慰蟺慰喂萎蟽蟿蔚 蟿畏谓 蟺蟻慰蔚喂未慰蟺慰委畏蟽畏 伪未蟻维谓蔚喂伪蟼 纬喂伪 魏维蟺慰喂慰 蠂蟻蠈谓慰</string>
+    <string name="mi2_prefs_do_not_disturb_start">螐蟻伪 苇谓伪蟻尉畏蟼</string>
+    <string name="mi2_prefs_do_not_disturb_end">螐蟻伪 位萎尉畏蟼</string>
+    <string name="automatic">螒蠀蟿蠈渭伪蟿伪</string>
+    <string name="simplified_chinese">螒蟺位慰蟺慰喂畏渭苇谓伪 螝喂谓苇味喂魏伪</string>
+    <string name="traditional_chinese">螤伪蟻伪未慰蟽喂伪魏维 螝喂谓苇味喂魏伪</string>
+    <string name="english">螒纬纬位喂魏维</string>
+    <string name="FetchActivityOperation_about_to_transfer_since">螛伪 渭蔚蟿伪蠁苇蟻蔚蟿蔚 未蔚未慰渭苇谓伪 伪蟺蠈 %1$s</string>
+    <string name="waiting_for_reconnect">螒谓伪渭慰谓萎 纬喂伪 蔚蟺伪谓伪蟽蠉谓未蔚蟽畏</string>
+    <string name="activity_prefs_about_you">危蠂蔚蟿喂魏维 渭蔚 蔚蟽维蟼</string>
+    <string name="activity_prefs_year_birth">螆蟿慰蟼 纬苇谓谓畏蟽畏蟼</string>
+    <string name="activity_prefs_gender">桅蠉位位慰</string>
+    <string name="activity_prefs_height_cm">螏蠄慰蟼 蟽蔚 蔚魏伪蟿慰蟽蟿维</string>
+    <string name="activity_prefs_weight_kg">螔维蟻慰蟼 蟽蔚 魏喂位维</string>
+    <string name="authenticating">螘蟺伪位萎胃蔚蠀蟽畏 蟿伪蠀蟿蠈蟿畏蟿伪蟼</string>
+    <string name="authentication_required">螒蟺伪喂蟿蔚委蟿伪喂 蔚蟺伪位萎胃蔚蠀蟽畏 蟿伪蠀蟿蠈蟿畏蟿伪蟼</string>
+    <string name="appwidget_text">螙zz</string>
+    <string name="add_widget">螤蟻蠈蟽胃蔚蟽畏 widget</string>
+    <string name="activity_prefs_sleep_duration">螤蟻慰蟿喂渭蠋渭蔚谓慰蟼 蠂蟻蠈谓慰蟼 蠉蟺谓慰蠀 (蟽蔚 蠋蟻蔚蟼)</string>
+    <string name="appwidget_setting_alarm">螆谓伪 尉蠀蟺谓畏蟿萎蟻喂 苇蠂蔚喂 慰蟻喂蟽蟿蔚委 纬喂伪 蟿喂蟼 %1$02d:%2$02d</string>
+    <string name="device_hw">螆魏未慰蟽畏 蠀位喂魏慰蠉: %1$s</string>
+    <string name="device_fw">螆魏未慰蟽畏 位慰纬喂蟽渭喂魏慰蠉: %1$s</string>
+    <string name="error_creating_directory_for_logfiles">危蠁维位渭伪 魏伪蟿维 蟿畏 未畏渭喂慰蠀蟻纬委伪 蠁伪魏苇位慰蠀 纬喂伪 蟿伪 伪蟻蠂蔚委伪 魏伪蟿伪纬蟻伪蠁萎蟼: %1$s</string>
+    <string name="DEVINFO_HR_VER">"HR: "</string>
+    <string name="updatefirmwareoperation_update_in_progress">螒谓伪尾维胃渭喂蟽畏 蟿慰蠀 位慰纬喂蟽渭喂魏慰蠉</string>
+    <string name="updatefirmwareoperation_firmware_not_sent">韦慰 位慰纬喂蟽渭喂魏蠈 未蔚谓 伪蟺蔚蟽蟿维位畏 蟽蟿畏 蟽蠀蟽魏蔚蠀萎</string>
+    <string name="charts_legend_heartrate">危蠁蠀纬渭慰委</string>
+    <string name="live_activity_heart_rate">危蠁蠀纬渭慰委</string>
+    <string name="pref_title_pebble_health_store_raw">螒蟺慰胃萎魏蔚蠀蟽畏 蟿蠅谓 伪魏伪蟿苇蟻纬伪蟽蟿蠅谓 蔚纬纬蟻伪蠁蠋谓 蟽蟿畏 尾维蟽畏 未蔚未慰渭苇谓蠅谓</string>
+    <string name="pref_summary_pebble_health_store_raw">螠蔚 伪蠀蟿萎 蟿畏谓 蔚蟺喂位慰纬萎 胃伪 伪蟺慰胃畏魏蔚蠀蟿慰蠉谓 蟿伪 未蔚未慰渭苇谓伪 \"蠅蟼 苇蠂慰蠀谓\", 渭蔚纬伪位蠋谓慰谓蟿伪蟼 蟿畏 蠂蟻萎蟽畏 蟿畏蟼 尾维蟽畏蟼 未蔚未慰渭苇谓蠅谓 纬喂伪 谓伪 蔚委谓伪喂 未喂伪胃苇蟽喂渭伪 纬喂伪 蔚蟻渭畏谓蔚委伪 伪蟻纬蠈蟿蔚蟻伪.</string>
+    <string name="action_db_management">螖喂伪蠂蔚委蟻喂蟽畏 未蔚未慰渭苇谓蠅谓</string>
+    <string name="title_activity_db_management">螖喂伪蠂蔚委蟻喂蟽畏 未蔚未慰渭苇谓蠅谓</string>
+    <string name="activity_db_management_import_export_explanation">螚 位蔚喂蟿慰蠀蟻纬委伪 蟿畏蟼 尾维蟽畏蟼 未蔚未慰渭苇谓蠅谓 蠂蟻畏蟽喂渭慰蟺慰喂蔚委 蟿慰谓 伪魏蠈位慰蠀胃慰 蠁维魏蔚位慰 蟽蟿畏 蟽蠀蟽魏蔚蠀萎 蟽伪蟼.
+\n螣 蠁维魏蔚位慰蟼 伪蠀蟿蠈蟼 蔚委谓伪喂 蟺蟻慰蟽蟺蔚位维蟽喂渭慰蟼 伪蟺蠈 维位位蔚蟼 蔚蠁伪蟻渭慰纬苇蟼 蟽蟿慰 Android 魏伪喂 伪蟺蠈 蟿慰谓 蠀蟺慰位慰纬喂蟽蟿萎.
+\n螘魏蔚委 胃伪 尾蟻蔚委蟿蔚 蟿畏谓 尾维蟽畏 未蔚未慰渭苇谓蠅谓 蟺慰蠀 胃伪 蔚尉维纬蔚蟿蔚 (萎 尾维位蟿蔚 蔚魏蔚委 蟿畏 尾维蟽畏 未蔚未慰渭苇谓蠅谓 蟺慰蠀 胃苇位蔚蟿蔚 谓伪 蔚喂蟽维纬蔚蟿蔚):</string>
+    <string name="activity_db_management_merge_old_title">螖喂伪纬蟻伪蠁萎 蟺伪位喂维蟼 尾维蟽畏蟼 未蔚未慰渭苇谓蠅谓</string>
+    <string name="dbmanagementactivvity_cannot_access_export_path">螖蔚谓 蔚委谓伪喂 未蠀谓伪蟿萎 畏 蟺蟻蠈蟽尾伪蟽畏 蟽蟿慰谓 蠁维魏蔚位慰. 螤伪蟻伪魏伪位蠋 蔚蟺喂魏慰喂谓蠅谓萎蟽蟿蔚 渭蔚 蟿慰蠀蟼 蟺蟻慰纬蟻伪渭渭伪蟿喂蟽蟿苇蟼.</string>
+    <string name="dbmanagementactivity_exported_to">螘尉伪纬蠅纬萎 蟽蟿慰: %1$s</string>
+    <string name="dbmanagementactivity_error_exporting_db">危蠁维位渭伪 魏伪蟿维 蟿畏谓 蔚尉伪纬蠅纬萎 蟿畏蟼 尾维蟽畏蟼 未蔚未慰渭苇谓蠅谓: %1$s</string>
+    <string name="dbmanagementactivity_error_exporting_shared">危蠁维位渭伪 蟻蠀胃渭委蟽蔚蠅谓 蔚尉伪纬蠅纬萎蟼 蟿畏蟼 尾维蟽畏蟼 未蔚未慰渭苇谓蠅谓: %1$s</string>
+    <string name="dbmanagementactivity_import_data_title">螘喂蟽伪纬蠅纬萎 未蔚未慰渭苇谓蠅谓;</string>
+    <string name="dbmanagementactivity_overwrite_database_confirmation">螛苇位蔚蟿蔚 谓伪 纬蟻维蠄蔚蟿蔚 蟺维谓蠅 蟽蟿伪 蠀蟺维蟻蠂慰蠀蟽伪 未蔚未慰渭苇谓伪; 螌位伪 蟿伪 未蔚未慰渭苇谓伪 蟿畏蟼 未蟻伪蟽蟿畏蟻喂蠈蟿畏蟿维蟼 蟽伪蟼 (伪谓 蠀蟺维蟻蠂慰蠀谓) 魏伪喂 慰喂 蟺蟻慰蟿喂渭萎蟽蔚喂蟼 胃伪 蠂伪胃慰蠉谓.</string>
+    <string name="dbmanagementactivity_import_successful">螚 蔚喂蟽伪纬蠅纬萎 萎蟿伪谓 蔚蟺喂蟿蠀蠂萎蟼.</string>
+    <string name="dbmanagementactivity_error_importing_db">危蠁维位渭伪 魏伪蟿维 蟿畏谓 蔚喂蟽伪纬蠅纬萎 蟿畏蟼 尾维蟽畏蟼 未蔚未慰渭苇谓蠅谓: %1$s</string>
+    <string name="dbmanagementactivity_error_importing_shared">危蠁维位渭伪 蟻蠀胃渭委蟽蔚蠅谓 蔚喂蟽伪纬蠅纬萎蟼 蟿畏蟼 尾维蟽畏蟼 未蔚未慰渭苇谓蠅谓: %1$s</string>
+    <string name="dbmanagementactivity_delete_activity_data_title">螖喂伪纬蟻伪蠁萎 蟿蠅谓 未蔚未慰渭苇谓蠅谓 蟿畏蟼 未蟻伪蟽蟿畏蟻喂蠈蟿畏蟿维蟼 蟽伪蟼;</string>
+    <string name="dbmanagementactivity_really_delete_entire_db">螛苇位蔚蟿蔚 蟺蟻伪纬渭伪蟿喂魏维 谓伪 蟽尾萎蟽蔚蟿蔚 蠈位畏 蟿畏 尾维蟽畏 未蔚未慰渭苇谓蠅谓; 螌位伪 蟿伪 未蔚未慰渭苇谓伪 蟿畏蟼 未蟻伪蟽蟿畏蟻喂蠈蟿畏蟿维蟼 蟽伪蟼 魏伪喂 慰喂 蟺位畏蟻慰蠁慰蟻委蔚蟼 纬喂伪 蟿畏谓 蟽蠀蟽魏蔚蠀萎 蟽伪蟼 胃伪 蠂伪胃慰蠉谓.</string>
+    <string name="dbmanagementactivity_database_successfully_deleted">韦伪 未蔚未慰渭苇谓伪 未喂伪纬蟻维蠁畏魏伪谓.</string>
+    <string name="dbmanagementactivity_db_deletion_failed">螚 未喂伪纬蟻伪蠁萎 蟿畏蟼 尾维蟽畏蟼 未蔚未慰渭苇谓蠅谓 伪蟺苇蟿蠀蠂蔚.</string>
+    <string name="dbmanagementactivity_delete_old_activity_db">螖喂伪纬蟻伪蠁萎 蟿畏蟼 蟺伪位喂维蟼 尾维蟽畏蟼 未蔚未慰渭苇谓蠅谓;</string>
+    <string name="dbmanagementactivity_delete_old_activitydb_confirmation">螛苇位蔚蟿蔚 蟺蟻伪纬渭伪蟿喂魏维 谓伪 未喂伪纬蟻伪蠁蔚委 畏 蟺伪位喂维 尾维蟽畏 未蔚未慰渭苇谓蠅谓; 韦伪 未蔚未慰渭苇谓伪 蟿畏蟼 未蟻伪蟽蟿畏蟻喂蠈蟿畏蟿维蟼 蟽伪蟼 蟺慰蠀 未蔚谓 苇蠂慰蠀谓 蔚喂蟽伪蠂胃蔚委 胃伪 蠂伪胃慰蠉谓.</string>
+    <string name="dbmanagementactivity_old_activity_db_successfully_deleted">韦伪 蟺伪位喂维 未蔚未慰渭苇谓伪 蟿畏蟼 未蟻伪蟽蟿畏蟻喂蠈蟿畏蟿维蟼 蟽伪蟼 未喂伪纬蟻维蠁畏魏伪谓.</string>
+    <string name="dbmanagementactivity_old_activity_db_deletion_failed">螚 未喂伪纬蟻伪蠁萎 蟿畏蟼 蟺伪位喂维蟼 尾维蟽畏蟼 未蔚未慰渭苇谓蠅谓 伪蟺苇蟿蠀蠂蔚.</string>
+    <string name="dbmanagementactivity_overwrite">螒谓蟿喂魏伪蟿维蟽蟿伪蟽畏</string>
+    <string name="Cancel">螁魏蠀蟻慰</string>
+    <string name="Delete">螖喂伪纬蟻伪蠁萎</string>
+    <string name="title_activity_vibration">螖蠈谓畏蟽畏</string>
+    <string name="title_activity_pebble_pairing">危蠉味蔚蠀尉畏 Pebble</string>
+    <string name="pebble_pairing_hint">螆谓伪 蟺伪蟻维胃蠀蟻慰 蟽蠉味蔚蠀尉畏蟼 胃伪 蔚渭蠁伪谓喂蟽蟿蔚委 蟽蟿畏谓 Abdroid 蟽蠀蟽魏蔚蠀萎 蟽伪蟼. 螒谓 伪蠀蟿蠈 未蔚谓 蟽蠀渭尾蔚委, 未蔚委蟿蔚 蟽蟿畏 蟺蔚蟻喂慰蠂萎 蔚喂未慰蟺慰喂萎蟽蔚蠅谓 魏伪喂 未蔚蠂胃蔚委蟿蔚 蟿畏谓 蟽蠉味蔚蠀尉畏. 螘蟺委蟽畏蟼 渭蔚蟿维 未蔚蠂胃蔚委蟿蔚 蟿畏谓 蟽蠉味蔚蠀尉畏 魏伪喂 蟽蟿慰 Pebble 蟽伪蟼.</string>
+    <string name="weather_notification_label">螔蔚尾伪喂蠅胃蔚委蟿蔚 蠈蟿喂 伪蠀蟿蠈 蟿慰 胃苇渭伪 蔚委谓伪喂 蔚谓蔚蟻纬慰蟺慰喂畏渭苇谓慰 蟽蟿畏谓 蔚蠁伪蟻渭慰纬萎 蟿慰蠀 魏伪喂蟻慰蠉 蠋蟽蟿蔚 谓伪 位维尾蔚喂 蟿喂蟼 蟺位畏蟻慰蠁慰蟻委蔚蟼 蟿慰 Pebble.
+\n
+\n螖蔚谓 蠂蟻蔚喂维味慰谓蟿伪喂 蔚未蠋 蟻蠀胃渭委蟽蔚喂蟼.
+\n
+\n螠蟺慰蟻蔚委蟿蔚 谓伪 蔚谓蔚蟻纬慰蟺慰喂萎蟽蔚蟿蔚 蟿畏谓 蔚蠁伪蟻渭慰纬萎 蟿慰蠀 魏伪喂蟻慰蠉 蟿慰蠀 Pebble 伪蟺蠈 蟿慰谓 未喂伪蠂蔚喂蟻喂蟽蟿萎 蔚蠁伪蟻渭慰纬蠋谓.
+\n
+\n韦伪 watchfaces 蟺慰蠀 蠀蟺慰蟽蟿畏蟻委味慰蠀谓 蟿畏谓 蔚蠁伪蟻渭慰纬萎 蟿慰蠀 魏伪喂蟻慰蠉, 胃伪 未蔚委尉慰蠀谓 蟿慰谓 魏伪喂蟻蠈 伪蠀蟿蠈渭伪蟿伪.</string>
+    <string name="pref_title_setup_bt_pairing">螘谓蔚蟻纬慰蟺慰委畏蟽畏 蟽蠉味蔚蠀尉畏蟼 Bluetooth</string>
+    <string name="pref_summary_setup_bt_pairing">螒蟺蔚谓蔚蟻纬慰蟺慰喂萎蟽蟿蔚 伪蠀蟿萎 蟿畏谓 蔚蟺喂位慰纬萎 伪谓 苇蠂蔚蟿蔚 蟺蟻慰尾位萎渭伪蟿伪 蟽蠉谓未蔚蟽畏蟼</string>
+    <string name="unit_metric">螠蔚蟿蟻喂魏蠈 (S.I.)</string>
+    <string name="unit_imperial">螒纬纬位慰蟽伪尉蠅谓喂魏蠈</string>
+    <string name="timeformat_24h">24蠅蟻慰</string>
+    <string name="timeformat_am_pm">蟺渭/渭渭</string>
+    <string name="pref_screen_notification_profile_alarm_clock">螢蠀蟺谓畏蟿萎蟻喂</string>
+    <string name="activity_web_view">螖蟻伪蟽蟿畏蟻喂蠈蟿畏蟿伪 蟺蟻慰尾慰位萎蟼 喂蟽蟿慰蠉</string>
+    <string name="StringUtils_sender">(%1$s)</string>
+    <string name="find_device_you_found_it">韦慰 尾蟻萎魏伪蟿蔚!</string>
+    <string name="miband2_prefs_timeformat">Mi Band 2: 螠慰蟻蠁萎 蠋蟻伪蟼</string>
+    <string name="mi2_fw_installhandler_fw53_hint">螤蟻苇蟺蔚喂 谓伪 蔚纬魏伪蟿伪蟽蟿萎蟽蔚蟿蔚 蟺蟻蠋蟿伪 蟿畏谓 苇魏未慰蟽畏 位慰纬喂蟽渭喂魏慰蠉 %1$s 蟺蟻喂谓 蟺蟻慰尾蔚委蟿蔚 蟽蟿畏谓 蔚纬魏伪蟿维蟽蟿伪蟽畏 蟿畏蟼 苇魏未慰蟽畏蟼 蟺慰蠀 蔚蟺喂胃蠀渭蔚委蟿蔚!</string>
+    <string name="mi2_enable_text_notifications">螘喂未慰蟺慰喂萎蟽蔚喂蟼 魏蔚喂渭苇谓慰蠀</string>
+    <string name="mi2_enable_text_notifications_summary">围蟻蔚喂维味蔚蟿伪喂 位慰纬喂蟽渭喂魏蠈 谓蔚蠋蟿蔚蟻慰 伪蟺蠈 蟿畏谓 苇魏未慰蟽畏 1.0.1.28 魏伪喂 蟿慰 伪蟻蠂蔚委慰 Mili_pro.ft* 谓伪 苇蠂蔚喂 蔚纬魏伪蟿伪蟽蟿伪胃蔚委.</string>
+    <string name="off">螒蟺蔚谓蔚蟻纬慰蟺慰喂畏渭苇谓慰</string>
+    <string name="mi2_dnd_off">螒蟺蔚谓蔚蟻纬慰蟺慰喂畏渭苇谓慰</string>
+    <string name="mi2_dnd_automatic">螒蠀蟿蠈渭伪蟿慰 (蔚谓蟿慰蟺喂蟽渭蠈蟼 蠉蟺谓慰蠀)</string>
+    <string name="mi2_dnd_scheduled">螤蟻慰纬蟻伪渭渭伪蟿喂蟽渭苇谓慰 (蟽蠀纬魏蔚魏蟻喂渭苇谓畏 蠋蟻伪)</string>
+    <string name="discovery_attempting_to_pair">螤蟻慰蟽蟺维胃蔚喂伪 蟽蠉味蔚蠀尉畏蟼 渭蔚 %1$s</string>
+    <string name="discovery_bonding_failed_immediately">螣 未蔚蟽渭蠈蟼 渭蔚 %1$s 伪蟺苇蟿蠀蠂蔚 维渭蔚蟽伪.</string>
+    <string name="discovery_trying_to_connect_to">螤蟻慰蟽蟺维胃蔚喂伪 蟽蠉谓未蔚蟽畏蟼 渭蔚 蟿慰: %1$s</string>
+    <string name="discovery_enable_bluetooth">螘谓蔚蟻纬慰蟺慰喂萎蟽蟿蔚 蟿慰 Bluetooth 纬喂伪 谓伪 尾蟻蔚委蟿蔚 蟿畏 蟽蠀蟽魏蔚蠀萎.</string>
+    <string name="discovery_successfully_bonded">螘蟺喂蟿蠀蠂萎蟼 未蔚蟽渭蠈蟼 渭蔚 %1$s.</string>
+    <string name="discovery_pair_title">危蠉味蔚蠀尉畏 渭蔚 %1$s;</string>
+    <string name="discovery_pair_question">螖喂伪位苇尉蟿蔚 \"危蠉味蔚蠀尉畏\" 纬喂伪 谓伪 纬委谓蔚喂 畏 蟽蠉味蔚蠀尉畏 蟿蠅谓 蟽蠀蟽魏蔚蠀蠋谓. 螒谓 伪蠀蟿蠈 伪蟺慰蟿蠉蠂蔚喂, 蟺蟻慰蟽蟺伪胃萎蟽蟿蔚 蟺维位喂 蠂蠅蟻委蟼 蟽蠉味蔚蠀尉畏.</string>
+    <string name="discovery_yes_pair">危蠉味蔚蠀尉畏</string>
+    <string name="discovery_dont_pair">螠畏 纬委谓蔚喂 危蠉味蔚蠀尉畏</string>
+    <string name="_pebble_watch_open_on_phone">螁谓慰喂纬渭伪 蟽蟿慰 蟿畏位苇蠁蠅谓慰</string>
+    <string name="_pebble_watch_mute">危委纬伪蟽畏</string>
+    <string name="_pebble_watch_reply">螒蟺维谓蟿畏蟽畏</string>
+    <string name="kind_firmware">螞慰纬喂蟽渭喂魏蠈</string>
+    <string name="kind_invalid">螠畏 苇纬魏蠀蟻伪 未蔚未慰渭苇谓伪</string>
+    <string name="kind_font">螕蟻伪渭渭伪蟿慰蟽蔚喂蟻维</string>
+    <string name="kind_gps">螞慰纬喂蟽渭喂魏蠈 GPS</string>
+    <string name="kind_gps_almanac">螚渭蔚蟻慰位蠈纬喂慰 GPS</string>
+    <string name="kind_gps_cep">危蠁维位渭伪 蟽蠉谓未蔚蟽畏蟼 GPS</string>
+    <string name="kind_resources">螤畏纬苇蟼</string>
+    <string name="kind_watchface">Watchface</string>
+    <string name="devicetype_unknown">螁纬谓蠅蟽蟿畏 蟽蠀蟽魏蔚蠀萎</string>
+    <string name="devicetype_test">螖慰魏喂渭萎 蟽蠀蟽魏蔚蠀萎蟼</string>
+    <string name="devicetype_pebble">Pebble</string>
+    <string name="devicetype_miband">Mi Band</string>
+    <string name="devicetype_miband2">Mi Band 2</string>
+    <string name="devicetype_amazfit_bip">Amazfit Bip</string>
+    <string name="devicetype_amazfit_cor">Amazfit Cor</string>
+    <string name="devicetype_vibratissimo">Vibratissimo</string>
+    <string name="devicetype_liveview">LiveView</string>
+    <string name="devicetype_hplus">HPlus</string>
+    <string name="devicetype_makibes_f68">Makibes F68</string>
+    <string name="devicetype_exrizu_k8">Exrizu K8</string>
+    <string name="devicetype_no1_f1">No.1 F1</string>
+    <string name="devicetype_teclast_h30">Teclast H30</string>
+    <string name="pref_title_weather">螝伪喂蟻蠈蟼</string>
+    <string name="pref_header_auto_export">螒蠀蟿蠈渭伪蟿畏 蔚尉伪纬蠅纬萎 蟻蠀胃渭委蟽蔚蠅谓</string>
+    <string name="pref_title_auto_export_enabled">螚 伪蠀蟿蠈渭伪蟿畏 蔚尉伪纬蠅纬萎 蔚委谓伪喂 蔚谓蔚蟻纬慰蟺慰喂畏渭苇谓畏</string>
+    <string name="pref_title_auto_export_location">螖喂伪未蟻慰渭萎 蔚尉伪纬蠅纬萎蟼</string>
+    <string name="pref_title_auto_export_interval">螝维胃蔚 蟺蠈蟿蔚 胃伪 纬委谓蔚蟿伪喂 畏 蔚尉伪纬蠅纬萎</string>
+    <string name="pref_summary_auto_export_interval">螘尉伪纬蠅纬萎 魏维胃蔚 %d 蠋蟻蔚蟼</string>
+    <string name="notif_export_failed_title">螚 蔚尉伪纬蠅纬萎 蟿畏蟼 尾维蟽畏蟼 未蔚未慰渭苇谓蠅谓 伪蟺苇蟿蠀蠂蔚! 螤伪蟻伪魏伪位蠋 蔚位苇纬尉蟿蔚 蟿喂蟼 蟻蠀胃渭委蟽蔚喂蟼 蟽伪蟼.</string>
+    <string name="choose_auto_export_location">螖喂伪位苇尉蟿蔚 未喂伪未蟻慰渭萎 蔚尉伪纬蠅纬萎蟼</string>
+    <string name="spanish">螜蟽蟺伪谓喂魏维</string>
+    <string name="notification_channel_name">螘喂未慰蟺慰喂萎蟽蔚喂蟼 Gadgetbridge</string>
+    <string name="devicetype_xwatch">XWatch</string>
+    <string name="on">螒谓慰喂蠂蟿蠈</string>
+    <string name="controlcenter_start_activity_tracks">螚 未蟻伪蟽蟿畏蟻喂蠈蟿畏蟿伪 蟽伪蟼 魏伪蟿伪纬蟻维蠁蔚蟿伪喂</string>
+    <string name="activity_type_not_measured">螖蔚谓 渭蔚蟿蟻萎胃畏魏蔚</string>
+    <string name="activity_type_activity">螖蟻伪蟽蟿畏蟻喂蠈蟿畏蟿伪</string>
+    <string name="activity_type_light_sleep">螘位伪蠁蟻蠉蟼 蠉蟺谓慰蟼</string>
+    <string name="activity_type_deep_sleep">螔伪胃蠉蟼 蠉蟺谓慰蟼</string>
+    <string name="activity_type_not_worn">螚 蟽蠀蟽魏蔚蠀萎 未蔚谓 蠁慰蟻喂苇蟿伪喂</string>
+    <string name="activity_type_running">韦蟻苇尉喂渭慰</string>
+    <string name="activity_type_walking">螤蔚蟻蟺维蟿畏渭伪</string>
+    <string name="activity_type_swimming">螝慰位蠉渭尾畏蟽畏</string>
+    <string name="activity_type_unknown">螁纬谓蠅蟽蟿畏 未蟻伪蟽蟿畏蟻喂蠈蟿畏蟿伪</string>
+    <string name="activity_summaries">螒胃位畏蟿喂魏苇蟼 螖蟻伪蟽蟿畏蟻喂蠈蟿畏蟿蔚蟼</string>
+    <string name="activity_type_biking">螤慰未畏位伪蟽委伪</string>
+    <string name="activity_type_treadmill">螖喂维未蟻慰渭慰蟼</string>
+    <string name="select_all">螘蟺喂位慰纬萎 蠈位蠅谓</string>
+    <string name="share">螝慰喂谓慰蟺慰委畏蟽畏</string>
+    <string name="reset_index">螘蟺伪谓伪蠁慰蟻维 畏渭蔚蟻慰渭畏谓委伪蟼 位萎蠄畏蟼</string>
+    <string name="pref_title_pebble_gatt_clientonly">渭蠈谓慰 GATT 蟺蔚位维蟿蔚蟼</string>
+    <string name="pref_summary_pebble_gatt_clientonly">螒蠀蟿蠈 蔚委谓伪喂 蟺蔚喂蟻伪渭伪蟿喂魏蠈 蠂伪蟻伪魏蟿畏蟻喂蟽蟿喂魏蠈 魏伪喂 渭蠈谓慰 纬喂伪 蟿慰 Pebble 2 , 未慰魏喂渭维蟽蟿蔚 蟿慰 渭蠈谓慰 伪谓 苇蠂蔚蟿蔚 蟺蟻慰尾位萎渭伪蟿伪 蟽蠀谓未蔚蟽喂渭蠈蟿畏蟿伪蟼</string>
+    <string name="menuitem_status">螝伪蟿维蟽蟿伪蟽畏</string>
+    <string name="menuitem_activity">螖蟻伪蟽蟿畏蟻喂蠈蟿畏蟿伪</string>
+    <string name="menuitem_weather">螝伪喂蟻蠈蟼</string>
+    <string name="menuitem_alarm">螢蠀蟺谓畏蟿萎蟻喂</string>
+    <string name="menuitem_timer">围蟻慰谓蠈渭蔚蟿蟻慰</string>
+    <string name="menuitem_compass">螤蠀尉委未伪</string>
+    <string name="menuitem_settings">巍蠀胃渭委蟽蔚喂蟼</string>
+    <string name="menuitem_alipay">Alipay</string>
+    <string name="devicetype_q8">Q8</string>
+    <string name="blacklist_all_for_notifications">螠伪蠉蟻畏 位委蟽蟿伪 纬喂伪 蠈位蔚蟼 蟿喂蟼 蔚喂未慰蟺慰喂萎蟽蔚喂蟼</string>
+    <string name="whitelist_all_for_notifications">螡伪 蔚喂未慰蟺慰喂萎蟽蔚喂 蠈位蔚蟼 慰喂 蔚喂未慰蟺慰喂萎蟽蔚喂蟼</string>
+    <string name="fw_upgrade_notice_miband3">鈥斘犗佅屛何滴瓜勎蔽� 谓伪 蔚纬魏伪蟿伪蟽蟿萎蟽蔚蟿蔚 蟿慰 位慰纬喂蟽渭喂魏蠈 %s 纬喂伪 蟿慰 Mi Band 3. 
+\n
+\n螤伪蟻伪魏伪位蠋 尾蔚尾伪喂蠅胃蔚委蟿蔚 蠈蟿喂 胃伪 蔚纬魏伪蟿伪蟽蟿萎蟽蔚蟿蔚 蟿慰 伪蟻蠂蔚委慰 .fw 魏伪喂 渭蔚蟿维 蟿慰 伪蟻蠂蔚委慰 .res. 韦慰 smartwatch 胃伪 蔚蟺伪谓蔚魏魏喂谓萎蟽蔚喂 渭蔚蟿维 蟿畏谓 蔚纬魏伪蟿维蟽蟿伪蟽畏 蟿慰蠀 .fw 伪蟻蠂蔚委慰蠀.
+\n
+\n危畏渭蔚委蠅蟽畏: 韦慰 伪蟻蠂蔚委慰 .res 未蔚谓 蠂蟻蔚喂维味蔚蟿伪喂 蔚纬魏伪蟿维蟽蟿伪蟽畏 伪谓 蔚委谓伪喂 伪魏蟻喂尾蠋蟼 蟿慰 委未喂慰 渭蔚 伪蠀蟿蠈 蟺慰蠀 苇蠂蔚喂 蔚纬魏伪蟿伪蟽蟿伪胃蔚委 蟺蟻慰畏纬慰蠉渭蔚谓伪.
+\n
+\n螤巍螣围惟巍螚危韦螘 螠螘 螖螜螝螚 危螒危 螘违螛违螡螚!</string>
+    <string name="pref_auto_fetch">螒蠀蟿蠈渭伪蟿畏 位萎蠄畏 未蔚未慰渭苇谓蠅谓 未蟻伪蟽蟿畏蟻喂蠈蟿畏蟿伪蟼</string>
+    <string name="devicetype_miband3">Mi Band 3</string>
+    <string name="pref_auto_fetch_summary">螚 位萎蠄畏 蟿蠅谓 未蔚未慰渭苇谓蠅谓 纬委谓蔚蟿伪喂 渭蠈谓慰 蠈蟿伪谓 尉蔚魏位蔚喂未蠋蟽蔚蟿蔚 蟿畏谓 慰胃蠈谓畏. 螖慰蠀位蔚蠉蔚喂 渭蠈谓慰 蠈蟿伪谓 苇蠂蔚蟿蔚 慰蟻委蟽蔚喂 苇谓伪谓 蟿蟻蠈蟺慰 魏位蔚喂未蠋渭伪蟿慰蟼 慰胃蠈谓畏蟼!</string>
+    <string name="pref_auto_fetch_limit_fetches">螘位维蠂喂蟽蟿慰蟼 蠂蟻蠈谓慰蟼 渭蔚蟿伪尉蠉 位萎蠄畏蟼 未蔚未慰渭苇谓蠅谓</string>
+    <string name="pref_auto_fetch_limit_fetches_summary">螞萎蠄畏 未蔚未慰渭苇谓蠅谓 魏维胃蔚 %d 位蔚蟺蟿维</string>
+    <string name="controlcenter_calibrate_device">巍蠉胃渭喂蟽畏 (魏伪位喂渭蟺蟻维蟻喂蟽渭伪) 蟽蠀蟽魏蔚蠀萎蟼</string>
+    <string name="prefs_screen_orientation">螤蟻慰蟽伪谓伪蟿慰位喂蟽渭蠈蟼 慰胃蠈谓畏蟼</string>
+    <string name="horizontal">螣蟻喂味蠈谓蟿喂伪</string>
+    <string name="vertical">螝维胃蔚蟿伪</string>
+    <string name="watch9_pairing_tap_hint">螌蟿伪谓 蟿慰 smartwatch 未慰谓蔚委蟿伪喂, 魏慰蠀谓萎蟽蟿蔚 蟿慰 萎 蟺伪蟿萎蟽蟿蔚 蟿慰 魏慰蠀渭蟺委 蟿慰蠀.</string>
+    <string name="mi3_prefs_band_screen_unlock">螢蔚魏位蔚委未蠅渭伪 慰胃蠈谓畏蟼 Mi Band</string>
+    <string name="mi3_prefs_band_screen_unlock_summary">危蠉蟻蔚蟿蔚 蟺蟻慰蟼 蟿伪 蟺维谓蠅 纬喂伪 谓伪 尉蔚魏位蔚喂未蠋蟽蔚蟿蔚 蟿畏谓 慰胃蠈谓畏 蟿慰蠀 Mi Band</string>
+    <string name="russian">巍蠋蟽喂魏伪</string>
+    <string name="german">螕蔚蟻渭伪谓喂魏维</string>
+    <string name="italian">螜蟿伪位喂魏维</string>
+    <string name="french">螕伪位位喂魏维</string>
+    <string name="polish">螤慰位蠅谓喂魏维</string>
+    <string name="korean">螝慰蟻蔚维蟿喂魏伪</string>
+    <string name="japanese">螜伪蟺蠅谓喂魏维</string>
+    <string name="devicetype_mykronoz_zetime">MyKronoz ZeTime</string>
+    <string name="devicetype_id115">ID115</string>
+    <string name="devicetype_watch9">Watch9</string>
+    <string name="menuitem_notifications">螘喂未慰蟺慰喂萎蟽蔚喂蟼</string>
+    <string name="menuitem_music">螠慰蠀蟽喂魏萎</string>
+    <string name="menuitem_more">螤蔚蟻喂蟽蟽蠈蟿蔚蟻伪</string>
+    <string name="watch9_time_minutes">螞蔚蟺蟿维:</string>
+    <string name="watch9_time_hours">螐蟻蔚蟼:</string>
+    <string name="watch9_time_seconds">螖蔚蠀蟿蔚蟻蠈位蔚蟺蟿伪:</string>
+    <string name="watch9_calibration_hint">巍蠀胃渭委蟽蟿蔚 蟿畏 蟽蠀蟽魏蔚蠀萎 蟽伪蟼 谓伪 未蔚委蠂谓蔚喂 蟿畏谓 蠋蟻伪 伪渭苇蟽蠅蟼.</string>
+    <string name="watch9_calibration_button">螒蟻蠂喂魏萎 蟻蠉胃渭喂蟽畏 (魏伪位喂渭蟺蟻维蟻喂蟽渭伪)</string>
+    <string name="title_activity_watch9_pairing">危蠉味蔚蠀尉畏 Watch 9</string>
+    <string name="title_activity_watch9_calibration">螒蟻蠂喂魏萎 蟻蠉胃渭喂蟽畏 (魏伪位喂渭蟺蟻维蟻喂蟽渭伪) Watch 9</string>
+    <string name="pref_title_notifications_timeout">螘位维蠂喂蟽蟿慰蟼 蠂蟻蠈谓慰蟼 渭蔚蟿伪尉蠉 蔚喂未慰蟺慰喂萎蟽蔚蠅谓</string>
+    <string name="no_limit">围蠅蟻委蟼 蠈蟻喂慰</string>
+    <string name="seconds_5">5 未蔚蠀蟿蔚蟻蠈位蔚蟺蟿伪</string>
+    <string name="seconds_10">10 未蔚蠀蟿蔚蟻蠈位蔚蟺蟿伪</string>
+    <string name="seconds_20">20 未蔚蠀蟿蔚蟻蠈位蔚蟺蟿伪</string>
+    <string name="seconds_30">30 未蔚蠀蟿蔚蟻蠈位蔚蟺蟿伪</string>
+    <string name="minutes_1">1 位蔚蟺蟿蠈</string>
+    <string name="minutes_5">5 位蔚蟺蟿维</string>
+    <string name="minutes_10">10 位蔚蟺蟿维</string>
+    <string name="minutes_30">30 位蔚蟺蟿维</string>
+    <string name="you_slept">螝慰喂渭畏胃萎魏伪蟿蔚 伪蟺蠈 %1$s 苇蠅蟼 %2$s</string>
+    <string name="you_did_not_sleep">螖蔚谓 魏慰喂渭畏胃萎魏伪蟿蔚</string>
+    <string name="norwegian_bokmal">螡慰蟻尾畏纬喂魏维 (Bokm氓l)</string>
+    <string name="controlcenter_change_led_color">螒位位伪纬萎 蠂蟻蠋渭伪蟿慰蟼 LED 蔚喂未慰蟺慰喂萎蟽蔚蠅谓</string>
+    <string name="controlcenter_change_fm_frequency">螒位位伪纬萎 蟽蠀蠂谓蠈蟿畏蟿伪蟼 FM</string>
+    <string name="mi3_prefs_night_mode">螞蔚喂蟿慰蠀蟻纬委伪 \"谓蠉蠂蟿伪蟼\"</string>
+    <string name="mi3_prefs_night_mode_summary">螠蔚委蠅蟽畏 蠁蠅蟿蔚喂谓蠈蟿畏蟿伪蟼 蟿畏蟼 慰胃蠈谓畏蟼 蟿慰蠀 Mi band 3 魏伪蟿维 蟿畏 未喂维蟻魏蔚喂伪 蟿畏蟼 谓蠉蠂蟿伪蟼</string>
+    <string name="ok">螘谓蟿维尉蔚喂</string>
+    <string name="mi3_night_mode_sunset">危蟿畏 未蠉蟽畏 蟿慰蠀 畏位委慰蠀</string>
+    <string name="devicetype_roidmi">Roidmi</string>
+    <string name="devicetype_roidmi3">Roidmi3</string>
+    <string name="share_log">螒蟺慰蟽蟿慰位萎 伪蟻蠂蔚委慰蠀 魏伪蟿伪纬蟻伪蠁萎蟼</string>
+    <string name="share_log_warning">螤伪蟻伪魏伪位蠋 苇蠂蔚蟿蔚 蠀蟺\'蠈蠄喂谓 蟽伪蟼 蠈蟿喂 蟿伪 伪蟻蠂蔚委伪 魏伪蟿伪纬蟻伪蠁萎蟼 蟿慰蠀 Gedgetbridge 蔚谓未苇蠂蔚蟿伪喂 谓伪 蟺蔚蟻喂苇蠂慰蠀谓 蟺蟻慰蟽蠅蟺喂魏维 未蔚未慰渭苇谓伪 蟽蠀渭蟺蔚蟻喂位伪渭尾伪谓慰渭苇谓蠅谓 未蔚未慰渭苇谓伪 蠀纬蔚委伪蟼, 渭慰谓伪未喂魏维 蠂伪蟻伪魏蟿畏蟻喂蟽蟿喂魏维 (蠈蟺蠅蟼 未喂蔚蠀胃蠉谓蟽蔚喂蟼 MAC), 渭慰蠀蟽喂魏苇蟼 蔚蟺喂位慰纬苇蟼 魏位蟺. 蟺蟻慰蟽蟺伪胃萎蟽蟿蔚 谓伪 蔚蟺蔚尉蔚蟻纬伪蟽蟿蔚委蟿蔚 蟿慰 伪蟻蠂蔚委慰 魏伪蟿伪纬蟻伪蠁萎蟼 魏伪喂 谓伪 伪蠁伪喂蟻苇蟽蔚蟿蔚 伪蠀蟿苇蟼 蟿喂蟼 蟺位畏蟻慰蠁慰蟻委蔚蟼 蟺蟻喂谓 蟿慰 蟽蟿蔚委位蔚蟿蔚 纬喂伪 蟿畏谓 伪谓伪蠁慰蟻维 蟺蟻慰尾位萎渭伪蟿慰蟼.</string>
+    <string name="warning">螤蟻慰蟽慰蠂萎!</string>
+    <string name="preferences_led_color">围蟻蠋渭伪 LED</string>
+    <string name="preferences_fm_frequency">危蠀蠂谓蠈蟿畏蟿伪 FM</string>
+    <string name="pref_invalid_frequency_title">螠畏 苇纬魏蠀蟻畏 蟽蠀蠂谓蠈蟿畏蟿伪</string>
+    <string name="pref_invalid_frequency_message">螤伪蟻伪魏伪位蠋 蔚喂蟽维纬蔚蟿蔚 蟽蠀蠂谓蠈蟿畏蟿伪 伪蟺蠈 87.5 苇蠅蟼 108.0</string>
+    <string name="pref_title_rtl">螒蟺蠈 未蔚尉喂维 蟺蟻慰蟼 伪蟻喂蟽蟿蔚蟻维</string>
+    <string name="pref_summary_rtl">螘谓蔚蟻纬慰蟺慰喂萎蟽蟿蔚 伪蠀蟿萎 蟿畏谓 蔚蟺喂位慰纬萎 伪谓 畏 蟽蠀蟽魏蔚蠀萎 蟽伪蟼 未蔚谓 苇蠂蔚喂 蠀蟺慰蟽蟿萎蟻喂尉畏 纬喂伪 纬位蠋蟽蟽蔚蟼 蟺慰蠀 纬蟻维蠁慰谓蟿伪喂 伪蟺蠈 未蔚尉喂维 蟺蟻慰蟼 伪蟻喂蟽蟿蔚蟻维</string>
+    <string name="pref_rtl_max_line_length">螠苇纬喂蟽蟿慰 渭萎魏慰蟼 纬蟻伪渭渭萎蟼 蟽蟿喂蟼 纬位蠋蟽蟽蔚蟼 蟺慰蠀 纬蟻维蠁慰谓蟿伪喂 伪蟺蠈 未蔚尉喂维</string>
+    <string name="pref_rtl_max_line_length_summary">螠伪魏蟻伪委谓蔚喂 萎 魏慰谓蟿伪委谓蔚喂 蟿喂蟼 纬蟻伪渭渭苇蟼 蟽蟿喂蟼 纬位蠋蟽蟽蔚蟼 蟺慰蠀 纬蟻维蠁慰谓蟿伪喂 伪蟺蠈 未蔚尉喂维</string>
+    <string name="notif_battery_low">%1$s 蠂伪渭畏位萎 渭蟺伪蟿伪蟻委伪</string>
+    <string name="notif_battery_low_extended">%1$s 蠂伪渭畏位萎 渭蟺伪蟿伪蟻委伪: %2$s</string>
+    <string name="lack_of_sleep">螆位位蔚喂蠄畏 蠉蟺谓慰蠀: %1$s</string>
+    <string name="overslept">螤伪蟻伪蟺维谓蠅 蠉蟺谓慰蟼: %1$s</string>
+    <string name="lack_of_step">螞喂纬蠈蟿蔚蟻伪 尾萎渭伪蟿伪: %1$d</string>
+    <string name="overstep">螤蔚蟻喂蟽蟽蠈蟿蔚蟻伪 尾萎渭伪蟿伪: %1$d</string>
+    <string name="live_activity_max_heart_rate">韦蠅蟻喂谓慰委 / 螠苇纬喂蟽蟿慰喂 蟺伪位渭慰委: %1$d / %2$d</string>
+    <string name="activity_prefs_charts">巍蠀胃渭委蟽蔚喂蟼 纬蟻伪蠁萎渭伪蟿慰蟼</string>
+    <string name="activity_prefs_chart_max_heart_rate">螠苇纬喂蟽蟿慰喂 蟺伪位渭慰委</string>
+    <string name="activity_prefs_chart_min_heart_rate">螘位维蠂喂蟽蟿慰喂 蟺伪位渭慰委</string>
+    <string name="pref_title_contextual_arabic">危蠀渭蠁蟻伪味蠈渭蔚谓伪 螒蟻伪尾喂魏维</string>
+    <string name="pref_summary_contextual_arabic">螘谓蔚蟻纬慰蟺慰喂萎蟽蟿蔚 伪蠀蟿萎 蟿畏谓 蔚蟺喂位慰纬萎 纬喂伪 蠀蟺慰蟽蟿萎蟻喂尉畏 蟽蠀渭蠁蟻伪味蠈渭蔚谓蠅谓 螒蟻伪尾喂魏蠋谓</string>
+    <string name="preferences_rtl_settings">违蟺慰蟽蟿萎蟻喂尉畏 纬位蠅蟽蟽蠋谓 纬蟻伪蠁萎蟼 伪蟺蠈 蟿伪 未蔚尉喂维</string>
+    <string name="no_data">围蠅蟻委蟼 未蔚未慰渭苇谓伪</string>
+    <string name="language_and_region_prefs">巍蠀胃渭委蟽蔚喂蟼 纬位蠋蟽蟽伪蟼 魏伪喂 蟺蔚蟻喂慰蠂萎蟼</string>
+    <string name="debugactivity_really_factoryreset_title">螘蟺伪谓伪蠁慰蟻维 蔚蟻纬慰蟽蟿伪蟽喂伪魏蠋谓 蟻蠀胃渭委蟽蔚蠅谓;</string>
+    <string name="debugactivity_really_factoryreset">螝维谓慰谓蟿伪蟼 蔚蟺伪谓伪蠁慰蟻维 蔚蟻纬慰蟽蟿伪蟽喂伪魏蠋谓 蟻蠀胃渭委蟽蔚蠅谓 蠈位伪 蟿伪 未蔚未慰渭苇谓伪 胃伪 未喂伪纬蟻伪蠁慰蠉谓 伪蟺蠈 蟿畏谓 蟽蠀谓未蔚未蔚渭苇谓畏 蟽蠀蟽魏蔚蠀萎 (伪谓 蠀蟺慰蟽蟿畏蟻委味蔚蟿伪喂). 螘蟺委蟽畏蟼, 慰喂 蟽蠀蟽魏蔚蠀苇蟼 Xiaomi/Huami 蟿蔚委谓慰蠀谓 谓伪 伪位位维味慰蠀谓 蟿畏谓 未喂蔚蠉胃蠀谓蟽畏 MAC 蟽蟿慰 Bluetooth 魏伪喂 畏 蔚蠁伪蟻渭慰纬萎 蟿喂蟼 蔚渭蠁伪谓委味蔚喂 蟽伪谓 渭喂伪 谓苇伪 蟽蠀蟽魏蔚蠀萎.</string>
+    <string name="devicetype_casiogb6900">Casio. GB-6900</string>
+    <string name="title_activity_notification_filter">螞委蟽蟿伪 伪蟺慰/蔚蟺喂位慰纬萎蟼 蔚喂未慰蟺慰喂萎蟽蔚蠅谓</string>
+    <string name="edittext_notification_filter_words_hint">螘喂蟽维纬蔚蟿蔚 蟿喂蟼 位苇尉蔚喂蟼 蟺慰蠀 蔚蟺喂胃蠀渭蔚委蟿蔚, 渭委伪 纬蟻伪渭渭萎 纬喂伪 魏维胃蔚 位苇尉畏</string>
+    <string name="toast_notification_filter_saved_successfully">韦慰 蠁委位蟿蟻慰 蔚喂未慰蟺慰喂萎蟽蔚蠅谓 伪蟺慰胃畏魏蔚蠉蟿畏魏蔚</string>
+    <string name="filter_mode_none">螡伪 渭畏谓 蠀蟺维蟻蠂蔚喂 蠁喂位蟿蟻维蟻喂蟽渭伪</string>
+    <string name="filter_mode_whitelist">螡伪 蠁伪委谓蔚蟿伪喂 蠈蟿伪谓 蔚渭蟺蔚蟻喂苇蠂慰谓蟿伪喂 位苇尉蔚喂蟼</string>
+    <string name="filter_mode_blacklist">螡伪 渭蟺位慰魏维蟻蔚蟿伪喂 蠈蟿伪谓 蔚渭蟺蔚蟻喂苇蠂慰谓蟿伪喂 位苇尉蔚喂蟼</string>
+    <string name="filter_submode_at_least_one">韦慰蠀位维蠂喂蟽蟿慰谓 渭喂伪 伪蟺蠈 蟿喂蟼 位苇尉蔚喂蟼</string>
+    <string name="filter_submode_all">螌位蔚蟼 慰喂 位苇尉蔚喂蟼</string>
+    <string name="toast_notification_filter_words_empty_hint">螤伪蟻伪魏伪位蠋 蔚喂蟽维纬蔚蟿蔚 蟿慰蠀位维蠂喂蟽蟿慰谓 渭喂伪 位苇尉畏</string>
+    <string name="filter_mode">螞蔚喂蟿慰蠀蟻纬委伪 蠁喂位蟿蟻伪蟻委蟽渭伪蟿慰蟼</string>
+    <string name="mode_configuration">巍蠉胃渭喂蟽畏 位蔚喂蟿慰蠀蟻纬委伪蟼</string>
+    <string name="save_configuration">螒蟺慰纬慰萎蟿蔚蠀蟽畏 蟻蠀胃渭委蟽蔚蠅谓</string>
+    <string name="appwidget_not_connected">螖蔚谓 蠀蟺维蟻蠂蔚喂 蟽蠉谓未蔚蟽畏, 蟿慰 尉蠀蟺谓畏蟿萎蟻喂 未蔚谓 蟻蠀胃渭委蟽蟿畏魏蔚.</string>
+    <string name="activity_type_exercise">螒蟽魏畏蟽畏</string>
+    <string name="prefs_disconnect_notification">螘喂未慰蟺慰委畏蟽畏 伪蟺慰蟽蠉谓未蔚蟽畏蟼</string>
+    <string name="zetime_title_settings">巍蠀胃渭委蟽蔚喂蟼 ZeTime</string>
+    <string name="zetime_title_heartrate">螖蔚未慰渭苇谓伪 魏伪蟻未喂伪魏蠋谓 蟺伪位渭蠋谓</string>
+    <string name="zetime_title_screentime">螖喂维蟻魏蔚喂伪 伪谓维渭渭伪蟿慰蟼 慰胃蠈谓畏蟼 (蟽蔚 未蔚蠀蟿蔚蟻蠈位蔚蟺蟿伪)</string>
+    <string name="zetime_title_heart_rate_alarm">蟺蟻慰蔚喂未慰蟺慰委畏蟽畏 魏伪蟻未喂伪魏慰蠉 蟻蠀胃渭慰蠉</string>
+    <string name="zetime_title_heart_rate_alarm_summary">韦慰 smartwatch 蟽伪蟼 胃伪 蟽伪蟼 蔚喂未慰蟺慰喂萎蟽蔚喂 蠈蟿伪谓 慰喂 魏伪蟻未喂伪魏慰委 蟺伪位渭慰委 尉蔚蟺蔚蟻维蟽慰蠀谓 蟿伪 蠈蟻喂伪.</string>
+    <string name="zetime_heart_rate_alarm_enable">螘谓蔚蟻纬慰蟺慰委畏蟽畏 蔚喂未慰蟺慰喂萎蟽蔚蠅谓 慰蟻委蠅谓 魏伪蟻未喂伪魏慰蠉 蟺伪位渭慰蠉</string>
+    <string name="activity_prefs_alarm_max_heart_rate">螠苇纬喂蟽蟿慰蟼 魏伪蟻未喂伪魏蠈蟼 蟺伪位渭蠈蟼</string>
+    <string name="activity_prefs_alarm_min_heart_rate">螘位维蠂喂蟽蟿慰蟼 魏伪蟻未喂伪魏蠈蟼 蟺伪位渭蠈蟼</string>
+    <string name="zetime_analog_mode">螒谓伪位慰纬喂魏萎 位蔚喂蟿慰蠀蟻纬委伪</string>
+    <string name="zetime_analog_mode_hands">螠蠈谓慰 蠂苇蟻喂伪</string>
+    <string name="zetime_analog_mode_handsandsteps">围苇蟻喂伪 魏伪喂 尾萎渭伪蟿伪</string>
+    <string name="zetime_activity_tracking">螤伪蟻伪魏慰位慰蠉胃畏蟽畏 未蟻伪蟽蟿畏蟻喂蠈蟿畏蟿伪蟼</string>
+    <string name="zetime_activity_tracking_summary">螘谓蔚蟻纬慰蟺慰喂蠋谓蟿伪蟼 蟿畏 蟺伪蟻伪魏慰位慰蠉胃畏蟽畏 未蟻伪蟽蟿畏蟻喂蠈蟿畏蟿伪蟼, 胃伪 渭蔚蟿蟻蠋谓蟿伪喂 蟿伪 尾萎渭伪蟿维 蟽伪蟼.</string>
+    <string name="zetime_handmove_display">螝委谓畏蟽畏 蠂蔚蟻喂慰蠉</string>
+    <string name="zetime_handmove_display_summary">螕蠀蟻委蟽蟿蔚 蟿慰谓 魏伪蟻蟺蠈 蟽伪蟼 纬喂伪 谓伪 蔚谓蔚蟻纬慰蟺慰喂萎蟽蔚蟿蔚 萎 谓伪 伪蟺蔚谓蔚蟻纬慰蟺慰喂萎蟽蔚蟿蔚 蟿畏谓 慰胃蠈谓畏.</string>
+    <string name="zetime_calories_type">韦蠉蟺慰蟼 胃蔚蟻渭委未蠅谓</string>
+    <string name="zetime_calories_type_active">螘谓蔚蟻纬苇蟼 渭蠈谓慰 胃蔚蟻渭委未蔚蟼 蟺慰蠀 苇蠂蔚蟿蔚 魏维蠄蔚喂</string>
+    <string name="zetime_calories_type_all">螘谓蔚蟻纬苇蟼 魏伪喂 伪谓蔚谓蔚蟻纬苇蟼 胃蔚蟻渭委未蔚蟼</string>
+    <string name="zetime_date_format">螠慰蟻蠁萎 畏渭蔚蟻慰渭畏谓委伪蟼</string>
+    <string name="zetime_date_format_1">螘螘/螠螠/螚螚</string>
+    <string name="zetime_date_format_2">螚螚/螠螠/螘螘</string>
+    <string name="zetime_date_format_3">螠螠/螚螚/螘螘</string>
+    <string name="zetime_prefs_inactivity_repetitions">螘蟺伪谓伪位萎蠄蔚喂蟼</string>
+    <string name="zetime_prefs_inactivity_mo">螖蔚蠀蟿苇蟻伪</string>
+    <string name="zetime_prefs_inactivity_tu">韦蟻委蟿畏</string>
+    <string name="zetime_prefs_inactivity_we">韦蔚蟿维蟻蟿畏</string>
+    <string name="zetime_prefs_inactivity_th">螤苇渭蟺蟿畏</string>
+    <string name="zetime_prefs_inactivity_fr">螤伪蟻伪蟽魏蔚蠀萎</string>
+    <string name="zetime_prefs_inactivity_sa">危维尾尾伪蟿慰</string>
+    <string name="zetime_prefs_inactivity_su">螝蠀蟻喂伪魏萎</string>
+    <string name="zetime_title_alarm_signaling">螣蟻委蟽蟿蔚 蟿慰谓 蟿蠉蟺慰 蟿畏蟼 蟺蟻慰蔚喂未慰蟺慰委畏蟽畏蟼</string>
+    <string name="zetime_signaling_none">螒胃蠈蟻蠀尾慰</string>
+    <string name="zetime_signaling_vibrate">危蠀谓蔚蠂萎蟼 未蠈谓畏蟽畏</string>
+    <string name="zetime_signaling_beep">危蠀谓蔚蠂苇蟼 渭蟺喂蟺</string>
+    <string name="zetime_signaling_vibrate_beep">危蠀谓蔚蠂萎蟼 未蠈谓畏蟽畏 魏伪喂 渭蟺喂蟺</string>
+    <string name="zetime_signaling_vibrate_once">螖蠈谓畏蟽畏 渭委伪 蠁慰蟻维</string>
+    <string name="zetime_signaling_vibrate_twice">螖蠈谓畏蟽畏 未蠉慰 蠁慰蟻苇蟼</string>
+    <string name="zetime_signaling_beep_once">螠蟺喂蟺 渭喂伪 蠁慰蟻维</string>
+    <string name="zetime_signaling_beep_twice">螠蟺喂蟺 未蠉慰 蠁慰蟻苇蟼</string>
+    <string name="zetime_signaling_vibrate_beep_once">螖蠈谓畏蟽畏 魏伪喂 渭蟺喂蟺 渭委伪 蠁慰蟻维</string>
+    <string name="pref_screen_notification_profile_missed_call">螘喂未慰蟺慰委畏蟽畏 伪谓伪蟺维谓蟿畏蟿蠅谓 魏位萎蟽蔚蠅谓</string>
+    <string name="pref_screen_notification_profile_calendar">螘喂未慰蟺慰委畏蟽畏 畏渭蔚蟻慰位慰纬委慰蠀</string>
+    <string name="pref_screen_notification_profile_inactivity">螘喂未慰蟺慰委畏蟽畏 伪未蟻维谓蔚喂伪蟼</string>
+    <string name="pref_screen_notification_profile_low_power">螤蟻慰蔚喂未慰蟺慰委畏蟽畏 蠂伪渭畏位萎蟼 喂蟽蠂蠉慰蟼</string>
+    <string name="pref_screen_notification_profile_anti_loss">螘喂未慰蟺慰委畏蟽畏 伪蟺蠋位蔚喂伪蟼</string>
+    <string name="interval_fifteen_minutes">魏维胃蔚 15 位蔚蟺蟿维</string>
+    <string name="interval_forty_five_minutes">魏维胃蔚 45 位蔚蟺蟿维</string>
+    <string name="activity_prefs_calories_burnt">螝伪胃畏渭蔚蟻喂谓蠈蟼 蟽蟿蠈蠂慰蟼: 胃蔚蟻渭委未蔚蟼 蟺慰蠀 魏伪委纬慰谓蟿伪喂</string>
+    <string name="activity_prefs_distance_meters">螝伪胃畏渭蔚蟻喂谓蠈蟼 蟽蟿蠈蠂慰蟼: 伪蟺蠈蟽蟿伪蟽畏 蟽蔚 渭苇蟿蟻伪</string>
+    <string name="activity_prefs_activetime_minutes">螝伪胃畏渭蔚蟻喂谓蠈蟼 蟽蟿蠈蠂慰蟼: 蔚谓蔚蟻纬蠈蟼 蠂蟻蠈谓慰蟼 蟽蔚 位蔚蟺蟿维</string>
+    <string name="devicetype_miscale2">Mi Scale 2</string>
+    <string name="pref_title_support_voip_calls">螘谓蔚蟻纬慰蟺慰委畏蟽畏 魏位萎蟽蔚蠅谓 蔚蠁伪蟻渭慰纬萎蟼 VoIP</string>
+    <string name="title_activity_device_specific_settings">螠慰谓伪未喂魏苇蟼 蟻蠀胃渭委蟽蔚喂蟼 蟽蠀蟽魏蔚蠀蠋谓</string>
+    <string name="pref_title_authkey">螝位蔚喂未委 蔚尉慰蠀蟽喂慰未蠈蟿畏蟽畏蟼</string>
+    <string name="pref_summary_authkey">螒位位伪纬萎 蟿慰蠀 魏位蔚喂未喂慰蠉 蔚尉慰蠀蟽喂慰未蠈蟿畏蟽畏蟼 蟽蔚 苇谓伪 魏慰喂谓蠈 纬喂伪 蠈位蔚蟼 蟽伪蟼 蟿喂蟼 蟽蠀蟽魏蔚蠀苇蟼 Android 伪蟺蠈 蟿喂蟼 慰蟺慰委蔚蟼 胃苇位蔚蟿蔚 谓伪 蟽蠀谓未蔚胃蔚委蟿蔚. 韦慰 蟺蟻慰畏纬慰蠉渭蔚谓慰 蟺蟻慰蔚蟺喂位蔚纬渭苇谓慰 魏位蔚喂未委 纬喂伪 蠈位蔚蟼 蟿喂蟼 蟽蠀蟽魏蔚蠀苇蟼 蔚委谓伪喂 0123456789@ABCDE</string>
+    <string name="devicetype_bfh16">BFH - 16</string>
+    <string name="fw_upgrade_notice_amazfitcor2">鈥斘犗佅屛何滴瓜勎蔽� 谓伪 蔚纬魏伪蟿伪蟽蟿萎蟽蔚蟿蔚 蟿慰 蠀位喂魏慰位慰纬喂蟽渭喂魏蠈 %s 蟽蟿慰 Amazfit Cor 2 蟽伪蟼.
+\n
+\n螤伪蟻伪魏伪位蠋 尾蔚尾伪喂蠅胃蔚委蟿蔚 蠈蟿喂 苇蠂蔚蟿蔚 蔚纬魏伪蟿伪蟽蟿萎蟽蔚喂 蟺蟻蠋蟿伪 蟿慰 伪蟻蠂蔚委慰 .fw 魏伪喂 渭蔚蟿维 蟿慰 伪蟻蠂蔚委慰 .res. 螚 蟽蠀蟽魏蔚蠀萎 蟺蟻蠈魏蔚喂蟿伪喂 谓伪 蔚蟺伪谓伪魏喂谓畏胃蔚委 渭蔚蟿维 蟿畏谓 蔚纬魏伪蟿维蟽蟿伪蟽畏 蟿慰蠀 伪蟻蠂蔚委慰蠀 .fw.
+\n
+\n危畏渭蔚委蠅蟽畏: 韦慰 伪蟻蠂蔚委慰 .res 未蔚谓 蠂蟻蔚喂维味蔚蟿伪喂 谓伪 蔚纬魏伪蟿伪蟽蟿伪胃蔚委 尉伪谓维 蔚伪谓 伪谓 蔚委谓伪喂 伪魏蟻喂尾蠋蟼 蟿慰 委未喂慰 渭蔚 伪蠀蟿蠈 蟺慰蠀 苇蠂蔚喂 蔚纬魏伪蟿伪蟽蟿伪胃蔚委 蟺蟻慰畏纬慰蠀渭苇谓蠅蟼.
+\n
+\n螤巍螣围惟巍螚危韦螘 螠螘 螖螜螝螚 危螒危 螘违螛违螡螚!
+\n
+\n螖螘螡 螘围螘螜 螖螣螝螜螠螒危韦螘螜, 螘螒螡 韦螣 螣螡螣螠螒 韦螚危 危违危螝螘违螚危 危螒危 螘螜螡螒螜 \" Amazfit Band 2\" 螤螜螛螒螡螣螡 螡螒 围巍螘螜螒危韦螘螜 螘螕螝螒韦螒危韦螒危螚 螘螡螣危 \"BEATS_W\" 违螞螜螝螣螞螣螕螜危螠螜螝螣违</string>
+    <string name="dutch">螣位位伪谓未喂魏维</string>
+    <string name="turkish">韦慰蠀蟻魏喂魏维</string>
+    <string name="ukrainian">螣蠀魏蟻伪谓喂魏维</string>
+    <string name="arabic">螒蟻伪尾喂魏维</string>
+    <string name="indonesian">螜谓未慰谓畏蟽喂伪魏维</string>
+    <string name="thai">韦伪蠆位伪谓未喂魏维</string>
+    <string name="vietnamese">螔喂蔚蟿谓伪渭苇味喂魏伪</string>
+    <string name="portuguese">螤慰蟻蟿慰纬伪位喂魏维</string>
+    <string name="devicetype_amazfit_cor2">Amazfit Cor 2</string>
+    <string name="devicetype_miband4">Mi Band 4</string>
+    <string name="fw_upgrade_notice_miband4">螤蟻蠈魏蔚喂蟿伪喂 谓伪 蔚纬魏伪蟿伪蟽蟿萎蟽蔚蟿蔚 蟿慰 蠀位喂魏慰位慰纬喂蟽渭喂魏蠈 苇魏未慰蟽畏蟼 %s 蟽蟿慰 Mi Band 4 蟽伪蟼.
+\n
+\n螤伪蟻伪魏伪位蠋 谓伪 蔚委蟽蟿蔚 蟽委纬慰蠀蟻慰喂 蠈蟿喂 胃伪 蔚纬魏伪蟿伪蟽蟿萎蟽蔚蟿蔚 蟿慰 伪蟻蠂蔚委慰 .fw, 魏伪喂 渭蔚蟿维 蟿慰 伪蟻蠂蔚委慰 .res. 韦慰 Mi Band 蟽伪蟼 胃伪 魏维谓蔚喂 蔚蟺伪谓蔚魏魏委谓畏蟽畏 渭蔚蟿维 蟿畏谓 蔚纬魏伪蟿维蟽蟿伪蟽畏 蟿慰蠀 伪蟻蠂蔚委慰蠀 .fw .
+\n
+\n危畏渭蔚委蠅蟽畏: 螖蔚谓 蠂蟻蔚喂维味蔚蟿伪喂 谓伪 蔚纬魏伪蟿伪蟽蟿萎蟽蔚蟿蔚 蟿慰 伪蟻蠂蔚委慰 .res 伪谓 蔚委谓伪喂 伪魏蟻喂尾蠋蟼 蟿慰 委未喂慰 渭蔚 伪蠀蟿蠈 蟺慰蠀 蔚纬魏伪蟿伪蟽蟿维胃畏魏蔚 蟺蟻慰畏纬慰蠀渭苇谓蠅蟼.
+\n
+\n螤巍螣围惟巍螚危韦螘 螠螘 螖螜螝螚 危螒危 螘违螛违螡螚!</string>
+    <string name="prefs_hr_alarm_activity">螘喂未慰蟺慰委畏蟽畏 魏伪蟻未喂伪魏慰蠉 蟻蠀胃渭慰蠉 魏伪蟿维 蟿畏 未喂维蟻魏蔚喂伪 伪胃位畏蟿喂魏萎蟼 未蟻伪蟽蟿畏蟻喂蠈蟿畏蟿伪蟼</string>
+    <string name="prefs_hr_alarm_low">围伪渭畏位蠈 蠈蟻喂慰</string>
+    <string name="prefs_hr_alarm_high">违蠄畏位蠈 蠈蟻喂慰</string>
+    <string name="average">螠苇蟽慰蟼 蠈蟻慰蟼: %1$s</string>
+    <string name="pref_header_charts">巍蠀胃渭委蟽蔚喂蟼 纬蟻伪蠁畏渭维蟿蠅谓</string>
+    <string name="pref_title_charts_average">螘渭蠁维谓喂蟽畏 蟿蠅谓 渭苇蟽蠅谓 蠈蟻蠅谓 蟽蟿伪 未喂伪纬蟻维渭渭伪蟿伪</string>
+    <string name="pref_title_charts_range">螘蠉蟻慰蟼 纬蟻伪蠁畏渭维蟿蠅谓</string>
+    <string name="pref_charts_range_on">韦慰 蔚蠉蟻慰蟼 未喂伪纬蟻伪渭渭维蟿蠅谓 苇蠂蔚喂 慰蟻喂蟽蟿蔚委 蟽蔚 苇谓伪 渭萎谓伪</string>
+    <string name="pref_charts_range_off">韦慰 蔚蠉蟻慰蟼 蟿蠅谓 纬蟻伪蠁畏渭维蟿蠅谓 苇蠂蔚喂 慰蟻喂蟽蟿蔚委 蟽蔚 渭喂伪 螘尾未慰渭维未伪</string>
+    <string name="weekstepschart_steps_a_month">螔萎渭伪蟿伪 伪谓维 渭萎谓伪</string>
+    <string name="weeksleepchart_sleep_a_month">螏蟺谓慰蟼 伪谓维 渭萎谓伪</string>
+    <string name="devicetype_mijia_lywsd02">螆尉蠀蟺谓慰 蟻慰位蠈喂 Mijia</string>
+    <string name="menuitem_nfc">NFC</string>
+    <string name="pref_summary_expose_hr">螘蟺喂蟿蟻苇蟺蔚喂 蟽蔚 维位位蔚蟼 蔚蠁伪蟻渭慰纬苇蟼 谓伪 苇蠂慰蠀谓 蟺蟻蠈蟽尾伪蟽畏 蟽蟿伪 未蔚未慰渭苇谓伪 魏伪蟻未喂伪魏慰蠉 蟺伪位渭慰蠉 蟽蔚 蟺蟻伪纬渭伪蟿喂魏蠈 蠂蟻蠈谓慰, 蔚谓蠋 蟿慰 Gadgetbridge 蔚委谓伪喂 蟽蠀谓未蔚未蔚渭苇谓慰</string>
+    <string name="pref_title_expose_hr">螤蟻蠈蟽尾伪蟽畏 未蔚未慰渭苇谓蠅谓 魏伪蟻未喂伪魏蠋谓 蟺伪位渭蠋谓 蟽蔚 蟺蟻伪纬渭伪蟿喂魏蠈 蠂蟻蠈谓慰 伪蟺蠈 蟿蟻委蟿蔚蟼 蔚蠁伪蟻渭慰纬苇蟼</string>
+    <string name="pref_title_use_custom_font">围蟻萎蟽畏 蟺蟻慰蟽伪蟻渭慰蟽渭苇谓畏蟼 纬蟻伪渭渭伪蟿慰蟽蔚喂蟻维蟼</string>
+    <string name="pref_summary_use_custom_font">螘谓蔚蟻纬慰蟺慰委畏蟽畏 伪蠀蟿萎蟼 蟿畏蟼 蔚蟺喂位慰纬萎蟼 蟽蔚 蟺蔚蟻委蟺蟿蠅蟽畏 蟺慰蠀 畏 蟽蠀蟽魏蔚蠀萎 蟽伪蟼 未喂伪胃苇蟿蔚喂 蠀蟺慰蟽蟿萎蟻喂尉畏 蟺蟻慰蟽伪蟻渭慰蟽渭苇谓畏蟼 纬蟻伪渭渭伪蟿慰蟽蔚喂蟻维蟼 纬喂伪 蠀蟺慰蟽蟿萎蟻喂尉畏 emoji</string>
+    <string name="activity_db_management_autoexport_explanation">螚 蟿慰蟺慰胃蔚蟽委伪 伪蠀蟿蠈渭伪蟿畏蟼 蔚尉伪纬蠅纬萎蟼 尾维蟽畏蟼 未蔚未慰渭苇谓蠅谓 苇蠂蔚喂 蟻蠀胃渭喂蟽蟿蔚委 蟽蔚:</string>
+    <string name="activity_db_management_autoexport_label">螒蠀蟿蠈渭伪蟿畏 蔚尉伪纬蠅纬萎</string>
+    <string name="activity_DB_ExportButton">螘尉伪纬蠅纬萎 未蔚未慰渭苇谓蠅谓</string>
+    <string name="activity_DB_import_button">螘喂蟽伪纬蠅纬萎 未蔚未慰渭苇谓蠅谓</string>
+    <string name="activity_DB_test_export_button">螘魏蟿苇位蔚蟽畏 伪蠀蟿蠈渭伪蟿畏蟼 蔚尉伪纬蠅纬萎蟼 蟿蠋蟻伪</string>
+    <string name="activity_DB_test_export_message">螘尉伪纬蠅纬萎 尾维蟽畏蟼 未蔚未慰渭苇谓蠅谓鈥�</string>
+    <string name="activity_DB_delete_legacy_button">螖喂伪纬蟻伪蠁萎 蟺伪位喂维蟼 尾维蟽畏蟼 未蔚未慰渭苇谓蠅谓</string>
+    <string name="activity_DB_empty_button">螁未蔚喂伪蟽渭伪 尾维蟽畏蟼 未蔚未慰渭苇谓蠅谓</string>
+    <string name="activity_db_management_empty_DB">螁未蔚喂伪 尾维蟽畏 未蔚未慰渭苇谓蠅谓</string>
+    <string name="activity_db_management_exportimport_label">螘尉伪纬蠅纬萎 魏伪喂 蔚喂蟽伪纬蠅纬萎</string>
+    <string name="activity_db_management_empty_db_warning">螤蟻慰蔚喂未慰蟺慰委畏蟽畏! 螤伪蟿蠋谓蟿伪蟼 伪蠀蟿蠈 蟿慰 魏慰蠀渭蟺委 胃伪 未喂伪纬蟻维蠄蔚蟿蔚 蟿畏 尾维蟽畏 未蔚未慰渭苇谓蠅谓 蟽伪蟼 魏伪喂 胃伪 尉蔚魏喂谓萎蟽蔚蟿蔚 伪蟺蠈 蟿畏谓 伪蟻蠂萎.</string>
+    <string name="appwidget_sleep_alarm_widget_label">螢蠀蟺谓畏蟿萎蟻喂 蠉蟺谓慰蠀</string>
+    <string name="widget_listing_label">螝伪蟿维蟽蟿伪蟽畏 魏伪喂 蟽蠀谓伪纬蔚蟻渭慰委</string>
+    <string name="widget_set_alarm_after">巍蠀胃渭委蟽蟿蔚 蟿慰 尉蠀蟺谓畏蟿萎蟻喂 渭蔚蟿维 伪蟺蠈:</string>
+    <string name="widget_5_minutes">5 位蔚蟺蟿维</string>
+    <string name="widget_10_minutes">10 位蔚蟺蟿维</string>
+    <string name="widget_20_minutes">20 位蔚蟺蟿维</string>
+    <string name="widget_1_hour">1 蠋蟻伪</string>
+    <string name="mi2_prefs_button_press_broadcast_default_value" translatable="false">nodomain.freeyourgadget.gadgetbridge.ButtonPressed</string>
+    <string name="pref_title_device_spec_settings_force_time">螘尉伪谓伪纬魏伪蟽蟿喂魏蠈蟼 蟽蠀纬蠂蟻慰谓喂蟽渭蠈蟼 蠂蟻蠈谓慰蠀</string>
+    <string name="pref_header_device_spec_settings">巍蠀胃渭委蟽蔚喂蟼 危蠀蟽魏蔚蠀萎蟼</string>
+    <string name="pref_summary_notifications_and_calls_title_shake_reject">螒谓蟿喂纬蟻伪蠁萎 魏慰蠀渭蟺喂慰蠉 蔚谓苇蟻纬蔚喂伪蟼 蟽蠀蟽魏蔚蠀苇蟼</string>
+    <string name="prefs_notifications_and_calls_shake_reject">螒纬谓蠈畏蟽畏/螒蟺蠈蟻蟻喂蠄畏 渭蔚 蟿慰 魏慰蠉谓畏渭伪 蟿慰蠀 魏伪蟻蟺慰蠉</string>
+    <string name="pref_summary_notifications_and_calls_title_reject">Off - 伪纬谓蠈畏蟽畏, On - 伪蟺蠈蟻蟻喂蠄畏</string>
+    <string name="pref_header_notifications_and_calls_callhandling">围蔚喂蟻喂蟽渭蠈蟼 魏位萎蟽蔚蠅谓</string>
+    <string name="pref_title_notifications_and_calls_repeat_on_misscall">螘蟺伪谓维位畏蠄畏 纬喂伪 X 位蔚蟺蟿维</string>
+    <string name="pref_summary_notifications_and_calls_enable_misscall">螘蟺伪谓维位畏蠄畏 魏维胃蔚 位蔚蟺蟿蠈</string>
+    <string name="pref_notifications_and_calls_enable_misscall">螘喂未慰蟺慰委畏蟽畏 伪谓伪蟺维谓蟿畏蟿畏蟼 魏位萎蟽畏蟼</string>
+    <string name="prefs_notifications_and_calls_continious_ring">螘喂未慰蟺慰委畏蟽畏 魏伪蟿维 蟿畏 未喂维蟻魏蔚喂伪 蔚喂蟽蔚蟻蠂蠈渭蔚谓畏蟼 魏位萎蟽蔚喂蟼</string>
+    <string name="pref_title_notifications_and_calls_repeat_on_call">螘蟺伪谓苇位伪尾蔚 蟿喂蟼 蔚喂未慰蟺慰喂萎蟽蔚喂蟼 魏位萎蟽蔚蠅谓</string>
+    <string name="pref_header_notifications_and_calls">螝位萎蟽蔚喂蟼 魏伪喂 蔚喂未慰蟺慰喂萎蟽蔚喂蟼</string>
+    <string name="title_activity_LenovoWatch_calibration">Watch X Plus 尾伪胃渭慰谓蠈渭畏蟽畏</string>
+    <string name="pref_display_add_device_fab_off">螣蟻伪蟿蠈 渭慰谓维蠂伪 蠈蟿伪谓 畏 蟽蠀蟽魏蔚蠀萎 苇蠂蔚喂 蟺蟻慰蟽蟿蔚胃蔚委</string>
+    <string name="pref_display_add_device_fab_on">螤维谓蟿慰蟿蔚 慰蟻伪蟿蠈</string>
+    <string name="pref_display_add_device_fab">螝慰蠀渭蟺委 蟽蠉谓未蔚蟽畏蟼 谓苇伪蟼 蟽蠀蟽魏蔚蠀萎蟼</string>
+    <string name="pref_title_vibration_strength">螆谓蟿伪蟽畏 未蠈谓畏蟽畏蟼</string>
+    <string name="pref_title_relax_firmware_checks">螘谓蔚蟻纬慰蟺慰喂萎蟽蟿蔚 伪蠀蟿蠈 蔚维谓 蔚蟺喂胃蠀渭蔚委蟿蔚 谓伪 蔚纬魏伪蟿伪蟽蟿萎蟽蔚蟿蔚 苇谓伪 蠀位喂魏慰位慰纬喂蟽渭喂魏蠈 蟿慰 慰蟺慰委慰 未蔚谓 蟺蟻慰慰蟻委味蔚蟿伪喂 纬喂伪 蟿畏谓 蟽蠀蟽魏蔚蠀萎 蟽伪蟼 (蔚谓蔚蟻纬萎蟽蟿蔚 渭蔚 未喂魏萎 蟽伪蟼 蔚蠀胃蠉谓畏)</string>
+    <string name="pref_summary_relax_firmware_checks">围伪位伪蟻慰委 苇位蔚纬蠂慰喂 蠀位喂魏慰位慰纬喂蟽渭喂魏慰蠉</string>
+    <string name="pref_title_sync_caldendar">危蠀纬蠂蟻慰谓喂蟽渭蠈蟼 蟽蠀渭尾维谓蟿蠅谓 畏渭蔚蟻慰位慰纬委慰蠀</string>
+    <string name="pref_summary_sync_calendar">螘谓蔚蟻纬慰蟺慰喂蔚委 蟿喂蟼 蔚喂未慰蟺慰喂萎蟽蔚喂蟼 畏渭蔚蟻慰位慰纬委慰蠀 伪魏蠈渭伪 魏伪喂 蠈蟿伪谓 畏 蟽蠀蟽魏蔚蠀萎 未蔚谓 蔚委谓伪喂 蟽蠀谓未蔚未蔚渭苇谓畏</string>
+    <string name="pref_summary_allow_high_mtu">螒蠀尉维谓蔚喂 蟿畏谓 蟿伪蠂蠉蟿畏蟿伪 渭蔚蟿伪蠁慰蟻维蟼, 伪位位维 渭蟺慰蟻蔚委 谓伪 渭畏谓 位蔚喂蟿慰蠀蟻纬蔚委 纬喂伪 渭蔚蟻喂魏苇蟼 Android 蟽蠀蟽魏蔚蠀苇蟼.</string>
+    <string name="pref_title_allow_high_mtu">违蠄畏位蠈 MTU</string>
+    <string name="pref_summary_custom_deviceicon">螘渭蠁维谓喂蟽畏 蔚谓蠈蟼 蟽蠀纬魏蔚魏蟻喂渭苇谓慰蠀 蔚喂魏慰谓喂未委慰蠀 蔚喂未慰蟺慰委畏蟽畏蟼 Android 伪谓蟿委 纬喂伪 蟿慰 蔚喂魏慰谓委未喂慰 蟿慰蠀 Gadgetbridge 蠈蟿伪谓 畏 蟽蠀蟽魏蔚蠀萎 蔚委谓伪喂 蟽蠀谓未蔚未蔚渭苇谓畏</string>
+    <string name="pref_title_custom_deviceicon">螘渭蠁维谓喂蟽畏 蟽蠀纬魏蔚魏蟻喂渭苇谓蠅谓 蔚喂魏慰谓喂未委蠅谓 蔚喂未慰蟺慰喂萎蟽蔚蠅谓 蟽蠀蟽魏蔚蠀苇蟼</string>
+    <string name="pref_theme_system">危蠉蟽蟿畏渭伪</string>
+    <string name="fw_upgrade_notice_amazfitgtr">鈥斘犗佅屛何滴瓜勎蔽� 谓伪 蔚纬魏伪蟿伪蟽蟿萎蟽蔚蟿蔚 蟿慰 蠀位喂魏慰位慰纬喂蟽渭喂魏蠈 %s 蟽蟿慰 Amazfit GTR 蟽伪蟼. 
+\n 
+\n螤伪蟻伪魏伪位蠋 尾蔚尾伪喂蠅胃蔚委蟿蔚 蠈蟿喂 苇蠂蔚蟿蔚 蔚纬魏伪蟿伪蟽蟿萎蟽蔚喂 蟺蟻蠋蟿伪 蟿慰 伪蟻蠂蔚委慰 .fw, 蟽蟿畏 蟽蠀谓苇蠂蔚喂伪 蟿慰 伪蟻蠂蔚委慰 .res 魏伪喂 蟿苇位慰蟼 蟿慰 伪蟻蠂蔚委慰 .gps. 螚 蟽蠀蟽魏蔚蠀萎 蟺蟻蠈魏蔚喂蟿伪喂 谓伪 蔚蟺伪谓伪魏喂谓畏胃蔚委 渭蔚蟿维 蟿畏谓 蔚纬魏伪蟿维蟽蟿伪蟽畏 蟿慰蠀 伪蟻蠂蔚委慰蠀 .fw.
+\n 
+\n危畏渭蔚委蠅蟽畏: 韦慰 伪蟻蠂蔚委慰.res 魏伪喂 .gps 未蔚谓 蠂蟻蔚喂维味蔚蟿伪喂 谓伪 蔚纬魏伪蟿伪蟽蟿伪胃蔚委 尉伪谓维 蔚伪谓 伪谓 蔚委谓伪喂 伪魏蟻喂尾蠋蟼 蟿慰 委未喂伪 渭蔚 伪蠀蟿维 蟺慰蠀 苇蠂慰蠀谓 蔚纬魏伪蟿伪蟽蟿伪胃蔚委 蟺蟻慰畏纬慰蠀渭苇谓蠅蟼. 
+\n 
+\n螘螡螘巍螕螇危螘韦螘 螠螘 螖螜螝螇 危螒危 螘违螛螏螡螚!</string>
+    <string name="fw_upgrade_notice_amazfitbip_lite">鈥斘犗佅屛何滴瓜勎蔽� 谓伪 蔚纬魏伪蟿伪蟽蟿萎蟽蔚蟿蔚 蟿慰 蠀位喂魏慰位慰纬喂蟽渭喂魏蠈 %s 蟽蟿慰 Amazfit Bip Lite 蟽伪蟼.
+\n
+\n螤伪蟻伪魏伪位蠋 尾蔚尾伪喂蠅胃蔚委蟿蔚 蠈蟿喂 苇蠂蔚蟿蔚 蔚纬魏伪蟿伪蟽蟿萎蟽蔚喂 蟺蟻蠋蟿伪 蟿慰 伪蟻蠂蔚委慰 .fw 魏伪喂 蟽蟿畏 蟽蠀谓苇蠂蔚喂伪 蟿慰 伪蟻蠂蔚委慰 .res. 螚 蟽蠀蟽魏蔚蠀萎 蟺蟻蠈魏蔚喂蟿伪喂 谓伪 蔚蟺伪谓伪魏喂谓畏胃蔚委 渭蔚蟿维 蟿畏谓 蔚纬魏伪蟿维蟽蟿伪蟽畏 蟿慰蠀 伪蟻蠂蔚委慰蠀 .fw.
+\n
+\n危畏渭蔚委蠅蟽畏: 韦慰 伪蟻蠂蔚委慰.res 未蔚谓 蠂蟻蔚喂维味蔚蟿伪喂 谓伪 蔚纬魏伪蟿伪蟽蟿伪胃蔚委 尉伪谓维 蔚伪谓 伪谓 蔚委谓伪喂 伪魏蟻喂尾蠋蟼 蟿慰 委未喂慰 渭蔚 伪蠀蟿蠈 蟺慰蠀 苇蠂蔚喂 蔚纬魏伪蟿伪蟽蟿伪胃蔚委 蟺蟻慰畏纬慰蠀渭苇谓蠅蟼.
+\n
+\n螤巍螣围惟巍螚危韦螘 螠螘 螖螜螝螚 危螒危 螘违螛违螡螚!</string>
+    <string name="fw_upgrade_notice_amazfitgts">鈥斘犗佅屛何滴瓜勎蔽� 谓伪 蔚纬魏伪蟿伪蟽蟿萎蟽蔚蟿蔚 蟿慰 蠀位喂魏慰位慰纬喂蟽渭喂魏蠈 %s 蟽蟿慰 Amazfit GTS 蟽伪蟼. 
+\n 
+\n螤伪蟻伪魏伪位蠋 尾蔚尾伪喂蠅胃蔚委蟿蔚 蠈蟿喂 苇蠂蔚蟿蔚 蔚纬魏伪蟿伪蟽蟿萎蟽蔚喂 蟺蟻蠋蟿伪 蟿慰 伪蟻蠂蔚委慰 .fw, 蟽蟿畏 蟽蠀谓苇蠂蔚喂伪 蟿慰 伪蟻蠂蔚委慰 .res 魏伪喂 蟿苇位慰蟼 蟿慰 伪蟻蠂蔚委慰 .gps. 螚 蟽蠀蟽魏蔚蠀萎 蟺蟻蠈魏蔚喂蟿伪喂 谓伪 蔚蟺伪谓伪魏喂谓畏胃蔚委 渭蔚蟿维 蟿畏谓 蔚纬魏伪蟿维蟽蟿伪蟽畏 蟿慰蠀 伪蟻蠂蔚委慰蠀 .fw.
+\n 
+\n危畏渭蔚委蠅蟽畏: 韦慰 伪蟻蠂蔚委慰.res 魏伪喂 .gps 未蔚谓 蠂蟻蔚喂维味蔚蟿伪喂 谓伪 蔚纬魏伪蟿伪蟽蟿伪胃蔚委 尉伪谓维 蔚伪谓 伪谓 蔚委谓伪喂 伪魏蟻喂尾蠋蟼 蟿慰 委未喂伪 渭蔚 伪蠀蟿维 蟺慰蠀 苇蠂慰蠀谓 蔚纬魏伪蟿伪蟽蟿伪胃蔚委 蟺蟻慰畏纬慰蠀渭苇谓蠅蟼. 
+\n 
+\n螘螡螘巍螕螇危螘韦螘 螠螘 螖螜螝螇 危螒危 螘违螛螏螡螚!</string>
+    <string name="pref_title_force_white_color_scheme">螘蟺喂尾慰位萎 渭伪蠉蟻慰蠀 蟽蔚 位蔚蠀魏蠈 蟽蠀谓未蠀伪蟽渭蠈 蠂蟻蠅渭维蟿蠅谓</string>
+    <plurals name="widget_alarm_target_hours">
+        <item quantity="one">%d 蠋蟻伪</item>
+        <item quantity="other">%d 蠋蟻蔚蟼</item>
+    </plurals>
+    <string name="hr_widget_last_notification">韦蔚位蔚蠀蟿伪委伪 蔚喂未慰蟺慰委畏蟽畏</string>
+    <string name="pref_qhybrid_save_raw_activity_files">螒蟺慰胃萎魏蔚蠀蟽畏 渭畏 蔚蟺蔚尉蔚蟻纬伪蟽渭苇谓蠅谓 伪蟻蠂蔚委蠅谓 未蟻伪蟽蟿畏蟻喂蠈蟿畏蟿伪蟼</string>
+    <string name="pref_qhybrid_title_widget_draw_circles">危蠂蔚未委伪蟽畏 魏蠉魏位蠅谓 纬蟻伪蠁喂魏蠋谓 蟽蟿慰喂蠂蔚委蠅谓</string>
+    <string name="error_no_location_access">螤蟻苇蟺蔚喂 谓伪 蠂慰蟻畏纬畏胃蔚委 维未蔚喂伪 纬喂伪 蟺蟻蠈蟽尾伪蟽畏 蟽蔚 未蔚未慰渭苇谓伪 蟿慰蟺慰胃蔚蟽委伪蟼 纬喂伪 谓伪 蔚委谓伪喂 未蠀谓伪蟿萎 畏 位蔚喂蟿慰蠀蟻纬委伪 蟿畏蟼 蟽维蟻蠅蟽畏蟼</string>
+    <string name="prefs_button_long_press_action_selection_title">螘谓苇蟻纬蔚喂伪 蟺伪蟻伪蟿蔚蟿伪渭苇谓慰蠀 蟺伪蟿萎渭伪蟿慰蟼 魏慰蠀渭蟺喂慰蠉</string>
+    <string name="prefs_button_variable_actions">螞蔚蟺蟿慰渭蔚蟻蔚委蟼 蟻蠀胃渭委蟽蔚喂蟼 蟺伪蟿畏渭维蟿蠅谓 魏慰蠀渭蟺喂慰蠉</string>
+    <string name="prefs_button_triple_press_action_selection_title">螘谓苇蟻纬蔚喂伪 纬蔚纬慰谓蠈蟿慰蟼3</string>
+    <string name="prefs_button_double_press_action_selection_title">螘谓苇蟻纬蔚喂伪 纬蔚纬慰谓蠈蟿慰蟼2</string>
+    <string name="prefs_button_single_press_action_selection_title">螘谓苇蟻纬蔚喂伪 纬蔚纬慰谓蠈蟿慰蟼1</string>
+    <string name="hr_appname_commute">螠蔚蟿伪魏委谓畏蟽畏</string>
+    <string name="hr_appname_stopwatch">围蟻慰谓蠈渭蔚蟿蟻慰</string>
+    <string name="hr_appname_workout">螤蟻慰蟺蠈谓畏蟽畏</string>
+    <string name="hr_appname_wellness">螘蠀蔚尉委伪</string>
+    <string name="hr_widget_nothing">韦委蟺慰蟿伪</string>
+    <string name="hr_widget_weather">螝伪喂蟻蠈蟼</string>
+    <string name="hr_widget_battery">螠蟺伪蟿伪蟻委伪</string>
+    <string name="hr_widget_calories">螛蔚蟻渭委未蔚蟼</string>
+    <string name="hr_widget_active_minutes">螘谓蔚蟻纬维 位蔚蟺蟿维</string>
+    <string name="hr_widget_date">螚渭蔚蟻慰渭畏谓委伪</string>
+    <string name="hr_widget_steps">螔萎渭伪蟿伪</string>
+    <string name="hr_widget_heart_rate">螝伪蟻未喂伪魏慰委 蟺伪位渭慰委</string>
+    <string name="qhybrid_offset_time_by">蠂蟻蠈谓慰蟼 渭蔚蟿伪蟿蠈蟺喂蟽畏蟼 魏伪蟿维</string>
+    <string name="qhybrid_offset_timezone">渭蔚蟿伪蟿蠈蟺喂蟽畏 味蠋谓畏蟼 蠋蟻伪蟼 魏伪蟿维</string>
+    <string name="qhybrid_changes_delay_prompt">畏 伪位位伪纬萎 渭蟺慰蟻蔚委 谓伪 未喂伪蟻魏苇蟽蔚喂 渭蔚蟻喂魏维 未蔚蠀蟿蔚蟻蠈位蔚蟺蟿伪 鈥�</string>
+    <string name="qhybrid_buttons_overwrite_error">危蠁维位渭伪 蟽蟿畏谓 伪谓蟿喂魏伪蟿维蟽蟿伪蟽畏 魏慰蠀渭蟺喂蠋谓</string>
+    <string name="qhybrid_buttons_overwrite_success">韦伪 魏慰蠀渭蟺喂维 伪谓蟿喂魏伪蟿伪蟽蟿维胃畏魏伪谓</string>
+    <string name="qhybrid_prompt_million_steps">螤伪蟻伪魏伪位蠋 蟻蠀胃渭委蟽蟿蔚 蟿慰谓 伪蟻喂胃渭蠈 尾畏渭维蟿蠅谓 蟽蟿慰 1 蔚魏伪蟿慰渭渭蠉蟻喂慰 纬喂伪 谓伪 蔚谓蔚蟻纬慰蟺慰喂萎蟽蔚蟿蔚 伪蠀蟿蠈.</string>
+    <string name="qhybrid_use_activity_hand_as_notification_counter">蠂蟻萎蟽畏 未蟻伪蟽蟿畏蟻喂蠈蟿畏蟿伪蟼 蠅蟼 渭蔚蟿蟻畏蟿萎 蔚喂未慰蟺慰喂萎蟽蔚蠅谓</string>
+    <string name="qhybrid_overwrite_buttons">伪谓蟿喂魏伪蟿维蟽蟿伪蟽畏 魏慰蠀渭蟺喂蠋谓</string>
+    <string name="qhybrid_second_timezone_offset_relative_to_utc">未蔚蠉蟿蔚蟻畏 味蠋谓畏 蠋蟻伪蟼 渭蔚蟿伪蟿慰蟺喂蟽渭苇谓畏 蟽蔚 蟽蠂苇蟽畏 渭蔚 UTC</string>
+    <string name="qhybrid_time_shift">蠂蟻慰谓喂魏萎 渭蔚蟿伪蟿蠈蟺喂蟽畏</string>
+    <string name="qhybrid_goal_in_steps">危蟿蠈蠂慰蟼 尾畏渭维蟿蠅谓</string>
+    <string name="qhybrid_vibration_strength">螆谓蟿伪蟽畏 未蠈谓畏蟽畏蟼:</string>
+    <string name="watch_not_connected">韦慰 蟻慰位蠈喂 未蔚谓 蔚委谓伪喂 蟽蠀谓未蔚未蔚渭苇谓慰</string>
+    <string name="menuitem_unknown">螁纬谓蠅蟽蟿慰</string>
+    <string name="menuitem_workout">螤蟻慰蟺蠈谓畏蟽畏</string>
+    <string name="menuitem_eventreminder">违蟺蔚谓胃蠉渭喂蟽畏 危蠀渭尾维谓蟿慰蟼</string>
+    <string name="menuitem_hr">螝伪蟻未喂伪魏慰委 蟺伪位渭慰委</string>
+    <string name="menuitem_pai">PAI (螤蟻慰蟽蠅蟺喂魏萎 螘蠀蠁蠀螑伪 螖蟻伪蟽蟿畏蟻喂蠈蟿畏蟿伪蟼)</string>
+    <string name="preferences_qhybrid_settings">巍蠀胃渭委蟽蔚喂蟼 Q Hybrid</string>
+    <string name="devicetype_amazfit_gts">Amazfit GTR</string>
+    <string name="notification_channel_high_priority_name">螣喂 蔚喂未慰蟺慰喂萎蟽蔚喂蟼 蟿慰蠀 Gadgetbridge 蠀蠄畏位萎蟼 蟺蟻慰蟿蔚蟻伪喂蠈蟿畏蟿伪蟼</string>
+    <string name="devicetype_banglejs">Bangle.js</string>
+    <string name="devicetype_makibes_hr3">Makibes HR3</string>
+    <string name="devicetype_itag">iTag</string>
+    <string name="devicetype_y5">Y5</string>
+    <string name="devicetype_watchxplus">Watch 围 Plus</string>
+    <string name="devicetype_watchx">Watch 围</string>
+    <string name="devicetype_qhybrid">Fossil Q Hybrid</string>
+    <string name="devicetype_amazfit_bips">Amazfit Bip S</string>
+    <string name="devicetype_amazfit_gtr">Amazfit GTR</string>
+    <string name="devicetype_amazfit_bip_lite">Amazfit Bip Lite</string>
+    <string name="activity_error_no_app_for_gpx">螕喂伪 谓伪 蟺蟻慰尾维位蔚蟿蔚 委蠂谓畏 未蟻伪蟽蟿畏蟻喂蠈蟿畏蟿伪蟼, 蔚纬魏伪蟿伪蟽蟿蔚委蟽蟿蔚 蔚蠁伪蟻渭慰纬萎 蟺慰蠀 谓伪 渭蟺慰蟻蔚委 谓伪 蠂蔚喂蟻喂蟽蟿蔚委 伪蟻蠂蔚委伪 GPX.</string>
+    <string name="maximum_duration">螖喂维蟻魏蔚喂伪</string>
+    <string name="find_lost_device_you_found_it">韦慰 尾蟻萎魏伪!</string>
+    <string name="pref_chart_sleep_rolling_24_off">螒蟺蠈 渭蔚蟽畏渭苇蟻喂 蟽蔚 渭蔚蟽畏渭苇蟻喂</string>
+    <string name="pref_chart_sleep_rolling_24_on">韦蔚位蔚蠀蟿伪委蔚蟼 24 蠋蟻蔚蟼</string>
+    <string name="pref_title_chart_sleep_rolling_24_hour">螘蠉蟻慰蟼 蠉蟺谓慰蠀</string>
+    <string name="pref_title_chart_heartrate_color">围蟻蠋渭伪 未喂伪纬蟻维渭渭伪蟿慰蟼 魏伪蟻未喂伪魏蠋谓 蟺伪位渭蠋谓</string>
+    <string name="pref_chart_heartrate_color_orange">螤慰蟻蟿慰魏伪位委</string>
+    <string name="pref_chart_heartrate_color_red">螝蠈魏魏喂谓慰</string>
+    <string name="czesh">韦蟽蔚蠂喂魏维</string>
+    <string name="swedish">危慰蠀畏未喂魏维</string>
+    <string name="hebrew">螘尾蟻伪蠆魏维</string>
+    <string name="greek">螘位位畏谓喂魏维</string>
+    <string name="hungarian">螣蠀纬纬蟻喂魏维</string>
+    <string name="romanian">巍慰蠀渭维谓喂魏伪</string>
+    <string name="pref_summary_force_white_color_scheme">围蟻萎蟽喂渭慰 伪谓 蟿慰 蟻慰位蠈喂 苇蠂蔚喂 蟽魏慰蠉蟻慰蠀蟼 未蔚委魏蟿蔚蟼</string>
+    <string name="bip_prefs_shotcuts_summary">螘蟺喂位苇尉蟿蔚 蟿喂蟼 蟽蠀谓蟿慰渭蔚蠉蟽蔚喂蟼 蟽蟿畏谓 慰胃蠈谓畏 蟿慰蠀 蟻慰位慰纬喂慰蠉</string>
+    <string name="bip_prefs_shortcuts">危蠀谓蟿慰渭蔚蠉蟽蔚喂蟼</string>
+    <string name="prefs_find_phone_duration">螖喂维蟻魏蔚喂伪 萎蠂慰蠀 魏位萎蟽畏蟼 蟽蔚 未蔚蠀蟿蔚蟻蠈位蔚蟺蟿伪</string>
+    <string name="prefs_find_phone_summary">围蟻畏蟽喂渭慰蟺慰喂萎蟽蟿蔚 蟿慰 蟻慰位蠈喂 蟽伪蟼 纬喂伪 谓伪 蔚谓蔚蟻纬慰蟺慰喂畏胃蔚委 慰 萎蠂慰蟼 魏位萎蟽畏蟼 蟿慰蠀 蟿畏位蔚蠁蠋谓慰蠀 蟽伪蟼.</string>
+    <string name="prefs_enable_find_phone">螘谓蔚蟻纬慰蟺慰委畏蟽畏 \'螘蠉蟻蔚蟽畏蟼 蟿畏位蔚蠁蠋谓慰蠀\'</string>
+    <string name="prefs_find_phone">螘蠉蟻蔚蟽畏 蟿畏位蔚蠁蠋谓慰蠀</string>
+    <string name="alarm_snooze">螒谓伪尾慰位萎</string>
+    <string name="discovery_need_to_enter_authkey">螒蠀蟿萎 畏 蟽蠀蟽魏蔚蠀萎 蠂蟻蔚喂维味蔚蟿伪喂 苇谓伪 渭蠀蟽蟿喂魏蠈 魏位蔚喂未委 蟺喂蟽蟿慰蟺慰委畏蟽畏蟼, 蟺伪蟿萎蟽蟿蔚 蟺伪蟻伪蟿蔚蟿伪渭苇谓伪 蟽蟿畏 蟽蠀蟽魏蔚蠀萎 纬喂伪 谓伪 蟿慰 蔚喂蟽维纬蔚蟿蔚. 螖喂维尾伪蟽蔚 蟿慰 wiki.</string>
+    <string name="find_my_phone_notification">螘蠉蟻蔚蟽畏 蟿慰蠀 蟿畏位蔚蠁蠋谓慰蠀</string>
+    <string name="pref_summary_disable_new_ble_scanning">螘谓蔚蟻纬慰蟺慰喂萎蟽蟿蔚 伪蠀蟿萎谓 蟿畏谓 蔚蟺喂位慰纬萎 伪谓 畏 蟽蠀蟽魏蔚蠀萎 蟽伪蟼 未蔚谓 蔚委谓伪喂 未蠀谓伪蟿蠈 谓伪 尾蟻蔚胃蔚委 魏伪蟿维 蟿慰谓 伪蟻蠂喂魏蠈 蔚谓蟿慰蟺喂蟽渭蠈</string>
+    <string name="pref_disable_new_ble_scanning">螒蟺蔚谓蔚蟻纬慰蟺慰委畏蟽畏 谓苇伪蟼 蟽维蟻蠅蟽畏蟼 BLE</string>
+    <string name="pref_header_auto_fetch">螒蠀蟿蠈渭伪蟿畏 位萎蠄畏</string>
+    <string name="preferences_makibes_hr3_settings">巍蠀胃渭委蟽蔚喂蟼 Makibes HR3</string>
+    <string name="power_mode_watch">螠蠈谓慰 蟻慰位蠈喂</string>
+    <string name="power_mode_saving">螘尉慰喂魏慰谓蠈渭畏蟽畏 蔚谓苇蟻纬蔚喂伪蟼</string>
+    <string name="power_mode_normal">螝伪谓慰谓喂魏萎</string>
+    <string name="power_mode_title">螞蔚喂蟿慰蠀蟻纬委伪 蟿蟻慰蠁慰未慰蟽委伪蟼 蟻慰位慰纬喂慰蠉</string>
+    <string name="prefs_sensors_button_bp_calibration_sum">螤伪蟿萎蟽蟿蔚 蔚未蠋 纬喂伪 谓伪 尉蔚魏喂谓萎蟽蔚蟿蔚 蟿畏 尾伪胃渭慰谓蠈渭畏蟽畏</string>
+    <string name="prefs_sensors_button_bp_calibration">螔伪胃渭慰谓蠈渭畏蟽畏</string>
+    <string name="pref_sensors_bp_calibration_high">危蠀蟽蟿慰位喂魏萎 蟺委蔚蟽畏 伪委渭伪蟿慰蟼 (\"渭蔚纬维位畏\")</string>
+    <string name="pref_sensors_bp_calibration_low">螖喂伪蟽蟿慰位喂魏萎 蟺委蔚蟽畏 伪委渭伪蟿慰蟼 (\"渭喂魏蟻萎\")</string>
+    <string name="pref_sensors_bp_calibration">螔伪胃渭慰谓蠈渭畏蟽畏 伪喂蟽胃畏蟿萎蟻伪 伪蟻蟿畏蟻喂伪魏萎蟼 蟺委蔚蟽畏蟼</string>
+    <string name="pref_title_sensors_altitude">螔伪胃渭慰谓蠈渭畏蟽畏 蠀蠄慰渭苇蟿蟻慰蠀</string>
+    <string name="pref_header_sensors_calibration">螔伪胃渭慰谓蠈渭畏蟽畏 伪喂蟽胃畏蟿萎蟻伪</string>
+    <string name="pref_title_device_spec_settings_show_raw_graph">螘渭蠁维谓喂蟽畏 蟺蟻蠅蟿慰纬蔚谓蠋谓 未蔚未慰渭苇谓蠅谓 蟽蟿慰 纬蟻维蠁畏渭伪 未蟻伪蟽蟿畏蟻喂蠈蟿畏蟿伪蟼</string>
+    <string name="pref_summary_device_spec_settings_title_force_time">螘蟺喂尾慰位萎 伪蠀蟿蠈渭伪蟿慰蠀 蟽蠀纬蠂蟻慰谓喂蟽渭慰蠉 蠋蟻伪蟼 魏伪蟿维 蟿畏谓 蔚蟺伪谓伪蟽蠉谓未蔚蟽畏. 螒谓伪位慰纬喂魏慰委 未蔚委魏蟿蔚蟼 渭蟺慰蟻蔚委 谓伪 未蔚委尉慰蠀谓 蔚蟽蠁伪位渭苇谓畏 蠋蟻伪!</string>
+    <string name="prefs_notifications_and_calls_reject">螝慰蠀渭蟺委 伪纬谓蠈畏蟽畏蟼 / 伪蟺蠈蟻蟻喂蠄畏蟼 魏位萎蟽畏蟼</string>
+    <string name="controlcenter_set_alias">螣蟻委蟽蟿蔚 蟿伪蠀蟿蠈蟿畏蟿伪</string>
+    <string name="about_additional_contributions">螤慰位位苇蟼 蔚蠀蠂伪蟻喂蟽蟿委蔚蟼 蟽蔚 蠈位慰蠀蟼 蟿慰蠀蟼 渭畏 伪谓伪蠁蔚蟻胃苇谓蟿蔚蟼 蟽蠀谓蔚喂蟽蠁苇蟻慰谓蟿蔚蟼 纬喂伪 蟿畏 蟽蠀渭尾慰位萎 蟽蟿慰谓 魏蠋未喂魏伪,蟽蟿畏谓 渭蔚蟿维蠁蟻伪蟽畏, 蟿畏谓 蠀蟺慰蟽蟿萎蟻喂尉畏, 蟿喂蟼 喂未苇蔚蟼, 蟿慰 魏委谓畏蟿蟻慰, 蟿喂蟼 伪谓伪蠁慰蟻苇蟼 蟽蠁伪位渭维蟿蠅谓, 蟿伪 蠂蟻萎渭伪蟿伪鈥� 鉁�</string>
+    <string name="about_additional_device_support">螤蟻蠈蟽胃蔚蟿畏 蠀蟺慰蟽蟿萎蟻喂尉畏 蟽蠀蟽魏蔚蠀蠋谓</string>
+    <string name="about_contributors">危蠀谓蟿蔚位蔚蟽蟿苇蟼</string>
+    <string name="about_core_team_title">螔伪蟽喂魏萎 慰渭维未伪 (魏伪蟿维 蟽蔚喂蟻维 渭蔚纬伪位蠉蟿蔚蟻畏蟼 蟽蠀谓蔚喂蟽蠁慰蟻维蟼 魏蠋未喂魏伪)</string>
+    <string name="about_description_generic">螠喂伪 未蠅蟻蔚维谓, 蔚位蔚蠉胃蔚蟻慰蠀 魏蠋未喂魏伪 魏伪喂 蠂蠅蟻委蟼 蟺蟻蠈蟽尾伪蟽畏 蟽蟿慰 未喂伪未委魏蟿蠀慰 蔚蠁伪蟻渭慰纬萎 Android 纬喂伪 谓伪 伪谓蟿喂魏伪蟿伪蟽蟿萎蟽蔚喂 蟿喂蟼 蠀蟺维蟻蠂慰蠀蟽蔚蟼 蔚蠁伪蟻渭慰纬苇蟼 魏位蔚喂蟽蟿慰蠉 魏蠋未喂魏伪 蟿蠅谓 魏伪蟿伪蟽魏蔚蠀伪蟽蟿蠋谓 蟿慰蠀 smartwatch 蟽伪蟼.</string>
+    <string name="about_activity_title_generic">危蠂蔚蟿喂魏维 渭蔚 蟿慰 Gadgetbridge</string>
+    <string name="about_title">危蠂蔚蟿喂魏维</string>
+    <string name="menuitem_worldclock">螤伪纬魏蠈蟽渭喂慰 巍慰位蠈喂</string>
+    <string name="devicetype_amazfit_trex">Amazfit T-Rex</string>
+    <string name="pref_title_weather_summary">围蟻畏蟽喂渭慰蟺慰喂蔚委蟿伪喂 纬喂伪 蟿畏谓 蠀蟺畏蟻蔚蟽委伪 蟺伪蟻慰蠂萎蟼 魏伪喂蟻慰蠉 蟿慰蠀 LineageOS, 维位位蔚蟼 蔚魏未蠈蟽蔚喂蟼 Android 蟺蟻苇蟺蔚喂 谓伪 蠂蟻畏蟽喂渭慰蟺慰喂慰蠉谓 渭喂伪 蔚蠁伪蟻渭慰纬萎 蠈蟺蠅蟼 \"Weather notification\". 螔蟻蔚委蟿蔚 蟺蔚蟻喂蟽蟽蠈蟿蔚蟻蔚蟼 蟺位畏蟻慰蠁慰蟻委蔚蟼 蟽蟿慰 wiki 蟿慰蠀 Gadgetbridge.</string>
+    <string name="fw_upgrade_notice_amazfit_trex">鈥斘犗佅屛何滴瓜勎蔽� 谓伪 蔚纬魏伪蟿伪蟽蟿萎蟽蔚蟿蔚 蟿慰 蠀位喂魏慰位慰纬喂蟽渭喂魏蠈 %s 蟽蟿慰 Amazfit T-Rex 蟽伪蟼. 
+\n 
+\n螤伪蟻伪魏伪位蠋 尾蔚尾伪喂蠅胃蔚委蟿蔚 蠈蟿喂 苇蠂蔚蟿蔚 蔚纬魏伪蟿伪蟽蟿萎蟽蔚喂 蟺蟻蠋蟿伪 蟿慰 伪蟻蠂蔚委慰 .fw 蟽蟿畏 蟽蠀谓苇蠂蔚喂伪 蟿慰 伪蟻蠂蔚委慰 .res 魏伪喂 蟿苇位慰蟼 蟿慰 .gps 伪蟻蠂蔚委慰. 螚 蟽蠀蟽魏蔚蠀萎 蟺蟻蠈魏蔚喂蟿伪喂 谓伪 蔚蟺伪谓伪魏喂谓萎蟽蔚喂 渭蔚蟿维 蟿畏谓 蔚纬魏伪蟿维蟽蟿伪蟽畏 蟿慰蠀 伪蟻蠂蔚委慰蠀 .fw. 
+\n 
+\n危畏渭蔚委蠅蟽畏: 韦伪 伪蟻蠂蔚委伪 .res 魏伪喂 .gps 未蔚谓 蠂蟻蔚喂维味蔚蟿伪喂 谓伪 蔚纬魏伪蟿伪蟽蟿伪胃蔚委 尉伪谓维 蔚伪谓 伪谓 蔚委谓伪喂 伪魏蟻喂尾蠋蟼 蟿慰 委未喂慰 渭蔚 伪蠀蟿蠈 蟺慰蠀 苇蠂蔚喂 蔚纬魏伪蟿伪蟽蟿伪胃蔚委 蟺蟻慰畏纬慰蠀渭苇谓蠅蟼. 
+\n 
+\n螘螡螘巍螕螇危螘韦螘 螠螘 螖螜螝螇 危螒危 螘违螛螏螡螚!</string>
+    <string name="activity_detail_show_gps_label">螖蔚委尉蔚 蟿慰 委蠂谓慰蟼 伪蟺慰 GPS</string>
+    <string name="activity_detail_duration_label">螖喂维蟻魏蔚喂伪</string>
+    <string name="activity_detail_end_label">韦苇位慰蟼</string>
+    <string name="activity_detail_start_label">螘谓伪蟻尉畏</string>
+    <string name="Steps">螔萎渭伪蟿伪</string>
+    <string name="Activity">螖蟻伪蟽蟿畏蟻喂蠈蟿畏蟿伪</string>
+    <string name="Speed">韦伪蠂蠉蟿畏蟿伪</string>
+    <string name="Elevation">违蠄蠈渭蔚蟿蟻慰</string>
+    <string name="Distance">螒蟺蠈蟽蟿伪蟽畏</string>
+    <string name="Swimming">螝慰位蠉渭尾畏蟽畏</string>
+    <string name="km">蠂位渭</string>
+    <string name="laps_unit">纬蠉蟻慰喂</string>
+    <string name="seconds">未蔚蠀蟿.</string>
+    <string name="strokes_second">str/s</string>
+    <string name="km_h">km/h</string>
+    <string name="meters_second">m/s</string>
+    <string name="steps_unit">尾萎渭伪蟿伪</string>
+    <string name="cm">蔚魏.</string>
+    <string name="meters">渭.</string>
+    <string name="flatSeconds">螘蟺委蟺蔚未慰</string>
+    <string name="ascentSeconds">螒谓维尾伪蟽畏</string>
+    <string name="laps">螕蠉蟻慰喂</string>
+    <string name="averageLapPace">螠苇蟽慰蟼 蟻蠀胃渭蠈蟼 纬蠉蟻慰蠀</string>
+    <string name="averageKMPaceSeconds">螔萎渭伪</string>
+    <string name="averageHR">螝伪蟻未喂伪魏蠈蟼 蟺伪位渭蠈蟼</string>
+    <string name="minPace">螒蟻纬蠈蟿蔚蟻慰蟼 蟻蠀胃渭蠈蟼</string>
+    <string name="maxPace">螕蟻畏纬慰蟻蠈蟿蔚蟻慰蟼 蟻蠀胃渭蠈蟼</string>
+    <string name="maxSpeed">螠苇纬喂蟽蟿畏</string>
+    <string name="caloriesBurnt">螛蔚蟻渭委未蔚蟼</string>
+    <string name="activeSeconds">螘谓蔚蟻纬蠈蟼</string>
+    <string name="steps">螔萎渭伪蟿伪</string>
+    <string name="minAltitude">螘位维蠂喂蟽蟿慰</string>
+    <string name="maxAltitude">螠苇纬喂蟽蟿慰</string>
+    <string name="descentMeters">螝伪蟿畏蠁蠈蟻伪</string>
+    <string name="ascentMeters">螒谓畏蠁蠈蟻伪</string>
+    <string name="distanceMeters">螒蟺蠈蟽蟿伪蟽畏</string>
+    <string name="error_location_enabled_mandatory">螚 蠀蟺畏蟻蔚蟽委伪 蟿慰蟺慰胃蔚蟽委伪蟼 蟺蟻苇蟺蔚喂 谓伪 蔚委谓伪喂 蔚谓蔚蟻纬慰蟺慰喂畏渭苇谓畏 纬喂伪 谓伪 纬委谓蔚喂 畏 蟽维蟻蠅蟽畏 蟿蠅谓 蟽蠀蟽魏蔚蠀蠋谓</string>
+    <string name="ignore_bonded_devices_description">螚 蔚谓蔚蟻纬慰蟺慰委畏蟽畏 伪蠀蟿萎蟼 蟿畏蟼 蔚蟺喂位慰纬萎蟼 胃伪 伪纬谓慰萎蟽蔚喂 蟽蠀蟽魏蔚蠀苇蟼 蟺慰蠀 苇蠂慰蠀谓 萎未畏 蟽蠀谓未蔚胃蔚委 / 蔚委谓伪喂 蟽蔚 味蔚蠉尉畏, 魏伪蟿维 蟿畏 蟽维蟻蠅蟽畏</string>
+    <string name="ignore_bonded_devices">螒纬谓蠈畏蟽蔚 蟿喂蟼 蟽蠀谓未蔚未蔚渭苇谓蔚蟼 蟽蠀蟽魏蔚蠀苇蟼</string>
+    <string name="error_retrieving_devices_database">危蠁维位渭伪 魏伪蟿维 蟿畏谓 伪谓维魏蟿畏蟽畏 蟽蠀蟽魏蔚蠀蠋谓 伪蟺蠈 蟿畏 尾维蟽畏 未蔚未慰渭苇谓蠅谓</string>
+    <string name="error_setting_alias">危蠁维位渭伪 魏伪蟿维 蟿畏 蟻蠉胃渭喂蟽畏 蠄蔚蠀未蠅谓蠉渭慰蠀:</string>
+    <string name="error_exporting_device_preferences">危蠁维位渭伪 魏伪蟿维 蟿畏谓 蔚尉伪纬蠅纬萎 蟽蠀纬魏蔚魏蟻喂渭苇谓蠅谓 蟺蟻慰蟿喂渭萎蟽蔚蠅谓 蟿畏蟼 蟽蠀蟽魏蔚蠀萎蟼</string>
+    <string name="pref_check_permission_status_summary">螘位苇纬尉蟿蔚 魏伪喂 味畏蟿萎蟽蟿蔚 蟿喂蟼 维未蔚喂蔚蟼 蟺慰蠀 位蔚委蟺慰蠀谓 伪魏蠈渭畏 魏伪喂 蠈蟿伪谓 蔚谓未苇蠂蔚蟿伪喂 谓伪 渭畏谓 蠂蟻蔚喂维味慰谓蟿伪喂 维渭蔚蟽伪. 螒蟺蔚谓蔚蟻纬慰蟺慰喂萎蟽蟿蔚 蟿慰 渭蠈谓慰 蔚维谓 慰喂 蟽蠀蟽魏蔚蠀苇蟼 蟽伪蟼 未蔚谓 蠀蟺慰蟽蟿畏蟻委味慰蠀谓 魏伪渭委伪 伪蟺蠈 伪蠀蟿苇蟼 蟿喂蟼 位蔚喂蟿慰蠀蟻纬委蔚蟼. 螒魏蠈渭伪 魏伪喂 畏 渭畏 蠂慰蟻萎纬畏蟽畏 渭喂伪蟼 维未蔚喂伪蟼 蔚谓未苇蠂蔚蟿伪喂 谓伪 蟺蟻慰魏伪位苇蟽蔚喂 蟺蟻慰尾位萎渭伪蟿伪!</string>
+    <string name="pref_check_permission_status">螘位苇纬尉蟿蔚 蟿畏谓 魏伪蟿维蟽蟿伪蟽畏 维未蔚喂伪蟼</string>
+    <string name="error_background_service_reason">螚 蔚魏魏委谓畏蟽畏 蟿畏蟼 蠀蟺畏蟻蔚蟽委伪蟼 蟺伪蟻伪蟽魏畏谓委慰蠀 伪蟺苇蟿蠀蠂蔚 位蠈纬蠅 蔚尉伪委蟻蔚蟽畏蟼. 螞维胃慰蟼:</string>
+    <string name="device_requires_key">螒螤螒螜韦螘螜韦螒螜 韦螣 螝螞螘螜螖螜</string>
+    <string name="device_is_currently_bonded">螘螜螡螒螜 螚螖螚 危违螡螖螘螠螘螡螣</string>
+    <string name="error_background_service_reason_truncated">螚 蔚魏魏委谓畏蟽畏 蟿畏蟼 蠀蟺畏蟻蔚蟽委伪蟼 蟺伪蟻伪蟽魏畏谓委慰蠀 伪蟺苇蟿蠀蠂蔚 蔚蟺蔚喂未萎鈥�</string>
+    <string name="error_background_service">螒蟺慰蟿蠀蠂委伪 苇谓伪蟻尉畏蟼 蟿畏蟼 蠀蟺畏蟻蔚蟽委伪蟼 蟺伪蟻伪蟽魏畏谓委慰蠀</string>
+    <string name="error_version_check_extreme_caution">螤巍螣危螣围螚: 危蠁维位渭伪 魏伪蟿维 蟿慰谓 苇位蔚纬蠂慰 蟿蠅谓 蟺位畏蟻慰蠁慰蟻喂蠋谓 苇魏未慰蟽畏蟼! 螖蔚谓 蟺蟻苇蟺蔚喂 谓伪 蟽蠀谓蔚蠂委蟽蔚蟿蔚! 螔位苇蟺蠅 慰蟿喂 蟿慰 蠈谓慰渭伪 蟿畏蟼 苇魏未慰蟽畏蟼 蔚委谓伪喂 \"%s\"</string>
+    <string name="permission_granting_mandatory">螌位伪 伪蠀蟿维 蟿伪 未喂魏伪喂蠋渭伪蟿伪 伪蟺伪喂蟿慰蠉谓蟿伪喂 魏伪喂 蔚维谓 未蔚谓 蠂慰蟻畏纬畏胃慰蠉谓 蔚谓未苇蠂蔚蟿伪喂 谓伪 蟺蟻慰魏蠉蠄蔚喂 伪蟽蟿维胃蔚喂伪</string>
+    <string name="menuitem_stress">螁纬蠂慰蟼</string>
+    <string name="fw_upgrade_notice_miband5">螤蟻蠈魏蔚喂蟿伪喂 谓伪 蔚纬魏伪蟿伪蟽蟿萎蟽蔚蟿蔚 蟿慰 蠀位喂魏慰位慰纬喂蟽渭喂魏蠈 %s 蟽蟿慰 Mi Band 5.
+\n
+\n螔蔚尾伪喂蠅胃蔚委蟿蔚 蠈蟿喂 苇蠂蔚蟿蔚 蔚纬魏伪蟿伪蟽蟿萎蟽蔚喂 蟿慰 伪蟻蠂蔚委慰 .fw 魏伪喂 渭蔚蟿维 伪蟺蠈 伪蠀蟿蠈 蟿慰 伪蟻蠂蔚委慰 .res. 韦慰 Mi Band 蟽伪蟼 胃伪 蔚蟺伪谓蔚魏魏喂谓萎蟽蔚喂 渭蔚蟿维 蟿畏谓 蔚纬魏伪蟿维蟽蟿伪蟽畏 蟿慰蠀 伪蟻蠂蔚委慰蠀 .fw .
+\n
+\n危畏渭蔚委蠅蟽畏: 未蔚谓 蠂蟻蔚喂维味蔚蟿伪喂 谓伪 蔚纬魏伪蟿伪蟽蟿萎蟽蔚蟿蔚 蟿慰 伪蟻蠂蔚委慰 .res 伪谓 蔚委谓伪喂 伪魏蟻喂尾蠋蟼 蟿慰 委未喂慰 渭蔚 伪蠀蟿蠈 蟺慰蠀 苇蠂蔚喂 蔚纬魏伪蟿伪蟽蟿伪胃蔚委 蟺蟻慰畏纬慰蠀渭苇谓蠅蟼.
+\n
+\n螤巍螣围惟巍螚危韦螘 螠螘 螖螜螝螚 危螒危 螘违螛违螡螚!</string>
+    <string name="companiondevice_pairing_details">螘谓蔚蟻纬慰蟺慰喂蔚委 蟿畏 谓苇伪 蠀蟺慰蟽蟿萎蟻喂尉畏 API CompanionDevice (苇蠂蔚喂 渭蠈谓慰 蔚蟺委未蟻伪蟽畏 蟽蟿慰 Android 8 萎 谓蔚蠈蟿蔚蟻畏 苇魏未慰蟽畏), 畏 慰蟺慰委伪 胃伪 伪蠀尉萎蟽蔚喂 蟿畏谓 伪尉喂慰蟺喂蟽蟿委伪 蔚维谓 畏 蠀蟺畏蟻蔚蟽委伪 蟺蟻苇蟺蔚喂 谓伪 蔚蟺伪谓蔚魏魏喂谓畏胃蔚委 蟽蟿慰 蟺伪蟻伪蟽魏萎谓喂慰, 伪蟺伪喂蟿蔚委 蔚蟺伪谓伪蟽蠉谓未蔚蟽畏 蠂蟻畏蟽喂渭慰蟺慰喂蠋谓蟿伪蟼 蟿慰 Gadgetbridge 纬喂伪 谓伪 苇蠂蔚喂 伪蟺慰蟿苇位蔚蟽渭伪</string>
+    <string name="companiondevice_pairing">危蠉味蔚蠀尉畏 蟽蠀谓慰未蔚蠀蟿喂魏萎蟼 蟽蠀蟽魏蔚蠀萎蟼</string>
+    <string name="require_location_provider">螚 蟿慰蟺慰胃蔚蟽委伪 蟺蟻苇蟺蔚喂 谓伪 蔚委谓伪喂 蔚谓蔚蟻纬慰蟺慰喂畏渭苇谓畏</string>
+    <string name="menuitem_cycles">螤蔚蟻委慰未慰蟼</string>
+    <string name="menuitem_breathing">螒谓伪蟺谓慰萎</string>
+    <string name="devicetype_pinetime_jf">PineTime (违位喂魏慰位慰纬喂蟽渭喂魏蠈 JF )</string>
+    <string name="devicetype_tlw64">TLW64</string>
+    <string name="devicetype_miband5">Mi Band 5</string>
+    <string name="activity_summary_edit_name_title">螘蟺蔚尉蔚蟻纬伪蟽委伪 蔚蟿喂魏苇蟿伪蟼</string>
+    <string name="activity_summary_detail">螞蔚蟺蟿慰渭苇蟻蔚喂伪 伪胃位畏蟿喂魏萎蟼 未蟻伪蟽蟿畏蟻喂蠈蟿畏蟿伪蟼</string>
+    <string name="activity_type_yoga">螕喂蠈纬魏伪</string>
+    <string name="activity_type_jump_roping">危蠂慰喂谓维魏喂</string>
+    <string name="activity_type_elliptical_trainer">螘位位蔚喂蟺蟿喂魏蠈 渭畏蠂维谓畏渭伪</string>
+    <string name="activity_type_indoor_cycling">螤慰未畏位伪蟽委伪 蔚蟽蠅蟿蔚蟻喂魏慰蠉 蠂蠋蟻慰蠀</string>
+    <string name="activity_type_swimming_openwater">螝慰位蠉渭尾畏蟽畏 (伪谓慰喂蠂蟿蠈 谓蔚蟻蠈)</string>
+    <string name="sonyswr12_settings_title">巍蠀胃渭委蟽蔚喂蟼 Sony SWR12</string>
+    <string name="sonyswr12_settings_low_vibration">螘谓蔚蟻纬慰蟺慰喂萎胃畏魏蔚 蠂伪渭畏位萎 未蠈谓畏蟽畏</string>
+    <string name="sonyswr12_settings_stamina">螚 位蔚喂蟿慰蠀蟻纬委伪 蔚尉慰喂魏慰谓蠈渭畏蟽畏蟼 蔚谓苇蟻纬蔚喂伪蟼 蔚委谓伪喂 蔚谓蔚蟻纬慰蟺慰喂畏渭苇谓畏</string>
+    <string name="sonyswr12_settings_alarm_interval">螆尉蠀蟺谓慰 未喂维蟽蟿畏渭伪 蟽蠀谓伪纬蔚蟻渭慰蠉 蟽蔚 位委纬伪 位蔚蟺蟿维</string>
+    <string name="sonyswr12_settings_low_vibration_summary">螘谓蔚蟻纬慰蟺慰喂萎蟽蟿蔚 蟿畏 蠂伪渭畏位萎 苇谓蟿伪蟽畏 蟿蠅谓 魏蟻伪未伪蟽渭蠋谓 蟽蟿畏 味蠋谓畏</string>
+    <string name="sonyswr12_settings_stamina_summary">螚 位蔚喂蟿慰蠀蟻纬委伪 蔚尉慰喂魏慰谓蠈渭畏蟽畏蟼 蔚谓苇蟻纬蔚喂伪蟼 伪蟺蔚谓蔚蟻纬慰蟺慰喂蔚委 蟿畏谓 蟺蔚蟻喂慰未喂魏萎 伪蠀蟿蠈渭伪蟿畏 渭苇蟿蟻畏蟽畏 蟿慰蠀 魏伪蟻未喂伪魏慰蠉 蟻蠀胃渭慰蠉 伪蠀尉维谓慰谓蟿伪蟼 苇蟿蟽喂 蟿慰 蠂蟻蠈谓慰 蔚蟻纬伪蟽委伪蟼</string>
+    <string name="sonyswr12_settings_alarm_interval_summary">韦慰 未喂维蟽蟿畏渭伪 苇尉蠀蟺谓慰蠀 蟽蠀谓伪纬蔚蟻渭慰蠉 蔚委谓伪喂 未喂维蟽蟿畏渭伪 蟺蟻喂谓 伪蟺蠈 蟿慰 蔚纬魏伪蟿蔚蟽蟿畏渭苇谓慰 蟽蠀谓伪纬蔚蟻渭蠈 危蔚 伪蠀蟿蠈 蟿慰 未喂维蟽蟿畏渭伪 畏 蟽蠀蟽魏蔚蠀萎 蟺蟻慰蟽蟺伪胃蔚委 谓伪 伪谓喂蠂谓蔚蠉蟽蔚喂 蟿畏谓 蔚位伪蠁蟻蠉蟿蔚蟻畏 蠁维蟽畏 蠉蟺谓慰蠀 纬喂伪 谓伪 尉蠀蟺谓萎蟽蔚喂 慰 蠂蟻萎蟽蟿畏蟼</string>
+    <string name="activity_list_summary_activities">螖蟻伪蟽蟿畏蟻喂蠈蟿畏蟿蔚蟼</string>
+    <string name="activity_list_summary_distance">螒蟺蠈蟽蟿伪蟽畏</string>
+    <string name="charts_activity_list">螞委蟽蟿伪 未蟻伪蟽蟿畏蟻喂o蟿萎蟿蠅谓</string>
+    <string name="prefs_charts_tabs_summary">螣蟻伪蟿苇蟼 魏伪蟻蟿苇位蔚蟼 纬蟻伪蠁喂渭维蟿蠅谓</string>
+    <string name="prefs_charts_tabs">螝伪蟻蟿苇位蔚蟼 纬蟻伪蠁喂渭维蟿蠅谓</string>
+    <string name="battery_detail_activity_title">螤位畏蟻慰蠁慰蟻委蔚蟼 螠蟺伪蟿伪蟻委伪蟼</string>
+    <string name="find_lost_device_message">螒谓伪味畏蟿畏蟽畏 纬喂伪 %1$s;</string>
+    <string name="calendar_year">螆蟿慰蟼</string>
+    <string name="calendar_six_months">6 渭萎谓蔚蟼</string>
+    <string name="calendar_three_months">3 渭萎谓蔚蟼</string>
+    <string name="calendar_month">螠萎谓伪蟼</string>
+    <string name="calendar_two_weeks">螖蠉慰 螘未慰渭维未蔚蟼</string>
+    <string name="calendar_week">螘尾未慰渭维未伪</string>
+    <string name="calendar_day">螠苇蟻伪</string>
+    <string name="battery_level">螘蟺委蟺蔚未慰 渭蟺伪蟿伪蟻委伪蟼</string>
+    <string name="lefun_prefs_interface_language_title">螕位蠋蟽蟽伪</string>
+</resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-en-rGB/strings.xml b/app/src/main/res/values-en-rGB/strings.xml
new file mode 100644
index 0000000..62fee56
--- /dev/null
+++ b/app/src/main/res/values-en-rGB/strings.xml
@@ -0,0 +1,1580 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <string name="pref_title_chart_heartrate_color">Heart rate colour</string>
+    <string name="pref_title_force_white_color_scheme">Force black on white colour scheme</string>
+    <string name="controlcenter_change_led_color">Change LED colour</string>
+    <string name="notif_battery_low_percent">%1$s battery left: %2$s%%</string>
+    <plurals name="widget_alarm_target_hours">
+        <item quantity="one">%d hour</item>
+        <item quantity="other">%d hours</item>
+    </plurals>
+    <string name="pbwinstallhandler_app_item">%1$s (%2$s)</string>
+    <string name="installhandler_firmware_name">%1$s: %2$s %3$s</string>
+    <string name="notif_battery_low_extended">%1$s battery low: %2$s</string>
+    <string name="notif_battery_low">%1$s battery low</string>
+    <string name="device_with_rssi">%1$s (%2$s)</string>
+    <string name="appversion_by_creator">%1$s by %2$s</string>
+    <string name="pref_qhybrid_title_widget_draw_circles">Draw widget circles</string>
+    <string name="pref_header_auto_fetch">Auto fetch</string>
+    <string name="pref_title_relax_firmware_checks">Enable this if you want to flash a firmware not intended for your device (at your own risk)</string>
+    <string name="pref_summary_relax_firmware_checks">Relax firmware checks</string>
+    <string name="devicetype_amazfit_bips">Amazfit Bip S</string>
+    <string name="pref_title_vibration_strength">Vibration strength</string>
+    <string name="hr_appname_commute">Commute</string>
+    <string name="hr_appname_stopwatch">Stopwatch</string>
+    <string name="hr_appname_workout">Workout</string>
+    <string name="hr_appname_wellness">Wellness</string>
+    <string name="pref_summary_custom_deviceicon">Show a device specific Android notification icon instead the Gadgetbridge icon when connected</string>
+    <string name="pref_title_custom_deviceicon">Show device specific notification icon</string>
+    <string name="notification_channel_high_priority_name">High-priority</string>
+    <string name="find_my_phone_notification">Find my phone</string>
+    <string name="pref_summary_force_white_color_scheme">Useful if you your watch has dark hands</string>
+    <string name="find_lost_device_you_found_it">Found it!</string>
+    <string name="hr_widget_nothing">Nothing</string>
+    <string name="hr_widget_weather">Weather</string>
+    <string name="hr_widget_battery">Battery</string>
+    <string name="hr_widget_calories">Calories</string>
+    <string name="hr_widget_active_minutes">Active minutes</string>
+    <string name="hr_widget_date">Date</string>
+    <string name="hr_widget_steps">Steps</string>
+    <string name="hr_widget_heart_rate">Heart rate</string>
+    <string name="pref_title_sync_caldendar">Sync calendar events</string>
+    <string name="pref_summary_sync_calendar">Enables calendar alerts, even when disconnected</string>
+    <string name="pref_summary_allow_high_mtu">Increases transfer speed, but might not work on some Android devices.</string>
+    <string name="pref_title_allow_high_mtu">Allow high MTU</string>
+    <string name="devicetype_itag">iTag</string>
+    <string name="error_no_location_access">Location access must be granted and enabled for scanning to work properly</string>
+    <string name="alarm_snooze">Snooze</string>
+    <string name="prefs_button_long_press_action_selection_title">Long press button action</string>
+    <string name="prefs_button_variable_actions">Detailed button press settings</string>
+    <string name="prefs_button_triple_press_action_selection_title">Event 3 action</string>
+    <string name="prefs_button_double_press_action_selection_title">Event 2 action</string>
+    <string name="prefs_button_single_press_action_selection_title">Event 1 action</string>
+    <string name="devicetype_y5">Y5</string>
+    <string name="devicetype_banglejs">Bangle.js</string>
+    <string name="pref_summary_disable_new_ble_scanning">Check this option if your device cannot be found during discovery</string>
+    <string name="pref_disable_new_ble_scanning">Disable new BLE scanning</string>
+    <string name="qhybrid_offset_time_by">offset time by</string>
+    <string name="qhybrid_changes_delay_prompt">change might take some seconds鈥�</string>
+    <string name="qhybrid_offset_timezone">offset timezone by</string>
+    <string name="qhybrid_buttons_overwrite_error">Error overwriting buttons</string>
+    <string name="qhybrid_buttons_overwrite_success">Buttons overwritten</string>
+    <string name="qhybrid_prompt_million_steps">Please set the step count to a million to activate that.</string>
+    <string name="qhybrid_use_activity_hand_as_notification_counter">use activity hand as notification counter</string>
+    <string name="qhybrid_overwrite_buttons">overwrite buttons</string>
+    <string name="qhybrid_second_timezone_offset_relative_to_utc">second timezone offset relative to UTC</string>
+    <string name="qhybrid_time_shift">time shift</string>
+    <string name="qhybrid_goal_in_steps">Goal in steps</string>
+    <string name="qhybrid_vibration_strength">vibration strength:</string>
+    <string name="watch_not_connected">Watch not connected</string>
+    <string name="preferences_qhybrid_settings">Q Hybrid Settings</string>
+    <string name="devicetype_qhybrid">Fossil Q Hybrid</string>
+    <string name="fw_upgrade_notice_amazfitgts">You are about to install the %s firmware on your Amazfit GTS.
+\n
+\nPlease make sure to install the .fw file, then the .res file, and finally the .gps file. Your watch will reboot after installing the .fw file.
+\n
+\nNote: You do not have to install .res and .gps if these files are exactly the same as the ones previously installed.
+\n
+\nPROCEED AT YOUR OWN RISK!</string>
+    <string name="devicetype_amazfit_gts">Amazfit GTS</string>
+    <string name="pref_chart_sleep_rolling_24_off">Noon to noon</string>
+    <string name="pref_chart_sleep_rolling_24_on">Past 24 hours</string>
+    <string name="pref_title_chart_sleep_rolling_24_hour">Sleep range</string>
+    <string name="pref_chart_heartrate_color_orange">Orange</string>
+    <string name="pref_chart_heartrate_color_red">Red</string>
+    <string name="fw_upgrade_notice_amazfitgtr">You are about to install the %s firmware on your Amazfit GTR.
+\n
+\nPlease make sure to install the .fw file, then the .res file, and finally the .gps file. Your watch will reboot after installing the .fw file.
+\n
+\nNote: You do not have to install .res and .gps if these files are exactly the same as the ones previously installed.
+\n
+\nPROCEED AT YOUR OWN RISK!</string>
+    <string name="devicetype_amazfit_gtr">Amazfit GTR</string>
+    <string name="fw_upgrade_notice_amazfitbip_lite">You are about to install the %s firmware on your Amazfit Bip Lite.
+\n
+\nPlease make sure to install the .fw file, and after that the .res file. Your watch will reboot after installing the .fw file.
+\n
+\nNote: You do not have to install .res if it is exactly the same as the one previously installed.
+\n
+\nPROCEED AT YOUR OWN RISK!</string>
+    <string name="discovery_need_to_enter_authkey">This device needs a secret auth key, long press on the device to enter it. Read the wiki.</string>
+    <string name="maximum_duration">Duration</string>
+    <string name="prefs_find_phone_duration">Ring duration in seconds</string>
+    <string name="prefs_find_phone_summary">Use your band to play your phone\'s ringtone.</string>
+    <string name="prefs_enable_find_phone">Turn on \'Find phone\'</string>
+    <string name="prefs_find_phone">Find phone</string>
+    <string name="devicetype_amazfit_bip_lite">Amazfit Bip Lite</string>
+    <string name="prefs_wearside">Wearing left or right\?</string>
+    <string name="devicetype_makibes_hr3">Makibes HR3</string>
+    <string name="preferences_makibes_hr3_settings">Makibes HR3 settings</string>
+    <string name="activity_error_no_app_for_gpx">To view activity trace, install app which can handle GPX files.</string>
+    <string name="pref_display_add_device_fab_off">Visible only if no device is added</string>
+    <string name="pref_display_add_device_fab_on">Always visible</string>
+    <string name="pref_display_add_device_fab">Connect new device button</string>
+    <string name="widget_1_hour">1 hour</string>
+    <string name="widget_20_minutes">20 minutes</string>
+    <string name="widget_10_minutes">10 minutes</string>
+    <string name="widget_5_minutes">5 minutes</string>
+    <string name="widget_set_alarm_after">Set alarm after:</string>
+    <string name="widget_listing_label">Status and Alarms</string>
+    <string name="appwidget_sleep_alarm_widget_label">Sleep Alarm</string>
+    <string name="activity_db_management_empty_db_warning">Warning! By pushing this button you will wipe your database and start from scratch.</string>
+    <string name="activity_db_management_exportimport_label">Export and Import</string>
+    <string name="activity_db_management_empty_DB">Empty Database</string>
+    <string name="activity_DB_empty_button">Empty Database</string>
+    <string name="activity_DB_delete_legacy_button">Delete old DB</string>
+    <string name="activity_DB_test_export_message">Exporting database鈥�</string>
+    <string name="activity_DB_test_export_button">Run AutoExport Now</string>
+    <string name="activity_DB_import_button">Import DB</string>
+    <string name="activity_DB_ExportButton">Export Data</string>
+    <string name="activity_db_management_autoexport_label">AutoExport</string>
+    <string name="activity_db_management_autoexport_explanation">Database autoexport location has been set to:</string>
+    <string name="pref_summary_use_custom_font">Enable this if your device has a custom font firmware for emoji support</string>
+    <string name="pref_title_use_custom_font">Use custom font</string>
+    <string name="pref_title_expose_hr">3rd party realtime HR access</string>
+    <string name="pref_summary_expose_hr">Allows other apps to access HR data in realtime while Gadgetbridge is connected</string>
+    <string name="menuitem_nfc">NFC</string>
+    <string name="devicetype_mijia_lywsd02">Mijia Smart Clock</string>
+    <string name="weeksleepchart_sleep_a_month">Sleep per month</string>
+    <string name="weekstepschart_steps_a_month">Steps per month</string>
+    <string name="pref_charts_range_off">Charts range is set to a Week</string>
+    <string name="pref_charts_range_on">Charts range is set to a Month</string>
+    <string name="pref_title_charts_range">Charts Range</string>
+    <string name="pref_title_charts_average">Show averages in the charts</string>
+    <string name="pref_header_charts">Charts Settings</string>
+    <string name="average">Average: %1$s</string>
+    <string name="prefs_hr_alarm_high">High limit</string>
+    <string name="prefs_hr_alarm_low">Low limit</string>
+    <string name="prefs_hr_alarm_activity">Heart rate alarm during sports activity</string>
+    <string name="fw_upgrade_notice_miband4">You are about to install the %s firmware on your Mi Band 4.
+\n
+\nPlease make sure to install the .fw file, and after that the .res file. Your band will reboot after installing the .fw file.
+\n
+\nNote: You do not have to install .res if it is exactly the same as the one previously installed.
+\n
+\nPROCEED AT YOUR OWN RISK!</string>
+    <string name="devicetype_miband4">Mi Band 4</string>
+    <string name="devicetype_amazfit_cor2">Amazfit Cor 2</string>
+    <string name="portuguese">Portuguese</string>
+    <string name="vietnamese">Vietnamese</string>
+    <string name="thai">Thai</string>
+    <string name="indonesian">Indonesian</string>
+    <string name="arabic">Arabic</string>
+    <string name="ukrainian">Ukrainian</string>
+    <string name="turkish">Turkish</string>
+    <string name="dutch">Dutch</string>
+    <string name="fw_upgrade_notice_amazfitcor2">You are about to install the %s firmware on your Amazfit Cor 2. 
+\n 
+\nPlease make sure to install the .fw file, and after that the .res file. Your band will reboot after installing the .fw file. 
+\n 
+\nNote: You do not have to install .res if it is exactly the same as the one previously installed. 
+\n 
+\nPROCEED AT YOUR OWN RISK! 
+\n 
+\nCOMPLETELY UNTESTED, PROBABLY YOU NEED TO FLASH A BEATS_W FIRMWARE IF YOUR DEVICE NAME IS \"Amazfit Band 2\"</string>
+    <string name="devicetype_bfh16">BFH-16</string>
+    <string name="pref_summary_authkey">Change the auth key to a common key on all your Android devices from which you would like to connect from. The previous default key for all devices is 0123456789@ABCDE</string>
+    <string name="pref_title_authkey">Auth Key</string>
+    <string name="title_activity_device_specific_settings">Device specific settings</string>
+    <string name="pref_title_support_voip_calls">Enable VoIP app calls</string>
+    <string name="devicetype_miscale2">Mi Scale 2</string>
+    <string name="activity_prefs_activetime_minutes">Daily target: active time in minutes</string>
+    <string name="activity_prefs_calories_burnt">Daily target: calories burnt</string>
+    <string name="interval_forty_five_minutes">every 45 minutes</string>
+    <string name="interval_fifteen_minutes">every 15 minutes</string>
+    <string name="pref_screen_notification_profile_anti_loss">Anti-loss warning</string>
+    <string name="pref_screen_notification_profile_low_power">Low power warning</string>
+    <string name="pref_screen_notification_profile_inactivity">Inactivity notification</string>
+    <string name="pref_screen_notification_profile_calendar">Calendar notification</string>
+    <string name="pref_screen_notification_profile_missed_call">Missed call notification</string>
+    <string name="zetime_signaling_vibrate_beep_once">Vibrate and beep once</string>
+    <string name="zetime_signaling_beep_twice">Beep twice</string>
+    <string name="zetime_signaling_beep_once">Beep once</string>
+    <string name="zetime_signaling_vibrate_twice">Vibrate twice</string>
+    <string name="zetime_signaling_vibrate_once">Vibrate once</string>
+    <string name="zetime_signaling_vibrate_beep">Continuous vibration and beeping</string>
+    <string name="zetime_signaling_beep">Continuous beeping</string>
+    <string name="zetime_signaling_vibrate">Continuous vibration</string>
+    <string name="zetime_signaling_none">Silent</string>
+    <string name="zetime_title_alarm_signaling">Set type of signaling for the alarm</string>
+    <string name="zetime_prefs_inactivity_su">Sunday</string>
+    <string name="zetime_prefs_inactivity_sa">Saturday</string>
+    <string name="zetime_prefs_inactivity_fr">Friday</string>
+    <string name="zetime_prefs_inactivity_th">Thursday</string>
+    <string name="zetime_prefs_inactivity_we">Wednesday</string>
+    <string name="zetime_prefs_inactivity_tu">Tuesday</string>
+    <string name="zetime_prefs_inactivity_mo">Monday</string>
+    <string name="zetime_prefs_inactivity_repetitions">Repetitions</string>
+    <string name="zetime_date_format_3">MM/DD/YY</string>
+    <string name="zetime_date_format_2">DD/MM/YY</string>
+    <string name="zetime_date_format_1">YY/MM/DD</string>
+    <string name="zetime_date_format">Date format</string>
+    <string name="zetime_calories_type_all">Active and inactive burnt calories</string>
+    <string name="zetime_calories_type_active">Only active burnt calories</string>
+    <string name="zetime_calories_type">Calories type</string>
+    <string name="zetime_handmove_display_summary">Rotate your wrist to activate or deactivate the display.</string>
+    <string name="zetime_handmove_display">Hand movement</string>
+    <string name="zetime_activity_tracking_summary">Switching the activity tracking on, will count your steps and so on.</string>
+    <string name="zetime_activity_tracking">Activity tracking</string>
+    <string name="zetime_analog_mode_handsandsteps">Hands and steps</string>
+    <string name="zetime_analog_mode_hands">Only hands</string>
+    <string name="zetime_analog_mode">Analog mode</string>
+    <string name="activity_prefs_alarm_min_heart_rate">Min heart rate</string>
+    <string name="activity_prefs_alarm_max_heart_rate">Max heart rate</string>
+    <string name="zetime_heart_rate_alarm_enable">Enable the heart rate alarm</string>
+    <string name="zetime_title_heart_rate_alarm_summary">The watch will warn you when your heart rate exceeds the limits.</string>
+    <string name="zetime_title_heart_rate_alarm">Heart rate alarm</string>
+    <string name="zetime_title_screentime">Screen on duration in seconds</string>
+    <string name="zetime_title_heartrate">Heart rate settings</string>
+    <string name="zetime_title_settings">ZeTime settings</string>
+    <string name="prefs_disconnect_notification">Disconnect notification</string>
+    <string name="activity_type_exercise">Exercise</string>
+    <string name="appwidget_not_connected">Not connected, alarm not set.</string>
+    <string name="save_configuration">Save Configuration</string>
+    <string name="mode_configuration">Mode Configuration</string>
+    <string name="filter_mode">Filter Mode</string>
+    <string name="toast_notification_filter_words_empty_hint">Please enter at least one word</string>
+    <string name="filter_submode_all">All of the words</string>
+    <string name="filter_submode_at_least_one">At least one of the words</string>
+    <string name="filter_mode_blacklist">Block when words are contained</string>
+    <string name="filter_mode_whitelist">Show when words are contained</string>
+    <string name="filter_mode_none">Do not filter</string>
+    <string name="toast_notification_filter_saved_successfully">Notification filter saved</string>
+    <string name="edittext_notification_filter_words_hint">Enter desired words, new line for each</string>
+    <string name="preferences_rtl_settings">Right To Left Support</string>
+    <string name="pref_summary_contextual_arabic">Enable this to support contextual Arabic</string>
+    <string name="pref_title_contextual_arabic">Contextual Arabic</string>
+    <string name="pref_rtl_max_line_length_summary">Lengthens or shortens the lines Right-To-Left text is separated into</string>
+    <string name="pref_rtl_max_line_length">Right-To-Left Max Line Length</string>
+    <string name="pref_summary_rtl">Enable this if your device can not show right-to-left languages</string>
+    <string name="pref_title_rtl">Right-To-Left</string>
+    <string name="activity_prefs_chart_min_heart_rate">Min heart rate</string>
+    <string name="activity_prefs_chart_max_heart_rate">Max heart rate</string>
+    <string name="activity_prefs_charts">Chart settings</string>
+    <string name="pref_invalid_frequency_message">Please enter a frequency between 87.5 and 108.0</string>
+    <string name="pref_invalid_frequency_title">Invalid frequency</string>
+    <string name="preferences_fm_frequency">FM Frequency</string>
+    <string name="devicetype_roidmi3">Roidmi 3</string>
+    <string name="devicetype_roidmi">Roidmi</string>
+    <string name="controlcenter_change_fm_frequency">Change FM Frequency</string>
+    <string name="warning">Warning!</string>
+    <string name="share_log_warning">Please keep in mind Gadgetbridge logs files that may contain lots of personal info, including but not limited to health data, unique identifiers (such as a device\'s MAC address), music preferences, etc. Consider editing the file and removing this info before sending the file to a public issue report.</string>
+    <string name="share_log">Share log</string>
+    <string name="ok">OK</string>
+    <string name="mi3_night_mode_sunset">At sunset</string>
+    <string name="mi3_prefs_night_mode_summary">Lower band screen brightness automatically at night</string>
+    <string name="mi3_prefs_night_mode">Night mode</string>
+    <string name="minutes_30">30 minutes</string>
+    <string name="minutes_10">10 minutes</string>
+    <string name="minutes_5">5 minutes</string>
+    <string name="minutes_1">1 minute</string>
+    <string name="seconds_30">30 seconds</string>
+    <string name="seconds_20">20 seconds</string>
+    <string name="seconds_10">10 seconds</string>
+    <string name="seconds_5">5 seconds</string>
+    <string name="no_limit">No limit</string>
+    <string name="pref_title_notifications_timeout">Minimum time between notifications</string>
+    <string name="norwegian_bokmal">Norwegian Bokm氓l</string>
+    <string name="you_did_not_sleep">You did not sleep</string>
+    <string name="you_slept">You slept from %1$s to %2$s</string>
+    <string name="japanese">Japanese</string>
+    <string name="korean">Korean</string>
+    <string name="polish">Polish</string>
+    <string name="french">French</string>
+    <string name="italian">Italian</string>
+    <string name="german">German</string>
+    <string name="mi3_prefs_band_screen_unlock_summary">Swipe up to unlock the band\'s screen</string>
+    <string name="mi3_prefs_band_screen_unlock">Band screen unlock</string>
+    <string name="title_activity_watch9_calibration">Watch 9 calibration</string>
+    <string name="title_activity_watch9_pairing">Watch 9 pairing</string>
+    <string name="watch9_calibration_button">Calibrate</string>
+    <string name="watch9_calibration_hint">Set the time your device is showing to you right now.</string>
+    <string name="watch9_time_seconds">Seconds:</string>
+    <string name="watch9_time_hours">Hours:</string>
+    <string name="watch9_time_minutes">Minutes:</string>
+    <string name="devicetype_watch9">Watch 9</string>
+    <string name="watch9_pairing_tap_hint">When your watch vibrates, shake the device or press its button.</string>
+    <string name="controlcenter_calibrate_device">Calibrate Device</string>
+    <string name="menuitem_music">Music</string>
+    <string name="menuitem_more">More</string>
+    <string name="menuitem_notifications">Notifications</string>
+    <string name="devicetype_id115">ID115</string>
+    <string name="vertical">Vertical</string>
+    <string name="horizontal">Horizontal</string>
+    <string name="prefs_screen_orientation">Screen orientation</string>
+    <string name="russian">Russian</string>
+    <string name="devicetype_mykronoz_zetime">MyKronoz ZeTime</string>
+    <string name="pref_auto_fetch_limit_fetches_summary">Fetches every %d minutes</string>
+    <string name="pref_auto_fetch_limit_fetches">Minimum time between fetches</string>
+    <string name="pref_auto_fetch_summary">Fetch happens upon screen unlock. Only works if a lock mechanism is set!</string>
+    <string name="pref_auto_fetch">Auto fetch activity data</string>
+    <string name="whitelist_all_for_notifications">Whitelist all for notifications</string>
+    <string name="blacklist_all_for_notifications">Blacklist all for notifications</string>
+    <string name="devicetype_miband3">Mi Band 3</string>
+    <string name="activity_prefs_distance_meters">Daily target: distance in metres</string>
+    <string name="mi2_prefs_button_press_broadcast_summary">Broadcast message sent with the event. Parameter 鈥榖utton_id鈥� is added automatically to each message.</string>
+    <string name="preferences_led_color">LED colour</string>
+    <string name="fw_upgrade_notice_miband3">You are about to install the %s firmware on your Mi Band 3.
+\n
+\nPlease make sure to install the .fw file, and after that the .res file. Your band will reboot after installing the .fw file.
+\n
+\nNote: You do not have to install .res if it is exactly the same as the one previously installed.
+\n
+\nPROCEED AT YOUR OWN RISK!</string>
+    <string name="devicetype_q8">Q8</string>
+    <string name="pref_summary_pebble_gatt_clientonly">This is for Pebble 2 only and experimental, try this if you have connectivity problems</string>
+    <string name="pref_title_pebble_gatt_clientonly">GATT client only</string>
+    <string name="menuitem_alipay">Alipay</string>
+    <string name="menuitem_settings">Settings</string>
+    <string name="menuitem_compass">Compass</string>
+    <string name="menuitem_timer">Countdown</string>
+    <string name="menuitem_alarm">Alarm</string>
+    <string name="menuitem_weather">Weather</string>
+    <string name="menuitem_activity">Workout History</string>
+    <string name="menuitem_status">Status</string>
+    <string name="reset_index">Reset fetch date</string>
+    <string name="share">Share</string>
+    <string name="select_all">Select all</string>
+    <string name="pref_title_audio_player">Preferred Audioplayer</string>
+    <string name="pref_title_general_autoreconnect">Reconnect automatically</string>
+    <string name="activity_type_treadmill">Treadmill</string>
+    <string name="activity_type_biking">Biking</string>
+    <string name="activity_summaries">Sport Activities</string>
+    <string name="activity_type_unknown">Unknown activity</string>
+    <string name="activity_type_swimming">Swimming</string>
+    <string name="activity_type_walking">Walking</string>
+    <string name="activity_type_running">Running</string>
+    <string name="activity_type_not_worn">Device not worn</string>
+    <string name="activity_type_deep_sleep">Deep sleep</string>
+    <string name="activity_type_light_sleep">Light sleep</string>
+    <string name="activity_type_activity">Activity</string>
+    <string name="activity_type_not_measured">Not measured</string>
+    <string name="controlcenter_start_activity_tracks">Your activity tracks</string>
+    <string name="on">On</string>
+    <string name="devicetype_xwatch">XWatch</string>
+    <string name="notification_channel_name">General</string>
+    <string name="spanish">Spanish</string>
+    <string name="choose_auto_export_location">Choose export location</string>
+    <string name="notif_export_failed_title">Export database failed! Please check your settings.</string>
+    <string name="pref_summary_auto_export_interval">Export every %d hour</string>
+    <string name="pref_title_auto_export_interval">Export interval</string>
+    <string name="pref_title_auto_export_location">Export location</string>
+    <string name="pref_title_auto_export_enabled">Auto export enabled</string>
+    <string name="pref_header_auto_export">Auto export</string>
+    <string name="pref_title_weather">Weather</string>
+    <string name="devicetype_teclast_h30">Teclast H30</string>
+    <string name="devicetype_no1_f1">No.1 F1</string>
+    <string name="devicetype_exrizu_k8">Exrizu K8</string>
+    <string name="devicetype_makibes_f68">Makibes F68</string>
+    <string name="devicetype_hplus">HPlus</string>
+    <string name="devicetype_liveview">LiveView</string>
+    <string name="devicetype_vibratissimo">Vibratissimo</string>
+    <string name="devicetype_amazfit_cor">Amazfit Cor</string>
+    <string name="devicetype_amazfit_bip">Amazfit Bip</string>
+    <string name="devicetype_miband2">Mi Band 2</string>
+    <string name="devicetype_miband">Mi Band</string>
+    <string name="devicetype_pebble">Pebble</string>
+    <string name="devicetype_test">Test Device</string>
+    <string name="devicetype_unknown">Unknown Device</string>
+    <string name="kind_watchface">Watchface</string>
+    <string name="kind_resources">Resources</string>
+    <string name="kind_gps_cep">GPS Error Correction</string>
+    <string name="kind_gps_almanac">GPS Almanac</string>
+    <string name="kind_gps">GPS Firmware</string>
+    <string name="kind_font">Font</string>
+    <string name="kind_invalid">Invalid data</string>
+    <string name="kind_firmware">Firmware</string>
+    <string name="pref_title_weather_location">Weather location (for LineageOS weather provider)</string>
+    <string name="interval_one_hour">once an hour</string>
+    <string name="interval_thirty_minutes">every 30 minutes</string>
+    <string name="interval_ten_minutes">every 10 minutes</string>
+    <string name="interval_five_minutes">every 5 minutes</string>
+    <string name="interval_one_minute">once a minute</string>
+    <string name="prefs_title_heartrate_measurement_interval">Whole day HR measurement</string>
+    <string name="english">English</string>
+    <string name="traditional_chinese">Traditional Chinese</string>
+    <string name="simplified_chinese">Simplified Chinese</string>
+    <string name="automatic">Automatic</string>
+    <string name="pref_title_charts_swipe">Enable left/right swipe in the charts activity</string>
+    <string name="fw_upgrade_notice_amazfitcor">You are about to install the %s firmware on your Amazfit Cor.
+\n
+\nPlease make sure to install the .fw file, and after that the .res file. Your band will reboot after installing the .fw file.
+\n
+\nNote: You do not have to install .res if it is exactly the same as the one previously installed.
+\n
+\nPROCEED AT YOUR OWN RISK!</string>
+    <string name="controlcenter_connect">Connect鈥�</string>
+    <string name="activity_web_view">Web View Activity</string>
+    <string name="pref_summary_pebble_enable_bgjs">When enabled, allows watchfaces to show weather, battery info etc.</string>
+    <string name="pref_title_pebble_enable_bgjs">Enable background JS</string>
+    <string name="_pebble_watch_reply">Reply</string>
+    <string name="_pebble_watch_mute">Mute</string>
+    <string name="_pebble_watch_open_on_phone">Open on Android device</string>
+    <string name="mi2_prefs_button_press_count_max_delay_summary">Maximum delay between button presses in milliseconds</string>
+    <string name="mi2_prefs_button_press_count_max_delay">Maximum delay between presses</string>
+    <string name="mi2_prefs_button_action_vibrate_summary">Enable band vibration on button action triggered</string>
+    <string name="mi2_prefs_button_action_vibrate">Enable band vibration</string>
+    <string name="mi2_prefs_button_action_summary">Enable action on specified number of button presses</string>
+    <string name="mi2_prefs_button_action">Enable button action</string>
+    <string name="mi2_prefs_button_press_broadcast">Broadcast message to send</string>
+    <string name="mi2_prefs_button_press_count_summary">Number of button presses to trigger an Event 1. Subsequent same amount of presses create Event 2 and so on.</string>
+    <string name="mi2_prefs_button_press_count">Button press count</string>
+    <string name="mi2_prefs_button_actions_summary">Specify button press actions</string>
+    <string name="mi2_prefs_button_actions">Button actions</string>
+    <string name="fw_upgrade_notice_amazfitbip">You are about to install the %s firmware on your Amazfit Bip.
+\n
+\nPlease make sure to install the .fw file, then the .res file, and finally the .gps file. Your watch will reboot after installing the .fw file.
+\n
+\nNote: You do not have to install .res and .gps if these files are exactly the same as the ones previously installed.
+\n
+\nPROCEED AT YOUR OWN RISK!</string>
+    <string name="discovery_dont_pair">Don\'t Pair</string>
+    <string name="discovery_yes_pair">Pair</string>
+    <string name="discovery_pair_question">Select Pair to pair your devices. If this fails, try again without pairing.</string>
+    <string name="discovery_pair_title">Pair with %1$s\?</string>
+    <string name="discovery_successfully_bonded">Bound to %1$s.</string>
+    <string name="discovery_enable_bluetooth">Enable Bluetooth to discover devices.</string>
+    <string name="discovery_trying_to_connect_to">Trying to connect to: %1$s</string>
+    <string name="discovery_bonding_failed_immediately">Bonding with %1$s failed immediately.</string>
+    <string name="discovery_attempting_to_pair">Attempting to pair with %1$s</string>
+    <string name="mi2_dnd_scheduled">Scheduled (time interval)</string>
+    <string name="mi2_dnd_automatic">Automatic (sleep detection)</string>
+    <string name="mi2_dnd_off">Off</string>
+    <string name="off">Off</string>
+    <string name="mi2_enable_text_notifications_summary">Needs firmware &gt;= 1.0.1.28 and Mili_pro.ft* installed.</string>
+    <string name="mi2_enable_text_notifications">Text notifications</string>
+    <string name="mi2_fw_installhandler_fw53_hint">You need to install version %1$s before installing this firmware!</string>
+    <string name="miband2_prefs_timeformat">Mi2: Time format</string>
+    <string name="find_device_you_found_it">You found it!</string>
+    <string name="StringUtils_sender">(%1$s)</string>
+    <string name="pref_screen_notification_profile_alarm_clock">Alarm clock</string>
+    <string name="timeformat_am_pm">AM/PM</string>
+    <string name="timeformat_24h">24H</string>
+    <string name="unit_imperial">Imperial</string>
+    <string name="unit_metric">Metric</string>
+    <string name="pref_summary_setup_bt_pairing">Deactivate this if you have trouble connecting</string>
+    <string name="pref_title_setup_bt_pairing">Enable Bluetooth pairing</string>
+    <string name="weather_notification_label">Make sure that this skin is enabled in the Weather Notification app to get weather information on your Pebble.
+\n
+\nNo configuration is needed here.
+\n
+\nYou can enable the system weather app of your Pebble from the app management.
+\n
+\nSupported watchfaces will show the weather automatically.</string>
+    <string name="pebble_pairing_hint">A pairing dialog will pop up on your Android device. If not, look in the notification drawer and accept the pairing request. Also accept it on your Pebble afterwards.</string>
+    <string name="title_activity_pebble_pairing">Pebble pairing</string>
+    <string name="title_activity_vibration">Vibration</string>
+    <string name="Delete">Delete</string>
+    <string name="Cancel">Cancel</string>
+    <string name="dbmanagementactivity_overwrite">Overwrite</string>
+    <string name="dbmanagementactivity_old_activity_db_deletion_failed">Old Activity database deletion failed.</string>
+    <string name="dbmanagementactivity_old_activity_db_successfully_deleted">Old activity data deleted.</string>
+    <string name="dbmanagementactivity_delete_old_activitydb_confirmation">Really delete the old activity database\? Activity data that was not imported will be lost.</string>
+    <string name="dbmanagementactivity_delete_old_activity_db">Delete old Activity Database\?</string>
+    <string name="dbmanagementactivity_db_deletion_failed">Database deletion failed.</string>
+    <string name="dbmanagementactivity_database_successfully_deleted">Data deleted.</string>
+    <string name="dbmanagementactivity_really_delete_entire_db">Really delete the entire database\? All your activity data and information about your devices will be lost.</string>
+    <string name="dbmanagementactivity_delete_activity_data_title">Delete Activity Data\?</string>
+    <string name="dbmanagementactivity_error_importing_shared">Error importing preference: %1$s</string>
+    <string name="dbmanagementactivity_error_importing_db">Error importing DB: %1$s</string>
+    <string name="dbmanagementactivity_import_successful">Imported.</string>
+    <string name="dbmanagementactivity_overwrite_database_confirmation">Really overwrite the current data? All your current activity data (if any) and preferences will be overwritten.</string>
+    <string name="pref_summary_pebble_health_store_raw">Stores the data \"as is\", increasing the database usage to allow for later interpretation.</string>
+    <string name="pref_title_pebble_health_store_raw">Store raw record in the database</string>
+    <string name="live_activity_heart_rate">Heart rate</string>
+    <string name="charts_legend_heartrate">Heart rate</string>
+    <string name="updatefirmwareoperation_firmware_not_sent">Firmware not sent</string>
+    <string name="updatefirmwareoperation_update_in_progress">Flashing firmware</string>
+    <string name="DEVINFO_HR_VER">"HR: "</string>
+    <string name="error_creating_directory_for_logfiles">Error creating directory for log files: %1$s</string>
+    <string name="device_fw">Firmware version: %1$s</string>
+    <string name="device_hw">Hardware revision: %1$s</string>
+    <string name="activity_prefs_sleep_duration">Preferred sleep duration in hours</string>
+    <string name="add_widget">Add widget</string>
+    <string name="appwidget_text">Zzz</string>
+    <string name="authentication_required">Authentication required</string>
+    <string name="authenticating">Authenticating</string>
+    <string name="activity_prefs_weight_kg">Weight in kg</string>
+    <string name="activity_prefs_height_cm">Height in cm</string>
+    <string name="activity_prefs_gender">Gender</string>
+    <string name="activity_prefs_year_birth">Year of birth</string>
+    <string name="activity_prefs_about_you">About you</string>
+    <string name="waiting_for_reconnect">Waiting for reconnect</string>
+    <string name="FetchActivityOperation_about_to_transfer_since">About to transfer data since %1$s</string>
+    <string name="mi2_prefs_do_not_disturb_end">End time</string>
+    <string name="mi2_prefs_do_not_disturb_start">Start time</string>
+    <string name="mi2_prefs_inactivity_warnings_dnd_summary">Disable inactivity warnings for a time interval</string>
+    <string name="mi2_prefs_inactivity_warnings_threshold">Inactivity threshold (in minutes)</string>
+    <string name="mi2_prefs_inactivity_warnings_summary">The band will vibrate when you have been inactive for a while</string>
+    <string name="mi2_prefs_inactivity_warnings">Inactivity warnings</string>
+    <string name="mi2_prefs_do_not_disturb_summary">The band won\'t receive notifications while active</string>
+    <string name="mi2_prefs_do_not_disturb">Do Not Disturb</string>
+    <string name="mi2_prefs_rotate_wrist_to_switch_info">Rotate wrist to switch info</string>
+    <string name="mi2_prefs_activate_display_on_lift">Activate display upon lift</string>
+    <string name="mi2_prefs_display_items_summary">Choose the items displayed on the band screen</string>
+    <string name="mi2_prefs_display_items">Display items</string>
+    <string name="mi2_prefs_goal_notification_summary">The band will vibrate when the daily steps goal is reached</string>
+    <string name="mi2_prefs_goal_notification">Goal notification</string>
+    <string name="dateformat_date_time">Time &amp; date</string>
+    <string name="dateformat_time">Time</string>
+    <string name="miband2_prefs_dateformat">Date format</string>
+    <string name="miband_prefs_device_time_offset_hours">Device time offset in hours (for detecting sleep of shift workers)</string>
+    <string name="miband_prefs_hr_sleep_detection">Use heart rate sensor to improve sleep detection</string>
+    <string name="miband_prefs_reserve_alarm_calendar">Alarms to reserve for upcoming events</string>
+    <string name="fwinstaller_firmware_not_compatible_to_device">This firmware is not compatible with the device</string>
+    <string name="miband_fwinstaller_incompatible_version">Incompatible firmware</string>
+    <string name="pref_title_keep_data_on_device">Keep activity data on device</string>
+    <string name="user_feedback_all_alarms_disabled">All alarms disabled</string>
+    <string name="device_not_connected">Not connected.</string>
+    <string name="abstract_chart_fragment_kind_not_worn">Not worn</string>
+    <string name="abstract_chart_fragment_kind_deep_sleep">Deep sleep</string>
+    <string name="abstract_chart_fragment_kind_light_sleep">Light sleep</string>
+    <string name="abstract_chart_fragment_kind_activity">Activity</string>
+    <string name="live_activity_start_your_activity">Start your activity</string>
+    <string name="live_activity_steps_per_minute_history">Steps per minute history</string>
+    <string name="live_activity_total_steps">Total steps</string>
+    <string name="live_activity_current_steps_per_minute">Current steps/min</string>
+    <string name="live_activity_steps_history">Steps history</string>
+    <string name="pref_summary_low_latency_fw_update">This might help on devices where firmware flashing fails.</string>
+    <string name="pref_title_low_latency_fw_update">Use low-latency mode for firmware flashing</string>
+    <string name="pref_summary_keep_data_on_device">Will keep activity data on the Mi Band even after synchronisation. Useful if GB is used together with other apps.</string>
+    <string name="pref_summary_dont_ack_transfers">If not ACKed to the band, activity data is not cleared. Useful if GB is used together with other apps.</string>
+    <string name="pref_title_dont_ack_transfer">Do not ACK activity data transfer</string>
+    <string name="weeksteps_today_steps_description">Steps today, target: %1$s</string>
+    <string name="liveactivity_live_activity">Live activity</string>
+    <string name="battery">Battery</string>
+    <string name="heart_rate">Heart rate</string>
+    <string name="clock">Clock</string>
+    <string name="distance">Distance</string>
+    <string name="calories">Calories</string>
+    <string name="chart_steps">Steps</string>
+    <string name="updatefirmwareoperation_write_failed">Firmware flashing failed</string>
+    <string name="updatefirmwareoperation_update_complete_rebooting">Firmware installation complete, rebooting device鈥�</string>
+    <string name="updatefirmwareoperation_update_complete">Firmware installation complete</string>
+    <string name="title_activity_notification_filter">Notification Filter</string>
+    <string name="appwidget_setting_alarm">Setting alarm for %1$02d:%2$02d</string>
+    <string name="devicetype_casiogb6900">Casio GB-6900</string>
+    <string name="debugactivity_really_factoryreset">Doing a factory reset will delete all data from the connected device (if supported). Xiaomi/Huami devices also change Bluetooth MAC address, so they appear as a new devices to Gadgetbridge.</string>
+    <string name="debugactivity_really_factoryreset_title">Really factory reset\?</string>
+    <string name="live_activity_max_heart_rate">Current / Max heart rate: %1$d / %2$d</string>
+    <string name="no_data">No data</string>
+    <string name="overstep">Overstep: %1$d</string>
+    <string name="lack_of_step">Lack of steps: %1$d</string>
+    <string name="overslept">Overslept: %1$s</string>
+    <string name="lack_of_sleep">Lack of sleep: %1$s</string>
+    <string name="language_and_region_prefs">Language and region settings</string>
+    <string name="updatefirmwareoperation_metadata_updateproblem">Problem with the firmware metadata transfer</string>
+    <string name="updatefirmwareoperation_updateproblem_do_not_reboot">Problem with the firmware transfer. DO NOT REBOOT your Mi Band!</string>
+    <string name="pbwinstallhandler_incorrect_hw_revision">Hardware revision mismatch!</string>
+    <string name="pbwinstallhandler_correct_hw_revision">Correct hardware revision</string>
+    <string name="pbw_installhandler_pebble_firmware">Pebble Firmware %1$s</string>
+    <string name="fwappinstaller_connection_state">Connection to device: %1$s</string>
+    <string name="miband_fwinstaller_untested_version">Untested version!</string>
+    <string name="miband_fwinstaller_compatible_version">Compatible version</string>
+    <string name="fwapp_install_device_not_ready">File cannot be installed, device not ready.</string>
+    <string name="updating_firmware">Flashing firmware鈥�</string>
+    <string name="activity_sleepchart_activity_and_sleep">Activity</string>
+    <string name="weekstepschart_steps_a_week">Steps per week</string>
+    <string name="weeksleepchart_today_sleep_description">Sleep today, target: %1$s</string>
+    <string name="weeksleepchart_sleep_a_week">Sleep per week</string>
+    <string name="sleepchart_your_sleep">Sleep</string>
+    <string name="notif_battery_low_bigtext_number_of_charges">Number of charges: %s</string>
+    <string name="notif_battery_low_bigtext_last_charge_time">Last charge: %s 
+\n</string>
+    <string name="notif_battery_low_title">Gadget battery Low!</string>
+    <string name="installer_activity_wait_while_determining_status">Please wait while determining the installation status鈥�</string>
+    <string name="pbw_install_handler_hw_revision_mismatch">Unable to install the given firmware: It doesn\'t match your Pebble\'s hardware revision.</string>
+    <string name="pbw_install_handler_unable_to_install">Unable to install the given file: %1$s</string>
+    <string name="installer_activity_unable_to_find_handler">Unable to find a handler to install this file.</string>
+    <string name="cannot_connect">Cannot connect: %1$s</string>
+    <string name="controlcenter_start_activitymonitor">Your activity (ALPHA)</string>
+    <string name="dbaccess_error_executing">Error executing \'%1$s\'</string>
+    <string name="miband_prefs_fitness_goal">Daily step target</string>
+    <string name="user_feedback_miband_activity_data_transfer">About to transfer %1$s of data starting from %2$s</string>
+    <string name="chart_no_data_synchronize">No data. Synchronise device\?</string>
+    <string name="user_feedback_miband_set_alarms_ok">Alarms sent to device.</string>
+    <string name="user_feedback_miband_set_alarms_failed">There was an error setting the alarms, please try again.</string>
+    <string name="alarm_smart_wakeup">Smart wakeup</string>
+    <string name="alarm_sat_short">Sat</string>
+    <string name="alarm_fri_short">Fri</string>
+    <string name="alarm_thu_short">Thu</string>
+    <string name="alarm_wed_short">Wed</string>
+    <string name="alarm_tue_short">Tue</string>
+    <string name="alarm_mon_short">Mon</string>
+    <string name="alarm_sun_short">Sun</string>
+    <string name="title_activity_alarm_details">Alarm details</string>
+    <string name="controlcenter_start_configure_alarms">Configure alarms</string>
+    <string name="title_activity_set_alarm">Configure alarms</string>
+    <string name="title_activity_charts">Activity and Sleep</string>
+    <string name="control_center_cancel_to_stop_vibration">Cancel to stop vibration.</string>
+    <string name="control_center_find_lost_device">Find lost device</string>
+    <string name="stats_y_axis_label">Steps per minute</string>
+    <string name="stats_x_axis_label">Total minutes</string>
+    <string name="stats_title">Speed zones</string>
+    <string name="pref_screen_notification_profile_generic_social">Social network</string>
+    <string name="pref_screen_notification_profile_generic_navigation">Navigation</string>
+    <string name="pref_screen_notification_profile_generic_chat">Chat</string>
+    <string name="pref_screen_notification_profile_incoming_call">Incoming call notification</string>
+    <string name="pref_screen_notification_profile_email">E-mail notification</string>
+    <string name="pref_screen_notification_profile_generic">Generic notification</string>
+    <string name="pref_header_vibration_settings">Vibration settings</string>
+    <string name="pref_screen_notification_profile_sms">SMS notification</string>
+    <string name="vibration_try">Try</string>
+    <string name="miband_prefs_vibration">Vibration</string>
+    <string name="vibration_profile_alarm_clock">Alarm clock</string>
+    <string name="vibration_profile_ring">Ring</string>
+    <string name="vibration_profile_waterdrop">Waterdrop</string>
+    <string name="vibration_profile_long">Long</string>
+    <string name="vibration_profile_medium">Medium</string>
+    <string name="vibration_profile_short">Short</string>
+    <string name="vibration_profile_staccato">Staccato</string>
+    <string name="pref_screen_vibration_profile">Vibration profile</string>
+    <string name="sleep_activity_date_range">From %1$s to %2$s</string>
+    <string name="busy_task_fetch_activity_data">Fetching activity data</string>
+    <string name="initializing">Initializing</string>
+    <string name="pref_write_logfiles">Write log files</string>
+    <string name="title_activity_sleepmonitor">Sleep monitor</string>
+    <string name="pref_header_vibration_count">Vibration count</string>
+    <string name="miband_prefs_alias">Name/Alias</string>
+    <string name="candidate_item_device_image">Device image</string>
+    <string name="discovery_note">Note:</string>
+    <string name="discovery_connected_devices_hint">Make your device discoverable. Currently connected devices will likely not be discovered. Activate location (e.g. GPS) on Android 6+. Disable Privacy Guard for Gadgetbridge, because it may crash and reboot your phone. If no device is found after a few minutes, try again after rebooting your mobile device.</string>
+    <string name="appinstaller_install">Install</string>
+    <string name="miband_pairing_tap_hint">When your Mi Band vibrates and blinks, tap it a few times in a row.</string>
+    <string name="miband_pairing_using_dummy_userdata">No valid user data given, using dummy user data for now.</string>
+    <string name="right">Right</string>
+    <string name="left">Left</string>
+    <string name="other">Other</string>
+    <string name="female">Female</string>
+    <string name="male">Male</string>
+    <string name="preferences_miband_settings">Mi Band / Amazfit settings</string>
+    <string name="preferences_category_device_specific_settings">Device specific settings</string>
+    <string name="message_cannot_pair_no_mac">No MAC address passed, cannot pair.</string>
+    <string name="pairing_already_bonded">Already bonded with %1$s (%2$s), connecting鈥�</string>
+    <string name="pairing_in_progress">Bonding in progress: %1$s (%2$s)</string>
+    <string name="pairing_unable_to_pair_with">Unable to pair with %1$s (%2$s)</string>
+    <string name="pairing_creating_bond_with">Creating bond with %1$s (%2$s)</string>
+    <string name="pairing">Pairing with %s鈥�</string>
+    <string name="title_activity_mi_band_pairing">Pair your Mi Band</string>
+    <string name="android_pairing_hint">Use the Android Bluetooth pairing dialog to pair the device.</string>
+    <string name="title_activity_android_pairing">Pair device</string>
+    <string name="action_discover">Connect new device</string>
+    <string name="discovery_start_scanning">Start discovery</string>
+    <string name="discovery_stop_scanning">Stop scanning</string>
+    <string name="title_activity_discovery">Device discovery</string>
+    <string name="initialized">initialized</string>
+    <string name="n_a">N/A</string>
+    <string name="app_install_info">You are about to install the following app:
+\n
+\n%1$s
+\nVersion %2$s by %3$s
+\n</string>
+    <string name="firmware_install_warning">YOU ARE TRYING TO INSTALL A FIRMWARE, PROCEED AT YOUR OWN RISK.
+\n
+\n
+\n This firmware is for HW Revision: %s</string>
+    <string name="installation_successful">Installed</string>
+    <string name="installation_failed_">Installation failed</string>
+    <string name="installing_binary_d_d">Installing binary %1$d/%2$d</string>
+    <string name="gadgetbridge_running_generic">Gadgetbridge running</string>
+    <string name="cannot_connect_bt_address_invalid_">Cannot connect. Bluetooth address invalid\?</string>
+    <string name="tap_a_device_to_connect">Tap a device to connect</string>
+    <string name="tap_connected_device_for_vibration">Tap connected device for vibration</string>
+    <string name="tap_connected_device_for_activity">Tap connected device for activity</string>
+    <string name="tap_connected_device_for_app_mananger">Tap connected device for App manager</string>
+    <string name="bluetooth_is_disabled_">Bluetooth is disabled.</string>
+    <string name="bluetooth_is_not_supported_">Bluetooth is not supported.</string>
+    <string name="this_is_a_test_notification_from_gadgetbridge">This is a test notification from Gadgetbridge</string>
+    <string name="test_notification">Test notification</string>
+    <string name="test">Test</string>
+    <string name="_unknown_">(unknown)</string>
+    <string name="unknown_state">Unknown state</string>
+    <string name="connected">Connected</string>
+    <string name="connecting">Connecting</string>
+    <string name="not_connected">Not connected</string>
+    <string name="preferences_hplus_settings">HPlus/Makibes settings</string>
+    <string name="prefs_title_all_day_heart_rate">All day heart rate measurement</string>
+    <string name="pref_title_screentime">Screen on duration</string>
+    <string name="pref_title_timeformat">Time format</string>
+    <string name="pref_title_unit_system">Units</string>
+    <string name="pref_title_pebble_reconnect_attempts">Reconnection attempts</string>
+    <string name="pref_summary_pebble_always_ack_pebblekit">Will cause messages that are sent to external 3rd party apps to be acknowledged always and immediately</string>
+    <string name="pref_title_pebble_always_ack_pebblekit">Prematurely ACK PebbleKit</string>
+    <string name="pref_summary_pebble_enable_applogs">Will cause logs from watch apps to be logged by Gadgetbridge (requires reconnect)</string>
+    <string name="pref_title_pebble_enable_applogs">Enable watch app logging</string>
+    <string name="pref_summary_pebble_mtu_limit">If your Pebble 2/Pebble LE does not work as expected, try this setting to limit the MTU (valid range 20鈥�512)</string>
+    <string name="pref_title_pebble_mtu_limit">Pebble 2/LE GATT MTU limit</string>
+    <string name="pref_summary_pebble_forcele">Use experimental Pebble LE support for all Pebbles, instead of BT classic. This requires pairing to non LE first, and then Pebble LE</string>
+    <string name="pref_title_pebble_forcele">Always prefer BLE</string>
+    <string name="pref_summary_pebble_forceuntested">Enable untested features. KNOW WHAT YOU ARE DOING!</string>
+    <string name="pref_title_pebble_forceuntested">Enable untested features</string>
+    <string name="pref_summary_pebble_forceprotocol">This option forces using the latest notification protocol depending on the firmware version. KNOW WHAT YOU ARE DOING!</string>
+    <string name="pref_title_pebble_forceprotocol">Force notification protocol</string>
+    <string name="toast_aqurired_networklocation">location acquired</string>
+    <string name="toast_enable_networklocationprovider">Please enable network location</string>
+    <string name="pref_summary_location_keep_uptodate">Try to get the current location at runtime, use the stored location as fallback</string>
+    <string name="pref_title_location_keep_uptodate">Keep location updated</string>
+    <string name="pref_title_location_longitude">Longitude</string>
+    <string name="pref_title_location_latitude">Latitude</string>
+    <string name="pref_title_location_aquire">Acquire location</string>
+    <string name="pref_header_location">Location</string>
+    <string name="pref_pebble_privacy_mode_complete">Show only the notification icon</string>
+    <string name="pref_pebble_privacy_mode_content">Shift the notification text off-screen</string>
+    <string name="pref_pebble_privacy_mode_off">Normal notifications</string>
+    <string name="pref_title_pebble_privacy_mode">Privacy mode</string>
+    <string name="pref_summary_autoremove_notifications">Notifications are automatically removed from the device when dismissed from the phone</string>
+    <string name="pref_title_autoremove_notifications">Autoremove dismissed notifications</string>
+    <string name="pref_summary_enable_calendar_sync">Send calendar events to the timeline</string>
+    <string name="pref_title_enable_calendar_sync">Sync calendar</string>
+    <string name="pref_summary_sunrise_sunset">Send sunrise and sunset times based on the location to the Pebble timeline</string>
+    <string name="pref_title_sunrise_sunset">Sunrise and sunset</string>
+    <string name="pref_header_pebble_timeline">Pebble timeline</string>
+    <string name="pref_summary_enable_pebblekit">Enable experimental support for Android apps using PebbleKit</string>
+    <string name="pref_title_enable_pebblekit">Allow 3rd party Android App access</string>
+    <string name="pref_summary_enable_outgoing_call">Disabling this will also stop the Pebble 2/LE to vibrate on outgoing calls</string>
+    <string name="pref_title_enable_outgoing_call">Support outgoing calls</string>
+    <string name="pref_title_pebble_sync_morpheuz">Sync Morpheuz</string>
+    <string name="pref_title_pebble_sync_misfit">Sync Misfit</string>
+    <string name="pref_title_pebble_sync_health">Sync Pebble Health</string>
+    <string name="pref_title_pebble_activitytracker">Preferred activity tracker</string>
+    <string name="pref_header_activitytrackers">Activity trackers</string>
+    <string name="pref_title_pebble_settings">Pebble settings</string>
+    <string name="pref_title_development_miaddr">Mi Band address</string>
+    <string name="pref_header_development">Developer options</string>
+    <string name="pref_title_canned_messages_set">Update on device</string>
+    <string name="pref_title_canned_messages_dismisscall">Call Dismissal</string>
+    <string name="pref_title_canned_reply_suffix">Common suffix</string>
+    <string name="pref_title_canned_replies">Replies</string>
+    <string name="pref_header_cannned_messages">Canned messages</string>
+    <string name="pref_blacklist_calendars">Blacklist Calendars</string>
+    <string name="pref_call_privacy_mode_complete">Hide name and number</string>
+    <string name="pref_call_privacy_mode_number">Hide number but display name</string>
+    <string name="pref_call_privacy_mode_name">Hide name but display number</string>
+    <string name="pref_call_privacy_mode_off">Display name and number</string>
+    <string name="pref_title_call_privacy_mode">Call privacy mode</string>
+    <string name="pref_header_privacy">Privacy</string>
+    <string name="never">Never</string>
+    <string name="when_screen_off">When screen is off</string>
+    <string name="always">Always</string>
+    <string name="pref_summary_transliteration">Enable this if your device has no support for your language\'s font</string>
+    <string name="pref_title_transliteration">Transliteration</string>
+    <string name="pref_summary_notification_filter">Block all notifications when Do Not Disturb is enabled on the phone</string>
+    <string name="pref_title_notification_filter">Do Not Disturb</string>
+    <string name="pref_title_whenscreenon">鈥lso when screen is on</string>
+    <string name="pref_title_notifications_generic">Generic notification support</string>
+    <string name="pref_summary_notifications_pebblemsg">Support for apps that send notifications to the Pebble via PebbleKit.</string>
+    <string name="pref_title_notifications_pebblemsg">Pebble Messages</string>
+    <string name="pref_title_notifications_sms">SMS</string>
+    <string name="pref_title_notifications_call">Phone Calls</string>
+    <string name="pref_title_notifications_repetitions">Repetitions</string>
+    <string name="pref_header_notifications">Notifications</string>
+    <string name="pref_summary_minimize_priority_on">The icon in the status bar and the notification in the lockscreen are hidden</string>
+    <string name="pref_summary_minimize_priority_off">The icon in the status bar and the notification in the lockscreen are shown</string>
+    <string name="pref_title_minimize_priority">Hide the Gadgetbridge notification</string>
+    <string name="pref_title_language">Language</string>
+    <string name="pref_theme_dark">Dark</string>
+    <string name="pref_theme_light">Light</string>
+    <string name="pref_title_theme">Theme</string>
+    <string name="pref_summary_datetime_syctimeonconnect">Sync time to Gadgetbridge device when connecting, and when time or time zone changes on Android device</string>
+    <string name="pref_title_datetime_syctimeonconnect">Sync time</string>
+    <string name="pref_header_datetime">Date and Time</string>
+    <string name="pref_default">Default</string>
+    <string name="pref_title_general_autostartonboot">Start automatically</string>
+    <string name="pref_title_general_autoconnectonbluetooth">Connect to Gadgetbridge device when Bluetooth is turned on</string>
+    <string name="pref_header_general">General settings</string>
+    <string name="title_activity_settings">Settings</string>
+    <string name="miband_firmware_suggest_whitelist">If you still want to proceed and things continue to work properly afterwards, please tell the Gadgetbridge developers to whitelist the %s firmware version.</string>
+    <string name="miband_firmware_unknown_warning">This firmware is untested and may not be compatible with Gadgetbridge.
+\n
+\nYou are DISCOURAGED from flashing it!</string>
+    <string name="miband_firmware_known">This firmware has been tested and is known to be compatible with Gadgetbridge.</string>
+    <string name="dbmanagementactivity_import_data_title">Import Data\?</string>
+    <string name="dbmanagementactivity_error_exporting_shared">Error exporting preference: %1$s</string>
+    <string name="dbmanagementactivity_error_exporting_db">Error exporting DB: %1$s</string>
+    <string name="dbmanagementactivity_exported_to">Exported to: %1$s</string>
+    <string name="dbmanagementactivvity_cannot_access_export_path">Cannot access export path. Please contact the developers.</string>
+    <string name="activity_db_management_merge_old_title">Legacy database delete</string>
+    <string name="activity_db_management_import_export_explanation">The export/import operations use the following path (see below) to a directory on your device. This directory is accessible to other Android apps and your computer. Do note, that this directory and all containing files are deleted if you uninstall Gadgetbridge. The data includes:
+\n Export_preference - global settings
+\n Export_preference_MAC - device specific settings
+\n Gadgetbridge - device and activity database
+\n Gadgetbridge_date - database exported on a date
+\n *.gpx - GPS recordings
+\n *.log - log files
+\nExpect to find your exported files (or place the files you want to import) there:</string>
+    <string name="title_activity_db_management">Database management</string>
+    <string name="action_db_management">Data management</string>
+    <string name="fw_multi_upgrade_notice">You are about to install the %1$s and %2$s firmware, instead of the ones currently on your Mi Band.</string>
+    <string name="fw_upgrade_notice">You are about to install the %s.</string>
+    <string name="title_activity_fw_app_insaller">FW/App installer</string>
+    <string name="title_activity_calblacklist">Blacklisted Calendars</string>
+    <string name="app_move_to_top">Move to top</string>
+    <string name="app_configure">Configure</string>
+    <string name="appmanager_weather_install_provider">Install the Weather Notification app</string>
+    <string name="appmanager_weather_deactivate">Deactivate System Weather app</string>
+    <string name="appmanager_weather_activate">Activate System Weather app</string>
+    <string name="appmanager_hrm_deactivate">Deactivate HRM</string>
+    <string name="appmanager_hrm_activate">Activate HRM</string>
+    <string name="appmanager_health_deactivate">Deactivate</string>
+    <string name="appmanager_health_activate">Activate</string>
+    <string name="appmanager_app_openinstore">Search in Pebble appstore</string>
+    <string name="appmananger_app_reinstall">Reinstall</string>
+    <string name="appmananger_app_delete_cache">Delete and remove from cache</string>
+    <string name="appmananger_app_delete">Delete</string>
+    <string name="appmanager_installed_watchfaces">Installed watchfaces</string>
+    <string name="appmanager_installed_watchapps">Installed apps</string>
+    <string name="appmanager_cached_watchapps_watchfaces">Apps in cache</string>
+    <string name="title_activity_appmanager">App Manager</string>
+    <string name="title_activity_debug">Debug</string>
+    <string name="controlcenter_snackbar_requested_screenshot">Taking a screenshot of the device</string>
+    <string name="controlcenter_snackbar_connecting">Connecting鈥�</string>
+    <string name="controlcenter_snackbar_disconnecting">Disconnecting</string>
+    <string name="controlcenter_snackbar_need_longpress">Long press the card to disconnect</string>
+    <string name="controlcenter_navigation_drawer_close">Close navigation drawer</string>
+    <string name="controlcenter_navigation_drawer_open">Open navigation drawer</string>
+    <string name="controlcenter_delete_device_dialogmessage">This will delete the device and all associated data!</string>
+    <string name="controlcenter_delete_device_name">Delete %1$s</string>
+    <string name="controlcenter_delete_device">Delete Device</string>
+    <string name="controlcenter_disconnect">Disconnect</string>
+    <string name="controlcenter_take_screenshot">Take Screenshot</string>
+    <string name="controlcenter_find_device">Find lost device</string>
+    <string name="controlcenter_fetch_activity_data">Synchronise</string>
+    <string name="action_donate">Donate</string>
+    <string name="action_quit">Quit</string>
+    <string name="action_debug">Debug</string>
+    <string name="action_settings">Settings</string>
+    <string name="title_activity_controlcenter_generic">Gadgetbridge</string>
+    <string name="application_name_generic">Gadgetbridge</string>
+    <string name="pref_qhybrid_save_raw_activity_files">Save raw activity files</string>
+    <string name="menuitem_workout">Workout</string>
+    <string name="menuitem_eventreminder">Event Reminder</string>
+    <string name="menuitem_hr">Heart Rate</string>
+    <string name="menuitem_pai">PAI</string>
+    <string name="devicetype_watchxplus">Watch X Plus</string>
+    <string name="devicetype_watchx">Watch X</string>
+    <string name="czesh">Czech</string>
+    <string name="swedish">Swedish</string>
+    <string name="hebrew">Hebrew</string>
+    <string name="greek">Greek</string>
+    <string name="hungarian">Hungarian</string>
+    <string name="romanian">Romanian</string>
+    <string name="power_mode_watch">Only watch</string>
+    <string name="power_mode_saving">Power saving</string>
+    <string name="power_mode_normal">Normal</string>
+    <string name="power_mode_title">Watch power mode</string>
+    <string name="prefs_sensors_button_bp_calibration_sum">Press here to begin calibration</string>
+    <string name="prefs_sensors_button_bp_calibration">Calibration</string>
+    <string name="pref_sensors_bp_calibration_high">Blood Pressure SYSTOLIC (high)</string>
+    <string name="pref_sensors_bp_calibration_low">Blood Pressure DIASTOLIC (low)</string>
+    <string name="pref_sensors_bp_calibration">Blood Pressure calibration</string>
+    <string name="pref_title_sensors_altitude">Altitude calibration</string>
+    <string name="pref_header_sensors_calibration">Sensor Calibration</string>
+    <string name="pref_title_device_spec_settings_show_raw_graph">Show RAW data on activity graph</string>
+    <string name="pref_summary_device_spec_settings_title_force_time">Force auto synchronise time on reconnect. Analog hands may show incorrect time!</string>
+    <string name="pref_title_device_spec_settings_force_time">Force synchronise time</string>
+    <string name="pref_header_device_spec_settings">Device settings</string>
+    <string name="pref_summary_notifications_and_calls_title_shake_reject">Duplicates watch button action</string>
+    <string name="prefs_notifications_and_calls_shake_reject">Shake wrist ignore/reject call</string>
+    <string name="pref_summary_notifications_and_calls_title_reject">Off 鈥� ignore, On 鈥� reject</string>
+    <string name="prefs_notifications_and_calls_reject">Button ignore/reject call</string>
+    <string name="pref_header_notifications_and_calls_callhandling">Call Handling</string>
+    <string name="pref_title_notifications_and_calls_repeat_on_misscall">Repeat for X minutes</string>
+    <string name="pref_summary_notifications_and_calls_enable_misscall">Repeats on every minute</string>
+    <string name="pref_notifications_and_calls_enable_misscall">Notify for missed call</string>
+    <string name="prefs_notifications_and_calls_continious_ring">Notification during phone ring</string>
+    <string name="pref_title_notifications_and_calls_repeat_on_call">Repeat call notification</string>
+    <string name="pref_header_notifications_and_calls">Notifications and Calls</string>
+    <string name="title_activity_LenovoWatch_calibration">Watch X Plus calibration</string>
+    <string name="pref_theme_system">System</string>
+    <string name="hr_widget_last_notification">Last notification</string>
+    <string name="menuitem_unknown">Unknown</string>
+    <string name="bip_prefs_shotcuts_summary">Choose the shortcuts on the band screen</string>
+    <string name="bip_prefs_shortcuts">Shortcuts</string>
+    <string name="controlcenter_set_alias">Set Alias</string>
+    <string name="sonyswr12_settings_title">Sony SWR12 settings</string>
+    <string name="sonyswr12_settings_low_vibration">Low vibration enabled</string>
+    <string name="sonyswr12_settings_stamina">Power saving mode on</string>
+    <string name="sonyswr12_settings_alarm_interval">Smart alarm interval in minutes</string>
+    <string name="sonyswr12_settings_low_vibration_summary">Enable low intensity of vibration on wristband</string>
+    <string name="sonyswr12_settings_stamina_summary">Power saving mode turns off periodic auto measuring of heart rate thus increases working time</string>
+    <string name="sonyswr12_settings_alarm_interval_summary">Smart alarm interval is interval before of installed alarm. In this interval device is trying to detect lightest phase of sleep to awake user</string>
+    <string name="battery_detail_activity_title">Battery info</string>
+    <string name="find_lost_device_message">Search for %1$s\?</string>
+    <string name="error_setting_alias">Error setting alias:</string>
+    <string name="calendar_year">Year</string>
+    <string name="calendar_week">Week</string>
+    <string name="calendar_two_weeks">Two weeks</string>
+    <string name="calendar_month">Month</string>
+    <string name="pref_header_other">Other</string>
+    <string name="pref_header_notification_application_settings">Per application settings</string>
+    <string name="pref_title_notification_use_as_deny">Deny notifications from selected apps</string>
+    <string name="pref_message_privacy_mode_off">Display content</string>
+    <string name="pref_applications_settings">Applications list</string>
+    <string name="pref_title_huami_force_new_protocol">New Auth Protocol</string>
+    <string name="chart_get_active_and_synchronize">Do some activity and synchronize device.</string>
+    <string name="prefs_sounds">Sounds</string>
+    <string name="prefs_sounds_summary">Configure when the device will beep</string>
+    <string name="lefun_prefs_hydration_reminder_title">Hydration reminder</string>
+    <string name="activity_prefs_chart_min_steps_per_minute">Minimal steps per minute to detect activity</string>
+    <string name="activity_prefs_chart_min_session_length">Minimal activity length (minutes)</string>
+    <string name="charts_legend_heartrate_average">Heart rate average</string>
+    <string name="dbmanagementactivity_error_cleaning_export_directory">Error erasing files from export directory %1$s</string>
+    <string name="activity_data_management_directory_content_title">Export/Import directory content</string>
+    <string name="activity_DB_clean_export_directory_warning_title">Delete files in the Export/Import directory\?</string>
+    <string name="activity_DB_clean_export_directory_warning_message">Really delete files in the Export/Import directory\?</string>
+    <string name="dbmanagementactivity_export_finished">Deletion finished</string>
+    <string name="activity_type_yoga">Yoga</string>
+    <string name="activity_type_rowing_machine">Rowing Machine</string>
+    <string name="activity_type_cricket">Cricket</string>
+    <string name="activity_type_badminton">Badminton</string>
+    <string name="activity_summary_detail_select_gpx_track">Select GPX track</string>
+    <string name="activity_summary_detail_clear_gpx_track">Clear GPX track</string>
+    <string name="activity_summary_detail_editing_gpx_track">Editing linked GPX track</string>
+    <string name="kind_agps_bundle">AGPS Bundle</string>
+    <string name="devicetype_miband6">Mi Band 6</string>
+    <string name="devicetype_amazfit_trex">Amazfit T-Rex</string>
+    <string name="devicetype_amazfit_gts2_mini">Amazfit GTS 2 Mini</string>
+    <string name="devicetype_um25">UM-25</string>
+    <string name="devicetype_casiogbx100">Casio GBX-100</string>
+    <string name="devicetype_sony_wh_1000xm3">Sony WH-1000XM3</string>
+    <string name="notification_channel_transfer_name">Data transfer</string>
+    <string name="notification_channel_low_battery_name">Low battery</string>
+    <string name="devicetype_lefun">Lefun</string>
+    <string name="menuitem_cycles">Cycle Tracking</string>
+    <string name="menuitem_stress">Stress</string>
+    <string name="menuitem_dnd">DND</string>
+    <string name="menuitem_stopwatch">Stopwatch</string>
+    <string name="menuitem_events">Events</string>
+    <string name="pref_title_lower_button_function_short">Lower Button short</string>
+    <string name="about_core_team_title">Core Team (in order of first code contribution)</string>
+    <string name="about_contributors">Contributors</string>
+    <string name="error_version_check_extreme_caution">CAUTION: Error when checking version information! You should not continue! Saw version name \"%s\"</string>
+    <string name="require_location_provider">Location must be enabled</string>
+    <string name="error_exporting_device_preferences">Error exporting device specific preferences</string>
+    <string name="averageStrokesPerSecond">Average Strokes</string>
+    <string name="ascentSeconds">Ascending</string>
+    <string name="descentSeconds">Descending</string>
+    <string name="baseAltitude">Base Elevation</string>
+    <string name="averageSpeed">Average Speed</string>
+    <string name="swolf_index">swolf index</string>
+    <string name="swim_style">swim style</string>
+    <string name="laps_unit">laps</string>
+    <string name="calories_unit">kcal</string>
+    <string name="seconds_m">sec/m</string>
+    <string name="minutes_km">min/km</string>
+    <string name="km">km</string>
+    <string name="mi">mi</string>
+    <string name="Strokes">Strokes</string>
+    <string name="Swimming">Swimming</string>
+    <string name="Distance">Distance</string>
+    <string name="Elevation">Elevation</string>
+    <string name="Speed">Speed</string>
+    <string name="Activity">Activity</string>
+    <string name="Steps">Steps</string>
+    <string name="activity_detail_start_label">Start</string>
+    <string name="activity_detail_end_label">End</string>
+    <string name="activity_detail_duration_label">Duration</string>
+    <string name="activity_detail_show_gps_label">Show GPS Track</string>
+    <string name="prefs_events_forwarding_title">Device actions</string>
+    <string name="prefs_events_forwarding_fellsleep">On Fall Asleep</string>
+    <string name="prefs_events_forwarding_wokeup">On Wake Up</string>
+    <string name="prefs_events_forwarding_startnonwear">On Not Wearing</string>
+    <string name="prefs_events_forwarding_broadcast_title">Broadcast message</string>
+    <string name="pref_header_filter">Sports Activities Filter</string>
+    <string name="activity_summaries_all_devices">All devices</string>
+    <string name="medley">Medley</string>
+    <string name="activity_filter_from_placeholder">Distant Past</string>
+    <string name="activity_filter_to_placeholder">Today</string>
+    <string name="prefs_key_vibration">Key Vibration</string>
+    <string name="breaststroke">Breaststroke</string>
+    <string name="devicetype_nut_mini">Nut mini</string>
+    <string name="prefs_autolight">Automatic Light</string>
+    <string name="qhybrid_calibration_align_hint">Use the buttons below to align the watch hands to 12:00.</string>
+    <string name="qhybrid_summary_file_management">Upload and download files</string>
+    <string name="prefs_operating_sounds">Operating Sounds</string>
+    <string name="prefs_fake_ring_duration">Fake continuous ringing</string>
+    <string name="fossil_hr_commute_processing">Processing request鈥�</string>
+    <string name="fossil_hr_synced_activity_data">Synchronised activity data</string>
+    <string name="pref_summary_canned_messages_set">Send the messages configured below to your device</string>
+    <string name="fossil_hr_commute_actions_explanation">The actions configured here will appear in the Commute app on your watch. Read the wiki for information on how to handle the intents produced by these actions.</string>
+    <string name="fossil_hr_edit_action">Edit action</string>
+    <string name="fossil_hr_new_action_cancel">Cancel</string>
+    <string name="fossil_hr_new_action">New action</string>
+    <string name="qhybrid_calibration_clockwise">Clockwise</string>
+    <string name="qhybrid_calibration_1_step">1 step</string>
+    <string name="qhybrid_calibration_10_steps">10 steps</string>
+    <string name="qhybrid_calibration_100_steps">100 steps</string>
+    <string name="qhybrid_watchface_configuration_old_firmware">Watchface configuration screen for watches with firmware version DN1.0.2.19r and lower</string>
+    <string name="qhybrid_pref_title_actions">Actions</string>
+    <string name="qhybrid_pref_summary_actions">Actions for the Commute app</string>
+    <string name="button_watchface_edit_name">Edit name</string>
+    <string name="button_watchface_add_widget">Add widget</string>
+    <string name="watchface_dialog_title_set_name">Set watchface name</string>
+    <string name="watchface_toast_settings_incomplete">Settings incomplete, widget not added</string>
+    <string name="watchface_dialog_title_add_widget">Add widget</string>
+    <string name="watchface_widget_type_weather">Weather</string>
+    <string name="watchface_widget_type_steps">Steps</string>
+    <string name="watchface_widget_type_heart_rate">Heart rate</string>
+    <string name="prefs_active_noise_cancelling">Active Noise Cancelling</string>
+    <string name="pref_header_sony_ambient_sound_control">Ambient Sound Control</string>
+    <string name="sony_ambient_sound">Mode</string>
+    <string name="sony_ambient_sound_off">Off</string>
+    <string name="sony_ambient_sound_noise_cancelling">Noise Cancelling</string>
+    <string name="sony_ambient_sound_wind_noise_reduction">Wind Noise Reduction</string>
+    <string name="sony_sound_position_front_right">Front Right</string>
+    <string name="sony_sound_position_rear_left">Rear Left</string>
+    <string name="watchface_dialog_widget_color_black">Black</string>
+    <string name="devicetype_waspos">Wasp-os</string>
+    <string name="devicetype_amazfit_gts2">Amazfit GTS 2</string>
+    <string name="devicetype_amazfit_gts2e">Amazfit GTS 2e</string>
+    <string name="watchface_widget_type_battery">Battery</string>
+    <string name="mi_h">mph</string>
+    <string name="movement_intensity">Movement intensity</string>
+    <string name="lefun_prefs_antilost_summary">The band will vibrate if your phone disconnects from the band</string>
+    <string name="lefun_prefs_interface_language_title">Interface language</string>
+    <string name="activity_type_soccer">Football</string>
+    <string name="activity_type_pingpong">Ping Pong</string>
+    <string name="activity_summary_today">Today</string>
+    <string name="activity_type_basketball">Basketball</string>
+    <string name="devicetype_amazfit_band5">Amazfit Band 5</string>
+    <string name="swolfIndex">swolfIndex</string>
+    <string name="meters">m</string>
+    <string name="pref_header_statistics">Sports Activities Statistics</string>
+    <string name="activity_error_no_app_for_png">To share this screenshot, install an app which can handle image files.</string>
+    <string name="devicetype_amazfit_neo">Amazfit Neo</string>
+    <string name="devicetype_amazfit_vergel">Amazfit Verge Lite</string>
+    <string name="pref_title_lower_button_function_long">Lower Button long</string>
+    <string name="pref_title_lower_button_function_double">Lower Button double</string>
+    <string name="gpx_receiver_activity_title">GPX Receiver Gadgetbridge</string>
+    <string name="activity_filter_name_contains">Label</string>
+    <string name="sony_equalizer_band_1000">1k</string>
+    <string name="watchface_dialog_widget_preset_right">Right</string>
+    <string name="prefs_notifications_enable">Enable notifications</string>
+    <string name="battery_level">Battery level</string>
+    <string name="calendar_three_months">3 months</string>
+    <string name="fw_upgrade_notice_amazfitvergel">You are about to install the %s firmware on your Amazfit Verge Lite.
+\n
+\nPlease make sure to install the .fw file, then the .res file, and finally the .gps file. Your watch will reboot after installing the .fw file.
+\n
+\nNote: You do not have to install .res and .gps if these files are exactly the same as the ones previously installed.
+\n
+\nPROCEED AT YOUR OWN RISK!</string>
+    <string name="pref_header_system">System</string>
+    <string name="pref_header_equalizer">Equalizer</string>
+    <string name="pref_title_notification_use_as">Use the Applications list to鈥�</string>
+    <string name="calendar_six_months">6 months</string>
+    <string name="fw_upgrade_notice_amazfitneo">You are about to install the %s firmware on your Amazfit Neo.
+\n
+\nYour band will reboot after installing the .fw file.
+\n
+\nPROCEED AT YOUR OWN RISK!</string>
+    <string name="pref_title_message_privacy_mode">Message privacy mode</string>
+    <string name="pref_message_privacy_mode_complete">Hide content</string>
+    <string name="watchface_install_info">You are about to install the following watchface:
+\n
+\n%1$s
+\nVersion %2$s by %3$s
+\n</string>
+    <string name="chart_no_active_data">No activities detected.</string>
+    <string name="calendar_day">Day</string>
+    <string name="pref_title_notification_use_as_allow">Allow notifications from selected apps</string>
+    <string name="steps_unit">steps</string>
+    <string name="meters_second">m/s</string>
+    <string name="pref_title_connected_advertisement">Visible while connected</string>
+    <string name="pref_summary_connected_advertisement">Makes the device discoverable via Bluetooth even when connected</string>
+    <string name="pref_summary_huami_force_new_protocol">Enable if your device no longer connects after a firmware upgrade</string>
+    <string name="devicestatus_upload_validating">Upload is being validated</string>
+    <string name="devicestatus_upload_aborted">Upload has been aborted!</string>
+    <string name="qhybrid_calibration_counterclockwise">Anti-clockwise</string>
+    <string name="lefun_prefs_hydration_reminder_summary">The band will vibrate to remind you to drink water</string>
+    <string name="lefun_prefs_antilost_title">Anti-loss</string>
+    <string name="lefun_prefs_hydration_reminder_interval_title">Hydration reminder interval (in minutes)</string>
+    <string name="set">Set</string>
+    <string name="activity_type_swimming_openwater">Swimming (Open water)</string>
+    <string name="activity_type_indoor_cycling">Indoor Cycling</string>
+    <string name="activity_type_elliptical_trainer">Elliptical Trainer</string>
+    <string name="activity_summary_detail">Sport Activity Detail</string>
+    <string name="activity_summary_edit_name_title">Edit label</string>
+    <string name="devicetype_amazfit_bips_lite">Amazfit Bip S Lite</string>
+    <string name="devicetype_amazfit_bipu">Amazfit Bip U</string>
+    <string name="devicetype_amazfit_bipupro">Amazfit Bip U Pro</string>
+    <string name="devicetype_amazfit_gtr2e">Amazfit GTR 2e</string>
+    <string name="devicetype_tlw64">TLW64</string>
+    <string name="devicetype_smaq2oss">SMA-Q2 OSS</string>
+    <string name="toast_app_must_not_be_selected">App must not be selected to be configured</string>
+    <string name="widget_settings_select_device_title">Select device</string>
+    <string name="activity_db_management_clean_export_directory_text">Exported files in the Export/Import directory are accessible by any app on your device. You might like to remove these files after synchronisation or backup. Make sure to have a backup before deleting them. GPX files, sub-directories and auto-exported database file (if exist) will not be deleted. The path to the Export/Import directory is:</string>
+    <string name="activity_type_strength_training">Strength Training</string>
+    <string name="devicetype_amazfit_gtr2">Amazfit GTR 2</string>
+    <string name="toast_app_must_be_selected">App must be selected to be configured</string>
+    <string name="companiondevice_pairing">CompanionDevice Pairing</string>
+    <string name="averageLapPace">Average Lap Pace</string>
+    <string name="laps">Laps</string>
+    <string name="cm">cm</string>
+    <string name="activity_summaries_statistics">Statistics</string>
+    <string name="pref_title_upper_button_function_short">Upper Button short</string>
+    <string name="pref_title_upper_button_function_double">Upper Button double</string>
+    <string name="about_activity_title_generic">About Gadgetbridge</string>
+    <string name="gpx_receiver_files_received">GPX file(s) received:</string>
+    <string name="gpx_receiver_overwrite_some_files">Some file(s) already exist. Overwrite\?</string>
+    <string name="about_additional_contributions">Many thanks to all unlisted contributors for contributing code, translations, support, ideas, motivation, bug reports, money鈥� 鉁�</string>
+    <string name="maxPace">Fastest Pace</string>
+    <string name="averageStride">Average Stride</string>
+    <string name="swimStyle">swimStyle</string>
+    <string name="km_h">km/h</string>
+    <string name="strokes_second">str/s</string>
+    <string name="strokes_unit">str</string>
+    <string name="seconds">sec</string>
+    <string name="prefs_events_forwarding_summary">Use device events to trigger actions and Android broadcasts</string>
+    <string name="activity_filter_date_from">From</string>
+    <string name="activity_filter_reset_filter">Reset Filter</string>
+    <string name="no">No</string>
+    <string name="sports_activity_quick_filter_this_week">This week</string>
+    <string name="qhybrid_pref_summary_external_intents">Allows other Android apps to upload/overwrite files</string>
+    <string name="devicetype_amazfit_trex_pro">Amazfit T-Rex Pro</string>
+    <string name="gps_track">GPS track</string>
+    <string name="prefs_events_forwarding_action_title">Run action</string>
+    <string name="activity_filter_date_to">To</string>
+    <string name="activity_filter_filter_title">Filter</string>
+    <string name="addto_filter">Add to filter</string>
+    <string name="qhybrid_title_apps_management">Apps management</string>
+    <string name="qhybrid_title_calibration">Calibration</string>
+    <string name="sony_automatic_power_off_3_hour">3 hours</string>
+    <string name="activity_filter_apply_filter">Apply Filter</string>
+    <string name="activity_filter_individual_items">Individually selected items</string>
+    <string name="activity_summaries_all_activities">All Activities</string>
+    <string name="sports_activity_quick_filter_7days">7 days</string>
+    <string name="fossil_hr_unavailable_unauthed">Not available in unauthenticated mode</string>
+    <string name="fossil_hr_auth_failed">Authentication failed, limited functionality</string>
+    <string name="pref_title_physical_buttons">Physical buttons</string>
+    <string name="yes">Yes</string>
+    <string name="watchface_widget_type_custom">Custom widget</string>
+    <string name="qhybrid_title_watchface">Watchface configuration</string>
+    <string name="qhybrid_title_apps">Apps</string>
+    <string name="qhybrid_title_background_image">Background image</string>
+    <string name="watchface_widget_type_date">Date</string>
+    <string name="pref_title_developer_settings">Developer settings</string>
+    <string name="pref_summary_developer_settings">Settings and functionality used by developers</string>
+    <string name="qhybrid_pref_title_external_intents">Allow dangerous external intents</string>
+    <string name="fw_upgrade_notice_miband5">You are about to install the %s firmware on your Mi Band 5.
+\n
+\nPlease make sure to install the .fw file, and after that the .res file. Your band will reboot after installing the .fw file.
+\n
+\nNote: You do not have to install .res if it is exactly the same as the one previously installed.
+\n
+\nPROCEED AT YOUR OWN RISK!</string>
+    <string name="activity_type_jump_roping">Skipping rope</string>
+    <string name="sony_automatic_power_off_off">Do not turn off</string>
+    <string name="sony_equalizer_band_400">400</string>
+    <string name="button_watchface_save_apply">Save and apply</string>
+    <string name="sony_touch_sensor">Touch sensor control</string>
+    <string name="pref_header_sony_equalizer_preset_custom_2">Custom Preset 2</string>
+    <string name="pref_header_sony_equalizer_preset_custom_1">Custom Preset 1</string>
+    <string name="sony_equalizer_preset_custom_2">Custom 2</string>
+    <string name="sony_equalizer_preset_treble_boost">Treble Boost</string>
+    <string name="sony_automatic_power_off_30_min">30 minutes</string>
+    <string name="sony_automatic_power_off_1_hour">1 hour</string>
+    <string name="sony_automatic_power_off_5_min">5 minutes</string>
+    <string name="sony_automatic_power_off">Automatic Power Off</string>
+    <string name="sony_equalizer_clear_bass">Clear Bass</string>
+    <string name="sony_equalizer_band_6300">6.3k</string>
+    <string name="sony_equalizer_preset_custom_1">Custom 1</string>
+    <string name="sony_equalizer_preset_off">Off</string>
+    <string name="sony_warn_sbc_codec">Warning: These settings only work for the SBC audio codec.</string>
+    <string name="sony_notification_voice_guide">Notifications &amp; Voice Guide</string>
+    <string name="sony_equalizer_band_16000">16k</string>
+    <string name="sony_equalizer_band_2500">2.5k</string>
+    <string name="sony_equalizer_preset_manual">Manual</string>
+    <string name="sony_equalizer_preset_speech">Speech</string>
+    <string name="sony_equalizer_preset_bass_boost">Bass Boost</string>
+    <string name="sony_equalizer_preset_vocal">Vocal</string>
+    <string name="sony_equalizer_preset_relaxed">Relaxed</string>
+    <string name="sony_equalizer_preset_mellow">Mellow</string>
+    <string name="sony_equalizer_preset_excited">Excited</string>
+    <string name="sony_equalizer_preset_bright">Bright</string>
+    <string name="sony_equalizer">Equalizer</string>
+    <string name="sony_surround_mode_concert_hall">Concert Hall</string>
+    <string name="sony_surround_mode_outdoor_stage">Outdoor Stage</string>
+    <string name="sony_surround_mode_club">Club</string>
+    <string name="sony_surround_mode_arena">Arena</string>
+    <string name="sony_surround_mode_off">Off</string>
+    <string name="button_watchface_preview">Preview on watch</string>
+    <string name="sony_surround_mode">Surround Mode</string>
+    <string name="sony_sound_position_rear_right">Rear Right</string>
+    <string name="appmanager_app_edit">Edit</string>
+    <string name="prefs_sleep_time_summary">Specifies times when sleep is registered</string>
+    <string name="sony_ambient_sound_level">Ambient Sound Level</string>
+    <string name="watchface_dialog_widget_type">Type:</string>
+    <string name="watchface_dialog_widget_y_coordinate">Y coordinate (max 240):</string>
+    <string name="watchface_dialog_widget_preset_top">Top</string>
+    <string name="watchface_dialog_widget_preset_bottom">Bottom</string>
+    <string name="watchface_dialog_widget_preset_left">Left</string>
+    <string name="qhybrid_title_watchface_designer">Watchface designer</string>
+    <string name="watchface_dialog_widget_color_white">White</string>
+    <string name="watchface_dialog_widget_x_coordinate">X coordinate (max 240):</string>
+    <string name="watchface_dialog_widget_color">Colour:</string>
+    <string name="watchface_setting_wrist_flick_move_relative">Move hands relative to time:</string>
+    <string name="watchface_dialog_title_settings">Watchface settings</string>
+    <string name="watchface_setting_title_display_refresh_timeout">Display refresh timeout</string>
+    <string name="watchface_setting_display_refresh_full">Full refresh (in minutes):</string>
+    <string name="watchface_setting_display_refresh_partial">Partial refresh (in minutes):</string>
+    <string name="watchface_setting_desc_wrist_flick">(to disable completely, enable relative movement and set all values to 0)</string>
+    <string name="watchface_setting_wrist_flick_hour">Hour hand (-360 to 360):</string>
+    <string name="watchface_setting_wrist_flick_minute">Minute hand (-360 to 360):</string>
+    <string name="watchface_setting_wrist_flick_duration">Duration (in ms):</string>
+    <string name="watchface_cache_confirm_overwrite">A watchface with this name already exists in the cache. Do you want to overwrite it\?</string>
+    <string name="watchface_widget_type_2nd_tz">2nd time zone</string>
+    <string name="watchface_widget_type_chance_rain">Chance of rain</string>
+    <string name="watchface_setting_power_saving_display">Disable display updates while off wrist</string>
+    <string name="prefs_sleep_time_label">Define sleep hours</string>
+    <string name="prefs_autoheartrate_measurement">Automatic Heart Rate measurements</string>
+    <string name="watchface_upload_failed">Upload of the watchface failed. Please try again.</string>
+    <string name="watchface_widget_type_active_mins">Active minutes</string>
+    <string name="watchface_setting_title_power_saving">Power saving</string>
+    <string name="watchface_setting_power_saving_hands">Disable hands movement while off wrist</string>
+    <string name="prefs_vibration_enable">Enable vibrations</string>
+    <string name="enable_vibrations_summary">Vibrations for calls, messages, notifications and more</string>
+    <string name="prefs_sleep_time">Sleep times</string>
+    <string name="nothing_prefs_inear_title">In-Ear detection</string>
+    <string name="prefs_pressure_relief">Pressure relief with ambient sound</string>
+    <string name="sony_sound_position_front_left">Front Left</string>
+    <string name="enable_notifications_summary">Notifications for calls, messages and more</string>
+    <string name="prefs_autoheartrate">Automatic Heart Rate</string>
+    <string name="prefs_autoheartrate_interval">Frequency of measurements</string>
+    <string name="devicetype_galaxybuds_live">Galaxy Buds Live</string>
+    <string name="nothing_prefs_audiomode_title">Audio mode</string>
+    <string name="sony_sound_position">Sound Position</string>
+    <string name="sony_sound_position_off">Off</string>
+    <string name="prefs_autoheartrate_summary">Periodical Heart Rate measurements during the day and also while asleep</string>
+    <string name="prefs_autoheartrate_sleep">Take measurements during sleep</string>
+    <string name="sony_sound_position_front">Front</string>
+    <string name="pref_title_equalizer_normal">Normal</string>
+    <string name="prefs_active_noise_cancelling_summary">Block noises of the surroundings</string>
+    <string name="pressure_relief_summary">Prevent feeling of pressure in ears when not using Active Noise Cancelling</string>
+    <string name="sony_ambient_sound_ambient_sound">Ambient Sound</string>
+    <string name="sony_ambient_sound_focus_voice">Focus on Voice</string>
+    <string name="menuitem_sleep">Sleep</string>
+    <string name="menuitem_pomodoro">Pomodoro Tracker</string>
+    <string name="devicetype_fitpro">FitPro</string>
+    <string name="menuitem_spo2">SpO2</string>
+    <string name="menuitem_mutephone">Mute Phone</string>
+    <string name="menuitem_goal">Activity Goal</string>
+    <string name="menuitem_widgets">Widgets</string>
+    <string name="menuitem_temperature">Temperature</string>
+    <string name="about_version">Version %s</string>
+    <string name="about_title">About</string>
+    <string name="about_description_generic">Cloudless copylefted libre replacement for closed source Android gadget apps from vendors.</string>
+    <string name="sports_activity_quick_filter_30days">30 days</string>
+    <string name="sports_activity_quick_filter_select">Time period</string>
+    <string name="device_is_currently_bonded">Already bonded</string>
+    <string name="sports_activity_quick_filter_last_month">Previous month</string>
+    <string name="about_additional_device_support">Additional device support</string>
+    <string name="backstroke">Backstroke</string>
+    <string name="prefs_autoremove_message">Automatically remove SMS notifications</string>
+    <string name="pref_title_ping_tone">Ping tone</string>
+    <string name="devicestatus_disconnected">Device has disconnected!</string>
+    <string name="devicestatus_upload_completed">Upload has completed</string>
+    <string name="prefs_charts_tabs">Charts tabs</string>
+    <string name="prefs_charts_tabs_summary">Visible chart tabs</string>
+    <string name="activity_list_summary_active_steps">Active steps</string>
+    <string name="activity_list_summary_distance">Distance</string>
+    <string name="activity_list_summary_active_time">Active time</string>
+    <string name="activity_list_summary_intensity">Movement
+\nIntensity</string>
+    <string name="activity_list_summary_activities">Activities</string>
+    <string name="dialog_hide">Hide</string>
+    <string name="show_ongoing_activity">Show ongoing activity popup</string>
+    <string name="devicestatus_connecting">Device is connecting</string>
+    <string name="devicestatus_connected">Device is connected</string>
+    <string name="devicestatus_upload_starting">Upload is starting</string>
+    <string name="devicestatus_upload_started">Upload has started</string>
+    <string name="devicestatus_disconnecting">Device is disconnecting!</string>
+    <string name="blood_pressure">Blood pressure</string>
+    <string name="getting_heart_rate">Measuring</string>
+    <string name="heart_rate_result">Measurement results</string>
+    <string name="charts_min_max_heartrate_popup">Lowest heart rate: %1$d
+\nHighest heart rate: %2$d
+\nMovement intensity: %3$s</string>
+    <string name="button_watchface_settings">Watchface settings</string>
+    <string name="watchface_setting_title_wrist_flick">Wrist flick</string>
+    <string name="watchface_dialog_widget_presets">Position presets:</string>
+    <string name="pref_theme_black_background">Use black background in Dark Theme</string>
+    <string name="pref_title_weather_summary">Used for the LineageOS weather provider, other Android versions need to use an app like \"Weather notification\". Find more information in the Gadgetbridge wiki.</string>
+    <string name="discovery_entered_invalid_authkey">The secret auth key you entered is invalid! Long press on the device to edit.</string>
+    <string name="charts_activity_list">Activity list</string>
+    <string name="devicestatus_upload_failed">Upload has failed</string>
+    <string name="firmware_update_progress">Upload is in progress
+\n%1d%% at %.2fkbps (average %.2fkbps)
+\nPart %1d of %1d</string>
+    <string name="activity_prefs_step_length_cm">Step length in cm</string>
+    <string name="activity_prefs_chart_min_steps_per_minute_for_run">Minimal steps per minute to detect run</string>
+    <string name="activity_prefs_chart_max_idle_phase_length">Pause length to separate activities (minutes)</string>
+    <string name="activity_DB_ShowContentButton">Show Export/Import directory content</string>
+    <string name="dbmanagementactivity_export_data_title">Export Data\?</string>
+    <string name="dbmanagementactivity_export_confirmation">Really export data\? Previously exported activity data (if any) and preferences will be overwritten.</string>
+    <string name="activity_db_management_clean_export_directory_label">Delete files in Export/Import directory</string>
+    <string name="devicetype_amazfit_x">Amazfit X</string>
+    <string name="devicetype_zepp_e">Zepp E</string>
+    <string name="menuitem_findphone">Find Phone</string>
+    <string name="menuitem_takephoto">Camera Remote</string>
+    <string name="menuitem_alexa">Alexa</string>
+    <string name="pref_title_middle_button_function_short">Middle Button short</string>
+    <string name="pref_title_upper_button_function_long">Upper Button long</string>
+    <string name="ft">ft</string>
+    <string name="bpm">bpm</string>
+    <string name="sports_activity_quick_filter_this_month">This month</string>
+    <string name="fossil_hr_warning_firmware_too_new">Some functions are disabled because the firmware of the watch is too new</string>
+    <string name="pref_summary_physical_buttons">Configure the functionality of the physical buttons on the watch</string>
+    <string name="sports_activity_quick_filter_last_week">Previous week</string>
+    <string name="freestyle">Freestyle</string>
+    <string name="qhybrid_title_file_management">File management</string>
+    <string name="qhybrid_summary_calibration">Calibrate the watch hands</string>
+    <string name="fossil_hr_edit_action_delete">Delete</string>
+    <string name="button_watchface_select_image">Change background image</string>
+    <string name="pref_title_middle_button_function_double">Middle Button double</string>
+    <string name="flatSeconds">Flat</string>
+    <string name="menuitem_barometer">Barometer</string>
+    <string name="pref_title_middle_button_function_long">Middle Button long</string>
+    <string name="minutes_mi">min/mi</string>
+    <string name="pref_summary_canned_messages_dismisscall">Dismiss calls from the watch with an SMS message</string>
+    <string name="devicetype_galaxybuds">Galaxy Buds</string>
+    <string name="prefs_equalizer_preset">Equalizer Preset</string>
+    <string name="pref_title_equalizer_bass_boost">Bass boost</string>
+    <string name="pref_title_equalizer_soft">Soft</string>
+    <string name="pref_title_equalizer_dynamic">Dynamic</string>
+    <string name="pref_title_equalizer_clear">Clear</string>
+    <string name="pref_title_equalizer_trebble">Treble boost</string>
+    <string name="prefs_dolby_mode">Dolby Mode</string>
+    <string name="prefs_equalizer">Equalizer</string>
+    <string name="prefs_equalizer_summary">Enable or disable equalizer</string>
+    <string name="prefs_dolby_summary">Dolby preset for equalizer</string>
+    <string name="prefs_game_mode">Game mode</string>
+    <string name="prefs_game_mode_summary">Only if your phone supports game mode</string>
+    <string name="prefs_touch_lock">Touch Lock</string>
+    <string name="prefs_touch_lock_summary">Disable touch events</string>
+    <string name="prefs_galaxy_buds_experimental">Experimental</string>
+    <string name="prefs_ambient_volume">Ambient volume</string>
+    <string name="menuitem_flashlight">Flashlight</string>
+    <string name="watchface_widget_type_calories">Calories</string>
+    <string name="devicetype_nothingear1">Nothing Ear (1)</string>
+    <string name="seconds_km">sec/km</string>
+    <string name="averageStrokeDistance">Average Stroke Distance</string>
+    <string name="averageHR">Heartrate</string>
+    <string name="averageKMPaceSeconds">Pace</string>
+    <string name="totalStride">Total stride</string>
+    <string name="activeSeconds">Active</string>
+    <string name="caloriesBurnt">Calories</string>
+    <string name="maxSpeed">Maximum</string>
+    <string name="minPace">Slowest Pace</string>
+    <string name="maxAltitude">Maximum</string>
+    <string name="steps">Steps</string>
+    <string name="minAltitude">Minimum</string>
+    <string name="distanceMeters">Distance</string>
+    <string name="ascentMeters">Uphill</string>
+    <string name="descentMeters">Downhill</string>
+    <string name="error_location_enabled_mandatory">Location must be turned on to scan for devices</string>
+    <string name="ignore_bonded_devices">Ignore bonded devices</string>
+    <string name="ignore_bonded_devices_description">Enabling this option will ignore devices that have been bonded/paired already when scanning</string>
+    <string name="error_retrieving_devices_database">Error retrieving devices from database</string>
+    <string name="pref_check_permission_status_summary">Check and ask for missing permissions even when they might not be instantly needed. Disable this only if your devices actually doesn\'t support any of these features. Not granting a permission might cause issues!</string>
+    <string name="pref_check_permission_status">Check permission status</string>
+    <string name="error_background_service_reason">Starting the background service failed because of an exception - click here for more information.\n\nError:</string>
+    <string name="device_requires_key">Key required</string>
+    <string name="error_background_service_reason_truncated">Starting the background service failed because鈥�</string>
+    <string name="error_background_service">Failed to start background service</string>
+    <string name="companiondevice_pairing_details">Enables the new CompanionDevice API support (only has an effect on Android 8 or above) which will increase reliability if the service needs to be restarted in the background, requires re-pairing using Gadgetbridge to have an effect</string>
+    <string name="permission_granting_mandatory">All these permissions are required and instability might occur if not granted</string>
+    <string name="about_links">Links</string>
+    <string name="menuitem_worldclock">World Clock</string>
+    <string name="menuitem_breathing">Breathing</string>
+    <string name="devicetype_sg2">Lemfo SG2</string>
+    <string name="devicetype_pinetime_jf">PineTime (JF Firmware)</string>
+    <string name="devicetype_miband5">Mi Band 5</string>
+    <string name="activity_summary_yesterday">Yesterday</string>
+    <string name="prefs_right">Right</string>
+    <string name="prefs_galaxy_touch_options">Touch Options</string>
+    <string name="prefs_ambient_voice_focus">Voice Focus</string>
+    <string name="prefs_ambient_voice_summary">Make voice stand out</string>
+    <string name="prefs_ambient_sound">Ambient Sound</string>
+    <string name="prefs_ambient_mode">Ambient Mode</string>
+    <string name="prefs_left">Left</string>
+    <string name="nothing_prefs_inear_summary">Play/pause the music depending if you wear the earbuds</string>
+    <string name="title_activity_notification_management">Notification settings</string>
+    <string name="check_all_applications">Check all applications</string>
+    <string name="uncheck_all_applications">Uncheck all applications</string>
+    <string name="fw_upgrade_notice_amazfit_trex">You are about to install the %s firmware on your Amazfit T-Rex.
+\n
+\nPlease make sure to install the .fw file, then the .res file, and finally the .gps file. Your watch will reboot after installing the .fw file.
+\n
+\nNote: You do not have to install .res and .gps if these files are exactly the same as the ones previously installed.
+\n
+\nPROCEED AT YOUR OWN RISK!</string>
+    <string name="fw_upgrade_notice_miband6">You are about to install the %s firmware on your Mi Band 6.
+\n
+\nPlease make sure to install the .fw file, and after that the .res file. Your band will reboot after installing the .fw file.
+\n
+\nNote: You do not have to install .res if it is exactly the same as the one previously installed.
+\n
+\nPROCEED AT YOUR OWN RISK!</string>
+    <string name="fw_upgrade_notice_amazfitx">You are about to install the %s firmware on your Amazfit X.
+\n
+\nPlease make sure to install the .fw file, and after that the .res file. Your band will reboot after installing the .fw file.
+\n
+\nNote: You do not have to install .res if it is exactly the same as the one previously installed.
+\n
+\nPROCEED AT YOUR OWN RISK!</string>
+    <string name="battery_case">Battery case</string>
+    <string name="left_earbud">Left earbud</string>
+    <string name="right_earbud">Right earbud</string>
+    <string name="prefs_fm_preset_instructions">Long press the button to store preset</string>
+    <string name="prefs_fm_presets_presets">Presets</string>
+    <string name="watchface_dialog_widget_timezone">Time zone:</string>
+    <string name="watchface_dialog_widget_timeout_show_circle">Show circle on timeout:</string>
+    <string name="watchface_dialog_widget_timeout_hide_text">Hide text on timeout:</string>
+    <string name="sony_automatic_power_off_when_taken_off">When taken off</string>
+    <string name="watchface_dialog_widget_update_timeout">Update timeout in minutes:</string>
+    <string name="device_card_activity_card_title">Activity info on device card</string>
+    <string name="prefs_activity_in_device_card_steps_title_summary">Show total steps</string>
+    <string name="device_card_activity_card_title_summary">Choose what activity details are displayed on device card</string>
+    <string name="prefs_activity_in_device_card_distance_title_summary">Distance is calculated from steps and step length (adjustable in Settings - About you)</string>
+    <string name="prefs_activity_in_device_card_sleep_title">Sleep</string>
+    <string name="prefs_activity_in_device_card_title">Show Activity info on device card</string>
+    <string name="prefs_activity_in_device_card_title_summary">Show current steps, distance or sleep on device card</string>
+    <string name="prefs_activity_in_device_card_sleep_title_summary">Show sleep duration</string>
+    <string name="qhybrid_title_on_device_confirmation">Enable on-device pairing confirmation</string>
+    <string name="qhybrid_summary_on_device_confirmation">On-device pairing confirmations can get annoying. Disabling them might lose you functionality.</string>
+    <string name="title_activity_set_reminders">Configure reminders</string>
+    <string name="controlcenter_start_configure_reminders">Configure reminders</string>
+    <string name="reminder_repeat">Repeat</string>
+    <string name="reminder_date">Date</string>
+    <string name="reminder_time">Time</string>
+    <string name="reminder_message">Message</string>
+    <string name="reminder_time_once">%1$s, once</string>
+    <string name="reminder_time_every_day">%1$s, every day</string>
+    <string name="reminder_time_every_week">%1$s, every week</string>
+    <string name="reminder_time_every_year">%1$s, every year</string>
+    <string name="reminder_every_day">Every day</string>
+    <string name="reminder_every_week">Every week</string>
+    <string name="reminder_every_year">Every year</string>
+    <string name="reminder_delete_confirm_title">Delete reminder</string>
+    <string name="reminder_delete_confirm_description">Are you sure you want to delete the reminder\?</string>
+    <string name="reminder_no_free_slots_title">No free slots</string>
+    <string name="title_activity_reminder_details">Reminder details</string>
+    <string name="miband_prefs_reserve_reminder_calendar">Reminders to reserve for upcoming events</string>
+    <string name="reminder_time_every_month">%1$s, every month</string>
+    <string name="reminder_once">Once</string>
+    <string name="reminder_every_month">Every month</string>
+    <string name="reminder_no_free_slots_description">The device has no free slots for reminders (total slots: %1$s)</string>
+    <string name="prefs_reserve_reminder_calendar_summary">Number of calendar events that will be synchronized</string>
+    <string name="mi2_prefs_do_not_disturb_lift_wrist">Activate display upon lift during Do Not Disturb</string>
+    <string name="maxHR">Max Heartrate</string>
+    <string name="spm">steps/min</string>
+    <string name="minSpeed">Minimum</string>
+    <string name="averageCadence">Average Cadence</string>
+    <string name="averageAltitude">Average</string>
+    <string name="maxCadence">Max Cadence</string>
+    <string name="minCadence">Min Cadence</string>
+    <string name="minHR">Min Heartrate</string>
+    <string name="maxStride">Max Stride</string>
+    <string name="minStride">Min Stride</string>
+    <string name="controlcenter_power_off">Power Off</string>
+    <string name="sony_pause_when_taken_off">Pause when headphones are taken off</string>
+    <string name="sony_button_mode_ambient_sound_control">Ambient Sound Control</string>
+    <string name="activity_prefs_discovery_pairing">Discovery and Pairing options</string>
+    <string name="controlcenter_power_off_confirm_title">Power Off</string>
+    <string name="add_test_device">Add test device</string>
+    <string name="devicetype_sony_wf_sp800n">Sony WF-SP800N</string>
+    <string name="controlcenter_power_off_confirm_description">Are you sure you want to power off the device\?</string>
+    <string name="sony_button_mode_left">Button Mode (Left)</string>
+    <string name="discover_unsupported_devices">Discover unsupported devices</string>
+    <string name="sony_button_mode_volume_control">Volume Control</string>
+    <string name="discover_unsupported_devices_description">Enabling this option will display all discovered Bluetooth devices when scanning. Short tap will copy device name and MAC address to the clipboard. Long press will launch `Add test device` dialog.</string>
+    <string name="sony_button_mode_right">Button Mode (Right)</string>
+    <string name="sony_button_mode_off">Off</string>
+    <string name="sony_button_mode_playback_control">Playback Control</string>
+    <string name="pref_header_sony_equalizer_bands">Bands</string>
+    <string name="sony_audio_upsampling">Audio Upsampling</string>
+    <string name="devicetype_bose_qc35">Bose QC35</string>
+    <string name="devicetype_vesc">VESC</string>
+    <string name="descentDistance">Downhill distance</string>
+    <string name="ascentDistance">Uphill distance</string>
+    <string name="activity_type_hiking">Hiking</string>
+    <string name="activity_type_climbing">Climbing</string>
+    <string name="pref_media_rewind">Skip back</string>
+    <string name="pref_device_action_broadcast">Send Broadcast</string>
+    <string name="about_hash">Commit %s</string>
+    <string name="pref_media_play">Media Play</string>
+    <string name="pref_media_pause">Media Pause</string>
+    <string name="pref_media_playpause">Toggle playback</string>
+    <string name="pref_media_volumeup">Volume Up</string>
+    <string name="pref_media_volumedown">Volume Down</string>
+    <string name="distance_format_miles">###.#mi</string>
+    <string name="devicetype_domyos_t540">Domyos T540</string>
+    <string name="pref_button_action_disabled">Disabled</string>
+    <string name="pref_media_next">Next Track</string>
+    <string name="pref_media_previous">Previous Track</string>
+    <string name="pref_media_forward">Skip forward</string>
+    <string name="distance_format_feet">###ft</string>
+    <string name="distance_format_meters">###m</string>
+    <string name="distance_format_kilometers">###.#km</string>
+    <string name="menuitem_menu">Menu</string>
+    <string name="prefs_activity_recognition">Activity recognition settings</string>
+    <string name="pref_activity_recognize_rowing">recognise rowing</string>
+    <string name="pref_activity_recognize_running">recognise running</string>
+    <string name="pref_activity_recognize_biking">recognise biking</string>
+    <string name="pref_activity_recognize_walking">recognise walking</string>
+    <string name="pref_activity_recognition_mode_none">none</string>
+    <string name="pref_activity_recognition_mode_auto">auto</string>
+    <string name="pref_activity_recognition_mode_ask">ask</string>
+    <string name="fossil_hr_button_config_info">Some buttons cannot be configured because their functions are hard-coded in the watch firmware.
+\n
+\nWarning: long-pressing the upper button when a watchface from the official Fossil app is installed will also toggle between showing/hiding widgets.</string>
+    <string name="watchface_dialog_widget_width">Width:</string>
+    <string name="devicetype_sony_wh_1000xm4">Sony WH-1000XM4</string>
+    <string name="sony_connect_two_devices">Connect to 2 devices simultaneously</string>
+    <string name="sony_speak_to_chat_focus_on_voice">Focus on Voice</string>
+    <string name="sony_speak_to_chat_sensitivity_low">Low</string>
+    <string name="sony_speak_to_chat_sensitivity_high">High</string>
+    <string name="sony_speak_to_chat_sensitivity_auto">Automatic</string>
+    <string name="sony_speak_to_chat_timeout_long">Long (1m)</string>
+    <string name="discovery_bluetooth_le_scan">Bluetooth LE scan:</string>
+    <string name="sony_speak_to_chat_timeout_standard">Standard (30s)</string>
+    <string name="sony_speak_to_chat_timeout_short">Short (15s)</string>
+    <string name="sony_speak_to_chat_timeout_off">Off</string>
+    <string name="sony_speak_to_chat_timeout">Timeout</string>
+    <string name="sony_speak_to_chat_sensitivity">Voice Detection Sensitivity</string>
+    <string name="sony_speak_to_chat">Speak-to-chat</string>
+    <string name="device_unsupported">UNSUPPORTED</string>
+    <string name="discovery_bluetooth_scan">Bluetooth scan:</string>
+    <string name="devicetype_sonyswr12">Sony SWR12</string>
+    <string name="sony_anc_optimize_confirmation_title">Noise Cancelling Optimiser</string>
+    <string name="sony_anc_optimizer_status_wearing_condition">Measuring wearing condition鈥�</string>
+    <string name="start">Start</string>
+    <string name="pref_header_sony_device_info">Device Information</string>
+    <string name="sony_anc_optimizer_status_not_running">Not Running</string>
+    <string name="sony_anc_optimizer_status_atmospheric_pressure">Measuring atmostpheric pressure鈥�</string>
+    <string name="sony_anc_optimizer_status_analyzing">Analyzing鈥�</string>
+    <string name="sony_anc_optimizer_status_finished">Finishing鈥�</string>
+    <string name="sony_anc_optimize_description">Click to start the noise cancelling optimiser.</string>
+    <string name="sony_anc_optimize_title">Optimise</string>
+    <string name="unknown">Unknown</string>
+    <string name="pref_anc_optimizer_state_pressure">Atmospheric pressure</string>
+    <string name="sony_anc_optimizer_status_starting">Starting鈥�</string>
+    <string name="audio_codec">Audio Codec</string>
+    <string name="pref_header_sony_anc_optimizer">Noise Cancelling Optimiser</string>
+    <string name="sony_anc_optimize_confirmation_description">Use the headphones as you normally would. If the wearing condition or atmospheric pressure change, run the optimiser again.</string>
+    <string name="devicetype_amazfit_pop_pro">Amazfit Pop Pro</string>
+    <string name="devicetype_amazfit_pop">Amazfit Pop</string>
+    <string name="menuitem_nothing">Nothing</string>
+    <string name="autoExport_lastTime_label">Last AutoExport: %1$s</string>
+    <string name="activity_db_management_autoexport_scheduled_no">AutoExport has not been not scheduled.</string>
+    <string name="pref_title_notifications_generic_settings">Android notification settings</string>
+    <string name="activity_db_management_autoexport_scheduled_yes">AutoExport has (originally) been scheduled for %1$s</string>
+    <string name="activity_db_management_autoexport_location">Location could not be understood. Likely an issue of newer Android permission system. Most likely, autoexport is not working now.</string>
+    <string name="pref_title_opentracks_packagename">OpenTracks package name</string>
+    <string name="pref_summary_opentracks_packagename">Used for starting/stopping GPS track recording in external fitness app.</string>
+    <string name="activity_db_management_autoexport_enabled_yes">AutoExport is enabled.</string>
+    <string name="activity_db_management_autoexport_enabled_no">AutoExport is not enabled.</string>
+    <string name="pref_device_action_fitness_app_control_start">Fitness App Tracking Start</string>
+    <string name="pref_device_action_fitness_app_control_stop">Fitness App Tracking Stop</string>
+</resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml
new file mode 100644
index 0000000..8df44c8
--- /dev/null
+++ b/app/src/main/res/values-es/strings.xml
@@ -0,0 +1,3693 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources xmlns:tools="http://schemas.android.com/tools">
+    <string name="application_name_generic">Gadgetbridge</string>
+    <string name="title_activity_controlcenter_generic">Gadgetbridge</string>
+    <string name="action_settings">Configuraci贸n</string>
+    <string name="action_debug">Depuraci贸n</string>
+    <string name="action_quit">Salir</string>
+    <string name="action_donate">Hacer donaci贸n</string>
+    <string name="controlcenter_fetch_activity_data">Sincronizar</string>
+    <string name="controlcenter_find_device">Localizar dispositivo perdido</string>
+    <string name="controlcenter_take_screenshot">Capturar pantalla</string>
+    <string name="controlcenter_disconnect">Desconectar</string>
+    <string name="controlcenter_delete_device">Borrar Dispositivo</string>
+    <string name="controlcenter_delete_device_name">Borrar %1$s</string>
+    <string name="controlcenter_delete_device_dialogmessage">隆Esta acci贸n borrar谩 el dispositivo y toda su informaci贸n asociada!</string>
+    <string name="controlcenter_navigation_drawer_open">Abrir el caj贸n de navegaci贸n</string>
+    <string name="controlcenter_navigation_drawer_close">Cerrar el caj贸n de navegaci贸n</string>
+    <string name="controlcenter_snackbar_need_longpress">Mantener pulsado el icono para desconectar</string>
+    <string name="controlcenter_snackbar_disconnecting">Desconectando</string>
+    <string name="controlcenter_snackbar_connecting">Conectando鈥�</string>
+    <string name="controlcenter_snackbar_requested_screenshot">Realizando una captura de pantalla del dispositivo</string>
+    <string name="title_activity_debug">Depuraci贸n</string>
+    <!--Strings related to AppManager-->
+    <string name="title_activity_appmanager">Gestor de aplicaciones</string>
+    <string name="appmanager_cached_watchapps_watchfaces">Aplicaciones en antememoria</string>
+    <string name="appmanager_installed_watchapps">Aplicaciones instaladas</string>
+    <string name="appmanager_installed_watchfaces">Esferas instaladas</string>
+    <string name="appmananger_app_delete">Eliminar</string>
+    <string name="appmananger_app_delete_cache">Eliminar y quitar de antememoria</string>
+    <string name="appmananger_app_reinstall">Reinstalar</string>
+    <string name="appmanager_app_openinstore">Buscar en tienda de aplicaciones de Pebble</string>
+    <string name="appmanager_health_activate">Activar</string>
+    <string name="appmanager_health_deactivate">Desactivar</string>
+    <string name="appmanager_hrm_activate">Activar Monitor de Ritmo Card铆aco</string>
+    <string name="appmanager_hrm_deactivate">Desactivar Monitor de Ritmo Card铆aco</string>
+    <string name="appmanager_weather_activate">Activar la aplicaci贸n Tiempo del sistema</string>
+    <string name="appmanager_weather_deactivate">Desactivar la aplicaci贸n Tiempo del Sistema</string>
+    <string name="appmanager_weather_install_provider">Instalar la aplicaci贸n de notificaci贸n del Tiempo</string>
+    <string name="app_configure">Configurar</string>
+    <string name="app_move_to_top">Mover arriba</string>
+    <!--Strings related to CalBlacklist-->
+    <string name="title_activity_calblacklist">Calendarios en lista negra</string>
+    <!--Strings related to FwAppInstaller-->
+    <string name="title_activity_fw_app_insaller">Instalador de FW/App</string>
+    <string name="fw_upgrade_notice">Est谩 a punto de instalar el %s.</string>
+    <string name="fw_multi_upgrade_notice">Est谩 a punto de instalar los 芦firmwares禄 %1$s y %2$s en sustituci贸n de los que hay actualmente en su Mi Band.</string>
+    <string name="miband_firmware_known">Este firmware ha sido probado y se sabe que es compatible con Gadgetbridge.</string>
+    <string name="miband_firmware_unknown_warning">Este firmware no ha sido probado y puede que no sea compatible con Gadgetbridge. 
+\n 
+\n隆NO se recomienda la instalaci贸n en tu Mi Band!</string>
+    <string name="miband_firmware_suggest_whitelist">Si a煤n deseas continuar y despu茅s las cosas contin煤an funcionando correctamente, pide a los desarrolladores de Gadgetbridge que incluyan en la lista blanca la versi贸n de firmware de %s.</string>
+    <!--Strings related to Settings-->
+    <string name="title_activity_settings">Ajustes</string>
+    <string name="pref_header_general">Ajustes generales</string>
+    <string name="pref_title_general_autoconnectonbluetooth">Conectarse al (los) dispositivo(s) Gadgetbridge cuando el Bluetooth est谩(n) activado(s)</string>
+    <string name="pref_title_general_autostartonboot">Iniciar autom谩ticamente</string>
+    <string name="pref_title_general_autoreconnect">Reconectar autom谩ticamente</string>
+    <string name="pref_title_audio_player">Reproductor de audio favorito</string>
+    <string name="pref_default">Predeterminado</string>
+    <string name="pref_header_datetime">Fecha y hora</string>
+    <string name="pref_title_datetime_syctimeonconnect">Sincronizar hora</string>
+    <string name="pref_summary_datetime_syctimeonconnect">Sincroniza la hora con el dispositivo o dispositivos Gadgetbridge cuando se conecta, cuando cambia la hora o la zona horaria en el dispositivo Android y peri贸dicamente</string>
+    <string name="pref_title_theme">Tema</string>
+    <string name="pref_theme_light">Claro</string>
+    <string name="pref_theme_dark">Oscuro</string>
+    <string name="pref_title_language">Idioma</string>
+    <string name="pref_title_minimize_priority">Ocultar la notificaci贸n de Gadgetbridge</string>
+    <string name="pref_summary_minimize_priority_off">El icono en la barra de estado y la notificaci贸n en la pantalla de bloqueo est谩n activos</string>
+    <string name="pref_summary_minimize_priority_on">El icono en la barra de estado y la notificaci贸n en la pantalla de bloqueo est谩n ocultos</string>
+    <string name="pref_header_notifications">Notificaciones</string>
+    <string name="pref_title_notifications_repetitions">Repeticiones</string>
+    <string name="pref_title_notifications_call">Llamadas telef贸nicas</string>
+    <string name="pref_title_notifications_sms">SMS</string>
+    <string name="pref_title_notifications_pebblemsg">Mensajes de Pebble</string>
+    <string name="pref_summary_notifications_pebblemsg">Soporte para aplicaciones que env铆an notificaciones al Pebble a trav茅s de PebbleKit.</string>
+    <string name="pref_title_notifications_generic">Soporte para notificaciones gen茅ricas</string>
+    <string name="pref_title_whenscreenon">鈥ambi茅n con pantalla encendida</string>
+    <string name="pref_title_notification_filter">No Molestar</string>
+    <string name="pref_summary_notification_filter">Bloquear todas las notificaciones cuando est谩 activado el modo \"No molestar\" en el tel茅fono</string>
+    <string name="pref_title_transliteration">Transcripci贸n</string>
+    <string name="pref_summary_transliteration">Activar en caso de que su dispositivo no soporte la fuente de su idioma</string>
+    <string name="always">Siempre</string>
+    <string name="when_screen_off">Cuando la pantalla est谩 apagada</string>
+    <string name="never">Nunca</string>
+    <string name="pref_header_privacy">Privacidad</string>
+    <string name="pref_title_call_privacy_mode">Modo de privacidad de llamada</string>
+    <string name="pref_call_privacy_mode_off">Mostrar nombre y n煤mero</string>
+    <string name="pref_call_privacy_mode_name">Ocultar nombre pero mostrar n煤mero</string>
+    <string name="pref_call_privacy_mode_number">Ocultar el n煤mero pero mostrar el nombre</string>
+    <string name="pref_call_privacy_mode_complete">Ocultar nombre y n煤mero</string>
+    <string name="pref_blacklist_calendars">Poner calendarios en lista negra</string>
+    <string name="pref_header_cannned_messages">Mensajes predeterminados</string>
+    <string name="pref_title_canned_replies">Respuestas</string>
+    <string name="pref_title_canned_reply_suffix">Sufijo habitual</string>
+    <string name="pref_title_canned_messages_dismisscall">Rechazar llamada</string>
+    <string name="pref_title_canned_messages_set">Actualizar en el dispositivo</string>
+    <string name="pref_header_development">Opciones de desarrollador</string>
+    <string name="pref_title_development_miaddr">Direcci贸n de MiBand</string>
+    <string name="pref_title_pebble_settings">Ajustes de Pebble</string>
+    <string name="pref_header_activitytrackers">Rastreadores de Actividad</string>
+    <string name="pref_title_pebble_activitytracker">Rastreador de actividad preferido</string>
+    <string name="pref_title_pebble_sync_health">Sincronizar con Pebble Health</string>
+    <string name="pref_title_pebble_sync_misfit">Sincronizar con Misfit</string>
+    <string name="pref_title_pebble_sync_morpheuz">Sincronizar con Morpheuz</string>
+    <string name="pref_title_enable_outgoing_call">Soporte para llamadas salientes</string>
+    <string name="pref_summary_enable_outgoing_call">Desactivar esto evitar谩 que el Pebble 2/LE vibre en llamadas salientes</string>
+    <string name="pref_title_enable_pebblekit">Permitir el acceso a aplicaciones Android de terceros</string>
+    <string name="pref_summary_enable_pebblekit">Activar el soporte experimental para aplicaciones Android que usan PebbleKit</string>
+    <string name="pref_header_pebble_timeline">Timeline Pebble</string>
+    <string name="pref_title_sunrise_sunset">Salida y puesta de Sol</string>
+    <string name="pref_summary_sunrise_sunset">Enviar las horas de salida y puesta de Sol bas谩ndose en la localizaci贸n a la timeline del Pebble</string>
+    <string name="pref_title_enable_calendar_sync">Sincronizar calendario</string>
+    <string name="pref_summary_enable_calendar_sync">Enviar los eventos del calendario a la timeline</string>
+    <string name="pref_title_autoremove_notifications">Eliminar autom谩ticamente las notificaciones rechazadas</string>
+    <string name="pref_summary_autoremove_notifications">Las notificaciones se borran autom谩ticamente del dispositivo cuando se rechazan en el tel茅fono</string>
+    <string name="pref_title_pebble_privacy_mode">Modo privado</string>
+    <string name="pref_pebble_privacy_mode_off">Notificaci贸n normal</string>
+    <string name="pref_pebble_privacy_mode_content">Desplaza la notificaci贸n fuera de la pantalla</string>
+    <string name="pref_pebble_privacy_mode_complete">Muestra solo el icono de notificaci贸n</string>
+    <string name="pref_header_location">Localizaci贸n</string>
+    <string name="pref_title_location_aquire">Obtener ubicaci贸n</string>
+    <string name="pref_title_location_latitude">Latitud</string>
+    <string name="pref_title_location_longitude">Longitud</string>
+    <string name="pref_title_location_keep_uptodate">Mantener la localizaci贸n actualizada</string>
+    <string name="pref_summary_location_keep_uptodate">Intenta obtener la ubicaci贸n actual en curso, utiliza la ubicaci贸n almacenada como alternativa</string>
+    <string name="toast_enable_networklocationprovider">Por favor, active la ubicaci贸n por red</string>
+    <string name="toast_aqurired_networklocation">localizaci贸n encontrada</string>
+    <string name="pref_title_pebble_forceprotocol">Forzar protocolo de notificaci贸n</string>
+    <string name="pref_summary_pebble_forceprotocol">Esta opci贸n obliga a usar el 煤ltimo protocolo de notificaci贸n dependiendo de la versi贸n del firmware. 隆SABE LO QUE EST脕S HACIENDO!</string>
+    <string name="pref_title_pebble_forceuntested">Activar caracter铆sticas no probadas</string>
+    <string name="pref_summary_pebble_forceuntested">Activar funciones no probadas. 隆ACT脥VELO S脫LO SI SABE LO QUE EST脕 HACIENDO!</string>
+    <string name="pref_title_pebble_forcele">Preferir siempre BLE</string>
+    <string name="pref_summary_pebble_forcele">Usar el soporte experimental de Pebble LE para todos los Pebble en lugar del Bluetooth cl谩sico. Esto requiere vincular primero con Pebble no LE, y luego Pebble LE</string>
+    <string name="pref_title_pebble_mtu_limit">Pebble 2/LE l铆mite de GATT MTU</string>
+    <string name="pref_summary_pebble_mtu_limit">Si tu Pebble 2/Pebble LE no funciona correctamente, prueba esta opci贸n para limitar el MTU (rango v谩lido 20鈥�512)</string>
+    <string name="pref_title_pebble_enable_applogs">Activar el registro de la aplicaci贸n del reloj</string>
+    <string name="pref_summary_pebble_enable_applogs">Producir谩 registros de las apps del reloj que Gadgetbridge guardar谩 (necesita reconexi贸n)</string>
+    <string name="pref_title_pebble_always_ack_pebblekit">ACK antes de tiempo de PebbleKit</string>
+    <string name="pref_summary_pebble_always_ack_pebblekit">Permitir谩 a los mensajes enviados a apps de terceros ser reconocidos siempre e inmediatamente</string>
+    <string name="pref_title_pebble_reconnect_attempts">Intentos de reconexi贸n</string>
+    <string name="pref_title_unit_system">Unidades</string>
+    <string name="pref_title_timeformat">Formato de hora</string>
+    <string name="pref_title_screentime">Tiempo de pantalla encendida</string>
+    <string name="prefs_title_all_day_heart_rate">Medici贸n del ritmo card铆aco todo el d铆a</string>
+    <string name="preferences_hplus_settings">Configuraci贸n de HPlus/Makibes</string>
+    <string name="not_connected">No conectado</string>
+    <string name="connecting">Conectando</string>
+    <string name="connected">Conectado</string>
+    <string name="unknown_state">Estado desconocido</string>
+    <string name="_unknown_">(desconocido)</string>
+    <string name="test">Probar</string>
+    <string name="test_notification">Probar notificaci贸n</string>
+    <string name="this_is_a_test_notification_from_gadgetbridge">Notificaci贸n de prueba desde Gadgetbridge</string>
+    <string name="bluetooth_is_not_supported_">No se admite el Bluetooth.</string>
+    <string name="bluetooth_is_disabled_">El Bluetooth est谩 desactivado.</string>
+    <string name="tap_connected_device_for_app_mananger">Pulse en el dispositivo conectado para acceder al administrador de aplicaciones</string>
+    <string name="tap_connected_device_for_activity">Pulse el dispositivo conectado para ver la actividad</string>
+    <string name="tap_connected_device_for_vibration">Pulse el dispositivo conectado para la vibraci贸n</string>
+    <string name="tap_a_device_to_connect">Pulse un dispositivo para conectarse</string>
+    <string name="cannot_connect_bt_address_invalid_">No se puede conectar. 驴Direcci贸n BT incorrecta?</string>
+    <string name="gadgetbridge_running_generic">Gadgetbridge ejecutandose</string>
+    <string name="installing_binary_d_d">Instalando binario %1$d/%2$d</string>
+    <string name="installation_failed_">Instalaci贸n fallida</string>
+    <string name="installation_successful">Instalado</string>
+    <string name="firmware_install_warning">EST脕 INTENTANDO INSTALAR UN FIRMWARE, PROCEDA BAJO SU PROPIA RESPONSABILIDAD.
+\n
+\n
+\n Este firmware es para la revisi贸n de HW: %s</string>
+    <string name="app_install_info">Est谩s a punto de instalar la siguiente app:
+\n
+\n%1$s
+\nVersi贸n %2$s de %3$s
+\n</string>
+    <string name="n_a">N/A</string>
+    <string name="initialized">iniciado</string>
+    <string name="appversion_by_creator">%1$s de %2$s</string>
+    <string name="title_activity_discovery">Detecci贸n de dispositivos</string>
+    <string name="discovery_stop_scanning">Detener el escaneo</string>
+    <string name="discovery_start_scanning">Empezar la detecci贸n</string>
+    <string name="action_discover">Conectar un nuevo dispositivo</string>
+    <string name="device_with_rssi">%1$s (%2$s)</string>
+    <string name="title_activity_android_pairing">Emparejar dispositivo</string>
+    <string name="android_pairing_hint">Usar el men煤 de emparejamiento Bluetooth de Android para vincular el dispositivo.</string>
+    <string name="title_activity_mi_band_pairing">Empareje la Mi Band</string>
+    <string name="pairing">Emparejando con %s鈥�</string>
+    <string name="pairing_creating_bond_with">Creando emparejamiento con %1$s (%2$s)</string>
+    <string name="pairing_unable_to_pair_with">No se ha podido emparejar con %1$s (%2$s)</string>
+    <string name="pairing_in_progress">Emparejamiento en curso: %1$s (%2$s)</string>
+    <string name="pairing_already_bonded">Emparejado con %1$s (%2$s), conectando鈥�</string>
+    <string name="message_cannot_pair_no_mac">Ninguna direcci贸n MAC proporcionada, no se puede emparejar.</string>
+    <string name="preferences_category_device_specific_settings">Ajustes espec铆ficos del dispositivo</string>
+    <string name="preferences_miband_settings">Ajustes de Mi Band / Amazfit</string>
+    <string name="male">Hombre</string>
+    <string name="female">Mujer</string>
+    <string name="other">Otro</string>
+    <string name="left">Izquierda</string>
+    <string name="right">Derecha</string>
+    <string name="miband_pairing_using_dummy_userdata">No se proporcionaron datos de usuario v谩lidos. Por ahora se emplear谩n datos ficticios.</string>
+    <string name="miband_pairing_tap_hint">Cuando su MiBand vibre y parpadee, p煤lsela repetidas veces.</string>
+    <string name="appinstaller_install">Instalar</string>
+    <string name="discovery_connected_devices_hint">Haga su dispositivo detectable. Los dispositivos conectados no ser谩n detectables. En Android 6 o superior, necesita activar la localizaci贸n (ej. GPS). Desactive Privacy Guard para Gadgetbridge, debido a que podr铆a detener y reiniciar su tel茅fono. Si su dispositivo no aparece en dos minutos, vuelva a intentarlo tras reiniciar su tel茅fono.</string>
+    <string name="discovery_note">Nota:</string>
+    <string name="candidate_item_device_image">Imagen del dispositivo</string>
+    <string name="miband_prefs_alias">Nombre/Apodo</string>
+    <string name="pref_header_vibration_count">N煤mero de vibraciones</string>
+    <string name="title_activity_sleepmonitor">Monitor de sue帽o</string>
+    <string name="pref_write_logfiles">Escribir registros</string>
+    <string name="initializing">Iniciando</string>
+    <string name="busy_task_fetch_activity_data">Recuperando datos de actividad</string>
+    <string name="sleep_activity_date_range">Desde %1$s a %2$s</string>
+    <string name="prefs_wearside">驴En derecha o en izquierda?</string>
+    <string name="pref_screen_vibration_profile">Perfil de vibraci贸n</string>
+    <string name="vibration_profile_staccato">Muy corto</string>
+    <string name="vibration_profile_short">Corto</string>
+    <string name="vibration_profile_medium">Medio</string>
+    <string name="vibration_profile_long">Largo</string>
+    <string name="vibration_profile_waterdrop">Muy largo</string>
+    <string name="vibration_profile_ring">Timbre</string>
+    <string name="vibration_profile_alarm_clock">Despertador</string>
+    <string name="miband_prefs_vibration">Vibraci贸n</string>
+    <string name="vibration_try">Probar</string>
+    <string name="pref_screen_notification_profile_sms">Notificaci贸n de SMS</string>
+    <string name="pref_header_vibration_settings">Ajustes de vibraci贸n</string>
+    <string name="pref_screen_notification_profile_generic">Notificaci贸n gen茅rica</string>
+    <string name="pref_screen_notification_profile_email">Notificaci贸n de correo electr贸nico</string>
+    <string name="pref_screen_notification_profile_incoming_call">Notificaci贸n de llamada entrante</string>
+    <string name="pref_screen_notification_profile_generic_chat">Chat</string>
+    <string name="pref_screen_notification_profile_generic_navigation">Navegaci贸n</string>
+    <string name="pref_screen_notification_profile_generic_social">Red social</string>
+    <string name="stats_title">Zonas de velocidad</string>
+    <string name="stats_x_axis_label">Total de minutos</string>
+    <string name="stats_y_axis_label">Pasos por minuto</string>
+    <string name="control_center_find_lost_device">Encuentra un dispositivo perdido</string>
+    <string name="control_center_cancel_to_stop_vibration">Cancelar para detener la vibraci贸n.</string>
+    <string name="title_activity_charts">Actividad y sue帽o</string>
+    <string name="title_activity_set_alarm">Configurar alarmas</string>
+    <string name="controlcenter_start_configure_alarms">Configurar alarmas</string>
+    <string name="title_activity_alarm_details">Detalles de alarma</string>
+    <string name="alarm_sun_short">Dom</string>
+    <string name="alarm_mon_short">Lun</string>
+    <string name="alarm_tue_short">Mar</string>
+    <string name="alarm_wed_short">Mi茅</string>
+    <string name="alarm_thu_short">Jue</string>
+    <string name="alarm_fri_short">Vie</string>
+    <string name="alarm_sat_short">S谩b</string>
+    <string name="alarm_smart_wakeup">Alarma inteligente</string>
+    <string name="user_feedback_miband_set_alarms_failed">Se produjo un error al fijar las alarmas. Int茅ntelo de nuevo.</string>
+    <string name="user_feedback_miband_set_alarms_ok">Se enviaron las alarmas al dispositivo.</string>
+    <string name="chart_no_data_synchronize">Sin datos. 驴Sincronizar dispositivo?</string>
+    <string name="user_feedback_miband_activity_data_transfer">A punto de transferir %1$s de datos desde %2$s</string>
+    <string name="miband_prefs_fitness_goal">Objetivo de pasos diarios</string>
+    <string name="dbaccess_error_executing">Error al ejecutar 芦%1$s禄</string>
+    <string name="controlcenter_start_activitymonitor">Su actividad</string>
+    <string name="cannot_connect">No se puede conectar: %1$s</string>
+    <string name="installer_activity_unable_to_find_handler">No se ha podido encontrar un controlador para instalar este archivo.</string>
+    <string name="pbw_install_handler_unable_to_install">No se pudo instalar el archivo indicado: %1$s</string>
+    <string name="pbw_install_handler_hw_revision_mismatch">No se puede instalar el 芦firmware禄 indicado: no coincide con la revisi贸n de 芦hardware禄 del Pebble.</string>
+    <string name="installer_activity_wait_while_determining_status">Por favor, espera mientras se determina el estado de la instalaci贸n鈥�</string>
+    <string name="notif_battery_low_title">隆Bater铆a baja del Gadget!</string>
+    <string name="notif_battery_low_percent">A %1$s le queda: %2$s%% bater铆a</string>
+    <string name="notif_battery_low_bigtext_last_charge_time">脷ltima carga: %s \n</string>
+    <string name="notif_battery_low_bigtext_number_of_charges">N煤mero de cargas: %s</string>
+    <string name="sleepchart_your_sleep">Sue帽o</string>
+    <string name="weeksleepchart_sleep_a_week">Sue帽o por semana</string>
+    <string name="weeksleepchart_today_sleep_description">Sue帽o hoy, objetivo: %1$s</string>
+    <string name="weekstepschart_steps_a_week">Pasos por semana</string>
+    <string name="activity_sleepchart_activity_and_sleep">Actividad</string>
+    <string name="updating_firmware">Instalando firmware鈥�</string>
+    <string name="fwapp_install_device_not_ready">El archivo no puede ser instalado, el dispositivo no est谩 listo.</string>
+    <string name="installhandler_firmware_name">%1$s: %2$s %3$s</string>
+    <string name="miband_fwinstaller_compatible_version">Versi贸n compatible</string>
+    <string name="miband_fwinstaller_untested_version">隆Versi贸n no probada!</string>
+    <string name="fwappinstaller_connection_state">Conexi贸n al dispositivo: %1$s</string>
+    <string name="pbw_installhandler_pebble_firmware">Pebble firmware %1$s</string>
+    <string name="pbwinstallhandler_correct_hw_revision">Revisi贸n de hardware correcta</string>
+    <string name="pbwinstallhandler_incorrect_hw_revision">隆La versi贸n de hardware es incorrecta!</string>
+    <string name="pbwinstallhandler_app_item">%1$s (%2$s)</string>
+    <string name="updatefirmwareoperation_updateproblem_do_not_reboot">Hubo un problema con la transferencia de firmware. 隆NO REINICIES tu Mi Band!</string>
+    <string name="updatefirmwareoperation_metadata_updateproblem">Hubo un problema con la transferencia de metadatos del firmware</string>
+    <string name="updatefirmwareoperation_update_complete">Instalaci贸n del firmware completa</string>
+    <string name="updatefirmwareoperation_update_complete_rebooting">Instalaci贸n del firmware completa, reiniciando dispositivo鈥�</string>
+    <string name="updatefirmwareoperation_write_failed">Fall贸 la instalaci贸n del firmware</string>
+    <string name="chart_steps">Pasos</string>
+    <string name="calories">Calor铆as</string>
+    <string name="distance">Distancia</string>
+    <string name="clock">Reloj</string>
+    <string name="heart_rate">Frecuencia card铆aca</string>
+    <string name="battery">Bater铆a</string>
+    <string name="liveactivity_live_activity">Actividad en directo</string>
+    <string name="weeksteps_today_steps_description">Pasos hoy, objetivo: %1$s</string>
+    <string name="pref_title_dont_ack_transfer">No hay confirmaci贸n en la transferencia de datos de actividad</string>
+    <string name="pref_summary_dont_ack_transfers">Si no se confirma el mensaje de recepci贸n, los datos de actividad no se borrar谩n. Es 煤til si GB se utiliza junto con otras aplicaciones.</string>
+    <string name="pref_summary_keep_data_on_device">Mantendr谩 los datos de actividad en el dispositivo incluso despu茅s de la sincronizaci贸n. 脷til si se utilizan GB junto con otras aplicaciones. Esto puede hacer que el reloj se quede sin espacio y/o deje de sincronizarse correctamente.</string>
+    <string name="pref_title_low_latency_fw_update">Usa el modo de baja latencia para las instalaciones de firmware</string>
+    <string name="pref_summary_low_latency_fw_update">Esto podr铆a ayudar en dispositivos donde las instalaciones de firmware fallan.</string>
+    <string name="live_activity_steps_history">Historial de pasos</string>
+    <string name="live_activity_current_steps_per_minute">Pasos/min actuales</string>
+    <string name="live_activity_total_steps">Pasos totales</string>
+    <string name="live_activity_steps_per_minute_history">Historial de pasos por minuto</string>
+    <string name="live_activity_start_your_activity">Empieza tu actividad</string>
+    <string name="abstract_chart_fragment_kind_activity">Actividad</string>
+    <string name="abstract_chart_fragment_kind_light_sleep">Sue帽o ligero</string>
+    <string name="abstract_chart_fragment_kind_deep_sleep">Sue帽o profundo</string>
+    <string name="abstract_chart_fragment_kind_not_worn">Sin usar</string>
+    <string name="device_not_connected">No conectado.</string>
+    <string name="user_feedback_all_alarms_disabled">Todas las alarmas deshabilitadas</string>
+    <string name="pref_title_keep_data_on_device">Mantener los datos de actividad en el dispositivo</string>
+    <string name="miband_fwinstaller_incompatible_version">Firmware incompatible</string>
+    <string name="fwinstaller_firmware_not_compatible_to_device">Este firmware no es compatible con tu dispositivo</string>
+    <string name="miband_prefs_reserve_alarm_calendar">Reserva de alarmas para pr贸ximos eventos</string>
+    <string name="miband_prefs_hr_sleep_detection">Usar sensor de pulso para mejorar la detecci贸n del sue帽o</string>
+    <string name="miband_prefs_device_time_offset_hours">Compensaci贸n de la hora del dispositivo en horas (para detectar el sue帽o de trabajadores a turnos)</string>
+    <string name="miband2_prefs_dateformat">Formato de fecha</string>
+    <string name="dateformat_time">Hora</string>
+    <string name="dateformat_date_time"><![CDATA[Fecha y hora]]></string>
+    <string name="mi2_prefs_goal_notification">Notificaci贸n de objetivo</string>
+    <string name="mi2_prefs_goal_notification_summary">La pulsera vibrar谩 cuando llegues al objetivo diario de pasos</string>
+    <string name="mi2_prefs_display_items">Elementos que mostrar</string>
+    <string name="mi2_prefs_display_items_summary">Selecciona los elementos que mostrar en la pulsera</string>
+    <string name="mi2_prefs_activate_display_on_lift">Activar pantalla al levantar</string>
+    <string name="mi2_prefs_rotate_wrist_to_switch_info">Gira la mu帽eca para cambiar la informaci贸n</string>
+    <string name="mi2_prefs_do_not_disturb">No Molestar</string>
+    <string name="mi2_prefs_do_not_disturb_summary">La pulsera no recibir谩 notificaciones</string>
+    <string name="mi2_prefs_inactivity_warnings">Alerta de inactividad</string>
+    <string name="mi2_prefs_inactivity_warnings_summary">La pulsera vibrar谩 cuando hayas estado inactivo por un tiempo</string>
+    <string name="mi2_prefs_inactivity_warnings_threshold">Umbral de inactividad (en minutos)</string>
+    <string name="mi2_prefs_inactivity_warnings_dnd_summary">Desactivar alarmas de inactividad por un intervalo de tiempo</string>
+    <string name="mi2_prefs_do_not_disturb_start">Hora de inicio</string>
+    <string name="mi2_prefs_do_not_disturb_end">Hora de fin</string>
+    <string name="FetchActivityOperation_about_to_transfer_since">A punto de transferir datos desde %1$s</string>
+    <string name="waiting_for_reconnect">Esperando reconexi贸n</string>
+    <string name="activity_prefs_about_you">Acerca de ti</string>
+    <string name="activity_prefs_year_birth">A帽o de nacimiento</string>
+    <string name="activity_prefs_gender">G茅nero</string>
+    <string name="activity_prefs_height_cm">Altura (cm)</string>
+    <string name="activity_prefs_weight_kg">Peso (kg)</string>
+    <string name="authenticating">Autenticando</string>
+    <string name="authentication_required">Autenticaci贸n requerida</string>
+    <string name="appwidget_text">Zzz</string>
+    <string name="add_widget">A帽adir widget</string>
+    <string name="activity_prefs_sleep_duration">Horas de sue帽o deseadas</string>
+    <string name="appwidget_setting_alarm">Se fij贸 una alarma a las %1$02d:%2$02d</string>
+    <string name="device_hw">Revisi贸n de hardware: %1$s</string>
+    <string name="device_fw">Versi贸n de firmware: %1$s</string>
+    <string name="error_creating_directory_for_logfiles">Error creando un directorio para los archivos de registro: %1$s</string>
+    <string name="DEVINFO_HR_VER">"Frec. card.: "</string>
+    <string name="updatefirmwareoperation_update_in_progress">Instalando firmware</string>
+    <string name="updatefirmwareoperation_firmware_not_sent">Firmware no enviado</string>
+    <string name="charts_legend_heartrate">Frecuencia card铆aca</string>
+    <string name="live_activity_heart_rate">Frecuencia card铆aca</string>
+    <string name="pref_title_pebble_health_store_raw">Almacenar datos en bruto en la base de datos </string>
+    <string name="pref_summary_pebble_health_store_raw">Guarda los datos en bruto para poder ser interpretados m谩s tarde, esto incrementa el tama帽o de la base de datos.</string>
+    <string name="action_db_management">Administraci贸n de datos</string>
+    <string name="title_activity_db_management">Administraci贸n de datos</string>
+    <string name="activity_db_management_import_export_explanation">Las operaciones de exportaci贸n/importaci贸n utilizan la siguiente ruta (v茅ase m谩s abajo) a un directorio de su dispositivo. Este directorio es accesible a otras aplicaciones Android y a su ordenador. Ten en cuenta que este directorio y todos los archivos que contiene se eliminan si desinstala Gadgetbridge. Los datos incluyen:
+\n Exportar_preferencia - ajustes globales
+\n Exportar_preferencia_MAC - ajustes espec铆ficos del dispositivo
+\n Gadgetbridge - base de datos de dispositivos y actividades
+\n Gadgetbridge_fecha - base de datos exportada en una fecha
+\n *.gpx - grabaciones GPS
+\n *.log - archivos de registro
+\nEspera encontrar sus archivos exportados (o coloca los archivos que deseas importar) all铆:</string>
+    <string name="activity_db_management_merge_old_title">Borrar la base de datos antigua</string>
+    <string name="dbmanagementactivvity_cannot_access_export_path">No se puede acceder a la ruta para exportar . Por favor, contacta con los desarrolladores.</string>
+    <string name="dbmanagementactivity_exported_to">Exportado a: %1$s</string>
+    <string name="dbmanagementactivity_error_exporting_db">Error de exportaci贸n de la base de datos: %1$s</string>
+    <string name="dbmanagementactivity_error_exporting_shared">Error de exportaci贸n de las preferencias: %1$s</string>
+    <string name="dbmanagementactivity_import_data_title">驴Importar Datos?</string>
+    <string name="dbmanagementactivity_overwrite_database_confirmation">驴Realmente se sobrescriben los datos actuales? Se sobrescribir谩n todos tus datos de actividad actuales (si los tienes), dispositivos y preferencias.</string>
+    <string name="dbmanagementactivity_import_successful">Importado.</string>
+    <string name="dbmanagementactivity_error_importing_db">Error de importaci贸n de la base de datos: %1$s</string>
+    <string name="dbmanagementactivity_error_importing_shared">Error de importaci贸n de las preferencias: %1$s</string>
+    <string name="dbmanagementactivity_delete_activity_data_title">驴Quieres borrar los datos de actividad?</string>
+    <string name="dbmanagementactivity_really_delete_entire_db">驴Quieres borrar la base de datos? Todos tus datos de actividad y la informaci贸n sobre tus dispositivos se borrar谩n.</string>
+    <string name="dbmanagementactivity_database_successfully_deleted">Datos borrados.</string>
+    <string name="dbmanagementactivity_db_deletion_failed">El borrado de la base de datos ha fallado.</string>
+    <string name="dbmanagementactivity_delete_old_activity_db">驴Quieres borrar la antigua base de datos de actividades?</string>
+    <string name="dbmanagementactivity_delete_old_activitydb_confirmation">驴Quiere borrar la base de datos antigua? Si los datos no se han importado, se perder谩n.</string>
+    <string name="dbmanagementactivity_old_activity_db_successfully_deleted">Los datos antiguos han sido borrados.</string>
+    <string name="dbmanagementactivity_old_activity_db_deletion_failed">El borrado de la Base de datos antiguos ha fallado.</string>
+    <string name="dbmanagementactivity_overwrite">Sobreescribir</string>
+    <string name="Cancel">Cancelar</string>
+    <string name="Delete">Eliminar</string>
+    <!--Strings related to Vibration Activity-->
+    <string name="title_activity_vibration">Vibraci贸n</string>
+    <!--Strings related to Pebble Pairing Activity-->
+    <string name="title_activity_pebble_pairing">Emparejamiento con Pebble</string>
+    <string name="pebble_pairing_hint">Aparecer谩 un di谩logo de emparejamiento en el dispositivo Android. Si no, busque la notificaci贸n en el caj贸n y acepte la solicitud. Posteriormente, ac茅ptela tambi茅n en el Pebble.</string>
+    <string name="weather_notification_label">Cerci贸rese de que esta m谩scara est茅 activada en la aplicaci贸n Notificaci贸n del tiempo para obtener informaci贸n meteorol贸gica en el Pebble.
+\n
+\nNo hace falta configurar nada aqu铆.
+\n
+\nPuede activar la aplicaci贸n del tiempo del sistema Pebble en la gesti贸n de aplicaciones.
+\n
+\nLas esferas compatibles mostrar谩n autom谩ticamente el estado del tiempo.</string>
+    <string name="pref_title_setup_bt_pairing">Activar el emparejamiento Bluetooth</string>
+    <string name="pref_summary_setup_bt_pairing">Desactiva esto si tienes problemas de conexi贸n</string>
+    <string name="unit_metric">M茅trica</string>
+    <string name="unit_imperial">Imperial</string>
+    <string name="timeformat_24h">24H</string>
+    <string name="timeformat_am_pm">AM/PM</string>
+    <string name="pref_screen_notification_profile_alarm_clock">Despertador</string>
+    <string name="StringUtils_sender">(%1$s)</string>
+    <string name="find_device_you_found_it">隆Lo ha encontrado!</string>
+    <string name="miband2_prefs_timeformat">Mi2: formato de hora</string>
+    <string name="mi2_fw_installhandler_fw53_hint">隆Debes instalar la versi贸n %1$s antes de instalar este firmware!</string>
+    <string name="mi2_enable_text_notifications">Notificaciones de texto</string>
+    <string name="mi2_enable_text_notifications_summary"><![CDATA[Needs firmware >= 1.0.1.28 y Mili_pro.ft* instalados.]]></string>
+    <string name="off">Apagado</string>
+    <string name="mi2_dnd_off">Apagado</string>
+    <string name="mi2_dnd_automatic">Autom谩tico (detecci贸n de sue帽o)</string>
+    <string name="mi2_dnd_scheduled">Programado (intervalo de tiempo)</string>
+    <string name="discovery_attempting_to_pair">Intento de emparejamiento con %1$s</string>
+    <string name="discovery_bonding_failed_immediately">El enlace con %1$s ha fallado inmediatamente.</string>
+    <string name="discovery_trying_to_connect_to">Intentando conectar con: %1$s</string>
+    <string name="discovery_enable_bluetooth">Activa el Bluetooth para encontrar dispositivos.</string>
+    <string name="discovery_successfully_bonded">Enlazado con %1$s.</string>
+    <string name="discovery_pair_title">Emparejar con %1$s?</string>
+    <string name="discovery_pair_question">Selecciona Emparejar para emparejar tus dispositivos. Si esto falla, prueba de nuevo sin emparejar.</string>
+    <string name="discovery_yes_pair">Emparejar</string>
+    <string name="discovery_dont_pair">No emparejar</string>
+    <string name="fw_upgrade_notice_amazfitbip">Est谩 a punto de instalar el 芦firmware禄 %s en su Amazfit Bip.
+\n
+\nAseg煤rese de instalar el archivo .fw y, a continuaci贸n, el archivo .res. Su reloj se reiniciar谩 tras instalar el archivo .fw.
+\n
+\nNota: no es necesario instalar los archivos .res y .gps si son id茅nticos a los instalados previamente.
+\n
+\n隆PROCEDA BAJO SU PROPIA CUENTA Y RIESGO!</string>
+    <string name="mi2_prefs_button_actions">Acciones del bot贸n</string>
+    <string name="mi2_prefs_button_actions_summary">Especifique acciones al presionar botones</string>
+    <string name="mi2_prefs_button_press_count">N煤mero de presiones</string>
+    <string name="mi2_prefs_button_press_count_summary">N煤mero de presiones de bot贸n para desencadenar un Suceso 1. Despu茅s, presionar la misma cantidad de veces crear谩 un Suceso 2 y as铆 sucesivamente.</string>
+    <string name="mi2_prefs_button_press_broadcast">Mensaje para enviar</string>
+    <string name="mi2_prefs_button_press_broadcast_summary">Mensaje de difusi贸n enviado con el evento. El par谩metro `button_id` se a帽ade autom谩ticamente a cada mensaje.</string>
+    <string name="mi2_prefs_button_action">Activar acci贸n por bot贸n</string>
+    <string name="mi2_prefs_button_action_summary">Activar acci贸n por un n煤mero definido de pulsaciones</string>
+    <string name="mi2_prefs_button_action_vibrate">Activar la vibraci贸n de la pulsera</string>
+    <string name="mi2_prefs_button_action_vibrate_summary">Activar vibraci贸n por acci贸n</string>
+    <string name="mi2_prefs_button_press_count_max_delay">Retardo m谩ximo entre pulsaciones</string>
+    <string name="mi2_prefs_button_press_count_max_delay_summary">Retardo m谩ximo entre pulsaciones en milisegundos</string>
+    <string name="_pebble_watch_open_on_phone">Abrir en el dispositivo Android</string>
+    <string name="_pebble_watch_mute">Silenciar</string>
+    <string name="_pebble_watch_reply">Responder</string>
+    <string name="pref_title_pebble_enable_bgjs">Activar JS en 2.潞 plano</string>
+    <string name="pref_summary_pebble_enable_bgjs">Permite a las watchfaces mostrar el tiempo, informaci贸n de bater铆a, etc.</string>
+    <string name="activity_web_view">Vista de actividad web</string>
+    <string name="controlcenter_connect">Conectar鈥�</string>
+    <string name="fw_upgrade_notice_amazfitcor">Est谩 a punto de instalar el 芦firmware禄 %s en su Amazfit Cor.
+\n
+\nAseg煤rese de instalar el archivo .fw y, a continuaci贸n, el archivo .res. Se reiniciar谩 la pulsera tras instalar el archivo .fw.
+\n
+\nNota: no es necesario instalar el archivo .res si es id茅ntico al instalado previamente.
+\n
+\n隆PROCEDA BAJO SU PROPIA CUENTA Y RIESGO!</string>
+    <string name="pref_title_charts_swipe">Permitir deslizar hacia izquierda/derecha en los gr谩ficos de actividad</string>
+    <string name="automatic">Autom谩tico</string>
+    <string name="simplified_chinese">Chino simplificado</string>
+    <string name="traditional_chinese">Chino tradicional</string>
+    <string name="english">Ingl茅s</string>
+    <string name="prefs_title_heartrate_measurement_interval">Medici贸n de pulso todo el d铆a</string>
+    <string name="interval_one_minute">Cada minuto</string>
+    <string name="interval_five_minutes">Cada 5 minutos</string>
+    <string name="interval_ten_minutes">Cada 10 minutos</string>
+    <string name="interval_thirty_minutes">Cada 30 minutos</string>
+    <string name="interval_one_hour">Cada hora</string>
+    <string name="pref_title_weather">Tiempo</string>
+    <string name="pref_title_weather_location">Ubicaci贸n para el tiempo (para el proveedor del tiempo de LineageOS)</string>
+    <string name="kind_firmware">Firmware</string>
+    <string name="kind_invalid">Datos no v谩lidos</string>
+    <string name="kind_font">Fuente</string>
+    <string name="kind_gps">Firmware del GPS</string>
+    <string name="kind_gps_almanac">Almanaque GPS</string>
+    <string name="kind_gps_cep">Correcci贸n de error del GPS</string>
+    <string name="kind_resources">Recursos</string>
+    <string name="kind_watchface">Esfera</string>
+    <string name="devicetype_unknown">Dispositivo desconocido</string>
+    <string name="devicetype_test">Dispositivo de prueba</string>
+    <string name="devicetype_pebble">Pebble</string>
+    <string name="devicetype_miband">Mi Band</string>
+    <string name="devicetype_miband2">Mi Band 2</string>
+    <string name="devicetype_amazfit_bip">Amazfit Bip</string>
+    <string name="devicetype_amazfit_cor">Amazfit Cor</string>
+    <string name="devicetype_vibratissimo">Vibratissimo</string>
+    <string name="devicetype_liveview">LiveView</string>
+    <string name="devicetype_hplus">HPlus</string>
+    <string name="devicetype_makibes_f68">Makibes F68</string>
+    <string name="devicetype_exrizu_k8">Exrizu K8</string>
+    <string name="devicetype_no1_f1">No.1 F1</string>
+    <string name="devicetype_teclast_h30">Teclast H30</string>
+    <string name="pref_header_auto_export">Exportar autom谩ticamente</string>
+    <string name="pref_title_auto_export_enabled">Exportaci贸n autom谩tica activada</string>
+    <string name="pref_title_auto_export_location">Ubicaci贸n de exportaci贸n</string>
+    <string name="pref_title_auto_export_interval">Intervalo de exportaci贸n</string>
+    <string name="pref_summary_auto_export_interval">Exportar cada %d hora(s)</string>
+    <string name="notif_export_failed_title">隆La exportaci贸n de la base de datos ha fallado! Por favor, revisa los ajustes.</string>
+    <string name="choose_auto_export_location">Elija la ubicaci贸n para exportaci贸n</string>
+    <string name="spanish">Espa帽ol</string>
+    <string name="notification_channel_name">General</string>
+    <string name="devicetype_xwatch">XWatch</string>
+    <string name="controlcenter_change_led_color">Cambiar color del LED</string>
+    <string name="controlcenter_change_fm_frequency">Cambiar frecuencia de FM</string>
+    <string name="controlcenter_calibrate_device">Calibrar dispositivo</string>
+    <string name="pref_auto_fetch">Extracci贸n autom谩tica de datos de actividad</string>
+    <string name="pref_auto_fetch_limit_fetches">Tiempo m铆nimo entre extracciones</string>
+    <string name="pref_auto_fetch_limit_fetches_summary">Extraer datos cada %d minutos</string>
+    <string name="activity_type_light_sleep">Sue帽o ligero</string>
+    <string name="activity_type_deep_sleep">Sue帽o profundo</string>
+    <string name="activity_summaries">Actividades deportivas</string>
+    <string name="menuitem_notifications">Notificaciones</string>
+    <string name="menuitem_weather">Tiempo</string>
+    <string name="menuitem_alarm">Alarma</string>
+    <string name="menuitem_timer">Cuenta atr谩s</string>
+    <string name="menuitem_compass">Br煤jula</string>
+    <string name="menuitem_settings">Ajustes</string>
+    <string name="menuitem_music">M煤sica</string>
+    <string name="menuitem_more">M谩s</string>
+    <string name="watch9_time_minutes">Minutos:</string>
+    <string name="watch9_time_hours">Horas:</string>
+    <string name="watch9_time_seconds">Segundos:</string>
+    <string name="watch9_calibration_button">Calibrar</string>
+    <string name="watch9_pairing_tap_hint">Cuando el dispositivo vibre, ag铆telo o pulse un bot贸n.</string>
+    <string name="debugactivity_really_factoryreset_title">驴Confirmar reinicio de f谩brica\?</string>
+    <string name="debugactivity_really_factoryreset">Al hacer un restablecimiento de f谩brica se eliminar谩n todos los datos del dispositivo conectado (si se admite). Los dispositivos Xiaomi / Huami tambi茅n cambian la direcci贸n MAC de Bluetooth, por lo que aparecen como nuevos dispositivos para Gadgetbridge.</string>
+    <string name="blacklist_all_for_notifications">Lista negra para todas las notificaciones</string>
+    <string name="whitelist_all_for_notifications">Lista blanca para todas las notificaciones</string>
+    <string name="fw_upgrade_notice_miband3">Est谩 a punto de instalar el 芦firmware禄 %s en su Mi Band 3.
+\n
+\nAseg煤rese de instalar el archivo .fw y, a continuaci贸n, el archivo .res. Se reiniciar谩 la pulsera tras instalar el archivo .fw.
+\n
+\nNota: no es necesario instalar el archivo .res si es id茅ntico al instalado previamente.
+\n
+\n隆PROCEDA BAJO SU PROPIA CUENTA Y RIESGO!</string>
+    <string name="pref_title_notifications_timeout">Tiempo m铆nimo entre notificaciones</string>
+    <string name="pref_title_rtl">Derecha a Izquierda</string>
+    <string name="pref_summary_rtl">Activar si su dispositivo no puede mostrar idiomas de derecha a izquierda</string>
+    <string name="pref_rtl_max_line_length">Longitud de l铆nea m谩xima de derecha a izquierda</string>
+    <string name="pref_rtl_max_line_length_summary">Alarga o acorta las l铆neas de texto de derecha a izquierda si contiene separaciones</string>
+    <string name="pref_title_pebble_gatt_clientonly">S贸lo cliente GATT</string>
+    <string name="pref_summary_pebble_gatt_clientonly">Esto es solo para Pebble 2 y experimental, pruebe esto si tiene problemas de conectividad</string>
+    <string name="prefs_screen_orientation">Orientaci贸n de pamtalla</string>
+    <string name="pref_auto_fetch_summary">La recuperaci贸n funciona luego de desbloquear la pantalla. 隆S贸lo funciona si se ha definido un mecanismo de bloqueo!</string>
+    <string name="horizontal">Horizontal</string>
+    <string name="vertical">Vertical</string>
+    <string name="notif_battery_low">%1$s bater铆a baja</string>
+    <string name="notif_battery_low_extended">%1$s bater铆a baja: %2$s</string>
+    <string name="lack_of_sleep">Sue帽o restante: %1$s</string>
+    <string name="overslept">Excedido: %1$s</string>
+    <string name="no_limit">Sin l铆mite</string>
+    <string name="seconds_5">5 segundos</string>
+    <string name="seconds_10">10 segundos</string>
+    <string name="seconds_20">20 segundos</string>
+    <string name="seconds_30">30 segundos</string>
+    <string name="minutes_1">1 minuto</string>
+    <string name="minutes_5">5 minutos</string>
+    <string name="minutes_10">10 minutos</string>
+    <string name="minutes_30">30 minutos</string>
+    <string name="lack_of_step">Pasos restantes: %1$d</string>
+    <string name="overstep">Excedido: %1$d</string>
+    <string name="live_activity_max_heart_rate">Frecuencia card铆aca Actual / m谩xima: %1$d / %2$d</string>
+    <string name="you_slept">%1$s - %2$s</string>
+    <string name="you_did_not_sleep">No dormiste</string>
+    <string name="prefs_disconnect_notification">Desconectar notificaci贸n</string>
+    <string name="mi3_prefs_band_screen_unlock">Desbloqueo de pantalla de Band</string>
+    <string name="mi3_prefs_band_screen_unlock_summary">Desliza hacia arriba para desbloquear la pantalla de la Band</string>
+    <string name="mi3_prefs_night_mode">Modo noche</string>
+    <string name="mi3_prefs_night_mode_summary">Disminuye el brillo de la pantalla de la Band autom谩ticamente por la noche</string>
+    <string name="norwegian_bokmal">Noruego</string>
+    <string name="russian">Ruso</string>
+    <string name="german">Alem谩n</string>
+    <string name="italian">Italiano</string>
+    <string name="french">Franc茅s</string>
+    <string name="polish">Polaco</string>
+    <string name="korean">Coreano</string>
+    <string name="japanese">Japon茅s</string>
+    <string name="activity_prefs_charts">Configuraciones de gr谩fico</string>
+    <string name="activity_prefs_chart_max_heart_rate">Frecuencia card铆aca m谩xima</string>
+    <string name="activity_prefs_chart_min_heart_rate">Frecuencia card铆aca m铆nima</string>
+    <string name="ok">Aceptar</string>
+    <string name="on">Encendido</string>
+    <string name="mi3_night_mode_sunset">Al atardecer</string>
+    <string name="controlcenter_start_activity_tracks">Sus pistas de actividad</string>
+    <string name="activity_type_not_measured">No medido</string>
+    <string name="activity_type_activity">Actividad</string>
+    <string name="activity_type_not_worn">Dispositivo no usado</string>
+    <string name="activity_type_running">Correr</string>
+    <string name="activity_type_walking">Caminar</string>
+    <string name="activity_type_swimming">Nadar</string>
+    <string name="activity_type_unknown">Actividad desconocida</string>
+    <string name="activity_type_biking">Ciclismo</string>
+    <string name="activity_type_treadmill">Cinta de andar</string>
+    <string name="activity_type_exercise">Ejercicio</string>
+    <string name="select_all">Seleccionar todo</string>
+    <string name="share">Compartir</string>
+    <string name="reset_index">Restablecer fecha de recuperaci贸n</string>
+    <string name="devicetype_miband3">Mi Band 3</string>
+    <string name="devicetype_q8">Q8</string>
+    <string name="devicetype_mykronoz_zetime">MyKronoz ZeTime</string>
+    <string name="devicetype_id115">ID115</string>
+    <string name="devicetype_watch9">Watch 9</string>
+    <string name="devicetype_roidmi">Roidmi</string>
+    <string name="devicetype_roidmi3">Roidmi 3</string>
+    <string name="devicetype_casiogb6900">Casio GB-6900</string>
+    <string name="menuitem_status">Estado</string>
+    <string name="menuitem_activity">Historial del entrenamiento</string>
+    <string name="menuitem_alipay">Alipay</string>
+    <string name="watch9_calibration_hint">Establezca la hora que su dispositivo le est谩 mostrando en este momento.</string>
+    <string name="share_log_warning">Tenga en cuenta que Gadgetbridge registra archivos que pueden contener mucha informaci贸n personal, incluidos, entre otros, datos de salud, identificadores 煤nicos (como la direcci贸n MAC de un dispositivo), preferencias de m煤sica, etc. Considere editar el archivo y eliminar esta informaci贸n antes de enviar el archivo a un informe p煤blico.</string>
+    <string name="no_data">No hay datos</string>
+    <string name="preferences_led_color">Color de LED</string>
+    <string name="preferences_fm_frequency">Frecuencia FM</string>
+    <string name="pref_invalid_frequency_title">Frecuencia no v谩lida</string>
+    <string name="pref_invalid_frequency_message">Proporcione una frecuencia entre 87,5 y 108,0</string>
+    <string name="language_and_region_prefs">Configuraci贸n de idioma y regi贸n</string>
+    <string name="title_activity_notification_filter">Filtro de Notificaciones</string>
+    <string name="edittext_notification_filter_words_hint">Ingrese las palabras deseadas, nueva l铆nea para cada una</string>
+    <string name="toast_notification_filter_saved_successfully">Filtro de notificaci贸n guardado</string>
+    <string name="filter_mode_none">No filtrar</string>
+    <string name="filter_mode_whitelist">Mostrar cuando contiene estas palabras</string>
+    <string name="filter_mode_blacklist">Bloquear cuando contiene estas palabras</string>
+    <string name="filter_submode_at_least_one">Al menos una de estas palabras</string>
+    <string name="filter_submode_all">Todas las palabras</string>
+    <string name="toast_notification_filter_words_empty_hint">Ingrese al menos una palabra</string>
+    <string name="filter_mode">Modo de filtro</string>
+    <string name="mode_configuration">Configuraci贸n de modo</string>
+    <string name="save_configuration">Guardar configuraci贸n</string>
+    <string name="appwidget_not_connected">No conectado, alarma no configurada.</string>
+    <string name="title_activity_watch9_pairing">Emparejamiento del Watch 9</string>
+    <string name="title_activity_watch9_calibration">Calibraci贸n del Watch 9</string>
+    <string name="preferences_rtl_settings">Compatibilidad con escrituras de derecha a izquierda</string>
+    <string name="share_log">Compartir registro</string>
+    <string name="warning">隆Advertencia!</string>
+    <string name="zetime_title_settings">Configuraci贸n de ZeTime</string>
+    <string name="zetime_title_heartrate">Configuraci贸n de frecuencia card铆aca</string>
+    <string name="zetime_title_screentime">Duraci贸n de encendido de pantalla en segundos</string>
+    <string name="zetime_title_heart_rate_alarm">Alarma de frecuencia card铆aca</string>
+    <string name="zetime_title_heart_rate_alarm_summary">El reloj le avisar谩 cuando su frecuencia card铆aca exceda los l铆mites.</string>
+    <string name="zetime_heart_rate_alarm_enable">Activar alarma de frecuencia card铆aca</string>
+    <string name="activity_prefs_alarm_max_heart_rate">Frecuencia card铆aca m谩xima</string>
+    <string name="activity_prefs_alarm_min_heart_rate">Frecuencia card铆aca m铆nima</string>
+    <string name="zetime_analog_mode">Modo anal贸gico</string>
+    <string name="zetime_analog_mode_hands">Solo manos</string>
+    <string name="zetime_analog_mode_handsandsteps">Manos y pasos</string>
+    <string name="zetime_activity_tracking">Seguimiento de actividad</string>
+    <string name="zetime_activity_tracking_summary">Al activar el seguimiento de actividad, contar谩 sus pasos, etc.</string>
+    <string name="zetime_handmove_display">Movimiento de mano</string>
+    <string name="zetime_handmove_display_summary">Gire la mu帽eca para activar o desactivar la pantalla.</string>
+    <string name="zetime_calories_type">Tipo de calor铆as</string>
+    <string name="zetime_calories_type_active">Solo activo para quemar calor铆as</string>
+    <string name="zetime_date_format">Formato de fecha</string>
+    <string name="zetime_date_format_1">aa/mm/dd</string>
+    <string name="zetime_date_format_2">dd/mm/aa</string>
+    <string name="zetime_date_format_3">mm/dd/aa</string>
+    <string name="zetime_prefs_inactivity_repetitions">Repeticiones</string>
+    <string name="zetime_prefs_inactivity_mo">Lunes</string>
+    <string name="zetime_prefs_inactivity_tu">Martes</string>
+    <string name="zetime_prefs_inactivity_we">Mi茅rcoles</string>
+    <string name="zetime_prefs_inactivity_th">Jueves</string>
+    <string name="zetime_prefs_inactivity_fr">Viernes</string>
+    <string name="zetime_prefs_inactivity_sa">S谩bado</string>
+    <string name="zetime_prefs_inactivity_su">Domingo</string>
+    <string name="zetime_title_alarm_signaling">Establecer el tipo de se帽alizaci贸n para la alarma</string>
+    <string name="zetime_signaling_none">Silencio</string>
+    <string name="zetime_signaling_vibrate">Vibraci贸n continua</string>
+    <string name="zetime_signaling_beep">Pitido continuo</string>
+    <string name="zetime_signaling_vibrate_beep">Vibraci贸n continua y pitidos</string>
+    <string name="zetime_signaling_vibrate_once">Vibrar una vez</string>
+    <string name="zetime_signaling_vibrate_twice">Vibrar dos veces</string>
+    <string name="zetime_signaling_beep_once">Sonar una vez</string>
+    <string name="zetime_signaling_beep_twice">Sonar dos veces</string>
+    <string name="zetime_signaling_vibrate_beep_once">Vibrar y sonar una vez</string>
+    <string name="pref_title_support_voip_calls">Activar llamadas a la aplicaci贸n VoIP</string>
+    <string name="title_activity_device_specific_settings">Configuraciones espec铆ficas del dispositivo</string>
+    <string name="pref_title_authkey">Clave de autenticaci贸n</string>
+    <string name="pref_summary_authkey">Cambie la clave de autenticaci贸n a una clave com煤n en todos sus dispositivos Android desde los que desea conectarse. La clave predeterminada anterior para todos los dispositivos es 0123456789@ABCDE</string>
+    <string name="fw_upgrade_notice_amazfitcor2">Est谩 a punto de instalar el firmware %s en su Amazfit Cor 2. 
+\n 
+\nAseg煤rese de instalar el archivo .fw y, a continuaci贸n, el archivo .res. La pulsera se reiniciar谩 despues de instalar el archivo .fw. 
+\n 
+\nNota: no es necesario instalar el archivo .res si es id茅ntico al instalado previamente. 
+\n 
+\nPROCEDA BAJO SU PROPIO RIESGO. 
+\n 
+\nNO SE HA REALIZADO NINGUNA PRUEBA. QUIZ脕 NECESITE INSTALAR UN FIRMWARE BEATS_W SI EL NOMBRE DE SU DISPOSITIVO ES \"Amazfit Band 2\"</string>
+    <string name="fw_upgrade_notice_miband4">Est谩 a punto de instalar el 芦firmware禄 %s en su Mi Band 4.
+\n
+\nAseg煤rese de instalar el archivo .fw y, a continuaci贸n, el archivo .res. Se reiniciar谩 la pulsera tras instalar el archivo .fw.
+\n
+\nNota: no es necesario instalar el archivo .res si es id茅ntico al instalado previamente.
+\n
+\n隆PROCEDA BAJO SU PROPIA CUENTA Y RIESGO!</string>
+    <string name="prefs_hr_alarm_activity">Alarma de frecuencia card铆aca durante actividad deportiva</string>
+    <string name="prefs_hr_alarm_low">L铆mite bajo</string>
+    <string name="prefs_hr_alarm_high">L铆mite alto</string>
+    <string name="pref_screen_notification_profile_missed_call">Notificaci贸n de llamada perdida</string>
+    <string name="pref_screen_notification_profile_calendar">Notificaci贸n de calendario</string>
+    <string name="pref_screen_notification_profile_inactivity">Notificaci贸n de inactividad</string>
+    <string name="interval_fifteen_minutes">Cada 15 minutos</string>
+    <string name="interval_forty_five_minutes">Cada 45 minutos</string>
+    <string name="dutch">Holand茅s</string>
+    <string name="turkish">Turco</string>
+    <string name="portuguese">Portugu茅s</string>
+    <string name="average">Media: %1$s</string>
+    <string name="weekstepschart_steps_a_month">Pasos por mes</string>
+    <string name="weeksleepchart_sleep_a_month">Sue帽o por mes</string>
+    <string name="menuitem_nfc">NFC</string>
+    <string name="pref_title_contextual_arabic">脕rabe contextual</string>
+    <string name="pref_summary_contextual_arabic">Activar esto para el soporte de 脕rabe contextual</string>
+    <string name="zetime_calories_type_all">Calor铆as quemadas activas e inactivas</string>
+    <string name="pref_screen_notification_profile_low_power">Aviso de bater铆a baja</string>
+    <string name="pref_screen_notification_profile_anti_loss">Aviso anti p茅rdida</string>
+    <string name="activity_prefs_calories_burnt">Objetivo diario: calor铆as quemadas</string>
+    <string name="activity_prefs_distance_meters">Objetivo diario: distancia en metros</string>
+    <string name="activity_prefs_activetime_minutes">Objetivo diario: tiempo activo en minutos</string>
+    <string name="devicetype_miscale2">Mi Scale 2</string>
+    <string name="devicetype_bfh16">BFH-16</string>
+    <string name="ukrainian">Ucraniano</string>
+    <string name="arabic">脕rabe</string>
+    <string name="indonesian">Indonesio</string>
+    <string name="thai">Tailand茅s</string>
+    <string name="vietnamese">Vietnamita</string>
+    <string name="devicetype_amazfit_cor2">Amazfit Cor 2</string>
+    <string name="devicetype_miband4">Mi Band 4</string>
+    <string name="pref_header_charts">Configuraci贸n de gr谩ficos</string>
+    <string name="pref_title_charts_average">Mostrar promedios en los gr谩ficos</string>
+    <string name="pref_title_charts_range">Rango de Gr谩ficos</string>
+    <string name="pref_charts_range_on">El rango de gr谩ficos est谩 configurado para un mes</string>
+    <string name="pref_charts_range_off">El rango de gr谩ficos est谩 configurado para una semana</string>
+    <string name="devicetype_mijia_lywsd02">Mijia Smart Clock</string>
+    <string name="pref_summary_expose_hr">Permite a otras aplicaciones acceder a los datos de frecuencia card铆aca en tiempo real mientras Gadgetbridge est谩 conectado</string>
+    <string name="pref_title_expose_hr">Acceso terceros frec. card铆aca</string>
+    <string name="pref_title_use_custom_font">Utilizar tipo de letra personalizado</string>
+    <string name="pref_summary_use_custom_font">Act铆velo si su dispositivo incluye 芦firmware禄 de tipo de letra personalizado para admitir emojis</string>
+    <string name="activity_db_management_autoexport_explanation">La ubicaci贸n de exportaci贸n autom谩tica se defini贸 en:</string>
+    <string name="activity_db_management_autoexport_label">Exportaci贸n autom谩tica</string>
+    <string name="activity_DB_ExportButton">Exportar datos</string>
+    <string name="activity_DB_import_button">Importar datos</string>
+    <string name="activity_DB_test_export_button">Ejecutar exportaci贸n autom谩tica ahora</string>
+    <string name="activity_DB_test_export_message">Exportando base de datos鈥�</string>
+    <string name="activity_DB_delete_legacy_button">Eliminar BD anterior</string>
+    <string name="activity_DB_empty_button">Vaciar base de datos</string>
+    <string name="activity_db_management_empty_DB">Vaciar base de datos</string>
+    <string name="activity_db_management_exportimport_label">Exportar e importar</string>
+    <string name="activity_db_management_empty_db_warning">Atenci贸n: al pulsar en este bot贸n se borrar谩 la base de datos y comenzar谩 de cero.</string>
+    <string name="widget_listing_label">Estado y alarmas</string>
+    <string name="widget_set_alarm_after">Fijar alarma tras:</string>
+    <string name="widget_5_minutes">5 minutos</string>
+    <string name="widget_10_minutes">10 minutos</string>
+    <string name="widget_20_minutes">20 minutos</string>
+    <string name="widget_1_hour">1 hora</string>
+    <plurals name="widget_alarm_target_hours">
+        <item quantity="one">%d hora</item>
+        <item quantity="many">%d horas</item>
+        <item quantity="other">%d horas</item>
+    </plurals>
+    <string name="pref_display_add_device_fab_on">Siempre visible</string>
+    <string name="pref_display_add_device_fab_off">Visible solo si no se a帽ade ning煤n dispositivo</string>
+    <string name="activity_error_no_app_for_gpx">Para ver el rastro de actividad, instale una aplicaci贸n que maneje archivos GPX.</string>
+    <string name="preferences_makibes_hr3_settings">Configuraci贸n de Makibes HR3</string>
+    <string name="prefs_find_phone">Encontrar tel茅fono</string>
+    <string name="prefs_enable_find_phone">Activar \"Encontrar tel茅fono\"</string>
+    <string name="prefs_find_phone_summary">Utilice su pulsera para reproducir el tono del tel茅fono.</string>
+    <string name="prefs_find_phone_duration">Duraci贸n del tono en segundos</string>
+    <string name="maximum_duration">Duraci贸n</string>
+    <string name="discovery_need_to_enter_authkey">Este dispositivo necesita una clave de autenticaci贸n secreta. Mantenga presionado el dispositivo para introducirla. Consulte el wiki.</string>
+    <string name="fw_upgrade_notice_amazfitbip_lite">Est谩 a punto de instalar el 芦firmware禄 %s en su Amazfit Bip Lite.
+\n
+\nAseg煤rese de instalar el archivo .fw y, a continuaci贸n, el archivo .res. Se reiniciar谩 el reloj tras instalar el archivo .fw.
+\n
+\nNota: no es necesario instalar el archivo .res si es id茅ntico al instalado previamente.
+\n
+\n隆PROCEDA BAJO SU PROPIA CUENTA Y RIESGO!</string>
+    <string name="fw_upgrade_notice_amazfitgtr">Est谩 a punto de instalar el 芦firmware禄 %s en su Amazfit GTR.
+\n
+\nAseg煤rese de instalar el archivo .fw y, a continuaci贸n, el archivo .res. Se reiniciar谩 el reloj tras instalar el archivo .fw.
+\n
+\nNota: no es necesario instalar los archivos .res y .gps si son id茅nticos a los instalados previamente.
+\n
+\n隆PROCEDA BAJO SU PROPIA CUENTA Y RIESGO!</string>
+    <string name="pref_chart_heartrate_color_red">Rojo</string>
+    <string name="pref_chart_heartrate_color_orange">Naranja</string>
+    <string name="pref_title_chart_heartrate_color">Color de frecuencia card铆aca</string>
+    <string name="pref_title_chart_sleep_rolling_24_hour">Intervalo de sue帽o</string>
+    <string name="pref_chart_sleep_rolling_24_on">脷ltimas 24 horas</string>
+    <string name="pref_chart_sleep_rolling_24_off">De mediod铆a a mediod铆a</string>
+    <string name="appwidget_sleep_alarm_widget_label">Alarma de sue帽o</string>
+    <string name="pref_display_add_device_fab">Bot贸n de a帽adir nuevo dispositivo</string>
+    <string name="devicetype_makibes_hr3">Makibes HR3</string>
+    <string name="devicetype_amazfit_bip_lite">Amazfit Bip Lite</string>
+    <string name="devicetype_amazfit_gtr">Amazfit GTR</string>
+    <string name="devicetype_amazfit_gts">Amazfit GTS</string>
+    <string name="fw_upgrade_notice_amazfitgts">Est谩 a punto de instalar el firmware %s en su Amazfit GTS.
+\n
+\nPor favor, aseg煤rese de instalar el archivo .fw primero, el archivo .res a continuaci贸n y por ultimo el archivo .gps. Su reloj se reiniciara despu茅s de instalar el archivo .fw.
+\n
+\nNota: No hace falta instalar los archivos .res y .gps si los archivos son id茅nticos a los previamente instalados.
+\n
+\n隆PROCEDE BAJO TU PROPIO RIESGO!</string>
+    <string name="watch_not_connected">Reloj no conectado</string>
+    <string name="qhybrid_vibration_strength">Intensidad de la vibraci贸n:</string>
+    <string name="qhybrid_goal_in_steps">Objetivo en pasos</string>
+    <string name="qhybrid_time_shift">diferencia horaria</string>
+    <string name="qhybrid_second_timezone_offset_relative_to_utc">segunda compensaci贸n de huso horario en relaci贸n a UTC</string>
+    <string name="qhybrid_overwrite_buttons">botones de sobreescritura</string>
+    <string name="qhybrid_prompt_million_steps">Establezca el contador de pasos a un mill贸n para activarlo.</string>
+    <string name="qhybrid_buttons_overwrite_success">Botones sobrescritos</string>
+    <string name="qhybrid_offset_timezone">compensar huso horario por</string>
+    <string name="qhybrid_changes_delay_prompt">El cambio puede demorar varios segundos鈥�</string>
+    <string name="qhybrid_offset_time_by">tiempo compensado por</string>
+    <string name="pref_summary_disable_new_ble_scanning">Marque esta opci贸n si su dispositivo no es encontrado durante el descubrimiento</string>
+    <string name="pref_summary_allow_high_mtu">Incrementa la velocidad de transferencia, pero puede no funcionar en algunos dispositivos Android.</string>
+    <string name="pref_summary_sync_calendar">Activa las alertas del calendario, incluso cuando se encuentre desconectado</string>
+    <string name="pref_title_sync_caldendar">Sincronizar eventos del calendario</string>
+    <string name="qhybrid_use_activity_hand_as_notification_counter">utilizar manecilla de actividad como contador de notificaciones</string>
+    <string name="qhybrid_buttons_overwrite_error">Error al sobrescribir botones</string>
+    <string name="alarm_snooze">Dormitar</string>
+    <string name="hr_widget_heart_rate">Frecuencia card铆aca</string>
+    <string name="hr_widget_steps">Pasos</string>
+    <string name="hr_widget_active_minutes">Minutos activos</string>
+    <string name="pref_title_force_white_color_scheme">Forzar el esquema de color negro sobre blanco</string>
+    <string name="preferences_qhybrid_settings">Configuraci贸n de Q Hybrid</string>
+    <string name="pref_disable_new_ble_scanning">Desactivar exploraci贸n de BLE nueva</string>
+    <string name="prefs_button_single_press_action_selection_title">Acci贸n de suceso 1</string>
+    <string name="prefs_button_double_press_action_selection_title">Acci贸n de suceso 2</string>
+    <string name="prefs_button_triple_press_action_selection_title">Acci贸n de suceso 3</string>
+    <string name="error_no_location_access">Debe concederse y activarse el permiso de ubicaci贸n para que funcione adecuadamente la exploraci贸n</string>
+    <string name="hr_widget_date">Fecha</string>
+    <string name="hr_widget_calories">Calor铆as</string>
+    <string name="hr_widget_battery">Bater铆a</string>
+    <string name="hr_widget_weather">Tiempo</string>
+    <string name="hr_widget_nothing">Nada</string>
+    <string name="devicetype_banglejs">Bangle.js</string>
+    <string name="devicetype_y5">Y5</string>
+    <string name="devicetype_itag">iTag</string>
+    <string name="hr_appname_commute">Conmutar</string>
+    <string name="hr_appname_stopwatch">Cron贸grafa</string>
+    <string name="hr_appname_workout">Rutina de ejercicio</string>
+    <string name="hr_appname_wellness">Bienestar</string>
+    <string name="pref_summary_force_white_color_scheme">脷til si tu reloj tiene manecillas oscuras</string>
+    <string name="find_my_phone_notification">Encuentra mi tel茅fono</string>
+    <string name="pref_title_vibration_strength">Intensidad de la vibraci贸n</string>
+    <string name="pref_title_relax_firmware_checks">Activar si desea instalar un firmware no destinado a su dispositivo (bajo su propio riesgo)</string>
+    <string name="pref_summary_relax_firmware_checks">Relaje las comprobaciones de firmware</string>
+    <string name="pref_title_allow_high_mtu">Permitir MTU alta</string>
+    <string name="pref_summary_custom_deviceicon">Mostrar un icono de notificaci贸n de Android espec铆fico del dispositivo en lugar del icono de Gadgetbridge cuando est茅 conectado</string>
+    <string name="pref_title_custom_deviceicon">Mostrar icono de notificaci贸n espec铆fica del dispositivo</string>
+    <string name="pref_theme_system">Sistema</string>
+    <string name="pref_title_notifications_and_calls_repeat_on_misscall">Repetir cada X minutos</string>
+    <string name="pref_summary_notifications_and_calls_enable_misscall">Repetir a cada minuto</string>
+    <string name="pref_notifications_and_calls_enable_misscall">Notificar llamada perdida</string>
+    <string name="prefs_notifications_and_calls_continious_ring">Notificaciones durante llamada entrante</string>
+    <string name="pref_title_notifications_and_calls_repeat_on_call">Repetir notificaci贸n de la llamada</string>
+    <string name="pref_header_notifications_and_calls">Llamadas y Notificaciones</string>
+    <string name="title_activity_LenovoWatch_calibration">Calibraci贸n Watch X Plus</string>
+    <string name="controlcenter_set_alias">Establecer Alias</string>
+    <string name="sonyswr12_settings_title">Configuraci贸n de Sony SWR12</string>
+    <string name="sonyswr12_settings_low_vibration">Baja vibraci贸n activada</string>
+    <string name="sonyswr12_settings_stamina">El modo de ahorro de energ铆a est谩 activado</string>
+    <string name="sonyswr12_settings_alarm_interval">Intervalo de alarma inteligente en minutos</string>
+    <string name="sonyswr12_settings_low_vibration_summary">Activar baja intensidad de vibraci贸n en la pulsera</string>
+    <string name="sonyswr12_settings_stamina_summary">El modo de ahorro de energ铆a desactiva la medici贸n autom谩tica peri贸dica de la frecuencia card铆aca, lo que aumenta el tiempo de trabajo</string>
+    <string name="sonyswr12_settings_alarm_interval_summary">El intervalo de alarma inteligente es el intervalo anterior a la alarma instalada. En este intervalo, el dispositivo est谩 tratando de detectar la fase m谩s ligera del sue帽o para despertar al usuario</string>
+    <string name="activity_prefs_step_length_cm">Longitud del paso en cm</string>
+    <string name="czesh">Checo</string>
+    <string name="swedish">Sueco</string>
+    <string name="hebrew">Hebreo</string>
+    <string name="greek">Griego</string>
+    <string name="hungarian">H煤ngaro</string>
+    <string name="romanian">Rumano</string>
+    <string name="lefun_prefs_interface_language_title">Idioma de la interfaz</string>
+    <string name="lefun_prefs_antilost_summary">La banda vibrar谩 si tu tel茅fono se desconecta de la banda</string>
+    <string name="lefun_prefs_antilost_title">Antip茅rdida</string>
+    <string name="lefun_prefs_hydration_reminder_interval_title">Intervalo del recordatorio de hidrataci贸n (en minutos)</string>
+    <string name="lefun_prefs_hydration_reminder_summary">La banda vibrar谩 para recordarte que bebas agua</string>
+    <string name="lefun_prefs_hydration_reminder_title">Recordatorio de hidrataci贸n</string>
+    <string name="charts_min_max_heartrate_popup">Ritmo card铆aco m谩s bajo: %1$d
+\nRitmo card铆aco m谩s alto: %2$d
+\nIntensidad de movimiento: %3$s</string>
+    <string name="movement_intensity">Intensidad de movimiento</string>
+    <string name="firmware_update_progress">La carga est谩 en curso
+\n%1d%% a %.2fkbps (promedio %.2fkbps)
+\nParte %1d de %1d</string>
+    <string name="devicestatus_upload_failed">La carga ha fallado</string>
+    <string name="devicestatus_upload_aborted">La carga ha sido abortada!</string>
+    <string name="devicestatus_upload_validating">La carga est谩 siendo validada</string>
+    <string name="devicestatus_upload_completed">La carga se ha completado</string>
+    <string name="devicestatus_disconnected">El dispositivo se ha desconectado!</string>
+    <string name="devicestatus_disconnecting">El dispositivo se est谩 desconectando!</string>
+    <string name="devicestatus_upload_started">La carga ha comenzado</string>
+    <string name="devicestatus_upload_starting">La carga est谩 comenzando</string>
+    <string name="devicestatus_connected">El dispositivo est谩 conectado</string>
+    <string name="devicestatus_connecting">El dispositivo se est谩 conectando</string>
+    <string name="activity_list_summary_activities">Actividades</string>
+    <string name="activity_list_summary_intensity">Movimiento
+\nIntensidad</string>
+    <string name="activity_list_summary_active_time">Tiempo activo</string>
+    <string name="activity_list_summary_distance">Distancia</string>
+    <string name="activity_list_summary_active_steps">Pasos activos</string>
+    <string name="charts_activity_list">Lista de actividades</string>
+    <string name="prefs_charts_tabs">Tablas de gr谩ficos</string>
+    <string name="chart_get_active_and_synchronize">Haga alguna actividad y sincronice el dispositivo.</string>
+    <string name="chart_no_active_data">No se han detectado actividades.</string>
+    <string name="pref_header_auto_fetch">Buscar autom谩ticamente</string>
+    <string name="pref_title_device_spec_settings_show_raw_graph">Mostrar datos RAW en el gr谩fico de actividad</string>
+    <string name="pref_summary_notifications_and_calls_title_reject">Apagar - ignorar, Encender - rechazar</string>
+    <string name="prefs_notifications_and_calls_reject">Bot贸n ignorar/rechazar llamada</string>
+    <string name="pref_header_notifications_and_calls_callhandling">Gesti贸n de llamadas</string>
+    <string name="pref_title_weather_summary">Usado para el proveedor del tiempo de LineageOS, otras versiones de Android necesitan usar una aplicaci贸n como \"Weather notification\". Encuentra m谩s informaci贸n en la wiki de Gadgetbridge.</string>
+    <string name="fw_upgrade_notice_miband5">Est谩s a punto de instalar el firmware %s en tu Mi Band 5.
+\n
+\nPor favor, aseg煤rate de instalar el archivo .fw, luego el archivo .res. Tu banda se reiniciar谩 despu茅s de instalar el archivo .fw.
+\n
+\nNota: No es necesario instalar el archivo .res si es exactamente igual al instalado anteriormente.
+\n
+\nPROCEDA BAJO SU PROPIO RIESGO!</string>
+    <string name="fw_upgrade_notice_amazfit_trex">Est谩s a punto de instalar el firmware %s en tu Amazfit T-Rex.
+\n
+\nPor favor aseg煤rate de instalar el archivo .fw, luego el .res y finalmente el archivo .gps. Tu reloj se reiniciar谩 despu茅s de instalar el archivo .fw.
+\n
+\nNota: No es necesario instalar los archivos .res y .gps si son exactamente iguales a los instalados anteriormente.
+\n
+\nPROCEDA BAJO SU PROPIO RIESGO!</string>
+    <string name="find_lost_device_message">Buscar por %1$s\?</string>
+    <string name="pref_sensors_bp_calibration_low">Presi贸n arterial DIAST脫LICA (baja)</string>
+    <string name="pref_header_sensors_calibration">Calibraci贸n del sensor</string>
+    <string name="pref_sensors_bp_calibration_high">Presi贸n arterial SIST脫LICA (alta)</string>
+    <string name="pref_sensors_bp_calibration">Calibraci贸n de la presi贸n sangu铆nea</string>
+    <string name="pref_title_sensors_altitude">Calibraci贸n de altitud</string>
+    <string name="bip_prefs_shotcuts_summary">Elegir los atajos en la pantalla de la banda</string>
+    <string name="bip_prefs_shortcuts">Atajos</string>
+    <string name="baseAltitude">Altitud base</string>
+    <string name="averageStrokesPerSecond">Promedio brazada</string>
+    <string name="averageStrokeDistance">Distancia promedio de brazada</string>
+    <string name="averageStride">Promedio de avance</string>
+    <string name="distanceMeters">Distancia</string>
+    <string name="permission_granting_mandatory">Todos estos permisos son necesarios y podr铆a producirse inestabilidad si no se conceden</string>
+    <string name="power_mode_title">Modo de energ铆a del reloj</string>
+    <string name="power_mode_watch">Solo reloj</string>
+    <string name="power_mode_saving">Ahorro de energ铆a</string>
+    <string name="power_mode_normal">Normal</string>
+    <string name="prefs_sensors_button_bp_calibration_sum">Presione aqu铆 para empezar la calibraci贸n</string>
+    <string name="prefs_sensors_button_bp_calibration">Calibraci贸n</string>
+    <string name="prefs_notifications_and_calls_shake_reject">Agitar la mu帽eca para ignorar/rechazar llamada</string>
+    <string name="pref_summary_notifications_and_calls_title_shake_reject">Duplica la acci贸n del bot贸n de reloj</string>
+    <string name="pref_summary_device_spec_settings_title_force_time">Forzar la auto-sincronizaci贸n del tiempo al reconectar. 隆Las manecillas anal贸gicas pueden mostrar una hora incorrecta!</string>
+    <string name="pref_title_device_spec_settings_force_time">Forzar sincronizar el tiempo</string>
+    <string name="pref_header_device_spec_settings">Ajustes del dispositivo</string>
+    <string name="menuitem_stress">Estr茅s</string>
+    <string name="averageSpeed">Promedio de velocidad</string>
+    <string name="averageLapPace">Ritmo promedio de la vuelta</string>
+    <string name="averageKMPaceSeconds">Ritmo</string>
+    <string name="ascentSeconds">Ascendiendo</string>
+    <string name="flatSeconds">Plano</string>
+    <string name="descentSeconds">Descendiendo</string>
+    <string name="descentMeters">Cuesta abajo</string>
+    <string name="ascentMeters">Cuesta arriba</string>
+    <string name="averageHR">Frecuencia card铆aca</string>
+    <string name="totalStride">Avance total</string>
+    <string name="qhybrid_calibration_align_hint">Usa los botones de abajo para alinear las manecillas del reloj a las 12:00.</string>
+    <string name="devicetype_nut_mini">Nut Mini</string>
+    <string name="medley">Medley</string>
+    <string name="backstroke">Estilo espalda</string>
+    <string name="freestyle">Estilo libre</string>
+    <string name="breaststroke">Estilo braza</string>
+    <string name="activity_filter_to_placeholder">hoy</string>
+    <string name="activity_filter_from_placeholder">pasado distante</string>
+    <string name="activity_summaries_all_devices">Todos los dispositivos</string>
+    <string name="sports_activity_quick_filter_select">Periodo de tiempo</string>
+    <string name="sports_activity_quick_filter_30days">30 d铆as</string>
+    <string name="sports_activity_quick_filter_7days">7 d铆as</string>
+    <string name="sports_activity_quick_filter_last_month">El mes anterior</string>
+    <string name="sports_activity_quick_filter_this_month">Este Mes</string>
+    <string name="sports_activity_quick_filter_last_week">La semana anterior</string>
+    <string name="sports_activity_quick_filter_this_week">Esta semana</string>
+    <string name="activity_summaries_all_activities">Todas las actividades</string>
+    <string name="activity_summaries_statistics">Estad铆sticas</string>
+    <string name="no">No</string>
+    <string name="yes">S铆</string>
+    <string name="activity_filter_individual_items">Elementos seleccionados individualmente</string>
+    <string name="addto_filter">A帽adir a filtro</string>
+    <string name="activity_filter_apply_filter">Aplicar Filtro</string>
+    <string name="activity_filter_name_contains">Etiquetar</string>
+    <string name="activity_filter_filter_title">Filtrar</string>
+    <string name="activity_filter_reset_filter">Reiniciar filtro</string>
+    <string name="activity_filter_date_to">Hasta</string>
+    <string name="activity_filter_date_from">Desde</string>
+    <string name="pref_header_statistics">Estad铆sticas de actividades deportivas</string>
+    <string name="pref_header_filter">Filtro de actividades deportivas</string>
+    <string name="prefs_events_forwarding_action_title">Ejecutar acci贸n</string>
+    <string name="prefs_events_forwarding_broadcast_title">Mensaje de difusi贸n</string>
+    <string name="prefs_events_forwarding_startnonwear">Al No Usar</string>
+    <string name="prefs_events_forwarding_wokeup">Al Despertarse</string>
+    <string name="prefs_events_forwarding_fellsleep">Al Dormirse</string>
+    <string name="prefs_events_forwarding_title">Acciones del dispositivo</string>
+    <string name="prefs_events_forwarding_summary">Usar eventos del dispositivo para desencadenar acciones y emisiones de Android</string>
+    <string name="gps_track">Seguimiento de GPS</string>
+    <string name="activity_detail_show_gps_label">Mostrar seguimiento del GPS</string>
+    <string name="activity_detail_duration_label">Duraci贸n</string>
+    <string name="activity_detail_end_label">Finalizar</string>
+    <string name="activity_detail_start_label">Iniciar</string>
+    <string name="Steps">Pasos</string>
+    <string name="Activity">Actividad</string>
+    <string name="Speed">Velocidad</string>
+    <string name="Elevation">Elevaci贸n</string>
+    <string name="Distance">Distancia</string>
+    <string name="companiondevice_pairing_details">Activar el nuevo soporte de la API de CompanionDevice (s贸lo tiene efecto en Android 8 o superior) que aumentar谩 la fiabilidad si el servicio necesita ser reiniciado en segundo plano, requiere un re-emparejamiento usando Gadgetbridge paraque surja efecto</string>
+    <string name="notification_channel_high_priority_name">Alta prioridad</string>
+    <string name="activity_error_no_app_for_png">Para compartir esta captura de pantalla, instale una aplicaci贸n que pueda manejar archivos de imagen.</string>
+    <string name="activity_summary_yesterday">Ayer</string>
+    <string name="activity_summary_today">Hoy</string>
+    <string name="activity_summary_edit_name_title">Editar etiqueta</string>
+    <string name="activity_summary_detail">Detalle de la actividad deportiva</string>
+    <string name="activity_type_badminton">B谩dminton</string>
+    <string name="activity_type_pingpong">Ping Pong</string>
+    <string name="activity_type_basketball">Baloncesto</string>
+    <string name="activity_type_cricket">Cr铆quet</string>
+    <string name="activity_type_rowing_machine">M谩quina de remos</string>
+    <string name="activity_type_soccer">F煤tbol</string>
+    <string name="activity_type_yoga">Yoga</string>
+    <string name="activity_type_jump_roping">Saltando la cuerda</string>
+    <string name="activity_type_elliptical_trainer">Entrenador El铆ptica</string>
+    <string name="activity_type_indoor_cycling">Ciclismo en interiores</string>
+    <string name="activity_type_swimming_openwater">Nadar (Aguas abiertas)</string>
+    <string name="find_lost_device_you_found_it">隆Encontrado!</string>
+    <string name="charts_legend_heartrate_average">Promedio de frecuencia card铆aca</string>
+    <string name="activity_prefs_chart_min_session_length">Duraci贸n m铆nima de la actividad (minutos)</string>
+    <string name="activity_prefs_chart_max_idle_phase_length">Duraci贸n de la pausa para separar las actividades (minutos)</string>
+    <string name="activity_prefs_chart_min_steps_per_minute">Pasos m铆nimos por minuto para detectar la actividad</string>
+    <string name="activity_prefs_chart_min_steps_per_minute_for_run">Pasos m铆nimos por minuto para detectar la corrida</string>
+    <string name="pref_summary_connected_advertisement">Hace que el dispositivo sea visible por Bluetooth incluso cuando est谩 conectado</string>
+    <string name="prefs_autoremove_message">Eliminar autom谩ticamente las notificaciones por SMS</string>
+    <string name="prefs_fake_ring_duration">Falso timbre continuo</string>
+    <string name="prefs_operating_sounds">Sonidos de operaci贸n</string>
+    <string name="prefs_key_vibration">Vibraci贸n de las teclas</string>
+    <string name="prefs_autolight">Luz autom谩tica</string>
+    <string name="Swimming">Nataci贸n</string>
+    <string name="Strokes">Pulsaciones</string>
+    <string name="km">km</string>
+    <string name="bpm">lat/min</string>
+    <string name="minutes_km">min/km</string>
+    <string name="seconds_m">m/s</string>
+    <string name="seconds_km">km/s</string>
+    <string name="calories_unit">kcal</string>
+    <string name="laps_unit">vueltas</string>
+    <string name="swim_style">estilo de nataci贸n</string>
+    <string name="swolfIndex">SWOLF</string>
+    <string name="swolf_index">铆ndice swolf</string>
+    <string name="seconds">seg</string>
+    <string name="strokes_unit">p</string>
+    <string name="strokes_second">p/s</string>
+    <string name="km_h">km/h</string>
+    <string name="meters_second">m/s</string>
+    <string name="steps_unit">pasos</string>
+    <string name="cm">cm</string>
+    <string name="meters">m</string>
+    <string name="laps">Vueltas</string>
+    <string name="swimStyle">Estilo de nataci贸n</string>
+    <string name="maxPace">Ritmo m谩s r谩pido</string>
+    <string name="minPace">Ritmo m谩s lento</string>
+    <string name="maxSpeed">M谩ximo</string>
+    <string name="caloriesBurnt">Calor铆as</string>
+    <string name="activeSeconds">Activo</string>
+    <string name="steps">Pasos</string>
+    <string name="minAltitude">M铆nimo</string>
+    <string name="maxAltitude">M谩ximo</string>
+    <string name="error_location_enabled_mandatory">La ubicaci贸n debe estar activada para buscar dispositivos</string>
+    <string name="ignore_bonded_devices_description">Al activar esta opci贸n se ignoran los dispositivos que ya han sido vinculados/emparejados al escanear</string>
+    <string name="ignore_bonded_devices">Ignorar los dispositivos vinculados</string>
+    <string name="error_retrieving_devices_database">Error al recuperar los dispositivos de la base de datos</string>
+    <string name="error_setting_alias">Error al establecer el alias:</string>
+    <string name="error_exporting_device_preferences">Error al exportar las preferencias espec铆ficas del dispositivo</string>
+    <string name="pref_check_permission_status_summary">Comprueba y pide los permisos que faltan, incluso cuando pueden no ser necesarios al instante. Desactiva esto solo si tus dispositivos no soportan ninguna de estas caracter铆sticas. 隆No conceder un permiso puede causar problemas!</string>
+    <string name="pref_check_permission_status">Comprobar el estado de los permisos</string>
+    <string name="error_background_service_reason">El inicio del servicio en segundo plano fall贸 debido a una excepci贸n - haga clic aqu铆 para obtener m谩s informaci贸n.\n\nError:</string>
+    <string name="device_requires_key">CLAVE REQUERIDA, PULSACI脫N LARGA PARA ENTRAR</string>
+    <string name="device_is_currently_bonded">YA SE HA HECHO EL ENLACE</string>
+    <string name="error_background_service_reason_truncated">El inicio del servicio en segundo plano fall贸 porque鈥�</string>
+    <string name="error_background_service">Fallo al iniciar el servicio en segundo plano</string>
+    <string name="companiondevice_pairing">Emparejamiento de dispositivos de compa帽铆a</string>
+    <string name="require_location_provider">La ubicaci贸n debe estar activada</string>
+    <string name="error_version_check_extreme_caution">ATENCI脫N: 隆Error al comprobar la informaci贸n de la versi贸n! 隆No debes continuar! Nombre de la versi贸n visto \"%s\"</string>
+    <string name="about_links">Enlaces</string>
+    <string name="about_additional_contributions">Muchas gracias a todos los colaboradores no incluidos en la lista por aportar c贸digo, traducciones, apoyo, ideas, motivaci贸n, informes de errores, dinero鈥� 鉁�</string>
+    <string name="about_additional_device_support">Soporte de dispositivos adicionales</string>
+    <string name="about_contributors">Colaboradores</string>
+    <string name="about_core_team_title">Equipo principal (por orden de primera contribuci贸n de c贸digo)</string>
+    <string name="about_description_generic">Una aplicaci贸n de Android gratuita, de c贸digo abierto y sin conexi贸n para reemplazar las aplicaciones de c贸digo cerrado de los fabricantes de relojes inteligentes.</string>
+    <string name="about_activity_title_generic">Acerca de Gadgetbridge</string>
+    <string name="about_version">Versi贸n %s</string>
+    <string name="about_title">Acerca de</string>
+    <string name="hr_widget_last_notification">脷ltima notificaci贸n</string>
+    <string name="pref_qhybrid_save_raw_activity_files">Guardar archivos de actividad sin procesar</string>
+    <string name="pref_qhybrid_title_widget_draw_circles">Dibujar c铆rculos de widgets</string>
+    <string name="prefs_button_long_press_action_selection_title">Acci贸n del bot贸n de pulsaci贸n larga</string>
+    <string name="prefs_button_variable_actions">Configuraci贸n detallada de la pulsaci贸n de botones</string>
+    <string name="pref_title_lower_button_function_double">Bot贸n inferior doble</string>
+    <string name="pref_title_middle_button_function_double">Bot贸n central doble</string>
+    <string name="pref_title_upper_button_function_double">Bot贸n superior doble</string>
+    <string name="pref_title_lower_button_function_long">Bot贸n inferior largo</string>
+    <string name="pref_title_middle_button_function_long">Bot贸n central largo</string>
+    <string name="pref_title_upper_button_function_long">Bot贸n superior largo</string>
+    <string name="pref_title_lower_button_function_short">Bot贸n inferior corto</string>
+    <string name="pref_title_middle_button_function_short">Bot贸n central corto</string>
+    <string name="pref_title_upper_button_function_short">Bot贸n superior corto</string>
+    <string name="widget_settings_select_device_title">Seleccionar dispositivo</string>
+    <string name="menuitem_sleep">Dormir</string>
+    <string name="menuitem_pomodoro">Pomodoro</string>
+    <string name="menuitem_goal">Objetivo de la actividad</string>
+    <string name="menuitem_stopwatch">Cron贸metro</string>
+    <string name="menuitem_dnd">No molestar</string>
+    <string name="menuitem_alexa">Alexa</string>
+    <string name="menuitem_takephoto">Control remoto de la c谩mara</string>
+    <string name="menuitem_mutephone">Silenciar tel茅fono</string>
+    <string name="menuitem_findphone">Encontrar tel茅fono</string>
+    <string name="menuitem_worldclock">Reloj mundial</string>
+    <string name="menuitem_unknown">Desconocido</string>
+    <string name="menuitem_workout">Entrenamiento</string>
+    <string name="menuitem_eventreminder">Recordatorio de eventos</string>
+    <string name="menuitem_spo2">SpO2</string>
+    <string name="menuitem_hr">Frecuencia card铆aca</string>
+    <string name="menuitem_pai">PAI</string>
+    <string name="menuitem_cycles">Seguimiento del ciclo</string>
+    <string name="menuitem_breathing">Respiraci贸n</string>
+    <string name="devicetype_lefun">Lefun</string>
+    <string name="devicetype_sg2">Lemfo SG2</string>
+    <string name="devicetype_amazfit_vergel">Amazfit Verge Lite</string>
+    <string name="devicetype_pinetime_jf">PineTime (Firmware JF)</string>
+    <string name="devicetype_tlw64">TLW64</string>
+    <string name="devicetype_casiogbx100">Casio GBX-100</string>
+    <string name="devicetype_watchxplus">Watch X Plus</string>
+    <string name="devicetype_watchx">Watch X</string>
+    <string name="devicetype_qhybrid">Fossil Q Hybrid</string>
+    <string name="devicetype_amazfit_gts2">Amazfit GTS 2</string>
+    <string name="devicetype_amazfit_gtr2">Amazfit GTR 2</string>
+    <string name="devicetype_amazfit_bipupro">Amazfit Bip U Pro</string>
+    <string name="devicetype_amazfit_bipu">Amazfit Bip U</string>
+    <string name="devicetype_amazfit_bips_lite">Amazfit Bip S Lite</string>
+    <string name="devicetype_amazfit_bips">Amazfit Bip S</string>
+    <string name="devicetype_amazfit_trex">Amazfit T-Rex</string>
+    <string name="devicetype_amazfit_neo">Amazfit Neo</string>
+    <string name="devicetype_amazfit_band5">Amazfit Band 5</string>
+    <string name="devicetype_miband5">Mi Band 5</string>
+    <string name="dbmanagementactivity_export_finished">Eliminaci贸n finalizada</string>
+    <string name="activity_db_management_clean_export_directory_text">Los archivos exportados en el directorio de Exportaci贸n/Importaci贸n son accesibles por cualquier aplicaci贸n de su dispositivo. Es posible que desees eliminar estos archivos despu茅s de la sincronizaci贸n o la copia de seguridad. Aseg煤rate de tener una copia de seguridad antes de eliminarlos. Los archivos GPX, los subdirectorios y el archivo de base de datos autoexportado (si existe) no se eliminar谩n. La ruta del directorio de exportaci贸n/importaci贸n es:</string>
+    <string name="activity_DB_clean_export_directory_warning_title">驴Eliminar archivos en el directorio de exportaci贸n/importaci贸n\?</string>
+    <string name="activity_DB_clean_export_directory_warning_message">驴Eliminar realmente los archivos del directorio de exportaci贸n/importaci贸n\?</string>
+    <string name="activity_db_management_clean_export_directory_label">Eliminar archivos en el directorio de exportaci贸n/importaci贸n</string>
+    <string name="activity_DB_ShowContentButton">Mostrar el contenido del directorio de exportaci贸n/importaci贸n</string>
+    <string name="activity_data_management_directory_content_title">Exportar/Importar el contenido del directorio</string>
+    <string name="dbmanagementactivity_export_confirmation">驴Exportar datos realmente\? Los datos de actividad previamente exportados (si los hay) y las preferencias se sobrescribir谩n.</string>
+    <string name="dbmanagementactivity_export_data_title">驴Exportar datos\?</string>
+    <string name="dbmanagementactivity_error_cleaning_export_directory">Error al borrar archivos del directorio de exportaci贸n %1$s</string>
+    <string name="prefs_charts_tabs_summary">Pesta帽as visibles del gr谩fico</string>
+    <string name="discovery_entered_invalid_authkey">隆La clave secreta de autentificaci贸n que ha introducido no es v谩lida! Haga una pulsaci贸n larga en el dispositivo para editarla.</string>
+    <string name="pref_title_connected_advertisement">Visible mientras est谩 conectado</string>
+    <string name="fw_upgrade_notice_amazfitvergel">Est谩s a punto de instalar el firmware %s en tu Amazfit Verge Lite.
+\n
+\nPor favor, aseg煤rate de instalar el archivo .fw, luego el archivo .res y finalmente el archivo .gps. Su reloj se reiniciar谩 despu茅s de instalar el archivo .fw.
+\n
+\nNota: No es necesario instalar los archivos .res y .gps si estos archivos son exactamente los mismos que los instalados anteriormente.
+\n
+\n隆PROCEDE BAJO TU PROPIO RIESGO!</string>
+    <string name="minutes_mi">min/mi</string>
+    <string name="ft">ft</string>
+    <string name="fossil_hr_auth_failed">Autenticaci贸n fallida, funcionalidad limitada</string>
+    <string name="fossil_hr_unavailable_unauthed">No est谩 disponible en modo no autenticado</string>
+    <string name="fossil_hr_synced_activity_data">Datos de actividad sincronizados</string>
+    <string name="fossil_hr_commute_processing">Procesando la solicitud鈥�</string>
+    <string name="mi">mi</string>
+    <string name="mi_h">mi/h</string>
+    <string name="devicetype_waspos">Wasp-os</string>
+    <string name="devicetype_zepp_e">Zepp E</string>
+    <string name="devicetype_amazfit_x">Amazfit X</string>
+    <string name="devicetype_amazfit_gts2e">Amazfit GTS 2e</string>
+    <string name="devicetype_amazfit_gts2_mini">Amazfit GTS 2 Mini</string>
+    <string name="devicetype_amazfit_gtr2e">Amazfit GTR 2e</string>
+    <string name="activity_type_strength_training">Entrenamiento de fuerza</string>
+    <string name="show_ongoing_activity">Mostrar ventana emergente de actividad en curso</string>
+    <string name="dialog_hide">Ocultar</string>
+    <string name="fw_upgrade_notice_amazfitx">Est谩s a punto de instalar el firmware %s en tu Amazfit X.
+\n
+\nPor favor, aseg煤rese de instalar el archivo .fw, y despu茅s el archivo .res. Su banda se reiniciar谩 despu茅s de instalar el archivo .fw.
+\n
+\nNota: No es necesario instalar .res si es exactamente el mismo que el instalado anteriormente.
+\n
+\n隆PROCEDA BAJO SU PROPIO RIESGO!</string>
+    <string name="calendar_year">A帽o</string>
+    <string name="calendar_six_months">6 meses</string>
+    <string name="calendar_three_months">3 meses</string>
+    <string name="calendar_month">Mes</string>
+    <string name="calendar_two_weeks">Dos semanas</string>
+    <string name="calendar_week">Semana</string>
+    <string name="calendar_day">D铆a</string>
+    <string name="battery_level">Nivel de la bater铆a</string>
+    <string name="battery_detail_activity_title">Informaci贸n de la bater铆a</string>
+    <string name="pref_summary_canned_messages_dismisscall">Descartar llamadas desde el reloj con un mensaje SMS</string>
+    <string name="pref_summary_canned_messages_set">Env铆e los mensajes configurados a continuaci贸n a su dispositivo</string>
+    <string name="qhybrid_summary_calibration">Calibrar las agujas del reloj</string>
+    <string name="qhybrid_summary_file_management">Subir y descargar archivos</string>
+    <string name="pref_summary_physical_buttons">Configurar la funcionalidad de los botones f铆sicos del reloj</string>
+    <string name="pref_title_physical_buttons">Botones f铆sicos</string>
+    <string name="fossil_hr_warning_firmware_too_new">Algunas funciones est谩n deshabilitadas porque el firmware del reloj es demasiado nuevo</string>
+    <string name="qhybrid_title_calibration">Calibraci贸n</string>
+    <string name="qhybrid_title_apps_management">Administraci贸n de aplicaciones</string>
+    <string name="qhybrid_title_file_management">Administraci贸n de archivos</string>
+    <string name="qhybrid_title_background_image">Imagen de fondo</string>
+    <string name="qhybrid_title_apps">Aplicaciones</string>
+    <string name="menuitem_temperature">Temperatura</string>
+    <string name="menuitem_widgets">Widgets</string>
+    <string name="menuitem_events">Eventos</string>
+    <string name="devicetype_um25">UM-25</string>
+    <string name="devicetype_miband6">Mi Band 6</string>
+    <string name="kind_agps_bundle">Paquete AGPS</string>
+    <string name="prefs_sounds_summary">Configurar cu谩ndo el dispositivo emitir谩 un pitido</string>
+    <string name="prefs_sounds">Sonidos</string>
+    <string name="fw_upgrade_notice_amazfitneo">Est谩 a punto de instalar el firmware %s en su Amazfit Neo.
+\n
+\nSu banda se reiniciar谩 despu茅s de instalar el archivo .fw.
+\n
+\n隆PROCEDA BAJO SU PROPIO RIESGO!</string>
+    <string name="fw_upgrade_notice_miband6">Est谩 a punto de instalar el 芦firmware禄 %s en su Mi Band 6.
+\n
+\nAseg煤rese de instalar el archivo .fw y, a continuaci贸n, el archivo .res. Se reiniciar谩 la pulsera tras instalar el archivo .fw.
+\n
+\nNota: no es necesario instalar el archivo .res si es id茅ntico al instalado previamente.
+\n
+\n隆PROCEDA BAJO SU PROPIA CUENTA Y RIESGO!</string>
+    <string name="qhybrid_pref_title_external_intents">Permitir intents externos peligrosos</string>
+    <string name="pref_summary_developer_settings">Ajustes y funcionalidades utilizadas por los desarrolladores</string>
+    <string name="pref_title_developer_settings">Configuraci贸n de los desarrolladores</string>
+    <string name="qhybrid_pref_title_actions">Acciones</string>
+    <string name="qhybrid_calibration_100_steps">100 pasos</string>
+    <string name="qhybrid_calibration_10_steps">10 pasos</string>
+    <string name="qhybrid_pref_summary_external_intents">Permite que otras apps de Android suban/sobreescriban archivos</string>
+    <string name="fossil_hr_new_action">Nueva acci贸n</string>
+    <string name="fossil_hr_new_action_cancel">cancelar</string>
+    <string name="fossil_hr_edit_action">Editar acci贸n</string>
+    <string name="fossil_hr_edit_action_delete">borrar</string>
+    <string name="fossil_hr_commute_actions_explanation">Las acciones configuradas aqu铆 aparecer谩n en la app Commute de su reloj. Lea la wiki para obtener informaci贸n sobre c贸mo manejar los Intents producidos por estas acciones.</string>
+    <string name="qhybrid_title_watchface">Configuraci贸n de Watchface</string>
+    <string name="notification_channel_low_battery_name">Bater铆a baja</string>
+    <string name="notification_channel_transfer_name">Transferencia de datos</string>
+    <string name="watchface_install_info">Est谩 a punto de instalar el siguiente watchface:
+\n
+\n%1$s
+\nVersi贸n %2$s por %3$s
+\n</string>
+    <string name="watchface_setting_power_saving_hands">Desactivar el movimiento de las manos mientras est茅 fuera de la mu帽eca</string>
+    <string name="watchface_setting_power_saving_display">Desactivar las actualizaciones de la pantalla mientras est茅 fuera de la mu帽eca</string>
+    <string name="watchface_setting_title_power_saving">Ahorro de energ铆a</string>
+    <string name="watchface_widget_type_chance_rain">Probabilidad de lluvia</string>
+    <string name="watchface_widget_type_active_mins">Minutos activos</string>
+    <string name="watchface_widget_type_2nd_tz">2陋 zona horaria</string>
+    <string name="watchface_widget_type_calories">Calor铆as</string>
+    <string name="watchface_widget_type_battery">Bater铆a</string>
+    <string name="watchface_upload_failed">La carga de la esfera del reloj ha fallado. Por favor, int茅ntelo de nuevo.</string>
+    <string name="watchface_cache_confirm_overwrite">Ya existe una esfera con este nombre en la cach茅. 驴Desea sobrescribirlo\?</string>
+    <string name="watchface_setting_wrist_flick_duration">Duraci贸n (en ms)</string>
+    <string name="watchface_setting_wrist_flick_minute">Manecilla de los minutos (-360 a 360)</string>
+    <string name="watchface_setting_wrist_flick_hour">Manecilla del reloj (-360 a 360)</string>
+    <string name="watchface_setting_wrist_flick_move_relative">Mover las manecillas en relaci贸n con el tiempo</string>
+    <string name="watchface_setting_desc_wrist_flick">(para desactivar por completo, active el movimiento relativo y ponga todos los valores a 0)</string>
+    <string name="watchface_setting_title_wrist_flick">Golpe de mu帽eca</string>
+    <string name="watchface_setting_display_refresh_partial">Refresco parcial (en minutos)</string>
+    <string name="watchface_setting_display_refresh_full">Refresco total (en minutos)</string>
+    <string name="watchface_setting_title_display_refresh_timeout">Tiempo de refresco de la pantalla</string>
+    <string name="watchface_dialog_title_settings">Ajustes de la esfera</string>
+    <string name="watchface_dialog_widget_color_black">Negro</string>
+    <string name="watchface_dialog_widget_color_white">Blanco</string>
+    <string name="watchface_dialog_widget_color">Color</string>
+    <string name="qhybrid_title_watchface_designer">Dise帽ador de la esfera</string>
+    <string name="watchface_dialog_widget_preset_right">Derecha</string>
+    <string name="watchface_dialog_widget_preset_left">Izquierda</string>
+    <string name="watchface_dialog_widget_preset_bottom">Inferior</string>
+    <string name="watchface_dialog_widget_preset_top">Superior</string>
+    <string name="watchface_dialog_widget_presets">Preajustes de posici贸n</string>
+    <string name="watchface_dialog_widget_y_coordinate">Coordenadas Y (m谩ximo 240)</string>
+    <string name="watchface_dialog_widget_x_coordinate">Coordenadas X (m谩ximo 240)</string>
+    <string name="watchface_dialog_widget_type">Tipo</string>
+    <string name="appmanager_app_edit">Editar</string>
+    <string name="button_watchface_save_apply">Guardar y aplicar</string>
+    <string name="button_watchface_preview">Vista previa en el reloj</string>
+    <string name="watchface_widget_type_heart_rate">Frecuencia card铆aca</string>
+    <string name="watchface_widget_type_steps">Pasos</string>
+    <string name="watchface_widget_type_weather">Tiempo</string>
+    <string name="watchface_widget_type_date">Fecha</string>
+    <string name="watchface_dialog_title_add_widget">A帽adir widget</string>
+    <string name="watchface_toast_settings_incomplete">Configuraci贸n incompleta, widget no a帽adido</string>
+    <string name="watchface_dialog_title_set_name">Establecer el nombre de la esfera</string>
+    <string name="button_watchface_settings">Ajustes de la esfera</string>
+    <string name="button_watchface_add_widget">A帽adir widget</string>
+    <string name="button_watchface_select_image">Cambiar imagen de fondo</string>
+    <string name="button_watchface_edit_name">Editar nombre</string>
+    <string name="devicetype_amazfit_trex_pro">Amazfit T-Rex Pro</string>
+    <string name="qhybrid_pref_summary_actions">Acciones para la aplicaci贸n Commute</string>
+    <string name="qhybrid_watchface_configuration_old_firmware">Pantalla de configuraci贸n de la esfera del reloj para los relojes con la versi贸n de firmware DN1.0.2.19r e inferior</string>
+    <string name="qhybrid_calibration_1_step">1 paso</string>
+    <string name="qhybrid_calibration_clockwise">En sentido horario</string>
+    <string name="qhybrid_calibration_counterclockwise">En sentido antihorario</string>
+    <string name="gpx_receiver_overwrite_some_files">Alg煤n(os) archivo(s) ya existen. 驴Sobreescribir\?</string>
+    <string name="gpx_receiver_files_received">Archivo(s) GPX recibido(s):</string>
+    <string name="gpx_receiver_activity_title">Receptor GPX Gadgetbridge</string>
+    <string name="menuitem_barometer">Bar贸metro</string>
+    <string name="devicetype_smaq2oss">SMA-Q2 OSS</string>
+    <string name="activity_summary_detail_editing_gpx_track">Edici贸n de la pista GPX vinculada</string>
+    <string name="activity_summary_detail_clear_gpx_track">Borrar pista GPX</string>
+    <string name="activity_summary_detail_select_gpx_track">Seleccionar pista GPX</string>
+    <string name="set">Establecer</string>
+    <string name="heart_rate_result">Resultados de la medici贸n</string>
+    <string name="getting_heart_rate">Medici贸n</string>
+    <string name="blood_pressure">Presi贸n arterial</string>
+    <string name="pref_theme_black_background">Usar fondo negro en el tema oscuro</string>
+    <string name="pref_summary_huami_force_new_protocol">Activar si su dispositivo ya no se conecta despu茅s de una actualizaci贸n de firmware</string>
+    <string name="pref_title_huami_force_new_protocol">Nuevo protocolo de autenticaci贸n</string>
+    <string name="menuitem_flashlight">Linterna</string>
+    <string name="enable_vibrations_summary">Vibraci贸n para llamadas, mensajes, notificaciones y m谩s</string>
+    <string name="prefs_vibration_enable">Activar vibraci贸n</string>
+    <string name="prefs_autoheartrate_interval">Frecuencia de las mediciones</string>
+    <string name="prefs_autoheartrate_sleep">Tomar medidas durante el sue帽o</string>
+    <string name="prefs_autoheartrate_measurement">Mediciones autom谩ticas del ritmo card铆aco</string>
+    <string name="prefs_autoheartrate_summary">Mediciones peri贸dicas del ritmo card铆aco durante el d铆a y tambi茅n mientras duerme</string>
+    <string name="prefs_autoheartrate">Ritmo card铆aco autom谩tico</string>
+    <string name="enable_notifications_summary">Notificaciones de llamadas, mensajes y m谩s</string>
+    <string name="prefs_notifications_enable">Activar notificaciones</string>
+    <string name="prefs_sleep_time_summary">Especifica las horas en las que se registra el sue帽o</string>
+    <string name="prefs_sleep_time_label">Definir las horas de sue帽o</string>
+    <string name="prefs_sleep_time">Horas de sue帽o</string>
+    <string name="devicetype_fitpro">FitPro</string>
+    <string name="pref_title_ping_tone">Tono de ping</string>
+    <string name="nothing_prefs_inear_summary">Reproducir/pausar la m煤sica dependiendo de si lleva los auriculares</string>
+    <string name="nothing_prefs_inear_title">Detecci贸n de la oreja</string>
+    <string name="nothing_prefs_audiomode_title">Modo de sonido</string>
+    <string name="devicetype_nothingear1">Nothing Ear (1)</string>
+    <string name="uncheck_all_applications">Desmarcar todas las aplicaciones</string>
+    <string name="check_all_applications">Marcar todas las aplicaciones</string>
+    <string name="pref_title_notification_use_as_deny">Denegar las notificaciones de las aplicaciones seleccionadas</string>
+    <string name="pref_title_message_privacy_mode">Modo de privacidad de los mensajes</string>
+    <string name="pref_message_privacy_mode_complete">Ocultar todo el contenido</string>
+    <string name="pref_message_privacy_mode_off">Mostrar todo el contenido</string>
+    <string name="pref_applications_settings">Lista de aplicaciones</string>
+    <string name="title_activity_notification_management">Ajustes de las notificaciones</string>
+    <string name="pref_header_notification_application_settings">Por ajustes de la aplicaci贸n</string>
+    <string name="pref_title_notification_use_as">Usar la lista de aplicaciones para鈥�</string>
+    <string name="pref_title_notification_use_as_allow">Permitir las notificaciones de las aplicaciones seleccionadas</string>
+    <string name="toast_app_must_not_be_selected">La aplicaci贸n no debe ser seleccionada para ser configurada</string>
+    <string name="toast_app_must_be_selected">La aplicaci贸n debe ser seleccionada para ser configurada</string>
+    <string name="prefs_ambient_voice_focus">Enfoque de la voz</string>
+    <string name="prefs_ambient_sound">Sonido ambiente</string>
+    <string name="prefs_galaxy_touch_options">Opciones t谩ctiles</string>
+    <string name="battery_case">Caja de la bater铆a</string>
+    <string name="right_earbud">Auricular derecho</string>
+    <string name="devicetype_galaxybuds">Galaxy Buds</string>
+    <string name="prefs_equalizer_preset">Preajuste del ecualizador</string>
+    <string name="pref_title_equalizer_bass_boost">Refuerzo de graves</string>
+    <string name="pref_title_equalizer_soft">Suave</string>
+    <string name="pref_title_equalizer_dynamic">Din谩mico</string>
+    <string name="pref_title_equalizer_clear">Claro</string>
+    <string name="pref_title_equalizer_trebble">Refuerzo de agudos</string>
+    <string name="prefs_dolby_mode">Modo Dolby</string>
+    <string name="prefs_equalizer">Ecualizador</string>
+    <string name="prefs_equalizer_summary">Activar o desactivar el ecualizador</string>
+    <string name="prefs_dolby_summary">Preajuste Dolby para el ecualizador</string>
+    <string name="prefs_game_mode">Modo juego</string>
+    <string name="prefs_game_mode_summary">S贸lo si su tel茅fono es compatible con el modo juego</string>
+    <string name="prefs_touch_lock">Bloqueo t谩ctil</string>
+    <string name="prefs_touch_lock_summary">Desactivar eventos t谩ctiles</string>
+    <string name="prefs_galaxy_buds_experimental">Experimental</string>
+    <string name="prefs_ambient_volume">Volumen ambiental</string>
+    <string name="prefs_ambient_voice_summary">Destaca la voz</string>
+    <string name="prefs_ambient_mode">Modo ambiente</string>
+    <string name="prefs_left">Izquierda</string>
+    <string name="prefs_right">Derecha</string>
+    <string name="left_earbud">Auricular izquierdo</string>
+    <string name="pref_header_equalizer">Ecualizador</string>
+    <string name="devicetype_sony_wh_1000xm3">Sony WH-1000XM3</string>
+    <string name="pref_title_equalizer_normal">Normal</string>
+    <string name="prefs_active_noise_cancelling">Cancelaci贸n de ruido activa</string>
+    <string name="prefs_pressure_relief">Alivio de la presi贸n con sonido ambiente</string>
+    <string name="pressure_relief_summary">Evita la sensaci贸n de presi贸n en los o铆dos cuando no se usa la Cancelaci贸n de ruido activa</string>
+    <string name="pref_header_sony_ambient_sound_control">Control del sonido ambiente</string>
+    <string name="sony_ambient_sound">Modo</string>
+    <string name="sony_ambient_sound_off">Apagado</string>
+    <string name="sony_ambient_sound_wind_noise_reduction">Reducci贸n del ruido del viento</string>
+    <string name="sony_ambient_sound_ambient_sound">Sonido ambiente</string>
+    <string name="sony_ambient_sound_focus_voice">Centrarse en la voz</string>
+    <string name="sony_ambient_sound_level">Nivel de sonido ambiente</string>
+    <string name="sony_sound_position_front">Frontal</string>
+    <string name="sony_sound_position_rear_left">Trasera izquierda</string>
+    <string name="sony_sound_position_rear_right">Trasera derecha</string>
+    <string name="sony_surround_mode">Modo envolvente</string>
+    <string name="sony_surround_mode_off">Apagado</string>
+    <string name="sony_surround_mode_arena">Arena</string>
+    <string name="sony_surround_mode_club">Club</string>
+    <string name="sony_surround_mode_outdoor_stage">Escenario al aire libre</string>
+    <string name="sony_surround_mode_concert_hall">Sala de conciertos</string>
+    <string name="sony_equalizer">Ecualizador</string>
+    <string name="sony_equalizer_preset_bright">Brillante</string>
+    <string name="sony_equalizer_preset_mellow">Tenue</string>
+    <string name="sony_equalizer_preset_relaxed">Relajado</string>
+    <string name="sony_equalizer_preset_vocal">Vocal</string>
+    <string name="sony_equalizer_preset_bass_boost">Refuerzo de graves</string>
+    <string name="sony_equalizer_preset_manual">Manual</string>
+    <string name="sony_equalizer_preset_custom_1">Personalizado 1</string>
+    <string name="sony_equalizer_preset_custom_2">Personalizado 2</string>
+    <string name="pref_header_sony_equalizer_preset_custom_1">Preajuste personalizado 1</string>
+    <string name="pref_header_sony_equalizer_preset_custom_2">Preajuste personalizado 2</string>
+    <string name="sony_equalizer_band_400">400</string>
+    <string name="sony_equalizer_band_1000">1k</string>
+    <string name="sony_equalizer_band_2500">2.5k</string>
+    <string name="sony_equalizer_band_6300">6.3k</string>
+    <string name="sony_equalizer_band_16000">16k</string>
+    <string name="sony_equalizer_clear_bass">Bajos claros</string>
+    <string name="sony_touch_sensor">Control por sensor t谩ctil</string>
+    <string name="sony_notification_voice_guide">Notificaciones y gu铆a de voz</string>
+    <string name="sony_automatic_power_off">Apagado autom谩tico</string>
+    <string name="sony_automatic_power_off_3_hour">3 horas</string>
+    <string name="watchface_widget_type_custom">Widget personalizado</string>
+    <string name="pref_header_system">Sistema</string>
+    <string name="devicetype_galaxybuds_live">Galaxy Buds Live</string>
+    <string name="prefs_active_noise_cancelling_summary">Bloquea los ruidos del entorno</string>
+    <string name="sony_sound_position_off">Apagado</string>
+    <string name="sony_warn_sbc_codec">Advertencia: Los ajustes del ecualizador, sonido y reverberaci贸n s贸lo funcionan para el c贸dec de audio SBC.</string>
+    <string name="sony_equalizer_preset_off">Apagado</string>
+    <string name="sony_equalizer_preset_treble_boost">Refuerzo de agudos</string>
+    <string name="pref_header_other">Otros</string>
+    <string name="sony_ambient_sound_noise_cancelling">Cancelaci贸n de ruido</string>
+    <string name="sony_sound_position">Posici贸n de sonido</string>
+    <string name="sony_sound_position_front_right">Frontal derecha</string>
+    <string name="sony_sound_position_front_left">Frontal izquierda</string>
+    <string name="sony_equalizer_preset_excited">Excitado</string>
+    <string name="sony_equalizer_preset_speech">Discurso</string>
+    <string name="sony_automatic_power_off_5_min">5 minutos</string>
+    <string name="sony_automatic_power_off_30_min">30 minutos</string>
+    <string name="sony_automatic_power_off_off">No apagar</string>
+    <string name="sony_automatic_power_off_1_hour">1 hora</string>
+    <string name="prefs_activity_in_device_card_title_summary">Mostrar los pasos actuales, la distancia o el registro de sue帽o en la tarjeta del dispositivo</string>
+    <string name="prefs_fm_preset_instructions">Presionar prolongadamente el bot贸n para guardar la preconfiguraci贸n</string>
+    <string name="prefs_fm_presets_presets">Preconfiguraci贸n</string>
+    <string name="sony_automatic_power_off_when_taken_off">Al quitar (el dispositivo)</string>
+    <string name="watchface_dialog_widget_timezone">Zona horaria</string>
+    <string name="watchface_dialog_widget_update_timeout">Tiempo de actualizaci贸n en minutos</string>
+    <string name="watchface_dialog_widget_timeout_hide_text">Ocultar texto en tiempo de espera</string>
+    <string name="watchface_dialog_widget_timeout_show_circle">Mostrar c铆rculo en tiempo de espera</string>
+    <string name="device_card_activity_card_title">Informaci贸n de actividad en la tarjeta del dispositivo</string>
+    <string name="device_card_activity_card_title_summary">Elija qu茅 detalles de la actividad son mostrados en la tarjeta del dispositivo</string>
+    <string name="prefs_activity_in_device_card_title">Mostrar informaci贸n de actividad en la tarjeta del dispositivo</string>
+    <string name="prefs_activity_in_device_card_sleep_title">Sue帽o</string>
+    <string name="prefs_activity_in_device_card_sleep_title_summary">Mostrar duraci贸n del sue帽o</string>
+    <string name="prefs_activity_in_device_card_distance_title_summary">La distancia es calculada en base a n煤mero de pasos y la longitud de la zancada (ajustables en Configuraci贸n - Acerca de ti)</string>
+    <string name="prefs_activity_in_device_card_steps_title_summary">Mostrar los pasos totales</string>
+    <string name="title_activity_set_reminders">Configurar recordatorios</string>
+    <string name="controlcenter_start_configure_reminders">Configurar recordatorios</string>
+    <string name="reminder_repeat">Repetir</string>
+    <string name="reminder_date">Fecha</string>
+    <string name="reminder_time_once">%1$s, una vez</string>
+    <string name="reminder_time_every_week">%1$s, todas las semanas</string>
+    <string name="reminder_time_every_month">%1$s, todos los meses</string>
+    <string name="reminder_time_every_year">%1$s, todos los a帽os</string>
+    <string name="reminder_once">Una vez</string>
+    <string name="reminder_every_month">Todos los meses</string>
+    <string name="reminder_delete_confirm_title">Eliminar recordatorio</string>
+    <string name="reminder_no_free_slots_title">No hay espacios libres</string>
+    <string name="title_activity_reminder_details">Detalles del recordatorio</string>
+    <string name="reminder_every_year">Todos los a帽os</string>
+    <string name="reminder_message">Mensaje</string>
+    <string name="reminder_time">Hora</string>
+    <string name="reminder_time_every_day">%1$s, todos los d铆as</string>
+    <string name="reminder_every_week">Todas las semanas</string>
+    <string name="reminder_every_day">Todos los d铆as</string>
+    <string name="reminder_delete_confirm_description">驴Est谩 seguro de que quiere eliminar el recordatorio\?</string>
+    <string name="reminder_no_free_slots_description">El dispositivo no tiene espacios libres para recordatorios (total de espacios: %1$s)</string>
+    <string name="miband_prefs_reserve_reminder_calendar">Recordatorios para reservar para los pr贸ximos eventos</string>
+    <string name="prefs_reserve_reminder_calendar_summary">N煤mero de eventos del calendario que se sincronizar谩n</string>
+    <string name="qhybrid_title_on_device_confirmation">Activar la confirmaci贸n del emparejamiento en el dispositivo</string>
+    <string name="qhybrid_summary_on_device_confirmation">Las confirmaciones de emparejamiento en el dispositivo pueden resultar molestas. Desactivarlas puede hacer que pierda funcionalidad.</string>
+    <string name="mi2_prefs_do_not_disturb_lift_wrist">Activar la pantalla al levantarse durante la funci贸n \"No molestar\"</string>
+    <string name="averageAltitude">Media</string>
+    <string name="minSpeed">M铆nimo</string>
+    <string name="maxHR">Ritmo card铆aco m谩ximo</string>
+    <string name="minHR">Ritmo card铆aco m铆nimo</string>
+    <string name="minStride">Zancada m铆nima</string>
+    <string name="averageCadence">Cadencia media</string>
+    <string name="minCadence">Cadencia m铆nima</string>
+    <string name="spm">pasos/min</string>
+    <string name="maxStride">Zancada m谩xima</string>
+    <string name="maxCadence">Cadencia m谩xima</string>
+    <string name="prefs_activity_recognition">Configuraci贸n del reconocimiento de la actividad</string>
+    <string name="pref_activity_recognize_running">reconocer correr</string>
+    <string name="pref_activity_recognize_biking">reconocer ciclismo</string>
+    <string name="pref_activity_recognize_walking">reconocer caminar</string>
+    <string name="pref_activity_recognize_rowing">reconocer remo</string>
+    <string name="pref_activity_recognition_mode_none">ninguno</string>
+    <string name="pref_activity_recognition_mode_ask">preguntar</string>
+    <string name="pref_activity_recognition_mode_auto">autom谩tico</string>
+    <string name="menuitem_menu">Men煤</string>
+    <string name="fossil_hr_button_config_info">Algunos botones no se pueden configurar porque sus funciones est谩n codificadas en el firmware del reloj.
+\n
+\nAdvertencia: si se pulsa prolongadamente el bot贸n superior cuando se instala una esfera de reloj de la aplicaci贸n oficial de Fossil, tambi茅n se alternar谩 entre mostrar y ocultar los widgets.</string>
+    <string name="controlcenter_power_off">Apagar</string>
+    <string name="controlcenter_power_off_confirm_title">Apagar</string>
+    <string name="controlcenter_power_off_confirm_description">驴Est谩 seguro de que quiere apagar el dispositivo\?</string>
+    <string name="activity_prefs_discovery_pairing">Opciones de descubrimiento y emparejamiento</string>
+    <string name="discover_unsupported_devices_description">Al activar esta opci贸n se mostrar谩n todos los dispositivos bluetooth descubiertos al escanear. Una pulsaci贸n corta copiar谩 el nombre del dispositivo y la direcci贸n mac en el portapapeles. Una pulsaci贸n larga iniciar谩 el di谩logo \"A帽adir dispositivo de prueba\". Puede causar problemas de congelaci贸n de la aplicaci贸n.</string>
+    <string name="devicetype_bose_qc35">Bose QC35</string>
+    <string name="devicetype_vesc">VESC</string>
+    <string name="descentDistance">Distancia en descenso</string>
+    <string name="ascentDistance">Distancia en ascenso</string>
+    <string name="activity_type_hiking">Senderismo</string>
+    <string name="add_test_device">A帽adir dispositivo de prueba</string>
+    <string name="devicetype_sony_wf_sp800n">Sony WF-SP800N</string>
+    <string name="activity_type_climbing">Escalada</string>
+    <string name="sony_button_mode_ambient_sound_control">Control del sonido ambiente</string>
+    <string name="sony_button_mode_playback_control">Control de reproducci贸n</string>
+    <string name="pref_button_action_disabled">Desactivado</string>
+    <string name="pref_header_sony_equalizer_bands">Bandas</string>
+    <string name="sony_audio_upsampling">Muestreo de audio</string>
+    <string name="sony_speak_to_chat_sensitivity_high">Alta</string>
+    <string name="sony_button_mode_left">Modo bot贸n (izquierda)</string>
+    <string name="sony_button_mode_right">Modo bot贸n (derecha)</string>
+    <string name="sony_button_mode_off">Apagado</string>
+    <string name="sony_button_mode_volume_control">Control de volumen</string>
+    <string name="pref_media_pause">Pausar multimedia</string>
+    <string name="pref_media_play">Reproducir multimedia</string>
+    <string name="pref_media_playpause">Conmutar reproducci贸n</string>
+    <string name="pref_media_previous">Pista anterior</string>
+    <string name="pref_media_volumeup">Subir volumen</string>
+    <string name="pref_media_next">Pista siguiente</string>
+    <string name="pref_media_volumedown">Bajar volumen</string>
+    <string name="pref_media_forward">Saltar adelante</string>
+    <string name="pref_media_rewind">Saltar atr谩s</string>
+    <string name="pref_device_action_broadcast">Enviar transmisi贸n</string>
+    <string name="distance_format_meters">### m</string>
+    <string name="distance_format_miles">###.# mi</string>
+    <string name="discover_unsupported_devices">Detectar dispositivos no compatibles</string>
+    <string name="about_hash">Confirmar %s</string>
+    <string name="devicetype_sony_wh_1000xm4">Sony WH-1000XM4</string>
+    <string name="sony_speak_to_chat_focus_on_voice">Centrarse en la voz</string>
+    <string name="sony_speak_to_chat_timeout_off">Apagada</string>
+    <string name="sony_speak_to_chat_timeout_short">Corta (15 s)</string>
+    <string name="devicetype_domyos_t540">Domyos T540</string>
+    <string name="sony_speak_to_chat_sensitivity_low">Baja</string>
+    <string name="sony_speak_to_chat_timeout">Tiempo de espera</string>
+    <string name="distance_format_feet">### ft</string>
+    <string name="sony_speak_to_chat">Hablar para chatear</string>
+    <string name="sony_speak_to_chat_sensitivity">Sensibilidad de detecci贸n de voz</string>
+    <string name="sony_speak_to_chat_sensitivity_auto">Autom谩tica</string>
+    <string name="sony_speak_to_chat_timeout_standard">Est谩ndar (30 s)</string>
+    <string name="sony_connect_two_devices">Conectar con 2 dispositivos simult谩neamente</string>
+    <string name="sony_speak_to_chat_timeout_long">Larga (1 min)</string>
+    <string name="sony_pause_when_taken_off">Pausar cuando se quitan los auriculares</string>
+    <string name="distance_format_kilometers">###.# km</string>
+    <string name="discovery_bluetooth_le_scan">Escaneo Bluetooth LE:</string>
+    <string name="device_unsupported">NO SOPORTADO</string>
+    <string name="discovery_bluetooth_scan">Escaneo Bluetooth:</string>
+    <string name="devicetype_sonyswr12">Sony SWR12</string>
+    <string name="watchface_dialog_widget_width">Ancho del widget (en p铆xeles)</string>
+    <string name="unknown">Desconocido</string>
+    <string name="start">Iniciar</string>
+    <string name="devicetype_amazfit_pop_pro">Amazfit Pop Pro</string>
+    <string name="pref_header_sony_anc_optimizer">Optimizador de cancelaci贸n de ruido</string>
+    <string name="sony_anc_optimize_title">Optimizar</string>
+    <string name="sony_anc_optimize_confirmation_title">Optimizador de cancelaci贸n de ruido</string>
+    <string name="pref_anc_optimizer_state_pressure">Presi贸n atmosf茅rica</string>
+    <string name="sony_anc_optimizer_status_starting">Iniciando鈥�</string>
+    <string name="sony_anc_optimizer_status_not_running">No se est谩 ejecutando</string>
+    <string name="sony_anc_optimizer_status_wearing_condition">Midiendo el estado de uso鈥�</string>
+    <string name="sony_anc_optimizer_status_atmospheric_pressure">Medir la presi贸n atmosf茅rica鈥�</string>
+    <string name="sony_anc_optimizer_status_analyzing">Analizando鈥�</string>
+    <string name="sony_anc_optimizer_status_finished">Finalizando鈥�</string>
+    <string name="devicetype_amazfit_pop">Amazfit Pop</string>
+    <string name="sony_anc_optimize_confirmation_description">Use los auriculares como lo har铆a normalmente. Si las condiciones de uso o la presi贸n atmosf茅rica cambian, vuelva a ejecutar el optimizador.</string>
+    <string name="audio_codec">C贸dec de audio</string>
+    <string name="pref_header_sony_device_info">Informaci贸n del dispositivo</string>
+    <string name="sony_anc_optimize_description">Pulse para iniciar el optimizador de cancelaci贸n de ruido.</string>
+    <string name="menuitem_nothing">Nada</string>
+    <string name="pref_title_notifications_generic_settings">Ajustes de notificaciones de Android</string>
+    <string name="pref_device_action_fitness_app_control_start">Inicio del seguimiento de la aplicaci贸n de ejercicio f铆sico</string>
+    <string name="pref_device_action_fitness_app_control_stop">Parada de seguimiento de la aplicaci贸n de ejercicio f铆sico</string>
+    <string name="pref_title_opentracks_packagename">Nombre del paquete OpenTracks</string>
+    <string name="pref_summary_opentracks_packagename">Se usa para iniciar/detener la grabaci贸n del seguimiento GPS en la aplicaci贸n externa de ejercicio f铆sico.</string>
+    <string name="activity_db_management_autoexport_enabled_yes">La autoexportaci贸n est谩 activada.</string>
+    <string name="activity_db_management_autoexport_enabled_no">La autoexportaci贸n no est谩 activada.</string>
+    <string name="activity_db_management_autoexport_scheduled_yes">La autoexportaci贸n ha sido programada (originalmente) para %1$s</string>
+    <string name="activity_db_management_autoexport_scheduled_no">La autoexportaci贸n no ha sido programada.</string>
+    <string name="watchface_dialog_pre_setting_position">preconfigurando la posici贸n a %s</string>
+    <string name="autoExport_lastTime_label">脷ltima autoexportaci贸n: %1$s</string>
+    <string name="activity_db_management_autoexport_location">No se ha podido entender la ubicaci贸n. Probablemente un problema del nuevo sistema de permisos de Android. Lo m谩s probable es que la autoexportaci贸n no funcione ahora.</string>
+    <string name="watchface_setting_light_up_on_notification">Encender al recibir una notificaci贸n nueva</string>
+    <string name="menuitem_email">Correo electr贸nico</string>
+    <string name="application_name_banglejs_main">Bangle.js Gadgetbridge</string>
+    <string name="title_activity_controlcenter_banglejs_main">Bangle.js Gadgetbridge</string>
+    <string name="about_activity_title_banglejs_main">Acerca de Bangle.js Gadgetbridge</string>
+    <string name="about_description_banglejs_main">Aplicaci贸n complementaria de Android para Bangle.js, basada en el proyecto Gadgetbridge, con acceso adicional a Internet.
+\n
+\nDebido a las pol铆ticas de Google Play Store, no podemos incluir un enlace de donaci贸n en la aplicaci贸n, pero si te gusta esta aplicaci贸n, considera hacer una donaci贸n a trav茅s de la p谩gina de inicio de Gadgetbridge que aparece a continuaci贸n.</string>
+    <string name="gadgetbridge_running_banglejs_main">Bangle.js ejecut谩ndose</string>
+    <string name="application_name_banglejs_nopebble">Bangle.js Gadgetbridge</string>
+    <string name="title_activity_controlcenter_banglejs_nopebble">Bangle.js Gadgetbridge</string>
+    <string name="about_activity_title_banglejs_nopebble">Acerca de Bangle.js Gadgetbridge</string>
+    <string name="about_description_banglejs_nopebble">Aplicaci贸n complementaria de Android para Bangle.js construida sobre el proyecto Gadgetbridge, con acceso a Internet a帽adido.</string>
+    <string name="gadgetbridge_running_banglejs_nopebble">Bangle.js ejecut谩ndose</string>
+    <string name="application_name_main_nightly">Gadgetbridge (Nightly)</string>
+    <string name="title_activity_controlcenter_main_nightly">Gadgetbridge Nightly</string>
+    <string name="about_activity_title_main_nightly">Acerca de Gadgetbridge Nightly</string>
+    <string name="about_description_main_nightly">Reemplazo libre y sin copias de las aplicaciones de c贸digo cerrado para Android de los vendedores. Versiones nightly de Gadgetbridge. No se puede instalar si ya tiene instalada la aplicaci贸n de Gadgetbridge o la de Pebble, debido a un conflicto en el proveedor de Pebble.</string>
+    <string name="gadgetbridge_running_main_nightly">GB Nightly ejecut谩ndose</string>
+    <string name="application_name_main_nopebble">Gadgetbridge (Nightly, Sin proveedor de Pebble)</string>
+    <string name="title_activity_controlcenter_main_nopebble">Gadgetbridge Nightly sin Pebble</string>
+    <string name="about_activity_title_main_nopebble">Acerca de Gadgetbridge Nightly sin Pebble</string>
+    <string name="about_description_main_nopebble">Reemplazo libre y sin copias de las aplicaciones de c贸digo cerrado para Android de los vendedores. Versiones nightly de Gadgetbridge. Esta versi贸n tiene el proveedor de Pebble renombrado para evitar conflictos, por lo que algunas integraciones relacionadas con Pebble no funcionar谩n, pero se puede instalar junto a la instalaci贸n existente de Gadgetbridge.</string>
+    <string name="gadgetbridge_running_main_nopebble">GB Nightly sin Pebble ejecut谩ndose</string>
+    <string name="pref_world_clocks_title">Relojes mundiales</string>
+    <string name="pref_world_clocks_summary">Configurar los relojes para otras zonas horarias</string>
+    <string name="world_clock_delete_confirm_title">Eliminar \'%1$s\'</string>
+    <string name="world_clock_delete_confirm_description">驴Est谩 seguro de que quiere eliminar el reloj mundial\?</string>
+    <string name="pref_title_banglejs_text_bitmap">Texto como mapa de bits</string>
+    <string name="pref_summary_banglejs_text_bitmap">Si una palabra no puede ser reproducida con la fuente del reloj, render铆cela en un mapa de bits en Gadgetbridge y muestre el mapa de bits en el reloj</string>
+    <string name="pref_title_device_internet_access">Permitir acceso a Internet</string>
+    <string name="pref_summary_device_internet_access">Permitir que las aplicaciones de este dispositivo accedan a Internet</string>
+    <string name="pref_title_device_intents">Permitir Intents</string>
+    <string name="low_power_gps">GPS de bajo consumo</string>
+    <string name="gps_bds">GPS + BDS</string>
+    <string name="fw_upgrade_notice_amazfit_gts4">Est谩 a punto de instalar el firmware %s en su Amazfit GTS 4.
+\n
+\nSu reloj se reiniciar谩 despu茅s de instalar el archivo .zip.
+\n
+\n隆PROCEDA BAJO SU PROPIO RIESGO!</string>
+    <string name="gps_gnolass">GPS + GNOLASS</string>
+    <string name="pref_header_workout">Entrenamiento</string>
+    <string name="pref_title_screen_on_on_notifications">Pantalla encendida en las notificaciones</string>
+    <string name="pref_summary_screen_on_on_notifications">Encender la pantalla de la pulsera cuando llegue una notificaci贸n</string>
+    <string name="pref_explanation_authkey">Algunos dispositivos requieren una clave de emparejamiento especial para el primer inicio del dispositivo. Pulse aqu铆 para obtener m谩s detalles en la wiki.</string>
+    <string name="appmanager_watchface_activate">Activar</string>
+    <string name="appmanager_app_start">Iniciar</string>
+    <string name="appmanager_app_download">Descargar a cach茅</string>
+    <string name="appmanager_download_started">Descarga de la App iniciada</string>
+    <string name="appmanager_downloaded_to_cache">Descargado %s a la cach茅</string>
+    <string name="appmanager_download_app_error">Error descargando la app</string>
+    <string name="fw_upgrade_notice_amazfit_gts4_mini">Est谩 a punto de instalar el firmware %s en su Amazfit GTS 4 Mini.
+\n
+\nSu reloj se reiniciar谩 despu茅s de instalar el archivo .zip.
+\n
+\n隆PROCEDA BAJO SU PROPIO RIESGO!</string>
+    <string name="fw_upgrade_notice_amazfit_gtr3">Est谩 a punto de instalar el firmware %s en su Amazfit GTR 3.
+\n
+\nSu pulsera se reiniciar谩 despu茅s de instalar el archivo .zip.
+\n
+\n隆PROCEDA BAJO SU PROPIO RIESGO!</string>
+    <string name="fw_upgrade_notice_amazfit_band7">Est谩 a punto de instalar el firmware %s en su Amazfit Band 7.
+\n
+\nSu pulsera se reiniciar谩 despu茅s de instalar el archivo .zip.
+\n
+\n隆PROCEDA BAJO SU PROPIO RIESGO!</string>
+    <string name="open_fw_installer_pick_file">Seleccionar archivo</string>
+    <string name="open_fw_installer_info_text_title">Instalador de archivos</string>
+    <string name="pref_header_connection">Conexi贸n</string>
+    <string name="pref_header_sound_vibration">Sonido y vibraci贸n</string>
+    <string name="pref_header_offline_voice">Voz sin conexi贸n</string>
+    <string name="pref_title_banglejs_txt_bitmap_size">Tama帽o de los mapas de bits de texto</string>
+    <string name="pref_explanation_authkey_new_protocol">Si aparece el mensaje \"Actualizar la aplicaci贸n a la 煤ltima versi贸n\" en la pulsera, aseg煤rese de comprobar el \"Nuevo protocolo de autentificaci贸n\" m谩s arriba. Pulse aqu铆 para obtener m谩s detalles en la wiki.</string>
+    <string name="pref_gps_header">GPS</string>
+    <string name="pref_gps_mode_preset">Modo GPS</string>
+    <string name="pref_gps_band">Banda GPS</string>
+    <string name="pref_gps_combination">Combinaci贸n GPS</string>
+    <string name="pref_gps_satellite_search">B煤squeda de sat茅lites</string>
+    <string name="pref_crown_vibration">Vibraci贸n de la corona</string>
+    <string name="pref_alert_tone">Tono de alerta</string>
+    <string name="pref_text_to_speech">Texto a voz</string>
+    <string name="offline_voice_respond_turn_wrist">Responder al girar la mu帽eca</string>
+    <string name="offline_voice_respond_screen_on">Responder cuando la pantalla est谩 encendida</string>
+    <string name="offline_voice_response_during_screen_lighting">Responder durante la iluminaci贸n de la pantalla</string>
+    <string name="pref_agps_header">AGPS</string>
+    <string name="pref_agps_expiry_reminder_enabled">Recordatorio de expiraci贸n de AGPS</string>
+    <string name="pref_header_health">Salud</string>
+    <string name="pref_workout_start_on_phone_title">Seguimiento de apps de fitness</string>
+    <string name="power_saving">Ahorro de energ铆a</string>
+    <string name="custom">Personalizado</string>
+    <string name="accuracy">Preciso</string>
+    <string name="balanced">Equilibrado</string>
+    <string name="gps">GPS</string>
+    <string name="gps_galileo">GPS + GALILEO</string>
+    <string name="all_satellites">Todos los sat茅lites</string>
+    <string name="accuracy_first">La precisi贸n primero</string>
+    <string name="dual_band">Banda dual</string>
+    <string name="single_band">Banda 煤nica</string>
+    <string name="speed_first">M谩s veloz primero</string>
+    <string name="fw_upgrade_notice_miband7">Est谩 a punto de instalar el firmware %s en su Xiaomi Smart Band 7.
+\n
+\nSu Band se reiniciar谩 despu茅s de instalar el archivo .zip.
+\n
+\n隆PROCEDA BAJO SU PROPIO RIESGO!</string>
+    <string name="open_fw_installer_select_file">Seleccione el archivo que desea cargar en el dispositivo: %s</string>
+    <string name="pref_header_calendar">Calendario</string>
+    <string name="open_fw_installer_warning_title">Aviso</string>
+    <string name="fw_upgrade_notice_amazfit_gts3">Est谩 a punto de instalar el firmware %s en su Amazfit GTS 3.
+\n
+\nSu reloj se reiniciar谩 despu茅s de instalar el archivo .zip.
+\n
+\n隆PROCEDA BAJO SU PROPIO RIESGO!</string>
+    <string name="open_fw_installer_getting_files_text">Dado que no podemos distribuir los archivos de firmware, tendr谩 que conseguirlos usted mismo. Esto significa que tendr谩 que buscar los archivos apk, en l铆nea, en foros, en Amazfitwatchfaces (para dispositivos Miband/Amazfit) y as铆 sucesivamente.</string>
+    <string name="pref_title_notification_prefer_long_text">Preferir un texto de notificaci贸n largo</string>
+    <string name="pref_summary_notification_prefer_long_text">Si est谩 disponible, env铆a el texto de notificaci贸n largo al dispositivo</string>
+    <string name="controlcenter_get_heartrate_measurement">Medici贸n de la frecuencia cardiaca</string>
+    <string name="open_fw_installer_warning_text">Esta funci贸n puede bloquear el dispositivo. Dicho lo cual, esto nunca ha sucedido a ninguno de los desarrolladores a trav茅s del flasheo, pero recuerde que usted est谩 haciendo esto bajo su propio riesgo.</string>
+    <string name="open_fw_installer_getting_files_title">Obtener el archivo de firmware/app</string>
+    <string name="about_description_banglejs_nightly">Aplicaci贸n complementaria de Android para Bangle.js construida sobre el proyecto Gadgetbridge, con acceso a Internet a帽adido.
+\n
+\nDebido a las pol铆ticas de Google Play Store, no se nos permite un enlace de donaci贸n en la propia aplicaci贸n, pero si le gusta esta aplicaci贸n por favor considere donar a trav茅s de la p谩gina de inicio de Gadgetbridge a continuaci贸n.</string>
+    <string name="application_name_banglejs_nightly">Bangle.js Gadgetbridge (Nightly)</string>
+    <string name="title_activity_controlcenter_banglejs_nightly">Bangle.js Gadgetbridge (Nightly)</string>
+    <string name="about_activity_title_banglejs_nightly">Acerca de Bangle.js Gadgetbridge (Nightly)</string>
+    <string name="gadgetbridge_running_banglejs_nightly">Bangle.js Nightly ejecut谩ndose</string>
+    <string name="fw_upgrade_notice_amazfit_trex2">Est谩 a punto de instalar el firmware %s en su Amazfit T-Rex 2.
+\n
+\nSu pulsera se reiniciar谩 despu茅s de instalar el archivo .zip.
+\n
+\n隆PROCEDA BAJO SU PROPIO RIESGO!</string>
+    <string name="open_fw_installer_info_text">El Instalador de Firmware/Watchface/App/Archivos le permite cargar/instalar archivos compatibles (firmware, watchfaces, aplicaciones, GPS, recursos, fuentes...) en el dispositivo. M谩s informaci贸n en la wiki: https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Firmware-Update</string>
+    <string name="open_fw_installer_connect_minimum_one_device">Conecte AL MENOS UN dispositivo al que desee enviar el archivo.</string>
+    <string name="pref_summary_notification_delay_calls">Retraso antes de enviar notificaciones de llamadas entrantes al dispositivo, en segundos.</string>
+    <string name="pref_summary_notification_media_ignores_application_list">Procesar las notificaciones multimedia antes que la lista de aplicaciones. Si esta preferencia no est谩 marcada, las aplicaciones multimedia deben estar permitidas en la lista de aplicaciones para que los controles multimedia funcionen en el dispositivo.</string>
+    <string name="pref_summary_banglejs_txt_bitmap_size">Tama帽o a utilizar para la representaci贸n de texto en mapa de bits</string>
+    <string name="pref_title_banglejs_webview_url">URL del cargador de Apps</string>
+    <string name="pref_summary_banglejs_webview_url">Si quiere un cargador de aplicaciones personalizado ponga aqu铆 su URL https://.../android.html. De lo contrario, deje en blanco https://banglejs.com/apps</string>
+    <string name="pref_blacklist_calendars_summary">Los calendarios de la lista negra no se sincronizar谩n con el dispositivo</string>
+    <string name="pref_summary_canned_replies">Responder desde el reloj con mensajes predefinidos</string>
+    <string name="pref_header_intent_api">API de funci贸n</string>
+    <string name="pref_header_authentication">Autentificaci贸n</string>
+    <string name="pref_summary_overwrite_settings_on_connection">Al conectarte a la pulsera, sobrescribe todos los ajustes de la misma.</string>
+    <string name="pref_summary_device_intents">Permitir que las aplicaciones de reloj Bangle.js env铆en Intent de Android, y permitir que otras aplicaciones en Android (como Tasker) env铆en datos a Bangle.js con Intent de com.banglejs.uart.tx. Necesita permiso para mostrarse sobre otras apps y trabajar en segundo plano.</string>
+    <string name="pref_vibrate_for_alert">Alerta por vibraci贸n</string>
+    <string name="pref_agps_expiry_reminder_time">Recordatorio del tiempo de expiraci贸n AGPS</string>
+    <string name="pref_agps_update_time">Tiempo de actualizaci贸n de AGPS</string>
+    <string name="pref_agps_expire_time">Tiempo de expiraci贸n de AGPS</string>
+    <string name="prefs_activity_monitoring_description">Aumenta autom谩ticamente la detecci贸n de la frecuencia cardiaca cuando la pulsera detecta ejercicio f铆sico, para aumentar su precisi贸n en la captura.</string>
+    <string name="pref_title_notification_media_ignores_application_list">Las notificaciones multimedia ignoran la lista de aplicaciones</string>
+    <string name="appmanager_app_share">Compartir</string>
+    <string name="appmanager_item_outdated">(obsoleto)</string>
+    <string name="open_fw_installer_connect_maximum_one_device">Conecte S脫LO UN dispositivo al que desee enviar el archivo.</string>
+    <string name="open_fw_installer_ensure_device_connected">Aseg煤rese de que el dispositivo %s est谩 conectado</string>
+    <string name="debugactivity_confirm_remove_device_preferences_title">驴Quitar las preferencias del dispositivo\?</string>
+    <string name="debugactivity_confirm_remove_device_preferences">Esto restablecer谩 las preferencias del dispositivo para todos los dispositivos conectados. 驴Est谩 seguro\?</string>
+    <string name="fw_upgrade_notice_amazfit_gtr4">Est谩 a punto de instalar el firmware %s en su Amazfit GTR 4.
+\n
+\nSu pulsera se reiniciar谩 despu茅s de instalar el archivo .zip.
+\n
+\n隆PROCEDA BAJO SU PROPIO RIESGO!</string>
+    <string name="pref_title_notification_delay_calls">Retardo de notificaci贸n de llamada</string>
+    <string name="pref_title_notifications_ignore_low_priority">Ignorar las notificaciones de baja prioridad</string>
+    <string name="pref_summary_notifications_ignore_low_priority">No enviar notificaciones de prioridad baja y m铆nima al reloj</string>
+    <string name="pref_title_overwrite_settings_on_connection">Sobrescribir los ajustes al conectarse</string>
+    <string name="czech">Checo</string>
+    <string name="menuitem_personal_activity_intelligence">Actividad personal inteligente</string>
+    <string name="menuitem_female_health">Salud femenina</string>
+    <string name="permission_notification_listener">%1$s necesita acceder a las notificaciones para mostrarlas en el reloj cuando la pantalla del tel茅fono est谩 apagada.
+\n
+\nPulse \'%2$s\' y despu茅s \'%1$s\' active \'Permitir acceso a notificaciones\' y a continuaci贸n, pulse \'Atr谩s\' para volver a %1$s.</string>
+    <string name="permission_location">%1$s necesita acceder a su ubicaci贸n en segundo plano para poder seguir conectado a su reloj aunque la pantalla est茅 apagada.\n\nPor favor, seleccione \"%2$s\" en la siguiente pantalla y, a continuaci贸n, pulse \"Atr谩s\" para volver a %1$s.</string>
+    <string name="hrZoneNa">N/D</string>
+    <string name="elevationGain">Aumento de la altitud</string>
+    <string name="watchface_dialog_widget_cat_custom_widget">Personalizar widget</string>
+    <string name="watchface_dialog_title_widget">Configuraci贸n del widget</string>
+    <string name="pref_voice_detect_duration_10">10 segundos</string>
+    <string name="prefs_double_tap_edge">Doble pulsaci贸n al borde</string>
+    <string name="sony_ambient_sound_control_button_modes">Modos del bot贸n de control de sonido ambiente</string>
+    <string name="sony_ambient_sound_control_button_mode_nc_as_off">Cancelaci贸n de ruido, sonido ambiente, apagado</string>
+    <string name="sony_ambient_sound_control_button_mode_as_off">Sonido ambiente, apagado</string>
+    <string name="info_no_devices_connected">Ning煤n dispositivo conectado</string>
+    <string name="controlcenter_set_folder_title">Establecer o crear una nueva carpeta</string>
+    <string name="autoconnect_from_device_summary">Establecer una conexi贸n cuando 茅sta es iniciada por un dispositivo, como los auriculares</string>
+    <string name="activity_prefs_allow_bluetooth_intent_api">API Intent de Bluetooth</string>
+    <string name="activity_prefs_summary_allow_bluetooth_intent_api">Permite el control de la conexi贸n Bluetooth a trav茅s de la API Intent</string>
+    <string name="pref_workout_detection_title">Detecci贸n de entrenamiento</string>
+    <string name="pref_workout_detection_time_summary">La cantidad de minutos que el entrenamiento debe estar en curso antes de detectarlo</string>
+    <string name="pref_header_display">Mostrar</string>
+    <string name="pref_header_time">Tiempo</string>
+    <string name="pref_workout_send_gps_title">Enviar GPS durante el entrenamiento</string>
+    <string name="pref_workout_detection_summary">Detecta el entrenamiento autom谩ticamente</string>
+    <string name="pref_workout_detection_sensitivity">Sensibilidad</string>
+    <string name="pref_workout_detection_ask_first">Preg煤ntame primero</string>
+    <string name="pref_workout_detection_ask_first_summary">Requerir confirmaci贸n en el reloj al detectar este entrenamiento</string>
+    <string name="pref_workout_detection_time">Minutos de actividad antes de la detecci贸n</string>
+    <string name="pref_sleep_mode_title">Modo reposo</string>
+    <string name="pref_sleep_mode_sleep_screen_title">Pantalla en reposo</string>
+    <string name="pref_sleep_mode_sleep_screen_summary">Muestra la pantalla de reposo al activar la pantalla durante el modo de reposo, para reducir las distracciones</string>
+    <string name="pref_sleep_mode_smart_enable_title">Activaci贸n inteligente</string>
+    <string name="pref_sleep_mode_smart_enable_summary">Activa autom谩ticamente el modo de reposo cuando lleves puesta la pulsera mientras duermes</string>
+    <string name="pref_cache_weather">Informaci贸n meteorol贸gica en cach茅</string>
+    <string name="buttons_on_right">Botones a la derecha</string>
+    <string name="discovery_scanning_intensity">Intensidad de escaneado</string>
+    <string name="pref_cache_weather_summary">La informaci贸n meteorol贸gica se almacenar谩 en cach茅 cuando se reinicie la aplicaci贸n.</string>
+    <string name="pref_screen_notification_profile_todo_list">Lista de tareas</string>
+    <string name="prefs_screen_timeout">Tiempo de espera de la pantalla</string>
+    <string name="watchface_dialog_widget_cat_generic">Gen茅rico</string>
+    <string name="vibration_profile_default">Por defecto</string>
+    <string name="pref_screen_notification_profile_schedule">Programar</string>
+    <string name="prefs_weardirection">Seguimiento del uso</string>
+    <string name="heartrate_bpm_130">130 ppm</string>
+    <string name="heartrate_bpm_110">110 ppm</string>
+    <string name="heartrate_bpm_120">120 ppm</string>
+    <string name="heartrate_bpm_125">125 ppm</string>
+    <string name="heartrate_bpm_140">140 ppm</string>
+    <string name="world_clock_no_free_slots_title">No hay slots libres</string>
+    <string name="heartrate_bpm_45">45 ppm</string>
+    <string name="heartrate_bpm_100">100 ppm</string>
+    <string name="spo2_perc_90">90%</string>
+    <string name="heartrate_bpm_105">105 ppm</string>
+    <string name="heartrate_bpm_135">135 ppm</string>
+    <string name="heartrate_bpm_145">145 ppm</string>
+    <string name="spo2_perc_85">85%</string>
+    <string name="heartrate_bpm_150">150 ppm</string>
+    <string name="spo2_off">Apagado</string>
+    <string name="world_clock_no_free_slots_description">El dispositivo no tiene slots libres para relojes mundiales (total slots: %1$s)</string>
+    <string name="function_enabled">Activado</string>
+    <string name="world_clock_timezone">Huso horario</string>
+    <string name="world_clock_code">C贸digo</string>
+    <string name="title_activity_world_clock_details">Detalles del reloj mundial</string>
+    <string name="prefs_heartrate_alert_experimental_title">Alerta de frecuencia card铆aca (experimental)</string>
+    <string name="prefs_heartrate_alert_experimental_description">Hacer vibrar la pulsera cuando la frecuencia cardiaca supera un umbral, sin ninguna actividad f铆sica evidente en los 煤ltimos 10 minutos. Esta funci贸n es experimental y no se ha probado exhaustivamente.</string>
+    <string name="prefs_heartrate_alert_high_threshold">Umbral de alerta de frecuencia cardiaca alta</string>
+    <string name="prefs_heartrate_alert_threshold">Umbral de alerta de frecuencia cardiaca</string>
+    <string name="prefs_stress_monitoring_description">Controla el nivel de estr茅s mientras descansa</string>
+    <string name="prefs_relaxation_reminder_description">Vibra la pulsera para avisarte si el valor de tensi贸n es superior a 80</string>
+    <string name="seconds_6">6 segundos</string>
+    <string name="prefs_spo2_monitoring_title">Control del ox铆geno en sangre</string>
+    <string name="seconds_25">25 segundos</string>
+    <string name="seconds_8">8 segundos</string>
+    <string name="seconds_15">15 segundos</string>
+    <string name="seconds_12">12 segundos</string>
+    <string name="seconds_13">13 segundos</string>
+    <string name="seconds_14">14 segundos</string>
+    <string name="abstract_chart_fragment_kind_rem_sleep">Sue帽o REM</string>
+    <string name="pref_sleep_breathing_quality_monitoring">Control de la calidad de la respiraci贸n durante el sue帽o</string>
+    <string name="pref_huami_truncate_fetch_operation_timestamps_title">Truncar las marcas de tiempo de las operaciones de recuperaci贸n</string>
+    <string name="pref_enable_unsupported_settings_title">Activar ajustes no compatibles</string>
+    <string name="prefs_password">Contrase帽a</string>
+    <string name="pref_title_third_party_app_device_settings">Permitir que aplicaciones de terceros cambien la configuraci贸n</string>
+    <string name="pref_enable_unsupported_settings_summary">Esto permitir谩 el acceso a todos los ajustes disponibles, incluso si no son compatibles con el dispositivo. Esto puede causar inestabilidad y fallos en el dispositivo.</string>
+    <string name="prefs_always_on_display">Siempre en pantalla</string>
+    <string name="pref_summary_third_party_app_device_settings">Permitir que otras aplicaciones de terceros instaladas establezcan la configuraci贸n del dispositivo a trav茅s de Intent.</string>
+    <string name="mi2_prefs_heart_rate_monitoring">Control de la frecuencia cardiaca</string>
+    <string name="mi2_prefs_heart_rate_monitoring_summary">Configurar la monitorizaci贸n de la frecuencia cardiaca</string>
+    <string name="prefs_always_on_display_follow_watchface">El estilo se ajusta a la esfera del reloj</string>
+    <string name="prefs_always_on_display_style">Estilo</string>
+    <string name="prefs_password_enabled">Contrase帽a activada</string>
+    <string name="prefs_password_4_digits_1_to_4_summary">La contrase帽a debe tener 4 d铆gitos, utilizando los n煤meros del 1 al 4</string>
+    <string name="prefs_password_6_digits_0_to_9_summary">La contrase帽a debe tener 6 d铆gitos, utilizando s贸lo n煤meros</string>
+    <string name="mi2_prefs_heart_rate_monitoring_alerts_summary">Configurar la monitorizaci贸n de la frecuencia cardiaca y los umbrales de alerta</string>
+    <string name="prefs_control_center_summary">Elija los elementos en el desplegable del centro de control</string>
+    <string name="prefs_activate_display_on_lift_sensitivity">Sensibilidad</string>
+    <string name="bengali">Bengal铆</string>
+    <string name="extended_ascii">ASCII extendido</string>
+    <string name="lithuanian">Lituano</string>
+    <string name="icelandic">Island茅s</string>
+    <string name="portuguese_br">Portugu茅s (Brasil)</string>
+    <string name="activity_prefs_target_weight_kg">Objetivo de peso en kg</string>
+    <string name="activity_prefs_goal_fat_burn_time_minutes">Objetivo diario: tiempo de actividad en minutos</string>
+    <string name="mi2_dnd_always">Siempre</string>
+    <string name="sensitive">Sensible</string>
+    <string name="activity_type_hiit">Entrenamiento en intervalos de alta intensidad</string>
+    <string name="activity_type_outdoor_cycling">Ciclismo al aire libre</string>
+    <string name="activity_type_dance">Bailar</string>
+    <string name="activity_type_indoor_fitness">Fitness en interior</string>
+    <string name="activity_type_pilates">Pilates</string>
+    <string name="activity_type_rowing">Remo</string>
+    <string name="activity_type_gymnastics">Gimnasia</string>
+    <string name="activity_type_stretching">Estiramientos</string>
+    <string name="activity_type_table_tennis">Tenis de mesa</string>
+    <string name="activity_type_street_dance">Street Dance</string>
+    <string name="activity_type_bowling">Bolera</string>
+    <string name="activity_type_boxing">Boxeo</string>
+    <string name="activity_type_indoor_ice_skating">Patinaje sobre hielo en interior</string>
+    <string name="devicetype_amazfit_gts3">Amazfit GTS 3</string>
+    <string name="devicetype_amazfit_trex_2">Amazfit T-Rex 2</string>
+    <string name="activity_type_kickboxing">kick boxing</string>
+    <string name="activity_type_zumba">Zumba</string>
+    <string name="devicetype_amazfit_gts4_mini">Amazfit GTS 4 Mini</string>
+    <string name="devicetype_miband7">Xiaomi Smart Band 7</string>
+    <string name="devicetype_amazfit_gts4">Amazfit GTS 4</string>
+    <string name="devicetype_amazfit_gtr4">Amazfit GTR 4</string>
+    <string name="devicetype_amazfit_band7">Amazfit Band 7</string>
+    <string name="devicetype_sony_linkbuds_s">Sony LinkBuds S</string>
+    <string name="notification_gps_title">GPS de Gadgetbridge</string>
+    <string name="menuitem_countdown">Cuenta atr谩s</string>
+    <string name="menuitem_workout_history">Historial de entrenamientos</string>
+    <string name="menuitem_workout_status">Estado del entrenamiento</string>
+    <string name="menuitem_bluetooth">Bluetooth</string>
+    <string name="menuitem_sun_moon">Sol y Luna</string>
+    <string name="menuitem_brightness">Brillo</string>
+    <string name="menuitem_unknown_app">Desconocido (%s)</string>
+    <string name="share_log_not_enabled_message">Primero debe activar el registro en Configuraci贸n - Escribir archivos de registro</string>
+    <string name="quick_alarm_description">Alerta desde el dispositivo</string>
+    <string name="pref_title_lower_button_short_press_action">Acci贸n de pulsaci贸n corta del bot贸n inferior</string>
+    <string name="menuitem_todo">Tareas pendientes</string>
+    <string name="menuitem_voice_memos">Notas de voz</string>
+    <string name="menuitem_one_tap_measuring">Medici贸n con una sola pulsaci贸n</string>
+    <string name="menuitem_wifi">Wi-Fi</string>
+    <string name="menuitem_lockscreen">Bloqueo de pantalla</string>
+    <string name="menuitem_unsupported">[NO SOPORTADO] %s</string>
+    <string name="hrZoneExtreme">Extremo</string>
+    <string name="hrZoneAnaerobic">Anaer贸bico</string>
+    <string name="hybridhr_widget_bg_thin_circle">C铆rculo estrecho</string>
+    <string name="error_setting_parent_folder">Error al establecer la carpeta principal: %s</string>
+    <string name="error_deleting_device">Error borrando el dispositivo: %s</string>
+    <string name="controlcenter_unset_folder">Carpeta sin configurar</string>
+    <string name="step_streaks_achievements_sharing_title">Progresos realizados</string>
+    <string name="steps_streaks_total_steps_hint_totals">N煤mero total de pasos registrados</string>
+    <string name="steps_streaks_total_steps_hint">N煤mero total de pasos en toda la serie</string>
+    <string name="menuitem_calendar">Calendario</string>
+    <string name="watchface_dialog_widget_cat_position">Posici贸n</string>
+    <string name="hybridhr_widget_bg_dashed_circle">C铆rculo discontinuo</string>
+    <string name="hybridhr_widget_bg_double_circle">C铆rculo doble</string>
+    <string name="watchface_dialog_widget_timezone_duration">Duraci贸n de la visibilidad del reloj (en segundos)</string>
+    <string name="steps_streaks_total_steps_average_hint">Media total de pasos al d铆a %d</string>
+    <string name="currentWorkoutLoad">Carga de entrenamiento</string>
+    <string name="heartrate_bpm_40">40 ppm</string>
+    <string name="permission_notification_policy_access">%1$s necesita acceder a los ajustes de No molestar para poder utilizarlos en su reloj cuando la pantalla de su tel茅fono est茅 apagada.
+\n
+\nPulse \'%2$s\' y a continuaci贸n \'%1$s\' active \'Permitir no molestar\' y a continuaci贸n, pulse \'Atr谩s\' para volver a %1$s.</string>
+    <string name="pref_voice_detect_duration_15">15 segundos</string>
+    <string name="controlcenter_connected_fraction">Conectado: %d/%d</string>
+    <string name="heartrate_bpm_50">50 ppm</string>
+    <string name="heartrate_bpm_115">115 ppm</string>
+    <string name="spo2_perc_80">80%</string>
+    <string name="seconds_9">9 segundos</string>
+    <string name="mi5_prefs_workout_activity_types">Tipos de actividades de entrenamiento</string>
+    <string name="mi5_prefs_workout_activity_types_summary">Elija los tipos de actividad que se mostrar谩n en la pantalla de entrenamientos</string>
+    <string name="pref_header_heartrate_sleep">Dormir</string>
+    <string name="pref_header_heartrate_allday">Supervisar todo el d铆a</string>
+    <string name="devicetype_sony_wf_1000xm3">Sony WF-1000XM3</string>
+    <string name="pref_workout_send_gps_summary">Env铆a la ubicaci贸n GPS actual a la pulsera durante un entrenamiento</string>
+    <string name="prefs_active_noise_cancelling_level_high">Alta</string>
+    <string name="pref_voice_detect_duration_5">5 segundos</string>
+    <string name="note">Nota</string>
+    <string name="quick_alarm">Alerta r谩pida</string>
+    <string name="watchface_setting_button_toggle_widgets">Alternar widgets</string>
+    <string name="steps_streaks_since_date">Desde %s</string>
+    <string name="pref_title_touch_volume">Volumen</string>
+    <string name="pref_balance">Equilibrado</string>
+    <string name="notification_gps_text">Enviando localizaci贸n GPS a %1$d dispositivos</string>
+    <string name="pref_device_action_phone_gps_location_listener_stop">Ubicaci贸n GPS receptor parado</string>
+    <string name="devicetype_binary_sensor">Sensor binario</string>
+    <string name="menuitem_offline_voice">Voz sin conexi贸n</string>
+    <string name="menuitem_membership_cards">Tarjetas de socio</string>
+    <string name="menuitem_phone">Tel茅fono</string>
+    <string name="menuitem_theater_mode">Modo sala</string>
+    <string name="menuitem_volume">Volumen</string>
+    <string name="menuitem_screen_always_lit">Pantalla siempre encendida</string>
+    <string name="pref_screen_auto_brightness_summary">Ajusta el brillo de la pantalla en funci贸n de la luz ambiental</string>
+    <string name="watchface_setting_button_move_hands">Mover las manos</string>
+    <string name="prefs_ambient_volume_right">Volumen sonido ambiente derecho</string>
+    <string name="prefs_voice_detect">Detecci贸n de voz</string>
+    <string name="pref_screen_brightness">Brillo de la pantalla</string>
+    <string name="pref_workout_start_on_phone_summary">Iniciar/detener el seguimiento de la aplicaci贸n de fitness en el tel茅fono cuando se inicia un entrenamiento GPS en la pulsera</string>
+    <string name="prefs_relaxation_reminder_title">Recordatorio de relajaci贸n</string>
+    <string name="seconds_11">11 segundos</string>
+    <string name="ukranian">Ucraniano</string>
+    <string name="activity_type_core_training">Entrenamiento b谩sico</string>
+    <string name="pref_workout_detection_categories_title">Categor铆as de entrenamiento</string>
+    <string name="pref_workout_detection_categories_summary">Categor铆as de entrenamientos para detectar autom谩ticamente</string>
+    <string name="pref_workout_detection_alert_title">Alerta</string>
+    <string name="pref_workout_detection_alert_summary">Notificar cuando se detecta un entrenamiento</string>
+    <string name="prefs_ambient_volume_left">Volumen sonido ambiente izquierdo</string>
+    <string name="prefs_noise_control">Reducci贸n del ruido</string>
+    <string name="prefs_in_ear_detection_summary">Reproducir llamadas a trav茅s de los auriculares cuando est谩n en los o铆dos</string>
+    <string name="prefs_seamless_connection_switch_summary">Cambia autom谩ticamente los auriculares entre dispositivos emparejados</string>
+    <string name="pref_title_touch_voice_assistant">Asistente de voz</string>
+    <string name="prefs_active_noise_cancelling_level_low">Baja</string>
+    <string name="prefs_switch_control_left">Botones de control a la izquierda</string>
+    <string name="pref_title_touch_spotify">Spotify</string>
+    <string name="prefs_noise_control_with_one_earbud_summary">Permite controlar el ruido cuando se utiliza un solo auricular</string>
+    <string name="pref_switch_controls_anc_off">Cancelaci贸n de ruido 鈫愨啋 Apagado</string>
+    <string name="pref_switch_controls_ambient_off">Ambiental 鈫愨啋 Apagado</string>
+    <string name="prefs_voice_detect_duration">Terminar despu茅s de silenciar por:</string>
+    <string name="pref_header_heartrate_alerts">Alertas de frecuencia card铆aca</string>
+    <string name="pref_screen_auto_brightness_title">Brillo autom谩tico</string>
+    <string name="controlcenter_folder_name">Nombre de la carpeta:</string>
+    <string name="auto_reconnect_ble_title">Conectar autom谩ticamente con el dispositivo</string>
+    <string name="connection_over_bt_classic">Conexi贸n a trav茅s de Bluetooth cl谩sico</string>
+    <string name="autoconnect_from_device_title">Conectar en conexi贸n desde el dispositivo</string>
+    <string name="step_streak_days_hint">N煤mero de d铆as consecutivos en los que se ha alcanzado el objetivo de pasos</string>
+    <string name="step_streaks_achievements_sharing_message">隆Mis logros diarios!</string>
+    <string name="intent_api_allow_activity_sync_title">Permitir la sincronizaci贸n de actividades</string>
+    <string name="prefs_hourly_chime_summary">El reloj emitir谩 un pitido cada hora</string>
+    <string name="devicetype_super_cars">Shell Racing</string>
+    <string name="watchface_dialog_widget_background">En segundo plano</string>
+    <string name="controlcenter_set_parent_folder">Establecer carpeta principal</string>
+    <string name="controlcenter_set_preferences">Establecer preferencias</string>
+    <string name="steps_streaks_total_steps">Total
+\npasos</string>
+    <string name="watchface_setting_title_custom_events">Personalizar eventos</string>
+    <string name="watchface_setting_button_toggle_backlight">Encender la retroiluminaci贸n</string>
+    <string name="pref_screen_notification_profile_event_reminder">Recordatorio del evento</string>
+    <string name="connection_over_ble">Conexi贸n a trav茅s de BLE</string>
+    <string name="prefs_always_on_display_summary">Mantener siempre encendida la pantalla de la pulsera</string>
+    <string name="info_connected_count">%d dispositivos conectados</string>
+    <string name="devicetype_amazfit_gtr3">Amazfit GTR 3</string>
+    <string name="pref_write_logfiles_not_available">La iniciaci贸n del registro de archivos ha fallado, la escritura de archivos de registro no est谩 disponible actualmente. Reinicie la aplicaci贸n para intentar inicializar de nuevo los archivos de registro.</string>
+    <string name="persian">Persa</string>
+    <string name="georgian">Georgiano</string>
+    <string name="dismiss">Descartar</string>
+    <string name="devicetype_amazfit_gtr_lite">Amazfit GTR Lite</string>
+    <string name="devicetype_sony_wh_1000xm2">Sony WH-1000XM2</string>
+    <string name="pref_title_upper_button_long_press_action">Acci贸n de pulsaci贸n larga del bot贸n superior</string>
+    <string name="pref_title_touch_ambient">Sonido ambiente</string>
+    <string name="pref_title_touch_quick_ambient">Sonido ambiente claro</string>
+    <string name="pref_title_touch_spotify_official_app">Spotify (s贸lo aplicaci贸n oficial)</string>
+    <string name="pref_title_touch_spotify_galaxy_app">Spotify (s贸lo aplicaci贸n Galaxy Wearable)</string>
+    <string name="pref_ambient_sound_tone_summary">De suave a n铆tido</string>
+    <string name="controlcenter_toggle_details">Cambiar detalles</string>
+    <string name="controlcenter_add_new_folder">A帽adir nueva carpeta</string>
+    <string name="auto_reconnect_ble_summary">Intenta conectarse al dispositivo de forma proactiva peri贸dicamente</string>
+    <string name="devicetype_galaxybuds_2">Galaxy Buds2</string>
+    <string name="intent_api_allow_activity_sync_summary">Permite la activaci贸n de la sincronizaci贸n de actividades a trav茅s de la API Intent</string>
+    <string name="pref_workout_detection_enabled">Detecci贸n activada</string>
+    <string name="pref_workout_detection_enabled_summary">Activar la detecci贸n autom谩tica de este entrenamiento</string>
+    <string name="pref_screen_vibration_patterns_summary">Configura los patrones de vibraci贸n para las distintas notificaciones</string>
+    <string name="world_clock_label">Etiqueta</string>
+    <string name="seconds_7">7 segundos</string>
+    <string name="kind_app">Aplicaci贸n</string>
+    <string name="hrZoneFatBurn">Quemagrasas</string>
+    <string name="hrZoneWarmUp">Calentamiento</string>
+    <string name="hrZoneAerobic">Aer贸bico</string>
+    <string name="TrainingEffect">Eficacia del entrenamiento</string>
+    <string name="prefs_seamless_connection_switch_title">Conmutador de conexi贸n continua</string>
+    <string name="prefs_customize_ambient_sound_summary">Personalizar el sonido ambiente</string>
+    <string name="prefs_ambient_sound_during_call_title">Sonido ambiente durante la llamada</string>
+    <string name="prefs_ambient_sound_during_call_summary">Escuchar claramente su propia voz durante las llamadas</string>
+    <string name="prefs_ambient_settings_title">Opciones de sonido ambiente</string>
+    <string name="prefs_active_noise_cancelling_level">Nivel de cancelaci贸n activa del ruido</string>
+    <string name="pref_title_touch_anc">Cancelaci贸n activa del ruido</string>
+    <string name="pref_switch_noise_control">Interruptor de control de ruido</string>
+    <string name="prefs_noise_control_with_one_earbud">Control de ruido con un solo auricular</string>
+    <string name="pref_ambient_sound_tone">Tono de sonido ambiente</string>
+    <string name="pref_switch_controls_anc_ambient">Cancelaci贸n de ruido 鈫愨啋 Ambiental</string>
+    <string name="prefs_double_tap_edge_summary">Detecta la doble pulsaci贸n aunque no se pulse en el panel t谩ctil</string>
+    <string name="pref_header_spo2">Ox铆geno en sangre</string>
+    <string name="pref_header_stress">Tensi贸n</string>
+    <string name="sony_quick_access_double_tap">Acceso r谩pido (doble pulsaci贸n)</string>
+    <string name="step_streak_longest">M谩s largo</string>
+    <string name="steps_streaks_hint">Serie de d铆as consecutivos sin interrupci贸n con pasos objetivo alcanzados</string>
+    <string name="steps_streaks">Medici贸n de los pasos</string>
+    <string name="step_streak_total">Total</string>
+    <string name="steps_streaks_streak_days">Racha
+\nD铆as</string>
+    <string name="prefs_hourly_chime">Tono horario</string>
+    <string name="steps_streaks_average_steps">Media
+\npasos</string>
+    <string name="prefs_touch_lock_buds2">Control t谩ctil</string>
+    <string name="steps_streaks_achievement_rate">脡xito
+\ntasa</string>
+    <string name="step_streak_average_steps_hint">Media de pasos por d铆a de la serie</string>
+    <string name="steps_streaks_total_days_hint_totals">Porcentaje de d铆as con objetivo alcanzado en relaci贸n con todos los d铆as con pasos</string>
+    <string name="devicetype_flipper_zero">Flipper zero</string>
+    <string name="intent_api_allow_trigger_export_title">Permitir la exportaci贸n de bases de datos</string>
+    <string name="supercars_lights_label">Iluminaci贸n</string>
+    <string name="supercars_lights_blinking_label">Intermitente</string>
+    <string name="pref_huami_truncate_fetch_operation_timestamps_summary">Trunca las marcas de tiempo de las operaciones de recuperaci贸n a minutos. Desactiva esta opci贸n para mantener las marcas de tiempo en segundos, si tienes problemas al recuperar de entrenamientos muy cortos.</string>
+    <string name="prefs_password_summary">Bloquea la pulsera con una contrase帽a cuando se la quita de la mu帽eca</string>
+    <string name="normal">Normal</string>
+    <string name="pref_cover_to_mute">Pulsar para silenciar</string>
+    <string name="discovery_scanning_intensity_warning">Si el dispositivo se bloquea o no responde, intente reducir la intensidad de escaneado. Si no se detecta el dispositivo, pruebe a aumentarla.</string>
+    <string name="pref_screen_notification_profile_find_device">Buscar dispositivo</string>
+    <string name="pref_screen_notification_idle_alerts">Alertas de inactividad</string>
+    <string name="pref_screen_vibration_patterns_title">Patrones de vibraci贸n</string>
+    <string name="prefs_spo2_monitoring_description">Controla autom谩ticamente los niveles de ox铆geno en sangre a lo largo del d铆a</string>
+    <string name="prefs_spo2_alert_threshold">Umbral de alerta de SPO2</string>
+    <string name="prefs_activity_monitoring_title">Seguimiento de la actividad</string>
+    <string name="scandinavian">Escandinavo</string>
+    <string name="portuguese_pt">Portugu茅s (Portugal)</string>
+    <string name="smart">Inteligente</string>
+    <string name="activity_type_outdoor_running">Correr al aire libre</string>
+    <string name="activity_type_indoor_walking">Caminar en interiores</string>
+    <string name="activity_type_freestyle">Estilo libre</string>
+    <string name="activity_type_pool_swimming">Nataci贸n en piscina</string>
+    <string name="activity_type_elliptical">El铆ptica</string>
+    <string name="activity_type_stepper">Aparato escalonado</string>
+    <string name="activity_type_volleyball">Voleibol</string>
+    <string name="notification_channel_gps">Localizaci贸n GPS</string>
+    <string name="menuitem_wechat_pay">WeChat Pay</string>
+    <string name="devicetype_galaxybuds_pro">Galaxy Buds Pro</string>
+    <string name="prefs_voice_detect_summary">Activa el sonido ambiente y baja la reproducci贸n autom谩ticamente tras detectar la voz</string>
+    <string name="step_streak_ongoing">En curso</string>
+    <string name="intent_api_allow_trigger_export_summary">Permitir la activaci贸n de la exportaci贸n de bases de datos a trav茅s de la API Intent</string>
+    <string name="intent_api_broadcast_export_summary">Emite un Intent cuando finalice la exportaci贸n de la base de datos</string>
+    <string name="intent_api_broadcast_export_title">Transmitir al exportar la base de datos</string>
+    <string name="supercars_turbo_speed_label">Velocidad turbo</string>
+    <string name="devicetype_asteroidos">AsteroidOS</string>
+    <string name="sony_quick_access_triple_tap">Acceso r谩pido (triple pulsaci贸n)</string>
+    <string name="pref_device_action_fitness_app_control_toggle">Alternar el seguimiento de aplicaciones de fitness</string>
+    <string name="pref_chart_sleep_lines_limit">Cu谩ntas l铆neas de sesi贸n de sue帽o mostrar antes de desplazarlas</string>
+    <string name="HeartRateZones">Zonas de frecuencia card铆aca</string>
+    <string name="elevationLoss">P茅rdida de altitud</string>
+    <string name="buttons_on_left">Botones a la izquierda</string>
+    <string name="menuitem_eject_water">Expulsar el agua</string>
+    <string name="permission_display_over_other_apps">%1$s necesita permiso para mostrarse sobre otras apps para permitir que Bangle.js inicie actividades en los relojes a trav茅s de intents cuando %1$s est谩 en segundo plano.
+\n
+\nEsto se puede utilizar para iniciar una aplicaci贸n de m煤sica, reproducir una canci贸n y muchas otras cosas.
+\n
+\nPor favor, pulse \'%2$s\' y luego \'%1$s\' active \'Permitir visualizaci贸n sobre otras apps\', despu茅s pulse \'Atr谩s\' para volver a %1$s.
+\n
+\nPara que %1$s deje de pedir permisos, vaya a \'Ajustes\' y desactive \'Comprobar estado de permisos\'.
+\n
+\nAseg煤rese de conceder a %1$s los permisos necesarios para funcionar como espera.</string>
+    <string name="maximumOxygenUptake">Consumo m谩ximo de ox铆geno</string>
+    <string name="aerobicTrainingEffect">Eficacia aer贸bica</string>
+    <string name="anaerobicTrainingEffect">Eficacia anaer贸bica</string>
+    <string name="sports_activity_confirm_delete_title">Borrar actividades %d</string>
+    <string name="sports_activity_confirm_delete_description">驴Est谩 seguro de que quiere borrar estas actividades %d\?</string>
+    <string name="prefs_switch_control_right">Botones de control a la derecha</string>
+    <string name="prefs_heartrate_alert_low_threshold">Umbral de alerta de frecuencia cardiaca baja</string>
+    <string name="prefs_stress_monitoring_title">Control del estr茅s</string>
+    <string name="estonian">Estonio</string>
+    <string name="activity_prefs_goal_standing_time_minutes">Objetivo diario: tiempo de reposo en minutos</string>
+    <string name="sony_speak_to_chat_sensitivity_standard">Est谩ndar</string>
+    <string name="prefs_control_center">Centro de control</string>
+    <string name="devicetype_sony_wf_1000xm4">Sony WF-1000XM4</string>
+    <string name="watchface_dialog_widget_cat_2nd_tz_widget">Widget de la 2陋 zona horaria</string>
+    <string name="sony_ambient_sound_control_button_mode_nc_as">Cancelaci贸n de ruido, sonido ambiente</string>
+    <string name="sony_ambient_sound_control_button_mode_nc_off">Cancelaci贸n de ruido, apagado</string>
+    <string name="devicetype_soflow_s06">SoFlow SO6</string>
+    <string name="devicetype_galaxybuds_2_pro">Galaxy Buds2 Pro</string>
+    <string name="pref_title_lock_unlock">Bloquear</string>
+    <string name="pref_workout_keep_screen_on_title">Mantener la pantalla encendida durante el entrenamiento</string>
+    <string name="pref_camera_remote_title">Control remoto de la c谩mara</string>
+    <string name="pref_camera_remote_summary">Permite que el reloj active la c谩mara del tel茅fono</string>
+    <string name="pref_workout_keep_screen_on_summary">La pantalla permanecer谩 encendida durante el entrenamiento y el brillo se ajustar谩 para mostrar continuamente los datos del entrenamiento en tiempo real</string>
+    <string name="wifi_hotspot">Punto de acceso Wi-Fi</string>
+    <string name="wifi_hotspot_summary">Control del punto de acceso Wi-Fi en el reloj</string>
+    <string name="wifi_hotspot_configuration">Configuraci贸n del punto de acceso Wi-Fi</string>
+    <string name="stop">Detener</string>
+    <string name="status">Estado</string>
+    <string name="wifi_hotspot_start_summary">Iniciar el punto de acceso Wi-Fi del reloj</string>
+    <string name="wifi_hotspot_stop_summary">Detener el punto de acceso Wi-Fi del reloj</string>
+    <string name="ftp_server">Servidor FTP</string>
+    <string name="ftp_server_summary">Controlar el servidor FTP del reloj</string>
+    <string name="ftp_server_start_summary">Iniciar el servidor FTP del reloj</string>
+    <string name="ftp_server_stop_summary">Detener el servidor FTP del reloj</string>
+    <string name="ftp_server_status">Estado del servidor FTP</string>
+    <string name="username">Nombre de usuario</string>
+    <string name="croatian">Croata</string>
+    <string name="wifi_ssid">SSID</string>
+    <string name="wifi_hotspot_status">Estado del punto de acceso Wi-Fi</string>
+    <string name="address">Direcci贸n</string>
+    <string name="ftp_server_configuration">Configuraci贸n del servidor FTP</string>
+    <string name="ftp_server_root_dir">Directorio ra铆z</string>
+    <string name="sony_speak_to_chat_summary">Desactiva autom谩ticamente la cancelaci贸n de ruido cuando empiece a hablar.</string>
+    <string name="pref_morning_updates_title">Actualizaci贸n matinal</string>
+    <string name="pref_morning_updates_summary">Mostrar actualizaciones cada ma帽ana</string>
+    <string name="pref_morning_updates_categories_title">Categor铆as de las actualizaciones matinales</string>
+    <string name="yesterdays_activity">Actividades de ayer</string>
+    <string name="pref_morning_updates_categories_summary">Lista de categor铆as a mostrar cada ma帽ana</string>
+    <string name="pref_summary_receive_calls_watch">Realiza y recibe llamadas directamente en el reloj</string>
+    <string name="bluetooth_calls">Llamadas por Bluetooth</string>
+    <string name="bluetooth_calls_pairing">Emparejamiento para llamadas Bluetooth</string>
+    <string name="bluetooth_calls_settings">Ajustes de las llamadas Bluetooth</string>
+    <string name="pref_display_caller_title">Mostrar informaci贸n del contacto</string>
+    <string name="pref_display_caller_summary">Mostrar el n煤mero de tel茅fono o el nombre de las llamadas entrantes</string>
+    <string name="pref_pair_bluetooth_calls_title">Emparejar para llamadas bluetooth</string>
+    <string name="pref_pair_bluetooth_calls_summary">Haz clic aqu铆 para iniciar el proceso de emparejamiento</string>
+    <string name="pref_pair_bluetooth_calls_help_title">C贸mo recibir llamadas por bluetooth</string>
+    <string name="pref_pair_bluetooth_calls_help_summary">Para recibir llamadas por bluetooth, tienes que emparejar tu tel茅fono con t煤 reloj.</string>
+    <string name="pref_pair_bluetooth_calls_help_1">1. Pulsa el bot贸n de abajo para iniciar el proceso de emparejamiento.</string>
+    <string name="pref_pair_bluetooth_calls_help_2">2. Ve a la configuraci贸n del Bluetooth de t煤 tel茅fono y empareja con el nuevo dispositivo que aparecer谩 (similar al nombre de t煤 reloj actual, pero con un sufijo, por ejemplo, \"Amazfit GTR 4 - AFC8\").</string>
+    <string name="pref_pair_bluetooth_calls_help_warning">ADVERTENCIA: Si activas las llamadas bluetooth sin emparejar con la segunda instancia, es posible que las notificaciones de llamadas no funcionen como se espera.</string>
+    <string name="pref_pair_bluetooth_calls_help_3">3. Habilita el ajuste para las llamadas Bluetooth a continuaci贸n.</string>
+    <string name="pref_summary_notification_cache_while_disconnected">Enviar notificaciones perdidas cuando un dispositivo se vuelve a conectar despu茅s de estar fuera de cobertura</string>
+    <string name="pref_title_notification_cache_while_disconnected">Cach茅 mientras est谩 fuera de cobertura</string>
+    <string name="sony_button_mode_help_summary">Descripci贸n de cada modo de bot贸n</string>
+    <string name="single_tap">Toque 煤nico</string>
+    <string name="double_tap">Toque doble</string>
+    <string name="triple_tap">Toque triple</string>
+    <string name="quick_attention">Pulsaci贸n r谩pida</string>
+    <string name="pref_switch_controls_anc_ambient_off">Cancelaci贸n de ruido 鈫愨啋 Ambiente 鈫愨啋 Apagado</string>
+    <string name="sony_button_mode_help_title">Modos de bot贸n - Ayuda</string>
+    <string name="long_press">Pulsaci贸n larga</string>
+    <string name="continue_pressing">Pulsaci贸n continua</string>
+    <string name="menuitem_mi_ai">MI AI</string>
+    <string name="menuitem_cards">Tarjetas</string>
+    <string name="menuitem_last_workout">脷ltimo entrenamiento</string>
+    <string name="prefs_shortcut_cards_summary">Accesos directos que se ven al deslizar el dedo hacia la derecha en la esfera del reloj. Cuando se ejecuta una aplicaci贸n, este ajuste no afecta a los accesos directos generados autom谩ticamente.</string>
+    <string name="prefs_shortcut_cards">Accesos directos</string>
+    <string name="menuitem_vo2_max">VO鈧� M谩x</string>
+    <string name="menuitem_recommendation">Recomendaci贸n</string>
+    <string name="menuitem_total_workout">Entrenamiento completo</string>
+    <string name="menuitem_forecast">Previsi贸n</string>
+    <string name="zepp_os_watchface_multiple_data">Datos M煤ltiples</string>
+    <string name="zepp_os_watchface_simplicity_data">Datos sencillos</string>
+    <string name="zepp_os_watchface_vibrant">Vibrante</string>
+    <string name="zepp_os_watchface_business_style">Estilo de negocios</string>
+    <string name="zepp_os_watchface_superposition">superposici贸n</string>
+    <string name="zepp_os_watchface_red_fantasy">Fantas铆a roja</string>
+    <string name="zepp_os_watchface_minimalist">Minimalismo</string>
+    <string name="zepp_os_watchface_rotating_earth">Rotaci贸n de la Tierra</string>
+    <string name="zepp_os_watchface_rush">R谩pido</string>
+    <string name="zepp_os_watchface_emerald_moonlight">Luz de luna esmeralda</string>
+    <string name="menuitem_aqi">脥ndice de la calidad del aire</string>
+    <string name="devicetype_amazfit_gtr3_pro">Amazfit GTR 3 Pro</string>
+    <string name="fw_upgrade_notice_amazfit_gtr3_pro">Est谩s a punto de instalar el firmware %s en su Amazfit GTR 3 Pro.
+\n
+\nSu reloj se reiniciar谩 despu茅s de instalar el archivo .zip.
+\n
+\n隆PROCEDA BAJO SU PROPIO RIESGO!</string>
+    <string name="devicetype_sony_wh_1000xm5">Sony WH-1000XM5</string>
+    <string name="fossil_hr_confirm_connection">Por favor, conf铆rmelo en su reloj</string>
+    <string name="fossil_hr_pairing_successful">Emparejamiento con el reloj correcto</string>
+    <string name="fossil_hr_pairing_failed">Fall贸 el emparejamiento con el reloj</string>
+    <string name="fossil_hr_confirmation_timeout">Tiempo l铆mite de confirmaci贸n, continuando</string>
+    <string name="fossil_hr_connection_not_confirmed">Conexi贸n no confirmada en el reloj, utilizando el modo no autenticado</string>
+    <string name="fossil_hr_confirmation_skipped">Omitir la confirmaci贸n en el dispositivo</string>
+    <string name="gpx_route_upload_complete">Carga de ruta Gpx completada</string>
+    <string name="gpx_route_upload_in_progress">Cargando ruta gpx</string>
+    <string name="kind_gpx_route">Ruta GPX</string>
+    <string name="dev_tools">Herramientas de desarrollo</string>
+    <string name="gpx_route_upload_failed">Error al cargar la ruta Gpx</string>
+    <string name="activity_error_share_failed">Error al compartir el archivo.</string>
+    <string name="activity_detail_share_gps_label">Compartir seguimiento GPS</string>
+    <string name="activity_detail_share_raw_summary">Compartir el resumen en bruto</string>
+    <string name="activity_detail_share_raw_details">Compartir detalles en bruto</string>
+    <string name="debug_companion_show_associated">Mostrar dispositivos asociados</string>
+    <string name="debug_companion_pair_current">Emparejar el dispositivo actual como compa帽ero</string>
+    <string name="pref_contacts_title">Contactos</string>
+    <string name="contact_no_free_slots_description">El dispositivo no tiene espacios libres para contactos (total de espacios: %1$s)</string>
+    <string name="title_activity_contact_details">Datos de contacto</string>
+    <string name="contact_name">Nombre</string>
+    <string name="pref_contacts_summary">Configurar los contactos del reloj</string>
+    <string name="contact_delete_confirm_title">Borrar contacto</string>
+    <string name="contact_phone_number">N煤mero de tel茅fono</string>
+    <string name="contact_delete_confirm_description">驴Seguro que quiere borrar \'%1$s\'\?</string>
+    <string name="title_activity_set_contacts">Configurar contactos</string>
+    <string name="contact_missing_number">El n煤mero de contacto est谩 vac铆o</string>
+    <string name="contact_missing_name">El nombre del contacto est谩 vac铆o</string>
+    <string name="pref_title_navigation_forward">Enviar navegaci贸n al reloj</string>
+    <string name="pref_summary_navigation_forward">Reenv铆a las instrucciones de las aplicaciones de navegaci贸n al reloj</string>
+    <string name="pref_header_navigation">Navegador</string>
+    <string name="intent_api_allow_debug_commands_summary">Permitir la activaci贸n de comandos de men煤 de depuraci贸n a trav茅s de Intent API</string>
+    <string name="intent_api_allow_debug_commands_title">Permitir comandos de depuraci贸n</string>
+    <string name="voice_service_package_title">Paquete de servicio de voz</string>
+    <string name="voice_service_package_summary">Aplicaci贸n que contiene el servicio de gesti贸n de comandos de voz</string>
+    <string name="voice_service_class_summary">Ruta completa del servicio de gesti贸n de comandos de voz</string>
+    <string name="voice_service">Servicio de voz</string>
+    <string name="voice_service_class_title">Clases de servicio de voz</string>
+    <string name="pref_app_logs_stop_summary">Detener el registro de las aplicaciones del reloj</string>
+    <string name="pref_app_logs_summary">Activar los registros de las aplicaciones del reloj</string>
+    <string name="pref_app_logs_title">Registros de aplicaciones</string>
+    <string name="pref_app_logs_start_summary">Iniciar el registro de las aplicaciones del reloj</string>
+    <string name="fwapp_install_device_not_supported">No se puede instalar el archivo, el dispositivo no es compatible.</string>
+    <string name="share_screenshot">Compartir captura de pantalla</string>
+    <string name="screenshot_taken">Captura de pantalla realizada</string>
+    <string name="stress_mild">Leve</string>
+    <string name="stress_moderate">Moderado</string>
+    <string name="stress_high">Alto</string>
+    <string name="charts_legend_stress_average">Promedio del estr茅s</string>
+    <string name="stress_relaxed">Relajado</string>
+    <string name="watchface_widget_type_uv_index">脥ndice UV</string>
+    <string name="action_changelog">Registro de los cambios</string>
+    <string name="pref_title_banglejs_phone_gps_network_only">Solo usa la red para determinar la ubicaci贸n</string>
+    <string name="pref_title_banglejs_phone_gps_update_interval">Intervalo de actualizaci贸n de datos GPS</string>
+    <string name="pref_summary_banglejs_phone_gps_enbale">Utiliza los datos GPS del tel茅fono m贸vil para sobrescribir los datos GPS del dispositivo de pulsera</string>
+    <string name="pref_summary_banglejs_phone_gps_update_interval">El intervalo de actualizaci贸n de la posici贸n GPS, en ms</string>
+    <string name="pref_title_banglejs_phone_gps_enbale">Utiliza los datos GPS de su tel茅fono</string>
+    <string name="pref_summary_banglejs_phone_gps_network_only">Utiliza s贸lo el proveedor de red para determinar la ubicaci贸n. Esto reduce el consumo de energ铆a a costa de la precisi贸n. Es necesaria una reconexi贸n.</string>
+    <string name="description">Descripci贸n</string>
+    <string name="preview_image">Vista previa de la imagen</string>
+    <string name="status_icon">Icono de estado</string>
+    <string name="changelog_full_title">Registro de los cambios</string>
+    <string name="changelog_ok_button">DE ACUERDO</string>
+    <string name="changelog_show_full">M谩s鈥�</string>
+    <string name="changelog_title">Qu茅 novedades hay</string>
+    <string name="title">T铆tulo</string>
+    <string name="pai_chart_per_week">PAI por semana</string>
+    <string name="pai_chart_per_month">PAI al mes</string>
+    <string name="pai_plus_num">+%d</string>
+    <string name="num_min">%d min</string>
+    <string name="pai_total">Total</string>
+    <string name="pai_day">Aumento del d铆a</string>
+    <string name="loyalty_cards_install">Instalar Catima</string>
+    <string name="loyalty_cards_sync_groups">Grupos a sincronizar</string>
+    <string name="loyalty_cards_sync_starred">Sincronizar s贸lo tarjetas favoritas</string>
+    <string name="loyalty_cards_sync_title">Sincronizar tarjetas de fidelizaci贸n</string>
+    <string name="loyalty_cards_catima_not_installed">Se necesita Catima para gestionar las tarjetas de fidelizaci贸n</string>
+    <string name="loyalty_cards_sync_options">Opciones de sincronizaci贸n</string>
+    <string name="loyalty_cards_sync">Sincronizar</string>
+    <string name="loyalty_cards_catima">Catima</string>
+    <string name="loyalty_cards">Tarjetas de fidelizaci贸n</string>
+    <string name="loyalty_cards_syncing">Sincronizando %d tarjetas de fidelizaci贸n con el dispositivo</string>
+    <string name="loyalty_cards_catima_package">Nombre del paquete Catima</string>
+    <string name="loyalty_cards_sync_groups_only">Sincronizar s贸lo grupos espec铆ficos</string>
+    <string name="loyalty_cards_sync_archived">Sincronizar tarjetas archivadas</string>
+    <string name="loyalty_cards_install_catima_fail">Fallo al abrir la tienda de aplicaciones para instalar Catima</string>
+    <string name="loyalty_cards_sync_summary">Toque para sincronizar las tarjetas con el reloj</string>
+    <string name="loyalty_cards_catima_not_compatible">La versi贸n instalada de Catima no es compatible con Gadgetbridge. Por favor, actualiza Catima y Gadgetbridge a las 煤ltimas versiones.</string>
+    <string name="loyalty_cards_catima_permissions_title">Faltan permisos</string>
+    <string name="loyalty_cards_open_catima">Abrir Catima</string>
+    <string name="loyalty_cards_catima_permissions_summary">Gadgetbridge necesita permisos de lectura en las tarjetas Catima para sincronizarlas. Toque este bot贸n para concederlos.</string>
+    <string name="pref_title_mb_intents">Transmitir directamente los intentos de los botones multimedia</string>
+    <string name="pref_summary_mb_intents">Activar si el control de medios del dispositivo no funciona para determinadas aplicaciones</string>
+    <string name="devicetype_bohemic_smart_bracelet">Pulsera inteligente Bohemia</string>
+    <string name="devicetype_vivomove_hr">Garmin Vivomove HR</string>
+    <string name="busy_task_fetch_sports_details">Obtener la informaci贸n deportiva</string>
+    <string name="busy_task_fetch_stress_data">Obtener los datos de la tensi贸n</string>
+    <string name="busy_task_fetch_pai_data">Obtener los datos PAI</string>
+    <string name="busy_task_fetch_spo2_data">Obtener los datos de SpO2</string>
+    <string name="busy_task_fetch_sleep_respiratory_rate_data">Obtener los datos de la respiraci贸n durante el sue帽o</string>
+    <string name="busy_task_fetch_sports_summaries">Consultar los res煤menes deportivos</string>
+    <string name="busy_task_fetch_debug_logs">Obtener los registros de depuraci贸n</string>
+    <string name="devicetype_casiogwb5600">Casio GW-B5600</string>
+    <string name="busy_task_fetch_hr_data">Obtener los datos de la frecuencia card铆aca</string>
+    <string name="devicetype_casiogmwb5000">Casio GMW-B5000</string>
+    <string name="error_no_bluetooth_scan">Para que el escaneado funcione correctamente, se debe conceder y activar el acceso al Bluetooth</string>
+    <string name="error_no_bluetooth_connect">Se debe otorgar y habilitar el acceso a la conexi贸n Bluetooth para que el escaneo funcione correctamente</string>
+    <string name="permission_request">%1$s te permite enviar mensajes y otros datos desde Android a tu dispositivo. Para ello necesita permiso para acceder a esos datos y, sin 茅l, podr铆a no funcionar correctamente.
+\n
+\nAhora se mostrar谩n unos di谩logos en Android solicitando esos permisos.
+\n
+\nPor favor, pulsa \'%2$s\' para continuar.</string>
+    <string name="activity_prefs_goals">Objetivos</string>
+    <string name="withings_bt_calibration_previous">Anterior</string>
+    <string name="activity_type_surfing">Surfear</string>
+    <string name="activity_type_windsurfing">Windsurf</string>
+    <string name="activity_type_kitesurfing">Kitesurf</string>
+    <string name="activity_type_rugby">Rugby</string>
+    <string name="activity_type_baseball">B茅isbol</string>
+    <string name="activity_type_handball">Balonmano</string>
+    <string name="activity_type_tennis">Tenis</string>
+    <string name="activity_type_squash">Squash</string>
+    <string name="activity_type_weightlifting">Halterofilia</string>
+    <string name="activity_type_snowboarding">Snowboard</string>
+    <string name="activity_type_icehockey">Jockey sobre hielo</string>
+    <string name="activity_type_iceskating">Patinaje sobre hielo</string>
+    <string name="activity_type_golf">Golf</string>
+    <string name="withings_calibration_text_hours">Utilice el dial de abajo para alinear la aguja de las horas con las 12.</string>
+    <string name="withings_calibration_text_minutes">Ahora utilice el dial para alinear el minutero con las 12.</string>
+    <string name="withings_calibration_text_activity_target">Por 煤ltimo, alinee la manecilla de actividad al 100%. Tenga en cuenta que esta manecilla s贸lo se mueve en el sentido de las agujas del reloj.</string>
+    <string name="withings_bt_calibration_next">Siguiente</string>
+    <string name="withings_steel_hr">Withings Steel HR</string>
+    <string name="activity_type_football">F煤tbol</string>
+    <string name="activity_type_riding">Equitaci贸n</string>
+    <string name="activity_type_hockey">Jockey</string>
+    <string name="activity_type_skiing">Esqu铆</string>
+    <string name="activity_type_other">Otros</string>
+    <string name="activity_type_dancing">Danza</string>
+    <string name="pref_canned_message">Mensaje</string>
+    <string name="drag_handle">control de arrastre</string>
+    <string name="find_my_phone_found_it">LO ENCONTR脡</string>
+    <string name="pref_theme_dynamic">Colores din谩micos</string>
+    <string name="preferences_miband_1_2_settings" tools:ignore="TypographyFractions">Ajustes de Mi Band 1/2</string>
+    <string name="preferences_miband_1_2_warning">Nota: Estas preferencias solo se aplican a las Mi Bands 1 y 2.</string>
+    <string name="pref_activity_full_sync_trigger_warning">Esto activar谩 una sincronizaci贸n completa de todos los datos de actividad del dispositivo. Puede tardar unos minutos en completarse.</string>
+    <string name="pref_activity_full_sync_trigger_summary">Activar una sincronizaci贸n completa de todos los datos de actividad</string>
+    <string name="pref_activity_full_sync_trigger_title">Sincronizaci贸n completa</string>
+    <string name="pref_show_changelog_summary">Mostrar el registro de cambios desde la 煤ltima versi贸n despu茅s de actualizar Gadgetbridge</string>
+    <string name="pref_show_changelog">Mostrar el registro de cambios al inicio</string>
+    <string name="menuitem_zepp_coach">Zepp Coach</string>
+    <string name="pref_theme_dynamic_colors_not_available_warning">Los colores din谩micos no est谩n disponibles en su dispositivo, solo Android 12+ soporta esta funcionalidad. Gadgetbridge utilizar谩 los colores por defecto de Material 3.</string>
+    <string name="pref_theme_dynamic_colors_explanation">Nota: para el tema de colores din谩micos, debe activar Colores de fondo de pantalla o Paleta de colores en los ajustes de apariencia de Android 12+. Si no lo hace, Gadgetbridge utilizar谩 los colores por defecto de Material 3.</string>
+    <string name="devicetype_amazfit_bip3_pro">Amazfit Bip 3 Pro</string>
+    <string name="fw_upgrade_notice_amazfitbip3pro">Est谩s a punto de instalar el firmware %s en tu Amazfit Bip 3 Pro.
+\n
+\nPor favor, aseg煤rate de instalar el archivo .fw, y despu茅s el archivo .res. Tu reloj se reiniciar谩 despu茅s de instalar el archivo .fw.
+\n
+\nNota: No es necesario instalar el archivo .res si es exactamente el mismo que ya tienes instalado.
+\n
+\n隆CONTINUA BAJO TU PROPIA RESPONSABILIDAD!</string>
+    <string name="fw_upgrade_notice_amazfit_cheetah_pro">Est谩 a punto de instalar el firmware %s en su Amazfit Cheetah Pro.
+\n
+\nSu band se reiniciar谩 despu茅s de instalar el archivo .zip.
+\n
+\n隆PROCEDE BAJO TU PROPIO RIESGO!</string>
+    <string name="devicetype_amazfit_cheetah_pro">Amazfit Cheetah Pro</string>
+    <string name="fw_upgrade_notice_zepp_os">Est谩 a punto de instalar el firmware %s en su %s.
+\n
+\nSu reloj se reiniciar谩 despu茅s de instalar el archivo .zip.
+\n
+\n隆PROCEDA BAJO SU PROPIO RIESGO!</string>
+    <string name="info_menu_structure_set">Estructura del men煤 JSON en GB</string>
+    <string name="error_invalid_menu_structure">Estructura de men煤 JSON no v谩lida</string>
+    <string name="button_open_menu_companion">Abrir el men煤 de la aplicaci贸n complementaria</string>
+    <string name="button_reset_menu_structure">Restablecer la estructura del men煤</string>
+    <string name="error_menu_companion_not_installed">Es probable que \"HR Menu Companion\" no est茅 instalado</string>
+    <string name="info_menu_structure_contents">Estructura del men煤: %s</string>
+    <string name="info_fossil_rebuild_watchface_custom_menu">Por favor, reconstruya su watchface para el men煤 personalizado</string>
+    <string name="devicetype_amazfit_bip5">Amazfit Bip 5</string>
+    <string name="devicetype_amazfit_cheetah_square">Amazfit Cheetah (Cuadrado)</string>
+    <string name="devicetype_amazfit_cheetah_round">Amazfit Cheetah (Redondo)</string>
+    <string name="devicetype_amazfit_trex_ultra">Amazfit T-Rex Ultra</string>
+    <string name="devicetype_amazfit_gtr_mini">Amazfit GTR Mini</string>
+    <string name="devicetype_amazfit_falcon">Amazfit Falcon</string>
+    <string name="device_experimental">EXPERIMENTAL</string>
+    <string name="info_menu_structure_removed">Estructura de men煤s eliminada</string>
+    <string name="spanish_us">Espa帽ol (Estados Unidos)</string>
+    <string name="french_ca">Franc茅s (Canad谩)</string>
+    <string name="french_fr">Franc茅s (Francia)</string>
+    <string name="english_ca">Ingl茅s (Canad谩)</string>
+    <string name="english_au">Ingl茅s (Australia)</string>
+    <string name="english_gb">Ingl茅s (Reino Unido)</string>
+    <string name="english_in">Ingl茅s (India)</string>
+    <string name="english_us">Ingl茅s (Estados Unidos)</string>
+    <string name="spanish_es">Espa帽ol (Espa帽a)</string>
+    <string name="spanish_mx">Espa帽ol (M茅xico)</string>
+    <string name="updatefirmwareoperation_updateproblem_free_space">El dispositivo no tiene suficiente espacio libre</string>
+    <string name="text_receiver_activity_title">Enviar un texto al dispositivo</string>
+    <string name="updatefirmwareoperation_failed_low_mtu">La MTU actual de %1$d es demasiado baja, por favor, active la MTU alta en la configuraci贸n del dispositivo y desconecte/conecte de nuevo el dispositivo.</string>
+    <string name="devicetype_miband2_hrx">Mi Band HRX</string>
+    <string name="new_discover_activity_description">Activa una nueva actividad de detecci贸n, que deber铆a solucionar los problemas de detecci贸n de dispositivos. Desactivela si tiene problemas al buscar o emparejar el dispositivo.</string>
+    <string name="new_discover_activity_title">Activar nueva actividad de detecci贸n</string>
+    <string name="prefs_wena3_receive_calls_title">Ajustes de llamadas entrantes</string>
+    <string name="prefs_wena3_button_action_name_start_timer">Iniciar temporizador activo</string>
+    <string name="prefs_wena3_menu_icon_hint">El icono de Ajustes siempre aparece al final</string>
+    <string name="prefs_wena3_notification_vibration_repetition_3">3 veces</string>
+    <string name="prefs_wena3_button_action_name_qrio_lock">Bloquear Qrio</string>
+    <string name="prefs_wena3_auto_power_off_turn_off_time_item">Hora de apagado</string>
+    <string name="prefs_wena3_vibration_short">Corta</string>
+    <string name="prefs_wena3_home_icon_name_calories">Calor铆as</string>
+    <string name="prefs_home_icon_left_item">Izquierda</string>
+    <string name="prefs_wena3_led_none">Sin LED</string>
+    <string name="prefs_wena3_vibration_step_down">Descendente</string>
+    <string name="prefs_wena3_vibration_smart_item">Vibraci贸n inteligente</string>
+    <string name="prefs_wena3_home_icon_name_riiiver">Riiiver</string>
+    <string name="prefs_wena3_hint_background_sync">Permitir que el Wena pida peri贸dicamente a Gadgetbridge que descargue los datos de actividad del dispositivo</string>
+    <string name="prefs_wena3_home_icon_name_suica">Balance Suica</string>
+    <string name="prefs_wena3_vibration_strength_item_medium">Media</string>
+    <string name="prefs_wena3_title_activity">Ajustes de actividad</string>
+    <string name="prefs_wena3_vibration_continuous">Continua</string>
+    <string name="prefs_wena3_home_icon_name_pedometer">N煤mero de pasos</string>
+    <string name="prefs_wena3_home_icon_name_qrio">Qrio</string>
+    <string name="white">Blanco</string>
+    <string name="prefs_wena3_home_icon_name_clock">Hora actual</string>
+    <string name="prefs_wena3_item_background_sync">Sincronizaci贸n de datos de actividad en segundo plano</string>
+    <string name="prefs_wena3_vibration_strength_item_weak">D茅bil</string>
+    <string name="prefs_wena3_home_icon_title">Iconos de la pantalla de inicio</string>
+    <string name="prefs_wena3_item_weather_statusbar">Tiempo en la barra de estado</string>
+    <string name="temperature_scale_celsius">Celsius</string>
+    <string name="prefs_wena3_vibration_step_up">Ascendente</string>
+    <string name="prefs_home_icon_right_item">Derecha</string>
+    <string name="prefs_wena3_vibration_basic">Simple</string>
+    <string name="prefs_wena3_title_screen">Ajustes de pantalla</string>
+    <string name="prefs_wena3_item_large_font">Mayor tama帽o de letra</string>
+    <string name="prefs_wena3_home_icon_name_energy">Body Energy</string>
+    <string name="prefs_wena3_notification_vibration_repetition_2">Dos veces</string>
+    <string name="prefs_wena3_auto_power_off_hint">El dispositivo se apagar谩 y encender谩 autom谩ticamente en el horario especificado</string>
+    <string name="prefs_wena3_hint_lift_wrist">Enciende la pantalla cuando mira su mu帽eca</string>
+    <string name="prefs_wena3_hint_large_font">Aumenta el tama帽o de letra en el calendario, las notificaciones, etc.</string>
+    <string name="prefs_wena3_button_action_item_double">Doble pulsaci贸n</string>
+    <string name="prefs_wena3_vibration_rapid">R谩pida</string>
+    <string name="prefs_wena3_button_action_name_qrio_unlock">Desbloquear Qrio</string>
+    <string name="temperature_scale_cf">Escala de temperatura</string>
+    <string name="prefs_wena3_notification_vibration_repetition_1">Una vez</string>
+    <string name="prefs_wena3_receive_calls_item">Recibir notificaci贸n de llamadas</string>
+    <string name="prefs_wena3_hint_weather_statusbar">Muestra el icono de las condiciones actuales en la esquina superior izquierda de la pantalla de inicio</string>
+    <string name="prefs_wena3_home_icon_name_wena_pay">Pagos de Wena</string>
+    <string name="prefs_wena3_button_action_item">Bot贸n de acci贸n</string>
+    <string name="prefs_wena3_auto_power_off_turn_on_time_item">Hora de reanudaci贸n</string>
+    <string name="prefs_wena3_notification_default_call_vibration">Vibraci贸n de llamada entrante</string>
+    <string name="green">Verde</string>
+    <string name="yellow">Amarillo</string>
+    <string name="devicetype_sony_wena3">Sony Wena 3</string>
+    <string name="prefs_wena3_notification_default_call_led">Color del LED de llamada entrante</string>
+    <string name="red">Rojo</string>
+    <string name="cyan">Azul cielo</string>
+    <string name="prefs_wena3_vibration_siren">Sirena</string>
+    <string name="temperature_scale_cf_summary">Seleccione si el dispositivo utiliza la escala Celsius o Fahrenheit.</string>
+    <string name="prefs_wena3_menu_icon_title">Iconos del men煤</string>
+    <string name="prefs_wena3_notification_default_call_vibration_repetition">Repetici贸n de vibraci贸n de llamada entrante</string>
+    <string name="prefs_wena3_notification_default_vibration">Notificaci贸n por vibraci贸n</string>
+    <string name="prefs_wena3_notification_vibration_repetition_0">Indefinidamente</string>
+    <string name="prefs_wena3_notification_vibration_repetition_4">4 veces</string>
+    <string name="temperature_scale_fahrenheit">Fahrenheit</string>
+    <string name="prefs_wena3_vibration_strength_item">Intensidad de vibraci贸n</string>
+    <string name="prefs_wena3_receive_calls_hint">Si est谩 desactivado, no recibir谩 notificaciones de las llamadas entrantes en el Wena</string>
+    <string name="prefs_wena3_button_action_item_long">Pulsaci贸n larga</string>
+    <string name="prefs_wena3_menu_icon_name_payment">Formas de pago</string>
+    <string name="prefs_wena3_status_page_title">Orden de las p谩ginas de estado</string>
+    <string name="prefs_wena3_title_alarm">Ajustes de alarma</string>
+    <string name="prefs_wena3_notification_default_vibration_repetition">Notificaci贸n repetida por vibraci贸n</string>
+    <string name="prefs_home_icon_center_item">Centro</string>
+    <string name="prefs_wena3_hint_rich_design">A帽ade rect谩ngulos redondeados alrededor de los iconos de la pantalla de inicio</string>
+    <string name="prefs_wena3_notification_default_led">Color del LED de notificaci贸n</string>
+    <string name="prefs_wena3_auto_power_off_item">Apagado autom谩tico</string>
+    <string name="prefs_wena3_home_icon_name_edy">Balance Edy</string>
+    <string name="prefs_wena3_vibration_warning">Aviso</string>
+    <string name="prefs_wena3_smart_alarm_margin_item">Margen de alarma inteligente</string>
+    <string name="prefs_wena3_item_rich_design">Utilizar un dise帽o elegante</string>
+    <string name="prefs_wena3_day_start_hour_item">El d铆a comienza en</string>
+    <string name="prefs_wena3_notification_per_app_settings_title">Ajustes de notificaciones por aplicaci贸n</string>
+    <string name="prefs_wena3_vibration_none">Sin vibraci贸n</string>
+    <string name="purple">Morado</string>
+    <string name="prefs_wena3_vibration_strength_item_strong">Fuerte</string>
+    <string name="prefs_wena3_vibration_triple">Triple</string>
+    <string name="blue">Azul</string>
+    <string name="prefs_wena3_button_action_name_activity_screen">Pantalla de actividad</string>
+    <string name="prefs_wena3_notification_settings_title">Ajustes de notificaci贸n</string>
+    <string name="devicetype_sony_wf_1000xm5">Sony WF-1000XM5</string>
+    <string name="pref_message_privacy_mode_bodyonly">Ocultar s贸lo el cuerpo del mensaje</string>
+    <string name="latvian">Let贸n</string>
+    <string name="common_symbols">S铆mbolos frecuentes</string>
+    <string name="pref_title_notifications_ignore_work_profile">Ignorar las notificaciones del perfil de trabajo</string>
+    <string name="pref_summary_notifications_ignore_work_profile">No enviar las notificaciones de las aplicaciones del perfil al reloj</string>
+    <string name="femometer_measurement_mode_normal">Modo Normal (entre 60 o 90 segundos)</string>
+    <string name="femometer_measurement_mode_quick">Modo R谩pido (30s)</string>
+    <string name="devicetype_amazfit_balance">Amazfit Balance</string>
+    <string name="femometer_measurement_mode_title">Modo de medici贸n</string>
+    <string name="femometer_measurement_mode_precise">Modo Preciso (3min)</string>
+    <string name="zepp_os_watchface_pure_white">Blanco puro</string>
+    <string name="zepp_os_watchface_the_ultima">El 煤ltimo</string>
+    <string name="menuitem_headphone">Auriculares</string>
+    <string name="zepp_os_watchface_free_combination">Combinaci贸n libre</string>
+    <string name="menuitem_body_composition">Forma f铆sica</string>
+    <string name="menuitem_zepp_pay">Zepp Pay</string>
+    <string name="zepp_os_watchface_starry_sky">Cielo estrellado</string>
+    <string name="zepp_os_watchface_vast_sky">Cielo vasto</string>
+    <string name="menuitem_workout_shortcuts">Atajos para el entrenamiento</string>
+    <string name="zepp_os_watchface_lightning_flash">Rel谩mpago</string>
+    <string name="menuitem_apps_shortcuts">Atajos de las aplicaciones</string>
+    <string name="zepp_os_watchface_guider">Gu铆a</string>
+    <string name="menuitem_readiness">Preparaci贸n</string>
+    <string name="menuitem_thermometer">Term贸metro</string>
+    <string name="devicetype_femometer_vinca2">Femometer Vinca II</string>
+    <string name="busy_task_fetch_statistics">Obtener las estad铆sticas</string>
+    <string name="busy_task_fetch_temperature">Obtener los datos de la temperatura</string>
+    <string name="call_rejection_method_ignore">Ignorar (silencio)</string>
+    <string name="avgStrokeRate">Tasa media de carrera</string>
+    <string name="zepp_os_watchface_city_of_speed">Velocidad en ciudad</string>
+    <string name="call_rejection_method_reject">Rechazar</string>
+    <string name="pref_call_rejection_method_title">M茅todo de rechazo de llamadas</string>
+    <string name="fossil_hr_nav_app_not_installed_notify_text">Se ha iniciado la navegaci贸n pero la aplicaci贸n de navegaci贸n no est谩 instalada en el reloj. Por favor, inst谩lela desde App Manager.</string>
+    <string name="yard">yarda</string>
+    <string name="strokes_minute">serie/min</string>
+    <string name="pref_call_rejection_method_summary">Qu茅 acci贸n se realiza cuando se rechaza una llamada entrante desde el reloj</string>
+    <string name="maxStrokeRate">Frecuencia m谩xima de carrera</string>
+    <string name="laneLength">Longitud de la pista</string>
+    <string name="strokes">Total de carreras</string>
+    <string name="fossil_hr_nav_app_not_installed_notify_title">La aplicaci贸n de navegaci贸n no est谩 instalada en el reloj</string>
+    <string name="devicetype_amazfit_active_edge">Amazfit Active Edge</string>
+    <string name="charts_legend_spo2_average">Promedio de ox铆geno en sangre</string>
+    <string name="devicetype_amazfit_active">Amazfit Active</string>
+    <string name="pref_summary_prefix_notification_with_app">Anteponer al t铆tulo de la notificaci贸n el nombre de la aplicaci贸n de origen</string>
+    <string name="pref_summary_osmand_packagename">Permite seleccionar la versi贸n de OsmAnd a la que conectarse</string>
+    <string name="pref_title_prefix_notification_with_app">Nombre de la aplicaci贸n en la notificaci贸n</string>
+    <string name="pref_title_osmand_packagename">Nombre del paquete OsmAnd</string>
+    <string name="pref_title_navigation_apps">Aplicaciones de navegaci贸n</string>
+    <string name="pref_title_navigation_prefs">Preferencias de navegaci贸n</string>
+    <string name="pref_navigation_app_gmaps">Google Maps</string>
+    <string name="pref_navigation_app_osmand">OsmAnd(+)</string>
+    <string name="pref_title_casio_alert_other">Alerta para otras notificaciones</string>
+    <string name="pref_title_casio_alert_calendar">Alerta de las notificaciones en el calendario</string>
+    <string name="pref_summary_casio_alert_call">Alerta (vibraci贸n/pitido) de llamadas entrantes</string>
+    <string name="devicetype_miband8">Xiaomi Smart Band 8</string>
+    <string name="menuitem_running">Ejecutando</string>
+    <string name="pref_sleep_mode_schedule_title">Horario del modo de reposo</string>
+    <string name="pref_title_casio_alert_call">Alerta de llamadas entrantes</string>
+    <string name="bedtime">Hora de acostarse</string>
+    <string name="pref_title_casio_alert_email">Alerta de notificaciones por correo electr贸nico</string>
+    <string name="pref_summary_casio_alert_email">Alerta (vibraci贸n/pitido) para notificaciones por correo electr贸nico</string>
+    <string name="menuitem_focus">Enfocar</string>
+    <string name="pref_summary_casio_alert_sms">Alerta (vibraci贸n/pitido) para notificaciones SMS (mensajes de texto)</string>
+    <string name="pref_title_preview_message_in_title">Mostrar una vista previa del mensaje en el t铆tulo</string>
+    <string name="devicetype_redmiwatch3active">Redmi Watch 3 Active</string>
+    <string name="pref_summary_casio_alert_calendar">Alerta (vibraci贸n/pitido) para las notificaciones de la agenda</string>
+    <string name="serial_number">N煤mero de serie</string>
+    <string name="danish">Dan茅s</string>
+    <string name="wake_up_time">Despertar</string>
+    <string name="menuitem_stats">Estad铆sticas</string>
+    <string name="pref_summary_casio_alert_other">Alerta (vibraci贸n/pitido) para las notificaciones de la otra categor铆a</string>
+    <string name="devicetype_xiaomi_watch_lite">Xiaomi Watch Lite</string>
+    <string name="pref_summary_preview_message_in_title">Muestra una vista previa del mensaje en el t铆tulo de una notificaci贸n seg煤n lo permita el dispositivo</string>
+    <string name="pref_sleep_mode_schedule_summary">Env铆a un recordatorio y entra en modo reposo a la hora de acostarte. A la hora programada para despertarse, sonar谩 la alarma del despertador.</string>
+    <string name="pref_title_casio_alert_sms">Alerta de notificaciones por SMS</string>
+    <string name="menuitem_alerts">Alertas</string>
+    <string name="devicetype_xiaomi_watch_s1_active">Xiaomi Watch S1 Active</string>
+    <string name="devicetype_miband7pro">Xiaomi Smart Band 7 Pro</string>
+    <string name="devicetype_mi_watch_color_sport">Mi Watch Color Sport</string>
+    <string name="devicetype_pixoo">Pixoo</string>
+    <string name="pref_summary_send_app_notifications">Enviar notificaciones de las aplicaciones al dispositivo</string>
+    <string name="pref_title_send_app_notifications">Enviar notificaciones</string>
+    <string name="pref_device_action_dnd_on">No molestar - Activado</string>
+    <string name="pref_device_action_dnd_alarms">No molestar - Solo alarmas</string>
+    <string name="pref_device_action_dnd_priority">No molestar - S贸lo con prioridad</string>
+    <string name="not_set">No fijado</string>
+    <string name="pref_device_action_dnd_off">No molestar - Desactivado</string>
+    <string name="pref_title_goal_secondary">Objetivo secundario</string>
+    <string name="standing_time">tiempo de reposo</string>
+    <string name="active_time">Tiempo activo</string>
+    <string name="pref_vitality_score_daily_title">Avances diarios</string>
+    <string name="pref_vitality_score_7_day_title">Progreso en 7 d铆as</string>
+    <string name="pref_vitality_score_7_day_summary">Recibir una notificaci贸n cuando su puntuaci贸n de vitalidad alcance 30, 60 o 100 en los 煤ltimos 7 d铆as</string>
+    <string name="pref_vitality_score_title">Nivel de vitalidad</string>
+    <string name="pref_vitality_score_daily_summary">Recibir una notificaci贸n cuando alcances el n煤mero m谩ximo de puntos de vitalidad del d铆a</string>
+    <string name="prefs_phone_silent_mode">Modo silencio del tel茅fono</string>
+    <string name="prefs_wearmode">Modo de uso</string>
+    <string name="wearmode_pebble">Pebble (hebilla de zapato)</string>
+    <string name="silent_mode_normal_vibrate">Normal / Vibraci贸n</string>
+    <string name="silent_mode_vibrate_silent">Vibrar / Silencio</string>
+    <string name="devicetype_redmi_smart_band_2">Redmi Smart Band 2</string>
+    <string name="wearmode_band">Pulsera (Pulsera)</string>
+    <string name="wearmode_necklace">Collar (tira para el cuello)</string>
+    <string name="silent_mode_normal_silent">Normal / Silencio</string>
+    <string name="activity_type_trail_run">Carrera de monta帽a</string>
+    <string name="widget_layout">Dise帽o del widget</string>
+    <string name="widget_screen_no_free_slots_description">El dispositivo no tiene espacio libre para pantallas con widgets (espacio total: %1$s)</string>
+    <string name="widget_unknown_workout">Entrenamiento desconocido - %s</string>
+    <string name="widget_move_up">Subir</string>
+    <string name="widget_screen_delete_confirm_description">驴Seguro que quieres borrar \'%1$s\'?</string>
+    <string name="activity_type_trekking">Senderismo</string>
+    <string name="widget_layout_two">2 widgets</string>
+    <string name="widget_screen_delete_confirm_title">Eliminar la pantalla de widgets</string>
+    <string name="widget_layout_top_1_bot_2">1 superior, 2 inferiores</string>
+    <string name="widget_layout_top_2_bot_2">2 superiores, 2 inferiores</string>
+    <string name="widget_screen_min_screens">Debe haber un m铆nimo de %1$s pantallas</string>
+    <string name="widget">Widget</string>
+    <string name="widget_screen">Pantalla con los widgets</string>
+    <string name="widget_move_down">Bajar</string>
+    <string name="widget_layout_single">1 widget</string>
+    <string name="widget_subtype">Subtipo de widget</string>
+    <string name="widget_screen_x">Pantalla %s</string>
+    <string name="widget_missing_parts">Por favor, seleccione todos los widgets</string>
+    <string name="widget_layout_top_2_bot_1">2 superior, 1 inferior</string>
+    <string name="activity_type_wrestling">Lucha libre</string>
+    <string name="devicetype_colacao23">ColaCao 2023</string>
+    <string name="devicetype_colacao21">ColaCao 2021</string>
+    <string name="clap_hands_to_wakeup_device">Aplaudir para subir la pantalla</string>"
+    <string name="clap_hands_to_wakeup_device_summary">Si vuelves a aplaudir, se apagar谩 la pantalla</string>"
+    <string name="devicetype_mijia_lywsd03">Sensor de temperatura y humedad Mijia 2</string>
+    <string name="pixoo_power_saving_summary">La pantalla se apagar谩 despu茅s de que el micr贸fono haya detectado un silencio durante un tiempo</string>
+    <string name="pref_title_fossil_hr_navigation_instructions">Instrucciones de navegaci贸n</string>
+    <string name="pref_summary_fossil_hr_navigation_instructions">Configurar el comportamiento de la aplicaci贸n de navegaci贸n en el reloj</string>
+    <string name="pref_summary_fossil_hr_nav_foreground">Si la aplicaci贸n de navegaci贸n debe pasar autom谩ticamente a primer plano cuando recibe una instrucci贸n de navegaci贸n</string>
+    <string name="pref_title_fossil_hr_nav_vibrate">Vibrar al recibir una nueva instrucci贸n</string>
+    <string name="pref_title_fossil_hr_nav_foreground">Pasar a primer plano</string>
+    <string name="prefs_device_name">Nombre del dispositivo</string>
+    <string name="pref_summary_fossil_hr_nav_vibrate">Si el reloj debe vibrar con cada instrucci贸n de navegaci贸n nueva o modificada (s贸lo cuando la aplicaci贸n est谩 en primer plano)</string>
+    <string name="devicetype_redmi_watch_2_lite">Redmi Watch 2 Lite</string>
+    <string name="notification_channel_connection_status_name">Estado de la conexi贸n</string>
+    <string name="devicetype_redmi_smart_band_pro">Redmi Smart Band Pro</string>
+    <string name="devicetype_nothingear2">Nothing Ear (2)</string>
+    <string name="devicetype_nothingearstick">Nothing Ear (Stick)</string>
+    <string name="prefs_active_noise_cancelling_transparency">Transparencia</string>
+    <string name="prefs_active_noise_cancelling_light">Cancelaci贸n activa del ruido ligera</string>
+    <string name="pref_do_not_disturb_not_wear">No molestar cuando no lo use</string>
+    <string name="manual">Manual</string>
+    <string name="pref_force_dnd_support">Forzar la funci贸n No molestar</string>
+    <string name="pref_force_dnd_support_summary">Forzar el soporte de No Molestar.
+\nUTIL脥CELO BAJO SU PROPIA RESPONSABILIDAD</string>
+    <string name="huawei_ignore_wakeup_status_start">Ignorar el estado de inicio del despertador</string>
+    <string name="huawei_ignore_wakeup_status_start_description">Puede ayudar a detectar correctamente el sue帽o. Visible inmediatamente en la vista de actividades diarias.</string>
+    <string name="huawei_ignore_wakeup_status_end">Ignorar estado de fin de despertador</string>
+    <string name="prefs_disconnect_notification_summary">Notificaci贸n en el dispositivo cuando se desconecta de BT.</string>
+    <string name="dnd_all_day">Todo el d铆a</string>
+    <string name="devicetype_honor_band3">Honor Band 3</string>
+    <string name="devicetype_honor_band4">Honor Band 4</string>
+    <string name="devicetype_honor_band5">Honor Band 5</string>
+    <string name="devicetype_honor_band6">Honor Band 6</string>
+    <string name="devicetype_honor_band7">Honor Band 7</string>
+    <string name="devicetype_huawei_band6">Huawei Band 6</string>
+    <string name="devicetype_huawei_band7">Huawei Band 7</string>
+    <string name="devicetype_huawei_band8">Huawei Band 8</string>
+    <string name="devicetype_huawei_watch_gt">Huawei Watch GT</string>
+    <string name="devicetype_huawei_band4pro">Huawei Band 4 (Pro)</string>
+    <string name="devicetype_huawei_watchgt2">Huawei Watch GT 2 (Pro)</string>
+    <string name="devicetype_huawei_watchgt2e">Huawei Watch GT 2e</string>
+    <string name="devicetype_huawei_talk_band_b6">Huawei Talk Band B6</string>
+    <string name="devicetype_huawei_watchgt3">Huawei Watch GT 3 (Pro)</string>
+    <string name="prefs_workmode">Modo de trabajo</string>
+    <string name="huawei_alarm_smart_description">No desmarque la casilla de activaci贸n inteligente.</string>
+    <string name="huawei_trusleep_title">HUAWEI TruSleep 鈩�</string>
+    <string name="huawei_trusleep_summary">Monitorice la calidad de su sue帽o y su patr贸n respiratorio en tiempo real.
+\nAnaliza tus patrones de sue帽o y diagnostica con precisi贸n 6 tipos de problemas de sue帽o.</string>
+    <string name="huawei_trusleep_summary_light">Control del sue帽o mejorado</string>
+    <string name="pref_enable_call_accept">Permitir aceptar llamadas</string>
+    <string name="pref_enable_call_accept_summary">Activar la aceptaci贸n de llamadas desde el dispositivo</string>
+    <string name="pref_enable_call_reject">Permitir rechazar llamadas</string>
+    <string name="pref_enable_call_reject_summary">Activar el rechazo de llamadas desde el dispositivo</string>
+    <string name="pref_disable_find_phone_with_dnd">Desactivar encontrar mi tel茅fono cuando no molestar est谩 activo</string>
+    <string name="pref_heartrate_automatic_enable">Activar la medici贸n autom谩tica del ritmo card铆aco</string>
+    <string name="pref_spo_automatic_enable">Activar la medici贸n autom谩tica de SpO2</string>
+    <string name="pref_force_options">Opci贸n obligatoria</string>
+    <string name="pref_force_smart_alarm">Forzar la alarma inteligente</string>
+    <string name="pref_force_wear_location">Lugar de desgaste de la fuerza</string>
+    <string name="pref_force_wear_location_summary">Soporte de localizaci贸n de desgaste forzoso.
+\nUSO BAJO SU PROPIA RESPONSABILIDAD</string>
+    <string name="huawei_reparse_workout_data">Reparar datos de entrenamiento</string>
+    <string name="huawei_reparse_workout_data_description">Esto s贸lo har谩 algo despu茅s de ciertas actualizaciones</string>
+    <string name="pref_summary_debug">Enviar una solicitud de depuraci贸n al dispositivo Huawei</string>
+    <string name="pref_title_debug">Solicitud de depuraci贸n</string>
+    <string name="pref_title_general_reconnectonlytoconnected">Reconectar s贸lo a los dispositivos conectados</string>
+    <string name="pref_summary_general_reconnectonlytoconnected">Reconectarse s贸lo a los dispositivos conectados, en lugar de volver a conectarse a todos los dispositivos</string>
+    <string name="do_not_disturb_lift_wrist_summary">S贸lo si se activa la visualizaci贸n al subir</string>
+    <string name="devicetype_huawei_band_aw70">Huawei Band (AW70)</string>
+    <string name="huawei_alarm_event_description">No marque la casilla de activaci贸n inteligente.</string>
+    <string name="pref_force_options_summary">Algunos dispositivos afirman falsamente no ser compatibles con algunas opciones. Esta configuraci贸n se puede utilizar para habilitarlos de todos modos.
+\nUTIL脥ZALO BAJO TU PROPIA RESPONSABILIDAD
+\nLea la wiki</string>
+    <string name="pref_force_smart_alarm_summary">Forzar el soporte de alarmas inteligentes.
+\nUSO BAJO SU PROPIA RESPONSABILIDAD</string>
+    <string name="huawei_ignore_wakeup_status_end_description">Puede ayudar a detectar correctamente el sue帽o. Visible inmediatamente en la vista de actividades diarias.</string>
+    <string name="updatefirmwareoperation_updateproblem_low_battery">La bater铆a del dispositivo est谩 demasiado baja</string>
+    <string name="menuitem_buzzer_intensity">Intensidad del zumbador</string>
+    <string name="prefs_password_4_digits_0_to_9_summary">La contrase帽a debe tener 4 d铆gitos, utilizando s贸lo n煤meros</string>
+    <string name="devicetype_miband8pro">Xiaomi Smart Band 8 Pro</string>
+    <string name="sony_protocol_v2">Versi贸n 2</string>
+    <string name="pref_force_connection_type_title">Forzar el tipo de conexi贸n</string>
+    <string name="activity_info">Informaci贸n de la actividad</string>
+    <string name="devicetype_mijia_mho_c303">Mijia MHO-C303</string>
+    <string name="sony_protocol_v1">Versi贸n 1</string>
+    <string name="sony_protocol_v3">Versi贸n 3</string>
+    <string name="protocol_version">Versi贸n del protocolo</string>
+    <string name="devicetype_xiaomi_watch_s1">Xiaomi Watch S1</string>
+    <string name="devicetype_xiaomi_watch_s3">Xiaomi Watch S3</string>
+    <string name="devicetype_xiaomi_watch_s1_pro">Xiaomi Watch S1 Pro</string>
+    <string name="uploading_watchface">Subiendo esfera del reloj鈥�</string>
+    <string name="uploadwatchfaceoperation_in_progress">Subiendo esfera del reloj</string>
+    <string name="uploadwatchfaceoperation_complete">Instalaci贸n de la esfera del reloj completada</string>
+    <string name="uploadwatchfaceoperation_failed">La instalaci贸n de la esfera del reloj fall贸</string>
+    <string name="pref_force_connection_type_auto">Autom谩tico</string>
+    <string name="pref_force_connection_type_ble">Bluetooth de baja energ铆a</string>
+    <string name="pref_force_connection_type_bt_classic">Bluetooth</string>
+    <string name="pref_force_connection_type_description">Puedes intentar forzar el tipo de conexi贸n en caso de que tu dispositivo no responda a Gadgetbridge</string>
+    <string name="flatDistance">Ruta llana</string>
+    <string name="devicetype_sony_wi_sp600n">Sony WI-SP600N</string>
+    <string name="heartrate_bpm_155">155 bpm</string>
+    <string name="heartrate_bpm_165">165 bpm</string>
+    <string name="heartrate_bpm_175">175 lpm</string>
+    <string name="heartrate_bpm_185">185 lpm</string>
+    <string name="heartrate_bpm_195">195 lpm</string>
+    <string name="prefs_heartrate_alert_active_high_threshold">Umbral de alerta de la frecuencia cardiaca alta</string>
+    <string name="activity_type_mountain_hike">Excursi贸n por la monta帽a</string>
+    <string name="activity_type_cross_trainer">Bicicleta est谩tica</string>
+    <string name="activity_type_free_training">Formaci贸n gratuita</string>
+    <string name="activity_type_rower">Remo</string>
+    <string name="activity_type_dynamic_cycle">Ciclo din谩mico</string>
+    <string name="activity_type_fitness_exercises">Ejercicios de fitness</string>
+    <string name="activity_type_crossfit">Crossfit</string>
+    <string name="activity_type_functional_training">Entrenamiento funcional</string>
+    <string name="activity_type_physical_training">Entrenamiento f铆sico</string>
+    <string name="activity_type_taekwondo">Taekwondo</string>
+    <string name="activity_type_cross_country_running">Carreras de campo a trav茅s</string>
+    <string name="activity_type_karate">Karate</string>
+    <string name="activity_type_fencing">Esgrima</string>
+    <string name="activity_type_kendo">Kendo</string>
+    <string name="activity_type_parallel_bar">Barras paralelas</string>
+    <string name="activity_type_cooldown">Enfriamiento</string>
+    <string name="activity_type_cross_training">Entrenamiento cruzado</string>
+    <string name="activity_type_fitness_gaming">Juegos de fitness</string>
+    <string name="activity_type_aerobic_exercise">Ejercicios aer贸bicos</string>
+    <string name="activity_type_rolling">Rolling</string>
+    <string name="activity_type_flexibility">Flexibilidad</string>
+    <string name="activity_type_smith_machine">M谩quina de Smith</string>
+    <string name="activity_type_pull_ups">Dominadas</string>
+    <string name="activity_type_plank">Plank</string>
+    <string name="activity_type_javelin">Jabalina</string>
+    <string name="activity_type_long_jump">Salto de longitud</string>
+    <string name="activity_type_high_jump">Salto de altura</string>
+    <string name="activity_type_trampoline">Trampol铆n</string>
+    <string name="activity_type_belly_dance">Danza del vientre</string>
+    <string name="activity_type_jazz_dance">Baile de jazz</string>
+    <string name="activity_type_latin_dance">Baile Latino</string>
+    <string name="activity_type_roller_skating">Patinar con patines</string>
+    <string name="activity_type_martial_arts">Artes marciales</string>
+    <string name="activity_type_tai_chi">Tai chi</string>
+    <string name="activity_type_hula_hooping">Hula hoop</string>
+    <string name="activity_type_disc_sports">Disco volador</string>
+    <string name="activity_type_darts">Dardos</string>
+    <string name="activity_type_archery">Tiro con arco</string>
+    <string name="activity_type_horse_riding">Equitaci贸n</string>
+    <string name="activity_type_kite_flying">Cometas</string>
+    <string name="activity_type_swing">Balanceo</string>
+    <string name="activity_type_stairs">Escaleras</string>
+    <string name="activity_type_fishing">Pesca</string>
+    <string name="activity_type_hand_cycling">Ciclismo manual</string>
+    <string name="activity_type_mind_and_body">Cuerpo y mente</string>
+    <string name="activity_type_kabaddi">Kabaddi</string>
+    <string name="activity_type_karting">Karting</string>
+    <string name="activity_type_billiards">Billares</string>
+    <string name="activity_type_shuttlecock">Pluma</string>
+    <string name="activity_type_softball">S贸ftbol</string>
+    <string name="activity_type_australian_football">F煤tbol australiano</string>
+    <string name="activity_type_pickleball">Pickleball</string>
+    <string name="activity_type_lacross">Lacr贸s</string>
+    <string name="activity_type_shot">Tiro</string>
+    <string name="activity_type_sailing">Navegaci贸n</string>
+    <string name="activity_type_jet_skiing">Moto de agua</string>
+    <string name="activity_type_skating">Skating</string>
+    <string name="activity_type_ice_hockey">Hockey sobre hielo</string>
+    <string name="activity_type_curling">Curling</string>
+    <string name="activity_type_cross_country_skiing">Esqu铆 de fondo</string>
+    <string name="activity_type_snow_sports">Deportes de invierno</string>
+    <string name="activity_type_skateboarding">Skate</string>
+    <string name="activity_type_rock_climbing">Roc贸dromo</string>
+    <string name="warning_missing_notification_permission">No se ha podido publicar la notificaci贸n en curso porque faltaba el permiso</string>
+    <string name="heartrate_bpm_205">205 lpm</string>
+    <string name="hydration_dnd_summary">Desactivar los avisos de hidrataci贸n durante un intervalo de tiempo</string>
+    <string name="activity_type_indoor_running">Correr en pista cubierta</string>
+    <string name="activity_type_stair_stepper">stepper</string>
+    <string name="activity_type_horizontal_bar">Barra horizontal</string>
+    <string name="activity_type_sit_ups">Abdominales</string>
+    <string name="activity_type_track_and_field">Atletismo</string>
+    <string name="activity_type_push_ups">Flexiones</string>
+    <string name="activity_type_battle_rope">Cuerda de batalla</string>
+    <string name="activity_type_dumbbell">Mancuerna</string>
+    <string name="activity_type_ballet">Ballet</string>
+    <string name="activity_type_other_dance">Otros bailes</string>
+    <string name="activity_type_dodgeball">Bal贸n prisionero</string>
+    <string name="activity_type_hunting">Cacer铆a</string>
+    <string name="activity_type_outdoor_walking">Senderismo al aire libre</string>
+    <string name="devicetype_honor_magicwatch2">Honor MagicWatch 2</string>
+    <string name="devicetype_redmi_watch_2">Redmi Watch 2</string>
+    <string name="devicetype_nothing_cmf_watch_pro">CMF Watch Pro</string>
+    <string name="devicetype_huawei_watchfit">Huawei Watch Fit</string>
+    <string name="pref_test_features_title">Caracter铆sticas</string>
+    <string name="pref_test_features_summary">Funciones activadas para este dispositivo de prueba</string>
+    <string name="devicetype_redmiwatch3">Redmi Watch 3</string>
+    <string name="alarm_smart_wakeup_interval_default">5 minutos</string>
+    <string name="alarm_smart_wakeup_interval">Intervalo de activaci贸n inteligente:</string>
+    <string name="devicetype_miband8active">Xiaomi Smart Band 8 Active</string>
+    <string name="device_state_waiting_scan">Esperando por el escaneo del dispositivo</string>
+    <string name="auto_reconnect_ble_scan_title">Reconexi贸n por escaneado BLE</string>
+    <string name="auto_reconnect_ble_scan_summary">Esperar a la exploraci贸n del dispositivo en lugar de intentos ciegos de conexi贸n</string>
+    <string name="prompt_restart_gadgetbridge">Por favor, reinicie GB para que surta efecto.</string>
+    <string name="waiting_for_bluetooth">Esperando al bluetooth鈥�</string>
+    <string name="error_scan_failed">Fallo en el escaneo: %d</string>
+    <string name="scan_not_scanning">Sin escanear</string>
+    <string name="scan_scanning_all_devices">Escanear todos los dispositivos</string>
+    <string name="scan_scanning_multiple_devices">Escaneando %d dispositivos</string>
+    <string name="scan_scanning_single_device">Escaneando dispositivo</string>
+    <string name="notification_channel_scan_service_name">Servicio de escaneado</string>
+    <string name="temperature">Temperatura</string>
+    <string name="pref_header_sony_sound_control">Control del sonido</string>
+    <string name="devicetype_amazfit_bip3">Amazfit Bip 3</string>
+    <string name="pref_wide_area_tap_summary">Reconocer palpitaciones entre las mejillas y las orejas</string>
+    <string name="devicetype_sony_linkbuds">Sony LinkBuds</string>
+    <string name="pref_wide_area_tap_title">Huella de amplio alcance</string>
+    <string name="pref_adaptive_volume_control_summary">Sube el volumen autom谩ticamente cuando el sonido ambiente sea alto</string>
+    <string name="pref_adaptive_volume_control_title">Control adaptativo del volumen</string>
+    <string name="fw_upgrade_notice_amazfitbip3">Est谩s a punto de instalar el firmware %s en tu Amazfit Bip 3.
+\n
+\nPor favor, aseg煤rate de instalar el archivo .fw, y despu茅s el archivo .res. Tu reloj se reiniciar谩 despu茅s de instalar el archivo .fw.
+\n
+\nNota: No es necesario instalar el archivo .res si es exactamente el mismo que el instalado anteriormente.
+\n
+\n隆PROCEDA BAJO SU PROPIO RIESGO!</string>
+    <string name="weekstepschart_steps_a_week_or_month">Pasos a la semana/al mes</string>
+    <string name="weeksleepchart_sleep_a_week_or_month">Horas de sue帽o por semana/mes</string>
+    <string name="devicetype_huawei_watchgt4">Huawei Watch GT 4</string>
+    <string name="devicetype_huawei_watchultimate">Huawei Watch Ultimate</string>
+    <string name="busy_task_fetch_sports_details_interrupted">Se interrumpi贸 la b煤squeda de los detalles deportivos</string>
+    <string name="unbind_before_pair_message">Este dispositivo ya est谩 vinculado en los ajustes de Android, lo que puede hacer que el emparejamiento falle en algunos dispositivos.
+\n
+\nSi no se puede a帽adir el dispositivo, elim铆nelo de los ajustes de Android y vuelva a intentarlo.</string>
+    <string name="companion_pairing_request_title">Dispositivo complementario</string>
+    <string name="companion_pairing_request_description">驴Empareje este dispositivo como complementario?
+\n
+\nSe recomienda para algunas funciones, como buscar dispositivo, y proporciona una mejor conexi贸n.</string>
+    <string name="unbind_before_pair_title">Ya vinculado</string>
+    <string name="devicesetting_scannable_rssi">Umbral m铆nimo de RSSI</string>
+    <string name="devicesetting_scannable_minimum_unseen_summary">Despu茅s de ser escaneado, el dispositivo debe permanecer oculto durante este tiempo antes de ser registrado de nuevo</string>
+    <string name="devicesetting_scannable_rssi_summary">El umbral RSSI m铆nimo para la detecci贸n</string>
+    <string name="devicesetting_scannable_debounce">Tiempo de espera para volver a escanear (segundos)</string>
+    <string name="devicetype_scannable">Dispositivo escaneable</string>
+    <string name="state_scanned">Escaneado</string>
+    <string name="devicesetting_scannable_debounce_summary">Despu茅s de ser escaneado, el dispositivo permanecer谩 como escaneado e ignorado durante el tiempo especificado</string>
+    <string name="devicesetting_scannable_minimum_unseen">Tiempo m铆nimo sin ser escaneado (segundos)</string>
+    <string name="swolfAvg">Promedio swolf</string>
+    <string name="swolfMax">Swolf m谩ximo</string>
+    <string name="swolfMin">Swolf m铆nimo</string>
+    <string name="stepRateSum">Velocidad total de pasos</string>
+    <string name="stepRateAvg">Frecuencia media del paso</string>
+    <string name="stepLengthAvg">Longitud media de los pasos</string>
+    <string name="groundContactTimeAvg">Tiempo medio de la pisada</string>
+    <string name="impactAvg">Promedio de la pisada</string>
+    <string name="impactMax">Pisada m谩xima</string>
+    <string name="swingAngleAvg">脕ngulo promedio de giro</string>
+    <string name="foreFootLandings">Pisadas con el antepi茅</string>
+    <string name="midFootLandings">Pisadas con el mediopi茅</string>
+    <string name="backFootLandings">Pisadas con el retropi茅</string>
+    <string name="eversionAngleAvg">Angulo medio de la eversi贸n</string>
+    <string name="eversionAngleMax">Angulo m谩ximo de la eversi贸n</string>
+    <string name="fmtPaceDistance">Ritmo %d distancia</string>
+    <string name="fmtPaceType">Tipo %d ritmo</string>
+    <string name="fmtPacePace">Ritmo %d</string>
+    <string name="fmtPaceCorrection">Correcci贸n del ritmo %d</string>
+    <string name="fmtPaceTypeAverage">Tipo de ritmo %d medio</string>
+    <string name="unknownDataEncountered">Se encontraron datos desconocidos</string>
+    <string name="milliseconds">milisegundos</string>
+    <string name="degrees">grados</string>
+    <string name="RunningForm">Forma de ejecuci贸n</string>
+    <string name="Pace">Ritmo</string>
+    <string name="pref_force_enable_heartrate_support">Forzar soporte de frecuencia card铆aca</string>
+    <string name="pref_force_enable_heartrate_support_summary">Forzar habilitar el soporte de frecuencia card铆aca.
+\n脷SELO BAJO SU PROPIO RIESGO</string>
+    <string name="pref_force_enable_spo2_support">Forzar el soporte de SpO2</string>
+    <string name="pref_force_enable_spo2_support_summary">Forzar la habilitaci贸n del soporte de SpO2.
+\n脷SELO BAJO SU PROPIO RIESGO</string>
+    <string name="error_showing_changelog">Error al mostrar el registro de cambios</string>
+    <string name="dashboard_settings">Ajustes del panel de control</string>
+    <string name="bottom_nav_dashboard">Panel de control</string>
+    <string name="bottom_nav_devices">Dispositivos</string>
+    <string name="pref_dashboard_first_title">Mostrar primero el panel de control</string>
+    <string name="pref_dashboard_first_summary">Mostrar el panel de control cuando se inicia Gadgetbridge, en lugar de la pantalla de dispositivos</string>
+    <string name="pref_dashboard_cards_title">Mostrar widgets en las tarjetas</string>
+    <string name="pref_dashboard_cards_summary">Dibujar tarjetas alrededor de los widgets del panel de control</string>
+    <string name="pref_dashboard_widget_settings">Ajustes del widget</string>
+    <string name="pref_dashboard_widget_today_title">Gr谩fico de actividad</string>
+    <string name="pref_dashboard_widget_today_24h_title">Modo 24h</string>
+    <string name="pref_dashboard_widget_double_size_title">Tama帽o doble</string>
+    <string name="pref_dashboard_widget_show_legend_title">Mostrar leyenda</string>
+    <string name="pref_dashboard_widget_goals_chart_title">Gr谩fico de objetivos</string>
+    <string name="pref_dashboard_devices_to_include">Dispositivos a incluir</string>
+    <string name="pref_dashboard_all_devices_title">Todos los dispositivos</string>
+    <string name="pref_dashboard_select_devices_title">Selecciona los dispositivos...</string>
+    <string name="pref_dashboard_widgets_order_summary">Seleccione qu茅 widgets est谩n activados y en qu茅 orden se muestran en el panel de control</string>
+    <string name="pref_dashboard_widget_today_24h_summary">Mostrar la actividad en un 煤nico c铆rculo de 24h en lugar de un c铆rculo doble de 12h</string>
+    <string name="pref_dashboard_widget_double_size_summary">Permitir que el widget ocupe dos columnas en el panel de control</string>
+    <string name="pref_dashboard_widget_show_legend_summary">Mostrar una leyenda debajo del widget explicando los colores</string>
+    <string name="pref_dashboard_all_devices_summary">Combinar los datos de la actividad de todos los dispositivos a帽adidos para los totales del panel de control</string>
+    <string name="pref_dashboard_select_devices_summary">Combinar los datos de actividad de dispositivos espec铆ficos para los totales del panel de control</string>
+    <string name="pref_dashboard_widget_today_hr_interval_title">Intervalo de frecuencia card铆aca</string>
+    <string name="activity_type_worn">Usada</string>
+    <string name="pref_dashboard_widget_today_hr_interval_summary">La cantidad de minutos que el gr谩fico muestra \"usado\" despu茅s de cada medici贸n correcta de la frecuencia card铆aca</string>
+    <string name="pref_auto_reply_calls_summary">El tel茅fono descolgar谩 autom谩ticamente las llamadas entrantes</string>
+    <string name="pref_auto_reply_calls_delay_summary">N煤mero de segundos tras los cuales se descuelga autom谩ticamente la llamada</string>
+    <string name="pref_auto_reply_calls_title">Responder autom谩ticamente a las llamadas</string>
+    <string name="pref_auto_reply_calls_delay_title">Retraso autom谩tico en la respuesta</string>
+    <string name="pref_header_audio">Audio</string>
+    <string name="pref_header_generic">Gen茅rico</string>
+    <string name="pref_summary_notification_wake_on_open">Despierta y desbloquea el dispositivo Android cuando el gadget env铆a una respuesta ABIERTO de vuelta. Debe estar en un estado de confianza.</string>
+    <string name="pref_title_notification_wake_on_open">Despertador y desbloqueo autom谩ticos</string>
+    <string name="armenian">armenio</string>
+    <string name="widget_layout_top_wide_bot_large">Ancho arriba, grande abajo</string>
+    <string name="widget_layout_top_large_bot_wide">Grande arriba, ancho abajo</string>
+    <string name="pref_speak_notifications_aloud_summary">Las notificaciones se leer谩n en voz alta a trav茅s de los auriculares</string>
+    <string name="pref_speak_notifications_aloud_title">Decir las notificaciones con volumen alto</string>
+    <string name="pref_header_calls_and_notifications">llamadas y notificaciones</string>
+    <string name="pref_title_bottom_navigation_bar">Barra de navegaci贸n inferior</string>
+    <string name="pref_summary_bottom_navigation_bar_off">Cambiar entre pantallas principales solo deslizando el dedo horizontalmente</string>
+    <string name="widget_name_colored_tile">%1$s (mosaico de color)</string>
+    <string name="widget_name_untitled">Widget sin t铆tulo ( %1$s )</string>
+    <string name="pref_summary_bottom_navigation_bar_on">Cambiar entre pantallas principales usando la barra de navegaci贸n o deslizando el dedo horizontalmente</string>
+    <string name="devicetype_redmi_watch_4">Redmi Watch 4</string>
+    <string name="pref_dashboard_widget_today_upside_down_title">Medianoche en el fondo</string>
+    <string name="pref_dashboard_widget_today_upside_down_summary">En el modo 24h, dibuje la medianoche en la parte inferior y el mediod铆a en la parte superior del gr谩fico</string>
+    <string name="sleepasandroid_settings">Sleep As Android</string>
+    <string name="pref_sleepasandroid_enable_summary">Habilitar la integraci贸n de Sleep As Android</string>
+    <string name="pref_sleepasandroid_device_summary">Seleccione el dispositivo como proveedor de datos de Sleep As Android</string>
+    <string name="pref_sleepasandroid_slot_summary">Qu茅 espacio usar al configurar alarmas</string>
+    <string name="pref_sleepasandroid_feat_oximetry">oximetria</string>
+    <string name="pref_sleepasandroid_device_title">Dispositivo del proveedor</string>
+    <string name="pref_sleepasandroid_features_title">Caracter铆sticas</string>
+    <string name="pref_sleepasandroid_features_summary">La compatibilidad var铆a de un dispositivo a otro</string>
+    <string name="pref_sleepasandroid_feat_alarms">Alarmas</string>
+    <string name="alarm_slot_reset">El espacio de la alarma se ha ajustado por defecto</string>
+    <string name="pref_sleepasandroid_slot_title">Espacio de alarmas</string>
+    <string name="pref_sleepasandroid_feat_notifications">Notificaciones</string>
+    <string name="pref_sleepasandroid_feat_movement">Aceler贸metro</string>
+    <string name="pref_sleepasandroid_feat_heartrate">Frecuencia card铆aca</string>
+    <string name="pref_sleepasandroid_feat_spo2">SPO2</string>
+    <string name="pref_title_huawei_account">Cuenta Huawei</string>
+    <string name="pref_summary_huawei_account">Cuenta de Huawei utilizada en el proceso de emparejamiento. Configurarlo permite el emparejamiento sin restablecimiento de f谩brica.</string>
+    <string name="serbian">Serbio</string>
+    <string name="watchface_resolution_doesnt_match">La resoluci贸n de la esfera del reloj no coincide con la pantalla del dispositivo. La esfera del reloj es de %1$s y la pantalla del dispositivo est谩 en %2$s</string>
+    <string name="cyclingPowerMin">Potencia m铆nima del ciclo</string>
+    <string name="devicetype_huawei_watchfit2">Huawei Watch Fit 2</string>
+    <string name="cyclingPowerAverage">Potencia media de ciclo</string>
+    <string name="cyclingPowerMax">Potencia m谩xima del ciclo</string>
+    <string name="huawei_trusleep_warning">Advertencia: si habilitas esta opci贸n, todo el sue帽o aparecer谩 como sue帽o ligero en Gadgetbridge. Haz clic aqu铆 si aceptas esta opci贸n.</string>
+    <string name="pref_touch_tone">Tono t谩ctil</string>
+    <string name="pref_wearing_tone">Tono de uso</string>
+    <string name="pref_wearing_tone_summary">Reproduce un tono cuando se inserta el auricular</string>
+    <string name="pref_touch_tone_summary">Reproduce un tono cuando se toca el auricular</string>
+    <string name="devicetype_soundcore_liberty3_pro">Soundcore Liberty 3 Pro</string>
+    <string name="pref_adaptive_noise_cancelling_title">ANC adaptativo</string>
+    <string name="pref_adaptive_noise_cancelling_summary">Configure la intensidad del ANC autom谩ticamente seg煤n el nivel del sonido ambiental</string>
+    <string name="devicetype_huawei_band9">Huawei Band 9</string>
+    <string name="devicetype_huawei_watchfit3">Huawei Watch Fit 3</string>
+    <string name="devicetype_huawei_watch4pro">Huawei Watch 4 (Pro)</string>
+    <string name="preferences_qhybrid_settings_summary">Ajustes heredados para los relojes Q Hybrid</string>
+    <string name="device_name_bicycle_sensor">Sensor para bicicletas</string>
+    <string name="devicetype_cycling_sensor">Sensor de velocidad de la bicicleta</string>
+    <string name="title_cycling">Ciclo indoor</string>
+    <string name="pref_summary_wheel_diameter">Di谩metro de la rueda en pulgadas. Normalmente 29, 27,5 o 26.</string>
+    <string name="pref_title_wheel_diameter">Di谩metro de la rueda</string>
+    <string name="pref_title_cycling_persistence_interval">Ecualizador de intervalos</string>
+    <string name="chart_cycling_point_label_distance">Hoy: %.1f km
+\nTotal: %.1f km</string>
+    <string name="chart_cycling_point_label_speed">%.1f km/h</string>
+    <string name="device_name_cycling_sensor">Sensor de bicicleta</string>
+    <string name="pref_summary_cycling_persistence_interval">Intervalo en segundos en el que los datos de ciclismo actuales deben escribirse en la base de datos</string>
+    <string name="toast_setting_requires_reconnect">Este ajuste tendr谩 efecto despu茅s de reconectar</string>
+    <string name="toast_camera_permission_required">Se necesita permiso de c谩mara para esta funci贸n.</string>
+    <string name="toast_camera_support_required">Para esta funci贸n se requiere soporte de c谩mara.</string>
+    <string name="toast_camera_photo_taken">La foto ha sido tomada y guardada en: %s</string>
+    <string name="open_camera">Abrir c谩mara</string>
+    <string name="pref_speak_notifications_focus_exclusive_title">Pausar el audio de otras aplicaciones</string>
+    <string name="pref_speak_notifications_focus_exclusive_summary_on">La reproducci贸n de otras aplicaciones se pausar谩 mientras se escucha la notificaci贸n</string>
+    <string name="pref_speak_notifications_focus_exclusive_summary_off">El volumen de reproducci贸n de otras aplicaciones bajar谩 mientras se escucha la notificaci贸n</string>
+    <string name="devicetype_amazfit_bip5_unity">Amazfit Bip 5 Unity</string>
+    <string name="pref_agps_status">Estado del AGPS</string>
+    <string name="agps_status_missing">Falta</string>
+    <string name="agps_status_current">Actual</string>
+    <string name="agps_status_error">Error</string>
+    <string name="devicetype_garmin_vivomove_style">Garmin V铆vomove Style</string>
+    <string name="devicetype_garmin_instinct_2_solar">Garmin Instinct 2 Solar</string>
+    <string name="devicetype_garmin_instinct_2_soltac">Garmin Instinct 2 SolTac</string>
+    <string name="devicetype_garmin_instinct_crossover">Garmin Instinct Crossover</string>
+    <string name="devicetype_garmin_forerunner_245">Garmin Forerunner 245</string>
+    <string name="devicetype_garmin_swim_2">Garmin Swim 2</string>
+    <string name="devicetype_garmin_vivoactive_4">Garmin V铆voactive 4</string>
+    <string name="devicetype_garmin_vivoactive_4s">Garmin V铆voactive 4S</string>
+    <string name="pref_title_garmin_default_reply_suffix">Utilizar un sufijo de respuesta predefinido</string>
+    <string name="none">Ninguna</string>
+    <string name="garmin_agps_url_i">URL del AGPS %1$d</string>
+    <string name="folder">Carpeta</string>
+    <string name="url">URL</string>
+    <string name="garmin_agps_local_file">Archivo local</string>
+    <string name="copied_to_clipboard">Copiado al portapapeles</string>
+    <string name="agps_status_pending">Pendiente</string>
+    <string name="devicetype_garmin_vivomove_hr">Garmin V铆vomove HR</string>
+    <string name="devicetype_garmin_venu_3">Garmin Venu 3</string>
+    <string name="devicetype_garmin_instinct_2s">Garmin Instinct 2S</string>
+    <string name="devicetype_garmin_instinct_solar">Garmin Instinct Solar</string>
+    <string name="devicetype_garmin_vivoactive_5">Garmin V铆voactive 5</string>
+    <string name="pref_summary_garmin_default_reply_suffix">Se a帽ade adem谩s del sufijo establecido en Gadgetbridge</string>
+    <string name="no_folder_selected">Ninguna carpeta seleccionada</string>
+    <string name="folder_is_empty">Carpeta vac铆a</string>
+    <string name="pref_garmin_agps_help">La siguiente lista contiene todas las URL solicitadas por el reloj para actualizaciones AGPS. Puede seleccionar un archivo del almacenamiento del tel茅fono que se enviar谩 al reloj cuando solicite una actualizaci贸n.</string>
+    <string name="devicetype_garmin_venu_2_plus">Garmin Venu 2 Plus</string>
+    <string name="devicetype_garmin_vivosmart_5">Garmin V铆vosmart 5</string>
+    <string name="devicetype_garmin_instinct_2s_solar">Garmin Instinct 2S Solar</string>
+    <string name="devicetype_garmin_epix_pro">Garmin Epix Pro</string>
+    <string name="devicetype_garmin_instinct_2x_solar">Garmin Instinct 2X Solar</string>
+    <string name="intent_api_broadcast_activity_sync_summary">Enviar una notificaci贸n cuando se complete la sincronizaci贸n de la actividad para cualquier dispositivo</string>
+    <string name="intent_api_broadcast_activity_sync_title">Transferir cuando finalice la sincronizaci贸n de actividades</string>
+    <string name="devicetype_garmin_fenix_6_sapphire">Garmin Fenix 6 Sapphire</string>
+    <string name="activity_prefs_height_inches">Altura en pulgadas</string>
+    <string name="loading">Cargando鈥�</string>
+    <string name="toggle_debug_mode">Conmutar el modo de depuraci贸n</string>
+    <string name="share_debug_info">Compartir la informaci贸n de depuraci贸n</string>
+    <string name="realtime_settings">Ajustes en tiempo real</string>
+    <string name="unsupported">No compatible</string>
+    <string name="max_val">M谩ximo: %d</string>
+    <string name="min_val">M铆nimo: %d</string>
+    <string name="devicetype_garmin_venu_3s">Garmin Venu 3S</string>
+    <string name="devicetype_garmin_fenix_7_pro">Garmin Fenix 7 Pro</string>
+    <string name="battery_low_threshold">Umbral de bater铆a baja</string>
+    <string name="default_percentage">Por defecto (%1$d%%)</string>
+    <string name="notif_battery_full_title">隆Bater铆a del dispositivo llena!</string>
+    <string name="battery_i">Bater铆a %d</string>
+    <string name="battery_percentage_str">%1$s%%</string>
+    <string name="battery_low_notify_enabled">Aviso de bater铆a baja</string>
+    <string name="notification_channel_full_battery_name">Bater铆a llena</string>
+    <string name="notif_battery_full">%1$s bater铆a llena</string>
+    <string name="show_in_notification">Mostrar en la notificaci贸n</string>
+    <string name="battery_full_notify_enabled">Notificar si la bater铆a est谩 llena</string>
+    <string name="battery_full_threshold">Umbral de bater铆a llena</string>
+    <string name="devicetype_garmin_fenix_7s">Garmin Fenix 7S</string>
+    <string name="authentication_failed_check_key">Error de autenticaci贸n, verifique la clave de autenticaci贸n</string>
+    <string name="devicetype_garmin_forerunner_255">Garmin Forerunner 255</string>
+    <string name="dashboard_calendar_month_goals_reached_title">% del objetivo de paso alcanzado</string>
+    <plurals name="amount_of_days">
+        <item quantity="one">%d d铆a</item>
+        <item quantity="many">%d d铆as</item>
+        <item quantity="other">%d d铆as</item>
+    </plurals>
+    <string name="pref_fetch_unknown_files_title">Recuperar archivos desconocidos</string>
+    <string name="pref_fetch_unknown_files_summary">Obtiene archivos de actividad desconocidos del reloj. No se procesan sino que se almacenan en el tel茅fono.</string>
+    <string name="devicetype_garmin_forerunner_265">Garmin Forerunner 265</string>
+    <string name="devicetype_garmin_venu_2">Garmin Venu 2</string>
+    <string name="devicetype_hama_fit6900">Hama Fit6900</string>
+    <string name="devicetype_garmin_forerunner_255s">Garmin Forerunner 255S</string>
+    <string name="devicetype_garmin_instinct">Garmin Instinct</string>
+    <string name="devicetype_moondrop_space_travel">Moondrop Space Travel</string>
+    <string name="moondrop_equalizer_preset_reference">Referencia</string>
+    <string name="moondrop_equalizer_preset_monitor">Monitor</string>
+    <string name="moondrop_touch_trigger">Desencadenar</string>
+    <string name="moondrop_touch_earbud">Auricular</string>
+    <string name="moondrop_touch_earbud_both">Ambos</string>
+    <string name="moondrop_touch_action_play_pause">Reproducir/Pausa</string>
+    <string name="moondrop_touch_action_call_start">Llamar</string>
+    <string name="moondrop_touch_action_call_pick_hang">Responder/colgar la llamada</string>
+    <string name="moondrop_touch_action_assistant">Activar asistente de voz</string>
+    <string name="moondrop_touch_action_anc_mode">Cambiar el modo de cancelaci贸n activa de ruido</string>
+    <string name="moondrop_touch_trigger_long_press_1s">Pulsaci贸n larga (1s)</string>
+    <string name="moondrop_touch_trigger_long_press_3s">Pulsaci贸n larga (3s)</string>
+    <string name="devicetype_garmin_fenix_6">Garmin Fenix 6</string>
+    <string name="moondrop_equalizer_preset_basshead">Basshead</string>
+    <string name="devicetype_garmin_fenix_5">Garmin Fenix 5</string>
+    <string name="devicetype_garmin_vivoactive_3">Garmin V铆voactive 3</string>
+    <string name="devicetype_garmin_vivomove_trend">Garmin V铆vomove Trend</string>
+    <string name="proprietary_app_warning">Esta funci贸n requiere la instalaci贸n de una aplicaci贸n propia</string>
+    <string name="pref_battery_polling_configuration">Configuraci贸n de sondeo de la bater铆a</string>
+    <string name="pref_battery_polling_enable">Activar sondeo de bater铆a</string>
+    <string name="pref_battery_polling_summary">Se trata del mejor esfuerzo, y podr铆a retrasarse por varias razones</string>
+    <string name="pref_battery_polling_interval">Intervalo de sondeo de la bater铆a</string>
+    <string name="pref_battery_polling_interval_format">cada %1$s minutos</string>
+    <string name="battery_polling_failed_start">No se ha iniciado el sondeo de la bater铆a</string>
+    <string name="devicetype_garmin_vivosport">Garmin V铆vosport</string>
+    <string name="devicetype_nothing_cmf_watch_pro_2">CMF Watch Pro 2</string>
+    <string name="activity_type_gateball">Pelota</string>
+    <string name="activity_type_parkour">Parkour</string>
+    <string name="activity_type_beach_soccer">Futbol playa</string>
+    <string name="activity_type_tae_bo">Tae Bo</string>
+    <string name="activity_type_beach_volleyball">V贸ley playa</string>
+    <string name="activity_type_sepak_takraw">Sepak Takraw</string>
+    <string name="activity_type_luge">Trineo</string>
+    <string name="activity_type_auto_racing">Automovilismo</string>
+    <string name="activity_type_parachuting">Paracaidismo</string>
+    <string name="pref_title_notification_times_enabled">Horarios de notificaci贸n</string>
+    <string name="devicetype_huawei_watch3">Huawei Watch 3 (Pro)</string>
+    <string name="devicetype_vivitar_hr_bp_monitor_activity_tracker">Vivitar HR &amp; BP Monitor Activity Tracker</string>
+    <string name="devicetype_nothing_cmf_buds_pro_2">CMF Buds Pro 2</string>
+    <string name="pref_summary_notification_times_enabled">Enviar notificaciones solo entre horas espec铆ficas</string>
+    <string name="devicetype_garmin_forerunner_255s_music">Garmin Forerunner 255S Music</string>
+    <string name="devicetype_garmin_forerunner_255_music">Garmin Forerunner 255 Music</string>
+    <string name="devicetype_huawei_watchgtrunner">Huawei Watch GT Runner</string>
+    <string name="find_my_phone_companion_warning">Para encontrar el tel茅fono, es necesario emparejar el dispositivo con el compa帽ero. Haz clic aqu铆 para obtener m谩s informaci贸n.</string>
+    <string name="sleep_colored_stats_rem">REM</string>
+    <string name="stats_empty_value">-</string>
+    <string name="stats_lowest_hr">frecuencia card铆aca m谩s baja</string>
+    <string name="hrv_status_unbalanced">Desequilibrado</string>
+    <string name="hrv_status_low">Bajo</string>
+    <string name="hrv_status_poor">Pobre</string>
+    <string name="hrv_status_last_night">Anoche</string>
+    <string name="hrv_status_seven_days_avg_long">Promedio de 7 d铆as</string>
+    <string name="hrv_status_unit">%1$d ms</string>
+    <string name="hrv_status_baseline">%1$d-%2$d ms</string>
+    <string name="hrv_status_baseline_label">Base</string>
+    <string name="hrv_status">Estado de la VFC</string>
+    <string name="sleep_colored_stats_deep">Profundo</string>
+    <string name="stats_highest_hr">Frecuencia card铆aca m谩s alta</string>
+    <string name="hrv_status_day_avg">Promedio diario</string>
+    <string name="sleep_colored_stats_light">Claro</string>
+    <string name="hrv_status_day_avg_legend">Promedio diario (ms)</string>
+    <string name="hrv_status_seven_days_avg">Media de 7 d铆as</string>
+    <string name="pref_header_hrv_status">Estado de la VFC</string>
+    <string name="hrv_status_seven_days_avg_status">Estado</string>
+    <string name="hrv_status_balanced">Equilibrado</string>
+    <string name="sleep_colored_stats_deep_avg">AVG profundo</string>
+    <string name="sleep_colored_stats_light_avg">AVG ligero</string>
+    <string name="sleep_colored_stats_rem_avg">Sue帽o REM promedio</string>
+    <string name="hrv_status_last_night_highest_5">Anoche, media m谩xima de 5 minutos</string>
+    <string name="devicetype_garmin_forerunner_965">Garmin Forerunner 965</string>
+    <string name="devicetype_garmin_fenix_5_plus">Garmin Fenix 5 Plus</string>
+    <string name="abstract_chart_fragment_kind_awake_sleep">Despierto</string>
+    <string name="gps_glonass">GPS + GLONASS</string>
+    <string name="minutes_20">20 minutos</string>
+    <string name="devicetype_soundcore_motion300">Soundcore Motion 300</string>
+    <string name="soundcore_voice_prompts">Indicaciones de voz</string>
+    <string name="soundcore_button_brightness_high">Alto</string>
+    <string name="soundcore_ldac_mode_summary">Habilitar LDAC reducir谩 la duraci贸n de la bater铆a y podr铆a provocar inestabilidad en la conexi贸n</string>
+    <string name="soundcore_adaptive_direction_title">Direcci贸n adaptativa</string>
+    <string name="soundcore_equalizer_preset_signature">Firma de Soundcore</string>
+    <string name="soundcore_equalizer_preset_voice">Voz</string>
+    <string name="soundcore_equalizer_preset_balanced">Equilibrado</string>
+    <string name="soundcore_equalizer_preset_xtra_bass">Extra Bass</string>
+    <string name="soundcore_equalizer_direction">Posici贸n del dispositivo</string>
+    <string name="minutes_60">60 minutos</string>
+    <string name="soundcore_button_brightness">Bot贸n de brillo</string>
+    <string name="soundcore_button_brightness_low">Bajo</string>
+    <string name="soundcore_button_brightness_medium">Medio</string>
+    <string name="soundcore_ldac_mode_title">Modo LDAC</string>
+    <string name="soundcore_adaptive_direction_summary">Ajustar el valor predeterminado del ecualizador autom谩ticamente seg煤n la posici贸n del dispositivo</string>
+    <string name="soundcore_equalizer_preset">Preestablecido</string>
+    <string name="soundcore_equalizer_custom_title">Personalizar鈥�</string>
+    <string name="soundcore_equalizer_custom_summary">Configuraci贸n del ecualizador param茅trico</string>
+    <string name="soundcore_equalizer_direction_standing">Vertical</string>
+    <string name="soundcore_equalizer_direction_lying">Horizontal</string>
+    <string name="soundcore_equalizer_reset_title">Restablecer los valores predeterminados</string>
+    <string name="soundcore_equalizer_band1">Banda 1</string>
+    <string name="soundcore_equalizer_band4">Banda 4</string>
+    <string name="soundcore_equalizer_band5">Banda 5</string>
+    <string name="soundcore_equalizer_band6">Banda 6</string>
+    <string name="soundcore_equalizer_band7">Banda 7</string>
+    <string name="soundcore_equalizer_band8">Banda 8</string>
+    <string name="soundcore_equalizer_direction_hanging">Colgado</string>
+    <string name="soundcore_equalizer_band9">Banda 9</string>
+    <string name="soundcore_equalizer_frequency">Frecuencia</string>
+    <string name="soundcore_equalizer_value">Valor</string>
+    <string name="soundcore_equalizer_reset_summary">Restaura todas las bandas del ecualizador a la configuraci贸n predeterminada</string>
+    <string name="soundcore_equalizer_band3">Banda 3</string>
+    <string name="soundcore_equalizer_band2">Banda 2</string>
+    <string name="body_energy_lost">Perdida</string>
+    <string name="body_energy_legend_level">Nivel de Body Energy</string>
+    <string name="body_energy">Body Energy</string>
+    <string name="body_energy_gained">Ganada</string>
+    <string name="transition">Transici贸n</string>
+    <string name="activity_type_handcycling">Triciclo manual</string>
+    <string name="activity_type_indoor_track">Pista cubierta</string>
+    <string name="activity_type_navigate">Navegar</string>
+    <string name="activity_type_transition">Transici贸n</string>
+    <string name="activity_type_fitness_equipment">Equipo de fitness</string>
+    <string name="activity_type_training">Entrenamiento</string>
+    <string name="activity_type_cardio">Cardio</string>
+    <string name="activity_type_flying">Vuelo</string>
+    <string name="activity_type_motorcycling">Motociclismo</string>
+    <string name="activity_type_hang_gliding">Ala delta</string>
+    <string name="activity_type_water_skiing">Esqu铆 acu谩tico</string>
+    <string name="activity_type_kayaking">Kayak</string>
+    <string name="authentication_failed_negotiation">No se pudo acordar la clave de autenticaci贸n</string>
+    <string name="activity_type_e_bike">Bicicleta el茅ctrica</string>
+    <string name="activity_type_platform_tennis">P谩del</string>
+    <string name="activity_type_american_football">F煤tbol americano</string>
+    <string name="activity_type_breathwork">Breathwork</string>
+    <string name="activity_type_xc_classic_ski">Esqu铆 de fondo</string>
+    <string name="activity_type_mountaineering">Monta帽ismo</string>
+    <string name="activity_type_multisport">Varios deportes</string>
+    <string name="activity_type_paddling">Remar</string>
+    <string name="activity_type_boating">Navegaci贸n</string>
+    <string name="activity_type_driving">Conduciendo</string>
+    <string name="activity_type_inline_skating">Patinaje en l铆nea</string>
+    <string name="activity_type_climb_indoor">Rocodromo</string>
+    <string name="activity_type_bouldering">B煤lder</string>
+    <string name="activity_type_sail_race">Regata</string>
+    <string name="activity_type_sail_expedition">Velero</string>
+    <string name="activity_type_snowshoe">Raquetas de nieve</string>
+    <string name="activity_type_snowmobiling">Motonieve</string>
+    <string name="activity_type_stand_up_paddleboarding">Surf de pala</string>
+    <string name="activity_type_wakeboarding">Wakeboarding</string>
+    <string name="activity_type_rafting">Descenso de r铆os</string>
+    <string name="activity_type_tactical">T谩ctico</string>
+    <string name="activity_type_jumpmaster">Maestro de salto</string>
+    <string name="activity_type_handcycling_indoor">Triciclo de mano interior</string>
+    <string name="activity_type_bike_commute">Desplazarse en bicicleta</string>
+    <string name="activity_type_ice_skating">Patinaje sobre hielo</string>
+    <string name="activity_type_sky_diving">Paracaidismo</string>
+    <string name="activity_type_racquetball">R谩quetbol</string>
+    <string name="activity_type_softball_slow_pitch">lanzamiento lento de softbol</string>
+    <string name="activity_type_shooting">Disparar</string>
+    <string name="activity_type_winter_sport">Deporte de invierno</string>
+    <string name="activity_type_health_snapshot">Panorama de salud</string>
+    <string name="activity_type_marine">Marina</string>
+    <string name="activity_type_video_gaming">Videojuegos</string>
+    <string name="activity_type_racket">Raqueta</string>
+    <string name="activity_type_padel">P谩del</string>
+    <string name="activity_type_para_sport">Para el deporte</string>
+    <string name="activity_type_water_tubing">Tubing</string>
+    <string name="activity_type_artistic_swimming">Nataci贸n art铆stica</string>
+    <string name="activity_type_bmx">BMX</string>
+    <string name="activity_type_board_game">Juego de mesa</string>
+    <string name="activity_type_ballroom_dance">Baile de sal贸n</string>
+    <string name="activity_type_cardio_combat">Body Combat</string>
+    <string name="activity_type_checkers">Damas</string>
+    <string name="activity_type_hula_hoop">Hula hoop</string>
+    <string name="activity_type_muay_thai">Muay thai</string>
+    <string name="activity_type_mixed_martial_arts">Artes marciales mixtas</string>
+    <string name="activity_type_lacrosse">Lacr贸s</string>
+    <string name="activity_type_wakesurfing">Wakesurf</string>
+    <string name="activity_type_bocce">Bochas</string>
+    <string name="activity_type_breaking">Breakdance</string>
+    <string name="activity_type_judo">Judo</string>
+    <string name="hrv">HRV</string>
+    <string name="activity_type_meditation">Meditaci贸n</string>
+    <string name="activity_type_disc_golf">Disc Golf</string>
+    <string name="activity_type_team_sport">Deporte de equipo</string>
+    <string name="activity_type_aerobic_combo">Combinaci贸n aer贸bica</string>
+    <string name="activity_type_aerobics">Aerobic</string>
+    <string name="activity_type_air_walker">Andador de aire</string>
+    <string name="activity_type_bridge">Bridge</string>
+    <string name="activity_type_folk_dance">Danza folcl贸rica</string>
+    <string name="activity_type_frisbee">Frisbi</string>
+    <string name="activity_type_hacky_sack">Hacky sack</string>
+    <string name="activity_type_hip_hop">Hip hop</string>
+    <string name="activity_type_jai_alai">Pelota vasca</string>
+    <string name="activity_type_jujitsu">Jujitsu</string>
+    <string name="activity_type_modern_dance">Danza moderna</string>
+    <string name="activity_type_parallel_bars">Barras paralelas</string>
+    <string name="steps_distance_unit">%1$,.2f km</string>
+    <string name="steps_total">Total de pasos</string>
+    <string name="steps_avg">Promedio de pasos</string>
+    <string name="distance_avg">Distancia promedio</string>
+    <string name="distance_total">Distancia total</string>
+    <string name="activity_type_ultimate_disc">Ultimate Frisbee</string>
+    <string name="activity_type_chess">Ajedrez</string>
+    <string name="activity_type_dragon_boat">Dragon boat</string>
+    <string name="activity_type_flowriding">Flowriding</string>
+    <string name="activity_type_futsal">F煤tbol sala</string>
+    <string name="activity_type_esports">eSports</string>
+    <string name="activity_type_finswimming">Nataci贸n con aletas</string>
+    <string name="insufficient_space_for_upload">Espacio insuficiente para la carga</string>
+    <string name="activity_type_free_sparring">Lucha libre</string>
+    <string name="activity_type_floor_climbing">Escalada</string>
+    <string name="activity_type_shuffleboard">Shuffleboard</string>
+    <string name="activity_type_billiard_pool">Billar</string>
+    <string name="activity_type_canoeing">Pirag眉ismo</string>
+    <string name="activity_type_water_scooter">Scooter subacu谩tico</string>
+    <string name="activity_type_laser_tag">Laser Combat</string>
+    <string name="activity_type_obstacle_race">Carreras de obst谩culos</string>
+    <string name="activity_type_orienteering">Orienteering</string>
+    <string name="activity_type_sledding">Descenso en trineo</string>
+    <string name="activity_type_biathlon">Biatl贸n</string>
+    <string name="activity_type_bungee_jumping">Puenting</string>
+    <string name="devicetype_garmin_venu">Garmin Venu</string>
+    <string name="cannot_upload_watchface_too_many_watchfaces_installed">No se puede cargar la esfera del reloj, hay demasiadas esferas instaladas</string>
+    <string name="activity_type_indoor_push_walk_speed">Impulso en interiores: velocidad de caminata</string>
+    <string name="activity_type_mass_gymnastics">Gimnasia de masas</string>
+    <string name="activity_type_pole_dance">Baile en barra</string>
+    <string name="activity_type_race_walking">Marcha</string>
+    <string name="activity_type_snorkeling">Esn贸rquel</string>
+    <string name="activity_type_somatosensory_game">Juego somatosensorial</string>
+    <string name="activity_type_spinning">Spinning</string>
+    <string name="activity_type_square_dance">Square dance</string>
+    <string name="activity_type_stair_climber">M谩quina para escalar</string>
+    <string name="activity_type_table_football">Futbol铆n</string>
+    <string name="activity_type_tug_of_war">Tira y afloja</string>
+    <string name="activity_type_wall_ball">Lanzamiento de bal贸n medicinal,</string>
+    <string name="activity_type_water_polo">Waterpolo</string>
+    <string name="activity_type_weiqi">Weiqi</string>
+    <string name="activity_type_body_combat">BodyCombat</string>
+    <string name="activity_type_plaza_dancing">Baile de plaza</string>
+    <string name="activity_type_bobsleigh">Bobsled</string>
+    <string name="devicetype_garmin_venu_2s">Garmin Venu 2S</string>
+    <string name="miscale_small_objects_summary">Almacenar el peso de objetos de menos de 10 kg</string>
+    <string name="miscale_weight_unit_chinese">Chino (jin)</string>
+    <string name="miscale_small_objects_title">Objetos peque帽os</string>
+    <string name="devicetype_mismartscale">Mi Smart Scale 2</string>
+    <string name="devicetype_micompositionscale">Mi Body Composition Scale 2</string>
+    <string name="estimatedSweatLoss">Cantidad estimada de sudor</string>
+    <string name="miscale_weight_unit_summary">Establecer la unidad de peso para las mediciones mostradas</string>
+    <string name="miscale_weight_unit_metric">Sistema m茅trico (kg)</string>
+    <string name="miscale_weight_unit_imperial">Imperial (lbs)</string>
+    <string name="miscale_weight_unit_title">Unidad de peso</string>
+    <string name="activity_type_grinding">Rutina</string>
+    <string name="activity_type_push_walk_speed">Impulso: velocidad de caminata</string>
+    <string name="minutes_2">2 minutos</string>
+    <string name="minutes_3">3 minutos</string>
+    <string name="minutes_4">4 minutos</string>
+    <string name="minutes_6">6 minutos</string>
+    <string name="minutes_7">7 minutos</string>
+    <string name="minutes_8">8 minutos</string>
+    <string name="minutes_9">9 minutos</string>
+    <string name="minutes_15">15 minutos</string>
+    <string name="minutes_45">45 minutos</string>
+    <string name="minutes_75">75 minutos</string>
+    <string name="minutes_120">120 minutos</string>
+    <string name="minutes_240">240 minutos</string>
+    <string name="minutes_255">255 minutos</string>
+    <string name="minutes_150">150 minutos</string>
+    <string name="minutes_180">180 minutos</string>
+    <string name="minutes_210">210 minutos</string>
+    <string name="dateformat_day_month">D铆a, Mes</string>
+    <string name="dateformat_month_day">Mes, D铆a</string>
+    <string name="activity_type_push_run_speed">Impulso: velocidad de carrera</string>
+    <string name="activity_type_indoor_push_run_speed">Impulso en interiores: Velocidad de carrera</string>
+    <string name="pref_time_sync">Sincronizaci贸n horaria autom谩tica</string>
+    <string name="user_feedback_set_settings_ok">Configuraci贸n enviada al dispositivo.</string>
+    <string name="minutes_90">90 minutos</string>
+    <string name="prefs_light_duration_longer">Mayor duraci贸n de la iluminaci贸n</string>
+    <string name="pref_app_connection_duration">Duraci贸n de la conexi贸n a la aplicaci贸n</string>
+    <string name="devicetype_colmi_r02">Colmi R02</string>
+    <string name="devicetype_colmi_r03">Colmi R03</string>
+    <string name="devicetype_colmi_r06">Colmi R06</string>
+    <string name="smart_ring_measurement_error_unknown">Error de medici贸n desconocido del anillo: %d</string>
+    <string name="busy_task_fetch_sleep_data">Obteniendo datos del sue帽o</string>
+    <string name="smart_ring_measurement_error_worn_incorrectly">Error de medici贸n. 驴Est谩n orientados correctamente los sensores del anillo?</string>
+    <string name="stress_average">Promedio</string>
+    <string name="devicetype_mijia_xmwsdj04">Sensor de temperatura y humedad Mijia 2 (tinta electr贸nica)</string>
+    <string name="devicetype_honor_watchgs3">Honor Watch GS 3</string>
+    <string name="weight_kg">%1$.2f kg</string>
+    <string name="weight_lbs">%1$.2f lbs</string>
+    <string name="target">Objetivo</string>
+    <string name="menuitem_weight">Peso</string>
+    <string name="activity_type_rem_sleep">Sue帽o REM</string>
+    <string name="sleep_colored_stats_awake_avg">Promedio despierto</string>
+    <string name="lactateThresholdHeartRate">Frecuencia card铆aca con umbral de lactato</string>
+    <string name="recoveryTime">Tiempo de recuperaci贸n</string>
+    <string name="devicetype_garmin_fenix_5x_plus">Garmin Fenix 5X Plus</string>
+    <string name="pref_header_sound">Sonido</string>
+    <string name="workout_set_repetitions_weight_lbs">%1d x %2$.2f lbs</string>
+    <string name="workoutSets">Conjuntos</string>
+    <string name="workout_set_i">Establecer %1d</string>
+    <string name="workout_set_repetitions">%1d x</string>
+    <string name="workout_set_repetitions_weight_kg">%1d x %2$.2f kg</string>
+    <string name="choose_device">Eligir un dispositivo</string>
+    <string name="no_supported_devices_found">No se encontraron dispositivos compatibles</string>
+    <string name="menuitem_stress_simple">Estr茅s (sencillo)</string>
+    <string name="menuitem_stress_breakdown">Estr茅s (crisis nerviosa)</string>
+    <string name="menuitem_stress_segmented">Estr茅s (segmentado)</string>
+    <string name="devicetype_soundcore_liberty4_nc">Soundcore Liberty 4 NC</string>
+    <string name="search">Buscar</string>
+    <string name="pref_deprecated_media_control_title">Control multimedia obsoleto</string>
+    <string name="pref_header_deprecated_functionalities">Funciones obsoletas</string>
+    <string name="pref_header_deprecated_functionalities_warning">Las siguientes funcionalidades han quedado obsoletas y se eliminar谩n pronto del software.
+\nSi necesitas activar alguna de las siguientes opciones, p贸ngate en contacto con el equipo del proyecto.</string>
+    <string name="pref_deprecated_media_control_summary">Enviar comandos de control de medios como eventos clave en lugar del controlador de medios.</string>
+    <string name="pref_title_nagivation_apps">Aplicaciones de navegaci贸n</string>
+    <string name="pref_header_external_integrations">Integraciones externas</string>
+    <string name="pref_header_automations">Automatizaciones</string>
+    <string name="pref_description_general">Inicio, idioma, regi贸n, ubicaci贸n</string>
+    <string name="pref_description_notifications">Notificaciones de aplicaciones, lista blanca/lista negra</string>
+    <string name="pref_description_user_interface">Tema, pantalla principal</string>
+    <string name="pref_description_dashboard">Widgets, dispositivos a incluir</string>
+    <string name="pref_description_developer_options">Registros,intenciones de la api</string>
+    <string name="pref_description_deprecated_functionalities">Configuraciones que se eliminar谩n en una versi贸n futura</string>
+    <string name="pref_header_main_screen">Pantalla principal</string>
+    <string name="pref_title_user_interface">Interfaz de usuario</string>
+    <string name="activity_prefs_date_birth">Fecha de nacimiento</string>
+    <string name="pref_description_automations">Exportaci贸n autom谩tica, recuperaci贸n autom谩tica</string>
+    <string name="pref_description_about_you">Fecha de nacimiento, g茅nero, altura, peso, objetivos</string>
+    <string name="devicetype_garmin_forerunner_165">Garmin Forerunner 165</string>
+    <string name="pref_continuous_skin_temperature_measurement_title">Medici贸n continua de la temperatura de la piel</string>
+    <string name="pref_continuous_skin_temperature_measurement_description">Actualmente no se admite la recuperaci贸n de temperatura. Esta configuraci贸n solo permite la medici贸n continua en el dispositivo</string>
+    <string name="prefs_summary_gatt_client_notification_intents">Recibir cambios caracter铆sticos de BLE a trav茅s de Intents</string>
+    <string name="prefs_title_gatt_client_allow_gatt_interactions">Permitir la interacci贸n de GATT a trav茅s de la API BLE Intent</string>
+    <string name="prefs_summary_gatt_client_device_state_updates">Recibir los cambios de estado de la conexi贸n BLE a trav茅s de Intents</string>
+    <string name="prefs_title_gatt_client_api_package">Paquete de la API BLE</string>
+    <string name="prefs_title_ble_intent_api">API BLE Intent</string>
+    <string name="devicetype_ble_gatt_client">Cliente gen茅rico BLE GATT</string>
+    <string name="prefs_title_gatt_client_notification_intents">Transmitir la intenci贸n de notificaci贸n del GATT a trav茅s de la API interna de Bluetooth Low Energy</string>
+    <string name="prefs_summary_gatt_client_allow_gatt_interactions">Permite enviar comandos de lectura/escritura y conexi贸n caracter铆sticos de BLE</string>
+    <string name="prefs_summary_gatt_client_api_package">Restringir la comunicaci贸n de la API BLE Intent a este paquete</string>
+    <string name="activity_db_management_export_to_zip">Exportar a un .zip</string>
+    <string name="activity_db_management_import_from_zip">Importar desde un zip</string>
+    <string name="backup_restore_exporting">Exportando a un zip鈥�</string>
+    <string name="backup_restore_exporting_preferences">Exportando preferencias鈥�</string>
+    <string name="backup_restore_exporting_database">Exportar base de datos鈥�</string>
+    <string name="backup_restore_exporting_files">Exportar archivos鈥�</string>
+    <string name="backup_restore_exporting_files_i_of_n">Exportando archivos... %1d de %2d</string>
+    <string name="backup_restore_error_import">Error al importar desde el zip</string>
+    <string name="backup_restore_abort_title">Abortar</string>
+    <string name="backup_restore_abort_export_confirmation">驴Abortar la exportaci贸n? Se eliminar谩 el archivo zip parcial.</string>
+    <string name="backup_restore_abort_import_confirmation">驴Abortar la importaci贸n? Esto puede dar lugar a una base de datos corrupta o incoherente.</string>
+    <string name="backup_restore_restart_title">Reiniciar</string>
+    <string name="backup_restore_restart_summary">%1s se reiniciar谩 ahora.</string>
+    <string name="activity_db_management_backup_restore_label">Copia de seguridad y restauraci贸n</string>
+    <string name="activity_db_management_backup_restore_explanation">Las operaciones de importaci贸n/exportaci贸n te permiten migrar o hacer una copia de seguridad de todos los ajustes, dispositivos y datos de Gadgetbridge a y desde un archivo zip.
+\n
+\nAl importar un archivo se eliminar谩n todos los datos, dispositivos y preferencias existentes, sustituy茅ndolos completamente por la copia de seguridad.</string>
+    <string name="backup_restore_exporting_finishing">Terminando la exportaci贸n鈥�</string>
+    <string name="backup_restore_importing_validating">Validando el archivo鈥�</string>
+    <string name="backup_restore_importing">Importando desde un zip鈥�</string>
+    <string name="backup_restore_do_not_exit">%s por favor, mant茅n esta pantalla abierta hasta que finalice la operaci贸n.</string>
+    <string name="backup_restore_importing_loading">Cargando el archivo鈥�</string>
+    <string name="backup_restore_importing_files_i_of_n">Importando archivos... %1d de %2d</string>
+    <string name="backup_restore_export_complete">Exportaci贸n finalizada</string>
+    <string name="backup_restore_importing_database">Importando base de datos鈥�</string>
+    <string name="backup_restore_importing_preferences">Importando ajustes鈥�</string>
+    <string name="backup_restore_warning_files">%1d archivos no pudieron ser restaurados:
+\n%2s</string>
+    <string name="backup_restore_import_complete">Importaci贸n finalizada</string>
+    <string name="backup_restore_error_export">Fall贸 la exportaci贸n a un zip</string>
+    <string name="devicetype_honor_watchgspro">Honor Watch GS Pro</string>
+    <string name="devicetype_amazfit_trex_3">Amazfit T-Rex 3</string>
+    <string name="menuitem_map">Mapa</string>
+    <string name="menuitem_heart_rate_push">Puls贸metro</string>
+    <string name="menuitem_night_display">Pantalla nocturna</string>
+    <string name="label_distance_trip">Viaje: %.1f kil贸metros</string>
+    <string name="label_distance_trip_mph">Viaje: %.1f mi</string>
+    <string name="label_distance_total_mph">Total: %.1f mi</string>
+    <string name="error_no_cycling_sensor_found">Sensor de ciclismo no encontrado</string>
+    <string name="label_distance_total">Total: %.1f km</string>
+    <string name="devicetype_garmin_forerunner_245_music">Garmin Forerunner 245 Music</string>
+    <string name="hr_resting">Descansando</string>
+    <string name="devicetype_sony_wf_c500">Sony WF-C500</string>
+    <string name="hr_maximum">M谩ximo</string>
+    <string name="hr_minimum">M铆nimo</string>
+    <string name="devicetype_garmin_enduro_3">Garmin Enduro 3</string>
+    <string name="bpm_value_unit">%1$d bpm</string>
+    <string name="hr_average">Promedio</string>
+    <string name="devicetype_garmin_forerunner_955">Garmin Forerunner 955</string>
+    <string name="devicetype_garmin_forerunner_265s">Garmin Forerunner 265S</string>
+    <string name="devicetype_redmi_watch_5_active">Redmi Watch 5 Active</string>
+    <string name="devicetype_miband9">Xiaomi Smart Band 9</string>
+    <string name="vo2max_running">VO鈧� M谩x. en carrera</string>
+    <string name="vo2max_cycling">VO鈧� m谩x. en ciclismo</string>
+    <string name="thirty_days_timeline">Cronograma de 30 d铆as</string>
+    <string name="devicetype_huawei_watchgt5">Huawei Watch GT 5 (Pro)</string>
+    <string name="pref_summary_sync_birthdays">Sincroniza los cumplea帽os de los contactos con los eventos del calendario</string>
+    <string name="pref_title_sync_birthdays">Sincronizar los cumplea帽os</string>
+    <string name="devicetype_sony_wf_c700n">Sony WF-C700N</string>
+    <string name="contact_birthday">El cumplea帽os de %1s</string>
+    <string name="birthdays">Cumplea帽os</string>
+    <string name="pref_crash_notification_title">Notificarme cuando no responda o falle</string>
+    <string name="pref_crash_notification_summary">Cuando la aplicaci贸n falla, muestra una notificaci贸n con el error</string>
+    <string name="app_crash_notification_title">%1s se ha bloqueado</string>
+    <string name="app_crash_share_stacktrace">Comparte el error</string>
+    <string name="pref_dashboard_widget_today_yesterday_data_title">Datos de ayer</string>
+    <string name="pref_dashboard_widget_today_yesterday_data_summary">Mostrar datos de ayer atenuados entre la hora actual y la medianoche</string>
+    <string name="pref_dashboard_widget_today_time_indicator_title">Indicador de la hora actual</string>
+    <string name="pref_dashboard_widget_today_time_indicator_summary">Mostrar un indicador en la hora actual, para separar visualmente los datos de ayer y hoy</string>
+    <string name="pref_title_calendar_lookahead">N煤mero de d铆as de antelaci贸n</string>
+    <string name="pref_summary_calendar_lookahead">Sincronizar hasta %1s d铆as de eventos del calendario</string>
+    <string name="hrZoneEasy">F谩cil</string>
+    <string name="hrZoneThreshold">Umbral</string>
+    <string name="hrZoneMaximum">M谩ximo</string>
+    <string name="workout_set_reps">Repeticiones</string>
+    <string name="paceCorrection">Correcci贸n</string>
+    <string name="devicetype_huawei_watchgtcyber">Huawei Watch GT Cyber</string>
+    <string name="activity_detail_share_json_details">Compartir detalles en JSON</string>
+    <string name="number_selected_items">%1d seleccionado</string>
+    <string name="mijia_lywsd_comfort_level_summary">Configura los l铆mites de temperatura y humedad para el emoji mostrado</string>
+    <string name="mijia_lywsd_comfort_temperature_title">Temperatura (掳C)</string>
+    <string name="mijia_lywsd_comfort_humidity_title">Humedad (%)</string>
+    <string name="mijia_lywsd_comfort_lower">L铆mite inferior</string>
+    <string name="mijia_lywsd_comfort_upper">L铆mite superior</string>
+    <string name="mijia_lywsd_comfort_level_title">Nivel de comodidad</string>
+    <string name="mijia_lywsd_comfort_temperature_summary">Rango recomendado: 19 - 27</string>
+    <string name="mijia_lywsd_comfort_humidity_summary">Rango recomendado: 20 - 85</string>
+    <string name="devicetype_garmin_venu_sq">Garmin Venu Sq</string>
+    <string name="devicetype_garmin_fenix_8">Garmin Fenix 8</string>
+    <string name="date_placeholders__date__time">%1s, %1s</string>
+    <string name="date_placeholders__start_time__end_time">%1s - %2s</string>
+    <string name="devicetype_garmin_fenix_6s_sapphire">Garmin Fenix 6S Sapphire</string>
+    <string name="devicetype_garmin_venu_sq_2">Garmin Venu Sq 2</string>
+    <string name="pref_dnd_follow_phone_title">Siga la configuraci贸n DND del tel茅fono</string>
+    <string name="inactivity_warnings_minimum_steps_title">Cantidad m铆nima de pasos</string>
+    <string name="inactivity_warnings_minimum_steps_summary">Cantidad m铆nima de pasos que se deben realizar durante los minutos l铆mite</string>
+    <string name="prefs_hrv_monitoring_title">Monitoreo del ritmo card铆aco</string>
+    <string name="busy_task_fetch_hrv_data">Obtenci贸n datos HRV</string>
+    <string name="pref_dnd_follow_phone_summary">Cuando DND est谩 habilitado o deshabilitado en el tel茅fono, tambi茅n se activa autom谩ticamente en el dispositivo</string>
+    <string name="prefs_hrv_monitoring_description">Monitoriza autom谩ticamente la frecuencia card铆aca a lo largo del d铆a</string>
+    <string name="fmtPaceAverage">Ritmo promedio</string>
+    <string name="devicetype_huawei_watchd2">Huawei Watch D2</string>
+    <string name="average_respiration_rate">Frecuencia de la respiraci贸n</string>
+    <string name="breaths_per_min">Respiraciones por minuto</string>
+    <string name="devicetype_idasen">Escritorios de trabajo ID脜SEN de IKEA</string>
+    <string name="min_respiration_rate">Frecuencia m铆nima de la respiraci贸n</string>
+    <string name="hrv_sdrr">VFC SDRR</string>
+    <string name="idasen_control_button_sit">SENTADO</string>
+    <string name="idasen_control_button_stand">DE PIE</string>
+    <string name="idasen_pref_value_warning">El valor debe estar comprendido entre 62 y 126 cm</string>
+    <string name="devicetype_colmi_r10">Colmi R10</string>
+    <string name="idasen_control_button_mid">MEDIO</string>
+    <string name="max_respiration_rate">Frecuencia m谩xima de la respiraci贸n</string>
+    <string name="hrv_rmssd">RMSSD de la variaci贸n del ritmo card铆aco</string>
+    <string name="idasen_pref_sit_height">Altura de la posici贸n sentado (en cent铆metros)</string>
+    <string name="idasen_pref_mid_height">Altura de posici贸n media (en cent铆metros)</string>
+    <string name="idasen_pref_stand_height">Altura de la posici贸n de pie (en cent铆metros)</string>
+    <string name="milliseconds_ms">ms</string>
+    <string name="music_upload_info">Est谩 a punto de cargar el siguiente archivo de m煤sica:\n\n%1$s\nT铆tulo de la canci贸n: %2$s\n脕lbum: %3$s\n</string>
+    <string name="file_already_exists">El archivo ya existe</string>
+    <string name="devicetype_garmin_fenix_7">Garmin Fenix 7</string>
+    <string name="devicetype_garmin_forerunner_235">Garmin Forerunner 235</string>
+    <string name="devicetype_sony_wi_c100">Sony WI-C100</string>
+    <string name="devicetype_bandw_pseries">Bowers and Wilkins P series</string>
+    <string name="devicetype_huawei_band3pro">Huawei Band 3 (Pro)</string>
+    <string name="pref_voice_passthrough_level">Nivel de cancelaci贸n</string>
+    <string name="pref_voice_passthrough_enabled_summary">Desactivar la cancelaci贸n de ruido</string>
+    <string name="devicetype_garmin_fenix_6s_pro">Garmin Fenix 6S Pro</string>
+    <string name="pref_voice_passthrough_enabled">Transferencia</string>
+    <string name="devicetype_garmin_forerunner_55">Garmin Forerunner 55</string>
+    <string name="devicetype_oppo_enco_air">Oppo Enco Air</string>
+    <string name="devicetype_garmin_forerunner_620">Garmin Forerunner 620</string>
+    <string name="pref_wear_sensor_summary">Detectar cuando no se est谩 usando el dispositivo</string>
+    <string name="pref_wear_sensor_title">Sensor de uso</string>
+    <string name="pref_developer_add_test_activities_title">Agregar actividades de prueba</string>
+    <string name="pref_developer_add_test_activities_summary">Llene la base de datos con actividades de prueba ficticias</string>
+    <string name="pref_music_management_title">Gestionar m煤sica</string>
+    <string name="pref_music_management_summary">Gestionar la m煤sica en el reloj</string>
+    <string name="music_add_to_playlist">A帽adir a la lista de reproducci贸n</string>
+    <string name="music_delete_from_playlist">Borrar de la lista de reproducci贸n</string>
+    <string name="music_all_songs">Toda las canciones</string>
+    <string name="music_rename_playlist">Renombrar la lista de reproducci贸n</string>
+    <string name="title_activity_musicmanager">Gestor de m煤sica</string>
+    <string name="music_error">Ocurri贸 un error</string>
+    <string name="music_delete_confirm_description">驴Est谩s seguro de que deseas eliminar \' %1$s \'?</string>
+    <string name="music_delete">Borrar la canci贸n</string>
+    <string name="music_new_playlist">Nueva lista de reproducci贸n</string>
+    <string name="music_huawei_device_info">Formatos soportados: %1$s\nAlmacenamiento del reloj : %2$d MB</string>
+    <string name="devicetype_realme_buds_t110">Realme Buds T110</string>
+    <string name="devicetype_garmin_instinct_2">Garmin Instinct 2</string>
+    <string name="menuitem_calories_active_goal">Objetivo de calor铆as (activo)</string>
+    <string name="active_calories">Calorias activas</string>
+    <string name="active_calories_short">Activo</string>
+    <string name="total_calories_burnt">Total quemadas</string>
+    <string name="activity_prefs_goal_active_calories_burnt">Objetivo diario: calor铆as activas quemadas</string>
+    <string name="active_calories_goal">Objetivo activo</string>
+    <string name="menuitem_calories_segmented">Calor铆as (segmentadas)</string>
+    <string name="first_start_overview_title">Resumen</string>
+    <string name="first_start_overview_desc">Gadgetbridge tiene dos vistas principales, cada una con su propio prop贸sito.</string>
+    <string name="first_start_permissions_request_button">Solicitar</string>
+    <string name="first_start_get_started_title">Empezar</string>
+    <string name="first_start_get_started_restore_button">Restaurar la copia de seguridad</string>
+    <string name="permission_internet_access_summary">Sincronizaci贸n con recursos en l铆nea</string>
+    <string name="permission_contacts_title">Contactos</string>
+    <string name="permission_calendar_title">Calendario</string>
+    <string name="permission_send_sms_title">Enviar SMS</string>
+    <string name="permission_send_sms_summary">Env铆o de SMS (respuestas predefinidas) desde dispositivos</string>
+    <string name="permission_read_call_log_title">Leer el registro de llamadas</string>
+    <string name="permission_read_phone_state_title">Leer el estado del tel茅fono</string>
+    <string name="permission_read_phone_state_summary">Comprobar el estado de las llamadas en curso</string>
+    <string name="permission_call_phone_title">Llamar por tel茅fono</string>
+    <string name="permission_call_phone_summary">Iniciar llamadas telef贸nicas desde dispositivos</string>
+    <string name="permission_process_outgoing_calls_summary">Leer el n煤mero de una llamada saliente para mostrarlo en un dispositivo</string>
+    <string name="permission_answer_phone_calls_title">Responder llamadas telef贸nicas</string>
+    <string name="permission_answer_phone_calls_summary">Responder llamadas telef贸nicas desde dispositivos</string>
+    <string name="permission_external_storage_title">Almacenamiento externo</string>
+    <string name="permission_external_storage_summary">Uso de im谩genes, tonos de llamada, archivos de aplicaciones y m谩s</string>
+    <string name="first_start_open_source_text">Gadgetbridge es una aplicaci贸n de c贸digo abierto. Est谩 desarrollada por la comunidad, para la comunidad.\n\nCualquiera es bienvenido a contribuir mediante c贸digo, documentaci贸n, pruebas y donaciones.\n\nGadgetbridge no contiene anuncios ni rastreo. Mantiene tus datos localmente en tu dispositivo Android, por lo que es 100% respetuosa con la privacidad.\n\nVisita nuestra p谩gina web para obtener m谩s informaci贸n, documentaci贸n y enlaces a nuestros canales de comunicaci贸n.</string>
+    <string name="devicetype_oppo_enco_air2">Oppo Enco Air2</string>
+    <string name="first_start_welcome_title">Bienvenido/-a</string>
+    <string name="first_start_intro_tag_line">Lib茅rate de las aplicaciones propietarias y los servicios en la nube de los proveedores de gadgets.</string>
+    <string name="permission_bluetooth_connect_title">Conexi贸n Bluetooth</string>
+    <string name="permission_bluetooth_connect_summary">Conexi贸n a dispositivos Bluetooth ya emparejados</string>
+    <string name="permission_contacts_summary">Env铆o de contactos a dispositivos</string>
+    <string name="permission_calendar_summary">Env铆o de calendario a dispositivos</string>
+    <string name="permission_process_outgoing_calls_title">Procesar llamadas salientes</string>
+    <string name="permission_receive_sms_title">Recibir SMS</string>
+    <string name="permission_read_call_log_summary">Reenv铆o del registro de llamadas a dispositivos</string>
+    <string name="permission_receive_sms_summary">Reenv铆o de mensajes SMS a dispositivos m贸viles</string>
+    <string name="permission_query_all_packages_summary">Leer nombres e 铆conos de todas las aplicaciones instaladas</string>
+    <string name="first_start_intro_welcome_to">Bienvenido/-a a</string>
+    <string name="first_start_open_source_title">C贸digo abierto</string>
+    <string name="first_start_overview_dashboard">El panel de control te permite obtener una idea r谩pida de c贸mo te est谩 yendo hoy. La vista de calendario muestra el estado de tus objetivos durante todo un mes.</string>
+    <string name="first_start_overview_devices">La vista de dispositivos muestra todos los dispositivos que has configurado y su estado, y da acceso a funciones espec铆ficas de cada dispositivo, como gr谩ficos detallados, ajustes, aplicaciones y alarmas.</string>
+    <string name="first_start_permissions_title">Permisos</string>
+    <string name="first_start_permissions_desc">Gadgetbridge necesita muchos permisos para realizar todas sus funciones. Revise los permisos y sus prop贸sitos a continuaci贸n.</string>
+    <string name="first_start_permissions_request_all_button">Solicitar todos los permisos</string>
+    <string name="first_start_get_started_desc">Para empezar, a帽ade tu primer dispositivo directamente desde esta pantalla, restaura una copia de seguridad o empieza con una base de datos vac铆a.</string>
+    <string name="first_start_get_started_add_first_device_button">A帽adir el primer dispositivo</string>
+    <string name="permission_notifications_summary">Reenviando notificaciones a los gadgets conectados</string>
+    <string name="permission_manage_dnd_summary">Cambio de la pol铆tica de notificaciones DND</string>
+    <string name="first_start_get_started_go_to_app_button">Ir a la aplicaci贸n</string>
+    <string name="permission_manage_dnd_title">Gestionar no molestar</string>
+    <string name="permission_fine_location_title">Ubicaci贸n precisa</string>
+    <string name="permission_fine_location_summary">Buscando dispositivos Bluetooth</string>
+    <string name="permission_displayover_title">Visualizaci贸n sobre otras aplicaciones</string>
+    <string name="permission_displayover_summary">Utilizado por Bangle.js para iniciar aplicaciones y otras funciones en el tel茅fono</string>
+    <string name="permission_bluetooth_title">Bluetooth</string>
+    <string name="permission_background_location_title">Ubicaci贸n en segundo plano</string>
+    <string name="permission_background_location_summary">Buscando dispositivos Bluetooth en segundo plano y env铆o de la ubicaci贸n a determinados aparatos</string>
+    <string name="permission_bluetooth_summary">Conexi贸n a dispositivos Bluetooth</string>
+    <string name="permission_bluetooth_scan_title">Escaneo Bluetooth</string>
+    <string name="permission_bluetooth_admin_title">Administraci贸n Bluetooth</string>
+    <string name="permission_bluetooth_admin_summary">Detectar y emparejar dispositivos Bluetooth</string>
+    <string name="permission_bluetooth_scan_summary">B煤squeda de nuevos dispositivos Bluetooth</string>
+    <string name="permission_post_notification_title">Notificaciones de publicaciones</string>
+    <string name="permission_post_notification_summary">Publicar una notificaci贸n continua que mantiene el servicio en funcionamiento</string>
+    <string name="permission_internet_access_title">Acceso a Internet</string>
+    <string name="permission_query_all_packages_title">Consultar todos los paquetes</string>
+    <string name="redmi_buds_5_pro_transparency_strength">Intensidad de la transparencia</string>
+    <string name="redmi_buds_5_pro_combo_transparency_off">Trasparente/ Apagar</string>
+    <string name="redmi_buds_5_pro_equalizer_band_4k">4 kHz</string>
+    <string name="redmi_buds_5_pro_equalizer_band_8k">8 kHz</string>
+    <string name="redmi_buds_5_pro_equalizer_band_12k">12 kHz</string>
+    <string name="redmi_buds_5_pro_equalizer_band_16k">16 kHz</string>
+    <string name="redmi_buds_5_pro_equalizer_neg6">-6 dB</string>
+    <string name="redmi_buds_5_pro_anc_balanced">Equilibrado</string>
+    <string name="redmi_buds_5_pro_anc_light">Claro</string>
+    <string name="redmi_buds_5_pro_anc_deep">Profundo</string>
+    <string name="devicetype_redmi_buds_5_pro">Redmi Buds 5 Pro</string>
+    <string name="redmi_buds_5_pro_transparency_ambient">Mejorar el sonido ambiental</string>
+    <string name="redmi_buds_5_pro_combo_anc_off">ANC / Apagar</string>
+    <string name="redmi_buds_5_pro_transparency_regular">Regular</string>
+    <string name="redmi_buds_5_pro_transparency_voice">Mejorar las voces</string>
+    <string name="redmi_buds_5_pro_double_connection_description">Permitir que los auriculares se conecten a dos dispositivos al mismo tiempo</string>
+    <string name="redmi_buds_5_pro_combo_anc_transparency">ANC / Transparencia</string>
+    <string name="redmi_buds_5_pro_combo_all">ANC / Transparencia / Apagado</string>
+    <string name="redmi_buds_5_pro_adaptive_sound">Sonido adaptativo</string>
+    <string name="redmi_buds_5_pro_double_connection">Conexi贸n doble</string>
+    <string name="redmi_buds_5_pro_adaptive_sound_description">Ajusta el sonido seg煤n la forma del o铆do y el entorno</string>
+    <string name="redmi_buds_5_pro_equalizer_preset_standard">Estandar</string>
+    <string name="redmi_buds_5_pro_equalizer_preset_treble">Mejorar los agudos</string>
+    <string name="redmi_buds_5_pro_equalizer_preset_bass">Mejorar los graves</string>
+    <string name="redmi_buds_5_pro_equalizer_preset_voice">Mejorar la voz</string>
+    <string name="redmi_buds_5_pro_equalizer_preset_custom">Personalizado</string>
+    <string name="redmi_buds_5_pro_equalizer_band_62">62 Hz</string>
+    <string name="redmi_buds_5_pro_equalizer_band_125">125 Hz</string>
+    <string name="redmi_buds_5_pro_equalizer_band_250">250 Hz</string>
+    <string name="redmi_buds_5_pro_equalizer_band_1k">1 kHz</string>
+    <string name="redmi_buds_5_pro_equalizer_band_500">500 Hz</string>
+    <string name="redmi_buds_5_pro_equalizer_band_2k">2 kHz</string>
+    <string name="redmi_buds_5_pro_equalizer_neg4">-4 dB</string>
+    <string name="redmi_buds_5_pro_equalizer_zero">0 dB</string>
+    <string name="redmi_buds_5_pro_equalizer_neg5">-5 dB</string>
+    <string name="redmi_buds_5_pro_equalizer_neg3">-3 dB</string>
+    <string name="redmi_buds_5_pro_equalizer_neg2">-2 dB</string>
+    <string name="redmi_buds_5_pro_equalizer_neg1">-1 dB</string>
+    <string name="redmi_buds_5_pro_equalizer_1">1 dB</string>
+    <string name="redmi_buds_5_pro_equalizer_4">4 dB</string>
+    <string name="redmi_buds_5_pro_equalizer_2">2 dB</string>
+    <string name="redmi_buds_5_pro_equalizer_3">3 dB</string>
+    <string name="redmi_buds_5_pro_equalizer_5">5 dB</string>
+    <string name="redmi_buds_5_pro_equalizer_6">6 dB</string>
+    <string name="devicetype_miband9pro">Xiaomi Smart Band 9 Pro</string>
+    <string name="respiratoryrate">Frecuencia respiratoria</string>
+    <string name="lowest">M谩s bajo</string>
+    <string name="music_multiselect_limit">Se alcanz贸 el l铆mite para seleccionar m谩s de un elemento</string>
+    <string name="highest">M谩s alto</string>
+    <string name="music_delete_multiple_confirm_description">驴Est谩s seguro de que quieres borrar \'%1$d\' canciones?</string>
+    <string name="sleep_avg">Promedio de sue帽o</string>
+    <string name="watchface_widget_type_sp02">Saturaci贸n de ox铆geno</string>
+    <string name="devicetype_colmi_r09">Colmi R09</string>
+    <string name="sleep_score">Resultados del sue帽o</string>
+    <string name="sleep_score_value">Resultado: %1d</string>
+    <string name="devicetype_huawei_band2pro">Huawei Band 2 (Pro)</string>
+    <string name="devicetype_huawei_freebuds_5i">Huawei FreeBuds 5i</string>
+    <string name="avgJumpRate">脥ndice medio de salto</string>
+    <string name="maxJumpRate">Salto m谩ximo</string>
+    <string name="totalJumps">Saltos totales</string>
+    <string name="Jumps">Saltos</string>
+    <string name="jumps_minute">saltos/min</string>
+    <string name="jumps_unit">saltos</string>
+    <string name="devicetype_redmi_watch_5_lite">Redmi Watch 5 Lite</string>
+    <string name="garmin_intensity_minutes">Minutos de intensa actividad</string>
+    <string name="devicetype_garmin_fenix_7x">Garmin Fenix 7X</string>
+</resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-et/strings.xml b/app/src/main/res/values-et/strings.xml
new file mode 100644
index 0000000..ab9d2af
--- /dev/null
+++ b/app/src/main/res/values-et/strings.xml
@@ -0,0 +1,856 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <string name="application_name_generic">Gadgetbridge</string>
+    <string name="title_activity_controlcenter_generic">Gadgetbridge</string>
+    <string name="action_settings">Seaded</string>
+    <string name="action_debug">Silumine</string>
+    <string name="action_quit">V盲lju</string>
+    <string name="action_donate">Annetamine</string>
+    <string name="controlcenter_fetch_activity_data">S眉nkrooni</string>
+    <string name="controlcenter_find_device">Leia kadunud seade</string>
+    <string name="controlcenter_take_screenshot">Tee ekraanipilt</string>
+    <string name="controlcenter_connect">脺henda鈥�</string>
+    <string name="controlcenter_disconnect">脺henda lahti</string>
+    <string name="controlcenter_delete_device">Kustuta seade</string>
+    <string name="controlcenter_delete_device_name">Kustuta %1$s</string>
+    <string name="controlcenter_delete_device_dialogmessage">See kustutab seadme ja k玫ik sellega kaasnevad andmed!</string>
+    <string name="controlcenter_navigation_drawer_open">Ava navigatsioonisahtel</string>
+    <string name="controlcenter_navigation_drawer_close">Sulge navigatsioonisahtel</string>
+    <string name="controlcenter_snackbar_need_longpress">Pikk puudutus lahti眉hendamiseks</string>
+    <string name="controlcenter_snackbar_disconnecting">脺hendan lahti</string>
+    <string name="controlcenter_snackbar_connecting">脺hendamine鈥�</string>
+    <string name="controlcenter_snackbar_requested_screenshot">Teen seadme ekraanist pilti</string>
+    <string name="title_activity_debug">Silumine</string>
+    <string name="title_activity_appmanager">Rakendustehaldur</string>
+    <string name="appmanager_cached_watchapps_watchfaces">Rakendused vahem盲lus</string>
+    <string name="appmanager_installed_watchapps">Paigaldatud rakendused</string>
+    <string name="appmanager_installed_watchfaces">Paigaldatud kella stiilid</string>
+    <string name="appmananger_app_delete">Kustuta</string>
+    <string name="appmananger_app_delete_cache">Kustuta ja eemalda vahem盲lust</string>
+    <string name="appmananger_app_reinstall">Taasinstalli</string>
+    <string name="appmanager_app_openinstore">Otsi Pebble\'i poest</string>
+    <string name="appmanager_health_activate">Aktiveeri</string>
+    <string name="appmanager_health_deactivate">Inaktiveeri</string>
+    <string name="appmanager_hrm_activate">Aktiveeri HRM</string>
+    <string name="appmanager_hrm_deactivate">Inaktiveeri HRM</string>
+    <string name="appmanager_weather_activate">Aktiveeri s眉steemi ilmarakendus</string>
+    <string name="appmanager_weather_deactivate">Inaktiveeri s眉steemi ilmarakendus</string>
+    <string name="appmanager_weather_install_provider">Paigalda ilmateavituste rakendus</string>
+    <string name="app_configure">Seadista</string>
+    <string name="app_move_to_top">Liiguta k玫ige 眉les</string>
+    <string name="title_activity_calblacklist">Kalendrite must nimekiri</string>
+    <string name="title_activity_fw_app_insaller">P眉sivara/rakenduse paigaldaja</string>
+    <string name="fw_upgrade_notice">Olete installeerimas p眉sivara %s.</string>
+    <string name="fw_upgrade_notice_amazfitbip">Olete installimas %s p眉sivara enda Amazfit Bip\'ile. 
+\n 
+\nPaigalda .fw fail ja seej盲rel .res fail ning l玫puks .gps fail. Teie kell taask盲ivitub p盲rast .fw faili paigaldust.
+\n
+\nM盲rkus: .res ja .gps faili ei pea paigaldama, kui tegu on samade failidega, mis olid eelmisel uuendusel. 
+\n 
+\nJ脛TKATE OMAL RIISIKOL!</string>
+    <string name="fw_upgrade_notice_amazfitcor">Olete paigaldamas %s p眉sivara enda Amazfit Cor\'ile. 
+\n 
+\nPalun veenduge, et enne paigaldate .fw faili ja p盲rast seda .res faili. Teie kell taask盲ivitub p盲rast .fw faili paigaldust. 
+\n 
+\nM盲rkus: .res faili ei pea paigaldama kui tegu on sama failiga, mis eelmisel uuendusel. 
+\n 
+\nJ脛TKATE OMAL RIISIKOL!</string>
+    <string name="fw_multi_upgrade_notice">Olete installimas %1$s ja %2$s p眉sivara selle asemel, mis praegu teie Mi Band\'il on.</string>
+    <string name="miband_firmware_known">See p眉sivara on testitud ja on teadaolevalt sobiv koos Gadgetbridge\'iga kasutamiseks.</string>
+    <string name="miband_firmware_unknown_warning">See p眉sivara on testimata ja ei pruugi olla Gadgetbridge\'iga 眉hilduv. 
+\n 
+\nEi soovita seda teie Mi Bandile paigaldada!</string>
+    <string name="title_activity_settings">Seaded</string>
+    <string name="pref_header_general">脺ldised seaded</string>
+    <string name="pref_title_general_autoconnectonbluetooth">脺hendu seadmega kui Bluetooth l眉litatakse sisse</string>
+    <string name="pref_title_general_autostartonboot">K盲ivitu automaatselt</string>
+    <string name="pref_title_general_autoreconnect">Taas眉hendu automaatselt</string>
+    <string name="pref_title_audio_player">Eelistatud heli m盲ngija</string>
+    <string name="pref_default">Vaikev盲盲rtus</string>
+    <string name="pref_header_datetime">Kuup盲ev ja kellaaeg</string>
+    <string name="pref_title_datetime_syctimeonconnect">S眉nkrooni aega</string>
+    <string name="pref_summary_datetime_syctimeonconnect">S眉nkrooni seadme aeg 眉hendumise ajal v玫i siis kui Androidis aeg v玫i ajatsoon muutub</string>
+    <string name="miband_firmware_suggest_whitelist">Kui te siiski soovite j盲tkata ja seade j盲tkab t枚枚d ka hiljem palun andke Gadgetbridge\'i arendajatele teada, et nad lisaksid %s versiooni toetatud p眉sivarade nimekirja.</string>
+    <string name="pref_title_charts_swipe">Luba viiped vasakule/paremale graafikute vaates</string>
+    <string name="pref_title_theme">Teema</string>
+    <string name="pref_theme_light">Hele</string>
+    <string name="pref_theme_dark">Tume</string>
+    <string name="pref_title_language">Keel</string>
+    <string name="pref_title_minimize_priority">Peida Gadgetbridge\'i teavitus</string>
+    <string name="pref_header_notifications">Teavitused</string>
+    <string name="pref_title_notifications_repetitions">Korduseid</string>
+    <string name="pref_title_notifications_call">Telefonik玫ned</string>
+    <string name="pref_title_notifications_sms">SMS</string>
+    <string name="pref_title_notifications_pebblemsg">Pebble\'i s玫numid</string>
+    <string name="pref_title_notifications_generic">脺ldine teavituste tugi</string>
+    <string name="pref_title_notification_filter">脛ra sega</string>
+    <string name="pref_title_transliteration">Transliteratsioon</string>
+    <string name="always">Alati</string>
+    <string name="when_screen_off">Kui ekraan on v盲ljal眉litatud</string>
+    <string name="never">Mitte kunagi</string>
+    <string name="pref_header_privacy">Privaatsus</string>
+    <string name="pref_call_privacy_mode_off">Kuva nimi ja number</string>
+    <string name="pref_call_privacy_mode_number">Peida number kuid kuva nimi</string>
+    <string name="pref_call_privacy_mode_complete">Peida nimi ja number</string>
+    <string name="pref_title_weather">Ilm</string>
+    <string name="pref_title_weather_location">Asukoht ilmateate jaoks (LineageOS\'i allikas)</string>
+    <string name="pref_blacklist_calendars">Kalendrite must nimekiri</string>
+    <string name="pref_summary_minimize_priority_off">Olekuriba ikooni ja lukustusekraani m盲rguandeid kuvatakse</string>
+    <string name="pref_summary_minimize_priority_on">Olekuriba ikoon ja lukustuserkaani m盲rguanded on peidetud</string>
+    <string name="pref_summary_notifications_pebblemsg">Tugi rakendustele, mis saadavad PebbleKit\'i kaudu m盲rguandeid Pebble\'le.</string>
+    <string name="pref_title_whenscreenon">鈥a sisse l眉litatud ekraaniga</string>
+    <string name="pref_summary_notification_filter">Peata soovimatute m盲rguannete saatmine \"脛ra sega\" re啪iimis</string>
+    <string name="pref_summary_transliteration">Luba see, kui su seadmel puudub su keele fondi tugi</string>
+    <string name="pref_title_call_privacy_mode">K玫ne privaatre啪iim</string>
+    <string name="pref_call_privacy_mode_name">Peida nimi, kuva ainult number</string>
+    <string name="pref_header_cannned_messages">Talletatud s玫numid</string>
+    <string name="pref_title_canned_replies">Vastused</string>
+    <string name="pref_title_canned_reply_suffix">脺hine j盲relliide</string>
+    <string name="pref_header_development">Arendajate seaded</string>
+    <string name="pref_title_development_miaddr">Mi Band\'i aadress</string>
+    <string name="pref_title_canned_messages_set">Uuenda Pebble\'l</string>
+    <string name="pref_title_pebble_settings">Pebble\'i seaded</string>
+    <string name="pref_header_activitytrackers">Aktiivsusmonitorid</string>
+    <string name="pref_title_pebble_activitytracker">Eelistatud aktiivsusmonitor</string>
+    <string name="pref_title_pebble_sync_health">S眉nkrooni Pebble\'i terviseandmeid</string>
+    <string name="pref_title_pebble_sync_misfit">S眉nkrooni Misfit\'i</string>
+    <string name="controlcenter_calibrate_device">Kalibreeri seade</string>
+    <string name="blacklist_all_for_notifications">Lisa k玫ikide nende teatised musta nimekirja</string>
+    <string name="whitelist_all_for_notifications">Lisa k玫ikide nende teatised valgesse nimekirja</string>
+    <string name="fw_upgrade_notice_miband3">Te kavatsete installida %s p眉sivara enda Mi Band 3le. 
+\n 
+\nVeenduge, et installite .fw faili ja seej盲rel .res fail. Kell taask盲ivitub p盲rast .fw faili installimist. 
+\n 
+\nM盲rkus: Sa ei pea installima .res faili kui see on t盲pselt sama, mis eelnevalt installitud. 
+\n 
+\nJ脛TKATE OMAL RIISIKOL!</string>
+    <string name="pref_title_canned_messages_dismisscall">K玫ne hargile panek</string>
+    <string name="pref_title_pebble_sync_morpheuz">S眉nkrooni Morpheuz</string>
+    <string name="pref_title_enable_outgoing_call">Toeta v盲ljaminevaid k玫nesid</string>
+    <string name="pref_summary_enable_outgoing_call">Selle valiku v盲ljal眉litamine l眉litab v盲lja ka Pebble 2/LE v盲risemise v盲ljaminevate k玫nede puhul</string>
+    <string name="pref_title_enable_pebblekit">Luba kolmanda osapoolte rakenduste ligip盲盲s</string>
+    <string name="pref_summary_enable_pebblekit">L眉lita sisse eksperimentaalne PebbleKit\'i kasutavate rakenduste tugi</string>
+    <string name="pref_header_pebble_timeline">Pebble ajajoon</string>
+    <string name="pref_title_sunrise_sunset">P盲ikeset玫us ja p盲ikeseloojang</string>
+    <string name="pref_summary_sunrise_sunset">Saada p盲ikeset玫usu ja p盲ikeseloojangu ajad asukoha j盲rgi Pebble ajajoonele</string>
+    <string name="pref_title_enable_calendar_sync">S眉nkrooni kalender</string>
+    <string name="pref_summary_enable_calendar_sync">Saada kalendri s眉ndmused ajajoonele</string>
+    <string name="pref_title_autoremove_notifications">Eemalda automaatselt eemaldatud teatised</string>
+    <string name="pref_summary_autoremove_notifications">Teatised eemaldatakse automaatselt Pebble\'st kui need eemaldatakse Androidi seadmest</string>
+    <string name="pref_title_pebble_privacy_mode">Privaatsusre啪iim</string>
+    <string name="pref_pebble_privacy_mode_off">Tavalised teatised</string>
+    <string name="pref_pebble_privacy_mode_content">Nihuta teatise tekst ekraanilt v盲lja</string>
+    <string name="pref_pebble_privacy_mode_complete">N盲ita ainult teatise ikooni</string>
+    <string name="pref_header_location">Asukoht</string>
+    <string name="pref_title_location_aquire">K眉si asukoht</string>
+    <string name="pref_title_location_latitude">Laiuskraad</string>
+    <string name="pref_title_location_longitude">Pikkuskraad</string>
+    <string name="pref_title_location_keep_uptodate">Hoia asukoht ajakohasena</string>
+    <string name="pref_summary_location_keep_uptodate">Proovi k眉sida ajakohast asukohta jooksvalt, kasuta eelnevalt salvestatut kui eelnev ei 玫nnestu</string>
+    <string name="toast_enable_networklocationprovider">Palun l眉lita sisse v玫rgu j盲rgi seatud asukoht</string>
+    <string name="toast_aqurired_networklocation">asukoht leitud</string>
+    <string name="pref_title_pebble_forceprotocol">Sunniviisiliselt kasuta teatiste protokolli</string>
+    <string name="pref_summary_pebble_forceprotocol">See valik sunnib k玫ige uuema teatiste protokolli kasutamise vastavalt p眉sivara versioonile. KASUTA AINULT KUI SA TEAD MIDA SA TEED!</string>
+    <string name="pref_title_pebble_forceuntested">L眉lita sisse testimata funktsioonid</string>
+    <string name="pref_summary_pebble_forceuntested">L眉lita sisse testimata funktsioonid. TEA, MIDA SA TEED!</string>
+    <string name="pref_title_pebble_forcele">Alati eelista Bluetooth LE\'d</string>
+    <string name="pref_auto_fetch_summary">Import k盲ivitatakse ekraani lahtilukustamise ajal. T枚枚tab ainult sellel juhul kui lukustusmehhanism on seatud!</string>
+    <string name="DEVINFO_HR_VER">"Pulss: "</string>
+    <string name="pref_title_notifications_timeout">L眉him aeg teavituste vahel</string>
+    <string name="pref_summary_pebble_forcele">Kasuta eksperimentaalset Pebble LE tuge k玫ikide Pebble\'de jaoks tavalise Bluetoothi asemel. See n玫uab sidumist tavalise Bluetoothi kaudu ja seej盲rel Bluetooth LE sidumist</string>
+    <string name="pref_title_pebble_mtu_limit">Pebble 2/LE GATT MTU limiit</string>
+    <string name="pref_summary_pebble_mtu_limit">Kui Pebble 2/Pebble LE ei taha ootusp盲raselt t枚枚tada proovi seada MTUle limiit vahemikus 20鈥�512</string>
+    <string name="pref_title_pebble_enable_applogs">L眉lita sisse kellarakenduste logimine</string>
+    <string name="pref_title_pebble_gatt_clientonly">Ainult GATT klient</string>
+    <string name="pref_summary_pebble_gatt_clientonly">See on ainult Pebble 2le ja eksperimentaalne, katseta kui on ilmnenud 眉henduvusprobleeme</string>
+    <string name="controlcenter_change_led_color">Muuda LED v盲rvi</string>
+    <string name="controlcenter_change_fm_frequency">Muuda FM sagedus</string>
+    <string name="debugactivity_really_factoryreset_title">Kas taastada algseaded\?</string>
+    <string name="debugactivity_really_factoryreset">Algseadete taastamine kustutab k玫ik andmed 眉hendatud seadmetest endist (kui v玫imalik). Xiaomi/Huami seadmed muudavad ka Bluetooth MAC aadressi ja Gadgetbridge tuvastab need edaspidi uute seadmetena.</string>
+    <string name="pref_title_support_voip_calls">Luba VoIP rakenduse k玫ned</string>
+    <string name="pref_title_rtl">Paremalt vasakule</string>
+    <string name="pref_summary_rtl">Luba see, kui seade ei saa kuvada paremalt vasakule keeli</string>
+    <string name="pref_rtl_max_line_length">Paremalt vasakule suurim rea pikkus</string>
+    <string name="pref_title_pebble_enable_bgjs">Luba taust JS</string>
+    <string name="pref_summary_pebble_enable_bgjs">Kui lubatud, v玫imaldab kellal kuvada ilma, aku andmeid jne.</string>
+    <string name="pref_title_pebble_reconnect_attempts">Taas眉hendamise katsed</string>
+    <string name="pref_title_unit_system">脺hikud</string>
+    <string name="pref_title_timeformat">Kellaaja vorming</string>
+    <string name="pref_title_screentime">Ekraani avatuse kestus</string>
+    <string name="prefs_title_all_day_heart_rate">Kogu p盲eva s眉dame l枚枚gisageduse m玫玫tmine</string>
+    <string name="preferences_hplus_settings">HPlus/Makibes seaded</string>
+    <string name="prefs_screen_orientation">Ekraani suund</string>
+    <string name="zetime_title_settings">ZeTime seaded</string>
+    <string name="zetime_title_heartrate">S眉dame l枚枚gisageduse seaded</string>
+    <string name="zetime_title_screentime">Ekraani avatuse kestus sekundites</string>
+    <string name="zetime_title_heart_rate_alarm">S眉dame l枚枚gisageduse alarm</string>
+    <string name="zetime_title_heart_rate_alarm_summary">Kell hoiatab, kui s眉dame l枚枚gisagedus 眉letab piirid.</string>
+    <string name="zetime_heart_rate_alarm_enable">Luba s眉dame l枚枚gisageduse alarm</string>
+    <string name="activity_prefs_alarm_max_heart_rate">Suurim s眉dame l枚枚gisagedus</string>
+    <string name="activity_prefs_alarm_min_heart_rate">V盲him s眉dame l枚枚gisagedus</string>
+    <string name="zetime_analog_mode">Analoog re啪iim</string>
+    <string name="zetime_analog_mode_hands">Ainult k盲ed</string>
+    <string name="zetime_analog_mode_handsandsteps">K盲ed ja sammud</string>
+    <string name="zetime_activity_tracking">Tegevuse j盲lgimine</string>
+    <string name="zetime_activity_tracking_summary">Tegevuse j盲lgimise sissel眉litamisel loetakse sammude arvu jne.</string>
+    <string name="zetime_handmove_display">K盲te liikumine</string>
+    <string name="zetime_handmove_display_summary">P枚枚ra rannet, et ekraan aktiveerida v玫i deaktiveerida.</string>
+    <string name="zetime_calories_type">Kalorite t眉眉p</string>
+    <string name="zetime_calories_type_active">Ainult aktiivselt p玫letatud kalorid</string>
+    <string name="zetime_calories_type_all">Aktiivselt ja passiivselt p玫letatud kalorid</string>
+    <string name="zetime_date_format">Kuup盲eva vorming</string>
+    <string name="zetime_prefs_inactivity_repetitions">Kordused</string>
+    <string name="zetime_prefs_inactivity_mo">Esmasp盲ev</string>
+    <string name="zetime_prefs_inactivity_tu">Teisip盲ev</string>
+    <string name="zetime_prefs_inactivity_we">Kolmap盲ev</string>
+    <string name="zetime_prefs_inactivity_th">Neljap盲ev</string>
+    <string name="zetime_prefs_inactivity_fr">Reede</string>
+    <string name="zetime_prefs_inactivity_sa">Laup盲ev</string>
+    <string name="zetime_prefs_inactivity_su">P眉hap盲ev</string>
+    <string name="zetime_title_alarm_signaling">M盲盲ra alarmi m盲rguande t眉眉p</string>
+    <string name="zetime_signaling_none">Vaikne</string>
+    <string name="zetime_signaling_vibrate">Pidev v盲rin</string>
+    <string name="zetime_signaling_beep">Pidev piiksumine</string>
+    <string name="zetime_signaling_vibrate_beep">Pidev v盲rin ja piiksumine</string>
+    <string name="zetime_signaling_vibrate_once">Vibreeri 眉ks kord</string>
+    <string name="zetime_signaling_vibrate_twice">Vibreeri kaks korda</string>
+    <string name="zetime_signaling_beep_once">脺ks piiks</string>
+    <string name="zetime_signaling_beep_twice">Kaks piiksu</string>
+    <string name="zetime_signaling_vibrate_beep_once">Vibreeri ja piiksu 眉ks kord</string>
+    <string name="title_activity_device_specific_settings">Seadme spetsiifilised s盲tted</string>
+    <string name="pref_header_auto_export">Automaatne eksport</string>
+    <string name="pref_title_auto_export_enabled">Automaatne eksport lubatud</string>
+    <string name="pref_title_auto_export_location">Ekspordi asukoht</string>
+    <string name="pref_title_auto_export_interval">Ekspordi intervall</string>
+    <string name="pref_summary_auto_export_interval">Eksport iga %d tunni j盲rel</string>
+    <string name="pref_auto_fetch">Tegevuste andmete automaatne import</string>
+    <string name="pref_auto_fetch_limit_fetches">V盲him importimise vahemik</string>
+    <string name="pref_auto_fetch_limit_fetches_summary">Import iga %d minuti j盲rel</string>
+    <string name="not_connected">Pole 眉hendatud</string>
+    <string name="connecting">脺hendamine</string>
+    <string name="connected">脺hendatud</string>
+    <string name="unknown_state">Tundmatu olek</string>
+    <string name="_unknown_">(teadmata)</string>
+    <string name="test">Test</string>
+    <string name="test_notification">Testi m盲rguande</string>
+    <string name="this_is_a_test_notification_from_gadgetbridge">See on Gadgetbridge test teavitus</string>
+    <string name="bluetooth_is_not_supported_">Bluetooth ei ole toetatud.</string>
+    <string name="bluetooth_is_disabled_">Bluetooth on keelatud.</string>
+    <string name="tap_connected_device_for_app_mananger">Puuduta rakenduste halduri jaoks 眉hendatud seadet</string>
+    <string name="tap_connected_device_for_activity">Puuduta 眉hendatud seadet tegevuse jaoks</string>
+    <string name="tap_connected_device_for_vibration">Puuduta 眉hendatud seadet v盲rina tekitamiseks</string>
+    <string name="tap_a_device_to_connect">Puuduta seadet, et 眉hendada</string>
+    <string name="cannot_connect_bt_address_invalid_">Ei saa 眉hendust. Vigane bluetooth aadress\?</string>
+    <string name="gadgetbridge_running_generic">Gadgetbridge t枚枚tab</string>
+    <string name="installing_binary_d_d">Binaarfaili %1$d/%2$d paigaldamine</string>
+    <string name="installation_failed_">Paigaldamine nurjus</string>
+    <string name="installation_successful">Paigaldatud</string>
+    <string name="firmware_install_warning">脺RITAD PAIGALDADA P脺SIVARA, J脛TKA OMAL VASTUTUSEL
+\n
+\n
+\nSee p眉sivara on sobilik HW Revision: %s</string>
+    <string name="app_install_info">Kavas on paigaldada rakendus:
+\n
+\n
+\n%1$s versioon %2$s / %3$s
+\n</string>
+    <string name="appversion_by_creator">%1$s / %2$s</string>
+    <string name="title_activity_discovery">Seadme tuvastamine</string>
+    <string name="discovery_stop_scanning">Peata skannimine</string>
+    <string name="discovery_start_scanning">Alusta avastamist</string>
+    <string name="action_discover">脺henda uus seade</string>
+    <string name="device_with_rssi">%1$s (%2$s)</string>
+    <string name="title_activity_android_pairing">Seadme sidumine</string>
+    <string name="android_pairing_hint">Kasuta seadme sidumiseks Androidi Bluetoothi sidumise dialoogi.</string>
+    <string name="title_activity_mi_band_pairing">Seo oma Mi Band</string>
+    <string name="pairing">Sidumine %s鈥�</string>
+    <string name="pairing_creating_bond_with">Sideme loomine - %1$s (%2$s)</string>
+    <string name="pairing_unable_to_pair_with">Sidumine nurjus - %1$s (%2$s)</string>
+    <string name="pairing_in_progress">Toimub sidumine: %1$s (%2$s)</string>
+    <string name="pref_header_vibration_count">V盲rinate arv</string>
+    <string name="pref_screen_vibration_profile">V盲rina profiil</string>
+    <string name="miband_prefs_vibration">V盲rin</string>
+    <string name="pref_header_vibration_settings">V盲rina seaded</string>
+    <string name="control_center_cancel_to_stop_vibration">T眉hista v盲rina peatamiseks.</string>
+    <string name="mi2_prefs_button_action_vibrate">Luba v玫ru v盲rin</string>
+    <string name="mi2_prefs_button_action_vibrate_summary">Luba v盲rin nupu abil</string>
+    <string name="title_activity_vibration">V盲rin</string>
+    <string name="reset_index">L盲htesta impordi aeg</string>
+    <string name="mi2_prefs_button_press_broadcast_default_value" translatable="false">nodomain.freeyourgadget.gadgetbridge.ButtonPressed</string>
+    <string name="n_a">Pole saadaval</string>
+    <string name="initialized">k盲ivitatud</string>
+    <string name="message_cannot_pair_no_mac">MAC aadressi ei edastatud, sidumine eba玫nnestus.</string>
+    <string name="preferences_category_device_specific_settings">Seadmespetsiifilised s盲tted</string>
+    <string name="preferences_miband_settings">Mi Band / Amazfit s盲tted</string>
+    <string name="male">Mees</string>
+    <string name="female">Naine</string>
+    <string name="other">Muu</string>
+    <string name="left">Vasakul</string>
+    <string name="right">Paremal</string>
+    <string name="appinstaller_install">Paigalda</string>
+    <string name="discovery_note">M盲rkus:</string>
+    <string name="candidate_item_device_image">Seadme pilt</string>
+    <string name="miband_prefs_alias">Nimi/Alias</string>
+    <string name="title_activity_sleepmonitor">Unej盲lgimine</string>
+    <string name="pref_write_logfiles">Kirjuta logifailidesse</string>
+    <string name="initializing">K盲ivitamine</string>
+    <string name="busy_task_fetch_activity_data">Aktiivsusandmete t玫mbamine</string>
+    <string name="vibration_profile_staccato">Staccato</string>
+    <string name="vibration_profile_short">L眉hike</string>
+    <string name="vibration_profile_medium">Keskmine</string>
+    <string name="vibration_profile_long">Pikk</string>
+    <string name="vibration_profile_waterdrop">Veepiisk</string>
+    <string name="vibration_profile_ring">R玫ngas</string>
+    <string name="vibration_profile_alarm_clock">脛ratuskell</string>
+    <string name="vibration_try">Proovi</string>
+    <string name="pref_screen_notification_profile_sms">SMS teavitus</string>
+    <string name="pref_screen_notification_profile_generic">脺ldised teavitused</string>
+    <string name="pref_screen_notification_profile_email">Teavitused e-postile</string>
+    <string name="pref_screen_notification_profile_incoming_call">Saabuva k玫ne teatis</string>
+    <string name="pref_screen_notification_profile_generic_chat">Vestlus</string>
+    <string name="pref_screen_notification_profile_generic_navigation">Navigeerimine</string>
+    <string name="pref_screen_notification_profile_generic_social">Sotsiaalv玫rgud</string>
+    <string name="interval_one_minute">kord minutis</string>
+    <string name="interval_five_minutes">iga 5 minuti tagant</string>
+    <string name="interval_ten_minutes">iga 10 minuti tagant</string>
+    <string name="interval_thirty_minutes">iga 30 minuti tagnt</string>
+    <string name="interval_one_hour">kord tunnis</string>
+    <string name="stats_x_axis_label">Minuteid kokku</string>
+    <string name="stats_y_axis_label">Samme minutis</string>
+    <string name="control_center_find_lost_device">Leia kaotatud seade</string>
+    <string name="title_activity_charts">Aktiivsus ja uni</string>
+    <string name="title_activity_set_alarm">Seadista 盲ratused</string>
+    <string name="controlcenter_start_configure_alarms">Seadista 盲ratused</string>
+    <string name="title_activity_alarm_details">脛ratuse info</string>
+    <string name="alarm_sun_short">P</string>
+    <string name="alarm_mon_short">E</string>
+    <string name="alarm_tue_short">T</string>
+    <string name="alarm_wed_short">K</string>
+    <string name="alarm_thu_short">N</string>
+    <string name="alarm_fri_short">R</string>
+    <string name="alarm_sat_short">L</string>
+    <string name="alarm_smart_wakeup">Nutkas 盲ratus</string>
+    <string name="user_feedback_miband_set_alarms_ok">脛ratused saadeti seadmesse.</string>
+    <string name="chart_no_data_synchronize">Andmeid pole. Kas s眉nkroonida seade\?</string>
+    <string name="miband_prefs_fitness_goal">P盲evane sammude eesm盲rk</string>
+    <string name="controlcenter_start_activitymonitor">Sinu aktiivsus (ALFA)</string>
+    <string name="notif_battery_low_title">Vidina aku on madal!</string>
+    <string name="notif_battery_low_bigtext_last_charge_time">Viimane laadimine: %s 
+\n</string>
+    <string name="notif_battery_low_bigtext_number_of_charges">Laadimiste arv: %s</string>
+    <string name="sleepchart_your_sleep">Uni</string>
+    <string name="weeksleepchart_sleep_a_week">Uni n盲dalas</string>
+    <string name="weekstepschart_steps_a_week">Samme n盲dalas</string>
+    <string name="activity_sleepchart_activity_and_sleep">Tegevus</string>
+    <string name="updating_firmware">P眉sivara uuendamine鈥�</string>
+    <string name="fwapp_install_device_not_ready">Faile ei saa paigaldada, seade pole valmis.</string>
+    <string name="installhandler_firmware_name">%1$s: %2$s %3$s</string>
+    <string name="miband_fwinstaller_compatible_version">脺hilduv versioon</string>
+    <string name="miband_fwinstaller_untested_version">Testimata versioon!</string>
+    <string name="fwappinstaller_connection_state">脺hendatud seademega: %1$s</string>
+    <string name="pbw_installhandler_pebble_firmware">Pebble p眉sivara %1$s</string>
+    <string name="pbwinstallhandler_app_item">%1$s (%2$s)</string>
+    <string name="updatefirmwareoperation_update_complete">P眉sivara paigaldamine on valmis</string>
+    <string name="updatefirmwareoperation_write_failed">P眉sivara uuendamine eba玫nnestus</string>
+    <string name="chart_steps">Samme</string>
+    <string name="calories">Kaloreid</string>
+    <string name="distance">Vahemaa</string>
+    <string name="clock">Kell</string>
+    <string name="heart_rate">Pulss</string>
+    <string name="battery">Aku</string>
+    <string name="live_activity_steps_history">Sammude ajalugu</string>
+    <string name="abstract_chart_fragment_kind_activity">Aktiivsus</string>
+    <string name="abstract_chart_fragment_kind_light_sleep">Kerge uni</string>
+    <string name="abstract_chart_fragment_kind_deep_sleep">S眉gav uni</string>
+    <string name="abstract_chart_fragment_kind_not_worn">Ei kantud</string>
+    <string name="device_not_connected">Pole 眉hendatud.</string>
+    <string name="user_feedback_all_alarms_disabled">K玫ik 盲ratused on v盲lja l眉litatud</string>
+    <string name="pref_title_keep_data_on_device">S盲ilita tegevus seadmes</string>
+    <string name="miband_fwinstaller_incompatible_version">Mitte眉hilduv p眉sivara</string>
+    <string name="miband2_prefs_dateformat">Kuup盲eva vorming</string>
+    <string name="dateformat_time">Kellaaeg</string>
+    <string name="dateformat_date_time">Kellaaeg ja kuup盲ev</string>
+    <string name="mi2_prefs_button_actions">Nupu toimingud</string>
+    <string name="mi2_prefs_button_actions_summary">M盲ra nupu vajutamise toimingud</string>
+    <string name="mi2_prefs_button_press_count">Nupu vajutamise arv</string>
+    <string name="mi2_prefs_button_action">Luba nupu toiming</string>
+    <string name="mi2_prefs_do_not_disturb">Mitte h盲irida</string>
+    <string name="mi2_prefs_do_not_disturb_start">Alguse aeg</string>
+    <string name="mi2_prefs_do_not_disturb_end">L玫pu aeg</string>
+    <string name="automatic">Automaatne</string>
+    <string name="simplified_chinese">Hiina (lihtsustatud)</string>
+    <string name="traditional_chinese">Hiina (traditsionaalne)</string>
+    <string name="english">Inglise</string>
+    <string name="spanish">Hispaania</string>
+    <string name="activity_prefs_about_you">Info sinu kohta</string>
+    <string name="activity_prefs_year_birth">S眉nniaasta</string>
+    <string name="activity_prefs_gender">Sugu</string>
+    <string name="activity_prefs_height_cm">Pikkus (cm)</string>
+    <string name="activity_prefs_weight_kg">Kaal (kg)</string>
+    <string name="authenticating">Autentimine</string>
+    <string name="authentication_required">Autentimine on n玫utud</string>
+    <string name="updatefirmwareoperation_firmware_not_sent">P眉sivara ei saadetud</string>
+    <string name="charts_legend_heartrate">Pulss</string>
+    <string name="live_activity_heart_rate">Pulss</string>
+    <string name="russian">Vene</string>
+    <string name="horizontal">Horisontaalne</string>
+    <string name="vertical">Vertikaalne</string>
+    <string name="german">Saksa</string>
+    <string name="italian">Itaalia</string>
+    <string name="french">Prantususe</string>
+    <string name="polish">Poola</string>
+    <string name="korean">Korea</string>
+    <string name="japanese">Jaapani</string>
+    <string name="you_did_not_sleep">Sa ei maganud</string>
+    <string name="no_limit">Limiiti pole</string>
+    <string name="seconds_5">5 sekundit</string>
+    <string name="seconds_10">10 sekundit</string>
+    <string name="seconds_20">20 sekundit</string>
+    <string name="seconds_30">30 sekundit</string>
+    <string name="minutes_1">1 minut</string>
+    <string name="minutes_5">5 minutit</string>
+    <string name="minutes_10">10 minutit</string>
+    <string name="minutes_30">30 minutit</string>
+    <string name="mi3_prefs_night_mode">脰枚re啪iim</string>
+    <string name="activity_prefs_charts">Diagrammi seaded</string>
+    <string name="activity_prefs_chart_max_heart_rate">Maksimaalne pulss</string>
+    <string name="activity_prefs_chart_min_heart_rate">Minimaalne pulss</string>
+    <string name="notif_battery_low">%1$s autase madal</string>
+    <string name="lack_of_sleep">Unepuudus: %1$s</string>
+    <string name="overslept">脺lemagamine: %1$s</string>
+    <string name="zetime_date_format_1">AA/KK/PP</string>
+    <string name="zetime_date_format_2">PP/KK/AA</string>
+    <string name="zetime_date_format_3">KK/PP/AA</string>
+    <string name="pref_screen_notification_profile_missed_call">Vastamata k玫ne teatis</string>
+    <string name="pref_screen_notification_profile_calendar">Kalendri teavitus</string>
+    <string name="pref_screen_notification_profile_low_power">Madala akutaseme hoiatus</string>
+    <string name="pref_screen_notification_profile_anti_loss">Kaotamisvastane hoiatus</string>
+    <string name="interval_fifteen_minutes">iga 15 minuti tgant</string>
+    <string name="interval_forty_five_minutes">Iga 45 minuti tagant</string>
+    <string name="pref_title_authkey">Autentimise v玫ti</string>
+    <string name="dutch">Hollandi</string>
+    <string name="turkish">T眉rgi</string>
+    <string name="ukrainian">Ukraina</string>
+    <string name="arabic">Araabia</string>
+    <string name="indonesian">Indoneesia</string>
+    <string name="thai">Tai</string>
+    <string name="vietnamese">Vietnami</string>
+    <string name="portuguese">Portugali</string>
+    <string name="prefs_hr_alarm_low">Madal limiit</string>
+    <string name="prefs_hr_alarm_high">K玫rge limiit</string>
+    <string name="average">Keskmine: %1$s</string>
+    <string name="pref_header_charts">Diagrammi seaded</string>
+    <string name="pref_title_charts_average">N盲ita diagrammis keskmisi</string>
+    <string name="pref_title_charts_range">Diagrammi vahemik</string>
+    <string name="weekstepschart_steps_a_month">Samme kuus</string>
+    <string name="pref_title_use_custom_font">Kasuta kohandatud fonti</string>
+    <string name="pref_display_add_device_fab">Uue seadme 眉hendamise nupp</string>
+    <string name="pref_display_add_device_fab_on">Alati n盲htav</string>
+    <string name="pref_display_add_device_fab_off">N盲htav ainult siis, kui 眉htegi seadet pole lisatud</string>
+    <string name="preferences_makibes_hr3_settings">Makebites HR3 seaded</string>
+    <string name="prefs_wearside">Paremal v玫i vasakul k盲el\?</string>
+    <string name="pref_chart_heartrate_color_red">Punane</string>
+    <string name="pref_chart_heartrate_color_orange">Oraan啪</string>
+    <string name="pref_title_chart_heartrate_color">Pulsi v盲rv</string>
+    <string name="pref_chart_sleep_rolling_24_on">Viimased 24 tundi</string>
+    <string name="controlcenter_set_alias">Sea kuvatav nimi</string>
+    <string name="fw_upgrade_notice_amazfitgts">Olete installimas %s p眉sivara enda Amazfit Bip\'ile. 
+\n 
+\nPaigalda .fw fail ja seej盲rel .res fail ning l玫puks .gps fail. Teie kell taask盲ivitub p盲rast .fw faili paigaldust. 
+\n 
+\nM盲rkus: .res ja .gps faili ei pea paigaldama, kui tegu on samade failidega, mis olid eelmisel uuendusel. 
+\n 
+\nJ脛TKATE OMAL VASTUTUSEL!</string>
+    <string name="fw_upgrade_notice_amazfitgtr">Olete installimas %s p眉sivara enda Amazfit Bip\'ile. 
+\n 
+\nPaigalda .fw fail ja seej盲rel .res fail ning l玫puks .gps fail. Teie kell taask盲ivitub p盲rast .fw faili paigaldust. 
+\n 
+\nM盲rkus: .res ja .gps faili ei pea paigaldama, kui tegu on samade failidega, mis olid eelmisel uuendusel. 
+\n 
+\nJ脛TKATE OMAL VASTUTUSEL!</string>
+    <string name="fw_upgrade_notice_amazfitcor2">Olete paigaldamas %s p眉sivara enda Amazfit Cor 2-le. 
+\n 
+\nPalun veenduge, et enne paigaldate .fw faili ja p盲rast seda .res faili. Teie kell taask盲ivitub p盲rast .fw faili paigaldust. 
+\n 
+\nM盲rkus: .res faili ei pea paigaldama kui tegu on sama failiga, mis eelmisel uuendusel. 
+\n 
+\nJ脛TKATE OMAL VASTUTUSEL!
+\n
+\nT脛IESTI TESTIMATA, ILMSELT PEATE INSTALEERIMA BEATS_W P脺SIVARA, KUI TEIE SEADME NIMI ON \"Amazfit Band 2\"</string>
+    <string name="fw_upgrade_notice_amazfitbip_lite">Olete paigaldamas %s p眉sivara enda Amazfit Bip Lite\'ile. 
+\n 
+\nPalun veenduge, et enne paigaldate .fw faili ja p盲rast seda .res faili. Teie kell taask盲ivitub p盲rast .fw faili paigaldust. 
+\n 
+\nM盲rkus: .res faili ei pea paigaldama kui tegu on sama failiga, mis eelmisel uuendusel. 
+\n 
+\nJ脛TKATE OMAL VASTUTUSEL!</string>
+    <string name="pref_theme_system">S眉steem</string>
+    <string name="pref_rtl_max_line_length_summary">Muuda paremalt-vasakule keelte puhul teksti reapikkust</string>
+    <string name="fw_upgrade_notice_miband4">Olete installimas %s p眉sivara enda Mi Band 4\'ile. 
+\n 
+\nVeenduge, et installida .fw faili ja p盲rast seda .res faili. Teie b盲nd taask盲ivitub p盲rast .fw faili installimist. 
+\n
+\nM盲rkus: sa ei pea installima .res, kui see on t盲pselt sama, mis varem installeeritud. 
+\n
+\nJ脛TKAKE OMAL VASTUTUSEL!</string>
+    <string name="pref_title_custom_deviceicon">Kuva seadmespetsiifilist teavitusikooni</string>
+    <string name="pref_summary_pebble_enable_applogs">Logi kellarakenduste v盲ljundit (vajab taas眉hendamist)</string>
+    <string name="pref_summary_pebble_always_ack_pebblekit">M盲rgi s玫numid kolmandate osapoolte rakendustele alati koheselt k盲ttesaaduks</string>
+    <string name="pref_title_pebble_always_ack_pebblekit">M盲rgi PebbleKit s玫numid enneaegselt k盲ttesaaduks</string>
+    <string name="pref_title_expose_hr">Kolmandate osapoolte juurdep盲盲s reaalajas HR-ile</string>
+    <string name="pref_summary_expose_hr">V玫imaldab teistel rakendustel juurdep盲盲su HR-andmetele reaalajas, kui Gadgetbridge on 眉hendatud</string>
+    <string name="pref_summary_use_custom_font">L眉litage see sisse, kui seadmel on emoji font paigaldatud</string>
+    <string name="pref_summary_authkey">Muutke autentimisv玫ti samasuguseks k玫igis oma Androidi seadmetes, millega soovite 眉henduse luua. K玫igi seadmete eelmine vaikev玫ti on 0123456789@ABCDE</string>
+    <string name="pref_summary_allow_high_mtu">Suurendab edastuskiirust, kuid m玫nes Androidi seadmes see ei pruugi t枚枚tada.</string>
+    <string name="pref_title_allow_high_mtu">Luba k玫rge MTU</string>
+    <string name="pref_summary_disable_new_ble_scanning">L眉litage see valik sisse, kui teie seadet ei leita skanneerimise ajal</string>
+    <string name="pref_disable_new_ble_scanning">脛ra luba uut t眉眉pi BLE skanneerimist</string>
+    <string name="pairing_already_bonded">Juba paaritatud %1$s (%2$s), 眉hendan鈥�</string>
+    <string name="pref_title_relax_firmware_checks">L眉lita see sisse kui sa tahad paigaldada enda seadmele p眉sivara, mis pole sellele m玫eldud (t盲iesti omal vastutusel)</string>
+    <string name="pref_screen_notification_profile_inactivity">Inaktiivsushoiatus</string>
+    <string name="sleep_activity_date_range">%1$s-ist %2$s-ni</string>
+    <string name="discovery_need_to_enter_authkey">See seade vajab salajast v玫tit, tehke pikk puudutus seadmel, et see sisestada. Lisainformatsioon vikis.</string>
+    <string name="miband_pairing_tap_hint">Kui teie Mi Band v盲riseb ja vilgub vajutage ekraanile paar korda.</string>
+    <string name="miband_pairing_using_dummy_userdata">Puuduvad korrektsed kasutaja andmed, kasutan vaikev盲盲rtuseid.</string>
+    <string name="find_my_phone_notification">Leia mu telefon</string>
+    <string name="pref_header_auto_fetch">Automaatne laadimine</string>
+    <string name="prefs_hr_alarm_activity">Pulsihoiatus spordi tegemise ajal</string>
+    <string name="power_mode_watch">Ainult kellaaeg</string>
+    <string name="power_mode_saving">Akut s盲盲stev</string>
+    <string name="power_mode_normal">Tavaline</string>
+    <string name="power_mode_title">Kella re啪iim</string>
+    <string name="prefs_sensors_button_bp_calibration_sum">Vajuta siia, et kalibratsiooniga alustada</string>
+    <string name="prefs_sensors_button_bp_calibration">Kalibratsioon</string>
+    <string name="pref_sensors_bp_calibration_high">Verer玫hk s眉stoolne (k玫rge)</string>
+    <string name="pref_sensors_bp_calibration_low">Verer玫hk diastoolne (madal)</string>
+    <string name="pref_sensors_bp_calibration">Verer玫hu kalibreerimine</string>
+    <string name="pref_title_sensors_altitude">K玫rguse kalibreerimine</string>
+    <string name="pref_header_sensors_calibration">Sensori kalibratsioon</string>
+    <string name="pref_title_device_spec_settings_show_raw_graph">N盲ita toorandmeid aktiivsusgraafikul</string>
+    <string name="pref_summary_device_spec_settings_title_force_time">S眉nkrooni aeg sunniviisiliselt 眉henduse ajal. V玫ib juhtuda, et seierid n盲itavad vale aega!</string>
+    <string name="pref_title_device_spec_settings_force_time">S眉nkrooni aeg sunniviisiliselt</string>
+    <string name="pref_header_device_spec_settings">Seadme seaded</string>
+    <string name="pref_summary_notifications_and_calls_title_shake_reject">Teeb sama mida kella nupp</string>
+    <string name="prefs_notifications_and_calls_shake_reject">Randme raputamisel k玫ne ignoreerimine/hargile panek</string>
+    <string name="pref_summary_notifications_and_calls_title_reject">V盲ljas - ignoreeri, Sees - pane hargile</string>
+    <string name="prefs_notifications_and_calls_reject">Nupuga k玫ne ignoreerimine v玫i hargile panek</string>
+    <string name="pref_header_notifications_and_calls_callhandling">K玫nes盲tted</string>
+    <string name="pref_title_notifications_and_calls_repeat_on_misscall">Korda nii palju minuteid</string>
+    <string name="pref_summary_notifications_and_calls_enable_misscall">Teavita iga minut</string>
+    <string name="pref_notifications_and_calls_enable_misscall">Teavita vastamata k玫nest</string>
+    <string name="prefs_notifications_and_calls_continious_ring">M盲rguanne telefoni helisemise ajal</string>
+    <string name="pref_title_notifications_and_calls_repeat_on_call">Korda k玫ne m盲rguannet</string>
+    <string name="pref_header_notifications_and_calls">M盲rguanded ja k玫ned</string>
+    <string name="title_activity_LenovoWatch_calibration">Watch X Plus kalibreerimine</string>
+    <string name="pref_title_vibration_strength">V盲rina tugevus</string>
+    <string name="pref_summary_relax_firmware_checks">L玫dvenda p眉sivarale rakendatavaid kontrolle</string>
+    <string name="pref_title_sync_caldendar">S眉nkrooni kalendris眉ndmused</string>
+    <string name="pref_summary_sync_calendar">L眉litab sisse kalendri m盲rguanded, isegi siis kui seade pole 眉henduses</string>
+    <string name="pref_summary_custom_deviceicon">N盲ita seadmespetsiifilist ikooni teavitustes Gadgetbridge\'i ikooni asemel kui seade on 眉hendatud</string>
+    <string name="pref_title_weather_summary">Kasutatakse LineageOS\'i s眉steemi poolt pakutava ilmateate jaoks, teised Android\'i versioonid peavad kasutama n盲iteks rakendust \"Weather notification\". Lisainfo Gadgetbridge\'i vikis.</string>
+    <string name="fw_upgrade_notice_amazfit_trex">Olete paigaldamas %s p眉sivara enda Amazfit T-Rex\'ile. 
+\n 
+\nPalun veenduge, et enne paigaldate .fw faili, p盲rast seda .res faili ja viimasena .gps faili. Teie kell taask盲ivitub p盲rast .fw faili paigaldust. 
+\n 
+\nM盲rkus: .res ega .gps faili ei pea paigaldama kui tegu on sama failiga, mis eelmisel uuendusel. 
+\n 
+\nJ脛TKATE OMAL VASTUTUSEL!</string>
+    <string name="discovery_yes_pair">Paarita</string>
+    <string name="discovery_pair_title">Paarita seadmega %1$s\?</string>
+    <string name="discovery_successfully_bonded">Paaritatud %1$s -iga</string>
+    <string name="discovery_enable_bluetooth">L眉lita Bluetooth sisse, et otsingut teostada.</string>
+    <string name="discovery_bonding_failed_immediately">Paaritamine %1$s -ga eba玫nnestus kohe.</string>
+    <string name="mi2_dnd_scheduled">Ajastatud (teatud intervalli tagant)</string>
+    <string name="mi2_dnd_automatic">Automaatne (unej盲lgimine)</string>
+    <string name="mi3_night_mode_sunset">P盲ikeseloojangul</string>
+    <string name="mi2_dnd_off">V盲ljas</string>
+    <string name="mi2_enable_text_notifications_summary">Vajalik on p眉sivaraversioon &gt;= 1.0.1.28 ja Mili_pro.ft* eelnev paigaldamine.</string>
+    <string name="mi2_enable_text_notifications">S玫numite teavitused</string>
+    <string name="mi2_fw_installhandler_fw53_hint">Te peate paigaldama versiooni %1$s enne selle p眉sivara paigaldamist!</string>
+    <string name="miband2_prefs_timeformat">Mi2: Ajaformaat</string>
+    <string name="find_lost_device_you_found_it">Leitud!</string>
+    <string name="find_device_you_found_it">Leidsite!</string>
+    <string name="StringUtils_sender">(%1$s)</string>
+    <string name="pref_screen_notification_profile_alarm_clock">脛ratuskell</string>
+    <string name="timeformat_am_pm">AM/PM (ennel玫una/p盲rastl玫una)</string>
+    <string name="timeformat_24h">24h</string>
+    <string name="unit_imperial">Inglise s眉steem</string>
+    <string name="unit_metric">SI-s眉steem</string>
+    <string name="pref_summary_setup_bt_pairing">L眉lita see v盲lja, kui ilmnevad probleemid paaritamisega</string>
+    <string name="pref_title_setup_bt_pairing">L眉lita Bluetooth\'i paaritamine sisse</string>
+    <string name="weather_notification_label">Veendu, et Gadgetbridge on Weather Notification sisse l眉litatud, et ilmateade teie Pebble\'isse ilmuks.
+\n
+\nRohkem seadistamist pole siin vaja tarvis teha.
+\n
+\nTeie Pebble\'il saate ilmateaterakenduse sisse l眉litada rakenduste haldamise alt.
+\n
+\nToetatud kellastiilid kuvavad ilmateadet automaatselt.</string>
+    <string name="title_activity_pebble_pairing">Pebble paaritamine</string>
+    <string name="ok">OK</string>
+    <string name="activity_db_management_exportimport_label">Eksport ja import</string>
+    <string name="activity_db_management_empty_DB">T眉hjenda andmebaas</string>
+    <string name="mi2_prefs_inactivity_warnings_threshold">Inaktiivsuse maksimaalne kestvus (minutites)</string>
+    <string name="mi2_prefs_inactivity_warnings_summary">Kell v盲riseb, kui sa oled t眉kk aega inaktiivne olnud</string>
+    <string name="mi2_prefs_inactivity_warnings">Inaktiivsushoiatused</string>
+    <string name="companiondevice_pairing_details">L眉litab sisse uue CompanionDevice-t眉眉pi paaritamise (omab efekti Android 8l v玫i uuemal), suurendab stabiilsust kui teenust peab taustal uuesti k盲ivitama, uuesti paaritamine Gadgetbridge\'i kasutades on vajalik, et asi omaks efekti</string>
+    <string name="pref_qhybrid_save_raw_activity_files">Salvestage tegevuste failid t枚枚tlemata</string>
+    <string name="qhybrid_changes_delay_prompt">muudatus v玫ib v玫tta m玫ne hetke鈥�</string>
+    <string name="qhybrid_overwrite_buttons">muuda nuppude k盲itumist</string>
+    <string name="qhybrid_time_shift">ajanihe</string>
+    <string name="qhybrid_goal_in_steps">Sammude eesm盲rk</string>
+    <string name="qhybrid_vibration_strength">v盲rina tugevus:</string>
+    <string name="watch_not_connected">Kell ei ole 眉henduses</string>
+    <string name="widget_5_minutes">5 minutit</string>
+    <string name="widget_set_alarm_after">Sea 盲ratus p盲rast:</string>
+    <string name="widget_listing_label">Staatus ja 盲ratused</string>
+    <string name="appwidget_sleep_alarm_widget_label">脛ratus</string>
+    <string name="appwidget_setting_alarm">Sean 盲ratuse %1$02d:%2$02d -ks</string>
+    <string name="appwidget_text">Zzz</string>
+    <string name="save_configuration">Salvesta seadistus</string>
+    <string name="mode_configuration">Re啪iimiseadistus</string>
+    <string name="filter_mode">Filtreerimisre啪iim</string>
+    <string name="toast_notification_filter_words_empty_hint">Sisesta v盲hemalt 眉ks s玫na</string>
+    <string name="title_activity_notification_filter">Teavituste filter</string>
+    <string name="language_and_region_prefs">Keele ja regioonis盲tted</string>
+    <string name="pref_invalid_frequency_title">Vigane sagedus</string>
+    <string name="preferences_led_color">Valgusdioodi v盲rv</string>
+    <string name="share_log">Jaga logi</string>
+    <string name="preferences_rtl_settings">Paremalt-vasakule keelte tugi</string>
+    <string name="pref_summary_contextual_arabic">L眉litage see sisse, et toetada kontekstuaalset araabia keelt</string>
+    <string name="pref_title_contextual_arabic">Kontekstuaalne araabia keel</string>
+    <string name="title_activity_watch9_calibration">Watch 9 kalibreerimine</string>
+    <string name="title_activity_watch9_pairing">Watch 9 paaritamine</string>
+    <string name="watch9_calibration_button">Kalibreeri</string>
+    <string name="watch9_calibration_hint">Sea aeg mida seade teile praegu kuvab.</string>
+    <string name="watch9_time_seconds">Sekundeid:</string>
+    <string name="watch9_time_hours">Tunde:</string>
+    <string name="watch9_time_minutes">Minuteid:</string>
+    <string name="menuitem_unknown">Teadmata</string>
+    <string name="menuitem_workout">Treening</string>
+    <string name="menuitem_eventreminder">S眉ndmuse meeldetuletus</string>
+    <string name="menuitem_hr">S眉dame l枚枚gisagedus</string>
+    <string name="menuitem_pai">PAI</string>
+    <string name="preferences_qhybrid_settings">Q Hybrid\'i s盲tted</string>
+    <string name="menuitem_alipay">Alipay</string>
+    <string name="menuitem_settings">Seadistused</string>
+    <string name="menuitem_compass">Kompass</string>
+    <string name="menuitem_timer">Taimer</string>
+    <string name="menuitem_alarm">脛ratus</string>
+    <string name="menuitem_breathing">Hingamine</string>
+    <string name="menuitem_weather">Ilmateade</string>
+    <string name="notification_channel_name">Gadgetbridge\'i teavitused</string>
+    <string name="choose_auto_export_location">Ekspordi asukoha valimine</string>
+    <string name="devicetype_bfh16">BFH-16</string>
+    <string name="devicetype_watchxplus">Watch X Plus</string>
+    <string name="devicetype_watchx">Watch X</string>
+    <string name="devicetype_watch9">Watch 9</string>
+    <string name="devicetype_id115">ID115</string>
+    <string name="devicetype_xwatch">XWatch</string>
+    <string name="devicetype_teclast_h30">Teclast H30</string>
+    <string name="devicetype_no1_f1">No.1 F1</string>
+    <string name="devicetype_q8">Q8</string>
+    <string name="devicetype_exrizu_k8">Exrizu K8</string>
+    <string name="updatefirmwareoperation_update_in_progress">P眉sivara paigaldamine</string>
+    <string name="hr_widget_last_notification">Viimane teavitus</string>
+    <string name="error_no_location_access">Juurdep盲盲s asukohale peab olema lubatud ja asukoht sisse l眉litatud skannimise korrektseks toimimiseks</string>
+    <string name="prefs_button_long_press_action_selection_title">Pika nupuvajutuse toiming</string>
+    <string name="prefs_button_variable_actions">脺ksikasjalikud nupuvajutusseaded</string>
+    <string name="prefs_button_triple_press_action_selection_title">3. s眉ndmuse tegevus</string>
+    <string name="prefs_button_single_press_action_selection_title">1. s眉ndmuse tegevus</string>
+    <string name="prefs_button_double_press_action_selection_title">2. s眉ndmuse tegevus</string>
+    <string name="hr_appname_wellness">Tervislikkus</string>
+    <string name="hr_widget_active_minutes">Aktiivseid minuteid</string>
+    <string name="hr_widget_date">Kuup盲ev</string>
+    <string name="hr_widget_steps">Sammud</string>
+    <string name="hr_widget_heart_rate">S眉dame l枚枚gisagedus</string>
+    <string name="qhybrid_offset_time_by">nihuta kellaaega</string>
+    <string name="live_activity_start_your_activity">Alusta tegevusega</string>
+    <string name="live_activity_steps_per_minute_history">Samme minuti kohta varem</string>
+    <string name="live_activity_total_steps">Samme kokku</string>
+    <string name="live_activity_current_steps_per_minute">Praegu samme minutis</string>
+    <string name="pref_summary_low_latency_fw_update">See v玫ib aidata seadmetega, millega p眉sivara paigaldamine eba玫nnestub.</string>
+    <string name="pref_title_low_latency_fw_update">Kasuta madala latentsiga re啪iimi p眉sivara paigaldamiseks</string>
+    <string name="pref_title_dont_ack_transfer">脛ra ACK-i aktiivsusinfo s眉nkroonimist</string>
+    <string name="overstep">Samme 眉le: %1$d</string>
+    <string name="lack_of_step">Puuduj盲盲vaid samme: %1$d</string>
+    <string name="weeksteps_today_steps_description">T盲naseid samme, sihtm盲rk: %1$s</string>
+    <string name="liveactivity_live_activity">Tegevus reaalajas</string>
+    <string name="weeksleepchart_today_sleep_description">T盲na枚ine magamine, eesm盲rk: %1$s</string>
+    <string name="notif_battery_low_extended">%1$s aku protsent on madal: %2$s%%</string>
+    <string name="notif_battery_low_percent">%1$s akust on j盲盲nud %2$s%%</string>
+    <string name="installer_activity_wait_while_determining_status">Palun oodake kuni installatsioonistaatust tuvastatakse鈥�</string>
+    <string name="pbw_install_handler_hw_revision_mismatch">Antud p眉sivara ei saa installida: See ei sobi teie Pebble\'i riistvaraversioonile.</string>
+    <string name="pbw_install_handler_unable_to_install">Antud faili ei saa installida: %1$s</string>
+    <string name="installer_activity_unable_to_find_handler">Selle faili installimiseks ei leitud t枚枚tlejat.</string>
+    <string name="menuitem_nfc">NFC</string>
+    <string name="menuitem_more">Rohkem</string>
+    <string name="menuitem_music">Muusika</string>
+    <string name="preferences_fm_frequency">FM sagedus</string>
+    <string name="no_data">Andmed puuduvad</string>
+    <string name="warning">Hoiatus!</string>
+    <string name="menuitem_worldclock">Maailma kellaaeg</string>
+    <string name="off">V盲ljas</string>
+    <string name="on">Sees</string>
+    <string name="Delete">Kustuta</string>
+    <string name="Cancel">T眉hista</string>
+    <string name="activity_type_unknown">Teadmata tegevus</string>
+    <string name="activity_type_not_worn">Seadet ei kantud</string>
+    <string name="activity_DB_import_button">Impordi andmebaas</string>
+    <string name="activity_DB_ExportButton">Ekspordi andmebaas</string>
+    <string name="activity_DB_empty_button">T眉hjenda andmebaas</string>
+    <string name="devicetype_pebble">Pebble</string>
+    <string name="devicetype_roidmi">Roidmi</string>
+    <string name="devicetype_banglejs">Bangle.js</string>
+    <string name="devicetype_makibes_hr3">Makibes HR3</string>
+    <string name="devicetype_mijia_lywsd02">Mijia tark kell</string>
+    <string name="devicetype_test">Testseade</string>
+    <string name="devicetype_miband5">Mi Band 5</string>
+    <string name="devicetype_miband4">Mi Band 4</string>
+    <string name="devicetype_miband2">Mi Band 2</string>
+    <string name="devicetype_miband">Mi Band</string>
+    <string name="devicetype_miband3">Mi Band 3</string>
+    <string name="devicetype_mykronoz_zetime">MyKronoz ZeTime</string>
+    <string name="devicetype_qhybrid">Fossil Q Hybrid</string>
+    <string name="menuitem_stress">Stress</string>
+    <string name="devicetype_tlw64">TLW64</string>
+    <string name="activity_type_not_measured">Ei ole m玫玫detud</string>
+    <string name="_pebble_watch_reply">Vasta</string>
+    <string name="_pebble_watch_mute">Vaigista</string>
+    <string name="discovery_dont_pair">脛ra paarita</string>
+    <string name="discovery_trying_to_connect_to">脺ritan 眉henduda seadmega: %1$s</string>
+    <string name="discovery_attempting_to_pair">脺ritan paaritada %1$s -ga</string>
+    <string name="maximum_duration">Kestvus</string>
+    <string name="menuitem_activity">Tegevused</string>
+    <string name="menuitem_notifications">Teavitused</string>
+    <string name="menuitem_status">Staatus</string>
+    <string name="devicetype_amazfit_gts">Amazfit GTS</string>
+    <string name="devicetype_amazfit_bips">Amazfit Bip S</string>
+    <string name="devicetype_amazfit_gtr">Amazfit GTR</string>
+    <string name="devicetype_amazfit_cor2">Amazfit Cor 2</string>
+    <string name="devicetype_amazfit_cor">Amazfit Cor</string>
+    <string name="devicetype_amazfit_bip_lite">Amazfit Bip Lite</string>
+    <string name="devicetype_amazfit_bip">Amazfit Bip</string>
+    <string name="devicetype_itag">iTag</string>
+    <string name="devicetype_miscale2">Mi Scale 2</string>
+    <string name="devicetype_casiogb6900">Casio GB-6900</string>
+    <string name="devicetype_y5">Y5</string>
+    <string name="devicetype_roidmi3">Roidmi 3</string>
+    <string name="about_additional_contributions">Suured t盲nud ka k玫ikidele teistele loetlemata abistajatele nende koodi, toe, ideede, motivatsiooni, vearaportite ja raha eest鈥� 鉁�</string>
+    <string name="about_additional_device_support">T盲iendavate seadmete tugi</string>
+    <string name="about_contributors">Kaasaaitajad</string>
+    <string name="about_core_team_title">Tuumiktiim (j盲rjestatud esimese koodirea kuup盲eva j盲rgi)</string>
+    <string name="about_description_generic">Tasuta ja pilvevaba asendus teie kantavate nutiseadmete tootjate rakendustele.</string>
+    <string name="about_activity_title_generic">Gadgetbridge\'ist</string>
+    <string name="about_title">Rakendusest</string>
+    <string name="hr_appname_stopwatch">Stopper</string>
+    <string name="hr_appname_workout">Trenn</string>
+    <string name="hr_widget_nothing">T眉hjus</string>
+    <string name="hr_widget_weather">Ilm</string>
+    <string name="hr_widget_battery">Aku</string>
+    <string name="hr_widget_calories">Kaloreid</string>
+    <string name="dbmanagementactivity_delete_old_activity_db">Kustuta vana tegevuste andmebaas\?</string>
+    <string name="dbmanagementactivity_db_deletion_failed">Andmete kustutamine eba玫nnestus.</string>
+    <string name="dbmanagementactivity_database_successfully_deleted">Andmed kustutatud.</string>
+    <string name="prefs_find_phone_duration">Heliseme kestvus sekundites</string>
+    <string name="updatefirmwareoperation_update_complete_rebooting">P眉sivara paigaldus 玫nnestus, taask盲ivitan seadme鈥�</string>
+    <string name="updatefirmwareoperation_metadata_updateproblem">Probleem p眉sivara metaandmete 眉lekandmisega</string>
+    <string name="updatefirmwareoperation_updateproblem_do_not_reboot">Probleem p眉sivara uuendamisega. 脛RA TAASK脛IVITA ENDA Mi Band\'i!</string>
+    <string name="pbwinstallhandler_incorrect_hw_revision">Riistvaraversiooni erinevus!</string>
+    <string name="pbwinstallhandler_correct_hw_revision">脮ige riistvara versioon</string>
+    <string name="notif_export_failed_title">Andmebaasi eksport eba玫nnestus! Kontrolli enda s盲tteid.</string>
+    <string name="czesh">T拧ehhi</string>
+    <string name="swedish">Rootsi</string>
+    <string name="hebrew">Heebrea</string>
+    <string name="greek">Kreeka</string>
+    <string name="hungarian">Ungari</string>
+    <string name="widget_1_hour">1 tund</string>
+    <string name="widget_20_minutes">20 minutit</string>
+    <string name="widget_10_minutes">10 minutit</string>
+    <string name="error_location_enabled_mandatory">Asukoha leidmine peab olema sisse l眉litatud seadmete otsimiseks</string>
+    <string name="ignore_bonded_devices_description">Kui see valik on sisse l眉litatud siis juba paaritatud seadmeid ei kuvata skanneerimisel</string>
+    <string name="ignore_bonded_devices">Ignoreeri paaritatud seadmeid</string>
+    <string name="error_retrieving_devices_database">Viga seadmete andmebaasist lugemisel</string>
+    <string name="error_setting_alias">Viga teise nime seadmisel:</string>
+    <string name="error_exporting_device_preferences">Viga seadmespetsiifliste s盲tete eksportimisel</string>
+    <string name="pref_check_permission_status_summary">Kontrolli ja k眉si ligip盲盲se isegi siis kui neid pole koheselt vaja. L眉lita see v盲lja kui su kasutatavad seadmed p盲riselt mitte 眉htegi nendest funktsionaalsustest ei kasuta. Ligip盲盲su mitteandmine v玫ib probleeme p玫hjustada!</string>
+    <string name="pref_check_permission_status">Kontrolli ligip盲盲sude staatust</string>
+    <string name="error_background_service_reason">Taustal t枚枚tava teenuse k盲ivitamine eba玫nnestus sest tekkis erind:</string>
+    <string name="device_requires_key">V脮TI VAJALIK</string>
+    <string name="device_is_currently_bonded">JUBA PAARITATUD</string>
+    <string name="error_background_service_reason_truncated">Taustal t枚枚tava teenuse k盲ivitamine eba玫nnestus sest鈥�</string>
+    <string name="error_background_service">Taustal t枚枚tava teenuse k盲ivitamine eba玫nnestus</string>
+    <string name="companiondevice_pairing">CompanionDevice-t眉眉pi paaritamine</string>
+    <string name="require_location_provider">Asukoht peab olema sisse l眉litatud</string>
+    <string name="error_version_check_extreme_caution">HOIATUS: Viga versiooniinfot kontrollides! Te ei tohiks j盲tkata! N盲gin versiooninime \"%s\"</string>
+    <string name="permission_granting_mandatory">K玫ik need ligip盲盲sud on vajalikud ning rakendus v玫ib valesti k盲ituda, kui ligip盲盲su ei anna</string>
+    <plurals name="widget_alarm_target_hours">
+        <item quantity="one">%d tund</item>
+        <item quantity="other">%d tundi</item>
+    </plurals>
+    <string name="cannot_connect">Ei saa 眉hendust luua: %1$s</string>
+    <string name="dbaccess_error_executing">Viga k盲itades \'%1$s\'</string>
+    <string name="user_feedback_miband_activity_data_transfer">Hakkan t玫stma %1$s andmed, alustades %2$s</string>
+    <string name="user_feedback_miband_set_alarms_failed">Tekkis viga 盲ratusi seades, proovi uuesti.</string>
+    <string name="alarm_snooze">Uinak</string>
+    <string name="stats_title">Kiirustsoonid</string>
+    <string name="prefs_title_heartrate_measurement_interval">Terve p盲eva kestev pulsim玫玫tmine</string>
+    <string name="watch9_pairing_tap_hint">Kui su kell v盲riseb, raputa seadet v玫i vajuta selle nupule.</string>
+    <string name="discovery_connected_devices_hint">Muuda enda seade n盲htavaks. Juba paaritatud seadmeid ilmselt ei kuvata. L眉lita sisse ja luba ligip盲盲s asukohale. L眉lita v盲lja Privacy Guard sest see v玫ib su telefoni taask盲ivitama panna. Kui 眉htegi seadet ei leita, proovi uuesti p盲rast taask盲ivitust.</string>
+    <string name="fw_upgrade_notice_miband5">Olete installimas %s p眉sivara enda Mi Band 5\'le.
+\n
+\nPaigalda .fw fail ja seej盲rel .res fail. Teie kell taask盲ivitub p盲rast .fw faili paigaldust.
+\n
+\nM盲rkus: .res faili ei pea paigaldama, kui tegu on juba paigaldatud versiooniga.
+\n
+\nJ脛TKATE OMAL VASTUTUSEL!</string>
+    <string name="share">Jaga</string>
+    <string name="select_all">Vali k玫ik</string>
+    <string name="activity_type_exercise">Trenn</string>
+    <string name="activity_type_treadmill">Jooksulint</string>
+    <string name="activity_type_biking">Jalgrattas玫it</string>
+    <string name="activity_summaries">Tegevused</string>
+    <string name="activity_db_management_merge_old_title">Vana versiooni andmebaasi kustutamine</string>
+    <string name="title_activity_db_management">Andmebaasihaldus</string>
+    <string name="action_db_management">Andmebaasihaldus</string>
+    <string name="activity_prefs_activetime_minutes">P盲evane eesm盲rk: aktiivsusminuteid</string>
+    <string name="activity_prefs_distance_meters">P盲evane eesm盲rk: vahemaa meetrites</string>
+    <string name="activity_prefs_calories_burnt">P盲evane eesm盲rk: p玫letatud kaloreid</string>
+    <string name="device_fw">P眉sivaraversioon: %1$s</string>
+    <string name="device_hw">Riistvaraversioon: %1$s</string>
+    <string name="activity_prefs_sleep_duration">Eelistatud magamise kestvus</string>
+    <string name="pref_chart_sleep_rolling_24_off">L玫unast l玫unani</string>
+    <string name="weeksleepchart_sleep_a_month">Und kuus</string>
+    <string name="dbmanagementactivity_error_exporting_shared">Viga s盲tteid eksportides: %1$s</string>
+    <string name="dbmanagementactivity_error_exporting_db">Viga andmebaasi eksportides: %1$s</string>
+    <string name="dbmanagementactivity_exported_to">Eksporditud: %1$s</string>
+    <string name="dbmanagementactivvity_cannot_access_export_path">Ligip盲盲s ekspordi sihtkohta puudub. Kontakteeru arendajatega.</string>
+    <string name="dbmanagementactivity_import_data_title">Impordin andmed\?</string>
+    <string name="dbmanagementactivity_import_successful">Imporditud.</string>
+    <string name="dbmanagementactivity_error_importing_shared">Viga seadmete s盲tteid importides: %1$s</string>
+    <string name="dbmanagementactivity_error_importing_db">Viga andmebaasi importides: %1$s</string>
+    <string name="mi2_prefs_button_press_count_max_delay">Maksimaalne ajavahe vajutustel</string>
+    <string name="prefs_disconnect_notification">脺henduse katkemise teavitused</string>
+    <string name="prefs_find_phone_summary">Kasuta Band\'i, et m盲ngida teie telefoni helinat.</string>
+    <string name="prefs_enable_find_phone">L眉lita sisse \\\'Otsi telefoni\\\'</string>
+    <string name="prefs_find_phone">Otsi telefoni</string>
+    <string name="miband_prefs_device_time_offset_hours">Seadme kellaaja nihe (nt. vahetustega t枚枚 jaoks)</string>
+    <string name="miband_prefs_hr_sleep_detection">Kasuta s眉dame l枚枚gisageduse andurit, et muuta unetuvastust paremaks</string>
+    <string name="miband_prefs_reserve_alarm_calendar">Mitu 盲ratust on reserveeritud tulevikus toimuvate s眉ndmuste jaoks</string>
+    <string name="fwinstaller_firmware_not_compatible_to_device">See p眉sivara ei ole sobilik sellele seadmele</string>
+    <string name="you_slept">Te magasite %1$s kuni %2$s</string>
+    <string name="live_activity_max_heart_rate">Praegune / Maksimaalne s眉dame l枚枚gisagedus: %1$d / %2$d</string>
+    <string name="pref_summary_keep_data_on_device">Hoiab aktiivsusandmed Mi Band\'il isegi p盲rast s眉nkroonimist. Kasulik, kui kasutate GB koos teiste sarnaste rakendustega.</string>
+    <string name="pref_summary_dont_ack_transfers">Kui Band\'ile kinnitust ei saada siis aktiivsusandmeid ei kustutata. Kasulik, kui kasutate GBd koos teiste sarnaste rakendustega.</string>
+    <string name="steps">Samme</string>
+    <string name="caloriesBurnt">Kaloreid</string>
+    <string name="activity_filter_name_contains">Nimi</string>
+    <string name="activity_filter_filter_title">Filter</string>
+    <string name="activity_summaries_statistics">Statistika</string>
+    <string name="sonyswr12_settings_title">Sony SWR12 s盲tted</string>
+    <string name="sonyswr12_settings_low_vibration">Madal vibratsioon on lubatud</string>
+    <string name="sonyswr12_settings_stamina">Energias盲盲sture啪iim on sisse l眉litatud</string>
+    <string name="sonyswr12_settings_alarm_interval">Nutika h盲ire intervall minutites</string>
+    <string name="sonyswr12_settings_low_vibration_summary">Luba madala vibratsiooni intensiivsus ribal</string>
+    <string name="sonyswr12_settings_stamina_summary">Energias盲盲sture啪iim l眉litab s眉dame l枚枚gisageduse perioodilise automaatse m玫玫tmise v盲lja, pikendades seega t枚枚aega</string>
+    <string name="sonyswr12_settings_alarm_interval_summary">Nutika h盲ire intervall on intervall enne installitud alarmi. Selles intervallis 眉ritab seade kasutaja 盲rkamiseks tuvastada une kergemat faasi</string>
+</resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-eu/strings.xml b/app/src/main/res/values-eu/strings.xml
new file mode 100644
index 0000000..a6b3dae
--- /dev/null
+++ b/app/src/main/res/values-eu/strings.xml
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources></resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-fa/strings.xml b/app/src/main/res/values-fa/strings.xml
new file mode 100644
index 0000000..0573b44
--- /dev/null
+++ b/app/src/main/res/values-fa/strings.xml
@@ -0,0 +1,232 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <string name="application_name_generic">诏噩鬲鈥屫ㄘ臂屫�</string>
+    <string name="title_activity_controlcenter_generic">诏噩鬲鈥屫ㄘ臂屫�</string>
+    <string name="action_settings">鬲賳馗蹖賲丕鬲</string>
+    <string name="action_debug">丕卮讴丕賱鈥屫藏й屰�</string>
+    <string name="action_quit">禺乇賵噩</string>
+    <string name="action_donate">丕賴丿丕蹖 讴賲讴 賲丕賱蹖</string>
+    <string name="controlcenter_fetch_activity_data">賴賲诏丕賲鈥屫池ж槽�</string>
+    <string name="controlcenter_find_device">倬蹖丿丕 讴乇丿賳 丿爻鬲诏丕賴 诏賲 卮丿賴</string>
+    <string name="controlcenter_take_screenshot">诏乇賮鬲賳 毓讴爻 丕夭 氐賮丨賴 賳賲丕蹖卮</string>
+    <string name="controlcenter_change_led_color">鬲睾蹖蹖乇 乇賳诏 丕賱鈥屫й屸�屫�</string>
+    <string name="controlcenter_change_fm_frequency">鬲睾蹖蹖乇 賮乇讴丕賳爻 乇丕丿蹖賵 丕賮鈥屫з�</string>
+    <string name="controlcenter_connect">丕鬲氐丕賱鈥�</string>
+    <string name="controlcenter_disconnect">賯胤毓</string>
+    <string name="controlcenter_delete_device">倬丕讴 讴乇丿賳 丿爻鬲诏丕賴</string>
+    <string name="controlcenter_delete_device_name">倬丕讴 讴乇丿賳 %1$s鈥屬囏�</string>
+    <string name="controlcenter_delete_device_dialogmessage">丕蹖賳 丕賯丿丕賲 丿爻鬲诏丕賴 賵 鬲賲丕賲 丕胤賱丕毓丕鬲 賲乇亘賵胤 乇丕 倬丕讴 禺賵丕賴丿 讴乇丿!</string>
+    <string name="controlcenter_navigation_drawer_open">亘丕夭 讴乇丿賳 賳賵丕乇 賲賳賵</string>
+    <string name="controlcenter_navigation_drawer_close">亘爻鬲賳 賳賵丕乇 賲賳賵</string>
+    <string name="controlcenter_snackbar_need_longpress">乇賵蹖 賳賵丕乇 胤賵賱丕賳蹖 賮卮丕乇 丿賴蹖丿 鬲丕 賯胤毓 卮賵丿</string>
+    <string name="controlcenter_snackbar_disconnecting">丿乇 丨丕賱 賯胤毓</string>
+    <string name="controlcenter_snackbar_connecting">丿乇 丨丕賱 丕鬲氐丕賱鈥�</string>
+    <string name="controlcenter_snackbar_requested_screenshot">诏乇賮鬲賳 毓讴爻 丕夭 氐賮丨賴 賳賲丕蹖卮 丿爻鬲诏丕賴</string>
+    <string name="controlcenter_calibrate_device">讴丕賱蹖亘乇賴 讴乇丿賳 丿爻鬲诏丕賴</string>
+    <string name="title_activity_debug">丕卮讴丕賱鈥屫藏й屰�</string>
+    <string name="debugactivity_really_factoryreset_title">丌蹖丕 丕夭 亘丕夭賳卮丕賳蹖 丿爻鬲诏丕賴 亘賴 丨丕賱鬲 讴丕乇禺丕賳賴 賲胤賲卅賳蹖丿責</string>
+    <string name="debugactivity_really_factoryreset">亘丕夭賳卮丕賳蹖 亘賴 賳爻禺賴鈥屰� 讴丕乇禺丕賳賴 鬲賲丕賲 丕胤賱丕毓丕鬲 丿爻鬲诏丕賴鈥屬囏й� 賲鬲氐賱 乇丕 倬丕讴 禺賵丕賴丿 讴乇丿 (丿乇氐賵乇鬲 倬卮鬲蹖亘丕賳蹖). 丿爻鬲诏丕賴鈥屬囏й� Xiaomi/Huami 賴賲趩賳蹖賳 MAC address賳蹖夭 鬲睾蹖蹖乇 禺賵丕賴丿 讴乇丿 賵 亘賴 氐賵乇鬲 丿爻鬲诏丕賴 噩丿蹖丿 丿乇 诏噩鬲鈥屫ㄘ臂屫� 卮賳丕爻丕蹖蹖 禺賵丕賴賳丿 卮丿.</string>
+    <string name="title_activity_appmanager">賲丿蹖乇 亘乇賳丕賲賴</string>
+    <string name="appmanager_cached_watchapps_watchfaces">亘乇賳丕賲賴鈥屬囏й� 丿乇賵賳 丨丕賮馗賴鈥屰� 倬賳賴丕賳 (cache)</string>
+    <string name="appmanager_installed_watchapps">亘乇賳丕賲賴鈥屬囏й� 賳氐亘 卮丿賴</string>
+    <string name="appmanager_installed_watchfaces">賳賲丕蹖賴鈥屬囏й� 爻丕毓鬲 賳氐亘 卮丿賴</string>
+    <string name="appmananger_app_delete">倬丕讴 讴乇丿賳</string>
+    <string name="appmananger_app_delete_cache">倬丕讴 讴乇丿賳 賵 丨匕賮 丕夭 丨丕賮馗賴鈥屰� 倬賳賴丕賳 (cache)</string>
+    <string name="appmananger_app_reinstall">亘丕夭賳氐亘</string>
+    <string name="appmanager_app_openinstore">噩爻鬲噩賵 丿乇 賮乇賵卮诏丕賴 丕倬 Pebble</string>
+    <string name="appmanager_health_activate">賮毓丕賱鈥屫池ж槽�</string>
+    <string name="appmanager_health_deactivate">睾蹖乇賮毓丕賱鈥屫池ж槽�</string>
+    <string name="appmanager_hrm_activate">賮毓丕賱鈥屫池ж槽� 囟乇亘丕賳鈥屫迟嗀� (HRM)</string>
+    <string name="appmanager_hrm_deactivate">睾蹖乇賮毓丕賱鈥屫池ж槽� 囟乇亘丕賳鈥屫迟嗀� (HRM)</string>
+    <string name="appmanager_weather_activate">賮毓丕賱鈥屫池ж槽� 亘乇賳丕賲賴鈥屰� 賴賵丕卮賳丕爻蹖 爻蹖爻鬲賲</string>
+    <string name="appmanager_weather_deactivate">睾蹖乇賮毓丕賱鈥屫池ж槽� 亘乇賳丕賲賴鈥屰� 賴賵丕卮賳丕爻蹖 爻蹖爻鬲賲</string>
+    <string name="appmanager_weather_install_provider">賳氐亘 亘乇賳丕賲賴鈥屰� 丌诏丕賴蹖 (Notification) 賴賵丕卮賳丕爻蹖</string>
+    <string name="app_configure">倬蹖讴乇亘賳丿蹖</string>
+    <string name="app_move_to_top">丕賳鬲賯丕賱 亘賴 亘丕賱丕</string>
+    <string name="blacklist_all_for_notifications">賴賲賴 乇丕 亘賴 賱蹖爻鬲 爻蹖丕賴 丌诏丕賴蹖鈥屬囏� 丕囟丕賮賴 讴賳</string>
+    <string name="whitelist_all_for_notifications">賴賲賴 乇丕 丕夭 賱蹖爻鬲 爻蹖丕賴 丌诏丕賴蹖 禺丕乇噩 讴賳</string>
+    <string name="title_activity_calblacklist">乇禺丿丕丿賴丕蹖 丿乇賵賳 賱蹖爻鬲 爻蹖丕賴</string>
+    <string name="title_activity_fw_app_insaller">賳氐亘 讴賳賳丿賴鈥屰� 賮乇賲鈥屬堐屫� (FW)/亘乇賳丕賲賴</string>
+    <string name="fw_upgrade_notice">卮賲丕 丿乇 丨丕賱 賳氐亘 賮乇賲賵乇 %s 亘賴 噩丕蹖 賳爻禺賴鈥屰� 賳氐亘 卮丿賴 亘乇 乇賵蹖 丿爻鬲诏丕賴 Mi Band 禺賵丿 賴爻鬲蹖丿.</string>
+    <string name="fw_upgrade_notice_amazfitbip">卮賲丕 丿乇 丨丕賱 賳氐亘 賮乇賲賵蹖乇 %s 亘賴 噩丕蹖 賳爻禺賴鈥屰� 賳氐亘 卮丿賴 亘乇乇賵蹖 丿爻鬲诏丕賴 Amazfit Bip 禺賵丿 賴爻鬲蹖丿.
+\n
+\n賱胤賮丕 賲胤賲卅賳 卮賵蹖丿 讴賴 丕亘鬲丿丕 賮丕蹖賱 .fw貙 爻倬爻 賮丕蹖賱 .res 賵 丿乇 丕賳鬲賴丕 賮丕蹖賱 .gps 乇丕 賳氐亘 讴賳蹖丿. 丿爻鬲诏丕賴 卮賲丕 倬爻 丕夭 賳氐亘 賮丕蹖賱 .fw 乇丕賴鈥屫з嗀ж槽� 賲噩丿丿 賲蹖鈥屫促堌�.
+\n
+\n賳讴鬲賴: 卮賲丕 賲噩亘賵乇 亘賴 賳氐亘 賮丕蹖賱鈥屬囏й� .res 賵 .gps 賳蹖爻鬲蹖丿 丕诏乇 丌賳賴丕 亘丕 賮丕蹖賱鈥屬囏й屰� 讴賴 賯亘賱丕 賳氐亘 卮丿賴鈥屫з嗀� 蹖讴蹖 賴爻鬲賳丿.
+\n
+\n亘丕 賲爻卅賵賱蹖鬲 禺賵丿鬲丕賳 丕蹖賳 讴丕乇 乇丕 丕賳噩丕賲 賲蹖鈥屫囒屫�!</string>
+    <string name="fw_upgrade_notice_amazfitcor">卮賲丕 丿乇 丨丕賱 賳氐亘 賮乇賲賵蹖乇 %s 亘賴 噩丕蹖 賳爻禺賴鈥屰� 賳氐亘 卮丿賴 亘乇乇賵蹖 丿爻鬲诏丕賴 Amazfit Cor 禺賵丿 賴爻鬲蹖丿.
+\n
+\n賱胤賮丕 賲胤賲卅賳 卮賵蹖丿 讴賴 丕亘鬲丿丕 賮丕蹖賱 .fw貙 爻倬爻 賮丕蹖賱 .res 賵 丿乇 丕賳鬲賴丕 賮丕蹖賱 .gps 乇丕 賳氐亘 讴賳蹖丿. 丿爻鬲诏丕賴 卮賲丕 倬爻 丕夭 賳氐亘 賮丕蹖賱 .fw 乇丕賴鈥屫з嗀ж槽� 賲噩丿丿 賲蹖鈥屫促堌�.
+\n
+\n賳讴鬲賴: 卮賲丕 賲噩亘賵乇 亘賴 賳氐亘 賮丕蹖賱鈥屬囏й� .res 賵 .gps 賳蹖爻鬲蹖丿 丕诏乇 丌賳賴丕 亘丕 賮丕蹖賱鈥屬囏й屰� 讴賴 賯亘賱丕 賳氐亘 卮丿賴鈥屫з嗀� 蹖讴蹖 賴爻鬲賳丿.
+\n
+\n亘丕 賲爻卅賵賱蹖鬲 禺賵丿鬲丕賳 丕蹖賳 讴丕乇 乇丕 丕賳噩丕賲 賲蹖鈥屫囒屫�!</string>
+    <string name="fw_upgrade_notice_miband3">卮賲丕 丿乇 丨丕賱 賳氐亘 賮乇賲賵蹖乇 %s 亘賴 噩丕蹖 賳爻禺賴鈥屰� 賳氐亘 卮丿賴 亘乇乇賵蹖 丿爻鬲诏丕賴 Mi Band 3 禺賵丿 賴爻鬲蹖丿.
+\n
+\n賱胤賮丕 賲胤賲卅賳 卮賵蹖丿 讴賴 丕亘鬲丿丕 賮丕蹖賱 .fw貙 爻倬爻 賮丕蹖賱 .res 賵 丿乇 丕賳鬲賴丕 賮丕蹖賱 .gps 乇丕 賳氐亘 讴賳蹖丿. 丿爻鬲诏丕賴 卮賲丕 倬爻 丕夭 賳氐亘 賮丕蹖賱 .fw 乇丕賴鈥屫з嗀ж槽� 賲噩丿丿 賲蹖鈥屫促堌�.
+\n
+\n賳讴鬲賴: 卮賲丕 賲噩亘賵乇 亘賴 賳氐亘 賮丕蹖賱鈥屬囏й� .res 賵 .gps 賳蹖爻鬲蹖丿 丕诏乇 丌賳賴丕 亘丕 賮丕蹖賱鈥屬囏й屰� 讴賴 賯亘賱丕 賳氐亘 卮丿賴鈥屫з嗀� 蹖讴蹖 賴爻鬲賳丿.
+\n
+\n亘丕 賲爻卅賵賱蹖鬲 禺賵丿鬲丕賳 丕蹖賳 讴丕乇 乇丕 丕賳噩丕賲 賲蹖鈥屫囒屫�!</string>
+    <string name="fw_multi_upgrade_notice">卮賲丕 丿乇 丨丕賱 賳氐亘 賮乇賲賵蹖乇 %1$s 賵 %2$s 亘賴 噩丕蹖 賳爻禺賴鈥屬囏й� 賳氐亘 卮丿賴 亘乇乇賵蹖 丿爻鬲诏丕賴 Mi Band 禺賵丿 賴爻鬲蹖丿.</string>
+    <string name="miband_firmware_known">丕蹖賳 賳爻禺賴鈥屰� 賮乇賲賵蹖乇 鬲爻鬲 卮丿賴 賵 亘丕 诏噩鬲鈥屫ㄘ臂屫� 爻丕夭诏丕乇 丕爻鬲.</string>
+    <string name="miband_firmware_unknown_warning">丕蹖賳 爻蹖爻鬲賲 毓丕賲賱 鬲爻鬲 賳卮丿賴 丕爻鬲 賵 賲賲讴賳 丕爻鬲 亘丕 Gadgetbridge 爻丕夭诏丕乇 賳亘丕卮丿.
+\n
+\n丕夭 趩卮賲讴 夭丿賳 丌賳 賲丨乇賵賲 賲蹖 卮賵蹖丿!</string>
+    <string name="miband_firmware_suggest_whitelist">丕诏乇 賴賲趩賳丕賳 賲丕蹖賱 亘賴 賳氐亘 賴爻鬲蹖丿 賵 亘毓丿 丕夭 賳氐亘 賴賲賴 趩蹖夭 丿乇爻鬲 讴丕乇 讴乇丿貙 賱胤賮丕 亘賴 鬲賵爻毓賴鈥屫囐嗀団�屬囏й� 诏噩鬲鈥屫ㄘ臂屫� 丕胤賱丕毓 丿賴蹖丿 鬲丕 賮乇賲賵蹖乇 %s 乇丕 亘賴 賱蹖爻鬲 爻賮蹖丿 丕囟丕賮賴 讴賳賳丿.</string>
+    <string name="title_activity_settings">鬲賳馗蹖賲丕鬲</string>
+    <string name="pref_header_general">鬲賳馗蹖賲丕鬲 毓賲賵賲蹖</string>
+    <string name="pref_title_general_autoconnectonbluetooth">丕鬲氐丕賱 亘賴 诏噩鬲鈥屫ㄘ臂屫� 賵賯鬲蹖 讴賴 亘賱賵鬲賵孬 乇賵卮賳 丕爻鬲</string>
+    <string name="pref_title_general_autostartonboot">乇丕賴鈥屫з嗀ж槽� 丕鬲賵賲丕鬲蹖讴</string>
+    <string name="pref_title_general_autoreconnect">丕鬲氐丕賱鈥屬呚� 丕鬲賵賲丕鬲蹖讴</string>
+    <string name="pref_title_audio_player">倬禺卮 讴賳賳丿賴鈥屰� 賲賵爻蹖賯蹖 賲賵乇丿 毓賱丕賯賴</string>
+    <string name="pref_default">倬蹖卮鈥屬佖必�</string>
+    <string name="pref_title_charts_swipe">賮毓丕賱 爻丕夭蹖 丕賲讴丕賳 讴卮蹖丿賳 亘賴 趩倬/乇丕爻鬲 丿乇 賯爻賲鬲 賳賲賵丿丕乇賴丕蹖 亘乇賳丕賲賴</string>
+    <string name="pref_header_datetime">鬲丕乇蹖禺 賵 爻丕毓鬲</string>
+    <string name="pref_title_datetime_syctimeonconnect">賴賲夭賲丕賳鈥屫池ж槽� 爻丕毓鬲</string>
+    <string name="pref_summary_datetime_syctimeonconnect">賴賲夭賲丕賳鈥屫池ж槽� 爻丕毓鬲 亘丕 诏噩鬲鈥屫ㄘ臂屫� 賵賯鬲蹖 賲鬲氐賱 賲蹖鈥屫促堌� 賵 賴賲趩賳蹖賳 賵賯鬲蹖 賲賵賯毓蹖鬲 夭賲丕賳蹖 亘乇乇賵蹖 诏賵卮蹖 鬲睾蹖蹖乇 賲蹖鈥屭┵嗀�</string>
+    <string name="pref_title_theme">倬賵爻鬲賴</string>
+    <string name="pref_theme_light">乇賵卮賳</string>
+    <string name="pref_theme_dark">鬲丕乇蹖讴</string>
+    <string name="pref_title_language">夭亘丕賳</string>
+    <string name="pref_title_minimize_priority">賲禺賮蹖 讴乇丿賳 丕毓賱丕賳 诏噩鬲鈥屫ㄘ臂屫�</string>
+    <string name="pref_summary_minimize_priority_off">丌蹖讴賵賳 亘乇賳丕賲賴 丿乇 賳賵丕乇 丕毓賱丕賳鈥屬囏� 賵 氐賮丨賴鈥屰� 賯賮賱 賳卮丕賳 丿丕丿賴 賲蹖鈥屫促堌�</string>
+    <string name="pref_summary_minimize_priority_on">丌蹖讴賵賳 亘乇賳丕賲賴 丿乇 賳賵丕乇 丕毓賱丕賳鈥屬囏� 賵 氐賮丨賴鈥屰� 賯賮賱 賳卮丕賳 丿丕丿賴 賳賲蹖鈥屫促堌�</string>
+    <string name="pref_header_notifications">丕毓賱丕賳鈥屬囏�</string>
+    <string name="pref_title_notifications_repetitions">鬲讴乇丕乇</string>
+    <string name="pref_title_notifications_call">鬲賲丕爻 鬲賱賮賳蹖</string>
+    <string name="pref_title_support_voip_calls">賮毓丕賱鈥屫池ж槽� VoIP 亘賵爻蹖賱賴鈥屰� 亘乇賳丕賲賴鈥屬囏�</string>
+    <string name="pref_title_notifications_sms">倬蹖丕賲讴</string>
+    <string name="pref_title_notifications_timeout">丨丿丕賯賱 夭賲丕賳 亘蹖賳 丕毓賱丕賳鈥屬囏�</string>
+    <string name="pref_title_notifications_pebblemsg">倬蹖丕賲鈥屬囏й� Pebble</string>
+    <string name="pref_summary_notifications_pebblemsg">倬卮鬲蹖亘丕賳蹖 丕夭 亘乇賳丕賲賴鈥屬囏й屰� 讴賴 亘賴 Pebble 亘賵爻蹖賱賴鈥屰� PebbleKit 倬蹖丕賲 賲蹖鈥屬佖必池嗀�.</string>
+    <string name="pref_title_notifications_generic">倬卮鬲蹖亘丕賳蹖 丕夭 丕毓賱丕賳鈥屬囏й� 毓賲賵賲蹖</string>
+    <string name="pref_title_whenscreenon">鈥囐呞嗁嗃屬� 賵賯鬲蹖 氐賮丨賴鈥屰� 賳賲丕蹖卮 乇賵卮賳 丕爻鬲</string>
+    <string name="pref_title_notification_filter">丨丕賱鬲 賲夭丕丨賲 賳卮賵蹖丿</string>
+    <string name="pref_summary_notification_filter">丕毓賱丕賳鈥屬囏й� 賳丕禺賵丕爻鬲賴 丿乇 丕蹖賳 丨丕賱鬲 睾蹖乇賮毓丕賱 賲蹖鈥屫促堎嗀�</string>
+    <string name="pref_title_transliteration">賳賵蹖爻賴鈥屭必з嗃� (Transliteration貙 亘乇丕蹖 賳賲丕蹖卮 丕賳诏賱蹖爻蹖 丨乇賵賮 賮丕乇爻蹖 賵 毓乇亘蹖)</string>
+    <string name="pref_summary_transliteration">丕蹖賳 丨丕賱鬲 乇丕 賮毓丕賱 讴賳蹖丿 丕诏乇 丿爻鬲诏丕賴 卮賲丕 丨乇賵賮 夭亘丕賳鬲丕賳 乇丕 倬卮鬲蹖亘丕賳蹖 賳賲蹖鈥屭┵嗀�</string>
+    <string name="pref_title_rtl">乇丕爻鬲-亘賴-趩倬</string>
+    <string name="pref_summary_rtl">丕蹖賳 诏夭蹖賳賴 乇丕 丕诏乇 丿爻鬲诏丕賴鬲丕賳 乇丕爻鬲鈥屭嗃屬� 乇丕 倬卮鬲蹖亘丕賳蹖 賳賲蹖鈥屭┵嗀� 賮毓丕賱 讴賳蹖丿</string>
+    <string name="pref_rtl_max_line_length">亘蹖卮鬲乇蹖賳 胤賵賱 禺胤 乇丕爻鬲-亘賴-趩倬</string>
+    <string name="pref_rtl_max_line_length_summary">禺胤賵胤 乇丕爻鬲-亘賴-趩倬 乇丕 讴賵鬲丕賴 賵 亘賱賳丿 賲蹖鈥屭┵嗀�</string>
+    <string name="always">賴賲蹖卮賴</string>
+    <string name="when_screen_off">賵賯鬲蹖 氐賮丨賴 賳賲丕蹖卮 禺丕賲賵卮 丕爻鬲</string>
+    <string name="never">賴乇诏夭</string>
+    <string name="pref_header_privacy">丨乇蹖賲 卮禺氐蹖</string>
+    <string name="pref_title_call_privacy_mode">丨丕賱鬲 丨乇蹖賲 卮禺氐蹖 鬲賲丕爻 鬲賱賮賳蹖</string>
+    <string name="pref_call_privacy_mode_off">賳賲丕蹖卮 賳丕賲 賵 卮賲丕乇賴</string>
+    <string name="pref_call_privacy_mode_name">賲禺賮蹖 讴乇丿賳 賳丕賲 賵賱蹖 賳賲丕蹖卮 卮賲丕乇賴</string>
+    <string name="pref_call_privacy_mode_number">賲禺賮蹖 讴乇丿賳 卮賲丕乇賴 賵賱蹖 賳賲丕蹖卮 賳丕賲</string>
+    <string name="pref_call_privacy_mode_complete">賲禺賮蹖 讴乇丿賳 卮賲丕乇賴 賵 賳丕賲</string>
+    <string name="pref_title_weather">賴賵丕卮賳丕爻蹖</string>
+    <string name="pref_title_weather_location">賲讴丕賳 鬲毓蹖蹖賳 卮丿賴 亘乇丕蹖 賴賵丕卮賳丕爻蹖 (CM/LOS)</string>
+    <string name="mi2_prefs_button_press_broadcast_default_value" translatable="false">nodomain.freeyourgadget.gadgetbridge.ButtonPressed</string>
+    <string name="sonyswr12_settings_low_vibration_summary">賱乇夭卮 讴賲 乇賵蹖 亘丕賳丿 乇丕 賮毓丕賱 讴賳蹖丿</string>
+    <string name="sonyswr12_settings_stamina_summary">丨丕賱鬲 氐乇賮賴 噩賵蹖蹖 丿乇 賲氐乇賮 丕賳乇跇蹖 丕賳丿丕夭賴 诏蹖乇蹖 丿賵乇賴 丕蹖 囟乇亘丕賳 賯賱亘 乇丕 禺丕賲賵卮 賲蹖 讴賳丿 賵 丿乇 賳鬲蹖噩賴 夭賲丕賳 讴丕乇 丕賮夭丕蹖卮 賲蹖 蹖丕亘丿</string>
+    <string name="sonyswr12_settings_alarm_interval_summary">賮丕氐賱賴 賴卮丿丕乇 賴賵卮賲賳丿 賮丕氐賱賴 賯亘賱 丕夭 夭賳诏 賴卮丿丕乇 賳氐亘 卮丿賴 丕爻鬲. 丿乇 丕蹖賳 賮丕氐賱賴 丿爻鬲诏丕賴 丿乇 鬲賱丕卮 丕爻鬲 鬲丕 讴賲鬲乇蹖賳 賲乇丨賱賴 禺賵丕亘 乇丕 亘乇丕蹖 讴丕乇亘乇 亘蹖丿丕乇 鬲卮禺蹖氐 丿賴丿</string>
+    <string name="sonyswr12_settings_title">鬲賳馗蹖賲丕鬲 Sony SWR12</string>
+    <string name="sonyswr12_settings_low_vibration">賱乇夭卮 讴賲 賮毓丕賱 丕爻鬲</string>
+    <string name="sonyswr12_settings_stamina">丨丕賱鬲 氐乇賮賴 噩賵蹖蹖 丿乇 賲氐乇賮 亘乇賯 乇賵卮賳 丕爻鬲</string>
+    <string name="sonyswr12_settings_alarm_interval">賮丕氐賱賴 夭賳诏 賴賵卮賲賳丿 丿乇 趩賳丿 丿賯蹖賯賴</string>
+    <string name="calories">讴丕賱乇蹖</string>
+    <string name="bluetooth_is_disabled_">亘賱賵鬲賵孬 睾蹖乇賮毓丕賱 丕爻鬲.</string>
+    <string name="calendar_two_weeks">鄄 賴賮鬲賴</string>
+    <string name="calendar_month">賲丕賴</string>
+    <string name="calendar_week">賴賮鬲賴</string>
+    <string name="calendar_day">乇賵夭</string>
+    <string name="calendar_three_months">鄢 賲丕賴</string>
+    <string name="battery_level">爻胤丨 亘丕鬲乇蹖</string>
+    <string name="calendar_six_months">鄱 賲丕賴</string>
+    <string name="calendar_year">爻丕賱</string>
+    <string name="battery_detail_activity_title">丕胤賱丕毓丕鬲 亘丕鬲乇蹖</string>
+    <string name="controlcenter_set_alias">鬲毓蹖蹖賳 賳丕賲 賲爻鬲毓丕乇</string>
+    <string name="find_lost_device_message">噩爻鬲噩賵 亘乇丕蹖 %1$s責</string>
+    <string name="controlcenter_power_off">禺丕賲賵卮 讴乇丿賳</string>
+    <string name="controlcenter_power_off_confirm_title">鬲丕蹖蹖丿 禺丕賲賵卮 讴乇丿賳</string>
+    <string name="controlcenter_power_off_confirm_description">丌蹖丕 賲胤賲卅賳 賴爻鬲蹖丿 讴賴 賲蹖禺賵丕賴蹖丿 丿爻鬲诏丕賴 禺丕賲賵卮 卮賵丿責</string>
+    <string name="device_card_activity_card_title">丕胤賱丕毓丕鬲 賮毓丕賱蹖鬲 乇賵蹖 讴丕乇鬲 丿爻鬲诏丕賴</string>
+    <string name="prefs_activity_in_device_card_sleep_title">禺賵丕亘</string>
+    <string name="device_card_activity_card_title_summary">丕賳鬲禺丕亘 噩夭卅蹖丕鬲 賮毓丕賱蹖鬲鈥屬囏й� 賳賲丕蹖卮 丿丕丿賴 卮丿賴 乇賵蹖 讴丕乇鬲鈥屬囏й� 丿爻鬲诏丕賴</string>
+    <string name="prefs_activity_in_device_card_steps_title_summary">賳賲丕蹖卮 鬲毓丿丕丿 讴賱 賯丿賲鈥屬囏�</string>
+    <string name="fw_upgrade_notice_miband5">卮賲丕 丿乇 丨丕賱 賳氐亘 賮乇賲賵蹖乇 (Firmware) %s 亘乇 乇賵蹖 丿爻鬲诏丕賴 賲蹖 亘賳丿 倬賳噩 (mi band 5) 禺賵丿 賲蹖鈥屫ㄘж篡屫�.
+\n
+\n賱胤賮丕 丕夭 賳氐亘 賮丕蹖賱 亘丕 倬爻賵賳丿 .fw 賵 倬爻 丕夭 丌賳 賮丕蹖賱 亘丕 倬爻賵賳丿 .res 賵 丕胤賲蹖賳丕賳 丨丕氐賱 賮乇賲丕蹖蹖丿. 爻丕毓鬲 卮賲丕 倬爻 丕夭 賳氐亘 賮丕蹖賱 亘丕 倬爻賵賳丿 .fw 乇丕賴鈥屫з嗀ж槽� 賲噩丿丿 (乇蹖鈥屫ж池ж必�) 禺賵丕賴丿 卮丿.
+\n
+\n蹖丕丿丿丕卮鬲: 丿乇氐賵乇鬲蹖 讴賴 賮丕蹖賱 亘丕 賮乇賲鬲 .res 卮賲丕 丿賯蹖賯丕 賲卮丕亘賴 賮丕蹖賱賽 丕夭 倬蹖卮 賳氐亘 卮丿賴 賲蹖鈥屫ㄘж簇� 賳蹖丕夭蹖 亘賴 賳氐亘 賲噩丿丿 丌賳 賳蹖爻鬲.
+\n
+\n亘丕 賲爻卅賵賱蹖鬲 禺賵丿鬲丕賳 丕丿丕賲賴 丿賴蹖丿!</string>
+    <string name="fw_upgrade_notice_amazfitcor2">卮賲丕 丿乇 丨丕賱 賳氐亘 賮乇賲賵蹖乇 (Firmware) %s 亘乇 乇賵蹖 丿爻鬲诏丕賴 丕賲蹖夭賮蹖鬲 讴賵乇 鄄 (Amazfit core 2) 禺賵丿 賲蹖鈥屫ㄘж篡屫�.
+\n
+\n賱胤賮丕 丕夭 賳氐亘 賮丕蹖賱 亘丕 倬爻賵賳丿 .fw 賵 倬爻 丕夭 丌賳 賮丕蹖賱 亘丕 倬爻賵賳丿 .res 丕胤賲蹖賳丕賳 丨丕氐賱 賮乇賲丕蹖蹖丿. 爻丕毓鬲 卮賲丕 倬爻 丕夭 賳氐亘 賮丕蹖賱 亘丕 倬爻賵賳丿 .fw 乇丕賴鈥屫з嗀ж槽� 賲噩丿丿 (乇蹖鈥屫ж池ж必�) 禺賵丕賴丿 卮丿.
+\n
+\n蹖丕丿丿丕卮鬲: 丿乇氐賵乇鬲蹖 讴賴 賮丕蹖賱 亘丕 賮乇賲鬲 .res 卮賲丕 丿賯蹖賯丕 賲卮丕亘賴 賮丕蹖賱賽 丕夭 倬蹖卮 賳氐亘 卮丿賴 賲蹖鈥屫ㄘж簇� 賳蹖丕夭蹖 亘賴 賳氐亘 賲噩丿丿 丌賳 賳蹖爻鬲.
+\n
+\n亘丕 賲爻卅賵賱蹖鬲 禺賵丿鬲丕賳 丕丿丕賲賴 丿賴蹖丿!
+\n
+\n讴丕賲賱丕 鬲爻鬲 賳卮丿賴貙 丿乇 氐賵乇鬲蹖 讴賴 賳丕賲 丿爻鬲诏丕賴 卮賲丕 \"Amazfit Band 2\" 賲蹖鈥屫ㄘж簇� 賲賲讴賳 丕爻鬲 賳蹖丕夭 丿丕卮鬲賴 亘丕卮蹖丿 讴賴 蹖讴 賮乇賲賵蹖乇 BEATS_W 乇賵蹖 丿爻鬲诏丕賴 禺賵丿 亘乇蹖夭蹖丿.</string>
+    <string name="fw_upgrade_notice_amazfit_trex">卮賲丕 丿乇 丨丕賱 賳氐亘 賮乇賲賵蹖乇 (Firmware) %s 亘乇 乇賵蹖 丿爻鬲诏丕賴 丕賲蹖夭賮蹖鬲 鬲蹖 乇讴爻 (Amazfit T-REX) 禺賵丿 賲蹖鈥屫ㄘж篡屫�.
+\n
+\n賱胤賮丕 丕夭 賳氐亘 賮丕蹖賱 亘丕 倬爻賵賳丿 .fw 賵 倬爻 丕夭 丌賳 賮丕蹖賱 亘丕 倬爻賵賳丿 .res 賵 丿乇 賳賴丕蹖鬲 賮丕蹖賱 亘丕 倬爻賵賳丿 .gps 丕胤賲蹖賳丕賳 丨丕氐賱 賮乇賲丕蹖蹖丿. 爻丕毓鬲 卮賲丕 倬爻 丕夭 賳氐亘 賮丕蹖賱 亘丕 倬爻賵賳丿 .fw 乇丕賴鈥屫з嗀ж槽� 賲噩丿丿 (乇蹖鈥屫ж池ж必�) 禺賵丕賴丿 卮丿.
+\n
+\n蹖丕丿丿丕卮鬲: 丿乇氐賵乇鬲蹖 讴賴 賮丕蹖賱鈥屬囏й� 亘丕 賮乇賲鬲 .res 賵 .gps 卮賲丕 丿賯蹖賯丕 賲卮丕亘賴 賮丕蹖賱賽 丕夭 倬蹖卮 賳氐亘 卮丿賴 賲蹖鈥屫ㄘж簇� 賳蹖丕夭蹖 亘賴 賳氐亘 賲噩丿丿 丌賳 賳蹖爻鬲.
+\n
+\n亘丕 賲爻卅賵賱蹖鬲 禺賵丿鬲丕賳 丕丿丕賲賴 丿賴蹖丿!</string>
+    <string name="check_all_applications">丕賳鬲禺丕亘 鬲賲丕賲 丕倬賱蹖讴蹖卮賳鈥屬囏�</string>
+    <string name="title_activity_notification_management">鬲賳馗蹖賲丕鬲 丕毓賱丕賳鈥屬囏�</string>
+    <string name="uncheck_all_applications">丨匕賮 丕賳鬲禺丕亘 鬲賲丕賲 丕倬賱蹖讴蹖卮賳鈥屬囏�</string>
+    <string name="fw_upgrade_notice_amazfitbip_lite">卮賲丕 丿乇 丨丕賱 賳氐亘 爻蹖爻鬲賲鈥屫关з呝� (Firmware) %s 亘乇 乇賵蹖 丿爻鬲诏丕賴 丕賲蹖夭賮蹖鬲 亘蹖倬 賱丕蹖鬲 (Amazfit bip lite) 禺賵丿 賲蹖鈥屫ㄘж篡屫�.
+\n
+\n賱胤賮丕 丕夭 賳氐亘 賮丕蹖賱 亘丕 倬爻賵賳丿 .fw 賵 倬爻 丕夭 丌賳 賮丕蹖賱 亘丕 倬爻賵賳丿 .res 丕胤賲蹖賳丕賳 丨丕氐賱 賮乇賲丕蹖蹖丿. 爻丕毓鬲 卮賲丕 倬爻 丕夭 賳氐亘 賮丕蹖賱 亘丕 倬爻賵賳丿 .fw 乇丕賴鈥屫з嗀ж槽� 賲噩丿丿 (乇蹖鈥屫ж池ж必�) 禺賵丕賴丿 卮丿.
+\n
+\n蹖丕丿丿丕卮鬲: 丿乇氐賵乇鬲蹖 讴賴 賮丕蹖賱 亘丕 賮乇賲鬲 .res 卮賲丕 丿賯蹖賯丕 賲卮丕亘賴 賮丕蹖賱賽 丕夭 倬蹖卮 賳氐亘 卮丿賴 賲蹖鈥屫ㄘж簇� 賳蹖丕夭蹖 亘賴 賳氐亘 賲噩丿丿 丌賳 賳蹖爻鬲.
+\n
+\n亘丕 賲爻卅賵賱蹖鬲 禺賵丿鬲丕賳 丕丿丕賲賴 丿賴蹖丿!</string>
+    <string name="prefs_activity_in_device_card_distance_title_summary">賲爻丕賮鬲 亘丕 丕爻鬲賮丕丿賴 丕夭 鬲毓丿丕丿 賯丿賲鈥屬囏� 賵 胤賵賱 賴乇 賯丿賲 (讴賴 丕夭 亘禺卮 丿乇亘丕乇賴 卮賲丕 丿乇 鬲賳馗蹖賲丕鬲 賯丕亘賱 鬲睾蹖蹖乇 丕爻鬲) 賲丨丕爻亘賴 賲蹖鈥屫促堌�</string>
+    <string name="prefs_activity_in_device_card_title">賳賲丕蹖卮 丕胤賱丕毓丕鬲 賮毓丕賱蹖鬲 乇賵蹖 讴丕乇鬲鈥屬囏й� 丿爻鬲诏丕賴</string>
+    <string name="prefs_activity_in_device_card_sleep_title_summary">賳賲丕蹖卮 賲丿鬲 禺賵丕亘</string>
+    <string name="prefs_activity_in_device_card_title_summary">賳賲丕蹖卮 鬲毓丿丕丿 賯丿賲鈥屬囏й� 賮毓賱蹖貙 賲爻丕賮鬲 胤蹖 卮丿賴 蹖丕 賲蹖夭丕賳 禺賵丕亘 乇賵蹖 讴丕乇鬲 丿爻鬲诏丕賴</string>
+    <string name="fw_upgrade_notice_miband6">卮賲丕 丿乇 丨丕賱 賳氐亘 賮乇賲賵蹖乇 (Firmware) %s 亘乇 乇賵蹖 丿爻鬲诏丕賴 賲蹖 亘賳丿 卮卮 (mi band 6) 禺賵丿 賲蹖鈥屫ㄘж篡屫�.
+\n
+\n賱胤賮丕 丕夭 賳氐亘 賮丕蹖賱 亘丕 倬爻賵賳丿 .fw 賵 倬爻 丕夭 丌賳 賮丕蹖賱 亘丕 倬爻賵賳丿 .res 賵 丕胤賲蹖賳丕賳 丨丕氐賱 賮乇賲丕蹖蹖丿. 爻丕毓鬲 卮賲丕 倬爻 丕夭 賳氐亘 賮丕蹖賱 亘丕 倬爻賵賳丿 .fw 乇丕賴鈥屫з嗀ж槽� 賲噩丿丿 (乇蹖鈥屫ж池ж必�) 禺賵丕賴丿 卮丿.
+\n
+\n蹖丕丿丿丕卮鬲: 丿乇氐賵乇鬲蹖 讴賴 賮丕蹖賱 亘丕 賮乇賲鬲 .res 卮賲丕 丿賯蹖賯丕 賲卮丕亘賴 賮丕蹖賱賽 丕夭 倬蹖卮 賳氐亘 卮丿賴 賲蹖鈥屫ㄘж簇� 賳蹖丕夭蹖 亘賴 賳氐亘 賲噩丿丿 丌賳 賳蹖爻鬲.
+\n
+\n亘丕 賲爻卅賵賱蹖鬲 禺賵丿鬲丕賳 丕丿丕賲賴 丿賴蹖丿!</string>
+    <string name="fw_upgrade_notice_amazfitgtr">卮賲丕 丿乇 丨丕賱 賳氐亘 賮乇賲賵蹖乇 (Firmware) %s 亘乇 乇賵蹖 丿爻鬲诏丕賴 丕賲蹖夭賮蹖鬲 噩蹖 鬲蹖 丌乇 (Amazfit GTR) 禺賵丿 賲蹖鈥屫ㄘж篡屫�.
+\n
+\n賱胤賮丕 丕夭 賳氐亘 賮丕蹖賱 亘丕 倬爻賵賳丿 .fw 賵 倬爻 丕夭 丌賳 賮丕蹖賱 亘丕 倬爻賵賳丿 .res 賵 丿乇 賳賴丕蹖鬲 賮丕蹖賱 亘丕 倬爻賵賳丿 .gps 丕胤賲蹖賳丕賳 丨丕氐賱 賮乇賲丕蹖蹖丿. 爻丕毓鬲 卮賲丕 倬爻 丕夭 賳氐亘 賮丕蹖賱 亘丕 倬爻賵賳丿 .fw 乇丕賴鈥屫з嗀ж槽� 賲噩丿丿 (乇蹖鈥屫ж池ж必�) 禺賵丕賴丿 卮丿.
+\n
+\n蹖丕丿丿丕卮鬲: 丿乇氐賵乇鬲蹖 讴賴 賮丕蹖賱鈥屬囏й� 亘丕 賮乇賲鬲 .res 賵 .gps 卮賲丕 丿賯蹖賯丕 賲卮丕亘賴 賮丕蹖賱賽 丕夭 倬蹖卮 賳氐亘 卮丿賴 賲蹖鈥屫ㄘж簇� 賳蹖丕夭蹖 亘賴 賳氐亘 賲噩丿丿 丌賳 賳蹖爻鬲.
+\n
+\n亘丕 賲爻卅賵賱蹖鬲 禺賵丿鬲丕賳 丕丿丕賲賴 丿賴蹖丿!</string>
+    <string name="fw_upgrade_notice_amazfitgts">卮賲丕 丿乇 丨丕賱 賳氐亘 賮乇賲賵蹖乇 (Firmware) %s 亘乇 乇賵蹖 丿爻鬲诏丕賴 丕賲蹖夭賮蹖鬲 噩蹖 鬲蹖 丕爻 (Amazfit GTS) 禺賵丿 賲蹖鈥屫ㄘж篡屫�.
+\n
+\n賱胤賮丕 丕夭 賳氐亘 賮丕蹖賱 亘丕 倬爻賵賳丿 .fw 賵 倬爻 丕夭 丌賳 賮丕蹖賱 亘丕 倬爻賵賳丿 .res 賵 丿乇 賳賴丕蹖鬲 賮丕蹖賱 亘丕 倬爻賵賳丿 .gps 丕胤賲蹖賳丕賳 丨丕氐賱 賮乇賲丕蹖蹖丿. 爻丕毓鬲 卮賲丕 倬爻 丕夭 賳氐亘 賮丕蹖賱 亘丕 倬爻賵賳丿 .fw 乇丕賴鈥屫з嗀ж槽� 賲噩丿丿 (乇蹖鈥屫ж池ж必�) 禺賵丕賴丿 卮丿.
+\n
+\n蹖丕丿丿丕卮鬲: 丿乇氐賵乇鬲蹖 讴賴 賮丕蹖賱鈥屬囏й� 亘丕 賮乇賲鬲 .res 賵 .gps 卮賲丕 丿賯蹖賯丕 賲卮丕亘賴 賮丕蹖賱賽 丕夭 倬蹖卮 賳氐亘 卮丿賴 賲蹖鈥屫ㄘж簇� 賳蹖丕夭蹖 亘賴 賳氐亘 賲噩丿丿 丌賳 賳蹖爻鬲.
+\n
+\n亘丕 賲爻卅賵賱蹖鬲 禺賵丿鬲丕賳 丕丿丕賲賴 丿賴蹖丿!</string>
+    <string name="fw_upgrade_notice_amazfitvergel">卮賲丕 丿乇 丨丕賱 賳氐亘 賮乇賲賵蹖乇 (Firmware) %s 亘乇 乇賵蹖 丿爻鬲诏丕賴 丕賲蹖夭賮蹖鬲 賵乇噩 賱丕蹖鬲 (Amazfit verge lite) 禺賵丿 賲蹖鈥屫ㄘж篡屫�.
+\n
+\n賱胤賮丕 丕夭 賳氐亘 賮丕蹖賱 亘丕 倬爻賵賳丿 .fw 賵 倬爻 丕夭 丌賳 賮丕蹖賱 亘丕 倬爻賵賳丿 .res 賵 丿乇 賳賴丕蹖鬲 賮丕蹖賱 亘丕 倬爻賵賳丿 .gps 丕胤賲蹖賳丕賳 丨丕氐賱 賮乇賲丕蹖蹖丿. 爻丕毓鬲 卮賲丕 倬爻 丕夭 賳氐亘 賮丕蹖賱 亘丕 倬爻賵賳丿 .fw 乇丕賴鈥屫з嗀ж槽� 賲噩丿丿 (乇蹖鈥屫ж池ж必�) 禺賵丕賴丿 卮丿.
+\n
+\n蹖丕丿丿丕卮鬲: 丿乇氐賵乇鬲蹖 讴賴 賮丕蹖賱鈥屬囏й� 亘丕 賮乇賲鬲 .res 賵 .gps 卮賲丕 丿賯蹖賯丕 賲卮丕亘賴 賮丕蹖賱賽 丕夭 倬蹖卮 賳氐亘 卮丿賴 賲蹖鈥屫ㄘж簇� 賳蹖丕夭蹖 亘賴 賳氐亘 賲噩丿丿 丌賳 賳蹖爻鬲.
+\n
+\n亘丕 賲爻卅賵賱蹖鬲 禺賵丿鬲丕賳 丕丿丕賲賴 丿賴蹖丿!</string>
+    <string name="fw_upgrade_notice_miband4">卮賲丕 丿乇 丨丕賱 賳氐亘 賮乇賲賵蹖乇 (Firmware) %s 亘乇 乇賵蹖 丿爻鬲诏丕賴 賲蹖 亘賳丿 趩賴丕乇 (mi band 4) 禺賵丿 賲蹖鈥屫ㄘж篡屫�.
+\n
+\n賱胤賮丕 丕夭 賳氐亘 賮丕蹖賱 亘丕 倬爻賵賳丿 .fw 賵 倬爻 丕夭 丌賳 賮丕蹖賱 亘丕 倬爻賵賳丿 .res 賵 丕胤賲蹖賳丕賳 丨丕氐賱 賮乇賲丕蹖蹖丿. 爻丕毓鬲 卮賲丕 倬爻 丕夭 賳氐亘 賮丕蹖賱 亘丕 倬爻賵賳丿 .fw 乇丕賴鈥屫з嗀ж槽� 賲噩丿丿 (乇蹖鈥屫ж池ж必�) 禺賵丕賴丿 卮丿.
+\n
+\n蹖丕丿丿丕卮鬲: 丿乇氐賵乇鬲蹖 讴賴 賮丕蹖賱 亘丕 賮乇賲鬲 .res 卮賲丕 丿賯蹖賯丕 賲卮丕亘賴 賮丕蹖賱賽 丕夭 倬蹖卮 賳氐亘 卮丿賴 賲蹖鈥屫ㄘж簇� 賳蹖丕夭蹖 亘賴 賳氐亘 賲噩丿丿 丌賳 賳蹖爻鬲.
+\n
+\n亘丕 賲爻卅賵賱蹖鬲 禺賵丿鬲丕賳 丕丿丕賲賴 丿賴蹖丿!</string>
+    <string name="fw_upgrade_notice_amazfitx">卮賲丕 丿乇 丨丕賱 賳氐亘 賮乇賲賵蹖乇 (Firmware) %s 亘乇 乇賵蹖 丿爻鬲诏丕賴 丕賲蹖夭賮蹖鬲 丕蹖讴爻 (Amazfit X) 禺賵丿 賲蹖鈥屫ㄘж篡屫�.
+\n
+\n賱胤賮丕 丕夭 賳氐亘 賮丕蹖賱 亘丕 倬爻賵賳丿 .fw 賵 倬爻 丕夭 丌賳 賮丕蹖賱 亘丕 倬爻賵賳丿 .res 賵 丕胤賲蹖賳丕賳 丨丕氐賱 賮乇賲丕蹖蹖丿. 爻丕毓鬲 卮賲丕 倬爻 丕夭 賳氐亘 賮丕蹖賱 亘丕 倬爻賵賳丿 .fw 乇丕賴鈥屫з嗀ж槽� 賲噩丿丿 (乇蹖鈥屫ж池ж必�) 禺賵丕賴丿 卮丿.
+\n
+\n蹖丕丿丿丕卮鬲: 丿乇氐賵乇鬲蹖 讴賴 賮丕蹖賱 亘丕 賮乇賲鬲 .res 卮賲丕 丿賯蹖賯丕 賲卮丕亘賴 賮丕蹖賱賽 丕夭 倬蹖卮 賳氐亘 卮丿賴 賲蹖鈥屫ㄘж簇� 賳蹖丕夭蹖 亘賴 賳氐亘 賲噩丿丿 丌賳 賳蹖爻鬲.
+\n
+\n亘丕 賲爻卅賵賱蹖鬲 禺賵丿鬲丕賳 丕丿丕賲賴 丿賴蹖丿!</string>
+</resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-fi/strings.xml b/app/src/main/res/values-fi/strings.xml
new file mode 100644
index 0000000..c346299
--- /dev/null
+++ b/app/src/main/res/values-fi/strings.xml
@@ -0,0 +1,664 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources xmlns:tools="http://schemas.android.com/tools">
+    <string name="action_settings">Asetukset</string>
+    <string name="controlcenter_fetch_activity_data">Synkronoi</string>
+    <string name="controlcenter_find_device">L枚yd盲 kadonnut laite</string>
+    <string name="application_name_generic">Gadgetbridge</string>
+    <string name="title_activity_controlcenter_generic">Gadgetbridge</string>
+    <string name="action_quit">Lopeta</string>
+    <string name="action_donate">Lahjoita</string>
+    <string name="controlcenter_take_screenshot">Ota kuvankaappaus</string>
+    <string name="controlcenter_disconnect">Katkaise yhteys</string>
+    <string name="controlcenter_delete_device">Poista laite</string>
+    <string name="controlcenter_delete_device_name">Poista %1$s</string>
+    <string name="controlcenter_delete_device_dialogmessage">T盲m盲 poistaa laitteen ja kaikki siihen liittyv盲t tiedot!</string>
+    <string name="controlcenter_navigation_drawer_open">Avaa navigointivetolaatikko</string>
+    <string name="controlcenter_navigation_drawer_close">Sulje navigointivetolaatikko</string>
+    <string name="controlcenter_snackbar_need_longpress">Paina korttia pitk盲盲n katkaistaksesi yhteyden</string>
+    <string name="controlcenter_snackbar_disconnecting">Yhteytt盲 katkaistaan</string>
+    <string name="controlcenter_snackbar_connecting">Yhdistet盲盲n鈥�</string>
+    <string name="controlcenter_snackbar_requested_screenshot">Otetaan ruutukaappausta laitteesta</string>
+    <string name="title_activity_appmanager">Sovellusten hallinta</string>
+    <string name="appmanager_cached_watchapps_watchfaces">V盲limuistissa olevat sovellukset</string>
+    <string name="appmanager_installed_watchapps">Asennetut sovellukset</string>
+    <string name="appmanager_installed_watchfaces">Asennetut kellotaulut</string>
+    <string name="appmananger_app_delete">Poista</string>
+    <string name="appmananger_app_delete_cache">Poista laitteesta ja v盲limuistista</string>
+    <string name="appmananger_app_reinstall">Asenna uudelleen</string>
+    <string name="appmanager_app_openinstore">Etsi Pebble appstoresta</string>
+    <string name="appmanager_health_activate">Aktivoi</string>
+    <string name="appmanager_health_deactivate">Poista k盲yt枚st盲</string>
+    <string name="appmanager_hrm_activate">Aktivoi sykemittari</string>
+    <string name="appmanager_hrm_deactivate">Poista sykemittari k盲yt枚st盲</string>
+    <string name="appmanager_weather_activate">Aktivoi j盲rjestelm盲n s盲盲sovellus</string>
+    <string name="appmanager_weather_deactivate">Ota j盲rjestelm盲n s盲盲sovellus pois k盲yt枚st盲</string>
+    <string name="appmanager_weather_install_provider">Asenna s盲盲ilmoitussovellus</string>
+    <string name="app_configure">M盲盲rit盲</string>
+    <string name="app_move_to_top">Siirr盲 ylimm盲ksi</string>
+    <string name="mi2_prefs_button_press_broadcast_default_value" translatable="false">nodomain.freeyourgadget.gadgetbridge.ButtonPressed</string>
+    <string name="sonyswr12_settings_title">Sony SWR12 asetukset</string>
+    <string name="sonyswr12_settings_low_vibration">Matala v盲rin盲 k盲yt枚ss盲</string>
+    <string name="sonyswr12_settings_stamina">Virrans盲盲st枚tila p盲盲ll盲</string>
+    <string name="sonyswr12_settings_alarm_interval">脛lyk盲s h盲lytysv盲li minuutteina</string>
+    <string name="sonyswr12_settings_low_vibration_summary">Ota k盲ytt枚枚n matala v盲rin盲n voimakkuus kaistalla</string>
+    <string name="sonyswr12_settings_stamina_summary">Virrans盲盲st枚tila poistaa s盲盲nn枚llisen automaattisen sykemittauksen k盲yt枚st盲, mik盲 pident盲盲 ty枚aikaa</string>
+    <string name="sonyswr12_settings_alarm_interval_summary">脛lyk盲s h盲lytysv盲li on aikav盲li ennen asennettua h盲lytyst盲. T盲ss盲 v盲liss盲 laite yritt盲盲 tunnistaa k盲ytt盲j盲n her盲tt盲misen unen kevyimm盲n vaiheen</string>
+    <string name="miband_firmware_known">T盲m盲 laiteohjelmisto on testattu ja sen tiedet盲盲n olevan yhteensopiva Gadgetbridgen kanssa.</string>
+    <string name="fw_multi_upgrade_notice">Olet asentamassa %1$s, sek盲 %2$s laiteohjelmistoa, Mi Bandin nykyisten laitteiden sijasta.</string>
+    <string name="fw_upgrade_notice_amazfitneo">Olet asentamassa %s laiteohjelmistoa laitteeseen Amazfit Neo.
+\n
+\nRannekkeesi k盲ynnistyy uudelleen .fw-tiedoston asentamisen j盲lkeen.
+\n
+\nJATKA OMALLA VASTUULLA!</string>
+    <string name="fw_upgrade_notice_amazfitx">Olet asentamassa %s laiteohjelmistoa laitteeseen Amazfit X.
+\n
+\nVarmista ett盲 asennat ensin .fw-tiedoston, sen j盲lkeen .res-tiedoston ja lopuksi .gps-tiedoston. Kellosi k盲ynnistyy uudelleen .fw-tiedoston asentamisen j盲lkeen.
+\n
+\nHuomio: Sinun ei tarvitse asentaa .res tai .gps tiedostoja jos n盲m盲 tiedostot ovat samat kuin jo asennetut.
+\n
+\nJATKA OMALLA VASTUULLA!</string>
+    <string name="fw_upgrade_notice_miband3">Olet asentamassa %s laiteohjelmistoa laitteeseen Mi Band 3.
+\n
+\nVarmista ett盲 asennat ensin .fw-tiedoston, sen j盲lkeen .res-tiedoston ja lopuksi .gps-tiedoston. Kellosi k盲ynnistyy uudelleen .fw-tiedoston asentamisen j盲lkeen.
+\n
+\nHuomio: Sinun ei tarvitse asentaa .res tai .gps tiedostoja jos n盲m盲 tiedostot ovat samat kuin jo asennetut.
+\n
+\nJATKA OMALLA VASTUULLA!</string>
+    <string name="fw_upgrade_notice_amazfitvergel">Olet asentamassa %s laiteohjelmistoa laitteeseen Amazfit Verge Lite.
+\n
+\nVarmista ett盲 asennat ensin .fw-tiedoston, sen j盲lkeen .res-tiedoston ja lopuksi .gps-tiedoston. Kellosi k盲ynnistyy uudelleen .fw-tiedoston asentamisen j盲lkeen.
+\n
+\nHuomio: Sinun ei tarvitse asentaa .res tai .gps tiedostoja jos n盲m盲 tiedostot ovat samat kuin jo asennetut.
+\n
+\nJATKA OMALLA VASTUULLA!</string>
+    <string name="fw_upgrade_notice_amazfit_trex">Olet asentamassa %s laiteohjelmistoa laitteeseen Amazfit T-Rex.
+\n
+\nVarmista ett盲 asennat ensin .fw-tiedoston, sen j盲lkeen .res-tiedoston ja lopuksi .gps-tiedoston. Kellosi k盲ynnistyy uudelleen .fw-tiedoston asentamisen j盲lkeen.
+\n
+\nHuomio: Sinun ei tarvitse asentaa .res tai .gps tiedostoja jos n盲m盲 tiedostot ovat samat kuin jo asennetut.
+\n
+\nJATKA OMALLA VASTUULLA!</string>
+    <string name="fw_upgrade_notice_amazfitgts">Olet asentamassa %s laiteohjelmistoa laitteeseen Amazfit GTS.
+\n
+\nVarmista ett盲 asennat ensin .fw-tiedoston, sen j盲lkeen .res-tiedoston ja lopuksi .gps-tiedoston. Kellosi k盲ynnistyy uudelleen .fw-tiedoston asentamisen j盲lkeen.
+\n
+\nHuomio: Sinun ei tarvitse asentaa .res tai .gps tiedostoja jos n盲m盲 tiedostot ovat samat kuin jo asennetut.
+\n
+\nJATKA OMALLA VASTUULLA!</string>
+    <string name="fw_upgrade_notice_amazfitgtr">Olet asentamassa %s laiteohjelmistoa laitteeseen Amazfit GTR.
+\n
+\nVarmista ett盲 asennat ensin .fw-tiedoston, sen j盲lkeen .res-tiedoston ja lopuksi .gps-tiedoston. Kellosi k盲ynnistyy uudelleen .fw-tiedoston asentamisen j盲lkeen.
+\n
+\nHuomio: Sinun ei tarvitse asentaa .res tai .gps tiedostoja jos n盲m盲 tiedostot ovat samat kuin jo asennetut.
+\n
+\nJATKA OMALLA VASTUULLA!</string>
+    <string name="fw_upgrade_notice_amazfitcor2">Olet asentamassa %s laiteohjelmistoa laitteeseen Amazfit Cor 2.
+\n
+\nVarmista ett盲 asennat ensin .fw-tiedoston, sen j盲lkeen .res-tiedoston ja lopuksi .gps-tiedoston. Kellosi k盲ynnistyy uudelleen .fw-tiedoston asentamisen j盲lkeen.
+\n
+\nHuomio: Sinun ei tarvitse asentaa .res tai .gps tiedostoja jos n盲m盲 tiedostot ovat samat kuin jo asennetut.
+\n
+\nJATKA OMALLA VASTUULLA!
+\n
+\nT脛YSIN TESTAAMATON, TODENN脛K脰ISESTI SINUN TARVITSEE ASENTAA BEATS_W LAITEOHJELMISTO JOS LAITTEESI NIMI ON \"Amazfit Band 2\"</string>
+    <string name="fw_upgrade_notice_amazfitcor">Olet asentamassa %s laiteohjelmistoa laitteeseen Amazfit Cor.
+\n
+\nVarmista ett盲 asennat ensin .fw-tiedoston, sen j盲lkeen .res-tiedoston ja lopuksi .gps-tiedoston. Kellosi k盲ynnistyy uudelleen .fw-tiedoston asentamisen j盲lkeen.
+\n
+\nHuomio: Sinun ei tarvitse asentaa .res tai .gps tiedostoja jos n盲m盲 tiedostot ovat samat kuin jo asennetut.
+\n
+\nJATKA OMALLA VASTUULLA!</string>
+    <string name="fw_upgrade_notice_amazfitbip_lite">Olet asentamassa %s laiteohjelmistoa laitteeseen Amazfit Bip Lite.
+\n
+\nVarmista ett盲 asennat ensin .fw-tiedoston, sen j盲lkeen .res-tiedoston ja lopuksi .gps-tiedoston. Kellosi k盲ynnistyy uudelleen .fw-tiedoston asentamisen j盲lkeen.
+\n
+\nHuomio: Sinun ei tarvitse asentaa .res tai .gps tiedostoja jos n盲m盲 tiedostot ovat samat kuin jo asennetut.
+\n
+\nJATKA OMALLA VASTUULLA!</string>
+    <string name="fw_upgrade_notice_amazfitbip">Olet asentamassa %s laiteohjelmistoa laitteeseen Amazfit Bip.
+\n
+\nVarmista ett盲 asennat ensin .fw-tiedoston, sen j盲lkeen .res-tiedoston ja lopuksi .gps-tiedoston. Kellosi k盲ynnistyy uudelleen .fw-tiedoston asentamisen j盲lkeen.
+\n
+\nHuomio: Sinun ei tarvitse asentaa .res tai .gps tiedostoja jos n盲m盲 tiedostot ovat samat kuin jo asennetut.
+\n
+\nJATKA OMALLA VASTUULLA!</string>
+    <string name="fw_upgrade_notice_miband4">Olet asentamassa %s laiteohjelmistoa laitteeseen Mi Band 4.
+\n
+\nVarmista ett盲 asennat ensin .fw-tiedoston, sen j盲lkeen .res-tiedoston ja lopuksi .gps-tiedoston. Kellosi k盲ynnistyy uudelleen .fw-tiedoston asentamisen j盲lkeen.
+\n
+\nHuomio: Sinun ei tarvitse asentaa .res tai .gps tiedostoja jos n盲m盲 tiedostot ovat samat kuin jo asennetut.
+\n
+\nJATKA OMALLA VASTUULLA!</string>
+    <string name="fw_upgrade_notice_miband5">Olet asentamassa %s laiteohjelmistoa laitteeseen Mi Band 5.
+\n
+\nVarmista ett盲 asennat ensin .fw-tiedoston, sen j盲lkeen .res-tiedoston ja lopuksi .gps-tiedoston. Kellosi k盲ynnistyy uudelleen .fw-tiedoston asentamisen j盲lkeen.
+\n
+\nHuomio: Sinun ei tarvitse asentaa .res tai .gps tiedostoja jos n盲m盲 tiedostot ovat samat kuin jo asennetut.
+\n
+\nJATKA OMALLA VASTUULLA!</string>
+    <string name="fw_upgrade_notice_miband6">Olet asentamassa %s laiteohjelmistoa laitteeseen Mi Band 6.
+\n
+\nVarmista ett盲 asennat ensin .fw-tiedoston, sen j盲lkeen .res-tiedoston ja lopuksi .gps-tiedoston. Kellosi k盲ynnistyy uudelleen .fw-tiedoston asentamisen j盲lkeen.
+\n
+\nHuomio: Sinun ei tarvitse asentaa .res tai .gps tiedostoja jos n盲m盲 tiedostot ovat samat kuin jo asennetut.
+\n
+\nJATKA OMALLA VASTUULLA!</string>
+    <string name="fw_upgrade_notice">Olet asentamassa %s.</string>
+    <string name="title_activity_fw_app_insaller">Laiteohjelmiston/ohjelmiston asennusohjelma</string>
+    <string name="title_activity_calblacklist">Mustalla listalla olevat Kalenterit</string>
+    <string name="whitelist_all_for_notifications">Lis盲盲 kaikki ilmoitukset valkoiselle listalle</string>
+    <string name="blacklist_all_for_notifications">Lis盲盲 kaikki ilmoitukset mustalle listalle</string>
+    <string name="debugactivity_really_factoryreset">Tehdasastetuksien palauttaminen poistaa yhdistetyst盲 laitteesta kaikki tiedot (jos tuettu). Xiaomin ja Huamin laitteet my枚s muuttavat Bluetooth MAC -osoitteen, joten ne n盲kyv盲t uusina laitteina Gadgetbridgess盲.</string>
+    <string name="debugactivity_really_factoryreset_title">Tahdotko varmasti palauttaa tehdasasteukset?</string>
+    <string name="title_activity_debug">Virheenetsint盲</string>
+    <string name="calendar_year">Vuosi</string>
+    <string name="calendar_two_weeks">2 viikkoa</string>
+    <string name="calendar_three_months">3 kuukautta</string>
+    <string name="calendar_six_months">6 kuukautta</string>
+    <string name="calendar_month">Kuukausi</string>
+    <string name="calendar_week">Viikko</string>
+    <string name="calendar_day">P盲iv盲</string>
+    <string name="battery_level">Akun varaustaso</string>
+    <string name="battery_detail_activity_title">Akun tiedot</string>
+    <string name="controlcenter_calibrate_device">Laitteen kalibrointi</string>
+    <string name="controlcenter_set_alias">M盲盲rit盲 alias</string>
+    <string name="controlcenter_connect">Yhdist盲</string>
+    <string name="controlcenter_change_fm_frequency">Muuta FM-taajuutta</string>
+    <string name="controlcenter_change_led_color">Muuta LED-v盲ri盲</string>
+    <string name="find_lost_device_message">Etsi %1$s?</string>
+    <string name="action_debug">Virheenetsint盲</string>
+    <string name="title_activity_notification_management">Ilmoitusasetukset</string>
+    <string name="pref_header_general">Yleiset</string>
+    <string name="pref_title_language">Kieli</string>
+    <string name="pref_summary_minimize_priority_off">Tilapalkin kuvake ja lukitusn盲yt枚n ilmoitus n盲ytet盲盲n</string>
+    <string name="pref_title_general_autoconnectonbluetooth">Yhdist盲 laitteeseen, kun Bluetooth on p盲盲ll盲</string>
+    <string name="pref_title_audio_player">Ensisijainen 盲盲nentoisto</string>
+    <string name="pref_summary_datetime_syctimeonconnect">Synkronoi aika Gadgetbridge-laitteeseen, kun muodostat yhteyden, ja kun aika tai aikavy枚hyke muuttuu Android-laitteessa</string>
+    <string name="pref_title_notifications_call">Puhelut</string>
+    <string name="uncheck_all_applications">Poista valinta kaikista sovelluksista</string>
+    <string name="pref_header_notifications">Yleiset</string>
+    <string name="pref_title_general_autoreconnect">Yhdist盲 uudelleen automaattisesti</string>
+    <string name="pref_summary_minimize_priority_on">Tilapalkin kuvake ja lukitusn盲yt枚n ilmoitus ovat piilotettu</string>
+    <string name="pref_title_notifications_repetitions">Toistot</string>
+    <string name="pref_title_notifications_generic">Yleinen ilmoitustuki</string>
+    <string name="pref_title_notification_use_as">K盲yt盲 sovellusluetteloa鈥�</string>
+    <string name="pref_title_whenscreenon">鈥y枚s n盲yt枚n ollessa p盲盲ll盲</string>
+    <string name="pref_header_notification_application_settings">Sovelluskohtaiset</string>
+    <string name="pref_title_notification_use_as_deny">Est盲 ilmoitukset valituista sovelluksista</string>
+    <string name="pref_title_ping_tone">Ping-盲盲ni</string>
+    <string name="pref_title_notifications_sms">Tekstiviesti</string>
+    <string name="pref_title_notifications_timeout">Ilmoitusten v盲linen v盲himm盲isaika</string>
+    <string name="pref_title_notifications_pebblemsg">Pebble-viestit</string>
+    <string name="pref_summary_notifications_pebblemsg">Tuki sovelluksille, jotka l盲hett盲v盲t ilmoituksia Pebbleen PebbleKitin kautta.</string>
+    <string name="pref_title_notification_filter">脛l盲 h盲iritse</string>
+    <string name="pref_theme_black_background">K盲yt盲 mustaa taustaa tummassa tyyliss盲</string>
+    <string name="pref_title_support_voip_calls">Ota VoIP-sovelluspuhelut k盲ytt枚枚n</string>
+    <string name="pref_title_minimize_priority">Piilota Gadgetbridge-ilmoitus</string>
+    <string name="pref_theme_dark">Tumma</string>
+    <string name="pref_theme_system">J盲rjestelm盲</string>
+    <string name="pref_theme_light">Vaalea</string>
+    <string name="pref_title_theme">Tyyli</string>
+    <string name="pref_title_datetime_syctimeonconnect">Synkronoi aika</string>
+    <string name="pref_header_datetime">P盲iv盲m盲盲r盲 ja aika</string>
+    <string name="pref_default">Oletus</string>
+    <string name="pref_title_general_autostartonboot">K盲ynnist盲 automaattisesti</string>
+    <string name="title_activity_settings">Asetukset</string>
+    <string name="miband_firmware_suggest_whitelist">Jos haluat silti jatkaa ja asiat toimivat kunnolla my枚s sen j盲lkeen, pyyd盲 Gadgetbridgen kehitt盲ji盲 merkitsem盲盲n %s-firmware-versio valkoiselle listalle.</string>
+    <string name="miband_firmware_unknown_warning">T盲t盲 laiteohjelmistoa ei ole testattu, eik盲 se v盲ltt盲m盲tt盲 ole yhteensopiva Gadgetbridgen kanssa.
+\n
+\nEmme suosittele sen asentamista!</string>
+    <string name="pref_summary_rtl">Ota t盲m盲 k盲ytt枚枚n, jos laitteesi ei voi n盲ytt盲盲 oikealta vasemmalle kieli盲</string>
+    <string name="pref_rtl_max_line_length">Oikealta vasemmalle enimm盲isrivin pituus</string>
+    <string name="pref_title_rtl">Oikealta vasemmalle</string>
+    <string name="check_all_applications">Valitse kaikki sovellukset</string>
+    <string name="pref_summary_notification_filter">脛l盲 l盲het盲 ilmoituksia laitteille, kun 脛l盲 h盲iritse -tila on p盲盲ll盲</string>
+    <string name="pref_title_notification_use_as_allow">Salli ilmoitukset valituista sovelluksista</string>
+    <string name="pref_title_transliteration">Translitterointi</string>
+    <string name="pref_summary_transliteration">Ota t盲m盲 k盲ytt枚枚n, jos laitteesi ei tue kielesi fonttia</string>
+    <string name="fossil_hr_edit_action_delete">poista</string>
+    <string name="prefs_activity_in_device_card_sleep_title">Uni</string>
+    <string name="prefs_activity_in_device_card_sleep_title_summary">N盲yt盲 unen kesto</string>
+    <string name="prefs_activity_in_device_card_distance_title_summary">Et盲isyys lasketaan askelista ja askelpituudesta (muutettavissa Asetuksissa - Tietoja sinusta)</string>
+    <string name="prefs_activity_in_device_card_steps_title_summary">N盲yt盲 kokonaisaskeleet</string>
+    <string name="miband_fwinstaller_untested_version">Testaamaton versio!</string>
+    <string name="find_lost_device_you_found_it">L枚ytyi!</string>
+    <string name="fossil_hr_new_action_cancel">peruuta</string>
+    <string name="pref_screen_notification_profile_alarm_clock">Her盲tyskello</string>
+    <string name="clock">Kello</string>
+    <string name="action_discover">Yhdist盲 uusi laite</string>
+    <string name="hr_widget_battery">Akku</string>
+    <string name="hr_widget_active_minutes">Aktiiviset minuutit</string>
+    <string name="devicetype_unknown">Tuntematon laite</string>
+    <string name="kind_watchface">Kellotaulu</string>
+    <string name="reminder_every_day">Joka p盲iv盲</string>
+    <string name="reminder_every_year">Joka vuosi</string>
+    <string name="devicetype_test">Testilaite</string>
+    <string name="title_activity_db_management">Tietojen hallinta</string>
+    <string name="pref_header_location">Sijainti</string>
+    <string name="reminder_every_week">Joka viikko</string>
+    <string name="reminder_every_month">Joka kuukausi</string>
+    <string name="reminder_delete_confirm_title">Poista muistutus</string>
+    <string name="alarm_sat_short">La</string>
+    <string name="alarm_sun_short">Su</string>
+    <string name="alarm_mon_short">Ma</string>
+    <string name="alarm_wed_short">Ke</string>
+    <string name="alarm_thu_short">To</string>
+    <string name="user_feedback_miband_set_alarms_ok">H盲lytykset ovat l盲hetetty laitteeseen.</string>
+    <string name="pref_title_lower_button_function_short">Alempi nappi lyhyt</string>
+    <string name="pref_title_middle_button_function_short">Keskimm盲inen nappi lyhyt</string>
+    <string name="pref_title_lower_button_function_double">Alempi nappi tupla</string>
+    <string name="kind_gps">GPS laiteohjelmisto</string>
+    <string name="timeformat_24h">24h</string>
+    <string name="timeformat_am_pm">AP/IP</string>
+    <string name="kind_font">Fontti</string>
+    <string name="hr_widget_steps">Askeleet</string>
+    <string name="hr_widget_date">PVM</string>
+    <string name="hr_widget_weather">S盲盲</string>
+    <string name="hr_appname_workout">Liikuntasuoritus</string>
+    <string name="hr_appname_stopwatch">Ajanottokello</string>
+    <string name="hr_appname_commute">Arkiliikkuminen</string>
+    <string name="pref_title_upper_button_function_short">Ylempi nappi lyhyt</string>
+    <string name="hr_appname_wellness">Hyvinvointi</string>
+    <string name="ft">jalka</string>
+    <string name="cm">cm</string>
+    <string name="steps_unit">askeleet</string>
+    <string name="hr_widget_calories">Kalorit</string>
+    <string name="hr_widget_nothing">Ei mit盲盲n</string>
+    <string name="pref_title_lower_button_function_long">Alempi nappi pitk盲</string>
+    <string name="pref_title_middle_button_function_long">Keskimm盲inen nappi pitk盲</string>
+    <string name="pref_title_upper_button_function_double">Ylempi nappi tupla</string>
+    <string name="control_center_find_lost_device">L枚yd盲 kadonnut laite</string>
+    <string name="pref_header_other">Muu</string>
+    <string name="pref_header_system">J盲rjestelm盲</string>
+    <string name="pref_header_equalizer">Taajuuskorjain</string>
+    <string name="always">Aina</string>
+    <string name="pref_call_privacy_mode_name">Piilota nimi, mutta n盲yt盲 numero</string>
+    <string name="pref_message_privacy_mode_complete">Piilota sis盲lt枚</string>
+    <string name="pref_title_message_privacy_mode">Viestien yksityisyystila</string>
+    <string name="pref_title_canned_messages_set">P盲ivit盲 laitteessa</string>
+    <string name="pref_title_pebble_settings">Pebblen asetukset</string>
+    <string name="pref_title_enable_outgoing_call">Tue l盲htevi盲 puheluita</string>
+    <string name="pref_header_notifications_and_calls">Ilmoitukset ja puhelut</string>
+    <string name="this_is_a_test_notification_from_gadgetbridge">T盲m盲 on Gadgetbridgest盲 l盲hetetty testi-ilmoitus</string>
+    <string name="alarm_fri_short">Pe</string>
+    <string name="alarm_tue_short">Ti</string>
+    <string name="alarm_snooze">Torkuta</string>
+    <string name="bluetooth_is_disabled_">Bluetooth on poistettu k盲yt枚st盲.</string>
+    <string name="bluetooth_is_not_supported_">Bluetooth ei ole tuettu.</string>
+    <string name="find_my_phone_notification">L枚yd盲 puhelimeni</string>
+    <string name="test">Testi</string>
+    <string name="test_notification">Testi-ilmoitus</string>
+    <string name="unknown_state">Tuntematon tila</string>
+    <string name="connected">Yhdistetty</string>
+    <string name="connecting">Yhdistet盲盲n</string>
+    <string name="pref_title_unit_system">Yksik枚t</string>
+    <string name="pref_title_timeformat">Aikamuoto</string>
+    <string name="pref_title_autoremove_notifications">Poista automaattisesti hyl盲tyt ilmoitukset</string>
+    <string name="pref_title_enable_calendar_sync">Synkronoi kalenteri</string>
+    <string name="pref_title_sunrise_sunset">Auringon nousu ja lasku</string>
+    <string name="pref_header_pebble_timeline">Pebble aikajana</string>
+    <string name="pref_title_enable_pebblekit">Salli 3 osapuolten Android appien p盲盲sy</string>
+    <string name="pref_title_pebble_sync_morpheuz">Synkronoi Morpheuz</string>
+    <string name="pref_title_pebble_sync_misfit">Synkronoi Misfit</string>
+    <string name="pref_title_pebble_sync_health">Synkronoi Pebble Health</string>
+    <string name="pref_title_canned_replies">Vastaukset</string>
+    <string name="pref_title_weather">S盲盲</string>
+    <string name="pref_call_privacy_mode_complete">Piilota nimi ja numero</string>
+    <string name="pref_header_privacy">Yksityisyys</string>
+    <string name="pref_call_privacy_mode_off">N盲yt盲 nimi ja numero</string>
+    <string name="pref_title_call_privacy_mode">Puheluiden yksityisyystila</string>
+    <string name="when_screen_off">Kun n盲ytt枚 on pois p盲盲lt盲</string>
+    <string name="never">Ei koskaan</string>
+    <string name="pref_title_upper_button_function_long">Ylempi nappi pitk盲</string>
+    <string name="pref_title_middle_button_function_double">Keskimm盲inen nappi tupla</string>
+    <string name="devicestatus_connecting">Laite yhdistyy</string>
+    <string name="devicestatus_connected">Laite on yhdistetty</string>
+    <string name="Delete">Poista</string>
+    <string name="pref_message_privacy_mode_off">N盲yt盲 sis盲lt枚</string>
+    <string name="pref_title_weather_location">S盲盲n sijainti (LineageOS:n s盲盲n tarjoajaa varten)</string>
+    <string name="pref_header_development">Kehitt盲j盲asetukset</string>
+    <string name="pref_call_privacy_mode_number">Piilota numero, mutta n盲yt盲 nimi</string>
+    <string name="devicetype_pinetime_jf">PineTime (JF-laiteohjelmisto)</string>
+    <string name="activity_type_elliptical_trainer">Crosstrainer</string>
+    <string name="controlcenter_power_off">Virta pois</string>
+    <string name="activity_type_treadmill">Juoksumatto</string>
+    <string name="activity_type_exercise">Harjoitus</string>
+    <string name="add_test_device">Lis盲盲 testilaite</string>
+    <string name="activity_type_yoga">Jooga</string>
+    <string name="select_all">Valitse kaikki</string>
+    <string name="controlcenter_power_off_confirm_title">Virta pois</string>
+    <string name="activity_type_soccer">Jalkapallo</string>
+    <string name="activity_summary_today">T盲n盲盲n</string>
+    <string name="pref_header_device_spec_settings">Laiteasetukset</string>
+    <string name="pref_header_sensors_calibration">Tunnistimen kalibrointi</string>
+    <string name="activity_type_pingpong">P枚yt盲tennis</string>
+    <string name="activity_type_unknown">Tuntematon aktiviteetti</string>
+    <string name="pref_summary_custom_deviceicon">N盲yt盲 laitekohtainen Android ilmoituskuvake Gadgetbridge-kuvakkeen sijasta, kun yhdistettyn盲</string>
+    <string name="pref_display_add_device_fab_on">Aina n盲kyviss盲</string>
+    <string name="pref_display_add_device_fab_off">N盲kyviss盲 ainoastaan, kun yht盲盲n laitetta ei ole lis盲tty</string>
+    <string name="activity_type_swimming_openwater">Avovesiuinti</string>
+    <string name="activity_type_rowing_machine">Soutulaite</string>
+    <string name="activity_type_cricket">Kriketti</string>
+    <string name="activity_type_basketball">Koripallo</string>
+    <string name="activity_type_badminton">Sulkapallo</string>
+    <string name="activity_type_strength_training">Voimaharjoittelu</string>
+    <string name="activity_summary_yesterday">Eilen</string>
+    <string name="controlcenter_power_off_confirm_description">Oletko varma, ett盲 haluat sammuttaa laitteen?</string>
+    <string name="pref_display_add_device_fab">Nappi uuden laitteen lis盲盲miseksi</string>
+    <string name="pref_summary_notifications_and_calls_enable_misscall">Toistaa joka minuutti</string>
+    <string name="activity_type_running">Juoksu</string>
+    <string name="activity_type_walking">K盲vely</string>
+    <string name="activity_type_swimming">Uinti</string>
+    <string name="activity_type_jump_roping">Hyppynaru</string>
+    <string name="activity_summaries">Urheiluaktiviteetit</string>
+    <string name="qhybrid_title_apps">Sovellukset</string>
+    <string name="kind_firmware">Laiteohjelmisto</string>
+    <string name="qhybrid_title_background_image">Taustakuva</string>
+    <string name="qhybrid_title_file_management">Tiedostonhallinta</string>
+    <string name="pref_title_physical_buttons">Fyysiset napit</string>
+    <string name="activity_type_biking">Py枚r盲ily</string>
+    <string name="activity_type_indoor_cycling">Sis盲py枚r盲ily</string>
+    <string name="prefs_activity_in_device_card_title">N盲yt盲 aktiivisuustiedot laitekortilla</string>
+    <string name="prefs_activity_in_device_card_title_summary">N盲yt盲 t盲m盲nhetkiset askeleet, kuljettu matka tai unen m盲盲r盲 laitekortilla</string>
+    <string name="device_card_activity_card_title">Aktiivisuustiedot laitekortilla</string>
+    <string name="device_card_activity_card_title_summary">Valitse mitk盲 aktiivisuustiedot n盲ytet盲盲n laitekortilla</string>
+    <string name="pref_applications_settings">Listaus sovelluksista</string>
+    <string name="share">Jaa</string>
+    <string name="qhybrid_title_apps_management">Sovellusten hallinta</string>
+    <string name="qhybrid_title_calibration">Kalibrointi</string>
+    <string name="pref_title_device_spec_settings_force_time">Pakota ajan synkronointi</string>
+    <string name="pref_title_vibration_strength">V盲rin盲n voimakkuus</string>
+    <string name="pref_summary_enable_calendar_sync">L盲het盲 kalenteritapahtumat aikajanalle</string>
+    <string name="pref_title_custom_deviceicon">N盲yt盲 laitekohtainen ilmoituskuvake</string>
+    <string name="pref_rtl_max_line_length_summary">Lyhent盲盲 tai pident盲盲 oikealta vasemmalle tekstien jakamista</string>
+    <string name="prefs_sounds">脛盲net</string>
+    <string name="menuitem_nfc">NFC</string>
+    <string name="menuitem_stress">Stressi</string>
+    <string name="menuitem_hr">Syke</string>
+    <string name="sony_automatic_power_off_3_hour">3 tuntia</string>
+    <string name="devicetype_bose_qc35">Bose QC35</string>
+    <string name="menuitem_notifications">Ilmoitukset</string>
+    <string name="prefs_right">Oikea</string>
+    <string name="sony_automatic_power_off_1_hour">1 tunti</string>
+    <string name="sony_automatic_power_off_5_min">5 minuuttia</string>
+    <string name="sony_automatic_power_off_30_min">30 minuuttia</string>
+    <string name="prefs_equalizer">Taajuuskorjain</string>
+    <string name="menuitem_status">Tila</string>
+    <string name="prefs_left">Vasen</string>
+    <string name="activity_prefs_chart_min_heart_rate">Minimisyke</string>
+    <string name="pref_title_charts_average">N盲yt盲 keskiarvot taulukoissa</string>
+    <string name="charts_legend_heartrate">Syke</string>
+    <string name="hr_widget_heart_rate">Syke</string>
+    <string name="pref_title_chart_heartrate_color">Sykkeen v盲ri</string>
+    <string name="weekstepschart_steps_a_month">Askeleet kuukaudessa</string>
+    <string name="pref_chart_heartrate_color_orange">Oranssi</string>
+    <string name="pref_chart_heartrate_color_red">Punainen</string>
+    <string name="Cancel">Peruuta</string>
+    <string name="ok">OK</string>
+    <string name="activity_prefs_gender">Sukupuoli</string>
+    <string name="pref_header_charts">Taulukoiden asetukset</string>
+    <string name="activity_prefs_height_cm">Pituus (cm)</string>
+    <string name="activity_prefs_step_length_cm">Askeleen pituus (cm)</string>
+    <string name="pref_chart_sleep_rolling_24_on">Viimeiset 24h</string>
+    <string name="live_activity_heart_rate">Syke</string>
+    <string name="action_db_management">Tietojen hallinta</string>
+    <string name="watch9_time_hours">Tunnit:</string>
+    <string name="language_and_region_prefs">Kieli ja alue</string>
+    <string name="device_fw">Laiteohjelmiston versio: %1$s</string>
+    <string name="DEVINFO_HR_VER">"SYKE: "</string>
+    <string name="charts_legend_heartrate_average">Keskisyke</string>
+    <string name="activity_type_hiking">Patikointi</string>
+    <string name="kind_resources">Resurssit</string>
+    <string name="yes">Kyll盲</string>
+    <string name="activity_type_climbing">Kiipeily</string>
+    <string name="devicetype_waspos">Wasp-OS</string>
+    <string name="menuitem_music">Musiikki</string>
+    <string name="menuitem_more">Enemm盲n</string>
+    <string name="menuitem_alexa">Alexa</string>
+    <string name="menuitem_flashlight">Taskulamppu</string>
+    <string name="menuitem_temperature">L盲mp枚tila</string>
+    <string name="watch9_time_minutes">Minuutit:</string>
+    <string name="watch9_time_seconds">Sekunnit:</string>
+    <string name="watch9_calibration_button">Kalibroi</string>
+    <string name="menuitem_mutephone">Mykist盲 puhelin</string>
+    <string name="appwidget_text">Zzz</string>
+    <string name="activity_prefs_chart_max_heart_rate">Maksimisyke</string>
+    <string name="activity_prefs_weight_kg">Paino (kg)</string>
+    <string name="activity_prefs_year_birth">Syntym盲vuosi</string>
+    <string name="activity_prefs_charts">Taulukoiden asetukset</string>
+    <string name="set">Aseta</string>
+    <string name="maximum_duration">Kesto</string>
+    <string name="_pebble_watch_mute">Mykist盲</string>
+    <string name="_pebble_watch_reply">Vastaa</string>
+    <string name="notification_channel_low_battery_name">Akku v盲hiss盲</string>
+    <string name="waiting_for_reconnect">Odottaa yhteyden muodostumista</string>
+    <string name="activity_prefs_about_you">Tietoja sinusta</string>
+    <string name="activity_list_summary_active_steps">Aktiiviset askeleet</string>
+    <string name="pref_screen_notification_profile_generic_navigation">Navigaatio</string>
+    <string name="stats_y_axis_label">Askeleita minuutissa</string>
+    <string name="about_links">Linkit</string>
+    <string name="zetime_prefs_inactivity_mo">Maanantai</string>
+    <string name="zetime_prefs_inactivity_we">Keskiviikko</string>
+    <string name="zetime_prefs_inactivity_su">Sunnuntai</string>
+    <string name="preferences_category_device_specific_settings">Laitekohtaiset asetukset</string>
+    <string name="calories">Kalorit</string>
+    <string name="distance">Et盲isyys</string>
+    <string name="live_activity_steps_history">Askeleiden historia</string>
+    <string name="live_activity_total_steps">Kokonaisaskeleet</string>
+    <string name="about_title">Tietoja</string>
+    <string name="about_activity_title_generic">Tietoja Gadgetbridgest盲</string>
+    <string name="activity_filter_date_from">Alkaen</string>
+    <string name="activity_filter_date_to">P盲盲ttyen</string>
+    <string name="pref_screen_notification_profile_missed_call">Vastaamatta j盲盲neen puhelun ilmoitus</string>
+    <string name="about_version">Versio %s</string>
+    <string name="chart_steps">Askeleet</string>
+    <string name="pref_screen_notification_profile_email">S盲hk枚posti-ilmoitus</string>
+    <string name="pref_screen_notification_profile_generic_social">Sosiaalinen verkosto</string>
+    <string name="pref_screen_notification_profile_incoming_call">Saapuvan puhelun ilmoitus</string>
+    <string name="zetime_prefs_inactivity_th">Torstai</string>
+    <string name="zetime_prefs_inactivity_tu">Tiistai</string>
+    <string name="pref_screen_notification_profile_calendar">Kalenteri-ilmoitus</string>
+    <string name="activity_detail_duration_label">Kesto</string>
+    <string name="watchface_setting_wrist_flick_duration">Kesto (ms):</string>
+    <string name="sleep_activity_date_range">Alkaen %1$s p盲盲ttyen %2$s</string>
+    <string name="weekstepschart_steps_a_week">Askeleita viikossa</string>
+    <string name="title_activity_device_specific_settings">Laitekohtaiset asetukset</string>
+    <string name="zetime_prefs_inactivity_sa">Lauantai</string>
+    <string name="zetime_prefs_inactivity_fr">Perjantai</string>
+    <string name="pref_title_location_latitude">Leveysaste</string>
+    <string name="pref_title_location_longitude">Pituusaste</string>
+    <string name="distance_format_feet">###ft</string>
+    <string name="distance_format_kilometers">###.#km</string>
+    <string name="distance_format_meters">###m</string>
+    <string name="devicetype_vesc">VESC</string>
+    <string name="watchface_dialog_widget_width">Leveys:</string>
+    <string name="watchface_dialog_widget_timezone">Aikavy枚hyke:</string>
+    <string name="pref_activity_recognition_mode_auto">automaattinen</string>
+    <string name="pref_activity_recognize_rowing">tunnista soutaminen</string>
+    <string name="pref_activity_recognize_running">tunnista juokseminen</string>
+    <string name="menuitem_menu">Valikko</string>
+    <string name="pref_activity_recognize_walking">tunnista k盲veleminen</string>
+    <string name="pref_activity_recognition_mode_ask">kysy</string>
+    <string name="pref_activity_recognize_biking">tunnista py枚r盲ily</string>
+    <string name="application_name_banglejs_main">Bangle.js Gadgetbridge</string>
+    <string name="gadgetbridge_running_banglejs_main">Bangle.js Gadgetbridge on k盲ynniss盲</string>
+    <string name="gadgetbridge_running_generic">Gadgetbridge on k盲ynniss盲</string>
+    <string name="supercars_lights_label">Valot</string>
+    <string name="title_activity_controlcenter_banglejs_main">Bangle.js Gadgetbridge</string>
+    <string name="title_activity_charts">Aktiivisuus ja uni</string>
+    <string name="info_no_devices_connected">Ei laitteita yhteydess盲</string>
+    <string name="zetime_title_settings">ZeTimen asetukset</string>
+    <string name="world_clock_delete_confirm_title">Poista \'%1$s\'</string>
+    <string name="activity_sleepchart_activity_and_sleep">Aktiivisuus</string>
+    <string name="preferences_hplus_settings">HPlusin/Makibesin asetukset</string>
+    <string name="pref_world_clocks_title">Maailmankellot</string>
+    <string name="preferences_miband_1_2_settings" tools:ignore="TypographyFractions">Mi Band 1/2:n asetukset</string>
+    <string name="world_clock_delete_confirm_description">Oletko varma, ett盲 haluat poistaa t盲m盲n maailmankellon\?</string>
+    <string name="not_connected">Ei yhteydess盲</string>
+    <string name="pref_theme_dynamic">Dynaamiset v盲rit</string>
+    <string name="changelog_ok_button">Valmis</string>
+    <string name="about_contributors">Osallistujat</string>
+    <string name="controlcenter_start_activitymonitor">Aktiivisuutesi</string>
+    <string name="preferences_qhybrid_settings">Q Hybridin asetukset</string>
+    <string name="changelog_full_title">Muutosloki</string>
+    <string name="pref_write_logfiles">Kirjoita lokitiedostoja</string>
+    <string name="pref_theme_dynamic_colors_not_available_warning">Dynaamiset v盲rit ei ole tuettu, se vaati Android 12:sta tai uudemman. T盲st盲 johtuen Gadgetbridge k盲ytt盲盲 oletus Material 3 -v盲rej盲.</string>
+    <string name="changelog_show_full">Lis盲盲鈥�</string>
+    <string name="action_changelog">Muutosloki</string>
+    <string name="about_hash">Commit %s</string>
+    <string name="reminder_delete_confirm_description">Oletko varma, ett盲 haluat poistaa muistutuksen\?</string>
+    <string name="sony_speak_to_chat_sensitivity_high">Korkea</string>
+    <string name="sony_speak_to_chat_focus_on_voice">Painotus 盲盲neen</string>
+    <string name="sony_automatic_power_off">Automaattinen sammutus</string>
+    <string name="sony_equalizer_preset_manual">Manuaalinen</string>
+    <string name="sony_speak_to_chat_timeout_short">Lyhyt (15s)</string>
+    <string name="sony_speak_to_chat_timeout_long">Pitk盲 (1m)</string>
+    <string name="sony_equalizer_preset_speech">Puhe</string>
+    <string name="sony_speak_to_chat_sensitivity_auto">Automaattinen</string>
+    <string name="audio_codec">脛盲nikoodekki</string>
+    <string name="sony_equalizer_preset_bass_boost">Basson tehostus</string>
+    <string name="sony_equalizer_preset_relaxed">Rento</string>
+    <string name="sony_equalizer_preset_mellow">Lempe盲</string>
+    <string name="sony_equalizer">Taajuuskorjain</string>
+    <string name="sony_anc_optimize_title">Optimoi</string>
+    <string name="sony_anc_optimizer_status_atmospheric_pressure">Mitataan ilmanpainetta鈥�</string>
+    <string name="sony_anc_optimizer_status_analyzing">Analysoidaan鈥�</string>
+    <string name="sony_speak_to_chat_timeout_standard">Vakio (30s)</string>
+    <string name="sony_speak_to_chat_sensitivity_low">Matala</string>
+    <string name="sony_equalizer_preset_excited">Innostunut</string>
+    <string name="sony_ambient_sound_noise_cancelling">Vastamelu</string>
+    <string name="sony_ambient_sound_level">Ymp盲rist枚盲盲nen taso</string>
+    <string name="sony_equalizer_band_6300">6300Hz</string>
+    <string name="sony_ambient_sound">Tila</string>
+    <string name="devicetype_sony_wh_1000xm5">Sony WH-1000XM5</string>
+    <string name="sony_equalizer_preset_custom_2">Mukautettu 2</string>
+    <string name="sony_touch_sensor">Kosketuspaneeli</string>
+    <string name="sony_speak_to_chat_sensitivity">Puheentunnistuksen herkkyys</string>
+    <string name="pref_anc_optimizer_state_pressure">Ilmanpaine</string>
+    <string name="sony_anc_optimizer_status_finished">Viimeistell盲盲n鈥�</string>
+    <string name="sony_anc_optimize_confirmation_description">Pid盲 kuulokkeita p盲盲ss盲 tavalliseen tapaan. Jos niiden pitotapa tai ilmanpaine muuttuu, optimoi uudelleen.</string>
+    <string name="devicetype_sony_wh_1000xm3">Sony WH-1000XM3</string>
+    <string name="sony_anc_optimizer_status_starting">Aloitetaan鈥�</string>
+    <string name="sony_automatic_power_off_when_taken_off">Kun kuulokkeet otetaan pois</string>
+    <string name="sony_equalizer_preset_vocal">Laulu</string>
+    <string name="sony_notification_voice_guide">Ilmoitukset ja 盲盲niopas</string>
+    <string name="devicetype_sony_wf_1000xm3">Sony WF-1000XM3</string>
+    <string name="sony_ambient_sound_ambient_sound">Ymp盲rist枚盲盲ni</string>
+    <string name="sony_ambient_sound_focus_voice">Painotus 盲盲neen</string>
+    <string name="sony_speak_to_chat_timeout_off">Pois</string>
+    <string name="devicetype_sonyswr12">Sony SWR12</string>
+    <string name="sony_equalizer_band_16000">16000Hz</string>
+    <string name="sony_equalizer_preset_off">Pois</string>
+    <string name="devicetype_sony_wh_1000xm2">Sony WH-1000XM2</string>
+    <string name="devicetype_sony_wf_sp800n">Sony WF-SP800N</string>
+    <string name="sony_speak_to_chat_timeout">Aika, kunnes tila suljetaan</string>
+    <string name="sony_equalizer_preset_treble_boost">Diskantin tehostus</string>
+    <string name="sony_anc_optimizer_status_wearing_condition">Mitataan kuulokkeiden pitotapaa鈥�</string>
+    <string name="sony_ambient_sound_off">Pois</string>
+    <string name="sony_equalizer_band_1000">1000Hz</string>
+    <string name="sony_pause_when_taken_off">Keskeyt盲, kun kuulokkeet otetaan pois</string>
+    <string name="sony_equalizer_preset_custom_1">Mukautettu 1</string>
+    <string name="sony_anc_optimize_description">Napsauta aloittaaksesi vastamelun optimoinnin.</string>
+    <string name="sony_automatic_power_off_off">脛l盲 sammuta</string>
+    <string name="sony_anc_optimize_confirmation_title">Vastamelun optimointi</string>
+    <string name="sony_sound_position_off">Pois</string>
+    <string name="sony_equalizer_preset_bright">Kirkas</string>
+    <string name="devicetype_sony_linkbuds_s">Sony LinkBuds S</string>
+    <string name="sony_speak_to_chat_summary">Sammuta vastamelu automaattisesti, kun alat puhua.</string>
+    <string name="devicetype_sony_wh_1000xm4">Sony WH-1000XM4</string>
+    <string name="sony_equalizer_band_2500">2500Hz</string>
+    <string name="sony_equalizer_band_400">400Hz</string>
+    <string name="pref_header_sony_anc_optimizer">Vastamelun optimointi</string>
+    <string name="devicetype_sony_wf_1000xm4">Sony WF-1000XM4</string>
+    <string name="pref_header_sony_ambient_sound_control">Ymp盲rist枚盲盲nten hallinta</string>
+    <string name="pref_header_time">Aika</string>
+    <string name="title_activity_controlcenter_main_nightly">Gadgetbridge Nightly</string>
+    <string name="appmanager_app_share">Jaa</string>
+    <string name="pref_header_calendar">Kalenteri</string>
+    <string name="about_activity_title_banglejs_main">Tietoja Bangle.js Gadgetbridgest盲</string>
+    <string name="pref_title_notification_prefer_long_text">Suosi ilmoitusten pitk盲盲 muotoa</string>
+    <string name="pref_title_notifications_ignore_low_priority">Sivuuta matalan prioriteetin ilmoitukset</string>
+    <string name="pref_summary_notifications_ignore_low_priority">脛l盲 l盲het盲 matalan prioriteetin ilmoituksia laitteille</string>
+    <string name="open_fw_installer_warning_title">Varoitus</string>
+    <string name="pref_title_notifications_generic_settings">Androidin ilmoitusasetukset</string>
+    <string name="pref_summary_notification_prefer_long_text">Jos saatavilla, l盲het盲 ilmoituksen pitk盲 muoto laitteisiin</string>
+    <string name="about_activity_title_main_nightly">Tietoja Gadgetbridge Nightlyst盲</string>
+    <string name="pref_header_health">Terveys</string>
+    <string name="about_activity_title_banglejs_nightly">Tietoja Bangle.js Gadgetbridge Nightlyst盲</string>
+    <string name="pref_title_notification_cache_while_disconnected">V盲limuistita ilmoitukset kun saavuttamattomissa</string>
+    <string name="gadgetbridge_running_banglejs_nightly">Bangle.js Gadgetbridge Nightly on k盲ynniss盲</string>
+    <string name="open_fw_installer_pick_file">Valitse tiedosto</string>
+    <string name="title_activity_controlcenter_banglejs_nightly">Bangle.js Gadgetbridge Nightly</string>
+    <string name="pref_summary_notification_cache_while_disconnected">Laitteiden ollessa saavuttamattomissa v盲limuistita ilmoitukset ja l盲het盲 ne niille yhteyden palattua</string>
+    <string name="gadgetbridge_running_main_nightly">Gadgetbridge Nightly on k盲ynniss盲</string>
+    <string name="application_name_banglejs_nightly">Bangle.js Gadgetbridge Nightly</string>
+    <string name="application_name_main_nightly">Gadgetbridge Nightly</string>
+    <string name="device_not_connected">Ei yhdistetty.</string>
+    <string name="pref_header_audio">脛盲ni</string>
+    <string name="dateformat_time">Aika</string>
+    <string name="dateformat_date_time">Aika ja p盲iv盲m盲盲r盲</string>
+    <string name="prefs_find_phone">Etsi puhelin</string>
+    <string name="button_watchface_add_widget">Lis盲盲 widgetti</string>
+    <string name="pref_header_workout">Kuntoilu</string>
+    <string name="qhybrid_calibration_1_step">1 askel</string>
+    <string name="watchface_dialog_widget_color_black">Musta</string>
+    <string name="watchface_dialog_widget_y_coordinate">Y kordinaatti (max 240)</string>
+    <string name="pref_header_generic">Geneeriset</string>
+    <string name="devicetype_amazfit_trex_pro">Amazfit T-Rex Pro</string>
+    <string name="qhybrid_calibration_counterclockwise">Vastap盲iv盲盲n</string>
+    <string name="qhybrid_calibration_100_steps">100 askelta</string>
+    <string name="pref_title_developer_settings">Kehitt盲j盲 asetukset</string>
+    <string name="watchface_dialog_widget_cat_2nd_tz_widget">Toinen aikavy枚hyke widgetti</string>
+    <string name="watchface_dialog_widget_x_coordinate">X kordinaatti (max 240)</string>
+    <string name="miband2_prefs_dateformat">Aika formaatti</string>
+    <string name="qhybrid_calibration_10_steps">10 askelta</string>
+    <string name="watchface_widget_type_steps">Askeleet</string>
+    <string name="pref_header_display">N盲ytt枚</string>
+    <string name="qhybrid_calibration_clockwise">My枚t盲p盲iv盲盲n</string>
+    <string name="watchface_dialog_widget_color_white">Valkoinen</string>
+    <string name="watchface_widget_type_date">P盲iv盲m盲盲r盲</string>
+    <string name="pref_header_sound_vibration">脛盲ni ja v盲rin盲</string>
+    <string name="pref_title_general_reconnectonlytoconnected">Uudelleen yhdist盲 vain yhdistettyihin laitteisiin</string>
+    <string name="pref_header_connection">Yhteys</string>
+    <string name="watchface_dialog_title_widget">Widgetti asetukset</string>
+    <string name="waiting_for_bluetooth">Odotetaan Bluetoothia鈥�</string>
+    <string name="watchface_dialog_widget_cat_custom_widget">Kustomi widgetti</string>
+    <string name="watchface_dialog_widget_cat_generic">Geneerinen</string>
+    <string name="watchface_dialog_widget_cat_position">Sijainti</string>
+    <string name="appmanager_app_edit">Muokkaa</string>
+    <string name="scan_scanning_all_devices">Skannataan kaikkia laitteita</string>
+    <string name="scan_not_scanning">Ei skannata</string>
+    <string name="watchface_dialog_widget_preset_right">Oikea</string>
+    <string name="watchface_dialog_widget_color">V盲ri</string>
+    <string name="watchface_dialog_widget_preset_left">Vasen</string>
+    <string name="watchface_widget_type_weather">S盲盲</string>
+    <string name="mi2_prefs_button_action_vibrate">Kytke rannekkeen v盲rin盲</string>
+    <string name="accuracy">Tarkkuus</string>
+    <string name="balanced">Tasoitettu</string>
+    <string name="all_satellites">Kaikki Satelliittit</string>
+    <string name="speed_first">Nopeus ensins</string>
+    <string name="about_description_generic">Pilvet枚n copyleft libre versio suljetun l盲hdekoodin Android gadget-sovelluksille.</string>
+    <string name="about_activity_title_main_nopebble">Tietoja Gadgetbridge Nightlysta (Ei Pebble盲)</string>
+    <string name="gadgetbridge_running_main_nopebble">Y枚llinen NoPebble GB k盲ynniss盲</string>
+    <string name="title_activity_controlcenter_main_nopebble">Gadgetbridge (Pebble-palveluntarjoajaa)</string>
+    <string name="application_name_main_nopebble">Gadgetbridge (Nightly, ei Pebble-palveluntarjoajaa)</string>
+    <string name="power_saving">Virrans盲盲st枚</string>
+    <string name="custom">Muokattu</string>
+    <string name="gps_galileo">GPS + GALILEO</string>
+    <string name="accuracy_first">Tarkkuus ensin</string>
+    <string name="appmanager_watchface_activate">Aktivoi</string>
+    <string name="appmanager_app_start">Aloita</string>
+    <string name="appmanager_app_download">Lataa v盲limuistiin</string>
+    <string name="appmanager_download_started">Sovelluksen lataus k盲ynnistetty</string>
+    <string name="controlcenter_get_heartrate_measurement">Hae sykkeen mittaus</string>
+    <string name="debugactivity_confirm_remove_device_preferences_title">Poista laitekohtaiset mukautukset?</string>
+    <string name="debugactivity_confirm_remove_device_preferences">T盲m盲 poistaa laitekohtaiset mukautukset kaikista yhdistetyist盲 laitteista. Oletko varma?</string>
+    <string name="single_band">Yksi Ranneke</string>
+    <string name="low_power_gps">V盲h盲virtainen GPS</string>
+    <string name="gps">GPS</string>
+    <string name="gps_bds">GPS + BDS</string>
+    <string name="appmanager_item_outdated">(vanhentunut)</string>
+    <string name="appmanager_downloaded_to_cache">Ladattu %s v盲limuistiin</string>
+    <string name="appmanager_download_app_error">Virhe ladataessa sovellusta</string>
+    <string name="gps_glonass">GPS + GLONASS</string>
+</resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-fr-rCA/strings.xml b/app/src/main/res/values-fr-rCA/strings.xml
new file mode 100644
index 0000000..5ad3dc8
--- /dev/null
+++ b/app/src/main/res/values-fr-rCA/strings.xml
@@ -0,0 +1,895 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources xmlns:tools="http://schemas.android.com/tools">
+    <string name="discovery_pair_title">Appairer avec %1$s\?</string>
+    <string name="dbmanagementactivity_delete_old_activitydb_confirmation">Voulez-vous vraiment d茅truire enti猫rement la base de donn茅es\? Toutes vos donn茅es non import茅es seront perdues.</string>
+    <string name="dbmanagementactivity_delete_old_activity_db">Voulez-vous d茅truire les anciennes activit茅s de la base de donn茅es\?</string>
+    <string name="dbmanagementactivity_really_delete_entire_db">Voulez-vous vraiment d茅truire enti猫rement la base de donn茅es\? Toutes vos donn茅es d\'activit茅 et vos informations issues de vos appareils seront perdues.</string>
+    <string name="dbmanagementactivity_delete_activity_data_title">D茅truire les anciennes donn茅es\?</string>
+    <string name="dbmanagementactivity_overwrite_database_confirmation">Voulez-vous vraiment effacer la base de donn茅es actuelle? Toutes vos donn茅es (si vous en avez) seront perdues.</string>
+    <string name="dbmanagementactivity_import_data_title">Importer des donn茅es\?</string>
+    <string name="chart_no_data_synchronize">Aucune donn茅e. Synchroniser l\'appareil\?</string>
+    <string name="prefs_wearside">Port main gauche ou droite\?</string>
+    <string name="cannot_connect_bt_address_invalid_">Connexion impossible. L鈥檃dresse Bluetooth est-elle valide\?</string>
+    <string name="debugactivity_really_factoryreset_title">Vraiment retourner aux param猫tres d\'usine\?</string>
+    <string name="mi2_fw_installhandler_fw53_hint">Vous devez installer la version %1$s avant d\'installer ce micrologiciel!</string>
+    <string name="find_lost_device_you_found_it">Trouv茅!</string>
+    <string name="find_device_you_found_it">Vous l\'avez trouv茅!</string>
+    <string name="updatefirmwareoperation_updateproblem_do_not_reboot">Probl猫me avec le transfert du micrologiciel. Ne red茅marrez pas votre Mi Band!</string>
+    <string name="pbwinstallhandler_incorrect_hw_revision">Version du mat茅riel incorrecte!</string>
+    <string name="miband_fwinstaller_untested_version">Version non test茅e!</string>
+    <string name="notif_export_failed_title">L\'exportation de la base de donn茅es a 茅chou茅! Veuillez v茅rifier vos param猫tres.</string>
+    <string name="pref_auto_fetch_summary">R茅cup茅ration au d茅verrouillage de l\'茅cran. Fonctionne uniquement si un m茅canisme de verrouillage est configur茅!</string>
+    <string name="pref_summary_pebble_forceuntested">Activer les fonctionnalit茅s non-test茅es. 脌 VOS PROPRES RISQUES ET P脡RILS!</string>
+    <string name="miband_firmware_unknown_warning">Ce micrologiciel n\'a pas 茅t茅 test茅 et peut ne pas 锚tre compatible avec Gadgetbridge. 
+\n 
+\nIl n\'est PAS conseill茅 de le flasher!</string>
+    <string name="fw_upgrade_notice_miband4">Vous 锚tes sur le point d\'installer le firmware %s sur votre Mi Band 4. 
+\n 
+\nVeuillez vous assurez d\'installer le fichier .fw, et apr猫s que le fichier .res. Votre bracelet red茅marrera apr猫s l\'installation du fichier .fw. 
+\n 
+\nRemarque : Vous n\'avez pas 脿 installer .res s\'il est exactement le m锚me que celui pr茅c茅demment install茅. 
+\n 
+\n脌 VOS RISQUES ET P脡RILS ET P脡RILS!</string>
+    <string name="fw_upgrade_notice_miband3">Vous allez installer le micrologiciel %s dans votre Mi Band 3. 
+\n 
+\nAssurez-vous d\'installer d\'abord le fichier .fw, et ensuite le fichiers .res. Votre bracelet red茅marrera apr猫s l\'installation du fichiers .fw. 
+\n 
+\nRemarque : Vous n\'avez pas besoin d\'installer le fichier .res si c\'est exactement le m锚me que celui d茅j脿 install茅. 
+\n 
+\n脌 VOS聽RISQUES聽ET聽P脡RILS ET P脡RILS!</string>
+    <string name="fw_upgrade_notice_amazfitgts">Vous 锚tes sur le point d\'installer le micrologiciel %s sur votre Amazfit GTS. 
+\n 
+\nVeuillez installer le fichier .fw, puis le fichier .res et enfin le fichier .gps. Votre montre red茅marrera apr猫s installation du fichier .fw. 
+\n 
+\nNote : vous n\'avez pas 脿 installer les fichiers .res et .gps s\'ils sont exactement les m锚mes que ceux install茅s pr茅c茅demment. 
+\n 
+\n脌 VOS RISQUES ET P脡RILS ET P脡RILS!</string>
+    <string name="fw_upgrade_notice_amazfitgtr">Vous 锚tes sur le point d\'installer le micrologiciel %s sur votre Amazfit GTR. 
+\n 
+\nVeuillez installer le fichier .fw, puis le fichier .res et enfin le fichier .gps. Votre montre red茅marrera apr猫s installation du fichier .fw. 
+\n 
+\nNote : vous n\'avez pas 脿 installer les fichiers .res et .gps s\'ils sont exactement les m锚mes que ceux install茅s pr茅c茅demment. 
+\n 
+\n脌 VOS RISQUES ET P脡RILS ET P脡RILS!</string>
+    <string name="fw_upgrade_notice_amazfitcor2">Vous 锚tes sur le point d\'installer le micrologiciel %s sur votre Amazfit Cor 2. \n \nVeuillez installer le fichier .fw, puis le fichier .res. Votre montre red茅marrera apr猫s installation du .fw. \n \nNote : il n\'est pas n茅cessaire d\'installer le fichier .res si celui-ci est identique 脿 celui install茅 pr茅c茅demment. \n \nCONTINUEZ 脌 VOS RISQUES ET P脡RILS! \n \nNON TEST脡, IL PEUT 脢TRE N脡CESSAIRE DE FLASHER UN MICROLOGICIEL BEATS_W SI LE NOM DE L\'APPAREIL EST 芦 Amazfit Band 2 禄</string>
+    <string name="fw_upgrade_notice_amazfitcor">Vous 锚tes sur le point d\'installer le micrologiciel %s sur votre Amazfit Cor. 
+\n 
+\nVeuillez installer le fichier .fw, puis le fichier .res. Votre montre red茅marrera apr猫s installation du .fw. 
+\n 
+\nNote : vous ne devez pas installer .res si celui-ci est identique 脿 celui install茅 pr茅c茅demment. 
+\n 
+\nCONTINUEZ 脌 VOS RISQUES ET P脡RILS!</string>
+    <string name="fw_upgrade_notice_amazfitbip_lite">Vous 锚tes sur le point d\'installer le micrologiciel %s sur votre Amazfit Bip Lite. 
+\n 
+\nVeuillez installer le fichier .fw, puis le fichier .res. Votre montre red茅marrera apr猫s installation du fichier .fw. 
+\n 
+\nNote : vous n\'avez pas 脿 installer le fichier .res si c\'est exactement le m锚me que celui install茅 pr茅c茅demment. 
+\n 
+\n脌 VOS RISQUES ET P脡RILS!</string>
+    <string name="fw_upgrade_notice_amazfitbip">Vous 锚tes sur le point d\'installer le micrologiciel %s sur votre Amazfit Bip. 
+\n 
+\nVeuillez installer le fichier .fw, puis le fichier .res, puis le fichier.gps.. Votre montre red茅marrera apr猫s installation du .fw. 
+\n 
+\nNote : vous ne devez pas installer les fichiers .res et .gps si ceux-ci sont identiques 脿 ceux install茅s pr茅c茅demment. 
+\n 
+\nCONTINUEZ 脌 VOS RISQUES ET P脡RILS!</string>
+    <string name="controlcenter_delete_device_dialogmessage">Ceci va supprimer l鈥檃ppareil et toutes les donn茅es associ茅es!</string>
+    <string name="watch9_time_hours">Heures聽:</string>
+    <string name="watch9_time_minutes">Minutes聽:</string>
+    <string name="activity_db_management_empty_db_warning">Attention! En cliquant sur ce bouton, vous supprimerez toutes vos donn茅es et recommencerez 脿 z茅ro.</string>
+    <string name="live_activity_max_heart_rate">Mesure cardiaque actuelle / maximum聽: %1$d / %2$d</string>
+    <string name="overstep">Trop d\'activit茅聽: %1$d</string>
+    <string name="lack_of_step">Manque d\'activit茅聽: %1$d</string>
+    <string name="overslept">Exc猫s de sommeil聽: %1$s</string>
+    <string name="lack_of_sleep">Manque de sommeil聽: %1$s</string>
+    <string name="zetime_activity_tracking_summary">Activer le suivi des activit茅s : comptera vos pas, etc.</string>
+    <string name="pref_summary_pebble_forceprotocol">Cette option force l\'utilisation du protocole de notification le plus r茅cent selon votre version du micrologiciel. 脌 VOS RISQUES ET P脡RILS!</string>
+    <string name="pref_summary_location_keep_uptodate">Essayer de garder la localisation 脿 jour pendant le fonctionnement, sinon utiliser l鈥檈mplacement enregistr茅</string>
+    <string name="hr_widget_calories">Calories</string>
+    <string name="appwidget_text">Zzz</string>
+    <string name="menuitem_alipay">Alipay</string>
+    <string name="devicetype_amazfit_gts">Amazfit GTS</string>
+    <string name="devicetype_makibes_hr3">Makibes HR3</string>
+    <string name="devicetype_mijia_lywsd02">Mijia Smart Clock</string>
+    <string name="devicetype_itag">iTag</string>
+    <string name="devicetype_miscale2">Mi Scale 2</string>
+    <string name="devicetype_casiogb6900">Casio GB-6900</string>
+    <string name="devicetype_roidmi3">Roidmi 3</string>
+    <string name="devicetype_roidmi">Roidmi</string>
+    <string name="devicetype_watch9">Watch 9</string>
+    <string name="devicetype_mykronoz_zetime">MyKronoz ZeTime</string>
+    <string name="devicetype_qhybrid">Fossil Q Hybrid</string>
+    <string name="devicetype_xwatch">XWatch</string>
+    <string name="devicetype_teclast_h30">Teclast H30</string>
+    <string name="devicetype_exrizu_k8">Exrizu K8</string>
+    <string name="devicetype_makibes_f68">Makibes F68</string>
+    <string name="devicetype_hplus">HPlus</string>
+    <string name="devicetype_liveview">LiveView</string>
+    <string name="devicetype_vibratissimo">Vibratissimo</string>
+    <string name="devicetype_amazfit_bips">Amazfit Bip S</string>
+    <string name="devicetype_amazfit_gtr">Amazfit GTR</string>
+    <string name="devicetype_amazfit_cor2">Amazfit Cor 2</string>
+    <string name="devicetype_amazfit_cor">Amazfit Cor</string>
+    <string name="devicetype_amazfit_bip_lite">Amazfit Bip Lite</string>
+    <string name="devicetype_amazfit_bip">Amazfit Bip</string>
+    <string name="devicetype_miband4">Mi Band 4</string>
+    <string name="devicetype_miband3">Mi Band 3</string>
+    <string name="devicetype_miband2">Mi Band 2</string>
+    <string name="devicetype_miband">Mi Band</string>
+    <string name="devicetype_pebble">Pebble</string>
+    <string name="title_activity_vibration">Vibration</string>
+    <string name="calories">Calories</string>
+    <string name="miband_prefs_vibration">Vibration</string>
+    <string name="vibration_profile_long">Long</string>
+    <string name="title_activity_controlcenter_generic">Gadgetbridge</string>
+    <string name="application_name_generic">Gadgetbridge</string>
+    <plurals name="widget_alarm_target_hours" tools:ignore="MissingQuantity">
+        <item quantity="one">%d heure</item>
+        <item quantity="other">%d heures</item>
+    </plurals>
+    <string name="pref_qhybrid_title_widget_draw_circles">Widget tra莽ant des cercles</string>
+    <string name="pbwinstallhandler_app_item">%1$s (%2$s)</string>
+    <string name="installhandler_firmware_name">%1$s: %2$s %3$s</string>
+    <string name="notif_battery_low_extended">%1$s pile faible聽: %2$s</string>
+    <string name="notif_battery_low">%1$s pile faible</string>
+    <string name="notif_battery_low_percent">%1$s pile restante聽: %2$s%%</string>
+    <string name="device_with_rssi">%1$s (%2$s)</string>
+    <string name="appversion_by_creator">%1$s par %2$s</string>
+    <string name="pref_header_auto_fetch">R茅cup茅ration automatique</string>
+    <string name="pref_title_relax_firmware_checks">Activez cette option si vous souhaitez flasher un microprogramme qui n\'est pas destin茅 脿 votre appareil (脿 vos propres risques)</string>
+    <string name="pref_summary_relax_firmware_checks">Assouplir les contr么les des microprogrammes</string>
+    <string name="pref_title_vibration_strength">Puissance du vibreur</string>
+    <string name="hr_appname_commute">Trajet</string>
+    <string name="hr_appname_stopwatch">Chronom猫tre</string>
+    <string name="hr_appname_workout">Entra卯nement</string>
+    <string name="hr_appname_wellness">Bien-锚tre</string>
+    <string name="pref_summary_custom_deviceicon">Afficher une ic么ne de notification Android sp茅cifique 脿 l\'appareil 脿 la place de l\'ic么ne Gadgetbridge lors de la connexion</string>
+    <string name="pref_title_custom_deviceicon">Afficher l鈥檌c么ne de notification sp茅cifique 脿 l鈥檃ppareil</string>
+    <string name="notification_channel_high_priority_name">Priorit茅 haute des notifications Gadgetbridge</string>
+    <string name="find_my_phone_notification">Trouver mon t茅l茅phone</string>
+    <string name="pref_summary_force_white_color_scheme">Utile si votre montre a les mains noires</string>
+    <string name="pref_title_force_white_color_scheme">Forcer le noir sur le blanc</string>
+    <string name="hr_widget_nothing">Rien</string>
+    <string name="hr_widget_weather">M茅t茅o</string>
+    <string name="hr_widget_active_minutes">Les minutes actives</string>
+    <string name="hr_widget_date">Date</string>
+    <string name="hr_widget_steps">脡tapes</string>
+    <string name="hr_widget_heart_rate">Fr茅quence cardiaque</string>
+    <string name="pref_title_sync_caldendar">Sync calendrier des 茅v茅nements</string>
+    <string name="pref_summary_sync_calendar">Permet les alertes de calendrier, m锚me en cas de d茅connexion</string>
+    <string name="pref_summary_allow_high_mtu">Augmente la vitesse de transfert, mais peut ne pas fonctionner avec quelques appareils Android.</string>
+    <string name="pref_title_allow_high_mtu">Autoriser une grande MTU</string>
+    <string name="error_no_location_access">L\'acc猫s 脿 la localisation doit 锚tre autoris茅 et activ茅 pour permettre 脿 la d茅tection de fonctionner correctement</string>
+    <string name="alarm_snooze">Reporter</string>
+    <string name="prefs_button_long_press_action_selection_title">Action d\'appui long de bouton</string>
+    <string name="prefs_button_variable_actions">Param猫tres d茅taill茅s des appuis de bouton</string>
+    <string name="prefs_button_triple_press_action_selection_title">Action de l\'茅v猫nement 3</string>
+    <string name="prefs_button_double_press_action_selection_title">Action de l\'茅v猫nement 2</string>
+    <string name="prefs_button_single_press_action_selection_title">Action de l\'茅v猫nement 1</string>
+    <string name="devicetype_y5">Y5</string>
+    <string name="devicetype_banglejs">Bangle.js</string>
+    <string name="pref_summary_disable_new_ble_scanning">Cochez cette option si votre appareil ne peut 锚tre d茅couvert</string>
+    <string name="pref_disable_new_ble_scanning">D茅sactiver la nouvelle d茅tection BLE</string>
+    <string name="qhybrid_offset_time_by">d茅cale l\'heure de</string>
+    <string name="qhybrid_changes_delay_prompt">le changement peut prendre quelques secondes鈥�</string>
+    <string name="qhybrid_offset_timezone">d茅cale le fuseau horaire de</string>
+    <string name="qhybrid_buttons_overwrite_error">Une erreur est survenue lors de la modification des boutons</string>
+    <string name="qhybrid_buttons_overwrite_success">Boutons modifi茅s</string>
+    <string name="qhybrid_prompt_million_steps">Veuillez r茅gler le compteur de pas 脿 un million pour l\'activer.</string>
+    <string name="qhybrid_use_activity_hand_as_notification_counter">utilise l\'activit茅 de la main comme compteur de notification</string>
+    <string name="qhybrid_overwrite_buttons">modifier les boutons</string>
+    <string name="qhybrid_second_timezone_offset_relative_to_utc">d茅calage du deuxi猫me fuseau horaire par rapport 脿 UTC</string>
+    <string name="qhybrid_time_shift">d茅calage horaire</string>
+    <string name="qhybrid_goal_in_steps">Objectif de pas</string>
+    <string name="qhybrid_vibration_strength">Puissance de vibration :</string>
+    <string name="watch_not_connected">Montre non connect茅e</string>
+    <string name="preferences_qhybrid_settings">Param猫tres hybrides Q</string>
+    <string name="pref_chart_sleep_rolling_24_off">De midi 脿 midi</string>
+    <string name="pref_chart_sleep_rolling_24_on">Derni猫res 24 heures</string>
+    <string name="pref_title_chart_sleep_rolling_24_hour">Plage de sommeil</string>
+    <string name="pref_title_chart_heartrate_color">Couleur du rythme cardiaque</string>
+    <string name="pref_chart_heartrate_color_orange">Orange</string>
+    <string name="pref_chart_heartrate_color_red">Rouge</string>
+    <string name="discovery_need_to_enter_authkey">Cet appareil a besoin d\'une cl茅 d\'auth. Pression longue sur celui-ci pour la saisir. Lisez le wiki.</string>
+    <string name="maximum_duration">Dur茅e</string>
+    <string name="prefs_find_phone_duration">Dur茅e de la sonnerie en secondes</string>
+    <string name="prefs_find_phone_summary">Utilisez votre bande pour lire l\'alarme de votre t茅l茅phone.</string>
+    <string name="prefs_enable_find_phone">Activer 芦 Trouver le t茅l. 禄</string>
+    <string name="prefs_find_phone">Trouver le t茅l茅phone</string>
+    <string name="preferences_makibes_hr3_settings">R茅glages Makibes HR3</string>
+    <string name="activity_error_no_app_for_gpx">Pour visualiser votre g茅olocalisation, installez une appli qui lit les fichiers GPX.</string>
+    <string name="pref_display_add_device_fab_off">Visible uniquement si aucun appareil n\'est ajout茅</string>
+    <string name="pref_display_add_device_fab_on">Toujours visible</string>
+    <string name="pref_display_add_device_fab">Bouton pour connecter un nouvel appareil</string>
+    <string name="widget_1_hour">1 heure</string>
+    <string name="widget_20_minutes">20 minutes</string>
+    <string name="widget_10_minutes">10 minutes</string>
+    <string name="widget_5_minutes">5 minutes</string>
+    <string name="widget_set_alarm_after">D茅finir l\'alarme apr猫s聽:</string>
+    <string name="widget_listing_label">脡tat et alarmes</string>
+    <string name="appwidget_sleep_alarm_widget_label">R茅veil</string>
+    <string name="activity_db_management_exportimport_label">Exporter et Importer</string>
+    <string name="activity_db_management_empty_DB">Vider la base de donn茅es</string>
+    <string name="activity_DB_empty_button">Vider la base de donn茅es</string>
+    <string name="activity_DB_delete_legacy_button">Supprimer l\'ancienne base de donn茅es</string>
+    <string name="activity_DB_test_export_message">Exportation de la base de donn茅es鈥�</string>
+    <string name="activity_DB_test_export_button">Lancer imm茅diatement l\'exportation automatique</string>
+    <string name="activity_DB_import_button">Importer la base de donn茅es</string>
+    <string name="activity_DB_ExportButton">Exporter la base de donn茅es</string>
+    <string name="activity_db_management_autoexport_label">Exportation automatique</string>
+    <string name="activity_db_management_autoexport_explanation">L\'export automatique des donn茅es est r茅gl茅 vers :</string>
+    <string name="pref_summary_use_custom_font">Activer ceci afin d鈥檃fficher les 茅mojis si votre appareil a un micrologiciel de police personnalis茅e</string>
+    <string name="pref_title_use_custom_font">Utiliser une police personnalis茅e</string>
+    <string name="pref_title_expose_hr">Acc猫s tiers fr茅q. cardiaque</string>
+    <string name="pref_summary_expose_hr">Permettre aux autres applis d\'acc茅der aux donn茅es de fr茅quence cardiaque en temps r茅el lorsque Gadgetbridge est connect茅</string>
+    <string name="menuitem_nfc">NFC</string>
+    <string name="weeksleepchart_sleep_a_month">Sommeil par mois</string>
+    <string name="weekstepschart_steps_a_month">Pas par mois</string>
+    <string name="pref_charts_range_off">Gamme des graphiques positionn茅e sur une semaine</string>
+    <string name="pref_charts_range_on">Gamme des graphiques positionn茅e sur un mois</string>
+    <string name="pref_title_charts_range">Gamme des graphiques</string>
+    <string name="pref_title_charts_average">Afficher les moyennes dans les graphiques</string>
+    <string name="pref_header_charts">Param猫tres graphiques</string>
+    <string name="average">Moyenne : %1$s</string>
+    <string name="prefs_hr_alarm_high">Limite haute</string>
+    <string name="prefs_hr_alarm_low">Limite basse</string>
+    <string name="prefs_hr_alarm_activity">Alarme de la fr茅quence cardiaque durant une activit茅 sportive</string>
+    <string name="portuguese">Portugais</string>
+    <string name="vietnamese">Vietnamien</string>
+    <string name="thai">Tha茂</string>
+    <string name="indonesian">Indon茅sien</string>
+    <string name="arabic">Arabe</string>
+    <string name="ukrainian">Ukrainien</string>
+    <string name="turkish">Turc</string>
+    <string name="dutch">N茅erlandais</string>
+    <string name="devicetype_bfh16">BFH-16</string>
+    <string name="pref_summary_authkey">Changez la cl茅 auteur 脿 une cl茅 g茅n茅rale sur tous vos appareils Android sur lesquels vous souhaitez vous connecter. La pr茅c茅dente cl茅 par d茅faut 茅tait 0123456789@ABCDE</string>
+    <string name="mi3_prefs_night_mode">Mode nuit</string>
+    <string name="minutes_30">30 minutes</string>
+    <string name="minutes_10">10 minutes</string>
+    <string name="minutes_5">5 minutes</string>
+    <string name="minutes_1">1 minute</string>
+    <string name="seconds_30">30 secondes</string>
+    <string name="seconds_20">20 secondes</string>
+    <string name="seconds_10">10 secondes</string>
+    <string name="seconds_5">5 secondes</string>
+    <string name="no_limit">Sans limite</string>
+    <string name="pref_title_notifications_timeout">Dur茅e minimum entre deux notifications</string>
+    <string name="norwegian_bokmal">Bokm氓l norv茅gien</string>
+    <string name="you_did_not_sleep">Vous n\'avez pas dormi</string>
+    <string name="you_slept">Vous avez dormi de %1$s 脿 %2$s</string>
+    <string name="japanese">Japonais</string>
+    <string name="korean">Kor茅en</string>
+    <string name="polish">Polonais</string>
+    <string name="french">Fran莽ais</string>
+    <string name="italian">Italien</string>
+    <string name="german">Allemand</string>
+    <string name="mi3_prefs_band_screen_unlock_summary">Glisser le doigt vers le haut pour d茅bloquer l\'茅cran du bracelet</string>
+    <string name="mi3_prefs_band_screen_unlock">D茅verrouillage de l\'茅cran du Band</string>
+    <string name="title_activity_watch9_calibration">Calibration de la Watch 9</string>
+    <string name="title_activity_watch9_pairing">Appairage de la Watch 9</string>
+    <string name="watch9_calibration_button">Calibrer</string>
+    <string name="watch9_calibration_hint">R茅gler l\'heure que votre appareil indique actuellement.</string>
+    <string name="watch9_pairing_tap_hint">Lorsque votre bracelet vibre, secouez-le ou pressez son bouton.</string>
+    <string name="controlcenter_calibrate_device">Calibrer l\'appareil</string>
+    <string name="menuitem_music">Musique</string>
+    <string name="menuitem_more">Suite</string>
+    <string name="menuitem_notifications">Notifications</string>
+    <string name="devicetype_id115">ID115</string>
+    <string name="vertical">Vertical</string>
+    <string name="horizontal">Horizontal</string>
+    <string name="prefs_screen_orientation">Orientation de l\'茅cran</string>
+    <string name="russian">Russe</string>
+    <string name="pref_auto_fetch_limit_fetches_summary">Synchronisation toutes les %d minutes</string>
+    <string name="pref_auto_fetch_limit_fetches">Temps minimum entre synchronisations</string>
+    <string name="pref_auto_fetch">R茅cup茅rer automatiquement les donn茅es d\'activit茅</string>
+    <string name="whitelist_all_for_notifications">Mettre toutes les notifications en liste blanche</string>
+    <string name="blacklist_all_for_notifications">Mettre toutes les notifications en liste noire</string>
+    <string name="devicetype_q8">Q8</string>
+    <string name="pref_summary_pebble_gatt_clientonly">C\'est uniquement pour les Pebble 2 et exp茅rimental, 脿 essayer si vous avez des probl猫mes de connectivit茅</string>
+    <string name="pref_title_pebble_gatt_clientonly">client GATT uniquement</string>
+    <string name="menuitem_settings">R茅glages</string>
+    <string name="menuitem_compass">Boussole</string>
+    <string name="menuitem_timer">Minuteur</string>
+    <string name="menuitem_alarm">Alarme</string>
+    <string name="menuitem_weather">M茅t茅o</string>
+    <string name="menuitem_activity">Activit茅</string>
+    <string name="menuitem_status">脡tat</string>
+    <string name="reset_index">R茅initialiser la date de r茅cup茅ration</string>
+    <string name="share">Partager</string>
+    <string name="select_all">Tout s茅lectionner</string>
+    <string name="pref_title_audio_player">Lecteur audio pr茅f茅r茅</string>
+    <string name="pref_title_general_autoreconnect">Reconnexion automatique</string>
+    <string name="activity_type_treadmill">Tapis de course</string>
+    <string name="activity_type_biking">V茅lo</string>
+    <string name="activity_summaries">Activit茅s</string>
+    <string name="activity_type_unknown">Activit茅 inconnue</string>
+    <string name="activity_type_swimming">Natation</string>
+    <string name="hr_widget_battery">Pile</string>
+    <string name="battery">Pile</string>
+    <string name="notif_battery_low_title">Niveau de la pile faible!</string>
+    <string name="pref_summary_pebble_enable_bgjs">Si activ茅, autorise l\'affichage de la m茅t茅o, niveau de la pile, etc.</string>
+    <string name="warning">Attention!</string>
+    <string name="watch9_time_seconds">Secondes聽:</string>
+    <string name="activity_type_walking">Marche</string>
+    <string name="activity_type_running">Course</string>
+    <string name="activity_type_not_worn">Dispositif non utilis茅</string>
+    <string name="activity_type_deep_sleep">Sommeil profond</string>
+    <string name="activity_type_light_sleep">Sommeil l茅ger</string>
+    <string name="activity_type_activity">Activit茅</string>
+    <string name="activity_type_not_measured">Non mesur茅e</string>
+    <string name="controlcenter_start_activity_tracks">Suivi de vos activit茅s</string>
+    <string name="on">Actif</string>
+    <string name="notification_channel_name">Notifications de Gadgetbridge</string>
+    <string name="spanish">Espagnol</string>
+    <string name="choose_auto_export_location">Choisissez l\'emplacement d\'exportation</string>
+    <string name="pref_summary_auto_export_interval">Exporter toutes les %d heures</string>
+    <string name="pref_title_auto_export_interval">Intervalle d\'exportation</string>
+    <string name="pref_title_auto_export_location">Emplacement de l\'exportation</string>
+    <string name="pref_title_auto_export_enabled">Exportation automatique activ茅e</string>
+    <string name="pref_header_auto_export">Exporter automatiquement</string>
+    <string name="pref_title_weather">M茅t茅o</string>
+    <string name="devicetype_no1_f1">No.1 F1</string>
+    <string name="devicetype_test">Appareil de test</string>
+    <string name="devicetype_unknown">Appareil inconnu</string>
+    <string name="kind_watchface">Cadran</string>
+    <string name="kind_resources">Ressources</string>
+    <string name="kind_gps_cep">Correction d\'erreurs GPS</string>
+    <string name="kind_gps_almanac">Almanach GPS</string>
+    <string name="kind_gps">Micrologiciel GPS</string>
+    <string name="kind_font">Police</string>
+    <string name="kind_invalid">Donn茅es non valides</string>
+    <string name="kind_firmware">Micrologiciel</string>
+    <string name="pref_title_weather_location">Emplacement m茅t茅o (CM/LOS)</string>
+    <string name="interval_one_hour">Une fois par heure</string>
+    <string name="interval_thirty_minutes">Toutes les 30 minutes</string>
+    <string name="interval_ten_minutes">Toutes les 10 minutes</string>
+    <string name="interval_five_minutes">Toutes les 5 minutes</string>
+    <string name="interval_one_minute">Une fois par minute</string>
+    <string name="prefs_title_heartrate_measurement_interval">Mesure du pouls toute la journ茅e</string>
+    <string name="english">Anglais</string>
+    <string name="traditional_chinese">Chinois traditionnel</string>
+    <string name="simplified_chinese">Chinois simplifi茅</string>
+    <string name="automatic">Automatique</string>
+    <string name="pref_title_charts_swipe">Permettre le balayage gauche/droite dans les graphiques d\'activit茅</string>
+    <string name="controlcenter_connect">Connexion鈥�</string>
+    <string name="activity_web_view">Activit茅 Web View</string>
+    <string name="pref_title_pebble_enable_bgjs">Activer t芒che de fond JS</string>
+    <string name="_pebble_watch_reply">R茅pondre</string>
+    <string name="_pebble_watch_mute">Silencieux</string>
+    <string name="_pebble_watch_open_on_phone">Ouvrir sur le smartphone Android</string>
+    <string name="mi2_prefs_button_press_count_max_delay_summary">D茅lai maximum entre pressions en millisecondes</string>
+    <string name="mi2_prefs_button_press_count_max_delay">D茅lai maximum entre pressions</string>
+    <string name="mi2_prefs_button_action_vibrate_summary">Activer la vibration apr猫s d茅clenchement de l\'action</string>
+    <string name="mi2_prefs_button_action_vibrate">Activer la vibration du bracelet</string>
+    <string name="mi2_prefs_button_action_summary">Activer action apr猫s nombre sp茅cifi茅 de pressions</string>
+    <string name="mi2_prefs_button_action">Activer action du bouton</string>
+    <string name="mi2_prefs_button_press_broadcast_summary">Message de diffusion envoy茅 avec l\'茅v猫nement. Le param猫tre 芦聽button_id聽禄 est automatiquement ajout茅 脿 chaque message.</string>
+    <string name="mi2_prefs_button_press_broadcast">Message 脿 envoyer</string>
+    <string name="mi2_prefs_button_press_count_summary">Nombre d\'appuis sur le boutton pour envoyer l鈥櫭塿猫nement 1. Appuyer de nouveau autant de fois cr茅era l\'脡v猫nement 2, etc.</string>
+    <string name="mi2_prefs_button_press_count">Nombre de pressions du bouton</string>
+    <string name="mi2_prefs_button_actions_summary">Sp茅cifier les actions par pression du bouton</string>
+    <string name="mi2_prefs_button_actions">Actions du bouton</string>
+    <string name="discovery_dont_pair">Ne pas appairer</string>
+    <string name="discovery_yes_pair">Appairer</string>
+    <string name="discovery_pair_question">S茅lectionnez Jumeler pour associer vos dispositifs. Si cela 茅choue, essayez 脿 nouveau sans jumelage.</string>
+    <string name="discovery_successfully_bonded">Li茅 脿 %1$s.</string>
+    <string name="discovery_enable_bluetooth">Activez le Bluetooth pour trouver des dispositifs.</string>
+    <string name="discovery_trying_to_connect_to">Tentative de connexion 脿 : %1$s</string>
+    <string name="discovery_bonding_failed_immediately">Le lien avec %1$s a 茅chou茅 instantan茅ment.</string>
+    <string name="discovery_attempting_to_pair">Tentative de jumelage avec %1$s</string>
+    <string name="mi2_dnd_scheduled">Programm茅 (intervalle de temps)</string>
+    <string name="mi2_dnd_automatic">Automatique (d茅tection de sommeil)</string>
+    <string name="mi2_dnd_off">脡teint</string>
+    <string name="off">脡teint</string>
+    <string name="mi2_enable_text_notifications_summary">Requis : micrologiciel version 1.0.1.28 ou plus, fichier Mili_pro.ft* install茅.</string>
+    <string name="mi2_enable_text_notifications">Notifications textuelles</string>
+    <string name="miband2_prefs_timeformat">Mi2 : format de l\'heure</string>
+    <string name="StringUtils_sender">(%1$s)</string>
+    <string name="pref_screen_notification_profile_alarm_clock">R茅veil</string>
+    <string name="timeformat_am_pm">AM/PM</string>
+    <string name="timeformat_24h">24H</string>
+    <string name="unit_imperial">Imp茅riale</string>
+    <string name="unit_metric">M茅trique</string>
+    <string name="pref_summary_setup_bt_pairing">D茅sactivez ceci si vous avez des probl猫mes de connexion</string>
+    <string name="pref_title_setup_bt_pairing">Activer le jumelage Bluetooth</string>
+    <string name="weather_notification_label">Assurez vous que ce th猫me soit activ茅 dans l\'application de notification de la m茅t茅o pour recevoir les informations sur votre Pebble.
+\n
+\nAucune configuration n\'est requise.
+\n
+\nVous pouvez activer l\'application m茅t茅o syst猫me de votre Pebble depuis la configuration de l\'application.
+\n
+\nLes watchfaces support茅es afficheront la m茅t茅o automatiquement.</string>
+    <string name="pebble_pairing_hint">Une fen锚tre de jumelage va s鈥檃fficher sur votre t茅l茅phone. Si cela ne se produit pas, regardez dans vos notifications et acceptez-la. Acceptez ensuite la demande de jumelage sur votre Pebble.</string>
+    <string name="title_activity_pebble_pairing">Jumelage avec une Pebble</string>
+    <string name="Delete">Supprimer</string>
+    <string name="Cancel">Annuler</string>
+    <string name="dbmanagementactivity_overwrite">脡craser</string>
+    <string name="dbmanagementactivity_old_activity_db_deletion_failed">脡chec de la destruction de l\'ancienne base de donn茅es.</string>
+    <string name="dbmanagementactivity_old_activity_db_successfully_deleted">Les anciennes donn茅es d\'activit茅 ont 茅t茅 effac茅es.</string>
+    <string name="dbmanagementactivity_db_deletion_failed">脡chec de la destruction de la base de donn茅es.</string>
+    <string name="dbmanagementactivity_database_successfully_deleted">Les donn茅es ont 茅t茅 effac茅es.</string>
+    <string name="dbmanagementactivity_error_importing_shared">Erreur d\'importation des pr茅f茅rences : %1$s</string>
+    <string name="dbmanagementactivity_error_importing_db">Erreur lors de l\'importation de la base de donn茅es : %1$s</string>
+    <string name="dbmanagementactivity_import_successful">Import茅.</string>
+    <string name="dbmanagementactivity_error_exporting_shared">Erreur d\'exportation des pr茅f茅rences : %1$s</string>
+    <string name="dbmanagementactivity_error_exporting_db">Erreur d\'exportation de la base de donn茅es : %1$s</string>
+    <string name="dbmanagementactivity_exported_to">Exporter vers : %1$s</string>
+    <string name="pref_title_pebble_health_store_raw">Stockez les enregistrements brut dans la base de donn茅es</string>
+    <string name="live_activity_heart_rate">Fr茅quence cardiaque</string>
+    <string name="charts_legend_heartrate">Fr茅quence cardiaque</string>
+    <string name="updatefirmwareoperation_firmware_not_sent">脡chec lors de l\'茅criture du micrologiciel</string>
+    <string name="updatefirmwareoperation_update_in_progress">Installation du micrologiciel</string>
+    <string name="DEVINFO_HR_VER">"Fr茅quence cardiaque聽: "</string>
+    <string name="error_creating_directory_for_logfiles">Erreur 脿 la cr茅ation de votre fichier log : %1$s</string>
+    <string name="device_fw">Version du micrologiciel : %1$s</string>
+    <string name="device_hw">R茅vision mat茅rielle : %1$s</string>
+    <string name="activity_prefs_sleep_duration">Temps de sommeil pr茅f茅r茅 en heures</string>
+    <string name="add_widget">Ajouter un widget</string>
+    <string name="authentication_required">Authentification requise</string>
+    <string name="authenticating">Authentification</string>
+    <string name="activity_prefs_weight_kg">Poids en kg</string>
+    <string name="activity_prefs_height_cm">Taille en cm</string>
+    <string name="activity_prefs_gender">Sexe</string>
+    <string name="activity_prefs_year_birth">Ann茅e de naissance</string>
+    <string name="activity_prefs_about_you">脌 propos de vous</string>
+    <string name="waiting_for_reconnect">En attente de reconnexion</string>
+    <string name="FetchActivityOperation_about_to_transfer_since">Sur le point de transf茅rer des donn茅es depuis %1$s</string>
+    <string name="mi2_prefs_do_not_disturb_end">Heure de fin</string>
+    <string name="mi2_prefs_do_not_disturb_start">Heure de d茅but</string>
+    <string name="mi2_prefs_inactivity_warnings_dnd_summary">D茅sactiver les alertes d\'inactivit茅 pendant un intervalle de temps</string>
+    <string name="mi2_prefs_inactivity_warnings_threshold">Seuil d\'inactivit茅 (en minutes)</string>
+    <string name="mi2_prefs_inactivity_warnings_summary">Le bracelet vibrera si vous n\'锚tes pas actif pendant un moment</string>
+    <string name="mi2_prefs_inactivity_warnings">Alertes d\'inactivit茅</string>
+    <string name="mi2_prefs_do_not_disturb_summary">Le bracelet ne recevra pas de notifications si activ茅</string>
+    <string name="mi2_prefs_do_not_disturb">Ne Pas D茅ranger</string>
+    <string name="mi2_prefs_rotate_wrist_to_switch_info">Tournez votre poignet pour changer d\'茅l茅ment</string>
+    <string name="mi2_prefs_activate_display_on_lift">Allumer l\'茅cran lors d\'un mouvement</string>
+    <string name="mi2_prefs_display_items_summary">Choisissez les 茅l茅ments 脿 afficher sur le bracelet</string>
+    <string name="mi2_prefs_display_items">脡l茅ments 脿 afficher</string>
+    <string name="mi2_prefs_goal_notification_summary">Le bracelet vibrera lorsque l\'objectif de pas quotidien sera atteint</string>
+    <string name="mi2_prefs_goal_notification">Notification d\'objectif</string>
+    <string name="dateformat_date_time">Date et heure</string>
+    <string name="dateformat_time">Heure</string>
+    <string name="miband2_prefs_dateformat">Format de la date</string>
+    <string name="miband_prefs_device_time_offset_hours">La compensation de temps en heure (pour travailleurs en rotation, par exemple)</string>
+    <string name="miband_prefs_hr_sleep_detection">Utiliser le capteur cardiaque pour am茅liorer la d茅tection du sommeil</string>
+    <string name="miband_prefs_reserve_alarm_calendar">Alarmes 脿 r茅server pour 茅v茅nements futurs</string>
+    <string name="fwinstaller_firmware_not_compatible_to_device">Ce micrologiciel n\'est pas compatible avec l\'appareil</string>
+    <string name="miband_fwinstaller_incompatible_version">Micrologiciel non compatible</string>
+    <string name="pref_title_keep_data_on_device">Conserver les activit茅s sur l\'appareil</string>
+    <string name="user_feedback_all_alarms_disabled">Toutes alarmes d茅sactiv茅es</string>
+    <string name="device_not_connected">Non connect茅.</string>
+    <string name="abstract_chart_fragment_kind_not_worn">Non port茅</string>
+    <string name="abstract_chart_fragment_kind_deep_sleep">Sommeil profond</string>
+    <string name="abstract_chart_fragment_kind_light_sleep">Sommeil l茅ger</string>
+    <string name="abstract_chart_fragment_kind_activity">Activit茅</string>
+    <string name="live_activity_start_your_activity">D茅marrez votre activit茅</string>
+    <string name="live_activity_steps_per_minute_history">Historique de pas/minute</string>
+    <string name="live_activity_total_steps">Nombre total de pas</string>
+    <string name="live_activity_current_steps_per_minute">Pas/minute actuel</string>
+    <string name="live_activity_steps_history">Historique de pas</string>
+    <string name="pref_summary_low_latency_fw_update">Cela peut aider sur les appareils o霉 les installations de micrologiciel 茅chouent.</string>
+    <string name="pref_title_low_latency_fw_update">Utilisez le mode basse latence pour installer un micrologiciel</string>
+    <string name="pref_summary_keep_data_on_device">Les donn茅es d\'activit茅s seront conserv茅es sur le Mi Band apr猫s la synchronisation. Utile si GB est utilis茅 avec d\'autres applications.</string>
+    <string name="pref_summary_dont_ack_transfers">Les donn茅es d\'activit茅s ne seront pas effac茅es du bracelet si elles ne sont pas confirm茅es. Utile si GB est utilis茅 avec d\'autres applications.</string>
+    <string name="pref_title_dont_ack_transfer">Ne pas confirmer le transfert de donn茅es d\'activit茅s</string>
+    <string name="weeksteps_today_steps_description">Nombre de pas aujourd\'hui, objectif : %1$s</string>
+    <string name="liveactivity_live_activity">Activit茅 en direct</string>
+    <string name="heart_rate">Fr茅quence cardiaque</string>
+    <string name="clock">Horloge</string>
+    <string name="distance">Distance</string>
+    <string name="chart_steps">Pas</string>
+    <string name="updatefirmwareoperation_write_failed">脡chec lors de l\'installation du micrologiciel</string>
+    <string name="updatefirmwareoperation_update_complete_rebooting">Installation compl猫te du micrologiciel, red茅marrage de l\'appareil鈥�</string>
+    <string name="updatefirmwareoperation_update_complete">Installation compl猫te du micrologiciel</string>
+    <string name="updatefirmwareoperation_metadata_updateproblem">Probl猫me avec le transfert de m茅tadonn茅es du micrologiciel</string>
+    <string name="pbwinstallhandler_correct_hw_revision">Version du mat茅riel correcte</string>
+    <string name="pbw_installhandler_pebble_firmware">Micrologiciel Pebble %1$s</string>
+    <string name="fwappinstaller_connection_state">Connexion 脿 l\'appareil : %1$s</string>
+    <string name="pref_title_authkey">Cl茅 Auteur</string>
+    <string name="title_activity_device_specific_settings">Param猫tres sp茅cifiques de l\'appareil</string>
+    <string name="pref_title_support_voip_calls">Activer les applications d\'appel VoIP</string>
+    <string name="activity_prefs_activetime_minutes">Objectif quotidien : temps d\'activit茅 en minutes</string>
+    <string name="activity_prefs_distance_meters">Objectif quotidien : distance en m猫tres</string>
+    <string name="activity_prefs_calories_burnt">Objectif quotidien聽: calories brul茅es</string>
+    <string name="interval_forty_five_minutes">Toutes les 45 min</string>
+    <string name="interval_fifteen_minutes">Toutes les 15 min</string>
+    <string name="pref_screen_notification_profile_anti_loss">Alerte Anti-Perte</string>
+    <string name="pref_screen_notification_profile_low_power">Alerte batterie faible</string>
+    <string name="pref_screen_notification_profile_inactivity">Notification d\'inactivit茅</string>
+    <string name="pref_screen_notification_profile_calendar">Notification du calendrier</string>
+    <string name="pref_screen_notification_profile_missed_call">Notification appel manqu茅</string>
+    <string name="zetime_signaling_vibrate_beep_once">Vibration et bip uniques</string>
+    <string name="zetime_signaling_beep_twice">Bip double</string>
+    <string name="zetime_signaling_beep_once">Bip unique</string>
+    <string name="zetime_signaling_vibrate_twice">Vibration double</string>
+    <string name="zetime_signaling_vibrate_once">Vibration unique</string>
+    <string name="zetime_signaling_vibrate_beep">Vibration et bip continus</string>
+    <string name="zetime_signaling_beep">Bip continu</string>
+    <string name="zetime_signaling_vibrate">Vibration continue</string>
+    <string name="zetime_signaling_none">Silencieux</string>
+    <string name="zetime_title_alarm_signaling">Type de signal pour l\'alarme</string>
+    <string name="zetime_prefs_inactivity_su">Dimanche</string>
+    <string name="zetime_prefs_inactivity_sa">Samedi</string>
+    <string name="zetime_prefs_inactivity_fr">Vendredi</string>
+    <string name="zetime_prefs_inactivity_th">Jeudi</string>
+    <string name="zetime_prefs_inactivity_we">Mercredi</string>
+    <string name="zetime_prefs_inactivity_tu">Mardi</string>
+    <string name="zetime_prefs_inactivity_mo">Lundi</string>
+    <string name="zetime_prefs_inactivity_repetitions">R茅p茅titions</string>
+    <string name="zetime_date_format_3">mm/jj/aaaa</string>
+    <string name="zetime_date_format_2">jj/mm/aaaa</string>
+    <string name="zetime_date_format_1">aaaa/mm/jj</string>
+    <string name="zetime_date_format">Format de la date</string>
+    <string name="zetime_calories_type_all">Calories br没l茅es activement et au repos</string>
+    <string name="zetime_calories_type_active">Seulement les calories br没l茅es activement</string>
+    <string name="zetime_calories_type">Type de calories</string>
+    <string name="zetime_handmove_display_summary">Tourner le poignet pour activer ou d茅sactiver l\'茅cran.</string>
+    <string name="zetime_handmove_display">Mouvement de la main</string>
+    <string name="zetime_activity_tracking">Suivi des Activit茅s</string>
+    <string name="zetime_analog_mode_handsandsteps">Aiguilles et Pas</string>
+    <string name="zetime_analog_mode_hands">Aiguilles seulement</string>
+    <string name="zetime_analog_mode">Mode analogique</string>
+    <string name="activity_prefs_alarm_min_heart_rate">Fr茅quence cardiaque minimale</string>
+    <string name="activity_prefs_alarm_max_heart_rate">Fr茅quence cardiaque maximale</string>
+    <string name="zetime_heart_rate_alarm_enable">Activer l\'alarme de fr茅quence cardiaque</string>
+    <string name="zetime_title_heart_rate_alarm_summary">La montre vous alertera quand votre fr茅quence cardiaque d茅passe les limites.</string>
+    <string name="zetime_title_heart_rate_alarm">Alarme Fr茅quence Cardiaque</string>
+    <string name="zetime_title_screentime">Dur茅e d\'茅cran allum茅 en secondes</string>
+    <string name="zetime_title_heartrate">Param猫tres Fr茅quence Cardiaque</string>
+    <string name="zetime_title_settings">Param猫tres ZeTime</string>
+    <string name="prefs_disconnect_notification">Notification de d茅connexion</string>
+    <string name="activity_type_exercise">Activit茅 physique</string>
+    <string name="appwidget_not_connected">Non connect茅, l\'alarme n\'est pas d茅finie.</string>
+    <string name="save_configuration">Sauvegarder la configuration</string>
+    <string name="mode_configuration">Mode Configuration</string>
+    <string name="filter_mode">Mode Filtre</string>
+    <string name="toast_notification_filter_words_empty_hint">Veuillez entrer au moins un mot</string>
+    <string name="filter_submode_all">Tous les mots</string>
+    <string name="filter_submode_at_least_one">Au moins un des mots</string>
+    <string name="filter_mode_blacklist">Bloquer lorsque contient les mots</string>
+    <string name="filter_mode_whitelist">Montrer lorsque contient les mots</string>
+    <string name="filter_mode_none">Ne pas filtrer</string>
+    <string name="toast_notification_filter_saved_successfully">Filtre des notifications sauvegard茅</string>
+    <string name="edittext_notification_filter_words_hint">Saisissez les mots d茅sir茅s, chacun sur une ligne</string>
+    <string name="title_activity_notification_filter">Filtre de notifications</string>
+    <string name="appwidget_setting_alarm">Une alarme a 茅t茅 enregistr茅 pour %1$02d:%2$02d</string>
+    <string name="debugactivity_really_factoryreset">Une r茅initialisation effacera toutes les donn茅es de l\'appareil connect茅 (si support茅). Les appareils Xiaomi/Huami modifient 茅galement l\'adresse MAC Bluetooth, de sorte qu\'ils apparaissent comme de nouveaux appareils dans Gadgetbridge.</string>
+    <string name="no_data">Pas de donn茅es</string>
+    <string name="language_and_region_prefs">Param猫tres de langue et de r茅gion</string>
+    <string name="preferences_rtl_settings">Support Droite-脿-Gauche</string>
+    <string name="pref_summary_contextual_arabic">A cocher pour activer le support \"Arabe contextuel\"</string>
+    <string name="pref_title_contextual_arabic">Arabe contextuel</string>
+    <string name="pref_rtl_max_line_length_summary">Allonge ou raccourcis les lignes dans les textes 茅crits de droite 脿 gauche</string>
+    <string name="pref_rtl_max_line_length">Longueur max. d\'une ligne en mode Droite-脿-Gauche</string>
+    <string name="pref_summary_rtl">Cocher ceci si votre appareil n\'est pas compatible avec les langues \"droite-脿-gauche\"</string>
+    <string name="pref_title_rtl">De droite 脿 gauche</string>
+    <string name="activity_prefs_chart_min_heart_rate">Fr茅quence cardiaque minimum</string>
+    <string name="activity_prefs_chart_max_heart_rate">Fr茅quence cardiaque maximum</string>
+    <string name="activity_prefs_charts">Param猫tres des graphiques</string>
+    <string name="pref_invalid_frequency_message">Veuillez introduire une fr茅quence entre 87.5 et 108.0</string>
+    <string name="pref_invalid_frequency_title">Fr茅quence non-valide</string>
+    <string name="preferences_fm_frequency">Fr茅quence FM</string>
+    <string name="preferences_led_color">Couleur de la DEL</string>
+    <string name="controlcenter_change_fm_frequency">Changer la fr茅quence FM</string>
+    <string name="controlcenter_change_led_color">Changer la couleur de la DEL</string>
+    <string name="share_log_warning">Veuillez garder 脿 l\'esprit que les fichiers logs de Gadgetbridge peuvent contenir des tas d\'informations personnelles, incluant entre autre des donn茅es relatives 脿 la sant茅, des identifiants uniques (telles que des adresses MAC), des pr茅f茅rences musicales, etc. Pensez 脿 modifier le fichier et retirer ces informations personnelles avant toute publication sur un rapport de bug public.</string>
+    <string name="share_log">Partager les logs</string>
+    <string name="ok">Ok</string>
+    <string name="mi3_night_mode_sunset">Au coucher du soleil</string>
+    <string name="mi3_prefs_night_mode_summary">Diminuer la luminosit茅 de l\'茅cran du bracelet automatiquement lorsqu\'il fait sombre</string>
+    <string name="miband_fwinstaller_compatible_version">Version compatible</string>
+    <string name="fwapp_install_device_not_ready">Le fichier ne peut pas 锚tre install茅, l\'appareil n\'est pas pr锚t.</string>
+    <string name="updating_firmware">Installation du micrologiciel鈥�</string>
+    <string name="activity_sleepchart_activity_and_sleep">Activit茅</string>
+    <string name="weekstepschart_steps_a_week">Pas de la semaine</string>
+    <string name="weeksleepchart_today_sleep_description">Sommeil aujourd\'hui, objectif : %1$s</string>
+    <string name="weeksleepchart_sleep_a_week">Sommeil de la semaine</string>
+    <string name="sleepchart_your_sleep">Sommeil</string>
+    <string name="notif_battery_low_bigtext_number_of_charges">Nombre de charges : %s</string>
+    <string name="notif_battery_low_bigtext_last_charge_time">Derni猫re charge : %s 
+\n</string>
+    <string name="installer_activity_wait_while_determining_status">Veuillez patienter pendant la d茅termination de l\'茅tat de l\'installation鈥�</string>
+    <string name="pbw_install_handler_hw_revision_mismatch">Impossible d\'installer le micrologiciel sp茅cifi茅 : il ne correspond pas 脿 la version du mat茅riel de votre Pebble.</string>
+    <string name="pbw_install_handler_unable_to_install">Impossible d\'installer le ficher suivant : %1$s</string>
+    <string name="installer_activity_unable_to_find_handler">Impossible de trouver un gestionnaire pour installer ce fichier.</string>
+    <string name="cannot_connect">Impossible de se connecter : %1$s</string>
+    <string name="controlcenter_start_activitymonitor">Votre activit茅 (ALPHA)</string>
+    <string name="dbaccess_error_executing">Erreur lors de l鈥檈x茅cution de %1$s\'</string>
+    <string name="miband_prefs_fitness_goal">Objectif de pas par jour</string>
+    <string name="user_feedback_miband_activity_data_transfer">Sur le point de transf茅rer %1$s de donn茅es 脿 partir de %2$s</string>
+    <string name="user_feedback_miband_set_alarms_ok">Alarmes envoy茅es 脿 l\'appareil.</string>
+    <string name="user_feedback_miband_set_alarms_failed">Une erreur s\'est produite lors du param茅trage des alarmes, veuillez r茅essayer.</string>
+    <string name="alarm_smart_wakeup">R茅veil intelligent</string>
+    <string name="alarm_sat_short">Sam</string>
+    <string name="alarm_fri_short">Ven</string>
+    <string name="alarm_thu_short">Jeu</string>
+    <string name="alarm_wed_short">Mer</string>
+    <string name="alarm_tue_short">Mar</string>
+    <string name="alarm_mon_short">Lun</string>
+    <string name="alarm_sun_short">Dim</string>
+    <string name="title_activity_alarm_details">D茅tails des alarmes</string>
+    <string name="controlcenter_start_configure_alarms">Configurer les alarmes</string>
+    <string name="title_activity_set_alarm">Configurer les alarmes</string>
+    <string name="title_activity_charts">Activit茅 et Sommeil</string>
+    <string name="control_center_cancel_to_stop_vibration">Annuler pour arr锚ter les vibrations.</string>
+    <string name="control_center_find_lost_device">Trouver l\'appareil perdu</string>
+    <string name="stats_y_axis_label">Pas par minute</string>
+    <string name="stats_x_axis_label">Total de minutes</string>
+    <string name="stats_title">Zones de vitesse</string>
+    <string name="pref_screen_notification_profile_generic_social">R茅seau social</string>
+    <string name="pref_screen_notification_profile_generic_navigation">Navigation</string>
+    <string name="pref_screen_notification_profile_generic_chat">Tchat</string>
+    <string name="pref_screen_notification_profile_incoming_call">Notification d\'appels entrants</string>
+    <string name="pref_screen_notification_profile_email">Notification par courriel</string>
+    <string name="pref_screen_notification_profile_generic">Notification g茅n茅rique</string>
+    <string name="pref_header_vibration_settings">Param猫tres des vibrations</string>
+    <string name="pref_screen_notification_profile_sms">Notification Texto</string>
+    <string name="vibration_try">Essayer</string>
+    <string name="vibration_profile_alarm_clock">R茅veil</string>
+    <string name="vibration_profile_ring">Sonnette</string>
+    <string name="vibration_profile_waterdrop">Goute d\'eau</string>
+    <string name="vibration_profile_medium">Moyen</string>
+    <string name="vibration_profile_short">Court</string>
+    <string name="vibration_profile_staccato">Saccad茅</string>
+    <string name="pref_screen_vibration_profile">Profil de vibration</string>
+    <string name="sleep_activity_date_range">De %1$s 脿 %2$s</string>
+    <string name="busy_task_fetch_activity_data">R茅cup茅ration des donn茅es d\'activit茅</string>
+    <string name="initializing">Initialisation</string>
+    <string name="pref_write_logfiles">脡crire des fichiers journaux</string>
+    <string name="title_activity_sleepmonitor">Moniteur de sommeil</string>
+    <string name="pref_header_vibration_count">Nombre de vibrations</string>
+    <string name="miband_prefs_alias">Nom/Pseudo</string>
+    <string name="candidate_item_device_image">Image de l\'appareil</string>
+    <string name="discovery_note">Note :</string>
+    <string name="discovery_connected_devices_hint">Mettez votre appareil en mode visible. Les appareils d茅j脿 connect茅s ne seront pas visibles. Sur Android 6 ou sup茅rieur, vous devez activer la localisation (ex. GPS). D茅sactivez Privacy Guard pour Gadgetbridge, il pourrait stopper et red茅marrer votre t茅l茅phone. Si votre appareil n\'est pas visible apr猫s 2 minutes, r茅essayez apr猫s avoir red茅marr茅 votre t茅l茅phone.</string>
+    <string name="appinstaller_install">Installer</string>
+    <string name="miband_pairing_tap_hint">Quand votre Mi Band vibre et clignote, appuyez dessus plusieurs fois d\'affil茅e.</string>
+    <string name="miband_pairing_using_dummy_userdata">Aucune donn茅e utilisateur valide fournie, utilisation de donn茅es fictives pour le moment.</string>
+    <string name="right">Droite</string>
+    <string name="left">Gauche</string>
+    <string name="other">Autre</string>
+    <string name="female">Femme</string>
+    <string name="male">Homme</string>
+    <string name="preferences_miband_settings">Param猫tres Mi Band / Amazfit</string>
+    <string name="preferences_category_device_specific_settings">Param猫tres sp茅cifiques 脿 l\'appareil</string>
+    <string name="message_cannot_pair_no_mac">Aucune adresse MAC fournie, ne peut 锚tre appair茅.</string>
+    <string name="pairing_already_bonded">D茅j脿 li茅 avec %1$s (%2$s), connexion鈥�</string>
+    <string name="pairing_in_progress">Cr茅ation du lien en cours : %1$s (%2$s)</string>
+    <string name="pairing_unable_to_pair_with">Impossible se s鈥檃ppairer avec %1$s (%2$s)</string>
+    <string name="pairing_creating_bond_with">Cr茅ation d鈥檜n lien avec %1$s (%2$s)</string>
+    <string name="pairing">Jumelage avec %s鈥�</string>
+    <string name="title_activity_mi_band_pairing">Appairer votre Mi Band</string>
+    <string name="android_pairing_hint">Utiliser l\'appairage Bluetooth d\'Android pour jumeler l\'appareil.</string>
+    <string name="title_activity_android_pairing">Appairer l\'appareil</string>
+    <string name="action_discover">Connecter un nouvel appareil</string>
+    <string name="discovery_start_scanning">D茅marrer le scan</string>
+    <string name="discovery_stop_scanning">Arr锚ter le scan</string>
+    <string name="title_activity_discovery">Scanner les appareils</string>
+    <string name="initialized">Initialis茅</string>
+    <string name="n_a">N.D.</string>
+    <string name="app_install_info">Vous 锚tes sur le point d\'installer l\'application suivante :
+\n
+\n
+\n%1$s Version %2$s par %3$s
+\n</string>
+    <string name="firmware_install_warning">VOUS TENTEZ D\'INSTALLER UN MICROLOGICIEL, PROC脡DEZ 脌 VOS RISQUES ET P脡RILS.
+\n
+\n
+\nCe micrologiciel est pour la version de mat茅riel : %s</string>
+    <string name="installation_successful">Install茅</string>
+    <string name="installation_failed_">脡chec de l\'installation</string>
+    <string name="installing_binary_d_d">Installation du binaire %1$d/%2$d</string>
+    <string name="gadgetbridge_running_generic">Gadgetbridge est en fonctionnement</string>
+    <string name="tap_a_device_to_connect">Tapez sur le p茅riph茅rique pour le connecter</string>
+    <string name="tap_connected_device_for_vibration">Cliquez sur connecter pour envoyer une vibration</string>
+    <string name="tap_connected_device_for_activity">Cliquez sur l\'appareil pour ouvrir le gestionnaire d鈥檃ctivit茅</string>
+    <string name="tap_connected_device_for_app_mananger">Cliquez sur l\'appareil pour ouvrir le gestionnaire d\'application</string>
+    <string name="bluetooth_is_disabled_">Le Bluetooth est d茅sactiv茅.</string>
+    <string name="bluetooth_is_not_supported_">Le Bluetooth n\'est pas support茅.</string>
+    <string name="this_is_a_test_notification_from_gadgetbridge">Ceci est un test de notification venant de Gadgetbridge</string>
+    <string name="test_notification">Notification de test</string>
+    <string name="test">Test</string>
+    <string name="_unknown_">(inconnu)</string>
+    <string name="unknown_state">脡tat inconnu</string>
+    <string name="connected">Connect茅</string>
+    <string name="connecting">Connexion en cours</string>
+    <string name="not_connected">Non connect茅</string>
+    <string name="preferences_hplus_settings">Param猫tres HPlus/Makibes</string>
+    <string name="prefs_title_all_day_heart_rate">Mesure de la fr茅quence cardiaque toute la journ茅e</string>
+    <string name="pref_title_screentime">Dur茅e d\'茅cran allum茅</string>
+    <string name="pref_title_timeformat">Format de l\'heure</string>
+    <string name="pref_title_unit_system">Unit茅s</string>
+    <string name="pref_title_pebble_reconnect_attempts">Tentatives de reconnexion</string>
+    <string name="pref_summary_pebble_always_ack_pebblekit">Ceci permettra aux messages envoy茅s 脿 des apps tierces d\'锚tre toujours reconnus imm茅diatement</string>
+    <string name="pref_title_pebble_always_ack_pebblekit">ACK 脿 l\'avance du PebbleKit</string>
+    <string name="pref_summary_pebble_enable_applogs">Ceci permettra 脿 Gadgetbridge de conserver les logs des Watch App (requiert une reconnexion)</string>
+    <string name="pref_title_pebble_enable_applogs">Activer les logs des apps du bracelet</string>
+    <string name="pref_summary_pebble_mtu_limit">Si votre Pebble 2/LE ne fonctionne pas correctement, essayez d\'activer cette option pour limiter le MTU (plage valide 20鈥�512)</string>
+    <string name="pref_title_pebble_mtu_limit">Limite du GATT MTU de Pebble 2/LE</string>
+    <string name="pref_summary_pebble_forcele">Utiliser le support exp茅rimental du LE pour toutes les Pebble au lieu du Bluetooth classique. Cela requiert le jumelage d\'une Pebble non LE, puis d\'une Pebble LE</string>
+    <string name="pref_title_pebble_forcele">Toujours pr茅f茅rer le BLE</string>
+    <string name="pref_title_pebble_forceuntested">Activer les fonctionnalit茅s non-test茅es</string>
+    <string name="pref_title_pebble_forceprotocol">Forcer le protocole de notification</string>
+    <string name="toast_aqurired_networklocation">Emplacement obtenu</string>
+    <string name="toast_enable_networklocationprovider">Veuillez activer la localisation r茅seau</string>
+    <string name="pref_title_location_keep_uptodate">Garder l鈥檈mplacement 脿 jour</string>
+    <string name="pref_title_location_longitude">Longitude</string>
+    <string name="pref_title_location_latitude">Latitude</string>
+    <string name="pref_title_location_aquire">Obtenir l\'emplacement</string>
+    <string name="pref_header_location">Emplacement</string>
+    <string name="pref_pebble_privacy_mode_complete">Ne montre que l\'ic么ne de notification</string>
+    <string name="pref_pebble_privacy_mode_content">D茅place les notifications hors de l\'茅cran</string>
+    <string name="pref_pebble_privacy_mode_off">Notifications normales</string>
+    <string name="pref_title_pebble_privacy_mode">Mode priv茅</string>
+    <string name="pref_summary_autoremove_notifications">Les notifications sont automatiquement effac茅es de la Pebble lorsqu\'elles sont rejet茅es sur l\'appareil Android</string>
+    <string name="pref_title_autoremove_notifications">Effacer automatiquement les notifications rejet茅es</string>
+    <string name="pref_summary_enable_calendar_sync">Envoyer les 茅v茅nements du calendrier sur la timeline</string>
+    <string name="pref_title_enable_calendar_sync">Synchroniser le calendrier</string>
+    <string name="pref_summary_sunrise_sunset">Envoyer les heures de lever et coucher du soleil dans l鈥檋istorique Pebble en fonction de l\'emplacement</string>
+    <string name="pref_title_sunrise_sunset">Lever et coucher de soleil</string>
+    <string name="pref_header_pebble_timeline">Timeline Pebble</string>
+    <string name="pref_summary_enable_pebblekit">Activer le support exp茅rimental pour les applications Android utilisant PebbleKit</string>
+    <string name="pref_title_enable_pebblekit">Permettre l\'acc猫s aux applications tierces Android</string>
+    <string name="pref_summary_enable_outgoing_call">D茅sactiver ceci emp锚chera la Pebble 2/LE de vibrer lors des appels sortants</string>
+    <string name="pref_title_enable_outgoing_call">Support des appels sortants</string>
+    <string name="pref_title_pebble_sync_morpheuz">Synchroniser Morpheuz</string>
+    <string name="pref_title_pebble_sync_misfit">Synchroniser Misfit</string>
+    <string name="pref_title_pebble_sync_health">Synchroniser Pebble Health</string>
+    <string name="pref_title_pebble_activitytracker">Traqueur d\'activit茅 pr茅f茅r茅</string>
+    <string name="pref_header_activitytrackers">Traqueurs d\'activit茅</string>
+    <string name="pref_title_pebble_settings">Param猫tres Pebble</string>
+    <string name="pref_title_development_miaddr">Adresse Mi Band</string>
+    <string name="pref_header_development">Options d茅veloppeur</string>
+    <string name="pref_title_canned_messages_set">Mise 脿 jour Pebble</string>
+    <string name="pref_title_canned_messages_dismisscall">Raccrocher</string>
+    <string name="pref_title_canned_reply_suffix">Suffixe fr茅quent</string>
+    <string name="pref_title_canned_replies">R茅ponses</string>
+    <string name="pref_header_cannned_messages">Mod猫les de messages</string>
+    <string name="pref_blacklist_calendars">Mettre des calendriers en liste noire</string>
+    <string name="pref_call_privacy_mode_complete">Masquer le nom et le num茅ro</string>
+    <string name="pref_call_privacy_mode_number">Masquer le num茅ro mais afficher le nom</string>
+    <string name="pref_call_privacy_mode_name">Masquer le nom mais afficher le num茅ro</string>
+    <string name="pref_call_privacy_mode_off">Afficher le nom et le num茅ro</string>
+    <string name="pref_title_call_privacy_mode">Mode de confidentialit茅 d\'appel</string>
+    <string name="pref_header_privacy">Confidentialit茅</string>
+    <string name="never">Jamais</string>
+    <string name="when_screen_off">Quand l\'茅cran est 茅teint</string>
+    <string name="always">Toujours</string>
+    <string name="pref_summary_transliteration">Activez ceci si votre appareil ne supporte pas la police de caract猫res</string>
+    <string name="pref_title_transliteration">Transcription</string>
+    <string name="pref_summary_notification_filter">Les notifications non-sollicit茅es sont suspendues dans ce mode</string>
+    <string name="pref_title_notification_filter">Ne Pas D茅ranger</string>
+    <string name="pref_title_whenscreenon">鈥 compris quand l\'茅cran est allum茅</string>
+    <string name="pref_title_notifications_generic">Support des notifications g茅n茅riques</string>
+    <string name="pref_summary_notifications_pebblemsg">Support des applications qui envoient des notification 脿 Pebble via PebbleKit.</string>
+    <string name="pref_title_notifications_pebblemsg">Messages Pebble</string>
+    <string name="pref_title_notifications_sms">Textos</string>
+    <string name="pref_title_notifications_call">Appels t茅l茅phoniques</string>
+    <string name="pref_title_notifications_repetitions">R茅p茅titions</string>
+    <string name="pref_header_notifications">Notifications</string>
+    <string name="pref_summary_minimize_priority_on">L\'ic么ne de la barre d\'茅tat et la notification de l\'茅cran de verrouillage sont masqu茅es</string>
+    <string name="pref_summary_minimize_priority_off">L\'ic么ne de la barre d\'茅tat et la notification de l\'茅cran de verrouillage sont affich茅es</string>
+    <string name="pref_title_minimize_priority">Masquer la notification de Gadgetbridge</string>
+    <string name="pref_title_language">Langue</string>
+    <string name="pref_theme_dark">Sombre</string>
+    <string name="pref_theme_light">Clair</string>
+    <string name="pref_title_theme">Th猫me</string>
+    <string name="pref_summary_datetime_syctimeonconnect">Synchroniser l\'heure sur l\'appareil lors de la connexion, et lorsque l\'heure ou le fuseau horaire changent sur Android</string>
+    <string name="pref_title_datetime_syctimeonconnect">Synchroniser l\'heure</string>
+    <string name="pref_header_datetime">Date et heure</string>
+    <string name="pref_default">Par d茅faut</string>
+    <string name="pref_title_general_autostartonboot">D茅marrer automatiquement</string>
+    <string name="pref_title_general_autoconnectonbluetooth">Connecter votre appareil quand le Bluetooth est activ茅</string>
+    <string name="pref_header_general">Param猫tres g茅n茅raux</string>
+    <string name="title_activity_settings">Param猫tres</string>
+    <string name="miband_firmware_suggest_whitelist">Si vous d茅sirez continuer malgr茅 tout, et que tout fonctionne correctement par la suite, veuillez en informer les d茅veloppeurs de Gadgetbridge pour demander l\'ajout du micrologiciel %s 脿 leur liste blanche.</string>
+    <string name="miband_firmware_known">Ce micrologiciel a 茅t茅 test茅 et est connu pour 锚tre compatible avec Gadgetbridge.</string>
+    <string name="fw_multi_upgrade_notice">Vous 锚tes sur le point d\'installer les micrologiciels %1$s et %2$s 脿 la place de ceux qui sont actuellement sur votre Mi Band.</string>
+    <string name="fw_upgrade_notice">Vous 锚tes sur le point d\'installer le micrologiciel %s.</string>
+    <string name="dbmanagementactivvity_cannot_access_export_path">Impossible d\'acc茅der au fichier d\'export. Merci de contacter les d茅veloppeurs.</string>
+    <string name="activity_db_management_merge_old_title">Effacer l\'ancienne base de donn茅es</string>
+    <string name="activity_db_management_import_export_explanation">Les op茅rations sur la base de donn茅es ont utilis茅 le chemin suivant sur votre appareil. 
+\nCe chemin est accessible par d\'autres applications Android ou par votre ordinateur. 
+\nVotre base de donn茅es (ou celle que vous souhaitez importer) se trouvera ici :</string>
+    <string name="title_activity_db_management">Gestion de base de donn茅es</string>
+    <string name="action_db_management">Gestion de base de donn茅es</string>
+    <string name="pref_summary_pebble_health_store_raw">Si coch茅, les donn茅es 芦聽brutes聽禄 sont stock茅es pour une interpr茅tation ult茅rieure, augmentant la taille de la base de donn茅es.</string>
+    <string name="title_activity_fw_app_insaller">Installateur d\'applications/firmware</string>
+    <string name="title_activity_calblacklist">Calendriers sur liste noire</string>
+    <string name="app_move_to_top">Haut de page</string>
+    <string name="app_configure">Configurer</string>
+    <string name="appmanager_weather_install_provider">Installer l\'application de notification de la m茅t茅o</string>
+    <string name="appmanager_weather_deactivate">D茅sactiver l\'application m茅t茅o</string>
+    <string name="appmanager_weather_activate">Activer l\'application m茅t茅o</string>
+    <string name="appmanager_hrm_deactivate">D茅sactiver la mesure du rythme cardiaque</string>
+    <string name="appmanager_hrm_activate">Activer la mesure du rythme cardiaque</string>
+    <string name="appmanager_health_deactivate">D茅sactiver</string>
+    <string name="appmanager_health_activate">Activer</string>
+    <string name="appmanager_app_openinstore">Rechercher dans le magasin d鈥檃pplication Pebble</string>
+    <string name="appmananger_app_reinstall">R茅installer</string>
+    <string name="appmananger_app_delete_cache">Supprimer et effacer du cache</string>
+    <string name="appmananger_app_delete">Supprimer</string>
+    <string name="appmanager_installed_watchfaces">Watchfaces install茅es</string>
+    <string name="appmanager_installed_watchapps">Applis install茅es</string>
+    <string name="appmanager_cached_watchapps_watchfaces">Applis en cache</string>
+    <string name="title_activity_appmanager">Gestionnaire d\'application</string>
+    <string name="title_activity_debug">D茅boguer</string>
+    <string name="controlcenter_snackbar_requested_screenshot">Capturer l\'茅cran de l\'appareil</string>
+    <string name="controlcenter_snackbar_connecting">Connexion鈥�</string>
+    <string name="controlcenter_snackbar_disconnecting">D茅connexion</string>
+    <string name="controlcenter_snackbar_need_longpress">Presser longuement l\'ic么ne pour d茅connecter</string>
+    <string name="controlcenter_navigation_drawer_close">Fermer le tiroir de navigation</string>
+    <string name="controlcenter_navigation_drawer_open">Ouvrir le tiroir de navigation</string>
+    <string name="controlcenter_delete_device_name">Supprimer %1$s</string>
+    <string name="controlcenter_delete_device">Supprimer l鈥檃ppareil</string>
+    <string name="controlcenter_disconnect">D茅connecter</string>
+    <string name="controlcenter_take_screenshot">Prendre une capture d\'茅cran</string>
+    <string name="controlcenter_find_device">Retrouver votre appareil perdu</string>
+    <string name="controlcenter_fetch_activity_data">Synchroniser</string>
+    <string name="action_donate">Faire un don</string>
+    <string name="action_quit">Quitter</string>
+    <string name="action_debug">D茅boguer</string>
+    <string name="action_settings">Param猫tres</string>
+    <string name="menuitem_workout">Entra卯nement</string>
+    <string name="menuitem_eventreminder">Rappel d\'茅v猫nement</string>
+    <string name="menuitem_hr">Fr茅quence cardiaque</string>
+    <string name="menuitem_pai">PAI</string>
+    <string name="devicetype_watchxplus">Watch X Plus</string>
+    <string name="devicetype_watchx">Watch X</string>
+    <string name="czesh">Tch猫que</string>
+    <string name="swedish">Su茅dois</string>
+    <string name="hebrew">H茅breu</string>
+    <string name="greek">Grec</string>
+    <string name="hungarian">Hongrois</string>
+    <string name="romanian">Roumain</string>
+    <string name="power_mode_saving">脡conomie d\'茅nergie</string>
+    <string name="power_mode_normal">Normal</string>
+    <string name="prefs_sensors_button_bp_calibration_sum">Appuyez ici pour commencer le calibrage</string>
+    <string name="prefs_sensors_button_bp_calibration">Calibrage</string>
+    <string name="pref_sensors_bp_calibration_high">Tension art茅rielle SYSTOLIQUE (茅lev茅e)</string>
+    <string name="pref_sensors_bp_calibration_low">Tension art茅rielle DIASTOLIQUE (basse)</string>
+    <string name="pref_sensors_bp_calibration">Calibrage de la tension art茅rielle</string>
+    <string name="pref_title_sensors_altitude">Calibrage de l\'altitude</string>
+    <string name="pref_header_sensors_calibration">Calibrage du capteur</string>
+    <string name="pref_title_device_spec_settings_force_time">Forcer l\'heure de synchronisation</string>
+    <string name="pref_header_device_spec_settings">Param猫tres du p茅riph茅rique</string>
+    <string name="prefs_notifications_and_calls_shake_reject">Secouer le poignet pour ignorer/rejeter un appel</string>
+    <string name="pref_summary_notifications_and_calls_title_reject">D茅sactiv茅 鈥� ignorer, Activ茅 鈥� rejeter</string>
+    <string name="prefs_notifications_and_calls_reject">Bouton ignorer/rejeter l\'appel</string>
+    <string name="pref_header_notifications_and_calls_callhandling">Traitement des appels</string>
+    <string name="pref_title_notifications_and_calls_repeat_on_misscall">R茅p茅ter pendant X minutes</string>
+    <string name="pref_summary_notifications_and_calls_enable_misscall">Se r茅p猫te chaque minute</string>
+    <string name="pref_notifications_and_calls_enable_misscall">Notifier un appel manqu茅</string>
+    <string name="prefs_notifications_and_calls_continious_ring">R茅p茅ter la notification d\'appel</string>
+    <string name="pref_title_notifications_and_calls_repeat_on_call">R茅p茅ter la notification d\'appel</string>
+    <string name="pref_header_notifications_and_calls">Notifications et appels</string>
+    <string name="title_activity_LenovoWatch_calibration">Calibrage de Watch X Plus</string>
+    <string name="pref_theme_system">Syst猫me</string>
+    <string name="sonyswr12_settings_title">Param猫tres Sony SWR12</string>
+    <string name="sonyswr12_settings_low_vibration">Faible vibration activ茅e</string>
+    <string name="sonyswr12_settings_stamina">Le mode d\'茅conomie d\'茅nergie est activ茅</string>
+    <string name="sonyswr12_settings_alarm_interval">Intervalle d\'alarme intelligente en minutes</string>
+    <string name="sonyswr12_settings_low_vibration_summary">Permettre une faible intensit茅 de vibration sur la bande</string>
+    <string name="sonyswr12_settings_stamina_summary">Le mode d\'茅conomie d\'茅nergie d茅sactive la mesure automatique p茅riodique de la fr茅quence cardiaque augmente ainsi le temps de travail</string>
+    <string name="sonyswr12_settings_alarm_interval_summary">L\'intervalle d\'alarme intelligente est l\'intervalle avant l\'alarme install茅e. Dans cet intervalle, l\'appareil tente de d茅tecter la phase de sommeil la plus l茅g猫re pour r茅veiller l\'utilisateur</string>
+    <string name="fw_upgrade_notice_miband5">Vous 锚tes sur le point d\'installer le micrologiciel %s dans votre Mi Band 5.
+\n
+\nVeuillez installer le fichier .fw, puis le fichier .res. Votre montre red茅marrera apr猫s avoir install茅 le fichier .fw.
+\n
+\nRemarque : Vous n\'avez pas besoin d\'installer le fichier .res si c\'est exactement le m锚me que celui pr茅c茅demment install茅.
+\n
+\nCONTINUEZ 脌 VOS RISQUES ET P脡RILS!</string>
+    <string name="fw_upgrade_notice_amazfit_trex">Vous 锚tes sur le point d\'installer le micrologiciel %s sur votre Amazfit T-Rex.
+\n
+\nVeuillez installer le fichier .fw, puis le fichier .res et finalement le fichier .gps. Votre montre red茅marrera apr猫s l\'installation du .fw.
+\n
+\nNote : vous n\'avez pas 脿 installer les fichier .res et .gps si ceux ci sont exactement les m锚me qu\'install茅 pr茅c茅demment.
+\n
+\n脌 VOS RISQUES ET P脡RILS!</string>
+    <string name="controlcenter_set_alias">D茅finir un alias</string>
+    <string name="find_lost_device_message">Rechercher %1$s\?</string>
+    <string name="pref_title_casio_alert_email">Alerte pour les notifications de courriel</string>
+    <string name="pref_summary_casio_alert_email">Alerte (vibre/bipe) pour les notifications de courriel</string>
+</resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml
new file mode 100644
index 0000000..4dc3a71
--- /dev/null
+++ b/app/src/main/res/values-fr/strings.xml
@@ -0,0 +1,3663 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources xmlns:tools="http://schemas.android.com/tools">
+    <string name="application_name_generic">Gadgetbridge</string>
+    <string name="title_activity_controlcenter_generic">Gadgetbridge</string>
+    <string name="action_settings">Param猫tres</string>
+    <string name="action_debug">D茅boguer</string>
+    <string name="action_quit">Quitter</string>
+    <string name="action_donate">Faire un don</string>
+    <string name="controlcenter_fetch_activity_data">Synchroniser</string>
+    <string name="controlcenter_find_device">Retrouver votre appareil perdu</string>
+    <string name="controlcenter_take_screenshot">Prendre une capture d\'茅cran</string>
+    <string name="controlcenter_disconnect">D茅connecter</string>
+    <string name="controlcenter_delete_device">Supprimer l鈥檃ppareil</string>
+    <string name="controlcenter_delete_device_name">Supprimer %1$s</string>
+    <string name="controlcenter_delete_device_dialogmessage">Ceci va supprimer l鈥檃ppareil et toutes les donn茅es associ茅es聽!</string>
+    <string name="controlcenter_navigation_drawer_open">Ouvrir le tiroir de navigation</string>
+    <string name="controlcenter_navigation_drawer_close">Fermer le tiroir de navigation</string>
+    <string name="controlcenter_snackbar_need_longpress">Presser longuement l\'ic么ne pour d茅connecter</string>
+    <string name="controlcenter_snackbar_disconnecting">D茅connexion</string>
+    <string name="controlcenter_snackbar_connecting">Connexion鈥�</string>
+    <string name="controlcenter_snackbar_requested_screenshot">Faire une capture d\'茅cran de l\'appareil</string>
+    <string name="title_activity_debug">D茅boguer</string>
+    <!--Strings related to AppManager-->
+    <string name="title_activity_appmanager">Gestionnaire d\'application</string>
+    <string name="appmanager_cached_watchapps_watchfaces">Applis en cache</string>
+    <string name="appmanager_installed_watchapps">Applis install茅es</string>
+    <string name="appmanager_installed_watchfaces">Cadrans install茅s</string>
+    <string name="appmananger_app_delete">Supprimer</string>
+    <string name="appmananger_app_delete_cache">Supprimer et effacer du cache</string>
+    <string name="appmananger_app_reinstall">R茅installer</string>
+    <string name="appmanager_app_openinstore">Rechercher dans le magasin d鈥檃pplication Pebble</string>
+    <string name="appmanager_health_activate">Activer</string>
+    <string name="appmanager_health_deactivate">D茅sactiver</string>
+    <string name="appmanager_hrm_activate">Activer la mesure du rythme cardiaque</string>
+    <string name="appmanager_hrm_deactivate">D茅sactiver la mesure du rythme cardiaque</string>
+    <string name="appmanager_weather_activate">Activer l\'application m茅t茅o</string>
+    <string name="appmanager_weather_deactivate">D茅sactiver l\'application m茅t茅o</string>
+    <string name="appmanager_weather_install_provider">Installer l\'application de notification de la m茅t茅o</string>
+    <string name="app_configure">Configurer</string>
+    <string name="app_move_to_top">Haut de page </string>
+    <!--Strings related to CalBlacklist-->
+    <string name="title_activity_calblacklist">Calendriers sur liste noire</string>
+    <!--Strings related to FwAppInstaller-->
+    <string name="title_activity_fw_app_insaller">Installateur d\'applications/firmware</string>
+    <string name="fw_upgrade_notice">Vous 锚tes sur le point d\'installer le micrologiciel %s.</string>
+    <string name="fw_multi_upgrade_notice">Vous 锚tes sur le point d\'installer les micrologiciels %1$s et %2$s 脿 la place de ceux qui sont actuellement sur votre Mi Band.</string>
+    <string name="miband_firmware_known">Ce micrologiciel a 茅t茅 test茅 et est connu pour 锚tre compatible avec Gadgetbridge.</string>
+    <string name="miband_firmware_unknown_warning">Ce micrologiciel n\'a pas 茅t茅 test茅 et peut ne pas 锚tre compatible avec Gadgetbridge. 
+\n 
+\nIl n\'est PAS conseill茅 de le flasher !</string>
+    <string name="miband_firmware_suggest_whitelist">Si vous d茅sirez continuer malgr茅 tout, et que tout fonctionne correctement par la suite, veuillez en informer les d茅veloppeurs de Gadgetbridge pour demander l\'ajout du micrologiciel %s 脿 leur liste blanche.</string>
+    <!--Strings related to Settings-->
+    <string name="title_activity_settings">Param猫tres</string>
+    <string name="pref_header_general">Param猫tres g茅n茅raux</string>
+    <string name="pref_title_general_autoconnectonbluetooth">Reconnecter votre appareil(s) quand le Bluetooth est activ茅</string>
+    <string name="pref_title_general_autostartonboot">D茅marrer automatiquement</string>
+    <string name="pref_title_general_autoreconnect">Reconnexion automatique </string>
+    <string name="pref_title_audio_player">Lecteur audio pr茅f茅r茅</string>
+    <string name="pref_default">Par d茅faut</string>
+    <string name="pref_header_datetime">Date et heure</string>
+    <string name="pref_title_datetime_syctimeonconnect">Synchroniser l\'heure</string>
+    <string name="pref_summary_datetime_syctimeonconnect">Synchroniser l\'heure sur les appareil(s) Gadgetbridge lors de la connexion, et lorsque l\'heure ou le fuseau horaire changent sur Android, et p茅riodiquement</string>
+    <string name="pref_title_theme">Th猫me</string>
+    <string name="pref_theme_light">Clair</string>
+    <string name="pref_theme_dark">Sombre</string>
+    <string name="pref_title_language">Langue</string>
+    <string name="pref_title_minimize_priority">Masquer la notification de Gadgetbridge</string>
+    <string name="pref_summary_minimize_priority_off">L\'ic么ne de la barre d\'茅tat et la notification de l\'茅cran de verrouillage sont affich茅es</string>
+    <string name="pref_summary_minimize_priority_on">L\'ic么ne de la barre d\'茅tat et la notification de l\'茅cran de verrouillage sont masqu茅es</string>
+    <string name="pref_header_notifications">Notifications</string>
+    <string name="pref_title_notifications_repetitions">R茅p茅titions</string>
+    <string name="pref_title_notifications_call">Appels t茅l茅phoniques</string>
+    <string name="pref_title_notifications_sms">Textos</string>
+    <string name="pref_title_notifications_pebblemsg">Messages Pebble</string>
+    <string name="pref_summary_notifications_pebblemsg">Support des applications qui envoient des notification 脿 Pebble via PebbleKit.</string>
+    <string name="pref_title_notifications_generic">Support des notifications g茅n茅riques</string>
+    <string name="pref_title_whenscreenon">鈥 compris quand l\'茅cran est allum茅</string>
+    <string name="pref_title_notification_filter">Ne Pas D茅ranger</string>
+    <string name="pref_summary_notification_filter">Toutes les notifications sont suspendues dans le mode Ne Pas D茅ranger</string>
+    <string name="pref_title_transliteration">Transcription</string>
+    <string name="pref_summary_transliteration">Activez ceci si votre appareil ne supporte pas la police de caract猫res</string>
+    <string name="always">Toujours</string>
+    <string name="when_screen_off">Quand l\'茅cran est 茅teint</string>
+    <string name="never">Jamais</string>
+    <string name="pref_header_privacy">Confidentialit茅</string>
+    <string name="pref_title_call_privacy_mode">Mode de confidentialit茅 d\'appel</string>
+    <string name="pref_call_privacy_mode_off">Afficher le nom et le num茅ro</string>
+    <string name="pref_call_privacy_mode_name">Masquer le nom mais afficher le num茅ro</string>
+    <string name="pref_call_privacy_mode_number">Masquer le num茅ro mais afficher le nom</string>
+    <string name="pref_call_privacy_mode_complete">Masquer le nom et le num茅ro</string>
+    <string name="pref_blacklist_calendars">Mettre des calendriers en liste noire</string>
+    <string name="pref_header_cannned_messages">Mod猫les de messages</string>
+    <string name="pref_title_canned_replies">R茅ponses</string>
+    <string name="pref_title_canned_reply_suffix">Suffixe fr茅quent</string>
+    <string name="pref_title_canned_messages_dismisscall">Raccrocher</string>
+    <string name="pref_title_canned_messages_set">Mise 脿 jour sur l\'appareil</string>
+    <string name="pref_header_development">Options d茅veloppeur</string>
+    <string name="pref_title_development_miaddr">Adresse Mi Band</string>
+    <string name="pref_title_pebble_settings">Param猫tres Pebble</string>
+    <string name="pref_header_activitytrackers">Traqueurs d\'activit茅</string>
+    <string name="pref_title_pebble_activitytracker">Traqueur d\'activit茅 pr茅f茅r茅</string>
+    <string name="pref_title_pebble_sync_health">Synchroniser Pebble Health</string>
+    <string name="pref_title_pebble_sync_misfit">Synchroniser Misfit</string>
+    <string name="pref_title_pebble_sync_morpheuz">Synchroniser Morpheuz</string>
+    <string name="pref_title_enable_outgoing_call">Support des appels sortants</string>
+    <string name="pref_summary_enable_outgoing_call">D茅sactiver ceci emp锚chera la Pebble 2/LE de vibrer lors des appels sortants</string>
+    <string name="pref_title_enable_pebblekit">Permettre l\'acc猫s aux applications tierces Android</string>
+    <string name="pref_summary_enable_pebblekit">Activer le support exp茅rimental pour les applications Android utilisant PebbleKit</string>
+    <string name="pref_header_pebble_timeline">Timeline Pebble</string>
+    <string name="pref_title_sunrise_sunset">Lever et coucher de soleil</string>
+    <string name="pref_summary_sunrise_sunset">Envoyer les heures de lever et coucher du soleil dans l鈥檋istorique Pebble en fonction de l\'emplacement</string>
+    <string name="pref_title_enable_calendar_sync">Synchroniser le calendrier</string>
+    <string name="pref_summary_enable_calendar_sync">Envoyer les 茅v茅nements du calendrier sur la timeline</string>
+    <string name="pref_title_autoremove_notifications">Effacer automatiquement les notifications rejet茅es</string>
+    <string name="pref_summary_autoremove_notifications">Les notifications sont automatiquement effac茅es de l\'appareil lorsqu\'elles sont rejet茅es sur le t茅l茅phone</string>
+    <string name="pref_title_pebble_privacy_mode">Mode priv茅</string>
+    <string name="pref_pebble_privacy_mode_off">Notifications normales</string>
+    <string name="pref_pebble_privacy_mode_content">D茅place les notifications hors de l\'茅cran</string>
+    <string name="pref_pebble_privacy_mode_complete">Ne montre que l\'ic么ne de notification</string>
+    <string name="pref_header_location">Emplacement</string>
+    <string name="pref_title_location_aquire">Obtenir l\'emplacement</string>
+    <string name="pref_title_location_latitude">Latitude</string>
+    <string name="pref_title_location_longitude">Longitude</string>
+    <string name="pref_title_location_keep_uptodate">Garder l鈥檈mplacement 脿 jour</string>
+    <string name="pref_summary_location_keep_uptodate">Essayer de garder la localisation 脿 jour pendant le fonctionnement, sinon utiliser l鈥檈mplacement enregistr茅</string>
+    <string name="toast_enable_networklocationprovider">Veuillez activer la localisation r茅seau</string>
+    <string name="toast_aqurired_networklocation">Emplacement obtenu</string>
+    <string name="pref_title_pebble_forceprotocol">Forcer le protocole de notification</string>
+    <string name="pref_summary_pebble_forceprotocol">Cette option force l\'utilisation du protocole de notification le plus r茅cent selon votre version du micrologiciel. 脌 VOS PROPRES RISQUES ET P脡RILS聽!</string>
+    <string name="pref_title_pebble_forceuntested">Activer les fonctionnalit茅s non test茅es</string>
+    <string name="pref_summary_pebble_forceuntested">Activer les fonctionnalit茅s non test茅es. 脌 VOS PROPRES RISQUES ET P脡RILS聽!</string>
+    <string name="pref_title_pebble_forcele">Toujours pr茅f茅rer le BLE</string>
+    <string name="pref_summary_pebble_forcele">Utiliser le support exp茅rimental du LE pour toutes les Pebble au lieu du Bluetooth classique. Cela requiert le jumelage d\'une Pebble non LE, puis d\'une Pebble LE</string>
+    <string name="pref_title_pebble_mtu_limit">Limite du GATT MTU de Pebble 2/LE</string>
+    <string name="pref_summary_pebble_mtu_limit">Si votre Pebble 2/LE ne fonctionne pas correctement, essayez d\'activer cette option pour limiter le MTU (plage valide 20鈥�512)</string>
+    <string name="pref_title_pebble_enable_applogs">Activer la journalisation des applis du bracelet</string>
+    <string name="pref_summary_pebble_enable_applogs">Ceci fera en sorte que les journaux des applis de montre soient journalis茅s par Gadgetbridge (requiert une reconnexion)</string>
+    <string name="pref_title_pebble_always_ack_pebblekit">ACK 脿 l\'avance du PebbleKit</string>
+    <string name="pref_summary_pebble_always_ack_pebblekit">Ceci permettra aux messages envoy茅s 脿 des apps tierces d\'锚tre toujours reconnus imm茅diatement</string>
+    <string name="pref_title_pebble_reconnect_attempts">Tentatives de reconnexion</string>
+    <string name="pref_title_unit_system">Unit茅s</string>
+    <string name="pref_title_timeformat">Format de l\'heure</string>
+    <string name="pref_title_screentime">Dur茅e d\'茅cran allum茅</string>
+    <string name="prefs_title_all_day_heart_rate">Mesure de la fr茅quence cardiaque toute la journ茅e</string>
+    <string name="preferences_hplus_settings">Param猫tres HPlus/Makibes</string>
+    <string name="not_connected">Non connect茅</string>
+    <string name="connecting">Connexion en cours</string>
+    <string name="connected">Connect茅</string>
+    <string name="unknown_state">脡tat inconnu</string>
+    <string name="_unknown_">(inconnu)</string>
+    <string name="test">Test</string>
+    <string name="test_notification">Notification de test</string>
+    <string name="this_is_a_test_notification_from_gadgetbridge">Ceci est un test de notification venant de Gadgetbridge</string>
+    <string name="bluetooth_is_not_supported_">Le Bluetooth n\'est pas support茅.</string>
+    <string name="bluetooth_is_disabled_">Le Bluetooth est d茅sactiv茅.</string>
+    <string name="tap_connected_device_for_app_mananger">Cliquez sur l\'appareil pour ouvrir le gestionnaire d\'application</string>
+    <string name="tap_connected_device_for_activity">Cliquez sur l\'appareil pour ouvrir le gestionnaire d鈥檃ctivit茅</string>
+    <string name="tap_connected_device_for_vibration">Cliquez sur connecter pour envoyer une vibration</string>
+    <string name="tap_a_device_to_connect">Tapez sur le p茅riph茅rique pour le connecter</string>
+    <string name="cannot_connect_bt_address_invalid_">Connexion impossible. L鈥檃dresse Bluetooth est-elle valide ?</string>
+    <string name="gadgetbridge_running_generic">Gadgetbridge est en fonctionnement</string>
+    <string name="installing_binary_d_d">Installation du binaire %1$d/%2$d</string>
+    <string name="installation_failed_">脡chec de l\'installation</string>
+    <string name="installation_successful">Install茅</string>
+    <string name="firmware_install_warning">VOUS TENTEZ D\'INSTALLER UN MICROLOGICIEL, PROC脡DEZ 脌 VOS RISQUES ET P脡RILS.\n\n\nCe micrologiciel est pour la version de mat茅riel : %s</string>
+    <string name="app_install_info">Vous 锚tes sur le point d\'installer l\'application suivante :
+\n
+\n%1$s-
+\nVersion %2$s par %3$s
+\n</string>
+    <string name="n_a">N.D.</string>
+    <string name="initialized">Initialis茅</string>
+    <string name="appversion_by_creator">%1$s par %2$s</string>
+    <string name="title_activity_discovery">Scanner les appareils</string>
+    <string name="discovery_stop_scanning">Arr锚ter le scan</string>
+    <string name="discovery_start_scanning">D茅marrer le scan</string>
+    <string name="action_discover">Connecter un nouvel appareil</string>
+    <string name="device_with_rssi">%1$s (%2$s)</string>
+    <string name="title_activity_android_pairing">Appairer l\'appareil</string>
+    <string name="android_pairing_hint">Utiliser l\'appairage Bluetooth d\'Android pour jumeler l\'appareil.</string>
+    <string name="title_activity_mi_band_pairing">Appairer votre Mi Band</string>
+    <string name="pairing">Jumelage avec %s鈥�</string>
+    <string name="pairing_creating_bond_with">Cr茅ation d鈥檜n lien avec %1$s (%2$s)</string>
+    <string name="pairing_unable_to_pair_with">Impossible se s鈥檃ppairer avec %1$s (%2$s)</string>
+    <string name="pairing_in_progress">Cr茅ation du lien en cours : %1$s (%2$s)</string>
+    <string name="pairing_already_bonded">D茅j脿 li茅 avec %1$s (%2$s), connexion鈥�</string>
+    <string name="message_cannot_pair_no_mac">Aucune adresse MAC fournie, ne peut 锚tre appair茅.</string>
+    <string name="preferences_category_device_specific_settings">Param猫tres sp茅cifiques 脿 l\'appareil </string>
+    <string name="preferences_miband_settings">Param猫tres Mi Band / Amazfit</string>
+    <string name="male">Homme</string>
+    <string name="female">Femme</string>
+    <string name="other">Autre</string>
+    <string name="left">Gauche</string>
+    <string name="right">Droite</string>
+    <string name="miband_pairing_using_dummy_userdata">Aucune donn茅e utilisateur valide fournie, utilisation de donn茅es fictives pour le moment.</string>
+    <string name="miband_pairing_tap_hint">Quand votre Mi Band vibre et clignote, appuyez dessus plusieurs fois d\'affil茅e.</string>
+    <string name="appinstaller_install">Installer</string>
+    <string name="discovery_connected_devices_hint">Mettez votre appareil en mode visible. Les appareils d茅j脿 connect茅s ne seront pas visibles. Sur Android 6 ou sup茅rieur, vous devez activer la localisation (ex. GPS). D茅sactivez Privacy Guard pour Gadgetbridge, il pourrait stopper et red茅marrer votre t茅l茅phone. Si votre appareil n\'est pas visible apr猫s 2 minutes, r茅essayez apr猫s avoir red茅marr茅 votre t茅l茅phone.</string>
+    <string name="discovery_note">Note :</string>
+    <string name="candidate_item_device_image">Image de l\'appareil</string>
+    <string name="miband_prefs_alias">Nom/Pseudo</string>
+    <string name="pref_header_vibration_count">Nombre de vibrations</string>
+    <string name="title_activity_sleepmonitor">Moniteur de sommeil</string>
+    <string name="pref_write_logfiles">脡crire des fichiers journaux</string>
+    <string name="initializing">Initialisation</string>
+    <string name="busy_task_fetch_activity_data">R茅cup茅ration des donn茅es d\'activit茅</string>
+    <string name="sleep_activity_date_range">De %1$s 脿 %2$s</string>
+    <string name="prefs_wearside">Port main gauche ou droite ?</string>
+    <string name="pref_screen_vibration_profile">Profil de vibration</string>
+    <string name="vibration_profile_staccato">Saccad茅</string>
+    <string name="vibration_profile_short">Court</string>
+    <string name="vibration_profile_medium">Moyen</string>
+    <string name="vibration_profile_long">Long</string>
+    <string name="vibration_profile_waterdrop">Goute d\'eau</string>
+    <string name="vibration_profile_ring">Sonnette</string>
+    <string name="vibration_profile_alarm_clock">Alarme</string>
+    <string name="miband_prefs_vibration">Vibreur</string>
+    <string name="vibration_try">Essayer</string>
+    <string name="pref_screen_notification_profile_sms">Notification Texto</string>
+    <string name="pref_header_vibration_settings">Param猫tres des vibrations</string>
+    <string name="pref_screen_notification_profile_generic">Notification g茅n茅rique</string>
+    <string name="pref_screen_notification_profile_email">Notification par courriel</string>
+    <string name="pref_screen_notification_profile_incoming_call">Notification d\'appels entrants</string>
+    <string name="pref_screen_notification_profile_generic_chat">Tchat</string>
+    <string name="pref_screen_notification_profile_generic_navigation">Navigation</string>
+    <string name="pref_screen_notification_profile_generic_social">R茅seau social</string>
+    <string name="stats_title">Zones de vitesse</string>
+    <string name="stats_x_axis_label">Total de minutes</string>
+    <string name="stats_y_axis_label">Pas par minute</string>
+    <string name="control_center_find_lost_device">Trouver l\'appareil perdu</string>
+    <string name="control_center_cancel_to_stop_vibration">Annuler pour arr锚ter les vibrations.</string>
+    <string name="title_activity_charts">Activit茅 et Sommeil</string>
+    <string name="title_activity_set_alarm">Configurer les alarmes</string>
+    <string name="controlcenter_start_configure_alarms">Configurer les alarmes</string>
+    <string name="title_activity_alarm_details">D茅tails des alarmes</string>
+    <string name="alarm_sun_short">Dim</string>
+    <string name="alarm_mon_short">Lun</string>
+    <string name="alarm_tue_short">Mar</string>
+    <string name="alarm_wed_short">Mer</string>
+    <string name="alarm_thu_short">Jeu</string>
+    <string name="alarm_fri_short">Ven</string>
+    <string name="alarm_sat_short">Sam</string>
+    <string name="alarm_smart_wakeup">R茅veil intelligent</string>
+    <string name="user_feedback_miband_set_alarms_failed">Une erreur s\'est produite lors du param茅trage des alarmes, veuillez r茅essayer.</string>
+    <string name="user_feedback_miband_set_alarms_ok">Alarmes envoy茅es 脿 l\'appareil.</string>
+    <string name="chart_no_data_synchronize">Aucune donn茅e. Synchroniser l\'appareil ?</string>
+    <string name="user_feedback_miband_activity_data_transfer">Sur le point de transf茅rer %1$s de donn茅es 脿 partir de %2$s</string>
+    <string name="miband_prefs_fitness_goal">Objectif de pas par jour</string>
+    <string name="dbaccess_error_executing">Erreur lors de l鈥檈x茅cution de %1$s\'</string>
+    <string name="controlcenter_start_activitymonitor">Votre activit茅</string>
+    <string name="cannot_connect">Impossible de se connecter : %1$s</string>
+    <string name="installer_activity_unable_to_find_handler">Impossible de trouver un gestionnaire pour installer ce fichier.</string>
+    <string name="pbw_install_handler_unable_to_install">Impossible d\'installer le ficher suivant : %1$s</string>
+    <string name="pbw_install_handler_hw_revision_mismatch">Impossible d\'installer le micrologiciel sp茅cifi茅 : il ne correspond pas 脿 la version du mat茅riel de votre Pebble.</string>
+    <string name="installer_activity_wait_while_determining_status">Veuillez patienter pendant la d茅termination de l\'茅tat de l\'installation鈥�</string>
+    <string name="notif_battery_low_title">Niveau de batterie faible !</string>
+    <string name="notif_battery_low_percent">%1$s batterie restante : %2$s%%</string>
+    <string name="notif_battery_low_bigtext_last_charge_time">Derni猫re charge : %s \n</string>
+    <string name="notif_battery_low_bigtext_number_of_charges">Nombre de charges : %s</string>
+    <string name="sleepchart_your_sleep">Sommeil</string>
+    <string name="weeksleepchart_sleep_a_week">Sommeil de la semaine</string>
+    <string name="weeksleepchart_today_sleep_description">Sommeil aujourd\'hui, objectif : %1$s</string>
+    <string name="weekstepschart_steps_a_week">Pas de la semaine</string>
+    <string name="activity_sleepchart_activity_and_sleep">Activit茅</string>
+    <string name="updating_firmware">Installation du micrologiciel鈥�</string>
+    <string name="fwapp_install_device_not_ready">Le fichier ne peut pas 锚tre install茅, l\'appareil n\'est pas pr锚t.</string>
+    <string name="installhandler_firmware_name">%1$s鈥�: %2$s %3$s</string>
+    <string name="miband_fwinstaller_compatible_version">Version compatible</string>
+    <string name="miband_fwinstaller_untested_version">Version non test茅e !</string>
+    <string name="fwappinstaller_connection_state">Connexion  脿 l\'appareil : %1$s</string>
+    <string name="pbw_installhandler_pebble_firmware">Micrologiciel Pebble %1$s</string>
+    <string name="pbwinstallhandler_correct_hw_revision">Version du mat茅riel correcte</string>
+    <string name="pbwinstallhandler_incorrect_hw_revision">Version du mat茅riel incorrecte !</string>
+    <string name="pbwinstallhandler_app_item">%1$s (%2$s)</string>
+    <string name="updatefirmwareoperation_updateproblem_do_not_reboot">Probl猫me avec le transfert du micrologiciel. Ne red茅marrez pas votre Mi Band !</string>
+    <string name="updatefirmwareoperation_metadata_updateproblem">Probl猫me avec le transfert de m茅tadonn茅es du micrologiciel</string>
+    <string name="updatefirmwareoperation_update_complete">Installation compl猫te du micrologiciel</string>
+    <string name="updatefirmwareoperation_update_complete_rebooting">Installation compl猫te du micrologiciel, red茅marrage de l\'appareil鈥�</string>
+    <string name="updatefirmwareoperation_write_failed">脡chec lors de l\'installation du micrologiciel</string>
+    <string name="chart_steps">Pas</string>
+    <string name="calories">Calories</string>
+    <string name="distance">Distance</string>
+    <string name="clock">Horloge</string>
+    <string name="heart_rate">Fr茅quence cardiaque</string>
+    <string name="battery">Batterie</string>
+    <string name="liveactivity_live_activity">Activit茅 en direct</string>
+    <string name="weeksteps_today_steps_description">Nombre de pas aujourd\'hui, objectif : %1$s</string>
+    <string name="pref_title_dont_ack_transfer">Ne pas confirmer le transfert de donn茅es d\'activit茅s</string>
+    <string name="pref_summary_dont_ack_transfers">Les donn茅es d\'activit茅s ne seront pas effac茅es du bracelet si elles ne sont pas confirm茅es par le bracelet. Utile si GB est utilis茅 avec d\'autres applications.</string>
+    <string name="pref_summary_keep_data_on_device">Les donn茅es d\'activit茅s seront conserv茅es sur l\'appareil apr猫s la synchronisation. Utile si GB est utilis茅 avec d\'autres applications. Cela peut entra卯ner la saturation de l\'espace dans la montre et/ou l鈥檃rr锚t de la synchronisation.</string>
+    <string name="pref_title_low_latency_fw_update">Utilisez le mode basse latence pour installer un micrologiciel</string>
+    <string name="pref_summary_low_latency_fw_update">Cela peut aider sur les appareils o霉 les installations de micrologiciel 茅chouent.</string>
+    <string name="live_activity_steps_history">Historique de pas</string>
+    <string name="live_activity_current_steps_per_minute">Pas/minute actuel</string>
+    <string name="live_activity_total_steps">Nombre total de pas</string>
+    <string name="live_activity_steps_per_minute_history">Historique de pas/minute</string>
+    <string name="live_activity_start_your_activity">D茅marrez votre activit茅</string>
+    <string name="abstract_chart_fragment_kind_activity">Activit茅</string>
+    <string name="abstract_chart_fragment_kind_light_sleep">Sommeil l茅ger</string>
+    <string name="abstract_chart_fragment_kind_deep_sleep">Sommeil profond</string>
+    <string name="abstract_chart_fragment_kind_not_worn">Non port茅</string>
+    <string name="device_not_connected">Non connect茅.</string>
+    <string name="user_feedback_all_alarms_disabled">Toutes alarmes d茅sactiv茅es</string>
+    <string name="pref_title_keep_data_on_device">Conserver les activit茅s sur l\'appareil</string>
+    <string name="miband_fwinstaller_incompatible_version">Micrologiciel non compatible</string>
+    <string name="fwinstaller_firmware_not_compatible_to_device">Ce micrologiciel n\'est pas compatible avec l\'appareil</string>
+    <string name="miband_prefs_reserve_alarm_calendar">Alarmes 脿 r茅server pour 茅v茅nements futurs</string>
+    <string name="miband_prefs_hr_sleep_detection">Utiliser le capteur cardiaque pour am茅liorer la d茅tection du sommeil</string>
+    <string name="miband_prefs_device_time_offset_hours">La compensation de temps en heure (pour travailleurs en rotation, par exemple)</string>
+    <string name="miband2_prefs_dateformat">Format de la date</string>
+    <string name="dateformat_time">Heure</string>
+    <string name="dateformat_date_time"><![CDATA[Date et heure]]></string>
+    <string name="mi2_prefs_goal_notification">Notification d\'objectif</string>
+    <string name="mi2_prefs_goal_notification_summary">Le bracelet vibrera lorsque l\'objectif de pas quotidien sera atteint</string>
+    <string name="mi2_prefs_display_items">脡l茅ments 脿 afficher</string>
+    <string name="mi2_prefs_display_items_summary">Choisissez les 茅l茅ments 脿 afficher sur le bracelet</string>
+    <string name="mi2_prefs_activate_display_on_lift">Allumer l\'茅cran lors d\'un mouvement</string>
+    <string name="mi2_prefs_rotate_wrist_to_switch_info">Tourner votre poignet pour changer d\'茅l茅ment</string>
+    <string name="mi2_prefs_do_not_disturb">Ne Pas D茅ranger</string>
+    <string name="mi2_prefs_do_not_disturb_summary">Le bracelet ne recevra pas de notifications si activ茅</string>
+    <string name="mi2_prefs_inactivity_warnings">Alertes d\'inactivit茅</string>
+    <string name="mi2_prefs_inactivity_warnings_summary">Le bracelet vibrera si vous n\'锚tes pas actif pendant un moment</string>
+    <string name="mi2_prefs_inactivity_warnings_threshold">Seuil d\'inactivit茅 (en minutes)</string>
+    <string name="mi2_prefs_inactivity_warnings_dnd_summary">D茅sactiver les alertes d\'inactivit茅 pendant un intervalle de temps</string>
+    <string name="mi2_prefs_do_not_disturb_start">Heure de d茅but</string>
+    <string name="mi2_prefs_do_not_disturb_end">Heure de fin</string>
+    <string name="FetchActivityOperation_about_to_transfer_since">Sur le point de transf茅rer des donn茅es depuis %1$s</string>
+    <string name="waiting_for_reconnect">En attente de reconnexion</string>
+    <string name="activity_prefs_about_you">脌 propos de vous</string>
+    <string name="activity_prefs_year_birth">Ann茅e de naissance</string>
+    <string name="activity_prefs_gender">Sexe</string>
+    <string name="activity_prefs_height_cm">Taille en cm</string>
+    <string name="activity_prefs_weight_kg">Poids en kg</string>
+    <string name="authenticating">Authentification</string>
+    <string name="authentication_required">Authentification requise</string>
+    <string name="appwidget_text">Zzz</string>
+    <string name="add_widget">Ajouter un widget</string>
+    <string name="activity_prefs_sleep_duration">
+Temps de sommeil pr茅f茅r茅 en heures</string>
+    <string name="appwidget_setting_alarm">Une alarme a 茅t茅 enregistr茅 pour %1$02d:%2$02d</string>
+    <string name="device_hw">R茅vision mat茅rielle : %1$s</string>
+    <string name="device_fw">Version du micrologiciel : %1$s</string>
+    <string name="error_creating_directory_for_logfiles">Erreur 脿 la cr茅ation de votre fichier log : %1$s</string>
+    <string name="DEVINFO_HR_VER">"Fr茅quence cardiaque聽: "</string>
+    <string name="updatefirmwareoperation_update_in_progress">Installation du micrologiciel</string>
+    <string name="updatefirmwareoperation_firmware_not_sent">脡chec lors de l\'茅criture du micrologiciel</string>
+    <string name="charts_legend_heartrate">Fr茅quence cardiaque</string>
+    <string name="live_activity_heart_rate">Fr茅quence cardiaque</string>
+    <string name="pref_title_pebble_health_store_raw">Stockez les enregistrements brut dans la base de donn茅es</string>
+    <string name="pref_summary_pebble_health_store_raw">Si coch茅, les donn茅es 芦聽brutes聽禄 sont stock茅es pour une interpr茅tation ult茅rieure, augmentant la taille de la base de donn茅es.</string>
+    <string name="action_db_management">Gestion des donn茅es</string>
+    <string name="title_activity_db_management">Gestion des donn茅es</string>
+    <string name="activity_db_management_import_export_explanation">Les op茅rations d\'Import/Export utilisent le r茅pertoire suivant (voir ci-dessous) sur votre appareil. Ce r茅pertoire est accessible 脿 d\'autres applications Android et depuis votre ordinateur. Merci de prendre note que ce r茅pertoire et tout son contenu seront effac茅s si vous d茅sinstallez Gadgetbridge. Cela inclut鈥�:
+\n Export_preference - r茅glages g茅n茅raux
+\n Export_preference_MAC - r茅glages sp茅cifiques 脿 l\'appareil
+\n Gadgetbridge - base de donn茅es des appareils et activit茅s
+\n Gadgetbridge_date - export de la base de donn茅es sur une date
+\n *.gpx - enregistrements GPS
+\n *.log - fichiers de log
+\nAttendez-vous 脿 trouver vos fichiers export茅s (ou 脿 placer l脿 les fichiers que vous voulez importer) 脿 cet endroit鈥�:</string>
+    <string name="activity_db_management_merge_old_title">Effacer l\'ancienne base de donn茅es</string>
+    <string name="dbmanagementactivvity_cannot_access_export_path">Impossible d\'acc茅der au fichier d\'export. Merci de contacter les d茅veloppeurs.</string>
+    <string name="dbmanagementactivity_exported_to">Exporter vers : %1$s</string>
+    <string name="dbmanagementactivity_error_exporting_db">Erreur d\'exportation de la base de donn茅es : %1$s</string>
+    <string name="dbmanagementactivity_error_exporting_shared">Erreur d\'exportation des pr茅f茅rences : %1$s</string>
+    <string name="dbmanagementactivity_import_data_title">Importer des donn茅es ?</string>
+    <string name="dbmanagementactivity_overwrite_database_confirmation">Voulez-vous vraiment 茅craser les donn茅es actuelles鈥�? Toutes vos donn茅es actuelles d\'activit茅 (s\'il y en a), appareils et pr茅f茅rences seront 茅cras茅es.</string>
+    <string name="dbmanagementactivity_import_successful">Import茅.</string>
+    <string name="dbmanagementactivity_error_importing_db">Erreur lors de l\'importation de la base de donn茅es : %1$s</string>
+    <string name="dbmanagementactivity_error_importing_shared">Erreur d\'importation des pr茅f茅rences : %1$s</string>
+    <string name="dbmanagementactivity_delete_activity_data_title">D茅truire les anciennes donn茅es ?</string>
+    <string name="dbmanagementactivity_really_delete_entire_db">Voulez-vous vraiment d茅truire enti猫rement la base de donn茅es ? Toutes vos donn茅es d\'activit茅 et vos informations issues de vos appareils seront perdues.</string>
+    <string name="dbmanagementactivity_database_successfully_deleted">Les donn茅es ont 茅t茅 effac茅es.</string>
+    <string name="dbmanagementactivity_db_deletion_failed">脡chec de la destruction de la base de donn茅es.</string>
+    <string name="dbmanagementactivity_delete_old_activity_db">Voulez-vous d茅truire les anciennes activit茅s de la base de donn茅es ?</string>
+    <string name="dbmanagementactivity_delete_old_activitydb_confirmation">Voulez-vous vraiment d茅truire enti猫rement la base de donn茅es ? Toutes vos donn茅es non import茅es seront perdues.</string>
+    <string name="dbmanagementactivity_old_activity_db_successfully_deleted">Les anciennes donn茅es d\'activit茅 ont 茅t茅 effac茅es.</string>
+    <string name="dbmanagementactivity_old_activity_db_deletion_failed">脡chec de la destruction de l\'ancienne base de donn茅es.</string>
+    <string name="dbmanagementactivity_overwrite">脡craser</string>
+    <string name="Cancel">Annuler</string>
+    <string name="Delete">Supprimer</string>
+    <!--Strings related to Vibration Activity-->
+    <string name="title_activity_vibration">Vibration</string>
+    <!--Strings related to Pebble Pairing Activity-->
+    <string name="title_activity_pebble_pairing">Jumelage avec une Pebble</string>
+    <string name="pebble_pairing_hint">Une fen锚tre de jumelage va s鈥檃fficher sur votre t茅l茅phone. Si cela ne se produit pas, regardez dans vos notifications et acceptez-la. Acceptez ensuite la demande de jumelage sur votre Pebble.</string>
+    <string name="weather_notification_label">Assurez vous que ce th猫me soit activ茅 dans l\'application de notification de la m茅t茅o pour recevoir les informations sur votre Pebble.
+\n
+\nAucune configuration n\'est requise.
+\n
+\nVous pouvez activer l\'application m茅t茅o syst猫me de votre Pebble depuis la configuration de l\'application.
+\n
+\nLes cadrans support茅s afficheront la m茅t茅o automatiquement.</string>
+    <string name="pref_title_setup_bt_pairing">Activer le jumelage Bluetooth</string>
+    <string name="pref_summary_setup_bt_pairing">D茅sactivez ceci si vous avez des probl猫mes de connexion</string>
+    <string name="unit_metric">M茅trique</string>
+    <string name="unit_imperial">Imp茅riale</string>
+    <string name="timeformat_24h">24H</string>
+    <string name="timeformat_am_pm">AM/PM</string>
+    <string name="pref_screen_notification_profile_alarm_clock">Alarme</string>
+    <string name="StringUtils_sender">(%1$s)</string>
+    <string name="find_device_you_found_it">Vous l\'avez trouv茅 !</string>
+    <string name="miband2_prefs_timeformat">Mi2 : format de l\'heure</string>
+    <string name="mi2_fw_installhandler_fw53_hint">Vous devez installer la version %1$s avant d\'installer ce micrologiciel !</string>
+    <string name="mi2_enable_text_notifications">Notifications textuelles</string>
+    <string name="mi2_enable_text_notifications_summary"><![CDATA[Requis : micrologiciel version 1.0.1.28 ou plus, fichier Mili_pro.ft* install茅.]]></string>
+    <string name="off">脡teint</string>
+    <string name="mi2_dnd_off">脡teint</string>
+    <string name="mi2_dnd_automatic">Automatique (d茅tection de sommeil)</string>
+    <string name="mi2_dnd_scheduled">Programm茅 (intervalle de temps)</string>
+    <string name="discovery_attempting_to_pair">Tentative de jumelage avec %1$s</string>
+    <string name="discovery_bonding_failed_immediately">Le lien avec %1$s a 茅chou茅 instantan茅ment.</string>
+    <string name="discovery_trying_to_connect_to">Tentative de connexion 脿 : %1$s</string>
+    <string name="discovery_enable_bluetooth">Activez le Bluetooth pour trouver des dispositifs.</string>
+    <string name="discovery_successfully_bonded">Li茅 脿 %1$s.</string>
+    <string name="discovery_pair_title">Appairer avec %1$s ?</string>
+    <string name="discovery_pair_question">S茅lectionnez Jumeler pour associer vos dispositifs. Si cela 茅choue, essayez 脿 nouveau sans jumelage.</string>
+    <string name="discovery_yes_pair">Appairer</string>
+    <string name="discovery_dont_pair">Ne pas appairer</string>
+    <string name="fw_upgrade_notice_amazfitbip">Vous 锚tes sur le point d\'installer le micrologiciel %s sur votre Amazfit Bip. 
+\n 
+\nVeuillez installer le fichier .fw, puis le fichier .res, et enfin le fichier .gps. Votre montre red茅marrera apr猫s l\'installation du .fw. 
+\n 
+\nNote : vous n\'avez pas 脿 installer les fichiers .res et .gps si ceux-ci sont identiques 脿 ceux install茅s pr茅c茅demment. 
+\n 
+\nCONTINUEZ 脌 VOS RISQUES ET P脡RILS聽!</string>
+    <string name="mi2_prefs_button_actions">Actions du bouton</string>
+    <string name="mi2_prefs_button_actions_summary">Sp茅cifier les actions par pression du bouton</string>
+    <string name="mi2_prefs_button_press_count">Nombre de pressions du bouton</string>
+    <string name="mi2_prefs_button_press_count_summary">Nombre d\'appuis sur le boutton pour envoyer l鈥櫭塿猫nement 1. Appuyer de nouveau autant de fois cr茅era l\'脡v猫nement 2, etc.</string>
+    <string name="mi2_prefs_button_press_broadcast">Message 脿 envoyer</string>
+    <string name="mi2_prefs_button_press_broadcast_summary">Message de diffusion envoy茅 avec l\'茅v猫nement. Le param猫tre 芦聽button_id聽禄 est automatiquement ajout茅 脿 chaque message.</string>
+    <string name="mi2_prefs_button_action">Activer action du bouton</string>
+    <string name="mi2_prefs_button_action_summary">Activer action apr猫s nombre sp茅cifi茅 de pressions</string>
+    <string name="mi2_prefs_button_action_vibrate">Activer la vibration du bracelet</string>
+    <string name="mi2_prefs_button_action_vibrate_summary">Activer la vibration apr猫s d茅clenchement de l\'action</string>
+    <string name="mi2_prefs_button_press_count_max_delay">D茅lai maximum entre pressions</string>
+    <string name="mi2_prefs_button_press_count_max_delay_summary">D茅lai maximum entre pressions en millisecondes</string>
+    <string name="_pebble_watch_open_on_phone">Ouvrir sur le smartphone Android</string>
+    <string name="_pebble_watch_mute">Silencieux</string>
+    <string name="_pebble_watch_reply">R茅pondre</string>
+    <string name="pref_title_pebble_enable_bgjs">Activer t芒che de fond JS</string>
+    <string name="pref_summary_pebble_enable_bgjs">Si activ茅, autorise l\'affichage de la m茅t茅o, niveau de batterie, etc.</string>
+    <string name="activity_web_view">Activit茅 Web View</string>
+    <string name="controlcenter_connect">Connecter鈥�</string>
+    <string name="fw_upgrade_notice_amazfitcor">Vous 锚tes sur le point d\'installer le micrologiciel %s sur votre Amazfit Cor. 
+\n 
+\nVeuillez installer le fichier .fw, puis le fichier .res. Votre montre red茅marrera apr猫s l\'installation du .fw. 
+\n 
+\nNote : vous n\'avez pas 脿 installer le .res si celui-ci est identique 脿 celui install茅 pr茅c茅demment. 
+\n 
+\nCONTINUEZ 脌 VOS RISQUES ET P脡RILS聽!</string>
+    <string name="pref_title_charts_swipe">Permettre le balayage gauche/droite dans les graphiques d\'activit茅</string>
+    <string name="automatic">Automatique</string>
+    <string name="simplified_chinese">Chinois simplifi茅</string>
+    <string name="traditional_chinese">Chinois traditionnel</string>
+    <string name="english">Anglais</string>
+    <string name="prefs_title_heartrate_measurement_interval">Mesure du pouls toute la journ茅e</string>
+    <string name="interval_one_minute">Une fois par minute</string>
+    <string name="interval_five_minutes">Toutes les 5 minutes</string>
+    <string name="interval_ten_minutes">Toutes les 10 minutes</string>
+    <string name="interval_thirty_minutes">Toutes les 30 minutes</string>
+    <string name="interval_one_hour">Une fois par heure</string>
+    <string name="pref_title_weather">M茅t茅o</string>
+    <string name="pref_title_weather_location">Emplacement m茅t茅o (du fournisseur m茅t茅o LineageOS)</string>
+    <string name="kind_firmware">Micrologiciel</string>
+    <string name="kind_invalid">Donn茅es non valides</string>
+    <string name="kind_font">Police</string>
+    <string name="kind_gps">Micrologiciel GPS</string>
+    <string name="kind_gps_almanac">Almanach GPS</string>
+    <string name="kind_gps_cep">Correction d\'erreurs GPS</string>
+    <string name="kind_resources">Ressources</string>
+    <string name="kind_watchface">Cadran</string>
+    <string name="devicetype_unknown">Appareil inconnu</string>
+    <string name="devicetype_test">Appareil de test</string>
+    <string name="devicetype_pebble">Pebble</string>
+    <string name="devicetype_miband">Mi Band</string>
+    <string name="devicetype_miband2">Mi Band 2</string>
+    <string name="devicetype_amazfit_bip">Amazfit Bip</string>
+    <string name="devicetype_amazfit_cor">Amazfit Cor</string>
+    <string name="devicetype_vibratissimo">Vibratissimo</string>
+    <string name="devicetype_liveview">Vue en direct</string>
+    <string name="devicetype_hplus">HPlus</string>
+    <string name="devicetype_makibes_f68">Makibes F68</string>
+    <string name="devicetype_exrizu_k8">Exrizu K8</string>
+    <string name="devicetype_no1_f1">No.1 F1</string>
+    <string name="devicetype_teclast_h30">Teclast H30</string>
+    <string name="pref_header_auto_export">Exporter automatiquement</string>
+    <string name="pref_title_auto_export_enabled">Exportation automatique activ茅e</string>
+    <string name="pref_title_auto_export_location">Emplacement de l\'exportation</string>
+    <string name="pref_title_auto_export_interval">Intervalle d\'exportation</string>
+    <string name="pref_summary_auto_export_interval">Exporter toutes les %d heures</string>
+    <string name="notif_export_failed_title">L\'exportation de la base de donn茅es a 茅chou茅聽! Veuillez v茅rifier vos param猫tres.</string>
+    <string name="choose_auto_export_location">Choisissez l\'emplacement d\'exportation</string>
+    <string name="spanish">Espagnol</string>
+    <string name="notification_channel_name">G茅n茅ral</string>
+    <string name="devicetype_xwatch">XWatch</string>
+    <string name="on">Actif</string>
+    <string name="activity_type_not_measured">Non mesur茅e</string>
+    <string name="activity_type_activity">Activit茅</string>
+    <string name="activity_type_light_sleep">Sommeil l茅ger</string>
+    <string name="activity_type_deep_sleep">Sommeil profond</string>
+    <string name="activity_type_running">Course</string>
+    <string name="activity_type_walking">Marche</string>
+    <string name="activity_type_swimming">Natation</string>
+    <string name="activity_type_unknown">Activit茅 inconnue</string>
+    <string name="activity_summaries">Activit茅s sportives</string>
+    <string name="activity_type_biking">V茅lo</string>
+    <string name="activity_type_treadmill">Tapis de course</string>
+    <string name="controlcenter_start_activity_tracks">Suivi de vos activit茅s</string>
+    <string name="activity_type_not_worn">Dispositif non utilis茅</string>
+    <string name="select_all">Tout s茅lectionner</string>
+    <string name="share">Partager</string>
+    <string name="reset_index">R茅initialiser la date de r茅cup茅ration</string>
+    <string name="menuitem_status">脡tat</string>
+    <string name="menuitem_activity">Historique d\'activit茅</string>
+    <string name="menuitem_weather">M茅t茅o</string>
+    <string name="menuitem_alarm">Alarme</string>
+    <string name="menuitem_timer">D茅compte</string>
+    <string name="menuitem_compass">Boussole</string>
+    <string name="menuitem_settings">R茅glages</string>
+    <string name="menuitem_alipay">Alipay</string>
+    <string name="blacklist_all_for_notifications">Mettre toutes les notifications en liste noire</string>
+    <string name="whitelist_all_for_notifications">Mettre toutes les notifications en liste blanche</string>
+    <string name="fw_upgrade_notice_miband3">Vous allez installer le micrologiciel %s dans votre Mi Band 3.
+\n
+\nVeuillez installer le fichier .fw, puis le fichier .res.. Votre bracelet red茅marrera apr猫s l\'installation du fichier .fw.
+\n 
+\nNote : vous n\'avez pas 脿 installer le fichier .res si celui-ci est exactement le m锚me que celui install茅 pr茅c茅demment.
+\n 
+\nCONTINUEZ 脌 VOS聽RISQUES聽ET聽P脡RILS !</string>
+    <string name="pref_title_pebble_gatt_clientonly">client GATT uniquement</string>
+    <string name="pref_summary_pebble_gatt_clientonly">Pour Pebble 2 seulement et exp茅rimental, essayez seulement si vous avez des probl猫mes de connexion</string>
+    <string name="prefs_screen_orientation">Orientation de l\'茅cran</string>
+    <string name="pref_auto_fetch">R茅cup茅rer automatiquement les donn茅es d\'activit茅</string>
+    <string name="pref_auto_fetch_summary">R茅cup茅ration au d茅verrouillage de l\'茅cran. Fonctionne uniquement si un m茅canisme de verrouillage est configur茅 !</string>
+    <string name="pref_auto_fetch_limit_fetches">Temps minimum entre synchronisations</string>
+    <string name="pref_auto_fetch_limit_fetches_summary">Synchronisation toutes les %d minutes</string>
+    <string name="horizontal">Horizontal</string>
+    <string name="vertical">Vertical</string>
+    <string name="russian">Russe</string>
+    <string name="devicetype_miband3">Mi Band 3</string>
+    <string name="devicetype_q8">Q8</string>
+    <string name="devicetype_mykronoz_zetime">MyKronoz ZeTime</string>
+    <string name="devicetype_id115">ID115</string>
+    <string name="menuitem_notifications">Notifications</string>
+    <string name="menuitem_music">Musique</string>
+    <string name="menuitem_more">Suite</string>
+    <string name="controlcenter_change_led_color">Changer la couleur de la LED</string>
+    <string name="controlcenter_change_fm_frequency">Changer la fr茅quence FM</string>
+    <string name="controlcenter_calibrate_device">Calibrer l\'appareil</string>
+    <string name="pref_title_notifications_timeout">Dur茅e minimum entre deux notifications</string>
+    <string name="pref_title_rtl">De droite 脿 gauche</string>
+    <string name="pref_summary_rtl">Cocher ceci si votre appareil n\'est pas compatible avec les langues \"droite-脿-gauche\"</string>
+    <string name="pref_rtl_max_line_length">Longueur max. d\'une ligne en mode Droite-脿-Gauche</string>
+    <string name="watch9_pairing_tap_hint">Lorsque votre bracelet vibre, secouez-le ou pressez son bouton.</string>
+    <string name="notif_battery_low">%1$s batterie faible</string>
+    <string name="notif_battery_low_extended">%1$s batterie faible聽: %2$s</string>
+    <string name="lack_of_sleep">Manque de sommeil聽: %1$s</string>
+    <string name="overslept">Exc猫s de sommeil聽: %1$s</string>
+    <string name="no_limit">Sans limite</string>
+    <string name="seconds_5">5 secondes</string>
+    <string name="seconds_10">10 secondes</string>
+    <string name="seconds_20">20 secondes</string>
+    <string name="seconds_30">30 secondes</string>
+    <string name="minutes_1">1 minute</string>
+    <string name="minutes_5">5 minutes</string>
+    <string name="minutes_10">10 minutes</string>
+    <string name="minutes_30">30 minutes</string>
+    <string name="lack_of_step">Manque d\'activit茅聽: %1$d</string>
+    <string name="overstep">Trop d\'activit茅聽: %1$d</string>
+    <string name="live_activity_max_heart_rate">Mesure cardiaque actuelle / maximum聽: %1$d / %2$d</string>
+    <string name="you_slept">%1$s - %2$s</string>
+    <string name="you_did_not_sleep">Vous n\'avez pas dormi</string>
+    <string name="mi3_prefs_band_screen_unlock_summary">Glisser le doigt vers le haut pour d茅bloquer l\'茅cran du bracelet</string>
+    <string name="mi3_prefs_night_mode">Mode nuit</string>
+    <string name="mi3_prefs_night_mode_summary">Diminuer la luminosit茅 de l\'茅cran du bracelet automatiquement lorsqu\'il fait sombre</string>
+    <string name="german">Allemand</string>
+    <string name="italian">Italien</string>
+    <string name="french">Fran莽ais</string>
+    <string name="polish">Polonais</string>
+    <string name="korean">Cor茅en</string>
+    <string name="japanese">Japonais</string>
+    <string name="activity_prefs_charts">Param猫tres des graphiques</string>
+    <string name="activity_prefs_chart_max_heart_rate">Fr茅quence cardiaque maximum</string>
+    <string name="activity_prefs_chart_min_heart_rate">Fr茅quence cardiaque minimum</string>
+    <string name="ok">Ok</string>
+    <string name="mi3_night_mode_sunset">Au coucher du soleil</string>
+    <string name="devicetype_watch9">Watch 9</string>
+    <string name="watch9_time_minutes">Minutes聽:</string>
+    <string name="watch9_time_hours">Heures聽:</string>
+    <string name="watch9_time_seconds">Secondes聽:</string>
+    <string name="watch9_calibration_hint">R茅gler l\'heure que votre appareil indique actuellement.</string>
+    <string name="watch9_calibration_button">Calibrer</string>
+    <string name="title_activity_watch9_pairing">Appairage de la Watch 9</string>
+    <string name="title_activity_watch9_calibration">Calibration de la Watch 9</string>
+    <string name="preferences_rtl_settings">Support Droite-脿-Gauche</string>
+    <string name="share_log">Partager les journaux</string>
+    <string name="share_log_warning">Veuillez garder 脿 l\'esprit que les fichiers de journalisation de Gadgetbridge peuvent contenir beaucoup d\'informations personnelles, incluant entre autre des donn茅es relatives 脿 la sant茅, des identifiants uniques (telles que des adresses MAC), des pr茅f茅rences musicales, etc. Pensez 脿 modifier le fichier et 脿 retirer ces informations personnelles avant toute publication sur un rapport de bug public.</string>
+    <string name="warning">Attention聽!</string>
+    <string name="no_data">Pas de donn茅es</string>
+    <string name="preferences_led_color">Couleur de la DEL</string>
+    <string name="preferences_fm_frequency">Fr茅quence FM</string>
+    <string name="pref_invalid_frequency_title">Fr茅quence invalide</string>
+    <string name="pref_invalid_frequency_message">Veuillez introduire une fr茅quence entre 87,5 et 108,0</string>
+    <string name="language_and_region_prefs">Param猫tres de langue et de r茅gion</string>
+    <string name="norwegian_bokmal">Bokm氓l norv茅gien</string>
+    <string name="devicetype_roidmi">Roidmi</string>
+    <string name="devicetype_roidmi3">Roidmi 3</string>
+    <string name="pref_title_contextual_arabic">Arabe contextuel</string>
+    <string name="pref_summary_contextual_arabic">Activer ceci pour prendre en charge l\'arabe contextuel</string>
+    <string name="debugactivity_really_factoryreset_title">脢tes-vous s没r de vouloir retourner aux param猫tres d\'usine聽\?</string>
+    <string name="debugactivity_really_factoryreset">Une r茅initialisation effacera toutes les donn茅es de l\'appareil connect茅 (si support茅). Les appareils Xiaomi/Huami modifient 茅galement l\'adresse MAC Bluetooth, ainsi ils appara卯tront comme nouveau dans Gadgetbridge.</string>
+    <string name="mi3_prefs_band_screen_unlock">D茅verrouillage de l\'茅cran du Band</string>
+    <string name="activity_type_exercise">Activit茅 physique</string>
+    <string name="devicetype_casiogb6900">Casio GB-6900</string>
+    <string name="title_activity_notification_filter">Filtre de notifications</string>
+    <string name="edittext_notification_filter_words_hint">Saisissez les mots d茅sir茅s, chacun sur une ligne</string>
+    <string name="toast_notification_filter_saved_successfully">Filtre des notifications sauvegard茅</string>
+    <string name="filter_mode_none">Ne pas filtrer</string>
+    <string name="filter_mode_whitelist">Montrer lorsque contient les mots</string>
+    <string name="filter_mode_blacklist">Bloquer lorsque contient les mots</string>
+    <string name="filter_submode_at_least_one">Au moins un des mots</string>
+    <string name="filter_submode_all">Tous les mots</string>
+    <string name="toast_notification_filter_words_empty_hint">Veuillez entrer au moins un mot</string>
+    <string name="filter_mode">Mode Filtre</string>
+    <string name="mode_configuration">Mode Configuration</string>
+    <string name="save_configuration">Sauvegarder la configuration</string>
+    <string name="appwidget_not_connected">Non connect茅, l\'alarme n\'est pas d茅finie.</string>
+    <string name="prefs_disconnect_notification">Notification de d茅connexion</string>
+    <string name="zetime_title_settings">Param猫tres ZeTime</string>
+    <string name="zetime_title_heartrate">Param猫tres Fr茅quence Cardiaque</string>
+    <string name="zetime_title_screentime">Dur茅e d\'茅cran allum茅 en secondes</string>
+    <string name="zetime_title_heart_rate_alarm">Alarme Fr茅quence Cardiaque</string>
+    <string name="zetime_title_heart_rate_alarm_summary">La montre vous alertera quand votre fr茅quence cardiaque d茅passe les limites.</string>
+    <string name="zetime_heart_rate_alarm_enable">Activer l\'alarme de fr茅quence cardiaque</string>
+    <string name="activity_prefs_alarm_max_heart_rate">Fr茅quence cardiaque maximale</string>
+    <string name="activity_prefs_alarm_min_heart_rate">Fr茅quence cardiaque minimale</string>
+    <string name="zetime_analog_mode">Mode analogique</string>
+    <string name="zetime_analog_mode_hands">Aiguilles seulement</string>
+    <string name="zetime_analog_mode_handsandsteps">Aiguilles et Pas</string>
+    <string name="zetime_activity_tracking">Suivi des Activit茅s</string>
+    <string name="zetime_activity_tracking_summary">Activer le suivi des activit茅s : comptera vos pas, etc.</string>
+    <string name="zetime_handmove_display_summary">Tourner le poignet pour activer ou d茅sactiver l\'茅cran.</string>
+    <string name="zetime_calories_type">Type de calories</string>
+    <string name="zetime_calories_type_active">Seulement les calories br没l茅es activement</string>
+    <string name="zetime_calories_type_all">Calories br没l茅es activement et au repos</string>
+    <string name="zetime_date_format">Format de la date</string>
+    <string name="zetime_date_format_1">aaaa/mm/jj</string>
+    <string name="zetime_date_format_2">jj/mm/aaaa</string>
+    <string name="zetime_date_format_3">mm/jj/aaaa</string>
+    <string name="zetime_prefs_inactivity_repetitions">R茅p茅titions</string>
+    <string name="zetime_prefs_inactivity_mo">Lundi</string>
+    <string name="zetime_prefs_inactivity_tu">Mardi</string>
+    <string name="zetime_prefs_inactivity_we">Mercredi</string>
+    <string name="zetime_prefs_inactivity_th">Jeudi</string>
+    <string name="zetime_prefs_inactivity_fr">Vendredi</string>
+    <string name="zetime_prefs_inactivity_sa">Samedi</string>
+    <string name="zetime_prefs_inactivity_su">Dimanche</string>
+    <string name="zetime_title_alarm_signaling">Type de signal pour l\'alarme</string>
+    <string name="zetime_signaling_none">Silencieux</string>
+    <string name="zetime_signaling_vibrate">Vibration continue</string>
+    <string name="zetime_signaling_beep">Bip continu</string>
+    <string name="zetime_signaling_vibrate_beep">Vibration et bip continus</string>
+    <string name="zetime_signaling_vibrate_once">Vibration unique</string>
+    <string name="zetime_signaling_vibrate_twice">Vibration double</string>
+    <string name="zetime_signaling_beep_once">Bip unique</string>
+    <string name="zetime_signaling_beep_twice">Bip double</string>
+    <string name="zetime_signaling_vibrate_beep_once">Vibration et bip uniques</string>
+    <string name="pref_screen_notification_profile_missed_call">Notification appel manqu茅</string>
+    <string name="pref_screen_notification_profile_calendar">Notification du calendrier</string>
+    <string name="pref_screen_notification_profile_inactivity">Notification d\'inactivit茅</string>
+    <string name="pref_screen_notification_profile_low_power">Alerte batterie faible</string>
+    <string name="pref_screen_notification_profile_anti_loss">Alerte Anti-Perte</string>
+    <string name="interval_fifteen_minutes">Toutes les 15 min</string>
+    <string name="interval_forty_five_minutes">Toutes les 45 min</string>
+    <string name="activity_prefs_calories_burnt">Objectif quotidien聽: calories brul茅es</string>
+    <string name="activity_prefs_distance_meters">Objectif quotidien : distance en m猫tres</string>
+    <string name="activity_prefs_activetime_minutes">Objectif quotidien : temps d\'activit茅 en minutes</string>
+    <string name="devicetype_miscale2">Mi Scale 2</string>
+    <string name="pref_title_support_voip_calls">Activer les applications d\'appel VoIP</string>
+    <string name="title_activity_device_specific_settings">Param猫tres sp茅cifiques de l\'appareil</string>
+    <string name="pref_title_authkey">Cl茅 d\'autorisation</string>
+    <string name="pref_summary_authkey">Changez la cl茅 d\'autorisation pour une cl茅 commune 脿 tous vos appareils Android sur lesquels vous souhaitez vous connecter. La pr茅c茅dente cl茅 par d茅faut est 0123456789@ABCDE</string>
+    <string name="devicetype_bfh16">BFH-16</string>
+    <string name="fw_upgrade_notice_amazfitcor2">Vous 锚tes sur le point d\'installer le micrologiciel %s sur votre Amazfit Cor 2. 
+\n 
+\nVeuillez installer le fichier .fw, puis le fichier .res. Votre montre red茅marrera apr猫s installation du .fw. 
+\n 
+\nNote : il n\'est pas n茅cessaire d\'installer le fichier .res si celui-ci est identique 脿 celui install茅 pr茅c茅demment. 
+\n 
+\nCONTINUEZ 脌 VOS RISQUES ET P脡RILS聽! 
+\n 
+\nNON TEST脡, IL PEUT 脢TRE N脡CESSAIRE DE FLASHER UN MICROLOGICIEL BEATS_W SI LE NOM DE L\'APPAREIL EST 芦 Amazfit Band 2 禄</string>
+    <string name="dutch">N茅erlandais</string>
+    <string name="turkish">Turc</string>
+    <string name="ukrainian">Ukrainien</string>
+    <string name="arabic">Arabe</string>
+    <string name="indonesian">Indon茅sien</string>
+    <string name="thai">Tha茂</string>
+    <string name="vietnamese">Vietnamien</string>
+    <string name="portuguese">Portugais</string>
+    <string name="devicetype_amazfit_cor2">Amazfit Cor 2</string>
+    <string name="pref_rtl_max_line_length_summary">Allonge ou raccourcis les lignes dans les textes 茅crits de droite 脿 gauche</string>
+    <string name="zetime_handmove_display">Mouvement de la main</string>
+    <string name="devicetype_miband4">Mi Band 4</string>
+    <string name="fw_upgrade_notice_miband4">Vous 锚tes sur le point d\'installer le firmware %s sur votre Mi Band 4.
+\n
+\nVeuillez installer le fichier .fw, puis le fichier .res. Votre bracelet red茅marrera apr猫s l\'installation du fichier .fw.
+\n 
+\nRemarque : Vous n\'avez pas 脿 installer le .res s\'il est exactement le m锚me que celui install茅 pr茅c茅demment.
+\n 
+\nCONTINUEZ 脌 VOS RISQUES ET P脡RILS聽!</string>
+    <string name="prefs_hr_alarm_activity">Alarme de la fr茅quence cardiaque durant une activit茅 sportive</string>
+    <string name="prefs_hr_alarm_low">Limite basse</string>
+    <string name="prefs_hr_alarm_high">Limite haute</string>
+    <string name="average">Moyenne : %1$s</string>
+    <string name="pref_header_charts">Param猫tres graphiques</string>
+    <string name="pref_title_charts_average">Afficher les moyennes dans les graphiques</string>
+    <string name="pref_title_charts_range">Gamme des graphiques</string>
+    <string name="weekstepschart_steps_a_month">Pas par mois</string>
+    <string name="weeksleepchart_sleep_a_month">Sommeil par mois</string>
+    <string name="menuitem_nfc">CCP</string>
+    <string name="pref_title_use_custom_font">Utiliser une police personnalis茅e</string>
+    <string name="activity_DB_test_export_message">Exportation de la base de donn茅es鈥�</string>
+    <string name="activity_db_management_exportimport_label">Exporter et Importer</string>
+    <string name="widget_listing_label">脡tat et alarmes</string>
+    <string name="widget_set_alarm_after">D茅finir l\'alarme apr猫s聽:</string>
+    <string name="widget_5_minutes">5 minutes</string>
+    <string name="widget_10_minutes">10 minutes</string>
+    <string name="widget_20_minutes">20 minutes</string>
+    <string name="widget_1_hour">1 heure</string>
+    <string name="pref_display_add_device_fab_on">Toujours visible</string>
+    <string name="pref_display_add_device_fab_off">Visible uniquement si aucun appareil n\'est ajout茅</string>
+    <string name="prefs_find_phone_duration">Dur茅e de la sonnerie en secondes</string>
+    <string name="maximum_duration">Dur茅e</string>
+    <string name="pref_charts_range_on">Gamme des graphiques positionn茅e sur un mois</string>
+    <string name="pref_charts_range_off">Gamme des graphiques positionn茅e sur une semaine</string>
+    <string name="devicetype_mijia_lywsd02">Mijia Smart Clock</string>
+    <string name="pref_summary_expose_hr">Permettre aux autres applis d\'acc茅der aux donn茅es de fr茅quence cardiaque en temps r茅el lorsque Gadgetbridge est connect茅</string>
+    <string name="pref_title_expose_hr">Acc猫s tiers fr茅q. cardiaque</string>
+    <string name="pref_summary_use_custom_font">Activer ceci afin d鈥檃fficher les 茅mojis si votre appareil a un micrologiciel de police personnalis茅e</string>
+    <string name="activity_db_management_autoexport_explanation">L\'export automatique des donn茅es est r茅gl茅 vers :</string>
+    <string name="activity_db_management_autoexport_label">Exportation automatique</string>
+    <string name="activity_DB_ExportButton">Exporter les donn茅es</string>
+    <string name="activity_DB_import_button">Importer les donn茅es</string>
+    <string name="activity_DB_test_export_button">Lancer imm茅diatement l\'exportation automatique</string>
+    <string name="activity_DB_delete_legacy_button">Supprimer l\'ancienne base de donn茅es</string>
+    <string name="activity_DB_empty_button">Vider la base de donn茅es</string>
+    <string name="activity_db_management_empty_DB">Vider la base de donn茅es</string>
+    <string name="activity_db_management_empty_db_warning">Attention聽! En cliquant sur ce bouton, vous supprimerez toutes vos donn茅es et recommencerez 脿 z茅ro.</string>
+    <string name="appwidget_sleep_alarm_widget_label">R茅veil</string>
+    <plurals name="widget_alarm_target_hours" tools:ignore="MissingQuantity">
+        <item quantity="one">%d heure</item>
+        <item quantity="many">%d heures</item>
+        <item quantity="other">%d heures</item>
+    </plurals>
+    <string name="pref_display_add_device_fab">Bouton pour connecter un nouvel appareil</string>
+    <string name="activity_error_no_app_for_gpx">Pour visualiser votre g茅olocalisation, installez une appli qui lit les fichiers GPX.</string>
+    <string name="preferences_makibes_hr3_settings">R茅glages Makibes HR3</string>
+    <string name="devicetype_makibes_hr3">Makibes HR3</string>
+    <string name="devicetype_amazfit_bip_lite">Amazfit Bip Lite</string>
+    <string name="prefs_find_phone">Trouver le t茅l茅phone</string>
+    <string name="prefs_enable_find_phone">Activer 芦 Trouver le t茅l. 禄</string>
+    <string name="prefs_find_phone_summary">Utilisez votre bande pour lire l\'alarme de votre t茅l茅phone.</string>
+    <string name="discovery_need_to_enter_authkey">Cet appareil a besoin d\'une cl茅 d\'auth. Pression longue sur celui-ci pour la saisir. Lisez le wiki.</string>
+    <string name="pref_chart_heartrate_color_red">Rouge</string>
+    <string name="pref_chart_heartrate_color_orange">Orange</string>
+    <string name="pref_chart_sleep_rolling_24_on">Derni猫res 24 heures</string>
+    <string name="pref_chart_sleep_rolling_24_off">De midi 脿 midi</string>
+    <string name="fw_upgrade_notice_amazfitbip_lite">Vous 锚tes sur le point d\'installer le micrologiciel %s sur votre Amazfit Bip Lite. 
+\n 
+\nVeuillez installer le fichier .fw, puis le fichier .res. Votre montre red茅marrera apr猫s l\'installation du .fw. 
+\n 
+\nNote : vous n\'avez pas 脿 installer le fichier .res si c\'est exactement le m锚me que celui install茅 pr茅c茅demment. 
+\n 
+\n脌 VOS RISQUES ET P脡RILS !</string>
+    <string name="devicetype_amazfit_gtr">Amazfit GTR</string>
+    <string name="fw_upgrade_notice_amazfitgtr">Vous 锚tes sur le point d\'installer le micrologiciel %s sur votre Amazfit GTR. 
+\n 
+\nVeuillez installer le fichier .fw, puis le fichier .res et enfin le fichier .gps. Votre montre red茅marrera apr猫s installation du fichier .fw. 
+\n 
+\nNote : vous n\'avez pas 脿 installer les fichiers .res et .gps si ceux-ci sont exactement les m锚mes que ceux install茅s pr茅c茅demment. 
+\n 
+\nCONTINUEZ 脌 VOS RISQUES ET P脡RILS ET P脡RILS聽!</string>
+    <string name="pref_title_chart_heartrate_color">Couleur du rythme cardiaque</string>
+    <string name="pref_title_chart_sleep_rolling_24_hour">Plage de sommeil</string>
+    <string name="devicetype_amazfit_gts">Amazfit GTS</string>
+    <string name="fw_upgrade_notice_amazfitgts">Vous 锚tes sur le point d\'installer le micrologiciel %s sur votre Amazfit GTS. 
+\n 
+\nVeuillez installer le fichier .fw, puis le fichier .res et enfin le fichier .gps. Votre montre red茅marrera apr猫s installation du fichier .fw. 
+\n 
+\nNote : vous n\'avez pas 脿 installer les fichiers .res et .gps s\'ils sont exactement les m锚mes que ceux install茅s pr茅c茅demment. 
+\n 
+\nCONTINUEZ 脌 VOS RISQUES ET P脡RILS聽!</string>
+    <string name="devicetype_qhybrid">Fossil Q Hybrid</string>
+    <string name="preferences_qhybrid_settings">Param猫tres hybrides Q</string>
+    <string name="watch_not_connected">Montre non connect茅e</string>
+    <string name="qhybrid_vibration_strength">Puissance de vibration :</string>
+    <string name="qhybrid_goal_in_steps">Objectif de pas</string>
+    <string name="qhybrid_time_shift">d茅calage horaire</string>
+    <string name="qhybrid_second_timezone_offset_relative_to_utc">d茅calage du deuxi猫me fuseau horaire par rapport 脿 UTC</string>
+    <string name="qhybrid_prompt_million_steps">Veuillez r茅gler le compteur de pas 脿 un million pour l\'activer.</string>
+    <string name="qhybrid_changes_delay_prompt">le changement peut prendre quelques secondes鈥�</string>
+    <string name="pref_disable_new_ble_scanning">D茅sactiver la nouvelle d茅tection BLE</string>
+    <string name="pref_summary_disable_new_ble_scanning">Cochez cette option si votre appareil ne peut 锚tre d茅couvert</string>
+    <string name="devicetype_banglejs">Bangle.js</string>
+    <string name="qhybrid_overwrite_buttons">modifier les boutons</string>
+    <string name="qhybrid_use_activity_hand_as_notification_counter">utilise l\'activit茅 de la main comme compteur de notification</string>
+    <string name="qhybrid_buttons_overwrite_success">Boutons modifi茅s</string>
+    <string name="qhybrid_buttons_overwrite_error">Une erreur est survenue lors de la modification des boutons</string>
+    <string name="qhybrid_offset_timezone">d茅cale le fuseau horaire de</string>
+    <string name="qhybrid_offset_time_by">d茅cale l\'heure de</string>
+    <string name="devicetype_y5">Y5</string>
+    <string name="prefs_button_single_press_action_selection_title">Action de l\'茅v猫nement 1</string>
+    <string name="prefs_button_double_press_action_selection_title">Action de l\'茅v猫nement 2</string>
+    <string name="prefs_button_triple_press_action_selection_title">Action de l\'茅v猫nement 3</string>
+    <string name="prefs_button_variable_actions">Param猫tres d茅taill茅s des appuis de bouton</string>
+    <string name="prefs_button_long_press_action_selection_title">Action d\'appui long de bouton</string>
+    <string name="alarm_snooze">Reporter</string>
+    <string name="error_no_location_access">L\'acc猫s 脿 la localisation doit 锚tre autoris茅 et activ茅 pour permettre 脿 la d茅tection de fonctionner correctement</string>
+    <string name="devicetype_itag">iTag</string>
+    <string name="pref_title_allow_high_mtu">Autoriser une grande MTU</string>
+    <string name="pref_summary_allow_high_mtu">Augmente la vitesse de transfert, mais peut ne pas fonctionner avec quelques appareils Android.</string>
+    <string name="pref_summary_sync_calendar">Permet les alertes de calendrier, m锚me en cas de d茅connexion</string>
+    <string name="pref_title_sync_caldendar">Sync. calendrier des 茅v猫nements</string>
+    <string name="hr_widget_heart_rate">Fr茅quence cardiaque</string>
+    <string name="hr_widget_steps">Pas</string>
+    <string name="hr_widget_date">Date</string>
+    <string name="hr_widget_active_minutes">Les minutes actives</string>
+    <string name="hr_widget_calories">Calories</string>
+    <string name="hr_widget_battery">Batterie</string>
+    <string name="hr_widget_weather">M茅t茅o</string>
+    <string name="hr_widget_nothing">Rien</string>
+    <string name="find_lost_device_you_found_it">Trouv茅 !</string>
+    <string name="pref_title_force_white_color_scheme">Forcer le noir sur le blanc</string>
+    <string name="pref_summary_force_white_color_scheme">Utile si votre montre a les mains noires</string>
+    <string name="hr_appname_commute">Trajet</string>
+    <string name="hr_appname_stopwatch">Chronom猫tre</string>
+    <string name="hr_appname_workout">Entra卯nement</string>
+    <string name="hr_appname_wellness">Bien-锚tre</string>
+    <string name="notification_channel_high_priority_name">Haute priorit茅</string>
+    <string name="devicetype_amazfit_bips">Amazfit Bip S</string>
+    <string name="find_my_phone_notification">Trouver mon t茅l茅phone</string>
+    <string name="pref_title_vibration_strength">Puissance du vibreur</string>
+    <string name="pref_title_relax_firmware_checks">Activez cette option si vous souhaitez flasher un microprogramme qui n\'est pas destin茅 脿 votre appareil (脿 vos propres risques)</string>
+    <string name="pref_summary_relax_firmware_checks">Assouplir les contr么les des microprogrammes</string>
+    <string name="pref_summary_custom_deviceicon">Afficher une ic么ne de notification Android sp茅cifique 脿 l\'appareil 脿 la place de l\'ic么ne Gadgetbridge lors de la connexion</string>
+    <string name="pref_title_custom_deviceicon">Afficher l鈥檌c么ne de notification sp茅cifique 脿 l鈥檃ppareil</string>
+    <string name="pref_qhybrid_title_widget_draw_circles">Widget tra莽ant des cercles</string>
+    <string name="pref_header_auto_fetch">R茅cup茅ration automatique</string>
+    <string name="pref_summary_notifications_and_calls_enable_misscall">Se r茅p猫te chaque minute</string>
+    <string name="devicetype_watchxplus">Watch X Plus</string>
+    <string name="devicetype_watchx">Watch X</string>
+    <string name="menuitem_hr">Fr茅quence cardiaque</string>
+    <string name="menuitem_pai">PAI</string>
+    <string name="menuitem_eventreminder">Rappel d\'茅v猫nement</string>
+    <string name="menuitem_workout">Entra卯nement</string>
+    <string name="czesh">Tch猫que</string>
+    <string name="swedish">Su茅dois</string>
+    <string name="hebrew">H茅breu</string>
+    <string name="greek">Grec</string>
+    <string name="hungarian">Hongrois</string>
+    <string name="romanian">Roumain</string>
+    <string name="title_activity_LenovoWatch_calibration">Calibrage de Watch X Plus</string>
+    <string name="power_mode_saving">脡conomie d\'茅nergie</string>
+    <string name="power_mode_normal">Normal</string>
+    <string name="prefs_sensors_button_bp_calibration_sum">Appuyez ici pour commencer le calibrage</string>
+    <string name="pref_sensors_bp_calibration_low">Tension art茅rielle DIASTOLIQUE (basse)</string>
+    <string name="pref_sensors_bp_calibration">Calibrage de la tension art茅rielle</string>
+    <string name="prefs_sensors_button_bp_calibration">Calibrage</string>
+    <string name="pref_sensors_bp_calibration_high">Tension art茅rielle SYSTOLIQUE (茅lev茅e)</string>
+    <string name="pref_title_sensors_altitude">Calibrage de l\'altitude</string>
+    <string name="pref_header_sensors_calibration">Calibrage du capteur</string>
+    <string name="pref_title_device_spec_settings_force_time">Forcer l\'heure de synchronisation</string>
+    <string name="pref_header_device_spec_settings">Param猫tres de l\'appareil</string>
+    <string name="prefs_notifications_and_calls_shake_reject">Secouer le poignet pour ignorer/rejeter un appel</string>
+    <string name="pref_summary_notifications_and_calls_title_reject">D茅sactiv茅 鈥� ignorer, Activ茅 鈥� rejeter</string>
+    <string name="prefs_notifications_and_calls_reject">Bouton ignorer/rejeter l\'appel</string>
+    <string name="pref_header_notifications_and_calls_callhandling">Traitement des appels</string>
+    <string name="pref_title_notifications_and_calls_repeat_on_misscall">R茅p茅ter pendant X minutes</string>
+    <string name="pref_notifications_and_calls_enable_misscall">Notifier un appel manqu茅</string>
+    <string name="prefs_notifications_and_calls_continious_ring">Notification durant la sonnerie</string>
+    <string name="pref_title_notifications_and_calls_repeat_on_call">R茅p茅ter la notification d\'appel</string>
+    <string name="pref_header_notifications_and_calls">Notifications et appels</string>
+    <string name="pref_theme_system">Syst猫me</string>
+    <string name="pref_summary_device_spec_settings_title_force_time">Forcer la synchronisation d猫s la reconnexion. Les montres analogiques peuvent montrer une heure incorrecte鈥�!</string>
+    <string name="pref_summary_notifications_and_calls_title_shake_reject">Action du bouton en doublons</string>
+    <string name="hr_widget_last_notification">Derni猫re notification</string>
+    <string name="pref_qhybrid_save_raw_activity_files">Enregistrer les donn茅es RAW (brutes) des activit茅s</string>
+    <string name="menuitem_unknown">Inconnu</string>
+    <string name="bip_prefs_shotcuts_summary">Choisir les 茅l茅ments apparaissant dans le menu</string>
+    <string name="bip_prefs_shortcuts">脡l茅ments du menu</string>
+    <string name="power_mode_watch">Seulement l\'heure</string>
+    <string name="power_mode_title">Mode d\'alimentation de la montre</string>
+    <string name="pref_title_device_spec_settings_show_raw_graph">Montrer les donn茅es RAW (brutes) sur le graphique d\'activit茅</string>
+    <string name="controlcenter_set_alias">D茅finir un alias</string>
+    <string name="laps">Tours</string>
+    <string name="maxPace">Rythme maximum</string>
+    <string name="minPace">Vitesse minimum</string>
+    <string name="backstroke">Dos crawl茅</string>
+    <string name="maxSpeed">Maximum</string>
+    <string name="medley">Medley</string>
+    <string name="prefs_events_forwarding_fellsleep">Au sommeil</string>
+    <string name="prefs_events_forwarding_title">Actions de l\'appareil</string>
+    <string name="prefs_events_forwarding_summary">Utiliser les 茅v猫nements de l\'appareil pour d茅clencher des actions et des messages diffus茅 Android</string>
+    <string name="menuitem_cycles">Suivi des Cycles f茅minins</string>
+    <string name="freestyle">Style libre</string>
+    <string name="breaststroke">Rythme mammaire</string>
+    <string name="activity_filter_to_placeholder">aujourd\'hui</string>
+    <string name="activity_filter_from_placeholder">pass茅 lointain</string>
+    <string name="activity_summaries_all_devices">Tous les appareils</string>
+    <string name="sports_activity_quick_filter_select">Intervalle de temps</string>
+    <string name="sports_activity_quick_filter_30days">30 jours</string>
+    <string name="sports_activity_quick_filter_7days">7 jours</string>
+    <string name="sports_activity_quick_filter_last_month">Mois pr茅c茅dent</string>
+    <string name="sports_activity_quick_filter_this_month">Mois en cours</string>
+    <string name="sports_activity_quick_filter_last_week">Semaine pr茅c茅dente</string>
+    <string name="sports_activity_quick_filter_this_week">Cette semaine</string>
+    <string name="activity_summaries_all_activities">Toutes les activit茅s</string>
+    <string name="activity_summaries_statistics">Statistiques</string>
+    <string name="no">Non</string>
+    <string name="yes">Oui</string>
+    <string name="activity_filter_individual_items">脡l茅ments s茅lectionn茅s individuellement</string>
+    <string name="addto_filter">Ajouter au filtre</string>
+    <string name="activity_filter_apply_filter">Appliquer les filtres</string>
+    <string name="activity_filter_name_contains">脡tiquette</string>
+    <string name="activity_filter_filter_title">Filtre</string>
+    <string name="activity_filter_reset_filter">Effacer les filtres</string>
+    <string name="activity_filter_date_to">脌</string>
+    <string name="activity_filter_date_from">De</string>
+    <string name="pref_header_statistics">Statistiques sur les activit茅s sportives</string>
+    <string name="pref_header_filter">Filtre sur les activit茅s sportives</string>
+    <string name="prefs_events_forwarding_action_title">Ex茅cuter l\'action</string>
+    <string name="prefs_events_forwarding_broadcast_title">Message diffus茅</string>
+    <string name="prefs_events_forwarding_startnonwear">Au non port</string>
+    <string name="prefs_events_forwarding_wokeup">Au r茅veil</string>
+    <string name="activity_detail_show_gps_label">Montrer la trace GPS</string>
+    <string name="activity_detail_duration_label">Dur茅e</string>
+    <string name="activity_detail_end_label">Fin</string>
+    <string name="activity_detail_start_label">D茅part</string>
+    <string name="Steps">Pas</string>
+    <string name="Activity">Activit茅</string>
+    <string name="Speed">Vitesse</string>
+    <string name="Elevation">Altitude</string>
+    <string name="Distance">Distance</string>
+    <string name="Swimming">Natation</string>
+    <string name="Strokes">Rythmes</string>
+    <string name="km">km</string>
+    <string name="bpm">bpm</string>
+    <string name="minutes_km">min/km</string>
+    <string name="seconds_m">sec/m</string>
+    <string name="seconds_km">sec/km</string>
+    <string name="calories_unit">kcal</string>
+    <string name="laps_unit">interm茅diaires</string>
+    <string name="swim_style">type de nage</string>
+    <string name="swolf_index">index swolf</string>
+    <string name="seconds">sec</string>
+    <string name="strokes_unit">rythme</string>
+    <string name="strokes_second">rythme/s</string>
+    <string name="km_h">km/h</string>
+    <string name="meters_second">m/s</string>
+    <string name="steps_unit">pas</string>
+    <string name="cm">cm</string>
+    <string name="meters">m</string>
+    <string name="averageSpeed">Vitesse moyenne</string>
+    <string name="baseAltitude">Altitude de d茅part</string>
+    <string name="flatSeconds">Plat</string>
+    <string name="ascentSeconds">Ascendant</string>
+    <string name="swimStyle">Type de nage</string>
+    <string name="swolfIndex">SWOLF</string>
+    <string name="averageLapPace">Moyenne de pause</string>
+    <string name="averageStrokesPerSecond">Foul茅es moyennes</string>
+    <string name="averageStrokeDistance">Distance moyenne de foul茅e</string>
+    <string name="averageStride">Foul茅e moyenne</string>
+    <string name="averageKMPaceSeconds">Rythme</string>
+    <string name="averageHR">Rythme cardiaque</string>
+    <string name="totalStride">Foul茅es totales</string>
+    <string name="caloriesBurnt">Calories</string>
+    <string name="activeSeconds">Actif</string>
+    <string name="steps">Pas</string>
+    <string name="minAltitude">Minimum</string>
+    <string name="maxAltitude">Maximum</string>
+    <string name="descentMeters">Descente</string>
+    <string name="ascentMeters">Mont茅e</string>
+    <string name="distanceMeters">Distance</string>
+    <string name="error_location_enabled_mandatory">La localisation doit 锚tre activ茅e pour scanner les appareils</string>
+    <string name="ignore_bonded_devices_description">Activer cette option ignorera les appareils d茅ja li茅s/pair茅s lors d\'un scan</string>
+    <string name="ignore_bonded_devices">Ignorer les appareils d茅ja li茅s</string>
+    <string name="error_retrieving_devices_database">Erreur lors de la r茅cup茅ration des appareils de la base de donn茅es</string>
+    <string name="error_setting_alias">Erreur pour configurer l\'alias:</string>
+    <string name="error_exporting_device_preferences">Erreur 脿 l\'exportation des pr茅f茅rences sp茅cifiques de cet appareil</string>
+    <string name="pref_check_permission_status_summary">Demander et v茅rifier les autorisations manquantes m锚me si elles ne sont pas imm茅diatement n茅cessaires. D茅sactiver uniquement si votre appareil ne supporte pas ces fonctions. Refuser une autorisation peut provoquer des probl猫mes !</string>
+    <string name="pref_check_permission_status">V茅rifier l\'茅tat des autorisations</string>
+    <string name="error_background_service_reason">Le d茅marrage du service en arri猫re-plan n\'a pas r茅ussi 脿 cause d\'une exception - cliquer ici pour plus d\'informations.\n\nErreur:</string>
+    <string name="device_requires_key">CL脡聽N脡CESSAIRE, APPUI LONG POUR聽ENTRER</string>
+    <string name="device_is_currently_bonded">D脡JA聽LI脡</string>
+    <string name="error_background_service_reason_truncated">Le d茅marrage du service en arri猫re-plan n\'a pas r茅ussi 脿 cause de鈥�</string>
+    <string name="error_background_service">Impossible de d茅marrer le service d\'arri猫re-plan</string>
+    <string name="companiondevice_pairing_details">Active le support de la nouvelle API CompanionDevice (n\'a d\'effet qu\'avec Android 8 ou sup茅rieur) ce qui am茅liore la fiabilit茅 si le service doit 锚tre red茅marr茅 en t芒che de fond, n茅cessite de refaire le pairage avec GadgetBridge pour 锚tre effectif</string>
+    <string name="companiondevice_pairing">Pairage du bracelet</string>
+    <string name="require_location_provider">La g茅olocalisation doit 锚tre activ茅e</string>
+    <string name="error_version_check_extreme_caution">ATTENTION: Erreur lors de la v茅rification de l\'information de version !聽Vous ne devriez pas poursuivre ! Num茅ro de version \"%s\"</string>
+    <string name="permission_granting_mandatory">Toutes ces permissions sont n茅cessaires et une instabilit茅 peut se produire si elles ne sont pas accord茅es</string>
+    <string name="about_links">Liens</string>
+    <string name="about_additional_contributions">Nombreux remerciements 脿 tous les participants non indiqu茅s pour la contribution au code, les traductions, le support, les id茅es, la motivation, les rapports de bugs, les soutiens financiers鈥� 鉁�</string>
+    <string name="about_additional_device_support">Support d\'appareil suppl茅mentaire</string>
+    <string name="about_contributors">Contributeurs</string>
+    <string name="about_core_team_title">脡quipe principal (dans l\'ordre de la premi猫re contribution au code)</string>
+    <string name="about_description_generic">Un rempla莽ant libre et sans cloud aux applications Android propri茅taires des fabricants de vos bracelets.</string>
+    <string name="about_activity_title_generic">脌 propos de Gadgetbridge</string>
+    <string name="about_title">脌 propos</string>
+    <string name="menuitem_worldclock">Horloge mondiale</string>
+    <string name="menuitem_stress">Stress</string>
+    <string name="menuitem_breathing">Respiration</string>
+    <string name="devicetype_sg2">Lemfo SG2</string>
+    <string name="devicetype_pinetime_jf">PineTime (Micrologiciel JF)</string>
+    <string name="devicetype_tlw64">TLW64</string>
+    <string name="devicetype_amazfit_trex">Amazfit T-Rex</string>
+    <string name="devicetype_miband5">Mi Band 5</string>
+    <string name="activity_summary_yesterday">Hier</string>
+    <string name="activity_summary_today">Aujourd\'hui</string>
+    <string name="activity_summary_edit_name_title">Modifier l\'茅tiquette</string>
+    <string name="activity_summary_detail">D茅tail de l\'activit茅 sportive</string>
+    <string name="activity_type_badminton">Badminton</string>
+    <string name="activity_type_pingpong">Ping-pong</string>
+    <string name="activity_type_basketball">Basketball</string>
+    <string name="activity_type_cricket">Cricket</string>
+    <string name="activity_type_rowing_machine">Aviron</string>
+    <string name="activity_type_soccer">Football</string>
+    <string name="activity_type_yoga">Yoga</string>
+    <string name="activity_type_jump_roping">Corde 脿 sauter</string>
+    <string name="activity_type_elliptical_trainer">Entra卯neur elliptique</string>
+    <string name="activity_type_indoor_cycling">V茅lo d\'int茅rieur</string>
+    <string name="activity_type_swimming_openwater">Natation (en eau libre)</string>
+    <string name="pref_title_weather_summary">Utilis茅 par le fournisseur m茅t茅o de LineageOS, les autres versions d\'Android doivent utiliser une application comme Weather notification. Se reporter au wiki Gadgetbridge.</string>
+    <string name="fw_upgrade_notice_miband5">Vous 锚tes sur le point d\'installer le micrologiciel %s dans votre Mi Band 5.
+\n
+\nVeuillez installer le fichier .fw, puis le fichier .res. Votre montre red茅marrera apr猫s avoir install茅 le fichier .fw.
+\n
+\nRemarque : Vous n\'avez pas besoin d\'installer le fichier .res si c\'est exactement le m锚me que celui pr茅c茅demment install茅.
+\n
+\nCONTINUEZ聽脌 VOS聽RISQUES聽ET聽P脡RILS !</string>
+    <string name="fw_upgrade_notice_amazfit_trex">Vous 锚tes sur le point d\'installer le micrologiciel %s sur votre Amazfit T-Rex.
+\n
+\nVeuillez installer le fichier .fw, puis le fichier .res et finalement le fichier .gps. Votre montre red茅marrera apr猫s l\'installation du .fw.
+\n
+\nNote : vous n\'avez pas 脿 installer les fichier .res et .gps si ceux ci sont exactement les m锚me qu\'install茅 pr茅c茅demment.
+\n
+\n脌 VOS RISQUES ET P脡RILS !</string>
+    <string name="activity_prefs_chart_min_session_length">Dur茅e d\'activit茅 minimale (minutes)</string>
+    <string name="activity_prefs_chart_max_idle_phase_length">Longueur de la pause pour s茅parer les activit茅s (minutes)</string>
+    <string name="activity_prefs_chart_min_steps_per_minute">Nombre de pas minimum par minute pour d茅tecter une activit茅</string>
+    <string name="activity_prefs_chart_min_steps_per_minute_for_run">Nombre de pas minimum par minute pour d茅tecter une course</string>
+    <string name="charts_activity_list">Activit茅s du jour</string>
+    <string name="chart_get_active_and_synchronize">Avoir une activit茅 et synchroniser l\'appareil.</string>
+    <string name="chart_no_active_data">Aucune activit茅 d茅tect茅e.</string>
+    <string name="devicetype_lefun">Lefun</string>
+    <string name="lefun_prefs_interface_language_title">langue de l\'interface</string>
+    <string name="lefun_prefs_antilost_summary">Le bracelet vibrera si votre t茅l茅phone se d茅connecte du bracelet</string>
+    <string name="lefun_prefs_antilost_title">Anti-perte</string>
+    <string name="lefun_prefs_hydration_reminder_interval_title">Intervalle de rappel d\'hydratation (en minutes)</string>
+    <string name="lefun_prefs_hydration_reminder_summary">Le bracelet vibrera pour vous rappeler de boire de l\'eau</string>
+    <string name="lefun_prefs_hydration_reminder_title">Rappel d\'hydratation</string>
+    <string name="devicetype_sonyswr12">Sony SWR12</string>
+    <string name="sonyswr12_settings_title">Param猫tres Sony SWR12</string>
+    <string name="sonyswr12_settings_low_vibration">Faible vibration activ茅e</string>
+    <string name="sonyswr12_settings_stamina">Le mode d\'茅conomie d\'茅nergie est activ茅</string>
+    <string name="sonyswr12_settings_alarm_interval">Intervalle d\'alarme intelligente en minutes</string>
+    <string name="sonyswr12_settings_low_vibration_summary">Permettre une faible intensit茅 de vibration sur la bande</string>
+    <string name="sonyswr12_settings_stamina_summary">Le mode d\'茅conomie d\'茅nergie d茅sactive la mesure automatique p茅riodique de la fr茅quence cardiaque augmente ainsi le temps de travail</string>
+    <string name="sonyswr12_settings_alarm_interval_summary">L\'intervalle d\'alarme intelligente est l\'intervalle avant l\'alarme install茅e. Dans cet intervalle, l\'appareil tente de d茅tecter la phase de sommeil la plus l茅g猫re pour r茅veiller l\'utilisateur</string>
+    <string name="devicetype_nut_mini">Nut mini</string>
+    <string name="descentSeconds">Descendant</string>
+    <string name="activity_error_no_app_for_png">Pour partager cette capture 茅cran, installer une application pour g茅rer les fichiers images.</string>
+    <string name="firmware_update_progress">Envoi en cours
+\n%1d%% 脿 %.2fkbps (moyenne %.2fkbps)
+\n脡l茅ment %1d de %1d</string>
+    <string name="devicestatus_upload_failed">L\'envoi n\'a pas r茅ussi</string>
+    <string name="devicestatus_upload_aborted">L\'envoi a avort茅 !</string>
+    <string name="devicestatus_upload_validating">L\'envoi est en cours de validation</string>
+    <string name="devicestatus_upload_completed">L\'envoi est termin茅</string>
+    <string name="devicestatus_disconnected">Appareil d茅connect茅 !</string>
+    <string name="devicestatus_disconnecting">L\'appareil se d茅connecte !</string>
+    <string name="devicestatus_upload_started">L\'envoi a d茅marr茅</string>
+    <string name="devicestatus_upload_starting">L\'envoi d茅marre</string>
+    <string name="devicestatus_connected">Appareil connect茅</string>
+    <string name="devicestatus_connecting">Connexion 脿 l\'apparel</string>
+    <string name="pref_title_lower_button_function_double">Double appui bouton du bas</string>
+    <string name="pref_title_middle_button_function_double">Double appui bouton du milieu</string>
+    <string name="pref_title_upper_button_function_double">Double appui bouton du haut</string>
+    <string name="pref_title_lower_button_function_long">Appui long bouton du bas</string>
+    <string name="pref_title_middle_button_function_long">Appui long bouton du milieu</string>
+    <string name="pref_title_upper_button_function_long">Appui long bouton du haut</string>
+    <string name="pref_title_lower_button_function_short">Appui court bouton du bas</string>
+    <string name="pref_title_middle_button_function_short">Appui court bouton du milieu</string>
+    <string name="pref_title_upper_button_function_short">Appui court bouton du haut</string>
+    <string name="devicetype_amazfit_band5">Amazfit Band 5</string>
+    <string name="activity_prefs_step_length_cm">Longueur de pas en cm</string>
+    <string name="menuitem_spo2">SpO2</string>
+    <string name="qhybrid_calibration_align_hint">Utiliser les boutons ci-dessous pour aligner les aiguilles du bracelet sur midi.</string>
+    <string name="gps_track">Trace GPS</string>
+    <string name="about_version">Version %s</string>
+    <string name="devicetype_amazfit_bips_lite">Amazfit Bip S Lite</string>
+    <string name="charts_legend_heartrate_average">Rythme cardiaque moyen</string>
+    <string name="charts_min_max_heartrate_popup">Rythme cardiaque le plus bas鈥�: %1$d
+\nRythme cardiaque le plus 茅lev茅鈥�: %2$d
+\nIntensit茅 du mouvement鈥�: %3$s</string>
+    <string name="find_lost_device_message">Rechercher %1$s \?</string>
+    <string name="devicetype_amazfit_gtr2">Amazfit GTR 2</string>
+    <string name="menuitem_stopwatch">脡tape</string>
+    <string name="menuitem_dnd">NPD</string>
+    <string name="menuitem_alexa">Alexa</string>
+    <string name="menuitem_takephoto">Appareil photo 脿 distance</string>
+    <string name="menuitem_mutephone">T茅l茅phone muet</string>
+    <string name="menuitem_findphone">Trouver mon t茅l茅phone</string>
+    <string name="movement_intensity">Intensit茅 du mouvement</string>
+    <string name="activity_list_summary_activities">Activit茅s</string>
+    <string name="activity_list_summary_intensity">Mouvement
+\nIntensit茅</string>
+    <string name="activity_list_summary_active_time">Dur茅e d\'activit茅</string>
+    <string name="activity_list_summary_distance">Distance</string>
+    <string name="activity_list_summary_active_steps">Pas r茅els</string>
+    <string name="devicetype_amazfit_gts2">Amazfit GTS 2</string>
+    <string name="devicetype_casiogbx100">Casio GBX-100</string>
+    <string name="prefs_charts_tabs_summary">Onglets visibles des tableaux</string>
+    <string name="prefs_charts_tabs">Onglets des Tableaux</string>
+    <string name="devicetype_amazfit_bipu">Amazfit Bip U</string>
+    <string name="menuitem_pomodoro">Tracker Pomodoro</string>
+    <string name="menuitem_sleep">Sommeil</string>
+    <string name="menuitem_goal">Objectif d\'activit茅</string>
+    <string name="pref_summary_connected_advertisement">Rend l\'appareil d茅couvrable via Bluetooth m锚me si connect茅</string>
+    <string name="pref_title_connected_advertisement">Visible si connect茅</string>
+    <string name="prefs_autoremove_message">D茅sactiver automatiquement les notifications SMS</string>
+    <string name="prefs_fake_ring_duration">Simulation de sonnerie permanente</string>
+    <string name="prefs_operating_sounds">Sons en fonctionnement</string>
+    <string name="prefs_key_vibration">Mode de vibration</string>
+    <string name="prefs_autolight">脡clairage automatique</string>
+    <string name="discovery_entered_invalid_authkey">La cl茅 secr猫te d\'identification saisie est invalide !聽Faire un appui long sur l\'appareil pour la modifier.</string>
+    <string name="devicetype_amazfit_vergel">Amazfit Verge Lite</string>
+    <string name="fw_upgrade_notice_amazfitvergel">Vous 锚tes sur le point d\'installer le firmware %s dans votre Amazfit Verge Lite.
+\n
+\nAssurez-vous d\'installer le fichier .fw, puis le fichier .res, et pour finir le fichier .gps. Votre montre va red茅marrer apr猫s l\'installation du fichier .fw.
+\n
+\nRemarque鈥�: Vous n\'avez pas besoin d\'installer les fichiers .res et .gps si ce sont exactement les m锚mes fichiers que pr茅c茅demment.
+\n
+\nMANIPULATION 脌 VOS聽RISQUES聽ET聽P脡RILS鈥�!</string>
+    <string name="activity_DB_ShowContentButton">Montrer le contenu du r茅pertoire d\'Import/Export</string>
+    <string name="activity_data_management_directory_content_title">Exporter/Importer le contenu du r茅pertoire</string>
+    <string name="widget_settings_select_device_title">Choisir l\'appareil</string>
+    <string name="dbmanagementactivity_export_confirmation">Exporter r茅ellement les donn茅es \?聽Les donn茅es et les pr茅f茅rences pr茅c茅demment export茅es (s\'il y en a) seront 茅cras茅es.</string>
+    <string name="dbmanagementactivity_export_data_title">Exporter les Donn茅es \?</string>
+    <string name="devicetype_amazfit_bipupro">Amazfit Bip U Pro</string>
+    <string name="dbmanagementactivity_export_finished">Suppression termin茅e</string>
+    <string name="activity_db_management_clean_export_directory_text">Les fichiers export茅s dans le r茅pertoire d\'Import/Export sont accessibles depuis n\'importe quelle application sur votre appareil. Il est pr茅f茅rable de supprimer ces fichiers apr猫s synchronisation ou sauvegarde. Assurez-vous d\'avoir une sauvegarde avant de supprimer les fichiers. Les fichiers GPX, les sous-r茅pertoires, et les fichiers de base de donn茅es auto-export茅s (s\'ils existent) ne seront pas supprim茅s. Le chemin du r茅pertoire d\'Import/Export est鈥�:</string>
+    <string name="activity_DB_clean_export_directory_warning_message">Vraiment effacer les fichiers dans le r茅pertoire d\'Import/Export \?</string>
+    <string name="activity_DB_clean_export_directory_warning_title">Effacer les fichiers dans le r茅pertoire d\'Import/Export \?</string>
+    <string name="activity_db_management_clean_export_directory_label">Effacer les fichiers dans le r茅pertoire d\'Import/Export</string>
+    <string name="dbmanagementactivity_error_cleaning_export_directory">Erreur lors de l\'effacement des fichiers du r茅pertoire d\'exportation %1$s</string>
+    <string name="devicetype_amazfit_neo">Amazfit Neo</string>
+    <string name="activity_type_strength_training">Entrainement de Puissance</string>
+    <string name="mi">miles</string>
+    <string name="minutes_mi">min/miles</string>
+    <string name="mi_h">miles/h</string>
+    <string name="ft">pieds</string>
+    <string name="devicetype_amazfit_gts2_mini">Amazfit GTS 2 Mini</string>
+    <string name="devicetype_zepp_e">Zepp E</string>
+    <string name="devicetype_amazfit_gtr2e">Amazfit GTR 2e</string>
+    <string name="devicetype_waspos">Wasp-os</string>
+    <string name="battery_detail_activity_title">Info. Batterie</string>
+    <string name="devicetype_amazfit_gts2e">Amazfit GTS 2e</string>
+    <string name="devicetype_amazfit_x">Amazfit X</string>
+    <string name="fw_upgrade_notice_amazfitx">Vous allez installer le microcode %s dans votre Amazfit X.
+\n
+\nAssurez-vous d\'abord d\'installer le fichier .fw, puis ensuite le fichier .res. Votre bracelet va red茅marrer apr猫s l\'installation du fichier .fw.
+\n
+\nRemarque鈥�: Vous n\'avez pas 脿 r茅installer le fichiers .res si c\'est exactement le m锚me que vous avez pr茅c茅demment install茅.
+\n
+\nA VOS聽RISQUES聽ET聽P脡RILS鈥�!</string>
+    <string name="calendar_year">Ann茅e</string>
+    <string name="calendar_six_months">Semestre</string>
+    <string name="calendar_month">Mois</string>
+    <string name="calendar_two_weeks">Deux semaines</string>
+    <string name="calendar_week">Semaine</string>
+    <string name="calendar_day">Jour</string>
+    <string name="battery_level">Niveau de batterie</string>
+    <string name="show_ongoing_activity">Afficher une popup pour l\'activit茅 en cours</string>
+    <string name="dialog_hide">Cacher</string>
+    <string name="calendar_three_months">3 mois</string>
+    <string name="fossil_hr_auth_failed">Authentification 茅chou茅e, fonctionnalit茅s limit茅es</string>
+    <string name="fossil_hr_unavailable_unauthed">Indisponible en mode non authentifi茅</string>
+    <string name="fossil_hr_synced_activity_data">Donn茅es d\'activit茅 synchronis茅es</string>
+    <string name="fossil_hr_commute_processing">En cours de traitement鈥�</string>
+    <string name="qhybrid_title_calibration">Calibration</string>
+    <string name="qhybrid_title_apps_management">Gestion des apps</string>
+    <string name="qhybrid_title_file_management">Gestion de fichiers</string>
+    <string name="qhybrid_title_background_image">Image de fond</string>
+    <string name="qhybrid_title_apps">Apps</string>
+    <string name="devicetype_um25">UM-25</string>
+    <string name="fossil_hr_warning_firmware_too_new">Certaines fonctions sont d茅sactiv茅es en raison de la version trop r茅cente du micrologiciel du bracelet</string>
+    <string name="prefs_sounds_summary">Configurer une fois que l\'appareil a bip茅</string>
+    <string name="prefs_sounds">Sons</string>
+    <string name="fw_upgrade_notice_amazfitneo">Vous 锚tes sur le point d\'installer le micrologiciel %s dans votre Amazfit Neo.
+\n
+\n- Votre bracelet va red茅marrer apr猫s l\'installation du micrologiciel.
+\n
+\nA聽VOS聽RISQUES ET P脡RILS !</string>
+    <string name="menuitem_temperature">Temp茅rature</string>
+    <string name="menuitem_widgets">Widgets</string>
+    <string name="menuitem_events">脡v猫nements</string>
+    <string name="pref_summary_canned_messages_dismisscall">Refuser les appels depuis la montre avec un message SMS</string>
+    <string name="pref_summary_canned_messages_set">Envoyer le message saisi ci-dessous vers votre bracelet</string>
+    <string name="qhybrid_summary_calibration">Calibrer les aiguilles de la montre</string>
+    <string name="qhybrid_summary_file_management">Envoyer et recevoir des fichiers</string>
+    <string name="pref_summary_physical_buttons">Configurer les fonctions des boutons physiques du bracelet</string>
+    <string name="pref_title_physical_buttons">Boutons physiques</string>
+    <string name="devicetype_miband6">Mi Band 6</string>
+    <string name="kind_agps_bundle">Bundle AGPS</string>
+    <string name="fw_upgrade_notice_miband6">Vous 锚tes sur le point d\'installer le micro-logiciel %s dans votre Mi Band 6.
+\n
+\nAssurez-vous d\'installer le fichier .fw, et puis ensuite le fichier .res. Votre bracelet red茅marrera apr猫s l\'installation du fichier .fw.
+\n
+\nRemarque. Vous n\'avez pas besoin d\'installer le fichier .res si c\'est exactement le m锚me que celui pr茅c茅demment install茅.
+\n
+\nA聽VOS聽RISQUES聽ET聽P脡RILS !</string>
+    <string name="watchface_install_info">Vous 锚tes sur le point d\'installer le cadran suivant鈥�:
+\n
+\n%1$s
+\nVersion %2$s par %3$s
+\n</string>
+    <string name="qhybrid_watchface_configuration_old_firmware">Configuration du cadran pour les bracelets dont le micro-logiciel est en version DN1.0.2.19r ou inf茅rieur</string>
+    <string name="qhybrid_calibration_100_steps">100 pas</string>
+    <string name="qhybrid_calibration_10_steps">10 pas</string>
+    <string name="qhybrid_calibration_1_step">1 pas</string>
+    <string name="qhybrid_calibration_clockwise">Dans le sens horaire</string>
+    <string name="qhybrid_calibration_counterclockwise">Dans le sens anti-horaire</string>
+    <string name="fossil_hr_new_action">Nouvelle action</string>
+    <string name="fossil_hr_new_action_cancel">annuler</string>
+    <string name="fossil_hr_edit_action">Modifier l\'action</string>
+    <string name="fossil_hr_edit_action_delete">supprimer</string>
+    <string name="fossil_hr_commute_actions_explanation">Les actions configur茅es ici apparaitront dans l\'app Commute dans votre bracelet. Consulter le wiki pour plus d\'informations sur la gestion des intentions g茅n茅r茅s par ces actions.</string>
+    <string name="qhybrid_title_watchface">Configuration du cadran</string>
+    <string name="notification_channel_low_battery_name">Batterie faible</string>
+    <string name="notification_channel_transfer_name">Transfert de donn茅es</string>
+    <string name="qhybrid_pref_summary_external_intents">Autoriser d\'autres apps Android 脿 envoyer/茅craser des fichiers</string>
+    <string name="qhybrid_pref_title_external_intents">Autorise des intentions externes dangereux</string>
+    <string name="pref_summary_developer_settings">R茅glages et fonctionnalit茅s utilis茅s par les d茅veloppeurs</string>
+    <string name="pref_title_developer_settings">R茅glages D茅veloppeurs</string>
+    <string name="qhybrid_pref_summary_actions">Actions pour l\'app Commute</string>
+    <string name="qhybrid_pref_title_actions">Actions</string>
+    <string name="devicetype_amazfit_trex_pro">Amazfit T-Rex Pro</string>
+    <string name="menuitem_barometer">Barom猫tre</string>
+    <string name="pref_theme_black_background">Utiliser un fond sombre en mode Th猫me Sombre</string>
+    <string name="gpx_receiver_overwrite_some_files">Certain(s) fichier(s) existe(nt) d茅ja. Les 茅craser \?</string>
+    <string name="gpx_receiver_files_received">Fichier(s) GPX re莽u(s):</string>
+    <string name="gpx_receiver_activity_title">R茅cepteur GPX聽Gadgetbridge</string>
+    <string name="activity_summary_detail_editing_gpx_track">Modifier les traces GPX聽li茅es</string>
+    <string name="activity_summary_detail_clear_gpx_track">Effacer la trace GPX</string>
+    <string name="activity_summary_detail_select_gpx_track">Choisir une trace GPX</string>
+    <string name="set">Configurer</string>
+    <string name="watchface_setting_wrist_flick_duration">Dur茅e (en ms)</string>
+    <string name="watchface_setting_wrist_flick_minute">Grande aiguille (-360 脿 360)</string>
+    <string name="watchface_setting_wrist_flick_hour">Petite aiguille (-360 脿 +360)</string>
+    <string name="watchface_setting_wrist_flick_move_relative">D茅placement des mains en fonction du temps</string>
+    <string name="watchface_setting_desc_wrist_flick">(pour d茅sactiver compl猫tement, permettre le mouvement relatif et configurer toutes les valeurs 脿 0)</string>
+    <string name="watchface_setting_title_wrist_flick">Mouvement du poignet</string>
+    <string name="watchface_setting_display_refresh_partial">Rafra卯chissement partiel (en minutes)</string>
+    <string name="watchface_setting_display_refresh_full">Rafra卯chissement complet (en minutes)</string>
+    <string name="watchface_setting_title_display_refresh_timeout">Intervalle de rafra卯chissement de l\'茅cran</string>
+    <string name="watchface_dialog_title_settings">R茅glages du th猫me d\'茅cran</string>
+    <string name="watchface_dialog_widget_color_black">Noir</string>
+    <string name="watchface_dialog_widget_color_white">Blanc</string>
+    <string name="watchface_dialog_widget_color">Couleur</string>
+    <string name="qhybrid_title_watchface_designer">Concepteur de th猫me d\'茅cran</string>
+    <string name="watchface_dialog_widget_preset_right">Droite</string>
+    <string name="watchface_dialog_widget_preset_left">Gauche</string>
+    <string name="watchface_dialog_widget_preset_bottom">Bas</string>
+    <string name="watchface_dialog_widget_preset_top">Sommet</string>
+    <string name="watchface_dialog_widget_presets">R茅glages de position</string>
+    <string name="watchface_dialog_widget_y_coordinate">Coordonn茅e Y (max 240)</string>
+    <string name="watchface_dialog_widget_x_coordinate">Coordonn茅e X (max 240)</string>
+    <string name="watchface_dialog_widget_type">Genre</string>
+    <string name="appmanager_app_edit">Modifier</string>
+    <string name="button_watchface_save_apply">Enregistrer et appliquer</string>
+    <string name="button_watchface_preview">Aper莽u sur la montre</string>
+    <string name="watchface_widget_type_heart_rate">Rythme cardiaque</string>
+    <string name="watchface_widget_type_steps">Pas</string>
+    <string name="watchface_widget_type_weather">M茅t茅o</string>
+    <string name="watchface_widget_type_date">Date</string>
+    <string name="watchface_dialog_title_add_widget">Ajouter un widget</string>
+    <string name="watchface_toast_settings_incomplete">Configuration incompl猫te, widget non ajout茅</string>
+    <string name="watchface_dialog_title_set_name">Configurer le nom du th猫me d\'茅cran</string>
+    <string name="button_watchface_settings">R茅glages du th猫me d\'茅cran</string>
+    <string name="button_watchface_add_widget">Ajouter un widget</string>
+    <string name="button_watchface_select_image">Changer l\'image d\'arri猫re-plan</string>
+    <string name="button_watchface_edit_name">Modifier le nom</string>
+    <string name="devicetype_smaq2oss">SMA-Q2 OSS</string>
+    <string name="watchface_upload_failed">L\'envoi du th猫me d\'茅cran a 茅chou茅. Merci de r茅essayer.</string>
+    <string name="watchface_cache_confirm_overwrite">Un th猫me d\'茅cran avec le m锚me nom existe d茅ja dans le cache. Voulez-vous l\'茅craser \?</string>
+    <string name="heart_rate_result">R茅sultats des mesures</string>
+    <string name="getting_heart_rate">Mesure en cours</string>
+    <string name="blood_pressure">Pression art茅rielle</string>
+    <string name="watchface_setting_power_saving_hands">D茅sactiver les d茅tections de mouvement lorsque non port茅</string>
+    <string name="watchface_setting_power_saving_display">Suspendre les mises 脿 jour de l\'茅cran quand non port茅</string>
+    <string name="watchface_setting_title_power_saving">脡conomie d\'茅nergie</string>
+    <string name="watchface_widget_type_chance_rain">Risque de pluie</string>
+    <string name="watchface_widget_type_active_mins">Minutes actives</string>
+    <string name="watchface_widget_type_2nd_tz">Zone temps interm茅diaire</string>
+    <string name="watchface_widget_type_calories">Calories</string>
+    <string name="watchface_widget_type_battery">Batterie</string>
+    <string name="pref_summary_huami_force_new_protocol">脌 activer si votre appareil ne se connecte plus apr猫s une mise 脿 jour du micrologiciel</string>
+    <string name="pref_title_huami_force_new_protocol">Nouveau protocole d\'identification</string>
+    <string name="menuitem_flashlight">Flash</string>
+    <string name="prefs_autoheartrate_interval">Fr茅quence des mesures</string>
+    <string name="prefs_autoheartrate_sleep">Prendre des mesures pendant le sommeil</string>
+    <string name="prefs_autoheartrate_measurement">Mesures automatiques du rythme cardiaque</string>
+    <string name="prefs_autoheartrate_summary">Mesures p茅riodiques du rythme cardiaque durant la journ茅e et 茅galement pendant le sommeil</string>
+    <string name="prefs_autoheartrate">Rythme cardiaque automatique</string>
+    <string name="enable_notifications_summary">Notifications pour les appels, les messages et autres</string>
+    <string name="enable_vibrations_summary">Vibrant pour les appels, messages, notifications et autres</string>
+    <string name="prefs_notifications_enable">Activer les notifications</string>
+    <string name="prefs_vibration_enable">Activer le mode vibreur</string>
+    <string name="prefs_sleep_time_summary">Configurer les p茅riodes horaires durant lesquelles le sommeil est enregistr茅</string>
+    <string name="prefs_sleep_time_label">Configurer les heures de sommeil</string>
+    <string name="prefs_sleep_time">Dur茅es de sommeil</string>
+    <string name="devicetype_fitpro">FitPro</string>
+    <string name="pref_title_ping_tone">Sonnerie du Ping</string>
+    <string name="devicetype_nothingear1">Nothing Ear (1)</string>
+    <string name="nothing_prefs_inear_summary">Mettre en lecture/pause la musique selon si vous portez les 茅couteurs ou pas</string>
+    <string name="nothing_prefs_audiomode_title">Mode Audio</string>
+    <string name="nothing_prefs_inear_title">D茅tection In-Ear</string>
+    <string name="check_all_applications">Activer toutes les applications</string>
+    <string name="uncheck_all_applications">D茅sactiver toutes les applications</string>
+    <string name="pref_title_notification_use_as">Utiliser la liste des applications pour鈥�</string>
+    <string name="pref_title_notification_use_as_deny">Bloquer les notifications des apps s茅lectionn茅es</string>
+    <string name="pref_title_message_privacy_mode">Mode messages priv茅s</string>
+    <string name="pref_message_privacy_mode_off">Afficher tout le contenu</string>
+    <string name="pref_message_privacy_mode_complete">Cacher tout le contenu</string>
+    <string name="pref_applications_settings">Liste des applications</string>
+    <string name="toast_app_must_be_selected">L\'app doit 锚tre s茅lectionn茅e pour 锚tre configur茅e</string>
+    <string name="pref_title_notification_use_as_allow">Autoriser les notifications des apps s茅lectionn茅es</string>
+    <string name="title_activity_notification_management">Configuration des notifications</string>
+    <string name="pref_header_notification_application_settings">R茅glages par application</string>
+    <string name="toast_app_must_not_be_selected">L\'app ne doit pas 锚tre s茅lectionn茅e pour 锚tre configur茅e</string>
+    <string name="prefs_equalizer_preset">Pr茅r茅glage d\'茅galisation</string>
+    <string name="pref_title_equalizer_bass_boost">Renforcement des basses</string>
+    <string name="pref_title_equalizer_soft">Logiciel</string>
+    <string name="pref_title_equalizer_clear">Effacer</string>
+    <string name="pref_title_equalizer_trebble">Renforcement des aigus</string>
+    <string name="prefs_dolby_mode">Mode Dolby</string>
+    <string name="prefs_equalizer">脡galiseur</string>
+    <string name="prefs_equalizer_summary">Activer ou d茅sactiver l\'茅galiseur</string>
+    <string name="prefs_dolby_summary">Pr茅r茅glage Dolby pour l\'茅galiseur</string>
+    <string name="prefs_game_mode">Mode Jeu</string>
+    <string name="prefs_touch_lock">Verrouillage du tactile</string>
+    <string name="prefs_touch_lock_summary">D茅sactiver les 茅v猫nements tactiles</string>
+    <string name="prefs_galaxy_buds_experimental">Exp茅rimental</string>
+    <string name="prefs_ambient_volume">Volume ambiant</string>
+    <string name="prefs_ambient_voice_focus">Direction de la voix</string>
+    <string name="prefs_ambient_voice_summary">Rendre la voix intelligible</string>
+    <string name="prefs_ambient_sound">Son ambiant</string>
+    <string name="prefs_ambient_mode">Mode Ambiance</string>
+    <string name="prefs_left">Gauche</string>
+    <string name="prefs_right">Droite</string>
+    <string name="prefs_galaxy_touch_options">Options tactiles</string>
+    <string name="devicetype_galaxybuds">Galaxy Buds</string>
+    <string name="pref_title_equalizer_dynamic">Dynamique</string>
+    <string name="prefs_game_mode_summary">Uniquement si votre t茅l茅phone g猫re le mode jeu</string>
+    <string name="left_earbud">脡couteur gauche</string>
+    <string name="right_earbud">脡couteur droit</string>
+    <string name="battery_case">Bo卯tier batterie</string>
+    <string name="pref_header_other">Autre</string>
+    <string name="pref_header_system">Syst猫me</string>
+    <string name="pref_header_equalizer">脡qualisateur</string>
+    <string name="devicetype_galaxybuds_live">Galaxy Buds Live</string>
+    <string name="pref_title_equalizer_normal">Normal</string>
+    <string name="prefs_active_noise_cancelling">Activer l\'annulation de bruit</string>
+    <string name="sony_ambient_sound_off">Off</string>
+    <string name="sony_ambient_sound_noise_cancelling">Annulation de bruit</string>
+    <string name="sony_ambient_sound_wind_noise_reduction">R茅duction du bruit du vent</string>
+    <string name="sony_ambient_sound_ambient_sound">Son ambiant</string>
+    <string name="sony_ambient_sound_focus_voice">Focus sur la voix</string>
+    <string name="sony_ambient_sound_level">Niveau du son ambiant</string>
+    <string name="sony_sound_position">Position du son</string>
+    <string name="sony_sound_position_off">Off</string>
+    <string name="sony_sound_position_front">Avant</string>
+    <string name="sony_sound_position_front_left">Avant gauche</string>
+    <string name="sony_sound_position_front_right">Avant droit</string>
+    <string name="sony_sound_position_rear_left">Arri猫re gauche</string>
+    <string name="sony_sound_position_rear_right">Arri猫re droit</string>
+    <string name="sony_surround_mode">Mode Surround</string>
+    <string name="sony_surround_mode_off">Off</string>
+    <string name="sony_surround_mode_arena">Ar猫ne</string>
+    <string name="sony_surround_mode_club">Club</string>
+    <string name="sony_surround_mode_outdoor_stage">Sc猫ne ext茅rieure</string>
+    <string name="sony_surround_mode_concert_hall">Hall de concert</string>
+    <string name="sony_warn_sbc_codec">Avertissement鈥�: L\'茅qualiseur, la position audio et les r茅glages surrond ne s\'appliquent qu\'au codec audio SBC.</string>
+    <string name="sony_equalizer">Egalisateur</string>
+    <string name="sony_equalizer_preset_off">Off</string>
+    <string name="sony_equalizer_preset_bright">Lumineux</string>
+    <string name="sony_equalizer_preset_excited">Excit茅</string>
+    <string name="sony_equalizer_preset_mellow">Calme</string>
+    <string name="sony_equalizer_preset_relaxed">Relax</string>
+    <string name="sony_equalizer_preset_vocal">Vocal</string>
+    <string name="prefs_pressure_relief">R茅duction de la pression d没e au bruit ambiant</string>
+    <string name="pref_header_sony_ambient_sound_control">Contr么le du son ambiant</string>
+    <string name="sony_ambient_sound">Mode</string>
+    <string name="pressure_relief_summary">R茅duire la sensation de pression dans les oreilles lorsque l\'annulation de bruit est active</string>
+    <string name="devicetype_sony_wh_1000xm3">Sony WH-1000XM3</string>
+    <string name="prefs_active_noise_cancelling_summary">Bloquer les sons ext茅rieurs</string>
+    <string name="sony_equalizer_preset_treble_boost">Renfort des aigus</string>
+    <string name="sony_equalizer_preset_bass_boost">Renfort des graves</string>
+    <string name="sony_equalizer_preset_speech">Parole</string>
+    <string name="prefs_activity_in_device_card_title">Afficher l\'activit茅 sur la carte de l\'appareil</string>
+    <string name="prefs_activity_in_device_card_sleep_title">Sommeil</string>
+    <string name="prefs_activity_in_device_card_sleep_title_summary">Afficher la dur茅e du sommeil</string>
+    <string name="prefs_activity_in_device_card_steps_title_summary">Afficher le nombre total de pas</string>
+    <string name="sony_equalizer_preset_manual">Manuel</string>
+    <string name="sony_equalizer_preset_custom_1">Personnalis茅 1</string>
+    <string name="sony_equalizer_preset_custom_2">Personnalis茅 2</string>
+    <string name="pref_header_sony_equalizer_preset_custom_1">Configuration personnalis茅e 1</string>
+    <string name="sony_equalizer_band_400">400</string>
+    <string name="sony_equalizer_band_2500">2.5k</string>
+    <string name="sony_equalizer_band_6300">6.3k</string>
+    <string name="sony_equalizer_band_16000">16k</string>
+    <string name="sony_equalizer_clear_bass">Graves claires</string>
+    <string name="sony_touch_sensor">Contr么le du capteur tactile</string>
+    <string name="sony_notification_voice_guide">Notifications et guide audio</string>
+    <string name="sony_automatic_power_off">Extinction automatique</string>
+    <string name="sony_automatic_power_off_off">Ne pas 茅teindre</string>
+    <string name="sony_automatic_power_off_3_hour">3 heures</string>
+    <string name="device_card_activity_card_title_summary">Choisir les activit茅s affich茅es sur la carte de l\'appareil</string>
+    <string name="watchface_widget_type_custom">Widget personnalis茅</string>
+    <string name="pref_header_sony_equalizer_preset_custom_2">Configuration personnalis茅e 2</string>
+    <string name="device_card_activity_card_title">Info Activit茅 sur la carte de l\'appareil</string>
+    <string name="sony_automatic_power_off_30_min">30 minutes</string>
+    <string name="prefs_activity_in_device_card_title_summary">Afficher le nombre de pas, la distance ou le sommeil sur la carte de l\'appareil</string>
+    <string name="prefs_activity_in_device_card_distance_title_summary">La distance est calcul茅e d\'apr猫s le nombre de pas et la longueur des pas (configurable dans les R茅glages - A propos de vous(</string>
+    <string name="sony_equalizer_band_1000">1k</string>
+    <string name="sony_automatic_power_off_5_min">5 minutes</string>
+    <string name="sony_automatic_power_off_1_hour">1 heure</string>
+    <string name="sony_automatic_power_off_when_taken_off">Lorsqu\'茅teint</string>
+    <string name="prefs_fm_preset_instructions">Faire un appui long sur le bouton pour enregistrer cette configuration</string>
+    <string name="prefs_fm_presets_presets">Configurations</string>
+    <string name="watchface_dialog_widget_timeout_show_circle">Montrer le cercle en cas de perte de connexion</string>
+    <string name="watchface_dialog_widget_timezone">Fuseau horaire</string>
+    <string name="watchface_dialog_widget_timeout_hide_text">Cacher le texte en cas de perte de communication</string>
+    <string name="watchface_dialog_widget_update_timeout">Mettre 脿 jour le temps de d茅connexion en minutes</string>
+    <string name="qhybrid_title_on_device_confirmation">Activer la confirmation de pairage sur l\'appareil</string>
+    <string name="qhybrid_summary_on_device_confirmation">La confirmation de pairage sur l\'appareil peut 锚tre ennuyeuse. Sa d茅sactivation peut entrainer la perte de fonctions.</string>
+    <string name="title_activity_set_reminders">Configurer les rappels</string>
+    <string name="controlcenter_start_configure_reminders">Configurer les rappels</string>
+    <string name="reminder_repeat">R茅p茅ter</string>
+    <string name="reminder_date">Date</string>
+    <string name="reminder_time">Heure</string>
+    <string name="reminder_message">Message</string>
+    <string name="reminder_time_every_week">%1$s, toutes les semaines</string>
+    <string name="reminder_time_every_month">%1$s, tous les mois</string>
+    <string name="reminder_time_every_year">%1$s, tous les ans</string>
+    <string name="reminder_once">Une fois</string>
+    <string name="reminder_every_year">Annuel</string>
+    <string name="reminder_delete_confirm_title">Effacer le rappel</string>
+    <string name="reminder_delete_confirm_description">脢tes-vous s没r de vouloir effacer ce rappel \?</string>
+    <string name="reminder_no_free_slots_title">Pas d\'emplacement disponible</string>
+    <string name="reminder_time_once">%1$s, une fois</string>
+    <string name="reminder_time_every_day">%1$s, tous les jours</string>
+    <string name="reminder_every_day">quotidien</string>
+    <string name="reminder_every_week">Hebdomadaire</string>
+    <string name="reminder_every_month">Mensuel</string>
+    <string name="reminder_no_free_slots_description">Plus d\'emplacement pour rappel disponible (emplacements totaux鈥�: %1$s)</string>
+    <string name="miband_prefs_reserve_reminder_calendar">Rappels r茅serv茅s pour des 茅v猫nements futurs</string>
+    <string name="prefs_reserve_reminder_calendar_summary">Nombre d\'茅v猫nements du calendrier qui seront synchronis茅s</string>
+    <string name="title_activity_reminder_details">D茅tails du rappel</string>
+    <string name="mi2_prefs_do_not_disturb_lift_wrist">Activer l\'茅cran lors d\'un retournement en mode Ne Pas D茅ranger</string>
+    <string name="maxHR">Rythme cardiaque max</string>
+    <string name="maxStride">Distance de pas max</string>
+    <string name="minStride">Distance de pas min</string>
+    <string name="minCadence">Cadence min</string>
+    <string name="spm">pas/min</string>
+    <string name="averageAltitude">Moyenne</string>
+    <string name="averageCadence">Cadence moyenne</string>
+    <string name="minSpeed">Minimum</string>
+    <string name="minHR">Rythme cardiaque min</string>
+    <string name="maxCadence">Cadence max</string>
+    <string name="activity_prefs_discovery_pairing">Options de d茅couverte et de pairage</string>
+    <string name="discover_unsupported_devices">D茅couvrir les appareils non pris en charge</string>
+    <string name="add_test_device">Ajouter un appareil de test</string>
+    <string name="controlcenter_power_off">脡teindre</string>
+    <string name="controlcenter_power_off_confirm_title">脡teindre</string>
+    <string name="controlcenter_power_off_confirm_description">Etes-vous s没r de vouloir 茅teindre l\'appareil \?</string>
+    <string name="discover_unsupported_devices_description">Activer cette option pour afficher tous les appareils bluetooth lors du scan. Un appui bref copie le nom de l\'appareil et sa mac adresse dans le presse-papier. Un appui long lancera le processus \"Ajouter un appareil de test\". Peut potentiellement provoquer un blocage de l\'app.</string>
+    <string name="devicetype_vesc">VESC</string>
+    <string name="devicetype_bose_qc35">Bose QC35</string>
+    <string name="activity_type_hiking">Randonn茅e</string>
+    <string name="ascentDistance">Distance en mont茅e</string>
+    <string name="descentDistance">Distance en descente</string>
+    <string name="sony_button_mode_left">Mode du bouton (Gauche)</string>
+    <string name="sony_button_mode_right">Mode du bouton (Droite)</string>
+    <string name="sony_button_mode_off">Eteint</string>
+    <string name="sony_button_mode_ambient_sound_control">Contr么le du son ambiant</string>
+    <string name="devicetype_sony_wf_sp800n">Sony WF-SP800N</string>
+    <string name="sony_audio_upsampling">Sur-茅chantillonnage audio</string>
+    <string name="activity_type_climbing">Randonn茅e</string>
+    <string name="sony_pause_when_taken_off">Pause lorsqu\'on enl猫ve les 茅couteurs</string>
+    <string name="sony_button_mode_playback_control">Contr么le de la lecture</string>
+    <string name="sony_button_mode_volume_control">Contr么le du volume</string>
+    <string name="pref_header_sony_equalizer_bands">Bandes</string>
+    <string name="pref_button_action_disabled">D茅sactiv茅</string>
+    <string name="pref_media_play">Lecteure m茅dia</string>
+    <string name="pref_media_pause">Pause M茅dia</string>
+    <string name="pref_media_playpause">Changer l\'茅tat de lecture</string>
+    <string name="pref_media_volumedown">Volume moins</string>
+    <string name="distance_format_miles">###.#mi</string>
+    <string name="pref_media_previous">Piste pr茅c茅dente</string>
+    <string name="pref_media_volumeup">Volume plus</string>
+    <string name="pref_media_next">Piste suivante</string>
+    <string name="pref_media_rewind">Piste pr茅c茅dente</string>
+    <string name="pref_device_action_broadcast">Envoyer un message Broadcast</string>
+    <string name="distance_format_meters">###m</string>
+    <string name="pref_media_forward">Avance rapide</string>
+    <string name="distance_format_kilometers">###.#km</string>
+    <string name="distance_format_feet">###ft</string>
+    <string name="about_hash">Commit %s</string>
+    <string name="menuitem_menu">Menu</string>
+    <string name="fossil_hr_button_config_info">Certains boutons ne peuvent pas 锚tre configur茅s car leurs fonctions sont encod茅es en dur dans le micro-logiciel de l\'appareil.
+\n
+\nAvertissement鈥�: Un appui long sur le bouton du haut quand l\'appareil a une application Fossil officielle install茅e provoquera l\'activation/d茅activation des widgets.</string>
+    <string name="devicetype_domyos_t540">Domyos T540</string>
+    <string name="prefs_activity_recognition">R茅glages de la d茅tection d\'activit茅</string>
+    <string name="pref_activity_recognize_running">d茅tection de course 脿 pieds</string>
+    <string name="pref_activity_recognize_biking">d茅tection de p茅dalage</string>
+    <string name="pref_activity_recognize_walking">d茅tection de marche</string>
+    <string name="pref_activity_recognize_rowing">d茅tection de pagayage</string>
+    <string name="pref_activity_recognition_mode_ask">demander</string>
+    <string name="pref_activity_recognition_mode_none">aucune</string>
+    <string name="pref_activity_recognition_mode_auto">auto</string>
+    <string name="watchface_dialog_widget_width">Largeur du widget (en pixels)</string>
+    <string name="discovery_bluetooth_scan">Scan Bluetooth鈥�:</string>
+    <string name="discovery_bluetooth_le_scan">Scan Bluetooth LE :</string>
+    <string name="sony_speak_to_chat">Speak-to-chat</string>
+    <string name="sony_speak_to_chat_sensitivity_auto">Automatique</string>
+    <string name="sony_speak_to_chat_timeout">D茅lai expir茅</string>
+    <string name="sony_speak_to_chat_timeout_off">脡teint</string>
+    <string name="sony_speak_to_chat_timeout_short">Court (15s)</string>
+    <string name="sony_speak_to_chat_timeout_standard">Standard (30s)</string>
+    <string name="sony_speak_to_chat_timeout_long">Long (1m)</string>
+    <string name="sony_connect_two_devices">Connecter 脿 2 appareils simultan茅ment</string>
+    <string name="devicetype_sony_wh_1000xm4">Sony WH-1000XM4</string>
+    <string name="sony_speak_to_chat_sensitivity_low">Bas</string>
+    <string name="sony_speak_to_chat_sensitivity">Sensibilit茅 de la d茅tection de voix</string>
+    <string name="sony_speak_to_chat_sensitivity_high">Haut</string>
+    <string name="sony_speak_to_chat_focus_on_voice">Focus sur la voix</string>
+    <string name="device_unsupported">Non support茅</string>
+    <string name="audio_codec">Codec Audio</string>
+    <string name="pref_header_sony_device_info">Information de l\'appareil</string>
+    <string name="start">D茅part</string>
+    <string name="pref_header_sony_anc_optimizer">Optimisation de l\'annulation de bruits</string>
+    <string name="sony_anc_optimize_description">Cliquer pour lancer l\'optimiseur d\'annulation de bruits.</string>
+    <string name="sony_anc_optimize_confirmation_title">Optimiseur d\'annulation de bruits</string>
+    <string name="pref_anc_optimizer_state_pressure">Pression atmosph茅rique</string>
+    <string name="sony_anc_optimizer_status_starting">D茅marrage鈥�</string>
+    <string name="sony_anc_optimizer_status_atmospheric_pressure">Mesure de la pression atmosph茅rique鈥�</string>
+    <string name="sony_anc_optimizer_status_analyzing">Analyse鈥�</string>
+    <string name="sony_anc_optimizer_status_finished">Fin鈥�</string>
+    <string name="sony_anc_optimize_title">Optimisation</string>
+    <string name="sony_anc_optimize_confirmation_description">Utiliser les 茅couteurs comme d\'habitude. Si les conditions ambiantes ou la pression atmosph茅rique change, relancer l\'optimiseur.</string>
+    <string name="unknown">Inconnu</string>
+    <string name="sony_anc_optimizer_status_not_running">Pas en service</string>
+    <string name="sony_anc_optimizer_status_wearing_condition">Mesure des conditions actuelles鈥�</string>
+    <string name="devicetype_amazfit_pop">Amazfit Pop</string>
+    <string name="devicetype_amazfit_pop_pro">Amazfit Pop Pro</string>
+    <string name="menuitem_nothing">Rien</string>
+    <string name="pref_title_opentracks_packagename">Nom du logiciel OpenTracks</string>
+    <string name="pref_summary_opentracks_packagename">Utilis茅 pour d茅marrer/arr锚ter l\'enregistrement GPS dans une app. de fitness externe.</string>
+    <string name="pref_device_action_fitness_app_control_start">D茅marrage de l\'enregistrement dans l\'app Fitness</string>
+    <string name="pref_device_action_fitness_app_control_stop">Arr锚t de l\'enregistrement dans l\'app Fitness</string>
+    <string name="pref_title_notifications_generic_settings">Configuration des notifications Android</string>
+    <string name="autoExport_lastTime_label">Dernier export automatique鈥�: %1$s</string>
+    <string name="activity_db_management_autoexport_enabled_yes">L\'export automatique est activ茅.</string>
+    <string name="activity_db_management_autoexport_scheduled_yes">L\'export automatique a 茅t茅 (initialement) r茅gl茅 pour %1$s</string>
+    <string name="activity_db_management_autoexport_scheduled_no">L\'export automatique n\'a pas 茅t茅 planifi茅.</string>
+    <string name="activity_db_management_autoexport_enabled_no">L\'export automatique est d茅sactiv茅.</string>
+    <string name="activity_db_management_autoexport_location">L\'emplacement n\'a pas pu 锚tre d茅tect茅. Probablement un probl猫me avec les nouvelles permissions du syst猫me Android. Tr猫s probablement, l\'export automatique ne fonctionne pas actuellement.</string>
+    <string name="watchface_dialog_pre_setting_position">pr茅-configuration de la position 脿 %s</string>
+    <string name="watchface_setting_light_up_on_notification">S\'allumer pour une nouvelle notification</string>
+    <string name="menuitem_email">Email</string>
+    <string name="title_activity_controlcenter_banglejs_main">Bangle.js Gadgetbridge</string>
+    <string name="about_activity_title_banglejs_main">脌 propos de Bangle.js Gadgetbridge</string>
+    <string name="application_name_banglejs_main">Bangle.js Gadgetbridge</string>
+    <string name="about_description_banglejs_main">Application compl茅mentaire Android pour Bangle.js 茅labor茅e sur la base du projet Gadgetbridge, avec des fonctionnalit茅s Internet suppl茅mentaires.
+\n
+\nEn raison des r猫gles du Google Play Store, nous ne pouvons pas mettre de lien de don directement dans l\'application, mais si vous appr茅ciez cette application, pensez 脿 faire un don via la page web de Gadgetbridge ci-dessous.</string>
+    <string name="application_name_banglejs_nopebble">Bangle.js pour Gadgetbridge</string>
+    <string name="title_activity_controlcenter_banglejs_nopebble">Bangle.js pour Gadgetbridge</string>
+    <string name="about_activity_title_main_nightly">脌 propos de Gatgetbridge Nightly</string>
+    <string name="about_activity_title_banglejs_nopebble">A propos de Bangle.js pour Gadgetbridge</string>
+    <string name="about_activity_title_main_nopebble">脌 propos de Gadgetbridge Nightly sans Pebble</string>
+    <string name="about_description_main_nightly">Rempla莽ant autonome et sous licence libre pour remplacer les applications propri茅taires des fabricants. Version Nightly de Gadgetbridge. Ne peut pas 锚tre install茅 si vous avez d茅ja Gadgetbridge ou Pebble d\'install茅, en raison d\'un conflit avec l\'application Pebble.</string>
+    <string name="application_name_main_nopebble">Gadgetbridage (Nightly, pas defournisseur Pebble)</string>
+    <string name="title_activity_controlcenter_main_nopebble">Gadgetbridge Nightly Sans Pebble</string>
+    <string name="gadgetbridge_running_main_nightly">GB Nightly en fonctionnement</string>
+    <string name="gadgetbridge_running_banglejs_main">Bangle.js en fonctionnement</string>
+    <string name="about_description_banglejs_nopebble">Application compl茅mentaire Android pour Bangle.js 茅labor茅e sur la base du projet Gadgetbridge, avec des fonctionnalit茅s Internet suppl茅mentaires.
+\n
+\nEn raison des r猫gles du Google Play Store, nous ne pouvons pas mettre de lien de don directement dans l\'application, mais si vous appr茅ciez cette application, pensez 脿 faire un don via la page web de Gadgetbridge ci-dessous.</string>
+    <string name="gadgetbridge_running_banglejs_nopebble">Bangle.js en fonctionnement</string>
+    <string name="application_name_main_nightly">Gadgetbridge (version Nightly)</string>
+    <string name="title_activity_controlcenter_main_nightly">Gadgetbrigde version Nightly</string>
+    <string name="pref_screen_notification_profile_event_reminder">Rappel d\'茅v猫nements</string>
+    <string name="pref_screen_notification_profile_find_device">Trouver l\'appareil</string>
+    <string name="pref_screen_notification_idle_alerts">Alertes d\'inactivit茅</string>
+    <string name="pref_screen_vibration_patterns_title">Modes de vibration</string>
+    <string name="pref_screen_vibration_patterns_summary">Configurer les modes de vibration pour les diff茅rentes notifications</string>
+    <string name="world_clock_delete_confirm_title">Effacer \'%1$s\'</string>
+    <string name="world_clock_timezone">Fuseau horaire</string>
+    <string name="world_clock_label">脡tiquette</string>
+    <string name="title_activity_world_clock_details">D茅tails du fuseau horaire</string>
+    <string name="normal">Normal</string>
+    <string name="sensitive">Sensible</string>
+    <string name="activity_type_outdoor_running">Course 脿 pied en ext茅rieur</string>
+    <string name="activity_type_freestyle">Exercice libre</string>
+    <string name="activity_type_elliptical">Ellyptique</string>
+    <string name="devicetype_sony_wf_1000xm3">Sony WF-1000XM3</string>
+    <string name="devicetype_galaxybuds_pro">Galaxy Buds Pro</string>
+    <string name="prefs_seamless_connection_switch_title">Changement de connexion sans coupure</string>
+    <string name="gadgetbridge_running_main_nopebble">GB nightly non Pebble en cours de fonctionnement</string>
+    <string name="heartrate_bpm_105">105 bpm</string>
+    <string name="about_description_main_nopebble">Rempla莽ant autonome et sous licence libre pour remplacer les applications propri茅taires des fabricants. Version Nightly de Gadgetbridge. Cette ersion contient le fournisseur Pebble renomm茅 pour 茅viter les conflits, donc certaines int茅grations Pebble ne marcheront pas, mais elle peut 锚tre install茅 脿 c么t茅 d\'une installation GadgetBridge existante.</string>
+    <string name="heartrate_bpm_145">145 bpm</string>
+    <string name="prefs_heartrate_alert_experimental_title">Alerte de rythme cardiaque (exp茅rimentale)</string>
+    <string name="prefs_stress_monitoring_description">Surveiller le niveau de stress pendant le repos</string>
+    <string name="mi2_prefs_heart_rate_monitoring">Surveillance du rythme cardiaque</string>
+    <string name="mi2_prefs_heart_rate_monitoring_alerts_summary">Configuration de la surveillance du rythme cardiaque et des seuils d\'alerte</string>
+    <string name="mi2_prefs_heart_rate_monitoring_summary">Configuration de la surveillance du rythme cardiaque</string>
+    <string name="heartrate_bpm_100">100 bpm</string>
+    <string name="heartrate_bpm_110">110 bpm</string>
+    <string name="heartrate_bpm_112">112 bpm</string>
+    <string name="heartrate_bpm_120">120 bpm</string>
+    <string name="heartrate_bpm_125">125 bpm</string>
+    <string name="heartrate_bpm_130">130 bpm</string>
+    <string name="heartrate_bpm_135">135 bpm</string>
+    <string name="heartrate_bpm_140">140 bpm</string>
+    <string name="world_clock_delete_confirm_description">Etes-vous s没r de vouloir supprimer l\'horloge mondiale \?</string>
+    <string name="world_clock_no_free_slots_title">Aucun emplacement de libre</string>
+    <string name="world_clock_no_free_slots_description">Cet appareil n\'a plus d\'emplacement disponible pour des fuseaux horaires (total des emplacements鈥�: %1$s)</string>
+    <string name="prefs_heartrate_alert_experimental_description">Faire vibrer le bracelet quand le rythme cardiaque passe un seuil, sans aucune activit茅 physique 茅vidente dans les 10聽derni猫res minutes. Cette fonctionnalit茅 est exp茅rimentale, et n\'a pas 茅t茅 beaucoup test茅.</string>
+    <string name="prefs_heartrate_alert_threshold">Seuil d\'alerte du rythme cardiaque</string>
+    <string name="prefs_stress_monitoring_title">Surveillance du stress</string>
+    <string name="prefs_activity_monitoring_title">Surveillante de l\'activit茅</string>
+    <string name="prefs_activity_monitoring_description">Augmenter automatiquement la fr茅quence de mesure du rythme cardiaque quand le bracelet d茅tecte une activit茅, pour augmenter la pr茅cision de mesure du rythme cardiaque.</string>
+    <string name="prefs_ambient_volume_left">Volume ambiant gauche</string>
+    <string name="prefs_ambient_volume_right">Volume ambiant droit</string>
+    <string name="prefs_customize_ambient_sound_summary">Personnaliser le son ambiant</string>
+    <string name="prefs_ambient_sound_during_call_title">Son ambiant pendant un appel</string>
+    <string name="prefs_ambient_settings_title">Options pour le son ambiant</string>
+    <string name="heartrate_bpm_150">150 bpm</string>
+    <string name="prefs_in_ear_detection_summary">Recevoir les appels dans les 茅couteurs lorsque vous les portez</string>
+    <string name="prefs_seamless_connection_switch_summary">Commutation entre les appareils d茅ja pair茅s automatiquement</string>
+    <string name="prefs_ambient_sound_during_call_summary">Entendre sa propre voix clairement durant un appel</string>
+    <string name="pref_world_clocks_title">Horloges mondiales</string>
+    <string name="pref_world_clocks_summary">Configurer les horloges pour diff茅rents fuseaux horaires</string>
+    <string name="prefs_activate_display_on_lift_sensitivity">Sensibilit茅</string>
+    <string name="mi5_prefs_workout_activity_types">Types d\'activit茅s physiques</string>
+    <string name="mi5_prefs_workout_activity_types_summary">Choisir les types d\'activit茅 脿 afficher sur l\'茅cran d\'activit茅 physique</string>
+    <string name="activity_type_outdoor_cycling">V茅lo en ext茅rieur</string>
+    <string name="pref_title_banglejs_text_bitmap">Texte comme Images</string>
+    <string name="pref_summary_banglejs_text_bitmap">Si un mot ne peut 锚tre affich茅 avec la police de la montre, en faire une image dans GadgetBridge et l\'afficher comme image dans la montre</string>
+    <string name="pref_title_device_internet_access">Autoriser l\'acc猫s 脿 Internet</string>
+    <string name="pref_summary_device_internet_access">Permettre aux apps sur cet appareil d\'acc茅der 脿 Internet</string>
+    <string name="pref_title_device_intents">Permettre les intentions</string>
+    <string name="pref_workout_start_on_phone_title">Application de suivi sportif</string>
+    <string name="pref_workout_start_on_phone_summary">D茅marre/arr锚te le suivi sportif sur le t茅l茅phone si une activit茅 GPS est d茅marr茅e sur le bracelet</string>
+    <string name="pref_workout_send_gps_title">Envoyer le GPS durant l\'exercice</string>
+    <string name="pref_workout_send_gps_summary">Envoyer les donn茅es GPS en cours durant un exercice</string>
+    <string name="notification_gps_title">GPS Gadgebridge</string>
+    <string name="notification_gps_text">Envoi des donn茅es GPS聽au(x) appareil(s) %1$d</string>
+    <string name="notification_channel_gps">Suivi GPS</string>
+    <string name="permission_notification_listener">%1$s a besoin d\'acc茅der aux notifications pour les afficher sur votre montre quand l\'茅cran de votre t茅l茅phone est 茅teint.
+\n
+\nMerci de s茅lectionner \'%2$s\' puis \'%1$s\' et activer \'Autoriser l\'acc猫s aux notifications\', puis appuyer sur \'Retour\' pour revenir 脿 %1$s.</string>
+    <string name="discovery_scanning_intensity">Intensit茅 du scan</string>
+    <string name="discovery_scanning_intensity_warning">Si vous rencontrez des probl猫mes de blocage ou non r茅ponse, essayer de r茅gler l\'intensit茅 du scan 脿 un faible niveau. Si votre appareil n\'est pas d茅couvert, essayer l\'intensit茅 du scan au plus haut niveau.</string>
+    <string name="portuguese_br">Portugais (Br茅sil)</string>
+    <string name="portuguese_pt">Portugais (Portugal)</string>
+    <string name="prefs_active_noise_cancelling_level_low">Bas</string>
+    <string name="prefs_double_tap_edge">Double appui sur le bord</string>
+    <string name="prefs_voice_detect_duration">Arr锚t apr猫s pause pendant鈥�:</string>
+    <string name="pref_voice_detect_duration_5">5 secondes</string>
+    <string name="prefs_switch_control_left">Mettre le contr么le 脿 gauche</string>
+    <string name="prefs_switch_control_right">Mettre le contr么le 脿 droite</string>
+    <string name="pref_title_touch_ambient">Son Ambiant</string>
+    <string name="pref_title_touch_spotify">Spotify</string>
+    <string name="pref_switch_noise_control">Changer le contr么le du bruit</string>
+    <string name="prefs_noise_control_with_one_earbud">Contr么le du bruit avec un 茅couteur</string>
+    <string name="prefs_active_noise_cancelling_level">Niveau actif de l\'annulation de bruit</string>
+    <string name="prefs_active_noise_cancelling_level_high">Haut</string>
+    <string name="pref_title_touch_voice_assistant">Assistant vocal</string>
+    <string name="pref_title_touch_anc">Annulation de bruit active</string>
+    <string name="pref_title_touch_quick_ambient">Son Ambiant</string>
+    <string name="prefs_double_tap_edge_summary">D茅tecter un double appui m锚me si hors zone tactile</string>
+    <string name="pref_voice_detect_duration_10">10 secondes</string>
+    <string name="pref_device_action_phone_gps_location_listener_stop">Arr锚t du gestionnaire GPS</string>
+    <string name="permission_notification_policy_access">%1$s doit acc茅der aux r茅glages Ne Pas D茅ranger afin de les respecter sur votre montre quand l\'茅cran de votre t茅l茅phone est 茅teint.
+\n
+\nMerci de s茅lectionner \'%2$s\' puis \'%1$s\' et activer \'Autoriser Ne Pas D茅ranger\', puis s茅lectionner \'Retour\' pour revenir 脿 %1$s.</string>
+    <string name="prefs_voice_detect_summary">Activer le son ambiant et r茅duction de bruit automatique lors de la d茅tection de voix</string>
+    <string name="quick_alarm">Alarme rapide</string>
+    <string name="quick_alarm_description">Alarme d\'un widget</string>
+    <string name="pref_summary_device_intents">Permettre 脿 Bangle.js d\'envoyer des notifications Android, et permettre aux autres apps Android (comme Tasker) d\'envoyer des donn茅es 脿 Bangle.js avec le dispositif com.banglejs.uart.tx . N茅cessite les droits d\'affichage par-dessus les autres apps pour fonctionner en arri猫re-plan.</string>
+    <string name="pref_title_touch_volume">Volume</string>
+    <string name="prefs_noise_control_with_one_earbud_summary">Permettre le contr么le du bruit en utilisant un seul 茅couteur</string>
+    <string name="pref_ambient_sound_tone">Tonalit茅 du son ambiant</string>
+    <string name="pref_ambient_sound_tone_summary">De doux 脿 Clair</string>
+    <string name="pref_balance">Balance</string>
+    <string name="pref_switch_controls_ambient_off">Ambiant 鈫愨啋 Off</string>
+    <string name="prefs_noise_control">Contr么le du bruit</string>
+    <string name="prefs_voice_detect">D茅tection de la voix</string>
+    <string name="pref_switch_controls_anc_ambient">Annulation de bruit 鈫愨啋 Ambiant</string>
+    <string name="pref_switch_controls_anc_off">Annulation de bruit 鈫愨啋 Off</string>
+    <string name="pref_voice_detect_duration_15">15 secondes</string>
+    <string name="pref_device_action_fitness_app_control_toggle">Basculer d\'app de suivi sportif</string>
+    <string name="pref_title_banglejs_webview_url">URL de chargement de l\'app</string>
+    <string name="permission_location">%1$s a besoin d\'acc猫s 脿 votre emplacement en arri猫re-plan pour lui permettre de rester connect茅 脿 votre montre m锚me quand votre 茅cran est 茅teint. \n \nVeuillez choisir \'%2$s\' dans l鈥櫭ヽran suivant, puis appuyez sur Retour pour revenir 脿 %1$s.</string>
+    <string name="pref_summary_banglejs_webview_url">Si vous souhaitez utiliser un chargeur d\'app sp茅cifique mettre votre URL https://鈥�/android.html ici. Sinon laissez blanc pour https://banglejs.com/apps</string>
+    <string name="info_no_devices_connected">Aucun appareil connect茅</string>
+    <string name="info_connected_count">%d appareils connect茅s</string>
+    <string name="ukranian">Ukrainien</string>
+    <string name="estonian">Estonien</string>
+    <string name="icelandic">Islandais</string>
+    <string name="lithuanian">Lituanien</string>
+    <string name="persian">Perse</string>
+    <string name="bengali">Bengali</string>
+    <string name="czech">Tch猫que</string>
+    <string name="extended_ascii">ASCII聽茅tendu</string>
+    <string name="scandinavian">Scandinave</string>
+    <string name="pref_title_notification_delay_calls">D茅lai de notification d\'appel</string>
+    <string name="pref_summary_notification_delay_calls">D茅lai avant d\'envoyer les notifications d\'appels entrants 脿 l\'appareil, en secondes.</string>
+    <string name="prefs_password">Mot de passe</string>
+    <string name="controlcenter_toggle_details">Afficher les d茅tails</string>
+    <string name="pref_blacklist_calendars_summary">Les calendriers bloqu茅s ne seront pas synchronis茅s avec l\'appareil</string>
+    <string name="controlcenter_set_preferences">Configurer les pr茅f茅rences</string>
+    <string name="controlcenter_connected_fraction">Connect茅鈥�: %d/%d</string>
+    <string name="error_deleting_device">Erreur lors de l\'effacement de l\'appareil : %s</string>
+    <string name="controlcenter_folder_name">Nom du dossier :</string>
+    <string name="controlcenter_add_new_folder">Ajouter un nouveau dossier</string>
+    <string name="controlcenter_set_folder_title">Configurer ou cr茅er un nouveau dossier</string>
+    <string name="auto_reconnect_ble_title">Auto-reconnexion 脿 l\'appareil</string>
+    <string name="auto_reconnect_ble_summary">Essayer de se reconnecter 脿 l\'appareil p茅riodiquement</string>
+    <string name="connection_over_ble">Connexion par BLE</string>
+    <string name="connection_over_bt_classic">Connexion en Bluetooth classique</string>
+    <string name="autoconnect_from_device_title">Connecter lors de la connexion de l\'appareil</string>
+    <string name="pref_explanation_authkey">Certains appareils n茅cessitent une cl茅 de pairage pour la toute premi猫re initialisation de l\'appareil. Touchez ici pour plus de d茅tails dans le wiki.</string>
+    <string name="controlcenter_unset_folder">Dossier non configur茅</string>
+    <string name="pref_explanation_authkey_new_protocol">Si vous obtenez le message \"Mettre 脿 jour l\'application dans sa derni猫re version\" sur le bracelet, assurez-vous de v茅rifier la section \"Nouveau protocole Auth\" ci-dessus. Ou ici pour plus d\'info dans le wiki.</string>
+    <string name="prefs_password_enabled">Mot de passe activ茅</string>
+    <string name="prefs_password_4_digits_1_to_4_summary">Le mot de passe doit 锚tre 脿 4聽chiffres, avec les chiffres de 1 脿 4</string>
+    <string name="error_setting_parent_folder">Erreur lors de la configuration du dossier parent鈥�: %s</string>
+    <string name="controlcenter_set_parent_folder">Configurer le dossier parent</string>
+    <string name="prefs_password_summary">Verrouiller le bracelet avec un mot de passe quand il n\'est pas au poignet</string>
+    <string name="prefs_password_6_digits_0_to_9_summary">Le mot de passe doit comporter 6 caract猫res, en utilisant seulement des chiffres</string>
+    <string name="autoconnect_from_device_summary">脡tablir la connexion lorsque celle-ci est initi茅e par l\'appareil, comme pour des 茅couteurs</string>
+    <string name="watchface_dialog_widget_timezone_duration">Dur茅e de visibilit茅 de la pendule (en secondes)</string>
+    <string name="open_fw_installer_getting_files_title">Obtenir le fichier de firmware/application</string>
+    <string name="open_fw_installer_pick_file">Choisir le fichier</string>
+    <string name="open_fw_installer_info_text_title">Installateur de fichier</string>
+    <string name="open_fw_installer_select_file">S茅lectionner un fichier 脿 envoyer 脿 votre appareil鈥�: %s</string>
+    <string name="open_fw_installer_warning_title">Avertissement</string>
+    <string name="open_fw_installer_info_text">L\'installateur de firmware/fa莽ade de montre/application/fichier vous permet d\'envoyer/installer des fichiers compatibles (firmwares, fa莽ades de montre, applications, GPS, ressources, polices,...) dans l\'appareil. Pour plus d\'information se reporter au wiki鈥�: https鈥�://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Firmware-Update</string>
+    <string name="open_fw_installer_warning_text">Cette fonction a la capacit茅 de rendre inop茅rant/endommager votre appareil. Cela 茅tant dit, cela ne s\'est jamais produit pour aucun des d茅veloppeurs, mais rappelez-vous que vous faites cela 脿 vos risques et p茅rils.</string>
+    <string name="open_fw_installer_ensure_device_connected">Assurez-vous que l\'appareil %s est bien connect茅</string>
+    <string name="open_fw_installer_connect_maximum_one_device">Merci de connecter UN聽SEUL appareil vers lequel vous souhaitez envoyer le fichier.</string>
+    <string name="open_fw_installer_getting_files_text">Comme nous ne pouvons pas distribue les fichiers de firmware, vous devrez les obtenir par vous-m锚me. Cela signifie que vous devrez chercher les fichiers dans des fichiers apk, en ligne, dans des forums, sur Amazfitwatchfaces (pour les appareils Miband/Amazfit) et ainsi de suite.</string>
+    <string name="open_fw_installer_connect_minimum_one_device">Merci de connecter AU聽MOINS聽UN聽appareil vers lequel vous voulez envoyer le fichier.</string>
+    <string name="appmanager_item_outdated">(obsol猫te)</string>
+    <string name="appmanager_app_share">Partager</string>
+    <string name="pref_title_banglejs_txt_bitmap_size">Taille des images texte</string>
+    <string name="pref_summary_banglejs_txt_bitmap_size">Taille 脿 utiliser pour le rendu de texte en image</string>
+    <string name="watchface_dialog_title_widget">R茅glages du widget</string>
+    <string name="watchface_dialog_widget_cat_generic">G茅n茅rique</string>
+    <string name="watchface_dialog_widget_cat_position">Position</string>
+    <string name="watchface_dialog_widget_background">Arri猫re-plan</string>
+    <string name="hybridhr_widget_bg_double_circle">Cercle double</string>
+    <string name="hybridhr_widget_bg_dashed_circle">Cercle en pointill茅</string>
+    <string name="watchface_dialog_widget_cat_2nd_tz_widget">Widget deuxi猫me fuseau horaire</string>
+    <string name="watchface_dialog_widget_cat_custom_widget">Widget personnalis茅</string>
+    <string name="hybridhr_widget_bg_thin_circle">Cercle fin</string>
+    <string name="watchface_setting_title_button_toggle_widgets">Intervertir les widgets</string>
+    <string name="watchface_setting_button_toggle_widgets">Intervertir les widgets</string>
+    <string name="steps_streaks">Rythme de marche</string>
+    <string name="step_streak_total">Total</string>
+    <string name="steps_streaks_total_steps">Pas
+\nTotal</string>
+    <string name="steps_streaks_streak_days">Jour
+\nD\'objectif</string>
+    <string name="steps_streaks_average_steps">Nombre de pas
+\nmoyen</string>
+    <string name="steps_streaks_achievement_rate">Taux
+\nde r茅ussite</string>
+    <string name="steps_streaks_since_date">Depuis %s</string>
+    <string name="steps_streaks_hint">S茅rie de jours cons茅cutifs sans interruption o霉 l\'objectif de pas a 茅t茅 atteint</string>
+    <string name="step_streak_ongoing">En cours</string>
+    <string name="step_streak_longest">Le plus long</string>
+    <string name="note">Remarque</string>
+    <string name="step_streak_days_hint">Nombre de jours cons茅cutifs o霉 l\'objectif de pas a 茅t茅 r茅alis茅</string>
+    <string name="steps_streaks_total_steps_average_hint">Moyenne totale %d de pas par jour</string>
+    <string name="share_log_not_enabled_message">Vous devez d\'abord activer l\'enregistrement des journaux dans les R茅glages - Enregistrer les journaux</string>
+    <string name="steps_streaks_total_steps_hint_totals">Nombre total de pas jamais enregistr茅</string>
+    <string name="steps_streaks_total_days_hint_totals">Pourcentage de jours o霉 l\'objectif a 茅t茅 r茅alis茅 compar茅 脿 tous les jours avec de l\'exercice</string>
+    <string name="step_streak_average_steps_hint">Moyenne de pas par jour pour l\'objectif</string>
+    <string name="steps_streaks_total_steps_hint">Nombre total de pas de l\'objectif</string>
+    <string name="watchface_setting_button_toggle_backlight">Allumer le r茅tro茅clairage</string>
+    <string name="watchface_setting_title_custom_events">脡v茅nements personnalis茅s</string>
+    <string name="pref_write_logfiles_not_available">Cr茅ation du fichier journal rat茅e, l\'茅criture du fichier journal est indisponible. Red茅marrer l\'application pour essayer de nouveau l\'茅criture du fichier journal.</string>
+    <string name="controlcenter_get_heartrate_measurement">Obtenir les mesures de rythme cardiaque</string>
+    <string name="about_activity_title_banglejs_nightly">脌 propos de Bangle.js Gadgetbridge (Version quotidienne)</string>
+    <string name="title_activity_controlcenter_banglejs_nightly">Bangle.js Gadgetbridge (Version quotidienne)</string>
+    <string name="application_name_banglejs_nightly">Bangle.js Gadgetbridge (Version quotidienne)</string>
+    <string name="devicetype_binary_sensor">Capteur binaire</string>
+    <string name="gadgetbridge_running_banglejs_nightly">Bangle.js version quotidienne est en fonctionnement</string>
+    <string name="about_description_banglejs_nightly">Application compl茅mentaire pour Bangle.js construit sur la base du projet Gadgetbridge project, avec l\'ajout de l\'acc猫s Internet.
+\n
+\nEn raison des contraintes du Google Play Store, nous ne sommes pas autoris茅s 脿 mettre un lien de donation dans l\'application elle-m锚me, mais si vous appr茅ciez cette application, merci de penser 脿 faire une donation via la page Gadgetbridge ci-dessous.</string>
+    <string name="prefs_hourly_chime">Beep horaire</string>
+    <string name="prefs_hourly_chime_summary">La montre fera un beep toutes les heures</string>
+    <string name="step_streaks_achievements_sharing_title">Objectifs de pas</string>
+    <string name="step_streaks_achievements_sharing_message">Mes r茅sultats quotidiens de pas !</string>
+    <string name="permission_display_over_other_apps">%1$s a besoin d\'une autorisation pour s\'afficher au-dessus des autres apps afin de pouvoir lancer des activit茅s via la commande vocale lorsque %1$s est en arri猫re-plan.
+\n
+\nCela peut 锚tre utilis茅 pour d茅marrer une application de musique et jouer un morceau, et de nombreuses autres choses.
+\n
+\nMerci de choisir \'%2$s\' puis \'%1$s\' et activer \'Permettre l\'affichage au-dessus des autres apps\', puis faire \'Retour\' pour revenir 脿 %1$s.
+\n
+\nPour emp锚cher %1$s de demander des permissions se rendre dans les \'R茅glages\' et d茅cocher \'V茅rifier l\'茅tat des permissions\'.
+\n
+\nAssurez-vous d\'accorder 脿 %1$s les permissions n茅cessaires pour les fonctionnalit茅s attendues.</string>
+    <string name="pref_header_time">Heure</string>
+    <string name="pref_header_workout">Exercice</string>
+    <string name="pref_summary_canned_replies">R茅ponse de la montre utilisant des messages pr茅enregistr茅s</string>
+    <string name="pref_title_screen_on_on_notifications">Allumer l\'茅cran pour les notifications</string>
+    <string name="pref_summary_screen_on_on_notifications">Allumer l\'茅cran du bracelet quand une notification arrive</string>
+    <string name="spo2_perc_80">80%</string>
+    <string name="spo2_perc_85">85%</string>
+    <string name="spo2_off">脡teint</string>
+    <string name="prefs_relaxation_reminder_description">Fais vibrer le bracelet pour vous avertir si le taux de stress est sup茅rieur 脿 80</string>
+    <string name="prefs_spo2_monitoring_title">Surveillance de l\'oxyg茅nation du sang</string>
+    <string name="prefs_spo2_monitoring_description">Surveille automatiquement le taux d\'oxyg茅nation du sang durant la journ茅e</string>
+    <string name="prefs_spo2_alert_threshold">Seuil d\'alerte SPO2</string>
+    <string name="seconds_11">11 secondes</string>
+    <string name="seconds_12">12 secondes</string>
+    <string name="seconds_13">13 secondes</string>
+    <string name="seconds_14">14 secondes</string>
+    <string name="seconds_15">15 secondes</string>
+    <string name="pref_sleep_breathing_quality_monitoring">Surveillance de la qualit茅 de la respiration durant le sommeil</string>
+    <string name="prefs_always_on_display">脡cran toujours allum茅</string>
+    <string name="devicetype_miband7">Xiaomi Smart Band 7</string>
+    <string name="menuitem_countdown">Compte 脿 rebours</string>
+    <string name="menuitem_personal_activity_intelligence">Activit茅 personnelle</string>
+    <string name="pref_header_stress">Stress</string>
+    <string name="pref_header_spo2">Oxyg茅nation du sang</string>
+    <string name="pref_screen_brightness">Luminosit茅 de l\'茅cran</string>
+    <string name="seconds_8">8 secondes</string>
+    <string name="menuitem_female_health">Sant茅 f茅minine</string>
+    <string name="heartrate_bpm_115">115 bpm</string>
+    <string name="fw_upgrade_notice_miband7">Vous 锚tes sur le point d\'installer le micro-logiciel %s dans votne Xiaomi Smart Band 7.
+\n
+\nVotre bracelet red茅marrera apr猫s l\'installation du fichier .zip.
+\n
+\nA聽VOS聽RISQUES聽ET聽P脡RILS !</string>
+    <string name="pref_header_calendar">Calendrier</string>
+    <string name="pref_header_connection">Connexion</string>
+    <string name="pref_header_display">Affichage</string>
+    <string name="pref_header_health">Sant茅</string>
+    <string name="pref_cache_weather">Mettre en cache les informations m茅t茅o</string>
+    <string name="pref_cache_weather_summary">Les informations m茅t茅o seront conserv茅es en cache entre les red茅marrages de l\'application.</string>
+    <string name="heartrate_bpm_40">40 bpm</string>
+    <string name="heartrate_bpm_45">45 bpm</string>
+    <string name="heartrate_bpm_50">50 bpm</string>
+    <string name="prefs_heartrate_alert_low_threshold">Seuil d\'alerte pour un taux trop bas du rythme cardiaque</string>
+    <string name="prefs_relaxation_reminder_title">Rappel de relaxation</string>
+    <string name="spo2_perc_90">90%</string>
+    <string name="prefs_heartrate_alert_high_threshold">Seuil d\'alerte pour un taux trop 茅lev茅 du rythme cardiaque</string>
+    <string name="seconds_7">7 secondes</string>
+    <string name="prefs_always_on_display_summary">Garder l\'茅cran du bracelet toujours allum茅</string>
+    <string name="pref_header_heartrate_sleep">Sommeil</string>
+    <string name="smart">Optimis茅</string>
+    <string name="pref_header_heartrate_allday">Surveillance toute la journ茅e</string>
+    <string name="seconds_6">6 secondes</string>
+    <string name="seconds_9">9 secondes</string>
+    <string name="prefs_screen_timeout">Temps de passage en veille de l\'茅cran</string>
+    <string name="mi2_dnd_always">Toujours</string>
+    <string name="menuitem_workout_history">Historique des entrainements</string>
+    <string name="menuitem_workout_status">脡tat des entrainements</string>
+    <string name="pref_header_heartrate_alerts">Alerte pour le rythme cardiaque</string>
+    <string name="pref_title_notifications_ignore_low_priority">Ignorer les notifications de faible priorit茅</string>
+    <string name="pref_summary_notifications_ignore_low_priority">Ne pas envoyer les notifications de priorit茅 minimum ou faible 脿 la montre</string>
+    <string name="devicetype_flipper_zero">Flipper zero</string>
+    <string name="activity_prefs_allow_bluetooth_intent_api">API聽Bluetooth d\'intention</string>
+    <string name="activity_prefs_summary_allow_bluetooth_intent_api">Permet le contr么le de la connexion Bluetooth via les intentions d\'API</string>
+    <string name="fw_upgrade_notice_amazfit_gts3">Vous 锚tes sur le point d\'installer le micro-logiciel %s dans votre Amazfit GTS 3.
+\n
+\nVotre bracelet red茅marrera apr猫s installation du fichier zip.
+\n
+\nA聽VOS聽RISQUES聽ET聽P脡RILS !</string>
+    <string name="pref_title_notification_prefer_long_text">Pr茅f茅rer les notifications texte longues</string>
+    <string name="pref_summary_notification_prefer_long_text">Si disponible, envoyer les notifications texte longues 脿 l\'appareil</string>
+    <string name="devicetype_amazfit_gts3">Amazfit GTS 3</string>
+    <string name="kind_app">App</string>
+    <string name="menuitem_unknown_app">Inconnu (%s)</string>
+    <string name="pref_summary_overwrite_settings_on_connection">Lors de la connexion au bracelet, 茅crasez tous les r茅glages de celui-ci.</string>
+    <string name="pref_title_overwrite_settings_on_connection">脡craser les r茅glages 脿 la connexion</string>
+    <string name="dismiss">Annuler</string>
+    <string name="fw_upgrade_notice_amazfit_gtr3">Vous 锚tes sur le point d\'installer le micro-logiciel %s dans votre Amazfit GTR 3.
+\n
+\nVotre bracelet red茅marrera automatiquement apr猫s l\'installation du fichier .zip.
+\n
+\nA聽VOS聽RISQUES聽ET聽P脡RILS !</string>
+    <string name="devicetype_amazfit_gtr3">Amazfit GTR 3</string>
+    <string name="pref_title_third_party_app_device_settings">Permettre 脿 des applications tierces 脿 modifier les r茅glages</string>
+    <string name="pref_summary_third_party_app_device_settings">Autoriser les applications tierces 脿 modifier les r茅glages 脿 travers les commandes vocales.</string>
+    <string name="activity_type_dance">Danse</string>
+    <string name="activity_type_hiit">Entrainement 脿 haute-intensit茅 par intervalles</string>
+    <string name="activity_type_core_training">Entrainement de fond</string>
+    <string name="activity_type_stretching">脡tirements</string>
+    <string name="activity_type_stepper">Stepper</string>
+    <string name="activity_type_pilates">Pilate</string>
+    <string name="activity_type_volleyball">Volley-Ball</string>
+    <string name="activity_type_table_tennis">Ping-Pong</string>
+    <string name="activity_type_bowling">Bouling</string>
+    <string name="activity_type_kickboxing">Kickboxing</string>
+    <string name="activity_type_street_dance">Danse de rue</string>
+    <string name="activity_type_indoor_ice_skating">Patinage sur glace en int茅rieur</string>
+    <string name="activity_type_boxing">Boxe</string>
+    <string name="activity_type_zumba">Zoumba</string>
+    <string name="activity_type_gymnastics">Gymnastique</string>
+    <string name="activity_type_indoor_fitness">Fitness en int茅rieur</string>
+    <string name="devicetype_super_cars">Course</string>
+    <string name="devicetype_amazfit_gtr_lite">Amazfit GTR Lite</string>
+    <string name="watchface_setting_button_move_hands">Bouger les mains</string>
+    <string name="supercars_turbo_speed_label">Vitesse Turbo</string>
+    <string name="supercars_lights_label">Lumi猫res</string>
+    <string name="abstract_chart_fragment_kind_rem_sleep">Sommeil REM</string>
+    <string name="pref_header_authentication">Identification</string>
+    <string name="sports_activity_confirm_delete_title">Activit茅s %d supprim茅es</string>
+    <string name="sports_activity_confirm_delete_description">Etes-vous s没r de vouloir effacer ces %d activit茅s \?</string>
+    <string name="supercars_lights_blinking_label">Clignotant</string>
+    <string name="pref_chart_sleep_lines_limit">Combien de lignes de sommeil doivent 锚tre affich茅es avant de les faire d茅filer</string>
+    <string name="HeartRateZones">Zones de rythme cardiaque</string>
+    <string name="hrZoneNa">N/A</string>
+    <string name="hrZoneWarmUp">Mise en route</string>
+    <string name="hrZoneFatBurn">脡limination mati猫res grasses</string>
+    <string name="anaerobicTrainingEffect">Effet ana茅robique</string>
+    <string name="currentWorkoutLoad">Intensit茅 de l\'exercice</string>
+    <string name="maximumOxygenUptake">Oxyg茅nation maximale</string>
+    <string name="hrZoneAerobic">A茅robique</string>
+    <string name="elevationGain">Gain d\'altitude</string>
+    <string name="elevationLoss">Perte d\'altitude</string>
+    <string name="hrZoneAnaerobic">Ana茅robique</string>
+    <string name="hrZoneExtreme">Extr锚me</string>
+    <string name="aerobicTrainingEffect">Effet a茅robique</string>
+    <string name="TrainingEffect">Effet de l\'entrainement</string>
+    <string name="devicetype_sony_wh_1000xm2">Sony WH-1000XM2</string>
+    <string name="devicetype_sony_wf_1000xm4">Sony WF-1000XM4</string>
+    <string name="accuracy">Pr茅cision</string>
+    <string name="gps">GPS</string>
+    <string name="low_power_gps">GPS en mode 茅conomie d\'茅nergie</string>
+    <string name="gps_bds">GPS + BDS</string>
+    <string name="gps_gnolass">GPS + GNOLASS</string>
+    <string name="all_satellites">Tous les satellites</string>
+    <string name="speed_first">Vitesse en premier</string>
+    <string name="accuracy_first">Pr茅cision en premier</string>
+    <string name="pref_header_sound_vibration">Son et Vibreur</string>
+    <string name="pref_header_offline_voice">Voix hors-ligne</string>
+    <string name="pref_gps_header">GPS</string>
+    <string name="pref_gps_satellite_search">Recherche satellites</string>
+    <string name="pref_crown_vibration">Vibration de la couronne</string>
+    <string name="pref_cover_to_mute">Couvrir pour couper le son</string>
+    <string name="pref_vibrate_for_alert">Vibrer pour alerter</string>
+    <string name="pref_text_to_speech">Texte en parole</string>
+    <string name="offline_voice_respond_turn_wrist">R茅pondre en tournant le poignet</string>
+    <string name="offline_voice_response_during_screen_lighting">R茅pondre lorsque l\'茅cran est allum茅</string>
+    <string name="pref_agps_header">AGPS</string>
+    <string name="pref_agps_expiry_reminder_enabled">Rappel d\'expiration AGPS</string>
+    <string name="pref_agps_expiry_reminder_time">Rappel d\'alerte d\'expiration GPS</string>
+    <string name="pref_workout_detection_categories_summary">D茅tection automatique des cat茅gories d\'exercices</string>
+    <string name="pref_workout_detection_alert_title">Alerte</string>
+    <string name="pref_workout_detection_alert_summary">Notifier lorsqu\'un exercice est d茅tect茅</string>
+    <string name="pref_sleep_mode_sleep_screen_summary">Montrer l\'茅cran de veille lors du r茅veil de l\'茅cran en mode sommeil, pour r茅duire les distractions</string>
+    <string name="buttons_on_left">Boutons de gauche</string>
+    <string name="buttons_on_right">Boutons de droite</string>
+    <string name="prefs_weardirection">Sens de port</string>
+    <string name="vibration_profile_default">D茅faut</string>
+    <string name="pref_screen_notification_profile_schedule">Planification</string>
+    <string name="pref_screen_notification_profile_todo_list">Liste 脌 faire</string>
+    <string name="seconds_25">25 secondes</string>
+    <string name="pref_enable_unsupported_settings_title">Activer les r茅glages non support茅s</string>
+    <string name="pref_enable_unsupported_settings_summary">Cela donne l\'acc猫s 脿 tous les r茅glages disponibles, y compris ceux non support茅s par l\'appareil. Cela peut causer des instabilit茅s et des plantages de l\'appareil.</string>
+    <string name="prefs_always_on_display_follow_watchface">Le style suit le th猫me de fa莽ade</string>
+    <string name="prefs_control_center_summary">Choisir les 茅l茅ments du menu contextuel du centre de contr么le</string>
+    <string name="activity_prefs_target_weight_kg">Poids souhait茅 en kg</string>
+    <string name="activity_type_indoor_walking">Marche int茅rieure</string>
+    <string name="activity_type_pool_swimming">Natation en piscine</string>
+    <string name="devicetype_amazfit_gtr4">Amazfit GTR 4</string>
+    <string name="menuitem_unsupported">[NON SUPPORT脡] %s</string>
+    <string name="pref_title_upper_button_long_press_action">Action sur un appui long du bouton sup茅rieur</string>
+    <string name="pref_screen_auto_brightness_title">Luminosit茅 automatique</string>
+    <string name="pref_screen_auto_brightness_summary">Ajuster la luminosit茅 de l\'茅cran en fonction de l\'茅clairage ambiant</string>
+    <string name="menuitem_todo">A faire</string>
+    <string name="menuitem_voice_memos">M茅mos vocaux</string>
+    <string name="menuitem_one_tap_measuring">Mesure un appui</string>
+    <string name="menuitem_wifi">Wi-Fi</string>
+    <string name="menuitem_lockscreen">Verrouillage 茅cran</string>
+    <string name="prefs_always_on_display_style">Style</string>
+    <string name="prefs_control_center">Centre de contr么le</string>
+    <string name="activity_prefs_goal_standing_time_minutes">Objectif quotidien鈥�: temps debout en minutes</string>
+    <string name="sony_speak_to_chat_sensitivity_standard">Standart</string>
+    <string name="custom">Personnalis茅</string>
+    <string name="dual_band">Double bande</string>
+    <string name="balanced">脡quilibr茅</string>
+    <string name="power_saving">脡conomie d\'茅nergie</string>
+    <string name="single_band">Bande unique</string>
+    <string name="gps_galileo">GPS + GALILEO</string>
+    <string name="fw_upgrade_notice_amazfit_gtr4">Vous 锚tes sur le point d\'installer le micro-logiciel %s dans votre Amazfit GTR 4.
+\n
+\nVotre bracelet red茅marrera apr猫s l\'installation du fichier .zip.
+\n
+\nA聽VOS聽RISQUES聽ET聽P脡RILS !</string>
+    <string name="pref_alert_tone">Tonalit茅 d\'alerte</string>
+    <string name="pref_workout_detection_summary">D茅tecter les exercices automatiquement</string>
+    <string name="pref_workout_detection_categories_title">Cat茅gories d\'exercices</string>
+    <string name="pref_gps_mode_preset">Mode GPS</string>
+    <string name="pref_gps_band">Bande GPS</string>
+    <string name="pref_gps_combination">Combinaison GPS</string>
+    <string name="offline_voice_respond_screen_on">R茅pondre lorsque l\'茅cran est allum茅</string>
+    <string name="pref_sleep_mode_title">Mode Sommeil</string>
+    <string name="pref_sleep_mode_sleep_screen_title">Veille 茅cran</string>
+    <string name="pref_workout_detection_title">D茅tection d\'exercice</string>
+    <string name="pref_workout_detection_sensitivity">Sensibilit茅</string>
+    <string name="pref_huami_truncate_fetch_operation_timestamps_summary">Tronquer les indicateurs de temps 脿 la minute. D茅sactiver ce r茅glage pour garder les indicateurs de temps 脿 la seconde, si vous rencontrez des probl猫mes 脿 enregistrer les exercices tr猫s courts.</string>
+    <string name="menuitem_calendar">Calendrier</string>
+    <string name="pref_sleep_mode_smart_enable_title">Mode Intelligent</string>
+    <string name="activity_prefs_goal_fat_burn_time_minutes">Objectif quotidien鈥�: temps d\'茅limination de calories en minutes</string>
+    <string name="menuitem_theater_mode">Mode Cin茅ma</string>
+    <string name="pref_sleep_mode_smart_enable_summary">Activer le mode sommeil automatiquement lors du port du bracelet durant le sommeil</string>
+    <string name="pref_huami_truncate_fetch_operation_timestamps_title">Tronquer les horaires lors de la r茅cup茅ration</string>
+    <string name="menuitem_sun_moon">Soleil &amp; Lune</string>
+    <string name="menuitem_offline_voice">Voix hors-ligne</string>
+    <string name="menuitem_screen_always_lit">脡cran tout le temps allum茅</string>
+    <string name="menuitem_bluetooth">Bluetooth</string>
+    <string name="menuitem_membership_cards">Cartes de membre</string>
+    <string name="menuitem_volume">Volume</string>
+    <string name="menuitem_phone">T茅l茅phone</string>
+    <string name="menuitem_brightness">Luminosit茅</string>
+    <string name="pref_title_lower_button_short_press_action">Action sur un appui court du bouton inf茅rieur</string>
+    <string name="pref_title_notification_media_ignores_application_list">Liste des applications dont les notifications m茅dia doivent 锚tre ignor茅es</string>
+    <string name="pref_summary_notification_media_ignores_application_list">Traiter les notifications m茅dia avant la liste des app. Si la pr茅f茅rence n\'est pas coch茅e, les applications m茅dia doivent 锚tre autoris茅es dans la liste des applications pour que les contr么les m茅dia fonctionnent sur l\'appareil.</string>
+    <string name="debugactivity_confirm_remove_device_preferences_title">Supprimer les pr茅f茅rences de l\'appareil \?</string>
+    <string name="debugactivity_confirm_remove_device_preferences">Cela supprimera toutes les pr茅f茅rences appareil pour tous les appareils connect茅s. Etes-vous s没r \?</string>
+    <string name="menuitem_eject_water">脡jecter l\'eau</string>
+    <string name="devicetype_amazfit_band7">Amazfit Band 7</string>
+    <string name="fw_upgrade_notice_amazfit_band7">Vous 锚tes sur le point d\'installer le micro-logiciel %s dans votre Amazfit Band 7.
+\n
+\nVotre bracelet red茅marrera apr猫s avoir install茅 le fichier .zip.
+\n
+\nA聽VOS聽RISQUES聽ET聽P脡RILS !</string>
+    <string name="function_enabled">Activ茅</string>
+    <string name="world_clock_code">Code</string>
+    <string name="devicetype_galaxybuds_2">Galaxy Buds2</string>
+    <string name="pref_title_touch_spotify_galaxy_app">Spotify (app Galaxy Wear uniquement)</string>
+    <string name="prefs_touch_lock_buds2">Contr么les tactiles</string>
+    <string name="pref_title_touch_spotify_official_app">Spotify (app officielle uniquement)</string>
+    <string name="fw_upgrade_notice_amazfit_gts4">Vous 锚tes sur le point d\'installer le micro-logiciel %s dans votre Amazfit GTS 4.
+\n
+\nVotre bracelet red茅marrera apr猫s l\'installation du .zip.
+\n
+\nA聽VOS聽RISQUES聽ET聽P脡RILS !</string>
+    <string name="devicetype_amazfit_gts4">Amazfit GTS 4</string>
+    <string name="fw_upgrade_notice_amazfit_gts4_mini">Vous 锚tes sur le point d\'installer le micro-logiciel %s dans votre Amazfit GTS 4 Mini.
+\n
+\nVotre bracelet red茅marrera une fois le fichier .zip install茅.
+\n
+\nA聽VOS聽RISQUES聽ET聽P脡RILS !</string>
+    <string name="devicetype_amazfit_gts4_mini">Amazfit GTS 4 Mini</string>
+    <string name="devicetype_sony_linkbuds_s">Sony LinkBuds S</string>
+    <string name="pref_header_intent_api">API vocale</string>
+    <string name="intent_api_allow_trigger_export_summary">Permettre le d茅clenchement de l\'export de la base de donn茅es via l\'API聽vocale</string>
+    <string name="intent_api_allow_activity_sync_title">Permettre le d茅clenchement de la sync.</string>
+    <string name="intent_api_allow_activity_sync_summary">Permettre le d茅clenchement de la sync via l\'API vocale</string>
+    <string name="intent_api_allow_trigger_export_title">Autoriser l\'export de la base de donn茅es</string>
+    <string name="intent_api_broadcast_export_title">Diffusion sur un export de la base de donn茅es</string>
+    <string name="intent_api_broadcast_export_summary">Diffuser une commande vocale quand l\'export de la base de donn茅es est fini</string>
+    <string name="sony_ambient_sound_control_button_modes">Modes des boutons de contr么le du son ambiant</string>
+    <string name="sony_ambient_sound_control_button_mode_nc_as_off">Annulation de bruit, Son ambiant, 脡teint</string>
+    <string name="sony_ambient_sound_control_button_mode_nc_as">Annulation de bruit, Son Ambiant</string>
+    <string name="sony_ambient_sound_control_button_mode_nc_off">Annulation de Bruit, 脡teint</string>
+    <string name="sony_ambient_sound_control_button_mode_as_off">Son Ambiant, 脡teint</string>
+    <string name="sony_quick_access_double_tap">Acc猫s rapide (Double Appui)</string>
+    <string name="sony_quick_access_triple_tap">Acc猫s Rapide (Triple Appui)</string>
+    <string name="menuitem_wechat_pay">Paiement WeChat</string>
+    <string name="pref_agps_update_time">Mise 脿 jour de l\'heure AGPS</string>
+    <string name="pref_agps_expire_time">Heure d\'expiration AGPS</string>
+    <string name="appmanager_app_start">D茅marrer</string>
+    <string name="appmanager_app_download">T茅l茅charger en cache</string>
+    <string name="appmanager_downloaded_to_cache">T茅l茅charg茅 %s dans le cache</string>
+    <string name="appmanager_download_app_error">Erreur en t茅l茅chargeant l\'app</string>
+    <string name="appmanager_download_started">T茅l茅chargement de l\'app d茅marr茅</string>
+    <string name="appmanager_watchface_activate">Activer</string>
+    <string name="devicetype_asteroidos">AsteroidOS</string>
+    <string name="devicetype_amazfit_trex_2">Amazfit T-Rex 2</string>
+    <string name="fw_upgrade_notice_amazfit_trex2">Vous 锚tes sur le point d\'installer le micro-logiciel %s dans votre Amazfit T-Rex 2.
+\n
+\nVotre bracelet red茅marrera une fois le fichier .zip install茅.
+\n
+\nA聽VOS聽RISQUES聽ET聽P脡RILS !</string>
+    <string name="pref_workout_detection_ask_first">Me demander d\'abord</string>
+    <string name="pref_workout_detection_ask_first_summary">Demander une confirmation sur le bracelet quand un exercice est d茅tect茅</string>
+    <string name="pref_workout_detection_time">Minutes d\'activit茅 avant la d茅tection</string>
+    <string name="pref_workout_detection_enabled_summary">Permettre la d茅tection automatique de cet exercice</string>
+    <string name="pref_workout_detection_enabled">D茅tection activ茅e</string>
+    <string name="pref_workout_detection_time_summary">Le nombre de minutes pendant lesquelles l\'exercice doit 锚tre en cours pour le d茅tecter</string>
+    <string name="activity_type_rowing">Rameur</string>
+    <string name="georgian">Georgien</string>
+    <string name="devicetype_soflow_s06">SoFlow SO6</string>
+    <string name="pref_title_lock_unlock">Verrouiler</string>
+    <string name="devicetype_galaxybuds_2_pro">Galaxy Buds2 Pro</string>
+    <string name="pref_workout_keep_screen_on_summary">L\'茅cran restera allum茅 durant un exercice, et la luminosit茅 sera ajust茅e en continu pour permettre l\'affichage des donn茅es en temps r茅el</string>
+    <string name="pref_workout_keep_screen_on_title">Garder l\'茅cran allum茅 durant un exercice</string>
+    <string name="pref_camera_remote_title">T茅l茅commande Photo</string>
+    <string name="pref_camera_remote_summary">Permettre au bracelet de d茅clencher l\'appareil photo du t茅l茅phone</string>
+    <string name="stop">Arr锚t</string>
+    <string name="wifi_hotspot">Point d\'acc猫s Wifi</string>
+    <string name="wifi_hotspot_status">Etat du point d\'acc猫s Wifi</string>
+    <string name="status">脡tat</string>
+    <string name="wifi_hotspot_configuration">Configuration du point d\'acc猫s Wifi</string>
+    <string name="wifi_ssid">SSID</string>
+    <string name="wifi_hotspot_summary">Contr么ler le point d\'acc猫s Wifi depuis la montre</string>
+    <string name="wifi_hotspot_start_summary">D茅marrer le point d鈥檃cc猫s Wifi depuis la montre</string>
+    <string name="wifi_hotspot_stop_summary">Arr锚ter le point d\'acc猫s Wifi depuis la montre</string>
+    <string name="ftp_server_summary">Contr么ler le serveur FTP depuis la montre</string>
+    <string name="ftp_server_stop_summary">Arr锚ter le serveur FTP depuis la montre</string>
+    <string name="ftp_server_configuration">Configuration du serveur FTP</string>
+    <string name="ftp_server_root_dir">R茅pertoire racine</string>
+    <string name="croatian">Croate</string>
+    <string name="ftp_server">Serveur FTP</string>
+    <string name="ftp_server_start_summary">D茅marrer le serveur FTP depuis la montre</string>
+    <string name="ftp_server_status">脡tat du serveur FTP</string>
+    <string name="username">Nom d\'utilisateur</string>
+    <string name="address">Adresse</string>
+    <string name="sony_speak_to_chat_summary">D茅sactiver l\'annulation de bruit automatiquement lors d\'un appel.</string>
+    <string name="yesterdays_activity">Activit茅 d\'hier</string>
+    <string name="pref_morning_updates_title">Mises 脿 jour le matin</string>
+    <string name="pref_morning_updates_summary">Afficher les mises 脿 jour tous les matins</string>
+    <string name="pref_morning_updates_categories_title">Les cat茅gories de mises 脿 jour du matin</string>
+    <string name="pref_morning_updates_categories_summary">Liste des cat茅gories 脿 afficher tous les matins</string>
+    <string name="bluetooth_calls_pairing">Pairage pour les appels Bluetooth</string>
+    <string name="bluetooth_calls_settings">Configuration des appels Bluetooth</string>
+    <string name="pref_display_caller_title">Afficher les informations du contact</string>
+    <string name="pref_display_caller_summary">Afficher le num茅ro ou le nom pour les appels entrants</string>
+    <string name="pref_pair_bluetooth_calls_summary">Appuyer ici pour commencer le processus de pairage</string>
+    <string name="pref_pair_bluetooth_calls_help_title">Comment recevoir des appels Bluetooth</string>
+    <string name="pref_pair_bluetooth_calls_help_1">1. Appuyer sur le bouton ci-dessous pour commencer le processus de pairage.</string>
+    <string name="pref_pair_bluetooth_calls_help_3">3. Activer les r茅glages d\'appel en Bluetooth ci-dessous.</string>
+    <string name="pref_pair_bluetooth_calls_help_warning">AVERTISSEMENT鈥�: Si vous activez les appels Bluetooth sans faire le pairage avec la seconde instance, les notifications d\'appel pourraient ne pas marcher comme pr茅vu.</string>
+    <string name="bluetooth_calls">Appels Bluetooth</string>
+    <string name="pref_summary_receive_calls_watch">Passer et recevoir des appels directement depuis la montre</string>
+    <string name="pref_pair_bluetooth_calls_title">Coupler pour les appels Bluetooth</string>
+    <string name="pref_pair_bluetooth_calls_help_summary">Afin de recevoir les appels en Bluetooth, vous devez pairer votre t茅l茅phone avec une seconde instance de la montre.</string>
+    <string name="pref_pair_bluetooth_calls_help_2">2. Rendez-vous dans les r茅glages Bluetooth de votre t茅l茅phone, et pairez-le avec le nouvel appareil qui apparaitra (nom similaire 脿 votre montre existante, mais avec un suffixe, du style \"Amazfit GTR 4 - AFC8\".</string>
+    <string name="pref_title_notification_cache_while_disconnected">Garder en cache lorsque hors de port茅e</string>
+    <string name="pref_summary_notification_cache_while_disconnected">Envoyer les notifications manqu茅es lorsqu\'un appareil se reconnecte apr猫s avoir 茅t茅 hors de port茅e</string>
+    <string name="pref_switch_controls_anc_ambient_off">Annulation de bruit 鈫愨啋 Ambiance 鈫愨啋 Off</string>
+    <string name="single_tap">Appui simple</string>
+    <string name="double_tap">Double appui</string>
+    <string name="triple_tap">Triple appui</string>
+    <string name="long_press">Appui long</string>
+    <string name="continue_pressing">Continuer 脿 presser</string>
+    <string name="quick_attention">Aper莽u rapide</string>
+    <string name="sony_button_mode_help_title">Bouton Modes - Aide</string>
+    <string name="sony_button_mode_help_summary">Description du mode de chaque bouton</string>
+    <string name="prefs_shortcut_cards">Cartes de raccourcis</string>
+    <string name="menuitem_forecast">Pr茅vision</string>
+    <string name="menuitem_vo2_max">VO鈧� Max</string>
+    <string name="menuitem_recommendation">Recommendation</string>
+    <string name="menuitem_cards">Cartes</string>
+    <string name="menuitem_mi_ai">MI AI</string>
+    <string name="zepp_os_watchface_minimalist">Minimal</string>
+    <string name="zepp_os_watchface_vibrant">Vibre</string>
+    <string name="zepp_os_watchface_business_style">Business Style</string>
+    <string name="zepp_os_watchface_rotating_earth">Rotating Earth</string>
+    <string name="zepp_os_watchface_emerald_moonlight">Emerald Moonlight</string>
+    <string name="zepp_os_watchface_superposition">superposition</string>
+    <string name="menuitem_aqi">Index de la Qualit茅 de l\'Air</string>
+    <string name="zepp_os_watchface_rush">Rapide</string>
+    <string name="menuitem_last_workout">Dernier exercice</string>
+    <string name="menuitem_total_workout">Total des exercices</string>
+    <string name="zepp_os_watchface_red_fantasy">Red Fantasy</string>
+    <string name="zepp_os_watchface_multiple_data">Donn茅es multiples</string>
+    <string name="zepp_os_watchface_simplicity_data">Donn茅es simplifi茅es</string>
+    <string name="prefs_shortcut_cards_summary">Cartes de raccourcis visibles en glissant vers la droite sur l\'茅cran de la montre. Lorsque l\'app est en fonctionnement, les cartes auto-g茅n茅r茅es ne sont pas affect茅s par ce r茅glage.</string>
+    <string name="fw_upgrade_notice_amazfit_gtr3_pro">Vous 锚tes sur le point d\'installer le micrologiciel %s dans votre Amazfit GTR 3 Pro.
+\n
+\nVotre bracelet red茅marrera apr猫s l\'installation du fichier .zip.
+\n
+\nA VOS RISQUES ET P脡RILS !</string>
+    <string name="devicetype_amazfit_gtr3_pro">Amazfit GTR 3 Pro</string>
+    <string name="devicetype_sony_wh_1000xm5">Sony WH-1000XM5</string>
+    <string name="fossil_hr_confirm_connection">Merci de confirmer sur le bracelet</string>
+    <string name="fossil_hr_connection_not_confirmed">Connexion non confirm茅e sur l\'appareil, utilisation du mode non identifi茅</string>
+    <string name="fossil_hr_pairing_successful">Pairage avec l\'appareil r茅ussie</string>
+    <string name="fossil_hr_pairing_failed">Pairage avec l\'appareil rat茅</string>
+    <string name="fossil_hr_confirmation_skipped">Passer la confirmation sur l\'appareil</string>
+    <string name="fossil_hr_confirmation_timeout">Hors d茅lai pour la confirmation</string>
+    <string name="gpx_route_upload_failed">L\'envoi du trac茅 Gpx a 茅chou茅</string>
+    <string name="kind_gpx_route">Trac茅 GPX</string>
+    <string name="debug_companion_show_associated">Afficher les appareils associ茅s</string>
+    <string name="activity_error_share_failed">Le partage du fichier a 茅chou茅.</string>
+    <string name="dev_tools">Outils Dev</string>
+    <string name="debug_companion_pair_current">Associer l\'appareil actuel comme compagnon</string>
+    <string name="gpx_route_upload_complete">L\'envoi du trac茅 Gpx a r茅ussi</string>
+    <string name="activity_detail_share_raw_details">Partager les d茅tails bruts</string>
+    <string name="activity_detail_share_gps_label">Partager un trac茅 GPS</string>
+    <string name="activity_detail_share_raw_summary">Partager le r茅sum茅 brut</string>
+    <string name="gpx_route_upload_in_progress">Envoi du trac茅 Gpx en cours</string>
+    <string name="contact_delete_confirm_title">Effacer le contact</string>
+    <string name="title_activity_contact_details">D茅tails du contact</string>
+    <string name="title_activity_set_contacts">Configurer les contacts</string>
+    <string name="contact_delete_confirm_description">Etes-vous s没r de vouloir effacer \'%1$s\'鈥痋?</string>
+    <string name="contact_name">Nom</string>
+    <string name="contact_missing_name">Le nom du contact est vide</string>
+    <string name="contact_missing_number">Le num茅ro du contact est vide</string>
+    <string name="pref_contacts_title">Contacts</string>
+    <string name="contact_no_free_slots_description">L\'appareil n\'a plus d\'emplacement libre pour des contacts (nombre d\'emplacements鈥�: %1$s)</string>
+    <string name="contact_phone_number">Num茅ro de t茅l茅phone</string>
+    <string name="pref_contacts_summary">Configurer les contacts sur la montre</string>
+    <string name="intent_api_allow_debug_commands_title">Permettre les commandes de d茅buggage</string>
+    <string name="intent_api_allow_debug_commands_summary">Permettre le d茅clenchement des commandes de d茅buggage depuis l\'API d\'intentions</string>
+    <string name="pref_header_navigation">Navigation</string>
+    <string name="pref_summary_navigation_forward">Transf猫re les instructions de navigation 脿 la montre</string>
+    <string name="pref_title_navigation_forward">Envoyer la navigation 脿 la montre</string>
+    <string name="voice_service_package_title">Paquet des services audio</string>
+    <string name="voice_service_package_summary">Application qui contient le service qui g猫re les commandes vocales</string>
+    <string name="voice_service_class_title">Classe des services audio</string>
+    <string name="voice_service_class_summary">Chemin d\'acc猫s au service qui g猫re les commandes vocales</string>
+    <string name="voice_service">Service vocal</string>
+    <string name="pref_app_logs_summary">Activer les logs des apps de la montre</string>
+    <string name="pref_app_logs_start_summary">Activer les logs pour les apps de la montre</string>
+    <string name="pref_app_logs_title">Logs de l\'app</string>
+    <string name="pref_app_logs_stop_summary">Arr锚ter les logs pour les apps de la montre</string>
+    <string name="fwapp_install_device_not_supported">Le fichier ne peut pas 锚tre install茅, l\'appareil n\'est pas support茅.</string>
+    <string name="share_screenshot">Partager la capture 茅cran</string>
+    <string name="screenshot_taken">Capture 茅cran faite</string>
+    <string name="stress_relaxed">Relax茅</string>
+    <string name="stress_mild">Moyen</string>
+    <string name="stress_moderate">Mod茅r茅</string>
+    <string name="stress_high">脡lev茅</string>
+    <string name="charts_legend_stress_average">Stress moyen</string>
+    <string name="watchface_widget_type_uv_index">Index UV</string>
+    <string name="action_changelog">Journal des modifications</string>
+    <string name="pref_title_banglejs_phone_gps_enbale">Utiliser les donn茅es GPS du t茅l茅phone</string>
+    <string name="pref_summary_banglejs_phone_gps_network_only">Utiliser uniquement le r茅seau pour d茅terminer la position. Cela r茅duit la consommation d\'茅nergie au prix de la pr茅cision. Une reconnexion est n茅cessaire.</string>
+    <string name="pref_summary_banglejs_phone_gps_enbale">Utiliser les donn茅es GPS du t茅l茅phone 脿 la place des donn茅es de l\'appareil bangle</string>
+    <string name="pref_title_banglejs_phone_gps_update_interval">Intervalle de mise 脿 jour du GPS</string>
+    <string name="pref_summary_banglejs_phone_gps_update_interval">Intervalle de mise 脿 jour de la position GPS, en ms</string>
+    <string name="preview_image">Aper莽u</string>
+    <string name="changelog_title">Quoi de neuf</string>
+    <string name="description">Description</string>
+    <string name="status_icon">Ic么ne d\'茅tat</string>
+    <string name="changelog_show_full">Plus鈥�</string>
+    <string name="changelog_full_title">Journal des modifications</string>
+    <string name="changelog_ok_button">OK</string>
+    <string name="pref_title_banglejs_phone_gps_network_only">Utiliser le r茅seau uniquement pour d茅terminer la position</string>
+    <string name="title">Titre</string>
+    <string name="pai_chart_per_month">PAI par mois</string>
+    <string name="pai_plus_num">+%d</string>
+    <string name="num_min">%d min</string>
+    <string name="pai_chart_per_week">PAI par semaine</string>
+    <string name="pai_total">PAI Total</string>
+    <string name="pai_day">Augmentation journali猫re du PAI</string>
+    <string name="loyalty_cards_install">Installer Catima</string>
+    <string name="loyalty_cards_sync_groups">Groupes 脿 synchroniser</string>
+    <string name="loyalty_cards_install_catima_fail">Impossible d\'ouvrir l\'app store pour installer Catima</string>
+    <string name="loyalty_cards_sync_summary">Toucher pour synchroniser les cartes dans la montre</string>
+    <string name="loyalty_cards_catima_not_installed">Catima est requis pour g茅rer les cartes de fid茅lit茅</string>
+    <string name="loyalty_cards_sync_options">Options de synchronisation</string>
+    <string name="loyalty_cards_sync">Synchronisation</string>
+    <string name="loyalty_cards_catima">Catima</string>
+    <string name="loyalty_cards_open_catima">D茅marrer Catima</string>
+    <string name="loyalty_cards_catima_permissions_title">Autorisations manquantes</string>
+    <string name="loyalty_cards_catima_permissions_summary">Gadgetbridge a besoin d\'une autorisation de lecture sur les cartes Catima pour les synchroniser. Appuyer sur ce bouton pour l\'autoriser.</string>
+    <string name="loyalty_cards_catima_package">Nom du logiciel Catima</string>
+    <string name="loyalty_cards_sync_groups_only">Synchroniser uniquement certains groupes</string>
+    <string name="loyalty_cards_sync_archived">Synchroniser les cartes archiv茅es</string>
+    <string name="loyalty_cards_sync_starred">Synchroniser uniquement les cartes 茅toil茅es</string>
+    <string name="loyalty_cards_sync_title">Synchroniser les cartes de fid茅lit茅</string>
+    <string name="loyalty_cards_syncing">Synchronisation de %d des cartes de fid茅lit茅 dans l\'appareil</string>
+    <string name="loyalty_cards_catima_not_compatible">La version install茅e de Catima est incompatible avec Gadgetbridge. Merci de mettre 脿 jour Catima et Gadgetbridge en derni猫re version.</string>
+    <string name="loyalty_cards">Cartes de fid茅lit茅</string>
+    <string name="pref_title_mb_intents">Diffuser les annonces Media directement</string>
+    <string name="pref_summary_mb_intents">Activer si le contr么le des m茅dias ne marche pas dans certaines applications</string>
+    <string name="devicetype_bohemic_smart_bracelet">Bohemic Smart Bracelet</string>
+    <string name="devicetype_vivomove_hr">Garmin Vivomove HR</string>
+    <string name="busy_task_fetch_stress_data">R茅cup茅ration des donn茅es de stress</string>
+    <string name="busy_task_fetch_hr_data">R茅cup茅ration des donn茅es de rythme cardiaque</string>
+    <string name="busy_task_fetch_sports_summaries">R茅cup茅ration des r茅sum茅s d\'exercice</string>
+    <string name="busy_task_fetch_sports_details">R茅cup茅ration des d茅tails d\'activit茅</string>
+    <string name="busy_task_fetch_debug_logs">R茅cup茅ration des logs de d茅pannage</string>
+    <string name="busy_task_fetch_pai_data">R茅cup茅ration des donn茅es RAI</string>
+    <string name="busy_task_fetch_spo2_data">R茅cup茅ration des donn茅es SpO2</string>
+    <string name="busy_task_fetch_sleep_respiratory_rate_data">R茅cup茅ration des donn茅es du rythme respiratoire</string>
+    <string name="devicetype_casiogwb5600">Casio GW-B5600</string>
+    <string name="devicetype_casiogmwb5000">Casio GMW-B5000</string>
+    <string name="activity_prefs_goals">Objectifs</string>
+    <string name="error_no_bluetooth_scan">L\'acc猫s au Bluetooth doit 锚tre autoris茅 et activ茅 pour que le scan fonctionne correctement</string>
+    <string name="error_no_bluetooth_connect">L\'acc猫s au Bluetooth doit 锚tre autoris茅 et activ茅 pour que le scan fonctionne correctement</string>
+    <string name="permission_request">%1$s vous permet d\'envoyer des messages et d\'autres donn茅es depuis Android vers votre appareil. Pour cela il a besoin de permission pour acc茅der 脿 ces donn茅es, sans quoi cela risque de ne pas fonctionner correctement.
+\n
+\nVous allez maintenant pouvoir configurer ces autorisations.
+\n
+\nMerci d\'appuyer sur \'%2$s\' pour continuer.</string>
+    <string name="activity_type_handball">Handball</string>
+    <string name="activity_type_windsurfing">Surf 脿 voile</string>
+    <string name="activity_type_kitesurfing">Kitesurfing</string>
+    <string name="activity_type_rugby">Rugby</string>
+    <string name="activity_type_baseball">Baseball</string>
+    <string name="activity_type_squash">Squash</string>
+    <string name="activity_type_skiing">Ski</string>
+    <string name="activity_type_snowboarding">Snowboard</string>
+    <string name="activity_type_riding">Course 脿 cheval</string>
+    <string name="activity_type_hockey">Hockey</string>
+    <string name="withings_calibration_text_hours">Merci d\'utiliser la molette ci-dessous pour aligner la main sur le 12.</string>
+    <string name="withings_calibration_text_minutes">Maintenant utiliser la molette pour aligner la main des minutes sur le 12.</string>
+    <string name="withings_calibration_text_activity_target">Pour finir aligner la main d\'activit茅 sur 100%. Attention cette main ne bouge que dans le sens des aiguilles d\'une montre.</string>
+    <string name="activity_type_weightlifting">Halth茅rophilie</string>
+    <string name="activity_type_dancing">Danse</string>
+    <string name="activity_type_icehockey">Hockey sur glace</string>
+    <string name="activity_type_iceskating">Patinage sur glace</string>
+    <string name="activity_type_golf">Golf</string>
+    <string name="activity_type_other">Autre</string>
+    <string name="withings_steel_hr">Withings Steel HR</string>
+    <string name="withings_bt_calibration_previous">Pr茅c茅dent</string>
+    <string name="withings_bt_calibration_next">Suivant</string>
+    <string name="activity_type_tennis">Tennis</string>
+    <string name="activity_type_surfing">Surf</string>
+    <string name="activity_type_football">Football</string>
+    <string name="drag_handle">Poignets</string>
+    <string name="pref_theme_dynamic">Couleurs dynamiques</string>
+    <string name="pref_canned_message">Message</string>
+    <string name="find_my_phone_found_it">TROUV脡</string>
+    <string name="preferences_miband_1_2_settings" tools:ignore="TypographyFractions">R茅glages Mi Band 1/2</string>
+    <string name="pref_activity_full_sync_trigger_warning">Cela va d茅clencher une synchronisation compl猫te des donn茅es d\'activit茅 de l\'appareil. Cela peut prendre quelques minutes.</string>
+    <string name="pref_activity_full_sync_trigger_summary">D茅clenche une sychronisation compl猫te de tous les donn茅es d\'activit茅</string>
+    <string name="pref_activity_full_sync_trigger_title">Synchronisation compl猫te</string>
+    <string name="preferences_miband_1_2_warning">Avertissement : Ces r茅glages s\'appliquent uniquement aux Mi Band 1 et 2.</string>
+    <string name="pref_show_changelog">Afficher le journal des modifications au d茅marrage</string>
+    <string name="pref_show_changelog_summary">Afficher le journal des modifications depuis la derni猫re version apr猫s la mise 脿 jour de GadgetBridge</string>
+    <string name="pref_theme_dynamic_colors_not_available_warning">Les coouleurs dynamiques ne sont pas disponibles sur votre appareil, seul Android 12 ou sup. le supporte. Gadgetbridge utilisera les couleurs par d茅faut Material 3.</string>
+    <string name="pref_theme_dynamic_colors_explanation">Remarque鈥�: pour le th猫me avec des couleurs dynamiques, vous devez activer les couleurs de papier-peint ou la palette de couleurs dans les r茅glages d\'apparenci d\'Android 12 ou sup茅rieur. Si vous ne le faites pas, Gadgetbridge utilisera les couleurs par d茅faut de Material 3.</string>
+    <string name="menuitem_zepp_coach">Zepp Coach</string>
+    <string name="devicetype_amazfit_bip3_pro">Amazfit Bip 3 Pro</string>
+    <string name="fw_upgrade_notice_amazfitbip3pro">Vous 锚tes sur le point d\'installer le micro-logiciel %s dans votre Amazfit Bip 3 Pro.
+\n
+\nAssurez-vous d\'installer le fichier .fw, et ensuite le fichier .res. Votre montre red茅marrera apr猫s l\'installation du fichier .fw.
+\n
+\nRemarque : Pas besoin d\'installer le fichier .res si c\'est exactement le m锚me que le pr茅c茅dent install茅.
+\n
+\nA VOS RISQUES ET P脡RILS !</string>
+    <string name="fw_upgrade_notice_amazfit_cheetah_pro">Vous 锚tes sur le point d\'installer le micro-logiciel %s dans votre Amazfit Cheetah Pro.
+\n
+\nVotre bracelet va red茅marrer apr猫s l\'installation du fichier .zip.
+\n
+\nA VOS RISQUES ET P脡RILS !</string>
+    <string name="devicetype_amazfit_cheetah_pro">Amazfit Cheetah Pro</string>
+    <string name="devicetype_amazfit_cheetah_square">Amazfit Cheetah (Square)</string>
+    <string name="devicetype_amazfit_falcon">Amazfit Falcon</string>
+    <string name="devicetype_amazfit_cheetah_round">Amazfit Cheetah (Round)</string>
+    <string name="devicetype_amazfit_bip5">Amazfit Bip 5</string>
+    <string name="fw_upgrade_notice_zepp_os">Vous 锚tes sur le point d\'installer le micro-logiciel %s dans votre %s.
+\n
+\nVotre montre va red茅marrer apr猫s l\'installation du fichier .zip.
+\n
+\nA VOS RISQUES ET P脡RILS !</string>
+    <string name="devicetype_amazfit_trex_ultra">Amazfit T-Rex Ultra</string>
+    <string name="devicetype_amazfit_gtr_mini">Amazfit GTR Mini</string>
+    <string name="device_experimental">EXP脡RIMENTAL</string>
+    <string name="info_menu_structure_removed">Structure de menu enlev茅</string>
+    <string name="error_menu_companion_not_installed">\'Compagnon HR\' probablement non install茅</string>
+    <string name="info_fossil_rebuild_watchface_custom_menu">Merci de reconstruire l\'茅cran de la montre pour un menu personnalis茅</string>
+    <string name="info_menu_structure_set">Structure JSON d茅finie dans GB</string>
+    <string name="info_menu_structure_contents">Structure de menu鈥�: %s</string>
+    <string name="error_invalid_menu_structure">Structure JSON invalide</string>
+    <string name="button_open_menu_companion">Ouvrir le menu de l\'app compagnon</string>
+    <string name="button_reset_menu_structure">Remettre 脿 z茅ro la structure de menu</string>
+    <string name="english_au">Anglais (Australien)</string>
+    <string name="english_gb">Anglais (Royaume-Uni)</string>
+    <string name="english_ca">Anglais (Canadien)</string>
+    <string name="english_in">Anglais (Indien)</string>
+    <string name="english_us">Anglais (Am茅ricain)</string>
+    <string name="spanish_es">Espagnol (Espagne)</string>
+    <string name="spanish_mx">Espagnol (Mexicain)</string>
+    <string name="spanish_us">Espagnol (Am茅ricain)</string>
+    <string name="french_ca">Fran莽ais (Canadien)</string>
+    <string name="french_fr">Fran莽ais (France)</string>
+    <string name="updatefirmwareoperation_updateproblem_free_space">Cet appareil n\'a pas assez d\'espace libre</string>
+    <string name="text_receiver_activity_title">Envoyer un message texte 脿 l\'appareil</string>
+    <string name="updatefirmwareoperation_failed_low_mtu">Le MTU聽actuel pour %1$d est trop faible, merci de r茅gler un MTU聽茅lev茅 dans les r茅glages de l\'appareil. et puis d茅connecter/reconnecter l\'appareil.</string>
+    <string name="devicetype_miband2_hrx">Mi Band HRX</string>
+    <string name="new_discover_activity_title">Activer la d茅couverte de nouvelles activit茅s</string>
+    <string name="prefs_wena3_receive_calls_title">R茅glages pour les appels entrants</string>
+    <string name="prefs_wena3_button_action_name_start_timer">D茅marrer le chronom猫tre</string>
+    <string name="prefs_wena3_menu_icon_hint">L\'ic么ne des r茅glages est toujours affich茅e en derni猫re</string>
+    <string name="prefs_wena3_notification_vibration_repetition_3">3 fois</string>
+    <string name="prefs_wena3_button_action_name_qrio_lock">Verrouillage Qrio</string>
+    <string name="prefs_wena3_auto_power_off_turn_off_time_item">Heure d\'extinction</string>
+    <string name="prefs_wena3_vibration_short">Court</string>
+    <string name="prefs_wena3_home_icon_name_calories">Calories</string>
+    <string name="prefs_home_icon_left_item">Gauche</string>
+    <string name="prefs_wena3_led_none">Aucune LED</string>
+    <string name="prefs_wena3_vibration_step_down">Descente</string>
+    <string name="prefs_wena3_vibration_smart_item">Vibration intelligente</string>
+    <string name="prefs_wena3_home_icon_name_riiiver">Riiiver</string>
+    <string name="pref_message_privacy_mode_bodyonly">Cacher uniquement le contenu</string>
+    <string name="prefs_wena3_hint_background_sync">Permet 脿 Wena de p茅riodiquement demander 脿 Gadgetbridge de r茅cup茅rer les donn茅es depuis votre appareil</string>
+    <string name="prefs_wena3_home_icon_name_suica">Balance Suica</string>
+    <string name="prefs_wena3_vibration_strength_item_medium">Moyen</string>
+    <string name="prefs_wena3_title_activity">R茅glages d\'activit茅</string>
+    <string name="prefs_wena3_vibration_continuous">Continu</string>
+    <string name="prefs_wena3_home_icon_name_pedometer">Nombre de pas</string>
+    <string name="prefs_wena3_home_icon_name_qrio">Qrio</string>
+    <string name="new_discover_activity_description">Activer le nouveau service de d茅couverte, ce qui devrait r茅soudre le probl猫me de d茅couverte. D茅sactiver cette option en cas de probl猫mes si vous avez des probl猫mes pour d茅couvrir ou pairer votre appareil.</string>
+    <string name="white">Blanc</string>
+    <string name="prefs_wena3_home_icon_name_clock">Heure actuelle</string>
+    <string name="prefs_wena3_item_background_sync">Activit茅 de synchronisation en tache de fond</string>
+    <string name="prefs_wena3_vibration_strength_item_weak">Faible</string>
+    <string name="prefs_wena3_home_icon_title">Ic么nes de l\'茅cran d\'accueil</string>
+    <string name="prefs_wena3_item_weather_statusbar">M茅to茅 dans la barre d\'茅tat</string>
+    <string name="temperature_scale_celsius">Celsius</string>
+    <string name="prefs_wena3_vibration_step_up">Mont茅e</string>
+    <string name="prefs_home_icon_right_item">Droite</string>
+    <string name="prefs_wena3_vibration_basic">Basique</string>
+    <string name="prefs_wena3_title_screen">R茅glages d\'affichage</string>
+    <string name="prefs_wena3_item_large_font">Taille de texte plus grande</string>
+    <string name="prefs_wena3_home_icon_name_energy">脡nergie corporelle</string>
+    <string name="prefs_wena3_notification_vibration_repetition_2">Deux fois</string>
+    <string name="prefs_wena3_auto_power_off_hint">L\'appareil s\'茅teindra et s\'allumera automatiquement selon les intervalles configur茅s</string>
+    <string name="prefs_wena3_hint_lift_wrist">Allumer l\'茅cran lorsque vous regardez votre poign茅e</string>
+    <string name="prefs_wena3_hint_large_font">Augmenter la taille du texte dans le calendrier, les notifications, etc.</string>
+    <string name="prefs_wena3_button_action_item_double">Double appui</string>
+    <string name="prefs_wena3_vibration_rapid">Rapide</string>
+    <string name="prefs_wena3_button_action_name_qrio_unlock">D茅verrouillage Qrio</string>
+    <string name="temperature_scale_cf">脡chelle de temp茅rature</string>
+    <string name="prefs_wena3_notification_vibration_repetition_1">Une fois</string>
+    <string name="prefs_wena3_receive_calls_item">Notifications pour les appels entrants</string>
+    <string name="prefs_wena3_hint_weather_statusbar">Afficher l\'ic么ne des conditions en cours dans le coin sup茅rieur gauche de l\'茅cran d\'accueil</string>
+    <string name="prefs_wena3_home_icon_name_wena_pay">Wena Pay</string>
+    <string name="prefs_wena3_button_action_item">Bouton Action</string>
+    <string name="prefs_wena3_auto_power_off_turn_on_time_item">Heure d\'allumage</string>
+    <string name="prefs_wena3_notification_default_call_vibration">Vibration pour les appels entrants</string>
+    <string name="green">Vert</string>
+    <string name="yellow">Jaune</string>
+    <string name="devicetype_sony_wena3">Sony Wena 3</string>
+    <string name="prefs_wena3_notification_default_call_led">Couleur de la LED聽pour les appels entrants</string>
+    <string name="red">Rouge</string>
+    <string name="cyan">Cyan</string>
+    <string name="prefs_wena3_vibration_siren">Sir猫ne</string>
+    <string name="temperature_scale_cf_summary">Choisir si l\'appareil utilise Celsius ou Farenheit.</string>
+    <string name="prefs_wena3_menu_icon_title">Ic么nes du Menu</string>
+    <string name="prefs_wena3_notification_default_call_vibration_repetition">R茅p茅ter les vibrations pour les appels entrants</string>
+    <string name="prefs_wena3_notification_default_vibration">Vibration de notification</string>
+    <string name="prefs_wena3_notification_vibration_repetition_0">Infiniment</string>
+    <string name="prefs_wena3_notification_vibration_repetition_4">4 fois</string>
+    <string name="temperature_scale_fahrenheit">Fahrenheit</string>
+    <string name="prefs_wena3_vibration_strength_item">Force des vibrations</string>
+    <string name="prefs_wena3_receive_calls_hint">Si 茅teint, vous ne serez pas notifi茅 des appels entrants sur le Wena</string>
+    <string name="prefs_wena3_button_action_item_long">Appui long</string>
+    <string name="prefs_wena3_menu_icon_name_payment">Payement</string>
+    <string name="latvian">Letton</string>
+    <string name="prefs_wena3_status_page_title">Organisation de la page d\'茅tat</string>
+    <string name="prefs_wena3_title_alarm">R茅glages de l\'alarme</string>
+    <string name="prefs_wena3_notification_default_vibration_repetition">R茅p茅ter les vibrations de notification</string>
+    <string name="prefs_home_icon_center_item">Centre</string>
+    <string name="prefs_wena3_hint_rich_design">Ajoute des rectangles ronds autour des ic么nes de l\'茅cran d\'accueil</string>
+    <string name="prefs_wena3_notification_default_led">Couleur de la LED de notification</string>
+    <string name="prefs_wena3_auto_power_off_item">Extinction automatique</string>
+    <string name="prefs_wena3_home_icon_name_edy">Balance Edy</string>
+    <string name="prefs_wena3_vibration_warning">Avertissement</string>
+    <string name="prefs_wena3_smart_alarm_margin_item">Marge de l\'alarme intelligente</string>
+    <string name="prefs_wena3_item_rich_design">Utiliser Rich Design</string>
+    <string name="prefs_wena3_day_start_hour_item">Le jour commence 脿</string>
+    <string name="prefs_wena3_notification_per_app_settings_title">R茅glages de notification par app</string>
+    <string name="prefs_wena3_vibration_none">Aucune vibration</string>
+    <string name="purple">Pourpre</string>
+    <string name="prefs_wena3_vibration_strength_item_strong">Fort</string>
+    <string name="prefs_wena3_vibration_triple">Triple</string>
+    <string name="blue">Bleu</string>
+    <string name="prefs_wena3_button_action_name_activity_screen">脡cran d\'activit茅</string>
+    <string name="prefs_wena3_notification_settings_title">R茅glages de notification</string>
+    <string name="devicetype_sony_wf_1000xm5">Sony WF-1000XM5</string>
+    <string name="common_symbols">Symboles communs</string>
+    <string name="pref_title_notifications_ignore_work_profile">Ignorer les notifications en profil travail</string>
+    <string name="pref_summary_notifications_ignore_work_profile">Ne pas envoyer les notifications des apps dans le profil travail 脿 la montre</string>
+    <string name="zepp_os_watchface_pure_white">Blanc pur</string>
+    <string name="call_rejection_method_ignore">Ignorer (silence)</string>
+    <string name="avgStrokeRate">Rythme moyen de course</string>
+    <string name="zepp_os_watchface_the_ultima">L\'ultima</string>
+    <string name="zepp_os_watchface_city_of_speed">Vitesse urbaine</string>
+    <string name="menuitem_headphone">脡couteur</string>
+    <string name="femometer_measurement_mode_normal">Mode normal (60s - 90s)</string>
+    <string name="call_rejection_method_reject">Rejet</string>
+    <string name="zepp_os_watchface_free_combination">Combinaison libre</string>
+    <string name="menuitem_body_composition">Composition corporelle</string>
+    <string name="menuitem_zepp_pay">Zepp Pay</string>
+    <string name="pref_call_rejection_method_title">M茅thode de rejet d\'appel</string>
+    <string name="busy_task_fetch_statistics">R茅cup茅ration des statistiques</string>
+    <string name="femometer_measurement_mode_quick">Mode Rapide (30s)</string>
+    <string name="zepp_os_watchface_starry_sky">Ciel 茅toil茅</string>
+    <string name="devicetype_amazfit_balance">Balance Amazfit</string>
+    <string name="fossil_hr_nav_app_not_installed_notify_text">La navigation a d茅marr茅e mais pas d\'app de navigation install茅e dans la montre. Merci d\'en installer une depuis le Gestionnaire d\'App.</string>
+    <string name="yard">yard</string>
+    <string name="strokes_minute">str/min</string>
+    <string name="femometer_measurement_mode_title">M茅thode de mesure</string>
+    <string name="pref_call_rejection_method_summary">Quelle action 脿 faire quand un appel entrant est rejet茅 depuis la montre</string>
+    <string name="devicetype_amazfit_active_edge">Amazfit Active Edge</string>
+    <string name="busy_task_fetch_temperature">R茅cup茅ration des donn茅es de temp茅rature</string>
+    <string name="femometer_measurement_mode_precise">Mode Pr茅cis (3 min)</string>
+    <string name="zepp_os_watchface_vast_sky">Ciel clair</string>
+    <string name="menuitem_workout_shortcuts">Raccourcis Exercice</string>
+    <string name="maxStrokeRate">Vitesse maximale de course</string>
+    <string name="zepp_os_watchface_lightning_flash">Flash lumineux</string>
+    <string name="laneLength">Longueur de ligne</string>
+    <string name="charts_legend_spo2_average">Moyenne de l\'oxyg猫ne dans le sang</string>
+    <string name="strokes">Vitesse totale</string>
+    <string name="menuitem_apps_shortcuts">Raccourcis Apps</string>
+    <string name="zepp_os_watchface_guider">Guide</string>
+    <string name="menuitem_readiness">Pr茅paration</string>
+    <string name="devicetype_amazfit_active">Amazfit Active</string>
+    <string name="menuitem_thermometer">Thermom猫tre</string>
+    <string name="devicetype_femometer_vinca2">Femometer Vinca II</string>
+    <string name="fossil_hr_nav_app_not_installed_notify_title">App de navigation non install茅e sur le t茅l茅phone</string>
+    <string name="pref_title_casio_alert_other">Alerte pour les autres notifications</string>
+    <string name="pref_title_casio_alert_calendar">Alerte pour les notifications du calendrier</string>
+    <string name="pref_summary_casio_alert_call">Alerte (vibre/bipe) pour les appels entrants</string>
+    <string name="pref_title_casio_alert_call">Alerte pour les appels entrants</string>
+    <string name="pref_title_casio_alert_email">Alerte pour les notifications d\'email</string>
+    <string name="pref_summary_casio_alert_email">Alerte (vibre/bipe) pour les notifications d\'email</string>
+    <string name="pref_summary_casio_alert_sms">Alerte (vibre/bipe) pour les notifications SMS (message texte)</string>
+    <string name="pref_title_preview_message_in_title">Montrer un aper莽u du message dans le titre</string>
+    <string name="pref_summary_casio_alert_calendar">Alerte (vibre/bipe) pour les notifications du calendrier</string>
+    <string name="pref_summary_casio_alert_other">Alerte (vibre/bipe) pour les notifications de la cat茅gorie autre</string>
+    <string name="pref_title_prefix_notification_with_app">Nom de l\'app dans la notification</string>
+    <string name="pref_title_casio_alert_sms">Alerte pour les notifications SMS</string>
+    <string name="devicetype_mi_watch_color_sport">Mi Watch Color Sport</string>
+    <string name="pref_title_navigation_apps">Applications de navigation</string>
+    <string name="devicetype_miband8">Xiaomi Smart Band 8</string>
+    <string name="menuitem_running">Course 脿 pieds</string>
+    <string name="pref_sleep_mode_schedule_title">Planification de l\'heure de coucher</string>
+    <string name="bedtime">Heure du coucher</string>
+    <string name="devicetype_pixoo">Pixoo</string>
+    <string name="pref_title_navigation_prefs">Pr茅f茅rences de navigation</string>
+    <string name="menuitem_focus">Focus</string>
+    <string name="pref_navigation_app_gmaps">Google Maps</string>
+    <string name="pref_summary_prefix_notification_with_app">Ajouter un pr茅fixe au titre de la notification avec le nom de l\'application source</string>
+    <string name="devicetype_redmiwatch3active">Redmi Watch 3 Active</string>
+    <string name="serial_number">Num茅ro de s茅rie</string>
+    <string name="danish">Danois</string>
+    <string name="pref_summary_osmand_packagename">Utilis茅 pour s茅lectionner la version d\'OsmAnd 脿 laquelle se connecter</string>
+    <string name="wake_up_time">R茅veil</string>
+    <string name="devicetype_miband7pro">Xiaomi Smart Band 7 Pro</string>
+    <string name="menuitem_stats">Stats</string>
+    <string name="devicetype_xiaomi_watch_lite">Xiaomi Watch Lite</string>
+    <string name="pref_summary_preview_message_in_title">Afficher un aper莽u du message dans le titre de la notification comme permis par l\'appareil</string>
+    <string name="pref_title_osmand_packagename">nom du paquet OsmAnd</string>
+    <string name="pref_navigation_app_osmand">OsmAnd(+)</string>
+    <string name="pref_sleep_mode_schedule_summary">Envoyer un rappel et se mettre en veille 脿 l\'heure du coucher. A l\'heure pr茅vue de r茅veil, l\'alarme de r茅veil sonnera.</string>
+    <string name="menuitem_alerts">Alertes</string>
+    <string name="devicetype_xiaomi_watch_s1_active">Xiaomi Watch S1 Active</string>
+    <string name="pref_device_action_dnd_on">Ne pas d茅ranger - Actif</string>
+    <string name="pref_title_goal_secondary">Objectif secondaire</string>
+    <string name="pref_vitality_score_7_day_summary">Envoyer une notification quand votre score de vitalit茅 atteint 30, 60 ou 100 sur les 7 derniers jours</string>
+    <string name="pref_vitality_score_title">Score Vitalit茅</string>
+    <string name="pref_device_action_dnd_alarms">Ne pas d茅ranger - Alarmes uniquement</string>
+    <string name="pref_vitality_score_daily_summary">Envoyer une notification lorsque vous atteignez le maximum de points quotidiens de vitalit茅</string>
+    <string name="pref_summary_send_app_notifications">Envoyer les notifications de l\'application 脿 l\'appareil</string>
+    <string name="pref_device_action_dnd_priority">Ne pas d茅ranger - Prioritaire uniquement</string>
+    <string name="not_set">Non configur茅</string>
+    <string name="standing_time">Temps de veille</string>
+    <string name="active_time">Temps actif</string>
+    <string name="pref_title_send_app_notifications">Envoyer des notifications</string>
+    <string name="pref_vitality_score_daily_title">Progr猫s quotidien</string>
+    <string name="pref_vitality_score_7_day_title">Progr猫s sur 7 jours</string>
+    <string name="pref_device_action_dnd_off">Ne pas d茅ranger - Off</string>
+    <string name="wearmode_necklace">Collier (tour de cou)</string>
+    <string name="prefs_wearmode">Mode de port</string>
+    <string name="alarm_smart_wakeup_interval">Intervalle de r茅veil intelligent鈥�:</string>
+    <string name="prefs_disconnect_notification_summary">Notification sur l\'appareil quand il est d茅connect茅 du BT.</string>
+    <string name="prefs_phone_silent_mode">Mode silencieux sur le t茅l茅phone</string>
+    <string name="silent_mode_normal_vibrate">Normal/Vibreur</string>
+    <string name="silent_mode_normal_silent">Normal / Silencieux</string>
+    <string name="prefs_device_name">Nom de l\'appareil</string>
+    <string name="pref_summary_debug">Envoyer une demande de diagnostic 脿 l\'appareil Huawei</string>
+    <string name="devicetype_mijia_lywsd03">Mijia Temperature and Humidity Sensor 2</string>
+    <string name="devicetype_honor_band7">Honor Band 7</string>
+    <string name="devicetype_redmi_smart_band_pro">Redmi Smart Band Pro</string>
+    <string name="devicetype_nothingearstick">Nothing Ear (Stick)</string>
+    <string name="devicetype_nothing_cmf_watch_pro">CMF Watch Pro</string>
+    <string name="huawei_trusleep_summary_light">Surveillance du sommeil am茅lior茅</string>
+    <string name="devicetype_xiaomi_watch_s1">Xiaomi Watch S1</string>
+    <string name="activity_type_trekking">Randonn茅e</string>
+    <string name="activity_type_trail_run">Trail</string>
+    <string name="activity_type_wrestling">Lutte</string>
+    <string name="widget">Widget</string>
+    <string name="widget_screen">脡cran du widget</string>
+    <string name="prefs_active_noise_cancelling_transparency">Transparence</string>
+    <string name="sony_protocol_v3">Version 3</string>
+    <string name="huawei_alarm_smart_description">Ne pas d茅sactiver l\'option r茅veil intelligent.</string>
+    <string name="huawei_trusleep_title">HUAWEI TruSleep 鈩�</string>
+    <string name="huawei_trusleep_summary">Surveiller la qualit茅 de votre sommeil et de votre respiration en temps r茅el.
+\nAnalyser votre mode de sommeil afin de diagnostiquer efficacement 6 types de probl猫mes de sommeil.</string>
+    <string name="pref_enable_call_accept">Autoriser les appels</string>
+    <string name="pref_enable_call_reject_summary">Permettre le rejet des appels depuis l\'appareil</string>
+    <string name="pref_heartrate_automatic_enable">Permettre la mesure automatique du rythme cardiaque</string>
+    <string name="pref_force_options">Forcer les options</string>
+    <string name="pref_title_debug">Demande de d茅pannage</string>
+    <string name="devicetype_xiaomi_watch_s3">Xiaomi Watch S3</string>
+    <string name="pref_title_fossil_hr_nav_vibrate">Vibrer lors d\'une nouvelle instruction</string>
+    <string name="pref_force_connection_type_title">Forcer le type de connexion</string>
+    <string name="pref_force_connection_type_description">Vous pouvez essayer de forcer le type de connexion dans le cas o霉 votre appareil ne r茅pond pas 脿 Gadgetbridge</string>
+    <string name="pref_force_connection_type_ble">Bluetooth LE</string>
+    <string name="pref_force_connection_type_bt_classic">Bluetooth Classic</string>
+    <string name="activity_info">Info d\'activit茅</string>
+    <string name="warning_missing_notification_permission">N\'a pas pu envoyer les notifications en cours en raison d\'autorisations manquantes</string>
+    <string name="widget_layout_single">1 widget</string>
+    <string name="pref_summary_fossil_hr_nav_vibrate">Si la montre doit vibrer 脿 chaque nouvelle ou modifi茅e instruction de navigation (seulement quand l\'app est au premier plan)</string>
+    <string name="widget_screen_delete_confirm_title">Supprimer un widget d\'茅cran</string>
+    <string name="widget_layout">Disposition du widget</string>
+    <string name="widget_subtype">Sous-type de widget</string>
+    <string name="widget_screen_x">脡cran %s</string>
+    <string name="widget_move_down">D茅placer vers le bas</string>
+    <string name="widget_missing_parts">Merci de s茅lectionner tous les widgets</string>
+    <string name="widget_unknown_workout">Exercice inconnu - %s</string>
+    <string name="pref_test_features_title">Fonctions</string>
+    <string name="pref_test_features_summary">Activer des fonctions pour cet appareil de test</string>
+    <string name="devicetype_nothingear2">Nothing Ear (2)</string>
+    <string name="widget_screen_min_screens">Il doit y avoir un minimum de %1$s 茅crans</string>
+    <string name="pref_title_general_reconnectonlytoconnected">Reconnexion uniquement aux appareils connect茅s</string>
+    <string name="prefs_password_4_digits_0_to_9_summary">Le mot de passe doit comporter 4 chiffres, et uniquement des chiffres</string>
+    <string name="devicetype_huawei_band_aw70">Huawei Band (AW70)</string>
+    <string name="devicetype_huawei_watchgt2e">Huawei Watch GT 2e</string>
+    <string name="prefs_workmode">Mode de fonctionnement</string>
+    <string name="huawei_reparse_workout_data_description">Cela fera quelque chose uniquement apr猫s certaines mises 脿 jour</string>
+    <string name="widget_screen_delete_confirm_description">脢tes-vous s没r de vouloir effacer \'%1$s\'?</string>
+    <string name="widget_screen_no_free_slots_description">Cet appareil n\'a plus d\'emplacement libre pour un widget d\'茅cran (nombre de widgets: %1$s)</string>
+    <string name="devicetype_colacao23">ColaCao 2023</string>
+    <string name="widget_layout_top_1_bot_2">1 haut, 2 bas</string>
+    <string name="widget_layout_top_2_bot_1">2 hauts, 1 bas</string>
+    <string name="widget_layout_top_2_bot_2">2 hauts, 2 bas</string>
+    <string name="menuitem_buzzer_intensity">Intensit茅 du buzzer</string>
+    <string name="pref_spo_automatic_enable">Permettre la mesure automatique du SpO2</string>
+    <string name="devicetype_huawei_watchgt3">Huawei Watch GT 3 (Pro)</string>
+    <string name="devicetype_redmi_smart_band_2">Redmi Smart Band 2</string>
+    <string name="flatDistance">Distance 脿 plat</string>
+    <string name="huawei_alarm_event_description">Ne pas v茅rifier l\'option r茅veil intelligent.</string>
+    <string name="wearmode_pebble">Pebble (boucle de chaussure)</string>
+    <string name="wearmode_band">Bracelet (poignet)</string>
+    <string name="alarm_smart_wakeup_interval_default">5 minutes</string>
+    <string name="prefs_heartrate_alert_active_high_threshold">Seuil d\'alerte pour fr茅quence cardiaque 茅lev茅e</string>
+    <string name="updatefirmwareoperation_updateproblem_low_battery">La batterie de l\'appareil est trop faible</string>
+    <string name="hydration_dnd_summary">D茅sactiver les alertes d\'hydratation durant un moment</string>
+    <string name="silent_mode_vibrate_silent">Vibreur / Silencieux</string>
+    <string name="do_not_disturb_lift_wrist_summary">Uniquement si l\'activation de l\'茅cran sur mouvement est activ茅</string>
+    <string name="pref_do_not_disturb_not_wear">Ne pas d茅ranger si non port茅</string>
+    <string name="manual">Manuel</string>
+    <string name="dnd_all_day">Toute la journ茅e</string>
+    <string name="activity_type_indoor_running">Course en int茅rieur</string>
+    <string name="activity_type_mountain_hike">Randonn茅e en montagne</string>
+    <string name="activity_type_cross_trainer">V茅lo elliptique</string>
+    <string name="activity_type_free_training">Entrainement libre</string>
+    <string name="activity_type_rower">Rameur</string>
+    <string name="activity_type_dynamic_cycle">V茅lo dynamique</string>
+    <string name="activity_type_stair_stepper">Escaliers (Stepper)</string>
+    <string name="activity_type_fitness_exercises">Exercices de Fitness</string>
+    <string name="activity_type_crossfit">Crossfit</string>
+    <string name="activity_type_functional_training">Entrainement fonctionnel</string>
+    <string name="activity_type_physical_training">Entrainement physique</string>
+    <string name="activity_type_taekwondo">Taekwondo</string>
+    <string name="activity_type_cross_country_running">Course de cross-country</string>
+    <string name="activity_type_karate">Karat茅</string>
+    <string name="activity_type_fencing">Escrime</string>
+    <string name="activity_type_kendo">Kendo</string>
+    <string name="activity_type_horizontal_bar">Barres horizontales</string>
+    <string name="activity_type_parallel_bar">Barres parall猫les</string>
+    <string name="activity_type_cooldown">Temps de r茅cup茅ration</string>
+    <string name="activity_type_cross_training">Entrainement crois茅</string>
+    <string name="activity_type_sit_ups">Relev茅 de buste</string>
+    <string name="activity_type_fitness_gaming">Jeux de fitness</string>
+    <string name="activity_type_aerobic_exercise">Exercices d\'a茅robic</string>
+    <string name="activity_type_rolling">Rollers</string>
+    <string name="activity_type_flexibility">Souplesse</string>
+    <string name="activity_type_track_and_field">Athl茅tisme</string>
+    <string name="activity_type_push_ups">Pompes</string>
+    <string name="activity_type_battle_rope">Traction 脿 la corde</string>
+    <string name="activity_type_smith_machine">Smith machine</string>
+    <string name="activity_type_pull_ups">Tractions</string>
+    <string name="activity_type_plank">Planche</string>
+    <string name="activity_type_javelin">Javelot</string>
+    <string name="activity_type_long_jump">Saut en longueur</string>
+    <string name="activity_type_high_jump">Saut en hauteur</string>
+    <string name="activity_type_trampoline">Trampoline</string>
+    <string name="activity_type_dumbbell">Halt猫res</string>
+    <string name="activity_type_belly_dance">Danse du ventre</string>
+    <string name="activity_type_jazz_dance">Danse Jazz</string>
+    <string name="activity_type_latin_dance">Dance latine</string>
+    <string name="activity_type_ballet">Ballet</string>
+    <string name="activity_type_other_dance">Autres danses</string>
+    <string name="activity_type_roller_skating">Patins 脿 roulettes</string>
+    <string name="activity_type_martial_arts">Arts martiaux</string>
+    <string name="activity_type_tai_chi">Tai chi</string>
+    <string name="activity_type_hula_hooping">Cerceaux</string>
+    <string name="activity_type_disc_sports">Lancer de disque</string>
+    <string name="activity_type_darts">Fl茅chettes</string>
+    <string name="activity_type_archery">Tir 脿 l\'arc</string>
+    <string name="activity_type_horse_riding">Course de cheval</string>
+    <string name="activity_type_kite_flying">Kite surf</string>
+    <string name="activity_type_swing">Balan莽oire</string>
+    <string name="activity_type_stairs">Escaliers</string>
+    <string name="activity_type_fishing">P锚che</string>
+    <string name="activity_type_hand_cycling">V茅lo classique</string>
+    <string name="activity_type_mind_and_body">Esprit et corps</string>
+    <string name="activity_type_kabaddi">Kabaddi</string>
+    <string name="activity_type_karting">Course de mini-voitures</string>
+    <string name="activity_type_billiards">Billard</string>
+    <string name="activity_type_shuttlecock">Badminton</string>
+    <string name="activity_type_softball">Softball</string>
+    <string name="activity_type_dodgeball">Dodgeball</string>
+    <string name="activity_type_australian_football">Football australien</string>
+    <string name="activity_type_pickleball">Pickleball</string>
+    <string name="activity_type_lacross">Lacrosse</string>
+    <string name="activity_type_shot">Tir</string>
+    <string name="activity_type_sailing">Voile</string>
+    <string name="activity_type_jet_skiing">Jet Ski</string>
+    <string name="activity_type_skating">Patinage</string>
+    <string name="activity_type_ice_hockey">Hockey sur glace</string>
+    <string name="activity_type_curling">Curling</string>
+    <string name="activity_type_cross_country_skiing">Ski de randonn茅e</string>
+    <string name="activity_type_snow_sports">Sports d\'hiver</string>
+    <string name="activity_type_skateboarding">Planche 脿 roulettes</string>
+    <string name="activity_type_rock_climbing">Escalade</string>
+    <string name="activity_type_hunting">Chasse</string>
+    <string name="activity_type_outdoor_walking">Marche en ext茅rieur</string>
+    <string name="devicetype_sony_wi_sp600n">Sony WI-SP600N</string>
+    <string name="devicetype_honor_band3">Honor Band 3</string>
+    <string name="devicetype_honor_band4">Honor Band 4</string>
+    <string name="devicetype_honor_band5">Honor Band 5</string>
+    <string name="devicetype_honor_band6">Honor Band 6</string>
+    <string name="devicetype_huawei_band6">Huawei Band 6</string>
+    <string name="devicetype_redmiwatch3">Redmi Watch 3</string>
+    <string name="devicetype_redmi_watch_2">Redmi Watch 2</string>
+    <string name="prefs_active_noise_cancelling_light">T茅moin lumineux de suppression du bruit</string>
+    <string name="sony_protocol_v1">Version 1</string>
+    <string name="sony_protocol_v2">Version 2</string>
+    <string name="protocol_version">Version de protocole</string>
+    <string name="pref_enable_call_reject">Permettre le rejet des appels</string>
+    <string name="pref_disable_find_phone_with_dnd">D茅sactiver la localisation du t茅l茅phone lorsque le mode Ne pas d茅ranger est actif</string>
+    <string name="notification_channel_connection_status_name">脡tat de la connexion</string>
+    <string name="uploading_watchface">Envoi d\'un th猫me鈥�</string>
+    <string name="uploadwatchfaceoperation_in_progress">Envoi d\'un th猫me</string>
+    <string name="uploadwatchfaceoperation_complete">Installation du th猫me termin茅</string>
+    <string name="uploadwatchfaceoperation_failed">Installation du th猫me 茅chou茅e</string>
+    <string name="heartrate_bpm_155">155 bpm</string>
+    <string name="heartrate_bpm_165">165 bpm</string>
+    <string name="heartrate_bpm_175">175 bpm</string>
+    <string name="heartrate_bpm_185">185 bpm</string>
+    <string name="heartrate_bpm_195">195 bpm</string>
+    <string name="heartrate_bpm_205">205 bpm</string>
+    <string name="devicetype_honor_magicwatch2">Honor MagicWatch 2</string>
+    <string name="devicetype_huawei_band7">Huawei Band 7</string>
+    <string name="devicetype_huawei_band8">Huawei Band 8</string>
+    <string name="devicetype_huawei_watch_gt">Huawei Watch GT</string>
+    <string name="devicetype_huawei_band4pro">Huawei Band 4 (Pro)</string>
+    <string name="devicetype_huawei_watchgt2">Huawei Watch GT 2 (Pro)</string>
+    <string name="devicetype_huawei_talk_band_b6">Huawei Talk Band B6</string>
+    <string name="pref_enable_call_accept_summary">Autoriser les appels depuis l\'appareil</string>
+    <string name="devicetype_miband8pro">Xiaomi Smart Band 8 Pro</string>
+    <string name="devicetype_mijia_mho_c303">Mijia MHO-C303</string>
+    <string name="devicetype_colacao21">ColaCao 2021</string>
+    <string name="widget_move_up">D茅placer vers le haut</string>
+    <string name="pref_force_connection_type_auto">Automatique</string>
+    <string name="devicetype_redmi_watch_2_lite">Redmi Watch 2 Lite</string>
+    <string name="devicetype_huawei_watchfit">Huawei Watch Fit</string>
+    <string name="pref_force_options_summary">Certains appareils pr茅tendent 脿 tort ne pas prendre en charge certaines options. Ces r茅glages peuvent 锚tre utilis茅s pour les activer malgr茅 tout.
+\n脌 UTILISER 脌 VOS RISQUES ET P脡RILS
+\nLire le wiki</string>
+    <string name="pref_force_smart_alarm">Forcer l\'alarme intelligente</string>
+    <string name="pref_force_smart_alarm_summary">Forcer la gestion des alarmes intelligentes
+\nUTILISER聽脌 VOS聽RISQUES聽ET聽P脡RILS</string>
+    <string name="pref_force_wear_location">Forcer la localisation</string>
+    <string name="huawei_ignore_wakeup_status_end_description">Peut aider pour une bonne d茅tection du sommeil. Visible imm茅diatement dans la vue des activit茅s quotidiennes.</string>
+    <string name="pref_force_wear_location_summary">Forcer la gestion de l\'emplacement
+\nUTILISER聽脌 VOS聽RISQUES聽ET聽P脡RILS</string>
+    <string name="pref_force_dnd_support">Forcer la gestion du mode Ne pas d茅ranger</string>
+    <string name="pref_force_dnd_support_summary">Forcer la gestion du mode Ne pas d茅ranger
+\nUTILISER聽脌聽VOS聽RISQUES聽ET聽P脡RILS</string>
+    <string name="huawei_ignore_wakeup_status_start">Ignorer le statut 脿 l\'allumage</string>
+    <string name="huawei_ignore_wakeup_status_start_description">Peut aider pour une d茅tection correcte du sommeil. Visible imm茅diatement dans la vue des activit茅s quotidiennes.</string>
+    <string name="huawei_ignore_wakeup_status_end">Ignorer l\'茅tat de r茅veil</string>
+    <string name="huawei_reparse_workout_data">Retraiter les donn茅es d\'exercice</string>
+    <string name="devicetype_xiaomi_watch_s1_pro">Xiaomi Watch S1 Pro</string>
+    <string name="widget_layout_two">2 widgets</string>
+    <string name="pref_title_fossil_hr_navigation_instructions">Instructions de navigation</string>
+    <string name="pref_summary_fossil_hr_navigation_instructions">Configurer sur la montre le comportement de la navigation</string>
+    <string name="pref_title_fossil_hr_nav_foreground">Venir au premier plan</string>
+    <string name="pref_summary_fossil_hr_nav_foreground">Si l\'application de navigation doit automatiquement passer au premier plan quand elle re莽oit une instruction de navigation</string>
+    <string name="pref_summary_general_reconnectonlytoconnected">Reconnecte uniquement aux appareils connect茅s, au lieu de reconnecter 脿 tous les appareils</string>
+    <string name="clap_hands_to_wakeup_device">Frapper des mains pour allumer l\'茅cran</string>
+    <string name="clap_hands_to_wakeup_device_summary">Frapper des mains 脿 nouveau 茅teindra l\'茅cran</string>
+    <string name="pixoo_power_saving_summary">L\'茅cran s\'茅teindra lorsque le microphone aura d茅tect茅 un long silence</string>
+    <string name="weekstepschart_steps_a_week_or_month">Pas par semaine/mois</string>
+    <string name="weeksleepchart_sleep_a_week_or_month">Sommeil par semaine/mois</string>
+    <string name="busy_task_fetch_sports_details_interrupted">La r茅cup茅ration des d茅tails sportifs a 茅t茅 interrompue</string>
+    <string name="pref_header_audio">Audio</string>
+    <string name="pref_header_generic">G茅n茅rique</string>
+    <string name="devicetype_miband8active">Xiaomi Smart Band 8 Active</string>
+    <string name="pref_title_notification_wake_on_open">R茅veil et d茅verrouillage automatiques</string>
+    <string name="pref_summary_notification_wake_on_open">R茅veille et d茅verrouille l\'appareil Android lorsque le gadget renvoie une r茅ponse OPEN. Doit 锚tre dans un 茅tat de confiance.</string>
+    <string name="fw_upgrade_notice_amazfitbip3">Vous 锚tes sur le point d\'installer le micrologiciel %s sur votre Amazfit Bip 3.
+\n
+\nAssurez-vous d\'installer le fichier .fw, puis le fichier .res. Votre montre red茅marrera apr猫s avoir install茅 le fichier .fw.
+\n
+\nRemarque鈥�: vous n\'锚tes pas oblig茅 d\'installer .res s\'il est exactement le m锚me que celui pr茅c茅demment install茅.
+\n
+\nPROC脡DEZ 脌 VOS PROPRES RISQUES鈥�!</string>
+    <string name="serbian">Serbe</string>
+    <string name="armenian">Arm茅nien</string>
+    <string name="pref_touch_tone_summary">脡met une tonalit茅 lorsque l\'茅couteur est touch茅</string>
+    <string name="pref_wearing_tone_summary">脡met une tonalit茅 lorsque l\'茅couteur est ins茅r茅</string>
+    <string name="pref_touch_tone">Tonalit茅 tactile</string>
+    <string name="pref_wearing_tone">Tonalit茅 lors du port</string>
+    <string name="devicetype_soundcore_liberty3_pro">Soundcore Liberty 3 Pro</string>
+    <string name="devicetype_huawei_watchfit2">Huawei Watch Fit 2</string>
+    <string name="scan_scanning_multiple_devices">Scan de %d appareils</string>
+    <string name="prompt_restart_gadgetbridge">Merci de red茅marrer GB聽pour que cela soit effectif.</string>
+    <string name="notification_channel_scan_service_name">Service de scan</string>
+    <string name="fmtPaceTypeAverage">Type de moyenne %d de rythme</string>
+    <string name="unknownDataEncountered">Donn茅es inconnues re莽ues</string>
+    <string name="cyclingPowerAverage">Puissance moyenne de p茅dalage</string>
+    <string name="cyclingPowerMin">Puissance minimum de p茅dalage</string>
+    <string name="cyclingPowerMax">Puissance maximale de p茅dalage</string>
+    <string name="milliseconds">millisecondes</string>
+    <string name="widget_name_untitled">Widget sans titre (%1$s)</string>
+    <string name="auto_reconnect_ble_scan_title">Reconnexion par un scan BLE</string>
+    <string name="auto_reconnect_ble_scan_summary">Attendre le scan d\'appareil au lieu d\'essayer de reconnecter 脿 l\'aveugle</string>
+    <string name="unbind_before_pair_title">D茅j脿 li茅</string>
+    <string name="unbind_before_pair_message">Cet appareil est d茅j脿 li茅 dans les r茅glages Android, ce qui peut rendre le pairage impossible pour certains appareils.
+\n
+\nSi l\'ajout de l\'appareil 茅choue, merci de le supprimer des r茅glages Android et essayer de nouveau.</string>
+    <string name="devicetype_scannable">Appareil scannable</string>
+    <string name="pref_header_sony_sound_control">Contr么le audio</string>
+    <string name="companion_pairing_request_title">Appareil compagnon</string>
+    <string name="pref_force_enable_heartrate_support_summary">Forcer la prise en charge du rythme cardiaque
+\nUTILISER聽脌聽VOS聽RISQUES聽ET聽P脡RILS</string>
+    <string name="pref_force_enable_spo2_support">Forcer la prise en charge du SpO2</string>
+    <string name="device_state_waiting_scan">En attente du scan des appareils</string>
+    <string name="devicetype_amazfit_bip3">Amazfit Bip 3</string>
+    <string name="devicetype_huawei_watchultimate">Huawei Watch Ultimate</string>
+    <string name="waiting_for_bluetooth">En attente du Bluetooth鈥�</string>
+    <string name="error_scan_failed">Scan 茅chou茅鈥�: %d</string>
+    <string name="scan_not_scanning">Scan non actif</string>
+    <string name="scan_scanning_all_devices">Scan de tous les appareils</string>
+    <string name="devicetype_sony_linkbuds">Sony LinkBuds</string>
+    <string name="pref_adaptive_volume_control_title">Contr么le du volume adaptatif</string>
+    <string name="devicetype_huawei_band9">Huawei Band 9</string>
+    <string name="devicetype_huawei_watchgt4">Huawei Watch GT 4</string>
+    <string name="devicetype_huawei_watchfit3">Huawei Watch Fit 3</string>
+    <string name="devicetype_huawei_watch4pro">Huawei Watch 4 (Pro)</string>
+    <string name="devicetype_redmi_watch_4">Redmi Watch 4</string>
+    <string name="preferences_qhybrid_settings_summary">Anciens r茅glages pour les montres Q Hybrid</string>
+    <string name="Pace">Rythme</string>
+    <string name="RunningForm">D茅part depuis</string>
+    <string name="pref_wide_area_tap_summary">Distinguer entre les taps sur les joues et sur les oreilles</string>
+    <string name="pref_wide_area_tap_title">Zone de tape large</string>
+    <string name="pref_adaptive_volume_control_summary">Augmenter le volume automatiquement quand le bruit ambiant est important</string>
+    <string name="pref_adaptive_noise_cancelling_title">ANC adaptatif</string>
+    <string name="pref_adaptive_noise_cancelling_summary">R茅gler la forte de l\'ANC automatiquement selon le niveau de bruit ambiant</string>
+    <string name="huawei_trusleep_warning">Avertissement鈥�: activer cette option fera apparaitre tous les sommeils comme sommeil l茅ger dans GadgetBridge鈥�! Cliquer ici pour plus de d茅tails.</string>
+    <string name="pref_force_enable_heartrate_support">Forcer la prise en charge du rythme cardiaque</string>
+    <string name="pref_force_enable_spo2_support_summary">Forcer la prise en charge du SpO2
+\nUTILISER聽脌聽VOS聽RISQUES聽ET聽P脡RILS</string>
+    <string name="widget_name_colored_tile">%1$s (tuile color茅e)</string>
+    <string name="scan_scanning_single_device">Scan d\'un appareil</string>
+    <string name="companion_pairing_request_description">Pairer cet appareil comme compagnon鈥�?
+\n
+\nCela est conseill茅 pour certaines fonctions comme retrouver son appareil, et apporte une meilleure connexion.</string>
+    <string name="state_scanned">Scann茅</string>
+    <string name="swolfAvg">Swolf moyen</string>
+    <string name="swolfMax">Swolf maximum</string>
+    <string name="swolfMin">Swolf minimum</string>
+    <string name="degrees">degr茅s</string>
+    <string name="pref_sleepasandroid_feat_spo2">SPO2</string>
+    <string name="bottom_nav_devices">Appareils</string>
+    <string name="pref_dashboard_widget_settings">R茅glages des widgets</string>
+    <string name="pref_dashboard_widget_show_legend_title">Afficher la l茅gende</string>
+    <string name="pref_dashboard_all_devices_summary">Combiner l\'activit茅 de tous les appareils ajout茅es pour les totaux du tableau de bord</string>
+    <string name="pref_dashboard_select_devices_summary">Combines les donn茅es d\'activit茅 des appareils choisis pour les totaux sur le tableau de bord</string>
+    <string name="pref_dashboard_widget_show_legend_summary">Afficher la l茅gende sous le widget expliquant les couleurs</string>
+    <string name="error_showing_changelog">Erreur lors de l\'affichage du Changelog</string>
+    <string name="dashboard_settings">R茅glages du Tableau de bord</string>
+    <string name="activity_type_worn">Port茅</string>
+    <string name="pref_dashboard_widget_today_hr_interval_title">intervalle de rythme cardiaque</string>
+    <string name="pref_sleepasandroid_features_title">Fonctionnalit茅s</string>
+    <string name="bottom_nav_dashboard">Tableau de bord</string>
+    <string name="pref_dashboard_widget_double_size_title">Taille double</string>
+    <string name="pref_auto_reply_calls_summary">Le t茅l茅phone d茅crochera automatiquement les appels entrants</string>
+    <string name="pref_sleepasandroid_feat_heartrate">Rythme cardiaque</string>
+    <string name="pref_sleepasandroid_feat_oximetry">Oxim茅trie</string>
+    <string name="widget_layout_top_wide_bot_large">Large au dessus, grand en dessous</string>
+    <string name="pref_dashboard_cards_title">Afficher les widgets sur les cartes</string>
+    <string name="pref_dashboard_widget_today_title">Tableau d\'activit茅</string>
+    <string name="pref_dashboard_widget_today_24h_title">mode 24h</string>
+    <string name="pref_dashboard_widget_goals_chart_title">Tableau des objectifs</string>
+    <string name="pref_dashboard_widgets_order_summary">Choisir quels widgets sont activ茅s et l\'ordre dans lequel ils sont affich茅s sur le tableau de bord</string>
+    <string name="sleepasandroid_settings">Sleep as Android</string>
+    <string name="pref_dashboard_widget_double_size_summary">Autoriser le widget 脿 occuper jusqu\'脿 2 colonnes sur le tableau de bord</string>
+    <string name="pref_sleepasandroid_feat_alarms">Alarmes</string>
+    <string name="pref_sleepasandroid_slot_summary">Quel emplacement d\'alarme utiliser lors de la configuration des alarmes</string>
+    <string name="pref_sleepasandroid_feat_notifications">Notifications</string>
+    <string name="pref_sleepasandroid_feat_movement">Acc茅l茅rom猫tre</string>
+    <string name="watchface_resolution_doesnt_match">La r茅solution du th猫me d\'茅cran ne correspond pas 脿 la r茅solution de l\'appareil. Le th猫me d\'茅cran est %1$s l\'茅cran de l\'appareil est %2$s</string>
+    <string name="device_name_cycling_sensor">Capteur cyclisme</string>
+    <string name="devicetype_cycling_sensor">Capteur de vitesse pour v茅lo</string>
+    <string name="pref_summary_wheel_diameter">Diam猫tre de la roue en pouces. Habituellement 29, 27,5 or 26.</string>
+    <string name="open_camera">Open Camera</string>
+    <string name="toast_camera_photo_taken">La photo a 茅t茅 prise et sauvegard茅e dans %s</string>
+    <string name="pref_auto_reply_calls_title">R茅pond automatiquement aux appels t茅l茅phoniques</string>
+    <string name="pref_dashboard_widget_today_hr_interval_summary">Le temps en minutes pendant lequel le graphique affiche \"port茅\" apr猫s chaque mesure du rythme cardiaque</string>
+    <string name="devicesetting_scannable_rssi">Seuil RSSI minimum</string>
+    <string name="devicesetting_scannable_minimum_unseen_summary">Une fois scann茅, l\'appareil sera invisible pour cette dur茅e avant de pouvoir 锚tre enregistr茅 de nouveau</string>
+    <string name="devicesetting_scannable_rssi_summary">Seuil minimum de RSSI pour la d茅tection</string>
+    <string name="pref_speak_notifications_aloud_title">Annoncer les notifications</string>
+    <string name="pref_auto_reply_calls_delay_title">D茅lai de r茅ponse automatique</string>
+    <string name="pref_header_calls_and_notifications">Appels et notifications</string>
+    <string name="pref_speak_notifications_focus_exclusive_title">Mettre en pause l\'audio des autres applications</string>
+    <string name="pref_speak_notifications_focus_exclusive_summary_on">La lecture des autres applications sera suspendue le temps que la notification soit jou茅e</string>
+    <string name="pref_summary_bottom_navigation_bar_on">Basculer entre les 茅crans principaux en utilisant la barre de navigation ou un balayage horizontal</string>
+    <string name="pref_speak_notifications_focus_exclusive_summary_off">Le volume de lecture des autres applications sera r茅duit pendant la lecture de la notification</string>
+    <string name="pref_summary_bottom_navigation_bar_off">Basculer entre les 茅crans principaux en utilisant uniquement le balayage horizontal</string>
+    <string name="chart_cycling_point_label_speed">%.1f km/h</string>
+    <string name="pref_summary_cycling_persistence_interval">Intervalle en seconde pour l\'茅criture des donn茅es de p茅dalage dans la base de donn茅es</string>
+    <string name="pref_title_cycling_persistence_interval">Intervalle persistant</string>
+    <string name="pref_title_bottom_navigation_bar">Barre de navigation en bas</string>
+    <string name="pref_dashboard_widget_today_upside_down_title">Minuit en bas</string>
+    <string name="pref_sleepasandroid_device_title">Fournisseur de l\'appareil</string>
+    <string name="pref_sleepasandroid_device_summary">Choisir l\'appareil fournisseur de donn茅es Sleep as Android</string>
+    <string name="pref_dashboard_widget_today_upside_down_summary">Dans le mode 24h, minuit est en bas de l\'茅cran et la mi-journ茅e au sommet du graphique</string>
+    <string name="pref_sleepasandroid_enable_summary">Permettre l\'int茅gration Sleep as Android</string>
+    <string name="pref_sleepasandroid_features_summary">Le support varie selon les appareils</string>
+    <string name="pref_title_huawei_account">Compte Huawei</string>
+    <string name="pref_summary_huawei_account">Compte Huawei utilis茅 pour le processus de pairage. Le configurer permet un pairage sans r茅initialisation.</string>
+    <string name="device_name_bicycle_sensor">Capteur v茅lo</string>
+    <string name="title_cycling">Cyclisme</string>
+    <string name="pref_title_wheel_diameter">Diam猫tre de la roue</string>
+    <string name="chart_cycling_point_label_distance">Aujourd\'hui鈥�: %.1f km
+\nTotal鈥�: %.1f km</string>
+    <string name="toast_setting_requires_reconnect">Ce r茅glage prendra effet 脿 la prochaine reconnexion</string>
+    <string name="toast_camera_permission_required">La permission Appareil Photo est n茅cessaire pour cette fonction.</string>
+    <string name="toast_camera_support_required">Le support de la Cam茅ra est n茅cessaire pour cette fonction.</string>
+    <string name="devicetype_amazfit_bip5_unity">Amazfit Bip 5 Unity</string>
+    <string name="pref_dashboard_first_summary">Montrer le tableau de bord lors du d茅marrage de Gadgetbridge, au lieu de l\'茅cran des appareils</string>
+    <string name="pref_dashboard_cards_summary">Dessiner les cartes autour des widgets sur le tableau de bord</string>
+    <string name="pref_dashboard_all_devices_title">Tous les appareils</string>
+    <string name="pref_dashboard_devices_to_include">Appareils 脿 inclure</string>
+    <string name="pref_dashboard_widget_today_24h_summary">Afficher l\'activit茅 dans un unique cercle de 24h au lieu d\'un double cercle de 12h</string>
+    <string name="pref_speak_notifications_aloud_summary">Les notifications seront lues 脿 haute voix dans les 茅couteurs</string>
+    <string name="pref_sleepasandroid_slot_title">Emplacement d\'alarmes</string>
+    <string name="alarm_slot_reset">L\'emplacement d\'alarme a 茅t茅 configur茅 脿 sa valeur par d茅faut</string>
+    <string name="widget_layout_top_large_bot_wide">Grand au-dessus, large en dessous</string>
+    <string name="pref_dashboard_select_devices_title">Choisir les appareils...</string>
+    <string name="pref_auto_reply_calls_delay_summary">Temps en seconde apr猫s lequel l\'appel est d茅croch茅 automatiquement</string>
+    <string name="devicesetting_scannable_minimum_unseen">Temps invisible minimum (secondes)</string>
+    <string name="devicesetting_scannable_debounce">D茅lai d\'attente de la redondance de scan (secondes)</string>
+    <string name="devicesetting_scannable_debounce_summary">Une fois scann茅, l\'appareil restera comme scann茅 et sera ignor茅 pendant le temps sp茅cifi茅</string>
+    <string name="pref_dashboard_first_title">Afficher le tableau de bord en premier</string>
+    <string name="stepRateSum">Somme des rythmes de marche</string>
+    <string name="stepRateAvg">Vitesse moyenne de marche</string>
+    <string name="stepLengthAvg">Longueur moyenne du pas</string>
+    <string name="groundContactTimeAvg">Temps de contact moyen avec le sol</string>
+    <string name="impactAvg">Impact moyen</string>
+    <string name="impactMax">Impact maximum</string>
+    <string name="foreFootLandings">Appui de l\'avant-pied</string>
+    <string name="midFootLandings">Appui du milieu du pied</string>
+    <string name="backFootLandings">Appui du talon</string>
+    <string name="eversionAngleAvg">Angle moyen d\'茅version</string>
+    <string name="eversionAngleMax">Angle maximum d\'茅version</string>
+    <string name="fmtPaceDistance">rythme %d distance</string>
+    <string name="fmtPacePace">Rythme %d rythme</string>
+    <string name="fmtPaceCorrection">Correction %d du rythme</string>
+    <string name="fmtPaceType">Rythme %d genre</string>
+    <string name="swingAngleAvg">Angle d\'oscillation moyen</string>
+    <plurals name="amount_of_days">
+        <item quantity="one">%d jour</item>
+        <item quantity="many">%d jours</item>
+        <item quantity="other">%d jours</item>
+    </plurals>
+    <string name="battery_full_notify_enabled">Notifier quand batterie pleine</string>
+    <string name="battery_full_threshold">Seuil de batterie pleine</string>
+    <string name="pref_fetch_unknown_files_title">R茅cup茅rer les fichiers inconnus</string>
+    <string name="pref_fetch_unknown_files_summary">R茅cup茅rer les fichiers d鈥檃ctivit茅 inconnus de la montre. Ils ne seront pas trait茅s, mais seront enregistr茅s dans le t茅l茅phone.</string>
+    <string name="default_percentage">D茅faut (%1$d%%)</string>
+    <string name="devicetype_garmin_epix_pro">Garmin Epix Pro</string>
+    <string name="devicetype_garmin_instinct_2s_solar">Garmin Instinct 2S Solar</string>
+    <string name="devicetype_garmin_instinct_2x_solar">Garmin Instinct 2X Solar</string>
+    <string name="devicetype_garmin_vivosmart_5">Garmin V铆vosmart 5</string>
+    <string name="dashboard_calendar_month_goals_reached_title">% de l\'objectif de pas atteint</string>
+    <string name="loading">Chargement鈥�</string>
+    <string name="battery_low_threshold">Seuil de batterie faible</string>
+    <string name="pref_title_garmin_default_reply_suffix">Utiliser un suffixe de r茅ponse pr茅d茅fini</string>
+    <string name="pref_garmin_agps_help">La liste ci-dessous contient toutes les URLs demand茅es par la montre pour la mise 脿 jour de l\'AGPS. Vous pouvez s茅lectionner un fichier depuis le t茅l茅phone qui sera envoy茅 脿 la montre quand une demande de mise 脿 jour sera faite.</string>
+    <string name="copied_to_clipboard">Copier dans le presse-papier</string>
+    <string name="devicetype_garmin_fenix_6_sapphire">Garmin Fenix 6 Sapphire</string>
+    <string name="devicetype_garmin_instinct">Garmin Instinct</string>
+    <string name="devicetype_garmin_instinct_crossover">Garmin Instinct Crossover</string>
+    <string name="devicetype_garmin_forerunner_245">Garmin Forerunner 245</string>
+    <string name="devicetype_garmin_swim_2">Garmin Swim 2</string>
+    <string name="devicetype_garmin_vivoactive_5">Garmin V铆voactive 5</string>
+    <string name="devicetype_garmin_forerunner_265">Garmin Forerunner 265</string>
+    <string name="notification_channel_full_battery_name">Batterie pleine</string>
+    <string name="devicetype_garmin_vivoactive_4">Garmin V铆voactive 4</string>
+    <string name="devicetype_garmin_vivoactive_4s">Garmin V铆voactive 4S</string>
+    <string name="devicetype_garmin_forerunner_255s">Garmin Forerunner 255S</string>
+    <string name="devicetype_hama_fit6900">Hama Fit6900</string>
+    <string name="battery_i">Batterie %d</string>
+    <string name="devicetype_garmin_venu_3s">Garmin Venu 3S</string>
+    <string name="none">Aucune</string>
+    <string name="battery_percentage_str">%1$s%%</string>
+    <string name="share_debug_info">Partager les infos de d茅bogage</string>
+    <string name="realtime_settings">Param猫tres en temps r茅el</string>
+    <string name="show_in_notification">Afficher dans les notifications</string>
+    <string name="unsupported">non pris en charge</string>
+    <string name="min_val">Minimum鈥�: %d</string>
+    <string name="max_val">Maximum鈥�: %d</string>
+    <string name="battery_low_notify_enabled">Notifier quand batterie faible</string>
+    <string name="notif_battery_full_title">Batterie pleine鈥�!</string>
+    <string name="notif_battery_full">%1$s batterie pleine</string>
+    <string name="garmin_agps_url_i">URL AGPS %1$d</string>
+    <string name="no_folder_selected">Pas de dossier s茅lectionn茅</string>
+    <string name="folder_is_empty">Dossier vide</string>
+    <string name="folder">Dossier</string>
+    <string name="url">URL</string>
+    <string name="garmin_agps_local_file">Fichier local</string>
+    <string name="devicetype_garmin_fenix_7s">Garmin Fenix 7S</string>
+    <string name="devicetype_garmin_fenix_7_pro">Garmin Fenix 7 Pro</string>
+    <string name="authentication_failed_check_key">脡chec de l鈥檃uthentification, v茅rifier la cl茅 d鈥檃uthentification</string>
+    <string name="toggle_debug_mode">Activer le mode d茅bogage</string>
+    <string name="devicetype_garmin_forerunner_255">Garmin Forerunner 255</string>
+    <string name="pref_agps_status">Statut AGPS</string>
+    <string name="agps_status_missing">Manquant</string>
+    <string name="agps_status_pending">En attente</string>
+    <string name="agps_status_current">Actuel</string>
+    <string name="agps_status_error">Erreur</string>
+    <string name="devicetype_garmin_vivomove_hr">Garmin Vivomove HR</string>
+    <string name="devicetype_garmin_vivomove_style">Garmin Vivomove Style</string>
+    <string name="devicetype_garmin_venu_2">Garmin Venu 2</string>
+    <string name="devicetype_garmin_instinct_2s">Garmin Instinct 2S</string>
+    <string name="devicetype_garmin_instinct_2_solar">Garmin Instinct 2 Solar</string>
+    <string name="devicetype_garmin_venu_2_plus">Garmin Venu 2 Plus</string>
+    <string name="devicetype_garmin_venu_3">Garmin Venu 3</string>
+    <string name="devicetype_garmin_instinct_solar">Garmin Instinct Solar</string>
+    <string name="devicetype_garmin_instinct_2_soltac">Garmin Instinct 2 SolTac</string>
+    <string name="sleep_colored_stats_light_avg">L茅ger moy</string>
+    <string name="sleep_colored_stats_rem_avg">REM moy</string>
+    <string name="stats_empty_value">-</string>
+    <string name="stats_lowest_hr">FC la plus basse</string>
+    <string name="stats_highest_hr">FC la plus haute</string>
+    <string name="sleep_colored_stats_light">L茅ger</string>
+    <string name="sleep_colored_stats_deep_avg">Profond moy</string>
+    <string name="hrv_status">Statut VFC</string>
+    <string name="hrv_status_balanced">Equilibr茅</string>
+    <string name="sleep_colored_stats_deep">Profond</string>
+    <string name="hrv_status_low">bas</string>
+    <string name="hrv_status_unbalanced">D茅s茅quilibr茅</string>
+    <string name="pref_header_hrv_status">Statut VFC</string>
+    <string name="devicetype_garmin_fenix_5">Garmin Fenix 5</string>
+    <string name="devicetype_garmin_fenix_6">Garmin Fenix 6</string>
+    <string name="proprietary_app_warning">Cette fonctionnalit茅 n茅cessite l鈥檌nstallation d鈥檜ne application propri茅taire</string>
+    <string name="devicetype_garmin_vivomove_trend">Garmin V铆vomove Trend</string>
+    <string name="devicetype_garmin_vivoactive_3">Garmin V铆voactive 3</string>
+    <string name="devicetype_garmin_forerunner_255_music">Garmin Forerunner 255 Music</string>
+    <string name="devicetype_garmin_forerunner_255s_music">Garmin Forerunner 255S Music</string>
+    <string name="sleep_colored_stats_rem">REM</string>
+    <string name="abstract_chart_fragment_kind_awake_sleep">Eveill茅</string>
+    <string name="hrv_status_poor">Mauvais</string>
+    <string name="devicetype_garmin_vivosport">Garmin V铆vosport</string>
+    <string name="pref_title_notification_times_enabled">Horaires des notifications</string>
+    <string name="pref_summary_notification_times_enabled">N\'envoyer les notifications que pendant ces horaires</string>
+    <string name="pref_battery_polling_interval_format">toutes les %1$s minutes</string>
+    <string name="minutes_20">20 minutes</string>
+    <string name="minutes_60">60 minutes</string>
+    <string name="hrv_status_day_avg">Moyenne journali猫re</string>
+    <string name="body_energy_lost">D茅pens茅e</string>
+    <string name="body_energy_gained">Gagn茅e</string>
+    <string name="body_energy_legend_level">Niveau de r茅serve d\'茅nergie</string>
+    <string name="body_energy">R茅serve d\'茅nergie</string>
+    <string name="find_my_phone_companion_warning">Association comme compagnon est requis pour trouver le t茅l茅phone. Cliquez ici pour plus d\'informations.</string>
+    <string name="hrv_status_last_night">Nuit derni猫re</string>
+    <string name="hrv_status_day_avg_legend">Moyenne journali猫re (ms)</string>
+    <string name="hrv_status_seven_days_avg">Moyenne sur 7 jours</string>
+    <string name="hrv_status_seven_days_avg_status">Statut</string>
+    <string name="hrv_status_seven_days_avg_long">Moyenne sur 7 jours</string>
+    <string name="hrv_status_baseline_label">Ligne de base</string>
+    <string name="gps_glonass">GPS + GLONASS</string>
+    <string name="activity_prefs_height_inches">Taille en pouces</string>
+    <string name="activity_type_tae_bo">Tae Bo</string>
+    <string name="transition">Transition</string>
+    <string name="activity_type_beach_soccer">Beach Soccer</string>
+    <string name="activity_type_beach_volleyball">Beach-volley</string>
+    <string name="activity_type_gateball">Gateball</string>
+    <string name="activity_type_sepak_takraw">Sepak Takraw</string>
+    <string name="activity_type_parachuting">Parachutisme</string>
+    <string name="activity_type_auto_racing">Course automobile</string>
+    <string name="activity_type_luge">Luge</string>
+    <string name="activity_type_parkour">Parkour</string>
+    <string name="activity_type_navigate">Naviguer</string>
+    <string name="activity_type_indoor_track">Circuit int茅rieur</string>
+    <string name="activity_type_handcycling">V茅lo 脿 main</string>
+    <string name="activity_type_handcycling_indoor">V茅lo 脿 main en int茅rieur</string>
+    <string name="activity_type_transition">Transition</string>
+    <string name="activity_type_fitness_equipment">Equipement fitness</string>
+    <string name="activity_type_platform_tennis">Paddle-tennis</string>
+    <string name="activity_type_training">Entrainement</string>
+    <string name="activity_type_breathwork">Ex. respiration</string>
+    <string name="activity_type_xc_classic_ski">Ski de rando</string>
+    <string name="activity_type_mountaineering">Alpinisme</string>
+    <string name="activity_type_multisport">Multisport</string>
+    <string name="activity_type_flying">Vol</string>
+    <string name="activity_type_boating">Bateau</string>
+    <string name="activity_type_driving">Conduite</string>
+    <string name="activity_type_hang_gliding">Deltaplane</string>
+    <string name="activity_type_inline_skating">Roller</string>
+    <string name="activity_type_climb_indoor">Escalade en int茅rieur</string>
+    <string name="activity_type_bouldering">Escalade Bloc</string>
+    <string name="activity_type_e_bike">V茅lo 茅lectrique</string>
+    <string name="activity_type_bike_commute">Traj. Quot. V茅lo</string>
+    <string name="activity_type_american_football">Football am茅ricain</string>
+    <string name="activity_type_cardio">Cardio</string>
+    <string name="activity_type_motorcycling">Moto</string>
+    <string name="activity_type_sail_race">R茅gate</string>
+    <string name="activity_type_snowmobiling">Motoneige</string>
+    <string name="activity_type_stand_up_paddleboarding">Standup paddle</string>
+    <string name="activity_type_wakeboarding">Wakeboard</string>
+    <string name="activity_type_rafting">Rafting</string>
+    <string name="activity_type_tactical">Tactique</string>
+    <string name="activity_type_jumpmaster">Jumpmaster</string>
+    <string name="activity_type_floor_climbing">Mont茅e d\'茅tages</string>
+    <string name="activity_type_softball_slow_pitch">Softball</string>
+    <string name="activity_type_sail_expedition">Exp茅d. voilier</string>
+    <string name="activity_type_ice_skating">Patins 脿 glace</string>
+    <string name="activity_type_sky_diving">Parachutisme</string>
+    <string name="activity_type_snowshoe">Raquet. neige</string>
+    <string name="activity_type_kayaking">Kayak</string>
+    <string name="activity_type_water_skiing">Ski nautique</string>
+    <string name="activity_type_winter_sport">Sport d\'hiver</string>
+    <string name="activity_type_grinding">Grinding</string>
+    <string name="activity_type_health_snapshot">Aper莽u sant茅</string>
+    <string name="activity_type_marine">Nautique</string>
+    <string name="activity_type_video_gaming">Jeux vid茅o</string>
+    <string name="activity_type_racket">Raquette</string>
+    <string name="activity_type_padel">Padel</string>
+    <string name="activity_type_racquetball">Racquetball</string>
+    <string name="activity_type_meditation">M茅ditation</string>
+    <string name="activity_type_disc_golf">Disc Golf</string>
+    <string name="activity_type_ultimate_disc">UItimate frisbee</string>
+    <string name="activity_type_water_tubing">Bou茅e gonflable</string>
+    <string name="activity_type_wakesurfing">Wakesurf</string>
+    <string name="activity_type_mixed_martial_arts">MMA</string>
+    <string name="activity_type_aerobics">A茅robic</string>
+    <string name="activity_type_artistic_swimming">Natation synchronis茅e</string>
+    <string name="activity_type_ballroom_dance">Danse de salon</string>
+    <string name="activity_type_bmx">BMX</string>
+    <string name="activity_type_board_game">Jeu de soci茅t茅</string>
+    <string name="activity_type_bocce">P茅tanque</string>
+    <string name="activity_type_breaking">Breakdance</string>
+    <string name="activity_type_chess">Echecs</string>
+    <string name="activity_type_esports">Esport</string>
+    <string name="activity_type_frisbee">Frisbee</string>
+    <string name="activity_type_futsal">Futsal</string>
+    <string name="activity_type_hip_hop">Hip-hop</string>
+    <string name="activity_type_jujitsu">Ju-jitsu</string>
+    <string name="activity_type_parallel_bars">Barres parall猫les</string>
+    <string name="activity_type_pole_dance">Pole dance</string>
+    <string name="activity_type_snorkeling">Tuba</string>
+    <string name="activity_type_shuffleboard">Shuffleboard</string>
+    <string name="activity_type_square_dance">Square dance</string>
+    <string name="activity_type_weiqi">Jeu de go</string>
+    <string name="activity_type_shooting">Tir</string>
+    <string name="activity_type_team_sport">Sport collectif</string>
+    <string name="activity_type_lacrosse">Lacrosse</string>
+    <string name="activity_type_hula_hoop">Hula hoop</string>
+    <string name="activity_type_judo">Judo</string>
+    <string name="activity_type_dragon_boat">Bateau-dragon</string>
+    <string name="activity_type_tug_of_war">Tir 脿 la corde</string>
+    <string name="activity_type_muay_thai">Muay thai</string>
+    <string name="activity_type_water_polo">Water-polo</string>
+    <string name="activity_type_paddling">Sport 脿 pagaie</string>
+    <string name="activity_type_para_sport">Handisport</string>
+    <string name="activity_type_air_walker">D茅ambulateur a茅rien</string>
+    <string name="activity_type_bridge">Bridge</string>
+    <string name="activity_type_cardio_combat">Cardio combat</string>
+    <string name="activity_type_checkers">Dames</string>
+    <string name="activity_type_finswimming">Nage avec palmes</string>
+    <string name="activity_type_flowriding">Flowriding</string>
+    <string name="activity_type_folk_dance">Danse traditionnelle</string>
+    <string name="activity_type_hacky_sack">Footbag</string>
+    <string name="activity_type_jai_alai">Pelote basque</string>
+    <string name="activity_type_mass_gymnastics">Massue (gymnastique)</string>
+    <string name="activity_type_modern_dance">Danse moderne</string>
+    <string name="activity_type_race_walking">Marche athl茅tique</string>
+    <string name="activity_type_somatosensory_game">Jeux de Somesth茅sie</string>
+    <string name="activity_type_spinning">Spinning</string>
+    <string name="activity_type_stair_climber">Mont茅e de marches</string>
+    <string name="activity_type_table_football">Baby-foot</string>
+    <string name="moondrop_touch_action_play_pause">Jouer/Pause</string>
+    <string name="moondrop_touch_trigger_long_press_3s">Appui long (3s)</string>
+    <string name="moondrop_touch_action_anc_mode">Activer le mode de suppression du bruit</string>
+    <string name="hrv_status_unit">%1$d ms</string>
+    <string name="hrv_status_baseline">%1$d-%2$d ms</string>
+    <string name="soundcore_equalizer_custom_title">Personnaliser鈥�</string>
+    <string name="soundcore_equalizer_custom_summary">Configurer l鈥櫭ゞaliseur param茅trique</string>
+    <string name="soundcore_equalizer_direction">Direction du p茅riph茅rique</string>
+    <string name="moondrop_equalizer_preset_monitor">Moniteur</string>
+    <string name="moondrop_touch_earbud">Ecouteur</string>
+    <string name="moondrop_touch_earbud_both">Les deux</string>
+    <string name="moondrop_touch_trigger">D茅clencheur</string>
+    <string name="activity_type_push_walk_speed">Pouss茅e - Vitesse de marche</string>
+    <string name="activity_type_indoor_push_walk_speed">Pouss茅e en int茅rieur - Vitesse de marche</string>
+    <string name="activity_type_push_run_speed">Pouss茅e - vitesse de course</string>
+    <string name="activity_type_indoor_push_run_speed">Pouss茅e en int茅rieur - vitesse de course</string>
+    <string name="activity_type_aerobic_combo">A茅robic combin茅</string>
+    <string name="activity_type_wall_ball">Wall ball</string>
+    <string name="soundcore_equalizer_preset_signature">Signature soundcore</string>
+    <string name="soundcore_equalizer_preset_xtra_bass">Xtra Bass</string>
+    <string name="soundcore_equalizer_preset_voice">voix</string>
+    <string name="soundcore_equalizer_frequency">Fr茅quence</string>
+    <string name="soundcore_equalizer_band5">Bande 5</string>
+    <string name="soundcore_equalizer_value">Valeur</string>
+    <string name="pref_summary_garmin_default_reply_suffix">Ajout茅 en plus du suffixe d茅fini dans Gadgetbridge</string>
+    <string name="pref_battery_polling_enable">Activer l\'interrogation de la batterie</string>
+    <string name="devicetype_soundcore_motion300">Soundcore Motion 300</string>
+    <string name="soundcore_equalizer_preset">Pr茅r茅gler</string>
+    <string name="soundcore_equalizer_direction_hanging">Suspendu</string>
+    <string name="soundcore_equalizer_reset_title">R茅initialiser les param猫tres par d茅faut</string>
+    <string name="soundcore_equalizer_band6">Bande 6</string>
+    <string name="soundcore_equalizer_band8">Bande 8</string>
+    <string name="soundcore_equalizer_band9">Bande 9</string>
+    <string name="soundcore_equalizer_band7">Bande 7</string>
+    <string name="pref_battery_polling_interval">Intervalle de l\'interrogation de la batterie</string>
+    <string name="activity_type_body_combat">Body combat</string>
+    <string name="activity_type_biathlon">Biathlon</string>
+    <string name="activity_type_bungee_jumping">Saut 脿 l\'茅lastique</string>
+    <string name="activity_type_orienteering">Course d\'orientation</string>
+    <string name="devicetype_garmin_fenix_5_plus">Garmin Fenix 5 Plus</string>
+    <string name="devicetype_garmin_forerunner_965">Garmin Forerunner 965</string>
+    <string name="devicetype_moondrop_space_travel">Moondrop Space Travel</string>
+    <string name="devicetype_huawei_watchgtrunner">Huawei Watch GT Runner</string>
+    <string name="soundcore_voice_prompts">Annonces vocales</string>
+    <string name="soundcore_button_brightness_low">Faible</string>
+    <string name="soundcore_button_brightness_high">Elev茅</string>
+    <string name="soundcore_ldac_mode_title">Mode LDAC</string>
+    <string name="soundcore_ldac_mode_summary">Activer le LDAC r茅duira la dur茅e de vie de la batterie et pourrait entra卯ner une instabilit茅 de connexion</string>
+    <string name="soundcore_equalizer_band1">Bande 1</string>
+    <string name="soundcore_equalizer_band2">Bande 2</string>
+    <string name="soundcore_equalizer_band3">Bande 3</string>
+    <string name="soundcore_equalizer_band4">Bande 4</string>
+    <string name="soundcore_equalizer_direction_standing">Debout</string>
+    <string name="soundcore_equalizer_direction_lying">Couch茅</string>
+    <string name="soundcore_equalizer_reset_summary">R茅tablir les param猫tres par d茅faut de toutes les bandes de l鈥櫭ゞaliseur</string>
+    <string name="cannot_upload_watchface_too_many_watchfaces_installed">Impossible de charger le cadran, trop de cadrans install茅s</string>
+    <string name="insufficient_space_for_upload">Espace insuffisant pour le chargement</string>
+    <string name="pref_battery_polling_configuration">Configuration de l鈥檌nterrogation de la batterie</string>
+    <string name="battery_polling_failed_start">脡chec du d茅marrage de l鈥檌nterrogation de la batterie</string>
+    <string name="activity_type_canoeing">Cano毛-kayak</string>
+    <string name="activity_type_water_scooter">Scooter des mers</string>
+    <string name="activity_type_bobsleigh">Bobsleigh</string>
+    <string name="activity_type_sledding">Luge</string>
+    <string name="activity_type_free_sparring">Combat libre</string>
+    <string name="activity_type_plaza_dancing">Danse de place</string>
+    <string name="activity_type_obstacle_race">Course d\'obstacles</string>
+    <string name="activity_type_laser_tag">Laser game</string>
+    <string name="activity_type_billiard_pool">Billard am茅ricain</string>
+    <string name="soundcore_button_brightness">Luminosit茅 des boutons</string>
+    <string name="soundcore_button_brightness_medium">Moyen</string>
+    <string name="soundcore_equalizer_preset_balanced">Equilibr茅</string>
+    <string name="soundcore_adaptive_direction_title">Direction adaptative</string>
+    <string name="soundcore_adaptive_direction_summary">Ajuster le pr茅r茅glage de l鈥櫭ゞaliseur automatiquement en fonction de la direction du p茅riph茅rique</string>
+    <string name="intent_api_broadcast_activity_sync_title">Transfert de la synchronisation des activit茅s termin茅</string>
+    <string name="pref_battery_polling_summary">C\'est le meilleur effort, et risque d\'锚tre retard茅 pour plusieurs raisons</string>
+    <string name="moondrop_touch_action_call_start">Appeler</string>
+    <string name="devicetype_huawei_watch3">Huawei Watch 3 (Pro)</string>
+    <string name="moondrop_touch_action_assistant">D茅clencher l\'assistant vocal</string>
+    <string name="moondrop_touch_trigger_long_press_1s">Appui long (1s)</string>
+    <string name="devicetype_nothing_cmf_watch_pro_2">CMF Watch Pro 2</string>
+    <string name="devicetype_nothing_cmf_buds_pro_2">CMF Buds Pro 2</string>
+    <string name="moondrop_equalizer_preset_reference">R茅f茅rence</string>
+    <string name="moondrop_equalizer_preset_basshead">Basshead</string>
+    <string name="moondrop_touch_action_call_pick_hang">D茅crocher/Suspendre appel</string>
+    <string name="intent_api_broadcast_activity_sync_summary">Envoyer une notification lorsque la synchronisation des activit茅s est termin茅e pour tout les appareils</string>
+    <string name="hrv_status_last_night_highest_5">Nuit derni猫re moy. max. de 5 min</string>
+    <string name="devicetype_vivitar_hr_bp_monitor_activity_tracker">Vivitar HR &amp; BP Monitor Activity Tracker</string>
+    <string name="devicetype_garmin_venu">Garmin Venu</string>
+    <string name="authentication_failed_negotiation">脡chec de la n茅gociation de la cl茅 d\'authentification</string>
+    <string name="devicetype_garmin_venu_2s">Garmin Venu 2S</string>
+    <string name="hrv">VFC</string>
+    <string name="devicetype_micompositionscale">Mi Body Composition Scale 2</string>
+    <string name="miscale_weight_unit_imperial">Imp茅rial (lb)</string>
+    <string name="miscale_weight_unit_chinese">Chinois (jin)</string>
+    <string name="miscale_small_objects_title">Petits objets</string>
+    <string name="miscale_small_objects_summary">Stocker le poids des objets plus l茅gers que 10 kg</string>
+    <string name="devicetype_mismartscale">Mi Smart Scale 2</string>
+    <string name="estimatedSweatLoss">Estimation de la transpiration</string>
+    <string name="miscale_weight_unit_title">Unit茅 de poids</string>
+    <string name="miscale_weight_unit_summary">D茅finir l鈥檜nit茅 de poids pour les mesures affich茅es</string>
+    <string name="miscale_weight_unit_metric">M茅trique (kg)</string>
+    <string name="steps_distance_unit">%1$,.2f km</string>
+    <string name="steps_total">Pas total</string>
+    <string name="distance_total">Distance totale</string>
+    <string name="steps_avg">Moy. de pas</string>
+    <string name="distance_avg">Distance moy.</string>
+    <string name="pref_time_sync">Synchronisation automatique de l鈥檋eure</string>
+    <string name="minutes_15">15 minutes</string>
+    <string name="minutes_45">45 minutes</string>
+    <string name="minutes_75">75 minutes</string>
+    <string name="minutes_90">90 minutes</string>
+    <string name="minutes_120">120 minutes</string>
+    <string name="minutes_150">150 minutes</string>
+    <string name="minutes_180">180 minutes</string>
+    <string name="minutes_210">210 minutes</string>
+    <string name="minutes_240">240 minutes</string>
+    <string name="minutes_255">255 minutes</string>
+    <string name="minutes_3">3 minutes</string>
+    <string name="minutes_4">4 minutes</string>
+    <string name="pref_app_connection_duration">Dur茅e de connexion 脿 l鈥檃pplication</string>
+    <string name="user_feedback_set_settings_ok">Param猫tres envoy茅s 脿 l鈥檃ppareil.</string>
+    <string name="minutes_2">2 minutes</string>
+    <string name="minutes_6">6 minutes</string>
+    <string name="minutes_7">7 minutes</string>
+    <string name="minutes_8">8 minutes</string>
+    <string name="minutes_9">9 minutes</string>
+    <string name="dateformat_day_month">Jour, Mois</string>
+    <string name="dateformat_month_day">Mois, Jour</string>
+    <string name="prefs_light_duration_longer">Dur茅e plus longue de la lumi猫re</string>
+    <string name="busy_task_fetch_sleep_data">Obtenir les donn茅es de sommeil</string>
+    <string name="devicetype_colmi_r02">Colmi R02</string>
+    <string name="smart_ring_measurement_error_worn_incorrectly">Erreur de mesure. Les capteurs de l\'anneau sont-ils correctement orient茅s鈥�?</string>
+    <string name="devicetype_colmi_r03">Colmi R03</string>
+    <string name="devicetype_colmi_r06">Colmi R06</string>
+    <string name="smart_ring_measurement_error_unknown">Erreur de mesure inconnue %d re莽ue de l\'anneau</string>
+    <string name="stress_average">Moyen</string>
+    <string name="devicetype_mijia_xmwsdj04">Mijia Temperature and Humidity Sensor 2 (E-ink)</string>
+    <string name="devicetype_honor_watchgs3">Honor Watch GS 3</string>
+    <string name="activity_type_rem_sleep">Sommeil REM</string>
+    <string name="menuitem_weight">Poids</string>
+    <string name="target">Cible</string>
+    <string name="lactateThresholdHeartRate">Fr茅quence cardiaque du seuil de lactate</string>
+    <string name="recoveryTime">Temps de r茅cup茅ration</string>
+    <string name="sleep_colored_stats_awake_avg">Moy. 茅veill茅</string>
+    <string name="weight_lbs">%1$.2f lbs</string>
+    <string name="weight_kg">%1$.2f kg</string>
+    <string name="devicetype_garmin_fenix_5x_plus">Garmin Fenix 5X Plus</string>
+    <string name="pref_description_developer_options">Logs, API d鈥檌ntention</string>
+    <string name="pref_header_sound">Son</string>
+    <string name="workoutSets">S茅rie</string>
+    <string name="workout_set_i">S茅rie %1d</string>
+    <string name="workout_set_repetitions">%1d x</string>
+    <string name="workout_set_repetitions_weight_kg">%1d x %2$.2f kg</string>
+    <string name="workout_set_repetitions_weight_lbs">%1d x %2$.2f lbs</string>
+    <string name="choose_device">Choisissez un appareil</string>
+    <string name="no_supported_devices_found">Aucun appareil pris en charge trouv茅</string>
+    <string name="search">Recherche</string>
+    <string name="pref_header_deprecated_functionalities">Fonctionnalit茅s obsol猫tes</string>
+    <string name="pref_title_nagivation_apps">Applications de navigation</string>
+    <string name="pref_header_external_integrations">Int茅grations ext茅rieures</string>
+    <string name="pref_header_automations">Automatisations</string>
+    <string name="pref_description_about_you">Date de naissance, sexe, taille, poids, objectifs</string>
+    <string name="pref_description_notifications">Notifications d\'application, liste blanche/liste noire</string>
+    <string name="menuitem_stress_breakdown">Stress (d茅taill茅)</string>
+    <string name="pref_header_deprecated_functionalities_warning">Les fonctionnalit茅s suivantes sont obsol猫tes et seront bient么t supprim茅es du logiciel.
+\nSi vous devez activer l\'un des param猫tres suivants, prenez contacte avec l\'茅quipe du projet.</string>
+    <string name="pref_deprecated_media_control_title">Contr么le des m茅dias obsol猫tes</string>
+    <string name="pref_deprecated_media_control_summary">Envoyez les commandes de contr么le des m茅dias comme des 茅v茅nements cl茅s au lieu du contr么leur des m茅dias.</string>
+    <string name="pref_description_dashboard">Widgets, dispositifs 脿 inclure</string>
+    <string name="pref_title_user_interface">Interface utilisateur</string>
+    <string name="pref_description_general">D茅marrage, langue, r茅gion, emplacement</string>
+    <string name="activity_prefs_date_birth">Date de naissance</string>
+    <string name="menuitem_stress_simple">Stress (simple)</string>
+    <string name="pref_description_user_interface">Th猫me, 茅cran principal</string>
+    <string name="pref_description_automations">Export et r茅cup茅ration automatique</string>
+    <string name="pref_description_deprecated_functionalities">Param猫tres qui seront supprim茅s dans une version future</string>
+    <string name="menuitem_stress_segmented">Stress (segment茅)</string>
+    <string name="pref_header_main_screen">脡cran principal</string>
+    <string name="devicetype_soundcore_liberty4_nc">Soundcore Liberty 4 NC</string>
+    <string name="devicetype_garmin_forerunner_165">Garmin Forerunner 165</string>
+    <string name="pref_continuous_skin_temperature_measurement_title">Mesure continue de la temp茅rature de la peau</string>
+    <string name="prefs_title_gatt_client_api_package">Paquet API de BLE</string>
+    <string name="backup_restore_abort_title">Abandonner</string>
+    <string name="backup_restore_abort_export_confirmation">Abandonner l鈥檈xportation鈥�? Le fichier Zip partiel sera supprim茅.</string>
+    <string name="backup_restore_restart_title">Red茅marrage</string>
+    <string name="devicetype_ble_gatt_client">Client g茅n茅rique BLE GATT</string>
+    <string name="prefs_summary_gatt_client_notification_intents">Recevoir les changements de caract茅ristiques BLE par l鈥檌nterm茅diaire des Intents</string>
+    <string name="prefs_summary_gatt_client_device_state_updates">Recevoir les changements d鈥櫭﹖at de connexion BLE via Intents</string>
+    <string name="backup_restore_exporting_preferences">Exportation des pr茅f茅rences鈥�</string>
+    <string name="backup_restore_exporting_database">Exportation de la base de donn茅es鈥�</string>
+    <string name="backup_restore_exporting_files">Exportation de fichiers鈥�</string>
+    <string name="backup_restore_exporting_files_i_of_n">Exportation des fichiers... %1d de %2d</string>
+    <string name="backup_restore_do_not_exit">%s Veuillez garder cet 茅cran ouvert jusqu鈥櫭� ce que l鈥檕p茅ration soit termin茅e.</string>
+    <string name="backup_restore_importing_database">Importation de la base de donn茅es鈥�</string>
+    <string name="backup_restore_restart_summary">%1s va red茅marrer.</string>
+    <string name="devicetype_amazfit_trex_3">Amazfit T-Rex 3</string>
+    <string name="prefs_title_ble_intent_api">API Intent BLE</string>
+    <string name="activity_db_management_backup_restore_label">Sauvegarder et restaurer</string>
+    <string name="activity_db_management_export_to_zip">Exporter Zip</string>
+    <string name="activity_db_management_import_from_zip">Importer Zip</string>
+    <string name="backup_restore_exporting">Exportation vers Zip鈥�</string>
+    <string name="backup_restore_error_export">Exportation vers Zip 茅chou茅e</string>
+    <string name="backup_restore_error_import">Importation depuis Zip 茅chou茅e</string>
+    <string name="backup_restore_abort_import_confirmation">Abandonner l鈥檌mportation鈥�? Cela peut entra卯ner une base de donn茅es corrompue ou incoh茅rente.</string>
+    <string name="menuitem_night_display">Affichage de nuit</string>
+    <string name="devicetype_honor_watchgspro">Honor Watch GS Pro</string>
+    <string name="menuitem_map">Carte</string>
+    <string name="pref_continuous_skin_temperature_measurement_description">La r茅cup茅ration de temp茅rature n鈥檈st pas prise en charge actuellement. Ce param猫tre permet uniquement une mesure continue sur l鈥檃ppareil</string>
+    <string name="prefs_title_gatt_client_notification_intents">Diffuser les intentions de notification GATT par l鈥檌nterm茅diaire de l鈥橝PI Intent BLE</string>
+    <string name="prefs_title_gatt_client_allow_gatt_interactions">Permettre l鈥檌nteraction GATT par l鈥檌nterm茅diaire de l鈥橝PI Intent BLE</string>
+    <string name="prefs_summary_gatt_client_allow_gatt_interactions">Autoriser l鈥檈nvoi de commandes de lecture/茅criture et de connexion des caract茅ristiques BLE</string>
+    <string name="prefs_summary_gatt_client_api_package">Restreindre la communication API d鈥檌ntention BLE 脿 ce paquet</string>
+    <string name="backup_restore_importing_files_i_of_n">Importation de fichiers... %1d de %2d</string>
+    <string name="activity_db_management_backup_restore_explanation">Les op茅rations d鈥檌mportation/exportation vous permettent de migrer ou de sauvegarder tous les param猫tres, p茅riph茅riques et donn茅es de Gadgetbridge vers et 脿 partir d鈥檜n fichier Zip.
+\n
+\nImporter un fichier supprimera toutes les donn茅es, les p茅riph茅riques et les pr茅f茅rences existants, en les rempla莽ant compl猫tement par la sauvegarde.</string>
+    <string name="backup_restore_importing_loading">Chargement du fichier鈥�</string>
+    <string name="backup_restore_importing_preferences">Importation des pr茅f茅rences鈥�</string>
+    <string name="backup_restore_exporting_finishing">Finition exportation鈥�</string>
+    <string name="backup_restore_importing">Importation 脿 partir de Zip鈥�</string>
+    <string name="backup_restore_importing_validating">Validation du fichier鈥�</string>
+    <string name="backup_restore_export_complete">Exportation accomplie</string>
+    <string name="backup_restore_warning_files">%1d fichiers 茅chou茅s 脿 锚tre restaur茅s鈥�:
+\n %2s</string>
+    <string name="backup_restore_import_complete">Importation accomplie</string>
+    <string name="label_distance_trip">Distance parcourue鈥�: %.1f聽km</string>
+    <string name="label_distance_total">Total鈥�: %.1f km</string>
+    <string name="error_no_cycling_sensor_found">aucun capteur de cyclisme trouv茅</string>
+    <string name="hr_resting">Au repos</string>
+    <string name="hr_maximum">Maximum</string>
+    <string name="hr_minimum">Minimum</string>
+    <string name="hr_average">Moyenne</string>
+    <string name="devicetype_garmin_forerunner_245_music">Garmin Forerunner 245 Music</string>
+    <string name="devicetype_sony_wf_c500">Sony WF-C500</string>
+    <string name="menuitem_heart_rate_push">Pulsation fr茅quence cardiaque</string>
+    <string name="devicetype_garmin_enduro_3">Garmin Enduro 3</string>
+    <string name="devicetype_garmin_forerunner_955">Garmin Forerunner 955</string>
+    <string name="bpm_value_unit">%1$d bpm</string>
+    <string name="label_distance_trip_mph">Voyage鈥�: %.1f mi</string>
+    <string name="label_distance_total_mph">Total鈥�: %.1f mi</string>
+    <string name="devicetype_miband9">Xiaomi Smart Band 9</string>
+    <string name="devicetype_garmin_forerunner_265s">Garmin Forerunner 265S</string>
+    <string name="devicetype_redmi_watch_5_active">Redmi Watch 5 Active</string>
+    <string name="devicetype_huawei_watchgt5">Huawei Watch GT 5 (Pro)</string>
+    <string name="vo2max_running">VO鈧� Max en course</string>
+    <string name="vo2max_cycling">VO鈧� Max en cyclisme</string>
+    <string name="thirty_days_timeline">Timeline 30 jours</string>
+    <string name="devicetype_huawei_watchd2">Huawei Watch D2</string>
+    <string name="max_respiration_rate">Fr茅quence respiratoire max</string>
+    <string name="devicetype_idasen">IKEA Idasen Desk</string>
+    <string name="devicetype_colmi_r10">Colmi R10</string>
+    <string name="average_respiration_rate">Fr茅quence respiratoire</string>
+    <string name="min_respiration_rate">Fr茅quence respiratoire min</string>
+    <string name="hrv_sdrr">VFC SDRR</string>
+    <string name="milliseconds_ms">ms</string>
+    <string name="idasen_pref_mid_height">Hauteur de la position moyenne (en centim猫tres)</string>
+    <string name="idasen_pref_sit_height">Hauteur de la position assise (en centim猫tres)</string>
+    <string name="idasen_pref_stand_height">Hauteur de la position du support (en centim猫tres)</string>
+    <string name="idasen_pref_value_warning">La valeur doit 锚tre comprise entre 62 - 126 cm</string>
+    <string name="pref_dashboard_widget_today_time_indicator_summary">Afficher un indicateur 脿 l鈥檋eure actuelle, pour s茅parer visuellement les donn茅es d鈥檋ier et d鈥檃ujourd鈥檋ui</string>
+    <string name="pref_dnd_follow_phone_title">Suivre le r茅glage NPD du t茅l茅phone</string>
+    <string name="pref_dnd_follow_phone_summary">Lorsque le mode NPD est activ茅 ou d茅sactiv茅 sur le t茅l茅phone, il bascule automatiquement sur l鈥檃ppareil 茅galement</string>
+    <string name="inactivity_warnings_minimum_steps_title">Nombre minimal de pas</string>
+    <string name="prefs_hrv_monitoring_title">Surveillance de la VFC</string>
+    <string name="date_placeholders__start_time__end_time">%1s - %2s</string>
+    <string name="date_placeholders__date__time">%1s, %1s</string>
+    <string name="mijia_lywsd_comfort_temperature_title">Temperature (掳C)</string>
+    <string name="mijia_lywsd_comfort_temperature_summary">Plage recommand茅e聽: 19 - 27</string>
+    <string name="mijia_lywsd_comfort_level_title">Niveau confort</string>
+    <string name="mijia_lywsd_comfort_humidity_title">Humidit茅 (%)</string>
+    <string name="mijia_lywsd_comfort_level_summary">Configurer les limites de temp茅rature et d鈥檋umidit茅 pour l鈥檃ffihage d\'emoji</string>
+    <string name="mijia_lywsd_comfort_lower">limite inf茅rieure</string>
+    <string name="mijia_lywsd_comfort_upper">Limite sup茅rieure</string>
+    <string name="mijia_lywsd_comfort_humidity_summary">Plage recommand茅e聽: 20 - 85</string>
+    <string name="pref_summary_sync_birthdays">Synchroniser les anniversaires de contact avec les 茅v茅nements du calendrier</string>
+    <string name="pref_title_sync_birthdays">Synchroniser les anniversaires</string>
+    <string name="devicetype_sony_wf_c700n">Sony WF-C700N</string>
+    <string name="birthdays">Anniversaires</string>
+    <string name="busy_task_fetch_hrv_data">R茅cup茅ration des donn茅es VFC</string>
+    <string name="pref_crash_notification_title">Notifier en cas d\'erreur</string>
+    <string name="pref_crash_notification_summary">Quand l\'application plante, afficher une notification avec l\'erreur</string>
+    <string name="fmtPaceAverage">Rythme moyen</string>
+    <string name="hrv_rmssd">VFC RMSSD</string>
+    <string name="breaths_per_min">inspirations/min</string>
+    <string name="prefs_hrv_monitoring_description">Surveiller automatiquement la variabilit茅 de la fr茅quence cardiaque tout au long de la journ茅e</string>
+    <string name="pref_dashboard_widget_today_yesterday_data_summary">Afficher les donn茅es d鈥檋ier qui sont entre l鈥檋eure actuelle et minuit</string>
+    <string name="pref_title_calendar_lookahead">Nombre de jours 脿 venir</string>
+    <string name="pref_summary_calendar_lookahead">Synchroniser jusqu鈥櫭� %1s jours d鈥櫭﹙茅nements du calendrier</string>
+    <string name="app_crash_notification_title">%1s a plant茅</string>
+    <string name="app_crash_share_stacktrace">Partager l\'erreur</string>
+    <string name="hrZoneMaximum">Maximum</string>
+    <string name="hrZoneThreshold">Seuil</string>
+    <string name="workout_set_reps">R茅p茅titions</string>
+    <string name="devicetype_garmin_venu_sq_2">Garmin Venu Sq 2</string>
+    <string name="devicetype_garmin_fenix_6s_sapphire">Garmin Fenix 6S Sapphire</string>
+    <string name="devicetype_huawei_watchgtcyber">Huawei Watch GT Cyber</string>
+    <string name="pref_dashboard_widget_today_time_indicator_title">indicateur d\'instant pr茅sent</string>
+    <string name="pref_dashboard_widget_today_yesterday_data_title">Donn茅es d\'hier</string>
+    <string name="activity_detail_share_json_details">Partager les d茅tails JSON</string>
+    <string name="number_selected_items">%1d s茅lectionn茅</string>
+    <string name="contact_birthday">Anniversaire de %1s</string>
+    <string name="paceCorrection">Correction</string>
+    <string name="devicetype_garmin_venu_sq">Garmin Venu Sq</string>
+    <string name="devicetype_garmin_fenix_8">Garmin Fenix 8</string>
+    <string name="hrZoneEasy">Facile</string>
+    <string name="idasen_control_button_sit">Assis</string>
+    <string name="idasen_control_button_stand">Debout</string>
+    <string name="idasen_control_button_mid">Moyen</string>
+    <string name="inactivity_warnings_minimum_steps_summary">Nombre minimal de pas 脿 prendre en compte pour le seuil de minutes</string>
+    <string name="devicetype_garmin_fenix_6s_pro">Garmin Fenix 6S Pro</string>
+    <string name="devicetype_bandw_pseries">Bowers and Wilkins P series</string>
+    <string name="devicetype_garmin_forerunner_55">Garmin Forerunner 55</string>
+    <string name="devicetype_garmin_forerunner_620">Garmin Forerunner 620</string>
+    <string name="devicetype_huawei_band3pro">Huawei Band 3 (Pro)</string>
+    <string name="pref_voice_passthrough_enabled">Passthrough</string>
+    <string name="pref_voice_passthrough_enabled_summary">Laissez les sons externes passer 脿 vos oreilles</string>
+    <string name="pref_voice_passthrough_level">Niveau de passthrough</string>
+    <string name="music_upload_info">Vous 锚tes sur le point de charger la musique suivante聽:\n\n%1$s\nTitre聽: %2$s\nAlbum聽: %3$s\n</string>
+    <string name="devicetype_garmin_fenix_7">Garmin Fenix 7</string>
+    <string name="pref_wear_sensor_title">Capteur d\'usure</string>
+    <string name="devicetype_oppo_enco_air">Oppo Enco Air</string>
+    <string name="pref_wear_sensor_summary">D茅tecter lorsque l鈥檃ppareil n鈥檈st pas port茅</string>
+    <string name="devicetype_garmin_forerunner_235">Garmin Forerunner 235</string>
+    <string name="devicetype_sony_wi_c100">Sony WI-C100</string>
+    <string name="file_already_exists">Fichier existant</string>
+    <string name="active_calories_short">Active</string>
+    <string name="active_calories">Calories actives</string>
+    <string name="active_calories_goal">Objectif actif</string>
+    <string name="total_calories_burnt">Total br没l茅</string>
+    <string name="activity_prefs_goal_active_calories_burnt">Objectif quotidien聽: calories actives br没l茅es</string>
+    <string name="devicetype_garmin_instinct_2">Garmin Instinct 2</string>
+    <string name="title_activity_musicmanager">gestionnaire de musique</string>
+    <string name="pref_music_management_summary">G茅rer la musique sur la montre</string>
+    <string name="music_huawei_device_info">Formats pris en charge聽: %1$s\nStockage de la montre聽: %2$d MB</string>
+    <string name="permission_bluetooth_title">Bluetooth</string>
+    <string name="permission_bluetooth_admin_summary">D茅couvrir et appairer des appareils Bluetooth</string>
+    <string name="permission_bluetooth_scan_title">Scan Bluetooth</string>
+    <string name="permission_post_notification_title">Notifications de publication</string>
+    <string name="menuitem_calories_segmented">Calories (segment茅es)</string>
+    <string name="menuitem_calories_active_goal">Objectif en calories (actif)</string>
+    <string name="pref_music_management_title">G茅rer la musique</string>
+    <string name="redmi_buds_5_pro_equalizer_preset_bass">Am茅liorer les basses</string>
+    <string name="redmi_buds_5_pro_equalizer_preset_voice">Am茅liorer la voix</string>
+    <string name="redmi_buds_5_pro_equalizer_preset_custom">Personnalis茅</string>
+    <string name="redmi_buds_5_pro_equalizer_band_62">62 Hz</string>
+    <string name="redmi_buds_5_pro_equalizer_band_125">125 Hz</string>
+    <string name="redmi_buds_5_pro_equalizer_band_250">250 Hz</string>
+    <string name="redmi_buds_5_pro_equalizer_band_2k">2 kHz</string>
+    <string name="redmi_buds_5_pro_equalizer_band_4k">4 kHz</string>
+    <string name="redmi_buds_5_pro_equalizer_band_8k">8 kHz</string>
+    <string name="redmi_buds_5_pro_equalizer_band_12k">12 kHz</string>
+    <string name="redmi_buds_5_pro_equalizer_band_16k">16 kHz</string>
+    <string name="redmi_buds_5_pro_equalizer_neg6">-6 dB</string>
+    <string name="redmi_buds_5_pro_equalizer_band_500">500 Hz</string>
+    <string name="redmi_buds_5_pro_equalizer_band_1k">1 kHz</string>
+    <string name="redmi_buds_5_pro_equalizer_neg2">-2 dB</string>
+    <string name="redmi_buds_5_pro_equalizer_neg1">-1 dB</string>
+    <string name="redmi_buds_5_pro_equalizer_zero">0 dB</string>
+    <string name="redmi_buds_5_pro_equalizer_1">1 dB</string>
+    <string name="redmi_buds_5_pro_equalizer_neg5">-5 dB</string>
+    <string name="redmi_buds_5_pro_equalizer_neg4">-4 dB</string>
+    <string name="redmi_buds_5_pro_equalizer_neg3">-3 dB</string>
+    <string name="redmi_buds_5_pro_equalizer_6">6 dB</string>
+    <string name="devicetype_miband9pro">Xiaomi Smart Band 9 Pro</string>
+    <string name="devicetype_oppo_enco_air2">Oppo Enco Air2</string>
+    <string name="redmi_buds_5_pro_anc_deep">Profond</string>
+    <string name="music_delete_confirm_description">脢tes-vous s没r de vouloir supprimer \'%1$s\'鈥�?</string>
+    <string name="music_rename_playlist">Renommer la playlist</string>
+    <string name="first_start_overview_dashboard">Le tableau de bord vous permet d鈥檃voir une id茅e rapide de comment vous allez aujourd\'hui. L鈥檃ffichage du calendrier indique l鈥櫭﹖at de vos objectifs sur un mois entier.</string>
+    <string name="permission_fine_location_summary">Rechercher les appareils Bluetooth</string>
+    <string name="permission_background_location_title">Localisation en arri猫re-plan</string>
+    <string name="first_start_get_started_title">Commencer</string>
+    <string name="first_start_get_started_desc">Pour commencer, ajoutez votre premier appareil directement 脿 partir de cet 茅cran, restaurez une sauvegarde ou commencez avec une base de donn茅es propre.</string>
+    <string name="first_start_get_started_add_first_device_button">Ajouter le premier appareil</string>
+    <string name="first_start_get_started_restore_button">Restaurer la sauvegarde</string>
+    <string name="first_start_open_source_text">Gadgetbridge est une application open source. Elle est d茅velopp茅e par la communaut茅, pour la communaut茅.\n\nToute personne est invit茅e 脿 contribuer via le code, la documentation, les tests et les dons.\n\nGadgetbridge ne contient aucune publicit茅 ni aucun suivi. Il conserve vos donn茅es localement sur votre appareil Android, donc il est 100% respectueux de la vie priv茅e.\n\nVisitez notre site web pour plus d鈥檌nformations, de documentation et des liens vers nos canaux de communication.</string>
+    <string name="first_start_permissions_title">Permissions</string>
+    <string name="first_start_permissions_desc">Gadgetbridge a besoin de beaucoup de permissions pour ex茅cuter toutes ses fonctions. Passez en revue les permissions et leurs objectifs ci-dessous.</string>
+    <string name="first_start_permissions_request_all_button">Demander toutes les permissions</string>
+    <string name="first_start_permissions_request_button">Demande</string>
+    <string name="first_start_get_started_go_to_app_button">Aller 脿 l鈥檃pplication</string>
+    <string name="first_start_welcome_title">Bienvenue</string>
+    <string name="lowest">Plus bas</string>
+    <string name="redmi_buds_5_pro_double_connection">Double connexion</string>
+    <string name="redmi_buds_5_pro_equalizer_preset_standard">Standard</string>
+    <string name="redmi_buds_5_pro_equalizer_preset_treble">Am茅liorer les aigus</string>
+    <string name="redmi_buds_5_pro_equalizer_3">3 dB</string>
+    <string name="redmi_buds_5_pro_equalizer_5">5 dB</string>
+    <string name="music_error">Une erreur s\'est produite</string>
+    <string name="first_start_open_source_title">Open Source</string>
+    <string name="permission_manage_dnd_title">G茅rer Ne pas d茅ranger</string>
+    <string name="permission_bluetooth_summary">Connexion aux appareils Bluetooth</string>
+    <string name="permission_bluetooth_scan_summary">Recherche de nouveaux appareils Bluetooth</string>
+    <string name="permission_bluetooth_connect_title">Connexion Bluetooth</string>
+    <string name="permission_bluetooth_connect_summary">Connexion 脿 des appareils Bluetooth d茅j脿 appari茅s</string>
+    <string name="permission_internet_access_title">Acc猫s Internet</string>
+    <string name="permission_internet_access_summary">Synchronisation avec les ressources en ligne</string>
+    <string name="permission_contacts_title">Contacts</string>
+    <string name="respiratoryrate">Rythme respiratoire</string>
+    <string name="devicetype_redmi_buds_5_pro">Redmi Buds 5 Pro</string>
+    <string name="highest">Plus haut</string>
+    <string name="redmi_buds_5_pro_anc_balanced">Equilibr茅</string>
+    <string name="redmi_buds_5_pro_double_connection_description">Permettre aux 茅couteurs de se connecter 脿 deux appareils en m锚me temps</string>
+    <string name="redmi_buds_5_pro_anc_light">L茅ger</string>
+    <string name="redmi_buds_5_pro_transparency_voice">Am茅liorer les voix</string>
+    <string name="redmi_buds_5_pro_transparency_ambient">Am茅liorer les sons ambiants</string>
+    <string name="redmi_buds_5_pro_adaptive_sound_description">Ajuste le son en fonction de la forme de l鈥檕reille et de l鈥檈nvironnement</string>
+    <string name="first_start_overview_title">Aper莽u</string>
+    <string name="redmi_buds_5_pro_equalizer_2">2 dB</string>
+    <string name="first_start_overview_desc">Gadgetbridge a deux vues principales, chacune avec son propre but.</string>
+    <string name="redmi_buds_5_pro_equalizer_4">4 dB</string>
+    <string name="music_delete_multiple_confirm_description">脢tes-vous s没r de vouloir supprimer \'%1$d\' chansons鈥�?</string>
+    <string name="first_start_overview_devices">La vue des appareils affiche tous les appareils que vous avez configur茅s et leur statut, et donne acc猫s 脿 des fonctions sp茅cifiques aux appareils telles que des graphiques d茅taill茅s, des param猫tres, des applications et des alarmes.</string>
+    <string name="permission_notifications_summary">Transfert des notifications vers les gadgets connect茅s</string>
+    <string name="permission_displayover_title">Afficher par-dessus d\'autres applications</string>
+    <string name="permission_displayover_summary">Utilis茅 par Bangle.js pour d茅marrer des applications et d鈥檃utres fonctionnalit茅s sur votre t茅l茅phone</string>
+    <string name="permission_background_location_summary">Recherche des p茅riph茅riques Bluetooth en arri猫re-plan et envoi de l鈥檈mplacement 脿 certains gadgets</string>
+    <string name="pref_developer_add_test_activities_summary">Remplir la base de donn茅es avec des activit茅s de test factices</string>
+    <string name="pref_developer_add_test_activities_title">Ajouter des activit茅s de test</string>
+    <string name="devicetype_realme_buds_t110">Realme Buds T110</string>
+    <string name="music_add_to_playlist">Ajouter 脿 la playlist</string>
+    <string name="music_delete_from_playlist">Supprimer de la liste de lecture</string>
+    <string name="music_delete">Supprimer la chanson</string>
+    <string name="music_all_songs">Toutes les chansons</string>
+    <string name="music_new_playlist">Nouvelle playlist</string>
+    <string name="permission_calendar_title">Calendrier</string>
+    <string name="permission_calendar_summary">Envoi de calendrier aux gadgets</string>
+    <string name="permission_receive_sms_title">Recevoir des SMS</string>
+    <string name="permission_send_sms_title">Envoyer des SMS</string>
+    <string name="permission_send_sms_summary">Envoi de SMS (r茅ponse pr茅remplie) 脿 partir des gadgets</string>
+    <string name="permission_read_call_log_title">Lire le journal des appels</string>
+    <string name="permission_call_phone_title">Appel t茅l茅phonique</string>
+    <string name="permission_call_phone_summary">Lancer des appels t茅l茅phoniques 脿 partir des gadgets</string>
+    <string name="permission_process_outgoing_calls_title">Traiter les appels sortants</string>
+    <string name="permission_answer_phone_calls_title">R茅pondre aux appels t茅l茅phoniques</string>
+    <string name="permission_external_storage_title">Stockage externe</string>
+    <string name="permission_contacts_summary">Envoi de contacts vers les gadgets</string>
+    <string name="permission_receive_sms_summary">Transfert de messages SMS vers les gadgets</string>
+    <string name="permission_read_phone_state_title">Lire l\'茅tat du t茅l茅phone</string>
+    <string name="permission_answer_phone_calls_summary">R茅pondre aux appels t茅l茅phoniques 脿 partir des gadgets</string>
+    <string name="permission_read_call_log_summary">Transfert du journal des appels aux gadgets</string>
+    <string name="permission_process_outgoing_calls_summary">Lecture du num茅ro d鈥檜n appel sortant pour l鈥檃fficher sur un gadget</string>
+    <string name="permission_query_all_packages_title">Interroger tous les paquets</string>
+    <string name="permission_external_storage_summary">Utilisation d鈥檌mages, de sonneries, de fichiers d鈥檃pplications et plus encore</string>
+    <string name="permission_query_all_packages_summary">Lecture des noms et ic么nes de toutes les applications install茅es</string>
+</resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-gl/strings.xml b/app/src/main/res/values-gl/strings.xml
new file mode 100644
index 0000000..f05bb39
--- /dev/null
+++ b/app/src/main/res/values-gl/strings.xml
@@ -0,0 +1,533 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <string name="application_name_generic">Gadgetbridge</string>
+    <string name="title_activity_controlcenter_generic">Gadgetbridge</string>
+    <string name="action_settings">Axustes</string>
+    <string name="action_debug">Depuraci贸n</string>
+    <string name="action_quit">Sa铆r</string>
+    <string name="action_donate">Doar</string>
+    <string name="controlcenter_fetch_activity_data">Sincronizar</string>
+    <string name="controlcenter_find_device">Atopar dispositivo perdido</string>
+    <string name="controlcenter_take_screenshot">Capturar pantalla</string>
+    <string name="controlcenter_disconnect">Desconectar</string>
+    <string name="controlcenter_delete_device">Borrar dispositivo</string>
+    <string name="controlcenter_delete_device_name">Borrar %1$s</string>
+    <string name="controlcenter_delete_device_dialogmessage">Borrar谩s o dispositivo e todos os datos asociados!</string>
+    <string name="controlcenter_navigation_drawer_open">Abrir men煤 de navegaci贸n</string>
+    <string name="controlcenter_navigation_drawer_close">Pechar men煤 de navegaci贸n</string>
+    <string name="controlcenter_snackbar_need_longpress">Manter pulsado o icono para desconectar</string>
+    <string name="controlcenter_snackbar_disconnecting">Desconectando</string>
+    <string name="controlcenter_snackbar_connecting">Conectando鈥�</string>
+    <string name="controlcenter_snackbar_requested_screenshot">Capturando pantalla do dispositivo</string>
+    <string name="title_activity_debug">Depurar</string>
+    <string name="title_activity_appmanager">Xestor de App</string>
+    <string name="appmanager_cached_watchapps_watchfaces">Apps en cach茅</string>
+    <string name="appmanager_installed_watchapps">Apps instaladas</string>
+    <string name="appmanager_installed_watchfaces">Visores instalados</string>
+    <string name="appmananger_app_delete">Borrar</string>
+    <string name="appmananger_app_delete_cache">Borrar e retirar da cach茅</string>
+    <string name="appmananger_app_reinstall">Reinstalar</string>
+    <string name="appmanager_app_openinstore">Buscar na appstore de Pebble</string>
+    <string name="appmanager_health_activate">Activar</string>
+    <string name="appmanager_health_deactivate">Desactivar</string>
+    <string name="appmanager_hrm_activate">Activar Monitor de Ritmo Card铆aco</string>
+    <string name="appmanager_hrm_deactivate">Desactivar Monitor de Ritmo Card铆aco</string>
+    <string name="appmanager_weather_activate">Activar app de clima do sistema</string>
+    <string name="appmanager_weather_deactivate">Desactivar app de clima do sistema</string>
+    <string name="appmanager_weather_install_provider">Instalar a app de notificaci贸ns do clima</string>
+    <string name="app_configure">Configurar</string>
+    <string name="app_move_to_top">Mover para arriba</string>
+    <string name="title_activity_calblacklist">Calendarios bloqueados</string>
+    <string name="title_activity_fw_app_insaller">Instalador de FW/App</string>
+    <string name="fw_upgrade_notice">Est谩 a piques de instalar o firmware %s.</string>
+    <string name="fw_multi_upgrade_notice">Est谩 a piques de instalar os firmwares %1$s e %2$s no canto dos que est谩n actualmente na s煤a Mi Band.</string>
+    <string name="miband_firmware_known">Este firmware foi probado e 茅 compat铆bel co Gadgetbridge.</string>
+    <string name="miband_firmware_unknown_warning">Este firmware non foi probado e pode non ser compat铆bel co Gadgetbridge. 
+\n 
+\nRECOMENDASE QUE NON o instale!</string>
+    <string name="miband_firmware_suggest_whitelist">Se a铆nda as铆 quere proceder e todo contin煤a a funcionar normalmente, por favor avise aos programadores do Gadgetbridge para que permitan a versi贸n %s do firmware.</string>
+    <string name="title_activity_settings">Axustes</string>
+    <string name="pref_header_general">Axustes xerais</string>
+    <string name="pref_title_general_autoconnectonbluetooth">Conectar ao dispositivo ao activar o Bluetooth</string>
+    <string name="pref_title_general_autostartonboot">Iniciar automaticamente</string>
+    <string name="pref_title_general_autoreconnect">Reconectar automaticamente</string>
+    <string name="pref_title_audio_player">Reprodutor de audio preferido</string>
+    <string name="pref_default">Predefinido</string>
+    <string name="pref_header_datetime">Data e Hora</string>
+    <string name="pref_title_datetime_syctimeonconnect">Sincronizar hora</string>
+    <string name="pref_summary_datetime_syctimeonconnect">Sincronizar hora ao conectar e ao cambiar o fuso horario no android</string>
+    <string name="pref_title_theme">Tema</string>
+    <string name="pref_theme_light">Claro</string>
+    <string name="pref_theme_dark">Escuro</string>
+    <string name="pref_title_language">Idioma</string>
+    <string name="pref_title_minimize_priority">Ocultar a notificaci贸n de Gadgetbridge</string>
+    <string name="pref_summary_minimize_priority_off">Amosar icono na barra de estado e notificaci贸n na pantalla de bloqueo</string>
+    <string name="pref_summary_minimize_priority_on">Ocultar icono na barra de estado e notificaci贸n na pantalla de bloqueo</string>
+    <string name="pref_header_notifications">Notificaci贸ns</string>
+    <string name="pref_title_notifications_repetitions">Repetici贸ns</string>
+    <string name="pref_title_notifications_call">Chamadas telef贸nicas</string>
+    <string name="pref_title_notifications_sms">SMS</string>
+    <string name="pref_title_notifications_pebblemsg">Mensaxes de Pebble</string>
+    <string name="pref_summary_notifications_pebblemsg">Soporte para aplicaci贸ns que env铆an notificaci贸ns a Pebble atrav茅s de PebbleKit.</string>
+    <string name="pref_title_notifications_generic">Soporte para notificaci贸ns xen茅ricas</string>
+    <string name="pref_title_whenscreenon">鈥� tam茅n coa pantalla acesa</string>
+    <string name="pref_title_notification_filter">Non Molestar</string>
+    <string name="pref_summary_notification_filter">Parar notificaci贸ns indesexadas no modo Non Molestar</string>
+    <string name="pref_title_transliteration">Transliteraci贸n</string>
+    <string name="pref_summary_transliteration">Activar isto se o dispositivo non ten soporte para o seu idioma</string>
+    <string name="always">Sempre</string>
+    <string name="when_screen_off">Cando a pantalla est谩 apagada</string>
+    <string name="never">Nunca</string>
+    <string name="pref_header_privacy">Privacidade</string>
+    <string name="pref_title_call_privacy_mode">Modo de privacidade de chamada</string>
+    <string name="pref_call_privacy_mode_off">Amosar nome e n煤mero</string>
+    <string name="pref_call_privacy_mode_name">Ocultar nome pero amosar n煤mero</string>
+    <string name="pref_call_privacy_mode_number">Ocultar n煤mero pero amosar nome</string>
+    <string name="pref_call_privacy_mode_complete">Ocultar nome e n煤mero</string>
+    <string name="pref_blacklist_calendars">Bloquear Calendarios</string>
+    <string name="pref_header_cannned_messages">Hist贸rico de mensaxes</string>
+    <string name="pref_title_canned_replies">Respostas</string>
+    <string name="pref_title_canned_reply_suffix">Sufixo com煤n</string>
+    <string name="pref_title_canned_messages_dismisscall">Rexeitar chamada</string>
+    <string name="pref_title_canned_messages_set">Actualizar no Pebble</string>
+    <string name="pref_header_development">Opci贸ns de desenvolvedor</string>
+    <string name="pref_title_development_miaddr">Enderezo do MiBand</string>
+    <string name="pref_title_pebble_settings">Axustes do Pebble</string>
+    <string name="pref_header_activitytrackers">Monitores de actividade</string>
+    <string name="pref_title_pebble_activitytracker">Monitor de actividade preferido</string>
+    <string name="pref_title_pebble_sync_health">Sincronizar con Pebble Health</string>
+    <string name="pref_title_pebble_sync_misfit">Sincronizar con Misfit</string>
+    <string name="pref_title_pebble_sync_morpheuz">Sincronizar con Morpheuz</string>
+    <string name="pref_title_enable_outgoing_call">Soporte para chamadas sa铆ntes</string>
+    <string name="pref_title_enable_pebblekit">Permitir acceso a Apps Android de terceiros</string>
+    <string name="pref_summary_enable_pebblekit">Habilitar soporte experimental para Apps Android que usen PebbleKit</string>
+    <string name="pref_title_sunrise_sunset">Mencer e solpor</string>
+    <string name="pref_summary_sunrise_sunset">Enviar mencer e solpor baseado na localizaci贸n do Pebble</string>
+    <string name="pref_title_enable_calendar_sync">Sincronizar calendario</string>
+    <string name="pref_title_autoremove_notifications">Borrar automaticamente as notificaci贸ns rexeitadas</string>
+    <string name="pref_title_pebble_privacy_mode">Modo de privacidade</string>
+    <string name="pref_pebble_privacy_mode_off">Notificaci贸ns normais</string>
+    <string name="pref_pebble_privacy_mode_complete">S贸 amosar o icono de notificaci贸n</string>
+    <string name="pref_header_location">Localizaci贸n</string>
+    <string name="pref_title_location_aquire">Obter ubicaci贸n</string>
+    <string name="pref_title_location_latitude">Latitude</string>
+    <string name="pref_title_location_longitude">Lonxitude</string>
+    <string name="pref_title_location_keep_uptodate">Manter actualizada a ubicaci贸n</string>
+    <string name="toast_enable_networklocationprovider">Por favor, active a ubicaci贸n por rede</string>
+    <string name="toast_aqurired_networklocation">ubicaci贸n obtida</string>
+    <string name="pref_title_pebble_forceprotocol">Forzar o protocolo de notificaci贸n</string>
+    <string name="pref_summary_pebble_forceprotocol">Esta opci贸n forza o uso do protocolo de notificaci贸n m谩is recente dependendo da versi贸n do firmware. HABILITE SE SABE O QUE EST脕 FACENDO!</string>
+    <string name="pref_title_pebble_forceuntested">Habilitar caracter铆sticas non probadas</string>
+    <string name="pref_title_pebble_forcele">Preferir sempre BLE</string>
+    <string name="pref_title_pebble_enable_applogs">Activar rexistro das Apps do dispositivo</string>
+    <string name="pref_summary_pebble_enable_applogs">Causa que os rexistros das apps do reloxo sexan gardados polo Gadgetbridge (necesita reconectar)</string>
+    <string name="pref_title_pebble_reconnect_attempts">Intentos de reconexi贸n</string>
+    <string name="pref_title_unit_system">Unidades</string>
+    <string name="pref_title_timeformat">Formato de hora</string>
+    <string name="pref_title_screentime">Tempo de pantalla acesa</string>
+    <string name="prefs_title_all_day_heart_rate">Medici贸n da frecuencia card铆aca durante todo o d铆a</string>
+    <string name="not_connected">Non conectado</string>
+    <string name="connecting">Conectando</string>
+    <string name="connected">Conectado</string>
+    <string name="unknown_state">Estado desco帽ecido</string>
+    <string name="_unknown_">(desco帽ecido)</string>
+    <string name="test">Probar</string>
+    <string name="test_notification">Probar notificaci贸n</string>
+    <string name="this_is_a_test_notification_from_gadgetbridge">Esta 茅 unha notificaci贸n de proba desde Gadgetbridge</string>
+    <string name="bluetooth_is_not_supported_">O Bluetooth non est谩 soportado.</string>
+    <string name="bluetooth_is_disabled_">O Bluetooh est谩 desactivado.</string>
+    <string name="tap_connected_device_for_app_mananger">Toque nun dispositivo conectado para xestionar as Apps</string>
+    <string name="tap_connected_device_for_activity">Toque nun dispositivo conectado para ver a actividade</string>
+    <string name="tap_connected_device_for_vibration">Toque nun dispositivo conectado para facelo Vibrar</string>
+    <string name="tap_a_device_to_connect">Toque nun dispositivo para conectar</string>
+    <string name="cannot_connect_bt_address_invalid_">Non foi pos铆bel conectar. Enderezo Bluetooth inv谩lido?</string>
+    <string name="gadgetbridge_running_generic">Gadgetbridge en execuci贸n</string>
+    <string name="installing_binary_d_d">Instalando binario %1$d/%2$d</string>
+    <string name="installation_failed_">Fallou a instalaci贸n</string>
+    <string name="installation_successful">Instalado</string>
+    <string name="initialized">inicializado</string>
+    <string name="appversion_by_creator">%1$s de %2$s</string>
+    <string name="title_activity_discovery">Descubrir dispositivos</string>
+    <string name="discovery_stop_scanning">Parar a busca</string>
+    <string name="discovery_start_scanning">Comezar a busca</string>
+    <string name="action_discover">Conectar novo dispositivo</string>
+    <string name="device_with_rssi">%1$s (%2$s)</string>
+    <string name="title_activity_android_pairing">Emparellar dispositivo</string>
+    <string name="android_pairing_hint">Usar o men煤 de emparellamento Bluetooth de Android para vincular o dispositivo.</string>
+    <string name="title_activity_mi_band_pairing">Emparella a t煤a MiBand</string>
+    <string name="pairing">Emparellando con %s鈥�</string>
+    <string name="pairing_creating_bond_with">Creando v铆nculo con %1$s (%2$s)</string>
+    <string name="pairing_unable_to_pair_with">Incapaz de emparellar con %1$s (%2$s)</string>
+    <string name="preferences_category_device_specific_settings">Axustes espec铆ficos do dispositivo</string>
+    <string name="pref_summary_enable_outgoing_call">Deshabilitar isto tam茅n impedir谩 o Pebble 2/LE vibrar en chamadas sa铆ntes</string>
+    <string name="pref_header_pebble_timeline">Timeline do Pebble</string>
+    <string name="pref_summary_enable_calendar_sync">Enviar os eventos do calendario ao timeline</string>
+    <string name="pref_summary_autoremove_notifications">As notificaci贸ns borranse autom谩ticamente do Pebble cando se rexeitan en Android</string>
+    <string name="pref_pebble_privacy_mode_content">Desplaza a notificaci贸n fora da pantalla</string>
+    <string name="pref_summary_location_keep_uptodate">Intentar obter a ubicaci贸n durante a execuci贸n, usar a ubicaci贸n almacenada como respaldo</string>
+    <string name="pref_summary_pebble_forceuntested">Activar caracter铆sticas non probadas. 隆ACTIVAO S脫 SE SABES O QUE FAS!</string>
+    <string name="pref_title_pebble_mtu_limit">Pebble 2/LE l铆mite de GATT MTU</string>
+    <string name="pref_summary_pebble_mtu_limit">Se o teu Pebble 2/Pebble LE non funciona correctamente, proba esta opci贸n para limitar o MTU (rango v谩lido 20鈥�512)</string>
+    <string name="pref_title_pebble_always_ack_pebblekit">Anticipar confirmaci贸ns do PebbleKit</string>
+    <string name="preferences_hplus_settings">Axustes de HPlus/Makibes</string>
+    <string name="firmware_install_warning">EST脕S INTENTANDO INSTALAR UN FIRMWARE, CONTIN脷A BAIXO A T脷A RESPONSABILIDADE
+\n
+\n
+\nEste firmware 茅 para a revisi贸n de HW: %s</string>
+    <string name="app_install_info">Est谩s a piques de instalar a seguinte app:
+\n
+\n
+\n%1$s Versi贸n %2$s de %3$s
+\n</string>
+    <string name="n_a">N/D</string>
+    <string name="pairing_in_progress">Asociaci贸n en progreso: %1$s (%2$s)</string>
+    <string name="pairing_already_bonded">Xa asociado con %1$s (%2$s), conectando 鈥�</string>
+    <string name="message_cannot_pair_no_mac">Neng煤n enderezo MAC fornecido, non 茅 pos铆bel emparellar.</string>
+    <string name="preferences_miband_settings">Axustes de MiBand / Amazfit</string>
+    <string name="male">Home</string>
+    <string name="female">Muller</string>
+    <string name="other">Outro</string>
+    <string name="left">Esquerda</string>
+    <string name="right">Dereita</string>
+    <string name="miband_pairing_using_dummy_userdata">Non se proporcionaron datos de usuario v谩lidos, entrementres usaranse dados ficticios.</string>
+    <string name="miband_pairing_tap_hint">Cando a s煤a MiBand vibre e parpadee, toque nela algunhas veces seguidas.</string>
+    <string name="appinstaller_install">Instalar</string>
+    <string name="discovery_connected_devices_hint">Torne o seu dispositivo vis铆bel. Os dispositivos actualmente vinculados non son normalmente descubertos. Active a localizaci贸n (i.e, GPS) no Android 6+. Desactive tam茅n a Protecci贸n da Privacidade para o Gadgetbridge, pois esta poder谩 bloquear e levar ao reinicio do seu tel茅fono. Se despois de alg煤ns minutos o dispositivo non foi encontrado, tente outra vez ap贸s reiniciar o tel茅fono.</string>
+    <string name="discovery_note">Nota:</string>
+    <string name="candidate_item_device_image">Imaxe do dispositivo</string>
+    <string name="miband_prefs_alias">Nome/Alcume</string>
+    <string name="pref_header_vibration_count">Cantidade de vibraci贸ns</string>
+    <string name="title_activity_sleepmonitor">Monitor de sono</string>
+    <string name="pref_write_logfiles">Escreber arquivos de rexistro</string>
+    <string name="initializing">Inicializando</string>
+    <string name="busy_task_fetch_activity_data">Adquirindo datos de actividade</string>
+    <string name="sleep_activity_date_range">De %1$s at茅 %2$s</string>
+    <string name="prefs_wearside">De que lado a usa?</string>
+    <string name="pref_screen_vibration_profile">Perfil de vibraci贸n</string>
+    <string name="vibration_profile_staccato">Moi curto</string>
+    <string name="vibration_profile_short">Curto</string>
+    <string name="vibration_profile_medium">Medio</string>
+    <string name="vibration_profile_long">Longo</string>
+    <string name="vibration_profile_waterdrop">Moi longo</string>
+    <string name="vibration_profile_ring">Timbre</string>
+    <string name="vibration_profile_alarm_clock">Alarma</string>
+    <string name="miband_prefs_vibration">Vibraci贸n</string>
+    <string name="vibration_try">Probar</string>
+    <string name="pref_screen_notification_profile_sms">Notificaci贸n de SMS</string>
+    <string name="pref_header_vibration_settings">Axustes de vibraci贸n</string>
+    <string name="pref_screen_notification_profile_generic">Notificaci贸n xen茅rica</string>
+    <string name="pref_screen_notification_profile_email">Notificaci贸n de correo electr贸nico</string>
+    <string name="pref_screen_notification_profile_incoming_call">Notificaci贸n de chamada entrante</string>
+    <string name="pref_screen_notification_profile_generic_chat">Conversas</string>
+    <string name="pref_screen_notification_profile_generic_navigation">Navegaci贸n</string>
+    <string name="pref_screen_notification_profile_generic_social">Rede social</string>
+    <string name="stats_title">Zonas de velocidade</string>
+    <string name="stats_x_axis_label">Minutos totais</string>
+    <string name="stats_y_axis_label">Pasos por minuto</string>
+    <string name="control_center_find_lost_device">Atopar dispositivo perdido</string>
+    <string name="control_center_cancel_to_stop_vibration">Cancelar para deter a vibraci贸n.</string>
+    <string name="title_activity_charts">A t煤a actividade</string>
+    <string name="title_activity_set_alarm">Configurar alarmas</string>
+    <string name="controlcenter_start_configure_alarms">Configurar alarmas</string>
+    <string name="title_activity_alarm_details">Detalles da alarma</string>
+    <string name="alarm_sun_short">Dom</string>
+    <string name="alarm_mon_short">Lun</string>
+    <string name="alarm_tue_short">Mar</string>
+    <string name="alarm_wed_short">M茅r</string>
+    <string name="alarm_thu_short">Xov</string>
+    <string name="alarm_fri_short">Ven</string>
+    <string name="alarm_sat_short">S谩b</string>
+    <string name="alarm_smart_wakeup">Espertador intelixente</string>
+    <string name="user_feedback_miband_set_alarms_failed">Houbo un erro configurando as alarmas, por favor t茅ntao de novo.</string>
+    <string name="user_feedback_miband_set_alarms_ok">Alarmas enviadas ao dispositivo.</string>
+    <string name="chart_no_data_synchronize">Sen datos. Sincronizar dispositivo?</string>
+    <string name="user_feedback_miband_activity_data_transfer">A piques de transferir %1$s de datos desde %2$s</string>
+    <string name="miband_prefs_fitness_goal">Obxectivo de pasos diarios</string>
+    <string name="dbaccess_error_executing">Erro executando \'%1$s\'</string>
+    <string name="controlcenter_start_activitymonitor">A t煤a actividade (ALPHA)</string>
+    <string name="cannot_connect">Impos铆bel conectar: %1$s</string>
+    <string name="installer_activity_unable_to_find_handler">Non foi pos铆bel atopar un controlador para instalar o arquivo.</string>
+    <string name="pbw_install_handler_unable_to_install">Impos铆bel instalar o arquivo fornecido: %1$s</string>
+    <string name="pbw_install_handler_hw_revision_mismatch">Non 茅 pos铆bel instalar o firmware fornecido: Non corresponde coa versi贸n do hardware do seu Pebble.</string>
+    <string name="installer_activity_wait_while_determining_status">Por favor, agarda mentres se determina o estado da instalaci贸n鈥�</string>
+    <string name="notif_battery_low_title">Dispositivo con bater铆a baixa!</string>
+    <string name="notif_battery_low_percent">%1$s bateria restante: %2$s%%</string>
+    <string name="notif_battery_low_bigtext_last_charge_time">脷ltima carga: %s 
+\n</string>
+    <string name="notif_battery_low_bigtext_number_of_charges">N煤mero de cargas: %s</string>
+    <string name="sleepchart_your_sleep">Sono</string>
+    <string name="weeksleepchart_sleep_a_week">Sono por semana</string>
+    <string name="weeksleepchart_today_sleep_description">Sono hoxe, obxectivo: %1$s</string>
+    <string name="weekstepschart_steps_a_week">Pasos por semana</string>
+    <string name="activity_sleepchart_activity_and_sleep">Actividade</string>
+    <string name="updating_firmware">Anovando Firmware鈥�</string>
+    <string name="fwapp_install_device_not_ready">O arquivo non pode ser instalado, o dispositivo non est谩 preparado.</string>
+    <string name="installhandler_firmware_name">%1$s: %2$s %3$s</string>
+    <string name="miband_fwinstaller_compatible_version">Versi贸n compat铆bel</string>
+    <string name="miband_fwinstaller_untested_version">Versi贸n sen probar!</string>
+    <string name="fwappinstaller_connection_state">Conexi贸n ao dispositivo: %1$s</string>
+    <string name="pbw_installhandler_pebble_firmware">Firmware do Pebble %1$s</string>
+    <string name="pbwinstallhandler_correct_hw_revision">Revisi贸n de hardware correcta</string>
+    <string name="pbwinstallhandler_incorrect_hw_revision">Revisi贸n de hardware inv谩lida!</string>
+    <string name="pbwinstallhandler_app_item">%1$s (%2$s)</string>
+    <string name="updatefirmwareoperation_updateproblem_do_not_reboot">Ocorreu un erro coa transferencia do firmware. NON REINICIE a s煤a Mi Band!</string>
+    <string name="updatefirmwareoperation_metadata_updateproblem">Problemas ao transferir os metadados do firmware</string>
+    <string name="updatefirmwareoperation_update_complete">Instalaci贸n do Firmware completa</string>
+    <string name="updatefirmwareoperation_update_complete_rebooting">Instalaci贸n do Firmware completa, reiniciando o dispositivo鈥�</string>
+    <string name="updatefirmwareoperation_write_failed">Fallou a escritura do firmware</string>
+    <string name="chart_steps">Pasos</string>
+    <string name="calories">Calor铆as</string>
+    <string name="distance">Distancia</string>
+    <string name="clock">Reloxo</string>
+    <string name="heart_rate">Ritmo card铆aco</string>
+    <string name="battery">Bater铆a</string>
+    <string name="liveactivity_live_activity">Actividade en tempo real</string>
+    <string name="weeksteps_today_steps_description">Pasos hoxe, obxectivo: %1$s</string>
+    <string name="pref_title_dont_ack_transfer">Non confirmar a transferencia de datos de actividade</string>
+    <string name="pref_summary_dont_ack_transfers">Se os datos de actividade non foran confirmados ao dispositivo, non ser谩n borrados del. 脷til se o GB 茅 utilizado en simult谩neo con outras aplicaci贸ns.</string>
+    <string name="pref_summary_keep_data_on_device">Manter谩 os datos na Mi Band mesmo despois da sincronizaci贸n. 脷til se o GB 茅 utilizado en simult谩neo con outras aplicaci贸ns.</string>
+    <string name="pref_title_low_latency_fw_update">Usar modo de baixa latencia para anovaci贸ns de Firmware</string>
+    <string name="pref_summary_low_latency_fw_update">Isto pode axudar en dispositivos nos que fallen as actualizaci贸ns de firmware</string>
+    <string name="live_activity_steps_history">Historial de pasos</string>
+    <string name="live_activity_current_steps_per_minute">Pasos/min actuais</string>
+    <string name="live_activity_total_steps">Pasos totais</string>
+    <string name="live_activity_steps_per_minute_history">Historial de pasos por minuto</string>
+    <string name="live_activity_start_your_activity">Comeza a t煤a actividade</string>
+    <string name="abstract_chart_fragment_kind_activity">Actividade</string>
+    <string name="abstract_chart_fragment_kind_light_sleep">Sono leve</string>
+    <string name="abstract_chart_fragment_kind_deep_sleep">Sono profundo</string>
+    <string name="abstract_chart_fragment_kind_not_worn">Sen usar</string>
+    <string name="device_not_connected">Non conectado.</string>
+    <string name="user_feedback_all_alarms_disabled">Todas as alarmas deshabilitadas</string>
+    <string name="pref_title_keep_data_on_device">Manter datos de actividade no dispositivo</string>
+    <string name="miband_fwinstaller_incompatible_version">Firmware incompat铆bel</string>
+    <string name="fwinstaller_firmware_not_compatible_to_device">Este firmware non 茅 compat铆bel co dispositivo</string>
+    <string name="miband_prefs_reserve_alarm_calendar">Alarmas reservadas para pr贸ximos eventos</string>
+    <string name="miband_prefs_hr_sleep_detection">Usar sensor de ritmo card铆aco para mellorar a detecci贸n de sono</string>
+    <string name="miband2_prefs_dateformat">Formato de data</string>
+    <string name="dateformat_time">Hora</string>
+    <string name="dateformat_date_time">Hora e data</string>
+    <string name="mi2_prefs_button_actions">Acci贸ns do bot贸n</string>
+    <string name="mi2_prefs_button_actions_summary">Especificar acci贸ns para pulsaci贸n do bot贸n do Mi Band 2</string>
+    <string name="mi2_prefs_button_press_count">Conta de pulsaci贸ns do bot贸n</string>
+    <string name="mi2_prefs_button_press_count_summary">N煤mero de pulsaci贸ns no bot贸n para activar o env铆o da mensaxe</string>
+    <string name="mi2_prefs_button_press_broadcast">Mensaxe a enviar</string>
+    <string name="mi2_prefs_button_action">Activar acci贸n do bot贸n</string>
+    <string name="mi2_prefs_button_action_summary">Activar acci贸n para un n煤mero definido de pulsaci贸ns de bot贸n</string>
+    <string name="mi2_prefs_button_action_vibrate">Activar vibraci贸n da banda</string>
+    <string name="mi2_prefs_button_action_vibrate_summary">Activar vibraci贸n da banda en acci贸n desencadenada por bot贸n</string>
+    <string name="mi2_prefs_button_press_count_max_delay">Retardo m谩ximo entre pulsaci贸ns</string>
+    <string name="mi2_prefs_button_press_count_max_delay_summary">Retardo m谩ximo en milisegundos entre pulsaci贸ns de bot贸n</string>
+    <string name="mi2_prefs_goal_notification">Notificaci贸n de obxectivo</string>
+    <string name="mi2_prefs_goal_notification_summary">A pulseira vai vibrar cando se acade o obxectivo diario de pasos</string>
+    <string name="mi2_prefs_display_items">Elementos a amosar</string>
+    <string name="mi2_prefs_display_items_summary">Escolle os elementos a amosar na pantalla da banda</string>
+    <string name="mi2_prefs_activate_display_on_lift">Activar pantalla ao levantar</string>
+    <string name="mi2_prefs_rotate_wrist_to_switch_info">Rodar o pulso para mudar a informaci贸n</string>
+    <string name="mi2_prefs_do_not_disturb">Non Molestar</string>
+    <string name="mi2_prefs_do_not_disturb_summary">A banda non recibir谩 notificaci贸ns mentres est谩 activa</string>
+    <string name="mi2_prefs_inactivity_warnings">Avisos de inactividade</string>
+    <string name="mi2_prefs_inactivity_warnings_summary">A pulseira vai vibrar se estiveches inactivo durante alg煤n tempo</string>
+    <string name="mi2_prefs_inactivity_warnings_threshold">Limite de inactividade (en minutos)</string>
+    <string name="mi2_prefs_inactivity_warnings_dnd_summary">Desactivar os avisos de inactividade durante un intervalo de tempo</string>
+    <string name="mi2_prefs_do_not_disturb_start">Hora de inicio</string>
+    <string name="mi2_prefs_do_not_disturb_end">Hora de fin</string>
+    <string name="FetchActivityOperation_about_to_transfer_since">A piques de transferir dados desde %1$s</string>
+    <string name="waiting_for_reconnect">Agardando reconexi贸n</string>
+    <string name="activity_prefs_about_you">Sobre ti</string>
+    <string name="activity_prefs_year_birth">Ano de nacemento</string>
+    <string name="activity_prefs_gender">X茅nero</string>
+    <string name="activity_prefs_height_cm">Altura en cm</string>
+    <string name="activity_prefs_weight_kg">Peso en kg</string>
+    <string name="authenticating">Autenticando</string>
+    <string name="authentication_required">Requerida autenticaci贸n</string>
+    <string name="appwidget_text">ZzZ</string>
+    <string name="add_widget">Engadir widget</string>
+    <string name="activity_prefs_sleep_duration">Duraci贸n de sono preferida en horas</string>
+    <string name="appwidget_setting_alarm">Unha alarma foi definida para %1$02d:%2$02d</string>
+    <string name="device_hw">Revisi贸n de hardware: %1$s</string>
+    <string name="device_fw">Versi贸n de firmware: %1$s</string>
+    <string name="error_creating_directory_for_logfiles">Erro creando un cartafol para os arquivos de rexistro: %1$s</string>
+    <string name="DEVINFO_HR_VER">"Versi贸n HW: "</string>
+    <string name="updatefirmwareoperation_update_in_progress">Anovaci贸n de Firmware en progreso</string>
+    <string name="updatefirmwareoperation_firmware_not_sent">Firmware non enviado</string>
+    <string name="charts_legend_heartrate">Ritmo card铆aco</string>
+    <string name="live_activity_heart_rate">Ritmo card铆aco</string>
+    <string name="pref_title_pebble_health_store_raw">Almacenar rexistro en bruto na base de datos</string>
+    <string name="action_db_management">Xesti贸n da base de datos</string>
+    <string name="title_activity_db_management">Xesti贸n da base de datos</string>
+    <string name="activity_db_management_import_export_explanation">A base de datos usa a seguinte ubicaci贸n no seu dispositivo.
+\nEsta ubicaci贸n 茅 acces铆bel para outras aplicaci贸ns Android e para o seu ordenador.
+\nAtopar谩 as s煤as bases de datos exportadas (ou a que quere importar) aqu铆:</string>
+    <string name="activity_db_management_merge_old_title">Borrar a base de datos antiga</string>
+    <string name="dbmanagementactivvity_cannot_access_export_path">Non se pode acceder 谩 ruta para exportar . Por favor, contacta cos desenvolvedores.</string>
+    <string name="dbmanagementactivity_exported_to">Exportado a: %1$s</string>
+    <string name="dbmanagementactivity_error_exporting_db">Erro ao exportar a base de datos: %1$s</string>
+    <string name="dbmanagementactivity_error_exporting_shared">Erro exportando as preferencias: %1$s</string>
+    <string name="dbmanagementactivity_import_data_title">Importar datos?</string>
+    <string name="dbmanagementactivity_overwrite_database_confirmation">Quere sobreescribir a base de datos actual? Perderanse todos os datos actuais de actividade (de habelos).</string>
+    <string name="dbmanagementactivity_import_successful">Importado.</string>
+    <string name="dbmanagementactivity_error_importing_db">Erro ao importar a base de datos: %1$s</string>
+    <string name="dbmanagementactivity_error_importing_shared">Erro importando preferencia: %1$s</string>
+    <string name="dbmanagementactivity_delete_activity_data_title">Borrar datos de actividade?</string>
+    <string name="dbmanagementactivity_really_delete_entire_db">Realmente quere borrar toda a base de datos? Perderanse todos os seus datos de actividade e a informaci贸n sobre os seus dispositivos.</string>
+    <string name="dbmanagementactivity_database_successfully_deleted">Datos eliminados.</string>
+    <string name="dbmanagementactivity_db_deletion_failed">Fallou o borrado da base de datos.</string>
+    <string name="dbmanagementactivity_delete_old_activity_db">Borrar a antiga base de datos de actividade?</string>
+    <string name="dbmanagementactivity_delete_old_activitydb_confirmation">Est谩 seguro de querer borrar a antiga base de datos de actividade? Perderanse os datos de actividade que non foran importados.</string>
+    <string name="dbmanagementactivity_old_activity_db_successfully_deleted">Datos antigos de actividade borrados.</string>
+    <string name="dbmanagementactivity_old_activity_db_deletion_failed">Fallou o borrado da base de datos de actividades antigas.</string>
+    <string name="dbmanagementactivity_overwrite">Sobreescribir</string>
+    <string name="Cancel">Cancelar</string>
+    <string name="Delete">Borrar</string>
+    <string name="title_activity_vibration">Vibraci贸n</string>
+    <string name="title_activity_pebble_pairing">Emparellando co Pebble</string>
+    <string name="pref_title_setup_bt_pairing">Activar o emparellamento Bluetooth</string>
+    <string name="pref_summary_setup_bt_pairing">Desactiva isto se tes problemas de conexi贸n</string>
+    <string name="unit_metric">M茅trico</string>
+    <string name="unit_imperial">Imperial</string>
+    <string name="timeformat_24h">24H</string>
+    <string name="timeformat_am_pm">AM/PM</string>
+    <string name="pref_screen_notification_profile_alarm_clock">Espertador</string>
+    <string name="StringUtils_sender">(%1$s)</string>
+    <string name="find_device_you_found_it">Atopado!</string>
+    <string name="miband2_prefs_timeformat">Mi2: formato de hora</string>
+    <string name="mi2_fw_installhandler_fw53_hint">Debe instalar a versi贸n %1$s antes de instalar este firmware!</string>
+    <string name="mi2_enable_text_notifications">Notificaci贸ns de texto</string>
+    <string name="mi2_enable_text_notifications_summary">Require firmware &gt;= 1.0.1.28 e Mili_pro.ft* instalado.</string>
+    <string name="off">Apagado</string>
+    <string name="mi2_dnd_off">Apagado</string>
+    <string name="mi2_dnd_automatic">Autom谩tico (detecci贸n de sono)</string>
+    <string name="mi2_dnd_scheduled">Axendado (intervalo de tempo)</string>
+    <string name="discovery_attempting_to_pair">Intentando emparellar con %1$s</string>
+    <string name="discovery_bonding_failed_immediately">O enlace con %1$s fallou de inmediato.</string>
+    <string name="discovery_trying_to_connect_to">Intentando conectar con: %1$s</string>
+    <string name="discovery_enable_bluetooth">Active o Bluetooth para achar dispositivos.</string>
+    <string name="discovery_successfully_bonded">Enlazado con %1$s.</string>
+    <string name="discovery_pair_title">Emparellar con %1$s?</string>
+    <string name="discovery_yes_pair">Emparellar</string>
+    <string name="discovery_dont_pair">Non emparellar</string>
+    <string name="_pebble_watch_open_on_phone">Abrir no tel茅fono</string>
+    <string name="_pebble_watch_mute">Silenciar</string>
+    <string name="_pebble_watch_reply">Responder</string>
+    <string name="fw_upgrade_notice_amazfitbip">Est谩s a piques de instalar firmware %s no teu Amazfit Bip.
+\n 
+\nPor favor, aseg煤rate de instalar o ficheiro .fw, despois o arquivo .res, e finalmente o ficheiro .gps . O teu reloxo reiniciar谩 despois de instalar o arquivo .fw. 
+\n 
+\nNota: non tes que instalar .res e .gps se estes arquivos son os mesmos que os previamente instalados. 
+\n 
+\nPROCEDE BAIXO A T脷A PROPIA RESPONSABILIDADE!</string>
+    <string name="pref_summary_pebble_forcele">Use soporte experimental do Pebble LE para todos os Pebble nas veces do Bluetooth cl谩sico. Isto require vincular primeiro con Pebble non LE, e despois Pebble LE</string>
+    <string name="pref_summary_pebble_always_ack_pebblekit">Isto far谩 que as mensaxes enviadas para apps de terceiros sexan reco帽ecidas sempre e inmediatamente</string>
+    <string name="miband_prefs_device_time_offset_hours">Compensaci贸n da hora do dispositivo en horas (para detectar o sono de traballadores a turnos)</string>
+    <string name="mi2_prefs_button_press_broadcast_summary">Enviar mensaxe despois dun n煤mero definido de pulsaci贸ns</string>
+    <string name="pref_summary_pebble_health_store_raw">Se marcado, os datos son almacenados no seu formato orixinal ficando dispon铆beis para posterior interpretaci贸n. Nota: A base de datos ser谩 m谩is grande!</string>
+    <string name="pebble_pairing_hint">Ver谩 unha notificaci贸n de emparellamento no seu dispositivo Android. Se non, acceda 谩s notificaci贸ns e acepte a solicitude de emparellamento. posteriormente a solicitude de emparellamento no seu Pebble.</string>
+    <string name="weather_notification_label">Aseg煤rate de que este tema est茅 activado na aplicaci贸n de notificaci贸n do clima para obter a informaci贸n no teu Pebble.
+\n
+\nNon se require configuraci贸n.
+\n
+\nPodes activar a aplicaci贸n meteorol贸xica do sistema desde a configuraci贸n da app.
+\n
+\nAs \"watchfaces\" soportadas amosar谩n a informaci贸n do clima autom谩ticamente.</string>
+    <string name="discovery_pair_question">Selecciona Emparellar para emparellar os teus dispositivos. Se isto falla, proba de novo sen emparellar.</string>
+    <string name="controlcenter_connect">Conectar鈥�</string>
+    <string name="fw_upgrade_notice_amazfitcor">Est谩s a piques de instalar firmware %s no teu Amazfit Cor.
+\n
+\nPor favor, aseg煤rate de instalar o ficheiro .fw, e despois diso o ficheiro .res. O teu reloxo reiniciar谩 despois de instalar o arquivo .fw.
+\n
+\nNota: non tes que instalar .res se este arquivo 茅 exactamente o mesmo que o previamente instalado.
+\n
+\nNON PROBADO, PODER脥A BLOQUEAR O TEU DISPOSITIVO, PROCEDE BAIXO A T脷A PROPIA RESPONSABILIDADE!</string>
+    <string name="pref_title_pebble_enable_bgjs">Habilitar JS de fondo</string>
+    <string name="pref_summary_pebble_enable_bgjs">Permite 谩s watchfaces amosar o tempo, informaci贸n de bater铆a, etc.</string>
+    <string name="activity_web_view">Actividade Vista Web</string>
+    <string name="pref_title_charts_swipe">Permitir deslizar a esquerda/dereita nas gr谩ficas de actividade</string>
+    <string name="automatic">Autom谩tico</string>
+    <string name="simplified_chinese">Chin茅s simplificado</string>
+    <string name="traditional_chinese">Chin茅s tradicional</string>
+    <string name="english">Ingl茅s</string>
+    <string name="pref_title_weather_location">Localizaci贸n para M茅teorolox铆a (CM/LOS)</string>
+    <string name="prefs_title_heartrate_measurement_interval">Medici贸n de pulso todo o d铆a</string>
+    <string name="interval_one_minute">cada minuto</string>
+    <string name="interval_five_minutes">cada 5 minutos</string>
+    <string name="interval_ten_minutes">cada 10 minutos</string>
+    <string name="interval_thirty_minutes">cada 30 minutos</string>
+    <string name="interval_one_hour">cada hora</string>
+    <string name="kind_firmware">Firmware</string>
+    <string name="kind_invalid">Datos inv谩lidos</string>
+    <string name="kind_font">Fonte</string>
+    <string name="kind_gps">Firmware do GPS</string>
+    <string name="kind_gps_almanac">Almanaque GPS</string>
+    <string name="kind_gps_cep">Correcci贸n de Erros do GPS</string>
+    <string name="kind_resources">Recursos</string>
+    <string name="devicetype_unknown">Dispositivo desco帽ecido</string>
+    <string name="devicetype_test">Dispositivo de probas</string>
+    <string name="devicetype_pebble">Pebble</string>
+    <string name="devicetype_miband">Mi Band</string>
+    <string name="devicetype_miband2">Mi Band 2</string>
+    <string name="devicetype_amazfit_bip">Amazfit Bip</string>
+    <string name="devicetype_amazfit_cor">Amazfit Cor</string>
+    <string name="devicetype_teclast_h30">Teclast H30</string>
+    <string name="pref_title_weather">Meteorolox铆a</string>
+    <string name="pref_header_auto_export">Autoexportaci贸n</string>
+    <string name="pref_title_auto_export_enabled">Autoexportaci贸n habilitada</string>
+    <string name="pref_title_auto_export_location">Exportar localizaci贸n</string>
+    <string name="pref_title_auto_export_interval">Exportar intervalo</string>
+    <string name="pref_summary_auto_export_interval">Exportar cada %d horas</string>
+    <string name="notif_export_failed_title">Fallou a exportaci贸n da base de datos! Por favor, comproba os teus axustes.</string>
+    <string name="kind_watchface">Watchface</string>
+    <string name="devicetype_vibratissimo">Vibratissimo</string>
+    <string name="devicetype_liveview">Vista en Vivo</string>
+    <string name="devicetype_hplus">HPlus</string>
+    <string name="devicetype_makibes_f68">Makibes F68</string>
+    <string name="devicetype_exrizu_k8">Exrizu K8</string>
+    <string name="devicetype_no1_f1">No.1 F1</string>
+    <string name="choose_auto_export_location">Escolle lugar para exportar</string>
+    <string name="spanish">Castel谩n</string>
+    <string name="devicetype_xwatch">XWatch</string>
+    <string name="notification_channel_name">Notificaci贸ns do Gadgetbridge</string>
+    <string name="on">Aceso</string>
+    <string name="controlcenter_start_activity_tracks">Os teus rexistros de actividade</string>
+    <string name="activity_type_not_measured">Sen medir</string>
+    <string name="activity_type_activity">Actividade</string>
+    <string name="activity_type_light_sleep">Sono leve</string>
+    <string name="activity_type_deep_sleep">Sono profundo</string>
+    <string name="activity_type_not_worn">Dispositivo non usado</string>
+    <string name="activity_type_running">Carreira</string>
+    <string name="activity_type_walking">Paseo</string>
+    <string name="activity_type_swimming">Nataci贸n</string>
+    <string name="activity_type_unknown">Actividade desco帽ecida</string>
+    <string name="activity_summaries">Actividades</string>
+    <string name="activity_type_biking">Ciclismo</string>
+    <string name="activity_type_treadmill">Fita de correr</string>
+    <string name="select_all">Seleccionar todas</string>
+    <string name="share">Compartir</string>
+    <string name="reset_index">Restablecer a data de obtenci贸n</string>
+    <string name="menuitem_status">Estado</string>
+    <string name="menuitem_activity">Actividade</string>
+    <string name="menuitem_weather">Meteorolox铆a</string>
+    <string name="menuitem_alarm">Alarma</string>
+    <string name="menuitem_timer">Cron贸metro</string>
+    <string name="menuitem_compass">Comp谩s</string>
+    <string name="menuitem_settings">Axustes</string>
+    <string name="menuitem_alipay">Alipay</string>
+    <string name="mi2_prefs_button_press_broadcast_default_value" translatable="false">nodomain.freeyourgadget.gadgetbridge.ButtonPressed</string>
+    <string name="sonyswr12_settings_title">Axustes Sony SWR12</string>
+    <string name="sonyswr12_settings_low_vibration">Vibraci贸n baixa habilitada</string>
+    <string name="sonyswr12_settings_alarm_interval">Intervalo de alarma intelixente en minutos</string>
+    <string name="sonyswr12_settings_low_vibration_summary">Activa a baixa intensidade de vibraci贸n na banda</string>
+    <string name="sonyswr12_settings_stamina_summary">O modo de aforro de enerx铆a desactiva a medici贸n autom谩tica peri贸dica da frecuencia card铆aca, polo que aumenta o tempo de traballo</string>
+    <string name="sonyswr12_settings_alarm_interval_summary">O intervalo de alarma intelixente 茅 o intervalo antes da alarma instalada. Neste intervalo o dispositivo est谩 intentando detectar a fase m谩is lixeira do sono para espertar ao usuario</string>
+    <string name="sonyswr12_settings_stamina">Modo de aforro de enerx铆a activado</string>
+    <string name="calendar_two_weeks">D煤as semanas</string>
+    <string name="calendar_week">Semana</string>
+    <string name="calendar_day">D铆a</string>
+    <string name="battery_level">Nivel da bater铆a</string>
+    <string name="battery_detail_activity_title">Info da bater铆a</string>
+    <string name="controlcenter_calibrate_device">Calibrar dispositivo</string>
+    <string name="controlcenter_set_alias">Establecer Alias</string>
+    <string name="controlcenter_change_fm_frequency">Cambiar frecuencia FM</string>
+    <string name="controlcenter_change_led_color">Cambiar cor LED</string>
+    <string name="find_lost_device_message">Buscar a %1$s\?</string>
+</resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-he/strings.xml b/app/src/main/res/values-he/strings.xml
new file mode 100644
index 0000000..fe28696
--- /dev/null
+++ b/app/src/main/res/values-he/strings.xml
@@ -0,0 +1,3093 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources xmlns:tools="http://schemas.android.com/tools">
+    <string name="application_name_generic">Gadgetbridge</string>
+    <string name="title_activity_controlcenter_generic">Gadgetbridge</string>
+    <string name="action_settings">讛讙讚专讜转</string>
+    <string name="action_debug">谞讬驻讜讬 砖讙讬讗讜转</string>
+    <string name="action_quit">讬爪讬讗讛</string>
+    <string name="controlcenter_fetch_activity_data">住谞讻专讜谉</string>
+    <string name="controlcenter_find_device">讗讬转讜专 讛转拽谉 砖讗讘讚</string>
+    <string name="controlcenter_take_screenshot">爪讬诇讜诐 讛诪住讱</string>
+    <string name="controlcenter_disconnect">谞讬转讜拽</string>
+    <string name="controlcenter_delete_device">诪讞讬拽转 讛转拽谉</string>
+    <string name="controlcenter_delete_device_name">诪讞讬拽转 %1$s</string>
+    <string name="controlcenter_delete_device_dialogmessage">驻注讜诇讛 讝讜 转诪讞拽 讗转 讛讛转拽谉 讜讗转 讻诇 讛诪讬讚注 讛诪砖讜讬讱 讗诇讬讜!</string>
+    <string name="title_activity_debug">谞讬驻讜讬 砖讙讬讗讜转</string>
+    <!--Strings related to AppManager-->
+    <string name="title_activity_appmanager">诪谞讛诇 讬讬砖讜诪讜谞讬诐</string>
+    <string name="appmanager_cached_watchapps_watchfaces">讬讬砖讜诪讜谞讬诐 讘诪讟诪讜谉</string>
+    <string name="appmanager_installed_watchapps">讬讬砖讜诪讜谞讬诐 诪讜转拽谞讬诐</string>
+    <string name="appmanager_installed_watchfaces">诪住讬讻讜转 砖注讜谉 诪讜转拽谞讜转</string>
+    <string name="appmananger_app_delete">诪讞讬拽讛</string>
+    <string name="appmananger_app_delete_cache">诪讞讬拽讛 讜讛住专讛 诪讛诪讟诪讜谉</string>
+    <string name="appmananger_app_reinstall">讛转拽谞讛 诪讞讚砖</string>
+    <string name="appmanager_app_openinstore">讞讬驻讜砖 讘讞谞讜转 讛讬讬砖讜诪讜谞讬诐 砖诇 Pebble</string>
+    <string name="appmanager_health_activate">讛驻注诇讛</string>
+    <string name="appmanager_health_deactivate">讻讬讘讜讬</string>
+    <string name="appmanager_hrm_activate">讛驻注诇转 诪注拽讘 讚讜驻拽</string>
+    <string name="appmanager_hrm_deactivate">讛砖讘转转 诪注拽讘 讚讜驻拽</string>
+    <string name="appmanager_weather_activate">讛驻注诇转 讬讬砖讜诪讜谉 诪讝讙 讛讗讜讜讬专 砖诇 讛诪注专讻转</string>
+    <string name="appmanager_weather_deactivate">讛砖讘转转 讬讬砖讜诪讜谉 诪讝讙 讛讗讜讜讬专 砖诇 讛诪注专讻转</string>
+    <string name="appmanager_weather_install_provider">讛转拽谞转 讬讬砖讜诪讜谉 讚讬讜讜讞 诪讝讙 讛讗讜讜讬专</string>
+    <string name="app_configure">讛讙讚专讛</string>
+    <string name="app_move_to_top">讛注讘专讛 诇诪注诇讛</string>
+    <!--Strings related to CalBlacklist-->
+    <!--Strings related to FwAppInstaller-->
+    <string name="title_activity_fw_app_insaller">讛转拽谞转 拽讜砖讞讛/讬讬砖讜诪讜谉</string>
+    <string name="fw_upgrade_notice">驻注讜诇讛 讝讜 转讜讘讬诇 诇讛转拽谞转 %s.</string>
+    <string name="fw_multi_upgrade_notice">讛拽讜砖讞讜转 讛诪讜注诪讚讜转 诇讛转拽谞讛 讛谞谉 %1$s 讜志%2$s 讘诪拽讜诐 讗诇讜 讛诪讜转拽谞转 注诇 讛志Mi Band 砖诇讱 谞讻讜谉 诇注讻砖讬讜.</string>
+    <string name="miband_firmware_known">拽讜砖讞讛 讝讜 谞讘讚拽讛 讜讬讚讜注 讻讬 讛讬讗 谞转诪讻转 讘志Gadgetbridge.</string>
+    <string name="miband_firmware_unknown_warning">拽讜砖讞讛 讝讜 诇讗 谞讘讚拽讛 讜讬转讻谉 讻讬 讗讬谞讛 谞转诪讻转 讘志Gadgetbridge.
+\n
+\n诇讗 诪讜诪诇抓 诇讛转拽讬谉 讗讜转讛!</string>
+    <string name="miband_firmware_suggest_whitelist">讗诐 讘讻诇 讝讗转 讘讞专转 诇讛诪砖讬讱 讜讛讚讘专讬诐 诪诪砖讬讻讬诐 诇注讘讜讚 讻专讙讬诇, 谞讗 诇住驻专 诇诪驻转讞讬诐 砖诇 Gadgetbridge 诇讛讜住讬祝 诇专砖讬诪转 讛讛讬转专 讗转 讙专住转 讛拽讜砖讞讛 %s.</string>
+    <!--Strings related to Settings-->
+    <string name="title_activity_settings">讛讙讚专讜转</string>
+    <string name="pref_header_general">讛讙讚专讜转 讻诇诇讬讜转</string>
+    <string name="pref_title_general_autoconnectonbluetooth">讛转讞讘专讜转 诇讛转拽谞讬 Gadgetbridge 讻讗砖专 讛志Bluetooth 驻讜注诇</string>
+    <string name="pref_title_general_autostartonboot">讛转讞诇讛 讗讜讟讜诪讟讬转</string>
+    <string name="pref_title_general_autoreconnect">讛转讞讘专讜转 讗讜讟讜诪讟讬转 诪讞讚砖</string>
+    <string name="pref_title_audio_player">谞讙谉 诪讚讬讛 诪讜注讚祝</string>
+    <string name="pref_default">讘专专转 诪讞讚诇</string>
+    <string name="pref_header_datetime">砖注讛 讜转讗专讬讱</string>
+    <string name="pref_title_datetime_syctimeonconnect">住谞讻专讜谉 讛砖注讛</string>
+    <string name="pref_summary_datetime_syctimeonconnect">住谞讻专讜谉 讛砖注讛 讘讛转拽谞讬 Gadgetbridge 讘注转 讛讛转讞讘专讜转 讜讻讗砖专 讛砖注讛 讗讜 讗讝讜专 讛讝诪谉 诪砖转谞讬诐 讘讛转拽谉 讛志Android 讜诪讚讬 驻注诐 讘驻注诐</string>
+    <string name="pref_title_theme">注专讻转 注讬爪讜讘</string>
+    <string name="pref_theme_light">讘讛讬专讛</string>
+    <string name="pref_theme_dark">讻讛讛</string>
+    <string name="pref_title_language">砖驻讛</string>
+    <string name="pref_title_minimize_priority">讛住转专转 讛讛讜讚注讜转 砖诇 Gadgetbridge</string>
+    <string name="pref_summary_minimize_priority_off">讛住诪诇 讘砖讜专转 讛诪爪讘 讜讛讚讬讜讜讞 讘诪住讱 讛谞注讬诇讛 诪讜驻讬注讬诐</string>
+    <string name="pref_summary_minimize_priority_on">讛住诪诇 讘砖讜专转 讛诪爪讘 讜讛讚讬讜讜讞 讘诪住讱 讛谞注讬诇讛 诪讜住转专讬诐</string>
+    <string name="pref_header_notifications">讛讜讚注讜转</string>
+    <string name="pref_title_notifications_repetitions">讞讝专讜转</string>
+    <string name="pref_title_notifications_call">砖讬讞讜转 讟诇驻讜谉</string>
+    <string name="pref_title_notifications_sms">诪住专讜谞讬诐</string>
+    <string name="pref_title_notifications_pebblemsg">诪住专讬 Pebble</string>
+    <string name="pref_summary_notifications_pebblemsg">转诪讬讻讛 讘讬讬砖讜诪讜谞讬诐 砖砖讜诇讞讬诐 讛讜讚注讜转 诇志Pebble 讘讗诪爪注讜转 PebbleKit.</string>
+    <string name="pref_title_notifications_generic">转诪讬讻讛 讘讛讜讚注讜转 讙谞专讬讜转</string>
+    <string name="pref_title_whenscreenon">鈥ψ捵� 讻讗砖专 讛诪住讱 驻注讬诇</string>
+    <string name="pref_title_notification_filter">诇讗 诇讛驻专讬注</string>
+    <string name="pref_summary_notification_filter">诇讞住讜诐 讗转 讻诇 讛讛转专讗讜转 讻讗砖专 诪爪讘 诇讗 诇讛驻专讬注 诪讜驻注诇 讘讟诇驻讜谉</string>
+    <string name="pref_title_transliteration">转注转讬拽</string>
+    <string name="always">转诪讬讚</string>
+    <string name="when_screen_off">讻讗砖专 讛诪住讱 讻讘讜讬</string>
+    <string name="never">诇注讜诇诐 诇讗</string>
+    <string name="pref_header_privacy">驻专讟讬讜转</string>
+    <string name="pref_title_call_privacy_mode">诪爪讘 驻专讟讬讜转 砖讬讞讛</string>
+    <string name="pref_call_privacy_mode_off">诇讛爪讬讙 砖诐 讜诪住驻专</string>
+    <string name="pref_call_privacy_mode_name">诇讛住转讬专 讗转 讛砖诐 讗讱 诇讛爪讬讙 讗转 讛诪住驻专</string>
+    <string name="pref_call_privacy_mode_complete">诇讛住转讬专 讗转 讛砖诐 讜讛诪住驻专</string>
+    <string name="pref_header_cannned_messages">讛讜讚注讜转 诪讜讙讚专讜转</string>
+    <string name="pref_title_canned_replies">转讙讜讘讜转</string>
+    <string name="pref_title_canned_reply_suffix">住讬讜诪转 诪砖讜转驻转</string>
+    <string name="pref_title_canned_messages_dismisscall">讛转注诇诪讜转 诪砖讬讞讜转</string>
+    <string name="pref_title_canned_messages_set">注讚讻讜谉 讘诪讻砖讬专</string>
+    <string name="pref_header_development">讗驻砖专讜讬讜转 驻讬转讜讞</string>
+    <string name="pref_title_development_miaddr">讻转讜讘转 Mi Band</string>
+    <string name="pref_title_pebble_settings">讛讙讚专讜转 Pebble</string>
+    <string name="pref_header_activitytrackers">注讜拽讘讬 驻注讬诇讜转</string>
+    <string name="pref_title_pebble_activitytracker">注讜拽讘 驻注讬诇讜转 诪讜注讚祝</string>
+    <string name="pref_title_pebble_sync_health">住谞讻专讜谉 Pebble Health</string>
+    <string name="pref_title_pebble_sync_misfit">住谞讻专讜谉 Misfit</string>
+    <string name="pref_title_pebble_sync_morpheuz">住谞讻专讜谉 Morpheuz</string>
+    <string name="pref_title_enable_outgoing_call">转诪讬讻讛 讘砖讬讞讜转 讬讜爪讗讜转</string>
+    <string name="pref_summary_enable_outgoing_call">讛砖讘转转 讗驻砖专讜转 讝讜 讙诐 转砖讘讬转 讗转 讛专讟讟 诇砖讬讞讜转 讬讜爪讗讜转 讘志Pebble 2/LE</string>
+    <string name="pref_title_enable_pebblekit">诇讗驻砖专 讙讬砖讛 诇讬讬砖讜诪讜谞讬 爪讚 砖诇讬砖讬 讘讗谞讚专讜讗讬讚</string>
+    <string name="pref_summary_enable_pebblekit">讛驻注诇转 转诪讬讻讛 谞讬住讬讜谞讬转 讘讬讬砖讜诪讜谞讬 Android 讘讗诪爪注讜转 PebbleKit</string>
+    <string name="pref_title_sunrise_sunset">讝专讬讞讛 讜砖拽讬注讛</string>
+    <string name="pref_summary_sunrise_sunset">砖诇讬讞转 诪讜注讚讬 讝专讬讞讛 讜砖拽讬注讛 讘讛转讘住住 注诇 讛诪讬拽讜诐 诇爪讬专 讛讝诪谉 砖诇 Pebble</string>
+    <string name="pref_title_autoremove_notifications">讛住专讛 讗讜讟讜诪讟讬转 砖诇 讚讬讜讜讞讬诐 砖讛转注诇诪转 诪讛诐</string>
+    <string name="pref_summary_autoremove_notifications">讚讬讜讜讞讬诐 诪讜住专讬诐 讗讜讟讜诪讟讬转 诪讛诪讻砖讬专 讻砖讛转注诇诪转 诪讛诐 讘讟诇驻讜谉</string>
+    <string name="pref_title_pebble_privacy_mode">诪爪讘 驻专讟讬讜转</string>
+    <string name="pref_pebble_privacy_mode_off">讚讬讜讜讞讬诐 专讙讬诇讬诐</string>
+    <string name="pref_pebble_privacy_mode_content">讛讞诇拽转 讟拽住讟 讛讚讬讜讜讞 讗诇 诪讞讜抓 诇诪住讱</string>
+    <string name="pref_pebble_privacy_mode_complete">讛爪讙转 住诪诇 讛讚讬讜讜讞 讘诇讘讚</string>
+    <string name="pref_header_location">诪讬拽讜诐</string>
+    <string name="pref_title_location_aquire">拽讘诇转 讛诪讬拽讜诐</string>
+    <string name="pref_title_location_latitude">专讜讞讘</string>
+    <string name="pref_title_location_longitude">讗讜专讱</string>
+    <string name="pref_title_location_keep_uptodate">诇讛砖讗讬专 讗转 讛诪讬拽讜诐 诪注讜讚讻谉</string>
+    <string name="pref_summary_location_keep_uptodate">诇谞住讜转 诇拽讘诇 讗转 讛诪讬拽讜诐 讛谞讜讻讞讬 讘讝诪谉 讛讛驻注诇讛, 讬砖 诇讛砖转诪砖 讘诪讬拽讜诐 砖谞砖诪专 专拽 讻讙讬讘讜讬</string>
+    <string name="toast_enable_networklocationprovider">谞讗 诇讛驻注讬诇 讗转 讛讞讬讘讜专 诇专砖转</string>
+    <string name="toast_aqurired_networklocation">讛诪讬拽讜诐 讛转拽讘诇</string>
+    <string name="pref_title_pebble_forceprotocol">讗讬诇讜抓 驻专讜讟讜拽讜诇 讚讬讜讜讞</string>
+    <string name="pref_summary_pebble_forceprotocol">讗驻砖专讜转 讝讜 诪讗诇爪转 讗转 讛砖讬诪讜砖 讘驻专讜讟讜拽讜诇 讛讚讬讜讜讞 讛注讚讻谞讬 讘讬讜转专 讘讛转讗诐 诇讙专住转 讛讞讜诪专讛. 砖讛讛砖诇讻讜转 转讛讬讬谞讛 讘专讜专讜转 诇讱 诇讞诇讜讟讬谉!</string>
+    <string name="pref_title_pebble_forceuntested">讛驻注诇转 转讻讜谞讜转 砖诇讗 谞讘讚拽讜</string>
+    <string name="pref_summary_pebble_forceuntested">讛驻注诇转 转讻讜谞讜转 砖诇讗 谞讘讚拽讜. 砖讛讛砖诇讻讜转 转讛讬讬谞讛 讘专讜专讜转 诇讱 诇讞诇讜讟讬谉!</string>
+    <string name="pref_title_pebble_forcele">转诪讬讚 诇讛注讚讬祝 BLE</string>
+    <string name="pref_summary_pebble_forcele">砖讬诪讜砖 讘转诪讬讻讛 谞讬住讬讜谞讬转 讘志Pebble LE, 注讘讜专 讻诇 住讜讙讬 讛志Pebble 讘诪拽讜诐 讛志Bluetooth 讛拽诇住讬. 谞讚专砖 爪讬诪讜讚 砖讗讬谞讜 LE 转讞讬诇讛 讜诇讗讞专 诪讻谉 爪讬诪讜讚 诪住讜讙 鈥濸ebble LE鈥�</string>
+    <string name="pref_title_pebble_mtu_limit">诪讙讘诇转 讛志MTU 砖诇 GATT 诇志Pebble 2/LE</string>
+    <string name="pref_summary_pebble_mtu_limit">讗诐 讛志Pebble 2/Pebble LE 砖诇讱 讗讬谞讜 注讜讘讚 讻爪驻讜讬, 讬砖 诇谞住讜转 讗驻砖专讜转 讝讗转 讻讚讬 诇讛讙讘讬诇 讗转 讻诪讜转 讛谞转讜谞讬诐 讛诪讜注讘专转 - MTU (讟讜讜讞 转拽谞讬 20鈥�512)</string>
+    <string name="pref_title_pebble_enable_applogs">讛驻注诇转 诪注拽讘 专讬砖讜诐 讗讞专 讬讬砖讜诪讜谉 讛砖注讜谉</string>
+    <string name="pref_summary_pebble_enable_applogs">讬讙专讜诐 诇专讬砖讜诪讬诐 诪讬讬砖讜诪讬 讛砖注讜谉 诇讛讬讜转 诪转讜注讚讬诐 讘志Gadgetbridge (谞讚专砖转 讛驻注诇讛 诪讞讚砖)</string>
+    <string name="pref_title_pebble_always_ack_pebblekit">讗讬砖讜专 诪住讬专讛 诪讜拽讚诐 诪爪讚 PebbleKit</string>
+    <string name="pref_summary_pebble_always_ack_pebblekit">讬讙专讜诐 诇讛讜讚注讜转 砖谞砖诇讞讜转 诇讬讬砖讜诪讜谞讬 爪讚 砖诇讬砖讬 诇拽讘诇 讗讬砖讜专 诪住讬专讛 转诪讬讚 讜讘讗讜驻谉 诪讬讬讚讬</string>
+    <string name="pref_title_pebble_reconnect_attempts">谞讬住讬讜谞讜转 讞讬讘讜专 诪讞讚砖</string>
+    <string name="pref_title_unit_system">讬讞讬讚讜转</string>
+    <string name="pref_title_timeformat">转讘谞讬转 讛讝诪谉</string>
+    <string name="pref_title_screentime">诪砖讱 诪住讱 驻注讬诇</string>
+    <string name="prefs_title_all_day_heart_rate">诪讚讬讚转 讚讜驻拽 讘诪砖讱 讻诇 讛讬讜诐</string>
+    <string name="preferences_hplus_settings">讛讙讚专讜转 HPlus/Makibes</string>
+    <string name="not_connected">诇讗 诪讞讜讘专</string>
+    <string name="connecting">讘讛转讞讘专讜转</string>
+    <string name="connected">诪讞讜讘专</string>
+    <string name="unknown_state">诪爪讘 诇讗 讬讚讜注</string>
+    <string name="_unknown_">(诇讗 讬讚讜注)</string>
+    <string name="test">讘讚讬拽讛</string>
+    <string name="test_notification">讛转专讗讛 诇讘讚讬拽讛</string>
+    <string name="this_is_a_test_notification_from_gadgetbridge">讝讜讛讬 讛转专讗讛 诇讘讚讬拽讛 诪志Gadgetbridge</string>
+    <string name="bluetooth_is_not_supported_">讗讬谉 转诪讬讻讛 讘志Bluetooth.</string>
+    <string name="bluetooth_is_disabled_">讛志Bluetooth 诪讜砖讘转.</string>
+    <string name="tap_connected_device_for_app_mananger">谞拽讬砖讛 注诇 讛讛转拽谉 讛诪讞讜讘专 诇诪谞讛诇 讛讬讬砖讜诪讬诐</string>
+    <string name="tap_connected_device_for_activity">谞拽讬砖讛 注诇 讛讛转拽谉 讛诪讞讜讘专 诇驻注讬诇讜转</string>
+    <string name="tap_connected_device_for_vibration">谞拽讬砖讛 注诇 讛讛转拽谉 讛诪讞讜讘专 诇专讟讟</string>
+    <string name="tap_a_device_to_connect">讬砖 诇讙注转 讘讛转拽谉 讻讚讬 诇讛转讞讘专</string>
+    <string name="cannot_connect_bt_address_invalid_">诇讗 谞讬转谉 诇讛转讞讘专. 讻转讜讘转 讛志Bluetooth 砖讙讜讬讛?</string>
+    <string name="gadgetbridge_running_generic">Gadgetbridge 驻注讬诇</string>
+    <string name="installing_binary_d_d">讛转拽谞转 讛讘讬谞专讬 %1$d/%2$d</string>
+    <string name="installation_failed_">讛讛转拽谞讛 谞讻砖诇讛</string>
+    <string name="installation_successful">讛讛转拽谞讛 讛爪诇讬讞讛</string>
+    <string name="firmware_install_warning">讝讛讜 谞讬住讬讜谉 诇讛转拽谞转 拽讜砖讞讛, 谞讬转谉 诇讛诪砖讬讱 讗诐 讘专讜专讜转 诇讱 讛讛砖诇讻讜转.\n\n\n 拽讜砖讞讛 讝讜 诪讬讜注讚转 诇讞讜诪专讛 讘诪讛讚讜专讛: %s</string>
+    <string name="app_install_info">讛讬讬砖讜诪讜谉 讛诪讜注诪讚 诇讛转拽谞讛 讛讜讗:
+\n
+\n%1$s
+\n讙专住讛 %2$s 诪讗转 %3$s
+\n</string>
+    <string name="n_a">诇讗 讝诪讬谉</string>
+    <string name="initialized">诪讗讜转讞诇</string>
+    <string name="appversion_by_creator">%1$s 诪讗转 %2$s</string>
+    <string name="title_activity_discovery">讙讬诇讜讬 讛转拽谞讬诐</string>
+    <string name="discovery_stop_scanning">诇讛驻住讬拽 讗转 讛住专讬拽讛</string>
+    <string name="discovery_start_scanning">诇讛转讞讬诇 讘讙讬诇讜讬</string>
+    <string name="action_discover">讞讬讘讜专 讛转拽谉 讞讚砖</string>
+    <string name="device_with_rssi">%1$s鈥� (%2$s)</string>
+    <string name="title_activity_android_pairing">爪讬诪讜讚 讛转拽谉</string>
+    <string name="android_pairing_hint">讬砖 诇讛砖转诪砖 讘讚讜志砖讬讞 爪讬诪讜讚 讛志Bluetooth 诇讗谞讚专讜讗讬讚 讻讚讬 诇讬爪讜专 爪讬诪讜讚 诇讛转拽谉.</string>
+    <string name="title_activity_mi_band_pairing">爪讬诪讜讚 讛志Mi Band 砖诇讱</string>
+    <string name="pairing">诪转讘爪注 爪讬诪讜讚 注诐 %s鈥�</string>
+    <string name="pairing_creating_bond_with">谞讜爪专 讗讬讙讜讚 注诐 %1$s鈥� (%2$s)</string>
+    <string name="pairing_unable_to_pair_with">诇讗 谞讬转谉 诇讬爪讜专 爪讬诪讜讚 注诐 %1$s 鈥�(%2$s)</string>
+    <string name="pairing_in_progress">诪转讘爪注 讗讬讙讜讚: %1$s鈥� (%2$s)</string>
+    <string name="pairing_already_bonded">讻讘专 谞讜爪专 讗讬讙讜讚 注诐 %1$s (%2$s), 诪转讘爪注转 讛转讞讘专讜转鈥�</string>
+    <string name="message_cannot_pair_no_mac">诇讗 讛讜注讘专讛 讻转讜讘转 讞讜诪专讛, 诇讗 谞讬转谉 诇爪诪讚.</string>
+    <string name="preferences_category_device_specific_settings">讛讙讚专讜转 讬讬注讜讚讬讜转 诇讛转拽谉</string>
+    <string name="preferences_miband_settings">讛讙讚专讜转 Mi Band / Amazfit</string>
+    <string name="male">讝讻专</string>
+    <string name="female">谞拽讘讛</string>
+    <string name="other">讗讞专</string>
+    <string name="left">砖诪讗诇</string>
+    <string name="right">讬诪讬谉</string>
+    <string name="miband_pairing_using_dummy_userdata">诇讗 谞讬转谞讜 谞转讜谞讬 诪砖转诪砖, 谞注砖讛 砖讬诪讜砖 讘谞转讜谞讬 讚诪讛 诇讘讬谞转讬讬诐.</string>
+    <string name="miband_pairing_tap_hint">讻讗砖专 讛志Mi Band 砖诇讱 专讜讟讟 讜诪讛讘讛讘, 讬砖 诇讙注转 讘讜 诪住驻专 驻注诪讬诐 讘专爪祝.</string>
+    <string name="appinstaller_install">讛转拽谞讛</string>
+    <string name="discovery_connected_devices_hint">讬砖 诇讛驻注讬诇 讗转 讛讗驻砖专讜转 诇讗讬转讜专 讛讛转拽谉 砖诇讱. 讛转拽谞讬诐 砖讻讘专 诪讞讜讘专讬诐 讻注转 诇讗 讬转讙诇讜. 讬砖 诇讛驻注讬诇 诪讬拽讜诐 (GPS) 讘讗谞讚专讜讗讬讚 6+. 讬砖 诇讻讘讜转 讗转 砖讜诪专 讛驻专讟讬讜转 注讘讜专 Gadgetbridge 讻讬讜讜谉 砖转讻讜谞讛 讝讜 注砖讜讬讛 诇讛拽专讬住 讜诇讛驻注讬诇 诪讞讚砖 讗转 讛讟诇驻讜谉 砖诇讱. 讗诐 诇讗 谞诪爪讗 讗祝 讛转拽谉 诇讗讞专 诪住驻专 讚拽讜转 讬砖 诇谞住讜转 砖讜讘 诇讗讞专 讛驻注诇转 讛讟诇驻讜谉 讛谞讬讬讚 砖诇讱 诪讞讚砖.</string>
+    <string name="discovery_note">诇转砖讜诪转 诇讘讱:</string>
+    <string name="candidate_item_device_image">转诪讜谞转 讛讛转拽谉</string>
+    <string name="miband_prefs_alias">砖诐/讻讬谞讜讬</string>
+    <string name="pref_header_vibration_count">住驻讬专转 专讟讟</string>
+    <string name="title_activity_sleepmonitor">诪注拽讘 砖讬谞讛</string>
+    <string name="pref_write_logfiles">讻转讬讘转 拽讜讘爪讬 专讬砖讜诐</string>
+    <string name="initializing">诪转讘爪注 讗转讞讜诇</string>
+    <string name="busy_task_fetch_activity_data">谞转讜谞讬 讛驻注讬诇讜转 诪转拽讘诇讬诐</string>
+    <string name="sleep_activity_date_range">诪志%1$s 注讚 %2$s</string>
+    <string name="prefs_wearside">注诇 讬讚 讬诪讬谉 讗讜 砖诪讗诇?</string>
+    <string name="pref_screen_vibration_profile">驻专讜驻讬诇 专讟讟</string>
+    <string name="vibration_profile_staccato">住讟拽讟讜</string>
+    <string name="vibration_profile_short">拽爪专</string>
+    <string name="vibration_profile_medium">讘讬谞讜谞讬</string>
+    <string name="vibration_profile_long">讗专讜讱</string>
+    <string name="vibration_profile_waterdrop">讟讬驻转 诪讬诐</string>
+    <string name="vibration_profile_ring">爪诇爪讜诇</string>
+    <string name="vibration_profile_alarm_clock">砖注讜谉 诪注讜专专</string>
+    <string name="miband_prefs_vibration">专讟讟</string>
+    <string name="vibration_try">讘讚讬拽讛</string>
+    <string name="pref_screen_notification_profile_sms">讛转专讗转 诪住专讜谉</string>
+    <string name="pref_header_vibration_settings">讛讙讚专讜转 专讟讟</string>
+    <string name="pref_screen_notification_profile_generic">讛转专讗讜转 讻诇诇讬讜转</string>
+    <string name="pref_screen_notification_profile_email">讛转专讗讛 注诇 讚讜讗状诇</string>
+    <string name="pref_screen_notification_profile_incoming_call">讛转专讗讛 注诇 砖讬讞讛 谞讻谞住转</string>
+    <string name="pref_screen_notification_profile_generic_chat">爪壮讗讟</string>
+    <string name="pref_screen_notification_profile_generic_navigation">谞讬讜讜讟</string>
+    <string name="pref_screen_notification_profile_generic_social">专砖转 讞讘专转讬转</string>
+    <string name="control_center_find_lost_device">讗讬转讜专 讛转拽谉 砖讗讘讚</string>
+    <string name="control_center_cancel_to_stop_vibration">讬砖 诇讘讟诇 讻讚讬 诇注爪讜专 讗转 讛专讟讟.</string>
+    <string name="title_activity_charts">驻注讬诇讜转 讜砖讬谞讛</string>
+    <string name="title_activity_set_alarm">讛讙讚专转 砖注讜谉 诪注讜专专</string>
+    <string name="controlcenter_start_configure_alarms">讛讙讚专转 砖注讜谉 诪注讜专专</string>
+    <string name="title_activity_alarm_details">驻专讟讬 讛砖注讜谉 讛诪注讜专专</string>
+    <string name="alarm_sun_short">讬讜诐 讗壮</string>
+    <string name="alarm_mon_short">讬讜诐 讘壮</string>
+    <string name="alarm_tue_short">讬讜诐 讙壮</string>
+    <string name="alarm_wed_short">讬讜诐 讚壮</string>
+    <string name="alarm_thu_short">讬讜诐 讛</string>
+    <string name="alarm_fri_short">讬讜诐 讜壮</string>
+    <string name="alarm_sat_short">砖讘转</string>
+    <string name="alarm_smart_wakeup">讛砖讻诪讛 讞讻诪讛</string>
+    <string name="user_feedback_miband_set_alarms_failed">讗讬专注讛 砖讙讬讗讛 讘注转 讛讙讚专转 讛讛转专讗讜转, 谞讗 诇谞住讜转 砖讜讘.</string>
+    <string name="user_feedback_miband_set_alarms_ok">讛讛转专讗讜转 谞砖诇讞讜 诇讛转拽谉.</string>
+    <string name="chart_no_data_synchronize">讗讬谉 谞转讜谞讬诐. 诇住谞讻专谉 诪讜诇 讛讛转拽谉?</string>
+    <string name="user_feedback_miband_activity_data_transfer">讘讛讻谞讜转 诇讛注讘专转 %1$s 砖诇 谞转讜谞讬诐 讛讞诇 诪志%2$s</string>
+    <string name="miband_prefs_fitness_goal">讬注讚 爪注讚讬诐 讬讜诪讬</string>
+    <string name="dbaccess_error_executing">砖讙讬讗讛 讘讛驻注诇转 鈥�%1$s鈥�</string>
+    <string name="controlcenter_start_activitymonitor">讛驻注讬诇讜转 砖诇讱</string>
+    <string name="cannot_connect">诇讗 谞讬转谉 诇讛转讞讘专: %1$s</string>
+    <string name="installer_activity_unable_to_find_handler">诇讗 谞讬转谉 诇诪爪讜讗 诪谞讙谞讜谉 诇讛转拽谞转 讛拽讜讘抓 讛讝讛.</string>
+    <string name="pbw_install_handler_unable_to_install">诇讗 谞讬转谉 诇讛转拽讬谉 讗转 讛拽讜讘抓 讛谞转讜谉: %1$s</string>
+    <string name="pbw_install_handler_hw_revision_mismatch">诇讗 谞讬转谉 诇讛转拽讬谉 讗转 讛拽讜砖讞讛 讛谞转讜谞讛: 讛讬讗 讗讬谞讛 转讜讗诪转 讗转 诪讛讚讜专转 讛讞讜诪专讛 砖诇 讛志Pebble 砖诇讱.</string>
+    <string name="installer_activity_wait_while_determining_status">谞讗 诇讛诪转讬谉 讘注转 讗讬转讜专 诪爪讘 讛讛转拽谞讛鈥�</string>
+    <string name="notif_battery_low_title">讛住讜诇诇讛 砖诇 讛讙讗讚讙壮讟 讞诇砖讛!</string>
+    <string name="notif_battery_low_percent">谞讜转专 讘住讜诇诇讛 砖诇 %1$s:鈥� %2$s%%</string>
+    <string name="notif_battery_low_bigtext_last_charge_time">讟注讬谞讛 讗讞专讜谞讛: %s \n</string>
+    <string name="notif_battery_low_bigtext_number_of_charges">诪住驻专 讟注讬谞讜转: %s</string>
+    <string name="sleepchart_your_sleep">砖讬谞讛</string>
+    <string name="weeksleepchart_sleep_a_week">砖讬谞讛 讘砖讘讜注</string>
+    <string name="weeksleepchart_today_sleep_description">砖讬谞讛 讛讬讜诐, 讬注讚: %1$s</string>
+    <string name="weekstepschart_steps_a_week">爪注讚讬诐 讘砖讘讜注</string>
+    <string name="activity_sleepchart_activity_and_sleep">驻注讬诇讜转</string>
+    <string name="updating_firmware">讛拽讜砖讞讛 诪转注讚讻谞转鈥�</string>
+    <string name="fwapp_install_device_not_ready">诇讗 谞讬转谉 诇讛转拽讬谉 讗转 讛拽讜讘抓, 讛讛转拽谉 诇讗 诪讜讻谉.</string>
+    <string name="installhandler_firmware_name">%3$s %2$s :%1$s</string>
+    <string name="miband_fwinstaller_compatible_version">讙专住讛 转讜讗诪转</string>
+    <string name="miband_fwinstaller_untested_version">讙专住讛 砖诇讗 谞讘讚拽讛!</string>
+    <string name="fwappinstaller_connection_state">讞讬讘讜专 诇讛转拽谉: %1$s</string>
+    <string name="pbw_installhandler_pebble_firmware">讛拽讜砖讞讛 砖诇 讛志Pebble鈥� %1$s</string>
+    <string name="pbwinstallhandler_correct_hw_revision">诪讛讚讜专转 讛讞讜诪专讛 讛谞讻讜谞讛</string>
+    <string name="pbwinstallhandler_incorrect_hw_revision">诪讛讚讜专转 讛讞讜诪专讛 讗讬谞讛 转讜讗诪转!</string>
+    <string name="pbwinstallhandler_app_item">%1$s鈥� (%2$s)</string>
+    <string name="updatefirmwareoperation_updateproblem_do_not_reboot">讗讬专注讛 转拽诇讛 注诐 讛注讘专转 讛拽讜砖讞讛. 谞讗 诇讗 诇讛驻注讬诇 诪讞讚砖 讗转 讛志Mi Band 砖诇讱!</string>
+    <string name="updatefirmwareoperation_metadata_updateproblem">讗讬专注讛 转拽诇讛 注诐 讛注讘专转 谞转讜谞讬 讛注诇 砖诇 讛拽讜砖讞讛</string>
+    <string name="updatefirmwareoperation_update_complete">讛转拽谞转 讛拽讜砖讞讛 讛讜砖诇诪讛</string>
+    <string name="updatefirmwareoperation_update_complete_rebooting">讛转拽谞转 讛拽讜砖讞讛 讛讜砖诇诪讛, 讛讛转拽谉 诪讜驻注诇 诪讞讚砖鈥�</string>
+    <string name="updatefirmwareoperation_write_failed">爪专讬讘转 讛拽讜砖讞讛 谞讻砖诇讛</string>
+    <string name="chart_steps">爪注讚讬诐</string>
+    <string name="liveactivity_live_activity">驻注讬诇讜转 讞讬讛</string>
+    <string name="weeksteps_today_steps_description">爪注讚讬诐 讛讬讜诐, 讬注讚: %1$s</string>
+    <string name="pref_title_dont_ack_transfer">诇讗 诇砖诇讜讞 讗讬砖讜专 注诇 讛注讘专转 谞转讜谞讬 驻注讬诇讜转</string>
+    <string name="pref_summary_dont_ack_transfers">讗诐 讛谞转讜谞讬诐 诇讗 讬讗讜砖专讜 讘爪诪讬讚, 谞转讜谞讬 讛驻注讬诇讜转 诇讗 讬讬诪讞拽讜. 砖讬诪讜砖讬 讗诐 谞注砖讛 砖讬诪讜砖 讘讬讬砖讜诪讜谞讬诐 砖讜谞讬诐 诇诪注讟 Gadgetbridge.</string>
+    <string name="pref_summary_keep_data_on_device">转讻讜谞讛 讝讜 转讙专讜诐 诇砖诪讬专转 谞转讜谞讬 讛驻注讬诇讜转 注诇 讛讛转拽谉 讙诐 诇讗讞专 讛住谞讻专讜谉. 砖讬诪讜砖讬 讗诐 谞注砖讛 砖讬诪讜砖 讘讬讬砖讜诪讜谞讬诐 砖讜谞讬诐 诇诪注讟 Gadgetbridge. 讬讻讜诇 诇讙诪讜专 讗转 讛诪拽讜诐 讛驻谞讜讬 讜/讗讜 诇注爪讜专 讗转 讛住谞讻专讜谉.</string>
+    <string name="pref_title_low_latency_fw_update">砖讬诪讜砖 讘诪爪讘 讞讬讘讜专 诪讛讬专 诇注讚讻讜谞讬 拽讜砖讞讛</string>
+    <string name="pref_summary_low_latency_fw_update">讛驻注诇转 讗驻砖专讜转 讝讜 注砖讜讬讛 诇住讬讬注 讘诪拽专讬诐 砖讘讛诐 注讚讻讜谉 讛拽讜砖讞讛 谞讻砖诇.</string>
+    <string name="live_activity_steps_history">讛讬住讟讜专讬讬转 讛爪注讚讬诐</string>
+    <string name="live_activity_current_steps_per_minute">爪注讚讬诐/讚拽讛 谞讻讜谉 诇注讻砖讬讜</string>
+    <string name="live_activity_total_steps">住讱 讻诇 讛爪注讚讬诐</string>
+    <string name="live_activity_steps_per_minute_history">讛讬住讟讜专讬讬转 爪注讚讬诐 讘讚拽讛</string>
+    <string name="live_activity_start_your_activity">讛转讞诇转 讛驻注讬诇讜转 砖诇讱</string>
+    <string name="abstract_chart_fragment_kind_activity">驻注讬诇讜转</string>
+    <string name="abstract_chart_fragment_kind_light_sleep">砖讬谞讛 拽诇讛</string>
+    <string name="abstract_chart_fragment_kind_deep_sleep">砖讬谞讛 注诪讜拽讛</string>
+    <string name="abstract_chart_fragment_kind_not_worn">诇讗 谞注谞讚</string>
+    <string name="device_not_connected">诇讗 诪讞讜讘专.</string>
+    <string name="user_feedback_all_alarms_disabled">讻诇 讛讛转专讗讜转 诪讜砖讘转讜转</string>
+    <string name="pref_title_keep_data_on_device">诇讛砖讗讬专 讗转 谞转讜谞讬 讛驻注讬诇讜转 讘讛转拽谉</string>
+    <string name="miband_fwinstaller_incompatible_version">拽讜砖讞讛 讘诇转讬 谞转诪讻转</string>
+    <string name="fwinstaller_firmware_not_compatible_to_device">拽讜砖讞讛 讝讜 讗讬谞讛 谞转诪讻转 讘讛转拽谉</string>
+    <string name="miband_prefs_reserve_alarm_calendar">讛转专讗讜转 诇砖诪讬专讛 注讘讜专 讗讬专讜注讬诐 注转讬讚讬讬诐</string>
+    <string name="miband_prefs_hr_sleep_detection">谞讬转谉 诇讛砖转诪砖 讘讞讬讬砖谉 讛讚讜驻拽 讻讚讬 诇砖驻专 讗转 讗讬转讜专 讛砖讬谞讛</string>
+    <string name="miband_prefs_device_time_offset_hours">讛驻专砖 讝诪谉 讘讛转拽谉 讘砖注讜转 (诇讝讬讛讜讬 砖讬谞讛 砖诇 注讜讘讚讬 诪砖诪专讜转)</string>
+    <string name="miband2_prefs_dateformat">转讘谞讬转 讝诪谉</string>
+    <string name="dateformat_time">讝诪谉</string>
+    <string name="dateformat_date_time"><![CDATA[讝诪谉 讜转讗专讬讱]]></string>
+    <string name="mi2_prefs_activate_display_on_lift">讛驻注诇转 讛转爪讜讙讛 注诐 讛谞驻转 讛讬讚</string>
+    <string name="FetchActivityOperation_about_to_transfer_since">注讜诪讚转 诇讛转专讞砖 讛注讘专讛 诪讗讝 %1$s</string>
+    <string name="waiting_for_reconnect">讘讛诪转谞讛 诇讞讬讘讜专 诪讞讚砖</string>
+    <string name="activity_prefs_about_you">注诇讬讱</string>
+    <string name="activity_prefs_year_birth">砖谞转 讛诇讬讚讛</string>
+    <string name="activity_prefs_gender">诪讙讚专</string>
+    <string name="activity_prefs_height_cm">讙讜讘讛 讘住状诪</string>
+    <string name="activity_prefs_weight_kg">诪砖拽诇 讘拽状讙</string>
+    <string name="authenticating">诪转讘爪注 讗讬诪讜转</string>
+    <string name="authentication_required">谞讚专砖 讗讬诪讜转</string>
+    <string name="appwidget_text">讞专专专</string>
+    <string name="add_widget">讛讜住驻转 讜讬讚讙壮讟</string>
+    <string name="activity_prefs_sleep_duration">诪砖讱 讛砖讬谞讛 讛诪讜注讚祝 讘砖注讜转</string>
+    <string name="appwidget_setting_alarm">讛讜讙讚专 砖注讜谉 诪注讜专专 诇砖注讛 %1$02d:%2$02d</string>
+    <string name="device_hw">诪讛讚讜专转 讞讜诪专讛: %1$s</string>
+    <string name="device_fw">诪讛讚讜专转 拽讜砖讞讛: %1$s</string>
+    <string name="error_creating_directory_for_logfiles">讗讬专注讛 砖讙讬讗讛 讘讬爪讬专转 转讬拽讬讬讛 诇拽讜讘爪讬 讛专讬砖讜诐: %1$s</string>
+    <string name="DEVINFO_HR_VER">"讚讜驻拽: "</string>
+    <string name="updatefirmwareoperation_update_in_progress">诪转讘爪注 注讚讻讜谉 拽讜砖讞讛</string>
+    <string name="updatefirmwareoperation_firmware_not_sent">诇讗 谞砖诇讞讛 拽讜砖讞讛</string>
+    <string name="charts_legend_heartrate">讚讜驻拽</string>
+    <string name="live_activity_heart_rate">讚讜驻拽</string>
+    <string name="pref_title_pebble_health_store_raw">讗讞住讜谉 谞转讜谞讬诐 讙讜诇诪讬讬诐 讘诪住讚 讛谞转讜谞讬诐</string>
+    <string name="pref_summary_pebble_health_store_raw">讛谞转讜谞讬诐 诪讗讜讞住谞讬诐 讻诪讜 砖讛诐, 诪讛 砖讬讙专讜诐 诇讛讙讚诇转 谞驻讞 诪住讚 讛谞转讜谞讬诐 诇爪讚 讗驻砖专讜转 诇驻注谞讜讞 讘注转讬讚.</string>
+    <string name="action_db_management">谞讬讛讜诇 谞转讜谞讬诐</string>
+    <string name="title_activity_db_management">谞讬讛讜诇 谞转讜谞讬诐</string>
+    <string name="activity_db_management_import_export_explanation">驻注讜诇讜转 讬讬讘讜讗/讬讬爪讜讗 诪砖转诪砖讜转 讘谞转讬讘 讛讘讗 (诪讜驻讬注 诇讛诇谉) 讘诪讻砖讬专 砖诇讱. 讛转讬拽讬讬讛 讛讝讗转 讞砖讜驻讛 诇讬讬砖讜诪讜谞讬 Android 讗讞专讬诐 讜诇诪讞砖讘 砖诇讱. 谞讗 诇砖讬诐 诇讘 砖讛转讬拽讬讬讛 讛住专转 注诇 讻诇 转讜讻谞讛 转讬诪讞拽 讘讻诇 讛住专转 Gadgetbridge. 讛谞转讜谞讬诐 讻讜诇诇讬诐:
+\nExport_preference - 讛讙讚专讜转 讙诇讜讘诇讬讜转
+\nExport_preference_MAC - 讛讙讚专讜转 诇诪讻砖讬专 诪住讜讬诐
+\n Gadgetbridge - 诪住讚 谞转讜谞讬 诪讻砖讬专 讜驻注讬诇讜转
+\n Gadgetbridge_date - 讛转讗专讬讱 讘讜讗 讬讬讜爪讗 诪住讚 讛谞转讜谞讬诐
+\n 鈥�*.gpx - 讛拽诇讟讜转 GPS
+\n 鈥�*.log - 拽讜讘爪讬 讬讜诪谞讬诐
+\n讛拽讘爪讬诐 砖诇讱 讗诪讜专讬诐 诇讛讬讜转 (讗讜 砖砖诐 注诇讬讱 诇讛讘讬讗 讗转 诪讛 砖诪讬讜注讚 诇讬讬讘讜讗) 驻讛:</string>
+    <string name="activity_db_management_merge_old_title">诪讞讬拽转 诪住讚 谞转讜谞讬诐 诪讬讜砖谉</string>
+    <string name="dbmanagementactivvity_cannot_access_export_path">诇讗 谞讬转谉 诇讙砖转 诇谞转讬讘 讛讬讬爪讜讗. 谞讗 诇讬爪讜专 拽砖专 注诐 讛诪驻转讞讬诐.</string>
+    <string name="dbmanagementactivity_exported_to">讬讬讜爪讗 讗诇: %1$s</string>
+    <string name="dbmanagementactivity_error_exporting_db">砖讙讬讗讛 讘讬讬爪讜讗 诪住讚 讛谞转讜谞讬诐: %1$s</string>
+    <string name="dbmanagementactivity_import_data_title">诇讬讬讘讗 谞转讜谞讬诐?</string>
+    <string name="dbmanagementactivity_overwrite_database_confirmation">讘讗诪转 诇砖讻转讘 注诇 讛谞转讜谞讬诐 讛谞讜讻讞讬讬诐? 讻诇 谞转讜谞讬 讛驻注讬诇讜转 讛谞讜讻讞讬讬诐 (讗诐 讬砖 讻讗诇讛), 讛讛转拽谞讬诐 讜讛讛注讚驻讜转 砖诇讱 讬讬讚专住讜.</string>
+    <string name="dbmanagementactivity_import_successful">讛讬讬讘讜讗 讛爪诇讬讞.</string>
+    <string name="dbmanagementactivity_error_importing_db">砖讙讬讗讛 讘讬讬讘讜讗 诪住讚 讛谞转讜谞讬诐: %1$s</string>
+    <string name="dbmanagementactivity_delete_activity_data_title">诇诪讞讜拽 讗转 谞转讜谞讬 讛驻注讬诇讜转?</string>
+    <string name="dbmanagementactivity_really_delete_entire_db">讘讗诪转 诇诪讞讜拽 讗转 讻诇 诪住讚 讛谞转讜谞讬诐? 讻诇 谞转讜谞讬 讛驻注讬诇讜转 讜讛诪讬讚注 注诇 讛讛转拽谞讬诐 砖诇讱 讬诇讱 诇讗讬讘讜讚.</string>
+    <string name="dbmanagementactivity_database_successfully_deleted">讛谞转讜谞讬诐 谞诪讞拽讜.</string>
+    <string name="dbmanagementactivity_db_deletion_failed">诪讞讬拽转 诪住讚 讛谞转讜谞讬诐 谞讻砖诇讛.</string>
+    <string name="dbmanagementactivity_delete_old_activity_db">诇诪讞讜拽 讗转 诪住讚 谞转讜谞讬 讛驻注讬诇讜转 讛诪讬讜砖谉?</string>
+    <string name="dbmanagementactivity_delete_old_activitydb_confirmation">诇诇诪讞讜拽 讗转 诪住讚 谞转讜谞讬 讛驻注讬诇讜转 讛诪讬讜砖谉? 谞转讜谞讬 驻注讬诇讜转 砖诇讗 讬讬讜讘讗讜 讬诇讻讜 诇讗讬讘讜讚.</string>
+    <string name="dbmanagementactivity_old_activity_db_successfully_deleted">谞转讜谞讬 驻注讬诇讜转 讬砖谞讬诐 谞诪讞拽讜.</string>
+    <string name="dbmanagementactivity_old_activity_db_deletion_failed">诪讞讬拽转 诪住讚 谞转讜谞讬 讛驻注讬诇讜转 讛讬砖谉 谞讻砖诇.</string>
+    <string name="dbmanagementactivity_overwrite">砖讻转讜讘</string>
+    <string name="Cancel">讘讬讟讜诇</string>
+    <string name="Delete">诪讞讬拽讛</string>
+    <!--Strings related to Vibration Activity-->
+    <string name="title_activity_vibration">专讟讟</string>
+    <!--Strings related to Pebble Pairing Activity-->
+    <string name="title_activity_pebble_pairing">爪讬诪讜讚 Pebble</string>
+    <string name="pebble_pairing_hint">讘诪住讱 砖诇 讛转拽谉 讛讗谞讚专讜讗讬讚 砖诇讱 讗诪讜专讛 诇拽驻讜抓 讞诇讜谞讬转 爪讬诪讜讚. 讗诐 讛讞诇讜谞讬转 诇讗 诪讜驻讬注讛, 讬砖 诇讞驻砖 讘诪讙讬专转 讛讚讬讜讜讞讬诐 讜诇讗砖专 讗转 讘拽砖转 讛爪讬诪讜讚. 诇讗讞专 诪讻谉 讬砖 诇讗砖专 讗转 讘拽砖转 讛爪讬诪讜讚 讘志Pebble 砖诇讱.</string>
+    <string name="weather_notification_label">谞讗 诇讜讜讚讗 砖注专讻转 注讬爪讜讘 讝讜 诪讜驻注诇转 讘讬讬砖讜诪讜谉 讚讬讜讜讞 诪讝讙 讛讗讜讜讬专 讻讚讬 诇拽讘诇 谞转讜谞讬 诪讝讙 讗讜讜讬专 诇志Pebble 砖诇讱.\n\n诇讗 谞讚专砖讜转 讻讗谉 讛讙讚专讜转.\n\n谞讬转谉 诇讛驻注讬诇 讗转 讬讬砖讜诪讜谉 诪讝讙 讛讗讜讜讬专 讛诪注专讻转讬 砖诇 讛志Pebble 砖诇讱 诪诪谞讛诇 讛讬讬砖讜诪讜谞讬诐.\n\n诪住讬讻讜转 讛砖注讜谉 讛转讜诪讻讜转 讬爪讬讙讜 讗转 诪讝讙 讛讗讜讜讬专 讗讜讟讜诪讟讬转.</string>
+    <string name="pref_title_setup_bt_pairing">讛驻注诇转 爪讬诪讜讚 Bluetooth</string>
+    <string name="pref_summary_setup_bt_pairing">讬砖 诇讛砖讘讬转 讗转 讝讛 讗诐 拽讜专讜转 转拽诇讜转 讘讞讬讘讜专</string>
+    <string name="unit_metric">诪讟专讬转</string>
+    <string name="unit_imperial">讗讬诪驻专讬讗诇讬转</string>
+    <string name="timeformat_24h">24砖壮</string>
+    <string name="timeformat_am_pm">AM/PM</string>
+    <string name="pref_screen_notification_profile_alarm_clock">砖注讜谉 诪注讜专专</string>
+    <string name="StringUtils_sender"> (%1$s)</string>
+    <string name="find_device_you_found_it">诪爪讗转 讗转 讝讛!</string>
+    <string name="miband2_prefs_timeformat">Mi2: 转讘谞讬转 讝诪谉</string>
+    <string name="action_donate">转专讜诪讛</string>
+    <string name="controlcenter_navigation_drawer_open">驻转讬讞转 诪讙讬专转 讛谞讬讜讜讟</string>
+    <string name="controlcenter_navigation_drawer_close">住讙讬专转 诪讙讬专转 讛谞讬讜讜讟</string>
+    <string name="controlcenter_snackbar_need_longpress">诇讞讬爪讛 讗专讜讻讛 注诇 讛讻专讟讬住 讻讚讬 诇谞转拽</string>
+    <string name="controlcenter_snackbar_disconnecting">诪转讘爪注 谞讬转讜拽</string>
+    <string name="controlcenter_snackbar_connecting">诪转讘爪注转 讛转讞讘专讜转鈥�</string>
+    <string name="controlcenter_snackbar_requested_screenshot">诪住讱 讛讛转拽谉 诪爪讜诇诐</string>
+    <string name="title_activity_calblacklist">讬讜诪谞讬诐 讞住讜诪讬诐</string>
+    <string name="pref_call_privacy_mode_number">讛住转专转 讛诪住驻专 讗讱 讛爪讙转 讛砖诐</string>
+    <string name="pref_blacklist_calendars">讞住讬诪转 讬讜诪谞讬诐</string>
+    <string name="pref_header_pebble_timeline">爪讬专 讝诪谉 Pebble</string>
+    <string name="pref_title_enable_calendar_sync">住谞讻专讜谉 讬讜诪谉</string>
+    <string name="pref_summary_enable_calendar_sync">砖诇讬讞转 讗讬专讜注讬诐 诪讛讬讜诪谉 诇爪讬专 讛讝诪谉</string>
+    <string name="stats_title">讗讝讜专讬 诪讛讬专讜转</string>
+    <string name="stats_x_axis_label">住讱 讻诇 讛讚拽讜转</string>
+    <string name="stats_y_axis_label">爪注讚讬诐 诇讚拽讛</string>
+    <string name="calories">拽诇讜专讬讜转</string>
+    <string name="distance">诪专讞拽</string>
+    <string name="clock">砖注讜谉</string>
+    <string name="heart_rate">讚讜驻拽</string>
+    <string name="battery">住讜诇诇讛</string>
+    <string name="mi2_prefs_goal_notification">讛转专讗转 讬注讚</string>
+    <string name="mi2_prefs_goal_notification_summary">讛爪诪讬讚 讬专讟讜讟 讻讗砖专 讛讙注转 诇讬注讚 讛爪注讚讬诐 讛讬讜诪讬</string>
+    <string name="mi2_prefs_display_items">讛爪讙转 驻专讬讟讬诐</string>
+    <string name="mi2_prefs_display_items_summary">讘讞讬专转 讛驻专讬讟讬诐 砖讬讜驻讬注讜 注诇 诪住讱 讛爪诪讬讚</string>
+    <string name="mi2_prefs_rotate_wrist_to_switch_info">讛讟讬讬转 讛讬讚 讻讚讬 诇注讘讜专 诇诪爪讘 谞转讜谞讬诐 讗讞专</string>
+    <string name="mi2_prefs_do_not_disturb">诇讗 诇讛驻专讬注</string>
+    <string name="mi2_prefs_do_not_disturb_summary">讛爪诪讬讚 诇讗 讬拽讘诇 讛转专讗讜转 讘讝诪谉 讛驻注讬诇讜转</string>
+    <string name="mi2_prefs_inactivity_warnings">讗讝讛专讜转 讞讜住专 驻注讬诇讜转</string>
+    <string name="mi2_prefs_inactivity_warnings_summary">讛爪诪讬讚 讬专讟讜讟 讻砖诇讗 讘讬爪注转 驻注讬诇讜转 讘诪砖讱 讝诪谉 诪讛</string>
+    <string name="mi2_prefs_inactivity_warnings_threshold">住祝 讞讜住专 驻注讬诇讜转 (讘讚拽讜转)</string>
+    <string name="mi2_prefs_inactivity_warnings_dnd_summary">讛砖讘转转 讗讝讛专讜转 讞讜住专 驻注讬诇讜转 诇驻专拽 讝诪谉 诪住讜讬诐</string>
+    <string name="mi2_prefs_do_not_disturb_start">诪讜注讚 讛讛转讞诇讛</string>
+    <string name="mi2_prefs_do_not_disturb_end">诪讜注讚 讛住讬讜诐</string>
+    <string name="fw_upgrade_notice_amazfitbip">驻注讜诇讛 讝讜 转转拽讬谉 讗转 讛拽讜砖讞讛 %s 讘志Amazfit Bip 砖诇讱.
+\n
+\n谞讗 诇讜讜讚讗 讗转 讛转拽谞转 拽讜砖讞转 讛志鈥�.fw 讘讛转讞诇讛, 诇讗讞专 诪讻谉 讗转 拽讜讘抓 讛志鈥�.res 讜诇讘住讜祝 拽讜讘抓 讛志鈥�.gps 讛砖注讜谉 砖诇讱 讬讜驻注诇 诪讞讚砖 诇讗讞专 讛转拽谞转 拽讜讘抓 讛志fw.
+\n
+\n诇转砖讜诪转 诇讘讱: 讗讬谉 爪讜专讱 讘讛转拽谞转 拽讜讘爪讬 讛志鈥�.res 讜讛志鈥�.gps 讗诐 讛诐 讻讘专 讝讛讬诐 诇讗诇讜 砖讛转拽谞转 讘注讘专.
+\n
+\n讛诪砖讱 讛转讛诇讬讱 讛讜讗 注诇 讗讞专讬讜转讱!</string>
+    <string name="pref_summary_transliteration">讬砖 诇讛驻注讬诇 讗驻砖专讜转 讝讜 讗诐 诇讛转拽谉 讗讬谉 转诪讬讻讛 讘讙讜驻谉 讛砖驻讛 砖诇讱</string>
+    <string name="dbmanagementactivity_error_exporting_shared">砖讙讬讗讛 讘讬讬爪讜讗 讛讛注讚驻讛: %1$s</string>
+    <string name="dbmanagementactivity_error_importing_shared">砖讙讬讗讛 讘讬讬讘讜讗 讛注讚驻讛: %1$s</string>
+    <string name="mi2_fw_installhandler_fw53_hint">注诇讬讱 诇讛转拽讬谉 讗转 讛讙专住讛 %1$s 讘讟专诐 讛转拽谞转 拽讜砖讞讛 讝讜!</string>
+    <string name="mi2_enable_text_notifications">讛转专讗讜转 讟拽住讟</string>
+    <string name="mi2_enable_text_notifications_summary"><![CDATA[谞讚专砖转 拽讜砖讞讛 >= 1.0.1.28 讜讛拽讜讘抓 Mili_pro.ft*鈥� 诪讜转拽谉.]]></string>
+    <string name="off">讻讘讜讬</string>
+    <string name="mi2_dnd_off">讻讘讜讬</string>
+    <string name="mi2_dnd_automatic">讗讜讟讜诪讟讬 (讝讬讛讜讬 砖讬谞讛)</string>
+    <string name="mi2_dnd_scheduled">诪转讜讝诪谉 (讛驻专砖讬 讝诪谉)</string>
+    <string name="discovery_attempting_to_pair">诪转讘爪注 谞讬住讬讜谉 诇爪讬诪讜讚 注诐 %1$s</string>
+    <string name="discovery_bonding_failed_immediately">讛讗讬讙讜讚 注诐 %1$s 谞讻砖诇 诪讬讬讚讬转.</string>
+    <string name="discovery_trying_to_connect_to">诪转讘爪注 谞讬住讬讜谉 诇讛转讞讘专 讗诇: %1$s</string>
+    <string name="discovery_enable_bluetooth">讬砖 诇讛驻注讬诇 Bluetooth 讻讚讬 诇讙诇讜转 讛转拽谞讬诐.</string>
+    <string name="discovery_successfully_bonded">讛转讘爪注 讗讬讙讜讚 注诐 %1$s.</string>
+    <string name="discovery_pair_title">诇讬爪讜专 爪讬诪讜讚 注诐 %1$s?</string>
+    <string name="discovery_pair_question">讬砖 诇讘讞讜专 讘爪讬诪讜讚 讻讚讬 诇爪诪讚 讗转 讛讛转拽谞讬诐 砖诇讱. 讗诐 讛转讛诇讬讱 谞讻砖诇, 讬砖 诇谞住讜转 砖讜讘 诇诇讗 爪讬诪讜讚.</string>
+    <string name="discovery_yes_pair">诇爪诪讚</string>
+    <string name="discovery_dont_pair">诇讗 诇爪诪讚</string>
+    <string name="mi2_prefs_button_actions">驻注讜诇讜转 讻驻转讜专</string>
+    <string name="mi2_prefs_button_actions_summary">爪讬讜谉 驻注讜诇讜转 讘诇讞讬爪讛 注诇 讛讻驻转讜专</string>
+    <string name="mi2_prefs_button_press_count">住驻讬专转 诇讞讬爪讜转 注诇 讛讻驻转讜专</string>
+    <string name="mi2_prefs_button_press_count_summary">诪住驻专 诇讞讬爪讜转 注诇 讛讻驻转讜专 讻讚讬 诇讛驻注讬诇 讗转 讗讬专讜注 1. 诇讞讬爪讜转 专爪讜驻讜转 讘讻诪讜转 讝讛讛 诇讬爪讬专转 讗讬专讜注 2 讜讗讬诇讱.</string>
+    <string name="mi2_prefs_button_press_broadcast">讛讜讚注转 砖讬讚讜专 诇砖诇讬讞讛</string>
+    <string name="mi2_prefs_button_press_broadcast_summary">讛讜讚注转 砖讬讚讜专 砖谞砖诇讞转 注诐 讛讗讬专讜注. 讛诪砖转谞讛 `button_id` (诪讝讛讛 讻驻转讜专) 谞讜住祝 讗讜讟讜诪讟讬转 诇讻诇 讛讜讚注讛.</string>
+    <string name="mi2_prefs_button_action">讛驻注诇转 驻注讜诇转 讻驻转讜专</string>
+    <string name="mi2_prefs_button_action_summary">讛驻注诇转 驻注讜诇讛 注诐 诪住驻专 诪讜讙讚专 砖诇 诇讞讬爪讜转 讻驻转讜专</string>
+    <string name="mi2_prefs_button_action_vibrate">讛驻注诇转 专讟讟 讘爪诪讬讚</string>
+    <string name="mi2_prefs_button_action_vibrate_summary">讛驻注诇转 专讟讟 讘爪诪讬讚 注诐 驻注讜诇转 讻驻转讜专</string>
+    <string name="mi2_prefs_button_press_count_max_delay">讛驻专砖 诪专讘讬 讘讬谉 诇讞讬爪讜转</string>
+    <string name="mi2_prefs_button_press_count_max_delay_summary">讛驻专砖 诪专讘讬 讘讬谉 诇讞讬爪讜转 注诇 讛讻驻转讜专 讘诪讬诇讬砖谞讬讜转</string>
+    <string name="_pebble_watch_open_on_phone">驻转讬讞讛 讘讛转拽谉 Android</string>
+    <string name="_pebble_watch_mute">讛砖转拽讛</string>
+    <string name="_pebble_watch_reply">转讙讜讘讛</string>
+    <string name="pref_title_pebble_enable_bgjs">讛驻注诇转 JS 讘专拽注</string>
+    <string name="activity_web_view">驻注讬诇讜转 转爪讜讙转 讚驻讚驻谉</string>
+    <string name="controlcenter_connect">讛转讞讘专 鈥�</string>
+    <string name="pref_summary_pebble_enable_bgjs">讻砖讗驻砖专讜转 讝讜 驻注讬诇讛, 讬转讗驻砖专 诇诪住讬讻讜转 讛砖注讜谉 诇讛爪讬讙 诪讝讙 讗讜讜讬专, 住讜诇诇讛 讜讻讜壮.</string>
+    <string name="fw_upgrade_notice_amazfitcor">驻注讜诇讛 讝讜 转转拽讬谉 讗转 讛拽讜砖讞讛 %s 讘诪拽讜诐 讛谞讜讻讞讬转 砖注诇 讛志Amazfit Cor 砖诇讱.
+\n
+\n谞讗 诇讜讜讚讗 讗转 讛转拽谞转 拽讜讘抓 讛志鈥�.fw, 讜诇讗讞专 诪讻谉 讗转 拽讜讘抓 讛志鈥�.res. 讛爪诪讬讚 砖诇讱 讬讜驻注诇 诪讞讚砖 诇讗讞专 讛转拽谞转 拽讜讘抓 讛志鈥�.fw
+\n
+\n诇转砖讜诪转 诇讘讱: 讗讬谉 爪讜专讱 诇讛转拽讬谉 讗转 拽讜讘抓 讛志鈥�.res 讗诐 讛讜讗 讝讛讛 诇拽讜讘抓 砖讛转拽谞转 讘注讘专.
+\n
+\n讛诪砖讱 诪注讘专 诇谞拽讜讚讛 讝讜 讛讜讗 注诇 讗讞专讬讜转讱!</string>
+    <string name="pref_title_charts_swipe">讛驻注诇转 讛住讟讛 诇讬诪讬谉/砖诪讗诇 讘驻注讬诇讜转 讛转专砖讬诪讬诐</string>
+    <string name="automatic">讗讜讟讜诪讟讬</string>
+    <string name="simplified_chinese">住讬谞讬转 诪驻讜砖讟转</string>
+    <string name="traditional_chinese">住讬谞讬转 诪住讜专转讬转</string>
+    <string name="english">讗谞讙诇讬转</string>
+    <string name="prefs_title_heartrate_measurement_interval">诪讚讚 讚讜驻拽 诇讬讜诐</string>
+    <string name="interval_one_minute">驻注诐 讘讚拽讛</string>
+    <string name="interval_five_minutes">讻诇 5 讚拽讜转</string>
+    <string name="interval_ten_minutes">讻诇 10 讚拽讜转</string>
+    <string name="interval_thirty_minutes">讻诇 讞爪讬 砖注讛</string>
+    <string name="interval_one_hour">驻注诐 讘砖注讛</string>
+    <string name="pref_title_weather_location">诪讬拽讜诐 诪讝讙 讗讜讜讬专 (诇住驻拽 诪讝讙 讛讗讜讜讬专 砖诇 LineageOS)</string>
+    <string name="kind_firmware">拽讜砖讞讛</string>
+    <string name="kind_invalid">谞转讜谞讬诐 砖讙讜讬讬诐</string>
+    <string name="kind_font">讙讜驻谉</string>
+    <string name="kind_gps">拽讜砖讞转 GPS</string>
+    <string name="kind_gps_almanac">讗诇诪谞讱 砖诇 GPS</string>
+    <string name="kind_gps_cep">转讬拽讜谉 砖讙讬讗讜转 GPS</string>
+    <string name="kind_resources">诪砖讗讘讬诐</string>
+    <string name="kind_watchface">驻谞讬 讛砖注讜谉</string>
+    <string name="devicetype_unknown">讛转拽谉 诇讗 讬讚讜注</string>
+    <string name="devicetype_test">讘讚讬拽转 讛讛转拽谉</string>
+    <string name="devicetype_pebble">Pebble</string>
+    <string name="devicetype_miband">Mi Band</string>
+    <string name="devicetype_miband2">Mi Band 2</string>
+    <string name="devicetype_amazfit_bip">Amazfit Bip</string>
+    <string name="devicetype_amazfit_cor">Amazfit Cor</string>
+    <string name="devicetype_vibratissimo">Vibratissimo</string>
+    <string name="devicetype_liveview">LiveView</string>
+    <string name="devicetype_hplus">HPlus</string>
+    <string name="devicetype_makibes_f68">Makibes F68</string>
+    <string name="devicetype_exrizu_k8">Exrizu K8</string>
+    <string name="devicetype_no1_f1">No.1 F1</string>
+    <string name="devicetype_teclast_h30">Teclast H30</string>
+    <string name="pref_title_weather">诪讝讙 讗讜讜讬专</string>
+    <string name="pref_header_auto_export">讬讬爪讜讗 讗讜讟讜诪讟讬</string>
+    <string name="pref_title_auto_export_enabled">讬讬爪讜讗 讗讜讟讜诪讟讬 诪讜驻注诇</string>
+    <string name="pref_title_auto_export_location">诪讬拽讜诐 诇讬讬爪讜讗</string>
+    <string name="pref_title_auto_export_interval">讛驻专砖讬诐 讘讬谉 讬讬爪讜讗 诇讬讬爪讜讗</string>
+    <string name="pref_summary_auto_export_interval">诇讬讬爪讗 讘讻诇 %d 砖注讜转</string>
+    <string name="notif_export_failed_title">讬讬爪讜讗 诪住讚 讛谞转讜谞讬诐 谞讻砖诇! 谞讗 诇讘讚讜拽 讗转 讛讛讙讚专讜转 砖诇讱.</string>
+    <string name="choose_auto_export_location">谞讗 诇讘讞讜专 讗转 诪讬拽讜诐 讛讬讬爪讜讗</string>
+    <string name="spanish">住驻专讚讬转</string>
+    <string name="notification_channel_name">讻诇诇讬</string>
+    <string name="devicetype_xwatch">XWatch</string>
+    <string name="on">驻注讬诇</string>
+    <string name="controlcenter_start_activity_tracks">诪住诇讜诇讬 讛驻注讬诇讜转 砖诇讱</string>
+    <string name="activity_type_not_measured">诇诇讗 诪讚讬讚讛</string>
+    <string name="activity_type_activity">驻注讬诇讜转</string>
+    <string name="activity_type_light_sleep">砖讬谞讛 拽诇讛</string>
+    <string name="activity_type_deep_sleep">砖讬谞讛 注诪讜拽讛</string>
+    <string name="activity_type_not_worn">讛讛转拽谉 诇讗 谞注谞讚</string>
+    <string name="activity_type_running">专讬爪讛</string>
+    <string name="activity_type_walking">讛诇讬讻讛</string>
+    <string name="activity_type_swimming">砖讞讬讬讛</string>
+    <string name="activity_type_unknown">驻注讬诇讜转 诇讗 讬讚讜注讛</string>
+    <string name="activity_summaries">驻注讬诇讜讬讜转 住驻讜专讟讬讘讬讜转</string>
+    <string name="activity_type_biking">专讻讬讘转 讗讜驻谞讬讬诐</string>
+    <string name="activity_type_treadmill">讛诇讬讻讜谉</string>
+    <string name="select_all">讘讞讬专转 讛讻讜诇</string>
+    <string name="share">砖讬转讜祝</string>
+    <string name="reset_index">讗讬驻讜住 诪讜注讚 讛拽讘诇讛</string>
+    <string name="menuitem_status">诪爪讘</string>
+    <string name="menuitem_activity">讛讬住讟讜专讬讬转 讗讬诪讜谞讬诐</string>
+    <string name="menuitem_weather">诪讝讙 讗讜讜讬专</string>
+    <string name="menuitem_alarm">砖注讜谉 诪注讜专专</string>
+    <string name="menuitem_timer">住驻讬专讛 诇讗讞讜专</string>
+    <string name="menuitem_compass">诪爪驻谉</string>
+    <string name="menuitem_settings">讛讙讚专讜转</string>
+    <string name="menuitem_alipay">Alipay</string>
+    <string name="pref_title_pebble_gatt_clientonly">诇拽讜讞 GATT 讘诇讘讚</string>
+    <string name="pref_summary_pebble_gatt_clientonly">诪爪讘 讝讛 诪讬讜注讚 诇志Pebble 2 讜讛讜讗 谞讬住讬讜谞讬, 谞讬转谉 诇谞住讜转 讝讗转 讗诐 谞转拽诇转 讘讘注讬讜转 讞讬讘讜专</string>
+    <string name="devicetype_q8">Q8</string>
+    <string name="fw_upgrade_notice_miband3">驻注讜诇讛 讝讜 转转拽讬谉 讗转 讛拽讜砖讞讛 %s 讘诪拽讜诐 讝讜 砖讻专讙注 诪讜转拽谞转 讘志Mi Band 3 砖诇讱.
+\n
+\n谞讗 诇讜讜讚讗 讗转 讛转拽谞转 拽讜讘抓 讛志鈥�.fw 讘讛转讞诇讛, 诇讗讞专 诪讻谉 讗转 拽讜讘抓 讛志鈥�.res 讛专爪讜注讛 砖诇讱 转讜驻注诇 诪讞讚砖 诇讗讞专 讛转拽谞转 拽讜讘抓 讛志fw.
+\n
+\n诇转砖讜诪转 诇讘讱: 讗讬谉 爪讜专讱 讘讛转拽谞转 拽讜讘抓 讛志鈥�.res 讗诐 讛讜讗 讻讘专 讝讛讛 诇讝讛 砖讛转拽谞转 讘注讘专.
+\n
+\n讛诪砖讱 讛转讛诇讬讱 讛讜讗 注诇 讗讞专讬讜转讱!</string>
+    <string name="devicetype_miband3">Mi Band 3</string>
+    <string name="blacklist_all_for_notifications">讛讜住驻转 讻讜诇诐 诇专砖讬诪讛 讛砖讞讜专讛 砖诇 讛讛讜讚注讜转</string>
+    <string name="whitelist_all_for_notifications">讛讜住驻转 讻讜诇诐 诇专砖讬诪转 讛讛讬转专 砖诇 讛讛讜讚注讜转</string>
+    <string name="pref_auto_fetch">讗讞讝讜专 讗讜讟讜诪讟讬 砖诇 谞转讜谞讬 驻注讬诇讜转</string>
+    <string name="pref_auto_fetch_summary">讛讗讞讝讜专 诪转讘爪注 讘注转 砖讞专讜专 谞注讬诇转 讛诪住讱. 注讜讘讚 专拽 讗诐 讛讜讙讚专 诪谞讙谞讜谉 谞注讬诇讛!</string>
+    <string name="pref_auto_fetch_limit_fetches">讝诪谉 诪讝注专讬 讘讬谉 讗讞讝讜专讬诐</string>
+    <string name="pref_auto_fetch_limit_fetches_summary">诪转讘爪注 讗讞讝讜专 讻诇 %d 讚拽讜转</string>
+    <string name="devicetype_mykronoz_zetime">MyKronoz ZeTime</string>
+    <string name="russian">专讜住讬转</string>
+    <string name="prefs_screen_orientation">讻讬讜讜谉 讛诪住讱</string>
+    <string name="horizontal">讗讜驻拽讬</string>
+    <string name="vertical">讗谞讻讬</string>
+    <string name="devicetype_id115">ID115</string>
+    <string name="menuitem_notifications">讛转专讗讜转</string>
+    <string name="menuitem_more">注讜讚</string>
+    <string name="menuitem_music">诪讜讝讬拽讛</string>
+    <string name="controlcenter_calibrate_device">讻讬讜诇 讛转拽谉</string>
+    <string name="watch9_pairing_tap_hint">讻砖讛砖注讜谉 砖诇讱 专讜讟讟, 注诇讬讱 诇谞注专 讗转 讛讛转拽谉 讗讜 诇诇讞讜抓 注诇 讛讻驻转讜专 砖诇讜.</string>
+    <string name="devicetype_watch9">Watch 9</string>
+    <string name="watch9_time_minutes">讚拽讜转:</string>
+    <string name="watch9_time_hours">砖注讜转:</string>
+    <string name="watch9_time_seconds">砖谞讬讜转:</string>
+    <string name="watch9_calibration_hint">讛讙讚专转 讛砖注讛 砖转讜驻讬注 讘讛转拽谉 砖诇讱 讻专讙注.</string>
+    <string name="watch9_calibration_button">讻讬讜诇</string>
+    <string name="title_activity_watch9_pairing">爪讬诪讜讚 Watch 9</string>
+    <string name="title_activity_watch9_calibration">讻讬讜诇 Watch 9</string>
+    <string name="mi3_prefs_band_screen_unlock">砖讞专讜专 谞注讬诇转 诪住讱 讛爪诪讬讚</string>
+    <string name="mi3_prefs_band_screen_unlock_summary">讬砖 诇讙专讜专 讻诇驻讬 诪注诇讛 讻讚讬 诇砖讞专专 讗转 谞注讬诇转 讛诪住讱 砖诇 讛爪诪讬讚</string>
+    <string name="german">讙专诪谞讬转</string>
+    <string name="italian">讗讬讟诇拽讬转</string>
+    <string name="french">爪专驻转讬转</string>
+    <string name="polish">驻讜诇谞讬转</string>
+    <string name="korean">拽讜专讬讗谞讬转</string>
+    <string name="japanese">讬驻谞讬转</string>
+    <string name="you_slept">%1$s - %2$s</string>
+    <string name="you_did_not_sleep">诇讗 讬砖谞转</string>
+    <string name="norwegian_bokmal">谞讜专讜讜讙讬转 住驻专讜转讬转</string>
+    <string name="pref_title_notifications_timeout">讝诪谉 诪讝注专讬 讘讬谉 讛讜讚注讜转</string>
+    <string name="no_limit">诇诇讗 讛讙讘诇讛</string>
+    <string name="seconds_5">5 砖谞讬讜转</string>
+    <string name="seconds_10">10 砖谞讬讜转</string>
+    <string name="seconds_20">20 砖谞讬讜转</string>
+    <string name="seconds_30">30 砖谞讬讜转</string>
+    <string name="minutes_1">讚拽讛</string>
+    <string name="minutes_5">5 讚拽讜转</string>
+    <string name="minutes_10">10 讚拽讜转</string>
+    <string name="minutes_30">30 讚拽讜转</string>
+    <string name="mi3_prefs_night_mode">诪爪讘 诇讬诇讛</string>
+    <string name="mi3_prefs_night_mode_summary">讛谞诪讻转 讘讛讬专讜转 讛诪住讱 讗讜讟讜诪讟讬转 讘诇讬诇讛</string>
+    <string name="mi3_night_mode_sunset">讘砖拽讬注讛</string>
+    <string name="ok">讗讬砖讜专</string>
+    <string name="share_log">砖讬转讜祝 讬讜诪谉</string>
+    <string name="share_log_warning">谞讗 诇砖讬诐 诇讘 砖拽讜讘爪讬 讛讬讜诪谉 砖诇 Gadgetbridge 注砖讜讬讬诐 诇讛讻讬诇 驻专讟讬诐 讗讬砖讬讬诐 专讘讬诐, 诇专讘讜转 讗讱 诇讗 专拽 谞转讜谞讬诐 专驻讜讗讬讬诐, 诪讝讛讬诐 讬讬讞讜讚讬讬诐 (讻讙讜谉 讻转讜讘转 讛讞讜诪专讛 砖诇 讛诪讻砖讬专), 讛注讚驻讜转 诪讜讝讬拽诇讬讜转 讜讻讜壮. 诪讜讟讘 诇注专讜讱 讗转 讛拽讜讘抓 讜诇讛住讬专 讗转 讛诪讬讚注 讛讝讛 讘讟专诐 讛讙砖转讜 讘讗讜驻谉 爪讬讘讜专讬 讻讚讬 诇讚讜讜讞 注诇 转拽诇讛.</string>
+    <string name="warning">讗讝讛专讛!</string>
+    <string name="controlcenter_change_led_color">讛讞诇驻转 爪讘注 谞讜专讬转</string>
+    <string name="controlcenter_change_fm_frequency">讛讞诇驻转 转讚讬专讜转 FM</string>
+    <string name="devicetype_roidmi">Roidmi</string>
+    <string name="devicetype_roidmi3">Roidmi 3</string>
+    <string name="preferences_led_color">爪讘注 谞讜专讬转</string>
+    <string name="preferences_fm_frequency">转讚专 FM</string>
+    <string name="pref_invalid_frequency_title">转讚专 砖讙讜讬</string>
+    <string name="pref_invalid_frequency_message">谞讗 诇讘讞讜专 讘转讚专 讘讬谉 87.5 诇讘讬谉 108.0</string>
+    <string name="notif_battery_low">讛住讜诇诇讛 砖诇 %1$s 讞诇砖讛</string>
+    <string name="notif_battery_low_extended">讛住讜诇诇讛 砖诇 %1$s 讞诇砖讛: %2$s</string>
+    <string name="activity_prefs_charts">讛讙讚专讜转 转专砖讬诪讬诐</string>
+    <string name="activity_prefs_chart_max_heart_rate">讚讜驻拽 诪专讘讬</string>
+    <string name="activity_prefs_chart_min_heart_rate">讚讜驻拽 诪讝注专讬</string>
+    <string name="pref_title_rtl">诪讬诪讬谉 诇砖诪讗诇</string>
+    <string name="pref_summary_rtl">讬砖 诇讛驻注讬诇 讗驻砖专讜转 讝讜 讗诐 讛讛转拽谉 诇讗 讬讻讜诇 诇讛爪讬讙 砖驻讜转 诪讬诪讬谉 诇砖诪讗诇</string>
+    <string name="pref_rtl_max_line_length">讙讜讚诇 砖讜专讛 诪专讘讬 诪讬诪讬谉 诇砖诪讗诇</string>
+    <string name="pref_rtl_max_line_length_summary">拽讬爪讜专 讗讜 讛讗专讻转 讛砖讜专讜转 砖讗诇讬讛谉 诪讜驻专讚 讟拽住讟 诪讬诪讬谉 诇砖诪讗诇</string>
+    <string name="pref_title_contextual_arabic">注专讘讬转 诪讞讜讘专转</string>
+    <string name="pref_summary_contextual_arabic">讬砖 诇讛驻注讬诇 讗驻砖专讜转 讝讜 讻讚讬 诇转诪讜讱 讘注专讘讬转 诪讞讜讘专转</string>
+    <string name="preferences_rtl_settings">转诪讬讻讛 讘讟拽住讟 诪讬诪讬谉 诇砖诪讗诇</string>
+    <string name="language_and_region_prefs">讛讙讚专讜转 砖驻讛 讜讗讬讝讜专</string>
+    <string name="lack_of_sleep">诪讞住讜专 讘砖讬谞讛: %1$s</string>
+    <string name="overslept">注讜讚祝 砖讬谞讛: %1$s</string>
+    <string name="lack_of_step">诪讞住讜专 讘爪注讚讬诐: %1$d</string>
+    <string name="overstep">注讜讚祝 爪注讚讬诐: %1$d</string>
+    <string name="no_data">讗讬谉 谞转讜谞讬诐</string>
+    <string name="live_activity_max_heart_rate">讚讜驻拽 谞讜讻讞讬 / 诪专讘讬: %1$d 鈥�/ %2$d</string>
+    <string name="debugactivity_really_factoryreset_title">诇讗驻住 诇讛讙讚专讜转 讬爪专谉\?</string>
+    <string name="debugactivity_really_factoryreset">讗讬驻讜住 诇讛讙讚专讜转 讬爪专谉 讬诪讞拽 讗转 讻诇 讛谞转讜谞讬诐 诪讛讛转拽谉 讛诪拽讜砖专 (讗诐 讬砖 转诪讬讻讛 讘讝讛). 诪讻砖讬专讬诐 诪讘讬转 Xiaomi/Huami 讙诐 诪砖谞讬诐 讗转 讻转讜讘转 讛讞讜诪专讛 (MAC), 讻讱 砖讘注讬谞讬 Gadgetbridge 讛诐 谞讞砖讘讬诐 诇诪讻砖讬专讬诐 讞讚砖讬诐.</string>
+    <string name="devicetype_casiogb6900">Casio GB-6900鈥�</string>
+    <string name="title_activity_notification_filter">诪住谞谉 讛转专讗讜转</string>
+    <string name="edittext_notification_filter_words_hint">谞讗 诇讛拽诇讬讚 讗转 讛诪讬诇讬诐 讛诪讘讜拽砖讜转, 讗讞转 讘讻诇 砖讜专讛</string>
+    <string name="toast_notification_filter_saved_successfully">诪住谞谉 讛讛转专讗讜转 谞砖诪专</string>
+    <string name="filter_mode_none">诇讗 诇住谞谉</string>
+    <string name="filter_mode_whitelist">诇讛爪讬讙 讻讗砖专 讛诪讬诇讬诐 诪讜驻讬注讜转</string>
+    <string name="filter_mode_blacklist">诇讞住讜诐 讻讗砖专 讛诪讬诇讬诐 诪讜驻讬注讜转</string>
+    <string name="filter_submode_at_least_one">诇驻讞讜转 讗讞转 诪讛诪讬诇讬诐</string>
+    <string name="filter_submode_all">讻诇 讛诪讬诇讬诐</string>
+    <string name="toast_notification_filter_words_empty_hint">谞讗 诇讛拽诇讬讚 诇驻讞讜转 诪讬诇讛 讗讞转</string>
+    <string name="filter_mode">诪爪讘 诪住谞谉</string>
+    <string name="mode_configuration">转爪讜专转 诪爪讘</string>
+    <string name="save_configuration">砖诪讬专转 转爪讜专讛</string>
+    <string name="appwidget_not_connected">诇讗 诪讞讜讘专, 诇讗 讛讜讙讚专 砖注讜谉 诪注讜专专.</string>
+    <string name="activity_type_exercise">讗讬诪讜谉</string>
+    <string name="prefs_disconnect_notification">讛转专讗转 谞讬转讜拽</string>
+    <string name="zetime_title_settings">讛讙讚专讜转 ZeTime</string>
+    <string name="zetime_title_heartrate">讛讙讚专讜转 讚讜驻拽</string>
+    <string name="zetime_title_screentime">诪砖讱 驻注讬诇讜转 诪住讱 讘砖谞讬讜转</string>
+    <string name="zetime_title_heart_rate_alarm">讛转专讗转 讚讜驻拽</string>
+    <string name="zetime_title_heart_rate_alarm_summary">讛砖注讜谉 讬讝讛讬专 讗讜转讱 讻砖讛讚讜驻拽 讞讜专讙 诪讛讙讘讜诇讜转.</string>
+    <string name="zetime_heart_rate_alarm_enable">讛驻注诇转 讛转专讗转 讚讜驻拽</string>
+    <string name="activity_prefs_alarm_max_heart_rate">讚讜驻拽 诪专讘讬</string>
+    <string name="activity_prefs_alarm_min_heart_rate">讚讜驻拽 诪讝注专讬</string>
+    <string name="zetime_analog_mode">诪爪讘 诪讞讜讙讬诐</string>
+    <string name="zetime_analog_mode_hands">讬讚讬讬诐 讘诇讘讚</string>
+    <string name="zetime_analog_mode_handsandsteps">讬讚讬讬诐 讜爪注讚讬诐</string>
+    <string name="zetime_activity_tracking">诪注拽讘 讗讞专 驻注讬诇讜转</string>
+    <string name="zetime_activity_tracking_summary">讛驻注诇转 诪注拽讘 讗讞专 驻注讬诇讜转 转住驻讜专 讗转 爪注讚讬讱 讜讻谉 讛诇讗讛.</string>
+    <string name="zetime_handmove_display">转谞讜注转 讬讚</string>
+    <string name="zetime_handmove_display_summary">讬砖 诇讛讟讜转 讗转 讻祝 讛讬讚 讻讚讬 诇讛驻注讬诇 讜诇讛砖讘讬转 讗转 讛转爪讜讙讛.</string>
+    <string name="zetime_calories_type">住讜讙 拽诇讜专讬讜转</string>
+    <string name="zetime_calories_type_active">拽诇讜专讬讜转 驻注讬诇讜转 砖谞砖专驻讜 讘诇讘讚</string>
+    <string name="zetime_calories_type_all">拽诇讜专讬讜转 驻注讬诇讜转 讜讘诇转讬 驻注讬诇讜转 砖谞砖专驻讜 讘诇讘讚</string>
+    <string name="zetime_date_format">转爪讜专转 转讗专讬讱</string>
+    <string name="zetime_date_format_1">YY/MM/DD</string>
+    <string name="zetime_date_format_2">DD/MM/YY</string>
+    <string name="zetime_date_format_3">MM/DD/YY</string>
+    <string name="zetime_prefs_inactivity_repetitions">讞讝专讜转</string>
+    <string name="zetime_prefs_inactivity_mo">讬讜诐 砖谞讬</string>
+    <string name="zetime_prefs_inactivity_tu">讬讜诐 砖诇讬砖讬</string>
+    <string name="zetime_prefs_inactivity_we">讬讜诐 专讘讬注讬</string>
+    <string name="zetime_prefs_inactivity_th">讬讜诐 讞诪讬砖讬</string>
+    <string name="zetime_prefs_inactivity_fr">讬讜诐 砖讬砖讬</string>
+    <string name="zetime_prefs_inactivity_sa">砖讘转</string>
+    <string name="zetime_prefs_inactivity_su">讬讜诐 专讗砖讜谉</string>
+    <string name="zetime_title_alarm_signaling">讛讙讚专转 住讜讙 讗讬转讜转 诇讛转专讗讛</string>
+    <string name="zetime_signaling_none">砖拽讟</string>
+    <string name="zetime_signaling_vibrate">专讟讟 诪转诪砖讱</string>
+    <string name="zetime_signaling_beep">爪驻爪讜祝 诪转诪砖讱</string>
+    <string name="zetime_signaling_vibrate_beep">专讟讟 讜爪驻爪讜祝 诪转诪砖讻讬诐</string>
+    <string name="zetime_signaling_vibrate_once">专讟讟 讞讚志驻注诪讬</string>
+    <string name="zetime_signaling_vibrate_twice">专讟讟 讻驻讜诇</string>
+    <string name="zetime_signaling_beep_once">爪驻爪讜祝 讞讚志驻注诪讬</string>
+    <string name="zetime_signaling_beep_twice">爪驻爪讜祝 讻驻讜诇</string>
+    <string name="zetime_signaling_vibrate_beep_once">爪驻爪讜祝 讜专讟讟 讞讚志驻注诪讬讬诐</string>
+    <string name="pref_screen_notification_profile_missed_call">讛转专讗转 砖讬讞讛 砖诇讗 谞注谞转讛</string>
+    <string name="pref_screen_notification_profile_calendar">讛转专讗转 讬讜诪谉</string>
+    <string name="pref_screen_notification_profile_inactivity">讛转专讗转 讞讜住专 驻注讬诇讜转</string>
+    <string name="pref_screen_notification_profile_low_power">讗讝讛专转 住讜诇诇讛 讞讚砖讛</string>
+    <string name="pref_screen_notification_profile_anti_loss">讗讝讛专转 诪谞讬注转 讗讬讘讜讚</string>
+    <string name="interval_fifteen_minutes">讻诇 专讘注 砖注讛</string>
+    <string name="interval_forty_five_minutes">讻诇 45 讚拽讜转</string>
+    <string name="activity_prefs_calories_burnt">讬注讚 讬讜诪讬: 拽诇讜专讬讜转 砖谞砖专驻讜</string>
+    <string name="activity_prefs_distance_meters">讬注讚 讬讜诪讬: 诪专讞拽 讘诪讟专讬诐</string>
+    <string name="activity_prefs_activetime_minutes">讬注讚 讬讜诪讬: 讝诪谉 驻注讬诇讜转 讘讚拽讜转</string>
+    <string name="devicetype_miscale2">Mi Scale 2</string>
+    <string name="pref_title_support_voip_calls">讛驻注诇转 砖讬讞讜转 VoIP 诪讛讬讬砖讜诪讜谉</string>
+    <string name="title_activity_device_specific_settings">讛讙讚专讜转 讬讬讞讜讚讬讜转 诇诪讻砖讬专</string>
+    <string name="pref_title_authkey">诪驻转讞 讗讬诪讜转</string>
+    <string name="pref_summary_authkey">谞讬转谉 诇讛讞诇讬祝 讗转 诪驻转讞 讛讗讬诪讜转 诇诪驻转讞 诪砖讜转祝 讘讻诇 诪讻砖讬专讬 讛志Android 砖诇讱 诪讛诐 讘专爪讜谞讱 诇讛转讞讘专. 诪驻转讞 讘专专转 讛诪讞讚诇 讛拽讜讚诐 诇讻诇 讛诪讻砖讬专讬诐 讛讜讗 0123456789@ABCDE</string>
+    <string name="devicetype_bfh16">BFH-16</string>
+    <string name="fw_upgrade_notice_amazfitcor2">驻注讜诇讛 讝讜 转转拽讬谉 讗转 讛拽讜砖讞讛 %s 讘诪拽讜诐 讝讜 砖讻专讙注 诪讜转拽谞转 讘志Amazfit Cor 2 砖诇讱. \n \n谞讗 诇讜讜讚讗 讗转 讛转拽谞转 拽讜砖讞转 讛志鈥�.fw 讘讛转讞诇讛, 诇讗讞专 诪讻谉 讗转 拽讜讘抓 讛志鈥�.res. 讛爪诪讬讚 砖诇讱 讬讜驻注诇 诪讞讚砖 诇讗讞专 讛转拽谞转 拽讜讘抓 讛志fw. \n \n诇转砖讜诪转 诇讘讱: 讗讬谉 爪讜专讱 讘讛转拽谞转 拽讜讘爪讬 讛志鈥�.res 讗诐 讛讜讗 讻讘专 讝讛讬诐 诇讝讛 砖讛转拽谞转 讘注讘专. \n \n讛诪砖讱 讛转讛诇讬讱 讛讜讗 注诇 讗讞专讬讜转讱! \n \n转讛诇讬讱 讝讛 诇讗 谞讘讚拽, 讻谞专讗讛 砖讬讛讬讛 注诇讬讱 诇讛转拽讬谉 拽讜砖讞讛 诪住讜讙 BEATS_W 讗诐 砖诐 讛诪讻砖讬专 砖诇讱 讛讜讗 鈥濧mazfit Band 2鈥�</string>
+    <string name="dutch">讛讜诇谞讚讬转</string>
+    <string name="turkish">讟讜专拽讬转</string>
+    <string name="ukrainian">讗讜拽专讗讬谞讬转</string>
+    <string name="arabic">注专讘讬转</string>
+    <string name="indonesian">讗讬谞讚讜谞讝讬转</string>
+    <string name="thai">转讗讬</string>
+    <string name="vietnamese">讜讬讬讟谞讗诪讬转</string>
+    <string name="portuguese">驻讜专讟讜讙诇讬转</string>
+    <string name="devicetype_amazfit_cor2">Amazfit Cor 2</string>
+    <string name="devicetype_miband4">Mi Band 4</string>
+    <string name="fw_upgrade_notice_miband4">驻注讜诇讛 讝讜 转转拽讬谉 讗转 讛拽讜砖讞讛 %s 讘志Mi Band 4 砖诇讱.
+\n
+\n谞讗 诇讜讜讚讗 讗转 讛转拽谞转 拽讜讘抓 讛志鈥�.fw 讘讛转讞诇讛, 诇讗讞专 诪讻谉 讗转 拽讜讘抓 讛志鈥�.res 讛砖注讜谉 砖诇讱 讬讜驻注诇 诪讞讚砖 诇讗讞专 讛转拽谞转 拽讜讘抓 讛志fw.
+\n
+\n诇转砖讜诪转 诇讘讱: 讗讬谉 爪讜专讱 讘讛转拽谞转 拽讜讘抓 讛志鈥�.res 讗诐 讛讜讗 讻讘专 讝讛讛 诇讝讛 砖讛转拽谞转 讘注讘专.
+\n
+\n讛诪砖讱 讛转讛诇讬讱 讛讜讗 注诇 讗讞专讬讜转讱!</string>
+    <string name="prefs_hr_alarm_activity">讛转专讗转 讚讜驻拽 讘诪讛诇讱 驻注讬诇讜转 住驻讜专讟讬讘讬转</string>
+    <string name="prefs_hr_alarm_low">讙讘讜诇 转讞转讜谉</string>
+    <string name="prefs_hr_alarm_high">讙讘讜诇 注诇讬讜谉</string>
+    <string name="average">诪诪讜爪注: %1$s</string>
+    <string name="pref_header_charts">讛讙讚专讜转 转专砖讬诪讬诐</string>
+    <string name="pref_title_charts_average">讛爪讙转 诪诪讜爪注讬诐 讘转专砖讬诪讬诐</string>
+    <string name="pref_title_charts_range">讟讜讜讞 讛转专砖讬诪讬诐</string>
+    <string name="pref_charts_range_on">讟讜讜讞 讛转专砖讬诪讬诐 诪讜讙讚专 诇讞讜讚砖</string>
+    <string name="pref_charts_range_off">讟讜讜讞 讛转专砖讬诪讬诐 诪讜讙讚专 诇砖讘讜注</string>
+    <string name="weekstepschart_steps_a_month">爪注讚讬诐 讘讞讜讚砖</string>
+    <string name="weeksleepchart_sleep_a_month">砖讬谞讛 讘讞讜讚砖</string>
+    <string name="devicetype_mijia_lywsd02">砖注讜谉 拽讬专 讞讻诐 诪讘讬转 Mijia</string>
+    <string name="menuitem_nfc">转拽砖讜专转 拽专讘讛</string>
+    <string name="pref_summary_expose_hr">诪讗驻砖专 诇讬讬砖讜诪讜谞讬诐 讗讞专讬诐 诇讙砖转 诇谞转讜谞讬 讚讜驻拽 讘讝诪谉 讗诪转 讻砖志Gadgetbridge 诪讞讜讘专</string>
+    <string name="pref_title_expose_hr">讙讬砖讛 砖诇 爪讚志砖诇讬砖讬 诇讚讜驻拽 讘讝诪谉 讗诪转</string>
+    <string name="pref_title_use_custom_font">讙讜驻谉 诪讜转讗诐</string>
+    <string name="pref_summary_use_custom_font">讬砖 诇讛驻注讬诇 转讻讜谞讛 讝讜 讗诐 诇诪讻砖讬专 砖诇讱 讬砖 拽讜砖讞讛 注诐 讙讜驻谉 砖转讜诪讱 讘讗诪讜讙壮讬</string>
+    <string name="activity_db_management_autoexport_explanation">诪讬拽讜诐 讛讬讬爪讜讗 讛讗讜讟讜诪讟讬 砖诇 诪住讚 讛谞转讜谞讬诐 讛讜讙讚专 诇讻讗谉:</string>
+    <string name="activity_db_management_autoexport_label">讬讬爪讜讗 讗讜讟讜诪讟讬</string>
+    <string name="activity_DB_ExportButton">讬讬爪讜讗 谞转讜谞讬诐</string>
+    <string name="activity_DB_import_button">讬讬讘讜讗 谞转讜谞讬诐</string>
+    <string name="activity_DB_test_export_button">讛驻注诇转 讬讬爪讜讗 讗讜讟讜诪讟讬 讻注转</string>
+    <string name="activity_DB_test_export_message">诪住讚 讛谞转讜谞讬诐 诪讬讜爪讗鈥�</string>
+    <string name="activity_DB_delete_legacy_button">诪讞讬拽转 诪住讚 谞转讜谞讬诐 讬砖谉</string>
+    <string name="activity_DB_empty_button">诪住讚 谞转讜谞讬诐 专讬拽</string>
+    <string name="activity_db_management_empty_DB">诪住讚 谞转讜谞讬诐 专讬拽</string>
+    <string name="activity_db_management_exportimport_label">讬讬爪讜讗 讜讬讬讘讜讗</string>
+    <string name="activity_db_management_empty_db_warning">讗讝讛专讛! 诇讞讬爪讛 注诇 讻驻转讜专 讝讛 转讜讘讬诇 诇诪讞讬拽转 讻诇 讛谞转讜谞讬诐 砖诇讱 讜讛讗讬住讜祝 讬转讞讬诇 诪讛讛转讞诇讛.</string>
+    <string name="appwidget_sleep_alarm_widget_label">讛转专讗转 砖讬谞讛</string>
+    <string name="widget_listing_label">诪爪讘 讜讛转专讗讜转</string>
+    <string name="widget_set_alarm_after">讛讙讚专转 讛转专讗讛 诇讗讞专:</string>
+    <string name="widget_5_minutes">5 讚拽讜转</string>
+    <string name="widget_10_minutes">10 讚拽讜转</string>
+    <string name="widget_20_minutes">20 讚拽讜转</string>
+    <string name="widget_1_hour">砖注讛</string>
+    <string name="pref_display_add_device_fab">讻驻转讜专 讛讜住驻转 讛转拽谉 讞讚砖</string>
+    <string name="pref_display_add_device_fab_on">讙诇讜讬 转诪讬讚</string>
+    <string name="pref_display_add_device_fab_off">讙诇讜讬 专拽 讗诐 讟专诐 谞讜住驻讜 讛转拽谞讬诐</string>
+    <plurals name="widget_alarm_target_hours">
+        <item quantity="one">砖注讛</item>
+        <item quantity="two">砖注转讬讬诐</item>
+        <item quantity="many">%d 砖注讜转</item>
+        <item quantity="other">%d 砖注讜转</item>
+    </plurals>
+    <string name="activity_error_no_app_for_gpx">讻讚讬 诇爪驻讜转 讘注拽讘讜转 驻注讬诇讜转, 注诇讬讱 诇讛转拽讬谉 讬讬砖讜诪讜谉 砖讬讻讜诇 诇讟驻诇 讘拽讜讘爪讬 GPX.</string>
+    <string name="preferences_makibes_hr3_settings">讛讙讚专讜转 砖诇 Makibes HR3</string>
+    <string name="devicetype_makibes_hr3">Makibes HR3</string>
+    <string name="devicetype_amazfit_bip_lite">Amazfit Bip Lite</string>
+    <string name="prefs_find_phone">讗讬转讜专 讛讟诇驻讜谉</string>
+    <string name="prefs_enable_find_phone">讛驻注诇转 鈥氉愖欁曌� 讛讟诇驻讜谉鈥�</string>
+    <string name="prefs_find_phone_summary">谞讬转谉 诇讛砖转诪砖 讘爪诪讬讚 讻讚讬 砖讛讟诇驻讜谉 砖诇讱 讬砖诪讬注 爪诇爪讜诇.</string>
+    <string name="prefs_find_phone_duration">诪砖讱 讝诪谉 讛爪诇爪讜诇 讘砖谞讬讜转</string>
+    <string name="maximum_duration">诪砖讱</string>
+    <string name="discovery_need_to_enter_authkey">诇诪讻砖讬专 讝讛 谞讚专砖 诪驻转讞 讗讬诪讜转 住讜讚讬, 讬砖 诇讙注转 谞讙讬注讛 讗专讜讻讛 注诇 讛诪讻砖讬专 讻讚讬 诇讛讝讬谉 讗讜转讜. 驻专讟讬诐 谞讜住驻讬诐 讘讜讜讬拽讬.</string>
+    <string name="fw_upgrade_notice_amazfitbip_lite">驻注讜诇讛 讝讜 转转拽讬谉 讗转 讛拽讜砖讞讛 %s 注诇 讛志Amazfit Bip Lite 砖诇讱.
+\n
+\n谞讗 诇讜讜讚讗 讗转 讛转拽谞转 拽讜砖讞转 讛志鈥�.fw 讘讛转讞诇讛, 诇讗讞专 诪讻谉 讗转 拽讜讘抓 讛志鈥�.res 讛砖注讜谉 砖诇讱 讬讜驻注诇 诪讞讚砖 诇讗讞专 讛转拽谞转 拽讜讘抓 讛志fw.
+\n
+\n诇转砖讜诪转 诇讘讱: 讗讬谉 爪讜专讱 讘讛转拽谞转 拽讜讘抓 讛志鈥�.res 讗诐 讛讜讗 讻讘专 讝讛讛 诇讝讛 砖讛转拽谞转 讘注讘专.
+\n
+\n讛诪砖讱 讛转讛诇讬讱 讛讜讗 注诇 讗讞专讬讜转讱!</string>
+    <string name="devicetype_amazfit_gtr">Amazfit GTR</string>
+    <string name="fw_upgrade_notice_amazfitgtr">驻注讜诇讛 讝讜 转转拽讬谉 讗转 讛拽讜砖讞讛 %s 讘诪拽讜诐 讝讜 砖讻专讙注 诪讜转拽谞转 讘志Amazfit GTR 砖诇讱.
+\n
+\n谞讗 诇讜讜讚讗 讗转 讛转拽谞转 拽讜砖讞转 讛志鈥�.fw 讘讛转讞诇讛, 诇讗讞专 诪讻谉 讗转 拽讜讘抓 讛志鈥�.res 讜诇讘住讜祝 拽讜讘抓 讛志鈥�.gps 讛砖注讜谉 砖诇讱 讬讜驻注诇 诪讞讚砖 诇讗讞专 讛转拽谞转 拽讜讘抓 讛志fw.
+\n
+\n诇转砖讜诪转 诇讘讱: 讗讬谉 爪讜专讱 讘讛转拽谞转 拽讜讘爪讬 讛志鈥�.res 讜讛志鈥�.gps 讗诐 讛诐 讻讘专 讝讛讬诐 诇讗诇讜 砖讛转拽谞转 讘注讘专.
+\n
+\n讛诪砖讱 讛转讛诇讬讱 讛讜讗 注诇 讗讞专讬讜转讱!</string>
+    <string name="pref_chart_heartrate_color_red">讗讚讜诐</string>
+    <string name="pref_chart_heartrate_color_orange">讻转讜诐</string>
+    <string name="pref_title_chart_heartrate_color">爪讘注 讚讜驻拽</string>
+    <string name="pref_title_chart_sleep_rolling_24_hour">讟讜讜讞 砖讬谞讛</string>
+    <string name="pref_chart_sleep_rolling_24_on">24 讛砖注讜转 讛讗讞专讜谞讜转</string>
+    <string name="pref_chart_sleep_rolling_24_off">爪讛专讬讬诐 注讚 爪讛专讬讬诐</string>
+    <string name="devicetype_amazfit_gts">Amazfit GTS</string>
+    <string name="fw_upgrade_notice_amazfitgts">驻注讜诇讛 讝讜 转转拽讬谉 讗转 讛拽讜砖讞讛 %s 讘志Amazfit GTS 砖诇讱.
+\n
+\n谞讗 诇讜讜讚讗 讗转 讛转拽谞转 拽讜砖讞转 讛志鈥�.fw 讘讛转讞诇讛, 诇讗讞专 诪讻谉 讗转 拽讜讘抓 讛志鈥�.res 讜诇讘住讜祝 拽讜讘抓 讛志鈥�.gps 讛砖注讜谉 砖诇讱 讬讜驻注诇 诪讞讚砖 诇讗讞专 讛转拽谞转 拽讜讘抓 讛志fw.
+\n
+\n诇转砖讜诪转 诇讘讱: 讗讬谉 爪讜专讱 讘讛转拽谞转 拽讜讘爪讬 讛志鈥�.res 讜讛志鈥�.gps 讗诐 讛诐 讻讘专 讝讛讬诐 诇讗诇讜 砖讛转拽谞转 讘注讘专.
+\n
+\n讛诪砖讱 讛转讛诇讬讱 讛讜讗 注诇 讗讞专讬讜转讱!</string>
+    <string name="mi2_prefs_button_press_broadcast_default_value" translatable="false">nodomain.freeyourgadget.gadgetbridge.ButtonPressed</string>
+    <string name="devicetype_qhybrid">Fossil Q Hybrid</string>
+    <string name="preferences_qhybrid_settings">讛讙讚专讜转 Q Hybrid</string>
+    <string name="watch_not_connected">讛砖注讜谉 诇讗 诪讞讜讘专</string>
+    <string name="qhybrid_vibration_strength">注爪诪转 专讟讟:</string>
+    <string name="qhybrid_goal_in_steps">讬注讚 讘爪注讚讬诐</string>
+    <string name="qhybrid_time_shift">讛讝讞转 讝诪谉</string>
+    <string name="qhybrid_second_timezone_offset_relative_to_utc">讛讬住讟 讗讝讜专 讝诪谉 砖谞讬 讘讬讞住 诇志UTC</string>
+    <string name="qhybrid_overwrite_buttons">讚专讬住转 讻驻转讜专讬诐</string>
+    <string name="qhybrid_prompt_million_steps">谞讗 诇讛讙讚讬专 讗转 诪谞讬讬谉 讛爪注讚讬诐 诇诪讬诇讬讜谉 讻讚讬 诇讛驻注讬诇 讝讗转.</string>
+    <string name="qhybrid_buttons_overwrite_success">讛讻驻转讜专讬诐 谞讚专住讜</string>
+    <string name="qhybrid_buttons_overwrite_error">讚专讬住转 讛讻驻转讜专讬诐 谞讻砖诇讛</string>
+    <string name="qhybrid_changes_delay_prompt">讛砖讬谞讜讬讬诐 讬转讘爪注讜 讘讛拽讚诐鈥�</string>
+    <string name="qhybrid_use_activity_hand_as_notification_counter">诇讛砖转诪砖 讘讬讚 讛驻注讬诇讜转 讻诪讜谞讛 讛转专讗讜转</string>
+    <string name="qhybrid_offset_timezone">诪专讞拽 诪讗讝讜专 讝诪谉</string>
+    <string name="qhybrid_offset_time_by">诪专讞拽 诪讛砖注讛</string>
+    <string name="pref_disable_new_ble_scanning">讛砖讘转转 住专讬拽转 BLE 讞讚砖讛</string>
+    <string name="pref_summary_disable_new_ble_scanning">讬砖 诇住诪谉 讗转 讛讗驻砖专讜转 讛讝讗转 讗诐 讛诪讻砖讬专 砖诇讱 诇讗 谞诪爪讗 讘诪讛诇讱 讛讗讬转讜专</string>
+    <string name="devicetype_banglejs">Bangle.js</string>
+    <string name="devicetype_y5">Y5</string>
+    <string name="prefs_button_single_press_action_selection_title">驻注讜诇讛 注诐 讗讬专讜注 1</string>
+    <string name="prefs_button_double_press_action_selection_title">驻注讜诇讛 注诐 讗讬专讜注 2</string>
+    <string name="prefs_button_triple_press_action_selection_title">驻注讜诇讛 注诐 讗讬专讜注 3</string>
+    <string name="prefs_button_variable_actions">讛讙讚专讜转 谞专讞讘讜转 诇诇讞讬爪转 讻驻转讜专</string>
+    <string name="prefs_button_long_press_action_selection_title">驻注讜诇讛 诇诇讞讬爪讛 讗专讜讻讛 注诇 讛讻驻转讜专</string>
+    <string name="alarm_snooze">谞讜讚谞讬拽</string>
+    <string name="error_no_location_access">讬砖 诇讛注谞讬拽 讙讬砖讛 诇诪讬拽讜诐 讻讚讬 砖讛住专讬拽讛 转注讘讜讚 讻专讗讜讬</string>
+    <string name="devicetype_itag">iTag鈥�</string>
+    <string name="pref_title_allow_high_mtu">诇讗驻砖专 MTU 讙讘讜讛</string>
+    <string name="pref_summary_allow_high_mtu">诪讙讘讬专 讗转 拽爪讘 讛讛注讘专讛, 讬砖 诪讻砖讬专讬 Android 砖诇讗 转讜诪讻讬诐 讘讝讛.</string>
+    <string name="pref_summary_sync_calendar">讛驻注诇转 讛转专讗讜转 诇讜讞 砖谞讛, 讗驻讬诇讜 诇诇讗 讞讬讘讜专</string>
+    <string name="pref_title_sync_caldendar">住谞讻专讜谉 讗讬专讜注讬 诇讜讞 砖谞讛</string>
+    <string name="hr_widget_heart_rate">讚讜驻拽</string>
+    <string name="hr_widget_steps">爪注讚讬诐</string>
+    <string name="hr_widget_date">转讗专讬讱</string>
+    <string name="hr_widget_active_minutes">讚拽讜转 驻注讬诇讜转</string>
+    <string name="hr_widget_calories">拽诇讜专讬讜转</string>
+    <string name="hr_widget_battery">住讜诇诇讛</string>
+    <string name="hr_widget_weather">诪讝讙 讗讜讜讬专</string>
+    <string name="hr_widget_nothing">讻诇讜诐</string>
+    <string name="find_lost_device_you_found_it">诪爪讗转讬!</string>
+    <string name="pref_title_force_white_color_scheme">诇讗讻讜祝 注专讻转 爪讘注讬诐 砖讞讜专 注诇 诇讘谉</string>
+    <string name="pref_summary_force_white_color_scheme">砖讬诪讜砖讬 诇讬讚讬讬诐 讻讛讜转</string>
+    <string name="find_my_phone_notification">讗讬转讜专 讛讟诇驻讜谉 砖诇讬</string>
+    <string name="notification_channel_high_priority_name">注讚讬驻讜转 讙讘讜讛讛</string>
+    <string name="pref_title_custom_deviceicon">讛爪讙转 住诪诇 讛转专讗讛 讬讬注讜讚讬 诇诪讻砖讬专</string>
+    <string name="pref_summary_custom_deviceicon">讛爪讙转 讛转专讗转 Android 转讜讗诪转 诪讻砖讬专 讘诪拽讜诐 讛住诪诇 砖诇 Gadgetbridge 讻砖讬砖 讞讬讘讜专</string>
+    <string name="hr_appname_wellness">专讜讜讞讛</string>
+    <string name="hr_appname_workout">讗讬诪讜谉</string>
+    <string name="hr_appname_stopwatch">砖注讜谉 注爪专</string>
+    <string name="hr_appname_commute">讛转谞讬讬讚讜转</string>
+    <string name="pref_title_vibration_strength">注爪诪转 讛专讟讟</string>
+    <string name="devicetype_amazfit_bips">Amazfit Bip S</string>
+    <string name="pref_title_relax_firmware_checks">讬砖 诇讛驻注讬诇 讝讗转 讗诐 诪讟专转讱 讛讬讗 诇爪专讜讘 拽讜砖讞讛 砖讗讬谞讛 诪讬讜注讚转 诇讛转拽谉 砖诇讱 (讘讗讞专讬讜转讱 讛讘诇注讚讬转)</string>
+    <string name="pref_summary_relax_firmware_checks">讛驻讞转转 讘讚讬拽讜转 讞讜诪专讛</string>
+    <string name="pref_header_auto_fetch">拽讘诇讛 讗讜讟讜诪讟讬转</string>
+    <string name="pref_qhybrid_title_widget_draw_circles">爪讬讜专 注讬讙讜诇讬 讜讬讚讙壮讟讬诐</string>
+    <string name="pref_qhybrid_save_raw_activity_files">砖诪讬专转 拽讜讘爪讬 驻注讬诇讜转 讙讜诇诪讬讬诐</string>
+    <string name="menuitem_workout">讛转注诪诇讜转</string>
+    <string name="menuitem_eventreminder">转讝讻讜专转 讗讬专讜注讬诐</string>
+    <string name="menuitem_hr">讚讜驻拽</string>
+    <string name="menuitem_pai">讘讬谞转 驻注讬诇讜转</string>
+    <string name="devicetype_watchxplus">Watch X Plus</string>
+    <string name="devicetype_watchx">Watch X</string>
+    <string name="power_mode_title">爪专讬讻转 讞砖诪诇 讘砖注讜谉</string>
+    <string name="pref_summary_notifications_and_calls_title_shake_reject">讻驻讬诇 砖诇 驻注讜诇转 讻驻转讜专 讛砖注讜谉</string>
+    <string name="power_mode_watch">砖注讜谉 讘诇讘讚</string>
+    <string name="power_mode_saving">讞讬住讻讜谉 讘讞砖诪诇</string>
+    <string name="power_mode_normal">专讙讬诇</string>
+    <string name="prefs_sensors_button_bp_calibration_sum">讬砖 诇诇讞讜抓 讻讗谉 讻讚讬 诇讛转讞讬诇 讻讬讜诇</string>
+    <string name="prefs_sensors_button_bp_calibration">讻讬讜诇</string>
+    <string name="pref_sensors_bp_calibration_high">诇讞抓 讚诐 住讬住讟讜诇讬 (讙讘讜讛)</string>
+    <string name="pref_sensors_bp_calibration_low">诇讞抓 讚诐 讚讬讗住讟讜诇讬 (谞诪讜讱)</string>
+    <string name="pref_sensors_bp_calibration">讻讬讜讜谉 诇讞抓 讚诐</string>
+    <string name="pref_title_sensors_altitude">讻讬讜讜谉 讙讜讘讛</string>
+    <string name="pref_summary_device_spec_settings_title_force_time">讗讬诇讜抓 住谞讻专讜谉 讛砖注讜谉 讘注转 讛转讞讘专讜转 诪讞讚砖. 诪讞讜讙讬诐 讗谞诇讜讙讬讬诐 注砖讜讬讬诐 诇讛爪讬讙 砖注讛 砖讙讜讬讛!</string>
+    <string name="pref_header_sensors_calibration">讻讬讜诇 讞讬讬砖谞讬诐</string>
+    <string name="pref_title_device_spec_settings_force_time">讗讬诇讜抓 住谞讻专讜谉 讛砖注讜谉</string>
+    <string name="pref_header_device_spec_settings">讛讙讚专讜转 讛转拽谉</string>
+    <string name="prefs_notifications_and_calls_shake_reject">谞讬注讜专 驻专拽 讻祝 讛讬讚 讻讚讬 诇讛转注诇诐/诇讚讞讜转 砖讬讞讜转</string>
+    <string name="pref_summary_notifications_and_calls_title_reject">讻讘讜讬 - 讛转注诇诪讜转, 驻注讬诇 - 讚讞讬讬讛</string>
+    <string name="prefs_notifications_and_calls_reject">讛转注诇诪讜转/讚讞讬讬讛 注诐 讻驻转讜专</string>
+    <string name="pref_header_notifications_and_calls_callhandling">讟讬驻讜诇 讘砖讬讞讜转</string>
+    <string name="pref_title_notifications_and_calls_repeat_on_misscall">讞讝专讛 诇诪砖讱 X 讚拽讜转</string>
+    <string name="pref_summary_notifications_and_calls_enable_misscall">讞讜讝专转 讻诇 讚拽讛</string>
+    <string name="pref_notifications_and_calls_enable_misscall">讛转专讗讛 注诇 砖讬讞讛 砖讛讜讞诪爪讛</string>
+    <string name="pref_header_notifications_and_calls">讛转专讗讜转 讜砖讬讞讜转</string>
+    <string name="pref_title_notifications_and_calls_repeat_on_call">讞讝专讛 注诇 讛转专讗讜转 注诇 砖讬讞讜转</string>
+    <string name="prefs_notifications_and_calls_continious_ring">讛转专讗讛 讘诪讛诇讱 爪诇爪讜诇 讟诇驻讜谉</string>
+    <string name="title_activity_LenovoWatch_calibration">讻讬讜诇 Watch X Plus</string>
+    <string name="pref_title_device_spec_settings_show_raw_graph">讛爪讙转 谞转讜谞讬诐 讙讜诇诪讬讬诐 讘转专砖讬诐 讛驻注讬诇讜转</string>
+    <string name="pref_theme_system">诪注专讻转</string>
+    <string name="hungarian">讛讜谞讙专讬转</string>
+    <string name="romanian">专讜诪谞讬转</string>
+    <string name="czesh">爪壮讻讬转</string>
+    <string name="swedish">砖讘讚讬转</string>
+    <string name="hebrew">注讘专讬转</string>
+    <string name="greek">讬讜讜谞讬转</string>
+    <string name="menuitem_unknown">诇讗 讬讚讜注</string>
+    <string name="bip_prefs_shotcuts_summary">谞讗 诇讘讞讜专 讗转 拽讬爪讜专讬 讛讚专讱 注诇 诪住讱 讛爪诪讬讚</string>
+    <string name="bip_prefs_shortcuts">拽讬爪讜专讬 讚专讱</string>
+    <string name="hr_widget_last_notification">讛转专讗讛 讗讞专讜谞讛</string>
+    <string name="controlcenter_set_alias">讛讙讚专转 讻讬谞讜讬</string>
+    <string name="fw_upgrade_notice_amazfit_trex">驻注讜诇讛 讝讜 转转拽讬谉 讗转 讛拽讜砖讞讛 %s 讘志Amazfit T-Rex 砖诇讱. 
+\n 
+\n谞讗 诇讜讜讚讗 讗转 讛转拽谞转 拽讜讘抓 讛志鈥�.fw 讘讛转讞诇讛, 诇讗讞专 诪讻谉 讗转 拽讜讘抓 讛志鈥�.res 讜诇讘住讜祝 拽讜讘抓 讛志鈥�.gps 讛砖注讜谉 砖诇讱 讬讜驻注诇 诪讞讚砖 诇讗讞专 讛转拽谞转 拽讜讘抓 讛志fw. 
+\n 
+\n诇转砖讜诪转 诇讘讱: 讗讬谉 爪讜专讱 讘讛转拽谞转 拽讜讘爪讬 讛志鈥�.res 讜讛志鈥�.gps 讗诐 讛诐 讻讘专 讝讛讬诐 诇讗诇讜 砖讛转拽谞转 讘注讘专. 
+\n 
+\n讛诪砖讱 讛转讛诇讬讱 讛讜讗 注诇 讗讞专讬讜转讱!</string>
+    <string name="devicetype_amazfit_trex">Amazfit T-Rex</string>
+    <string name="about_additional_contributions">转讜讚转谞讜 谞转讜谞讛 诇讻诇 讛转讜专诪讬诐 砖转专诪讜 拽讜讚, 转专讙讜诪讬诐, 转诪讬讻讛, 专注讬讜谞讜转, 诪讜讟讬讘爪讬讛, 讚讬讜讜讞讬 转拽诇讜转, 讻住祝鈥� 鉁�</string>
+    <string name="about_additional_device_support">转诪讬讻讛 讘诪讻砖讬专讬诐 谞讜住驻讬诐</string>
+    <string name="about_contributors">转讜专诪讬诐</string>
+    <string name="about_core_team_title">爪讜讜转 讛诇讬讘讛 (诪住讜讚专讬诐 诇驻讬 诪讜注讚 转专讜诪转 讛拽讜讚 讛专讗砖讜谞讛)</string>
+    <string name="about_description_generic">讞诇讜驻讛 讞讜驻砖讬转 讜谞讟讜诇转 注谞谉 诇讬讬砖讜诪讜谞讬 讛志Android 诇讞驻讬爪讬诐 注诐 讛拽讜讚 讛住讙讜专 砖诇 讛讬爪专谞讬诐.</string>
+    <string name="about_activity_title_generic">注诇 讗讜讚讜转 Gadgetbridge</string>
+    <string name="about_title">注诇 讗讜讚讜转</string>
+    <string name="pref_title_weather_summary">诪砖诪砖 诇住驻拽 诪讝讙 讛讗讜讜讬专 砖诇 LineageOS, 讙专住讗讜转 讗讞专讜转 砖诇 Android 爪专讬讻讜转 诇讛砖转诪砖 讘讬讬砖讜诪讜谞讬诐 讻诪讜 鈥炞斪ㄗ愖曌� 诪讝讙 讗讜讜讬专鈥�. 讗驻砖专 诇诪爪讜讗 诪讬讚注 谞讜住祝 讘讜讜讬拽讬 砖诇 Gadgetbridge.</string>
+    <string name="menuitem_worldclock">砖注讜谉 注讜诇诪讬</string>
+    <string name="permission_granting_mandatory">讻诇 讛讛专砖讗讜转 讛讗诇讜 谞讚专砖讜转 讜讗诐 诇讗 转讜注谞拽谞讛 讛讬爪讬讘讜转 转转注专注专</string>
+    <string name="error_version_check_extreme_caution">讗讝讛专讛: 讘讚讬拽转 驻专讟讬 讛讙专住讛 谞转拽诇讛 讘砖讙讬讗讛! 注讚讬祝 砖诇讗 诇讛诪砖讬讱! 讛讙专住讛 砖谞爪驻转讛 讛讬讗 鈥�%s鈥�</string>
+    <string name="error_background_service_reason">讛转讞诇转 砖讬专讜转 讛专拽注 谞讻砖诇讛 注拽讘 讞专讬讙讛 - 谞讗 诇诇讞讜抓 讻讗谉 诇驻专讟讬诐 谞讜住驻讬诐. \n\n砖讙讬讗讛:</string>
+    <string name="error_background_service_reason_truncated">讛转讞诇转 砖讬专讜转 讛专拽注 谞讻砖诇 注拽讘鈥�</string>
+    <string name="error_background_service">讛转讞诇转 砖讬专讜转 讛专拽注 谞讻砖诇讛</string>
+    <string name="fw_upgrade_notice_miband5">驻注讜诇讛 讝讜 转转拽讬谉 讗转 讛拽讜砖讞讛 %s 讘诪拽讜诐 讛谞讜讻讞讬转 砖注诇 讛志Mi Band 5 砖诇讱.
+\n
+\n谞讗 诇讜讜讚讗 讗转 讛转拽谞转 拽讜讘抓 讛志鈥�.fw, 讜诇讗讞专 诪讻谉 讗转 拽讜讘抓 讛志鈥�.res. 讛爪诪讬讚 砖诇讱 讬讜驻注诇 诪讞讚砖 诇讗讞专 讛转拽谞转 拽讜讘抓 讛志鈥�.fw
+\n
+\n诇转砖讜诪转 诇讘讱: 讗讬谉 爪讜专讱 诇讛转拽讬谉 讗转 拽讜讘抓 讛志鈥�.res 讗诐 讛讜讗 讝讛讛 诇拽讜讘抓 砖讛转拽谞转 讘注讘专.
+\n
+\n讛诪砖讱 诪注讘专 诇谞拽讜讚讛 讝讜 讛讜讗 注诇 讗讞专讬讜转讱!</string>
+    <string name="menuitem_stress">诇讞抓</string>
+    <string name="menuitem_cycles">诪注拽讘 诪讞讝讜专</string>
+    <string name="menuitem_breathing">谞砖讬诪讛</string>
+    <string name="devicetype_miband5">Mi Band 5</string>
+    <string name="ignore_bonded_devices_description">讛驻注诇转 讗驻砖专讜转 讝讜 转转注诇诐 诪诪讻砖讬专讬诐 砖讻讘专 讗讜讙讚讜/爪讜诪讚讜 讟专诐 讛住专讬拽讛</string>
+    <string name="ignore_bonded_devices">讛转注诇诪讜转 诪诪讻砖讬专讬诐 砖讗讜讙讚讜</string>
+    <string name="error_retrieving_devices_database">砖讙讬讗讛 讘拽讘诇转 诪讻砖讬专讬诐 诪诪住讚 讛谞转讜谞讬诐</string>
+    <string name="error_setting_alias">砖讙讬讗讛 讘讛讙讚专转 讻讬谞讜讬:</string>
+    <string name="error_exporting_device_preferences">砖讙讬讗讛 讘讬讬爪讜讗 讛注讚驻讜转 讬讬讞讜讚讬讜转 诇诪讻砖讬专</string>
+    <string name="pref_check_permission_status">讘讚讬拽转 诪爪讘 讛讛专砖讗讜转</string>
+    <string name="device_requires_key">谞讚专砖 诪驻转讞, 诇诇讞讜抓 讗专讜讻讜转 讻讚讬 诇讛拽诇讬讚讛</string>
+    <string name="device_is_currently_bonded">讻讘专 诪讗讜讙讚</string>
+    <string name="devicetype_tlw64">TLW64</string>
+    <string name="error_location_enabled_mandatory">讬砖 诇讛驻注讬诇 诪讬拽讜诐 讻讚讬 诇住专讜拽 诇讗讬转讜专 诪讻砖讬专讬诐</string>
+    <string name="companiondevice_pairing_details">诪驻注讬诇 讗转 讛转诪讬讻讛 讘志API 讛讞讚砖 诪住讜讙 CompanionDevice (转拽祝 注诇 Android 8 讜诪注诇讛) 砖诪讙讘讬专 讗转 讛讗诪讬谞讜转 讗诐 讬砖 诇讛驻注讬诇 讗转 讛砖讬专讜转 诪讞讚砖 讘专拽注, 讚讜专砖 爪讬诪讜讚 诪讞讚砖 讘讗诪爪注讜转 Gadgetbridge 讻讚讬 砖讬讬讻谞住 诇转讜拽祝</string>
+    <string name="companiondevice_pairing">爪讬诪讜讚 CompanionDevice</string>
+    <string name="require_location_provider">讬砖 诇讛驻注讬诇 诪讬拽讜诐</string>
+    <string name="pref_check_permission_status_summary">诇讘讚讜拽 讜诇讘拽砖 讛专砖讗讜转 讞住专讜转 讗驻讬诇讜 讗诐 讗讬谞谉 谞讚专砖讜转 讘讗讜转讜 讛专讙注. 讬砖 诇讛砖讘讬转 讗转 讝讛 专拽 讗诐 讛诪讻砖讬专讬诐 砖诇讱 讗讻谉 诇讗 转讜诪讻讬诐 讘转讻讜谞讜转 讛讗诇讜. 讞住讱 讘讛注谞拽转 讛专砖讗讜转 注诇讜诇 诇讛讜讘讬诇 诇转拽诇讜转!</string>
+    <string name="devicetype_pinetime_jf">PineTime (拽讜砖讞讛 诪住讜讙 JF)</string>
+    <string name="distanceMeters">诪专讞拽</string>
+    <string name="activity_summary_detail">驻专讟讬 讛驻注讬诇讜转 讛住驻讜专讟讬讘讬转</string>
+    <string name="activity_detail_show_gps_label">讛爪讙转 诪住诇讜诇 GPS</string>
+    <string name="activity_detail_end_label">住讬讜诐</string>
+    <string name="activity_detail_start_label">讛转讞诇讛</string>
+    <string name="Steps">爪注讚讬诐</string>
+    <string name="Activity">驻注讬诇讜转</string>
+    <string name="Speed">诪讛讬专讜转</string>
+    <string name="Elevation">注讬诇讜讬</string>
+    <string name="Distance">诪专讞拽</string>
+    <string name="Swimming">砖讞讬讬讛</string>
+    <string name="Strokes">转谞讜注讜转 讞转讬专讛</string>
+    <string name="bpm">住诇状讚</string>
+    <string name="minutes_km">讚拽壮/拽状诪</string>
+    <string name="seconds_km">砖谞壮/拽状诪</string>
+    <string name="calories_unit">拽诇讜专讬讜转</string>
+    <string name="laps_unit">讛拽驻讜转</string>
+    <string name="swim_style">住讙谞讜谉 砖讞讬讬讛</string>
+    <string name="swolf_index">爪讬讜谉 swolf</string>
+    <string name="seconds">砖谞讬讜转</string>
+    <string name="strokes_unit">讞转讬专讜转</string>
+    <string name="strokes_second">讞转讬专讜转/砖谞壮</string>
+    <string name="km_h">拽诪状砖</string>
+    <string name="meters_second">诪壮/砖谞壮</string>
+    <string name="steps_unit">爪注讚讬诐</string>
+    <string name="averageStrokesPerSecond">转谞讜注讜转 讞转讬专讛 讘诪诪讜爪注</string>
+    <string name="averageStrokeDistance">诪专讞拽 转谞讜注转 讞转讬专讛 诪诪讜爪注</string>
+    <string name="cm">住状诪</string>
+    <string name="meters">诪壮</string>
+    <string name="flatSeconds">砖讟讜讞</string>
+    <string name="ascentSeconds">注诇讬讬讛</string>
+    <string name="laps">讛拽驻讜转</string>
+    <string name="swimStyle">住讙谞讜谉 砖讞讬讬讛</string>
+    <string name="swolfIndex">SWOLF</string>
+    <string name="averageLapPace">拽爪讘 诪诪讜爪注 诇讛拽驻讛</string>
+    <string name="activity_detail_duration_label">诪砖讱</string>
+    <string name="averageStride">驻住讬注讛 诪诪讜爪注转</string>
+    <string name="averageKMPaceSeconds">拽爪讘</string>
+    <string name="averageHR">讚讜驻拽</string>
+    <string name="totalStride">住讱 讻诇 讛驻住讬注讜转</string>
+    <string name="minPace">讛拽爪讘 讛讗讟讬 讘讬讜转专</string>
+    <string name="maxPace">讛拽爪讘 讛诪讛讬专 讘讬讜转专</string>
+    <string name="maxSpeed">诪专讘讬</string>
+    <string name="caloriesBurnt">拽诇讜专讬讜转</string>
+    <string name="activeSeconds">驻注讬诇</string>
+    <string name="steps">爪注讚讬诐</string>
+    <string name="minAltitude">诪讝注专讬</string>
+    <string name="maxAltitude">诪专讘讬</string>
+    <string name="descentMeters">讘诪讜专讚</string>
+    <string name="ascentMeters">讘诪注诇讛</string>
+    <string name="seconds_m">砖谞壮/诪壮</string>
+    <string name="prefs_events_forwarding_action_title">讛专爪转 驻注讜诇讛</string>
+    <string name="prefs_events_forwarding_broadcast_title">讛驻爪转 讛讜讚注讛</string>
+    <string name="prefs_events_forwarding_startnonwear">讻砖诇讗 谞注谞讚</string>
+    <string name="prefs_events_forwarding_wokeup">注诐 讛砖讻诪讛</string>
+    <string name="prefs_events_forwarding_fellsleep">注诐 讛讬专讚诪讜转</string>
+    <string name="prefs_events_forwarding_title">驻注讜诇讜转 诪讻砖讬专</string>
+    <string name="prefs_events_forwarding_summary">诇讛砖转诪砖 讘讗讬专讜注讬 讛诪讻砖讬专 讻讚讬 诇讙专讜专 驻注讜诇讜转 讜砖讬讚讜专讬 Android</string>
+    <string name="activity_type_yoga">讬讜讙讛</string>
+    <string name="activity_type_jump_roping">拽驻讬爪讛 讘讞讘诇</string>
+    <string name="activity_type_elliptical_trainer">讗诇讬驻讟讬拽诇 注诐 讬讚讬讜转</string>
+    <string name="activity_type_indoor_cycling">专讻讬讘讛 讘转讜讱 诪讘谞讛</string>
+    <string name="activity_type_swimming_openwater">砖讞讬讬讛 讘诪讬诐 驻转讜讞讬诐</string>
+    <string name="activity_filter_apply_filter">讛讞诇转 诪住谞谉</string>
+    <string name="activity_filter_filter_title">诪住谞谉</string>
+    <string name="activity_filter_reset_filter">讗讬驻讜住 讛诪住谞谉</string>
+    <string name="activity_filter_date_to">住讬讜诐</string>
+    <string name="activity_filter_date_from">讛转讞诇讛</string>
+    <string name="pref_header_filter">诪住谞谉 驻注讬诇讜讬讜转 住驻讜专讟讬讘讬讜转</string>
+    <string name="medley">诪注讜专讘</string>
+    <string name="backstroke">讙讘</string>
+    <string name="freestyle">讞讜驻砖讬</string>
+    <string name="breaststroke">讞讝讛</string>
+    <string name="activity_summaries_statistics">住讟讟讬住讟讬拽讛</string>
+    <string name="pref_header_statistics">住讟讟讬住讟讬拽转 驻注讬诇讜转 住驻讜专讟讬讘讬转</string>
+    <string name="km">拽状诪</string>
+    <string name="activity_filter_name_contains">转讜讜讬转</string>
+    <string name="activity_summary_edit_name_title">注专讬讻转 讛转讜讜讬转</string>
+    <string name="activity_summaries_all_activities">讻诇 讛驻注讬诇讜讬讜转</string>
+    <string name="activity_summary_yesterday">讗转诪讜诇</string>
+    <string name="activity_summary_today">讛讬讜诐</string>
+    <string name="devicetype_sg2">Lemfo SG2</string>
+    <string name="activity_summaries_all_devices">讻诇 讛诪讻砖讬专讬诐</string>
+    <string name="sports_activity_quick_filter_select">转拽讜驻讛</string>
+    <string name="sports_activity_quick_filter_30days">30 讬诪讬诐</string>
+    <string name="sports_activity_quick_filter_7days">7 讬诪讬诐</string>
+    <string name="sports_activity_quick_filter_last_month">讞讜讚砖 砖注讘专</string>
+    <string name="sports_activity_quick_filter_this_month">讛讞讜讚砖</string>
+    <string name="sports_activity_quick_filter_last_week">砖讘讜注 砖注讘专</string>
+    <string name="sports_activity_quick_filter_this_week">讛砖讘讜注</string>
+    <string name="no">诇讗</string>
+    <string name="yes">讻谉</string>
+    <string name="activity_filter_individual_items">驻专讬讟讬诐 砖谞讘讞专讜 驻专讟谞讬转</string>
+    <string name="addto_filter">讛讜住驻讛 诇诪住谞谉</string>
+    <string name="averageSpeed">诪讛讬专讜转 诪诪讜爪注转</string>
+    <string name="activity_type_badminton">讘讚诪讬谞讟讜谉</string>
+    <string name="activity_type_pingpong">驻讬谞讙 驻讜谞讙</string>
+    <string name="activity_type_basketball">讻讚讜专住诇</string>
+    <string name="activity_type_cricket">拽专讬拽讟</string>
+    <string name="activity_type_rowing_machine">诪讻讜谞转 讞转讬专讛</string>
+    <string name="activity_type_soccer">讻讚讜专讙诇</string>
+    <string name="activity_filter_to_placeholder">讛讬讜诐</string>
+    <string name="activity_filter_from_placeholder">诪专讞拽 砖注讘专</string>
+    <string name="about_links">拽讬砖讜专讬诐</string>
+    <string name="activity_prefs_chart_min_session_length">诪砖讱 诪讝注专讬 诇驻注讬诇讜转 (讚拽讜转)</string>
+    <string name="activity_prefs_chart_max_idle_phase_length">诪砖讱 讛砖讛讬讬讛 诇讛驻专讚转 驻注讬诇讜讬讜转 (讚拽讜转)</string>
+    <string name="activity_prefs_chart_min_steps_per_minute">讻诪讜转 诪讝注专讬转 砖诇 爪注讚讬诐 诇讚拽讛 诇讝讬讛讜讬 驻注讬诇讜转</string>
+    <string name="activity_prefs_chart_min_steps_per_minute_for_run">讻诪讜转 诪讝注专讬转 砖诇 爪注讚讬诐 诇讚拽讛 诇讝讬讛讜讬 专讬爪讛</string>
+    <string name="charts_activity_list">专砖讬诪转 驻注讬诇讜讬讜转</string>
+    <string name="chart_get_active_and_synchronize">讬砖 诇注砖讜转 驻注讬诇讜转 讻诇砖讛讬 讜诇住谞讻专谉 讗转 讛讛转拽谉.</string>
+    <string name="chart_no_active_data">诇讗 讝讜讛讜 驻注讬诇讜讬讜转.</string>
+    <string name="devicetype_lefun">Lefun</string>
+    <string name="lefun_prefs_interface_language_title">砖驻转 讛诪谞砖拽</string>
+    <string name="lefun_prefs_antilost_summary">讛爪诪讬讚 讬专讜讟讜 讗诐 讛讟诇驻讜谉 砖诇讱 诪转谞转拽 诪讛爪诪讬讚</string>
+    <string name="lefun_prefs_antilost_title">诪讜谞注 讗讬讘讜讚</string>
+    <string name="lefun_prefs_hydration_reminder_interval_title">讛驻专砖 转讝讻讜专转 砖转讬讬讛 (讘讚拽讜转)</string>
+    <string name="lefun_prefs_hydration_reminder_summary">讛爪诪讬讚 讬专讟讜讟 讻讚讬 诇讛讝讻讬专 诇讱 诇砖转讜转 诪讬诐</string>
+    <string name="lefun_prefs_hydration_reminder_title">转讝讻讜专转 砖转讬讬讛</string>
+    <string name="devicetype_sonyswr12">Sony SWR12</string>
+    <string name="sonyswr12_settings_title">Sony SWR12 讛讙讚专讜转</string>
+    <string name="sonyswr12_settings_low_vibration">专讟讟 谞诪讜讱 诪讜驻注诇</string>
+    <string name="sonyswr12_settings_stamina">诪爪讘 讞讬住讻讜谉 讘讞砖诪诇 驻讜注诇</string>
+    <string name="sonyswr12_settings_alarm_interval">诪专讜讜讞 讗讝注拽讛 讞讻诐 讘讚拽讜转</string>
+    <string name="sonyswr12_settings_low_vibration_summary">讗驻砖专 注讜爪诪转 专讟讟 谞诪讜讻讛 讘诇讛拽讛</string>
+    <string name="sonyswr12_settings_stamina_summary">诪爪讘 讞讬住讻讜谉 讘讞砖诪诇 诪讻讘讛 诪讚讬讚讛 讗讜讟讜诪讟讬转 转拽讜驻转讬转 砖诇 讚讜驻拽 讜讘讻讱 诪讙讚讬诇 讗转 讝诪谉 讛注讘讜讚讛</string>
+    <string name="sonyswr12_settings_alarm_interval_summary">诪专讜讜讞 讗讝注拽讛 讞讻诐 讛讜讗 诪专讜讜讞 诇驻谞讬 讛讛转专讗讛 讛诪讜转拽谞转. 讘诪专讜讜讞 讝讛 诪讻砖讬专 诪谞住讛 诇讗转专 讗转 砖诇讘 讛砖讬谞讛 讛拽诇 讘讬讜转专 诇诪砖转诪砖 注专</string>
+    <string name="baseAltitude">讘住讬住 诇讞讬砖讜讘 专讜诐</string>
+    <string name="devicetype_nut_mini">Nut mini</string>
+    <string name="descentSeconds">讬专讬讚讛</string>
+    <string name="activity_error_no_app_for_png">讻讚讬 诇砖转祝 讗转 爪讬诇讜诐 讛诪住讱, 注诇讬讱 诇讛转拽讬谉 讬讬砖讜诪讜谉 砖讬讻讜诇 诇讟驻诇 讘拽讜讘爪讬 转诪讜谞讛.</string>
+    <string name="firmware_update_progress">诪转讘爪注转 讛注诇讗讛
+\n%1d%% 讘志%.2f 拽住诇状砖 (诪诪讜爪注 %.2f 拽住诇状砖)
+\n讞诇拽 %1d 诪转讜讱 %1d</string>
+    <string name="devicestatus_upload_failed">讛讛注诇讗讛 谞讻砖诇讛</string>
+    <string name="devicestatus_upload_aborted">讛讛注诇讗讛 讘讜讟诇讛!</string>
+    <string name="devicestatus_upload_validating">讛讛注诇讗讛 谞讘讚拽转</string>
+    <string name="devicestatus_upload_completed">讛讛注诇讗讛 讛讜砖诇诪讛</string>
+    <string name="devicestatus_disconnected">讛诪讻砖讬专 讛转谞转拽!</string>
+    <string name="devicestatus_disconnecting">讛诪讻砖讬专 诪转谞转拽!</string>
+    <string name="devicestatus_upload_starting">讛注诇讗讛 诪转讞讬诇讛</string>
+    <string name="devicestatus_upload_started">讛讞诇讛 讛注诇讗讛</string>
+    <string name="devicestatus_connected">讛诪讻砖讬专 诪讞讜讘专</string>
+    <string name="devicestatus_connecting">讛诪讻砖讬专 诪转讞讘专</string>
+    <string name="pref_title_lower_button_function_double">讻驻转讜专 转讞转讜谉 讻驻讜诇</string>
+    <string name="pref_title_middle_button_function_double">讻驻转讜专 讗诪爪注讬 讻驻讜诇</string>
+    <string name="pref_title_upper_button_function_double">讻驻转讜专 注诇讬讜谉 讻驻讜诇</string>
+    <string name="pref_title_lower_button_function_long">讻驻转讜专 转讞转讜谉 讗专讜讱</string>
+    <string name="pref_title_middle_button_function_long">讻驻转讜专 讗诪爪注讬 讗专讜讱</string>
+    <string name="pref_title_upper_button_function_long">讻驻转讜专 注诇讬讜谉 讗专讜讱</string>
+    <string name="pref_title_lower_button_function_short">讻驻转讜专 转讞转讜谉 拽爪专</string>
+    <string name="pref_title_middle_button_function_short">讻驻转讜专 讗诪爪注讬 拽爪专</string>
+    <string name="pref_title_upper_button_function_short">讻驻转讜专 注诇讬讜谉 拽爪专</string>
+    <string name="activity_prefs_step_length_cm">讗讜专讱 爪注讚 讘住状诪</string>
+    <string name="devicetype_amazfit_band5">Amazfit Band 5</string>
+    <string name="menuitem_spo2">专讬讜讜讬 讞诪爪谉 讘讚诐</string>
+    <string name="qhybrid_calibration_align_hint">谞讬转谉 诇讻讜讜谉 讗转 讛诪讞讜讙讬诐 讘注讝专转 讛讻驻转讜专讬诐 砖诇讛诇谉 诇志12:00.</string>
+    <string name="gps_track">诪住诇讜诇 GPS</string>
+    <string name="about_version">讙专住讛 %s</string>
+    <string name="devicetype_amazfit_bips_lite">Amazfit Bip S Lite</string>
+    <string name="charts_legend_heartrate_average">讚讜驻拽 诪诪讜爪注</string>
+    <string name="charts_min_max_heartrate_popup">讚讜驻拽 谞诪讜讱: %1$d
+\n讚讜驻拽 讙讘讜讛: %2$d
+\n讞讜讝拽 转谞讜注讛: %3$s</string>
+    <string name="find_lost_device_message">诇讞驻砖 讗讞专 %1$s\?</string>
+    <string name="devicetype_amazfit_gtr2">Amazfit GTR 2</string>
+    <string name="menuitem_stopwatch">砖注讜谉 注爪专</string>
+    <string name="menuitem_dnd">诇讗 诇讛驻专讬注</string>
+    <string name="menuitem_alexa">Alexa</string>
+    <string name="menuitem_takephoto">砖诇讬讟讛 讘诪爪诇诪讛</string>
+    <string name="menuitem_mutephone">讛砖转拽转 讛讟诇驻讜谉</string>
+    <string name="menuitem_findphone">讗讬转讜专 讛讟诇驻讜谉</string>
+    <string name="movement_intensity">讞讜讝拽 转谞讜注讛</string>
+    <string name="activity_list_summary_activities">驻注讬诇讜讬讜转</string>
+    <string name="activity_list_summary_intensity">讞讜讝拽
+\n讛转谞讜注讛</string>
+    <string name="activity_list_summary_active_steps">爪注讚讬诐 讘驻注讬诇讜转</string>
+    <string name="activity_list_summary_active_time">讝诪谉 驻注讬诇讜转</string>
+    <string name="activity_list_summary_distance">诪专讞拽</string>
+    <string name="devicetype_amazfit_gts2">Amazfit GTS 2</string>
+    <string name="devicetype_casiogbx100">Casio GBX-100鈥�</string>
+    <string name="prefs_charts_tabs_summary">诇砖讜谞讬转 转专砖讬诪讬诐</string>
+    <string name="prefs_charts_tabs">诇砖讜谞讬转</string>
+    <string name="devicetype_amazfit_bipu">Amazfit Bip U</string>
+    <string name="menuitem_pomodoro">注讜拽讘 驻讜诪讜讚讜专讜</string>
+    <string name="menuitem_sleep">砖讬谞讛</string>
+    <string name="menuitem_goal">讬注讚 驻注讬诇讜转</string>
+    <string name="pref_summary_connected_advertisement">讛讜驻讱 讗转 讛讛转拽谉 诇驻转讜讞 诇讙讬诇讜讬 讚专讱 Bluetooth 讗驻讬诇讜 讻砖讛讜讗 诪讞讜讘专</string>
+    <string name="pref_title_connected_advertisement">讙诇讜讬 讻砖诪讞讜讘专</string>
+    <string name="prefs_autoremove_message">诇讛住讬专 讛转专讗讜转 诪住专讜谞讬诐 讗讜讟讜诪讟讬转</string>
+    <string name="prefs_fake_ring_duration">爪诇爪讜诇 诪转诪砖讱 诪讝讜讬祝</string>
+    <string name="prefs_key_vibration">专讟讟 诪拽砖讬诐</string>
+    <string name="prefs_operating_sounds">爪诇讬诇讬 讛驻注诇讛</string>
+    <string name="prefs_autolight">转讗讜专讛 讗讜讟讜诪讟讬转</string>
+    <string name="discovery_entered_invalid_authkey">诪驻转讞 讛讗讬诪讜转 讛住讜讚讬 砖诪住专转 讗讬谞讜 转拽讬谉! 诇讞讬爪讛 讗专讜讻讛 注诇 讛诪讻砖讬专 诇注专讬讻讛.</string>
+    <string name="fw_upgrade_notice_amazfitvergel">驻注讜诇讛 讝讜 转转拽讬谉 讗转 讛拽讜砖讞讛 %s 讘诪拽讜诐 讝讜 砖讻专讙注 诪讜转拽谞转 讘志Amazfit Verge Lite 砖诇讱.
+\n
+\n谞讗 诇讜讜讚讗 讗转 讛转拽谞转 拽讜砖讞转 讛志鈥�.fw 讘讛转讞诇讛, 诇讗讞专 诪讻谉 讗转 拽讜讘抓 讛志鈥�.res 讜诇讘住讜祝 拽讜讘抓 讛志鈥�.gps 讛砖注讜谉 砖诇讱 讬讜驻注诇 诪讞讚砖 诇讗讞专 讛转拽谞转 拽讜讘抓 讛志fw.
+\n
+\n诇转砖讜诪转 诇讘讱: 讗讬谉 爪讜专讱 讘讛转拽谞转 拽讜讘爪讬 讛志鈥�.res 讜讛志鈥�.gps 讗诐 讛诐 讻讘专 讝讛讬诐 诇讗诇讜 砖讛转拽谞转 讘注讘专.
+\n
+\n讛诪砖讱 讛转讛诇讬讱 讛讜讗 注诇 讗讞专讬讜转讱!</string>
+    <string name="devicetype_amazfit_vergel">Amazfit Verge Lite</string>
+    <string name="activity_DB_ShowContentButton">讛爪讙转 转讜讻谉 转讬拽讬讬转 讬讬讘讜讗/讬讬爪讜讗</string>
+    <string name="activity_data_management_directory_content_title">讬讬爪讜讗/讬讬讘讜讗 转讜讻谉 转讬拽讬讬讛</string>
+    <string name="widget_settings_select_device_title">谞讗 诇讘讞讜专 诪讻砖讬专</string>
+    <string name="dbmanagementactivity_export_confirmation">诇讬讬爪讗 谞转讜谞讬诐\? 谞转讜谞讬 讛驻注讬诇讜转 砖讬讜爪讗讜 拽讜讚诐 诇讻谉 (讗诐 讬砖 讻讗诇讜) 讜讛讛注讚驻讜转 讬讬讚专住讜.</string>
+    <string name="dbmanagementactivity_export_data_title">诇讬讬爪讗 谞转讜谞讬诐\?</string>
+    <string name="devicetype_amazfit_bipupro">Amazfit Bip U Pro</string>
+    <string name="activity_db_management_clean_export_directory_text">讛拽讘爪讬诐 砖讘转讬拽讬讬讛 讛讬讬爪讜讗/讬讬讘讜讗 讝诪讬谞讬诐 诇讻诇 讬讬砖讜诪讜谉 讘诪讻砖讬专 砖诇讱. 讬讻讜诇 诇讛讬讜转 砖讻讚讗讬 诇讱 诇讛住讬专 讗转 讛拽讘爪讬诐 讛讗诇讜 诇讗讞专 住谞讻专讜谉 讗讜 讙讬讘讜讬. 谞讗 诇讜讜讚讗 砖讬砖 诇讱 讙讬讘讜讬 讘讟专诐 诪讞讬拽转诐. 拽讜讘爪讬 GPX, 转转志转讬拽讬讜转 讜拽讜讘抓 诪住讚 谞转讜谞讬诐 砖讬讜爪讗 讗讜讟讜诪讟讬转 (讗诐 拽讬讬诐) 诇讗 讬讬诪讞拽讜. 讛谞转讬讘 诇转讬拽讬讬转 讛讬讬爪讜讗/讬讬讘讜讗 讛讜讗:</string>
+    <string name="dbmanagementactivity_export_finished">讛诪讞讬拽讛 讛讜砖诇诪讛</string>
+    <string name="activity_DB_clean_export_directory_warning_message">讘讗诪转 诇诪讞讜拽 讗转 讛拽讘爪讬诐 砖讘转讬拽讬讬转 讬讬爪讜讗/讬讬讘讜讗\?</string>
+    <string name="activity_DB_clean_export_directory_warning_title">诇诪讞讜拽 拽讘爪讬诐 讘转讬拽讬讬转 讬讬爪讜讗/讬讬讘讜讗\?</string>
+    <string name="activity_db_management_clean_export_directory_label">诪讞讬拽转 拽讘爪讬诐 讘转讬拽讬讬转 讬讬爪讜讗/讬讬讘讜讗</string>
+    <string name="dbmanagementactivity_error_cleaning_export_directory">砖讙讬讗讛 讘诪讞讬拽转 拽讘爪讬诐 诪转讬拽讬讬转 讛讬讬爪讜讗 %1$s</string>
+    <string name="devicetype_amazfit_neo">Amazfit Neo</string>
+    <string name="activity_type_strength_training">讗讬诪讜谉 讻讜讞</string>
+    <string name="mi">诪讬讬诇</string>
+    <string name="minutes_mi">讚拽讜转 诇诪讬讬诇</string>
+    <string name="mi_h">诪讬讬诇 诇砖注讛</string>
+    <string name="ft">专讙诇</string>
+    <string name="devicetype_amazfit_gts2_mini">Amazfit GTS 2 Mini</string>
+    <string name="devicetype_zepp_e">Zepp E</string>
+    <string name="devicetype_amazfit_gtr2e">Amazfit GTR 2e</string>
+    <string name="devicetype_waspos">Wasp-os</string>
+    <string name="battery_detail_activity_title">驻专讟讬诐 注诇 讛住讜诇诇讛</string>
+    <string name="devicetype_amazfit_gts2e">Amazfit GTS 2e</string>
+    <string name="devicetype_amazfit_x">Amazfit X</string>
+    <string name="fw_upgrade_notice_amazfitx">驻注讜诇讛 讝讜 转转拽讬谉 讗转 讛拽讜砖讞讛 %s 讘志Amazfit X 砖诇讱.
+\n
+\n谞讗 诇讜讜讚讗 讗转 讛转拽谞转 拽讜讘抓 讛志鈥�.fw 讘讛转讞诇讛 讜诇讗讞专 诪讻谉 讗转 拽讜讘抓 讛志鈥�.res 讛砖注讜谉 砖诇讱 讬讜驻注诇 诪讞讚砖 诇讗讞专 讛转拽谞转 拽讜讘抓 讛志fw.
+\n
+\n诇转砖讜诪转 诇讘讱: 讗讬谉 爪讜专讱 讘讛转拽谞转 拽讜讘抓 讛志鈥�.res 讗诐 讛讜讗 讻讘专 讝讛讛 诇讝讛 砖讛转拽谞转 讘注讘专.
+\n
+\n讛诪砖讱 讛转讛诇讬讱 讛讜讗 注诇 讗讞专讬讜转讱!</string>
+    <string name="calendar_year">砖谞讛</string>
+    <string name="calendar_six_months">6 讞讜讚砖讬诐</string>
+    <string name="calendar_month">讞讜讚砖</string>
+    <string name="calendar_two_weeks">砖讘讜注讬讬诐</string>
+    <string name="calendar_week">砖讘讜注</string>
+    <string name="calendar_day">讬讜诐</string>
+    <string name="battery_level">专诪转 讛住讜诇诇讛</string>
+    <string name="show_ongoing_activity">讛爪讙转 讞诇讜谞讬转 驻注讬诇讜转 谞讜讻讞讬转</string>
+    <string name="dialog_hide">讛住转专讛</string>
+    <string name="calendar_three_months">3 讞讜讚砖讬诐</string>
+    <string name="fossil_hr_auth_failed">讛讗讬诪讜转 谞讻砖诇, 讛讬讻讜诇讜转 转讜讙讘诇谞讛</string>
+    <string name="fossil_hr_unavailable_unauthed">诇讗 讝诪讬谉 讘诪爪讘 讘诇转讬志诪讗讜诪转</string>
+    <string name="fossil_hr_synced_activity_data">谞转讜谞讬 驻注讬诇讜转 诪住讜谞讻专谞讬诐</string>
+    <string name="fossil_hr_commute_processing">讛讘拽砖讛 讘讛诇讬讻讬 注讬讘讜讚鈥屸��</string>
+    <string name="qhybrid_title_calibration">讻讬讜诇</string>
+    <string name="qhybrid_title_apps_management">谞讬讛讜诇 讬讬砖讜诪讜谞讬诐</string>
+    <string name="qhybrid_title_file_management">谞讬讛讜诇 拽讘爪讬诐</string>
+    <string name="qhybrid_title_background_image">转诪讜谞转 专拽注</string>
+    <string name="qhybrid_title_apps">讬讬砖讜诪讜谞讬诐</string>
+    <string name="devicetype_um25">UM-25</string>
+    <string name="fossil_hr_warning_firmware_too_new">讞诇拽 诪讛驻讜谞拽爪讬讜转 诪讜砖讘转讜转 讻讬讜讜谉 砖讛拽讜砖讞讛 砖诇 讛砖注讜谉 讞讚砖讛 诪讚讬</string>
+    <string name="prefs_sounds_summary">诇讛讙讚讬专 诪转讬 讛讛转拽谉 讬爪驻爪祝</string>
+    <string name="prefs_sounds">爪诇讬诇讬诐</string>
+    <string name="fw_upgrade_notice_amazfitneo">驻注讜诇讛 讝讜 转转拽讬谉 讗转 讛拽讜砖讞讛 %s 注诇 讛志Amazfit Neo 砖诇讱.
+\n
+\n讛砖注讜谉 砖诇讱 讬讜驻注诇 诪讞讚砖 诇讗讞专 讛转拽谞转 拽讜讘抓 讛志鈥�.fw
+\n
+\n讛诪砖讱 讛转讛诇讬讱 讛讜讗 注诇 讗讞专讬讜转讱!</string>
+    <string name="menuitem_temperature">讟诪驻专讟讜专讛</string>
+    <string name="menuitem_widgets">讜讬讚讙壮讟讬诐</string>
+    <string name="menuitem_events">讗讬专讜注讬诐</string>
+    <string name="devicetype_miband6">Mi Band 6</string>
+    <string name="fw_upgrade_notice_miband6">驻注讜诇讛 讝讜 转转拽讬谉 讗转 讛拽讜砖讞讛 %s 讘志Mi Band 6 砖诇讱.
+\n
+\n谞讗 诇讜讜讚讗 讗转 讛转拽谞转 拽讜讘抓 讛志鈥�.fw 讘讛转讞诇讛, 诇讗讞专 诪讻谉 讗转 拽讜讘抓 讛志鈥�.res 讛砖注讜谉 砖诇讱 讬讜驻注诇 诪讞讚砖 诇讗讞专 讛转拽谞转 拽讜讘抓 讛志fw.
+\n
+\n诇转砖讜诪转 诇讘讱: 讗讬谉 爪讜专讱 讘讛转拽谞转 拽讜讘抓 讛志鈥�.res 讗诐 讛讜讗 讻讘专 讝讛讛 诇讝讛 砖讛转拽谞转 讘注讘专.
+\n
+\n讛诪砖讱 讛转讛诇讬讱 讛讜讗 注诇 讗讞专讬讜转讱!</string>
+    <string name="pref_summary_canned_messages_dismisscall">讚讞讬讬转 砖讬讞讜转 讚专讱 讛砖注讜谉 注诐 诪住专讜谉</string>
+    <string name="pref_summary_canned_messages_set">诇砖诇讜讞 讗转 讛讛讜讚注讜转 砖诪讜讙讚专讜转 诇讛诇谉 讗诇 讛砖注讜谉 砖诇讱</string>
+    <string name="qhybrid_summary_calibration">讻讬讜诇 诪讞讜讙讬 讛砖注讜谉</string>
+    <string name="qhybrid_summary_file_management">讛注诇讗讛 讜讛讜专讚讛 砖诇 拽讘爪讬诐</string>
+    <string name="pref_summary_physical_buttons">讛讙讚专转 讗讜驻谉 驻注讜诇转 讻驻转讜专讬 讛讞讜诪专讛 砖讘砖注讜谉</string>
+    <string name="pref_title_physical_buttons">讻驻转讜专讬 讞讜诪专讛</string>
+    <string name="kind_agps_bundle">诪讗讙讚 AGPS</string>
+    <string name="qhybrid_calibration_100_steps">100 爪注讚讬诐</string>
+    <string name="qhybrid_calibration_10_steps">10 爪注讚讬诐</string>
+    <string name="qhybrid_calibration_1_step">爪注讚 讗讞讚</string>
+    <string name="qhybrid_calibration_clockwise">注诐 讻讬讜讜谉 讛砖注讜谉</string>
+    <string name="qhybrid_calibration_counterclockwise">谞讙讚 讻讬讜讜谉 讛砖注讜谉</string>
+    <string name="fossil_hr_new_action">驻注讜诇讛 讞讚砖讛</string>
+    <string name="fossil_hr_new_action_cancel">讘讬讟讜诇</string>
+    <string name="fossil_hr_edit_action">注专讬讻转 驻注讜诇讛</string>
+    <string name="fossil_hr_edit_action_delete">诪讞讬拽讛</string>
+    <string name="qhybrid_watchface_configuration_old_firmware">诪住讱 讛讙讚专讜转 驻谞讬 砖注讜谉 诇砖注讜谞讬诐 注诐 讙专住转 拽讜砖讞讛 DN1.0.2.19r 讜诪讟讛</string>
+    <string name="qhybrid_title_watchface">讛讙讚专讜转 驻谞讬 讛砖注讜谉</string>
+    <string name="watchface_install_info">驻谞讬 讛砖注讜谉 讛讘讗讬诐 诪讜注诪讚讬诐 诇讛转拽谞讛:
+\n
+\n%1$s
+\n讙专住讛 %2$s 诪讗转 %3$s
+\n</string>
+    <string name="fossil_hr_commute_actions_explanation">讛驻注讜诇讜转 砖诪讜驻讬注讜转 讻讗谉 转讜驻注谞讛 讘讬讬砖讜诪讜谉 讛转注讘讜专讛 讘砖注讜谉 砖诇讱. 讘讜讜讬拽讬 讬砖 诪讬讚注 注诇 讗讜驻谉 讛讟讬驻讜诇 讘志Intents 砖诪讬讜爪专讬诐 注诇 讬讚讬 讛驻注讜诇讜转 讛讗诇讜.</string>
+    <string name="notification_channel_low_battery_name">住讜诇诇讛 讞诇砖讛</string>
+    <string name="notification_channel_transfer_name">讛注讘专转 谞转讜谞讬诐</string>
+    <string name="qhybrid_pref_summary_external_intents">诪讗驻砖专 诇讬讬砖讜诪讜谞讬 Android 讗讞专讬诐 诇讛注诇讜转/诇讚专讜住 拽讘爪讬诐</string>
+    <string name="qhybrid_pref_title_external_intents">诇讗驻砖专 intents 讞讬爪讜谞讬讬诐 诪住讜讻谞讬诐</string>
+    <string name="pref_summary_developer_settings">讛讙讚专讜转 讜讬讻讜诇讜转 诇砖讬诪讜砖诐 砖诇 诪转讻谞转讬诐</string>
+    <string name="pref_title_developer_settings">讛讙讚专讜转 驻讬转讜讞</string>
+    <string name="qhybrid_pref_summary_actions">驻注讜诇讜转 诇讬讬砖讜诪讜谉 讛转注讘讜专讛</string>
+    <string name="qhybrid_pref_title_actions">驻注讜诇讜转</string>
+    <string name="devicetype_amazfit_trex_pro">Amazfit T-Rex Pro</string>
+    <string name="menuitem_barometer">诪讚 诇讞抓</string>
+    <string name="pref_theme_black_background">诇讛砖转诪砖 讘专拽注 砖讞讜专 讘注专讻转 注讬爪讜讘 讻讛讛</string>
+    <string name="gpx_receiver_overwrite_some_files">讞诇拽 诪讛拽讘爪讬诐 讻讘专 拽讬讬诪讬诐. 诇讚专讜住\?</string>
+    <string name="gpx_receiver_files_received">拽讜讘爪讬 GPX 砖讛转拽讘诇讜:</string>
+    <string name="gpx_receiver_activity_title">诪拽诇讟 GPX 诇志Gadgetbridge</string>
+    <string name="activity_summary_detail_editing_gpx_track">注专讬讻转 诪住诇讜诇 GPX 诪拽讜砖专</string>
+    <string name="activity_summary_detail_clear_gpx_track">诪讞讬拽转 诪住诇讜诇 GPX</string>
+    <string name="activity_summary_detail_select_gpx_track">讘讞讬专转 诪住诇讜诇 GPX</string>
+    <string name="set">讛讙讚专讛</string>
+    <string name="watchface_setting_wrist_flick_duration">诪砖讱 (讘诪讬诇讬砖谞讬讜转)</string>
+    <string name="watchface_setting_wrist_flick_minute">诪讞讜讙 讛讚拽讜转 (鈥�-360 注讚 360)</string>
+    <string name="watchface_setting_wrist_flick_hour">诪讞讜讙 讛砖注讜转 (鈥�-360 注讚 360)</string>
+    <string name="watchface_setting_wrist_flick_move_relative">讛讝讝转 讛诪讞讜讙讬诐 讘讬讞住 诇砖注讛</string>
+    <string name="watchface_setting_desc_wrist_flick">(讻讚讬 诇讛砖讘讬转 诇讞诇讜讟讬谉, 讬砖 诇讛驻注讬诇 转谞讜注讛 讬讞住讬转 讜诇讛讙讚讬专 讗转 讻诇 讛注专讻讬诐 诇志0)</string>
+    <string name="watchface_setting_display_refresh_partial">专注谞讜谉 讞诇拽讬 (讘讚拽讜转)</string>
+    <string name="watchface_setting_display_refresh_full">专注谞讜谉 诪诇讗 (讘讚拽讜转)</string>
+    <string name="watchface_setting_title_display_refresh_timeout">转讜诐 讝诪谉 专注谞讜谉 讛转爪讜讙讛</string>
+    <string name="watchface_dialog_title_settings">讛讙讚专讜转 驻谞讬 砖注讜谉</string>
+    <string name="watchface_dialog_widget_color_black">砖讞讜专</string>
+    <string name="watchface_dialog_widget_color_white">诇讘谉</string>
+    <string name="watchface_dialog_widget_color">爪讘注</string>
+    <string name="qhybrid_title_watchface_designer">诪注爪讘 驻谞讬 讛砖注讜谉</string>
+    <string name="watchface_dialog_widget_preset_right">讬诪讬谉</string>
+    <string name="watchface_dialog_widget_preset_left">砖诪讗诇</string>
+    <string name="watchface_dialog_widget_preset_bottom">转讞转讜谉</string>
+    <string name="watchface_dialog_widget_preset_top">注诇讬讜谉</string>
+    <string name="watchface_dialog_widget_presets">注专讻讜转 诪讬拽讜诐</string>
+    <string name="watchface_dialog_widget_y_coordinate">谞拽讜讚讛 讘爪讬专 Y鈥� (240 诇讻诇 讛讬讜转专)</string>
+    <string name="watchface_dialog_widget_x_coordinate">谞拽讜讚讛 讘爪讬专 X鈥� (240 诇讻诇 讛讬讜转专)</string>
+    <string name="watchface_dialog_widget_type">住讜讙</string>
+    <string name="appmanager_app_edit">注专讬讻讛</string>
+    <string name="button_watchface_save_apply">砖诪讬专讛 讜讛讞诇讛</string>
+    <string name="button_watchface_preview">转爪讜讙讛 诪拽讚讬诪讛 讘砖注讜谉</string>
+    <string name="watchface_widget_type_heart_rate">讚讜驻拽</string>
+    <string name="watchface_widget_type_steps">爪注讚讬诐</string>
+    <string name="watchface_widget_type_weather">诪讝讙 讗讜讜讬专</string>
+    <string name="watchface_widget_type_date">转讗专讬讱</string>
+    <string name="watchface_dialog_title_add_widget">讛讜住驻转 讜讬讚讙壮讟</string>
+    <string name="watchface_toast_settings_incomplete">讛讛讙讚专讜转 诇讗 讛讜砖诇诪讜, 讛讜讜讬讚讙壮讟 诇讗 谞讜住祝</string>
+    <string name="watchface_dialog_title_set_name">讛讙讚专转 砖诐 诇驻谞讬 讛砖注讜谉</string>
+    <string name="button_watchface_settings">讛讙讚专讜转 驻谞讬 讛砖注讜谉</string>
+    <string name="button_watchface_add_widget">讛讜住驻转 讜讬讚讙壮讟</string>
+    <string name="button_watchface_select_image">讛讞诇驻转 转诪讜谞转 讛专拽注</string>
+    <string name="button_watchface_edit_name">注专讬讻转 讛砖诐</string>
+    <string name="devicetype_smaq2oss">SMA-Q2 OSS</string>
+    <string name="watchface_upload_failed">讛注诇讗转 驻谞讬 讛砖注讜谉 谞讻砖诇讛. 谞讗 诇谞住讜转 砖讜讘.</string>
+    <string name="watchface_cache_confirm_overwrite">驻谞讬 砖注讜谉 讘砖诐 讛讝讛 讻讘专 拽讬讬诪讬诐 讘讗讞住讜谉. 诇讚专讜住 讗讜转诐\?</string>
+    <string name="heart_rate_result">转讜爪讗讜转 讛诪讚讬讚讛</string>
+    <string name="getting_heart_rate">讘诪讚讬讚讛</string>
+    <string name="blood_pressure">诇讞抓 讚诐</string>
+    <string name="watchface_setting_power_saving_hands">讛砖讘转转 转谞讜注讜转 讛诪讞讜讙讬诐 讻砖诇讗 谞注谞讚</string>
+    <string name="watchface_setting_power_saving_display">讛砖讘转转 注讚讻讜谞讬 讛转爪讜讙讛 讻砖诇讗 谞注谞讚</string>
+    <string name="watchface_setting_title_power_saving">讞住讻讜谉 讘讞砖诪诇</string>
+    <string name="watchface_widget_type_chance_rain">住讬讻讜讬 诇讙砖诐</string>
+    <string name="watchface_widget_type_active_mins">讚拽讜转 驻注讬诇讜转</string>
+    <string name="watchface_widget_type_2nd_tz">讗讝讜专 讝诪谉 诪砖谞讬</string>
+    <string name="watchface_widget_type_calories">拽诇讜专讬讜转</string>
+    <string name="watchface_widget_type_battery">住讜诇诇讛</string>
+    <string name="pref_summary_huami_force_new_protocol">讬砖 诇讛驻注讬诇 讗诐 讛诪讻砖讬专 砖诇讱 诇讗 诪转讞讘专 注讜讚 诇讗讞专 砖讚专讜讙 讞讜诪专讛</string>
+    <string name="pref_title_huami_force_new_protocol">驻专讜讟讜拽讜诇 讗讬诪讜转 讞讚砖</string>
+    <string name="watchface_setting_title_wrist_flick">住讬讘讜讘 讛砖注讜谉 讘诪讛讬专讜转</string>
+    <string name="menuitem_flashlight">驻谞住</string>
+    <string name="prefs_autoheartrate_interval">转讚讬专讜转 讛讘讚讬拽讜转</string>
+    <string name="prefs_autoheartrate_sleep">诇诪讚讜讚 讘讝诪谉 讛砖讬谞讛</string>
+    <string name="prefs_autoheartrate_measurement">诪讚讬讚转 讚讜驻拽 讗讜讟讜诪讟讬转</string>
+    <string name="prefs_autoheartrate_summary">诪讚讬讚讜转 讚讜驻拽 诪讞讝讜专讬讜转 讘诪讛诇讱 讛讬讜诐 讜讙诐 讘讝诪谉 讛砖讬谞讛</string>
+    <string name="prefs_autoheartrate">讚讜驻拽 讗讜讟讜诪讟讬</string>
+    <string name="enable_notifications_summary">讛转专讗讜转 注诇 砖讬讞讜转, 讛讜讚注讜转 讜注讜讚</string>
+    <string name="prefs_notifications_enable">讛驻注诇转 讛转专讗讜转</string>
+    <string name="enable_vibrations_summary">专讟讟 诇砖讬讞讜转, 讛讜讚注讜转, 讛转专讗讜转 讜注讜讚</string>
+    <string name="prefs_vibration_enable">讛驻注诇转 专讟讟</string>
+    <string name="prefs_sleep_time_summary">诪爪讬讬谉 讝诪谞讬诐 讘讛诐 谞专砖诪转 砖讬谞讛</string>
+    <string name="prefs_sleep_time_label">讛讙讚专转 砖注讜转 砖讬谞讛</string>
+    <string name="prefs_sleep_time">讝诪谞讬 砖讬谞讛</string>
+    <string name="devicetype_fitpro">FitPro</string>
+    <string name="pref_title_ping_tone">爪诇讬诇 驻讬谞讙</string>
+    <string name="devicetype_nothingear1">Nothing Ear (1)</string>
+    <string name="nothing_prefs_inear_summary">谞讙讬谞讛/讛砖讛讬讛 砖诇 讛诪讜讝讬拽讛 讘讛转讗诐 诇讛专讻讘转 讗讜讝谞讬讜转</string>
+    <string name="nothing_prefs_inear_title">讝讬讛讜讬 转讜讱 讗讜讝谞讬</string>
+    <string name="nothing_prefs_audiomode_title">诪爪讘 砖诪注</string>
+    <string name="uncheck_all_applications">讘讬讟讜诇 住讬诪讜谉 讻诇 讛讬讬砖讜诪讜谞讬诐</string>
+    <string name="pref_title_notification_use_as_allow">诇讗驻砖专 讛转专讗讜转 诪讛讬讬砖讜诪讜谞讬诐 讛谞讘讞专讬诐</string>
+    <string name="title_activity_notification_management">讛讙讚专讜转 讛讜讚注讛</string>
+    <string name="pref_title_message_privacy_mode">诪爪讘 驻专讟讬讜转 讛讜讚注讜转</string>
+    <string name="pref_header_notification_application_settings">讛讙讚专讜转 谞驻专讚讜转 诇讻诇 讬讬砖讜诪讜谉</string>
+    <string name="toast_app_must_not_be_selected">讬讬砖讜诪讜谉 爪专讬讱 诇讛讬讜转 诇讗 诪住讜诪谉 讻讚讬 砖讬讜讙讚专</string>
+    <string name="toast_app_must_be_selected">讬讬砖讜诪讜谉 讞讬讬讘 诇讛讬讜转 诪住讜诪谉 讻讚讬 砖讬讜讙讚专</string>
+    <string name="pref_message_privacy_mode_complete">讛住转专转 讻诇 讛转讜讻谉</string>
+    <string name="pref_applications_settings">专砖讬诪转 讬讬砖讜诪讜谞讬诐</string>
+    <string name="check_all_applications">住讬诪讜谉 讻诇 讛讬讬砖讜诪讜谞讬诐</string>
+    <string name="pref_title_notification_use_as">诇讛砖转诪砖 讘专砖讬诪转 讛讬讬砖讜诪讜谞讬诐 讻讚讬鈥�</string>
+    <string name="pref_title_notification_use_as_deny">诇讚讞讜转 讛转专讗讜转 诪讛讬讬砖讜诪讜谞讬诐 讛谞讘讞专讬诐</string>
+    <string name="pref_message_privacy_mode_off">讛爪讙转 讻诇 讛转讜讻谉</string>
+    <string name="prefs_ambient_voice_summary">讛讚讙砖转 讛拽讜诇 诪砖讗专 讛爪诇讬诇讬诐</string>
+    <string name="devicetype_galaxybuds">Galaxy Buds</string>
+    <string name="prefs_equalizer_preset">注专讻讛 砖诇 讗拽讜讜诇讬讬讝专</string>
+    <string name="pref_title_equalizer_bass_boost">讛讙讘专转 讘讗住</string>
+    <string name="pref_title_equalizer_soft">专讻讛</string>
+    <string name="pref_title_equalizer_dynamic">讚讬谞诪讬转</string>
+    <string name="pref_title_equalizer_clear">爪诇讜诇讛</string>
+    <string name="pref_title_equalizer_trebble">讛讙讘专转 讟专讘诇</string>
+    <string name="prefs_dolby_mode">诪爪讘 Dolby</string>
+    <string name="prefs_equalizer">讗拽讜讜诇讬讬讝专</string>
+    <string name="prefs_equalizer_summary">讛驻注诇转 讗讜 讛砖讘转转 讗拽讜讜诇讬讬讝专</string>
+    <string name="prefs_game_mode">诪爪讘 诪砖讞拽</string>
+    <string name="prefs_touch_lock">谞注讬诇讛 讘谞讙讬注讛</string>
+    <string name="prefs_touch_lock_summary">讛砖讘转转 讗讬专讜注讬 诪讙注</string>
+    <string name="prefs_galaxy_buds_experimental">谞讬住讬讜谞讬</string>
+    <string name="prefs_ambient_volume">注爪诪转 砖诪注 讛拽驻讬转</string>
+    <string name="prefs_ambient_voice_focus">讛转诪拽讚讜转 讘拽讜诇</string>
+    <string name="prefs_ambient_sound">爪诇讬诇 讛拽驻讬</string>
+    <string name="prefs_ambient_mode">诪爪讘 讛拽驻讬</string>
+    <string name="prefs_left">砖诪讗诇</string>
+    <string name="prefs_right">讬诪讬谉</string>
+    <string name="prefs_galaxy_touch_options">讗驻砖专讜讬讜转 诪讙注</string>
+    <string name="prefs_dolby_summary">注专讻转 Dolby 诇讗拽讜讜诇讬讬讝专</string>
+    <string name="prefs_game_mode_summary">专拽 讗诐 讛诪讻砖讬专 砖诇讱 转讜诪讱 讘诪爪讘 诪砖讞拽</string>
+    <string name="battery_case">讗专讬讝转 住讜诇诇讛</string>
+    <string name="left_earbud">讗讜讝谞讬转 砖诪讗诇讬转</string>
+    <string name="right_earbud">讗讜讝谞讬转 讬诪谞讬转</string>
+    <string name="pref_header_system">诪注专讻转</string>
+    <string name="pref_header_equalizer">讗拽讜讜诇讬讬讝专</string>
+    <string name="devicetype_sony_wh_1000xm3">Sony WH-1000XM3</string>
+    <string name="devicetype_galaxybuds_live">Galaxy Buds Live</string>
+    <string name="pref_title_equalizer_normal">专讙讬诇</string>
+    <string name="prefs_active_noise_cancelling_summary">讞住讬诪转 专注砖讬 专拽注</string>
+    <string name="prefs_pressure_relief">讛驻讙转 诪转讞讬诐 注诐 爪诇讬诇讬诐 讗讜驻驻讬诐</string>
+    <string name="pressure_relief_summary">诪谞讬注转 转讞讜砖转 讛诇讞抓 讘讗讝谞讬讬诐 讘讛注讚专 砖讬诪讜砖 讘讘讬讟讜诇 专注砖讬诐 驻注讬诇</string>
+    <string name="pref_header_sony_ambient_sound_control">讘拽专转 砖诪注 讛拽驻讬</string>
+    <string name="sony_ambient_sound">诪爪讘</string>
+    <string name="sony_ambient_sound_off">讻讘讜讬</string>
+    <string name="sony_ambient_sound_noise_cancelling">讘讬讟讜诇 专注砖讬诐</string>
+    <string name="sony_ambient_sound_wind_noise_reduction">讛驻讞转转 专注砖讬 专讜讞</string>
+    <string name="sony_ambient_sound_ambient_sound">爪诇讬诇 讛拽驻讬</string>
+    <string name="sony_ambient_sound_focus_voice">讛转诪拽讚讜转 注诇 拽讜诇</string>
+    <string name="sony_ambient_sound_level">注爪诪转 砖诪注 讛拽驻讬</string>
+    <string name="sony_sound_position">诪讬拽讜诐 讛爪诇讬诇</string>
+    <string name="sony_sound_position_off">讻讘讜讬</string>
+    <string name="sony_sound_position_front">拽讚讬诪讛</string>
+    <string name="sony_sound_position_front_left">拽讚讬诪讛 诪砖诪讗诇</string>
+    <string name="sony_sound_position_front_right">拽讚讬诪讛 诪讬诪讬谉</string>
+    <string name="sony_sound_position_rear_left">讗讞讜专讛 诪砖诪讗诇</string>
+    <string name="sony_sound_position_rear_right">讗讞讜专讛 诪讬诪讬谉</string>
+    <string name="sony_surround_mode">诪爪讘 讛讬拽驻讬</string>
+    <string name="sony_surround_mode_off">讻讘讜讬</string>
+    <string name="sony_surround_mode_arena">讝讬专讛</string>
+    <string name="sony_surround_mode_club">诪讜注讚讜谉</string>
+    <string name="sony_surround_mode_outdoor_stage">讘诪讛 讘讗讜讜讬专 讛驻转讜讞</string>
+    <string name="sony_surround_mode_concert_hall">讛讬讻诇 诪讜驻注讬诐</string>
+    <string name="sony_warn_sbc_codec">讗讝讛专讛: 讛讛讙讚专讜转 砖诇 讛讗拽讜讜诇讬讬讝专, 诪讬拽讜诐 讛砖诪注 讜讛讙讚专讜转 讛砖诪注 讛讛讬拽驻讬 注讜讘讚讜转 专拽 诪讜诇 诪驻注谞讞 讛砖诪注 SBC.</string>
+    <string name="sony_equalizer">讗拽讜讜诇讬讬讝专</string>
+    <string name="sony_equalizer_preset_off">讻讘讜讬</string>
+    <string name="sony_equalizer_preset_bright">讘讛讬专</string>
+    <string name="sony_equalizer_preset_excited">谞诇讛讘</string>
+    <string name="sony_equalizer_preset_mellow">谞讜讙讛</string>
+    <string name="sony_equalizer_preset_relaxed">专讙讜注</string>
+    <string name="sony_equalizer_preset_vocal">拽讜诇</string>
+    <string name="sony_equalizer_preset_treble_boost">讛讙讘专转 讟专讘诇</string>
+    <string name="sony_equalizer_preset_bass_boost">讛讙讘专转 讘讗住</string>
+    <string name="sony_equalizer_preset_speech">讚讬讘讜专</string>
+    <string name="sony_equalizer_preset_manual">讬讚谞讬</string>
+    <string name="sony_equalizer_preset_custom_1">诪讜转讗诐 1</string>
+    <string name="sony_equalizer_preset_custom_2">诪讜转讗诐 2</string>
+    <string name="pref_header_sony_equalizer_preset_custom_1">注专讻转 讛讙讚专讜转 诪讜转讗诪转 1</string>
+    <string name="pref_header_sony_equalizer_preset_custom_2">注专讻转 讛讙讚专讜转 诪讜转讗诪转 2</string>
+    <string name="sony_equalizer_band_400">400</string>
+    <string name="sony_equalizer_band_1000">1 拽壮</string>
+    <string name="sony_equalizer_band_2500">2.5 拽壮</string>
+    <string name="sony_equalizer_band_6300">6.3 拽壮</string>
+    <string name="sony_equalizer_band_16000">16 拽壮</string>
+    <string name="sony_equalizer_clear_bass">讘讗住 谞拽讬</string>
+    <string name="sony_touch_sensor">讘拽专转 讞讬讬砖谉 诪讙注</string>
+    <string name="sony_notification_voice_guide">讛转专讗讜转 讜讛谞讞讬讛 拽讜诇讬转</string>
+    <string name="sony_automatic_power_off_5_min">5 讚拽讜转</string>
+    <string name="sony_automatic_power_off_30_min">讞爪讬 砖注讛</string>
+    <string name="sony_automatic_power_off_3_hour">3 砖注讜转</string>
+    <string name="pref_header_other">讗讞专</string>
+    <string name="prefs_active_noise_cancelling">讘讬讟讜诇 专注砖讬诐 驻注讬诇</string>
+    <string name="sony_automatic_power_off">讻讬讘讜讬 讗讜讟讜诪讟讬</string>
+    <string name="sony_automatic_power_off_1_hour">砖注讛</string>
+    <string name="watchface_widget_type_custom">讜讬讚讙壮讟 诪讜转讗诐 讗讬砖讬转</string>
+    <string name="sony_automatic_power_off_off">诇讗 诇讻讘讜转</string>
+    <string name="device_card_activity_card_title">驻专讟讬 驻注讬诇讜转 注诇 讻专讟讬住 诪讻砖讬专</string>
+    <string name="device_card_activity_card_title_summary">谞讗 诇讘讞讜专 讗讬诇讜 驻专讟讬 驻注讬诇讜转 诪讜驻讬注讬诐 讘讻专讟讬住 讛诪讻砖讬专</string>
+    <string name="prefs_activity_in_device_card_title">讛爪讙转 驻专讟讬 驻注讬诇讜转 讘讻专讟讬住 讛诪讻砖讬专</string>
+    <string name="prefs_activity_in_device_card_title_summary">讛爪讙转 讛爪注讚讬诐 讛谞讜讻讞讬讬诐, 讛诪专讞拽 讗讜 讛砖讬谞讛 讘讻专讟讬住 讛诪讻砖讬专</string>
+    <string name="prefs_activity_in_device_card_sleep_title">砖讬谞讛</string>
+    <string name="prefs_activity_in_device_card_distance_title_summary">讛诪专讞拽 诪讞讜砖讘 诪爪注讚讬诐 讜讗讜专讱 爪注讚 (讗驻砖专 诇讛讙讚讬专 转讞转 讛讙讚专讜转 - 注诇讬讱)</string>
+    <string name="prefs_activity_in_device_card_steps_title_summary">讛爪讙转 住讱 讻诇 讛爪注讚讬诐</string>
+    <string name="prefs_activity_in_device_card_sleep_title_summary">讛爪讙转 诪砖讱 砖讬谞讛</string>
+    <string name="sony_automatic_power_off_when_taken_off">注诐 讛讛住专讛</string>
+    <string name="prefs_fm_presets_presets">注专讻讜转</string>
+    <string name="prefs_fm_preset_instructions">诇讞讬爪讛 讗专讜讻讛 注诇 讛讻驻转讜专 转讗讞住谉 注专讻讛</string>
+    <string name="watchface_dialog_widget_timeout_hide_text">讛住转专转 讛讟拽住讟 注诐 转讜诐 讛讝诪谉</string>
+    <string name="watchface_dialog_widget_timeout_show_circle">讛爪讙转 注讬讙讜诇 注诐 转讜诐 讛讝诪谉</string>
+    <string name="watchface_dialog_widget_update_timeout">转讜诐 讝诪谉 注讚讻讜谉 讘砖谞讬讜转</string>
+    <string name="watchface_dialog_widget_timezone">讗讝讜专 讝诪谉</string>
+    <string name="qhybrid_summary_on_device_confirmation">讗讬砖讜专讬 爪讬诪讜讚 讘讛转拽谞讬诐 讬讻讜诇讬诐 诇讛专讙讬讝. 讛砖讘转转诐 注砖讜讬讛 诇讗讘讚 诪讛讬讻讜诇讜转.</string>
+    <string name="qhybrid_title_on_device_confirmation">讛驻注诇转 讗讬砖讜专 爪讬诪讜讚 讘诪讻砖讬专</string>
+    <string name="title_activity_set_reminders">讛讙讚专转 转讝讻讜专讜转</string>
+    <string name="controlcenter_start_configure_reminders">讛讙讚专转 转讝讻讜专讜转</string>
+    <string name="reminder_repeat">讞讝专讛</string>
+    <string name="reminder_time_every_month">%1$s, 讘讻诇 讞讜讚砖</string>
+    <string name="reminder_every_month">讻诇 讞讜讚砖</string>
+    <string name="miband_prefs_reserve_reminder_calendar">转讝讻讜专讜转 诇砖讬诪讜专 诇讗讬专讜注讬诐 拽专讜讘讬诐</string>
+    <string name="title_activity_reminder_details">驻专讟讬 转讝讻讜专转</string>
+    <string name="reminder_date">转讗专讬讱</string>
+    <string name="reminder_time">砖注讛</string>
+    <string name="reminder_time_once">%1$s, 驻注诐 讗讞转</string>
+    <string name="reminder_message">讛讜讚注讛</string>
+    <string name="reminder_time_every_week">%1$s, 讘讻诇 砖讘讜注</string>
+    <string name="reminder_time_every_day">%1$s, 讘讻诇 讬讜诐</string>
+    <string name="reminder_time_every_year">%1$s, 讘讻诇 砖谞讛</string>
+    <string name="reminder_delete_confirm_title">诪讞讬拽转 转讝讻讜专转</string>
+    <string name="reminder_delete_confirm_description">诇诪讞讜拽 讗转 讛转讝讻讜专转\?</string>
+    <string name="reminder_no_free_slots_description">诇诪讻砖讬专 讗讬谉 讞诇讜谞讜转 驻谞讜讬讬诐 诇转讝讻讜专讜转 (住讱 讻诇 讛讞诇讜谞讜转: %1$s)</string>
+    <string name="reminder_once">驻注诐 讗讞转</string>
+    <string name="reminder_every_day">讻诇 讬讜诐</string>
+    <string name="reminder_every_week">讻诇 砖讘讜注</string>
+    <string name="reminder_every_year">讻诇 砖谞讛</string>
+    <string name="reminder_no_free_slots_title">讗讬谉 讞诇讜谞讜转 驻谞讜讬讬诐</string>
+    <string name="prefs_reserve_reminder_calendar_summary">诪住驻专 讗讬专讜注讬 诇讜讞 讛砖谞讛 砖讬住讜谞讻专谞讜</string>
+    <string name="mi2_prefs_do_not_disturb_lift_wrist">讛驻注诇转 讛转爪讜讙讛 注拽讘 讛谞驻讛 讘诪爪讘 诇讗 诇讛驻专讬注</string>
+    <string name="averageAltitude">诪诪讜爪注</string>
+    <string name="minCadence">拽爪讘 诪讝注专讬</string>
+    <string name="minSpeed">诪讝注专讬</string>
+    <string name="maxHR">讚讜驻拽 诪专讘讬</string>
+    <string name="minHR">讚讜驻拽 诪讝注专讬</string>
+    <string name="averageCadence">拽爪讘 诪诪讜爪注</string>
+    <string name="maxCadence">拽爪讘 诪专讘讬</string>
+    <string name="spm">爪注讚讬诐 诇讚拽讛</string>
+    <string name="maxStride">爪注讚 诪专讘讬</string>
+    <string name="minStride">爪注讚 诪讝注专讬</string>
+    <string name="pref_header_sony_equalizer_bands">爪诪讬讚讬诐</string>
+    <string name="sony_audio_upsampling">Upsampling 诇砖诪注</string>
+    <string name="controlcenter_power_off_confirm_description">诇讻讘讜转 讗转 讛讛转拽谉\?</string>
+    <string name="sony_button_mode_volume_control">讘拽专转 砖诪注</string>
+    <string name="activity_prefs_discovery_pairing">讗驻砖专讜讬讜转 讙讬诇讜讬 讜爪讬诪讜讚</string>
+    <string name="sony_button_mode_right">诪爪讘 讛讻驻转讜专 (讬诪讬谉)</string>
+    <string name="discover_unsupported_devices">讙讬诇讜讬 讛转拽谞讬诐 砖讗讬谞诐 谞转诪讻讬诐</string>
+    <string name="controlcenter_power_off">讻讬讘讜讬</string>
+    <string name="controlcenter_power_off_confirm_title">讻讬讘讜讬</string>
+    <string name="add_test_device">讛讜住驻转 讛转拽谉 讘讚讬拽讛</string>
+    <string name="sony_button_mode_left">诪爪讘 讻驻转讜专 (砖诪讗诇)</string>
+    <string name="sony_button_mode_off">讻讘讜讬</string>
+    <string name="devicetype_sony_wf_sp800n">Sony WF-SP800N</string>
+    <string name="discover_unsupported_devices_description">讛驻注诇转 讗驻砖专讜转 讝讜 转爪讬讙 讛转拽谞讬 Bluetooth 讘注转 讛住专讬拽讛. 谞讙讬注讛 拽爪专讛 转注转讬拽 讗转 砖诐 讛讛转拽谉 讜讗转 讻转讜讘转 讛讞讜诪专讛 砖诇讜 诇诇讜讞 讛讙讝讬专讬诐. 诇讞讬爪讛 讗专讜讻讛 转驻注讬诇 讗转 讞诇讜谞讬转 `讛讜住驻转 讛转拽谉 讘讚讬拽讛`. 注诇讜诇 诇讙专讜诐 诇转拽诇讜转 讘讚诪讜转 讛拽驻讗转 讛讬讬砖讜诪讜谉.</string>
+    <string name="sony_pause_when_taken_off">诇讛砖讛讜转 讻讗砖专 讛讗讜讝谞讬讜转 诪讜住专讜转</string>
+    <string name="sony_button_mode_ambient_sound_control">讘拽专转 砖诪注 讛拽驻讬</string>
+    <string name="sony_button_mode_playback_control">讘拽专转 谞讙讬谞讛</string>
+    <string name="devicetype_bose_qc35">Bose QC35</string>
+    <string name="devicetype_vesc">VESC</string>
+    <string name="descentDistance">诪专讞拽 讘诪讜专讚</string>
+    <string name="ascentDistance">诪专讞拽 讘诪注诇讛</string>
+    <string name="activity_type_hiking">讟讬讜诇</string>
+    <string name="activity_type_climbing">讟讬驻讜住</string>
+    <string name="distance_format_kilometers">###.# 拽状诪</string>
+    <string name="distance_format_feet">### 专讙诇</string>
+    <string name="about_hash">讛讙砖讛 %s</string>
+    <string name="pref_media_play">谞讙讬谞转 诪讚讬讛</string>
+    <string name="pref_media_playpause">讛讞诇驻转 诪爪讘 谞讙讬谞讛</string>
+    <string name="pref_media_next">讛专爪讜注讛 讛讘讗讛</string>
+    <string name="pref_media_previous">讛专爪讜注讛 讛拽讜讚诪转</string>
+    <string name="pref_media_volumeup">讛讙讘专转 注爪诪转 讛砖诪注</string>
+    <string name="pref_media_forward">诇讚诇讙 拽讚讬诪讛</string>
+    <string name="pref_media_rewind">诇讚诇讙 讗讞讜专讛</string>
+    <string name="pref_device_action_broadcast">砖诇讬讞转 砖讬讚讜专</string>
+    <string name="distance_format_meters">### 诪壮</string>
+    <string name="pref_button_action_disabled">诪讜砖讘转</string>
+    <string name="pref_media_pause">讛砖讛讬转 诪讚讬讛</string>
+    <string name="pref_media_volumedown">讛谞诪讻转 注爪诪转 讛砖诪注</string>
+    <string name="distance_format_miles">###.# 诪讬壮</string>
+    <string name="pref_activity_recognize_rowing">讝讬讛讜讬 讞转讬专讛</string>
+    <string name="menuitem_menu">转驻专讬讟</string>
+    <string name="pref_activity_recognize_running">讝讬讛讜讬 专讬爪讛</string>
+    <string name="pref_activity_recognition_mode_ask">诇砖讗讜诇</string>
+    <string name="prefs_activity_recognition">讛讙讚专讜转 讝讬讛讜讬 驻注讬诇讜转</string>
+    <string name="pref_activity_recognition_mode_none">讻诇讜诐</string>
+    <string name="pref_activity_recognize_walking">讝讬讛讜讬 讛诇讬讻讛</string>
+    <string name="pref_activity_recognize_biking">讝讬讛讜讬 专讻讬讘转 讗讜驻谞讬讬诐</string>
+    <string name="pref_activity_recognition_mode_auto">讗讜讟讜诪讟讬</string>
+    <string name="fossil_hr_button_config_info">诇讗 谞讬转谉 诇讛讙讚讬专 讞诇拽 诪讛讻驻转讜专讬诐 讻讬讜讜谉 砖讛转讻讜谞讜转 砖诇讛谉 讻转讜讘讜转 讘讗讜驻谉 诪讜讘谞讛 讘讞讜诪专讛 砖诇 讛砖注讜谉.
+\n
+\n讗讝讛专讛: 诇讞讬爪讛 讗专讜讻讛 注诇 讛讻驻转讜专 讛注诇讬讜谉 讻讗砖专 诪讜转拽谞讬诐 驻谞讬 砖注讜谉 诪讛讬讬砖讜诪讜谉 讛专砖诪讬 砖诇 Fossil 讙诐 转讞诇讬祝 讘讬谉 讛爪讙讛/讛住转专讛 砖诇 讜讬讚讙壮讟讬诐.</string>
+    <string name="devicetype_domyos_t540">Domyos T540</string>
+    <string name="sony_speak_to_chat_sensitivity">专讙讬砖讜转 讝讬讛讜讬 拽讜诇讬</string>
+    <string name="devicetype_sony_wh_1000xm4">Sony WH-1000XM4</string>
+    <string name="sony_speak_to_chat_sensitivity_auto">讗讜讟讜诪讟讬转</string>
+    <string name="sony_speak_to_chat_sensitivity_high">讙讘讜讛讛</string>
+    <string name="sony_speak_to_chat">诇讞讬爪讛 诇讚讬讘讜专</string>
+    <string name="sony_speak_to_chat_sensitivity_low">谞诪讜讻讛</string>
+    <string name="sony_speak_to_chat_focus_on_voice">讛转诪拽讚讜转 讘拽讜诇</string>
+    <string name="sony_speak_to_chat_timeout">讝诪谉 诇讛诪转谞讛</string>
+    <string name="sony_speak_to_chat_timeout_off">讻讘讜讬</string>
+    <string name="sony_speak_to_chat_timeout_short">拽爪专 (15 砖谞壮)</string>
+    <string name="sony_speak_to_chat_timeout_standard">专讙讬诇 (30 砖谞壮)</string>
+    <string name="sony_speak_to_chat_timeout_long">讗专讜讱 (讚拽讛)</string>
+    <string name="sony_connect_two_devices">讛转讞讘专讜转 诇砖谞讬 诪讻砖讬专讬诐 讘讜志讝诪谞讬转</string>
+    <string name="watchface_dialog_widget_width">专讜讞讘 讛讜讜讬讚讙壮讟 (讘驻讬拽住诇讬诐)</string>
+    <string name="discovery_bluetooth_le_scan">住专讬拽转 Bluetooth LE:</string>
+    <string name="discovery_bluetooth_scan">住专讬拽转 Bluetooth:</string>
+    <string name="device_unsupported">诇讗 谞转诪讱</string>
+    <string name="audio_codec">诪驻注谞讞 砖诪注</string>
+    <string name="pref_header_sony_device_info">驻专讟讬 讛转拽谉</string>
+    <string name="unknown">诇讗 讬讚讜注</string>
+    <string name="sony_anc_optimize_title">砖讬驻讜专</string>
+    <string name="sony_anc_optimize_description">诪砖驻专 讘讬讟讜诇 讛专注砖讬诐 讬讜驻注诇 讘诇讞讬爪讛.</string>
+    <string name="sony_anc_optimizer_status_wearing_condition">诪爪讘 讛讘诇讗讬 谞诪讚讚鈥�</string>
+    <string name="sony_anc_optimizer_status_atmospheric_pressure">讛诇讞抓 讛讗讟诪讜住驻专讬 谞诪讚讚鈥�</string>
+    <string name="sony_anc_optimizer_status_analyzing">诪转讘爪注 谞讬转讜讞鈥�</string>
+    <string name="sony_anc_optimizer_status_finished">讛转讛诇讬讱 诪住转讬讬诐鈥�</string>
+    <string name="start">讛转讞诇讛</string>
+    <string name="pref_header_sony_anc_optimizer">诪砖驻专 讘讬讟讜诇 专注砖讬诐</string>
+    <string name="sony_anc_optimize_confirmation_title">诪砖驻专 讘讬讟讜诇 讛专注砖讬诐</string>
+    <string name="pref_anc_optimizer_state_pressure">诇讞抓 讗讟诪讜住驻专讬</string>
+    <string name="sony_anc_optimizer_status_not_running">诇讗 驻讜注诇</string>
+    <string name="sony_anc_optimizer_status_starting">诪讜驻注诇鈥�</string>
+    <string name="sony_anc_optimize_confirmation_description">谞讬转谉 诇讛砖转诪砖 讘讗讜讝谞讬讜转 讻专讙讬诇. 讗诐 诪爪讘 讛讘诇讗讬 讗讜 讛诇讞抓 讛讗讟诪讜住驻专讬 诪砖转谞讛, 讬砖 诇讛专讬抓 讗转 讛诪砖驻专 砖讜讘.</string>
+    <string name="devicetype_amazfit_pop">Amazfit Pop</string>
+    <string name="devicetype_amazfit_pop_pro">Amazfit Pop Pro</string>
+    <string name="menuitem_nothing">讻诇讜诐</string>
+    <string name="pref_title_opentracks_packagename">砖诐 讞讘讬诇讛 讘志OpenTracks</string>
+    <string name="pref_summary_opentracks_packagename">诪砖诪砖 诇讛驻注诇转/注爪讬专转 讛拽诇讟转 诪住诇讜诇讬 GPS 诪讬讬砖讜诪讜谞讬 讞讬讟讜讘 讞讬爪讜谞讬讬诐.</string>
+    <string name="pref_device_action_fitness_app_control_stop">注爪讬专转 诪注拽讘 讬讬砖讜诪讜谉 讞讬讟讜讘</string>
+    <string name="pref_device_action_fitness_app_control_start">讛转讞诇转 诪注拽讘 讬讬砖讜诪讜谉 讞讬讟讜讘</string>
+    <string name="pref_title_notifications_generic_settings">讛讙讚专讜转 讛讜讚注讜转 砖诇 Android</string>
+    <string name="autoExport_lastTime_label">讛讬讬爪讜讗 讛讗讜讟讜诪讟讬 讛讗讞专讜谉: %1$s</string>
+    <string name="activity_db_management_autoexport_enabled_yes">讬讬爪讜讗 讗讜讟讜诪讟讬 诪讜驻注诇.</string>
+    <string name="activity_db_management_autoexport_enabled_no">讬讬爪讜讗 讗讜讟讜诪讟讬 诪讜砖讘转.</string>
+    <string name="activity_db_management_autoexport_scheduled_no">讛讬讬爪讜讗 讛讗讜讟讜诪讟讬 诇讗 转讜讝诪谉.</string>
+    <string name="activity_db_management_autoexport_scheduled_yes">讛讬讬爪讜讗 讛讗讜讟讜诪讟讬 转讜讝诪谉 (讘诪拽讜专) 诇志%1$s</string>
+    <string name="activity_db_management_autoexport_location">诇讗 谞讬转谉 诇驻注谞讞 讗转 讛诪讬拽讜诐. 讻谞专讗讛 转拽诇讛 讘诪注专讻转 讛讛专砖讗讜转 讛讞讚砖讛 砖诇 Android. 住讘讬专 诇讛谞讬讞 砖讛讬讬爪讜讗 讛讗讜讟讜诪讟讬 诇讗 注讜讘讚 讻专讙注.</string>
+    <string name="watchface_dialog_pre_setting_position">讛诪讬拽讜诐 诪讜讙讚专 诪专讗砖 诇讻讚讬 %s</string>
+    <string name="watchface_setting_light_up_on_notification">讛讗专讛 注诐 讛转专讗讜转 讞讚砖讜转</string>
+    <string name="menuitem_email">讚讜讗状诇</string>
+    <string name="application_name_banglejs_main">Bangle.js Gadgetbridge</string>
+    <string name="title_activity_controlcenter_main_nightly">讙专住讛 谞住讬讜谞讬转 砖诇 Gadgetbridge</string>
+    <string name="about_activity_title_main_nightly">注诇 讛讙专住讛 讛谞住讬讜谞讬转 砖诇 Gadgetbridge</string>
+    <string name="gadgetbridge_running_banglejs_nopebble">Bangle.js 驻注讬诇</string>
+    <string name="title_activity_controlcenter_banglejs_main">Bangle.js Gadgetbridge</string>
+    <string name="about_activity_title_banglejs_main">注诇 Bangle.js Gadgetbridge</string>
+    <string name="title_activity_controlcenter_banglejs_nopebble">Bangle.js Gadgetbridge</string>
+    <string name="about_activity_title_banglejs_nopebble">注诇 Bangle.js Gadgetbridge</string>
+    <string name="about_description_banglejs_nopebble">讬讬砖讜诪讜谉 Android 诪诇讜讜讛 诇志Bangle.js 砖谞讘谞讛 注诇 讙讘讬 诪讬讝诐 Gadgetbridge 注诐 转讜住驻转 砖诇 讙讬砖讛 诇讗讬谞讟专谞讟.
+\n
+\n注拽讘 诪讚讬谞讬讜转 砖诇 讞谞讜转 Google Play, 讗住讜专 诇谞讜 诇讛爪讬讘 拽讬砖讜专 转专讜诪讛 讘讬讬砖讜诪讜谉 注爪诪讜 讗讱 讗诐 讛讬讬砖讜诪讜谉 谞讜砖讗 讞谉 讘注讬谞讬讱 讗驻砖专 诇转专讜诐 讚专讱 注诪讜讚 讛讘讬转 砖诇 Gadgetbridge 诇讛诇谉.</string>
+    <string name="about_description_banglejs_main">讬讬砖讜诪讜谉 Android 诪诇讜讜讛 诇志Bangle.js 砖谞讘谞讛 注诇 讙讘讬 诪讬讝诐 Gadgetbridge 注诐 转讜住驻转 砖诇 讙讬砖讛 诇讗讬谞讟专谞讟.
+\n
+\n注拽讘 诪讚讬谞讬讜转 砖诇 讞谞讜转 Google Play, 讗住讜专 诇谞讜 诇讛爪讬讘 拽讬砖讜专 转专讜诪讛 讘讬讬砖讜诪讜谉 注爪诪讜 讗讱 讗诐 讛讬讬砖讜诪讜谉 谞讜砖讗 讞谉 讘注讬谞讬讱 讗驻砖专 诇转专讜诐 讚专讱 注诪讜讚 讛讘讬转 砖诇 Gadgetbridge 诇讛诇谉.</string>
+    <string name="application_name_main_nightly">Gadgetbridge (讙专住讛 谞讬住讬讜谞讬转)</string>
+    <string name="application_name_banglejs_nopebble">Bangle.js Gadgetbridge</string>
+    <string name="gadgetbridge_running_banglejs_main">Bangle.js 驻注讬诇</string>
+    <string name="gadgetbridge_running_main_nightly">GB 讘诪讛讚讜专讛 诇讬诇讬转 驻注讬诇</string>
+    <string name="about_activity_title_main_nopebble">注诇 诪讛讚讜专讛 诇讬诇讬转 砖诇 Gadgetbridge 讘诇讬 Pebble</string>
+    <string name="gadgetbridge_running_main_nopebble">GB 诇诇讗 Pebble 讘诪讛讚讜专讛 诇讬诇讬转 驻注讬诇</string>
+    <string name="about_description_main_nightly">讞诇讜驻讛 讞讜驻砖讬转 讜谞讟讜诇转 注谞谉 诇讬讬砖讜诪讜谞讬 讛讙讗讚讙壮讟讬诐 讛住讙讜专讬诐 诇志Android 诪讛讬爪专谞讬诐. 诪讛讚讜专讜转 诇讬诇讬讜转 砖诇 Gadgetbridge. 讗讬 讗驻砖专 诇讛转拽讬谉 讗诐 讻讘专 讬砖 诇讱 讗转 讛讬讬砖讜诪讜谞讬诐 Gadgetbridge 讗讜 Pebble 诪讜转拽谞讬诐 注拽讘 住转讬专讛 讘住驻拽 讛志Pebble.</string>
+    <string name="title_activity_controlcenter_main_nopebble">诪讛讚讜专讛 诇讬诇讬转 砖诇 Gadgetbridge 讘诇讬 Pebble</string>
+    <string name="application_name_main_nopebble">Gadgetbridge (诪讛讚讜专讛 诇讬诇讬转, 讘诇讬 住驻拽 Pebble)</string>
+    <string name="about_description_main_nopebble">讞诇讜驻讛 讞讜驻砖讬转 讜谞讟讜诇转 注谞谉 诇讬讬砖讜诪讜谞讬 讛讙讗讚讙壮讟讬诐 讛住讙讜专讬诐 诇志Android 诪讛讬爪专谞讬诐. 诪讛讚讜专讜转 诇讬诇讬讜转 砖诇 Gadgetbridge. 讘诪讛讚讜专讛 讝讜 砖诪讜 砖诇 住驻拽 讛志Pebble 讛砖转谞讛 讻讚讬 诇诪谞讜注 住转讬专讜转, 诇讻谉 讻诪讛 诪讛砖讬诇讜讘讬诐 诪讜诇 Pebble 诇讗 讬注讘讚讜 讗讱 谞讬转谉 诇讛转拽讬谉 讗讜转讜 诇爪讚 讛转拽谞讛 拽讬讬诪转 砖诇 Gadgetbridge.</string>
+    <string name="prefs_activate_display_on_lift_sensitivity">专讙讬砖讜转</string>
+    <string name="normal">专讙讬诇讛</string>
+    <string name="sensitive">专讙讬砖讛</string>
+    <string name="pref_device_action_fitness_app_control_toggle">讛讞诇驻转 诪爪讘 诪注拽讘 讘讬讬砖讜诪讜谉 讞讬讟讜讘</string>
+    <string name="world_clock_no_free_slots_title">讗讬谉 诪拽讜诪讜转 驻谞讜讬讬诐</string>
+    <string name="world_clock_no_free_slots_description">诇诪讻砖讬专 讗讬谉 注讜讚 诪拽讜诪讜转 驻谞讜讬讬诐 诇砖注讜谞讬 讛注讜诇诐 (住讱 讻诇 讛诪拽讜诪讜转: %1$s)</string>
+    <string name="world_clock_timezone">讗讝讜专 讝诪谉</string>
+    <string name="world_clock_label">转讜讜讬转</string>
+    <string name="pref_world_clocks_title">砖注讜谞讬 讛注讜诇诐</string>
+    <string name="pref_world_clocks_summary">讛讙讚专转 砖注讜谞讬诐 诇讗讝讜专讬 讝诪谉 讗讞专讬诐</string>
+    <string name="world_clock_delete_confirm_description">诇诪讞讜拽 讗转 讛砖注讜谉 讛注讜诇诪讬\?</string>
+    <string name="world_clock_delete_confirm_title">诪讞讬拽转 鈥�%1$s鈥�</string>
+    <string name="title_activity_world_clock_details">驻专讟讬 讛砖注讜谉 讛注讜诇诪讬</string>
+    <string name="activity_type_outdoor_running">专讬爪讛 讘讞讜抓</string>
+    <string name="activity_type_freestyle">住讙谞讜谉 讞讜驻砖讬</string>
+    <string name="activity_type_outdoor_cycling">专讻讬讘转 讗讜驻谞讬讬诐 讘讞讜抓</string>
+    <string name="activity_type_elliptical">讗诇讬驻讟讬拽诇 讘诇讬 讬讚讬讜转</string>
+    <string name="mi5_prefs_workout_activity_types_summary">谞讗 诇讘讞讜专 讗转 住讜讙讬 讛驻注讬诇讜转 诇讛爪讙讛 讘诪住讱 讛讗讬诪讜谉</string>
+    <string name="mi5_prefs_workout_activity_types">住讜讙讬 驻注讬诇讜转 讗讬诪讜谉</string>
+    <string name="pref_screen_vibration_patterns_title">转讘谞讬讜转 专讟讟</string>
+    <string name="pref_screen_vibration_patterns_summary">讛讙讚专转 转讘谞讬讜转 专讟讟 诇讛转专讗讜转 讛砖讜谞讜转</string>
+    <string name="pref_screen_notification_profile_event_reminder">诪转讝讻专 讗讬专讜注讬诐</string>
+    <string name="pref_screen_notification_idle_alerts">讛转专讗讜转 讘讟诇讛</string>
+    <string name="pref_screen_notification_profile_find_device">讗讬转讜专 讛诪讻砖讬专</string>
+    <string name="devicetype_sony_wf_1000xm3">Sony WF-1000XM3</string>
+    <string name="prefs_switch_control_left">讘讜专专 砖诪讗诇</string>
+    <string name="pref_title_touch_ambient">爪诇讬诇 讛拽驻讬</string>
+    <string name="heartrate_bpm_110">讚讜驻拽 110</string>
+    <string name="heartrate_bpm_120">讚讜驻拽 120</string>
+    <string name="heartrate_bpm_125">讚讜驻拽 125</string>
+    <string name="heartrate_bpm_135">讚讜驻拽 135</string>
+    <string name="heartrate_bpm_140">讚讜驻拽 140</string>
+    <string name="prefs_stress_monitoring_description">诪注拽讘 讗讞专 专诪讜转 诇讞抓 讘讝诪谉 诪谞讜讞讛</string>
+    <string name="prefs_activity_monitoring_title">诪注拽讘 驻注讬诇讜转</string>
+    <string name="prefs_activity_monitoring_description">诇讛讙讘讬专 讗转 转讚讬讙讜转 讘讚讬拽转 讛讚讜驻拽 讻砖讛爪诪讬讚 诪讝讛讛 驻注讬诇讜转 讙讜驻谞讬诐, 讻讚讬 诇砖驻专 讗转 讚讬讜拽 诇讻讬讚转 讛讚讜驻拽.</string>
+    <string name="mi2_prefs_heart_rate_monitoring_summary">讛讙讚专转 诪注拽讘 讗讞专 讚讜驻拽</string>
+    <string name="prefs_seamless_connection_switch_title">诪注讘专 讞诇拽 讘讬谉 讞讬讘讜专讬诐</string>
+    <string name="prefs_seamless_connection_switch_summary">诪注讘讬专 讗转 讛讗讜讝谞讬讜转 讘讬谉 讛讛转拽谞讬诐 讛诪爪讜诪讚讬诐 讗讜讟讜诪讟讬转</string>
+    <string name="prefs_ambient_volume_left">注爪诪转 砖诪注 讛拽驻讬转 砖诪讗诇</string>
+    <string name="prefs_ambient_volume_right">注爪诪转 砖诪注 讛拽驻讬转 讬诪讬谉</string>
+    <string name="prefs_customize_ambient_sound_summary">讛转讗诪转 爪诇讬诇讬诐 讛拽驻讬讬诐</string>
+    <string name="prefs_ambient_sound_during_call_summary">诇砖诪讜注 讗转 讛拽讜诇 砖诇讱 讘讘讛讬专讜转 讘诪讛诇讱 砖讬讞讜转</string>
+    <string name="prefs_ambient_settings_title">讗驻砖专讜讬讜转 爪诇讬诇 讛拽驻讬</string>
+    <string name="prefs_active_noise_cancelling_level">专诪转 讘讬讟讜诇 专注砖讬诐 驻注讬诇</string>
+    <string name="prefs_active_noise_cancelling_level_high">讙讘讜讛讛</string>
+    <string name="prefs_active_noise_cancelling_level_low">谞诪讜讻讛</string>
+    <string name="prefs_switch_control_right">讘讜专专 讬诪讬谉</string>
+    <string name="pref_title_touch_voice_assistant">诪住讬讬注 拽讜诇讬</string>
+    <string name="pref_title_touch_anc">讘讬讟讜诇 专注砖讬诐 驻注讬诇</string>
+    <string name="pref_title_touch_quick_ambient">爪诇讬诇 讛拽驻讬 诪讛讬专</string>
+    <string name="pref_title_touch_volume">注爪诪转 砖诪注</string>
+    <string name="pref_title_touch_spotify">Spotify</string>
+    <string name="pref_switch_noise_control">讛讞诇驻转 讘拽专转 砖诪注</string>
+    <string name="prefs_noise_control_with_one_earbud">砖诇讬讟讛 讘专注砖 注诐 讗讞转 讛讗讜讝谞讬讜转</string>
+    <string name="prefs_noise_control_with_one_earbud_summary">诇讗驻砖专 讘拽专转 专注砖 讘砖讬诪讜砖 讘讗讜讝谞讬讬讛 讗讞转 讘诇讘讚</string>
+    <string name="pref_ambient_sound_tone">讙讜讘讛 爪诇讬诇 讛拽驻讬</string>
+    <string name="pref_ambient_sound_tone_summary">诪专讱 诇爪诇讜诇</string>
+    <string name="pref_balance">讗讬讝讜谉</string>
+    <string name="pref_switch_controls_anc_ambient">讘讬讟讜诇 专注砖 鈫掆啇 讛拽驻讬</string>
+    <string name="pref_switch_controls_anc_off">讘讬讟讜诇 专注砖 鈫掆啇 讻讘讜讬</string>
+    <string name="pref_switch_controls_ambient_off">讛拽驻讬 鈫掆啇 讻讘讜讬</string>
+    <string name="prefs_noise_control">讘拽专转 专注砖</string>
+    <string name="prefs_voice_detect">讝讬讛讜讬 拽讜诇</string>
+    <string name="prefs_voice_detect_duration">诇住讬讬诐 诇讗讞专 讟讜讜讞 砖拽讟 砖诇:</string>
+    <string name="pref_voice_detect_duration_10">10 砖谞讬讜转</string>
+    <string name="prefs_voice_detect_summary">讛驻注诇转 爪诇讬诇 讛拽驻讬 讜讛讞诇砖转 注讜爪诪转 讛谞讙讬谞讛 讗讜讟讜诪讟讬转 诇讗讞专 砖讝讜讛讛 拽讜诇</string>
+    <string name="prefs_double_tap_edge">谞讙讬注讛 讻驻讜诇讛 讘拽爪讛</string>
+    <string name="pref_voice_detect_duration_15">15 砖谞讬讜转</string>
+    <string name="prefs_in_ear_detection_summary">诇讛砖诪讬注 砖讬讞讜转 讚专讱 讛讗讜讝谞讬讜转 砖诇讱 讻砖讛谉 讘转讜讱 讛讗讜讝谞讬讬诐</string>
+    <string name="heartrate_bpm_100">讚讜驻拽 100</string>
+    <string name="heartrate_bpm_105">讚讜驻拽 105</string>
+    <string name="heartrate_bpm_112">讚讜驻拽 112</string>
+    <string name="prefs_heartrate_alert_threshold">住祝 讛转专讗转 讚讜驻拽</string>
+    <string name="heartrate_bpm_130">讚讜驻拽 130</string>
+    <string name="prefs_stress_monitoring_title">诪注拽讘 讗讞专 诇讞抓</string>
+    <string name="heartrate_bpm_145">讚讜驻拽 145</string>
+    <string name="heartrate_bpm_150">讚讜驻拽 150</string>
+    <string name="mi2_prefs_heart_rate_monitoring">诪注拽讘 讗讞专 讚讜驻拽</string>
+    <string name="prefs_heartrate_alert_experimental_title">讛转专讗转 讚讜驻拽 (谞讬住讬讜谞讬)</string>
+    <string name="pref_voice_detect_duration_5">5 砖谞讬讜转</string>
+    <string name="mi2_prefs_heart_rate_monitoring_alerts_summary">讛讙讚专转 诪注拽讘 讗讞专 讚讜驻拽 讜住祝 讛转专讗讛</string>
+    <string name="devicetype_galaxybuds_pro">Galaxy Buds Pro</string>
+    <string name="prefs_ambient_sound_during_call_title">爪诇讬诇 讛拽驻讬 讘诪讛诇讱 砖讬讞讛</string>
+    <string name="prefs_double_tap_edge_summary">诇讝讛讜转 谞讙讬注讛 讻驻讜诇讛 讻砖诇讗 讘讜爪注讛 讘诪砖讟讞 讛诪讙注</string>
+    <string name="prefs_heartrate_alert_experimental_description">诇讛驻注讬诇 讗转 讛专讟讟 讘爪诪讬讚 讻讗砖专 讛讚讜驻拽 讬讜专讚 诪转讞转 诇住祝, 诇诇讗 驻注讬诇讜转 驻讬讝讬转 诪讜讘讛拽转 讘志10 讛讚拽讜转 讛讗讞专讜谞讜转. 讬讻讜诇转 讝讗转 谞诪爪讗转 讘砖诇讘讬 谞讬住讜讬 讜诇讗 谞讘讚拽讛 讘讗讜驻谉 拽驻讚谞讬.</string>
+    <string name="pref_title_banglejs_text_bitmap">讟拽住讟 讻诪驻转 住讬讘讬讜转</string>
+    <string name="pref_title_device_internet_access">诇讗驻砖专 讙讬砖讛 诇讗讬谞讟专谞讟</string>
+    <string name="pref_summary_device_internet_access">诇讗驻砖专 诇讬讬砖讜诪讜谞讬诐 讘诪讻砖讬专 讛讝讛 诇讙砖转 诇讗讬谞讟专谞讟</string>
+    <string name="pref_summary_banglejs_text_bitmap">讗诐 讗讬 讗驻砖专 诇注讘讚 转诪讜谞讜转 注诐 讙讜驻谉 讛砖注讜谉, 讛讜讗 讬讜诪专 诇诪驻转 住讬讘讬讜转 讘志Gadgetbridge 砖转讜爪讙 讘砖注讜谉</string>
+    <string name="pref_summary_device_intents">诇讗驻砖专 诇讬讬砖讜诪讜谞讬 砖注讜谉 砖诇 Bangle.js 诇砖诇讜讞 Intents 诇志Android 讜诇讗驻砖专 诇讬讬砖讜诪讬 Android 讗讞专讬诐 (讻诪讜 Tasker) 诇砖诇讜讞 谞转讜谞讬诐 诇志Bangle.js 讘讗诪爪注讜转 讛志Intent 鈥廲om.banglejs.uart.tx. 讚讜专砖 讛专砖讗讜转 讻讚讬 诇讛爪讬讙 注诇 讙讘讬 讬讬砖讜诪讜谞讬诐 讗讞专讬诐 砖驻讜注诇讬诐 讘专拽注.</string>
+    <string name="pref_title_device_intents">诇讗驻砖专 Intents</string>
+    <string name="pref_workout_send_gps_title">砖诇讬讞转 GPS 讘诪讛诇讱 讗讬诪讜谉</string>
+    <string name="pref_workout_send_gps_summary">砖诇讬讞转 诪讬拽讜诐 讛志GPS 讛谞讜讻讞讬 诇爪诪讬讚 讘诪讛诇讱 讗讬诪讜谉</string>
+    <string name="notification_gps_title">GPS 砖诇 Gadgetbridge</string>
+    <string name="notification_gps_text">砖诇讬讞转 诪讬拽讜诐 GPS 诇志%1$d 诪讻砖讬专讬诐</string>
+    <string name="pref_device_action_phone_gps_location_listener_stop">注爪讬专转 讛讗讝谞转 诪讬拽讜诐 GPS</string>
+    <string name="pref_workout_start_on_phone_title">诪注拽讘 讘讬讬砖讜诪讜谉 讞讬讟讜讘</string>
+    <string name="pref_workout_start_on_phone_summary">讛转讞诇转/注爪讬专转 诪注拽讘 讘讬讬砖讜诪讜谉 讞讬讟讜讘 讘讟诇驻讜谉 讻讗砖专 诪讜驻注诇 讗讬诪讜谉 GPS 讘爪诪讬讚</string>
+    <string name="notification_channel_gps">诪注拽讘 GPS</string>
+    <string name="discovery_scanning_intensity">注讜爪诪转 住专讬拽讛</string>
+    <string name="discovery_scanning_intensity_warning">讗诐 讞讜讜讬转 拽驻讗讜谉 讗讜 讞讜住专 转讙讜讘转讬讜转, 讻讚讗讬 诇谞住讜转 诇砖谞讜转 讗转 注讜爪诪转 讛住专讬拽讛 诇专诪讛 谞诪讜讻讛. 讗诐 讛诪讻砖讬专 砖诇讱 诇讗 诪转讙诇讛, 讻讚讗讬 诇谞住讜转 诇砖谞讜转 讗转 注讜爪诪转 讛住专讬拽讛 诇专诪讛 讙讘讜讛讛.</string>
+    <string name="portuguese_pt">驻讜专讟讜讙诇讬转 砖诇 驻讜专讟讜讙诇</string>
+    <string name="quick_alarm_description">砖注讜谉 诪注讜专专 诪讜讜讬讚讙壮讟</string>
+    <string name="portuguese_br">驻讜专讟讜讙诇讬转 讘专讝讬诇讗讬转</string>
+    <string name="quick_alarm">砖注讜谉 诪注讜专专 诪讛讬专</string>
+    <string name="pref_title_banglejs_webview_url">讻转讜讘转 讟讜注谉 讬讬砖讜诪讜谉</string>
+    <string name="pref_summary_banglejs_webview_url">讗诐 讘专爪讜谞讱 诇讛砖转诪砖 讘讟讜注谉 讬讬砖讜诪讜谉 讗讞专 谞讗 诇爪讬讬谉 讗转 讛讻转讜讘转 https://鈥�/android.html, 讗诐 诇讗 讗驻砖专 诇讛砖讗讬专 专讬拽 讻讚讬 诇讛砖转诪砖 讘志https://banglejs.com/apps</string>
+    <string name="permission_notification_listener">诇志%1$s 讚专讜砖讛 讙讬砖讛 诇讛转专讗讜转 讻讚讬 诇讛爪讬讙 讗讜转谉 讘砖注讜谉 砖诇讱 讻讗砖专 诪住讱 讛讟诇驻讜谉 砖诇讱 讻讘讜讬.
+\n
+\n谞讗 诇讙注转 讘志鈥�%2$s鈥� 讜讗讝 鈥�%1$s鈥� 讜诇讛驻注讬诇 讗转 鈥氉溩愖ぷ┳� 讙讬砖讛 诇讛转专讗讜转鈥� 讜讗讝 诇讙注转 讘鈥氉椬栕ㄗ斺�� 讻讚讬 诇讞讝讜专 讗诇 %1$s.</string>
+    <string name="permission_location">诇志%1$s 讚专讜砖讛 讙讬砖讛 诇诪讬拽讜诐 砖诇讱 讘专拽注 讻讚讬 诇讗驻砖专 诇讜 诇讛讬砖讗专 诪讞讜讘专 诇砖注讜谉 砖诇讱 讙诐 讻讗砖专 讛诪住讱 砖诇讱 讻讘讜讬. \n \n谞讗 诇讘讞讜专 讘鈥�%2$s鈥� 讘诪住讱 讛讘讗, 讜讗讝 诇诇讞讜抓 注诇 鈥氉椬栕ㄗ斺�� 讻讚讬 诇讞讝讜专 讗诇 %1$s.</string>
+    <string name="permission_notification_policy_access">诇志%1$s 讚专讜砖讛 讙讬砖讛 诇讛讙讚专讜转 诇讗 诇讛驻专讬注 讻讚讬 诇讻讘讚 讗讜转谉 讘砖注讜谉 砖诇讱 讻讗砖专 诪住讱 讛讟诇驻讜谉 砖诇讱 讻讘讜讬.
+\n
+\n谞讗 诇讙注转 讘志鈥�%2$s鈥� 讜讗讝 鈥�%1$s鈥� 讜诇讛驻注讬诇 讗转 鈥氉溩愖ぷ┳� 诇讗 诇讛驻专讬注鈥� 讜讗讝 诇讙注转 讘鈥氉椬栕ㄗ斺�� 讻讚讬 诇讞讝讜专 讗诇 %1$s.</string>
+    <string name="extended_ascii">ASCII 诪讜专讞讘</string>
+    <string name="scandinavian">住拽谞讚讬谞讘讬转</string>
+    <string name="icelandic">讗讬住诇谞讚讬转</string>
+    <string name="czech">爪壮讻讬转</string>
+    <string name="lithuanian">诇讬讟讗讬转</string>
+    <string name="ukranian">讗讜拽专讗讬谞讬转</string>
+    <string name="persian">驻专住讬转</string>
+    <string name="bengali">讘谞讙诇讬转</string>
+    <string name="estonian">讗住讟讜谞讬转</string>
+    <string name="info_no_devices_connected">讗讬谉 讛转拽谞讬诐 诪讞讜讘专讬诐</string>
+    <string name="info_connected_count">%d 讛转拽谞讬诐 诪讞讜讘专讬诐</string>
+    <string name="pref_title_notification_delay_calls">讛砖讛讬讬转 讛讜讚注转 砖讬讞讛</string>
+    <string name="pref_summary_notification_delay_calls">讛砖讛讬讛 讘讟专诐 砖诇讬讞转 讛讜讚注讜转 注诇 砖讬讞讜转 谞讻谞住讜转 诇讛转拽谉, 讘砖谞讬讜转.</string>
+    <string name="prefs_password_summary">诇谞注讜诇 讗转 讛爪诪讬讚 注诐 住讬住诪讛 讻砖讛讜讗 诇讗 谞注谞讚</string>
+    <string name="prefs_password_enabled">住讬住诪讛 诪讜驻注诇转</string>
+    <string name="prefs_password_4_digits_1_to_4_summary">讛住讬住诪讛 讞讬讬讘转 诇讛讬讜转 讘讗讜专讱 4 住驻专讜转 讜注诐 讛诪住驻专讬诐 1 注讚 4</string>
+    <string name="prefs_password_6_digits_0_to_9_summary">讛住讬住诪讛 讞讬讬讘转 诇讛讬讜转 讘讗讜专讱 6 住驻专讜转 注诐 诪住驻专讬诐 讘诇讘讚</string>
+    <string name="controlcenter_connected_fraction">诪讞讜讘专: %d/%d</string>
+    <string name="pref_blacklist_calendars_summary">诇讜讞讜转 砖谞讛 讞住讜诪讬诐 诇讗 讬住讜谞讻专谞讜 诇诪讻砖讬专</string>
+    <string name="prefs_password">住讬住诪讛</string>
+    <string name="error_deleting_device">砖讙讬讗讛 讘诪讞讬拽转 讛转拽谉: %s</string>
+    <string name="controlcenter_set_parent_folder">讛讙讚专转 转讬拽讬讬转 讛讜专讛</string>
+    <string name="controlcenter_set_preferences">讛讙讚专转 讛注讚驻讜转</string>
+    <string name="pref_explanation_authkey">讞诇拽 诪讛诪讻砖讬专讬诐 讚讜专砖讬诐 诪驻转讞 爪讬诪讜讚 诪讬讜讞讚 注诐 讛驻注诇转 讛诪讻砖讬专 诇专讗砖讜谞讛. 讘谞讙讬注讛 谞讬转谉 诇拽讘诇 驻专讟讬诐 谞讜住驻讬诐 讘讜讜讬拽讬.</string>
+    <string name="pref_explanation_authkey_new_protocol">讗诐 诪讜驻讬注讛 讛讛讜讚注讛 鈥濽pdate to the latest version鈥� 讘爪诪讬讚, 谞讗 诇讜讜讚讗 砖住讬诪谞转 讗转 鈥炞ぷㄗ曌樧曌ё曌� 讗讬诪讜转 讞讚砖鈥� 诇注讬诇. 谞讙讬注讛 讻讗谉 转注讘讬专 讗讜转讱 诇驻专讟讬诐 谞讜住驻讬诐 讘讜讜讬拽讬.</string>
+    <string name="controlcenter_toggle_details">讛讞诇驻转 转爪讜讙转 驻专讟讬诐</string>
+    <string name="error_setting_parent_folder">砖讙讬讗讛 讘讛讙讚专转 转讬拽讬讬转 讛讜专讛: %s</string>
+    <string name="controlcenter_add_new_folder">讛讜住驻转 转讬拽讬讬讛 讞讚砖讛</string>
+    <string name="controlcenter_unset_folder">讘讬讟讜诇 讛讙讚专转 转讬拽讬讬讛</string>
+    <string name="auto_reconnect_ble_title">讛转讞讘专讜转 讗讜讟讜诪讟讬转 诪讞讚砖 诇讛转拽谉</string>
+    <string name="controlcenter_folder_name">砖诐 转讬拽讬讬讛:</string>
+    <string name="controlcenter_set_folder_title">讛讙讚专讛 讗讜 讬爪讬专讛 砖诇 转讬拽讬讬讛 讞讚砖讛</string>
+    <string name="auto_reconnect_ble_summary">诇谞住讜转 诇讬讝讜诐 讞讬讘讜专 诇讛转拽谉 诪讚讬 驻注诐 讘驻注诐</string>
+    <string name="connection_over_bt_classic">讞讬讘讜专 讚专讱 讘诇讜讟讜转壮 拽诇住讬</string>
+    <string name="autoconnect_from_device_title">讛转讞讘专讜转 注诐 讞讬讘讜专 诪讛诪讻砖讬专</string>
+    <string name="connection_over_ble">讞讬讘讜专 讚专讱 BLE</string>
+    <string name="watchface_dialog_widget_timezone_duration">诪砖讱 讛讜驻注转 讛砖注讜谉 (讘砖谞讬讜转)</string>
+    <string name="autoconnect_from_device_summary">讛拽诪转 讞讬讘讜专 讻讗砖专 讛讛转拽谉 讬讜讝诐 讞讬讘讜专 讻诪讜 讗讜讝谞讬讜转</string>
+    <string name="open_fw_installer_warning_title">讗讝讛专讛</string>
+    <string name="open_fw_installer_pick_file">讘讞讬专转 拽讜讘抓</string>
+    <string name="open_fw_installer_select_file">谞讗 诇讘讞讜专 拽讜讘抓 砖讘专爪讜谞讱 诇讛注诇讜转 诇讛转拽谉: %s</string>
+    <string name="open_fw_installer_connect_minimum_one_device">谞讗 诇讛转讞讘专 诇讛转拽谉 讗讞讚 诇驻讞讜转 讗诇讬讜 讘专爪讜谞讱 诇砖诇讜讞 讗转 讛拽讜讘抓.</string>
+    <string name="open_fw_installer_ensure_device_connected">谞讗 诇讜讜讚讗 砖讛讛转拽谉 %s 诪讞讜讘专</string>
+    <string name="open_fw_installer_getting_files_text">诪讗讞专 砖讗住讜专 诇谞讜 诇讛驻讬抓 拽讜讘爪讬 拽讜砖讞讜转, 讬讛讬讛 注诇讬讱 诇讛砖讬讙 讗转 讛拽讘爪讬诐 讘注爪诪讱. 诪砖诪注讜转 讛讚讘专 讛讬讗 砖爪专讬讱 诇讞驻砖 讗转 讛拽讘爪讬诐 讘拽讜讘爪讬 讛志apk, 讘专砖转, 讘驻讜专讜诪讬诐 讘志Amazfitwatchfaces (注讘讜专 讛转拽谞讬 Miband/Amazfit) 讜讻谉 讛诇讗讛.</string>
+    <string name="open_fw_installer_info_text_title">诪转拽讬谉 拽讘爪讬诐</string>
+    <string name="open_fw_installer_connect_maximum_one_device">谞讗 诇讛转讞讘专 诇讛转拽谉 讗讞讚 讘诇讘讚 讗诇讬讜 讘专爪讜谞讱 诇砖诇讜讞 讗转 讛拽讜讘抓.</string>
+    <string name="open_fw_installer_warning_text">讬讻讜诇转 讝讜 讬讻讜诇讛 诇驻讙讜诐 讘诪讻砖讬专 砖诇讱. 注诐 讝讗转, 讝讛 诪注讜诇诐 诇讗 拽专讛 诇讗祝 讗讞讚 诪讛诪转讻谞转讬诐 讘诪讛诇讱 讛爪专讬讘讛 讗讱 讞砖讜讘 诇讝讻讜专 砖讝讛 讘讗讞专讬讜转讱 讘诇讘讚.</string>
+    <string name="open_fw_installer_getting_files_title">拽讜讘抓 讛拽讜砖讞讛/讛讬讬砖讜诪讜谉 诪转拽讘诇</string>
+    <string name="appmanager_item_outdated">(诇讗 注讚讻谞讬)</string>
+    <string name="appmanager_app_share">砖讬转讜祝</string>
+    <string name="open_fw_installer_info_text">诪转拽讬谉 讛拽讜砖讞讜转/驻谞讬 砖注讜谉/讬讬砖讜诪讜谞讬诐/拽讘爪讬诐 诪讗驻砖专 诇讱 诇讛注诇讜转/诇讛转拽讬谉 拽讘爪讬诐 谞转诪讻讬诐 (拽讜砖讞讛, 驻谞讬 砖注讜谉, 讬讬砖讜诪讜谞讬诐, GPS, 诪砖讗讘讬诐, 讙讜驻谞讬诐鈥�) 诇诪讻砖讬专. 讬砖 诪讬讚注 谞讜住祝 讘讜讜讬拽讬: https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Firmware-Update</string>
+    <string name="watchface_dialog_widget_background">专拽注</string>
+    <string name="hybridhr_widget_bg_thin_circle">注讬讙讜诇 讚拽</string>
+    <string name="hybridhr_widget_bg_double_circle">注讬讙讜诇 讻驻讜诇</string>
+    <string name="watchface_dialog_widget_cat_generic">讻诇诇讬</string>
+    <string name="watchface_dialog_widget_cat_2nd_tz_widget">讜讬讚讙壮讟 讗讝讜专 讝诪谉 诪砖谞讬</string>
+    <string name="hybridhr_widget_bg_dashed_circle">注讬讙讜诇 诪拽讜讜拽讜讜</string>
+    <string name="watchface_dialog_widget_cat_position">诪讬拽讜诐</string>
+    <string name="watchface_dialog_widget_cat_custom_widget">讜讬讚讙壮讟 诪砖诇讱</string>
+    <string name="watchface_dialog_title_widget">讛讙讚专讜转 讜讬讚讙壮讟</string>
+    <string name="pref_title_banglejs_txt_bitmap_size">讙讜讚诇 诪驻讜转 住讬讘讬讜转 砖诇 讻转讘</string>
+    <string name="pref_summary_banglejs_txt_bitmap_size">讛讙讜讚诇 诇砖讬诪讜砖 诇注讬讘讜讚 诪驻讜转 住讬讘讬讜转 砖诇 讟拽住讟</string>
+    <string name="steps_streaks_achievement_rate">拽爪讘
+\n讛讬砖讙讬讜转</string>
+    <string name="share_log_not_enabled_message">注诇讬讱 诇讛驻注讬诇 转讬注讜讚 诇讬讜诪谞讬诐 转讞讬诇讛 讚专讱 讛讙讚专讜转 - 讻转讬讘转 拽讜讘爪讬 讬讜诪谉</string>
+    <string name="steps_streaks">专爪驻讬 爪注讬讚讛</string>
+    <string name="steps_streaks_hint">专爪祝 砖诇 讬诪讬诐 诇诇讗 讛驻住拽讛 讘讛诐 讛讙注转 诇讬注讚 讛爪注讚讬诐</string>
+    <string name="step_streak_ongoing">诪转诪砖讱</string>
+    <string name="step_streak_longest">讛讗专讜讱 讘讬讜转专</string>
+    <string name="step_streak_total">住讱 讛讻讜诇</string>
+    <string name="steps_streaks_since_date">诪讗讝 %s</string>
+    <string name="step_streak_average_steps_hint">爪注讚讬诐 讘诪诪讜爪注 诇讬讜诐 讘专爪祝</string>
+    <string name="steps_streaks_total_steps_hint_totals">诪住驻专 讛爪注讚讬诐 讛讻讜诇诇 砖转讜注讚讜 讗讬志驻注诐</string>
+    <string name="steps_streaks_total_days_hint_totals">讗讞讜讝 讛讬诪讬诐 讘讛诐 讛讙注转 诇讬注讚 讘讬讞住 诇讻诇 讛讬诪讬诐 讘讛诐 爪注讚转</string>
+    <string name="step_streak_days_hint">诪住驻专 讛讬诪讬诐 讛专爪讜驻讬诐 讘讛诐 讛讙注转 诇讬注讚 讛爪注讚讬诐</string>
+    <string name="steps_streaks_total_steps_hint">诪住驻专 讛爪注讚讬诐 讛讻讜诇诇 讘讻诇 讛专爪祝</string>
+    <string name="steps_streaks_total_steps_average_hint">诪诪讜爪注 讻诇诇讬 %d 爪注讚讬诐 讘讬讜诐</string>
+    <string name="steps_streaks_total_steps">住讱
+\n爪注讚讬诐</string>
+    <string name="steps_streaks_streak_days">讬诪讬
+\n专爪祝</string>
+    <string name="steps_streaks_average_steps">爪注讚讬诐
+\n讘诪诪讜爪注</string>
+    <string name="note">驻转拽</string>
+    <string name="watchface_setting_title_button_toggle_widgets">谞讬讛讜诇 讜讬讚讙壮讟讬诐</string>
+    <string name="watchface_setting_button_toggle_widgets">谞讬讛讜诇 讜讬讚讙壮讟讬诐</string>
+    <string name="watchface_setting_title_custom_events">讗讬专讜注讬诐 讘讛转讗诪讛 讗讬砖讬转</string>
+    <string name="watchface_setting_button_toggle_backlight">讛驻注诇转 转讗讜专讛 讗讞讜专讬转</string>
+    <string name="controlcenter_get_heartrate_measurement">诪砖讬讻转 诪讚讬讚转 讚讜驻拽</string>
+    <string name="pref_write_logfiles_not_available">讛转讞诇转 转讬注讜讚 诇拽讘爪讬诐 谞讻砖诇讛, 讻转讬讘转 拽讜讘爪讬 讬讜诪谉 讗讬谞讛 讝诪讬谞讛 讻注转. 讬砖 诇讛驻注讬诇 讗转 讛讬讬砖讜诪讜谉 诪讞讚砖 讻讚讬 诇谞住讜转 诇讛转讞讬诇 讗转 拽讜讘爪讬 讛讬讜诪谉 诪讞讚砖.</string>
+    <string name="devicetype_binary_sensor">讞讬讬砖谉 讘讬谞专讬</string>
+    <string name="about_activity_title_banglejs_nightly">注诇 Bangle.js Gadgetbridge (Nightly)</string>
+    <string name="application_name_banglejs_nightly">Bangle.js Gadgetbridge (Nightly)</string>
+    <string name="gadgetbridge_running_banglejs_nightly">Nightly Bangle.js 驻注讬诇</string>
+    <string name="title_activity_controlcenter_banglejs_nightly">Bangle.js Gadgetbridge (Nightly)</string>
+    <string name="step_streaks_achievements_sharing_message">讛讬砖讙讬 讛爪注讚讬诐 讛讬讜诪讬讬诐 砖诇讬!</string>
+    <string name="step_streaks_achievements_sharing_title">讛讬砖讙讬 爪注讚讬诐</string>
+    <string name="prefs_hourly_chime_summary">讛砖注讜谉 讬爪驻爪祝 讻诇 砖注讛</string>
+    <string name="prefs_hourly_chime">讛转专讗讛 砖注转讬转</string>
+    <string name="about_description_banglejs_nightly">讬讬砖讜诪讜谉 谞诇讜讜讛 诇志Bangle.js 诇住讘讬讘转 Android 砖谞讘谞讛 注诇 讙讘讬 诪讬讝诐 Gadgetbridge 讘转讜住驻转 讙讬砖讛 诇讗讬谞讟专谞讟.
+\n
+\n注拽讘 诪讚讬谞讬讜转 砖诇 讞谞讜转 Google Play 讗住讜专 诇谞讜 诇讛爪讬讘 拽讬砖讜专 转专讜诪讛 讘讬讬砖讜诪讜谉 注爪诪讜 讗讘诇 讗诐 讛讬讬砖讜诪讜谉 讛讝讛 谞讜砖讗 讞谉 讘注讬谞讬讱, 谞讗 诇砖拽讜诇 诇转专讜诐 诇讜 讚专讱 讛讗转专 砖诇 Gadgetbridge 砖诇讛诇谉.</string>
+    <string name="permission_display_over_other_apps">%1$s 讝拽讜拽 诇讛专砖讗讜转 讻讚讬 诇讛爪讬讙 注诇 驻谞讬 讬讬砖讜诪讜谞讬诐 讗讞专讬诐 讻讚讬 诇讗驻砖专 诇砖注讜谞讬 Bangle.js 诇讛专讬抓 驻注讬诇讜讬讜转 讚专讱 Intens 讻讗砖专 %1$s 驻讜注诇 讘专拽注.
+\n
+\n讗驻砖专 诇讛砖转诪砖 讘讝讛 讻讚讬 诇讛驻注讬诇 讬讬砖讜诪讜谉 诪讜讝讬拽讛 讜诇谞讙谉 砖讬专 讜诇注讜讚 诪讙讜讜谉 诪砖讬诪讜转.
+\n
+\n谞讗 诇讙注转 讘志鈥�%2$s鈥� 讜讗讝 讘志鈥�%1$s鈥� 讜诇住诪谉 讗转 鈥氉溩愖ぷ┳� 讛爪讙讛 注诇 讙讘讬 讬讬砖讜诪讜谞讬诐 讗讞专讬诐鈥�, 讜讗讝 诇讙注转 讘志鈥氉椬栕ㄗ斺�� 讻讚讬 诇讞讝讜专 讗诇 %1$s.
+\n
+\n讻讚讬 诇注爪讜专 讗转 讘拽砖转 讛讛专砖讗讜转 诪爪讚 %1$s 讬砖 诇讙砖转 讗诇 讛讛讙讚专讜转 讜诇讘讟诇 讗转 讛住讬诪讜谉 鈥氉懽撟欁ё� 诪爪讘 讛专砖讗讜转鈥�.
+\n
+\n谞讗 诇讜讜讚讗 砖讛注谞拽转 诇志%1$s 讗转 讛讛专砖讗讜转 讛谞讚专砖讜转 讻讚讬 砖讬驻注诇 讻专讗讜讬.</string>
+    <string name="pref_header_calendar">诇讜讞 砖谞讛</string>
+    <string name="pref_header_connection">讞讬讘讜专</string>
+    <string name="pref_header_display">转爪讜讙讛</string>
+    <string name="pref_header_health">讘专讬讗讜转</string>
+    <string name="pref_header_time">砖注讛</string>
+    <string name="pref_header_workout">讗讬诪讜谉</string>
+    <string name="pref_summary_canned_replies">转讙讜讘讛 诪讛砖注讜谉 注诐 讛讜讚注讜转 诪讜讙讚专讜转 诪专讗砖</string>
+    <string name="pref_title_screen_on_on_notifications">诪住讱 驻注讬诇 讘讛转专讗讜转</string>
+    <string name="pref_summary_screen_on_on_notifications">诇讛驻注讬诇 讗转 诪住讱 讛爪诪讬讚 讻砖诪讙讬注讛 讛转专讗讛</string>
+    <string name="pref_cache_weather">砖诪讬专转 驻专讟讬 诪讝讙 讛讗讜讜讬专</string>
+    <string name="pref_cache_weather_summary">驻专讟讬 诪讝讙 讛讗讜讜讬专 讬讬砖诪专讜 讘讬谉 讛驻注诇讜转 砖诇 讛讬讬砖讜诪讜谉 诪讞讚砖.</string>
+    <string name="spo2_perc_80">80%</string>
+    <string name="spo2_perc_85">85%</string>
+    <string name="spo2_perc_90">90%</string>
+    <string name="prefs_heartrate_alert_high_threshold">住祝 讗讝讛专转 讚讜驻拽 讙讘讜讛</string>
+    <string name="prefs_relaxation_reminder_description">诇讛专讟讬讟 讗转 讛爪诪讬讚 讻讚讬 诇讛讜讚讬注 诇讱 讗诐 注专讱 讛诇讞抓 注讜诇讛 注诇 80</string>
+    <string name="prefs_spo2_alert_threshold">住祝 讗讝讛专转 SpO鈧�</string>
+    <string name="seconds_6">6 砖谞讬讜转</string>
+    <string name="seconds_7">7 砖谞讬讜转</string>
+    <string name="prefs_always_on_display">转爪讜讙讛 驻注讬诇讛 转诪讬讚</string>
+    <string name="prefs_always_on_display_summary">诇讛砖讗讬专 讗转 转爪讜讙转 讛爪诪讬讚 驻注讬诇讛 转诪讬讚</string>
+    <string name="prefs_screen_timeout">转讜诐 讝诪谉 讛诪住讱</string>
+    <string name="smart">讞讻诐</string>
+    <string name="mi2_dnd_always">转诪讬讚</string>
+    <string name="devicetype_miband7">Xiaomi Smart Band 7鈥�</string>
+    <string name="menuitem_countdown">住驻讬专讛 诇讗讞讜专</string>
+    <string name="menuitem_personal_activity_intelligence">讞讜讻诪转 驻注讬诇讜转 讗讬砖讬转</string>
+    <string name="menuitem_workout_history">讛讬住讟讜专讬讬转 讗讬诪讜谞讬诐</string>
+    <string name="menuitem_female_health">讘专讬讗讜转 讛讗讬砖讛</string>
+    <string name="menuitem_workout_status">诪爪讘 讗讬诪讜谉</string>
+    <string name="pref_header_heartrate_sleep">砖讬谞讛</string>
+    <string name="pref_header_heartrate_allday">诪注拽讘 讻诇诇 讬讜诪讬</string>
+    <string name="pref_header_heartrate_alerts">讗讝讛专讜转 讚讜驻拽</string>
+    <string name="pref_header_stress">诇讞抓</string>
+    <string name="pref_header_spo2">讞诪爪谉 讘讚诐</string>
+    <string name="seconds_11">11 砖谞讬讜转</string>
+    <string name="heartrate_bpm_40">讚讜驻拽 40</string>
+    <string name="spo2_off">讻讘讜讬</string>
+    <string name="prefs_heartrate_alert_low_threshold">住祝 讗讝讛专转 讚讜驻拽 谞诪讜讱</string>
+    <string name="prefs_spo2_monitoring_title">诪注拽讘 讗讞专 讞诪爪谉 讘讚诐</string>
+    <string name="heartrate_bpm_50">讚讜驻拽 50</string>
+    <string name="heartrate_bpm_115">讚讜驻拽 115</string>
+    <string name="seconds_15">15 砖谞讬讜转</string>
+    <string name="heartrate_bpm_45">讚讜驻拽 45</string>
+    <string name="prefs_relaxation_reminder_title">转讝讻讜专转 诪谞讜讞讛</string>
+    <string name="prefs_spo2_monitoring_description">诇注拽讜讘 讗讜讟讜诪讟讬转 讗讞专 专诪讜转 讛讞诪爪谉 讘讚诐 讘诪讛诇讱 讛讬讜诐</string>
+    <string name="seconds_14">14 砖谞讬讜转</string>
+    <string name="seconds_13">13 砖谞讬讜转</string>
+    <string name="pref_sleep_breathing_quality_monitoring">诪注拽讘 讗讞专 讗讬讻讜转 谞砖讬诪讛 讘砖讬谞讛</string>
+    <string name="pref_screen_brightness">讘讛讬专讜转 讛诪住讱</string>
+    <string name="seconds_8">8 砖谞讬讜转</string>
+    <string name="seconds_9">9 砖谞讬讜转</string>
+    <string name="seconds_12">12 砖谞讬讜转</string>
+    <string name="fw_upgrade_notice_miband7">驻注讜诇讛 讝讜 转转拽讬谉 讗转 讛拽讜砖讞讛 %s 注诇 讛志Xiaomi Smart Band 7 砖诇讱.
+\n
+\n讛爪诪讬讚 砖诇讱 讬讜驻注诇 诪讞讚砖 诇讗讞专 讛转拽谞转 拽讜讘抓 讛志鈥�.zip.
+\n
+\n讛诪砖讱 讛转讛诇讬讱 讛讜讗 注诇 讗讞专讬讜转讱 讘诇讘讚!</string>
+    <string name="pref_title_notifications_ignore_low_priority">诇讛转注诇诐 诪讛讜讚注讜转 讘注讚讬驻讜转 谞诪讜讻讛</string>
+    <string name="pref_summary_notifications_ignore_low_priority">诇讗 诇砖诇讜讞 讛讜讚注讜转 讘注讚讬驻讜转 谞诪讜讻讛 讜诪讝注专讬转 诇砖注讜谉</string>
+    <string name="devicetype_flipper_zero">鈥嶧lipper zero</string>
+    <string name="activity_prefs_summary_allow_bluetooth_intent_api">诇讗驻砖专 砖诇讬讟讛 讘讞讬讘讜专 讛志Bluetooth 讚专讱 API 砖诇 Intent</string>
+    <string name="pref_summary_notification_prefer_long_text">讗诐 谞讬转谉, 诇砖诇讜讞 讗转 讛讟拽住讟 讛讗专讜讱 砖诇 讛讛转专讗讛 诇诪讻砖讬专</string>
+    <string name="pref_title_notification_prefer_long_text">诇讛注讚讬祝 讟拽住讟 讛转专讗讜转 讗专讜讱</string>
+    <string name="devicetype_amazfit_gts3">鈥嶢mazfit GTS 3</string>
+    <string name="fw_upgrade_notice_amazfit_gts3">驻注讜诇讛 讝讜 转转拽讬谉 讗转 讛拽讜砖讞讛 %s 讘志Amazfit GTS 3 砖诇讱.
+\n
+\n讛爪诪讬讚 砖诇讱 讬讜驻注诇 诪讞讚砖 诇讗讞专 讛转拽谞转 拽讜讘抓 讛志鈥�.zip
+\n
+\n讛诪砖讱 讛转讛诇讬讱 讛讜讗 注诇 讗讞专讬讜转讱!</string>
+    <string name="activity_prefs_allow_bluetooth_intent_api">API 砖诇 Bluetooth Intent</string>
+    <string name="kind_app">讬讬砖讜诪讜谉</string>
+    <string name="menuitem_unknown_app">诇讗 讬讚讜注 (%s)</string>
+    <string name="pref_title_overwrite_settings_on_connection">讚专讬住转 讛讙讚专讜转 注诐 讛转讞讘专讜转</string>
+    <string name="pref_summary_overwrite_settings_on_connection">讘注转 讛转讞讘专讜转 诇爪诪讬讚, 诇讚专讜住 讗转 讻诇 讛讛讙讚专讜转 砖讘讜.</string>
+    <string name="dismiss">讛转注诇诪讜转</string>
+    <string name="fw_upgrade_notice_amazfit_gtr3">驻注讜诇讛 讝讜 转转拽讬谉 讗转 讛拽讜砖讞讛 %s 注诇 讛志Amazfit GTR 3 砖诇讱.
+\n
+\n讛爪诪讬讚 砖诇讱 讬讜驻注诇 诪讞讚砖 诇讗讞专 讛转拽谞转 拽讜讘抓 讛志鈥�.zip.
+\n
+\n讛诪砖讱 讛转讛诇讬讱 讛讜讗 注诇 讗讞专讬讜转讱 讘诇讘讚!</string>
+    <string name="devicetype_amazfit_gtr3">Amazfit GTR 3鈥�</string>
+    <string name="activity_type_table_tennis">讟谞讬住 砖讜诇讞谉</string>
+    <string name="activity_type_bowling">讻讚讜专转 (讘讗讜诇讬谞讙)</string>
+    <string name="activity_type_street_dance">专讬拽讜讚 专讞讜讘</string>
+    <string name="activity_type_zumba">讝讜诪讘讛</string>
+    <string name="supercars_turbo_speed_label">诪讛讬专讜转 讙讘讜讛讛</string>
+    <string name="supercars_lights_label">讗讜专讜转</string>
+    <string name="pref_title_third_party_app_device_settings">诇讗驻砖专 诇讬讬砖讜诪讜谞讬 爪讚志砖诇讬砖讬 诇砖谞讜转 讛讙讚专讜转</string>
+    <string name="pref_summary_third_party_app_device_settings">诇讗驻砖专 诇讬讬砖讜诪讜谞讬 爪讚志砖诇讬砖讬 讗讞专讬诐 砖诪讜转拽谞讬诐 诇砖谞讜转 讛讙讚专讜转 讚专讱 Intents.</string>
+    <string name="activity_type_dance">专讬拽讜讚</string>
+    <string name="activity_type_indoor_fitness">讗讬诪讜谉 讘讬转讬</string>
+    <string name="activity_type_gymnastics">讻讜砖专</string>
+    <string name="activity_type_core_training">讗讬诪讜谉 诇讬讘讛</string>
+    <string name="activity_type_stretching">诪转讬讞讜转</string>
+    <string name="activity_type_stepper">住讟驻专</string>
+    <string name="activity_type_pilates">驻讬诇讟讬住</string>
+    <string name="activity_type_volleyball">讻讚讜专注祝</string>
+    <string name="activity_type_hiit">讗讬诪讜谉 讘注爪讬诪讜转 讙讘讜讛讛 注诐 讛驻住拽讜转</string>
+    <string name="activity_type_boxing">讗讬讙专讜祝</string>
+    <string name="activity_type_kickboxing">拽讬拽讘讜拽住讬谞讙</string>
+    <string name="activity_type_indoor_ice_skating">讛讞诇拽专讞 讘诪讘谞讛</string>
+    <string name="devicetype_super_cars">诪讬专讜抓 诪讻讜谞讬讜转</string>
+    <string name="devicetype_amazfit_gtr_lite">Amazfit GTR Lite鈥�</string>
+    <string name="watchface_setting_button_move_hands">讛讝讝转 讛诪讞讜讙讬诐</string>
+    <string name="pref_header_authentication">讗讬诪讜转</string>
+    <string name="supercars_lights_blinking_label">讛讘讛讜讘</string>
+    <string name="abstract_chart_fragment_kind_rem_sleep">砖讬谞讛 注诐 转谞讜注转 注讬谞讬讬诐 诪讛讬专讛</string>
+    <string name="sports_activity_confirm_delete_title">诪讞讬拽转 %d 驻注讬诇讜讬讜转</string>
+    <string name="sports_activity_confirm_delete_description">诇诪讞讜拽 %d 驻注讬诇讜讬讜转\?</string>
+    <string name="hrZoneExtreme">拽讬爪讜谞讬</string>
+    <string name="hrZoneAnaerobic">讗谞讗讬专讜讘讬</string>
+    <string name="anaerobicTrainingEffect">讛砖驻注讛 讗谞讗讬专讜讘讬转</string>
+    <string name="currentWorkoutLoad">注讜诪住 砖诇 讗讬诪讜谉</string>
+    <string name="hrZoneAerobic">讗讬专讜讘讬</string>
+    <string name="HeartRateZones">讗讝讜专讬 讚讜驻拽</string>
+    <string name="hrZoneWarmUp">讛转讞诪诪讜转</string>
+    <string name="hrZoneFatBurn">砖专讬驻转 砖讜诪谉</string>
+    <string name="TrainingEffect">讛砖驻注转 讗讬诪讜谉</string>
+    <string name="hrZoneNa">诇讗 讝诪讬谉</string>
+    <string name="aerobicTrainingEffect">讛砖驻注转 讗讬专讜讘讬</string>
+    <string name="maximumOxygenUptake">拽诇讬讟转 讞诪爪谉 诪专讘讬转</string>
+    <string name="elevationGain">爪讘讬专转 讙讜讘讛</string>
+    <string name="elevationLoss">讛谞诪讻转 讙讜讘讛</string>
+    <string name="pref_chart_sleep_lines_limit">讻诪讛 砖讜专讜转 诪讜驻注讬 砖讬谞讛 诇讛爪讬讙 诇驻谞讬 砖讬讚专砖讜 讙诇讬诇讛</string>
+    <string name="accuracy">讚讬讜拽</string>
+    <string name="power_saving">讞讬住讻讜谉 讘讞砖诪诇</string>
+    <string name="custom">诪砖诇讱</string>
+    <string name="balanced">诪讗讜讝谉</string>
+    <string name="devicetype_sony_wh_1000xm2">Sony WH-1000XM2鈥�</string>
+    <string name="devicetype_sony_wf_1000xm4">Sony WF-1000XM4鈥�</string>
+    <string name="single_band">转讚专 讗讞讚</string>
+    <string name="dual_band">砖谞讬 转讚专讬诐</string>
+    <string name="low_power_gps">GPS 讞住讻讜谞讬</string>
+    <string name="gps_bds">GPS + BDS</string>
+    <string name="gps_galileo">GPS + GALILEO</string>
+    <string name="all_satellites">讻诇 讛诇讜讜讬讬谞讬诐</string>
+    <string name="speed_first">转讬注讚讜祝 诪讛讬专讜转</string>
+    <string name="pref_header_sound_vibration">砖诪注 讜专讟讟</string>
+    <string name="pref_header_offline_voice">拽讜诇 讘诇转讬 诪拽讜讜谉</string>
+    <string name="pref_gps_header">GPS</string>
+    <string name="pref_gps_mode_preset">诪爪讘 GPS</string>
+    <string name="pref_gps_band">转讚专 GPS</string>
+    <string name="pref_gps_combination">砖讬诇讜讘 GPS</string>
+    <string name="pref_gps_satellite_search">讞讬驻讜砖 诇讜讜讬讬谞讬诐</string>
+    <string name="pref_alert_tone">爪诇讬诇 讛转专讗讛</string>
+    <string name="pref_cover_to_mute">讬砖 诇讻住讜转 讻讚讬 诇讛砖转讬拽</string>
+    <string name="pref_workout_detection_categories_title">拽讟讙讜专讬讜转 讗讬诪讜谉</string>
+    <string name="pref_workout_detection_title">讝讬讛讜讬 讗讬诪讜谉</string>
+    <string name="pref_workout_detection_categories_summary">拽讟讙讜专讬讜转 讗讬诪讜谉 诇讝讬讛讜讬 讗讜讟讜诪讟讬</string>
+    <string name="pref_workout_detection_alert_summary">诇讛讜讚讬注 讻讗砖专 讝讜讛讛 讗讬诪讜谉</string>
+    <string name="pref_sleep_mode_title">诪爪讘 砖讬谞讛</string>
+    <string name="pref_sleep_mode_sleep_screen_title">诪住讱 砖讬谞讛</string>
+    <string name="pref_sleep_mode_smart_enable_title">讛驻注诇讛 讞讻诪讛</string>
+    <string name="buttons_on_left">讻驻转讜专讬诐 诪砖诪讗诇</string>
+    <string name="buttons_on_right">讻驻转讜专讬诐 诪讬诪讬谉</string>
+    <string name="prefs_weardirection">讻讬讜讜谉 注谞讬讚讛</string>
+    <string name="vibration_profile_default">讘专讬专转 诪讞讚诇</string>
+    <string name="seconds_25">25 砖谞讬讜转</string>
+    <string name="prefs_always_on_display_follow_watchface">讛住讙谞讜谉 讛讜讗 讻诪讜 驻谞讬 讛砖注讜谉</string>
+    <string name="prefs_always_on_display_style">住讙谞讜谉</string>
+    <string name="menuitem_sun_moon">砖诪砖 讜讬专讞</string>
+    <string name="menuitem_todo">诪讟诇讜转</string>
+    <string name="menuitem_calendar">诇讜讞 砖谞讛</string>
+    <string name="menuitem_volume">注爪诪转 砖诪注</string>
+    <string name="menuitem_theater_mode">诪爪讘 转讬讗讟专讜谉</string>
+    <string name="menuitem_phone">讟诇驻讜谉</string>
+    <string name="menuitem_membership_cards">讻专讟讬住讬 讞讘专讜转</string>
+    <string name="menuitem_offline_voice">拽讜诇 讘诇转讬 诪拽讜讜谉</string>
+    <string name="menuitem_lockscreen">诪住讱 谞注讬诇讛</string>
+    <string name="menuitem_brightness">讘讛讬专讜转</string>
+    <string name="pref_screen_auto_brightness_title">讘讛讬专讜转 讗讜讟讜诪讟讬转</string>
+    <string name="sony_speak_to_chat_sensitivity_standard">专讙讬诇讛</string>
+    <string name="pref_enable_unsupported_settings_title">讛驻注诇转 讛讙讚专讜转 讘诇转讬 谞转诪讻讜转</string>
+    <string name="pref_screen_auto_brightness_summary">讛转讗诪转 讘讛讬专讜转 讛诪住讱 讘讛转讗诐 诇讗讜专 讘住讘讬讘讛</string>
+    <string name="pref_title_lower_button_short_press_action">驻注讜诇转 诇讞讬爪讛 拽爪专讛 注诇 讻驻转讜专 转讞转讜谉</string>
+    <string name="pref_title_upper_button_long_press_action">驻注讜诇转 诇讞讬爪讛 讗专讜讻讛 注诇 讻驻转讜专 注诇讬讜谉</string>
+    <string name="menuitem_unsupported">[诇讗 谞转诪讱] %s</string>
+    <string name="pref_workout_detection_sensitivity">专讙讬砖讜转</string>
+    <string name="pref_text_to_speech">讟拽住讟 诇讚讬讘讜专</string>
+    <string name="pref_workout_detection_alert_title">讛转专讗讛</string>
+    <string name="menuitem_wifi">专砖转 讗诇讞讜讟讬转</string>
+    <string name="menuitem_one_tap_measuring">诪讚讬讚讛 讘谞讙讬注讛 讗讞转</string>
+    <string name="menuitem_voice_memos">诪讝讻专讬诐 拽讜诇讬讬诐</string>
+    <string name="accuracy_first">转讬注讚讜祝 讚讬讜拽</string>
+    <string name="menuitem_screen_always_lit">诪住讱 转诪讬讚 驻注讬诇</string>
+    <string name="gps">GPS</string>
+    <string name="fw_upgrade_notice_amazfit_gtr4">驻注讜诇讛 讝讜 转转拽讬谉 讗转 讛拽讜砖讞讛 %s 注诇 讛志Amazfit GTR 4 砖诇讱.
+\n
+\n讛爪诪讬讚 砖诇讱 讬讜驻注诇 诪讞讚砖 诇讗讞专 讛转拽谞转 拽讜讘抓 讛志鈥�.zip.
+\n
+\n讛诪砖讱 讛转讛诇讬讱 讛讜讗 注诇 讗讞专讬讜转讱 讘诇讘讚!</string>
+    <string name="pref_workout_detection_summary">讝讬讛讜讬 讗讬诪讜谉 讗讜讟讜诪讟讬转</string>
+    <string name="pref_screen_notification_profile_schedule">诇讜状讝</string>
+    <string name="pref_screen_notification_profile_todo_list">专砖讬诪转 诪讟诇讜转</string>
+    <string name="pref_sleep_mode_sleep_screen_summary">诇讛爪讬讙 讗转 诪住讱 讛砖讬谞讛 讘注转 讛砖讻诪转 讛诪住讱 讘诪讛诇讱 诪爪讘 砖讬谞讛 讻讚讬 诇讛驻讞讬转 讛驻专注讜转</string>
+    <string name="pref_sleep_mode_smart_enable_summary">诇讛驻注讬诇 讗转 诪爪讘 讛砖讬谞讛 讗讜讟讜诪讟讬转 转讜讱 注谞讬讚转 讛爪诪讬讚 转讜讱 讻讚讬 砖讬谞讛</string>
+    <string name="pref_vibrate_for_alert">专讟讟 诇讛转专讗讛</string>
+    <string name="gps_gnolass">GPS + GLONASS</string>
+    <string name="pref_crown_vibration">专讟讟 讘讻转专</string>
+    <string name="offline_voice_respond_turn_wrist">诇讛讙讬讘 注诐 住讬讘讜讘 讻祝 讛讬讚</string>
+    <string name="offline_voice_respond_screen_on">诇讛讙讬讘 讻砖讛诪住讱 驻注讬诇</string>
+    <string name="pref_agps_header">AGPS鈥�</string>
+    <string name="pref_agps_expiry_reminder_enabled">转讝讻讜专转 转驻讜讙转 AGPS</string>
+    <string name="offline_voice_response_during_screen_lighting">诇讛讙讬讘 讘诪讛诇讱 驻诇讟 转讗讜专讛 诪讛诪住讱</string>
+    <string name="prefs_control_center">诪专讻讝 讘拽专讛</string>
+    <string name="activity_type_indoor_walking">讛诇讬讻讛 讘诪拽讜诐 住讙讜专</string>
+    <string name="activity_type_pool_swimming">砖讞讬讬讛 讘讘专讬讻讛</string>
+    <string name="menuitem_bluetooth">Bluetooth</string>
+    <string name="menuitem_eject_water">讛讜爪讗转 诪讬诐</string>
+    <string name="activity_prefs_goal_fat_burn_time_minutes">讬注讚 讬讜诪讬: 讝诪谉 砖专讬驻转 砖讜诪谉 讘讚拽讜转</string>
+    <string name="prefs_control_center_summary">谞讗 诇讘讞讜专 讗转 讛驻专讬讟讬诐 诇专砖讬诪讛 讛谞讙诇诇转 砖诇 诪专讻讝 讛讘拽专讛</string>
+    <string name="activity_prefs_target_weight_kg">诪砖拽诇 讬注讚 讘拽讬诇讜讙专诪讬诐</string>
+    <string name="activity_prefs_goal_standing_time_minutes">讬注讚 讬讜诪讬: 讝诪谉 注诪讬讚讛 讘讚拽讜转</string>
+    <string name="devicetype_amazfit_gtr4">Amazfit GTR 4鈥�</string>
+    <string name="pref_agps_expiry_reminder_time">诪讜注讚 转讝讻讜专转 转驻讜讙转 AGPS</string>
+    <string name="pref_enable_unsupported_settings_summary">讛讙讚专讛 讝讜 转讗驻砖专 讙讬砖讛 诇讻诇 讛讛讙讚专讜转 讛讝诪讬谞讜转, 讗驻讬诇讜 讗诐 讛讛转拽谉 诇讗 转讜诪讱 讘讛谉. 注诇讜诇 诇注专注专 讗转 讛讬爪讬讘讜转 讜诇讙专讜诐 诇拽专讬住转 讛讛转拽谉.</string>
+    <string name="pref_huami_truncate_fetch_operation_timestamps_title">拽讬爪讜专 讞讜转诪讜转 讛讝诪谉 砖诇 驻注讬诇讜讬讜转 诪砖讬讻讛</string>
+    <string name="pref_huami_truncate_fetch_operation_timestamps_summary">讞讬转讜讱 讞讜转诪讜转 讛讝诪谉 砖诇 驻注讜诇讜转 讛诪砖讬讻讛 诇讻讚讬 讚拽讜转. 讛砖讘转转 讛讛讙讚专讛 讛讝讗转 诪砖讗讬专讛 讗转 讞讜转诪转 讛讝诪谉 讘砖谞讬讜转, 讗诐 谞转拽诇转 讘转拽诇讜转 讘诪砖讬讻转 讗讬诪讜谞讬诐 拽爪专讬诐 讘诪讬讜讞讚.</string>
+    <string name="pref_title_notification_media_ignores_application_list">专砖讬诪转 讬讬砖讜诪讜谞讬诐 诇讛转注诇诪讜转 讘讛转专讗讜转 诪讚讬讛</string>
+    <string name="pref_summary_notification_media_ignores_application_list">诇注讘讚 讗转 讘拽砖讜转 讛诪讚讬讛 诇驻谞讬 专砖讬诪转 讛讬讬砖讜诪讜谞讬诐. 讗诐 讛注讚驻讛 讝讜 诇讗 诪住讜诪谞转, 讬砖 诇讗驻砖专 讬讬砖讜诪讜谞讬 诪讚讬讛 讘专砖讬诪转 讛讬讬砖讜诪讜谞讬诐 讻讚讬 砖驻拽讚讬 讛诪讚讬讛 讬注讘讚讜 注诇 讛诪讻砖讬专.</string>
+    <string name="debugactivity_confirm_remove_device_preferences_title">诇讛住讬专 讗转 讛注讚驻讜转 讛诪讻砖讬专\?</string>
+    <string name="debugactivity_confirm_remove_device_preferences">驻注讜诇讛 讝讜 转讗驻住 讗转 讛注讚驻讜转 讛诪讻砖讬专 诇讻诇 讛诪讻砖讬专讬诐 讛诪拽讜砖专讬诐. 诇讛诪砖讬讱\?</string>
+    <string name="fw_upgrade_notice_amazfit_band7">驻注讜诇讛 讝讜 转转拽讬谉 讗转 讛拽讜砖讞讛 %s 注诇 讛志Amazfit Band 7 砖诇讱.
+\n
+\n讛爪诪讬讚 砖诇讱 讬讜驻注诇 诪讞讚砖 诇讗讞专 讛转拽谞转 拽讜讘抓 讛志鈥�.zip.
+\n
+\n讛诪砖讱 讛转讛诇讬讱 讛讜讗 注诇 讗讞专讬讜转讱 讘诇讘讚!</string>
+    <string name="devicetype_amazfit_band7">Amazfit Band 7鈥�</string>
+    <string name="world_clock_code">拽讜讚</string>
+    <string name="pref_title_touch_spotify_official_app">Spotify (讛讬讬砖讜诪讜谉 讛专砖诪讬 讘诇讘讚)</string>
+    <string name="pref_title_touch_spotify_galaxy_app">Spotify (讬讬砖讜诪讜谉 Galaxy Wearable 讘诇讘讚)</string>
+    <string name="devicetype_galaxybuds_2">Galaxy Buds2鈥�</string>
+    <string name="prefs_touch_lock_buds2">驻拽讚讬 诪讙注</string>
+    <string name="function_enabled">诪讜驻注诇</string>
+    <string name="devicetype_amazfit_gts4">Amazfit GTS 4鈥�</string>
+    <string name="fw_upgrade_notice_amazfit_gts4">驻注讜诇讛 讝讜 转转拽讬谉 讗转 讛拽讜砖讞讛 %s 讘志Amazfit GTS 4 砖诇讱.
+\n
+\n讛爪诪讬讚 砖诇讱 讬讜驻注诇 诪讞讚砖 诇讗讞专 讛转拽谞转 拽讜讘抓 讛志鈥�.zip
+\n
+\n讛诪砖讱 讛转讛诇讬讱 讛讜讗 注诇 讗讞专讬讜转讱!</string>
+    <string name="devicetype_amazfit_gts4_mini">Amazfit GTS 4 Mini鈥�</string>
+    <string name="fw_upgrade_notice_amazfit_gts4_mini">驻注讜诇讛 讝讜 转转拽讬谉 讗转 讛拽讜砖讞讛 %s 讘志Amazfit GTS 4Mini 砖诇讱.
+\n
+\n讛爪诪讬讚 砖诇讱 讬讜驻注诇 诪讞讚砖 诇讗讞专 讛转拽谞转 拽讜讘抓 讛志鈥�.zip
+\n
+\n讛诪砖讱 讛转讛诇讬讱 讛讜讗 注诇 讗讞专讬讜转讱!</string>
+    <string name="devicetype_sony_linkbuds_s">Sony LinkBuds S鈥�</string>
+    <string name="pref_header_intent_api">API 砖诇 Intent</string>
+    <string name="intent_api_allow_activity_sync_title">诇讗驻砖专 讛拽驻爪转 住谞讻专讜谉 驻注讬诇讜转</string>
+    <string name="intent_api_allow_trigger_export_title">诇讗驻砖专 讬讬爪讜讗 诪住讚 谞转讜谞讬诐</string>
+    <string name="intent_api_allow_activity_sync_summary">诇讗驻砖专 讛拽驻爪转 住谞讻专讜谉 驻注讬诇讜转 讚专讱 API 砖诇 Intent</string>
+    <string name="intent_api_allow_trigger_export_summary">诇讗驻砖专 讛拽驻爪转 讬讬爪讜讗 诪住讚 谞转讜谞讬诐 讚专讱 API 砖诇 Intent</string>
+    <string name="intent_api_broadcast_export_title">诇讛驻讬抓 砖讬讚讜专 注诇 讬讬爪讜讗 诪住讚 谞转讜谞讬诐</string>
+    <string name="intent_api_broadcast_export_summary">诇讛驻讬抓 intent 注诐 住讬讜诐 讬讬爪讜讗 诪住讚 讛谞转讜谞讬诐</string>
+    <string name="sony_ambient_sound_control_button_modes">诪爪讘讬 讻驻转讜专 砖诇讬讟讛 讛砖诪注 讛讬拽驻讬</string>
+    <string name="sony_ambient_sound_control_button_mode_nc_as_off">讘讬讟讜诇 专注砖讬诐, 砖诪注 讛讬拽驻讬, 讻讘讜讬</string>
+    <string name="sony_ambient_sound_control_button_mode_nc_as">讘讬讟讜诇 专注砖讬诐, 砖诪注 讛讬拽驻讬</string>
+    <string name="sony_ambient_sound_control_button_mode_nc_off">讘讬讟讜诇 专注砖讬诐, 讻讘讜讬</string>
+    <string name="sony_ambient_sound_control_button_mode_as_off">砖诪注 讛讬拽驻讬, 讻讘讜讬</string>
+    <string name="sony_quick_access_double_tap">讙讬砖讛 诪讛讬专讛 (谞拽讬砖讛 讻驻讜诇讛)</string>
+    <string name="sony_quick_access_triple_tap">讙讬砖讛 诪讛讬专讛 (谞拽讬砖讛 诪砖讜诇砖转)</string>
+    <string name="menuitem_wechat_pay">鈥嶹eChat Pay</string>
+    <string name="pref_agps_expire_time">诪讜注讚 转驻讜讙转 AGPS</string>
+    <string name="pref_agps_update_time">诪讜注讚 注讚讻讜谉 AGPS</string>
+    <string name="appmanager_watchface_activate">讛驻注诇讛</string>
+    <string name="appmanager_downloaded_to_cache">讛转拽讘诇讜 %s 诇诪讟诪讜谉</string>
+    <string name="appmanager_app_start">讛转讞诇讛</string>
+    <string name="appmanager_app_download">讛讜专讚讛 诇诪讟诪讜谉</string>
+    <string name="appmanager_download_app_error">砖讙讬讗讛 讘讛讜专讚转 讬讬砖讜诪讜谉</string>
+    <string name="appmanager_download_started">讛讜专讚转 讛讬讬砖讜诪讜谉 讛讞诇讛</string>
+    <string name="devicetype_asteroidos">AsteroidOS鈥�</string>
+    <string name="devicetype_amazfit_trex_2">Amazfit T-Rex 2鈥�</string>
+    <string name="fw_upgrade_notice_amazfit_trex2">驻注讜诇讛 讝讜 转转拽讬谉 讗转 讛拽讜砖讞讛 %s 讘志Amazfit T-Rex 2 砖诇讱.
+\n
+\n讛爪诪讬讚 砖诇讱 讬讜驻注诇 诪讞讚砖 诇讗讞专 讛转拽谞转 拽讜讘抓 讛志鈥�.zip
+\n
+\n讛诪砖讱 讛转讛诇讬讱 讛讜讗 注诇 讗讞专讬讜转讱!</string>
+    <string name="georgian">讙讗讜专讙讬转</string>
+    <string name="activity_type_rowing">讞转讬专讛</string>
+    <string name="pref_workout_detection_enabled">讝讬讛讜讬 诪讜驻注诇</string>
+    <string name="pref_workout_detection_enabled_summary">讛驻注诇转 讝讬讛讜讬 讗讜讟讜诪讟讬 砖诇 住讜讙 讛讗讬诪讜谉 讛讝讛</string>
+    <string name="pref_workout_detection_ask_first">诇砖讗讜诇 讗讜转讬 拽讜讚诐</string>
+    <string name="pref_workout_detection_ask_first_summary">诇讚专讜砖 讗讬砖讜专 讘砖注讜谉 讘注转 讝讬讛讜讬 住讜讙 讛讗讬诪讜谉 讛讝讛</string>
+    <string name="pref_workout_detection_time">讚拽讜转 驻注讬诇讜转 注讚 讛讝讬讛讜讬</string>
+    <string name="pref_workout_detection_time_summary">讻诪讜转 讛讚拽讜转 诇转讜讱 讛讗讬诪讜谉 讘讟专诐 讝讬讛讜讬讜</string>
+    <string name="devicetype_soflow_s06">SoFlow SO6鈥�</string>
+    <string name="pref_title_lock_unlock">谞注讬诇讛</string>
+    <string name="devicetype_galaxybuds_2_pro">Galaxy Buds2 Pro鈥�</string>
+    <string name="pref_camera_remote_summary">诇讗驻砖专 诇砖注讜谉 诇讛驻注讬诇 讗转 讛诪爪诇诪讛 砖诇 讛讟诇驻讜谉</string>
+    <string name="pref_workout_keep_screen_on_title">诇讛砖讗讬专 讗转 讛诪住讱 讚讜诇拽 讘诪讛诇讱 讗讬诪讜谉</string>
+    <string name="wifi_hotspot_summary">砖诇讬讟讛 讘谞拽讜讚转 讛专砖转 讛讗诇讞讜讟讬转 讛讞诪讛 讚专讱 讛砖注讜谉</string>
+    <string name="wifi_hotspot_configuration">讛讙讚专讜转 谞拽讜讚讛 讗诇讞讜讟讬转 讞诪讛</string>
+    <string name="wifi_ssid">砖诐 专砖转</string>
+    <string name="wifi_hotspot_status">诪爪讘 谞拽讜讚讛 讗诇讞讜讟讬转 讞诪讛</string>
+    <string name="wifi_hotspot_start_summary">讛驻注诇转 讛谞拽讜讚讛 讛讞诪讛 讘砖注讜谉</string>
+    <string name="ftp_server_summary">砖诇讬讟讛 讘砖专转 讛志FTP 讘砖注讜谉</string>
+    <string name="ftp_server_start_summary">讛驻注诇转 砖专转 讛志FTP 讘砖注讜谉</string>
+    <string name="ftp_server_stop_summary">注爪讬专转 砖专转 讛志FTP 讘砖注讜谉</string>
+    <string name="ftp_server_status">诪爪讘 砖专转 FTP</string>
+    <string name="ftp_server_configuration">讛讙讚专讜转 砖专转 FTP</string>
+    <string name="username">砖诐 诪砖转诪砖</string>
+    <string name="wifi_hotspot">谞拽讜讚讛 讗诇讞讜讟讬转 讞诪讛</string>
+    <string name="status">诪爪讘</string>
+    <string name="pref_camera_remote_title">诪爪诇诪讛 诪专讞讜拽</string>
+    <string name="stop">注爪讬专讛</string>
+    <string name="pref_workout_keep_screen_on_summary">讛诪住讱 讬讬砖讗专 讚讜诇拽 讘诪讛诇讱 讗讬诪讜谉 讜讛讘讛讬专讜转 转讻讜讜谉 讻讚讬 诇讛爪讬讙 谞转讜谞讬 讗讬诪讜谉 讘讝诪谉 讗诪转 讜讘讗讜驻谉 专爪讬祝</string>
+    <string name="ftp_server_root_dir">转讬拽讬讬转 诪拽讜专</string>
+    <string name="wifi_hotspot_stop_summary">注爪讬专转 讛谞拽讜讚讛 讛讞诪讛 讘砖注讜谉</string>
+    <string name="ftp_server">砖专转 FTP</string>
+    <string name="address">讻转讜讘转</string>
+    <string name="croatian">拽专讜讗讟讬转</string>
+    <string name="sony_speak_to_chat_summary">讻讬讘讜讬 讘讬讟讜诇 专注砖讬诐 讗讜讟讜诪讟讬转 讘注转 讚讬讘讜专.</string>
+    <string name="yesterdays_activity">讛驻注讬诇讜转 砖诇 讗转诪讜诇</string>
+    <string name="bluetooth_calls">砖讬讞讜转 Bluetooth</string>
+    <string name="bluetooth_calls_pairing">爪讬诪讜讚 诇砖讬讞讜转 Bluetooth</string>
+    <string name="bluetooth_calls_settings">讛讙讚专讜转 砖讬讞讜转 Bluetooth</string>
+    <string name="pref_display_caller_title">讛爪讙转 驻专讟讬 拽砖专</string>
+    <string name="pref_display_caller_summary">讛爪讙转 诪住驻专 讟诇驻讜谉 讗讜 砖诐 诇砖讬讞讜转 谞讻谞住讜转</string>
+    <string name="pref_pair_bluetooth_calls_title">爪讬诪讜讚 诇砖讬讞讜转 Bluetooth</string>
+    <string name="pref_pair_bluetooth_calls_summary">诇讞讬爪讛 讻讗谉 转转讞讬诇 讗转 转讛诇讬讱 讛爪讬诪讜讚</string>
+    <string name="pref_pair_bluetooth_calls_help_title">讗讬讱 诇拽讘诇 砖讬讞讜转 Bluetooth</string>
+    <string name="pref_pair_bluetooth_calls_help_1">1. 诇讙注转 讘讻驻转讜专 砖诇讛诇谉 讻讚讬 诇讛转讞讬诇 讗转 转讛诇讬讱 讛爪讬诪讜讚.</string>
+    <string name="pref_pair_bluetooth_calls_help_3">3. 诇讛驻注讬诇 讗转 讛讙讚专讜转 砖讬讞讜转 Bluetooth 诇讛诇谉.</string>
+    <string name="pref_pair_bluetooth_calls_help_2">2. 诇讙砖转 诇讛讙讚专讜转 讛志Bluetooth 砖诇 讛讟诇驻讜谉 砖诇讱 讜诇爪诪讚 注诐 讛诪讻砖讬专 讛讞讚砖 砖讬讜驻讬注 (砖诐 砖讚讜诪讛 诇砖注讜谉 讛谞讜讻讞讬 砖诇讱, 讗讘诇 注诐 住讬讜诪转, 诇诪砖诇: 鈥濧mazfit GTR 4 - AFC8鈥�.</string>
+    <string name="pref_morning_updates_categories_title">拽讟讙讜专讬讜转 注讚讻讜谞讬 讘讜拽专</string>
+    <string name="pref_morning_updates_summary">讛爪讙转 注讚讻讜谞讬诐 讻诇 讘讜拽专</string>
+    <string name="pref_morning_updates_title">注讚讻讜谞讬 讘讜拽专</string>
+    <string name="pref_pair_bluetooth_calls_help_summary">讻讚讬 诇拽讘诇 砖讬讞讜转 Bluetooth 讬砖 诇爪诪讚 讗转 讛讟诇驻讜谉 砖诇讱 注诐 诪讜驻注 砖谞讬 砖诇 讛砖注讜谉.</string>
+    <string name="pref_summary_receive_calls_watch">讛转拽砖专讜转 讜诪注谞讛 诇砖讬讞讜转 讬砖讬专讜转 诪讛砖注讜谉</string>
+    <string name="pref_morning_updates_categories_summary">专砖讬诪转 讛拽讟讙讜专讬讜转 诇讛爪讙讛 讘讻诇 讘讜拽专</string>
+    <string name="pref_pair_bluetooth_calls_help_warning">讗讝讛专讛: 讻讚讬 诇讛驻注讬诇 砖讬讞讜转 Bluetooth 诪讘诇讬 诇爪诪讚 诇诪讜驻注 讛砖谞讬, 讛讜讚注讜转 砖讬讞讛 诇讗 转注讘讜讚谞讛 讻诪转讜讻谞谉.</string>
+    <string name="pref_title_notification_cache_while_disconnected">砖诪讬专讛 讘诪讟诪讜谉 转讜讱 讞专讬讙讛 诪讛讟讜讜讞</string>
+    <string name="pref_summary_notification_cache_while_disconnected">诇砖诇讜讞 讛转专讗讜转 砖讛讜讞诪爪讜 讻讗砖专 讛讛转拽谉 诪转讞讘专 诪讞讚砖 诇讗讞专 讛讬爪讬讗讛 诪讛讟讜讜讞</string>
+    <string name="pref_switch_controls_anc_ambient_off">讘讬讟讜诇 专注砖讬诐 鈫掆啇 讛拽驻讬 鈫掆啇 讻讘讜讬</string>
+    <string name="sony_button_mode_help_title">诪爪讘讬 讻驻转讜专讬诐 - 注讝专讛</string>
+    <string name="sony_button_mode_help_summary">转讬讗讜专 砖诇 讻诇 诪爪讘 讻驻转讜专</string>
+    <string name="single_tap">谞讙讬注讛 讘讜讚讚转</string>
+    <string name="double_tap">谞讙讬注讛 讻驻讜诇讛</string>
+    <string name="triple_tap">谞讙讬注讛 诪砖讜诇砖转</string>
+    <string name="long_press">谞讙讬注讛 讗专讜讻讛</string>
+    <string name="quick_attention">转砖讜诪转 诇讘 诪讛讬专讛</string>
+    <string name="continue_pressing">诇讛诪砖讬讱 诇诇讞讜抓</string>
+    <string name="menuitem_cards">讻专讟讬住讬诐</string>
+    <string name="menuitem_mi_ai">MI AI</string>
+    <string name="prefs_shortcut_cards">讻专讟讬住讬 拽讬爪讜专讬诐</string>
+    <string name="prefs_shortcut_cards_summary">讻专讟讬住讬 拽讬爪讜专讬诐 诪讜驻讬注讬诐 讻砖诪讞诇讬拽讬诐 讬诪讬谞讛 注诇 驻谞讬 讛砖注讜谉. 讻讗砖专 讬讬砖讜诪讜谉 专抓, 讻专讟讬住讬诐 砖谞讜爪专讜 讗讜讟讜诪讟讬转 诇讗 诪讜砖驻注讬诐 注诇 讬讚讬 讛讛讙讚专讛 讛讝讗转.</string>
+    <string name="menuitem_forecast">转砖拽讬祝</string>
+    <string name="menuitem_last_workout">讗讬诪讜谉 讗讞专讜谉</string>
+    <string name="menuitem_total_workout">住讱 讻诇 讛讗讬诪讜谞讬诐</string>
+    <string name="menuitem_recommendation">讛诪诇爪讛</string>
+    <string name="zepp_os_watchface_multiple_data">谞转讜谞讬诐 诪讙讜讜谞讬诐</string>
+    <string name="zepp_os_watchface_rush">讘讛诇讛</string>
+    <string name="zepp_os_watchface_minimalist">住讙驻谞讜转</string>
+    <string name="zepp_os_watchface_vibrant">注讝</string>
+    <string name="zepp_os_watchface_rotating_earth">讻讚讜专 讗专抓 诪住转讜讘讘</string>
+    <string name="zepp_os_watchface_superposition">住讜驻专 驻讜讝讬爪讬讛</string>
+    <string name="zepp_os_watchface_red_fantasy">驻谞讟讝讬讛 讗讚讜诪讛</string>
+    <string name="zepp_os_watchface_emerald_moonlight">讗讜专 讬专讞 讘专拽转</string>
+    <string name="zepp_os_watchface_simplicity_data">谞转讜谞讬诐 驻砖讜讟讬诐</string>
+    <string name="zepp_os_watchface_business_style">住讙谞讜谉 注住拽讬</string>
+    <string name="menuitem_vo2_max">爪讞状诪</string>
+    <string name="menuitem_aqi">诪讚讚 讗讬讻讜转 讗讜讜讬专</string>
+    <string name="fw_upgrade_notice_amazfit_gtr3_pro">驻注讜诇讛 讝讜 转转拽讬谉 讗转 讛拽讜砖讞讛 %s 注诇 讛志Amazfit GTR 3 Pro 砖诇讱.
+\n
+\n讛爪诪讬讚 砖诇讱 讬讜驻注诇 诪讞讚砖 诇讗讞专 讛转拽谞转 拽讜讘抓 讛志鈥�.zip.
+\n
+\n讛诪砖讱 讛转讛诇讬讱 讛讜讗 注诇 讗讞专讬讜转讱 讘诇讘讚!</string>
+    <string name="devicetype_amazfit_gtr3_pro">Amazfit GTR 3 Pro鈥�</string>
+    <string name="devicetype_sony_wh_1000xm5">Sony WH-1000XM5鈥�</string>
+    <string name="fossil_hr_confirm_connection">谞讗 诇讗砖专 讘砖注讜谉 砖诇讱</string>
+    <string name="fossil_hr_connection_not_confirmed">讛讞讬讘讜专 诇讗 讗讜砖专 讘砖注讜谉, 诪注讘专 诇诪爪讘 诇讗 诪讗讜诪转</string>
+    <string name="fossil_hr_pairing_successful">讛爪讬诪讜讚 注诐 讛砖注讜谉 讛爪诇讬讞</string>
+    <string name="fossil_hr_pairing_failed">讛爪讬诪讜讚 注诐 讛砖注讜谉 谞讻砖诇</string>
+    <string name="fossil_hr_confirmation_skipped">讗讬砖讜专 讘砖注讜谉 讚讜诇讙</string>
+    <string name="fossil_hr_confirmation_timeout">驻讙 讞诇讜谉 讛讝诪谉 诇讗讬砖讜专, 诪诪砖讬讻讬诐</string>
+    <string name="gpx_route_upload_in_progress">谞转讬讘讬 GPX 谞砖诇讞讬诐</string>
+    <string name="activity_detail_share_raw_details">砖讬转讜祝 驻专讟讬诐 讙讜诇诪讬讬诐</string>
+    <string name="gpx_route_upload_failed">讛注诇讗转 谞转讬讘讬诐 讻志GPX 谞讻砖诇讛</string>
+    <string name="gpx_route_upload_complete">讛注诇讗转 谞转讬讘讬诐 讻志GPX 讛讜砖诇诪讛</string>
+    <string name="debug_companion_pair_current">爪讬诪讜讚 讛诪讻砖讬专 讛谞讜讻讞讬 讻诪诇讜讜讛</string>
+    <string name="activity_error_share_failed">砖讬转讜祝 讛拽讜讘抓 谞讻砖诇.</string>
+    <string name="kind_gpx_route">谞转讬讘 GPX</string>
+    <string name="activity_detail_share_gps_label">砖讬转讜祝 诪住诇讜诇 GPX</string>
+    <string name="activity_detail_share_raw_summary">砖讬转讜祝 转拽爪讬专 讙讜诇诪讬</string>
+    <string name="dev_tools">讻诇讬诐 诇诪驻转讞讬诐</string>
+    <string name="debug_companion_show_associated">讛爪讙转 诪讻砖讬专讬诐 诪诇讜讜讬诐 诪砖讜讬讻讬诐</string>
+    <string name="pref_contacts_title">讗谞砖讬 拽砖专</string>
+    <string name="title_activity_contact_details">驻专讟讬 拽砖专</string>
+    <string name="contact_name">砖诐</string>
+    <string name="contact_missing_number">诪住驻专 讗讬砖 讛拽砖专 专讬拽</string>
+    <string name="contact_no_free_slots_description">诇诪讻砖讬专 讗讬谉 诪拽讜诪讜转 驻谞讜讬讬诐 诇讗谞砖讬 拽砖专 (住讱 讻诇 讛诪拽讜诪讜转: %1$s)</string>
+    <string name="title_activity_set_contacts">讛讙讚专转 讗谞砖讬 拽砖专</string>
+    <string name="pref_contacts_summary">讛讙讚专转 讗谞砖讬 拽砖专 讘砖注讜谉</string>
+    <string name="contact_delete_confirm_description">诇诪讞讜拽 讗转 鈥�%1$s鈥橽?</string>
+    <string name="contact_delete_confirm_title">诪讞讬拽转 讗讬砖 拽砖专</string>
+    <string name="contact_phone_number">诪住壮 讟诇驻讜谉</string>
+    <string name="contact_missing_name">砖诐 讗讬砖 讛拽砖专 专讬拽</string>
+    <string name="pref_header_navigation">谞讬讜讜讟</string>
+    <string name="pref_summary_navigation_forward">讛注讘专转 讛谞讞讬讜转 诪讬讬砖讜诪讜谞讬 讛谞讬讜讜讟 诇砖注讜谉</string>
+    <string name="pref_title_navigation_forward">砖诇讬讞转 谞讬讜讜讟 诇砖注讜谉</string>
+    <string name="intent_api_allow_debug_commands_summary">诇讗驻砖专 讛驻注诇转 驻拽讜讚讜转 转驻专讬讟 谞讬驻讜讬 砖讙讬讗讜转 讚专讱 API 砖诇 Intent</string>
+    <string name="intent_api_allow_debug_commands_title">诇讗驻砖专 驻拽讜讚讜转 谞讬驻讜讬 砖讙讬讗讜转</string>
+    <string name="voice_service">砖讬专讜转 拽讜诇讬</string>
+    <string name="voice_service_package_title">讞讘讬诇转 砖讬专讜转 拽讜诇讬</string>
+    <string name="voice_service_class_summary">谞转讬讘 诪诇讗 砖诇 砖讬专讜转 砖诪讟驻诇 讘驻拽讜讚讜转 拽讜诇讬讜转</string>
+    <string name="voice_service_package_summary">讬讬砖讜诪讜谉 砖诪讻讬诇 讗转 讛砖讬专讜转 砖诪讟驻诇 讘驻拽讜讚讜转 拽讜诇讬讜转</string>
+    <string name="voice_service_class_title">诪讞诇拽转 砖讬专讜转 拽讜诇讬</string>
+    <string name="pref_app_logs_summary">讛驻注诇转 讬讜诪谞讬诐 诪讬讬砖讜诪讜谞讬 讛砖注讜谉</string>
+    <string name="pref_app_logs_start_summary">讛转讞诇转 转讬注讜讚 诪讬讬砖讜诪讜谞讬 讛砖注讜谉</string>
+    <string name="pref_app_logs_title">讬讜诪谞讬 讛讬讬砖讜诪讜谞讬诐</string>
+    <string name="pref_app_logs_stop_summary">注爪讬专转 转讬注讜讚 诪讬讬砖讜诪讜谞讬 讛砖注讜谉</string>
+    <string name="fwapp_install_device_not_supported">讗讬 讗驻砖专 诇讛转拽讬谉 讗转 讛拽讜讘抓, 讛诪讻砖讬专 诇讗 谞转诪讱.</string>
+    <string name="share_screenshot">砖讬转讜祝 爪讬诇讜诐 诪住讱</string>
+    <string name="screenshot_taken">讛诪住讱 爪讜诇诐</string>
+    <string name="charts_legend_stress_average">诪诪讜爪注 诇讞抓</string>
+    <string name="stress_mild">讘讬谞讜谞讬</string>
+    <string name="stress_moderate">诪转讜谉</string>
+    <string name="stress_high">讙讘讜讛</string>
+    <string name="stress_relaxed">专讙讜注</string>
+    <string name="watchface_widget_type_uv_index">诪讚讚 拽专讬谞讛 讗讜诇讟专讛 住讙讜诇讛</string>
+    <string name="pref_title_banglejs_phone_gps_update_interval">讛驻专砖 讘讬谉 注讚讻讜谞讬 谞转讜谞讬 谞讜讜讟谉</string>
+    <string name="pref_summary_banglejs_phone_gps_network_only">讗驻砖专 诇讛砖转诪砖 讘讗驻砖专讜转 讛讝讗转 讻讚讬 诇讛砖转诪砖 专拽 讘专砖转 讻讚讬 诇讝讛讜转 讗转 讛诪讬拽讜诐. 诪驻讞讬转 讗转 爪专讬讻转 讛住讜诇诇讛 讘转诪讜专讛 诇讚讬讜拽. 谞讚专砖 讞讬讘讜专 诪讞讚砖.</string>
+    <string name="pref_summary_banglejs_phone_gps_update_interval">讛讛驻专砖 诇注讚讻讜谉 诪讬拽讜诐 讛谞讜讜讟谉, 讘诪讬诇讬砖谞讬讜转</string>
+    <string name="pref_title_banglejs_phone_gps_enbale">诇讛砖转诪砖 讘谞转讜谞讬 讛谞讜讜讟谉 砖诇 讛讟诇驻讜谉</string>
+    <string name="changelog_show_full">注讜讚鈥�</string>
+    <string name="preview_image">转诪讜谞转 转爪讜讙讛 诪拽讚讬诪讛</string>
+    <string name="description">转讬讗讜专</string>
+    <string name="action_changelog">讬讜诪谉 砖讬谞讜讬讬诐</string>
+    <string name="status_icon">住诪诇 诪爪讘</string>
+    <string name="title">讻讜转专转</string>
+    <string name="pref_summary_banglejs_phone_gps_enbale">诇讛砖转诪砖 讘谞转讜谞讬 讛谞讜讜讟谉 砖诇 讛讟诇驻讜谉 讻讚讬 诇讚专讜住 讗转 谞转讜谞讬 讛谞讜讜讟谉 砖诇 诪讻砖讬专 讛志bangle</string>
+    <string name="pref_title_banglejs_phone_gps_network_only">诇讛砖转诪砖 专拽 讘专砖转 讻讚讬 诇诪爪讜讗 诪讬拽讜诐</string>
+    <string name="changelog_full_title">讬讜诪谉 砖讬谞讜讬讬诐</string>
+    <string name="changelog_ok_button">讗讬砖讜专</string>
+    <string name="changelog_title">诪讛 讞讚砖</string>
+    <string name="num_min">%d 讚拽壮</string>
+    <string name="pai_plus_num">+%d</string>
+    <string name="pai_chart_per_month">PAI 讘讞讜讚砖</string>
+    <string name="pai_chart_per_week">PAI 讘砖讘讜注</string>
+    <string name="loyalty_cards_install">讛转拽谞转 Catima</string>
+    <string name="loyalty_cards_sync_groups_only">住谞讻专讜谉 拽讘讜爪讜转 诪住讜讬诪讜转 讘诇讘讚</string>
+    <string name="loyalty_cards_sync_archived">住谞讻专讜谉 讻专讟讬住讬诐 讘讗专讻讬讜谉</string>
+    <string name="loyalty_cards_install_catima_fail">驻转讬讞转 讛讞谞讜转 诇讛转拽谞转 Catima 谞讻砖诇讛</string>
+    <string name="loyalty_cards_sync_starred">诇住谞讻专谉 讻专讟讬住讬诐 讘讻讜讻讘 讘诇讘讚</string>
+    <string name="loyalty_cards_catima_not_compatible">讙专住转 讛志Catima 讛诪讜转拽谞转 诇讗 转讜讗诪转 诇志Gadgetbridge. 谞讗 诇注讚讻谉 讗转 Catima 讜讗转 Gadgetbridge 诇讙专住讗讜转 讛注讚讻谞讬讜转 讘讬讜转专.</string>
+    <string name="loyalty_cards_catima">Catima</string>
+    <string name="loyalty_cards_open_catima">驻转讬讞转 Catima</string>
+    <string name="pai_total">住讱 讛讻讜诇</string>
+    <string name="pai_day">注诇讬讬讛 讬讜诪讬转</string>
+    <string name="loyalty_cards_sync_groups">拽讘讜爪讜转 诇住谞讻专讜谉</string>
+    <string name="loyalty_cards_sync_title">住谞讻专讜谉 讻专讟讬住讬 诪讜注讚讜谉</string>
+    <string name="loyalty_cards_catima_package">砖诐 讞讘讬诇转 Catima</string>
+    <string name="loyalty_cards_sync_summary">讬砖 诇诇讞讜抓 讻讚讬 诇住谞讻专谉 讗转 讛讻专讟讬住讬诐 诇砖注讜谉</string>
+    <string name="loyalty_cards_catima_not_installed">爪专讬讱 Catima 讻讚讬 诇谞讛诇 讗转 讻专讟讬住讬 讛诪讜注讚讜谉</string>
+    <string name="loyalty_cards_catima_permissions_title">讞住专讜转 讛专砖讗讜转</string>
+    <string name="loyalty_cards_sync_options">讗驻砖专讜讬讜转 住谞讻专讜谉</string>
+    <string name="loyalty_cards_sync">住谞讻专讜谉</string>
+    <string name="loyalty_cards_catima_permissions_summary">诇志Gadgetbridge 讚专讜砖讜转 讛专砖讗讜转 诇拽专讜讗 讻专讟讬住讬诐 诪志Catima 讻讚讬 住诇谞讻专谉 讗讜转诐. 谞讙讬注讛 讘讻驻转讜专 讛讝讛 转讗砖专 讗转 讛讙讬砖讛.</string>
+    <string name="loyalty_cards">讻专讟讬住讬 诪讜注讚讜谉</string>
+    <string name="loyalty_cards_syncing">%d 讻专讟讬住讬 诪讜注讚讜谉 诪住讜谞讻专谞讬诐 诇诪讻砖讬专</string>
+    <string name="pref_title_mb_intents">砖讬讚讜专 Intents 砖诇 讻驻转讜专讬 诪讚讬讛 讬砖讬专讜转</string>
+    <string name="pref_summary_mb_intents">讬砖 诇讛驻注讬诇 讗诐 讘拽专转 讛诪讚讬讛 砖诇 讛诪讻砖讬专 诇讗 注讜讘讚转 讘讬讬砖讜诪讬诐 诪住讜讬诪讬诐</string>
+    <string name="devicetype_bohemic_smart_bracelet">爪诪讬讚 讞讻诐 砖诇 Bohemic</string>
+    <string name="devicetype_vivomove_hr">Garmin Vivomove HR鈥�</string>
+    <string name="busy_task_fetch_sports_summaries">转拽爪讬专讬 讛住驻讜专讟 诪注讜讚讻谞讬诐</string>
+    <string name="busy_task_fetch_sports_details">驻专讟讬 讛住驻讜专讟 诪转注讚讻谞讬诐</string>
+    <string name="busy_task_fetch_debug_logs">讬讜诪谞讬 谞讬驻讜讬 讛转拽诇讜转 诪注讜讚讻谞讬诐</string>
+    <string name="busy_task_fetch_pai_data">谞转讜谞讬 PAI 诪注讜讚讻谞讬诐</string>
+    <string name="busy_task_fetch_spo2_data">谞转讜谞讬 讞诪爪谉 讘讚诐 诪注讜讚讻谞讬诐</string>
+    <string name="busy_task_fetch_hr_data">谞转讜谞讬 讚讜驻拽 诪注讜讚讻谞讬诐</string>
+    <string name="busy_task_fetch_stress_data">谞转讜谞讬 讛诇讞抓 诪注讜讚讻谞讬诐</string>
+    <string name="busy_task_fetch_sleep_respiratory_rate_data">谞转讜谞讬 拽爪讘 谞砖讬诪讜转 讘砖讬谞讛 诪注讜讚讻谞讬诐</string>
+    <string name="devicetype_casiogwb5600">Casio GW-B5600鈥�</string>
+    <string name="devicetype_casiogmwb5000">Casio GMW-B5000鈥�</string>
+    <string name="activity_prefs_goals">讬注讚讬诐</string>
+    <string name="error_no_bluetooth_scan">讬砖 诇讛注谞讬拽 讙讬砖讛 诇住专讬拽转 Bluetooth 讜诇讛驻注讬诇 讗讜转讛 讻讚讬 砖讝讛 讬注讘讜讚 讻专讗讜讬</string>
+    <string name="error_no_bluetooth_connect">讬砖 诇讛注谞讬拽 讙讬砖讛 诇讞讬讘讜专 Bluetooth 讜诇讛驻注讬诇 讗讜转讜 讻讚讬 砖讛住专讬拽讛 转注讘讜讚 讻专讗讜讬</string>
+    <string name="permission_request">%1$s 诪讗驻砖专 诇讱 诇砖诇讜讞 讛讜讚注讜转 讜谞转讜谞讬诐 谞讜住驻讬诐 诪志Android 诇诪讻砖讬专 砖诇讱. 讻讚讬 诇注砖讜转 讝讗转 谞讚专砖转 讙讬砖讛 诇谞转讜谞讬诐 讛讗诇讛 讜讘诇注讚讬讛, 讻谞专讗讛 砖讝讛 诇讗 讬注讘讜讚 讻专讗讜讬.
+\n
+\n注讻砖讬讜 讬讜爪讙讜 讘驻谞讬讱 诪住驻专 讞诇讜谞讬讜转 砖诇 Android 砖诪讘拽砖讜转 讗转 讛讛专砖讗讜转 讛讗诇讛.
+\n
+\n谞讗 诇讙注转 讘志鈥�%2$s鈥� 讻讚讬 诇讛诪砖讬讱.</string>
+    <string name="activity_type_surfing">讙诇讬砖讛</string>
+    <string name="activity_type_windsurfing">讙诇讬砖转 专讜讞</string>
+    <string name="activity_type_rugby">专讜讙讘讬</string>
+    <string name="activity_type_baseball">讘讬讬住讘讜诇</string>
+    <string name="activity_type_handball">讻讚讜专志讬讚</string>
+    <string name="activity_type_tennis">讟谞讬住</string>
+    <string name="activity_type_squash">住拽讜讜砖</string>
+    <string name="activity_type_weightlifting">讛专诪转 诪砖拽讜诇讜转</string>
+    <string name="activity_type_dancing">专讬拽讜讚</string>
+    <string name="activity_type_hockey">讛讜拽讬</string>
+    <string name="activity_type_icehockey">讛讜拽讬 拽专讞</string>
+    <string name="activity_type_iceskating">讛讞诇拽讛 注诇 拽专讞</string>
+    <string name="activity_type_football">驻讜讟讘讜诇</string>
+    <string name="activity_type_kitesurfing">拽讬讬讟住专驻讬谞讙</string>
+    <string name="activity_type_snowboarding">讙诇讬砖转 砖诇讙</string>
+    <string name="activity_type_other">讗讞专</string>
+    <string name="withings_steel_hr">Withings Steel HR鈥�</string>
+    <string name="withings_bt_calibration_previous">讛拽讜讚诐</string>
+    <string name="withings_calibration_text_minutes">讻注转 讬砖 诇讛砖转诪砖 讘讞讜讙讛 讻讚讬 诇讬讬砖专 讗转 诪讞讜讙 讛讚拽讜转 诇志12.</string>
+    <string name="withings_bt_calibration_next">讛讘讗</string>
+    <string name="activity_type_skiing">住拽讬</string>
+    <string name="activity_type_golf">讙讜诇祝</string>
+    <string name="withings_calibration_text_hours">谞讗 诇讛砖转诪砖 讘讞讜讙讛 砖诇讛诇谉 讻讚讬 诇讬讬砖专 讗转 诪讞讜讙 讛砖注讜转 诇志12.</string>
+    <string name="activity_type_riding">专讻讬讘转 住讜住讬诐</string>
+    <string name="withings_calibration_text_activity_target">讜诇讘住讜祝, 讬砖 诇讬讬砖专 讗转 诪讞讜讙 讛驻注讬诇讜转 诇志100%. 讻讚讗讬 诇砖讬诐 诇讘 砖讛诪讞讜讙 讛讝讛 谞注 专拽 谞讙讚 讻讬讜讜谉 讛砖注讜谉.</string>
+    <string name="pref_theme_dynamic">爪讘注讬诐 讚讬谞诪讬讬诐</string>
+    <string name="pref_canned_message">讛讜讚注讛</string>
+    <string name="preferences_miband_1_2_settings" tools:ignore="TypographyFractions">讛讙讚专讜转 Mi Band 1/2</string>
+    <string name="preferences_miband_1_2_warning">讗讝讛专讛: 讛讛注讚驻讜转 讛讗诇讜 讞诇讜转 专拽 注诇 专爪讜注讜转 Mi Band 1 讜志2.</string>
+    <string name="drag_handle">讬讚讬转 诪砖讬讻讛</string>
+    <string name="find_my_phone_found_it">诪爪讗转讬 讗讜转讜</string>
+    <string name="pref_activity_full_sync_trigger_title">住谞讻专讜谉 诪诇讗</string>
+    <string name="pref_show_changelog">讛爪讙转 讬讜诪谉 砖讬谞讜讬讬诐 讘注诇讬讬讛</string>
+    <string name="pref_show_changelog_summary">讛爪讙转 讬讜诪谉 讛砖讬谞讜讬讬诐 诪讗讝 讛讙专住讛 讛讗讞专讜谞讛 诇讗讞专 注讚讻讜谉 Gadgetbridge</string>
+    <string name="pref_activity_full_sync_trigger_summary">讛拽驻爪转 住谞讻专讜谉 诪诇讗 砖诇 讻诇 谞转讜谞讬 讛驻注讬诇讜转</string>
+    <string name="pref_activity_full_sync_trigger_warning">驻注讜诇讛 讝讜 转拽驻讬抓 住谞讻专讜谉 诪诇讗 砖诇 讻诇 谞转讜谞讬 讛驻注讬诇讜转 诪讜诇 讛诪讻砖讬专. 注诇讜诇 诇讗专讜讱 讻诪讛 讚拽讜转 注讚 砖讬讜砖诇诐.</string>
+    <string name="menuitem_zepp_coach">Zepp Coach鈥�</string>
+    <string name="pref_theme_dynamic_colors_explanation">诇转砖讜诪转 诇讘讱: 诇讟讜讘转 注专讻转 讛爪讘注讬诐 讛讚讬谞诪讬讬诐 讬砖 诇讛驻注讬诇 爪讘注讬 讟驻讟 讗讜 注专讻转 爪讘注讬诐 讘讛讙讚专讜转 讛诪专讗讛 讘志Android 12+鈥� 砖诇讱. 讗诐 诇讗, Gadgetbridge 讬砖转诪砖 讘爪讘注讬 Material 3 讻讘专讬专转 诪讞讚诇.</string>
+    <string name="pref_theme_dynamic_colors_not_available_warning">爪讘注讬诐 讚讬谞诪讬讬诐 诇讗 讝诪讬谞讬诐 讘诪讻砖讬专 砖诇讱, 专拽 Android 12 讜诪注诇讛 转讜诪讱 讘讬讻讜诇转 讛讝讗转. Gadgetbridge 讬砖转诪砖 讘爪讘注讬 讘专讬专转 讛诪讞讚诇 砖诇 Material 3.</string>
+    <string name="devicetype_amazfit_bip3_pro">Amazfit Bip 3 Pro鈥�</string>
+    <string name="fw_upgrade_notice_amazfitbip3pro">驻注讜诇讛 讝讜 转转拽讬谉 讗转 讛拽讜砖讞讛 %s 注诇 讛志Amazfit Bip 3 Pro 砖诇讱.
+\n
+\n谞讗 诇讜讜讚讗 讗转 讛转拽谞转 拽讜砖讞转 讛志鈥�.fw 讘讛转讞诇讛, 诇讗讞专 诪讻谉 讗转 拽讜讘抓 讛志鈥�.res 讛砖注讜谉 砖诇讱 讬讜驻注诇 诪讞讚砖 诇讗讞专 讛转拽谞转 拽讜讘抓 讛志fw.
+\n
+\n诇转砖讜诪转 诇讘讱: 讗讬谉 爪讜专讱 讘讛转拽谞转 拽讜讘抓 讛志鈥�.res 讗诐 讛讜讗 讻讘专 讝讛讛 诇讝讛 砖讛转拽谞转 讘注讘专.
+\n
+\n讛诪砖讱 讛转讛诇讬讱 讛讜讗 注诇 讗讞专讬讜转讱!</string>
+    <string name="fw_upgrade_notice_amazfit_cheetah_pro">驻注讜诇讛 讝讜 转转拽讬谉 讗转 讛拽讜砖讞讛 %s 注诇 讛志Amazfit Cheetah Pro 砖诇讱.
+\n
+\n讛爪诪讬讚 砖诇讱 讬讜驻注诇 诪讞讚砖 诇讗讞专 讛转拽谞转 拽讜讘抓 讛志鈥�.zip.
+\n
+\n讛诪砖讱 讛转讛诇讬讱 讛讜讗 注诇 讗讞专讬讜转讱 讘诇讘讚!</string>
+    <string name="fw_upgrade_notice_zepp_os">驻注讜诇讛 讝讜 转转拽讬谉 讗转 讛拽讜砖讞讛 %s 注诇 讛志%s 砖诇讱.
+\n
+\n讛爪诪讬讚 砖诇讱 讬讜驻注诇 诪讞讚砖 诇讗讞专 讛转拽谞转 拽讜讘抓 讛志鈥�.zip.
+\n
+\n讛诪砖讱 讛转讛诇讬讱 讛讜讗 注诇 讗讞专讬讜转讱 讘诇讘讚!</string>
+    <string name="devicetype_amazfit_cheetah_square">Amazfit Cheetah (诪专讜讘注)</string>
+    <string name="devicetype_amazfit_cheetah_round">Amazfit Cheetah (诪注讜讙诇)</string>
+    <string name="devicetype_amazfit_cheetah_pro">Amazfit Cheetah Pro鈥�</string>
+    <string name="devicetype_amazfit_bip5">Amazfit Bip 5鈥�</string>
+    <string name="devicetype_amazfit_trex_ultra">Amazfit T-Rex Ultra鈥�</string>
+    <string name="devicetype_amazfit_gtr_mini">Amazfit GTR Mini鈥�</string>
+    <string name="devicetype_amazfit_falcon">Amazfit Falcon鈥�</string>
+    <string name="device_experimental">谞讬住讬讜谞讬</string>
+    <string name="button_reset_menu_structure">讗讬驻讜住 诪讘谞讛 转讘谞讬转</string>
+    <string name="info_menu_structure_removed">诪讘谞讛 讛转驻专讬讟 讛讜住专</string>
+    <string name="info_menu_structure_contents">诪讘谞讛 转驻专讬讟: %s</string>
+    <string name="button_open_menu_companion">驻转讬讞转 讬讬砖讜诪讜谉 诪诇讜讜讛 诇转驻专讬讟</string>
+    <string name="error_menu_companion_not_installed">鈥氉炞∽欁欁� 转驻专讬讟 讚讜驻拽鈥� 讻谞专讗讛 诇讗 诪讜转拽谉</string>
+    <string name="info_menu_structure_set">JSON 诪讘谞讛 转驻专讬讟讬诐 砖讛讜讙讚专 讘志GB</string>
+    <string name="info_fossil_rebuild_watchface_custom_menu">谞讗 诇讘谞讜转 讗转 驻谞讬 讛砖注讜谉 砖诇讱 诪讞讚砖 诇转驻专讬讟 诪讜转讗诐 讗讬砖讬转</string>
+    <string name="error_invalid_menu_structure">JSON 诪讘谞讛 转驻专讬讟讬诐 砖讙讜讬</string>
+    <string name="english_au">讗谞讙诇讬转 讗讜住讟专诇讬转</string>
+    <string name="english_ca">讗谞讙诇讬转 拽谞讚讬转</string>
+    <string name="english_gb">讗谞讙诇讬转 讘专讬讟讬转</string>
+    <string name="english_in">讗谞讙诇讬转 讛讜讚讬转</string>
+    <string name="spanish_mx">住驻专讚讬转 诪拽住讬拽谞讬转</string>
+    <string name="spanish_us">住驻专讚讬转 讗诪专讬拽讗讬转</string>
+    <string name="french_ca">爪专驻转讬转 拽谞讚讬转</string>
+    <string name="french_fr">爪专驻转讬转 砖诇 爪专驻转</string>
+    <string name="english_us">讗谞讙诇讬转 讗诪专讬拽讗讬转</string>
+    <string name="spanish_es">住驻专讚讬转 砖诇 住驻专讚</string>
+    <string name="updatefirmwareoperation_updateproblem_free_space">讗讬谉 诪住驻讬拽 诪拽讜诐 驻谞讜讬 讘诪讻砖讬专</string>
+    <string name="text_receiver_activity_title">砖诇讬讞转 讟拽住讟 诇诪讻砖讬专</string>
+    <string name="updatefirmwareoperation_failed_low_mtu">讛讙讚专转 讛志MTU (讬讞讬讚转 讛注讘专讛 诪专讘讬转) 讛谞讜讻讞讬转 %1$d 谞诪讜讻讛 诪讚讬, 谞讗 诇讛驻注讬诇 MTU 讙讘讜讛 讘讛讙讚专讜转 讛诪讻砖讬专 讜诇谞转拽 讜诇讞讘专 讗转 讛诪讻砖讬专 砖讜讘.</string>
+    <string name="devicetype_miband2_hrx">Mi Band HRX鈥�</string>
+    <string name="new_discover_activity_description">讛驻注诇转 驻注讬诇讜转 讛讙讬诇讜讬 讛讞讚砖讛 砖讗诪讜专讛 诇驻转讜专 转拽诇讜转 讘讙讬诇讜讬 讛转拽谞讬诐. 专爪讜讬 诇讛砖讘讬转 讗讜转讛 讗诐 爪爪讜转 转拽诇讜转 讘诪讛诇讱 讞讬驻讜砖 讗讞专 讗讜 爪讬诪讜讚 讛讛转拽谉 砖诇讱.</string>
+    <string name="new_discover_activity_title">讛驻注诇转 驻注讬诇讜转 讙讬诇讜讬 讞讚砖讛</string>
+    <string name="devicetype_sony_wena3">Sony Wena 3鈥�</string>
+    <string name="prefs_home_icon_left_item">砖诪讗诇</string>
+    <string name="white">诇讘谉</string>
+    <string name="prefs_home_icon_right_item">讬诪讬谉</string>
+    <string name="prefs_wena3_title_screen">讛讙讚专讜转 转爪讜讙讛</string>
+    <string name="green">讬专讜拽</string>
+    <string name="yellow">爪讛讜讘</string>
+    <string name="red">讗讚讜诐</string>
+    <string name="cyan">爪讬讗谉</string>
+    <string name="prefs_home_icon_center_item">诪专讻讝</string>
+    <string name="purple">住讙讜诇</string>
+    <string name="blue">讻讞讜诇</string>
+    <string name="prefs_wena3_receive_calls_title">讛讙讚专讜转 砖讬讞讜转 谞讻谞住讜转</string>
+    <string name="prefs_wena3_button_action_name_start_timer">讛驻注诇转 诪转讝诪谉 驻注讬诇</string>
+    <string name="prefs_wena3_menu_icon_hint">住诪诇 讛讛讙讚专讜转 转诪讬讚 诪讜驻讬注 讘住讜祝</string>
+    <string name="prefs_wena3_notification_vibration_repetition_3">3 驻注诪讬诐</string>
+    <string name="prefs_wena3_button_action_name_qrio_lock">谞注讬诇讛 注诐 Qrio</string>
+    <string name="prefs_wena3_auto_power_off_turn_off_time_item">砖注转 讻讬讘讜讬</string>
+    <string name="prefs_wena3_vibration_short">拽爪专</string>
+    <string name="prefs_wena3_home_icon_name_calories">拽诇讜专讬讜转</string>
+    <string name="prefs_wena3_led_none">讘诇讬 谞讜专讬转</string>
+    <string name="prefs_wena3_vibration_step_down">讛讜诇讱 讜谞讞诇砖</string>
+    <string name="prefs_wena3_vibration_smart_item">专讟讟 讞讻诐</string>
+    <string name="prefs_wena3_home_icon_name_riiiver">Riiiver鈥�</string>
+    <string name="prefs_wena3_hint_background_sync">诇讗驻砖专 诇志Wena 诇讘拽砖 诪志Gadgetbridge 诇讛讜专讬讚 谞转讜谞讬 驻注讬诇讜转 诪讛诪讻砖讬专 诪讚讬 驻注诐 讘驻注诐</string>
+    <string name="prefs_wena3_home_icon_name_suica">讬转专讛 讘志Suica</string>
+    <string name="prefs_wena3_vibration_strength_item_medium">讘讬谞讜谞讬转</string>
+    <string name="prefs_wena3_title_activity">讛讙讚专讜转 驻注讬诇讜转</string>
+    <string name="prefs_wena3_vibration_continuous">专爪讬祝</string>
+    <string name="prefs_wena3_home_icon_name_pedometer">诪讚 爪注讚讬诐</string>
+    <string name="prefs_wena3_home_icon_name_qrio">Qrio鈥�</string>
+    <string name="prefs_wena3_home_icon_name_clock">讛砖注讛 讻专讙注</string>
+    <string name="prefs_wena3_item_background_sync">住谞讻专讜谉 谞转讜谞讬诐 讘驻注讬诇讜转 专拽注</string>
+    <string name="prefs_wena3_vibration_strength_item_weak">讞诇砖讛</string>
+    <string name="prefs_wena3_home_icon_title">住诪诇讬 诪住讱 讛讘讬转</string>
+    <string name="prefs_wena3_item_weather_statusbar">诪讝讙 讗讜讜讬专 讘砖讜专转 讛诪爪讘</string>
+    <string name="temperature_scale_celsius">爪诇讝讬讜住</string>
+    <string name="prefs_wena3_vibration_step_up">讛讜诇讱 讜诪转讙讘专</string>
+    <string name="prefs_wena3_vibration_basic">讘住讬住讬</string>
+    <string name="prefs_wena3_item_large_font">讻转讘 讙讚讜诇 讬讜转专</string>
+    <string name="prefs_wena3_home_icon_name_energy">讗谞专讙讬讬转 讙讜祝</string>
+    <string name="prefs_wena3_notification_vibration_repetition_2">驻注诪讬讬诐</string>
+    <string name="prefs_wena3_auto_power_off_hint">讛诪讻砖讬专 讬讬讻讘讛 讜讬讬讚诇拽 讗讜讟讜诪讟讬转 诇驻讬 转讝诪讜谉 诪讜讙讚专</string>
+    <string name="prefs_wena3_hint_lift_wrist">诇讛驻注讬诇 讗转 讛转爪讜讙讛 讘注转 讛住转讻诇讜转 注诇 驻专拽 讻祝 讛讬讚 砖诇讱</string>
+    <string name="prefs_wena3_hint_large_font">诇讛讙讚讬诇 讗转 讛讻转讘 讘诇讜讞 讛砖谞讛, 讛转专讗讜转 讜注讜讚.</string>
+    <string name="prefs_wena3_button_action_item_double">诇讞讬爪讛 讻驻讜诇讛</string>
+    <string name="prefs_wena3_vibration_rapid">诪讛讬专</string>
+    <string name="prefs_wena3_button_action_name_qrio_unlock">砖讞专讜专 注诐 Qrio</string>
+    <string name="temperature_scale_cf">住讜诇诐 讟诪驻专讟讜专讛</string>
+    <string name="prefs_wena3_notification_vibration_repetition_1">驻注诐 讗讞转</string>
+    <string name="prefs_wena3_receive_calls_item">拽讘诇转 讛转专讗讜转 注诇 砖讬讞讜转</string>
+    <string name="prefs_wena3_hint_weather_statusbar">讛爪讙转 住诪诇 讛转谞讗讬诐 讛谞讜讻讞讬讬诐 讘驻讬谞讛 讛砖诪讗诇讬转 讛注诇讬讜谞讛 砖诇 诪住讱 讛讘讬转</string>
+    <string name="prefs_wena3_home_icon_name_wena_pay">Wena Pay鈥�</string>
+    <string name="prefs_wena3_button_action_item">驻注讜诇转 讻驻转讜专</string>
+    <string name="prefs_wena3_auto_power_off_turn_on_time_item">砖注转 讛转讗讜砖砖讜转</string>
+    <string name="prefs_wena3_notification_default_call_vibration">专讟讟 讘砖讬讞讛 谞讻谞住转</string>
+    <string name="prefs_wena3_notification_default_call_led">爪讘注 谞讜专讬转 砖讬讞讛 谞讻谞住转</string>
+    <string name="prefs_wena3_vibration_siren">爪驻讬专讛</string>
+    <string name="temperature_scale_cf_summary">谞讗 诇讘讞讜专 讛讗诐 讛诪讻砖讬专 讬砖转诪砖 讘住讜诇诐 爪诇讝讬讜住 讗讜 驻专谞讛讬讬讟.</string>
+    <string name="prefs_wena3_menu_icon_title">住诪诇讬诐 讘转驻专讬讟</string>
+    <string name="prefs_wena3_notification_default_call_vibration_repetition">讞讝专讛 注诇 专讟讟 讘砖讬讞讛 谞讻谞住转</string>
+    <string name="prefs_wena3_notification_default_vibration">专讟讟 讛转专讗讛</string>
+    <string name="prefs_wena3_notification_vibration_repetition_0">诇诇讗 讛驻住拽讛</string>
+    <string name="prefs_wena3_notification_vibration_repetition_4">4 驻注诪讬诐</string>
+    <string name="temperature_scale_fahrenheit">驻专谞讛讬讬讟</string>
+    <string name="prefs_wena3_vibration_strength_item">注讜爪诪转 专讟讟</string>
+    <string name="prefs_wena3_receive_calls_hint">讗诐 讛讗驻砖专讜转 讻讘讜讬讛, 诇讗 转讜驻注谞讛 讛转专讗讜转 注诇 砖讬讞讜转 谞讻谞住讜转 讘志Wena</string>
+    <string name="prefs_wena3_button_action_item_long">诇讞讬爪讛 讗专讜讻讛</string>
+    <string name="prefs_wena3_menu_icon_name_payment">转砖诇讜诐</string>
+    <string name="prefs_wena3_status_page_title">住讚专 注诪讜讚 诪爪讘</string>
+    <string name="prefs_wena3_title_alarm">讛讙讚专讜转 砖注讜谉 诪注讜专专</string>
+    <string name="prefs_wena3_notification_default_vibration_repetition">讞讝专讛 注诇 专讟讟 讛转专讗讛</string>
+    <string name="prefs_wena3_hint_rich_design">讛讜住驻转 专讬讘讜注讬诐 诪注讜讙诇讬诐 诪住讘讬讘 诇住诪诇讬 诪住讱 讛讘讬转</string>
+    <string name="prefs_wena3_notification_default_led">爪讘注 谞讜专讬转 讛转专讗讛</string>
+    <string name="prefs_wena3_auto_power_off_item">讻讬讘讜讬 讗讜讟讜诪讟讬</string>
+    <string name="prefs_wena3_home_icon_name_edy">讬转专讛 讘志Edy</string>
+    <string name="prefs_wena3_vibration_warning">讗讝讛专讛</string>
+    <string name="prefs_wena3_smart_alarm_margin_item">讟讜讜讞 砖注讜谉 诪注讜专专 讞讻诐</string>
+    <string name="prefs_wena3_item_rich_design">诇讛砖转诪砖 讘注讬爪讜讘 注砖讬专</string>
+    <string name="prefs_wena3_day_start_hour_item">讛讬讜诐 诪转讞讬诇 讘志</string>
+    <string name="prefs_wena3_notification_per_app_settings_title">讛讙讚专讜转 讛转专讗讛 诇驻讬 讬讬砖讜诪讜谉</string>
+    <string name="prefs_wena3_vibration_none">讘诇讬 专讟讟</string>
+    <string name="prefs_wena3_vibration_strength_item_strong">讞讝拽讛</string>
+    <string name="prefs_wena3_vibration_triple">诪砖讜诇砖</string>
+    <string name="prefs_wena3_button_action_name_activity_screen">诪住讱 驻注讬诇讜转</string>
+    <string name="prefs_wena3_notification_settings_title">讛讙讚专讜转 讛转专讗讛</string>
+    <string name="devicetype_sony_wf_1000xm5">Sony WF-1000XM5鈥�</string>
+    <string name="pref_message_privacy_mode_bodyonly">讛住转专转 讛讙讜祝 讘诇讘讚</string>
+    <string name="latvian">诇讟讘讬转</string>
+    <string name="common_symbols">住讬诪谞讬诐 谞驻讜爪讬诐</string>
+    <string name="pref_title_notifications_ignore_work_profile">讛转注诇诪讜转 诪讛讜讚注讜转 驻专讜驻讬诇 注讘讜讚讛</string>
+    <string name="pref_summary_notifications_ignore_work_profile">诇讗 诇砖诇讜讞 讛讜讚注讜转 诪讬讬砖讜诪讜谞讬诐 讘驻专讜驻讬诇 讛注讘讜讚讛 砖诇 讛砖注讜谉</string>
+    <string name="femometer_measurement_mode_title">诪爪讘 诪讚讬讚讛</string>
+    <string name="femometer_measurement_mode_quick">诪爪讘 诪讛讬专 (30 砖谞壮)</string>
+    <string name="femometer_measurement_mode_normal">诪爪讘 专讙讬诇 (60鈥�-90 砖谞壮)</string>
+    <string name="femometer_measurement_mode_precise">诪爪讘 诪讚讜讬拽 (3 讚拽壮)</string>
+    <string name="pref_test_features_summary">讬讻讜诇讜转 砖诪讜驻注诇讜转 讘讛转拽谉 讛讝讛</string>
+    <string name="pref_summary_preview_message_in_title">诪爪讬讙 转爪讜讙讛 诪拽讚讬诪讛 砖诇 讛讛讜讚注讛 讘讻讜转专转 讛讛转专讗讛 讻驻讬 砖诪专砖讛 讛诪讻砖讬专</string>
+    <string name="pref_title_casio_alert_calendar">讛转专讗讛 诇讛讜讚注讜转 诇讜讞 砖谞讛</string>
+    <string name="pref_summary_send_app_notifications">砖诇讬讞转 讛讜讚注讜转 讬讬砖讜诐 诇讛转拽谉</string>
+    <string name="pref_title_goal_secondary">讬注讚 诪砖谞讬</string>
+    <string name="pref_title_send_app_notifications">砖诇讬讞转 讛转专讗讜转</string>
+    <string name="prefs_wearmode">诪爪讘 注讟讬讬讛</string>
+    <string name="pref_sleep_mode_schedule_summary">诇砖诇讜讞 转讝讻讜专转 讜诇讛讬讻谞住 诇诪爪讘 砖讬谞讛 讘讝诪谉 讛砖讬谞讛. 讘诪讜注讚 讛讛砖讻诪讛 讛诪转讜讝诪谉, 转讜砖诪注 讛转专讗转 讛讛砖讻诪讛.</string>
+    <string name="pref_vitality_score_7_day_summary">拽讘诇转 讛讜讚注讛 讻讗砖专 讚讬专讜讙 讛讞讬讜谞讬转 砖诇讱 诪讙讬注 诇志30, 60 讗讜 100 讘志7 讛讬诪讬诐 讛讞讜诇驻讬诐</string>
+    <string name="pref_force_connection_type_description">讗驻砖专 诇谞住讜转 诇讻驻讜转 讗转 住讜讙 讛讞讬讘讜专 讗诐 讛讛转拽谉 砖诇讱 诇讗 诪讙讬讘 诇志Gadgetbridge</string>
+    <string name="fossil_hr_nav_app_not_installed_notify_text">讛谞讬讜讜讟 讛讞诇 讗讱 navigationApp (讬讬砖讜诪讜谉 讛谞讬讜讜讟) 诇讗 诪讜转拽谉 讘砖注讜谉 砖诇讱. 谞讗 诇讛转拽讬谉 讗讜转讜 诪诪谞讛诇 讛讬讬砖讜诪讜谞讬诐.</string>
+    <string name="pref_call_rejection_method_summary">讗讬讝讜 驻注讜诇讛 诇谞拽讜讟 讻砖砖讬讞讛 谞讻谞住转 谞讚讞讬转 讘砖注讜谉</string>
+    <string name="huawei_ignore_wakeup_status_start_description">讬讻讜诇 诇住讬讬注 注诐 讝讬讛讜讬 谞讻讜谉 砖诇 砖讬谞讛. 讙诇讜讬 诪讬讬讚讬转 讘转爪讜讙转 讛驻注讬诇讜讬讜转 讛讬讜诪讬转.</string>
+    <string name="huawei_ignore_wakeup_status_end_description">讬讻讜诇 诇住讬讬注 注诐 讝讬讛讜讬 谞讻讜谉 砖诇 砖讬谞讛. 讙诇讜讬 诪讬讬讚讬转 讘转爪讜讙转 讛驻注讬诇讜讬讜转 讛讬讜诪讬转.</string>
+    <string name="busy_task_fetch_temperature">谞转讜谞讬 讛讟诪驻专讟讜专讛 诪转拽讘诇讬诐</string>
+    <string name="busy_task_fetch_statistics">讛住讟讟讬住讟讬拽讛 诪转拽讘诇转</string>
+    <string name="pref_disable_find_phone_with_dnd">诇讛砖讘讬转 讗转 讗讬转讜专 讛讟诇驻讜谉 砖诇讬 讻砖诪爪讘 鈥炞溩� 诇讛驻专讬注鈥� 驻注讬诇</string>
+    <string name="pref_title_general_reconnectonlytoconnected">讛转讞讘专讜转 诪讞讚砖 诇专拽 诇讛转拽谞讬诐 讛诪讞讜讘专讬诐</string>
+    <string name="pref_summary_general_reconnectonlytoconnected">讛转讞讘专讜转 诪讞讚砖 专拽 诇讛转拽谞讬诐 讘诪讞讜讘专讬诐, 讘诪拽讜诐 诇讛转讞讘专 诪讞讚砖 诇讻诇 讛讛转拽谞讬诐</string>
+    <string name="updatefirmwareoperation_updateproblem_low_battery">住讜诇诇转 讛讛转拽谉 讞诇砖讛</string>
+    <string name="clap_hands_to_wakeup_device">诪讞讬讗转 讻驻讬讬诐 转讚诇讬拽 讗转 讛转爪讜讙讛</string>
+    <string name="clap_hands_to_wakeup_device_summary">诪讞讬讗转 讻驻讬讬诐 谞讜住驻转 转讻讘讛 讗转 讛转爪讜讙讛</string>
+    <string name="pixoo_power_saving_summary">讛诪住讱 讬讬讻讘讛 诇讗讞专 砖讝讜讛讛 砖拽讟 诇诪砖讱 讝诪谉 诪讛</string>
+    <string name="pref_vitality_score_daily_summary">拽讘诇转 讛讜讚注讛 讻砖讛讙注转 诇讻诪讜转 谞拽讜讚讜转 讛讞讬讜谞讬讜转 讛诪专讘讬讜转 诇讗讜转讜 讛讬讜诐</string>
+    <string name="widget_screen_no_free_slots_description">诇诪讻砖讬专 讗讬谉 讞诇诇讬诐 驻谞讜讬讬诐 诇讞诇讜谞讬讜转 讛讜讜讬讚讙壮讟讬诐 (住讱 讻诇 讛讞诇诇讬诐: %1$s)</string>
+    <string name="wearmode_band">爪诪讬讚 (讬讚)</string>
+    <string name="wearmode_pebble">讞诇讜拽 谞讞诇 (讗讘讝诐 诇谞注诇)</string>
+    <string name="wearmode_necklace">诪讞专讜讝转 (专爪讜注讛 住讘讬讘 讛爪讜讜讗专)</string>
+    <string name="pref_title_preview_message_in_title">讛爪讙转 转拽爪讬专 砖诇 讛讛讜讚注讛 讘讻讜转专转</string>
+    <string name="pref_summary_casio_alert_calendar">讛转专讗讛 (专讟讟/爪驻爪讜祝) 注诇 讛讜讚注讜转 诪诇讜讞 讛砖谞讛</string>
+    <string name="pref_title_casio_alert_call">讛转专讗讛 注诇 砖讬讞讜转 谞讻谞住讜转</string>
+    <string name="pref_summary_casio_alert_call">讛转专讗讛 (专讟讟/爪驻爪讜祝) 注诇 砖讬讞讜转 谞讻谞住讜转</string>
+    <string name="pref_title_casio_alert_email">讛转专讗讛 诇讛讜讚注讜转 讚讜讗状诇</string>
+    <string name="pref_summary_casio_alert_email">讛转专讗讛 (专讟讟/爪驻爪讜祝) 注诇 讛讜讚注讜转 注诇 讚讜讗状诇</string>
+    <string name="pref_title_casio_alert_sms">讛转专讗讛 注诇 诪住专讜谞讬诐</string>
+    <string name="pref_summary_casio_alert_sms">讛转专讗讛 (专讟讟/爪驻爪讜祝) 注诇 讛讜讚注讜转 注诇 诪住专讜谞讬诐</string>
+    <string name="pref_title_casio_alert_other">讛转专讗讛 注诇 讛讜讚注讜转 讗讞专讜转</string>
+    <string name="pref_summary_casio_alert_other">讛转专讗讛 (专讟讟/爪驻爪讜祝) 注诇 讛讜讚注讜转 讘拽讟讙讜专讬讛 讛讗讞专转</string>
+    <string name="heartrate_bpm_155">讚讜驻拽 155</string>
+    <string name="heartrate_bpm_165">讚讜驻拽 165</string>
+    <string name="heartrate_bpm_175">讚讜驻拽 175</string>
+    <string name="heartrate_bpm_185">讚讜驻拽 185</string>
+    <string name="heartrate_bpm_195">讚讜驻拽 195</string>
+    <string name="heartrate_bpm_205">讚讜驻拽 205</string>
+    <string name="prefs_heartrate_alert_active_high_threshold">住祝 讛转专讗转 讚讜驻拽 驻注讬诇讜转 讙讘讜讛</string>
+    <string name="pref_summary_osmand_packagename">诪砖诪砖 诇讘讞讬专转 讙专住转 讛志OsmAnd 讗诇讬讛 诇讛转讞讘专</string>
+    <string name="pref_summary_fossil_hr_nav_vibrate">讛讗诐 注诇 讛砖注讜谉 诇专讟讜讟 注诇 讻诇 讛谞讞讬讬转 谞讬讜讜讟 讞讚砖讛 讗讜 注讚讻谞讬转 (专拽 讻砖讛讬讬砖讜诪讜谉 讘专拽注)</string>
+    <string name="pref_summary_fossil_hr_nav_foreground">讛讗诐 注诇 讬讬砖讜诪讜谉 讛谞讬讜讜讟 诇拽驻讜抓 诇讞讝讬转 讗讜讟讜诪讟讬转 讻砖讛讜讗 诪拽讘诇 讛谞讞讬讜转 谞讬讜讜讟</string>
+    <string name="pref_force_dnd_support">讗讬诇讜抓 转诪讬讻讛 讘鈥炞溩� 诇讛驻专讬注鈥�</string>
+    <string name="pref_title_prefix_notification_with_app">砖诐 讛讬讬砖讜诪讜谉 讘讛转专讗讛</string>
+    <string name="pref_summary_fossil_hr_navigation_instructions">讛讙讚专转 讛转谞讛讙讜转 讬讬砖讜诪讜谉 讛谞讬讜讜讟 讘砖注讜谉</string>
+    <string name="pref_device_action_dnd_alarms">诇讗 诇讛驻专讬注 - 讛转专讗讜转 讘诇讘讚</string>
+    <string name="huawei_alarm_smart_description">诇讗 诇讘讟诇 讗转 转讬讘转 住讬诪讜谉 讛讛砖讻诪讛 讛讞讻诪讛.</string>
+    <string name="huawei_alarm_event_description">诇讗 诇住诪谉 讗转 转讬讘转 住讬诪讜谉 讛讛砖讻诪讛 讛讞讻诪讛.</string>
+    <string name="devicetype_miband8">Xiaomi Smart Band 8鈥�</string>
+    <string name="devicetype_miband7pro">Xiaomi Smart Band 7 Pro鈥�</string>
+    <string name="pref_device_action_dnd_priority">诇讗 诇讛驻专讬注 - 讚讞讜祝 讘诇讘讚</string>
+    <string name="pref_device_action_dnd_off">诇讗 诇讛驻专讬注 - 讻讘讜讬</string>
+    <string name="pref_device_action_dnd_on">诇讗 诇讛驻专讬注 - 驻注讬诇</string>
+    <string name="pref_enable_call_accept_summary">讛驻注诇转 拽讘诇转 砖讬讞讜转 诪讛诪讻砖讬专</string>
+    <string name="devicetype_xiaomi_watch_s1_active">Xiaomi Watch S1 Active鈥�</string>
+    <string name="devicetype_mijia_lywsd03">讞讬讬砖谉 讟诪驻专讟讜专讛 讜诇讞讜转 2 砖诇 Mijia</string>
+    <string name="devicetype_miband8pro">Xiaomi Smart Band 8 Pro鈥�</string>
+    <string name="fossil_hr_nav_app_not_installed_notify_title">诇讗 诪讜转拽谉 讬讬砖讜诪讜谉 谞讬讜讜讟 讘砖注讜谉</string>
+    <string name="pref_do_not_disturb_not_wear">诇讗 诇讛驻专讬注 讻砖诇讗 谞注谞讚</string>
+    <string name="pref_enable_call_reject_summary">讛驻注诇转 讚讞讬讬转 砖讬讞讜转 诪讛诪讻砖讬专</string>
+    <string name="devicetype_huawei_watchgt2">Huawei Watch GT 2 (Pro)鈥�</string>
+    <string name="devicetype_huawei_watchgt3">Huawei Watch GT 3 (Pro)鈥�</string>
+    <string name="devicetype_redmiwatch3active">Redmi Watch 3 Active鈥�</string>
+    <string name="activity_type_crossfit">拽专讜住驻讬讟</string>
+    <string name="activity_type_fencing">住讬讬祝</string>
+    <string name="prefs_password_4_digits_0_to_9_summary">讛住讬住诪讛 讞讬讬讘转 诇讛讬讜转 诪讜专讻讘转 诪志4 住驻专讜转 讘诇讘讚</string>
+    <string name="zepp_os_watchface_city_of_speed">注讬专 砖诇 诪讛讬专讜转</string>
+    <string name="active_time">讝诪谉 驻注讬诇</string>
+    <string name="standing_time">讝诪谉 注诪讬讚讛</string>
+    <string name="silent_mode_normal_silent">专讙讬诇 / 砖拽讟</string>
+    <string name="silent_mode_vibrate_silent">专讟讟 / 砖拽讟</string>
+    <string name="alarm_smart_wakeup_interval_default">5 讚拽讜转</string>
+    <string name="prefs_disconnect_notification_summary">讛转专讗讛 讘讛转拽谉 讘注转 谞讬转讜拽 诪讘诇讜讟讜转壮.</string>
+    <string name="hydration_dnd_summary">讛砖讘转转 讗讝讛专讜转 砖转讬讬讛 诇驻专拽 讝诪谉 诪住讜讬诐</string>
+    <string name="prefs_phone_silent_mode">诪爪讘 砖拽讟 讘讟诇驻讜谉</string>
+    <string name="silent_mode_normal_vibrate">专讙讬诇 / 专讟讟</string>
+    <string name="prefs_device_name">砖诐 讛讛转拽谉</string>
+    <string name="do_not_disturb_lift_wrist_summary">专拽 讗诐 讛驻注诇转 讛转爪讜讙讛 讘讛谞驻转 讛讬注讚 驻注讬诇讛</string>
+    <string name="manual">讬讚谞讬</string>
+    <string name="charts_legend_spo2_average">诪诪讜爪注 讞诪爪谉 讘讚诐</string>
+    <string name="dnd_all_day">讻诇 讛讬讜诐</string>
+    <string name="activity_type_indoor_running">专讬爪讛 讘诪拽讜诐 住讙讜专</string>
+    <string name="activity_type_mountain_hike">讟讬讜诇 讘讛专讬诐</string>
+    <string name="activity_type_free_training">讗讬诪讜谉 讞讜驻砖讬</string>
+    <string name="activity_type_rower">诪转拽谉 讞转讬专讛</string>
+    <string name="activity_type_fitness_exercises">讗讬诪讜谞讬 讞讬讟讜讘</string>
+    <string name="activity_type_functional_training">讗讬诪讜谉 驻讜谞拽爪讬讜谞诇讬</string>
+    <string name="activity_type_physical_training">讗讬诪讜谉 驻讬讝讬</string>
+    <string name="activity_type_taekwondo">讟讗拽讜讜谞讚讜</string>
+    <string name="activity_type_karate">拽专讗讟讛</string>
+    <string name="activity_type_kendo">拽谞讚讜</string>
+    <string name="activity_type_horizontal_bar">诪讜讟 讗讜驻拽讬</string>
+    <string name="activity_type_parallel_bar">诪讜讟 诪拽讘讬诇讬</string>
+    <string name="activity_type_cooldown">诪谞讜讞讛</string>
+    <string name="activity_type_fitness_gaming">诪砖讞拽 讞讬讟讜讘</string>
+    <string name="activity_type_aerobic_exercise">转专讙讬诇 讗讬专讜讘讬</string>
+    <string name="danish">讚谞讬转</string>
+    <string name="devicetype_femometer_vinca2">Femometer Vinca II鈥�</string>
+    <string name="huawei_reparse_workout_data_description">讝讛 讬讘爪注 诪砖讛讜 专拽 讗讞专讬 注讚讻讜谞讬诐 诪住讜讬诪讬诐</string>
+    <string name="huawei_reparse_workout_data">驻注谞讜讞 谞转讜谞讬 讗讬诪讜谉 诪讞讚砖</string>
+    <string name="pref_title_debug">讘拽砖转 谞讬驻讜讬 转拽诇讜转</string>
+    <string name="pref_call_rejection_method_title">砖讬讟转 讚讞讬讬转 砖讬讞讛</string>
+    <string name="serial_number">诪住驻专 住讬讚讜专讬</string>
+    <string name="pref_sleep_mode_schedule_title">转讝诪讜谉 诪爪讘 砖讬谞讛</string>
+    <string name="wake_up_time">讛砖讻诪讛</string>
+    <string name="bedtime">讛专讚诪讛</string>
+    <string name="menuitem_focus">诪讬拽讜讚</string>
+    <string name="pref_vitality_score_title">讚讬专讜讙 讞讬讜谞讬讜转</string>
+    <string name="devicetype_pixoo">Pixoo鈥�</string>
+    <string name="devicetype_mi_watch_color_sport">Mi Watch Color Sport鈥�</string>
+    <string name="pref_vitality_score_daily_title">讛转拽讚诪讜转 讬讜诪讬转</string>
+    <string name="widget_move_down">讛讜专讚讛 诇诪讟讛</string>
+    <string name="uploadwatchfaceoperation_complete">讛转拽谞转 驻谞讬 砖注讜谉 讛讜砖诇诪讛</string>
+    <string name="uploadwatchfaceoperation_failed">讛转拽谞转 驻谞讬 砖注讜谉 谞讻砖诇讛</string>
+    <string name="pref_title_fossil_hr_nav_foreground">拽讬讚讜诐 诇讞讝讬转</string>
+    <string name="widget">讜讬讚讙壮讟</string>
+    <string name="pref_title_fossil_hr_navigation_instructions">讛谞讞讬讜转 谞讬讜讜讟</string>
+    <string name="widget_layout_top_2_bot_1">2 诇诪注诇讛, 1 诇诪讟讛</string>
+    <string name="pref_title_osmand_packagename">砖诐 讞讘讬诇转 OsmAnd</string>
+    <string name="pref_summary_debug">砖诇讬讞转 讘拽砖转 谞讬驻讜讬 转拽诇讜转 诇诪讻砖讬专 Huawei</string>
+    <string name="scan_scanning_single_device">讛转拽谉 谞住专拽</string>
+    <string name="scan_scanning_all_devices">讻诇 讛讛转拽谞讬诐 谞住专拽讬诐</string>
+    <string name="scan_not_scanning">诇讗 诪转讘爪注转 住专讬拽讛</string>
+    <string name="error_scan_failed">讛住专讬拽讛 谞讻砖诇讛: %d</string>
+    <string name="waiting_for_bluetooth">讘讛诪转谞讛 诇讘诇讜讟讜转壮鈥�</string>
+    <string name="auto_reconnect_ble_scan_summary">诇讛诪转讬谉 诇住专讬拽转 讛转拽谞讬诐 讘诪拽讜诐 谞讬住讬讜谞讜转 讛转讞讘专讜转 注讬讜讜专讬诐</string>
+    <string name="auto_reconnect_ble_scan_title">讛转讞讘专讜转 诪讞讚砖 诇驻讬 住专讬拽转 BLE</string>
+    <string name="device_state_waiting_scan">讘讛诪转谞讛 诇住专讬拽转 讛转拽谞讬诐</string>
+    <string name="widget_screen_delete_confirm_title">诪讞讬拽转 诪住讱 讜讬讚讙壮讟讬诐</string>
+    <string name="widget_layout">驻专讬住转 讜讬讚讙壮讟讬诐</string>
+    <string name="widget_subtype">转转志住讜讙 讜讬讚讙壮讟</string>
+    <string name="widget_screen_x">诪住讱 %s</string>
+    <string name="pref_test_features_title">讬讻讜诇讜转</string>
+    <string name="warning_missing_notification_permission">诇讗 谞讬转谉 诇驻专住诐 讛转专讗讜转 转讛诇讬讱 注拽讘 诪讞住讜专 讘讛专砖讗讜转</string>
+    <string name="pref_force_connection_type_bt_classic">讘诇讜讟讜转壮 拽诇住讬</string>
+    <string name="notification_channel_connection_status_name">诪爪讘 讞讬讘讜专</string>
+    <string name="widget_screen">诪住讱 讜讬讚讙壮讟讬诐</string>
+    <string name="widget_screen_min_screens">诪讜讻专讞讬诐 诇讛讬讜转 诇驻讞讜转 %1$s 诪住讻讬诐</string>
+    <string name="not_set">诇讗 讛讜讙讚专</string>
+    <string name="devicetype_xiaomi_watch_s3">Xiaomi Watch S3鈥�</string>
+    <string name="menuitem_stats">住讟讟讬住讟讬拽讛</string>
+    <string name="scan_scanning_multiple_devices">谞住专拽讬诐 %d 讛转拽谞讬诐</string>
+    <string name="notification_channel_scan_service_name">砖讬专讜转 住专讬拽讛</string>
+    <string name="prompt_restart_gadgetbridge">谞讗 诇讛驻注讬诇 讗转 GB 诪讞讚砖 讻讚讬 砖讝讛 讬讬讻谞住 诇转讜拽祝.</string>
+    <string name="activity_info">驻专讟讬 驻注讬诇讜转</string>
+    <string name="uploadwatchfaceoperation_in_progress">驻谞讬 砖注讜谉 谞砖诇讞讬诐</string>
+    <string name="uploading_watchface">驻谞讬 砖注讜谉 谞砖诇讞讬诐鈥�</string>
+    <string name="widget_unknown_workout">讗讬诪讜谉 诇讗 诪讜讻专 - %s</string>
+    <string name="widget_missing_parts">谞讗 诇讘讞讜专 讗转 讻诇 讛讜讜讬讚讙壮讟讬诐</string>
+    <string name="widget_move_up">讛注诇讗讛 诇诪注诇讛</string>
+    <string name="widget_layout_two">2 讜讬讚讙壮讟讬诐</string>
+    <string name="widget_layout_single">讜讬讚讙壮讟</string>
+    <string name="widget_layout_top_2_bot_2">2 诇诪注诇讛, 2 诇诪讟讛</string>
+    <string name="widget_layout_top_1_bot_2">1 诇诪注诇讛, 2 诇诪讟讛</string>
+    <string name="widget_screen_delete_confirm_description">诇诪讞讜拽 讗转 鈥�%1$s鈥�?</string>
+    <string name="pref_vitality_score_7_day_title">讛转拽讚诪讜转 讘志7 讬诪讬诐</string>
+    <string name="devicetype_xiaomi_watch_s1_pro">Xiaomi Watch S1 Pro鈥�</string>
+    <string name="devicetype_xiaomi_watch_s1">Xiaomi Watch S1鈥�</string>
+    <string name="menuitem_running">专讬爪讛</string>
+    <string name="call_rejection_method_reject">讚讞讬讬讛</string>
+    <string name="pref_force_connection_type_auto">讗讜讟讜诪讟讬</string>
+    <string name="call_rejection_method_ignore">讛转注诇诪讜转 (砖拽讟)</string>
+    <string name="pref_title_navigation_prefs">讛注讚驻讜转 谞讬讜讜讟</string>
+    <string name="menuitem_alerts">讛转专讗讜转</string>
+    <string name="pref_navigation_app_gmaps">Google Maps鈥�</string>
+    <string name="pref_navigation_app_osmand">OsmAnd(+)鈥�</string>
+    <string name="pref_title_navigation_apps">讬讬砖讜诪讜谞讬 谞讬讜讜讟</string>
+    <string name="pref_force_connection_type_title">讗讻讬驻转 住讜讙 讞讬讘讜专</string>
+    <string name="pref_title_fossil_hr_nav_vibrate">专讟讟 注诐 拽讘诇转 讛谞讞讬讜转 讞讚砖讜转</string>
+    <string name="menuitem_workout_shortcuts">拽讬爪讜专讬 讗讬诪讜谞讬诐</string>
+    <string name="devicetype_redmi_watch_2">Redmi Watch 2鈥�</string>
+    <string name="pref_force_smart_alarm_summary">讗讬诇讜抓 转诪讬讻讛 讘讛转专讗讜转 讞讻诪讜转
+\n注诇 讗讞专讬讜转讱 讘诇讘讚</string>
+    <string name="huawei_ignore_wakeup_status_end">讛转注诇诪讜转 诪诪爪讘 住讬讜诐 讛砖讻诪讛</string>
+    <string name="devicetype_redmi_smart_band_pro">Redmi Smart Band Pro鈥�</string>
+    <string name="laneLength">讗讜专讱 讛诪住诇讜诇</string>
+    <string name="pref_force_wear_location_summary">讗讬诇讜抓 转诪讬讻讛 讘诪拽讜诐 注谞讬讚讛
+\n注诇 讗讞专讬讜转讱 讘诇讘讚</string>
+    <string name="pref_force_wear_location">讗讬诇讜抓 诪拽讜诐 注谞讬讚讛</string>
+    <string name="devicetype_huawei_talk_band_b6">Huawei Talk Band B6鈥�</string>
+    <string name="yard">讬讗专讚</string>
+    <string name="pref_force_smart_alarm">讗讬诇讜抓 讛转专讗讛 讞讻诪讛</string>
+    <string name="pref_summary_prefix_notification_with_app">诇讛讜住讬祝 诇驻谞讬 讻讜转专转 讛讛转专讗讛 讗转 砖诐 讬讬砖讜诪讜谉 讛诪拽讜专</string>
+    <string name="strokes_minute">讞转讬专讜转/讚拽壮</string>
+    <string name="alarm_smart_wakeup_interval">讛驻专砖讬 讛砖讻诪讛 讞讻诪讛:</string>
+    <string name="devicetype_huawei_watchgt2e">Huawei Watch GT 2e鈥�</string>
+    <string name="devicetype_redmi_watch_2_lite">Redmi Watch 2 Lite鈥�</string>
+    <string name="activity_type_cross_trainer">讗讬诪讜谉 诪砖讜诇讘</string>
+    <string name="activity_type_stair_stepper">诪讻砖讬专 诪讚专讙讜转</string>
+    <string name="activity_type_sit_ups">讻驻讬驻讜转 讘讟谉</string>
+    <string name="activity_type_ice_hockey">讛讜拽讬 拽专讞</string>
+    <string name="activity_type_skating">讛讞诇拽讛</string>
+    <string name="activity_type_shot">讛讚讬驻转 讻讚讜专 讘专讝诇</string>
+    <string name="activity_type_lacross">诇拽专讜住</string>
+    <string name="activity_type_pickleball">驻讬拽诇讘讜诇</string>
+    <string name="activity_type_australian_football">驻讜讟讘讜诇 讗讜住讟专诇讬</string>
+    <string name="activity_type_dodgeball">诪讞谞讬讬诐</string>
+    <string name="activity_type_softball">住讜驻讟讘讜诇</string>
+    <string name="activity_type_snow_sports">住驻讜专讟 砖诇讙</string>
+    <string name="activity_type_curling">拽专诇讬谞讙</string>
+    <string name="devicetype_redmi_smart_band_2">鈥嶳edmi Smart Band 2</string>
+    <string name="devicetype_redmiwatch3">Redmi Watch 3鈥�</string>
+    <string name="devicetype_huawei_band4pro">Huawei Band 4 (Pro)鈥�</string>
+    <string name="devicetype_huawei_watch_gt">Huawei Watch GT鈥�</string>
+    <string name="menuitem_readiness">诪讜讻谞讜转</string>
+    <string name="menuitem_thermometer">诪讚 讞讜诐</string>
+    <string name="menuitem_zepp_pay">Zepp Pay鈥�</string>
+    <string name="menuitem_apps_shortcuts">拽讬爪讜专讬 讬讬砖讜诪讜谞讬诐</string>
+    <string name="menuitem_body_composition">讛专讻讘 讙讜祝</string>
+    <string name="menuitem_buzzer_intensity">注讜爪诪转 讝诪讝诐</string>
+    <string name="huawei_ignore_wakeup_status_start">讛转注诇诪讜转 诪诪爪讘 讛转讞诇转 讛砖讻诪讛</string>
+    <string name="pref_force_dnd_support_summary">讗讬诇讜抓 转诪讬讻讛 讘鈥炞溩� 诇讛驻专讬注鈥�
+\n注诇 讗讞专讬讜转讱 讘诇讘讚</string>
+    <string name="devicetype_xiaomi_watch_lite">Xiaomi Watch Lite鈥�</string>
+    <string name="devicetype_huawei_watchfit">Huawei Watch Fit鈥�</string>
+    <string name="temperature">讟诪驻专讟讜专讛</string>
+    <string name="activity_type_sailing">砖讬讬讟</string>
+    <string name="activity_type_wrestling">讛讬讗讘拽讜转</string>
+    <string name="activity_type_trail_run">专讬爪转 诪住诇讜诇</string>
+    <string name="pref_spo_automatic_enable">讛驻注诇转 诪讚讬讚讛 讗讜讟讜诪讟讬转 砖诇 讞诪爪谉 讘讚诐</string>
+    <string name="pref_enable_call_reject">讛驻注诇转 讚讞讬讬转 砖讬讞讜转</string>
+    <string name="huawei_trusleep_summary_light">诪注拽讘 砖讬谞讛 诪砖讜驻专</string>
+    <string name="pref_force_options">讗讬诇讜抓 讗驻砖专讜讬讜转</string>
+    <string name="activity_type_high_jump">拽驻讬爪讛 诇讙讜讘讛</string>
+    <string name="activity_type_long_jump">拽驻讬爪讛 诇专讜讞拽</string>
+    <string name="activity_type_javelin">讛讟诇转 讻讬讚讜谉</string>
+    <string name="activity_type_plank">驻诇讗谞拽</string>
+    <string name="activity_type_pull_ups">注诇讬讬转 诪转讞</string>
+    <string name="activity_type_smith_machine">诪讻讜谞转 住诪讬转壮</string>
+    <string name="activity_type_battle_rope">讞讘诇 拽专讜住驻讬讟</string>
+    <string name="activity_type_push_ups">砖讻讬讘讜转 住诪讬讻讛</string>
+    <string name="activity_type_track_and_field">讗转诇讟讬拽讛 拽诇讛</string>
+    <string name="activity_type_flexibility">讙诪讬砖讜转</string>
+    <string name="activity_type_hula_hooping">讞讬砖讜拽 讛讜诇讛 讛讜驻</string>
+    <string name="activity_type_tai_chi">讟讗讬 爪壮讬</string>
+    <string name="activity_type_martial_arts">讗讜诪谞讜转 诇讞讬诪讛</string>
+    <string name="activity_type_other_dance">专讬拽讜讚 讗讞专</string>
+    <string name="activity_type_ballet">讘诇讟</string>
+    <string name="activity_type_latin_dance">专讬拽讜讚 诇讟讬谞讬</string>
+    <string name="activity_type_jazz_dance">专讬拽讜讚 讙壮讗讝</string>
+    <string name="activity_type_belly_dance">专讬拽讜讚讬 讘讟谉</string>
+    <string name="activity_type_dumbbell">讛专诪转 诪砖拽讜诇讬讜转</string>
+    <string name="activity_type_trampoline">讟专诪驻讜诇讬谞讛</string>
+    <string name="activity_type_stairs">诪讚专讙讜转</string>
+    <string name="activity_type_kite_flying">讛注驻转 注驻讬驻讜谞讬诐</string>
+    <string name="activity_type_horse_riding">专讻讬讘讛 注诇 住讜住讬诐</string>
+    <string name="activity_type_archery">拽砖转讜转</string>
+    <string name="activity_type_darts">讞讬爪讬诐</string>
+    <string name="activity_type_billiards">讘讬诇讬讗专讚</string>
+    <string name="activity_type_karting">拽专讟讬谞讙</string>
+    <string name="activity_type_kabaddi">拽讘讗讚讬</string>
+    <string name="activity_type_mind_and_body">谞驻砖 讜讙讜祝</string>
+    <string name="activity_type_fishing">讚讬讙</string>
+    <string name="activity_type_outdoor_walking">讛诇讬讻讛 讘讞讜抓</string>
+    <string name="activity_type_hunting">爪讬讚</string>
+    <string name="activity_type_rock_climbing">拽讬专 讟讬驻讜住</string>
+    <string name="activity_type_skateboarding">住拽讬讬讟讘讜专讚</string>
+    <string name="pref_enable_call_accept">讛驻注诇转 拽讘诇转 砖讬讞讜转</string>
+    <string name="pref_heartrate_automatic_enable">讛驻注诇转 诪讚讬讚转 讚讜驻拽 讗讜讟讜诪讟讬转</string>
+    <string name="activity_type_roller_skating">讛讞诇拽讛 注诇 讙诇讙讬诇讬讜转</string>
+    <string name="devicetype_nothing_cmf_watch_pro">CMF Watch Pro鈥�</string>
+    <string name="devicetype_nothingear2">Nothing Ear (2)鈥�</string>
+    <string name="pref_header_sony_sound_control">讘拽专转 砖诪注</string>
+    <string name="pref_wide_area_tap_title">讟驻讬讞讛 讘讗讝讜专 专讞讘</string>
+    <string name="devicetype_nothingearstick">Nothing Ear (Stick)鈥�</string>
+    <string name="prefs_active_noise_cancelling_transparency">砖拽讬驻讜转</string>
+    <string name="degrees">诪注诇讜转</string>
+    <string name="Pace">爪注讚</string>
+    <string name="RunningForm">爪讜专转 专讬爪讛</string>
+    <string name="pref_wide_area_tap_summary">讝讬讛讜讬 讟驻讬讞讜转 讘讬谉 讛诇讞讬讬诐 讜讛讗讜讝谞讬讬诐</string>
+    <string name="pref_force_enable_heartrate_support_summary">讗讬诇讜抓 讛驻注诇转 转诪讬讻讛 讘讚讜驻拽.
+\n注诇 讗讞专讬讜转讱 讘诇讘讚</string>
+    <string name="pref_force_enable_spo2_support">讗讬诇讜抓 转诪讬讻讛 讘诪讚讬讚转 讞诪爪谉</string>
+    <string name="unbind_before_pair_title">讻讘专 诪讗讜讙讚</string>
+    <string name="pref_force_enable_heartrate_support">讗讬诇讜抓 转诪讬讻讛 讘讚讜驻拽</string>
+    <string name="pref_force_enable_spo2_support_summary">讗讬诇讜抓 转诪讬讻讛 讘诪讚讬讚转 讞诪爪谉 讘讚诐.
+\n诇砖讬诪讜砖 注诇 讗讞专讬讜转讱 讘诇讘讚</string>
+    <string name="state_scanned">谞住专拽</string>
+    <string name="weeksleepchart_sleep_a_week_or_month">砖讬谞讛 讘砖讘讜注/讞讜讚砖</string>
+    <string name="pref_header_audio">砖诪注</string>
+    <string name="weekstepschart_steps_a_week_or_month">爪注讚讬诐 诇砖讘讜注/讞讜讚砖</string>
+    <string name="busy_task_fetch_sports_details_interrupted">诪砖讬讻转 驻专讟讬 讛住驻讜专讟 谞拽讟注讛</string>
+    <string name="fw_upgrade_notice_amazfitbip3">驻注讜诇讛 讝讜 转转拽讬谉 讗转 讛拽讜砖讞讛 %s 注诇 讛志Amazfit Bip 3 砖诇讱.
+\n
+\n谞讗 诇讜讜讚讗 讗转 讛转拽谞转 拽讜砖讞转 讛志鈥�.fw 讘讛转讞诇讛, 诇讗讞专 诪讻谉 讗转 拽讜讘抓 讛志鈥�.res 讛砖注讜谉 砖诇讱 讬讜驻注诇 诪讞讚砖 诇讗讞专 讛转拽谞转 拽讜讘抓 讛志fw.
+\n
+\n诇转砖讜诪转 诇讘讱: 讗讬谉 爪讜专讱 讘讛转拽谞转 拽讜讘抓 讛志鈥�.res 讗诐 讛讜讗 讻讘专 讝讛讛 诇讝讛 砖讛转拽谞转 讘注讘专.
+\n
+\n讛诪砖讱 讛转讛诇讬讱 讛讜讗 注诇 讗讞专讬讜转讱!</string>
+    <string name="pref_header_generic">讙谞专讬</string>
+    <string name="pref_title_notification_wake_on_open">讛转注讜专专讜转 讜砖讞专讜专 谞注讬诇讛 讗讜讟讜诪讟讬讬诐</string>
+    <string name="pref_summary_notification_wake_on_open">诇讛注讬专 讜诇砖讞专专 讗转 讛谞注讬诇讛 砖诇 诪讻砖讬专 讛志Android 讻砖讛讛转拽谉 砖讜诇讞 转讙讜讘转 驻转讬讞讛 讘讞讝专讛. 讞讬讬讘 诇讛讬讜转 讘诪爪讘 讗诪讬谉.</string>
+    <string name="pref_auto_reply_calls_delay_title">讛砖讛讬讬转 诪注谞讛 讗讜讟讜诪讟讬</string>
+    <string name="pref_auto_reply_calls_delay_summary">诪住驻专 讛砖谞讬讜转 砖诇讗讞专讬讛谉 讛砖讬讞讛 转转拽讘诇 讗讜讟讜诪讟讬转</string>
+    <string name="pref_auto_reply_calls_title">诇注谞讜转 诇砖讬讞讜转 讟诇驻讜谉 讗讜讟讜诪讟讬转</string>
+    <string name="pref_auto_reply_calls_summary">讛讟诇驻讜谉 讬拽讘诇 砖讬讞讜转 谞讻谞住讜转 讗讜讟讜诪讟讬转</string>
+    <string name="pref_dashboard_widget_today_hr_interval_title">诪砖讱 讚讜驻拽</string>
+    <string name="pref_dashboard_select_devices_summary">砖讬诇讜讘 驻注讬诇讜转 诪讛转拽谞讬诐 诪住讜讬诪讬诐 诇住讬讻讜诪讬诐 砖诇 诇讜讞 讛讘拽专讛</string>
+    <string name="pref_dashboard_widget_today_hr_interval_summary">讻诪讜转 讛讚拽讜转 砖讛转专砖讬诐 诪爪讬讙 鈥氉犠⒆犠撯�� 讗讞专讬 讻诇 诪讚讬讚转 讚讜驻拽 诪讜爪诇讞转</string>
+    <string name="devicetype_amazfit_active_edge">Amazfit Active Edge鈥�</string>
+    <string name="devicetype_amazfit_bip3">Amazfit Bip 3鈥�</string>
+    <string name="devicetype_huawei_watchgt4">Huawei Watch GT 4鈥�</string>
+    <string name="devicetype_huawei_watchultimate">Huawei Watch Ultimate鈥�</string>
+    <string name="sony_protocol_v2">讙专住讛 2</string>
+    <string name="sony_protocol_v1">讙专住讛 1</string>
+    <string name="companion_pairing_request_title">讛转拽谉 诇讬讜讜讬</string>
+    <string name="zepp_os_watchface_the_ultima">讛讗讜诇讟讬诪讛</string>
+    <string name="prefs_workmode">诪爪讘 注讘讜讚讛</string>
+    <string name="devicetype_mijia_mho_c303">Mijia MHO-C303鈥�</string>
+    <string name="devicetype_sony_linkbuds">Sony LinkBuds鈥�</string>
+    <string name="zepp_os_watchface_lightning_flash">讛讘讝拽 诪讛讘讛讘</string>
+    <string name="zepp_os_watchface_free_combination">砖讬诇讜讘 讞讜驻砖讬</string>
+    <string name="zepp_os_watchface_pure_white">诇讘谉 讟讛讜专</string>
+    <string name="devicetype_huawei_band8">Huawei Band 8鈥�</string>
+    <string name="zepp_os_watchface_starry_sky">诇讬诇 讻讜讻讘讬诐</string>
+    <string name="devicetype_honor_band5">Honor Band 5鈥�</string>
+    <string name="devicetype_honor_band3">Honor Band 3鈥�</string>
+    <string name="devicetype_honor_band4">Honor Band 4鈥�</string>
+    <string name="sony_protocol_v3">讙专住讛 3</string>
+    <string name="devicetype_miband8active">Xiaomi Smart Band 8 Active鈥�</string>
+    <string name="devicetype_amazfit_balance">Amazfit Balance鈥�</string>
+    <string name="devicetype_colacao21">ColaCao 2021鈥�</string>
+    <string name="devicetype_colacao23">ColaCao 2023鈥�</string>
+    <string name="devicetype_huawei_band_aw70">Huawei Band (AW70)鈥�</string>
+    <string name="devicetype_huawei_band6">Huawei Band 6鈥�</string>
+    <string name="devicetype_huawei_band7">Huawei Band 7鈥�</string>
+    <string name="devicetype_redmi_watch_4">Redmi Watch 4鈥�</string>
+    <string name="zepp_os_watchface_vast_sky">砖诪讬讬诐 驻专讜住讬诐</string>
+    <string name="zepp_os_watchface_guider">诪谞讞讛</string>
+    <string name="flatDistance">诪专讞拽 讘诪讬砖讜专</string>
+    <string name="huawei_trusleep_title">HUAWEI TruSleep 鈩⑩��</string>
+    <string name="widget_layout_top_wide_bot_large">专讞讘 诇诪注诇讛, 讙讚讜诇 诇诪讟讛</string>
+    <string name="widget_layout_top_large_bot_wide">讙讚讜诇 诇诪注诇讛, 专讞讘 诇诪讟讛</string>
+    <string name="widget_name_colored_tile">%1$s (讗专讬讞 爪讘注讜谞讬)</string>
+    <string name="widget_name_untitled">讜讬讚讙壮讟 诇诇讗 砖诐 (%1$s)</string>
+    <string name="pref_force_connection_type_ble">讘诇讜讟讜转壮 LE (讗谞专讙讬讛 谞诪讜讻讛)</string>
+    <string name="devicetype_honor_magicwatch2">Honor MagicWatch 2鈥�</string>
+    <string name="devicetype_amazfit_active">Amazfit Active鈥�</string>
+    <string name="devicetype_sony_wi_sp600n">Sony WI-SP600N鈥�</string>
+    <string name="devicetype_honor_band6">Honor Band 6鈥�</string>
+    <string name="devicetype_honor_band7">Honor Band 7鈥�</string>
+    <string name="protocol_version">讙专住转 驻专讜讟讜拽讜诇</string>
+    <string name="stepRateSum">住讬讻讜诐 拽爪讘 讛爪注讚讬诐</string>
+    <string name="stepRateAvg">拽爪讘 爪注讚讬诐 诪诪讜爪注</string>
+    <string name="stepLengthAvg">讗讜专讱 爪注讚 诪诪讜爪注</string>
+    <string name="groundContactTimeAvg">讝诪谉 诪讙注 诪诪讜爪注 注诐 讛拽专拽注</string>
+    <string name="impactAvg">讛诇讬诪讛 诪诪讜爪注转</string>
+    <string name="impactMax">讛诇讬诪讛 诪专讘讬转</string>
+    <string name="swingAngleAvg">讝讜讜讬转 讛谞驻讛 诪诪讜爪注转</string>
+    <string name="unknownDataEncountered">讛转讙诇讜 谞转讜谞讬诐 诇讗 诪讜讻专讬诐</string>
+    <string name="milliseconds">诪讬诇讬砖谞讬讜转</string>
+    <string name="bottom_nav_dashboard">诇讜讞 讘拽专讛</string>
+    <string name="bottom_nav_devices">讛转拽谞讬诐</string>
+    <string name="dashboard_settings">讛讙讚专讜转 诇讜讞 讘拽专讛</string>
+    <string name="pref_dashboard_first_title">诇讛爪讬讙 讗转 诇讜讞 讛讘拽专讛 转讞讬诇讛</string>
+    <string name="pref_dashboard_first_summary">诇讛爪讬讙 讗转 诇讜讞 讛讘拽专讛 注诐 讛驻注诇转 Gadgetbridge 讘诪拽讜诐 讗转 诪住讱 讛讛转拽谞讬诐</string>
+    <string name="pref_dashboard_widget_today_24h_title">诪爪讘 24 砖注讜转</string>
+    <string name="pref_dashboard_cards_title">讛爪讙转 讜讬讚讙壮讟讬诐 注诇 讛讻专讟讬住讬诐</string>
+    <string name="pref_dashboard_widget_today_title">转专砖讬诐 驻注讬诇讜转</string>
+    <string name="pref_dashboard_widget_settings">讛讙讚专讜转 讜讬讚讙壮讟</string>
+    <string name="pref_dashboard_cards_summary">爪讬讜专 讻专讟讬住讬诐 住讘讬讘 讛讜讜讬讚讙壮讟讬诐 讘诇讜讞 讛讘拽专讛</string>
+    <string name="pref_dashboard_widget_show_legend_title">讛爪讙转 诪拽专讗</string>
+    <string name="pref_dashboard_widget_double_size_title">讙讜讚诇 讻驻讜诇</string>
+    <string name="pref_dashboard_devices_to_include">讛转拽谞讬诐 砖讬砖 诇讻诇讜诇</string>
+    <string name="pref_dashboard_widget_goals_chart_title">转专砖讬诐 讬注讚讬诐</string>
+    <string name="pref_dashboard_all_devices_summary">砖讬诇讜讘 谞转讜谞讬 讛驻注讬诇讜转 诪讻诇 讛讛转拽谞讬诐 砖谞讜住驻讜 诇住讬讻讜诪讬诐 砖讘诇讜讞 讛讘拽专讛</string>
+    <string name="pref_dashboard_widget_show_legend_summary">讛爪讙转 诪拽专讗 诪转讞转 诇讜讜讬讚讙壮讟 砖诪住讘讬专 讗转 讛爪讘注讬诐</string>
+    <string name="devicesetting_scannable_minimum_unseen">讝诪谉 谞转拽 诪讝注专讬 (砖谞讬讜转)</string>
+    <string name="pref_dashboard_widget_today_24h_summary">诇讛爪讬讙 讗转 讛驻注讬诇讜转 讘注讬讙讜诇 讗讞讬讚 砖诇 24 砖注讜转 讘诪拽讜诐 砖谞讬 注讬讙讜诇讬诐 砖诇 12 砖注讜转</string>
+    <string name="activity_type_worn">谞注谞讚</string>
+    <string name="pref_dashboard_select_devices_title">讘讞讬专转 讛转拽谞讬诐鈥�</string>
+    <string name="pref_dashboard_all_devices_title">讻诇 讛讛转拽谞讬诐</string>
+    <string name="pref_dashboard_widget_double_size_summary">诇讗驻砖专 诇讜讜讬讚讙壮讟 诇转驻讜住 砖转讬 注诪讜讚讜转 讘诇讜讞 讛讘拽专讛</string>
+    <string name="error_showing_changelog">砖讙讬讗讛 讘讛爪讙转 讬讜诪谉 讛砖讬谞讜讬讬诐</string>
+    <string name="devicesetting_scannable_rssi_summary">住祝 讛拽诇讬讟讛 讛诪讝注专讬 诇讝讬讛讜讬</string>
+    <string name="devicesetting_scannable_rssi">住祝 拽诇讬讟讛 诪讝注专讬</string>
+    <string name="devicesetting_scannable_minimum_unseen_summary">诇讗讞专 讛住专讬拽讛, 讛讛转拽谉 爪专讬讱 诇讛讬讜转 讘谞转拽 诇诪砖讱 驻专拽 讛讝诪谉 砖讛讜讙讚专 诇驻谞讬 砖讬讬专砖诐 砖讜讘</string>
+    <string name="pref_dashboard_widgets_order_summary">谞讗 诇讘讞讜专 讗讬诇讜 讜讬讚讙壮讟讬诐 讬讜驻注诇讜 讜讘讗讬讝讛 住讚专 讬讜爪讙讜 讘诇讜讞 讛讘拽专讛</string>
+    <string name="pref_header_calls_and_notifications">砖讬讞讜转 讜讛转专讗讜转</string>
+    <string name="pref_speak_notifications_aloud_title">讛拽专讗转 讛转专讗讜转 讘拽讜诇 专诐</string>
+    <string name="pref_speak_notifications_aloud_summary">讛转专讗讜转 转讬拽专讗谞讛 讘拽讜诇 专诐 讚专讱 讛讗讜讝谞讬讜转</string>
+    <string name="armenian">讗专诪谞讬转</string>
+    <string name="pref_sleepasandroid_device_title">讛转拽谉 住驻拽</string>
+    <string name="pref_sleepasandroid_features_summary">讛转诪讬讻讛 诪砖转谞讛 诪讛转拽谉 诇讛转拽谉</string>
+    <string name="pref_sleepasandroid_feat_spo2">SpO2</string>
+    <string name="pref_sleepasandroid_feat_heartrate">讚讜驻拽</string>
+    <string name="pref_sleepasandroid_feat_movement">诪讚 转讗讜爪讛</string>
+    <string name="pref_sleepasandroid_feat_oximetry">诪讚讬讚转 讞诪爪谉</string>
+    <string name="pref_sleepasandroid_feat_notifications">讛讜讚注讜转</string>
+    <string name="pref_sleepasandroid_features_title">讬讻讜诇讜转</string>
+    <string name="pref_dashboard_widget_today_upside_down_title">讞爪讜转 诇诪讟讛</string>
+    <string name="pref_dashboard_widget_today_upside_down_summary">讘诪爪讘 24 砖注讜转, 诇爪讬讬专 讗转 讞爪讜转 诇诪讟讛, 诪专讻讝 讛讬讜诐 讘专讗砖 讛转专砖讬诐</string>
+    <string name="pref_adaptive_volume_control_title">讘拽专转 注讜爪诪转 砖诪注 诪住转讙诇转</string>
+    <string name="watchface_resolution_doesnt_match">专讝讜诇讜爪讬讬转 驻谞讬 讛砖注讜谉 诇讗 转讜讗诪转 诇诪住讱 讛讛转拽谉. 驻谞讬 讛砖注讜谉 讛诐 %1$s 诪住讱 讛讛转拽谉 讛讜讗 %2$s</string>
+    <string name="pref_title_bottom_navigation_bar">住专讙诇 谞讬讜讜讟 转讞转讜谉</string>
+    <string name="huawei_trusleep_warning">讗讝讛专讛: 讛驻注诇转 讛讛讙讚专讛 讛讝讗转 转讙专讜诐 诇讻诇 讛砖讬谞讛 诇讛讜驻讬注 讻拽诇讬诇讛 讘志Gadgetbridge! 讬砖 诇诇讞讜抓 讻讗谉 讗诐 讝讛 诪拽讜讘诇 注诇讬讱.</string>
+    <string name="pref_sleepasandroid_device_summary">谞讗 诇讘讞讜专 讛转拽谉 讻住驻拽 谞转讜谞讬诐 诇志Sleep As Android</string>
+    <string name="pref_sleepasandroid_enable_summary">讛驻注诇转 砖讬诇讜讘 诪讜诇 Sleep As Android</string>
+    <string name="prefs_active_noise_cancelling_light">讘讬讟讜诇 专注砖讬诐 驻注讬诇 拽诇讬诇</string>
+    <string name="cyclingPowerMax">注讜爪诪转 讚讬讜讜砖 诪专讘讬转</string>
+    <string name="cyclingPowerMin">注讜爪诪转 讚讬讜讜砖 诪讝注专讬转</string>
+    <string name="cyclingPowerAverage">注讜爪诪转 讚讬讜讜砖 诪诪讜爪注转</string>
+    <string name="pref_sleepasandroid_slot_title">讞诇讜谉 讛转专讗讛</string>
+    <string name="pref_sleepasandroid_feat_alarms">讛转专讗讜转</string>
+    <string name="pref_adaptive_volume_control_summary">诇讛讙讘讬专 讗转 注讜爪诪转 讛砖诪注 讗讜讟讜诪讟讬转 讻砖讛专注砖 讛讛讬拽驻讬 讞讝拽</string>
+    <string name="devicesetting_scannable_debounce_summary">诇讗讞专 讛住专讬拽讛 讛讛转拽谉 讬讬谞注抓 讻谞住专拽 讜谞驻住讞 注诇讬讜 诇讻诪讜转 讛讝诪谉 砖爪讜讬谞讛</string>
+    <string name="serbian">住专讘讬转</string>
+    <string name="sleepasandroid_settings">鈥嶴leep As Android</string>
+    <string name="pref_sleepasandroid_slot_summary">讘讗讬讝讛 讞诇讜谉 讛转专讗讛 诇讛砖转诪砖 讘注转 讛讙讚专转 讛转专讗讜转</string>
+    <string name="pref_summary_huawei_account">谞注砖讛 砖讬诪讜砖 讘讞砖讘讜谉 Huawei 讘转讛诇讬讱 讛爪讬诪讜讚. 讛讙讚专讜转 诪讗驻砖专转 诇爪诪讚 讗讜转讜 诇诇讗 讗讬驻讜住 诇讛讙讚专讜转 讬爪专谉.</string>
+    <string name="pref_title_huawei_account">讞砖讘讜谉 Huawei</string>
+    <string name="alarm_slot_reset">讞诇讜谉 讛讛转专讗讛 讛讜讙讚专 诇讘专讬专转 讛诪讞讚诇</string>
+    <string name="devicetype_scannable">讛转拽谉 住专讬拽</string>
+    <string name="pref_summary_bottom_navigation_bar_off">诪注讘专 讘讬谉 讛诪住讻讬诐 讛专讗砖讬讬诐 专拽 注诐 讛讞诇拽讛 讗讜驻拽讬转</string>
+    <string name="pref_summary_bottom_navigation_bar_on">讛讞诇驻讛 讘讬谉 诪住讻讬诐 专讗砖讬讬诐 讘讗诪爪注讜转 住专讙诇 谞讬讜讜讟 讗讜 讛讞诇拽讛 讗讜驻拽讬转</string>
+    <string name="open_camera">驻转讬讞转 诪爪诇诪讛</string>
+    <string name="toast_camera_permission_required">谞讞讜爪讛 讛专砖讗讛 诇诪爪诇诪讛 诇驻注讜诇讛 讛讝讗转.</string>
+    <string name="toast_setting_requires_reconnect">讛讛讙讚专讛 讛讝讗转 转讞讜诇 诇讗讞专 讞讬讘讜专 诪讞讚砖</string>
+    <string name="chart_cycling_point_label_distance">讛讬讜诐: %.1f 拽状诪
+\n住讱 讛讻讜诇: %.1f 拽状诪</string>
+    <string name="chart_cycling_point_label_speed">%.1f 拽诪状砖</string>
+    <string name="proprietary_app_warning">讛讬讻讜诇转 讛讝讗转 讚讜专砖转 讛转拽谞转 讬讬砖讜诪讜谉 拽谞讬讬谞讬</string>
+    <string name="notif_battery_full_title">住讜诇诇转 讛讛转拽谉 诪诇讗讛!</string>
+    <string name="pref_touch_tone">爪诇讬诇 诪讙注</string>
+    <string name="pref_wearing_tone">爪诇讬诇 注谞讬讚讛</string>
+    <string name="pref_agps_status">诪爪讘 APGS</string>
+    <string name="agps_status_missing">讞住专</string>
+    <string name="agps_status_pending">诪诪转讬谉</string>
+    <string name="agps_status_current">谞讜讻讞讬</string>
+    <string name="agps_status_error">砖讙讬讗讛</string>
+    <string name="pref_title_notification_times_enabled">讝诪谞讬 讛转专讗讜转</string>
+    <string name="pref_summary_notification_times_enabled">诇砖诇讜讞 讛转专讗讜转 专拽 讘讟讜讜讞讬 讝诪谉 诪住讜讬诪讬诐</string>
+    <string name="battery_full_threshold">住祝 住讜诇诇讛 诪诇讗讛</string>
+    <string name="pref_fetch_unknown_files_title">诪砖讬讻转 拽讘爪讬诐 诇讗 诪讜讻专讬诐</string>
+    <string name="copied_to_clipboard">讛讜注转拽 诇诇讜讞 讛讙讝讬专讬诐</string>
+    <string name="no_folder_selected">诇讗 谞讘讞专讛 转讬拽讬讬讛</string>
+    <string name="battery_full_notify_enabled">讛转专讗讛 注诇 住讜诇诇讛 诪诇讗讛</string>
+    <string name="url">讻转讜讘转</string>
+    <string name="folder_is_empty">讛转讬拽讬讬讛 专讬拽讛</string>
+    <string name="garmin_agps_local_file">拽讜讘抓 诪拽讜诪讬</string>
+    <string name="realtime_settings">讛讙讚专讜转 讝诪谉 讗诪转</string>
+    <string name="battery_percentage_str">%1$s%%</string>
+    <string name="default_percentage">讘专讬专转 诪讞讚诇 (%1$d%%)</string>
+    <string name="unsupported">诇讗 谞转诪讱</string>
+    <string name="loading">讘讟注讬谞讛鈥�</string>
+    <string name="folder">转讬拽讬讬讛</string>
+    <string name="battery_low_threshold">住祝 住讜诇诇讛 讞诇砖讛</string>
+    <string name="battery_low_notify_enabled">讛转专讗讛 注诇 住讜诇诇讛 讞诇砖讛</string>
+    <string name="none">诇诇讗</string>
+    <string name="pref_battery_polling_interval_format">讻诇 %1$s 讚拽讜转</string>
+    <string name="minutes_20">20 讚拽讜转</string>
+    <string name="minutes_60">60 讚拽讜转</string>
+    <string name="sleep_colored_stats_rem">转谞讜注转 注讬谞讬讬诐 诪讛讬专讛</string>
+    <string name="workout_set_repetitions">%1d 脳</string>
+    <string name="workout_set_repetitions_weight_kg">%1d 脳 %2$.2f 拽状讙</string>
+    <string name="pref_touch_tone_summary">谞讬讙讜谉 爪诇讬诇 讘注转 谞讙讬注讛 讘讗讜讝谞讬讜转 转讜讱 讛讗讜讝谉</string>
+    <string name="minutes_2">2 讚拽讜转</string>
+    <string name="pref_wearing_tone_summary">谞讬讙讜谉 爪诇讬诇 注诐 讛讻谞住转 讛讗讜讝谞讬讬讛 诇讗讜讝谉</string>
+    <string name="activity_type_training">讗讬诪讜谉</string>
+    <string name="devicetype_garmin_venu_3">Garmin Venu 3鈥�</string>
+    <string name="devicetype_garmin_venu">Garmin Venu鈥�</string>
+    <string name="devicetype_garmin_venu_2s">Garmin Venu 2S鈥�</string>
+    <string name="hrv">砖讜谞讜转 拽爪讘 诇讘</string>
+    <string name="pref_time_sync">住谞讻专讜谉 讝诪谉 讗讜讟讜诪讟讬</string>
+    <string name="busy_task_fetch_sleep_data">谞转讜谞讬 讛砖讬谞讛 谞诪砖讻讬诐</string>
+    <string name="minutes_3">3 讚拽讜转</string>
+    <string name="minutes_4">4 讚拽讜转</string>
+    <string name="minutes_6">6 讚拽讜转</string>
+    <string name="minutes_7">7 讚拽讜转</string>
+    <string name="minutes_8">8 讚拽讜转</string>
+    <string name="minutes_9">9 讚拽讜转</string>
+    <string name="minutes_15">15 讚拽讜转</string>
+    <string name="minutes_45">45 讚拽讜转</string>
+    <string name="minutes_75">75 讚拽讜转</string>
+    <string name="minutes_90">90 讚拽讜转</string>
+    <string name="minutes_120">120 讚拽讜转</string>
+    <string name="minutes_150">150 讚拽讜转</string>
+    <string name="minutes_180">180 讚拽讜转</string>
+    <string name="minutes_210">210 讚拽讜转</string>
+    <string name="minutes_240">240 讚拽讜转</string>
+    <string name="stress_average">诪诪讜爪注</string>
+    <string name="sleep_colored_stats_deep">注诪讜拽讛</string>
+    <string name="abstract_chart_fragment_kind_awake_sleep">注专谞讜转</string>
+    <string name="sleep_colored_stats_light">拽诇讛</string>
+    <string name="pref_header_sound">爪诇讬诇</string>
+    <string name="workoutSets">住讟讬诐</string>
+    <string name="workout_set_i">住讟 %1d</string>
+    <string name="choose_device">谞讗 诇讘讞讜专 讛转拽谉</string>
+    <string name="no_supported_devices_found">诇讗 谞诪爪讗讜 讛转拽谞讬诐 谞转诪讻讬诐</string>
+    <string name="hrv_status">诪爪讘 砖讜谞讜转 拽爪讘 诇讘</string>
+    <string name="devicetype_garmin_venu_2">Garmin Venu 2鈥�</string>
+    <string name="search">讞讬驻讜砖</string>
+    <string name="devicetype_garmin_venu_2_plus">Garmin Venu 2 Plus鈥�</string>
+    <string name="pref_title_nagivation_apps">讬讬砖讜诪讜谞讬 谞讬讜讜讟</string>
+    <string name="pref_header_external_integrations">砖讬诇讜讘讬诐 讞讬爪讜谞讬讬诐</string>
+    <string name="pref_header_automations">讗讜讟讜诪爪讬讜转</string>
+    <string name="pref_description_general">讟注讬谞讛, 砖驻讛, 讗讝讜专, 诪讬拽讜诐</string>
+    <string name="pref_description_about_you">转讗专讬讱 诇讬讚讛, 诪讙讚专, 讙讜讘讛, 诪砖拽诇, 讬注讚讬诐</string>
+    <string name="pref_description_notifications">讛转专讗讜转 讬讬砖讜诪讜谞讬诐, 专砖讬诪讜转 讛讬转专/讞住讬诪讛</string>
+    <string name="pref_description_user_interface">注专讻转 注讬爪讜讘, 诪住讱 专讗砖讬</string>
+    <string name="pref_description_dashboard">讜讬讚讙壮讟讬诐, 讛转拽谞讬诐 诇讻诇讜诇</string>
+    <string name="pref_description_automations">讬讬爪讜讗 讗讜讟讜诪讟讬, 诪砖讬讻讛 讗讜讟讜诪讟讬转</string>
+    <string name="pref_description_developer_options">讬讜诪谞讬诐, API 砖诇 Intent</string>
+    <string name="pref_description_deprecated_functionalities">讛讙讚专讜转 砖讬讜住专讜 讘讙专住讛 注转讬讚讬诐</string>
+    <string name="pref_header_main_screen">诪住讱 专讗砖讬</string>
+    <string name="pref_title_user_interface">诪谞砖拽 诪砖转诪砖</string>
+    <string name="user_feedback_set_settings_ok">讛讛讙讚专讜转 谞砖诇讞讜 诇讛转拽谉.</string>
+    <string name="minutes_255">255 讚拽讜转</string>
+    <string name="pref_header_deprecated_functionalities">讬讻讜诇讜转 注讘专</string>
+    <string name="pref_header_deprecated_functionalities_warning">讛讬讻讜诇讜转 讛讘讗讜转 讬爪讗讜 诪讻诇诇 砖讬诪讜砖 讜讛谉 转讜住专谞讛 讘拽专讜讘 诪讛转讜讻谞讛.
+\n讗诐 爪专讬讱 诇讛驻注讬诇 讗转 讗讞转 诪讛讛讙讚专讜转 讛讘讗讜转 讻讚讗讬 诇讬爪讜专 拽砖专 注诐 爪讜讜转 讛诪讬讝诐.</string>
+    <string name="pref_summary_sync_birthdays">诇住谞讻专谉 讗转 讬诪讬 讛讛讜诇讚转 砖诇 讗谞砖讬 讛拽砖专 诇爪讚 讗讬专讜注讬 诇讜讞 砖谞讛</string>
+    <string name="pref_title_sync_birthdays">住谞讻专讜谉 讬诪讬 讛讜诇讚转</string>
+    <string name="busy_task_fetch_hrv_data">谞转讜谞讬 砖讜谞讜转 拽爪讘 诇讘 诪注讜讚讻谞讬诐</string>
+    <string name="milliseconds_ms">诪状砖</string>
+    <string name="prefs_hrv_monitoring_description">诇诪讚讜讚 砖讜谞讜转 拽爪讘 诇讘 讘诪讛诇讱 讛讬讜诐</string>
+    <string name="pref_summary_calendar_lookahead">诇住谞讻专谉 注讚 %1s 讬诪讬诐 砖诇 讗讬专讜注讬 诇讜讞 砖谞讛</string>
+    <string name="pref_title_calendar_lookahead">诪住驻专 讛讬诪讬诐 诪专讗砖</string>
+    <string name="notif_battery_full">讛住讜诇诇讛 砖诇 %1$s 诪诇讗讛</string>
+    <string name="gps_glonass">GPS + GLONASS</string>
+    <string name="hr_resting">诪谞讜讞讛</string>
+    <string name="hr_maximum">诪专讘讬</string>
+    <string name="hr_minimum">诪讝注专讬</string>
+    <string name="hr_average">诪诪讜爪注</string>
+    <string name="transition">诪注讘专讜谉</string>
+    <string name="activity_type_navigate">谞讬讜讜讟</string>
+    <string name="activity_prefs_height_inches">讙讜讘讛 讘讗讬谞爪壮讬诐</string>
+    <string name="authentication_failed_check_key">讛讗讬诪讜转 谞讻砖诇, 谞讗 诇讘讚讜拽 讗转 诪驻转讞 讛讗讬诪讜转</string>
+    <string name="authentication_failed_negotiation">讛诪砖讗 讜诪转谉 注诇 诪驻转讞 讛讗讬诪讜转 谞讻砖诇</string>
+    <string name="dateformat_month_day">讞讜讚砖, 讬讜诐</string>
+    <string name="music_rename_playlist">砖讬谞讜讬 砖诐 专砖讬诪转 谞讙讬谞讛</string>
+    <string name="music_error">讗讬专注讛 砖讙讬讗讛</string>
+    <string name="music_huawei_device_info">转住讚讬专讬诐 谞转诪讻讬诐: %1$s\n砖讟讞 讗讞住讜谉 讘砖注讜谉: %2$d 诪状讘</string>
+    <string name="dateformat_day_month">讬讜诐, 讞讜讚砖</string>
+    <string name="music_delete_from_playlist">诪讞讬拽转 诪专砖讬诪转 讛谞讙讬谞讛</string>
+    <string name="music_all_songs">讻诇 讛砖讬专讬诐</string>
+    <string name="pref_music_management_summary">谞讬讛讜诇 诪讜讝讬拽讛 讚专讱 讛砖注讜谉</string>
+    <string name="music_delete_confirm_description">诇诪讞讜拽 讗转 鈥�%1$s鈥�?</string>
+    <string name="music_add_to_playlist">讛讜住驻讛 诇专砖讬诪转 谞讙讬谞讛</string>
+    <string name="music_new_playlist">专砖讬诪转 谞讙讬谞讛 讞讚砖讛</string>
+    <string name="music_delete">诪讞讬拽转 砖讬专</string>
+    <string name="title_activity_musicmanager">诪谞讛诇 诪讜讝讬拽讛</string>
+    <string name="activity_prefs_date_birth">转讗专讬讱 讛诇讬讚讛</string>
+    <string name="permission_read_call_log_summary">讛注讘专转 讬讜诪谉 砖讬讞讜转 诇讙讗讚讙壮讟讬诐</string>
+    <string name="permission_read_call_log_title">拽专讬讗转 讬讜诪谉 砖讬讞讜转</string>
+    <string name="permission_contacts_title">讗谞砖讬 拽砖专</string>
+    <string name="permission_process_outgoing_calls_summary">拽专讬讗转 讛诪住驻专 砖诇 砖讬讞讜转 讬讜爪讗讜转 讻讚讬 诇讛爪讬讙 讗讜转讜 讘讙讗讚讙壮讟</string>
+    <string name="permission_answer_phone_calls_summary">诪注谞讛 诇砖讬讞讜转 讟诇驻讜谉 诪讙讗讚讙壮讟讬诐</string>
+    <string name="permission_answer_phone_calls_title">诪注谞讛 诇砖讬讞讜转 讟诇驻讜谉</string>
+    <string name="permission_query_all_packages_summary">讛拽专讗转 砖诪讜转 讜住诪诇讬诐 砖诇 讻诇 讛讬讬砖讜诪讬诐 讛诪讜转拽谞讬诐</string>
+    <string name="permission_query_all_packages_title">转砖讗讜诇 讻诇 讛讞讘讬诇讜转</string>
+    <string name="permission_read_phone_state_title">拽专讬讗转 诪爪讘 讟诇驻讜谉</string>
+    <string name="permission_send_sms_title">砖诇讬讞转 诪住专讜谞讬诐</string>
+    <string name="permission_process_outgoing_calls_title">注讬讘讜讚 砖讬讞讜转 讬讜爪讗讜转</string>
+    <string name="permission_call_phone_summary">讬讝讬诪转 砖讬讞讜转 诪讛讙讗讚讙壮讟讬诐</string>
+    <string name="devicetype_redmi_buds_5_pro">鈥嶳edmi Buds 5 Pro</string>
+    <string name="permission_external_storage_title">讗讞住讜谉 讞讬爪讜谞讬</string>
+    <string name="permission_call_phone_title">砖讬讞转 讟诇驻讜谉</string>
+    <string name="permission_read_phone_state_summary">拽专讬讗转 诪爪讘 砖诇 砖讬讞讜转 讘讝诪谉 砖诪转讘爪注讜转</string>
+    <string name="permission_internet_access_summary">住谞讻专讜谉 注诐 诪砖讗讘讬诐 诪拽讜讜谞讬诐</string>
+    <string name="permission_internet_access_title">讙讬砖讛 诇讗讬谞讟专谞讟</string>
+    <string name="permission_manage_dnd_title">谞讬讛讜诇 鈥炞溩� 诇讛驻专讬注鈥�</string>
+    <string name="totalJumps">住讱 讻诇 讛拽驻讬爪讜转</string>
+    <string name="avgJumpRate">拽爪讘 拽驻讬爪讜转 诪诪讜爪注</string>
+    <string name="maxJumpRate">拽爪讘 拽驻讬爪讜转 诪专讘讬</string>
+    <string name="mijia_lywsd_comfort_level_title">专诪转 谞讜讞讜转</string>
+    <string name="mijia_lywsd_comfort_level_summary">讛讙讚专转 诪讙讘诇讜转 讛讟诪驻专讟讜专讛 讜讛诇讞讜转 诇讗诪讜讙壮讬 讛诪讜爪讙</string>
+    <string name="miscale_weight_unit_chinese">住讬谞讬 (讙壮讬谉)</string>
+    <string name="active_calories">拽诇讜专讬讜转 驻注讬诇讜转</string>
+    <string name="respiratoryrate">拽爪讘 谞砖讬诪讛</string>
+    <string name="active_calories_short">驻注讬诇</string>
+    <string name="stats_highest_hr">讛讚讜驻拽 讛讙讘讜讛 讘讬讜转专</string>
+    <string name="activity_prefs_goal_active_calories_burnt">讬注讚 讬讜诪讬: 拽诇讜专讬讜转 驻注讬诇讜转 砖谞砖专驻讜</string>
+    <string name="devicetype_idasen">IKEA Idasen Desk鈥�</string>
+    <string name="soundcore_equalizer_value">注专讱</string>
+    <string name="devicetype_micompositionscale">Mi Body Composition Scale 2鈥�</string>
+    <string name="devicetype_mismartscale">Mi Smart Scale 2鈥�</string>
+    <string name="devicetype_casioecbs100">Casio ECB-S100鈥�</string>
+    <string name="music_upload_info">讛驻注讜诇讛 讛讝讗转 转注诇讛 讗转 拽讜讘抓 讛诪讜讝讬拽讛 讛讘讗:\n\n%1$s\n砖诐 讛砖讬专: %2$s\n讗诇讘讜诐: %3$s\n</string>
+    <string name="mijia_lywsd_comfort_humidity_title">诇讞讜转 (%)</string>
+    <string name="mijia_lywsd_comfort_humidity_summary">讛讟讜讜讞 讛诪讜诪诇抓: 20 - 85</string>
+    <string name="miscale_small_objects_title">注爪诪讬诐 拽讟谞讬诐</string>
+    <string name="mijia_lywsd_comfort_upper">讛住祝 讛注诇讬讜谉</string>
+    <string name="miscale_weight_unit_metric">诪讟专讬 (拽状讙)</string>
+    <string name="sleep_score_value">爪讬讜谉: %1d</string>
+    <string name="sleep_score">爪讬讜谉 砖讬谞讛</string>
+    <string name="stats_lowest_hr">讛讚讜驻拽 讛谞诪讜讱 讘讬讜转专</string>
+    <string name="miscale_weight_unit_title">讬讞讬讚转 诪砖拽诇</string>
+    <string name="workout_set_reps">讞讝专讜转</string>
+    <string name="soundcore_equalizer_frequency">转讚讬专讜转</string>
+    <string name="mijia_lywsd_comfort_temperature_title">讟诪驻专讟讜专讛 (鈥幝癈)</string>
+    <string name="mijia_lywsd_comfort_lower">讛住祝 讛转讞转讜谉</string>
+    <string name="mijia_lywsd_comfort_temperature_summary">讛讟讜讜讞 讛诪讜诪诇抓: 19 - 27</string>
+    <string name="miscale_weight_unit_summary">讛讙讚专转 讬讞讬讚转 诪砖拽诇 诇诪讚讬讚讜转 砖诪讜爪讙讜转</string>
+    <string name="miscale_weight_unit_imperial">讗讬诪驻专讬讗诇讬 (诇讬讘专讜转)</string>
+    <string name="miscale_small_objects_summary">讗讞住讜谉 诪砖拽诇讬诐 砖诇 驻专讬讟讬诐 诪转讞转 诇志10 拽状讙</string>
+</resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml
new file mode 100644
index 0000000..14e4434
--- /dev/null
+++ b/app/src/main/res/values-hi/strings.xml
@@ -0,0 +1,162 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <string name="vibration_profile_alarm_clock">啶呧げ啶距ぐ啷嵿ぎ 啶樴ぁ啶监</string>
+    <string name="miband_pairing_tap_hint">啶溹が 啶嗋お啶曕ぞ 啶忇ぎ 啶嗋 (Mi) 啶啶傕ぁ 啶掂ぞ啶囙が啷嵿ぐ啷囙 啶斷ぐ 啶啶侧た啶傕 啶曕ぐ啶むぞ 啶灌, 啶む 啶囙じ啷� 啶曕 啶ぞ啶� 啶忇 啶啶曕啶むた 啶啶� 啶熰啶� 啶曕ぐ啷囙啷�</string>
+    <string name="appinstaller_install">啶囙啶膏啶熰啶�</string>
+    <string name="miband_pairing_using_dummy_userdata">啶ぞ啶ㄠ啶� 啶夃お啶啶椸啶班啶むぞ 啶∴啶熰ぞ 啶ㄠす啷�啶� 啶︵た啶ぞ 啶椸く啶�,啶呧き啷� 啶曕 啶侧た啶� 啶∴ぎ啷� 啶夃お啶啶椸啶班啶むぞ 啶∴啶熰ぞ 啶曕ぞ 啶夃お啶啶� 啶曕た啶ぞ 啶溹ぞ 啶班す啶� 啶灌啷�</string>
+    <string name="vertical">啶栢ぁ啶监ぞ</string>
+    <string name="horizontal">啶曕啶粪啶むた啶�</string>
+    <string name="right">啶︵ぞ啶傕</string>
+    <string name="left">啶ぞ啶忇</string>
+    <string name="other">啶呧え啷嵿く</string>
+    <string name="female">啶す啶苦げ啶�</string>
+    <string name="male">啶ㄠぐ</string>
+    <string name="preferences_miband_settings">啶 啶啶傕ぁ / 啶呧ぎ啷囙啶监か啶苦 啶膏啶熰た啶傕啷嵿じ</string>
+    <string name="preferences_category_device_specific_settings">啶∴た啶掂ぞ啶囙じ 啶掂た啶多た啶粪啶� 啶膏啶熰た啶傕啷嵿じ</string>
+    <string name="discovery_note">啶о啶ぞ啶� 啶︵啶�:</string>
+    <string name="pref_screen_vibration_profile">啶曕啶え 啶啶班啶ぜ啶距啶�</string>
+    <string name="prefs_wearside">啶ぞ啶忇 啶ぞ 啶︵ぞ啶忇 啶す啶ㄠ 啶灌啶廫?</string>
+    <string name="sleep_activity_date_range">%1$s 啶膏 %2$s 啶む</string>
+    <string name="busy_task_fetch_activity_data">啶椸い啶苦さ啶苦ぇ啶� 啶∴啶熰ぞ 啶啶班ぞ啶啶� 啶曕た啶ぞ 啶溹ぞ 啶班す啶�</string>
+    <string name="initializing">啶多啶班 啶灌 啶班す啶� 啶灌</string>
+    <string name="pref_write_logfiles">啶侧啶� 啶ぜ啶距啶侧啶� 啶侧た啶栢啶�</string>
+    <string name="title_activity_sleepmonitor">啶ㄠ啶傕う 啶曕 啶ㄠた啶椸ぐ啶距え啷�</string>
+    <string name="watch9_pairing_tap_hint">啶溹が 啶嗋お啶曕 啶樴ぁ啶监 啶曕啶え 啶曕ぐ啷�, 啶む 啶∴た啶掂ぞ啶囙じ 啶曕 啶灌た啶侧ぞ啶忇 啶ぞ 啶夃じ啶曕ぞ 啶啶� 啶︵が啶距啶傕イ</string>
+    <string name="pref_header_vibration_count">啶曕啶え 啶曕 啶膏啶栢啶ぞ</string>
+    <string name="miband_prefs_alias">啶ㄠぞ啶� / 啶夃お啶ㄠぞ啶�</string>
+    <string name="candidate_item_device_image">啶∴た啶掂ぞ啶囙じ 啶囙ぎ啷囙</string>
+    <string name="vibration_profile_waterdrop"/>
+    <string name="vibration_profile_ring">啶班た啶傕</string>
+    <string name="vibration_profile_long">啶侧啶ぞ</string>
+    <string name="vibration_profile_medium">啶ぇ啷嵿く啶�</string>
+    <string name="vibration_profile_short">啶涏啶熰ぞ</string>
+    <string name="pref_screen_notification_profile_generic_chat">啶氞啶�</string>
+    <string name="pref_screen_notification_profile_missed_call">啶た啶膏啶� 啶曕啶� 啶膏啶氞え啶�</string>
+    <string name="pref_screen_notification_profile_incoming_call">啶嗋え啷� 啶掂ぞ啶侧 啶曕啶� 啶呧ぇ啶苦じ啷傕啶ㄠぞ</string>
+    <string name="pref_screen_notification_profile_email">啶� - 啶啶� 啶呧ぇ啶苦じ啷傕啶ㄠぞ</string>
+    <string name="pref_screen_notification_profile_generic">啶膏ぞ啶ぞ啶ㄠ啶� 啶呧ぇ啶苦じ啷傕啶ㄠぞ</string>
+    <string name="pref_header_vibration_settings">啶曕啶え 啶膏啶熰た啶傕啷嵿じ</string>
+    <string name="pref_screen_notification_profile_sms">啶忇じ啶忇ぎ啶忇じ 啶呧ぇ啶苦じ啷傕啶ㄠぞ</string>
+    <string name="vibration_try">啶啶班く啶む啶�</string>
+    <string name="miband_prefs_vibration">啶曕啶え</string>
+    <string name="pref_screen_notification_profile_anti_loss">啶ㄠ啶曕じ啶距え 啶掂た啶班啶о 啶氞啶むぞ啶掂え啷�</string>
+    <string name="pref_screen_notification_profile_low_power">啶曕ぎ 啶た啶溹げ啷� 啶曕 啶氞啶むぞ啶掂え啷�</string>
+    <string name="pref_screen_notification_profile_inactivity">啶ㄠた啶粪啶曕啶班た啶い啶� 啶膏啶氞え啶�</string>
+    <string name="pref_screen_notification_profile_calendar">啶曕啶侧啶傕ぁ啶� 啶膏啶氞え啶�</string>
+    <string name="pref_screen_notification_profile_generic_social">啶膏啶多げ 啶ㄠ啶熰さ啶班啶�</string>
+    <string name="pref_screen_notification_profile_generic_navigation">啶ぅ 啶啶班う啶班啶多え</string>
+    <string name="stats_title">啶椸い啶� 啶曕啶粪啶む啶�</string>
+    <string name="interval_one_hour">啶忇 啶樴啶熰 啶啶� 啶忇 啶ぞ啶�</string>
+    <string name="interval_forty_five_minutes">啶灌ぐ 45 啶た啶ㄠ 啶啶�</string>
+    <string name="interval_thirty_minutes">啶灌ぐ 30 啶た啶ㄠ 啶啶�</string>
+    <string name="interval_fifteen_minutes">啶灌ぐ 15 啶た啶ㄠ 啶啶�</string>
+    <string name="interval_ten_minutes">啶灌ぐ 10 啶た啶ㄠ 啶啶�</string>
+    <string name="interval_five_minutes">啶灌ぐ 5 啶た啶ㄠ 啶啶�</string>
+    <string name="interval_one_minute">啶忇 啶た啶ㄠ 啶啶� 啶忇 啶ぞ啶�</string>
+    <string name="prefs_title_heartrate_measurement_interval">啶啶班 啶︵た啶� 啶忇啶嗋ぐ (HR) 啶ぞ啶�</string>
+    <string name="title_activity_set_alarm">啶呧げ啶距ぐ啷嵿ぎ 啶曕 啶曕啶ㄠ啶ぜ啶苦啶� 啶曕ぐ啷囙</string>
+    <string name="title_activity_charts">啶椸い啶苦さ啶苦ぇ啶� 啶斷ぐ 啶ㄠ啶傕う</string>
+    <string name="control_center_cancel_to_stop_vibration">啶曕啶え 啶曕 啶班啶曕え啷� 啶曕 啶侧た啶� 啶班う啷嵿う 啶曕ぐ啷囙啷�</string>
+    <string name="control_center_find_lost_device">啶栢啶� 啶灌啶� 啶∴た啶掂ぞ啶囙じ 啶曕ぞ 啶い啶� 啶侧啶距啶�</string>
+    <string name="stats_y_axis_label">啶啶班い啶� 啶た啶ㄠ 啶曕う啶�</string>
+    <string name="stats_x_axis_label">啶曕啶� 啶た啶ㄠ</string>
+    <string name="alarm_fri_short">啶多啶曕啶�</string>
+    <string name="alarm_thu_short">啶椸啶班</string>
+    <string name="alarm_wed_short">啶啶�</string>
+    <string name="alarm_tue_short">啶啶椸げ</string>
+    <string name="alarm_mon_short">啶膏啶�</string>
+    <string name="alarm_sun_short">啶班さ啶�</string>
+    <string name="title_activity_alarm_details">啶呧げ啶距ぐ啷嵿ぎ 啶掂た啶掂ぐ啶�</string>
+    <string name="controlcenter_start_configure_alarms">啶呧げ啶距ぐ啷嵿ぎ 啶曕 啶曕啶ㄠ啶ぜ啶苦啶� 啶曕ぐ啷囙</string>
+    <string name="user_feedback_miband_activity_data_transfer">%1$s 啶∴啶熰ぞ 啶膏啶ムぞ啶ㄠぞ啶傕い啶班ぃ 啶曕た啶ぞ 啶溹ぞ啶忇啶� 啶溹た啶膏啷� 啶多啶班啶嗋い %2$s 啶膏 啶灌</string>
+    <string name="chart_no_data_synchronize">啶曕啶� 啶∴啶熰ぞ 啶夃お啶侧が啷嵿ぇ 啶ㄠす啷�啶� 啶灌啷� 啶夃お啶曕ぐ啶� 啶膏た啶傕啷嵿ぐ啶ㄠぞ啶囙啶� 啶曕ぐ啷囙\?</string>
+    <string name="user_feedback_miband_set_alarms_ok">啶呧げ啶距ぐ啷嵿ぎ 啶啶曕啶むた 啶ぐ 啶啶溹 啶椸く啶距イ</string>
+    <string name="user_feedback_miband_set_alarms_failed">啶呧げ啶距ぐ啷嵿ぎ 啶膏啶� 啶曕ぐ啶ㄠ 啶啶� 啶忇 啶む啶班啶熰た 啶灌啶�, 啶曕啶く啶� 啶啶ㄠ 啶啶班く啶距じ 啶曕ぐ啷囙啷�</string>
+    <string name="alarm_snooze">啶ぞ啶� 啶︵た啶侧ぞ啶忇</string>
+    <string name="alarm_smart_wakeup">啶膏啶ぞ啶班啶� 啶掂啶曕啶�</string>
+    <string name="alarm_sat_short">啶多え啶�</string>
+    <string name="installer_activity_wait_while_determining_status">啶曕啶く啶� 啶啶班い啷�啶曕啶粪ぞ 啶曕ぐ啷囙 啶囙え啷嵿じ啷嵿啶侧啶多え 啶曕 啶膏啶ムた啶むた 啶ㄠた啶班啶оぞ啶班た啶� 啶曕 啶溹ぞ 啶班す啷� 啶灌鈥�</string>
+    <string name="pbw_install_handler_hw_revision_mismatch">啶︵た啶� 啶椸 啶ぐ啷嵿ぎ啶掂啶ぐ 啶曕 啶囙啶膏啶熰啶� 啶曕ぐ啶ㄠ 啶啶� 啶呧じ啶ぐ啷嵿ぅ: 啶す 啶嗋お啶曕 啶啶げ (Pebble) 啶曕 啶灌ぞ啶班啶∴さ啷囙く啶� 啶膏啶多啶оえ 啶膏 啶啶� 啶ㄠす啷�啶� 啶栢ぞ啶むぞ 啶灌啷�</string>
+    <string name="pbw_install_handler_unable_to_install">啶︵ 啶椸 啶ぜ啶距啶� 啶曕 啶囙啶膏啶熰啶� 啶曕ぐ啶ㄠ 啶啶� 啶呧じ啶ぐ啷嵿ぅ: %1$s</string>
+    <string name="installer_activity_unable_to_find_handler">啶囙じ 啶ぜ啶距啶� 啶曕 啶囙啶膏啶熰啶� 啶曕ぐ啶ㄠ 啶曕 啶侧た啶� 啶灌啶傕ぁ啶侧ぐ 啶栢啶溹え啷� 啶啶� 啶呧じ啶ぐ啷嵿ぅ啷�</string>
+    <string name="cannot_connect">啶溹啶∴ぜ 啶ㄠす啷�啶� 啶膏啶むぞ: %1$s</string>
+    <string name="controlcenter_start_activitymonitor">啶嗋お啶曕 啶椸い啶苦さ啶苦ぇ啶� (ALPHA)</string>
+    <string name="dbaccess_error_executing">啶ㄠた啶粪啶ぞ啶︵え 啶啶� 啶む啶班啶熰た \'%1$s\'</string>
+    <string name="miband_prefs_fitness_goal">啶︵啶ㄠた啶� 啶曕う啶� 啶曕ぞ 啶侧啷嵿し啷嵿く</string>
+    <string name="notif_export_failed_title">啶∴啶熰ぞ啶啶� 啶ㄠた啶班啶ぞ啶� 啶掂た啶げ! 啶曕啶く啶� 啶呧お啶ㄠ 啶膏啶熰た啶傕啷嵿じ 啶溹ぞ啶傕啷囙啷�</string>
+    <string name="notif_battery_low_extended">%1$s 啶曕ぎ 啶啶熰ぐ啷�: %2$s</string>
+    <string name="notif_battery_low">%1$s 啶曕ぎ 啶啶熰ぐ啷�</string>
+    <string name="notif_battery_low_bigtext_number_of_charges">啶氞ぞ啶班啶溹啶� 啶曕 啶膏啶栢啶ぞ: %s</string>
+    <string name="notif_battery_low_bigtext_last_charge_time">啶呧啶むた啶� 啶氞ぞ啶班啶�: %s
+\n</string>
+    <string name="notif_battery_low_percent">%1$s 啶啶熰ぐ啷� 啶多啶�: %2$s%%</string>
+    <string name="notif_battery_low_title">啶椸啶溹啶� 啶曕 啶啶熰ぐ啷� 啶曕ぎ 啶灌!</string>
+    <string name="updating_firmware">啶ぐ啷嵿ぎ啶掂啶ぐ 啶啶侧啶� 啶曕た啶ぞ 啶溹ぞ 啶班す啶锯��</string>
+    <string name="overslept">啶呧い啶� 啶ㄠ啶傕う: %1$s</string>
+    <string name="lack_of_sleep">啶ㄠ啶傕う 啶曕 啶曕ぎ啷�: %1$s</string>
+    <string name="activity_sleepchart_activity_and_sleep">啶椸い啶苦さ啶苦ぇ啶�</string>
+    <string name="weekstepschart_steps_a_week">啶啶班い啶� 啶膏お啷嵿い啶距す 啶曕う啶�</string>
+    <string name="weeksleepchart_today_sleep_description">啶嗋 啶曕 啶ㄠ啶傕う, 啶侧啷嵿し啷嵿く: %1$s</string>
+    <string name="weeksleepchart_sleep_a_week">啶啶班い啶� 啶膏お啷嵿い啶距す 啶ㄠ啶傕う</string>
+    <string name="sleepchart_your_sleep">啶ㄠ啶傕う</string>
+    <string name="fwappinstaller_connection_state">啶啶曕啶むた 啶曕 啶侧た啶� 啶曕え啷囙啷嵿ざ啶�: %1$s</string>
+    <string name="miband_fwinstaller_untested_version">啶呧え啶涏啶� 啶膏啶膏啶曕ぐ啶�!</string>
+    <string name="miband_fwinstaller_compatible_version">啶膏啶椸い 啶膏啶膏啶曕ぐ啶�</string>
+    <string name="installhandler_firmware_name">%1$s: %2$s %3$s</string>
+    <string name="fwapp_install_device_not_ready">啶ぜ啶距啶� 啶膏啶ムぞ啶た啶� 啶ㄠす啷�啶� 啶曕 啶溹ぞ 啶膏啶む, 啶∴た啶掂ぞ啶囙じ 啶む啶ぞ啶� 啶ㄠす啷�啶� 啶灌啷�</string>
+    <string name="chart_steps">啶曕う啶�</string>
+    <string name="updatefirmwareoperation_write_failed">啶ぜ啶班啶さ啷囙く啶� 啷炧啶侧啶多た啶傕 啶掂た啶げ 啶班す啶�</string>
+    <string name="updatefirmwareoperation_update_complete_rebooting">啶ぜ啶班啶さ啷囙く啶� 啶囙啶膏啶熰啶侧啶多え 啶啶班啶�, 啶啶曕啶むた 啶班た啶啶� 啶灌 啶班す啶锯��</string>
+    <string name="updatefirmwareoperation_update_complete">啶ぐ啷嵿ぎ啶掂啶ぐ 啶膏啶ムぞ啶え啶� 啶啶班啶�</string>
+    <string name="updatefirmwareoperation_metadata_updateproblem">啶ぐ啷嵿ぎ啶掂啶ぐ 啶啶熰ぞ啶∴啶熰ぞ 啶灌じ啷嵿い啶距啶むぐ啶� 啶曕 啶膏ぞ啶� 啶膏ぎ啶膏啶ぞ</string>
+    <string name="seconds_20">20 啶膏啶曕啶�</string>
+    <string name="seconds_10">10 啶膏啶曕啶�</string>
+    <string name="seconds_5">5 啶膏啶曕啶�</string>
+    <string name="no_limit">啶曕啶� 啶膏啶ぞ 啶ㄠす啷�啶�</string>
+    <string name="battery">啶啶熰ぐ啷�</string>
+    <string name="heart_rate">啶灌啶︵く 啶椸い啶�</string>
+    <string name="clock">啶樴ぁ啶监</string>
+    <string name="distance">啶︵啶班</string>
+    <string name="calories">啶曕啶侧啶班</string>
+    <string name="liveactivity_live_activity">啶溹啶掂た啶� 啶椸い啶苦さ啶苦ぇ啶�</string>
+    <string name="minutes_30">30 啶た啶ㄠ</string>
+    <string name="minutes_10">10 啶た啶ㄠ</string>
+    <string name="minutes_5">5 啶た啶ㄠ</string>
+    <string name="minutes_1">1 啶た啶ㄠ</string>
+    <string name="seconds_30">30 啶膏啶曕啶�</string>
+    <string name="pref_summary_low_latency_fw_update">啶す 啶夃え 啶啶曕啶むた啶啶� 啶ぐ 啶曕ぞ啶班啶� 啶灌 啶膏啶むぞ 啶灌, 啶溹す啶距 啶ぐ啷嵿ぎ啶掂啶ぐ 啶啶侧啶多た啶傕 啶掂た啶げ 啶灌 啶溹ぞ啶む 啶灌啷�</string>
+    <string name="pref_title_low_latency_fw_update">啶ぐ啷嵿ぎ啶掂啶ぐ 啶啶侧啶多た啶傕 啶曕 啶侧た啶� 啶曕ぎ-啶掂た啶侧啶い啶� 啶啶� 啶曕ぞ 啶夃お啶啶� 啶曕ぐ啷囙</string>
+    <string name="pref_summary_keep_data_on_device">啶膏た啶傕啷嵿ぐ啶ㄠぞ啶囙啶监啶多え 啶曕 啶ぞ啶� 啶 Mi 啶啶傕ぁ 啶ぐ 啶椸い啶苦さ啶苦ぇ啶� 啶∴啶熰ぞ 啶班啷囙啶椸啷� 啶夃お啶啶椸 啶呧啶� GB 啶呧え啷嵿く 啶忇お啷嵿げ啶苦啷囙ざ啶� 啶曕 啶膏ぞ啶� 啶啶班く啷嬥 啶曕た啶ぞ 啶溹ぞ啶忇イ</string>
+    <string name="pref_summary_dont_ack_transfers">啶う啶� 啶啶傕ぁ 啶ぐ 啶膏啶ㄠた啶多啶氞た啶� 啶� 啶灌, 啶む 啶椸い啶苦さ啶苦ぇ啶� 啶∴啶熰ぞ 啶膏ぞ啶ぜ 啶ㄠす啷�啶� 啶曕た啶ぞ 啶溹ぞ啶むぞ 啶灌啷� 啶夃お啶啶椸 啶呧啶� GB 啶呧え啷嵿く 啶忇お啷嵿げ啶苦啷囙ざ啶� 啶曕 啶膏ぞ啶� 啶啶班く啷嬥 啶曕た啶ぞ 啶溹ぞ啶忇イ</string>
+    <string name="pref_title_dont_ack_transfer">啶椸い啶苦さ啶苦ぇ啶� 啶∴啶熰ぞ 啶膏啶ムぞ啶ㄠぞ啶傕い啶班ぃ 啶膏啶ㄠた啶多啶氞た啶� 啶� 啶曕ぐ啷囙</string>
+    <string name="average">啶斷じ啶�: %1$s</string>
+    <string name="overstep">啶呧ぇ啶苦 啶曕う啶�: %1$d</string>
+    <string name="lack_of_step">啶氞ぐ啶`啶� 啶曕 啶曕ぎ啷�: %1$d</string>
+    <string name="weeksteps_today_steps_description">啶嗋 啶曕 啶曕う啶�, 啶侧啷嵿し啷嵿く: %1$s</string>
+    <string name="live_activity_max_heart_rate">啶掂ぐ啷嵿い啶ぞ啶� / 啶呧ぇ啶苦啶むぎ 啶灌啶︵く 啶椸い啶�: %1$d / %2$d</string>
+    <string name="live_activity_start_your_activity">啶呧お啶ㄠ 啶椸い啶苦さ啶苦ぇ啶� 啶多啶班 啶曕ぐ啷囙</string>
+    <string name="live_activity_steps_per_minute_history">啶啶班い啶� 啶た啶ㄠ 啶曕う啶 啶曕ぞ 啶囙い啶苦す啶距じ</string>
+    <string name="live_activity_total_steps">啶曕啶� 啶曕う啶啶�</string>
+    <string name="live_activity_current_steps_per_minute">啶掂ぐ啷嵿い啶ぞ啶� 啶曕う啶� 啶啶班い啶� 啶た啶ㄠ</string>
+    <string name="live_activity_steps_history">啶曕う啶 啶曕ぞ 啶囙い啶苦す啶距じ</string>
+    <string name="you_did_not_sleep">啶嗋お 啶膏啶 啶ㄠす啷�啶�</string>
+    <string name="you_slept">啶嗋お %1$s 啶膏 %2$s 啶む 啶膏 啶椸</string>
+    <string name="abstract_chart_fragment_kind_not_worn">啶す啶ㄠぞ 啶ㄠす啷�啶�</string>
+    <string name="abstract_chart_fragment_kind_deep_sleep">啶椸す啶班 啶ㄠ啶傕う</string>
+    <string name="abstract_chart_fragment_kind_light_sleep">啶灌げ啶曕 啶ㄠ啶傕う</string>
+    <string name="abstract_chart_fragment_kind_activity">啶椸い啶苦さ啶苦ぇ啶�</string>
+    <string name="prefs_find_phone">啶ぜ啷嬥え 啶⑧啶傕あ啷囙</string>
+    <string name="miband_prefs_reserve_alarm_calendar">啶嗋啶距ぎ啷� 啶嗋く啷嬥啶ㄠぞ啶� 啶灌啶む 啶嗋ぐ啶曕啶粪た啶� 啶呧げ啶距ぐ啷嵿ぎ</string>
+    <string name="fwinstaller_firmware_not_compatible_to_device">啶す 啶ぐ啷嵿ぎ啶掂啶ぐ 啶囙じ 啶啶曕啶むた 啶曕 啶啶椸啶� 啶ㄠす啷� 啶灌</string>
+    <string name="miband_fwinstaller_incompatible_version">啶呧く啷嬥啷嵿く 啶ぐ啷嵿ぎ啶掂啶ぐ</string>
+    <string name="pref_title_keep_data_on_device">啶呧お啶ㄠ 啶椸い啶苦さ啶苦ぇ啶� 啶曕 啶啶曕啶むた 啶啶� 啶班啷囙</string>
+    <string name="user_feedback_all_alarms_disabled">啶膏き啷� 啶呧げ啶距ぐ啷嵿ぎ 啶啶� 啶灌</string>
+    <string name="device_not_connected">啶曕え啷囙啷嵿 啶ㄠす啷�啶�.</string>
+    <string name="sonyswr12_settings_alarm_interval">啶た啶ㄠ啷嬥 啶啶� 啶膏啶ぞ啶班啶� 啶呧げ啶距ぐ啷嵿ぎ 啶呧啶むぐ啶距げ</string>
+    <string name="sonyswr12_settings_low_vibration_summary">啶啶傕ぁ 啶ぐ 啶曕啶え 啶曕 啶曕ぎ 啶む啶掂啶班い啶� 啶曕 啶膏啷嵿し啶� 啶曕ぐ啷囙</string>
+    <string name="sonyswr12_settings_stamina_summary">啶ぞ啶掂ぐ 啶膏啶掂た啶傕 啶啶� 啶灌啶︵く 啶椸い啶� 啶曕 啶嗋さ啶оた啶� 啶戉啷� 啶ぞ啶� 啶曕 啶啶� 啶曕ぐ 啶︵啶むぞ 啶灌 啶溹た啶膏じ啷� 啶曕ぞ啶� 啶曕ぞ 啶膏ぎ啶� 啶あ啶� 啶溹ぞ啶むぞ 啶灌</string>
+    <string name="sonyswr12_settings_alarm_interval_summary">啶膏啶ムぞ啶た啶� 啶呧げ啶距ぐ啷嵿ぎ 啶膏 啶す啶侧 啶膏啶ぞ啶班啶� 啶呧げ啶距ぐ啷嵿ぎ 啶呧啶むぐ啶距げ 啶灌啷� 啶囙じ 啶呧啶むぐ啶距げ 啶∴た啶掂ぞ啶囙じ 啶啶� 啶夃お啶啶椸啶班啶むぞ 啶曕 啶溹啶距え啷� 啶曕 啶侧た啶� 啶ㄠ啶傕う 啶曕 啶膏が啶膏 啶灌げ啷嵿啷� 啶氞ぐ啶� 啶曕ぞ 啶い啶� 啶侧啶距え啷� 啶曕 啶曕啶多た啶� 啶曕 啶溹ぞ 啶班す啷� 啶灌</string>
+    <string name="sonyswr12_settings_title">Sony SWR12 啶膏ぎ啶距く啷嬥啶�</string>
+    <string name="sonyswr12_settings_low_vibration">啶曕ぎ 啶曕啶え 啶膏啷嵿し啶�</string>
+    <string name="sonyswr12_settings_stamina">啶ぐ 啶た啶溹げ啷� 啶曕 啶啶� 啶啶�</string>
+</resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml
new file mode 100644
index 0000000..a9a97ac
--- /dev/null
+++ b/app/src/main/res/values-hr/strings.xml
@@ -0,0 +1,1609 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <string name="application_name_generic">Gadgetbridge</string>
+    <string name="male">Mu拧ko</string>
+    <string name="female">沤ensko</string>
+    <string name="other">Ostalo</string>
+    <string name="left">Lijevo</string>
+    <string name="mi2_prefs_button_press_broadcast_default_value" translatable="false">nodomain.freeyourgadget.gadgetbridge.ButtonPressed</string>
+    <string name="fw_upgrade_notice">Spremate se instalirati %s.</string>
+    <string name="title_activity_fw_app_insaller">Program za instalaciju FW-a/aplikacija</string>
+    <string name="title_activity_calblacklist">Kalendari na popisu nedopu拧tenih</string>
+    <string name="whitelist_all_for_notifications">Sve obavijesti stavi na popis dopu拧tenih</string>
+    <string name="blacklist_all_for_notifications">Sve obavijesti stavi na popis nedopu拧tenih</string>
+    <string name="app_move_to_top">Premjesti na vrh</string>
+    <string name="app_configure">Konfiguriraj</string>
+    <string name="appmanager_weather_install_provider">Instalirajte aplikaciju Weather Notification</string>
+    <string name="appmanager_weather_deactivate">Deaktivirajte sistemsku aplikaciju Vrijeme</string>
+    <string name="appmanager_weather_activate">Aktivirajte sistemsku aplikaciju Vrijeme</string>
+    <string name="appmanager_hrm_deactivate">Deaktiviraj HRM</string>
+    <string name="appmanager_hrm_activate">Aktivirajte HRM</string>
+    <string name="appmanager_health_deactivate">Deaktiviraj</string>
+    <string name="appmanager_health_activate">Aktiviraj</string>
+    <string name="appmanager_app_openinstore">Tra啪i u trgovini aplikacija Pebble</string>
+    <string name="appmananger_app_reinstall">Instaliraj ponovo</string>
+    <string name="error_setting_alias">Pogre拧ka pri postavljanju aliasa:</string>
+    <string name="miband_prefs_alias">Ime/pseudonim</string>
+    <string name="controlcenter_set_alias">Postavi pseudonim</string>
+    <string name="action_quit">Izlaz</string>
+    <string name="action_debug">Debug</string>
+    <string name="title_activity_controlcenter_generic">Gadgetbridge</string>
+    <string name="dbmanagementactivity_delete_old_activitydb_confirmation">Stvarno izbrisati staru bazu podataka aktivnosti\? Podaci aktivnosti koji nisu uvezeni 膰e se izgubiti.</string>
+    <string name="dbmanagementactivity_delete_old_activity_db">Izbrisati staru bazu podataka aktivnosti\?</string>
+    <string name="dbmanagementactivity_database_successfully_deleted">Podaci izbrisani.</string>
+    <string name="dbmanagementactivity_really_delete_entire_db">Stvarno izbrisati cijelu bazu podataka\? Svi va拧i podaci o aktivnostima i podaci o va拧im ure膽ajima bit 膰e izgubljeni.</string>
+    <string name="dbmanagementactivity_delete_activity_data_title">Izbrisati podatke aktivnosti\?</string>
+    <string name="activity_db_management_merge_old_title">Brisanje naslije膽ene baze podataka</string>
+    <string name="appmananger_app_delete_cache">Izbri拧i i ukloni iz predmemorije</string>
+    <string name="debugactivity_really_factoryreset">Vra膰anjem na tvorni膷ke postavke izbrisat 膰e se svi podaci s povezanog ure膽aja (ako je podr啪ano). Xiaomi/Huami ure膽aji tako膽er mijenjaju Bluetooth MAC adresu, tako da se pojavljuju kao novi ure膽aji za Gadgetbridge.</string>
+    <string name="appmananger_app_delete">Izbri拧i</string>
+    <string name="dbmanagementactivity_old_activity_db_successfully_deleted">Stari podaci o aktivnostima su izbrisani.</string>
+    <string name="activity_DB_delete_legacy_button">Izbri拧i stari DB</string>
+    <string name="Delete">Izbri拧i</string>
+    <string name="debugactivity_really_factoryreset_title">Jeste li sigurni da 啪elite vratiti na tvorni膷ke postavke\?</string>
+    <string name="controlcenter_calibrate_device">Kalibriraj ure膽aj</string>
+    <string name="controlcenter_snackbar_requested_screenshot">Izrada snimke zaslona ure膽aja</string>
+    <string name="controlcenter_snackbar_connecting">Povezivanje鈥�</string>
+    <string name="controlcenter_snackbar_disconnecting">Prekidanje veze</string>
+    <string name="controlcenter_snackbar_need_longpress">Dugo pritisnite karticu za prekid veze</string>
+    <string name="controlcenter_navigation_drawer_close">Zatvori ladicu za navigaciju</string>
+    <string name="controlcenter_navigation_drawer_open">Otvori ladicu za navigaciju</string>
+    <string name="controlcenter_delete_device_dialogmessage">Ovo 膰e obrisati ure膽aj i sve povezane podatke!</string>
+    <string name="controlcenter_delete_device_name">Izbri拧i %1$s</string>
+    <string name="controlcenter_delete_device">Izbri拧i ure膽aj</string>
+    <string name="controlcenter_disconnect">Prekini vezu</string>
+    <string name="controlcenter_connect">Pove啪i鈥�</string>
+    <string name="controlcenter_change_fm_frequency">Promjena FM frekvencije</string>
+    <string name="controlcenter_change_led_color">Promijeni LED boju</string>
+    <string name="controlcenter_take_screenshot">Napravi snimku zaslona</string>
+    <string name="controlcenter_find_device">Prona膽i izgubljeni ure膽aj</string>
+    <string name="controlcenter_fetch_activity_data">Sinkronizacija</string>
+    <string name="action_donate">Donirajte</string>
+    <string name="action_settings">Postavke</string>
+    <string name="sonyswr12_settings_stamina">Uklju膷en je na膷in u拧tede energije</string>
+    <string name="sonyswr12_settings_alarm_interval">Pametni interval alarma u minutama</string>
+    <string name="sonyswr12_settings_low_vibration_summary">Omogu膰ite niski intenzitet vibracija na pojasu</string>
+    <string name="sonyswr12_settings_stamina_summary">Na膷in u拧tede energije isklju膷uje povremeno automatsko mjerenje brzine otkucaja srca 膷ime se pove膰ava radno vrijeme</string>
+    <string name="sonyswr12_settings_alarm_interval_summary">Interval pametnog alarma je interval prije instaliranog alarma. U ovom intervalu ure膽aj poku拧ava otkriti najlak拧u fazu spavanja za budnog korisnika</string>
+    <string name="sonyswr12_settings_title">Sony SWR12 postavke</string>
+    <string name="sonyswr12_settings_low_vibration">Omogu膰ena slaba vibracija</string>
+    <string name="find_lost_device_message">Tra啪iti %1$s\?</string>
+    <string name="controlcenter_power_off">Isklju膷ivanje</string>
+    <string name="controlcenter_power_off_confirm_title">Isklju膷ivanje</string>
+    <string name="battery_level">Razina baterija</string>
+    <string name="calendar_week">Tjedan</string>
+    <string name="calendar_month">Mjesec</string>
+    <string name="calendar_three_months">3 mjeseca</string>
+    <string name="calendar_six_months">6 mjeseci</string>
+    <string name="calendar_year">Godina</string>
+    <string name="device_card_activity_card_title">Informacije o aktivnosti na kartici ure膽aja</string>
+    <string name="prefs_activity_in_device_card_title">Prika啪i podatke o aktivnosti na kartici ure膽aja</string>
+    <string name="device_card_activity_card_title_summary">Odaberite koje se pojedinosti aktivnosti prikazuju na kartici ure膽aja</string>
+    <string name="prefs_activity_in_device_card_title_summary">Prika啪i trenutne korake, udaljenost ili spavanje na kartici ure膽aja</string>
+    <string name="prefs_activity_in_device_card_sleep_title">Spavanje</string>
+    <string name="calendar_day">Dan</string>
+    <string name="pref_button_action_disabled">Onemogu膰eno</string>
+    <string name="pref_media_next">Sljede膰a pjesma</string>
+    <string name="pref_media_volumeup">Poja膷avanje glasno膰e</string>
+    <string name="pref_media_forward">Presko膷i naprijed</string>
+    <string name="pref_media_rewind">Presko膷i natrag</string>
+    <string name="distance_format_kilometers">###.#km</string>
+    <string name="distance_format_miles">###.#mi</string>
+    <string name="distance_format_feet">###ft</string>
+    <string name="title_activity_debug">Debug</string>
+    <string name="pref_media_volumedown">Sti拧avanje glasno膰e</string>
+    <string name="battery_detail_activity_title">Informacije o bateriji</string>
+    <string name="prefs_activity_in_device_card_sleep_title_summary">Prika啪i trajanje spavanja</string>
+    <string name="prefs_activity_in_device_card_distance_title_summary">Udaljenost se izra膷unava iz koraka i duljine koraka (podesivo u Postavkama - O vama)</string>
+    <string name="prefs_activity_in_device_card_steps_title_summary">Prika啪i ukupan broj koraka</string>
+    <string name="controlcenter_power_off_confirm_description">Jeste li sigurni da 啪elite isklju膷iti ure膽aj\?</string>
+    <string name="pref_media_previous">Prethodna pjesma</string>
+    <string name="distance_format_meters">###m</string>
+    <string name="pref_device_action_broadcast">Po拧alji emitiranje</string>
+    <string name="calendar_two_weeks">Dva tjedna</string>
+    <string name="title_activity_appmanager">Upravitelj aplikacija</string>
+    <string name="pref_activity_recognize_rowing">prepoznaj veslanje</string>
+    <string name="pref_activity_recognition_mode_none">ni拧ta</string>
+    <string name="pref_activity_recognition_mode_ask">pitaj</string>
+    <string name="menuitem_menu">Izbornik</string>
+    <string name="watchface_dialog_widget_color">Boja</string>
+    <string name="enable_vibrations_summary">Vibracije za pozive, poruke, obavijesti i drugo</string>
+    <string name="enable_notifications_summary">Obavijesti za pozive, poruke i vi拧e</string>
+    <string name="prefs_dolby_summary">Dolby unaprijed postavljen za ekvilajzer</string>
+    <string name="prefs_game_mode">Na膷in igranja</string>
+    <string name="sony_warn_sbc_codec">Upozorenje: Postavke ekvilajzera, audio polo啪aja i surround zvuka rade samo za SBC audio kodek.</string>
+    <string name="sony_notification_voice_guide">Obavijesti i glasovni vodi膷</string>
+    <string name="sony_automatic_power_off_30_min">30 minuta</string>
+    <string name="sony_automatic_power_off_1_hour">1 sat</string>
+    <string name="sony_automatic_power_off_when_taken_off">Kada se skine</string>
+    <string name="pref_activity_recognize_running">prepoznaj tr膷anje</string>
+    <string name="prefs_activity_recognition">Postavke prepoznavanja aktivnosti</string>
+    <string name="fw_upgrade_notice_amazfitbip">Spremate se instalirati %s firmware na va拧 Amazfit Bip.
+\n
+\nObavezno instalirajte .fw datoteku, zatim .res datoteku i na kraju .gps datoteku. Va拧 sat 膰e se ponovno pokrenuti nakon instaliranja .fw datoteke.
+\n
+\nNapomena: Ne morate instalirati .res i .gps ako su ove datoteke potpuno iste kao one prethodno instalirane.
+\n
+\nNASTAVITE NA VLASTITU ODGOVORNOST!</string>
+    <string name="fw_upgrade_notice_amazfitcor2">Spremate se instalirati %s firmware na va拧 Amazfit Cor 2.
+\n
+\nObavezno instalirajte .fw datoteku, a nakon toga .res datoteku. Va拧 band 膰e se ponovno pokrenuti nakon instaliranja.
+\n
+\nNapomena: Ne morate instalirati .res ako je potpuno isti kao onaj koji je prethodno instaliran.
+\n
+\nNASTAVITE NA VLASTITU ODGOVORNOST!
+\n
+\nPOTPUNO NETESTIRANO, VJEROJATNO TREBATE FLASHATI BEATS_W FIRMWARE AKO JE VA艩 URE膼AJ \"Amazfit Band 2\"</string>
+    <string name="fw_upgrade_notice_miband4">Spremate se instalirati %s firmware na va拧 Mi Band 4.
+\n
+\nObavezno instalirajte .fw datoteku, a nakon toga .res datoteku. Va拧 band 膰e se ponovno pokrenuti nakon instaliranja.
+\n
+\nNapomena: Ne morate instalirati .res ako je potpuno isti kao onaj koji je prethodno instaliran.
+\n
+\nNASTAVITE NA VLASTITU ODGOVORNOST!</string>
+    <string name="fw_upgrade_notice_miband5">Spremate se instalirati %s firmware na va拧 Mi Band 5.
+\n
+\nObavezno instalirajte .fw datoteku, a nakon toga .res datoteku. Va拧 band 膰e se ponovno pokrenuti nakon instaliranja.
+\n
+\nNapomena: Ne morate instalirati .res ako je potpuno isti kao onaj koji je prethodno instaliran.
+\n
+\nNASTAVITE NA VLASTITU ODGOVORNOST!</string>
+    <string name="title_activity_settings">Postavke</string>
+    <string name="pref_header_general">Op膰e postavke</string>
+    <string name="pref_title_theme">Tema</string>
+    <string name="pref_summary_minimize_priority_on">Ikona u statusnoj traci i obavijest na zaklju膷anom zaslonu su skrivene</string>
+    <string name="pref_summary_notifications_pebblemsg">Podr拧ka za aplikacije koje 拧alju obavijesti Pebbleu putem PebbleKita.</string>
+    <string name="pref_title_notifications_generic">Podr拧ka za generi膷ke obavijesti</string>
+    <string name="pref_title_canned_messages_set">A啪uriranje na ure膽aju</string>
+    <string name="pref_title_pebble_sync_health">Sinkroniziraj Pebble Health</string>
+    <string name="pref_title_pebble_sync_misfit">Neprikladna sinkronizacija</string>
+    <string name="pref_summary_enable_outgoing_call">Onemogu膰avanje 膰e tako膽er zaustaviti Pebble 2/LE da vibrira pri odlaznim pozivima</string>
+    <string name="pref_header_pebble_timeline">Vremenska traka</string>
+    <string name="pref_summary_sunrise_sunset">Po拧alji vrijeme izlaska i zalaska sunca na temelju lokacije na vremensku traku Pebble</string>
+    <string name="pref_summary_autoremove_notifications">Obavijesti se automatski uklanjaju s ure膽aja kada se odbace s telefona</string>
+    <string name="pref_summary_allow_high_mtu">Pove膰ava brzinu prijenosa, ali mo啪da ne膰e raditi na nekim Android ure膽ajima.</string>
+    <string name="pref_summary_sync_calendar">Omogu膰uje upozorenja kalendara, 膷ak i kada je veza prekinuta</string>
+    <string name="pref_header_notifications_and_calls">Obavijesti i pozivi</string>
+    <string name="zetime_handmove_display_summary">Okrenite zape拧膰e da biste aktivirali ili deaktivirali zaslon.</string>
+    <string name="zetime_calories_type">Vrsta kalorija</string>
+    <string name="zetime_calories_type_all">Aktivne i neaktivne potro拧ene kalorije</string>
+    <string name="zetime_date_format">Format datuma</string>
+    <string name="zetime_prefs_inactivity_repetitions">Ponavljanja</string>
+    <string name="zetime_prefs_inactivity_we">Srijeda</string>
+    <string name="zetime_prefs_inactivity_th">膶etvrtak</string>
+    <string name="zetime_signaling_vibrate">Neprekidna vibracija</string>
+    <string name="zetime_signaling_beep">Neprekidno pi拧tanje</string>
+    <string name="zetime_signaling_vibrate_beep">Neprekidna vibracija i zvu膷ni signal</string>
+    <string name="zetime_signaling_vibrate_once">Vibriraj jednom</string>
+    <string name="zetime_signaling_vibrate_twice">Vibriraj dvaput</string>
+    <string name="pref_summary_authkey">Promijenite klju膷 autentifikacije u zajedni膷ki klju膷 na svim svojim Android ure膽ajima s kojih se 啪elite povezati. Prethodni zadani klju膷 za sve ure膽aje je 0123456789@ABCDE</string>
+    <string name="pref_title_auto_export_enabled">Omogu膰en automatski izvoz</string>
+    <string name="pref_auto_fetch">Automatsko dohva膰anje podataka o aktivnostima</string>
+    <string name="pref_auto_fetch_summary">Dohva膰anje se doga膽a nakon otklju膷avanja zaslona. Radi samo ako je postavljen mehanizam za zaklju膷avanje!</string>
+    <string name="connecting">Povezivanje</string>
+    <string name="connected">Povezano</string>
+    <string name="unknown_state">Nepoznato stanje</string>
+    <string name="_unknown_">(nepoznato)</string>
+    <string name="test_notification">Testna obavijest</string>
+    <string name="this_is_a_test_notification_from_gadgetbridge">Ovo je testna obavijest s Gadgetbridge-a</string>
+    <string name="find_my_phone_notification">Prona膽i moj telefon</string>
+    <string name="bluetooth_is_not_supported_">Bluetooth nije podr啪an.</string>
+    <string name="bluetooth_is_disabled_">Bluetooth je onemogu膰en.</string>
+    <string name="tap_connected_device_for_app_mananger">Dodirnite povezani ure膽aj za upravitelja aplikacija</string>
+    <string name="tap_connected_device_for_activity">Dodirnite povezani ure膽aj za aktivnost</string>
+    <string name="tap_connected_device_for_vibration">Dodirnite povezani ure膽aj za vibraciju</string>
+    <string name="cannot_connect_bt_address_invalid_">Ne mogu se povezati. Bluetooth adresa nije va啪e膰a\?</string>
+    <string name="installing_binary_d_d">Instaliranje binarne datoteke %1$d/%2$d</string>
+    <string name="installation_successful">Instalirano</string>
+    <string name="message_cannot_pair_no_mac">MAC adresa nije proslije膽ena, nije mogu膰e upariti.</string>
+    <string name="pref_header_vibration_count">Broj vibracija</string>
+    <string name="installer_activity_wait_while_determining_status">Molimo pri膷ekajte dok utvr膽ujete status instalacije鈥�</string>
+    <string name="installhandler_firmware_name">%1$s: %2$s %3$s</string>
+    <string name="updatefirmwareoperation_update_complete_rebooting">Instalacija firmwarea dovr拧ena, ponovno pokretanje ure膽aja鈥�</string>
+    <string name="updatefirmwareoperation_write_failed">Flashanje firmvera nije uspjelo</string>
+    <string name="pref_summary_keep_data_on_device">Zadr啪at 膰e podatke o aktivnostima na ure膽aju 膷ak i nakon sinkronizacije. Korisno ako se GB koristi zajedno s drugim aplikacijama.</string>
+    <string name="prefs_find_phone_summary">Upotrijebite svoj bend za reprodukciju melodije zvona na telefonu.</string>
+    <string name="mi2_prefs_inactivity_warnings_summary">Band 膰e vibrirati kada ste neko vrijeme bili neaktivni</string>
+    <string name="mi2_prefs_inactivity_warnings_dnd_summary">Onemogu膰i upozorenja o neaktivnosti za vremenski interval</string>
+    <string name="pref_summary_force_white_color_scheme">Korisno ako va拧 sat ima tamne kazaljke</string>
+    <string name="french">Francuski</string>
+    <string name="pref_chart_heartrate_color_red">Crvena</string>
+    <string name="activity_prefs_chart_min_steps_per_minute_for_run">Minimalni koraci u minuti za otkrivanje tr膷anja</string>
+    <string name="title_activity_db_management">Upravljanje podacima</string>
+    <string name="activity_db_management_import_export_explanation">Operacije izvoza/uvoza koriste sljede膰i put (pogledajte dolje) do direktorija na va拧em ure膽aju. Ovaj direktorij je dostupan drugim Android aplikacijama i va拧em ra膷unalu. Imajte na umu da se ovaj direktorij i sve datoteke u njemu bri拧u ako deinstalirate Gadgetbridge. Podaci uklju膷uju:
+\n Export_preference - globalne postavke
+\n Export_preference_MAC - postavke specifi膷ne za ure膽aj
+\n Gadgetbridge - bazu podataka ure膽aja i aktivnosti
+\n Gadgetbridge_date - baza podataka izvezena na datum
+\n *.gpx - GPS zapise
+\n *.log - datoteke zapisa
+\nO膷ekujte da 膰ete tamo prona膰i svoje izvezene datoteke (ili smjestiti datoteke koje 啪elite uvesti).:</string>
+    <string name="dbmanagementactivity_error_exporting_db">Pogre拧ka pri izvozu baze podataka: %1$s</string>
+    <string name="dbmanagementactivity_error_cleaning_export_directory">Pogre拧ka prilikom brisanja datoteka iz direktorija za izvoz %1$s</string>
+    <string name="dbmanagementactivity_export_data_title">Izvesti podatke\?</string>
+    <string name="dbmanagementactivity_error_importing_shared">Pogre拧ka pri uvozu preferencija: %1$s</string>
+    <string name="activity_db_management_exportimport_label">Izvoz i uvoz</string>
+    <string name="pref_summary_setup_bt_pairing">Deaktivirajte ovo ako imate problema s povezivanjem</string>
+    <string name="devicetype_miband6">Mi Band 6</string>
+    <string name="devicetype_amazfit_band5">Amazfit Band 5</string>
+    <string name="devicetype_amazfit_bips_lite">Amazfit Bip S Lite</string>
+    <string name="devicetype_qhybrid">Fossil Q Hybrid</string>
+    <string name="devicetype_casiogb6900">Casio GB-6900</string>
+    <string name="devicetype_bfh16">BFH-16</string>
+    <string name="devicetype_banglejs">Bangle.js</string>
+    <string name="menuitem_notifications">Obavijesti</string>
+    <string name="menuitem_activity">Povijest vje啪banja</string>
+    <string name="menuitem_goal">Cilj aktivnosti</string>
+    <string name="preferences_rtl_settings">Podr拧ka zdesna nalijevo</string>
+    <string name="share_log">Dijeli zapisnik</string>
+    <string name="share_log_warning">Imajte na umu da datoteke zapisnika Gadgetbridgea koje mogu sadr啪avati mnogo osobnih podataka, uklju膷uju膰i, ali ne ograni膷avaju膰i se na zdravstvene podatke, jedinstvene identifikatore (kao 拧to je MAC adresa ure膽aja), glazbene postavke itd. Razmislite o ure膽ivanju datoteke i uklanjanju ovih informacija prije slanja datoteke javnom izvje拧膰u o problemu.</string>
+    <string name="toast_app_must_be_selected">Aplikacija mora biti odabrana da bi se konfigurirala</string>
+    <string name="edittext_notification_filter_words_hint">Unesite 啪eljene rije膷i, novi redak za svaku</string>
+    <string name="qhybrid_prompt_million_steps">Postavite broj koraka na milijun da biste to aktivirali.</string>
+    <string name="hr_widget_battery">Baterija</string>
+    <string name="hr_widget_weather">Vrijeme</string>
+    <string name="hr_widget_nothing">Ni拧ta</string>
+    <string name="about_additional_contributions">Veliko hvala svim nenavedenim suradnicima na doprinosu kodu, prijevodima, podr拧ci, idejama, motivaciji, izvje拧膰ima o gre拧kama, novcu鈥� 鉁�</string>
+    <string name="error_version_check_extreme_caution">OPREZ: Pogre拧ka prilikom provjere informacija o verziji! Ne biste trebali nastaviti! Vidio sam naziv verzije \"%s\"</string>
+    <string name="require_location_provider">Lokacija mora biti omogu膰ena</string>
+    <string name="companiondevice_pairing_details">Omogu膰uje novu podr拧ku za CompanionDevice API (ima u膷inak samo na Android 8 ili noviji) koja 膰e pove膰ati pouzdanost ako se usluga treba ponovno pokrenuti u pozadini, zahtijeva ponovno uparivanje pomo膰u Gadgetbridge-a da bi imalo u膷inak</string>
+    <string name="error_background_service">Pokretanje pozadinske usluge nije uspjelo</string>
+    <string name="error_background_service_reason_truncated">Pokretanje pozadinske usluge nije uspjelo jer鈥�</string>
+    <string name="device_is_currently_bonded">VE膯 POVEZANO</string>
+    <string name="device_requires_key">POTREBAN JE KLJU膶, PRITISNI ZA UNOS</string>
+    <string name="error_background_service_reason">Pokretanje pozadinske usluge nije uspjelo zbog iznimke 鈥� Pritisni ovdje za vi拧e informacija.\n\nGre拧ka:</string>
+    <string name="error_retrieving_devices_database">Pogre拧ka prilikom dohva膰anja ure膽aja iz baze podataka</string>
+    <string name="ignore_bonded_devices_description">Omogu膰avanje ove opcije zanemarit 膰e ure膽aje koji su ve膰 bili povezani/upareni prilikom skeniranja</string>
+    <string name="distanceMeters">Udaljenost</string>
+    <string name="ascentMeters">Uzbrdo</string>
+    <string name="descentMeters">Nizbrdo</string>
+    <string name="ascentDistance">Udaljenost uzbrdo</string>
+    <string name="descentDistance">Udaljenost nizbrdo</string>
+    <string name="averageAltitude">Prosjek</string>
+    <string name="steps">Koraci</string>
+    <string name="activeSeconds">Aktivan</string>
+    <string name="caloriesBurnt">Kalorije</string>
+    <string name="maxSpeed">Maksimum</string>
+    <string name="totalStride">Ukupni korak</string>
+    <string name="averageHR">Broj otkucaja srca</string>
+    <string name="maxHR">Maksimalni broj otkucaja srca</string>
+    <string name="minHR">Minimalni broj otkucaja srca</string>
+    <string name="averageKMPaceSeconds">Tempo</string>
+    <string name="averageStride">Prosje膷na duljina koraka</string>
+    <string name="maxStride">Maksimalna duljina koraka</string>
+    <string name="minStride">Minimalna duljina koraka</string>
+    <string name="averageCadence">Prosje膷ni ritam</string>
+    <string name="maxCadence">Maksimalni ritam</string>
+    <string name="minCadence">Minimalni ritam</string>
+    <string name="averageStrokeDistance">Prosje膷na udaljenost zamaha</string>
+    <string name="averageStrokesPerSecond">Prosje膷ni zamasi</string>
+    <string name="averageLapPace">Prosje膷ni tempo kruga</string>
+    <string name="swolfIndex">SWOLF</string>
+    <string name="swimStyle">Stil plivanja</string>
+    <string name="descentSeconds">Silazno</string>
+    <string name="flatSeconds">Ravno</string>
+    <string name="baseAltitude">Osnovna visina</string>
+    <string name="averageSpeed">Prosje膷na brzina</string>
+    <string name="meters">m</string>
+    <string name="steps_unit">koraci</string>
+    <string name="km_h">km/h</string>
+    <string name="strokes_second">zamaha/s</string>
+    <string name="strokes_unit">zamah</string>
+    <string name="seconds">s</string>
+    <string name="swolf_index">swolf-indeks</string>
+    <string name="swim_style">stil plivanja</string>
+    <string name="laps_unit">krugovi</string>
+    <string name="calories_unit">kcal</string>
+    <string name="seconds_m">s/m</string>
+    <string name="minutes_km">min/km</string>
+    <string name="spm">korak/min</string>
+    <string name="bpm">tempo/min</string>
+    <string name="mi">mi</string>
+    <string name="Strokes">Zamasi</string>
+    <string name="Swimming">Plivanje</string>
+    <string name="Distance">Udaljenost</string>
+    <string name="Elevation">Visina</string>
+    <string name="Speed">Brzina</string>
+    <string name="Activity">Aktivnost</string>
+    <string name="activity_detail_start_label">Po膷etak</string>
+    <string name="activity_detail_end_label">Kraj</string>
+    <string name="activity_detail_duration_label">Trajanje</string>
+    <string name="prefs_events_forwarding_summary">Upotrijebite doga膽aje ure膽aja za pokretanje radnji i Android emitiranja</string>
+    <string name="prefs_events_forwarding_title">Radnje ure膽aja</string>
+    <string name="prefs_events_forwarding_broadcast_title">Emitiranje poruke</string>
+    <string name="pref_header_statistics">Statistika sportskih aktivnosti</string>
+    <string name="activity_filter_date_from">Od</string>
+    <string name="activity_filter_date_to">Prima</string>
+    <string name="activity_filter_reset_filter">Poni拧ti filtar</string>
+    <string name="activity_filter_filter_title">Filtar</string>
+    <string name="activity_filter_name_contains">Oznaka</string>
+    <string name="activity_filter_apply_filter">Primijeni filtar</string>
+    <string name="addto_filter">Dodajte u filter</string>
+    <string name="activity_filter_individual_items">Pojedina膷no odabrane stavke</string>
+    <string name="sports_activity_quick_filter_this_month">Ovaj mjesec</string>
+    <string name="sports_activity_quick_filter_last_month">Prethodni mjesec</string>
+    <string name="activity_summaries_all_devices">Svi ure膽aji</string>
+    <string name="activity_filter_from_placeholder">daleka pro拧lost</string>
+    <string name="activity_filter_to_placeholder">danas</string>
+    <string name="devicetype_nut_mini">Nut mini</string>
+    <string name="qhybrid_calibration_align_hint">Upotrijebite donje gumbe za poravnanje kazaljki sata na 12:00.</string>
+    <string name="prefs_autolight">Automatsko svjetlo</string>
+    <string name="prefs_key_vibration">Vibracija tipki</string>
+    <string name="prefs_operating_sounds">Zvukovi rada</string>
+    <string name="prefs_autoremove_message">Automatski ukloni SMS obavijesti</string>
+    <string name="fossil_hr_commute_processing">Obrada zahtjeva鈥�</string>
+    <string name="fossil_hr_unavailable_unauthed">Nije dostupno u neautoriziranom na膷inu</string>
+    <string name="qhybrid_title_background_image">Pozadinska slika</string>
+    <string name="qhybrid_title_file_management">Upravljanje datotekama</string>
+    <string name="qhybrid_title_calibration">Kalibracija</string>
+    <string name="fossil_hr_warning_firmware_too_new">Neke funkcije su onemogu膰ene jer je firmware sata previ拧e nov</string>
+    <string name="pref_summary_physical_buttons">Konfigurirajte funkcionalnost fizi膷kih gumba na satu</string>
+    <string name="qhybrid_summary_calibration">Kalibrirajte kazaljke na satu</string>
+    <string name="pref_summary_canned_messages_set">Po拧aljite dolje konfigurirane poruke na svoj ure膽aj</string>
+    <string name="fossil_hr_commute_actions_explanation">Ovdje konfigurirane radnje pojavit 膰e se u aplikaciji Putovanje na posao na va拧em satu. Pro膷itajte wiki za informacije o tome kako postupati s namjerama koje proizvedu ove radnje.</string>
+    <string name="fossil_hr_edit_action_delete">izbri拧i</string>
+    <string name="fossil_hr_new_action_cancel">otka啪i</string>
+    <string name="fossil_hr_new_action">Nova radnja</string>
+    <string name="qhybrid_calibration_counterclockwise">U suprotnom smjeru kazaljke na satu</string>
+    <string name="qhybrid_calibration_clockwise">U smjeru kazaljke na satu</string>
+    <string name="qhybrid_calibration_1_step">1 korak</string>
+    <string name="qhybrid_calibration_10_steps">10 koraka</string>
+    <string name="qhybrid_watchface_configuration_old_firmware">Zaslon za konfiguraciju broj膷anika za satove s broj膷anik verzijom DN1.0.2.19r i ni啪om</string>
+    <string name="qhybrid_pref_title_actions">Radnje</string>
+    <string name="qhybrid_pref_summary_actions">Radnje za aplikaciju Putovanje na posao</string>
+    <string name="pref_title_developer_settings">Postavke programera</string>
+    <string name="pref_summary_developer_settings">Postavke i funkcije koje koriste razvojni programeri</string>
+    <string name="qhybrid_pref_title_external_intents">Dopusti opasne vanjske namjere</string>
+    <string name="qhybrid_pref_summary_external_intents">Omogu膰uje drugim Android aplikacijama u膷itavanje/prepisivanje datoteka</string>
+    <string name="button_watchface_edit_name">Uredi naziv</string>
+    <string name="button_watchface_settings">Postavke broj膷anika</string>
+    <string name="watchface_dialog_title_set_name">Postavi naziv broj膷anika</string>
+    <string name="watchface_toast_settings_incomplete">Postavke nepotpune, widget nije dodan</string>
+    <string name="watchface_dialog_title_add_widget">Dodaj widget</string>
+    <string name="watchface_widget_type_steps">Koraci</string>
+    <string name="watchface_widget_type_heart_rate">Broj otkucaja srca</string>
+    <string name="button_watchface_preview">Pregled na satu</string>
+    <string name="button_watchface_save_apply">Spremi i primijeni</string>
+    <string name="watchface_dialog_widget_type">Vrsta</string>
+    <string name="watchface_dialog_widget_y_coordinate">Koordinata Y (maks. 240)</string>
+    <string name="watchface_dialog_widget_color_white">Bijela</string>
+    <string name="watchface_dialog_widget_color_black">Crna</string>
+    <string name="watchface_dialog_title_settings">Postavke broj膷anika</string>
+    <string name="watchface_setting_title_display_refresh_timeout">Istek vremena za osvje啪avanje zaslona</string>
+    <string name="watchface_setting_title_wrist_flick">Traj zglobom</string>
+    <string name="watchface_setting_desc_wrist_flick">(da biste potpuno onemogu膰ili, omogu膰ite relativno kretanje i postavite sve vrijednosti na 0)</string>
+    <string name="watchface_setting_wrist_flick_move_relative">Pomicanje kazaljki u odnosu na vrijeme</string>
+    <string name="watchface_setting_wrist_flick_hour">Kazaljka za sate (-360 do 360)</string>
+    <string name="watchface_setting_wrist_flick_minute">Kazaljka za minute (-360 do 360)</string>
+    <string name="watchface_cache_confirm_overwrite">Pokaziva膷 s ovim imenom ve膰 postoji u predmemoriji. 沤elite li ga prebrisati\?</string>
+    <string name="watchface_upload_failed">Prijenos izgleda sata nije uspio. Poku拧ajte ponovno.</string>
+    <string name="watchface_widget_type_battery">Baterija</string>
+    <string name="watchface_widget_type_calories">Kalorije</string>
+    <string name="watchface_widget_type_2nd_tz">2. vremenska zona</string>
+    <string name="watchface_widget_type_active_mins">Aktivne minute</string>
+    <string name="watchface_setting_title_power_saving">U拧teda energije</string>
+    <string name="watchface_setting_power_saving_display">Onemogu膰i a啪uriranja zaslona dok nemate zape拧膰e</string>
+    <string name="prefs_sleep_time">Vrijemena mirovanja</string>
+    <string name="prefs_sleep_time_label">Definirajte sate spavanja</string>
+    <string name="prefs_sleep_time_summary">Odre膽uje vrijeme kada je spavanje registrirano</string>
+    <string name="prefs_vibration_enable">Omogu膰i vibracije</string>
+    <string name="prefs_notifications_enable">Omogu膰i obavijesti</string>
+    <string name="prefs_autoheartrate_summary">Periodi膷na mjerenja otkucaja srca tijekom dana i tijekom spavanja</string>
+    <string name="prefs_autoheartrate_measurement">Automatsko mjerenje otkucaja srca</string>
+    <string name="prefs_autoheartrate_sleep">Mjerite tijekom spavanja</string>
+    <string name="prefs_autoheartrate_interval">U膷estalost mjerenja</string>
+    <string name="nothing_prefs_inear_title">Otkrivanje u uhu</string>
+    <string name="nothing_prefs_audiomode_title">Audio na膷in</string>
+    <string name="prefs_equalizer_preset">Unaprijed postavljen ekvilajzer</string>
+    <string name="prefs_ambient_mode">Ambijentalni na膷in rada</string>
+    <string name="prefs_active_noise_cancelling_summary">Blokiraj buku iz okoline</string>
+    <string name="pressure_relief_summary">Sprije膷ite osje膰aj pritiska u u拧ima kada ne koristite aktivno uklanjanje buke</string>
+    <string name="prefs_right">Desno</string>
+    <string name="prefs_galaxy_touch_options">Opcije dodira</string>
+    <string name="sony_ambient_sound_ambient_sound">Ambijentalni zvuk</string>
+    <string name="sony_ambient_sound_focus_voice">Fokusiranje na glas</string>
+    <string name="sony_ambient_sound_level">Razina ambijentalnog zvuka</string>
+    <string name="sony_sound_position">Polo啪aj zvuka</string>
+    <string name="sony_sound_position_front">Sprijeda</string>
+    <string name="sony_sound_position_front_left">Sprijeda lijevo</string>
+    <string name="sony_sound_position_rear_right">Straga desno</string>
+    <string name="sony_surround_mode">Surround na膷in rada</string>
+    <string name="sony_surround_mode_off">Isklju膷eno</string>
+    <string name="sony_surround_mode_arena">Arena</string>
+    <string name="sony_surround_mode_concert_hall">Koncertna dvorana</string>
+    <string name="sony_equalizer">Ekvilajzer</string>
+    <string name="sony_equalizer_preset_off">Isklju膷eno</string>
+    <string name="sony_equalizer_preset_bright">Svijetlo</string>
+    <string name="sony_equalizer_preset_mellow">Le啪erno</string>
+    <string name="sony_equalizer_preset_relaxed">Opu拧teno</string>
+    <string name="sony_equalizer_preset_vocal">Vokalno</string>
+    <string name="sony_equalizer_preset_treble_boost">Poja膷anje visokih tonova</string>
+    <string name="sony_equalizer_preset_custom_1">Prilago膽eno 1</string>
+    <string name="sony_equalizer_preset_custom_2">Prilago膽eno 2</string>
+    <string name="pref_header_sony_equalizer_preset_custom_1">Prilago膽eno unaprijed postavljeno 1</string>
+    <string name="pref_header_sony_equalizer_preset_custom_2">Prilago膽eno unaprijed postavljeno 2</string>
+    <string name="sony_equalizer_band_400">400</string>
+    <string name="sony_equalizer_band_1000">1.000</string>
+    <string name="sony_equalizer_band_16000">16.000</string>
+    <string name="sony_equalizer_clear_bass">膶isti bas</string>
+    <string name="sony_touch_sensor">Upravljanje senzorom na dodir</string>
+    <string name="sony_automatic_power_off">Automatsko isklju膷ivanje</string>
+    <string name="sony_pause_when_taken_off">Pauzirajte kada se slu拧alice skinu</string>
+    <string name="sony_button_mode_ambient_sound_control">Upravljanje ambijentalnim zvukom</string>
+    <string name="sony_button_mode_playback_control">Kontrola reprodukcije</string>
+    <string name="sony_button_mode_volume_control">Kontrola glasno膰e</string>
+    <string name="watchface_widget_type_custom">Prilago膽eni widget</string>
+    <string name="watchface_dialog_widget_timezone">Vremenska zona</string>
+    <string name="watchface_dialog_widget_timeout_show_circle">Prika啪i krug nakon isteka vremena</string>
+    <string name="qhybrid_title_on_device_confirmation">Omogu膰i potvrdu uparivanja na ure膽aju</string>
+    <string name="qhybrid_summary_on_device_confirmation">Potvrde uparivanja na ure膽aju mogu biti neugodne. Ako ih onemogu膰ite, mo啪ete izgubiti funkcionalnost.</string>
+    <string name="pref_media_playpause">Uklju膷ite reprodukciju</string>
+    <string name="pref_activity_recognize_biking">prepoznaj vo啪nju bicikla</string>
+    <string name="pref_activity_recognition_mode_auto">automatski</string>
+    <string name="fossil_hr_button_config_info">Neki se gumbi ne mogu konfigurirati jer su njihove funkcije tvrdo kodirane u firmwareu sata.
+\n
+\nUpozorenje: dugim pritiskom na gornju tipku kada je instaliran broj膷anik iz slu啪bene aplikacije Fossil tako膽er 膰e se prebacivati izme膽u prikazivanja/skrivanja widgeta.</string>
+    <string name="pref_activity_recognize_walking">prepoznaj hodanje</string>
+    <string name="pref_title_physical_buttons">Fizi膷ki gumbi</string>
+    <string name="title_activity_set_reminders">Konfiguriranje podsjetnika</string>
+    <string name="controlcenter_start_configure_reminders">Konfiguracija podsjetnika</string>
+    <string name="reminder_repeat">Ponovi</string>
+    <string name="reminder_date">Datum</string>
+    <string name="reminder_every_month">Svaki mjesec</string>
+    <string name="reminder_every_year">Svake godine</string>
+    <string name="alarm_tue_short">Uto</string>
+    <string name="prefs_charts_tabs_summary">Vidljive kartice grafikona</string>
+    <string name="sleepchart_your_sleep">Spavanje</string>
+    <string name="weeksleepchart_sleep_a_week">Spavanje tjedno</string>
+    <string name="weekstepschart_steps_a_week">Koraka tjedno</string>
+    <string name="activity_list_summary_active_time">Aktivno vrijeme</string>
+    <string name="activity_list_summary_intensity">Pokret
+\nIntenzitet</string>
+    <string name="fwappinstaller_connection_state">Veza s ure膽ajem: %1$s</string>
+    <string name="pbw_installhandler_pebble_firmware">Firmware Pebble %1$s</string>
+    <string name="pbwinstallhandler_correct_hw_revision">Ispravna revizija hardvera</string>
+    <string name="minutes_1">1 minuta</string>
+    <string name="pref_title_low_latency_fw_update">Koristite na膷in niske latencije za flashanje firmware</string>
+    <string name="abstract_chart_fragment_kind_deep_sleep">Duboki san</string>
+    <string name="pref_title_keep_data_on_device">Zadr啪i podatke o aktivnostima na ure膽aju</string>
+    <string name="miband_prefs_reserve_reminder_calendar">Podsjetnici za rezerviranje za nadolaze膰e doga膽aje</string>
+    <string name="DEVINFO_HR_VER">"HR: "</string>
+    <string name="prefs_button_long_press_action_selection_title">Radnja dugog pritiska gumba</string>
+    <string name="prefs_events_forwarding_startnonwear">Ne nosi se</string>
+    <string name="prefs_reserve_reminder_calendar_summary">Broj kalendarskih doga膽aja koji 膰e se sinkronizirati</string>
+    <string name="prefs_find_phone">Prona膽i telefon</string>
+    <string name="mi2_prefs_inactivity_warnings_threshold">Prag neaktivnosti (u minutama)</string>
+    <string name="device_hw">Hardware revizija: %1$s</string>
+    <string name="activity_prefs_distance_meters">Dnevni cilj: udaljenost u metrima</string>
+    <string name="dbmanagementactivity_export_confirmation">Stvarno izvozite podatke\? Prethodno izvezeni podaci o aktivnostima (ako ih ima) i postavke bit 膰e prebrisane.</string>
+    <string name="dbmanagementactivity_error_importing_db">Pogre拧ka pri uvozu baze podataka: %1$s</string>
+    <string name="discovery_yes_pair">Upari</string>
+    <string name="activity_type_treadmill">Traka za tr膷anje</string>
+    <string name="select_all">Odaberi sve</string>
+    <string name="freestyle">Slobodni stil</string>
+    <string name="prefs_autoheartrate">Automatski otkucaji srca</string>
+    <string name="charts_legend_heartrate_average">Prosjek otkucaja srca</string>
+    <string name="menuitem_cycles">Pra膰enje ciklusa</string>
+    <string name="czesh">膶e拧ki</string>
+    <string name="activity_prefs_charts">Postavke grafikona</string>
+    <string name="activity_type_badminton">Badminton</string>
+    <string name="activity_type_strength_training">Trening snage</string>
+    <string name="activity_type_unknown">Nepoznata aktivnost</string>
+    <string name="pref_title_connected_advertisement">Vidljivo dok ste povezani</string>
+    <string name="weekstepschart_steps_a_month">Koraka mjese膷no</string>
+    <string name="updatefirmwareoperation_firmware_not_sent">Firmware nije poslan</string>
+    <string name="dbmanagementactivity_exported_to">Izvezeno u: %1$s</string>
+    <string name="dbmanagementactivity_error_exporting_shared">Pogre拧ka pri izvozu preferencija: %1$s</string>
+    <string name="title_activity_vibration">Vibracija</string>
+    <string name="off">Isklju膷eno</string>
+    <string name="activity_type_biking">Vo啪nja biciklom</string>
+    <string name="menuitem_alarm">Alarm</string>
+    <string name="warning">Upozorenje!</string>
+    <string name="pref_title_lower_button_function_double">Donji gumb dvostruko</string>
+    <string name="minutes_mi">min/mi</string>
+    <string name="sony_sound_position_front_right">Sprijeda desno</string>
+    <string name="sony_sound_position_rear_left">Straga lijevo</string>
+    <string name="discovery_bonding_failed_immediately">Povezivanje s %1$s odmah nije uspjelo.</string>
+    <string name="discovery_trying_to_connect_to">Poku拧avam se povezati s: %1$s</string>
+    <string name="discovery_enable_bluetooth">Omogu膰ite Bluetooth za otkrivanje ure膽aja.</string>
+    <string name="discovery_successfully_bonded">Vezano za %1$s.</string>
+    <string name="discovery_pair_title">Upariti s %1$s\?</string>
+    <string name="discovery_pair_question">Odaberite Upari da biste uparili svoje ure膽aje. Ako to ne uspije, poku拧ajte ponovno bez uparivanja.</string>
+    <string name="discovery_dont_pair">Nemoj upariti</string>
+    <string name="_pebble_watch_open_on_phone">Otvori na Android ure膽aju</string>
+    <string name="_pebble_watch_mute">Isklju膷i zvuk</string>
+    <string name="_pebble_watch_reply">Odgovori</string>
+    <string name="controlcenter_start_activity_tracks">Va拧e staze aktivnosti</string>
+    <string name="activity_type_not_measured">Nije mjereno</string>
+    <string name="activity_type_activity">Aktivnost</string>
+    <string name="activity_type_deep_sleep">Duboki san</string>
+    <string name="activity_type_not_worn">Ure膽aj nije no拧en</string>
+    <string name="activity_type_walking">Hodanje</string>
+    <string name="activity_type_swimming">Plivanje</string>
+    <string name="activity_type_indoor_cycling">Vo啪nja biciklom u zatvorenom</string>
+    <string name="activity_type_basketball">Ko拧arka</string>
+    <string name="activity_type_pingpong">Stolni tenis</string>
+    <string name="activity_summaries">Sportske aktivnosti</string>
+    <string name="activity_summary_detail">Pojedinosti o sportskoj aktivnosti</string>
+    <string name="activity_summary_edit_name_title">Uredi oznaku</string>
+    <string name="activity_summary_detail_select_gpx_track">Odaberi GPX stazu</string>
+    <string name="activity_summary_detail_clear_gpx_track">Izbri拧i GPX stazu</string>
+    <string name="activity_summary_yesterday">Ju膷er</string>
+    <string name="activity_error_no_app_for_png">Da biste podijelili ovu snimku zaslona, instalirajte aplikaciju koja mo啪e rukovati slikovnim datotekama.</string>
+    <string name="share">Dijeli</string>
+    <string name="kind_gps_almanac">GPS Almanac</string>
+    <string name="kind_gps_cep">Ispravljanje GPS pogre拧ke</string>
+    <string name="kind_resources">Resursi</string>
+    <string name="kind_watchface">Broj膷anik</string>
+    <string name="devicetype_unknown">Nepoznati ure膽aj</string>
+    <string name="kind_agps_bundle">AGPS Paket</string>
+    <string name="add_test_device">Dodaj testni ure膽aj</string>
+    <string name="devicetype_miband2">Mi Band 2</string>
+    <string name="devicetype_miband3">Mi Band 3</string>
+    <string name="devicetype_miband4">Mi Band 4</string>
+    <string name="devicetype_test">Testiraj ure膽aj</string>
+    <string name="devicetype_amazfit_bip_lite">Amazfit Bip Lite</string>
+    <string name="devicetype_amazfit_cor">Amazfit Cor</string>
+    <string name="devicetype_amazfit_gtr">Amazfit GTR</string>
+    <string name="devicetype_amazfit_bipu">Amazfit Bip U</string>
+    <string name="devicetype_amazfit_bipupro">Amazfit Bip U Pro</string>
+    <string name="devicetype_amazfit_gtr2e">Amazfit GTR 2e</string>
+    <string name="devicetype_amazfit_gts2_mini">Amazfit GTS 2 Mini</string>
+    <string name="devicetype_amazfit_gts2e">Amazfit GTS 2e</string>
+    <string name="devicetype_zepp_e">Zepp E</string>
+    <string name="devicetype_vibratissimo">Vibratissimo</string>
+    <string name="devicetype_um25">UM-25</string>
+    <string name="devicetype_liveview">LiveView</string>
+    <string name="devicetype_hplus">HPlus</string>
+    <string name="devicetype_exrizu_k8">Exrizu K8</string>
+    <string name="devicetype_teclast_h30">Teclast H30</string>
+    <string name="devicetype_xwatch">XWatch</string>
+    <string name="devicetype_mykronoz_zetime">MyKronoz ZeTime</string>
+    <string name="devicetype_id115">ID115</string>
+    <string name="devicetype_watch9">Watch 9</string>
+    <string name="devicetype_watchx">Watch X</string>
+    <string name="devicetype_watchxplus">Watch X Plus</string>
+    <string name="devicetype_roidmi3">Roidmi 3</string>
+    <string name="devicetype_y5">Y5</string>
+    <string name="devicetype_mijia_lywsd02">Mijia Smart Clock</string>
+    <string name="devicetype_tlw64">TLW64</string>
+    <string name="devicetype_pinetime_jf">PineTime (JF Firmware)</string>
+    <string name="devicetype_waspos">Wasp-os</string>
+    <string name="devicetype_smaq2oss">SMA-Q2 OSS</string>
+    <string name="devicetype_fitpro">FitPro</string>
+    <string name="devicetype_sony_wh_1000xm3">Sony WH-1000XM3</string>
+    <string name="devicetype_sony_wf_sp800n">Sony WF-SP800N</string>
+    <string name="choose_auto_export_location">Odaberite lokaciju za izvoz</string>
+    <string name="menuitem_weather">Vrijeme</string>
+    <string name="menuitem_breathing">Disanje</string>
+    <string name="menuitem_timer">Odbrojavanje</string>
+    <string name="menuitem_stress">Stres</string>
+    <string name="menuitem_hr">Broj otkucaja srca</string>
+    <string name="menuitem_findphone">Prona膽i telefon</string>
+    <string name="menuitem_takephoto">Daljinski za kameru</string>
+    <string name="menuitem_alexa">Alexa</string>
+    <string name="menuitem_dnd">DND</string>
+    <string name="menuitem_stopwatch">艩toperica</string>
+    <string name="menuitem_pomodoro">Pomodoro pra膰enje vremena</string>
+    <string name="menuitem_events">Doga膽aji</string>
+    <string name="menuitem_widgets">Widgeti</string>
+    <string name="menuitem_temperature">Temperatura</string>
+    <string name="watch9_time_hours">Sati:</string>
+    <string name="watch9_time_seconds">Sekunde:</string>
+    <string name="watch9_calibration_hint">Postavite vrijeme koje vam se ure膽aj trenutno prikazuje.</string>
+    <string name="watch9_calibration_button">Kalibriraj</string>
+    <string name="title_activity_watch9_pairing">Watch 9 uparivanje</string>
+    <string name="title_activity_watch9_calibration">Watch 9 kalibracija</string>
+    <string name="pref_summary_contextual_arabic">Omogu膰ite ovo za podr拧ku kontekstualnog arapskog</string>
+    <string name="no_data">Nema podataka</string>
+    <string name="preferences_led_color">Boja LED dioda</string>
+    <string name="pref_invalid_frequency_title">Neva啪e膰a frekvenciju</string>
+    <string name="pref_invalid_frequency_message">Molimo unesite frekvenciju izme膽u 87,5 i 108,0</string>
+    <string name="language_and_region_prefs">Postavke jezika i regije</string>
+    <string name="title_activity_notification_filter">Filtar obavijesti</string>
+    <string name="toast_app_must_not_be_selected">Aplikacija se ne smije odabrati za konfiguraciju</string>
+    <string name="toast_notification_filter_saved_successfully">Filtar obavijesti spremljen</string>
+    <string name="filter_mode_none">Ne filtriraj</string>
+    <string name="filter_mode_whitelist">Prika啪i kada su rije膷i sadr啪ane</string>
+    <string name="filter_mode_blacklist">Blokiraj kada su rije膷i sadr啪ane</string>
+    <string name="filter_submode_at_least_one">Najmanje jedna od rije膷i</string>
+    <string name="filter_submode_all">Sve rije膷i</string>
+    <string name="appwidget_text">Zzz</string>
+    <string name="appwidget_sleep_alarm_widget_label">Alarm za spavanje</string>
+    <string name="widget_listing_label">Status i alarmi</string>
+    <string name="widget_set_alarm_after">Postavi alarm nakon:</string>
+    <string name="widget_20_minutes">20 minuta</string>
+    <string name="qhybrid_vibration_strength">Ja膷ina vibracije:</string>
+    <string name="qhybrid_goal_in_steps">Cilj u koracima</string>
+    <string name="qhybrid_use_activity_hand_as_notification_counter">koristite ruku aktivnosti kao broja膷 obavijesti</string>
+    <string name="qhybrid_buttons_overwrite_success">Gumbi su prepisani</string>
+    <string name="qhybrid_buttons_overwrite_error">Pogre拧ka pri prepisivanju gumba</string>
+    <string name="qhybrid_changes_delay_prompt">promjena mo啪e potrajati nekoliko sekundi鈥�</string>
+    <string name="hr_widget_active_minutes">Aktivne minute</string>
+    <string name="pref_title_middle_button_function_short">Srednji gumb kratko</string>
+    <string name="pref_title_lower_button_function_short">Donji gumb kratko</string>
+    <string name="pref_title_lower_button_function_long">Donji gumb dugo</string>
+    <string name="prefs_button_triple_press_action_selection_title">Radnja doga膽aja 3</string>
+    <string name="error_location_enabled_mandatory">Lokacija mora biti uklju膷ena za skeniranje ure膽aja</string>
+    <string name="maxAltitude">Maksimum</string>
+    <string name="Steps">Koraci</string>
+    <string name="activity_summaries_all_activities">Sve aktivnosti</string>
+    <string name="qhybrid_title_apps">Aplikacije</string>
+    <string name="devicetype_amazfit_trex_pro">Amazfit T-Rex Pro</string>
+    <string name="watchface_dialog_widget_presets">Predodre膽eni podaci polo啪aja</string>
+    <string name="watchface_dialog_widget_preset_top">Gore</string>
+    <string name="watchface_dialog_widget_preset_left">Lijevo</string>
+    <string name="watchface_dialog_widget_preset_right">Desno</string>
+    <string name="prefs_game_mode_summary">Samo ako va拧 telefon podr啪ava na膷in igranja</string>
+    <string name="title_activity_notification_management">Postavke obavijesti</string>
+    <string name="check_all_applications">Provjeri sve aplikacije</string>
+    <string name="uncheck_all_applications">Poni拧ti sve aplikacije</string>
+    <string name="fw_upgrade_notice_amazfitcor">Spremate se instalirati %s firmware na va拧 Amazfit Cor.
+\n
+\nObavezno instalirajte .fw datoteku, a nakon toga .res datoteku. Va拧 band 膰e se ponovno pokrenuti nakon instaliranja.
+\n
+\nNapomena: Ne morate instalirati .res ako je potpuno isti kao onaj koji je prethodno instaliran.
+\n
+\nNASTAVITE NA VLASTITU ODGOVORNOST!</string>
+    <string name="fw_upgrade_notice_amazfit_trex">Spremate se instalirati %s firmware na va拧 Amazfit T-Rex.
+\n
+\nObavezno instalirajte .fw datoteku, zatim .res datoteku i na kraju .gps datoteku. Va拧 sat 膰e se ponovno pokrenuti nakon instaliranja .fw datoteke.
+\n
+\nNapomena: Ne morate instalirati .res i .gps ako su ove datoteke potpuno iste kao one prethodno instalirane.
+\n
+\nNASTAVITE NA VLASTITU ODGOVORNOST!</string>
+    <string name="fw_upgrade_notice_amazfitvergel">Spremate se instalirati %s firmware na va拧 Amazfit Verge Lite.
+\n
+\nObavezno instalirajte .fw datoteku, zatim .res datoteku i na kraju .gps datoteku. Va拧 sat 膰e se ponovno pokrenuti nakon instaliranja .fw datoteke.
+\n
+\nNapomena: Ne morate instalirati .res i .gps ako su ove datoteke potpuno iste kao one prethodno instalirane.
+\n
+\nNASTAVITE NA VLASTITU ODGOVORNOST!</string>
+    <string name="fw_upgrade_notice_miband3">Spremate se instalirati %s firmware na va拧 Mi Band 3.
+\n
+\nObavezno instalirajte .fw datoteku, a nakon toga .res datoteku. Va拧 band 膰e se ponovno pokrenuti nakon instaliranja.
+\n
+\nNapomena: Ne morate instalirati .res ako je potpuno isti kao onaj koji je prethodno instaliran.
+\n
+\nNASTAVITE NA VLASTITU ODGOVORNOST!</string>
+    <string name="fw_upgrade_notice_miband6">Spremate se instalirati %s firmware na va拧 Mi Band 6.
+\n
+\nObavezno instalirajte .fw datoteku, a nakon toga .res datoteku. Va拧 band 膰e se ponovno pokrenuti nakon instaliranja.
+\n
+\nNapomena: Ne morate instalirati .res ako je potpuno isti kao onaj koji je prethodno instaliran.
+\n
+\nNASTAVITE NA VLASTITU ODGOVORNOST!</string>
+    <string name="fw_upgrade_notice_amazfitx">Spremate se instalirati %s firmware na va拧 Amazfit X.
+\n
+\nObavezno instalirajte .fw datoteku, a nakon toga .res datoteku. Va拧 band 膰e se ponovno pokrenuti nakon instaliranja.
+\n
+\nNapomena: Ne morate instalirati .res ako je potpuno isti kao onaj koji je prethodno instaliran.
+\n
+\nNASTAVITE NA VLASTITU ODGOVORNOST!</string>
+    <string name="fw_multi_upgrade_notice">Spremate se instalirati %1$s i %2$s firmware, umjesto onih koje su trenutno na va拧em Mi Band.</string>
+    <string name="fw_upgrade_notice_amazfitneo">Spremate se instalirati %s firmware na va拧 Amazfit Neo.
+\n
+\nVa拧 band 膰e se ponovno pokrenuti nakon instaliranja.
+\n
+\nNASTAVITE NA VLASTITU ODGOVORNOST!</string>
+    <string name="pref_title_general_autostartonboot">Pokreni automatski</string>
+    <string name="miband_firmware_unknown_warning">Ovaj firmware nije testiran i mo啪da nije kompatibilan s Gadgetbridgeom.
+\n
+\nOBESHRABLJENI ste da ga fle拧ate!</string>
+    <string name="pref_title_general_autoconnectonbluetooth">Pove啪ite se s Gadgetbridge ure膽ajima kada je Bluetooth uklju膷en</string>
+    <string name="pref_title_general_autoreconnect">Automatsko ponovno povezivanje</string>
+    <string name="pref_title_audio_player">沤eljeni audioplayer</string>
+    <string name="pref_theme_dark">Tamna</string>
+    <string name="pref_title_minimize_priority">Sakrij obavijest Gadgetbridge</string>
+    <string name="pref_header_notifications">Obavijesti</string>
+    <string name="pref_title_notifications_repetitions">Ponavljanja</string>
+    <string name="pref_title_notifications_call">Telefonski pozivi</string>
+    <string name="pref_title_support_voip_calls">Omogu膰i pozive VoIP aplikacija</string>
+    <string name="pref_title_notifications_sms">SMS</string>
+    <string name="pref_title_notifications_timeout">Minimalno vrijeme izme膽u obavijesti</string>
+    <string name="pref_title_notifications_pebblemsg">Pebble poruke</string>
+    <string name="pref_title_whenscreenon">鈥ako膽er kada je zaslon uklju膷en</string>
+    <string name="pref_header_notification_application_settings">Postavke aplikacije</string>
+    <string name="pref_title_notification_use_as">Koristite popis aplikacija za鈥�</string>
+    <string name="pref_title_notification_use_as_deny">Odbij obavijesti odabranih aplikacija</string>
+    <string name="pref_title_notification_use_as_allow">Dopusti obavijesti odabranih aplikacija</string>
+    <string name="pref_title_transliteration">Transliteracija</string>
+    <string name="pref_summary_transliteration">Omogu膰ite ovo ako va拧 ure膽aj nema podr拧ku za font va拧eg jezika</string>
+    <string name="pref_rtl_max_line_length_summary">Produljuje ili skra膰uje retke s desna na lijevo tekst na koje je odvojen</string>
+    <string name="pref_call_privacy_mode_number">Sakrij broj, ali prika啪i ime</string>
+    <string name="pref_call_privacy_mode_complete">Sakrij ime i broj</string>
+    <string name="pref_title_message_privacy_mode">Na膷in privatnosti poruka</string>
+    <string name="pref_message_privacy_mode_off">Prika啪i sav sadr啪aj</string>
+    <string name="pref_message_privacy_mode_complete">Sakrij sav sadr啪aj</string>
+    <string name="pref_header_development">Opcije razvojnog programera</string>
+    <string name="pref_title_pebble_privacy_mode">Na膷in privatnosti</string>
+    <string name="toast_enable_networklocationprovider">Molimo omogu膰ite mre啪nu lokaciju</string>
+    <string name="pref_title_pebble_forceprotocol">Prisilni protokol za obavijesti</string>
+    <string name="pref_summary_pebble_forceprotocol">Ova opcija prisiljava kori拧tenje najnovijeg protokola obavijesti ovisno o verziji firmvera. ZNAJ 艩TO RADI艩!</string>
+    <string name="pref_title_pebble_mtu_limit">Pebble 2/LE GATT MTU ograni膷enje</string>
+    <string name="pref_summary_pebble_mtu_limit">Ako va拧 Pebble 2/Pebble LE ne radi prema o膷ekivanjima, poku拧ajte s ovom postavkom ograni膷iti MTU (va啪e膰i raspon 20鈥�512)</string>
+    <string name="pref_title_pebble_always_ack_pebblekit">Preuranjeno ACK PebbleKit</string>
+    <string name="pref_summary_pebble_enable_bgjs">Kada je omogu膰eno, dopu拧ta broj膷anicima prikaz vremena, podataka o bateriji itd.</string>
+    <string name="pref_summary_expose_hr">Omogu膰uje drugim aplikacijama pristup HR podacima u stvarnom vremenu dok je Gadgetbridge povezan</string>
+    <string name="pref_title_expose_hr">pristup HR-u tre膰e strane u stvarnom vremenu</string>
+    <string name="pref_title_use_custom_font">Koristite prilago膽eni font</string>
+    <string name="pref_display_add_device_fab">Gumb za povezivanje novog ure膽aja</string>
+    <string name="pref_summary_huami_force_new_protocol">Omogu膰ite ako se va拧 ure膽aj vi拧e ne povezuje nakon nadogradnje firmvera</string>
+    <string name="pref_title_unit_system">Jedinice</string>
+    <string name="pref_title_timeformat">Format vremena</string>
+    <string name="pref_title_screentime">Trajanje zaslona</string>
+    <string name="prefs_title_all_day_heart_rate">Cjelodnevno mjerenje otkucaja srca</string>
+    <string name="preferences_hplus_settings">HPlus/Makibes postavke</string>
+    <string name="title_activity_LenovoWatch_calibration">Watch X Plus kalibracija</string>
+    <string name="pref_title_notifications_and_calls_repeat_on_call">Obavijest o ponavljanju poziva</string>
+    <string name="prefs_notifications_and_calls_continious_ring">Obavijest tijekom zvonjenja telefona</string>
+    <string name="pref_summary_notifications_and_calls_enable_misscall">Ponavlja se svake minute</string>
+    <string name="pref_title_notifications_and_calls_repeat_on_misscall">Ponavljaj X minuta</string>
+    <string name="pref_summary_notifications_and_calls_title_reject">Isklju膷eno - zanemari, Uklju膷eno - odbaci</string>
+    <string name="pref_header_device_spec_settings">Postavke ure膽aja</string>
+    <string name="pref_title_sensors_altitude">Kalibracija nadmorske visine</string>
+    <string name="pref_sensors_bp_calibration">Kalibracija krvnog tlaka</string>
+    <string name="prefs_sensors_button_bp_calibration">Kalibracija</string>
+    <string name="prefs_sensors_button_bp_calibration_sum">Pritisnite ovdje za po膷etak kalibracije</string>
+    <string name="power_mode_title">Na膷in napajanja sata</string>
+    <string name="power_mode_normal">Normalno</string>
+    <string name="zetime_title_settings">Postavke za ZeTime</string>
+    <string name="zetime_handmove_display">Pokret ruke</string>
+    <string name="zetime_prefs_inactivity_mo">Ponedjeljak</string>
+    <string name="zetime_prefs_inactivity_fr">Petak</string>
+    <string name="zetime_signaling_none">Tiho</string>
+    <string name="zetime_signaling_beep_twice">Beep dvaput</string>
+    <string name="zetime_signaling_vibrate_beep_once">Vibriraj i dodirni jednom</string>
+    <string name="pref_header_auto_export">Automatski izvoz</string>
+    <string name="pref_summary_auto_export_interval">Izvezi svaki %d sat</string>
+    <string name="installation_failed_">Instalacija nije uspjela</string>
+    <string name="pairing_unable_to_pair_with">Nije mogu膰e upariti s %1$s (%2$s)</string>
+    <string name="pairing_in_progress">Spajanje u tijeku: %1$s (%2$s)</string>
+    <string name="pairing_already_bonded">Ve膰 povezan s %1$s (%2$s), povezivanje鈥�</string>
+    <string name="preferences_category_device_specific_settings">Postavke specifi膷ne za ure膽aj</string>
+    <string name="preferences_miband_settings">Mi Band/Amazfit postavke</string>
+    <string name="horizontal">Horizontalno</string>
+    <string name="vertical">Okomito</string>
+    <string name="discovery_connected_devices_hint">U膷inite svoj ure膽aj vidljivim. Trenutno povezani ure膽aji vjerojatno ne膰e biti otkriveni. Aktivirajte lokaciju (npr. GPS) na Androidu 6+. Onemogu膰ite Privacy Guard za Gadgetbridge jer se mo啪e sru拧iti i ponovno pokrenuti va拧 telefon. Ako se ure膽aj ne prona膽e nakon nekoliko minuta, poku拧ajte ponovno nakon ponovnog pokretanja mobilnog ure膽aja.</string>
+    <string name="watch9_pairing_tap_hint">Kada va拧 sat vibrira, protresite ure膽aj ili pritisnite njegov gumb.</string>
+    <string name="title_activity_sleepmonitor">Nadzor spavanja</string>
+    <string name="pref_write_logfiles">Pisanje datoteka dnevnika</string>
+    <string name="prefs_wearside">Nosite li lijevo ili desno\?</string>
+    <string name="pref_screen_notification_profile_sms">SMS obavijest</string>
+    <string name="pref_header_vibration_settings">Postavke vibracije</string>
+    <string name="pref_screen_notification_profile_generic">Op膰a obavijest</string>
+    <string name="pref_screen_notification_profile_email">Obavijest e-po拧te</string>
+    <string name="pref_screen_notification_profile_incoming_call">Obavijest dolaznog poziva</string>
+    <string name="pref_screen_notification_profile_calendar">Obavijest kalendara</string>
+    <string name="pref_screen_notification_profile_inactivity">Obavijest neaktivnosti</string>
+    <string name="pref_screen_notification_profile_low_power">Upozorenje o niskoj potro拧nji energije</string>
+    <string name="pref_screen_notification_profile_anti_loss">Upozorenje protiv gubitka</string>
+    <string name="prefs_title_heartrate_measurement_interval">Cjelodnevno mjerenje HR</string>
+    <string name="interval_one_minute">jednom u minuti</string>
+    <string name="interval_five_minutes">svakih 5 minuta</string>
+    <string name="interval_ten_minutes">svakih 10 minuta</string>
+    <string name="interval_forty_five_minutes">svakih 45 minuta</string>
+    <string name="interval_one_hour">jednom na sat</string>
+    <string name="stats_title">Zone brzine</string>
+    <string name="title_activity_charts">Aktivnost i spavanje</string>
+    <string name="title_activity_set_alarm">Konfiguracija alarma</string>
+    <string name="reminder_time">Vrijeme</string>
+    <string name="reminder_message">Poruka</string>
+    <string name="reminder_time_once">%1$s, jednom</string>
+    <string name="reminder_time_every_day">%1$s, svaki dan</string>
+    <string name="reminder_time_every_week">%1$s, svaki tjedan</string>
+    <string name="reminder_time_every_month">%1$s, svaki mjesec</string>
+    <string name="reminder_time_every_year">%1$s, svake godine</string>
+    <string name="reminder_once">Jednom</string>
+    <string name="reminder_every_day">Svaki dan</string>
+    <string name="reminder_every_week">Svaki tjedan</string>
+    <string name="reminder_delete_confirm_title">Izbri拧i podsjetnik</string>
+    <string name="reminder_delete_confirm_description">Jeste li sigurni da 啪elite izbrisati podsjetnik\?</string>
+    <string name="reminder_no_free_slots_title">Nema slobodnih mjesta</string>
+    <string name="reminder_no_free_slots_description">Ure膽aj nema slobodnih mjesta za podsjetnike (ukupno mjesta: %1$s)</string>
+    <string name="title_activity_reminder_details">Pojedinosti o podsjetniku</string>
+    <string name="alarm_sun_short">Ned</string>
+    <string name="alarm_mon_short">Pon</string>
+    <string name="alarm_wed_short">Sri</string>
+    <string name="alarm_thu_short">膶et</string>
+    <string name="alarm_fri_short">Pet</string>
+    <string name="alarm_sat_short">Sub</string>
+    <string name="alarm_smart_wakeup">Pametno bu膽enje</string>
+    <string name="alarm_snooze">Odgodi</string>
+    <string name="user_feedback_miband_set_alarms_failed">Do拧lo je do pogre拧ke prilikom postavljanja alarma, poku拧ajte ponovno.</string>
+    <string name="dbaccess_error_executing">Pogre拧ka pri izvr拧avanju \'%1$s\'</string>
+    <string name="controlcenter_start_activitymonitor">Va拧a aktivnost</string>
+    <string name="cannot_connect">Ne mogu se povezati: %1$s</string>
+    <string name="installer_activity_unable_to_find_handler">Nije mogu膰e prona膰i rukovatelja za instalaciju ove datoteke.</string>
+    <string name="pbw_install_handler_unable_to_install">Nije mogu膰e instalirati datu datoteku: %1$s</string>
+    <string name="notif_battery_low_title">Slaba baterija!</string>
+    <string name="notif_battery_low_percent">%1$s preostalo baterije: %2$s%%</string>
+    <string name="notif_battery_low_bigtext_last_charge_time">Zadnje punjenje: %s
+\n</string>
+    <string name="notif_battery_low_bigtext_number_of_charges">Broj punjenja: %s</string>
+    <string name="notif_battery_low">%1$s baterija je prazna</string>
+    <string name="notif_battery_low_extended">%1$s baterija je prazna: %2$s</string>
+    <string name="prefs_charts_tabs">Kartice grafikona</string>
+    <string name="weeksleepchart_today_sleep_description">Spavaj danas, cilj: %1$s</string>
+    <string name="activity_sleepchart_activity_and_sleep">Aktivnost</string>
+    <string name="charts_activity_list">Popis aktivnosti</string>
+    <string name="activity_list_summary_active_steps">Aktivni koraci</string>
+    <string name="activity_list_summary_distance">Udaljenost</string>
+    <string name="activity_list_summary_activities">Aktivnosti</string>
+    <string name="dialog_hide">Sakrij</string>
+    <string name="show_ongoing_activity">Prika啪i sko膷ni prozor aktivnosti u tijeku</string>
+    <string name="lack_of_sleep">Nedostatak sna: %1$s</string>
+    <string name="overslept">Prespavao: %1$s</string>
+    <string name="prefs_sounds">Zvukovi</string>
+    <string name="prefs_sounds_summary">Konfigurirajte kada 膰e ure膽aj bipati</string>
+    <string name="devicestatus_connected">Ure膽aj je povezan</string>
+    <string name="devicestatus_disconnecting">Ure膽aj se prekida!</string>
+    <string name="devicestatus_upload_failed">Prijenos nije uspio</string>
+    <string name="firmware_update_progress">Prijenos je u tijeku
+\n%1d%% pri %.2fkbps (prosje膷no %.2fkbps)
+\nDio %1d od %1d</string>
+    <string name="updating_firmware">Flashanje firmware鈥�</string>
+    <string name="fwapp_install_device_not_ready">Datoteka se ne mo啪e instalirati, ure膽aj nije spreman.</string>
+    <string name="miband_fwinstaller_compatible_version">Kompatibilna verzija</string>
+    <string name="miband_fwinstaller_untested_version">Netestirana verzija!</string>
+    <string name="pbwinstallhandler_app_item">%1$s (%2$s)</string>
+    <string name="updatefirmwareoperation_updateproblem_do_not_reboot">Problem s prijenosom firmwarea. NEMOJTE PONOVNO POKRETATI svoj Mi Band!</string>
+    <string name="chart_steps">Koraci</string>
+    <string name="calories">Kalorije</string>
+    <string name="distance">Udaljenost</string>
+    <string name="clock">Sat</string>
+    <string name="getting_heart_rate">Mjerenje</string>
+    <string name="heart_rate_result">Rezultati mjerenja</string>
+    <string name="movement_intensity">Intenzitet pokreta</string>
+    <string name="no_limit">Bez ograni膷enja</string>
+    <string name="seconds_5">5 sekundi</string>
+    <string name="seconds_10">10 sekundi</string>
+    <string name="minutes_5">5 minuta</string>
+    <string name="minutes_10">10 minuta</string>
+    <string name="liveactivity_live_activity">Aktivnost u啪ivo</string>
+    <string name="weeksteps_today_steps_description">Koraci danas, cilj: %1$s</string>
+    <string name="lack_of_step">Nedostatak koraka: %1$d</string>
+    <string name="average">Prosjek: %1$s</string>
+    <string name="pref_title_dont_ack_transfer">Ne dozvoli ACK prijenos podataka aktivnosti</string>
+    <string name="pref_summary_dont_ack_transfers">Ako nije ACK na bend, podaci o aktivnostima se ne bri拧u. Korisno ako se GB koristi zajedno s drugim aplikacijama.</string>
+    <string name="pref_summary_low_latency_fw_update">To mo啪e pomo膰i na ure膽ajima na kojima lashanje firmvera ne uspije.</string>
+    <string name="live_activity_start_your_activity">Zapo膷nite svoju aktivnost</string>
+    <string name="live_activity_max_heart_rate">Trenuta膷ni/maks. broj otkucaja srca: %1$d/%2$d</string>
+    <string name="abstract_chart_fragment_kind_light_sleep">Lagano spavanje</string>
+    <string name="charts_min_max_heartrate_popup">Najni啪i broj otkucaja srca: %1$d
+\nNajvi拧i broj otkucaja srca: %2$d
+\nIntenzitet pokreta: %3$s</string>
+    <string name="device_not_connected">Nije povezano.</string>
+    <string name="user_feedback_all_alarms_disabled">Svi alarmi su onemogu膰eni</string>
+    <string name="miband_fwinstaller_incompatible_version">Nekompatibilan firmware</string>
+    <string name="fwinstaller_firmware_not_compatible_to_device">Ovaj firmware nije kompatibilan s ure膽ajem</string>
+    <string name="miband_prefs_reserve_alarm_calendar">Alarmi za rezerviranje za nadolaze膰e doga膽aje</string>
+    <string name="miband_prefs_hr_sleep_detection">Koristite senzor otkucaja srca da pobolj拧ate detekciju sna</string>
+    <string name="miband_prefs_device_time_offset_hours">Pomak vremena ure膽aja u satima (za otkrivanje spavanja radnika u smjenama)</string>
+    <string name="prefs_enable_find_phone">Uklju膷ite \'Prona膽i telefon\'</string>
+    <string name="mi2_prefs_button_press_count">Broj pritisaka gumba</string>
+    <string name="mi2_prefs_button_press_broadcast_summary">Poruka emitiranja poslana uz doga膽aj. Parametar `button_id` se automatski dodaje svakoj poruci.</string>
+    <string name="mi2_prefs_button_action_vibrate">Omogu膰i vibraciju trake</string>
+    <string name="mi2_prefs_goal_notification_summary">Band 膰e vibrirati kada se postigne cilj dnevnih koraka</string>
+    <string name="mi2_prefs_display_items">Prika啪i stavke</string>
+    <string name="mi2_prefs_display_items_summary">Odaberite stavke prikazane na zaslonu banda</string>
+    <string name="mi2_prefs_activate_display_on_lift">Aktiviraj prikaz nakon podizanja</string>
+    <string name="mi2_prefs_rotate_wrist_to_switch_info">Zakrenite zape拧膰e za promjenu informacija</string>
+    <string name="mi2_prefs_do_not_disturb">Ne ometaj</string>
+    <string name="mi2_prefs_do_not_disturb_summary">Band ne膰e primati obavijesti dok je aktivan</string>
+    <string name="mi2_prefs_inactivity_warnings">Upozorenja o neaktivnosti</string>
+    <string name="mi2_prefs_do_not_disturb_start">Vrijeme po膷etka</string>
+    <string name="mi2_prefs_do_not_disturb_lift_wrist">Aktivirajte prikaz nakon podizanja tijekom DND-a</string>
+    <string name="mi3_prefs_band_screen_unlock">Otklju膷avanje zaslona banda</string>
+    <string name="mi3_prefs_band_screen_unlock_summary">Prevucite prstom prema gore za otklju膷avanje zaslona banda</string>
+    <string name="mi3_prefs_night_mode">No膰ni na膷in rada</string>
+    <string name="mi3_prefs_night_mode_summary">Automatski smanji svjetlinu zaslona no膰u</string>
+    <string name="lefun_prefs_hydration_reminder_title">Podsjetnik za hidrataciju</string>
+    <string name="lefun_prefs_hydration_reminder_summary">Band 膰e vibrirati kako bi vas podsjetio da pijete vodu</string>
+    <string name="lefun_prefs_hydration_reminder_interval_title">Interval podsjetnika za hidrataciju (u minutama)</string>
+    <string name="lefun_prefs_antilost_title">Bez gubitka</string>
+    <string name="lefun_prefs_antilost_summary">Band 膰e vibrirati ako se va拧 telefon odvoji od banda</string>
+    <string name="lefun_prefs_interface_language_title">Jezik su膷elja</string>
+    <string name="simplified_chinese">Pojednostavljeni kineski</string>
+    <string name="traditional_chinese">Tradicionalni kineski</string>
+    <string name="english">engleski</string>
+    <string name="norwegian_bokmal">Norwegian Bokm氓l</string>
+    <string name="spanish">艩panjolski</string>
+    <string name="russian">Ruski</string>
+    <string name="polish">Poljski</string>
+    <string name="korean">Korejski</string>
+    <string name="japanese">Japanski</string>
+    <string name="dutch">Nizozemski</string>
+    <string name="turkish">Turski</string>
+    <string name="ukrainian">Ukrajinski</string>
+    <string name="arabic">arapski</string>
+    <string name="indonesian">Indonezijski</string>
+    <string name="vietnamese">Vijetnamski</string>
+    <string name="portuguese">Portugalski</string>
+    <string name="romanian">Rumunjski</string>
+    <string name="hungarian">ma膽arski</string>
+    <string name="greek">Gr膷ki</string>
+    <string name="hebrew">Hebrew</string>
+    <string name="swedish">艩vedski</string>
+    <string name="FetchActivityOperation_about_to_transfer_since">O prijenosu podataka od %1$s</string>
+    <string name="waiting_for_reconnect">膶eka se ponovno povezivanje</string>
+    <string name="activity_prefs_about_you">O vama</string>
+    <string name="activity_prefs_year_birth">Godina ro膽enja</string>
+    <string name="activity_prefs_gender">Spol</string>
+    <string name="activity_prefs_height_cm">Visina u cm</string>
+    <string name="activity_prefs_weight_kg">Te啪ina u kg</string>
+    <string name="activity_prefs_step_length_cm">Duljina koraka u cm</string>
+    <string name="activity_prefs_discovery_pairing">Opcije otkrivanja i uparivanja</string>
+    <string name="activity_prefs_chart_max_heart_rate">Maksimalni broj otkucaja srca</string>
+    <string name="activity_prefs_chart_min_heart_rate">Minimalni broj otkucaja srca</string>
+    <string name="pref_charts_range_off">Raspon grafikona postavljen je na tjedan</string>
+    <string name="pref_chart_heartrate_color_orange">Naran膷asta</string>
+    <string name="pref_title_chart_heartrate_color">Boja otkucaja srca</string>
+    <string name="pref_chart_sleep_rolling_24_off">Od podne do podne</string>
+    <string name="activity_prefs_chart_min_steps_per_minute">Minimalni koraci u minuti za otkrivanje aktivnosti</string>
+    <string name="activity_prefs_chart_max_idle_phase_length">Duljina pauze za odvajanje aktivnosti (minuta)</string>
+    <string name="activity_prefs_chart_min_session_length">Minimalna duljina aktivnosti (minuta)</string>
+    <string name="authenticating">Provjera autenti膷nosti</string>
+    <string name="authentication_required">Potrebna je provjera autenti膷nosti</string>
+    <string name="activity_prefs_sleep_duration">沤eljeno trajanje spavanja u satima</string>
+    <string name="device_fw">Firmware verzija: %1$s</string>
+    <string name="error_creating_directory_for_logfiles">Pogre拧ka pri izradi direktorija za datoteke zapisa: %1$s</string>
+    <string name="updatefirmwareoperation_update_in_progress">Flashanje firmware</string>
+    <string name="charts_legend_heartrate">Broj otkucaja srca</string>
+    <string name="live_activity_heart_rate">Broj otkucaja srca</string>
+    <string name="activity_prefs_calories_burnt">Dnevni cilj: potro拧ene kalorije</string>
+    <string name="action_db_management">Upravljanje podacima</string>
+    <string name="dbmanagementactivvity_cannot_access_export_path">Ne mogu pristupiti putu izvoza. Molimo kontaktirajte programere.</string>
+    <string name="dbmanagementactivity_overwrite_database_confirmation">Stvarno prepisati trenutne podatke? Svi va拧i podaci trenuta膷ne aktivnosti (ako ih ima), ure膽aji i postavke 膰e se prepisati.</string>
+    <string name="dbmanagementactivity_import_successful">Uvezeno.</string>
+    <string name="dbmanagementactivity_db_deletion_failed">Brisanje baze podataka nije uspjelo.</string>
+    <string name="activity_db_management_autoexport_explanation">Lokacija automatskog izvoza baze podataka postavljena je na:</string>
+    <string name="activity_db_management_autoexport_label">Automatski izvoz</string>
+    <string name="activity_DB_ExportButton">Izvoz podataka</string>
+    <string name="activity_DB_import_button">Uvoz podataka</string>
+    <string name="activity_DB_test_export_button">Pokreni automatski izvoz sada</string>
+    <string name="activity_DB_test_export_message">Izvoz baze podataka鈥�</string>
+    <string name="activity_DB_empty_button">Prazna baza podataka</string>
+    <string name="activity_db_management_empty_DB">Prazna baza podataka</string>
+    <string name="activity_db_management_empty_db_warning">Upozorenje! Pritiskom na ovaj gumb izbrisat 膰ete svoju bazu podataka i po膷eti ispo膷etka.</string>
+    <string name="Cancel">Odustani</string>
+    <string name="ok">U redu</string>
+    <string name="activity_DB_clean_export_directory_warning_message">Stvarno izbrisati datoteke u direktoriju za izvoz/uvoz\?</string>
+    <string name="activity_db_management_clean_export_directory_text">Izvezenim datotekama u direktoriju za izvoz/uvoz dostupna je svaka aplikacija na va拧em ure膽aju. Mo啪da biste 啪eljeli ukloniti ove datoteke nakon sinkronizacije ili sigurnosne kopije. Obavezno napravite sigurnosnu kopiju prije nego 拧to ih izbri拧ete. GPX datoteke, poddirektoriji i automatski izvezena datoteka baze podataka (ako postoje) ne膰e se izbrisati. Put do direktorija za izvoz/uvoz je:</string>
+    <string name="title_activity_pebble_pairing">Pebble uparivanje</string>
+    <string name="pebble_pairing_hint">Na va拧em Android ure膽aju pojavit 膰e se dijalo拧ki okvir za uparivanje. Ako ne, pogledajte u ladicu s obavijestima i prihvatite zahtjev za uparivanje. Prihvatite to i na svom Pebbleu nakon toga.</string>
+    <string name="weather_notification_label">Provjerite je li ovaj izgled omogu膰en u aplikaciji Weather Notification da biste dobili informacije o vremenu na va拧em Pebbleu.
+\n
+\nOvdje nije potrebna konfiguracija.
+\n
+\nMo啪ete omogu膰iti sistemsku aplikaciju za vremensku prognozu va拧eg Pebblea iz upravljanja aplikacijom.
+\n
+\nPodr啪ani broj膷anici 膰e automatski prikazati vrijeme.</string>
+    <string name="activity_web_view">Aktivnost web-prikaza</string>
+    <string name="StringUtils_sender">(%1$s)</string>
+    <string name="find_device_you_found_it">Prona拧li ste!</string>
+    <string name="find_lost_device_you_found_it">Prona拧ao sam!</string>
+    <string name="miband2_prefs_timeformat">Mi2: format vremena</string>
+    <string name="mi2_fw_installhandler_fw53_hint">Morate instalirati verziju %1$s prije instaliranja ovog firmvera!</string>
+    <string name="mi2_enable_text_notifications">Tekstualne obavijesti</string>
+    <string name="mi2_dnd_off">Isklju膷eno</string>
+    <string name="mi3_night_mode_sunset">Pri zalasku sunca</string>
+    <string name="mi2_dnd_automatic">Automatski (prepoznavanje spavanja)</string>
+    <string name="mi2_dnd_scheduled">Zakazano (vremenski interval)</string>
+    <string name="maximum_duration">Trajanje</string>
+    <string name="prefs_fm_preset_instructions">Dugo pritisnite gumb za spremanje unaprijed postavljenih postavki</string>
+    <string name="prefs_fm_presets_presets">Unaprijed postavljene postavke</string>
+    <string name="widget_5_minutes">5 minuta</string>
+    <string name="qhybrid_offset_timezone">pomaknite vremensku zonu za</string>
+    <string name="error_exporting_device_preferences">Pogre拧ka pri izvozu postavki specifi膷nih za ure膽aj</string>
+    <string name="ignore_bonded_devices">Zanemari povezane ure膽aje</string>
+    <string name="minAltitude">Minimum</string>
+    <string name="laps">Krugovi</string>
+    <string name="meters_second">m/s</string>
+    <string name="prefs_events_forwarding_fellsleep">U snu</string>
+    <string name="watchface_dialog_widget_preset_bottom">Dolje</string>
+    <string name="watchface_setting_wrist_flick_duration">Trajanje (u ms)</string>
+    <string name="pref_header_sony_ambient_sound_control">Upravljanje ambijentalnim zvukom</string>
+    <string name="sony_ambient_sound_off">Isklju膷eno</string>
+    <string name="sony_ambient_sound_noise_cancelling">Poni拧tavanje buke</string>
+    <string name="sony_ambient_sound_wind_noise_reduction">Smanjenje buke vjetra</string>
+    <string name="sony_equalizer_preset_excited">Uzbu膽eno</string>
+    <string name="pref_header_other">Ostalo</string>
+    <string name="pref_header_system">Sustav</string>
+    <string name="pref_header_equalizer">Ekvilizator</string>
+    <string name="pref_title_ping_tone">Ping ton</string>
+    <string name="pref_applications_settings">Popis aplikacija</string>
+    <string name="pref_title_development_miaddr">Mi Band adresa</string>
+    <string name="pref_title_pebble_settings">Postavke oblutaka</string>
+    <string name="discovery_need_to_enter_authkey">Ovom ure膽aju je potreban tajni klju膷 za autentifikaciju, dugo pritisnite ure膽aj da biste ga unijeli. Pro膷itajte wiki.</string>
+    <string name="discovery_note">Napomena:</string>
+    <string name="busy_task_fetch_activity_data">Dohva膰anje podataka o aktivnostima</string>
+    <string name="sleep_activity_date_range">Od %1$s do %2$s</string>
+    <string name="pref_screen_vibration_profile">Profil vibracije</string>
+    <string name="vibration_profile_staccato">Staccato</string>
+    <string name="interval_fifteen_minutes">svakih 15 minuta</string>
+    <string name="interval_thirty_minutes">svakih 30 minuta</string>
+    <string name="user_feedback_miband_set_alarms_ok">Alarmi poslani na ure膽aj.</string>
+    <string name="blood_pressure">Krvni tlak</string>
+    <string name="live_activity_steps_history">Povijest koraka</string>
+    <string name="live_activity_current_steps_per_minute">Trenuta膷ni koraci/min</string>
+    <string name="live_activity_total_steps">Ukupno koraka</string>
+    <string name="live_activity_steps_per_minute_history">Povijest koraka u minuti</string>
+    <string name="you_did_not_sleep">Niste spavali</string>
+    <string name="automatic">Automatski</string>
+    <string name="pref_title_charts_range">Raspon grafikona</string>
+    <string name="pref_charts_range_on">Raspon grafikona postavljen je na mjesec</string>
+    <string name="pref_title_upper_button_function_double">Gornji gumb dvostruko</string>
+    <string name="pref_summary_use_custom_font">Omogu膰ite ovo ako va拧 ure膽aj ima prilago膽eni firmware font za emoji podr拧ku</string>
+    <string name="pref_title_huami_force_new_protocol">Novi protokol za autentifikaciju</string>
+    <string name="right">Desno</string>
+    <string name="devicetype_amazfit_x">Amazfit X</string>
+    <string name="devicetype_no1_f1">No.1 F1</string>
+    <string name="about_links">Poveznice</string>
+    <string name="ascentSeconds">Uzlazno</string>
+    <string name="devicetype_bose_qc35">Bose QC35</string>
+    <string name="ft">ft</string>
+    <string name="mi_h">mi/h</string>
+    <string name="qhybrid_title_apps_management">Upravljanje aplikacijama</string>
+    <string name="pref_summary_canned_messages_dismisscall">Odbacite pozive sa sata SMS porukom</string>
+    <string name="qhybrid_calibration_100_steps">100 koraka</string>
+    <string name="pref_title_equalizer_soft">Meko</string>
+    <string name="sony_equalizer_band_2500">2.500</string>
+    <string name="sony_equalizer_band_6300">6.300</string>
+    <string name="watchface_dialog_widget_update_timeout">Vrijeme 膷ekanja a啪uriranja u minutama</string>
+    <string name="watchface_dialog_widget_timeout_hide_text">Sakrij tekst nakon isteka vremena</string>
+    <string name="menuitem_mutephone">Be拧umno</string>
+    <string name="qhybrid_time_shift">vremenski pomak</string>
+    <string name="qhybrid_overwrite_buttons">gumbi za prepisivanje</string>
+    <string name="gpx_receiver_activity_title">Gadgetbridge GPX prijemnika</string>
+    <string name="gpx_receiver_files_received">Primljene GPX datoteke:</string>
+    <string name="gpx_receiver_overwrite_some_files">Neke datoteke ve膰 postoje. Prepisati\?</string>
+    <string name="pref_check_permission_status">Provjerite status dopu拧tenja</string>
+    <string name="cm">cm</string>
+    <string name="fossil_hr_edit_action">Ure膽ivanje radnje</string>
+    <string name="watchface_dialog_widget_x_coordinate">Koordinata X (maks. 240)</string>
+    <string name="activity_type_hiking">Pje拧a膷enje</string>
+    <string name="pref_title_middle_button_function_long">Srednji gumb dugo</string>
+    <string name="pref_title_middle_button_function_double">Srednji gumb dvostruko</string>
+    <string name="permission_granting_mandatory">Sva su ta dopu拧tenja potrebna i mo啪e do膰i do nestabilnosti ako se ne daju</string>
+    <string name="pref_check_permission_status_summary">Provjerite i zatra啪ite nedostaju膰a dopu拧tenja 膷ak i kada mo啪da nisu potrebna odmah. Onemogu膰ite ovo samo ako va拧i ure膽aji zapravo ne podr啪avaju nijednu od ovih zna膷ajki. Nedavanje dopu拧tenja mo啪e uzrokovati probleme!</string>
+    <string name="sports_activity_quick_filter_7days">7 dana</string>
+    <string name="sports_activity_quick_filter_30days">30 dana</string>
+    <string name="sports_activity_quick_filter_select">Vremensko razdoblje</string>
+    <string name="breaststroke">Prsno</string>
+    <string name="backstroke">Le膽no</string>
+    <string name="watchface_widget_type_date">Datum</string>
+    <string name="watchface_widget_type_weather">Vrijeme</string>
+    <string name="qhybrid_title_watchface_designer">Dizajner broj膷anika</string>
+    <string name="watchface_setting_display_refresh_full">Potpuno osvje啪avanje (u minutama)</string>
+    <string name="watchface_widget_type_chance_rain">Mogu膰a ki拧a</string>
+    <string name="pref_title_equalizer_trebble">Poja膷anje visokih tonova</string>
+    <string name="prefs_dolby_mode">Dolby na膷in rada</string>
+    <string name="prefs_equalizer">Ekvilizator</string>
+    <string name="prefs_pressure_relief">Opu拧tanje tlaka uz ambijentalni zvuk</string>
+    <string name="sony_sound_position_off">Isklju膷eno</string>
+    <string name="menuitem_pai">PAI</string>
+    <string name="pref_pebble_privacy_mode_off">Uobi膷ajene obavijesti</string>
+    <string name="devicestatus_connecting">Ure膽aj se povezuje</string>
+    <string name="pref_title_setup_bt_pairing">Omogu膰i Bluetooth uparivanje</string>
+    <string name="menuitem_flashlight">Svjetiljka</string>
+    <string name="pref_summary_connected_advertisement">膶ini ure膽aj vidljivim putem Bluetootha 膷ak i kada je povezan</string>
+    <string name="chart_no_active_data">Nijedna aktivnost nije otkrivena.</string>
+    <string name="activity_data_management_directory_content_title">Izvoz/uvoz sadr啪aja direktorija</string>
+    <string name="dbmanagementactivity_export_finished">Brisanje je zavr拧eno</string>
+    <string name="mi2_prefs_button_press_broadcast">Emitirajte poruku za slanje</string>
+    <string name="menuitem_sleep">Spavanje</string>
+    <string name="activity_type_climbing">Penjanje</string>
+    <string name="activity_type_elliptical_trainer">Elipti膷ki trener</string>
+    <string name="activity_type_jump_roping">Preskakanje u啪eta</string>
+    <string name="activity_type_soccer">Nogomet</string>
+    <string name="activity_type_rowing_machine">Stroj za veslanje</string>
+    <string name="activity_type_cricket">Kriket</string>
+    <string name="activity_summary_detail_editing_gpx_track">Ure膽ivanje povezane GPX staze</string>
+    <string name="activity_summary_today">Danas</string>
+    <string name="activity_type_exercise">Vje啪ba</string>
+    <string name="activity_error_no_app_for_gpx">Da biste vidjeli pra膰enje aktivnosti, instalirajte aplikaciju koja mo啪e rukovati GPX datotekama.</string>
+    <string name="reset_index">Poni拧ti datum dohva膰anja</string>
+    <string name="kind_firmware">Firmware</string>
+    <string name="kind_invalid">Neva啪e膰i podaci</string>
+    <string name="kind_font">Font</string>
+    <string name="kind_gps">GPS Firmware</string>
+    <string name="devicetype_miband5">Mi Band 5</string>
+    <string name="devicetype_amazfit_neo">Amazfit Neo</string>
+    <string name="devicetype_amazfit_bip">Amazfit Bip</string>
+    <string name="devicetype_amazfit_cor2">Amazfit Cor 2</string>
+    <string name="devicetype_amazfit_trex">Amazfit T-Rex</string>
+    <string name="widget_settings_select_device_title">Odaberi ure膽aj</string>
+    <string name="widget_10_minutes">10 minuta</string>
+    <string name="widget_1_hour">1 sat</string>
+    <string name="watch_not_connected">Sat nije povezan</string>
+    <string name="hr_widget_calories">Kalorije</string>
+    <string name="hr_appname_wellness">Wellness</string>
+    <string name="hr_appname_workout">Vje啪banje</string>
+    <string name="hr_appname_stopwatch">艩toperica</string>
+    <string name="hr_appname_commute">Putovanje na posao</string>
+    <string name="pref_title_upper_button_function_short">Gornji gumb kratko</string>
+    <string name="pref_title_upper_button_function_long">Gornji gumb dugo</string>
+    <string name="prefs_button_single_press_action_selection_title">Radnja doga膽aja 1</string>
+    <string name="prefs_button_double_press_action_selection_title">Radnja doga膽aja 2</string>
+    <string name="prefs_button_variable_actions">Detaljne postavke pritiska na gumb</string>
+    <string name="discover_unsupported_devices">Otkrijte nepodr啪ane ure膽aje</string>
+    <string name="activity_detail_show_gps_label">Prika啪i GPS stazu</string>
+    <string name="gps_track">GPS staza</string>
+    <string name="no">Ne</string>
+    <string name="prefs_fake_ring_duration">La啪no kontinuirano zvono</string>
+    <string name="qhybrid_summary_file_management">Prijenos i preuzimanje datoteka</string>
+    <string name="sony_automatic_power_off_off">Ne isklju膷iti</string>
+    <string name="sony_automatic_power_off_5_min">5 minuta</string>
+    <string name="sony_button_mode_left">Na膷in gumba (lijevo)</string>
+    <string name="sony_button_mode_right">Na膷in gumba (desno)</string>
+    <string name="sony_button_mode_off">Isklju膷eno</string>
+    <string name="activity_type_running">Tr膷anje</string>
+    <string name="devicetype_amazfit_bips">Amazfit Bip S</string>
+    <string name="devicetype_amazfit_gtr2">Amazfit GTR 2</string>
+    <string name="devicetype_amazfit_gts2">Amazfit GTS 2</string>
+    <string name="title_activity_discovery">Otkrivanje ure膽aja</string>
+    <string name="pref_theme_black_background">Koristite crnu pozadinu u tamnoj temi</string>
+    <string name="app_install_info">Spremate se instalirati sljede膰u aplikaciju:
+\n
+\n%1$s
+\nVerzija %2$s od %3$s
+\n</string>
+    <string name="watchface_install_info">Spremate se instalirati sljede膰i broj膷anik:
+\n
+\n%1$s
+\nVerzija %2$s od %3$s
+\n</string>
+    <string name="n_a">N/A</string>
+    <string name="initialized">Pokrenuto</string>
+    <string name="appversion_by_creator">%1$s od %2$s</string>
+    <string name="discovery_stop_scanning">Zaustavi skeniranje</string>
+    <string name="discovery_start_scanning">Pokreni otkrivanje</string>
+    <string name="action_discover">Pove啪ite novi ure膽aj</string>
+    <string name="device_with_rssi">%1$s (%2$s)</string>
+    <string name="android_pairing_hint">Za uparivanje ure膽aja koristite dijalo拧ki okvir Android Bluetooth uparivanja.</string>
+    <string name="title_activity_mi_band_pairing">Uparite svoj Mi Band</string>
+    <string name="pairing">Uparivanje sa %s鈥�</string>
+    <string name="pairing_creating_bond_with">Stvaranje veze s %1$s (%2$s)</string>
+    <string name="title_activity_android_pairing">Uparite ure膽aj</string>
+    <string name="miband_pairing_tap_hint">Kada va拧 Mi Band vibrira i treperi, dodirnite ga nekoliko puta zaredom.</string>
+    <string name="appinstaller_install">Instaliraj</string>
+    <string name="discovery_entered_invalid_authkey">Tajni klju膷 autentifikacije koji ste unijeli nije va啪e膰i! Dugo pritisnite na ure膽aju za ure膽ivanje.</string>
+    <string name="vibration_profile_medium">Srednje</string>
+    <string name="vibration_profile_long">Dugo</string>
+    <string name="vibration_profile_waterdrop">Kapljica</string>
+    <string name="vibration_profile_ring">Zvono</string>
+    <string name="vibration_profile_alarm_clock">Budilica</string>
+    <string name="miband_prefs_vibration">Vibracija</string>
+    <string name="pref_screen_notification_profile_missed_call">Obavijest propu拧tenog poziva</string>
+    <string name="pref_screen_notification_profile_generic_chat">Chat</string>
+    <string name="pref_screen_notification_profile_generic_navigation">Navigacija</string>
+    <string name="pref_screen_notification_profile_generic_social">Dru拧tvena mre啪a</string>
+    <string name="controlcenter_start_configure_alarms">Konfiguracija alarma</string>
+    <string name="title_activity_alarm_details">Pojedinosti o alarmu</string>
+    <string name="chart_no_data_synchronize">Nema podataka. Sinkronizirati ure膽aj\?</string>
+    <string name="chart_get_active_and_synchronize">Obavite neke aktivnosti i sinkronizirajte ure膽aj.</string>
+    <string name="user_feedback_miband_activity_data_transfer">O prijenosu %1$s podataka po膷ev拧i od %2$s</string>
+    <string name="miband_prefs_fitness_goal">Dnevni cilj koraka</string>
+    <string name="devicestatus_upload_starting">Po膷inje prijenos</string>
+    <string name="devicestatus_upload_started">Prijenos je zapo膷eo</string>
+    <string name="devicestatus_disconnected">Ure膽aj je prekinuo vezu!</string>
+    <string name="dateformat_date_time"><![CDATA[Vrijeme i datum]]></string>
+    <string name="prefs_disconnect_notification">Obavijest o prekidu veze</string>
+    <string name="prefs_find_phone_duration">Trajanje zvona u sekundama</string>
+    <string name="miband2_prefs_dateformat">Format datuma</string>
+    <string name="dateformat_time">Vrijeme</string>
+    <string name="mi2_prefs_button_actions">Radnje gumba</string>
+    <string name="mi2_prefs_button_actions_summary">Odredite radnje pritiska na gumb</string>
+    <string name="mi2_prefs_button_press_count_summary">Broj pritisaka gumba za pokretanje Doga膽aja 1. Naknadni isti broj pritisaka stvaraju Doga膽aj 2 i tako dalje.</string>
+    <string name="mi2_prefs_button_action">Omogu膰i radnju gumba</string>
+    <string name="mi2_prefs_button_action_summary">Omogu膰i radnju za odre膽eni broj pritisaka na gumb</string>
+    <string name="mi2_prefs_button_action_vibrate_summary">Omogu膰i vibraciju trake na aktiviranoj radnji gumba</string>
+    <string name="mi2_prefs_button_press_count_max_delay">Maksimalno ka拧njenje izme膽u pritisaka</string>
+    <string name="mi2_prefs_button_press_count_max_delay_summary">Maksimalno ka拧njenje izme膽u pritiskanja gumba u milisekundama</string>
+    <string name="mi2_prefs_goal_notification">Obavijest o cilju</string>
+    <string name="appmanager_installed_watchfaces">Instalirani broj膷anici</string>
+    <string name="fw_upgrade_notice_amazfitbip_lite">Spremate se instalirati %s firmware na va拧 Amazfit Bip Lite.
+\n
+\nObavezno instalirajte .fw datoteku, a nakon toga .res datoteku. Va拧 sat 膰e se ponovno pokrenuti nakon instaliranja .fw datoteke.
+\n
+\nNapomena: Ne morate instalirati .res ako je potpuno isti kao onaj koji je prethodno instaliran.
+\n
+\nNASTAVITE NA VLASTITU ODGOVORNOST!</string>
+    <string name="fw_upgrade_notice_amazfitgtr">Spremate se instalirati %s firmware na va拧 Amazfit GTR.
+\n
+\nObavezno instalirajte .fw datoteku, zatim .res datoteku i na kraju .gps datoteku. Va拧 sat 膰e se ponovno pokrenuti nakon instaliranja .fw datoteke.
+\n
+\nNapomena: Ne morate instalirati .res i .gps ako su ove datoteke potpuno iste kao one prethodno instalirane.
+\n
+\nNASTAVITE NA VLASTITU ODGOVORNOST!</string>
+    <string name="pref_title_relax_firmware_checks">Omogu膰ite ovo ako 啪elite flashati firmware koji nije namijenjen va拧em ure膽aju (na vlastitu odgovornost)</string>
+    <string name="pref_display_add_device_fab_off">Vidljivo samo ako nije dodan nijedan ure膽aj</string>
+    <string name="miband_pairing_using_dummy_userdata">Nema valjanih korisni膷kih podataka, za sada se koriste la啪ni korisni膷ki podaci.</string>
+    <string name="candidate_item_device_image">Slika ure膽aja</string>
+    <string name="initializing">Pokretanje</string>
+    <string name="vibration_profile_short">Kratko</string>
+    <string name="vibration_try">Isprobajte</string>
+    <string name="stats_x_axis_label">Ukupno minuta</string>
+    <string name="stats_y_axis_label">Koraci u minuti</string>
+    <string name="control_center_find_lost_device">Prona膽i izgubljeni ure膽aj</string>
+    <string name="control_center_cancel_to_stop_vibration">Otka啪ite da biste zaustavili vibraciju.</string>
+    <string name="devicestatus_upload_completed">Prijenos je dovr拧en</string>
+    <string name="devicestatus_upload_validating">U膷itavanje se provjerava</string>
+    <string name="devicestatus_upload_aborted">Prijenos je prekinut!</string>
+    <string name="bip_prefs_shortcuts">Pre膷aci</string>
+    <string name="bip_prefs_shotcuts_summary">Odaberite pre膷ace na zaslonu trake</string>
+    <string name="pref_title_force_white_color_scheme">Nametni crnu na bijeloj shemi boja</string>
+    <string name="german">Njema膷ki</string>
+    <string name="italian">Talijanski</string>
+    <string name="weeksleepchart_sleep_a_month">Spavanje mjese膷no</string>
+    <string name="pref_title_chart_sleep_rolling_24_hour">Raspon spavanja</string>
+    <string name="pref_chart_sleep_rolling_24_on">Posljednja 24 sata</string>
+    <string name="activity_prefs_activetime_minutes">Dnevni cilj: aktivno vrijeme u minutama</string>
+    <string name="pref_title_pebble_health_store_raw">Spremi neobra膽eni zapis u bazu podataka</string>
+    <string name="pref_summary_pebble_health_store_raw">Pohranjuje podatke \"kakvi jesu\", pove膰avaju膰i kori拧tenje baze podataka kako bi se omogu膰ila kasnija interpretacija.</string>
+    <string name="dbmanagementactivity_import_data_title">Uvesti podatke\?</string>
+    <string name="dbmanagementactivity_old_activity_db_deletion_failed">Brisanje baze podataka stare aktivnosti nije uspjelo.</string>
+    <string name="dbmanagementactivity_overwrite">Prepi拧i</string>
+    <string name="set">Postavi</string>
+    <string name="activity_DB_ShowContentButton">Prika啪i sadr啪aj direktorija za izvoz/uvoz</string>
+    <string name="activity_db_management_clean_export_directory_label">Izbri拧ite datoteke u direktoriju za izvoz/uvoz</string>
+    <string name="activity_DB_clean_export_directory_warning_title">沤elite li izbrisati datoteke u direktoriju za izvoz/uvoz\?</string>
+    <string name="unit_metric">Metri膷ki</string>
+    <string name="unit_imperial">Imperial</string>
+    <string name="timeformat_24h">24H</string>
+    <string name="timeformat_am_pm">AM/PM</string>
+    <string name="pref_screen_notification_profile_alarm_clock">Alarm</string>
+    <string name="activity_type_light_sleep">Lagano spavanje</string>
+    <string name="devicetype_pebble">Pebble</string>
+    <string name="devicetype_miband">Mi Band</string>
+    <string name="devicetype_roidmi">Roidmi</string>
+    <string name="preferences_fm_frequency">FM frekvencija</string>
+    <string name="toast_notification_filter_words_empty_hint">Molimo unesite barem jednu rije膷</string>
+    <string name="filter_mode">Na膷in filtra</string>
+    <string name="mode_configuration">Na膷in konfiguracije</string>
+    <string name="save_configuration">Spremi konfiguraciju</string>
+    <string name="appwidget_not_connected">Nije povezano, alarm nije postavljen.</string>
+    <string name="add_widget">Dodaj widget</string>
+    <string name="appwidget_setting_alarm">Postavljanje alarma za %1$02d:%2$02d</string>
+    <string name="about_version">Verzija %s</string>
+    <string name="about_activity_title_generic">O Gadgetbridge</string>
+    <string name="about_description_generic">Zamjena za libre bez oblaka bez kopiranja za zatvorene aplikacije za Android gadgete od dobavlja膷a.</string>
+    <string name="about_core_team_title">Osnovni tim (po redoslijedu doprinosa prvog koda)</string>
+    <string name="qhybrid_title_watchface">Konfiguracija broj膷anika</string>
+    <string name="button_watchface_select_image">Promijeni sliku pozadine</string>
+    <string name="button_watchface_add_widget">Dodaj widget</string>
+    <string name="sony_equalizer_preset_bass_boost">Poja膷anje basa</string>
+    <string name="sony_equalizer_preset_speech">Govor</string>
+    <string name="sony_equalizer_preset_manual">Ru膷no</string>
+    <string name="sony_automatic_power_off_3_hour">3 sata</string>
+    <string name="menuitem_barometer">Barometar</string>
+    <string name="watch9_time_minutes">Minute:</string>
+    <string name="qhybrid_second_timezone_offset_relative_to_utc">drugi pomak vremenske zone u odnosu na UTC</string>
+    <string name="devicetype_casiogbx100">Casio GBX-100</string>
+    <string name="mi2_enable_text_notifications_summary"><![CDATA[Firmware >= 1.0.1.28 i Mili_pro.ft* moraju biti instalirani.]]></string>
+    <string name="devicetype_miscale2">Mi Scale 2</string>
+    <string name="devicetype_itag">iTag</string>
+    <string name="fossil_hr_synced_activity_data">Sinkronizirani podaci o aktivnostima</string>
+    <string name="fossil_hr_auth_failed">Provjera autenti膷nosti nije uspjela, ograni膷ena funkcionalnost</string>
+    <string name="devicetype_galaxybuds">Galaxy Buds</string>
+    <string name="prefs_touch_lock">Zaklju膷avanje dodirom</string>
+    <string name="discover_unsupported_devices_description">Omogu膰avanjem ove opcije prikazat 膰e se svi otkriveni bluetooth ure膽aji prilikom skeniranja. Kratki dodir kopirat 膰e naziv ure膽aja i mac adresu u me膽uspremnik. Dugi pritisak pokrenut 膰e dijalo拧ki okvir `Dodaj testni ure膽aj`. Mo啪e prouzro膷iti mogu膰e probleme zamrzavanja aplikacije.</string>
+    <string name="pref_media_play">Reprodukcija</string>
+    <string name="pref_media_pause">Pauza</string>
+    <string name="about_hash">Uredi %s</string>
+    <string name="about_additional_device_support">Podr拧ka za dodatne ure膽aje</string>
+    <string name="companiondevice_pairing">Uparivanje CompanionDevice</string>
+    <string name="minSpeed">Minimum</string>
+    <string name="minPace">Najsporiji tempo</string>
+    <string name="maxPace">Najbr啪i tempo</string>
+    <string name="prefs_events_forwarding_wokeup">Prilikom bu膽enja</string>
+    <string name="yes">Da</string>
+    <string name="activity_summaries_statistics">Statistika</string>
+    <string name="sports_activity_quick_filter_this_week">Ovaj tjedan</string>
+    <string name="sports_activity_quick_filter_last_week">Prethodni tjedan</string>
+    <string name="watchface_setting_display_refresh_partial">Djelomi膷no osvje啪avanje (u minutama)</string>
+    <string name="watchface_setting_power_saving_hands">Onemogu膰ite pokrete ruku dok su izvan zape拧膰a</string>
+    <string name="devicetype_galaxybuds_live">Galaxy Buds Live</string>
+    <string name="nothing_prefs_inear_summary">Reproduciraj/pauziraj glazbu ovisno o tome nosi拧 li slu拧alice</string>
+    <string name="pref_title_equalizer_bass_boost">Poja膷anje basa</string>
+    <string name="pref_title_equalizer_dynamic">Dinami膷no</string>
+    <string name="pref_title_equalizer_clear">膶isto</string>
+    <string name="prefs_equalizer_summary">Omogu膰i ili onemogu膰i ekvilizator</string>
+    <string name="prefs_touch_lock_summary">Onemogu膰i dodirne doga膽aje</string>
+    <string name="prefs_galaxy_buds_experimental">Eksperimentalno</string>
+    <string name="prefs_ambient_volume">Ambijentalna glasno膰a</string>
+    <string name="prefs_ambient_voice_focus">Glasovni fokus</string>
+    <string name="prefs_left">Lijevo</string>
+    <string name="devicetype_vesc">VESC</string>
+    <string name="about_contributors">Suradnici</string>
+    <string name="km">km</string>
+    <string name="devicetype_nothingear1">Nothing Ear (1)</string>
+    <string name="medley">Mje拧ovito</string>
+    <string name="seconds_km">s/km</string>
+    <string name="pref_title_contextual_arabic">Kontekstualni arapski</string>
+    <string name="devicetype_amazfit_gts">Amazfit GTS</string>
+    <string name="devicetype_makibes_hr3">Makibes HR3</string>
+    <string name="devicetype_q8">Q8</string>
+    <string name="devicetype_makibes_f68">Makibes F68</string>
+    <string name="activity_type_yoga">Joga</string>
+    <string name="activity_type_swimming_openwater">Plivanje (otvorena voda)</string>
+    <string name="abstract_chart_fragment_kind_not_worn">Nije no拧eno</string>
+    <string name="abstract_chart_fragment_kind_activity">Aktivnost</string>
+    <string name="overstep">Prekorak: %1$d</string>
+    <string name="minutes_30">30 minuta</string>
+    <string name="seconds_20">20 sekundi</string>
+    <string name="seconds_30">30 sekundi</string>
+    <string name="heart_rate">Broj otkucaja srca</string>
+    <string name="battery">Baterija</string>
+    <string name="updatefirmwareoperation_metadata_updateproblem">Problem s prijenosom metapodataka firmwarea</string>
+    <string name="updatefirmwareoperation_update_complete">Instalacija firmwarea dovr拧ena</string>
+    <string name="pbwinstallhandler_incorrect_hw_revision">Nepodudaranje revizije hardvera!</string>
+    <string name="notif_export_failed_title">Izvoz baze podataka nije uspio! Provjerite svoje postavke.</string>
+    <string name="pbw_install_handler_hw_revision_mismatch">Nije mogu膰e instalirati dati firmware: ne odgovara reviziji hardvera va拧eg Pebblea.</string>
+    <string name="pref_title_notification_filter">DND</string>
+    <string name="pref_summary_minimize_priority_off">Ikona u statusnoj traci i obavijest na zaklju膷anom zaslonu se prikazuju</string>
+    <string name="pref_title_language">Jezik</string>
+    <string name="pref_theme_system">Sustav</string>
+    <string name="pref_theme_light">Svjetla</string>
+    <string name="pref_title_datetime_syctimeonconnect">Vrijeme sinkronizacije</string>
+    <string name="pref_summary_datetime_syctimeonconnect">Sinkroniziraj vrijeme s Gadgetbridge ure膽ajima prilikom povezivanja, kada se promijeni vrijeme ili vremenska zona na Android ure膽aju i povremeno</string>
+    <string name="pref_header_datetime">Datum i vrijeme</string>
+    <string name="pref_default">Zadano</string>
+    <string name="miband_firmware_suggest_whitelist">Ako dalje 啪elite nastaviti i stvari nakon toga nastavi raditi ispravno, recitirajte programima Gadgetbridge-a da nastavite u popisu dopu拧tene verzije %s firmware-a.</string>
+    <string name="miband_firmware_known">Ovaj firmware je testiran i poznato je da je kompatibilan s Gadgetbridgeom.</string>
+    <string name="discovery_attempting_to_pair">Poku拧aj uparivanja s %1$s</string>
+    <string name="firmware_install_warning">POKU艩AVATE INSTALIRATI FIRMWARE, NASTAVITE NA VLASTITU ODGOVORNOST.
+\n
+\n
+\n Ovaj firmware je za HW reviziju: %s</string>
+    <string name="gadgetbridge_running_generic">Gadgetbridge je pokrenut</string>
+    <string name="tap_a_device_to_connect">Dodirnite ure膽aj za povezivanje</string>
+    <string name="test">Test</string>
+    <string name="not_connected">Nije povezano</string>
+    <string name="pref_summary_disable_new_ble_scanning">Ozna膷ite ovu opciju ako se va拧 ure膽aj ne mo啪e prona膰i tijekom otkrivanja</string>
+    <string name="pref_disable_new_ble_scanning">Onemogu膰i novo BLE skeniranje</string>
+    <string name="pref_auto_fetch_limit_fetches">Minimalno vrijeme izme膽u dohva膰anja</string>
+    <string name="pref_auto_fetch_limit_fetches_summary">Dohva膰a svakih %d minuta</string>
+    <string name="pref_header_auto_fetch">Automatsko dohva膰anje</string>
+    <string name="pref_title_auto_export_interval">Interval izvoza</string>
+    <string name="pref_title_auto_export_location">Izvoz lokacije</string>
+    <string name="prefs_hr_alarm_low">Donja granica</string>
+    <string name="prefs_hr_alarm_high">Visoka granica</string>
+    <string name="prefs_hr_alarm_activity">Alarm otkucaja srca tijekom sportske aktivnosti</string>
+    <string name="pref_title_authkey">Klju膷 za autentifikaciju</string>
+    <string name="title_activity_device_specific_settings">Postavke specifi膷ne za ure膽aj</string>
+    <string name="zetime_signaling_beep_once">Beep jedanput</string>
+    <string name="zetime_prefs_inactivity_sa">Subota</string>
+    <string name="zetime_prefs_inactivity_su">Nedjelja</string>
+    <string name="zetime_title_alarm_signaling">Postavite vrstu signalizacije za alarm</string>
+    <string name="zetime_prefs_inactivity_tu">Utorak</string>
+    <string name="zetime_date_format_3">MM/DD/YY</string>
+    <string name="zetime_date_format_2">DD/MM/YY</string>
+    <string name="zetime_date_format_1">YY/MM/DD</string>
+    <string name="zetime_calories_type_active">Samo aktivno sagorjele kalorije</string>
+    <string name="zetime_analog_mode_handsandsteps">Ruke i koraci</string>
+    <string name="zetime_activity_tracking">Pra膰enje aktivnosti</string>
+    <string name="zetime_activity_tracking_summary">Ako uklju膷ite pra膰enje aktivnosti, brojit 膰e se va拧i koraci i tako dalje.</string>
+    <string name="zetime_analog_mode">Analogni na膷in rada</string>
+    <string name="zetime_analog_mode_hands">Samo ruke</string>
+    <string name="activity_prefs_alarm_max_heart_rate">Maksimalni broj otkucaja srca</string>
+    <string name="activity_prefs_alarm_min_heart_rate">Minimalni broj otkucaja srca</string>
+    <string name="zetime_title_heart_rate_alarm">Alarm otkucaja srca</string>
+    <string name="zetime_title_heart_rate_alarm_summary">Sat 膰e vas upozoriti kada va拧 otkucaj srca prije膽e granice.</string>
+    <string name="zetime_heart_rate_alarm_enable">Omogu膰i alarm otkucaja srca</string>
+    <string name="zetime_title_heartrate">Postavke otkucaja srca</string>
+    <string name="zetime_title_screentime">Trajanje zaslona u sekundama</string>
+    <string name="prefs_screen_orientation">Orijentacija zaslona</string>
+    <string name="preferences_makibes_hr3_settings">Makibes HR3 postavke</string>
+    <string name="power_mode_saving">U拧teda energije</string>
+    <string name="power_mode_watch">Samo sat</string>
+    <string name="pref_sensors_bp_calibration_low">DIJASTOLI膶KI krvni tlak (nizak)</string>
+    <string name="pref_sensors_bp_calibration_high">SISTOLI膶KI Krvni tlak (visok)</string>
+    <string name="pref_header_sensors_calibration">Kalibracija senzora</string>
+    <string name="pref_title_device_spec_settings_show_raw_graph">Prika啪i RAW podatke na grafikonu aktivnosti</string>
+    <string name="pref_summary_device_spec_settings_title_force_time">Prisilna automatska sinkronizacija vremena pri ponovnom povezivanju. Analogne kazaljke mogu pokazati neto膷no vrijeme!</string>
+    <string name="pref_summary_notifications_and_calls_title_shake_reject">Duplicira radnju gumba sata</string>
+    <string name="pref_title_device_spec_settings_force_time">Prisilno vrijeme sinkronizacije</string>
+    <string name="prefs_notifications_and_calls_shake_reject">Protresite zape拧膰e ignoriranje/odbijanje poziva</string>
+    <string name="prefs_notifications_and_calls_reject">Gumb ignoriraj/odbij poziv</string>
+    <string name="pref_header_notifications_and_calls_callhandling">Upravljanje pozivima</string>
+    <string name="pref_notifications_and_calls_enable_misscall">Obavijesti o propu拧tenom pozivu</string>
+    <string name="pref_display_add_device_fab_on">Uvijek vidljiv</string>
+    <string name="pref_title_vibration_strength">Ja膷ina vibracije</string>
+    <string name="pref_summary_relax_firmware_checks">Opustite provjere firmvera</string>
+    <string name="pref_title_sync_caldendar">Sinkroniziraj doga膽aje kalendara</string>
+    <string name="pref_title_allow_high_mtu">Dopusti visoki MTU</string>
+    <string name="pref_title_pebble_reconnect_attempts">Poku拧aji ponovnog povezivanja</string>
+    <string name="pref_title_pebble_enable_bgjs">Omogu膰i pozadinski JS</string>
+    <string name="pref_summary_pebble_always_ack_pebblekit">Do膰i 膰e do toga da poruke koje se 拧alju vanjskim aplikacijama tre膰ih strana uvijek i odmah budu potvr膽ene</string>
+    <string name="pref_summary_pebble_enable_applogs">Gadgetbridge 膰e zabilje啪iti zapisnike iz aplikacija za sat (zahtijeva ponovno povezivanje)</string>
+    <string name="pref_summary_pebble_gatt_clientonly">Ovo je samo za Pebble 2 i eksperimentalno, poku拧ajte s ovim ako imate problema s povezivanjem</string>
+    <string name="pref_title_pebble_gatt_clientonly">Samo GATT klijent</string>
+    <string name="pref_title_pebble_enable_applogs">Omogu膰i bilje啪enje aplikacije za gledanje</string>
+    <string name="pref_summary_pebble_forcele">Koristite eksperimentalnu Pebble LE podr拧ku za sve Pebbles, umjesto BT classic. To zahtijeva prvo uparivanje s ne LE, a zatim Pebble LE</string>
+    <string name="pref_title_pebble_forcele">Uvijek preferiraj BLE</string>
+    <string name="pref_title_pebble_forceuntested">Omogu膰i neprovjerene zna膷ajke</string>
+    <string name="pref_summary_pebble_forceuntested">Omogu膰i neprovjerene zna膷ajke. ZNAJ 艩TO RADI艩!</string>
+    <string name="toast_aqurired_networklocation">Lokacija je dobivena</string>
+    <string name="pref_summary_location_keep_uptodate">Poku拧ajte dobiti trenutnu lokaciju tijekom izvo膽enja, upotrijebite pohranjenu lokaciju kao zamjenu</string>
+    <string name="pref_title_location_keep_uptodate">Odr啪avajte lokaciju a啪uriranom</string>
+    <string name="pref_title_location_aquire">Nabavite lokaciju</string>
+    <string name="pref_title_location_latitude">Geografska 拧irina</string>
+    <string name="pref_title_location_longitude">Du啪ina</string>
+    <string name="pref_pebble_privacy_mode_complete">Prika啪i samo ikonu obavijesti</string>
+    <string name="pref_header_location">Lokacija</string>
+    <string name="pref_pebble_privacy_mode_content">Prebacite tekst obavijesti izvan zaslona</string>
+    <string name="pref_title_autoremove_notifications">Automatski ukloni odba膷ene obavijesti</string>
+    <string name="pref_summary_custom_deviceicon">Prika啪i ikonu obavijesti za Android za odre膽eni ure膽aj umjesto ikone Gadgetbridgea kada je spojen</string>
+    <string name="pref_title_enable_calendar_sync">Sinkroniziraj kalendar</string>
+    <string name="pref_summary_enable_calendar_sync">Po拧alji doga膽aje iz kalendara na vremensku traku</string>
+    <string name="pref_title_custom_deviceicon">Prika啪i ikonu obavijesti specifi膷ne za ure膽aj</string>
+    <string name="pref_title_sunrise_sunset">Izlazak i zalazak sunca</string>
+    <string name="pref_title_enable_pebblekit">Dopusti pristup Android aplikaciji tre膰e strane</string>
+    <string name="pref_summary_enable_pebblekit">Omogu膰ite eksperimentalnu podr拧ku za Android aplikacije pomo膰u PebbleKita</string>
+    <string name="pref_title_pebble_sync_morpheuz">Sinkroniziraj Morpheuz</string>
+    <string name="pref_title_enable_outgoing_call">Podr拧ka za odlazne pozive</string>
+    <string name="pref_title_pebble_activitytracker">沤eljeni alat za pra膰enje aktivnosti</string>
+    <string name="pref_header_activitytrackers">Pra膰enje aktivnosti</string>
+    <string name="pref_title_canned_messages_dismisscall">Odbacivanje poziva</string>
+    <string name="pref_title_canned_replies">Odgovori</string>
+    <string name="pref_title_canned_reply_suffix">Uobi膷ajeni sufiks</string>
+    <string name="pref_blacklist_calendars">Kalendari na crnoj listi</string>
+    <string name="pref_header_cannned_messages">Sadr啪ajne poruke</string>
+    <string name="pref_title_weather_summary">Koristi se za davatelja vremenske prognoze LineageOS, druge verzije Androida moraju koristiti aplikaciju kao 拧to je \"Obavijest o vremenu\". Vi拧e informacija potra啪ite na wikiju Gadgetbridge.</string>
+    <string name="pref_title_weather">Vrijeme</string>
+    <string name="pref_title_weather_location">Vremenska lokacija (za davatelja vremena LineageOS)</string>
+    <string name="pref_call_privacy_mode_name">Sakrij ime, ali prika啪i broj</string>
+    <string name="pref_call_privacy_mode_off">Prikaz imena i broja</string>
+    <string name="pref_title_call_privacy_mode">Na膷in privatnosti poziva</string>
+    <string name="when_screen_off">Kada je zaslon isklju膷en</string>
+    <string name="never">Nikad</string>
+    <string name="pref_header_privacy">Privatnost</string>
+    <string name="always">Uvijek</string>
+    <string name="pref_summary_rtl">Omogu膰ite ovo ako va拧 ure膽aj ne mo啪e prikazati jezike koji se pi拧u zdesna nalijevo</string>
+    <string name="pref_rtl_max_line_length">Maksimalna duljina linije zdesna nalijevo</string>
+    <string name="pref_title_rtl">Zdesna nalijevo</string>
+    <string name="fw_upgrade_notice_amazfitgts">Spremate se instalirati %s firmware na va拧 Amazfit GTS.
+\n
+\nObavezno instalirajte .fw datoteku, zatim .res datoteku i na kraju .gps datoteku. Va拧 sat 膰e se ponovno pokrenuti nakon instaliranja .fw datoteke.
+\n
+\nNapomena: Ne morate instalirati .res i .gps ako su ove datoteke potpuno iste kao one prethodno instalirane.
+\n
+\nNASTAVITE NA VLASTITU ODGOVORNOST!</string>
+    <string name="appmanager_installed_watchapps">Instalirane aplikacije</string>
+    <string name="appmanager_cached_watchapps_watchfaces">Aplikacije u predmemoriji</string>
+    <string name="prefs_ambient_voice_summary">Istaknite glas</string>
+    <string name="prefs_ambient_sound">Ambijentalni zvuk</string>
+    <string name="pref_header_charts">Postavke grafikona</string>
+    <string name="pref_title_charts_swipe">Omogu膰i povla膷enje lijevo/desno u aktivnosti grafikona</string>
+    <string name="pref_title_charts_average">Prika啪i prosjeke na grafikonima</string>
+    <string name="on">Uklju膷eno</string>
+    <string name="appmanager_app_edit">Uredi</string>
+    <string name="sony_surround_mode_club">Klub</string>
+    <string name="sony_surround_mode_outdoor_stage">Vanjska pozornica</string>
+    <string name="you_slept">%1$s 鈥� %2$s</string>
+    <string name="mi2_prefs_do_not_disturb_end">Vrijeme zavr拧etka</string>
+    <string name="notification_channel_name">Op膰enito</string>
+    <string name="notification_channel_high_priority_name">Visok prioritet</string>
+    <string name="notification_channel_transfer_name">Prijenos podataka</string>
+    <string name="notification_channel_low_battery_name">Slaba baterija</string>
+    <string name="devicetype_amazfit_vergel">Amazfit Verge Lite</string>
+    <string name="devicetype_sg2">Lemfo SG2</string>
+    <string name="devicetype_lefun">Lefun</string>
+    <string name="menuitem_status">Status</string>
+    <string name="menuitem_compass">Kompas</string>
+    <string name="menuitem_settings">Postavke</string>
+    <string name="menuitem_alipay">Alipay</string>
+    <string name="preferences_qhybrid_settings">Q Hybrid postavke</string>
+    <string name="menuitem_music">Glazba</string>
+    <string name="menuitem_more">Vi拧e</string>
+    <string name="menuitem_nfc">NFC</string>
+    <string name="menuitem_spo2">SpO2</string>
+    <string name="menuitem_eventreminder">Podsjetnik na doga膽aj</string>
+    <string name="menuitem_workout">Vje啪banje</string>
+    <string name="menuitem_unknown">Nepoznato</string>
+    <string name="menuitem_worldclock">Svjetski sat</string>
+    <string name="qhybrid_offset_time_by">pomak vremena za</string>
+    <string name="hr_widget_heart_rate">Broj otkucaja srca</string>
+    <string name="hr_widget_steps">Koraci</string>
+    <string name="hr_widget_date">Datum</string>
+    <string name="error_no_location_access">Pristup lokaciji mora biti odobren i omogu膰en da bi skeniranje ispravno radilo</string>
+    <string name="pref_qhybrid_title_widget_draw_circles">Nacrtajte krugove widgeta</string>
+    <string name="pref_qhybrid_save_raw_activity_files">Spremi neobra膽ene datoteke aktivnosti</string>
+    <string name="hr_widget_last_notification">Posljednja obavijest</string>
+    <string name="about_title">O aplikaciji</string>
+    <string name="prefs_events_forwarding_action_title">Pokreni radnju</string>
+    <string name="pref_header_filter">Filter sportskih aktivnosti</string>
+    <string name="pref_title_equalizer_normal">Normalno</string>
+    <string name="prefs_active_noise_cancelling">Aktivno poni拧tavanje buke</string>
+    <string name="sony_ambient_sound">Na膷in</string>
+    <string name="pref_header_sony_equalizer_bands">Bandovi</string>
+    <string name="sony_audio_upsampling">Pove膰anje zvuka</string>
+    <string name="thai">tajlandski</string>
+    <string name="pref_summary_notification_filter">Blokiraj sve obavijesti kada je DND na膷in omogu膰en na telefonu</string>
+    <plurals name="widget_alarm_target_hours">
+        <item quantity="one">%d sat</item>
+        <item quantity="few">%d sata</item>
+        <item quantity="other">%d sati</item>
+    </plurals>
+    <string name="battery_case">Ku膰i拧te za baterije</string>
+    <string name="left_earbud">Lijeva slu拧alica</string>
+    <string name="right_earbud">Desna slu拧alica</string>
+    <string name="devicetype_sony_wh_1000xm4">Sony WH-1000XM4</string>
+    <string name="sony_speak_to_chat_sensitivity">Osjetljivost otkrivanja glasa</string>
+    <string name="device_unsupported">NEPODR沤ANO</string>
+    <string name="devicetype_domyos_t540">Domyos T540</string>
+    <string name="devicetype_sonyswr12">Sony SWR12</string>
+    <string name="watchface_dialog_widget_width">艩irina widgeta (u pikselima)</string>
+    <string name="discovery_bluetooth_scan">Bluetooth pretraga:</string>
+    <string name="discovery_bluetooth_le_scan">Bluetooth LE pretraga:</string>
+    <string name="sony_speak_to_chat_sensitivity_auto">Automaska</string>
+    <string name="sony_speak_to_chat_sensitivity_high">Visoka</string>
+    <string name="sony_speak_to_chat_sensitivity_low">Niska</string>
+    <string name="sony_speak_to_chat_focus_on_voice">Fokusiranje na glas</string>
+    <string name="sony_speak_to_chat_timeout">Vremensko ograni膷enje</string>
+    <string name="sony_speak_to_chat_timeout_off">Isklju膷eno</string>
+    <string name="sony_speak_to_chat_timeout_short">Kratko (15 s)</string>
+    <string name="sony_speak_to_chat_timeout_standard">Standardno (30 s)</string>
+    <string name="sony_speak_to_chat_timeout_long">Dugo (1 min)</string>
+    <string name="sony_connect_two_devices">Pove啪i se na dva ure膽aja istovremeno</string>
+    <string name="sony_speak_to_chat">Speak-to-chat</string>
+    <string name="pref_header_sony_device_info">Informacije ura膽aja</string>
+    <string name="audio_codec">Audio kodek</string>
+    <string name="start">Po膷etak</string>
+    <string name="devicetype_amazfit_pop">Amazfit Pop</string>
+    <string name="devicetype_amazfit_pop_pro">Amazfit Pop Pro</string>
+    <string name="menuitem_nothing">Ni拧ta</string>
+    <string name="unknown">Nepoznato</string>
+    <string name="pref_anc_optimizer_state_pressure">Atmosferski tlak</string>
+    <string name="sony_anc_optimizer_status_analyzing">Analiziranje 鈥�</string>
+    <string name="pref_title_notifications_generic_settings">Postavke obavijesti za Android</string>
+    <string name="autoExport_lastTime_label">Zadnji automatski izvoz: %1$s</string>
+    <string name="activity_db_management_autoexport_enabled_yes">Automatski izvoz je deaktiviran.</string>
+    <string name="activity_db_management_autoexport_enabled_no">Automatski izvoz nije aktiviran.</string>
+    <string name="activity_db_management_autoexport_scheduled_yes">Automatski izvoz je (izvorno) planiran za %1$s</string>
+    <string name="activity_db_management_autoexport_scheduled_no">Automatski izvoz nije planiran.</string>
+    <string name="sony_anc_optimize_title">Optimiraj</string>
+    <string name="sony_anc_optimizer_status_starting">Pokretanje 鈥�</string>
+    <string name="sony_anc_optimizer_status_not_running">Nije pokrenuto</string>
+    <string name="sony_anc_optimizer_status_atmospheric_pressure">Mjerenje atmostferskog tlaka 鈥�</string>
+    <string name="sony_anc_optimizer_status_finished">Dovr拧avanje 鈥�</string>
+    <string name="sony_anc_optimizer_status_wearing_condition">Mjerenje stanja no拧enja 鈥�</string>
+    <string name="activity_db_management_autoexport_location">Nije bilo mogu膰e razumjeti lokaciju. Vjerojatno se radi o problemu novijeg Android sustava dozvola. Najvjerojatnije automatski izvoz sada ne radi.</string>
+    <string name="pref_header_sony_anc_optimizer">Optimizator poni拧tavanje buke</string>
+    <string name="sony_anc_optimize_confirmation_title">Optimizator poni拧tavanje buke</string>
+    <string name="sony_anc_optimize_confirmation_description">Koristi slu拧alice kao obi膷no. Ako se stanje no拧enja ili atmosferski tlak promijene, ponovo pokreni optimizator.</string>
+    <string name="sony_anc_optimize_description">Pritisni za pokretanje optimizatora poni拧tavanja buke.</string>
+    <string name="pref_device_action_fitness_app_control_start">Pokretanje pra膰enja aplikacije za fitness</string>
+    <string name="pref_device_action_fitness_app_control_stop">Prekid pra膰enja aplikacije za fitness</string>
+    <string name="pref_title_opentracks_packagename">Ime OpenTracks paketa</string>
+    <string name="watchface_dialog_pre_setting_position">predpode拧avanje polo啪aja na %s</string>
+    <string name="watchface_setting_light_up_on_notification">Zasvijetli pri novoj obavijesti</string>
+    <string name="pref_summary_opentracks_packagename">Koristi se za pokretanje/prekidanje snimanja GPS zapisa u vanjskoj aplikaciji za fitness.</string>
+    <string name="menuitem_email">E-mail</string>
+    <string name="title_activity_controlcenter_banglejs_main">Bangle.js Gadgetbridge</string>
+    <string name="about_activity_title_banglejs_main">O Bangle.js Gadgetbridge</string>
+    <string name="application_name_banglejs_main">Bangle.js Gadgetbridge</string>
+    <string name="appmanager_watchface_activate">Aktiviraj</string>
+    <string name="vibration_profile_default">Zadano</string>
+    <string name="watchface_dialog_widget_cat_custom_widget">Prilago膽eni widget</string>
+    <string name="watchface_setting_title_custom_events">Prilago膽en doga膽aj</string>
+    <string name="watchface_setting_button_toggle_widgets">Uklj./Isklj. widgete</string>
+    <string name="zepp_os_watchface_superposition">superpozicija</string>
+    <string name="watchface_dialog_widget_cat_generic">Op膰e</string>
+    <string name="watchface_dialog_widget_cat_2nd_tz_widget">Widget za drugu vremensku zonu</string>
+    <string name="watchface_dialog_widget_background">Pozadina</string>
+    <string name="watchface_dialog_widget_cat_position">Polo啪aj</string>
+    <string name="watchface_setting_button_move_hands">Pomakni kazaljke</string>
+    <string name="prefs_always_on_display_follow_watchface">Stil slijedi broj膷anik</string>
+    <string name="watchface_dialog_title_widget">Postavke widgeta</string>
+    <string name="device_experimental">EKSPERIMENTALNO</string>
+    <string name="stress_average">Prosjek</string>
+    <string name="total_calories_burnt">Ukupno potro拧eno</string>
+    <string name="hrZoneMaximum">Maksimum</string>
+    <string name="hr_average">Prosjek</string>
+    <string name="hr_resting">Odmaranje</string>
+    <string name="hr_maximum">Maksimum</string>
+    <string name="hr_minimum">Minimum</string>
+</resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml
new file mode 100644
index 0000000..552ec24
--- /dev/null
+++ b/app/src/main/res/values-hu/strings.xml
@@ -0,0 +1,2710 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources xmlns:tools="http://schemas.android.com/tools">
+    <string name="application_name_generic">Gadgetbridge</string>
+    <string name="title_activity_controlcenter_generic">Gadgetbridge </string>
+    <string name="action_settings">Be谩ll铆t谩sok</string>
+    <string name="action_debug">Hibakeres茅s</string>
+    <string name="action_quit">Kil茅p茅s</string>
+    <string name="controlcenter_fetch_activity_data">Szinkroniz谩l谩s</string>
+    <string name="controlcenter_find_device">Elveszett eszk枚z keres茅se</string>
+    <string name="controlcenter_take_screenshot">K茅perny艖k茅p k茅sz铆t茅se</string>
+    <string name="controlcenter_disconnect">Lev谩laszt谩s</string>
+    <string name="controlcenter_delete_device">Eszk枚z t枚rl茅se</string>
+    <string name="controlcenter_delete_device_name">%1$s t枚rl茅se</string>
+    <string name="controlcenter_delete_device_dialogmessage">Az eszk枚z 茅s az 枚sszes hozz谩tartoz贸 adat t枚rl茅sre ker眉l!</string>
+    <string name="controlcenter_navigation_drawer_open">Navig谩ci贸s fi贸k megnyit谩sa</string>
+    <string name="controlcenter_navigation_drawer_close">Navig谩ci贸s fi贸k bez谩r谩sa</string>
+    <string name="controlcenter_snackbar_need_longpress">Nyomja hosszan a lev谩laszt谩shoz</string>
+    <string name="controlcenter_snackbar_disconnecting">Lev谩laszt谩s...</string>
+    <string name="controlcenter_snackbar_connecting">Kapcsol贸d谩s鈥�</string>
+    <string name="controlcenter_snackbar_requested_screenshot">K茅perny艖k茅p k茅sz铆t茅se az eszk枚zr艖l</string>
+    <string name="title_activity_debug">Hibakeres茅s</string>
+    <!--Strings related to AppManager-->
+    <string name="title_activity_appmanager">Alkalmaz谩skezel艖</string>
+    <string name="appmanager_cached_watchapps_watchfaces">Gyors铆t贸t谩razott alkalmaz谩sok</string>
+    <string name="appmanager_installed_watchapps">Telep铆tett alkalmaz谩sok</string>
+    <string name="appmanager_installed_watchfaces">Telep铆tett 贸rafel眉letek</string>
+    <string name="appmananger_app_delete">T枚rl茅s</string>
+    <string name="appmananger_app_delete_cache">T枚rl茅s 茅s elt谩vol铆t谩s a gyors铆t贸t谩rb贸l</string>
+    <string name="appmananger_app_reinstall">脷jratelep铆t茅s</string>
+    <string name="appmanager_app_openinstore">Keres茅s a Pebble alkalmaz谩s-谩ruh谩zban</string>
+    <string name="appmanager_health_activate">Aktiv谩l谩s</string>
+    <string name="appmanager_health_deactivate">Deaktiv谩l谩s</string>
+    <string name="appmanager_hrm_activate">Pulzusm茅r艖 aktiv谩l谩sa</string>
+    <string name="appmanager_hrm_deactivate">Pulzusm茅r艖 deaktiv谩l谩sa</string>
+    <string name="appmanager_weather_activate">Rendszer id艖j谩r谩s alkalmaz谩s aktiv谩l谩sa</string>
+    <string name="appmanager_weather_deactivate">Rendszer id艖j谩r谩s alkalmaz谩s deaktiv谩l谩sa</string>
+    <string name="appmanager_weather_install_provider">Id艖j谩r谩s 茅rtes铆t艖 alkalmaz谩s telep铆t茅se</string>
+    <string name="app_configure">Be谩ll铆t谩s</string>
+    <string name="app_move_to_top">A tetej茅re mozgat谩s</string>
+    <!--Strings related to FwAppInstaller-->
+    <string name="title_activity_fw_app_insaller">FW/alkalmaz谩s telep铆t艖</string>
+    <string name="fw_upgrade_notice">A(z) %s firmware-t k铆v谩nja telep铆teni.</string>
+    <string name="fw_multi_upgrade_notice">A(z) %1$s 茅s a(z) %2$s firmware-v谩ltozatokat k铆v谩nja telep铆teni az Mi k茅sz眉l茅k茅re a mostani helyett.</string>
+    <string name="miband_firmware_known">Ez a firmware tesztelt 茅s kompatibilis a Gadgetbridge-dzsel.</string>
+    <string name="miband_firmware_unknown_warning">Ez a firmware nincs tesztelve, lehets茅ges, hogy nem kompatibilis a Gadgetbridge-dzsel.
+\n
+\nNEM javasoljuk, ezen firmware telep铆t茅s茅t!</string>
+    <string name="miband_firmware_suggest_whitelist">Ha ennek ellen茅re is telep铆teni szeretn茅, 茅s a dolgok j贸l m疟k枚dnek ut谩na, jelezze a Gadgetbridge fejleszt艖inek, hogy feh茅rlist谩ra tehess茅k a(z) %s firmware-v谩ltozatot.</string>
+    <!--Strings related to Settings-->
+    <string name="title_activity_settings">Be谩ll铆t谩sok</string>
+    <string name="pref_header_general">脕ltal谩nos be谩ll铆t谩sok</string>
+    <string name="pref_title_general_autoconnectonbluetooth">Kapcsol贸djon a Gadgetbridge eszk枚z(枚k)h枚z a Bluetooth bekapcsol谩sakor</string>
+    <string name="pref_title_general_autostartonboot">Automatikus ind铆t谩s</string>
+    <string name="pref_title_general_autoreconnect">Automatikus 煤jrakapcsol贸d谩s</string>
+    <string name="pref_title_audio_player">El艖nyben r茅szes铆tett zenelej谩tsz贸</string>
+    <string name="pref_default">Alap茅rtelmezett</string>
+    <string name="pref_header_datetime">D谩tum 茅s id艖</string>
+    <string name="pref_title_datetime_syctimeonconnect">Id艖 szinkroniz谩l谩sa</string>
+    <string name="pref_summary_datetime_syctimeonconnect">Szinkroniz谩lja az id艖t a Gadgetbridge eszk枚z(枚k)kel, amikor kapcsol贸dnak, amikor az id艖 vagy az id艖z贸na megv谩ltozik az Android eszk枚z枚n 茅s id艖szakosan</string>
+    <string name="pref_title_theme">T茅ma</string>
+    <string name="pref_theme_light">Vil谩gos</string>
+    <string name="pref_theme_dark">S枚t茅t</string>
+    <string name="pref_title_language">Nyelv</string>
+    <string name="pref_title_minimize_priority">Gadgetbridge 茅rtes铆t茅s elrejt茅se</string>
+    <string name="pref_summary_minimize_priority_off">Az ikon megjelenik az 谩llapotsoron 茅s az 茅rtes铆t茅s a z谩r贸k茅perny艖n</string>
+    <string name="pref_summary_minimize_priority_on">Az ikon nem jelenik meg az 谩llapotsoron, sem az 茅rtes铆t茅s a z谩r贸k茅perny艖n</string>
+    <string name="pref_header_notifications">脡rtes铆t茅sek</string>
+    <string name="pref_title_notifications_repetitions">Ism茅tl茅sek</string>
+    <string name="pref_title_notifications_call">Telefonh铆v谩sok</string>
+    <string name="pref_title_notifications_sms">SMS</string>
+    <string name="pref_title_notifications_pebblemsg">Pebble 眉zenetek</string>
+    <string name="pref_summary_notifications_pebblemsg">Alkalmaz谩sok t谩mogat谩sa, amelyek 茅rtes铆t茅seket k眉ldenek a Pebble-re PebbleKit-en kereszt眉l.</string>
+    <string name="pref_title_notifications_generic">脕ltal谩nos 茅rtes铆t茅st谩mogat谩s</string>
+    <string name="pref_title_whenscreenon">鈥� 茅s amikor a kijelz艖 be van kapcsolva</string>
+    <string name="pref_title_notification_filter">Ne zavarjanak m贸d</string>
+    <string name="pref_summary_notification_filter">脰sszes 茅rtes铆t茅s blokkol谩sa, amikor a telefon ne zavarjanak m贸dban van</string>
+    <string name="pref_title_transliteration">Transzliter谩ci贸</string>
+    <string name="pref_summary_transliteration">Enged茅lyezze ezt, ha az eszk枚z nem t谩mogatja nyelv茅nek a bet疟t铆pus谩t</string>
+    <string name="always">Mindig</string>
+    <string name="when_screen_off">Amikor a kijelz艖 ki van kapcsolva</string>
+    <string name="never">Soha</string>
+    <string name="pref_header_privacy">Adatv茅delem</string>
+    <string name="pref_title_call_privacy_mode">H铆v谩s adatv茅delmi m贸d</string>
+    <string name="pref_call_privacy_mode_off">N茅v 茅s sz谩m megfelen铆t茅se</string>
+    <string name="pref_call_privacy_mode_name">N茅v rejt茅se, de a sz谩m megfelen铆t茅se</string>
+    <string name="pref_call_privacy_mode_number">Sz谩m elrejt茅se, de a n茅v megfelen铆t茅se</string>
+    <string name="pref_call_privacy_mode_complete">N茅v 茅s sz谩m elrejt茅se</string>
+    <string name="pref_header_cannned_messages">El艖re be谩ll铆tott 眉zenetek</string>
+    <string name="pref_title_canned_replies">V谩laszok</string>
+    <string name="pref_title_canned_reply_suffix">K枚z枚s ut贸tag</string>
+    <string name="pref_title_canned_messages_dismisscall">H铆v谩s elutas铆t谩sa</string>
+    <string name="pref_title_canned_messages_set">Friss铆t茅s az eszk枚z枚n</string>
+    <string name="pref_header_development">Fejleszt艖i be谩ll铆t谩sok</string>
+    <string name="pref_title_development_miaddr">Mi Band c铆m</string>
+    <string name="pref_title_pebble_settings">Pebble be谩ll铆t谩sok</string>
+    <string name="pref_header_activitytrackers">Aktivit谩sm茅r艖k</string>
+    <string name="pref_title_pebble_activitytracker">Prefer谩lt aktivit谩sm茅r艖</string>
+    <string name="pref_title_pebble_sync_health">Pebble Health szinkroniz谩l谩sa</string>
+    <string name="pref_title_pebble_sync_misfit">Misfit szinkroniz谩l谩sa</string>
+    <string name="pref_title_pebble_sync_morpheuz">Morpheuz szinkroniz谩l谩sa</string>
+    <string name="pref_title_enable_outgoing_call">Kimen艖 h铆v谩sok t谩mogat谩sa</string>
+    <string name="pref_summary_enable_outgoing_call">Ennek a letilt谩s谩val a kimen艖 h铆v谩s rezg茅s茅t is letiltja a Pebble 2/LE eszk枚z枚n</string>
+    <string name="pref_title_enable_pebblekit">Harmadik f茅lt艖l sz谩rmaz贸 Android alkalmaz谩sok hozz谩f茅r茅s茅nek enged茅lyez茅se</string>
+    <string name="pref_summary_enable_pebblekit">K铆s茅rleti t谩mogat谩s enged茅lyez茅se a PebbleKit-et haszn谩l贸 Android alkalmaz谩sok sz谩m谩ra</string>
+    <string name="pref_header_pebble_timeline">Pebble Id艖vonal</string>
+    <string name="pref_title_sunrise_sunset">Napkelte 茅s napnyugta</string>
+    <string name="pref_summary_sunrise_sunset">A napkelte 茅s napnyugta idej茅nek k眉ld茅se a Pebble id艖vonalra a helysz铆n alapj谩n</string>
+    <string name="pref_title_enable_calendar_sync">Napt谩r szinkroniz谩l谩sa</string>
+    <string name="pref_summary_enable_calendar_sync">Napt谩rbejegyz茅sek k眉ld茅se az id艖vonalra</string>
+    <string name="pref_title_autoremove_notifications">Elt谩vol铆tott 茅rtes铆t茅sek automatikus t枚rl茅se</string>
+    <string name="pref_summary_autoremove_notifications">Az 茅rtes铆t茅sek automatikusan el lesznek t谩vol铆tva az eszk枚zr艖l, amikor elutas铆tja 艖ket a telefonon</string>
+    <string name="pref_title_pebble_privacy_mode">Adatv茅delmi m贸d</string>
+    <string name="pref_pebble_privacy_mode_off">Norm谩l 茅rtes铆t茅sek</string>
+    <string name="pref_pebble_privacy_mode_content">Az 茅rtes铆t茅s sz枚veg茅nek a k茅perny艖r艖l t枚rt茅n艖 谩thelyez茅se</string>
+    <string name="pref_pebble_privacy_mode_complete">Csak az 茅rtes铆t茅si ikon megjelen铆t茅se</string>
+    <string name="pref_header_location">Helysz铆n</string>
+    <string name="pref_title_location_aquire">Helysz铆n meghat谩roz谩sa</string>
+    <string name="pref_title_location_latitude">Sz茅less茅g</string>
+    <string name="pref_title_location_longitude">Hossz煤s谩g</string>
+    <string name="pref_title_location_keep_uptodate">Helysz铆n frissen tart谩sa</string>
+    <string name="pref_summary_location_keep_uptodate">Aktu谩lis helysz铆n meghat谩roz谩s谩nak megpr贸b谩l谩sa fut谩sid艖ben, hiba eset茅n az elt谩rolt helysz铆n haszn谩lata</string>
+    <string name="toast_enable_networklocationprovider">K茅rem, enged茅lyezze a h谩l贸zati helysz铆n-meghat谩roz谩st</string>
+    <string name="toast_aqurired_networklocation">helysz铆n meghat谩rozva</string>
+    <string name="pref_title_pebble_forceprotocol">脡rtes铆t茅si protokoll er艖ltet茅se</string>
+    <string name="pref_summary_pebble_forceprotocol">Ez az opci贸 a legfrissebb 茅rtes铆t茅si protokollt er艖lteti a firmware verzi贸t贸l f眉gg艖en. CSAK AKKOR ENGED脡LYEZZE, HA TUDJA MIT CSIN脕L!</string>
+    <string name="pref_title_pebble_forceuntested">Nem tesztelt funkci贸k enged茅lyez茅se</string>
+    <string name="pref_summary_pebble_forceuntested">Nem tesztelt funkci贸k enged茅lyez茅se. CSAK AKKOR ENGED脡LYEZZE, HA TUDJA MIT CSIN脕L!</string>
+    <string name="pref_title_pebble_forcele">BLE prefer谩l谩sa mindig</string>
+    <string name="pref_summary_pebble_forcele">K铆s茅rleti Pebble LE-t谩mogat谩s haszn谩lata minden Pebble eset茅ben, a BT classic helyett. Ehhez el艖sz枚r a nem LE, majd a Pebble LE p谩ros铆t谩sa sz眉ks茅ges</string>
+    <string name="pref_title_pebble_mtu_limit">Pebble 2/LE GATT MTU hat谩r茅rt茅k</string>
+    <string name="pref_summary_pebble_mtu_limit">Ha a Pebble 2/Pebble LE nem m疟k枚dik elv谩rtan, pr贸b谩lja be谩ll铆tani az MTU hat谩r茅rt茅ket (az 茅rv茅nyes tartom谩ny: 20鈥�512)</string>
+    <string name="pref_title_pebble_enable_applogs">脫raalkalmaz谩s-napl贸z谩s enged茅lyez茅se</string>
+    <string name="pref_summary_pebble_enable_applogs">A kar贸ra-alkalmaz谩sok napl贸it a Gadgetbridge is napl贸zni fogja (煤jrakapcsol贸d谩st ig茅nyel)</string>
+    <string name="pref_title_pebble_always_ack_pebblekit">Korai ACK PebbleKit</string>
+    <string name="pref_summary_pebble_always_ack_pebblekit">Minden esetben azonnal felismeri az 眉zeneteket, amelyeket k眉ls艖 harmadik alkalmaz谩soknak k眉ldenek</string>
+    <string name="pref_title_pebble_reconnect_attempts">脷jrakapcsol贸d谩si k铆s茅rletek</string>
+    <string name="pref_title_unit_system">Egys茅gek</string>
+    <string name="pref_title_timeformat">Id艖form谩tum</string>
+    <string name="pref_title_screentime">Kijelz艖 bekapcsolva tart谩s谩nak ideje</string>
+    <string name="prefs_title_all_day_heart_rate">Eg茅sz napos pulzusm茅r茅s</string>
+    <string name="preferences_hplus_settings">HPlus/Makibes be谩ll铆t谩sok</string>
+    <string name="not_connected">Nincs kapcsol贸dva</string>
+    <string name="connecting">Kapcsol贸d谩s</string>
+    <string name="connected">Kapcsol贸dva</string>
+    <string name="unknown_state">Ismeretlen 谩llapot</string>
+    <string name="_unknown_">(ismeretlen)</string>
+    <string name="test">Teszt</string>
+    <string name="test_notification">Teszt 茅rtes铆t茅s</string>
+    <string name="this_is_a_test_notification_from_gadgetbridge">Ez egy teszt 茅rtes铆t茅s a Gadgetbridge-t艖l</string>
+    <string name="bluetooth_is_not_supported_">A Bluetooth nem t谩mogatott.</string>
+    <string name="bluetooth_is_disabled_">A Bluetooth ki van kapcsolva.</string>
+    <string name="tap_connected_device_for_app_mananger">Koppintson a kapcsol贸dott eszk枚zre az alkalmaz谩skezel艖h枚z</string>
+    <string name="tap_connected_device_for_activity">Koppintson a kapcsol贸dott eszk枚zre az aktivit谩shoz</string>
+    <string name="tap_connected_device_for_vibration">Koppintson a kapcsol贸dott eszk枚zre a rezget茅shez</string>
+    <string name="tap_a_device_to_connect">Koppintson az eszk枚zre a kapcsol贸d谩shoz</string>
+    <string name="cannot_connect_bt_address_invalid_">Nem lehet kapcsol贸dni. Lehet, hogy 茅rv茅nytelen a Bluetooth c铆m?</string>
+    <string name="gadgetbridge_running_generic">A Gadgetbridge fut</string>
+    <string name="installing_binary_d_d">%1$d/%2$d bin谩ris telep铆t茅se</string>
+    <string name="installation_failed_">Sikertelen telep铆t茅s</string>
+    <string name="installation_successful">Telep铆tve</string>
+    <string name="firmware_install_warning">FIRMWARE-T PR脫B脕L TELEP脥TENI, CSAK SAJ脕T FELEL艕SS脡G脡RE FOLYTASSA.
+\n
+\n
+\n Ez a firmware ehhez a hardververzi贸hoz tartozik: %s</string>
+    <string name="app_install_info">A k枚vetkez艖 alkalmaz谩st fogja telep铆teni:
+\n
+\n%1$s
+\nVerzi贸: %2$s, k茅sz铆t艖: %3$s
+\n</string>
+    <string name="n_a">N/A</string>
+    <string name="initialized">inicializ谩lt</string>
+    <string name="appversion_by_creator">%1$s , k茅sz铆t艖: %2$s</string>
+    <string name="title_activity_discovery">Eszk枚z felder铆t茅se</string>
+    <string name="discovery_stop_scanning">Keres茅s le谩ll铆t谩sa</string>
+    <string name="discovery_start_scanning">Felder铆t茅s ind铆t谩sa</string>
+    <string name="action_discover">脷j eszk枚z kapcsol贸d谩sa</string>
+    <string name="device_with_rssi">%1$s (%2$s)</string>
+    <string name="title_activity_android_pairing">Eszk枚z p谩ros铆t谩sa</string>
+    <string name="android_pairing_hint">Haszn谩lja az Android Bluetooth p谩ros铆t谩si p谩rbesz茅dpanel茅t az eszk枚z p谩ros铆t谩s谩hoz.</string>
+    <string name="title_activity_mi_band_pairing">P谩ros铆tsa a Mi Band-j茅t</string>
+    <string name="pairing">%s p谩ros铆t谩sa鈥�</string>
+    <string name="pairing_creating_bond_with">P谩ros铆t谩s ezzel: %1$s (%2$s)</string>
+    <string name="pairing_unable_to_pair_with">Sikertelen p谩ros铆t谩s ezzel: %1$s (%2$s)</string>
+    <string name="pairing_in_progress">P谩ros铆t谩s folyamatban ezzel: %1$s (%2$s)</string>
+    <string name="pairing_already_bonded">M谩r p谩ros铆tva van ezzel: %1$s (%2$s), csatlakoz谩s鈥�</string>
+    <string name="message_cannot_pair_no_mac">Nincs 谩tadott MAC-c铆m, nem lehet p谩ros铆tani.</string>
+    <string name="preferences_category_device_specific_settings">Eszk枚zspecifikus be谩ll铆t谩sok</string>
+    <string name="preferences_miband_settings">MI Band / Amazfit be谩ll铆t谩sok</string>
+    <string name="male">F茅rfi</string>
+    <string name="female">N艖</string>
+    <string name="other">Egy茅b</string>
+    <string name="left">Bal</string>
+    <string name="right">Jobb</string>
+    <string name="miband_pairing_using_dummy_userdata">脡rv茅nytelen felhaszn谩l贸i adatok vannak megadva, alap茅rtelmezett adatok haszn谩lata.</string>
+    <string name="miband_pairing_tap_hint">Amikor rezegni kezd a Mi Band-je, 茅rintse meg p谩rszor egym谩s ut谩n.</string>
+    <string name="appinstaller_install">Telep铆t茅s</string>
+    <string name="discovery_connected_devices_hint">Tegye felder铆thet艖v茅 a k茅sz眉l茅k茅t. A jelenleg kapcsol贸dott k茅sz眉l茅kek nem fognak felder铆thet艖k茅nt megjelenni. Aktiv谩lja a helysz铆n-meghat谩roz谩st (Pl: GPS) az Android 6+ k茅sz眉l茅k茅n. Kapcsolja ki a Privacy Guard-ban a Gadgetbridge-et (Ha van Privacy Guard be谩ll铆t谩sa), k眉l枚nben az alkalmaz谩s 枚sszeomolhat, illetve 煤jraindulhat a telefonja. Ha nem tal谩lhat贸 az eszk枚z p谩r percen bel眉l, pr贸b谩lja meg 煤jraind铆tani a telefonj谩t.</string>
+    <string name="discovery_note">Megjegyz茅s:</string>
+    <string name="candidate_item_device_image">Eszk枚zk茅p</string>
+    <string name="miband_prefs_alias">N茅v/谩ln茅v</string>
+    <string name="pref_header_vibration_count">Rezg茅sek sz谩ma</string>
+    <string name="title_activity_sleepmonitor">Alv谩sfigyel艖</string>
+    <string name="pref_write_logfiles">Napl贸f谩jlok 铆r谩sa</string>
+    <string name="initializing">Inicializ谩l谩s</string>
+    <string name="busy_task_fetch_activity_data">Aktivit谩si adatok lek茅rdez茅se</string>
+    <string name="sleep_activity_date_range">%1$s 茅s %2$s k枚z枚tt</string>
+    <string name="prefs_wearside">Melyik kez茅n hordja\?</string>
+    <string name="pref_screen_vibration_profile">Rezg茅s profil</string>
+    <string name="vibration_profile_staccato">Szaggatott</string>
+    <string name="vibration_profile_short">R枚vid</string>
+    <string name="vibration_profile_medium">K枚zepes</string>
+    <string name="vibration_profile_long">Hossz煤</string>
+    <string name="vibration_profile_waterdrop">V铆zcsepp</string>
+    <string name="vibration_profile_ring">Cs枚rg茅s</string>
+    <string name="vibration_profile_alarm_clock">脡breszt艖贸ra</string>
+    <string name="miband_prefs_vibration">Rezg茅s</string>
+    <string name="vibration_try">Kipr贸b谩l谩s</string>
+    <string name="pref_screen_notification_profile_sms">SMS 茅rtes铆t茅s</string>
+    <string name="pref_header_vibration_settings">Rezg茅s be谩ll铆t谩sok</string>
+    <string name="pref_screen_notification_profile_generic">脕ltal谩nos 茅rtes铆t茅s</string>
+    <string name="pref_screen_notification_profile_email">Email 茅rtes铆t茅s</string>
+    <string name="pref_screen_notification_profile_incoming_call">Bej艖v艖 h铆v谩s 茅rtes铆t茅s</string>
+    <string name="pref_screen_notification_profile_generic_chat">Cseveg茅s</string>
+    <string name="pref_screen_notification_profile_generic_navigation">Navig谩ci贸</string>
+    <string name="pref_screen_notification_profile_generic_social">K枚z枚ss茅gi m茅dia</string>
+    <string name="stats_title">Sebess茅gz贸n谩k</string>
+    <string name="stats_x_axis_label">脰sszes perc</string>
+    <string name="stats_y_axis_label">Percenk茅nti l茅p茅s</string>
+    <string name="control_center_find_lost_device">Elveszett eszk枚z keres茅se</string>
+    <string name="control_center_cancel_to_stop_vibration">A rezg茅s le谩ll铆t谩s谩hoz: M茅gse.</string>
+    <string name="title_activity_charts">Aktivit谩sa 茅s alv谩sa</string>
+    <string name="title_activity_set_alarm">脡breszt艖k be谩ll铆t谩sa</string>
+    <string name="controlcenter_start_configure_alarms">脡breszt艖k be谩ll铆t谩sa</string>
+    <string name="title_activity_alarm_details">脡breszt茅s r茅szletei</string>
+    <string name="alarm_sun_short">Vas</string>
+    <string name="alarm_mon_short">H茅t</string>
+    <string name="alarm_tue_short">Ked</string>
+    <string name="alarm_wed_short">Sze</string>
+    <string name="alarm_thu_short">Cs眉</string>
+    <string name="alarm_fri_short">P茅n</string>
+    <string name="alarm_sat_short">Szo</string>
+    <string name="alarm_smart_wakeup">Okos 茅breszt茅s</string>
+    <string name="user_feedback_miband_set_alarms_failed">Hiba t枚rt茅nt az 茅breszt茅sek be谩ll铆t谩sakor, pr贸b谩ld 煤jra.</string>
+    <string name="user_feedback_miband_set_alarms_ok">脡breszt茅sek elk眉ldve az eszk枚zre.</string>
+    <string name="chart_no_data_synchronize">Nincs adat. Szinkroniz谩lja az eszk枚zt\?</string>
+    <string name="user_feedback_miband_activity_data_transfer">%1$s adat 谩tvitele %2$s-t艖l kezdve</string>
+    <string name="miband_prefs_fitness_goal">Napi l茅p茅sc茅l</string>
+    <string name="dbaccess_error_executing">Hiba \'%1$s\' v茅grehajt谩sakor</string>
+    <string name="controlcenter_start_activitymonitor">Aktivit谩s</string>
+    <string name="cannot_connect">Nem lehet kapcsol贸dni: %1$s</string>
+    <string name="installer_activity_unable_to_find_handler">A telep铆t茅shez sz眉ks茅ges seg茅df谩jl nem tal谩lhat贸.</string>
+    <string name="pbw_install_handler_unable_to_install">Nem siker眉lt telep铆teni a f谩jlt: %1$s</string>
+    <string name="pbw_install_handler_hw_revision_mismatch">Nem lehet telep铆teni a firmware-t: nem kompatibilis a Pebble hardver verzi贸j谩val.</string>
+    <string name="installer_activity_wait_while_determining_status">K茅rem v谩rjon a telep铆t茅si 谩llapot meg谩llap铆t谩s谩ig鈥�</string>
+    <string name="notif_battery_low_title">Alacsony az eszk枚z akkumul谩tor t枚lt茅se!</string>
+    <string name="notif_battery_low_percent">%1$s fennmarad贸 akkumul谩tor t枚lt茅s: %2$s%%</string>
+    <string name="notif_battery_low_bigtext_last_charge_time">Utolj谩ra t枚ltve: %s \n</string>
+    <string name="notif_battery_low_bigtext_number_of_charges">T枚lt茅sek sz谩ma: %s</string>
+    <string name="sleepchart_your_sleep">Alv谩s</string>
+    <string name="weeksleepchart_sleep_a_week">Heti alv谩s</string>
+    <string name="weeksleepchart_today_sleep_description">Mai alv谩s, c茅l: %1$s</string>
+    <string name="weekstepschart_steps_a_week">Heti l茅p茅s</string>
+    <string name="activity_sleepchart_activity_and_sleep">Aktivit谩s</string>
+    <string name="updating_firmware">Firmware telep铆t茅se鈥�</string>
+    <string name="fwapp_install_device_not_ready">A f谩jlt nem lehet telep铆teni, a k茅sz眉l茅k nincs k茅szen.</string>
+    <string name="installhandler_firmware_name">%1$s: %2$s %3$s</string>
+    <string name="miband_fwinstaller_compatible_version">Kompatibilis verzi贸</string>
+    <string name="miband_fwinstaller_untested_version">Nem tesztelt verzi贸!</string>
+    <string name="fwappinstaller_connection_state">Kapcsol贸d谩s az eszk枚zh枚z: %1$s</string>
+    <string name="pbw_installhandler_pebble_firmware">Pebble firmware %1$s</string>
+    <string name="pbwinstallhandler_correct_hw_revision">Helyes hardververzi贸</string>
+    <string name="pbwinstallhandler_incorrect_hw_revision">Hardververzi贸 elt茅r茅s!</string>
+    <string name="pbwinstallhandler_app_item">%1$s (%2$s)</string>
+    <string name="updatefirmwareoperation_updateproblem_do_not_reboot">Probl茅ma volt a firmware k眉ld茅sekor. NE IND脥TSA 脷JRA a Mi Band-ed!</string>
+    <string name="updatefirmwareoperation_metadata_updateproblem">Probl茅ma volt a Firmware metaadatainak k眉ld茅sekor</string>
+    <string name="updatefirmwareoperation_update_complete">A Firmware telep铆t茅se k茅sz</string>
+    <string name="updatefirmwareoperation_update_complete_rebooting">A Firmware telep铆t茅se k茅sz, az eszk枚z 煤jraind铆t谩sa鈥�</string>
+    <string name="updatefirmwareoperation_write_failed">Firmware telep铆t茅se sikertelen</string>
+    <string name="chart_steps">L茅p茅sek</string>
+    <string name="calories">Kal贸ria</string>
+    <string name="distance">T谩vols谩g</string>
+    <string name="clock">脫ra</string>
+    <string name="heart_rate">Pulzus</string>
+    <string name="battery">Akkumul谩tor</string>
+    <string name="liveactivity_live_activity">Aktu谩lis aktivit谩s</string>
+    <string name="weeksteps_today_steps_description">L茅p茅sek ma, c茅l: %1$s</string>
+    <string name="pref_title_dont_ack_transfer">Ne er艖s铆tse meg az aktivit谩sadatok 谩tvitel茅t</string>
+    <string name="pref_summary_dont_ack_transfers">Ha az eszk枚z nem kap meger艖s铆t茅st, az aktivit谩si adatok nem t枚rl艖dnek. Hasznos, ha a GB-t m谩s alkalmaz谩sokkal egy眉tt haszn谩lj谩k.</string>
+    <string name="pref_summary_keep_data_on_device">Az adatokat meghagyja az eszk枚z枚n szinkroniz谩l谩s ut谩n is. Hasznos, ha a GadgetBridge-et egy眉tt haszn谩lja m谩s alkalmaz谩sokkal.</string>
+    <string name="pref_title_low_latency_fw_update">Haszn谩lja a low-latency m贸dot a firmware telep铆t茅sn茅l</string>
+    <string name="pref_summary_low_latency_fw_update">Ez seg铆thet azon az eszk枚z枚k枚n, ahol a firmware telep铆t茅se nem siker眉l.</string>
+    <string name="live_activity_steps_history">L茅p茅sel艖zm茅nyek</string>
+    <string name="live_activity_current_steps_per_minute">Jelenlegi l茅p茅s/perc</string>
+    <string name="live_activity_total_steps">脰sszes l茅p茅s</string>
+    <string name="live_activity_steps_per_minute_history">Percenk茅nti l茅p茅s el艖zm茅nyek</string>
+    <string name="live_activity_start_your_activity">Aktivit谩s kezd茅se</string>
+    <string name="abstract_chart_fragment_kind_activity">Aktivit谩s</string>
+    <string name="abstract_chart_fragment_kind_light_sleep">K枚nny疟 alv谩s</string>
+    <string name="abstract_chart_fragment_kind_deep_sleep">M茅ly alv谩s</string>
+    <string name="abstract_chart_fragment_kind_not_worn">Nem viselt</string>
+    <string name="device_not_connected">Nincs kapcsol贸dva.</string>
+    <string name="user_feedback_all_alarms_disabled">Mindegyik 茅breszt茅s letiltva</string>
+    <string name="pref_title_keep_data_on_device">Aktivit谩sadatok eszk枚z枚n hagy谩sa</string>
+    <string name="miband_fwinstaller_incompatible_version">Nem kompatibilis firmware</string>
+    <string name="fwinstaller_firmware_not_compatible_to_device">Ez a firmware nem kompatibilis az eszk枚z茅vel</string>
+    <string name="miband_prefs_reserve_alarm_calendar">Napt谩ri esem茅nyek sz谩m谩ra fenntartott riaszt谩sok sz谩ma</string>
+    <string name="miband_prefs_hr_sleep_detection">Pulzus szenzor haszn谩lata az alv谩s茅rz茅kel茅s jav铆t谩s谩hoz</string>
+    <string name="miband_prefs_device_time_offset_hours">Eszk枚z id艖eltol谩sa 贸r谩kban (hogy az 茅jszakai m疟szakban dolgoz贸k alv谩s谩t is 茅rz茅kelje)</string>
+    <string name="miband2_prefs_dateformat">D谩tumform谩tum</string>
+    <string name="dateformat_time">Id艖</string>
+    <string name="dateformat_date_time">Id艖 茅s d谩tum</string>
+    <string name="mi2_prefs_goal_notification">C茅l 茅rtes铆t茅s</string>
+    <string name="mi2_prefs_goal_notification_summary">A kark枚t艖 rezegni fog amikor el茅rte a napi l茅p茅sc茅lt</string>
+    <string name="mi2_prefs_display_items">Megjelen铆tend艖 elemek</string>
+    <string name="mi2_prefs_display_items_summary">V谩lassza ki milyen elemek jelenjenek meg a kark枚t艖 kijelz艖j茅n</string>
+    <string name="mi2_prefs_activate_display_on_lift">Kijelz艖 aktiv谩l谩sa felemel茅skor</string>
+    <string name="mi2_prefs_rotate_wrist_to_switch_info">Forgassa el a kark枚t艖t inform谩ci贸 v谩lt谩shoz</string>
+    <string name="mi2_prefs_do_not_disturb">Ne zavarjanak</string>
+    <string name="mi2_prefs_do_not_disturb_summary">A kark枚t艖 nem fog 茅rtes铆t茅seket jelezni am铆g ez akt铆v</string>
+    <string name="mi2_prefs_inactivity_warnings">Inaktivit谩s figyelmeztet茅sek</string>
+    <string name="mi2_prefs_inactivity_warnings_summary">A kark枚t艖 rezegni fog amikor inakt铆v m谩r egy ideje</string>
+    <string name="mi2_prefs_inactivity_warnings_threshold">Inaktivit谩s k眉sz枚b (percben)</string>
+    <string name="mi2_prefs_inactivity_warnings_dnd_summary">Inaktivit谩s figyelmeztet茅sek tilt谩sa adott id艖re</string>
+    <string name="mi2_prefs_do_not_disturb_start">Kezdete</string>
+    <string name="mi2_prefs_do_not_disturb_end">V茅ge</string>
+    <string name="FetchActivityOperation_about_to_transfer_since">Adat谩tvitelre k茅sz眉l: %1$s 贸ta</string>
+    <string name="waiting_for_reconnect">V谩rakoz谩s az 煤jrakapcsol贸d谩sra</string>
+    <string name="activity_prefs_about_you">脰nr艖l</string>
+    <string name="activity_prefs_year_birth">Sz眉let茅si 茅v</string>
+    <string name="activity_prefs_gender">Nem</string>
+    <string name="activity_prefs_height_cm">Magass谩g cm-ben</string>
+    <string name="activity_prefs_weight_kg">Tests煤ly kg-ban</string>
+    <string name="authenticating">Hiteles铆t茅s</string>
+    <string name="authentication_required">Hiteles铆t茅s sz眉ks茅ges</string>
+    <string name="appwidget_text">Zzz</string>
+    <string name="add_widget">Modul hozz谩ad谩sa</string>
+    <string name="activity_prefs_sleep_duration">Prefer谩lt alv谩sid艖 贸r谩ban</string>
+    <string name="appwidget_setting_alarm">脡breszt艖 be谩ll铆tva: %1$02d:%2$02d</string>
+    <string name="device_hw">Hardware v谩ltozat: %1$s</string>
+    <string name="device_fw">Firmware v谩ltozat: %1$s</string>
+    <string name="error_creating_directory_for_logfiles">Hiba a k枚nyvt谩r l茅trehoz谩sakor a napl贸f谩jlok sz谩m谩ra: %1$s</string>
+    <string name="DEVINFO_HR_VER">"HR: "</string>
+    <string name="updatefirmwareoperation_update_in_progress">Firmware telep铆t茅se</string>
+    <string name="updatefirmwareoperation_firmware_not_sent">Nem k眉ld枚tt firmware</string>
+    <string name="charts_legend_heartrate">Pulzus</string>
+    <string name="live_activity_heart_rate">Pulzus</string>
+    <string name="pref_title_pebble_health_store_raw">Nyers adatok t谩rol谩sa az adatb谩zisban</string>
+    <string name="pref_summary_pebble_health_store_raw">Az adatokat eredeti form谩ban t谩rolja, n枚velve az adatb谩zishaszn谩latot a k茅s艖bbi feldolgoz谩s lehet艖v茅 t茅tele 茅rdek茅ben.</string>
+    <string name="action_db_management">Adatkezel茅s</string>
+    <string name="title_activity_db_management">Adatkezel茅s</string>
+    <string name="activity_db_management_import_export_explanation">Az export谩l谩s/import谩l谩s m疟veletek a k枚vetkez艖 k枚nyvt谩r 煤tvonal谩t (l谩sd lentebb) haszn谩lj谩k az eszk枚z茅n. Ez a k枚nyvt谩r el茅rhet艖 m谩s Android alkalmaz谩sok 茅s a sz谩m铆t贸g茅pe sz谩m谩ra. Jegyezze meg, hogy ez a k枚nyv谩r 茅s az 枚sszes tartalmazott f谩jl t枚r枚lve lesz, ha elt谩vol铆tja a Gadgetbridge-et. Az adatok tartalmazz谩k:
+\n Export_preference - glob谩lis be谩ll铆t谩sok
+\n Export_preference_MAC - eszk枚zspecifikus be谩ll铆t谩sok
+\n Gadgetbridge - eszk枚z- 茅s aktivit谩s-adatb谩zis
+\n Gadgetbridge_date - egy d谩tumon export谩lt adatb谩zis
+\n *.gpx - GPS feljegyz茅sek
+\n *.log - napl贸f谩jlok
+\nV谩rhat贸an itt megtal谩lja az export谩lt f谩jlokat (vagy helyezze ide az import谩lni k铆v谩nt f谩jlokat):</string>
+    <string name="activity_db_management_merge_old_title">R茅gi adatb谩zis t枚rl茅se</string>
+    <string name="dbmanagementactivvity_cannot_access_export_path">Nem 茅rhet艖 el az export谩l谩si 煤tvonal. Vegye fel a kapcsolatot a fejleszt艖kkel.</string>
+    <string name="dbmanagementactivity_exported_to">Export谩lva ide: %1$s</string>
+    <string name="dbmanagementactivity_error_exporting_db">Hiba az adatb谩zis export谩l谩sa sor谩n: %1$s</string>
+    <string name="dbmanagementactivity_error_exporting_shared">Hiba a be谩ll铆t谩sok export谩l谩sa sor谩n: %1$s</string>
+    <string name="dbmanagementactivity_import_data_title">Adatok import谩l谩sa?</string>
+    <string name="dbmanagementactivity_overwrite_database_confirmation">Biztosan fel眉l akarja 铆rni a jelenlegi adatokat? Az 枚sszes jelenlegi aktivit谩sadata (ha van) el fog veszni.</string>
+    <string name="dbmanagementactivity_import_successful">Import谩lva.</string>
+    <string name="dbmanagementactivity_error_importing_db">Hiba az adatb谩zis import谩l谩sa sor谩n: %1$s</string>
+    <string name="dbmanagementactivity_error_importing_shared">Hiba a be谩ll铆t谩sok import谩l谩sa sor谩n: %1$s</string>
+    <string name="dbmanagementactivity_delete_activity_data_title">T枚rli az aktivit谩sadatokat\?</string>
+    <string name="dbmanagementactivity_really_delete_entire_db">T茅nyleg t枚r枚lni akarja az eg茅sz adatb谩zist\? Minden aktivit谩sadata 茅s az 枚nr艖l t谩rolt inform谩ci贸 el fog veszni.</string>
+    <string name="dbmanagementactivity_database_successfully_deleted">Adatok t枚r枚lve.</string>
+    <string name="dbmanagementactivity_db_deletion_failed">Az adatb谩zis t枚rl茅se sikertelen.</string>
+    <string name="dbmanagementactivity_delete_old_activity_db">T枚rli a r茅gi aktivit谩sadatb谩zist\?</string>
+    <string name="dbmanagementactivity_delete_old_activitydb_confirmation">Biztosan t枚rli a r茅gi aktivit谩sadatb谩zist\? A nem import谩lt aktivit谩si adatok el fognak veszni.</string>
+    <string name="dbmanagementactivity_old_activity_db_successfully_deleted">R茅gi aktivit谩sadatok t枚r枚lve.</string>
+    <string name="dbmanagementactivity_old_activity_db_deletion_failed">Nem siker眉lt a r茅gi aktivit谩sadatb谩zis t枚rl茅se.</string>
+    <string name="dbmanagementactivity_overwrite">Fel眉l铆r谩s</string>
+    <string name="Cancel">M茅gse</string>
+    <string name="Delete">T枚rl茅s</string>
+    <!--Strings related to Vibration Activity-->
+    <string name="title_activity_vibration">Rezg茅s</string>
+    <!--Strings related to Pebble Pairing Activity-->
+    <string name="title_activity_pebble_pairing">Pebble p谩ros铆t谩sa</string>
+    <string name="pebble_pairing_hint">Egy p谩ros铆t谩si p谩rbesz茅dablak fog megjelennie az Android eszk枚z茅n. Ha ez nem t枚rt茅nik meg, n茅zze meg az 茅rtes铆t茅si s谩vban 茅s fogadja el a p谩ros铆t谩si k茅relmet. Ut谩na fogadja el a k茅relmet a Pebble-n is.</string>
+    <string name="weather_notification_label">Gy艖z艖dj枚n meg r贸la, hogy ez a kin茅zet enged茅lyezve van az Id艖j谩r谩s 茅rtes铆t茅s alkalmaz谩sban, hogy id艖j谩r谩si inform谩ci贸kat kapjon a Pebble k茅sz眉l茅k茅n. \n \nItt nincs sz眉ks茅g konfigur谩ci贸ra. \n \nA Pebble rendszer id艖j谩r谩s applik谩ci贸j谩t az alkalmaz谩skezel茅sb艖l enged茅lyezheti. \n \nA t谩mogatott 贸rafel眉letek automatikusan megjelen铆tik az id艖j谩r谩st.</string>
+    <string name="pref_title_setup_bt_pairing">Bluetooth p谩ros铆t谩s enged茅lyez茅se</string>
+    <string name="pref_summary_setup_bt_pairing">Kapcsolja ki ezt, ha gondja van a kapcsol贸d谩ssal</string>
+    <string name="unit_metric">Metrikus</string>
+    <string name="unit_imperial">Birodalmi</string>
+    <string name="timeformat_24h">24H</string>
+    <string name="timeformat_am_pm">DE/DU</string>
+    <string name="pref_screen_notification_profile_alarm_clock">脡breszt艖贸ra</string>
+    <string name="StringUtils_sender"> (%1$s)</string>
+    <string name="find_device_you_found_it">Megtal谩lta!</string>
+    <string name="miband2_prefs_timeformat">Mi2: id艖 form谩tum</string>
+    <string name="mi2_fw_installhandler_fw53_hint">Ezt a verzi贸t kell telep铆tenie %1$s firmware telep铆t茅s el艖tt!</string>
+    <string name="mi2_enable_text_notifications">Sz枚veges 茅rtes铆t茅sek</string>
+    <string name="mi2_enable_text_notifications_summary">Telep铆tve kell legyen a firmware &gt;= 1.0.1.28 茅s a Mili_pro.ft*.</string>
+    <string name="off">Ki</string>
+    <string name="mi2_dnd_off">Ki</string>
+    <string name="mi2_dnd_automatic">Automatikus (alv谩s茅rz茅kel茅s)</string>
+    <string name="mi2_dnd_scheduled">Id艖z铆tett (id艖 intervallum)</string>
+    <string name="discovery_attempting_to_pair">P谩ros铆t谩s megk铆s茅rl茅se ezzel: %1$s</string>
+    <string name="discovery_bonding_failed_immediately">A p谩ros铆t谩s a(z) %1$s eszk枚zzel v谩ratlanul megszakadt.</string>
+    <string name="discovery_trying_to_connect_to">Kapcsol贸d谩s megk铆s茅rl茅se ezzel: %1$s</string>
+    <string name="discovery_enable_bluetooth">Enged茅lyezze a Bluetooth-ot az eszk枚z枚k felder铆t茅s茅hez.</string>
+    <string name="discovery_successfully_bonded">P谩ros铆tva ezzel: %1$s.</string>
+    <string name="discovery_pair_title">%1$s p谩ros铆t谩sa?</string>
+    <string name="discovery_pair_question">V谩lassza ki a p谩ros铆t谩st az eszk枚z p谩ros铆t谩s谩hoz. Ha ez nem siker眉l, pr贸b谩lja meg 煤jra p谩ros铆t谩s n茅lk眉l.</string>
+    <string name="discovery_yes_pair">P谩ros铆t谩s</string>
+    <string name="discovery_dont_pair">Ne p谩ros铆tsa</string>
+    <string name="action_donate">Adom谩nyoz谩s</string>
+    <string name="controlcenter_connect">Kapcsol贸d谩s鈥�</string>
+    <string name="pref_title_weather">Id艖j谩r谩s</string>
+    <string name="pref_auto_fetch">Aktivit谩sadatok automatikus lek茅rdez茅se</string>
+    <string name="horizontal">V铆zszintes</string>
+    <string name="vertical">F眉gg艖leges</string>
+    <string name="controlcenter_change_led_color">LED sz铆n茅nek v谩ltoztat谩sa</string>
+    <string name="controlcenter_change_fm_frequency">FM frekvencia v谩ltoztat谩sa</string>
+    <string name="pref_title_rtl">Jobbr贸l-balra</string>
+    <string name="debugactivity_really_factoryreset_title">Biztosan vissza szeretn茅 谩ll铆tani a gy谩ri be谩ll铆t谩sokat?</string>
+    <string name="zetime_analog_mode">Anal贸g m贸d</string>
+    <string name="zetime_handmove_display_summary">Forgassa a csukl贸j谩t, hogy be- 茅s kikapcsolja k茅perny艖t.</string>
+    <string name="zetime_date_format">D谩tum form谩tum</string>
+    <string name="zetime_prefs_inactivity_repetitions">Ism茅tl茅sek</string>
+    <string name="zetime_prefs_inactivity_mo">H茅tf艖</string>
+    <string name="zetime_prefs_inactivity_tu">Kedd</string>
+    <string name="zetime_prefs_inactivity_we">Szerda</string>
+    <string name="zetime_prefs_inactivity_th">Cs眉t枚rt枚k</string>
+    <string name="zetime_prefs_inactivity_fr">P茅ntek</string>
+    <string name="zetime_prefs_inactivity_sa">Szombat</string>
+    <string name="zetime_prefs_inactivity_su">Vas谩rnap</string>
+    <string name="zetime_signaling_none">N茅ma</string>
+    <string name="zetime_signaling_vibrate">Folytonos rezg茅s</string>
+    <string name="zetime_signaling_beep">Folytonos csipog谩s</string>
+    <string name="zetime_signaling_vibrate_beep">Folytonos rezg茅s 茅s csipog谩s</string>
+    <string name="pref_screen_notification_profile_missed_call">Nem fogadott h铆v谩s 茅rtes铆t茅s</string>
+    <string name="pref_screen_notification_profile_calendar">Napt谩r 茅rtes铆t茅s</string>
+    <string name="title_activity_device_specific_settings">Eszk枚zspecifikus be谩ll铆t谩sok</string>
+    <string name="controlcenter_calibrate_device">Eszk枚z kalibr谩l谩sa</string>
+    <string name="mi2_prefs_button_press_count">Gombnyom谩sok sz谩ma</string>
+    <string name="pref_summary_pebble_enable_bgjs">Ha enged茅lyezve van, akkor lehet艖v茅 teszi, hogy az 贸rafel眉letek id艖j谩r谩si, akkumul谩tor-inform谩ci贸kat 茅s egyebeket jelen铆tsenek meg.</string>
+    <string name="interval_one_minute">percenk茅nt</string>
+    <string name="interval_five_minutes">5 percenk茅nt</string>
+    <string name="interval_ten_minutes">10 percenk茅nt</string>
+    <string name="interval_thirty_minutes">30 percenk茅nt</string>
+    <string name="interval_one_hour">贸r谩nk茅nt</string>
+    <string name="pref_title_weather_location">Helysz铆n az id艖j谩r谩shoz (a LineageOS id艖j谩r谩s-szolg谩ltat贸 sz谩m谩ra)</string>
+    <string name="watch9_pairing_tap_hint">Amikor az 贸ra rezeg, r谩zza meg az eszk枚zt, vagy nyomja meg a gombj谩t.</string>
+    <string name="you_slept">脰n %1$s 茅s %2$s k枚z枚tt aludt</string>
+    <string name="you_did_not_sleep">脰n nem aludt</string>
+    <string name="no_limit">Nincs korl谩t</string>
+    <string name="seconds_5">5 m谩sodperc</string>
+    <string name="seconds_10">10 m谩sodperc</string>
+    <string name="seconds_20">20 m谩sodperc</string>
+    <string name="seconds_30">30 m谩sodperc</string>
+    <string name="minutes_1">1 perc</string>
+    <string name="minutes_5">5 perc</string>
+    <string name="minutes_10">10 perc</string>
+    <string name="minutes_30">30 perc</string>
+    <string name="notif_battery_low">%1$s akkumul谩tora mer眉l</string>
+    <string name="notif_battery_low_extended">%1$s akkumul谩tora alacsony szinten: %2$s</string>
+    <string name="debugactivity_really_factoryreset">A gy谩ri be谩ll铆t谩sok vissza谩ll铆t谩sa minden adatot t枚r枚l a p谩ros铆tott eszk枚zr艖l (ha t谩mogatott). A Xiaomi/Huami eszk枚z枚knek emellett megv谩ltozik a Bluetooth MAC-c铆me is, 铆gy 煤j eszk枚zk茅nt jelennek meg a Gadgetbridge sz谩m谩ra.</string>
+    <string name="mi2_prefs_button_press_broadcast_default_value" translatable="false">nodomain.freeyourgadget.gadgetbridge.ButtonPressed</string>
+    <string name="prefs_title_heartrate_measurement_interval">Eg茅sz napos pulzusm茅r茅s</string>
+    <string name="prefs_screen_orientation">K茅perny艖 t谩jol谩sa</string>
+    <string name="pref_summary_sync_calendar">Napt谩r 茅rtes铆t茅sek bekapcsol谩sa, akkor is, ha a k茅sz眉l茅k le van v谩lasztva a telefonr贸l</string>
+    <string name="fw_upgrade_notice_amazfitcor">A(z) %s firmware-t k铆v谩nja telep铆teni az Amazfit Cor k茅sz眉l茅k茅re.
+\n
+\nGy艖z艖dj枚n meg r贸la, hogy a .fw f谩jlt, majd a .res f谩jlt telep铆ti. A k茅sz眉l茅ke az .fw f谩jl telep铆t茅se ut谩n 煤jraindul.
+\n
+\nMegjegyz茅s: Nem kell telep铆tenie a .res f谩jlt, ha pontosan megegyezik a kor谩bban telep铆tettel.
+\n
+\nCSAK SAJ脕T FELEL艕SS脡G脡RE FOLYTASSA!</string>
+    <string name="interval_forty_five_minutes">45 percenk茅nt</string>
+    <string name="interval_fifteen_minutes">15 percenk茅nt</string>
+    <string name="find_my_phone_notification">Tal谩ld meg a telefonom</string>
+    <string name="prefs_hr_alarm_high">Fels艖 hat谩r</string>
+    <string name="prefs_hr_alarm_low">Als贸 hat谩r</string>
+    <string name="zetime_signaling_beep_twice">K茅tszer csipog</string>
+    <string name="zetime_signaling_beep_once">Egyszer csipog</string>
+    <string name="zetime_signaling_vibrate_twice">K茅tszer rezeg</string>
+    <string name="zetime_signaling_vibrate_once">Egyszer rezeg</string>
+    <string name="zetime_date_format_3">HH/NN/脡脡</string>
+    <string name="zetime_date_format_2">NN/HH/脡脡</string>
+    <string name="zetime_date_format_1">脡脡/HH/NN</string>
+    <string name="zetime_calories_type_all">Akt铆van 茅s inakt铆van el茅getett kal贸ri谩k</string>
+    <string name="zetime_calories_type_active">Csak akt铆van el茅getett kal贸ri谩k</string>
+    <string name="zetime_calories_type">Kal贸ria t铆pusa</string>
+    <string name="zetime_handmove_display">K茅zmozg谩s</string>
+    <string name="power_mode_watch">Csak 贸ra</string>
+    <string name="power_mode_saving">Energiatakar茅kos</string>
+    <string name="power_mode_normal">脕ltal谩nos</string>
+    <string name="pref_header_notifications_and_calls">脡rtes铆t茅sek 茅s h铆v谩sok</string>
+    <string name="pref_display_add_device_fab_off">Csak akkor l谩that贸 ha nincs hozz谩adott eszk枚z</string>
+    <string name="pref_display_add_device_fab_on">Mindig l谩that贸</string>
+    <string name="pref_display_add_device_fab">脷j eszk枚z kapcsol贸d谩s gomb</string>
+    <string name="pref_title_vibration_strength">Rezg茅s er艖ss茅ge</string>
+    <string name="pref_title_sync_caldendar">Napt谩r esem茅nyek szinkroniz谩l谩sa</string>
+    <string name="pref_summary_allow_high_mtu">N枚veli az adat谩tvitel sebess茅g茅t, de egyes Android k茅sz眉l茅keken nem t谩mogatott.</string>
+    <string name="pref_title_allow_high_mtu">Magas MTU enged茅lyez茅se</string>
+    <string name="pref_title_use_custom_font">Egy茅ni bet疟t铆pus haszn谩lata</string>
+    <string name="pref_summary_expose_hr">Enged茅lyezi, hogy m谩s alkalmaz谩sok val贸s id艖ben hozz谩f茅rjenek a HR adatokhoz am铆g a Gadgetbridge kapcsol贸dva van</string>
+    <string name="pref_title_pebble_gatt_clientonly">Csak GATT kliens</string>
+    <string name="pref_title_custom_deviceicon">Eszk枚z specifikus 茅rtes铆t茅s ikon megjelen铆t茅se</string>
+    <string name="pref_title_notifications_timeout">Minimum id艖k枚z az 茅rtes铆t茅sek k枚z枚tt</string>
+    <string name="pref_theme_system">Rendszer</string>
+    <string name="title_activity_calblacklist">Tilt贸list谩s napt谩rak</string>
+    <string name="sonyswr12_settings_title">Sony SWR12 be谩ll铆t谩sok</string>
+    <string name="sonyswr12_settings_low_vibration">Alacsony rezg茅s enged茅lyezve</string>
+    <string name="sonyswr12_settings_stamina">Az energiatakar茅kos m贸d be van kapcsolva</string>
+    <string name="sonyswr12_settings_alarm_interval">Intelligens 茅breszt茅si intervallum percekben</string>
+    <string name="sonyswr12_settings_low_vibration_summary">Enged茅lyezze a rezg茅s alacsony intenzit谩s谩t a kark枚t艖n</string>
+    <string name="sonyswr12_settings_stamina_summary">Az energiatakar茅kos 眉zemm贸d kikapcsolja a pulzus id艖szakos automatikus m茅r茅s茅t, 铆gy megn艖 a munkaid艖</string>
+    <string name="sonyswr12_settings_alarm_interval_summary">Az intelligens 茅breszt茅s intervallum a telep铆tett 茅breszt茅s el艖tti intervallum. Ebben az intervallumban az eszk枚z megpr贸b谩lja 茅szlelni az alv谩s legk枚nnyebb f谩zis谩t a felhaszn谩l贸 fel茅breszt茅s茅re</string>
+    <string name="find_lost_device_message">%1$s keres茅se\?</string>
+    <string name="controlcenter_power_off">Kikapcsol谩s</string>
+    <string name="controlcenter_power_off_confirm_title">Kikapcsol谩s</string>
+    <string name="controlcenter_set_alias">脕ln茅v be谩ll铆t谩sa</string>
+    <string name="controlcenter_power_off_confirm_description">Biztosan ki szeretn茅 kapcsolni az eszk枚zt\?</string>
+    <string name="prefs_activity_in_device_card_title_summary">Jelenlegi l茅p茅ssz谩m, t谩vols谩g, vagy alv谩sid艖 megjelen铆t茅se az eszk枚zlapon</string>
+    <string name="activity_prefs_step_length_cm">L茅p茅s hossza cm-ben</string>
+    <string name="prefs_activity_in_device_card_steps_title_summary">脰sszes l茅p茅ssz谩m megjelen铆t茅se</string>
+    <string name="title_activity_notification_management">脡rtes铆t茅si be谩ll铆t谩sok</string>
+    <string name="fw_upgrade_notice_amazfitbip">A(z) %s firmware-t k铆v谩nja telep铆teni az Amazfit Bip k茅sz眉l茅k茅re.
+\n
+\nGy艖z艖dj枚n meg r贸la, hogy a .fw f谩jlt, majd a .res f谩jlt 茅s v茅g眉l a .gps f谩jlt telep铆ti. A k茅sz眉l茅ke az .fw f谩jl telep铆t茅se ut谩n 煤jraindul.
+\n
+\nMegjegyz茅s: Nem kell telep铆tenie a .res 茅s .gps f谩jlokat, ha ezek a f谩jlok pontosan megegyeznek a kor谩bban telep铆tettekkel.
+\n
+\nCSAK SAJ脕T FELEL艕SS脡G脡RE FOLYTASSA!</string>
+    <string name="fw_upgrade_notice_amazfitbip_lite">A(z) %s firmware-t k铆v谩nja telep铆teni az Amazfit Bip Lite k茅sz眉l茅k茅re.
+\n
+\nGy艖z艖dj枚n meg r贸la, hogy a .fw f谩jlt, majd a .res f谩jlt telep铆ti. A k茅sz眉l茅ke az .fw f谩jl telep铆t茅se ut谩n 煤jraindul.
+\n
+\nMegjegyz茅s: Nem kell telep铆tenie a .res f谩jlt, ha pontosan megegyezik a kor谩bban telep铆tettel.
+\n
+\nCSAK SAJ脕T FELEL艕SS脡G脡RE FOLYTASSA!</string>
+    <string name="calendar_three_months">3 h贸nap</string>
+    <string name="device_card_activity_card_title_summary">V谩lassza ki, hogy mely tev茅kenys茅gek r茅szletei jelenjenek meg az eszk枚zlapon</string>
+    <string name="prefs_activity_in_device_card_title">Tev茅kenys茅gi adatok megjelen铆t茅se az eszk枚zlapon</string>
+    <string name="prefs_activity_in_device_card_sleep_title">Alv谩s</string>
+    <string name="prefs_activity_in_device_card_sleep_title_summary">Az alv谩s id艖tartam谩nak megjelen铆t茅se</string>
+    <string name="prefs_activity_in_device_card_distance_title_summary">A t谩vols谩got a l茅p茅sek 茅s a l茅p茅shossz alapj谩n sz谩m铆tja ki (be谩ll铆that贸 a Be谩ll铆t谩sok - 脰nr贸l men眉pontban)</string>
+    <string name="battery_detail_activity_title">Akkumul谩tor-inform谩ci贸</string>
+    <string name="calendar_day">Nap</string>
+    <string name="calendar_week">H茅t</string>
+    <string name="calendar_two_weeks">K茅t h茅t</string>
+    <string name="calendar_month">H贸nap</string>
+    <string name="calendar_six_months">6 h贸nap</string>
+    <string name="calendar_year">脡v</string>
+    <string name="device_card_activity_card_title">Tev茅kenys茅gi adatok az eszk枚zlapon</string>
+    <string name="battery_level">Az akkumul谩tor t枚lt枚tts茅gi szintje</string>
+    <string name="whitelist_all_for_notifications">脰sszes 茅rtes铆t茅s feh茅rlist谩ra</string>
+    <string name="blacklist_all_for_notifications">脰sszes 茅rtes铆t茅s tilt贸list谩ra</string>
+    <string name="check_all_applications">Az 枚sszes alkalmaz谩s kijel枚l茅se</string>
+    <string name="uncheck_all_applications">Az 枚sszes alkalmaz谩s kijel枚l茅s茅nek t枚rl茅se</string>
+    <string name="about_activity_title_generic">A Gadgetbridge n茅vjegye</string>
+    <string name="appmanager_app_share">Megoszt谩s</string>
+    <string name="button_watchface_select_image">H谩tt茅rk茅p megv谩ltoztat谩sa</string>
+    <string name="about_links">Hivatkoz谩sok</string>
+    <string name="about_contributors">K枚zrem疟k枚d艖k</string>
+    <string name="open_fw_installer_info_text_title">F谩jltelep铆t艖</string>
+    <string name="about_additional_device_support">Tov谩bbi eszk枚zt谩mogat谩s</string>
+    <string name="pref_rtl_max_line_length">Jobbr贸l-balra maxim谩lis sorhossz</string>
+    <string name="pref_summary_rtl">Enged茅lyezze, ha az eszk枚ze nem jelen铆ti meg rendesen a jobbr贸l-balra nyelveket</string>
+    <string name="about_version">Verzi贸: %s</string>
+    <string name="watchface_setting_button_toggle_backlight">H谩tt茅rvil谩g铆t谩s bekapcsol谩sa</string>
+    <string name="pref_world_clocks_title">Vil谩g贸r谩k</string>
+    <string name="pref_world_clocks_summary">脫r谩k be谩ll铆t谩sa m谩s id艖z贸n谩khoz</string>
+    <string name="language_and_region_prefs">Nyelvi- 茅s r茅gi贸be谩ll铆t谩sok</string>
+    <string name="about_title">N茅vjegy</string>
+    <string name="about_core_team_title">F艖 csapat (az els艖 k贸d-hozz谩j谩rul谩s sorrendj茅ben)</string>
+    <string name="lefun_prefs_interface_language_title">Fel眉let nyelve</string>
+    <string name="zetime_title_alarm_signaling">Jelz茅s t铆pus谩nak be谩ll铆t谩sa az 茅breszt艖h枚z</string>
+    <string name="widget_set_alarm_after">脡breszt艖 be谩ll铆t谩sa ennyi id艖 m煤lva:</string>
+    <string name="menuitem_alarm">脡breszt茅s</string>
+    <string name="appwidget_not_connected">Nincs kapcsol贸dva, 茅breszt艖 nincs be谩ll铆tva.</string>
+    <string name="zetime_analog_mode_handsandsteps">Kezek 茅s l茅p茅sek</string>
+    <string name="find_lost_device_you_found_it">Megvan!</string>
+    <string name="activity_db_management_empty_DB">Adatb谩zis 眉r铆t茅se</string>
+    <string name="activity_db_management_empty_db_warning">Figyelem! Ennek a gombnak a megnyom谩s谩val t枚rli az adatb谩zist, 茅s el枚lr艖l kezdi.</string>
+    <string name="watchface_dialog_widget_background">H谩tt茅r</string>
+    <string name="watchface_dialog_widget_timezone">Id艖z贸na</string>
+    <string name="controlcenter_toggle_details">R茅szletek megjelen铆t茅se/elrejt茅se</string>
+    <string name="preferences_fm_frequency">FM frekvencia</string>
+    <string name="watchface_dialog_widget_width">Modul sz茅less茅ge (pixelben)</string>
+    <string name="zetime_analog_mode_hands">Csak kezek</string>
+    <string name="share_log">Napl贸 megoszt谩sa</string>
+    <string name="about_description_generic">Felh艖mentes 茅s szabad forr谩sk贸d煤 kiad谩s, amely helyettes铆ti a gy谩rt贸k z谩rt forr谩sk贸don alapul贸 Android k眉ty眉 alkalmaz谩sait.</string>
+    <string name="application_name_banglejs_main">Bangle.js Gadgetbridge</string>
+    <string name="title_activity_controlcenter_banglejs_main">Bangle.js Gadgetbridge</string>
+    <string name="about_activity_title_banglejs_main">A Bangle.js Gadgetbridge n茅vjegye</string>
+    <string name="watchface_dialog_widget_color_white">Feh茅r</string>
+    <string name="watchface_dialog_widget_color">Sz铆n</string>
+    <string name="watchface_dialog_widget_x_coordinate">X koordin谩ta (max 240)</string>
+    <string name="watchface_dialog_widget_y_coordinate">Y koordin谩ta (max 240)</string>
+    <string name="watchface_setting_button_toggle_widgets">Modulok megjelen铆t茅se/elrejt茅se</string>
+    <string name="dialog_hide">Elrejt茅s</string>
+    <string name="activity_DB_test_export_message">Adatb谩zis export谩l谩sa鈥�</string>
+    <string name="activity_DB_empty_button">Adatb谩zis 眉r铆t茅se</string>
+    <string name="controlcenter_set_parent_folder">Sz眉l艖mappa be谩ll铆t谩sa</string>
+    <string name="pref_media_playpause">Lej谩tsz谩s elind铆t谩sa/meg谩ll铆t谩sa</string>
+    <string name="watchface_dialog_widget_cat_generic">脕ltal谩nos</string>
+    <string name="watchface_dialog_widget_color_black">Fekete</string>
+    <string name="watchface_dialog_title_widget">Modul be谩ll铆t谩sok</string>
+    <string name="watchface_dialog_widget_cat_custom_widget">Egy茅ni modul</string>
+    <string name="watchface_dialog_widget_type">T铆pus</string>
+    <string name="error_setting_parent_folder">Hiba a sz眉l艖mappa be谩ll铆t谩sakor: %s</string>
+    <string name="preferences_led_color">LED sz铆n</string>
+    <string name="controlcenter_set_preferences">Eszk枚zbe谩ll铆t谩sok</string>
+    <string name="reminder_message">脺zenet</string>
+    <string name="reminder_date">D谩tum</string>
+    <string name="prefs_relaxation_reminder_title">Pihen茅si eml茅keztet艖</string>
+    <string name="pref_screen_notification_profile_event_reminder">Esem茅nyeml茅keztat艖</string>
+    <string name="reminder_once">Egyszer</string>
+    <string name="reminder_delete_confirm_description">Biztosan t枚r枚lni szeretn茅 az eml茅keztet艖t\?</string>
+    <string name="title_activity_reminder_details">Eml茅keztet艖 r茅szletei</string>
+    <string name="title_activity_set_reminders">Eml茅keztet艖k be谩ll铆t谩sa</string>
+    <string name="watchface_widget_type_battery">Akkumul谩tor</string>
+    <string name="reminder_every_week">Minden h茅ten</string>
+    <string name="reminder_every_month">Minden h贸napban</string>
+    <string name="hr_widget_battery">Akkumul谩tor</string>
+    <string name="reminder_repeat">Ism茅tl茅s</string>
+    <string name="controlcenter_start_configure_reminders">Eml茅keztet艖k be谩ll铆t谩sa</string>
+    <string name="reminder_delete_confirm_title">Eml茅keztet艖 t枚rl茅se</string>
+    <string name="lefun_prefs_hydration_reminder_title">Hidrat谩l谩si eml茅keztet艖</string>
+    <string name="menuitem_eventreminder">Esem茅nyeml茅keztet艖</string>
+    <string name="reminder_time">Id艖</string>
+    <string name="reminder_every_day">Minden nap</string>
+    <string name="reminder_every_year">Minden 茅vben</string>
+    <string name="sports_activity_quick_filter_last_week">El艖z艖 h茅t</string>
+    <string name="sports_activity_quick_filter_this_month">Ez a h贸nap</string>
+    <string name="sports_activity_confirm_delete_title">%d aktivit谩s t枚rl茅se</string>
+    <string name="sports_activity_quick_filter_last_month">El艖z艖 h贸nap</string>
+    <string name="sports_activity_quick_filter_30days">30 nap</string>
+    <string name="sports_activity_quick_filter_select">Id艖szak</string>
+    <string name="activity_summary_detail">Sportaktivit谩s r茅szletei</string>
+    <string name="sports_activity_quick_filter_this_week">Ez a h茅t</string>
+    <string name="sports_activity_confirm_delete_description">Biztosan t枚r枚lni szeretne %d aktivit谩st\?</string>
+    <string name="sports_activity_quick_filter_7days">7 nap</string>
+    <string name="activity_summaries">Sportaktivit谩sok</string>
+    <string name="pref_title_support_voip_calls">VoIP alkalmaz谩s h铆v谩sainak enged茅lyez茅se</string>
+    <string name="pref_blacklist_calendars">Napt谩rak tilt贸list谩z谩sa</string>
+    <string name="pref_title_auto_export_location">Export谩l谩s helye</string>
+    <string name="pref_title_auto_export_enabled">Automatikus export谩l谩s enged茅lyezve</string>
+    <string name="pref_header_auto_export">Automatikus export谩l谩s</string>
+    <string name="alarm_snooze">Szundi</string>
+    <string name="devicetype_miband">Mi Band</string>
+    <string name="devicetype_miband2">Mi Band 2</string>
+    <string name="sony_automatic_power_off">Automatikus kikapcsol谩s</string>
+    <string name="quick_alarm">Gyors 茅breszt茅s</string>
+    <string name="sony_automatic_power_off_1_hour">1 贸ra</string>
+    <string name="activity_prefs_goal_fat_burn_time_minutes">Napi c茅l: zs铆r茅get茅s ideje percben</string>
+    <string name="title_activity_controlcenter_main_nightly">Korai hozz谩f茅r茅s疟 Gadgetbridge kiad谩s</string>
+    <string name="sony_automatic_power_off_5_min">5 perc</string>
+    <string name="sony_speak_to_chat_sensitivity_auto">Automatikus</string>
+    <string name="reminder_time_every_day">%1$s, minden nap</string>
+    <string name="menuitem_activity">Edz茅sel艖zm茅nyek</string>
+    <string name="widget_listing_label">脕llapot 茅s riaszt谩sok</string>
+    <string name="dual_band">K茅ts谩vos</string>
+    <string name="devicetype_miband7">Xiaomi Smart Band 7</string>
+    <string name="activity_prefs_activetime_minutes">Napi c茅l: akt铆v id艖 percben</string>
+    <string name="hr_appname_workout">Edz茅s</string>
+    <string name="pref_workout_keep_screen_on_title">K茅perny艖 bekapcsolva tart谩sa edz茅s k枚zben</string>
+    <string name="activity_summary_yesterday">Tegnap</string>
+    <string name="devicetype_miband3">Mi Band 3</string>
+    <string name="title_activity_controlcenter_main_nopebble">Korai hozz谩f茅r茅s疟 Gadgetbiridge kiad谩s, Pebble szolg谩ltat贸 n茅lk眉l</string>
+    <string name="pref_workout_send_gps_title">GPS k眉ld茅se edz茅s k枚zben</string>
+    <string name="menuitem_total_workout">脰sszes edz茅s</string>
+    <string name="sony_equalizer_band_6300">6.3k</string>
+    <string name="menuitem_workout_history">Edz茅sel艖zm茅nyek</string>
+    <string name="appwidget_sleep_alarm_widget_label">脡breszt茅s</string>
+    <string name="menuitem_workout">Edz茅s</string>
+    <string name="pref_show_changelog_summary">A Gadgetbridge friss铆t茅se ut谩ni v谩ltoz谩snapl贸 megjelen铆t茅se az utols贸 verzi贸 贸ta</string>
+    <string name="about_activity_title_main_nopebble">Korai hozz谩f茅r茅s疟 Gadgetbiridge kiad谩s, Pebble szolg谩ltat贸 n茅lk眉l n茅vjegye</string>
+    <string name="activity_prefs_goal_standing_time_minutes">Napi c茅l: 谩ll谩si id艖 percben</string>
+    <string name="automatic">Automatikus</string>
+    <string name="title_activity_LenovoWatch_calibration">Watch X Plus kalibr谩ci贸</string>
+    <string name="activity_prefs_calories_burnt">Napi c茅l: el茅getett kal贸ri谩k</string>
+    <string name="title_activity_set_contacts">N茅vjegyek be谩ll铆t谩sa</string>
+    <string name="sony_automatic_power_off_when_taken_off">Lev茅telkor</string>
+    <string name="activity_summary_today">Ma</string>
+    <string name="prefs_autolight">Automatikus f茅ny</string>
+    <string name="pref_header_workout">Edz茅s</string>
+    <string name="menuitem_workout_status">Edz茅s谩llapot</string>
+    <string name="changelog_ok_button">OK</string>
+    <string name="devicetype_amazfit_band7">Amazfit Band 7</string>
+    <plurals name="widget_alarm_target_hours">
+        <item quantity="one">%d 贸ra</item>
+        <item quantity="other">%d 贸ra</item>
+    </plurals>
+    <string name="about_activity_title_main_nightly">A korai hozz谩f茅r茅s疟 Gadgetbridge kiad谩s n茅vjegye</string>
+    <string name="single_band">Egys谩vos</string>
+    <string name="menuitem_last_workout">Utols贸 edz茅s</string>
+    <string name="pref_show_changelog">V谩ltoz谩snapl贸 megjelen铆t茅se indul谩skor</string>
+    <string name="pref_title_device_spec_settings_show_raw_graph">NYERS adatok megjelen铆t茅se az aktivit谩sgrafikonon</string>
+    <string name="changelog_title">脷jdons谩gok</string>
+    <string name="about_activity_title_banglejs_nightly">A Bangle.js Gadgetbridge (korai hozz谩f茅r茅s疟 kiad谩s) n茅vjegye</string>
+    <string name="sony_automatic_power_off_3_hour">3 贸ra</string>
+    <string name="activity_prefs_alarm_min_heart_rate">Minimum pulzus</string>
+    <string name="yesterdays_activity">Tegnapi aktivit谩s</string>
+    <string name="activity_prefs_alarm_max_heart_rate">Maximum pulzus</string>
+    <string name="zetime_activity_tracking">Aktivit谩sm茅r茅s</string>
+    <string name="sony_equalizer_band_16000">16k</string>
+    <string name="devicetype_miband5">Mi Band 5</string>
+    <string name="activity_filter_to_placeholder">ma</string>
+    <string name="changelog_full_title">V谩ltoz谩snapl贸</string>
+    <string name="activity_prefs_distance_meters">Napi c茅l: t谩vols谩g m茅terben</string>
+    <string name="steps_streaks_total_steps_average_hint">脰sszesen %d l茅p茅s naponta</string>
+    <string name="pref_workout_detection_ask_first">K茅rdezzen el艖sz枚r</string>
+    <string name="title_activity_controlcenter_banglejs_nightly">Bangle.js Gadgetbridge (korai hozz谩f茅r茅s疟 kiad谩s)</string>
+    <string name="sony_equalizer_band_1000">1k</string>
+    <string name="pref_workout_detection_enabled">脡szlel茅s enged茅lyez茅se</string>
+    <string name="devicetype_amazfit_band5">Amazfit Band 5</string>
+    <string name="pref_workout_detection_time">脡szlel茅s el艖tti akt铆v percek</string>
+    <string name="pref_workout_detection_categories_title">Edz茅s kateg贸ri谩k</string>
+    <string name="debugactivity_confirm_remove_device_preferences_title">Elt谩vol铆tja az eszk枚zbe谩ll铆t谩sokat\?</string>
+    <string name="changelog_show_full">T枚bb鈥�</string>
+    <string name="action_changelog">V谩ltoz谩snapl贸</string>
+    <string name="hr_widget_calories">Kal贸ri谩k</string>
+    <string name="pref_header_sony_equalizer_bands">Kark枚t艖k</string>
+    <string name="pref_workout_detection_sensitivity">脡rz茅kenys茅g</string>
+    <string name="devicetype_miband4">Mi Band 4</string>
+    <string name="caloriesBurnt">Kal贸ri谩k</string>
+    <string name="sony_equalizer_band_2500">2.5k</string>
+    <string name="sony_equalizer_band_400">400</string>
+    <string name="calories_unit">kcal</string>
+    <string name="devicetype_miband6">Mi Band 6</string>
+    <string name="sony_automatic_power_off_30_min">30 perc</string>
+    <string name="watchface_widget_type_calories">Kal贸ri谩k</string>
+    <string name="prefs_autoheartrate">Automatikus pulzus</string>
+    <string name="devicetype_roidmi3">Roidmi 3</string>
+    <string name="menuitem_pai">PAI</string>
+    <string name="devicetype_amazfit_bips">Amazfit Bip S</string>
+    <string name="Speed">Sebess茅g</string>
+    <string name="japanese">Jap谩n</string>
+    <string name="pref_header_charts">Diagram be谩ll铆t谩sok</string>
+    <string name="menuitem_alipay">Alipay</string>
+    <string name="italian">Olasz</string>
+    <string name="devicetype_roidmi">Roidmi</string>
+    <string name="russian">Orosz</string>
+    <string name="hr_widget_date">D谩tum</string>
+    <string name="menuitem_hr">Pulzus</string>
+    <string name="czesh">Cseh</string>
+    <string name="devicetype_watchx">Watch X</string>
+    <string name="menuitem_more">T枚bb</string>
+    <string name="korean">Koreai</string>
+    <string name="english">Angol</string>
+    <string name="devicetype_watchxplus">Watch X Plus</string>
+    <string name="devicetype_unknown">Ismeretlen eszk枚z</string>
+    <string name="kind_resources">Er艖forr谩sok</string>
+    <string name="warning">Figyelmeztet茅s!</string>
+    <string name="activity_summaries_statistics">Statisztika</string>
+    <string name="activity_type_biking">Ker茅kp谩roz谩s</string>
+    <string name="german">N茅met</string>
+    <string name="Swimming">脷sz谩s</string>
+    <string name="devicetype_amazfit_bip">Amazfit Bip</string>
+    <string name="hr_widget_nothing">Semmi</string>
+    <string name="activity_type_walking">S茅ta</string>
+    <string name="qhybrid_offset_timezone">id艖z贸na eltol谩sa ennyivel:</string>
+    <string name="turkish">T枚r枚k</string>
+    <string name="descentMeters">Lejt艖</string>
+    <string name="watch9_time_hours">脫ra:</string>
+    <string name="hebrew">H茅ber</string>
+    <string name="devicetype_watch9">Watch 9</string>
+    <string name="activity_prefs_chart_min_heart_rate">Minimum pulzus</string>
+    <string name="widget_5_minutes">5 perc</string>
+    <string name="maxAltitude">Maximum</string>
+    <string name="devicetype_pinetime_jf">PineTime (JF Firmware)</string>
+    <string name="minAltitude">Minimum</string>
+    <string name="arabic">Arab</string>
+    <string name="maxSpeed">Maximum</string>
+    <string name="spanish">Spanyol</string>
+    <string name="polish">Lengyel</string>
+    <string name="_pebble_watch_mute">N茅m铆t谩s</string>
+    <string name="maximum_duration">Id艖tartam</string>
+    <string name="romanian">Rom谩n</string>
+    <string name="devicetype_makibes_f68">Makibes F68</string>
+    <string name="menuitem_settings">Be谩ll铆t谩sok</string>
+    <string name="activity_filter_name_contains">C铆mke</string>
+    <string name="_pebble_watch_reply">V谩lasz</string>
+    <string name="menuitem_status">脕llapot</string>
+    <string name="kind_firmware">Firmware</string>
+    <string name="hr_widget_steps">L茅p茅sek</string>
+    <string name="cm">cm</string>
+    <string name="greek">G枚r枚g</string>
+    <string name="menuitem_worldclock">Vil谩g贸ra</string>
+    <string name="activity_type_unknown">Ismeretlen aktivit谩s</string>
+    <string name="seconds">mp</string>
+    <string name="mi2_prefs_button_actions_summary">Gombnyom谩s m疟veletek megad谩sa</string>
+    <string name="meters">m</string>
+    <string name="hr_widget_heart_rate">Pulzus</string>
+    <string name="activity_type_yoga">J贸ga</string>
+    <string name="kind_watchface">脫rafel眉let</string>
+    <string name="watch9_time_minutes">Perc:</string>
+    <string name="menuitem_music">Zene</string>
+    <string name="prefs_find_phone">Telefon keres茅se</string>
+    <string name="devicetype_amazfit_cor">Amazfit Cor</string>
+    <string name="pref_invalid_frequency_title">脡rv茅nytelen frekvencia</string>
+    <string name="activity_type_activity">Aktivit谩s</string>
+    <string name="activity_db_management_exportimport_label">Export谩l谩s 茅s import谩l谩s</string>
+    <string name="devicetype_amazfit_trex">Amazfit T-Rex</string>
+    <string name="ok">OK</string>
+    <string name="kind_font">Bet疟t铆pus</string>
+    <string name="devicetype_test">Tesz eszk枚z</string>
+    <string name="menuitem_breathing">L茅legz茅s</string>
+    <string name="widget_10_minutes">10 perc</string>
+    <string name="french">Francia</string>
+    <string name="activity_type_running">Fut谩s</string>
+    <string name="kind_invalid">脡rv茅nytelen adatok</string>
+    <string name="select_all">脰sszes kiv谩laszt谩sa</string>
+    <string name="devicetype_exrizu_k8">Exrizu K8</string>
+    <string name="menuitem_weather">Id艖j谩r谩s</string>
+    <string name="pref_title_auto_export_interval">Export谩l谩si id艖k枚z</string>
+    <string name="menuitem_notifications">脡rtes铆t茅sek</string>
+    <string name="qhybrid_offset_time_by">id艖 eltol谩sa ennyivel:</string>
+    <string name="activity_DB_delete_legacy_button">R茅gi adatb谩zis t枚rl茅se</string>
+    <string name="watch9_time_seconds">M谩sodperc:</string>
+    <string name="activity_type_jump_roping">Ugr谩l贸k枚t茅l</string>
+    <string name="mi2_prefs_button_actions">Gomb m疟veletek</string>
+    <string name="laps">K枚r枚k</string>
+    <string name="Activity">Aktivit谩s</string>
+    <string name="menuitem_timer">Visszasz谩ml谩l谩s</string>
+    <string name="devicetype_teclast_h30">Teclast H30</string>
+    <string name="notification_channel_name">脕ltal谩nos</string>
+    <string name="devicetype_qhybrid">Fossil Q Hybrid</string>
+    <string name="swedish">Sv茅d</string>
+    <string name="activity_DB_ExportButton">Adatok export谩l谩sa</string>
+    <string name="menuitem_stress">Stressz</string>
+    <string name="dutch">Holland</string>
+    <string name="hungarian">Magyar</string>
+    <string name="menuitem_compass">Ir谩nyt疟</string>
+    <string name="activity_type_deep_sleep">M茅ly alv谩s</string>
+    <string name="no_data">Nincs adat</string>
+    <string name="widget_20_minutes">20 perc</string>
+    <string name="menuitem_unknown">Ismeretlen</string>
+    <string name="hr_widget_active_minutes">Akt铆v percek</string>
+    <string name="activity_detail_duration_label">Id艖tartam</string>
+    <string name="mi2_prefs_button_action">Gombm疟velet enged茅lyez茅se</string>
+    <string name="indonesian">Indon茅ziai</string>
+    <string name="average">脕tlag: %1$s</string>
+    <string name="activity_DB_import_button">Adatok import谩l谩sa</string>
+    <string name="activity_type_treadmill">Fut贸pad</string>
+    <string name="prefs_sensors_button_bp_calibration">Kalibr谩l谩s</string>
+    <string name="pref_summary_auto_export_interval">Export谩l谩s minden %d. 贸r谩ban</string>
+    <string name="pref_theme_dynamic_colors_not_available_warning">A dinamikus sz铆nek nem el茅rhet艖k az eszk枚z茅n, csak az Android 12+ verzi贸k t谩mogatj谩k ezt a funkci贸t. A Gadgetbridge az alap茅rtelmezett Material 3 sz铆neket fogja haszn谩lni.</string>
+    <string name="hr_appname_commute">Ing谩z谩s</string>
+    <string name="averageHR">Pulzus</string>
+    <string name="devicetype_amazfit_gtr">Amazfit GTR</string>
+    <string name="kind_gps">GPS firmware</string>
+    <string name="hr_widget_last_notification">Utols贸 茅rtes铆t茅s</string>
+    <string name="ascentMeters">Emelked艖</string>
+    <string name="km_h">km/h</string>
+    <string name="widget_1_hour">1 贸ra</string>
+    <string name="flatSeconds">Lapos</string>
+    <string name="devicetype_id115">ID115</string>
+    <string name="km">km</string>
+    <string name="watch9_calibration_button">Kalibr谩l谩s</string>
+    <string name="hr_widget_weather">Id艖j谩r谩s</string>
+    <string name="steps">L茅p茅s</string>
+    <string name="devicetype_vibratissimo">Vibratissimo</string>
+    <string name="vietnamese">Vietn谩mi</string>
+    <string name="devicetype_hplus">HPlus</string>
+    <string name="thai">Thai</string>
+    <string name="simplified_chinese">Egyszer疟s铆tett k铆nai</string>
+    <string name="Distance">T谩vols谩g</string>
+    <string name="devicetype_xwatch">XWatch</string>
+    <string name="devicetype_casiogb6900">Casio GB-6900</string>
+    <string name="share">Megoszt谩s</string>
+    <string name="portuguese">Portug谩l</string>
+    <string name="activity_prefs_charts">Diagram be谩ll铆t谩sok</string>
+    <string name="activity_type_swimming">脷sz谩s</string>
+    <string name="meters_second">m/s</string>
+    <string name="activity_db_management_autoexport_label">Automatikus export谩l谩s</string>
+    <string name="activity_prefs_chart_max_heart_rate">Maximum pulzus</string>
+    <string name="menuitem_nfc">NFC</string>
+    <string name="devicetype_amazfit_cor2">Amazfit Cor 2</string>
+    <string name="bpm">bpm</string>
+    <string name="activeSeconds">Akt铆v</string>
+    <string name="devicetype_pebble">Pebble</string>
+    <string name="ukrainian">Ukr谩n</string>
+    <string name="kind_gps_cep">GPS hibakorrekci贸</string>
+    <string name="ascentSeconds">N枚vekv艖</string>
+    <string name="busy_task_fetch_debug_logs">Hibakeres茅si napl贸k lek茅rdez茅se</string>
+    <string name="title_activity_watch9_calibration">Watch 9 kalibr谩l谩s</string>
+    <string name="pref_auto_fetch_limit_fetches">Minim谩lis id艖 lek茅rdez茅sek k枚z枚tt</string>
+    <string name="busy_task_fetch_spo2_data">SpO2 adatok lek茅rdez茅se</string>
+    <string name="pref_sleep_mode_smart_enable_title">Okos enged茅lyez茅s</string>
+    <string name="mode_configuration">M贸dbe谩ll铆t谩s</string>
+    <string name="busy_task_fetch_sports_summaries">Sport枚sszes铆t茅sek lek茅rdez茅se</string>
+    <string name="qhybrid_overwrite_buttons">gombok fel眉l铆r谩sa</string>
+    <string name="choose_auto_export_location">V谩lassza ki az export谩l谩s hely茅t</string>
+    <string name="weekstepschart_steps_a_month">Havi l茅p茅s</string>
+    <string name="pai_chart_per_week">Heti PAI</string>
+    <string name="prefs_sleep_time_label">Adja meg, hogy h谩ny 贸r谩t aludjon</string>
+    <string name="qhybrid_buttons_overwrite_success">Gombok fel眉l铆rva</string>
+    <string name="filter_mode_none">Ne sz疟rje</string>
+    <string name="prefs_events_forwarding_fellsleep">Elalv谩skor</string>
+    <string name="activity_type_not_measured">Nincs megm茅rve</string>
+    <string name="pai_chart_per_month">Havi PAI</string>
+    <string name="prefs_vibration_enable">Rezg茅sek enged茅lyez茅se</string>
+    <string name="lack_of_sleep">Alv谩s hi谩nya: %1$s</string>
+    <string name="filter_mode_whitelist">Jelen铆tse meg, amikor ezeket a szavakat tartalmazza</string>
+    <string name="mi3_night_mode_sunset">Naplementekor</string>
+    <string name="busy_task_fetch_hr_data">Pulzusadatok lek茅rdez茅se</string>
+    <string name="devicestatus_connected">Eszk枚z kapcsol贸dva</string>
+    <string name="traditional_chinese">Tradicion谩lis k铆nai</string>
+    <string name="devicetype_mykronoz_zetime">MyKronoz ZeTime</string>
+    <string name="seconds_m">mp/m</string>
+    <string name="on">Be</string>
+    <string name="busy_task_fetch_stress_data">Stresszadatok lek茅rdez茅se</string>
+    <string name="menuitem_sleep">Alv谩s</string>
+    <string name="seconds_km">mp/km</string>
+    <string name="busy_task_fetch_sleep_respiratory_rate_data">Alv谩si l茅gz茅ssz谩m adatok lek茅rdez茅se</string>
+    <string name="reset_index">Lek茅rdez茅si d谩tum vissza谩ll铆t谩sa</string>
+    <string name="pref_vibrate_for_alert">Rezg茅s 茅breszt茅skor</string>
+    <string name="activity_type_pingpong">Asztalitenisz</string>
+    <string name="qhybrid_buttons_overwrite_error">Hiba a gombok fel眉l铆r谩sakor</string>
+    <string name="qhybrid_vibration_strength">rezg茅s er艖ss茅ge:</string>
+    <string name="title_activity_notification_filter">脡rtes铆t茅ssz疟r艖</string>
+    <string name="abstract_chart_fragment_kind_rem_sleep">REM alv谩s</string>
+    <string name="pref_sleep_mode_title">Alv谩s m贸d</string>
+    <string name="pref_check_permission_status">Enged茅ly谩llapot ellen艖rz茅se</string>
+    <string name="mi2_prefs_button_action_vibrate">Kark枚t艖 rezg茅s茅nek enged茅lyez茅se</string>
+    <string name="zetime_title_screentime">K茅perny艖 bekapcsolt ideje m谩sodpercben</string>
+    <string name="mi3_prefs_night_mode_summary">Automatikusan cs枚kkenti a kark枚t艖 k茅perny艖j茅nek f茅nyerej茅t 茅jszaka</string>
+    <string name="watch9_calibration_hint">脕ll铆tsa be az id艖t, amelyet az eszk枚ze jelenleg mutat.</string>
+    <string name="busy_task_fetch_sports_details">Sportr茅szletek lek茅rdez茅se</string>
+    <string name="pref_chart_sleep_rolling_24_on">El艖z艖 24 贸ra</string>
+    <string name="pref_header_sound_vibration">Hang 茅s rezg茅s</string>
+    <string name="pref_chart_sleep_rolling_24_off">D茅lt艖l d茅lig</string>
+    <string name="pref_header_heartrate_sleep">Alv谩s</string>
+    <string name="prefs_events_forwarding_title">Eszk枚zm疟veletek</string>
+    <string name="devicetype_sonyswr12">Sony SWR12</string>
+    <string name="vibration_profile_default">Alap茅rtelmezett</string>
+    <string name="activity_prefs_chart_min_steps_per_minute_for_run">Minimum percenk茅nti l茅p茅s fut谩s 茅szlel茅s茅hez</string>
+    <string name="pref_screen_notification_profile_inactivity">Inaktivit谩si 茅rtes铆t茅s</string>
+    <string name="qhybrid_changes_delay_prompt">a v谩ltoztat谩s eltarthat n茅h谩ny m谩sodpercig鈥�</string>
+    <string name="watch_not_connected">Az 贸ra nincs kapcsol贸dva</string>
+    <string name="weeksleepchart_sleep_a_month">Havi alv谩s</string>
+    <string name="descentSeconds">Cs枚kken艖</string>
+    <string name="activity_type_light_sleep">K枚nny疟 alv谩s</string>
+    <string name="activity_prefs_chart_min_steps_per_minute">Minimum percenk茅nti l茅p茅s aktivit谩s 茅szlel茅s茅hez</string>
+    <string name="prefs_sleep_time">Alv谩sid艖k</string>
+    <string name="devicetype_q8">Q8</string>
+    <string name="activity_type_badminton">Tollaslabda</string>
+    <string name="pref_screen_vibration_patterns_title">Rezg茅smint谩k</string>
+    <string name="filter_mode_blacklist">Blokkolja, amikor ezeket a szavakat tartalmazza</string>
+    <string name="pref_crown_vibration">Koronarezg茅s</string>
+    <string name="mi3_prefs_night_mode">脡jszakai m贸d</string>
+    <string name="busy_task_fetch_pai_data">PAI adatok lek茅rdez茅se</string>
+    <string name="devicetype_liveview">脡l艖 n茅zet</string>
+    <string name="pref_auto_fetch_limit_fetches_summary">Minden %d. percben lek茅rdezi</string>
+    <string name="pref_header_auto_fetch">Automatikus lek茅rdez茅s</string>
+    <string name="world_clock_label">C铆mke</string>
+    <string name="sony_speak_to_chat_sensitivity_high">Magas</string>
+    <string name="devicetype_y5">Y5</string>
+    <string name="pref_header_time">Id艖</string>
+    <string name="sony_equalizer_preset_manual">K茅zi</string>
+    <string name="pref_summary_canned_replies">V谩lasz az 贸r谩r贸l el艖re be谩ll铆tott 眉zenetekkel</string>
+    <string name="prefs_active_noise_cancelling_level_low">Alacsony</string>
+    <string name="set">Be谩ll铆t谩s</string>
+    <string name="appmanager_item_outdated">(elavult)</string>
+    <string name="accuracy">Pontoss谩g</string>
+    <string name="notification_channel_high_priority_name">Magas priorit谩s煤</string>
+    <string name="pref_balance">Egyens煤ly</string>
+    <string name="lithuanian">Litv谩n</string>
+    <string name="sony_equalizer_preset_speech">Besz茅d</string>
+    <string name="prefs_password">Jelsz贸</string>
+    <string name="appmanager_app_start">Kezd茅s</string>
+    <string name="pref_header_offline_voice">Internet n茅lk眉li hang</string>
+    <string name="qhybrid_title_apps">Alkalmaz谩sok</string>
+    <string name="laps_unit">k枚r枚k</string>
+    <string name="sony_equalizer_preset_relaxed">Nyugodt</string>
+    <string name="pref_header_calendar">Napt谩r</string>
+    <string name="minutes_km">perc/km</string>
+    <string name="sony_anc_optimize_title">Optimaliz谩l谩s</string>
+    <string name="distance_format_kilometers">###.# km</string>
+    <string name="pref_title_equalizer_clear">Tiszta</string>
+    <string name="sony_anc_optimizer_status_analyzing">Analiz谩l谩s鈥�</string>
+    <string name="pref_chart_heartrate_color_orange">Narancss谩rga</string>
+    <string name="bengali">Beng谩li</string>
+    <string name="no">Nem</string>
+    <string name="normal">Norm谩l</string>
+    <string name="prefs_left">Bal</string>
+    <string name="pref_header_system">Rendszer</string>
+    <string name="sony_speak_to_chat_sensitivity_low">Alacsony</string>
+    <string name="unknown">Ismeretlen</string>
+    <string name="sony_equalizer_preset_excited">Izgatott</string>
+    <string name="devicetype_banglejs">Bangle.js</string>
+    <string name="pref_title_touch_spotify">Spotify</string>
+    <string name="devicetype_itag">iTag</string>
+    <string name="sony_ambient_sound">M贸d</string>
+    <string name="prefs_right">Jobb</string>
+    <string name="balanced">Kiegyens煤lyozott</string>
+    <string name="averageAltitude">脕tlagos</string>
+    <string name="all_satellites">脰sszes m疟hold</string>
+    <string name="pref_theme_dynamic">Dinamikus sz铆nek</string>
+    <string name="czech">Cseh</string>
+    <string name="yes">Igen</string>
+    <string name="devicetype_tlw64">TLW64</string>
+    <string name="open_fw_installer_warning_title">Figyelmeztet茅s</string>
+    <string name="accuracy_first">Pontoss谩g el艖sz枚r</string>
+    <string name="activity_detail_end_label">Befejez茅s</string>
+    <string name="menuitem_flashlight">Zsebl谩mpa</string>
+    <string name="watchface_dialog_widget_preset_right">Jobb</string>
+    <string name="activity_filter_date_from">Ett艖l</string>
+    <string name="persian">Perzsa</string>
+    <string name="sony_surround_mode_club">Klub</string>
+    <string name="pref_header_other">Egy茅b</string>
+    <string name="gps">GPS</string>
+    <string name="sony_anc_optimizer_status_finished">Befejez茅s鈥�</string>
+    <string name="start">Kezd茅s</string>
+    <string name="minSpeed">Minimum</string>
+    <string name="activity_filter_date_to">Eddig</string>
+    <string name="fossil_hr_edit_action_delete">t枚rl茅s</string>
+    <string name="prefs_activate_display_on_lift_sensitivity">脡rz茅kenys茅g</string>
+    <string name="sony_anc_optimizer_status_starting">Kezd茅s鈥�</string>
+    <string name="activity_type_basketball">Kos谩rlabda</string>
+    <string name="sensitive">脡rz茅keny</string>
+    <string name="sony_surround_mode_arena">Ar茅na</string>
+    <string name="device_unsupported">NEM T脕MOGATOTT</string>
+    <string name="activity_detail_start_label">Kezd茅s</string>
+    <string name="icelandic">Izlandi</string>
+    <string name="watchface_dialog_widget_preset_left">Bal</string>
+    <string name="activity_type_hiking">T煤r谩z谩s</string>
+    <string name="sony_sound_position_front">El眉ls艖</string>
+    <string name="pref_header_health">Eg茅szs茅g</string>
+    <string name="qhybrid_calibration_counterclockwise">脫ramutat贸 j谩r谩s谩val ellent茅tesen</string>
+    <string name="appmanager_app_download">Let枚lt茅s gyors铆t贸t谩rba</string>
+    <string name="pref_header_connection">Kapcsolat</string>
+    <string name="watchface_widget_type_weather">Id艖j谩r谩s</string>
+    <string name="sony_speak_to_chat_timeout_off">Ki</string>
+    <string name="prefs_sounds">Hangok</string>
+    <string name="activity_filter_filter_title">Sz疟r艖</string>
+    <string name="distanceMeters">T谩vols谩g</string>
+    <string name="pref_header_notification_application_settings">Alkalmaz谩sonk茅nri be谩ll铆t谩sok</string>
+    <string name="getting_heart_rate">M茅r茅s</string>
+    <string name="sony_equalizer_preset_off">Ki</string>
+    <string name="menuitem_stopwatch">Stopper</string>
+    <string name="menuitem_events">Esem茅nyek</string>
+    <string name="pref_title_equalizer_normal">Norm谩l</string>
+    <string name="activity_type_soccer">Futball</string>
+    <string name="scandinavian">Skandin谩v</string>
+    <string name="devicetype_waspos">Wasp-os</string>
+    <string name="menuitem_alexa">Alexa</string>
+    <string name="pref_title_equalizer_dynamic">Dinamikus</string>
+    <string name="estonian">脡szt</string>
+    <string name="open_fw_installer_pick_file">F谩jl kiv谩laszt谩sa</string>
+    <string name="hr_appname_stopwatch">Stopper</string>
+    <string name="pref_chart_heartrate_color_red">V枚r枚s</string>
+    <string name="sony_ambient_sound_off">Ki</string>
+    <string name="appmanager_app_edit">Szerkeszt茅s</string>
+    <string name="sony_button_mode_off">Ki</string>
+    <string name="appmanager_watchface_activate">Aktiv谩l谩s</string>
+    <string name="menuitem_menu">Men眉</string>
+    <string name="ukranian">Ukr谩n</string>
+    <string name="menuitem_barometer">Barom茅ter</string>
+    <string name="menuitem_email">Email</string>
+    <string name="menuitem_temperature">H艖m茅rs茅klet</string>
+    <string name="qhybrid_title_calibration">Kalibr谩l谩s</string>
+    <string name="steps_unit">l茅p茅s</string>
+    <string name="fossil_hr_new_action_cancel">m茅gse</string>
+    <string name="pref_title_equalizer_soft">L谩gy</string>
+    <string name="watchface_widget_type_steps">L茅p茅sek</string>
+    <string name="distance_format_meters">### m</string>
+    <string name="qhybrid_calibration_clockwise">脫ramutat贸 j谩r谩s谩nak megfelel艖en</string>
+    <string name="sony_sound_position_off">Ki</string>
+    <string name="watchface_widget_type_date">D谩tum</string>
+    <string name="prefs_active_noise_cancelling_level_high">Magas</string>
+    <string name="prefs_galaxy_buds_experimental">Exponenci谩lis</string>
+    <string name="devicetype_fitpro">FitPro</string>
+    <string name="appmanager_downloaded_to_cache">%s let枚ltve gyors铆t贸t谩rba</string>
+    <string name="sony_surround_mode_off">Ki</string>
+    <string name="pref_header_display">Kijelz艖</string>
+    <string name="appmanager_download_app_error">Hiba az alkalmaz谩s let枚lt茅sekor</string>
+    <string name="activity_list_summary_activities">Aktivit谩sok</string>
+    <string name="custom">Egy茅ni</string>
+    <string name="menuitem_nothing">Semmi</string>
+    <string name="speed_first">Sebess茅g el艖sz枚r</string>
+    <string name="devicetype_um25">UM-25</string>
+    <string name="devicetype_bfh16">BFH-16</string>
+    <string name="application_name_banglejs_nightly">Bangle.js Gadgetbridge (korai hozz谩f茅r茅s疟 kiad谩s)</string>
+    <string name="application_name_main_nightly">Gadgetbridge (korai hozz谩f茅r茅s疟 kiad谩s)</string>
+    <string name="Steps">L茅p茅sek</string>
+    <string name="activity_type_climbing">M谩sz谩s</string>
+    <string name="activity_list_summary_distance">T谩vols谩g</string>
+    <string name="activity_list_summary_active_steps">Akt铆v l茅p茅sek</string>
+    <string name="fossil_hr_commute_processing">K茅r茅s feldolgoz谩sa鈥�</string>
+    <string name="pref_media_next">K枚vetkez艖 sz谩m</string>
+    <string name="heart_rate_result">M茅r茅s eredm茅nyek</string>
+    <string name="dismiss">Elvet茅s</string>
+    <string name="devicetype_amazfit_pop">Amazfit Pop</string>
+    <string name="hrZoneExtreme">Extr茅m</string>
+    <string name="activity_summaries_all_activities">脰sszes aktivit谩s</string>
+    <string name="pref_title_authkey">Hiteles铆t艖 kulcs</string>
+    <string name="devicetype_sg2">Lemfo SG2</string>
+    <string name="menuitem_recommendation">Aj谩nl谩s</string>
+    <string name="activity_type_stretching">Ny煤jt谩s</string>
+    <string name="sony_speak_to_chat_timeout_short">R枚vid (15s)</string>
+    <string name="title">C铆m</string>
+    <string name="pref_gps_header">GPS</string>
+    <string name="menuitem_bluetooth">Bluetooth</string>
+    <string name="pref_contacts_title">Kapcsolatok</string>
+    <string name="zetime_title_settings">ZeTime be谩ll铆t谩sok</string>
+    <string name="activity_type_squash">Fallabda</string>
+    <string name="devicetype_bose_qc35">Bose QC35</string>
+    <string name="world_clock_delete_confirm_title">\'%1$s\' t枚rl茅se</string>
+    <string name="activity_type_tennis">Tenisz</string>
+    <string name="wifi_ssid">SSID</string>
+    <string name="activity_type_skiing">S铆el茅s</string>
+    <string name="devicetype_casiogbx100">Casio GBX-100</string>
+    <string name="fossil_hr_new_action">脷j m疟velet</string>
+    <string name="prefs_always_on_display_style">St铆lus</string>
+    <string name="activity_type_other">Egy茅b</string>
+    <string name="pref_button_action_disabled">Kikapcsolva</string>
+    <string name="prefs_notifications_enable">脡rtes铆t茅sek enged茅lyez茅se</string>
+    <string name="menuitem_goal">Aktivit谩s c茅l</string>
+    <string name="prefs_ambient_voice_focus">Hangf贸kusz</string>
+    <string name="activity_list_summary_active_time">Akt铆v id艖</string>
+    <string name="watchface_widget_type_heart_rate">Pulzus</string>
+    <string name="supercars_lights_blinking_label">Villog谩s</string>
+    <string name="overslept">Tov谩bb aludt: %1$s</string>
+    <string name="qhybrid_calibration_100_steps">100 l茅p茅s</string>
+    <string name="dbmanagementactivity_export_data_title">Export谩lja az adatokat\?</string>
+    <string name="devicetype_domyos_t540">Domyos T540</string>
+    <string name="activity_type_weightlifting">S煤lyemel茅s</string>
+    <string name="hrZoneAnaerobic">Anaerobik</string>
+    <string name="step_streak_total">脰sszes</string>
+    <string name="devicetype_amazfit_gts">Amazfit GTS</string>
+    <string name="activity_filter_apply_filter">Sz疟r艖 alkalmaz谩sa</string>
+    <string name="pref_header_sony_device_info">Eszk枚zinform谩ci贸</string>
+    <string name="pref_header_authentication">Hiteles铆t茅s</string>
+    <string name="activity_type_rugby">R枚gbi</string>
+    <string name="pref_title_lock_unlock">Lez谩r谩s</string>
+    <string name="mi2_dnd_always">Mindig</string>
+    <string name="activity_summaries_all_devices">脰sszes eszk枚z</string>
+    <string name="devicetype_amazfit_neo">Amazfit Neo</string>
+    <string name="stress_mild">Enyhe</string>
+    <string name="menuitem_brightness">F茅nyess茅g</string>
+    <string name="description">Le铆r谩s</string>
+    <string name="sony_equalizer_preset_custom_2">Egy茅ni 2</string>
+    <string name="pref_title_developer_settings">Fejleszt艖i be谩ll铆t谩sok</string>
+    <string name="menuitem_findphone">Telefon keres茅se</string>
+    <string name="username">Felhaszn谩l贸n茅v</string>
+    <string name="world_clock_timezone">Id艖z贸na</string>
+    <string name="activity_type_dancing">T谩nc</string>
+    <string name="function_enabled">Bekapcsolva</string>
+    <string name="activity_type_dance">T谩nc</string>
+    <string name="menuitem_cards">K谩rty谩k</string>
+    <string name="pref_applications_settings">Alkalmaz谩slista</string>
+    <string name="activity_type_boxing">Box</string>
+    <string name="prefs_game_mode">J谩t茅k m贸d</string>
+    <string name="activity_type_surfing">Sz枚rf</string>
+    <string name="watchface_setting_title_power_saving">Akkumul谩tork铆m茅l艖</string>
+    <string name="pref_screen_notification_profile_find_device">Eszk枚z keres茅se</string>
+    <string name="pref_title_physical_buttons">Fizikai gombok</string>
+    <string name="notification_channel_transfer_name">Adat谩tvitel</string>
+    <string name="widget_settings_select_device_title">Eszk枚z kiv谩laszt谩sa</string>
+    <string name="menuitem_mutephone">Telefon n茅m铆t谩sa</string>
+    <string name="movement_intensity">Mozg谩sintenzit谩s</string>
+    <string name="devicetype_sony_wh_1000xm3">Sony WH-1000XM3</string>
+    <string name="overstep">T枚bblet l茅p茅s: %1$d</string>
+    <string name="stress_high">Magas</string>
+    <string name="menuitem_wifi">Wi-Fi</string>
+    <string name="pref_media_volumeup">Hanger艖 n枚vel茅se</string>
+    <string name="fossil_hr_edit_action">M疟velet szerkeszt茅se</string>
+    <string name="stress_moderate">M茅rs茅kelt</string>
+    <string name="dbmanagementactivity_export_finished">T枚rl茅s k茅sz</string>
+    <string name="stop">Meg谩ll铆t谩s</string>
+    <string name="pref_header_device_spec_settings">Eszk枚zbe谩ll铆t谩sok</string>
+    <string name="contact_name">N茅v</string>
+    <string name="qhybrid_calibration_10_steps">10 l茅p茅s</string>
+    <string name="watchface_dialog_widget_preset_bottom">Als贸</string>
+    <string name="prefs_dolby_mode">Dolby m贸d</string>
+    <string name="maxHR">Maximum pulzus</string>
+    <string name="spo2_perc_80">80%</string>
+    <string name="activity_type_gymnastics">Gimnasztika</string>
+    <string name="sony_button_mode_volume_control">Hanger艖szab谩lyz谩s</string>
+    <string name="menuitem_volume">Hanger艖</string>
+    <string name="devicetype_sony_wf_1000xm3">Sony WF-1000XM3</string>
+    <string name="spo2_perc_85">85%</string>
+    <string name="menuitem_forecast">El艖rejelz茅s</string>
+    <string name="pref_header_navigation">Navig谩ci贸</string>
+    <string name="activity_type_kickboxing">Kickbox</string>
+    <string name="activity_prefs_goals">C茅lok</string>
+    <string name="menuitem_countdown">Visszasz谩ml谩l谩s</string>
+    <string name="devicetype_vesc">VESC</string>
+    <string name="status">脕llapot</string>
+    <string name="pref_header_stress">Stressz</string>
+    <string name="stress_relaxed">Nyugodt</string>
+    <string name="averageSpeed">脕tlagos sebess茅g</string>
+    <string name="activity_summary_edit_name_title">C铆mke szerkeszt茅se</string>
+    <string name="battery_case">Akkumul谩tortok</string>
+    <string name="devicetype_amazfit_x">Amazfit X</string>
+    <string name="address">C铆m</string>
+    <string name="devicetype_smaq2oss">SMA-Q2 OSS</string>
+    <string name="pai_plus_num">+%d</string>
+    <string name="device_is_currently_bonded">M脕R 脰SSZE VAN P脕ROS脥TVA</string>
+    <string name="pref_media_previous">El艖z艖 sz谩m</string>
+    <string name="activity_type_strength_training">Er艖s铆t茅s</string>
+    <string name="world_clock_code">K贸d</string>
+    <string name="smart">Okos</string>
+    <string name="devicetype_sony_wf_sp800n">Sony WF-SP800N</string>
+    <string name="georgian">Gr煤ziai</string>
+    <string name="devicetype_asteroidos">AsteroidOS</string>
+    <string name="spo2_perc_90">90%</string>
+    <string name="withings_bt_calibration_next">K枚vetkez艖</string>
+    <string name="pref_message_privacy_mode_off">Az 枚sszes tartalom megjelen铆t茅se</string>
+    <string name="devicetype_zepp_e">Zepp E</string>
+    <string name="pref_media_volumedown">Hanger艖 cs枚kkent茅se</string>
+    <string name="loyalty_cards_sync">Szinkroniz谩ci贸</string>
+    <string name="button_watchface_edit_name">N茅v szerkeszt茅se</string>
+    <string name="step_streak_longest">Leghosszabb</string>
+    <string name="activity_type_golf">Golf</string>
+    <string name="device_experimental">K脥S脡RLETI</string>
+    <string name="sony_equalizer_clear_bass">Tiszta basszus</string>
+    <string name="spo2_off">Ki</string>
+    <string name="activity_type_handball">K茅zilabda</string>
+    <string name="sony_equalizer_preset_custom_1">Egy茅ni 1</string>
+    <string name="pref_header_notifications_and_calls_callhandling">H铆v谩skezel茅s</string>
+    <string name="menuitem_todo">Tennival贸</string>
+    <string name="sony_button_mode_playback_control">Lej谩tsz谩svez茅rl茅s</string>
+    <string name="withings_bt_calibration_previous">El艖z艖</string>
+    <string name="menuitem_phone">Telefon</string>
+    <string name="devicetype_nut_mini">Nut mini</string>
+    <string name="menuitem_calendar">Napt谩r</string>
+    <string name="croatian">Horv谩t</string>
+    <string name="qhybrid_title_background_image">H谩tt茅rk茅p</string>
+    <string name="activity_type_football">Futball</string>
+    <string name="save_configuration">Konfigur谩ci贸 ment茅se</string>
+    <string name="watchface_widget_type_active_mins">Akt铆v percek</string>
+    <string name="pref_canned_message">脺zenet</string>
+    <string name="menuitem_pomodoro">Pomodoro k枚vet艖</string>
+    <string name="watchface_dialog_widget_preset_top">Fels艖</string>
+    <string name="pref_message_privacy_mode_complete">Az 枚sszes tartalom elrejt茅se</string>
+    <string name="zepp_os_watchface_minimalist">Minimalista</string>
+    <string name="qhybrid_title_file_management">F谩jlkezel茅s</string>
+    <string name="minHR">Minimum pulzus</string>
+    <string name="hrZoneWarmUp">Bemeleg铆t谩s</string>
+    <string name="blood_pressure">V茅rnyom谩s</string>
+    <string name="kind_app">Alkalmaz谩s</string>
+    <string name="qhybrid_title_apps_management">Alkalmaz谩skezel茅s</string>
+    <string name="activity_type_volleyball">R枚plabda</string>
+    <string name="devicetype_sony_wh_1000xm4">Sony WH-1000XM4</string>
+    <string name="activity_type_zumba">Zumba</string>
+    <string name="activity_list_summary_intensity">Mozg谩s
+\nIntenzit谩s</string>
+    <string name="supercars_lights_label">F茅nyek</string>
+    <string name="pref_agps_header">AGPS</string>
+    <string name="hrZoneAerobic">Aerobik</string>
+    <string name="devicetype_galaxybuds">Galaxy Buds</string>
+    <string name="pref_workout_detection_alert_title">Riaszt谩s</string>
+    <string name="qhybrid_calibration_1_step">1 l茅p茅s</string>
+    <string name="appmanager_download_started">Az alkalmaz谩s let枚lt茅se elindult</string>
+    <string name="power_saving">Energiatakar茅kos</string>
+    <string name="gadgetbridge_running_banglejs_main">A Bangle.js fut</string>
+    <string name="debugactivity_confirm_remove_device_preferences">Ez a m疟velet az 枚sszes p谩ros铆tott eszk枚z be谩ll铆t谩s谩t alap helyzetbe helyezi. Biztos benne?</string>
+    <string name="gps_gnolass">GPS + GLONASS</string>
+    <string name="gps_bds">GPS + BDS</string>
+    <string name="low_power_gps">Alacsony energi谩j煤 GPS</string>
+    <string name="heartrate_bpm_110">110 bpm</string>
+    <string name="heartrate_bpm_135">135 bpm</string>
+    <string name="prefs_autoheartrate_interval">M茅r茅sek gyakoris谩ga</string>
+    <string name="heartrate_bpm_105">105 bpm</string>
+    <string name="heartrate_bpm_40">40 bpm</string>
+    <string name="zetime_title_heart_rate_alarm">Pulzus riaszt谩s</string>
+    <string name="heartrate_bpm_130">130 bpm</string>
+    <string name="zetime_title_heartrate">Pulzus be谩ll铆t谩sok</string>
+    <string name="live_activity_max_heart_rate">Jelenlegi / maximum pulzus: %1$d / %2$d</string>
+    <string name="mi2_prefs_heart_rate_monitoring_summary">Pulzusm茅r茅s be谩ll铆t谩sa</string>
+    <string name="pref_header_heartrate_allday">Eg茅sz napos m茅r茅s</string>
+    <string name="mi2_prefs_heart_rate_monitoring">Pulzusm茅r茅s</string>
+    <string name="pref_header_heartrate_alerts">Pulzus riaszt谩sok</string>
+    <string name="heartrate_bpm_125">125 bpm</string>
+    <string name="heartrate_bpm_100">100 bpm</string>
+    <string name="heartrate_bpm_145">145 bpm</string>
+    <string name="heartrate_bpm_115">115 bpm</string>
+    <string name="heartrate_bpm_150">150 bpm</string>
+    <string name="prefs_autoheartrate_measurement">Automatikus pulzusm茅r茅sek</string>
+    <string name="heartrate_bpm_140">140 bpm</string>
+    <string name="prefs_autoheartrate_sleep">Alv谩s k枚zben v茅gezzen m茅r茅seket</string>
+    <string name="heartrate_bpm_45">45 bpm</string>
+    <string name="heartrate_bpm_120">120 bpm</string>
+    <string name="zetime_heart_rate_alarm_enable">Pulzus riaszt谩s enged茅lyez茅se</string>
+    <string name="heartrate_bpm_50">50 bpm</string>
+    <string name="require_location_provider">A helysz铆n-meghat谩roz谩snak enged茅lyezve kell lennie</string>
+    <string name="pref_title_banglejs_phone_gps_network_only">H谩l贸zat haszn谩lata csak a helysz铆n meghat谩roz谩s谩hoz</string>
+    <string name="notification_gps_text">GPS helysz铆n k眉ld茅se %1$d eszk枚z枚kre</string>
+    <string name="zepp_os_watchface_pure_white">Tiszta feh茅r</string>
+    <string name="charts_legend_heartrate_average">脕tlagos pulzus</string>
+    <string name="pref_summary_authkey">M贸dos铆tsa a hiteles铆t茅si kulcsot egy k枚z枚s kulcsra az 枚sszes olyan Android-eszk枚z茅n, amelyr艖l kapcsol贸dni szeretne. A kor谩bbi alap茅rtelmezett kulcs minden eszk枚z枚n: 0123456789@ABCDE</string>
+    <string name="prefs_wena3_notification_vibration_repetition_3">H谩romszor</string>
+    <string name="zepp_os_watchface_city_of_speed">Sebess茅g v谩rosa</string>
+    <string name="prefs_phone_silent_mode">Telefon n茅ma m贸d</string>
+    <string name="watchface_dialog_widget_cat_2nd_tz_widget">M谩sodik id艖z贸na fel眉leti eleme</string>
+    <string name="pref_cache_weather">Id艖j谩r谩s-inform谩ci贸 gyors铆t贸t谩raz谩sa</string>
+    <string name="prefs_wena3_vibration_short">R枚vid</string>
+    <string name="devicestatus_connecting">Az eszk枚z kapcsol贸dik</string>
+    <string name="pref_title_chart_heartrate_color">Pulzus sz铆n</string>
+    <string name="discovery_bluetooth_scan">Bluetooth keres茅s:</string>
+    <string name="zepp_os_watchface_free_combination">Szabad kombin谩ci贸</string>
+    <string name="activity_db_management_autoexport_enabled_yes">Az automatikus export谩l谩s enged茅lyezve van.</string>
+    <string name="activity_db_management_clean_export_directory_label">F谩jlok t枚rl茅se az export谩l谩s/import谩l谩s k枚nyvt谩rb贸l</string>
+    <string name="prefs_wena3_vibration_smart_item">Okos rezg茅s</string>
+    <string name="zepp_os_watchface_business_style">脺zleti st铆lus</string>
+    <string name="watchface_cache_confirm_overwrite">Egy 贸rafel眉let ezzel a n茅vvel m谩r l茅tezik a gyors铆t贸t谩rban. Szeretn茅 fel眉l铆rni?</string>
+    <string name="prefs_wena3_vibration_strength_item_medium">K枚zepes</string>
+    <string name="zepp_os_watchface_emerald_moonlight">Smaragd holdf茅ny</string>
+    <string name="activity_data_management_directory_content_title">Export谩l谩s/import谩l谩s k枚nyvt谩r tartalma</string>
+    <string name="silent_mode_normal_vibrate">Norm谩l / rezg茅s</string>
+    <string name="prefs_disconnect_notification">Lev谩laszt谩s 茅rtes铆t茅s</string>
+    <string name="prefs_wena3_vibration_continuous">Folyamatos</string>
+    <string name="fossil_hr_connection_not_confirmed">A kapcsol贸d谩s nincs meger艖s铆tve az 贸r谩n, hiteles铆t茅s n茅lk眉li m贸d haszn谩lata</string>
+    <string name="zepp_os_watchface_vibrant">脡l茅nk</string>
+    <string name="activity_db_management_autoexport_scheduled_no">Az automatikus export谩l谩s nincs id艖z铆tve.</string>
+    <string name="activity_db_management_clean_export_directory_text">Az export谩lt f谩jlok az export谩l谩s/import谩l谩s k枚nyvt谩rban b谩rmilyen alkalmaz谩s sz谩m谩ra el茅rhet艖ek az eszk枚z茅n. Lehet, hogy t枚r枚lni szeretn茅 ezeket a f谩jlokat szinkroniz谩l谩s vagy biztons谩gi ment茅s ut谩n. Gy艖z艖dj枚n meg, hogy van ment茅se, miel艖tt t枚rli 艖ket. A GPX f谩jlok, az alk枚nyvt谩rak 茅s az automatikusan export谩lt adatb谩zis-f谩jlok (ha l茅teznek) nem lesznek t枚r枚lve. Az export谩l谩s/import谩l谩s k枚nyvt谩r 煤tvonala:</string>
+    <string name="prefs_wena3_vibration_strength_item_weak">Gyenge</string>
+    <string name="activity_db_management_autoexport_enabled_no">Az automatikus export谩l谩s nincs enged茅lyezve.</string>
+    <string name="pref_screen_vibration_patterns_summary">Rezg茅smint谩k be谩ll铆t谩sa a k眉l枚nb枚z艖 茅rtes铆t茅sekhez</string>
+    <string name="zepp_os_watchface_starry_sky">Csillagos 茅g</string>
+    <string name="discovery_entered_invalid_authkey">A be铆rt titkos hiteles铆t茅si kulcs 茅rv茅nytelen! Nyomjon hosszan az eszk枚zre a szerkeszt茅shez.</string>
+    <string name="activity_DB_ShowContentButton">Export谩l谩s/import谩l谩s k枚nyvt谩r tartalm谩nak megjelen铆t茅se</string>
+    <string name="enable_vibrations_summary">Rezg茅s h铆v谩sokhoz, 眉zenetekhez, 茅rtes铆t茅sekhez 茅s t枚bbh枚z</string>
+    <string name="zepp_os_watchface_red_fantasy">V枚r枚s fant谩zia</string>
+    <string name="prefs_hr_alarm_activity">Pulzus riaszt谩s sport aktivit谩s k枚zben</string>
+    <string name="devicestatus_disconnecting">Az eszk枚z lev谩laszt贸dik!</string>
+    <string name="prefs_wena3_vibration_basic">Alap</string>
+    <string name="activity_DB_clean_export_directory_warning_title">T枚rli a f谩jlokat az export谩l谩s/import谩l谩s k枚nyvt谩rb贸l?</string>
+    <string name="prefs_wena3_notification_vibration_repetition_2">K茅tszer</string>
+    <string name="discovery_need_to_enter_authkey">Ennek az eszk枚znek sz眉ks茅ge van egy titkos hiteles铆t茅si kulcsra, nyomjon hosszan az eszk枚zre, hogy be tudja 铆rni. Olvassa ez a wikit.</string>
+    <string name="activity_DB_clean_export_directory_warning_message">Biztosan t枚rli a f谩jlokat az export谩l谩s/import谩l谩s k枚nyvt谩rb贸l?</string>
+    <string name="prefs_wena3_vibration_rapid">Gyors</string>
+    <string name="about_additional_contributions">Sok k枚sz枚net minden fel nem sorolt k枚zrem疟k枚d艖nek, akik k枚zrem疟k枚dtek ezekben: k贸dok, ford铆t谩sok, t谩mogat谩s, 枚tletek, motiv谩ci贸, hibajelent茅sek, p茅nz鈥� 鉁�</string>
+    <string name="silent_mode_vibrate_silent">Rezg茅s / n茅ma</string>
+    <string name="prefs_wena3_notification_vibration_repetition_1">Egyszer</string>
+    <string name="pref_title_connected_advertisement">L谩that贸, am铆g kapcsol贸dva van</string>
+    <string name="HeartRateZones">Pulzus z贸n谩k</string>
+    <string name="button_watchface_save_apply">Ment茅s 茅s alkalmaz</string>
+    <string name="prefs_wena3_notification_default_call_vibration">Bej枚v艖 h铆v谩s rezg茅s</string>
+    <string name="pref_cache_weather_summary">Az id艖j谩r谩s-inform谩ci贸 gyors铆t贸t谩razva lesz az alkalmaz谩s-煤jraind铆t谩sok k枚z枚tt.</string>
+    <string name="zepp_os_watchface_vast_sky">Hatalmas 茅g</string>
+    <string name="about_description_main_nopebble">Felh艖mentes 茅s szabad forr谩sk贸d煤 kiad谩s, amely helyettes铆ti a gy谩rt贸k z谩rt forr谩sk贸don alapul贸 Android k眉ty眉 alkalmaz谩sait. A Gadgetbridge korai hozz谩f茅r茅s疟 kiad谩sa. Ebben a verzi贸ban a Pebble szolg谩ltat贸t 谩tnevezt茅k az 眉tk枚z茅sek elker眉l茅se 茅rdek茅ben, 铆gy egyes Pebble-lel kapcsolatos integr谩ci贸k nem m疟k枚dnek, de telep铆thet艖 a megl茅v艖 Gadgetbridge-telep铆t茅s mell茅.</string>
+    <string name="autoExport_lastTime_label">Utols贸 automatikus export谩l谩s: %1$s</string>
+    <string name="zepp_os_watchface_simplicity_data">Egyszer疟 adatok</string>
+    <string name="pref_title_huami_force_new_protocol">脷j hiteles铆t茅si protokoll</string>
+    <string name="qhybrid_title_watchface">脫rafel眉let be谩ll铆t谩sa</string>
+    <string name="pref_title_notification_cache_while_disconnected">Gyors铆t贸t谩raz谩s, am铆g hat贸t谩von k铆v眉l van</string>
+    <string name="discovery_bluetooth_le_scan">Bluetooth LE keres茅s:</string>
+    <string name="devicestatus_disconnected">Az eszk枚z lev谩laszt贸dott!</string>
+    <string name="controlcenter_get_heartrate_measurement">Legyen pulzusm茅r茅s</string>
+    <string name="zepp_os_watchface_lightning_flash">Vill谩ml谩s</string>
+    <string name="prefs_wena3_vibration_siren">Szir茅na</string>
+    <string name="ignore_bonded_devices_description">Ennek az opci贸nak az enged茅lyez茅se ignor谩lni fogja keres茅skor azokat az eszk枚z枚ket, amik m谩r 枚sszek枚ttetve/p谩ros铆tva vannak</string>
+    <string name="activity_DB_test_export_button">Automatikus export谩l谩s futtat谩sa most</string>
+    <string name="prefs_wena3_notification_default_call_vibration_repetition">Bej枚v艖 h铆v谩s rezg茅s ism茅tl茅s</string>
+    <string name="prefs_wena3_notification_default_vibration">脡rtes铆t茅s rezg茅s</string>
+    <string name="prefs_wena3_notification_vibration_repetition_0">Folyamatosan</string>
+    <string name="activity_prefs_discovery_pairing">Felder铆t茅si 茅s p谩ros铆t谩si be谩ll铆t谩sok</string>
+    <string name="prefs_wena3_notification_vibration_repetition_4">N茅gyszer</string>
+    <string name="pref_summary_notification_cache_while_disconnected">Kihagyott 茅rtes铆t茅sek elk眉ld茅se, amikor az eszk枚z 煤jracsatlakozik, miut谩n hat贸t谩von k铆v眉l volt</string>
+    <string name="prefs_wena3_vibration_strength_item">Rezg茅s er艖ss茅ge</string>
+    <string name="zetime_title_heart_rate_alarm_summary">Az 贸ra figyelmeztetni fog, amikor a pulzus 谩tl茅pi a hat谩rokat.</string>
+    <string name="lefun_prefs_antilost_summary">A kark枚t艖 rezegni fog, ha a telefon lev谩lszt贸dik a k茅sz眉l茅kr艖l</string>
+    <string name="zepp_os_watchface_multiple_data">Sok adat</string>
+    <string name="about_description_main_nightly">Felh艖mentes 茅s szabad forr谩sk贸d煤 kiad谩s, amely helyettes铆ti a gy谩rt贸k z谩rt forr谩sk贸don alapul贸 Android k眉ty眉 alkalmaz谩sait. A Gadgetbridge korai hozz谩f茅r茅s疟 kiad谩sai. Nem telep铆thet艖, ha m谩r telep铆tve van a Gadgetbridge vagy a Pebble alkalmaz谩s, a Pebble szolg谩ltat贸 konfliktusa miatt.</string>
+    <string name="ignore_bonded_devices">脰sszek枚ttetett eszk枚z枚k ignor谩l谩sa</string>
+    <string name="prefs_wena3_notification_default_vibration_repetition">脡rtes铆t茅s rezg茅s ism茅tl茅s</string>
+    <string name="zepp_os_watchface_rotating_earth">Forg贸 f枚ld</string>
+    <string name="about_hash">Kommit %s</string>
+    <string name="pref_summary_disable_new_ble_scanning">Enged茅lyezze ezt az opci贸t, ha az eszk枚ze nem tal谩lhat贸 felder铆t茅s sor谩n</string>
+    <string name="fossil_hr_unavailable_unauthed">Nem el茅rhet艖 hiteles铆t茅s n茅lk眉li m贸dban</string>
+    <string name="silent_mode_normal_silent">Norm谩l / n茅ma</string>
+    <string name="prefs_wena3_vibration_warning">Figyelmeztet茅s</string>
+    <string name="fossil_hr_auth_failed">A hiteles铆t茅s sikertelen, korl谩tozott funkcionalit谩s</string>
+    <string name="pref_explanation_authkey_new_protocol">Ha \"Friss铆tse az alkalmaz谩st a legut贸bbi verzi贸ra\" 眉zenetet kap, gy艖z艖dj枚n meg r贸la, hogy enged茅lyezte az \"脷j hiteles铆t茅si protokoll\" opci贸t fentebb. Koppintson ide a tov谩bbi r茅szletek茅rt a wikiben.</string>
+    <string name="prefs_wena3_vibration_none">Nincs rezg茅s</string>
+    <string name="prefs_wena3_vibration_strength_item_strong">Er艖s</string>
+    <string name="prefs_wena3_vibration_triple">H谩romszor</string>
+    <string name="pref_explanation_authkey">N茅h谩ny eszk枚znek sz眉ks茅ge van egy k眉l枚nleges p谩ros铆t谩si kulcsra az els艖 alkalommal. Koppintson ide a tov谩bbi r茅szletek茅rt a wikiben.</string>
+    <string name="about_description_banglejs_main">A Gadgetbridge projektre 茅p眉l艖 Android t谩rsalkalmaz谩s a Bangle.js sz谩m谩ra, hozz谩adott internet-hozz谩f茅r茅ssel.
+\n
+\nA Google Play 脕ruh谩z szab谩lyzata miatt nem enged茅lyezz眉k az adom谩nyoz谩si hivatkoz谩st mag谩ban az alkalmaz谩sban, de ha tetszik az alkalmaz谩s, fontolja meg az adom谩nyoz谩st az al谩bbi Gadgetbridge honlapon kereszt眉l.</string>
+    <string name="sony_quick_access_triple_tap">Gyors el茅r茅s (h谩rom 茅rint茅s)</string>
+    <string name="devicetype_sony_wh_1000xm2">Sony WH-1000XM2</string>
+    <string name="gadgetbridge_running_main_nightly">Korai hozz谩f茅r茅s疟 Gadgetbridge kiad谩s fut</string>
+    <string name="application_name_main_nopebble">Gadgetbridge (korai hozz谩f茅r茅s疟 kiad谩s, Pebble szolg谩ltat贸 n茅lk眉l)</string>
+    <string name="gadgetbridge_running_main_nopebble">Korai verzi贸j煤 Pebble n茅lk眉li Gadgetbridge fut</string>
+    <string name="activity_type_indoor_cycling">Belt茅ri ker茅kp谩roz谩s</string>
+    <string name="devicetype_sony_wh_1000xm5">Sony WH-1000XM5</string>
+    <string name="gadgetbridge_running_banglejs_nightly">Korai hozz谩f茅r茅s疟 Bangle.js kiad谩s fut</string>
+    <string name="activity_type_indoor_fitness">Belt茅ri fitnesz</string>
+    <string name="sony_anc_optimizer_status_not_running">Nem fut</string>
+    <string name="pref_activity_recognize_walking">s茅ta felismer茅se</string>
+    <string name="pref_activity_recognize_rowing">evez茅s felismer茅se</string>
+    <string name="activity_type_indoor_ice_skating">Belt茅ri j茅gkorcsoly谩z谩s</string>
+    <string name="activity_type_iceskating">J茅gkorcsoly谩z谩s</string>
+    <string name="devicetype_sony_linkbuds_s">Sony LinkBuds S</string>
+    <string name="activity_type_indoor_walking">Belt茅ri s茅ta</string>
+    <string name="sony_quick_access_double_tap">Gyors el茅r茅s (kett艖 茅rint茅s)</string>
+    <string name="devicetype_sony_wf_1000xm4">Sony WF-1000XM4</string>
+    <string name="devicetype_sony_wf_1000xm5">Sony WF-1000XM5</string>
+    <string name="menuitem_running">Fut谩s</string>
+    <string name="pref_activity_recognize_biking">ker茅kp谩roz谩s felismer茅se</string>
+    <string name="wearmode_pebble">Pebble (cip艖csat)</string>
+    <string name="pref_summary_pebble_gatt_clientonly">Ez csak a Pebble 2 sz谩m谩ra k茅sz眉lt 茅s k铆s茅rleti verzi贸, pr贸b谩lja ezt, ha kapcsol贸d谩si probl茅m谩i vannak</string>
+    <string name="pref_activity_recognize_running">fut谩s felismer茅se</string>
+    <string name="about_description_banglejs_nightly">A Gadgetbridge projektre 茅p眉l艖 Android t谩rsalkalmaz谩s a Bangle.js sz谩m谩ra, hozz谩adott internet-hozz谩f茅r茅ssel.
+\n
+\nA Google Play 脕ruh谩z szab谩lyzata miatt nem enged茅lyezz眉k az adom谩nyoz谩si hivatkoz谩st mag谩ban az alkalmaz谩sban, de ha tetszik az alkalmaz谩s, fontolja meg az adom谩nyoz谩st az al谩bbi Gadgetbridge honlapon kereszt眉l.</string>
+    <string name="activity_type_outdoor_running">K眉lt茅ri fut谩s</string>
+    <string name="_pebble_watch_open_on_phone">Megnyit谩s Android eszk枚z枚n</string>
+    <string name="activity_type_outdoor_cycling">K眉lt茅ri ker茅kp谩roz谩s</string>
+    <string name="pref_title_pebble_enable_bgjs">H谩tt茅rbeli JS enged茅lyez茅se</string>
+    <string name="devicetype_sony_wena3">Sony Wena 3</string>
+    <string name="sony_surround_mode_outdoor_stage">K眉lt茅ri sz铆npad</string>
+    <string name="notif_export_failed_title">Az adatb谩zis export谩l谩sa meghi煤sult! Ellen艖rizze a be谩ll铆t谩sait.</string>
+    <string name="fw_upgrade_notice_amazfitcor2">A(z) %s firmware-t k铆v谩nja telep铆teni az Amazfit Cor 2 k茅sz眉l茅k茅re.
+\n
+\nGy艖z艖dj枚n meg r贸la, hogy a .fw f谩jlt, majd a .res f谩jlt telep铆ti. A k茅sz眉l茅ke az .fw f谩jl telep铆t茅se ut谩n 煤jraindul.
+\n
+\nMegjegyz茅s: Nem kell telep铆tenie a .res f谩jlt, ha pontosan megegyezik a kor谩bban telep铆tettel.
+\n
+\nCSAK SAJ脕T FELEL艕SS脡G脡RE FOLYTASSA!
+\n
+\nTELJESEN TESZTELETLEN, V脡LEM脡NYESEN BEATS_W FIRMWARE-T KELL TELEP脥TENIE, HA A K脡SZ脺L脡K NEVE \"Amazfit Band 2\"</string>
+    <string name="fw_upgrade_notice_amazfitgts">A(z) %s firmware-t k铆v谩nja telep铆teni az Amazfit GTS k茅sz眉l茅k茅re.
+\n
+\nGy艖z艖dj枚n meg r贸la, hogy a .fw f谩jlt, majd a .res f谩jlt 茅s v茅g眉l a .gps f谩jlt telep铆ti. A k茅sz眉l茅ke az .fw f谩jl telep铆t茅se ut谩n 煤jraindul.
+\n
+\nMegjegyz茅s: Nem kell telep铆tenie a .res 茅s .gps f谩jlokat, ha ezek a f谩jlok pontosan megegyeznek a kor谩bban telep铆tettekkel.
+\n
+\nCSAK SAJ脕T FELEL艕SS脡G脡RE FOLYTASSA!</string>
+    <string name="fw_upgrade_notice_amazfitvergel">A(z) %s firmware-t k铆v谩nja telep铆teni az Amazfit Verge Lite k茅sz眉l茅k茅re.
+\n
+\nGy艖z艖dj枚n meg r贸la, hogy a .fw f谩jlt, majd a .res f谩jlt 茅s v茅g眉l a .gps f谩jlt telep铆ti. A k茅sz眉l茅ke az .fw f谩jl telep铆t茅se ut谩n 煤jraindul.
+\n
+\nMegjegyz茅s: Nem kell telep铆tenie a .res 茅s .gps f谩jlokat, ha ezek a f谩jlok pontosan megegyeznek a kor谩bban telep铆tettekkel.
+\n
+\nCSAK SAJ脕T FELEL艕SS脡G脡RE FOLYTASSA!</string>
+    <string name="fw_upgrade_notice_amazfit_gts4">A(z) %s firmware-t k铆v谩nja telep铆teni az Amazfit GTS 4 k茅sz眉l茅k茅re.
+\n
+\nA .zip f谩jl telep铆t茅se ut谩n a k茅sz眉l茅ke 煤jraindul.
+\n
+\nCSAK SAJ脕T FELEL艕SS脡G脡RE FOLYTASSA!</string>
+    <string name="fw_upgrade_notice_miband5">A(z) %s firmware-t k铆v谩nja telep铆teni az Mi Band 5 k茅sz眉l茅k茅re.
+\n
+\nGy艖z艖dj枚n meg r贸la, hogy a .fw f谩jlt, majd a .res f谩jlt telep铆ti. A k茅sz眉l茅ke az .fw f谩jl telep铆t茅se ut谩n 煤jraindul.
+\n
+\nMegjegyz茅s: Nem kell telep铆tenie a .res f谩jlt, ha pontosan megegyezik a kor谩bban telep铆tettel.
+\n
+\nCSAK SAJ脕T FELEL艕SS脡G脡RE FOLYTASSA!</string>
+    <string name="fw_upgrade_notice_miband6">A(z) %s firmware-t k铆v谩nja telep铆teni az Mi Band 6 k茅sz眉l茅k茅re.
+\n
+\nGy艖z艖dj枚n meg r贸la, hogy a .fw f谩jlt, majd a .res f谩jlt telep铆ti. A k茅sz眉l茅ke az .fw f谩jl telep铆t茅se ut谩n 煤jraindul.
+\n
+\nMegjegyz茅s: Nem kell telep铆tenie a .res f谩jlt, ha pontosan megegyezik a kor谩bban telep铆tettel.
+\n
+\nCSAK SAJ脕T FELEL艕SS脡G脡RE FOLYTASSA!</string>
+    <string name="fw_upgrade_notice_amazfit_gts3">A(z) %s firmware-t k铆v谩nja telep铆teni az Amazfit GTS 3 k茅sz眉l茅k茅re.
+\n
+\nA .zip f谩jl telep铆t茅se ut谩n a k茅sz眉l茅ke 煤jraindul.
+\n
+\nCSAK SAJ脕T FELEL艕SS脡G脡RE FOLYTASSA!</string>
+    <string name="fw_upgrade_notice_amazfit_gtr3_pro">A(z) %s firmware-t k铆v谩nja telep铆teni az Amazfit GTR 3 Pro k茅sz眉l茅k茅re.
+\n
+\nA .zip f谩jl telep铆t茅se ut谩n a k茅sz眉l茅ke 煤jraindul.
+\n
+\nCSAK SAJ脕T FELEL艕SS脡G脡RE FOLYTASSA!</string>
+    <string name="fw_upgrade_notice_amazfit_gtr4">A(z) %s firmware-t k铆v谩nja telep铆teni az Amazfit GTR 4 k茅sz眉l茅k茅re.
+\n
+\nA .zip f谩jl telep铆t茅se ut谩n a k茅sz眉l茅ke 煤jraindul.
+\n
+\nCSAK SAJ脕T FELEL艕SS脡G脡RE FOLYTASSA!</string>
+    <string name="fw_upgrade_notice_amazfit_trex2">A(z) %s firmware-t k铆v谩nja telep铆teni az Amazfit T-Rex 2 k茅sz眉l茅k茅re.
+\n
+\nA .zip f谩jl telep铆t茅se ut谩n a k茅sz眉l茅ke 煤jraindul.
+\n
+\nCSAK SAJ脕T FELEL艕SS脡G脡RE FOLYTASSA!</string>
+    <string name="fw_upgrade_notice_amazfitbip3pro">A(z) %s firmware-t k铆v谩nja telep铆teni az Amazfit Bip 3 Pro k茅sz眉l茅k茅re.
+\n
+\nGy艖z艖dj枚n meg r贸la, hogy a .fw f谩jlt, majd a .res f谩jlt telep铆ti. A k茅sz眉l茅ke az .fw f谩jl telep铆t茅se ut谩n 煤jraindul.
+\n
+\nMegjegyz茅s: Nem kell telep铆tenie a .res f谩jlt, ha pontosan megegyezik a kor谩bban telep铆tettel.
+\n
+\nCSAK SAJ脕T FELEL艕SS脡G脡RE FOLYTASSA!</string>
+    <string name="fw_upgrade_notice_amazfitx">A(z) %s firmware-t k铆v谩nja telep铆teni az Amazfit X k茅sz眉l茅k茅re.
+\n
+\nGy艖z艖dj枚n meg r贸la, hogy a .fw f谩jlt, majd a .res f谩jlt telep铆ti. A k茅sz眉l茅ke az .fw f谩jl telep铆t茅se ut谩n 煤jraindul.
+\n
+\nMegjegyz茅s: Nem kell telep铆tenie a .res f谩jlt, ha pontosan megegyezik a kor谩bban telep铆tettel.
+\n
+\nCSAK SAJ脕T FELEL艕SS脡G脡RE FOLYTASSA!</string>
+    <string name="gps_galileo">GPS + GALILEO</string>
+    <string name="fw_upgrade_notice_miband3">A(z) %s firmware-t k铆v谩nja telep铆teni az Mi Band 3 k茅sz眉l茅k茅re.
+\n
+\nGy艖z艖dj枚n meg r贸la, hogy a .fw f谩jlt, majd a .res f谩jlt telep铆ti. A k茅sz眉l茅ke az .fw f谩jl telep铆t茅se ut谩n 煤jraindul.
+\n
+\nMegjegyz茅s: Nem kell telep铆tenie a .res f谩jlt, ha pontosan megegyezik a kor谩bban telep铆tettel.
+\n
+\nCSAK SAJ脕T FELEL艕SS脡G脡RE FOLYTASSA!</string>
+    <string name="fw_upgrade_notice_amazfitgtr">A(z) %s firmware-t k铆v谩nja telep铆teni az Amazfit GTR k茅sz眉l茅k茅re.
+\n
+\nGy艖z艖dj枚n meg r贸la, hogy a .fw f谩jlt, majd a .res f谩jlt 茅s v茅g眉l a .gps f谩jlt telep铆ti. A k茅sz眉l茅ke az .fw f谩jl telep铆t茅se ut谩n 煤jraindul.
+\n
+\nMegjegyz茅s: Nem kell telep铆tenie a .res 茅s .gps f谩jlokat, ha ezek a f谩jlok pontosan megegyeznek a kor谩bban telep铆tettekkel.
+\n
+\nCSAK SAJ脕T FELEL艕SS脡G脡RE FOLYTASSA!</string>
+    <string name="fw_upgrade_notice_amazfit_trex">A(z) %s firmware-t k铆v谩nja telep铆teni az Amazfit T-Rex k茅sz眉l茅k茅re.
+\n
+\nGy艖z艖dj枚n meg r贸la, hogy a .fw f谩jlt, majd a .res f谩jlt 茅s v茅g眉l a .gps f谩jlt telep铆ti. A k茅sz眉l茅ke az .fw f谩jl telep铆t茅se ut谩n 煤jraindul.
+\n
+\nMegjegyz茅s: Nem kell telep铆tenie a .res 茅s .gps f谩jlokat, ha ezek a f谩jlok pontosan megegyeznek a kor谩bban telep铆tettekkel.
+\n
+\nCSAK SAJ脕T FELEL艕SS脡G脡RE FOLYTASSA!</string>
+    <string name="fw_upgrade_notice_miband4">A(z) %s firmware-t k铆v谩nja telep铆teni az Mi Band 4 k茅sz眉l茅k茅re.
+\n
+\nGy艖z艖dj枚n meg r贸la, hogy a .fw f谩jlt, majd a .res f谩jlt telep铆ti. A k茅sz眉l茅ke az .fw f谩jl telep铆t茅se ut谩n 煤jraindul.
+\n
+\nMegjegyz茅s: Nem kell telep铆tenie a .res f谩jlt, ha pontosan megegyezik a kor谩bban telep铆tettel.
+\n
+\nCSAK SAJ脕T FELEL艕SS脡G脡RE FOLYTASSA!</string>
+    <string name="fw_upgrade_notice_miband7">A(z) %s firmware-t k铆v谩nja telep铆teni a Xiaomi Smart Band 7 k茅sz眉l茅k茅re.
+\n
+\nA .zip f谩jl telep铆t茅se ut谩n a k茅sz眉l茅ke 煤jraindul.
+\n
+\nCSAK SAJ脕T FELEL艕SS脡G脡RE FOLYTASSA!</string>
+    <string name="fw_upgrade_notice_amazfit_gts4_mini">A(z) %s firmware-t k铆v谩nja telep铆teni az Amazfit GTS 4 Mini k茅sz眉l茅k茅re.
+\n
+\nA .zip f谩jl telep铆t茅se ut谩n a k茅sz眉l茅ke 煤jraindul.
+\n
+\nCSAK SAJ脕T FELEL艕SS脡G脡RE FOLYTASSA!</string>
+    <string name="fw_upgrade_notice_amazfit_gtr3">A(z) %s firmware-t k铆v谩nja telep铆teni az Amazfit GTR 3 k茅sz眉l茅k茅re.
+\n
+\nA .zip f谩jl telep铆t茅se ut谩n a k茅sz眉l茅ke 煤jraindul.
+\n
+\nCSAK SAJ脕T FELEL艕SS脡G脡RE FOLYTASSA!</string>
+    <string name="fw_upgrade_notice_amazfit_band7">A(z) %s firmware-t k铆v谩nja telep铆teni az Amazfit Band 7 k茅sz眉l茅k茅re.
+\n
+\nA .zip f谩jl telep铆t茅se ut谩n a k茅sz眉l茅ke 煤jraindul.
+\n
+\nCSAK SAJ脕T FELEL艕SS脡G脡RE FOLYTASSA!</string>
+    <string name="fw_upgrade_notice_amazfit_cheetah_pro">A(z) %s firmware-t k铆v谩nja telep铆teni az Amazfit Cheetah Pro k茅sz眉l茅k茅re.
+\n
+\nA .zip f谩jl telep铆t茅se ut谩n a k茅sz眉l茅ke 煤jraindul.
+\n
+\nCSAK SAJ脕T FELEL艕SS脡G脡RE FOLYTASSA!</string>
+    <string name="fw_upgrade_notice_zepp_os">A(z) %s firmware-t k铆v谩nja telep铆teni a(z) %s k茅sz眉l茅k茅re.
+\n
+\nA .zip f谩jl telep铆t茅se ut谩n a k茅sz眉l茅ke 煤jraindul.
+\n
+\nCSAK SAJ脕T FELEL艕SS脡G脡RE FOLYTASSA!</string>
+    <string name="open_fw_installer_info_text">A Firmware/脫rafel眉let/Alkalmaz谩s/F谩jl telep铆t艖 lehet艖v茅 teszi a t谩mogatott f谩jlok (firmware, sz谩mlapok, alkalmaz谩sok, GPS, er艖forr谩sok, bet疟t铆pusok...) felt枚lt茅s茅t/telep铆t茅s茅t a k茅sz眉l茅kre. Tov谩bbi inform谩ci贸k a wikiben tal谩l: https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Firmware-Update</string>
+    <string name="open_fw_installer_connect_maximum_one_device">CSAK EGY olyan eszk枚zt p谩ros铆tson, amelyre el szeretn茅 k眉ldeni a f谩jlt.</string>
+    <string name="pref_summary_general_reconnectonlytoconnected">脷jrakapcsol贸d谩s csak a p谩ros铆tott eszk枚z枚kh枚z, ahelyett, hogy az 枚sszes eszk枚zzel 煤jrakapcsol贸dna</string>
+    <string name="pref_display_caller_summary">Bej枚v艖 h铆v谩sok telefonsz谩m谩nak vagy nev茅nek megjelen铆t茅se</string>
+    <string name="open_fw_installer_ensure_device_connected">Gy艖z艖dj枚n meg r贸la, hogy a(z) %s eszk枚z p谩茅ros铆tva van</string>
+    <string name="pref_title_notification_prefer_long_text">Hossz煤 茅rtes铆t茅si sz枚veg el艖nyben r茅szes铆t茅se</string>
+    <string name="pref_title_notification_use_as_deny">脡rtes铆t茅sek letilt谩sa kiv谩lasztott alkalmaz谩sokb贸l</string>
+    <string name="pref_title_notification_delay_calls">H铆v谩s 茅rtes铆t茅si k茅sleltet茅s</string>
+    <string name="pref_summary_notification_delay_calls">A bej枚v艖 h铆v谩sokr贸l sz贸l贸 茅rtes铆t茅sek k茅sz眉l茅kre k眉ld茅s茅nek k茅sleltet茅se m谩sodpercben.</string>
+    <string name="pref_pair_bluetooth_calls_title">P谩ros铆t谩s bluetooth h铆v谩sokhoz</string>
+    <string name="pref_summary_notification_prefer_long_text">Ha el茅rhet艖, k眉ldje el a hossz煤 茅rtes铆t茅si sz枚veget az eszk枚zre</string>
+    <string name="pref_title_notification_use_as_allow">脡rtes铆t茅sek enged茅lyez茅se kiv谩lasztott alkalmaz谩sokb贸l</string>
+    <string name="open_fw_installer_connect_minimum_one_device">P谩ros铆tson LEGAL脕BB EGY olyan eszk枚zt, amelyre el szeretn茅 k眉ldeni a f谩jlt.</string>
+    <string name="open_fw_installer_select_file">V谩lassza ki a k茅sz眉l茅kre felt枚lteni k铆v谩nt f谩jlt: %s</string>
+    <string name="open_fw_installer_getting_files_title">A firmware/alkalmaz谩sf谩jl megszerz茅se</string>
+    <string name="pref_title_general_reconnectonlytoconnected">脷jrakapcsol贸d谩s csak a csatlakoztatott eszk枚z枚kh枚z</string>
+    <string name="pref_title_notifications_ignore_low_priority">Alacsony priorit谩s煤 茅rtes铆t茅sek figyelmen k铆v眉l hagy谩sa</string>
+    <string name="pref_summary_notifications_ignore_work_profile">Ne k眉ldj枚n 茅rtes铆t茅seket a munkaprofilban l茅v艖 alkalmaz谩sokb贸l az 贸r谩ra</string>
+    <string name="pref_summary_banglejs_text_bitmap">Ha egy sz贸 nem jelen铆thet艖 meg az 贸ra bet疟t铆pus谩val, jelen铆tse meg bitt茅rk茅pk茅nt a Gadgetbridge-ben, 茅s jelen铆tse meg a bitk茅pet az 贸r谩n</string>
+    <string name="fw_upgrade_notice_amazfitneo">A(z) %s firmware-t k铆v谩nja telep铆teni az Amazfit Neo k茅sz眉l茅k茅re.
+\n
+\nA .fw f谩jl telep铆t茅se ut谩n a kark枚t艖je 煤jraindul.
+\n
+\nCSAK SAJ脕T FELEL艕SS脡G脡RE FOLYTASSA!</string>
+    <string name="open_fw_installer_warning_text">Ez a funkci贸 k茅pes t枚nkretenni a k茅sz眉l茅ket. Ez azonban m茅g soha nem t枚rt茅nt meg egyik fejleszt艖vel sem a telep铆t茅s sor谩n, de ne feledje, hogy ezt saj谩t felel艖ss茅g茅re teszi.</string>
+    <string name="open_fw_installer_getting_files_text">Mivel a firmware f谩jlokat nem 谩ll m贸dunkban terjeszteni, a f谩jlokat saj谩t mag谩nak kell beszereznie. Ez azt jelenti, hogy meg kell keresnie a f谩jlokat az apk f谩jlokban, az interneten, a f贸rumokon, az Amazfitwatchfaces-en (Miband/Amazfit k茅sz眉l茅kek eset茅ben) stb.</string>
+    <string name="pref_header_equalizer">Hangsz铆nszab谩lyz贸</string>
+    <string name="pref_pair_bluetooth_calls_summary">Kattintson ide a p谩ros铆t谩si folyamat elind铆t谩s谩hoz</string>
+    <string name="pref_pair_bluetooth_calls_help_title">Hogyan fogadhat bluetooth h铆v谩sokat</string>
+    <string name="pref_pair_bluetooth_calls_help_2">2. L茅pjen be a telefonja Bluetooth be谩ll铆t谩saiba, 茅s p谩ros铆tsa az 煤j eszk枚zt, amely megjelenik (hasonl贸 n茅vvel, mint a jelenlegi 贸r谩ja, de egy ut贸taggal, pl. \"Amazfit GTR 4 - AFC8\".</string>
+    <string name="pref_pair_bluetooth_calls_help_summary">Ahhoz, hogy bluetooth h铆v谩sokat fogadhasson, p谩ros铆tania kell a telefonj谩t az 贸ra egy m谩sodik p茅ld谩ny谩val.</string>
+    <string name="pref_pair_bluetooth_calls_help_1">1. 脡rintse meg az al谩bbi gombot a p谩ros铆t谩si folyamat elind铆t谩s谩hoz.</string>
+    <string name="pref_pair_bluetooth_calls_help_3">3. Enged茅lyezze az al谩bbi Bluetooth h铆v谩sok be谩ll铆t谩s谩t.</string>
+    <string name="pref_title_notifications_generic_settings">Android 茅rtes铆t茅si be谩ll铆t谩sok</string>
+    <string name="pref_summary_notifications_ignore_low_priority">Ne k眉ldj枚n alacsony 茅s minim谩lis priorit谩s煤 茅rtes铆t茅seket az 贸r谩ra</string>
+    <string name="pref_title_notification_media_ignores_application_list">A m茅dia茅rtes铆t茅sek figyelmen k铆v眉l hagyj谩k az alkalmaz谩slist谩t</string>
+    <string name="pref_summary_notification_media_ignores_application_list">A m茅dia茅rtes铆t茅sek feldolgoz谩sa az alkalmaz谩slista el艖tt. Ha ez a be谩ll铆t谩s nincs bejel枚lve, a m茅diaalkalmaz谩soknak enged茅lyezve kell lenni眉k az alkalmaz谩slist谩ban ahhoz, hogy a m茅diavez茅rl艖k m疟k枚djenek az eszk枚z枚n.</string>
+    <string name="pref_title_notification_use_as">Haszn谩lja az Alkalmaz谩sok list谩j谩t鈥�</string>
+    <string name="bluetooth_calls">Bluetooth h铆v谩sok</string>
+    <string name="pref_title_mb_intents">K枚zvetlen m茅dia k枚zvet铆t茅s gomb</string>
+    <string name="pref_summary_mb_intents">Enged茅lyez茅s, ha az eszk枚z m茅diavez茅rl茅se nem m疟k枚dik bizonyos alkalmaz谩sokn谩l</string>
+    <string name="bluetooth_calls_pairing">Bluetooth h铆v谩sok p谩ros铆t谩sa</string>
+    <string name="pref_theme_black_background">Haszn谩ljon fekete h谩tteret a s枚t茅t t茅m谩ban</string>
+    <string name="pref_summary_receive_calls_watch">H铆v谩sok kezdem茅nyez茅se 茅s fogad谩sa k枚zvetlen眉l az 贸r谩n</string>
+    <string name="bluetooth_calls_settings">Bluetooth h铆v谩sok be谩ll铆t谩sai</string>
+    <string name="pref_display_caller_title">Mutassa meg az el茅rhet艖s茅geket</string>
+    <string name="pref_title_banglejs_text_bitmap">Sz枚veg bitt茅rk茅pk茅nt</string>
+    <string name="pref_pair_bluetooth_calls_help_warning">FIGYELMEZTET脡S: Ha enged茅lyezi a bluetooth h铆v谩sokat a m谩sodik p茅ld谩ny p谩ros铆t谩sa n茅lk眉l, el艖fordulhat, hogy a h铆v谩s茅rtes铆t茅sek nem a megfelel艖 m贸don fognak m疟k枚dni.</string>
+    <string name="pref_title_notifications_ignore_work_profile">Munkaprofil-茅rtes铆t茅sek figyelmen k铆v眉l hagy谩sa</string>
+    <string name="pref_title_banglejs_phone_gps_enbale">Telefonos GPS-adatok haszn谩lata</string>
+    <string name="pref_title_banglejs_webview_url">Alkalmaz谩sbet枚lt艖 URL</string>
+    <string name="pref_summary_custom_deviceicon">Csatlakoz谩skor egy eszk枚zspecifikus Android 茅rtes铆t茅si ikont jelen铆tsen meg a Gadgetbridge ikon helyett</string>
+    <string name="pref_title_screen_on_on_notifications">Bekapcsolt kijelz艖 az 脡rtes铆t茅sek-n茅l</string>
+    <string name="pref_summary_banglejs_phone_gps_network_only">Csak a h谩l贸zati szolg谩ltat贸 haszn谩lata a helysz铆n meghat谩roz谩s谩hoz. Ez cs枚kkenti az energiafogyaszt谩st a pontoss谩g rov谩s谩ra. 脷jracsatlakoz谩s sz眉ks茅ges.</string>
+    <string name="pref_title_banglejs_phone_gps_update_interval">GPS adatfriss铆t茅si id艖k枚z</string>
+    <string name="pref_summary_banglejs_webview_url">Ha egy茅ni alkalmaz谩sbet枚lt艖t szeretne, adja meg https://鈥�/android.html URL-j茅t ide. Ellenkez艖 esetben hagyja 眉resen a https://banglejs.com/apps mez艖t</string>
+    <string name="pref_title_message_privacy_mode">脺zenet adatv茅delmi m贸d</string>
+    <string name="pref_title_weather_summary">A LineageOS id艖j谩r谩s-szolg谩ltat贸hoz haszn谩latos m谩s Android verzi贸knak olyan alkalmaz谩st kell haszn谩lniuk, mint az \"Id艖j谩r谩s 茅rtes铆t茅s\". Tov谩bbi inform谩ci贸kat a Gadgetbridge wikiben tal谩l.</string>
+    <string name="pref_title_device_intents">Intentek enged茅lyez茅se</string>
+    <string name="pref_title_preview_message_in_title">Az 眉zenet el艖n茅zet茅nek megjelen铆t茅se a c铆mben</string>
+    <string name="pref_summary_preview_message_in_title">Megjelen铆ti az 眉zenet el艖n茅zet茅t az 茅rtes铆t茅s c铆m茅ben, ahogy azt az eszk枚z enged茅lyezi</string>
+    <string name="pref_title_casio_alert_calendar">Riaszt谩s a napt谩ri 茅rtes铆t茅sekn茅l</string>
+    <string name="pref_summary_casio_alert_email">Riaszt谩s (vibr谩l谩s/csipog谩s) az e-mail 茅rtes铆t茅sekn茅l</string>
+    <string name="pref_summary_casio_alert_other">Riaszt谩s (vibr谩l谩s/csipog谩s) az egy茅b kateg贸ri谩ba tartoz贸 茅rtes铆t茅sekn茅l</string>
+    <string name="intent_api_broadcast_export_title">K枚zvet铆t茅s az adatb谩zis export谩l谩sakor</string>
+    <string name="activity_prefs_allow_bluetooth_intent_api">Bluetooth Intent API</string>
+    <string name="pref_message_privacy_mode_bodyonly">Csak az 眉zenet sz枚veg茅nek elrejt茅se</string>
+    <string name="pref_blacklist_calendars_summary">A tilt贸list谩n szerepl艖 napt谩rak nem lesznek szinkroniz谩lva az eszk枚zzel</string>
+    <string name="intent_api_allow_activity_sync_title">Enged茅lyezze a tev茅kenys茅g szinkroniz谩l谩s谩nak kiv谩lt谩s谩t</string>
+    <string name="intent_api_allow_activity_sync_summary">Tev茅kenys茅g szinkroniz谩l谩s ind铆t谩s谩nak enged茅lyez茅se Intent API-n kereszt眉l</string>
+    <string name="activity_prefs_summary_allow_bluetooth_intent_api">Bluetooth-kapcsolat vez茅rl茅s茅nek enged茅lyez茅se Intent API-n kereszt眉l</string>
+    <string name="pref_title_ping_tone">Cseng艖hang</string>
+    <string name="pref_title_banglejs_txt_bitmap_size">Sz枚veg Bitk茅p m茅rete</string>
+    <string name="pref_summary_banglejs_txt_bitmap_size">A bitt茅rk茅pes sz枚vegmegjelen铆t茅shez haszn谩lhat贸 m茅ret</string>
+    <string name="pref_summary_banglejs_phone_gps_enbale">Haszn谩lja a telefon GPS-adatait a kark枚t艖 GPS-adatainak fel眉l铆r谩s谩hoz</string>
+    <string name="pref_rtl_max_line_length_summary">Meghosszabb铆tja vagy ler枚vid铆ti azokat a sorokat, amelyekre a jobbr贸l balra halad贸 sz枚veg fel van osztva</string>
+    <string name="pref_summary_casio_alert_calendar">Riaszt谩s (vibr谩l谩s/csipog谩s) a napt谩r茅rtes铆t茅sekn茅l</string>
+    <string name="pref_title_casio_alert_call">Riaszt谩s a bej枚v艖 h铆v谩sokn谩l</string>
+    <string name="pref_summary_casio_alert_call">Riaszt谩s (vibr谩l谩s/csipog谩s) a napt谩ri 茅rtes铆t茅sekn茅l</string>
+    <string name="pref_title_casio_alert_email">Riaszt谩s az e-mail 茅rtes铆t茅sekn茅l</string>
+    <string name="pref_title_casio_alert_sms">Riaszt谩s az SMS 茅rtes铆t茅sekn茅l</string>
+    <string name="pref_summary_casio_alert_sms">Riaszt谩s (vibr谩l谩s/csipog谩s) az SMS 茅rtes铆t茅sekn茅l (sz枚veges 眉zenet)</string>
+    <string name="pref_title_casio_alert_other">Riaszt谩s egy茅b 茅rtes铆t茅sekn茅l</string>
+    <string name="pref_summary_banglejs_phone_gps_update_interval">A GPS poz铆ci贸 friss铆t茅s茅nek gyakoris谩ga ms-ban</string>
+    <string name="pref_header_intent_api">Intent API</string>
+    <string name="intent_api_broadcast_export_summary">Intent k枚zvet铆t茅se, amikor az adatb谩zis export谩l谩sa befejez艖dik</string>
+    <string name="intent_api_allow_trigger_export_title">Adatb谩zisexport enged茅lyez茅se</string>
+    <string name="intent_api_allow_trigger_export_summary">Adatb谩zisexport kiv谩lt谩s谩nak enged茅lyez茅se Intent API-n kereszt眉l</string>
+    <string name="femometer_measurement_mode_normal">Norm谩l m贸d (60-90s)</string>
+    <string name="femometer_measurement_mode_precise">Prec铆z m贸d (3 perc)</string>
+    <string name="pref_gps_band">GPS s谩v</string>
+    <string name="pref_agps_expiry_reminder_enabled">AGPS lej谩rati eml茅keztet艖</string>
+    <string name="prefs_sensors_button_bp_calibration_sum">Kattintson ide a kalibr谩l谩s megkezd茅s茅hez</string>
+    <string name="pref_workout_detection_ask_first_summary">Edz茅s 茅szlel茅sekor az 贸r谩n t枚rt茅n艖 meger艖s铆t茅s sz眉ks茅ges</string>
+    <string name="world_clock_no_free_slots_title">Nincs szabad hely</string>
+    <string name="pref_summary_connected_advertisement">Felfedezhet艖v茅 teszi az eszk枚zt Bluetooth-on kereszt眉l, m茅g akkor is, ha csatlakoztatva van</string>
+    <string name="pref_summary_use_custom_font">Enged茅lyezze ezt, ha eszk枚ze egy茅ni bet疟t铆pus-firmware-rel rendelkezik a hangulatjelek t谩mogat谩s谩hoz</string>
+    <string name="pref_title_device_internet_access">Internet-hozz谩f茅r茅s enged茅lyez茅se</string>
+    <string name="pref_summary_send_app_notifications">Alkalmaz谩s茅rtes铆t茅sek k眉ld茅se az eszk枚zre</string>
+    <string name="pref_title_relax_firmware_checks">Enged茅lyezze ezt, ha olyan firmware-t szeretne flashelni, amely nem az 脰n k茅sz眉l茅k茅hez k茅sz眉lt (saj谩t felel艖ss茅g茅re)</string>
+    <string name="pref_summary_huami_force_new_protocol">Enged茅lyez茅s, ha az eszk枚z m谩r nem csatlakozik a firmware-friss铆t茅s ut谩n</string>
+    <string name="pref_title_goal_secondary">M谩sodlagos c茅l</string>
+    <string name="pref_summary_notifications_and_calls_enable_misscall">Percenk茅nti ism茅tl艖d茅s</string>
+    <string name="pref_title_sensors_altitude">Magass谩g kalibr谩l谩s</string>
+    <string name="femometer_measurement_mode_title">M茅r茅si m贸d</string>
+    <string name="zetime_signaling_vibrate_beep_once">Vibr谩l谩s 茅s csipog谩s egyszer</string>
+    <string name="pref_gps_mode_preset">GPS m贸d</string>
+    <string name="pref_agps_expiry_reminder_time">AGPS lej谩rati eml茅keztet艖 ideje</string>
+    <string name="pref_camera_remote_title">Kamera t谩vvez茅rl茅s</string>
+    <string name="pref_workout_start_on_phone_summary">Fitnesz alkalmaz谩s nyomon k枚vet茅s茅nek elind铆t谩sa/le谩ll铆t谩sa a telefonon, amikor a GPS-edz茅s elindul a kark枚t艖n</string>
+    <string name="pref_workout_detection_categories_summary">Automatikusan felismerhet艖 edz茅skateg贸ri谩k</string>
+    <string name="pref_workout_detection_time_summary">Az edz茅s percek sz谩mainak folyamatban kell lennie, miel艖tt 茅szleln茅</string>
+    <string name="pref_sleep_mode_sleep_screen_summary">Jelen铆tse meg az alv贸 k茅perny艖t, amikor alv贸 眉zemm贸dban fel茅breszti a k茅perny艖t, hogy cs枚kkentse a zavar贸 t茅nyez艖ket</string>
+    <string name="preferences_miband_1_2_warning">Figyelmeztet茅s: Ezek a be谩ll铆t谩sok csak a Mi Bands 1-re 茅s 2-re vonatkoznak.</string>
+    <string name="pref_write_logfiles_not_available">A f谩jlnapl贸z谩s inicializ谩l谩sa sikertelen, a napl贸f谩jlok 铆r谩sa jelenleg nem el茅rhet艖. Ind铆tsa 煤jra az alkalmaz谩st, hogy 煤jra megpr贸b谩lja inicializ谩lni a napl贸f谩jlokat.</string>
+    <string name="contact_no_free_slots_description">Az eszk枚z枚n nincs szabad hely az n茅vjegyek sz谩m谩ra (枚sszes hely: %1$s)</string>
+    <string name="world_clock_no_free_slots_description">Az eszk枚z枚n nincs szabad hely a vil谩g贸r谩k sz谩m谩ra (枚sszes hely: %1$s)</string>
+    <string name="chart_get_active_and_synchronize">V茅gezzen valamilyen tev茅kenys茅get, 茅s szinkroniz谩lja az eszk枚zt.</string>
+    <string name="pref_summary_device_intents">Enged茅lyezze a Bangle.js 贸raalkalmaz谩sok sz谩m谩ra az Android Intentek k眉ld茅s茅t, 茅s az Androidon fut贸 egy茅b alkalmaz谩sok (p茅ld谩ul a Tasker) sz谩m谩ra, hogy adatokat k眉ldjenek a Bangle.js r茅sz茅re a com.banglejs.uart.tx Intentel. A h谩tt茅rben val贸 m疟k枚d茅shez enged茅lyre van sz眉ks茅ge a t枚bbi alkalmaz谩s feletti megjelen铆t茅shez.</string>
+    <string name="reminder_time_once">%1$s, egyszer</string>
+    <string name="pref_workout_detection_alert_summary">脡rtes铆t茅s, ha egy edz茅s 茅szlelve lett</string>
+    <string name="pref_disable_new_ble_scanning">Az 煤j BLE szkennel茅s letilt谩sa</string>
+    <string name="contact_delete_confirm_description">Biztosan t枚rli a k枚vetkez艖t: 鈥�%1$s鈥�?</string>
+    <string name="pref_screen_notification_idle_alerts">脺resj谩rati riaszt谩sok</string>
+    <string name="pref_notifications_and_calls_enable_misscall">脡rtes铆t茅s a nem fogadott h铆v谩sr贸l</string>
+    <string name="pref_summary_device_spec_settings_title_force_time">Automatikus id艖szinkroniz谩l谩s k茅nyszer铆t茅se 煤jracsatlakoz谩skor. Az anal贸g mutat贸k helytelen id艖t mutathatnak!</string>
+    <string name="pref_sensors_bp_calibration">V茅rnyom谩s kalibr谩l谩s</string>
+    <string name="pref_morning_updates_categories_title">Reggeli friss铆t茅sek kateg贸ri谩i</string>
+    <string name="pref_agps_expire_time">AGPS lej谩rati id艖</string>
+    <string name="offline_voice_respond_turn_wrist">V谩laszoljon a csukl贸 elford铆t谩sakor</string>
+    <string name="busy_task_fetch_temperature">H艖m茅rs茅kleti adatok lek茅r茅se</string>
+    <string name="prefs_weardirection">Visel茅si ir谩ny</string>
+    <string name="busy_task_fetch_statistics">Statisztik谩k lek茅r茅se</string>
+    <string name="pref_summary_screen_on_on_notifications">Kapcsolja be a kark枚t艖 k茅perny艖j茅t, amikor egy 茅rtes铆t茅s 茅rkezik</string>
+    <string name="prefs_notifications_and_calls_reject">H铆v谩s figyelmen k铆v眉l hagy谩sa/elutas铆t谩sa gomb</string>
+    <string name="pref_summary_notifications_and_calls_title_reject">Ki 鈥� figyelmen k铆v眉l hagy谩s, Be 鈥� elutas铆t谩s</string>
+    <string name="prefs_notifications_and_calls_shake_reject">Csukl贸r谩z谩s, figyelmen k铆v眉l hagyja/elutas铆tja a h铆v谩st</string>
+    <string name="pref_summary_notifications_and_calls_title_shake_reject">Megkett艖zi az 贸ragomb m疟velet茅t</string>
+    <string name="pref_title_device_spec_settings_force_time">K茅nyszer铆tett id艖szinkroniz谩l谩s</string>
+    <string name="pref_header_sensors_calibration">脡rz茅kel艖 kalibr谩l谩sa</string>
+    <string name="pref_sensors_bp_calibration_low">DIASZTOL脡S v茅rnyom谩s (alacsony)</string>
+    <string name="pref_sensors_bp_calibration_high">SZISZTOL脡S v茅rnyom谩s (magas)</string>
+    <string name="power_mode_title">脫ra energia 眉zemm贸d</string>
+    <string name="femometer_measurement_mode_quick">Gyors m贸d (30 mp)</string>
+    <string name="pref_gps_combination">GPS kombin谩ci贸</string>
+    <string name="pref_gps_satellite_search">M疟holdas keres茅s</string>
+    <string name="pref_alert_tone">Riaszt谩si hang</string>
+    <string name="pref_cover_to_mute">N茅m铆t谩s lefed茅sre</string>
+    <string name="preferences_miband_1_2_settings" tools:ignore="TypographyFractions">Mi Band 1/2 be谩ll铆t谩sok</string>
+    <string name="buttons_on_left">Gombok a bal oldalon</string>
+    <string name="buttons_on_right">Gombok a jobb oldalon</string>
+    <string name="pref_screen_notification_profile_low_power">Alacsony energiaell谩t谩s figyelmeztet茅s</string>
+    <string name="prefs_notifications_and_calls_continious_ring">脡rtes铆t茅s telefoncs枚rg茅s k枚zben</string>
+    <string name="preferences_makibes_hr3_settings">Makibes HR3 be谩ll铆t谩sok</string>
+    <string name="pref_title_notifications_and_calls_repeat_on_call">Ism茅telt h铆v谩s茅rtes铆t茅s</string>
+    <string name="zetime_activity_tracking_summary">Ha bekapcsolja a tev茅kenys茅gk枚vet茅st, sz谩molni fogja a l茅p茅seit, 茅s 铆gy tov谩bb.</string>
+    <string name="pref_morning_updates_categories_summary">Minden reggel megjelen艖 kateg贸ri谩k list谩ja</string>
+    <string name="pref_workout_detection_enabled_summary">Enged茅lyezze az edz茅s automatikus 茅szlel茅s茅t</string>
+    <string name="pref_workout_detection_title">Edz茅s茅rz茅kel茅s</string>
+    <string name="reminder_time_every_year">%1$s, minden 茅vben</string>
+    <string name="prefs_heartrate_alert_experimental_title">Sz铆vritmus riaszt谩s (k铆s茅rleti)</string>
+    <string name="watchface_install_info">A k枚vetkez艖 贸rafel眉letet k茅sz眉l telep铆teni: \n \n%1$s \nVerzi贸: %2$s, k茅sz铆t艖: %3$s\n</string>
+    <string name="clap_hands_to_wakeup_device">A k茅perny艖 fel茅leszt茅s茅hez tapsoljon a kez茅vel</string>
+    <string name="clap_hands_to_wakeup_device_summary">Az 煤jb贸li tapsol谩s kikapcsolja a k茅perny艖t</string>
+    <string name="pixoo_power_saving_summary">A k茅perny艖 kikapcsol, ha a mikrofon egy ideig csendet 茅szlel</string>
+    <string name="offline_voice_respond_screen_on">V谩laszoljon, amikor a k茅perny艖 be van kapcsolva</string>
+    <string name="offline_voice_response_during_screen_lighting">V谩laszoljon a k茅perny艖 megvil谩g铆t谩sa k枚zben</string>
+    <string name="pref_morning_updates_title">Reggeli friss铆t茅sek</string>
+    <string name="pref_morning_updates_summary">Friss铆t茅sek megjelen铆t茅se minden reggel</string>
+    <string name="pref_workout_start_on_phone_title">Fitnesz alkalmaz谩s k枚vet茅se</string>
+    <string name="pref_workout_send_gps_summary">Edz茅s k枚zben k眉ldje el az aktu谩lis GPS-poz铆ci贸t a kark枚t艖nek</string>
+    <string name="pref_workout_keep_screen_on_summary">A k茅perny艖 bekapcsolva marad az edz茅s alatt, 茅s a f茅nyer艖t a rendszer 煤gy 谩ll铆tja be, hogy folyamatosan megjelen铆tse a val贸s idej疟 edz茅sadatokat</string>
+    <string name="pref_workout_detection_summary">Az edz茅s automatikus felismer茅se</string>
+    <string name="pref_screen_notification_profile_anti_loss">Veszt茅sg谩tl贸 figyelmeztet茅s</string>
+    <string name="pref_screen_notification_profile_schedule">Menetrend</string>
+    <string name="pref_screen_notification_profile_todo_list">Feladatlista</string>
+    <string name="wearmode_band">Kark枚t艖 (csukl贸p谩nt)</string>
+    <string name="wearmode_necklace">Nyakl谩nc (nyakp谩nt)</string>
+    <string name="prefs_wearmode">Visel茅si m贸d</string>
+    <string name="pref_title_send_app_notifications">脡rtes铆t茅sek k眉ld茅se</string>
+    <string name="pref_title_overwrite_settings_on_connection">Csatlakoz谩si be谩ll铆t谩sok fel眉l铆r谩sa</string>
+    <string name="pref_summary_overwrite_settings_on_connection">A kark枚t艖h枚z val贸 csatlakoz谩skor 铆rja fel眉l a rajta l茅v艖 枚sszes be谩ll铆t谩st.</string>
+    <string name="pref_summary_device_internet_access">Enged茅lyezze az alkalmaz谩sok sz谩m谩ra az internet-hozz谩f茅r茅st ezen az eszk枚z枚n</string>
+    <string name="pref_summary_relax_firmware_checks">A firmware-ellen艖rz茅sek laz铆t谩sa</string>
+    <string name="pref_contacts_summary">N茅vjegyek konfigur谩l谩sa az 贸r谩n</string>
+    <string name="heartrate_bpm_155">155 bpm</string>
+    <string name="heartrate_bpm_165">165 bpm</string>
+    <string name="heartrate_bpm_175">175 bpm</string>
+    <string name="heartrate_bpm_185">185 bpm</string>
+    <string name="heartrate_bpm_195">195 bpm</string>
+    <string name="heartrate_bpm_205">205 bpm</string>
+    <string name="title_activity_world_clock_details">Vil谩g贸ra r茅szletei</string>
+    <string name="chart_no_active_data">Nem 茅szlelt眉nk tev茅kenys茅get.</string>
+    <string name="pref_title_notifications_and_calls_repeat_on_misscall">Ism茅tl茅s X percig</string>
+    <string name="pref_agps_update_time">AGPS friss铆t茅si id艖</string>
+    <string name="pref_text_to_speech">Sz枚vegb艖l besz茅dd茅 alak铆t谩s</string>
+    <string name="pref_camera_remote_summary">Lehet艖v茅 teszi, hogy az 贸ra aktiv谩lja a telefon kamer谩j谩t</string>
+    <string name="pref_sleep_mode_sleep_screen_title">Alv贸 k茅perny艖</string>
+    <string name="pref_sleep_mode_smart_enable_summary">Automatikusan enged茅lyezze az alv贸 眉zemm贸dot, ha alv谩s k枚zben viseli a kark枚t艖t</string>
+    <string name="pref_auto_fetch_summary">A lek茅r茅s a k茅perny艖 felold谩sakor t枚rt茅nik. Csak akkor m疟k枚dik, ha be van 谩ll铆tva egy z谩rszerkezet!</string>
+    <string name="reminder_time_every_week">%1$s, minden h茅ten</string>
+    <string name="reminder_time_every_month">%1$s, minden h贸napban</string>
+    <string name="reminder_no_free_slots_description">Az eszk枚z枚n nincs szabad hely az eml茅keztet艖k sz谩m谩ra (枚sszes id艖k枚z: %1$s)</string>
+    <string name="reminder_no_free_slots_title">Nincs szabad hely</string>
+    <string name="contact_delete_confirm_title">N茅vjegy t枚rl茅se</string>
+    <string name="world_clock_delete_confirm_description">Biztosan t枚rli a vil谩g贸r谩t?</string>
+    <string name="title_activity_contact_details">N茅vjegy adatok</string>
+    <string name="prefs_relaxation_reminder_description">A kark枚t艖 rezg茅s茅vel 茅rtes铆tse, ha a fesz眉lts茅g 茅rt茅ke meghaladja a 80-at</string>
+    <string name="devicestatus_upload_failed">A felt枚lt茅s nem siker眉lt</string>
+    <string name="prefs_always_on_display_follow_watchface">A st铆lus k枚veti az 贸rafel眉letet</string>
+    <string name="prefs_heartrate_alert_low_threshold">Alacsony pulzussz谩m riaszt谩si k眉sz枚b</string>
+    <string name="pref_title_expose_hr">Harmadik f茅l 谩ltali val贸s idej疟 HR hozz谩f茅r茅se</string>
+    <string name="prefs_heartrate_alert_high_threshold">Magas pulzussz谩m riaszt谩si k眉sz枚b</string>
+    <string name="prefs_stress_monitoring_description">Pihen茅s k枚zbeni stresszszint ellen艖rz茅se</string>
+    <string name="updatefirmwareoperation_updateproblem_free_space">A k茅sz眉l茅ken nincs el茅g szabad hely</string>
+    <string name="pref_enable_unsupported_settings_title">Enged茅lyezze a nem t谩mogatott be谩ll铆t谩sokat</string>
+    <string name="prefs_find_phone_duration">A csenget茅s id艖tartama m谩sodpercben</string>
+    <string name="prefs_screen_timeout">K茅perny艖 id艖t煤ll茅p茅se</string>
+    <string name="english_us">Angol (Egyes眉lt 脕llamok)</string>
+    <string name="activity_type_pool_swimming">Medence 煤sz谩s</string>
+    <string name="mi2_prefs_button_action_summary">M疟velet enged茅lyez茅se meghat谩rozott sz谩m煤 gombnyom谩sra</string>
+    <string name="prefs_reserve_reminder_calendar_summary">A szinkroniz谩land贸 napt谩resem茅nyek sz谩ma</string>
+    <string name="mi2_prefs_button_press_count_max_delay">Maxim谩lis k茅sleltet茅s a lenyom谩sok k枚z枚tt</string>
+    <string name="prefs_device_name">Eszk枚z neve</string>
+    <string name="prefs_password_6_digits_0_to_9_summary">A jelsz贸nak 6 sz谩mjegy疟nek kell lennie, csak sz谩mokat haszn谩lva</string>
+    <string name="do_not_disturb_lift_wrist_summary">Csak akkor, ha a kijelz艖 aktiv谩l谩sa emel茅skor enged茅lyezve van</string>
+    <string name="lefun_prefs_hydration_reminder_interval_title">Hidrat谩l谩si eml茅keztet艖 intervallum (percben)</string>
+    <string name="activity_prefs_chart_max_idle_phase_length">Sz眉net hossza a tev茅kenys茅gek elk眉l枚n铆t茅s茅hez (perc)</string>
+    <string name="dbmanagementactivity_export_confirmation">Val贸ban export谩lni k铆v谩nja az adatokat? A kor谩bban export谩lt tev茅kenys茅gadatok (ha vannak) 茅s be谩ll铆t谩sok fel眉l铆r贸dnak.</string>
+    <string name="activity_db_management_autoexport_location">A helysz铆n nem 茅rtelmezhet艖. Val贸sz铆n疟leg az 煤jabb Android enged茅lyez茅si rendszer probl茅m谩ja. Val贸sz铆n疟bb, hogy az automatikus export谩l谩s jelenleg nem m疟k枚dik.</string>
+    <string name="dnd_all_day">Eg茅sz nap</string>
+    <string name="activity_type_free_training">Szabadedz茅s</string>
+    <string name="activity_type_indoor_running">Belt茅ri fut谩s</string>
+    <string name="activity_type_mountain_hike">Hegyi t煤ra</string>
+    <string name="activity_type_cross_trainer">Cross edz艖</string>
+    <string name="activity_type_rower">Evez茅s</string>
+    <string name="activity_type_taekwondo">Taekwondo</string>
+    <string name="activity_type_cross_country_running">Terepfut谩s</string>
+    <string name="activity_type_karate">Karate</string>
+    <string name="activity_type_fencing">V铆v谩s</string>
+    <string name="activity_type_fitness_gaming">Fitness j谩t茅kok</string>
+    <string name="activity_type_sit_ups">Fel眉l茅sek</string>
+    <string name="activity_type_smith_machine">Smith-g茅p</string>
+    <string name="activity_type_pull_ups">H煤z贸dzkod谩s</string>
+    <string name="activity_type_javelin">Gerely</string>
+    <string name="activity_type_long_jump">T谩volugr谩s</string>
+    <string name="activity_type_high_jump">Magasugr谩s</string>
+    <string name="activity_type_trampoline">Trambulin</string>
+    <string name="prefs_heartrate_alert_experimental_description">R谩zza meg a kark枚t艖t, ha a pulzussz谩m t煤ll茅p egy k眉sz枚b茅rt茅ket, an茅lk眉l, hogy az elm煤lt 10 percben b谩rmilyen nyilv谩nval贸 fizikai tev茅kenys茅get v茅gzett volna. Ez a funkci贸 k铆s茅rleti jelleg疟, 茅s nem lett alaposan tesztelve.</string>
+    <string name="prefs_stress_monitoring_title">Stresszfigyel茅s</string>
+    <string name="prefs_heartrate_alert_threshold">Pulzussz谩m riaszt谩si k眉sz枚b茅rt茅ke</string>
+    <string name="prefs_charts_tabs">Diagramok f眉lek</string>
+    <string name="devicestatus_upload_completed">A felt枚lt茅s befejez艖d枚tt</string>
+    <string name="devicestatus_upload_validating">A felt枚lt茅s ellen艖rz茅se folyamatban van</string>
+    <string name="devicestatus_upload_aborted">A felt枚lt茅s megszakadt!</string>
+    <string name="firmware_update_progress">Felt枚lt茅s folyamatban
+\n%1d%% %.2fkbps sebess茅ggel (谩tlag %.2fkbps)
+\n%1d r茅sz %1d-b艖l</string>
+    <string name="updatefirmwareoperation_failed_low_mtu">A %1$d jelenlegi MTU 茅rt茅ke t煤l alacsony, enged茅lyezze a magas MTU 茅rt茅ket az eszk枚z be谩ll铆t谩saiban, 茅s v谩lassza az eszk枚z lev谩laszt谩s谩t/p谩ros铆t谩s谩t.</string>
+    <string name="pref_title_third_party_app_device_settings">Enged茅lyezze a harmadik f茅lt艖l sz谩rmaz贸 alkalmaz谩sok be谩ll铆t谩sainak m贸dos铆t谩s谩t</string>
+    <string name="pref_summary_third_party_app_device_settings">Enged茅lyezze m谩s telep铆tett harmadik f茅lt艖l sz谩rmaz贸 alkalmaz谩sok sz谩m谩ra az eszk枚zbe谩ll铆t谩sok intenten kereszt眉l t枚rt茅n艖 be谩ll铆t谩s谩t.</string>
+    <string name="activity_type_hand_cycling">K茅zi ker茅kp谩roz谩s</string>
+    <string name="activity_type_skating">Korcsoly谩z谩s</string>
+    <string name="activity_type_curling">Curling</string>
+    <string name="activity_type_skateboarding">G枚rdeszk谩z谩s</string>
+    <string name="activity_type_outdoor_walking">Szabadt茅ri s茅ta</string>
+    <string name="activity_type_ice_hockey">J茅gkorong</string>
+    <string name="activity_type_cross_country_skiing">S铆fut谩s</string>
+    <string name="norwegian_bokmal">Norv茅g Bokm氓l</string>
+    <string name="prefs_always_on_display_summary">Tartsa a kakr枚t艖 kijelz艖j茅t mindig bekapcsolt 谩llapotba</string>
+    <string name="prefs_password_summary">Z谩rja le a kark枚t艖t jelsz贸val, amikor elt谩vol铆tja a csukl贸j谩r贸l</string>
+    <string name="prefs_shortcut_cards_summary">Jobbra h煤z谩skor az 贸rafel眉leten l谩that贸v谩 v谩lnak a gyorsbillenty疟k谩rty谩k. Ha egy alkalmaz谩s fut, az automatikusan gener谩lt k谩rty谩kat ez a be谩ll铆t谩s nem 茅rinti.</string>
+    <string name="prefs_control_center">Vez茅rl艖k枚zpont</string>
+    <string name="activity_type_swimming_openwater">脷sz谩s (ny铆lt v铆zen)</string>
+    <string name="extended_ascii">Kib艖v铆tett ASCII</string>
+    <string name="devicestatus_upload_starting">A felt枚lt茅s megkezd艖dik</string>
+    <string name="prefs_enable_find_phone">Kapcsolja be a 鈥濼elefon keres茅se鈥� funkci贸t</string>
+    <string name="mi3_prefs_band_screen_unlock">Kark枚t艖 k茅perny艖 felold谩sa</string>
+    <string name="charts_legend_stress_average">Stressz 谩tlag</string>
+    <string name="devicestatus_upload_started">A felt枚lt茅s megkezd艖d枚tt</string>
+    <string name="gpx_route_upload_complete">Gpx 煤tvonal felt枚lt茅se befejez艖d枚tt</string>
+    <string name="pref_huami_truncate_fetch_operation_timestamps_title">A leh铆v谩si m疟velet id艖b茅lyegz艖inek csonk铆t谩sa</string>
+    <string name="portuguese_br">Portug谩l (Braz铆lia)</string>
+    <string name="dbmanagementactivity_error_cleaning_export_directory">Hiba t枚rt茅nt a f谩jlok t枚rl茅sekor a %1$s exportk枚nyvt谩rb贸l</string>
+    <string name="activity_type_hiit">Magas intenzit谩s煤 intervall edz茅s</string>
+    <string name="prefs_password_4_digits_0_to_9_summary">A jelsz贸nak 4 sz谩mjegy疟nek kell lennie, csak sz谩mokat haszn谩lva</string>
+    <string name="activity_web_view">Webes n茅zet tev茅kenys茅g</string>
+    <string name="prefs_shortcut_cards">Gyorsbillenty疟 k谩rty谩k</string>
+    <string name="bip_prefs_shotcuts_summary">V谩lassza ki a gyorsbillenty疟ket a kark枚t艖 k茅perny艖j茅n</string>
+    <string name="bip_prefs_shortcuts">Gyorsbillenty疟k</string>
+    <string name="prefs_control_center_summary">V谩lassza ki az elemeket a vez茅rl艖k枚zpont leg枚rd眉l艖 men眉j茅ben</string>
+    <string name="show_ongoing_activity">Folyamatban l茅v艖 tev茅kenys茅g felugr贸 ablak谩nak megjelen铆t茅se</string>
+    <string name="prefs_charts_tabs_summary">L谩that贸 diagram f眉lek</string>
+    <string name="charts_activity_list">Tev茅kenys茅g lista</string>
+    <string name="prefs_sounds_summary">A k茅sz眉l茅k hangjelz茅s茅nek be谩ll铆t谩sa</string>
+    <string name="fwapp_install_device_not_supported">A f谩jl nem telep铆thet艖, az eszk枚z nem t谩mogatott.</string>
+    <string name="seconds_6">6 m谩sodperc</string>
+    <string name="seconds_8">8 m谩sodperc</string>
+    <string name="seconds_9">9 m谩sodperc</string>
+    <string name="seconds_7">7 m谩sodperc</string>
+    <string name="prefs_find_phone_summary">Haszn谩lja kark枚t艖j茅t a telefon cseng艖hangj谩nak lej谩tsz谩s谩hoz.</string>
+    <string name="mi2_prefs_button_press_count_summary">A gombnyom谩sok sz谩ma az 1. esem茅ny elind铆t谩s谩hoz. Ugyanannyi tov谩bbi megnyom谩ssal l茅trej枚n a 2. esem茅ny 茅s 铆gy tov谩bb.</string>
+    <string name="mi2_prefs_button_press_broadcast">K眉ldend艖 眉zenet k枚zvet铆t茅se</string>
+    <string name="mi2_prefs_button_press_broadcast_summary">Az esem茅nnyel egy眉tt k枚zvet铆tett 眉zenet. A \"button_id\" param茅ter automatikusan hozz谩ad贸dik minden 眉zenethez.</string>
+    <string name="mi2_prefs_button_action_vibrate_summary">S谩vrezg茅s enged茅lyez茅se a gombm疟velet aktiv谩l谩sakor</string>
+    <string name="prefs_password_enabled">Jelsz贸 enged茅lyezve</string>
+    <string name="prefs_password_4_digits_1_to_4_summary">A jelsz贸nak 4 sz谩mjegy疟nek kell lennie, 1-4 sz谩mjegyeket haszn谩lva</string>
+    <string name="mi2_prefs_heart_rate_monitoring_alerts_summary">Konfigur谩lja a pulzussz谩m-figyel茅st 茅s a riaszt谩si k眉sz枚b茅rt茅keket</string>
+    <string name="mi5_prefs_workout_activity_types">Edz茅s tev茅kenys茅gt铆pusok</string>
+    <string name="pref_summary_force_white_color_scheme">Hasznos, ha az 贸r谩j谩nak s枚t茅t mutat贸ja van</string>
+    <string name="lefun_prefs_hydration_reminder_summary">A kark枚t艖 vibr谩l谩ssal eml茅kezteti 脰nt a v铆ziv谩sra</string>
+    <string name="lefun_prefs_antilost_title">Elveszt茅s elleni v茅delem</string>
+    <string name="spanish_es">Spanyol (Spanyolorsz谩g)</string>
+    <string name="english_in">Angol (India)</string>
+    <string name="english_au">Angol (Ausztr谩lia)</string>
+    <string name="english_ca">Angol (Kanada)</string>
+    <string name="english_gb">Angol (Egyes眉lt Kir谩lys谩g)</string>
+    <string name="latvian">Lett</string>
+    <string name="portuguese_pt">Portug谩l (Portug谩lia)</string>
+    <string name="pref_title_charts_swipe">Enged茅lyezze a balra/jobbra cs煤sztat谩st a diagramok tev茅kenys茅g茅ben</string>
+    <string name="pref_title_charts_average">脕tlagok megjelen铆t茅se a diagramokon</string>
+    <string name="pref_title_charts_range">Diagramok tartom谩nya</string>
+    <string name="pref_chart_sleep_lines_limit">H谩ny alv谩si munkamenet sort jelen铆tsen meg a g枚rget茅s el艖tt</string>
+    <string name="pref_title_chart_sleep_rolling_24_hour">Alv谩startom谩ny</string>
+    <string name="activity_type_kitesurfing">Kite sz枚rf枚z茅s</string>
+    <string name="activity_type_freestyle">Szabad st铆lus</string>
+    <string name="common_symbols">K枚z枚s szimb贸lumok</string>
+    <string name="french_ca">Francia (Kanada)</string>
+    <string name="french_fr">Francia (Franciaorsz谩g)</string>
+    <string name="seconds_11">11 m谩sodperc</string>
+    <string name="charts_min_max_heartrate_popup">Legalacsonyabb pulzussz谩m: %1$d
+\nLegmagasabb pulzussz谩m: %2$d
+\nMozg谩s intenzit谩sa: %3$s</string>
+    <string name="mi5_prefs_workout_activity_types_summary">V谩lassza ki az edz茅sek k茅perny艖j茅n megjelen铆tend艖 tev茅kenys茅gt铆pusokat</string>
+    <string name="pref_title_force_white_color_scheme">Fekete-feh茅r茅n sz铆ns茅ma k茅nyszer铆t茅se</string>
+    <string name="activity_prefs_target_weight_kg">C茅ls煤ly kg-ban</string>
+    <string name="spanish_mx">Spanyol (Mexik贸)</string>
+    <string name="spanish_us">Spanyol (Egyes眉lt 脕llamok)</string>
+    <string name="pref_charts_range_off">A diagramok tartom谩nya egy h茅tre van be谩ll铆tva</string>
+    <string name="prefs_heartrate_alert_active_high_threshold">Magas aktivit谩s煤 pulzussz谩m riaszt谩si k眉sz枚b</string>
+    <string name="prefs_spo2_monitoring_description">Automatikusan figyeli a v茅r oxig茅nszintj茅t a nap folyam谩n</string>
+    <string name="prefs_spo2_alert_threshold">SPO2 riaszt谩si k眉sz枚b</string>
+    <string name="prefs_spo2_monitoring_title">V茅roxig茅n szint figyel茅se</string>
+    <string name="prefs_activity_monitoring_description">Automatikusan n枚velje a pulzus茅rz茅kel茅si frekvenci谩t, amikor a kark枚t艖 fizikai gyakorlatot 茅szlel, a pulzusr枚gz铆t茅s pontoss谩g谩nak n枚vel茅se 茅rdek茅ben.</string>
+    <string name="updatefirmwareoperation_updateproblem_low_battery">A k茅sz眉l茅k akkumul谩torszintje t煤l alacsony</string>
+    <string name="gpx_route_upload_failed">Gpx 煤tvonal felt枚lt茅se sikertelen</string>
+    <string name="gpx_route_upload_in_progress">Gpx 煤tvonal felt枚lt茅se</string>
+    <string name="seconds_12">12 m谩sodperc</string>
+    <string name="seconds_13">13 m谩sodperc</string>
+    <string name="seconds_14">14 m谩sodperc</string>
+    <string name="seconds_15">15 m谩sodperc</string>
+    <string name="pref_enable_unsupported_settings_summary">Ez lehet艖v茅 teszi az 枚sszes el茅rhet艖 be谩ll铆t谩shoz val贸 hozz谩f茅r茅st, m茅g akkor is, ha azokat az eszk枚z nem t谩mogatja. Ez instabilit谩st 茅s 枚sszeoml谩st okozhat az eszk枚z枚n.</string>
+    <string name="miband_prefs_reserve_reminder_calendar">Eml茅keztet艖k a k枚zelg艖 esem茅nyekre val贸 foglal谩shoz</string>
+    <string name="pref_sleep_breathing_quality_monitoring">Alv谩si l茅gz茅s min艖s茅g茅nek ellen艖rz茅se</string>
+    <string name="prefs_disconnect_notification_summary">脡rtes铆t茅s a k茅sz眉l茅ken a BT-r贸l val贸 lev谩laszt谩skor.</string>
+    <string name="hydration_dnd_summary">Kapcsolja ki a hidrat谩l谩si figyelmeztet茅seket egy bizonyos id艖intervallumra</string>
+    <string name="prefs_always_on_display">Mindig megjelen铆tve</string>
+    <string name="mi2_prefs_do_not_disturb_lift_wrist">Aktiv谩lja a kijelz艖t emel茅skor a Ne zavarjanak m贸dban</string>
+    <string name="pref_do_not_disturb_not_wear">Ne zavarja, ha nem viseli</string>
+    <string name="mi3_prefs_band_screen_unlock_summary">Cs煤sztassa felfel茅 az ujj谩t a kark枚t艖 k茅perny艖j茅nek felold谩s谩hoz</string>
+    <string name="manual">K茅zik枚nyv</string>
+    <string name="danish">D谩n</string>
+    <string name="pref_charts_range_on">A diagramok tartom谩nya egy h贸napra van 谩ll铆tva</string>
+    <string name="activity_prefs_chart_min_session_length">Minim谩lis tev茅kenys茅g id艖tartama (perc)</string>
+    <string name="charts_legend_spo2_average">V茅roxig茅n 谩tlag</string>
+    <string name="active_time">Akt铆v id艖</string>
+    <string name="standing_time">脕ll谩sid艖</string>
+    <string name="controlcenter_start_activity_tracks">Az 脰n aktivit谩s谩nak nyomon k枚vet茅se</string>
+    <string name="activity_type_not_worn">Nem viselt eszk枚z</string>
+    <string name="activity_type_dynamic_cycle">Dinamikus ciklus</string>
+    <string name="activity_type_stair_stepper">L茅pcs艖s l茅peget艖</string>
+    <string name="activity_type_fitness_exercises">Fitness gyakorlatok</string>
+    <string name="activity_type_crossfit">Crossfit</string>
+    <string name="activity_type_functional_training">Funkcion谩lis edz茅s</string>
+    <string name="activity_type_physical_training">Testedz茅s</string>
+    <string name="activity_type_kendo">Kendo</string>
+    <string name="activity_type_horizontal_bar">V铆zszintes s谩v</string>
+    <string name="activity_type_parallel_bar">P谩rhuzamos s谩v</string>
+    <string name="activity_type_cooldown">Leh疟l茅s</string>
+    <string name="activity_type_cross_training">Cross edz茅s</string>
+    <string name="activity_type_aerobic_exercise">Aerobic edz茅s</string>
+    <string name="activity_type_rolling">Gur铆t谩s</string>
+    <string name="activity_type_flexibility">Rugalmass谩g</string>
+    <string name="activity_type_track_and_field">Atl茅tika</string>
+    <string name="activity_type_push_ups">Fekv艖t谩masz</string>
+    <string name="activity_type_battle_rope">Funkcion谩lis k枚t茅l</string>
+    <string name="activity_type_plank">Plankel茅s</string>
+    <string name="activity_type_dumbbell">S煤lyz贸</string>
+    <string name="activity_type_belly_dance">Hast谩nc</string>
+    <string name="activity_type_jazz_dance">Jazz t谩nc</string>
+    <string name="activity_type_latin_dance">Latin t谩nc</string>
+    <string name="activity_type_ballet">Balett</string>
+    <string name="activity_type_other_dance">Egy茅b t谩nc</string>
+    <string name="activity_type_roller_skating">G枚rkorcsoly谩z谩s</string>
+    <string name="activity_type_martial_arts">K眉zd艖sportok</string>
+    <string name="activity_type_tai_chi">Tai chi</string>
+    <string name="activity_type_hula_hooping">Hula karik谩z谩s</string>
+    <string name="activity_type_disc_sports">Korong sportok</string>
+    <string name="activity_type_darts">Darts</string>
+    <string name="activity_type_archery">脥j谩szat</string>
+    <string name="activity_type_horse_riding">Lovagl谩s</string>
+    <string name="activity_type_kite_flying">S谩rk谩nyrep眉l茅s</string>
+    <string name="activity_type_swing">Hint谩z谩s</string>
+    <string name="activity_type_stairs">L茅pcs艖z茅s</string>
+    <string name="activity_type_fishing">Horg谩szat</string>
+    <string name="activity_type_mind_and_body">Test 茅s l茅lek</string>
+    <string name="activity_type_kabaddi">Kabaddi</string>
+    <string name="activity_type_karting">Gokartoz谩s</string>
+    <string name="activity_type_billiards">Bili谩rd</string>
+    <string name="activity_type_shuttlecock">Shuttlecock</string>
+    <string name="activity_type_softball">Softball</string>
+    <string name="activity_type_dodgeball">Kidob贸s</string>
+    <string name="activity_type_australian_football">Ausztr谩l futball</string>
+    <string name="activity_type_pickleball">Pickleball</string>
+    <string name="activity_type_lacross">Lacrosse</string>
+    <string name="activity_type_shot">L枚v茅szet</string>
+    <string name="activity_type_sailing">Vitorl谩z谩s</string>
+    <string name="activity_type_jet_skiing">Jetskiz茅s</string>
+    <string name="activity_type_snow_sports">Havas sportok</string>
+    <string name="activity_type_rock_climbing">Sziklam谩sz谩s</string>
+    <string name="activity_type_hunting">Vad谩szat</string>
+    <string name="activity_type_windsurfing">Sz枚rf枚z茅s</string>
+    <string name="activity_type_elliptical_trainer">Elliptikus tr茅ner</string>
+    <string name="activity_type_elliptical">Elliptikus</string>
+    <string name="activity_type_core_training">Core edz茅s</string>
+    <string name="activity_type_stepper">Tapos谩s</string>
+    <string name="prefs_activity_monitoring_title">Tev茅kenys茅gfigyel茅s</string>
+    <string name="num_min">%d perc</string>
+    <string name="seconds_25">25 m谩sodperc</string>
+    <string name="lack_of_step">Hi谩nyz贸 l茅p茅sek: %1$d</string>
+    <string name="pref_huami_truncate_fetch_operation_timestamps_summary">A lek茅rdez茅si m疟veletek id艖b茅lyegeinek percekre t枚rt茅n艖 cs枚kkent茅se. Ha a nagyon r枚vid edz茅sek lek茅rdez茅se sor谩n probl茅m谩k mer眉lnek fel, tiltsa le ezt a be谩ll铆t谩st, hogy az id艖b茅lyegek m谩sodpercben maradjanak.</string>
+    <string name="mi2_prefs_button_press_count_max_delay_summary">Maxim谩lis k茅sleltet茅s a gombnyom谩sok k枚z枚tt ezredm谩sodpercben</string>
+    <string name="activity_db_management_autoexport_explanation">Az adatb谩zis automatikus export谩l谩si helye a k枚vetkez艖re lett be谩ll铆tva:</string>
+    <string name="activity_db_management_autoexport_scheduled_yes">Az automatikus export谩l谩s (eredetileg) %1$s-ra lett 眉temezve</string>
+    <string name="activity_type_rowing_machine">Evez艖 g茅p</string>
+    <string name="activity_type_rowing">Evez茅s</string>
+    <string name="activity_type_cricket">Krikett</string>
+    <string name="activity_type_baseball">Baseball</string>
+    <string name="devicetype_femometer_vinca2">Femometer Vinca II</string>
+    <string name="menuitem_offline_voice">Kapcsolat n茅lk眉li hang</string>
+    <string name="devicetype_redmiwatch3">Redmi Watch 3</string>
+    <string name="menuitem_cycles">Ciklusk枚vet茅s</string>
+    <string name="menuitem_widgets">Modulok</string>
+    <string name="menuitem_buzzer_intensity">Hangjelz茅s intenzit谩sa</string>
+    <string name="activity_type_hockey">Hoki</string>
+    <string name="devicetype_lefun">Lefun</string>
+    <string name="devicetype_redmi_smart_band_pro">Redmi Smart Band Pro</string>
+    <string name="menuitem_wechat_pay">WeChat Pay</string>
+    <string name="menuitem_mi_ai">MI AI</string>
+    <string name="menuitem_theater_mode">Mozi m贸d</string>
+    <string name="menuitem_unsupported">[NEM T脕MOGATOTT] %s</string>
+    <string name="menuitem_headphone">Fejhallgat贸</string>
+    <string name="menuitem_unknown_app">Ismeretlen (%s)</string>
+    <string name="zepp_os_watchface_superposition">fedv茅ny</string>
+    <string name="pref_summary_contextual_arabic">Enged茅lyezze ezt a kontextu谩lis arab nyelv t谩mogat谩s谩hoz</string>
+    <string name="preferences_rtl_settings">Jobbr贸l balra t谩mogat谩s</string>
+    <string name="activity_type_exercise">Gyakorl谩s</string>
+    <string name="activity_type_riding">Lovagl谩s</string>
+    <string name="devicetype_miband8">Xiaomi Smart Band 8</string>
+    <string name="devicetype_amazfit_gts2_mini">Amazfit GTS 2 Mini</string>
+    <string name="notification_gps_title">Gadgetbridge GPS</string>
+    <string name="devicetype_amazfit_vergel">Amazfit Verge Lite</string>
+    <string name="activity_type_trail_run">T谩jfut谩s</string>
+    <string name="activity_type_wrestling">Birk贸z谩s</string>
+    <string name="activity_summary_detail_clear_gpx_track">GPX nyomvonal t枚rl茅se</string>
+    <string name="activity_error_no_app_for_gpx">A tev茅kenys茅g nyomon k枚vet茅s茅hez telep铆tsen olyan alkalmaz谩st, amely k茅pes kezelni a GPX f谩jlokat.</string>
+    <string name="activity_error_no_app_for_png">A k茅perny艖k茅p megoszt谩s谩hoz telep铆tsen egy alkalmaz谩st, amely k茅pes kezelni a k茅pf谩jlokat.</string>
+    <string name="screenshot_taken">Pillanatk茅p k茅sz眉lt</string>
+    <string name="devicetype_amazfit_gtr2e">Amazfit GTR 2e</string>
+    <string name="devicetype_amazfit_gts2">Amazfit GTS 2</string>
+    <string name="devicetype_amazfit_gts2e">Amazfit GTS 2e</string>
+    <string name="devicetype_redmi_watch_2">Redmi Watch 2</string>
+    <string name="menuitem_vo2_max">VO鈧� Max</string>
+    <string name="menuitem_takephoto">Kamera t谩vvez茅rl艖</string>
+    <string name="menuitem_personal_activity_intelligence">Szem茅lyes tev茅kenys茅g intelligencia</string>
+    <string name="menuitem_membership_cards">Tags谩gi k谩rty谩k</string>
+    <string name="menuitem_screen_always_lit">K茅perny艖 mindig vil谩g铆tson</string>
+    <string name="menuitem_eject_water">V铆z ki眉r铆t茅se</string>
+    <string name="title_activity_watch9_pairing">Watch 9 p谩ros铆t谩s</string>
+    <string name="pref_title_contextual_arabic">Kontextu谩lis arab nyelv</string>
+    <string name="menuitem_zepp_coach">Zepp edz艖</string>
+    <string name="menuitem_spo2">SpO2</string>
+    <string name="activity_type_street_dance">Utcai t谩nc</string>
+    <string name="kind_gps_almanac">GPS Almanac</string>
+    <string name="devicetype_amazfit_bips_lite">Amazfit Bip S Lite</string>
+    <string name="devicetype_binary_sensor">Binary sensor</string>
+    <string name="menuitem_aqi">Air Quality Index</string>
+    <string name="menuitem_apps_shortcuts">Alkalmaz谩sok parancsikonjai</string>
+    <string name="menuitem_dnd">DND</string>
+    <string name="zepp_os_watchface_rush">Rohan谩s</string>
+    <string name="devicetype_amazfit_bip_lite">Amazfit Bip Lite</string>
+    <string name="devicetype_amazfit_pop_pro">Amazfit Pop Pro</string>
+    <string name="devicetype_amazfit_gtr2">Amazfit GTR 2</string>
+    <string name="devicetype_huawei_watchgt3">Huawei Watch GT 3 (Pro)</string>
+    <string name="activity_type_icehockey">J茅ghoki</string>
+    <string name="notification_channel_gps">GPS nyomk枚vet茅s</string>
+    <string name="devicetype_makibes_hr3">Makibes HR3</string>
+    <string name="devicetype_honor_band3">Honor Band 3</string>
+    <string name="devicetype_honor_band4">Honor Band 4</string>
+    <string name="devicetype_honor_band6">Honor Band 5</string>
+    <string name="devicetype_honor_band7">Honor Band 7</string>
+    <string name="devicetype_honor_band5">Honor Band 5</string>
+    <string name="devicetype_huawei_band4pro">Huawei Band 4 (Pro)</string>
+    <string name="devicetype_amazfit_bipupro">Amazfit Bip U Pro</string>
+    <string name="devicetype_amazfit_active">Amazfit Active</string>
+    <string name="devicetype_amazfit_active_edge">Amazfit Active Edge</string>
+    <string name="devicetype_amazfit_cheetah_round">Amazfit Cheetah (Round)</string>
+    <string name="devicetype_amazfit_falcon">Amazfit Falcon</string>
+    <string name="devicetype_amazfit_bipu">Amazfit Bip U</string>
+    <string name="devicetype_amazfit_gtr_lite">Amazfit GTR Lite</string>
+    <string name="devicetype_colacao21">ColaCao 2021</string>
+    <string name="devicetype_redmiwatch3active">Redmi Watch 3 Active</string>
+    <string name="devicetype_redmi_smart_band_2">Redmi Smart Band 2</string>
+    <string name="menuitem_voice_memos">Hangjegyzetek</string>
+    <string name="menuitem_sun_moon">Nap 茅s Hold</string>
+    <string name="menuitem_female_health">N艖i eg茅szs茅g</string>
+    <string name="menuitem_one_tap_measuring">Egy茅rint茅ses m茅r茅s</string>
+    <string name="zepp_os_watchface_the_ultima">Az utols贸</string>
+    <string name="devicetype_sony_wi_sp600n">Sony WI-SP600N</string>
+    <string name="devicetype_huawei_band_aw70">Huawei Band (AW70)</string>
+    <string name="devicetype_huawei_band6">Huawei Band 6</string>
+    <string name="devicetype_huawei_band7">Huawei Band 7</string>
+    <string name="devicetype_huawei_band8">Huawei Band 8</string>
+    <string name="devicetype_huawei_watch_gt">Huawei Watch GT</string>
+    <string name="devicetype_huawei_watchgt2">Huawei Watch GT 2 (Pro)</string>
+    <string name="devicetype_huawei_watchgt2e">Huawei Watch GT 2e</string>
+    <string name="devicetype_huawei_talk_band_b6">Huawei Talk Band B6</string>
+    <string name="devicetype_bohemic_smart_bracelet">Bohemic Smart Bracelet</string>
+    <string name="menuitem_lockscreen">Z谩rol谩si k茅perny艖</string>
+    <string name="activity_error_share_failed">A f谩jl megoszt谩sa sikertelen.</string>
+    <string name="devicetype_vivomove_hr">Garmin Vivomove HR</string>
+    <string name="activity_type_pilates">Pilates</string>
+    <string name="activity_type_table_tennis">Asztali tenisz</string>
+    <string name="activity_type_bowling">Bowling</string>
+    <string name="activity_type_snowboarding">Snowboardoz谩s</string>
+    <string name="activity_type_trekking">Trekking</string>
+    <string name="activity_summary_detail_select_gpx_track">GPX nyomvonal kiv谩laszt谩sa</string>
+    <string name="kind_agps_bundle">AGPS Bundle</string>
+    <string name="kind_gpx_route">GPX 煤tvonal</string>
+    <string name="add_test_device">Tesztk茅sz眉l茅k hozz谩ad谩sa</string>
+    <string name="devicetype_miband7pro">Xiaomi Smart Band 7 Pro</string>
+    <string name="devicetype_amazfit_balance">Amazfit Balance</string>
+    <string name="devicetype_amazfit_cheetah_square">Amazfit Cheetah (Square)</string>
+    <string name="devicetype_amazfit_cheetah_pro">Amazfit Cheetah Pro</string>
+    <string name="devicetype_amazfit_gts3">Amazfit GTS 3</string>
+    <string name="devicetype_amazfit_gts4">Amazfit GTS 4</string>
+    <string name="devicetype_amazfit_gts4_mini">Amazfit GTS 4 Mini</string>
+    <string name="devicetype_amazfit_gtr3">Amazfit GTR 3</string>
+    <string name="devicetype_amazfit_gtr_mini">Amazfit GTR Mini</string>
+    <string name="devicetype_no1_f1">No.1 F1</string>
+    <string name="devicetype_honor_magicwatch2">Honor MagicWatch 2</string>
+    <string name="devicetype_miband8pro">Xiaomi Smart Band 8 Pro</string>
+    <string name="devicetype_amazfit_gtr3_pro">Amazfit GTR 3 Pro</string>
+    <string name="devicetype_amazfit_bip3_pro">Amazfit Bip 3 Pro</string>
+    <string name="devicetype_amazfit_bip5">Amazfit Bip 5</string>
+    <string name="devicetype_amazfit_gtr4">Amazfit GTR 4</string>
+    <string name="devicetype_amazfit_trex_2">Amazfit T-Rex 2</string>
+    <string name="devicetype_amazfit_trex_ultra">Amazfit T-Rex Ultra</string>
+    <string name="devicetype_casiogwb5600">Casio GW-B5600</string>
+    <string name="devicetype_casiogmwb5000">Casio GMW-B5000</string>
+    <string name="devicetype_miscale2">Mi Scale 2</string>
+    <string name="devicetype_mijia_lywsd02">Mijia Smart Clock</string>
+    <string name="devicetype_mijia_lywsd03">Mijia Temperature and Humidity Sensor 2</string>
+    <string name="devicetype_mijia_mho_c303">Mijia MHO-C303</string>
+    <string name="devicetype_colacao23">ColaCao 2023</string>
+    <string name="activity_summary_detail_editing_gpx_track">Kapcsol贸d贸 GPX nyomvonal szerkeszt茅se</string>
+    <string name="share_screenshot">K茅perny艖k茅p megoszt谩sa</string>
+    <string name="menuitem_workout_shortcuts">Edz茅s parancsikonok</string>
+    <string name="menuitem_body_composition">Test fel茅p铆t茅s</string>
+    <string name="devicetype_redmi_watch_2_lite">Redmi Watch 2 Lite</string>
+    <string name="notification_channel_low_battery_name">Alacsony akkumul谩tor t枚lt枚tts茅g</string>
+    <string name="preferences_qhybrid_settings">Q Hybrid be谩ll铆t谩sok</string>
+    <string name="devicetype_huawei_watchfit">Huawei Watch Fit</string>
+    <string name="devicetype_xiaomi_watch_lite">Xiaomi Watch Lite</string>
+    <string name="menuitem_zepp_pay">Zepp fizet茅s</string>
+    <string name="menuitem_thermometer">H艖m茅r艖</string>
+    <string name="menuitem_readiness">K茅szenl茅t</string>
+    <string name="zepp_os_watchface_guider">脷tikalauz</string>
+    <string name="qhybrid_prompt_million_steps">脕ll铆tsa a l茅p茅ssz谩mot egy milli贸ra az aktiv谩l谩shoz.</string>
+    <string name="hr_appname_wellness">Wellness</string>
+    <string name="prefs_fm_preset_instructions">Hosszan nyomja meg a gombot az el艖be谩ll铆t谩s t谩rol谩s谩hoz</string>
+    <string name="toast_app_must_not_be_selected">Az alkalmaz谩s nem v谩laszthat贸 ki a konfigur谩l谩shoz</string>
+    <string name="toast_app_must_be_selected">A konfigur谩l谩shoz ki kell v谩lasztani az alkalmaz谩st</string>
+    <string name="edittext_notification_filter_words_hint">Adja meg a k铆v谩nt szavakat, 煤j sor minden egyes sz贸hoz</string>
+    <string name="toast_notification_filter_saved_successfully">脡rtes铆t茅si sz疟r艖 mentve</string>
+    <string name="toast_notification_filter_words_empty_hint">Adjon meg legal谩bb egy sz贸t</string>
+    <string name="pref_title_upper_button_function_short">Fels艖 gomb r枚viden</string>
+    <string name="pref_title_lower_button_function_double">Als贸 gomb dupl谩n</string>
+    <string name="prefs_button_variable_actions">R茅szletes gombnyom谩s be谩ll铆t谩sok</string>
+    <string name="error_no_location_access">Ahhoz, hogy a szkennel茅s megfelel艖en m疟k枚dj枚n, meg kell adni 茅s enged茅lyezni kell a helysz铆nhozz谩f茅r茅st</string>
+    <string name="error_no_bluetooth_connect">A szkennel茅s megfelel艖 m疟k枚d茅s茅hez enged茅lyezni kell a Bluetooth-kapcsolathoz val贸 hozz谩f茅r茅st</string>
+    <string name="pref_qhybrid_title_widget_draw_circles">Modulk枚r枚k rajzol谩sa</string>
+    <string name="pref_qhybrid_save_raw_activity_files">Nyers tev茅kenys茅gf谩jlok ment茅se</string>
+    <string name="permission_granting_mandatory">Mindezek az enged茅lyek sz眉ks茅gesek, 茅s ha nem adj谩k meg 艖ket, instabilit谩s l茅phet fel</string>
+    <string name="permission_notification_policy_access">A(z) %1$s-nek hozz谩f茅r茅sre van sz眉ks茅ge a Ne zavarjon be谩ll铆t谩sokhoz, hogy azokat az 贸r谩j谩n tiszteletben tarthassa, amikor a telefon k茅perny艖je ki van kapcsolva.
+\n
+\nKoppintson a(z) \"%2$s\", majd a(z) \"%1$s\" gombra, 茅s enged茅lyezze a \"Ne zavarjon\" enged茅lyez茅s茅t, majd koppintson a \"Vissza\" gombra a(z) %1$s oldalra val贸 visszat茅r茅shez.</string>
+    <string name="note">Jegyzet</string>
+    <string name="pref_invalid_frequency_message">Adjon meg egy 87,5 茅s 108,0 k枚z枚tti frekvenci谩t</string>
+    <string name="pref_title_lower_button_short_press_action">Als贸 gomb r枚vid megnyom谩s煤 m疟velet</string>
+    <string name="share_log_not_enabled_message">El艖sz枚r enged茅lyeznie kell a napl贸z谩st a Be谩ll铆t谩sok - Napl贸f谩jlok 铆r谩sa men眉pontban</string>
+    <string name="quick_alarm_description">Riaszt谩s a modulr贸l</string>
+    <string name="qhybrid_goal_in_steps">C茅l l茅p茅senk茅nt</string>
+    <string name="prefs_button_single_press_action_selection_title">1. esem茅ny m疟velet</string>
+    <string name="prefs_button_long_press_action_selection_title">Hossz煤 gombnyom谩s m疟velet</string>
+    <string name="prefs_fm_presets_presets">El艖be谩ll铆t谩sok</string>
+    <string name="filter_mode">Sz疟r艖 m贸d</string>
+    <string name="gpx_receiver_files_received">Be茅rkezett GPX f谩jl(ok):</string>
+    <string name="pref_title_lower_button_function_short">Als贸 gomb r枚viden</string>
+    <string name="pref_title_upper_button_function_long">Fels艖 gomb hosszan</string>
+    <string name="pref_title_middle_button_function_long">K枚z茅ps艖 gomb hosszan</string>
+    <string name="filter_submode_at_least_one">Legal谩bb egy sz贸t</string>
+    <string name="filter_submode_all">Minden sz贸t</string>
+    <string name="qhybrid_second_timezone_offset_relative_to_utc">m谩sodik id艖z贸na eltol贸d谩s a UTC-hez k茅pest</string>
+    <string name="qhybrid_use_activity_hand_as_notification_counter">haszn谩lja a tev茅kenys茅gmutat贸t 茅rtes铆t茅si sz谩ml谩l贸k茅nt</string>
+    <string name="pref_title_middle_button_function_short">K枚z茅ps艖 gomb r枚viden</string>
+    <string name="prefs_button_double_press_action_selection_title">2. esem茅ny m疟velet</string>
+    <string name="prefs_button_triple_press_action_selection_title">3. esem茅ny m疟velet</string>
+    <string name="gpx_receiver_activity_title">GPX-vev艖 Gadgetbridge</string>
+    <string name="gpx_receiver_overwrite_some_files">N茅h谩ny f谩jl m谩r l茅tezik. Fel眉l铆rja?</string>
+    <string name="error_version_check_extreme_caution">FIGYELEM: Hiba a verzi贸inform谩ci贸 ellen艖rz茅s茅n茅l! Nem szabad folytatni! \"%s\" verzi贸n茅v l谩ttam</string>
+    <string name="share_log_warning">Ne feledje, hogy a Gadgetbridge olyan f谩jlokat napl贸z, amelyek sok szem茅lyes adatot tartalmazhatnak, t枚bbek k枚z枚tt eg茅szs茅g眉gyi adatokat, egyedi azonos铆t贸kat (p茅ld谩ul az eszk枚z MAC-c铆m茅t), zenei preferenci谩kat stb. Fontolja meg a f谩jl szerkeszt茅s茅t 茅s ezen inform谩ci贸k elt谩vol铆t谩s谩t, miel艖tt elk眉ldi a f谩jlt egy nyilv谩nos hibajelent茅sre.</string>
+    <string name="pref_title_upper_button_long_press_action">Fels艖 gomb hossz煤 megnyom谩s煤 m疟velet</string>
+    <string name="pref_title_lower_button_function_long">Als贸 gomb hosszan</string>
+    <string name="pref_title_middle_button_function_double">K枚z茅ps艖 gomb dupl谩n</string>
+    <string name="error_no_bluetooth_scan">A szkennel茅s megfelel艖 m疟k枚d茅s茅hez biztos铆tani 茅s enged茅lyezni kell a Bluetooth szkennel茅si hozz谩f茅r茅st</string>
+    <string name="permission_request">A(z) %1$s lehet艖v茅 teszi, hogy 眉zeneteket 茅s egy茅b adatokat k眉ldj枚n Androidr贸l az eszk枚z茅re. Ehhez enged茅lyre van sz眉ks茅ge az adatokhoz val贸 hozz谩f茅r茅shez, 茅s e n茅lk眉l el艖fordulhat, hogy nem m疟k枚dik megfelel艖en.
+\n
+\nMost megjelenik n茅h谩ny Android p谩rbesz茅dpanel, amelyek ezeket az enged茅lyeket k茅rik.
+\n
+\nA folytat谩shoz 茅rintse meg a(z) 鈥�%2$s鈥� elemet.</string>
+    <string name="permission_notification_listener">A(z) %1$s-nek hozz谩f茅r茅sre van sz眉ks茅ge az 茅rtes铆t茅sekhez, hogy megjelen铆thesse azokat az 贸r谩j谩n, amikor a telefon k茅perny艖je ki van kapcsolva.
+\n
+\nKoppintson a(z) \'%2$s\', majd a(z) \'%1$s\' elemre, 茅s enged茅lyezze az \'脡rtes铆t茅si hozz谩f茅r茅s enged茅lyez茅se\' opci贸t, majd koppintson a \'Vissza\' elemre a(z) %1$s oldalra val贸 visszat茅r茅shez.</string>
+    <string name="permission_location">A(z) %1$s-nek hozz谩f茅r茅sre van sz眉ks茅ge az tart贸zkod谩si helysz铆n茅hez a h谩tt茅rben, hogy akkor is kapcsolatban maradhasson az 贸r谩j谩val, amikor a k茅perny艖je ki van kapcsolva. \n \nKoppintson a \'%2$s\' gombra a beleegyez茅shez.</string>
+    <string name="permission_display_over_other_apps">A(z) %1$s-nek enged茅lyre van sz眉ks茅ge a m谩s alkalmaz谩sok feletti megjelen铆t茅shez, hogy a Bangle.js 贸r谩k tev茅kenys茅geket ind铆thassanak intenten kereszt眉l, amikor a(z) %1$s a h谩tt茅rben van.
+\n
+\nEz haszn谩lhat贸 egy zenei alkalmaz谩s elind铆t谩s谩ra 茅s egy dal lej谩tsz谩s谩ra, 茅s sok m谩s dologra.
+\n
+\nKoppintson a(z) \'%2$s\', majd a(z) \'%1$s\' elemre, 茅s enged茅lyezze a \'Megjelen铆t茅s enged茅lyez茅se m谩s alkalmaz谩sok felett\' opci贸t, majd koppintson a \'Vissza\' elemre a(z) %1$s elemhez val贸 visszat茅r茅shez.
+\n
+\nAz enged茅lyek k茅r茅s茅nek le谩ll铆t谩s谩hoz a %1$s menjen a \'Be谩ll铆t谩sok\' men眉pontba, 茅s t枚r枚lje a \'Jogosults谩gi 谩llapot ellen艖rz茅se\' jel枚l艖n茅gyzetet.
+\n
+\nGy艖z艖dj枚n meg r贸la, hogy a %1$s megkapja az elv谩rt m疟k枚d茅shez sz眉ks茅ges enged茅lyeket.</string>
+    <string name="text_receiver_activity_title">Sz枚veg k眉ld茅se az eszk枚zre</string>
+    <string name="pref_title_upper_button_function_double">Fels艖 gomb dupl谩n</string>
+    <string name="companiondevice_pairing">T谩rseszk枚z p谩ros铆t谩s</string>
+    <string name="qhybrid_time_shift">id艖eltol贸d谩s</string>
+    <string name="baseAltitude">B谩zismagass谩g</string>
+    <string name="error_retrieving_devices_database">Hiba az eszk枚z枚k adatb谩zisb贸l val贸 lek茅r茅sekor</string>
+    <string name="error_background_service">Nem siker眉lt elind铆tani a h谩tt茅rszolg谩ltat谩st</string>
+    <string name="error_background_service_reason_truncated">A h谩tt茅rszolg谩ltat谩s ind铆t谩sa sikertelen, mert鈥�</string>
+    <string name="aerobicTrainingEffect">Aerobic hat谩s</string>
+    <string name="currentWorkoutLoad">Edz茅sterhel茅s</string>
+    <string name="maximumOxygenUptake">Maxim谩lis oxig茅nfelv茅tel</string>
+    <string name="minStride">Min. l茅p茅ssz谩m</string>
+    <string name="avgStrokeRate">脕tlagos l枚ketsz谩m ar谩ny</string>
+    <string name="maxStrokeRate">Max. l枚ketsz谩m</string>
+    <string name="swim_style">煤sz谩s st铆lus</string>
+    <string name="mi_h">m茅rf枚ld/贸ra</string>
+    <string name="minutes_mi">perc/mi</string>
+    <string name="error_background_service_reason">A h谩tt茅rszolg谩ltat谩s ind铆t谩sa egy kiv茅tel miatt sikertelen volt. Hiba:</string>
+    <string name="error_setting_alias">Hiba az alias be谩ll铆t谩sakor:</string>
+    <string name="discover_unsupported_devices">Nem t谩mogatott eszk枚z枚k felfedez茅se</string>
+    <string name="swolf_index">swolf index</string>
+    <string name="spm">l茅p茅s/perc</string>
+    <string name="mi">m茅rf枚ld</string>
+    <string name="ft">l谩b</string>
+    <string name="strokes_unit">str</string>
+    <string name="minPace">Leglassabb temp贸</string>
+    <string name="averageStrokesPerSecond">脕tlagos l枚ket</string>
+    <string name="strokes_minute">l枚ket/perc</string>
+    <string name="hrZoneFatBurn">Zs铆r茅get茅s</string>
+    <string name="strokes_second">l枚ket/s</string>
+    <string name="companiondevice_pairing_details">Enged茅lyezi az 煤j CompanionDevice API t谩mogat谩s谩t (csak Android 8 vagy 煤jabb rendszerekre van hat谩ssal), amely n枚veli a megb铆zhat贸s谩got, ha a szolg谩ltat谩st 煤jra kell ind铆tani a h谩tt茅rben. A hat谩s el茅r茅s茅hez 煤jra kell p谩ros铆tani a Gadgetbridge haszn谩lat谩val</string>
+    <string name="laneLength">S谩vhossz</string>
+    <string name="pref_check_permission_status_summary">Ellen艖rizze 茅s k茅rje a hi谩nyz贸 enged茅lyeket akkor is, ha esetleg nincs r谩juk azonnal sz眉ks茅g. Csak akkor kapcsolja ki, ha eszk枚zei val贸j谩ban nem t谩mogatj谩k ezen funkci贸k egyik茅t sem. Az enged茅ly hi谩nya probl茅m谩kat okozhat!</string>
+    <string name="averageKMPaceSeconds">Temp贸</string>
+    <string name="hrZoneNa">N/A</string>
+    <string name="device_requires_key">KULCS SZ脺KS脡GES, A BEL脡P脡SHEZ HOSSZAN NYOMJA MEG</string>
+    <string name="error_location_enabled_mandatory">Az eszk枚z枚k keres茅s茅hez be kell kapcsolni a helysz铆n-meghat谩roz谩st</string>
+    <string name="error_exporting_device_preferences">Hiba az eszk枚zspecifikus be谩ll铆t谩sok export谩l谩s谩ban</string>
+    <string name="flatDistance">S铆k t谩vols谩g</string>
+    <string name="elevationGain">Magass谩gn枚veked茅s</string>
+    <string name="maxPace">Leggyorsabb temp贸</string>
+    <string name="totalStride">Teljes l茅p茅ssz谩m</string>
+    <string name="TrainingEffect">Edz茅s hat谩sa</string>
+    <string name="anaerobicTrainingEffect">Anaerob hat谩s</string>
+    <string name="averageStride">脕tlagos l茅p茅ssz谩m</string>
+    <string name="averageCadence">脕tlagos 眉tem</string>
+    <string name="maxCadence">Max. 眉tem</string>
+    <string name="strokes">Teljes l枚ketsz谩m</string>
+    <string name="averageLapPace">脕tlagos k枚rtemp贸</string>
+    <string name="yard">yard</string>
+    <string name="discover_unsupported_devices_description">Ha enged茅lyezi ezt az opci贸t, az 枚sszes felfedezett Bluetooth-eszk枚z megjelenik a keres茅s sor谩n. A r枚vid 茅rint茅ssel a v谩g贸lapra m谩solja az eszk枚z nev茅t 茅s MAC-c铆m茅t. Hosszan megnyomva elind铆tja a \"Teszteszk枚z hozz谩ad谩sa\" p谩rbesz茅dpanelt. Lehets茅ges, hogy az alkalmaz谩s lefagy谩s谩t okozhatja.</string>
+    <string name="ascentDistance">Emelked艖 t谩vols谩g</string>
+    <string name="descentDistance">Lejt艖 t谩vols谩g</string>
+    <string name="minCadence">Min. 眉tem</string>
+    <string name="elevationLoss">Magass谩gveszt茅s</string>
+    <string name="averageStrokeDistance">脕tlagos l枚kett谩vols谩g</string>
+    <string name="alarm_smart_wakeup_interval">Intelligens 茅breszt茅si intervallum:</string>
+    <string name="alarm_smart_wakeup_interval_default">5 perc</string>
+    <string name="maxStride">Max. l茅p茅ssz谩m</string>
+    <string name="swolfIndex">SWOLF</string>
+    <string name="swimStyle">脷sz谩si st铆lus</string>
+    <string name="sony_speak_to_chat_timeout">Id艖t煤ll茅p茅s</string>
+    <string name="sony_speak_to_chat_sensitivity">Besz茅d茅rz茅kel茅s 茅rz茅kenys茅ge</string>
+    <string name="dev_tools">Fejleszt艖eszk枚z枚k</string>
+    <string name="activity_filter_from_placeholder">t谩voli m煤lt</string>
+    <string name="watchface_setting_title_display_refresh_timeout">Friss铆t茅s id艖t煤ll茅p茅s megjelen铆t茅se</string>
+    <string name="nothing_prefs_inear_summary">A zenelej谩tsz谩s ind铆t谩sa/sz眉neteltet茅se att贸l f眉gg艖en, hogy viseli-e a f眉lhallgat贸t</string>
+    <string name="weekstepschart_steps_a_week_or_month">Heti/havi l茅p茅ssz谩m</string>
+    <string name="weeksleepchart_sleep_a_week_or_month">Heti/havi alv谩sid艖</string>
+    <string name="sony_speak_to_chat_focus_on_voice">Besz茅dhang kiemel茅se</string>
+    <string name="prefs_game_mode_summary">Csak ha a telefon t谩mogatja a j谩t茅k m贸dot</string>
+    <string name="fossil_hr_synced_activity_data">Szinkroniz谩lt tev茅kenys茅gadatok</string>
+    <string name="devicetype_amazfit_bip3">Amazfit Bip 3</string>
+    <string name="watchface_widget_type_2nd_tz">2. id艖z贸na</string>
+    <string name="watchface_widget_type_uv_index">UV sug谩rz谩s</string>
+    <string name="devicetype_galaxybuds_pro">Galaxy Buds Pro</string>
+    <string name="enable_notifications_summary">H铆v谩s, 眉zenet 茅s egy茅b 茅rtes铆t茅sek</string>
+    <string name="devicetype_sony_linkbuds">Sony LinkBuds</string>
+    <string name="watchface_dialog_title_settings">脫rafel眉let be谩ll铆t谩sok</string>
+    <string name="pref_switch_controls_anc_off">Zajsz疟r茅s 鈫愨啋 Ki</string>
+    <string name="devicetype_miband8active">Xiaomi Smart Band 8 Active</string>
+    <string name="activity_filter_reset_filter">Sz疟r艖k t枚rl茅se</string>
+    <string name="watchface_dialog_title_set_name">脫rafel眉let elnevez茅se</string>
+    <string name="watchface_upload_failed">Az 贸rafel眉letet nem siker眉lt felt枚lteni. Pr贸b谩ld 煤jra.</string>
+    <string name="devicetype_galaxybuds_2">Galaxy Buds2</string>
+    <string name="prefs_active_noise_cancelling_light">Alacsony akt铆v zajsz疟r茅s</string>
+    <string name="left_earbud">Bal oldali f眉lhallgat贸</string>
+    <string name="pref_title_touch_anc">Akt铆v zajsz疟r茅s</string>
+    <string name="pref_title_touch_volume">Hanger艖</string>
+    <string name="sony_automatic_power_off_off">Ne kapcsolja ki</string>
+    <string name="medley">Vegyes煤sz谩s</string>
+    <string name="pref_summary_canned_messages_set">A k枚vetkez艖 be谩ll铆tott 眉zenetek k眉ld茅se az eszk枚zre</string>
+    <string name="pref_summary_canned_messages_dismisscall">H铆v谩sok visszautas铆t谩sa az 贸r谩r贸l SMS 眉zenettel</string>
+    <string name="pref_switch_controls_anc_ambient_off">Zajsz疟r茅s 鈫愨啋 K枚rnyezet 鈫愨啋 Ki</string>
+    <string name="backstroke">H谩t煤sz谩s</string>
+    <string name="sony_speak_to_chat_timeout_standard">脕ltal谩nos (30 mp)</string>
+    <string name="addto_filter">Sz疟r艖 hozz谩ad谩sa</string>
+    <string name="prefs_autoremove_message">SMS 茅rtes铆t茅sek automatikus t枚rl茅se</string>
+    <string name="breaststroke">Mell煤sz谩s</string>
+    <string name="qhybrid_pref_summary_external_intents">Enged茅lyezi m谩s Android alkalmaz谩sok sz谩m谩ra, hogy f谩jlokat t枚ltsenek fel/铆rjanak fel眉l</string>
+    <string name="button_watchface_preview">Vill谩mn茅zet az 贸r谩n</string>
+    <string name="watchface_dialog_widget_cat_position">Helyzet</string>
+    <string name="watchface_setting_title_custom_events">Egy茅ni esem茅nyek</string>
+    <string name="prefs_autoheartrate_summary">Id艖k枚z枚nk茅nti pulzusm茅r茅s napk枚zben 茅s alv谩s k枚zben is</string>
+    <string name="prefs_in_ear_detection_summary">H铆v谩sok tov谩bb铆t谩sa a f眉lhallgat贸ra, amennyiben azok a f眉lben vannak</string>
+    <string name="prefs_equalizer">Hangsz铆nszab谩lyz贸</string>
+    <string name="prefs_equalizer_summary">Hangsz铆nszab谩lyz贸 bekapcsol谩sa vagy kikapcsol谩sa</string>
+    <string name="audio_codec">Audi贸 kodek</string>
+    <string name="pref_title_touch_voice_assistant">Hangasszisztens</string>
+    <string name="pref_header_statistics">Sporttev茅kenys茅gek statisztik谩i</string>
+    <string name="qhybrid_summary_file_management">F谩jlok fel- 茅s let枚lt茅se</string>
+    <string name="button_watchface_add_widget">Modul hozz谩ad谩sa</string>
+    <string name="watchface_setting_display_refresh_full">Teljes friss铆t茅s (perc)</string>
+    <string name="watchface_setting_display_refresh_partial">R茅szleges friss铆t茅s (perc)</string>
+    <string name="prefs_active_noise_cancelling_level">Akt铆v zajsz疟r茅s szintje</string>
+    <string name="right_earbud">Jobb oldali f眉lhallgat贸</string>
+    <string name="pref_title_touch_spotify_official_app">Spotify (csak a hivatalos alkalmaz谩s)</string>
+    <string name="pref_switch_controls_ambient_off">K枚rnyezet 鈫愨啋 Ki</string>
+    <string name="pref_switch_controls_anc_ambient">Zajsz疟r茅s 鈫愨啋 K枚rnyezet</string>
+    <string name="prefs_voice_detect_summary">K枚rnyezeti hangok enged茅lyez茅se 茅s a m茅dialej谩tsz谩s lehalk铆t谩sa besz茅d茅rz茅kel茅skor</string>
+    <string name="sony_speak_to_chat_timeout_long">Hossz煤 (1 p)</string>
+    <string name="sony_speak_to_chat_sensitivity_standard">脕ltal谩nos</string>
+    <string name="freestyle">Szabadst铆lus</string>
+    <string name="devicetype_galaxybuds_2_pro">Galaxy Buds2 Pro</string>
+    <string name="prefs_active_noise_cancelling">Akt铆v zajsz疟r茅s</string>
+    <string name="prefs_voice_detect">Besz茅d茅rz茅kel茅s</string>
+    <string name="pref_header_audio">Hang</string>
+    <string name="pref_header_generic">脕ltal谩nos</string>
+    <string name="devicetype_huawei_watchgt4">Huawei Watch GT 4</string>
+    <string name="devicetype_huawei_watchultimate">Huawei Watch Ultimate</string>
+    <string name="pref_title_notification_wake_on_open">Automatikus 茅breszt茅s 茅s felold谩s</string>
+    <string name="armenian">脰rm茅ny</string>
+    <string name="devicetype_redmi_watch_4">Redmi Watch 4</string>
+    <string name="Strokes">Le眉t茅sek</string>
+    <string name="fw_upgrade_notice_amazfitbip3">A(z) %s firmware-t k铆v谩nja telep铆teni az Amazfit Bip 3 k茅sz眉l茅k茅re.
+\n
+\nGy艖z艖dj枚n meg r贸la, hogy a .fw f谩jlt, majd a .res f谩jlt telep铆ti. A k茅sz眉l茅ke az .fw f谩jl telep铆t茅se ut谩n 煤jraindul.
+\n
+\nMegjegyz茅s: Nem kell telep铆tenie a .res f谩jlt, ha pontosan megegyezik a kor谩bban telep铆tettel.
+\n
+\nCSAK SAJ脕T FELEL艕SS脡G脡RE FOLYTASSA!</string>
+    <string name="pref_summary_notification_wake_on_open">脡breszd fel 茅s oldd fel az Android k茅sz眉l茅ket, ha a k茅sz眉l茅k OPEN v谩laszt k眉ld vissza. Megb铆zhat贸 谩llapotban kell lennie.</string>
+    <string name="busy_task_fetch_sports_details_interrupted">A sportadatok lek茅r茅se megszakadt</string>
+    <string name="swolfAvg">脕tlagos swolf</string>
+    <string name="swolfMax">Maximum swolf</string>
+    <string name="swolfMin">Minimum swolf</string>
+    <string name="milliseconds">ezredm谩sodperc</string>
+    <string name="degrees">fok</string>
+    <string name="Pace">Temp贸</string>
+    <string name="Elevation">Emelked茅s</string>
+    <string name="RunningForm">Fut贸forma</string>
+    <string name="prefs_touch_lock">脡rint艖k茅perny艖 z谩rol谩sa</string>
+    <string name="sony_connect_two_devices">Csatlakoz谩s 2 eszk枚zh枚z p谩rhuzamosan</string>
+    <string name="pref_title_touch_ambient">K枚rnyezeti hang</string>
+    <string name="prefs_equalizer_preset">Hangsz铆nszab谩lyz贸 el艖be谩ll铆t谩s</string>
+    <string name="prefs_active_noise_cancelling_summary">K枚rnyezeti zajok sz疟r茅se</string>
+    <string name="pressure_relief_summary">Megakad谩lyozza a nyom谩s茅rz茅st a f眉lben, amikor az akt铆v zajsz疟r茅s nincs haszn谩latban</string>
+    <string name="pref_summary_physical_buttons">Az 贸ra fizikai gombjainak be谩ll铆t谩sa</string>
+    <string name="qhybrid_pref_title_actions">M疟veletek</string>
+    <string name="button_watchface_settings">脫rafel眉let be谩ll铆t谩sok</string>
+    <string name="stepRateSum">L茅p茅ssz谩m 枚sszege</string>
+    <string name="impactMax">Maximum hat谩s</string>
+    <string name="impactAvg">Hat谩s 谩tlag</string>
+    <string name="swingAngleAvg">Leng茅ssz枚g 谩tlag</string>
+    <string name="eversionAngleAvg">Kifordul谩si sz枚g 谩tlag</string>
+    <string name="fmtPaceDistance">脺tem %d t谩vols谩g</string>
+    <string name="stepRateAvg">L茅p茅ssz谩m 谩tlag</string>
+    <string name="stepLengthAvg">L茅p茅shossz 谩tlag</string>
+    <string name="groundContactTimeAvg">Talajjal val贸 茅rintkez茅si id艖 谩tlag</string>
+    <string name="foreFootLandings">L谩bfej le茅rkez茅s</string>
+    <string name="backFootLandings">L谩bsarok le茅rkez茅s</string>
+    <string name="activity_detail_share_raw_summary">Nyers 枚sszefoglal贸 megoszt谩sa</string>
+    <string name="midFootLandings">Talpk枚z茅p le茅rkez茅s</string>
+    <string name="eversionAngleMax">Maxim谩lis kifordul谩si sz枚g</string>
+    <string name="fmtPaceType">脺tem %d t铆pus</string>
+    <string name="fmtPacePace">脺tem %d 眉tem</string>
+    <string name="gps_track">GPS nyomvonal</string>
+    <string name="fmtPaceCorrection">脺tem %d korrekci贸</string>
+    <string name="fmtPaceTypeAverage">脺tem t铆pus %d 谩tlag</string>
+    <string name="unknownDataEncountered">Ismeretlen adatok</string>
+    <string name="activity_detail_show_gps_label">GPS nyomvonal megjelen铆t茅se</string>
+    <string name="activity_detail_share_gps_label">GPS nyomvonal megoszt谩sa</string>
+    <string name="activity_detail_share_raw_details">Nyers r茅szletek megoszt谩sa</string>
+    <string name="prefs_ambient_sound_during_call_summary">Visszhang h铆v谩s k枚zben</string>
+    <string name="pref_header_filter">Sportok sz疟r茅se</string>
+    <string name="prefs_fake_ring_duration">Hamis folyamatos cseng茅s</string>
+    <string name="hybridhr_widget_bg_double_circle">Dupla k枚r</string>
+    <string name="prefs_ambient_sound">Hang谩tereszt茅s</string>
+    <string name="prefs_key_vibration">Gomb vibr谩ci贸</string>
+    <string name="activity_filter_individual_items">Egy茅nileg kijel枚lt elemek</string>
+    <string name="hybridhr_widget_bg_dashed_circle">Szaggatott k枚r</string>
+    <string name="watchface_setting_power_saving_hands">Mutat贸k mozg谩s谩nak kikapcsol谩sa lev茅teln茅l</string>
+    <string name="devicetype_nothingear1">Nothing Ear (1)</string>
+    <string name="pref_title_equalizer_bass_boost">Maximum basszus</string>
+    <string name="watchface_widget_type_chance_rain">Es艖 val贸sz铆n疟s茅ge</string>
+    <string name="prefs_sleep_time_summary">Id艖 pontos铆t谩sa elalv谩skor</string>
+    <string name="prefs_ambient_volume">Hang谩tereszt茅s hangereje</string>
+    <string name="pref_title_touch_quick_ambient">Gyors hang谩tereszt茅s</string>
+    <string name="pref_title_equalizer_trebble">Magas hangok er艖s铆t茅se</string>
+    <string name="watchface_dialog_widget_presets">Alap茅rtelmezett poz铆ci贸k</string>
+    <string name="prefs_events_forwarding_startnonwear">Lev茅teln茅l</string>
+    <string name="prefs_events_forwarding_wokeup">脡bred茅sn茅l</string>
+    <string name="qhybrid_pref_title_external_intents">Vesz茅lyes k眉ls艖 forr谩sok enged茅lyez茅se</string>
+    <string name="devicetype_nothingear2">Nothing Ear (2)</string>
+    <string name="prefs_dolby_summary">Dolby el艖be谩ll铆t谩s a hangsz铆nszab谩lyz贸hoz</string>
+    <string name="prefs_touch_lock_buds2">脡rint艖 funkci贸k</string>
+    <string name="prefs_touch_lock_summary">脡rint艖 kijelz艖 kikapcsol谩sa</string>
+    <string name="prefs_ambient_volume_left">Hang谩tereszt茅s hangereje (bal)</string>
+    <string name="prefs_customize_ambient_sound_summary">Hang谩tereszt茅s testreszab谩sa</string>
+    <string name="prefs_ambient_sound_during_call_title">Hang谩tereszt茅s h铆v谩s k枚zben</string>
+    <string name="prefs_ambient_settings_title">Hang谩tereszt茅s be谩ll铆t谩sok</string>
+    <string name="watchface_setting_wrist_flick_minute">Perc mutat贸</string>
+    <string name="fossil_hr_warning_firmware_too_new">Egyes funkci贸k le vannak tiltva, mert az 贸ra firmware-je t煤l 煤j</string>
+    <string name="qhybrid_calibration_align_hint">Az al谩bbi gombok seg铆ts茅g茅vel igaz铆tsa az 贸ra mutat贸it 12:00-ra.</string>
+    <string name="hybridhr_widget_bg_thin_circle">V茅kony k枚r</string>
+    <string name="watchface_setting_power_saving_display">K茅perny艖 funkci贸k nem lev茅teln茅l</string>
+    <string name="nothing_prefs_audiomode_title">脡rtes铆t茅s m贸d</string>
+    <string name="devicetype_galaxybuds_live">Galaxy Buds Live</string>
+    <string name="prefs_seamless_connection_switch_title">Z枚kken艖mentes 谩tkapcsol谩s</string>
+    <string name="prefs_ambient_volume_right">Hang谩tereszt茅s hangereje (jobb)</string>
+    <string name="prefs_ambient_mode">K枚rnyezeti hang m贸d</string>
+    <string name="prefs_events_forwarding_broadcast_title">脺zenet k枚zvet铆t茅se</string>
+    <string name="qhybrid_watchface_configuration_old_firmware">脫rafel眉let be谩ll铆t谩sok Dn1.0.2.19r 茅s r茅gebbi firmwarrel rendelkez艖 eszk枚z枚kh枚z</string>
+    <string name="qhybrid_title_watchface_designer">脫rafel眉let-tervez艖</string>
+    <string name="watchface_setting_title_wrist_flick">Csukl贸 mozg谩s</string>
+    <string name="watchface_setting_wrist_flick_hour">脫ra mutat贸</string>
+    <string name="prefs_active_noise_cancelling_transparency">脕ttetsz艖 hang</string>
+    <string name="prefs_pressure_relief">Nyom谩scs枚kkent茅s hang谩tereszt茅ssel</string>
+    <string name="prefs_galaxy_touch_options">脡rint茅s be谩ll铆t谩sok</string>
+    <string name="pref_title_touch_spotify_galaxy_app">Spotify (kiz谩r贸lag Galaxy Wearable)</string>
+    <string name="pref_ambient_sound_tone_summary">L谩gyb贸l tiszt谩ba</string>
+    <string name="prefs_operating_sounds">Haszn谩lati hangok</string>
+    <string name="qhybrid_pref_summary_actions">K枚zleked茅si app funkci贸k</string>
+    <string name="pref_summary_developer_settings">Fejleszt艖i be谩ll铆t谩sok 茅s funkci贸k</string>
+    <string name="devicetype_amazfit_trex_pro">Amazfit T-Rex Pro</string>
+    <string name="watchface_setting_wrist_flick_duration">Id艖tartam (ms)</string>
+    <string name="qhybrid_summary_calibration">脫ra mutat贸k kalibr谩l谩sa</string>
+    <string name="devicetype_nothingearstick">Nothing Ear (Stick)</string>
+    <string name="devicetype_nothing_cmf_watch_pro">CMF Watch Pro</string>
+    <string name="prefs_ambient_voice_summary">Besz茅dhang kiemel茅se</string>
+    <string name="pref_ambient_sound_tone">Hang谩tereszt茅s t贸nus</string>
+    <string name="watchface_setting_button_move_hands">Mutat贸k mozgat谩sa</string>
+    <string name="prefs_events_forwarding_action_title">Futtat谩s</string>
+    <string name="sony_equalizer">Hangsz铆nszab谩lyz贸</string>
+    <string name="sony_equalizer_preset_bright">脡l茅nk</string>
+    <string name="sony_equalizer_preset_mellow">M茅lab煤s</string>
+    <string name="fossil_hr_button_config_info">Egyes gombok nem konfigur谩lhat贸ak, mert funkci贸jukat az 贸ra firmware-je kem茅nyen k贸dolja. \n \nFigyelem: a fels艖 gomb hossz煤 megnyom谩s谩val, amikor a hivatalos Fossil alkalmaz谩sb贸l sz谩rmaz贸 贸rafel眉let van telep铆tve, a modulok megjelen铆t茅se/elrejt茅se k枚z枚tt is v谩ltani fog.</string>
+    <string name="sony_warn_sbc_codec">Figyelmeztet茅s: A hangsz铆nszab谩lyz贸, a hangpoz铆ci贸 茅s a k枚rnyezeti be谩ll铆t谩sok csak az SBC audio codec eset茅ben m疟k枚dnek.</string>
+    <string name="sony_equalizer_preset_vocal">脡nek</string>
+    <string name="watchface_widget_type_custom">Egy茅ni modul</string>
+    <string name="pref_force_wear_location">Viselt eszk枚z helysz铆n-meghat谩roz贸 szolg谩ltat谩s k茅nyszer铆t茅se</string>
+    <string name="sony_equalizer_preset_bass_boost">Basszuser艖s铆t茅s</string>
+    <string name="sony_equalizer_preset_treble_boost">Magas hangok er艖s铆t茅se</string>
+    <string name="watchface_dialog_widget_timeout_show_circle">K枚r mutat谩sa id艖t煤ll茅p茅skor</string>
+    <string name="watchface_setting_desc_wrist_flick">(a teljes letilt谩shoz enged茅lyezze a relat铆v mozg谩st, 茅s 谩ll铆tsa az 枚sszes 茅rt茅ket 0-ra)</string>
+    <string name="widget_screen_no_free_slots_description">Az eszk枚z枚n nincs szabad hely a modulk茅perny艖k sz谩m谩ra (枚sszes hely: %1$s)</string>
+    <string name="watchface_setting_wrist_flick_move_relative">Mozgassa a mutat贸kat az id艖h枚z k茅pest</string>
+    <string name="pref_header_sony_equalizer_preset_custom_1">Egy茅ni el艖be谩ll铆t谩s 1</string>
+    <string name="pref_header_sony_equalizer_preset_custom_2">Egy茅ni el艖be谩ll铆t谩s 2</string>
+    <string name="watchface_dialog_widget_timezone_duration">Az 贸ra l谩that贸s谩g谩nak id艖tartama (m谩sodpercben)</string>
+    <string name="pref_force_wear_location_summary">Viselt eszk枚z helysz铆n-meghat谩roz贸 szolg谩ltat谩s k茅nyszer铆t茅se.
+\nHASZN脕LAT SAJ脕T FELEL艕SS脡GRE</string>
+    <string name="watchface_dialog_pre_setting_position">el艖re be谩ll铆tott poz铆ci贸 ehhez: %s</string>
+    <string name="info_fossil_rebuild_watchface_custom_menu">脕ll铆tsa 枚ssze az egy茅ni men眉h枚z az 贸rafel眉letet</string>
+    <string name="uploading_watchface">脫rafel眉let felt枚lt茅se鈥�</string>
+    <string name="uploadwatchfaceoperation_in_progress">脫rafel眉let felt枚lt茅se</string>
+    <string name="pref_device_action_phone_gps_location_listener_stop">GPS helysz铆n-meghat谩roz谩s figyel艖 meg谩ll铆t谩sa</string>
+    <string name="watchface_setting_light_up_on_notification">脷j 茅rtes铆t茅skor vil谩g铆t</string>
+    <string name="watchface_dialog_widget_update_timeout">Friss铆t茅si id艖korl谩t percben</string>
+    <string name="watchface_dialog_widget_timeout_hide_text">Sz枚veg elrejt茅se id艖t煤ll茅p茅skor</string>
+    <string name="uploadwatchfaceoperation_failed">Az 贸rafel眉let telep铆t茅se sikertelen</string>
+    <string name="uploadwatchfaceoperation_complete">Az 贸rafel眉let telep铆t茅se k茅sz</string>
+    <string name="prefs_events_forwarding_summary">K茅sz眉l茅k-esem茅nyek haszn谩lata akci贸k 茅s Android ad谩sok ind铆t谩s谩hoz</string>
+    <string name="agps_status_pending">F眉gg艖ben</string>
+    <string name="proprietary_app_warning">Ez a funkci贸 egy z谩rt forr谩sk贸d煤 alkalmaz谩s telep铆t茅s茅t ig茅nyli</string>
+    <string name="agps_status_error">Hiba</string>
+    <string name="pref_wearing_tone_summary">Hangjelz茅s a f眉lhallgat贸 behelyez茅sekor</string>
+    <string name="pref_title_notification_times_enabled">脡rtes铆t茅si id艖pontok</string>
+    <string name="pref_summary_notification_times_enabled">Csak meghat谩rozott id艖pontok k枚z枚tt k眉ldj枚n 茅rtes铆t茅st</string>
+    <string name="pref_touch_tone">脡rint茅shang</string>
+    <string name="pref_agps_status">AGPS st谩tusz</string>
+    <string name="gps_glonass">GPS + GLONASS</string>
+    <string name="agps_status_missing">Hi谩nyzik</string>
+    <string name="agps_status_current">Jelenlegi</string>
+    <string name="activity_prefs_height_inches">Magass谩g inchben</string>
+    <string name="notif_battery_full_title">Eszk枚z akkumul谩tora tele!</string>
+    <string name="notif_battery_full">%1$s akkumul谩tora felt枚ltve</string>
+    <string name="authentication_failed_check_key">Hiteles铆t茅s sikertelen, k茅rem ellen艖rizze a hiteles铆t艖 kulcsot</string>
+    <string name="pref_touch_tone_summary">Hangjelz茅s a f眉lhallgat贸 meg茅rint茅sekor</string>
+    <string name="pref_description_dashboard">Modulok, bevonhat贸 eszk枚z枚k</string>
+    <string name="pref_wearing_tone">Hangv茅tel</string>
+    <string name="pref_header_sound">Hang</string>
+    <string name="pref_time_sync">Automatikus id艖szinkroniz谩l谩s</string>
+    <string name="search">Keres茅s</string>
+    <string name="pref_title_nagivation_apps">Navig谩ci贸s applik谩ci贸k</string>
+    <string name="pref_header_external_integrations">K眉ls艖 integr谩ci贸k</string>
+    <string name="pref_header_automations">Aut贸matiz谩ci贸</string>
+    <string name="pref_description_general">Rendszerind铆t谩s, nyelv, r茅gi贸, tart贸zkod谩si hely</string>
+    <string name="pref_description_about_you">Sz眉let茅snap, nem, magass谩g, s煤ly, c茅lok</string>
+    <string name="pref_description_notifications">脡rtes铆t茅sek, Enged茅lyezettek/Tilt贸list谩sok</string>
+    <string name="pref_description_user_interface">T茅ma, f艖k茅perny艖</string>
+    <string name="pref_description_automations">Automatikus export谩l谩s, Automatikus lek茅rdez茅s</string>
+    <string name="pref_description_developer_options">Napl贸k, Intent API</string>
+    <string name="pref_description_deprecated_functionalities">J枚v艖beli verzi贸kban elt谩vol铆tand贸 be谩ll铆t谩sok</string>
+    <string name="pref_header_main_screen">F艖k茅perny艖</string>
+    <string name="pref_title_user_interface">Felhaszn谩l贸i fel眉let</string>
+    <string name="activity_type_beach_volleyball">Strandr枚plabda</string>
+    <string name="pref_device_action_dnd_on">Ne zavarjanak - Be</string>
+    <string name="temperature_scale_cf_summary">Megadhatja, hogy az eszk枚z Celsius vagy Fahrenheit sk谩l谩t haszn谩ljon.</string>
+    <string name="pref_dashboard_widget_show_legend_title">Felirat megjelen铆t茅se</string>
+    <string name="pref_device_action_dnd_off">Ne zavarjanak - Ki</string>
+    <string name="double_tap">Dupla koppint谩s</string>
+    <string name="pref_media_pause">M茅dia sz眉net</string>
+    <string name="pref_activity_recognition_mode_ask">k茅rdezzen r谩</string>
+    <string name="pref_continuous_skin_temperature_measurement_title">A b艖r h艖m茅rs茅klet茅nek folyamatos m茅r茅se</string>
+    <string name="prefs_activity_recognition">Tev茅kenys茅g felismer茅s茅nek be谩ll铆t谩sai</string>
+    <string name="pref_force_options_summary">N茅h谩ny eszk枚z hamisan azt 谩ll铆tja, hogy nem t谩mogat egyes be谩ll铆t谩sokat. Ezzel a kapcsol贸val ett艖l f眉ggetlen眉l aktiv谩lni lehet 艖ket.\nCSAK SAJ脕T FELEL艕SS脡GRE!\nT枚bb inform谩ci贸 a Wiki-n.</string>
+    <string name="pref_dashboard_widget_today_title">Tev茅kenys茅ggrafikon</string>
+    <string name="pref_device_action_dnd_alarms">Ne zavarjanak - Csak riaszt谩sok</string>
+    <string name="pref_enable_call_reject_summary">H铆v谩sok elutas铆t谩s谩nak enged茅lyez茅se err艖l az eszk枚zr艖l</string>
+    <string name="autoconnect_from_device_summary">Csatlakoz谩s, ha egy eszk枚z fel艖l k茅relem 茅rkezik, p茅ld谩ul fejhallgat贸</string>
+    <string name="prefs_wena3_auto_power_off_hint">Az eszk枚z automatikusan ki- 茅s bekapcsol a megadott id艖pontokban</string>
+    <string name="pref_dashboard_widget_today_24h_title">24 贸r谩s m贸d</string>
+    <string name="pref_dashboard_widget_double_size_title">Dupla m茅ret</string>
+    <string name="minutes_60">60 perc</string>
+    <string name="scan_scanning_all_devices">Minden eszk枚z keres茅se</string>
+    <string name="single_tap">Koppint谩s</string>
+    <string name="pref_continuous_skin_temperature_measurement_description">A h艖m茅rs茅klet lek茅rdez茅se jelenleg nem t谩mogatott. Ez a be谩ll铆t谩s csak az eszk枚z枚n t枚rt茅n艖 folyamatos m茅r茅st kapcsolja be.</string>
+    <string name="distance_avg">T谩vols谩g 脕TL</string>
+    <string name="transition">脕tmenet</string>
+    <string name="choose_device">Eszk枚z kiv谩laszt谩sa</string>
+    <string name="music_upload_info">A k枚vetkez艖 zenesz谩mot k茅sz眉l felt枚lteni:\n\n%1$s\nZenesz谩m c铆me: %2$s\nAlbum: %3$s\n</string>
+    <string name="minutes_2">2 perc</string>
+    <string name="minutes_3">3 perc</string>
+    <string name="minutes_150">150 perc</string>
+    <string name="stats_lowest_hr">Legalacsonyabb pulzus</string>
+    <string name="stats_highest_hr">Legmagasabb pulzus</string>
+    <string name="activity_type_auto_racing">Aut贸versenyz茅s</string>
+    <string name="pref_summary_debug">Hibakeres茅si k茅relem k眉ld茅se egy Huawei eszk枚z fel茅</string>
+    <string name="pref_title_sync_birthdays">Sz眉let茅snapok szinkroniz谩l谩sa</string>
+    <string name="pref_summary_sync_birthdays">A napt谩rbejegyez茅sek mellett a n茅vjegyek sz眉let茅snapjainak szinkroniz谩l谩sa</string>
+    <string name="no_supported_devices_found">Nem tal谩lhat贸 t谩mogatott eszk枚z</string>
+    <string name="stress_average">脕tlag</string>
+    <string name="activity_type_beach_soccer">Strandlabdar煤g谩s</string>
+    <string name="activity_type_bike_commute">Biciklis k枚zleked茅s</string>
+    <string name="activity_type_cardio">Kardi贸 edz茅s</string>
+    <string name="pref_media_play">M茅dialej谩tsz谩s</string>
+    <string name="qhybrid_title_on_device_confirmation">P谩ros铆t谩s enged茅lyez茅se az eszk枚z枚n</string>
+    <string name="debug_companion_pair_current">Eszk枚z p谩ros铆t谩sa t谩rseszk枚zk茅nt</string>
+    <string name="dashboard_calendar_month_goals_reached_title">%% a l茅p茅ssz谩mc茅lb贸l el茅rve</string>
+    <string name="pref_dashboard_first_title">A M疟szerfal megjelen铆t茅se els艖k茅nt</string>
+    <string name="pref_dashboard_widget_today_24h_summary">A tev茅kenys茅g megjelen铆t茅se k茅t 12 贸r谩s k枚r helyett egyetlen 24 贸r谩s k枚rben</string>
+    <string name="bottom_nav_dashboard">M疟szerfal</string>
+    <string name="serbian">Szerb</string>
+    <string name="idasen_pref_value_warning">Az 茅rt茅knek 62 - 126 cm k枚z茅 kell esnie</string>
+    <string name="steps_total">脰sszes l茅p茅ssz谩m</string>
+    <string name="pref_wear_sensor_summary">Nem visel茅s 茅szlel茅se</string>
+    <string name="info_no_devices_connected">Nincs csatlakoztatott eszk枚z</string>
+    <string name="auto_reconnect_ble_title">Automatikus 煤jracsatlakoz谩s az eszk枚zh枚z</string>
+    <string name="bottom_nav_devices">Eszk枚z枚k</string>
+    <string name="huawei_trusleep_summary">Val贸s id艖ben figyeli az alv谩smin艖s茅get 茅s a l茅gz茅st.\nAlv谩selemz茅s 茅s 6 f茅le alv谩si probl茅ma pontos diagnosztiz谩l谩sa.</string>
+    <string name="companion_pairing_request_title">T谩rseszk枚z</string>
+    <string name="companion_pairing_request_description">T谩rseszk枚zk茅nt p谩ros铆tja ezt az eszk枚zt?\n\nAj谩nlott n茅h谩ny funkci贸 haszn谩lat谩hoz, mint p茅ld谩ul az eszk枚z megtal谩l谩sa, illetve jobb kapcsol贸d谩st eredm茅nyez.</string>
+    <string name="scan_scanning_single_device">1 eszk枚z keres茅se</string>
+    <string name="scan_scanning_multiple_devices">%d eszk枚z keres茅se</string>
+    <string name="unbind_before_pair_message">Ezt az eszk枚zt az Android be谩ll铆t谩sokon bel眉l m谩r 枚sszekapcsolta, amely p谩ros铆t谩si hib谩hoz vezethet egyes eszk枚z枚kn茅l.\n\nHa az eszk枚z hozz谩ad谩sa sikertelen, t枚r枚lje az Android be谩ll铆t谩sokon bel眉l 茅s pr贸b谩lja 煤jra!</string>
+    <string name="devicesetting_scannable_debounce_summary">Miut谩n megtal谩lt谩k, az eszk枚z felder铆tett 谩llapotba ker眉l 茅s meghat谩rozott ideig nem fog megjelenni</string>
+    <string name="pref_dashboard_all_devices_title">Minden eszk枚z</string>
+    <string name="busy_task_fetch_hrv_data">HRV adat lek茅rdez茅se</string>
+    <string name="minutes_45">45 perc</string>
+    <string name="minutes_20">20 perc</string>
+    <string name="minutes_75">75 perc</string>
+    <string name="minutes_90">90 perc</string>
+    <string name="minutes_120">120 perc</string>
+    <string name="minutes_180">180 perc</string>
+    <string name="stats_empty_value">-</string>
+    <string name="sleep_colored_stats_light_avg">K枚nny疟 脕TL</string>
+    <string name="date_placeholders__date__time">%1s, %1s</string>
+    <string name="sleep_colored_stats_rem_avg">REM 脕TL</string>
+    <string name="activity_prefs_date_birth">Sz眉let茅si id艖</string>
+    <string name="activity_type_motorcycling">Motorker茅kp谩roz谩s</string>
+    <string name="activity_type_boating">Haj贸z谩s</string>
+    <string name="activity_type_flying">Rep眉l茅s</string>
+    <string name="activity_type_kayaking">Kajakoz谩s</string>
+    <string name="activity_type_climb_indoor">Falm谩sz谩s</string>
+    <string name="activity_type_bouldering">Boulder m谩sz谩s</string>
+    <string name="distance_total">脰sszes t谩vols谩g</string>
+    <string name="devicetype_scannable">Felder铆thet艖 eszk枚z</string>
+    <string name="pref_dashboard_select_devices_title">Eszk枚z枚k kiv谩laszt谩sa...</string>
+    <string name="pref_dashboard_widget_goals_chart_title">C茅lok grafikon</string>
+    <string name="info_connected_count">%d eszk枚z csatlakoztatva</string>
+    <string name="pref_dashboard_first_summary">Ind铆t谩s ut谩n a M疟szerfal megjelen铆t茅se az Eszk枚z枚k helyett</string>
+    <string name="pref_title_calendar_lookahead">H谩tral茅v艖 napok sz谩ma</string>
+    <string name="pref_summary_calendar_lookahead">Az elk枚vetkezend艖 %1s nap napt谩rbejegyz茅seinek szinkroniz谩l谩sa</string>
+    <string name="minutes_15">15 perc</string>
+    <string name="minutes_240">240 perc</string>
+    <string name="minutes_255">255 perc</string>
+    <string name="minutes_210">210 perc</string>
+    <string name="dateformat_day_month">Nap, H贸nap</string>
+    <string name="dateformat_month_day">H贸nap, Nap</string>
+    <string name="activity_type_rem_sleep">REM alv谩s</string>
+    <string name="activity_type_transition">脕tmenet</string>
+    <string name="activity_type_mountaineering">Hegym谩sz谩s</string>
+    <string name="activity_type_american_football">Amerikai futball</string>
+    <string name="activity_type_training">Edz茅s</string>
+    <string name="activity_type_paddling">Lass煤 evez茅s</string>
+    <string name="activity_type_driving">Vezet茅s</string>
+    <string name="activity_type_ice_skating">J茅gkorcsolya</string>
+    <string name="activity_type_snowmobiling">H贸mobiloz谩s</string>
+    <string name="activity_type_modern_dance">Modern t谩nc</string>
+    <string name="activity_type_muay_thai">Muay thai</string>
+    <string name="devicetype_miband9">Xiaomi Smart Band 9</string>
+    <string name="devicesetting_scannable_rssi">Minim谩lis RSSI hat谩r茅rt茅k</string>
+    <string name="pref_media_rewind">Ugr谩s h谩tra</string>
+    <string name="pref_device_action_dnd_priority">Ne zavarjanak - Csak els艖bbs茅ggel</string>
+    <string name="prefs_workmode">Munka m贸d</string>
+    <string name="pref_activity_recognition_mode_none">nincs</string>
+    <string name="huawei_trusleep_summary_light">Fejlesztett alv谩sfigyel茅s</string>
+    <string name="huawei_trusleep_warning">Figyelem: aktiv谩l谩s ut谩n minden alv谩sadat k枚nny疟 alv谩sk茅nt jelenik meg a Gadgetbridge-ben! Koppintson ide, ha ezt elfogadja!</string>
+    <string name="pref_summary_navigation_forward">A navig谩ci贸s applik谩ci贸k utas铆t谩sainak tov谩bb铆t谩sa az 贸r谩ra</string>
+    <string name="error_deleting_device">Hiba az eszk枚z t枚rl茅se sor谩n: %s</string>
+    <string name="fossil_hr_confirmation_skipped">Eszk枚z枚n t枚rt茅n艖 j贸v谩hagy谩s kihagy谩sa</string>
+    <string name="hrv">Sz铆vfrekvencia-variabilit谩s</string>
+    <string name="triple_tap">Tripla koppint谩s</string>
+    <string name="busy_task_fetch_sleep_data">Alv谩sadatok lek茅rdez茅se</string>
+    <string name="minutes_6">6 perc</string>
+    <string name="minutes_7">7 perc</string>
+    <string name="minutes_9">9 perc</string>
+    <string name="minutes_4">4 perc</string>
+    <string name="minutes_8">8 perc</string>
+    <string name="pref_enable_call_accept_summary">H铆v谩sfogad谩s enged茅lyez茅se err艖l az eszk枚zr艖l</string>
+    <string name="sleep_colored_stats_awake_avg">脡brenl茅t 脕TL</string>
+    <string name="pref_activity_full_sync_trigger_warning">Ez az opci贸 az eszk枚z枚n l茅v艖 枚sszes tev茅kenys茅gadatot szinkroniz谩lja. A folyamat p谩r percig eltarthat.</string>
+    <string name="find_my_phone_companion_warning">A telefon megtal谩l谩sa funkci贸hoz t谩rseszk枚zk茅nt val贸 p谩ros铆t谩s sz眉ks茅ges. Tov谩bbi inform谩ci贸 itt.</string>
+    <string name="abstract_chart_fragment_kind_awake_sleep">脡brenl茅t</string>
+    <string name="sleep_colored_stats_deep">M茅ly</string>
+    <string name="sleep_colored_stats_light">K枚nny疟</string>
+    <string name="sleep_colored_stats_rem">REM</string>
+    <string name="sleep_colored_stats_deep_avg">M茅ly 脕TL</string>
+    <string name="hrv_status">Sz铆vfrekvencia-variabilit谩s 谩llapota</string>
+    <string name="activity_type_parachuting">Ejt艖erny艖z茅s</string>
+    <string name="pref_media_forward">Ugr谩s el艖re</string>
+    <string name="authentication_failed_negotiation">Hiteles铆t茅si kulcs egyeztet茅si hiba</string>
+    <string name="activity_type_parkour">Parkour</string>
+    <string name="pref_dashboard_devices_to_include">Felhaszn谩lt eszk枚z枚k</string>
+    <string name="dashboard_settings">M疟szerfal be谩ll铆t谩sok</string>
+    <string name="hr_resting">Pihen茅s</string>
+    <string name="hr_maximum">Maximum</string>
+    <string name="hr_minimum">Minimum</string>
+    <string name="hr_average">脕tlag</string>
+    <string name="steps_avg">L茅p茅ssz谩m 脕TL</string>
+    <string name="pref_activity_recognition_mode_auto">automatikus</string>
+    <string name="user_feedback_set_settings_ok">Be谩ll铆t谩sok elk眉ldve az eszk枚zre.</string>
+    <string name="steps_streaks_since_date">Ekkort贸l: %s</string>
+    <string name="pref_title_opentracks_packagename">OpenTracks csomag neve</string>
+    <string name="pref_spo_automatic_enable">Automatikus v茅roxig茅nszint-m茅r茅s bekapcsol谩sa</string>
+    <string name="pref_force_smart_alarm">Okos 茅breszt茅s k茅nyszer铆t茅se</string>
+    <string name="pref_heartrate_automatic_enable">Automatikus pulzusm茅r茅s bekapcsol谩sa</string>
+    <string name="pref_title_navigation_forward">Navig谩ci贸 k眉ld茅se 贸r谩ra</string>
+    <string name="pref_enable_call_reject">H铆v谩sok visszautas铆t谩s谩nak enged茅lyez茅se</string>
+    <string name="controlcenter_set_folder_title">脷j mappa megad谩sa vagy l茅trehoz谩sa</string>
+    <string name="auto_reconnect_ble_summary">Csatlakoz谩si k铆s茅rlet kezdem茅nyez茅se eszk枚zh枚z id艖k枚z枚nk茅nt</string>
+    <string name="step_streak_ongoing">Folyamatban l茅v艖</string>
+    <string name="pref_enable_call_accept">H铆v谩sfogad谩s enged茅lyez茅se</string>
+    <string name="pref_summary_opentracks_packagename">GPS 煤tvonal r枚gz铆t茅s茅nek ind铆t谩sa/le谩ll铆t谩sa k眉ls艖 fitnesz alkalmaz谩sban.</string>
+    <string name="step_streak_days_hint">Egym谩st k枚vet艖 napok sz谩ma, amelyeken siker眉lt el茅rni a l茅p茅sc茅lt</string>
+    <string name="controlcenter_connected_fraction">Csatlakoztatva: %d/%d</string>
+    <string name="connection_over_ble">Csatlakoz谩s BLE-n kereszt眉l</string>
+    <string name="connection_over_bt_classic">Klasszikus Bluetooth csatlakoz谩s</string>
+    <string name="autoconnect_from_device_title">Csatlakoz谩s az eszk枚zt艖l j枚v艖 k茅relemre</string>
+    <string name="pref_disable_find_phone_with_dnd">Telefon megtal谩l谩s谩nak kikapcsol谩sa Ne zavarjanak m贸dban</string>
+    <string name="steps_streaks_hint">Megszak铆t谩s n茅lk眉li, egym谩st k枚vet艖 napok sorozata, amelyeken siker眉lt el茅rni a meghat谩rozott l茅p茅ssz谩mot</string>
+    <string name="steps_streaks_total_steps_hint_totals">脰sszes valaha r枚gz铆tett l茅p茅ssz谩m</string>
+    <string name="controlcenter_folder_name">Mappa neve:</string>
+    <string name="controlcenter_add_new_folder">脷j mappa hozz谩ad谩sa</string>
+    <string name="steps_streaks_average_steps">脕tlag\nl茅p茅s</string>
+    <string name="steps_streaks_achievement_rate">Teljes铆t茅si\nar谩ny</string>
+    <string name="controlcenter_unset_folder">Nincs megadott mappa</string>
+    <string name="pref_force_options">Be谩ll铆t谩sok k茅nyszer铆t茅se</string>
+    <string name="steps_streaks_total_steps">脰sszes\nl茅p茅s</string>
+    <string name="cannot_upload_watchface_too_many_watchfaces_installed">Nem lehet 贸rafel眉letet felt枚lteni, t煤l sok 贸rafel眉let van telep铆tve</string>
+    <string name="watchface_resolution_doesnt_match">Az 贸rafel眉let felbont谩sa nem egyezik a k茅sz眉l茅k k茅perny艖j茅vel. Az 贸rafel眉let felbont谩sa %1$s, a k茅sz眉l茅k k茅perny艖je %2$s</string>
+    <string name="ftp_server_start_summary">Az FTP-kiszolg谩l贸 elind铆t谩sa az 贸r谩n</string>
+    <string name="widget_layout_two">2 modul</string>
+    <string name="devicetype_sony_wf_c500">Sony WF-C500</string>
+    <string name="devicetype_sony_wi_c100">Sony WI-C100</string>
+    <string name="devicetype_sony_wf_c700n">Sony WF-C700N</string>
+    <string name="pref_dashboard_widget_double_size_summary">Enged茅lyezze, hogy a modul k茅t oszlopot foglaljon el a m疟szerfalon</string>
+    <string name="pref_dashboard_widget_today_hr_interval_title">Pulzussz谩m intervallum</string>
+    <string name="ftp_server_status">FTP-kiszolg谩l贸 谩llapota</string>
+    <string name="widget_missing_parts">V谩lassza ki az 枚sszes modult</string>
+    <string name="ftp_server_summary">Az FTP-kiszolg谩l贸 vez茅rl茅se az 贸r谩n</string>
+    <string name="ftp_server_root_dir">Gy枚k茅rk枚nyvt谩r</string>
+    <string name="pref_dashboard_cards_title">Modulok megjelen铆t茅se a k谩rty谩kon</string>
+    <string name="pref_dashboard_cards_summary">K谩rty谩k rajzol谩sa a m疟szerfal moduljai k枚r茅</string>
+    <string name="pref_dashboard_widget_settings">Modul be谩ll铆t谩sok</string>
+    <string name="pref_dashboard_widget_show_legend_summary">A sz铆neket magyar谩z贸 lista megjelen铆t茅se a modul alatt</string>
+    <string name="widget">Modul</string>
+    <string name="sony_protocol_v1">1. verzi贸</string>
+    <string name="sony_protocol_v2">2. verzi贸</string>
+    <string name="sony_protocol_v3">3. verzi贸</string>
+    <string name="widget_layout_single">1 modul</string>
+    <string name="widget_layout">Modul elrendez茅se</string>
+    <string name="widget_name_untitled">C铆m n茅lk眉li modul (%1$s)</string>
+    <string name="widget_screen_delete_confirm_title">Modulk茅perny艖 t枚rl茅se</string>
+    <string name="ftp_server">FTP-kiszolg谩l贸</string>
+    <string name="ftp_server_stop_summary">Az FTP-kiszolg谩l贸 le谩ll铆t谩sa az 贸r谩n</string>
+    <string name="widget_subtype">Modul alt铆pusa</string>
+    <string name="pref_dashboard_widgets_order_summary">V谩lassza ki, hogy mely modulok legyenek enged茅lyezve 茅s milyen sorrendben jelenjenek meg a m疟szerfalon</string>
+    <string name="pref_switch_noise_control">Zajsz疟r艖 kapcsol贸</string>
+    <string name="ftp_server_configuration">FTP-kiszolg谩l贸 be谩ll铆t谩sa</string>
+    <string name="widget_screen">Modulk茅perny艖</string>
+</resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-ia/strings.xml b/app/src/main/res/values-ia/strings.xml
new file mode 100644
index 0000000..a6b3dae
--- /dev/null
+++ b/app/src/main/res/values-ia/strings.xml
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources></resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-id/strings.xml b/app/src/main/res/values-id/strings.xml
new file mode 100644
index 0000000..149cab0
--- /dev/null
+++ b/app/src/main/res/values-id/strings.xml
@@ -0,0 +1,3157 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources xmlns:tools="http://schemas.android.com/tools">
+    <string name="application_name_generic">Gadgetbridge</string>
+    <string name="title_activity_controlcenter_generic">Gadgetbridge</string>
+    <string name="action_settings">Pengaturan</string>
+    <string name="action_quit">Keluar</string>
+    <string name="action_donate">Donasi</string>
+    <string name="controlcenter_fetch_activity_data">Sinkronisasi</string>
+    <string name="controlcenter_find_device">Cari perangkat hilang</string>
+    <string name="controlcenter_take_screenshot">Ambil Tangkapan Layar</string>
+    <string name="controlcenter_change_led_color">Ubah Warna LED</string>
+    <string name="controlcenter_change_fm_frequency">Ubah Frekuensi FM</string>
+    <string name="controlcenter_connect">Sambungkan鈥�</string>
+    <string name="controlcenter_disconnect">Putuskan</string>
+    <string name="controlcenter_delete_device">Hapus Perangkat</string>
+    <string name="controlcenter_delete_device_name">Hapus %1$s</string>
+    <string name="controlcenter_delete_device_dialogmessage">Tindakan ini akan menghapus semua perangkat dan data!</string>
+    <string name="controlcenter_navigation_drawer_open">Buka laci navigasi</string>
+    <string name="controlcenter_navigation_drawer_close">Tutup laci navigasi</string>
+    <string name="controlcenter_snackbar_need_longpress">Tekan lama kartu untuk memutuskan</string>
+    <string name="controlcenter_snackbar_disconnecting">Memutuskan</string>
+    <string name="controlcenter_snackbar_connecting">Menyambungkan鈥�</string>
+    <string name="controlcenter_snackbar_requested_screenshot">Sedang mengambil tangkapan layar perangkat</string>
+    <string name="controlcenter_calibrate_device">Kalibrasi Perangkat</string>
+    <string name="title_activity_debug">Awakutu</string>
+    <string name="debugactivity_really_factoryreset_title">Kembalikan ke pengaturan pabrik\?</string>
+    <string name="mi2_prefs_button_press_broadcast_default_value" translatable="false">nodomain.freeyourgadget.gadgetbridge.ButtonPressed</string>
+    <string name="appmananger_app_reinstall">Instal ulang</string>
+    <string name="appmananger_app_delete_cache">Hapus dan singkirkan dari cache</string>
+    <string name="appmananger_app_delete">Hapus</string>
+    <string name="appmanager_installed_watchfaces">Watchfaces terpasang</string>
+    <string name="appmanager_installed_watchapps">Aplikasi terpasang</string>
+    <string name="appmanager_cached_watchapps_watchfaces">Aplikasi dalam cache</string>
+    <string name="title_activity_appmanager">Pengelola Aplikasi</string>
+    <string name="debugactivity_really_factoryreset">Mengembalikan ke pengaturan pabrik akan mengkapus semua data dari perangkat yang terhubung (jika mendukung). Perangkat Xiaomi/Huami juga mengubah alamat MAC Bluetooth, agar muncul sebagai perangkat baru di Gadgetbridge.</string>
+    <string name="action_debug">Awakutu</string>
+    <string name="sonyswr12_settings_title">Pengaturan Sony SWR12</string>
+    <string name="sonyswr12_settings_alarm_interval">Interval alarm pintar dalam beberapa menit</string>
+    <string name="sonyswr12_settings_low_vibration_summary">Aktifkan getaran intensitas rendah pada pita</string>
+    <string name="sonyswr12_settings_stamina_summary">Mode hemat daya menonaktifkan pengukuran detak jantung otomatis berkala sehingga meningkatkan waktu kerja</string>
+    <string name="sonyswr12_settings_alarm_interval_summary">Interval alarm pintar adalah interval sebelum alarm dipasang. Dalam interval ini perangkat mencoba mendeteksi fase tidur paling ringan untuk membangunkan pengguna</string>
+    <string name="sonyswr12_settings_low_vibration">Getaran rendah diaktifkan</string>
+    <string name="sonyswr12_settings_stamina">Mode hemat daya aktif</string>
+    <string name="title_activity_fw_app_insaller">Penginstal FW / Aplikasi</string>
+    <string name="prefs_autoremove_message">Hapus notifikasi SMS secara otomatis</string>
+    <string name="fw_upgrade_notice">Anda akan memasang %s.</string>
+    <string name="title_activity_calblacklist">Kalender yang diblacklist</string>
+    <string name="whitelist_all_for_notifications">Whitelist untuk semua notifikasi</string>
+    <string name="blacklist_all_for_notifications">Blacklist untuk semua notifikasi</string>
+    <string name="app_move_to_top">Pindah ke atas</string>
+    <string name="app_configure">Konfigurasi</string>
+    <string name="appmanager_weather_install_provider">Pasang aplikasi notifikasi Cuaca</string>
+    <string name="appmanager_weather_deactivate">Nonaktifkan aplikasi Sistem Cuaca</string>
+    <string name="appmanager_weather_activate">Aktifkan aplikasi Sistem Cuaca</string>
+    <string name="appmanager_hrm_deactivate">Nonaktifkan HRM</string>
+    <string name="appmanager_hrm_activate">Aktifkan HRM</string>
+    <string name="appmanager_health_deactivate">Nonaktifkan</string>
+    <string name="appmanager_health_activate">Aktifkan</string>
+    <string name="appmanager_app_openinstore">Cari di Pebble appstore</string>
+    <string name="battery_detail_activity_title">Info baterai</string>
+    <string name="controlcenter_set_alias">Atur Alias</string>
+    <string name="find_lost_device_message">Cari %1$s\?</string>
+    <string name="pbwinstallhandler_correct_hw_revision">Revisi perangkat keras yang benar</string>
+    <string name="pbw_installhandler_pebble_firmware">Firmware Pebble %1$s</string>
+    <string name="fwappinstaller_connection_state">Koneksi ke perangkat: %1$s</string>
+    <string name="miband_fwinstaller_untested_version">Versi yang belum teruji!</string>
+    <string name="miband_fwinstaller_compatible_version">Versi yang kompatibel</string>
+    <string name="installhandler_firmware_name">%1$s: %2$s %3$s</string>
+    <string name="fwapp_install_device_not_ready">File tidak dapat diinstal, perangkat tidak siap.</string>
+    <string name="updating_firmware">Mem-flash perangkat tegar鈥�</string>
+    <string name="firmware_update_progress">Pengunggahan sedang berlangsung
+\n%1d %% pada %.2f kbps (rata-rata %.2f kbps)
+\nBagian %1d dari %1d</string>
+    <string name="devicestatus_upload_failed">Unggahan gagal</string>
+    <string name="devicestatus_upload_aborted">Unggahan telah dibatalkan!</string>
+    <string name="devicestatus_upload_validating">Unggahan sedang divalidasi</string>
+    <string name="devicestatus_upload_completed">Pengunggahan telah selesai</string>
+    <string name="devicestatus_disconnected">Perangkat telah terputus!</string>
+    <string name="devicestatus_disconnecting">Perangkat terputus!</string>
+    <string name="devicestatus_upload_started">Pengunggahan telah dimulai</string>
+    <string name="devicestatus_upload_starting">Pengunggahan dimulai</string>
+    <string name="devicestatus_connected">Perangkat tersambung</string>
+    <string name="devicestatus_connecting">Peranti sedang tersambung</string>
+    <string name="prefs_sounds_summary">Konfigurasi kapan perangkat akan berbunyi bip</string>
+    <string name="prefs_sounds">Suara</string>
+    <string name="overslept">Ketiduran: %1$s</string>
+    <string name="lack_of_sleep">Kurang tidur: %1$s</string>
+    <string name="show_ongoing_activity">Tampilkan popup aktifitas yang sedang berlangsung</string>
+    <string name="dialog_hide">Sembunyikan</string>
+    <string name="activity_list_summary_activities">Aktifitas</string>
+    <string name="activity_list_summary_intensity">Gerakan
+\nIntensitas</string>
+    <string name="activity_list_summary_active_time">Waktu aktif</string>
+    <string name="activity_list_summary_distance">Jarak</string>
+    <string name="activity_list_summary_active_steps">Langkah aktif</string>
+    <string name="charts_activity_list">Daftar aktifitas</string>
+    <string name="activity_sleepchart_activity_and_sleep">Aktifitas</string>
+    <string name="weekstepschart_steps_a_week">Langkah per minggu</string>
+    <string name="weeksleepchart_today_sleep_description">Tidur hari ini, target: %1$s</string>
+    <string name="weeksleepchart_sleep_a_week">Tidur per minggu</string>
+    <string name="sleepchart_your_sleep">Tidur</string>
+    <string name="prefs_charts_tabs_summary">Tab bagan yang terlihat</string>
+    <string name="prefs_charts_tabs">Tab bagan</string>
+    <string name="notif_export_failed_title">Ekspor database gagal! Silakan periksa pengaturan Anda.</string>
+    <string name="notif_battery_low_extended">%1$s baterai lemah: %2$s</string>
+    <string name="notif_battery_low">%1$s baterai lemah</string>
+    <string name="notif_battery_low_bigtext_number_of_charges">Jumlah pengisian: %s</string>
+    <string name="notif_battery_low_bigtext_last_charge_time">Terakhir diisi: %s
+\n</string>
+    <string name="notif_battery_low_percent">%1$s baterai tersisa: %2$s%%</string>
+    <string name="notif_battery_low_title">Baterai gadget Lemah!</string>
+    <string name="installer_activity_wait_while_determining_status">Harap tunggu saat menentukan status penginstalan鈥�</string>
+    <string name="pbw_install_handler_hw_revision_mismatch">Tidak dapat menginstal perangkat tegar: Ini tidak cocok dengan revisi perangkat keras Pebble Anda.</string>
+    <string name="pbw_install_handler_unable_to_install">Tidak dapat menginstal file: %1$s</string>
+    <string name="installer_activity_unable_to_find_handler">Tidak dapat menemukan handler untuk menginstal file ini.</string>
+    <string name="cannot_connect">Tidak dapat terhubung: %1$s</string>
+    <string name="alarm_sat_short">Sab</string>
+    <string name="alarm_fri_short">Jum</string>
+    <string name="alarm_thu_short">Kam</string>
+    <string name="alarm_wed_short">Rab</string>
+    <string name="alarm_tue_short">Sel</string>
+    <string name="alarm_mon_short">Sen</string>
+    <string name="alarm_sun_short">Min</string>
+    <string name="title_activity_alarm_details">Detil alarm</string>
+    <string name="bluetooth_is_disabled_">Bluetooth nonaktif.</string>
+    <string name="bluetooth_is_not_supported_">Bluetooth tidak didukung.</string>
+    <string name="find_my_phone_notification">Cari ponsel saya</string>
+    <string name="qhybrid_title_calibration">Kalibrasi</string>
+    <string name="qhybrid_title_apps_management">Manajemen aplikasi</string>
+    <string name="qhybrid_title_file_management">Manajemen file</string>
+    <string name="qhybrid_title_background_image">Gambar latar belakang</string>
+    <string name="qhybrid_title_apps">Aplikasi</string>
+    <string name="fw_upgrade_notice_amazfitbip_lite">Anda akan menginstal perangkat tegar %s di Amazfit Bip Lite Anda.
+\n
+\nPastikan untuk menginstal file .fw, dan setelah itu file .res. Jam tangan Anda akan reboot setelah menginstal file .fw.
+\n
+\nCatatan: Anda tidak perlu menginstal .res jika sama persis dengan yang diinstal sebelumnya.
+\n
+\nLANJUTKAN DENGAN RISIKO DITANGGUNG SENDIRI!</string>
+    <string name="fw_upgrade_notice_amazfitbip">Anda akan menginstal perangkat tegar %s di Amazfit Bip Anda.
+\n
+\nPastikan untuk menginstal file .fw, lalu file .res, dan file .gps. Jam tangan Anda akan reboot setelah menginstal file .fw.
+\n
+\nCatatan: Anda tidak perlu menginstal .res dan .gps jika file-file ini persis sama dengan yang sebelumnya diinstal.
+\n
+\nLANJUTKAN DENGAN RISIKO DITANGGUNG SENDIRI!</string>
+    <string name="calendar_year">Tahun</string>
+    <string name="calendar_six_months">6 bulan</string>
+    <string name="calendar_three_months">3 bulan</string>
+    <string name="calendar_month">Bulan</string>
+    <string name="calendar_two_weeks">Dua minggu</string>
+    <string name="calendar_week">Minggu</string>
+    <string name="calendar_day">Hari</string>
+    <string name="battery_level">Tingkat baterai</string>
+    <string name="pref_theme_dark">Gelap</string>
+    <string name="dbmanagementactivity_export_data_title">Ekspor Data\?</string>
+    <string name="dbmanagementactivity_import_data_title">Impor data\?</string>
+    <string name="dbmanagementactivity_error_exporting_db">Galat saat mengekspor DB: %1$s</string>
+    <string name="dbmanagementactivity_exported_to">Diekspor ke: %1$s</string>
+    <string name="title_activity_db_management">Manajemen data</string>
+    <string name="action_db_management">Manajemen data</string>
+    <string name="activity_prefs_calories_burnt">Target harian: kalori yang terbakar</string>
+    <string name="charts_legend_heartrate_average">Rata-rata detak jantung</string>
+    <string name="live_activity_heart_rate">Detak jantung</string>
+    <string name="charts_legend_heartrate">Detak jantung</string>
+    <string name="authentication_required">Otentikasi diperlukan</string>
+    <string name="authenticating">Mengautentikasi</string>
+    <string name="pref_title_chart_heartrate_color">Warna detak jantung</string>
+    <string name="pref_chart_heartrate_color_orange">Oranye</string>
+    <string name="pref_chart_heartrate_color_red">Merah</string>
+    <string name="hr_widget_nothing">Tidak ada</string>
+    <string name="hr_widget_weather">Cuaca</string>
+    <string name="hr_widget_battery">Baterai</string>
+    <string name="hr_widget_calories">Kalori</string>
+    <string name="gpx_receiver_activity_title">Penerima GPX Gadgetbridge</string>
+    <string name="require_location_provider">Lokasi harus diaktifkan</string>
+    <string name="about_activity_title_generic">Tentang Gadgetbridge</string>
+    <string name="about_version">Versi %s</string>
+    <string name="about_title">Tentang</string>
+    <string name="about_links">Tautan</string>
+    <string name="distanceMeters">Jarak</string>
+    <string name="maxSpeed">Maksimum</string>
+    <string name="caloriesBurnt">Kalori</string>
+    <string name="activeSeconds">Aktif</string>
+    <string name="minAltitude">Minimum</string>
+    <string name="maxAltitude">Maksimum</string>
+    <string name="meters_second">m/dtk</string>
+    <string name="cm">cm</string>
+    <string name="meters">m</string>
+    <string name="km_h">km/j</string>
+    <string name="minutes_km">men/km</string>
+    <string name="seconds_m">dtk/m</string>
+    <string name="seconds_km">dtk/km</string>
+    <string name="Distance">Jarak</string>
+    <string name="activity_detail_duration_label">Durasi</string>
+    <string name="activity_detail_end_label">Selesai</string>
+    <string name="activity_detail_start_label">Mulai</string>
+    <string name="Steps">Langkah</string>
+    <string name="Activity">Aktifitas</string>
+    <string name="no">Tidak</string>
+    <string name="yes">Ya</string>
+    <string name="activity_filter_date_to">Ke</string>
+    <string name="activity_filter_date_from">Dari</string>
+    <string name="sports_activity_quick_filter_this_week">Minggu ini</string>
+    <string name="activity_summaries_all_activities">Semua Aktifitas</string>
+    <string name="activity_summaries_statistics">Statistik</string>
+    <string name="button_watchface_save_apply">Simpan dan terapkan</string>
+    <string name="watchface_dialog_widget_preset_right">Kanan</string>
+    <string name="watchface_dialog_widget_preset_left">Kiri</string>
+    <string name="watchface_dialog_widget_preset_bottom">Bawah</string>
+    <string name="watchface_dialog_widget_preset_top">Atas</string>
+    <string name="watchface_setting_title_power_saving">Penghemat daya</string>
+    <string name="fossil_hr_new_action_cancel">batal</string>
+    <string name="fossil_hr_edit_action_delete">hapus</string>
+    <string name="qhybrid_calibration_100_steps">100 langkah</string>
+    <string name="qhybrid_calibration_10_steps">10 langkah</string>
+    <string name="qhybrid_calibration_1_step">1 langkah</string>
+    <string name="button_watchface_add_widget">Tambah widget</string>
+    <string name="watchface_widget_type_heart_rate">Detak jantung</string>
+    <string name="watchface_widget_type_steps">Langkah</string>
+    <string name="watchface_widget_type_weather">Cuaca</string>
+    <string name="watchface_widget_type_date">Tanggal</string>
+    <string name="watchface_dialog_widget_y_coordinate">Koordinat Y (maks 240)</string>
+    <string name="watchface_dialog_widget_x_coordinate">Koordinat X (maks 240)</string>
+    <string name="watchface_dialog_widget_type">Jenis</string>
+    <string name="watchface_dialog_widget_color_black">Hitam</string>
+    <string name="watchface_widget_type_calories">Kalori</string>
+    <string name="watchface_widget_type_battery">Baterai</string>
+    <string name="pref_title_general_autoreconnect">Sambung ulang otomatis</string>
+    <string name="pref_title_ping_tone">Nada ping</string>
+    <string name="pref_title_transliteration">Transliterasi</string>
+    <string name="pref_header_notifications">Notifikasi</string>
+    <string name="pref_theme_system">Sistem</string>
+    <string name="pref_title_theme">Tema</string>
+    <string name="pref_theme_light">Terang</string>
+    <string name="pref_title_datetime_syctimeonconnect">Waktu sinkronisasi</string>
+    <string name="pref_header_datetime">Tanggal dan Waktu</string>
+    <string name="pref_header_general">Pengaturan umum</string>
+    <string name="pref_call_privacy_mode_off">Tampilkan nama dan nomor</string>
+    <string name="pref_title_call_privacy_mode">Panggil mode privasi</string>
+    <string name="when_screen_off">Saat layar mati</string>
+    <string name="always">Selalu</string>
+    <string name="check_all_applications">Centang semua aplikasi</string>
+    <string name="pref_title_support_voip_calls">Aktifkan panggilan aplikasi VoIP</string>
+    <string name="title_activity_notification_management">Pengaturan notifikasi</string>
+    <string name="uncheck_all_applications">Hapus centang semua aplikasi</string>
+    <string name="pref_default">Bawaan</string>
+    <string name="pref_title_notifications_call">Panggilan Telepon</string>
+    <string name="pref_title_general_autoconnectonbluetooth">Sambungkan ke perangkat Gadgetbridge saat Bluetooth diaktifkan</string>
+    <string name="pref_title_general_autostartonboot">Mulai otomatis</string>
+    <string name="pref_title_audio_player">Audioplayer Pilihan</string>
+    <string name="pref_title_notifications_repetitions">Repetisi</string>
+    <string name="pref_title_notifications_sms">SMS</string>
+    <string name="pref_header_privacy">Privasi</string>
+    <string name="never">Tidak pernah</string>
+    <string name="title_activity_settings">Pengaturan</string>
+    <string name="pref_title_language">Bahasa</string>
+    <string name="pref_title_notifications_timeout">Waktu minimum antara notifikasi</string>
+    <string name="pref_title_notifications_pebblemsg">Pesan Pebble</string>
+    <string name="prefs_sleep_time">Waktu tidur</string>
+    <string name="sony_ambient_sound_wind_noise_reduction">Peredam Kebisingan Angin</string>
+    <string name="sony_ambient_sound_ambient_sound">Suara Sekitar</string>
+    <string name="sony_ambient_sound_focus_voice">Fokus pada Suara</string>
+    <string name="sony_surround_mode_club">Klub</string>
+    <string name="sony_surround_mode_outdoor_stage">Panggung Luar Ruangan</string>
+    <string name="watchface_dialog_widget_color">Warna</string>
+    <string name="sony_ambient_sound_off">Mati</string>
+    <string name="sony_ambient_sound">Mode</string>
+    <string name="prefs_activity_in_device_card_sleep_title">Tidur</string>
+    <string name="prefs_activity_in_device_card_sleep_title_summary">Tampilkan durasi tidur</string>
+    <string name="indonesian">Bahasa Indonesia</string>
+    <string name="thai">Thai</string>
+    <string name="activity_prefs_height_cm">Tinggi dalam cm</string>
+    <string name="activity_prefs_step_length_cm">Panjang langkah dalam cm</string>
+    <string name="greek">Yunani</string>
+    <string name="swedish">Swedia</string>
+    <string name="sony_surround_mode_concert_hall">Konser</string>
+    <string name="prefs_equalizer_summary">Aktifkan atau nonaktifkan ekualiser</string>
+    <string name="prefs_equalizer">Ekualiser</string>
+    <string name="sony_equalizer_preset_off">Mati</string>
+    <string name="watchface_setting_display_refresh_full">Penyegaran penuh (dalam menit)</string>
+    <string name="watchface_setting_display_refresh_partial">Penyegaran parsial (dalam menit)</string>
+    <string name="prefs_game_mode">Mode gim</string>
+    <string name="sony_ambient_sound_level">Tingkat Suara Sekitar</string>
+    <string name="sony_sound_position">Posisi Suara</string>
+    <string name="sony_sound_position_off">Mati</string>
+    <string name="sony_surround_mode">Mode Surround</string>
+    <string name="sony_equalizer">Ekualiser</string>
+    <string name="device_card_activity_card_title">Info aktivitas pada kartu perangkat</string>
+    <string name="device_card_activity_card_title_summary">Pilih detail aktivitas apa yang ditampilkan di kartu perangkat</string>
+    <string name="ukrainian">Ukraina</string>
+    <string name="hebrew">Ibrani</string>
+    <string name="activity_prefs_about_you">Tentang anda</string>
+    <string name="watchface_setting_title_display_refresh_timeout">Tampilkan batas waktu penyegaran</string>
+    <string name="watchface_widget_type_active_mins">Menit aktif</string>
+    <string name="watchface_widget_type_chance_rain">Kemungkinan hujan</string>
+    <string name="prefs_sleep_time_label">Tentukan waktu tidur</string>
+    <string name="devicetype_galaxybuds">Galaxy Buds</string>
+    <string name="devicetype_galaxybuds_live">Galaxy Buds Live</string>
+    <string name="nothing_prefs_audiomode_title">Mode Suara</string>
+    <string name="prefs_equalizer_preset">Preset Ekualiser</string>
+    <string name="pref_title_equalizer_clear">Jelas</string>
+    <string name="pref_title_equalizer_trebble">Penguat trebel</string>
+    <string name="prefs_dolby_summary">Preset Dolby untuk ekualiser</string>
+    <string name="pref_header_sony_ambient_sound_control">Kontrol Suara Sekitar</string>
+    <string name="sony_ambient_sound_noise_cancelling">Peredam Kebisingan</string>
+    <string name="sony_sound_position_front">Depan</string>
+    <string name="sony_sound_position_front_left">Kiri Depan</string>
+    <string name="sony_equalizer_band_400">400</string>
+    <string name="sony_equalizer_band_1000">1k</string>
+    <string name="sony_equalizer_band_2500">2.5k</string>
+    <string name="sony_equalizer_band_16000">16k</string>
+    <string name="sony_automatic_power_off_5_min">5 menit</string>
+    <string name="sony_automatic_power_off_30_min">30 menit</string>
+    <string name="sony_automatic_power_off_3_hour">3 jam</string>
+    <string name="sony_button_mode_off">Mati</string>
+    <string name="distance_format_meters">###m</string>
+    <string name="controlcenter_power_off_confirm_title">Matikan</string>
+    <string name="watchface_dialog_widget_color_white">Putih</string>
+    <string name="pref_title_equalizer_normal">Normal</string>
+    <string name="distance_format_miles">###.#mi</string>
+    <string name="turkish">Turki</string>
+    <string name="controlcenter_power_off_confirm_description">Apakah Anda yakin ingin mematikan perangkat\?</string>
+    <string name="sony_sound_position_front_right">Kanan Depan</string>
+    <string name="distance_format_kilometers">###.#km</string>
+    <string name="prefs_activity_in_device_card_title">Tampilkan info Aktivitas di kartu perangkat</string>
+    <string name="prefs_activity_in_device_card_steps_title_summary">Tampilkan jumlah langkah</string>
+    <string name="pref_title_equalizer_soft">Lembut</string>
+    <string name="prefs_dolby_mode">Mode Dolby</string>
+    <string name="controlcenter_power_off">Matikan</string>
+    <string name="prefs_activity_in_device_card_distance_title_summary">Jarak dihitung dari langkah dan panjang langkah (dapat disesuaikan di Pengaturan - Tentang Anda)</string>
+    <string name="distance_format_feet">###ft</string>
+    <string name="watchface_dialog_title_settings">Pengaturan tampilan jam</string>
+    <string name="pref_title_equalizer_bass_boost">Penguat bass</string>
+    <string name="pref_title_equalizer_dynamic">Dinamis</string>
+    <string name="sony_sound_position_rear_left">Kiri Belakang</string>
+    <string name="sony_automatic_power_off_1_hour">1 jam</string>
+    <string name="activity_prefs_gender">Jenis Kelamin</string>
+    <string name="activity_prefs_weight_kg">Berat dalam kg</string>
+    <string name="dutch">Belanda</string>
+    <string name="japanese">Jepang</string>
+    <string name="sony_sound_position_rear_right">Kanan Belakang</string>
+    <string name="pref_button_action_disabled">Nonaktif</string>
+    <string name="activity_prefs_year_birth">Tahun lahir</string>
+    <string name="portuguese">Portugis</string>
+    <string name="vietnamese">Vietnam</string>
+    <string name="czesh">Ceko</string>
+    <string name="arabic">Arab</string>
+    <string name="prefs_left">Kiri</string>
+    <string name="prefs_right">Kanan</string>
+    <string name="prefs_galaxy_touch_options">Opsi Sentuhan</string>
+    <string name="sony_surround_mode_off">Mati</string>
+    <string name="sony_surround_mode_arena">Arena</string>
+    <string name="sony_equalizer_band_6300">6.3k</string>
+    <string name="battery_case">Pelindung baterai</string>
+    <string name="left_earbud">Earbud kiri</string>
+    <string name="right_earbud">Earbud kanan</string>
+    <string name="minHR">Detak Jantung Min</string>
+    <string name="pref_title_opentracks_packagename">Nama paket OpenTracks</string>
+    <string name="pref_summary_notifications_ignore_low_priority">Jangan kirim notifikasi prioritas rendah dan minimum ke jam tangan</string>
+    <string name="abstract_chart_fragment_kind_not_worn">Tidak dipakai</string>
+    <string name="activity_summary_detail">Detail Aktivitas Olahraga</string>
+    <string name="unknown">Tidak diketahui</string>
+    <string name="installation_failed_">Pemasangan gagal</string>
+    <string name="minutes_30">30 menit</string>
+    <string name="activity_prefs_distance_meters">Sasaran sehari: jarak dalam meter</string>
+    <string name="activity_type_jump_roping">Tali Lompat</string>
+    <string name="menuitem_status">Status</string>
+    <string name="_unknown_">(tidak diketahui)</string>
+    <string name="updatefirmwareoperation_write_failed">Flash perangkat tegar gagal</string>
+    <string name="battery">Baterai</string>
+    <string name="no_limit">Tanpa batasan</string>
+    <string name="live_activity_start_your_activity">Mulai aktivitas Anda</string>
+    <string name="prefs_find_phone">Temukan ponsel</string>
+    <string name="miband2_prefs_dateformat">Format tanggal</string>
+    <string name="mi2_prefs_button_press_count_summary">Jumlah tekanan tombol untuk memicu Peristiwa 1. Jumlah tekanan tombol sama yang berlanjut akan membuat Peristiwa 2 dan seterusnya.</string>
+    <string name="mi3_prefs_night_mode">Mode malam</string>
+    <string name="activity_prefs_charts">Pengaturan bagan</string>
+    <string name="activity_prefs_activetime_minutes">Sasaran setiap hari: waktu aktif dalam menit</string>
+    <string name="companiondevice_pairing">Penyandingan CompanionDevice</string>
+    <string name="ignore_bonded_devices">Abaikan perangkat yang dijalin</string>
+    <string name="miband_prefs_fitness_goal">Sasaran langkah sehari</string>
+    <string name="pref_title_physical_buttons">Tombol fisik</string>
+    <string name="pref_header_sony_equalizer_bands">Band</string>
+    <string name="sony_button_mode_right">Mode Tombol (Kanan)</string>
+    <string name="devicetype_miscale2">Mi Scale 2</string>
+    <string name="menuitem_goal">Sasaran Aktivitas</string>
+    <string name="weeksleepchart_sleep_a_month">Tidur per bulan</string>
+    <string name="pref_title_chart_sleep_rolling_24_hour">Jangka tidur</string>
+    <string name="pref_chart_sleep_rolling_24_off">Siang sampai siang</string>
+    <string name="activity_prefs_chart_max_idle_phase_length">Panjang waktu untuk memisahkan aktivitas (menit)</string>
+    <string name="activity_prefs_chart_min_steps_per_minute_for_run">Langkah per menit minimal untuk mendeteksi lari</string>
+    <string name="activity_prefs_chart_min_session_length">Panjang aktivitas minimal (menit)</string>
+    <string name="activity_prefs_chart_min_steps_per_minute">Langkah per detik minimal untuk mendeteksi aktivitas</string>
+    <string name="activity_prefs_sleep_duration">Durasi tidur yang diinginkan dalam jam</string>
+    <string name="DEVINFO_HR_VER">"HR: "</string>
+    <string name="updatefirmwareoperation_update_in_progress">Sedang flash perangkat tegar</string>
+    <string name="error_creating_directory_for_logfiles">Kesalahan membuat direktori untuk berkas catatan: %1$s</string>
+    <string name="device_hw">Revisi perangkat keras: %1$s</string>
+    <string name="device_fw">Versi perangkat tegar: %1$s</string>
+    <string name="updatefirmwareoperation_firmware_not_sent">Firmware belum dikirim</string>
+    <string name="activity_db_management_autoexport_scheduled_yes">Ekspor otomatis telah dijadwalkan (sebelumnya) untuk %1$s</string>
+    <string name="Delete">Hapus</string>
+    <string name="activity_db_management_autoexport_enabled_no">Ekspor otomatis tidak diaktifkan.</string>
+    <string name="activity_db_management_autoexport_explanation">Lokasi ekspor otomatis basis data telah ditetapkan ke:</string>
+    <string name="pref_title_setup_bt_pairing">Aktifkan penyandingan Bluetooth</string>
+    <string name="pref_summary_setup_bt_pairing">Nonaktifkan ini jika Anda mengalami masalah menghubung</string>
+    <string name="miband2_prefs_timeformat">Mi2: Format waktu</string>
+    <string name="find_lost_device_you_found_it">Ketemu!</string>
+    <string name="find_device_you_found_it">Anda menemukannya!</string>
+    <string name="mi2_fw_installhandler_fw53_hint">Anda harus memasang versi %1$s sebelum memasang perangkat tegar ini!</string>
+    <string name="mi2_enable_text_notifications_summary">Membutuhkan perangkat tegar &gt;= 1.0.1.28 dan Mili_pro.ft terpasang.</string>
+    <string name="discovery_dont_pair">Jangan Sandingkan</string>
+    <string name="discovery_successfully_bonded">Dijalin ke %1$s.</string>
+    <string name="discovery_pair_title">Sandingkan dengan %1$s\?</string>
+    <string name="_pebble_watch_open_on_phone">Buka di perangkat Android</string>
+    <string name="activity_type_not_measured">Tidak diukur</string>
+    <string name="activity_type_deep_sleep">Tidur nyenyak</string>
+    <string name="activity_type_elliptical_trainer">Pelatih Elips</string>
+    <string name="activity_summary_detail_editing_gpx_track">Menyunting trek GPX yang tertaut</string>
+    <string name="activity_summary_yesterday">Kemarin</string>
+    <string name="activity_type_exercise">Olahraga</string>
+    <string name="reset_index">Atur ulang tanggal pendapatan</string>
+    <string name="kind_invalid">Data tidak absah</string>
+    <string name="kind_gps">Firmware GPS</string>
+    <string name="devicetype_miband3">Mi Band 3</string>
+    <string name="menuitem_weather">Cuaca</string>
+    <string name="devicetype_um25">UM-25</string>
+    <string name="devicetype_sony_wf_1000xm3">Sony WF-1000XM3</string>
+    <string name="menuitem_worldclock">Jam Dunia</string>
+    <string name="menuitem_dnd">Jangan ganggu</string>
+    <string name="hr_appname_commute">Perjalanan</string>
+    <string name="hr_appname_stopwatch">Stopwatch</string>
+    <string name="pref_title_upper_button_function_short">Tombol Atas pendek</string>
+    <string name="pref_title_lower_button_function_short">Tombol Bawah pendek</string>
+    <string name="pref_qhybrid_title_widget_draw_circles">Gambar lingkaran widget</string>
+    <string name="button_watchface_preview">Tampilkan pada jam tangan</string>
+    <string name="prefs_galaxy_buds_experimental">Eksperimental</string>
+    <string name="prefs_ambient_sound">Suara Sekitar</string>
+    <string name="pref_header_heartrate_alerts">Peringatan detak jantung</string>
+    <string name="pref_header_stress">Stres</string>
+    <string name="sony_anc_optimizer_status_analyzing">Menganalisa鈥�</string>
+    <string name="sony_anc_optimizer_status_atmospheric_pressure">Mengukur tekanan atmosfir鈥�</string>
+    <string name="pref_activity_recognition_mode_auto">otomatis</string>
+    <string name="watchface_dialog_pre_setting_position">posisi diatur dahulu ke %s</string>
+    <string name="controlcenter_add_new_folder">Tambahkan folder baru</string>
+    <string name="controlcenter_unset_folder">Batalkan pengaturan folder</string>
+    <string name="step_streak_ongoing">Berlangsung</string>
+    <string name="steps_streaks_total_steps_average_hint">Jumlah rata-rata %d langkah per hari</string>
+    <string name="activity_prefs_allow_bluetooth_intent_api">API Intent Bluetooth</string>
+    <string name="activity_filter_from_placeholder">masa lalu yang jauh</string>
+    <string name="averageSpeed">Kecepatan Rata-Rata</string>
+    <string name="set">Tetapkan</string>
+    <string name="devicetype_sony_wh_1000xm2">Sony WH-1000XM2</string>
+    <string name="device_requires_key">KUNCI DIBUTUHKAN, TEKAN LAMA UNTUK MEMASUKKAN</string>
+    <string name="fossil_hr_new_action">Tindakan baru</string>
+    <string name="pref_summary_canned_messages_set">Kirim pesan yang diatur di bawah ke perangkat Anda</string>
+    <string name="watchface_dialog_title_set_name">Tetapkan nama wajah jam</string>
+    <string name="sony_equalizer_preset_speech">Bicara</string>
+    <string name="watchface_setting_power_saving_hands">Nonaktifkan pergerakan tangan saat lepas dari pergelangan</string>
+    <string name="prefs_ambient_volume">Volume sekitar</string>
+    <string name="fw_upgrade_notice_miband7">Anda akan memasang perangkat tegar %s pada Xiaomi Smart Band 7 Anda. 
+\n 
+\nGelang Anda akan dinyalakan ulang setelah memasang berkas .zip. 
+\n 
+\nLANJUTKAN DENGAN RISIKO ANDA SENDIRI!</string>
+    <string name="open_fw_installer_pick_file">Pilih berkas</string>
+    <string name="open_fw_installer_getting_files_text">Karena kami tidak mungkin mendistribusikan berkas perangkat tegar, Anda harus mendapatkan berkasnya sendiri. Ini berarti Anda harus mencari untuk berkas dalam berkas APK, secara daring, dalam forum, di Amazfitwatchfaces (untuk perangkat Miband/Amazfit) dan lain-lain.</string>
+    <string name="pref_header_equalizer">Equalizer</string>
+    <string name="pref_title_notification_delay_calls">Jeda notifikasi panggilan</string>
+    <string name="pref_summary_notification_delay_calls">Jeda sebelum mengirimkan notifikasi telepon masuk ke perangkat, dalam detik.</string>
+    <string name="pref_summary_pebble_forceuntested">Aktifkan fitur yang belum dicoba. KETAHUI APA YANG ANDA LAKUKAN!</string>
+    <string name="pref_title_pebble_forceuntested">Aktifkan fitur yang belum dicoba</string>
+    <string name="pref_title_pebble_always_ack_pebblekit">Prematur ACK PebbleKit</string>
+    <string name="pref_display_add_device_fab">Tombol hubungkan perangkat baru</string>
+    <string name="pref_display_add_device_fab_on">Selalu terlihat</string>
+    <string name="power_mode_title">Mode daya jam tangan</string>
+    <string name="power_mode_watch">Hanya jam tangan</string>
+    <string name="pref_workout_send_gps_summary">Kirim lokasi GPS saat ini ke gelang ketika olahraga</string>
+    <string name="installing_binary_d_d">Memasang biner %1$d/%2$d</string>
+    <string name="pref_screen_notification_profile_event_reminder">Pengingat acara</string>
+    <string name="spo2_perc_85">85%</string>
+    <string name="spo2_perc_90">90%</string>
+    <string name="spo2_off">Nonaktif</string>
+    <string name="prefs_relaxation_reminder_description">Getar gelang untuk memberi tahu Anda jika nilai stres lebih dari 80</string>
+    <string name="prefs_relaxation_reminder_title">Pengingat beristirahat</string>
+    <string name="prefs_spo2_monitoring_title">Pemantauan Oksigen Darah</string>
+    <string name="seconds_8">8 detik</string>
+    <string name="seconds_6">6 detik</string>
+    <string name="seconds_7">7 detik</string>
+    <string name="seconds_9">9 detik</string>
+    <string name="seconds_15">15 detik</string>
+    <string name="seconds_20">20 detik</string>
+    <string name="you_did_not_sleep">Anda tidak tidur</string>
+    <string name="mi2_prefs_button_press_count_max_delay_summary">Jeda maksimum antara penekanan tombol dalam milidetik</string>
+    <string name="prefs_always_on_display_summary">Tetap nyalakan tampilan gelang</string>
+    <string name="prefs_password_4_digits_1_to_4_summary">Kata sandi harus berisi 4 digit, menggunakan nomor 1 sampai 4</string>
+    <string name="hungarian">Hongaria</string>
+    <string name="dbmanagementactivity_error_importing_shared">Kesalahan mengimpor preferensi: %1$s</string>
+    <string name="activity_DB_ExportButton">Ekspor Data</string>
+    <string name="discovery_trying_to_connect_to">Mencoba menghubungkan ke: %1$s</string>
+    <string name="_pebble_watch_mute">Bisukan</string>
+    <string name="kind_font">Fon</string>
+    <string name="menuitem_notifications">Notifikasi</string>
+    <string name="hr_widget_steps">Langkah</string>
+    <string name="qhybrid_pref_summary_external_intents">Memungkinkan aplikasi Android lain untuk unggah/timpa berkas</string>
+    <string name="watchface_dialog_widget_cat_position">Posisi</string>
+    <string name="pref_header_heartrate_sleep">Tidur</string>
+    <string name="pref_header_heartrate_allday">Pemantauan seharian</string>
+    <string name="menuitem_menu">Menu</string>
+    <string name="step_streak_total">Jumlah</string>
+    <string name="prefs_hourly_chime">Bunyian setiap jam</string>
+    <string name="no_data">Tidak ada data</string>
+    <string name="prefs_fm_preset_instructions">Tekan lama pada tombol untuk menyimpan prasetel</string>
+    <string name="save_configuration">Simpan Konfigurasi</string>
+    <string name="widget_listing_label">Status dan Alarm</string>
+    <string name="widget_5_minutes">5 menit</string>
+    <string name="widget_20_minutes">20 menit</string>
+    <string name="widget_1_hour">1 jam</string>
+    <string name="qhybrid_buttons_overwrite_success">Tombol ditimpa</string>
+    <string name="error_exporting_device_preferences">Kesalahan mengekspor preferensi spesifik perangkat</string>
+    <string name="error_location_enabled_mandatory">Lokasi harus dinyalakan untuk memindai perangkat</string>
+    <string name="descentDistance">Jarak turun</string>
+    <string name="seconds">dtk</string>
+    <string name="devicetype_nut_mini">Nut mini</string>
+    <string name="menuitem_settings">Pengaturan</string>
+    <string name="prefs_button_double_press_action_selection_title">Tindakan Peristiwa 2</string>
+    <string name="prefs_events_forwarding_startnonwear">Pada Tidak Memakai</string>
+    <string name="pref_header_notification_application_settings">Pengaturan per aplikasi</string>
+    <string name="pref_title_weather">Cuaca</string>
+    <string name="pref_title_canned_replies">Balasan</string>
+    <string name="dbmanagementactivity_delete_activity_data_title">Hapus Data Aktivitas\?</string>
+    <string name="activity_DB_empty_button">Kosongkan Basis Data</string>
+    <string name="watch9_time_minutes">Menit:</string>
+    <string name="edittext_notification_filter_words_hint">Masukkan kata yang diinginkan, baris baru untuk setiap kata</string>
+    <string name="toast_app_must_not_be_selected">Aplikasi harus tidak dipilih untuk diatur</string>
+    <string name="watchface_setting_power_saving_display">Nonaktifkan pembaruan tampilan saat gelang mati</string>
+    <string name="pref_title_touch_volume">Volume</string>
+    <string name="activity_db_management_autoexport_label">Ekspor Otomatis</string>
+    <string name="error_background_service">Gagal memulai layanan latar belakang</string>
+    <string name="activity_filter_apply_filter">Terapkan Saringan</string>
+    <string name="addto_filter">Tambahkan ke saringan</string>
+    <string name="pref_workout_start_on_phone_title">Pelacakan aplikasi kebugaran</string>
+    <string name="pref_workout_start_on_phone_summary">Mulai/hentikan pelacakan aplikasi kebugaran pada ponsel ketika sebuah olahraga GPS dimulai dari gelang</string>
+    <string name="pref_auto_fetch_limit_fetches_summary">Mendapatkan setiap %d menit</string>
+    <string name="appinstaller_install">Pasang</string>
+    <string name="discovery_note">Catatan:</string>
+    <string name="alarm_smart_wakeup">Pembangunan pintar</string>
+    <string name="chart_no_active_data">Tidak ada aktivitas yang dideteksi.</string>
+    <string name="user_feedback_miband_set_alarms_ok">Alarm dikirim ke perangkat.</string>
+    <string name="live_activity_current_steps_per_minute">Langkah/mnt saat ini</string>
+    <string name="lefun_prefs_antilost_summary">Gelang akan bergetar ketika ponsel Anda terputus dari gelang</string>
+    <string name="dbmanagementactivity_overwrite_database_confirmation">Benar-benar timpa data saat ini? Semua data aktivitas, perangkat, dan preferensi saat ini (jika ada) akan ditimpa.</string>
+    <string name="dbmanagementactivity_old_activity_db_successfully_deleted">Data aktivitas lawas dihapus.</string>
+    <string name="StringUtils_sender">(%1$s)</string>
+    <string name="activity_type_biking">Bersepeda</string>
+    <string name="activity_type_treadmill">Treadmill</string>
+    <string name="activity_type_yoga">Yoga</string>
+    <string name="select_all">Pilih semua</string>
+    <string name="fossil_hr_warning_firmware_too_new">Beberapa fungsi dinonaktifkan karena perangkat tegar jam tangan terlalu baru</string>
+    <string name="qhybrid_title_watchface">Konfigurasi wajah jam</string>
+    <string name="prefs_autoheartrate">Detak Jantung Otomatis</string>
+    <string name="enable_vibrations_summary">Getaran untuk panggilan, pesan, dan lain-lain</string>
+    <string name="prefs_seamless_connection_switch_summary">Mengubah bud antara perangkat yang disandingkan secara otomatis</string>
+    <string name="prefs_seamless_connection_switch_title">Perubahan koneksi mulus</string>
+    <string name="prefs_game_mode_summary">Hanya jika ponsel Anda mendukung mode permainan</string>
+    <string name="prefs_ambient_voice_summary">Buat suara menonjol</string>
+    <string name="sony_equalizer_clear_bass">Bass Jelas</string>
+    <string name="sony_audio_upsampling">Upsampling Audio</string>
+    <string name="pref_media_volumedown">Volume Bawah</string>
+    <string name="steps_streaks_total_steps_hint_totals">Jumlah langkah yang telah direkam</string>
+    <string name="step_streak_average_steps_hint">Langkah per hari runtunan rata-rata</string>
+    <string name="share_log_not_enabled_message">Anda harus mengaktifkan pencatatan dahulu di Pengaturan - Tulis berkas catatan</string>
+    <string name="totalStride">Jumlah langkah</string>
+    <string name="bpm">bpm</string>
+    <string name="Strokes">Gaya</string>
+    <string name="spm">langkah/mnt</string>
+    <string name="Swimming">Berenang</string>
+    <string name="Elevation">Ketinggian</string>
+    <string name="sports_activity_quick_filter_7days">7 hari</string>
+    <string name="prefs_events_forwarding_summary">Gunakan peristiwa perangkat untuk memicu tindakan dan siaran Android</string>
+    <string name="prefs_events_forwarding_title">Tindakan perangkat</string>
+    <string name="prefs_events_forwarding_action_title">Jalankan tindakan</string>
+    <string name="prefs_fake_ring_duration">Dering berkelanjutan buatan</string>
+    <string name="watchface_dialog_title_widget">Pengaturan widget</string>
+    <string name="prefs_notifications_enable">Aktifkan notifikasi</string>
+    <string name="pref_header_sony_equalizer_preset_custom_1">Prasetel Kustom 1</string>
+    <string name="fossil_hr_unavailable_unauthed">Tidak tersedia dalam mode tidak terotentikasi</string>
+    <string name="pref_theme_black_background">Gunakan latar belakang hitam di Tema Gelap</string>
+    <string name="activity_error_no_app_for_gpx">Untuk melihat jejak aktivitas, pasang aplikasi yang dapat menangani berkas GPX.</string>
+    <string name="pref_header_notifications_and_calls">Notifikasi dan Panggilan</string>
+    <string name="pref_title_device_spec_settings_force_time">Paksa sinkron waktu</string>
+    <string name="devicetype_sg2">Lemfo SG2</string>
+    <string name="qhybrid_changes_delay_prompt">perubahan mungkin membutuhkan beberapa detik鈥�</string>
+    <string name="prefs_button_triple_press_action_selection_title">Tindakan Peristiwa 3</string>
+    <string name="error_setting_alias">Kesalahan menetapkan alias:</string>
+    <string name="activity_db_management_exportimport_label">Ekspor dan Impor</string>
+    <string name="prefs_pressure_relief">Legakan tekanan dengan suara sekitar</string>
+    <string name="devicetype_banglejs">Bangle.js</string>
+    <string name="appwidget_text">Zzz</string>
+    <string name="descentSeconds">Turun</string>
+    <string name="fossil_hr_commute_processing">Mengolah permintaan鈥�</string>
+    <string name="pref_summary_developer_settings">Pengaturan dan kemampuan digunakan oleh pengembang</string>
+    <string name="pref_title_banglejs_webview_url">URL pemuat aplikasi</string>
+    <string name="about_description_banglejs_main">Aplikasi pendamping Android untuk Bangle.js dibuat di atas proyek Gadgetbridge, dengan tambahan akses internet. 
+\n 
+\nKarena kebijakan Google Play Store, kami tidak diperbolehkan untuk menambahkan tautan donasi dalam aplikasi sendiri, tetapi jika Anda suka aplikasi ini pertimbangkan untuk berdonasi melalui laman beranda Gadgetbridge di bawah.</string>
+    <string name="pref_summary_sunrise_sunset">Kirim waktu matahari terbit dan matahari terbenam berdasarkan lokasi ke linimasa Pebble</string>
+    <string name="pref_title_sync_caldendar">Sinkron acara kalender</string>
+    <string name="pref_summary_relax_firmware_checks">Nonaktifkan pemeriksaan perangkat tegar</string>
+    <string name="cannot_connect_bt_address_invalid_">Tidak dapat menyambung. Alamat Bluetooth tidak absah\?</string>
+    <string name="prefs_enable_find_phone">Aktifkan \'Temukan perangkat\'</string>
+    <string name="mi2_prefs_do_not_disturb">Jangan Ganggu</string>
+    <string name="mi2_prefs_heart_rate_monitoring">Pemantauan Detak Jantung</string>
+    <string name="mi3_prefs_band_screen_unlock">Pembukaan layar gelang</string>
+    <string name="prefs_activate_display_on_lift_sensitivity">Sensitivitas</string>
+    <string name="activity_DB_test_export_message">Mengekspor basis data鈥�</string>
+    <string name="activity_db_management_empty_DB">Kosongkan Basis Data</string>
+    <string name="start">Mulai</string>
+    <string name="timeformat_24h">24J</string>
+    <string name="devicetype_y5">Y5</string>
+    <string name="qhybrid_offset_timezone">geser zona waktu dengan</string>
+    <string name="notification_channel_low_battery_name">Baterai lemah</string>
+    <string name="notification_channel_gps">Pelacakan GPS</string>
+    <string name="menuitem_workout">Olahraga</string>
+    <string name="menuitem_hr">Detak Jantung</string>
+    <string name="menuitem_spo2">SpO2</string>
+    <string name="menuitem_eventreminder">Pengingat Acara</string>
+    <string name="menuitem_pomodoro">Pelacak Pomodoro</string>
+    <string name="pref_title_middle_button_function_short">Tombol Tengan pendek</string>
+    <string name="aerobicTrainingEffect">Efek Aerobik</string>
+    <string name="anaerobicTrainingEffect">Efek Anaerobik</string>
+    <string name="qhybrid_watchface_configuration_old_firmware">Layar pengaturan wajah jam untuk jam tangan dengan versi perangkat tegar DN1.0.2.19r dan lebih rendah</string>
+    <string name="watchface_setting_wrist_flick_hour">Tangan jam (-360 ke 360)</string>
+    <string name="prefs_ambient_mode">Mode Sekitar</string>
+    <string name="prefs_touch_lock_summary">Nonaktifkan peristiwa sentuhan</string>
+    <string name="prefs_active_noise_cancelling_level_high">Tinggi</string>
+    <string name="pref_ambient_sound_tone">Nada Suara Sekitar</string>
+    <string name="pref_ambient_sound_tone_summary">Dari Lembut ke Jelas</string>
+    <string name="sony_anc_optimizer_status_not_running">Tidak Berjalan</string>
+    <string name="auto_reconnect_ble_title">Hubungkan ulang ke perangkat secara otomatis</string>
+    <string name="supercars_lights_blinking_label">Berkedip</string>
+    <string name="devicetype_super_cars">Shell Racing</string>
+    <string name="activity_prefs_summary_allow_bluetooth_intent_api">Memperbolehkan mengendalikan koneksi Bluetooth melalui API Intent</string>
+    <string name="accuracy">Akurasi</string>
+    <string name="balanced">Seimbang</string>
+    <string name="power_saving">Penghemat Daya</string>
+    <string name="single_band">Jalur Tunggal</string>
+    <string name="low_power_gps">GPS Daya Rendah</string>
+    <string name="gps_gnolass">GPS + GLONASS</string>
+    <string name="all_satellites">Semua Satelit</string>
+    <string name="speed_first">Kecepatan Terlebih Dahulu</string>
+    <string name="accuracy_first">Akurasi Terlebih Dahulu</string>
+    <string name="pref_header_sound_vibration">Suara &amp; Getaran</string>
+    <string name="pref_header_offline_voice">Suara Luring</string>
+    <string name="pref_title_banglejs_text_bitmap">Teks sebagai Bitmap</string>
+    <string name="pref_summary_banglejs_text_bitmap">Jika sebuah kata tidak dapat ditampilkan dengan fon jam tangan, tampilkan sebagai bitmap dalam Gadgetbridge dan tampilkan bitmap pada jam tangan</string>
+    <string name="pref_rtl_max_line_length">Panjang Baris Kanan ke Kiri Maks</string>
+    <string name="pref_title_screen_on_on_notifications">Layar Nyala pada Notifikasi</string>
+    <string name="pref_summary_screen_on_on_notifications">Nyalakan layar gelang ketika sebuah notifikasi datang</string>
+    <string name="pref_title_location_keep_uptodate">Tetap perbarui lokasi</string>
+    <string name="toast_enable_networklocationprovider">Mohon aktifkan lokasi jaringan</string>
+    <string name="pref_summary_pebble_always_ack_pebblekit">Akan menyebabkan pesan yang dikirim ke aplikasi pihak ketiga eksternal diakui selalu dan segera</string>
+    <string name="pref_title_pebble_enable_bgjs">Aktifkan JS latar belakang</string>
+    <string name="pref_title_expose_hr">Akses detak jantung waktu nyata pihak ketiga</string>
+    <string name="pref_summary_pebble_enable_bgjs">Saat diaktifkan, memungkinkan muka jam untuk menampilkan cuaca, info baterai, dll.</string>
+    <string name="pref_title_use_custom_font">Gunakan fon kustom</string>
+    <string name="pref_summary_use_custom_font">Aktifkan ini jika perangkat Anda memiliki perangkat tegar fon kustom untuk dukungan emoji</string>
+    <string name="pref_title_allow_high_mtu">Perbolehkan MTU tinggi</string>
+    <string name="pref_summary_allow_high_mtu">Meningkatkan kecepatan transfer, tetapi mungkin tidak bekerja pada beberapa perangkat Android.</string>
+    <string name="pref_title_huami_force_new_protocol">Protokol Otentikasi Baru</string>
+    <string name="pref_summary_huami_force_new_protocol">Aktifkan jika perangkat Anda tidak terhubung setelah sebuah pembaruan perangkat tegar</string>
+    <string name="power_mode_saving">Penghemat daya</string>
+    <string name="prefs_sensors_button_bp_calibration_sum">Tekan di sini untuk memulai kalibrasi</string>
+    <string name="zetime_handmove_display_summary">Putar pergelangan tangan Anda untuk mengaktifkan atau menonaktifkan layar.</string>
+    <string name="pref_agps_header">AGPS</string>
+    <string name="pref_sleep_mode_title">Mode Tidur</string>
+    <string name="right">Kanan</string>
+    <string name="pref_screen_notification_profile_anti_loss">Peringatan anti hilang</string>
+    <string name="pref_screen_notification_profile_schedule">Jadwal</string>
+    <string name="prefs_heartrate_alert_threshold">Ambang peringatan detak jantung</string>
+    <string name="chart_steps">Langkah</string>
+    <string name="calories">Kalori</string>
+    <string name="blood_pressure">Tekanan darah</string>
+    <string name="getting_heart_rate">Mengukur</string>
+    <string name="pref_enable_unsupported_settings_title">Aktifkan pengaturan yang tidak didukung</string>
+    <string name="pref_enable_unsupported_settings_summary">Ini akan mengaktifkan akses ke semua pengaturan yang tersedia, bahkan jika tidak didukung oleh perangkat. Ini bisa menjadi tidak stabil dan mogok pada perangkat.</string>
+    <string name="abstract_chart_fragment_kind_rem_sleep">Tidur REM</string>
+    <string name="user_feedback_all_alarms_disabled">Semua alarm dinonaktifkan</string>
+    <string name="you_slept">%1$s - %2$s</string>
+    <string name="prefs_find_phone_summary">Gunakan gelang Anda untuk memainkan bunyi ponsel Anda.</string>
+    <string name="mi2_prefs_display_items_summary">Pilih item untuk ditampilkan pada layar gelang</string>
+    <string name="prefs_password_6_digits_0_to_9_summary">Kata sandi harus memiliki 6 digit, dengan hanya nomor</string>
+    <string name="prefs_password_enabled">Kata Sandi Diaktifkan</string>
+    <string name="mi2_prefs_do_not_disturb_lift_wrist">Aktifkan layar pada pengangkatan saat Jangan Ganggu</string>
+    <string name="pref_header_charts">Pengaturan Bagan</string>
+    <string name="activity_prefs_target_weight_kg">Sasaran berat dalam kg</string>
+    <string name="pref_title_charts_average">Tampilkan rata-rata dalam bagan</string>
+    <string name="pref_title_charts_swipe">Aktifkan usapan kiri/kanan dalam bagan aktivitas</string>
+    <string name="pref_title_charts_range">Jangka Bagan</string>
+    <string name="activity_prefs_chart_min_heart_rate">Detak jantung min</string>
+    <string name="pref_chart_sleep_lines_limit">Berapa garis sesi tidur untuk ditampilkan sebelum menggulirnya</string>
+    <string name="pref_summary_pebble_health_store_raw">Menyimpan data dengan adanya, meningkatkan penggunaan basis data untuk memperbolehkan interpretasi nanti.</string>
+    <string name="Cancel">Batalkan</string>
+    <string name="ok">Oke</string>
+    <string name="activity_data_management_directory_content_title">Ekspor/Impor konten direktori</string>
+    <string name="activity_type_walking">Berjalan</string>
+    <string name="activity_type_indoor_walking">Berjalan Dalam Ruangan</string>
+    <string name="activity_type_cricket">Kriket</string>
+    <string name="activity_type_rowing_machine">Mesin dayung</string>
+    <string name="activity_type_outdoor_cycling">Bersepeda Luar Ruangan</string>
+    <string name="activity_type_pool_swimming">Berenang Kolam</string>
+    <string name="activity_summary_detail_select_gpx_track">Pilih trek GPX</string>
+    <string name="devicetype_amazfit_gtr3">Amazfit GTR 3</string>
+    <string name="devicetype_miband4">Mi Band 4</string>
+    <string name="devicetype_miband5">Mi Band 5</string>
+    <string name="menuitem_alarm">Alarm</string>
+    <string name="devicetype_tlw64">TLW64</string>
+    <string name="devicetype_zepp_e">Zepp E</string>
+    <string name="devicetype_amazfit_x">Amazfit X</string>
+    <string name="menuitem_compass">Kompas</string>
+    <string name="menuitem_music">Musik</string>
+    <string name="menuitem_more">Lain</string>
+    <string name="preferences_qhybrid_settings">Pengaturan Q Hybrid</string>
+    <string name="menuitem_sun_moon">Matahari &amp; Bulan</string>
+    <string name="menuitem_voice_memos">Memo Suara</string>
+    <string name="menuitem_brightness">Kecerahan</string>
+    <string name="menuitem_bluetooth">Bluetooth</string>
+    <string name="menuitem_wifi">Wi-Fi</string>
+    <string name="menuitem_screen_always_lit">Layar Selalu Nyala</string>
+    <string name="menuitem_volume">Volume</string>
+    <string name="toast_notification_filter_saved_successfully">Saringan notifikasi disimpan</string>
+    <string name="language_and_region_prefs">Pengaturan bahasa dan wilayah</string>
+    <string name="hr_widget_date">Tanggal</string>
+    <string name="hr_widget_heart_rate">Detak jantung</string>
+    <string name="qhybrid_offset_time_by">geser waktu dengan</string>
+    <string name="qhybrid_use_activity_hand_as_notification_counter">gunakan tangan aktivitas sebagai penghitung notifikasi</string>
+    <string name="companiondevice_pairing_details">Mengaktifkan dukungan API CompanionDevice baru (hanya ada efek di Android 8 dan lebih) yang akan meningkatkan keandalan ketika perangkat harus dimulai ulang di latar belakang, membutuhkan menyandingan ulang ke Gadgetbridge untuk diterapkan</string>
+    <string name="laps_unit">putaran</string>
+    <string name="qhybrid_calibration_counterclockwise">Berlawanan arah jarum jam</string>
+    <string name="watchface_setting_button_toggle_backlight">Nyalakan lampu belakang</string>
+    <string name="watchface_setting_button_toggle_widgets">Alih widget</string>
+    <string name="watchface_setting_button_move_hands">Gerakkan tangan</string>
+    <string name="prefs_autoheartrate_measurement">Pengukuran Detak Jantung otomatis</string>
+    <string name="prefs_noise_control">Kendali bunyi</string>
+    <string name="prefs_voice_detect">Deteksi suara</string>
+    <string name="prefs_double_tap_edge">Ketuk dua kali pada ujung</string>
+    <string name="sony_anc_optimize_title">Optimalkan</string>
+    <string name="sony_anc_optimizer_status_starting">Memulai鈥�</string>
+    <string name="sony_speak_to_chat_sensitivity_high">Tinggi</string>
+    <string name="sony_speak_to_chat_sensitivity_standard">Standar</string>
+    <string name="sony_speak_to_chat_timeout_off">Mati</string>
+    <string name="sony_speak_to_chat_timeout_short">Pendek (15s)</string>
+    <string name="sony_automatic_power_off_off">Jangan matikan</string>
+    <string name="sony_button_mode_playback_control">Kendali Pemutaran</string>
+    <string name="sony_automatic_power_off_when_taken_off">Ketika dimatikan</string>
+    <string name="pref_screen_auto_brightness_summary">Atur kecerahan layar berdasarkan cahaya sekitar</string>
+    <string name="pref_screen_auto_brightness_title">Kecerahan Otomatis</string>
+    <string name="watchface_widget_type_custom">Widget kustom</string>
+    <string name="pref_screen_brightness">Kecerahan Layar</string>
+    <string name="watchface_dialog_widget_timezone_duration">Durasi keterlihatan jam (dalam detik)</string>
+    <string name="watchface_dialog_widget_timezone">Zona Waktu</string>
+    <string name="watchface_dialog_widget_update_timeout">Waktu habis pembaruan dalam menit</string>
+    <string name="pref_activity_recognize_biking">mengenal bersepeda</string>
+    <string name="pref_device_action_fitness_app_control_stop">Berhenti Pelacakan Aplikasi Kesehatan</string>
+    <string name="pref_activity_recognize_rowing">mengenal mendayung</string>
+    <string name="pref_activity_recognition_mode_none">tidak ada</string>
+    <string name="pref_activity_recognition_mode_ask">tanya</string>
+    <string name="controlcenter_set_parent_folder">Tetapkan sebagai folder induk</string>
+    <string name="controlcenter_connected_fraction">Terhubung: %d/%d</string>
+    <string name="autoconnect_from_device_summary">Lakukan sebuah hubungan ketika hubungan dilakukan oleh perangkat, seperti headphone</string>
+    <string name="steps_streaks_hint">Serangkaian hari berturut-turut tanpa gangguan dengan tujuan langkah tercapai</string>
+    <string name="steps_streaks_since_date">Sejak %s</string>
+    <string name="steps_streaks_total_days_hint_totals">Persentase hari dengan sasaran yang dicapai dalam relasi semua hari dengan langkah</string>
+    <string name="step_streaks_achievements_sharing_message">Pencapaian langkah seharian saya!</string>
+    <string name="prefs_hourly_chime_summary">Jam tangan akan berbunyi setiap jam</string>
+    <string name="devicetype_flipper_zero">Flipper zero</string>
+    <string name="pref_header_authentication">Otentikasi</string>
+    <string name="pref_title_pebble_activitytracker">Pelacak aktivitas yang disukai</string>
+    <string name="pref_pebble_privacy_mode_off">Notifikasi biasa</string>
+    <string name="pref_header_location">Lokasi</string>
+    <string name="pref_title_pebble_forcele">Selalu gunakan BLE</string>
+    <string name="pref_summary_location_keep_uptodate">Coba dapatkan lokasi saat ini saat berjalan, gunakan lokasi yang disimpan sebagai cadangan</string>
+    <string name="pref_summary_pebble_enable_applogs">Akan membuat catatan dari aplikasi jam tangan untuk dicatat oleh Gadgetbridge (membutuhkan koneksi ulang)</string>
+    <string name="pref_title_pebble_gatt_clientonly">Hanya klien GATT</string>
+    <string name="pref_title_screentime">Layar pada durasi</string>
+    <string name="preferences_hplus_settings">Pengaturan HPlus/Makibes</string>
+    <string name="pref_notifications_and_calls_enable_misscall">Beri tahu untuk panggilan tidak terjawab</string>
+    <string name="activity_prefs_alarm_min_heart_rate">Detak jantung min</string>
+    <string name="zetime_title_heart_rate_alarm_summary">Jam tangan akan memperingati Anda ketika detak jantung Anda melebihi batasan.</string>
+    <string name="zetime_heart_rate_alarm_enable">Aktifkan alarm detak jantung</string>
+    <string name="pref_title_auto_export_location">Ekspor lokasi</string>
+    <string name="not_connected">Tidak terhubung</string>
+    <string name="firmware_install_warning">ANDA MENCOBA UNTUK MEMASANG SEBUAH FIRMWARE, LANJUTKAN DENGAN RISIKO ANDA SENDIRI. 
+\n 
+\n 
+\n Firmware ini untuk Revisi Perangkat Keras: %s</string>
+    <string name="appversion_by_creator">%1$s oleh %2$s</string>
+    <string name="pref_header_vibration_count">Jumlah getaran</string>
+    <string name="watch9_pairing_tap_hint">Jika jam tangan Anda bergetar, guncang perangkat atau tekan tombolnya.</string>
+    <string name="pref_screen_vibration_profile">Profil getaran</string>
+    <string name="pref_screen_notification_profile_incoming_call">Notifikasi panggilan masuk</string>
+    <string name="pref_screen_notification_idle_alerts">Peringatan Menganggur</string>
+    <string name="interval_five_minutes">setiap 5 menit</string>
+    <string name="heartrate_bpm_40">40 bpm</string>
+    <string name="title_activity_charts">Aktivitas dan Tidur</string>
+    <string name="title_activity_set_alarm">Atur alarm</string>
+    <string name="pbwinstallhandler_incorrect_hw_revision">Revisi perangkat keras tidak cocok!</string>
+    <string name="updatefirmwareoperation_updateproblem_do_not_reboot">Terjadi masalah dengan transfer perangkat tegar. JANGAN MULAI ULANG Mi Band Anda!</string>
+    <string name="updatefirmwareoperation_metadata_updateproblem">Masalah dengan transfer metadata perangkat tegar</string>
+    <string name="clock">Jam</string>
+    <string name="heart_rate">Detak jantung</string>
+    <string name="minutes_5">5 menit</string>
+    <string name="minutes_10">10 menit</string>
+    <string name="liveactivity_live_activity">Aktivitas langsung</string>
+    <string name="weeksteps_today_steps_description">Langkah hari ini, sasaran: %1$s</string>
+    <string name="prefs_find_phone_duration">Durasi bunyi dalam detik</string>
+    <string name="mi2_prefs_rotate_wrist_to_switch_info">Putar pergelangan tangan untuk mengubah info</string>
+    <string name="waiting_for_reconnect">Menunggu untuk dihubungkan ulang</string>
+    <string name="discovery_attempting_to_pair">Mencoba menyanding dengan %1$s</string>
+    <string name="_pebble_watch_reply">Balas</string>
+    <string name="devicetype_vibratissimo">Vibratissimo</string>
+    <string name="devicetype_amazfit_bips">Amazfit Bip S</string>
+    <string name="devicetype_amazfit_cor">Amazfit Cor</string>
+    <string name="devicetype_roidmi3">Roidmi 3</string>
+    <string name="notification_channel_transfer_name">Transfer data</string>
+    <string name="devicetype_amazfit_cor2">Amazfit Cor 2</string>
+    <string name="devicetype_amazfit_bips_lite">Amazfit Bip S Lite</string>
+    <string name="menuitem_stopwatch">Stopwatch</string>
+    <string name="menuitem_events">Acara</string>
+    <string name="menuitem_barometer">Barometer</string>
+    <string name="share_log">Bagikan catatan</string>
+    <string name="title_activity_watch9_calibration">Kalibrasi Watch 9</string>
+    <string name="pref_title_contextual_arabic">Arab secara konteks</string>
+    <string name="menuitem_email">Surel</string>
+    <string name="prefs_fm_presets_presets">Prasetel</string>
+    <string name="toast_notification_filter_words_empty_hint">Masukkan setidaknya satu kata</string>
+    <string name="toast_app_must_be_selected">Aplikasi harus dipilih untuk diatur</string>
+    <string name="title_activity_notification_filter">Saringan Notifikasi</string>
+    <string name="filter_submode_all">Semua kata</string>
+    <string name="filter_mode_whitelist">Tampilkan ketika berisi kata</string>
+    <string name="qhybrid_buttons_overwrite_error">Kesalahan menimpa tombol</string>
+    <string name="qhybrid_vibration_strength">kekuatan getaran:</string>
+    <string name="qhybrid_prompt_million_steps">Mohon tetapkan hitungan langkah ke jutaan untuk mengaktifkan itu.</string>
+    <string name="qhybrid_time_shift">pergeseran waktu</string>
+    <string name="watch_not_connected">Jam tangan tidak terhubung</string>
+    <string name="about_additional_device_support">Dukungan perangkat tambahan</string>
+    <string name="pref_qhybrid_save_raw_activity_files">Simpan berkas aktivitas mentah</string>
+    <string name="gpx_receiver_overwrite_some_files">Beberapa berkas sudah ada. Timpa\?</string>
+    <string name="error_no_location_access">Akses lokasi harus diberikan dan diaktifkan untuk pemindaian untuk bekerja dengan benar</string>
+    <string name="error_retrieving_devices_database">Kesalahan mendapatkan perangkat dari basis data</string>
+    <string name="ignore_bonded_devices_description">Mengaktifkan opsi ini akan mengabaikan perangkat yang telah dijalin/disandingkan ketika memindai</string>
+    <string name="sports_activity_confirm_delete_title">Hapus %d aktivitas</string>
+    <string name="sports_activity_confirm_delete_description">Apakah Anda yakin ingin menghapus %d aktivitas\?</string>
+    <string name="watchface_dialog_widget_background">Latar belakang</string>
+    <string name="hybridhr_widget_bg_thin_circle">Lingkaran tipis</string>
+    <string name="watchface_widget_type_2nd_tz">Zona waktu kedua</string>
+    <string name="enable_notifications_summary">Notifikasi untuk panggilan, pesan, dan lain-lain</string>
+    <string name="prefs_autoheartrate_summary">Pengukuran Detak Jantung berkala saat pagi hari dan saat tidur</string>
+    <string name="prefs_autoheartrate_interval">Frekuensi pengukuran</string>
+    <string name="devicetype_galaxybuds_pro">Galaxy Buds Pro</string>
+    <string name="prefs_in_ear_detection_summary">Mainkan panggilan melalui earbud Anda ketika berada di telinga Anda</string>
+    <string name="prefs_customize_ambient_sound_summary">Ubah Suara Sekitar</string>
+    <string name="prefs_switch_control_left">Ubah kendali kiri</string>
+    <string name="pref_title_touch_ambient">Suara Sekitar</string>
+    <string name="prefs_ambient_settings_title">Opsi Suara Sekitar</string>
+    <string name="prefs_ambient_sound_during_call_title">Suara Sekitar saat panggilan</string>
+    <string name="prefs_active_noise_cancelling_summary">Blokir suara sekitar</string>
+    <string name="prefs_ambient_sound_during_call_summary">Dengar suara sendiri dengan jelas saat panggilan</string>
+    <string name="pref_header_sony_anc_optimizer">Pengoptimal Pembatalan Suara</string>
+    <string name="sony_anc_optimize_description">Klik untuk memulai optimasi pembatalan suara.</string>
+    <string name="sony_touch_sensor">Kendali sensor sentuhan</string>
+    <string name="sony_speak_to_chat_sensitivity">Sensitivitas Deteksi Suara</string>
+    <string name="pref_device_action_fitness_app_control_start">Mulai Pelacakan Aplikasi Kesehatan</string>
+    <string name="pref_device_action_phone_gps_location_listener_stop">Berhenti Pendengar Lokasi GPS</string>
+    <string name="devicetype_vesc">VESC</string>
+    <string name="devicetype_bose_qc35">Bose QC35</string>
+    <string name="pref_media_forward">Lewati berikutnya</string>
+    <string name="pref_media_rewind">Lewati sebelumnya</string>
+    <string name="watchface_dialog_widget_timeout_hide_text">Sembunyikan teks saat waktu habis</string>
+    <string name="watchface_dialog_widget_timeout_show_circle">Tampilkan lingkaran saat waktu habis</string>
+    <string name="fossil_hr_button_config_info">Beberapa tombol tidak dapat diatur karena kemampuan mereka dikode secara keras dalam perangkat tegar. 
+\n 
+\nPeringatan: menekan lama pada tombol atas ketika sebuah wajah jam dari aplikasi Fossil resmi terpasang akan juga mengubah antara menampilkan/menyembunyikan widget.</string>
+    <string name="activity_DB_test_export_button">Jalankan Ekspor Otomatis Sekarang</string>
+    <string name="activity_db_management_clean_export_directory_label">Hapus file dalam direktori Ekspor/Impor</string>
+    <string name="activity_DB_clean_export_directory_warning_title">Hapus berkas dalam direktori Ekspor/Impor\?</string>
+    <string name="activity_DB_clean_export_directory_warning_message">Benar-benar ingin hapus berkas dalam direktori Ekspor/Impor\?</string>
+    <string name="pebble_pairing_hint">Sebuah dialog penyandingan akan muncul di perangkat Android Anda. Jika tidak, lihat dalam laci notifikasi dan terima permintaan penyandingan. Juga terima di Pebble Anda setelah itu.</string>
+    <string name="mi2_dnd_automatic">Otomatis (deteksi tidur)</string>
+    <string name="activity_type_activity">Aktivitas</string>
+    <string name="preferences_led_color">Warna LED</string>
+    <string name="hr_widget_last_notification">Notifikasi terakhir</string>
+    <string name="minSpeed">Minimum</string>
+    <string name="minPace">Kecepatan Paling Lambat</string>
+    <string name="flatSeconds">Datar</string>
+    <string name="baseAltitude">Ketinggian Dasar</string>
+    <string name="steps_unit">langkah</string>
+    <string name="ft">kaki</string>
+    <string name="sports_activity_quick_filter_select">Periode waktu</string>
+    <string name="fossil_hr_auth_failed">Otentikasi gagal, kemampuan terbatas</string>
+    <string name="fossil_hr_edit_action">Sunting tindakan</string>
+    <string name="nothing_prefs_inear_title">Deteksi dalam telinga</string>
+    <string name="sony_speak_to_chat_focus_on_voice">Fokus pada Suara</string>
+    <string name="about_description_generic">Pengganti untuk aplikasi gawai Android sumber tertutup dari vendor yang bebas dan tanpa awan.</string>
+    <string name="gadgetbridge_running_generic">Gadgetbridge berjalan</string>
+    <string name="application_name_banglejs_main">Bangle.js Gadgetbridge</string>
+    <string name="about_activity_title_banglejs_main">Tentang Bangle.js Gadgetbridge</string>
+    <string name="gadgetbridge_running_banglejs_main">Bangle.js berjalan</string>
+    <string name="title_activity_controlcenter_main_nightly">Gadgetbridge Nightly</string>
+    <string name="gadgetbridge_running_main_nightly">Nightly GB berjalan</string>
+    <string name="application_name_main_nopebble">Gadgetbridge (Nightly, Tanpa penyedia Pebble)</string>
+    <string name="about_activity_title_main_nopebble">Tentang Gadgetbridge Nightly Tanpa Pebble</string>
+    <string name="gadgetbridge_running_main_nopebble">Nightly GB TanpaPebble berjalan</string>
+    <string name="pref_summary_minimize_priority_off">Ikon dalam bilah status dan notifikasi di layar kunci ditampilkan</string>
+    <string name="pref_title_notification_use_as_allow">Perbolehkan notifikasi dari aplikasi yang dipilih</string>
+    <string name="pref_blacklist_calendars">Daftar Hitam Kalender</string>
+    <string name="pref_title_canned_messages_dismisscall">Penolakan Panggilan</string>
+    <string name="pref_title_enable_calendar_sync">Sinkron kalender</string>
+    <string name="pref_header_device_spec_settings">Pengaturan perangkat</string>
+    <string name="pref_title_sensors_altitude">Kalibrasi ketinggian</string>
+    <string name="prefs_sensors_button_bp_calibration">Kalibrasi</string>
+    <string name="zetime_signaling_beep_once">Bunyi sekali</string>
+    <string name="prefs_hr_alarm_low">Batasan rendah</string>
+    <string name="prefs_hr_alarm_high">Batasan tinggi</string>
+    <string name="pref_title_auto_export_interval">Interval ekspor</string>
+    <string name="pref_disable_new_ble_scanning">Nonaktifkan pemindaian BLE baru</string>
+    <string name="unknown_state">Keadaan tidak diketahui</string>
+    <string name="tap_connected_device_for_activity">Ketuk perangkat terhubung untuk aktivitas</string>
+    <string name="n_a">Tidak diketahui</string>
+    <string name="device_with_rssi">%1$s (%2$s)</string>
+    <string name="busy_task_fetch_activity_data">Mendapatkan data aktivitas</string>
+    <string name="vibration_profile_alarm_clock">Jam alarm</string>
+    <string name="interval_one_minute">setiap menit</string>
+    <string name="reminder_time">Waktu</string>
+    <string name="reminder_message">Pesan</string>
+    <string name="reminder_time_every_year">%1$s, setiap tahun</string>
+    <string name="chart_no_data_synchronize">Tidak ada data. Sinkron perangkat\?</string>
+    <string name="activity_db_management_clean_export_directory_text">Berkas yang diekspor dalam direktori Ekspor/Impor dapat diakses oleh aplikasi apa pun di perangkat. Anda mungkin ingin menghapus berkasi ini setelah penyinkronan atau pencadangan. Pastikan untuk memiliki cadangan sebelum menghapusnya. Berkas GPX, subdirektori dan berkas basis data yang diekspor secara otomatis (jika ada) tidak akan dihapus. Jalur ke direktori Ekspor/Impor adalah:</string>
+    <string name="dismiss">Abaikan</string>
+    <string name="unit_metric">Metrik</string>
+    <string name="dbmanagementactivity_export_finished">Penghapusan selesai</string>
+    <string name="weather_notification_label">Pastikan kulit ini diaktifkan dalam aplikasi Notifikasi Cuaca untuk mendapatkan informasi cuaca di Pebble Anda. 
+\n 
+\nTidak ada pengaturan yang dibutuhkan di sini. 
+\n 
+\nAnda dapat mengaktifkan aplikasi cuaca sistem dari Pebble Anda dari pengelola aplikasi. 
+\n 
+\nWajah jam yang didukung akan menampilkan cuaca secara otomatis.</string>
+    <string name="title_activity_vibration">Getaran</string>
+    <string name="kind_resources">Sumber daya</string>
+    <string name="kind_app">Aplikasi</string>
+    <string name="devicetype_unknown">Perangkat Tidak Diketahui</string>
+    <string name="devicetype_test">Uji Coba Perangkat</string>
+    <string name="kind_agps_bundle">Bundel AGPS</string>
+    <string name="kind_gps_cep">Pembenaran Kesalahan GPS</string>
+    <string name="devicetype_pebble">Pebble</string>
+    <string name="devicetype_miband">Mi Band</string>
+    <string name="devicetype_amazfit_neo">Amazfit Neo</string>
+    <string name="devicetype_miband7">Xiaomi Smart Band 7</string>
+    <string name="add_test_device">Tambahkan perangkat uji coba</string>
+    <string name="devicetype_amazfit_gts3">Amazfit GTS 3</string>
+    <string name="devicetype_miband6">Mi Band 6</string>
+    <string name="menuitem_activity">Riwayat Olahraga</string>
+    <string name="menuitem_cycles">Pelacakan Siklus</string>
+    <string name="menuitem_timer">Hitungan Mundur</string>
+    <string name="menuitem_breathing">Bernapas</string>
+    <string name="devicetype_amazfit_vergel">Amazfit Verge Lite</string>
+    <string name="notification_gps_text">Mengirim lokasi GPS ke %1$d perangkat</string>
+    <string name="devicetype_lefun">Lefun</string>
+    <string name="devicetype_amazfit_gts">Amazfit GTS</string>
+    <string name="pref_invalid_frequency_title">Frekuensi tidak absah</string>
+    <string name="filter_mode_none">Jangan saring</string>
+    <string name="filter_mode_blacklist">Blokir ketika berisi kata</string>
+    <string name="pref_summary_contextual_arabic">Aktifkan ini untuk mendukung Arab kontekstual</string>
+    <string name="sports_activity_quick_filter_this_month">Bulan ini</string>
+    <string name="step_streaks_achievements_sharing_title">Pencapaian Langkah</string>
+    <string name="pref_media_pause">Jeda Media</string>
+    <string name="averageKMPaceSeconds">Laju</string>
+    <string name="watchface_setting_title_custom_events">Acara kustom</string>
+    <string name="prefs_autoheartrate_sleep">Ukur saat tidur</string>
+    <string name="devicetype_nothingear1">Nothing Ear (1)</string>
+    <string name="pref_balance">Seimbang</string>
+    <string name="pref_voice_detect_duration_15">15 detik</string>
+    <string name="prefs_double_tap_edge_summary">Deteksi ketukan ganda bahkan jika tidak disentuh pada tempat sentuhan</string>
+    <string name="sony_equalizer_preset_relaxed">Santai</string>
+    <string name="sony_speak_to_chat_timeout">Waktu habis</string>
+    <string name="pref_device_action_broadcast">Kirim Siaran</string>
+    <string name="steps_streaks_total_steps">Jumlah 
+\nlangkah</string>
+    <string name="steps_streaks_streak_days">Hari 
+\nBeruntun</string>
+    <string name="prefs_button_variable_actions">Pengaturan tekanan tombol rinci</string>
+    <string name="pref_summary_notifications_pebblemsg">Dukungan untuk aplikasi yang mengirimkan notifikasi ke Pebble melalui PebbleKit.</string>
+    <string name="pref_title_notification_use_as">Gunakan daftar Aplikasi untuk鈥�</string>
+    <string name="pref_title_whenscreenon">鈥uga saat layar nyala</string>
+    <string name="pref_title_vibration_strength">Kekuatan getaran</string>
+    <string name="prefs_title_all_day_heart_rate">Pengukuran detak jantung seharian</string>
+    <string name="pref_summary_notifications_and_calls_title_reject">Mati - abaikan, Nyala - tolak</string>
+    <string name="pref_sensors_bp_calibration_high">Tekanan Darah SISTOLIK (tinggi)</string>
+    <string name="zetime_title_settings">Pengaturan ZeTime</string>
+    <string name="zetime_title_heartrate">Pengaturan detak jantung</string>
+    <string name="zetime_title_screentime">Durasi layar nyala dalam detik</string>
+    <string name="zetime_handmove_display">Pergerakan tangan</string>
+    <string name="zetime_calories_type_all">Kalori yang dibakar secara aktif dan tidak aktif</string>
+    <string name="zetime_date_format_1">TT/BB/HH</string>
+    <string name="zetime_date_format_2">HH/BB/TT</string>
+    <string name="zetime_prefs_inactivity_repetitions">Pengulangan</string>
+    <string name="zetime_prefs_inactivity_mo">Senin</string>
+    <string name="zetime_prefs_inactivity_we">Rabu</string>
+    <string name="zetime_signaling_vibrate_once">Getar sekali</string>
+    <string name="pref_auto_fetch">Dapatkan data aktivitas secara otomatis</string>
+    <string name="discovery_stop_scanning">Berhenti memindai</string>
+    <string name="discovery_start_scanning">Mulai penemuan</string>
+    <string name="discovery_bluetooth_le_scan">Pemindaian Bluetooth LE:</string>
+    <string name="action_discover">Hubungkan perangkat baru</string>
+    <string name="title_activity_android_pairing">Sandingkan perangkat</string>
+    <string name="pairing">Menyanding dengan %s鈥�</string>
+    <string name="pairing_already_bonded">Sudah menjalin dengan %1$s (%2$s), menghubungkan鈥�</string>
+    <string name="female">Wanita</string>
+    <string name="message_cannot_pair_no_mac">Tidak ada alamat MAC yang dikirim, tidak dapat menyandingkan.</string>
+    <string name="male">Pria</string>
+    <string name="miband_pairing_using_dummy_userdata">Tidak ada data pengguna absah yang diberikan, menggunakan data pengguna buatan untuk sekarang.</string>
+    <string name="initializing">Memulai</string>
+    <string name="vibration_profile_long">Panjang</string>
+    <string name="miband_prefs_vibration">Getaran</string>
+    <string name="pref_header_vibration_settings">Pengaturan getaran</string>
+    <string name="pref_screen_notification_profile_generic">Notifikasi generik</string>
+    <string name="pref_screen_notification_profile_generic_chat">Obrolan</string>
+    <string name="pref_screen_notification_profile_generic_navigation">Navigasi</string>
+    <string name="pref_screen_notification_profile_generic_social">Jaringan sosial</string>
+    <string name="pref_screen_notification_profile_inactivity">Notifikasi tidak aktif</string>
+    <string name="heartrate_bpm_135">135 bpm</string>
+    <string name="heartrate_bpm_130">130 bpm</string>
+    <string name="control_center_cancel_to_stop_vibration">Batalkan untuk menghentikan getaran.</string>
+    <string name="reminder_once">Sekali</string>
+    <string name="reminder_every_month">Setiap bulan</string>
+    <string name="reminder_every_year">Setiap tahun</string>
+    <string name="reminder_delete_confirm_title">Hapus pengingat</string>
+    <string name="reminder_delete_confirm_description">Apakah Anda yakin ingin menghapus pengingat\?</string>
+    <string name="world_clock_timezone">Zona Waktu</string>
+    <string name="world_clock_delete_confirm_description">Apakah Anda yakin ingin menghapus jam dunia\?</string>
+    <string name="world_clock_no_free_slots_title">Tidak ada tempat kosong</string>
+    <string name="world_clock_no_free_slots_description">Perangkat ini tidak memiliki tempat kosong untuk jam dunia (jumlah tempat: %1$s)</string>
+    <string name="world_clock_label">Label</string>
+    <string name="prefs_stress_monitoring_title">Pemantauan stres</string>
+    <string name="dateformat_date_time">Waktu &amp; tanggal</string>
+    <string name="mi2_prefs_button_press_broadcast">Siarkan pesan untuk dikirim</string>
+    <string name="prefs_disconnect_notification">Putuskan notifikasi</string>
+    <string name="prefs_reserve_reminder_calendar_summary">Jumlah acara kalender yang akan disinkron</string>
+    <string name="spanish">Spanyol</string>
+    <string name="norwegian_bokmal">Bokm氓l Norwegia</string>
+    <string name="pref_title_pebble_health_store_raw">Simpan rekaman mentah dalam basis data</string>
+    <string name="activity_db_management_merge_old_title">Penghapusan basis data lawas</string>
+    <string name="activity_type_elliptical">Elips</string>
+    <string name="share">Bagikan</string>
+    <string name="devicetype_q8">Q8</string>
+    <string name="devicetype_id115">ID115</string>
+    <string name="devicetype_watchxplus">Watch X Plus</string>
+    <string name="devicetype_casiogb6900">Casio GB-6900</string>
+    <string name="devicetype_casiogbx100">Casio GBX-100</string>
+    <string name="devicetype_itag">iTag</string>
+    <string name="devicetype_mijia_lywsd02">Mijia Smart Clock</string>
+    <string name="devicetype_makibes_hr3">Makibes HR3</string>
+    <string name="device_is_currently_bonded">SUDAH DIJALIN</string>
+    <string name="device_unsupported">TIDAK DIDUKUNG</string>
+    <string name="steps">Langkah</string>
+    <string name="maxPace">Kecepatan Paling Cepat</string>
+    <string name="averageHR">Detak Jantung</string>
+    <string name="maxHR">Detak Jantung Maks</string>
+    <string name="averageStride">Langkah Rata-Rata</string>
+    <string name="averageCadence">Irama Rata-Rata</string>
+    <string name="laps">Putaran</string>
+    <string name="maxCadence">Irama Maks</string>
+    <string name="activity_summaries_all_devices">Semua perangkat</string>
+    <string name="fossil_hr_commute_actions_explanation">Tindakan yang diatur di sini akan muncul di aplikasi Perjalanan di jam tangan Anda. Baca wiki untuk informasi lanjut cara menangani intent yang dihasilkan oleh tindakan ini.</string>
+    <string name="watchface_dialog_widget_presets">Prasetel posisi</string>
+    <string name="watchface_setting_wrist_flick_move_relative">Gerakkan tangan relatif dengan waktu</string>
+    <string name="watchface_setting_wrist_flick_minute">Tangan menit (-360 ke 360)</string>
+    <string name="watchface_upload_failed">Pengunggahan wajah jam gagal. Mohon coba lagi.</string>
+    <string name="watchface_cache_confirm_overwrite">Sebuah wajah jam dengan nama ini sudah ada dalam tembolok. Apakah Anda ingin menimpanya\?</string>
+    <string name="prefs_ambient_volume_left">Volume Sekitar Kiri</string>
+    <string name="prefs_ambient_volume_right">Volume Sekitar Kanan</string>
+    <string name="prefs_ambient_voice_focus">Fokus Suara</string>
+    <string name="prefs_active_noise_cancelling">Pembatalan Suara Aktif</string>
+    <string name="prefs_active_noise_cancelling_level">Tingkat Pembatalan Suara Aktif</string>
+    <string name="pref_title_touch_voice_assistant">Asisten Suara</string>
+    <string name="pref_title_touch_anc">Pembatalan Suara Aktif</string>
+    <string name="pref_title_touch_quick_ambient">Suara Sekitar Cepat</string>
+    <string name="prefs_noise_control_with_one_earbud_summary">Perbolehkan kendali suara ketika menggunakan satu earbud saja</string>
+    <string name="pref_switch_controls_anc_ambient">Pembatalan suara 鈫愨啋 Sekitar</string>
+    <string name="pref_switch_controls_anc_off">Pembatalan suara 鈫愨啋 Mati</string>
+    <string name="pref_switch_controls_ambient_off">Sekitar 鈫愨啋 Mati</string>
+    <string name="prefs_voice_detect_duration">Akhiri setelah diam selama:</string>
+    <string name="sony_pause_when_taken_off">Jeda ketika headphone tidak digunakan</string>
+    <string name="sony_button_mode_left">Mode Tombol (Kiri)</string>
+    <string name="pref_device_action_fitness_app_control_toggle">Saklar Pelacakan Aplikasi Kesehatan</string>
+    <string name="error_setting_parent_folder">Kesalahan menetapkan folder induk: %s</string>
+    <string name="pref_title_device_internet_access">Perbolehkan akses internet</string>
+    <string name="pref_title_device_intents">Perbolehkan Intent</string>
+    <string name="pref_summary_device_internet_access">Perbolehkan aplikasi di perangkat ini mengakses internet</string>
+    <string name="simplified_chinese">Cina Disederhanakan</string>
+    <string name="traditional_chinese">Cina Tradisional</string>
+    <string name="pref_switch_noise_control">Ubah Kendali Suara</string>
+    <string name="Speed">Kecepatan</string>
+    <string name="mi2_prefs_inactivity_warnings">Peringatan tidak aktif</string>
+    <string name="mi2_prefs_button_press_broadcast_summary">Pesan siaran dikirim dengan peristiwa. Parameter \'button_id\' ditambahkan secara otomatis untuk setiap pesan.</string>
+    <string name="device_not_connected">Tidak terhubung.</string>
+    <string name="pref_title_notification_filter">Jangan Ganggu</string>
+    <string name="fw_upgrade_notice_amazfit_gtr3">Anda akan memasang perangkat tegar %s pada Amazfit GTR 3 Anda. 
+\n 
+\nGelang Anda akan dinyalakan ulang setelah memasang berkas .zip. 
+\n 
+\nLANJUTKAN DENGAN RISIKO ANDA SENDIRI!</string>
+    <string name="pref_title_notification_use_as_deny">Mengabaikan notifikasi dari aplikasi yang dipilih</string>
+    <string name="pref_title_weather_summary">Digunakan untuk penyedia cuaca LineageOS, versi Android lain harus menggunakan aplikasi seperti \"Weather notification\". Temukan lebih banyak informasi di wiki Gadgetbridge.</string>
+    <string name="miband_firmware_suggest_whitelist">Jika Anda masih ingin melanjutkan dan hal-hal masih bekerja dengan benar setelah itu, mohon beri tahu pengembang Gadgetbridge untuk mendaftar versi perangkat tegar %s ke daftar putih.</string>
+    <string name="open_fw_installer_info_text">Pemasang Firmware/Muka Jam/Aplikasi/Berkas memungkinkan Anda untuk mengunggah/memasang berkas yang didukung (perangkat tegar, muka jam, GPS, sumber daya, fon...) ke perangkat. Lihat beberapa informasi lebih lanjut di wiki: https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Firmware-Update</string>
+    <string name="seconds_5">5 detil</string>
+    <string name="seconds_10">10 detik</string>
+    <string name="seconds_11">11 detik</string>
+    <string name="seconds_12">12 detik</string>
+    <string name="seconds_13">13 detik</string>
+    <string name="seconds_14">14 detik</string>
+    <string name="autoExport_lastTime_label">Ekspor otomatis terakhir: %1$s</string>
+    <string name="activity_type_soccer">Sepak bola</string>
+    <string name="activity_type_climbing">Memanjat</string>
+    <string name="kind_watchface">Muka jam</string>
+    <string name="devicetype_amazfit_pop">Amazfit Pop</string>
+    <string name="devicetype_amazfit_bipupro">Amazfit Bip U Pro</string>
+    <string name="devicetype_amazfit_bipu">Amazfit Bip U</string>
+    <string name="devicetype_amazfit_trex">Amazfit T-Rex</string>
+    <string name="devicetype_sonyswr12">Sony SWR12</string>
+    <string name="devicetype_waspos">Wasp-os</string>
+    <string name="menuitem_pai">PAI</string>
+    <string name="menuitem_stress">Stres</string>
+    <string name="error_version_check_extreme_caution">PERINGATAN: Kesalahan ketika memeriksa informasi versi! Anda seharusnya tidak melanjutkan! Melihat nama versi \"%s\"</string>
+    <string name="calories_unit">kcal</string>
+    <string name="strokes_unit">str</string>
+    <string name="pref_header_filter">Saringan Aktivitas Olahraga</string>
+    <string name="pref_header_statistics">Statistik AKtivitas Olahraga</string>
+    <string name="activity_filter_individual_items">Item yang dipilih secara individu</string>
+    <string name="freestyle">Gaya bebas</string>
+    <string name="appmanager_app_edit">Sunting</string>
+    <string name="watchface_setting_title_wrist_flick">Ibaskan pergelangan tangan</string>
+    <string name="pref_header_sony_device_info">Informasi Perangkat</string>
+    <string name="sony_anc_optimize_confirmation_description">Gunakan headphone sebagaimana Anda biasanya gunakan. Jika kondisi penggunaan atau tekanan atmosfer berubah, jalankan pengoptimal lagi.</string>
+    <string name="pref_anc_optimizer_state_pressure">Tekanan atmosfir</string>
+    <string name="sony_anc_optimize_confirmation_title">Pengoptimal Pembatalan Suara</string>
+    <string name="qhybrid_summary_on_device_confirmation">Konfirmasi penyandingan pada perangkat dapat mengganggu. Menonaktifkannya mungkin menghilangkan kemampuan.</string>
+    <string name="qhybrid_title_on_device_confirmation">Aktifkan konfirmasi penyandingan pada perangkat</string>
+    <string name="pref_header_calendar">Kalender</string>
+    <string name="open_fw_installer_getting_files_title">Mendapatkan berkas perangkat tegar/aplikasi</string>
+    <string name="pref_header_system">Sistem</string>
+    <string name="pref_header_connection">Koneksi</string>
+    <string name="fw_upgrade_notice_amazfit_gts3">Anda akan memasang perangkat tegar %s pada Amazfit GTS 3 Anda. 
+\n 
+\nGelang Anda akan dinyalakan ulang setelah memasang berkas .zip. 
+\n 
+\nLANJUTKAN DENGAN RISIKO ANDA SENDIRI!</string>
+    <string name="fw_upgrade_notice_amazfitx">Anda akan memasang perangkat tegar %s pada Amazfit X Anda. 
+\n 
+\nPastikan Anda memasang berkas .fw, dan setelah itu berkas .res. Gelang Anda akan dinyalakan ulang setelah memasang berkas .fw. 
+\n 
+\nCatatan: Anda tidak harus memasang .res jika sama persis dengan yang sebelumnya dipasang. 
+\n 
+\nLANJUTKAN DENGAN RISIKO ANDA SENDIRI!</string>
+    <string name="pref_header_display">Tampilan</string>
+    <string name="pref_header_health">Kesehatan</string>
+    <string name="pref_header_time">Waktu</string>
+    <string name="pref_header_workout">Olahraga</string>
+    <string name="pref_title_notifications_generic_settings">Pengaturan notifikasi Android</string>
+    <string name="pref_title_notifications_ignore_low_priority">Abaikan notifkasi prioritas rendah</string>
+    <string name="activity_summary_detail_clear_gpx_track">Hapus trek GPX</string>
+    <string name="pref_title_canned_reply_suffix">Akhiram umum</string>
+    <string name="pref_title_notification_prefer_long_text">Lebih suka teks notifikasi panjang</string>
+    <string name="pref_summary_notification_prefer_long_text">Jika tersedia, kirim teks notifikasi panjang ke perangkat</string>
+    <string name="pref_call_privacy_mode_name">Sembunyikan nama tapi tampilkan nomor</string>
+    <string name="pref_call_privacy_mode_number">Sembunyikan nomor tapi tampilkan nama</string>
+    <string name="pref_call_privacy_mode_complete">Sembunyikan nama dan nomor</string>
+    <string name="pref_message_privacy_mode_complete">Sembunyikan semua konten</string>
+    <string name="pref_title_weather_location">Lokasi cuaca (untuk penyedia cuaca LineageOS)</string>
+    <string name="pref_blacklist_calendars_summary">Kalender yang di daftar hitam tidak akan disinkronkan ke perangkat</string>
+    <string name="pref_header_cannned_messages">Pesan kalengan</string>
+    <string name="pref_summary_canned_replies">Balas dari jam tangan menggunakan pesan prasetel</string>
+    <string name="pref_cache_weather_summary">Informasi cuaca akan disimpan di semua pemulaian ulang aplikasi.</string>
+    <string name="spo2_perc_80">80%</string>
+    <string name="heartrate_bpm_125">125 bpm</string>
+    <string name="pref_summary_auto_export_interval">Ekspor setiap %d jam</string>
+    <string name="prefs_heartrate_alert_high_threshold">Ambang tinggi peringatan detak jantung</string>
+    <string name="prefs_heartrate_alert_low_threshold">Ambang rendah peringatan detak jantung</string>
+    <string name="dbaccess_error_executing">Kesalahan memulai \'%1$s\'</string>
+    <string name="pbwinstallhandler_app_item">%1$s (%2$s)</string>
+    <string name="pref_summary_dont_ack_transfers">Jika tidak di-ACK ke band, data aktivitas tidak disimpan. Berguna jika GB digunakan bersama dengan aplikasi lain.</string>
+    <string name="abstract_chart_fragment_kind_activity">Aktivitas</string>
+    <string name="live_activity_total_steps">Jumlah langkah</string>
+    <string name="abstract_chart_fragment_kind_light_sleep">Tidur ringan</string>
+    <string name="abstract_chart_fragment_kind_deep_sleep">Tidur nyenyak</string>
+    <string name="prefs_always_on_display">Layar Selalu Nyala</string>
+    <string name="prefs_password_summary">Kunci gelang dengan sebuah kata sandi ketika dilepaskan dari pergelangan</string>
+    <string name="on">Nyala</string>
+    <string name="activity_db_management_autoexport_enabled_yes">Ekspor otomatis diaktifkan.</string>
+    <string name="activity_db_management_autoexport_scheduled_no">Ekspor otomatis belum dijadwalkan.</string>
+    <string name="activity_db_management_autoexport_location">Lokasi tidak dapat dimengerti. Kemungkinan sistem perizinan Anda yang lebih baru. Kemungkinan ekspor otomatis tidak bekerja sekarang.</string>
+    <string name="controlcenter_get_heartrate_measurement">Dapatkan pengukuran detak jantung</string>
+    <string name="prefs_activity_in_device_card_title_summary">Tampilkan langkah, jarak, atau tidur saat ini pada kartu perangkat</string>
+    <string name="fw_upgrade_notice_miband3">Anda akan memasang perangkat tegar %s pada Mi Band 3 Anda. 
+\n 
+\nPastikan Anda memasang berkas .fw, dan setelah itu berkas .res. Gelang Anda akan dinyalakan ulang setelah memasang berkas .fw. 
+\n 
+\nCatatan: Anda tidak harus memasang .res jika sama persis dengan yang sebelumnya dipasang. 
+\n 
+\nLANJUTKAN DENGAN RISIKO ANDA SENDIRI!</string>
+    <string name="fw_upgrade_notice_amazfitvergel">Anda akan memasang perangkat tegar %s pada Amazfit Verge Lite Anda. 
+\n 
+\nPastikan Anda memasang berkas .fw, dan setelah itu berkas .res, dan akhirnya berkas .gps. Jam tangan Anda akan dinyalakan ulang setelah memasang berkas .fw. 
+\n 
+\nCatatan: Anda tidak harus memasang .res dan .gps jika berkas ini sama persis dengan yang sebelumnya dipasang. 
+\n 
+\nLANJUTKAN DENGAN RISIKO ANDA SENDIRI!</string>
+    <string name="miband_firmware_unknown_warning">Firmware ini belum dicoba dan mungkin tidak kompatibel dengan Gadgetbridge. 
+\n 
+\nAnda DIHARUSKAN UNTUK TIDAK flash perangkat tegar ini!</string>
+    <string name="open_fw_installer_warning_title">Peringatan</string>
+    <string name="open_fw_installer_select_file">Pilih sebuah berkas yang Anda ingin unggah ke perangkat: %s</string>
+    <string name="open_fw_installer_info_text_title">Pemasang Berkas</string>
+    <string name="open_fw_installer_connect_minimum_one_device">Mohon hubungkan SETIDAKNYA SATU perangkat yang Anda ingin kirim berkas ke perangkat tersebut.</string>
+    <string name="pref_header_other">Lain</string>
+    <string name="open_fw_installer_connect_maximum_one_device">Mohon hubungkan HANYA SATU perangkat yang Anda ingin kirim berkas ke perangkat tersebut.</string>
+    <string name="pref_title_minimize_priority">Sembunyikan notifikasi Gadgetbridge</string>
+    <string name="pref_summary_notification_filter">Blokir semua notifikasi ketika Jangan Ganggu diaktifkan di ponsel</string>
+    <string name="pref_title_pebble_privacy_mode">Mode privasi</string>
+    <string name="pref_title_pebble_forceprotocol">Paksa protokol notifikasi</string>
+    <string name="pref_summary_pebble_forcele">Gunakan dukungan eksperimental Pebble LE untuk semua Pebble, daripada BT klasik. Ini membutuhkan penyandingan ke non LE dahulu, lalu Pebble LE</string>
+    <string name="zetime_signaling_none">Diam</string>
+    <string name="zetime_calories_type">Jenis kalori</string>
+    <string name="zetime_calories_type_active">Hanya kalori yang dibakar secara aktif</string>
+    <string name="title_activity_discovery">Penemuan perangkat</string>
+    <string name="discovery_entered_invalid_authkey">Kunci otentikasi rahasia yang Anda masukkan tidak absah! Tekan lama pada perangkat untuk menyunting.</string>
+    <string name="vibration_profile_staccato">Staccato</string>
+    <string name="pref_screen_vibration_patterns_summary">Atur pola getaran untuk notifikasi berbeda</string>
+    <string name="initialized">dimulai</string>
+    <string name="user_feedback_miband_set_alarms_failed">Ada kesalahan menetapkan alarm, mohon coba lagi.</string>
+    <string name="chart_get_active_and_synchronize">Lakukan beberapa aktivitas dan sinkron perangkat.</string>
+    <string name="portuguese_br">Portugis (Brasil)</string>
+    <string name="activity_summary_today">Hari ini</string>
+    <string name="devicetype_makibes_f68">Makibes F68</string>
+    <string name="menuitem_alexa">Alexa</string>
+    <string name="left">Kiri</string>
+    <string name="horizontal">Horizontal</string>
+    <string name="pref_title_third_party_app_device_settings">Perbolehkan aplikasi pihak ketiga untuk mengubah pengaturan</string>
+    <string name="live_activity_max_heart_rate">Detak jantung saat ini / maks: %1$d / %2$d</string>
+    <string name="pref_summary_third_party_app_device_settings">Perbolehkan aplikasi pihak ketiga terpasang lain untuk menetapkan pengaturan perangkat melalui intent.</string>
+    <string name="miband_prefs_reserve_alarm_calendar">Alarm untuk disimpan untuk acara masa mendatang</string>
+    <string name="mi2_prefs_button_actions_summary">Tentukan tindakan tekanan tombol</string>
+    <string name="automatic">Otomatis</string>
+    <string name="mi2_prefs_button_action_vibrate">Aktifkan getaran gelang</string>
+    <string name="mi2_prefs_button_press_count_max_delay">Jeda maksimum antara tekanan</string>
+    <string name="mi2_prefs_button_action_vibrate_summary">Aktifkan getaran gelang pada pemicuan tindakan tombol</string>
+    <string name="dbmanagementactivity_export_confirmation">Benar-benar ekspor data\? Data aktivitas dan preferensi sebelumnya (jika ada) akan ditimpa.</string>
+    <string name="dbmanagementactivity_import_successful">Diimpor.</string>
+    <string name="activity_DB_import_button">Impor Data</string>
+    <string name="activity_DB_delete_legacy_button">Hapus basis data lawas</string>
+    <string name="dbmanagementactivity_error_importing_db">Kesalahan mengimpor basis data: %1$s</string>
+    <string name="discovery_bonding_failed_immediately">Penjalinan dengan %1$s langsung gagal.</string>
+    <string name="sensitive">Sensitif</string>
+    <string name="mi2_dnd_always">Selalu</string>
+    <string name="discovery_yes_pair">Sandingkan</string>
+    <string name="discovery_pair_question">Pilih Sandingkan untuk menyandingkan perangkat Anda. Jika ini gagal, coba lagi tanpa penyandingan.</string>
+    <string name="activity_type_freestyle">Gaya bebas</string>
+    <string name="activity_type_swimming">Berenang</string>
+    <string name="activity_type_hiking">Mendaki</string>
+    <string name="activity_type_indoor_cycling">Bersepeda Dalam Ruangan</string>
+    <string name="controlcenter_start_activity_tracks">Trek aktivitas Anda</string>
+    <string name="activity_type_swimming_openwater">Berenang (Air terbuka)</string>
+    <string name="activity_type_badminton">Bulu tangkis</string>
+    <string name="activity_type_pingpong">Ping Pong</string>
+    <string name="activity_type_strength_training">Pelatihan Kekuatan</string>
+    <string name="menuitem_alipay">Alipay</string>
+    <string name="menuitem_takephoto">Remote Kamera</string>
+    <string name="menuitem_widgets">Widget</string>
+    <string name="menuitem_temperature">Suhu</string>
+    <string name="menuitem_flashlight">Senter</string>
+    <string name="menuitem_mutephone">Bisukan Ponsel</string>
+    <string name="menuitem_unknown_app">Tidak diketahui (%s)</string>
+    <string name="pref_title_lower_button_function_double">Tombol Bawah ganda</string>
+    <string name="pref_check_permission_status">Periksa status izin</string>
+    <string name="qhybrid_calibration_clockwise">Arah jarum jam</string>
+    <string name="button_watchface_settings">Pengaturan wajah jam</string>
+    <string name="pref_media_playpause">Saklar pemutaran</string>
+    <string name="activity_type_indoor_fitness">Kesehatan Dalam Ruang</string>
+    <string name="activity_type_hiit">Pelatihan interval intensitas tinggi</string>
+    <string name="activity_type_dance">Menari</string>
+    <string name="activity_type_gymnastics">Senam</string>
+    <string name="activity_type_stretching">Peregangan</string>
+    <string name="activity_type_core_training">Pelatihan Inti</string>
+    <string name="activity_type_kickboxing">Tinju tendang</string>
+    <string name="activity_type_zumba">Zumba</string>
+    <string name="activity_type_street_dance">Menari Jalanan</string>
+    <string name="activity_type_indoor_ice_skating">Seluncur Es Dalam Ruangan</string>
+    <string name="activity_type_boxing">Tinju</string>
+    <string name="menuitem_personal_activity_intelligence">Kecerdasan Aktivitas Pribadi</string>
+    <string name="notification_gps_title">Gadgetbridge GPS</string>
+    <string name="menuitem_countdown">Hitungan mundur</string>
+    <string name="devicetype_sony_wh_1000xm4">Sony WH-1000XM4</string>
+    <string name="note">Catatan</string>
+    <string name="qhybrid_second_timezone_offset_relative_to_utc">pergeseran zona waktu kedua relatif ke UTC</string>
+    <string name="error_background_service_reason">Pemulaian layanan latar belakang gagal karena sebuah pengecualian. Kesalahan:</string>
+    <string name="pref_title_developer_settings">Pengaturan pengembang</string>
+    <string name="qhybrid_pref_title_external_intents">Perbolehkan intent eksternal berbahaya</string>
+    <string name="pref_media_play">Mainkan Media</string>
+    <string name="pref_write_logfiles_not_available">Pemulaian pencatatan berkas gagal, menulis berkas catatan saat ini tidak dimungkinkan. Mulai ulang aplikasi untuk mencoba memulai catatan berkas lagi.</string>
+    <string name="interval_forty_five_minutes">setiap 45 menit</string>
+    <string name="korean">Korea</string>
+    <string name="activity_prefs_chart_max_heart_rate">Detak jantung maks</string>
+    <string name="pref_title_lower_button_function_long">Tombol Bawah panjang</string>
+    <string name="ascentMeters">Naik</string>
+    <string name="devicetype_amazfit_gtr_lite">Amazfit GTR Lite</string>
+    <string name="devicetype_amazfit_gtr2">Amazfit GTR 2</string>
+    <string name="devicetype_amazfit_gtr2e">Amazfit GTR 2e</string>
+    <string name="devicetype_teclast_h30">Teclast H30</string>
+    <string name="devicetype_liveview">LiveView</string>
+    <string name="devicetype_hplus">HPlus</string>
+    <string name="devicetype_exrizu_k8">Exrizu K8</string>
+    <string name="devicetype_no1_f1">No.1 F1</string>
+    <string name="menuitem_workout_status">Status Olahraga</string>
+    <string name="watch9_calibration_button">Kalibrasi</string>
+    <string name="watch9_time_seconds">Detik:</string>
+    <string name="qhybrid_goal_in_steps">Sasaran dalam langkah</string>
+    <string name="averageAltitude">Rata-rata</string>
+    <string name="pressure_relief_summary">Cegah perasaan tekanan dalam telinga ketika tidak menggunakan Pembatalan Suara Aktif</string>
+    <string name="prefs_touch_lock">Kunci Sentuh</string>
+    <string name="hybridhr_widget_bg_double_circle">Lingkaran ganda</string>
+    <string name="watchface_setting_desc_wrist_flick">(untuk menonaktifkan secara seluruh, aktifkan gerakan relatif dan tetapkan semua nilai menjadi 0)</string>
+    <string name="pref_media_next">Trek Berikutnya</string>
+    <string name="discovery_bluetooth_scan">Pemindaian Bluetooth:</string>
+    <string name="pref_media_volumeup">Volume Atas</string>
+    <string name="pref_media_previous">Trek Sebelumnya</string>
+    <string name="prefs_activity_recognition">Pengaturan pengenalan aktivitas</string>
+    <string name="bengali">Benggala</string>
+    <string name="pref_summary_opentracks_packagename">Digunakan untuk memulai/menghentikan perekaman trek GPS di aplikasi kesehatan eksternal.</string>
+    <string name="pref_huami_truncate_fetch_operation_timestamps_title">Kecilkan stempel waktu operasi pendapatan</string>
+    <string name="pref_huami_truncate_fetch_operation_timestamps_summary">Kecilkan stempel waktu operasi pendapatan ke menit. Nonaktifkan pengaturan ini untuk tetap dalam detik, jika Anda mengalami masalah saat mendapatkan olahraga sangat pendek.</string>
+    <string name="pref_title_dont_ack_transfer">Jangan ACK transfer data aktivitas</string>
+    <string name="pref_summary_keep_data_on_device">Akan menyimpan data aktivitas di perangkat bahkan setelah sinkronisasi. Berguna jika GB digunakan bersama dengan aplikasi lain. Ini dapat mengakibatkan jam tangan kehabisan ruangan dan/atau berhenti menyinkronkan dengan benar.</string>
+    <string name="miband_prefs_reserve_reminder_calendar">Pengingat untuk disimpan untuk acara masa depan</string>
+    <string name="mi2_prefs_button_actions">Tindakan tombol</string>
+    <string name="pref_sleep_breathing_quality_monitoring">Pemantauan kualitas pernapasan tidur</string>
+    <string name="mi2_prefs_goal_notification">Notifikasi sasaran</string>
+    <string name="mi2_prefs_button_action">Aktifkan tindakan tombol</string>
+    <string name="pref_title_force_white_color_scheme">Paksa hitam pada skema warna putih</string>
+    <string name="pref_summary_force_white_color_scheme">Berguna jika jam tangan memiliki tangan gelap</string>
+    <string name="lefun_prefs_hydration_reminder_title">Pengingat hidrasi</string>
+    <string name="prefs_screen_timeout">Waktu Habis Layar</string>
+    <string name="czech">Ceko</string>
+    <string name="english">Inggris</string>
+    <string name="russian">Rusia</string>
+    <string name="german">Jerman</string>
+    <string name="icelandic">Islandia</string>
+    <string name="lithuanian">Lithuania</string>
+    <string name="persian">Persia</string>
+    <string name="extended_ascii">ASCII yang diperpanjang</string>
+    <string name="activity_prefs_discovery_pairing">Opsi Penemuan dan Penyandingan</string>
+    <string name="FetchActivityOperation_about_to_transfer_since">Akan mentransfer data sejak %1$s</string>
+    <string name="pref_charts_range_off">Jangka bagan ditetapkan ke Minggu</string>
+    <string name="weekstepschart_steps_a_month">Langkah per bulan</string>
+    <string name="pref_chart_sleep_rolling_24_on">24 jam lalu</string>
+    <string name="activity_prefs_goal_fat_burn_time_minutes">Sasaran setiap hari: waktu bakar lemak dalam menit</string>
+    <string name="title_activity_pebble_pairing">Penyandingan Pebble</string>
+    <string name="activity_type_basketball">Bola basket</string>
+    <string name="activity_type_stepper">Stepper</string>
+    <string name="activity_type_not_worn">Perangkat tidak dipakai</string>
+    <string name="activity_type_running">Berlari</string>
+    <string name="devicetype_amazfit_gts2_mini">Amazfit GTS 2 Mini</string>
+    <string name="devicetype_amazfit_gts2">Amazfit GTS 2</string>
+    <string name="devicetype_roidmi">Roidmi</string>
+    <string name="devicetype_pinetime_jf">PineTime (Firmware JF)</string>
+    <string name="devicetype_amazfit_gts2e">Amazfit GTS 2e</string>
+    <string name="menuitem_findphone">Temukan Ponsel</string>
+    <string name="menuitem_sleep">Tidur</string>
+    <string name="menuitem_one_tap_measuring">Pengukuran Satu Ketukan</string>
+    <string name="menuitem_offline_voice">Suara Luring</string>
+    <string name="pref_invalid_frequency_message">Mohon masukkan frekuensi antara 87,5 dan 108,0</string>
+    <string name="appwidget_not_connected">Tidak terhubung, alarm tidak diatur.</string>
+    <string name="prefs_button_long_press_action_selection_title">Tindakan tekan lama tombol</string>
+    <string name="permission_notification_policy_access">%1$s membutuhkan akses ke pengaturan Jangan Ganggu untuk mencukupinya di jam tangan ketika layar ponsel sedang mati. 
+\n 
+\nMohon ketuk \'%2$s\' lalu \'%1$s\' dan aktifkan \'Perbolehkan Jangan Ganggu\', lalu ketuk \'Kembali\' untuk kembali ke %1$s.</string>
+    <string name="currentWorkoutLoad">Muatan Olahraga</string>
+    <string name="hrZoneExtreme">Ekstrim</string>
+    <string name="TrainingEffect">Efek Pelatihan</string>
+    <string name="ascentSeconds">Naik</string>
+    <string name="mi_h">mil/jam</string>
+    <string name="minutes_mi">mnt/mil</string>
+    <string name="backstroke">Gaya punggung</string>
+    <string name="prefs_operating_sounds">Suara Operasi</string>
+    <string name="qhybrid_summary_file_management">Unggah dan unduh berkas</string>
+    <string name="qhybrid_summary_calibration">Kalibrasi tangan jam</string>
+    <string name="devicetype_amazfit_trex_pro">Amazfit T-Rex Pro</string>
+    <string name="watchface_dialog_widget_cat_custom_widget">Widget kustom</string>
+    <string name="watchface_dialog_widget_cat_2nd_tz_widget">Widget zona waktu kedua</string>
+    <string name="prefs_sleep_time_summary">Menetapkan waktu ketika tidur didaftarkan</string>
+    <string name="prefs_vibration_enable">Aktifkan getaran</string>
+    <string name="prefs_switch_control_right">Ubah kendali kanan</string>
+    <string name="prefs_active_noise_cancelling_level_low">Rendah</string>
+    <string name="prefs_noise_control_with_one_earbud">Kendali Suara dengan satu earbud</string>
+    <string name="pref_voice_detect_duration_5">5 detik</string>
+    <string name="sony_equalizer_preset_vocal">Vokal</string>
+    <string name="sony_anc_optimizer_status_wearing_condition">Mengukur kondisi pemakaian鈥�</string>
+    <string name="pref_activity_recognize_running">mengenal berlari</string>
+    <string name="info_no_devices_connected">Tidak ada perangkat yang terhubung</string>
+    <string name="info_connected_count">%d perangkat terhubung</string>
+    <string name="watchface_setting_light_up_on_notification">Nyalakan pada notifikasi baru</string>
+    <string name="connection_over_ble">Koneksi melalui BLE</string>
+    <string name="supercars_lights_label">Lampu</string>
+    <string name="watchface_dialog_widget_width">Lebar widget (dalam pizel)</string>
+    <string name="reminder_repeat">Ulangi</string>
+    <string name="reminder_time_every_week">%1$s, setiap minggu</string>
+    <string name="reminder_time_every_month">%1$s, setiap bulan</string>
+    <string name="miband_prefs_hr_sleep_detection">Gunakan sensor detak jantung untuk meningkatkan deteksi tidur</string>
+    <string name="dbmanagementactivity_error_cleaning_export_directory">Kesalahan menghapus berkas dari direktori ekspor %1$s</string>
+    <string name="dbmanagementactivity_overwrite">Timpa</string>
+    <string name="dbmanagementactivity_old_activity_db_deletion_failed">Penghapusan basis data Aktivitas lawas gagal.</string>
+    <string name="maximum_duration">Durasi</string>
+    <string name="choose_auto_export_location">Pilih lokasi ekspor</string>
+    <plurals name="widget_alarm_target_hours">
+        <item quantity="other">%d jam</item>
+    </plurals>
+    <string name="sony_anc_optimizer_status_finished">Menyelesaikan鈥�</string>
+    <string name="sony_speak_to_chat_timeout_standard">Standar (30s)</string>
+    <string name="sony_connect_two_devices">Hubungkan ke 2 perangkat bersamaan</string>
+    <string name="share_log_warning">Diingat bahwa Gadgetbridge mencatat berkas yang mungkin berisi banyak informasi pribadi, termasuk tetapi tidak terbatas dengan data kesehatan, pengenal unik (seperti alamat MAC perangkat), preferensi musik, dll. Pertimbangkan untuk menyunting berkas dan menghapus informasi ini sebelum mengirim berkas ke laporan masalah publik.</string>
+    <string name="preferences_fm_frequency">Frekuensi FM</string>
+    <string name="preferences_rtl_settings">Dukungan Kanan Ke Kiri</string>
+    <string name="filter_mode">Mode Saring</string>
+    <string name="mode_configuration">Konfigurasi Mode</string>
+    <string name="activity_detail_show_gps_label">Tampilkan Trek GPS</string>
+    <string name="activity_filter_name_contains">Label</string>
+    <string name="prefs_events_forwarding_broadcast_title">Siarkan pesan</string>
+    <string name="sports_activity_quick_filter_last_week">Minggu sebelumnya</string>
+    <string name="sony_equalizer_preset_treble_boost">Peningkatan Treble</string>
+    <string name="sony_equalizer_preset_bass_boost">Peningkatan Bass</string>
+    <string name="sony_warn_sbc_codec">Peringatan: Pengaturan equalizer, posisi audio, dan sekitaran hanya bekerja untuk kodek audio SBC.</string>
+    <string name="zetime_signaling_vibrate_twice">Getar dua kali</string>
+    <string name="pref_title_authkey">Kunci Otentikasi</string>
+    <string name="medley">Campuran</string>
+    <string name="title_activity_controlcenter_banglejs_nightly">Bangle.js Gadgetbridge (Nightly)</string>
+    <string name="about_activity_title_banglejs_nightly">Tentang Bangle.js Gadgetbridge (Nightly)</string>
+    <string name="gadgetbridge_running_banglejs_nightly">Nightly Bangle.js berjalan</string>
+    <string name="title_activity_LenovoWatch_calibration">Kalibrasi Watch X Plus</string>
+    <string name="sony_automatic_power_off">Daya Mati Otomatis</string>
+    <string name="sony_speak_to_chat">Bicara untuk bercakap</string>
+    <string name="sony_equalizer_preset_custom_2">Kustom 2</string>
+    <string name="sony_button_mode_ambient_sound_control">Kendali Suara Sekitar</string>
+    <string name="filter_submode_at_least_one">Setidaknya salah satu kata</string>
+    <string name="prefs_key_vibration">Getaran Tombol</string>
+    <string name="pref_activity_recognize_walking">mengenal berjalan</string>
+    <string name="activity_type_light_sleep">Tidur ringan</string>
+    <string name="devicetype_amazfit_band5">Amazfit Band 5</string>
+    <string name="devicetype_binary_sensor">Sensor biner</string>
+    <string name="sony_speak_to_chat_sensitivity_auto">Otomatis</string>
+    <string name="activity_prefs_alarm_max_heart_rate">Detak jantung maks</string>
+    <string name="dateformat_time">Waktu</string>
+    <string name="menuitem_nfc">NFC</string>
+    <string name="descentMeters">Turun</string>
+    <string name="steps_streaks">Langkah beruntun</string>
+    <string name="appmanager_app_share">Bagikan</string>
+    <string name="fw_upgrade_notice_miband4">Anda akan memasang perangkat tegar %s pada Mi Band 4 Anda. 
+\n 
+\nPastikan Anda memasang berkas .fw, dan setelah itu berkas .res. Gelang Anda akan dinyalakan ulang setelah memasang berkas .fw. 
+\n 
+\nCatatan: Anda tidak harus memasang .res jika sama persis dengan yang sebelumnya dipasang. 
+\n 
+\nLANJUTKAN DENGAN RISIKO ANDA SENDIRI!</string>
+    <string name="fw_upgrade_notice_miband5">Anda akan memasang perangkat tegar %s pada Mi Band 5 Anda. 
+\n 
+\nPastikan Anda memasang berkas .fw, dan setelah itu berkas .res. Gelang Anda akan dinyalakan ulang setelah memasang berkas .fw. 
+\n 
+\nCatatan: Anda tidak harus memasang .res jika sama persis dengan yang sebelumnya dipasang. 
+\n 
+\nLANJUTKAN DENGAN RISIKO ANDA SENDIRI!</string>
+    <string name="power_mode_normal">Normal</string>
+    <string name="devicetype_sony_wf_sp800n">Sony WF-SP800N</string>
+    <string name="debugactivity_confirm_remove_device_preferences_title">Hapus preferensi perangkat\?</string>
+    <string name="debugactivity_confirm_remove_device_preferences">Ini akan mengatur ulang preferensi perangkat untuk semua perangkat yang terhubung. Apakah Anda yakin\?</string>
+    <string name="appmanager_item_outdated">(lawas)</string>
+    <string name="fw_upgrade_notice_amazfitcor">Anda akan memasang perangkat tegar %s pada Amazfit Cor Anda. 
+\n 
+\nPastikan Anda memasang berkas .fw, dan setelah itu berkas .res. Gelang Anda akan dinyalakan ulang setelah memasang berkas .fw. 
+\n 
+\nCatatan: Anda tidak harus memasang .res jika sama persis dengan yang sebelumnya dipasang. 
+\n 
+\nLANJUTKAN DENGAN RISIKO ANDA SENDIRI!</string>
+    <string name="fw_upgrade_notice_amazfit_trex">Anda akan memasang perangkat tegar %s pada Amazfit T-Rex Anda. 
+\n 
+\nPastikan Anda memasang berkas .fw, dan setelah itu berkas .res, dan akhirnya berkas .gps. Jam tangan Anda akan dinyalakan ulang setelah memasang berkas .fw. 
+\n 
+\nCatatan: Anda tidak harus memasang .res dan .gps jika berkas ini sama persis dengan yang sebelumnya dipasang. 
+\n 
+\nLANJUTKAN DENGAN RISIKO ANDA SENDIRI!</string>
+    <string name="open_fw_installer_warning_text">Fitur ini dapat mem-brick perangkat Anda. Walaupun ini tidak pernah terjadi ke pengembang siapa pun melalui flashing, ingat bahwa Anda melakukan ini dengan risiko Anda sendiri.</string>
+    <string name="fw_upgrade_notice_amazfit_gtr4">Anda akan memasang perangkat tegar %s pada Amazfit GTR 4 Anda. 
+\n 
+\nGelang Anda akan dinyalakan ulang setelah memasang berkas .zip. 
+\n 
+\nLANJUTKAN DENGAN RISIKO ANDA SENDIRI!</string>
+    <string name="fw_upgrade_notice_amazfitneo">Anda akan memasang perangkat tegar %s pada Amazfit Neo Anda. 
+\n 
+\nGelang Anda akan dinyalakan ulang setelah memasang berkas .fw. 
+\n 
+\nLANJUTKAN DENGAN RISIKO ANDA SENDIRI!</string>
+    <string name="miband_firmware_known">Firmware ini telah dicoba dan diketahui kompatibel dengan Gadgetbridge.</string>
+    <string name="pref_title_notifications_generic">Dukungan notifikasi generik</string>
+    <string name="pref_title_notification_media_ignores_application_list">Daftar abaikan aplikasi notifikasi media</string>
+    <string name="pref_summary_transliteration">Aktifkan ini jika perangkat Anda tidak memiliki dukungan untuk fon bahasa Anda</string>
+    <string name="pref_rtl_max_line_length_summary">Memanjangkan atau memendekkan baris teks Kanan ke Kiri yang dipisah</string>
+    <string name="pref_title_message_privacy_mode">Mode privasi pesan</string>
+    <string name="pref_message_privacy_mode_off">Tampilkan semua konten</string>
+    <string name="pref_applications_settings">Daftar aplikasi</string>
+    <string name="pref_summary_expose_hr">Perbolehkan aplikasi lain untuk mengakses data detak jantung saat Gadgetbridge sedang terhubung</string>
+    <string name="pref_title_connected_advertisement">Terlihat saat sedang terhubung</string>
+    <string name="pref_summary_pebble_mtu_limit">Jika Pebble 2/Pebble LE Anda tidak bekerja sesuai harapan, coba tetapkan pengaturan ini untuk membatasi MTU (jangka absah 20鈥�512)</string>
+    <string name="pref_title_pebble_enable_applogs">Aktifkan pencatatan aplikasi jam tangan</string>
+    <string name="pref_summary_pebble_gatt_clientonly">Ini hanya untuk Pebble 2 dan eksperimental, coba ini jika Anda mengalami masalah konektivitas</string>
+    <string name="pref_title_pebble_reconnect_attempts">Percobaan koneksi ulang</string>
+    <string name="pref_summary_notifications_and_calls_enable_misscall">Mengulangi pada saat setiap menit</string>
+    <string name="pref_title_notifications_and_calls_repeat_on_misscall">Ulangi untuk X menit</string>
+    <string name="prefs_screen_orientation">Orientasi layar</string>
+    <string name="zetime_title_heart_rate_alarm">Alarm detak jantung</string>
+    <string name="zetime_analog_mode">Mode analog</string>
+    <string name="zetime_analog_mode_hands">Hanya tangan</string>
+    <string name="zetime_analog_mode_handsandsteps">Tangan dan langkah</string>
+    <string name="zetime_activity_tracking">Pelacakan aktivitas</string>
+    <string name="zetime_activity_tracking_summary">Menyalakan pelacakan aktivitas, akan menghitung langkah Anda dan lain-lain.</string>
+    <string name="pref_cover_to_mute">Tutupi untuk Membisukan</string>
+    <string name="pref_vibrate_for_alert">Getar untuk Peringatan</string>
+    <string name="pref_text_to_speech">Teks ke Suara</string>
+    <string name="offline_voice_respond_turn_wrist">Tanggapi ketika memutar pergelangan tangan</string>
+    <string name="offline_voice_respond_screen_on">Tanggapi ketika layar nyala</string>
+    <string name="offline_voice_response_during_screen_lighting">Tanggapi saat penerangan layar</string>
+    <string name="pref_agps_expiry_reminder_enabled">Pengingat Kedaluwarsa AGPS</string>
+    <string name="pref_header_auto_export">Ekspor otomatis</string>
+    <string name="pref_auto_fetch_summary">Pendapatan hanya terjadi pada pembukaan kunci layar. Hanya bekerja jika mekanisme kunci ditetapkan!</string>
+    <string name="pref_title_auto_export_enabled">Ekspor otomatis diaktifkan</string>
+    <string name="pref_header_auto_fetch">Dapatkan secara otomatis</string>
+    <string name="pref_auto_fetch_limit_fetches">Waktu minimum antara pendapatan</string>
+    <string name="discovery_scanning_intensity">Intensitas pemindaian</string>
+    <string name="connecting">Menhubungkan</string>
+    <string name="pref_screen_notification_profile_email">Notifikasi surel</string>
+    <string name="pref_screen_notification_profile_missed_call">Notifikasi panggilan tidak terjawab</string>
+    <string name="heartrate_bpm_100">100 bpm</string>
+    <string name="heartrate_bpm_105">105 bpm</string>
+    <string name="heartrate_bpm_110">110 bpm</string>
+    <string name="reminder_time_once">%1$s, sekali</string>
+    <string name="controlcenter_start_configure_alarms">Atur alarm</string>
+    <string name="controlcenter_start_configure_reminders">Atur pengingat</string>
+    <string name="pref_world_clocks_summary">Atur jam untuk zona waktu lain</string>
+    <string name="reminder_date">Tanggal</string>
+    <string name="alarm_snooze">Tidur sebentar</string>
+    <string name="bip_prefs_shotcuts_summary">Pilih pintasan pada layar gelang</string>
+    <string name="prefs_control_center_summary">Pilih item di tarik-turun pusat kendali</string>
+    <string name="lefun_prefs_hydration_reminder_summary">Gelang akan bergetar untuk mengingatkan Anda untuk minum air</string>
+    <string name="bip_prefs_shortcuts">Pintasan</string>
+    <string name="prefs_control_center">Pusat Kendali</string>
+    <string name="lefun_prefs_hydration_reminder_interval_title">Interval pengingat hidrasi (dalam menit)</string>
+    <string name="unit_imperial">Imperial</string>
+    <string name="prefs_button_single_press_action_selection_title">Tindakan Peristiwa 1</string>
+    <string name="maxStride">Langkah Maks</string>
+    <string name="maximumOxygenUptake">Serapan Oksigen Maksimum</string>
+    <string name="mi">mil</string>
+    <string name="gps_track">Trek GPS</string>
+    <string name="watchface_setting_wrist_flick_duration">Durasi (dalam md)</string>
+    <string name="hybridhr_widget_bg_dashed_circle">Lingkaran bergaris</string>
+    <string name="nothing_prefs_inear_summary">Main/jeda musik berdasarkan jika Anda memakai earbud</string>
+    <string name="prefs_voice_detect_summary">Aktifkan suara Sekitar dan kecilkan pemainan secara otomatis ketika suara telah dideteksi</string>
+    <string name="pref_title_touch_spotify">Spotify</string>
+    <string name="audio_codec">Kodek Audio</string>
+    <string name="pref_voice_detect_duration_10">10 detik</string>
+    <string name="error_deleting_device">Kesalahan menghapus perangkat: %s</string>
+    <string name="open_fw_installer_ensure_device_connected">Pastikan bahwa perangkat %s dihubungkan</string>
+    <string name="pref_summary_datetime_syctimeonconnect">Sinkronkan waktu ke perangkat Gadgetbridge ketika menghubungkan, ketika waktu atau zona waktu berubah di perangkat Android, dan secara berkala</string>
+    <string name="pref_summary_minimize_priority_on">Ikon dalam bilah status dan notifikasi di layar kunci disembunyikan</string>
+    <string name="pref_summary_notification_media_ignores_application_list">Proses notifikasi media sebelum daftar aplikasi. Jika preferensi ini tidak dicentang, aplikasi media harus diperbolehkan di daftar aplikasi untuk kendali media bekerja pada perangkat.</string>
+    <string name="pref_title_canned_messages_set">Perbarui di perangkat</string>
+    <string name="pref_header_development">Opsi pengembang</string>
+    <string name="pref_title_development_miaddr">Alamat Mi Band</string>
+    <string name="pref_title_pebble_settings">Pengaturan Pebble</string>
+    <string name="pref_header_activitytrackers">Pelacak aktivitas</string>
+    <string name="pref_title_pebble_sync_morpheuz">Sinkron Morpheuz</string>
+    <string name="pref_title_enable_outgoing_call">Dukung panggilan keluar</string>
+    <string name="pref_summary_enable_outgoing_call">Menonaktifkan ini juga akan menghentikan Pebble 2/LE untuk bergetar pada panggilan keluar</string>
+    <string name="pref_title_enable_pebblekit">Perbolehkan akses aplikasi Android pihak ketiga</string>
+    <string name="pref_title_sunrise_sunset">Matahari terbit dan matahari terbenam</string>
+    <string name="pref_summary_enable_calendar_sync">Kirim peristiwa kalender ke linimasa</string>
+    <string name="pref_title_location_aquire">Dapatkan lokasi</string>
+    <string name="toast_aqurired_networklocation">lokasi didapatkan</string>
+    <string name="pref_summary_pebble_forceprotocol">Opsi ini memaksa menggunakan protokol notifikasi terkini berdasarkan versi perangkat tegar. KETAHUI APA YANG ANDA LAKUKAN!</string>
+    <string name="pref_title_pebble_mtu_limit">Pebble 2/LE batas GATT MTU</string>
+    <string name="pref_summary_connected_advertisement">Membuat perangkat dapat ditemukan melalui Bluetooth bahkan saat terhubung</string>
+    <string name="pref_summary_device_intents">Perbolehkan aplikasi jam tangan Bangle.js untuk mengirim Intent Android, dan memperbolehkan aplikasi lain di Android (seperti Tasker) untuk mengirim data ke Bangle.js dengan Intent com.banglejs.uart.tx. Membutuhkan izin untuk menampilkan di atas aplikasi lain untuk bekerja di latar belakang.</string>
+    <string name="pref_summary_sync_calendar">Mengaktifkan peringatan kalender, bahkan saat putus hubungan</string>
+    <string name="pref_title_relax_firmware_checks">Aktifkan ini jika Anda ingin flash sebuah perangkat tegar yang tidak dimaksud untuk perangkat Anda (dengan risiko Anda sendiri)</string>
+    <string name="pref_display_add_device_fab_off">Hanya terlihat jika tidak ada perangkat yang ditambahkan</string>
+    <string name="pref_title_timeformat">Format waktu</string>
+    <string name="pref_title_notifications_and_calls_repeat_on_call">Ulangi notifikasi panggilan</string>
+    <string name="pref_header_notifications_and_calls_callhandling">Penanganan Panggilan</string>
+    <string name="prefs_notifications_and_calls_reject">Tombol abaikan/tolak panggilan</string>
+    <string name="prefs_notifications_and_calls_shake_reject">Guncang pergelangan tangan untuk mengabaikan/menolak panggilan</string>
+    <string name="pref_summary_notifications_and_calls_title_shake_reject">Menggandakan tindakan tombol jam tangan</string>
+    <string name="pref_title_device_spec_settings_show_raw_graph">Tampilkan data MENTAH di grafik aktivitas</string>
+    <string name="pref_sensors_bp_calibration">Kalibrasi tekanan darah</string>
+    <string name="pref_sensors_bp_calibration_low">Tekanan darah DIASTOLIK (rendah)</string>
+    <string name="preferences_makibes_hr3_settings">Pengaturan Makibes HR3</string>
+    <string name="zetime_date_format">Format tanggal</string>
+    <string name="zetime_date_format_3">BB/HH/TT</string>
+    <string name="zetime_prefs_inactivity_tu">Selasa</string>
+    <string name="zetime_prefs_inactivity_th">Kamis</string>
+    <string name="zetime_prefs_inactivity_fr">Jumat</string>
+    <string name="zetime_prefs_inactivity_su">Minggu</string>
+    <string name="zetime_signaling_beep">Bunyi berkelanjutan</string>
+    <string name="zetime_signaling_vibrate">Getaran berkelanjutan</string>
+    <string name="zetime_signaling_beep_twice">Bunyi dua kali</string>
+    <string name="zetime_signaling_vibrate_beep_once">Getar dan bunyi sekali</string>
+    <string name="title_activity_device_specific_settings">Perangkat spesifik perangkat</string>
+    <string name="pref_summary_authkey">Ubah kunci otentikasi ke sebuah kunci umum pada semua perangkat Android Anda yang Anda ingin hubungkan dari situ. Kunci bawaan sebelumnya untuk semua perangkat adalah 0123456789@ABCDE</string>
+    <string name="pref_explanation_authkey">Beberapa perangkat membutuhkan kunci penyandingan khusu untuk initialisasi paling pertama dari perangkat. Ketuk di sini untuk informasi lanjut di wiki.</string>
+    <string name="pref_explanation_authkey_new_protocol">Jika Anda mendapatkan pesan \"Perbarui aplikasi ke versi baru\" atau \"Update app to latest version\" pada gelang, pastikan untuk mencentang \"Protokol Otentikasi Baru\" di atas. Ketuk di sini untuk informasi lanjut di wiki.</string>
+    <string name="prefs_hr_alarm_activity">Alarm detak jantung saat aktivitas olahraga</string>
+    <string name="pref_gps_header">GPS</string>
+    <string name="pref_gps_mode_preset">Mode GPS</string>
+    <string name="pref_gps_combination">Kombinasi GPS</string>
+    <string name="pref_crown_vibration">Getaran Mahkota</string>
+    <string name="pref_alert_tone">Bunyi Peringatan</string>
+    <string name="pref_agps_expiry_reminder_time">Waktu Pengingat Kedaluwarsa AGPS</string>
+    <string name="pref_workout_send_gps_title">Kirim GPS saat olahraga</string>
+    <string name="pref_workout_detection_title">Deteksi Olahraga</string>
+    <string name="pref_workout_detection_summary">Deteksi olahraga secara otomatis</string>
+    <string name="pref_workout_detection_categories_title">Kategori Olahraga</string>
+    <string name="pref_workout_detection_categories_summary">Kategori olahraga untuk dideteksi secara otomatis</string>
+    <string name="pref_workout_detection_alert_title">Peringatan</string>
+    <string name="pref_workout_detection_alert_summary">Beri tahu ketika olahraga dideteksi</string>
+    <string name="pref_workout_detection_sensitivity">Sensitivitas</string>
+    <string name="pref_sleep_mode_sleep_screen_title">Layar Tidur</string>
+    <string name="pref_sleep_mode_sleep_screen_summary">Tampilkan Layar Tidur ketika membangunkan layar saat mode tidur, untuk mengurangi gangguan</string>
+    <string name="pref_sleep_mode_smart_enable_title">Pengaktifan Pintar</string>
+    <string name="pref_sleep_mode_smart_enable_summary">Aktifkan mode tidur secara otomatis ketika memakai gelang saat tidur</string>
+    <string name="discovery_scanning_intensity_warning">Jika Anda mengalami pembekuan atau tidak menanggapi, coba atur Intensitas pemindaian ke tingkat yang rendah. Jika perangkat Anda tidak ditemukan, coba atur Intesitas pemindaian ke tingkat yang tinggi.</string>
+    <string name="pref_summary_disable_new_ble_scanning">Centang opsi ini jika perangkat Anda tidak dapat ditemukan saat penemuan</string>
+    <string name="connected">Terhubung</string>
+    <string name="test">Uji coba</string>
+    <string name="test_notification">Uji coba notifikasi</string>
+    <string name="this_is_a_test_notification_from_gadgetbridge">Ini adalah notifikasi uji coba dari Gadgetbridge</string>
+    <string name="tap_connected_device_for_app_mananger">Ketuk perangkat terhubung untuk pengelola aplikasi</string>
+    <string name="tap_connected_device_for_vibration">Ketuk perangkat terhubung untuk getaran</string>
+    <string name="tap_a_device_to_connect">Ketuk sebuah perangkat untuk menghubungkan</string>
+    <string name="installation_successful">Terpasang</string>
+    <string name="app_install_info">Anda akan memasang aplikasi berikut: 
+\n 
+\n%1$s 
+\nVersi %2$s oleh %3$s
+\n</string>
+    <string name="android_pairing_hint">Gunakan dialog penyandingan Bluetooth Android untuk menyanding perangkat.</string>
+    <string name="title_activity_mi_band_pairing">Sandingkan Mi Band Anda</string>
+    <string name="pairing_creating_bond_with">Membuat hubungan dengan %1$s (%2$s)</string>
+    <string name="pairing_unable_to_pair_with">Tidak dapat menyanding dengan %1$s (%2$s)</string>
+    <string name="pairing_in_progress">Proses menjalin sedang dilakukan: %1$s (%2$s)</string>
+    <string name="preferences_category_device_specific_settings">Pengaturan spesifik perangkat</string>
+    <string name="other">Lain</string>
+    <string name="vertical">Vertikal</string>
+    <string name="buttons_on_right">Tombol di kanan</string>
+    <string name="miband_pairing_tap_hint">Ketika Mi Band Anda bergetar dan berkedip, ketuk beberapa kali.</string>
+    <string name="candidate_item_device_image">Gambar perangkat</string>
+    <string name="miband_prefs_alias">Nama/Alias</string>
+    <string name="title_activity_sleepmonitor">Pemantauan tidur</string>
+    <string name="pref_write_logfiles">Tulis berkas catatan</string>
+    <string name="pref_cache_weather">Simpan informasi cuaca</string>
+    <string name="sleep_activity_date_range">Dari %1$s sampai %2$s</string>
+    <string name="prefs_wearside">Memakai di kiri atau kanan\?</string>
+    <string name="prefs_weardirection">Arah pemakaian</string>
+    <string name="vibration_profile_default">Bawaan</string>
+    <string name="vibration_profile_short">Pendek</string>
+    <string name="vibration_profile_medium">Sedang</string>
+    <string name="vibration_profile_waterdrop">Tetesan air</string>
+    <string name="vibration_profile_ring">Dering</string>
+    <string name="vibration_try">Coba</string>
+    <string name="pref_screen_notification_profile_sms">Notifikasi SMS</string>
+    <string name="pref_screen_notification_profile_low_power">Peringatan daya rendah</string>
+    <string name="pref_screen_notification_profile_todo_list">Daftar tugas</string>
+    <string name="prefs_title_heartrate_measurement_interval">Pengukuran detak jantung seharian</string>
+    <string name="pref_screen_notification_profile_find_device">Temukan perangkat</string>
+    <string name="pref_screen_vibration_patterns_title">Pola Getaran</string>
+    <string name="interval_ten_minutes">setiap 10 menit</string>
+    <string name="heartrate_bpm_45">45 bpm</string>
+    <string name="heartrate_bpm_50">50 bpm</string>
+    <string name="heartrate_bpm_115">115 bpm</string>
+    <string name="heartrate_bpm_120">120 bpm</string>
+    <string name="heartrate_bpm_140">140 bpm</string>
+    <string name="heartrate_bpm_145">145 bpm</string>
+    <string name="interval_one_hour">setiap jam</string>
+    <string name="stats_title">Zona kecepatan</string>
+    <string name="stats_x_axis_label">Jumlah menit</string>
+    <string name="stats_y_axis_label">Langkah per menit</string>
+    <string name="control_center_find_lost_device">Temukan perangkat hilang</string>
+    <string name="title_activity_set_reminders">Atur pengingat</string>
+    <string name="pref_world_clocks_title">Jam Dunia</string>
+    <string name="reminder_time_every_day">%1$s, setiap hari</string>
+    <string name="reminder_every_day">Setiap hari</string>
+    <string name="reminder_every_week">Setiap minggu</string>
+    <string name="reminder_no_free_slots_title">Tidak ada tempat kosong</string>
+    <string name="title_activity_reminder_details">Detail pengingat</string>
+    <string name="prefs_heartrate_alert_experimental_title">Peringatan detak jantung (eksperimental)</string>
+    <string name="prefs_stress_monitoring_description">Pantau tingkat stres saat beristirahat</string>
+    <string name="prefs_spo2_monitoring_description">Pantau oksigen darah secara otomatis seharian</string>
+    <string name="prefs_spo2_alert_threshold">Peringatan ambang SPO2</string>
+    <string name="prefs_activity_monitoring_title">Pemantauan aktivitas</string>
+    <string name="prefs_activity_monitoring_description">Tingkatkan frekuensi deteksi detak jantung secara otomatis ketika gelang mendeteksi olahraga fisik, untuk meningkatkan akurasi pendapatan detak jantung.</string>
+    <string name="controlcenter_start_activitymonitor">Aktivitas Anda</string>
+    <string name="updatefirmwareoperation_update_complete">Pemasangan perangkat tegar selesai</string>
+    <string name="updatefirmwareoperation_update_complete_rebooting">Pemasangan perangkat tegar selesai, memulai ulang perangkat鈥�</string>
+    <string name="distance">Jarak</string>
+    <string name="heart_rate_result">Hasil pengukuran</string>
+    <string name="movement_intensity">Intensitas gerakan</string>
+    <string name="seconds_25">25 detik</string>
+    <string name="seconds_30">30 detik</string>
+    <string name="minutes_1">1 menit</string>
+    <string name="lack_of_step">Kekuarangan langkah: %1$d</string>
+    <string name="pref_title_low_latency_fw_update">Gunakan mode latensi rendah untuk flash perangkat tegar</string>
+    <string name="pref_summary_low_latency_fw_update">Ini mungkin membantu pada perangkat di mana flash perangkat tegar gagal.</string>
+    <string name="live_activity_steps_history">Riwayat langkah</string>
+    <string name="miband_fwinstaller_incompatible_version">Firmware tidak kompatibel</string>
+    <string name="mi2_prefs_display_items">Tampilkan item</string>
+    <string name="mi2_prefs_button_press_count">Jumlah tekanan tombol</string>
+    <string name="live_activity_steps_per_minute_history">Riwayat langkah per menit</string>
+    <string name="fwinstaller_firmware_not_compatible_to_device">Firmware ini tidak kompatibel dengan perangkat ini</string>
+    <string name="miband_prefs_device_time_offset_hours">Pergeseran waktu perangkat dalam jam (untuk mendeteksi tidur pekerja pergeseran)</string>
+    <string name="mi2_prefs_do_not_disturb_summary">Gelang tidak akan menerima notifikasi saat aktif</string>
+    <string name="mi2_prefs_inactivity_warnings_dnd_summary">Nonaktifkan peringatan tidak aktif selama interval waktu</string>
+    <string name="prefs_always_on_display_style">Gaya</string>
+    <string name="mi2_prefs_activate_display_on_lift">Aktifkan layar pada pengangkatan</string>
+    <string name="mi2_prefs_heart_rate_monitoring_summary">Atur pemantauan detak jantung</string>
+    <string name="mi2_prefs_heart_rate_monitoring_alerts_summary">Atur ambang pemantauan dan peringatan detak jantung</string>
+    <string name="mi2_prefs_inactivity_warnings_summary">Gelang akan bergetar ketika Anda belum aktif</string>
+    <string name="polish">Polandia</string>
+    <string name="romanian">Rumania</string>
+    <string name="lefun_prefs_antilost_title">Anti kehilangan</string>
+    <string name="estonian">Estonia</string>
+    <string name="scandinavian">Skandinavia</string>
+    <string name="mi2_prefs_do_not_disturb_start">Waktu mulai</string>
+    <string name="lefun_prefs_interface_language_title">Bahasa antarmuka</string>
+    <string name="portuguese_pt">Portugis (Portugal)</string>
+    <string name="mi3_prefs_band_screen_unlock_summary">Usap ke atas untuk membuka kunci layar gelang</string>
+    <string name="mi3_prefs_night_mode_summary">Kurangi kecerahan layar secara otomatis saat malam</string>
+    <string name="activity_prefs_goal_standing_time_minutes">Sasaran setiap hari: waktu berdiri dalam menit</string>
+    <string name="pref_charts_range_on">Jangka bagan ditetapkan ke Bulan</string>
+    <string name="dbmanagementactivvity_cannot_access_export_path">Tidak dapat mengakses jalur ekspor. Mohon hubungi pengembang.</string>
+    <string name="dbmanagementactivity_database_successfully_deleted">Data dihapus.</string>
+    <string name="dbmanagementactivity_db_deletion_failed">Penghapusan basis data gagal.</string>
+    <string name="activity_web_view">Aktivitas Tampilan Web</string>
+    <string name="activity_DB_ShowContentButton">Tampilkan konten direktori ekspor/impor</string>
+    <string name="dbmanagementactivity_delete_old_activitydb_confirmation">Benar-benar hapus basis data aktivitas lawas\? Data aktivitas yang belum diimpor akan hilang.</string>
+    <string name="off">Mati</string>
+    <string name="mi2_dnd_off">Mati</string>
+    <string name="normal">Normal</string>
+    <string name="smart">Pintar</string>
+    <string name="mi2_enable_text_notifications">Notifikasi teks</string>
+    <string name="mi3_night_mode_sunset">Pada matahari terbenam</string>
+    <string name="mi2_dnd_scheduled">Dijadwalkan (interval waktu)</string>
+    <string name="activity_type_outdoor_running">Berlari Luar Ruangan</string>
+    <string name="activity_type_table_tennis">Tenis Meja</string>
+    <string name="discovery_enable_bluetooth">Aktifkan Bluetooth untuk menemukan perangkat.</string>
+    <string name="activity_type_bowling">Bowling</string>
+    <string name="activity_type_unknown">Aktivitas tidak diketahui</string>
+    <string name="activity_summary_edit_name_title">Sunting label</string>
+    <string name="activity_error_no_app_for_png">Untuk membagikan tangkapan layar, pasang sebuah aplikasi yang dapat menangani berkas gambar.</string>
+    <string name="devicetype_amazfit_bip">Amazfit Bip</string>
+    <string name="devicetype_amazfit_gtr">Amazfit GTR</string>
+    <string name="devicetype_miband2">Mi Band 2</string>
+    <string name="devicetype_amazfit_gtr4">Amazfit GTR 4</string>
+    <string name="devicetype_amazfit_band7">Amazfit Band 7</string>
+    <string name="watch9_time_hours">Jam:</string>
+    <string name="menuitem_unknown">Tidak diketahui</string>
+    <string name="menuitem_nothing">Tidak ada</string>
+    <string name="menuitem_calendar">Kalender</string>
+    <string name="menuitem_todo">Tugas</string>
+    <string name="menuitem_phone">Ponsel</string>
+    <string name="menuitem_lockscreen">Layar kunci</string>
+    <string name="menuitem_workout_history">Riwayat Olahraga</string>
+    <string name="warning">Peringatan!</string>
+    <string name="add_widget">Tambahkan widget</string>
+    <string name="appwidget_sleep_alarm_widget_label">Alarm Tidur</string>
+    <string name="widget_settings_select_device_title">Pilih perangkat</string>
+    <string name="title_activity_watch9_pairing">Penyandingan Watch 9</string>
+    <string name="widget_set_alarm_after">Tetapkan alarm setelah:</string>
+    <string name="appwidget_setting_alarm">Menetapkan alarm untuk %1$02d:%2$02d</string>
+    <string name="watch9_calibration_hint">Tetapkan waktu perangkat Anda yang ditampilkan kepada Anda sekarang.</string>
+    <string name="hr_appname_wellness">Kesehatan</string>
+    <string name="hr_appname_workout">Olahraga</string>
+    <string name="widget_10_minutes">10 menit</string>
+    <string name="qhybrid_overwrite_buttons">timpa tombol</string>
+    <string name="hr_widget_active_minutes">Menit aktif</string>
+    <string name="quick_alarm_description">Alarm dari widget</string>
+    <string name="pref_title_upper_button_long_press_action">Tindakan tekanan pendek Tombol Atas</string>
+    <string name="about_contributors">Kontributor</string>
+    <string name="about_hash">Komit %s</string>
+    <string name="pref_title_upper_button_function_long">Tombol Atas panjang</string>
+    <string name="pref_title_lower_button_short_press_action">Tindakan tekanan pendek Tombol Bawah</string>
+    <string name="about_core_team_title">Tim Inti (dalam urutan kontribusi kode pertama)</string>
+    <string name="about_additional_contributions">Banyak terima kasih kepada semua kontributor yang belum didaftarkan di sini untuk kontribusi kode, terjemahan, dukungan, ide, motivasi, laporan kutu, uang鈥� 鉁�</string>
+    <string name="discover_unsupported_devices_description">Mengaktifkan opsi ini akan menampilkan semua perangkat Bluetooth saat memindai. Ketukan pendek akan menyalin nama perangkat dan alamat MAC ke papan klip. Tekanan lama akan meluncur dialog \'Tambahkan perangkat percobaan\'. Dapat memicu masalah pembekuan aplikasi.</string>
+    <string name="discover_unsupported_devices">Temukan perangkat yang tidak didukung</string>
+    <string name="permission_granting_mandatory">Izin ini dibutuhkan dan ketidakstabilan dapat terjadi jika tidak diberikan</string>
+    <string name="elevationGain">Kenaikan ketinggian</string>
+    <string name="HeartRateZones">Zona Detak Jantung</string>
+    <string name="hrZoneNa">Tidak diketahui</string>
+    <string name="hrZoneAnaerobic">Anaerobik</string>
+    <string name="minStride">Langkah Min</string>
+    <string name="minCadence">Irama Min</string>
+    <string name="hrZoneFatBurn">Bakaran Lemak</string>
+    <string name="averageStrokeDistance">Jarak Langkah Rata-Rata</string>
+    <string name="swolfIndex">SWOLF</string>
+    <string name="swimStyle">Gaya Berenang</string>
+    <string name="km">km</string>
+    <string name="averageStrokesPerSecond">Gaya Rata-Rata</string>
+    <string name="swim_style">gaya berenang</string>
+    <string name="averageLapPace">Kecepatan Putaran Rata-Rata</string>
+    <string name="activity_filter_to_placeholder">hari ini</string>
+    <string name="breaststroke">Gaya dada</string>
+    <string name="activity_filter_reset_filter">Atur Ulang Saringan</string>
+    <string name="prefs_events_forwarding_fellsleep">Pada Tidur</string>
+    <string name="prefs_events_forwarding_wokeup">Pada Bangun</string>
+    <string name="fossil_hr_synced_activity_data">Data aktivitas disinkron</string>
+    <string name="qhybrid_calibration_align_hint">Gunakan tombol di bawah untuk menyesuaikan tangan jam ke 12.00.</string>
+    <string name="qhybrid_pref_title_actions">Tindakan</string>
+    <string name="button_watchface_edit_name">Sunting nama</string>
+    <string name="button_watchface_select_image">Ubah gambar latar belakang</string>
+    <string name="qhybrid_pref_summary_actions">Tindakan untuk aplikasi Perjalanan</string>
+    <string name="pref_summary_canned_messages_dismisscall">Abaikan panggilan dari jam tangan dengan sebuah pesan SMS</string>
+    <string name="pref_summary_physical_buttons">Atur kemampuan tombol fisik pada jam tangan</string>
+    <string name="watchface_dialog_widget_cat_generic">Generik</string>
+    <string name="qhybrid_title_watchface_designer">Perancang wajah jam</string>
+    <string name="pref_header_spo2">Oksigen Darah</string>
+    <string name="sony_equalizer_preset_bright">Terang</string>
+    <string name="sony_equalizer_preset_excited">Senang</string>
+    <string name="sony_equalizer_preset_mellow">Lembut</string>
+    <string name="sony_equalizer_preset_manual">Manual</string>
+    <string name="sony_speak_to_chat_sensitivity_low">Rendah</string>
+    <string name="sony_equalizer_preset_custom_1">Kustom 1</string>
+    <string name="sony_speak_to_chat_timeout_long">Panjang (1m)</string>
+    <string name="controlcenter_set_preferences">Atur preferensi</string>
+    <string name="pref_header_sony_equalizer_preset_custom_2">Prasetel Kustom 2</string>
+    <string name="sony_notification_voice_guide">Panduan Notifikasi &amp; Suara</string>
+    <string name="step_streak_longest">Terpanjang</string>
+    <string name="auto_reconnect_ble_summary">Coba hubungkan ke perangkat secara proaktif secara berkala</string>
+    <string name="controlcenter_toggle_details">Alih tampilan</string>
+    <string name="controlcenter_folder_name">Nama folder:</string>
+    <string name="steps_streaks_average_steps">Langkah 
+\nrata-rata</string>
+    <string name="steps_streaks_achievement_rate">Tingkat 
+\npencapaian</string>
+    <string name="connection_over_bt_classic">Hubungan melalui Bluetooth klasik</string>
+    <string name="controlcenter_set_folder_title">Tetapkan atau buat folder baru</string>
+    <string name="autoconnect_from_device_title">Hubungkan pada koneksi dari perangkat</string>
+    <string name="step_streak_days_hint">Jumlah hari berurutan dengan sasaran langkah tercapai</string>
+    <string name="supercars_turbo_speed_label">Kecepatan Turbo</string>
+    <string name="steps_streaks_total_steps_hint">Jumlah langkah dalam seluruh runtunan</string>
+    <string name="pref_title_rtl">Kanan ke Kiri</string>
+    <string name="pref_summary_rtl">Aktifkan ini jika perangkat Anda tidak dapat menampilkan bahasa kanan ke kiri</string>
+    <string name="ukranian">Ukraina</string>
+    <string name="devicetype_bfh16">BFH-16</string>
+    <string name="devicetype_watchx">Watch X</string>
+    <string name="devicetype_domyos_t540">Domyos T540</string>
+    <string name="devicetype_smaq2oss">SMA-Q2 OSS</string>
+    <string name="devicetype_fitpro">FitPro</string>
+    <string name="devicetype_sony_wh_1000xm3">Sony WH-1000XM3</string>
+    <string name="devicetype_mykronoz_zetime">MyKronoz ZeTime</string>
+    <string name="quick_alarm">Alarm cepat</string>
+    <string name="devicetype_qhybrid">Fossil Q Hybrid</string>
+    <string name="devicetype_xwatch">XWatch</string>
+    <string name="ascentDistance">Jarak naik</string>
+    <string name="gps_bds">GPS + BDS</string>
+    <string name="application_name_banglejs_nightly">Bangle.js Gadgetbridge (Nightly)</string>
+    <string name="about_description_banglejs_nightly">Aplikasi pendamping Android untuk Bangle.js dibuat di atas proyek Gadgetbridge, dengan tambahan akses internet. 
+\n 
+\nKarena kebijakan Google Play Store, kami tidak diperbolehkan untuk menambahkan tautan donasi dalam aplikasi sendiri, tetapi jika Anda suka aplikasi ini pertimbangkan untuk berdonasi melalui laman beranda Gadgetbridge di bawah.</string>
+    <string name="application_name_main_nightly">Gadgetbridge (Nightly)</string>
+    <string name="dual_band">Jalur Ganda</string>
+    <string name="fw_upgrade_notice_amazfitgtr">Anda akan memasang perangkat tegar %s pada Amazfit GTR Anda. 
+\n 
+\nPastikan Anda memasang berkas .fw, dan setelah itu berkas .res, dan akhirnya berkas .gps. Jam tangan Anda akan dinyalakan ulang setelah memasang berkas .fw. 
+\n 
+\nCatatan: Anda tidak harus memasang .res dan .gps jika berkas ini sama persis dengan yang sebelumnya dipasang. 
+\n 
+\nLANJUTKAN DENGAN RISIKO ANDA SENDIRI!</string>
+    <string name="title_activity_controlcenter_banglejs_main">Bangle.js Gadgetbridge</string>
+    <string name="about_description_main_nightly">Pengganti untuk aplikasi gawai Android sumber tertutup dari vendor yang bebas dan tanpa awan. Rilis nightly dari Gadgetbridge. Tidak dapat dipasang jika Anda sudah punya aplikasi Gadgetbridge atau Pebble, karena konflik dalam penyedia Pebble.</string>
+    <string name="title_activity_controlcenter_main_nopebble">Gadgetbridge Nightly Tanpa Pebble</string>
+    <string name="about_description_main_nopebble">Pengganti untuk aplikasi gawai Android sumber tertutup dari vendor yang bebas dan tanpa awan. Rilis nightly dari Gadgetbridge. Versi ini mengubah nama penyedia Pebble untuk mencegah konflik, jadi beberapa integrasi berkaitan dengan Pebble tidak akan bekerja, tetapi dapat dipasang di samping pemasangan Gadgetbridge yang sudah ada.</string>
+    <string name="gps">GPS</string>
+    <string name="fw_upgrade_notice_amazfitgts">Anda akan memasang perangkat tegar %s pada Amazfit GTS Anda. 
+\n 
+\nPastikan Anda memasang berkas .fw, dan setelah itu berkas .res, dan akhirnya berkas .gps. Jam tangan Anda akan dinyalakan ulang setelah memasang berkas .fw. 
+\n 
+\nCatatan: Anda tidak harus memasang .res dan .gps jika berkas ini sama persis dengan yang sebelumnya dipasang. 
+\n 
+\nLANJUTKAN DENGAN RISIKO ANDA SENDIRI!</string>
+    <string name="fw_multi_upgrade_notice">Anda akan memasang perangkat tegar %1$s dan %2$s, daripada yang sudah ada saat ini pada Mi Band Anda.</string>
+    <string name="about_activity_title_main_nightly">Tentang Gadgetbridge Nightly</string>
+    <string name="custom">Kustom</string>
+    <string name="gps_galileo">GPS + GALILEO</string>
+    <string name="fw_upgrade_notice_amazfit_band7">Anda akan memasang perangkat tegar %s pada Amazfit Band 7 Anda. 
+\n 
+\nGelang Anda akan dinyalakan ulang setelah memasang berkas .zip. 
+\n 
+\nLANJUTKAN DENGAN RISIKO ANDA SENDIRI!</string>
+    <string name="fw_upgrade_notice_amazfitcor2">Anda akan memasang perangkat tegar %s pada Amazfit Cor 2 Anda. 
+\n 
+\nPastikan Anda memasang berkas .fw, dan setelah itu berkas .res. Gelang Anda akan dinyalakan ulang setelah memasang berkas .fw. 
+\n 
+\nCatatan: Anda tidak harus memasang .res jika sama persis dengan yang sebelumnya dipasang. 
+\n 
+\nLANJUTKAN DENGAN RISIKO ANDA SENDIRI! 
+\n 
+\nSAMA SEKALI BELUM DICOBA, MUNGKIN ANDA HARUS FLASH SEBUAH FIRMWARE BEATS_W JIKA NAMA PERANGKAT ANDA ADALAH \"Amazfit Band 2\"</string>
+    <string name="fw_upgrade_notice_miband6">Anda akan memasang perangkat tegar %s pada Mi Band 6 Anda. 
+\n 
+\nPastikan Anda memasang berkas .fw, dan setelah itu berkas .res. Gelang Anda akan dinyalakan ulang setelah memasang berkas .fw. 
+\n 
+\nCatatan: Anda tidak harus memasang .res jika sama persis dengan yang sebelumnya dipasang. 
+\n 
+\nLANJUTKAN DENGAN RISIKO ANDA SENDIRI!</string>
+    <string name="pref_title_banglejs_txt_bitmap_size">Ukuran Teks Bitmap</string>
+    <string name="pref_summary_banglejs_txt_bitmap_size">Ukuran yang digunakan untuk rendering teks bitmap</string>
+    <string name="pref_summary_banglejs_webview_url">Jika Anda ingin pemuat aplikasi kustom letakkan URL https://鈥�/android.html di sini. Jika tidak tinggalkan kosong untuk https://banglejs.com/apps</string>
+    <string name="pref_title_pebble_sync_health">Sinkron Kesehatan Pebble</string>
+    <string name="pref_summary_enable_pebblekit">Aktifkan dukungan eksperimental untuk aplikasi Android menggunakan PebbleKit</string>
+    <string name="pref_header_pebble_timeline">Linimasa Pebble</string>
+    <string name="pref_title_unit_system">Unit</string>
+    <string name="pref_header_sensors_calibration">Kalibrasi Sensor</string>
+    <string name="zetime_title_alarm_signaling">Tetapkan jenis isyarat untuk alarm</string>
+    <string name="pref_title_pebble_sync_misfit">Sinkron Misfit</string>
+    <string name="pref_summary_custom_deviceicon">Tampilkan sebuah ikon notifikasi Android spesifik perangkat daripada ikon Gadgetbridge ketika terhubung</string>
+    <string name="pref_title_autoremove_notifications">Hapus notifikasi yang diabaikan secara otomatis</string>
+    <string name="pref_summary_autoremove_notifications">Notifikasi dihapus dari perangkat secara otomatis ketika diabaikan dari ponsel</string>
+    <string name="pref_pebble_privacy_mode_content">Geser teks notifikasi di luar layar</string>
+    <string name="pref_title_location_latitude">Garis lintang</string>
+    <string name="pref_title_location_longitude">Garis bujur</string>
+    <string name="prefs_notifications_and_calls_continious_ring">Notifikasi saat panggilan ponsel</string>
+    <string name="pref_summary_device_spec_settings_title_force_time">Paksa sinkron waktu pada hubungan ulang. Tangan analog mungkin menampilkan waktu yang salah!</string>
+    <string name="pref_gps_band">Gelang GPS</string>
+    <string name="pref_title_custom_deviceicon">Tampilkan ikon notifikasi spesifik perangkat</string>
+    <string name="zetime_prefs_inactivity_sa">Sabtu</string>
+    <string name="pref_pebble_privacy_mode_complete">Hanya tampilkan ikon notifikasi</string>
+    <string name="pref_gps_satellite_search">Pencarian Satelit</string>
+    <string name="preferences_miband_settings">Pengaturan Mi Band / Amazfit</string>
+    <string name="watchface_install_info">Anda akan memasang wajah jam berikut: 
+\n 
+\n%1$s 
+\nVersi %2$s oleh %3$s
+\n</string>
+    <string name="world_clock_delete_confirm_title">Hapus \'%1$s\'</string>
+    <string name="title_activity_world_clock_details">Detail Jam Dunia</string>
+    <string name="buttons_on_left">Tombol di kiri</string>
+    <string name="discovery_connected_devices_hint">Buat perangkat Anda dapat ditemukan. Perangkat yang saat ini terhubung biasanya tidak akan ditemukan. Aktifkan lokasi (mis. GPS) di Android 6+. Nonaktifkan Perlindungan Privasi untuk Gadgetbridge, karena mungkin mogok dan memulai ulang ponsel Anda. Jika tidak ada perangkat yang ditemukan setelah beberapa menit, coba lagi setelah memulai ulang perangkat ponsel Anda.</string>
+    <string name="discovery_need_to_enter_authkey">Perangkat ini membutuhkan kunci otentikasi rahasia, tekan lama pada perangkat untuk memasukkannya. Baca wiki.</string>
+    <string name="pref_screen_notification_profile_calendar">Notifikasi kalender</string>
+    <string name="interval_fifteen_minutes">setiap 15 menit</string>
+    <string name="interval_thirty_minutes">setiap 30 menit</string>
+    <string name="heartrate_bpm_150">150 bpm</string>
+    <string name="user_feedback_miband_activity_data_transfer">Akan mentransfer data %1$s dimulai dari %2$s</string>
+    <string name="reminder_no_free_slots_description">Perangkat ini tidak memiliki tempat kosong untuk pengingat (jumlah tempat: %1$s)</string>
+    <string name="prefs_heartrate_alert_experimental_description">Getar gelang ketika detak jantung melebihi ambang, tanpa aktivitas fisik apa pun yang ada dalam 10 menit terakhir. Fitur ini eksperimental, dan tidak dicoba secara lebih dalam.</string>
+    <string name="overstep">Terlalu banyak langkah: %1$d</string>
+    <string name="average">Rata-rata: %1$s</string>
+    <string name="error_background_service_reason_truncated">Pemulaian layanan latar belakang gagal karena鈥�</string>
+    <string name="pref_check_permission_status_summary">Periksa dan tanya untuk izin yang belum ada bahkan ketika mereka belum dibutuhkan secara langsung. Hanya nonaktifkan ini jika perangkat Anda sebenarnya mendukung fitur apa pun. Tidak memberikan izin mungkin akan mengalami masalah!</string>
+    <string name="devicetype_sony_wf_1000xm4">Sony WF-1000XM4</string>
+    <string name="kind_gps_almanac">GPS Almanac</string>
+    <string name="gpx_receiver_files_received">Berkas GPX yang diterima:</string>
+    <string name="activity_summaries">Aktivitas Olahraga</string>
+    <string name="menuitem_eject_water">Keluarkan Air</string>
+    <string name="prefs_autolight">Lampu otomatis</string>
+    <string name="devicetype_watch9">Watch 9</string>
+    <string name="sports_activity_quick_filter_last_month">Bulan sebelumnya</string>
+    <string name="sports_activity_quick_filter_30days">30 hari</string>
+    <string name="italian">Italia</string>
+    <string name="french">Prancis</string>
+    <string name="notification_channel_name">Umum</string>
+    <string name="notification_channel_high_priority_name">Prioritas Tinggi</string>
+    <string name="timeformat_am_pm">AM/PM</string>
+    <string name="kind_firmware">Firmware</string>
+    <string name="activity_filter_filter_title">Saring</string>
+    <string name="hrZoneAerobic">Aerobik</string>
+    <string name="mi2_prefs_do_not_disturb_end">Waktu akhir</string>
+    <string name="hrZoneWarmUp">Pemanasan</string>
+    <string name="pref_screen_notification_profile_alarm_clock">Jam alarm</string>
+    <string name="prefs_password">Kata sandi</string>
+    <string name="activity_type_pilates">Pilates</string>
+    <string name="activity_type_volleyball">Bola voli</string>
+    <string name="sony_button_mode_volume_control">Kendali Volume</string>
+    <string name="menuitem_female_health">Kesehatan Wanita</string>
+    <string name="menuitem_membership_cards">Kartu Keanggotaan</string>
+    <string name="elevationLoss">Kehilangan ketinggian</string>
+    <string name="menuitem_unsupported">[TIDAK DIDUKUNG] %s</string>
+    <string name="menuitem_theater_mode">Mode Teater</string>
+    <string name="devicetype_amazfit_bip_lite">Amazfit Bip Lite</string>
+    <string name="pref_title_overwrite_settings_on_connection">Timpa pengaturan saat menghubungkan</string>
+    <string name="pref_summary_overwrite_settings_on_connection">Ketika menghubungkan ke gelang, timpa semua pengaturan yang di situ.</string>
+    <string name="zetime_signaling_vibrate_beep">Getaran dan bunyi berkelanjutan</string>
+    <string name="mi5_prefs_workout_activity_types">Jenis Aktivitas Olahraga</string>
+    <string name="mi5_prefs_workout_activity_types_summary">Pilih jenis aktivitas untuk ditampilkan di layar olahraga</string>
+    <string name="swolf_index">indeks swolf</string>
+    <string name="strokes_second">str/d</string>
+    <string name="pref_title_middle_button_function_long">Tombol Tengah panjang</string>
+    <string name="pref_title_upper_button_function_double">Tombol Atas ganda</string>
+    <string name="pref_title_middle_button_function_double">Tombol Tengah ganda</string>
+    <string name="devicetype_amazfit_pop_pro">Amazfit Pop Pro</string>
+    <string name="prefs_always_on_display_follow_watchface">Gaya mengikuti wajah jam</string>
+    <string name="dbmanagementactivity_error_exporting_shared">Kesalahan mengekspor preferensi: %1$s</string>
+    <string name="mi2_prefs_inactivity_warnings_threshold">Ambang tidak aktif (dalam menit)</string>
+    <string name="dbmanagementactivity_delete_old_activity_db">Hapus basis data aktivitas lawas\?</string>
+    <string name="pref_title_keep_data_on_device">Simpan data aktivitas di perangkat</string>
+    <string name="mi2_prefs_goal_notification_summary">Gelang akan bergetar ketika sasaran langkah dicapai</string>
+    <string name="mi2_prefs_button_action_summary">Aktifkan tindakan pada jumlah tekanan tombol yang ditetapkan</string>
+    <string name="charts_min_max_heartrate_popup">Detak jantung terendah: %1$d 
+\nDetak jantung tertinggi: %2$d 
+\nIntensitas gerakan:%3$s</string>
+    <string name="activity_db_management_empty_db_warning">Peringatan! Dengan menekan tombol ini Anda akan menghapus basis data Anda dan mulai dari awal.</string>
+    <string name="dbmanagementactivity_really_delete_entire_db">Benar-benar hapus seluruh basis data\? Semua data aktivitas dan informasi tentang perangkat Anda akan hilang.</string>
+    <string name="permission_location">%1$s membutuhkan akses ke lokasi Anda di latar belakang untuk memungkinkan untuk tetap terhubung ke jam tangan Anda bahkan ketika layar Anda mati. \n \nMohon ketuk \'%2$s\' untuk menerima.</string>
+    <string name="permission_notification_listener">%1$s membutuhkan akses ke Notifikasi untuk dapat ditampilkan di jam tangan Anda ketika layar ponsel Anda mati. 
+\n 
+\nMohon ketuk \'%2$s\'\' lalu \'%1$s\' dan aktifkan \'Perbolehkan Akses Notifikasi\', lalu ketuk \'Kembali\' untuk kembali ke %1$s.</string>
+    <string name="permission_display_over_other_apps">%1$s membutuhkan izin untuk menampilkan di atas aplikasi lain untuk memperbolehkan jam tangan Bangle.js untuk memulai aktivitas melalui intent ketika %1$s berada dalam latar belakang. 
+\n 
+\nIni dapat digunakan untuk memulai sebuah aplikasi musik dan memutarkan lagu, dan banyak hal lain. 
+\n 
+\nMohon ketuk \'%2$s\' lalu \'%1$s\' dan aktifkan \'Izinkan tampil di atas aplikasi lain\', lalu ketuk \'Kembali\' untuk kembali ke %1$s. 
+\n 
+\nUntuk menghentikan %1$s meminta izin pergi ke \'Pengaturan\' dan hapus centang \'Periksa status izin\'. 
+\n 
+\nPastikan untuk memberikan %1$s izin yang dibutuhkan untuk berfungsi sesuai yang Anda harapkan.</string>
+    <string name="activity_db_management_import_export_explanation">Operasi ekspor/impor menggunakan jalur berikut (lihat di bawah) ke sebuah direktori di perangkat Anda. Direktori ini dapat diakses oleh aplikasi Android lain dan komputer Anda. Dicatat, bahwa direktori dan semua berkas yang berisi akan dihapus jika Anda menghapus Gadgetbridge. Data berisi: 
+\n Export_preference - pengaturan global 
+\n Export_preference_MAC - pengaturan spesifik perangkat
+\n Gadgetbridge - basis data perangkat dan aktivitas
+\n Gadgetbridge_date - basis data yang diekspor pada sebuah tanggal 
+\n *.gpx - rekaman GPS 
+\n *.log - berkas catatan
+\nPastikan Anda sudah tahu tempat untuk mencari berkas yang telah diekspor (atau tempatkan berkas yang Anda ingin impor) di situ:</string>
+    <string name="function_enabled">Diaktifkan</string>
+    <string name="world_clock_code">Kode</string>
+    <string name="pref_title_touch_spotify_galaxy_app">Spotify (aplikasi Galaxy Wearable saja</string>
+    <string name="prefs_touch_lock_buds2">Kendali sentuh</string>
+    <string name="pref_title_touch_spotify_official_app">Spotify (aplikasi resmi saja)</string>
+    <string name="devicetype_galaxybuds_2">Galaxy Buds2</string>
+    <string name="devicetype_amazfit_gts4">Amazfit GTS 4</string>
+    <string name="fw_upgrade_notice_amazfit_gts4">Anda akan memasang perangkat tegar %s pada Amazfit GTS 4 Anda.
+\n
+\nGelang Anda akan dinyalakan ulang setelah memasang berkas .zip.
+\n
+\nLANJUTKAN DENGAN RISIKO ANDA SENDIRI!</string>
+    <string name="fw_upgrade_notice_amazfit_gts4_mini">Anda akan memasang perangkat tegar %s pada Amazfit GTS 4 Mini Anda.
+\n
+\nGelang Anda akan dinyalakan ulang setelah memasang berkas .zip.
+\n
+\nLANJUTKAN DENGAN RISIKO ANDA SENDIRI!</string>
+    <string name="devicetype_amazfit_gts4_mini">Amazfit GTS 4 Mini</string>
+    <string name="devicetype_sony_linkbuds_s">Sony LinkBuds S</string>
+    <string name="intent_api_broadcast_export_title">Siarkan pada pengeksporan basis data</string>
+    <string name="pref_header_intent_api">API Intent</string>
+    <string name="intent_api_allow_activity_sync_title">Perbolehkan pemicuan penyinkronan aktivitas</string>
+    <string name="intent_api_allow_activity_sync_summary">Perbolehkan memicu penyinkronan aktivitas melalui API Intent</string>
+    <string name="intent_api_allow_trigger_export_title">Perbolehkan pengeksporan basis data</string>
+    <string name="intent_api_allow_trigger_export_summary">Perbolehkan memicu pengeksporan basis data melalui API Intent</string>
+    <string name="intent_api_broadcast_export_summary">Siarkan intent ketika pengeksporan basis data selesai</string>
+    <string name="sony_ambient_sound_control_button_mode_nc_as_off">Pembatalan Kebisingan, Suara Sekitar, Mati</string>
+    <string name="sony_ambient_sound_control_button_mode_nc_as">Pembatalan Kebisingan, Suara Sekitar</string>
+    <string name="sony_ambient_sound_control_button_mode_nc_off">Pembatalan Kebisingan, Mati</string>
+    <string name="sony_quick_access_double_tap">Akses Cepat (Ketuk Dua Kali)</string>
+    <string name="sony_quick_access_triple_tap">Akses Cepat (Ketuk Tiga Kali)</string>
+    <string name="sony_ambient_sound_control_button_modes">Mode Tombol Kontrol Suara Sekitar</string>
+    <string name="sony_ambient_sound_control_button_mode_as_off">Suara Sekitar, Mati</string>
+    <string name="menuitem_wechat_pay">WeChat Pay</string>
+    <string name="pref_agps_update_time">Waktu Pembaruan AGPS</string>
+    <string name="pref_agps_expire_time">Waktu Kedaluwarsa AGPS</string>
+    <string name="appmanager_app_start">Mulai</string>
+    <string name="appmanager_downloaded_to_cache">%s telah diunduh ke tembolok</string>
+    <string name="appmanager_download_app_error">Terjadi kesalahan mengunduh aplikasi</string>
+    <string name="appmanager_watchface_activate">Aktifkan</string>
+    <string name="appmanager_app_download">Unduh ke tembolok</string>
+    <string name="appmanager_download_started">Pengunduhan aplikasi dimulai</string>
+    <string name="devicetype_asteroidos">AsteroidOS</string>
+    <string name="fw_upgrade_notice_amazfit_trex2">Anda akan memasang perangkat tegar %s pada Amazfit T-Rex 2 Anda.
+\n
+\nGelang Anda akan dinyalakan ulang setelah memasang berkas .zip.
+\n
+\nLANJUTKAN DENGAN RISIKO ANDA SENDIRI!</string>
+    <string name="devicetype_amazfit_trex_2">Amazfit T-Rex 2</string>
+    <string name="pref_workout_detection_enabled_summary">Aktifkan deteksi otomatis untuk olahraga ini</string>
+    <string name="pref_workout_detection_ask_first">Tanyakan saya terlebih dahulu</string>
+    <string name="pref_workout_detection_ask_first_summary">Membutuhkan konfirmasi pada jam ketika mendeteksi olahraga ini</string>
+    <string name="pref_workout_detection_time">Menit aktif sebelum deteksi</string>
+    <string name="activity_type_rowing">Mendayung</string>
+    <string name="pref_workout_detection_time_summary">Jumlah menit olahraga harus berlangsung sebelum mendeteksinya</string>
+    <string name="pref_workout_detection_enabled">Deteksi diaktifkan</string>
+    <string name="georgian">Georgia</string>
+    <string name="devicetype_soflow_s06">SoFlow SO6</string>
+    <string name="devicetype_galaxybuds_2_pro">Galaxy Buds2 Pro</string>
+    <string name="pref_title_lock_unlock">Kunci</string>
+    <string name="pref_workout_keep_screen_on_title">Biarkan layar nyala saat berolahraga</string>
+    <string name="pref_camera_remote_title">Remot Kamera</string>
+    <string name="pref_camera_remote_summary">Memperbolehkan jam tangan untuk memicu kamera ponsel</string>
+    <string name="wifi_ssid">SSID</string>
+    <string name="wifi_hotspot_status">Keadaan Hotspot Wi-Fi</string>
+    <string name="wifi_hotspot_stop_summary">Hentikan hotspot Wi-Fi di jam tangan</string>
+    <string name="croatian">Kroasia</string>
+    <string name="status">Keadaan</string>
+    <string name="wifi_hotspot">Hotspot Wi-Fi</string>
+    <string name="wifi_hotspot_summary">Atur hotspot Wi-Fi di jam tangan</string>
+    <string name="ftp_server_configuration">Konfigurasi Server FTP</string>
+    <string name="pref_workout_keep_screen_on_summary">Layar akan terus nyala saat berolahraga, dan kecerahan akan diatur untuk menampilkan data olahraga waktu nyata secara berkelanjutan</string>
+    <string name="stop">Berhenti</string>
+    <string name="wifi_hotspot_configuration">Konfigurasi Hotspot Wi-Fi</string>
+    <string name="ftp_server_status">Keadaan Server FTP</string>
+    <string name="wifi_hotspot_start_summary">Mulai hotspot Wi-Fi di jam tangan</string>
+    <string name="ftp_server">Server FTP</string>
+    <string name="ftp_server_start_summary">Mulai server FTP di jam tangan</string>
+    <string name="address">Alamat</string>
+    <string name="ftp_server_summary">Kontrol server FTP di jam tangan</string>
+    <string name="ftp_server_stop_summary">Hentikan server FTP di jam tangan</string>
+    <string name="ftp_server_root_dir">Direktori Akar</string>
+    <string name="username">Nama pengguna</string>
+    <string name="sony_speak_to_chat_summary">Nonaktifkan peredam bising secara otomatis saat Anda mulai berbicara.</string>
+    <string name="pref_summary_notification_cache_while_disconnected">Kirim notifikasi yang terlewat ketika sebuah perangkat menghubungkan kembali setelah di luar jangka</string>
+    <string name="pref_morning_updates_categories_title">Kategori Pembaruan Pagi</string>
+    <string name="pref_title_notification_cache_while_disconnected">Tembolok saat di luar jangka</string>
+    <string name="pref_morning_updates_title">Pembaruan Pagi</string>
+    <string name="pref_morning_updates_summary">Menampilkan pembaruan setiap pagi</string>
+    <string name="pref_morning_updates_categories_summary">Daftar kategori untuk ditampilkan setiap pagi</string>
+    <string name="yesterdays_activity">Aktivitas Kemarin</string>
+    <string name="pref_summary_receive_calls_watch">Lakukan dan terima panggilan secara langsung di jam tangan</string>
+    <string name="bluetooth_calls">Panggilan Bluetooth</string>
+    <string name="bluetooth_calls_pairing">Penyandingan panggilan Bluetooth</string>
+    <string name="bluetooth_calls_settings">Pengaturan panggilan Bluetooth</string>
+    <string name="pref_display_caller_title">Tampilkan informasi kontak</string>
+    <string name="pref_display_caller_summary">Tampilkan nomor telepon atau nama untuk panggilan masuk</string>
+    <string name="pref_pair_bluetooth_calls_title">Sandingkan untuk panggilan Bluetooth</string>
+    <string name="pref_pair_bluetooth_calls_summary">Klik di sini untuk memulai proses penyandingan</string>
+    <string name="pref_pair_bluetooth_calls_help_title">Cara menerima panggilan Bluetooth</string>
+    <string name="pref_pair_bluetooth_calls_help_summary">Untuk menerima panggilan Bluetooth, Anda harus menyandingkan ponsel Anda dengan bagian jam tangan yang kedua.</string>
+    <string name="pref_pair_bluetooth_calls_help_1">1. Ketuk tombol di bawah untuk memulai proses penyandingan.</string>
+    <string name="pref_pair_bluetooth_calls_help_2">2. Pergi ke pengaturan Bluetooth ponsel Anda, dan sandingkan dengan perangkat baru yang akan muncul (nama sama dengan jam tangan Anda, tetapi dengan akhiran, mis. \"Amazfit GTR 4 - AFC8\").</string>
+    <string name="pref_pair_bluetooth_calls_help_3">3. Aktifkan pengaturan panggilan Bluetooth di bawah.</string>
+    <string name="pref_pair_bluetooth_calls_help_warning">PERINGATAN: Jika Anda mengaktifkan panggilan Bluetooth tanpa menyandingkan dengan bagian kedua, notifikasi panggilan mungkin tidak bekerja dengan seharusnya.</string>
+    <string name="pref_switch_controls_anc_ambient_off">Pembatalan bising 鈫愨啋 Ambien 鈫愨啋 Nonaktif</string>
+    <string name="sony_button_mode_help_title">Mode Tombol - Bantuan</string>
+    <string name="quick_attention">Pengingat Cepat</string>
+    <string name="sony_button_mode_help_summary">Deskripsi dari setiap mode tombol</string>
+    <string name="long_press">Tekan Lama</string>
+    <string name="single_tap">Ketuk Sekali</string>
+    <string name="continue_pressing">Lanjut Menekan</string>
+    <string name="triple_tap">Ketuk Tiga Kali</string>
+    <string name="double_tap">Ketuk Dua Kali</string>
+    <string name="menuitem_cards">Kartu</string>
+    <string name="menuitem_mi_ai">MI AI</string>
+    <string name="prefs_shortcut_cards">Kartu Pintasan</string>
+    <string name="menuitem_forecast">Prakiraan</string>
+    <string name="prefs_shortcut_cards_summary">Kartu pintasan terlihat saat mengusap ke kanan di wajah jam. Ketika sebuah aplikasi sedang berjalan, kartu yang dibuat secara otomatis tidak berpengaruh oleh pengaturan ini.</string>
+    <string name="menuitem_total_workout">Jumlah olahraga</string>
+    <string name="menuitem_recommendation">Saran</string>
+    <string name="menuitem_last_workout">Olahraga terakhir</string>
+    <string name="menuitem_vo2_max">Maks VO</string>
+    <string name="zepp_os_watchface_multiple_data">Beberapa Data</string>
+    <string name="zepp_os_watchface_rush">Cepat</string>
+    <string name="zepp_os_watchface_simplicity_data">Data Kesederhanaan</string>
+    <string name="zepp_os_watchface_vibrant">Cerah</string>
+    <string name="zepp_os_watchface_emerald_moonlight">Sinar Bulan Zamrud</string>
+    <string name="zepp_os_watchface_rotating_earth">Bumi Berputar</string>
+    <string name="zepp_os_watchface_superposition">superposisi</string>
+    <string name="zepp_os_watchface_minimalist">Minimalis</string>
+    <string name="zepp_os_watchface_red_fantasy">Fantasi Merah</string>
+    <string name="zepp_os_watchface_business_style">Gaya Bisnis</string>
+    <string name="menuitem_aqi">Indeks Kualitas Udara</string>
+    <string name="devicetype_sony_wh_1000xm5">Sony WH-1000XM5</string>
+    <string name="fossil_hr_connection_not_confirmed">Koneksi tidak dikonfirmasi di jam tangan, menggunakan mode tidak diautentikasi</string>
+    <string name="fossil_hr_confirm_connection">Harap konfirmasi di jam tangan Anda</string>
+    <string name="fw_upgrade_notice_amazfit_gtr3_pro">Anda akan memasang perangkat tegar %s pada Amazfit GTR 3 Pro Anda.
+\n
+\nGelang Anda akan dinyalakan ulang setelah memasang berkas .zip.
+\n
+\nLANJUTKAN DENGAN RISIKO ANDA SENDIRI!</string>
+    <string name="devicetype_amazfit_gtr3_pro">Amazfit GTR 3 Pro</string>
+    <string name="fossil_hr_pairing_successful">Berhasil menyandingkan dengan jam tangan</string>
+    <string name="fossil_hr_pairing_failed">Penyandingan dengan jam tangan gagal</string>
+    <string name="fossil_hr_confirmation_skipped">Melewatkan konfirmasi pada perangkat</string>
+    <string name="fossil_hr_confirmation_timeout">Waktu konfirmasi habis, melanjutkan</string>
+    <string name="gpx_route_upload_failed">Pengunggahan rute GPX gagal</string>
+    <string name="gpx_route_upload_complete">Pengunggahan rute GPX selesai</string>
+    <string name="gpx_route_upload_in_progress">Mengunggah rute GPX</string>
+    <string name="activity_error_share_failed">Pembagian berkas gagal.</string>
+    <string name="kind_gpx_route">Rute GPX</string>
+    <string name="activity_detail_share_gps_label">Bagikan Trek GPS</string>
+    <string name="activity_detail_share_raw_summary">Bagikan Ikhtisar Mentah</string>
+    <string name="activity_detail_share_raw_details">Bagikan Detail Mentah</string>
+    <string name="dev_tools">Alat Pengembang</string>
+    <string name="debug_companion_show_associated">Tampilkan perangkat pendamping terkait</string>
+    <string name="debug_companion_pair_current">Sandingkan perangkat saat ini sebagai pendamping</string>
+    <string name="title_activity_set_contacts">Atur kontak</string>
+    <string name="pref_contacts_title">Kontak</string>
+    <string name="pref_contacts_summary">Atur kontak di jam tangan</string>
+    <string name="contact_no_free_slots_description">Perangkat ini tidak memiliki tempat kosong untuk kontak (jumlah tempat: %1$s)</string>
+    <string name="title_activity_contact_details">Detail kontak</string>
+    <string name="contact_phone_number">Nomor telepon</string>
+    <string name="contact_name">Nama</string>
+    <string name="contact_missing_name">Nama kontak kosong</string>
+    <string name="contact_delete_confirm_title">Hapus kontak</string>
+    <string name="contact_missing_number">Nomor kontak kosong</string>
+    <string name="contact_delete_confirm_description">Apakah Anda yakin ingin menghapus \'%1$s\'\?</string>
+    <string name="pref_header_navigation">Navigasi</string>
+    <string name="pref_summary_navigation_forward">Teruskan petunjuk dari aplikasi navigasi ke jam tangan</string>
+    <string name="pref_title_navigation_forward">Kirim navigasi ke jam tangan</string>
+    <string name="intent_api_allow_debug_commands_summary">Perbolehkan memicu menu perintah pengawakutu melalui API Intent</string>
+    <string name="intent_api_allow_debug_commands_title">Perbolehkan Perintah Pengawakutu</string>
+    <string name="voice_service_package_title">Paket layanan suara</string>
+    <string name="voice_service_package_summary">Aplikasi yang berisi layanan menangani perintah suara</string>
+    <string name="voice_service_class_title">Kelas layanan suara</string>
+    <string name="voice_service">Layanan Suara</string>
+    <string name="voice_service_class_summary">Jalur layanan lengkap penanganan perintah suara</string>
+    <string name="pref_app_logs_summary">Aktifkan catatan dari aplikasi jam tangan</string>
+    <string name="pref_app_logs_title">Catatan aplikasi</string>
+    <string name="pref_app_logs_start_summary">Mulai mencatat dari aplikasi jam tangan</string>
+    <string name="pref_app_logs_stop_summary">Berhenti mencatat dari aplikasi jam tangan</string>
+    <string name="fwapp_install_device_not_supported">Berkas tidak dapat dipasang, perangkat tidak didukung.</string>
+    <string name="charts_legend_stress_average">Rata鈥恟ata stres</string>
+    <string name="stress_moderate">Sedang</string>
+    <string name="share_screenshot">Bagikan tangkapan layar</string>
+    <string name="stress_relaxed">Santai</string>
+    <string name="stress_high">Tinggi</string>
+    <string name="screenshot_taken">Tangkapan layar diambil</string>
+    <string name="stress_mild">Ringan</string>
+    <string name="watchface_widget_type_uv_index">Indeks UV</string>
+    <string name="pref_title_banglejs_phone_gps_network_only">Hanya gunakan jaringan untuk menentukan lokasi</string>
+    <string name="title">Judul</string>
+    <string name="pref_title_banglejs_phone_gps_enbale">Gunakan data GPS ponsel</string>
+    <string name="pref_summary_banglejs_phone_gps_enbale">Gunakan data GPS ponsel untuk menimpa data GPS dari perangkat Bangle</string>
+    <string name="pref_title_banglejs_phone_gps_update_interval">Interval pembaruan data GPS</string>
+    <string name="description">Deskripsi</string>
+    <string name="preview_image">Pratinjau gambar</string>
+    <string name="status_icon">Ikon status</string>
+    <string name="changelog_full_title">Catatan Perubahan</string>
+    <string name="changelog_show_full">Lainnya鈥�</string>
+    <string name="changelog_ok_button">Oke</string>
+    <string name="changelog_title">Apa Yang Baru</string>
+    <string name="pref_summary_banglejs_phone_gps_network_only">Hanya gunakan penyedia jaringan untuk menentukan lokasi. Hal ini akan mengurangi konsumsi daya dengan mengorbankan keakuratan. Diperlukan koneksi ulang.</string>
+    <string name="pref_summary_banglejs_phone_gps_update_interval">Interval seberapa sering posisi gps diperbarui, dalam ms</string>
+    <string name="action_changelog">Catatan Perubahan</string>
+    <string name="pai_chart_per_month">PAI per bulan</string>
+    <string name="pai_day">Pemambahan hari</string>
+    <string name="pai_plus_num">+%d</string>
+    <string name="pai_total">Jumlah</string>
+    <string name="pai_chart_per_week">PAI per minggu</string>
+    <string name="num_min">%d mnt</string>
+    <string name="loyalty_cards_catima_package">Nama paket Catima</string>
+    <string name="loyalty_cards_sync_groups_only">Hanya sinkronkan grup spesifik</string>
+    <string name="loyalty_cards_sync_groups">Grup untuk Disinkronkan</string>
+    <string name="loyalty_cards_sync_archived">Sinkronkan kartu yang terarsip</string>
+    <string name="loyalty_cards_install_catima_fail">Gagal membuka toko aplikasi untuk memasang Catima</string>
+    <string name="loyalty_cards_sync_title">Sinkronkan Kartu Loyalitas</string>
+    <string name="loyalty_cards_sync_summary">Ketuk untuk menyinkronkan kartu ke jam tangan</string>
+    <string name="loyalty_cards_catima_not_installed">Catima dibutuhkan untuk mengelola kartu loyalitas</string>
+    <string name="loyalty_cards_catima_not_compatible">Versi Catima yang terpasang tidak kompatibel dengan Gadgetbridge. Silakan perbarui Catima dan Gadgetbridge ke versi yang terbaru.</string>
+    <string name="loyalty_cards_catima">Catima</string>
+    <string name="loyalty_cards_catima_permissions_summary">Gadgetbridge perlu izin membaca pada kartu Catima untuk disinkronkan. Ketuk tombol ini untuk mengizinkan.</string>
+    <string name="loyalty_cards">Kartu Loyalitas</string>
+    <string name="loyalty_cards_install">Pasang Catima</string>
+    <string name="loyalty_cards_open_catima">Buka Catima</string>
+    <string name="loyalty_cards_sync_starred">Hanya sinkronkan kartu yang dibintangi</string>
+    <string name="loyalty_cards_syncing">Menyinkronkan %d kartu loyalitas ke perangkat</string>
+    <string name="loyalty_cards_sync">Sinkronkan</string>
+    <string name="loyalty_cards_sync_options">Opsi Sinkronisasi</string>
+    <string name="loyalty_cards_catima_permissions_title">Izin dibutuhkan</string>
+    <string name="devicetype_bohemic_smart_bracelet">Bohemic Smart Bracelet</string>
+    <string name="pref_title_mb_intents">Intent Tombol Media Siaran Secara Langsung</string>
+    <string name="pref_summary_mb_intents">Aktifkan jika kontrol media perangkat tidak berfungsi untuk aplikasi tertentu</string>
+    <string name="devicetype_vivomove_hr">Garmin Vivomove HR</string>
+    <string name="devicetype_casiogwb5600">Casio GW-B5600</string>
+    <string name="devicetype_casiogmwb5000">Casio GMW-B5000</string>
+    <string name="busy_task_fetch_sports_summaries">Mendapatkan rangkuman olahraga</string>
+    <string name="busy_task_fetch_sports_details">Mendapatkan detail olahraga</string>
+    <string name="busy_task_fetch_sleep_respiratory_rate_data">Mendapatkan data tingkat pernapasan saat tidur</string>
+    <string name="busy_task_fetch_debug_logs">Mendapatkan catatan pengawakutu</string>
+    <string name="busy_task_fetch_spo2_data">Mendapatkan data SpO2</string>
+    <string name="busy_task_fetch_stress_data">Mendapatkan data stres</string>
+    <string name="busy_task_fetch_hr_data">Mendapatkan data detak jantung</string>
+    <string name="busy_task_fetch_pai_data">Mendapatkan data PAI</string>
+    <string name="activity_prefs_goals">Sasaran</string>
+    <string name="error_no_bluetooth_connect">Akses koneksi Bluetooth harus diberikan dan diaktifkan agar pemindaian dapat bekerja dengan baik</string>
+    <string name="permission_request">%1$s memungkinkan Anda untuk mengirim pesan dan data lain dari Android ke perangkat Anda. Untuk melakukan hal ini, diperlukan izin untuk mengakses data tersebut dan tanpa izin, aplikasi ini tidak akan berfungsi dengan baik.
+\n
+\nSekarang Anda akan dihadapkan pada beberapa dialog Android yang meminta izin tersebut.
+\n
+\nSilakan ketuk \'%2$s\' untuk melanjutkan.</string>
+    <string name="activity_type_windsurfing">Selancar angin</string>
+    <string name="activity_type_football">Sepak bola</string>
+    <string name="activity_type_baseball">Bisbol</string>
+    <string name="activity_type_handball">Bola tangan</string>
+    <string name="activity_type_tennis">Tenis</string>
+    <string name="activity_type_squash">Squash</string>
+    <string name="activity_type_rugby">Rugbi</string>
+    <string name="activity_type_weightlifting">Angkat Berat</string>
+    <string name="activity_type_dancing">Menari</string>
+    <string name="activity_type_skiing">Ski</string>
+    <string name="activity_type_snowboarding">Seluncur salju</string>
+    <string name="activity_type_riding">Menunggang Kuda</string>
+    <string name="activity_type_hockey">Hoki</string>
+    <string name="activity_type_icehockey">Hoki es</string>
+    <string name="activity_type_iceskating">Seluncur Es</string>
+    <string name="activity_type_golf">Golf</string>
+    <string name="activity_type_other">Lainnya</string>
+    <string name="withings_steel_hr">Withings Steel HR</string>
+    <string name="withings_calibration_text_activity_target">Terakhir, sejajarkan jarum penunjuk aktivitas ke 100%. Perlu diketahui bahwa jarum penunjuk ini hanya bergerak searah jarum jam.</string>
+    <string name="withings_bt_calibration_previous">Sebelumnya</string>
+    <string name="withings_bt_calibration_next">Lanjutkan</string>
+    <string name="activity_type_surfing">Berselancar</string>
+    <string name="activity_type_kitesurfing">Berselancar layang-layang</string>
+    <string name="error_no_bluetooth_scan">Akses pemindaian Bluetooth harus diberikan dan diaktifkan agar pemindaian dapat bekerja dengan baik</string>
+    <string name="withings_calibration_text_hours">Silakan gunakan dial di bawah ini untuk menyelaraskan jarum penunjuk jam ke angka 12.</string>
+    <string name="withings_calibration_text_minutes">Sekarang, gunakan dial untuk menyelaraskan jarum penunjuk menit ke angka 12.</string>
+    <string name="pref_theme_dynamic">Warna dinamis</string>
+    <string name="drag_handle">seret pegangan</string>
+    <string name="preferences_miband_1_2_settings" tools:ignore="TypographyFractions">Pengaturan Mi Band 1/2</string>
+    <string name="find_my_phone_found_it">DITEMUKAN</string>
+    <string name="pref_activity_full_sync_trigger_summary">Picu sinkronisasi lengkap semua data aktivitas</string>
+    <string name="pref_activity_full_sync_trigger_title">Sinkronisasi lengkap</string>
+    <string name="pref_activity_full_sync_trigger_warning">Ini akan memicu sinkronisasi penuh semua data aktivitas dari perangkat. Ini mungkin memerlukan beberapa menit untuk selesai.</string>
+    <string name="menuitem_zepp_coach">Zepp Coach</string>
+    <string name="pref_show_changelog">Tampilkan catatan perubahan saat aplikasi dibuka</string>
+    <string name="pref_canned_message">Pesan</string>
+    <string name="preferences_miband_1_2_warning">Peringatan: Preferensi ini hanya berlaku pada Mi Band 1 dan 2.</string>
+    <string name="pref_show_changelog_summary">Tampilkan catatan perubahan sejak versi terakhir setelah Gadgetbridge diperbarui</string>
+    <string name="pref_theme_dynamic_colors_not_available_warning">Warna dinamis tidak tersedia pada perangkat Anda, hanya Android 12+ mendukung fitur ini. Gadgetbridge akan menggunakan warna Materia) 3 bawaan.</string>
+    <string name="pref_theme_dynamic_colors_explanation">Catatan: untuk tema warna dinamis, Anda perlu mengaktifkan Warna Wallpaper atau Palet Warma di pengaturan Android 12+ Anda. Jika tidak, Gadgetbridge akan menggunakan warna Material 3 bawaan.</string>
+    <string name="fw_upgrade_notice_amazfitbip3pro">Anda akan memasang perangkat tegar %s pada Amazfit Bip 3 Pro Anda. 
+\n 
+\nPastikan Anda memasang berkas .fw, dan setelah itu berkas .res. Jam tangan Anda akan dinyalakan ulang setelah memasang berkas .fw. 
+\n 
+\nCatatan: Anda tidak harus memasang .res jika sama persis dengan yang sebelumnya dipasang. 
+\n 
+\nLANJUTKAN DENGAN RISIKO ANDA SENDIRI!</string>
+    <string name="devicetype_amazfit_bip3_pro">Amazfit Bip 3 Pro</string>
+    <string name="fw_upgrade_notice_amazfit_cheetah_pro">Anda akan memasang perangkat tegar %s pada Amazfit Cheetah Pro Anda.
+\n
+\nGelang Anda akan dinyalakan ulang setelah memasang berkas .zip.
+\n
+\nLANJUTKAN DENGAN RISIKO ANDA SENDIRI!</string>
+    <string name="fw_upgrade_notice_zepp_os">Anda akan memasang perangkat tegar %s pada %s Anda.
+\n
+\nJam tangan Anda akan dinyalakan ulang setelah memasang berkas .zip.
+\n
+\nLANJUTKAN DENGAN RISIKO ANDA SENDIRI!</string>
+    <string name="device_experimental">EKSPERIMENTAL</string>
+    <string name="info_menu_structure_set">JSON struktur menu ditetapkan dalam GB</string>
+    <string name="error_invalid_menu_structure">JSON struktur menu tidak valid</string>
+    <string name="devicetype_amazfit_gtr_mini">Amazfit GTR Mini</string>
+    <string name="error_menu_companion_not_installed">\'HR Menu Companion\' mungkin belum dipasang</string>
+    <string name="devicetype_amazfit_cheetah_square">Amazfit Cheetah (Kotak)</string>
+    <string name="devicetype_amazfit_cheetah_round">Amazfit Cheetah (Bulat)</string>
+    <string name="devicetype_amazfit_trex_ultra">Amazfit T-Rex Ultra</string>
+    <string name="devicetype_amazfit_falcon">Amazfit Falcon</string>
+    <string name="button_open_menu_companion">Buka aplikasi pendamping menu</string>
+    <string name="info_menu_structure_removed">Struktur menu dihapus</string>
+    <string name="info_menu_structure_contents">Struktur menu: %s</string>
+    <string name="button_reset_menu_structure">Atur ulang struktur menu</string>
+    <string name="info_fossil_rebuild_watchface_custom_menu">Silakan bangun ulang wajah jam tangan Anda untuk menu kustom</string>
+    <string name="devicetype_amazfit_cheetah_pro">Amazfit Cheetah Pro</string>
+    <string name="devicetype_amazfit_bip5">Amazfit Bip 5</string>
+    <string name="english_au">Inggris (Australia)</string>
+    <string name="english_ca">Inggris (Kanada)</string>
+    <string name="english_gb">Inggris (Britania Raya)</string>
+    <string name="english_in">Inggris (India)</string>
+    <string name="english_us">Inggris (Amerika Serikat)</string>
+    <string name="spanish_es">Spanyol (Spanyol)</string>
+    <string name="spanish_us">Spanyol (Amerika Serikat)</string>
+    <string name="spanish_mx">Spanyol (Meksiko)</string>
+    <string name="french_ca">Prancis (Kanada)</string>
+    <string name="french_fr">Prancis (Prancis)</string>
+    <string name="updatefirmwareoperation_updateproblem_free_space">Perangkat tidak memiliki ruang penyimpanan kosong yang cukup</string>
+    <string name="updatefirmwareoperation_failed_low_mtu">MTU %1$d saat ini terlalu rendah, silakan aktitkan MTU yang tintgi dalan pengaturan perangkat dan putuskan/hubungkan ulang perangkat.</string>
+    <string name="text_receiver_activity_title">Kirim teks ke perangkat</string>
+    <string name="devicetype_miband2_hrx">Mi Band HRX</string>
+    <string name="new_discover_activity_description">Aktifkan aktivitas penemuan baru, yang seharusnya memperbaiki masalah penemuan perangkat. Nonaktifkan ini jika Anda menghadapi masalah apa pun saat mencari atau memasangkan perangkat Anda.</string>
+    <string name="new_discover_activity_title">Aktifkan aktivitas penemuan baru</string>
+    <string name="prefs_home_icon_left_item">Kiri</string>
+    <string name="prefs_wena3_title_activity">Pengaturan Aktivitas</string>
+    <string name="white">Putih</string>
+    <string name="prefs_home_icon_right_item">Kanan</string>
+    <string name="prefs_wena3_title_screen">Pengaturan Tampilan</string>
+    <string name="green">Hijau</string>
+    <string name="yellow">Kuning</string>
+    <string name="devicetype_sony_wena3">Sony Wena 3</string>
+    <string name="red">Merah</string>
+    <string name="cyan">Sian</string>
+    <string name="prefs_home_icon_center_item">Tengah</string>
+    <string name="purple">Ungu</string>
+    <string name="blue">Biru</string>
+    <string name="prefs_wena3_receive_calls_title">Pengaturan Panggilan Masuk</string>
+    <string name="prefs_wena3_button_action_name_start_timer">Mulai pewaktu aktif</string>
+    <string name="prefs_wena3_menu_icon_hint">Ikon Pengaturan selalu ditampilkan pada akhir</string>
+    <string name="prefs_wena3_notification_vibration_repetition_3">Tiga kali</string>
+    <string name="prefs_wena3_button_action_name_qrio_lock">Kunci Qrio</string>
+    <string name="prefs_wena3_auto_power_off_turn_off_time_item">Waktu daya mati</string>
+    <string name="prefs_wena3_vibration_short">Pendek</string>
+    <string name="prefs_wena3_home_icon_name_calories">Kalori</string>
+    <string name="prefs_wena3_led_none">Tanpa LED</string>
+    <string name="prefs_wena3_vibration_step_down">Melangkah Bawah</string>
+    <string name="prefs_wena3_vibration_smart_item">Getaran Pintar</string>
+    <string name="prefs_wena3_home_icon_name_riiiver">Riiiver</string>
+    <string name="prefs_wena3_hint_background_sync">Perbolehkan Wena untuk meminta Gadgetbridge mengunduh data aktivitas dari perangkat</string>
+    <string name="prefs_wena3_home_icon_name_suica">Saldo Suica</string>
+    <string name="prefs_wena3_vibration_strength_item_medium">Sedang</string>
+    <string name="prefs_wena3_vibration_continuous">Berkelanjutan</string>
+    <string name="prefs_wena3_home_icon_name_pedometer">Hitungan Langkah</string>
+    <string name="prefs_wena3_home_icon_name_qrio">Qrio</string>
+    <string name="prefs_wena3_home_icon_name_clock">Waktu Saat Ini</string>
+    <string name="prefs_wena3_item_background_sync">Sinkronisasi Data Aktivitas Latar Belakang</string>
+    <string name="prefs_wena3_vibration_strength_item_weak">Lemah</string>
+    <string name="prefs_wena3_home_icon_title">Ikon Layar Beranda</string>
+    <string name="prefs_wena3_item_weather_statusbar">Cuaca Dalam Bilah Status</string>
+    <string name="temperature_scale_celsius">Celsius</string>
+    <string name="prefs_wena3_vibration_step_up">Melangkah Atas</string>
+    <string name="prefs_wena3_vibration_basic">Dasaran</string>
+    <string name="prefs_wena3_item_large_font">Ukuran Fon Lebih Besar</string>
+    <string name="prefs_wena3_home_icon_name_energy">Energi Badan</string>
+    <string name="prefs_wena3_notification_vibration_repetition_2">Dua kali</string>
+    <string name="prefs_wena3_auto_power_off_hint">Perangkat akan dimatikan dan dinyalakan secara otomatis berdasarkan jadwal yang ditentukan</string>
+    <string name="prefs_wena3_hint_lift_wrist">Nyalakan layar ketika Anda melihat pergelangan tangan Anda</string>
+    <string name="prefs_wena3_hint_large_font">Perbesar ukuran fon dalam kalender, notifikasi, dll.</string>
+    <string name="prefs_wena3_button_action_item_double">Tekan Ganda</string>
+    <string name="prefs_wena3_vibration_rapid">Cepat</string>
+    <string name="prefs_wena3_button_action_name_qrio_unlock">Buka Kunci Qrio</string>
+    <string name="temperature_scale_cf">Skala suhu</string>
+    <string name="prefs_wena3_notification_vibration_repetition_1">Sekali</string>
+    <string name="prefs_wena3_receive_calls_item">Terima Pemberitahuan Panggilan</string>
+    <string name="prefs_wena3_hint_weather_statusbar">Tampilkan ikon kondisi saat ini di sudut kiri atas layar beranda</string>
+    <string name="prefs_wena3_home_icon_name_wena_pay">Wena Pay</string>
+    <string name="prefs_wena3_button_action_item">Tindakan Tombol</string>
+    <string name="prefs_wena3_auto_power_off_turn_on_time_item">Waktu daya nyala</string>
+    <string name="prefs_wena3_notification_default_call_vibration">Getaran Panggilan Masuk</string>
+    <string name="prefs_wena3_notification_default_call_led">Warna LED Panggilan Masuk</string>
+    <string name="prefs_wena3_vibration_siren">Sirene</string>
+    <string name="temperature_scale_cf_summary">Pilih apakah perangkat menggunakan skala Celsius atau Fahrenheit.</string>
+    <string name="prefs_wena3_menu_icon_title">Ikon Menu</string>
+    <string name="prefs_wena3_notification_default_call_vibration_repetition">Pengulangan Getaran Panggilan Masuk</string>
+    <string name="prefs_wena3_notification_default_vibration">Getaran Pemberitahuan</string>
+    <string name="prefs_wena3_notification_vibration_repetition_0">Tidak terbatas</string>
+    <string name="prefs_wena3_notification_vibration_repetition_4">Empat kali</string>
+    <string name="temperature_scale_fahrenheit">Fahrenheit</string>
+    <string name="prefs_wena3_vibration_strength_item">Kekuatan Getaran</string>
+    <string name="prefs_wena3_receive_calls_hint">Jika dimatikan, Anda tidak akan diberi tahu tentang panggilan masuk di Wena</string>
+    <string name="prefs_wena3_button_action_item_long">Tekan Lama</string>
+    <string name="prefs_wena3_menu_icon_name_payment">Pembayaran</string>
+    <string name="prefs_wena3_status_page_title">Pengurutan Halaman Status</string>
+    <string name="prefs_wena3_title_alarm">Pengaturan Alarm</string>
+    <string name="prefs_wena3_notification_default_vibration_repetition">Pengulangan Getaran Pemberitahuan</string>
+    <string name="prefs_wena3_hint_rich_design">Menambahkan persegi panjang bulat di sekitar ikon layar beranda</string>
+    <string name="prefs_wena3_notification_default_led">Warna LED Pemberitahuan</string>
+    <string name="prefs_wena3_auto_power_off_item">Daya Mati Otomatis</string>
+    <string name="prefs_wena3_home_icon_name_edy">Saldo Edy</string>
+    <string name="prefs_wena3_vibration_warning">Peringatan</string>
+    <string name="prefs_wena3_smart_alarm_margin_item">Margin Alarm Pintar</string>
+    <string name="prefs_wena3_item_rich_design">Gunakan Desain Kaya</string>
+    <string name="prefs_wena3_day_start_hour_item">Hari Dimulai Pada</string>
+    <string name="prefs_wena3_notification_per_app_settings_title">Pengaturan Pemberitahuan Per Aplikasi</string>
+    <string name="prefs_wena3_vibration_none">Tidak Ada Getaran</string>
+    <string name="prefs_wena3_vibration_strength_item_strong">Kuat</string>
+    <string name="prefs_wena3_vibration_triple">Tiga Kali</string>
+    <string name="prefs_wena3_button_action_name_activity_screen">Layar Aktivitas</string>
+    <string name="prefs_wena3_notification_settings_title">Pengaturan Pemberitahuan</string>
+    <string name="devicetype_sony_wf_1000xm5">Sony WF-1000XM5</string>
+    <string name="pref_message_privacy_mode_bodyonly">Sembunyikan bodi saja</string>
+    <string name="latvian">Latvia</string>
+    <string name="common_symbols">Simbol Umum</string>
+    <string name="pref_title_notifications_ignore_work_profile">Abaikan notifikasi profil kerja</string>
+    <string name="pref_summary_notifications_ignore_work_profile">Jangan kirimkan notifikasi dari aplikasi dalam profil kerja ke jam tangan</string>
+    <string name="menuitem_alerts">Peringatan</string>
+    <string name="menuitem_thermometer">Termometer</string>
+    <string name="devicetype_pixoo">Pixoo</string>
+    <string name="menuitem_stats">Statistik</string>
+    <string name="pref_navigation_app_osmand">OsmAnd(+)</string>
+    <string name="yard">yard</string>
+    <string name="menuitem_focus">Fokus</string>
+    <string name="bedtime">Waktu tidur</string>
+    <string name="menuitem_readiness">Kesediaan</string>
+    <string name="call_rejection_method_reject">Tolak</string>
+    <string name="menuitem_running">Berlari</string>
+    <string name="danish">Denmark</string>
+    <string name="zepp_os_watchface_guider">Pemandu</string>
+    <string name="menuitem_headphone">Headphone</string>
+    <string name="strokes_minute">str/mnt</string>
+    <string name="activity_type_trekking">Trekking</string>
+    <string name="pref_title_osmand_packagename">Nama paket OsmAnd</string>
+    <string name="femometer_measurement_mode_title">Mode pengukuran</string>
+    <string name="femometer_measurement_mode_precise">Mode Persis (3 menit)</string>
+    <string name="devicetype_amazfit_balance">Amazfit Balance</string>
+    <string name="zepp_os_watchface_lightning_flash">Kilatan petir</string>
+    <string name="zepp_os_watchface_city_of_speed">Kota kecepatan</string>
+    <string name="flatDistance">Jarak datar</string>
+    <string name="avgStrokeRate">Tingkat Membelai Rata-rata</string>
+    <string name="devicetype_nothing_cmf_watch_pro">CMF Watch Pro</string>
+    <string name="pref_summary_debug">Kirim permintaan pengawakutuan ke perangkat Huawei</string>
+    <string name="pref_call_rejection_method_summary">Tindakan apa yang diambil ketika panggilan yang masuk ditolak dari jam tangan</string>
+    <string name="call_rejection_method_ignore">Abaikan (diam)</string>
+    <string name="pref_call_rejection_method_title">Metode penolakan panggilan</string>
+    <string name="serial_number">Nomor seri</string>
+    <string name="pref_title_prefix_notification_with_app">Nama aplikasi dalam notifikasi</string>
+    <string name="pref_sleep_mode_schedule_title">Jadwal Mode Tidur</string>
+    <string name="pref_sleep_mode_schedule_summary">Kirim pengingat dan masuk mode tidur pada saat waktu tidur. Pada waktu bangun yang dijadwalkan, alarm bangun akan dibunyikan.</string>
+    <string name="devicetype_xiaomi_watch_s1">Xiaomi Watch S1</string>
+    <string name="devicetype_xiaomi_watch_s3">Xiaomi Watch S3</string>
+    <string name="pref_force_connection_type_bt_classic">Bluetooth Classic</string>
+    <string name="pref_test_features_title">Fitur</string>
+    <string name="devicetype_redmi_smart_band_pro">Redmi Smart Band Pro</string>
+    <string name="menuitem_zepp_pay">Zepp Pay</string>
+    <string name="menuitem_workout_shortcuts">Pintasan olahraga</string>
+    <string name="zepp_os_watchface_pure_white">Putih murni</string>
+    <string name="pref_vitality_score_daily_title">Progres harian</string>
+    <string name="zepp_os_watchface_free_combination">Kombinasi bebas</string>
+    <string name="widget_screen">Layar Widget</string>
+    <string name="widget_screen_delete_confirm_title">Hapus layar widget</string>
+    <string name="widget_layout_top_1_bot_2">1 atas, 2 bawah</string>
+    <string name="active_time">Waktu aktif</string>
+    <string name="devicetype_redmiwatch3active">Redmi Watch 3 Active</string>
+    <string name="pref_summary_prefix_notification_with_app">Awali judul notifikasi dengan nama aplikasi sumber</string>
+    <string name="pref_summary_osmand_packagename">Digunakan untuk memilih versi OsmAnd untuk menghubungkan</string>
+    <string name="pref_title_navigation_prefs">Preferensi navigasi</string>
+    <string name="pref_test_features_summary">Fitur yang diaktifkan untuk perangkat uji coba</string>
+    <string name="devicetype_nothingear2">Nothing Ear (2)</string>
+    <string name="pref_device_action_dnd_off">Jangan ganggu - Mati</string>
+    <string name="pref_summary_fossil_hr_nav_foreground">Apakah aplikasi navigasi secara otomatis datang ke latar depan ketika menerima petunjuk navigasi</string>
+    <string name="pref_title_fossil_hr_nav_vibrate">Getar pada petunjuk baru</string>
+    <string name="prefs_active_noise_cancelling_light">Pembatalan Kebisingan Aktif Ringan</string>
+    <string name="devicetype_nothingearstick">Nothing Ear (Stick)</string>
+    <string name="prefs_active_noise_cancelling_transparency">Transparansi</string>
+    <string name="pref_device_action_dnd_priority">Jangan ganggu - Hanya prioritas</string>
+    <string name="devicetype_mi_watch_color_sport">Mi Watch Color Sport</string>
+    <string name="pref_vitality_score_7_day_summary">Dapatkan notifikasi ketika skor vitalitas Anda mencapai 30, 60, atau 100 dalam 7 hari terakhir</string>
+    <string name="pref_vitality_score_daily_summary">Dapatkan notifikasi ketika Anda mencapai jumlah poin vitalitas untuk hari ini</string>
+    <string name="widget">Widget</string>
+    <string name="notification_channel_connection_status_name">Status Koneksi</string>
+    <string name="busy_task_fetch_temperature">Mendapatkan data suhu</string>
+    <string name="laneLength">Panjang Lajur</string>
+    <string name="pref_title_general_reconnectonlytoconnected">Hubungkan kembali ke perangkat yang terhubung saja</string>
+    <string name="pref_summary_general_reconnectonlytoconnected">Hubungkan kembali ke perangkat yang terhubung saja, daripada menghubungkan kembali ke semua perangkat</string>
+    <string name="busy_task_fetch_statistics">Mendapatkan statistik</string>
+    <string name="pref_enable_call_reject_summary">Aktifkan penolakan panggilan dari perangkat</string>
+    <string name="pref_title_fossil_hr_nav_foreground">Datang ke latar depan</string>
+    <string name="prefs_password_4_digits_0_to_9_summary">Kata sandi harus memiliki 4 digit, menggunakan nomor saja</string>
+    <string name="activity_type_trail_run">Lari jejak</string>
+    <string name="devicetype_huawei_band6">Huawei Band 6</string>
+    <string name="devicetype_huawei_band7">Huawei Band 7</string>
+    <string name="devicetype_huawei_band8">Huawei Band 8</string>
+    <string name="devicetype_huawei_talk_band_b6">Huawei Talk Band B6</string>
+    <string name="devicetype_huawei_watchgt3">Huawei Watch GT 3 (Pro)</string>
+    <string name="menuitem_buzzer_intensity">Intensitas Bel</string>
+    <string name="huawei_trusleep_summary">Pantau kualitas tidur dan pola penghirupan napas Anda dalam waktu nyata.
+\nAnalisa pola tidur Anda dan diagnosis 6 jenis permasalahan tidur secara akurat.</string>
+    <string name="wake_up_time">Bangun</string>
+    <string name="not_set">Tidak diatur</string>
+    <string name="silent_mode_normal_silent">Normal / Diam</string>
+    <string name="dnd_all_day">Seharian</string>
+    <string name="huawei_ignore_wakeup_status_end_description">Dapat membantu dengan deteksi tidur yang sesuai. Terlihat secara langsung dalam tampilan aktivitas harian.</string>
+    <string name="huawei_reparse_workout_data">Uraikan ulang data olahraga</string>
+    <string name="huawei_reparse_workout_data_description">Ini hanya akan melakukan sesuatu setelah beberapa pembaruan</string>
+    <string name="clap_hands_to_wakeup_device_summary">Menepuk lagi akan mematikan layar</string>
+    <string name="clap_hands_to_wakeup_device">Tepuk tangan untuk menyalakan layar</string>
+    <string name="pixoo_power_saving_summary">Layar akan mati setelah mikrofon mendeteksi kediaman selama beberapa waktu</string>
+    <string name="prefs_wearmode">Mode pemakaian</string>
+    <string name="silent_mode_vibrate_silent">Getar / Diam</string>
+    <string name="standing_time">Waktu berdiri</string>
+    <string name="activity_type_wrestling">Gulat</string>
+    <string name="devicetype_miband7pro">Xiaomi Smart Band 7 Pro</string>
+    <string name="devicetype_miband8">Xiaomi Smart Band 8</string>
+    <string name="devicetype_colacao21">ColaCao 2021</string>
+    <string name="devicetype_colacao23">ColaCao 2023</string>
+    <string name="devicetype_xiaomi_watch_lite">Xiaomi Watch Lite</string>
+    <string name="devicetype_redmi_smart_band_2">Redmi Smart Band 2</string>
+    <string name="menuitem_body_composition">Komposisi tubuh</string>
+    <string name="zepp_os_watchface_starry_sky">Langit berbintang</string>
+    <string name="fossil_hr_nav_app_not_installed_notify_title">Aplikasi navigasi tidak terpasang di jam tangan</string>
+    <string name="widget_screen_no_free_slots_description">Perangkat tidak memiliki ruang tersisa untuk layar widget (jumlah ruang: %1$s)</string>
+    <string name="widget_layout_two">2 widget</string>
+    <string name="zepp_os_watchface_vast_sky">Langit Luas</string>
+    <string name="sony_protocol_v1">Versi 1</string>
+    <string name="pref_heartrate_automatic_enable">Aktifkan pengukuran detak jantung otomatis</string>
+    <string name="pref_force_smart_alarm">Paksa alarm pintar</string>
+    <string name="pref_force_smart_alarm_summary">Paksa dukungan alarm pintar.
+\nGUNAKAN DENGAN RISIKO ANDA SENDIRI</string>
+    <string name="pref_force_wear_location">Paksa lokasi pemakaian</string>
+    <string name="pref_force_wear_location_summary">Paksa dukungan lokasi pemakaian.
+\nGUNAKAN DENGAN RISIKO ANDA SENDIRI</string>
+    <string name="pref_force_dnd_support">Paksa dukungan Jangan Ganggu</string>
+    <string name="pref_force_dnd_support_summary">Paksa dukungan Jangan Ganggu.
+\nGUNAKAN DENGAN RISIKO ANDA SENDIRI</string>
+    <string name="huawei_ignore_wakeup_status_start">Abaikan status mulai bangun</string>
+    <string name="pref_title_debug">Permintaan pengawakutuan</string>
+    <string name="activity_info">Info aktivitas</string>
+    <string name="wearmode_band">Gelang</string>
+    <string name="wearmode_pebble">Pebble (gesper sepatu)</string>
+    <string name="wearmode_necklace">Kalung (tali leher)</string>
+    <string name="pref_title_send_app_notifications">Kirim notifikasi</string>
+    <string name="pref_summary_send_app_notifications">Kirim notifikasi aplikasi ke perangkat ini</string>
+    <string name="pref_title_preview_message_in_title">Tampilkan pratinjau pesan dalam judul</string>
+    <string name="pref_title_casio_alert_calendar">Peringatan untuk notifikasi kalender</string>
+    <string name="pref_summary_preview_message_in_title">Menampilkan pratinjau pesan dalam judul dari sebuah notifikasi yang diperbolehkan oleh perangkat</string>
+    <string name="pref_summary_casio_alert_calendar">Perignatan (getaran/suara) untuk notifikasi kalender</string>
+    <string name="pref_title_casio_alert_call">Peringatan untuk panggilan masuk</string>
+    <string name="pref_summary_casio_alert_call">Peringatan (getaran/suara) untuk panggilan masuk</string>
+    <string name="pref_title_casio_alert_email">Peringatan untuk notifikasi surel</string>
+    <string name="pref_summary_casio_alert_sms">Peringatan (getaran/suara) untuk notifikasi SMS (pesan teks)</string>
+    <string name="pref_title_casio_alert_other">Peringatan untuk notifikasi lainnya</string>
+    <string name="alarm_smart_wakeup_interval_default">5 menit</string>
+    <string name="prefs_phone_silent_mode">Mode Diam Ponsel</string>
+    <string name="prefs_device_name">Nama perangkat</string>
+    <string name="do_not_disturb_lift_wrist_summary">Hanya jika mengaktifkan layar ketika dinaikkan</string>
+    <string name="charts_legend_spo2_average">Rata-rata oksigen darah</string>
+    <string name="activity_type_indoor_running">Lari Dalam Ruangan</string>
+    <string name="activity_type_mountain_hike">Pendakian Gunung</string>
+    <string name="activity_type_cross_trainer">Pelatih lintas</string>
+    <string name="activity_type_free_training">Pelatihan bebas</string>
+    <string name="activity_type_stair_stepper">Pelangkah tangga</string>
+    <string name="activity_type_fitness_exercises">Latihan kebugaran</string>
+    <string name="activity_type_cross_country_running">Lari lintas alam</string>
+    <string name="activity_type_karate">Karate</string>
+    <string name="activity_type_fencing">Anggar</string>
+    <string name="activity_type_kendo">Kendo</string>
+    <string name="activity_type_horizontal_bar">Bilah horizontal</string>
+    <string name="activity_type_parallel_bar">Bilah paralel</string>
+    <string name="activity_type_cooldown">Istirahat</string>
+    <string name="activity_type_sit_ups">Sit up</string>
+    <string name="activity_type_roller_skating">Sepatu roda</string>
+    <string name="activity_type_martial_arts">Seni bela diri</string>
+    <string name="activity_type_tai_chi">Tai chi</string>
+    <string name="activity_type_hula_hooping">Hula hoop</string>
+    <string name="activity_type_disc_sports">Olahraga cakram</string>
+    <string name="activity_type_darts">Panahan dart</string>
+    <string name="activity_type_archery">Panahan</string>
+    <string name="activity_type_horse_riding">Berkuda</string>
+    <string name="activity_type_kite_flying">Layang-layangan</string>
+    <string name="activity_type_swing">Ayunan</string>
+    <string name="activity_type_stairs">Tangga</string>
+    <string name="activity_type_fishing">Memancing</string>
+    <string name="activity_type_mind_and_body">Pikiran dan tubuh</string>
+    <string name="activity_type_karting">Karting</string>
+    <string name="activity_type_shuttlecock">Kok</string>
+    <string name="activity_type_softball">Softball</string>
+    <string name="activity_type_dodgeball">Dodgeball</string>
+    <string name="activity_type_pickleball">Bola acar</string>
+    <string name="activity_type_lacross">Lacross</string>
+    <string name="activity_type_shot">Shot</string>
+    <string name="activity_type_sailing">Pelayaran</string>
+    <string name="activity_type_jet_skiing">Ski jet</string>
+    <string name="activity_type_skating">Seluncur es</string>
+    <string name="activity_type_skateboarding">Skateboard</string>
+    <string name="activity_type_rock_climbing">Panjat tebing</string>
+    <string name="devicetype_honor_band4">Honor Band 4</string>
+    <string name="devicetype_honor_band5">Honor Band 5</string>
+    <string name="devicetype_honor_band6">Honor Band 6</string>
+    <string name="devicetype_huawei_watch_gt">Huawei Watch GT</string>
+    <string name="devicetype_huawei_band4pro">Huawei Band 4 (Pro)</string>
+    <string name="devicetype_huawei_watchgt2">Huawei Watch GT 2 (Pro)</string>
+    <string name="devicetype_huawei_watchgt2e">Huawei Watch GT 2e</string>
+    <string name="devicetype_femometer_vinca2">Femometer Vinca II</string>
+    <string name="devicetype_redmiwatch3">Redmi Watch 3</string>
+    <string name="maxStrokeRate">Tingkat Membelai Maks</string>
+    <string name="pref_spo_automatic_enable">Aktifkan pengukuran SpO2 otomatis</string>
+    <string name="pref_force_options">Paksa opsi</string>
+    <string name="pref_force_options_summary">Beberapa perangkat mengklaim secara palsu bahwa tidak ada dukungan untuk beberapa opsi. Pengaturan berikut dapat digunakan untuk mengaktifkannya saja.
+\nGUNAKAN DENGAN RISIKO ANDA SENDIRI
+\nBaca wikinya</string>
+    <string name="huawei_ignore_wakeup_status_start_description">Dapat membantu dengan deteksi tidur yang sesuai. Terlihat secara langsung dalam tampilan aktivitas harian.</string>
+    <string name="huawei_ignore_wakeup_status_end">Abaikan status akhir bangun</string>
+    <string name="fossil_hr_nav_app_not_installed_notify_text">Navigasi dimulai tetapi aplikasi navigasi tidak terpasang di jam tangan. Silakan pasang dari App Manager.</string>
+    <string name="pref_title_navigation_apps">Aplikasi navigasi</string>
+    <string name="widget_screen_min_screens">Harus memiliki minimum %1$s layar</string>
+    <string name="widget_layout_top_2_bot_1">2 atas, 1 bawah</string>
+    <string name="widget_layout_top_2_bot_2">2 atas, 2 bawah</string>
+    <string name="widget_layout">Tata letak widget</string>
+    <string name="widget_subtype">Subjenis Widget</string>
+    <string name="alarm_smart_wakeup_interval">Interval pembangunan pintar:</string>
+    <string name="pref_summary_casio_alert_email">Peringatan (getaran/suara) untuk notifikasi surel</string>
+    <string name="pref_summary_casio_alert_other">Peringatan (getaran/suara) untuk notifikasi dalam kategori lain</string>
+    <string name="pref_title_casio_alert_sms">Peringatan untuk notifikasi SMS</string>
+    <string name="pref_title_goal_secondary">Sasaran sekunder</string>
+    <string name="femometer_measurement_mode_quick">Mode Cepat (30 detik)</string>
+    <string name="femometer_measurement_mode_normal">Mode Normal (60鈥�90 detik)</string>
+    <string name="pref_do_not_disturb_not_wear">Jangan ganggu ketika tidak memakai</string>
+    <string name="activity_type_functional_training">Pelatihan fungsional</string>
+    <string name="activity_type_cross_training">Pelatihan lintas</string>
+    <string name="activity_type_long_jump">Lompat panjang</string>
+    <string name="updatefirmwareoperation_updateproblem_low_battery">Baterai perangkat terlalu rendah</string>
+    <string name="hydration_dnd_summary">Nonaktifkan peringatan hidrasi selama interval waktu</string>
+    <string name="manual">Manual</string>
+    <string name="activity_type_rower">Pendayung</string>
+    <string name="activity_type_dynamic_cycle">Siklus dinamis</string>
+    <string name="activity_type_physical_training">Pelatihan fisik</string>
+    <string name="activity_type_fitness_gaming">Permainan kebugaran</string>
+    <string name="activity_type_high_jump">Lompat tinggi</string>
+    <string name="activity_type_dumbbell">Barbel</string>
+    <string name="prefs_disconnect_notification_summary">Notifikasi di perangkat ketika terputus dari BT.</string>
+    <string name="silent_mode_normal_vibrate">Normal / Getar</string>
+    <string name="activity_type_crossfit">CrossFit</string>
+    <string name="activity_type_taekwondo">Taekwondo</string>
+    <string name="activity_type_aerobic_exercise">Latihan aerobik</string>
+    <string name="activity_type_rolling">Rolling</string>
+    <string name="activity_type_track_and_field">Lintasan dan lapangan</string>
+    <string name="activity_type_trampoline">Trampolin</string>
+    <string name="activity_type_ballet">Balet</string>
+    <string name="activity_type_other_dance">Tarian lainnya</string>
+    <string name="activity_type_latin_dance">Tarian Latin</string>
+    <string name="activity_type_belly_dance">Tarian perut</string>
+    <string name="activity_type_flexibility">Fleksibilitas</string>
+    <string name="activity_type_jazz_dance">Tarian jazz</string>
+    <string name="activity_type_push_ups">Push up</string>
+    <string name="activity_type_hand_cycling">Bersepeda tangan</string>
+    <string name="activity_type_curling">Keriting</string>
+    <string name="activity_type_battle_rope">Tali pertempuran</string>
+    <string name="activity_type_kabaddi">Kabaddi</string>
+    <string name="activity_type_australian_football">Sepak bola Australia</string>
+    <string name="activity_type_ice_hockey">Hoki es</string>
+    <string name="activity_type_snow_sports">Olahraga salju</string>
+    <string name="activity_type_smith_machine">Mesin Smith</string>
+    <string name="activity_type_billiards">Biliar</string>
+    <string name="devicetype_sony_wi_sp600n">Sony WI-SP600N</string>
+    <string name="devicetype_redmi_watch_2">Redmi Watch 2</string>
+    <string name="menuitem_apps_shortcuts">Pintasan aplikasi</string>
+    <string name="sony_protocol_v2">Versi 2</string>
+    <string name="pref_enable_call_accept">Aktifkan penerimaan panggilan</string>
+    <string name="pref_enable_call_accept_summary">Aktifkan penerimaan panggilan dari perangkat</string>
+    <string name="zepp_os_watchface_the_ultima">Ultima</string>
+    <string name="activity_type_pull_ups">Pull up</string>
+    <string name="activity_type_plank">Plank</string>
+    <string name="activity_type_javelin">Lembing</string>
+    <string name="activity_type_cross_country_skiing">Ski lintas alam</string>
+    <string name="devicetype_mijia_lywsd03">Mijia Temperature and Humidity Sensor 2</string>
+    <string name="pref_enable_call_reject">Aktifkan penolakan panggilan</string>
+    <string name="devicetype_honor_band3">Honor Band 3</string>
+    <string name="pref_disable_find_phone_with_dnd">Nonaktifkan temukan ponsel saya ketika jangan ganggu aktif</string>
+    <string name="devicetype_honor_band7">Honor Band 7</string>
+    <string name="pref_navigation_app_gmaps">Google Maps</string>
+    <string name="devicetype_xiaomi_watch_s1_pro">Xiaomi Watch S1 Pro</string>
+    <string name="devicetype_xiaomi_watch_s1_active">Xiaomi Watch S1 Active</string>
+    <string name="pref_vitality_score_7_day_title">Progres 7 hari</string>
+    <string name="pref_vitality_score_title">Skor Vitalitas</string>
+    <string name="heartrate_bpm_155">155 bpm</string>
+    <string name="heartrate_bpm_165">165 bpm</string>
+    <string name="heartrate_bpm_175">175 bpm</string>
+    <string name="heartrate_bpm_185">185 bpm</string>
+    <string name="heartrate_bpm_195">195 bpm</string>
+    <string name="heartrate_bpm_205">205 bpm</string>
+    <string name="prefs_heartrate_alert_active_high_threshold">Ambang peringatan detak jantung aktivitas tinggi</string>
+    <string name="activity_type_hunting">Berburu</string>
+    <string name="activity_type_outdoor_walking">Jalan di luar ruang</string>
+    <string name="devicetype_honor_magicwatch2">Honor MagicWatch 2</string>
+    <string name="devicetype_huawei_band_aw70">Huawei Band (AW70)</string>
+    <string name="protocol_version">Versi Protokol</string>
+    <string name="sony_protocol_v3">Versi 3</string>
+    <string name="prefs_workmode">Mode Kerja</string>
+    <string name="huawei_trusleep_summary_light">Pemantauan tidur yang ditingkatkan</string>
+    <string name="devicetype_miband8pro">Xiaomi Smart Band 8 Pro</string>
+    <string name="devicetype_amazfit_active">Amazfit Active</string>
+    <string name="devicetype_amazfit_active_edge">Amazfit Active Edge</string>
+    <string name="devicetype_mijia_mho_c303">Mijia MHO-C303</string>
+    <string name="warning_missing_notification_permission">Tidak dapat mengirim notifikasi sedang berlangsung karena perizinan yang hilang</string>
+    <string name="devicetype_redmi_watch_2_lite">Redmi Watch 2 Lite</string>
+    <string name="devicetype_huawei_watchfit">Huawei Watch Fit</string>
+    <string name="strokes">Jumlah Belai</string>
+    <string name="pref_device_action_dnd_alarms">Jangan ganggu - Hanya alarm</string>
+    <string name="pref_device_action_dnd_on">Jangan ganggu - Nyala</string>
+    <string name="huawei_alarm_smart_description">Jangan hilangkan centang pembangunan pintar.</string>
+    <string name="huawei_alarm_event_description">Jangan centang pembangunan pintar.</string>
+    <string name="huawei_trusleep_title">HUAWEI TruSleep鈩�</string>
+    <string name="widget_move_up">Pindah ke atas</string>
+    <string name="widget_move_down">Pindah ke bawah</string>
+    <string name="widget_missing_parts">Silakan pilih semua widget</string>
+    <string name="widget_unknown_workout">Olahraga tidak diketahui - %s</string>
+    <string name="pref_title_fossil_hr_navigation_instructions">Petunjuk navigasi</string>
+    <string name="pref_summary_fossil_hr_navigation_instructions">Atur perilaku aplikasi navigasi pada jam tangan</string>
+    <string name="pref_summary_fossil_hr_nav_vibrate">Apakah jam tangan bergetar pada petunjuk navigasi baru atau berubah (hanya ketika aplikasi dalam latar depan)</string>
+    <string name="uploading_watchface">Mengunggah wajah jam鈥�</string>
+    <string name="uploadwatchfaceoperation_in_progress">Mengunggah wajah jam</string>
+    <string name="uploadwatchfaceoperation_complete">Pemasangan wajah jam selesai</string>
+    <string name="uploadwatchfaceoperation_failed">Pemasangan wajah jam gagal</string>
+    <string name="pref_force_connection_type_auto">Otomatis</string>
+    <string name="pref_force_connection_type_title">Paksa jenis koneksi</string>
+    <string name="pref_force_connection_type_description">Anda dapat memaksakan jenis koneksi jika perangkat Anda tidak merespons ke Gadgetbridge</string>
+    <string name="pref_force_connection_type_ble">Bluetooth LE</string>
+    <string name="widget_screen_delete_confirm_description">Apakah Anda yakin ingin menghapus \'%1$s\'?</string>
+    <string name="widget_screen_x">Layar %s</string>
+    <string name="widget_layout_single">1 widget</string>
+    <string name="auto_reconnect_ble_scan_title">Hubungkan ulang dengan pemindaian BLE</string>
+    <string name="prompt_restart_gadgetbridge">Silakan mulai ulang GB untuk menerapkan.</string>
+    <string name="scan_scanning_all_devices">Memindai semua perangkat</string>
+    <string name="notification_channel_scan_service_name">Pindai perangkat</string>
+    <string name="device_state_waiting_scan">Menunggu pemindaian perangkat</string>
+    <string name="auto_reconnect_ble_scan_summary">Tunggu pemindaian perangkat daripada percobaan koneksi buta</string>
+    <string name="waiting_for_bluetooth">Menunggu Bluetooth鈥�</string>
+    <string name="error_scan_failed">Pemindaian gagal: %d</string>
+    <string name="scan_not_scanning">Tidak memindai</string>
+    <string name="scan_scanning_single_device">Memindai 1 perangkat</string>
+    <string name="scan_scanning_multiple_devices">Memindai %d perangkat</string>
+    <string name="devicetype_miband8active">Xiaomi Smart Band 8 Active</string>
+    <string name="temperature">Suhu</string>
+    <string name="pref_adaptive_volume_control_title">Kontrol volume adaptif</string>
+    <string name="pref_wide_area_tap_title">Ketukan area luas</string>
+    <string name="devicetype_sony_linkbuds">Sony LinkBuds</string>
+    <string name="pref_header_sony_sound_control">Kontrol Suara</string>
+    <string name="pref_wide_area_tap_summary">Kenali ketukan antara pipi dan telinga</string>
+    <string name="pref_adaptive_volume_control_summary">Naikkan volume secara otomatis ketika suara sekitar keras</string>
+    <string name="devicetype_amazfit_bip3">Amazfit Bip 3</string>
+    <string name="fw_upgrade_notice_amazfitbip3">Anda akan memasang perangkat tegar %s pada Amazfit Bip 3 Anda.
+\n
+\nPastikan Anda memasang berkas .fw, dan setelah itu berkas .res. Jam tangan Anda akan dinyalakan ulang setelah memasang berkas .fw.
+\n
+\nCatatan: Anda tidak harus memasang .res jika sama persis dengan yang sebelumnya dipasang.
+\n
+\nLANJUTKAN DENGAN RISIKO ANDA SENDIRI!</string>
+    <string name="weekstepschart_steps_a_week_or_month">Langkah per minggu/bulan</string>
+    <string name="weeksleepchart_sleep_a_week_or_month">Tidur per minggu/bulan</string>
+    <string name="busy_task_fetch_sports_details_interrupted">Pengambilan detail olahraga ditunda</string>
+    <string name="devicetype_huawei_watchgt4">Huawei Watch GT 4</string>
+    <string name="devicetype_huawei_watchultimate">Huawei Watch Ultimate</string>
+    <string name="companion_pairing_request_title">Perangkat pendamping</string>
+    <string name="companion_pairing_request_description">Sandingkan perangkat ini sebagai pendamping?
+\n
+\nIni disarankan untuk beberapa fungsi seperti cari perangkat, dan menyediakan koneksi yang lebih baik.</string>
+    <string name="unbind_before_pair_title">Sudah tersanding</string>
+    <string name="unbind_before_pair_message">Perangkat ini sudah disandingkan dalam pengaturan Android yang dapat membuat penyandingan gagal untuk beberapa perangkat.
+\n
+\nJika penambahan perangkat gagal, silakan hapus dari pengaturan Android dan coba lagi.</string>
+    <string name="devicesetting_scannable_debounce">Waktu habis penghilangan pemindaian (detik)</string>
+    <string name="devicesetting_scannable_rssi_summary">Ambang RSSI minimum untuk deteksi</string>
+    <string name="devicetype_scannable">Perangkat dapat dipindai</string>
+    <string name="state_scanned">Dipindai</string>
+    <string name="devicesetting_scannable_minimum_unseen">Waktu tidak terlihat minimum (detik)</string>
+    <string name="devicesetting_scannable_rssi">Ambang RSSI minimal</string>
+    <string name="devicesetting_scannable_debounce_summary">Setelah dipindai, perangkat akan ditetapkan sebagai dipindai dan diabaikan selama waktu yang sudah ditentukan</string>
+    <string name="devicesetting_scannable_minimum_unseen_summary">Setelah dipindai, perangkat harus tidak terlihat selama waktu ini sebelum didaftarkan lagi</string>
+    <string name="swolfAvg">Swolf rata-rata</string>
+    <string name="swolfMax">Swolf maksimum</string>
+    <string name="swolfMin">Swolf minimum</string>
+    <string name="stepRateSum">Jumlah laju langkah</string>
+    <string name="stepRateAvg">Laju langkah rata-rata</string>
+    <string name="stepLengthAvg">Panjang Langkah Rata-rata</string>
+    <string name="groundContactTimeAvg">Waktu kontak tanah rata-rata</string>
+    <string name="impactAvg">Dampak rata-rata</string>
+    <string name="impactMax">Dampak maksimum</string>
+    <string name="swingAngleAvg">Sudut ayunan rata-rata</string>
+    <string name="backFootLandings">Landasan kaki belakang</string>
+    <string name="eversionAngleAvg">Sudut eversi rata-rata</string>
+    <string name="eversionAngleMax">Sudut eversi maksimum</string>
+    <string name="fmtPaceDistance">Jarak laju %d</string>
+    <string name="fmtPaceType">Jenis laju %d</string>
+    <string name="fmtPacePace">Laju dalam laju %d</string>
+    <string name="fmtPaceCorrection">Koreksi laju %d</string>
+    <string name="fmtPaceTypeAverage">Rata-rata Jenis Laju %d</string>
+    <string name="unknownDataEncountered">Mendapatkan data tidak diketahui</string>
+    <string name="milliseconds">milidetik</string>
+    <string name="degrees">derajat</string>
+    <string name="Pace">Laju</string>
+    <string name="midFootLandings">Landasan kaki tengah</string>
+    <string name="foreFootLandings">Landasan kaki depan</string>
+    <string name="RunningForm">Bentuk Lari</string>
+    <string name="pref_force_enable_heartrate_support">Paksa dukungan detak jantung</string>
+    <string name="pref_force_enable_heartrate_support_summary">Paksa mengaktifkan dukungan detak jantung.
+\nGUNAKAN DENGAN RISIKO ANDA SENDIRI</string>
+    <string name="pref_force_enable_spo2_support">Paksa dukungan SpO2</string>
+    <string name="pref_force_enable_spo2_support_summary">Paksa mengaktifkan dukungan SpO2.
+\nGUNAKAN DENGAN RISIKO ANDA SENDIRI</string>
+    <string name="activity_type_worn">Dipakai</string>
+    <string name="dashboard_settings">Pengaturan dasbor</string>
+    <string name="bottom_nav_devices">Perangkat</string>
+    <string name="pref_dashboard_first_title">Tampilkan dasbor terlebih dahulu</string>
+    <string name="pref_dashboard_first_summary">Tampilkan dasbor ketika Gadgetbridge dibuka, daripada layar perangkat</string>
+    <string name="pref_dashboard_cards_title">Tampilkan widget pada kartu</string>
+    <string name="pref_dashboard_widget_today_title">Bagan aktivitas</string>
+    <string name="pref_dashboard_widget_today_24h_title">Mode 24j</string>
+    <string name="pref_dashboard_widget_double_size_title">Ukuran ganda</string>
+    <string name="pref_dashboard_widget_show_legend_title">Tampilkan legenda</string>
+    <string name="pref_dashboard_widget_goals_chart_title">Bagan capaian</string>
+    <string name="pref_dashboard_devices_to_include">Perangkat untuk disertakan</string>
+    <string name="pref_dashboard_widget_today_24h_summary">Tampilkan aktivitas dalam lingkaran 24j daripada lingkaran 12j ganda</string>
+    <string name="pref_dashboard_widget_double_size_summary">Perbolehkan widget untuk memakai sampai dua kolom di dasbor</string>
+    <string name="pref_dashboard_widget_show_legend_summary">Tampilkan legenda di bawah widget menjelaskan warnanya</string>
+    <string name="pref_dashboard_all_devices_summary">Gabungkan data aktivitas dari semua perangkat yang ditambahkan untuk jumlah di dasbor</string>
+    <string name="pref_dashboard_widget_today_hr_interval_title">Interval detak jantung</string>
+    <string name="pref_dashboard_widget_today_hr_interval_summary">Jumlah menit bagan menampilkan \'dipakai\' setelah pengukuran detak jantung berhasil</string>
+    <string name="error_showing_changelog">Terjadi kesalahan menampilkan catatan perubahan</string>
+    <string name="bottom_nav_dashboard">Dasbor</string>
+    <string name="pref_dashboard_widget_settings">Pengaturan widget</string>
+    <string name="pref_dashboard_all_devices_title">Semua perangkat</string>
+    <string name="pref_dashboard_select_devices_title">Pilih perangkat...</string>
+    <string name="pref_dashboard_widgets_order_summary">Pilih widget apa saja yang diaktifkan dan dalam urutan yang ditampilkan di dasbor</string>
+    <string name="pref_dashboard_select_devices_summary">Gabungkan data aktivitas dari perangkat tertentu untuk jumlah di dasbor</string>
+    <string name="pref_dashboard_cards_summary">Gambar kartu di sekitar widget dalam dasbor</string>
+    <string name="pref_auto_reply_calls_summary">Ponsel akan mengangkat panggilan masuk secara otomatis</string>
+    <string name="pref_auto_reply_calls_title">Jawab panggilan telepon secara otomatis</string>
+    <string name="pref_auto_reply_calls_delay_summary">Detik setelah panggilan diambil secara otomatis</string>
+    <string name="pref_auto_reply_calls_delay_title">Jeda Penjawaban Otomatis</string>
+    <string name="pref_header_audio">Audio</string>
+    <string name="pref_header_generic">Generik</string>
+    <string name="pref_title_notification_wake_on_open">Bangun dan buka kunci otomatis</string>
+    <string name="pref_summary_notification_wake_on_open">Bangun dan buka kunci perangkat Android ketika perangkat mengirim respons OPEN kembali. Perlu dalam keadaan terpercaya.</string>
+    <string name="pref_speak_notifications_aloud_summary">Notifikasi akan dibaca melalui headphone</string>
+    <string name="pref_speak_notifications_aloud_title">Bacakan Notifikasi</string>
+    <string name="devicetype_redmi_watch_4">Redmi Watch 4</string>
+    <string name="widget_layout_top_large_bot_wide">Besar atas, lebar bawah</string>
+    <string name="widget_layout_top_wide_bot_large">Lebar atas, besar bawah</string>
+    <string name="widget_name_colored_tile">%1$s (ubin berwarna)</string>
+    <string name="widget_name_untitled">Widget tidak berjudul (%1$s)</string>
+    <string name="pref_header_calls_and_notifications">Panggilan dan notifikasi</string>
+    <string name="armenian">Armenia</string>
+    <string name="pref_sleepasandroid_feat_spo2">SPO2</string>
+    <string name="pref_title_huawei_account">Akun Huawei</string>
+    <string name="pref_summary_huawei_account">Akun Huawei digunakan dalam proses penyandingan. Dapat menyandingkan tanpa pengaturan ulang ke pengaturan pabrik.</string>
+    <string name="serbian">Serbia</string>
+    <string name="pref_sleepasandroid_enable_summary">Aktifkan integrasi Sleep As Android</string>
+    <string name="pref_sleepasandroid_device_title">Perangkat penyedia</string>
+    <string name="pref_sleepasandroid_features_summary">Dukungan tergantung pada perangkat</string>
+    <string name="pref_sleepasandroid_slot_title">Slot alarm</string>
+    <string name="pref_sleepasandroid_slot_summary">Slot alarm mana yang digunakan ketika mengatur alarm</string>
+    <string name="alarm_slot_reset">Slot alarm telah diatur menjadi bawaan</string>
+    <string name="pref_sleepasandroid_feat_notifications">Notifikasi</string>
+    <string name="pref_dashboard_widget_today_upside_down_title">Tengah malam di bawah</string>
+    <string name="pref_dashboard_widget_today_upside_down_summary">Dalam mode 24 jam, gambar tengah malam di bawah, tengah hari di atas bagan</string>
+    <string name="sleepasandroid_settings">Sleep As Android</string>
+    <string name="pref_sleepasandroid_device_summary">Pilih perangkat sebagai penyedia data Sleep As Amdroid</string>
+    <string name="pref_sleepasandroid_features_title">Fitur</string>
+    <string name="pref_sleepasandroid_feat_alarms">Alarm</string>
+    <string name="pref_sleepasandroid_feat_movement">Akselerometer</string>
+    <string name="pref_sleepasandroid_feat_heartrate">Detak jantung</string>
+    <string name="pref_sleepasandroid_feat_oximetry">Oksimetri</string>
+    <string name="pref_title_bottom_navigation_bar">Bilah navigasi bawah</string>
+    <string name="pref_summary_bottom_navigation_bar_on">Ganti antara layar utama menggunakan bilah navigasi atau pengusapan horizontal</string>
+    <string name="pref_summary_bottom_navigation_bar_off">Ganti antara layar utama hanya menggunakan pengusapan horizontal</string>
+    <string name="watchface_resolution_doesnt_match">Resolusi wajah jam tidak cocok dengan layar perangkat. Resolusi wajah jam adalah %1$s dan layar perangkat adalah %2$s</string>
+    <string name="devicetype_huawei_watchfit2">Huawei Watch Fit 2</string>
+    <string name="cyclingPowerAverage">Daya bersepeda rata-rata</string>
+    <string name="cyclingPowerMin">Daya bersepeda minimum</string>
+    <string name="cyclingPowerMax">Daya bersepeda maksimum</string>
+    <string name="huawei_trusleep_warning">Peringatan: mengaktifkan ini akan membuat semua tidur sebagai tidur ringan di Gadgetbridge! Klik di sini jika Anda menerima hal ini.</string>
+    <string name="battery_percentage_str">%1$s%%</string>
+    <string name="toggle_debug_mode">Saklar mode pengawakutuan</string>
+    <string name="toast_setting_requires_reconnect">Pengaturan ini akan diterapkan setelah penghubungan ulang</string>
+    <string name="devicetype_moondrop_space_travel">Moondrop Space Travel</string>
+    <string name="moondrop_touch_earbud">Earbud</string>
+    <string name="moondrop_touch_earbud_both">Keduanya</string>
+    <string name="folder">Folder</string>
+    <string name="devicetype_garmin_epix_pro">Garmin Epix Pro</string>
+    <string name="loading">Memuat鈥�</string>
+    <string name="device_name_cycling_sensor">Sensor bersepeda</string>
+    <string name="copied_to_clipboard">Disalin ke papan klip</string>
+    <string name="url">URL</string>
+    <string name="devicetype_garmin_fenix_5">Garmin Fenix 5</string>
+    <string name="devicetype_garmin_fenix_6">Garmin Fenix 6</string>
+    <string name="devicetype_garmin_instinct_2s_solar">Garmin Instinct 2S Solar</string>
+    <string name="devicetype_garmin_instinct_crossover">Garmin Instinct Crossover</string>
+    <string name="devicetype_garmin_forerunner_255s">Garmin Forerunner 255S</string>
+    <string name="none">Tidak ada</string>
+    <string name="garmin_agps_local_file">Berkas lokal</string>
+    <string name="devicetype_garmin_vivoactive_3">Garmin V铆voactive 3</string>
+    <string name="moondrop_equalizer_preset_reference">Referensi</string>
+    <string name="moondrop_equalizer_preset_basshead">Basshead</string>
+    <string name="moondrop_equalizer_preset_monitor">Monitor</string>
+    <string name="moondrop_touch_trigger">Picuan</string>
+    <string name="moondrop_touch_action_play_pause">Putar/Jeda</string>
+    <string name="devicetype_garmin_fenix_6_sapphire">Garmin Fenix 6 Sapphire</string>
+    <string name="devicetype_huawei_band9">Huawei Band 9</string>
+    <string name="devicetype_huawei_watch4pro">Huawei Watch 4 (Pro)</string>
+    <string name="garmin_agps_url_i">URL %1$d AGPS</string>
+    <string name="devicetype_huawei_watchfit3">Huawei Watch Fit 3</string>
+    <string name="devicetype_garmin_instinct">Garmin Instinct</string>
+    <string name="devicetype_garmin_instinct_2s">Garmin Instinct 2S</string>
+    <string name="devicetype_garmin_instinct_2x_solar">Garmin Instinct 2X Solar</string>
+    <string name="devicetype_garmin_instinct_2_soltac">Garmin Instinct 2 SolTac</string>
+    <string name="devicetype_garmin_instinct_2_solar">Garmin Instinct 2 Solar</string>
+    <string name="devicetype_garmin_forerunner_265">Garmin Forerunner 265</string>
+    <string name="devicetype_garmin_vivoactive_4">Garmin V铆voactive 4</string>
+    <string name="devicetype_garmin_vivoactive_4s">Garmin V铆voactive 4S</string>
+    <string name="devicetype_garmin_vivoactive_5">Garmin V铆voactive 5</string>
+    <string name="open_camera">Buka Kamera</string>
+    <string name="devicetype_garmin_venu_3s">Garmin Venu 3S</string>
+    <string name="dashboard_calendar_month_goals_reached_title">% target langkah tercapai</string>
+    <plurals name="amount_of_days">
+        <item quantity="other">%d hari</item>
+    </plurals>
+    <string name="pref_fetch_unknown_files_summary">Dapatkan berkas aktivitas yang tidak diketahui dari jam tangan. Mereka tidak akan diproses, tetapi akan disimpan dalam ponsel.</string>
+    <string name="pref_summary_cycling_persistence_interval">Interval dalam detik ketika data bersepeda saat ini dituliskan ke basis data</string>
+    <string name="chart_cycling_point_label_speed">%.1f km/j</string>
+    <string name="devicetype_cycling_sensor">Sensor kecepatan bersepeda</string>
+    <string name="pref_summary_wheel_diameter">Diameter roda dalam inci. Biasanya 29, 27.5, atau 26.</string>
+    <string name="title_cycling">Bersepeda</string>
+    <string name="pref_title_wheel_diameter">Diameter roda</string>
+    <string name="pref_title_cycling_persistence_interval">Interval ketekunan</string>
+    <string name="proprietary_app_warning">Fitur ini memerlukan pemasangan aplikasi bersumber tertutup</string>
+    <string name="authentication_failed_check_key">Autentikasi gagal, silakan periksa kunci autentikasi</string>
+    <string name="activity_prefs_height_inches">Tinggi dalam inci</string>
+    <string name="pref_battery_polling_summary">Ini merupakan hasil upaya terbaik, dan dapat terlambat karena beberapa alasan</string>
+    <string name="pref_battery_polling_configuration">Konfigurasi pendapatan baterai</string>
+    <string name="pref_battery_polling_enable">Aktifkan pendapatan baterai</string>
+    <string name="pref_battery_polling_interval">Interval pendapatan baterai</string>
+    <string name="pref_battery_polling_interval_format">setiap %1$s menit</string>
+    <string name="battery_low_notify_enabled">Beri tahu pada baterai lemah</string>
+    <string name="battery_polling_failed_start">Gagal memulai pendapatan baterai</string>
+    <string name="no_folder_selected">Tidak ada folder yang dipilih</string>
+    <string name="pref_title_garmin_default_reply_suffix">Gunakan akhiran balasan yang sudah ditentukan</string>
+    <string name="pref_garmin_agps_help">Daftar di bawah berisi semua URL yang diminta oleh jam tangan untuk pembaruan AGPS. Anda dapat memilih berkas dari penyimpanan ponsel yang akan dikirim ke jam tangan ketika meminta pembaruan.</string>
+    <string name="battery_full_threshold">Ambang baterai penuh</string>
+    <string name="pref_fetch_unknown_files_title">Dapatkan berkas tidak diketahui</string>
+    <string name="pref_adaptive_noise_cancelling_title">ANC Adaptif</string>
+    <string name="pref_adaptive_noise_cancelling_summary">Tetapkan kekuatan ANC secara otomatis tergantung pada tingkat suara ambien</string>
+    <string name="chart_cycling_point_label_distance">Hari ini: %.1f km
+\nJumlah: %.1f km</string>
+    <string name="devicetype_garmin_vivomove_trend">Garmin V铆vomove Trend</string>
+    <string name="devicetype_garmin_venu_2_plus">Garmin Venu 2 Plus</string>
+    <string name="devicetype_garmin_instinct_solar">Garmin Instinct Solar</string>
+    <string name="devicetype_garmin_vivosmart_5">Garmin V铆vosmart 5</string>
+    <string name="devicetype_hama_fit6900">Hama Fit6900</string>
+    <string name="moondrop_touch_action_call_pick_hang">Jawab/Tolak Panggilan</string>
+    <string name="moondrop_touch_action_call_start">Mulai Panggilan</string>
+    <string name="moondrop_touch_action_assistant">Picu Asisten Suara</string>
+    <string name="moondrop_touch_trigger_long_press_3s">Tekanan Panjang (3 dtk)</string>
+    <string name="moondrop_touch_action_anc_mode">Saklar Mode Pembatalan Suara Aktif</string>
+    <string name="pref_speak_notifications_focus_exclusive_title">Jeda audio dari aplikasi lain</string>
+    <string name="pref_speak_notifications_focus_exclusive_summary_on">Pemutaran dari aplikasi lain akan dijeda ketika notifikasi ini diucapkan</string>
+    <string name="device_name_bicycle_sensor">Sensor sepeda</string>
+    <string name="toast_camera_permission_required">Perizinan kamera diperlukan untuk fungsi ini.</string>
+    <string name="toast_camera_support_required">Dukungan kamera diperlukan untuk fungsi ini.</string>
+    <string name="toast_camera_photo_taken">Foto telah diambil dan disimpan pada: %s</string>
+    <string name="unsupported">Tidak didukung</string>
+    <string name="realtime_settings">Pengaturan waktu nyata</string>
+    <string name="min_val">Minimum: %d</string>
+    <string name="max_val">Maksimum: %d</string>
+    <string name="folder_is_empty">Folder kosong</string>
+    <string name="share_debug_info">Bagikan info pengawakutuan</string>
+    <string name="notif_battery_full_title">Baterai gadget penuh!</string>
+    <string name="notif_battery_full">Baterai %1$s penuh</string>
+    <string name="devicetype_garmin_fenix_7_pro">Garmin Fenix 7 Pro</string>
+    <string name="notification_channel_full_battery_name">Baterai penuh</string>
+    <string name="show_in_notification">Tampilkan dalam notifikasi</string>
+    <string name="default_percentage">Bawaan (%1$d%%)</string>
+    <string name="pref_touch_tone">Nada Sentuh</string>
+    <string name="pref_wearing_tone">Nada Pemakaian</string>
+    <string name="pref_touch_tone_summary">Memutar nada ketika earbud disentuh</string>
+    <string name="pref_wearing_tone_summary">Memutar nada ketika earbud dimasukkan</string>
+    <string name="devicetype_garmin_fenix_7s">Garmin Fenix 7S</string>
+    <string name="battery_i">Baterai %d</string>
+    <string name="intent_api_broadcast_activity_sync_summary">Siarkan intent ketika sinkronisasi aktivitas selesai untuk perangkat apa pun</string>
+    <string name="battery_low_threshold">Ambang baterai lemah</string>
+    <string name="battery_full_notify_enabled">Beri tahu pada baterai penuh</string>
+    <string name="devicetype_garmin_forerunner_255">Garmin Forerunner 255</string>
+    <string name="devicetype_soundcore_liberty3_pro">Soundcore Liberty 3 Pro</string>
+    <string name="preferences_qhybrid_settings_summary">Pengaturan lawas untuk jam tangan Q Hybrid</string>
+    <string name="moondrop_touch_trigger_long_press_1s">Tekanan Panjang (1 dtk)</string>
+    <string name="intent_api_broadcast_activity_sync_title">Siarkan pada penyelesaian sinkronisasi aktivitas</string>
+    <string name="agps_status_missing">Hilang</string>
+    <string name="agps_status_pending">Tertunda</string>
+    <string name="agps_status_current">Saat ini</string>
+    <string name="agps_status_error">Kesalahan</string>
+    <string name="pref_agps_status">Status AGPS</string>
+    <string name="devicetype_amazfit_bip5_unity">Amazfit Bip 5 Unity</string>
+    <string name="devicetype_garmin_vivomove_hr">Garmin V铆vomove HR</string>
+    <string name="devicetype_garmin_vivomove_style">Garmin V铆vomove Style</string>
+    <string name="devicetype_garmin_venu_3">Garmin Venu 3</string>
+    <string name="devicetype_garmin_swim_2">Garmin Swim 2</string>
+    <string name="pref_summary_garmin_default_reply_suffix">Disertakan dengan tambahan set akhiran dalam Gadgetbridge</string>
+    <string name="pref_speak_notifications_focus_exclusive_summary_off">Volume pemutaran aplikasi lain akan direndahkan ketika notifikasi ini diucapkan</string>
+    <string name="devicetype_garmin_venu_2">Garmin Venu 2</string>
+    <string name="devicetype_garmin_forerunner_245">Garmin Forerunner 245</string>
+    <string name="devicetype_miband9">Xiaomi Smart Band 9</string>
+    <string name="devicetype_amazfit_trex_3">Amazfit T-Rex 3</string>
+    <string name="devicetype_garmin_forerunner_255_music">Garmin Forerunner 255 Music</string>
+    <string name="pref_title_notification_times_enabled">Waktu notifikasi</string>
+    <string name="pref_summary_notification_times_enabled">Hanya kirim notifikasi pada waktu tertentu</string>
+    <string name="devicetype_redmi_watch_5_active">Redmi Watch 5 Active</string>
+    <string name="devicetype_colmi_r02">Colmi R02</string>
+    <string name="devicetype_colmi_r06">Colmi R06</string>
+    <string name="devicetype_colmi_r03">Colmi R03</string>
+    <string name="minutes_20">20 menit</string>
+    <string name="minutes_240">240 menit</string>
+    <string name="devicetype_garmin_venu_2s">Garmin Venu 2S</string>
+    <string name="devicetype_micompositionscale">Mi Body Composition Scale 2</string>
+    <string name="devicetype_mijia_xmwsdj04">Mijia Temperature and Humidity Sensor 2 (E-ink)</string>
+    <string name="devicetype_honor_watchgs3">Honor Watch GS 3</string>
+    <string name="devicetype_nothing_cmf_buds_pro_2">CMF Buds Pro 2</string>
+    <string name="devicetype_nothing_cmf_watch_pro_2">CMF Watch Pro 2</string>
+    <string name="devicetype_sony_wf_c700n">Sony WF-C700N</string>
+    <string name="devicetype_soundcore_motion300">Soundcore Motion 300</string>
+    <string name="devicetype_huawei_watch3">Huawei Watch 3 (Pro)</string>
+    <string name="devicetype_garmin_fenix_5_plus">Garmin Fenix 5 Plus</string>
+    <string name="pref_title_calendar_lookahead">Jumlah hari berikut</string>
+    <string name="pref_summary_calendar_lookahead">Sinkronkan sampai dengan %1s hari dari acara kalender</string>
+    <string name="minutes_255">255 menit</string>
+    <string name="devicetype_garmin_fenix_5x_plus">Garmin Fenix 5X Plus</string>
+    <string name="devicetype_garmin_forerunner_245_music">Garmin Forerunner 245 Music</string>
+    <string name="devicetype_vivitar_hr_bp_monitor_activity_tracker">Vivitar HR &amp; BP Monitor Activity Tracker</string>
+    <string name="devicetype_huawei_watchgtrunner">Huawei Watch GT Runner</string>
+    <string name="pref_time_sync">Sinkronisasi waktu otomatis</string>
+    <string name="minutes_7">7 menit</string>
+    <string name="minutes_8">8 menit</string>
+    <string name="minutes_2">2 menit</string>
+    <string name="minutes_3">3 menit</string>
+    <string name="minutes_4">4 menit</string>
+    <string name="minutes_6">6 menit</string>
+    <string name="devicetype_huawei_watchgtcyber">Huawei Watch GT Cyber</string>
+    <string name="minutes_150">150 menit</string>
+    <string name="devicetype_garmin_venu">Garmin Venu</string>
+    <string name="devicetype_garmin_forerunner_265s">Garmin Forerunner 265S</string>
+    <string name="devicetype_garmin_forerunner_965">Garmin Forerunner 965</string>
+    <string name="devicetype_mismartscale">Mi Smart Scale 2</string>
+    <string name="devicetype_soundcore_liberty4_nc">Soundcore Liberty 4 NC</string>
+    <string name="devicetype_honor_watchgspro">Honor Watch GS Pro</string>
+    <string name="search">Cari</string>
+    <string name="minutes_9">9 menit</string>
+    <string name="minutes_15">15 menit</string>
+    <string name="minutes_45">45 menit</string>
+    <string name="minutes_60">60 menit</string>
+    <string name="minutes_75">75 menit</string>
+    <string name="minutes_90">90 menit</string>
+    <string name="minutes_120">120 menit</string>
+    <string name="minutes_180">180 menit</string>
+    <string name="minutes_210">210 menit</string>
+    <string name="devicetype_garmin_forerunner_955">Garmin Forerunner 955</string>
+    <string name="devicetype_garmin_vivosport">Garmin V铆vosport</string>
+    <string name="devicetype_garmin_fenix_8">Garmin Fenix 8</string>
+    <string name="devicetype_sony_wf_c500">Sony WF-C500</string>
+    <string name="devicetype_huawei_watchgt5">Huawei Watch GT 5 (Pro)</string>
+    <string name="devicetype_ble_gatt_client">Klien Generik BLE GATT</string>
+    <string name="pref_title_nagivation_apps">Aplikasi navigasi</string>
+    <string name="pref_header_external_integrations">Integrasi Eksternal</string>
+    <string name="pref_header_automations">Otomatisasi</string>
+    <string name="pref_description_general">Pemulaian, bahasa, wilayah, lokasi</string>
+    <string name="pref_description_about_you">Tanggal lahir, kelamin, tinggi, berat, goal</string>
+    <string name="pref_description_notifications">Notifikasi aplikasi, daftar putih/hitam</string>
+    <string name="pref_description_dashboard">Widget, perangkat yang disertakan</string>
+    <string name="pref_description_automations">Ekspor otomatis, pendapatan data otomatis</string>
+    <string name="pref_description_developer_options">Log, API intent</string>
+    <string name="pref_header_main_screen">Layar utama</string>
+    <string name="pref_title_user_interface">Antarmuka pengguna</string>
+    <string name="devicetype_garmin_venu_sq">Garmin Venu Sq</string>
+    <string name="devicetype_garmin_enduro_3">Garmin Enduro 3</string>
+    <string name="devicetype_garmin_forerunner_165">Garmin Forerunner 165</string>
+    <string name="devicetype_garmin_forerunner_255s_music">Garmin Forerunner 255S Music</string>
+    <string name="pref_description_deprecated_functionalities">Pengaturan yang akan dihapus dalam versi depan</string>
+    <string name="pref_header_sound">Suara</string>
+    <string name="pref_description_user_interface">Tema, layar utama</string>
+    <string name="gps_glonass">GPS + GLONASS</string>
+</resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml
new file mode 100644
index 0000000..17bbfc3
--- /dev/null
+++ b/app/src/main/res/values-it/strings.xml
@@ -0,0 +1,3674 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources xmlns:tools="http://schemas.android.com/tools">
+    <string name="application_name_generic">Gadgetbridge</string>
+    <string name="title_activity_controlcenter_generic">Gadgetbridge</string>
+    <string name="action_settings">Impostazioni</string>
+    <string name="action_debug">Debug</string>
+    <string name="action_quit">Esci</string>
+    <string name="action_donate">Dona</string>
+    <string name="controlcenter_fetch_activity_data">Sincronizza</string>
+    <string name="controlcenter_find_device">Trova dispositivo smarrito</string>
+    <string name="controlcenter_take_screenshot">Cattura una schermata</string>
+    <string name="controlcenter_disconnect">Disconnetti</string>
+    <string name="controlcenter_delete_device">Rimuovi dispositivo</string>
+    <string name="controlcenter_delete_device_name">Rimuovi %1$s</string>
+    <string name="controlcenter_delete_device_dialogmessage">Il dispositivo verr脿 rimosso e tutti i dati ad esso associati verranno cancellati!</string>
+    <string name="controlcenter_navigation_drawer_open">Apri men霉 di navigazione</string>
+    <string name="controlcenter_navigation_drawer_close">Chiudi men霉 di navigazione</string>
+    <string name="controlcenter_snackbar_need_longpress">Premi a lungo sulla scheda per scollegare</string>
+    <string name="controlcenter_snackbar_disconnecting">Disconnessione</string>
+    <string name="controlcenter_snackbar_connecting">Connessione鈥�</string>
+    <string name="controlcenter_snackbar_requested_screenshot">Acquisizione di una schermata del dispositivo</string>
+    <string name="title_activity_debug">Debug</string>
+    <!--Strings related to AppManager-->
+    <string name="title_activity_appmanager">Gestione app</string>
+    <string name="appmanager_cached_watchapps_watchfaces">App nella cache</string>
+    <string name="appmanager_installed_watchapps">App installate</string>
+    <string name="appmanager_installed_watchfaces">Watchface installate</string>
+    <string name="appmananger_app_delete">Cancella</string>
+    <string name="appmananger_app_delete_cache">Cancella e rimuovi dalla cache</string>
+    <string name="appmananger_app_reinstall">Re-installazione</string>
+    <string name="appmanager_app_openinstore">Cerca nell\'Appstore di Pebble</string>
+    <string name="appmanager_health_activate">Attiva</string>
+    <string name="appmanager_health_deactivate">Disattiva</string>
+    <string name="appmanager_hrm_activate">Attiva il monitor del battito cardiaco</string>
+    <string name="appmanager_hrm_deactivate">Disattiva il monitor del battito cardiaco</string>
+    <string name="appmanager_weather_activate">Attiva l\'applicazione meteo di sistema</string>
+    <string name="appmanager_weather_deactivate">Disattiva l\'applicazione meteo di sistema</string>
+    <string name="appmanager_weather_install_provider">Installa l\'applicazione Weather Notification</string>
+    <string name="app_configure">Configura</string>
+    <string name="app_move_to_top">Sposta in cima</string>
+    <!--Strings related to CalBlacklist-->
+    <string name="title_activity_calblacklist">Calendari ignorati</string>
+    <!--Strings related to FwAppInstaller-->
+    <string name="title_activity_fw_app_insaller">Installazione FW/App</string>
+    <string name="fw_upgrade_notice">Stai per installare il firmware %s.</string>
+    <string name="fw_multi_upgrade_notice">Stai per installare i firmware %1$s e %2$s al posto di quelli al momento sulla tua Mi Band.</string>
+    <string name="miband_firmware_known">Questo firmware 猫 stato testato ed 猫 compatibile con Gadgetbridge.</string>
+    <string name="miband_firmware_unknown_warning">Questo firmware non 猫 stato testato e potrebbe non essere compatibile con Gadgetbridge. 
+\n 
+\nNON 猫 consigliato installarlo sulla tua Mi Band!</string>
+    <string name="miband_firmware_suggest_whitelist">Se si procede e tutto continua a funzionare ti invitiamo a contattare gli sviluppatori e dire loro di aggiungere il firmware %s all\'elenco di quelli compatibili.</string>
+    <!--Strings related to Settings-->
+    <string name="title_activity_settings">Impostazioni</string>
+    <string name="pref_header_general">Impostazioni generali</string>
+    <string name="pref_title_general_autoconnectonbluetooth">Collegati al/ai dispositivo/i Gadgetbridge quando il Bluetooth viene acceso</string>
+    <string name="pref_title_general_autostartonboot">Avvio automatico</string>
+    <string name="pref_title_general_autoreconnect">Riconnessione automatica</string>
+    <string name="pref_title_audio_player">Riproduttore musicale preferito</string>
+    <string name="pref_default">Predefinito</string>
+    <string name="pref_header_datetime">Data e ora</string>
+    <string name="pref_title_datetime_syctimeonconnect">Sincronizza l\'ora</string>
+    <string name="pref_summary_datetime_syctimeonconnect">Sincronizza l\'orario del/i dispositivo/i al collegamento con Gadgetbridge e quando viene cambiata l\'ora o il fuso orario sul dispositivo e periodicamente</string>
+    <string name="pref_title_theme">Tema</string>
+    <string name="pref_theme_light">Chiaro</string>
+    <string name="pref_theme_dark">Scuro</string>
+    <string name="pref_title_language">Lingua</string>
+    <string name="pref_title_minimize_priority">Nascondi l\'icona di Gadgetbridge nell\'area delle notifiche</string>
+    <string name="pref_summary_minimize_priority_off">L\'icona nella barra di stato e la notifica nella schermata di blocco vengono mostrate</string>
+    <string name="pref_summary_minimize_priority_on">L\'icona nella barra di stato e la notifica nella schermata di blocco non vengono mostrate</string>
+    <string name="pref_header_notifications">Notifiche</string>
+    <string name="pref_title_notifications_repetitions">Ripetute</string>
+    <string name="pref_title_notifications_call">Chiamate telefoniche</string>
+    <string name="pref_title_notifications_sms">SMS</string>
+    <string name="pref_title_notifications_pebblemsg">Messaggi Pebble</string>
+    <string name="pref_summary_notifications_pebblemsg">Supporto per applicazioni che inviano le notifiche a Pebble usando PebbleKit.</string>
+    <string name="pref_title_notifications_generic">Supporto notifiche generiche</string>
+    <string name="pref_title_whenscreenon">鈥nche quando lo schermo 猫 acceso</string>
+    <string name="pref_title_notification_filter">Non disturbare</string>
+    <string name="pref_summary_notification_filter">Blocca tutte le notifiche quando il telefono 猫 in modalit脿 Non Disturbare</string>
+    <string name="pref_title_transliteration">Traslitterazione</string>
+    <string name="pref_summary_transliteration">Abilita questa opzione se il tuo dispositivo non supporta tutti i caratteri della tua lingua</string>
+    <string name="always">Sempre</string>
+    <string name="when_screen_off">Quando lo schermo 猫 spento</string>
+    <string name="never">Mai</string>
+    <string name="pref_header_privacy">Privacy</string>
+    <string name="pref_title_call_privacy_mode">Impostazioni privacy chiamate</string>
+    <string name="pref_call_privacy_mode_off">Mostra nome e numero</string>
+    <string name="pref_call_privacy_mode_name">Nascondi il nome ma mostra il numero</string>
+    <string name="pref_call_privacy_mode_number">Nascondi il numero ma mostra il nome</string>
+    <string name="pref_call_privacy_mode_complete">Nascondi nome e numero</string>
+    <string name="pref_header_cannned_messages">Messaggi preimpostati</string>
+    <string name="pref_title_canned_replies">Risposte</string>
+    <string name="pref_title_canned_reply_suffix">Suffisso applicato alle risposte automatiche</string>
+    <string name="pref_title_canned_messages_dismisscall">Rifiuto chiamata</string>
+    <string name="pref_title_canned_messages_set">Invia al dispositivo</string>
+    <string name="pref_header_development">Opzioni sviluppatore</string>
+    <string name="pref_title_development_miaddr">Indirizzo Mi Band</string>
+    <string name="pref_title_pebble_settings">Impostazioni Pebble</string>
+    <string name="pref_header_activitytrackers">Tracciatore di attivit脿</string>
+    <string name="pref_title_pebble_activitytracker">Tracciatore di attivit脿 preferito</string>
+    <string name="pref_title_pebble_sync_health">Sincronizza Pebble Health</string>
+    <string name="pref_title_pebble_sync_misfit">Sincronizza Misfit</string>
+    <string name="pref_title_pebble_sync_morpheuz">Sincronizza Morpheuz</string>
+    <string name="pref_title_enable_outgoing_call">Mostra le chiamate in uscita</string>
+    <string name="pref_summary_enable_outgoing_call">Disabilitare questo impedir脿 la vibrazione del Pebble 2/LE durante le chiamate in uscita</string>
+    <string name="pref_title_enable_pebblekit">Consenti accesso ad altre applicazioni</string>
+    <string name="pref_summary_enable_pebblekit">Attiva il supporto sperimentale ad applicazioni Android con PebbleKit</string>
+    <string name="pref_title_sunrise_sunset">Alba e tramonto</string>
+    <string name="pref_summary_sunrise_sunset">Mostra gli orari calcolati per l\'alba e il tramonto sulla timeline</string>
+    <string name="pref_title_enable_calendar_sync">Sincronizza calendario</string>
+    <string name="pref_title_autoremove_notifications">Rimuovi automaticamente le notifiche</string>
+    <string name="pref_summary_autoremove_notifications">Le notifiche vengono rimosse automaticamente dal dispositivo dopo averle nascoste sul telefono</string>
+    <string name="pref_title_pebble_privacy_mode">Impostazioni privacy</string>
+    <string name="pref_pebble_privacy_mode_off">Notifiche normali</string>
+    <string name="pref_pebble_privacy_mode_content">Sposta il testo delle notifiche fuori dallo schermo</string>
+    <string name="pref_pebble_privacy_mode_complete">Mostra solo l\'icona della notifica</string>
+    <string name="pref_header_location">Posizione</string>
+    <string name="pref_title_location_aquire">Acquisisci posizione</string>
+    <string name="pref_title_location_latitude">Latitudine</string>
+    <string name="pref_title_location_longitude">Longitudine</string>
+    <string name="pref_title_location_keep_uptodate">Mantieni aggiornata la posizione</string>
+    <string name="pref_summary_location_keep_uptodate">Cerca di ottenere la posizione aggiornata durante l\'utilizzo, usa quella memorizzata come backup</string>
+    <string name="toast_enable_networklocationprovider">Per cortesia abilita la localizzazione utilizzando la rete</string>
+    <string name="toast_aqurired_networklocation">posizione acquisita</string>
+    <string name="pref_title_pebble_forceprotocol">Forza protocollo delle notifiche</string>
+    <string name="pref_summary_pebble_forceprotocol">Questa opzione forza l\'utilizzo della versione pi霉 recente del protocollo delle notifiche legato al firmware del tuo dispositivo. ABILITALA SOLO SE SAI COSA STAI FACENDO!</string>
+    <string name="pref_title_pebble_forceuntested">Abilita funzionalit脿 non testate</string>
+    <string name="pref_summary_pebble_forceuntested">Abilita funzionalit脿 non testate. SAI QUELLO CHE STAI FACENDO!</string>
+    <string name="pref_title_pebble_forcele">Usa sempre BLE</string>
+    <string name="pref_summary_pebble_forcele">Utilizza il supporto sperimentale Pebble LE per tutti i Pebble in alternativa al BT. Questo richiede un nuovo accoppiamento a LE prima e poi al Pebble LE</string>
+    <string name="pref_title_pebble_mtu_limit">Limita la MTU del Pebble 2/LE</string>
+    <string name="pref_summary_pebble_mtu_limit">Se il tuo Pebble 2/LE non funziona come dovrebbe, prova a impostare un limite alla MTU (range valido 20鈥�512)</string>
+    <string name="pref_title_pebble_enable_applogs">Abilita il log delle applicazioni eseguite su Pebble</string>
+    <string name="pref_summary_pebble_enable_applogs">Il log delle applicazioni che girano su Pebble verr脿 aggiunto a quello di Gadgetbridge (richiede riconessione)</string>
+    <string name="pref_title_pebble_always_ack_pebblekit">Invia l\'ACK dei messaggi PebbleKit prematuramente</string>
+    <string name="pref_summary_pebble_always_ack_pebblekit">I messaggi inviati alle applicazioni di terze parti verranno immediatamente confermate</string>
+    <string name="pref_title_pebble_reconnect_attempts">Tentativi di riconessione</string>
+    <!--HPlus Preferences-->
+    <string name="pref_title_unit_system">Unit脿</string>
+    <string name="pref_title_timeformat">Formato orario</string>
+    <string name="pref_title_screentime">Durata dell\'accensione dello schermo</string>
+    <string name="prefs_title_all_day_heart_rate">Misurazione della frequenza cardiaca per tutto il giorno</string>
+    <string name="preferences_hplus_settings">Impostazioni HPlus/Makibes</string>
+    <string name="not_connected">Non connesso</string>
+    <string name="connecting">In collegamento</string>
+    <string name="connected">Connesso</string>
+    <string name="_unknown_">(sconosciuto)</string>
+    <string name="test">Test</string>
+    <string name="test_notification">Notifica di prova</string>
+    <string name="this_is_a_test_notification_from_gadgetbridge">Notifica di prova creata da Gadgetbridge</string>
+    <string name="bluetooth_is_not_supported_">Bluetooth non supportato.</string>
+    <string name="bluetooth_is_disabled_">Bluetooth disabilitato.</string>
+    <string name="tap_connected_device_for_app_mananger">Tocca il dispositivo connesso per gestire le App</string>
+    <string name="tap_connected_device_for_activity">Tocca il dispositivo connesso per vedere le tue attivit脿</string>
+    <string name="tap_connected_device_for_vibration">Tocca il dispositivo connesso per la vibrazione</string>
+    <string name="tap_a_device_to_connect">tocca il dispositivo a cui connettersi</string>
+    <string name="cannot_connect_bt_address_invalid_">Impossibile connettersi. Indirizzo Bluetooth non valido?</string>
+    <string name="gadgetbridge_running_generic">Gadgetbridge in esecuzione</string>
+    <string name="installing_binary_d_d">Installazione del binario %1$d/%2$d</string>
+    <string name="installation_failed_">Installazione fallita</string>
+    <string name="installation_successful">Installazione conclusa con successo</string>
+    <string name="firmware_install_warning">STAI CERCANDO DI INSTALLARE UN FIRMWARE, PROCEDI A TUO RISCHIO E PERICOLO.
+\n
+\n
+\nQuesto firmware 猫 per la versione HW: %s</string>
+    <string name="app_install_info">Stai per installare l\'app:
+\n
+\n%1$s
+\nVersione %2$s di %3$s
+\n</string>
+    <string name="n_a">N/A</string>
+    <string name="initialized">inizializzato</string>
+    <string name="appversion_by_creator">%1$s di %2$s</string>
+    <string name="title_activity_discovery">Ricerca dispositivi</string>
+    <string name="discovery_stop_scanning">Interrompi scansione</string>
+    <string name="discovery_start_scanning">Inizia scansione</string>
+    <string name="action_discover">Connetti un nuovo dispositivo</string>
+    <string name="device_with_rssi">%1$s (%2$s)</string>
+    <string name="title_activity_android_pairing">Accoppia dispositivo</string>
+    <string name="android_pairing_hint">Utilizza la funzione del sistema operativo per accoppiare il dispositivo.</string>
+    <string name="title_activity_mi_band_pairing">Accoppia la Mi Band</string>
+    <string name="pairing">Accoppiamento con %s鈥�</string>
+    <string name="pairing_creating_bond_with">Creazione bonding con %1$s (%2$s)</string>
+    <string name="pairing_unable_to_pair_with">Impossibile completare il pairing con %1$s (%2$s)</string>
+    <string name="pairing_in_progress">Bonding in corso: %1$s (%2$s)</string>
+    <string name="pairing_already_bonded">Accoppiamento gi脿 effettuato con %1$s (%2$s), connessione in corso鈥�</string>
+    <string name="message_cannot_pair_no_mac">Indirizzo MAC mancante, impossibile completare l\'accoppiamento.</string>
+    <string name="preferences_category_device_specific_settings">Impostazioni specifiche dispositivo</string>
+    <string name="preferences_miband_settings">Impostazioni Mi Band / Amazfit</string>
+    <string name="male">Uomo</string>
+    <string name="female">Donna</string>
+    <string name="other">Altro</string>
+    <string name="left">Sinistro</string>
+    <string name="right">Destro</string>
+    <string name="miband_pairing_using_dummy_userdata">Dati dell\'utente non inseriti, vengono usati dati d\'esempio.</string>
+    <string name="miband_pairing_tap_hint">Quando la Mi Band vibra e lampeggia, dalle qualche leggero colpetto.</string>
+    <string name="appinstaller_install">Installa</string>
+    <string name="discovery_connected_devices_hint">Imposta il tuo dispositivo perch茅 sia rilevabile. I dispositivi attualmente connessi non saranno probabilmente rilevati. Abilita i servizi di geolocalizzazione su android 6+. Disabilita Privacy Guard per Gadgetbridge, potrebbe causare crash e riavvii. Se non vedi il tuo dispositivo entro un paio di minuti, riprova dopo avere riavviato il dispositivo Android.</string>
+    <string name="discovery_note">Nota:</string>
+    <string name="candidate_item_device_image">Immagine dispositivo</string>
+    <string name="miband_prefs_alias">Nome / Soprannome</string>
+    <string name="pref_header_vibration_count">Numero vibrazioni</string>
+    <string name="title_activity_sleepmonitor">Monitoraggio del sonno</string>
+    <string name="pref_write_logfiles">Salva il log su file</string>
+    <string name="initializing">Inizializzazione in corso</string>
+    <string name="busy_task_fetch_activity_data">Recupero dati attivit脿</string>
+    <string name="sleep_activity_date_range">Da %1$s a %2$s</string>
+    <string name="prefs_wearside">Indossato sul braccio sinistro o destro?</string>
+    <string name="pref_screen_vibration_profile">Profilo vibrazioni</string>
+    <string name="vibration_profile_staccato">Staccato</string>
+    <string name="vibration_profile_short">Breve</string>
+    <string name="vibration_profile_medium">Medio</string>
+    <string name="vibration_profile_long">Lungo</string>
+    <string name="vibration_profile_waterdrop">Goccia</string>
+    <string name="vibration_profile_ring">Anello</string>
+    <string name="vibration_profile_alarm_clock">Sveglia</string>
+    <string name="miband_prefs_vibration">Vibrazione</string>
+    <string name="vibration_try">Test</string>
+    <string name="pref_screen_notification_profile_sms">Notifiche SMS</string>
+    <string name="pref_header_vibration_settings">Impostazioni vibrazioni</string>
+    <string name="pref_screen_notification_profile_generic">Notifiche generiche</string>
+    <string name="pref_screen_notification_profile_email">Notifiche via e-mail</string>
+    <string name="pref_screen_notification_profile_incoming_call">Notifiche chiamata in arrivo</string>
+    <string name="pref_screen_notification_profile_generic_chat">Chat</string>
+    <string name="pref_screen_notification_profile_generic_navigation">Navigazione</string>
+    <string name="pref_screen_notification_profile_generic_social">Social Network</string>
+    <string name="stats_title">Zone di velocit脿</string>
+    <string name="stats_x_axis_label">Minuti totali</string>
+    <string name="stats_y_axis_label">Passi al minuto</string>
+    <string name="control_center_find_lost_device">Trova dispositivo smarrito</string>
+    <string name="control_center_cancel_to_stop_vibration">Annulla per fermare la vibrazione.</string>
+    <string name="title_activity_charts">Attivit脿 e Sonno</string>
+    <string name="title_activity_set_alarm">Configurazione sveglia</string>
+    <string name="controlcenter_start_configure_alarms">Configurazione sveglia</string>
+    <string name="title_activity_alarm_details">Sveglia</string>
+    <string name="alarm_sun_short">Dom</string>
+    <string name="alarm_mon_short">Lu</string>
+    <string name="alarm_tue_short">Mar</string>
+    <string name="alarm_wed_short">Mer</string>
+    <string name="alarm_thu_short">Gio</string>
+    <string name="alarm_fri_short">Ven</string>
+    <string name="alarm_sat_short">Sab</string>
+    <string name="alarm_smart_wakeup">Sveglia intelligente</string>
+    <string name="user_feedback_miband_set_alarms_failed">Errore nell\'impostazione delle sveglie, riprova di nuovo.</string>
+    <string name="user_feedback_miband_set_alarms_ok">Sveglie inviate al dispositivo.</string>
+    <string name="chart_no_data_synchronize">Non ci sono dati. Hai effettuato la sincronizzazione con il device?</string>
+    <string name="user_feedback_miband_activity_data_transfer">Vengono trasferiti %1$s a partire dal %2$s</string>
+    <string name="miband_prefs_fitness_goal">Traguardo giornaliero di passi</string>
+    <string name="dbaccess_error_executing">Errore eseguendo \'%1$s\'</string>
+    <string name="controlcenter_start_activitymonitor">Le tue attivit脿</string>
+    <string name="cannot_connect">Impossibile connettere: %1$s</string>
+    <string name="installer_activity_unable_to_find_handler">Impossibile aprire questo file.</string>
+    <string name="pbw_install_handler_unable_to_install">Impossibile installare il file: %1$s</string>
+    <string name="pbw_install_handler_hw_revision_mismatch">Impossibile installare il firmware: non corrisponde alla versione hardware del tuo Pebble.</string>
+    <string name="installer_activity_wait_while_determining_status">Lo stato dell\'installazione viene rilevato鈥�</string>
+    <string name="notif_battery_low_title">Batteria del dispositivo bassa!</string>
+    <string name="notif_battery_low_percent">%1$s batteria rimanente: %2$s%%</string>
+    <string name="notif_battery_low_bigtext_last_charge_time">Ultima ricarica: %s \n</string>
+    <string name="notif_battery_low_bigtext_number_of_charges">Numero di ricariche: %s</string>
+    <string name="sleepchart_your_sleep">Sonno</string>
+    <string name="weeksleepchart_sleep_a_week">Sonno settimanale</string>
+    <string name="weeksleepchart_today_sleep_description">Sonno di oggi, obiettivo: %1$s</string>
+    <string name="weekstepschart_steps_a_week">Passi della settimana</string>
+    <string name="activity_sleepchart_activity_and_sleep">Attivit脿</string>
+    <string name="updating_firmware">Aggiornamento del Firmware鈥�</string>
+    <string name="fwapp_install_device_not_ready">Il file non pu貌 essere installato, il dispositivo non 猫 pronto.</string>
+    <string name="installhandler_firmware_name">%1$s: %2$s %3$s</string>
+    <string name="miband_fwinstaller_compatible_version">Versione compatibile</string>
+    <string name="miband_fwinstaller_untested_version">Versione non testata!</string>
+    <string name="fwappinstaller_connection_state">Connessione al device: %1$s</string>
+    <string name="pbw_installhandler_pebble_firmware">Firmware Pebble: %1$s</string>
+    <string name="pbwinstallhandler_correct_hw_revision">Versione hardware corretta</string>
+    <string name="pbwinstallhandler_incorrect_hw_revision">Conflitto nella versione hardware!</string>
+    <string name="pbwinstallhandler_app_item">%1$s (%2$s)</string>
+    <string name="updatefirmwareoperation_updateproblem_do_not_reboot">Problema durante il caricamento del firmware. NON RIAVVIARE la Mi Band!</string>
+    <string name="updatefirmwareoperation_metadata_updateproblem">Problema durante il caricamento dei metadati del firmware</string>
+    <string name="updatefirmwareoperation_update_complete">Installazione del firmware completata</string>
+    <string name="updatefirmwareoperation_update_complete_rebooting">Installazione del firmware completata, riavvio del dispositivo鈥�</string>
+    <string name="updatefirmwareoperation_write_failed">Aggiornamento del firmware non riuscita</string>
+    <string name="chart_steps">Passi</string>
+    <string name="calories">Calorie</string>
+    <string name="distance">Distanza</string>
+    <string name="clock">Orologio</string>
+    <string name="heart_rate">Pulsazioni</string>
+    <string name="battery">Batteria</string>
+    <string name="liveactivity_live_activity">Attivit脿 in tempo reale</string>
+    <string name="weeksteps_today_steps_description">Passi di oggi, traguardo: %1$s</string>
+    <string name="pref_title_dont_ack_transfer">Non confermare il trasferimento dati</string>
+    <string name="pref_summary_dont_ack_transfers">Se ACK non viene inviato alla band, i dati relativi alle attivit脿 non vengono cancellati. Utile se GB viene usato insieme ad altre applicazioni.</string>
+    <string name="pref_summary_keep_data_on_device">Conserva i dati delle attivit脿 sull dispositivo anche dopo averli sincronizzati. Utile se GB 猫 usato insieme ad altre app. Pu貌 causare l\'esaurimento dello spazio disponibile sull\'orologio e/o interrompere la sincronizzazione.</string>
+    <string name="pref_title_low_latency_fw_update">Utilizza la modalit脿 a bassa latenza per gli aggiornamenti del firmware</string>
+    <string name="pref_summary_low_latency_fw_update">Pu貌 essere utile quando l\'aggiornamento del firmware fallisce.</string>
+    <string name="live_activity_steps_history">Storico dei passi</string>
+    <string name="live_activity_current_steps_per_minute">Passi/minuto</string>
+    <string name="live_activity_total_steps">Passi totali</string>
+    <string name="live_activity_steps_per_minute_history">Storico passi/minuto</string>
+    <string name="live_activity_start_your_activity">Inizia la tua attivit脿</string>
+    <string name="abstract_chart_fragment_kind_activity">Attivit脿</string>
+    <string name="abstract_chart_fragment_kind_light_sleep">Sonno leggero</string>
+    <string name="abstract_chart_fragment_kind_deep_sleep">Sonno profondo</string>
+    <string name="abstract_chart_fragment_kind_not_worn">Non indossata</string>
+    <string name="device_not_connected">Non connesso.</string>
+    <string name="user_feedback_all_alarms_disabled">Tutte le sveglie disabilitate</string>
+    <string name="pref_title_keep_data_on_device">Conserva i dati delle attivit脿 sul dispositivo</string>
+    <string name="miband_fwinstaller_incompatible_version">Firmware non compatibile</string>
+    <string name="fwinstaller_firmware_not_compatible_to_device">Questo firmware non 猫 compatibile con il dispositivo</string>
+    <string name="miband_prefs_reserve_alarm_calendar">Sveglie da riservare per i prossimi eventi del calendario</string>
+    <string name="miband_prefs_hr_sleep_detection">Utilizza il sensore del battito cardiaco per migliorare il riconoscimento del sonno</string>
+    <string name="miband_prefs_device_time_offset_hours">Sfasamento dell\'orario per il device (per consentire il rilevamento del sonno per chi lavora a turni)</string>
+    <string name="miband2_prefs_dateformat">Formato Data</string>
+    <string name="dateformat_time">Ora</string>
+    <string name="dateformat_date_time"><![CDATA[Ora e data]]></string>
+    <string name="mi2_prefs_goal_notification">Notifica raggiungimento obbiettivi</string>
+    <string name="mi2_prefs_goal_notification_summary">La band vibrer脿 una volta raggiunto l\'obbiettivo passi giornalieri</string>
+    <string name="mi2_prefs_display_items">Elementi mostrati</string>
+    <string name="mi2_prefs_display_items_summary">Scegli gli elementi da mostrare sullo schermo della band</string>
+    <string name="mi2_prefs_activate_display_on_lift">Attiva il display quando il dispositivo viene sollevato</string>
+    <string name="mi2_prefs_rotate_wrist_to_switch_info">Rotazione del polso per passare da una vista all\'altra</string>
+    <string name="mi2_prefs_do_not_disturb">Non disturbare</string>
+    <string name="mi2_prefs_do_not_disturb_summary">La band non ricever脿 notifiche quando 猫 attiva</string>
+    <string name="mi2_prefs_inactivity_warnings">Allarmi inattivit脿</string>
+    <string name="mi2_prefs_inactivity_warnings_summary">La band vibrer脿 dopo un determinato periodo di inattivit脿</string>
+    <string name="mi2_prefs_inactivity_warnings_threshold">Limite inattivit脿 (in minuti)</string>
+    <string name="mi2_prefs_inactivity_warnings_dnd_summary">Disabilita allarmi inattivit脿 per un intervallo di tempo</string>
+    <string name="mi2_prefs_do_not_disturb_start">Ora inizio</string>
+    <string name="mi2_prefs_do_not_disturb_end">Ora fine</string>
+    <string name="FetchActivityOperation_about_to_transfer_since">Vengono trasferiti dati a partire dal %1$s</string>
+    <string name="waiting_for_reconnect">in attesa di riconnessione</string>
+    <string name="activity_prefs_about_you">Informazioni sull\'utilizzatore</string>
+    <string name="activity_prefs_year_birth">Anno di nascita</string>
+    <string name="activity_prefs_gender">Sesso</string>
+    <string name="activity_prefs_height_cm">Altezza in cm</string>
+    <string name="activity_prefs_weight_kg">Peso in kg</string>
+    <string name="authenticating">In autenticazione</string>
+    <string name="authentication_required">Autenticazione necessaria</string>
+    <string name="appwidget_text">Zzz</string>
+    <string name="add_widget">Aggiungi widget</string>
+    <string name="activity_prefs_sleep_duration">Ore di sonno preferite</string>
+    <string name="appwidget_setting_alarm">Impostata sveglia per %1$02d:%2$02d</string>
+    <string name="device_hw">Versione Hardware: %1$s</string>
+    <string name="device_fw">Versione Firmware: %1$s</string>
+    <string name="error_creating_directory_for_logfiles">Errore durante la creazione della directory per i file di log: %1$s</string>
+    <string name="DEVINFO_HR_VER">"Battito Cardiaco: "</string>
+    <string name="updatefirmwareoperation_update_in_progress">Aggiornamento firmware in corso</string>
+    <string name="updatefirmwareoperation_firmware_not_sent">Firmware non inviato</string>
+    <string name="charts_legend_heartrate">Battito cardiaco</string>
+    <string name="live_activity_heart_rate">Battito cardiaco</string>
+    <string name="pref_title_pebble_health_store_raw">Memorizza i record grezzi nel database</string>
+    <string name="pref_summary_pebble_health_store_raw">Salva i dati \"as is\", aumentando l\'utilizzo del database in modo da permettere interrogazioni successive.</string>
+    <string name="action_db_management">Gestione dei dati</string>
+    <string name="title_activity_db_management">Gestione dei dati</string>
+    <string name="activity_db_management_import_export_explanation">Le operazioni di importazione/esportazione utilizzano questo percorso (vedi sotto) a una cartella sul tuo dispositivo. Questa cartella 猫 accessibile da altre applicazioni Android e dal tuo computer. Nota che questa cartella e tutti i file in essa contenuti vengono eliminati quando disinstalli Gadgetbridge. Tra i dati sono inclusi:
+\nExport_preference - impostazioni globali
+\nExport_preference_MAC - impostazioni specifiche del dispositivo
+\nGadgetbridge - database del dispositivo e delle attivit脿
+\nGadgetbridge_data - database esportato in una data particolare
+\n*.gpx - registrazioni del GPS
+\n*.log - file di log
+\nLa posizione dei file esportati (o quella da cui vuoi importare i file) 猫 la seguente:</string>
+    <string name="activity_db_management_merge_old_title">Elimina database nel vecchio formato</string>
+    <string name="dbmanagementactivvity_cannot_access_export_path">Impossibile accedere al path esterno. Per cortesia contatta gli sviluppatori del progetto.</string>
+    <string name="dbmanagementactivity_exported_to">Esportato su: %1$s</string>
+    <string name="dbmanagementactivity_error_exporting_db">Errore esportando il DB: %1$s</string>
+    <string name="dbmanagementactivity_error_exporting_shared">"Errore nell\'esportazione delle preferenze: %1$s"</string>
+    <string name="dbmanagementactivity_import_data_title">Importare i dati?</string>
+    <string name="dbmanagementactivity_overwrite_database_confirmation">Vuoi davvero sovrascrivere i dati attuali? Tutti i dati delle tue attivit脿 (se presenti), dei dispositivi e delle preferenze saranno sovrascritti.</string>
+    <string name="dbmanagementactivity_import_successful">Importazione avvenuta con successo.</string>
+    <string name="dbmanagementactivity_error_importing_db">Errore importando il DB: %1$s</string>
+    <string name="dbmanagementactivity_error_importing_shared">"Errore nell\'importazione della preferenza: %1$s"</string>
+    <string name="dbmanagementactivity_delete_activity_data_title">Cancella i dati delle attivit脿?</string>
+    <string name="dbmanagementactivity_really_delete_entire_db">Vuoi davvero cancellare il database? Tutte le informazioni sulle tue attivit脿 e sui tuoi dispositivi saranno perse.</string>
+    <string name="dbmanagementactivity_database_successfully_deleted">Dati cancellati con successo.</string>
+    <string name="dbmanagementactivity_db_deletion_failed">Cancellazione del database fallita.</string>
+    <string name="dbmanagementactivity_delete_old_activity_db">Cancella i dati delle attivit脿 nel vecchio formato?</string>
+    <string name="dbmanagementactivity_delete_old_activitydb_confirmation">Vuoi davvero cancellare il database nel vecchio formato? Tutte le informazioni che non hai importato nel nuovo database saranno perse.</string>
+    <string name="dbmanagementactivity_old_activity_db_successfully_deleted">Dati delle vechie attivit脿 eliminati con successo.</string>
+    <string name="dbmanagementactivity_old_activity_db_deletion_failed">Cancellazione del database nel vecchio formato fallita.</string>
+    <string name="dbmanagementactivity_overwrite">Sovrascrivi</string>
+    <string name="Cancel">Annulla</string>
+    <string name="Delete">Cancella</string>
+    <!--Strings related to Vibration Activity-->
+    <string name="title_activity_vibration">Vibrazione</string>
+    <!--Strings related to Pebble Pairing Activity-->
+    <string name="title_activity_pebble_pairing">Pairing del Pebble</string>
+    <string name="pebble_pairing_hint">Un avviso di accoppiamento dovrebbe comparire sul tuo dispositivo Android. Se non succedesse controlla l\'area delle notifiche ed accetta la richiesta. Dopo fai lo stesso sul tuo Pebble.</string>
+    <string name="weather_notification_label">Assicurati che questa skin sia abilitata nell\'applicazione \"Notifiche Meteo\" per ricevere informazioni meteo sul tuo Pebble.\n\nNon 猫 necessaria nessuna ulteriore configurazione.\n\nPuoi abilitare l\'applicazione Meteo di sistema sul tuo Pebble dalla gestione applicazioni.\n\nLe watchfaces che lo supportano mostreranno il meteo automaticamente.</string>
+    <string name="pref_title_setup_bt_pairing">Abilita il pairing bluetooth</string>
+    <string name="pref_summary_setup_bt_pairing">Disattiva se hai problemi di connession</string>
+    <string name="unit_metric">Metrico</string>
+    <string name="unit_imperial">Imperiale</string>
+    <string name="timeformat_24h">24H</string>
+    <string name="timeformat_am_pm">AM/PM</string>
+    <string name="pref_screen_notification_profile_alarm_clock">Sveglia</string>
+    <string name="find_device_you_found_it">Trovato!</string>
+    <string name="find_lost_device_you_found_it">Trovato!</string>
+    <string name="miband2_prefs_timeformat">Mi2: Formato dell\'orario</string>
+    <string name="mi2_fw_installhandler_fw53_hint">脠 necessario installare la verione %1$s prima di installare questo firmware!</string>
+    <string name="mi2_enable_text_notifications">Notifiche</string>
+    <string name="mi2_enable_text_notifications_summary"><![CDATA[Richiede versione firmware >= 1.0.1.28 e Mili_pro.ft* installati.]]></string>
+    <string name="off">spento</string>
+    <string name="mi2_dnd_off">Spento</string>
+    <string name="mi2_dnd_automatic">Automatico (rilevamento del sonno)</string>
+    <string name="discovery_attempting_to_pair">Tentativo di connessione a %1$s in corso</string>
+    <string name="discovery_bonding_failed_immediately">La connessione con %1$s 猫 subito fallita.</string>
+    <string name="discovery_trying_to_connect_to">Tentativo di connessione con: %1$s</string>
+    <string name="discovery_enable_bluetooth">Abilitare Bluetooth per l\'individuazione dei dispositivi.</string>
+    <string name="discovery_dont_pair">Non connettersi</string>
+    <string name="controlcenter_connect">Connetti鈥�</string>
+    <string name="fw_upgrade_notice_amazfitbip">Stai per installare il firmware %s sul tuo Amazfit Bip.
+\n
+\nAssicurati di installare prima il file .fw e dopo il file .res. L\'orologio si riavvier脿 al termine del caricamento del file .fw.
+\n
+\nNota: non 猫 necessario installare il file .res se 猫 uguale a quello installato in precedenza.
+\n
+\nPROCEDI A TUO RISCHIO E PERICOLO!</string>
+    <string name="fw_upgrade_notice_amazfitcor">Stai per installare il firmware %s sul Amazfit Cor.
+\n
+\nSi prega di installare prima il file .fw e successivamente il file .res. La tua band si riavvier脿 dopo l\'installazione del file .fw.
+\n
+\nNOTA: Non 猫 necessario installare il file .res se risulta essere uguale a quello precedentemente installato.
+\n
+\nPROCEDI A TUO RISCHIO E PERICOLO!</string>
+    <string name="pref_title_charts_swipe">Abilita swipe Sinistra/Destra nel grafico delle attivit脿</string>
+    <string name="pref_title_weather">Meteo</string>
+    <string name="pref_title_weather_location">Posizione meteo (per il provider meteo di LineageOS)</string>
+    <string name="pref_blacklist_calendars">Calendari ignorati</string>
+    <string name="pref_header_pebble_timeline">Timeline Pebble</string>
+    <string name="pref_summary_enable_calendar_sync">Invia gli eventi del calendario alla timeline</string>
+    <string name="pref_title_pebble_enable_bgjs">Abilita background JS</string>
+    <string name="pref_summary_pebble_enable_bgjs">Quando abilitato permette a watchface di visualizzare meteo, batteria, etc.</string>
+    <string name="pref_header_auto_export">Esporta automaticamente le preferenze</string>
+    <string name="pref_title_auto_export_enabled">Esportazione automatica abilitata</string>
+    <string name="pref_title_auto_export_location">Esporta posizione</string>
+    <string name="pref_title_auto_export_interval">Intervallo di esportazione</string>
+    <string name="pref_summary_auto_export_interval">Esporta ogni %d ora/e</string>
+    <string name="unknown_state">Stato sconosciuto</string>
+    <string name="prefs_title_heartrate_measurement_interval">Misurazione battito continua</string>
+    <string name="interval_one_minute">ogni minuto</string>
+    <string name="interval_five_minutes">ogni 5 minuti</string>
+    <string name="interval_ten_minutes">ogni 10 minuti</string>
+    <string name="interval_thirty_minutes">ogni 30 minuti</string>
+    <string name="interval_one_hour">ogni ora</string>
+    <string name="notif_export_failed_title">Esportazione del database fallita! Per favore controlla le impostazioni.</string>
+    <string name="mi2_prefs_button_actions">Azioni tasto</string>
+    <string name="mi2_prefs_button_actions_summary">Specifica le azioni alla pressione del pulsante</string>
+    <string name="mi2_prefs_button_press_count">Conteggio pressione tasto</string>
+    <string name="mi2_prefs_button_press_count_summary">Numero di volte che il tasto deve essere premuto per generare un Evento 1. Successivo medesimo numero di volte che il tasto deve essere premuto per un Evento 2 e cos矛 via.</string>
+    <string name="mi2_prefs_button_press_broadcast">Messaggio da inviare in broadcast</string>
+    <string name="mi2_prefs_button_press_broadcast_summary">Messaggio inviato con l\'evento. Il parametro `button_id` 猫 aggiunto automaticamente al ogni messaggio.</string>
+    <string name="mi2_prefs_button_action">Abilita azioni tasto</string>
+    <string name="mi2_prefs_button_action_summary">Abilita azioni selezionando un numero specifico di pressioni del tasto</string>
+    <string name="mi2_prefs_button_action_vibrate">Abilita vibrazione band</string>
+    <string name="mi2_prefs_button_action_vibrate_summary">Abilita vibrazioni band con pressione del tasto</string>
+    <string name="mi2_prefs_button_press_count_max_delay">Massimo ritardo tra due pressioni</string>
+    <string name="mi2_prefs_button_press_count_max_delay_summary">Massimo ritardo tra due pressioni del pulsante in millisecondi</string>
+    <string name="automatic">Automatico</string>
+    <string name="simplified_chinese">Cinese semplificato</string>
+    <string name="traditional_chinese">Cinese tradizionale</string>
+    <string name="english">Inglese</string>
+    <string name="spanish">Spagnolo</string>
+    <string name="activity_web_view">Web View Activity</string>
+    <string name="StringUtils_sender">(%1$s)</string>
+    <string name="mi2_dnd_scheduled">Schedulato (intervallo di tempo)</string>
+    <string name="discovery_successfully_bonded">Connesso a %1$s.</string>
+    <string name="discovery_pair_title">Accoppiare con %1$s?</string>
+    <string name="discovery_pair_question">Seleziona \'Accoppia\' per accoppiare i tuoi dispositivi. Se dovesse fallire riprova di nuova senza effettuare l\'accoppiamento.</string>
+    <string name="discovery_yes_pair">Accoppia</string>
+    <string name="_pebble_watch_open_on_phone">Apri nel dispositivo Android</string>
+    <string name="_pebble_watch_mute">Muto</string>
+    <string name="_pebble_watch_reply">Rispondi</string>
+    <string name="kind_firmware">Firmware</string>
+    <string name="kind_invalid">Data non valida</string>
+    <string name="kind_font">Carattere</string>
+    <string name="kind_gps">Firmware GPS</string>
+    <string name="kind_gps_almanac">Almanacco GPS</string>
+    <string name="kind_gps_cep">Errore Connessione GPS</string>
+    <string name="kind_resources">Risorse</string>
+    <string name="kind_watchface">Watchface</string>
+    <string name="devicetype_unknown">Device sconosciuto</string>
+    <string name="devicetype_test">Testa il Device</string>
+    <string name="devicetype_pebble">Pebble</string>
+    <string name="devicetype_miband">Mi Band</string>
+    <string name="devicetype_miband2">Mi Band 2</string>
+    <string name="devicetype_amazfit_bip">Amazfit Bip</string>
+    <string name="devicetype_amazfit_cor">Amazfit Cor</string>
+    <string name="devicetype_vibratissimo">Vibratissimo</string>
+    <string name="devicetype_liveview">LiveView</string>
+    <string name="devicetype_hplus">HPlus</string>
+    <string name="devicetype_makibes_f68">Makibes F68</string>
+    <string name="devicetype_exrizu_k8">Exrizu K8</string>
+    <string name="devicetype_no1_f1">No.1 F1</string>
+    <string name="devicetype_teclast_h30">Teclast H30</string>
+    <string name="choose_auto_export_location">Seleziona percorso di esportazione</string>
+    <string name="notification_channel_name">Generale</string>
+    <string name="devicetype_xwatch">XWatch</string>
+    <string name="on">Acceso</string>
+    <string name="blacklist_all_for_notifications">Blocca tutte le notifiche</string>
+    <string name="whitelist_all_for_notifications">Consenti le notifiche da tutte le applicazioni</string>
+    <string name="fw_upgrade_notice_miband3">Stai per installare il firmware %s sul Mi Band 3.
+\n
+\nSi prega di installare prima il file .fw e successivamente il file .res. La Miband3 si riavvier脿 dopo l\'installazione del file .fw.
+\n
+\nNOTA: Non 猫 necessario installare il file .res se risulta essere uguale a quello precedentemente installato.
+\n
+\nPROCEDI A TUO RISCHIO E PERICOLO!</string>
+    <string name="pref_title_pebble_gatt_clientonly">Modalit脿 GATT client</string>
+    <string name="pref_summary_pebble_gatt_clientonly">Sperimentale, compatibile solo con Pebble 2, attivare solo in caso di problemi di connettivit脿</string>
+    <string name="pref_auto_fetch">Recupero automatico dati delle attivit脿</string>
+    <string name="pref_auto_fetch_summary">Sincronizza i dati quando lo schermo viene sbloccato. Funziona solo se 猫 attivata una modalit脿 di blocco!</string>
+    <string name="controlcenter_start_activity_tracks">Traccia delle tua attivit脿</string>
+    <string name="activity_type_not_measured">Non misurato</string>
+    <string name="activity_type_activity">Attivit脿</string>
+    <string name="activity_type_light_sleep">Sonno leggero</string>
+    <string name="activity_type_deep_sleep">Sonno profondo</string>
+    <string name="activity_type_not_worn">Dispositivo non indossato</string>
+    <string name="activity_type_running">Corsa</string>
+    <string name="activity_type_walking">Camminata</string>
+    <string name="activity_type_swimming">Nuoto</string>
+    <string name="activity_type_unknown">Attivit脿 sconosciuta</string>
+    <string name="activity_summaries">Attivit脿 Sportive</string>
+    <string name="activity_type_biking">Bicicletta</string>
+    <string name="activity_type_treadmill">Tapis roulant</string>
+    <string name="select_all">Seleziona tutto</string>
+    <string name="share">Condividi</string>
+    <string name="reset_index">Reset data di recupero</string>
+    <string name="devicetype_miband3">Mi Band 3</string>
+    <string name="devicetype_q8">Q8</string>
+    <string name="menuitem_status">Stato</string>
+    <string name="menuitem_activity">Storico Allenamenti</string>
+    <string name="menuitem_weather">Meteo</string>
+    <string name="menuitem_alarm">Allarmi</string>
+    <string name="menuitem_timer">Conto alla rovescia</string>
+    <string name="menuitem_compass">Bussola</string>
+    <string name="menuitem_settings">Impostazioni</string>
+    <string name="menuitem_alipay">Alipay</string>
+    <string name="controlcenter_change_led_color">Cambia colore LED</string>
+    <string name="controlcenter_change_fm_frequency">Cambia frequenza FM</string>
+    <string name="controlcenter_calibrate_device">Calibra dispositivo</string>
+    <string name="pref_title_notifications_timeout">Tempo minimo tra le notifiche</string>
+    <string name="pref_title_rtl">Da destra a sinistra</string>
+    <string name="pref_summary_rtl">Abilita questo se il tuo dispositivo non mostra le lingue lette da destra a sinistra</string>
+    <string name="pref_rtl_max_line_length">Lunghezza massima delle righe destra-sinistra</string>
+    <string name="pref_rtl_max_line_length_summary">Aumenta o diminuisci il numero di righe con cui separare il testo da destra a sinistra</string>
+    <string name="prefs_screen_orientation">Orientamento dello schermo</string>
+    <string name="pref_auto_fetch_limit_fetches">Tempo minimo tra le sincronizzazioni</string>
+    <string name="horizontal">Orizzontale</string>
+    <string name="vertical">Verticale</string>
+    <string name="watch9_pairing_tap_hint">Quando il tuo orologio vibra, scuotilo o premi il tasto.</string>
+    <string name="notif_battery_low">%1$s batteria scarica</string>
+    <string name="pref_auto_fetch_limit_fetches_summary">Sincronizza ogni %d minuti</string>
+    <string name="no_limit">Nessun limite</string>
+    <string name="seconds_5">5 secondi</string>
+    <string name="seconds_10">10 secondi</string>
+    <string name="seconds_20">20 secondi</string>
+    <string name="seconds_30">30 secondi</string>
+    <string name="minutes_1">1 minuto</string>
+    <string name="minutes_5">5 minuti</string>
+    <string name="minutes_10">10 minuti</string>
+    <string name="minutes_30">30 minuti</string>
+    <string name="you_slept">%1$s - %2$s</string>
+    <string name="you_did_not_sleep">Non hai dormito</string>
+    <string name="mi3_prefs_band_screen_unlock_summary">Scorri verso l\'alto per sbloccare lo schermo della band</string>
+    <string name="mi3_prefs_night_mode">Modalit脿 notturna</string>
+    <string name="norwegian_bokmal">Norvegese Bokm氓l</string>
+    <string name="russian">Russo</string>
+    <string name="german">Tedesco</string>
+    <string name="italian">Italiano</string>
+    <string name="french">Francese</string>
+    <string name="polish">Polacco</string>
+    <string name="korean">Coreano</string>
+    <string name="japanese">Giapponese</string>
+    <string name="activity_prefs_chart_max_heart_rate">Frequenza cardiaca massima</string>
+    <string name="activity_prefs_chart_min_heart_rate">Frequenza cardiaca minima</string>
+    <string name="ok">OK</string>
+    <string name="mi3_night_mode_sunset">Al tramonto</string>
+    <string name="devicetype_mykronoz_zetime">MyKronoz ZeTime</string>
+    <string name="devicetype_id115">ID115</string>
+    <string name="devicetype_roidmi3">Roidmi 3</string>
+    <string name="menuitem_notifications">Notifiche</string>
+    <string name="menuitem_music">Musica</string>
+    <string name="watch9_time_minutes">Minuti:</string>
+    <string name="watch9_time_hours">Ore:</string>
+    <string name="watch9_time_seconds">Secondi:</string>
+    <string name="watch9_calibration_button">Calibra</string>
+    <string name="title_activity_watch9_pairing">accoppiamento Watch 9</string>
+    <string name="title_activity_watch9_calibration">calibrazione Watch 9</string>
+    <string name="preferences_rtl_settings">Supporto da destra a sinistra</string>
+    <string name="share_log">Condividi log</string>
+    <string name="notif_battery_low_extended">%1$s batteria scarica: %2$s</string>
+    <string name="lack_of_sleep">Debito di sonno: %1$s</string>
+    <string name="mi3_prefs_night_mode_summary">Riduci automaticamente la luminosit脿 dello schermo della band di notte</string>
+    <string name="activity_prefs_charts">Impostazioni grafici</string>
+    <string name="devicetype_watch9">Watch 9</string>
+    <string name="devicetype_roidmi">Roidmi</string>
+    <string name="menuitem_more">Altro</string>
+    <string name="pref_title_contextual_arabic">Arabo contestuale</string>
+    <string name="warning">Attenzione!</string>
+    <string name="no_data">Nessun dato</string>
+    <string name="preferences_led_color">Colore LED</string>
+    <string name="preferences_fm_frequency">Frequenze FM</string>
+    <string name="pref_invalid_frequency_title">Frequenza non valida</string>
+    <string name="pref_invalid_frequency_message">Per favore introdurre una frequenza compresa tra 87,5 e 108,0</string>
+    <string name="language_and_region_prefs">Impostazioni lingua e regione</string>
+    <string name="live_activity_max_heart_rate">Attuale / Massima frequenza cardiaca: %1$d / %2$d</string>
+    <string name="mi3_prefs_band_screen_unlock">Sblocca lo schermo della band</string>
+    <string name="watch9_calibration_hint">Imposta l\'orario che il tuo dispositivo ti sta mostrando adesso.</string>
+    <string name="pref_summary_contextual_arabic">Abilita questo per supportare l\'arabo contestuale</string>
+    <string name="share_log_warning">Tieni presente che i file di log di Gadgetbridge possono contenere molte informazioni personali, inclusi, a titolo esemplificativo, dati sulla salute, identificatori univoci (come l\'indirizzo MAC del dispositivo), preferenze musicali, ecc. Modifica il file per rimuovere queste informazioni prima di inviare il file in una segnalazione di errore visibile pubblicamente.</string>
+    <string name="overslept">Sonno extra: %1$s</string>
+    <string name="lack_of_step">Passi mancanti: %1$d</string>
+    <string name="overstep">Passi eccedenti: %1$d</string>
+    <string name="debugactivity_really_factoryreset_title">Vuoi veramente ripristinare le impostazioni di fabbrica\?</string>
+    <string name="debugactivity_really_factoryreset">Effettuare il ripristino delle impostazioni di fabbrica canceller脿 tutti i dati presenti sul dispositivo connesso (se supportato). I dispositivi Xiaomi/Huami cambiano anche MAC Address Bluetooth, e in modo risultano dispositivi nuovi per Gadgetbrige.</string>
+    <string name="devicetype_casiogb6900">Casio GB-6900</string>
+    <string name="title_activity_notification_filter">Filtro Notifiche</string>
+    <string name="edittext_notification_filter_words_hint">Inserisci le parole desiderate, una per linea</string>
+    <string name="toast_notification_filter_saved_successfully">Filtro Notifiche salvato</string>
+    <string name="filter_mode_none">Non filtrare</string>
+    <string name="filter_mode_whitelist">Mostra quando sono contenute le parole</string>
+    <string name="filter_mode_blacklist">Blocca quando sono contenute le parole</string>
+    <string name="filter_submode_at_least_one">Almeno una delle parole</string>
+    <string name="filter_submode_all">Tutte le parole</string>
+    <string name="toast_notification_filter_words_empty_hint">Per favore, inserisci almeno una parola</string>
+    <string name="filter_mode">Modalit脿 filtro</string>
+    <string name="mode_configuration">Modalit脿 Configurazione</string>
+    <string name="save_configuration">Salva Configurazione</string>
+    <string name="appwidget_not_connected">Non connesso, sveglia non impostata.</string>
+    <string name="activity_type_exercise">Esercizio</string>
+    <string name="zetime_title_heartrate">Impostazioni Battito cardiaco</string>
+    <string name="zetime_title_screentime">Durata schermo acceso in secondi</string>
+    <string name="zetime_title_heart_rate_alarm_summary">L\'orologio ti avviser脿 quando la frequenza del tuo battito cardiaco superer脿 il limite.</string>
+    <string name="activity_prefs_alarm_max_heart_rate">Frequenza cardiaca massima</string>
+    <string name="activity_prefs_alarm_min_heart_rate">Frequenza cardiaca minima</string>
+    <string name="zetime_handmove_display_summary">Ruota il polso per attivare o disattivare il display.</string>
+    <string name="zetime_date_format">Formato data</string>
+    <string name="zetime_date_format_1">AA/MM/GG</string>
+    <string name="zetime_date_format_2">GG/MM/AA</string>
+    <string name="zetime_date_format_3">MM/GG/AA</string>
+    <string name="zetime_prefs_inactivity_mo">Luned矛</string>
+    <string name="zetime_prefs_inactivity_tu">Marted矛</string>
+    <string name="zetime_prefs_inactivity_we">Mercoled矛</string>
+    <string name="zetime_prefs_inactivity_th">Gioved矛</string>
+    <string name="zetime_prefs_inactivity_fr">Venerd矛</string>
+    <string name="zetime_prefs_inactivity_sa">Sabato</string>
+    <string name="zetime_prefs_inactivity_su">Domenica</string>
+    <string name="pref_title_support_voip_calls">Abilita chiamate VoIP</string>
+    <string name="zetime_title_settings">Impostazioni ZeTime</string>
+    <string name="zetime_title_heart_rate_alarm">Avviso frequenza cardiaca</string>
+    <string name="zetime_heart_rate_alarm_enable">Abilita l\'avviso per la frequenza cardiaca</string>
+    <string name="zetime_analog_mode">Analogico</string>
+    <string name="zetime_analog_mode_hands">Solo mani</string>
+    <string name="zetime_analog_mode_handsandsteps">Mani e passi</string>
+    <string name="zetime_activity_tracking">Registrazione attivit脿</string>
+    <string name="zetime_activity_tracking_summary">Attivando la registrazione dell\'attivit脿, verranno contati i passi e altre misure.</string>
+    <string name="zetime_handmove_display">Movimento della mano</string>
+    <string name="zetime_calories_type">Tipo calorie</string>
+    <string name="zetime_calories_type_active">Solo calorie bruciate attivamente</string>
+    <string name="zetime_calories_type_all">Calorie bruciate attivamente e passivamente</string>
+    <string name="zetime_prefs_inactivity_repetitions">Ripetizioni</string>
+    <string name="zetime_title_alarm_signaling">Imposta il tipo di segnale per l\'allarme</string>
+    <string name="zetime_signaling_none">Silenzioso</string>
+    <string name="zetime_signaling_vibrate">Vibrazione continua</string>
+    <string name="zetime_signaling_beep">Beep continuo</string>
+    <string name="zetime_signaling_vibrate_beep">Beep e vibrazione continui</string>
+    <string name="zetime_signaling_vibrate_once">VIbra una volta</string>
+    <string name="zetime_signaling_vibrate_twice">VIbra due volte</string>
+    <string name="zetime_signaling_beep_once">Beep singolo</string>
+    <string name="zetime_signaling_beep_twice">Beep doppio</string>
+    <string name="zetime_signaling_vibrate_beep_once">Vibrazione e beep singoli</string>
+    <string name="title_activity_device_specific_settings">Impostazioni specifiche del dispositivo</string>
+    <string name="pref_title_authkey">Chiave di autenticazione</string>
+    <string name="pref_summary_authkey">Cambia la chiave di autenticazione con una chiave comune a tutti i tuoi dispositivi Android dai quali vuoi connetterti. La chiave predefinita precedente per tutti i dispositivi 猫 0123456789@ABCDE</string>
+    <string name="pref_screen_notification_profile_missed_call">Notifica chiamata persa</string>
+    <string name="pref_screen_notification_profile_calendar">Notifica calendario</string>
+    <string name="pref_screen_notification_profile_inactivity">Notifica inattivit脿</string>
+    <string name="pref_screen_notification_profile_low_power">Avviso batteria scarica</string>
+    <string name="pref_screen_notification_profile_anti_loss">Avviso anti-perdita</string>
+    <string name="interval_fifteen_minutes">ogni 15 minuti</string>
+    <string name="interval_forty_five_minutes">ogni 45 minuti</string>
+    <string name="prefs_disconnect_notification">Notifica disconnessione</string>
+    <string name="activity_prefs_calories_burnt">Obiettivo giornaliero: calorie bruciate</string>
+    <string name="activity_prefs_distance_meters">Obiettivo giornaliero: distanza percorsa in metri</string>
+    <string name="activity_prefs_activetime_minutes">Obiettivo giornaliero: tempo di attivit脿 in minuti</string>
+    <string name="devicetype_miscale2">Mi Scale 2</string>
+    <string name="devicetype_bfh16">BFH-16</string>
+    <string name="fw_upgrade_notice_amazfitcor2">Stai per installare il firmware %s sul tuo Amazfit Cor 2. 
+\n 
+\nAssicurati di installare il file .fw e successivamente il file .res. La band si riavvier脿 dopo aver installato il file .fw. 
+\n 
+\nNota: non 猫 necessario installare .res se 猫 esattamente lo stesso di quello installato in precedenza. 
+\n 
+\nPROCEDI A TUO RISCHIO! 
+\n 
+\nCOMPLETAMENTE NON TESTATO, PROBABILMENTE DOVRAI FLASHARE IL FIRMWARE BEATS_W SE IL NOME DEL TUO DISPOSITIVO 猫 \"Amazfit Band 2\"</string>
+    <string name="dutch">Olandese</string>
+    <string name="turkish">Turco</string>
+    <string name="ukrainian">Ucraino</string>
+    <string name="arabic">Arabo</string>
+    <string name="indonesian">Indonesiano</string>
+    <string name="thai">Tailandese</string>
+    <string name="vietnamese">Vietnamita</string>
+    <string name="portuguese">Portoghese</string>
+    <string name="devicetype_amazfit_cor2">Amazfit Cor 2</string>
+    <string name="devicetype_miband4">Mi Band 4</string>
+    <string name="fw_upgrade_notice_miband4">Stai per installare il firmware %s sul Mi Band 4.
+\n
+\nSi prega di installare prima il file .fw e successivamente il file .res. La Mi Band 4 si riavvier脿 dopo l\'installazione del file .fw.
+\n
+\nNOTA: Non 猫 necessario installare il file .res se risulta essere uguale a quello precedentemente installato.
+\n
+\nPROCEDI A TUO RISCHIO E PERICOLO!</string>
+    <string name="prefs_hr_alarm_activity">Imposta allarme frequenza cardiaca durante l\'attivit脿 sportiva</string>
+    <string name="prefs_hr_alarm_low">Limite inferiore</string>
+    <string name="prefs_hr_alarm_high">Limite Superiore</string>
+    <string name="mi2_prefs_button_press_broadcast_default_value" translatable="false">nodomain.freeyourgadget.gadgetbridge.ButtonPressed</string>
+    <string name="pref_header_charts">Impostazioni dei grafici</string>
+    <string name="pref_title_charts_average">Mostra le medie nei grafici</string>
+    <string name="pref_title_charts_range">Intervallo dei grafici</string>
+    <string name="pref_charts_range_on">L\'intervallo dei grafici 猫 impostato su di un mese</string>
+    <string name="pref_charts_range_off">L\'intervallo dei grafici 猫 impostato su di una settimana</string>
+    <string name="weekstepschart_steps_a_month">Passi al mese</string>
+    <string name="weeksleepchart_sleep_a_month">Sonno per mese</string>
+    <string name="menuitem_nfc">NFC</string>
+    <string name="pref_summary_expose_hr">Consente ad altre applicazioni di accedere ai dati cardiaci in tempo reale mentre Gadgetbridge 猫 collegato</string>
+    <string name="pref_title_expose_hr">Accesso ai dati cardiaci in tempo reale a terze parti</string>
+    <string name="pref_title_use_custom_font">Utilizza un font personalizzato</string>
+    <string name="pref_summary_use_custom_font">Attiva se il tuo dispositivo ha un firmware personalizzato che supporta gli emoji</string>
+    <string name="activity_db_management_autoexport_explanation">Il percorso di esportazione automatica del database 猫 stato impostato su:</string>
+    <string name="activity_db_management_autoexport_label">Esportazione automatica</string>
+    <string name="activity_DB_ExportButton">Esporta dati</string>
+    <string name="activity_DB_import_button">Importa dati</string>
+    <string name="activity_DB_test_export_button">Esegui esportazione automatica ora</string>
+    <string name="activity_DB_test_export_message">Esportazione database in corso鈥�</string>
+    <string name="activity_DB_delete_legacy_button">Elimina db precedente</string>
+    <string name="activity_DB_empty_button">Database vuoto</string>
+    <string name="activity_db_management_empty_DB">Database vuoto</string>
+    <string name="activity_db_management_exportimport_label">Esportazione e importazione</string>
+    <string name="activity_db_management_empty_db_warning">Attenzione! Premendo questo pulsante si cancella il database e si riparte da zero.</string>
+    <string name="widget_set_alarm_after">Imposta sveglia dopo:</string>
+    <string name="widget_5_minutes">5 minuti</string>
+    <string name="widget_10_minutes">10 minuti</string>
+    <string name="widget_20_minutes">20 minuti</string>
+    <string name="widget_1_hour">1 ora</string>
+    <string name="pref_display_add_device_fab_on">Sempre visibile</string>
+    <string name="pref_display_add_device_fab_off">Visibile solo se non viene aggiunto alcun dispositivo</string>
+    <string name="activity_error_no_app_for_gpx">Per visualizzare la traccia delle attivit脿, installare un\'app in grado di gestire i file GPX.</string>
+    <string name="preferences_makibes_hr3_settings">Impostazioni Makibes HR3</string>
+    <string name="devicetype_makibes_hr3">Makibes HR3</string>
+    <string name="devicetype_amazfit_bip_lite">Amazfit Bip Lite</string>
+    <string name="prefs_find_phone">Trova telefono</string>
+    <string name="prefs_enable_find_phone">Abilita \'Trova telefono\'</string>
+    <string name="prefs_find_phone_summary">Usa la tua band per riprodurre la suoneria del tuo telefono.</string>
+    <string name="prefs_find_phone_duration">Durata dello squillo in secondi</string>
+    <string name="maximum_duration">Durata</string>
+    <string name="discovery_need_to_enter_authkey">Questo dispositivo ha bisogno di una chiave di autenticazione segreta, premi a lungo sul dispositivo per inserirla. Leggi la wiki.</string>
+    <string name="devicetype_amazfit_gtr">Amazfit GTR</string>
+    <string name="pref_chart_heartrate_color_red">Rosso</string>
+    <string name="pref_chart_heartrate_color_orange">Arancione</string>
+    <string name="pref_title_chart_heartrate_color">Colore della frequenza cardiaca</string>
+    <string name="pref_title_chart_sleep_rolling_24_hour">Intervallo di sonno</string>
+    <string name="pref_chart_sleep_rolling_24_on">Ultime 24 ore</string>
+    <string name="pref_chart_sleep_rolling_24_off">Da mezzogiorno a mezzogiorno</string>
+    <string name="devicetype_amazfit_gts">Amazfit GTS</string>
+    <string name="devicetype_qhybrid">Fossil Q Hybrid</string>
+    <string name="watch_not_connected">Orologio non connesso</string>
+    <string name="qhybrid_vibration_strength">forza della vibrazione:</string>
+    <string name="qhybrid_goal_in_steps">Obiettivo in passi</string>
+    <string name="qhybrid_changes_delay_prompt">Il cambiamento potrebbe richiedere qualche secondo鈥�</string>
+    <string name="pref_disable_new_ble_scanning">Disattivare nuove scansioni BLE</string>
+    <string name="pref_summary_disable_new_ble_scanning">Selezionare questa opzione se il dispositivo viene trovato durante il rilevamento</string>
+    <string name="devicetype_banglejs">Bangle.js</string>
+    <string name="devicetype_y5">Y5</string>
+    <string name="prefs_button_single_press_action_selection_title">Evento 1 azione</string>
+    <string name="prefs_button_double_press_action_selection_title">Evento 2 azione</string>
+    <string name="prefs_button_triple_press_action_selection_title">Evento 3 azione</string>
+    <string name="prefs_button_long_press_action_selection_title">Azione di pressione prolungata del pulsante</string>
+    <string name="alarm_snooze">Snooze</string>
+    <string name="error_no_location_access">Per il corretto funzionamento della scansione dei dispositivi occorre concedere e abilitare l\'accesso alla posizione</string>
+    <string name="devicetype_itag">iTag</string>
+    <string name="pref_title_allow_high_mtu">Consenti MTU elevato</string>
+    <string name="pref_summary_allow_high_mtu">Aumenta la velocit脿 di trasferimento, ma potrebbe non funzionare su alcuni dispositivi Android.</string>
+    <string name="pref_title_sync_caldendar">Sincronizza gli eventi del calendario</string>
+    <string name="average">Media: %1$s</string>
+    <string name="devicetype_mijia_lywsd02">Mijia Smart Clock</string>
+    <string name="appwidget_sleep_alarm_widget_label">Sveglia</string>
+    <string name="widget_listing_label">Stato e Sveglie</string>
+    <string name="pref_display_add_device_fab">Pulsante per connettere un nuovo dispositivo</string>
+    <string name="fw_upgrade_notice_amazfitbip_lite">Stai per installare il firmware %s sul tuo Amazfit Bip Lite.
+\n
+\nAssicurati di installare prima il file .fw e dopo il file .res. L\'orologio si riavvier脿 al termine del caricamento del file .fw.
+\n
+\nNota: non 猫 necessario installare il file .res se 猫 uguale a quello installato in precedenza.
+\n
+\nPROCEDI A TUO RISCHIO E PERICOLO!</string>
+    <string name="fw_upgrade_notice_amazfitgtr">Stai per installare il firmware %s sul tuo Amazfit GTR.
+\n
+\nAssicurati di installare il file .fw, quindi il file .res e infine il file .gps. L\'orologio si riavvier脿 dopo aver installato il file .fw.
+\n
+\nNota: non 猫 necessario installare .res e .gps se questi file sono esattamente gli stessi installati in precedenza.
+\n
+\nPROCEDI A TUO RISCHIO!</string>
+    <string name="fw_upgrade_notice_amazfitgts">Stai per installare il firmware %s sul tuo Amazfit GTS.
+\n
+\nAssicurati di installare il file .fw, quindi il file .res e infine il file .gps. L\'orologio si riavvier脿 dopo aver installato il file .fw.
+\n
+\nNota: non 猫 necessario installare .res e .gps se questi file sono esattamente gli stessi installati in precedenza.
+\n
+\nPROCEDI A TUO RISCHIO!</string>
+    <string name="preferences_qhybrid_settings">Impostazioni Q Hybrid</string>
+    <string name="qhybrid_prompt_million_steps">Impostare il contatore dei passi a un milione per attivare.</string>
+    <string name="qhybrid_buttons_overwrite_success">Pulsanti sovrascritti</string>
+    <string name="qhybrid_buttons_overwrite_error">Errore durante la modifica dei pulsanti</string>
+    <string name="prefs_button_variable_actions">Impostazioni dettagliate per la pressione del pulsante</string>
+    <string name="pref_summary_sync_calendar">Abilita gli avvisi del calendario, anche in caso di disconnessione</string>
+    <string name="hr_widget_heart_rate">Frequenza cardiaca</string>
+    <string name="hr_widget_steps">Passi</string>
+    <string name="hr_widget_date">Data</string>
+    <string name="hr_widget_active_minutes">Minuti attivi</string>
+    <string name="hr_widget_calories">Calorie</string>
+    <string name="hr_widget_battery">Batteria</string>
+    <string name="hr_widget_weather">Meteo</string>
+    <string name="hr_widget_nothing">Nulla</string>
+    <string name="pref_title_force_white_color_scheme">Forza la combinazione di colori nero su bianco</string>
+    <string name="pref_summary_force_white_color_scheme">Utile se il vostro orologio ha le lancette scure</string>
+    <plurals name="widget_alarm_target_hours">
+        <item quantity="one">%d ora</item>
+        <item quantity="many">%d ore</item>
+        <item quantity="other">%d ore</item>
+    </plurals>
+    <string name="qhybrid_time_shift">scostamento orario</string>
+    <string name="qhybrid_second_timezone_offset_relative_to_utc">compensazione del secondo fuso orario rispetto a UTC</string>
+    <string name="qhybrid_offset_timezone">compensazione del fuso orario di</string>
+    <string name="qhybrid_offset_time_by">compensazione dell\'ora di</string>
+    <string name="hr_appname_commute">Commuta</string>
+    <string name="hr_appname_stopwatch">Cronometro</string>
+    <string name="hr_appname_workout">Allenamento</string>
+    <string name="hr_appname_wellness">Benessere</string>
+    <string name="qhybrid_use_activity_hand_as_notification_counter">utilizzare la mano di attivit脿 come contatore di notifica</string>
+    <string name="qhybrid_overwrite_buttons">sovrascrivere i pulsanti</string>
+    <string name="notification_channel_high_priority_name">Alta priorit脿</string>
+    <string name="find_my_phone_notification">Trova il mio telefono</string>
+    <string name="pref_title_vibration_strength">Resistenza alle vibrazioni</string>
+    <string name="pref_title_relax_firmware_checks">Attiva questo se vuoi flashare un firmware non destinato al tuo dispositivo (a proprio rischio e pericolo)</string>
+    <string name="pref_summary_custom_deviceicon">Mostra l\'icona di notifica di un dispositivo specifico per Android invece l\'icona di Gadgetbridge quando 猫 connesso</string>
+    <string name="pref_title_custom_deviceicon">Mostra l\'icona di notifica specifica del dispositivo</string>
+    <string name="menuitem_workout">Allenamento</string>
+    <string name="menuitem_hr">Frequenza cardiaca</string>
+    <string name="menuitem_pai">PAI</string>
+    <string name="devicetype_watchxplus">Watch X Plus</string>
+    <string name="devicetype_watchx">Watch X</string>
+    <string name="devicetype_amazfit_bips">Amazfit Bip S</string>
+    <string name="czesh">Ceco</string>
+    <string name="swedish">Svedese</string>
+    <string name="hebrew">Ebraico</string>
+    <string name="greek">Greco</string>
+    <string name="hungarian">Ungherese</string>
+    <string name="romanian">Rumeno</string>
+    <string name="power_mode_normal">Normale</string>
+    <string name="pref_sensors_bp_calibration_high">Pressione sanguigna SISTOLICA (alta)</string>
+    <string name="pref_sensors_bp_calibration_low">Pressione sanguigna DIASTOLICA (bassa)</string>
+    <string name="pref_header_device_spec_settings">Impostazioni dell\'apparecchio</string>
+    <string name="prefs_notifications_and_calls_reject">Pulsante ignora/rifiuta la chiamata</string>
+    <string name="pref_header_notifications_and_calls_callhandling">Gestione delle chiamate</string>
+    <string name="pref_notifications_and_calls_enable_misscall">Notifica per chiamata senza risposta</string>
+    <string name="pref_header_notifications_and_calls">Notifiche e chiamate</string>
+    <string name="pref_theme_system">Sistema</string>
+    <string name="pref_summary_relax_firmware_checks">Allenta i controlli del firmware</string>
+    <string name="error_background_service_reason">L\'avvio del servizio in background non 猫 riuscito a causa di un\'eccezione - clicca qui per maggiori informazioni.\n\nErrore:</string>
+    <string name="error_background_service_reason_truncated">L\'avvio del servizio in background non 猫 riuscito perch茅鈥�</string>
+    <string name="error_background_service">Impossibile avviare il servizio in background</string>
+    <string name="error_version_check_extreme_caution">ATTENZIONE: Errore durante la verifica delle informazioni sulla versione! Non dovresti continuare! Visto il nome della versione \"%s\"</string>
+    <string name="permission_granting_mandatory">Sono richieste tutte queste autorizzazioni e l\'app potrebbe essere instabile se non concesse</string>
+    <string name="about_additional_contributions">Grazie mille a tutti i collaboratori non elencati per aver contribuito con codice, traduzioni, supporto, idee, motivazione, segnalazioni di bug, donazioni 鈥� 鉁�</string>
+    <string name="about_additional_device_support">Supporto a dispositivi aggiuntivi</string>
+    <string name="about_contributors">Contributori</string>
+    <string name="about_core_team_title">Team principale (ordinati in base al primo contributo di codice)</string>
+    <string name="about_description_generic">Un\'alternativa gratuita, libera e senza cloud alle applicazioni Android a sorgente chiusa del tuo dispositivo.</string>
+    <string name="about_activity_title_generic">Su Gadgetbridge</string>
+    <string name="about_title">Informazioni</string>
+    <string name="hr_widget_last_notification">Ultima notifica</string>
+    <string name="pref_qhybrid_save_raw_activity_files">Salva i dati raw (grezzi) delle attivit脿</string>
+    <string name="pref_qhybrid_title_widget_draw_circles">Disegna i cerchi del widget</string>
+    <string name="menuitem_worldclock">Orologio mondiale</string>
+    <string name="menuitem_unknown">Sconosciuto</string>
+    <string name="menuitem_eventreminder">Promemoria evento</string>
+    <string name="devicetype_amazfit_trex">Amazfit T-Rex</string>
+    <string name="bip_prefs_shotcuts_summary">Scegli le scorciatoie che appiano sullo schermo della band</string>
+    <string name="bip_prefs_shortcuts">Scorciatoie</string>
+    <string name="pref_header_auto_fetch">Recupero automatico</string>
+    <string name="power_mode_watch">Solo orologio</string>
+    <string name="power_mode_saving">Risparmio energetico</string>
+    <string name="power_mode_title">Modalit脿 di alimentazione dell\'orologio</string>
+    <string name="prefs_sensors_button_bp_calibration_sum">Premi qui per iniziare la calibrazione</string>
+    <string name="prefs_sensors_button_bp_calibration">Calibrazione</string>
+    <string name="pref_sensors_bp_calibration">Calibrazione della pressione arteriosa</string>
+    <string name="pref_title_sensors_altitude">Calibrazione dell\'altitudine</string>
+    <string name="pref_header_sensors_calibration">Calibrazione del sensore</string>
+    <string name="pref_title_device_spec_settings_show_raw_graph">Mostra i dati RAW (grezzi) sul grafico delle attivit脿</string>
+    <string name="pref_summary_device_spec_settings_title_force_time">Forzare l\'auto-sincronizzazione dell\'ora alla riconnessione. Le lancette analogiche potrebbero mostrare un orario errato!</string>
+    <string name="pref_title_device_spec_settings_force_time">Forzare la sincronizzazione dell\'ora</string>
+    <string name="pref_summary_notifications_and_calls_title_shake_reject">Duplica l\'azione del pulsante dell\'orologio</string>
+    <string name="prefs_notifications_and_calls_shake_reject">Scuotere il polso per ignorare / rifiutare la chiamata</string>
+    <string name="pref_summary_notifications_and_calls_title_reject">Off - ignora, On - rifiuta</string>
+    <string name="pref_title_notifications_and_calls_repeat_on_misscall">Ripetere per X minuti</string>
+    <string name="pref_summary_notifications_and_calls_enable_misscall">Ripete ogni minuto</string>
+    <string name="prefs_notifications_and_calls_continious_ring">Notifica durante la suoneria del cellulare</string>
+    <string name="pref_title_notifications_and_calls_repeat_on_call">Ripeti la notifica di chiamata</string>
+    <string name="title_activity_LenovoWatch_calibration">Calibrazione del Watch X Plus</string>
+    <string name="pref_title_weather_summary">Usato per il provider meteo di LineageOS, altre versioni di Android necessitano di usare un\'app come \"Weather Notifications\". Trova pi霉 informazioni nella wiki di Gadgetbridge.</string>
+    <string name="fw_upgrade_notice_amazfit_trex">Stai per installare il firmware %s sul tuo Amazfit T-Rex.
+\n
+\nAssicurati di installare prima il file .fw e dopo il file .res. L\'orologio si riavvier脿 al termine del caricamento del file .fw.
+\n
+\nNota: non 猫 necessario installare il file .res se 猫 uguale a quello installato in precedenza.
+\n
+\nPROCEDI A TUO RISCHIO E PERICOLO!</string>
+    <string name="controlcenter_set_alias">Imposta Alias</string>
+    <string name="menuitem_stress">Stress</string>
+    <string name="menuitem_cycles">Tracciamento Cicli</string>
+    <string name="menuitem_breathing">Respirazione</string>
+    <string name="devicetype_miband5">Mi Band 5</string>
+    <string name="fw_upgrade_notice_miband5">Stai per installare il firmware %s sul Mi Band 5.
+\n
+\nSi prega di installare prima il file .fw e successivamente il file .res. La Mi Band 5 si riavvier脿 dopo l\'installazione del file .fw.
+\n
+\nNOTA: Non 猫 necessario installare il file .res se risulta essere uguale a quello precedentemente installato.
+\n
+\nPROCEDI A TUO RISCHIO E PERICOLO!</string>
+    <string name="companiondevice_pairing_details">Abilita il supporto alla nuova API CompanionDevice (ha effetto solo su dispositivi Android 8 o superiore) che aumenta l\'affidabilit脿 se il servizio necessita di essere riavviato in background, richiede una ri-associazione utilizzando Gadgetbridge per avere un effetto</string>
+    <string name="error_exporting_device_preferences">Errore durante l\'esportazione delle preferenze specifiche del dispositivo</string>
+    <string name="error_location_enabled_mandatory">La posizione deve essere attivata per cercare i dispositivi</string>
+    <string name="ignore_bonded_devices_description">L\'abilitazione di questa opzione ignorer脿 i dispositivi che sono stati gi脿 associati / accoppiati durante la scansione</string>
+    <string name="ignore_bonded_devices">Ignora i dispositivi associati</string>
+    <string name="error_retrieving_devices_database">Errore durante il recupero dei dispositivi dal database</string>
+    <string name="error_setting_alias">Errore durante l\'impostazione dell\'alias:</string>
+    <string name="pref_check_permission_status_summary">Controlla e chiedi le autorizzazioni mancanti anche quando potrebbero non essere immediatamente necessarie. Disabilita questo solo se i tuoi dispositivi in realt脿 non supportano nessuna di queste funzionalit脿. Non concedere un\'autorizzazione potrebbe causare problemi!</string>
+    <string name="pref_check_permission_status">Verifica dello stato delle autorizzazioni</string>
+    <string name="device_requires_key">CHIAVE RICHIESTA, TIENI PREMUTO PER INSERIRE</string>
+    <string name="device_is_currently_bonded">GI脌 ASSOCIATO</string>
+    <string name="companiondevice_pairing">Associazione del CompanionDevice</string>
+    <string name="require_location_provider">La posizione deve essere abilitata</string>
+    <string name="devicetype_tlw64">TLW64</string>
+    <string name="activity_detail_show_gps_label">Mostra Percorso GPS</string>
+    <string name="activity_detail_duration_label">Durata</string>
+    <string name="activity_detail_end_label">Fine</string>
+    <string name="activity_detail_start_label">Inizio</string>
+    <string name="Steps">Passi</string>
+    <string name="Activity">Attivit脿</string>
+    <string name="Speed">Velocit脿</string>
+    <string name="Distance">Distanza</string>
+    <string name="Swimming">Nuoto</string>
+    <string name="bpm">bpm</string>
+    <string name="minutes_km">min/km</string>
+    <string name="seconds_km">sec/km</string>
+    <string name="calories_unit">kcal</string>
+    <string name="laps_unit">giri</string>
+    <string name="swim_style">stile di nuoto</string>
+    <string name="seconds">s</string>
+    <string name="km_h">km/h</string>
+    <string name="meters_second">m/s</string>
+    <string name="steps_unit">passi</string>
+    <string name="cm">cm</string>
+    <string name="meters">m</string>
+    <string name="flatSeconds">Piano</string>
+    <string name="laps">Giri</string>
+    <string name="swimStyle">Stile Nuoto</string>
+    <string name="averageStride">Camminata media</string>
+    <string name="averageKMPaceSeconds">Ritmo</string>
+    <string name="averageHR">Battito cardiaco</string>
+    <string name="totalStride">Camminata totale</string>
+    <string name="maxPace">Ritmo pi霉 veloce</string>
+    <string name="minPace">Ritmo pi霉 lento</string>
+    <string name="maxSpeed">Massima</string>
+    <string name="caloriesBurnt">Calorie</string>
+    <string name="activeSeconds">Attivo</string>
+    <string name="steps">Passi</string>
+    <string name="minAltitude">Minimo</string>
+    <string name="maxAltitude">Massimo</string>
+    <string name="descentMeters">Discesa</string>
+    <string name="ascentMeters">Salita</string>
+    <string name="distanceMeters">Distanza</string>
+    <string name="devicetype_pinetime_jf">PineTime (JF Firmware)</string>
+    <string name="activity_summary_detail">Dettagli dell\'Attivit脿 Sportiva</string>
+    <string name="strokes_unit">bracc</string>
+    <string name="strokes_second">bracc/s</string>
+    <string name="activity_type_cricket">Cricket</string>
+    <string name="activity_type_yoga">Yoga</string>
+    <string name="devicetype_sg2">Lemfo SG2</string>
+    <string name="activity_type_badminton">Volano</string>
+    <string name="activity_type_pingpong">Ping pong</string>
+    <string name="Strokes">Bracciate</string>
+    <string name="averageStrokesPerSecond">Bracciate medie</string>
+    <string name="averageStrokeDistance">Distanza media bracciata</string>
+    <string name="medley">Misto</string>
+    <string name="backstroke">Dorso</string>
+    <string name="freestyle">Stile libero</string>
+    <string name="breaststroke">Rana</string>
+    <string name="activity_filter_to_placeholder">oggi</string>
+    <string name="activity_filter_from_placeholder">passato</string>
+    <string name="activity_summaries_all_devices">Tutti i dispositivi</string>
+    <string name="sports_activity_quick_filter_select">Periodo di tempo</string>
+    <string name="sports_activity_quick_filter_30days">30 giorni</string>
+    <string name="sports_activity_quick_filter_7days">7 giorni</string>
+    <string name="sports_activity_quick_filter_last_month">Mese scorso</string>
+    <string name="sports_activity_quick_filter_this_month">Questo mese</string>
+    <string name="sports_activity_quick_filter_last_week">Settimana scorsa</string>
+    <string name="sports_activity_quick_filter_this_week">Questa settimana</string>
+    <string name="activity_summaries_all_activities">Tutte le attivit脿</string>
+    <string name="activity_summaries_statistics">Statistiche</string>
+    <string name="yes">S矛</string>
+    <string name="activity_filter_individual_items">Elementi selezionati individualmente</string>
+    <string name="addto_filter">Aggiungi al filtro</string>
+    <string name="activity_filter_apply_filter">Applica filtro</string>
+    <string name="activity_filter_name_contains">Etichetta</string>
+    <string name="activity_filter_reset_filter">Rimuovi filtri</string>
+    <string name="activity_filter_filter_title">Filtra</string>
+    <string name="activity_filter_date_to">A</string>
+    <string name="activity_filter_date_from">Da</string>
+    <string name="pref_header_statistics">Statistiche delle attivit脿 sportive</string>
+    <string name="pref_header_filter">Filtro delle attivit脿 sportive</string>
+    <string name="prefs_events_forwarding_action_title">Avvia azione</string>
+    <string name="prefs_events_forwarding_broadcast_title">Invia messaggio</string>
+    <string name="prefs_events_forwarding_startnonwear">Quando non si indossa il dispositivo</string>
+    <string name="prefs_events_forwarding_wokeup">Quando ci si sveglia</string>
+    <string name="prefs_events_forwarding_fellsleep">Quando ci si addormenta</string>
+    <string name="prefs_events_forwarding_title">Azioni sul dispositivo</string>
+    <string name="Elevation">Elevazione</string>
+    <string name="swolf_index">indice swolf</string>
+    <string name="averageSpeed">Velocit脿 media</string>
+    <string name="baseAltitude">Elevazione base</string>
+    <string name="ascentSeconds">Salita</string>
+    <string name="averageLapPace">Andatura media giro</string>
+    <string name="about_links">Collegamenti</string>
+    <string name="activity_summary_yesterday">Ieri</string>
+    <string name="activity_summary_today">Oggi</string>
+    <string name="activity_summary_edit_name_title">Modifica etichetta</string>
+    <string name="activity_type_basketball">Basket</string>
+    <string name="activity_type_rowing_machine">Remoergometro</string>
+    <string name="activity_type_soccer">Calcio</string>
+    <string name="activity_type_jump_roping">Salti alla corda</string>
+    <string name="activity_type_indoor_cycling">Bicicletta su Rulli</string>
+    <string name="activity_type_swimming_openwater">Nuoto (all\'aperto)</string>
+    <string name="no">No</string>
+    <string name="km">km</string>
+    <string name="devicetype_sonyswr12">Sony SWR12</string>
+    <string name="sonyswr12_settings_title">Impostazioni Sony SWR12</string>
+    <string name="sonyswr12_settings_low_vibration">Bassa vibrazione abilitata</string>
+    <string name="sonyswr12_settings_stamina">La modalit脿 di risparmio energetico 猫 attiva</string>
+    <string name="sonyswr12_settings_alarm_interval">Intervallo di allarme intelligente in pochi minuti</string>
+    <string name="sonyswr12_settings_low_vibration_summary">Abilita una bassa intensit脿 di vibrazione sulla banda</string>
+    <string name="sonyswr12_settings_stamina_summary">La modalit脿 di risparmio energetico disattiva la misurazione automatica periodica della frequenza cardiaca, quindi aumenta il tempo di lavoro</string>
+    <string name="sonyswr12_settings_alarm_interval_summary">L\'intervallo di allarme intelligente 猫 l\'intervallo prima dell\'allarme installato. In questo intervallo il dispositivo sta cercando di rilevare la fase pi霉 leggera del sonno per svegliare l\'utente</string>
+    <string name="gps_track">Traccia GPS</string>
+    <string name="about_version">Versione %s</string>
+    <string name="devicetype_amazfit_bips_lite">Amazfit Bip S Lite</string>
+    <string name="devicetype_amazfit_band5">Amazfit Band 5</string>
+    <string name="activity_error_no_app_for_png">Per condividere questa schermata, installa un\'app che possa aprire file di immagine.</string>
+    <string name="charts_legend_heartrate_average">Media del battito cardiaco</string>
+    <string name="activity_prefs_chart_min_session_length">Lunghezza minima delle attivit脿 (minuti)</string>
+    <string name="activity_prefs_chart_max_idle_phase_length">Lunghezza della pausa per la separazione delle attivit脿 (minuti)</string>
+    <string name="activity_prefs_chart_min_steps_per_minute">Numero minimo di passi al minuto per il rilevamento di attivit脿</string>
+    <string name="activity_prefs_chart_min_steps_per_minute_for_run">Numero minimo di passi al minuto per il rilevamento della corsa</string>
+    <string name="activity_prefs_step_length_cm">Lunghezza del passo in cm</string>
+    <string name="lefun_prefs_interface_language_title">Lingua interfaccia</string>
+    <string name="lefun_prefs_antilost_summary">La band vibrer脿 se il tuo telefono si scollega dalla band</string>
+    <string name="lefun_prefs_antilost_title">Anti-perdita</string>
+    <string name="lefun_prefs_hydration_reminder_interval_title">Intervallo di promemoria per l\'acqua (in minuti)</string>
+    <string name="lefun_prefs_hydration_reminder_summary">La band vibrer脿 per ricordarti di bere dell\'acqua</string>
+    <string name="lefun_prefs_hydration_reminder_title">Promemoria per l\'acqua</string>
+    <string name="charts_min_max_heartrate_popup">Frequenza cardiaca pi霉 bassa: %1$d
+\nFrequenza cardiaca pi霉 alta: %2$d
+\nIntensit脿 del movimento: %3$s</string>
+    <string name="firmware_update_progress">Caricamento in corso
+\n%1d%% a %.2fkbps (media %.2fkbps)
+\nParte %1d di %1d</string>
+    <string name="devicestatus_upload_failed">Caricamento non riuscito</string>
+    <string name="devicestatus_upload_aborted">Caricamento annullato!</string>
+    <string name="devicestatus_upload_validating">Il caricamento 猫 in fase di validazione</string>
+    <string name="devicestatus_upload_completed">Caricamento completato</string>
+    <string name="devicestatus_disconnected">Il dispositivo si 猫 disconnesso!</string>
+    <string name="devicestatus_disconnecting">Il dispositivo si sta disconnettendo!</string>
+    <string name="devicestatus_upload_started">Il caricamento 猫 iniziato</string>
+    <string name="devicestatus_upload_starting">Avvio del caricamento</string>
+    <string name="devicestatus_connected">Dispositivo connesso</string>
+    <string name="devicestatus_connecting">Connessione al dispositivo</string>
+    <string name="charts_activity_list">Elenco attivit脿</string>
+    <string name="chart_get_active_and_synchronize">Fai qualche attivit脿 e sincronizza il dispositivo.</string>
+    <string name="chart_no_active_data">Nessuna attivit脿 rilevata.</string>
+    <string name="find_lost_device_message">Cercare %1$s\?</string>
+    <string name="devicetype_lefun">Lefun</string>
+    <string name="devicetype_casiogbx100">Casio GBX-100</string>
+    <string name="devicetype_amazfit_gts2">Amazfit GTS 2</string>
+    <string name="devicetype_amazfit_gtr2">Amazfit GTR 2</string>
+    <string name="devicetype_amazfit_bipu">Amazfit Bip U</string>
+    <string name="activity_type_elliptical_trainer">Allenatore ellittico</string>
+    <string name="movement_intensity">Intensit脿 del movimento</string>
+    <string name="activity_list_summary_activities">Attivit脿</string>
+    <string name="activity_list_summary_intensity">Movimento
+\nIntensit脿</string>
+    <string name="activity_list_summary_active_time">Tempo attivo</string>
+    <string name="activity_list_summary_distance">Distanza</string>
+    <string name="activity_list_summary_active_steps">Passi attivi</string>
+    <string name="prefs_charts_tabs_summary">Schede grafico visibili</string>
+    <string name="prefs_charts_tabs">Schede dei grafici</string>
+    <string name="menuitem_pomodoro">Tecnica Pomodoro</string>
+    <string name="menuitem_sleep">Sonno</string>
+    <string name="menuitem_goal">Obiettivo attivit脿 fisica</string>
+    <string name="menuitem_alexa">Alexa</string>
+    <string name="menuitem_takephoto">Telecomando Fotocamera</string>
+    <string name="menuitem_mutephone">Silenzia cellulare</string>
+    <string name="menuitem_findphone">Trova cellulare</string>
+    <string name="menuitem_spo2">Ossigenazione sangue</string>
+    <string name="pref_summary_connected_advertisement">Mantieni visibile il dispositivo via Bluetooth anche quando questo 猫 connesso</string>
+    <string name="pref_title_connected_advertisement">Dispositivo visibile durante la connessione</string>
+    <string name="prefs_autoremove_message">Chiudi automaticamente le notifiche SMS</string>
+    <string name="prefs_fake_ring_duration">Suoneria continua fittizia</string>
+    <string name="prefs_operating_sounds">Suoni di operazione</string>
+    <string name="prefs_key_vibration">Vibrazione chiave</string>
+    <string name="prefs_autolight">Luce automatica</string>
+    <string name="qhybrid_calibration_align_hint">Usa i tasti in basso per allineare le lancette dell\'orologio alle ore 12:00.</string>
+    <string name="devicetype_nut_mini">Nut mini</string>
+    <string name="prefs_events_forwarding_summary">Utilizza gli eventi del dispositivo per avviare azioni e broadcast di Android</string>
+    <string name="seconds_m">sec/m</string>
+    <string name="descentSeconds">Discendente</string>
+    <string name="swolfIndex">SWOLF</string>
+    <string name="pref_title_lower_button_function_double">Pulsante inferiore, doppio</string>
+    <string name="pref_title_middle_button_function_double">Pulsante centrale, doppio</string>
+    <string name="pref_title_upper_button_function_double">Pulsante superiore, doppio</string>
+    <string name="pref_title_lower_button_function_long">Pulsante inferiore, prolungato</string>
+    <string name="pref_title_middle_button_function_long">Pulsante centrale, prolungato</string>
+    <string name="pref_title_upper_button_function_long">Pulsante superiore, prolungato</string>
+    <string name="pref_title_lower_button_function_short">Pulsante inferiore, breve</string>
+    <string name="pref_title_middle_button_function_short">Pulsante centrale, breve</string>
+    <string name="pref_title_upper_button_function_short">Pulsante superiore, breve</string>
+    <string name="menuitem_stopwatch">Cronometro</string>
+    <string name="menuitem_dnd">Non Dis.</string>
+    <string name="devicetype_amazfit_vergel">Amazfit Verge Lite</string>
+    <string name="discovery_entered_invalid_authkey">La chiave di autenticazione segreta che hai inserito non 猫 valida! Tieni premuto sul dispositivo per modificarla.</string>
+    <string name="fw_upgrade_notice_amazfitvergel">Stai per installare il firmware %s sul tuo Amazfit Verge Lite.
+\n
+\nAssicurati di installare prima il file .fw, poi il file .res e infine il file .gps. Il tuo orologio verr脿 riavviato dopo l\'installazione del file .fw.
+\n
+\nNota: Non 猫 necessario installare i file .res e .gps se questi sono gli stessi di quelli installati in precedenza.
+\n
+\nPROCEDI A TUO RISCHIO!</string>
+    <string name="devicetype_amazfit_gtr2e">Amazfit GTR 2e</string>
+    <string name="fossil_hr_warning_firmware_too_new">Alcune funzioni sono disabilitate perch茅 il firmware dell\'orologio 猫 troppo recente</string>
+    <string name="qhybrid_title_calibration">Calibrazione</string>
+    <string name="qhybrid_title_apps_management">Gestione delle applicazioni</string>
+    <string name="qhybrid_title_file_management">Gestione dei file</string>
+    <string name="qhybrid_title_background_image">Immagine di sfondo</string>
+    <string name="qhybrid_title_apps">Applicazioni</string>
+    <string name="fossil_hr_auth_failed">Autenticazione non andata a buon fine, funzionalit脿 limitate</string>
+    <string name="fossil_hr_unavailable_unauthed">Non disponibile nella modalit脿 senza autenticazione</string>
+    <string name="fossil_hr_synced_activity_data">Dati delle attivit脿 sincronizzati</string>
+    <string name="fossil_hr_commute_processing">Esecuzione della richiesta in corso鈥�</string>
+    <string name="mi">mi</string>
+    <string name="minutes_mi">min/mi</string>
+    <string name="mi_h">mi/h</string>
+    <string name="ft">ft</string>
+    <string name="widget_settings_select_device_title">Seleziona dispositivo</string>
+    <string name="menuitem_temperature">Temperatura corporea</string>
+    <string name="menuitem_widgets">Widget</string>
+    <string name="menuitem_events">Eventi</string>
+    <string name="devicetype_waspos">Wasp-os</string>
+    <string name="devicetype_um25">UM-25</string>
+    <string name="devicetype_zepp_e">Zepp E</string>
+    <string name="devicetype_amazfit_x">Amazfit X</string>
+    <string name="devicetype_amazfit_gts2e">Amazfit GTS 2e</string>
+    <string name="devicetype_amazfit_gts2_mini">Amazfit GTS 2 Mini</string>
+    <string name="devicetype_amazfit_bipupro">Amazfit Bip U Pro</string>
+    <string name="devicetype_amazfit_neo">Amazfit Neo</string>
+    <string name="activity_type_strength_training">Allenamento di forza</string>
+    <string name="dbmanagementactivity_export_finished">Eliminazione completata</string>
+    <string name="activity_db_management_clean_export_directory_text">I file esportati nella cartella di importazione/esportazione sono accessibili da qualsiasi app sul tuo dispositivo. Se vuoi puoi eliminare questi file dopo la sincronizzazione o il backup. Assicurati di avere un backup dei file prima di eliminarli. I file GPX, le sottocartelle e i file del database esportati automaticamente (se presenti) non saranno eliminati. Il percorso della cartella di esportazione/importazione 猫 la seguente:</string>
+    <string name="activity_DB_clean_export_directory_warning_message">Vuoi davvero eliminare i file dalla cartella di importazione/esportazione\?</string>
+    <string name="activity_DB_clean_export_directory_warning_title">Vuoi eliminare i file dalla cartella di importazione/esportazione\?</string>
+    <string name="activity_db_management_clean_export_directory_label">Elimina i file nella cartella di importazione/esportazione</string>
+    <string name="activity_DB_ShowContentButton">Mostra i contenuti della cartella di importazione/esportazione</string>
+    <string name="activity_data_management_directory_content_title">Esporta/Importa i contenuti della cartella</string>
+    <string name="dbmanagementactivity_export_confirmation">Vuoi davvero esportare i dati\? I dati delle attivit脿 esportati precedentemente (se presenti) e le preferenza verranno sovrascritte.</string>
+    <string name="dbmanagementactivity_export_data_title">Vuoi esportare i dati\?</string>
+    <string name="dbmanagementactivity_error_cleaning_export_directory">Si 猫 verificato un errore durante l\'eliminazione dei file dalla cartella di esportazione %1$s</string>
+    <string name="prefs_sounds_summary">Configura quando vuoi che il dispositivo emetta un suono</string>
+    <string name="prefs_sounds">Suoni</string>
+    <string name="show_ongoing_activity">Mostra la finestra dell\'attivit脿 in corso</string>
+    <string name="dialog_hide">Nascondi</string>
+    <string name="fw_upgrade_notice_amazfitneo">Stai per installare il firmware %s sul tuo Amazfit Neo.
+\n
+\nIl tuo bracciale si riavvier脿 dopo aver installato il file .fw.
+\n
+\nPROCEDI A TUO RISCHIO E PERICOLO!</string>
+    <string name="fw_upgrade_notice_amazfitx">Stai per installare il firmware %s sul tuo Amazfit X.
+\n
+\nAssicurati di installare prima il file .fw e poi quello .res. Il bracciale si riavvier脿 dopo aver installato il file .fw.
+\n
+\nNota: Non c\'猫 bisogno di installare il file .res se 猫 usuale a quello installato in precedenza.
+\n
+\nPROCEDI A TUO RISCHIO E PERICOLO!</string>
+    <string name="calendar_year">Anno</string>
+    <string name="calendar_six_months">6 mesi</string>
+    <string name="calendar_three_months">3 mesi</string>
+    <string name="calendar_month">Mese</string>
+    <string name="calendar_two_weeks">Due settimane</string>
+    <string name="calendar_week">Settimana</string>
+    <string name="calendar_day">Giorno</string>
+    <string name="battery_level">Livello della batteria</string>
+    <string name="battery_detail_activity_title">Informazioni sulla batteria</string>
+    <string name="watchface_install_info">Stai per installare la watchface seguente:
+\n
+\n%1$s
+\nVersione %2$s di %3$s
+\n</string>
+    <string name="pref_theme_black_background">Utilizza lo sfondo nero nel tema scuro</string>
+    <string name="fw_upgrade_notice_miband6">Stai per installare il firmware %s sulla tua Mi Band 6.
+\n
+\nAssicurati di installare prima il file .fw e dopo il file .res. L\'orologio si riavvier脿 al termine del caricamento del file .fw.
+\n
+\nNota: non 猫 necessario installare il file .res se 猫 uguale a quello installato in precedenza.
+\n
+\nPROCEDI A TUO RISCHIO E PERICOLO!</string>
+    <string name="watchface_setting_title_power_saving">Risparmio energetico</string>
+    <string name="activity_summary_detail_select_gpx_track">Seleziona traccia GPX</string>
+    <string name="set">Imposta</string>
+    <string name="blood_pressure">Pressione sanguigna</string>
+    <string name="getting_heart_rate">Misurazione in corso</string>
+    <string name="heart_rate_result">Risultato della misurazione</string>
+    <string name="devicetype_miband6">Mi Band 6</string>
+    <string name="pref_title_huami_force_new_protocol">Nuovo protocollo di autenticazione</string>
+    <string name="pref_summary_huami_force_new_protocol">Abilita se il tuo dispositivo non si riesce pi霉 a connettere dopo un aggiornamento del firmware</string>
+    <string name="pref_title_notification_use_as_deny">Nega le notifiche dalle applicazioni selezionate</string>
+    <string name="title_activity_notification_management">Impostazioni Notifiche</string>
+    <string name="pref_message_privacy_mode_complete">Nascondi tutto il contenuto</string>
+    <string name="menuitem_barometer">Barometro</string>
+    <string name="menuitem_flashlight">Torcia</string>
+    <string name="gpx_receiver_overwrite_some_files">Alcuni file(s) esistono gi脿. Sovrascrivere\?</string>
+    <string name="pref_header_notification_application_settings">Impostazioni per applicazione</string>
+    <string name="uncheck_all_applications">Deseleziona tutte le applicazioni</string>
+    <string name="button_watchface_select_image">Cambia immagine di sfondo</string>
+    <string name="pref_applications_settings">Lista applicazioni</string>
+    <string name="pref_title_notification_use_as_allow">Permetti le notifiche dalle applicazioni selezionate</string>
+    <string name="pref_title_physical_buttons">Pulsanti fisici</string>
+    <string name="qhybrid_calibration_counterclockwise">Senso contro-orario</string>
+    <string name="qhybrid_summary_file_management">Carica e scarica files</string>
+    <string name="devicetype_amazfit_trex_pro">Amazfit T-Rex Pro</string>
+    <string name="watchface_dialog_title_add_widget">Aggiungi widget</string>
+    <string name="qhybrid_calibration_clockwise">Senso orario</string>
+    <string name="pref_summary_developer_settings">Impostazioni e funzionalit脿 utilizzate da sviluppatori</string>
+    <string name="qhybrid_pref_summary_external_intents">Permette ad altre app di caricare/sovrascrivere files</string>
+    <string name="pref_title_developer_settings">Impostazioni sviluppatore</string>
+    <string name="button_watchface_add_widget">Aggiungi widget</string>
+    <string name="watchface_toast_settings_incomplete">Impostazioni incomplete, widget non aggiunto</string>
+    <string name="reminder_date">Data</string>
+    <string name="unknown">Sconosciuto</string>
+    <string name="title_activity_set_reminders">Configura promemoria</string>
+    <string name="reminder_repeat">Ripeti</string>
+    <string name="reminder_time">Orario</string>
+    <string name="reminder_message">Messaggio</string>
+    <string name="miband_prefs_reserve_reminder_calendar">Promemoria da riservare per i prossimi eventi del calendario</string>
+    <string name="controlcenter_power_off">Spegni</string>
+    <string name="controlcenter_power_off_confirm_title">Spegni</string>
+    <string name="controlcenter_power_off_confirm_description">Sei sicuro di voler spegnere il dispositivo\?</string>
+    <string name="device_card_activity_card_title">Informazioni sull\'attivit脿 nella carta del dispositivo</string>
+    <string name="device_card_activity_card_title_summary">Scegli quale attivit脿 mostrare nella carta del dispositivo</string>
+    <string name="prefs_activity_in_device_card_title">Mostra informazioni sull\'attivit脿 nella carta del dispositivo</string>
+    <string name="prefs_activity_in_device_card_title_summary">Mostra i passi, la distanza o il sonno attuali nella carta del dispositivo</string>
+    <string name="prefs_activity_in_device_card_sleep_title_summary">Mostra la durata del sonno</string>
+    <string name="prefs_activity_in_device_card_distance_title_summary">La distanza viene calcolata dai passi e dalla lunghezza del passo (configurabile in Impostazioni - Informazioni sull\'utilizzatore)</string>
+    <string name="prefs_activity_in_device_card_steps_title_summary">Mostra passi totali</string>
+    <string name="pref_header_system">Sistema</string>
+    <string name="pref_header_equalizer">Equalizzatore</string>
+    <string name="pref_title_notification_use_as">Utilizza la lista delle applicazioni per鈥�</string>
+    <string name="pref_title_message_privacy_mode">Modalit脿 privacy per i messaggi</string>
+    <string name="pref_message_privacy_mode_off">Mostra tutto il contenuto</string>
+    <string name="controlcenter_start_configure_reminders">Configura promemoria</string>
+    <string name="reminder_no_free_slots_title">Spazio esaurito</string>
+    <string name="reminder_no_free_slots_description">Il dispositivo non ha spazio libero per aggiungere altri promemoria (totali: %1$s)</string>
+    <string name="title_activity_reminder_details">Dettagli promemoria</string>
+    <string name="reminder_time_every_day">%1$s, ogni giorno</string>
+    <string name="reminder_time_every_week">%1$s, ogni settimana</string>
+    <string name="reminder_time_every_month">%1$s, ogni mese</string>
+    <string name="reminder_time_every_year">%1$s, ogni anno</string>
+    <string name="reminder_once">Una volta sola</string>
+    <string name="reminder_every_day">Ogni giorno</string>
+    <string name="reminder_every_week">Ogni settimana</string>
+    <string name="reminder_every_month">Ogni mese</string>
+    <string name="reminder_every_year">Ogni anno</string>
+    <string name="reminder_delete_confirm_title">Elimina promemoria</string>
+    <string name="reminder_delete_confirm_description">Sei sicuro di voler eliminare il promemoria\?</string>
+    <string name="mi2_prefs_do_not_disturb_lift_wrist">Attiva il display quando il dispositivo viene sollevato durante la modalit脿 non disturbare</string>
+    <string name="pref_title_ping_tone">Suono di ping</string>
+    <string name="prefs_activity_in_device_card_sleep_title">Sonno</string>
+    <string name="pref_header_other">Altro</string>
+    <string name="discovery_bluetooth_scan">Scansione Bluetooth:</string>
+    <string name="discovery_bluetooth_le_scan">Scansione Bluetooth LE:</string>
+    <string name="reminder_time_once">%1$s, una volta sola</string>
+    <string name="check_all_applications">Controlla tutte le applicazioni</string>
+    <string name="prefs_reserve_reminder_calendar_summary">Numero di eventi del calendario che verranno sincronizzati</string>
+    <string name="discovery_scanning_intensity_warning">Se il dispositivo si blocca o non risponde, prova a impostare l\'Intensit脿 di scansione ad un livello inferiore. Se il dispositivo non viene rilevato, prova a impostare l\'Intensit脿 di scansione ad un livello pi霉 alto.</string>
+    <string name="pref_write_logfiles_not_available">L\'inizializzazione dei file di log non 猫 riuscita, la scrittura dei file di log non 猫 attualmente disponibile. Riavvia l\'applicazione per provare a inizializzare di nuovo i file di log.</string>
+    <string name="pref_world_clocks_title">Orologi mondiali</string>
+    <string name="world_clock_label">Etichetta</string>
+    <string name="prefs_activity_monitoring_description">Aumenta automaticamente la cadenza di rilevamento della frequenza cardiaca quando la band rileva l\'esercizio fisico, per aumentare la precisione della cattura della frequenza cardiaca.</string>
+    <string name="seconds_6">6 secondi</string>
+    <string name="seconds_7">7 secondi</string>
+    <string name="info_connected_count">%d dispositivi connessi</string>
+    <string name="menuitem_personal_activity_intelligence">Intelligenza Attivit脿 Personale</string>
+    <string name="menuitem_workout_history">Storico Allenamenti</string>
+    <string name="menuitem_female_health">Salute femminile</string>
+    <string name="menuitem_unknown_app">Sconosciuto (%s)</string>
+    <string name="share_log_not_enabled_message">Devi prima abilitare la registrazione in Impostazioni - Scrittura dei file di log</string>
+    <string name="quick_alarm_description">Sveglia dal widget</string>
+    <string name="toast_app_must_be_selected">L\'app deve essere selezionata per essere configurata</string>
+    <string name="permission_notification_policy_access">%1$s deve accedere alle impostazioni Non Disturbare per poterle soddisfare sull\'orologio quando lo schermo del telefono 猫 spento.
+\n
+\nTocca \"%2$s\" e poi \"%1$s\" e attiva \"Consenti Non Disturbare\", quindi tocca \"Indietro\" per tornare a %1$s.</string>
+    <string name="prefs_password_4_digits_1_to_4_summary">La password deve avere 4 cifre, usando i numeri da 1 a 4</string>
+    <string name="permission_display_over_other_apps">%1$s ha bisogno dell\'autorizzazione per essere visualizzata sopra le altre app, per consentire agli orologi Bangle.js di avviare attivit脿 tramite intenti quando %1$s 猫 in background.
+\n
+\nQuesto pu貌 essere usato per avviare un\'app musicale e riprodurre una canzone, e varie altre cose.
+\n
+\nTocca \"%2$s\" e poi \"%1$s\" e abilita \"Consenti visualizzazione su altre app\", quindi tocca \"Indietro\" per tornare a %1$s.
+\n
+\nPer evitare che %1$s chieda autorizzazioni, vai in \"Impostazioni\" e deseleziona \"Verifica stato autorizzazioni\".
+\n
+\nAssicurati di concedere a %1$s le autorizzazioni necessarie per funzionare correttamente.</string>
+    <string name="seconds_14">14 secondi</string>
+    <string name="devicetype_miband7">Xiaomi Smart Band 7</string>
+    <string name="about_hash">Commit %s</string>
+    <string name="qhybrid_title_watchface">Configurazione quadrante</string>
+    <string name="prefs_double_tap_edge">Doppio tocco sul bordo</string>
+    <string name="qhybrid_calibration_1_step">1 passo</string>
+    <string name="activity_db_management_autoexport_scheduled_yes">Esportazione automatica (inizialmente) programmata per %1$s</string>
+    <string name="activity_type_climbing">Arrampicata</string>
+    <string name="button_watchface_edit_name">Modifica nome</string>
+    <string name="button_watchface_save_apply">Salva e applica</string>
+    <string name="watchface_setting_wrist_flick_move_relative">Sposta le lancette in base al tempo</string>
+    <string name="watchface_widget_type_battery">Batteria</string>
+    <string name="fw_upgrade_notice_miband7">Stai per installare il firmware %s sulla tua Xiaomi Smart Band 7.
+\n
+\nLa band si riavvier脿 dopo l\'installazione del file .zip.
+\n
+\nPROCEDI A TUO RISCHIO E PERICOLO!</string>
+    <string name="pref_header_calendar">Calendario</string>
+    <string name="pref_header_connection">Collegamento</string>
+    <string name="pref_blacklist_calendars_summary">I calendari ignorati non saranno sincronizzati con il dispositivo</string>
+    <string name="pref_title_screen_on_on_notifications">Notifiche con schermo acceso</string>
+    <string name="pref_summary_screen_on_on_notifications">Accendi lo schermo della band quando arriva una notifica</string>
+    <string name="spo2_perc_80">80%</string>
+    <string name="heartrate_bpm_40">40 bpm</string>
+    <string name="heartrate_bpm_45">45 bpm</string>
+    <string name="heartrate_bpm_50">50 bpm</string>
+    <string name="heartrate_bpm_115">115 bpm</string>
+    <string name="spo2_perc_85">85%</string>
+    <string name="spo2_off">Spento</string>
+    <string name="world_clock_no_free_slots_title">Nessuno slot libero</string>
+    <string name="world_clock_no_free_slots_description">Il dispositivo non ha slot liberi per gli orologi mondiali (slot totali: %1$s)</string>
+    <string name="spo2_perc_90">90%</string>
+    <string name="prefs_relaxation_reminder_title">Promemoria relax</string>
+    <string name="prefs_heartrate_alert_low_threshold">Soglia allarme frequenza cardiaca bassa</string>
+    <string name="prefs_relaxation_reminder_description">Fai vibrare la band per avvisarti se il valore di stress 猫 superiore a 80</string>
+    <string name="prefs_spo2_monitoring_title">Monitoraggio Ossigeno nel Sangue</string>
+    <string name="prefs_spo2_monitoring_description">Monitora automaticamente i livelli di ossigeno nel sangue durante la giornata</string>
+    <string name="prefs_spo2_alert_threshold">Soglia avviso SPO2</string>
+    <string name="seconds_15">15 secondi</string>
+    <string name="mi2_prefs_heart_rate_monitoring">Monitoraggio Frequenza Cardiaca</string>
+    <string name="prefs_screen_timeout">Timeout Schermo</string>
+    <string name="persian">Persiano</string>
+    <string name="devicetype_domyos_t540">Domyos T540</string>
+    <string name="devicetype_sony_wh_1000xm3">Sony WH-1000XM3</string>
+    <string name="devicetype_sony_wf_1000xm3">Sony WF-1000XM3</string>
+    <string name="menuitem_countdown">Conto alla rovescia</string>
+    <string name="menuitem_workout_status">Stato Allenamento</string>
+    <string name="spm">passi/min</string>
+    <string name="pref_summary_canned_messages_set">Invia al dispositivo i messaggi configurati di seguito</string>
+    <string name="fossil_hr_commute_actions_explanation">Le azioni configurate qui appariranno nell\'app Commute sull\'orologio. Leggi la wiki per informazioni su come gestire gli intenti prodotti da queste azioni.</string>
+    <string name="watchface_dialog_title_set_name">Imposta nome quadrante</string>
+    <string name="watchface_dialog_widget_background">Sfondo</string>
+    <string name="hybridhr_widget_bg_thin_circle">Cerchio sottile</string>
+    <string name="activity_type_hiking">Escursione</string>
+    <string name="discover_unsupported_devices">Rileva dispositivi non supportati</string>
+    <string name="maxHR">Frequenza Cardiaca Massima</string>
+    <string name="button_watchface_preview">Anteprima sull\'orologio</string>
+    <string name="watchface_dialog_widget_x_coordinate">Coordinata X (max 240)</string>
+    <string name="watchface_setting_desc_wrist_flick">(per disabilitare completamente, abilita il movimento relativo e imposta tutti i valori a 0)</string>
+    <string name="watchface_widget_type_chance_rain">Possibilit脿 di pioggia</string>
+    <string name="prefs_pressure_relief">Rilievo della pressione con suono ambiente</string>
+    <string name="pressure_relief_summary">Evita la sensazione di pressione nelle orecchie quando non si usa la Cancellazione Attiva del Rumore</string>
+    <string name="open_fw_installer_warning_title">Avvertenza</string>
+    <string name="open_fw_installer_warning_text">Questa funzione pu貌 mandare in tilt il tuo dispositivo. Detto questo, non 猫 mai successo a nessuno degli sviluppatori attraverso il flashing, ma ricorda che lo fai a tuo rischio e pericolo.</string>
+    <string name="open_fw_installer_getting_files_title">Ottieni il file firmware/app</string>
+    <string name="open_fw_installer_ensure_device_connected">Assicurati che il dispositivo %s sia connesso</string>
+    <string name="pref_title_device_internet_access">Consenti Accesso a Internet</string>
+    <string name="pref_summary_device_internet_access">Consenti l\'accesso a internet alle app su questo dispositivo</string>
+    <string name="kind_agps_bundle">Pacchetto AGPS</string>
+    <string name="prefs_sleep_time">Ore di sonno</string>
+    <string name="watchface_setting_power_saving_hands">Disabilita il movimento delle lancette se non indossato</string>
+    <string name="watchface_upload_failed">Il caricamento del quadrante non 猫 riuscito. Riprova.</string>
+    <string name="watchface_widget_type_calories">Calorie</string>
+    <string name="prefs_autoheartrate_sleep">Misura durante il sonno</string>
+    <string name="prefs_autoheartrate_interval">Frequenza misurazioni</string>
+    <string name="devicetype_galaxybuds_live">Galaxy Buds Live</string>
+    <string name="nothing_prefs_audiomode_title">Modalit脿 audio</string>
+    <string name="prefs_equalizer_preset">Preimpostazioni Equalizzatore</string>
+    <string name="prefs_galaxy_buds_experimental">Sperimentali</string>
+    <string name="prefs_right">Destra</string>
+    <string name="audio_codec">Codec Audio</string>
+    <string name="pref_workout_start_on_phone_title">Registrazione app fitness</string>
+    <string name="pref_workout_start_on_phone_summary">Inizia/ferma registrazione app fitness sul telefono quando un allenamento GPS 猫 avviavviato sulla band</string>
+    <string name="pref_workout_send_gps_title">Invia GPS durante l\'allenamento</string>
+    <string name="pref_workout_send_gps_summary">Invia la posizione GPS corrente alla band durante un allenamento</string>
+    <string name="add_test_device">Aggiungi dispositivo di test</string>
+    <string name="menuitem_email">E-mail</string>
+    <string name="prefs_fm_presets_presets">Preimpostati</string>
+    <string name="gpx_receiver_activity_title">Ricevitore GPX Gadgetbridge</string>
+    <string name="permission_notification_listener">%1$s ha bisogno di accedere alle Notifiche per poterle visualizzare sull\'orologio quando lo schermo del telefono 猫 spento.
+\n
+\nTocca \"%2$s\" e poi \"%1$s\" e abilita \"Consenti Accesso alle Notifiche\", quindi tocca \"Indietro\" per tornare a %1$s.</string>
+    <string name="watchface_dialog_widget_preset_bottom">In basso</string>
+    <string name="qhybrid_calibration_10_steps">10 passi</string>
+    <string name="qhybrid_calibration_100_steps">100 passi</string>
+    <string name="watchface_widget_type_weather">Meteo</string>
+    <string name="watchface_dialog_widget_presets">Posizioni preimpostate</string>
+    <string name="watchface_dialog_widget_preset_left">Sinistra</string>
+    <string name="pref_title_equalizer_dynamic">Dinamico</string>
+    <string name="pref_title_equalizer_trebble">Potenziamento degli acuti</string>
+    <string name="prefs_dolby_mode">Modalit脿 Dolby</string>
+    <string name="prefs_equalizer">Equalizzatore</string>
+    <string name="prefs_game_mode_summary">Solo se il telefono supporta la modalit脿 gioco</string>
+    <string name="prefs_touch_lock_summary">Disabilita eventi di tocco</string>
+    <string name="prefs_seamless_connection_switch_summary">Scambio automatico degli auricolari tra i dispositivi accoppiati</string>
+    <string name="prefs_ambient_volume">Volume ambiente</string>
+    <string name="prefs_ambient_volume_left">Volume Ambiente Sinistra</string>
+    <string name="prefs_ambient_volume_right">Volume Ambiente Destra</string>
+    <string name="gpx_receiver_files_received">File GPX ricevuto(i):</string>
+    <string name="fossil_hr_edit_action_delete">cancella</string>
+    <string name="fossil_hr_new_action_cancel">annulla</string>
+    <string name="fossil_hr_new_action">Nuova azione</string>
+    <string name="watchface_widget_type_active_mins">Minuti di attivit脿</string>
+    <string name="watchface_dialog_widget_cat_generic">Generico</string>
+    <string name="watchface_setting_title_display_refresh_timeout">Mostra timeout aggiornamento</string>
+    <string name="watchface_setting_wrist_flick_minute">Lancetta dei minuti (da -360 a 360)</string>
+    <string name="hybridhr_widget_bg_dashed_circle">Cerchio tratteggiato</string>
+    <string name="prefs_sleep_time_summary">Specifica le ore in cui registrare il sonno</string>
+    <string name="prefs_vibration_enable">Abilita vibrazione</string>
+    <string name="hybridhr_widget_bg_double_circle">Cerchio doppio</string>
+    <string name="prefs_autoheartrate_summary">Misurazioni periodiche della frequenza cardiaca durante il giorno e anche durante il sonno</string>
+    <string name="watchface_dialog_pre_setting_position">posizione preimpostata a %s</string>
+    <string name="info_no_devices_connected">Nessun dispositivo connesso</string>
+    <string name="notification_channel_low_battery_name">Batteria scarica</string>
+    <string name="pref_screen_notification_profile_event_reminder">Promemoria eventi</string>
+    <string name="pref_screen_notification_profile_find_device">Trova dispositivo</string>
+    <string name="prefs_game_mode">Modalit脿 gioco</string>
+    <string name="pref_title_notifications_generic_settings">Impostazioni notifiche Android</string>
+    <string name="devicetype_sony_wf_sp800n">Sony WF-SP800N</string>
+    <string name="toast_app_must_not_be_selected">L\'app non deve essere selezionata per essere configurata</string>
+    <string name="qhybrid_pref_summary_actions">Azioni per la app Commute</string>
+    <string name="nothing_prefs_inear_summary">Riproduci/pausa la musica a seconda che si indossino gli auricolari</string>
+    <string name="activity_summary_detail_clear_gpx_track">Cancella traccia GPX</string>
+    <string name="activity_summary_detail_editing_gpx_track">Modifica traccia GPX collegata</string>
+    <string name="notification_gps_text">Invio posizione GPS a %1$d dispositivi</string>
+    <string name="menuitem_nothing">Niente</string>
+    <string name="fossil_hr_edit_action">Modifica azione</string>
+    <string name="pref_title_equalizer_bass_boost">Potenziamento bassi</string>
+    <string name="pref_title_equalizer_soft">Morbido</string>
+    <string name="pref_switch_controls_anc_ambient">Cancellazione rumore 鈫愨啋 Ambiente</string>
+    <string name="pref_switch_controls_ambient_off">Ambiente 鈫愨啋 Spento</string>
+    <string name="prefs_noise_control">Controllo rumore</string>
+    <string name="prefs_voice_detect">Rilevamento voce</string>
+    <string name="pref_ambient_sound_tone">Tono del Suono Ambiente</string>
+    <string name="pref_ambient_sound_tone_summary">Da Morbido a Limpido</string>
+    <string name="prefs_touch_lock">Blocco tocchi</string>
+    <string name="pref_title_device_intents">Consenti Intenti</string>
+    <string name="world_clock_delete_confirm_title">Cancella \'%1$s\'</string>
+    <string name="activity_type_elliptical">Ellittica</string>
+    <string name="watchface_setting_display_refresh_partial">Aggiornamento parziale (in minuti)</string>
+    <string name="watchface_setting_title_wrist_flick">Colpo di polso</string>
+    <string name="prefs_noise_control_with_one_earbud_summary">Consenti il controllo del rumore quando si usa un solo auricolare</string>
+    <string name="pref_title_touch_volume">Volume</string>
+    <string name="prefs_voice_detect_summary">Abilita Suono Ambiente e abbassa automaticamente la riproduzione dopo il rilevamento della voce</string>
+    <string name="about_activity_title_banglejs_main">Su Bangle.js Gadgetbridge</string>
+    <string name="title_activity_controlcenter_banglejs_main">Bangle.js Gadgetbridge</string>
+    <string name="application_name_banglejs_main">Bangle.js Gadgetbridge</string>
+    <string name="about_description_banglejs_main">Applicazione Android per Bangle.js basata sul progetto Gadgetbridge, con l\'aggiunta dell\'accesso a Internet.
+\n
+\nA causa delle politiche di Google Play Store, non 猫 possibile inserire un link per le donazioni nell\'applicazione stessa, ma se ti piace questa applicazione, considera una donazione tramite la pagina di Gadgetbridge qui sotto.</string>
+    <string name="gadgetbridge_running_banglejs_main">Bangle.js in esecuzione</string>
+    <string name="about_activity_title_main_nightly">Su Gadgetbridge Nightly</string>
+    <string name="application_name_main_nightly">Gadgetbridge (Nightly)</string>
+    <string name="title_activity_controlcenter_main_nightly">Gadgetbridge Nightly</string>
+    <string name="about_description_main_nightly">Un\'alternativa gratuita, libera e senza cloud alle applicazioni Android a sorgente chiusa del tuo dispositivo. Versione Nightly di Gadgetbridge. Non pu貌 essere installata se si ha gi脿 installato l\'applicazione Gadgetbridge o Pebble, a causa di conflitto col provider Pebble.</string>
+    <string name="gadgetbridge_running_main_nightly">Nightly GB in esecuzione</string>
+    <string name="application_name_main_nopebble">Gadgetbridge (Nightly, Senza Pebble provider)</string>
+    <string name="title_activity_controlcenter_main_nopebble">Gadgetbridge Nightly Senza Pebble</string>
+    <string name="about_activity_title_main_nopebble">Su Gadgetbridge Nightly Senza Pebble</string>
+    <string name="about_description_main_nopebble">Un\'alternativa gratuita, libera e senza cloud alle applicazioni Android a sorgente chiusa del tuo dispositivo. Versione Nightly di Gadgetbridge. In questa versione il provider Pebble 猫 stato rinominato per evitare conflitti, quindi alcune integrazioni relative a Pebble non funzioneranno, ma pu貌 essere installata insieme ad un\'installazione esistente di Gadgetbridge.</string>
+    <string name="qhybrid_watchface_configuration_old_firmware">Configurazione dei quadranti per orologi con versione firmware DN1.0.2.19r e inferiore</string>
+    <string name="qhybrid_pref_title_external_intents">Consenti intenti esterni rischiosi</string>
+    <string name="watchface_cache_confirm_overwrite">Nella cache esiste gi脿 un quadrante con questo nome. Vuoi sovrascriverlo\?</string>
+    <string name="devicetype_galaxybuds">Galaxy Buds</string>
+    <string name="watchface_dialog_widget_width">Larghezza widget (in pixel)</string>
+    <string name="pref_summary_opentracks_packagename">Utilizzato per avviare/fermare la registrazione della traccia GPS in un\'app fitness esterna.</string>
+    <string name="watchface_setting_title_custom_events">Eventi personalizzati</string>
+    <string name="pref_title_equalizer_clear">Limpido</string>
+    <string name="enable_notifications_summary">Notifiche per chiamate, messaggi, notifiche e altro</string>
+    <string name="prefs_autoheartrate">Frequenza cardiaca automatica</string>
+    <string name="prefs_autoheartrate_measurement">Misurazione automatica della frequenza cardiaca</string>
+    <string name="prefs_dolby_summary">Preimpostazione Dolby per equalizzatore</string>
+    <string name="right_earbud">Earbud destra</string>
+    <string name="pref_title_touch_anc">Cancellazione Attiva Rumore</string>
+    <string name="battery_case">Custodia batteria</string>
+    <string name="left_earbud">Earbud sinistra</string>
+    <string name="button_watchface_settings">Impostazioni quadrante</string>
+    <string name="appmanager_item_outdated">(obsoleto)</string>
+    <string name="open_fw_installer_info_text_title">Installer file</string>
+    <string name="pref_title_banglejs_webview_url">URL app loader</string>
+    <string name="pref_summary_banglejs_webview_url">Se vuoi usare un app loader personalizzato, inserisci l\'URL di https://.../android.html. Altrimenti lascia vuoto per https://banglejs.com/apps</string>
+    <string name="pref_screen_notification_idle_alerts">Avvisi inattivit脿</string>
+    <string name="pref_screen_vibration_patterns_title">Schemi vibrazione</string>
+    <string name="pref_screen_vibration_patterns_summary">Configura i modelli di vibrazione per le varie notifiche</string>
+    <string name="heartrate_bpm_120">120 bpm</string>
+    <string name="heartrate_bpm_125">125 bpm</string>
+    <string name="heartrate_bpm_130">130 bpm</string>
+    <string name="heartrate_bpm_135">135 bpm</string>
+    <string name="heartrate_bpm_140">140 bpm</string>
+    <string name="pref_world_clocks_summary">Configura orologi per altri fusi orari</string>
+    <string name="world_clock_delete_confirm_description">Sei sicuro di voler cancellare l\'orologio mondiale\?</string>
+    <string name="prefs_heartrate_alert_threshold">Soglia allarme frequenza cardiaca</string>
+    <string name="prefs_stress_monitoring_title">Monitoraggio stress</string>
+    <string name="prefs_stress_monitoring_description">Monitora i livelli di stress durante il riposo</string>
+    <string name="prefs_password">Password</string>
+    <string name="prefs_password_summary">Blocca la band con una password se rimossa dal polso</string>
+    <string name="prefs_password_enabled">Password Attiva</string>
+    <string name="bengali">Bengalese</string>
+    <string name="estonian">Estone</string>
+    <string name="icelandic">Islandese</string>
+    <string name="portuguese_br">Portoghese (Brasile)</string>
+    <string name="portuguese_pt">Portoghese (Portogallo)</string>
+    <string name="activity_type_outdoor_running">Corsa all\'aperto</string>
+    <string name="activity_db_management_autoexport_location">Non 猫 stato possibile trovare la posizione. Probabilmente 猫 un problema del nuovo sistema di permessi di Android. Molto probabilmente, l\'esportazione automatica non funziona ora.</string>
+    <string name="sensitive">Sensibile</string>
+    <string name="activity_type_freestyle">Stile libero</string>
+    <string name="activity_type_outdoor_cycling">Ciclismo all\'aperto</string>
+    <string name="descentDistance">Distanza in discesa</string>
+    <string name="minSpeed">Minima</string>
+    <string name="minCadence">Cadenza Minima</string>
+    <string name="pref_summary_physical_buttons">Configura le funzionalit脿 dei pulsanti fisici sull\'orologio</string>
+    <string name="qhybrid_summary_calibration">Calibra le lancette dell\'orologio</string>
+    <string name="watchface_widget_type_steps">Passi</string>
+    <string name="qhybrid_pref_title_actions">Azioni</string>
+    <string name="watchface_widget_type_heart_rate">Frequenza cardiaca</string>
+    <string name="watchface_dialog_widget_type">Tipo</string>
+    <string name="watchface_dialog_widget_y_coordinate">Coordinata Y (max 240)</string>
+    <string name="watchface_dialog_widget_color_black">Nero</string>
+    <string name="watchface_dialog_title_settings">Impostazioni quadrante</string>
+    <string name="watchface_widget_type_2nd_tz">2掳 fuso orario</string>
+    <string name="nothing_prefs_inear_title">Rilevamento auricolare</string>
+    <string name="prefs_sleep_time_label">Definisci ore di sonno</string>
+    <string name="prefs_notifications_enable">Abilita notifiche</string>
+    <string name="enable_vibrations_summary">Vibrazione per chiamate, messaggi, notifiche e altro</string>
+    <string name="devicetype_nothingear1">Nothing Ear (1)</string>
+    <string name="prefs_in_ear_detection_summary">Riproduci chiamate negli auricolari se indossati</string>
+    <string name="prefs_galaxy_touch_options">Opzioni Tocco</string>
+    <string name="pref_title_touch_ambient">Suono Ambiente</string>
+    <string name="pref_title_touch_spotify">Spotify</string>
+    <string name="pref_voice_detect_duration_5">5 secondi</string>
+    <string name="start">Inizio</string>
+    <string name="activity_db_management_autoexport_enabled_yes">Esportazione automatica attiva.</string>
+    <string name="devicetype_amazfit_pop">Amazfit Pop</string>
+    <string name="devicetype_amazfit_pop_pro">Amazfit Pop Pro</string>
+    <string name="discover_unsupported_devices_description">Abilitando questa opzione, durante la scansione verranno visualizzati tutti i dispositivi Bluetooth rilevati. Tocca per copiare il nome del dispositivo e l\'indirizzo mac negli appunti. Premi a lungo per avviare la finestra \"Aggiungi dispositivo di prova\". Pu貌 causare il blocco dell\'app.</string>
+    <string name="prefs_active_noise_cancelling_summary">Blocca i rumori dell\'ambiente circostante</string>
+    <string name="fw_upgrade_notice_amazfit_gts3">Stai per installare il firmware %s sul tuo Amazfit GTS 3.
+\n
+\nLa band si riavvier脿 dopo l\'installazione del file .zip.
+\n
+\nPROCEDI A TUO RISCHIO E PERICOLO!</string>
+    <string name="open_fw_installer_info_text">L\'installer di Firmware/Quadrante/App/File consente di caricare/installare i file supportati (firmware, quadranti, applicazioni, GPS, risorse, font...) sul dispositivo. Per ulteriori informazioni, consultare la wiki: https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Firmware-Update</string>
+    <string name="pref_header_display">Schermo</string>
+    <string name="pref_header_health">Salute</string>
+    <string name="pref_header_time">Ora</string>
+    <string name="pref_header_workout">Allenamento</string>
+    <string name="pref_title_notifications_ignore_low_priority">Ignora notifiche con bassa priorit脿</string>
+    <string name="pref_summary_notifications_ignore_low_priority">Non inviare notifiche con bassa e minima priorit脿 all\'orologio</string>
+    <string name="normal">Normale</string>
+    <string name="pref_title_notification_prefer_long_text">Preferisci testo di notifica lungo</string>
+    <string name="pref_summary_notification_prefer_long_text">Se disponibile, invia il testo di notifica lungo al dispositivo</string>
+    <string name="pref_summary_banglejs_txt_bitmap_size">Dimensione del testo convertito in bitmap</string>
+    <string name="pref_title_banglejs_txt_bitmap_size">Dimensione testo bitmap</string>
+    <string name="pref_summary_canned_replies">Rispondi dall\'orologio con messaggi preimpostati</string>
+    <string name="pref_cache_weather">Cache dei dati meteo</string>
+    <string name="pref_cache_weather_summary">Le informazioni meteo vengono memorizzate tra successivi riavvii dell\'applicazione.</string>
+    <string name="seconds_8">8 secondi</string>
+    <string name="seconds_9">9 secondi</string>
+    <string name="seconds_11">11 secondi</string>
+    <string name="seconds_12">12 secondi</string>
+    <string name="seconds_13">13 secondi</string>
+    <string name="mi2_prefs_heart_rate_monitoring_summary">Configura monitoraggio frequenza cardiaca</string>
+    <string name="prefs_always_on_display">Schermo sempre attivo</string>
+    <string name="smart">Intelligente</string>
+    <string name="mi2_dnd_always">Sempre</string>
+    <string name="devicetype_amazfit_gts3">Amazfit GTS 3</string>
+    <string name="watchface_dialog_widget_preset_top">In alto</string>
+    <string name="watchface_setting_button_toggle_backlight">Accendi retroilluminazione</string>
+    <string name="watchface_setting_power_saving_display">Disabilita gli aggiornamenti del display se non indossato</string>
+    <string name="pref_title_equalizer_normal">Normale</string>
+    <string name="prefs_equalizer_summary">Attiva o disattiva equalizzatore</string>
+    <string name="prefs_ambient_settings_title">Opzioni Suono Ambiente</string>
+    <string name="prefs_active_noise_cancelling">Cancellazione Attiva del Rumore</string>
+    <string name="prefs_double_tap_edge_summary">Rileva il doppio tocco anche quando non 猫 stato toccato il touch pad</string>
+    <string name="controlcenter_get_heartrate_measurement">Misura la frequenza cardiaca</string>
+    <string name="open_fw_installer_pick_file">Seleziona file</string>
+    <string name="prefs_heartrate_alert_experimental_title">Allarme frequenza cardiaca (sperimentale)</string>
+    <string name="mi2_prefs_heart_rate_monitoring_alerts_summary">Configura monitoraggio della frequenza cardiaca e soglie di allarme</string>
+    <string name="mi5_prefs_workout_activity_types_summary">Scegli i tipi di attivit脿 da visualizzare nella schermata degli allenamenti</string>
+    <string name="note">Nota</string>
+    <string name="averageAltitude">Media</string>
+    <string name="appmanager_app_edit">Modifica</string>
+    <string name="watchface_widget_type_date">Data</string>
+    <string name="prefs_heartrate_alert_experimental_description">Fai vibrare la band quando la frequenza cardiaca supera una soglia, senza alcuna attivit脿 fisica evidente negli ultimi 10 minuti. Questa funzione 猫 sperimentale e non 猫 stata testata a fondo.</string>
+    <string name="prefs_password_6_digits_0_to_9_summary">La password deve avere 6 cifre, usando solo numeri</string>
+    <string name="czech">Ceco</string>
+    <string name="extended_ascii">ASCII esteso</string>
+    <string name="lithuanian">Lituano</string>
+    <string name="activity_db_management_autoexport_enabled_no">Esportazione automatica spenta.</string>
+    <string name="activity_db_management_autoexport_scheduled_no">Esportazione automatica non programmata.</string>
+    <string name="devicetype_sony_wh_1000xm4">Sony WH-1000XM4</string>
+    <string name="notification_channel_transfer_name">Trasferimento dati</string>
+    <string name="prefs_fm_preset_instructions">Premi a lungo il pulsante per memorizzare la preimpostazione</string>
+    <string name="watchface_setting_light_up_on_notification">Illumina con nuove notifiche</string>
+    <string name="title_activity_world_clock_details">Dettagli orologio mondiale</string>
+    <string name="prefs_activate_display_on_lift_sensitivity">Sensibilit脿</string>
+    <string name="device_unsupported">NON SUPPORTATO</string>
+    <string name="qhybrid_title_watchface_designer">Designer quadranti</string>
+    <string name="watchface_dialog_widget_preset_right">Destra</string>
+    <string name="watchface_dialog_widget_color">Colore</string>
+    <string name="watchface_dialog_widget_color_white">Bianco</string>
+    <string name="kind_app">Applicazione</string>
+    <string name="devicetype_smaq2oss">SMA-Q2 OSS</string>
+    <string name="devicetype_fitpro">FitPro</string>
+    <string name="notification_channel_gps">Tracciamento GPS</string>
+    <string name="notification_gps_title">GPS Gadgetbridge</string>
+    <string name="maxCadence">Cadenza Massima</string>
+    <string name="minStride">Distanza Minima</string>
+    <string name="minHR">Frequenza Cardiaca Minima</string>
+    <string name="maxStride">Distanza Massima</string>
+    <string name="averageCadence">Cadenza Media</string>
+    <string name="pref_summary_canned_messages_dismisscall">Ignora le chiamate dall\'orologio con un messaggio SMS</string>
+    <string name="prefs_active_noise_cancelling_level">Livello di Cancellazione Attiva del Rumore</string>
+    <string name="pref_title_touch_voice_assistant">Assistente Vocale</string>
+    <string name="prefs_active_noise_cancelling_level_high">Alto</string>
+    <string name="prefs_voice_detect_duration">Termina dopo un silenzio di:</string>
+    <string name="pref_voice_detect_duration_10">10 secondi</string>
+    <string name="pref_voice_detect_duration_15">15 secondi</string>
+    <string name="pref_title_notification_delay_calls">Ritardo di notifica chiamata</string>
+    <string name="pref_summary_notification_delay_calls">Ritardo prima di inviare le notifiche di chiamata in arrivo al dispositivo, in secondi.</string>
+    <string name="gadgetbridge_running_banglejs_nightly">Nightly Bangle.js in esecuzione</string>
+    <string name="application_name_banglejs_nightly">Bangle.js Gadgetbridge (Nightly)</string>
+    <string name="prefs_left">Sinistra</string>
+    <string name="pref_switch_controls_anc_off">Cancellazione rumore 鈫愨啋 Spento</string>
+    <string name="pref_explanation_authkey">Alcuni dispositivi richiedono una chiave di accoppiamento speciale per la prima inizializzazione. Tocca qui per maggiori dettagli nella wiki.</string>
+    <string name="heartrate_bpm_145">145 bpm</string>
+    <string name="prefs_customize_ambient_sound_summary">Personalizza Suono Ambiente</string>
+    <string name="prefs_ambient_sound_during_call_title">Suono Ambiente in chiamata</string>
+    <string name="prefs_ambient_sound_during_call_summary">Ascolta la propria voce chiaramente in chiamata</string>
+    <string name="prefs_ambient_mode">Modalit脿 Ambiente</string>
+    <string name="pref_title_touch_quick_ambient">Suono Ambiente Rapido</string>
+    <string name="pref_balance">Equilibrio</string>
+    <string name="devicetype_binary_sensor">Sensore binario</string>
+    <string name="ascentDistance">Distanza in salita</string>
+    <string name="appmanager_app_share">Condividi</string>
+    <string name="open_fw_installer_connect_maximum_one_device">Connetti SOLO UN dispositivo a cui desideri inviare il file.</string>
+    <string name="pref_title_banglejs_text_bitmap">Testo come bitmap</string>
+    <string name="pref_summary_banglejs_text_bitmap">Se una parola non pu貌 essere resa con il font dell\'orologio, crea una bitmap in Gadgetbridge e usa quella sull\'orologio</string>
+    <string name="open_fw_installer_getting_files_text">Poich茅 non possiamo distribuire i file del firmware, dovrai procurarteli da solo. Questo significa che dovrai cercarli nei file apk, online, nei forum, su Amazfitwatchfaces (per i dispositivi Miband/Amazfit) e cos矛 via.</string>
+    <string name="open_fw_installer_select_file">Seleziona un file da caricare sul dispositivo: %s</string>
+    <string name="open_fw_installer_connect_minimum_one_device">Connetti ALMENO UN dispositivo a cui desideri inviare il file.</string>
+    <string name="heartrate_bpm_105">105 bpm</string>
+    <string name="heartrate_bpm_110">110 bpm</string>
+    <string name="heartrate_bpm_150">150 bpm</string>
+    <string name="autoExport_lastTime_label">Ultima esportazione automatica: %1$s</string>
+    <string name="prefs_ambient_voice_summary">Fai risaltare la voce</string>
+    <string name="prefs_ambient_sound">Suono Ambiente</string>
+    <string name="prefs_active_noise_cancelling_level_low">Basso</string>
+    <string name="prefs_switch_control_left">Interruttore di sinistra</string>
+    <string name="prefs_switch_control_right">Interruttore di destra</string>
+    <string name="heartrate_bpm_100">100 bpm</string>
+    <string name="watchface_setting_display_refresh_full">Aggiornamento completo (in minuti)</string>
+    <string name="watchface_setting_wrist_flick_hour">Lancetta delle ore (da -360 a 360)</string>
+    <string name="watchface_dialog_widget_cat_position">Posizione</string>
+    <string name="watchface_dialog_widget_cat_2nd_tz_widget">Widget 2掳 fuso orario</string>
+    <string name="watchface_dialog_widget_cat_custom_widget">Widget personalizzato</string>
+    <string name="watchface_dialog_title_widget">Impostazioni widget</string>
+    <string name="watchface_setting_wrist_flick_duration">Durata (in ms)</string>
+    <string name="discovery_scanning_intensity">Intensit脿 di scansione</string>
+    <string name="world_clock_timezone">Fuso orario</string>
+    <string name="prefs_heartrate_alert_high_threshold">Soglia allarme frequenza cardiaca elevata</string>
+    <string name="prefs_activity_monitoring_title">Monitoraggio attivit脿</string>
+    <string name="pref_sleep_breathing_quality_monitoring">Monitoraggio qualit脿 respirazione durante il sonno</string>
+    <string name="prefs_always_on_display_summary">Mantieni il display della band sempre acceso</string>
+    <string name="mi5_prefs_workout_activity_types">Tipi Attivit脿 Allenamento</string>
+    <string name="scandinavian">Scandinavo</string>
+    <string name="ukranian">Ucraino</string>
+    <string name="activity_prefs_discovery_pairing">Opzioni di rilevamento e accoppiamento</string>
+    <string name="quick_alarm">Sveglia rapida</string>
+    <string name="permission_location">%1$s ha bisogno di accedere alla posizione in background per rimanere connesso all\'orologio anche quando lo schermo 猫 spento. \n \nTocca \'%2$s\' per acconsentire.</string>
+    <string name="about_description_banglejs_nightly">Applicazione Android per Bangle.js basata sul progetto Gadgetbridge, con l\'aggiunta dell\'accesso a Internet.
+\n
+\nA causa delle politiche di Google Play Store, non 猫 possibile inserire un link per le donazioni nell\'applicazione stessa, ma se ti piace questa applicazione, considera una donazione tramite la pagina di Gadgetbridge qui sotto.</string>
+    <string name="title_activity_controlcenter_banglejs_nightly">Bangle.js Gadgetbridge (Nightly)</string>
+    <string name="about_activity_title_banglejs_nightly">Su Bangle.js Gadgetbridge (Nightly)</string>
+    <string name="pref_title_overwrite_settings_on_connection">Sovrascrivi impostazioni al collegamento</string>
+    <string name="pref_summary_overwrite_settings_on_connection">Durante la connessione alla band, sovrascrivi tutte le sue impostazioni.</string>
+    <string name="pref_summary_device_intents">Consenti alle app del Bangle.js di inviare Intenti Android e permetti ad altre app su Android (come Tasker) di inviare dati a Bangle.js con l\'intento com.banglejs.uart.tx. Serve l\'autorizzazione a visualizzare su altre app per funzionare in background.</string>
+    <string name="pref_explanation_authkey_new_protocol">Se ricevi \"Aggiorna l\'app alla versione pi霉 recente\" sulla band, assicurati di controllare il \"Nuovo protocollo di autorizzazione\" come sopra. Tocca qui per maggiori dettagli nella wiki.</string>
+    <string name="sony_anc_optimize_confirmation_title">Ottimizzazione Cancellazione Rumore</string>
+    <string name="sony_sound_position_front">Anteriore</string>
+    <string name="prefs_hourly_chime">Segnale orario</string>
+    <string name="watchface_dialog_widget_timezone_duration">Durata visibilit脿 orologio (in secondi)</string>
+    <string name="controlcenter_set_preferences">Imposta preferenze</string>
+    <string name="controlcenter_set_parent_folder">Imposta cartella superiore</string>
+    <string name="sony_surround_mode_club">Discoteca</string>
+    <string name="steps_streaks_total_steps">Passi
+\ntotali</string>
+    <string name="sony_ambient_sound_level">Livello Suono Ambiente</string>
+    <string name="pref_header_spo2">Ossigeno nel sangue</string>
+    <string name="sony_sound_position_rear_left">Posteriore Sinistra</string>
+    <string name="sony_sound_position_rear_right">Posteriore Destra</string>
+    <string name="sony_warn_sbc_codec">Attenzione: Le impostazioni di equalizzatore, posizione audio e surround funzionano solo per il codec audio SBC.</string>
+    <string name="controlcenter_unset_folder">Resetta cartella</string>
+    <string name="steps_streaks_total_steps_average_hint">Totale passi medi %d al giorno</string>
+    <string name="step_streak_longest">Massima</string>
+    <string name="step_streak_average_steps_hint">Passi medi per giorno durante la striscia</string>
+    <string name="steps_streaks_total_steps_hint_totals">Numero totale di passi registrati</string>
+    <string name="activity_prefs_summary_allow_bluetooth_intent_api">Consenti il controllo della connessione Bluetooth tramite API Intenti</string>
+    <string name="sony_ambient_sound_focus_voice">Concentrato sulla Voce</string>
+    <string name="pref_header_sony_ambient_sound_control">Controllo Suono Ambiente</string>
+    <string name="sony_sound_position_front_right">Anteriore Destra</string>
+    <string name="sony_surround_mode_off">Spenta</string>
+    <string name="sony_equalizer">Equalizzatore</string>
+    <string name="prefs_activity_recognition">Impostazioni riconoscimento attivit脿</string>
+    <string name="pref_activity_recognize_rowing">riconosci canottaggio</string>
+    <string name="pref_activity_recognize_running">riconosci corsa</string>
+    <string name="sony_button_mode_off">Spenta</string>
+    <string name="sony_ambient_sound">Modalit脿</string>
+    <string name="pref_media_play">Riproduzione Media</string>
+    <string name="devicetype_bose_qc35">Bose QC35</string>
+    <string name="devicetype_vesc">VESC</string>
+    <string name="pref_header_heartrate_allday">Monitoraggio 24 ore</string>
+    <string name="pref_header_heartrate_alerts">Avvisi frequenza cardiaca</string>
+    <string name="sony_sound_position">Posizione Suono</string>
+    <string name="sony_speak_to_chat_timeout">Timeout</string>
+    <string name="sony_speak_to_chat_focus_on_voice">Concentrato sulla Voce</string>
+    <string name="sony_automatic_power_off_30_min">30 minuti</string>
+    <string name="sony_button_mode_left">Modalit脿 Pulsante (Sinistro)</string>
+    <string name="watchface_setting_button_toggle_widgets">Inverti widget</string>
+    <string name="sony_ambient_sound_off">Spento</string>
+    <string name="pref_header_sony_device_info">Informazioni Dispositivo</string>
+    <string name="sony_anc_optimize_description">Clicca per avviare l\'ottimizzazione della cancellazione del rumore.</string>
+    <string name="sony_surround_mode_arena">Arena</string>
+    <string name="sony_anc_optimizer_status_atmospheric_pressure">Misurazione pressione atmosferica鈥�</string>
+    <string name="sony_equalizer_preset_off">Spento</string>
+    <string name="sony_equalizer_preset_relaxed">Rilassato</string>
+    <string name="sony_automatic_power_off_off">Non spegnere</string>
+    <string name="sony_automatic_power_off_5_min">5 minuti</string>
+    <string name="watchface_dialog_widget_timezone">Fuso orario</string>
+    <string name="watchface_dialog_widget_timeout_hide_text">Nascondi testo al timeout</string>
+    <string name="distance_format_meters">###m</string>
+    <string name="distance_format_kilometers">###.#km</string>
+    <string name="pref_device_action_phone_gps_location_listener_stop">Ferma Listener Posizione GPS</string>
+    <string name="pref_activity_recognition_mode_ask">chiedi</string>
+    <string name="distance_format_feet">###ft</string>
+    <string name="controlcenter_set_folder_title">Imposta o crea nuova cartella</string>
+    <string name="auto_reconnect_ble_title">Connetti automaticamente al dispositivo</string>
+    <string name="auto_reconnect_ble_summary">Cerca di connettersi periodicamente al dispositivo proattivamente</string>
+    <string name="connection_over_bt_classic">Connessione via Bluetooth classico</string>
+    <string name="steps_streaks">Striscia passi</string>
+    <string name="steps_streaks_hint">Serie di giorni consecutivi senza interruzioni raggiungendo l\'obiettivo dei passi</string>
+    <string name="steps_streaks_average_steps">Media
+\npassi</string>
+    <string name="steps_streaks_since_date">Da %s</string>
+    <string name="sony_equalizer_clear_bass">Bassi Limpidi</string>
+    <string name="prefs_seamless_connection_switch_title">Interruttore connessione uniforme</string>
+    <string name="sony_anc_optimizer_status_finished">Conclusione鈥�</string>
+    <string name="sony_equalizer_preset_excited">Eccitato</string>
+    <string name="sony_equalizer_preset_mellow">Morbido</string>
+    <string name="sony_button_mode_right">Modalit脿 Pulsante (Destro)</string>
+    <string name="menuitem_menu">Menu</string>
+    <string name="prefs_noise_control_with_one_earbud">Controllo Rumore con una earbud</string>
+    <string name="pref_device_action_fitness_app_control_start">Avvia Tracciamento App Fitness</string>
+    <string name="gadgetbridge_running_main_nopebble">Nightly SenzaPebble GB in esecuzione</string>
+    <string name="sony_anc_optimize_title">Ottimizza</string>
+    <string name="pref_anc_optimizer_state_pressure">Pressione atmosferica</string>
+    <string name="sony_equalizer_preset_bright">Intenso</string>
+    <string name="sony_equalizer_preset_treble_boost">Aumento Acuti</string>
+    <string name="pref_media_forward">Salta in avanti</string>
+    <string name="pref_activity_recognize_walking">Riconosci camminata</string>
+    <string name="pref_activity_recognize_biking">riconosci pedalata</string>
+    <string name="pref_title_opentracks_packagename">Nome pacchetto OpenTracks</string>
+    <string name="pref_media_rewind">Salta indietro</string>
+    <string name="devicetype_galaxybuds_pro">Galaxy Buds Pro</string>
+    <string name="sony_automatic_power_off_when_taken_off">Quando tolto</string>
+    <string name="sony_button_mode_ambient_sound_control">Controllo Suono Ambiente</string>
+    <string name="sony_button_mode_playback_control">Controllo Riproduzione</string>
+    <string name="watchface_dialog_widget_timeout_show_circle">Mostra cerchio al timeout</string>
+    <string name="pref_device_action_broadcast">Invia Broadcast</string>
+    <string name="pref_device_action_fitness_app_control_toggle">Interruttore Tracciamento App Fitness</string>
+    <string name="qhybrid_summary_on_device_confirmation">Le conferme di accoppiamento sul dispositivo possono diventare fastidiose. Disattivandole si potrebbe perdere la funzionalit脿.</string>
+    <string name="pref_media_next">Prossima Traccia</string>
+    <string name="autoconnect_from_device_summary">Stabilisci una connessione quando la connessione viene avviata da un dispositivo, come le cuffie</string>
+    <string name="step_streak_ongoing">In corso</string>
+    <string name="steps_streaks_total_days_hint_totals">Percentuale di giorni con obiettivo raggiunto in relazione a tutti i giorni con passi</string>
+    <string name="step_streak_days_hint">Numero di giorni consecutivi in cui 猫 stato raggiunto l\'obiettivo dei passi</string>
+    <string name="steps_streaks_total_steps_hint">Numero totale di passi nell\'intera striscia</string>
+    <string name="watchface_dialog_widget_update_timeout">Tempo di aggiornamento in minuti</string>
+    <string name="pref_media_volumeup">Volume Pi霉</string>
+    <string name="pref_switch_noise_control">Interruttore Controllo Rumore</string>
+    <string name="sony_ambient_sound_noise_cancelling">Cancellazione Rumore</string>
+    <string name="sony_anc_optimize_confirmation_description">Usa le cuffie normalmente. Se le condizioni di utilizzo o la pressione atmosferica cambiano, esegui nuovamente l\'ottimizzazione.</string>
+    <string name="sony_equalizer_preset_speech">Conversazione</string>
+    <string name="pref_header_sony_equalizer_preset_custom_1">Preset Personalizzato 1</string>
+    <string name="pref_header_sony_equalizer_bands">Bande</string>
+    <string name="sony_equalizer_preset_manual">Manuale</string>
+    <string name="sony_equalizer_preset_custom_1">Personalizzato 1</string>
+    <string name="sony_touch_sensor">Controllo sensore tattile</string>
+    <string name="sony_speak_to_chat_sensitivity_auto">Automatica</string>
+    <string name="sony_speak_to_chat_sensitivity_high">Alta</string>
+    <string name="sony_speak_to_chat_timeout_off">Spento</string>
+    <string name="sony_speak_to_chat_sensitivity_low">Bassa</string>
+    <string name="controlcenter_toggle_details">Mostra/nascondi dettagli</string>
+    <string name="controlcenter_connected_fraction">Connesso: %d/%d</string>
+    <string name="error_deleting_device">Errore cancellando il dispositivo: %s</string>
+    <string name="error_setting_parent_folder">Errore impostando la cartella superiore: %s</string>
+    <string name="sony_anc_optimizer_status_starting">Avvio鈥�</string>
+    <string name="sony_sound_position_front_left">Anteriore Sinistra</string>
+    <string name="sony_anc_optimizer_status_not_running">Non Attivo</string>
+    <string name="sony_anc_optimizer_status_wearing_condition">Misurazione condizioni meteo鈥�</string>
+    <string name="sony_equalizer_band_6300">6.3k</string>
+    <string name="sony_equalizer_band_16000">16k</string>
+    <string name="sony_audio_upsampling">Sovracampionamento Audio</string>
+    <string name="sony_button_mode_volume_control">Controllo Volume</string>
+    <string name="watchface_widget_type_custom">Widget Personslizzato</string>
+    <string name="qhybrid_title_on_device_confirmation">Attiva conferma di accoppiamento sul dispositivo</string>
+    <string name="sony_automatic_power_off">Spegnimento Automatico</string>
+    <string name="sony_automatic_power_off_3_hour">3 ore</string>
+    <string name="pref_screen_brightness">Luminosit脿 Schermo</string>
+    <string name="distance_format_miles">###.#mi</string>
+    <string name="sony_equalizer_band_400">400</string>
+    <string name="sony_equalizer_band_1000">1k</string>
+    <string name="sony_equalizer_band_2500">2.5k</string>
+    <string name="pref_header_heartrate_sleep">Sonno</string>
+    <string name="pref_header_stress">Stress</string>
+    <string name="sony_ambient_sound_wind_noise_reduction">Riduzione Rumore Vento</string>
+    <string name="pref_header_sony_anc_optimizer">Ottimizzazione Cancellazione Rumore</string>
+    <string name="step_streaks_achievements_sharing_title">Risultati Passi</string>
+    <string name="sony_sound_position_off">Spenta</string>
+    <string name="sony_equalizer_preset_custom_2">Personalizzato 2</string>
+    <string name="pref_header_sony_equalizer_preset_custom_2">Preset Personalizzato 2</string>
+    <string name="sony_speak_to_chat_timeout_short">Breve (15s)</string>
+    <string name="sony_speak_to_chat_timeout_standard">Standard (30s)</string>
+    <string name="prefs_ambient_voice_focus">Concentrazione sulla voce</string>
+    <string name="sony_surround_mode">Modalit脿 Surround</string>
+    <string name="steps_streaks_streak_days">Striscia
+\nGiorni</string>
+    <string name="pref_activity_recognition_mode_auto">auto</string>
+    <string name="fossil_hr_button_config_info">Alcuni pulsanti non possono essere configurati perch茅 le loro funzioni sono codificate nel firmware dell\'orologio.
+\n
+\nAttenzione: premendo a lungo il pulsante superiore quando 猫 installato un quadrante dall\'app ufficiale Fossil, cambier脿 anche la visualizzazione dei widget.</string>
+    <string name="autoconnect_from_device_title">Connetti alla connessione dal dispositivo</string>
+    <string name="controlcenter_folder_name">Nome cartella:</string>
+    <string name="controlcenter_add_new_folder">Aggiungi nuova cartella</string>
+    <string name="connection_over_ble">Connessione via BLE</string>
+    <string name="sony_ambient_sound_ambient_sound">Suono Ambiente</string>
+    <string name="sony_surround_mode_outdoor_stage">Concerto all\'aperto</string>
+    <string name="sony_surround_mode_concert_hall">Concerto al chiuso</string>
+    <string name="sony_anc_optimizer_status_analyzing">Analisi鈥�</string>
+    <string name="sony_equalizer_preset_vocal">Vocale</string>
+    <string name="sony_equalizer_preset_bass_boost">Aumento Bassi</string>
+    <string name="sony_speak_to_chat_sensitivity">Sensibilit脿 Rilevamento Voce</string>
+    <string name="sony_speak_to_chat_timeout_long">Lungo (1m)</string>
+    <string name="sony_notification_voice_guide">Notifiche &amp; Guida Vocale</string>
+    <string name="sony_automatic_power_off_1_hour">1 ora</string>
+    <string name="sony_connect_two_devices">Connetti a 2 dispositivi simultaneamente</string>
+    <string name="sony_pause_when_taken_off">Pausa quando si tolgono le cuffie</string>
+    <string name="pref_button_action_disabled">Disattivata</string>
+    <string name="pref_media_pause">Pausa Media</string>
+    <string name="pref_media_previous">Traccia Precedente</string>
+    <string name="pref_media_volumedown">Volume Meno</string>
+    <string name="pref_device_action_fitness_app_control_stop">Termina Tracciamento App Fitness</string>
+    <string name="pref_activity_recognition_mode_none">nessuna</string>
+    <string name="step_streak_total">Totale</string>
+    <string name="step_streaks_achievements_sharing_message">I miei risultati giornalieri in passi!</string>
+    <string name="prefs_hourly_chime_summary">L\'orologio emette un beep una volta all\'ora</string>
+    <string name="sony_speak_to_chat">Parla-per-chattare</string>
+    <string name="devicetype_flipper_zero">Flipper zero</string>
+    <string name="activity_prefs_allow_bluetooth_intent_api">API Intenti Bluetooth</string>
+    <string name="steps_streaks_achievement_rate">Frequenza
+\nconseguimento</string>
+    <string name="pref_media_playpause">Cambia stato riproduzione</string>
+    <string name="dismiss">Ignora</string>
+    <string name="fw_upgrade_notice_amazfit_gtr3">Stai per installare il firmware %s sul tuo Amazfit GTR 3.
+\n
+\nLa band si riavvier脿 al termine del caricamento del file .zip.
+\n
+\nPROCEDI A TUO RISCHIO E PERICOLO!</string>
+    <string name="devicetype_amazfit_gtr3">Amazfit GTR 3</string>
+    <string name="pref_title_third_party_app_device_settings">Consenti ad app di terze parti di cambiare le impostazioni</string>
+    <string name="pref_summary_third_party_app_device_settings">Consenti ad altre app di terze parti installate di modificare le impostazioni del dispositivo con intenti.</string>
+    <string name="activity_type_gymnastics">Ginnastica</string>
+    <string name="activity_type_table_tennis">Ping-pong</string>
+    <string name="activity_type_indoor_fitness">Fitness al chiuso</string>
+    <string name="activity_type_stretching">Stretching</string>
+    <string name="activity_type_pilates">Pilates</string>
+    <string name="activity_type_indoor_ice_skating">Pattinaggio sul ghiaccio al chiuso</string>
+    <string name="activity_type_kickboxing">Kickboxing</string>
+    <string name="activity_type_bowling">Bowling</string>
+    <string name="activity_type_dance">Ballo</string>
+    <string name="activity_type_zumba">Zumba</string>
+    <string name="activity_type_boxing">Pugilato</string>
+    <string name="activity_type_volleyball">Pallavolo</string>
+    <string name="pref_gps_header">GPS</string>
+    <string name="menuitem_theater_mode">Modalit脿 Teatro</string>
+    <string name="pref_title_touch_spotify_official_app">Spotify (solo app ufficiale)</string>
+    <string name="hrZoneNa">N/A</string>
+    <string name="menuitem_eject_water">Rimuovi Acqua</string>
+    <string name="HeartRateZones">Zone Battito Cardiaco</string>
+    <string name="menuitem_unsupported">[NON SUPPORTATO] %s</string>
+    <string name="world_clock_code">Codice</string>
+    <string name="function_enabled">Attivata</string>
+    <string name="activity_type_core_training">Allenamento Core</string>
+    <string name="menuitem_volume">Volume</string>
+    <string name="sports_activity_confirm_delete_title">Elimina %d attivit脿</string>
+    <string name="sports_activity_confirm_delete_description">Sei sicuro di voler eliminare %d attivit脿\?</string>
+    <string name="devicetype_sony_wh_1000xm2">Sony WH-1000XM2</string>
+    <string name="accuracy">Precisione</string>
+    <string name="balanced">Equilibrato</string>
+    <string name="power_saving">Risparmio energetico</string>
+    <string name="custom">Personalizzato</string>
+    <string name="single_band">Banda singola</string>
+    <string name="dual_band">Banda doppia</string>
+    <string name="low_power_gps">GPS a basso consumo</string>
+    <string name="gps_bds">GPS + BDS</string>
+    <string name="gps">GPS</string>
+    <string name="gps_gnolass">GPS + GNOLASS</string>
+    <string name="gps_galileo">GPS + GALILEO</string>
+    <string name="all_satellites">Tutti i satelliti</string>
+    <string name="speed_first">Preferisci velocit脿</string>
+    <string name="accuracy_first">Preferisci precisione</string>
+    <string name="prefs_always_on_display_follow_watchface">Segui stile quadrante</string>
+    <string name="prefs_always_on_display_style">Stile</string>
+    <string name="prefs_control_center_summary">Scegli le voci nel menu a tendina del centro di controllo</string>
+    <string name="prefs_control_center">Centro di Controllo</string>
+    <string name="activity_prefs_goal_standing_time_minutes">Obiettivo giornaliero: tempo in piedi in minuti</string>
+    <string name="activity_prefs_goal_fat_burn_time_minutes">Obiettivo giornaliero: tempo consumo grassi in minuti</string>
+    <string name="fw_upgrade_notice_amazfit_gtr4">Stai per installare il firmware %s sul tuo Amazfit GTR 4.
+\n
+\nLa band si riavvier脿 dopo l\'installazione del file .zip.
+\n
+\nPROCEDI A TUO RISCHIO E PERICOLO!</string>
+    <string name="pref_header_sound_vibration">Suono &amp; Vibrazione</string>
+    <string name="activity_type_indoor_walking">Camminata al chiuso</string>
+    <string name="menuitem_todo">Da Fare</string>
+    <string name="menuitem_calendar">Calendario</string>
+    <string name="menuitem_bluetooth">Bluetooth</string>
+    <string name="hrZoneWarmUp">Riscaldamento</string>
+    <string name="pref_header_authentication">Autenticazione</string>
+    <string name="fw_upgrade_notice_amazfit_gts4">Stai per installare il firmware %s sul tuo Amazfit GTS 4.
+\n
+\nLa band si riavvier脿 dopo l\'installazione del file .zip.
+\n
+\nPROCEDI A TUO RISCHIO E PERICOLO!</string>
+    <string name="pref_title_upper_button_long_press_action">Azione pressione prolungata pulsante superiore</string>
+    <string name="pref_title_lower_button_short_press_action">Azione pressione breve pulsante inferiore</string>
+    <string name="activity_type_hiit">Allenamento a intervalli ad alta intensit脿</string>
+    <string name="activity_type_stepper">Step</string>
+    <string name="watchface_setting_button_move_hands">Movimento mani</string>
+    <string name="maximumOxygenUptake">Massimo Assorbimento Ossigeno</string>
+    <string name="menuitem_brightness">Luminosit脿</string>
+    <string name="pref_header_intent_api">API Intenti</string>
+    <string name="pref_sleep_mode_smart_enable_title">Attivazione intelligente</string>
+    <string name="buttons_on_left">Pulsanti a sinistra</string>
+    <string name="vibration_profile_default">Predefinito</string>
+    <string name="abstract_chart_fragment_kind_rem_sleep">Sonno REM</string>
+    <string name="activity_prefs_target_weight_kg">Peso desiderato in kg</string>
+    <string name="pref_chart_sleep_lines_limit">Quante righe della sessione di sonno sono mostrate prima di scorrere</string>
+    <string name="activity_type_pool_swimming">Nuoto in piscina</string>
+    <string name="activity_type_street_dance">Danza di Strada</string>
+    <string name="menuitem_screen_always_lit">Schermo Sempre Acceso</string>
+    <string name="menuitem_wifi">Wi-Fi</string>
+    <string name="menuitem_lockscreen">Schermata di blocco</string>
+    <string name="hrZoneFatBurn">Consumo Grassi</string>
+    <string name="elevationLoss">Perdita di quota</string>
+    <string name="elevationGain">Aumento di quota</string>
+    <string name="pref_title_touch_spotify_galaxy_app">Spotify (solo app Galaxy Wearable)</string>
+    <string name="pref_screen_auto_brightness_title">Luminosit脿 Automatica</string>
+    <string name="pref_screen_auto_brightness_summary">Regola la luminosit脿 dello schermo in base alla luce ambientale</string>
+    <string name="devicetype_galaxybuds_2">Galaxy Buds2</string>
+    <string name="prefs_touch_lock_buds2">Controlli Tocco</string>
+    <string name="intent_api_broadcast_export_title">Trasmetti sull\'esportazione database</string>
+    <string name="intent_api_broadcast_export_summary">Trasmetti un intento al termine dell\'esportazione database</string>
+    <string name="supercars_lights_label">Luci</string>
+    <string name="intent_api_allow_trigger_export_title">Consenti esportazione database</string>
+    <string name="intent_api_allow_trigger_export_summary">Consenti attivazione dell\'esportazione database attraverso API intenti</string>
+    <string name="intent_api_allow_activity_sync_title">Consenti sincronizzazione attivit脿</string>
+    <string name="intent_api_allow_activity_sync_summary">Consenti l\'attivazione della sincronizzazione attivit脿 attraverso API intenti</string>
+    <string name="supercars_turbo_speed_label">Velocit脿 Turbo</string>
+    <string name="buttons_on_right">Pulsanti a destra</string>
+    <string name="pref_huami_truncate_fetch_operation_timestamps_title">Tronca i timestamp dell\'operazione di recupero</string>
+    <string name="pref_huami_truncate_fetch_operation_timestamps_summary">Tronca i timestamp delle operazioni di recupero in minuti. Disattiva questa impostazione per tenere i timestamp in secondi, se riscontri problemi durante il recupero di allenamenti molto brevi.</string>
+    <string name="pref_enable_unsupported_settings_summary">Questo abilita l\'accesso a tutte le impostazioni disponibili, anche se non supportate dal dispositivo. Ci貌 pu貌 causare instabilit脿 e arresti anomali del dispositivo.</string>
+    <string name="fw_upgrade_notice_amazfit_gts4_mini">Stai per installare il firmware %s sul tuo Amazfit GTS 4 Mini.
+\n
+\nLa band si riavvier脿 dopo l\'installazione del file .zip.
+\n
+\nPROCEDI A TUO RISCHIO E PERICOLO!</string>
+    <string name="pref_header_offline_voice">Voce Offline</string>
+    <string name="pref_title_notification_media_ignores_application_list">Lista app ignorate per notifiche media</string>
+    <string name="pref_enable_unsupported_settings_title">Attiva impostazioni non supportate</string>
+    <string name="devicetype_amazfit_gts4">Amazfit GTS 4</string>
+    <string name="devicetype_amazfit_gts4_mini">Amazfit GTS 4 Mini</string>
+    <string name="devicetype_sony_wf_1000xm4">Sony WF-1000XM4</string>
+    <string name="hrZoneAerobic">Aerobica</string>
+    <string name="aerobicTrainingEffect">Effetto Aerobico</string>
+    <string name="anaerobicTrainingEffect">Effetto Anaerobico</string>
+    <string name="currentWorkoutLoad">Carico Allenamento</string>
+    <string name="debugactivity_confirm_remove_device_preferences_title">Rimuovere le preferenze del dispositivo\?</string>
+    <string name="debugactivity_confirm_remove_device_preferences">Cos矛 si ripristineranno le preferenze per tutti i dispositivi collegati. Sei sicuro\?</string>
+    <string name="pref_alert_tone">Tono di Avviso</string>
+    <string name="pref_vibrate_for_alert">Vibra per gli Avvisi</string>
+    <string name="pref_text_to_speech">Sintesi Vocale</string>
+    <string name="pref_gps_band">Banda GPS</string>
+    <string name="pref_crown_vibration">Vibrazione Corona</string>
+    <string name="pref_gps_combination">Combinazione GPS</string>
+    <string name="pref_gps_satellite_search">Ricerca satelliti</string>
+    <string name="pref_cover_to_mute">Copri per silenziare</string>
+    <string name="offline_voice_respond_turn_wrist">Rispondi ruotando il polso</string>
+    <string name="offline_voice_respond_screen_on">Rispondi quando lo schermo 猫 attivo</string>
+    <string name="pref_workout_detection_title">Rilevamento allenamento</string>
+    <string name="seconds_25">25 secondi</string>
+    <string name="menuitem_voice_memos">Note Vocali</string>
+    <string name="menuitem_sun_moon">Sole &amp; Luna</string>
+    <string name="menuitem_offline_voice">Voce Offline</string>
+    <string name="menuitem_membership_cards">Carte Fedelt脿</string>
+    <string name="menuitem_phone">Telefono</string>
+    <string name="menuitem_one_tap_measuring">Misurazione con un tocco</string>
+    <string name="hrZoneAnaerobic">Anaerobica</string>
+    <string name="supercars_lights_blinking_label">Lampeggianti</string>
+    <string name="fw_upgrade_notice_amazfit_band7">Stai per installare il firmware %s sul tuo Amazfit Band 7.
+\n
+\nLa band si riavvier脿 dopo l\'installazione del file .zip.
+\n
+\nPROCEDI A TUO RISCHIO E PERICOLO!</string>
+    <string name="pref_summary_notification_media_ignores_application_list">Elabora le notifiche multimediali prima dell\'elenco delle app. Se non selezionata, le app multimediali devono essere abilitate nell\'elenco per far funzionare i controlli multimediali sul dispositivo.</string>
+    <string name="pref_workout_detection_sensitivity">Sensibilit脿</string>
+    <string name="pref_gps_mode_preset">Modalit脿 GPS</string>
+    <string name="pref_agps_header">AGPS</string>
+    <string name="pref_agps_expiry_reminder_enabled">Promemoria Scadenza AGPS</string>
+    <string name="pref_agps_expiry_reminder_time">Tempo Promemoria Scadenza AGPS</string>
+    <string name="pref_workout_detection_summary">Rileva allenamenti automaticamente</string>
+    <string name="pref_workout_detection_categories_title">Categorie Allenamenti</string>
+    <string name="pref_workout_detection_categories_summary">Categorie allenamenti da rilevare automaticamente</string>
+    <string name="pref_workout_detection_alert_summary">Notifica quando viene rilevato un allenamento</string>
+    <string name="pref_sleep_mode_title">Modalit脿 Sospensione</string>
+    <string name="pref_sleep_mode_sleep_screen_title">Schermata Sospensione</string>
+    <string name="pref_sleep_mode_sleep_screen_summary">Mostra la Schermata di Sospensione al risveglio dello schermo durante la modalit脿 di sospensione, per ridurre le distrazioni</string>
+    <string name="pref_workout_detection_alert_title">Avviso</string>
+    <string name="offline_voice_response_during_screen_lighting">Rispondi durante l\'illuminazione dello schermo</string>
+    <string name="pref_sleep_mode_smart_enable_summary">Attiva automaticamente la modalit脿 sospensione quando si indossa la band durante il sonno</string>
+    <string name="prefs_weardirection">Direzione d\'uso</string>
+    <string name="pref_screen_notification_profile_schedule">Pianifica</string>
+    <string name="pref_screen_notification_profile_todo_list">Lista Da Fare</string>
+    <string name="devicetype_amazfit_gtr4">Amazfit GTR 4</string>
+    <string name="devicetype_amazfit_band7">Amazfit Band 7</string>
+    <string name="devicetype_amazfit_gtr_lite">Amazfit GTR Lite</string>
+    <string name="hrZoneExtreme">Estrema</string>
+    <string name="TrainingEffect">Effetto Allenamento</string>
+    <string name="sony_speak_to_chat_sensitivity_standard">Standard</string>
+    <string name="devicetype_super_cars">Shell Racing</string>
+    <string name="appmanager_watchface_activate">Abilita</string>
+    <string name="appmanager_app_start">Avvia</string>
+    <string name="appmanager_app_download">Scarica nella cache</string>
+    <string name="appmanager_download_started">Download app avviato</string>
+    <string name="appmanager_downloaded_to_cache">%s scaricato nella cache</string>
+    <string name="pref_agps_update_time">Ora Aggiornamento AGPS</string>
+    <string name="menuitem_wechat_pay">WeChat Pay</string>
+    <string name="sony_ambient_sound_control_button_mode_nc_as_off">Cancellazione Rumore, Suono Ambiente, Spento</string>
+    <string name="sony_ambient_sound_control_button_mode_nc_as">Cancellazione Rumore, Suono Ambiente</string>
+    <string name="sony_quick_access_double_tap">Accesso Rapido (Doppio Tocco)</string>
+    <string name="sony_ambient_sound_control_button_mode_nc_off">Cancellazione Rumore, Spento</string>
+    <string name="sony_ambient_sound_control_button_mode_as_off">Suono Ambiente, Spento</string>
+    <string name="sony_quick_access_triple_tap">Accesso Rapido (Triplo Tocco)</string>
+    <string name="appmanager_download_app_error">Errore nello scaricamento</string>
+    <string name="pref_agps_expire_time">Ora Scadenza AGPS</string>
+    <string name="sony_ambient_sound_control_button_modes">Modalit脿 Pulsante Controllo Suono Ambiente</string>
+    <string name="devicetype_asteroidos">AsteroidOS</string>
+    <string name="devicetype_sony_linkbuds_s">Sony LinkBuds S</string>
+    <string name="devicetype_amazfit_trex_2">Amazfit T-Rex 2</string>
+    <string name="fw_upgrade_notice_amazfit_trex2">Stai per installare il firmware %s sul tuo Amazfit T-Rex 2.
+\n
+\nLa band si riavvier脿 dopo l\'installazione del file .zip.
+\n
+\nPROCEDI A TUO RISCHIO E PERICOLO!</string>
+    <string name="georgian">Georgiano</string>
+    <string name="pref_workout_keep_screen_on_title">Mantieni lo schermo acceso durante un allenamento</string>
+    <string name="pref_workout_detection_time">Minuti di attivit脿 prima del rilevamento</string>
+    <string name="pref_workout_detection_time_summary">Minuti di allenamento prima che questo sia rilevato</string>
+    <string name="devicetype_galaxybuds_2_pro">Galaxy Buds2 Pro</string>
+    <string name="pref_title_lock_unlock">Blocca</string>
+    <string name="pref_camera_remote_title">Telecomando Fotocamera</string>
+    <string name="pref_camera_remote_summary">Consenti all\'orologio di attivare la fotocamera del telefono</string>
+    <string name="pref_workout_keep_screen_on_summary">Lo schermo rimarr脿 acceso durante l\'allenamento e la luminosit脿 verr脿 regolata per mostrare continuamente i dati dell\'allenamento in tempo reale</string>
+    <string name="pref_workout_detection_ask_first_summary">Richiedi la conferma sull\'orologio per rilevare questo allenamento</string>
+    <string name="devicetype_soflow_s06">SoFlow SO6</string>
+    <string name="pref_workout_detection_enabled">Rilevamento attivo</string>
+    <string name="pref_workout_detection_enabled_summary">Attiva rilevamento automatico per questo allenamento</string>
+    <string name="pref_workout_detection_ask_first">Chiedimi prima</string>
+    <string name="activity_type_rowing">Canottaggio</string>
+    <string name="pref_title_notification_cache_while_disconnected">Salva se fuori portata</string>
+    <string name="pref_summary_notification_cache_while_disconnected">Invia notifiche perse quando il dispositivo si riconnette dopo essere stato fuori portata</string>
+    <string name="wifi_hotspot">Hotspot Wi-Fi</string>
+    <string name="single_tap">Tocco Singolo</string>
+    <string name="triple_tap">Triplo Tocco</string>
+    <string name="long_press">Pressione Prolungata</string>
+    <string name="quick_attention">Attenzione Rapida</string>
+    <string name="wifi_hotspot_configuration">Configurazione Hotspot Wi-Fi</string>
+    <string name="wifi_hotspot_status">Stato Hotspot Wi-Fi</string>
+    <string name="double_tap">Doppio Tocco</string>
+    <string name="yesterdays_activity">Attivit脿 di ieri</string>
+    <string name="wifi_hotspot_summary">Controlla l\'hotspot Wi-Fi sull\'orologio</string>
+    <string name="sony_button_mode_help_title">Modalit脿 pulsanti - Guida</string>
+    <string name="sony_button_mode_help_summary">Una descrizione per ciascuna modalit脿 dei pulsanti</string>
+    <string name="pref_switch_controls_anc_ambient_off">Cancellazione rumore 鈫愨啋 Ambiente 鈫愨啋 Spento</string>
+    <string name="sony_speak_to_chat_summary">Disattiva automaticamente la cancellazione del rumore quando inizi a parlare.</string>
+    <string name="croatian">Croato</string>
+    <string name="stop">Ferma</string>
+    <string name="status">Stato</string>
+    <string name="ftp_server">Server FTP</string>
+    <string name="wifi_hotspot_stop_summary">Ferma l\'hotspot Wi-Fi sull\'orologio</string>
+    <string name="ftp_server_summary">Controlla il server FTP sull\'orologio</string>
+    <string name="ftp_server_status">Stato Server FTP</string>
+    <string name="ftp_server_configuration">Configurazione Server FTP</string>
+    <string name="ftp_server_root_dir">Cartella di Root</string>
+    <string name="ftp_server_start_summary">Avvia il server FTP sull\'orologio</string>
+    <string name="ftp_server_stop_summary">Ferma il server FTP sull\'orologio</string>
+    <string name="pref_morning_updates_title">Aggiornamenti della Mattina</string>
+    <string name="pref_morning_updates_summary">Mostra aggiornamenti ogni mattina</string>
+    <string name="pref_morning_updates_categories_title">Categorie Aggiornamenti della Mattina</string>
+    <string name="pref_morning_updates_categories_summary">Lista di categorie da mostrare ogni mattina</string>
+    <string name="wifi_hotspot_start_summary">Avvia l\'hotspot Wi-Fi sull\'orologio</string>
+    <string name="address">Indirizzo</string>
+    <string name="username">Nome utente</string>
+    <string name="pref_summary_receive_calls_watch">Esegui e ricevi chiamate direttamente sull\'orologio</string>
+    <string name="bluetooth_calls_settings">Impostazioni chiamate bluetooth</string>
+    <string name="bluetooth_calls_pairing">Accoppiamento chiamate bluetooth</string>
+    <string name="bluetooth_calls">Chiamate bluetooth</string>
+    <string name="pref_display_caller_title">Mostra informazioni contatto</string>
+    <string name="pref_display_caller_summary">Visualizza numero di telefono o nome per chiamate in arrivo</string>
+    <string name="pref_pair_bluetooth_calls_title">Accoppia per chiamate bluetooth</string>
+    <string name="pref_pair_bluetooth_calls_summary">Tocca per avviare l\'accoppiamento</string>
+    <string name="pref_pair_bluetooth_calls_help_title">Come ricevere chiamate bluetooth</string>
+    <string name="pref_pair_bluetooth_calls_help_summary">Per ricevere chiamate bluetooth 猫 necessario accoppiare il telefono a una seconda istanza dell\'orologio.</string>
+    <string name="pref_pair_bluetooth_calls_help_1">1. Tocca il pulsante in basso per avviare l\'accoppiamento.</string>
+    <string name="pref_pair_bluetooth_calls_help_2">2. Vai nelle impostazioni bluetooth del telefono e associa il nuovo dispositivo visualizzato (nome simile a quello dell\'orologio corrente, ma con un suffisso, ad es. \"Amazfit GTR 4 - AFC8\".</string>
+    <string name="pref_pair_bluetooth_calls_help_3">3. Attiva l\'impostazione delle chiamate Bluetooth in basso.</string>
+    <string name="pref_pair_bluetooth_calls_help_warning">ATTENZIONE: se attivi le chiamate bluetooth senza effettuare l\'accoppiamento con la seconda istanza, le notifiche di chiamata potrebbero non funzionare correttamente.</string>
+    <string name="prefs_shortcut_cards_summary">Schede di scelta rapida visualizzate quando si scorre a destra dal quadrante. Quando un\'app 猫 in esecuzione, le schede generate automaticamente non sono influenzate da questa impostazione.</string>
+    <string name="prefs_shortcut_cards">Schede di Scelta Rapida</string>
+    <string name="menuitem_forecast">Previsione</string>
+    <string name="menuitem_last_workout">Ultimo allenamento</string>
+    <string name="menuitem_total_workout">Totale allenamenti</string>
+    <string name="menuitem_vo2_max">Max VO鈧�</string>
+    <string name="zepp_os_watchface_minimalist">Minimalista</string>
+    <string name="zepp_os_watchface_simplicity_data">Dati in Semplicit脿</string>
+    <string name="zepp_os_watchface_rush">Fretta</string>
+    <string name="zepp_os_watchface_superposition">sovrapposizione</string>
+    <string name="zepp_os_watchface_rotating_earth">Terra in Rotazione</string>
+    <string name="zepp_os_watchface_vibrant">Vivace</string>
+    <string name="continue_pressing">Tieni premuto</string>
+    <string name="zepp_os_watchface_emerald_moonlight">Chiaro di luna Smeraldo</string>
+    <string name="zepp_os_watchface_multiple_data">Dati Multipli</string>
+    <string name="zepp_os_watchface_business_style">Stile Business</string>
+    <string name="zepp_os_watchface_red_fantasy">Fantasia in Rosso</string>
+    <string name="wifi_ssid">SSID</string>
+    <string name="menuitem_cards">Schede</string>
+    <string name="menuitem_mi_ai">AI MI</string>
+    <string name="menuitem_recommendation">Consigli</string>
+    <string name="menuitem_aqi">Induce Qualit脿 dell\'Aria</string>
+    <string name="fw_upgrade_notice_amazfit_gtr3_pro">Stai per installare il firmware %s sul tuo Amazfit GTR 3 Pro.
+\n
+\nLa band si riavvier脿 dopo l\'installazione del file .zip.
+\n
+\nPROCEDI A TUO RISCHIO E PERICOLO!</string>
+    <string name="devicetype_sony_wh_1000xm5">Sony WH-1000XM5</string>
+    <string name="devicetype_amazfit_gtr3_pro">Amazfit GTR 3 Pro</string>
+    <string name="debug_companion_pair_current">Accoppia il dispositivo corrente</string>
+    <string name="gpx_route_upload_failed">Caricamento traccia GPX fallito</string>
+    <string name="gpx_route_upload_complete">Caricamento traccia GPX completato</string>
+    <string name="gpx_route_upload_in_progress">Caricamento traccia GPX</string>
+    <string name="activity_detail_share_gps_label">Condividi traccia GPX</string>
+    <string name="intent_api_allow_debug_commands_title">Consenti Comandi di Debug</string>
+    <string name="contact_name">Nome</string>
+    <string name="fossil_hr_pairing_successful">Accoppiamento con l\'orologio riuscito</string>
+    <string name="fossil_hr_pairing_failed">Accoppiamento con l\'orologio fallito</string>
+    <string name="intent_api_allow_debug_commands_summary">Consenti di attivare comandi del menu di debug tramite l\'API Intenti</string>
+    <string name="fossil_hr_confirm_connection">Conferma sull\'orologio</string>
+    <string name="fossil_hr_connection_not_confirmed">Connessione non confermata sull\'orologio, modalit脿 non autenticata attiva</string>
+    <string name="fossil_hr_confirmation_timeout">Timeout di conferma, continua</string>
+    <string name="fossil_hr_confirmation_skipped">Conferma sul dispositivo saltata</string>
+    <string name="contact_phone_number">Numero di telefono</string>
+    <string name="contact_missing_number">Il numero del contatto 猫 vuoto</string>
+    <string name="contact_missing_name">Il nome del contatto 猫 vuoto</string>
+    <string name="title_activity_set_contacts">Configura contatti</string>
+    <string name="pref_contacts_title">Contatti</string>
+    <string name="pref_contacts_summary">Configura contatti sull\'orologio</string>
+    <string name="contact_delete_confirm_title">Elimina contatto</string>
+    <string name="contact_delete_confirm_description">Sei sicuro di voler eliminare \'%1$s\'\?</string>
+    <string name="contact_no_free_slots_description">Il dispositivo non ha campi liberi per contatti (campi totali: %1$s)</string>
+    <string name="dev_tools">Strumenti Sviluppatori</string>
+    <string name="debug_companion_show_associated">Mostra i dispositivi associati</string>
+    <string name="activity_detail_share_raw_details">Condividi Dettagli Raw</string>
+    <string name="activity_detail_share_raw_summary">Condividi Riepilogo Raw</string>
+    <string name="pref_header_navigation">Navigazione</string>
+    <string name="title_activity_contact_details">Dettagli contatto</string>
+    <string name="activity_error_share_failed">Condivisione file fallita.</string>
+    <string name="kind_gpx_route">Traccia GPX</string>
+    <string name="pref_title_navigation_forward">Invia navigazione all\'orologio</string>
+    <string name="pref_summary_navigation_forward">Inoltra istruzioni da app di navigazione all\'orologio</string>
+    <string name="action_changelog">Changelog</string>
+    <string name="num_min">%d min</string>
+    <string name="pai_plus_num">+%d</string>
+    <string name="changelog_ok_button">OK</string>
+    <string name="withings_bt_calibration_previous">Precedente</string>
+    <string name="withings_bt_calibration_next">Successivo</string>
+    <string name="devicetype_vivomove_hr">Garmin Vivomove HR</string>
+    <string name="watchface_widget_type_uv_index">Indice UV</string>
+    <string name="stress_relaxed">Rilassato</string>
+    <string name="stress_high">Elevato</string>
+    <string name="loyalty_cards_catima_package">Nome pacchetto Catima</string>
+    <string name="loyalty_cards_install_catima_fail">Non siamo riusciti ad aprire l\'app store per installare Catima</string>
+    <string name="loyalty_cards_catima_not_installed">Catima 猫 necessario per gestire le carte fedelt脿</string>
+    <string name="loyalty_cards_catima_permissions_title">Permessi mancanti</string>
+    <string name="loyalty_cards_open_catima">Apri Catima</string>
+    <string name="loyalty_cards_syncing">Sincronizzando %d carte con il dispositivo</string>
+    <string name="title">Titolo</string>
+    <string name="pref_summary_banglejs_phone_gps_network_only">Usa solo il fornitore di rete per determinare la posizione. Questo riduce il consumo di energia al costo della precisione. 脠 necessaria una riconnessione.</string>
+    <string name="pref_title_banglejs_phone_gps_update_interval">Intervallo di aggiornamento dati GPS</string>
+    <string name="pref_summary_banglejs_phone_gps_update_interval">Intervallo per quanto frequentemente aggiornare la posizione, in ms</string>
+    <string name="busy_task_fetch_sports_details">Recuperando i dettagli degli sport</string>
+    <string name="busy_task_fetch_sports_summaries">Recuperando i riepiloghi degli sport</string>
+    <string name="busy_task_fetch_debug_logs">Recuperando log di debug</string>
+    <string name="busy_task_fetch_stress_data">Recuperando dati sullo stress</string>
+    <string name="busy_task_fetch_spo2_data">Recuperando dati SpO2</string>
+    <string name="busy_task_fetch_hr_data">Recuperando dati frequenza cardiaca</string>
+    <string name="busy_task_fetch_sleep_respiratory_rate_data">Recuperando dati della frequenza respiratoria nel sonno</string>
+    <string name="fwapp_install_device_not_supported">Installazione file fallita, dispositivo non supportato.</string>
+    <string name="devicetype_casiogmwb5000">Casio GMW-B5000</string>
+    <string name="status_icon">Icona stato</string>
+    <string name="changelog_full_title">Registro modifiche</string>
+    <string name="loyalty_cards_install">Installa Catima</string>
+    <string name="loyalty_cards_catima_permissions_summary">Gadgetbridge ha bisogno del permesso per leggere le carte Catima per sincronizzarle. Toccare questo pulsante per concederlo.</string>
+    <string name="loyalty_cards_catima">Catima</string>
+    <string name="loyalty_cards">Carte Fedelt脿</string>
+    <string name="changelog_show_full">Altro鈥�</string>
+    <string name="changelog_title">Novit脿</string>
+    <string name="voice_service_class_title">Classe servizi vocali</string>
+    <string name="voice_service_package_summary">Applicazione contenente i servizi per gestire i comandi vocali</string>
+    <string name="voice_service_class_summary">Percorso completo del servizio che gestisce i comandi vocali</string>
+    <string name="pref_app_logs_start_summary">Inizia i log per le applicazioni dell\'orologio</string>
+    <string name="pref_app_logs_summary">Abilita i log per le applicazioni dell\'orologio</string>
+    <string name="loyalty_cards_sync_groups_only">Sincronizza solo alcuni gruppi</string>
+    <string name="loyalty_cards_sync_groups">Gruppi da Sincronizzare</string>
+    <string name="pref_title_mb_intents">Trasmetti Direttamente Intenti Pulsanti Media</string>
+    <string name="pref_summary_mb_intents">Attiva se i controlli multimediali del dispositivo non funzionano correttamente con alcune applicazioni</string>
+    <string name="loyalty_cards_sync_summary">Tocca per sincronizzare le carte con l\'orologio</string>
+    <string name="loyalty_cards_sync_options">Opzioni Sincronizzazione</string>
+    <string name="loyalty_cards_sync">Sincronizza</string>
+    <string name="loyalty_cards_catima_not_compatible">La versione di Catima installata non 猫 compatibile con Gadgetbridge. Aggiorna Catima e Gadgetbridge alle ultime versioni.</string>
+    <string name="activity_prefs_goals">Obiettivi</string>
+    <string name="charts_legend_stress_average">Stress medio</string>
+    <string name="error_no_bluetooth_scan">L\'accesso alla scansione Bluetooth deve essere consentito e abilitato perch茅 la scansione funzioni correttamente</string>
+    <string name="error_no_bluetooth_connect">L\'accesso alla connessione Bluetooth deve essere consentito e abilitato perch茅 la scansione funzioni correttamente</string>
+    <string name="activity_type_surfing">Surf</string>
+    <string name="activity_type_windsurfing">Windsurf</string>
+    <string name="activity_type_rugby">Rugby</string>
+    <string name="activity_type_football">Football americano</string>
+    <string name="activity_type_kitesurfing">Kite</string>
+    <string name="activity_type_baseball">Baseball</string>
+    <string name="activity_type_squash">Squash</string>
+    <string name="activity_type_handball">Pallamano</string>
+    <string name="activity_type_tennis">Tennis</string>
+    <string name="activity_type_weightlifting">Sollevamento pesi</string>
+    <string name="activity_type_dancing">Danza</string>
+    <string name="activity_type_skiing">Sci</string>
+    <string name="activity_type_snowboarding">Snowboard</string>
+    <string name="activity_type_riding">Equitazione</string>
+    <string name="activity_type_hockey">Hockey</string>
+    <string name="activity_type_icehockey">Hockey su ghiaccio</string>
+    <string name="activity_type_iceskating">Pattinaggio su ghiaccio</string>
+    <string name="activity_type_golf">Golf</string>
+    <string name="activity_type_other">Altro</string>
+    <string name="share_screenshot">Condividi schermata</string>
+    <string name="screenshot_taken">Schermata acquisita</string>
+    <string name="devicetype_casiogwb5600">Casio GW-B5600</string>
+    <string name="permission_request">%1$s consente di inviare messaggi e altri dati da Android al dispositivo. Per fare questo richiede il permesso di accedere a tali dati e senza, potrebbe non funzionare correttamente.
+\n
+\nOra vedrai alcune finestre di dialogo Android che richiedono queste autorizzazioni.
+\n
+\nPremi %2$s per continuare.</string>
+    <string name="voice_service">Servizi Vocali</string>
+    <string name="pref_app_logs_stop_summary">Termina i log per le applicazioni dell\'orologio</string>
+    <string name="description">Descrizione</string>
+    <string name="preview_image">Immagine anteprima</string>
+    <string name="loyalty_cards_sync_archived">Sincronizza carte archiviate</string>
+    <string name="loyalty_cards_sync_title">Sincronizza Carte Fedelt脿</string>
+    <string name="voice_service_package_title">Pacchetto servizi vocali</string>
+    <string name="pref_app_logs_title">Registri app</string>
+    <string name="loyalty_cards_sync_starred">Sincronizza solo carte preferite</string>
+    <string name="pref_title_banglejs_phone_gps_network_only">Usa solo la rete per determinare la posizione</string>
+    <string name="pref_title_banglejs_phone_gps_enbale">Usa dati gps del telefono</string>
+    <string name="pref_summary_banglejs_phone_gps_enbale">Usa i dati gps del telefono per sovrascrivere i dati gps del dispositivo bangle</string>
+    <string name="stress_moderate">Moderato</string>
+    <string name="stress_mild">Leggero</string>
+    <string name="pref_theme_dynamic">Colori dinamici</string>
+    <string name="withings_steel_hr">Withings Steel HR</string>
+    <string name="preferences_miband_1_2_settings" tools:ignore="TypographyFractions">Impostazioni Mi Band 1/2</string>
+    <string name="preferences_miband_1_2_warning">Attenzione: Queste preferenze si applicano solo a Mi Band 1 e 2.</string>
+    <string name="pref_activity_full_sync_trigger_summary">Avvia una sincronizzazione completa dei dati attivit脿</string>
+    <string name="pref_activity_full_sync_trigger_title">Sincronizzazione completa</string>
+    <string name="pref_show_changelog">Mostra il changelog all\'avvio</string>
+    <string name="pref_show_changelog_summary">Visualizza il changelog dall\'ultima versione dopo l\'aggiornamento di Gadgetbridge</string>
+    <string name="find_my_phone_found_it">TROVATO</string>
+    <string name="pref_activity_full_sync_trigger_warning">Questo avvier脿 una sincronizzazione completa di tutti i dati di attivit脿 sul dispositivo. Potrebbe richiedere alcuni minuti.</string>
+    <string name="pref_canned_message">Messaggio</string>
+    <string name="devicetype_amazfit_cheetah_pro">Amazfit Cheetah Pro</string>
+    <string name="devicetype_amazfit_bip3_pro">Amazfit Bip 3 Pro</string>
+    <string name="fw_upgrade_notice_amazfitbip3pro">Stai per installare il firmware %s sul tuo Amazfit Bip 3 Pro.
+\n
+\nAssicurati di installare prima il file .fw e dopo il file .res. L\'orologio si riavvier脿 al termine del caricamento del file .fw.
+\n
+\nNota: non 猫 necessario installare il file .res se 猫 uguale a quello installato in precedenza.
+\n
+\nPROCEDI A TUO RISCHIO E PERICOLO!</string>
+    <string name="fw_upgrade_notice_amazfit_cheetah_pro">Stai per installare il firmware %s sul tuo Amazfit Cheetah Pro.
+\n
+\nLa band si riavvier脿 dopo l\'installazione del file .zip.
+\n
+\nPROCEDI A TUO RISCHIO E PERICOLO!</string>
+    <string name="pref_theme_dynamic_colors_not_available_warning">I colori dinamici non sono disponibili sul tuo dispositivo, solo Android 12+ supporta questa funzionalit脿. Gadgetbridge user脿 i colori predefiniti di Material 3.</string>
+    <string name="pref_theme_dynamic_colors_explanation">Nota: per il tema con colori dinamici 猫 necessario attivare Colori sfondo o Tavolozza colori nelle impostazioni di aspetto di Android 12+. Altrimenti, Gadgetbridge user脿 i colori predefiniti di Material 3.</string>
+    <string name="drag_handle">Trascina manopola</string>
+    <string name="withings_calibration_text_activity_target">Infine, allinea la lancetta delle attivit脿 al 100%. Tieni presente che questa lancetta si muove solo in senso orario.</string>
+    <string name="english_au">Inglese (Australia)</string>
+    <string name="english_in">Inglese (India)</string>
+    <string name="english_ca">Inglese (Canada)</string>
+    <string name="english_gb">Ingledd (Regno Unito)</string>
+    <string name="english_us">Inglese (Stati Uniti)</string>
+    <string name="spanish_mx">Spagnolo (Messico)</string>
+    <string name="spanish_us">Spagnolo (Stati Uniti)</string>
+    <string name="devicetype_amazfit_bip5">Amazfit Bip 5</string>
+    <string name="spanish_es">Spagnolo (Spagna)</string>
+    <string name="french_ca">Francese (Canada)</string>
+    <string name="devicetype_amazfit_cheetah_round">Amazfit Cheetah (Tondo)</string>
+    <string name="devicetype_amazfit_trex_ultra">Amazfit T-Rex Ultra</string>
+    <string name="french_fr">Francese (Francia)</string>
+    <string name="devicetype_amazfit_gtr_mini">Amazfit GTR Mini</string>
+    <string name="withings_calibration_text_minutes">Ora, utilizzare il quadrante per allineare la lancetta dei minuti alle 12.</string>
+    <string name="withings_calibration_text_hours">Usa il quadrante sottostante per allineare la lancetta delle ore alle 12.</string>
+    <string name="fw_upgrade_notice_zepp_os">Stai per installare il firmware %s sul tuo %s.
+\n
+\nL\'orologio si riavvier脿 dopo l\'installazione del file .zip.
+\n
+\nPROCEDI A TUO RISCHIO E PERICOLO!</string>
+    <string name="devicetype_amazfit_cheetah_square">Amazfit Cheetah (Quadrato)</string>
+    <string name="device_experimental">SPERIMENTALE</string>
+    <string name="zepp_os_watchface_pure_white">Bianco puro</string>
+    <string name="menuitem_headphone">Auricolari</string>
+    <string name="femometer_measurement_mode_normal">Modalit脿 normale (60s-90s)</string>
+    <string name="updatefirmwareoperation_updateproblem_free_space">Il dispositivo non dispone di spazio libero sufficiente</string>
+    <string name="prefs_home_icon_left_item">Sinistra</string>
+    <string name="updatefirmwareoperation_failed_low_mtu">L\'MTU attuale di %1$d 猫 troppo basso, attiva un MTU elevato nelle impostazioni del dispositivo e scollega/ricollega il dispositivo.</string>
+    <string name="prefs_wena3_title_activity">Impostazioni Attivit脿</string>
+    <string name="white">Bianco</string>
+    <string name="femometer_measurement_mode_quick">Modalit脿 rapida (30s)</string>
+    <string name="pref_title_notifications_ignore_work_profile">Ignora notifiche del profilo di lavoro</string>
+    <string name="prefs_home_icon_right_item">Destra</string>
+    <string name="prefs_wena3_title_screen">Impostazioni Schermo</string>
+    <string name="femometer_measurement_mode_title">Modalit脿 di misurazione</string>
+    <string name="prefs_wena3_item_large_font">Dimensione Carattere pi霉 grande</string>
+    <string name="prefs_wena3_hint_large_font">Aumenta la dimensione dei caratteri nel calendario, notifiche, ecc.</string>
+    <string name="text_receiver_activity_title">Invia testo al dispositivo</string>
+    <string name="femometer_measurement_mode_precise">Modalit脿 precisa (3min)</string>
+    <string name="green">Verde</string>
+    <string name="yellow">Giallo</string>
+    <string name="menuitem_workout_shortcuts">Scorciatoie allenamento</string>
+    <string name="red">Rosso</string>
+    <string name="cyan">Ciano</string>
+    <string name="menuitem_apps_shortcuts">Scorciatoie app</string>
+    <string name="pref_summary_notifications_ignore_work_profile">Non inviare all\'orologio le notifiche delle app del profilo di lavoro</string>
+    <string name="latvian">Lettone</string>
+    <string name="prefs_home_icon_center_item">Centro</string>
+    <string name="menuitem_thermometer">Termometro</string>
+    <string name="prefs_wena3_item_rich_design">Utilizzare Design Ricco</string>
+    <string name="common_symbols">Simboli Comuni</string>
+    <string name="purple">Viola</string>
+    <string name="blue">Blu</string>
+    <string name="prefs_wena3_menu_icon_hint">L\'icona Impostazioni 猫 sempre visualizzata alla fine</string>
+    <string name="info_menu_structure_contents">Struttura menu: %s</string>
+    <string name="prefs_wena3_home_icon_name_calories">Calorie</string>
+    <string name="prefs_wena3_vibration_smart_item">Vibrazione Intelligente</string>
+    <string name="pref_message_privacy_mode_bodyonly">Nascondi solo il corpo</string>
+    <string name="prefs_wena3_hint_background_sync">Consenti a Wena di chiedere periodicamente a Gadgetbridge di scaricare i dati di attivit脿 dal dispositivo</string>
+    <string name="prefs_wena3_vibration_strength_item_medium">Media</string>
+    <string name="error_invalid_menu_structure">Struttura menu JSON invalida</string>
+    <string name="prefs_wena3_home_icon_name_pedometer">Contapassi</string>
+    <string name="devicetype_miband2_hrx">Mi Band HRX</string>
+    <string name="busy_task_fetch_statistics">Recupero statistiche</string>
+    <string name="prefs_wena3_home_icon_name_clock">Ora Corrente</string>
+    <string name="prefs_wena3_item_background_sync">Sincronizzazione Dati Attivit脿 in Background</string>
+    <string name="prefs_wena3_vibration_strength_item_weak">Debole</string>
+    <string name="prefs_wena3_home_icon_title">Icone Schermata Iniziale</string>
+    <string name="devicetype_bohemic_smart_bracelet">Bohemic Smart Bracelet</string>
+    <string name="zepp_os_watchface_starry_sky">Cielo stellato</string>
+    <string name="devicetype_amazfit_balance">Amazfit Balance</string>
+    <string name="prefs_wena3_item_weather_statusbar">Meteo Nella Barra Di Stato</string>
+    <string name="temperature_scale_celsius">Centigradi</string>
+    <string name="prefs_wena3_home_icon_name_energy">Energia Corporea</string>
+    <string name="prefs_wena3_hint_lift_wrist">Accendi il display quando guardi il polso</string>
+    <string name="button_reset_menu_structure">Reimposta struttura menu</string>
+    <string name="prefs_wena3_button_action_item_double">Doppia Pressione</string>
+    <string name="devicetype_amazfit_falcon">Amazfit Falcon</string>
+    <string name="devicetype_amazfit_active_edge">Amazfit Active Edge</string>
+    <string name="busy_task_fetch_temperature">Recupero dati temperatura</string>
+    <string name="prefs_wena3_hint_weather_statusbar">Mostra le condizioni meteo attuali nell\'angolo in alto a sinistra della schermata iniziale</string>
+    <string name="devicetype_sony_wena3">Sony Wena 3</string>
+    <string name="prefs_wena3_menu_icon_title">Icone Menu</string>
+    <string name="charts_legend_spo2_average">Media ossigeno nel sangue</string>
+    <string name="temperature_scale_fahrenheit">Fahrenheit</string>
+    <string name="prefs_wena3_vibration_strength_item">Intensit脿 Vibrazione</string>
+    <string name="prefs_wena3_button_action_item_long">Pressione Prolungata</string>
+    <string name="prefs_wena3_menu_icon_name_payment">Pagamento</string>
+    <string name="prefs_wena3_title_alarm">Impostazioni Sveglia</string>
+    <string name="info_menu_structure_removed">Struttura menu rimossa</string>
+    <string name="prefs_wena3_hint_rich_design">Aggiungi rettangoli arrotondati intorno alle icone della schermata iniziale</string>
+    <string name="info_fossil_rebuild_watchface_custom_menu">Ricompila il quadrante dell\'orologio per il menu personalizzato</string>
+    <string name="devicetype_amazfit_active">Amazfit Active</string>
+    <string name="info_menu_structure_set">Struttura menu JSON impostata in GB</string>
+    <string name="prefs_wena3_vibration_strength_item_strong">Forte</string>
+    <string name="prefs_wena3_button_action_name_activity_screen">Schermta Attivit脿</string>
+    <string name="prefs_wena3_notification_settings_title">Impostazioni Notifiche</string>
+    <string name="devicetype_sony_wf_1000xm5">Sony WF-1000XM5</string>
+    <string name="devicetype_femometer_vinca2">Femometer Vinca II</string>
+    <string name="pai_chart_per_week">PAI settimanale</string>
+    <string name="pai_chart_per_month">PAI mensile</string>
+    <string name="weekstepschart_steps_a_week_or_month">Passi mensili/settimanali</string>
+    <string name="weeksleepchart_sleep_a_week_or_month">Sono mensile/settimanale</string>
+    <string name="dnd_all_day">Tutto il giorno</string>
+    <string name="busy_task_fetch_sports_details_interrupted">Il recupero dei dettagli sportivi 猫 stato interrotto</string>
+    <string name="heartrate_bpm_165">165 bpm</string>
+    <string name="heartrate_bpm_175">175 bpm</string>
+    <string name="heartrate_bpm_185">185 bpm</string>
+    <string name="pref_title_casio_alert_email">Avviso per notifiche e-mail</string>
+    <string name="pref_summary_casio_alert_email">Avviso (vibrazione/bip) per le notifiche via e-mail</string>
+    <string name="pref_title_casio_alert_sms">Avviso per notifiche SMS</string>
+    <string name="pref_summary_casio_alert_sms">Avviso (vibrazione/bip) per le notifiche SMS (messaggi di testo)</string>
+    <string name="pref_title_casio_alert_other">Avviso per altre notifiche</string>
+    <string name="pref_summary_casio_alert_other">Avviso (vibrazione/bip) per le notifiche con altre categorie</string>
+    <string name="busy_task_fetch_pai_data">Recupero dati PAI</string>
+    <string name="manual">Manuale</string>
+    <string name="updatefirmwareoperation_updateproblem_low_battery">Il dispositivo 猫 troppo scarico</string>
+    <string name="clap_hands_to_wakeup_device">Batti le mani per attivare lo schermo</string>
+    <string name="clap_hands_to_wakeup_device_summary">Battere di nuovo le mani spegner脿 lo schermo</string>
+    <string name="pixoo_power_saving_summary">Lo schermo si spegner脿 se il microfono rileva silenzio per un certo periodo</string>
+    <string name="prefs_wearmode">Modalit脿 uso</string>
+    <string name="wearmode_pebble">Pebble (fibbia per scarpe)</string>
+    <string name="wearmode_necklace">Collana (laccetto collo)</string>
+    <string name="danish">Danese</string>
+    <string name="pref_title_general_reconnectonlytoconnected">Riconnetti solo ai dispositivi collegati</string>
+    <string name="pref_summary_general_reconnectonlytoconnected">Riconnetti solo ai dispositivi collegati, invece di riconnettersi a tutti i dispositivi</string>
+    <string name="pref_title_preview_message_in_title">Mostra anteprima del messaggio nel titolo</string>
+    <string name="pref_summary_preview_message_in_title">Mostra un\'anteprima del messaggio nel titolo di una notifica, come consentito dal dispositivo</string>
+    <string name="pref_title_casio_alert_call">Avviso per chiamate in arrivo</string>
+    <string name="heartrate_bpm_195">195 bpm</string>
+    <string name="wearmode_band">Cinturino (polsino)</string>
+    <string name="pref_title_send_app_notifications">Invia notifiche</string>
+    <string name="pref_summary_send_app_notifications">Inviare le notifiche delle app al dispositivo</string>
+    <string name="alarm_smart_wakeup_interval_default">5 minuti</string>
+    <string name="heartrate_bpm_155">155 bpm</string>
+    <string name="heartrate_bpm_205">205 bpm</string>
+    <string name="prefs_device_name">Nome dispositivo</string>
+    <string name="pref_title_goal_secondary">Obiettivo secondario</string>
+    <string name="pref_summary_casio_alert_call">Avviso (vibrazione/bip) per le chiamate in arrivo</string>
+    <string name="pref_title_casio_alert_calendar">Avviso per notifiche del calendario</string>
+    <string name="pref_summary_casio_alert_calendar">Avviso (vibrazione/bip) per le notifiche del calendario</string>
+    <string name="fw_upgrade_notice_amazfitbip3">Stai per installare il firmware %s sul tuo Amazfit Bip 3.
+\n
+\nAssicurati di installare prima il file .fw e dopo il file .res. L\'orologio si riavvier脿 al termine del caricamento del file .fw.
+\n
+\nNota: non 猫 necessario installare il file .res se 猫 uguale a quello installato in precedenza.
+\n
+\nPROCEDI A TUO RISCHIO E PERICOLO!</string>
+    <string name="pref_header_generic">Generico</string>
+    <string name="pref_header_audio">Suono</string>
+    <string name="activity_type_darts">Freccette</string>
+    <string name="activity_type_kite_flying">Aquilone</string>
+    <string name="devicetype_miband7pro">Xiaomi Smart Band 7 Pro</string>
+    <string name="devicetype_colacao21">ColaCao 2021</string>
+    <string name="hydration_dnd_summary">Disattivare avvisi di idratazione per un intervallo di tempo</string>
+    <string name="silent_mode_vibrate_silent">Vibrazione / Silenzioso</string>
+    <string name="activity_type_mountain_hike">Escursione in montagna</string>
+    <string name="activity_type_fitness_exercises">Esercizi fitness</string>
+    <string name="activity_type_cross_country_running">Corsa campestre</string>
+    <string name="activity_type_karate">Karate</string>
+    <string name="activity_type_cooldown">Raffreddamento</string>
+    <string name="activity_type_cross_training">Allenamento incrociato</string>
+    <string name="activity_type_plank">Plancia</string>
+    <string name="activity_type_aerobic_exercise">Esercizi aerobici</string>
+    <string name="activity_type_javelin">Giavellotto</string>
+    <string name="activity_type_long_jump">Salto in lungo</string>
+    <string name="activity_type_high_jump">Salto in alto</string>
+    <string name="activity_type_trampoline">Trampolino</string>
+    <string name="activity_type_dumbbell">Pesi</string>
+    <string name="activity_type_tai_chi">Tai chi</string>
+    <string name="activity_type_hula_hooping">Hula hoop</string>
+    <string name="activity_type_disc_sports">Lancio del disco</string>
+    <string name="activity_type_shuttlecock">Volano</string>
+    <string name="activity_type_archery">Tiro con l\'arco</string>
+    <string name="activity_type_softball">Softball</string>
+    <string name="activity_type_sailing">Vela</string>
+    <string name="activity_type_ice_hockey">Hockey su ghiaccio</string>
+    <string name="activity_type_dodgeball">Palla prigioniera</string>
+    <string name="activity_type_skateboarding">Skateboard</string>
+    <string name="activity_type_rock_climbing">Arrampicata su roccia</string>
+    <string name="activity_type_hunting">Caccia</string>
+    <string name="activity_type_trail_run">Corsa su sentiero</string>
+    <string name="devicetype_huawei_watchfit">Huawei Watch Fit</string>
+    <string name="devicetype_huawei_watchultimate">Huawei Watch Ultimate</string>
+    <string name="menuitem_zepp_pay">Zepp Pay</string>
+    <string name="zepp_os_watchface_guider">Guida</string>
+    <string name="menuitem_zepp_coach">Zepp Coach</string>
+    <string name="prefs_disconnect_notification_summary">Notifica sul dispositivo quando viene disconnesso dal BT.</string>
+    <string name="prefs_phone_silent_mode">Telefono in silenzioso</string>
+    <string name="pref_do_not_disturb_not_wear">Non disturbare quando non indossato</string>
+    <string name="devicetype_amazfit_bip3">Amazfit Bip 3</string>
+    <string name="devicetype_huawei_talk_band_b6">Huawei Talk Band B6</string>
+    <string name="devicetype_huawei_watchgt2e">Huawei Watch GT 2e</string>
+    <string name="pref_title_notification_wake_on_open">Risveglio e sblocco automatico</string>
+    <string name="pref_summary_notification_wake_on_open">Risveglia e sblocca il dispositivo Android quando il gadget invia una risposta OPEN. Deve essere segnato come affidabile.</string>
+    <string name="activity_type_crossfit">Crossfit</string>
+    <string name="activity_type_functional_training">Allenamento funzionale</string>
+    <string name="activity_type_physical_training">Allenamento fisico</string>
+    <string name="activity_type_taekwondo">Taekwondo</string>
+    <string name="activity_type_fencing">Scherma</string>
+    <string name="activity_type_kendo">Kendo</string>
+    <string name="devicetype_mijia_lywsd03">Sensore di temperatura e umidit脿 Mijia 2</string>
+    <string name="devicetype_mijia_mho_c303">Mijia MHO-C303</string>
+    <string name="devicetype_sony_linkbuds">Sony LinkBuds</string>
+    <string name="devicetype_honor_band3">Honor Band 3</string>
+    <string name="devicetype_honor_band6">Honor Band 6</string>
+    <string name="devicetype_huawei_band8">Huawei Band 8</string>
+    <string name="devicetype_huawei_watch_gt">Huawei Watch GT</string>
+    <string name="devicetype_huawei_band4pro">Huawei Band 4 (Pro)</string>
+    <string name="devicetype_huawei_watchgt3">Huawei Watch GT 3 (Pro)</string>
+    <string name="devicetype_huawei_watchgt4">Huawei Watch GT 4</string>
+    <string name="devicetype_xiaomi_watch_lite">Xiaomi Watch Lite</string>
+    <string name="devicetype_redmiwatch3active">Redmi Watch 3 Active</string>
+    <string name="devicetype_redmiwatch3">Redmi Watch 3</string>
+    <string name="devicetype_redmi_smart_band_2">Redmi Smart Band 2</string>
+    <string name="prefs_heartrate_alert_active_high_threshold">Soglia di allarme per elevata frequenza cardiaca</string>
+    <string name="activity_type_flexibility">Flessibilit脿</string>
+    <string name="activity_type_track_and_field">Atletica leggera</string>
+    <string name="activity_type_pull_ups">Trazioni</string>
+    <string name="activity_type_jazz_dance">Danza jazz</string>
+    <string name="activity_type_latin_dance">Danza latina</string>
+    <string name="activity_type_kabaddi">Kabaddi</string>
+    <string name="activity_type_mind_and_body">Corpo e mente</string>
+    <string name="activity_type_karting">Go-kart</string>
+    <string name="activity_type_australian_football">Football australiano</string>
+    <string name="activity_type_lacross">Lacrosse</string>
+    <string name="activity_type_shot">Tiro</string>
+    <string name="activity_type_skating">Pattinaggio</string>
+    <string name="devicetype_miband8">Xiaomi Smart Band 8</string>
+    <string name="devicetype_miband8pro">Xiaomi Smart Band 8 Pro</string>
+    <string name="devicetype_honor_magicwatch2">Honor MagicWatch 2</string>
+    <string name="devicetype_huawei_band6">Huawei Band 6</string>
+    <string name="zepp_os_watchface_the_ultima">L\'ultima</string>
+    <string name="devicetype_honor_band4">Honor Band 4</string>
+    <string name="devicetype_honor_band5">Honor Band 5</string>
+    <string name="devicetype_honor_band7">Honor Band 7</string>
+    <string name="silent_mode_normal_silent">Normale / Silenzioso</string>
+    <string name="silent_mode_normal_vibrate">Normale / Vibrazione</string>
+    <string name="activity_type_wrestling">Lotta</string>
+    <string name="devicetype_colacao23">ColaCao 2023</string>
+    <string name="devicetype_miband8active">Xiaomi Smart Band 8 Active</string>
+    <string name="devicetype_huawei_band_aw70">Huawei Band (AW70)</string>
+    <string name="armenian">Armeno</string>
+    <string name="activity_type_indoor_running">Corsa al coperto</string>
+    <string name="activity_type_free_training">Allenamento libero</string>
+    <string name="activity_type_rower">Vogatore</string>
+    <string name="activity_type_stair_stepper">Stepper</string>
+    <string name="activity_type_horizontal_bar">Barre orizzontali</string>
+    <string name="activity_type_parallel_bar">Barre parallele</string>
+    <string name="activity_type_sit_ups">Addominali</string>
+    <string name="activity_type_push_ups">Flessioni</string>
+    <string name="activity_type_battle_rope">Tiro della fune</string>
+    <string name="activity_type_belly_dance">Danza del ventre</string>
+    <string name="activity_type_ballet">Balletto</string>
+    <string name="activity_type_other_dance">Altre danze</string>
+    <string name="activity_type_roller_skating">Pattini a rotelle</string>
+    <string name="activity_type_martial_arts">Arti marziali</string>
+    <string name="activity_type_horse_riding">Equitazione</string>
+    <string name="activity_type_swing">Altalena</string>
+    <string name="activity_type_stairs">Scale</string>
+    <string name="activity_type_fishing">Pesca</string>
+    <string name="activity_type_hand_cycling">Ciclismo manuale</string>
+    <string name="activity_type_billiards">Bigliardo</string>
+    <string name="activity_type_cross_country_skiing">Sci di fondo</string>
+    <string name="activity_type_snow_sports">Sport invernali</string>
+    <string name="activity_type_outdoor_walking">Camminata all\'estero</string>
+    <string name="devicetype_huawei_band7">Huawei Band 7</string>
+    <string name="devicetype_huawei_watchgt2">Huawei Watch GT 2 (Pro)</string>
+    <string name="devicetype_redmi_watch_2">Redmi Watch 2</string>
+    <string name="devicetype_redmi_smart_band_pro">Redmi Smart Band Pro</string>
+    <string name="devicetype_redmi_watch_4">Redmi Watch 4</string>
+    <string name="menuitem_readiness">Preparazione</string>
+    <string name="menuitem_body_composition">Composizione corporea</string>
+    <string name="menuitem_buzzer_intensity">Intensit脿 cicalino</string>
+    <string name="alarm_smart_wakeup_interval">Intervallo di attivazione intelligente:</string>
+    <string name="standing_time">Tempo di riposo</string>
+    <string name="devicetype_redmi_watch_2_lite">Redmi Watch 2 Lite</string>
+    <string name="prefs_password_4_digits_0_to_9_summary">La password deve essere di 4 cifre, utilizzando solo numeri</string>
+    <string name="active_time">Tempo di attivit脿</string>
+    <string name="devicetype_sony_wi_sp600n">Sony WI-SP600N</string>
+    <string name="swolfAvg">Swolf medio</string>
+    <string name="swolfMax">Swolf massimo</string>
+    <string name="swolfMin">Swolf minimo</string>
+    <string name="zepp_os_watchface_lightning_flash">Lampo di luce</string>
+    <string name="yard">iarda</string>
+    <string name="stepLengthAvg">Lunghezza media dei passi</string>
+    <string name="pref_header_sony_sound_control">Controllo sonoro</string>
+    <string name="pai_total">Totale</string>
+    <string name="sony_protocol_v3">Versione 3</string>
+    <string name="protocol_version">Versione protocollo</string>
+    <string name="maxStrokeRate">Frequenza Massima Corse</string>
+    <string name="strokes">Corse Totali</string>
+    <string name="pref_device_action_dnd_off">Non disturbare - Spento</string>
+    <string name="activity_type_curling">Curling</string>
+    <string name="devicetype_nothingear2">Nothing Ear (2)</string>
+    <string name="do_not_disturb_lift_wrist_summary">Solo se l\'attivazione del display al sollevamento 猫 abilitata</string>
+    <string name="sony_protocol_v2">Versione 2</string>
+    <string name="activity_type_pickleball">Pickleball</string>
+    <string name="activity_type_jet_skiing">Moto d\'acqua</string>
+    <string name="activity_type_trekking">Escursionismo</string>
+    <string name="fmtPaceTypeAverage">Tipo di ritmo %d medio</string>
+    <string name="unknownDataEncountered">Dati sconosciuti riscontrati</string>
+    <string name="strokes_minute">serie/min</string>
+    <string name="devicetype_nothingearstick">Nothing Ear (Stick)</string>
+    <string name="devicetype_nothing_cmf_watch_pro">CMF Watch Pro</string>
+    <string name="pai_day">Aumento giornaliero</string>
+    <string name="sony_protocol_v1">Versione 1</string>
+    <string name="activity_type_smith_machine">Macchina Smith</string>
+    <string name="zepp_os_watchface_free_combination">Combinazione libera</string>
+    <string name="zepp_os_watchface_city_of_speed">Citt脿 di velocit脿</string>
+    <string name="avgStrokeRate">Frequenza Media Corse</string>
+    <string name="stepRateAvg">Velocit脿 media dei passi</string>
+    <string name="eversionAngleAvg">Angolo medio di estroflessione</string>
+    <string name="eversionAngleMax">Angolo massimo di estroflessione</string>
+    <string name="fmtPaceDistance">Ritmo %d distanza</string>
+    <string name="fmtPaceType">Tipo %d ritmo</string>
+    <string name="fmtPacePace">Ritmo %d</string>
+    <string name="fmtPaceCorrection">Correzione ritmo %d</string>
+    <string name="milliseconds">millisecondi</string>
+    <string name="degrees">gradi</string>
+    <string name="pref_device_action_dnd_priority">Non disturbare - Solo priorit脿</string>
+    <string name="pref_device_action_dnd_alarms">Non disturbare - Solo sveglie</string>
+    <string name="pref_device_action_dnd_on">Non disturbare - Acceso</string>
+    <string name="huawei_trusleep_title">HUAWEI TruSleep 鈩�</string>
+    <string name="prefs_workmode">Modalit脿 lavoro</string>
+    <string name="flatDistance">Distanza piana</string>
+    <string name="prefs_active_noise_cancelling_transparency">Trasparenza</string>
+    <string name="laneLength">Lunghezza Corsia</string>
+    <string name="stepRateSum">Velocit脿 totale dei passi</string>
+    <string name="Pace">Ritmo</string>
+    <string name="devicetype_huawei_watchfit2">Huawei Watch Fit 2</string>
+    <string name="activity_type_fitness_gaming">Gioco di fitness</string>
+    <string name="zepp_os_watchface_vast_sky">Cielo vasto</string>
+    <string name="serbian">Serbo</string>
+    <string name="activity_type_dynamic_cycle">Ciclo dinamico</string>
+    <string name="pref_touch_tone">Tono tocco</string>
+    <string name="pref_wearing_tone">Tono inserimento</string>
+    <string name="pref_touch_tone_summary">Riproduci un tono quando l\'auricolare viene toccato</string>
+    <string name="pref_wearing_tone_summary">Riproduci un tono quando l\'auricolare viene inserito</string>
+    <string name="devicetype_soundcore_liberty3_pro">Soundcore Liberty 3 Pro</string>
+    <string name="preferences_qhybrid_settings_summary">Impostazioni obsolete per gli orologi Q Hybrid</string>
+    <string name="devicetype_huawei_band9">Huawei Band 9</string>
+    <string name="devicetype_huawei_watchfit3">Huawei Watch Fit 3</string>
+    <string name="devicetype_huawei_watch4pro">Huawei Watch 4 (Pro)</string>
+    <string name="impactMax">Impatto massimo</string>
+    <string name="pref_force_options">Forza le opzioni</string>
+    <string name="pref_spo_automatic_enable">Abilita la misurazione automatica della SpO2</string>
+    <string name="pref_force_options_summary">Alcuni dispositivi dichiarano falsamente di non supportare alcune opzioni. Questa impostazione pu貌 essere utilizzata per abilitarle comunque.
+\nUSA A TUO RISCHIO E PERICOLO
+\nLeggi il wiki</string>
+    <string name="pref_force_dnd_support_summary">Forza il supporto a Non Disturbare
+\nUSA A TUO RISCHIO E PERICOLO</string>
+    <string name="pref_summary_debug">Inviare una richiesta di debug al dispositivo Huawei</string>
+    <string name="pref_title_debug">Richiesta debug</string>
+    <string name="devicetype_garmin_epix_pro">Garmin Epix Pro</string>
+    <string name="devicetype_garmin_instinct_2s_solar">Garmin Instinct 2S Solar</string>
+    <string name="devicetype_garmin_vivosmart_5">Garmin V铆vosmart 5</string>
+    <string name="impactAvg">Impatto medio</string>
+    <string name="groundContactTimeAvg">Tempo medio di contatto col terreno</string>
+    <string name="cyclingPowerMax">Potenza massima di pedalata</string>
+    <string name="foreFootLandings">Atterraggi con avampiede</string>
+    <string name="midFootLandings">Atterraggi con pianta del piede</string>
+    <string name="prefs_wena3_auto_power_off_turn_off_time_item">Ora di spegnimento</string>
+    <string name="prefs_wena3_button_action_name_start_timer">Avvia il cronometro attivo</string>
+    <string name="prefs_wena3_receive_calls_title">Impostazioni Chiamate in Arrivo</string>
+    <string name="prefs_wena3_receive_calls_hint">Se disattivato, non riceverai notifiche delle chiamate in arrivo sul Wena</string>
+    <string name="menuitem_stats">Statistiche</string>
+    <string name="pref_summary_osmand_packagename">Usato per selezionare la versione di OsmAnd a cui connettersi</string>
+    <string name="prefs_active_noise_cancelling_light">Cancellazione Attiva del Rumore Leggera</string>
+    <string name="pref_call_rejection_method_summary">Quale azione eseguire quando una chiamata in arrivo viene rifiutata dall\'orologio</string>
+    <string name="pref_summary_prefix_notification_with_app">Inizia il titolo della notifica con il nome dell\'applicazione di origine</string>
+    <string name="pref_title_navigation_prefs">Preferenze navigazione</string>
+    <string name="pref_title_osmand_packagename">Nome pacchetto OsmAnd</string>
+    <string name="devicetype_garmin_instinct_2x_solar">Garmin Instinct 2X Solar</string>
+    <string name="cyclingPowerMin">Potenza minima di pedalata</string>
+    <string name="cyclingPowerAverage">Potenza media di pedalata</string>
+    <string name="swingAngleAvg">Angolo di oscillazione medio</string>
+    <string name="intent_api_broadcast_activity_sync_title">Trasferisci al termine della sincronizzazione delle attivit脿</string>
+    <string name="intent_api_broadcast_activity_sync_summary">Trasmetti un intento quando la sincronizzazione delle attivit脿 termina per uno dei dispositivi</string>
+    <string name="pref_call_rejection_method_title">Metodo rifiuto chiamate</string>
+    <string name="activity_type_rolling">Roller</string>
+    <string name="pref_navigation_app_gmaps">Google Maps</string>
+    <string name="pref_adaptive_volume_control_summary">Aumenta automaticamente il volume quando il suono ambientale 猫 forte</string>
+    <string name="pref_adaptive_noise_cancelling_title">ANC adattivo</string>
+    <string name="pref_adaptive_noise_cancelling_summary">Imposta automaticamente l\'intensit脿 dell\'ANC in base al livello sonoro dell\'ambiente</string>
+    <string name="huawei_alarm_smart_description">Non disattivare la casella di controllo risveglio intelligente.</string>
+    <string name="huawei_alarm_event_description">Non attivare la casella di controllo risveglio intelligente.</string>
+    <string name="huawei_trusleep_summary">Monitora la qualit脿 del sonno e il modello di respirazione in tempo reale.
+\nAnalizza i modelli di sonno e diagnostica con precisione 6 tipi di problemi del sonno.</string>
+    <string name="prefs_wena3_smart_alarm_margin_item">Margine Sveglia Intelligente</string>
+    <string name="pref_adaptive_volume_control_title">Controllo volume adattivo</string>
+    <string name="pref_heartrate_automatic_enable">Abilita la misurazione automatica della frequenza cardiaca</string>
+    <string name="pref_force_smart_alarm">Forza sveglia intelligente</string>
+    <string name="pref_force_wear_location_summary">Forza supporto posizione orologio.
+\nUSA A TUO RISCHIO E PERICOLO</string>
+    <string name="pref_force_smart_alarm_summary">Supporto per gli allarmi intelligenti.
+\nUSA A TUO RISCHIO E PERICOLO</string>
+    <string name="pref_force_wear_location">Forza posizione orologio</string>
+    <string name="prefs_wena3_home_icon_name_suica">Saldo Suica</string>
+    <string name="prefs_wena3_home_icon_name_riiiver">Riiiver</string>
+    <string name="prefs_wena3_auto_power_off_turn_on_time_item">Ora di riattivazione</string>
+    <string name="prefs_wena3_notification_default_vibration">Vibrazione Notifiche</string>
+    <string name="prefs_wena3_notification_default_vibration_repetition">Ripetizione Vibrazione Notifiche</string>
+    <string name="prefs_wena3_notification_vibration_repetition_0">Indefinitamente</string>
+    <string name="prefs_wena3_notification_vibration_repetition_3">3 volte</string>
+    <string name="prefs_wena3_notification_vibration_repetition_4">4 volte</string>
+    <string name="prefs_wena3_notification_per_app_settings_title">Impostazioni Notifiche per app</string>
+    <string name="prefs_wena3_notification_default_call_vibration">Vibrazione Chiamate in Arrivo</string>
+    <string name="prefs_wena3_vibration_rapid">Rapida</string>
+    <string name="prefs_wena3_vibration_step_down">Decrescente</string>
+    <string name="prefs_wena3_vibration_warning">Avviso</string>
+    <string name="temperature_scale_cf_summary">Selezionare se il dispositivo utilizza la scala Celsius o Fahrenheit.</string>
+    <string name="pref_title_navigation_apps">Applicazioni navigazione</string>
+    <string name="pref_wide_area_tap_summary">Riconosci i colpetti tra le guance e le orecchie</string>
+    <string name="pref_force_enable_spo2_support_summary">Forzare l\'abilitazione del supporto SpO2.
+\nUSA A TUO RISCHIO E PERICOLO</string>
+    <string name="prefs_wena3_notification_default_led">Colore LED di Notifica</string>
+    <string name="pref_force_enable_heartrate_support">Forza supporto frequenza cardiaca</string>
+    <string name="huawei_ignore_wakeup_status_start_description">Pu貌 aiutare a rilevare correttamente il sonno. Visibile immediatamente nella vista delle attivit脿 quotidiane.</string>
+    <string name="huawei_reparse_workout_data_description">Questo funziona solo dopo determinati aggiornamenti</string>
+    <string name="prefs_wena3_home_icon_name_wena_pay">Wena Pay</string>
+    <string name="prefs_wena3_home_icon_name_qrio">Qrio</string>
+    <string name="prefs_wena3_notification_vibration_repetition_2">Due volte</string>
+    <string name="prefs_wena3_notification_vibration_repetition_1">Una volta</string>
+    <string name="prefs_wena3_notification_default_call_vibration_repetition">Ripetizione Vibrazione Chiamate in Arrivo</string>
+    <string name="prefs_wena3_notification_default_call_led">Colore LED Chiamate in Arrivo</string>
+    <string name="prefs_wena3_vibration_continuous">Continua</string>
+    <string name="prefs_wena3_vibration_none">Nessuna Vibrazione</string>
+    <string name="prefs_wena3_vibration_triple">Tripla</string>
+    <string name="prefs_wena3_vibration_short">Breve</string>
+    <string name="prefs_wena3_led_none">Senza LED</string>
+    <string name="pref_title_prefix_notification_with_app">Nome app nelle notifiche</string>
+    <string name="huawei_trusleep_summary_light">Monitoraggio del sonno migliorato</string>
+    <string name="pref_force_dnd_support">Forza supporto a Non Disturbare</string>
+    <string name="prefs_wena3_receive_calls_item">Ricevi Notifiche di Chiamata</string>
+    <string name="prefs_wena3_vibration_basic">Semplice</string>
+    <string name="backFootLandings">Atterraggi con tallone</string>
+    <string name="pref_enable_call_accept">Abilita accettazione chiamate</string>
+    <string name="pref_enable_call_reject">Abilita rifiuto chiamate</string>
+    <string name="pref_enable_call_reject_summary">Abilita il rifiuto delle chiamate dal dispositivo</string>
+    <string name="pref_disable_find_phone_with_dnd">Disattiva la funzione Trova il mio telefono quando la funzione Non disturbare 猫 attiva</string>
+    <string name="pref_force_enable_heartrate_support_summary">Forza l\'attivazione del supporto della frequenza cardiaca.
+\nUSA A TUO RISCHIO E PERICOLO</string>
+    <string name="huawei_ignore_wakeup_status_start">Ignora lo stato di inizio del risveglio</string>
+    <string name="huawei_ignore_wakeup_status_end_description">Pu貌 aiutare a rilevare correttamente il sonno. Visibile immediatamente nella vista delle attivit脿 quotidiane.</string>
+    <string name="pref_force_enable_spo2_support">Forza supporto SpO2</string>
+    <string name="huawei_reparse_workout_data">Ripeti analisi dati di allenamento</string>
+    <string name="prefs_wena3_button_action_item">Azione Pulsante</string>
+    <string name="prefs_wena3_button_action_name_qrio_unlock">Sblocco Qrio</string>
+    <string name="prefs_wena3_button_action_name_qrio_lock">Blocco Qrio</string>
+    <string name="temperature_scale_cf">Unit脿 di temperatura</string>
+    <string name="fossil_hr_nav_app_not_installed_notify_title">App di navigazione non installata sull\'orologio</string>
+    <string name="fossil_hr_nav_app_not_installed_notify_text">Navigazione avviata ma navigationApp non 猫 installata sull\'orologio. Installarla da App Manager.</string>
+    <string name="call_rejection_method_reject">Rifiuta</string>
+    <string name="call_rejection_method_ignore">Ignora (silenzioso)</string>
+    <string name="prefs_wena3_vibration_siren">Sirena</string>
+    <string name="serial_number">Numero di serie</string>
+    <string name="pref_navigation_app_osmand">OsmAnd(+)</string>
+    <string name="huawei_trusleep_warning">Attenzione: attivando questa opzione, tutti i sonni verranno visualizzati come sonni leggeri in Gadgetbridge! Clicca qui se accetti.</string>
+    <string name="pref_enable_call_accept_summary">Abilita l\'accettazione delle chiamate dal dispositivo</string>
+    <string name="prefs_wena3_home_icon_name_edy">Saldo Edy</string>
+    <string name="prefs_wena3_day_start_hour_item">Il giorno inizia alle</string>
+    <string name="prefs_wena3_auto_power_off_item">Spegnimento Automatico</string>
+    <string name="prefs_wena3_auto_power_off_hint">Il dispositivo si spegner脿 e accender脿 automaticamente secondo l\'orario specificato</string>
+    <string name="menuitem_alerts">Allerte</string>
+    <string name="menuitem_running">In esecuzione</string>
+    <string name="prefs_wena3_vibration_step_up">Crescente</string>
+    <string name="huawei_ignore_wakeup_status_end">Ignora lo stato di fine del risveglio</string>
+    <string name="pref_agps_status">Stato AGPS</string>
+    <string name="agps_status_missing">Mancante</string>
+    <string name="agps_status_pending">In attesa</string>
+    <string name="agps_status_current">Attuale</string>
+    <string name="agps_status_error">Errore</string>
+    <string name="devicetype_amazfit_bip5_unity">Amazfit Bip 5 Unity</string>
+    <string name="devicetype_garmin_vivomove_hr">Garmin V铆vomove HR</string>
+    <string name="devicetype_garmin_vivomove_style">Garmin V铆vomove Style</string>
+    <string name="devicetype_garmin_venu_2_plus">Garmin Venu 2 Plus</string>
+    <string name="devicetype_garmin_venu_3">Garmin Venu 3</string>
+    <string name="devicetype_garmin_instinct_solar">Garmin Instinct Solar</string>
+    <string name="devicetype_garmin_instinct_2s">Garmin Instinct 2S</string>
+    <string name="devicetype_garmin_instinct_2_solar">Garmin Instinct 2 Solar</string>
+    <string name="devicetype_garmin_instinct_2_soltac">Garmin Instinct 2 SolTac</string>
+    <string name="devicetype_garmin_instinct_crossover">Garmin Instinct Crossover</string>
+    <string name="devicetype_garmin_forerunner_245">Garmin Forerunner 245</string>
+    <string name="devicetype_garmin_swim_2">Garmin Swim 2</string>
+    <string name="devicetype_garmin_vivoactive_4">Garmin V铆voactive 4</string>
+    <string name="devicetype_garmin_vivoactive_4s">Garmin V铆voactive 4S</string>
+    <string name="devicetype_garmin_vivoactive_5">Garmin V铆voactive 5</string>
+    <string name="widget_screen_delete_confirm_title">Cancella schermata widget</string>
+    <string name="moondrop_touch_earbud">Auricolare</string>
+    <string name="moondrop_touch_earbud_both">Entrambi</string>
+    <string name="pref_vitality_score_daily_title">Progresso giornaliero</string>
+    <string name="widget_layout_two">2 widget</string>
+    <string name="pref_vitality_score_daily_summary">Ricevi una notifica quando raggiungi il numero massimo di punti vitalit脿 per la giornata</string>
+    <string name="devicetype_garmin_forerunner_255">Garmin Forerunner 255</string>
+    <string name="devicetype_garmin_forerunner_255s">Garmin Forerunner 255S</string>
+    <string name="devicetype_garmin_fenix_5">Garmin Fenix 5</string>
+    <string name="devicetype_garmin_fenix_6">Garmin Fenix 6</string>
+    <string name="devicetype_garmin_fenix_6_sapphire">Garmin Fenix 6 Sapphire</string>
+    <string name="widget_screen_min_screens">Ci deve essere un minimo di %1$s schermate</string>
+    <string name="widget_screen_x">Schermata %s</string>
+    <string name="widget_screen_no_free_slots_description">Il dispositivo non ha slot liberi per le schermate widget (slot totali: %1$s)</string>
+    <string name="widget_screen_delete_confirm_description">Sei sicuro di voler cancellare \'%1$s\'?</string>
+    <string name="devicetype_garmin_forerunner_265">Garmin Forerunner 265</string>
+    <string name="devicetype_garmin_venu_3s">Garmin Venu 3S</string>
+    <string name="devicetype_pixoo">Pixoo</string>
+    <string name="pref_vitality_score_7_day_title">Progresso in 7 giorni</string>
+    <string name="not_set">Non impostato</string>
+    <string name="pref_vitality_score_title">Punteggio Vitalit脿</string>
+    <string name="pref_vitality_score_7_day_summary">Ricevi una notifica quando il punteggio di vitalit脿 raggiunge 30, 60 o 100 negli ultimi 7 giorni</string>
+    <string name="widget_layout_top_2_bot_1">2 sopra, 1 sotto</string>
+    <string name="widget_screen">Schermata Widget</string>
+    <string name="widget_layout_top_1_bot_2">1 sopra, 2 sotto</string>
+    <string name="proprietary_app_warning">Questa funzione richiede l\'installazione di un\'app proprietaria</string>
+    <string name="notif_battery_full">%1$s batteria piena</string>
+    <string name="activity_prefs_height_inches">Altezza in pollici</string>
+    <string name="devicetype_garmin_vivoactive_3">Garmin V铆voactive 3</string>
+    <string name="widget_layout_top_large_bot_wide">Grande sopra, largo sotto</string>
+    <string name="devicetype_garmin_instinct">Garmin Instinct</string>
+    <string name="devicetype_hama_fit6900">Hama Fit6900</string>
+    <string name="devicetype_xiaomi_watch_s3">Xiaomi Watch S3</string>
+    <string name="devicetype_garmin_vivomove_trend">Garmin V铆vomove Trend</string>
+    <string name="devicetype_garmin_fenix_7_pro">Garmin Fenix 7 Pro</string>
+    <string name="devicetype_moondrop_space_travel">Moondrop Space Travel</string>
+    <string name="notification_channel_full_battery_name">Batteria piena</string>
+    <string name="moondrop_equalizer_preset_reference">Referenza</string>
+    <string name="widget">Widget</string>
+    <string name="notif_battery_full_title">Batteria del dispositivo piena!</string>
+    <string name="battery_i">Batteria %d</string>
+    <string name="authentication_failed_check_key">Autenticazione fallita, controlla la chiave di autenticazione</string>
+    <string name="devicetype_xiaomi_watch_s1_pro">Xiaomi Watch S1 Pro</string>
+    <string name="devicetype_mi_watch_color_sport">Mi Watch Color Sport</string>
+    <string name="widget_layout_top_2_bot_2">2 sopra, 2 sotto</string>
+    <string name="widget_layout_single">1 widget</string>
+    <string name="devicetype_xiaomi_watch_s1">Xiaomi Watch S1</string>
+    <string name="devicetype_xiaomi_watch_s1_active">Xiaomi Watch S1 Active</string>
+    <string name="devicetype_garmin_fenix_7s">Garmin Fenix 7S</string>
+    <string name="widget_layout_top_wide_bot_large">Largo sopra, grande sotto</string>
+    <string name="devicetype_garmin_venu_2">Garmin Venu 2</string>
+    <string name="activity_type_beach_volleyball">Beach Volley</string>
+    <string name="pref_title_notification_times_enabled">Orari di notifica</string>
+    <string name="pref_summary_notification_times_enabled">Inviare notifiche solo in orari specifici</string>
+    <string name="minutes_20">20 minuti</string>
+    <string name="stats_empty_value">-</string>
+    <string name="activity_type_gateball">Gateball</string>
+    <string name="activity_type_parkour">Parkour</string>
+    <string name="activity_type_handcycling">Handbike</string>
+    <string name="activity_type_american_football">Football Americano</string>
+    <string name="activity_type_paddling">Pagaiata</string>
+    <string name="activity_type_motorcycling">Motociclismo</string>
+    <string name="activity_type_boating">Navigazione</string>
+    <string name="activity_type_parachuting">Paracadutismo</string>
+    <string name="minutes_60">60 minuti</string>
+    <string name="activity_type_luge">Slittino</string>
+    <string name="activity_type_auto_racing">Corse automobilistiche</string>
+    <string name="activity_type_e_bike">Bicicletta elettrica</string>
+    <string name="activity_type_training">Allenamento</string>
+    <string name="activity_type_cardio">Cardio</string>
+    <string name="activity_type_driving">Guida</string>
+    <string name="activity_type_ice_skating">Pattinaggio su Ghiaccio</string>
+    <string name="hrv_status">Stato HRV</string>
+    <string name="sleep_colored_stats_light">Leggero</string>
+    <string name="sleep_colored_stats_rem">REM</string>
+    <string name="sleep_colored_stats_deep">Profondo</string>
+    <string name="gps_glonass">GPS + GLONASS</string>
+    <string name="milliseconds_ms">ms</string>
+    <string name="activity_type_tae_bo">Tae Bo</string>
+    <string name="activity_type_beach_soccer">Calcio da spiaggia</string>
+    <string name="devicetype_idasen">Scrivania IKEA Idasen</string>
+    <string name="devicetype_colmi_r10">Colmi R10</string>
+    <string name="breaths_per_min">Respiri/min</string>
+    <string name="devicetype_huawei_watchgtrunner">Huawei Watch GT Runner</string>
+    <string name="devicetype_vivitar_hr_bp_monitor_activity_tracker">Monitor Attivit脿 HR &amp; BP Vivitar</string>
+    <string name="workoutSets">Serie</string>
+    <string name="thirty_days_timeline">Cronologia 30 giorni</string>
+    <string name="moondrop_touch_action_anc_mode">Commuta Modalit脿 Cancellazione Attiva Rumore</string>
+    <string name="moondrop_touch_trigger_long_press_3s">Pressione Prolungata (3s)</string>
+    <string name="devicetype_huawei_watchgt5">Huawei Watch GT 5 (Pro)</string>
+    <string name="sleep_colored_stats_rem_avg">AVG REM</string>
+    <string name="devicetype_garmin_fenix_7">Garmin Fenix 7</string>
+    <string name="devicetype_garmin_vivosport">Garmin V铆vosport</string>
+    <string name="lactateThresholdHeartRate">Soglia Frequenza Cardiaca del Lattato</string>
+    <string name="activity_type_navigate">Navigazione</string>
+    <string name="activity_type_flying">Volo</string>
+    <string name="activity_type_climb_indoor">Arrampicata al coperto</string>
+    <string name="activity_type_sky_diving">paracadutismo</string>
+    <string name="activity_type_tactical">Sport tattico</string>
+    <string name="activity_type_video_gaming">Video Ludica</string>
+    <string name="activity_type_mixed_martial_arts">Arti Marziali Miste</string>
+    <string name="activity_type_aerobic_combo">Aerobica combinata</string>
+    <string name="activity_type_chess">Scacchi</string>
+    <string name="activity_type_dragon_boat">Dragon boat</string>
+    <string name="activity_type_mass_gymnastics">Ginnastica in massa</string>
+    <string name="activity_type_modern_dance">Danza moderna</string>
+    <string name="activity_type_pole_dance">Pole dance</string>
+    <string name="activity_type_shuffleboard">Shuffleboard</string>
+    <string name="activity_type_stair_climber">Salita su scale</string>
+    <string name="activity_type_weiqi">Gioco del go</string>
+    <string name="activity_type_laser_tag">Laser tag</string>
+    <string name="hrv_status_baseline_label">Linea guida</string>
+    <string name="devicetype_miband9">Xiaomi Smart Band 9</string>
+    <string name="devicetype_garmin_forerunner_255s_music">Garmin Forerunner 255S Music</string>
+    <string name="devicetype_redmi_watch_5_active">Redmi Watch 5 Active</string>
+    <string name="steps_avg">AVG Passi</string>
+    <string name="activity_detail_share_json_details">Condividi Dettagli JSON</string>
+    <string name="moondrop_touch_action_play_pause">Riproduci/Pausa</string>
+    <string name="moondrop_touch_action_call_pick_hang">Rispondi/Rifiuta Chiamata</string>
+    <string name="stats_highest_hr">HR massimo</string>
+    <string name="activity_type_meditation">Meditazione</string>
+    <string name="activity_type_wakesurfing">Wakesurf</string>
+    <string name="activity_type_breaking">Breaking</string>
+    <string name="activity_type_sledding">Pattino</string>
+    <string name="body_energy_gained">Acquisita</string>
+    <string name="body_energy_lost">Persa</string>
+    <string name="devicetype_garmin_forerunner_245_music">Garmin Forerunner 245 Music</string>
+    <string name="devicetype_sony_wf_c500">Sony WF-C500</string>
+    <string name="devicetype_honor_watchgs3">Honor Watch GS 3</string>
+    <string name="menuitem_stress_breakdown">Stress (rottura)</string>
+    <string name="menuitem_stress_segmented">Stress (segmentato)</string>
+    <string name="recoveryTime">Tempo di Recupero</string>
+    <string name="workout_set_i">Serie %1d</string>
+    <string name="activity_type_mountaineering">Alpinismo</string>
+    <string name="activity_type_sail_race">Gara di vela</string>
+    <string name="activity_type_rafting">Rafting</string>
+    <string name="activity_type_health_snapshot">Panoramica sulla salute</string>
+    <string name="activity_type_marine">Marittima</string>
+    <string name="activity_type_finswimming">Finswimming</string>
+    <string name="activity_type_esports">Sport elettronici</string>
+    <string name="activity_type_flowriding">Simulatore di surf</string>
+    <string name="busy_task_fetch_hrv_data">Recupero dati HRV</string>
+    <string name="date_placeholders__date__time">%1s, %1s</string>
+    <string name="date_placeholders__start_time__end_time">%1s - %2s</string>
+    <string name="activity_type_stand_up_paddleboarding">Stand Up Paddle</string>
+    <string name="activity_type_jumpmaster">Jumpmaster</string>
+    <string name="activity_type_multisport">Sport multipli</string>
+    <string name="activity_type_floor_climbing">Arrampicata a terra</string>
+    <string name="activity_type_push_walk_speed">Spinta - Andatura Veloce</string>
+    <string name="activity_type_softball_slow_pitch">Softball a passo lento</string>
+    <string name="activity_type_indoor_push_walk_speed">Spinta al coperto - Andatura Veloce</string>
+    <string name="activity_type_snorkeling">Snorkeling</string>
+    <string name="activity_type_somatosensory_game">Gioco somatosensoriale</string>
+    <string name="activity_type_spinning">Spinning</string>
+    <string name="activity_type_wall_ball">Wall ball</string>
+    <string name="devicetype_amazfit_trex_3">Amazfit T-Rex 3</string>
+    <string name="devicetype_garmin_venu_sq_2">Garmin Venu Sq 2</string>
+    <string name="devicetype_garmin_venu_2s">Garmin Venu 2S</string>
+    <string name="devicetype_garmin_fenix_6s_sapphire">Garmin Fenix 6S Sapphire</string>
+    <string name="devicetype_soundcore_motion300">Soundcore Motion 300</string>
+    <string name="pref_title_nagivation_apps">App di navigazione</string>
+    <string name="pref_header_automations">Automazioni</string>
+    <string name="pref_description_about_you">Data di nascita, sesso, altezza, peso, obiettivi</string>
+    <string name="pref_description_developer_options">Registri, API degli intenti</string>
+    <string name="pref_description_automations">Esportazione automatica, recupero automatico</string>
+    <string name="pref_header_main_screen">Schermata principale</string>
+    <string name="pref_title_user_interface">Interfaccia utente</string>
+    <string name="pref_summary_sync_birthdays">Sincronizza i compleanni dei contatti con gli eventi del calendario</string>
+    <string name="pref_title_sync_birthdays">Sincronizza i compleanni</string>
+    <string name="find_my_phone_companion_warning">L\'accoppiamento con il companion 猫 necessario per trovare il telefono. Per ulteriori informazioni, fare clic qui.</string>
+    <string name="music_upload_info">Si sta per caricare il seguente file musicale:\n\n%1$s\nTitolo della canzone: %2$s\nAlbum: %3$s\n</string>
+    <string name="authentication_failed_negotiation">Negoziazione della chiave di autenticazione fallita</string>
+    <string name="activity_type_rem_sleep">Sonno REM</string>
+    <string name="activity_type_indoor_track">Percorso indoor</string>
+    <string name="activity_type_kayaking">Kayak</string>
+    <string name="vo2max_cycling">Massima VO鈧� in Ciclismo</string>
+    <string name="target">Obiettivo</string>
+    <string name="activity_type_push_run_speed">Spinta - Corsa Veloce</string>
+    <string name="activity_type_indoor_push_run_speed">Spinta al Coperto - Corsa Veloce</string>
+    <string name="activity_type_lacrosse">Lacrosse</string>
+    <string name="activity_type_water_tubing">Water Tubing</string>
+    <string name="activity_type_para_sport">Para Sport</string>
+    <string name="activity_type_ultimate_disc">Ultimate</string>
+    <string name="activity_type_aerobics">Aerobica</string>
+    <string name="activity_type_plaza_dancing">Ballo di piazza</string>
+    <string name="hrv_status_day_avg">Media giornaliera</string>
+    <string name="activity_type_body_combat">Combattimento corpo a corpo</string>
+    <string name="hrv_status_day_avg_legend">Media giornalista (ms)</string>
+    <string name="activity_type_table_football">Biliardino</string>
+    <string name="activity_type_water_polo">Pallanuoto</string>
+    <string name="activity_type_biathlon">Biathlon</string>
+    <string name="devicetype_sony_wf_c700n">Sony WF-C700N</string>
+    <string name="devicetype_huawei_watchd2">Huawei Watch D2</string>
+    <string name="devicetype_colmi_r06">Colmi R06</string>
+    <string name="distance_total">Distanza Totale</string>
+    <string name="pref_header_hrv_status">Stato HRV</string>
+    <string name="body_energy">Energia Corporea</string>
+    <string name="moondrop_touch_trigger">Attivazione</string>
+    <string name="pref_wide_area_tap_title">Selezione area ampia</string>
+    <string name="min_respiration_rate">Frequenza Minima Respiro</string>
+    <string name="average_respiration_rate">Frequenza Respiro</string>
+    <string name="max_respiration_rate">Frequenza Massima Respiro</string>
+    <string name="hrv_sdrr">HRV SDRR</string>
+    <string name="hrv_rmssd">HRV RMSSD</string>
+    <string name="fmtPaceAverage">Media del ritmo</string>
+    <string name="activity_type_bike_commute">Bici Pendolari</string>
+    <string name="activity_type_handcycling_indoor">Handcycling al coperto</string>
+    <string name="activity_type_fitness_equipment">Attrezzature fitness</string>
+    <string name="activity_type_platform_tennis">Piattaforma Tennis</string>
+    <string name="activity_type_racket">Racchetta</string>
+    <string name="activity_type_grinding">Grind</string>
+    <string name="activity_type_padel">Padel</string>
+    <string name="activity_type_racquetball">Racquetball</string>
+    <string name="activity_type_disc_golf">Golf a disco</string>
+    <string name="activity_type_bridge">Bridge</string>
+    <string name="activity_type_ballroom_dance">Ballo da sala</string>
+    <string name="activity_type_board_game">Giochi da tavola</string>
+    <string name="activity_type_bocce">Bocce</string>
+    <string name="activity_type_cardio_combat">Cardio combat</string>
+    <string name="activity_type_muay_thai">Muay thai</string>
+    <string name="activity_type_race_walking">Gara podistica</string>
+    <string name="activity_type_square_dance">Quadriglia</string>
+    <string name="activity_type_tug_of_war">Tiro alla fune</string>
+    <string name="activity_type_free_sparring">Free sparring</string>
+    <string name="activity_type_obstacle_race">Corsa a ostacoli</string>
+    <string name="activity_type_billiard_pool">Biliardo</string>
+    <string name="activity_type_canoeing">Canoa</string>
+    <string name="activity_type_water_scooter">Moto d\'acqua</string>
+    <string name="activity_type_bobsleigh">Guidoslitta</string>
+    <string name="activity_type_bungee_jumping">Bungee jumping</string>
+    <string name="activity_type_orienteering">Orienteering</string>
+    <string name="hrv_status_seven_days_avg">Media a 7 giorni</string>
+    <string name="hrv_status_balanced">Bilanciato</string>
+    <string name="hrv_status_unbalanced">Sbilanciato</string>
+    <string name="hrv_status_low">Basso</string>
+    <string name="hrv_status_baseline">%1$d-%2$d ms</string>
+    <string name="bpm_value_unit">%1$d bpm</string>
+    <string name="hrv_status_poor">Scarso</string>
+    <string name="hrv_status_unit">%1$d ms</string>
+    <string name="steps_distance_unit">%1$,.2f km</string>
+    <string name="body_energy_legend_level">Livello Energia del Corpo</string>
+    <string name="devicetype_garmin_forerunner_255_music">Garmin Forerunner 255 Music</string>
+    <string name="devicetype_garmin_forerunner_265s">Garmin Forerunner 265S</string>
+    <string name="devicetype_mismartscale">Mi Smart Scale 2</string>
+    <string name="pref_title_calendar_lookahead">Numero di giorni di anticipo</string>
+    <string name="pref_summary_calendar_lookahead">Sincronizza fino a %1s giorni di eventi del calendario</string>
+    <string name="sleep_colored_stats_deep_avg">AVG Profonda</string>
+    <string name="sleep_colored_stats_light_avg">AVG Leggera</string>
+    <string name="transition">Transizione</string>
+    <string name="app_crash_share_stacktrace">Condividi errore</string>
+    <string name="steps_total">Passi totali</string>
+    <string name="distance_avg">Distanza media</string>
+    <string name="workout_set_repetitions">%1d x</string>
+    <string name="RunningForm">Forma nella Corsa</string>
+    <string name="hrZoneEasy">Facile</string>
+    <string name="activity_type_bmx">BMX</string>
+    <string name="devicetype_huawei_watch3">Huawei Watch 3 (Pro)</string>
+    <string name="devicetype_colmi_r02">Colmi R02</string>
+    <string name="devicetype_colmi_r03">Colmi R03</string>
+    <string name="workout_set_repetitions_weight_kg">%1d x %2$.2f kg</string>
+    <string name="workout_set_repetitions_weight_lbs">%1d per %2$.2f libbre</string>
+    <string name="devicetype_nothing_cmf_buds_pro_2">CMF Buds Pro 2</string>
+    <string name="devicetype_nothing_cmf_watch_pro_2">CMF Watch Pro 2</string>
+    <string name="moondrop_touch_action_assistant">Attiva Assistente Vocale</string>
+    <string name="moondrop_touch_action_call_start">Inizia Chiamata</string>
+    <string name="activity_type_worn">Indossato</string>
+    <string name="estimatedSweatLoss">Perdita di sudore stimata</string>
+    <string name="hrv">HRV</string>
+    <string name="activity_type_bouldering">Sassismo</string>
+    <string name="activity_type_winter_sport">Sport Invernale</string>
+    <string name="activity_type_air_walker">Air walker</string>
+    <string name="activity_type_artistic_swimming">Nuoto sincronizzato</string>
+    <string name="activity_type_team_sport">Sport di Squadra</string>
+    <string name="activity_type_parallel_bars">Parallele</string>
+    <string name="activity_type_hip_hop">Hip-hop</string>
+    <string name="activity_type_hula_hoop">Hula hoop</string>
+    <string name="activity_type_jai_alai">Jai alai</string>
+    <string name="activity_type_judo">Judo</string>
+    <string name="activity_type_jujitsu">Jujitsu</string>
+    <string name="activity_type_cross_trainer">Cyclette ellittica</string>
+    <string name="pref_time_sync">Sincronizzazione automatica orario</string>
+    <string name="minutes_2">2 minuti</string>
+    <string name="dateformat_day_month">Giorno, Mese</string>
+    <string name="dateformat_month_day">Mese, Giorno</string>
+    <string name="activity_type_snowshoe">Ciaspolata</string>
+    <string name="activity_type_hacky_sack">Hacky sack</string>
+    <string name="busy_task_fetch_sleep_data">Recupero dati del sonno</string>
+    <string name="user_feedback_set_settings_ok">Impostazioni inviate al dispositivo.</string>
+    <string name="minutes_15">15 minuti</string>
+    <string name="devicetype_huawei_watchgtcyber">Huawei Watch GT Cyber</string>
+    <string name="menuitem_map">Mappa</string>
+    <string name="pref_crash_notification_title">Notifica arresto anomalo</string>
+    <string name="pref_crash_notification_summary">Quando l\'applicazione si blocca, mostra una notifica con l\'errore</string>
+    <string name="app_crash_notification_title">%1s si 猫 arrestato</string>
+    <string name="vo2max_running">Massima VO鈧� in Corsa</string>
+    <string name="weight_lbs">%1$.2f libbre</string>
+    <string name="paceCorrection">Correzione</string>
+    <string name="workout_set_reps">Ripetizioni</string>
+    <string name="sleep_colored_stats_awake_avg">AVG Sveglio</string>
+    <string name="activity_type_transition">Transizione</string>
+    <string name="activity_type_breathwork">Lavoro sul respiro</string>
+    <string name="activity_type_xc_classic_ski">Sci XC Classico</string>
+    <string name="activity_type_sail_expedition">Spedizione in barca a vela</string>
+    <string name="activity_type_wakeboarding">Wakeboard</string>
+    <string name="activity_type_shooting">Sport di tiro</string>
+    <string name="activity_type_folk_dance">Danza popolare</string>
+    <string name="activity_type_frisbee">Frisbee</string>
+    <string name="activity_type_futsal">Futsal</string>
+    <string name="weight_kg">%1$.2f kg</string>
+    <string name="abstract_chart_fragment_kind_awake_sleep">Sveglio</string>
+    <string name="stats_lowest_hr">HR minimo</string>
+    <string name="pref_header_sound">Audio</string>
+    <string name="activity_type_sepak_takraw">Sepak Takraw</string>
+    <string name="activity_type_snowmobiling">Motoslitta</string>
+    <string name="activity_type_water_skiing">Sci Nautico</string>
+    <string name="devicetype_garmin_fenix_5_plus">Garmin Fenix 5 Plus</string>
+    <string name="hrZoneThreshold">Soglia</string>
+    <string name="hrZoneMaximum">Massimo</string>
+    <string name="prefs_light_duration_longer">Durata maggiore della luce</string>
+    <string name="pref_dashboard_widget_today_hr_interval_summary">Il numero di minuti in cui il grafico mostra \'indossato\' dopo ogni misurazione corretta della frequenza cardiaca</string>
+    <string name="choose_device">Scegli un dispositivo</string>
+    <string name="no_supported_devices_found">Non sono stati trovati dispositivi supportati</string>
+    <string name="menuitem_stress_simple">Stress (semplice)</string>
+    <string name="search">Cerca</string>
+    <string name="devicetype_garmin_enduro_3">Garmin Enduro 3</string>
+    <string name="devicetype_garmin_fenix_5x_plus">Garmin Fenix 5X Plus</string>
+    <string name="hr_resting">A riposo</string>
+    <string name="hr_maximum">Massimo</string>
+    <string name="hr_minimum">Minimo</string>
+    <string name="hr_average">Media</string>
+    <string name="minutes_3">3 minuti</string>
+    <string name="minutes_45">45 minuti</string>
+    <string name="minutes_75">75 minuti</string>
+    <string name="minutes_90">90 minuti</string>
+    <string name="minutes_180">180 minuti</string>
+    <string name="minutes_210">210 minuti</string>
+    <string name="minutes_240">240 minuti</string>
+    <string name="minutes_255">255 minuti</string>
+    <string name="stress_average">Media</string>
+    <string name="activity_type_hang_gliding">Deltaplano</string>
+    <string name="activity_type_inline_skating">Pattinaggio in linea</string>
+    <string name="activity_type_checkers">Dama</string>
+    <string name="devicetype_garmin_venu">Garmin Venu</string>
+    <string name="devicetype_garmin_forerunner_955">Garmin Forerunner 955</string>
+    <string name="devicetype_soundcore_liberty4_nc">Soundcore Liberty 4 NC</string>
+    <string name="menuitem_heart_rate_push">Frequenza Cardiaca Push</string>
+    <string name="menuitem_night_display">Visualizzazione notturna</string>
+    <string name="moondrop_equalizer_preset_monitor">Controllo</string>
+    <string name="devicetype_garmin_fenix_8">Garmin Fenix 8</string>
+    <string name="devicetype_garmin_forerunner_165">Garmin Forerunner 165</string>
+    <string name="devicetype_garmin_forerunner_965">Garmin Forerunner 965</string>
+    <string name="devicetype_mijia_xmwsdj04">Sensore Temperatura e Umidit脿 Mijia 2 (E-ink)</string>
+    <string name="devicetype_micompositionscale">Mi Body Composition Scale 2</string>
+    <string name="menuitem_weight">Peso</string>
+    <string name="hrv_status_seven_days_avg_status">Stato</string>
+    <string name="hrv_status_last_night">Ultima notte</string>
+    <string name="hrv_status_last_night_highest_5">Ultima notte max media 5 min</string>
+    <string name="hrv_status_seven_days_avg_long">Media 7 giorni</string>
+    <string name="pref_header_external_integrations">Integrazioni Esterne</string>
+    <string name="pref_description_general">Avvio, lingua, zona, posizione</string>
+    <string name="pref_description_notifications">Notifiche delle app, whitelist/blacklist</string>
+    <string name="pref_description_user_interface">Tema, schermata principale</string>
+    <string name="pref_description_dashboard">Widget, dispositivi da includere</string>
+    <string name="pref_description_deprecated_functionalities">Impostazioni che saranno rimosse in una versione futura</string>
+    <string name="minutes_4">4 minuti</string>
+    <string name="minutes_6">6 minuti</string>
+    <string name="minutes_7">7 minuti</string>
+    <string name="minutes_8">8 minuti</string>
+    <string name="minutes_9">9 minuti</string>
+    <string name="minutes_120">120 minuti</string>
+    <string name="minutes_150">150 minuti</string>
+    <string name="activity_prefs_date_birth">Data di nascita</string>
+    <string name="devicetype_garmin_venu_sq">Garmin Venu Sq</string>
+    <string name="devicetype_honor_watchgspro">Honor Watch GS Pro</string>
+    <string name="moondrop_equalizer_preset_basshead">A Testa Bassa</string>
+    <string name="soundcore_voice_prompts">Prompt Vocali</string>
+    <string name="moondrop_touch_trigger_long_press_1s">Pressione Prolungata (1s)</string>
+    <string name="mijia_lywsd_comfort_humidity_title">Umidit脿 (%)</string>
+    <string name="mijia_lywsd_comfort_lower">Limite Inferiore</string>
+    <string name="mijia_lywsd_comfort_upper">Limite Superiore</string>
+    <string name="idasen_control_button_mid">MED</string>
+    <string name="idasen_control_button_sit">SED</string>
+    <string name="idasen_pref_mid_height">Altezza della posizione centrale (in centimetri)</string>
+    <string name="idasen_control_button_stand">ALTO</string>
+    <string name="idasen_pref_value_warning">Il valore deve essere compreso tra 62 e 126 cm</string>
+    <string name="soundcore_ldac_mode_title">Modalit脿 LDAC</string>
+    <string name="soundcore_ldac_mode_summary">L\'attivazione di LDAC riduce la durata della batteria e potrebbe causare l\'instabilit脿 della connessione</string>
+    <string name="soundcore_adaptive_direction_title">Direzione Adattiva</string>
+    <string name="notification_channel_connection_status_name">Stato Connessione</string>
+    <string name="uploading_watchface">Caricamento quadrante鈥�</string>
+    <string name="pref_force_connection_type_ble">Bluetooth LE</string>
+    <string name="widget_move_down">Muovi gi霉</string>
+    <string name="warning_missing_notification_permission">Impossibile pubblicare una notifica in corso a causa di un\'autorizzazione mancante</string>
+    <string name="pref_test_features_title">Funzioni</string>
+    <string name="widget_move_up">Muovi su</string>
+    <string name="pref_test_features_summary">Funzioni abilitate per questo dispositivo di test</string>
+    <string name="miscale_weight_unit_metric">Metrica (kg)</string>
+    <string name="miscale_weight_unit_imperial">Imperiale (libbre)</string>
+    <string name="miscale_weight_unit_chinese">Cinese (jin)</string>
+    <string name="mijia_lywsd_comfort_level_title">Livello Comfort</string>
+    <string name="wake_up_time">Sveglia</string>
+    <string name="widget_name_untitled">Widget senza titolo (%1$s)</string>
+    <string name="pref_title_fossil_hr_navigation_instructions">Istruzioni Navigazione</string>
+    <string name="pref_summary_fossil_hr_navigation_instructions">Configurare il comportamento dell\'app di navigazione nell\'orologio</string>
+    <string name="activity_info">Info attivit脿</string>
+    <string name="soundcore_button_brightness">Luminosit脿 dei pulsanti</string>
+    <string name="soundcore_button_brightness_low">Bassa</string>
+    <string name="soundcore_button_brightness_high">Alta</string>
+    <string name="soundcore_button_brightness_medium">Media</string>
+    <string name="button_open_menu_companion">Apri menu della app del companion</string>
+    <string name="widget_name_colored_tile">%1$s (tessera colorata)</string>
+    <string name="scan_scanning_multiple_devices">Scansione di %d dispositivi</string>
+    <string name="pref_app_connection_duration">Durata connessione app</string>
+    <string name="widget_missing_parts">Seleziona tutti i widget</string>
+    <string name="pref_title_fossil_hr_nav_vibrate">Vibra con nuove istruzioni</string>
+    <string name="uploadwatchfaceoperation_in_progress">Caricamento quadrante</string>
+    <string name="uploadwatchfaceoperation_complete">Installazione quadrante completata</string>
+    <string name="uploadwatchfaceoperation_failed">Installazione quadrante fallita</string>
+    <string name="pref_force_connection_type_bt_classic">Bluetooth Classica</string>
+    <string name="mijia_lywsd_comfort_temperature_title">Temperatura (掳C)</string>
+    <string name="widget_layout">Aspetto widget</string>
+    <string name="auto_reconnect_ble_scan_summary">Attendere la scansione del dispositivo invece di tentare la connessione alla cieca</string>
+    <string name="widget_unknown_workout">Esercizio sconosciuto - %s</string>
+    <string name="scan_not_scanning">Non scansionando</string>
+    <string name="unbind_before_pair_message">Questo dispositivo 猫 gi脿 collegato nelle impostazioni di Android, il che pu貌 far fallire l\'associazione per alcuni dispositivi.\n\nSe l\'aggiunta del dispositivo non riesce, rimuoverlo dalle impostazioni di Android e riprovare.</string>
+    <string name="unbind_before_pair_title">Gi脿 collegato</string>
+    <string name="companion_pairing_request_title">Dispositivo companion</string>
+    <string name="mijia_lywsd_comfort_level_summary">Configurare i limiti di temperatura e umidit脿 per le emoji visualizzate</string>
+    <string name="idasen_pref_sit_height">Altezza della posizione seduta (in centimetri)</string>
+    <string name="idasen_pref_stand_height">Altezza della posizione in piedi (in centimetri)</string>
+    <string name="mijia_lywsd_comfort_temperature_summary">Intervallo consigliato: 19 - 27</string>
+    <string name="mijia_lywsd_comfort_humidity_summary">Intervallo consigliato: 20 - 85</string>
+    <string name="widget_subtype">Sottotipo Widget</string>
+    <string name="error_scan_failed">Scansione fallita: %d</string>
+    <string name="auto_reconnect_ble_scan_title">Riconnessione tramite scansione BLE</string>
+    <string name="pref_summary_fossil_hr_nav_foreground">Se la app di navigazione deve passare automaticamente in primo piano quando riceve un\'istruzione di navigazione</string>
+    <string name="prompt_restart_gadgetbridge">Riavviare GB affinch茅 abbia effetto.</string>
+    <string name="soundcore_equalizer_direction">Direzione Dispositivo</string>
+    <string name="soundcore_adaptive_direction_summary">Regola automaticamente la preimpostazione dell\'equalizzatore in base alla direzione del dispositivo</string>
+    <string name="pref_summary_fossil_hr_nav_vibrate">Se l\'orologio deve vibrare ad ogni istruzione di navigazione nuova o modificata (solo quando l\'applicazione 猫 in primo piano)</string>
+    <string name="pref_force_connection_type_title">Forza tipo di connessione</string>
+    <string name="pref_force_connection_type_description">Si pu貌 provare a forzare il tipo di connessione nel caso in cui il dispositivo non risponda a Gadgetbridge</string>
+    <string name="pref_force_connection_type_auto">Automatica</string>
+    <string name="device_state_waiting_scan">In attesa della scansione del dispositivo</string>
+    <string name="pref_continuous_skin_temperature_measurement_description">Il recupero della temperatura non 猫 attualmente supportato. Questa impostazione abilita solo la misurazione continua sul dispositivo</string>
+    <string name="pref_continuous_skin_temperature_measurement_title">Misurazione continua temperatura della pelle</string>
+    <string name="pref_title_fossil_hr_nav_foreground">Porta in primo piano</string>
+    <string name="soundcore_equalizer_direction_lying">Sdraiato</string>
+    <string name="waiting_for_bluetooth">Attesa del bluetooth鈥�</string>
+    <string name="notification_channel_scan_service_name">Servizio di scansione</string>
+    <string name="pref_sleep_mode_schedule_summary">Invia un promemoria e attiva la modalit脿 di riposo al momento di coricarsi. All\'ora di sveglia programmata, la sveglia suoner脿.</string>
+    <string name="scan_scanning_all_devices">Scansione di tutti i dispositivi</string>
+    <string name="scan_scanning_single_device">Scansione di 1 dispositivo</string>
+    <string name="soundcore_equalizer_band7">Banda 7</string>
+    <string name="soundcore_equalizer_band8">Banda 8</string>
+    <string name="soundcore_equalizer_band9">Banda 9</string>
+    <string name="soundcore_equalizer_frequency">Frequenza</string>
+    <string name="soundcore_equalizer_value">Valore</string>
+    <string name="miscale_weight_unit_title">Unit脿 di Peso</string>
+    <string name="miscale_weight_unit_summary">Imposta l\'unit脿 di peso per le misure visualizzate</string>
+    <string name="soundcore_equalizer_band1">Banda 1</string>
+    <string name="soundcore_equalizer_band2">Banda 2</string>
+    <string name="soundcore_equalizer_band3">Banda 3</string>
+    <string name="soundcore_equalizer_band4">Banda 4</string>
+    <string name="soundcore_equalizer_band5">Banda 5</string>
+    <string name="soundcore_equalizer_band6">Banda 6</string>
+    <string name="soundcore_equalizer_preset">Predisposizione</string>
+    <string name="soundcore_equalizer_preset_signature">Firma soundcore</string>
+    <string name="soundcore_equalizer_preset_xtra_bass">Bass eXtra</string>
+    <string name="soundcore_equalizer_direction_standing">In piedi</string>
+    <string name="soundcore_equalizer_reset_summary">Riporta tutte le bande dell\'equalizzatore alle impostazioni predefinite</string>
+    <string name="soundcore_equalizer_direction_hanging">Appeso</string>
+    <string name="miscale_small_objects_summary">Memorizzare il peso di oggetti pi霉 leggeri di 10 kg</string>
+    <string name="miscale_small_objects_title">Piccoli Oggetti</string>
+    <string name="error_menu_companion_not_installed">\'HR Menu Companion\' probabilmente non installata</string>
+    <string name="soundcore_equalizer_preset_voice">Voce</string>
+    <string name="soundcore_equalizer_preset_balanced">Bilanciato</string>
+    <string name="soundcore_equalizer_custom_title">Personalizza鈥�</string>
+    <string name="soundcore_equalizer_custom_summary">Configura impostazioni equalizzatore parametrico</string>
+    <string name="soundcore_equalizer_reset_title">Ripristina predefinito</string>
+    <string name="menuitem_focus">Focus</string>
+    <string name="bedtime">Sonno</string>
+    <string name="pref_sleep_mode_schedule_title">Programma Modalit脿 A Riposo</string>
+    <string name="prefs_wena3_status_page_title">Ordinamento Pagina di Stato</string>
+    <string name="devicesetting_scannable_minimum_unseen_summary">Dopo la scansione, il dispositivo deve rimanere non visibile per questo periodo di tempo prima di essere nuovamente registrato</string>
+    <string name="devicesetting_scannable_rssi_summary">La soglia RSSI minima per il rilevamento</string>
+    <string name="state_scanned">Scansionato</string>
+    <string name="devicesetting_scannable_debounce">Timeout di smorzamento scansionabilit脿 (secondi)</string>
+    <string name="devicesetting_scannable_minimum_unseen">Tempo minimo non visibile (secondi)</string>
+    <string name="companion_pairing_request_description">Accoppiare questo dispositivo come companion?\n\nQuesta opzione 猫 consigliata per alcune funzioni, come la ricerca del dispositivo, e garantisce una connessione migliore.</string>
+    <string name="devicesetting_scannable_rssi">Soglia RSSI minima</string>
+    <string name="devicesetting_scannable_debounce_summary">Dopo la scansione, il dispositivo viene mantenuto come scansionato e ignorato per il periodo di tempo specificato</string>
+    <string name="devicetype_scannable">Dispositivi scansionabili</string>
+    <string name="error_showing_changelog">Errore di visualizzazione del registro modifiche</string>
+    <string name="bottom_nav_devices">Dispositivi</string>
+    <string name="bottom_nav_dashboard">Cruscotto</string>
+    <string name="pref_dashboard_first_title">Mostra prima il cruscotto</string>
+    <string name="dashboard_settings">Impostazioni Cruscotto</string>
+    <string name="pref_dashboard_cards_summary">Disegnare tessere intorno ai widget del cruscotto</string>
+    <string name="pref_dashboard_first_summary">Mostra il cruscotto all\'avvio di Gadgetbridge, invece della schermata dei dispositivi</string>
+    <string name="pref_auto_reply_calls_title">Rispondi automaticamente alle chiamate telefoniche</string>
+    <string name="pref_auto_reply_calls_summary">Il telefono risponde automaticamente alle chiamate in arrivo</string>
+    <string name="pref_summary_bottom_navigation_bar_off">Passa da una schermata principale all\'altra solo con lo swipe orizzontale</string>
+    <string name="pref_dashboard_all_devices_title">Tutti i dispositivi</string>
+    <string name="pref_dashboard_widget_goals_chart_title">Grafico obiettivi</string>
+    <string name="pref_title_garmin_default_reply_suffix">Utilizza un suffisso di risposta predefinito</string>
+    <string name="pref_dashboard_widget_today_upside_down_title">Mezzanotte in basso</string>
+    <string name="pref_dashboard_devices_to_include">Dispositivi da includere</string>
+    <string name="pref_dashboard_widget_show_legend_title">Mostra legenda</string>
+    <string name="pref_summary_garmin_default_reply_suffix">Appendice in aggiunta al suffisso impostato in Gadgetbridge</string>
+    <string name="pref_dashboard_select_devices_summary">Combina i dati di attivit脿 da dispositivi specifici per i totali sul cruscotto</string>
+    <string name="pref_dashboard_widget_today_24h_summary">Mostra l\'attivit脿 in un singolo cerchio di 24 ore invece che in un due cerchi concentrici di 12 ore</string>
+    <string name="pref_dashboard_widget_today_hr_interval_title">Intervallo frequenza cardiaca</string>
+    <string name="pref_auto_reply_calls_delay_title">Ritardo Risposta Automatica</string>
+    <string name="pref_speak_notifications_aloud_summary">Le notifiche saranno lette ad alta voce attraverso le cuffie</string>
+    <string name="pref_speak_notifications_aloud_title">Leggi Notifiche ad Alta Voce</string>
+    <string name="pref_auto_reply_calls_delay_summary">Numero di secondi dopo i quali si risponde automaticamente alla chiamata</string>
+    <string name="pref_dashboard_select_devices_title">Seleziona dispositivi...</string>
+    <string name="dashboard_calendar_month_goals_reached_title">% dei passi raggiunti</string>
+    <plurals name="amount_of_days">
+        <item quantity="one">%d giorno</item>
+        <item quantity="many">%d di giorni</item>
+        <item quantity="other">%d giorni</item>
+    </plurals>
+    <string name="pref_dashboard_widget_double_size_title">Grandezza raddoppiata</string>
+    <string name="pref_dashboard_widget_today_upside_down_summary">In modalit脿 24 ore, disegna la mezzanotte in basso e il mezzogiorno in alto nel grafico</string>
+    <string name="pref_dashboard_widgets_order_summary">Seleziona quali widget sono abilitati e in quale ordine vengono visualizzati sul cruscotto</string>
+    <string name="pref_dashboard_widget_settings">Impostazioni widget</string>
+    <string name="pref_dashboard_widget_today_title">Grafico attivit脿</string>
+    <string name="pref_dashboard_widget_today_24h_title">Modalit脿 24 ore</string>
+    <string name="pref_speak_notifications_focus_exclusive_title">Metti in pausa l\'audio di altre applicazioni</string>
+    <string name="pref_speak_notifications_focus_exclusive_summary_on">La riproduzione di altre applicazioni viene messa in pausa durante l\'ascolto della notifica</string>
+    <string name="pref_dashboard_cards_title">Mostra i widget sulle tessere</string>
+    <string name="pref_speak_notifications_focus_exclusive_summary_off">Il volume di riproduzione delle altre applicazioni verr脿 abbassato durante la notifica</string>
+    <string name="pref_header_calls_and_notifications">Chiamate e notifiche</string>
+    <string name="pref_title_bottom_navigation_bar">Barra di navigazione inferiore</string>
+    <string name="pref_summary_bottom_navigation_bar_on">Passa da una schermata principale all\'altra utilizzando la barra di navigazione o lo scorrimento orizzontale</string>
+    <string name="pref_dashboard_widget_double_size_summary">Consenti al widget di occupare due colonne sul cruscotto</string>
+    <string name="pref_dashboard_widget_show_legend_summary">Mostra una legenda sotto il widget che spieghi il significato dei colori</string>
+    <string name="pref_dashboard_all_devices_summary">Somma i dati delle attivit脿 di tutti i dispositivi aggiunti per i totali sul cruscotto</string>
+    <string name="pref_sleepasandroid_slot_title">Registri sveglie</string>
+    <string name="chart_cycling_point_label_distance">Oggi: %.1f km\nTotale: %.1f km</string>
+    <string name="copied_to_clipboard">Copiato negli appunti</string>
+    <string name="pref_sleepasandroid_enable_summary">Attiva l\'integrazione di Sleep As Android</string>
+    <string name="pref_sleepasandroid_device_title">Dispositivo fornitore</string>
+    <string name="pref_sleepasandroid_features_title">Caratteristiche</string>
+    <string name="pref_sleepasandroid_feat_spo2">SpO2</string>
+    <string name="pref_title_huawei_account">Account Huawei</string>
+    <string name="open_camera">Apri Fotocamera</string>
+    <string name="toast_camera_permission_required">Per questa funzione 猫 necessaria l\'autorizzazione della fotocamera.</string>
+    <string name="toast_camera_support_required">Per questa funzione 猫 necessario il supporto della fotocamera.</string>
+    <string name="toast_camera_photo_taken">La foto 猫 stata scattata e salvata in: %s</string>
+    <string name="toggle_debug_mode">Imposta modalit脿 debug</string>
+    <string name="battery_full_threshold">Soglia batteria ricaricata</string>
+    <string name="cannot_upload_watchface_too_many_watchfaces_installed">Impossibile caricare il quadrante, troppi quadranti installati</string>
+    <string name="alarm_slot_reset">Il registro della sveglia 猫 stato reimpostato</string>
+    <string name="garmin_agps_local_file">Documenti locali</string>
+    <string name="battery_percentage_str">%1$s%%</string>
+    <string name="pref_fetch_unknown_files_title">Recuperare file sconosciuti</string>
+    <string name="device_name_bicycle_sensor">Sensore Bici</string>
+    <string name="pref_sleepasandroid_device_summary">Seleziona il dispositivo come fornitore di dati Sleep As Android</string>
+    <string name="folder_is_empty">La cartella 猫 vuota</string>
+    <string name="sleepasandroid_settings">Sleep As Android</string>
+    <string name="insufficient_space_for_upload">Spazio insufficiente per il caricamento</string>
+    <string name="pref_summary_cycling_persistence_interval">Intervallo in secondi per la scrittura dei dati ciclistici correnti nel database</string>
+    <string name="pref_summary_huawei_account">Un account Huawei utilizzato nel processo di associazione. L\'impostazione consente l\'accoppiamento senza reset di fabbrica.</string>
+    <string name="watchface_resolution_doesnt_match">La risoluzione del quadrante non corrisponde allo schermo del dispositivo. La superficie dell\'orologio 猫 %1$s lo schermo del dispositivo 猫 %2$s</string>
+    <string name="pref_battery_polling_configuration">Configurazione controllo batteria</string>
+    <string name="url">URL</string>
+    <string name="pref_garmin_agps_help">L\'elenco seguente contiene tutti gli URL richiesti dall\'orologio per gli aggiornamenti AGPS. 脠 possibile selezionare un file dalla memoria del telefono che verr脿 inviato all\'orologio quando richiede un aggiornamento.</string>
+    <string name="max_val">Massimo: %d</string>
+    <string name="folder">Cartella</string>
+    <string name="show_in_notification">Mostra nella notifica</string>
+    <string name="share_debug_info">Condividi le informazioni di debug</string>
+    <string name="battery_low_notify_enabled">Notifica batteria scarica</string>
+    <string name="realtime_settings">Impostazioni in tempo reale</string>
+    <string name="unsupported">Non supportato</string>
+    <string name="no_folder_selected">Nessuna cartella selezionata</string>
+    <string name="default_percentage">Predefinito (%1$d%%)</string>
+    <string name="battery_low_threshold">Soglia batteria scarica</string>
+    <string name="battery_full_notify_enabled">Notifica batteria ricaricata</string>
+    <string name="min_val">Minimo: %d</string>
+    <string name="pref_sleepasandroid_feat_oximetry">Ossimetria</string>
+    <string name="pref_summary_wheel_diameter">Diametro della ruota in pollici. In genere 29, 27,5 o 26.</string>
+    <string name="pref_sleepasandroid_feat_heartrate">Battito cardiaco</string>
+    <string name="pref_battery_polling_interval_format">Ogni %1$s minuti</string>
+    <string name="pref_title_cycling_persistence_interval">Intervallo di persistenza</string>
+    <string name="pref_sleepasandroid_feat_notifications">Notifiche</string>
+    <string name="pref_sleepasandroid_feat_movement">Accelerometro</string>
+    <string name="garmin_agps_url_i">URL AGPS %1$d</string>
+    <string name="pref_sleepasandroid_feat_alarms">Sveglie</string>
+    <string name="pref_sleepasandroid_slot_summary">Quale registro delle sveglia utilizzare quando se ne imposta una</string>
+    <string name="battery_polling_failed_start">Impossibile avviare il controllo della batteria</string>
+    <string name="title_cycling">Ciclismo</string>
+    <string name="chart_cycling_point_label_speed">%.1f km/h</string>
+    <string name="toast_setting_requires_reconnect">Questa impostazione avr脿 effetto dopo una nuova connessione</string>
+    <string name="pref_battery_polling_enable">Abilita controllo batteria</string>
+    <string name="pref_battery_polling_interval">Intervallo controllo batteria</string>
+    <string name="none">Nessuno</string>
+    <string name="pref_battery_polling_summary">Si tratta di un grosso sforzo, che potrebbe essere rimandato per diverse ragioni</string>
+    <string name="pref_sleepasandroid_features_summary">Il supporto varia da dispositivo a dispositivo</string>
+    <string name="device_name_cycling_sensor">Sensore di pedalata</string>
+    <string name="devicetype_cycling_sensor">Sensore di velocit脿 ciclistica</string>
+    <string name="number_selected_items">%1d selezionati</string>
+    <string name="pref_fetch_unknown_files_summary">Recupera i file di attivit脿 sconosciuti dall\'orologio. Non verranno elaborati, ma saranno salvati nel telefono.</string>
+    <string name="pref_title_wheel_diameter">Diametro ruota</string>
+    <string name="loading">Caricamento鈥�</string>
+    <string name="backup_restore_exporting_preferences">Esportazione preferenze鈥�</string>
+    <string name="inactivity_warnings_minimum_steps_summary">Quantit脿 minima di passi che devono essere compiuti durante i minuti di soglia</string>
+    <string name="backup_restore_exporting_database">Esportazione banca dati.鈥�</string>
+    <string name="smart_ring_measurement_error_worn_incorrectly">Errore di misurazione. I sensori dell\'anello sono orientati correttamente?</string>
+    <string name="prefs_summary_gatt_client_allow_gatt_interactions">Consente di inviare comandi di lettura/scrittura e di connessione della caratteristica BLE</string>
+    <string name="prefs_summary_gatt_client_device_state_updates">Ricevere le modifiche allo stato della connessione BLE tramite gli Intenti</string>
+    <string name="activity_db_management_backup_restore_explanation">Le operazioni di importazione/esportazione consentono di migrare o eseguire il backup di tutte le impostazioni, i dispositivi e i dati di Gadgetbridge da e verso un file zip.\n\nL\'importazione di un file rimuove tutti i dati, i dispositivi e le preferenze esistenti, sostituendoli completamente con il backup.</string>
+    <string name="backup_restore_exporting_finishing">Conclusione esportazione鈥�</string>
+    <string name="backup_restore_importing">Importazione da zip鈥�</string>
+    <string name="label_distance_trip">Parziale: %.1f km</string>
+    <string name="backup_restore_restart_title">Riavvia</string>
+    <string name="devicetype_ble_gatt_client">Client generico BLE GATT</string>
+    <string name="pref_deprecated_media_control_summary">Invia i comandi di controllo dei media come eventi chiave invece che come controller dei media.</string>
+    <string name="backup_restore_exporting_files_i_of_n">Esportazione file... %1d di %2d</string>
+    <string name="backup_restore_importing_loading">Caricamento file鈥�</string>
+    <string name="backup_restore_exporting_files">Esportazione file鈥�</string>
+    <string name="backup_restore_restart_summary">%1s verr脿 riavviato.</string>
+    <string name="label_distance_total_mph">Totale: %.1f mi</string>
+    <string name="file_already_exists">File gi脿 esistente</string>
+    <string name="prefs_title_gatt_client_notification_intents">Trasmissione di Intenti di notifica GATT tramite API Intenti BLE</string>
+    <string name="backup_restore_error_export">Esportazione in zip fallita</string>
+    <string name="error_no_cycling_sensor_found">Non 猫 stato trovato alcun sensore di ciclismo</string>
+    <string name="pref_dnd_follow_phone_title">Segui l\'impostazione ND del telefono</string>
+    <string name="prefs_hrv_monitoring_title">Monitoraggio HRV</string>
+    <string name="prefs_hrv_monitoring_description">Monitoraggio automatico della variabilit脿 della frequenza cardiaca durante la giornata</string>
+    <string name="pref_header_deprecated_functionalities_warning">Le seguenti funzionalit脿 sono state deprecate e saranno presto rimosse dal software.\nSe avete bisogno di attivare una delle seguenti impostazioni, contattate il team del progetto.</string>
+    <string name="pref_deprecated_media_control_title">Controllo multimediale deprecato</string>
+    <string name="prefs_title_gatt_client_api_package">Pacchetto API BLE</string>
+    <string name="prefs_title_ble_intent_api">API Intenti BLE</string>
+    <string name="prefs_summary_gatt_client_api_package">Limita la comunicazione dell\'API Intenti BLE a questo pacchetto</string>
+    <string name="contact_birthday">Compleanno di %1s</string>
+    <string name="birthdays">Compleanni</string>
+    <string name="pref_header_deprecated_functionalities">Funzionalit脿 deprecate</string>
+    <string name="backup_restore_importing_files_i_of_n">Importazione file... %1d di %2d</string>
+    <string name="backup_restore_error_import">Importazione da zip fallita</string>
+    <string name="backup_restore_abort_import_confirmation">Annullare l\'importazione? Ci貌 potrebbe comportare una banca dati corrotta o inconsistente.</string>
+    <string name="inactivity_warnings_minimum_steps_title">Numero minimo di passi</string>
+    <string name="activity_db_management_backup_restore_label">Salvataggio e Ripristino</string>
+    <string name="backup_restore_importing_database">Importazione banca dati鈥�</string>
+    <string name="backup_restore_importing_preferences">Importazione preferenze鈥�</string>
+    <string name="backup_restore_export_complete">Esportazione completata</string>
+    <string name="activity_db_management_import_from_zip">Importa zip</string>
+    <string name="backup_restore_import_complete">Importazione completata</string>
+    <string name="backup_restore_do_not_exit">%s tieni aperta questa schermata fino al termine dell\'operazione.</string>
+    <string name="pref_dashboard_widget_today_yesterday_data_title">Dati di ieri</string>
+    <string name="pref_dashboard_widget_today_time_indicator_title">Indicatore dell\'ora attuale</string>
+    <string name="backup_restore_abort_title">Interrompi</string>
+    <string name="backup_restore_abort_export_confirmation">Annullare l\'esportazione? Il zip parziale sar脿 cancellato.</string>
+    <string name="prefs_summary_gatt_client_notification_intents">Ricevere le modifiche alle caratteristiche BLE tramite gli Intenti</string>
+    <string name="activity_db_management_export_to_zip">Esporta zip</string>
+    <string name="backup_restore_importing_validating">Validazione file鈥�</string>
+    <string name="pref_dashboard_widget_today_yesterday_data_summary">Mostra i dati di ieri oscurati tra l\'ora attuale e la mezzanotte</string>
+    <string name="pref_dnd_follow_phone_summary">Quando la funzione ND 猫 attivata o disattivata sul telefono, viene attivata automaticamente anche sul dispositivo</string>
+    <string name="label_distance_total">Totale: %.1f km</string>
+    <string name="label_distance_trip_mph">Parziale: %.1f mi</string>
+    <string name="smart_ring_measurement_error_unknown">Errore di misura sconosciuto %d ricevuto dall\'anello</string>
+    <string name="prefs_title_gatt_client_allow_gatt_interactions">Consentire l\'interazione con il GATT attraverso l\'API Intenti BLE</string>
+    <string name="backup_restore_warning_files">%1d file non ripristinati: \n%2s</string>
+    <string name="pref_dashboard_widget_today_time_indicator_summary">Mostra un indicatore all\'ora corrente, per separare visivamente i dati di ieri e di oggi</string>
+    <string name="backup_restore_exporting">Esportazione in formato zip鈥�</string>
+    <string name="devicetype_sony_wi_c100">Sony WI-C100</string>
+    <string name="devicetype_garmin_forerunner_235">Garmin Forerunner 235</string>
+    <string name="devicetype_bandw_pseries">Bowers e Wilkins serie P</string>
+    <string name="devicetype_garmin_fenix_6s_pro">Garmin Fenix 6S Pro</string>
+    <string name="devicetype_huawei_band3pro">Huawei Band 3 (Pro)</string>
+    <string name="pref_voice_passthrough_enabled_summary">Consenti ai suoni esterni di trapassare alle tue orecchie</string>
+    <string name="pref_voice_passthrough_enabled">Trapasso</string>
+    <string name="pref_voice_passthrough_level">Livello di trapasso</string>
+    <string name="devicetype_garmin_forerunner_55">Garmin Forerunner 55</string>
+    <string name="devicetype_oppo_enco_air">Oppo Enco Air</string>
+    <string name="pref_wear_sensor_summary">Rileva quando il dispositivo non viene indossato</string>
+    <string name="pref_wear_sensor_title">Sensore Indossabile</string>
+    <string name="devicetype_garmin_forerunner_620">Garmin Forerunner 620</string>
+    <string name="pref_developer_add_test_activities_summary">Popolare il database con attivit脿 di test fittizie</string>
+    <string name="pref_developer_add_test_activities_title">Aggiungi attivit脿 di test</string>
+    <string name="devicetype_garmin_instinct_2">Garmin Instinct 2</string>
+    <string name="devicetype_realme_buds_t110">Realme Buds T110</string>
+    <string name="title_activity_musicmanager">Gestore musica</string>
+    <string name="music_rename_playlist">Rinomina lista di riproduzione</string>
+    <string name="music_new_playlist">Nuova lista di riproduzione</string>
+    <string name="music_error">Si 猫 verificato un errore</string>
+    <string name="music_all_songs">Tutte le canzoni</string>
+    <string name="music_huawei_device_info">Formati supportati: %1$s\nMemoria dell\'orologio: %2$d MB</string>
+    <string name="pref_music_management_title">Gestisci musica</string>
+    <string name="pref_music_management_summary">Gestisci la musica sull\'orologio</string>
+    <string name="music_delete_confirm_description">Vuoi davvero cancellare \'%1$s\'?</string>
+    <string name="music_add_to_playlist">Aggiungi alla lista di riproduzione</string>
+    <string name="music_delete_from_playlist">Rimuovi dalla lista di riproduzione</string>
+    <string name="music_delete">Cancella canzone</string>
+    <string name="active_calories">Calorie attive</string>
+    <string name="total_calories_burnt">Totale bruciato</string>
+    <string name="active_calories_short">Attivo</string>
+    <string name="activity_prefs_goal_active_calories_burnt">Obiettivo giornaliero: calorie attive bruciate</string>
+    <string name="redmi_buds_5_pro_adaptive_sound">Suono adattivo</string>
+    <string name="redmi_buds_5_pro_adaptive_sound_description">Regola il suono in base alla forma dell\'orecchio e all\'ambiente</string>
+    <string name="redmi_buds_5_pro_equalizer_preset_treble">Migliora gli acuti</string>
+    <string name="redmi_buds_5_pro_equalizer_band_62">62 Hz</string>
+    <string name="redmi_buds_5_pro_equalizer_band_125">125 Hz</string>
+    <string name="devicetype_miband9pro">Xiaomi Smart Band 9 Pro</string>
+    <string name="redmi_buds_5_pro_transparency_ambient">Migliora i suoni ambientali</string>
+    <string name="redmi_buds_5_pro_combo_anc_off">ANC / Spento</string>
+    <string name="redmi_buds_5_pro_double_connection_description">Consentire agli auricolari di connettersi a due dispositivi contemporaneamente</string>
+    <string name="redmi_buds_5_pro_equalizer_preset_bass">Migliora i bassi</string>
+    <string name="redmi_buds_5_pro_equalizer_preset_voice">Migliora la voce</string>
+    <string name="redmi_buds_5_pro_equalizer_band_250">250 Hz</string>
+    <string name="redmi_buds_5_pro_equalizer_band_500">500 Hz</string>
+    <string name="redmi_buds_5_pro_equalizer_band_4k">4 kHz</string>
+    <string name="redmi_buds_5_pro_equalizer_band_2k">2 kHz</string>
+    <string name="redmi_buds_5_pro_anc_balanced">Equilibrato</string>
+    <string name="redmi_buds_5_pro_transparency_voice">Migliora le voci</string>
+    <string name="redmi_buds_5_pro_double_connection">Doppia connessione</string>
+    <string name="redmi_buds_5_pro_equalizer_preset_custom">Personalizzato</string>
+    <string name="redmi_buds_5_pro_equalizer_band_1k">1 kHz</string>
+    <string name="active_calories_goal">Obiettivo attivo</string>
+    <string name="first_start_overview_dashboard">Il cruscotto consente di avere una rapida idea di come si sta procedendo oggi. La vista del calendario mostra lo stato dei vostri obiettivi per un mese intero.</string>
+    <string name="respiratoryrate">Frequenza respiratoria</string>
+    <string name="permission_bluetooth_connect_title">Connessione Bluetooth</string>
+    <string name="first_start_overview_desc">Gadgetbridge ha due visuali principali, ognuna con un proprio scopo.</string>
+    <string name="permission_post_notification_summary">Pubblicazione di notifiche permanenti che mantengono il servizio in funzione</string>
+    <string name="permission_internet_access_title">Accesso alla rete</string>
+    <string name="permission_internet_access_summary">Sincronizzazione con le risorse online</string>
+    <string name="permission_contacts_summary">Invia contatti ai dispositivi</string>
+    <string name="permission_receive_sms_title">Ricevi SMS</string>
+    <string name="menuitem_calories_active_goal">Obiettivo calorie (attive)</string>
+    <string name="redmi_buds_5_pro_transparency_strength">Grado di Trasparenza</string>
+    <string name="redmi_buds_5_pro_combo_anc_transparency">ANC / Trasparenza</string>
+    <string name="redmi_buds_5_pro_combo_all">ANC / Trasparenza / Off</string>
+    <string name="redmi_buds_5_pro_equalizer_preset_standard">Standard</string>
+    <string name="redmi_buds_5_pro_equalizer_neg2">-2 dB</string>
+    <string name="redmi_buds_5_pro_equalizer_3">3 dB</string>
+    <string name="redmi_buds_5_pro_equalizer_5">5 dB</string>
+    <string name="redmi_buds_5_pro_equalizer_6">6 dB</string>
+    <string name="menuitem_calories_segmented">Calorie (segmentate)</string>
+    <string name="redmi_buds_5_pro_transparency_regular">Regolare</string>
+    <string name="redmi_buds_5_pro_anc_deep">Profondo</string>
+    <string name="redmi_buds_5_pro_combo_transparency_off">Trasparenza / Off</string>
+    <string name="redmi_buds_5_pro_equalizer_band_8k">8 kHz</string>
+    <string name="redmi_buds_5_pro_equalizer_1">1 dB</string>
+    <string name="redmi_buds_5_pro_equalizer_zero">0 dB</string>
+    <string name="redmi_buds_5_pro_equalizer_2">2 dB</string>
+    <string name="redmi_buds_5_pro_equalizer_4">4 dB</string>
+    <string name="first_start_overview_devices">La vista Dispositivi mostra tutti i dispositivi configurati e il loro stato e d脿 accesso a funzioni specifiche del dispositivo, come grafici dettagliati, impostazioni, app e sveglie.</string>
+    <string name="permission_post_notification_title">Pubblica notifiche</string>
+    <string name="permission_read_phone_state_summary">Leggi lo stato delle chiamate in corso</string>
+    <string name="permission_call_phone_title">Chiamata</string>
+    <string name="permission_receive_sms_summary">Inoltro di messaggi SMS ai dispositivi</string>
+    <string name="permission_call_phone_summary">Avvia una telefonata dai dispositivi</string>
+    <string name="permission_process_outgoing_calls_title">Elabora le chiamate in uscita</string>
+    <string name="first_start_welcome_title">Benvenuto</string>
+    <string name="first_start_open_source_title">Open Source</string>
+    <string name="first_start_permissions_request_button">Richiedi</string>
+    <string name="permission_bluetooth_admin_summary">Scopri e accoppia i dispositivi Bluetooth</string>
+    <string name="permission_bluetooth_scan_title">Scansione bluetooth</string>
+    <string name="permission_bluetooth_scan_summary">Scansione di nuovi dispositivi Bluetooth</string>
+    <string name="permission_contacts_title">Contatti</string>
+    <string name="permission_bluetooth_title">Bluetooth</string>
+    <string name="permission_bluetooth_summary">Collegamento ai dispositivi Bluetooth</string>
+    <string name="permission_bluetooth_admin_title">Amministratore Bluetooth</string>
+    <string name="permission_calendar_title">Calendario</string>
+    <string name="permission_calendar_summary">Invia calendario ai dispositivi</string>
+    <string name="permission_read_phone_state_title">Leggi lo stato del telefono</string>
+    <string name="permission_send_sms_summary">Invio di SMS (risposta automatica) dai dispositivi</string>
+    <string name="permission_read_call_log_title">Leggi il registro delle chiamate</string>
+    <string name="permission_read_call_log_summary">Inoltra il registro chiamate ai dispositivi</string>
+    <string name="permission_answer_phone_calls_title">Rispondi alle telefonate</string>
+    <string name="permission_query_all_packages_title">Interroga tutti i pacchetti</string>
+    <string name="first_start_overview_title">Panoramica</string>
+    <string name="permission_external_storage_title">Archiviazione esterna</string>
+    <string name="sleep_avg">MEDIA Sonno</string>
+    <string name="lowest">Minimo</string>
+    <string name="highest">Massimo</string>
+    <string name="first_start_intro_welcome_to">Benvenuto in</string>
+    <string name="music_delete_multiple_confirm_description">Siete sicuri di voler eliminare \'%1$d\' brani?</string>
+    <string name="music_multiselect_limit">Limite di selezione di pi霉 elementi raggiunto</string>
+    <string name="first_start_open_source_text">Gadgetbridge 猫 un\'applicazione open source. 脠 sviluppata dalla comunit脿, per la comunit脿.\n\nChiunque pu貌 contribuire con codice, documentazione, test e donazioni.\n\nGadgetbridge non contiene pubblicit脿 n茅 traccianti. Conserva i tuoi dati localmente sul tuo dispositivo Android, quindi 猫 al 100% rispettoso della privacy.\n\nVisita il nostro sito web per ulteriori informazioni, documentazione e link ai nostri canali di comunicazione.</string>
+    <string name="first_start_intro_tag_line">Liberatevi dalle applicazioni proprietarie e dai servizi cloud dei fornitori di dispositivi.</string>
+    <string name="first_start_permissions_request_all_button">Richiedi tutte le autorizzazioni</string>
+    <string name="first_start_permissions_desc">Gadgetbridge ha bisogno di molti permessi per svolgere tutte le sue funzioni. Esamina i permessi e i loro scopi qui di seguito.</string>
+    <string name="first_start_permissions_title">Autorizzazioni</string>
+    <string name="first_start_get_started_title">Iniziamo</string>
+    <string name="first_start_get_started_add_first_device_button">Aggiungi il primo dispositivo</string>
+    <string name="first_start_get_started_desc">Per iniziare, aggiungi il primo dispositivo direttamente da questa schermata, ripristina un backup o inizia con un database pulito.</string>
+    <string name="first_start_get_started_restore_button">Ripristina salvataggio</string>
+    <string name="first_start_get_started_go_to_app_button">Vai all\'app</string>
+    <string name="permission_displayover_title">Mostra sopra altre app</string>
+    <string name="permission_notifications_summary">Inoltro delle notifiche ai disoositivi collegati</string>
+    <string name="permission_manage_dnd_title">Gestione di Non disturbare</string>
+    <string name="permission_manage_dnd_summary">Modifica del criterio di notifica ND</string>
+    <string name="permission_displayover_summary">Utilizzato da Bangle.js per l\'avvio di applicazioni e altre funzionalit脿 sul telefono</string>
+    <string name="permission_fine_location_title">Posizione precisa</string>
+    <string name="permission_fine_location_summary">Scansione dei dispositivi Bluetooth</string>
+    <string name="permission_background_location_title">Posizione in secondo piano</string>
+    <string name="permission_background_location_summary">Scansione dei dispositivi Bluetooth in secondo piano e invio della posizione a determinati dispositivi</string>
+    <string name="permission_process_outgoing_calls_summary">Legge il numero di una chiamata in uscita per visualizzarlo su un dispositivo</string>
+    <string name="permission_bluetooth_connect_summary">Connessione a dispositivi Bluetooth gi脿 accoppiati</string>
+    <string name="permission_answer_phone_calls_summary">Rispondere alle telefonate dai dispositivi</string>
+    <string name="permission_query_all_packages_summary">Lettura dei nomi e delle icone di tutte le app installate</string>
+    <string name="permission_external_storage_summary">Utilizzo di immagini, suonerie, file di app e altro ancora</string>
+    <string name="sleep_score">Punteggio sonno</string>
+    <string name="sleep_score_value">Punteggio: %1d</string>
+    <string name="devicetype_redmi_buds_5_pro">Redmi Buds 5 Pro</string>
+    <string name="devicetype_colmi_r09">Colmi R09</string>
+    <string name="watchface_widget_type_sp02">SpO2</string>
+    <string name="devicetype_oppo_enco_air2">Oppo Enco Air2</string>
+    <string name="redmi_buds_5_pro_equalizer_band_16k">16 kHz</string>
+    <string name="redmi_buds_5_pro_equalizer_neg5">-5 dB</string>
+    <string name="redmi_buds_5_pro_equalizer_neg6">-6 dB</string>
+    <string name="redmi_buds_5_pro_equalizer_band_12k">12 kHz</string>
+    <string name="redmi_buds_5_pro_equalizer_neg3">-3 dB</string>
+    <string name="redmi_buds_5_pro_equalizer_neg1">-1 dB</string>
+    <string name="devicetype_huawei_band2pro">Huawei Band 2 (Pro)</string>
+    <string name="redmi_buds_5_pro_equalizer_neg4">-4 dB</string>
+    <string name="redmi_buds_5_pro_anc_light">Leggero</string>
+    <string name="devicetype_huawei_freebuds_5i">Huawei FreeBuds 5i</string>
+    <string name="maxJumpRate">Frequenza Massima Salti</string>
+    <string name="jumps_minute">salti/min</string>
+    <string name="jumps_unit">salti</string>
+    <string name="Jumps">Salti</string>
+    <string name="avgJumpRate">Frequenza Media Salti</string>
+    <string name="totalJumps">Salti Totali</string>
+    <string name="permission_send_sms_title">Invia SMS</string>
+    <string name="garmin_intensity_minutes">Minuti di intensit脿</string>
+    <string name="stepRateMax">Frequenza massima dei passi</string>
+    <string name="weekly_total">Totale settimanale</string>
+    <string name="devicetype_redmi_watch_5_lite">Redmi Watch 5 Lite</string>
+    <string name="devicetype_garmin_fenix_7x">Garmin Fenix 7X</string>
+</resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml
new file mode 100644
index 0000000..51d3176
--- /dev/null
+++ b/app/src/main/res/values-ja/strings.xml
@@ -0,0 +1,2707 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources xmlns:tools="http://schemas.android.com/tools">
+    <string name="application_name_generic">Gadgetbridge</string>
+    <string name="title_activity_controlcenter_generic">Gadgetbridge</string>
+    <string name="action_settings">瑷畾</string>
+    <string name="action_debug">銉囥儛銉冦偘</string>
+    <string name="action_quit">绲備簡</string>
+    <string name="action_donate">瀵勪粯</string>
+    <string name="controlcenter_fetch_activity_data">鍚屾湡</string>
+    <string name="controlcenter_find_device">銉囥儛銈ゃ偣銈掕銇ゃ亼銈�</string>
+    <string name="controlcenter_take_screenshot">銈广偗銉兗銉炽偡銉с儍銉堛倰鎾倠</string>
+    <string name="controlcenter_disconnect">鍒囨柇</string>
+    <string name="controlcenter_delete_device">銉囥儛銈ゃ偣銈掑墛闄�</string>
+    <string name="controlcenter_delete_device_name">%1$s 銇墛闄�</string>
+    <string name="controlcenter_delete_device_dialogmessage">銉囥儛銈ゃ偣銇ㄩ枹閫c仚銈嬨儑銉笺偪銇屽墛闄ゃ仌銈屻伨銇欙紒</string>
+    <string name="controlcenter_navigation_drawer_open">銉娿儞銈层兗銈枫儳銉炽儔銉儻銉笺倰闁嬨亸</string>
+    <string name="controlcenter_navigation_drawer_close">銉娿儞銈层兗銈枫儳銉炽儔銉儻銉笺倰闁夈仒銈�</string>
+    <string name="controlcenter_snackbar_need_longpress">銈兗銉夈倰闀锋娂銇椼仚銈嬨仺鍒囨柇銇椼伨銇�</string>
+    <string name="controlcenter_snackbar_disconnecting">鍒囨柇涓�</string>
+    <string name="controlcenter_snackbar_connecting">鎺ョ稓涓��</string>
+    <string name="controlcenter_snackbar_requested_screenshot">銉囥儛銈ゃ偣銇偣銈儶銉笺兂銈枫儳銉冦儓銈掑彇寰椾腑</string>
+    <string name="title_activity_debug">銉囥儛銉冦偘</string>
+    <!--Strings related to AppManager-->
+    <string name="title_activity_appmanager">銈€儣銉伄绠$悊</string>
+    <string name="appmanager_cached_watchapps_watchfaces">銈儯銉冦偡銉ヤ腑銇偄銉椼儶</string>
+    <string name="appmanager_installed_watchapps">銈ゃ兂銈广儓銉笺儷娓堛伩銇偄銉椼儶</string>
+    <string name="appmanager_installed_watchfaces">銈ゃ兂銈广儓銉笺儷娓堛伩銇偊銈┿儍銉併儠銈с偆銈�</string>
+    <string name="appmananger_app_delete">鍓婇櫎</string>
+    <string name="appmananger_app_delete_cache">銈儯銉冦偡銉ャ亱銈夊墛闄�</string>
+    <string name="appmananger_app_reinstall">鍐嶃偆銉炽偣銉堛兗銉�</string>
+    <string name="appmanager_app_openinstore">Pebble 銈€儣銉偣銉堛偄銇ф绱�</string>
+    <string name="appmanager_health_activate">鏈夊姽鍖�</string>
+    <string name="appmanager_health_deactivate">鐒″姽鍖�</string>
+    <string name="appmanager_hrm_activate">HRM 銈掓湁鍔广伀銇欍倠</string>
+    <string name="appmanager_hrm_deactivate">HRM 銈掔劇鍔广伀銇欍倠</string>
+    <string name="appmanager_weather_activate">銈枫偣銉嗐儬銇ぉ姘椼偄銉椼儶銈掓湁鍔广伀銇欍倠</string>
+    <string name="appmanager_weather_deactivate">銈枫偣銉嗐儬銇ぉ姘椼偄銉椼儶銈掔劇鍔广伀銇欍倠</string>
+    <string name="appmanager_weather_install_provider">澶╂皸閫氱煡銈€儣銉倰銈ゃ兂銈广儓銉笺儷</string>
+    <string name="app_configure">瑷畾</string>
+    <string name="app_move_to_top">鍏堥牠銇Щ鍕�</string>
+    <!--Strings related to CalBlacklist-->
+    <string name="title_activity_calblacklist">銉栥儵銉冦偗銉偣銉堛伀銇椼仧銈儸銉炽儉銉�</string>
+    <!--Strings related to FwAppInstaller-->
+    <string name="title_activity_fw_app_insaller">銉曘偂銉笺儬銈︺偋銈�/銈€儣銉伄銈ゃ兂銈广儓銉笺儷</string>
+    <string name="fw_upgrade_notice">%s 銈掋偆銉炽偣銉堛兗銉仐銈堛亞銇ㄣ仐銇︺亜銇俱仚銆�</string>
+    <string name="fw_multi_upgrade_notice">Mi Band銇�佺従鍦ㄣ伄銉曘偂銉笺儬銈︺偋銈€伄浠c倧銈娿伀 %1$s 銇娿倛銇� %2$s 銈掋偆銉炽偣銉堛兗銉仐銈堛亞銇ㄣ仐銇︺亜銇俱仚銆�</string>
+    <string name="miband_firmware_known">銇撱伄銉曘偂銉笺儬銈︺偋銈€伅銉嗐偣銉堟笀銇裤仹銆丟adgetbridge銇ㄤ簰鎻涙�с亴銇傘倠銇撱仺銇岀⒑瑾嶃仌銈屻仸銇勩伨銇欍��</string>
+    <string name="miband_firmware_unknown_warning">銇撱伄銉曘偂銉笺儬銈︺偋銈€伅鏈儐銈广儓銇с�丟adgetbridge銇ㄤ簰鎻涙�с亴銇亜鍙兘鎬с亴銇傘倞銇俱仚銆�
+\n
+\n銉曘儵銉冦偡銉ャ仚銈嬨亾銇ㄣ伅鎺ㄥエ銇曘倢銇俱仜銈擄紒</string>
+    <string name="miband_firmware_suggest_whitelist">缍氳銇椼�併倐銇楁銇椼亸鍕曚綔銇椼仧鍫村悎銇�丟adgetbridge銇枊鐧鸿�呫伀銉曘偂銉笺儬銈︺偋銈€儛銉笺偢銉с兂 %s 銈掋儧銉偆銉堛儶銈广儓銇叆銈屻倠銈堛亞浼濄亪銇︺亸銇犮仌銇勩��</string>
+    <!--Strings related to Settings-->
+    <string name="title_activity_settings">瑷畾</string>
+    <string name="pref_header_general">鍏ㄨ埇瑷畾</string>
+    <string name="pref_title_general_autoconnectonbluetooth">Bluetooth銇屻偑銉炽伀銇仯銇熴仺銇嶃伀 Gadgetbridge 銉囥儛銈ゃ偣銇帴缍氥仚銈�</string>
+    <string name="pref_title_general_autostartonboot">鑷嫊鐨勩伀闁嬪銇欍倠</string>
+    <string name="pref_title_general_autoreconnect">鑷嫊鐨勩伀鍐嶆帴缍氥仚銈�</string>
+    <string name="pref_title_audio_player">鍎厛銇欍倠闊虫ソ銉椼儸銈ゃ儰銉�</string>
+    <string name="pref_default">銉囥儠銈┿儷銉�</string>
+    <string name="pref_header_datetime">鏃ヤ粯銇ㄦ檪鍒�</string>
+    <string name="pref_title_datetime_syctimeonconnect">鏅傞枔銈掑悓鏈熴仚銈�</string>
+    <string name="pref_summary_datetime_syctimeonconnect">銉囥儛銈ゃ偣鎺ョ稓鏅傘仺銆丄ndroid 銇ф檪闁撱伨銇熴伅銈裤偆銉犮偩銉笺兂銈掑鏇淬仐銇熴仺銇嶃伀銉囥儛銈ゃ偣銇ㄦ檪闁撱倰鍚屾湡銇椼伨銇�</string>
+    <string name="pref_title_theme">銉嗐兗銉�</string>
+    <string name="pref_theme_light">銉┿偆銉�</string>
+    <string name="pref_theme_dark">銉�銉笺偗</string>
+    <string name="pref_title_language">瑷�瑾�</string>
+    <string name="pref_title_minimize_priority">Gadgetbridge 銇�氱煡銈掗潪琛ㄧず銇仚銈�</string>
+    <string name="pref_summary_minimize_priority_off">銈广儐銉笺偪銈广儛銉笺伄銈€偆銈炽兂銇ㄣ儹銉冦偗鐢婚潰銇�氱煡銇岃〃绀恒仌銈屻伨銇�</string>
+    <string name="pref_summary_minimize_priority_on">銈广儐銉笺偪銈广儛銉笺伄銈€偆銈炽兂銇ㄣ儹銉冦偗鐢婚潰銇�氱煡銇〃绀恒仌銈屻伨銇涖倱</string>
+    <string name="pref_header_notifications">閫氱煡</string>
+    <string name="pref_title_notifications_repetitions">绻般倞杩斻仐</string>
+    <string name="pref_title_notifications_call">鐫�淇¢�氱煡</string>
+    <string name="pref_title_notifications_sms">SMS</string>
+    <string name="pref_title_notifications_pebblemsg">Pebble 銉°儍銈汇兗銈�</string>
+    <string name="pref_summary_notifications_pebblemsg">PebbleKit 绲岀敱銇� Pebble 銇�氱煡銈掗�佷俊銇欍倠銈€儣銉偙銉笺偡銉с兂銈掋偟銉濄兗銉堛仐銇俱仚銆�</string>
+    <string name="pref_title_notifications_generic">閫氱煡鍏ㄨ埇銇偟銉濄兗銉�</string>
+    <string name="pref_title_whenscreenon">Android 銇敾闈€亴 ON 銇仺銇嶃伀銈傘偊銈┿儍銉併伀閫氱煡銈掕〃绀恒仚銈�</string>
+    <string name="pref_title_notification_filter">銈点偆銉兂銉� 銉€兗銉�</string>
+    <string name="pref_summary_notification_filter">銈点偆銉兂銉� 銉€兗銉夈亴鏈夊姽銇仺銇嶃伅銇欍伖銇︺伄閫氱煡銈掋儢銉儍銈仚銈�</string>
+    <string name="pref_title_transliteration">缈诲瓧</string>
+    <string name="pref_summary_transliteration">銉囥儛銈ゃ偣銇屻儲銉笺偠銉笺伄瑷�瑾炪伄銉曘偐銉炽儓銈掋偟銉濄兗銉堛仐銇︺亜銇亜鍫村悎銆併亾銈屻倰鏈夊姽銇仐銇︺亸銇犮仌銇�</string>
+    <string name="always">甯搞伀</string>
+    <string name="when_screen_off">銈广偗銉兗銉炽亴 OFF 銇仺銇�</string>
+    <string name="never">銇仐</string>
+    <string name="pref_header_privacy">銉椼儵銈ゃ儛銈枫兗</string>
+    <string name="pref_title_call_privacy_mode">閫氳┍銇儣銉┿偆銉愩偡銉艰ō瀹�</string>
+    <string name="pref_call_privacy_mode_off">鍚嶅墠銇ㄧ暘鍙枫倰琛ㄧず</string>
+    <string name="pref_call_privacy_mode_name">鍚嶅墠銇潪琛ㄧず銆佺暘鍙枫伅琛ㄧず</string>
+    <string name="pref_call_privacy_mode_number">鐣彿銇潪琛ㄧず銆佸悕鍓嶃伅琛ㄧず</string>
+    <string name="pref_call_privacy_mode_complete">鍚嶅墠銇ㄧ暘鍙枫倰闈炶〃绀�</string>
+    <string name="pref_blacklist_calendars">銉栥儵銉冦偗銉偣銉堛伄銈儸銉炽儉銉�</string>
+    <string name="pref_header_cannned_messages">瀹氬瀷銉°儍銈汇兗銈�</string>
+    <string name="pref_title_canned_replies">杩斾俊</string>
+    <string name="pref_title_canned_reply_suffix">鍏遍�氥伄鎺ュ熬瑾�</string>
+    <string name="pref_title_canned_messages_dismisscall"/>
+    <string name="pref_title_canned_messages_set">銉囥儛銈ゃ偣銇洿鏂�</string>
+    <string name="pref_header_development">闁嬬櫤鑰呯敤瑷畾</string>
+    <string name="pref_title_development_miaddr">Mi Band 銇偄銉夈儸銈�</string>
+    <string name="pref_title_pebble_settings">Pebble 銇ō瀹�</string>
+    <string name="pref_header_activitytrackers">銈€偗銉嗐偅銉撱儐銈c儓銉┿儍銈兗</string>
+    <string name="pref_title_pebble_activitytracker">鍎厛銇欍倠銈€偗銉嗐偅銉撱儐銈c儓銉┿儍銈兗</string>
+    <string name="pref_title_pebble_sync_health">Pebble Health 銇悓鏈�</string>
+    <string name="pref_title_pebble_sync_misfit">Misfit 銇悓鏈�</string>
+    <string name="pref_title_pebble_sync_morpheuz">Morpheuz 銇悓鏈�</string>
+    <string name="pref_title_enable_outgoing_call">鐧轰俊銇偟銉濄兗銉�</string>
+    <string name="pref_summary_enable_outgoing_call">銇撱倢銈掔劇鍔广伀銇欍倠銇ㄣ�丳ebble 2/LE 銇櫤淇℃檪銇尟鍕曘仐銇亸銇倞銇俱仚</string>
+    <string name="pref_title_enable_pebblekit">銈点兗銉夈儜銉笺儐銈h= Android 銈€儣銉伄銈€偗銈汇偣銈掕ū鍙�</string>
+    <string name="pref_summary_enable_pebblekit">PebbleKit銈掍娇鐢ㄣ仐銇ndroid銈€儣銉敤銇疅楱撶殑銇偟銉濄兗銉堛倰鏈夊姽銇仐銇俱仚</string>
+    <string name="pref_header_pebble_timeline">Pebble 銈裤偆銉犮儵銈ゃ兂</string>
+    <string name="pref_title_sunrise_sunset">鏃ャ伄鍑恒仺鏃ャ伄鍏ャ倞</string>
+    <string name="pref_summary_sunrise_sunset">鍫存墍銇熀銇ャ亜銇︺�丳ebble 銇偪銈ゃ儬銉┿偆銉炽伀鏃ャ伄鍑恒兓鏃ャ伄鍏ャ倞銇檪闁撱倰閫佷俊銇椼伨銇�</string>
+    <string name="pref_title_enable_calendar_sync">銈儸銉炽儉銉笺伄鍚屾湡</string>
+    <string name="pref_summary_enable_calendar_sync">銈儸銉炽儉銉笺伄銈ゃ儥銉炽儓銈掋偪銈ゃ儬銉┿偆銉炽伀閫佷俊銇椼伨銇�</string>
+    <string name="pref_title_autoremove_notifications">娑堝幓銇椼仧閫氱煡銈掕嚜鍕曞墛闄ゃ仚銈�</string>
+    <string name="pref_summary_autoremove_notifications">Android 銇ч�氱煡銇屽墛闄ゃ仌銈屻仧銇ㄣ亶銇�併儑銉愩偆銈广亱銈夎嚜鍕曠殑銇墛闄ゃ仐銇俱仚</string>
+    <string name="pref_title_pebble_privacy_mode">銉椼儵銈ゃ儛銈枫兗銉€兗銉�</string>
+    <string name="pref_pebble_privacy_mode_off">閫氬父銇�氱煡</string>
+    <string name="pref_pebble_privacy_mode_content">閫氱煡銉嗐偔銈广儓銈掔敾闈㈠銇偡銉曘儓銇欍倠</string>
+    <string name="pref_pebble_privacy_mode_complete">閫氱煡銈€偆銈炽兂銇伩銈掕〃绀恒仚銈�</string>
+    <string name="pref_header_location">浣嶇疆鎯呭牨</string>
+    <string name="pref_title_location_aquire">浣嶇疆鎯呭牨銈掑彇寰椼仚銈�</string>
+    <string name="pref_title_location_latitude">绶害</string>
+    <string name="pref_title_location_longitude">绲屽害</string>
+    <string name="pref_title_location_keep_uptodate">浣嶇疆鎯呭牨銇洿鏂般倰缍欑稓銇欍倠</string>
+    <string name="pref_summary_location_keep_uptodate">鍕曚綔涓伀鐝惧湪銇綅缃儏鍫便伄鍙栧緱銈掕│銇裤�佺櫥閷层仌銈屻仸銇勩倠浣嶇疆鎯呭牨銇唬鏇裤仺銇椼仸浣跨敤銇椼伨銇�</string>
+    <string name="toast_enable_networklocationprovider">銉嶃儍銉堛儻銉笺偗銇綅缃儏鍫便倰鏈夊姽銇仐銇︺亸銇犮仌銇�</string>
+    <string name="toast_aqurired_networklocation">浣嶇疆鎯呭牨銈掑彇寰椼仐銇俱仐銇�</string>
+    <string name="pref_title_pebble_forceprotocol">閫氱煡銉椼儹銉堛偝銉倰寮峰埗銇欍倠</string>
+    <string name="pref_summary_pebble_forceprotocol">銇撱伄銈儣銈枫儳銉炽伅銆併儠銈°兗銉犮偊銈с偄銇儛銉笺偢銉с兂銇繙銇樸仸寮峰埗鐨勩伀鏈�鏂般伄閫氱煡銉椼儹銉堛偝銉倰浣跨敤銇椼伨銇欍�備綍銈掋仐銇︺亜銈嬨亱銈忋亱銇c仸銇勩倠鍫村悎銇伩鏈夊姽銇仐銇︺亸銇犮仌銇勶紒</string>
+    <string name="pref_title_pebble_forceuntested">鏈儐銈广儓銇鑳姐倰鏈夊姽銇仚銈�</string>
+    <string name="pref_summary_pebble_forceuntested">銉嗐偣銉堛仌銈屻仸銇勩仾銇勬鑳姐倰鏈夊姽銇仐銇俱仚銆備綍銈掋仐銇︺亜銈嬨亱銈忋亱銇c仸銇勩倠鍫村悎銇伩鏈夊姽銇仐銇︺亸銇犮仌銇勶紒</string>
+    <string name="pref_title_pebble_forcele">甯搞伀 BLE 銈掑劒鍏堛仚銈�</string>
+    <string name="pref_summary_pebble_forcele">Bluetooth Classic 銇唬銈忋倞銇疅楱撶殑銇� Pebble LE 銇偟銉濄兗銉堛倰銇欍伖銇︺伄 Pebble 銇娇鐢ㄣ仐銇俱仚銆傞潪 LE 銇с儦銈€儶銉炽偘銇椼仧銇傘仺銇� Pebble LE 銇ф帴缍氥仚銈嬪繀瑕併亴銇傘倞銇俱仚</string>
+    <string name="pref_title_pebble_mtu_limit">Pebble 2/LE GATT MTU 鍒堕檺</string>
+    <string name="pref_summary_pebble_mtu_limit">Pebble 2/Pebble LE 銇屾湡寰呫仼銇娿倞銇鑳姐仐銇亜鍫村悎銇�併亾銇ō瀹氥倰瑭︺仐銇� MTU 銈掑埗闄愩仐銇︺亸銇犮仌銇� (鏈夊姽绡勫洸 20鈥�512)</string>
+    <string name="pref_title_pebble_enable_applogs">銈︺偐銉冦儊銈€儣銉伄銉偘銈掓湁鍔广伀銇欍倠</string>
+    <string name="pref_summary_pebble_enable_applogs">Gadgetbridge 銇屻偊銈┿儍銉併偄銉椼儶銇嬨倝銉偘銈掕閷层仚銈嬨倛銇嗐伀銇椼伨銇�(鍐嶆帴缍氥亴蹇呰銇с仚)</string>
+    <string name="pref_title_pebble_always_ack_pebblekit">鏃╂湡 ACK PebbleKit</string>
+    <string name="pref_summary_pebble_always_ack_pebblekit">澶栭儴銇偟銉笺儔銉戙兗銉嗐偅銈€儣銉偙銉笺偡銉с兂銇�佷俊銇曘倢銇熴儭銉冦偦銉笺偢銇�佸父銇嵆搴с伀鎵胯獚銇曘倢銇俱仚</string>
+    <string name="pref_title_pebble_reconnect_attempts">鍐嶆帴缍氥伄瑭﹁</string>
+    <string name="pref_title_unit_system">鍗樹綅</string>
+    <string name="pref_title_timeformat">鏅傚埢銇舰寮�</string>
+    <string name="pref_title_screentime">鐢婚潰銈兂銇檪闁�</string>
+    <string name="prefs_title_all_day_heart_rate">鍏ㄦ棩蹇冩媿娓畾</string>
+    <string name="preferences_hplus_settings">HPlus/Makibes 瑷畾</string>
+    <string name="not_connected">鏈帴缍�</string>
+    <string name="connecting">鎺ョ稓涓�</string>
+    <string name="connected">鎺ョ稓娓堛伩</string>
+    <string name="unknown_state">涓嶆槑銇姸鎱�</string>
+    <string name="_unknown_">(涓嶆槑)</string>
+    <string name="test">銉嗐偣銉�</string>
+    <string name="test_notification">閫氱煡銇儐銈广儓</string>
+    <string name="this_is_a_test_notification_from_gadgetbridge">銇撱倢銇� Gadgetbridge 銇嬨倝銇儐銈广儓閫氱煡銇с仚</string>
+    <string name="bluetooth_is_not_supported_">Bluetooth 銇偟銉濄兗銉堛仌銈屻仸銇勩伨銇涖倱銆�</string>
+    <string name="bluetooth_is_disabled_">Bluetooth 銇岀劇鍔广仹銇欍��</string>
+    <string name="tap_connected_device_for_app_mananger">鎺ョ稓娓堛伩銇儑銉愩偆銈广倰銈裤儍銉椼仐銇︺偄銉椼儶銇鐞嗐倰琛ㄧず</string>
+    <string name="tap_connected_device_for_activity">鎺ョ稓娓堛伩銇儑銉愩偆銈广倰銈裤儍銉椼仐銇︺偄銈儐銈c儞銉嗐偅銈掕〃绀�</string>
+    <string name="tap_connected_device_for_vibration">鎺ョ稓娓堛伩銇儑銉愩偆銈广倰銈裤儍銉椼仐銇︽尟鍕曘仚銈�</string>
+    <string name="tap_a_device_to_connect">鎺ョ稓銇欍倠銉囥儛銈ゃ偣銈掋偪銉冦儣</string>
+    <string name="cannot_connect_bt_address_invalid_">鎺ョ稓銇с亶銇俱仜銈撱�� Bluetooth銈€儔銉偣銇岀劇鍔广仹銇欍亱锛�</string>
+    <string name="gadgetbridge_running_generic">Gadgetbridge 銇嫊浣滀腑銇с仚</string>
+    <string name="installing_binary_d_d">銉愩偆銉娿儶銉笺伄銈ゃ兂銈广儓銉笺儷涓� %1$d/%2$d</string>
+    <string name="installation_failed_">銈ゃ兂銈广儓銉笺儷銇け鏁椼仐銇俱仐銇�</string>
+    <string name="installation_successful">銈ゃ兂銈广儓銉笺儷銇椼伨銇椼仧</string>
+    <string name="firmware_install_warning">銉曘偂銉笺儬銈︺偋銈€倰銈ゃ兂銈广儓銉笺儷銇椼倛銇嗐仺銇椼仸銇勩伨銇欍�傝嚜宸辫铂浠汇仹琛屻仯銇︿笅銇曘亜銆�
+\n
+\n
+\n 銇撱伄銉曘偂銉笺儬銈︺偋銈€伅娆°伄 HW 銉儞銈搞儳銉崇敤: %s</string>
+    <string name="app_install_info">娆°伄銈€儣銉倰銈ゃ兂銈广儓銉笺儷銇椼倛銇嗐仺銇椼仸銇勩伨銇�:
+\n
+\n%1$s
+\n銉愩兗銈搞儳銉� %2$s 浣滆�� %3$s
+\n</string>
+    <string name="n_a">N/A</string>
+    <string name="initialized">鍒濇湡鍖栥仐銇俱仐銇�</string>
+    <string name="appversion_by_creator">%1$s 浣滆�� %2$s</string>
+    <string name="title_activity_discovery">銉囥儛銈ゃ偣銇鍑�</string>
+    <string name="discovery_stop_scanning">妞滃嚭銈掑仠姝�</string>
+    <string name="discovery_start_scanning">妞滃嚭銈掗枊濮�</string>
+    <string name="action_discover">鏂般仐銇勩儑銉愩偆銈广伀鎺ョ稓</string>
+    <string name="device_with_rssi">%1$s (%2$s)</string>
+    <string name="title_activity_android_pairing">銉囥儛銈ゃ偣銇儦銈€儶銉炽偘</string>
+    <string name="android_pairing_hint">Android 銇� Bluetooth 銉氥偄銉兂銈� 銉�銈ゃ偄銉偘銈掍娇鐢ㄣ仐銇︺儑銉愩偆銈广倰銉氥偄銉兂銈般仐銇俱仚銆�</string>
+    <string name="title_activity_mi_band_pairing">Mi Band 銈掋儦銈€儶銉炽偘</string>
+    <string name="pairing">%s 銇ㄣ儦銈€儶銉炽偘涓��</string>
+    <string name="pairing_creating_bond_with">%1$s (%2$s) 銇ㄧ磹浠樸亼銈掍綔鎴愪腑</string>
+    <string name="pairing_unable_to_pair_with">%1$s (%2$s) 銇ㄣ儦銈€儶銉炽偘銇с亶銇俱仜銈�</string>
+    <string name="pairing_in_progress">绱愪粯銇戙亴閫茶涓�: %1$s (%2$s)</string>
+    <string name="pairing_already_bonded">鏃€伀 %1$s (%2$s) 銇ㄧ磹浠樸亼銈夈倢銇︺亜銇俱仚銆傛帴缍氫腑鈥�</string>
+    <string name="message_cannot_pair_no_mac">MAC銈€儔銉偣銇屾浮銇曘倢銇︺亜銇亜銇熴倎銉氥偄銉兂銈般仹銇嶃伨銇涖倱銆�</string>
+    <string name="preferences_category_device_specific_settings">銉囥儛銈ゃ偣鍥烘湁銇ō瀹�</string>
+    <string name="preferences_miband_settings">Mi Band/Amazfit銇ō瀹�</string>
+    <string name="male">鐢锋��</string>
+    <string name="female">濂虫��</string>
+    <string name="other">銇濄伄浠�</string>
+    <string name="left">宸�</string>
+    <string name="right">鍙�</string>
+    <string name="miband_pairing_using_dummy_userdata">鏈夊姽銇儲銉笺偠銉笺儑銉笺偪銇屼笌銇堛倝銈屻仸銇勩仾銇勩仧銈併�佺従鍦ㄣ伅銉�銉熴兗銇儲銉笺偠銉笺儑銉笺偪銈掍娇鐢ㄣ仐銇︺亜銇俱仚銆�</string>
+    <string name="miband_pairing_tap_hint">Mi Band 銇屾尟鍕曘仐鐐规粎銇椼仧銈夋暟鍥為�g稓銇椼仸銈裤儍銉椼仐銇︺亸銇犮仌銇勩��</string>
+    <string name="appinstaller_install">銈ゃ兂銈广儓銉笺儷</string>
+    <string name="discovery_connected_devices_hint">銉囥儛銈ゃ偣銈掓鍑哄彲鑳姐仾鐘舵厠銇仐銇︺亸銇犮仌銇勩�傜従鍦ㄦ帴缍氫腑銇儑銉愩偆銈广伅銇娿仢銈夈亸妞滃嚭銇曘倢銇俱仜銈撱�侫ndroid 6 浠ラ檷銇с伅浣嶇疆鎯呭牨 锛堜緥锛� GPS锛夈倰鏈夊姽銇仐銇︺亸銇犮仌銇勩�備笉鍏峰悎銈掑紩銇嶈捣銇撱仚鍙兘鎬с亴銇傘倠銇熴倎銆丟adgetbridge 銇銇椼仸銉椼儵銈ゃ儛銈枫兗銈兗銉夈倰鐒″姽銇仐銇︺亸銇犮仌銇勩�傛暟鍒嗙祵銇c仸銈傘儑銉愩偆銈广亴琛ㄧず銇曘倢銇亜鍫村悎銆丄ndroid 銉囥儛銈ゃ偣銈掑啀璧峰嫊銇椼仸銇嬨倝鍐嶈│琛屻仐銇︺亸銇犮仌銇勩��</string>
+    <string name="discovery_note">娉細</string>
+    <string name="candidate_item_device_image">銉囥儛銈ゃ偣銈ゃ儭銉笺偢</string>
+    <string name="miband_prefs_alias">鍚嶅墠/鍒ュ悕</string>
+    <string name="pref_header_vibration_count">鎸嫊鍥炴暟</string>
+    <string name="title_activity_sleepmonitor">鐫$湢娓畾</string>
+    <string name="pref_write_logfiles">銉偘銉曘偂銈ゃ儷銈掑嚭鍔�</string>
+    <string name="initializing">鍒濇湡鍖栦腑</string>
+    <string name="busy_task_fetch_activity_data">銈€偗銉嗐偅銉撱儐銈c儑銉笺偪銈掑彇寰椾腑</string>
+    <string name="sleep_activity_date_range">%1$s銇嬨倝%2$s銇俱仹</string>
+    <string name="prefs_wearside">宸﹀彸銇┿仭銈夈伀韬伀銇ゃ亼銇俱仚銇嬶紵</string>
+    <string name="pref_screen_vibration_profile">鎸嫊銇儣銉儠銈°偆銉�</string>
+    <string name="vibration_profile_staccato">銈广偪銉冦偒銉笺儓</string>
+    <string name="vibration_profile_short">鐭�</string>
+    <string name="vibration_profile_medium">涓�</string>
+    <string name="vibration_profile_long">闀�</string>
+    <string name="vibration_profile_waterdrop">銈︺偐銉笺偪銉笺儔銉儍銉�</string>
+    <string name="vibration_profile_ring">槌淬倞闊裤亸</string>
+    <string name="vibration_profile_alarm_clock">鐩銇俱仐鏅傝▓</string>
+    <string name="miband_prefs_vibration">鎸嫊</string>
+    <string name="vibration_try">瑭︺仚</string>
+    <string name="pref_screen_notification_profile_sms">SMS 閫氱煡</string>
+    <string name="pref_header_vibration_settings">鎸嫊銇ō瀹�</string>
+    <string name="pref_screen_notification_profile_generic">涓�鑸殑銇�氱煡</string>
+    <string name="pref_screen_notification_profile_email">銉°兗銉�氱煡</string>
+    <string name="pref_screen_notification_profile_incoming_call">鐫�淇¢�氱煡</string>
+    <string name="pref_screen_notification_profile_generic_chat">銉併儯銉冦儓</string>
+    <string name="pref_screen_notification_profile_generic_navigation">銉娿儞銈层兗銈枫儳銉�</string>
+    <string name="pref_screen_notification_profile_generic_social">SNS</string>
+    <string name="stats_title">銈广償銉笺儔銈俱兗銉�</string>
+    <string name="stats_x_axis_label">鍚堣▓鏅傞枔锛堝垎锛�</string>
+    <string name="stats_y_axis_label">姝╂暟锛堝垎锛�</string>
+    <string name="control_center_find_lost_device">銇亸銇椼仧銉囥儛銈ゃ偣銈掓帰銇�</string>
+    <string name="control_center_cancel_to_stop_vibration">銈儯銉炽偦銉仹鎸嫊銈掑仠姝€仐銇俱仚銆�</string>
+    <string name="title_activity_charts">銈€偗銉嗐偅銉撱儐銈c仺鐫$湢</string>
+    <string name="title_activity_set_alarm">銈€儵銉笺儬銇ō瀹�</string>
+    <string name="controlcenter_start_configure_alarms">銈€儵銉笺儬銈掕ō瀹�</string>
+    <string name="title_activity_alarm_details">銈€儵銉笺儬銇┏绱�</string>
+    <string name="alarm_sun_short">鏃�</string>
+    <string name="alarm_mon_short">鏈�</string>
+    <string name="alarm_tue_short">鐏�</string>
+    <string name="alarm_wed_short">姘�</string>
+    <string name="alarm_thu_short">鏈�</string>
+    <string name="alarm_fri_short">閲�</string>
+    <string name="alarm_sat_short">鍦�</string>
+    <string name="alarm_smart_wakeup">銈广優銉笺儓 銈︺偋銈ゃ偗銈€儍銉�</string>
+    <string name="user_feedback_miband_set_alarms_failed">銈€儵銉笺儬銇ō瀹氫腑銇偍銉┿兗銇岀櫤鐢熴仐銇俱仐銇熴�傚啀瑭﹁銇椼仸銇忋仩銇曘亜銆�</string>
+    <string name="user_feedback_miband_set_alarms_ok">銈€儵銉笺儬銇屻儑銉愩偆銈广伀閫佷俊銇曘倢銇俱仐銇熴��</string>
+    <string name="chart_no_data_synchronize">銉囥兗銈裤亴銇傘倞銇俱仜銈撱�傘儑銉愩偆銈广倰鍚屾湡銇椼伨銇欍亱锛�</string>
+    <string name="user_feedback_miband_activity_data_transfer">%1$s 銇儑銉笺偪銈� %2$s 銇嬨倝杌㈤�併仐銇俱仚</string>
+    <string name="miband_prefs_fitness_goal">姣庢棩銇洰妯欐鏁�</string>
+    <string name="dbaccess_error_executing">\'%1$s\' 銇疅琛屻偍銉┿兗</string>
+    <string name="controlcenter_start_activitymonitor">銇傘仾銇熴伄銈€偗銉嗐偅銉撱儐銈�</string>
+    <string name="cannot_connect">鎺ョ稓銇с亶銇俱仜銈�: %1$s</string>
+    <string name="installer_activity_unable_to_find_handler">銇撱伄銉曘偂銈ゃ儷銈掋偆銉炽偣銉堛兗銉仚銈嬨儚銉炽儔銉┿兗銈掕銇ゃ亱銈娿伨銇涖倱銆�</string>
+    <string name="pbw_install_handler_unable_to_install">鎸囧畾銇曘倢銇熴儠銈°偆銉倰銈ゃ兂銈广儓銉笺儷銇с亶銇俱仜銈�: %1$s</string>
+    <string name="pbw_install_handler_hw_revision_mismatch">鎸囧畾銇曘倢銇熴儠銈°兗銉犮偊銈с偄銈掋偆銉炽偣銉堛兗銉仹銇嶃伨銇涖倱锛� Pebble 銇儚銉笺儔銈︺偋銈€儶銉撱偢銉с兂銇ㄤ竴鑷淬仐銇俱仜銈撱��</string>
+    <string name="installer_activity_wait_while_determining_status">銈ゃ兂銈广儓銉笺儷鐘舵硜銈掔⒑瑾嶄腑 銇椼伆銈夈亸銇婂緟銇°亸銇犮仌銇勨��</string>
+    <string name="notif_battery_low_title">銈偢銈с儍銉堛伄銉愩儍銉嗐儶銉兼畫閲忋亴灏戙仾銇忋仾銇c仸銇勩伨銇欙紒</string>
+    <string name="notif_battery_low_percent">%1$s 銉愩儍銉嗐儶銉兼畫閲�: %2$s%%</string>
+    <string name="notif_battery_low_bigtext_last_charge_time">鏈�寰屻伄鍏呴浕: %s
+\n</string>
+    <string name="notif_battery_low_bigtext_number_of_charges">鍏呴浕鍥炴暟: %s</string>
+    <string name="sleepchart_your_sleep">鐫$湢</string>
+    <string name="weeksleepchart_sleep_a_week">1閫遍枔銇潯鐪�</string>
+    <string name="weeksleepchart_today_sleep_description">浠婃棩銇潯鐪犮�佺洰妯�: %1$s</string>
+    <string name="weekstepschart_steps_a_week">1閫遍枔銇鏁�</string>
+    <string name="activity_sleepchart_activity_and_sleep">銈€偗銉嗐偅銉撱儐銈�</string>
+    <string name="updating_firmware">銉曘偂銉笺儬銈︺偋銈€伄鏇搞亶杈笺伩涓��</string>
+    <string name="fwapp_install_device_not_ready">銉曘偂銈ゃ儷銈掋偆銉炽偣銉堛兗銉仹銇嶃伨銇涖倱銆傘儑銉愩偆銈广伄婧栧倷銇屻仹銇嶃仸銇勩伨銇涖倱銆�</string>
+    <string name="installhandler_firmware_name">%1$s: %2$s %3$s</string>
+    <string name="miband_fwinstaller_compatible_version">浜掓彌鎬с伄銇傘倠銉愩兗銈搞儳銉�</string>
+    <string name="miband_fwinstaller_untested_version">鏈儐銈广儓銇儛銉笺偢銉с兂锛�</string>
+    <string name="fwappinstaller_connection_state">銉囥儛銈ゃ偣銇搞伄鎺ョ稓: %1$s</string>
+    <string name="pbw_installhandler_pebble_firmware">Pebble 銉曘偂銉笺儬銈︺偋銈� %1$s</string>
+    <string name="pbwinstallhandler_correct_hw_revision">姝c仐銇勩儚銉笺儔銈︺偋銈€儶銉撱偢銉с兂</string>
+    <string name="pbwinstallhandler_incorrect_hw_revision">銉忋兗銉夈偊銈с偄銉儞銈搞儳銉炽亴涓�鑷淬仐銇俱仜銈擄紒</string>
+    <string name="pbwinstallhandler_app_item">%1$s (%2$s)</string>
+    <string name="updatefirmwareoperation_updateproblem_do_not_reboot">銉曘偂銉笺儬銈︺偋銈€伄杌㈤�併伀鍟忛銇屻亗銈娿伨銇欍�侻i Band 銈掑啀璧峰嫊銇椼仾銇勩仹銇忋仩銇曘亜锛�</string>
+    <string name="updatefirmwareoperation_metadata_updateproblem">銉曘偂銉笺儬銈︺偋銈€伄銉°偪銉囥兗銈胯虎閫併伀鍟忛銇屻亗銈娿伨銇�</string>
+    <string name="updatefirmwareoperation_update_complete">銉曘偂銉笺儬銈︺偋銈€伄銈ゃ兂銈广儓銉笺儷銇屽畬浜嗐仐銇俱仐銇�</string>
+    <string name="updatefirmwareoperation_update_complete_rebooting">銉曘偂銉笺儬銈︺偋銈€伄銈ゃ兂銈广儓銉笺儷銇屽畬浜嗐仐銇俱仐銇熴�傘儑銉愩偆銈广倰鍐嶈捣鍕曘仐銇︺亜銇俱仚鈥�</string>
+    <string name="updatefirmwareoperation_write_failed">銉曘偂銉笺儬銈︺偋銈€伄鏇搞亶杈笺伩銇け鏁椼仐銇俱仐銇�</string>
+    <string name="chart_steps">姝╂暟</string>
+    <string name="calories">銈儹銉兗</string>
+    <string name="distance">璺濋洟</string>
+    <string name="clock">鏅傝▓</string>
+    <string name="heart_rate">蹇冩媿鏁�</string>
+    <string name="battery">銉愩儍銉嗐儶銉�</string>
+    <string name="liveactivity_live_activity">銉偄銉偪銈ゃ儬銇偄銈儐銈c儞銉嗐偅</string>
+    <string name="weeksteps_today_steps_description">浠婃棩銇鏁般�佺洰妯�: %1$s</string>
+    <string name="pref_title_dont_ack_transfer">銈€偗銉嗐偅銉撱儐銈c儑銉笺偪杌㈤�併伀蹇滅瓟銇椼仾銇�</string>
+    <string name="pref_summary_dont_ack_transfers">銈€偗銉嗐偅銉撱儐銈c儑銉笺偪銇�併儛銉炽儔銇屽繙绛斻仐銇亜鍫村悎銇�併偗銉偄銇曘倢銇俱仜銈撱�� GB銈掍粬銇偄銉椼儶銇ㄤ竴绶掋伀浣跨敤銇欍倠鍫村悎銇究鍒┿仹銇欍��</string>
+    <string name="pref_summary_keep_data_on_device">鍚屾湡寰屻倐銉囥儛銈ゃ偣銇偄銈儐銈c儞銉嗐偅銉囥兗銈裤倰淇濇寔銇椼伨銇欍�� Gadgetbridge 銈掍粬銇偄銉椼儶銇ㄤ竴绶掋伀浣跨敤銇欍倠鍫村悎銇究鍒┿仹銇欍��</string>
+    <string name="pref_title_low_latency_fw_update">銉曘偂銉笺儬銈︺偋銈€伄鏇搞亶杈笺伩銇綆閬呭欢銉€兗銉夈倰浣跨敤銇欍倠</string>
+    <string name="pref_summary_low_latency_fw_update">銉曘偂銉笺儬銈︺偋銈€伄鏇搞亶杈笺伩銇け鏁椼仚銈嬨儑銉愩偆銈广仹褰圭珛銇ゃ亾銇ㄣ亴銇傘倞銇俱仚銆�</string>
+    <string name="live_activity_steps_history">姝╂暟灞ユ</string>
+    <string name="live_activity_current_steps_per_minute">鐝惧湪銇鏁�/鍒�</string>
+    <string name="live_activity_total_steps">鍚堣▓姝╂暟</string>
+    <string name="live_activity_steps_per_minute_history">姣庡垎銇鏁板饱姝�</string>
+    <string name="live_activity_start_your_activity">銇傘仾銇熴伄銈€偗銉嗐偅銉撱儐銈c倰闁嬪</string>
+    <string name="abstract_chart_fragment_kind_activity">銈€偗銉嗐偅銉撱儐銈�</string>
+    <string name="abstract_chart_fragment_kind_light_sleep">娴呫亜鐫$湢</string>
+    <string name="abstract_chart_fragment_kind_deep_sleep">娣便亜鐫$湢</string>
+    <string name="abstract_chart_fragment_kind_not_worn">鏈鐫�</string>
+    <string name="device_not_connected">鏈帴缍氥仹銇欍��</string>
+    <string name="user_feedback_all_alarms_disabled">銇欍伖銇︺伄銈€儵銉笺儬銈掔劇鍔广伀銇椼伨銇椼仧</string>
+    <string name="pref_title_keep_data_on_device">銈€偗銉嗐偅銉撱儐銈c儑銉笺偪銈掋儑銉愩偆銈广伀淇濇寔銇欍倠</string>
+    <string name="miband_fwinstaller_incompatible_version">浜掓彌鎬с伄銇亜銉曘偂銉笺儬銈︺偋銈�</string>
+    <string name="fwinstaller_firmware_not_compatible_to_device">銇撱伄銉曘偂銉笺儬銈︺偋銈€伅銉囥儛銈ゃ偣銇ㄤ簰鎻涙�с亴銇傘倞銇俱仜銈�</string>
+    <string name="miband_prefs_reserve_alarm_calendar">浠婂緦銇偆銉欍兂銉堛伄銇熴倎銇簣绱勩仚銈嬨偄銉┿兗銉�</string>
+    <string name="miband_prefs_hr_sleep_detection">鐫$湢銇鍑恒倰鏀瑰杽銇欍倠銇熴倎銇績鎷嶃偦銉炽偟銉笺倰浣跨敤銇欍倠</string>
+    <string name="miband_prefs_device_time_offset_hours">銉囥儛銈ゃ偣鏅傚埢銇偑銉曘偦銉冦儓 (浜や唬鍕ゅ嫏鑰呫伄鐫$湢妞滅煡鐢�)</string>
+    <string name="miband2_prefs_dateformat">鏃ヤ粯褰㈠紡</string>
+    <string name="dateformat_time">鏅傚埢</string>
+    <string name="dateformat_date_time">鏅傚埢銇ㄦ棩浠�</string>
+    <string name="mi2_prefs_goal_notification">鐩閬旀垚閫氱煡</string>
+    <string name="mi2_prefs_goal_notification_summary">1鏃ャ伄姝╂暟銇岀洰妯欍伀閬斻仚銈嬨仺銉愩兂銉夈亴鎸嫊銇椼伨銇�</string>
+    <string name="mi2_prefs_display_items">琛ㄧず銈€偆銉嗐儬</string>
+    <string name="mi2_prefs_display_items_summary">銉愩兂銉夌敾闈€伀琛ㄧず銇曘倢銈嬨偄銈ゃ儐銉犮倰閬告姙</string>
+    <string name="mi2_prefs_activate_display_on_lift">鎸併仭涓娿亽銇熴仺銇嶃伀鐢婚潰銈掔偣鐏仚銈�</string>
+    <string name="mi2_prefs_rotate_wrist_to_switch_info">鎵嬮銈掑洖銇椼仸鎯呭牨銈掑垏銈婃浛銇堛倠</string>
+    <string name="mi2_prefs_do_not_disturb">銈点偆銉兂銉� 銉€兗銉�</string>
+    <string name="mi2_prefs_do_not_disturb_summary">銈€偗銉嗐偅銉栥仾闁撱�併儛銉炽儔銇�氱煡銈掑彈淇°仐銇俱仜銈�</string>
+    <string name="mi2_prefs_inactivity_warnings">鐒℃椿鍕曘伄璀﹀憡</string>
+    <string name="mi2_prefs_inactivity_warnings_summary">銇椼伆銈夈亸銇枔銆佹椿鍕曘仐銇亱銇c仧銇ㄣ亶銇儛銉炽儔銇屾尟鍕曘仐銇俱仚</string>
+    <string name="mi2_prefs_inactivity_warnings_threshold">鐒℃椿鍕曘伄銇椼亶銇勫�わ紙鍒嗭級</string>
+    <string name="mi2_prefs_inactivity_warnings_dnd_summary">鐒℃椿鍕曘伄璀﹀憡銈掍竴瀹氭湡闁撶劇鍔广伀銇椼伨銇�</string>
+    <string name="mi2_prefs_do_not_disturb_start">闁嬪鏅傚埢</string>
+    <string name="mi2_prefs_do_not_disturb_end">绲備簡鏅傚埢</string>
+    <string name="FetchActivityOperation_about_to_transfer_since">%1$s 銇嬨倝銇儑銉笺偪銈掕虎閫併仐銇俱仚</string>
+    <string name="waiting_for_reconnect">鍐嶆帴缍氥伄寰呮涓�</string>
+    <string name="activity_prefs_about_you">銉︺兗銈躲兗鎯呭牨</string>
+    <string name="activity_prefs_year_birth">瑾曠敓骞�</string>
+    <string name="activity_prefs_gender">鎬у垾</string>
+    <string name="activity_prefs_height_cm">韬暦锛坈m锛�</string>
+    <string name="activity_prefs_weight_kg">浣撻噸锛坘g锛�</string>
+    <string name="authenticating">瑾嶈涓�</string>
+    <string name="authentication_required">瑾嶈銇屽繀瑕併仹銇�</string>
+    <string name="appwidget_text">Zzz</string>
+    <string name="add_widget">銈︺偅銈搞偋銉冦儓銈掕拷鍔�</string>
+    <string name="activity_prefs_sleep_duration">鐩鐫$湢鏅傞枔 (鏅傞枔)</string>
+    <string name="appwidget_setting_alarm">銈€儵銉笺儬銈� %1$02d:%2$02d 銇ō瀹氥仐銇俱仐銇�</string>
+    <string name="device_hw">銉忋兗銉夈偊銈с偄銉儞銈搞儳銉�: %1$s</string>
+    <string name="device_fw">銉曘偂銉笺儬銈︺偋銈€儛銉笺偢銉с兂: %1$s</string>
+    <string name="error_creating_directory_for_logfiles">銉偘銉曘偂銈ゃ儷銇儑銈c儸銈儓銉倰浣滄垚涓伀銈ㄣ儵銉�: %1$s</string>
+    <string name="DEVINFO_HR_VER">"HR: "</string>
+    <string name="updatefirmwareoperation_update_in_progress">銉曘偂銉笺儬銈︺偋銈€倰鏇存柊銇椼仸銇勩伨銇�</string>
+    <string name="updatefirmwareoperation_firmware_not_sent">銉曘偂銉笺儬銈︺偋銈€倰閫佷俊銇椼伨銇涖倱銇с仐銇�</string>
+    <string name="charts_legend_heartrate">蹇冩媿鏁�</string>
+    <string name="live_activity_heart_rate">蹇冩媿鏁�</string>
+    <string name="pref_title_pebble_health_store_raw">銉囥兗銈裤儥銉笺偣銇敓銇閷层倰鏍肩磵銇欍倠</string>
+    <string name="pref_summary_pebble_health_store_raw">銉囥兗銈裤倰銆屻仢銇伨銇俱�嶆牸绱嶃仐銆併亗銇ㄣ仹瑙i噲銇с亶銈嬨倛銇嗐伀銉囥兗銈裤儥銉笺偣銇娇鐢ㄩ噺銈掑銈勩仐銇俱仚銆�</string>
+    <string name="action_db_management">銉囥兗銈裤伄绠$悊</string>
+    <string name="title_activity_db_management">銉囥兗銈裤伄绠$悊</string>
+    <string name="activity_db_management_import_export_explanation">銉囥兗銈裤儥銉笺偣鎿嶄綔銇�併儑銉愩偆銈广伄娆°伄銉戙偣銈掍娇鐢ㄣ仐銇俱仚銆�
+\n銇撱伄銉戙偣銇�佷粬銇� Android 銈€儣銉偙銉笺偡銉с兂銈勩亰浣裤亜銇偝銉炽償銉ャ兗銈裤亴銈€偗銈汇偣鍙兘銇с仚銆�
+\n銈ㄣ偗銈广儩銉笺儓銇曘倢銇熴儑銉笺偪銉欍兗銈广亴瑕嬨仱銇嬨倞銇俱仚 (銇俱仧銇偆銉炽儩銉笺儓銇椼仧銇勩儑銉笺偪銉欍兗銈广倰閰嶇疆銇椼伨銇�):</string>
+    <string name="activity_db_management_merge_old_title">銉偓銈枫兗 銉囥兗銈裤儥銉笺偣銇墛闄�</string>
+    <string name="dbmanagementactivvity_cannot_access_export_path">銈ㄣ偗銈广儩銉笺儓 銉戙偣銇偄銈偦銈广仹銇嶃伨銇涖倱銆傞枊鐧鸿�呫伀閫g怠銇椼仸銇忋仩銇曘亜銆�</string>
+    <string name="dbmanagementactivity_exported_to">銈ㄣ偗銈广儩銉笺儓銇椼伨銇椼仧: %1$s</string>
+    <string name="dbmanagementactivity_error_exporting_db">銉囥兗銈裤儥銉笺偣銇偍銈偣銉濄兗銉堛偍銉┿兗 : %1$s</string>
+    <string name="dbmanagementactivity_error_exporting_shared">瑷畾銇偍銈偣銉濄兗銉堟檪銇偍銉┿兗銇岀櫤鐢熴仐銇俱仐銇�: %1$s</string>
+    <string name="dbmanagementactivity_import_data_title">銉囥兗銈裤倰銈ゃ兂銉濄兗銉堛仐銇俱仚銇�?</string>
+    <string name="dbmanagementactivity_overwrite_database_confirmation">鐝惧湪銇儑銉笺偪銈掍笂鏇搞亶銇椼仸銈傘倛銈嶃仐銇勩仹銇欍亱锛� 鐝惧湪銇偄銈儐銈c儞銉嗐偅銉囥兗銈裤伅 (銈傘仐銇傘倢銇�) 銇欍伖銇︿笂鏇搞亶銇曘倢銇俱仚銆�</string>
+    <string name="dbmanagementactivity_import_successful">銈ゃ兂銉濄兗銉堛仐銇俱仐銇熴��</string>
+    <string name="dbmanagementactivity_error_importing_db">銉囥兗銈裤儥銉笺偣銇偆銉炽儩銉笺儓鏅傘伀銈ㄣ儵銉笺亴鐧虹敓銇椼伨銇椼仧: %1$s</string>
+    <string name="dbmanagementactivity_error_importing_shared">瑷畾銇偆銉炽儩銉笺儓鏅傘伀銈ㄣ儵銉笺亴鐧虹敓銇椼伨銇椼仧: %1$s</string>
+    <string name="dbmanagementactivity_delete_activity_data_title">銈€偗銉嗐偅銉撱儐銈c儑銉笺偪銈掑墛闄ゃ仐銇俱仚銇嬶紵</string>
+    <string name="dbmanagementactivity_really_delete_entire_db">銉囥兗銈裤儥銉笺偣鍏ㄤ綋銈掑墛闄ゃ仐銇︺倐銈堛倣銇椼亜銇с仚銇嬶紵銈€偗銉嗐偅銉撱儐銈c儑銉笺偪銇ㄣ儑銉愩偆銈广伀闁€仚銈嬫儏鍫便亴銇欍伖銇﹀け銈忋倢銇俱仚銆�</string>
+    <string name="dbmanagementactivity_database_successfully_deleted">銉囥兗銈裤倰鍓婇櫎銇椼伨銇椼仧銆�</string>
+    <string name="dbmanagementactivity_db_deletion_failed">銉囥兗銈裤儥銉笺偣銇墛闄ゃ伀澶辨晽銇椼伨銇椼仧銆�</string>
+    <string name="dbmanagementactivity_delete_old_activity_db">鍙ゃ亜銈€偗銉嗐偅銉撱儐銈� 銉囥兗銈裤儥銉笺偣銈掑墛闄ゃ仐銇俱仚銇嬶紵</string>
+    <string name="dbmanagementactivity_delete_old_activitydb_confirmation">鍙ゃ亜銈€偗銉嗐偅銉撱儐銈� 銉囥兗銈裤儥銉笺偣銈掑墛闄ゃ仐銇︺倐銈堛倣銇椼亜銇с仚銇嬶紵銈ゃ兂銉濄兗銉堛仌銈屻仾銇嬨仯銇熴偄銈儐銈c儞銉嗐偅銉囥兗銈裤伅澶便倧銈屻伨銇欍��</string>
+    <string name="dbmanagementactivity_old_activity_db_successfully_deleted">鍙ゃ亜銈€偗銉嗐偅銉撱儐銈� 銉囥兗銈裤倰鍓婇櫎銇椼伨銇椼仧銆�</string>
+    <string name="dbmanagementactivity_old_activity_db_deletion_failed">鍙ゃ亜銈€偗銉嗐偅銉撱儐銈� 銉囥兗銈裤儥銉笺偣銇墛闄ゃ伀澶辨晽銇椼伨銇椼仧銆�</string>
+    <string name="dbmanagementactivity_overwrite">涓婃浉銇�</string>
+    <string name="Cancel">銈儯銉炽偦銉�</string>
+    <string name="Delete">鍓婇櫎</string>
+    <!--Strings related to Vibration Activity-->
+    <string name="title_activity_vibration">鎸嫊</string>
+    <!--Strings related to Pebble Pairing Activity-->
+    <string name="title_activity_pebble_pairing">Pebble 銉氥偄銉兂銈�</string>
+    <string name="pebble_pairing_hint">Android 銉囥儛銈ゃ偣銇с儦銈€儶銉炽偘銇儉銈ゃ偄銉偘銇屻儩銉冦儣銈€儍銉椼仐銇俱仚銆� 琛ㄧず銇曘倢銇亱銇c仧鍫村悎銇�侀�氱煡銉夈儹銉兗銈掕銇广仸銉氥偄銉兂銈拌姹傘倰浜嗘壙銇椼仸銇忋仩銇曘亜銆� 銇濄伄寰屻�丳ebble 銇с倐鍚屾銇簡鎵裤仐銇︺亸銇犮仌銇勩��</string>
+    <string name="weather_notification_label">Pebble 銇уぉ姘楁儏鍫便倰鍙栧緱銇欍倠銇伅銆佸ぉ姘楅�氱煡銈€儣銉仹銇撱伄銈广偔銉炽亴鏈夊姽銇仾銇c仸銇勩倠銇撱仺銈掔⒑瑾嶃仐銇︺亸銇犮仌銇勩��
+\n
+\n銇撱亾銇у繀瑕併仾瑷畾銇亗銈娿伨銇涖倱銆�
+\n
+\n銈€儣銉伄绠$悊銇嬨倝Pebble銇偡銈广儐銉犲ぉ姘椼偄銉椼儶銈掓湁鍔广伀銇欍倠銇撱仺銇屻仹銇嶃伨銇欍��
+\n
+\n銈点儩銉笺儓銇曘倢銇︺亜銈嬨偊銈┿儍銉併儠銈с偆銈广亴鑷嫊鐨勩伀澶╂皸銈掕〃绀恒仐銇俱仚銆�</string>
+    <string name="pref_title_setup_bt_pairing">Bluetooth 銉氥偄銉兂銈般倰鏈夊姽銇仚銈�</string>
+    <string name="pref_summary_setup_bt_pairing">鎺ョ稓銇晱椤屻亴銇傘倠鍫村悎銇�併亾銇鑳姐倰鐒″姽銇仐銇︺亸銇犮仌銇�</string>
+    <string name="unit_metric">銉°兗銉堛儷</string>
+    <string name="unit_imperial">銉ゃ兗銉夈兓銉濄兂銉�</string>
+    <string name="timeformat_24h">24鏅傞枔</string>
+    <string name="timeformat_am_pm">AM/PM</string>
+    <string name="pref_screen_notification_profile_alarm_clock">銈€儵銉笺儬銈儹銉冦偗</string>
+    <string name="StringUtils_sender"> (%1$s)</string>
+    <string name="find_device_you_found_it">瑕嬨仱銇嬨倞銇俱仐銇燂紒</string>
+    <string name="miband2_prefs_timeformat">Mi2: 鏅傚埢銇舰寮�</string>
+    <string name="mi2_fw_installhandler_fw53_hint">銇撱伄銉曘偂銉笺儬銈︺偋銈€倰銈ゃ兂銈广儓銉笺儷銇欍倠鍓嶃伀銆併儛銉笺偢銉с兂 %1$s 銈掋偆銉炽偣銉堛兗銉仚銈嬪繀瑕併亴銇傘倞銇俱仚锛�</string>
+    <string name="mi2_enable_text_notifications">銉嗐偔銈广儓閫氱煡</string>
+    <string name="mi2_enable_text_notifications_summary">銉曘偂銉笺儬銈︺偋銈� 1.0.1.28 浠ヤ笂 銇� Mili_pro.ft* 銈掋偆銉炽偣銉堛兗銉仐銇︺亜銈嬨亾銇ㄣ亴蹇呰銇с仚銆�</string>
+    <string name="off">銈儠</string>
+    <string name="mi2_dnd_off">銈儠</string>
+    <string name="mi2_dnd_automatic">鑷嫊锛堢潯鐪犳鍑猴級</string>
+    <string name="mi2_dnd_scheduled">銈广偙銈搞儱銉笺儷 锛堟檪闁撻枔闅旓級</string>
+    <string name="discovery_attempting_to_pair">%1$s 銇ㄣ伄銉氥偄銉兂銈般倰瑭︺伩銇︺亜銇俱仚</string>
+    <string name="discovery_bonding_failed_immediately">%1$s 銇ㄣ伄鎺ョ稓銇け鏁椼仐銇俱仐銇熴��</string>
+    <string name="discovery_trying_to_connect_to">鎺ョ稓銇│琛屼腑锛� %1$s</string>
+    <string name="discovery_enable_bluetooth">Bluetooth 銈掓湁鍔广伀銇椼仸銉囥儛銈ゃ偣銈掓鍑恒仐銇俱仚銆�</string>
+    <string name="discovery_successfully_bonded">%1$s 銇ㄦ帴缍氥仐銇俱仐銇熴��</string>
+    <string name="discovery_pair_title">%1$s銇ㄣ儦銈€儶銉炽偘銇椼伨銇欍亱锛�</string>
+    <string name="discovery_pair_question">銉氥偄銉兂銈般仚銈嬨儑銉愩偆銈广倰閬告姙銇椼仸銇忋仩銇曘亜銆� 澶辨晽銇椼仧鍫村悎銇�併儦銈€儶銉炽偘銇涖仛銇啀瑭﹁銇椼仸銇忋仩銇曘亜銆�</string>
+    <string name="discovery_yes_pair">銉氥偄銉兂銈�</string>
+    <string name="discovery_dont_pair">銉氥偄銉兂銈般仐銇亜</string>
+    <string name="fw_upgrade_notice_amazfitbip">Amazfit Bip 銇� %s 銇儠銈°兗銉犮偊銈с偄銈掋偆銉炽偣銉堛兗銉仐銈堛亞銇ㄣ仐銇︺亜銇俱仚銆�
+\n
+\n.fw 銉曘偂銈ゃ儷銆併仢銇緦 .res 銉曘偂銈ゃ儷銆佹渶寰屻伀 .gps 銉曘偂銈ゃ儷銈掋偆銉炽偣銉堛兗銉仐銇︺亸銇犮仌銇勩�傘偊銈┿儍銉併伅銆�.fw 銉曘偂銈ゃ儷銈掋偆銉炽偣銉堛兗銉仐銇熷緦銇啀璧峰嫊銇椼伨銇欍��
+\n
+\n娉�: 銇撱倢銈夈伄銉曘偂銈ゃ儷銇屽墠鍥炪伀銈ゃ兂銈广儓銉笺儷銇椼仧銈傘伄銇ㄥ畬鍏ㄣ伀鍚屻仒鍫村悎銆�.res 銇� .gps 銈掋偆銉炽偣銉堛兗銉仚銈嬪繀瑕併伅銇傘倞銇俱仜銈撱��
+\n
+\n銇旇嚜韬伄璨换銇ц銇c仸銇忋仩銇曘亜!</string>
+    <string name="mi2_prefs_button_actions">銉溿偪銉炽伄鍕曚綔</string>
+    <string name="mi2_prefs_button_actions_summary">銉溿偪銉炽伄鍕曚綔銈掓寚瀹氥仐銇俱仚</string>
+    <string name="mi2_prefs_button_press_count">銉溿偪銉炽倰鎶笺仚鍥炴暟</string>
+    <string name="mi2_prefs_button_press_count_summary">銈ゃ儥銉炽儓 1 銈掔櫤鍕曘仌銇涖倠銇熴倎銇娂銇欍儨銈裤兂銇洖鏁般�傜稓銇戙仸鍚屻仒鍥炴暟銉溿偪銉炽倰鎶笺仚銇ㄣ偆銉欍兂銉� 2 銇岀櫤鐢熴仐銇俱仚銆�</string>
+    <string name="mi2_prefs_button_press_broadcast">閫佷俊銇欍倠銉栥儹銉笺儔銈儯銈广儓 銉°儍銈汇兗銈�</string>
+    <string name="mi2_prefs_button_press_broadcast_summary"/>
+    <string name="mi2_prefs_button_action">銉溿偪銉炽伄鍕曚綔銈掓湁鍔广伀銇欍倠</string>
+    <string name="mi2_prefs_button_action_summary">鎸囧畾銇椼仧銉溿偪銉炽倰鎶笺仐銇熸暟銇嫊浣溿倰鏈夊姽銇仐銇俱仚</string>
+    <string name="mi2_prefs_button_action_vibrate">銉愩兂銉夈伄鎸嫊銈掓湁鍔广伀銇欍倠</string>
+    <string name="mi2_prefs_button_action_vibrate_summary">銉溿偪銉炽伄鍕曚綔銈掋儓銉偓銉笺伀銉愩兂銉夈伄鎸嫊銈掓湁鍔广伀銇椼伨銇�</string>
+    <string name="mi2_prefs_button_press_count_max_delay">銉溿偪銉炽倰鎶笺仚闁撻殧銇渶澶ч亝寤舵檪闁�</string>
+    <string name="mi2_prefs_button_press_count_max_delay_summary">銉溿偪銉炽倰鎶笺仚闁撻殧銇渶澶ч亝寤舵檪闁� (銉熴儶绉�)</string>
+    <string name="_pebble_watch_open_on_phone">Android 銇ч枊銇�</string>
+    <string name="_pebble_watch_mute">銉熴儱銉笺儓</string>
+    <string name="_pebble_watch_reply">杩斾俊</string>
+    <string name="controlcenter_connect">鎺ョ稓鈥�</string>
+    <string name="fw_upgrade_notice_amazfitcor">Amazfit Cor 銇� %s 銇儠銈°兗銉犮偊銈с偄銈掋偆銉炽偣銉堛兗銉仐銈堛亞銇ㄣ仐銇︺亜銇俱仚銆�
+\n
+\n.fw 銉曘偂銈ゃ儷銈掋偆銉炽偣銉堛兗銉仐銆併仢銇緦 .res 銉曘偂銈ゃ儷銈掋偆銉炽偣銉堛兗銉仐銇忋仩銇曘亜銆傘儛銉炽儔銇��.fw 銉曘偂銈ゃ儷銈掋偆銉炽偣銉堛兗銉仐銇熷緦銇啀璧峰嫊銇椼伨銇欍��
+\n
+\n娉�: .res 銉曘偂銈ゃ儷銇屽墠鍥炪伀銈ゃ兂銈广儓銉笺儷銇椼仧銈傘伄銇ㄥ悓銇樺牬鍚堛�佹柊銇熴伀.res 銉曘偂銈ゃ儷銈掋偆銉炽偣銉堛兗銉仚銈嬪繀瑕併伅銇傘倞銇俱仜銈撱��
+\n
+\n銇旇嚜韬伄璨换銇ц銇c仸涓嬨仌銇勶紒</string>
+    <string name="pref_title_pebble_enable_bgjs">銉愩儍銈偘銉┿偊銉炽儔 JS 銈掓湁鍔广伀銇椼伨銇�</string>
+    <string name="pref_summary_pebble_enable_bgjs">鏈夊姽銇仚銈嬨仺銆併偊銈┿儍銉併儠銈с偆銈广伀澶╂皸銈勩儛銉冦儐銉兗鎯呭牨绛夈倰琛ㄧず銇欍倠銇撱仺銇屻仹銇嶃倠銈堛亞銇仾銈娿伨銇�</string>
+    <string name="activity_web_view">Web View 銈€偗銉嗐偅銉撱儐銈�</string>
+    <string name="pref_title_charts_swipe">宸�/鍙炽伄銈广儻銈ゃ儣銇с儊銉c兗銉堛倰鍒囥倞鏇裤亪銈�</string>
+    <string name="prefs_title_heartrate_measurement_interval">绲傛棩蹇冩媿娓畾</string>
+    <string name="interval_one_minute">1鍒嗛枔銇�1鍥�</string>
+    <string name="interval_five_minutes">5鍒嗐仈銇�</string>
+    <string name="interval_ten_minutes">10鍒嗐仈銇�</string>
+    <string name="interval_thirty_minutes">30鍒嗐仈銇�</string>
+    <string name="interval_one_hour">1鏅傞枔銇�1鍥�</string>
+    <string name="automatic">鑷嫊</string>
+    <string name="simplified_chinese">绨′綋涓浗瑾�</string>
+    <string name="traditional_chinese">绻佷綋涓浗瑾�</string>
+    <string name="english">鑻辫獮</string>
+    <string name="pref_title_weather">澶╂皸</string>
+    <string name="pref_title_weather_location">澶╂皸銇牬鎵� (LineageOS 銇� Weather provider 鐢�)</string>
+    <string name="kind_firmware">銉曘偂銉笺儬銈︺偋銈�</string>
+    <string name="kind_invalid">鐒″姽銇儑銉笺偪</string>
+    <string name="kind_font">銉曘偐銉炽儓</string>
+    <string name="kind_gps">GPS 銉曘偂銉笺儬銈︺偋銈�</string>
+    <string name="kind_gps_almanac">GPS 骞撮憫</string>
+    <string name="kind_gps_cep">GPS 銈ㄣ儵銉艰▊姝�</string>
+    <string name="kind_resources">銉偨銉笺偣</string>
+    <string name="kind_watchface">銈︺偐銉冦儊銉曘偋銈ゃ偣</string>
+    <string name="devicetype_unknown">涓嶆槑銇儑銉愩偆銈�</string>
+    <string name="devicetype_test">銉嗐偣銉� 銉囥儛銈ゃ偣</string>
+    <string name="devicetype_pebble">Pebble</string>
+    <string name="devicetype_miband">Mi Band</string>
+    <string name="devicetype_miband2">Mi Band 2</string>
+    <string name="devicetype_amazfit_bip">Amazfit Bip</string>
+    <string name="devicetype_amazfit_cor">Amazfit Cor</string>
+    <string name="devicetype_vibratissimo">Vibratissimo</string>
+    <string name="devicetype_liveview">LiveView</string>
+    <string name="devicetype_hplus">HPlus</string>
+    <string name="devicetype_makibes_f68">Makibes F68</string>
+    <string name="devicetype_exrizu_k8">Exrizu K8</string>
+    <string name="devicetype_no1_f1">No.1 F1</string>
+    <string name="devicetype_teclast_h30">Teclast H30</string>
+    <string name="pref_header_auto_export">鑷嫊銈ㄣ偗銈广儩銉笺儓</string>
+    <string name="pref_title_auto_export_enabled">鑷嫊銈ㄣ偗銈广儩銉笺儓銈掓湁鍔广伀銇欍倠</string>
+    <string name="pref_title_auto_export_location">銈ㄣ偗銈广儩銉笺儓銇欍倠鍫存墍</string>
+    <string name="pref_title_auto_export_interval">銈ㄣ偗銈广儩銉笺儓銇欍倠闁撻殧</string>
+    <string name="pref_summary_auto_export_interval">%d 鏅傞枔銇斻仺銇偍銈偣銉濄兗銉�</string>
+    <string name="notif_export_failed_title">銉囥兗銈裤儥銉笺偣銇偍銈偣銉濄兗銉堛伀澶辨晽銇椼伨銇椼仧锛佽ō瀹氥倰纰鸿獚銇椼仸銇忋仩銇曘亜銆�</string>
+    <string name="choose_auto_export_location">銈ㄣ偗銈广儩銉笺儓鍏堛倰閬告姙</string>
+    <string name="spanish">銈广儦銈ゃ兂瑾�</string>
+    <string name="devicetype_xwatch">XWatch</string>
+    <string name="notification_channel_name">鍏ㄨ埇</string>
+    <string name="on">銈兂</string>
+    <string name="controlcenter_start_activity_tracks">銈€偗銉嗐偅銉撱儐銈c伄杩借贰</string>
+    <string name="activity_type_not_measured">鏈脯瀹�</string>
+    <string name="activity_type_activity">銈€偗銉嗐偅銉撱儐銈�</string>
+    <string name="activity_type_light_sleep">娴呫亜鐫$湢</string>
+    <string name="activity_type_deep_sleep">娣便亜鐫$湢</string>
+    <string name="activity_type_not_worn">銉囥儛銈ゃ偣鏈鐫�</string>
+    <string name="activity_type_running">銉┿兂銉嬨兂銈�</string>
+    <string name="activity_type_walking">銈︺偐銉笺偔銉炽偘</string>
+    <string name="activity_type_swimming">銈广偆銉熴兂銈�</string>
+    <string name="activity_type_unknown">涓嶆槑銇偄銈儐銈c儞銉嗐偅</string>
+    <string name="activity_summaries">銈广儩銉笺儎 銈€偗銉嗐偅銉撱儐銈�</string>
+    <string name="activity_type_biking">鑷虎杌�</string>
+    <string name="activity_type_treadmill">銉┿兂銉嬨兂銈般優銈枫兂</string>
+    <string name="select_all">銇欍伖銇﹂伕鎶�</string>
+    <string name="share">鍏辨湁</string>
+    <string name="reset_index">鍙栧緱鏃ユ檪銈掋儶銈汇儍銉�</string>
+    <string name="pref_title_pebble_gatt_clientonly">GATT 銈儵銈ゃ偄銉炽儓銇伩</string>
+    <string name="pref_summary_pebble_gatt_clientonly">Pebble 2 鍚戙亼銇疅楱撶殑銇鑳姐仹銇欍�傛帴缍氥伀鍟忛銇屻亗銈嬪牬鍚堛�併亾銈屻倰瑭︺仐銇︺亸銇犮仌銇�</string>
+    <string name="devicetype_q8">Q8</string>
+    <string name="menuitem_status">銈广儐銉笺偪銈�</string>
+    <string name="menuitem_activity">銉兗銈偄銈︺儓銇饱姝�</string>
+    <string name="menuitem_weather">澶╂皸</string>
+    <string name="menuitem_alarm">銈€儵銉笺儬</string>
+    <string name="menuitem_timer">銈偊銉炽儓銉�銈︺兂</string>
+    <string name="menuitem_compass">銈炽兂銉戙偣</string>
+    <string name="menuitem_settings">瑷畾</string>
+    <string name="menuitem_alipay">Alipay</string>
+    <string name="controlcenter_change_led_color">LED 銇壊銈掑鏇�</string>
+    <string name="controlcenter_change_fm_frequency">FM 銇懆娉㈡暟銈掑鏇�</string>
+    <string name="controlcenter_calibrate_device">銉囥儛銈ゃ偣銇鏁�</string>
+    <string name="debugactivity_really_factoryreset_title">宸ュ牬鍑鸿嵎鐘舵厠銇儶銈汇儍銉堛仐銇俱仚銇嬶紵</string>
+    <string name="debugactivity_really_factoryreset">宸ュ牬鍑鸿嵎鐘舵厠銇儶銈汇儍銉堛仚銈嬨仺銆佹帴缍氥仌銈屻仸銇勩倠銉囥儛銈ゃ偣銇嬨倝銇欍伖銇︺伄銉囥兗銈裤倰鍓婇櫎銇椼伨銇�(銈点儩銉笺儓銇曘倢銇︺亜銈嬪牬鍚�)銆� Xiaomi/Huami銉囥儛銈ゃ偣銇с伅Bluetooth銇甅AC銈€儔銉偣銈傚鏇淬仚銈嬨仧銈併�丟adgetbridge銇伅鏂般仐銇勩儑銉愩偆銈广仺銇椼仸琛ㄧず銇曘倢銇俱仚銆�</string>
+    <string name="blacklist_all_for_notifications">銇欍伖銇﹂�氱煡銇儢銉┿儍銈儶銈广儓銇仚銈�</string>
+    <string name="whitelist_all_for_notifications">銇欍伖銇﹂�氱煡銇儧銉偆銉堛儶銈广儓銇仚銈�</string>
+    <string name="fw_upgrade_notice_miband3">Mi Band 3 銇� %s 銉曘偂銉笺儬銈︺偋銈€倰銈ゃ兂銈广儓銉笺儷銇椼伨銇欍��
+\n
+\n.fw 銉曘偂銈ゃ儷銈掋偆銉炽偣銉堛兗銉仐銇︺�併仢銇緦 .res 銉曘偂銈ゃ儷銈掋偆銉炽偣銉堛兗銉仚銈嬨倛銇嗐伀銇椼仸銇忋仩銇曘亜銆� .fw 銉曘偂銈ゃ儷銇偆銉炽偣銉堛兗銉緦銆併儛銉炽儔銇啀璧峰嫊銇椼伨銇欍��
+\n
+\n娉�: .res 銇屼互鍓嶃伀銈ゃ兂銈广儓銉笺儷銇椼仧銈傘伄銇ㄥ叏銇忓悓銇樺牬鍚堛伅銆併偆銉炽偣銉堛兗銉仚銈嬪繀瑕併伅銇傘倞銇俱仜銈撱��
+\n
+\n銇旇嚜韬伄璨换銇ц銇c仸銇忋仩銇曘亜锛�</string>
+    <string name="pref_title_notifications_timeout">閫氱煡銇枔銇渶灏忔檪闁�</string>
+    <string name="pref_title_rtl">鍙虫í鏇搞亶</string>
+    <string name="pref_summary_rtl">銉囥儛銈ゃ偣銇屽彸銇嬨倝宸︺伕鏇搞亸瑷�瑾炪倰琛ㄧず銇с亶銇亜鍫村悎銆併亾銈屻倰鏈夊姽銇仐銇︺亸銇犮仌銇�</string>
+    <string name="pref_rtl_max_line_length">鍙虫í鏇搞亶銇渶澶ц闀�</string>
+    <string name="pref_rtl_max_line_length_summary">鍙虫í鏇搞亶銇枃銇銈掍几闀枫伨銇熴伅鐭府銇椼仸鍒嗗壊銇椼伨銇�</string>
+    <string name="prefs_screen_orientation">鐢婚潰銇悜銇�</string>
+    <string name="pref_auto_fetch">銈€偗銉嗐偅銉撱儐銈c儑銉笺偪銇嚜鍕曞彇寰�</string>
+    <string name="pref_auto_fetch_summary">鐢婚潰銇儹銉冦偗瑙i櫎鏅傘伀鍙栧緱銈掕銇勩伨銇欍�傘儹銉冦偗姗熻兘銇岃ō瀹氥仌銈屻仸銇勩倠鍫村悎銇伩鍕曚綔銇椼伨銇欙紒</string>
+    <string name="pref_auto_fetch_limit_fetches">鍙栧緱闁撻殧銇渶灏忔檪闁�</string>
+    <string name="pref_auto_fetch_limit_fetches_summary">%d 鍒嗐仈銇ㄣ伀鍙栧緱</string>
+    <string name="horizontal">妯悜銇�</string>
+    <string name="vertical">绺﹀悜銇�</string>
+    <string name="watch9_pairing_tap_hint">銈︺偐銉冦儊銇屾尟鍕曘仐銇熴仺銇嶃�併儑銉愩偆銈广倰鎸倠銇嬨儨銈裤兂銈掓娂銇椼仸銇忋仩銇曘亜銆�</string>
+    <string name="notif_battery_low">%1$s 銉愩儍銉嗐儶銉兼畫閲� 浣�</string>
+    <string name="notif_battery_low_extended">%1$s 銉愩儍銉嗐儶銉兼畫閲� 浣�: %2$s</string>
+    <string name="lack_of_sleep">鐫$湢涓嶈冻: %1$s</string>
+    <string name="overslept">鐫$湢閬庡壈: %1$s</string>
+    <string name="no_limit">鐒″埗闄�</string>
+    <string name="seconds_5">5 绉�</string>
+    <string name="seconds_10">10 绉�</string>
+    <string name="seconds_20">20 绉�</string>
+    <string name="seconds_30">30 绉�</string>
+    <string name="minutes_1">1 鍒�</string>
+    <string name="minutes_5">5 鍒�</string>
+    <string name="minutes_10">10 鍒�</string>
+    <string name="minutes_30">30 鍒�</string>
+    <string name="lack_of_step">姝╂暟涓嶈冻: %1$d</string>
+    <string name="overstep">姝╂暟閬庡壈: %1$d</string>
+    <string name="live_activity_max_heart_rate">鐝惧湪 / 鏈�澶у績鎷嶆暟: %1$d / %2$d</string>
+    <string name="you_slept">%1$s 銇嬨倝 %2$s 銇枔銆佺湢銇c仸銇勩伨銇椼仧</string>
+    <string name="you_did_not_sleep">鐫$湢銇椼伨銇涖倱銇с仐銇�</string>
+    <string name="mi3_prefs_band_screen_unlock">銉愩兂銉夈伄鐢婚潰銉儍銈В闄�</string>
+    <string name="mi3_prefs_band_screen_unlock_summary">涓娿伀銈广儻銈ゃ儣銇欍倠銇ㄣ�併儛銉炽儔銇敾闈€儹銉冦偗銈掕В闄ゃ仐銇俱仚</string>
+    <string name="mi3_prefs_night_mode">澶滈枔銉€兗銉�</string>
+    <string name="mi3_prefs_night_mode_summary">澶滈枔銇儛銉炽儔銇敾闈€倰鑷嫊鐨勩伀鏆椼亸銇椼伨銇�</string>
+    <string name="norwegian_bokmal">銉庛儷銈︺偋銉艰獮 銉栥兗銈儮銉笺儷</string>
+    <string name="russian">銉偡銈㈣獮</string>
+    <string name="german">銉夈偆銉勮獮</string>
+    <string name="italian">銈ゃ偪銉偄瑾�</string>
+    <string name="french">銉曘儵銉炽偣瑾�</string>
+    <string name="polish">銉濄兗銉┿兂銉夎獮</string>
+    <string name="korean">鏈濋瑾�</string>
+    <string name="japanese">鏃ユ湰瑾�</string>
+    <string name="activity_prefs_charts">銉併儯銉笺儓瑷畾</string>
+    <string name="activity_prefs_chart_max_heart_rate">鏈�澶у績鎷嶆暟</string>
+    <string name="activity_prefs_chart_min_heart_rate">鏈�灏忓績鎷嶆暟</string>
+    <string name="ok">OK</string>
+    <string name="mi3_night_mode_sunset">鏃ユ病鏅�</string>
+    <string name="devicetype_miband3">Mi Band 3</string>
+    <string name="devicetype_mykronoz_zetime">MyKronoz ZeTime</string>
+    <string name="devicetype_id115">ID115</string>
+    <string name="devicetype_watch9">Watch 9</string>
+    <string name="devicetype_roidmi">Roidmi</string>
+    <string name="devicetype_roidmi3">Roidmi 3</string>
+    <string name="devicetype_casiogb6900">CASIO GB-6900</string>
+    <string name="menuitem_notifications">閫氱煡</string>
+    <string name="menuitem_music">闊虫ソ</string>
+    <string name="menuitem_more">銇濄伄浠�</string>
+    <string name="watch9_time_minutes">鍒嗭細</string>
+    <string name="watch9_time_hours">鏅傦細</string>
+    <string name="watch9_time_seconds">绉掞細</string>
+    <string name="watch9_calibration_hint">銇勩伨琛ㄧず銇椼仸銇勩倠鏅傚埢銈掋儑銉愩偆銈广伀瑷畾銇椼伨銇欍��</string>
+    <string name="watch9_calibration_button">瑾挎暣</string>
+    <string name="title_activity_watch9_pairing">Watch 9 銇儦銈€儶銉炽偘</string>
+    <string name="title_activity_watch9_calibration">Watch 9 銇鏁�</string>
+    <string name="pref_title_contextual_arabic">鏂囪剤銈€儵銉撱偄瑾�</string>
+    <string name="pref_summary_contextual_arabic">鏈夊姽銇仚銈嬨仺鏂囪剤銈€儵銉撱偄瑾炪倰銈点儩銉笺儓銇椼伨銇�</string>
+    <string name="preferences_rtl_settings">鍙虫í鏇搞亶銇偟銉濄兗銉�</string>
+    <string name="share_log">銉偘銈掑叡鏈�</string>
+    <string name="share_log_warning">GadgetBridge 銇�佸仴搴枫儑銉笺偪銈勩儲銉嬨兗銈疘D锛堛儑銉愩偆銈广伄 MAC 銈€儔銉偣銇仼锛夈�侀煶妤姐伄濂姐伩銇仼銆佸銇忋伄鍊嬩汉鎯呭牨銈掑惈銈�鍙兘鎬с亴銇傘倠銉曘偂銈ゃ儷銈掕閷层仐銇俱仚銆傚晱椤屽牨鍛娿倰閫佷俊銇欍倠鍓嶃伀銆併儠銈°偆銉倰绶ㄩ泦銇椼仸銇撱倢銈夈伄鎯呭牨銈掑墛闄ゃ仚銈嬨亾銇ㄣ倰妞滆◣銇椼仸銇忋仩銇曘亜銆�</string>
+    <string name="warning">娉ㄦ剰锛�</string>
+    <string name="no_data">銉囥兗銈裤仾銇�</string>
+    <string name="preferences_led_color">LED 銇壊</string>
+    <string name="preferences_fm_frequency">FM 銇懆娉㈡暟</string>
+    <string name="pref_invalid_frequency_title">鍛ㄦ尝鏁般亴姝c仐銇忋亗銈娿伨銇涖倱</string>
+    <string name="pref_invalid_frequency_message">87.5 銇嬨倝 108.0 銇枔銇у懆娉㈡暟銈掑叆鍔涖仐銇︺亸銇犮仌銇�</string>
+    <string name="language_and_region_prefs">瑷�瑾炪仺鍦板煙銇ō瀹�</string>
+    <string name="title_activity_notification_filter">閫氱煡銉曘偅銉偪銉�</string>
+    <string name="edittext_notification_filter_words_hint">甯屾湜銇崢瑾炪倰銆併仢銈屻仦銈屾柊銇椼亜琛屻伀鍏ュ姏銇椼仸銇忋仩銇曘亜</string>
+    <string name="toast_notification_filter_saved_successfully">閫氱煡銉曘偅銉偪銉笺倰淇濆瓨銇椼伨銇椼仧</string>
+    <string name="filter_mode_none">銉曘偅銉偪銉笺仐銇亜</string>
+    <string name="filter_mode_whitelist">鍗樿獮銇屽惈銇俱倢銇︺亜銈嬨仺銇嶃伀琛ㄧず銇欍倠</string>
+    <string name="filter_mode_blacklist">鍗樿獮銇屽惈銇俱倢銈嬨仺銇嶃伀銉栥儹銉冦偗銇欍倠</string>
+    <string name="filter_submode_at_least_one">銇勩仛銈屻亱銇崢瑾炪倰鍚個</string>
+    <string name="filter_submode_all">銇欍伖銇︺伄鍗樿獮</string>
+    <string name="toast_notification_filter_words_empty_hint">灏戙仾銇忋仺銈傦紤銇ゃ伄鍗樿獮銈掑叆鍔涖仐銇︺亸銇犮仌銇�</string>
+    <string name="filter_mode">銉曘偅銉偪銉笺儮銉笺儔</string>
+    <string name="mode_configuration">銉€兗銉夎ō瀹�</string>
+    <string name="save_configuration">瑷畾銈掍繚瀛�</string>
+    <string name="appwidget_not_connected">鎺ョ稓銇曘倢銇︺亜銇亜銇熴倎銈€儵銉笺儬銇ō瀹氥仌銈屻伨銇涖倱銆�</string>
+    <string name="fw_upgrade_notice_amazfitcor2">Amazfit Cor 2 銇� %s 銉曘偂銉笺儬銈︺偋銈€倰銈ゃ兂銈广儓銉笺儷銇椼倛銇嗐仺銇椼仸銇勩伨銇欍��
+\n聽
+\n.fw 銉曘偂銈ゃ儷銈掋偆銉炽偣銉堛兗銉仐銇︺亱銈夈��.res 銉曘偂銈ゃ儷銈掋偆銉炽偣銉堛兗銉仐銇︺亸銇犮仌銇勩�� .fw 銉曘偂銈ゃ儷銈掋偆銉炽偣銉堛兗銉仐銇熷緦銆併儛銉炽儔銇屽啀璧峰嫊銇椼伨銇欍��
+\n聽
+\n娉�: .res 銉曘偂銈ゃ儷銇屽墠鍥炪伀銈ゃ兂銈广儓銉笺儷銇椼仧銈傘伄銇ㄥ悓銇樺牬鍚堛�佹柊銇熴伀.res 銉曘偂銈ゃ儷銈掋偆銉炽偣銉堛兗銉仚銈嬪繀瑕併伅銇傘倞銇俱仜銈撱��
+\n聽
+\n鑷繁璨换銇笅銇ц銇c仸銇忋仩銇曘亜锛�
+\n聽
+\n瀹屽叏銇伅銉嗐偣銉堛仌銈屻仸銇勩伨銇涖倱銆傘儑銉愩偆銈瑰悕銇� \"Amazfit Band 2\" 銇牬鍚堛�併亰銇濄倝銇� BEATS_W 銉曘偂銉笺儬銈︺偋銈€倰銉曘儵銉冦偡銉ャ仚銈嬪繀瑕併亴銇傘倞銇俱仚</string>
+    <string name="pref_title_support_voip_calls">VoIP 銈€儣銉伄閫氳┍銈掓湁鍔广伀銇欍倠</string>
+    <string name="fw_upgrade_notice_miband4">Mi Band 4 銇� %s 銉曘偂銉笺儬銈︺偋銈€倰銈ゃ兂銈广儓銉笺儷銇椼倛銇嗐仺銇椼仸銇勩伨銇欍��
+\n
+\n.fw 銉曘偂銈ゃ儷銈掋偆銉炽偣銉堛兗銉仐銇︺亱銈夈��.res 銉曘偂銈ゃ儷銈掋偆銉炽偣銉堛兗銉仐銇︺亸銇犮仌銇勩�� .fw 銉曘偂銈ゃ儷銈掋偆銉炽偣銉堛兗銉仚銈嬨仺銆併儛銉炽儔銇屽啀璧峰嫊銇椼伨銇欍��
+\n
+\n娉�: 浠ュ墠銇偆銉炽偣銉堛兗銉仐銇熴倐銇仺銇俱仯銇熴亸鍚屻仒鍫村悎銆�.res 銈掋偆銉炽偣銉堛兗銉仚銈嬪繀瑕併伅銇傘倞銇俱仜銈撱��
+\n
+\n銇旇嚜韬伄璨换銇ц銇c仸銇忋仩銇曘亜锛�</string>
+    <string name="pref_summary_expose_hr">Gadgetbridge 銇帴缍氫腑銇�佷粬銇偄銉椼儶銇� HR 銉囥兗銈裤伀銉偄銉偪銈ゃ儬銇с偄銈偦銈广仹銇嶃倠銈堛亞銇仐銇俱仚</string>
+    <string name="pref_title_expose_hr">銈点兗銉夈儜銉笺儐銈c伄銉偄銉偪銈ゃ儬 HR 銈€偗銈汇偣</string>
+    <string name="pref_title_use_custom_font">銈偣銈裤儬銉曘偐銉炽儓銈掍娇鐢ㄣ仚銈�</string>
+    <string name="pref_summary_use_custom_font">銉囥儛銈ゃ偣銇档鏂囧瓧銇蹇溿仐銇熴偣銈裤儬銉曘偐銉炽儓銉曘偂銉笺儬銈︺偋銈€亴銇傘倠鍫村悎銆併亾銈屻倰鏈夊姽銇仐銇︺亸銇犮仌銇�</string>
+    <string name="pref_display_add_device_fab">鏂般仐銇勩儑銉愩偆銈广倰鎺ョ稓銇欍倠銉溿偪銉�</string>
+    <string name="pref_display_add_device_fab_on">甯搞伀琛ㄧず</string>
+    <string name="pref_display_add_device_fab_off">銉囥儛銈ゃ偣銇岀櫥閷层仌銈屻仸銇勩仾銇勫牬鍚堛伀銇伩琛ㄧず</string>
+    <string name="pref_header_charts">銉併儯銉笺儓銇ō瀹�</string>
+    <string name="pref_title_charts_average">銈般儵銉曘伀骞冲潎鍊ゃ倰琛ㄧず銇欍倠</string>
+    <string name="czesh">銉併偋銈宠獮</string>
+    <string name="swedish">銈广偊銈с兗銉囥兂瑾�</string>
+    <string name="hebrew">銉樸儢銉┿偆瑾�</string>
+    <string name="greek">銈儶銈枫儯瑾�</string>
+    <string name="hungarian">銉忋兂銈儶銉艰獮</string>
+    <string name="romanian">銉兗銉炪儖銈㈣獮</string>
+    <string name="portuguese">銉濄儷銉堛偓銉獮</string>
+    <string name="vietnamese">銉欍儓銉娿儬瑾�</string>
+    <string name="thai">銈裤偆瑾�</string>
+    <string name="arabic">銈€儵銉撱偄瑾�</string>
+    <string name="ukrainian">銈︺偗銉┿偆銉婅獮</string>
+    <string name="turkish">銉堛儷銈宠獮</string>
+    <string name="dutch">銈儵銉炽儉瑾�</string>
+    <string name="pref_summary_force_white_color_scheme">銈︺偐銉冦儊銇嚌銇屾殫鑹层伄鍫村悎銇焦绔嬨仭銇俱仚</string>
+    <string name="bip_prefs_shotcuts_summary">銉愩兂銉夌敾闈€伄銈枫儳銉笺儓銈儍銉堛倰閬告姙銇欍倠</string>
+    <string name="bip_prefs_shortcuts">銈枫儳銉笺儓銈儍銉�</string>
+    <string name="prefs_enable_find_phone">銆屻偣銉炪兗銉堛儠銈┿兂銈掓帰銇欍�嶃倰 ON 銇仚銈�</string>
+    <string name="prefs_find_phone">銈广優銉笺儓銉曘偐銉炽倰鎺€仚</string>
+    <string name="average">骞冲潎: %1$s</string>
+    <string name="alarm_snooze">銈广儗銉笺偤</string>
+    <string name="interval_forty_five_minutes">45鍒嗐仈銇�</string>
+    <string name="interval_fifteen_minutes">15鍒嗐仈銇�</string>
+    <string name="pref_screen_notification_profile_calendar">銈儸銉炽儉銉笺伄閫氱煡</string>
+    <string name="pref_screen_notification_profile_missed_call">涓嶅湪鐫�淇¢�氱煡</string>
+    <string name="find_my_phone_notification">銈广優銉笺儓銉曘偐銉炽倰鎺€仚</string>
+    <string name="zetime_signaling_none">銈点偆銉兂銉�</string>
+    <string name="zetime_date_format_3">MM/DD/YY</string>
+    <string name="zetime_date_format_2">DD/MM/YY</string>
+    <string name="zetime_date_format_1">YY/MM/DD</string>
+    <string name="zetime_handmove_display">鎵嬨伄鍕曘亶</string>
+    <string name="zetime_title_settings">ZeTime 瑷畾</string>
+    <string name="preferences_makibes_hr3_settings">Makibes HR3 瑷畾</string>
+    <string name="pref_title_sync_caldendar">銈儸銉炽儉銉笺伄浜堝畾銈掑悓鏈熴仚銈�</string>
+    <string name="pref_theme_system">銈枫偣銉嗐儬</string>
+    <string name="zetime_prefs_inactivity_su">鏃ユ洔鏃�</string>
+    <string name="zetime_prefs_inactivity_sa">鍦熸洔鏃�</string>
+    <string name="zetime_prefs_inactivity_fr">閲戞洔鏃�</string>
+    <string name="zetime_prefs_inactivity_th">鏈ㄦ洔鏃�</string>
+    <string name="zetime_prefs_inactivity_we">姘存洔鏃�</string>
+    <string name="zetime_prefs_inactivity_tu">鐏洔鏃�</string>
+    <string name="zetime_prefs_inactivity_mo">鏈堟洔鏃�</string>
+    <string name="zetime_analog_mode">銈€儕銉偘銉€兗銉�</string>
+    <string name="activity_prefs_alarm_min_heart_rate">鏈�灏忓績鎷嶆暟</string>
+    <string name="activity_prefs_alarm_max_heart_rate">鏈�澶у績鎷嶆暟</string>
+    <string name="zetime_heart_rate_alarm_enable">蹇冩媿鏁般偄銉┿兗銉犮倰鏈夊姽銇仚銈�</string>
+    <string name="zetime_title_heart_rate_alarm">蹇冩媿鏁般偄銉┿兗銉�</string>
+    <string name="zetime_title_heartrate">蹇冩媿鏁般伄瑷畾</string>
+    <string name="pref_header_device_spec_settings">銉囥儛銈ゃ偣銇ō瀹�</string>
+    <string name="pref_header_notifications_and_calls">閫氱煡銇ㄩ�氳┍</string>
+    <string name="seconds">绉�</string>
+    <string name="km_h">km/h</string>
+    <string name="meters_second">m/s</string>
+    <string name="steps_unit">姝�</string>
+    <string name="cm">cm</string>
+    <string name="meters">m</string>
+    <string name="averageKMPaceSeconds">銉氥兗銈�</string>
+    <string name="averageHR">蹇冩媿鏁�</string>
+    <string name="maxPace">鏈�閫熴伄銉氥兗銈�</string>
+    <string name="maxSpeed">鏈�澶�</string>
+    <string name="caloriesBurnt">銈儹銉兗</string>
+    <string name="activeSeconds">銈€偗銉嗐偅銉�</string>
+    <string name="steps">姝╂暟</string>
+    <string name="minAltitude">鏈�灏�</string>
+    <string name="maxAltitude">鏈�澶�</string>
+    <string name="distanceMeters">璺濋洟</string>
+    <string name="device_requires_key">銈兗銇屽繀瑕併仹銇欍�傞暦鎶笺仐銇椼仸鍏ュ姏銇椼仸銇忋仩銇曘亜</string>
+    <string name="about_contributors">璨㈢尞鑰�</string>
+    <string name="about_activity_title_generic">Gadgetbridge 銇仱銇勩仸</string>
+    <string name="about_title">銇撱伄銈€儣銉伀銇ゃ亜銇�</string>
+    <string name="hr_widget_last_notification">鏈�寰屻伄閫氱煡</string>
+    <string name="hr_appname_commute">Commute</string>
+    <string name="hr_appname_stopwatch">銈广儓銉冦儣銈︺偐銉冦儊</string>
+    <string name="hr_widget_weather">澶╂皸</string>
+    <string name="hr_widget_battery">銉愩儍銉嗐儶銉�</string>
+    <string name="hr_widget_calories">銈儹銉兗</string>
+    <string name="hr_widget_date">鏃ヤ粯</string>
+    <string name="hr_widget_steps">姝╂暟</string>
+    <string name="hr_widget_heart_rate">蹇冩媿鏁�</string>
+    <string name="qhybrid_vibration_strength">鎸嫊銇挤銇曪細</string>
+    <string name="widget_1_hour">1 鏅傞枔</string>
+    <string name="widget_20_minutes">20 鍒�</string>
+    <string name="widget_10_minutes">10 鍒�</string>
+    <string name="widget_5_minutes">5 鍒�</string>
+    <string name="appwidget_sleep_alarm_widget_label">鐫$湢銈€儵銉笺儬</string>
+    <string name="menuitem_worldclock">涓栫晫鏅傝▓</string>
+    <string name="menuitem_unknown">涓嶆槑</string>
+    <string name="menuitem_hr">蹇冩媿鏁�</string>
+    <string name="menuitem_pai">PAI</string>
+    <string name="menuitem_stress">銈广儓銉偣</string>
+    <string name="menuitem_nfc">NFC</string>
+    <string name="devicetype_amazfit_gtr">Amazfit GTR</string>
+    <string name="devicetype_amazfit_cor2">Amazfit Cor 2</string>
+    <string name="devicetype_amazfit_bip_lite">Amazfit Bip Lite</string>
+    <string name="devicetype_miband5">Mi Band 5</string>
+    <string name="devicetype_miband4">Mi Band 4</string>
+    <string name="activity_type_exercise">浣撴搷</string>
+    <string name="activity_summary_yesterday">鏄ㄦ棩</string>
+    <string name="activity_summary_today">浠婃棩</string>
+    <string name="activity_type_yoga">銉ㄣ偓</string>
+    <string name="activity_type_jump_roping">绺勮烦銇�</string>
+    <string name="activity_type_indoor_cycling">灞嬪唴銈点偆銈儶銉炽偘</string>
+    <string name="maximum_duration">鏈熼枔</string>
+    <string name="find_lost_device_you_found_it">瑕嬨仱銇戙仧锛�</string>
+    <string name="activity_DB_delete_legacy_button">鍙ゃ亜銉囥兗銈裤儥銉笺偣銈掑墛闄�</string>
+    <string name="activity_db_management_autoexport_label">鑷嫊銈ㄣ偗銈广儩銉笺儓</string>
+    <string name="pref_chart_sleep_rolling_24_on">閬庡幓24鏅傞枔</string>
+    <string name="pref_title_chart_heartrate_color">蹇冩媿鏁般伄鑹�</string>
+    <string name="pref_chart_heartrate_color_orange">姗�</string>
+    <string name="pref_chart_heartrate_color_red">璧�</string>
+    <string name="indonesian">銈ゃ兂銉夈儘銈枫偄瑾�</string>
+    <string name="prefs_disconnect_notification">鍒囨柇閫氱煡</string>
+    <string name="Distance">璺濋洟</string>
+    <string name="sonyswr12_settings_title">Sony SWR12 瑷畾</string>
+    <string name="sonyswr12_settings_low_vibration">寮便亜鎸嫊銇搞伄瀵惧繙</string>
+    <string name="sonyswr12_settings_stamina">鐪侀浕鍔涖儮銉笺儔銈掋偑銉炽伀銇欍倠</string>
+    <string name="sonyswr12_settings_alarm_interval">鍒嗗崢浣嶃伄銈广優銉笺儓銈€儵銉笺儬闁撻殧</string>
+    <string name="sonyswr12_settings_low_vibration_summary">銉愩兂銉夈仹浣庡挤搴︺伄鎸嫊銈掓湁鍔广伀銇椼伨銇�</string>
+    <string name="sonyswr12_settings_stamina_summary">鐪侀浕鍔涖儮銉笺儔銇с伅銆佸績鎷嶆暟銇畾鏈熺殑銇嚜鍕曟脯瀹氥亴銈儠銇仾銈娿�佸嫊浣滄檪闁撱亴闀枫亸銇倞銇俱仚</string>
+    <string name="sonyswr12_settings_alarm_interval_summary">銈广優銉笺儓銈€儵銉笺儬闁撻殧銇�併偆銉炽偣銉堛兗銉仌銈屻仧銈€儵銉笺儬銇墠銇枔闅斻仹銇欍�� 銇撱伄闁撻殧銇с伅銆併儑銉愩偆銈广伅銉︺兗銈躲兗銈掔洰瑕氥倎銇曘仜銈嬨仧銈併伀鐫$湢銇渶銈傝唤銇勬闅庛倰妞滃嚭銇椼倛銇嗐仺銇椼仸銇勩伨銇�</string>
+    <string name="prefs_wena3_receive_calls_title">鐫�淇¤ō瀹�</string>
+    <string name="prefs_wena3_button_action_name_start_timer">瑷畾涓偪銈ゃ優銉笺倰闁嬪</string>
+    <string name="prefs_wena3_menu_icon_hint">瑷畾銈€偆銈炽兂銇父銇儭銉嬨儱銉笺伄鏈�寰屻伀琛ㄧず銇曘倢銇俱仚</string>
+    <string name="prefs_wena3_notification_vibration_repetition_3">3 鍥�</string>
+    <string name="prefs_wena3_button_action_name_qrio_lock">Qrio銉儍銈�</string>
+    <string name="prefs_wena3_auto_power_off_turn_off_time_item">闆绘簮銈儠</string>
+    <string name="prefs_wena3_vibration_short">銈枫儳銉笺儓</string>
+    <string name="prefs_wena3_home_icon_name_calories">銈儹銉兗</string>
+    <string name="prefs_home_icon_left_item">宸�</string>
+    <string name="prefs_wena3_led_none">鐐圭伅銇仐</string>
+    <string name="prefs_wena3_vibration_step_down">銈广儐銉冦儣銉汇儉銈︺兂</string>
+    <string name="prefs_wena3_vibration_smart_item">銈广優銉笺儓銉愩偆銉�</string>
+    <string name="prefs_wena3_home_icon_name_riiiver">Riiiver</string>
+    <string name="prefs_wena3_hint_background_sync">Wena銇嬨倝瀹氭湡銇悓鏈熷寲瑕佹眰銈掑彈淇°仐銇熼殯銇獹adgetbridge銇偄銈儐銈c儞銉嗐偅銉囥兗銈裤倰鍚屾湡鍖栥仐銇俱仚</string>
+    <string name="prefs_wena3_home_icon_name_suica">Suica娈嬮珮</string>
+    <string name="prefs_wena3_vibration_strength_item_medium">鏅��</string>
+    <string name="prefs_wena3_title_activity">銈€偗銉嗐偅銉撱儐銈hō瀹�</string>
+    <string name="prefs_wena3_vibration_continuous">閫g稓</string>
+    <string name="prefs_wena3_home_icon_name_pedometer">姝╂暟</string>
+    <string name="prefs_wena3_home_icon_name_qrio">Qrio</string>
+    <string name="white">鐧�</string>
+    <string name="prefs_wena3_home_icon_name_clock">鐝惧湪鏅傚埢</string>
+    <string name="prefs_wena3_item_background_sync">鑷嫊銉囥兗銈垮悓鏈熷寲</string>
+    <string name="prefs_wena3_vibration_strength_item_weak">寮�</string>
+    <string name="prefs_wena3_home_icon_title">銉涖兗銉犵敾闈甫銇虫浛銇�</string>
+    <string name="prefs_wena3_item_weather_statusbar">銈广儐銉笺偪銈广儛銉笺伀澶╂皸浜堝牨</string>
+    <string name="prefs_wena3_vibration_step_up">銈广儐銉冦儣銉汇偄銉冦儣</string>
+    <string name="prefs_home_icon_right_item">鍙�</string>
+    <string name="prefs_wena3_vibration_basic">銉欍兗銈枫儍銈�</string>
+    <string name="prefs_wena3_title_screen">鐢婚潰琛ㄧず瑷畾</string>
+    <string name="prefs_wena3_item_large_font">銉曘偐銉炽儓銈点偆銈哄ぇ銇嶃倎</string>
+    <string name="prefs_wena3_home_icon_name_energy">銈ㄣ儕銈搞兗</string>
+    <string name="prefs_wena3_notification_vibration_repetition_2">2 鍥�</string>
+    <string name="prefs_wena3_auto_power_off_hint">鎸囧畾銇椼仧鏅傞枔銇ena銇浕婧愩倰鑷嫊銇с偑銉曘兓銈兂銇仐銇俱仚</string>
+    <string name="prefs_wena3_hint_lift_wrist">鑵曢銈掑洖銇欍仺銇嶇敾闈€倰鐐圭伅銇椼伨銇�</string>
+    <string name="prefs_wena3_hint_large_font">閫氱煡銆併偣銈便偢銉ャ兗銉仾銇┿伄鏂囧瓧銈掑ぇ銇嶃亸銇仐銇俱仚</string>
+    <string name="prefs_wena3_button_action_item_double">銉�銉栥儷銈儶銉冦偗</string>
+    <string name="prefs_wena3_vibration_rapid">銉┿償銉冦儔</string>
+    <string name="prefs_wena3_button_action_name_qrio_unlock">Qrio瑙i櫎</string>
+    <string name="prefs_wena3_notification_vibration_repetition_1">1 鍥�</string>
+    <string name="prefs_wena3_receive_calls_item">鐫�淇¢�氱煡銈掑彈銇戝彇銈�</string>
+    <string name="prefs_wena3_hint_weather_statusbar">銉涖兗銉犵敾闈€伄宸︿笂銇ぉ姘椼偄銈ゃ偝銉炽倰琛ㄧず銇椼伨銇�</string>
+    <string name="prefs_wena3_home_icon_name_wena_pay">Wena Pay</string>
+    <string name="prefs_wena3_button_action_item">銉溿偪銉虫搷浣�</string>
+    <string name="prefs_wena3_auto_power_off_turn_on_time_item">闆绘簮銈兂</string>
+    <string name="prefs_wena3_notification_default_call_vibration">鐫�淇°儛銈ゃ儢</string>
+    <string name="green">绶�</string>
+    <string name="yellow">榛�</string>
+    <string name="prefs_wena3_notification_default_call_led">鐫�淇°仺銇嶃伄LED鑹�</string>
+    <string name="red">璧�</string>
+    <string name="cyan">闈掔窇</string>
+    <string name="prefs_wena3_vibration_siren">銈点偆銉兂</string>
+    <string name="prefs_wena3_menu_icon_title">銉°儖銉ャ兗涓︺伋鏇裤亪</string>
+    <string name="prefs_wena3_notification_default_call_vibration_repetition">鐫�淇°儛銈ゃ儢绻般倞杩斻仐</string>
+    <string name="prefs_wena3_notification_default_vibration">閫氱煡銇儛銈ゃ儢</string>
+    <string name="prefs_wena3_notification_vibration_repetition_0">銉溿偪銉炽倰鎿嶄綔銇欍倠銇俱仹</string>
+    <string name="prefs_wena3_notification_vibration_repetition_4">4 鍥�</string>
+    <string name="prefs_wena3_vibration_strength_item">銉愩偆銉栧挤銇�</string>
+    <string name="prefs_wena3_receive_calls_hint">鐒″姽銇仚銈嬨仺鐫�淇¢�氱煡銇疻ena銇眾銇嬨仾銇忋仾銈�</string>
+    <string name="prefs_wena3_button_action_item_long">闀锋娂銇�</string>
+    <string name="prefs_wena3_menu_icon_name_payment">鏀墪銇�</string>
+    <string name="prefs_wena3_status_page_title">銈€偗銉嗐偅銉撱儐銈g敾闈甫銇虫浛銇�</string>
+    <string name="prefs_wena3_title_alarm">銈€儵銉笺儬瑷畾</string>
+    <string name="prefs_wena3_notification_default_vibration_repetition">閫氱煡銇儛銈ゃ儢绻般倞杩斻仐</string>
+    <string name="prefs_home_icon_center_item">涓�</string>
+    <string name="prefs_wena3_hint_rich_design">銉涖兗銉犵敾闈€伄銈€偆銈炽兂銇儗鏅倰琛ㄧず銇椼伨銇�</string>
+    <string name="prefs_wena3_notification_default_led">閫氱煡銇ㄣ亶銇甃ED鑹�</string>
+    <string name="prefs_wena3_auto_power_off_item">鑷嫊闆绘簮銈儠銉汇偑銉�</string>
+    <string name="prefs_wena3_home_icon_name_edy">Edy娈嬮珮</string>
+    <string name="prefs_wena3_vibration_warning">銉兗銉嬨兂銈�</string>
+    <string name="prefs_wena3_smart_alarm_margin_item">銈广優銉笺儓銈€儵銉笺儬浣欒▓</string>
+    <string name="prefs_wena3_item_rich_design">銉儍銉併儑銈躲偆銉�</string>
+    <string name="prefs_wena3_day_start_hour_item">銉囥兗銈块�併倞鏅傞枔</string>
+    <string name="prefs_wena3_notification_per_app_settings_title">銈€儣銉垾閫氱煡瑷畾</string>
+    <string name="prefs_wena3_vibration_none">銉愩偆銉栥仾銇�</string>
+    <string name="purple">绱�</string>
+    <string name="prefs_wena3_vibration_strength_item_strong">寮�</string>
+    <string name="prefs_wena3_vibration_triple">銉堛儶銉椼儷</string>
+    <string name="blue">闈�</string>
+    <string name="prefs_wena3_button_action_name_activity_screen">銈€偗銉嗐偅銉撱儐銈g敾闈�</string>
+    <string name="prefs_wena3_notification_settings_title">閫氱煡瑷畾</string>
+    <string name="application_name_banglejs_nightly">Bangle.js Gadgetbridge (Nightly)</string>
+    <string name="pref_pair_bluetooth_calls_help_title">Bluetooth 閫氳┍銇潃淇℃柟娉�</string>
+    <string name="fw_upgrade_notice_miband7">Xiaomi Smart Band 7 銇� %s 銉曘偂銉笺儬銈︺偋銈€倰銈ゃ兂銈广儓銉笺儷銇椼倛銇嗐仺銇椼仸銇勩伨銇欍��
+\n
+\n.zip銉曘偂銈ゃ儷銈掋偆銉炽偣銉堛兗銉仚銈嬨仺銆併儛銉炽儔銇屽啀璧峰嫊銇椼伨銇欍��
+\n
+\n鑷繁璨换銇笅銇ц銇c仸銇忋仩銇曘亜锛�</string>
+    <string name="fw_upgrade_notice_amazfit_gtr4">Amazfit GTR 4銇� %s 銉曘偂銉笺儬銈︺偋銈€倰銈ゃ兂銈广儓銉笺儷銇椼倛銇嗐仺銇椼仸銇勩伨銇欍��
+\n
+\n.zip銉曘偂銈ゃ儷銈掋偆銉炽偣銉堛兗銉仚銈嬨仺銆併儛銉炽儔銇屽啀璧峰嫊銇椼伨銇欍��
+\n
+\n鑷繁璨换銇笅銇ц銇c仸銇忋仩銇曘亜锛�</string>
+    <string name="pref_pair_bluetooth_calls_help_summary">Bluetooth閫氳┍銈掔潃淇°仚銈嬨伀銇�丄ndroid 銈掋偊銈┿儍銉併伄2鐣洰銇偆銉炽偣銈裤兂銈广仺銉氥偄銉兂銈般仚銈嬪繀瑕併亴銇傘倞銇俱仚銆�</string>
+    <string name="pref_pair_bluetooth_calls_help_warning">娉ㄦ剰锛�2鐣洰銇偆銉炽偣銈裤兂銈广倰鏈夊姽銇仜銇氥伀 Bluetooth 閫氳┍銈掓湁鍔广伀銇欍倠銇ㄣ�佺潃淇¢�氱煡銇屼笂鎵嬨亸鍕曚綔銇椼仾銇勫牬鍚堛亴銇傘倞銇俱仚銆�</string>
+    <string name="pref_title_notification_use_as">銈€儣銉儶銈广儓銇娇鐢ㄦ柟娉曗��</string>
+    <string name="appmanager_watchface_activate">鏈夊姽鍖�</string>
+    <string name="appmanager_app_start">瀹熻</string>
+    <string name="appmanager_app_download">銈儯銉冦偡銉ャ伀銉�銈︺兂銉兗銉�</string>
+    <string name="appmanager_download_started">銈€儣銉伄銉�銈︺兂銉兗銉夈倰闁嬪銇椼伨銇椼仧</string>
+    <string name="appmanager_download_app_error">銈€儣銉伄銉�銈︺兂銉兗銉変腑銇偍銉┿兗銇岀櫤鐢熴仐銇俱仐銇�</string>
+    <string name="pref_title_notifications_ignore_low_priority">鍎厛搴︺伄浣庛亜閫氱煡銈掗潪琛ㄧず銇仚銈�</string>
+    <string name="find_lost_device_message">%1$s 銈掓帰銇椼伨銇欍亱锛�</string>
+    <string name="controlcenter_power_off">闆绘簮銈掑垏銈�</string>
+    <string name="battery_detail_activity_title">銉愩儍銉嗐儶銉兼儏鍫�</string>
+    <string name="pref_header_connection">鎺ョ稓</string>
+    <string name="pref_title_ping_tone">閫氱煡闊�</string>
+    <string name="calendar_day">鏃�</string>
+    <string name="pref_header_audio">闊冲0</string>
+    <string name="pref_header_calendar">銈儸銉炽儉銉�</string>
+    <string name="pref_header_generic">鍏ㄨ埇</string>
+    <string name="pref_header_health">鍋ュ悍绠$悊</string>
+    <string name="pref_header_display">銉囥偅銈广儣銉偆</string>
+    <string name="pref_header_sound_vibration">闊炽仺鎸嫊</string>
+    <string name="pref_header_equalizer">銈ゃ偝銉┿偆銈躲兗</string>
+    <string name="pref_summary_general_reconnectonlytoconnected">銇欍伖銇︺伄銉囥儛銈ゃ偣銇啀鎺ョ稓銇欍倠銇仹銇仾銇忋�佹帴缍氭笀銇裤伄銉囥儛銈ゃ偣銇伄銇垮啀鎺ョ稓</string>
+    <string name="pref_summary_mb_intents">涓�閮ㄣ伄銈€儣銉仹銉囥儛銈ゃ偣銇儭銉囥偅銈€偝銉炽儓銉兗銉亴姗熻兘銇椼仾銇勫牬鍚堛伀鏈夊姽鍖栥仐銇︺亸銇犮仌銇�</string>
+    <string name="pref_theme_dynamic">銉�銈ゃ儕銉熴儍銈偒銉┿兗</string>
+    <string name="pref_title_notification_delay_calls">鐫�淇¢�氱煡銇亝寤�</string>
+    <string name="pref_summary_receive_calls_watch">銈︺偐銉冦儊涓娿仹鐧轰俊/鐫�淇°仚銈�</string>
+    <string name="pref_title_banglejs_webview_url">銈€儣銉儹銉笺儉銉笺伄 URL</string>
+    <string name="accuracy">绮惧害</string>
+    <string name="balanced">銉愩儵銉炽偣</string>
+    <string name="power_saving">鐪侀浕鍔�</string>
+    <string name="custom">銈偣銈裤儬</string>
+    <string name="gps_galileo">GPS + GALILEO</string>
+    <string name="speed_first">閫熷害鍎厛</string>
+    <string name="accuracy_first">绮惧害鍎厛</string>
+    <string name="application_name_main_nightly">Gadgetbridge (Nightly)</string>
+    <string name="title_activity_controlcenter_main_nightly">Gadgetbridge Nightly</string>
+    <string name="about_activity_title_main_nightly">Gadgetbridge Nightly 銇仱銇勩仸</string>
+    <string name="controlcenter_power_off_confirm_title">闆绘簮銈掑垏銈�</string>
+    <string name="controlcenter_power_off_confirm_description">銉囥儛銈ゃ偣銇浕婧愩倰鍒囥倞銇俱仚銇嬶紵</string>
+    <string name="pref_header_other">銇濄伄浠�</string>
+    <string name="controlcenter_get_heartrate_measurement">蹇冩媿鏁般倰娓倠</string>
+    <string name="battery_level">銉愩儍銉嗐儶銉兼畫閲�</string>
+    <string name="calendar_week">1閫遍枔</string>
+    <string name="title_activity_notification_management">閫氱煡瑷畾</string>
+    <string name="open_fw_installer_ensure_device_connected">%s 銇屾帴缍氥仌銈屻仸銇勩倠銇撱仺銈掔⒑瑾嶃仐銇︺亸銇犮仌銇�</string>
+    <string name="fw_upgrade_notice_amazfitneo">Amazfit Neo 銇� %s 銉曘偂銉笺儬銈︺偋銈€倰銈ゃ兂銈广儓銉笺儷銇椼倛銇嗐仺銇椼仸銇勩伨銇欍��
+\n
+\n.fw銉曘偂銈ゃ儷銈掋偆銉炽偣銉堛兗銉仚銈嬨仺銆併儛銉炽儔銇屽啀璧峰嫊銇椼伨銇欍��
+\n
+\n鑷繁璨换銇笅銇ц銇c仸銇忋仩銇曘亜锛�</string>
+    <string name="about_activity_title_banglejs_nightly">Bangle.js Gadgetbridge (Nightly) 銇仱銇勩仸</string>
+    <string name="pref_title_notification_wake_on_open">鑷嫊鐐圭伅銇ㄣ儹銉冦偗瑙i櫎</string>
+    <string name="pref_display_caller_title">閫g怠鍏堛伄鎯呭牨銈掕〃绀恒仚銈�</string>
+    <string name="pref_summary_notifications_ignore_low_priority">鍎厛搴︺伄浣庛亜閫氱煡銈掋偊銈┿儍銉併伀閫佷俊銇椼仾銇勩倛銇嗐伀銇椼伨銇�</string>
+    <string name="pref_title_notification_cache_while_disconnected">鎺ョ稓銇屽垏鏂仌銈屻仸銇勩倠闁撱伄閫氱煡銈掋偔銉c儍銈枫儱銇欍倠</string>
+    <string name="pref_header_notification_application_settings">銈€儣銉仈銇ㄣ伄瑷畾</string>
+    <string name="pref_title_notification_use_as_deny">閬告姙銇椼仧銈€儣銉亱銈夈伄閫氱煡銈掓嫆鍚︺仚銈�</string>
+    <string name="pref_title_notification_use_as_allow">閬告姙銇椼仧銈€儣銉亱銈夈伄閫氱煡銈掕ū鍙仚銈�</string>
+    <string name="pref_message_privacy_mode_complete">銇欍伖銇︺伄鍐呭銈掗潪琛ㄧず</string>
+    <string name="pref_message_privacy_mode_bodyonly">鏈枃銇伩闈炶〃绀�</string>
+    <string name="pref_title_weather_summary">LineageOS 銇� Weather provider 銇娇鐢ㄣ仌銈屻伨銇欍亴銆佷粬銇� Android 銉愩兗銈搞儳銉炽仹銇� \"Weather notification\" 銇倛銇嗐仾銈€儣銉倰浣跨敤銇欍倠蹇呰銇屻亗銈娿伨銇欍�傝┏銇椼亸銇� Gadgetbridge 銇� Wiki 銈掋仈瑕с亸銇犮仌銇勩��</string>
+    <string name="pref_applications_settings">銈€儣銉偙銉笺偡銉с兂涓�瑕�</string>
+    <string name="debugactivity_confirm_remove_device_preferences">鎺ョ稓銇曘倢銇熴仚銇广仸銇儑銉愩偆銈广伄鐠板瑷畾銈掑墛闄ゃ仐銇俱仚銇屻倛銈嶃仐銇勩仹銇欍亱锛�</string>
+    <string name="pref_title_general_reconnectonlytoconnected">鎺ョ稓涓伄銉囥儛銈ゃ偣銇伄銇垮啀鎺ョ稓銇欍倠</string>
+    <string name="pref_summary_notification_prefer_long_text">鍙兘銇牬鍚堛伀闀锋枃銇�氱煡銈掋儑銉愩偆銈广伀閫佷俊銇椼伨銇�</string>
+    <string name="pref_summary_notifications_ignore_work_profile">浠曚簨鐢ㄣ儣銉儠銈°偆銉伄銈€儣銉亱銈夈伄閫氱煡銈掋偊銈┿儍銉併伀閫佷俊銇椼仾銇勩倛銇嗐伀銇椼伨銇�</string>
+    <string name="pref_pair_bluetooth_calls_help_2">2. Android 銇� Bluetooth 銇ō瀹氥伀绉诲嫊銇椼�佽〃绀恒仌銈屻仧鏂般仐銇勩儑銉愩偆銈广仺銉氥偄銉兂銈般仐銇俱仚锛堜娇鐢ㄤ腑銇偊銈┿儍銉併仺浼笺仧鍚嶅墠銇с仚銇屻�併偊銈┿儍銉併伄鍚嶅墠銇湯灏俱伀鎺ラ牠杈炪亴浠樸亶銇俱仚銆備緥锛� Amazfit GTR 4 - AFC8锛夈��</string>
+    <string name="pref_title_notifications_generic_settings">Android 閫氱煡瑷畾</string>
+    <string name="pref_title_notifications_ignore_work_profile">浠曚簨鐢ㄣ儣銉儠銈°偆銉倰闈炶〃绀恒伀銇欍倠</string>
+    <string name="pref_summary_banglejs_text_bitmap">銈︺偐銉冦儊鍐呰數銇儠銈┿兂銉堛仹琛ㄧず銇с亶銇亜鍫村悎銇� Gadgetbridge 銇с儠銈┿兂銉堛倰銉撱儍銉堛優銉冦儣銇с儸銉炽儉銉兂銈般仐銆併偊銈┿儍銉併伀琛ㄧず銇椼伨銇�</string>
+    <string name="pref_summary_banglejs_txt_bitmap_size">銉曘偐銉炽儓銈掋儞銉冦儓銉炪儍銉椼伀銉兂銉�銉兂銈般仚銈嬨仺銇嶃伄澶с亶銇�</string>
+    <string name="pref_title_banglejs_phone_gps_enbale">Android 銇� GPS 銉囥兗銈裤倰浣跨敤銇欍倠</string>
+    <string name="pref_title_banglejs_phone_gps_network_only">娓綅銇儘銉冦儓銉兗銈伄銇裤倰浣跨敤銇欍倠</string>
+    <string name="pref_summary_banglejs_phone_gps_enbale">Android 銇� GPS 銉囥兗銈裤倰浣裤仯銇� Bangle 銉囥儛銈ゃ偣銇� GPS 銉囥兗銈裤倰涓婃浉銇嶃仐銇俱仚</string>
+    <string name="pref_summary_banglejs_phone_gps_update_interval">GPS 銇綅缃儏鍫便亴鏇存柊銇曘倢銈嬮枔闅� (銉熴儶绉�)</string>
+    <string name="pref_summary_banglejs_webview_url">銈偣銈裤儬銈€儣銉儹銉笺儉銉笺亴蹇呰銇牬鍚堛伅 https://鈥�/android.html 銇� URL 銈掋亾銇撱伀鍏ュ姏銇椼仸銇忋仩銇曘亜銆傜┖鐧姐伀銇欍倠銇� https://banglejs.com/apps 銇屾寚瀹氥仌銈屻伨銇�</string>
+    <string name="pref_header_authentication">瑾嶈</string>
+    <string name="pref_title_custom_deviceicon">銉囥儛銈ゃ偣鍥烘湁銇�氱煡銈€偆銈炽兂銈掕〃绀恒仚銈�</string>
+    <string name="fw_upgrade_notice_miband5">Mi Band 4 銇� %s 銉曘偂銉笺儬銈︺偋銈€倰銈ゃ兂銈广儓銉笺儷銇椼倛銇嗐仺銇椼仸銇勩伨銇欍��
+\n
+\n.fw 銉曘偂銈ゃ儷銈掋偆銉炽偣銉堛兗銉仐銇︺亱銈夈��.res 銉曘偂銈ゃ儷銈掋偆銉炽偣銉堛兗銉仐銇︺亸銇犮仌銇勩�� .fw 銉曘偂銈ゃ儷銈掋偆銉炽偣銉堛兗銉仚銈嬨仺銆併儛銉炽儔銇屽啀璧峰嫊銇椼伨銇欍��
+\n
+\n娉�: .res銉曘偂銈ゃ儷銇屼互鍓嶃伀銈ゃ兂銈广儓銉笺儷銇椼仧銈傘伄銇ㄣ伨銇c仧銇忓悓銇樺牬鍚堛�佹柊銇熴伀.res 銈掋偆銉炽偣銉堛兗銉仚銈嬪繀瑕併伅銇傘倞銇俱仜銈撱��
+\n
+\n鑷繁璨换銇笅銇ц銇c仸銇忋仩銇曘亜锛�</string>
+    <string name="fw_upgrade_notice_amazfit_band7">Amazfit Band 7銇� %s 銉曘偂銉笺儬銈︺偋銈€倰銈ゃ兂銈广儓銉笺儷銇椼倛銇嗐仺銇椼仸銇勩伨銇欍��
+\n
+\n.zip銉曘偂銈ゃ儷銈掋偆銉炽偣銉堛兗銉仚銈嬨仺銆併儛銉炽儔銇屽啀璧峰嫊銇椼伨銇欍��
+\n
+\n鑷繁璨换銇笅銇ц銇c仸銇忋仩銇曘亜锛�</string>
+    <string name="fw_upgrade_notice_miband6">Mi Band 4 銇� %s 銉曘偂銉笺儬銈︺偋銈€倰銈ゃ兂銈广儓銉笺儷銇椼倛銇嗐仺銇椼仸銇勩伨銇欍��
+\n
+\n.fw 銉曘偂銈ゃ儷銈掋偆銉炽偣銉堛兗銉仐銇︺亱銈夈��.res 銉曘偂銈ゃ儷銈掋偆銉炽偣銉堛兗銉仐銇︺亸銇犮仌銇勩�� .fw 銉曘偂銈ゃ儷銈掋偆銉炽偣銉堛兗銉仚銈嬨仺銆併儛銉炽儔銇屽啀璧峰嫊銇椼伨銇欍��
+\n
+\n娉�: .res銉曘偂銈ゃ儷銇屼互鍓嶃伀銈ゃ兂銈广儓銉笺儷銇椼仧銈傘伄銇ㄣ伨銇c仧銇忓悓銇樺牬鍚堛�佹柊銇熴伀.res 銈掋偆銉炽偣銉堛兗銉仚銈嬪繀瑕併伅銇傘倞銇俱仜銈撱��
+\n
+\n鑷繁璨换銇笅銇ц銇c仸銇忋仩銇曘亜锛�</string>
+    <string name="open_fw_installer_getting_files_title">銉曘偂銉笺儬銈︺偋銈�/銈€儣銉� 銉曘偂銈ゃ儷銈掑彇寰�</string>
+    <string name="pref_header_system">銈枫偣銉嗐儬</string>
+    <string name="pref_header_offline_voice">銈儠銉┿偆銉炽儨銈ゃ偣</string>
+    <string name="pref_pair_bluetooth_calls_title">Bluetooth 閫氳┍鐢ㄣ伄銉氥偄銉兂銈�</string>
+    <string name="pref_pair_bluetooth_calls_help_1">1. 涓嬨伄銉溿偪銉炽倰銈裤儍銉椼仐銇︺儦銈€儶銉炽偘銈掗枊濮嬨仚銈嬨��</string>
+    <string name="pref_summary_notification_delay_calls">鐫�淇¢�氱煡銈掋儑銉愩偆銈广伀閫佷俊銇欍倠銇俱仹銇亝寤舵檪闁擄紙绉掞級</string>
+    <string name="pref_pair_bluetooth_calls_summary">銉氥偄銉兂銈般倰闁嬪銇欍倠銇伅銇撱亾銈掋偪銉冦儣</string>
+    <string name="pref_title_notification_prefer_long_text">闀锋枃銇�氱煡銈掑劒鍏堛仚銈�</string>
+    <string name="pref_title_banglejs_text_bitmap">銉撱儍銉堛優銉冦儣銉曘偐銉炽儓銈掍娇鐢ㄣ仚銈�</string>
+    <string name="pref_title_banglejs_txt_bitmap_size">銉曘偐銉炽儓銇儞銉冦儓銉炪儍銉椼偟銈ゃ偤</string>
+    <string name="pref_summary_banglejs_phone_gps_network_only">娓綅銇儘銉冦儓銉兗銈儣銉儛銈ゃ儉銉笺伄銇裤倰浣跨敤銇椼伨銇欍�傛秷璨婚浕鍔涖倰娓涖倝銇椼伨銇欍亴銆佺簿搴︺亴浣庛亸銇倞銇俱仚銆傚啀鎺ョ稓銇屽繀瑕併仹銇欍��</string>
+    <string name="pref_title_banglejs_phone_gps_update_interval">GPS 銉囥兗銈裤伄鏇存柊闁撻殧</string>
+    <string name="pref_blacklist_calendars_summary">銉栥儵銉冦偗銉偣銉堛伀鐧婚尣銇椼仧銈儸銉炽儉銉笺伅銉囥儛銈ゃ偣銇悓鏈熴仌銈屻伨銇涖倱</string>
+    <string name="pref_canned_message">銉°儍銈汇兗銈�</string>
+    <string name="pref_summary_canned_replies">銉椼儶銈汇儍銉堛伄銉°儍銈汇兗銈搞倰浣裤仯銇︺偊銈┿儍銉併亱銈夎繑淇°仚銈�</string>
+    <string name="pref_theme_black_background">銉�銉笺偗銉嗐兗銉炪仹榛掋亜鑳屾櫙銈掍娇鐢ㄣ仚銈�</string>
+    <string name="controlcenter_set_alias">銉囥儛銈ゃ偣銇悕鍓嶃倰浠樸亼銈�</string>
+    <string name="gps">GPS</string>
+    <string name="gps_bds">GPS + BDS</string>
+    <string name="gps_gnolass">GPS + GNOLASS</string>
+    <string name="prefs_activity_in_device_card_sleep_title">鐫$湢</string>
+    <string name="prefs_activity_in_device_card_steps_title_summary">姝╂暟銇悎瑷堛倰琛ㄧず</string>
+    <string name="calendar_two_weeks">2閫遍枔</string>
+    <string name="calendar_month">1銇嬫湀</string>
+    <string name="calendar_three_months">3銇嬫湀</string>
+    <string name="calendar_six_months">6銇嬫湀</string>
+    <string name="calendar_year">1骞�</string>
+    <string name="fw_upgrade_notice_amazfit_gts3">Amazfit GTS 3 銇� %s 銉曘偂銉笺儬銈︺偋銈€倰銈ゃ兂銈广儓銉笺儷銇椼倛銇嗐仺銇椼仸銇勩伨銇欍��
+\n
+\n.zip銉曘偂銈ゃ儷銈掋偆銉炽偣銉堛兗銉仚銈嬨仺銆併儛銉炽儔銇屽啀璧峰嫊銇椼伨銇欍��
+\n
+\n鑷繁璨换銇笅銇ц銇c仸銇忋仩銇曘亜锛�</string>
+    <string name="fw_upgrade_notice_amazfitgts">Amazfit GTS 銇� %s 銉曘偂銉笺儬銈︺偋銈€倰銈ゃ兂銈广儓銉笺儷銇椼倛銇嗐仺銇椼仸銇勩伨銇欍��
+\n聽
+\n.fw 銉曘偂銈ゃ儷銈掋偆銉炽偣銉堛兗銉仐銆佹銇�.res 銉曘偂銈ゃ儷銆佹渶寰屻伀.gps銉曘偂銈ゃ儷銈掋偆銉炽偣銉堛兗銉仐銇︺亸銇犮仌銇勩�� .fw 銉曘偂銈ゃ儷銈掋偆銉炽偣銉堛兗銉仐銇熷緦銆併偊銈┿儍銉併亴鍐嶈捣鍕曘仐銇俱仚銆�
+\n聽
+\n娉�: .res 銉曘偂銈ゃ儷銇� .gps 銉曘偂銈ゃ儷銇屽墠鍥炪伀銈ゃ兂銈广儓銉笺儷銇椼仧銈傘伄銇ㄥ悓銇樺牬鍚堛�佹柊銇熴伀銇撱倢銈夈伄銉曘偂銈ゃ儷銈掋偆銉炽偣銉堛兗銉仚銈嬪繀瑕併伅銇傘倞銇俱仜銈撱��
+\n聽
+\n鑷繁璨换銇笅銇ц銇c仸銇忋仩銇曘亜锛�</string>
+    <string name="fw_upgrade_notice_amazfit_gts4_mini">Amazfit GTS 4 Mini銇� %s 銉曘偂銉笺儬銈︺偋銈€倰銈ゃ兂銈广儓銉笺儷銇椼倛銇嗐仺銇椼仸銇勩伨銇欍��
+\n
+\n.zip銉曘偂銈ゃ儷銈掋偆銉炽偣銉堛兗銉仚銈嬨仺銆併儛銉炽儔銇屽啀璧峰嫊銇椼伨銇欍��
+\n
+\n鑷繁璨换銇笅銇ц銇c仸銇忋仩銇曘亜锛�</string>
+    <string name="fw_upgrade_notice_amazfit_gtr3_pro">Amazfit GTR 3 Pro銇� %s 銉曘偂銉笺儬銈︺偋銈€倰銈ゃ兂銈广儓銉笺儷銇椼倛銇嗐仺銇椼仸銇勩伨銇欍��
+\n
+\n.zip銉曘偂銈ゃ儷銈掋偆銉炽偣銉堛兗銉仚銈嬨仺銆併儛銉炽儔銇屽啀璧峰嫊銇椼伨銇欍��
+\n
+\n鑷繁璨换銇笅銇ц銇c仸銇忋仩銇曘亜锛�</string>
+    <string name="fw_upgrade_notice_amazfit_trex2">Amazfit T-Rex 2銇� %s 銉曘偂銉笺儬銈︺偋銈€倰銈ゃ兂銈广儓銉笺儷銇椼倛銇嗐仺銇椼仸銇勩伨銇欍��
+\n
+\n.zip銉曘偂銈ゃ儷銈掋偆銉炽偣銉堛兗銉仚銈嬨仺銆併儛銉炽儔銇屽啀璧峰嫊銇椼伨銇欍��
+\n
+\n鑷繁璨换銇笅銇ц銇c仸銇忋仩銇曘亜锛�</string>
+    <string name="fw_upgrade_notice_amazfit_gtr3">Amazfit GTR 3銇� %s 銉曘偂銉笺儬銈︺偋銈€倰銈ゃ兂銈广儓銉笺儷銇椼倛銇嗐仺銇椼仸銇勩伨銇欍��
+\n
+\n.zip銉曘偂銈ゃ儷銈掋偆銉炽偣銉堛兗銉仚銈嬨仺銆併儛銉炽儔銇屽啀璧峰嫊銇椼伨銇欍��
+\n
+\n鑷繁璨换銇笅銇ц銇c仸銇忋仩銇曘亜锛�</string>
+    <string name="fw_upgrade_notice_amazfit_cheetah_pro">Amazfit Cheetah Pro銇� %s 銉曘偂銉笺儬銈︺偋銈€倰銈ゃ兂銈广儓銉笺儷銇椼倛銇嗐仺銇椼仸銇勩伨銇欍��
+\n
+\n.zip銉曘偂銈ゃ儷銈掋偆銉炽偣銉堛兗銉仚銈嬨仺銆併儛銉炽儔銇屽啀璧峰嫊銇椼伨銇欍��
+\n
+\n鑷繁璨换銇笅銇ц銇c仸銇忋仩銇曘亜锛�</string>
+    <string name="pref_header_time">鏅傞枔</string>
+    <string name="fw_upgrade_notice_zepp_os">%s銇� %s 銉曘偂銉笺儬銈︺偋銈€倰銈ゃ兂銈广儓銉笺儷銇椼倛銇嗐仺銇椼仸銇勩伨銇欍��
+\n
+\n.zip銉曘偂銈ゃ儷銈掋偆銉炽偣銉堛兗銉仚銈嬨仺銆併儛銉炽儔銇屽啀璧峰嫊銇椼伨銇欍��
+\n
+\n鑷繁璨换銇笅銇ц銇c仸銇忋仩銇曘亜锛�</string>
+    <string name="pref_header_workout">銉兗銈偄銈︺儓</string>
+    <string name="pref_title_message_privacy_mode">銉°儍銈汇兗銈搞伄銉椼儵銈ゃ儛銈枫兗瑷畾</string>
+    <string name="pref_message_privacy_mode_off">銇欍伖銇︺伄鍐呭銈掕〃绀�</string>
+    <string name="title_activity_controlcenter_banglejs_nightly">Bangle.js Gadgetbridge (Nightly)</string>
+    <string name="prefs_activity_in_device_card_distance_title_summary">璺濋洟銇鏁般仺姝╁箙銇嬨倝绠楀嚭銇曘倢銇︺亜銇俱仚锛堣ō瀹� - 銉︺兗銈躲兗鎯呭牨 銇ц鏁淬仹銇嶃伨銇欙級</string>
+    <string name="appmanager_app_share">鍏辨湁</string>
+    <string name="check_all_applications">銇欍伖銇︺伄銈€儣銉倰纰鸿獚銇欍倠</string>
+    <string name="fw_upgrade_notice_amazfitbip_lite">Amazfit Bip Lite 銇� %s 銇儠銈°兗銉犮偊銈с偄銈掋偆銉炽偣銉堛兗銉仐銈堛亞銇ㄣ仐銇︺亜銇俱仚銆�
+\n
+\n.fw 銉曘偂銈ゃ儷銆併仢銇緦 .res 銉曘偂銈ゃ儷銆佹渶寰屻伀 .gps 銉曘偂銈ゃ儷銈掋偆銉炽偣銉堛兗銉仐銇︺亸銇犮仌銇勩�傘偊銈┿儍銉併伅銆�.fw 銉曘偂銈ゃ儷銈掋偆銉炽偣銉堛兗銉仐銇熷緦銇啀璧峰嫊銇椼伨銇欍��
+\n
+\n娉�: 銇撱倢銈夈伄銉曘偂銈ゃ儷銇屽墠鍥炪伀銈ゃ兂銈广儓銉笺儷銇椼仧銈傘伄銇ㄥ畬鍏ㄣ伀鍚屻仒鍫村悎銆�.res 銇� .gps 銈掋偆銉炽偣銉堛兗銉仚銈嬪繀瑕併伅銇傘倞銇俱仜銈撱��
+\n
+\n銇旇嚜韬伄璨换銇ц銇c仸銇忋仩銇曘亜!</string>
+    <string name="fw_upgrade_notice_amazfitbip3pro">Amazfit Bip 3 Pro 銇� %s 銇儠銈°兗銉犮偊銈с偄銈掋偆銉炽偣銉堛兗銉仐銈堛亞銇ㄣ仐銇︺亜銇俱仚銆�
+\n
+\n.fw 銉曘偂銈ゃ儷銈掋偆銉炽偣銉堛兗銉仐銆併仢銇緦 .res 銉曘偂銈ゃ儷銈掋偆銉炽偣銉堛兗銉仐銇︺亸銇犮仌銇勩�傘偊銈┿儍銉併伅銆�.fw 銉曘偂銈ゃ儷銈掋偆銉炽偣銉堛兗銉仐銇熷緦銇啀璧峰嫊銇椼伨銇欍��
+\n
+\n娉�: 銇撱倢銈夈伄銉曘偂銈ゃ儷銇屽墠鍥炪伀銈ゃ兂銈广儓銉笺儷銇椼仧銈傘伄銇ㄥ畬鍏ㄣ伀鍚屻仒鍫村悎銆�.res 銉曘偂銈ゃ儷銈掋偆銉炽偣銉堛兗銉仚銈嬪繀瑕併伅銇傘倞銇俱仜銈撱��
+\n
+\n銇旇嚜韬伄璨换銇ц銇c仸銇忋仩銇曘亜锛�</string>
+    <string name="open_fw_installer_info_text">銉曘偂銉笺儬銈︺偋銈�/銈︺偐銉冦儊銉曘偋銈ゃ偣/銈€儣銉�/銉曘偂銈ゃ儷銈ゃ兂銈广儓銉笺儵銈掍娇銇嗐仺銆併儠銈°兗銉犮偊銈с偄銆併偊銈┿儍銉併儠銈с偆銈广�併偄銉椼儶銆丟PS銆佹儏鍫便�併儠銈┿兂銉堢瓑銇蹇溿仐銇熴儠銈°偆銉倰銈€儍銉椼儹銉笺儔/銈ゃ兂銈广儓銉笺儷銇с亶銇俱仚銆傝┏绱般仾鎯呭牨銇疻iki銇х⒑瑾嶃仹銇嶃伨銇欙細https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Firmware-Update</string>
+    <string name="open_fw_installer_connect_maximum_one_device">銉曘偂銈ゃ儷銈掗�佷俊銇椼仧銇勩儑銉愩偆銈广倰1銇ゃ仩銇戞帴缍氥仐銇︺亸銇犮仌銇勩��</string>
+    <string name="fw_upgrade_notice_amazfit_trex">Amazfit T-Rex 銇� %s 銉曘偂銉笺儬銈︺偋銈€倰銈ゃ兂銈广儓銉笺儷銇椼倛銇嗐仺銇椼仸銇勩伨銇欍��
+\n聽
+\n.fw 銉曘偂銈ゃ儷銈掋偆銉炽偣銉堛兗銉仐銆佹銇�.res 銉曘偂銈ゃ儷銆佹渶寰屻伀.gps銉曘偂銈ゃ儷銈掋偆銉炽偣銉堛兗銉仐銇︺亸銇犮仌銇勩�� .fw 銉曘偂銈ゃ儷銈掋偆銉炽偣銉堛兗銉仐銇熷緦銆併偊銈┿儍銉併亴鍐嶈捣鍕曘仐銇俱仚銆�
+\n聽
+\n娉�: .res 銉曘偂銈ゃ儷銇� .gps 銉曘偂銈ゃ儷銇屽墠鍥炪伀銈ゃ兂銈广儓銉笺儷銇椼仧銈傘伄銇ㄥ悓銇樺牬鍚堛�佹柊銇熴伀銇撱倢銈夈伄銉曘偂銈ゃ儷銈掋偆銉炽偣銉堛兗銉仚銈嬪繀瑕併伅銇傘倞銇俱仜銈撱��
+\n聽
+\n鑷繁璨换銇笅銇ц銇c仸銇忋仩銇曘亜锛�</string>
+    <string name="fw_upgrade_notice_amazfitvergel">Amazfit Verge Lite 銇� %s 銉曘偂銉笺儬銈︺偋銈€倰銈ゃ兂銈广儓銉笺儷銇椼倛銇嗐仺銇椼仸銇勩伨銇欍��
+\n聽
+\n.fw 銉曘偂銈ゃ儷銈掋偆銉炽偣銉堛兗銉仐銆佹銇�.res 銉曘偂銈ゃ儷銆佹渶寰屻伀.gps銉曘偂銈ゃ儷銈掋偆銉炽偣銉堛兗銉仐銇︺亸銇犮仌銇勩�� .fw 銉曘偂銈ゃ儷銈掋偆銉炽偣銉堛兗銉仐銇熷緦銆併儛銉炽儔銇屽啀璧峰嫊銇椼伨銇欍��
+\n聽
+\n娉�: .res 銉曘偂銈ゃ儷銇� .gps 銉曘偂銈ゃ儷銇屽墠鍥炪伀銈ゃ兂銈广儓銉笺儷銇椼仧銈傘伄銇ㄥ悓銇樺牬鍚堛�佹柊銇熴伀銇撱倢銈夈伄銉曘偂銈ゃ儷銈掋偆銉炽偣銉堛兗銉仚銈嬪繀瑕併伅銇傘倞銇俱仜銈撱��
+\n聽
+\n鑷繁璨换銇笅銇ц銇c仸銇忋仩銇曘亜锛�</string>
+    <string name="fw_upgrade_notice_amazfit_gts4">Amazfit GTS 4銇� %s 銉曘偂銉笺儬銈︺偋銈€倰銈ゃ兂銈广儓銉笺儷銇椼倛銇嗐仺銇椼仸銇勩伨銇欍��
+\n
+\n.zip銉曘偂銈ゃ儷銈掋偆銉炽偣銉堛兗銉仚銈嬨仺銆併儛銉炽儔銇屽啀璧峰嫊銇椼伨銇欍��
+\n
+\n鑷繁璨换銇笅銇ц銇c仸銇忋仩銇曘亜锛�</string>
+    <string name="fw_upgrade_notice_amazfitx">Amazfit X 銇� %s 銉曘偂銉笺儬銈︺偋銈€倰銈ゃ兂銈广儓銉笺儷銇椼倛銇嗐仺銇椼仸銇勩伨銇欍��
+\n
+\n.fw 銉曘偂銈ゃ儷銈掋偆銉炽偣銉堛兗銉仐銇︺亱銈夈��.res 銉曘偂銈ゃ儷銈掋偆銉炽偣銉堛兗銉仐銇︺亸銇犮仌銇勩�� .fw 銉曘偂銈ゃ儷銈掋偆銉炽偣銉堛兗銉仚銈嬨仺銆併儛銉炽儔銇屽啀璧峰嫊銇椼伨銇欍��
+\n
+\n娉�: .res銉曘偂銈ゃ儷銇屼互鍓嶃伀銈ゃ兂銈广儓銉笺儷銇椼仧銈傘伄銇ㄣ伨銇c仧銇忓悓銇樺牬鍚堛�佹柊銇熴伀.res 銈掋偆銉炽偣銉堛兗銉仚銈嬪繀瑕併伅銇傘倞銇俱仜銈撱��
+\n
+\n鑷繁璨换銇笅銇ц銇c仸銇忋仩銇曘亜锛�</string>
+    <string name="pref_pair_bluetooth_calls_help_3">3. 浠ヤ笅銇瓸luetooth 閫氳┍瑷畾銈掓湁鍔广伀銇椼伨銇欍��</string>
+    <string name="bluetooth_calls">Bluetooth 閫氳┍</string>
+    <string name="bluetooth_calls_pairing">Bluetooth 閫氳┍銇儦銈€儶銉炽偘</string>
+    <string name="bluetooth_calls_settings">Bluetooth 閫氳┍銇ō瀹�</string>
+    <string name="pref_display_caller_summary">鐫�淇℃檪銇浕瑭辩暘鍙枫亱鍚嶅墠銈掕〃绀恒仐銇俱仚</string>
+    <string name="pref_summary_custom_deviceicon">鎺ョ稓鏅傘伀 Gadgetbridge 銇偄銈ゃ偝銉炽伄浠c倧銈娿伀 銉囥儛銈ゃ偣鍥烘湁銇� Android 閫氱煡銈€偆銈炽兂銈掕〃绀恒仐銇俱仚</string>
+    <string name="pref_title_preview_message_in_title">銈裤偆銉堛儷銇儭銉冦偦銉笺偢銇儣銉儞銉ャ兗銈掕〃绀恒仚銈�</string>
+    <string name="pref_summary_notification_cache_while_disconnected">鎺ョ稓銇岄�斿垏銈屻仧銉囥儛銈ゃ偣銇屽啀鎺ョ稓銇曘倢銇熴仺銇嶃伀瑕嬮�冦仐銇熼�氱煡銈掗�佷俊銇椼伨銇�</string>
+    <string name="fw_upgrade_notice_amazfitbip3">Amazfit Bip 3 銇� %s 銇儠銈°兗銉犮偊銈с偄銈掋偆銉炽偣銉堛兗銉仐銈堛亞銇ㄣ仐銇︺亜銇俱仚銆�
+\n
+\n.fw 銉曘偂銈ゃ儷銈掋偆銉炽偣銉堛兗銉仐銆併仢銇緦 .res 銉曘偂銈ゃ儷銈掋偆銉炽偣銉堛兗銉仐銇︺亸銇犮仌銇勩�傘偊銈┿儍銉併伅銆�.fw 銉曘偂銈ゃ儷銈掋偆銉炽偣銉堛兗銉仐銇熷緦銇啀璧峰嫊銇椼伨銇欍��
+\n
+\n娉�: 銇撱倢銈夈伄銉曘偂銈ゃ儷銇屽墠鍥炪伀銈ゃ兂銈广儓銉笺儷銇椼仧銈傘伄銇ㄥ畬鍏ㄣ伀鍚屻仒鍫村悎銆�.res 銉曘偂銈ゃ儷銈掋偆銉炽偣銉堛兗銉仚銈嬪繀瑕併伅銇傘倞銇俱仜銈撱��
+\n
+\n銇旇嚜韬伄璨换銇ц銇c仸銇忋仩銇曘亜锛�</string>
+    <string name="fw_upgrade_notice_amazfitgtr">Amazfit GTR 銇� %s 銉曘偂銉笺儬銈︺偋銈€倰銈ゃ兂銈广儓銉笺儷銇椼倛銇嗐仺銇椼仸銇勩伨銇欍��
+\n聽
+\n.fw 銉曘偂銈ゃ儷銈掋偆銉炽偣銉堛兗銉仐銆佹銇�.res 銉曘偂銈ゃ儷銆佹渶寰屻伀.gps銉曘偂銈ゃ儷銈掋偆銉炽偣銉堛兗銉仐銇︺亸銇犮仌銇勩�� .fw 銉曘偂銈ゃ儷銈掋偆銉炽偣銉堛兗銉仐銇熷緦銆併偊銈┿儍銉併亴鍐嶈捣鍕曘仐銇俱仚銆�
+\n聽
+\n娉�: .res 銉曘偂銈ゃ儷銇� .gps 銉曘偂銈ゃ儷銇屽墠鍥炪伀銈ゃ兂銈广儓銉笺儷銇椼仧銈傘伄銇ㄥ悓銇樺牬鍚堛�佹柊銇熴伀銇撱倢銈夈伄銉曘偂銈ゃ儷銈掋偆銉炽偣銉堛兗銉仚銈嬪繀瑕併伅銇傘倞銇俱仜銈撱��
+\n聽
+\n鑷繁璨换銇笅銇ц銇c仸銇忋仩銇曘亜锛�</string>
+    <string name="open_fw_installer_warning_title">娉ㄦ剰</string>
+    <string name="open_fw_installer_warning_text">銇撱伄姗熻兘銇儑銉愩偆銈广倰浣跨敤涓嶈兘銇仚銈嬪彲鑳芥�с亴銇傘倞銇俱仚銆傞枊鐧鸿�呫伅銇濄伄銈堛亞銇祵楱撱伅銇椼仸銇勩伨銇涖倱銇屻�佸疅琛屻仚銈嬮殯銇嚜宸辫铂浠汇伄涓嬨仹琛屻仯銇︺亸銇犮仌銇勩��</string>
+    <string name="open_fw_installer_pick_file">銉曘偂銈ゃ儷銇伕鎶�</string>
+    <string name="open_fw_installer_info_text_title">銉曘偂銈ゃ儷 銈ゃ兂銈广儓銉笺儵</string>
+    <string name="open_fw_installer_connect_minimum_one_device">銉曘偂銈ゃ儷銈掗�佷俊銇椼仧銇勩儑銉愩偆銈广倰灏戙仾銇忋仺銈�1銇ゆ帴缍氥仐銇︺亸銇犮仌銇勩��</string>
+    <string name="application_name_banglejs_main">Bangle.js Gadgetbridge</string>
+    <string name="title_activity_controlcenter_banglejs_main">Bangle.js Gadgetbridge</string>
+    <string name="about_activity_title_banglejs_main">Bangle.js Gadgetbridge 銇仱銇勩仸</string>
+    <string name="about_description_banglejs_main">Gadgetbridge 銉椼儹銈搞偋銈儓涓娿伀妲嬬瘔銇曘倢銇� Bangle.js 銇� Android 銈炽兂銉戙儖銈兂 銈€儣銉�傘偆銉炽偪銉笺儘銉冦儓 銈€偗銈汇偣銇岃拷鍔犮仌銈屻仸銇勩伨銇欍��
+\n
+\nGoogle Play 銈广儓銈€伄銉濄儶銈枫兗銇倛銈娿�併偄銉椼儶鑷綋銇瘎浠樸儶銉炽偗銈掍娇鐢ㄣ仚銈嬨亾銇ㄣ伅銇с亶銇俱仜銈撱亴銆併亾銇偄銉椼儶銈掓皸銇叆銇c仸銇勩仧銇犮亼銇俱仐銇熴倝銆佷互涓嬨伄 Gadgetbridge 銉涖兗銉犮儦銉笺偢銇嬨倝瀵勪粯銈掋仈妞滆◣銇忋仩銇曘亜銆�</string>
+    <string name="busy_task_fetch_spo2_data">SpO2 銇儑銉笺偪銈掑彇寰椾腑</string>
+    <string name="reminder_every_year">姣庡勾</string>
+    <string name="pref_screen_notification_profile_todo_list">To-Do 銉偣銉�</string>
+    <string name="pref_screen_notification_profile_schedule">銈广偙銈搞儱銉笺儷</string>
+    <string name="zetime_prefs_inactivity_repetitions">绻般倞杩斻仐</string>
+    <string name="zetime_signaling_beep">銉撱兗銉楅煶銈掗炒銈夈仐缍氥亼銈�</string>
+    <string name="zetime_signaling_vibrate">鎸嫊銇曘仜缍氥亼銈�</string>
+    <string name="zetime_signaling_vibrate_beep">鎸嫊銇ㄣ儞銉笺儣闊炽倰槌淬倝銇楃稓銇戙倠</string>
+    <string name="zetime_signaling_vibrate_twice">2鍥炴尟鍕曘仌銇涖倠</string>
+    <string name="zetime_signaling_vibrate_once">1鍥炪仩銇戞尟鍕曘仌銇涖倠</string>
+    <string name="prefs_hr_alarm_activity">銈广儩銉笺儎 銈€偗銉嗐偅銉撱儐銈f檪銇績鎷嶆暟銈€儵銉笺儬</string>
+    <string name="pref_sleep_mode_sleep_screen_title">銈广儶銉笺儣鐢婚潰</string>
+    <string name="pref_header_auto_fetch">鑷嫊鍙栧緱</string>
+    <string name="heartrate_bpm_135">135 bpm</string>
+    <string name="reminder_every_month">姣庢湀</string>
+    <string name="pref_summary_device_internet_access">銇撱伄銉囥儛銈ゃ偣涓娿伄銈€儣銉伀銈ゃ兂銈裤兗銉嶃儍銉堟帴缍氥倰瑷卞彲銇椼伨銇�</string>
+    <string name="pref_summary_sync_calendar">鍒囨柇銇曘倢銇︺亜銈嬨仺銇嶃伀銈傘偒銉兂銉�銉笺伄銈€儵銉笺儓銈掓湁鍔广伀銇椼伨銇�</string>
+    <string name="pref_title_vibration_strength">鎸嫊銇挤銇�</string>
+    <string name="pref_summary_relax_firmware_checks">銉曘偂銉笺儬銈︺偋銈€伄纰鸿獚銈掔珐鍜屻仐銇俱仚</string>
+    <string name="pref_agps_header">瑁滃姪 GPS</string>
+    <string name="pref_workout_detection_sensitivity">鎰熷害</string>
+    <string name="pref_cache_weather">澶╂皸鎯呭牨銈掋偔銉c儍銈枫儱銇欍倠</string>
+    <string name="prefs_notifications_and_calls_shake_reject">鎵嬮銈掓尟銇c仸鐫�淇°倰鐒¤/鎷掑惁銇欍倠</string>
+    <string name="fossil_hr_auth_failed">瑾嶈銇け鏁椼仐銇俱仐銇熴�傛鑳姐亴鍒堕檺銇曘倢銇俱仚</string>
+    <string name="qhybrid_calibration_1_step">1 銈广儐銉冦儣</string>
+    <string name="pref_force_connection_type_bt_classic">Bluetooth Classic</string>
+    <string name="pref_screen_vibration_patterns_title">鎸嫊銇儜銈裤兗銉�</string>
+    <string name="pref_screen_vibration_patterns_summary">銇曘伨銇栥伨銇�氱煡銇尟鍕曘儜銈裤兗銉炽倰瑷畾銇欍倠</string>
+    <string name="heartrate_bpm_145">145 bpm</string>
+    <string name="heartrate_bpm_185">185 bpm</string>
+    <string name="pref_title_device_intents">銈ゃ兂銉嗐兂銉堛倰瑷卞彲銇欍倠</string>
+    <string name="reminder_time_once">%1$s, 1搴︺仩銇�</string>
+    <string name="pref_title_casio_alert_calendar">銈儸銉炽儉銉奸�氱煡銇偄銉┿兗銉�</string>
+    <string name="pref_title_casio_alert_call">鐫�淇°伄銈€儵銉笺儓</string>
+    <string name="pref_summary_casio_alert_call">鐫�淇℃檪銇偄銉┿兗銉�(鎸嫊/銉撱兗銉楅煶)銈掗炒銈夈仐銇俱仚</string>
+    <string name="pref_title_casio_alert_email">銉°兗銉�氱煡銇偄銉┿兗銉�</string>
+    <string name="pref_summary_casio_alert_sms">SMS閫氱煡鏅傘伀銈€儵銉笺儓锛堟尟鍕�/銉撱兗銉楅煶锛夈倰槌淬倝銇椼伨銇�</string>
+    <string name="pref_title_casio_alert_other">銇濄伄浠栥伄閫氱煡銇偄銉┿兗銉�</string>
+    <string name="pref_summary_casio_alert_other">浠栥偒銉嗐偞銉伄閫氱煡鏅傘伀銈€儵銉笺儓(鎸嫊/銉撱兗銉楅煶)銈掗炒銈夈仐銇俱仚</string>
+    <string name="pref_summary_screen_on_on_notifications">閫氱煡銇屻亶銇熴仺銇嶃伀銉愩兂銉夈伄鐢婚潰銈� ON 銇仐銇俱仚</string>
+    <string name="pref_summary_send_app_notifications">銈€儣銉伄閫氱煡銈掋儑銉愩偆銈广伀閫佷俊銇椼伨銇�</string>
+    <string name="pref_title_allow_high_mtu">楂� MTU 銈掕ū鍙仚銈�</string>
+    <string name="pref_title_overwrite_settings_on_connection">鎺ョ稓鏅傘伀瑷畾銈掍笂鏇搞亶銇欍倠</string>
+    <string name="pref_summary_overwrite_settings_on_connection">銉愩兂銉夈伀鎺ョ稓銇欍倠銇ㄣ亶銇�併儛銉炽儔涓娿伄銇欍伖銇︺伄瑷畾銈掍笂鏇搞亶銇椼伨銇欍��</string>
+    <string name="pref_title_device_internet_access">銈ゃ兂銈裤兗銉嶃儍銉堟帴缍氥倰瑷卞彲銇欍倠</string>
+    <string name="pref_title_relax_firmware_checks">銇婁娇銇勩伄銉囥儛銈ゃ偣鍚戙亼銇с仾銇勩儠銈°兗銉犮偊銈с偄銈掓浉銇嶈炯銇裤仧銇勫牬鍚堛伀鏈夊姽鍖栵紙鑷繁璨换銇э級</string>
+    <string name="pref_summary_huami_force_new_protocol">銉曘偂銉笺儬銈︺偋銈㈡洿鏂板緦銇儑銉愩偆銈广伀鎺ョ稓銇с亶銇亸銇仯銇熷牬鍚堛�併亾銈屻倰鏈夊姽銇仐銇︺亸銇犮仌銇�</string>
+    <string name="title_activity_LenovoWatch_calibration">Watch X Plus 銇鏁�</string>
+    <string name="prefs_notifications_and_calls_continious_ring">鐫�淇′腑銇�氱煡</string>
+    <string name="pref_notifications_and_calls_enable_misscall">涓嶅湪鐫�淇°倰閫氱煡銇欍倠</string>
+    <string name="pref_title_notifications_and_calls_repeat_on_misscall">X 鍒嗛枔绻般倞杩斻仚</string>
+    <string name="pref_summary_notifications_and_calls_title_reject">OFF - 鐒¤銆丱N - 鎷掑惁</string>
+    <string name="prefs_notifications_and_calls_reject">銉溿偪銉炽仹鐫�淇$潃淇°倰鐒¤/鎷掑惁銇欍倠</string>
+    <string name="pref_summary_notifications_and_calls_title_shake_reject">銈︺偐銉冦儊銇儨銈裤兂鍕曚綔銈掕瑁姐仚銈�</string>
+    <string name="pref_header_sensors_calibration">銈汇兂銈点兗銇姝�</string>
+    <string name="pref_sensors_bp_calibration_low">琛�鍦� (涓�)</string>
+    <string name="pref_sensors_bp_calibration_high">琛�鍦� (涓�)</string>
+    <string name="power_mode_title">銈︺偐銉冦儊銇浕婧愩儮銉笺儔</string>
+    <string name="femometer_measurement_mode_title">娓畾銉€兗銉�</string>
+    <string name="zetime_analog_mode_handsandsteps">閲濄仺姝╂暟</string>
+    <string name="zetime_date_format">鏃ヤ粯銇舰寮�</string>
+    <string name="prefs_hr_alarm_high">涓婇檺</string>
+    <string name="pref_gps_header">GPS</string>
+    <string name="pref_gps_mode_preset">GPS 銉€兗銉�</string>
+    <string name="pref_text_to_speech">銉嗐偔銈广儓銇銇夸笂銇�</string>
+    <string name="pref_disable_new_ble_scanning">鏂般仐銇� BLE 銈广偔銉c兂銈掔劇鍔广伀銇欍倠</string>
+    <string name="unknown">涓嶆槑</string>
+    <string name="discovery_bluetooth_scan">Bluetooth 銈广偔銉c兂锛�</string>
+    <string name="busy_task_fetch_sports_details">銈广儩銉笺儎銇┏绱般倰鍙栧緱涓�</string>
+    <string name="busy_task_fetch_sports_summaries">銈广儩銉笺儎銇瑕併倰鍙栧緱涓�</string>
+    <string name="busy_task_fetch_sports_details_interrupted">銈广儩銉笺儎銇┏绱般伄鍙栧緱銇腑鏂仌銈屻伨銇椼仧</string>
+    <string name="busy_task_fetch_debug_logs">銉囥儛銉冦偘銉偘銈掑彇寰椾腑</string>
+    <string name="busy_task_fetch_stress_data">銈广儓銉偣銉囥兗銈裤倰鍙栧緱涓�</string>
+    <string name="busy_task_fetch_sleep_respiratory_rate_data">鐫$湢鏅傚懠鍚告暟銇儑銉笺偪銈掑彇寰椾腑</string>
+    <string name="busy_task_fetch_statistics">绲辫▓銇彇寰椾腑</string>
+    <string name="prefs_wearmode">瑁呯潃銉€兗銉�</string>
+    <string name="heartrate_bpm_50">50 bpm</string>
+    <string name="heartrate_bpm_120">120 bpm</string>
+    <string name="heartrate_bpm_125">125 bpm</string>
+    <string name="heartrate_bpm_140">140 bpm</string>
+    <string name="heartrate_bpm_165">165 bpm</string>
+    <string name="heartrate_bpm_175">175 bpm</string>
+    <string name="heartrate_bpm_195">195 bpm</string>
+    <string name="heartrate_bpm_205">205 bpm</string>
+    <string name="reminder_no_free_slots_title">绌恒亶銈广儹銉冦儓銇仐</string>
+    <string name="reminder_every_week">姣庨��</string>
+    <string name="reminder_delete_confirm_title">銉優銈ゃ兂銉�銉笺伄鍓婇櫎</string>
+    <string name="contact_delete_confirm_title">閫g怠鍏堛伄鍓婇櫎</string>
+    <string name="world_clock_no_free_slots_description">銇撱伄銉囥儛銈ゃ偣銇伅涓栫晫鏅傝▓鐢ㄣ伄绌恒亶銈广儹銉冦儓銇屻亗銈娿伨銇涖倱锛堝悎瑷堛偣銉儍銉堬細%1$s锛�</string>
+    <string name="function_enabled">鏈夊姽</string>
+    <string name="world_clock_timezone">銈裤偆銉犮偩銉笺兂</string>
+    <string name="world_clock_code">銈炽兗銉�</string>
+    <string name="title_activity_world_clock_details">涓栫晫鏅傝▓銇┏绱�</string>
+    <string name="pref_title_developer_settings">闁嬬櫤鑰呭悜銇戙伄瑷畾</string>
+    <string name="fossil_hr_edit_action_delete">鍓婇櫎</string>
+    <string name="qhybrid_calibration_counterclockwise">鍙嶆檪瑷堝洖銈�</string>
+    <string name="connection_over_ble">Bluetooth LE 銇倛銈嬫帴缍�</string>
+    <string name="pref_header_notifications_and_calls_callhandling">閫氳┍銇蹇�</string>
+    <string name="reminder_delete_confirm_description">銉優銈ゃ兂銉�銉笺倰鍓婇櫎銇椼伨銇欍亱锛�</string>
+    <string name="contact_delete_confirm_description">\'%1$s\' 銈掑墛闄ゃ仐銇俱仚銇嬶紵</string>
+    <string name="world_clock_no_free_slots_title">绌恒亶銈广儹銉冦儓銇仐</string>
+    <string name="title_activity_contact_details">閫g怠鍏堛伄瑭崇窗</string>
+    <string name="qhybrid_calibration_clockwise">鏅傝▓鍥炪倞</string>
+    <string name="pref_title_casio_alert_sms">SMS閫氱煡銇偄銉┿兗銉�</string>
+    <string name="pref_summary_casio_alert_email">銉°兗銉�氱煡鏅傘伀銈€儵銉笺儓锛堟尟鍕�/銉撱兗銉楅煶锛夈倰槌淬倝銇椼伨銇�</string>
+    <string name="pref_title_device_spec_settings_show_raw_graph">銈€偗銉嗐偅銉撱儐銈c偘銉┿儠銇敓銉囥兗銈裤倰琛ㄧず銇欍倠</string>
+    <string name="power_mode_watch">鏅傝▓銇伩</string>
+    <string name="power_mode_normal">銉庛兗銉炪儷</string>
+    <string name="femometer_measurement_mode_normal">閫氬父銉€兗銉夛紙60绉�-90绉掞級</string>
+    <string name="femometer_measurement_mode_precise">绮惧瘑銉€兗銉夛紙3鍒嗭級</string>
+    <string name="reminder_message">銉°儍銈汇兗銈�</string>
+    <string name="zetime_title_screentime">鐢婚潰銈兂銇檪闁� (绉�)</string>
+    <string name="zetime_analog_mode_hands">閲濄伄銇�</string>
+    <string name="zetime_activity_tracking_summary">銈€偗銉嗐偅銉撱儐銈� 銉堛儵銉冦偔銉炽偘銈� ON 銇仚銈嬨仺銆佹鏁般仾銇┿亴銈偊銉炽儓銇曘倢銇俱仚銆�</string>
+    <string name="pref_force_connection_type_ble">Bluetooth LE</string>
+    <string name="fossil_hr_new_action">鏂般仐銇勩偄銈偡銉с兂</string>
+    <string name="pref_summary_connected_advertisement">鎺ョ稓涓仹銈� Bluetooth 銇с儑銉愩偆銈广倰妞滃嚭銇с亶銈嬨倛銇嗐伀銇椼伨銇�</string>
+    <string name="pref_title_connected_advertisement">鎺ョ稓涓伀妞滃嚭鍙兘銇仚銈�</string>
+    <string name="pref_title_huami_force_new_protocol">鏂般仐銇勮獚瑷笺儣銉儓銈炽儷</string>
+    <string name="pref_summary_notifications_and_calls_enable_misscall">姣庡垎绻般倞杩斻仚</string>
+    <string name="zetime_handmove_display_summary">鎵嬮銈掋伨銈忋仚銇ㄧ敾闈€伄琛ㄧず/闈炶〃绀恒亴銇с亶銇俱仚銆�</string>
+    <string name="controlcenter_start_configure_reminders">銉優銈ゃ兂銉�銉笺倰瑷畾</string>
+    <string name="pref_world_clocks_title">涓栫晫鏅傝▓</string>
+    <string name="pref_title_screen_on_on_notifications">閫氱煡鏅傘伀鐢婚潰銈� ON 銇仚銈�</string>
+    <string name="pref_workout_detection_title">銉兗銈偄銈︺儓銇鍑�</string>
+    <string name="pref_workout_keep_screen_on_summary">銉兗銈偄銈︺儓涓伅鐢婚潰銈掋偑銉炽伄銇俱伨銇仐銆併儶銈€儷銈裤偆銉犮伄銉兗銈偄銈︺儓 銉囥兗銈裤倰琛ㄧず銇楃稓銇戙倠銇熴倎銇槑銈嬨仌銇岃鏁淬仌銈屻伨銇�</string>
+    <string name="spo2_perc_85">85%</string>
+    <string name="spo2_perc_90">90%</string>
+    <string name="spo2_off">Off</string>
+    <string name="clap_hands_to_wakeup_device">鎵嬨倰鍙┿亜銇︾敾闈€倰 ON 銇仚銈�</string>
+    <string name="pref_title_authkey">瑾嶈銈兗</string>
+    <string name="pixoo_power_saving_summary">銉炪偆銈亴鐒¢煶鐘舵厠銈掍竴瀹氭湡闁撴鐭ャ仚銈嬨仺鐢婚潰銇屻偑銉曘伀銇倞銇俱仚</string>
+    <string name="clap_hands_to_wakeup_device_summary">鍐嶅害鎵嬨倰鍙┿亸銇ㄧ敾闈€倰銈儠銇仐銇俱仚</string>
+    <string name="pref_summary_authkey">鎺ョ稓銇椼仧銇勩仚銇广仸銇� Android 銉囥儛銈ゃ偣涓娿仹瑾嶈銈兗銈掑叡閫氥伄銈兗銇鏇淬仐銇︺亸銇犮仌銇勩�備互鍓嶃伄銉囥儠銈┿儷銉堛偔銉笺伅 0123456789@ABCDE 銇с仚</string>
+    <string name="pref_explanation_authkey">涓�閮ㄣ伄銉囥儛銈ゃ偣銇с伅鍒濇湡瑷畾銇壒鍒ャ仾銉氥偄銉兂銈� 銈兗銇屽繀瑕併仹銇欍�傘亾銇撱倰銈裤儍銉椼仚銈嬨仺瑭崇窗銈扺iki銇х⒑瑾嶃仹銇嶃伨銇欍��</string>
+    <string name="prefs_hr_alarm_low">涓嬮檺</string>
+    <string name="pref_gps_band">GPS 銉愩兂銉�</string>
+    <string name="pref_gps_combination">GPS 銇祫銇垮悎銈忋仜</string>
+    <string name="pref_gps_satellite_search">琛涙槦銇绱�</string>
+    <string name="pref_crown_vibration">銉儱銉笺偤銇尟鍕�</string>
+    <string name="pref_workout_detection_summary">銉兗銈偄銈︺儓銈掕嚜鍕曠殑銇鍑恒仐銇俱仚</string>
+    <string name="pref_workout_send_gps_title">銉兗銈偄銈︺儓涓伀浣嶇疆鎯呭牨銈掗�佷俊銇欍倠</string>
+    <string name="pref_workout_send_gps_summary">銉兗銈偄銈︺儓涓伀鐝惧湪銇綅缃儏鍫便倰銉愩兂銉夈伀閫佷俊銇椼伨銇�</string>
+    <string name="pref_workout_keep_screen_on_title">銉兗銈偄銈︺儓涓伅鐢婚潰銈� ON 銇伨銇俱伀銇欍倠</string>
+    <string name="pref_workout_detection_categories_summary">鑷嫊妞滃嚭銇曘倢銈嬨儻銉笺偗銈€偊銉堛伄绋</string>
+    <string name="pref_workout_detection_alert_title">銈€儵銉笺儓</string>
+    <string name="pref_workout_detection_categories_title">銉兗銈偄銈︺儓銇ó椤�</string>
+    <string name="pref_workout_detection_alert_summary">銉兗銈偄銈︺儓銇屾鍑恒仌銈屻仧銇ㄣ亶銇�氱煡銇椼伨銇�</string>
+    <string name="pref_workout_detection_enabled_summary">銇撱伄銉兗銈偄銈︺儓銇嚜鍕曟鍑恒倰鏈夊姽銇仐銇俱仚</string>
+    <string name="pref_workout_detection_ask_first">鏈�鍒濄伀纰鸿獚銇欍倠</string>
+    <string name="pref_workout_detection_ask_first_summary">銇撱伄銉兗銈偄銈︺儓銈掓鍑恒仐銇熴仺銇嶃�併偊銈┿儍銉佷笂銇ф壙瑾嶃倰姹傘倎銇俱仚</string>
+    <string name="pref_workout_detection_enabled">妞滃嚭銈掓湁鍔广伀銇欍倠</string>
+    <string name="pref_summary_disable_new_ble_scanning">妞滃嚭涓伀銉囥儛銈ゃ偣銇岃銇ゃ亱銈夈仾銇勫牬鍚堛�併亾銇偑銉椼偡銉с兂銈掋儊銈с儍銈仐銇︺亸銇犮仌銇�</string>
+    <string name="discovery_bluetooth_le_scan">Bluetooth LE 銈广偔銉c兂锛�</string>
+    <string name="preferences_miband_1_2_warning">娉ㄦ剰锛氥亾銈屻倝銇ō瀹氥伅Mi Band 1銇∕i Band 2銇伄銇块仼鐢ㄣ仌銈屻伨銇欍��</string>
+    <string name="buttons_on_left">宸﹀伌銇儨銈裤兂</string>
+    <string name="buttons_on_right">鍙冲伌銇儨銈裤兂</string>
+    <string name="discovery_need_to_enter_authkey">銇撱伄銉囥儛銈ゃ偣銇獚瑷笺偔銉笺亴蹇呰銇с仚銆傞暦鎶笺仐銇椼仸鍏ュ姏銇椼伨銇欍�傝┏绱般倰鐭ャ倠銇伅Wiki銈掕銈撱仹銇忋仩銇曘亜銆�</string>
+    <string name="discovery_entered_invalid_authkey">鍏ュ姏銇椼仧瑾嶈銈兗銇劇鍔广仹銇欙紒 闀锋娂銇椼仐銇︾法闆嗐仐銇俱仚銆�</string>
+    <string name="busy_task_fetch_temperature">浣撴俯銇儑銉笺偪銈掑彇寰椾腑</string>
+    <string name="vibration_profile_default">銉囥儠銈┿儷銉�</string>
+    <string name="heartrate_bpm_40">40 bpm</string>
+    <string name="heartrate_bpm_45">45 bpm</string>
+    <string name="heartrate_bpm_105">105 bpm</string>
+    <string name="heartrate_bpm_110">110 bpm</string>
+    <string name="heartrate_bpm_115">115 bpm</string>
+    <string name="heartrate_bpm_130">130 bpm</string>
+    <string name="heartrate_bpm_100">100 bpm</string>
+    <string name="heartrate_bpm_150">150 bpm</string>
+    <string name="pref_world_clocks_summary">浠栥伄銈裤偆銉犮偩銉笺兂銇檪瑷堛倰瑷畾銇欍倠</string>
+    <string name="pref_contacts_title">閫g怠鍏�</string>
+    <string name="reminder_time">鏅傚埢</string>
+    <string name="title_activity_set_contacts">閫g怠鍏堛伄瑷畾</string>
+    <string name="title_activity_set_reminders">銉優銈ゃ兂銉�銉笺伄瑷畾</string>
+    <string name="pref_contacts_summary">銈︺偐銉冦儊銇�g怠鍏堛倰瑷畾銇欍倠</string>
+    <string name="reminder_date">鏃ヤ粯</string>
+    <string name="reminder_time_every_week">%1$s, 姣庨��</string>
+    <string name="reminder_time_every_year">%1$s, 姣庡勾</string>
+    <string name="reminder_once">1鍥炪仩銇�</string>
+    <string name="reminder_every_day">姣庢棩</string>
+    <string name="reminder_time_every_day">%1$s, 姣庢棩</string>
+    <string name="title_activity_reminder_details">銉優銈ゃ兂銉�銉笺伄瑭崇窗</string>
+    <string name="pref_header_navigation">銉娿儞銈层兗銈枫儳銉�</string>
+    <string name="world_clock_label">銉┿儥銉�</string>
+    <string name="qhybrid_calibration_10_steps">10 銈广儐銉冦儣</string>
+    <string name="qhybrid_calibration_100_steps">100 銈广儐銉冦儣</string>
+    <string name="wearmode_band">銉愩兂銉夛紙銉偣銉堛儛銉炽儔锛�</string>
+    <string name="fossil_hr_synced_activity_data">鍚屾湡娓堛伩銇偄銈儐銈c儞銉嗐偅 銉囥兗銈�</string>
+    <string name="fossil_hr_new_action_cancel">銈儯銉炽偦銉�</string>
+    <string name="pref_summary_physical_buttons">銈︺偐銉冦儊銇墿鐞嗐儨銈裤兂銇鑳姐倰瑷畾銇椼伨銇�</string>
+    <string name="devicetype_qhybrid">Fossil Q Hybrid</string>
+    <string name="fossil_hr_unavailable_unauthed">闈炶獚瑷笺儮銉笺儔銇с伅鍒╃敤銇с亶銇俱仜銈�</string>
+    <string name="fossil_hr_warning_firmware_too_new">銉曘偂銉笺儬銈︺偋銈€亴鏂般仐銇欍亷銈嬨仧銈佷竴閮ㄣ伄姗熻兘銇岀劇鍔瑰寲銇曘倢銇︺亜銇俱仚</string>
+    <string name="pref_title_physical_buttons">鐗╃悊銉溿偪銉�</string>
+    <string name="pref_title_send_app_notifications">閫氱煡銈掗�併倠</string>
+    <string name="heartrate_bpm_155">155 bpm</string>
+    <string name="pref_summary_preview_message_in_title">銉囥儛銈ゃ偣銇ū鍙仌銈屻仸銇勩倠鍫村悎銆併儭銉冦偦銉笺偢銇儣銉儞銉ャ兗銈掗�氱煡銇偪銈ゃ儓銉伀琛ㄧず銇椼伨銇�</string>
+    <string name="pref_summary_casio_alert_calendar">銈儸銉炽儉銉奸�氱煡鏅傘伀銈€儵銉笺儓锛堟尟鍕�/銉撱兗銉楅煶锛夈倰槌淬倝銇椼伨銇�</string>
+    <string name="pref_summary_allow_high_mtu">杌㈤�侀�熷害銈掑悜涓娿仌銇涖伨銇欍亴銆佷竴閮ㄣ伄 Android 銉囥儛銈ゃ偣銇с伅姗熻兘銇椼仾銇勫彲鑳芥�с亴銇傘倞銇俱仚銆�</string>
+    <string name="pref_title_goal_secondary">2鐣洰銇洰妯�</string>
+    <string name="pref_title_notifications_and_calls_repeat_on_call">鐫�淇¢�氱煡銈掔拱銈婅繑銇�</string>
+    <string name="pref_title_device_spec_settings_force_time">鏅傚埢銈掑挤鍒剁殑銇悓鏈熴仚銈�</string>
+    <string name="pref_summary_device_spec_settings_title_force_time">鍐嶆帴缍氥仐銇熴仺銇嶃伀鏅傚埢銈掕嚜鍕曘仹鍚屾湡銇欍倠銈堛亞銇仐銇俱仚銆傘偄銉娿儹銈伴嚌銇岄枔閬曘仯銇熸檪鍒汇倰绀恒仚銇撱仺銇屻亗銈娿伨銇欙紒</string>
+    <string name="pref_title_sensors_altitude">楂樺害瑷堛伄瑁滄</string>
+    <string name="pref_sensors_bp_calibration">琛�鍦ц▓銇姝�</string>
+    <string name="prefs_sensors_button_bp_calibration">瑁滄</string>
+    <string name="prefs_sensors_button_bp_calibration_sum">銇撱亾銈掓娂銇椼仸瑁滄銈掗枊濮�</string>
+    <string name="power_mode_saving">鐪侀浕鍔�</string>
+    <string name="femometer_measurement_mode_quick">銈偆銉冦偗銉€兗銉夛紙30绉掞級</string>
+    <string name="zetime_title_heart_rate_alarm_summary">蹇冩媿鏁般亴闄愬害銈掕秴銇堛倠銇ㄣ偊銈┿儍銉併亴璀﹀憡銇椼伨銇欍��</string>
+    <string name="zetime_activity_tracking">銈€偗銉嗐偅銉撱儐銈� 銉堛儵銉冦偔銉炽偘</string>
+    <string name="title_activity_device_specific_settings">銉囥儛銈ゃ偣鍥烘湁銇ō瀹�</string>
+    <string name="spo2_perc_80">80%</string>
+    <string name="pref_explanation_authkey_new_protocol">銉愩兂銉夈伀\"銈€儣銉倰鏈�鏂扮増銇洿鏂般仐銇︺亸銇犮仌銇刓"銇ㄣ亜銇嗐儭銉冦偦銉笺偢銇岃〃绀恒仌銈屻仧鍫村悎銇�佷笂瑷樸伄\"鏂般仐銇勮獚瑷笺儣銉儓銈炽儷\"銈掔⒑瑾嶃仐銇︿笅銇曘亜銆傘亾銇撱倰銈裤儍銉椼仚銈嬨仺Wiki銇ц┏绱般倰纰鸿獚銇с亶銇俱仚銆�</string>
+    <string name="preferences_miband_1_2_settings" tools:ignore="TypographyFractions">Mi Band 1/2 銇ō瀹�</string>
+    <string name="pref_sleep_mode_title">銈广儶銉笺儣銉€兗銉�</string>
+    <string name="watchface_install_info">娆°伄銈︺偐銉冦儊銉曘偋銈ゃ偣銈掋偆銉炽偣銉堛兗銉仐銈堛亞銇ㄣ仐銇︺亜銇俱仚:
+\n
+\n%1$s
+\n銉愩兗銈搞儳銉� %2$s 浣滆�� %3$s
+\n</string>
+    <string name="wearmode_pebble">Pebble 锛堥澊銇儛銉冦偗銉級</string>
+    <string name="wearmode_necklace">銉嶃儍銈儸銈癸紙銉嶃儍銈偣銉堛儵銉冦儣锛�</string>
+    <string name="pref_cache_weather_summary">澶╂皸鎯呭牨銇偄銉椼儶銇啀璧峰嫊銈掋伨銇熴亜銇с偔銉c儍銈枫儱銇曘倢銇俱仚銆�</string>
+    <string name="busy_task_fetch_pai_data">PAI 銇儑銉笺偪銈掑彇寰椾腑</string>
+    <string name="busy_task_fetch_hr_data">蹇冩媿鏁般伄銉囥兗銈裤倰鍙栧緱涓�</string>
+    <string name="prefs_weardirection">瑁呯潃銇欍倠鍚戙亶</string>
+    <string name="pref_screen_notification_profile_inactivity">鐒℃椿鍕曘伄閫氱煡</string>
+    <string name="pref_screen_notification_profile_event_reminder">銈ゃ儥銉炽儓 銉優銈ゃ兂銉�銉�</string>
+    <string name="pref_screen_notification_profile_find_device">銉囥儛銈ゃ偣銈掕銇ゃ亼銈�</string>
+    <string name="pref_screen_notification_profile_low_power">闆绘睜娈嬮噺銇鍛�</string>
+    <string name="pref_screen_notification_profile_anti_loss">绱涘け闃叉銇鍛�</string>
+    <string name="reminder_repeat">绻般倞杩斻仐</string>
+    <string name="reminder_time_every_month">%1$s, 姣庢湀</string>
+    <string name="reminder_no_free_slots_description">銇撱伄銉囥儛銈ゃ偣銇伅銉優銈ゃ兂銉�銉肩敤銇┖銇嶃偣銉儍銉堛亴銇傘倞銇俱仜銈擄紙鍚堣▓銈广儹銉冦儓锛�%1$s锛�</string>
+    <string name="contact_no_free_slots_description">銇撱伄銉囥儛銈ゃ偣銇伅閫g怠鍏堢敤銇┖銇嶃偣銉儍銉堛亴銇傘倞銇俱仜銈擄紙鍚堣▓銈广儹銉冦儓锛�%1$s锛�</string>
+    <string name="world_clock_delete_confirm_title">\'%1$s\'銇墛闄�</string>
+    <string name="world_clock_delete_confirm_description">涓栫晫鏅傝▓銈掑墛闄ゃ仐銇俱仚銇嬶紵</string>
+    <string name="devicestatus_upload_aborted">銈€儍銉椼儹銉笺儔銇屼腑鏂仌銈屻伨銇椼仧锛�</string>
+    <string name="devicestatus_disconnected">銉囥儛銈ゃ偣銇屽垏鏂仌銈屻伨銇椼仧锛�</string>
+    <string name="prefs_sounds">闊�</string>
+    <string name="seconds_8">8 绉�</string>
+    <string name="chart_no_active_data">銈€偗銉嗐偅銉撱儐銈c亴妞滃嚭銇曘倢銇俱仜銈撱仹銇椼仧銆�</string>
+    <string name="activity_list_summary_distance">璺濋洟</string>
+    <string name="devicestatus_upload_starting">銈€儍銉椼儹銉笺儔闁嬪涓�</string>
+    <string name="firmware_update_progress">銈€儍銉椼儹銉笺儔涓仹銇�
+\n%1d%% at %.2fkbps (average %.2fkbps)
+\nPart %1d of %1d</string>
+    <string name="updatefirmwareoperation_updateproblem_free_space">銉囥儛銈ゃ偣銇崄鍒嗐仾绌恒亶瀹归噺銇屻亗銈娿伨銇涖倱</string>
+    <string name="devicestatus_connected">銉囥儛銈ゃ偣銇帴缍氭笀銇裤仹銇�</string>
+    <string name="gpx_route_upload_failed">GPX 銉兗銉堛伄銈€儍銉椼儹銉笺儔銇け鏁椼仐銇俱仐銇�</string>
+    <string name="prefs_heartrate_alert_high_threshold">楂樺績鎷嶆暟銈€儵銉笺儓銇仐銇嶃亜鍊�</string>
+    <string name="prefs_sounds_summary">銉撱兗銉楅煶銈掗炒銈夈仚銈裤偆銉熴兂銈般倰瑷畾銇椼伨銇�</string>
+    <string name="seconds_11">11 绉�</string>
+    <string name="prefs_heartrate_alert_experimental_title">蹇冩媿鏁般偄銉┿兗銉堬紙瀹熼〒鐨勶級</string>
+    <string name="prefs_heartrate_alert_experimental_description">鐩磋繎銇�10 鍒嗛枔銇槑纰恒仾韬綋娲诲嫊銇屻仾銇忋�佸績鎷嶆暟銇屻仐銇嶃亜鍊ゃ倰瓒呫亪銇熷牬鍚堛伀銉愩兂銉夈倰鎸嫊銇曘仜銇俱仚銆傘亾銇鑳姐伅瑭﹂〒鐨勩仾銈傘伄銇с�佸崄鍒嗐伀銉嗐偣銉堛仌銈屻仧銈傘伄銇с伅銇傘倞銇俱仜銈撱��</string>
+    <string name="devicestatus_upload_validating">銈€儍銉椼儹銉笺儔銇瑷间腑銇с仚</string>
+    <string name="devicestatus_upload_failed">銈€儍銉椼儹銉笺儔銇け鏁椼仐銇俱仐銇�</string>
+    <string name="heart_rate_result">娓畾绲愭灉</string>
+    <string name="prefs_heartrate_alert_threshold">蹇冩媿鏁般偄銉┿兗銉堛伄銇椼亶銇勫��</string>
+    <string name="activity_list_summary_intensity">閬嬪嫊
+\n寮峰害</string>
+    <string name="devicestatus_upload_started">銈€儍銉椼儹銉笺儔銈掗枊濮嬨仐銇俱仐銇�</string>
+    <string name="devicestatus_connecting">銉囥儛銈ゃ偣銇帴缍氫腑銇с仚</string>
+    <string name="devicestatus_disconnecting">銉囥儛銈ゃ偣銇帴缍氳В闄や腑銇с仚锛�</string>
+    <string name="devicestatus_upload_completed">銈€儍銉椼儹銉笺儔銇垚鍔熴仐銇俱仐銇�</string>
+    <string name="fwapp_install_device_not_supported">銉曘偂銈ゃ儷銈掋偆銉炽偣銉堛兗銉仹銇嶃伨銇涖倱銆傘儑銉愩偆銈广伅銈点儩銉笺儓銇曘倢銇︺亜銇俱仜銈撱��</string>
+    <string name="gpx_route_upload_complete">GPX 銉兗銉堛伄銈€儍銉椼儹銉笺儔銇屽畬浜嗐仐銇俱仐銇�</string>
+    <string name="gpx_route_upload_in_progress">GPX 銉兗銉堛倰銈€儍銉椼儹銉笺儔涓�</string>
+    <string name="blood_pressure">琛�鍦�</string>
+    <string name="getting_heart_rate">娓畾</string>
+    <string name="movement_intensity">閬嬪嫊寮峰害</string>
+    <string name="seconds_6">6 绉�</string>
+    <string name="seconds_7">7 绉�</string>
+    <string name="seconds_9">9 绉�</string>
+    <string name="seconds_13">13 绉�</string>
+    <string name="seconds_14">14 绉�</string>
+    <string name="seconds_15">15 绉�</string>
+    <string name="charts_activity_list">銈€偗銉嗐偅銉撱儐銈d竴瑕�</string>
+    <string name="updatefirmwareoperation_updateproblem_low_battery">銉囥儛銈ゃ偣銇儛銉冦儐銉兗銇屽皯銇仚銇庛伨銇�</string>
+    <string name="seconds_12">12 绉�</string>
+    <string name="seconds_25">25 绉�</string>
+    <string name="dialog_hide">闅犮仚</string>
+    <string name="alarm_smart_wakeup_interval">銈广優銉笺儓 銈︺偋銈ゃ偗銈€儍銉椼伄闁撻殧锛�</string>
+    <string name="alarm_smart_wakeup_interval_default">5 鍒�</string>
+    <string name="prefs_phone_silent_mode">銈广優銉笺儓銉曘偐銉炽伄銈点偆銉兂銉� 銉€兗銉�</string>
+    <string name="silent_mode_vibrate_silent">鎸嫊 / 銈点偆銉兂銉�</string>
+    <string name="prefs_find_phone_summary">銉愩兂銉夈倰浣裤仯銇︺偣銉炪兗銉堛儠銈┿兂銇潃淇¢煶銈掗炒銈夈仐銇俱仚銆�</string>
+    <string name="prefs_disconnect_notification_summary">Bluetooth 鎺ョ稓銇屽垏鏂仌銈屻仧銇ㄣ亶銇儑銉愩偆銈广仹閫氱煡銇椼伨銇欍��</string>
+    <string name="pref_summary_third_party_app_device_settings">銈ゃ兂銈广儓銉笺儷銇曘倢銇︺亜銈嬨偟銉笺儔銉戙兗銉嗐偅銇偄銉椼儶銇屻偆銉炽儐銉炽儓銈掗�氥仒銇︺儑銉愩偆銈广伄瑷畾銈掕銇堛倠銈堛亞銇仐銇俱仚銆�</string>
+    <string name="pref_title_third_party_app_device_settings">銈点兗銉夈儜銉笺儐銈c伄銈€儣銉伀瑷畾銇鏇淬倰瑷卞彲銇欍倠</string>
+    <string name="prefs_reserve_reminder_calendar_summary">鍚屾湡銇曘倢銈嬨偒銉兂銉�銉� 銈ゃ儥銉炽儓銇暟</string>
+    <string name="prefs_find_phone_duration">鐫�淇¢煶銇稒缍氭檪闁� (绉�)</string>
+    <string name="hydration_dnd_summary">姘村垎瑁滅郸銇鍛娿倰涓�瀹氭湡闁撶劇鍔广伀銇椼伨銇�</string>
+    <string name="mi2_prefs_heart_rate_monitoring">蹇冩媿鏁般伄銉€儖銈裤儶銉炽偘</string>
+    <string name="mi2_prefs_heart_rate_monitoring_summary">蹇冩媿鏁般伄銉€儖銈裤儶銉炽偘銈掕ō瀹氥仐銇俱仚</string>
+    <string name="silent_mode_normal_silent">銉庛兗銉炪儷 / 銈点偆銉兂銉�</string>
+    <string name="abstract_chart_fragment_kind_rem_sleep">銉儬鐫$湢</string>
+    <string name="pref_enable_unsupported_settings_title">銈点儩銉笺儓銇曘倢銇︺亜銇亜瑷畾銈掓湁鍔广伀銇欍倠</string>
+    <string name="pref_enable_unsupported_settings_summary">銈点儩銉笺儓銇曘倢銇︺亜銇亜绔湯銇с倐鍒╃敤鍙兘銇仚銇广仸銇ō瀹氥伀銈€偗銈汇偣銇с亶銈嬨倛銇嗐伀銇倞銇俱仚銆傘儑銉愩偆銈广亴涓嶅畨瀹氥伀銇倞銆併偗銉┿儍銈枫儱銇欍倠鍙兘鎬с亴銇傘倞銇俱仚銆�</string>
+    <string name="charts_min_max_heartrate_popup">鏈�浣庡績鎷嶆暟锛� %1$d
+\n鏈�楂樺績鎷嶆暟锛� %2$d
+\n閬嬪嫊寮峰害锛� %3$s</string>
+    <string name="pref_sleep_breathing_quality_monitoring">鐫$湢鍛煎惛銇唱銇儮銉嬨偪銉兂銈�</string>
+    <string name="silent_mode_normal_vibrate">銉庛兗銉炪儷 / 鎸嫊</string>
+    <string name="prefs_shortcut_cards">銈枫儳銉笺儓銈儍銉� 銈兗銉�</string>
+    <string name="prefs_password_6_digits_0_to_9_summary">銉戙偣銉兗銉夈伅鏁板瓧銇伩銈掔敤銇勩仧6妗併仹銇傘倠蹇呰銇屻亗銈娿伨銇�</string>
+    <string name="prefs_password_enabled">銉戙偣銉兗銉夋湁鍔�</string>
+    <string name="prefs_password_4_digits_1_to_4_summary">銉戙偣銉兗銉夈伅1銇嬨倝4銇暟瀛椼倰浣裤仯銇�4妗併仹銇傘倠蹇呰銇屻亗銈娿伨銇�</string>
+    <string name="spanish_us">銈广儦銈ゃ兂瑾烇紙銈€儭銉偒锛�</string>
+    <string name="croatian">銈儹銈€儊銈㈣獮</string>
+    <string name="estonian">銈ㄣ偣銉堛儖銈㈣獮</string>
+    <string name="french_ca">銉曘儵銉炽偣瑾烇紙銈儕銉�锛�</string>
+    <string name="activity_prefs_step_length_cm">姝╁箙锛坈m锛�</string>
+    <string name="prefs_activate_display_on_lift_sensitivity">鎰熷害</string>
+    <string name="icelandic">銈€偆銈广儵銉炽儔瑾�</string>
+    <string name="prefs_shortcut_cards_summary">銈枫儳銉笺儓銈儍銉� 銈兗銉夈伅銈︺偐銉冦儊銉曘偋銈ゃ偣銈掑彸銇偣銉偆銉椼仚銈嬨仺琛ㄧず銇曘倢銇俱仚銆傘偄銉椼儶銇屽疅琛屻仌銈屻仸銇勩倠鍫村悎銇�佽嚜鍕曠敓鎴愩仌銈屻仧銈兗銉夈伅銇撱伄瑷畾銇奖闊裤倰鍙椼亼銇俱仜銈撱��</string>
+    <string name="english_us">鑻辫獮锛堛偄銉°儶銈級</string>
+    <string name="spanish_mx">銈广儦銈ゃ兂瑾烇紙銉°偔銈枫偝锛�</string>
+    <string name="mi2_prefs_heart_rate_monitoring_alerts_summary">蹇冩媿鏁般儮銉嬨偪銉兂銈般仺銈€儵銉笺儓銇仐銇嶃亜鍊ゃ倰瑷畾銇椼伨銇�</string>
+    <string name="prefs_always_on_display">甯告檪琛ㄧず銉囥偅銈广儣銉偆</string>
+    <string name="prefs_always_on_display_style">銈广偪銈ゃ儷</string>
+    <string name="prefs_password_summary">鎵嬮銇嬨倝銉愩兂銉夈倰銇仛銇椼仧銇ㄣ亶銆併儜銈广儻銉笺儔銇с儹銉冦偗銇椼伨銇�</string>
+    <string name="pref_title_force_white_color_scheme">鐧藉湴銇粧銇厤鑹层倰寮峰埗銇欍倠</string>
+    <string name="common_symbols">鍏遍�氳鍙�</string>
+    <string name="czech">銉併偋銈宠獮</string>
+    <string name="latvian">銉┿儓銉撱偄瑾�</string>
+    <string name="english_ca">鑻辫獮锛堛偒銉娿儉锛�</string>
+    <string name="manual">鎵嬪嫊</string>
+    <string name="spanish_es">銈广儦銈ゃ兂瑾烇紙銈广儦銈ゃ兂锛�</string>
+    <string name="pref_do_not_disturb_not_wear">鏈鐫�鏅傘伅銈点偆銉兂銉� 銉€兗銉夈伀銇欍倠</string>
+    <string name="prefs_screen_timeout">鐢婚潰娑堢伅</string>
+    <string name="lefun_prefs_antilost_title">绱涘け闃叉</string>
+    <string name="prefs_password_4_digits_0_to_9_summary">銉戙偣銉兗銉夈伅鏁板瓧銇伩銈掔敤銇勩仧4妗併仹銇傘倠蹇呰銇屻亗銈娿伨銇�</string>
+    <string name="lefun_prefs_antilost_summary">銉愩兂銉夈仺銈广優銉笺儓銉曘偐銉炽伄鎺ョ稓銇屽垏鏂仌銈屻仧銇ㄣ亶銇儛銉炽儔銇屾尟鍕曘仐銇俱仚</string>
+    <string name="lefun_prefs_hydration_reminder_title">姘村垎瑁滅郸銇儶銉炪偆銉炽儉銉�</string>
+    <string name="lefun_prefs_hydration_reminder_interval_title">姘村垎瑁滅郸銉優銈ゃ兂銉�銉笺伄闁撻殧锛堝垎锛�</string>
+    <string name="lefun_prefs_hydration_reminder_summary">銉愩兂銉夈倰鎸嫊銇曘仜銇︽按鍒嗚绲︺倰淇冦仐銇俱仚</string>
+    <string name="lefun_prefs_interface_language_title">銈ゃ兂銈裤兗銉曘偋銉笺偣銇█瑾�</string>
+    <string name="english_au">鑻辫獮锛堛偑銉笺偣銉堛儵銉偄锛�</string>
+    <string name="english_gb">鑻辫獮锛堛偆銈儶銈癸級</string>
+    <string name="english_in">鑻辫獮锛堛偆銉炽儔锛�</string>
+    <string name="extended_ascii">鎷″嫉ASCII</string>
+    <string name="lithuanian">銉儓銈€儖銈㈣獮</string>
+    <string name="scandinavian">銈广偒銉炽偢銉娿儞銈㈣獮</string>
+    <string name="persian">銉氥儷銈枫偄瑾�</string>
+    <string name="ukranian">銈︺偗銉┿偆銉婅獮</string>
+    <string name="french_fr">銉曘儵銉炽偣瑾烇紙銉曘儵銉炽偣锛�</string>
+    <string name="portuguese_br">銉濄儷銉堛偓銉獮锛堛儢銉┿偢銉級</string>
+    <string name="portuguese_pt">銉濄儷銉堛偓銉獮锛堛儩銉儓銈儷锛�</string>
+    <string name="prefs_password">銉戙偣銉兗銉�</string>
+    <string name="armenian">銈€儷銉°儖銈㈣獮</string>
+    <string name="activity_prefs_goals">鐩</string>
+    <string name="activity_prefs_target_weight_kg">鐩浣撻噸锛坘g锛�</string>
+    <string name="danish">銉囥兂銉炪兗銈獮</string>
+    <string name="prefs_control_center">銈炽兂銉堛儹銉笺儷銈汇兂銈裤兗</string>
+    <string name="prefs_device_name">銉囥儛銈ゃ偣銇悕鍓�</string>
+    <string name="georgian">銈搞儳銉笺偢銈㈣獮</string>
+    <string name="bengali">銉欍兂銈儷瑾�</string>
+    <string name="prefs_always_on_display_summary">銉愩兂銉夈伄鐢婚潰銈掑父鏅傝〃绀恒仚銈�</string>
+    <string name="charts_legend_heartrate_average">骞冲潎蹇冩媿鏁�</string>
+    <string name="activity_prefs_discovery_pairing">妞滃嚭銇ㄣ儦銈€儶銉炽偘銇ō瀹�</string>
+    <string name="pref_title_charts_range">銉併儯銉笺儓銇瘎鍥�</string>
+    <string name="charts_legend_stress_average">骞冲潎銈广儓銉偣</string>
+    <string name="activity_prefs_distance_meters">1鏃ャ伄鐩: 璺濋洟 (m)</string>
+    <string name="charts_legend_spo2_average">骞冲潎琛�涓吀绱犳績搴�</string>
+    <string name="activity_prefs_calories_burnt">1鏃ャ伄鐩: 銈儹銉兗娑堣不</string>
+    <string name="dbmanagementactivity_export_data_title">銉囥兗銈裤倰銈ㄣ偗銈广儩銉笺儓銇椼伨銇欍亱锛�</string>
+    <string name="activity_db_management_empty_db_warning">娉ㄦ剰锛併亾銇儨銈裤兂銈掓娂銇欍仺銉囥兗銈裤儥銉笺偣銇屻儶銈汇儍銉堛仌銈屻伨銇欍��</string>
+    <string name="dbmanagementactivity_export_finished">鍓婇櫎銇屽畬浜嗐仐銇俱仐銇�</string>
+    <string name="activity_db_management_autoexport_explanation">銉囥兗銈裤儥銉笺偣銇嚜鍕曘偍銈偣銉濄兗銉堝厛銈掓銇牬鎵�銇ō瀹氥仐銇俱仐銇燂細</string>
+    <string name="qhybrid_title_apps_management">銈€儣銉伄绠$悊</string>
+    <string name="activity_db_management_autoexport_enabled_no">鑷嫊銈ㄣ偗銈广儩銉笺儓銇湁鍔广仹銇亗銈娿伨銇涖倱銆�</string>
+    <string name="activity_db_management_autoexport_enabled_yes">鑷嫊銈ㄣ偗銈广儩銉笺儓銇屾湁鍔广伀銇仯銇︺亜銇俱仚銆�</string>
+    <string name="activity_db_management_autoexport_scheduled_no">鑷嫊銈ㄣ偗銈广儩銉笺儓銇簣瀹氥仌銈屻仸銇勩伨銇涖倱銆�</string>
+    <string name="activity_db_management_autoexport_scheduled_yes">鑷嫊銈ㄣ偗銈广儩銉笺儓銇� %1$s 銇簣瀹氥仌銈屻仸銇勩伨銇椼仧</string>
+    <string name="activity_DB_test_export_message">銉囥兗銈裤儥銉笺偣銇偍銈偣銉濄兗銉堜腑鈥�</string>
+    <string name="activity_DB_empty_button">銉囥兗銈裤儥銉笺偣銈掔┖銇仚銈�</string>
+    <string name="activity_db_management_empty_DB">銉囥兗銈裤儥銉笺偣銈掔┖銇仚銈�</string>
+    <string name="activity_DB_import_button">銉囥兗銈裤伄銈ゃ兂銉濄兗銉�</string>
+    <string name="autoExport_lastTime_label">鏈�寰屻伄鑷嫊銈ㄣ偗銈广儩銉笺儓锛�%1$s</string>
+    <string name="activity_DB_ExportButton">銉囥兗銈裤伄銈ㄣ偗銈广儩銉笺儓</string>
+    <string name="activity_DB_test_export_button">浠娿仚銇愯嚜鍕曘偍銈偣銉濄兗銉堛倰瀹熻銇欍倠</string>
+    <string name="activity_db_management_autoexport_location">淇濆瓨鍏堛倰瑾嶈瓨銇с亶銇俱仜銈撱仹銇椼仧銆傛柊銇椼亜 Android銇儜銉笺儫銉冦偡銉с兂 銈枫偣銉嗐儬銇晱椤屻仹銇傘倠銇嬨倐銇椼倢銇俱仜銈撱�傝嚜鍕曘偍銈偣銉濄兗銉堛伅銇娿仢銈夈亸姗熻兘銇椼仸銇勩伨銇涖倱銆�</string>
+    <string name="activity_db_management_exportimport_label">銈ㄣ偗銈广儩銉笺儓銇ㄣ偆銉炽儩銉笺儓</string>
+    <string name="dbmanagementactivity_export_confirmation">銉囥兗銈裤倰銈ㄣ偗銈广儩銉笺儓銇椼仸銈傘倛銈嶃仐銇勩仹銇欍亱锛熶互鍓嶃伀銈ㄣ偗銈广儩銉笺儓銇椼仧銈€偗銉嗐偅銉撱儐銈c儑銉笺偪銇� (銈傘仐銇傘倢銇�) 銇欍伖銇︿笂鏇搞亶銇曘倢銇俱仚銆�</string>
+    <string name="activity_type_trampoline">銉堛儵銉炽儩銉兂</string>
+    <string name="activity_type_mountain_hike">鐧诲北</string>
+    <string name="sensitive">銈汇兂銈枫儐銈c儢</string>
+    <string name="activity_type_cross_trainer">銈儹銈广儓銉兗銉娿兗</string>
+    <string name="activity_type_free_training">銉曘儶銉笺儓銉兗銉嬨兂銈�</string>
+    <string name="activity_type_rower">銉兗銈ゃ兂銈�</string>
+    <string name="activity_type_stair_stepper">銈广儐銉冦儜銉�</string>
+    <string name="activity_type_physical_training">銉曘偅銈搞偒銉儓銉兗銉嬨兂銈�</string>
+    <string name="activity_type_cross_country_running">銈儹銈广偒銉炽儓銉兗绔惰蛋</string>
+    <string name="activity_type_fencing">銉曘偋銉炽偡銉炽偘</string>
+    <string name="activity_type_cross_training">銈儹銈广儓銉兗銉嬨兂銈�</string>
+    <string name="activity_type_fitness_gaming">銉曘偅銉冦儓銉嶃偣銈层兗銉�</string>
+    <string name="activity_type_push_ups">鑵曠珛銇︿紡銇�</string>
+    <string name="activity_type_battle_rope">銉愩儓銉儹銉笺儣</string>
+    <string name="activity_type_smith_machine">銈广儫銈广優銈枫兂</string>
+    <string name="activity_type_martial_arts">姝﹁姼</string>
+    <string name="activity_type_tai_chi">澶サ鎷�</string>
+    <string name="activity_type_hula_hooping">銉曘儵銉曘兗銉�</string>
+    <string name="activity_type_disc_sports">銉囥偅銈广偗銈广儩銉笺儎</string>
+    <string name="activity_type_darts">銉�銉笺儎</string>
+    <string name="activity_type_swing">銉栥儵銉炽偝</string>
+    <string name="activity_type_stairs">闅庢</string>
+    <string name="activity_type_fishing">閲c倞</string>
+    <string name="activity_type_karate">绌烘墜</string>
+    <string name="mi2_dnd_always">甯搞伀</string>
+    <string name="dnd_all_day">涓�鏃ヤ腑</string>
+    <string name="activity_type_rolling">銉兗銉兂銈�</string>
+    <string name="activity_type_flexibility">鏌旇粺浣撴搷</string>
+    <string name="activity_type_outdoor_running">灞嬪銉┿兂銉嬨兂銈�</string>
+    <string name="activity_type_indoor_running">灞嬪唴銉┿兂銉嬨兂銈�</string>
+    <string name="activity_type_fitness_exercises">銉曘偅銉冦儓銉嶃偣</string>
+    <string name="activity_type_crossfit">銈儹銈广儠銈c儍銉�</string>
+    <string name="activity_type_functional_training">銉曘偂銉炽偗銈枫儳銉娿儷銉堛儸銉笺儖銉炽偘</string>
+    <string name="activity_type_taekwondo">銉嗐偝銉炽儔銉�</string>
+    <string name="activity_type_kendo">鍓i亾</string>
+    <string name="activity_type_horizontal_bar">閴勬</string>
+    <string name="activity_type_aerobic_exercise">鏈夐吀绱犻亱鍕�</string>
+    <string name="activity_type_track_and_field">銉堛儵銉冦偗锛嗐儠銈c兗銉儔</string>
+    <string name="activity_type_pull_ups">鎳稿瀭</string>
+    <string name="activity_type_plank">銉椼儵銉炽偗</string>
+    <string name="activity_type_javelin">銈勩倞鎶曘亽</string>
+    <string name="normal">銉庛兗銉炪儷</string>
+    <string name="smart">銈广優銉笺儓</string>
+    <string name="activity_type_long_jump">璧般倞骞呰烦銇�</string>
+    <string name="activity_type_high_jump">璧般倞楂樿烦銇�</string>
+    <string name="activity_type_dumbbell">銉�銉炽儥銉�</string>
+    <string name="activity_type_belly_dance">銉欍儶銉笺儉銉炽偣</string>
+    <string name="activity_type_jazz_dance">銈搞儯銈恒儉銉炽偣</string>
+    <string name="activity_type_latin_dance">銉┿儐銉炽儉銉炽偣</string>
+    <string name="activity_type_ballet">銉愩儸銈�</string>
+    <string name="activity_type_other_dance">銇濄伄浠栥伄銉�銉炽偣</string>
+    <string name="activity_type_roller_skating">銉兗銉┿兗銈广偙銉笺儓</string>
+    <string name="activity_type_cooldown">銈兗銉儉銈︺兂</string>
+    <string name="activity_type_horse_riding">涔楅Μ</string>
+    <string name="activity_type_parallel_bar">骞宠妫�</string>
+    <string name="activity_type_sit_ups">涓婁綋璧枫亾銇�</string>
+    <string name="activity_type_archery">銈€兗銉併偋銉兗</string>
+    <string name="activity_type_kite_flying">鍑ф彋銇�</string>
+    <string name="activity_type_soccer">銈点儍銈兗</string>
+    <string name="activity_type_hand_cycling">銉忋兂銉夈偟銈ゃ偗銉�</string>
+    <string name="activity_type_kabaddi">銈儛銉囥偅</string>
+    <string name="activity_type_softball">銈姐儠銉堛儨銉笺儷</string>
+    <string name="activity_type_sailing">銈汇兗銉兂銈�</string>
+    <string name="activity_type_jet_skiing">銈搞偋銉冦儓銈广偔銉�</string>
+    <string name="activity_type_kitesurfing">銈偆銉堛偟銉笺儠銈c兂</string>
+    <string name="activity_type_freestyle">銉曘儶銉笺偣銈裤偆銉�</string>
+    <string name="activity_type_hiking">銉忋偆銈兂銈�</string>
+    <string name="activity_type_pool_swimming">銉椼兗銉偣銈ゃ儫銉炽偘</string>
+    <string name="activity_type_swimming_openwater">銈兗銉椼兂銈︺偐銉笺偪銉笺偣銈ゃ儫銉炽偘</string>
+    <string name="activity_type_outdoor_cycling">灞嬪銈点偆銈儶銉炽偘</string>
+    <string name="activity_type_baseball">閲庣悆</string>
+    <string name="activity_type_australian_football">銈兗銈广儓銉┿儶銈€兂銉曘儍銉堛儨銉笺儷</string>
+    <string name="activity_type_outdoor_walking">灞嬪銈︺偐銉笺偔銉炽偘</string>
+    <string name="activity_type_rowing_machine">銉兗銈ゃ兂銈般優銈枫兂</string>
+    <string name="activity_type_cricket">銈儶銈便儍銉�</string>
+    <string name="activity_type_cross_country_skiing">銈儹銈广偒銉炽儓銉兗銈广偔銉�</string>
+    <string name="activity_type_snow_sports">銈︺偅銉炽偪銉笺偣銉濄兗銉�</string>
+    <string name="activity_type_surfing">銈点兗銉曘偅銉�</string>
+    <string name="activity_type_indoor_walking">灞嬪唴銈︺偐銉笺偔銉炽偘</string>
+    <string name="activity_type_windsurfing">銈︺偅銉炽儔銈点兗銉曘偅銉�</string>
+    <string name="activity_type_rock_climbing">銉儍銈偗銉┿偆銉熴兂銈�</string>
+    <string name="activity_type_hunting">鐙╃専</string>
+    <string name="activity_type_climbing">銈儵銈ゃ儫銉炽偘</string>
+    <string name="activity_type_football">銉曘儍銉堛儨銉笺儷</string>
+    <string name="activity_type_basketball">銉愩偣銈便儍銉堛儨銉笺儷</string>
+    <string name="activity_type_karting">銈淬兗銈兗銉�</string>
+    <string name="activity_type_billiards">銉撱儶銉ゃ兗銉�</string>
+    <string name="activity_type_shuttlecock">銉愩儔銉熴兂銉堛兂</string>
+    <string name="activity_type_dodgeball">銉夈儍銈搞儨銉笺儷</string>
+    <string name="activity_type_pickleball">銉斻儍銈儷銉溿兗銉�</string>
+    <string name="activity_type_lacross">銉┿偗銉偣</string>
+    <string name="activity_type_shot">灏勬拑</string>
+    <string name="activity_type_skating">銈广偙銉笺儓</string>
+    <string name="activity_type_ice_hockey">銈€偆銈广儧銉冦偙銉�</string>
+    <string name="activity_type_curling">銈兗銉兂銈�</string>
+    <string name="activity_type_skateboarding">銈广偙銉笺儓銉溿兗銉�</string>
+    <string name="activity_type_elliptical_trainer">銈儹銈广儓銉兗銉娿兗</string>
+    <string name="activity_type_elliptical">銈ㄣ儶銉椼儐銈c偒銉�</string>
+    <string name="activity_type_rugby">銉┿偘銉撱兗</string>
+    <string name="activity_type_rowing">銉兗銈ゃ兂銈�</string>
+    <string name="zepp_os_watchface_superposition">superposition</string>
+    <string name="yesterdays_activity">鏄ㄦ棩銇偄銈儐銈c儞銉嗐偅</string>
+    <string name="activity_type_stretching">銈广儓銉儍銉�</string>
+    <string name="menuitem_total_workout">銇欍伖銇︺伄銉兗銈偄銈︺儓</string>
+    <string name="menuitem_wifi">Wi-Fi</string>
+    <string name="menuitem_bluetooth">Bluetooth</string>
+    <string name="zepp_os_watchface_starry_sky">Starry sky</string>
+    <string name="zepp_os_watchface_guider">Guider</string>
+    <string name="devicetype_amazfit_active">Amazfit Active</string>
+    <string name="devicetype_amazfit_gts3">Amazfit GTS 3</string>
+    <string name="devicetype_casiogwb5600">CASIO GW-B5600</string>
+    <string name="menuitem_todo">To-Do</string>
+    <string name="menuitem_sun_moon">澶櫧銇ㄦ湀</string>
+    <string name="devicetype_colacao23">ColaCao 2023</string>
+    <string name="zepp_os_watchface_city_of_speed">City of speed</string>
+    <string name="activity_type_stepper">銈广儐銉冦儜銉�</string>
+    <string name="menuitem_offline_voice">銈儠銉┿偆銉炽儨銈ゃ偣</string>
+    <string name="kind_app">銈€儣銉�</string>
+    <string name="devicetype_miband8active">Xiaomi Smart Band 8 Active</string>
+    <string name="devicetype_miband8pro">Xiaomi Smart Band 8 Pro</string>
+    <string name="devicetype_amazfit_gtr_lite">Amazfit GTR Lite</string>
+    <string name="devicetype_amazfit_bips_lite">Amazfit Bip S Lite</string>
+    <string name="devicetype_um25">UM-25</string>
+    <string name="devicetype_waspos">Wasp-os</string>
+    <string name="devicetype_sony_wi_sp600n">SONY WI-SP600N</string>
+    <string name="devicetype_sony_linkbuds">SONY LinkBuds</string>
+    <string name="devicetype_honor_band6">Honor Band 6</string>
+    <string name="notification_channel_low_battery_name">銉愩儍銉嗐儶銉兼畫閲� 浣�</string>
+    <string name="notification_channel_gps">GPS 銉堛儵銉冦偔銉炽偘</string>
+    <string name="devicetype_amazfit_gts">Amazfit GTS</string>
+    <string name="menuitem_vo2_max">VO鈧� Max</string>
+    <string name="menuitem_forecast">浜堝牨</string>
+    <string name="menuitem_last_workout">鏈�寰屻伄銉兗銈偄銈︺儓</string>
+    <string name="menuitem_spo2">SpO2</string>
+    <string name="menuitem_eventreminder">銈ゃ儥銉炽儓 銉優銈ゃ兂銉�銉�</string>
+    <string name="menuitem_takephoto">銈儭銉┿儶銉€偝銉�</string>
+    <string name="menuitem_pomodoro">銉濄儮銉夈兗銉� 銉堛儵銉冦偒銉�</string>
+    <string name="menuitem_female_health">濂虫�с伄鍋ュ悍</string>
+    <string name="menuitem_email">銉°兗銉�</string>
+    <string name="menuitem_countdown">銈偊銉炽儓銉�銈︺兂</string>
+    <string name="menuitem_personal_activity_intelligence">銉戙兗銈姐儕銉偄銈儐銈c儞銉嗐偅銈ゃ兂銉嗐儶銈搞偋銉炽偣 (PAI)</string>
+    <string name="devicetype_huawei_watchgt2e">Huawei Watch GT 2e</string>
+    <string name="zepp_os_watchface_vast_sky">Vast Sky</string>
+    <string name="zepp_os_watchface_pure_white">Pure white</string>
+    <string name="devicetype_miband6">Mi Band 6</string>
+    <string name="activity_summary_detail_clear_gpx_track">GPS 銉堛儵銉冦偗銇墛闄�</string>
+    <string name="devicetype_huawei_watchultimate">Huawei Watch Ultimate</string>
+    <string name="devicetype_femometer_vinca2">Femometer Vinca II</string>
+    <string name="devicetype_redmi_smart_band_2">Redmi Smart Band 2</string>
+    <string name="devicetype_redmi_watch_2">Redmi Watch 2</string>
+    <string name="notification_gps_text">浣嶇疆鎯呭牨銈� %1$d 銉囥儛銈ゃ偣銇�佷俊涓�</string>
+    <string name="devicetype_amazfit_vergel">Amazfit Verge Lite</string>
+    <string name="devicetype_sg2">Lemfo SG2</string>
+    <string name="menuitem_nothing">銇仐</string>
+    <string name="menuitem_aqi">绌烘皸璩寚鏁�</string>
+    <string name="menuitem_recommendation">銇娿仚銇欍倎</string>
+    <string name="menuitem_readiness">銉儑銈c儘銈�</string>
+    <string name="screenshot_taken">鎾奖銇曘倢銇熴偣銈儶銉笺兂銈枫儳銉冦儓</string>
+    <string name="devicetype_amazfit_gts4">Amazfit GTS 4</string>
+    <string name="devicetype_amazfit_gts4_mini">Amazfit GTS 4 Mini</string>
+    <string name="zepp_os_watchface_the_ultima">The ultima</string>
+    <string name="menuitem_cards">銈兗銉�</string>
+    <string name="kind_gpx_route">GPX 銉兗銉�</string>
+    <string name="devicetype_sony_wf_1000xm5">SONY WF-1000XM5</string>
+    <string name="devicetype_sony_linkbuds_s">SONY LinkBuds S</string>
+    <string name="devicetype_casiogmwb5000">CASIO GMW-B5000</string>
+    <string name="devicetype_miscale2">Mi Scale 2</string>
+    <string name="devicetype_itag">iTag</string>
+    <string name="devicetype_bfh16">BFH-16</string>
+    <string name="devicetype_mijia_lywsd02">Mijia Smart Clock</string>
+    <string name="devicetype_makibes_hr3">Makibes HR3</string>
+    <string name="devicetype_huawei_band8">Huawei Band 8</string>
+    <string name="devicetype_huawei_watch_gt">Huawei Watch GT</string>
+    <string name="devicetype_lefun">Lefun</string>
+    <string name="zepp_os_watchface_free_combination">Free combination</string>
+    <string name="activity_error_share_failed">銉曘偂銈ゃ儷銇叡鏈夈伀澶辨晽銇椼伨銇椼仧銆�</string>
+    <string name="devicetype_amazfit_bip5">Amazfit Bip 5</string>
+    <string name="devicetype_amazfit_gtr4">Amazfit GTR 4</string>
+    <string name="devicetype_amazfit_trex_2">Amazfit T-Rex 2</string>
+    <string name="devicetype_amazfit_band7">Amazfit Band 7</string>
+    <string name="devicetype_amazfit_trex_ultra">Amazfit T-Rex Ultra</string>
+    <string name="devicetype_sony_wh_1000xm5">SONY WH-1000XM5</string>
+    <string name="devicetype_huawei_band4pro">Huawei Band 4 (Pro)</string>
+    <string name="menuitem_workout">銉兗銈偄銈︺儓</string>
+    <string name="menuitem_widgets">銈︺偅銈搞偋銉冦儓</string>
+    <string name="devicetype_redmi_watch_4">Redmi Watch 4</string>
+    <string name="notification_channel_high_priority_name">鍎厛搴� 楂�</string>
+    <string name="notification_gps_title">Gadgetbridge GPS</string>
+    <string name="activity_type_core_training">浣撳构銉堛儸銉笺儖銉炽偘</string>
+    <string name="activity_type_hiit">楂樺挤搴︺偆銉炽偪銉笺儛銉儓銉兗銉嬨兂銈帮紙HIIT锛�</string>
+    <string name="activity_type_boxing">銉溿偗銈枫兂銈�</string>
+    <string name="activity_type_zumba">銈恒兂銉�</string>
+    <string name="activity_type_indoor_ice_skating">灞嬪唴銈€偆銈广偣銈便兗銉�</string>
+    <string name="activity_summary_detail">銈广儩銉笺儎 銈€偗銉嗐偅銉撱儐銈c伄瑭崇窗</string>
+    <string name="activity_type_other">銇濄伄浠�</string>
+    <string name="kind_agps_bundle">AGPS 銉愩兂銉夈儷</string>
+    <string name="devicetype_amazfit_trex">Amazfit T-Rex</string>
+    <string name="devicetype_amazfit_bips">Amazfit Bip S</string>
+    <string name="devicetype_sony_wf_1000xm3">SONY WF-1000XM3</string>
+    <string name="notification_channel_transfer_name">銉囥兗銈胯虎閫�</string>
+    <string name="menuitem_barometer">姘楀湩瑷�</string>
+    <string name="zepp_os_watchface_rotating_earth">Rotating Earth</string>
+    <string name="activity_type_gymnastics">浣撴搷</string>
+    <string name="devicetype_miband8">Xiaomi Smart Band 8</string>
+    <string name="devicetype_amazfit_balance">Amazfit Balance</string>
+    <string name="devicetype_amazfit_active_edge">Amazfit Active Edge</string>
+    <string name="devicetype_amazfit_cheetah_square">Amazfit Cheetah (Square)</string>
+    <string name="devicetype_amazfit_cheetah_round">Amazfit Cheetah (Round)</string>
+    <string name="devicetype_amazfit_cheetah_pro">Amazfit Cheetah Pro</string>
+    <string name="devicetype_amazfit_gtr3">Amazfit GTR 3</string>
+    <string name="devicetype_amazfit_gtr3_pro">Amazfit GTR 3 Pro</string>
+    <string name="devicetype_amazfit_bip3">Amazfit Bip 3</string>
+    <string name="devicetype_amazfit_band5">Amazfit Band 5</string>
+    <string name="devicetype_amazfit_neo">Amazfit Neo</string>
+    <string name="devicetype_amazfit_gtr_mini">Amazfit GTR Mini</string>
+    <string name="devicetype_amazfit_falcon">Amazfit Falcon</string>
+    <string name="devicetype_amazfit_bipu">Amazfit Bip U</string>
+    <string name="devicetype_amazfit_bipupro">Amazfit Bip U Pro</string>
+    <string name="devicetype_amazfit_pop">Amazfit Pop</string>
+    <string name="devicetype_amazfit_pop_pro">Amazfit Pop Pro</string>
+    <string name="devicetype_watchx">Watch X</string>
+    <string name="devicetype_watchxplus">Watch X Plus</string>
+    <string name="devicetype_y5">Y5</string>
+    <string name="devicetype_casiogbx100">CASIO GBX-100</string>
+    <string name="devicetype_mijia_lywsd03">Mijia Temperature and Humidity Sensor 2</string>
+    <string name="devicetype_mijia_mho_c303">Mijia MHO-C303</string>
+    <string name="devicetype_banglejs">Bangle.js</string>
+    <string name="devicetype_tlw64">TLW64</string>
+    <string name="devicetype_pinetime_jf">PineTime (JF Firmware)</string>
+    <string name="devicetype_smaq2oss">SMA-Q2 OSS</string>
+    <string name="devicetype_fitpro">FitPro</string>
+    <string name="devicetype_colacao21">ColaCao 2021</string>
+    <string name="devicetype_sonyswr12">SONY SWR12</string>
+    <string name="devicetype_domyos_t540">Domyos T540</string>
+    <string name="devicetype_sony_wh_1000xm2">SONY WH-1000XM2</string>
+    <string name="devicetype_sony_wh_1000xm3">SONY WH-1000XM3</string>
+    <string name="devicetype_sony_wf_sp800n">SONY WF-SP800N</string>
+    <string name="devicetype_sony_wf_1000xm4">SONY WF-1000XM4</string>
+    <string name="devicetype_binary_sensor">Binary sensor</string>
+    <string name="devicetype_honor_band3">Honor Band 3</string>
+    <string name="devicetype_honor_band5">Honor Band 5</string>
+    <string name="devicetype_redmiwatch3active">Redmi Watch 3 Active</string>
+    <string name="devicetype_redmiwatch3">Redmi Watch 3</string>
+    <string name="devicetype_redmi_smart_band_pro">Redmi Smart Band Pro</string>
+    <string name="devicetype_honor_band7">Honor Band 7</string>
+    <string name="devicetype_huawei_band_aw70">Huawei Band (AW70)</string>
+    <string name="devicetype_huawei_band7">Huawei Band 7</string>
+    <string name="devicetype_huawei_watchgt2">Huawei Watch GT 2 (Pro)</string>
+    <string name="devicetype_huawei_talk_band_b6">Huawei Talk Band B6</string>
+    <string name="devicetype_huawei_watchgt4">Huawei Watch GT 4</string>
+    <string name="devicetype_huawei_watchfit">Huawei Watch Fit</string>
+    <string name="menuitem_zepp_pay">Zepp Pay</string>
+    <string name="menuitem_zepp_coach">Zepp銈炽兗銉�</string>
+    <string name="menuitem_thermometer">娓╁害瑷�</string>
+    <string name="menuitem_body_composition">韬綋绲勬垚</string>
+    <string name="menuitem_workout_shortcuts">銉兗銈偄銈︺儓銇偡銉с兗銉堛偒銉冦儓</string>
+    <string name="menuitem_apps_shortcuts">銈€儣銉伄銈枫儳銉笺儓銈儍銉�</string>
+    <string name="menuitem_cycles">銈点偆銈儷 銉堛儵銉冦偔銉炽偘</string>
+    <string name="menuitem_wechat_pay">WeChat Pay</string>
+    <string name="menuitem_mi_ai">MI AI</string>
+    <string name="preferences_qhybrid_settings">Q Hybrid 銇ō瀹�</string>
+    <string name="menuitem_findphone">銈广優銉笺儓銉曘偐銉炽倰鎺€仚</string>
+    <string name="menuitem_mutephone">銈广優銉笺儓銉曘偐銉炽倰銉熴儱銉笺儓銇仚銈�</string>
+    <string name="menuitem_alexa">Alexa</string>
+    <string name="menuitem_goal">銈€偗銉嗐偅銉撱儐銈c伄鐩</string>
+    <string name="menuitem_lockscreen">銉儍銈敾闈�</string>
+    <string name="zepp_os_watchface_lightning_flash">Lightning flash</string>
+    <string name="share_log_not_enabled_message">瑷畾銇嬨倝銆屻儹銈般儠銈°偆銉倰鍑哄姏銆� 銈掓湁鍔广伀銇欍倠蹇呰銇屻亗銈娿伨銇�</string>
+    <string name="devicetype_honor_band4">Honor Band 4</string>
+    <string name="devicetype_amazfit_gts2e">Amazfit GTS 2e</string>
+    <string name="devicetype_sony_wh_1000xm4">SONY WH-1000XM4</string>
+    <string name="devicetype_xiaomi_watch_lite">Xiaomi Watch Lite</string>
+    <string name="devicetype_amazfit_bip3_pro">Amazfit Bip 3 Pro</string>
+    <string name="menuitem_membership_cards">浼氬摗瑷�</string>
+    <string name="menuitem_screen_always_lit">鐢婚潰銈掑父銇偣鐏�</string>
+    <string name="menuitem_brightness">鏄庛倠銇�</string>
+    <string name="menuitem_headphone">銉樸儍銉夈儧銉�</string>
+    <string name="zepp_os_watchface_minimalist">Minimalist</string>
+    <string name="activity_type_weightlifting">銈︺偍銈ゃ儓銉儠銉嗐偅銉炽偘</string>
+    <string name="activity_type_pilates">銉斻儵銉嗐偅銈�</string>
+    <string name="activity_type_volleyball">銉愩儸銉笺儨銉笺儷</string>
+    <string name="activity_type_icehockey">銈€偆銈广儧銉冦偙銉�</string>
+    <string name="activity_type_tennis">銉嗐儖銈�</string>
+    <string name="activity_type_pingpong">鍗撶悆</string>
+    <string name="activity_type_squash">銈广偒銉冦偡銉�</string>
+    <string name="activity_type_badminton">銉愩儔銉熴兂銉堛兂</string>
+    <string name="activity_summary_detail_select_gpx_track">GPX 銉堛儵銉冦偗銇伕鎶�</string>
+    <string name="activity_error_no_app_for_png">銈广偗銉兗銉炽偡銉с儍銉堛倰鍏辨湁銇欍倠銇伅銆佺敾鍍忋儠銈°偆銉倰鎵便亪銈嬨偄銉椼儶銈掋偆銉炽偣銉堛兗銉仐銇︺亸銇犮仌銇勩��</string>
+    <string name="activity_type_trekking">銉堛儸銉冦偔銉炽偘</string>
+    <string name="activity_type_iceskating">銈€偆銈广偣銈便兗銉�</string>
+    <string name="activity_type_trail_run">銉堛儸銈ゃ儷銉┿兂銉嬨兂銈�</string>
+    <string name="activity_summary_edit_name_title">銉┿儥銉伄绶ㄩ泦</string>
+    <string name="share_screenshot">銈广偗銉兗銉炽偡銉с儍銉堛伄鍏辨湁</string>
+    <string name="add_test_device">銉嗐偣銉� 銉囥儛銈ゃ偣銈掕拷鍔犮仚銈�</string>
+    <string name="devicetype_miband7">Xiaomi Smart Band 7</string>
+    <string name="devicetype_miband7pro">Xiaomi Smart Band 7 Pro</string>
+    <string name="devicetype_amazfit_gtr2">Amazfit GTR 2</string>
+    <string name="devicetype_amazfit_gts2_mini">Amazfit GTS 2 Mini</string>
+    <string name="devicetype_amazfit_x">Amazfit X</string>
+    <string name="devicetype_zepp_e">Zepp E</string>
+    <string name="devicetype_vivomove_hr">Garmin Vivomove HR</string>
+    <string name="menuitem_temperature">娓╁害</string>
+    <string name="menuitem_calendar">銈儸銉炽儉銉�</string>
+    <string name="menuitem_flashlight">鎳愪腑闆荤伅</string>
+    <string name="menuitem_workout_status">銉兗銈偄銈︺儓銇姸娉�</string>
+    <string name="menuitem_voice_memos">銉溿偆銈广儭銉�</string>
+    <string name="menuitem_phone">銈广優銉笺儓銉曘偐銉�</string>
+    <string name="menuitem_workout_history">銉兗銈偄銈︺儓銇饱姝�</string>
+    <string name="menuitem_one_tap_measuring">銉兂銈裤儍銉楁脯瀹�</string>
+    <string name="menuitem_theater_mode">銈枫偄銈裤兗銉€兗銉�</string>
+    <string name="menuitem_volume">闊抽噺</string>
+    <string name="zepp_os_watchface_emerald_moonlight">Emerald Moonlight</string>
+    <string name="menuitem_eject_water">姘淬倰鎺掑嚭</string>
+    <string name="menuitem_buzzer_intensity">銉栥偠銉笺伄寮枫仌</string>
+    <string name="menuitem_unknown_app">涓嶆槑 (%s)</string>
+    <string name="menuitem_unsupported">[鏈偟銉濄兗銉圿 %s</string>
+    <string name="zepp_os_watchface_red_fantasy">Red Fantasy</string>
+    <string name="zepp_os_watchface_multiple_data">Multiple Data</string>
+    <string name="zepp_os_watchface_rush">Rush</string>
+    <string name="zepp_os_watchface_simplicity_data">Simplicity Data</string>
+    <string name="zepp_os_watchface_vibrant">Vibrant</string>
+    <string name="zepp_os_watchface_business_style">Business Style</string>
+    <string name="activity_type_bowling">銉溿偊銉兂銈�</string>
+    <string name="activity_type_street_dance">銈广儓銉兗銉堛儉銉炽偣</string>
+    <string name="activity_type_golf">銈淬儷銉�</string>
+    <string name="activity_type_wrestling">銉偣銉兂銈�</string>
+    <string name="activity_type_dance">銉�銉炽偣</string>
+    <string name="activity_type_indoor_fitness">灞嬪唴銉曘偅銉冦儓銉嶃偣</string>
+    <string name="devicetype_amazfit_gts2">Amazfit GTS 2</string>
+    <string name="devicetype_amazfit_gtr2e">Amazfit GTR 2e</string>
+    <string name="devicetype_honor_magicwatch2">Honor MagicWatch 2</string>
+    <string name="devicetype_huawei_band6">Huawei Band 6</string>
+    <string name="activity_type_handball">銉忋兂銉夈儨銉笺儷</string>
+    <string name="devicetype_bohemic_smart_bracelet">Bohemic Smart Bracelet</string>
+    <string name="devicetype_redmi_watch_2_lite">Redmi Watch 2 Lite</string>
+    <string name="activity_type_table_tennis">鍗撶悆</string>
+    <string name="activity_type_kickboxing">銈儍銈儨銈偡銉炽偘</string>
+    <string name="activity_type_dancing">銉�銉炽偣</string>
+    <string name="activity_type_skiing">銈广偔銉�</string>
+    <string name="activity_type_hockey">銉涖儍銈便兗</string>
+    <string name="activity_error_no_app_for_gpx">銈€偗銉嗐偅銉撱儐銈� 銉堛儸銉笺偣銈掕〃绀恒仚銈嬨伀銇�丟PX 銉曘偂銈ゃ儷銈掓壉銇堛倠銈€儣銉倰銈ゃ兂銈广儓銉笺儷銇椼仸銇忋仩銇曘亜銆�</string>
+    <string name="note">鍌欒��</string>
+    <string name="activity_type_strength_training">绛嬪姏銉堛儸銉笺儖銉炽偘</string>
+    <string name="activity_type_snowboarding">銈广儙銉笺儨銉笺儔</string>
+    <string name="activity_type_riding">涔楅Μ</string>
+    <string name="devicetype_huawei_watchgt3">Huawei Watch GT 3 (Pro)</string>
+    <string name="menuitem_stopwatch">銈广儓銉冦儣銈︺偐銉冦儊</string>
+    <string name="menuitem_events">銈ゃ儥銉炽儓</string>
+    <string name="menuitem_dnd">DND</string>
+    <string name="menuitem_sleep">鐫$湢</string>
+    <string name="menuitem_breathing">鍛煎惛</string>
+    <string name="widget_settings_select_device_title">銉囥儛銈ゃ偣銇伕鎶�</string>
+    <string name="widget_set_alarm_after">浣曞垎寰屻伀銈€儵銉笺儬銈掋偦銉冦儓銇欍倠銇嬶細</string>
+    <string name="quick_alarm">銈偆銉冦偗銈€儵銉笺儬</string>
+    <string name="prefs_fm_preset_instructions">銉溿偪銉炽倰闀锋娂銇椼仹銉椼儶銈汇儍銉堛倰淇濆瓨</string>
+    <string name="widget_listing_label">銈广儐銉笺偪銈广仺銈€儵銉笺儬</string>
+    <string name="prefs_fm_presets_presets">銉椼儶銈汇儍銉�</string>
+    <string name="watch_not_connected">銈︺偐銉冦儊銇屾帴缍氥仌銈屻仸銇勩伨銇涖倱</string>
+    <string name="pref_title_lower_button_function_short">涓嬨儨銈裤兂锛堢煭锛�</string>
+    <string name="pref_title_upper_button_function_long">涓娿儨銈裤兂锛堥暦锛�</string>
+    <string name="pref_title_middle_button_function_double">涓儨銈裤兂锛�2 鍥烇級</string>
+    <string name="hr_appname_workout">銉兗銈偄銈︺儓</string>
+    <string name="pref_title_middle_button_function_long">涓儨銈裤兂锛堥暦锛�</string>
+    <string name="pref_title_upper_button_function_double">涓娿儨銈裤兂锛�2 鍥烇級</string>
+    <string name="qhybrid_buttons_overwrite_success">銉溿偪銉炽亴涓婃浉銇嶃仌銈屻伨銇椼仧</string>
+    <string name="about_links">銉兂銈�</string>
+    <string name="qhybrid_changes_delay_prompt">澶夋洿銇弽鏄犮伀鏁扮銇嬨亱銈娿伨銇欌��</string>
+    <string name="pref_title_lower_button_function_double">涓嬨儨銈裤兂锛�2 鍥烇級</string>
+    <string name="about_additional_contributions">銈€儣銉枊鐧恒伀璨㈢尞銇椼仸銇勩仧銇犮亜銇熴仚銇广仸銇并鐚�呫伀鎰熻瑵銇椼伨銇欌�︹湂</string>
+    <string name="gpx_receiver_overwrite_some_files">銉曘偂銈ゃ儷銇棦銇瓨鍦ㄣ仐銇俱仚銆備笂鏇搞亶銇椼伨銇欍亱锛�</string>
+    <string name="about_version">銉愩兗銈搞儳銉� %s</string>
+    <string name="qhybrid_overwrite_buttons">銉溿偪銉炽倰涓婃浉銇�</string>
+    <string name="qhybrid_buttons_overwrite_error">銉溿偪銉炽倰涓婃浉銇嶃仹銇嶃伨銇涖倱銇с仐銇�</string>
+    <string name="text_receiver_activity_title">銉嗐偔銈广儓銈掋儑銉愩偆銈广伀閫併倠</string>
+    <string name="qhybrid_goal_in_steps">鐩姝╂暟</string>
+    <string name="qhybrid_time_shift">銈裤偆銉犮偡銉曘儓</string>
+    <string name="pref_title_upper_button_long_press_action">涓娿儨銈裤兂銈掗暦銇忔娂銇椼仧銇ㄣ亶銇嫊浣�</string>
+    <string name="pref_title_upper_button_function_short">涓娿儨銈裤兂锛堢煭锛�</string>
+    <string name="pref_title_middle_button_function_short">涓儨銈裤兂锛堢煭锛�</string>
+    <string name="pref_title_lower_button_function_long">涓嬨儨銈裤兂锛堥暦锛�</string>
+    <string name="hr_widget_active_minutes">娲诲嫊鏅傞枔</string>
+    <string name="hr_appname_wellness">銈︺偋銉儘銈�</string>
+    <string name="pref_title_lower_button_short_press_action">涓嬨儨銈裤兂銈掔煭銇忔娂銇椼仧銇ㄣ亶銇嫊浣�</string>
+    <string name="hr_widget_nothing">銇仐</string>
+    <string name="about_core_team_title">銈炽偄 銉併兗銉狅紙璨㈢尞銇椼仧闋嗭級</string>
+    <string name="about_hash">銈炽儫銉冦儓 %s</string>
+    <string name="gpx_receiver_files_received">GPX 銉曘偂銈ゃ儷銈掑彈淇°仐銇俱仐銇燂細</string>
+    <string name="appmanager_downloaded_to_cache">%s 銈掋偔銉c儍銈枫儱銇儉銈︺兂銉兗銉夈仐銇俱仐銇�</string>
+    <string name="pref_title_mb_intents">銉°儑銈c偄銉溿偪銉炽伄銈ゃ兂銉嗐兂銉堛倰鐩存帴閫佷俊銇欍倠</string>
+    <string name="low_power_gps">鐪侀浕鍔� GPS</string>
+    <string name="all_satellites">銇欍伖銇︺伄琛涙槦</string>
+    <string name="gadgetbridge_running_main_nightly">Gadgetbridge Nightly 銇嫊浣滀腑銇с仚</string>
+    <string name="application_name_main_nopebble">Gadgetbridge (Nightly, No Pebble provider)</string>
+    <string name="title_activity_controlcenter_main_nopebble">Gadgetbridge Nightly No Pebble</string>
+    <string name="about_activity_title_main_nopebble">Gadgetbridge Nightly No Pebble 銇仱銇勩仸</string>
+    <string name="gadgetbridge_running_main_nopebble">Gadgetbridge Nightly No Pebble 銇嫊浣滀腑銇с仚</string>
+    <string name="appmanager_item_outdated">(鍙ゃ亜銉愩兗銈搞儳銉�)</string>
+    <string name="uncheck_all_applications">銇欍伖銇︺伄銈€儣銉倰鏈⒑瑾嶃伀銇欍倠</string>
+    <string name="about_description_generic">銉°兗銈兗銇屾彁渚涖仚銈嬨偗銉兗銈恒儔銈姐兗銈广伄 Android 銈€儣銉伄浠f浛銇ㄣ仐銇﹂枊鐧恒仌銈屻仧銆併偗銉┿偊銉夈儸銈广仹銈炽償銉笺儸銉曘儓銇嚜鐢便仾銈€儣銉��</string>
+    <string name="permission_request">%1$s 銈掍娇銇嗐仺銆� Android 銇嬨倝銇傘仾銇熴伄銉囥儛銈ゃ偣銇儭銉冦偦銉笺偢銈勩仢銇粬銇儑銉笺偪銈掗�併倠銇撱仺銇屻仹銇嶃伨銇欍�傘儑銉笺偪銇搞伄銈€偗銈汇偣瑷卞彲銇屻仾銇勫牬鍚堛�併亾銇鑳姐伅姝c仐銇忓嫊浣溿仐銇亜鍙兘鎬с亴銇傘倞銇俱仚銆�
+\n
+\n鍕曚綔銇繀瑕併仾銈€偗銈汇偣瑷卞彲銈掕姹傘仚銈嬨儉銈ゃ偄銉偘銇岃〃绀恒仌銈屻伨銇欍��
+\n
+\n\'%2$s\' 銈掋偪銉冦儣銇椼仸閫层伩銇俱仚銆�</string>
+    <string name="permission_location">%1$s 銇�佺敾闈€亴銈儠銇姸鎱嬨仹銈傘偊銈┿儍銉併仺銇帴缍氥倰缍寔銇欍倠銇熴倎銇儛銉冦偗銈般儵銈︺兂銉夈仹浣嶇疆鎯呭牨銇偄銈偦銈广仚銈嬫ī闄愩亴蹇呰銇с仚銆� \n \n\'%2$s\' 銈掋偪銉冦儣銇椼仸鍚屾剰銇椼伨銇欍��</string>
+    <string name="permission_granting_mandatory">銇撱倢銈夈仚銇广仸銇ū鍙亴銇亼銈屻伆銆併偄銉椼儶銇屾銇椼亸鍕曚綔銇椼仾銇勫彲鑳芥�с亴銇傘倞銇俱仚</string>
+    <string name="gadgetbridge_running_banglejs_main">Bangle.js 銇嫊浣滀腑銇с仚</string>
+    <string name="about_description_banglejs_nightly">Gadgetbridge 銉椼儹銈搞偋銈儓涓娿伀妲嬬瘔銇曘倢銇� Bangle.js 銇� Android 銈炽兂銉戙儖銈兂 銈€儣銉�傘偆銉炽偪銉笺儘銉冦儓 銈€偗銈汇偣銇岃拷鍔犮仌銈屻仸銇勩伨銇欍��
+\n
+\nGoogle Play 銈广儓銈€伄銉濄儶銈枫兗銇倛銈娿�併偄銉椼儶鑷綋銇瘎浠樸儶銉炽偗銈掍娇鐢ㄣ仚銈嬨亾銇ㄣ伅銇с亶銇俱仜銈撱亴銆併亾銇偄銉椼儶銈掓皸銇叆銇c仸銇勩仧銇犮亼銇俱仐銇熴倝銆佷互涓嬨伄 Gadgetbridge 銉涖兗銉犮儦銉笺偢銇嬨倝瀵勪粯銈掋仈妞滆◣銇忋仩銇曘亜銆�</string>
+    <string name="about_description_main_nightly">銉°兗銈兗銇屾彁渚涖仚銈嬨偗銉兗銈恒儔銈姐兗銈广伄 Android 銈€儣銉伄浠f浛銇ㄣ仐銇﹂枊鐧恒仌銈屻仧銆併偗銉┿偊銉夈儸銈广仹銈炽償銉笺儸銉曘儓銇嚜鐢便仾銈€儣銉�侴adgetbridge 銇� Nightly 銉儶銉笺偣銆傘仚銇с伀 Gadgetbridge 銇� Pebble 銈€儣銉亴銈ゃ兂銈广儓銉笺儷銇曘倢銇︺亜銈嬪牬鍚堛�丳ebble provider 銇鍚堛伄銇熴倎銈ゃ兂銈广儓銉笺儷銇с亶銇俱仜銈撱��</string>
+    <string name="about_description_main_nopebble">銉°兗銈兗銇屾彁渚涖仚銈嬨偗銉兗銈恒儔銈姐兗銈广伄 Android 銈€儣銉伄浠f浛銇ㄣ仐銇﹂枊鐧恒仌銈屻仧銆併偗銉┿偊銉夈儸銈广仹銈炽償銉笺儸銉曘儓銇嚜鐢便仾銈€儣銉�傘亾銇儛銉笺偢銉с兂銇鍚堛倰閬裤亼銈嬨仧銈併伀 Pebble provider 銇悕鍓嶃亴澶夋洿銇曘倢銇︺亜銈嬨仧銈� Pebble 闁㈤�c伄姗熻兘銇嫊浣溿仐銇俱仜銈撱亴銆佹棦瀛樸伄 Gadgetbridge 銇ㄤ竴绶掋伀銈ゃ兂銈广儓銉笺儷銇欍倠銇撱仺銇屻仹銇嶃伨銇欍��</string>
+    <string name="single_band">銈枫兂銈般儷銉愩兂銉�</string>
+    <string name="dual_band">銉囥儱銈€儷銉愩兂銉�</string>
+    <string name="gadgetbridge_running_banglejs_nightly">Nightly Bangle.js 銇嫊浣滀腑銇с仚</string>
+    <string name="permission_display_over_other_apps">%1$s 銇�� %1$s 銇屻儛銉冦偗銈般儵銈︺兂銉夈仹鍕曚綔銇椼仸銇勩倠銇ㄣ亶銇� Bangle.js 銈︺偐銉冦儊銇屻偆銉炽儐銉炽儓銈掔祵鐢便仐銇︺偄銈儐銈c儞銉嗐偅銈掗枊濮嬨仚銈嬨仧銈併伀銆佷粬銇偄銉椼儶銇笂銇噸銇仸琛ㄧず銇欍倠妯╅檺銇屽繀瑕併仹銇欍��
+\n
+\n銇撱倢銇倛銇c仸銆侀煶妤姐偄銉椼儶銈掕捣鍕曘仐銇﹂煶妤姐倰鍐嶇敓銇椼仧銈娿�併仢銇粬銇勩倣銇勩倣銇亾銇ㄣ亴銇с亶銇俱仚銆�
+\n
+\n\'%2$s\' 銈掋偪銉冦儣銇椼�� \'%1$s\' 銈掗伕鎶炪仐銇� \'浠栥伄銈€儣銉伄涓娿伀閲嶃伃銇﹁〃绀恒仹銇嶃倠銈堛亞銇仚銈媆' 銈掓湁鍔广伀銇椼�佹埢銈嬨倰銈裤儍銉椼仐銇� %1$s 銇埢銇c仸銇忋仩銇曘亜銆�
+\n
+\n%1$s 銇屻亾銇ī闄愩倰姹傘倎銈嬨伄銈掑仠姝€仚銈嬨伀銇� Gadgetbridge 銇ō瀹氥倰闁嬨亶銆� \'妯╅檺銇姸鎱嬨倰纰鸿獚銇欍倠\' 銈掋偑銉曘伀銇椼仸銇忋仩銇曘亜銆�
+\n
+\n銈€儣銉倰鏈熷緟閫氥倞銇嫊浣溿仌銇涖倠銇伅銆佸繀瑕併仾妯╅檺銈� %1$s 銇笌銇堛仸銇忋仩銇曘亜銆�</string>
+    <string name="device_experimental">瑭﹂〒鐨�</string>
+    <string name="debugactivity_confirm_remove_device_preferences_title">銉囥儛銈ゃ偣銇挵澧冭ō瀹氥倰鍓婇櫎銇椼伨銇欍亱锛�</string>
+    <string name="device_unsupported">鏈偟銉濄兗銉�</string>
+    <string name="open_fw_installer_getting_files_text">銉曘偂銉笺儬銈︺偋銈� 銉曘偂銈ゃ儷銇嚜韬仹鍏ユ墜銇欍倠蹇呰銇屻亗銈娿伨銇欍�侫pk 銉曘偂銈ゃ儷銆併偆銉炽偪銉笺儘銉冦儓銆併儠銈┿兗銉┿儬銆丄mazfitwatchfaces锛圡iband/Amazfit 銉囥儛銈ゃ偣鍚戙亼锛夈仾銇┿仹銉曘偂銈ゃ儷銈掓帰銇欏繀瑕併亴銇傘倞銇俱仚銆�</string>
+    <string name="error_background_service_reason_truncated">浠ヤ笅銇悊鐢便仹銉愩儍銈偘銉┿偊銉炽儔銈点兗銉撱偣銇枊濮嬨伀澶辨晽銇椼伨銇椼仧鈥�</string>
+    <string name="action_changelog">Changelog</string>
+    <string name="prefs_activity_in_device_card_sleep_title_summary">鐫$湢銇湡闁撱倰琛ㄧず銇欍倠</string>
+    <string name="error_version_check_extreme_caution">娉ㄦ剰锛氥儛銉笺偢銉с兂鎯呭牨銇⒑瑾嶄腑銇偍銉┿兗銇岀櫤鐢熴仐銇俱仐銇熴�傜稓琛屻仐銇亜銇с亸銇犮仌銇勩�傘儛銉笺偢銉с兂鍚嶃伅 \"%s\" 銇с仚</string>
+    <string name="open_fw_installer_select_file">銉囥儛銈ゃ偣銇偄銉冦儣銉兗銉夈仐銇熴亜銉曘偂銈ゃ儷銈掗伕鎶烇細 %s</string>
+    <string name="device_is_currently_bonded">鏃€伀鎺ョ稓娓堛伩銇с仚</string>
+    <string name="error_background_service_reason">銉愩儍銈偘銉┿偊銉炽儔銈点兗銉撱偣銇枊濮嬨伅渚嬪銇仧銈佸け鏁椼仐銇俱仐銇熴�傘偍銉┿兗锛�</string>
+    <string name="error_background_service">銉愩儍銈偘銉┿偊銉炽儔銈点兗銉撱偣銇枊濮嬨伀澶辨晽銇椼伨銇椼仧</string>
+    <string name="require_location_provider">浣嶇疆鎯呭牨銈掓湁鍔广伀銇欍倠蹇呰銇屻亗銈娿伨銇�</string>
+    <string name="pref_check_permission_status">妯╅檺銇姸鎱嬨倰纰鸿獚銇欍倠</string>
+    <string name="strokes_minute">銈广儓銉兗銈�/绉�</string>
+    <string name="aerobicTrainingEffect">鏈夐吀绱犮伄鍔规灉</string>
+    <string name="pref_show_changelog">璧峰嫊鏅傘伀 Changelog 銈掕〃绀�</string>
+    <string name="pref_show_changelog_summary">Gadgetbridge 銇屾洿鏂般仌銈屻仧寰屻伀鍓嶃儛銉笺偢銉с兂銇嬨倝銇鏇村饱姝淬倰琛ㄧず銇椼伨銇�</string>
+    <string name="laps_unit">鍛�</string>
+    <string name="bpm">bpm</string>
+    <string name="Strokes">銈广儓銉兗銈�</string>
+    <string name="qhybrid_pref_summary_actions">Commute 銈€儣銉伄銈€偗銈枫儳銉�</string>
+    <string name="button_watchface_preview">銈︺偐銉冦儊銇ц│銇�</string>
+    <string name="watchface_dialog_widget_cat_position">浣嶇疆</string>
+    <string name="qhybrid_title_background_image">鑳屾櫙鐢诲儚</string>
+    <string name="qhybrid_title_watchface">銈︺偐銉冦儊銉曘偋銈ゃ偣銇ō瀹�</string>
+    <string name="qhybrid_pref_title_external_intents">鍗遍櫤銇閮ㄣ偆銉炽儐銉炽儓銈掕ū鍙�</string>
+    <string name="discover_unsupported_devices">鏈偟銉濄兗銉堛伄銉囥儛銈ゃ偣銈傝〃绀恒仚銈�</string>
+    <string name="maxStride">鏈�澶ф骞�</string>
+    <string name="HeartRateZones">蹇冩媿銈俱兗銉�</string>
+    <string name="hrZoneFatBurn">鑴傝偑鐕冪劶</string>
+    <string name="watchface_dialog_widget_cat_generic">鍏ㄨ埇</string>
+    <string name="hrZoneWarmUp">銈︺偐銉笺儫銉炽偘銈€儍銉�</string>
+    <string name="activity_detail_duration_label">鏈熼枔</string>
+    <string name="qhybrid_title_apps">銈€儣銉�</string>
+    <string name="button_watchface_add_widget">銈︺偅銈搞偋銉冦儓銇拷鍔�</string>
+    <string name="TrainingEffect">銉堛儸銉笺儖銉炽偘銇姽鏋�</string>
+    <string name="fossil_hr_edit_action">銈€偗銈枫儳銉炽伄绶ㄩ泦</string>
+    <string name="pref_summary_developer_settings">闁嬬櫤鑰呭悜銇戙伄瑷畾銇ㄦ鑳�</string>
+    <string name="devicetype_amazfit_trex_pro">Amazfit T-Rex Pro</string>
+    <string name="watchface_widget_type_date">鏃ヤ粯</string>
+    <string name="watchface_widget_type_steps">姝╂暟</string>
+    <string name="watchface_widget_type_heart_rate">蹇冩媿鏁�</string>
+    <string name="qhybrid_pref_summary_external_intents">浠栥伄 Android 銈€儣銉亱銈夈儠銈°偆銉倰銈€儍銉椼儹銉笺儔/涓婃浉銇嶃仚銈嬨亾銇ㄣ倰瑷卞彲銇椼伨銇�</string>
+    <string name="permission_notification_listener">%1$s 銇�丄ndroid 銇敾闈€亴銈儠銇仺銇嶃伀銈傘偊銈┿儍銉併伀閫氱煡銈掕〃绀恒仚銈嬨仧銈併伀銆侀�氱煡銇偄銈偦銈广仚銈嬪繀瑕併亴銇傘倞銇俱仚銆�
+\n
+\n\'%2$s\' 銈掋偪銉冦儣銇椼�� \'%1$s\' 銈掗伕鎶炪仐銇� \'閫氱煡銇搞伄銈€偗銈汇偣瑷卞彲\' 銈掓湁鍔广伀銇椼�� 鎴汇倠銉溿偪銉炽倰銈裤儍銉椼仐銇� %1$s 銇埢銇c仸銇忋仩銇曘亜銆�</string>
+    <string name="permission_notification_policy_access">%1$s 銇�� Android 銇屻偑銉曘伄銇ㄣ亶銇倐銈︺偐銉冦儊銇с偟銈ゃ儸銉炽儓 銉€兗銉夈倰鏈夊姽銇仚銈嬨仧銈併伀銆併偟銈ゃ儸銉炽儓 銉€兗銉夈伄瑷畾銇偄銈偦銈广仚銈嬪繀瑕併亴銇傘倞銇俱仚銆�
+\n
+\n\'%2$s\' 銈掋偪銉冦儣銇椼�� \'%1$s\' 銈掗伕鎶炪仐銇� \'銈点偆銉兂銉� 銉€兗銉夈伄鍒╃敤銈掕ū鍙痋' 銈掓湁鍔广伀銇椼�� 鎴汇倠銉溿偪銉炽倰銈裤儍銉椼仐銇� %1$s 銇埢銇c仸銇忋仩銇曘亜銆�</string>
+    <string name="minHR">鏈�灏忓績鎷嶆暟</string>
+    <string name="seconds_km">绉�/km</string>
+    <string name="seconds_m">绉�/m</string>
+    <string name="calories_unit">kcal</string>
+    <string name="qhybrid_summary_calibration">銈︺偐銉冦儊銇嚌銈掕姝c仐銇俱仚</string>
+    <string name="Speed">閫熷害</string>
+    <string name="watchface_widget_type_weather">澶╂皸</string>
+    <string name="minCadence">鏈�灏忋偙銈ゃ儑銉炽偣</string>
+    <string name="spm">姝�/min</string>
+    <string name="averageAltitude">骞冲潎</string>
+    <string name="minSpeed">鏈�灏�</string>
+    <string name="hrZoneNa">鍒╃敤涓嶅彲</string>
+    <string name="hrZoneAnaerobic">鐒¢吀绱�</string>
+    <string name="anaerobicTrainingEffect">鐒¢吀绱犮伄鍔规灉</string>
+    <string name="maximumOxygenUptake">鏈�澶ч吀绱犳憘鍙栭噺</string>
+    <string name="minStride">鏈�灏忔骞�</string>
+    <string name="maxCadence">鏈�澶с偙銈ゃ儑銉炽偣</string>
+    <string name="discover_unsupported_devices_description">銇撱伄銈儣銈枫儳銉炽倰鏈夊姽銇仚銈嬨仺銆佹鍑恒仌銈屻仧銇欍伖銇︺伄 Bluetooth 銉囥儛銈ゃ偣銈掕〃绀恒仐銇俱仚銆傜煭銇忋偪銉冦儣銇欍倠銇ㄣ�併儑銉愩偆銈瑰悕銇� MAC 銈€儔銉偣銈掋偗銉儍銉椼儨銉笺儔銇偝銉斻兗銇椼伨銇欍�傞暦銇忋偪銉冦儣銇欍倠銇ㄣ�乗'銉嗐偣銉堛儑銉愩偆銈广伄杩藉姞\' 銉�銈ゃ偄銉偘銇岃〃绀恒仌銈屻伨銇欍�傘偄銉椼儶銇屽仠姝€仚銈嬪彲鑳芥�с亴銇傘倞銇俱仚銆�</string>
+    <string name="hrZoneExtreme">銈ㄣ偗銈广儓銉兗銉�</string>
+    <string name="currentWorkoutLoad">銉兗銈偄銈︺儓銇矤鑽�</string>
+    <plurals name="widget_alarm_target_hours">
+        <item quantity="other">%d 鏅傞枔</item>
+    </plurals>
+    <string name="qhybrid_summary_file_management">銉曘偂銈ゃ儷銈掋偄銉冦儣銉兗銉�/銉�銈︺兂銉兗銉夈仐銇俱仚</string>
+    <string name="descentMeters">涓嬨倞鍧�</string>
+    <string name="ascentDistance">涓娿倞鍧傘伄璺濋洟</string>
+    <string name="strokes_unit">銈广儓銉兗銈�</string>
+    <string name="swolf_index">SWOLF 鎸囨暟</string>
+    <string name="minutes_km">鍒�/km</string>
+    <string name="swim_style">娉虫硶</string>
+    <string name="qhybrid_title_file_management">銉曘偂銈ゃ儷銇鐞�</string>
+    <string name="button_watchface_settings">銈︺偐銉冦儊銉曘偋銈ゃ偣銇ō瀹�</string>
+    <string name="watchface_dialog_title_set_name">銈︺偐銉冦儊銉曘偋銈ゃ偣銇悕鍓嶃倰浠樸亼銈�</string>
+    <string name="button_watchface_save_apply">淇濆瓨銇椼仸閬╃敤</string>
+    <string name="appmanager_app_edit">绶ㄩ泦</string>
+    <string name="error_exporting_device_preferences">銉囥儛銈ゃ偣鍥烘湁瑷畾銇偍銈偣銉濄兗銉堜腑銇偍銉┿兗銇岀櫤鐢熴仐銇俱仐銇�</string>
+    <string name="error_setting_alias">銈ㄣ偆銉偄銈广伄瑷畾涓伀銈ㄣ儵銉笺亴鐧虹敓銇椼伨銇椼仧锛�</string>
+    <string name="averageStride">骞冲潎姝╁箙</string>
+    <string name="averageCadence">骞冲潎銈便偆銉囥兂銈�</string>
+    <string name="maxHR">鏈�澶у績鎷嶆暟</string>
+    <string name="swolfIndex">SWOLF</string>
+    <string name="swimStyle">娉虫硶</string>
+    <string name="laneLength">銉兗銉抽暦</string>
+    <string name="swingAngleAvg">骞冲潎銈广偆銉炽偘瑙掑害</string>
+    <string name="unknownDataEncountered">涓嶆槑銇儑銉笺偪銇с仚</string>
+    <string name="strokes_second">銈广儓銉兗銈�/绉�</string>
+    <string name="ft">ft</string>
+    <string name="yard">yard</string>
+    <string name="hrZoneAerobic">鏈夐吀绱�</string>
+    <string name="mi">mi</string>
+    <string name="minPace">鏈�閬呫伄銉氥兗銈�</string>
+    <string name="activity_detail_start_label">闁嬪</string>
+    <string name="activity_detail_end_label">绲備簡</string>
+    <string name="Swimming">銈广偆銉熴兂銈�</string>
+    <string name="prefs_operating_sounds">鎿嶄綔闊�</string>
+    <string name="qhybrid_title_calibration">瑁滄</string>
+    <string name="qhybrid_pref_title_actions">銈€偗銈枫儳銉�</string>
+    <string name="button_watchface_select_image">鑳屾櫙鐢诲儚銇鏇�</string>
+    <string name="watchface_dialog_widget_cat_2nd_tz_widget">2鐣洰銇偪銈ゃ儬銈俱兗銉炽伄銈︺偅銈搞偋銉冦儓</string>
+    <string name="watchface_dialog_widget_cat_custom_widget">銈偣銈裤儬 銈︺偅銈搞偋銉冦儓</string>
+    <string name="watchface_dialog_widget_type">绋</string>
+    <string name="watchface_dialog_widget_x_coordinate">X 搴ф锛堟渶澶у�� 240锛�</string>
+    <string name="pref_check_permission_status_summary">鐩淬仭銇繀瑕併亴銇亜鍫村悎銇с亗銇c仸銈傘�佷笉瓒炽仐銇︺亜銈嬫ī闄愩倰銉併偋銉冦偗銇椼仸瑷卞彲銈掓眰銈併伨銇欍�傘儑銉愩偆銈广亴妯╅檺銈掑繀瑕併仺銇欍倠姗熻兘銇蹇溿仐銇︺亜銇亜銇ㄣ亶銇伄銇跨劇鍔广伀銇椼仸銇忋仩銇曘亜銆傛ī闄愩亴銇亜銇ㄥ晱椤屻亴鐧虹敓銇欍倠鍫村悎銇屻亗銈娿伨銇欙紒</string>
+    <string name="baseAltitude">鍩烘簴妯欓珮</string>
+    <string name="qhybrid_calibration_align_hint">浠ヤ笅銇儨銈裤兂銈掍娇銇c仸銈︺偐銉冦儊銇嚌銈� 12:00 銇悎銈忋仜銇︺亸銇犮仌銇勩��</string>
+    <string name="qhybrid_watchface_configuration_old_firmware">銉曘偂銉笺儬銈︺偋銈� 銉愩兗銈搞儳銉� DN1.0.2.19r 浠ヤ笅 銇偊銈┿儍銉佸悜銇戙伄銈︺偐銉冦儊銉曘偋銈ゃ偣瑷畾鐢婚潰</string>
+    <string name="button_watchface_edit_name">鍚嶅墠銇法闆�</string>
+    <string name="watchface_dialog_widget_y_coordinate">Y 搴ф锛堟渶澶у�� 240锛�</string>
+    <string name="ascentMeters">涓娿倞鍧�</string>
+    <string name="descentDistance">涓嬨倞鍧傘伄璺濋洟</string>
+    <string name="flatDistance">骞冲湴銇窛闆�</string>
+    <string name="elevationLoss">鍠け妯欓珮</string>
+    <string name="totalStride">鍚堣▓姝╁箙</string>
+    <string name="laps">銉┿儍銉�</string>
+    <string name="error_location_enabled_mandatory">銉囥儛銈ゃ偣銈掋偣銈儯銉炽仚銈嬨伀銇綅缃儏鍫便倰鏈夊姽銇仚銈嬪繀瑕併亴銇傘倞銇俱仚</string>
+    <string name="elevationGain">鐛插緱妯欓珮</string>
+    <string name="swolfAvg">骞冲潎 SWOLF</string>
+    <string name="swolfMax">鏈�澶� SWOLF</string>
+    <string name="swolfMin">鏈�灏� SWOLF</string>
+    <string name="averageSpeed">骞冲潎閫熷害</string>
+    <string name="groundContactTimeAvg">骞冲潎鎺ュ湴鏅傞枔</string>
+    <string name="mi_h">mi/h</string>
+    <string name="minutes_mi">min/mi</string>
+    <string name="milliseconds">銉熴儶绉�</string>
+    <string name="km">km</string>
+    <string name="degrees">搴�</string>
+    <string name="Pace">銉氥兗銈�</string>
+    <string name="Elevation">楂樺害</string>
+    <string name="Activity">銈€偗銉嗐偅銉撱儐銈�</string>
+    <string name="Steps">姝╂暟</string>
+    <string name="RunningForm">銉┿兂銉嬨兂銈般儠銈┿兗銉�</string>
+    <string name="watchface_dialog_widget_color">鑹�</string>
+    <string name="watchface_dialog_widget_preset_left">宸�</string>
+    <string name="watchface_dialog_widget_color_black">榛�</string>
+    <string name="watchface_dialog_title_widget">銈︺偅銈搞偋銉冦儓銇ō瀹�</string>
+    <string name="watchface_dialog_widget_width">銈︺偅銈搞偋銉冦儓銇箙锛堛償銈偦銉暟锛�</string>
+    <string name="watchface_setting_title_display_refresh_timeout">鐢婚潰鏇存柊銇檪闁�</string>
+    <string name="watchface_setting_display_refresh_partial">涓�閮ㄦ洿鏂帮紙鍒嗭級</string>
+    <string name="hybridhr_widget_bg_dashed_circle">鐮寸窔銇唵</string>
+    <string name="devicetype_galaxybuds_pro">Galaxy Buds Pro</string>
+    <string name="watchface_dialog_widget_preset_right">鍙�</string>
+    <string name="watchface_setting_display_refresh_full">鍏ㄦ洿鏂帮紙鍒嗭級</string>
+    <string name="devicetype_nothingear2">Nothing Ear (2)</string>
+    <string name="watchface_widget_type_chance_rain">闄嶆按纰虹巼</string>
+    <string name="devicetype_galaxybuds_2">Galaxy Buds2</string>
+    <string name="hybridhr_widget_bg_thin_circle">绱般亜鍐�</string>
+    <string name="watchface_setting_button_toggle_widgets">銈︺偅銈搞偋銉冦儓銇垏銈婃浛銇�</string>
+    <string name="watchface_setting_button_move_hands">閲濄倰鍕曘亱銇�</string>
+    <string name="watchface_widget_type_calories">銈儹銉兗</string>
+    <string name="watchface_setting_power_saving_display">瑁呯潃銇椼仸銇勩仾銇勯枔銇敾闈€倰鏇存柊銇椼仾銇�</string>
+    <string name="hybridhr_widget_bg_double_circle">浜岄噸銇唵</string>
+    <string name="watchface_setting_power_saving_hands">瑁呯潃銇椼仸銇勩仾銇勯枔銇嚌銇嫊銇嶃倰鐒″姽鍖栥仚銈�</string>
+    <string name="watchface_dialog_widget_color_white">鐧�</string>
+    <string name="watchface_dialog_title_settings">銈︺偐銉冦儊銉曘偋銈ゃ偣銇ō瀹�</string>
+    <string name="watchface_widget_type_2nd_tz">2鐣洰銇偪銈ゃ儬銈俱兗銉�</string>
+    <string name="watchface_setting_title_custom_events">銈偣銈裤儬 銈ゃ儥銉炽儓</string>
+    <string name="watchface_setting_button_toggle_backlight">銉愩儍銈儵銈ゃ儓銈掔偣鐏�</string>
+    <string name="watchface_widget_type_uv_index">绱绶氭寚鏁�</string>
+    <string name="watchface_setting_title_power_saving">鐪侀浕鍔�</string>
+    <string name="watchface_dialog_widget_background">鏋�</string>
+    <string name="watchface_upload_failed">銈︺偐銉冦儊銉曘偋銈ゃ偣銇偄銉冦儣銉兗銉夈伀澶辨晽銇椼伨銇椼仧銆傚啀瑭﹁銇椼仸銇忋仩銇曘亜銆�</string>
+    <string name="watchface_cache_confirm_overwrite">鍚屻仒鍚嶅墠銇偊銈┿儍銉併儠銈с偆銈广亴鏃€伀瀛樺湪銇椼伨銇欍�備笂鏇搞亶銇椼伨銇欍亱锛�</string>
+    <string name="qhybrid_title_watchface_designer">銈︺偐銉冦儊銉曘偋銈ゃ偣銇法闆�</string>
+    <string name="watchface_dialog_widget_presets">浣嶇疆銇儣銉偦銉冦儓</string>
+    <string name="devicetype_nothingear1">Nothing Ear (1)</string>
+    <string name="devicetype_nothingearstick">Nothing Ear (Stick)</string>
+    <string name="devicetype_nothing_cmf_watch_pro">CMF Watch Pro</string>
+    <string name="devicetype_galaxybuds">Galaxy Buds</string>
+    <string name="devicetype_galaxybuds_live">Galaxy Buds Live</string>
+    <string name="watchface_dialog_widget_preset_top">涓�</string>
+    <string name="watchface_dialog_widget_preset_bottom">涓�</string>
+    <string name="watchface_widget_type_battery">銉愩儍銉嗐儶銉�</string>
+    <string name="devicetype_galaxybuds_2_pro">Galaxy Buds2 Pro</string>
+    <string name="watchface_widget_type_active_mins">娲诲嫊鏅傞枔</string>
+    <string name="prefs_game_mode">銈层兗銉犮儮銉笺儔</string>
+    <string name="pref_title_equalizer_dynamic">銉�銈ゃ儕銉熴儍銈�</string>
+    <string name="nothing_prefs_audiomode_title">銈兗銉囥偅銈� 銉€兗銉�</string>
+    <string name="nothing_prefs_inear_title">瑁呯潃妞滃嚭</string>
+    <string name="nothing_prefs_inear_summary">銈ゃ儰銉涖兂銈掕鐫�銇椼仸銇勩倠銇嬨仼銇嗐亱銇ч煶妤姐倰鍐嶇敓/鍋滄銇椼伨銇�</string>
+    <string name="pref_title_equalizer_soft">銈姐儠銉�</string>
+    <string name="prefs_game_mode_summary">Android 銇屻偛銉笺儬銉€兗銉夈伀瀵惧繙銇椼仸銇勩倠鍫村悎銇伩</string>
+    <string name="prefs_touch_lock">銈裤儍銉併倰銉栥儹銉冦偗</string>
+    <string name="prefs_ambient_voice_summary">澹般倰闅涚珛銇熴仜銇俱仚</string>
+    <string name="prefs_ambient_volume_right">銈€兂銉撱偍銉炽儓 銉溿儶銉ャ兗銉狅紙鍙筹級</string>
+    <string name="prefs_ambient_voice_focus">闊冲0銉曘偐銉笺偒銈�</string>
+    <string name="prefs_ambient_volume">銈€兂銉撱偍銉炽儓 銉溿儶銉ャ兗銉�</string>
+    <string name="prefs_equalizer_preset">銉椼儶銈汇儍銉堛伄銈ゃ偝銉┿偆銈躲兗</string>
+    <string name="prefs_equalizer">銈ゃ偝銉┿偆銈躲兗</string>
+    <string name="prefs_equalizer_summary">銈ゃ偝銉┿偆銈躲兗銈掓湁鍔�/鐒″姽銇仐銇俱仚</string>
+    <string name="prefs_dolby_mode">Dolby 銉€兗銉�</string>
+    <string name="prefs_dolby_summary">銈ゃ偝銉┿偆銈躲兗銇� Dolby 銉椼儶銈汇儍銉�</string>
+    <string name="pref_title_equalizer_bass_boost">浣庨煶銉栥兗銈广儓</string>
+    <string name="pref_title_equalizer_clear">銈儶銈�</string>
+    <string name="pref_title_equalizer_trebble">楂橀煶銉栥兗銈广儓</string>
+    <string name="prefs_customize_ambient_sound_summary">澶栭煶鍙栥倞杈笺伩姗熻兘銈掋偒銈广偪銉炪偆銈恒仐銇俱仚</string>
+    <string name="prefs_ambient_sound_during_call_title">閫氳┍涓伄澶栭煶鍙栥倞杈笺伩</string>
+    <string name="prefs_ambient_sound_during_call_summary">閫氳┍涓伀鑷垎銇0銇屻伅銇c亶銈婅仦銇撱亪銈嬨倛銇嗐伀銇椼伨銇�</string>
+    <string name="prefs_ambient_sound">澶栭煶鍙栥倞杈笺伩</string>
+    <string name="prefs_touch_lock_buds2">銈裤儍銉� 銈炽兂銉堛儹銉笺儷</string>
+    <string name="prefs_touch_lock_summary">銈裤儍銉佹搷浣溿倰鐒″姽銇仐銇俱仚</string>
+    <string name="prefs_seamless_connection_switch_title">浠栥伄銉囥儛銈ゃ偣銇嚜鍕曠殑銇垏銈婃彌銇堛倠</string>
+    <string name="prefs_seamless_connection_switch_summary">銉氥偄銉兂銈版笀銇裤伄銉囥儛銈ゃ偣闁撱仹鑷嫊鐨勩伀鎺ョ稓鍏堛倰鍒囥倞鏇裤亪銇俱仚</string>
+    <string name="prefs_ambient_volume_left">銈€兂銉撱偍銉炽儓 銉溿儶銉ャ兗銉狅紙宸︼級</string>
+    <string name="prefs_ambient_settings_title">澶栭煶鍙栥倞杈笺伩姗熻兘銇ō瀹�</string>
+    <string name="prefs_active_noise_cancelling">銈€偗銉嗐偅銉栥儙銈ゃ偤銈儯銉炽偦銉兂銈�</string>
+    <string name="prefs_active_noise_cancelling_light">杌藉害銇偄銈儐銈c儢銉庛偆銈恒偔銉c兂銈汇儶銉炽偘</string>
+    <string name="prefs_ambient_mode">澶栭煶鍙栥倞杈笺伩銉€兗銉�</string>
+    <string name="prefs_galaxy_buds_experimental">瑭﹂〒鐨�</string>
+    <string name="pref_title_equalizer_normal">銉庛兗銉炪儷</string>
+    <string name="prefs_right">鍙�</string>
+    <string name="pref_title_touch_spotify_galaxy_app">Spotify (Galaxy Wearable 銈€儣銉伄銇�)</string>
+    <string name="pref_voice_detect_duration_5">5 绉�</string>
+    <string name="left_earbud">銈ゃ儰銉涖兂 (宸�)</string>
+    <string name="pref_header_spo2">琛�涓吀绱�</string>
+    <string name="pref_title_touch_voice_assistant">銉溿偆銈广偄銈枫偣銈裤兂銉�</string>
+    <string name="prefs_active_noise_cancelling_transparency">閫忛亷搴�</string>
+    <string name="right_earbud">銈ゃ儰銉涖兂 (鍙�)</string>
+    <string name="prefs_active_noise_cancelling_level">銈€偗銉嗐偅銉栥儙銈ゃ偤銈儯銉炽偦銉伄銉儥銉�</string>
+    <string name="prefs_pressure_relief">銈堛倞鑷劧銇闊冲彇銈婅炯銇�</string>
+    <string name="pressure_relief_summary">銈€偗銉嗐偅銉栥儙銈ゃ偤銈儯銉炽偦銉倰浣跨敤銇椼仸銇勩仾銇勩仺銇嶃伄鑰炽伕銇湩杩劅銈掗槻銇庛伨銇�</string>
+    <string name="pref_header_heartrate_sleep">鐫$湢</string>
+    <string name="battery_case">銉愩儍銉嗐儶銉笺偙銉笺偣</string>
+    <string name="pref_title_touch_spotify">Spotify</string>
+    <string name="pref_title_touch_ambient">鍛ㄥ洸銇煶</string>
+    <string name="prefs_voice_detect_duration">绲備簡銇欍倠銇俱仹銇劇闊崇姸鎱嬨伄鏅傞枔:</string>
+    <string name="pref_title_touch_anc">銈€偗銉嗐偅銉栥儙銈ゃ偤銈儯銉炽偦銉兂銈�</string>
+    <string name="pref_voice_detect_duration_10">10 绉�</string>
+    <string name="prefs_active_noise_cancelling_summary">鍛ㄥ洸銇洃闊炽倰閬柇銇椼伨銇�</string>
+    <string name="prefs_left">宸�</string>
+    <string name="pref_header_heartrate_alerts">蹇冩媿鏁般偄銉┿兗銉�</string>
+    <string name="audio_codec">銈兗銉囥偅銈偝銉笺儑銉冦偗</string>
+    <string name="pref_title_touch_volume">闊抽噺</string>
+    <string name="pref_header_stress">銈广儓銉偣</string>
+    <string name="prefs_active_noise_cancelling_level_high">寮�</string>
+    <string name="prefs_active_noise_cancelling_level_low">寮�</string>
+    <string name="pref_header_heartrate_allday">鍏ㄦ棩娓畾</string>
+    <string name="pref_header_sony_sound_control">銈点偊銉炽儔 銈炽兂銉堛儹銉笺儷</string>
+    <string name="pref_voice_detect_duration_15">15 绉�</string>
+    <string name="pref_header_sony_ambient_sound_control">澶栭煶銈炽兂銉堛儹銉笺儷</string>
+    <string name="pref_header_sony_device_info">銉囥儛銈ゃ偣鎯呭牨</string>
+    <string name="pref_title_touch_spotify_official_app">Spotify (鍏紡銈€儣銉伄銇�)</string>
+    <string name="sony_speak_to_chat_focus_on_voice">銉溿偆銈广儠銈┿兗銈偣</string>
+    <string name="sony_sound_position">闊虫ソ銇岃仦銇撱亪銈嬫柟鍚戙倰澶夋洿</string>
+    <string name="sony_ambient_sound_wind_noise_reduction">棰ㄣ儙銈ゃ偤浣庢笡</string>
+    <string name="sony_anc_optimize_confirmation_title">銉庛偆銈恒偔銉c兂銈汇儶銉炽偘銇渶閬╁寲</string>
+    <string name="sony_ambient_sound_ambient_sound">澶栭煶鍙栥倞杈笺伩</string>
+    <string name="sony_anc_optimize_title">鏈�閬╁寲</string>
+    <string name="sony_surround_mode_concert_hall">Concert Hall</string>
+    <string name="sony_warn_sbc_codec">娉ㄦ剰锛氥偆銈炽儵銈ゃ偠銉笺�侀煶妤姐亴鑱炪亾銇堛倠鏂瑰悜銇鏇淬�併偟銉┿偊銉炽儔(VPT)銇�丼BC 銈炽兗銉囥儍銈仹銇伩鍕曚綔銇椼伨銇欍��</string>
+    <string name="sony_anc_optimize_confirmation_description">銇勩仱銈傘仼銇娿倞瑁呯潃銇椼仸銇忋仩銇曘亜銆傝鐫�鐘舵厠銈勬皸鍦с亴澶夊寲銇椼仧銇ㄣ亶銇�佸啀搴﹀疅琛屻仐銇︺亸銇犮仌銇勩��</string>
+    <string name="sony_anc_optimizer_status_starting">闁嬪銇椼仸銇勩伨銇欌��</string>
+    <string name="sony_equalizer_preset_off">Off</string>
+    <string name="sony_equalizer_preset_excited">Excited</string>
+    <string name="sony_equalizer_preset_relaxed">Relaxed</string>
+    <string name="pref_header_sony_equalizer_preset_custom_2">Custom Preset 2</string>
+    <string name="sony_equalizer_band_16000">16k</string>
+    <string name="sony_equalizer_clear_bass">CLEAR BASS</string>
+    <string name="sony_audio_upsampling">DSEE HX</string>
+    <string name="pref_adaptive_volume_control_title">銈€儉銉椼儐銈c儢銉溿儶銉ャ兗銉犮偝銉炽儓銉兗銉�</string>
+    <string name="sony_speak_to_chat_summary">瑭便仐濮嬨倎銈嬨仺鑷嫊銇с儙銈ゃ偤銈儯銉炽偦銉兂銈般倰銈儠銇仐銇俱仚銆�</string>
+    <string name="sony_speak_to_chat_sensitivity">澹般伄妞滃嚭鎰熷害</string>
+    <string name="sony_speak_to_chat_sensitivity_auto">鑷嫊</string>
+    <string name="sony_speak_to_chat_sensitivity_low">浣庢劅搴�</string>
+    <string name="sony_notification_voice_guide">閫氱煡闊炽仺闊冲0銈偆銉�銉炽偣</string>
+    <string name="sony_automatic_power_off">鑷嫊闆绘簮銈儠</string>
+    <string name="sony_automatic_power_off_off">銈儠銇椼仾銇�</string>
+    <string name="sony_automatic_power_off_5_min">5 鍒�</string>
+    <string name="watchface_dialog_widget_timezone">銈裤偆銉犮偩銉笺兂</string>
+    <string name="devicetype_sony_wena3">SONY Wena 3</string>
+    <string name="withings_steel_hr">Withings Steel HR</string>
+    <string name="huawei_trusleep_title">HUAWEI TruSleep 鈩�</string>
+    <string name="sony_equalizer_band_6300">6.3k</string>
+    <string name="sony_automatic_power_off_30_min">30 鍒�</string>
+    <string name="sony_ambient_sound_off">銈儠</string>
+    <string name="sony_equalizer_preset_mellow">Mellow</string>
+    <string name="sony_equalizer_preset_bass_boost">Bass Boost</string>
+    <string name="pref_wide_area_tap_title">銉偆銉夈偍銉偄銈裤儍銉�</string>
+    <string name="sony_speak_to_chat_timeout_long">闀枫亜 (绱� 1 鍒�)</string>
+    <string name="pref_anc_optimizer_state_pressure">姘楀湩</string>
+    <string name="pref_wide_area_tap_summary">鑰冲懆杈恒伄椤斻伄銈裤儍銉椼倰瑾嶈瓨銇椼伨銇�</string>
+    <string name="sony_speak_to_chat_sensitivity_high">楂樻劅搴�</string>
+    <string name="devicetype_bose_qc35">Bose QC35</string>
+    <string name="sony_anc_optimizer_status_finished">绲備簡銇椼仸銇勩伨銇欌��</string>
+    <string name="sony_sound_position_rear_right">Rear Right</string>
+    <string name="sony_surround_mode_outdoor_stage">Outdoor Stage</string>
+    <string name="sony_equalizer_preset_custom_2">Custom 2</string>
+    <string name="pref_header_sony_equalizer_bands">Bands</string>
+    <string name="sony_equalizer_preset_vocal">Vocal</string>
+    <string name="sony_equalizer_preset_treble_boost">Treble Boost</string>
+    <string name="sony_equalizer_preset_manual">Manual</string>
+    <string name="pref_header_sony_equalizer_preset_custom_1">Custom Preset 1</string>
+    <string name="sony_speak_to_chat">銈广償銉笺偗銉汇儓銈ャ兓銉併儯銉冦儓</string>
+    <string name="sony_pause_when_taken_off">銉樸儍銉夈儧銉炽倰澶栥仐銇熴倝闊虫ソ銈掍竴鏅傚仠姝�</string>
+    <string name="double_tap">銉�銉栥儷銈裤儍銉�</string>
+    <string name="long_press">闀锋娂銇�</string>
+    <string name="watchface_widget_type_custom">銈偣銈裤儬銈︺偅銈搞偋銉冦儓</string>
+    <string name="sony_automatic_power_off_1_hour">1 鏅傞枔</string>
+    <string name="sony_automatic_power_off_when_taken_off">澶栥仐銇熴仺銇�</string>
+    <string name="sony_automatic_power_off_3_hour">3 鏅傞枔</string>
+    <string name="sony_equalizer_preset_custom_1">Custom 1</string>
+    <string name="sony_sound_position_front">Front</string>
+    <string name="sony_sound_position_off">Off</string>
+    <string name="sony_sound_position_front_left">Front Left</string>
+    <string name="sony_anc_optimizer_status_wearing_condition">瑁呯潃鐘舵厠銈掓脯瀹氥仐銇︺亜銇俱仚鈥�</string>
+    <string name="sony_anc_optimizer_status_analyzing">瑙f瀽銇椼仸銇勩伨銇欌��</string>
+    <string name="sony_speak_to_chat_timeout_short">鐭亜 (绱� 5 绉�)</string>
+    <string name="single_tap">銈裤儍銉�</string>
+    <string name="sony_ambient_sound_noise_cancelling">銉庛偆銈恒偔銉c兂銈汇儶銉炽偘</string>
+    <string name="sony_anc_optimizer_status_not_running">姝€伨銇c仸銇勩伨銇�</string>
+    <string name="sony_equalizer_preset_bright">Bright</string>
+    <string name="sony_connect_two_devices">2鍙般伄姗熷櫒銇ㄥ悓鏅傘伀鎺ョ稓</string>
+    <string name="sony_touch_sensor">銈裤儍銉併偦銉炽偟銉笺伄銈炽兂銉堛儹銉笺儷</string>
+    <string name="pref_adaptive_volume_control_summary">鍛ㄥ洸銇煶銇悎銈忋仜銇﹂煶閲忋倰鑷嫊銇ц鏁淬仐銇俱仚</string>
+    <string name="huawei_trusleep_summary_light">鏀瑰杽銇曘倢銇熺潯鐪犮儮銉嬨偪銉兂銈�</string>
+    <string name="sony_equalizer">銈ゃ偝銉┿偆銈躲兗</string>
+    <string name="triple_tap">銉堛儶銉椼儷銈裤儍銉�</string>
+    <string name="sony_surround_mode">銈点儵銈︺兂銉�(VPT)</string>
+    <string name="sony_surround_mode_off">Off</string>
+    <string name="sony_surround_mode_arena">Arena</string>
+    <string name="sony_surround_mode_club">Club</string>
+    <string name="continue_pressing">鎶笺仐缍氥亼銈�</string>
+    <string name="devicetype_vesc">VESC</string>
+    <string name="sony_ambient_sound">銉€兗銉�</string>
+    <string name="sony_ambient_sound_focus_voice">銉溿偆銈广儠銈┿兗銈偣</string>
+    <string name="sony_ambient_sound_level">澶栭煶鍙栥倞杈笺伩銇儸銉欍儷</string>
+    <string name="pref_header_sony_anc_optimizer">銉庛偆銈恒偔銉c兂銈汇儶銉炽偘銇渶閬╁寲</string>
+    <string name="sony_anc_optimize_description">銈裤儍銉椼仐銇︺儙銈ゃ偤銈儯銉炽偦銉兂銈般伄鏈�閬╁寲銈掗枊濮嬨仐銇俱仚銆�</string>
+    <string name="sony_sound_position_front_right">Front Right</string>
+    <string name="sony_sound_position_rear_left">Rear Left</string>
+    <string name="pref_adaptive_noise_cancelling_title">銈€儉銉椼儐銈c儢銈点偊銉炽儔銈炽兂銉堛儹銉笺儷</string>
+    <string name="pref_adaptive_noise_cancelling_summary">鍛ㄥ洸銇煶銇悎銈忋仜銇︺儙銈ゃ偤銈儯銉炽偦銉兂銈般伄寮枫仌銈掕嚜鍕曘仹瑾挎暣銇椼伨銇�</string>
+    <string name="sony_speak_to_chat_sensitivity_standard">妯欐簴</string>
+    <string name="sony_speak_to_chat_timeout_off">鑷嫊銇х祩浜嗐仐銇亜</string>
+    <string name="sony_speak_to_chat_timeout_standard">妯欐簴 (绱� 30 绉�)</string>
+    <string name="huawei_trusleep_summary">鐫$湢銇唱銇ㄥ懠鍚搞伄銉戙偪銉笺兂銈掋儶銈€儷銈裤偆銉犮仹娓畾銇椼伨銇欍��
+\n鐫$湢銉戙偪銉笺兂銈掑垎鏋愩仐銆�6銇ゃ伄鐫$湢瑾查銈掓纰恒伀瑷烘柇銇椼伨銇欍��</string>
+    <string name="sony_equalizer_preset_speech">Speech</string>
+    <string name="sony_anc_optimizer_status_atmospheric_pressure">姘楀湩銈掓脯瀹氥仐銇︺亜銇俱仚鈥�</string>
+    <string name="sony_equalizer_band_2500">2.5k</string>
+    <string name="sony_equalizer_band_1000">1k</string>
+    <string name="sony_equalizer_band_400">400</string>
+    <string name="sony_speak_to_chat_timeout">绲備簡銇欍倠銇俱仹銇檪闁�</string>
+    <string name="sony_quick_access_double_tap">Quick Access (銉�銉栥儷銈裤儍銉�)</string>
+    <string name="huawei_alarm_smart_description">銈广優銉笺儓璧峰簥銈掔劇鍔广伀銇椼仾銇勩仹銇忋仩銇曘亜銆�</string>
+    <string name="prefs_activity_recognition">銈€偗銉嗐偅銉撱儐銈f鍑恒伄瑷畾</string>
+    <string name="pref_activity_recognize_biking">鑷虎杌娿伄妞滃嚭</string>
+    <string name="pref_activity_recognize_walking">銈︺偐銉笺偔銉炽偘銇鍑�</string>
+    <string name="pref_activity_recognition_mode_auto">鑷嫊</string>
+    <string name="pref_navigation_app_osmand">OsmAnd(+)</string>
+    <string name="devicetype_xiaomi_watch_s1">Xiaomi Watch S1</string>
+    <string name="devicetype_mi_watch_color_sport">Mi Watch Color Sport</string>
+    <string name="sony_button_mode_off">Off</string>
+    <string name="devicetype_xiaomi_watch_s1_pro">Xiaomi Watch S1 Pro</string>
+    <string name="huawei_trusleep_warning">娉ㄦ剰锛氥亾銈屻倰鏈夊姽銇仚銈嬨仺 Gadgetbridge 銇潯鐪犮亴琛ㄧず銇曘倢銇亸銇倞銇俱仚锛佷簡鎵裤仚銈嬪牬鍚堛伅銇撱亾銈掋偪銉冦儣銇椼仸銇忋仩銇曘亜銆�</string>
+    <string name="pref_activity_recognition_mode_none">銇仐</string>
+    <string name="temperature_scale_celsius">鎽傛皬</string>
+    <string name="temperature_scale_fahrenheit">鑿皬</string>
+    <string name="fossil_hr_nav_app_not_installed_notify_title">銉娿儞銈层兗銈枫儳銉炽偄銉椼儶銇屻偊銈┿儍銉併伀銈ゃ兂銈广儓銉笺儷銇曘倢銇︺亜銇俱仜銈�</string>
+    <string name="pref_title_navigation_prefs">銉娿儞銈层兗銈枫儳銉炽伄瑷畾</string>
+    <string name="pref_navigation_app_gmaps">Google 銉炪儍銉�</string>
+    <string name="pref_title_navigation_apps">銉娿儞銈层兗銈枫儳銉炽偄銉椼儶</string>
+    <string name="serial_number">銈枫儶銈€儷銉娿兂銉愩兗</string>
+    <string name="fossil_hr_nav_app_not_installed_notify_text">銉娿儞銈层兗銈枫儳銉炽亴闁嬪銇曘倢銇俱仐銇熴亴銆併儕銉撱偛銉笺偡銉с兂銈€儣銉亴銈︺偐銉冦儊銇偆銉炽偣銉堛兗銉仌銈屻仸銇勩伨銇涖倱銆傘偄銉椼儶銇鐞嗐亱銈夈偆銉炽偣銉堛兗銉仐銇︺亸銇犮仌銇勩��</string>
+    <string name="pref_media_pause">鍋滄</string>
+    <string name="watchface_dialog_widget_timezone_duration">鏅傝▓銈掕〃绀恒仚銈嬫檪闁� (绉�)</string>
+    <string name="pref_media_next">娆°伄鏇�</string>
+    <string name="distance_format_meters">###m</string>
+    <string name="distance_format_miles">###.#mi</string>
+    <string name="sony_button_mode_left">銉溿偪銉炽伄姗熻兘 (宸�)</string>
+    <string name="pref_screen_brightness">鐢婚潰銇槑銈嬨仌</string>
+    <string name="pref_media_play">鍐嶇敓</string>
+    <string name="pref_device_action_dnd_off">銈点偆銉兂銉� 銉€兗銉� - 銈儠</string>
+    <string name="distance_format_kilometers">###.#km</string>
+    <string name="pref_title_opentracks_packagename">OpenTracks 銇儜銉冦偙銉笺偢鍚�</string>
+    <string name="devicetype_xiaomi_watch_s3">Xiaomi Watch S3</string>
+    <string name="devicetype_xiaomi_watch_s1_active">Xiaomi Watch S1 Active</string>
+    <string name="pref_screen_auto_brightness_title">鏄庛倠銇曘伄鑷嫊瑾跨瘈</string>
+    <string name="sony_quick_access_triple_tap">Quick Access (銉堛儶銉椼儷銈裤儍銉�)</string>
+    <string name="sony_button_mode_help_title">銉溿偪銉炽伄姗熻兘 - 銉樸儷銉�</string>
+    <string name="sony_button_mode_help_summary">鍚勩儨銈裤兂銇鑳姐伄瑾槑</string>
+    <string name="sony_button_mode_ambient_sound_control">澶栭煶銈炽兂銉堛儹銉笺儷</string>
+    <string name="pref_media_volumeup">闊抽噺銈€儍銉�</string>
+    <string name="pref_media_volumedown">闊抽噺銉�銈︺兂</string>
+    <string name="pref_summary_fossil_hr_navigation_instructions">銈︺偐銉冦儊銇儕銉撱偛銉笺偡銉с兂銈€儣銉伄鍕曚綔銈掕ō瀹氥仐銇俱仚</string>
+    <string name="pref_button_action_disabled">鐒″姽</string>
+    <string name="pref_media_playpause">鍐嶇敓/鍋滄</string>
+    <string name="pref_media_previous">鍓嶃伄鏇�</string>
+    <string name="pref_device_action_dnd_priority">銈点偆銉兂銉� 銉€兗銉� - 閲嶈搴︺伄楂樸亜閫氱煡銇伩</string>
+    <string name="pref_device_action_dnd_alarms">銈点偆銉兂銉� 銉€兗銉� - 銈€儵銉笺儬銇伩</string>
+    <string name="pref_device_action_dnd_on">銈点偆銉兂銉� 銉€兗銉� - 銈兂</string>
+    <string name="distance_format_feet">###ft</string>
+    <string name="pref_activity_recognize_running">銉┿兂銉嬨兂銈般伄妞滃嚭</string>
+    <string name="pref_activity_recognition_mode_ask">纰鸿獚銇欍倠</string>
+    <string name="menuitem_menu">銉°儖銉ャ兗</string>
+    <string name="pref_activity_recognize_rowing">銉兗銈ゃ兂銈般伄妞滃嚭</string>
+    <string name="quick_attention">銈偆銉冦偗銈€儐銉炽偡銉с兂</string>
+    <string name="pref_screen_auto_brightness_summary">鍛ㄥ洸銇槑銈嬨仌銇繙銇樸仸鐢婚潰銇槑銈嬨仌銈掕鏁淬仐銇俱仚</string>
+    <string name="pref_title_fossil_hr_navigation_instructions">銉娿儞銈层兗銈枫儳銉�</string>
+    <string name="sony_button_mode_playback_control">鍐嶇敓銈炽兂銉堛儹銉笺儷</string>
+    <string name="sony_button_mode_volume_control">闊抽噺銈炽兂銉堛儹銉笺儷</string>
+    <string name="sony_button_mode_right">銉溿偪銉炽伄姗熻兘 (鍙�)</string>
+    <string name="huawei_alarm_event_description">銈广優銉笺儓璧峰簥銈掓湁鍔广伀銇椼仾銇勩仹銇忋仩銇曘亜銆�</string>
+    <string name="pref_summary_opentracks_packagename">澶栭儴銉曘偅銉冦儓銉嶃偣銈€儣銉伄 GPS 瑷橀尣銇枊濮�/鍋滄銇娇鐢ㄣ仌銈屻伨銇欍��</string>
+    <string name="pref_summary_navigation_forward">銉娿儞銈层兗銈枫儳銉炽偄銉椼儶銇嬨倝銇寚绀恒倰銈︺偐銉冦儊銇虎閫併仐銇俱仚</string>
+    <string name="sony_protocol_v1">Version 1</string>
+    <string name="sony_protocol_v2">Version 2</string>
+    <string name="sony_protocol_v3">Version 3</string>
+    <string name="protocol_version">Protocol Version</string>
+    <string name="pref_title_navigation_forward">銈︺偐銉冦儊銇儕銉撱偛銉笺偡銉с兂銈掗�佷俊銇欍倠</string>
+    <string name="fossil_hr_button_config_info">涓�閮ㄣ伄銉溿偪銉炽伄鍕曚綔銇偊銈┿儍銉併伄銉曘偂銉笺儬銈︺偋銈€仹鍥哄畾銇曘倢銇︺亜銈嬨仧銈佽ō瀹氥仹銇嶃伨銇涖倱銆�
+\n
+\n娉ㄦ剰锛欶ossil鍏紡銈€儣銉仹瑷畾銇椼仧銈︺偐銉冦儊銉曘偋銈ゃ偣銈掍娇銇c仸銇勩倠銇ㄣ亶銇笂銉溿偪銉炽倰闀锋娂銇椼仚銈嬨仺銆併偊銈c偢銈с儍銉堛伄琛ㄧず/闈炶〃绀恒倐鍒囥倞鏇裤倧銈娿伨銇欍��</string>
+    <string name="devicetype_pixoo">Pixoo</string>
+    <string name="pref_force_options_summary">涓�閮ㄣ伄銉囥儛銈ゃ偣銇�併亜銇忋仱銇嬨伄銈儣銈枫儳銉炽伀瀵惧繙銇椼仸銇勩仾銇勩仺鍋姐仯銇︺亜銈嬪牬鍚堛亴銇傘倞銇俱仚銆傘亾銇ō瀹氥伅銇濄倢銈夈伄銈儣銈枫儳銉炽倰寮峰埗鐨勩伀鏈夊姽鍖栥仐銇俱仚銆�
+\n鑷繁璨换銇т娇鐢ㄣ仐銇︺亸銇犮仌銇勩��
+\n瑭崇窗銇� Wiki 銈掔⒑瑾嶃仐銇︺亸銇犮仌銇�</string>
+    <string name="error_deleting_device">銉囥儛銈ゃ偣銇墛闄や腑銇偍銉┿兗銇岀櫤鐢熴仐銇俱仐銇�: %s</string>
+    <string name="controlcenter_connected_fraction">鎺ョ稓娓堛伩: %d/%d</string>
+    <string name="error_setting_parent_folder">瑕儠銈┿儷銉�銇ō瀹氫腑銇偍銉┿兗銇岀櫤鐢熴仐銇俱仐銇�: %s</string>
+    <string name="temperature_scale_cf_summary">銉囥儛銈ゃ偣銇岃彲姘忋仺鎽傛皬銇仼銇°倝銈掍娇鐢ㄣ仚銈嬨亱閬告姙銇椼伨銇欍��</string>
+    <string name="temperature_scale_cf">娓╁害銇崢浣�</string>
+    <string name="pref_summary_osmand_packagename">閫f惡銇欍倠 OsmAnd 銇儜銉冦偙銉笺偢鍚嶃倰閬告姙銇椼伨銇�</string>
+    <string name="wifi_ssid">SSID</string>
+    <string name="pref_title_osmand_packagename">OsmAnd 銇儜銉冦偙銉笺偢鍚�</string>
+    <string name="devicetype_miband2_hrx">Mi Band HRX</string>
+    <string name="info_no_devices_connected">銉囥儛銈ゃ偣銇屾帴缍氥仌銈屻仸銇勩伨銇涖倱</string>
+    <string name="controlcenter_unset_folder">銉曘偐銉儉銈掓湭瑷畾銇仚銈�</string>
+    <string name="controlcenter_toggle_details">瑭崇窗銇〃绀�/闈炶〃绀�</string>
+    <string name="controlcenter_add_new_folder">鏂般仐銇勩儠銈┿儷銉�銈掕拷鍔犮仚銈�</string>
+    <string name="controlcenter_set_parent_folder">瑕儠銈┿儷銉�銈掕ō瀹氥仚銈�</string>
+    <string name="wifi_hotspot">Wi-Fi 銉涖儍銉堛偣銉濄儍銉�</string>
+    <string name="ftp_server">FTP 銈点兗銉愩兗</string>
+    <string name="controlcenter_set_folder_title">銉曘偐銉儉銇ō瀹�</string>
+    <string name="info_connected_count">%d 鍙般伄銉囥儛銈ゃ偣銇屾帴缍氭笀銇裤仹銇�</string>
+    <string name="pref_force_options">銈儣銈枫儳銉炽倰寮峰埗鐨勩伀鏈夊姽鍖�</string>
+    <string name="pref_spo_automatic_enable">鑷嫊SpO2娓畾銈掓湁鍔广伀銇欍倠</string>
+    <string name="auto_reconnect_ble_title">鑷嫊銇с儑銉愩偆銈广伀鍐嶆帴缍�</string>
+    <string name="auto_reconnect_ble_summary">瀹氭湡鐨勩伀銉囥儛銈ゃ偣銇搞伄鎺ョ稓銈掕│銇裤伨銇�</string>
+    <string name="pref_enable_call_accept">闆昏┍銇繙銇樸倝銈屻倠銈堛亞銇仚銈�</string>
+    <string name="devicetype_asteroidos">AsteroidOS</string>
+    <string name="devicetype_soflow_s06">SoFlow SO6</string>
+    <string name="pref_enable_call_accept_summary">銉囥儛銈ゃ偣銇嬨倝鐫�淇°伀蹇溿仒銈夈倢銈嬨倛銇嗐伀銇椼伨銇�</string>
+    <string name="pref_disable_find_phone_with_dnd">銈点偆銉兂銉� 銉€兗銉夋湁鍔规檪銇�屻偣銉炪兗銉堛儠銈┿兂銈掓帰銇欍�嶃倰鐒″姽銇仚銈�</string>
+    <string name="pref_heartrate_automatic_enable">鑷嫊蹇冩媿娓畾銈掓湁鍔广伀銇欍倠</string>
+    <string name="devicetype_super_cars">Shell Racing</string>
+    <string name="controlcenter_folder_name">銉曘偐銉儉鍚�:</string>
+    <string name="pref_enable_call_reject_summary">銉囥儛銈ゃ偣銇嬨倝闆昏┍銈掓嫆鍚︺仹銇嶃倠銈堛亞銇仐銇俱仚</string>
+    <string name="pref_enable_call_reject">闆昏┍銈掓嫆鍚︺仹銇嶃倠銈堛亞銇仚銈�</string>
+    <string name="supercars_turbo_speed_label">Turbo Speed</string>
+    <string name="pref_dashboard_all_devices_title">銇欍伖銇︺伄銉囥儛銈ゃ偣</string>
+    <string name="pref_dashboard_select_devices_title">銉囥儛銈ゃ偣銈掗伕鎶炩��</string>
+    <string name="devicetype_huawei_watchfit2">Huawei Watch Fit 2</string>
+    <string name="autoconnect_from_device_title">銉囥儛銈ゃ偣銇嬨倝銇帴缍�</string>
+    <string name="steps_streaks_average_steps">骞冲潎
+\n姝╂暟</string>
+    <string name="steps_streaks_since_date">%s 銇嬨倝</string>
+    <string name="prefs_hourly_chime">鏅傚牨</string>
+    <string name="intent_api_broadcast_export_title">銉囥兗銈裤儥銉笺偣銇偍銈偣銉濄兗銉堟檪銇儢銉兗銉夈偔銉c偣銉�</string>
+    <string name="intent_api_broadcast_export_summary">銉囥兗銈裤儥銉笺偣銇偍銈偣銉濄兗銉堛亴绲備簡銇椼仧銇ㄣ亶銇偆銉炽儐銉炽儓銈掋儢銉兗銉夈偔銉c偣銉堛仚銈�</string>
+    <string name="intent_api_allow_debug_commands_title">銉囥儛銉冦偘 銈炽優銉炽儔銈掕ū鍙�</string>
+    <string name="fossil_hr_pairing_successful">銉氥偄銉兂銈般伀鎴愬姛銇椼伨銇椼仧</string>
+    <string name="fossil_hr_pairing_failed">銉氥偄銉兂銈般伀澶辨晽銇椼伨銇椼仧</string>
+    <string name="pai_chart_per_week">1閫遍枔銇甈AI</string>
+    <string name="num_min">%d 鍒�</string>
+    <string name="step_streak_average_steps_hint">鏈熼枔涓伄1鏃ャ亗銇熴倞銇钩鍧囨鏁�</string>
+    <string name="contact_missing_number">閫g怠鍏堛伄鐣彿銇屻亗銈娿伨銇涖倱</string>
+    <string name="pref_title_debug">銉囥儛銉冦偘 銉偗銈ㄣ偣銉�</string>
+    <string name="title">椤屽悕</string>
+    <string name="pref_dashboard_widget_today_24h_title">24鏅傞枔琛ㄧず</string>
+    <string name="pref_title_huawei_account">Huawei 銈€偒銈︺兂銉�</string>
+    <string name="pref_summary_huawei_account">銉氥偄銉兂銈版檪銇娇鐢ㄣ仚銈婬uawei 銈€偒銈︺兂銉堛�傘亾銈屻倰瑷畾銇欍倠銇ㄥ伐鍫村嚭鑽风姸鎱嬨伀銉偦銉冦儓銇欍倠銇撱仺銇亸銉氥偄銉兂銈般仹銇嶃伨銇欍��</string>
+    <string name="connection_over_bt_classic">Bluetooth Classic 銇倛銈嬫帴缍�</string>
+    <string name="step_streak_ongoing">缍欑稓涓�</string>
+    <string name="steps_streaks_streak_days">缍欑稓
+\n鏃ユ暟</string>
+    <string name="bottom_nav_dashboard">銉�銉冦偡銉ャ儨銉笺儔</string>
+    <string name="pref_dashboard_first_title">銉�銉冦偡銉ャ儨銉笺儔銈掓渶鍒濄伀琛ㄧず銇欍倠</string>
+    <string name="pref_dashboard_widget_settings">銈︺偅銈搞偋銉冦儓銇ō瀹�</string>
+    <string name="pref_dashboard_widget_today_title">娲诲嫊銉併儯銉笺儓</string>
+    <string name="address">銈€儔銉偣</string>
+    <string name="steps_streaks_hint">涓柇銇欍倠銇撱仺銇亸閫g稓銇х洰妯欍伄姝╂暟銈掗仈鎴愩仐銇熸棩鏁�</string>
+    <string name="pref_charts_range_on">銉併儯銉笺儓銇瘎鍥层伅銆屾湀銆嶃伀銇仯銇︺亜銇俱仚</string>
+    <string name="pai_plus_num">+%d</string>
+    <string name="activity_list_summary_activities">銈€偗銉嗐偅銉撱儐銈�</string>
+    <string name="activity_prefs_goal_standing_time_minutes">1鏃ャ伄鐩: 绔嬨仯銇︺亜銈嬫檪闁� (鍒�)</string>
+    <string name="dbmanagementactivity_error_cleaning_export_directory">銈ㄣ偗銈广儩銉笺儓 銉囥偅銉偗銉堛儶 %1$s 銇嬨倝銇儠銈°偆銉墛闄や腑銇偍銉┿兗銇岀櫤鐢熴仐銇俱仐銇�</string>
+    <string name="steps_streaks">鐩姝╂暟銇�g稓閬旀垚鏃ユ暟</string>
+    <string name="contact_name">鍚嶅墠</string>
+    <string name="step_streak_total">鍚堣▓</string>
+    <string name="pref_title_lock_unlock">銉儍銈�</string>
+    <string name="contact_missing_name">閫g怠鍏堛伄鍚嶅墠銇屻亗銈娿伨銇涖倱</string>
+    <string name="intent_api_allow_activity_sync_summary">Intent API 銈掔祵鐢便仐銇︺偄銈儐銈c儞銉嗐偅鍚屾湡銈掋儓銉偓銉笺仹銇嶃倠銈堛亞銇仐銇俱仚</string>
+    <string name="pref_header_intent_api">Intent API</string>
+    <string name="autoconnect_from_device_summary">銉樸儍銉夈儧銉崇瓑銇儑銉愩偆銈广亱銈夋帴缍氥亴闁嬪銇曘倢銇熴仺銇嶃伀鎺ョ稓銈掔⒑绔嬨仐銇俱仚</string>
+    <string name="pref_force_connection_type_auto">鑷嫊</string>
+    <string name="pai_chart_per_month">1銇嬫湀闁撱伄PAI</string>
+    <string name="steps_streaks_total_days_hint_totals">銇欍伖銇︺伄姝┿亜銇熸棩鏁般伀瀵俱仚銈嬬洰妯欓仈鎴愭棩鏁般伄鍓插悎</string>
+    <string name="step_streaks_achievements_sharing_message">绉併伄姝╂暟閬旀垚瀹熺妇锛�</string>
+    <string name="ftp_server_start_summary">銈︺偐銉冦儊涓娿仹 FTP 銈点兗銉愩兗銈掗枊濮嬨仐銇俱仚</string>
+    <string name="description">瑾槑鏂�</string>
+    <string name="notification_channel_connection_status_name">鎺ョ稓鐘舵厠</string>
+    <string name="error_showing_changelog">Changelog 銇〃绀轰腑銇偍銉┿兗銇岀櫤鐢熴仐銇俱仐銇�</string>
+    <string name="activity_list_summary_active_time">娲诲嫊鏅傞枔</string>
+    <string name="pref_chart_sleep_rolling_24_off">鍓嶆棩12:00銇嬨倝褰撴棩12:00</string>
+    <string name="active_time">娲诲嫊鏅傞枔</string>
+    <string name="activity_prefs_activetime_minutes">1鏃ャ伄鐩: 娲诲嫊鏅傞枔 (鍒�)</string>
+    <string name="steps_streaks_total_steps">鍚堣▓
+\n姝╂暟</string>
+    <string name="steps_streaks_total_steps_average_hint">1鏃ュ钩鍧� %d 姝�</string>
+    <string name="status_icon">銈广儐銉笺偪銈广偄銈ゃ偝銉�</string>
+    <string name="changelog_show_full">More鈥�</string>
+    <string name="intent_api_allow_trigger_export_summary">Intent API 銈掔祵鐢便仐銇︺儑銉笺偪銉欍兗銈广伄銈ㄣ偗銈广儩銉笺儓銈掋儓銉偓銉笺仹銇嶃倠銈堛亞銇仐銇俱仚</string>
+    <string name="activity_prefs_summary_allow_bluetooth_intent_api">Intent API 銈掔祵鐢便仐銇� Bluetooth 鎺ョ稓銈掑埗寰°仹銇嶃倠銈堛亞銇仐銇俱仚</string>
+    <string name="activity_prefs_allow_bluetooth_intent_api">Bluetooth Intent API</string>
+    <string name="pref_sleepasandroid_feat_alarms">銈€儵銉笺儬</string>
+    <string name="pref_sleepasandroid_feat_notifications">閫氱煡</string>
+    <string name="wifi_hotspot_stop_summary">銈︺偐銉冦儊涓娿仹 Wi-Fi 銉涖儍銉堛偣銉濄儍銉堛倰鍋滄銇椼伨銇�</string>
+    <string name="wifi_hotspot_summary">銈︺偐銉冦儊涓娿仹 Wi-Fi 銉涖儍銉堛偣銉濄儍銉堛倰鍒跺尽銇с亶銈嬨倛銇嗐伀銇椼伨銇�</string>
+    <string name="ftp_server_status">FTP 銈点兗銉愩兗銇姸鎱�</string>
+    <string name="preview_image">銉椼儸銉撱儱銉肩敾鍍�</string>
+    <string name="changelog_full_title">Changelog</string>
+    <string name="activity_prefs_goal_fat_burn_time_minutes">1鏃ャ伄鐩: 鑴傝偑鐕冪劶鏅傞枔 (鍒�)</string>
+    <string name="standing_time">绔嬨仯銇︺亜銈嬫檪闁�</string>
+    <string name="devicetype_soundcore_liberty3_pro">Soundcore Liberty 3 Pro</string>
+    <string name="weekstepschart_steps_a_month">1銇嬫湀闁撱伄姝╂暟</string>
+    <string name="pref_charts_range_off">銉併儯銉笺儓銇瘎鍥层伅銆岄�便�嶃伀銇仯銇︺亜銇俱仚</string>
+    <string name="weeksleepchart_sleep_a_month">1銇嬫湀闁撱伄鐫$湢</string>
+    <string name="pref_title_chart_sleep_rolling_24_hour">鐫$湢銇瘎鍥�</string>
+    <string name="ftp_server_summary">銈︺偐銉冦儊涓娿仹 FTP 銈点兗銉愩兗銈掑埗寰°仐銇俱仚</string>
+    <string name="ftp_server_stop_summary">銈︺偐銉冦儊涓娿仹 FTP 銈点兗銉愩兗銈掑仠姝€仐銇俱仚</string>
+    <string name="ftp_server_configuration">FTP 銈点兗銉愩兗銇ō瀹�</string>
+    <string name="ftp_server_root_dir">銉兗銉堛儑銈c儸銈儓銉�</string>
+    <string name="username">銉︺兗銈躲兗鍚�</string>
+    <string name="fossil_hr_confirm_connection">銈︺偐銉冦儊銇ф帴缍氥倰鎵胯獚銇椼仸銇忋仩銇曘亜</string>
+    <string name="fossil_hr_connection_not_confirmed">銈︺偐銉冦儊銇ф帴缍氥亴鎵胯獚銇曘倢銇亱銇c仧銇熴倎銆侀潪瑾嶈銉€兗銉夈倰浣跨敤銇椼仸銇勩伨銇�</string>
+    <string name="wifi_hotspot_configuration">Wi-Fi 銉涖儍銉堛偣銉濄儍銉堛伄瑷畾</string>
+    <string name="wifi_hotspot_status">Wi-Fi 銉涖儍銉堛偣銉濄儍銉堛伄鐘舵厠</string>
+    <string name="wifi_hotspot_start_summary">銈︺偐銉冦儊涓娿仹 Wi-Fi 銉涖儍銉堛偣銉濄儍銉堛倰闁嬪銇椼伨銇�</string>
+    <string name="contact_phone_number">闆昏┍鐣彿</string>
+    <string name="dashboard_settings">銉�銉冦偡銉ャ儨銉笺儔銇ō瀹�</string>
+    <string name="pref_dashboard_first_summary">Gadgetbridge 璧峰嫊鏅傘伀銉囥儛銈ゃ偣銇竴瑕с仹銇仾銇忋�併儉銉冦偡銉ャ儨銉笺儔銈掕〃绀恒仐銇俱仚</string>
+    <string name="bottom_nav_devices">銉囥儛銈ゃ偣</string>
+    <string name="pref_dashboard_cards_title">銈︺偅銈搞偋銉冦儓銈掋偒銉笺儔銇笂銇〃绀恒仚銈�</string>
+    <string name="pref_dashboard_cards_summary">銉�銉冦偡銉ャ儨銉笺儔涓娿伄銈︺偅銈搞偋銉冦儓銇懆銈娿伀鏋犮倰浠樸亼銇俱仚</string>
+    <string name="devicetype_huawei_band9">Huawei Band 9</string>
+    <string name="devicetype_huawei_watchfit3">Huawei Watch Fit 3</string>
+    <string name="devicetype_huawei_watch4pro">Huawei Watch 4 Pro</string>
+    <string name="activity_summaries_all_devices">銇欍伖銇︺伄銉囥儛銈ゃ偣</string>
+    <string name="devicetype_flipper_zero">Flipper zero</string>
+    <string name="step_streak_longest">鏈�闀�</string>
+    <string name="steps_streaks_achievement_rate">閬旀垚
+\n鐜�</string>
+    <string name="steps_streaks_total_steps_hint_totals">銇撱倢銇俱仹銇閷层仌銈屻仧姝╂暟銇悎瑷�</string>
+    <string name="step_streak_days_hint">閫g稓銇х洰妯欍伄姝╂暟銈掗仈鎴愩仐銇熸棩鏁�</string>
+    <string name="steps_streaks_total_steps_hint">鏈熼枔涓伄鍚堣▓姝╂暟</string>
+    <string name="step_streaks_achievements_sharing_title">姝╂暟瀹熺妇</string>
+    <string name="prefs_hourly_chime_summary">1鏅傞枔姣庛伀銈︺偐銉冦儊銇屻儞銉笺儣闊炽倰槌淬倝銇欍倛銇嗐伀銇椼伨銇�</string>
+    <string name="intent_api_allow_debug_commands_summary">Intent API 銈掔祵鐢便仐銇︺儑銉愩儍銈般儭銉嬨儱銉� 銈炽優銉炽儔銈掋儓銉偓銉笺仹銇嶃倠銈堛亞銇仐銇俱仚</string>
+    <string name="pref_summary_debug">Huawei 銉囥儛銈ゃ偣銇儑銉愩儍銈� 銉偗銈ㄣ偣銉堛倰閫佷俊銇椼伨銇�</string>
+    <string name="pref_title_bottom_navigation_bar">鐢婚潰涓嬮儴銇儕銉撱偛銉笺偡銉с兂銉愩兗銈掕〃绀恒仚銈�</string>
+    <string name="pref_summary_bottom_navigation_bar_on">銉娿儞銈层兗銈枫儳銉炽儛銉笺伨銇熴伅妯偣銉偆銉椼仹銉°偆銉崇敾闈€倰鍒囥倞鏇裤亪銇俱仚</string>
+    <string name="pref_sleepasandroid_feat_movement">鍔犻�熷害銈汇兂銈点兗</string>
+    <string name="intent_api_allow_activity_sync_title">銈€偗銉嗐偅銉撱儐銈e悓鏈熴伄銉堛儶銈兗銈掕ū鍙�</string>
+    <string name="intent_api_allow_trigger_export_title">銉囥兗銈裤儥銉笺偣銇偍銈偣銉濄兗銉堛倰瑷卞彲</string>
+    <string name="title_cycling">銈点偆銈儶銉炽偘</string>
+    <string name="pref_summary_wheel_diameter">銈裤偆銉ゃ伄鐩村緞(銈ゃ兂銉�)銆備竴鑸殑銇伅26, 27, 27.5, 29銇仼銇с仚銆�</string>
+    <string name="pref_title_wheel_diameter">銈裤偆銉ゃ伄鐩村緞</string>
+    <string name="chart_cycling_point_label_distance">浠婃棩: %.1f km
+\n鍚堣▓: %.1f km</string>
+    <string name="chart_cycling_point_label_speed">%.1f km/h</string>
+    <string name="devicetype_nut_mini">Nut mini</string>
+    <string name="pref_dashboard_widgets_order_summary">鏈夊姽銇仚銈嬨偊銈c偢銈с儍銉堛仺銆併儉銉冦偡銉ャ儨銉笺儔銇〃绀恒仚銈嬨偊銈c偢銈с儍銉堛伄闋嗙暘銈掗伕鎶炪仐銇俱仚</string>
+    <string name="pref_summary_bottom_navigation_bar_off">妯偣銉偆銉椼仹銉°偆銉崇敾闈€倰鍒囥倞鏇裤亪銇俱仚</string>
+    <string name="pref_dashboard_devices_to_include">銉�銉冦偡銉ャ儨銉笺儔銇惈銈併倠銉囥儛銈ゃ偣</string>
+    <string name="pref_dashboard_widget_goals_chart_title">鐩銉併儯銉笺儓</string>
+    <string name="pref_summary_device_intents">Bangle.js 銈︺偐銉冦儊銈€儣銉亴 Android Intent 銈掗�佷俊銇с亶銈嬨倛銇嗐伀銇椼�丄ndroid 涓娿伄浠栥伄銈€儣銉�(Tasker銇仼)銇� com.banglejs.uart.tx Intent 銈掍娇銇c仸 Bangle.js 銇儑銉笺偪銈掗�佷俊銇с亶銈嬨倛銇嗐伀銇椼伨銇欍�傘儛銉冦偗銈般儵銈︺兂銉夈仹鍕曚綔銇欍倠銇伅銆佷粬銇偄銉椼儶銇笂銇噸銇仸琛ㄧず銇欍倠妯╅檺銇屽繀瑕併仹銇欍��</string>
+    <string name="pref_dashboard_select_devices_summary">鎸囧畾銇椼仧銉囥儛銈ゃ偣銇偄銈儐銈c儞銉嗐偅銈掔当鍚堛仐銇︺儉銉冦偡銉ャ儨銉笺儔銇〃绀恒仐銇俱仚</string>
+    <string name="pref_dashboard_widget_show_legend_title">鍑′緥銈掕〃绀恒仚銈�</string>
+    <string name="pref_dashboard_widget_double_size_title">澶с亶銇勩偊銈c偢銈с儍銉�</string>
+    <string name="pref_dashboard_widget_today_24h_summary">銈€偗銉嗐偅銉撱儐銈c倰2銇ゃ伄12鏅傞枔銈点兗銈儷銇с伅銇亸銆�1銇ゃ伄24鏅傞枔銈点兗銈儷銇ц〃绀恒仐銇俱仚</string>
+    <string name="activity_type_worn">瑁呯潃</string>
+    <string name="pref_dashboard_widget_double_size_summary">銈︺偅銈搞偋銉冦儓銇屻儉銉冦偡銉ャ儨銉笺儔銇�2鍒椼倰浣跨敤銇с亶銈嬨倛銇嗐伀銇椼伨銇�</string>
+    <string name="pref_dashboard_widget_show_legend_summary">銈︺偅銈搞偋銉冦儓銇笅閮ㄣ伀銈般儵銉曘伄鑹层倰瑾槑銇欍倠鍑′緥銈掕〃绀恒仐銇俱仚</string>
+    <string name="pref_dashboard_all_devices_summary">鐧婚尣銇椼仧銇欍伖銇︺伄銉囥儛銈ゃ偣銇偄銈儐銈c儞銉嗐偅銉囥兗銈裤倰绲卞悎銇椼仸銉�銉冦偡銉ャ儨銉笺儔銇〃绀恒仐銇俱仚</string>
+    <string name="zetime_signaling_beep_once">1鍥炪仩銇戙儞銉笺儣闊炽倰槌淬倝銇�</string>
+    <string name="pref_dashboard_widget_today_upside_down_summary">24鏅傞枔銉€兗銉夈伄銇ㄣ亶銇�0鏅傘倰銉併儯銉笺儓銇笅鍋淬伀鎻忓啓銇椼��12鏅傘倰涓婂伌銇〃绀恒仐銇俱仚</string>
+    <string name="watchface_resolution_doesnt_match">銈︺偐銉冦儊銉曘偋銈ゃ偣銇В鍍忓害銇屻儑銉愩偆銈广伄鐢婚潰銇ㄤ竴鑷淬仐銇俱仜銈撱�傘偊銈┿儍銉併儠銈с偆銈广伅 %1$s 銇с�併儑銉愩偆銈广伄鐢婚潰銇� %2$s 銇с仚</string>
+    <string name="toast_setting_requires_reconnect">瑷畾銇啀鎺ョ稓鏅傘伀鍙嶆槧銇曘倢銇俱仚</string>
+    <string name="zetime_signaling_beep_twice">2鍥炪儞銉笺儣闊炽倰槌淬倝銇�</string>
+    <string name="zetime_signaling_vibrate_beep_once">1鍥炪仩銇戞尟鍕曘仺銉撱兗銉楅煶銈掗炒銈夈仚</string>
+    <string name="zetime_calories_type">銈儹銉兗銇ó椤�</string>
+    <string name="zetime_title_alarm_signaling">銈€儵銉笺儬銇炒銈婃柟銈掕ō瀹氥仚銈�</string>
+    <string name="toast_camera_permission_required">銇撱伄姗熻兘銇伅銈儭銉┿伄妯╅檺銇屽繀瑕併仹銇欍��</string>
+    <string name="toast_camera_support_required">銇撱伄姗熻兘銇伅銈儭銉┿伄銈点儩銉笺儓銇屽繀瑕併仹銇欍��</string>
+    <string name="toast_camera_photo_taken">鍐欑湡銈掓銇牬鎵�銇繚瀛樸仐銇俱仐銇�: %s</string>
+    <string name="pref_dashboard_widget_today_upside_down_title">0鏅傘倰涓嬪伌銇〃绀�</string>
+    <string name="pref_alert_tone">銈€儵銉笺儓闊�</string>
+    <string name="chart_get_active_and_synchronize">浣曘倝銇嬨伄銈€偗銉嗐偅銉撱儐銈c倰琛屻亜銆併儑銉愩偆銈广倰鍚屾湡銇椼仸銇忋仩銇曘亜銆�</string>
+    <string name="pref_agps_expiry_reminder_enabled">瑁滃姪 GPS 鏈夊姽鏈熼檺銉優銈ゃ兂銉�銉�</string>
+    <string name="pref_sleep_mode_smart_enable_title">銈广優銉笺儓銈广儶銉笺儣</string>
+    <string name="pref_sleep_mode_smart_enable_summary">鐫$湢涓伀銉愩兂銉夈亴瑁呯潃銇曘倢銇熴仺銇嶃�併偣銉兗銉椼儮銉笺儔銈掕嚜鍕曠殑銇湁鍔广伀銇椼伨銇�</string>
+    <string name="prefs_stress_monitoring_description">浼戙倱銇с亜銈嬮枔銇偣銉堛儸銈广儸銉欍儷銈掋儮銉嬨偪銉兂銈般仐銇俱仚</string>
+    <string name="pref_touch_tone">銈裤儍銉侀煶</string>
+    <string name="pref_touch_tone_summary">銈ゃ儰銉涖兂銇屻偪銉冦儊銇曘倢銇熴仺銇嶃伀闊炽倰槌淬倝銇椼伨銇�</string>
+    <string name="pref_wearing_tone">瑁呯潃闊�</string>
+    <string name="pref_wearing_tone_summary">銈ゃ儰銉涖兂銇岃鐫�銇曘倢銇熴仺銇嶃伀闊炽倰槌淬倝銇椼伨銇�</string>
+    <string name="prefs_stress_monitoring_title">銈广儓銉偣銉€儖銈裤儶銉炽偘</string>
+    <string name="changelog_ok_button">OK</string>
+    <string name="loyalty_cards_catima">Catima</string>
+    <string name="pref_theme_dynamic_colors_explanation">娉ㄦ剰: 銉�銈ゃ儕銉熴儍銈偒銉┿兗銇儐銉笺優銈掍娇鐢ㄣ仚銈嬨伀銇�丄ndroid 12 浠ヤ笂銇瑕宠ō瀹氥仹澹佺礄銇壊銇俱仧銇偒銉┿兗銉戙儸銉冦儓銈掓湁鍔广伀銇欍倠蹇呰銇屻亗銈娿伨銇欍�傛湁鍔广伀銇椼仾銇勫牬鍚堛�丟adgetbridge 銇儑銉曘偐銉儓銇� Material 3 銈儵銉笺倰浣跨敤銇椼伨銇欍��</string>
+    <string name="loyalty_cards_catima_not_installed">銉濄偆銉炽儓銈兗銉夈伄绠$悊銇伅 Catima 銇屽繀瑕併仹銇�</string>
+    <string name="withings_calibration_text_hours">涓嬨伄銉�銈ゃ儰銉倰浣裤仯銇︽檪閲濄倰 12 銇悎銈忋仜銇︺亸銇犮仌銇勩��</string>
+    <string name="activity_info">銈€偗銉嗐偅銉撱儐銈f儏鍫�</string>
+    <string name="loyalty_cards_sync_archived">銈€兗銈偆銉栥仐銇熴偒銉笺儔銈掑悓鏈熴仚銈�</string>
+    <string name="loyalty_cards_sync_summary">銈裤儍銉椼仐銇︺偒銉笺儔銈掋偊銈┿儍銉併伀鍚屾湡銇椼伨銇�</string>
+    <string name="widget">銈︺偅銈搞偋銉冦儓</string>
+    <string name="pref_auto_reply_calls_title">鑷嫊鐨勩伀闆昏┍銇繙绛斻仚銈�</string>
+    <string name="pref_auto_reply_calls_delay_title">鑷嫊鐨勩伀蹇滅瓟銇欍倠銇俱仹銇檪闁�</string>
+    <string name="pref_speak_notifications_focus_exclusive_title">浠栥伄銈€儣銉伄闊冲0銈掍竴鏅傚仠姝€仚銈�</string>
+    <string name="pref_speak_notifications_aloud_summary">銉樸儍銉夈儧銉炽仹閫氱煡銈掕銇夸笂銇掋伨銇�</string>
+    <string name="uploadwatchfaceoperation_complete">銈︺偐銉冦儊銉曘偋銈ゃ偣銇偆銉炽偣銉堛兗銉亴瀹屼簡銇椼伨銇椼仧</string>
+    <string name="loyalty_cards_install">Catima 銈掋偆銉炽偣銉堛兗銉仚銈�</string>
+    <string name="loyalty_cards_catima_not_compatible">銈ゃ兂銈广儓銉笺儷銇曘倢銇︺亜銈� Catima 銇儛銉笺偢銉с兂銇� Gadgetbridge 銇蹇溿仐銇︺亜銇俱仜銈撱�侰atima 銇� Gadgetbridge 銈掓渶鏂扮増銇洿鏂般仐銇︺亸銇犮仌銇勩��</string>
+    <string name="loyalty_cards_catima_permissions_summary">Gadgetbridge 銇� Catima 銇偒銉笺儔銈掑悓鏈熴仚銈嬨伀銇偒銉笺儔銈掕銇垮彇銈嬫ī闄愩亴蹇呰銇с仚銆傘亾銇儨銈裤兂銈掋偪銉冦儣銇椼仸妯╅檺銈掕ū鍙仐銇︺亸銇犮仌銇勩��</string>
+    <string name="loyalty_cards_sync_options">鍚屾湡瑷畾</string>
+    <string name="loyalty_cards_sync">鍚屾湡</string>
+    <string name="loyalty_cards_open_catima">Catima 銈掗枊銇�</string>
+    <string name="withings_bt_calibration_previous">鎴汇倠</string>
+    <string name="withings_calibration_text_minutes">娆°伀銆併儉銈ゃ儰銉倰浣裤仯銇﹀垎閲濄倰 12 銇悎銈忋仜銇︺亸銇犮仌銇勩��</string>
+    <string name="pref_test_features_summary">銇撱伄銉嗐偣銉堛儑銉愩偆銈广仹鏈夊姽銇鑳�</string>
+    <string name="loyalty_cards_sync_title">銉濄偆銉炽儓銈兗銉夈倰鍚屾湡銇欍倠</string>
+    <string name="activity_summaries_all_activities">銇欍伖銇︺伄銈€偗銉嗐偅銉撱儐銈�</string>
+    <string name="pref_force_connection_type_title">鎺ョ稓褰㈠紡銈掓寚瀹氥仚銈�</string>
+    <string name="loyalty_cards_catima_permissions_title">涓嶈冻銇椼仸銇勩倠妯╅檺</string>
+    <string name="withings_calibration_text_activity_target">鏈�寰屻伀銆併偄銈儐銈c儞銉嗐偅銇嚌銈� 100% 銇悎銈忋仜銇︺亸銇犮仌銇勩�傘亾銇嚌銇檪瑷堝洖銈娿伀銇椼亱鍕曘亶銇俱仜銈撱��</string>
+    <string name="loyalty_cards_catima_package">Catioma 銇儜銉冦偙銉笺偢鍚�</string>
+    <string name="loyalty_cards_sync_groups_only">鐗瑰畾銇偘銉兗銉椼伄銇垮悓鏈熴仚銈�</string>
+    <string name="loyalty_cards_sync_groups">鍚屾湡銇欍倠銈般儷銉笺儣</string>
+    <string name="loyalty_cards_install_catima_fail">銈€儣銉偣銉堛偄銈掗枊銇戙伨銇涖倱銇с仐銇�</string>
+    <string name="pref_theme_dynamic_colors_not_available_warning">銉�銈ゃ儕銉熴儍銈偒銉┿兗銇� Android 12 鏈簚銇儑銉愩偆銈广仹銇埄鐢ㄣ仹銇嶃伨銇涖倱銆侴adgetbridge 銇儑銉曘偐銉儓銇� Material 3 銈儵銉笺倰浣跨敤銇椼伨銇欍��</string>
+    <string name="prompt_restart_gadgetbridge">鍙嶆槧銇欍倠銇伅 Gadgetbridge 銈掑啀璧峰嫊銇椼仸銇忋仩銇曘亜銆�</string>
+    <string name="uploadwatchfaceoperation_in_progress">銈︺偐銉冦儊銉曘偋銈ゃ偣銈掋偄銉冦儣銉兗銉夈仐銇︺亜銇俱仚</string>
+    <string name="pref_speak_notifications_aloud_title">閫氱煡銈掕銇夸笂銇掋倠</string>
+    <string name="pref_speak_notifications_focus_exclusive_summary_on">閫氱煡銇銇夸笂銇掍腑銇粬銇偄銉椼儶銇煶澹般倰涓�鏅傚仠姝€仐銇俱仚</string>
+    <string name="pref_speak_notifications_focus_exclusive_summary_off">閫氱煡銇銇夸笂銇掍腑銇粬銇偄銉椼儶銇煶澹般倰灏忋仌銇忋仐銇俱仚</string>
+    <string name="pref_header_calls_and_notifications">閫氳┍銇ㄩ�氱煡</string>
+    <string name="pref_auto_reply_calls_delay_summary">鑷嫊銇ч浕瑭便伀鍑恒倠銇俱仹銇鏁�</string>
+    <string name="loyalty_cards_sync_starred">銇婃皸銇叆銈娿伀鐧婚尣銇椼仧銈兗銉夈伄銇垮悓鏈熴仚銈�</string>
+    <string name="changelog_title">What\'s New</string>
+    <string name="activity_summaries_statistics">绲辫▓</string>
+    <string name="loyalty_cards">銉濄偆銉炽儓銈兗銉�</string>
+    <string name="loyalty_cards_syncing">%d 鏋氥伄銉濄偆銉炽儓銈兗銉夈倰銉囥儛銈ゃ偣銇悓鏈熴仐銇︺亜銇俱仚</string>
+    <string name="withings_bt_calibration_next">閫层個</string>
+    <string name="pref_test_features_title">姗熻兘</string>
+    <string name="sports_activity_quick_filter_this_month">浠婃湀</string>
+    <string name="sports_activity_quick_filter_7days">7鏃ラ枔</string>
+    <string name="sports_activity_quick_filter_30days">30鏃ラ枔</string>
+    <string name="uploadwatchfaceoperation_failed">銈︺偐銉冦儊銉曘偋銈ゃ偣銇偆銉炽偣銉堛兗銉伀澶辨晽銇椼伨銇椼仧</string>
+    <string name="pref_force_connection_type_description">銉囥儛銈ゃ偣銇� Gadgetbridge 銇繙绛斻仐銇亜鍫村悎銆佹帴缍氬舰寮忋倰鎸囧畾銇椼仸銇裤仸銇忋仩銇曘亜</string>
+    <string name="pref_auto_reply_calls_summary">鐫�淇°亴銇傘仯銇熴仺銇嶃伀鑷嫊銇ч浕瑭便伀鍑恒伨銇�</string>
+    <string name="sports_activity_quick_filter_last_week">鍏堥��</string>
+    <string name="sports_activity_quick_filter_this_week">鍏堥��</string>
+    <string name="sports_activity_quick_filter_last_month">鍏堟湀</string>
+    <string name="sports_activity_quick_filter_select">鏈熼枔</string>
+    <string name="activity_filter_to_placeholder">浠婃棩</string>
+    <string name="toast_app_must_be_selected">瑷畾銇欍倠銇伅銈€儣銉倰閬告姙銇欍倠蹇呰銇屻亗銈娿伨銇�</string>
+    <string name="toast_app_must_not_be_selected">瑷畾銇欍倠銇伅銈€儣銉倰鏈伕鎶炪伀銇欍倠蹇呰銇屻亗銈娿伨銇�</string>
+    <string name="devicetype_amazfit_bip5_unity">Amazfit Bip 5 Unity</string>
+    <string name="qhybrid_use_activity_hand_as_notification_counter">銈€偗銉嗐偅銉撱儐銈c伄閲濄倰閫氱煡銈偊銉炽偪銉笺仺銇椼仸浣裤亞</string>
+    <string name="pref_write_logfiles_not_available">銉曘偂銈ゃ儷銉偖銉炽偘銇垵鏈熷寲銇け鏁椼仐銇熴仧銈併�併儹銈般儠銈°偆銉伄瑷橀尣銇従鍦ㄥ埄鐢ㄣ仹銇嶃伨銇涖倱銆傘偄銉椼儶銈掑啀璧峰嫊銇椼仸銉偘銉曘偂銈ゃ儷銇垵鏈熷寲銈掋倓銈婄洿銇椼仸銇忋仩銇曘亜銆�</string>
+    <string name="status">鐘舵厠</string>
+    <string name="dismiss">鐒¤</string>
+    <string name="start">闁嬪</string>
+    <string name="pref_workout_detection_time">銉兗銈偄銈︺儓銇屾鍑恒仌銈屻倠銇俱仹銇檪闁�</string>
+    <string name="set">銈汇儍銉�</string>
+    <string name="mi5_prefs_workout_activity_types_summary">銉兗銈偄銈︺儓鐢婚潰銇〃绀恒仚銈嬨偄銈儐銈c儞銉嗐偅銇ó椤炪倰閬告姙銇椼伨銇�</string>
+    <string name="prefs_heartrate_alert_low_threshold">浣庡績鎷嶆暟銈€儵銉笺儓銇仐銇嶃亜鍊�</string>
+    <string name="pref_agps_update_time">瑁滃姪 GPS 鏇存柊鏅傞枔</string>
+    <string name="pref_agps_expiry_reminder_time">瑁滃姪 GPS 鏈夊姽鏈熼檺銉優銈ゃ兂銉�銉笺伄鏅傞枔</string>
+    <string name="pref_camera_remote_summary">銈︺偐銉冦儊銇с偒銉°儵銇偡銉c儍銈裤兗銈掑垏銈屻倠銈堛亞銇仐銇俱仚</string>
+    <string name="prefs_spo2_monitoring_title">SpO2 銉€儖銈裤儶銉炽偘</string>
+    <string name="show_ongoing_activity">閫茶涓伄銈€偗銉嗐偅銉撱儐銈c倰銉濄儍銉椼偄銉冦儣銇ц〃绀�</string>
+    <string name="mi5_prefs_workout_activity_types">銉兗銈偄銈︺儓銈€偗銉嗐偅銉撱儐銈c伄绋</string>
+    <string name="serbian">銈汇儷銉撱偄瑾�</string>
+    <string name="prefs_activity_monitoring_description">銉愩兂銉夈亴閬嬪嫊銈掓鍑恒仐銇熴仺銇嶃�佽嚜鍕曠殑銇績鎷嶆暟銈掓脯瀹氥仚銈嬮牷搴︺倰涓娿亽銇﹀績鎷嶆暟銇崟鎹夌簿搴︺倰鍚戜笂銇曘仜銇俱仚銆�</string>
+    <string name="about_additional_device_support">杩藉姞銇儑銉愩偆銈广偟銉濄兗銉�</string>
+    <string name="stop">鍋滄</string>
+    <string name="agps_status_missing">涓嶆槑</string>
+    <string name="agps_status_pending">鏈⒑瀹�</string>
+    <string name="agps_status_current">鐝惧湪</string>
+    <string name="pref_camera_remote_title">銉儮銉笺儓銈枫儯銉冦偪銉�</string>
+    <string name="prefs_relaxation_reminder_title">浼戞啯銉優銈ゃ兂銉�銉�</string>
+    <string name="prefs_spo2_alert_threshold">SpO2 銈€儵銉笺儓銇仐銇嶃亜鍊�</string>
+    <string name="prefs_activity_monitoring_title">銈€偗銉嗐偅銉撱儐銈c儮銉嬨偪銉兂銈�</string>
+    <string name="devicetype_garmin_vivomove_hr">Garmin V铆vomove HR</string>
+    <string name="devicetype_garmin_vivomove_style">Garmin V铆vomove Style</string>
+    <string name="devicetype_garmin_venu_3">Garmin Venu 3</string>
+    <string name="devicetype_garmin_instinct_solar">Garmin Instinct Solar</string>
+    <string name="devicetype_garmin_instinct_2_solar">Garmin Instinct 2 Solar</string>
+    <string name="devicetype_garmin_instinct_2_soltac">Garmin Instinct 2 SolTac</string>
+    <string name="devicetype_garmin_instinct_crossover">Garmin Instinct Crossover</string>
+    <string name="devicetype_garmin_forerunner_245">Garmin Forerunner 245</string>
+    <string name="devicetype_garmin_swim_2">Garmin Swim 2</string>
+    <string name="devicetype_garmin_vivoactive_4">Garmin V铆voactive 4</string>
+    <string name="devicetype_garmin_vivoactive_4s">Garmin V铆voactive 4S</string>
+    <string name="devicetype_garmin_vivoactive_5">Garmin V铆voactive 5</string>
+    <string name="folder">銉曘偐銉儉</string>
+    <string name="copied_to_clipboard">銈儶銉冦儣銉溿兗銉夈伀銈炽償銉笺仐銇俱仐銇�</string>
+    <string name="no_folder_selected">銉曘偐銉儉銇岄伕鎶炪仌銈屻仸銇勩伨銇涖倱</string>
+    <string name="folder_is_empty">銉曘偐銉儉銇┖銇с仚</string>
+    <string name="url">URL</string>
+    <string name="garmin_agps_local_file">銉兗銈儷銉曘偂銈ゃ儷</string>
+    <string name="pref_agps_expire_time">瑁滃姪 GPS 鏈夊姽鏈熼檺</string>
+    <string name="pref_agps_status">瑁滃姪 GPS 銇姸鎱�</string>
+    <string name="agps_status_error">銈ㄣ儵銉�</string>
+    <string name="prefs_relaxation_reminder_description">銈广儓銉偣鍊ゃ亴 80 銈掕秴銇堛倠銇ㄣ儛銉炽儔銈掓尟鍕曘仌銇涖仸閫氱煡銇椼伨銇�</string>
+    <string name="pref_workout_detection_time_summary">銉兗銈偄銈︺儓銇ㄣ仐銇︽鍑恒仌銈屻倠銇俱仹銇亱鍕曘伄缍欑稓鏅傞枔</string>
+    <string name="updatefirmwareoperation_failed_low_mtu">鐝惧湪銇� %1$d 銇� MTU 銇綆銇欍亷銇俱仚銆傘儑銉愩偆銈硅ō瀹氥仹楂� MTU 銈掓湁鍔广伀銇椼�併儑銉愩偆銈广倰鍒囨柇/鍐嶆帴缍氥仐銇︺亸銇犮仌銇勩��</string>
+    <string name="prefs_spo2_monitoring_description">琛�涓吀绱犳績搴︺倰涓�鏃ヤ腑銆佽嚜鍕曠殑銇儮銉嬨偪銉兂銈般仐銇俱仚</string>
+    <string name="devicetype_garmin_instinct_2s">Garmin Instinct 2S</string>
+    <string name="watchface_setting_wrist_flick_hour">鏅傞嚌 (-360 銇嬨倝 360)</string>
+    <string name="pref_summary_canned_messages_set">浠ヤ笅銇ō瀹氥仐銇熴儭銉冦偦銉笺偢銈掋儑銉愩偆銈广伀閫佷俊銇椼伨銇�</string>
+    <string name="yes">銇亜</string>
+    <string name="watchface_setting_wrist_flick_minute">鍒嗛嚌 (-360 銇嬨倝 360)</string>
+    <string name="activity_filter_from_placeholder">銇嬨仾銈婂墠</string>
+    <string name="devicetype_garmin_vivosmart_5">Garmin V铆vosmart 5</string>
+    <string name="activity_filter_reset_filter">銉曘偅銉偪銈掋儶銈汇儍銉�</string>
+    <string name="activity_filter_name_contains">銉┿儥銉�</string>
+    <string name="activity_filter_apply_filter">銉曘偅銉偪銈掗仼鐢�</string>
+    <string name="no">銇勩亜銇�</string>
+    <string name="sports_activity_confirm_delete_description">%d 銇偄銈儐銈c儞銉嗐偅銈掑墛闄ゃ仐銇俱仚銇嬶紵</string>
+    <string name="prefs_autolight">鑷嫊鐐圭伅</string>
+    <string name="fossil_hr_commute_processing">銉偗銈ㄣ偣銉堛伄鍑︾悊涓��</string>
+    <string name="activity_filter_filter_title">銉曘偅銉偪</string>
+    <string name="addto_filter">銉曘偅銉偪銇拷鍔�</string>
+    <string name="sports_activity_confirm_delete_title">%d 銇偄銈儐銈c儞銉嗐偅銇墛闄�</string>
+    <string name="backstroke">鑳屾吵銇�</string>
+    <string name="fossil_hr_commute_actions_explanation">銇撱亾銇цō瀹氥仐銇熴偄銈偡銉с兂銇�併偊銈┿儍銉併伄 Commute 銈€儣銉伀琛ㄧず銇曘倢銇俱仚銆傘亾銈屻倝銇偄銈偡銉с兂銇倛銇c仸鐢熸垚銇曘倢銈嬨偆銉炽儐銉炽儓銇嚘鐞嗘柟娉曘伀銇ゃ亜銇︺伅銆亀iki 銈掋亰瑾伩銇忋仩銇曘亜銆�</string>
+    <string name="prefs_autoremove_message">SMS 閫氱煡銈掕嚜鍕曠殑銇墛闄ゃ仚銈�</string>
+    <string name="medley">銉°儔銉兗</string>
+    <string name="freestyle">鑷敱褰�</string>
+    <string name="devicetype_garmin_venu_2_plus">Garmin Venu 2 Plus</string>
+    <string name="breaststroke">骞虫吵銇�</string>
+    <string name="pref_switch_noise_control">銉庛偆銈恒偝銉炽儓銉兗銉伄鍒囥倞鏇裤亪</string>
+    <string name="prefs_noise_control_with_one_earbud">鐗囨柟銇偆銉ゃ儧銉炽仹銉庛偆銈恒偝銉炽儓銉兗銉�</string>
+    <string name="pref_switch_controls_anc_ambient_off">銉庛偆銈恒偔銉c兂銈汇儶銉炽偘 鈫愨啋 鍛ㄥ洸銇煶 鈫愨啋 銈儠</string>
+    <string name="prefs_double_tap_edge_summary">銈裤儍銉併儜銉冦儔澶栥伄銉�銉栥儷銈裤儍銉椼倰妞滃嚭銇椼伨銇�</string>
+    <string name="pref_switch_controls_anc_off">銉庛偆銈恒偔銉c兂銈汇儶銉炽偘 鈫愨啋 銈儠</string>
+    <string name="prefs_voice_detect">闊冲0妞滃嚭</string>
+    <string name="pref_switch_controls_ambient_off">鍛ㄥ洸銇煶 鈫愨啋 銈儠</string>
+    <string name="prefs_galaxy_touch_options">銈裤儍銉佽ō瀹�</string>
+    <string name="prefs_double_tap_edge">銈ゃ儰銉涖兂銇銈掋儉銉栥儷銈裤儍銉�</string>
+    <string name="pref_ambient_sound_tone_summary">銈姐儠銉堛亱銈夈偗銉偄銇枔銇ц鏁淬仐銇俱仚</string>
+    <string name="prefs_noise_control_with_one_earbud_summary">銈ゃ儰銉涖兂銈掔墖鏂广伄銇夸娇鐢ㄣ仐銇︺亜銈嬨仺銇嶃仹銈傘儙銈ゃ偤銈炽兂銉堛儹銉笺儷銈掍娇銇堛倠銈堛亞銇仐銇俱仚</string>
+    <string name="prefs_voice_detect_summary">鍛ㄥ洸銇煶銇彇銈婅炯銇裤倰鏈夊姽銇仐銆佸0銇屾鍑恒仌銈屻仧銇ㄣ亶銇偝銉炽儐銉炽儎銇煶閲忋倰鑷嫊鐨勩伀涓嬨亽銇俱仚</string>
+    <string name="pref_balance">銉愩儵銉炽偣</string>
+    <string name="prefs_noise_control">銉庛偆銈恒偝銉炽儓銉兗銉�</string>
+    <string name="pref_switch_controls_anc_ambient">銉庛偆銈恒偔銉c兂銈汇儶銉炽偘 鈫愨啋 鍛ㄥ洸銇煶</string>
+    <string name="pref_ambient_sound_tone">鍛ㄥ洸銇煶銈掑彇銈婅炯銈�闅涖伄銉堛兗銉�</string>
+    <string name="prefs_in_ear_detection_summary">銈ゃ儰銉涖兂瑁呯潃鏅傘伀闆昏┍銇煶澹般倰銈ゃ儰銉涖兂銇у啀鐢熴仐銇俱仚</string>
+    <string name="devicetype_garmin_instinct_2s_solar">Garmin Instinct 2S Solar</string>
+    <string name="devicetype_hama_fit6900">Hama Fit6900</string>
+    <string name="devicetype_garmin_epix_pro">Garmin Epix Pro</string>
+    <string name="devicetype_garmin_instinct_2x_solar">Garmin Instinct 2X Solar</string>
+    <string name="devicetype_garmin_venu_2">Garmin Venu 2</string>
+    <string name="devicetype_garmin_forerunner_265">Garmin Forerunner 265</string>
+    <string name="devicetype_garmin_venu_3s">Garmin Venu 3S</string>
+    <string name="devicetype_garmin_fenix_6_sapphire">Garmin Fenix 6 Sapphire</string>
+    <string name="devicetype_garmin_fenix_7_pro">Garmin Fenix 7 Pro</string>
+    <string name="devicetype_garmin_fenix_7s">Garmin Fenix 7S</string>
+    <string name="devicetype_garmin_forerunner_255">Garmin Forerunner 255</string>
+</resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-ka/strings.xml b/app/src/main/res/values-ka/strings.xml
new file mode 100644
index 0000000..f828b79
--- /dev/null
+++ b/app/src/main/res/values-ka/strings.xml
@@ -0,0 +1,190 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <string name="application_name_generic">Gadgetbridge</string>
+    <string name="title_activity_controlcenter_generic">Gadgetbridge</string>
+    <string name="action_settings">醿炨儛醿犪儛醿涐償醿⑨儬醿斸儜醿�</string>
+    <string name="action_quit">醿ㄡ償醿儳醿曖償醿⑨儛</string>
+    <string name="action_donate">醿撫儩醿溼儛醿儤醿�</string>
+    <string name="controlcenter_fetch_activity_data">醿♂儤醿溼儱醿犪儩醿溼儤醿栣儛醿儤醿�</string>
+    <string name="controlcenter_find_device">醿撫儛醿欋儛醿犪儝醿a儦醿� 醿涐儩醿儳醿濁儜醿樶儦醿濁儜醿樶儭 醿炨儩醿曖儨醿�</string>
+    <string name="controlcenter_change_led_color">LED 醿め償醿犪儤醿� 醿ㄡ償醿儠醿氠儛</string>
+    <string name="controlcenter_change_fm_frequency">FM 醿♂儤醿儴醿樶儬醿樶儭 醿ㄡ償醿儠醿氠儛</string>
+    <string name="controlcenter_connect">醿撫儛醿欋儛醿曖儴醿樶儬醿斸儜醿愨��</string>
+    <string name="controlcenter_disconnect">醿掅儛醿椺儤醿ㄡ儠醿�</string>
+    <string name="controlcenter_delete_device">醿涐儩醿儳醿濁儜醿樶儦醿濁儜醿樶儭 醿儛醿ㄡ儦醿�</string>
+    <string name="controlcenter_delete_device_name">醿儛醿ㄡ儦醿� %1$s</string>
+    <string name="controlcenter_delete_device_dialogmessage">醿斸儭 醿儛醿ㄡ儦醿樶儭 醿償醿氠儭醿愥儸醿п儩醿� 醿撫儛 醿涐儛醿♂儣醿愥儨 醿撫儛醿欋儛醿曖儴醿樶儬醿斸儜醿a儦 醿п儠醿斸儦醿� 醿涐儩醿溼儛醿償醿涐儭!</string>
+    <string name="controlcenter_snackbar_need_longpress">醿撫儛醿愥儹醿樶儬醿� 醿戓儛醿犪儛醿椺儭 醿掅儛醿椺儤醿ㄡ儠醿樶儭醿椺儠醿樶儭</string>
+    <string name="controlcenter_snackbar_disconnecting">醿掅儛醿椺儤醿ㄡ儛醿曖儭</string>
+    <string name="controlcenter_snackbar_connecting">醿愥儥醿愥儠醿ㄡ儤醿犪償醿戓儭鈥�</string>
+    <string name="controlcenter_calibrate_device">醿涐儩醿儳醿濁儜醿樶儦醿濁儜醿樶儭 醿撫儛醿欋儛醿氠儤醿戓儬醿斸儜醿�</string>
+    <string name="debugactivity_really_factoryreset_title">醿掅儭醿a儬醿� 醿犪償醿愥儦醿a儬醿愥儞 醿涐儩醿儳醿濁儜醿樶儦醿濁儜醿樶儭 醿掅儛醿撫儛醿⑨儠醿樶儬醿椺儠醿怽?</string>
+    <string name="title_activity_appmanager">醿愥優醿氠儤醿欋儛醿儤醿愥儭 醿涐償醿溼償醿償醿犪儤</string>
+    <string name="appmanager_cached_watchapps_watchfaces">醿愥優醿氠儤醿欋儛醿儤醿斸儜醿� 醿メ償醿ㄡ儤醿ㄡ儤</string>
+    <string name="appmanager_installed_watchapps">醿撫儛醿樶儨醿♂儮醿愥儦醿樶儬醿斸儜醿a儦醿� 醿愥優醿氠儤醿欋儛醿儤醿斸儜醿�</string>
+    <string name="appmanager_installed_watchfaces">醿撫儛醿樶儨醿♂儮醿愥儦醿樶儬醿斸儜醿a儦醿� 醿儤醿め償醿犪儜醿氠儛醿⑨償醿戓儤</string>
+    <string name="appmananger_app_delete">醿儛醿ㄡ儦醿�</string>
+    <string name="appmananger_app_delete_cache">醿儛醿ㄡ儦醿� 醿撫儛 醿メ償醿ㄡ儤醿撫儛醿� 醿愥儧醿濁儲醿斸儜醿�</string>
+    <string name="appmananger_app_reinstall">醿樶儭醿斸儠 醿樶儨醿♂儮醿愥儦醿愥儶醿樶儛</string>
+    <string name="appmanager_app_openinstore">醿め償醿戓儦醿樶儭 醿涐儛醿︶儛醿栣儤醿愥儴醿� 醿償醿戓儨醿�</string>
+    <string name="appmanager_health_activate">醿掅儛醿愥儱醿⑨儤醿a儬醿斸儜醿�</string>
+    <string name="appmanager_health_deactivate">醿掅儛醿涐儩醿犪儮醿曖儛</string>
+    <string name="app_configure">醿欋儩醿溼儰醿樶儝醿a儬醿愥儶醿樶儛</string>
+    <string name="sonyswr12_settings_title">Sony SWR12 醿炨儛醿犪儛醿涐償醿⑨儬醿斸儜醿�</string>
+    <string name="sonyswr12_settings_alarm_interval">醿♂儤醿掅儨醿愥儦醿樶儭 醿儥醿曖儤醿愥儨醿� 醿樶儨醿⑨償醿犪儠醿愥儦醿� 醿儯醿椺償醿戓儴醿�</string>
+    <string name="sonyswr12_settings_low_vibration_summary">醿┽儛醿犪儣醿斸儣 醿曖儤醿戓儬醿愥儶醿樶儤醿� 醿撫儛醿戓儛醿氠儤 醿樶儨醿⑨償醿溼儭醿樶儠醿濁儜醿� 醿撫儤醿愥優醿愥儢醿濁儨醿ㄡ儤</string>
+    <string name="sonyswr12_settings_stamina_summary">醿斸儨醿斸儬醿掅儤醿樶儭 醿撫儛醿栣儩醿掅儠醿樶儭 醿犪償醿熱儤醿涐儤 醿掅儛醿涐儩醿犪儣醿愥儠醿� 醿掅儯醿氠儤醿♂儶醿斸儧醿樶儭 醿炨償醿犪儤醿濁儞醿a儦 醿愥儠醿⑨儩醿涐儛醿⑨儯醿� 醿掅儛醿栣儩醿涐儠醿愥儭 醿撫儛 醿愥儧醿樶儣 醿栣儬醿撫儤醿� 醿♂儛醿涐儯醿ㄡ儛醿� 醿撫儬醿濁儭</string>
+    <string name="sonyswr12_settings_alarm_interval_summary">醿儥醿曖儤醿愥儨醿� 醿掅儛醿溼儝醿愥儴醿樶儭 醿樶儨醿⑨償醿犪儠醿愥儦醿� 醿愥儬醿樶儭 醿撫儛醿樶儨醿♂儮醿愥儦醿樶儬醿斸儜醿a儦醿� 醿涐儛醿︶儠醿樶儷醿愥儬醿樶儭 醿樶儨醿⑨償醿犪儠醿愥儦醿�. 醿愥儧 醿樶儨醿⑨償醿犪儠醿愥儦醿ㄡ儤 醿涐儩醿儳醿濁儜醿樶儦醿濁儜醿� 醿儞醿樶儦醿濁儜醿� 醿掅儛醿涐儩醿愥儠醿氠儤醿溼儩醿� 醿儤醿氠儤醿� 醿п儠醿斸儦醿愥儢醿� 醿涐儭醿a儜醿a儱醿� 醿め儛醿栣儛, 醿犪儩醿� 醿掅儛醿樶儲醿曖儤醿儩醿� 醿涐儩醿涐儺醿涐儛醿犪償醿戓償醿氠儤</string>
+    <string name="sonyswr12_settings_low_vibration">醿┽儛醿犪儣醿a儦醿樶儛 醿撫儛醿戓儛醿氠儤 醿曖儤醿戓儬醿愥儶醿樶儛</string>
+    <string name="sonyswr12_settings_stamina">醿斸儨醿斸儬醿掅儤醿樶儭 醿撫儛醿栣儩醿掅儠醿樶儭 醿犪償醿熱儤醿涐儤 醿┽儛醿犪儣醿a儦醿樶儛</string>
+    <string name="fw_upgrade_notice">醿撫儛醿愥儤醿溼儭醿⑨儛醿氠儤醿犪償 %s.</string>
+    <string name="title_activity_fw_app_insaller">FW/醿愥優醿� 醿樶儨醿♂儮醿愥儦醿斸儬醿�</string>
+    <string name="title_activity_calblacklist">醿欋儛醿氠償醿溼儞醿愥儬醿� 醿ㄡ儛醿� 醿♂儤醿愥儴醿�</string>
+    <string name="app_move_to_top">醿栣償醿曖儤醿� 醿掅儛醿撫儛醿♂儛醿♂儠醿氠償醿氠儛醿�</string>
+    <string name="devicetype_amazfit_bipu">Amazfit Bip U</string>
+    <string name="devicetype_amazfit_bips_lite">Amazfit Bip S Lite</string>
+    <string name="devicetype_amazfit_bips">Amazfit Bip S</string>
+    <string name="devicetype_amazfit_trex">Amazfit T-Rex</string>
+    <string name="devicetype_amazfit_gtr">Amazfit GTR</string>
+    <string name="devicetype_amazfit_cor2">Amazfit Cor 2</string>
+    <string name="devicetype_amazfit_cor">Amazfit Cor</string>
+    <string name="devicetype_amazfit_bip_lite">Amazfit Bip Lite</string>
+    <string name="devicetype_amazfit_bip">Amazfit Bip</string>
+    <string name="devicetype_amazfit_neo">Amazfit Neo</string>
+    <string name="devicetype_amazfit_band5">Amazfit Band 5</string>
+    <string name="devicetype_miband5">Mi Band 5</string>
+    <string name="devicetype_miband4">Mi Band 4</string>
+    <string name="devicetype_miband3">Mi Band 3</string>
+    <string name="devicetype_miband2">Mi Band 2</string>
+    <string name="devicetype_miband">Mi Band</string>
+    <string name="devicetype_pebble">Pebble</string>
+    <string name="devicetype_test">醿涐儩醿儳醿濁儜醿樶儦醿濁儜醿樶儭 醿ㄡ償醿♂儛醿涐儩醿儧醿斸儜醿氠儛醿�</string>
+    <string name="devicetype_unknown">醿a儶醿溼儩醿戓儤 醿涐儩醿儳醿濁儜醿樶儦醿濁儜醿�</string>
+    <string name="kind_watchface">醿儤醿め償醿犪儜醿氠儛醿⑨儤</string>
+    <string name="kind_resources">醿犪償醿♂儯醿犪儭醿斸儜醿�</string>
+    <string name="kind_gps_cep">GPS 醿ㄡ償醿儞醿濁儧醿樶儭 醿ㄡ償醿♂儸醿濁儬醿斸儜醿�</string>
+    <string name="kind_gps_almanac">GPS 醿愥儦醿涐儛醿溼儛醿儤</string>
+    <string name="kind_gps">GPS Firmware</string>
+    <string name="kind_font">醿ㄡ儬醿樶儰醿⑨儤</string>
+    <string name="kind_invalid">醿愥儬醿愥儭醿儩醿犪儤 醿涐儩醿溼儛醿償醿涐償醿戓儤</string>
+    <string name="kind_firmware">Firmware</string>
+    <string name="reset_index">醿┽儛醿涐儩醿⑨儠醿樶儬醿椺儠醿樶儭 醿椺儛醿犪儤醿︶儤醿� 醿掅儛醿撫儛醿♂儛醿п償醿溼償醿戓儦醿愥儞</string>
+    <string name="share">醿掅儛醿栣儤醿愥儬醿斸儜醿�</string>
+    <string name="select_all">醿п儠醿斸儦醿愥儭 醿ㄡ償醿♂儛醿犪儵醿斸儠醿愥儞</string>
+    <string name="activity_error_no_app_for_png">醿愥儧 醿斸儥醿犪儛醿溼儤醿� 醿愥儨醿愥儜醿斸儹醿撫儤醿� 醿掅儛醿♂儛醿栣儤醿愥儬醿斸儜醿氠儛醿� 醿撫儛醿愥儤醿溼儭醿⑨儛醿氠儤醿犪償 醿愥優醿�, 醿犪儩醿涐償醿氠儭醿愥儶 醿ㄡ償醿a儷醿氠儤醿� 醿掅儛醿a儧醿欋儦醿愥儠醿撫償醿� 醿掅儛醿涐儩醿♂儛醿儯醿氠償醿戓儤醿� 醿め儛醿樶儦醿斸儜醿�.</string>
+    <string name="activity_error_no_app_for_gpx">醿愥儱醿⑨儤醿曖儩醿戓儤醿� 醿欋儠醿愥儦醿樶儭 醿♂儛醿溼儛醿儛醿曖儛醿�, 醿撫儛醿愥儤醿溼儭醿⑨儛醿氠儤醿犪償 醿愥優醿�, 醿犪儩醿涐償醿氠儭醿愥儶 醿ㄡ償醿a儷醿氠儤醿� GPX 醿め儛醿樶儦醿斸儜醿樶儭 醿涐儛醿犪儣醿曖儛.</string>
+    <string name="activity_type_exercise">醿曖儛醿犪儻醿樶儴醿�</string>
+    <string name="activity_type_treadmill">醿♂儛醿犪儜醿斸儨醿� 醿戓儤醿氠儤醿欋儤</string>
+    <string name="activity_type_biking">醿曖償醿氠儩醿♂儤醿炨償醿撫償醿戓儤</string>
+    <string name="activity_summary_yesterday">醿掅儯醿ㄡ儤醿�</string>
+    <string name="activity_summary_today">醿撫儲醿斸儭</string>
+    <string name="activity_summary_edit_name_title">醿犪償醿撫儛醿メ儮醿樶儬醿斸儜醿� 醿斸儮醿樶儥醿斸儮醿�</string>
+    <string name="activity_summary_detail">醿♂優醿濁儬醿⑨儯醿氠儤 醿♂儛醿メ儧醿樶儛醿溼儩醿戓儛醿� 醿撫償醿⑨儛醿氠儤</string>
+    <string name="activity_summaries">醿♂優醿濁儬醿⑨儯醿氠儤 醿♂儛醿メ儧醿樶儛醿溼儩醿戓儛</string>
+    <string name="activity_type_unknown">醿a儶醿溼儩醿戓儤 醿♂儛醿メ儧醿樶儛醿溼儩醿戓儛</string>
+    <string name="activity_type_strength_training">醿儛醿氠儤醿� 醿曖儛醿犪儻醿樶儴醿�</string>
+    <string name="activity_type_badminton">醿戓儛醿撫儧醿樶儨醿⑨儩醿溼儤</string>
+    <string name="activity_type_pingpong">醿涐儛醿掅儤醿撫儤醿� 醿⑨償醿溼儤醿♂儤</string>
+    <string name="activity_type_basketball">醿欋儛醿氠儛醿椺儜醿a儬醿椺儤</string>
+    <string name="activity_type_cricket">醿欋儬醿樶儥醿斸儮醿�</string>
+    <string name="activity_type_rowing_machine">醿溼儤醿┽儜醿濁儭醿溼儩醿戓儛</string>
+    <string name="activity_type_soccer">醿め償醿儜醿a儬醿椺儤</string>
+    <string name="activity_type_yoga">醿樶儩醿掅儛</string>
+    <string name="activity_type_jump_roping">醿儮醿濁儧醿� 醿椺儩醿欋儤</string>
+    <string name="activity_type_elliptical_trainer">醿斸儦醿樶儰醿♂儯醿犪儤 醿涐儸醿曖儬醿椺儨醿斸儦醿�</string>
+    <string name="activity_type_indoor_cycling">醿曖償醿氠儩醿♂儤醿炨償醿撫儤醿� 醿ㄡ償醿溼儩醿戓儛醿ㄡ儤</string>
+    <string name="activity_type_swimming_openwater">醿儯醿犪儠醿� (醿︶儤醿� 醿儳醿愥儦醿ㄡ儤)</string>
+    <string name="activity_type_swimming">醿儯醿犪儠醿�</string>
+    <string name="activity_type_walking">醿掅儛醿♂償醿樶儬醿溼償醿戓儛</string>
+    <string name="activity_type_running">醿♂儤醿犪儜醿樶儦醿�</string>
+    <string name="activity_type_not_worn">醿掅儛醿償醿⑨儤 醿愥儬 醿愥儬醿樶儭 醿溼儛醿儧醿愥儬醿�</string>
+    <string name="activity_type_deep_sleep">醿︶儬醿涐儛 醿儤醿氠儤</string>
+    <string name="activity_type_light_sleep">醿涐儭醿a儜醿a儱醿� 醿儤醿氠儤</string>
+    <string name="activity_type_activity">醿♂儛醿メ儧醿樶儛醿溼儩醿戓儛</string>
+    <string name="activity_type_not_measured">醿愥儬 醿樶儢醿濁儧醿斸儜醿�</string>
+    <string name="controlcenter_start_activity_tracks">醿ㄡ償醿溼儤 醿♂儛醿メ儧醿樶儛醿溼儩醿戓儤醿� 醿⑨儬醿斸儥醿斸儜醿�</string>
+    <string name="_pebble_watch_reply">醿炨儛醿♂儯醿儤</string>
+    <string name="_pebble_watch_mute">醿撫儛醿撫儯醿涐償醿戓儛</string>
+    <string name="_pebble_watch_open_on_phone">Android 醿涐儩醿儳醿濁儜醿樶儦醿濁儜醿樶儣 醿掅儛醿♂儛醿儭醿溼償醿氠儛醿�</string>
+    <string name="discovery_dont_pair">醿愥儬 醿撫儛醿儳醿曖儤醿氠儞醿斸儭</string>
+    <string name="discovery_yes_pair">醿撫儛醿儳醿曖儤醿氠償醿戓儛</string>
+    <string name="discovery_pair_question">醿愥儤醿犪儵醿樶償醿� \"醿撫儛醿儳醿曖儤醿氠償醿戓儛\" 醿椺儱醿曖償醿溼儤 醿涐儩醿儳醿濁儜醿樶儦醿濁儜醿斸儜醿樶儭 醿撫儛醿♂儛醿儳醿曖儤醿氠償醿戓儦醿愥儞. 醿椺儯 醿斸儭 醿曖償醿� 醿涐儩醿償醿犪儺醿撫儛, 醿樶儭醿斸儠 醿♂儶醿愥儞醿� 醿撫儛醿儳醿曖儤醿氠償醿戓儤醿� 醿掅儛醿犪償醿ㄡ償.</string>
+    <string name="discovery_pair_title">醿a儨醿撫儛 醿撫儛醿儳醿曖儤醿氠儞醿斸儭 %1$s醿?</string>
+    <string name="discovery_successfully_bonded">%1$s醿椺儛醿溼儛醿� 醿ㄡ償醿欋儬醿a儦醿�.</string>
+    <string name="discovery_enable_bluetooth">醿a儨醿撫儛 醿┽儛醿犪儣醿� Bluetooth 醿涐儩醿儳醿濁儜醿樶儦醿濁儜醿斸儜醿樶儭 醿愥儲醿涐儩醿♂儛醿┽償醿溼儛醿�.</string>
+    <string name="discovery_trying_to_connect_to">醿儞醿樶儦醿濁儜醿� %1$s醿椺儛醿� 醿撫儛醿欋儛醿曖儴醿樶儬醿斸儜醿愥儭</string>
+    <string name="discovery_attempting_to_pair">醿儞醿樶儦醿濁儜醿� %1$s醿椺儛醿� 醿撫儛醿儳醿曖儤醿氠償醿戓儛醿�</string>
+    <string name="discovery_bonding_failed_immediately">%1$s醿椺儛醿� 醿撫儛醿儳醿曖儤醿氠償醿戓儛 醿涐儛醿ㄡ儤醿溼儠醿� 醿曖償醿� 醿涐儩醿償醿犪儺醿撫儛.</string>
+    <string name="maximum_duration">醿儛醿溼儝醿犪儷醿氠儤醿曖儩醿戓儛</string>
+    <string name="mi2_dnd_scheduled">醿撫儛醿掅償醿掅儧醿樶儦醿� (醿撫儬醿濁儤醿� 醿樶儨醿⑨償醿犪儠醿愥儦醿�)</string>
+    <string name="mi2_dnd_automatic">醿愥儠醿⑨儩醿涐儛醿⑨儯醿犪儤 (醿儤醿氠儤醿� 醿掅儛醿涐儩醿曖儦醿斸儨醿�)</string>
+    <string name="mi3_night_mode_sunset">醿涐儢醿樶儭 醿┽儛醿♂儠醿氠儤醿♂儛醿�</string>
+    <string name="mi2_dnd_off">醿掅儛醿涐儩醿犪儣醿曖儛</string>
+    <string name="off">醿掅儛醿涐儩醿犪儣醿曖儛</string>
+    <string name="on">醿┽儛醿犪儣醿曖儛</string>
+    <string name="mi2_enable_text_notifications_summary">醿♂儹醿樶儬醿撫償醿戓儛 醿撫儛醿樶儨醿♂儮醿愥儦醿樶儬醿斸儜醿a儦醿� firmware &gt;= 1.0.1.28 醿撫儛 Mili_pro.ft*.</string>
+    <string name="mi2_enable_text_notifications">醿⑨償醿メ儭醿⑨儯醿犪儤 醿ㄡ償醿⑨儳醿濁儜醿樶儨醿斸儜醿斸儜醿�</string>
+    <string name="mi2_fw_installhandler_fw53_hint">醿愥儧 firmware-醿樶儭 醿樶儨醿♂儮醿愥儦醿愥儶醿樶儛醿涐儞醿� 醿a儨醿撫儛 醿撫儛醿愥儳醿斸儨醿濁儣 %1$s 醿曖償醿犪儭醿樶儛!</string>
+    <string name="miband2_prefs_timeformat">Mi2: 醿撫儬醿濁儤醿� 醿め儩醿犪儧醿愥儮醿�</string>
+    <string name="find_lost_device_you_found_it">醿曖儤醿炨儩醿曖償!</string>
+    <string name="find_device_you_found_it">醿樶優醿濁儠醿溼償!</string>
+    <string name="StringUtils_sender">(%1$s)</string>
+    <string name="activity_web_view">醿愥儱醿⑨儤醿曖儩醿戓儤醿� 醿曖償醿� 鈥� 醿償醿撫儤</string>
+    <string name="pref_screen_notification_profile_alarm_clock">醿涐儛醿︶儠醿樶儷醿愥儬醿�</string>
+    <string name="prefs_charts_tabs">醿撫儤醿愥儝醿犪儛醿涐儤醿� 醿┽儛醿溼儛醿犪儣醿斸儜醿�</string>
+    <string name="notif_export_failed_title">醿涐儩醿溼儛醿償醿涐儣醿� 醿戓儛醿栣儤醿� 醿斸儱醿♂優醿濁儬醿⑨儤 醿曖償醿� 醿掅儛醿溼儺醿濁儬醿儤醿斸儦醿撫儛! 醿掅儣醿儩醿�, 醿ㄡ償醿愥儧醿濁儸醿涐儩 醿ㄡ償醿溼儤 醿炨儛醿犪儛醿涐償醿⑨儬醿斸儜醿�.</string>
+    <string name="notif_battery_low_extended">%1$s 醿斸儦醿斸儧醿斸儨醿⑨儤 醿儞醿斸儜醿�: %2$s</string>
+    <string name="notif_battery_low">%1$s 醿斸儦醿斸儧醿斸儨醿⑨儤 醿儞醿斸儜醿�</string>
+    <string name="notif_battery_low_bigtext_number_of_charges">醿撫儛醿⑨償醿溼儠醿樶儭 醿犪儛醿濁儞醿斸儨醿濁儜醿�: %s</string>
+    <string name="notif_battery_low_bigtext_last_charge_time">醿戓儩醿氠儩 醿撫儛醿⑨償醿溼儠醿�: %s
+\n</string>
+    <string name="notif_battery_low_percent">%1$s 醿愥儥醿a儧醿a儦醿愥儮醿濁儬醿樶儭 醿撫儛醿溼儛醿犪儵醿斸儨醿樶儛: %2$s%%</string>
+    <string name="notif_battery_low_title">醿掅儛醿償醿⑨儤醿� 醿愥儥醿a儧醿a儦醿愥儮醿濁儬醿� 醿撫儛醿戓儛醿氠儤醿�!</string>
+    <string name="installer_activity_wait_while_determining_status">醿掅儣醿儩醿�, 醿撫儛醿斸儦醿濁儞醿� 醿♂儛醿溼儛醿� 醿撫儛醿撫儝醿樶儨醿撫償醿戓儛 醿樶儨醿♂儮醿愥儦醿愥儶醿樶儤醿� 醿♂儮醿愥儮醿a儭醿樷��</string>
+    <string name="pbw_install_handler_hw_revision_mismatch">醿ㄡ償醿a儷醿氠償醿戓償醿氠儤醿� 醿涐儩醿償醿涐儯醿氠儤 firmware 醿撫儛醿樶儨醿♂儮醿愥儦醿樶儬醿斸儜醿�: 醿愥儬 醿斸儧醿椺儺醿曖償醿曖儛 醿椺儱醿曖償醿溼儤 Pebble- 醿樶儭 醿⑨償醿メ儨醿樶儥醿a儬 醿曖償醿犪儭醿樶儛醿�.</string>
+    <string name="pbw_install_handler_unable_to_install">醿愥儬 醿ㄡ償醿a儷醿氠儤醿� 醿愥儧 醿め儛醿樶儦醿樶儭 醿樶儨醿♂儮醿愥儦醿愥儶醿樶儛: %1$s</string>
+    <string name="installer_activity_unable_to_find_handler">醿愥儬 醿ㄡ償醿a儷醿氠儤醿� 醿愥儧 醿め儛醿樶儦醿樶儭 醿撫儛醿♂儛醿п償醿溼償醿戓儦醿愥儞 醿撫儛醿涐儧醿a儴醿愥儠醿斸儜醿氠儤醿� 醿炨儩醿曖儨醿�.</string>
+    <string name="cannot_connect">醿愥儬 醿ㄡ償醿a儷醿氠儤醿� 醿撫儛醿欋儛醿曖儴醿樶儬醿斸儜醿�: %1$s</string>
+    <string name="controlcenter_start_activitymonitor">醿ㄡ償醿溼儤 醿♂儛醿メ儧醿樶儛醿溼儩醿戓儛</string>
+    <string name="dbaccess_error_executing">\'%1$s\'-醿樶儭 醿ㄡ償醿♂儬醿a儦醿斸儜醿樶儭醿愥儭 醿涐儩醿儞醿� 醿ㄡ償醿儞醿濁儧醿�</string>
+    <string name="miband_prefs_fitness_goal">醿п儩醿曖償醿氠儞醿︶儤醿a儬醿� 醿溼儛醿戓儤醿儤醿� 醿涐儤醿栣儛醿溼儤</string>
+    <string name="user_feedback_miband_activity_data_transfer">%1$s 醿涐儩醿溼儛醿償醿涐償醿戓儤醿� 醿掅儛醿撫儛醿⑨儛醿溼儛醿� %2$s-醿撫儛醿� 醿撫儛醿儳醿斸儜醿a儦醿�</string>
+    <string name="chart_get_active_and_synchronize">醿撫儛醿欋儛醿曖儞醿� 醿愥儱醿⑨儤醿曖儩醿戓儤醿� 醿撫儛 醿掅儛醿愥儥醿斸儣醿� 醿涐儩醿儳醿濁儜醿樶儦醿濁儜醿樶儭 醿♂儤醿溼儱醿犪儩醿溼儤醿栣儛醿儤醿�.</string>
+    <string name="chart_no_active_data">醿愥儱醿⑨儤醿曖儩醿戓儛 醿愥儬 醿愥儬醿樶儭 醿掅儛醿涐儩醿曖儦醿斸儨醿樶儦醿�.</string>
+    <string name="chart_no_data_synchronize">岵涐儩醿溼儛醿償醿涐償醿戓儤 醿愥儬 醿愥儬醿樶儭. 醿涐儩醿儳醿濁儜醿樶儦醿濁儜醿樶儭 醿♂儤醿溼儱醿犪儩醿溼儤醿栣儛醿儤醿怽?</string>
+    <string name="user_feedback_miband_set_alarms_ok">醿涐儛醿︶儠醿樶儷醿愥儬醿斸儜醿� 醿掅儛醿掅儢醿愥儠醿溼儤醿� 醿樶儱醿溼儛 醿涐儩醿儳醿濁儜醿樶儦醿濁儜醿愥儴醿�.</string>
+    <string name="user_feedback_miband_set_alarms_failed">醿涐儛醿︶儠醿樶儷醿愥儬醿斸儜醿樶儭 醿撫儛醿п償醿溼償醿戓儤醿♂儛醿� 醿涐儩醿儞醿� 醿ㄡ償醿儞醿濁儧醿�, 岵掅儣醿儩醿曖儣 醿欋儤醿撫償醿� 醿♂儶醿愥儞醿�.</string>
+    <string name="alarm_snooze">醿┽儛醿儤醿溼償醿戓儛</string>
+    <string name="alarm_smart_wakeup">醿儥醿曖儤醿愥儨醿� 醿掅儛醿︶儠醿樶儷醿斸儜醿�</string>
+    <string name="alarm_sat_short">醿ㄡ儛醿�</string>
+    <string name="alarm_fri_short">醿炨儛醿�</string>
+    <string name="alarm_thu_short">醿儯醿�</string>
+    <string name="appmanager_weather_install_provider">醿撫儛醿愥儤醿溼儭醿⑨儛醿氠儤醿犪償 醿愥儧醿樶儨醿撫儤醿� 醿ㄡ償醿⑨儳醿濁儜醿樶儨醿斸儜醿樶儭 醿愥優醿�</string>
+    <string name="appmanager_weather_activate">醿掅儛醿愥儛醿メ儮醿樶儯醿犪償 醿♂儤醿♂儮醿斸儧醿樶儭 醿愥儧醿樶儨醿撫儤醿� 醿愥優醿氠儤醿欋儛醿儤醿�</string>
+    <string name="appmanager_weather_deactivate">醿掅儛醿涐儩醿犪儣醿� 醿♂儤醿♂儮醿斸儧醿樶儭 醿愥儧醿樶儨醿撫儤醿� 醿愥優醿氠儤醿欋儛醿儤醿�</string>
+    <string name="appmanager_hrm_activate">HRM 醿掅儛醿愥儱醿⑨儤醿a儬醿斸儜醿�</string>
+    <string name="appmanager_hrm_deactivate">醿掅儛醿涐儩醿犪儣醿� HRM</string>
+    <string name="controlcenter_snackbar_requested_screenshot">醿掅儛醿欋償醿椺償醿戓儛 醿♂儥醿犪儤醿溼儴醿濁儮醿�</string>
+    <string name="controlcenter_navigation_drawer_close">醿♂儛醿溼儛醿曖儤醿掅儛醿儤醿� 醿a儻醿犪儤醿� 醿撫儛醿儯醿犪儠醿�</string>
+    <string name="controlcenter_navigation_drawer_open">醿♂儛醿溼儛醿曖儤醿掅儛醿儤醿� 醿a儻醿犪儤醿� 醿掅儛醿♂儛醿儭醿溼償醿氠儛醿�</string>
+    <string name="prefs_autoremove_message">醿愥儠醿⑨儩醿涐儛醿⑨儯醿犪儛醿� 醿愥儧醿濁儤醿︶儩醿� SMS 醿ㄡ償醿⑨儳醿濁儜醿樶儨醿斸儜醿斸儜醿�</string>
+    <string name="debugactivity_really_factoryreset">醿メ儛醿犪儺醿溼儤醿� 醿掅儛醿撫儛醿⑨儠醿樶儬醿椺儠醿樶儭醿愥儭 醿儛醿樶儴醿氠償醿戓儛 醿п儠醿斸儦醿� 醿涐儩醿溼儛醿償醿涐儤 醿撫儛醿欋儛醿曖儴醿樶儬醿斸儜醿a儦醿� 醿涐儩醿儳醿濁儜醿樶儦醿濁儜醿樶儞醿愥儨 (醿椺儯 醿涐儺醿愥儬醿撫儛醿償醿犪儤醿氠儤醿�). Xiaomi/Huami 醿涐儩醿儳醿濁儜醿樶儦醿濁儜醿斸儜醿� 醿愥儭醿斸儠醿� 醿儠醿氠儤醿� Bluetooth MAC 醿涐儤醿♂儛醿涐儛醿犪儣醿�, 醿愥儧醿樶儮醿濁儧 醿樶儭醿樶儨醿� Gadgetbridge- 醿樶儭醿椺儠醿樶儭 醿愥儺醿愥儦醿� 醿涐儩醿儳醿濁儜醿樶儦醿濁儜醿斸儜醿樶儣 醿掅儛醿涐儩醿┽儨醿撫償醿戓儤醿愥儨.</string>
+    <string name="title_activity_debug">醿掅儛醿涐儛醿犪儣醿曖儛</string>
+    <string name="calendar_year">醿償醿氠儤</string>
+    <string name="calendar_six_months">醿斸儱醿曖儭醿� 醿椺儠醿�</string>
+    <string name="calendar_month">醿椺儠醿�</string>
+    <string name="calendar_two_weeks">醿濁儬醿� 醿欋儠醿樶儬醿�</string>
+    <string name="calendar_week">醿欋儠醿樶儬醿�</string>
+    <string name="calendar_day">醿撫儲醿斸儭</string>
+    <string name="battery_level">醿斸儦醿斸儧醿斸儨醿⑨儤醿� 醿撫儩醿溼償</string>
+    <string name="battery_detail_activity_title">醿斸儦醿斸儧醿斸儨醿⑨儤醿� 醿樶儨醿め儩醿犪儧醿愥儶醿樶儛</string>
+    <string name="controlcenter_set_alias">醿愥儤醿犪儵醿樶償 醿涐償醿⑨儭醿愥儺醿斸儦醿�</string>
+    <string name="find_lost_device_message">醿涐儩醿曖儷醿斸儜醿溼儩 %1$s\?</string>
+    <string name="controlcenter_take_screenshot">醿掅儛醿欋償醿椺償醿戓儛 醿♂儥醿犪儤醿溼儴醿濁儮醿�</string>
+    <string name="action_debug">醿掅儛醿涐儛醿犪儣醿曖儛</string>
+    <string name="calendar_three_months">3 醿椺儠醿�</string>
+    <string name="about_activity_title_generic">Gadgetbridge 醿ㄡ償醿♂儛醿償醿�</string>
+    <string name="blacklist_all_for_notifications">醿ㄡ償醿⑨儳醿濁儜醿樶儨醿斸儜醿斸儜醿樶儞醿愥儨 醿п儠醿斸儦醿愥儭 醿掅儛醿儺醿犪儤醿氠儠醿�</string>
+    <string name="whitelist_all_for_notifications">醿п儠醿斸儦醿� 醿掅儛醿め儬醿椺儺醿樶儦醿斸儜醿樶儭 醿椺償醿椺儬 醿♂儤醿愥儴醿� 醿┽儛醿♂儧醿�</string>
+    <string name="pref_header_sensors_calibration">醿♂償醿溼儭醿濁儬醿樶儭 醿欋儛醿氠儤醿戓儬醿愥儶醿樶儛</string>
+</resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml
new file mode 100644
index 0000000..ed6e307
--- /dev/null
+++ b/app/src/main/res/values-ko/strings.xml
@@ -0,0 +1,768 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <string name="application_name_generic">臧�鞝笇毽快</string>
+    <string name="title_activity_controlcenter_generic">臧�鞝笇毽快</string>
+    <string name="action_settings">靹れ爼</string>
+    <string name="action_debug">霐旊矂攴�</string>
+    <string name="action_quit">膦呺</string>
+    <string name="controlcenter_fetch_activity_data">霃欔赴頇�</string>
+    <string name="controlcenter_find_device">鞛冹柎氩勲Π 旮瓣赴 彀娟赴</string>
+    <string name="controlcenter_take_screenshot">鞀ろ伂毽办兎 彀嶊赴</string>
+    <string name="controlcenter_disconnect">鞐瓣舶 頃挫牅</string>
+    <string name="title_activity_debug">霐旊矂攴�</string>
+    <!--Strings related to AppManager-->
+    <string name="title_activity_appmanager">鞎� 甏�毽瀽</string>
+    <string name="appmananger_app_delete">靷牅</string>
+    <string name="appmananger_app_reinstall">鞛劋旃�</string>
+    <string name="appmanager_health_activate">頇滌劚頇�</string>
+    <string name="appmanager_health_deactivate">牍勴櫆靹表檾</string>
+    <string name="app_configure">靹れ爼</string>
+    <!--Strings related to FwAppInstaller-->
+    <string name="title_activity_fw_app_insaller">韼岇洦鞏�/鞎� 靹れ箻甏�毽瀽</string>
+    <string name="fw_upgrade_notice">%s鞚�/毳� 靹れ箻頃╇媹雼�.</string>
+    <string name="fw_multi_upgrade_notice">歆�旮� 氙鸽按霌滌棎 旮办〈 韼岇洦鞏� 雽�鞁� %1$s鞕� %2$s 韼岇洦鞏措ゼ 靹れ箻頃橂牑瓿� 頃╇媹雼�.</string>
+    <string name="miband_firmware_known">鞚� 韼岇洦鞏措姅 韰岇姢韸鸽ゼ 瓯办长瓿� 臧�鞝笇毽快鞕� 順疙櫂靹膘澊 頇曥澑霅橃棃鞀惦媹雼�.</string>
+    <string name="miband_firmware_unknown_warning">鞚� 韼岇洦鞏措姅 韰岇姢韸鸽ゼ 瓯办箻歆� 鞎婌晿瓿� 臧�鞝笇毽快鞕� 順疙櫂霅橃 鞎婌潉 靾� 鞛堨姷雼堧嫟.\n\n氙鸽按霌滌棎 靹れ箻頃橃 鞎婋姅 瓴冹澊 膦嬱姷雼堧嫟!</string>
+    <string name="miband_firmware_suggest_whitelist">攴鸽灅霃� 瓿勳啀 歆勴枆頃橁赴毳� 鞗愴晿瓿� 鞚错泟鞐� 氍胳牅鞐嗢澊 鞛戨彊頃滊嫟氅�, %s 韼岇洦鞏搓皜 鞝曥儊鞝侅溂搿� 霃欖瀾頃滊嫟瓿� 臧�鞝笇毽快 臧滊皽歆勳棎瓴� 鞎岆牑欤检劯鞖�.</string>
+    <!--Strings related to Settings-->
+    <string name="title_activity_settings">靹れ爼</string>
+    <string name="pref_header_general">鞚茧皹 靹れ爼</string>
+    <string name="pref_title_general_autoconnectonbluetooth">敫旊(韴姢毳� 旒滊┐ 臧�鞝笇毽快 旮瓣赴鞐� 鞐瓣舶</string>
+    <string name="pref_title_general_autoreconnect">鞛愲彊 鞛棸瓴�</string>
+    <string name="pref_title_audio_player">靹犿樃 鞚岇晠頂岆爤鞚挫柎</string>
+    <string name="pref_default">旮半掣臧�</string>
+    <string name="pref_header_datetime">雮犾鞕� 鞁滉皠</string>
+    <string name="pref_title_datetime_syctimeonconnect">鞁滉皠 霃欔赴頇�</string>
+    <string name="pref_summary_datetime_syctimeonconnect">鞐瓣舶鞁�, 鞁滉皠 霕愲姅 鞁滉皠雽� 氤�瓴届嫓, 霕� 欤缄赴鞝侅溂搿� 臧�鞝笇毽快 旮瓣赴鞕� 鞁滉皠 霃欔赴頇�</string>
+    <string name="pref_title_theme">韰岆</string>
+    <string name="pref_theme_light">氚濎潓</string>
+    <string name="pref_theme_dark">鞏措憪鞗�</string>
+    <string name="pref_header_notifications">鞎岆</string>
+    <string name="pref_title_notifications_repetitions">氚橂车</string>
+    <string name="pref_title_notifications_call">鞝勴檾</string>
+    <string name="pref_title_notifications_sms">SMS</string>
+    <string name="pref_title_notifications_pebblemsg">韼橂笖 氅旍劯歆�</string>
+    <string name="pref_title_notifications_generic">鞚茧皹 鞎岆 歆�鞗�</string>
+    <string name="pref_title_whenscreenon">鈥檾氅� 旒滌 靸來儨 韽暔</string>
+    <string name="always">頃儊</string>
+    <string name="when_screen_off">頇旊┐鞚� 旰检牳 鞛堨潉 霑�</string>
+    <string name="never">頃橃 鞎婌潓</string>
+    <string name="pref_title_canned_reply_suffix">鞚茧皹鞝侅澑 鞝戨靷�</string>
+    <string name="pref_header_development">臧滊皽鞛� 鞓奠厴</string>
+    <string name="pref_title_development_miaddr">Mi Band 欤检唽</string>
+    <string name="pref_title_pebble_settings">Pebble 靹れ爼</string>
+    <string name="pref_title_pebble_activitytracker">靹犿樃頃橂姅 頇滊彊 於旍爜鞛�</string>
+    <string name="pref_title_enable_pebblekit">鞝� 3鞛� 鞎堧摐搿滌澊霌� 鞎� 鞝戧芳鞚� 項堨毄</string>
+    <string name="pref_summary_enable_pebblekit">PebbleKit鞚� 靷毄頃橂姅 鞎堧摐搿滌澊霌� 鞎膘潉 鞁ろ棙鞝侅溂搿� 歆�鞗�</string>
+    <string name="pref_title_pebble_forceprotocol">臧曥牅 鞎岆 頂勲韱犾綔</string>
+    <string name="pref_summary_pebble_forceprotocol">鞚� 鞓奠厴鞚� 韼岇洦鞏� 氩勳爠鞐� 霐半澕 斓滌嫚 鞎岆 頂勲韱犾綔鞚� 臧曥牅搿� 靷毄頃橂弰搿� 頃╇媹雼�. 鞛愳劯頃� 雮挫毄鞚� 鞎� 瓴届毎鞐愲 鞚� 鞓奠厴鞚� 靷毄頃橃劯鞖�!</string>
+    <string name="pref_title_pebble_forceuntested">韰岇姢韸鸽ゼ 瓯办箻歆� 鞎婌潃 旮半姤 靷毄</string>
+    <string name="pref_summary_pebble_forceuntested">韰岇姢韸鸽ゼ 瓯办箻歆� 鞎婌潃 旮半姤鞚� 頇滌劚頇旐暕雼堧嫟. 頇曥嫟頃橁矊 鞎勲姅 瓴届毎鞐愲 靷毄頃橃劯鞖�!</string>
+    <string name="pref_title_pebble_reconnect_attempts">鞛爲靻� 鞁滊弰</string>
+    <string name="not_connected">鞐瓣舶霅橃 鞎婌潓</string>
+    <string name="connecting">鞐瓣舶 欷�</string>
+    <string name="connected">鞐瓣舶霅�</string>
+    <string name="unknown_state">鞎� 靾� 鞐嗠姅 靸來儨</string>
+    <string name="_unknown_">(鞎� 靾� 鞐嗢潓)</string>
+    <string name="test">韰岇姢韸�</string>
+    <string name="test_notification">鞎岆 韰岇姢韸�</string>
+    <string name="this_is_a_test_notification_from_gadgetbridge">鞚� 鞎岆鞚� 臧�鞝笇毽快臧� 氤措偢 韰岇姢韸胳毄 鞎岆鞛呺媹雼�</string>
+    <string name="bluetooth_is_not_supported_">敫旊(韴姢臧� 歆�鞗愲悩歆� 鞎婌姷雼堧嫟.</string>
+    <string name="bluetooth_is_disabled_">敫旊(韴姢臧� 頃挫牅霅橃柎 鞛堨姷雼堧嫟.</string>
+    <string name="tap_connected_device_for_app_mananger">鞐瓣舶霅� 旮瓣赴毳� 靹犿儩頃� 鞎� 甏�毽瀽 鞁ろ枆</string>
+    <string name="tap_a_device_to_connect">旮瓣赴毳� 靹犿儩頃� 鞐瓣舶</string>
+    <string name="cannot_connect_bt_address_invalid_">鞐瓣舶頃� 靾� 鞐嗢姷雼堧嫟. 敫旊(韴姢 欤检唽臧� 鞓皵毳挫 鞎婌潃 瓴� 臧欖姷雼堧嫟\?</string>
+    <string name="gadgetbridge_running_generic">臧�鞝笇毽快 鞁ろ枆欷�</string>
+    <string name="installing_binary_d_d">氚旍澊雱堧Μ 靹れ箻欷�: %1$d/%2$d</string>
+    <string name="installation_failed_">靹れ箻 鞁ろ尐</string>
+    <string name="installation_successful">靹れ箻霅�</string>
+    <string name="firmware_install_warning">韼岇洦鞏措ゼ 靹れ箻頃橂牑瓿� 頃╇媹雼�. 鞙勴棙鞐� 雽�頃� 毂呾瀯鞚� 歆�瓿� 歆勴枆頃橃劯鞖�.\n\n\n 鞚� 韼岇洦鞏措姅 雼れ潓 頃橂摐鞗柎 毽箘鞝勳棎 靷毄霅╇媹雼�: %s</string>
+    <string name="app_install_info">歆�旮� 雼れ潓 鞎膘潉 靹れ箻頃橂牑瓿� 頃╇媹雼�:\n\n\n%1$s 氩勳爠 %2$s , 雼れ潓鞙茧攵�韯�: %3$s\n</string>
+    <string name="n_a">N/A</string>
+    <string name="initialized">鞁滌瀾霅�</string>
+    <string name="appversion_by_creator">%1$s by %2$s</string>
+    <string name="title_activity_discovery">旮瓣赴 彀娟赴</string>
+    <string name="discovery_stop_scanning">鞀れ簲 欷戩</string>
+    <string name="discovery_start_scanning">旮瓣赴 韮愳儔 鞁滌瀾</string>
+    <string name="action_discover">靸� 旮瓣赴 鞐瓣舶</string>
+    <string name="device_with_rssi">%1$s (%2$s)</string>
+    <string name="title_activity_android_pairing">旮瓣赴 韼橃柎毵�</string>
+    <string name="android_pairing_hint">旮瓣赴毳� 韼橃柎毵來晿霠る┐ 鞎堧摐搿滌澊霌� 敫旊(韴姢 韼橃柎毵� 靹れ爼鞚� 靷毄頃橃劯鞖�.</string>
+    <string name="title_activity_mi_band_pairing">Mi Band 韼橃柎毵�</string>
+    <string name="pairing">%s鞐� 韼橃柎毵佲��</string>
+    <string name="message_cannot_pair_no_mac">MAC 欤检唽臧� 韱店臣霅橃 鞎婌晿鞀惦媹雼�. 韼橃柎毵� 頃� 靾� 鞐嗢姷雼堧嫟.</string>
+    <string name="preferences_category_device_specific_settings">旮瓣赴 韸轨爼 靹れ爼</string>
+    <string name="preferences_miband_settings">Mi Band / Amazfit 靹れ爼</string>
+    <string name="male">雮劚</string>
+    <string name="female">鞐劚</string>
+    <string name="other">旮绊儉</string>
+    <string name="left">鞕检</string>
+    <string name="right">鞓るジ飒�</string>
+    <string name="miband_pairing_using_dummy_userdata">鞓皵毳挫 鞎婌潃 靷毄鞛� 鞝曤炒鞛呺媹雼�. 鞚茧嫧 鞛勳嫓 靷毄鞛� 鞝曤炒毳� 靷毄頃╇媹雼�.</string>
+    <string name="miband_pairing_tap_hint">Mi Band臧� 歆勲彊頃橁碃 旯滊埂鞚� 霑�, 鞐半嫭鞎� 氇� 氩� 霊愲摐毽劯鞖�.</string>
+    <string name="appinstaller_install">靹れ箻</string>
+    <string name="discovery_note">鞎岆: </string>
+    <string name="candidate_item_device_image">旮瓣赴 鞚措歆�</string>
+    <string name="miband_prefs_alias">鞚措/氤勲獏</string>
+    <string name="pref_header_vibration_count">歆勲彊 須熿垬</string>
+    <string name="title_activity_sleepmonitor">靾橂┐ 旄§爼瓿�</string>
+    <string name="pref_write_logfiles">搿滉犯 韺岇澕 鞛戩劚</string>
+    <string name="initializing">齑堦赴頇� 欷�</string>
+    <string name="busy_task_fetch_activity_data">頇滊彊 雿办澊韯� 臧�鞝胳槫電� 欷�</string>
+    <string name="sleep_activity_date_range">%1$s鞐愳劀 %2$s(鞙�)搿�</string>
+    <string name="prefs_wearside">鞕检瓿� 鞓るジ飒� 欷� 鞏措姁 氚╉枼鞙茧 彀╈毄頃╇媹旯�?</string>
+    <string name="pref_screen_vibration_profile">歆勲彊 頂勲韺岇澕</string>
+    <string name="vibration_profile_staccato">鞀ろ儉旃错啝</string>
+    <string name="vibration_profile_short">歆ш矊</string>
+    <string name="vibration_profile_medium">氤错喌</string>
+    <string name="vibration_profile_long">旮戈矊</string>
+    <string name="vibration_profile_waterdrop">氍茧癌鞖�</string>
+    <string name="vibration_profile_ring">氩唽毽�</string>
+    <string name="vibration_profile_alarm_clock">鞎岆瀸 鞁滉硠</string>
+    <string name="miband_prefs_vibration">歆勲彊</string>
+    <string name="pref_screen_notification_profile_sms">SMS 鞎岆</string>
+    <string name="pref_header_vibration_settings">歆勲彊 靹れ爼</string>
+    <string name="pref_screen_notification_profile_generic">鞚茧皹 鞎岆</string>
+    <string name="pref_screen_notification_profile_incoming_call">瓯鸽牑鞓る姅 鞝勴檾 鞎岆</string>
+    <string name="control_center_find_lost_device">鞛冹柎氩勲Π 旮瓣赴 彀娟赴</string>
+    <string name="control_center_cancel_to_stop_vibration">歆勲彊鞚� 氅堨稊霠る┐ 旆唽毳� 靹犿儩頃橃劯鞖�.</string>
+    <string name="title_activity_charts">雼轨嫚鞚� 頇滊彊</string>
+    <string name="title_activity_set_alarm">鞎岆瀸 靹れ爼</string>
+    <string name="controlcenter_start_configure_alarms">鞎岆瀸 靹れ爼</string>
+    <string name="title_activity_alarm_details">鞎岆瀸 靸侅劯 鞝曤炒</string>
+    <string name="alarm_sun_short">鞚检殧鞚�</string>
+    <string name="alarm_mon_short">鞗旍殧鞚�</string>
+    <string name="alarm_tue_short">頇旍殧鞚�</string>
+    <string name="alarm_wed_short">靾橃殧鞚�</string>
+    <string name="alarm_thu_short">氇╈殧鞚�</string>
+    <string name="alarm_fri_short">旮堨殧鞚�</string>
+    <string name="alarm_sat_short">韱犾殧鞚�</string>
+    <string name="alarm_smart_wakeup">鞀る韸疙暅 鞎岆瀸</string>
+    <string name="user_feedback_miband_set_alarms_failed">鞎岆瀸 靹れ爼 鞓る臧� 氚滌儩頄堨姷雼堧嫟. 雼れ嫓 鞁滊弰頃橃劯鞖�.</string>
+    <string name="user_feedback_miband_set_alarms_ok">鞎岆瀸鞚� 旮瓣赴搿� 氤措儓鞀惦媹雼�.</string>
+    <string name="chart_no_data_synchronize">雿办澊韯瓣皜 鞐嗢姷雼堧嫟. 旮瓣赴鞕� 霃欔赴頇旐暊旯岇殧?</string>
+    <string name="user_feedback_miband_activity_data_transfer">%2$s 鞐愳劀攵�韯� %1$s 雿办澊韯半ゼ 鞝勳啞頃橂牑瓿� 頃╇媹雼�</string>
+    <string name="miband_prefs_fitness_goal">鞚检澕 氇╉憸 瓯胳潓 靾�</string>
+    <string name="dbaccess_error_executing">\'%1$s\' 鞁ろ枆 鞓る</string>
+    <string name="controlcenter_start_activitymonitor">雼轨嫚鞚� 頇滊彊 (齑堦赴 氩勳爠)</string>
+    <string name="cannot_connect">鞝戩啀頃� 靾� 鞐嗢潓: %1$s</string>
+    <string name="installer_activity_unable_to_find_handler">鞚� 韺岇澕鞚� 靹れ箻頃橂姅 雿� 頃勳殧頃� 頃鸽摛霟ゼ 彀眷潉 靾� 鞐嗢姷雼堧嫟.</string>
+    <string name="pbw_install_handler_unable_to_install">欤检柎歆� 韺岇澕鞚� 靹れ箻頃� 靾� 鞐嗢姷雼堧嫟: %1$s</string>
+    <string name="pbw_install_handler_hw_revision_mismatch">欤检柎歆� 韼岇洦鞏措ゼ 靹れ箻頃� 靾� 鞐嗢姷雼堧嫟. Pebble鞚� 頃橂摐鞗柎 毽箘鞝勱臣 鞚检箻頃橃 鞎婌姷雼堧嫟.</string>
+    <string name="installer_activity_wait_while_determining_status">靹れ箻 靸來儨毳� 韺岇晠頃橂姅 霃欖晥 旮半嫟霠れ<靹胳殧鈥�</string>
+    <string name="notif_battery_low_title">旮瓣赴鞚� 氚绊劙毽� 鞛旊焿鞚� 鞝侅姷雼堧嫟!</string>
+    <string name="notif_battery_low_percent">%1$s 氚绊劙毽� 雮潓: %2$s%%</string>
+    <string name="notif_battery_low_bigtext_last_charge_time">毵堨毵� 於╈爠: %s</string>
+    <string name="notif_battery_low_bigtext_number_of_charges">於╈爠 須熿垬: %s</string>
+    <string name="sleepchart_your_sleep">雼轨嫚鞚� 靾橂┐</string>
+    <string name="weekstepschart_steps_a_week">"欤缄皠 瓯胳潓 靾� "</string>
+    <string name="activity_sleepchart_activity_and_sleep">雼轨嫚鞚� 頇滊彊瓿� 靾橂┐</string>
+    <string name="updating_firmware">韼岇洦鞏� 鞐呺嵃鞚错姼 欷戔��</string>
+    <string name="fwapp_install_device_not_ready">韺岇澕鞚� 靹れ箻頃� 靾� 鞐嗢姷雼堧嫟. 旮瓣赴臧� 欷�牍勲悩鞏� 鞛堨 鞎婌姷雼堧嫟.</string>
+    <string name="installhandler_firmware_name">%1$s: %2$s %3$s</string>
+    <string name="miband_fwinstaller_compatible_version">順疙櫂 氩勳爠</string>
+    <string name="miband_fwinstaller_untested_version">韰岇姢韸鸽ゼ 瓯办箻歆� 鞎婌潃 氩勳爠!</string>
+    <string name="fwappinstaller_connection_state">旮瓣赴鞐� 鞐瓣舶: %1$s</string>
+    <string name="pbw_installhandler_pebble_firmware">Pebble 韼岇洦鞏� %1$s</string>
+    <string name="pbwinstallhandler_correct_hw_revision">鞓皵毳� 頃橂摐鞗柎 毽箘鞝�</string>
+    <string name="pbwinstallhandler_incorrect_hw_revision">頃橂摐鞗柎 毽箘鞝勳澊 鞚检箻頃橃 鞎婌姷雼堧嫟!</string>
+    <string name="pbwinstallhandler_app_item">%1$s (%2$s)</string>
+    <string name="updatefirmwareoperation_updateproblem_do_not_reboot">韼岇洦鞏� 鞝勳啞鞐� 氍胳牅臧� 鞛堨姷雼堧嫟. Mi Band毳� 鞛秬韺呿晿歆� 毵堨劯鞖�!</string>
+    <string name="updatefirmwareoperation_metadata_updateproblem">韼岇洦鞏� 氅旐儉雿办澊韯� 鞝勳啞鞐� 氍胳牅臧� 鞛堨姷雼堧嫟</string>
+    <string name="updatefirmwareoperation_update_complete">韼岇洦鞏� 靹れ箻 鞕勲</string>
+    <string name="updatefirmwareoperation_update_complete_rebooting">韼岇洦鞏� 靹れ箻 鞕勲, 旮瓣赴毳� 鞛秬韺呿暕雼堧嫟鈥�</string>
+    <string name="updatefirmwareoperation_write_failed">韼岇洦鞏� 頂岆灅鞁� 鞁ろ尐</string>
+    <string name="chart_steps">瓯胳潓 靾�</string>
+    <string name="liveactivity_live_activity">鞁れ嫓臧� 頇滊彊</string>
+    <string name="weeksteps_today_steps_description">鞓る姌 瓯胳潓 靾�, 氇╉憸: %1$s</string>
+    <string name="pref_title_dont_ack_transfer">頇滊彊 雿办澊韯� 鞝勳啞 鞕勲毳� 頇曥澑頃橃 鞎婌潓</string>
+    <string name="pref_summary_dont_ack_transfers">毵岇暯 頇滊彊 雿办澊韯瓣皜 氚措摐鞐� 頇曥澑霅橃 鞎婌晿雼る┐, 歆�鞗岇歆� 鞎婌潉 瓴冹瀰雼堧嫟. 臧�鞝笇毽快臧� 雼るジ 鞎彪摛瓿� 臧欖澊 靷毄霅� 霑� 鞙犾毄頃╇媹雼�.</string>
+    <string name="pref_summary_keep_data_on_device">霃欔赴頇� 鞚错泟鞐愲弰 Mi Band鞚� 頇滊彊 雿办澊韯瓣皜 鞙犾霅� 瓴冹瀰雼堧嫟. 臧�鞝笇毽快臧� 雼るジ 鞎彪摛瓿� 臧欖澊 靷毄霅� 霑� 鞙犾毄頃╇媹雼�.</string>
+    <string name="live_activity_steps_history">瓯胳潓 靾� 旮半</string>
+    <string name="live_activity_current_steps_per_minute">順勳灛鞚� 攵勲嫻 瓯胳潓 靾�</string>
+    <string name="live_activity_total_steps">鞝勳泊 瓯胳潓 靾�</string>
+    <string name="live_activity_steps_per_minute_history">攵勲嫻 瓯胳潓 靾� 旮半</string>
+    <string name="live_activity_start_your_activity">頇滊彊 鞁滌瀾</string>
+    <string name="abstract_chart_fragment_kind_activity">頇滊彊</string>
+    <string name="abstract_chart_fragment_kind_light_sleep">鞏曥潃 靾橂┐</string>
+    <string name="abstract_chart_fragment_kind_deep_sleep">旯婌潃 靾橂┐</string>
+    <string name="abstract_chart_fragment_kind_not_worn">彀╈毄頃橃 鞎婌潓</string>
+    <string name="device_not_connected">鞐瓣舶霅橃 鞎婌潓.</string>
+    <string name="user_feedback_all_alarms_disabled">氇摖 鞎岆瀸 頃挫牅霅�</string>
+    <string name="pref_title_keep_data_on_device">旮瓣赴鞐� 頇滊彊 雿办澊韯� 鞙犾</string>
+    <string name="miband_fwinstaller_incompatible_version">順疙櫂霅橃 鞎婋姅 韼岇洦鞏�</string>
+    <string name="fwinstaller_firmware_not_compatible_to_device">鞚� 韼岇洦鞏措姅 旮瓣赴鞕� 順疙櫂霅橃 鞎婌姷雼堧嫟</string>
+    <string name="miband_prefs_reserve_alarm_calendar">雼り皜鞓る姅 鞚措菠韸鸽ゼ 鞙勴暣 鞓堨暯頃� 鞎岆瀸</string>
+    <string name="miband_prefs_hr_sleep_detection">靾橂┐ 旄§爼鞚� 頄レ儊鞁滍偆旮� 鞙勴暣 鞁皶 靹检劀 靷毄</string>
+    <string name="waiting_for_reconnect">鞛爲靻嶌潉 旮半嫟毽姅 欷�</string>
+    <string name="activity_prefs_about_you">雼轨嫚鞐� 雽�頃�</string>
+    <string name="activity_prefs_year_birth">於滌儩雲勲弰</string>
+    <string name="activity_prefs_gender">靹彪硠</string>
+    <string name="activity_prefs_height_cm">韨� (cm)</string>
+    <string name="activity_prefs_weight_kg">氇鸽瓴� (kg)</string>
+    <string name="authenticating">鞚胳 欷�</string>
+    <string name="authentication_required">鞚胳 頃勳殧</string>
+    <string name="appwidget_text">Zzz</string>
+    <string name="add_widget">鞙勳牤 於旉皜</string>
+    <string name="activity_prefs_sleep_duration">靹犿樃頃橂姅 靾橂┐ 鞁滉皠</string>
+    <string name="appwidget_setting_alarm">%1$02d:%2$02d 鞎岆瀸鞚� 靹れ爼霅橃棃鞀惦媹雼�</string>
+    <string name="device_hw">頃橂摐鞗柎: %1$s</string>
+    <string name="device_fw">韼岇洦鞏�: %1$s</string>
+    <string name="error_creating_directory_for_logfiles">旮半 韺岇澕鞚� 鞝�鞛ロ暊 霐旊爥韱犽Μ 靸濎劚 鞓る: %1$s</string>
+    <string name="DEVINFO_HR_VER">"鞁皶靾�: "</string>
+    <string name="updatefirmwareoperation_update_in_progress">韼岇洦鞏� 頂岆灅鞁� 欷�</string>
+    <string name="updatefirmwareoperation_firmware_not_sent">韼岇洦鞏搓皜 鞝勳啞霅橃 鞎婌潓</string>
+    <string name="charts_legend_heartrate">鞁皶靾�</string>
+    <string name="live_activity_heart_rate">鞁皶靾�</string>
+    <string name="Delete">靷牅</string>
+    <!--Strings related to Vibration Activity-->
+    <!--Strings related to Pebble Pairing Activity-->
+    <string name="action_donate">旮半秬</string>
+    <string name="controlcenter_delete_device">旮瓣赴 靷牅</string>
+    <string name="controlcenter_delete_device_name">%1$s 靷牅</string>
+    <string name="controlcenter_delete_device_dialogmessage">旮瓣赴鞕� 甏�霠悳 氇摖 雿办澊韯瓣皜 靷牅霅╇媹雼�.</string>
+    <string name="controlcenter_navigation_drawer_open">韮愳儔 靹滊瀺 鞐搓赴</string>
+    <string name="controlcenter_navigation_drawer_close">韮愳儔 靹滊瀺 雼赴</string>
+    <string name="controlcenter_snackbar_need_longpress">旃措摐毳� 旮戈矊 雸岆煬 鞐瓣舶 頃挫牅</string>
+    <string name="controlcenter_snackbar_disconnecting">鞐瓣舶 頃挫牅 欷�</string>
+    <string name="controlcenter_snackbar_connecting">鞐瓣舶 欷戔��</string>
+    <string name="controlcenter_snackbar_requested_screenshot">旮瓣赴鞚� 鞀ろ伂毽办兎鞚� 彀嶋姅 欷�</string>
+    <string name="appmanager_cached_watchapps_watchfaces">旌愳嫓鞐� 鞝�鞛ル悳 鞎彪摛</string>
+    <string name="appmanager_installed_watchapps">靹れ箻霅� 鞎彪摛</string>
+    <string name="appmanager_installed_watchfaces">靹れ箻霅� 鞗岇箻韼橃澊鞀�</string>
+    <string name="appmananger_app_delete_cache">靷牅頃橁碃 旌愳嫓鞐愳劀 鞝滉卑</string>
+    <string name="appmanager_app_openinstore">韼橂笖 鞎膘姢韱犾柎鞐愳劀 瓴�靸�</string>
+    <string name="appmanager_hrm_activate">鞁皶靾� 氇媹韯半 頇滌劚頇�</string>
+    <string name="appmanager_hrm_deactivate">鞁皶靾� 氇媹韯半 牍勴櫆靹表檾</string>
+    <string name="appmanager_weather_activate">鞁滌姢韰� 雮犾敤 鞎� 頇滌劚頇�</string>
+    <string name="appmanager_weather_deactivate">鞁滌姢韰� 雮犾敤 鞎� 牍勴櫆靹表檾</string>
+    <string name="appmanager_weather_install_provider">雮犾敤 鞎岆 鞎� 靹れ箻</string>
+    <string name="app_move_to_top">毵� 鞙勲 鞚措彊</string>
+    <string name="title_activity_calblacklist">鞝滌櫢霅� 雼牓</string>
+    <string name="fw_upgrade_notice_amazfitbip">歆�旮� 鞏措鞚挫頃� 牍曥棎 %s 韼岇洦鞏措ゼ 靹れ箻頃橂牑瓿� 頃╇媹雼�.\n\n.fw 韺岇澕, .res 韺岇澕, .gps 韺岇澕 靾滌劀雽�搿� 靹れ箻頃橃嫮鞁滌槫. .fw 韺岇澕鞚� 靹れ箻頃� 頉� 旮瓣赴臧� 鞛嫓鞛� 霅╇媹雼�.\n\n牍勱碃: 毵岇暯 .res 韺岇澕瓿� .gps 韺岇澕鞚� 鞚挫爠瓿� 鞕勳爠 霃欖澕頃橂嫟氅� 雼れ嫓 靹れ箻頃� 頃勳殧 鞐嗢姷雼堧嫟.\n\n氤胳澑鞚� 毂呾瀯 頃橃棎 歆勴枆頃橃劯鞖�!</string>
+    <string name="pref_title_general_autostartonboot">鞛愲彊 鞁滌瀾</string>
+    <string name="pref_title_language">鞏胳柎</string>
+    <string name="pref_title_minimize_priority">臧�鞝笇毽快 鞎岆 靾赴旮�</string>
+    <string name="pref_summary_minimize_priority_off">靸來儨 響滌嫓欷勱臣 鞛犼笀 頇旊┐ 鞎岆鞐� 鞎勳澊旖橃澊 響滌嫓霅╇媹雼�</string>
+    <string name="pref_summary_minimize_priority_on">靸來儨 響滌嫓欷勱臣 鞛犼笀 頇旊┐ 鞎岆鞐� 鞎勳澊旖橃澊 靾波歆戨媹雼�</string>
+    <string name="pref_summary_notifications_pebblemsg">PebbleKit毳� 靷毄頃橃棳 韼橂笖鞐� 鞎岆鞚� 氤措偞電� 鞎膘潉 歆�鞗愴暕雼堧嫟.</string>
+    <string name="pref_title_notification_filter">氚╉暣 旮堨 氇摐</string>
+    <string name="pref_summary_notification_filter">氚╉暣旮堨 旮半姤 頇滌劚頇旍嫓 氇摖 鞎岆 毵夑赴</string>
+    <string name="pref_title_transliteration">響滉赴氩� 氤�頇�</string>
+    <string name="pref_summary_transliteration">毵岇暯 鞛レ箻臧� 雼轨嫚鞚� 靷毄頃橂姅 鞏胳柎鞚� 韽绊姼毳� 歆�鞗愴晿歆� 鞎婋姅雼る┐ 鞚� 鞓奠厴鞚� 頇滌劚頇� 頃橃劯鞖�</string>
+    <string name="pref_header_privacy">頂勲澕鞚措矂鞁�</string>
+    <string name="pref_title_call_privacy_mode">鞝勴檾 頂勲澕鞚措矂鞁� 氇摐</string>
+    <string name="pref_call_privacy_mode_off">鞚措瓿� 氩堩樃毳� 響滌嫓</string>
+    <string name="pref_call_privacy_mode_name">鞚措鞚� 靾赴歆�毵� 氩堩樃電� 響滌嫓</string>
+    <string name="pref_call_privacy_mode_number">鞚措鞚� 響滌嫓頃橃毵� 氩堩樃電� 靾赴旮�</string>
+    <string name="pref_call_privacy_mode_complete">鞚措瓿� 氩堩樃毳� 靾赴旮�</string>
+    <string name="pref_blacklist_calendars">敫旊灆毽姢韸� 旌橂Π雿�</string>
+    <string name="pref_header_cannned_messages">靸侅毄甑�</string>
+    <string name="pref_title_canned_replies">雼奠灔</string>
+    <string name="pref_title_canned_messages_dismisscall">鞝勴檾 瓯办爤</string>
+    <string name="pref_title_canned_messages_set">韼橂笖鞐愳劀 鞐呺嵃鞚错姼</string>
+    <string name="pref_header_activitytrackers">頇滊彊 於旍爜鞛� 靹れ爼</string>
+    <string name="pref_title_pebble_sync_health">Pebble Health 霃欔赴頇�</string>
+    <string name="pref_title_pebble_sync_misfit">Misfit 霃欔赴頇�</string>
+    <string name="pref_title_pebble_sync_morpheuz">Morpheuz 霃欔赴頇�</string>
+    <string name="pref_title_enable_outgoing_call">瓯半姅 鞝勴檾毳� 歆�鞗�</string>
+    <string name="pref_summary_enable_outgoing_call">鞚� 鞓奠厴鞚� 牍勴櫆靹表檾 頃橂┐ Pebble 2/LE臧� 鞝勴檾毳� 瓯� 霑� 歆勲彊頃橃 鞎婌姷雼堧嫟</string>
+    <string name="pref_header_pebble_timeline">韼橂笖 韮�鞛勲澕鞚�</string>
+    <string name="pref_title_sunrise_sunset">鞚检稖瓿� 鞚茧</string>
+    <string name="pref_summary_sunrise_sunset">順勳灛 鞙勳箻 旮半皹鞙茧 韮�鞛勲澕鞚胳棎 鞚检稖瓿� 鞚茧 鞁滉皠鞚� 氤挫棳欷嶋媹雼�</string>
+    <string name="pref_title_enable_calendar_sync">旌橂Π雿� 霃欔赴頇�</string>
+    <string name="pref_summary_enable_calendar_sync">韮�鞛勲澕鞚胳棎 旌橂Π雿� 鞚措菠韸鸽ゼ 氤挫棳欷嶋媹雼�</string>
+    <string name="pref_title_autoremove_notifications">雼潃 鞎岆鞚� 鞛愲彊鞙茧 靷牅</string>
+    <string name="pref_summary_autoremove_notifications">鞎堧摐搿滌澊霌� 旮瓣赴鞐愳劀 鞎岆鞚� 雼溂氅� 韼橂笖鞐愳劀霃� 鞛愲彊鞙茧 靷牅頃╇媹雼�</string>
+    <string name="pref_title_pebble_privacy_mode">頂勲澕鞚措矂鞁� 氇摐</string>
+    <string name="pref_pebble_privacy_mode_off">鞚茧皹 鞎岆</string>
+    <string name="pref_pebble_privacy_mode_content">鞎岆 雮挫毄鞚� 氚旊 氤挫棳欤检 鞎婌姷雼堧嫟</string>
+    <string name="pref_pebble_privacy_mode_complete">鞎岆 鞎勳澊旖橂 氤挫棳欷嶋媹雼�</string>
+    <string name="pref_header_location">鞙勳箻</string>
+    <string name="pref_title_location_aquire">鞙勳箻 彀娟赴</string>
+    <string name="pref_title_location_latitude">鞙勲弰</string>
+    <string name="pref_title_location_longitude">瓴诫弰</string>
+    <string name="pref_title_location_keep_uptodate">鞙勳箻毳� 鞛愲彊鞙茧 鞐呺嵃鞚错姼</string>
+    <string name="pref_summary_location_keep_uptodate">靷毄 欷� 順勳灛 鞙勳箻毳� 氚涭晞鞓る牑瓿� 鞁滊弰頃╇媹雼�. 鞁ろ尐頃橂┐ 鞝�鞛ル悳 鞙勳箻毳� 靷毄頃╇媹雼�</string>
+    <string name="toast_enable_networklocationprovider">雱ろ姼鞗岉伂 旮半皹 鞙勳箻 旄‰焿 旮半姤鞚� 頇滌劚頇� 頃� 欤检劯鞖�</string>
+    <string name="toast_aqurired_networklocation">鞙勳箻毳� 彀眷潓</string>
+    <string name="pref_title_pebble_forcele">敫旊(韴姢 鞝�鞝勲牓 鞐瓣舶鞚� 鞏胳牅雮� 靹犿樃</string>
+    <string name="pref_summary_pebble_forcele">鞚茧皹 敫旊(韴姢 頂勲韱犾綔鞚� 鞎勲媽 鞁ろ棙鞝侅澑 敫旊(韴姢 鞝�鞝勲牓 鞐瓣舶鞚� 靷毄頃╇媹雼�. 鞚茧皹鞝侅澑 鞐瓣舶鞚� 斓滌唽頃� 頃滊矆 頃� 頉� Pebble LE毳� 韼橃柎毵� 頃橂姅 瓴冹澊 頃勳殧頃╇媹雼�</string>
+    <string name="pref_title_pebble_mtu_limit">Pebble 2/LE GATT MTU 鞝滍暅</string>
+    <string name="pref_summary_pebble_mtu_limit">毵岇暯 Pebble 2/Pebble LE臧� 旮半寑頃� 雽�搿� 鞛戨彊頃橃 鞎婋姅雼る┐, 鞚� 靹れ爼鞚� 靷毄頃橃棳 MTU毳� 鞝滍暅頃橃劯鞖� (20鈥�512旯岇 靹れ爼 臧�電�)</string>
+    <string name="pref_title_pebble_enable_applogs">鞁滉硠 鞎� 搿滉犯 頇滌劚頇�</string>
+    <string name="pref_summary_pebble_enable_applogs">鞁滉硠 鞎彪摛鞚� 搿滉犯毳� Gadgetbridge臧� 旮半頃╇媹雼� (鞛棸瓴� 頃勳殧)</string>
+    <string name="pref_title_pebble_always_ack_pebblekit">雭濍倶旮� 鞝� 氙鸽Μ PebbleKit 鞐瓣舶鞐� ACK 氤措偞旮�</string>
+    <string name="pref_summary_pebble_always_ack_pebblekit">鞝� 3鞛� 鞎膘溂搿� 氤措偞電� 氅旍劯歆�鞐� 鞏胳牅雮� 氚旊 ACK鞚� 氤措儏雼堧嫟</string>
+    <string name="pref_title_unit_system">雼渼</string>
+    <string name="pref_title_timeformat">鞁滉皠 順曥嫕</string>
+    <string name="controlcenter_connect">鞐瓣舶鈥�</string>
+    <string name="fw_upgrade_notice_amazfitcor">歆�旮� 鞏措鞚挫頃� 旖旍柎鞐� %s 韼岇洦鞏措ゼ 靹れ箻頃橂牑瓿� 頃╇媹雼�.\n\n.fw 韺岇澕, .res 韺岇澕 靾滌劀雽�搿� 靹れ箻頃橃嫮鞁滌槫. .fw 韺岇澕鞚� 靹れ箻頃� 頉� 旮瓣赴臧� 鞛嫓鞛� 霅╇媹雼�.\n\n牍勱碃: 毵岇暯 .res 韺岇澕鞚� 鞚挫爠瓿� 鞕勳爠 霃欖澕頃橂嫟氅� 雼れ嫓 靹れ箻頃� 頃勳殧 鞐嗢姷雼堧嫟.\n\n氤胳澑鞚� 毂呾瀯 頃橃棎 歆勴枆頃橃劯鞖�!</string>
+    <string name="pref_title_charts_swipe">頇滊彊 彀姼鞐愳劀 膦�/鞖� 氚�鞏措剺旮瓣赴 靷毄</string>
+    <string name="pref_title_weather_location">雮犾敤 鞙勳箻 (CM/毽媹歆� OS)</string>
+    <string name="pref_title_pebble_enable_bgjs">氚标犯霛检毚霌� JS 靷毄頃橁赴</string>
+    <string name="pref_summary_pebble_enable_bgjs">頇滌劚頇旐暊 瓴届毎 鞁滉硠 頇旊┐鞐� 雮犾敤鞕� 氚绊劙毽� 鞝曤炒 霌膘潉 響滌嫓頃╇媹雼�.</string>
+    <string name="pref_title_screentime">頇旊┐ 旒滌 鞁滉皠</string>
+    <string name="prefs_title_all_day_heart_rate">頃橂(膦呾澕 鞁皶靾� 旄§爼</string>
+    <string name="preferences_hplus_settings">HPlus/Makibes 靹れ爼</string>
+    <string name="tap_connected_device_for_activity">鞐瓣舶霅� 旮瓣赴毳� 韯办箻頃挫劀 頇滊彊 頇曥澑</string>
+    <string name="tap_connected_device_for_vibration">鞐瓣舶霅� 旮瓣赴毳� 韯办箻頃挫劀 歆勲彊</string>
+    <string name="pairing_creating_bond_with">%1$s (%2$s)鞐� 鞐瓣舶 靸濎劚頃橂姅 欷�</string>
+    <string name="pairing_unable_to_pair_with">%1$s (%2$s)鞐� 韼橃柎毵� 攵堦皜電�</string>
+    <string name="pairing_in_progress">鞐瓣舶 靸濎劚 欷�: %1$s (%2$s)</string>
+    <string name="pairing_already_bonded">鞚措 %1$s (%2$s)鞐� 鞐瓣舶鞚� 靸濎劚霅橃棃鞙茧┌ 鞐瓣舶頃橂姅 欷戔��</string>
+    <string name="discovery_connected_devices_hint">旮瓣赴毳� 氚滉铂 臧�電ロ晿霃勲 靹れ爼頃橃劯鞖�. 順勳灛 鞐瓣舶霅� 旮瓣赴電� 雮橅儉雮橃 鞎婌潉 臧�電レ劚鞚� 雴掛姷雼堧嫟. 鞎堧摐搿滌澊霌� 6.0 鞚挫儊鞚� 氩勳爠鞐愳劀電� 鞙勳箻(鞓�: GPS) 旮半姤鞚� 頇滌劚頇旐晿靹胳殧. Privacy Guard臧� 靹れ爼霅橃柎 鞛堧嫟氅� 鞓れ瀾霃欖潉 鞚检溂韨� 臧�電レ劚鞚� 鞛堨溂氙�搿� 臧�鞝笇毽快毳� 鞓堨櫢 觳橂Μ頃橃劯鞖�. 氇� 攵� 鞎堨棎 旮瓣赴臧� 氚滉铂霅橃 鞎婋姅雼る┐, 旮瓣赴毳� 鞛嫓鞛戫晿瓿� 雼れ嫓 鞁滊弰頃橃劯鞖�.</string>
+    <string name="vibration_try">鞁滊弰頃橁赴</string>
+    <string name="pref_screen_notification_profile_email">鞚措鞚� 鞎岆</string>
+    <string name="pref_screen_notification_profile_generic_chat">毂�</string>
+    <string name="pref_screen_notification_profile_generic_navigation">雮措箘瓴岇澊靺�</string>
+    <string name="pref_screen_notification_profile_generic_social">靻岇厹 雱ろ姼鞗岉伂</string>
+    <string name="prefs_title_heartrate_measurement_interval">頃橂(膦呾澕 鞁皶靾� 旄§爼</string>
+    <string name="interval_one_minute">1攵� 臧勱博</string>
+    <string name="interval_five_minutes">5攵� 臧勱博</string>
+    <string name="interval_ten_minutes">10攵� 臧勱博</string>
+    <string name="interval_thirty_minutes">30攵� 臧勱博</string>
+    <string name="interval_one_hour">1鞁滉皠 臧勱博</string>
+    <string name="stats_title">靻嶋弰 甑皠</string>
+    <string name="stats_x_axis_label">齑� 攵勳垬</string>
+    <string name="stats_y_axis_label">攵勲嫻 瓯胳潓 靾�</string>
+    <string name="weeksleepchart_sleep_a_week">"欤缄皠 靾橂┐ 鞁滉皠 "</string>
+    <string name="weeksleepchart_today_sleep_description">鞓る姌鞚� 靾橂┐, 氇╉憸 靾橂┐ 鞁滉皠: %1$s</string>
+    <string name="calories">旃茧毽�</string>
+    <string name="distance">瓯半Μ</string>
+    <string name="clock">鞁滉硠</string>
+    <string name="heart_rate">鞁皶靾�</string>
+    <string name="battery">氚绊劙毽�</string>
+    <string name="pref_title_low_latency_fw_update">韼岇洦鞏� 頂岆灅鞁膘棎 雮潃 歆�鞐� 氇摐 靷毄</string>
+    <string name="pref_summary_low_latency_fw_update">韼岇洦鞏� 鞐呺嵃鞚错姼臧� 鞁ろ尐頃橂姅 鞛レ箻鞐� 鞚� 旮半姤鞚� 霃勳泙鞚� 霅� 靾� 鞛堨姷雼堧嫟.</string>
+    <string name="miband_prefs_device_time_offset_hours">旮瓣赴 鞁滉皠鞚� 靸侅噭頃橁赴(甑愲寑 攴茧鞛愳潣 靾橂┐ 旄§爼鞚� 鞙勴暣)</string>
+    <string name="miband2_prefs_dateformat">雮犾 順曥嫕</string>
+    <string name="dateformat_time">鞁滉皠</string>
+    <string name="dateformat_date_time">鞁滉皠 &amp; 雮犾</string>
+    <string name="mi2_prefs_button_actions">氩勴娂 霃欖瀾</string>
+    <string name="mi2_prefs_button_actions_summary">Mi Band 2鞚� 氩勴娂鞚� 雸勲ゼ 瓴届毎 霃欖瀾 靹れ爼頃橁赴</string>
+    <string name="mi2_prefs_button_press_count">氩勴娂 雸勲ゴ電� 須熿垬</string>
+    <string name="mi2_prefs_button_press_count_summary">氅旍劯歆� 敫岆霌滌簮鞀ろ姼毳� 氚滊彊頃橁赴 鞙勴暅 氩勴娂 雸勲 須熿垬</string>
+    <string name="mi2_prefs_button_press_broadcast">敫岆霌滌簮鞀ろ姼 氅旍劯歆�</string>
+    <string name="mi2_prefs_button_press_broadcast_summary">氩勴娂 雸勲 須熿垬鞐� 霃勲嫭頃� 瓴届毎鞐� 氤措偧 敫岆霌滌簮鞀ろ姼 氅旍劯歆� 靹れ爼</string>
+    <string name="mi2_prefs_button_action">氩勴娂 霃欖瀾 靷毄</string>
+    <string name="mi2_prefs_button_action_summary">韸轨爼 須熿垬毵岉伡 氩勴娂鞚� 雸勲ゼ瓴届毎 鞛戨彊頃橂姅 旮半姤</string>
+    <string name="mi2_prefs_button_action_vibrate">氚措摐 歆勲彊 靷毄</string>
+    <string name="mi2_prefs_button_action_vibrate_summary">氩勴娂 霃欖瀾鞚� 鞛戨彊頃� 瓴届毎 旮瓣赴 歆勲彊</string>
+    <string name="mi2_prefs_button_press_count_max_delay">氩勴娂 雸勲臧� 斓滊寑 臧勱博</string>
+    <string name="mi2_prefs_button_press_count_max_delay_summary">氩勴娂 雸勲 臧勱博鞚� 氚�毽磮(Milisecond) 雼渼搿� 靹れ爼</string>
+    <string name="mi2_prefs_goal_notification">氇╉憸 雼劚 鞎岆</string>
+    <string name="mi2_prefs_goal_notification_summary">氇╉憸 瓯胳潓 靾� 雼劚鞁� 氚措摐 歆勲彊</string>
+    <string name="mi2_prefs_display_items">頃 響滌嫓</string>
+    <string name="mi2_prefs_display_items_summary">氚措摐 頇旊┐鞐� 響滌嫓頃� 頃 靹犿儩</string>
+    <string name="mi2_prefs_activate_display_on_lift">旮瓣赴毳� 霌れ柎 頇旊┐ 響滌嫓</string>
+    <string name="mi2_prefs_rotate_wrist_to_switch_info">靻愲鞚� 韹�鞏� 響滌嫓 頃 鞝勴櫂</string>
+    <string name="mi2_prefs_do_not_disturb">氚╉暣 旮堨</string>
+    <string name="mi2_prefs_do_not_disturb_summary">氚╉暣 旮堨 欷戩棎電� 氚措摐鞐� 鞎岆鞚� 響滌嫓頃橃 鞎婌潓</string>
+    <string name="mi2_prefs_inactivity_warnings">攵�霃� 鞛愳劯 瓴疥碃</string>
+    <string name="mi2_prefs_inactivity_warnings_summary">鞝曧暣歆� 鞁滉皠 霃欖晥 霃欖瀾鞚� 臧愳霅橃 鞎婌潉 瓴届毎 歆勲彊鞙茧 鞎岆</string>
+    <string name="mi2_prefs_inactivity_warnings_threshold">瓴疥碃 旮办 鞁滉皠</string>
+    <string name="mi2_prefs_inactivity_warnings_dnd_summary">鞚检爼 鞁滉皠霃欖晥 鞗�歆侅澊歆� 鞎婌潓 瓴疥碃毳� 牍勴櫆靹表檾</string>
+    <string name="mi2_prefs_do_not_disturb_start">鞁滌瀾 鞁滉皠</string>
+    <string name="mi2_prefs_do_not_disturb_end">膦呺 鞁滉皠</string>
+    <string name="automatic">鞛愲彊</string>
+    <string name="simplified_chinese">欷戧淡鞏� 臧勳泊</string>
+    <string name="traditional_chinese">欷戧淡鞏� 氩堨泊</string>
+    <string name="english">鞓侅柎</string>
+    <string name="FetchActivityOperation_about_to_transfer_since">%1$s 鞚错泟鞚� 雿办澊韯� 鞝勳啞 欷�</string>
+    <string name="pref_title_pebble_health_store_raw">雿办澊韯半矤鞚挫姢鞐� 氙戈皜瓿� 雿办澊韯� 鞝�鞛�</string>
+    <string name="pref_summary_pebble_health_store_raw">鞚� 旮半姤鞚� 靷毄頃� 瓴届毎 雿办澊韯半姅 臧�瓿惦悩歆� 鞎婌潃 靸來儨搿� 鞝�鞛ル悩鞏�, 於旐泟 雿办澊韯� 攵勳劃鞚� 臧�電ロ晿瓴� 頃橁赴 鞙勴暣 雿办澊韯半矤鞚挫姢鞐愳劀 雿� 韥� 鞖╇焿鞚� 彀頃╇媹雼�.</string>
+    <string name="action_db_management">雿办澊韯半矤鞚挫姢 甏�毽�</string>
+    <string name="title_activity_db_management">雿办澊韯半矤鞚挫姢 甏�毽�</string>
+    <string name="activity_db_management_import_export_explanation">雿办澊韯半矤鞚挫姢 鞛戩梾鞚� 雼れ潓 瓴诫毳� 靷毄頃╇媹雼�. 
+\n鞚� 瓴诫電� 雼るジ 旎错摠韯� 氚� 鞎堧摐搿滌澊霌� 鞎膘棎靹� 鞝戧芳 臧�電ロ暕雼堧嫟. 
+\n雮措炒雮� 雿办澊韯半矤鞚挫姢毳� 鞐赴靹� 頇曥澑頃橃劯鞖� (順轨潃 臧�鞝胳槵 雿办澊韯半矤鞚挫姢毳� 鞐赴鞐� 鞝�鞛ロ晿靹胳殧):</string>
+    <string name="activity_db_management_merge_old_title">雮波歆� 雿办澊韯半矤鞚挫姢 靷牅</string>
+    <string name="dbmanagementactivvity_cannot_access_export_path">雮措炒雮� 瓴诫鞐� 鞝戧芳頃� 靾� 鞐嗢姷雼堧嫟. 臧滊皽鞛愳棎瓴� 鞐半澖頃橃劯鞖�.</string>
+    <string name="dbmanagementactivity_exported_to">%1$s 搿� 雮措炒雰堨潓</string>
+    <string name="dbmanagementactivity_error_exporting_db">雿办澊韯半矤鞚挫姢 雮措炒雮搓赴 鞓る: %1$s</string>
+    <string name="dbmanagementactivity_error_exporting_shared">靹れ爼 雮措炒雮搓赴 鞓る: %1$s</string>
+    <string name="dbmanagementactivity_import_data_title">雿办澊韯半ゼ 臧�鞝胳槵旯岇殧?</string>
+    <string name="dbmanagementactivity_overwrite_database_confirmation">旮办〈 雿办澊韯半矤鞚挫姢毳� 雿柎鞊戈箤鞖�? 旮办〈鞚� 頇滊彊 雿办澊韯瓣皜 靻岇嫟霅╇媹雼�.</string>
+    <string name="dbmanagementactivity_import_successful">臧�鞝胳槫旮� 靹标车.</string>
+    <string name="dbmanagementactivity_error_importing_db">雿办澊韯半矤鞚挫姢 臧�鞝胳槫旮� 鞓る: %1$s</string>
+    <string name="dbmanagementactivity_error_importing_shared">靹れ爼 臧�鞝胳槫旮� 鞓る: %1$s</string>
+    <string name="dbmanagementactivity_delete_activity_data_title">頇滊彊 雿办澊韯半ゼ 靷牅頃犼箤鞖�?</string>
+    <string name="dbmanagementactivity_really_delete_entire_db">鞝曤搿� 氇摖 雿办澊韯半矤鞚挫姢毳� 靷牅頃犼箤鞖�? 氇摖 頇滊彊 雿办澊韯� 氚� 旮瓣赴 鞝曤炒臧� 靻岇嫟霅╇媹雼�.</string>
+    <string name="dbmanagementactivity_database_successfully_deleted">雿办澊韯� 靷牅霅�.</string>
+    <string name="dbmanagementactivity_db_deletion_failed">雿办澊韯半矤鞚挫姢 靷牅 鞁ろ尐.</string>
+    <string name="dbmanagementactivity_delete_old_activity_db">鞓る灅霅� 頇滊彊 雿办澊韯半矤鞚挫姢毳� 靷牅頃犼箤鞖�?</string>
+    <string name="dbmanagementactivity_delete_old_activitydb_confirmation">鞝曤搿� 鞓る灅霅� 頇滊彊 雿办澊韯半矤鞚挫姢毳� 靷牅頃犼箤鞖�? 雮措炒雮挫 鞎婌潃 頇滊彊 雿办澊韯半姅 靻岇嫟霅╇媹雼�.</string>
+    <string name="dbmanagementactivity_old_activity_db_successfully_deleted">鞓る灅霅� 頇滊彊 雿办澊韯� 靷牅霅�.</string>
+    <string name="dbmanagementactivity_old_activity_db_deletion_failed">鞓る灅霅� 頇滊彊 雿办澊韯� 靷牅 鞁ろ尐.</string>
+    <string name="dbmanagementactivity_overwrite">雿柎鞊瓣赴</string>
+    <string name="Cancel">旆唽</string>
+    <string name="title_activity_vibration">歆勲彊</string>
+    <string name="title_activity_pebble_pairing">Pebble 韼橃柎毵�</string>
+    <string name="pebble_pairing_hint">韼橃柎毵� 頇旊┐鞚� 鞎堧摐搿滌澊霌� 旮瓣赴鞐� 雮橅儉雮� 瓴冹瀰雼堧嫟. 攴鸽爣歆� 鞎婋嫟氅�, 鞎岆 靹滊瀺鞚� 鞐挫柎靹� 韼橃柎毵� 鞖旍箔鞚� 靾橂澖頃橃劯鞖�. 攴鸽Μ瓿� 雮橃劀 Pebble鞐愳劀 韼橃柎毵� 鞖旍箔鞚� 靾橂澖頃橃劯鞖�.</string>
+    <string name="weather_notification_label">Pebble鞐� 雮犾敤 鞝曤炒毳� 鞏魂赴 鞙勴暣 鞚� 鞀ろ偍鞚� Weather Notification 鞎膘棎靹� 頇滌劚頇旊悩鞐堧姅歆�毳� 頇曥澑頃橃劯鞖�.
+\n
+\n雼るジ 靹れ爼鞚� 頃勳殧頃橃 鞎婌姷雼堧嫟.
+\n
+\nPebble鞚� 鞁滌姢韰� 雮犾敤 鞎膘潉 鞎� 甏�毽棎靹� 靷毄頃� 靾� 鞛堨姷雼堧嫟.
+\n
+\n鞚措ゼ 歆�鞗愴晿電� 鞗岇箻韼橃澊鞀る姅 鞛愲彊鞙茧 雮犾敤毳� 響滌嫓頃� 瓴冹瀰雼堧嫟.</string>
+    <string name="pref_title_setup_bt_pairing">敫旊(韴姢 韼橃柎毵� 靷毄</string>
+    <string name="pref_summary_setup_bt_pairing">旮瓣赴 鞐瓣舶鞐� 氍胳牅臧� 鞛堨潉 瓴届毎 鞚� 靹れ爼鞚� 頃挫牅頃橃劯鞖�</string>
+    <string name="unit_metric">氙疙劙 雼渼</string>
+    <string name="unit_imperial">鞓侁淡鞁� 雼渼</string>
+    <string name="timeformat_24h">24鞁滉皠</string>
+    <string name="timeformat_am_pm">鞓れ爠/鞓ろ泟</string>
+    <string name="pref_screen_notification_profile_alarm_clock">鞎岆瀸 鞁滉硠</string>
+    <string name="activity_web_view">鞗� 敕� 頇滊彊</string>
+    <string name="StringUtils_sender">(%1$s)</string>
+    <string name="find_device_you_found_it">彀眷晿鞀惦媹雼�!</string>
+    <string name="miband2_prefs_timeformat">Mi2: 鞁滉皠 順曥嫕</string>
+    <string name="mi2_fw_installhandler_fw53_hint">鞚� 韼岇洦鞏措ゼ 靹れ箻頃橁赴 鞝勳棎 %1$s 氩勳爠鞚� 毹检爛 靹れ箻頃挫暭 頃╇媹雼�!</string>
+    <string name="mi2_enable_text_notifications">旮�鞛愲 響滌嫓霅� 鞎岆</string>
+    <string name="mi2_enable_text_notifications_summary">1.0.1.28 鞚挫儊 氩勳爠鞚� 韼岇洦鞏挫檧 Mili_pro.ft臧� 靹れ箻霅橃柎 鞛堨柎鞎� 頃╇媹雼�.</string>
+    <string name="off">旰检</string>
+    <string name="mi2_dnd_off">旰检</string>
+    <string name="mi2_dnd_automatic">鞛愲彊 (靾橂┐ 旄§爼)</string>
+    <string name="mi2_dnd_scheduled">鞓堨暯霅� (鞁滉皠 臧勱博)</string>
+    <string name="discovery_attempting_to_pair">%1$s鞐� 韼橃柎毵� 鞁滊弰頃橂姅 欷�</string>
+    <string name="discovery_bonding_failed_immediately">%1$s鞐� 鞐瓣舶 靸濎劚 鞁ろ尐.</string>
+    <string name="discovery_trying_to_connect_to">鞐瓣舶 鞁滊弰頃橂姅 欷�: %1$s</string>
+    <string name="discovery_enable_bluetooth">旮瓣赴毳� 氚滉铂頃橁赴 鞙勴暣 敫旊(韴姢毳� 頇滌劚頇旐晿靹胳殧.</string>
+    <string name="discovery_successfully_bonded">%1$s鞐� 鞐瓣舶 靹标车.</string>
+    <string name="discovery_pair_title">%1$s 鞕� 韼橃柎毵來暊旯岇殧?</string>
+    <string name="discovery_pair_question">旮瓣赴鞐� 韼橃柎毵來晿霠る┐ \'韼橃柎毵乗'鞚� 靹犿儩頃橃劯鞖�. 毵岇暯 鞁ろ尐頃滊嫟氅� 韼橃柎毵� 鞐嗢澊 雼れ嫓 鞁滊弰頃橃劯鞖�.</string>
+    <string name="discovery_yes_pair">韼橃柎毵�</string>
+    <string name="discovery_dont_pair">韼橃柎毵來晿歆� 鞎婈赴</string>
+    <string name="_pebble_watch_open_on_phone">鞎堧摐搿滌澊霌� 鞛レ箻鞐愳劀 鞐搓赴</string>
+    <string name="_pebble_watch_mute">鞚岇唽瓯�</string>
+    <string name="_pebble_watch_reply">雼奠灔</string>
+    <string name="controlcenter_start_activity_tracks">雼轨嫚鞚� 頇滊彊 韸鸽灆</string>
+    <string name="activity_type_not_measured">旄§爼霅橃 鞎婌潓</string>
+    <string name="activity_type_activity">頇滊彊</string>
+    <string name="activity_type_light_sleep">鞏曥潃 靾橂┐</string>
+    <string name="activity_type_deep_sleep">旯婌潃 靾橂┐</string>
+    <string name="activity_type_not_worn">霐旊皵鞚挫姢 旯 鞎婌潓</string>
+    <string name="activity_type_running">雼Μ旮�</string>
+    <string name="activity_type_walking">瓯逢赴</string>
+    <string name="activity_type_swimming">靾橃榿</string>
+    <string name="activity_type_unknown">鞎� 靾� 鞐嗠姅 頇滊彊</string>
+    <string name="activity_summaries">頇滊彊膦呿暕</string>
+    <string name="activity_type_biking">氚旍澊韥�</string>
+    <string name="activity_type_treadmill">韸鸽爤霌滊皜</string>
+    <string name="select_all">鞝勳泊 靹犿儩</string>
+    <string name="share">瓿奠湢</string>
+    <string name="reset_index">雿办澊韯� 攵堧煬鞓り赴 齑堦赴頇�</string>
+    <string name="kind_firmware">韼岇洦鞏�</string>
+    <string name="kind_invalid">鞓皵毳挫 鞎婌潃 雿办澊韯�</string>
+    <string name="kind_font">旮�昙�</string>
+    <string name="kind_gps">GPS 韼岇洦鞏�</string>
+    <string name="kind_gps_almanac">GPS 鞐瓣皭</string>
+    <string name="kind_gps_cep">GPS 鞓る 氤挫爼</string>
+    <string name="kind_resources">毽唽鞀�</string>
+    <string name="kind_watchface">鞗岇箻韼橃澊鞀�</string>
+    <string name="devicetype_unknown">鞎� 靾� 鞐嗠姅 旮瓣赴</string>
+    <string name="devicetype_test">旮瓣赴 韰岇姢韸�</string>
+    <string name="devicetype_pebble">Pebble</string>
+    <string name="devicetype_miband">Mi Band</string>
+    <string name="devicetype_miband2">Mi Band 2</string>
+    <string name="devicetype_miband3">Mi Band 3</string>
+    <string name="devicetype_amazfit_bip">Amazfit Bip</string>
+    <string name="devicetype_amazfit_cor">Amazfit Cor</string>
+    <string name="devicetype_vibratissimo">Vibratissimo</string>
+    <string name="devicetype_liveview">LiveView</string>
+    <string name="devicetype_hplus">HPlus</string>
+    <string name="devicetype_makibes_f68">Makibes F68</string>
+    <string name="devicetype_exrizu_k8">Exrizu K8</string>
+    <string name="devicetype_q8">Q8</string>
+    <string name="devicetype_no1_f1">No.1 F1</string>
+    <string name="devicetype_teclast_h30">Teclast H30</string>
+    <string name="devicetype_xwatch">XWatch</string>
+    <string name="choose_auto_export_location">於旍稖 鞙勳箻毳� 靹犿儩頃橃劯鞖�</string>
+    <string name="notification_channel_name">臧�鞝笇毽快 鞎岆</string>
+    <!-- Menus on the smart device -->
+    <string name="menuitem_status">靸來儨</string>
+    <string name="menuitem_activity">頇滊彊</string>
+    <string name="menuitem_weather">雮犾敤</string>
+    <string name="menuitem_alarm">鞎岆瀸</string>
+    <string name="menuitem_timer">韮�鞚措ǜ</string>
+    <string name="menuitem_compass">旎错寣鞀�</string>
+    <string name="menuitem_settings">靹れ爼</string>
+    <string name="menuitem_alipay">Alipay</string>
+    <string name="controlcenter_change_led_color">LED 靸夓儊 氤�瓴�</string>
+    <string name="controlcenter_change_fm_frequency">FM 欤柬寣靾� 氤�瓴�</string>
+    <string name="controlcenter_calibrate_device">霐旊皵鞚挫姢 臁办爼頃橁赴</string>
+    <string name="blacklist_all_for_notifications">氇摖 鞎岆 敫旊灆毽姢韸�</string>
+    <string name="whitelist_all_for_notifications">氇摖 鞎岆 頇旍澊韸鸽Μ鞀ろ姼</string>
+    <string name="fw_upgrade_notice_miband3">歆�旮� 氙鸽按霌� 3鞐� %s 韼岇洦鞏措ゼ 靹れ箻頃橂牑瓿� 頃╇媹雼�.\n\n.fw 韺岇澕, .res 韺岇澕 靾滌劀雽�搿� 靹れ箻頃橃嫮鞁滌槫. .fw 韺岇澕鞚� 靹れ箻頃� 頉� 旮瓣赴臧� 鞛嫓鞛� 霅╇媹雼�.\n\n牍勱碃: 毵岇暯 .res 韺岇澕鞚� 鞚挫爠瓿� 鞕勳爠 霃欖澕頃橂嫟氅� 雼れ嫓 靹れ箻頃� 頃勳殧 鞐嗢姷雼堧嫟.\n\n氤胳澑鞚� 毂呾瀯 頃橃棎 歆勴枆頃橃劯鞖�!</string>
+    <string name="pref_title_notifications_timeout">鞎岆瀸臧� 斓滌唽 鞁滉皠</string>
+    <string name="pref_title_rtl">鞓るジ飒届棎靹� 鞕检鞙茧</string>
+    <string name="pref_summary_rtl">毵岇暯 雼轨嫚鞚� 霐旊皵鞚挫姢鞐愳劀 膦岇毎 鞏胳柎臧� 氤挫澊歆� 鞎婋姅雼る┐ 鞚搓矁鞚� 頇滌劚頇旐晿靹胳殧</string>
+    <string name="pref_rtl_max_line_length">膦岇毎 斓滊寑 霛检澑 旮胳澊</string>
+    <string name="pref_rtl_max_line_length_summary">鞓るジ飒届棎靹� 鞕检鞙茧 鞊半姅 氍胳瀽臧� 甑秳霅橂姅 欷勳潉 電橃澊瓯半倶 欷勳瀰雼堧嫟</string>
+    <string name="pref_title_weather">雮犾敤</string>
+    <string name="pref_title_pebble_gatt_clientonly">"GATT client 毵� "</string>
+    <string name="pref_summary_pebble_gatt_clientonly">"鞚搓矁鞚� Pebble2 毵岇潣 瓴冹澊瓿� 鞁ろ棙鞝侅瀰雼堧嫟. 毵岇暯 鞐瓣舶 氍胳牅臧� 鞛堧嫟氅� 鞁滊弰頃措炒靹胳殧 "</string>
+    <string name="prefs_screen_orientation">頇旊┐ 氚╉枼</string>
+    <string name="pref_header_auto_export">鞛愲彊 於旍稖</string>
+    <string name="pref_title_auto_export_enabled">鞛愲彊 於旍稖 頇滌劚頇�</string>
+    <string name="pref_title_auto_export_location">於旍稖 鞛レ唽</string>
+    <string name="pref_title_auto_export_interval">於旍稖 臧勱博</string>
+    <string name="pref_summary_auto_export_interval">"毵� %d 鞁滉皠 毵堧嫟 於旍稖 "</string>
+    <string name="pref_auto_fetch">頇滊彊 雿办澊韯� 鞛愲彊鞙茧 臧�鞝胳槫旮�</string>
+    <string name="pref_auto_fetch_summary">臧�鞝� 鞓り赴電� 頇旊┐ 鞛犼笀 頃挫牅鞁� 氚滌儩頃╇媹雼�. 鞛犼笀 氅旍护雼堨鞚� 靹れ爼霅� 瓴届毎鞐愲 鞛戨彊頃╇媹雼�!</string>
+    <string name="pref_auto_fetch_limit_fetches">臧�鞝胳槫電� 斓滌唽 鞁滉皠</string>
+    <string name="pref_auto_fetch_limit_fetches_summary">毵� %d 攵勲雼� 雿办澊韯� 臧�鞝胳槫旮�</string>
+    <string name="horizontal">靾橅弶</string>
+    <string name="vertical">靾橃</string>
+    <string name="watch9_pairing_tap_hint">鞁滉硠臧� 歆勲彊頃橂┐ 鞛レ箻毳� 頋旊摛瓯半倶 氩勴娂鞚� 雸勲ゴ鞁嫓鞓�.</string>
+    <string name="notif_battery_low">%1$s 氚绊劙毽� 攵�臁�</string>
+    <string name="notif_battery_low_extended">%1$s 氚绊劙毽� 攵�臁�: %2$s</string>
+    <string name="notif_export_failed_title">雿办澊韯半矤鞚挫姢 於旍稖 鞁ろ尐! 雼轨嫚鞚� 靹れ爼鞚� 頇曥澑頃挫<靹胳殧.</string>
+    <string name="lack_of_sleep">靾橂┐ 攵�臁�: %1$s</string>
+    <string name="overslept">電灎: %1$s</string>
+    <string name="no_limit">鞝滍暅 鞐嗢潓</string>
+    <string name="seconds_5">5齑�</string>
+    <string name="seconds_10">10齑�</string>
+    <string name="seconds_20">20齑�</string>
+    <string name="seconds_30">30齑�</string>
+    <string name="minutes_1">1攵�</string>
+    <string name="minutes_5">5攵�</string>
+    <string name="minutes_10">10攵�</string>
+    <string name="minutes_30">30攵�</string>
+    <string name="lack_of_step">瓯胳潓靾� 攵�臁�: %1$d</string>
+    <string name="overstep">瓯胳潓靾� 齑堦臣 : %1$d</string>
+    <string name="live_activity_max_heart_rate">順勳灛 / 斓滊寑 鞁皶靾�: %1$d / %2$d</string>
+    <string name="you_slept">雼轨嫚鞚� 靾橂┐鞁滉皠鞚� %1$s 攵�韯� %2$s 旯岇</string>
+    <string name="you_did_not_sleep">鞛犾潉 鞛愳 鞎婌晿鞀惦媹雼�</string>
+    <string name="mi3_prefs_band_screen_unlock">氚措摐 鞀ろ伂毽� 鞏鸽澖</string>
+    <string name="mi3_prefs_band_screen_unlock_summary">鞀柎鞓牑 氚措摐 鞀ろ伂毽� 頇旊┐ 鞛犼笀 頃挫牅</string>
+    <string name="mi3_prefs_night_mode">鞎缄皠 氇摐</string>
+    <string name="mi3_prefs_night_mode_summary">氚れ澊 霅橂┐ 鞛愲彊鞙茧 氚措摐 氚濌赴 臧愳唽</string>
+    <string name="norwegian_bokmal">雲鸽ゴ鞗澊 攵�韥鞏�</string>
+    <string name="spanish">鞀ろ帢鞚胳柎</string>
+    <string name="russian">霟嫓鞎勳柎</string>
+    <string name="german">霃呾澕鞏�</string>
+    <string name="italian">鞚错儓毽晞鞏�</string>
+    <string name="french">頂勲瀾鞀れ柎(攵堨柎)</string>
+    <string name="polish">韽措瀫霌滌柎</string>
+    <string name="korean">頃滉淡鞏�</string>
+    <string name="japanese">鞚茧掣鞏�</string>
+    <string name="activity_prefs_charts">彀姼 靹れ爼</string>
+    <string name="activity_prefs_chart_max_heart_rate">斓滊寑 鞁皶靾�</string>
+    <string name="activity_prefs_chart_min_heart_rate">斓滌唽 鞁皶靾�</string>
+    <string name="ok">頇曥澑</string>
+    <string name="on">旒滉赴</string>
+    <string name="mi3_night_mode_sunset">鞚茧</string>
+    <string name="devicetype_mykronoz_zetime">MyKronoz ZeTime</string>
+    <string name="devicetype_watch9">"Watch 9 "</string>
+    <string name="devicetype_roidmi">"Roidmi "</string>
+    <string name="devicetype_roidmi3">"Roidmi 3 "</string>
+    <string name="menuitem_notifications">鞎岆瀸</string>
+    <string name="language_and_region_prefs">鞏胳柎 氚� 歆�鞐� 靹れ爼</string>
+    <string name="debugactivity_really_factoryreset_title">鞝曤搿� 瓿奠灔 齑堦赴頇旊ゼ 鞁ろ枆頃犼箤鞖擻?</string>
+    <string name="debugactivity_really_factoryreset">瓿奠灔 齑堦赴頇旊ゼ 頃橂┐ 鞐瓣舶霅� 鞛レ箻鞐愳劀 (歆�鞗愳嫓) 雿办澊韯半ゼ 氇憪 靷牅頃╇媹雼�. 靸れ槫氙�/Huami 鞛レ箻鞚� 瓴届毎 敫旊(韴姢 MAC 欤检唽霃� 氤�瓴诫悩氙�搿�, 臧�鞝笇毽快鞐愳劀 靸� 鞛レ箻搿� 雮橅儉雮橁矊 霅╇媹雼�.</string>
+    <string name="devicetype_id115">ID115</string>
+    <string name="menuitem_music">鞚岇晠</string>
+    <string name="menuitem_more">雿� 氤搓赴</string>
+    <string name="watch9_time_minutes">攵�:</string>
+    <string name="watch9_time_hours">鞁滉皠:</string>
+    <string name="watch9_time_seconds">齑�:</string>
+    <string name="watch9_calibration_hint">鞛レ箻鞐� 氤挫澊電� 鞁滉皠鞚� 靹れ爼頃橃劯鞖�.</string>
+    <string name="watch9_calibration_button">氤挫爼</string>
+    <string name="title_activity_watch9_pairing">Watch 9 韼橃柎毵�</string>
+    <string name="title_activity_watch9_calibration">Watch 9 氤挫爼</string>
+    <string name="pref_title_contextual_arabic">氍鸽Д 鞎勲瀺鞏� 旎る嫕 旒滉赴</string>
+    <string name="pref_summary_contextual_arabic">氍鸽Д 鞎勲瀺鞏� 觳橂Μ 歆�鞗愳潉 頇滌劚頇� 頃╇媹雼�</string>
+    <string name="preferences_rtl_settings">鞓るジ飒届棎靹� 鞕检鞙茧 鞊半姅 氍胳瀽 歆�鞗�</string>
+    <string name="share_log">旮半 瓿奠湢</string>
+    <string name="share_log_warning">Gadgetbridge 旮半鞚� 瓯搓皶 雿办澊韯�, 臧滌澑鞚� 鞁濍硠頃� 靾� 鞛堧姅 鞝曤炒 (鞓堧ゼ 霌れ柎靹� 鞛レ箻鞚� MAC 欤检唽), 鞚岇晠 靹れ爼 霌膘潉 韽暔頃橂姅 毵庫潃 臧滌澑鞝曤炒毳� 韽暔頃橁碃 鞛堨潉 靾� 鞛堨姷雼堧嫟. 瓿店皽 鞚挫妶 韸鸽爤旎れ棎 韺岇澕鞚� 氤措偞旮� 鞝勳棎 韺岇澕鞚� 韼胳頃� 鞚措煬頃� 鞝曤炒毳� 鞝滉卑頃橂姅 瓴冹潉 瓿犽牑頃橃劯鞖�.</string>
+    <string name="warning">瓴疥碃!</string>
+    <string name="no_data">雿办澊韯� 鞐嗢潓</string>
+    <string name="preferences_led_color">LED 靸夓儊</string>
+    <string name="preferences_fm_frequency">FM 欤柬寣靾�</string>
+    <string name="pref_invalid_frequency_title">鞓皵毳挫 鞎婌潃 欤柬寣靾�</string>
+    <string name="pref_invalid_frequency_message">87.5鞕� 108.0 靷澊鞚� 欤柬寣靾橂ゼ 鞛呺牓頃橃劯鞖�</string>
+    <string name="mi2_prefs_button_press_broadcast_default_value" translatable="false">nodomain.freeyourgadget.gadgetbridge.ButtonPressed</string>
+    <string name="sonyswr12_settings_title">Sony SWR12 靹れ爼</string>
+    <string name="sonyswr12_settings_low_vibration">鞝�歆勲彊 臧�電�</string>
+    <string name="sonyswr12_settings_low_vibration_summary">靻愲 氚措摐鞐愳劀 雮潃 臧曤弰鞚� 歆勲彊 頇滌劚頇�</string>
+    <string name="sonyswr12_settings_stamina">鞝堨爠 氇摐 旒滉赴</string>
+    <string name="sonyswr12_settings_stamina_summary">鞝堨爠 氇摐電旍<旮办爜鞚� 鞁皶靾� 鞛愲彊 旄§爼鞚� 頃挫牅頃橃棳 鞛戩梾 鞁滉皠鞚� 電橂雼堧嫟.</string>
+    <string name="sonyswr12_settings_alarm_interval">攵� 雼渼鞚� 鞀る韸� 鞎岆瀸 臧勱博</string>
+    <string name="sonyswr12_settings_alarm_interval_summary">鞀る韸� 鞎岆瀸 臧勱博鞚� 靹れ箻霅� 鞎岆瀸 鞚挫爠鞚� 臧勱博鞛呺媹雼�. 鞚� 臧勱博鞐愳劀 鞛レ箻電� 靷毄鞛愲ゼ 旯毎旮� 鞙勴暣 臧�鞛� 臧�氩检毚 靾橂┐ 雼硠毳� 臧愳頃橂牑瓿犿暕雼堧嫟.</string>
+    <string name="controlcenter_power_off">鞝勳洂 雭勱赴</string>
+    <string name="calendar_day">鞚�</string>
+    <string name="calendar_two_weeks">2欤�</string>
+    <string name="accuracy">鞝曧檿霃�</string>
+    <string name="about_activity_title_generic">臧�鞝笇毽快 鞝曤炒</string>
+    <string name="application_name_banglejs_main">Bangle.js 臧�鞝笇毽快</string>
+    <string name="title_activity_controlcenter_banglejs_main">Bangle.js 臧�鞝笇毽快</string>
+    <string name="about_activity_title_banglejs_main">Bangle.js 臧�鞝笇毽快 鞝曤炒</string>
+    <string name="controlcenter_power_off_confirm_title">鞝勳洂 雭勱赴</string>
+    <string name="controlcenter_power_off_confirm_description">旮瓣赴鞚� 鞝勳洂鞚� 雭勱碃 鞁鹅潃 瓴冹澊 頇曥嫟頃滉皜鞖�?</string>
+    <string name="battery_level">氚绊劙毽� 霠堧波</string>
+    <string name="calendar_week">欤�</string>
+    <string name="controlcenter_set_alias">氤勲獏 靹れ爼</string>
+    <string name="prefs_activity_in_device_card_sleep_title">靾橂┐</string>
+    <string name="activity_list_summary_distance">瓯半Μ</string>
+    <string name="activity_list_summary_active_steps">頇滌劚 瓯胳潓</string>
+    <string name="controlcenter_get_heartrate_measurement">鞁皶靾� 旄§爼 鞏魂赴</string>
+    <string name="battery_detail_activity_title">氚绊劙毽� 鞝曤炒</string>
+    <string name="power_saving">鞝堨爠</string>
+    <string name="custom">旎れ姢韰�</string>
+    <string name="dialog_hide">靾赴旮�</string>
+    <string name="charts_activity_list">頇滊彊 氇╇</string>
+    <string name="action_changelog">氤�瓴� 旮半</string>
+    <string name="balanced">攴犿槙</string>
+    <string name="low_power_gps">鞝�鞝勲牓 GPS</string>
+    <string name="gps">GPS</string>
+    <string name="gps_bds">GPS + BDS</string>
+    <string name="gps_gnolass">GPS + GNOLASS</string>
+    <string name="gps_galileo">GPS + GALILEO</string>
+    <string name="prefs_activity_in_device_card_sleep_title_summary">靾橂┐ 鞁滉皠 響滌嫓</string>
+    <string name="prefs_activity_in_device_card_distance_title_summary">瓯半Μ電� 瓯胳潓瓿� 氤错彮(靹れ爼 - 雮橃潣 鞝曤炒鞐愳劀 氤�瓴� 臧�電�)鞐愳劀 瓿勳偘霅╇媹雼�</string>
+    <string name="prefs_activity_in_device_card_steps_title_summary">鞝勳泊 瓯胳潓 靾� 響滌嫓</string>
+    <string name="calendar_month">雼�</string>
+    <string name="calendar_three_months">3雼�</string>
+    <string name="calendar_six_months">6雼�</string>
+    <string name="calendar_year">雲�</string>
+    <string name="appmanager_app_start">鞁滌瀾</string>
+    <string name="activity_list_summary_active_time">頇滌劚 鞁滉皠</string>
+    <string name="activity_list_summary_activities">頇滊彊</string>
+    <string name="fw_upgrade_notice_amazfit_band7">歆�旮� 鞏措鞚挫頃� 氚措摐 7鞐� %s 韼岇洦鞏措ゼ 靹れ箻頃橂牑瓿� 頃╇媹雼�.\n\n.zip 韺岇澕鞚� 靹れ箻頃� 頉� 旮瓣赴臧� 鞛嫓鞛戨惄雼堧嫟.\n\n氤胳澑鞚� 毂呾瀯 頃橃棎 歆勴枆頃橃劯鞖�!</string>
+    <string name="pref_pair_bluetooth_calls_summary">鞐赴毳� 雸岆煬 韼橃柎毵� 鞁滌瀾</string>
+    <string name="pref_title_notification_cache_while_disconnected">鞐瓣舶 霊愳爤鞁� 旌愳嫳</string>
+    <string name="pref_title_support_voip_calls">VoIP 鞎� 鞝勴檾 頇滌劚頇�</string>
+    <string name="appmanager_downloaded_to_cache">%s鞚�/毳� 旌愳嫓鞐� 鞝�鞛ロ暔</string>
+    <string name="appmanager_download_app_error">鞎� 雼れ毚搿滊摐 鞓る</string>
+    <string name="find_lost_device_message">%1$s鞚�/毳� 彀眷潉旯岇殧?</string>
+    <string name="pref_header_equalizer">鞚错�勲澕鞚挫爛</string>
+    <string name="pref_header_other">旮绊儉</string>
+    <string name="pref_header_health">瓯搓皶</string>
+    <string name="pref_header_time">鞁滉皠</string>
+    <string name="about_description_banglejs_main">Bangle.js鞖� 鞎堧摐搿滌澊霌� 霃欕皹 鞎膘潃 臧�鞝笇毽快 頂勲鞝濏姼毳� 旮半皹鞙茧 鞚疙劙雱� 鞝戧芳 甓岉暅鞚� 於旉皜頃╇媹雼�.\n\n甑竴 頂岆爤鞚� 鞀ろ啝鞏� 鞝曥眳鞙茧 鞚疙暣 鞎� 雮挫棎 旮半秬 毵來伂毳� 響滌嫓頃� 靾� 鞐嗢姷雼堧嫟. 頃橃毵� 鞚� 鞎膘澊 毵堨潓鞐� 霌滌叏雼る┐ 鞎勲灅 臧�鞝笇毽快 頇堩帢鞚挫毳� 韱淀暣 旮半秬毳� 攵�韮侂摐毽诫媹雼�.</string>
+    <string name="gadgetbridge_running_banglejs_main">Bangle.js 鞁ろ枆欷�</string>
+    <string name="title_activity_notification_management">鞎岆 靹れ爼</string>
+    <string name="appmanager_item_outdated">(甑矂鞝�)</string>
+    <string name="fw_upgrade_notice_miband5">歆�旮� 氙鸽按霌� 5鞐� %s 韼岇洦鞏措ゼ 靹れ箻頃橂牑瓿� 頃╇媹雼�.\n\n.fw 韺岇澕, .res 韺岇澕 靾滌劀雽�搿� 靹れ箻頃橃嫮鞁滌槫. .fw 韺岇澕鞚� 靹れ箻頃� 頉� 旮瓣赴臧� 鞛嫓鞛� 霅╇媹雼�.\n\n牍勱碃: 毵岇暯 .res 韺岇澕鞚� 鞚挫爠瓿� 鞕勳爠 霃欖澕頃橂嫟氅� 雼れ嫓 靹れ箻頃� 頃勳殧 鞐嗢姷雼堧嫟.\n\n氤胳澑鞚� 毂呾瀯 頃橃棎 歆勴枆頃橃劯鞖�!</string>
+    <string name="fw_upgrade_notice_amazfitneo">歆�旮� 鞏措鞚挫頃� 雱れ槫鞐� %s 韼岇洦鞏措ゼ 靹れ箻頃橂牑瓿� 頃╇媹雼�.\n\n.fw 韺岇澕鞚� 靹れ箻頃� 頉� 旮瓣赴臧� 鞛嫓鞛戨惄雼堧嫟.\n\n氤胳澑鞚� 毂呾瀯 頃橃棎 歆勴枆頃橃劯鞖�!</string>
+    <string name="application_name_banglejs_nightly">Bangle.js 臧�鞝笇毽快 (Nightly)</string>
+    <string name="pref_header_calendar">雼牓</string>
+    <string name="pref_summary_receive_calls_watch">鞁滉硠鞐愳劀 氚旊 鞝勴檾毳� 瓯戈碃 氚涥赴</string>
+    <string name="pref_title_notification_wake_on_open">鞛愲彊 旯泙 氚� 鞛犼笀 頃挫牅</string>
+    <string name="debugactivity_confirm_remove_device_preferences_title">旮瓣赴 靹れ爼鞚� 歆�鞖戈箤鞖�?</string>
+    <string name="debugactivity_confirm_remove_device_preferences">氇摖 鞐瓣舶 旮瓣赴鞚� 靹れ爼鞚� 齑堦赴頇旐暕雼堧嫟. 歆勴枆頃犼箤鞖�?</string>
+    <string name="pref_theme_system">鞁滌姢韰�</string>
+    <string name="pref_theme_dynamic">鞙犽彊 旎煬</string>
+    <string name="pref_title_notification_delay_calls">鞝勴檾 鞎岆 歆�鞐�</string>
+    <string name="open_fw_installer_select_file">旮瓣赴鞐� 鞓Υ 韺岇澕鞚� 瓿犽ゴ靹胳殧: %s</string>
+    <string name="pref_summary_general_reconnectonlytoconnected">氇摖 旮瓣赴 雽�鞁� 鞐瓣舶霅� 旮瓣赴毵� 鞛棸瓴�</string>
+    <string name="open_fw_installer_connect_minimum_one_device">韺岇澕鞚� 氤措偧 旮瓣赴 頃橂倶 鞚挫儊鞚� 鞐瓣舶頃橃劯鞖�.</string>
+    <string name="pref_title_general_reconnectonlytoconnected">鞐瓣舶霅� 旮瓣赴毵� 鞛棸瓴�</string>
+    <string name="pref_summary_mb_intents">鞚茧秬 鞏错攲毽紑鞚挫厴鞐愳劀 氙鸽敂鞏� 臁办爤鞚� 鞛戨彊頃橃 鞎婌潉 鞁� 頇滌劚頇�</string>
+    <string name="bluetooth_calls">敫旊(韴姢 鞝勴檾</string>
+    <string name="pref_pair_bluetooth_calls_help_warning">欤检潣: 霊橃Ц 韼橃柎毵侅潉 頃橃 鞎婈碃 敫旊(韴姢 鞝勴檾毳� 頇滌劚頇旐暊 瓴届毎, 鞝勴檾 鞎岆鞚� 鞝曥儊 鞛戨彊頃橃 鞎婌潉 靾� 鞛堨姷雼堧嫟.</string>
+    <string name="pref_title_notifications_ignore_work_profile">須岇偓 頂勲頃� 鞎岆 氍挫嫓</string>
+    <string name="pref_summary_notifications_ignore_low_priority">鞝�靾滌渼 氚� 斓滌爛靾滌渼 鞎岆鞚� 旮瓣赴鞐� 氤措偞歆� 鞎婌潓</string>
+    <string name="pref_title_banglejs_txt_bitmap_size">旮�鞛� 牍勴姼毵� 韥赴</string>
+    <string name="fw_upgrade_notice_miband6">歆�旮� 氙鸽按霌� 6鞐� %s 韼岇洦鞏措ゼ 靹れ箻頃橂牑瓿� 頃╇媹雼�.\n\n.fw 韺岇澕, .res 韺岇澕 靾滌劀雽�搿� 靹れ箻頃橃嫮鞁滌槫. .fw 韺岇澕鞚� 靹れ箻頃� 頉� 旮瓣赴臧� 鞛嫓鞛� 霅╇媹雼�.\n\n牍勱碃: 毵岇暯 .res 韺岇澕鞚� 鞚挫爠瓿� 鞕勳爠 霃欖澕頃橂嫟氅� 雼れ嫓 靹れ箻頃� 頃勳殧 鞐嗢姷雼堧嫟.\n\n氤胳澑鞚� 毂呾瀯 頃橃棎 歆勴枆頃橃劯鞖�!</string>
+    <string name="fw_upgrade_notice_amazfit_gts4_mini">歆�旮� 鞏措鞚挫頃� GTS 4 氙鸽媹鞐� %s 韼岇洦鞏措ゼ 靹れ箻頃橂牑瓿� 頃╇媹雼�.\n\n.zip 韺岇澕鞚� 靹れ箻頃� 頉� 旮瓣赴臧� 鞛嫓鞛戨惄雼堧嫟.\n\n氤胳澑鞚� 毂呾瀯 頃橃棎 歆勴枆頃橃劯鞖�!</string>
+    <string name="open_fw_installer_warning_title">瓴疥碃</string>
+    <string name="pref_pair_bluetooth_calls_help_1">1. 鞎勲灅 氩勴娂鞚� 雸岆煬 韼橃柎毵侅潉 鞁滌瀾.</string>
+    <string name="pref_pair_bluetooth_calls_help_summary">敫旊(韴姢 鞝勴檾毳� 靾橃嫚頃橂牑氅�, 鞝勴檾毳� 鞁滉硠鞚� 霊橃Ц 鞚胳姢韯挫姢鞕� 韼橃柎毵來暣鞎� 頃╇媹雼�.</string>
+    <string name="pref_title_notifications_generic_settings">鞎堧摐搿滌澊霌� 鞎岆 靹れ爼</string>
+    <string name="pref_header_notification_application_settings">鞎犿攲毽紑鞚挫厴氤� 靹れ爼</string>
+    <string name="pref_summary_banglejs_phone_gps_enbale">Bangle 旮瓣赴鞚� GPS 鞝曤炒毳� 韽� GPS 鞝曤炒搿� 雽�觳�</string>
+    <string name="proprietary_app_warning">鞚� 旮半姤鞚� 霃呾爯 鞎膘潉 靹れ箻頃挫暭 鞚挫毄 臧�電ロ暕雼堧嫟</string>
+    <string name="pref_header_audio">靻岆Μ</string>
+    <string name="pref_header_connection">鞐瓣舶</string>
+    <string name="pref_summary_notification_delay_calls">旮瓣赴搿� 鞝勴檾 靾橃嫚 鞎岆鞚� 氤措偞旮� 鞝� 歆�鞐�, 齑� 雼渼.</string>
+    <string name="pref_summary_notification_wake_on_open">旮瓣赴臧� 鞐措 鞚戨嫷鞚� 氤措偞氅� 鞎堧摐搿滌澊霌滊ゼ 旯毎瓿� 鞛犼笀 頃挫牅頃橁赴. 鞁犽頃橂姅 旮瓣赴搿� 霌彪霅橃柎 鞛堨柎鞎� 霅�.</string>
+    <string name="pref_pair_bluetooth_calls_title">敫旊(韴姢 鞝勴檾搿� 韼橃柎毵�</string>
+    <string name="pref_pair_bluetooth_calls_help_title">敫旊(韴姢 鞝勴檾 靾橃嫚 氚╇矔</string>
+    <string name="pref_pair_bluetooth_calls_help_2">2. 鞝勴檾旮办潣 敫旊(韴姢 靹れ爼鞙茧 臧�靹�, 瓿� 霚姅 靸� 旮瓣赴鞕� 韼橃柎毵來晿旮� (鞁滉硠 鞚措瓿� 牍勳姺頃橂倶 鞝戨鞏搓皜 攵欖潓. 鞓�: \"Amazfit GTR 4 - AFC8\").</string>
+    <string name="pref_summary_notification_cache_while_disconnected">鞐瓣舶氩旍渼 氚栰棎 雮橁皵雼� 鞛爲靻嶌嫓 氇� 氚涭潃 鞎岆 氤措偞旮�</string>
+    <string name="pref_summary_notification_prefer_long_text">臧�電ロ晿氅� 旮� 氍胳灔鞙茧 鞎岆 氤措偞旮�</string>
+    <string name="pref_title_banglejs_phone_gps_enbale">韽� GPS 鞝曤炒 靷毄</string>
+    <string name="open_fw_installer_connect_maximum_one_device">韺岇澕鞚� 氤措偧 旮瓣赴 頃橂倶毵� 鞐瓣舶頃橃劯鞖�.</string>
+    <string name="open_fw_installer_ensure_device_connected">%s 旮瓣赴臧� 鞐瓣舶霅愲姅歆� 頇曥澑頃橃劯鞖�</string>
+    <string name="fw_upgrade_notice_amazfit_cheetah_pro">歆�旮� 鞏措鞚挫頃� 旃橅儉 頂勲鞐� %s 韼岇洦鞏措ゼ 靹れ箻頃橂牑瓿� 頃╇媹雼�.\n\n.zip 韺岇澕鞚� 靹れ箻頃� 頉� 旮瓣赴臧� 鞛嫓鞛戨惄雼堧嫟.\n\n氤胳澑鞚� 毂呾瀯 頃橃棎 歆勴枆頃橃劯鞖�!</string>
+    <string name="open_fw_installer_pick_file">韺岇澕 靹犿儩</string>
+    <string name="pref_header_sound">靻岆Μ</string>
+    <string name="bluetooth_calls_pairing">敫旊(韴姢 鞝勴檾 韼橃柎毵�</string>
+    <string name="pref_display_caller_summary">靾橃嫚 鞝勴檾鞚� 鞝勴檾氩堩樃 霕愲姅 鞚措 氤挫澊旮�</string>
+    <string name="pref_pair_bluetooth_calls_help_3">3. 鞎勲灅 靹れ爼鞐愳劀 敫旊(韴姢 鞝勴檾 頇滌劚頇旐晿旮�.</string>
+    <string name="pref_title_ping_tone">頃� 靻岆Μ</string>
+    <string name="about_description_generic">韥措歃堧摐 靻岇姢 鞎堧摐搿滌澊霌� 欤茧硛旮瓣赴鞖� 鞎膘潉 雽�鞁犿晿電� 韮堩伌霛检毎霌� 旃错敿霠堩攧韸� 毽笇霠� 雽�觳挫牅.</string>
+    <string name="search">彀娟赴</string>
+    <string name="accuracy_first">鞝曧檿霃� 鞖办劆</string>
+    <string name="check_all_applications">氇摖 鞎� 靹犿儩</string>
+    <string name="fw_upgrade_notice_amazfitbip3">歆�旮� 鞏措鞚挫頃� 牍� 3鞐� %s 韼岇洦鞏措ゼ 靹れ箻頃橂牑瓿� 頃╇媹雼�.\n\n.fw 韺岇澕, .res 韺岇澕 靾滌劀雽�搿� 靹れ箻頃橃嫮鞁滌槫. .fw 韺岇澕鞚� 靹れ箻頃� 頉� 旮瓣赴臧� 鞛嫓鞛� 霅╇媹雼�.\n\n牍勱碃: 毵岇暯 .res 韺岇澕鞚� 鞚挫爠瓿� 鞕勳爠 霃欖澕頃橂嫟氅� 雼れ嫓 靹れ箻頃� 頃勳殧 鞐嗢姷雼堧嫟.\n\n氤胳澑鞚� 毂呾瀯 頃橃棎 歆勴枆頃橃劯鞖�!</string>
+    <string name="fw_upgrade_notice_amazfitbip3pro">歆�旮� 鞏措鞚挫頃� 牍� 3 頂勲鞐� %s 韼岇洦鞏措ゼ 靹れ箻頃橂牑瓿� 頃╇媹雼�.\n\n.fw 韺岇澕, .res 韺岇澕 靾滌劀雽�搿� 靹れ箻頃橃嫮鞁滌槫. .fw 韺岇澕鞚� 靹れ箻頃� 頉� 旮瓣赴臧� 鞛嫓鞛� 霅╇媹雼�.\n\n牍勱碃: 毵岇暯 .res 韺岇澕鞚� 鞚挫爠瓿� 鞕勳爠 霃欖澕頃橂嫟氅� 雼れ嫓 靹れ箻頃� 頃勳殧 鞐嗢姷雼堧嫟.\n\n氤胳澑鞚� 毂呾瀯 頃橃棎 歆勴枆頃橃劯鞖�!</string>
+    <string name="fw_upgrade_notice_amazfitbip_lite">歆�旮� 鞏措鞚挫頃� 牍� 霛检澊韸胳棎 %s 韼岇洦鞏措ゼ 靹れ箻頃橂牑瓿� 頃╇媹雼�.\n\n.fw 韺岇澕, .res 韺岇澕 靾滌劀雽�搿� 靹れ箻頃橃嫮鞁滌槫. .fw 韺岇澕鞚� 靹れ箻頃� 頉� 旮瓣赴臧� 鞛嫓鞛� 霅╇媹雼�.\n\n牍勱碃: 毵岇暯 .res 韺岇澕鞚� 鞚挫爠瓿� 鞕勳爠 霃欖澕頃橂嫟氅� 雼れ嫓 靹れ箻頃� 頃勳殧 鞐嗢姷雼堧嫟.\n\n氤胳澑鞚� 毂呾瀯 頃橃棎 歆勴枆頃橃劯鞖�!</string>
+    <string name="fw_upgrade_notice_amazfitcor2">歆�旮� 鞏措鞚挫頃� 旖旍柎 2鞐� %s 韼岇洦鞏措ゼ 靹れ箻頃橂牑瓿� 頃╇媹雼�.\n\n.fw 韺岇澕, .res 韺岇澕 靾滌劀雽�搿� 靹れ箻頃橃嫮鞁滌槫. .fw 韺岇澕鞚� 靹れ箻頃� 頉� 旮瓣赴臧� 鞛嫓鞛� 霅╇媹雼�.\n\n牍勱碃: 毵岇暯 .res 韺岇澕鞚� 鞚挫爠瓿� 鞕勳爠 霃欖澕頃橂嫟氅� 雼れ嫓 靹れ箻頃� 頃勳殧 鞐嗢姷雼堧嫟.\n\n氤胳澑鞚� 毂呾瀯 頃橃棎 歆勴枆頃橃劯鞖�!\n\n韰岇姢韸鸽悩歆� 鞎婌潓, 毵岇暯 旮瓣赴氇呾澊 \"Amazfit Band 2\"鞚� 瓴届毎 BEATS_W 韼岇洦鞏� 靹れ箻臧� 頃勳殧頃� 靾橂弰 鞛堨潓.</string>
+    <string name="fw_upgrade_notice_amazfitx">歆�旮� 鞏措鞚挫頃� X鞐� %s 韼岇洦鞏措ゼ 靹れ箻頃橂牑瓿� 頃╇媹雼�.\n\n.fw 韺岇澕, .res 韺岇澕 靾滌劀雽�搿� 靹れ箻頃橃嫮鞁滌槫. .fw 韺岇澕鞚� 靹れ箻頃� 頉� 旮瓣赴臧� 鞛嫓鞛� 霅╇媹雼�.\n\n牍勱碃: 毵岇暯 .res 韺岇澕鞚� 鞚挫爠瓿� 鞕勳爠 霃欖澕頃橂嫟氅� 雼れ嫓 靹れ箻頃� 頃勳殧 鞐嗢姷雼堧嫟.\n\n氤胳澑鞚� 毂呾瀯 頃橃棎 歆勴枆頃橃劯鞖�!</string>
+    <string name="fw_upgrade_notice_amazfit_gtr4">歆�旮� 鞏措鞚挫頃� GTR 4鞐� %s 韼岇洦鞏措ゼ 靹れ箻頃橂牑瓿� 頃╇媹雼�.\n\n.zip 韺岇澕鞚� 靹れ箻頃� 頉� 旮瓣赴臧� 鞛嫓鞛戨惄雼堧嫟.\n\n氤胳澑鞚� 毂呾瀯 頃橃棎 歆勴枆頃橃劯鞖�!</string>
+    <string name="fw_upgrade_notice_zepp_os">歆�旮� %s 韼岇洦鞏措ゼ %s鞐� 靹れ箻頃橂牑瓿� 頃╇媹雼�.\n\n.zip 韺岇澕鞚� 靹れ箻頃� 頉� 旮瓣赴臧� 鞛嫓鞛戨惄雼堧嫟.\n\n氤胳澑鞚� 毂呾瀯 頃橃棎 歆勴枆頃橃劯鞖�!</string>
+    <string name="open_fw_installer_getting_files_text">鞚� 鞎膘潃 韼岇洦鞏� 韺岇澕鞚� 氚绊彫頃� 靾� 鞐嗢溂氙�搿�, 靷毄鞛愱皜 歆侅爲 韺岇澕鞚� 甑箘頃挫暭 頃╇媹雼�. 韼岇洦鞏� 韺岇澕鞚� apk 韺岇澕, 鞚疙劙雱�, 韽熂, Amazfitwatchfaces (氙鸽按霌�/鞏措鞚挫頃�) 霌膘棎靹� 彀眷晞氤挫劯鞖�.</string>
+    <string name="pref_header_offline_voice">鞓ろ攧霛检澑 鞚岇劚</string>
+    <string name="bluetooth_calls_settings">敫旊(韴姢 鞝勴檾 靹れ爼</string>
+    <string name="pref_display_caller_title">鞝勴檾氩堩樃 鞝曤炒 氤挫澊旮�</string>
+    <string name="pref_title_notification_times_enabled">鞎岆 鞁滉皠</string>
+    <string name="pref_summary_notification_times_enabled">鞎岆鞚� 氤措偧 靾� 鞛堧姅 鞁滉皠雽� 靹れ爼</string>
+    <string name="pref_title_notification_media_ignores_application_list">氙鸽敂鞏� 鞎岆 氍挫嫓 鞎� 氇╇</string>
+    <string name="pref_title_banglejs_text_bitmap">旮�鞛� 牍勴姼毵� 氤�頇�</string>
+    <string name="pref_summary_banglejs_txt_bitmap_size">牍勴姼毵� 旮�鞛� 氤�頇� 韥赴</string>
+    <string name="single_band">鞁标竴 氚措摐</string>
+    <string name="dual_band">霌�鞏� 氚措摐</string>
+    <string name="gps_glonass">GPS + 旮�搿滊倶鞀�</string>
+    <string name="all_satellites">氇摖 鞙勳劚</string>
+    <string name="speed_first">靻嶋弰 鞖办劆</string>
+    <string name="device_card_activity_card_title">旮瓣赴 旃措摐鞐� 頇滊彊旮半 響滌嫓</string>
+    <string name="device_card_activity_card_title_summary">旮瓣赴 旃措摐鞐� 鞏措枻 頇滊彊雮挫棴鞚� 響滌嫓頃犾 瓿犽雼堧嫟</string>
+    <string name="prefs_activity_in_device_card_title">旮瓣赴 旃措摐鞐� 頇滊彊旮半 氤挫澊旮�</string>
+    <string name="prefs_activity_in_device_card_title_summary">順勳灛 瓯胳潓靾�, 瓯半Μ, 霕愲姅 靾橂┐ 旮半鞚� 旮瓣赴 旃措摐鞐� 氤挫澊旮�</string>
+    <string name="appmanager_watchface_activate">頇滌劚頇�</string>
+    <string name="appmanager_app_download">旌愳嫓搿� 雼れ毚搿滊摐</string>
+    <string name="appmanager_app_share">瓿奠湢</string>
+    <string name="appmanager_download_started">鞎� 雼れ毚搿滊摐 鞁滌瀾</string>
+    <string name="uncheck_all_applications">氇摖 鞎� 靹犿儩 頃挫牅</string>
+    <string name="fw_upgrade_notice_amazfitgtr">歆�旮� 鞏措鞚挫頃� GTR鞐� %s 韼岇洦鞏措ゼ 靹れ箻頃橂牑瓿� 頃╇媹雼�.\n\n.fw 韺岇澕, .res 韺岇澕, .gps 韺岇澕 靾滌劀雽�搿� 靹れ箻頃橃嫮鞁滌槫. .fw 韺岇澕鞚� 靹れ箻頃� 頉� 旮瓣赴臧� 鞛嫓鞛� 霅╇媹雼�.\n\n牍勱碃: 毵岇暯 .res 韺岇澕瓿� .gps 韺岇澕鞚� 鞚挫爠瓿� 鞕勳爠 霃欖澕頃橂嫟氅� 雼れ嫓 靹れ箻頃� 頃勳殧 鞐嗢姷雼堧嫟.\n\n氤胳澑鞚� 毂呾瀯 頃橃棎 歆勴枆頃橃劯鞖�!</string>
+    <string name="fw_upgrade_notice_amazfitgts">歆�旮� 鞏措鞚挫頃� GTS鞐� %s 韼岇洦鞏措ゼ 靹れ箻頃橂牑瓿� 頃╇媹雼�.\n\n.fw 韺岇澕, .res 韺岇澕, .gps 韺岇澕 靾滌劀雽�搿� 靹れ箻頃橃嫮鞁滌槫. .fw 韺岇澕鞚� 靹れ箻頃� 頉� 旮瓣赴臧� 鞛嫓鞛� 霅╇媹雼�.\n\n牍勱碃: 毵岇暯 .res 韺岇澕瓿� .gps 韺岇澕鞚� 鞚挫爠瓿� 鞕勳爠 霃欖澕頃橂嫟氅� 雼れ嫓 靹れ箻頃� 頃勳殧 鞐嗢姷雼堧嫟.\n\n氤胳澑鞚� 毂呾瀯 頃橃棎 歆勴枆頃橃劯鞖�!</string>
+    <string name="fw_upgrade_notice_amazfit_trex">歆�旮� 鞏措鞚挫頃� 韹半爥鞀れ棎 %s 韼岇洦鞏措ゼ 靹れ箻頃橂牑瓿� 頃╇媹雼�.\n\n.fw 韺岇澕, .res 韺岇澕, .gps 韺岇澕 靾滌劀雽�搿� 靹れ箻頃橃嫮鞁滌槫. .fw 韺岇澕鞚� 靹れ箻頃� 頉� 旮瓣赴臧� 鞛嫓鞛� 霅╇媹雼�.\n\n牍勱碃: 毵岇暯 .res 韺岇澕瓿� .gps 韺岇澕鞚� 鞚挫爠瓿� 鞕勳爠 霃欖澕頃橂嫟氅� 雼れ嫓 靹れ箻頃� 頃勳殧 鞐嗢姷雼堧嫟.\n\n氤胳澑鞚� 毂呾瀯 頃橃棎 歆勴枆頃橃劯鞖�!</string>
+    <string name="fw_upgrade_notice_miband4">歆�旮� 氙鸽按霌� 4鞐� %s 韼岇洦鞏措ゼ 靹れ箻頃橂牑瓿� 頃╇媹雼�.\n\n.fw 韺岇澕, .res 韺岇澕 靾滌劀雽�搿� 靹れ箻頃橃嫮鞁滌槫. .fw 韺岇澕鞚� 靹れ箻頃� 頉� 旮瓣赴臧� 鞛嫓鞛� 霅╇媹雼�.\n\n牍勱碃: 毵岇暯 .res 韺岇澕鞚� 鞚挫爠瓿� 鞕勳爠 霃欖澕頃橂嫟氅� 雼れ嫓 靹れ箻頃� 頃勳殧 鞐嗢姷雼堧嫟.\n\n氤胳澑鞚� 毂呾瀯 頃橃棎 歆勴枆頃橃劯鞖�!</string>
+    <string name="fw_upgrade_notice_miband7">歆�旮� 靸れ槫氙� 鞀る韸鸽按霌� 7鞐� %s 韼岇洦鞏措ゼ 靹れ箻頃橂牑瓿� 頃╇媹雼�.\n\n.zip 韺岇澕鞚� 靹れ箻頃� 頉� 旮瓣赴臧� 鞛嫓鞛戨惄雼堧嫟.\n\n氤胳澑鞚� 毂呾瀯 頃橃棎 歆勴枆頃橃劯鞖�!</string>
+    <string name="fw_upgrade_notice_amazfit_gts3">歆�旮� 鞏措鞚挫頃� GTS 3鞐� %s 韼岇洦鞏措ゼ 靹れ箻頃橂牑瓿� 頃╇媹雼�.\n\n.zip 韺岇澕鞚� 靹れ箻頃� 頉� 旮瓣赴臧� 鞛嫓鞛戨惄雼堧嫟.\n\n氤胳澑鞚� 毂呾瀯 頃橃棎 歆勴枆頃橃劯鞖�!</string>
+    <string name="fw_upgrade_notice_amazfitvergel">歆�旮� 鞏措鞚挫頃� 氩勳 霛检澊韸胳棎 %s 韼岇洦鞏措ゼ 靹れ箻頃橂牑瓿� 頃╇媹雼�.\n\n.fw 韺岇澕, .res 韺岇澕, .gps 韺岇澕 靾滌劀雽�搿� 靹れ箻頃橃嫮鞁滌槫. .fw 韺岇澕鞚� 靹れ箻頃� 頉� 旮瓣赴臧� 鞛嫓鞛� 霅╇媹雼�.\n\n牍勱碃: 毵岇暯 .res 韺岇澕瓿� .gps 韺岇澕鞚� 鞚挫爠瓿� 鞕勳爠 霃欖澕頃橂嫟氅� 雼れ嫓 靹れ箻頃� 頃勳殧 鞐嗢姷雼堧嫟.\n\n氤胳澑鞚� 毂呾瀯 頃橃棎 歆勴枆頃橃劯鞖�!</string>
+    <string name="fw_upgrade_notice_amazfit_gts4">歆�旮� 鞏措鞚挫頃� GTS 4鞐� %s 韼岇洦鞏措ゼ 靹れ箻頃橂牑瓿� 頃╇媹雼�.\n\n.zip 韺岇澕鞚� 靹れ箻頃� 頉� 旮瓣赴臧� 鞛嫓鞛戨惄雼堧嫟.\n\n氤胳澑鞚� 毂呾瀯 頃橃棎 歆勴枆頃橃劯鞖�!</string>
+    <string name="fw_upgrade_notice_amazfit_gtr3">歆�旮� 鞏措鞚挫頃� GTR 3鞐� %s 韼岇洦鞏措ゼ 靹れ箻頃橂牑瓿� 頃╇媹雼�.\n\n.zip 韺岇澕鞚� 靹れ箻頃� 頉� 旮瓣赴臧� 鞛嫓鞛戨惄雼堧嫟.\n\n氤胳澑鞚� 毂呾瀯 頃橃棎 歆勴枆頃橃劯鞖�!</string>
+    <string name="fw_upgrade_notice_amazfit_gtr3_pro">歆�旮� 鞏措鞚挫頃� GTR 3 頂勲鞐� %s 韼岇洦鞏措ゼ 靹れ箻頃橂牑瓿� 頃╇媹雼�.\n\n.zip 韺岇澕鞚� 靹れ箻頃� 頉� 旮瓣赴臧� 鞛嫓鞛戨惄雼堧嫟.\n\n氤胳澑鞚� 毂呾瀯 頃橃棎 歆勴枆頃橃劯鞖�!</string>
+    <string name="fw_upgrade_notice_amazfit_trex2">歆�旮� 鞏措鞚挫頃� 韹半爥鞀� 2鞐� %s 韼岇洦鞏措ゼ 靹れ箻頃橂牑瓿� 頃╇媹雼�.\n\n.zip 韺岇澕鞚� 靹れ箻頃� 頉� 旮瓣赴臧� 鞛嫓鞛戨惄雼堧嫟.\n\n氤胳澑鞚� 毂呾瀯 頃橃棎 歆勴枆頃橃劯鞖�!</string>
+    <string name="open_fw_installer_warning_text">鞚� 旮半姤鞚� 旮瓣赴毳� 靷毄 攵堦皜電ロ暅 靸來儨搿� 毵岆摛 靾� 鞛堨姷雼堧嫟. 鞁れ牅搿� 臧滊皽歆勳澊 鞚措ゼ 氇╆博頃� 瓴届毎電� 鞐嗢溂雮�, 鞓れ瀾霃欖嫓 毂呾瀯鞚� 靷毄鞛愳棎瓴� 鞛堨潓鞚� 氇呾嫭頃橃劯鞖�.</string>
+    <string name="open_fw_installer_getting_files_title">韼岇洦鞏�/鞎� 韺岇澕 氚涥赴</string>
+    <string name="open_fw_installer_info_text_title">韺岇澕 靹れ箻甏�毽瀽</string>
+    <string name="pref_header_system">鞁滌姢韰�</string>
+    <string name="pref_header_display">頇旊┐</string>
+    <string name="pref_header_sound_vibration">靻岆Μ 氚� 歆勲彊</string>
+    <string name="pref_header_workout">鞖措彊</string>
+    <string name="pref_title_mb_intents">氙鸽敂鞏� 氩勴娂 鞚疙厫韸� 歆侅爲 氚╈啞</string>
+    <string name="pref_title_nagivation_apps">旮胳晥雮� 鞎�</string>
+    <string name="pref_header_external_integrations">鞕鸽秬 雮挫灔旮半姤</string>
+    <string name="pref_header_automations">鞛愲彊頇�</string>
+    <string name="pref_description_general">鞁滌瀾, 鞏胳柎, 歆�鞐�, 鞙勳箻</string>
+    <string name="pref_description_about_you">靸濍厔, 靹彪硠, 韨�, 氍搓矊, 氇╉憸</string>
+    <string name="pref_description_notifications">鞎� 鞎岆, 韽暔/鞝滌櫢氇╇</string>
+    <string name="pref_description_user_interface">韰岆, 欤柬檾氅�</string>
+    <string name="pref_description_dashboard">韽暔頃� 鞙勳牤, 旮瓣赴</string>
+    <string name="pref_description_automations">鞛愲彊 雮措炒雮搓赴, 鞛愲彊 臧�鞝胳槫旮�</string>
+    <string name="pref_description_developer_options">搿滉犯, 鞚疙厫韸� API</string>
+    <string name="pref_description_deprecated_functionalities">於旐泟 鞝滉卑霅� 靹れ爼</string>
+    <string name="pref_header_main_screen">欤柬檾氅�</string>
+    <string name="pref_theme_black_background">鞏措憪鞖� 韰岆鞐� 瓴�鞝� 靷毄</string>
+    <string name="pref_title_user_interface">靷毄鞛� 鞚疙劙韼橃澊鞀�</string>
+    <string name="pref_title_notifications_ignore_low_priority">雮潃 靾滌渼 鞎岆 氍挫嫓</string>
+    <string name="pref_summary_notifications_ignore_work_profile">須岇偓 頂勲頃勳棎靹� 鞓る姅 鞎岆 旮瓣赴鞐� 氤措偞歆� 鞎婈赴</string>
+    <string name="pref_title_notification_prefer_long_text">旮� 鞎岆 靹犿樃</string>
+    <string name="pref_summary_banglejs_text_bitmap">鞁滉硠鞚� 韽绊姼搿� 響滌嫓霅橃 鞎婋姅 旮�鞛愱皜 鞛堨潉 瓴届毎, 臧�鞝笇毽快鞐愳劀 牍勴姼毵奠溂搿� 氤�頇橅晿鞐� 鞁滉硠鞐愳劀 氤挫澊旮�</string>
+</resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-lt/strings.xml b/app/src/main/res/values-lt/strings.xml
new file mode 100644
index 0000000..7aad9c8
--- /dev/null
+++ b/app/src/main/res/values-lt/strings.xml
@@ -0,0 +1,562 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <string name="application_name_generic">Gadgetbridge</string>
+    <string name="title_activity_controlcenter_generic">Gadgetbridge</string>
+    <string name="action_settings">Nustatymai</string>
+    <string name="action_debug">艩alinti riktus</string>
+    <string name="action_quit">I拧eiti</string>
+    <string name="action_donate">Aukoti</string>
+    <string name="controlcenter_fetch_activity_data">Sinchronizuoti</string>
+    <string name="controlcenter_find_device">Surasti pamest膮 寞rengin寞</string>
+    <string name="controlcenter_take_screenshot">Padaryti ekrano nuotrauk膮</string>
+    <string name="controlcenter_connect">Prijungti鈥�</string>
+    <string name="controlcenter_disconnect">Atjungti</string>
+    <string name="controlcenter_delete_device">I拧trinti 寞rengin寞</string>
+    <string name="controlcenter_delete_device_name">I拧trinti %1$s</string>
+    <string name="controlcenter_delete_device_dialogmessage">艩iuo veiksmu i拧trinsite 寞rengin寞 ir visus jo sukauptus duomenis!</string>
+    <string name="controlcenter_snackbar_requested_screenshot">Daroma 寞renginio ekrano nuotrauka</string>
+    <string name="appmanager_installed_watchfaces">漠diegti laikrod啪io stiliai</string>
+    <string name="menuitem_weather">Oras</string>
+    <string name="menuitem_alarm">沤adintuvas</string>
+    <string name="menuitem_timer">Laikmatis</string>
+    <string name="menuitem_compass">Kompasas</string>
+    <string name="menuitem_settings">Nustatymai</string>
+    <string name="menuitem_alipay">Alipay</string>
+    <string name="controlcenter_navigation_drawer_open">Atverti programos meniu</string>
+    <string name="controlcenter_navigation_drawer_close">U啪verti programos meniu</string>
+    <string name="controlcenter_snackbar_need_longpress">Nor臈dami atjungti ilgai spauskite kortel臋</string>
+    <string name="controlcenter_snackbar_disconnecting">Atjungiama</string>
+    <string name="controlcenter_snackbar_connecting">Jungiama鈥�</string>
+    <string name="title_activity_debug">Derinti</string>
+    <string name="title_activity_appmanager">Program懦 tvarkykl臈</string>
+    <string name="appmanager_cached_watchapps_watchfaces">Programos talpykloje</string>
+    <string name="appmanager_installed_watchapps">漠diegtos programos</string>
+    <string name="appmananger_app_delete">I拧trinti</string>
+    <string name="appmananger_app_delete_cache">I拧trinti ir pa拧alinti i拧 talpyklos</string>
+    <string name="appmananger_app_reinstall">漠diegti i拧 naujo</string>
+    <string name="appmanager_app_openinstore">Ie拧koti \"Pebble\" program懦 parduotuv臈je</string>
+    <string name="appmanager_health_activate">漠jungti</string>
+    <string name="appmanager_health_deactivate">I拧jungti</string>
+    <string name="appmanager_hrm_activate">漠jungti 拧irdies ritmo steb臈jim膮</string>
+    <string name="appmanager_hrm_deactivate">I拧jungti 拧irdies ritmo steb臈jim膮</string>
+    <string name="appmanager_weather_activate">漠jungti sistemos or懦 prognoz臈s program膮</string>
+    <string name="appmanager_weather_deactivate">I拧jungti sistemos or懦 prognoz臈s program膮</string>
+    <string name="appmanager_weather_install_provider">漠diegti or懦 prognoz臈s prane拧im懦 program膮</string>
+    <string name="app_configure">Konfig奴ruoti</string>
+    <string name="app_move_to_top">Perkelti 寞 vir拧懦</string>
+    <string name="title_activity_calblacklist">Kalendori懦 juodasis s膮ra拧as</string>
+    <string name="title_activity_fw_app_insaller">FW/Program懦 diegykl臈</string>
+    <string name="devicetype_test">I拧bandyti 寞rengin寞</string>
+    <string name="devicetype_pebble">Pebble</string>
+    <string name="devicetype_miband">Mi Band</string>
+    <string name="devicetype_miband2">Mi Band 2</string>
+    <string name="devicetype_amazfit_bip">Amazfit Bip</string>
+    <string name="devicetype_amazfit_cor">Amazfit Cor</string>
+    <string name="devicetype_vibratissimo">Vibratissimo</string>
+    <string name="devicetype_liveview">LiveView</string>
+    <string name="devicetype_hplus">HPlus</string>
+    <string name="devicetype_makibes_f68">Makibes F68</string>
+    <string name="devicetype_exrizu_k8">Exrizu K8</string>
+    <string name="menuitem_activity">Veikla</string>
+    <string name="controlcenter_calibrate_device">Kalibruoti 寞rengin寞</string>
+    <string name="mi2_prefs_button_press_broadcast_default_value" translatable="false">nodomain.freeyourgadget.gadgetbridge.ButtonPressed</string>
+    <string name="fw_upgrade_notice_amazfitbip">Jau instaliav臋 %s programin臈s 寞rangos 寞 Amazfit Bip.
+\n
+\n漠sitikinkite, kad instaliavote .fw fail膮 ir po to .res fail膮 ir pabaigoje .gps fail膮.
+\n
+\nPastaba. Neprivalote instaliuoti .res ir .gps fail懦, jei tie failai yra identi拧ki prie拧 tai instaliuotiem.
+\n
+\nVYKDYKITE TAI SAVO ATSAKOMYBE!</string>
+    <string name="fw_upgrade_notice_amazfitcor">Jau instaliav臋 %s programin臈s 寞rangos 寞 Amazfit Cor.
+\n
+\n漠sitikinkite, kad instaliavote .fw fail膮 ir po to .res fail膮. J奴s懦 apyrank臈 persikraus instaliavus .fw fail膮.
+\n
+\nPastaba. Neprivalote instaliuoti .res failo jei failas yra identi拧kas prie拧 tai instaliuotam.
+\n
+\nVYKDYKITE TAI SAVO ATSAKOMYBE!</string>
+    <string name="miband_firmware_known">艩i programin臈 寞ranga i拧testuota ir 啪inoma, kad suderinama su Gadgetbridge.</string>
+    <string name="miband_firmware_unknown_warning">艩i programin臈 寞ranga netestuota ir ne啪inoma ar ji suderinama su Gadgetbridge.
+\n
+\nJ奴s 漠SP臇TAS prie拧 寞ra拧ant!</string>
+    <string name="title_activity_settings">Nustatymai</string>
+    <string name="pref_header_general">Pagrindiniai nustatymai</string>
+    <string name="pref_title_general_autoconnectonbluetooth">Prisijungti prie Gadgetbridge 寞renginio kai Bluetooth bus 寞jungtas</string>
+    <string name="pref_title_general_autostartonboot">Paleisti automati拧kai</string>
+    <string name="pref_title_general_autoreconnect">Pakartotinai prisijungti automati拧kai</string>
+    <string name="pref_title_audio_player">Pageidaujamas audio grotuvas</string>
+    <string name="pref_default">Numatytieji</string>
+    <string name="pref_header_datetime">Data ir laikas</string>
+    <string name="pref_title_datetime_syctimeonconnect">Sinchronizacijos laikas</string>
+    <string name="pref_summary_datetime_syctimeonconnect">Atnaujinti laik膮 Gadgetbridge 寞renginy jungiantis ir kai laiko ar laiko zonos kei膷iasi android 寞renginy</string>
+    <string name="pref_title_theme">Tema</string>
+    <string name="pref_theme_light">艩viesi</string>
+    <string name="pref_theme_dark">Tamsi</string>
+    <string name="pref_title_language">Kalba</string>
+    <string name="pref_title_minimize_priority">Sl臈pti Gadgetbridge prane拧imus</string>
+    <string name="pref_summary_minimize_priority_off">Ikon臈l臈 para拧t臈s juostoje ir prane拧imas u啪krato ekrane rodomi</string>
+    <string name="pref_summary_minimize_priority_on">Ikon臈l臈 para拧t臈s juostoje ir prane拧imas u啪krato ekrane slepiami</string>
+    <string name="pref_header_notifications">Prane拧imai</string>
+    <string name="pref_title_notifications_repetitions">Pasikartojimai</string>
+    <string name="pref_title_notifications_call">Skambu膷iai</string>
+    <string name="pref_title_notifications_sms">SMS</string>
+    <string name="pref_title_notifications_pebblemsg">I拧拧okantys prane拧imai</string>
+    <string name="pref_summary_notifications_pebblemsg">Palaikyti program臈les kurios siun膷ia prane拧imus 寞 Pebble per PebbleKit.</string>
+    <string name="pref_title_whenscreenon">鈥aip pat kai ekranas 寞jungtas</string>
+    <string name="pref_title_notification_filter">Netrukdyti</string>
+    <string name="pref_summary_notification_filter">Blokuoti visus prane拧imus kai Netrukdymo re啪imas 寞jungtas telefone</string>
+    <string name="pref_title_transliteration">Transliteracija</string>
+    <string name="pref_summary_transliteration">Leisti tai jei 寞renginys nepalaiko fonto j奴s懦 kalboje</string>
+    <string name="always">Visada</string>
+    <string name="when_screen_off">Kai ekranas i拧jungtas</string>
+    <string name="never">Niekada</string>
+    <string name="pref_header_privacy">Privatumas</string>
+    <string name="pref_call_privacy_mode_off">Rodyti vard膮 ir numer寞</string>
+    <string name="pref_call_privacy_mode_name">Sl臈pti vard膮, bet rodyti numer寞</string>
+    <string name="pref_call_privacy_mode_number">Sl臈pti numer寞, bet rodyti vard膮</string>
+    <string name="pref_call_privacy_mode_complete">Sl臈pti vard膮 ir numer寞</string>
+    <string name="pref_title_weather">Orai</string>
+    <string name="pref_title_weather_location">Or懦 vietov臈 (LineageOS or懦 拧altiniui)</string>
+    <string name="pref_blacklist_calendars">Blokuoti kalendoriai</string>
+    <string name="pref_header_cannned_messages">Paruo拧ti prane拧imai</string>
+    <string name="pref_title_canned_replies">Atsakymai</string>
+    <string name="pref_title_canned_reply_suffix">漠prasta priesaga</string>
+    <string name="pref_title_canned_messages_dismisscall">Atmesti skambu膷iai</string>
+    <string name="pref_header_development">K奴r臈jo nustatymai</string>
+    <string name="pref_title_development_miaddr">Mi Band adresas</string>
+    <string name="pref_header_activitytrackers">Aktyvumo sekiklis</string>
+    <string name="pref_title_pebble_activitytracker">Pageidautinas veikimo sekiklis</string>
+    <string name="pref_title_enable_pebblekit">Leisti tre膷i懦j懦 拧ali懦 android program臈lei</string>
+    <string name="pref_title_sunrise_sunset">Saul臈tekis ir saul臈lydis</string>
+    <string name="pref_title_enable_calendar_sync">Sinchronizuoti kalendori懦</string>
+    <string name="pref_summary_enable_calendar_sync">Si懦sti kalendoriaus prane拧imus 寞 laiko juost膮</string>
+    <string name="pref_title_autoremove_notifications">Automati拧kai pa拧alinti atmestus prane拧imus</string>
+    <string name="pref_title_pebble_privacy_mode">Privatumo r臈啪imas</string>
+    <string name="pref_pebble_privacy_mode_off">Paprastas prane拧imas</string>
+    <string name="pref_pebble_privacy_mode_complete">Rodyti tik prane拧imo ikon膮</string>
+    <string name="pref_header_location">Vieta</string>
+    <string name="pref_title_location_latitude">Platuma</string>
+    <string name="pref_title_location_longitude">Ilguma</string>
+    <string name="pref_title_location_keep_uptodate">I拧laikyti viet膮 atnaujint膮</string>
+    <string name="pref_summary_location_keep_uptodate">Stengtis gauti esam膮 viet膮 b臈giojant, naudoti i拧saugot膮 viet膮 atsargai</string>
+    <string name="toast_enable_networklocationprovider">Pra拧ome paleisti tinklo viet膮</string>
+    <string name="pref_title_pebble_forceuntested">Leisti nepatikrintas funkcijas</string>
+    <string name="pref_summary_pebble_forceuntested">Leisit netestuotas funkcijas. 沤INOKITE K膭 DAROTE!</string>
+    <string name="pref_title_pebble_forcele">Visada teikti pirmenyb臋 BLE</string>
+    <string name="pref_title_pebble_mtu_limit">Pebble 2/LE GATT MTU limitas</string>
+    <string name="pref_summary_pebble_mtu_limit">Jei Pebble 2/Pebble LE neveikia kaip tik臈tasi, pabandykite 拧iuos apribojimus MTU (galimas ruo啪as 20鈥�512)</string>
+    <string name="pref_title_pebble_gatt_clientonly">Tik GATT klientus</string>
+    <string name="pref_title_pebble_enable_applogs">Leisti laikrod啪io program臈l臈s veiklos 啪urnalo vedim膮</string>
+    <string name="pref_title_unit_system">Vienetai</string>
+    <string name="pref_title_timeformat">Laiko formatas</string>
+    <string name="prefs_title_all_day_heart_rate">Visos dienos 拧irdies ritmo matavimai</string>
+    <string name="preferences_hplus_settings">HPlus/Makibes nustatymai</string>
+    <string name="pref_header_auto_export">Automatinis eksportavimas</string>
+    <string name="pref_title_auto_export_enabled">Automatinis i拧eksportavimas leid啪iamas</string>
+    <string name="pref_title_auto_export_location">Eksportavimo vieta</string>
+    <string name="pref_summary_auto_export_interval">Eksportuoti kiekvien膮 %d valand膮</string>
+    <string name="not_connected">Neprisijungta</string>
+    <string name="connecting">Prisijungiama</string>
+    <string name="connected">Prisijungta</string>
+    <string name="unknown_state">Ne啪inoma b奴sena</string>
+    <string name="_unknown_">(ne啪inoma)</string>
+    <string name="test">Testas</string>
+    <string name="test_notification">Testinis prane拧imas</string>
+    <string name="this_is_a_test_notification_from_gadgetbridge">艩is testinis prane拧imas i拧 Gadgetbridge</string>
+    <string name="bluetooth_is_not_supported_">Bluetooth nepalaikomas.</string>
+    <string name="bluetooth_is_disabled_">Bluetooth i拧jungtas.</string>
+    <string name="tap_a_device_to_connect">Bagstel臈kite 寞rengin寞, kad prisijungtum臈te</string>
+    <string name="gadgetbridge_running_generic">Gadgetbridge paleistas</string>
+    <string name="installation_successful">Instaluota</string>
+    <string name="title_activity_discovery">漠renginio paie拧ka</string>
+    <string name="discovery_stop_scanning">Stabdyti skenavim膮</string>
+    <string name="discovery_start_scanning">Paleisti paie拧k膮</string>
+    <string name="action_discover">Prijungti nauj膮 寞rengin寞</string>
+    <string name="title_activity_android_pairing">Suporuoti 寞rengin寞</string>
+    <string name="title_activity_mi_band_pairing">Prijungti j奴s懦 Mi Band</string>
+    <string name="pairing">Poruojamas su %s鈥�</string>
+    <string name="pairing_creating_bond_with">Kuriamas ry拧ys su %1$s (%2$s)</string>
+    <string name="pairing_unable_to_pair_with">Negalima suporuoti su %1$s (%2$s)</string>
+    <string name="male">Vyras</string>
+    <string name="female">Moteris</string>
+    <string name="other">Kita</string>
+    <string name="left">Kair臈</string>
+    <string name="right">De拧in臈</string>
+    <string name="appinstaller_install">Instaliuoti</string>
+    <string name="discovery_note">Pastaba:</string>
+    <string name="candidate_item_device_image">漠renginio paveikliukas</string>
+    <string name="miband_prefs_alias">Vardas/Slapyvardis</string>
+    <string name="title_activity_sleepmonitor">Miego steb臈tojas</string>
+    <string name="pref_write_logfiles">Vesti 啪urnal膮</string>
+    <string name="sleep_activity_date_range">Nuo %1$s iki %2$s</string>
+    <string name="pref_screen_vibration_profile">Vibravimo profilis</string>
+    <string name="vibration_profile_short">Trumpas</string>
+    <string name="vibration_profile_medium">Vidutinis</string>
+    <string name="vibration_profile_long">Ilgas</string>
+    <string name="vibration_profile_ring">Skamb臈ti</string>
+    <string name="vibration_profile_alarm_clock">沤adintuvas</string>
+    <string name="miband_prefs_vibration">Vibracija</string>
+    <string name="vibration_try">Bandyti</string>
+    <string name="pref_screen_notification_profile_sms">SMS prane拧imai</string>
+    <string name="pref_header_vibration_settings">Vibracijos nustatymai</string>
+    <string name="pref_screen_notification_profile_email">El. pa拧o prane拧imai</string>
+    <string name="pref_screen_notification_profile_incoming_call">漠einan膷i懦 skambu膷i懦 prane拧imai</string>
+    <string name="pref_screen_notification_profile_generic_navigation">Navigacija</string>
+    <string name="pref_screen_notification_profile_generic_social">Socialinis tinklas</string>
+    <string name="interval_one_minute">Kart膮 per minut臋</string>
+    <string name="interval_five_minutes">kas 5 minutes</string>
+    <string name="interval_ten_minutes">kas 10 minu膷i懦</string>
+    <string name="interval_thirty_minutes">kas 30 minu膷i懦</string>
+    <string name="interval_one_hour">kas valand膮</string>
+    <string name="stats_title">Grei膷io zonos</string>
+    <string name="stats_y_axis_label">沤ingsniai per minut臋</string>
+    <string name="control_center_find_lost_device">Rasti pamest膮 寞rengin寞</string>
+    <string name="control_center_cancel_to_stop_vibration">At拧aukti vibravimo stabdym膮.</string>
+    <string name="title_activity_charts">Veikla ir miegas</string>
+    <string name="title_activity_set_alarm">Konfig奴ruoti aliarmus</string>
+    <string name="alarm_sun_short">Sk.</string>
+    <string name="alarm_mon_short">Pr.</string>
+    <string name="alarm_tue_short">An.</string>
+    <string name="alarm_wed_short">Tr.</string>
+    <string name="alarm_thu_short">Kt.</string>
+    <string name="alarm_fri_short">Pn.</string>
+    <string name="alarm_sat_short">艩t.</string>
+    <string name="alarm_smart_wakeup">I拧manus pak臈limas</string>
+    <string name="fw_upgrade_notice_miband3">Jau instaliav臋 %s programin臈s 寞rangos 寞 Mi Band 3.
+\n
+\n漠sitikinkite, kad instaliavote .fw fail膮 ir po to .res fail膮. J奴s懦 laikrodis persikraus instaliavus .fw fail膮.
+\n
+\nPastaba. Neprivalote instaliuoti .res failo, jei failas yra identi拧kas prie拧 tai instaliuotam.
+\n
+\nVYKDYKITE TAI SAVO ATSAKOMYBE!</string>
+    <string name="prefs_screen_orientation">Ekrano orantacija</string>
+    <string name="horizontal">Horizontalus</string>
+    <string name="vertical">Vertikalus</string>
+    <string name="pref_title_notifications_timeout">Minimalus laikas tarp prane拧im懦</string>
+    <string name="controlcenter_change_fm_frequency">Pakeisti FM da啪n寞</string>
+    <string name="pref_title_rtl">I拧 de拧in臈s 寞 kair臋</string>
+    <string name="pref_summary_rtl">Leisti jei 寞renginys nerodo kalb懦 i拧 de拧in臈s 寞 kair臋</string>
+    <string name="pref_rtl_max_line_length">Maksimalus i拧 de拧in臈s 寞 kair臋 eilut臈s ilgis</string>
+    <string name="debugactivity_really_factoryreset_title">Ar tikrai atkurti gamyklinius nustatymus\?</string>
+    <string name="debugactivity_really_factoryreset">Atkuriant gamyklinius nustatymus bus pa拧alinti visi duomenys ir prijungto 寞renginio (jei palaikoma). Taip pat Xiaomi/Huami 寞renginiai pasikeis Bluetooth MAC adres膮, tod臈l jie bus matomi kaip nauji 寞renginiai Gadgetbridge.</string>
+    <string name="zetime_title_settings">ZeTime nustatymai</string>
+    <string name="zetime_title_heartrate">艩irdies ritmo nustatymai</string>
+    <string name="zetime_heart_rate_alarm_enable">Leisti 拧irdies ritmo aliarm膮</string>
+    <string name="activity_prefs_alarm_max_heart_rate">Maksimalus 拧irdies ritmas</string>
+    <string name="activity_prefs_alarm_min_heart_rate">Minimalus 拧irdies ritmas</string>
+    <string name="zetime_analog_mode_hands">Tik rankos</string>
+    <string name="zetime_analog_mode_handsandsteps">Rankos ir 啪ingsniai</string>
+    <string name="zetime_activity_tracking">veiklos sekimas</string>
+    <string name="zetime_handmove_display">Rankos judesiai</string>
+    <string name="zetime_date_format">Laiko formatas</string>
+    <string name="zetime_date_format_1">YY/MM/DD</string>
+    <string name="zetime_date_format_2">DD/MM/YY</string>
+    <string name="zetime_date_format_3">MM/DD/YY</string>
+    <string name="zetime_prefs_inactivity_mo">Pirmadienis</string>
+    <string name="zetime_prefs_inactivity_we">Tre膷iadienis</string>
+    <string name="zetime_prefs_inactivity_th">Ketvirtadienis</string>
+    <string name="zetime_prefs_inactivity_fr">Penktadienis</string>
+    <string name="zetime_prefs_inactivity_sa">艩e拧tadienis</string>
+    <string name="zetime_prefs_inactivity_su">Sekmadienis</string>
+    <string name="zetime_title_alarm_signaling">Pasirinkti signalo tip膮 啪adintuvui</string>
+    <string name="zetime_signaling_none">Tylusis</string>
+    <string name="zetime_signaling_vibrate">Nuolatinis vibravimas</string>
+    <string name="zetime_signaling_beep">Nuolatinis pyps臈jimas</string>
+    <string name="zetime_signaling_vibrate_beep">Nuolatinis vibravimas ir pyps臈jimas</string>
+    <string name="zetime_signaling_vibrate_once">Suvibruoti kart膮</string>
+    <string name="zetime_signaling_vibrate_twice">Suvibruoti dukart</string>
+    <string name="zetime_signaling_beep_once">Supyps臈ti kart膮</string>
+    <string name="zetime_signaling_beep_twice">Supyps臈ti dukart</string>
+    <string name="zetime_signaling_vibrate_beep_once">Suvibruoti ir supyps臈ti kart膮</string>
+    <string name="pref_screen_notification_profile_missed_call">Praleist懦 skambu膷i懦 prane拧imai</string>
+    <string name="pref_screen_notification_profile_calendar">Kalendoriaus prane拧imai</string>
+    <string name="pref_screen_notification_profile_low_power">Bei拧sikrovimo prane拧imas</string>
+    <string name="interval_fifteen_minutes">kas 15 minu膷i懦</string>
+    <string name="interval_forty_five_minutes">kas 45 minutes</string>
+    <string name="pref_title_support_voip_calls">Leisi VoIP program臈l臈s skambu膷ius</string>
+    <string name="title_activity_device_specific_settings">漠renginio special奴s nustatymai</string>
+    <string name="pref_title_authkey">Prisijungimo raktas</string>
+    <string name="fw_upgrade_notice_amazfitcor2">Jau instaliav臋 %s programin臈s 寞rangos 寞 Amazfit Cor 2. \n \n漠sitikinkite, kad instaliavote .fw fail膮, tik po to .res fail膮. J奴s懦 apyrank臈 persikraus instaliavus .fw fail膮. \n \nPastaba. Neprivalote instaliuoti .res failo, jei failas yra identi拧kas prie拧 tai instaliuotam. \n \nVISI艩KAI NETESTUOTAS, TIKRIAUSIAI REIKIA 漠RA艩YTI BEATS_W PROGRAMIN臉 漠RANG膭 JEI 漠RENGINIO PAVADINIMAS YRA 鈥濧mazfit Band 2鈥�</string>
+    <string name="fw_upgrade_notice_miband4">Jau instaliav臋 %s programin臈s 寞rangos 寞 Mi Band 4.
+\n
+\n漠sitikinkite, kad instaliavote .fw fail膮 ir po to .res fail膮. J奴s懦 laikrodis persikraus instaliavus .fw fail膮.
+\n
+\nPastaba. Neprivalote instaliuoti .res failo, jei failas yra identi拧kas prie拧 tai instaliuotam.
+\n
+\nVYKDYKITE TAI SAVO ATSAKOMYBE!</string>
+    <string name="prefs_hr_alarm_activity">艩irdies ritmo aliarmas sportuojant</string>
+    <string name="pref_title_use_custom_font">Naudoti tinkint膮 拧rift膮</string>
+    <string name="pref_display_add_device_fab">Prijungti nauj膮 寞renginio mygtuk膮</string>
+    <string name="pref_display_add_device_fab_on">Visada rodomas</string>
+    <string name="pref_display_add_device_fab_off">Rodomas tik jei 寞renginys yra prijungtas</string>
+    <string name="preferences_makibes_hr3_settings">Makibes HR3 nustatymai</string>
+    <string name="prefs_wearside">Ne拧iojama kair臈je ar de拧in臈je\?</string>
+    <string name="fw_upgrade_notice_amazfitbip_lite">Jau instaliav臋 %s programin臈s 寞rangos 寞 Amazfit Bip Lite.
+\n
+\n漠sitikinkite, kad instaliavote .fw fail膮 ir po to .res fail膮. J奴s懦 laikrodis persikraus instaliavus .fw fail膮.
+\n
+\nPastaba. Neprivalote instaliuoti .res failo, jei failas yra identi拧kas prie拧 tai instaliuotam.
+\n
+\nVYKDYKITE TAI SAVO ATSAKOMYBE!</string>
+    <string name="alarm_snooze">Sn奴stelti</string>
+    <string name="pref_title_allow_high_mtu">Leisti didel寞 MTU</string>
+    <string name="pref_summary_allow_high_mtu">Padidins persiuntimo greit寞, bet gali ir neveikti kai kuriuose Android 寞renginiuose..</string>
+    <string name="sonyswr12_settings_title">Sony SWR12 nustatymai</string>
+    <string name="sonyswr12_settings_low_vibration">漠jungta ma啪a vibracija</string>
+    <string name="sonyswr12_settings_stamina">漠jungtas energijos taupymo re啪imas</string>
+    <string name="sonyswr12_settings_alarm_interval">I拧maniojo 啪adintuvo intervalas minut臈mis</string>
+    <string name="sonyswr12_settings_low_vibration_summary">漠jungti ma啪膮 juostos vibracijos intensyvum膮</string>
+    <string name="sonyswr12_settings_stamina_summary">Energijos taupymo re啪imas i拧jungia periodin寞 automatin寞 拧irdies ritmo matavim膮 ir taip pailgina darbo laik膮</string>
+    <string name="sonyswr12_settings_alarm_interval_summary">I拧maniojo aliarmo intervalas yra intervalas prie拧 寞diegt膮 signalizacij膮. 艩iuo intervalu prietaisas bando nustatyti lengviausi膮 miego faz臋, kad pa啪adint懦 vartotoj膮</string>
+    <string name="pref_title_overwrite_settings_on_connection">Perra拧yti nustatymus prisijungus</string>
+    <string name="pref_sensors_bp_calibration">Kraujo spaudimo kalibravimas</string>
+    <string name="appmanager_watchface_activate">漠jungti</string>
+    <string name="appmanager_app_start">Prad臈ti</string>
+    <string name="title_activity_notification_management">Prane拧im懦 nustatymai</string>
+    <string name="pref_header_display">Ekranas</string>
+    <string name="pref_header_health">Sveikata</string>
+    <string name="pref_header_time">Laikas</string>
+    <string name="pref_header_workout">Treniruot臈</string>
+    <string name="pref_theme_black_background">Naudoti juod膮 fon膮 su tamsia tema</string>
+    <string name="pref_title_notification_delay_calls">Prane拧imo apie skambut寞 u啪laikymas</string>
+    <string name="pref_title_notifications_generic_settings">Android prane拧im懦 nustatymai</string>
+    <string name="pref_title_notifications_ignore_low_priority">Ignoruoti 啪emo prioriteto prane拧imus</string>
+    <string name="pref_title_call_privacy_mode">Skambu膷i懦 privatumo re啪imas</string>
+    <string name="pref_header_notifications_and_calls">Prane拧imai ir skambu膷iai</string>
+    <string name="pref_header_notifications_and_calls_callhandling">Skambu膷i懦 valdymas</string>
+    <string name="pref_header_device_spec_settings">漠renginio nustatymai</string>
+    <string name="pref_header_sensors_calibration">Jutikli懦 kalibravimas</string>
+    <string name="pref_title_sensors_altitude">Auk拧膷io kalibravimas</string>
+    <string name="power_mode_normal">Normalus</string>
+    <string name="zetime_analog_mode">Analoginis re啪imas</string>
+    <string name="zetime_prefs_inactivity_tu">Antradienis</string>
+    <string name="prefs_hr_alarm_high">Vir拧utin臈 riba</string>
+    <string name="calendar_three_months">3 m臈nesiai</string>
+    <string name="unknown">Ne啪inoma</string>
+    <string name="pref_auto_fetch_limit_fetches">Ma啪iausias laikas tarp parsiuntim懦</string>
+    <string name="pref_camera_remote_title">Nuotolinis fotoaparato valdymas</string>
+    <string name="discovery_bluetooth_scan">Bluetooth skenavimas:</string>
+    <string name="pref_title_device_spec_settings_force_time">Priverstinai sinchronizuoti laik膮</string>
+    <string name="battery_level">Akumuliatoriaus lygis</string>
+    <string name="battery_detail_activity_title">Akumuliatoriaus informacija</string>
+    <string name="pref_header_other">Kita</string>
+    <string name="all_satellites">Visi palydovai</string>
+    <string name="pref_header_system">Sistema</string>
+    <string name="pref_gps_header">GPS</string>
+    <string name="title_activity_LenovoWatch_calibration">Watch X Plus kalibravimas</string>
+    <string name="pref_summary_device_spec_settings_title_force_time">Priverstinai automati拧kai sinchronizuoti laik膮 prisijungus i拧 naujo. Analogin臈s rodykl臈s gali rodyti neteising膮 laik膮!</string>
+    <string name="pref_title_device_spec_settings_show_raw_graph">Rodyti neapdorotus duomenis aktyvumo diagramoje</string>
+    <string name="initialized">inicializuotas</string>
+    <string name="about_activity_title_generic">Apie Gadgetbridge</string>
+    <string name="about_activity_title_banglejs_main">Apie Bangle.js Gadgetbridge</string>
+    <string name="controlcenter_power_off">I拧jungti</string>
+    <string name="controlcenter_power_off_confirm_title">I拧jungti</string>
+    <string name="controlcenter_power_off_confirm_description">Ar tikrai norite i拧jungti 寞rengin寞\?</string>
+    <string name="calendar_month">M臈nuo</string>
+    <string name="calendar_six_months">6 m臈nesiai</string>
+    <string name="calendar_year">Metai</string>
+    <string name="pref_notifications_and_calls_enable_misscall">Prane拧ti apie praleist膮 skambut寞</string>
+    <string name="pref_title_notifications_and_calls_repeat_on_misscall">Kartoti kas X minu膷i懦</string>
+    <string name="prefs_hr_alarm_low">沤emutin臈 riba</string>
+    <string name="controlcenter_set_alias">Nustatyti slapyvard寞</string>
+    <string name="controlcenter_change_led_color">Pakeisti LED spalv膮</string>
+    <string name="controlcenter_get_heartrate_measurement">Matuoti 拧irdies ritm膮</string>
+    <string name="calendar_day">Diena</string>
+    <string name="calendar_week">Savait臈</string>
+    <string name="calendar_two_weeks">Dvi savait臈s</string>
+    <string name="pairing_in_progress">Ry拧io k奴rimas vyksta: %1$s (%2$s)</string>
+    <string name="pref_theme_system">Sistemos</string>
+    <string name="pref_blacklist_calendars_summary">Blokuoti kalendoriai nebus sinchronizuojami su 寞renginiu</string>
+    <string name="pref_title_expose_hr">Tre膷i懦j懦 拧ali懦 prieiga prie 拧irdies ritmo realiu laiku</string>
+    <string name="pref_title_huami_force_new_protocol">Naujas autentikavimo protokolas</string>
+    <string name="pref_summary_huami_force_new_protocol">漠junkite jei j奴s懦 寞renginys nebeprisijungia po programin臈s 寞rangos atnaujinimo</string>
+    <string name="pref_title_screentime">Ekrano 寞jungimo trukm臈</string>
+    <string name="pref_gps_band">GPS da啪ni懦 juosta</string>
+    <string name="pref_camera_remote_summary">Leid啪ia laikrod啪iui paleisti telefono foto-kamer膮</string>
+    <string name="pref_header_auto_fetch">Automatinis parsiuntimas</string>
+    <string name="find_my_phone_notification">Rasti mano telefon膮</string>
+    <string name="discovery_bluetooth_le_scan">Bluetooth LE skenavimas:</string>
+    <string name="pairing_already_bonded">Ry拧ys sukurtas su %1$s (%2$s), jungiamasi鈥�</string>
+    <string name="pref_header_connection">Prisijungimas</string>
+    <string name="pref_title_notifications_generic">Bendras prane拧im懦 palaikymas</string>
+    <string name="pref_title_message_privacy_mode">沤inu膷i懦 privatumo re啪imas</string>
+    <string name="pref_message_privacy_mode_off">Rodyti turin寞</string>
+    <string name="pref_message_privacy_mode_complete">Sl臈pti turin寞</string>
+    <string name="pref_summary_expose_hr">Leisti kitoms program臈l臈ms pasiekti 拧irdies ritmo duomenis realiu laiku, kai Gadgetbridge yra prisijung臋s</string>
+    <string name="pref_summary_sync_calendar">漠jungia kalendoriaus 寞sp臈jimus, net atsijungus</string>
+    <string name="pref_sensors_bp_calibration_low">Kraujo spaudimas DIASTOLINIS (apatinis)</string>
+    <string name="pref_sensors_bp_calibration_high">Kraujo spaudimas SISTOLINIS (vir拧utinis)</string>
+    <string name="prefs_sensors_button_bp_calibration">Kalibravimas</string>
+    <string name="prefs_sensors_button_bp_calibration_sum">Spauskite 膷ia, kad prad臈tum臈te kalibravim膮</string>
+    <string name="power_mode_title">Laikrod啪io energijos re啪imas</string>
+    <string name="power_mode_watch">Tik laikrodis</string>
+    <string name="pref_title_notifications_and_calls_repeat_on_call">Kartoti skambu膷io prane拧im膮</string>
+    <string name="pref_sleep_mode_title">Miego re啪imas</string>
+    <string name="appmanager_app_share">Dalintis</string>
+    <string name="debugactivity_confirm_remove_device_preferences_title">Pa拧alinti 寞renginio nustatymus\?</string>
+    <string name="pref_gps_mode_preset">GPS re啪imas</string>
+    <string name="pref_header_calendar">Kalendorius</string>
+    <string name="pref_title_pebble_settings">Pebble nustatymai</string>
+    <string name="pref_title_vibration_strength">Vibravimo stiprumas</string>
+    <string name="pref_header_sound_vibration">Garsas ir vibracija</string>
+    <string name="pref_applications_settings">Program臈li懦 s膮ra拧as</string>
+    <string name="pref_gps_satellite_search">Palydov懦 paie拧ka</string>
+    <string name="pref_gps_combination">GPS kombinacija</string>
+    <string name="find_lost_device_message">Ie拧koti %1$s\?</string>
+    <string name="appmanager_item_outdated">(pasen臋s)</string>
+    <string name="pref_summary_notification_delay_calls">Palaukti prie拧 siun膷iant prane拧im膮 apie 寞einant寞 skambut寞, sekund臈mis.</string>
+    <string name="pref_title_weather_summary">Nadojama LineageOS or懦 拧altiniui, kitos Android versijos turi naudoti program臈l臋 pvz. \"Weather notification\". Daugiau informacijos rasite Gadgetbridge wiki.</string>
+    <string name="pref_summary_canned_replies">Atsakyti i拧 laikrod啪io naudojant paruo拧tus prane拧imus</string>
+    <string name="pref_summary_connected_advertisement">Padarys 寞rengin寞 aptinkam膮 per Bluetooth, net kai jis prisijung臋s</string>
+    <string name="pref_title_connected_advertisement">Matomas kai prisijung臋s</string>
+    <string name="pref_summary_overwrite_settings_on_connection">Prisijungus prie apyrank臈s, perra拧yti visus jos nustatymus.</string>
+    <string name="pref_title_device_internet_access">Leisti interneto prieig膮</string>
+    <string name="prefs_notifications_and_calls_continious_ring">Prane拧imas skambant telefonui</string>
+    <string name="pref_summary_device_internet_access">Leisti 拧io 寞renginio program臈l臈ms pasiekti internet膮</string>
+    <string name="pref_title_sync_caldendar">Sinchronizuoti kalendoriaus 寞vykius</string>
+    <string name="pref_summary_notifications_and_calls_enable_misscall">Kartoti kas minut臋</string>
+    <string name="power_mode_saving">Taupantis energij膮</string>
+    <string name="pref_auto_fetch_summary">Parsiuntimas 寞vyksta atrakinus ekran膮. Veikia, tik jei nustatytas u啪rakinimo mechanizmas!</string>
+    <string name="pref_auto_fetch_limit_fetches_summary">Parsiun膷ia kas %d minutes</string>
+    <string name="pref_sleep_mode_sleep_screen_title">Miego ekranas</string>
+    <string name="pref_sleep_mode_sleep_screen_summary"/>
+    <string name="pref_auto_fetch">Automati拧kai parsi懦sti aktyvumo duomenis</string>
+    <string name="pref_summary_receive_calls_watch">Skambinti ir atsiliepti tiesiai i拧 laikrod啪io</string>
+    <string name="bluetooth_calls">Bluetooth skambu膷iai</string>
+    <string name="bluetooth_calls_pairing">Bluetooth skambu膷i懦 poravimas</string>
+    <string name="bluetooth_calls_settings">Bluetooth skambu膷i懦 nustatymai</string>
+    <string name="pref_display_caller_title">Rodyti adresato informacij膮</string>
+    <string name="pref_display_caller_summary">Rodyti 寞einan膷io skambu膷io telefono numer寞 ar vard膮</string>
+    <string name="buttons_on_right">Mygtukai de拧in臈je</string>
+    <string name="title_activity_set_reminders">Konfig奴ruoti priminimus</string>
+    <string name="pref_world_clocks_title">Pasaulio laikrod啪iai</string>
+    <string name="reminder_time_every_day">%1$s, kasdien</string>
+    <string name="reminder_time_once">%1$s, vien膮 kart膮</string>
+    <string name="controlcenter_start_configure_alarms">Konfig奴ruoti 啪adintuvus</string>
+    <string name="reminder_message">沤inut臈</string>
+    <string name="function_enabled">漠jungta</string>
+    <string name="world_clock_code">Kodas</string>
+    <string name="prefs_relaxation_reminder_title">Atsipalaidavimo priminimas</string>
+    <string name="reminder_date">Data</string>
+    <string name="world_clock_no_free_slots_title">N臈ra laisv懦 viet懦</string>
+    <string name="chart_get_active_and_synchronize">Atlikite koki膮 nors veikl膮 ir sinchronizuokite 寞rengin寞.</string>
+    <string name="pref_screen_notification_profile_generic">Bendras prane拧imas</string>
+    <string name="controlcenter_start_configure_reminders">Konfig奴ruoti priminimus</string>
+    <string name="dbaccess_error_executing">Klaida vykdant \'%1$s\'</string>
+    <string name="title_activity_set_contacts">Konfig奴ruoti adresatus</string>
+    <string name="pref_contacts_title">Adresatai</string>
+    <string name="reminder_repeat">Kartoti</string>
+    <string name="contact_delete_confirm_title">I拧trinti adresat膮</string>
+    <string name="world_clock_delete_confirm_title">I拧trinti \'%1$s\'</string>
+    <string name="world_clock_label">Pavadinimas</string>
+    <string name="title_activity_alarm_details">沤adintuvo informacija</string>
+    <string name="busy_task_fetch_activity_data">Parsiun膷iami aktyvumo duomenys</string>
+    <string name="prefs_title_heartrate_measurement_interval">Visos dienos 拧irdies ritmo matavimas</string>
+    <string name="buttons_on_left">Mygtukai kair臈je</string>
+    <string name="miband_pairing_using_dummy_userdata">N臈ra teising懦 vartotojo duomen懦, kol kas naudojami fiktyv奴s vartotojo duomenys.</string>
+    <string name="pref_screen_notification_profile_event_reminder">漠vykio priminimas</string>
+    <string name="reminder_once">Vien膮 kart膮</string>
+    <string name="world_clock_timezone">Laiko zona</string>
+    <string name="user_feedback_miband_set_alarms_ok">沤adintuvai nusi懦sti 寞 寞rengin寞.</string>
+    <string name="vibration_profile_waterdrop">Vandens la拧as</string>
+    <string name="reminder_time_every_month">%1$s, kas m臈nes寞</string>
+    <string name="stats_x_axis_label">I拧 viso minu膷i懦</string>
+    <string name="prefs_heartrate_alert_experimental_title">艩irdies ritmo 寞sp臈jimas (eksperimentinis)</string>
+    <string name="reminder_time_every_year">%1$s, kasmet</string>
+    <string name="notif_battery_low_title">漠renginio baterija i拧sikrovusi!</string>
+    <string name="pref_screen_notification_profile_todo_list">Darb懦 s膮ra拧as</string>
+    <string name="pref_screen_notification_profile_generic_chat">Pokalbis</string>
+    <string name="pref_screen_notification_profile_inactivity">Neaktyvumo prane拧imas</string>
+    <string name="pref_screen_notification_profile_find_device">Rasti 寞rengin寞</string>
+    <string name="reminder_time_every_week">%1$s, kas savait臋</string>
+    <string name="reminder_every_month">Kiekvien膮 m臈nes寞</string>
+    <string name="reminder_time">Laikas</string>
+    <string name="reminder_every_day">Kasdien</string>
+    <string name="world_clock_delete_confirm_description">Ar tikrai norite i拧trinti pasaulio laikrod寞\?</string>
+    <string name="reminder_delete_confirm_title">I拧trinti priminim膮</string>
+    <string name="reminder_delete_confirm_description">Ar tikrai norite i拧trinti priminim膮\?</string>
+    <string name="chart_no_data_synchronize">N臈ra duomen懦. Sinchronizuoti 寞rengin寞\?</string>
+    <string name="user_feedback_miband_activity_data_transfer">Tuojau bus persi懦sta %1$s duomen懦, pradedant nuo %2$s</string>
+    <string name="miband_prefs_fitness_goal">Kasdienis 啪ingsni懦 tikslas</string>
+    <string name="chart_no_active_data">Neaptikta jokios veiklos.</string>
+    <string name="prefs_stress_monitoring_title">Streso steb臈jimas</string>
+    <string name="prefs_stress_monitoring_description">Steb臈ti streso lyg寞 ilsintis</string>
+    <string name="title_activity_reminder_details">Priminimo informacija</string>
+    <string name="pref_screen_notification_profile_schedule">Tvarkara拧tis</string>
+    <string name="pref_world_clocks_summary">Konfig奴ruoti laikrod啪ius kitoms laiko zonoms</string>
+    <string name="pref_contacts_summary">Konfig奴ruoti adresatus laikrodyje</string>
+    <string name="contact_no_free_slots_description">漠renginys neturi laisv懦 viet懦 adresatams (i拧 viso viet懦: %1$s)</string>
+    <string name="contact_delete_confirm_description">Ar tikrai norite i拧trinti \'%1$s\'\?</string>
+    <string name="user_feedback_miband_set_alarms_failed">漠vyko klaida nustatant 啪adintuvus, bandykite dar kart膮.</string>
+    <string name="title_activity_world_clock_details">Pasaulio laikrod啪io informacija</string>
+    <string name="title_activity_contact_details">Adresato informacija</string>
+    <string name="cannot_connect">Nepavyko prisijungti: %1$s</string>
+    <string name="prefs_weardirection">Ne拧iojimo kryptis</string>
+    <string name="vibration_profile_default">Numatytasis</string>
+    <string name="reminder_every_week">Kiekvien膮 savait臋</string>
+    <string name="reminder_every_year">Kiekvienais metais</string>
+    <string name="reminder_no_free_slots_title">N臈ra laisv懦 viet懦</string>
+    <string name="reminder_no_free_slots_description">漠renginys neturi laisv懦 viet懦 priminimams (i拧 viso viet懦: %1$s)</string>
+    <string name="world_clock_no_free_slots_description">漠renginys neturi laisv懦 viet懦 pasaulio laikrod啪iams (i拧 viso viet懦: %1$s)</string>
+    <string name="mi2_prefs_activate_display_on_lift">Aktyvuoti ekran膮, pak臈lus</string>
+    <string name="dateformat_time">Laikas</string>
+    <string name="mi2_prefs_display_items_summary">Pasirinkite elementus kurie bus rodomi apyrank臈s ekrane</string>
+    <string name="weeksteps_today_steps_description">艩iandienos 啪ingsniai, tikslas: %1$s</string>
+    <string name="controlcenter_start_activitymonitor">J奴s懦 aktyvumas</string>
+    <string name="pref_title_dont_ack_transfer">Nepatvirtinti aktyvumo duomen懦 perdavimo</string>
+    <string name="miband_prefs_hr_sleep_detection">Naudoti 拧irdies ritmo jutikl寞 miego aptikimo pagerinimui</string>
+    <string name="abstract_chart_fragment_kind_not_worn">Nene拧iota</string>
+    <string name="abstract_chart_fragment_kind_activity">Aktyvumas</string>
+    <string name="mi2_prefs_inactivity_warnings">Neaktyvumo 寞sp臈jimai</string>
+    <string name="miband_pairing_tap_hint">Kai j奴s懦 Mi Band vibruos ir mirks臈s, bakstel臈kite j寞 kelet膮 kart懦 i拧 eil臈s.</string>
+    <string name="chart_steps">沤ingsniai</string>
+    <string name="pref_title_keep_data_on_device">Laikyti aktyvumo duomenis 寞renginyje</string>
+    <string name="pref_title_auto_export_interval">Eksportavimo intervalas</string>
+    <string name="calories">Kalorijos</string>
+    <string name="live_activity_steps_history">沤ingsni懦 istorija</string>
+    <string name="miband_prefs_device_time_offset_hours">漠renginio laiko poslinkis valandomis (skirta aptikti pamainini懦 darbuotoj懦 mieg膮)</string>
+    <string name="notif_battery_low_percent">%1$s akumuliatoriuje liko: %2$s%%</string>
+    <string name="abstract_chart_fragment_kind_light_sleep">Lengvas miegas</string>
+    <string name="abstract_chart_fragment_kind_deep_sleep">Gilus miegas</string>
+    <string name="mi2_prefs_rotate_wrist_to_switch_info">Pasukti rie拧膮, kad pakeistum臈te informacij膮</string>
+    <string name="pref_title_canned_messages_set">Atnaujinti 寞renginyje</string>
+    <string name="fw_upgrade_notice">J奴s instaliuosite %s.</string>
+    <string name="preferences_miband_settings">Mi Band / Amazfit nustatymai</string>
+    <string name="preferences_category_device_specific_settings">Nuo 寞renginio priklausantys nustatymai</string>
+    <string name="distance">Atstumas</string>
+    <string name="pref_summary_dont_ack_transfers">Nepatvirtinus, aktyvumo duomenys apyrank臈je nei拧valomi. Naudinga jei GB naudojama su kitomis program臈l臈mis.</string>
+    <string name="mi2_prefs_button_actions_summary">Nurodykite mygtuko paspaudimo veiksmus</string>
+    <string name="mi2_prefs_button_press_count">Mygtuko paspaudim懦 skai膷ius</string>
+    <string name="miband2_prefs_dateformat">Datos formatas</string>
+    <string name="dateformat_date_time">Laikas ir data</string>
+    <string name="pref_title_location_aquire">Rasti vietov臋</string>
+    <string name="cannot_connect_bt_address_invalid_">Nepavyko prisijungti. Neteisingas Bluetooth adresas\?</string>
+    <string name="mi2_prefs_button_actions">Mygtuko veiksmai</string>
+    <string name="mi2_prefs_display_items">Rodomi elementai</string>
+    <string name="mi2_prefs_do_not_disturb">Netrukdyti</string>
+    <string name="battery">Akumuliatorius</string>
+    <string name="toast_aqurired_networklocation">vietov臈 rasta</string>
+    <string name="heart_rate">艩irdies ritmas</string>
+    <string name="liveactivity_live_activity">Aktyvumas realiu laiku</string>
+    <string name="clock">Laikrodis</string>
+    <string name="pref_summary_keep_data_on_device">Laikys aktyvumo duomenis 寞renginyje, net po sinchronizacijos. Naudinga jei GB naudojama su kitomis program臈l臈mis.</string>
+    <string name="live_activity_steps_per_minute_history">沤ingsni懦 per minut臋 istorija</string>
+    <string name="live_activity_start_your_activity">Prad臈kite veikl膮</string>
+    <string name="live_activity_total_steps">I拧 viso 啪ingsni懦</string>
+    <string name="mi2_prefs_goal_notification">Tikslo prane拧imas</string>
+    <string name="mi2_prefs_goal_notification_summary">Apyrank臈 vibruos pasiekus kasdien寞 啪ingsni懦 tiksl膮</string>
+    <string name="mi2_prefs_inactivity_warnings_summary">Apyrank臈 vibruos jei neb奴site aktyvus kur寞 laik膮</string>
+    <string name="device_not_connected">Neprisijung臋s.</string>
+    <string name="weeksleepchart_sleep_a_week">Miegas per savait臋</string>
+    <string name="notif_battery_low_bigtext_number_of_charges">漠krovim懦 skai膷ius: %s</string>
+    <string name="sleepchart_your_sleep">Miegas</string>
+    <string name="weeksleepchart_today_sleep_description">艩iandienos miegas, tikslas %1$s</string>
+    <string name="weekstepschart_steps_a_week">沤ingsniai per savait臋</string>
+    <string name="activity_sleepchart_activity_and_sleep">Aktyvumas</string>
+    <string name="notif_export_failed_title">Duomen懦 baz臈s eksportavimas nepavyko! Pra拧ome pasitikrinti nustatymus.</string>
+    <string name="notif_battery_low_bigtext_last_charge_time">Paskutinis 寞krovimas: %s
+\n</string>
+    <string name="live_activity_current_steps_per_minute">Dabartinis greitis 啪ingsniai/min</string>
+    <string name="user_feedback_all_alarms_disabled">Visi 啪adintuvai i拧jungti</string>
+    <string name="miband_prefs_reserve_alarm_calendar">沤adintuvai rezervuoti ateities 寞vykiams</string>
+    <string name="mi2_prefs_do_not_disturb_summary">Apyrank臈 negaus prane拧im懦 kol 寞jungta</string>
+</resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-lv/strings.xml b/app/src/main/res/values-lv/strings.xml
new file mode 100644
index 0000000..a0124b4
--- /dev/null
+++ b/app/src/main/res/values-lv/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <string name="controlcenter_take_screenshot">Veikt ekr膩nuz艈膿mumu</string>
+    <string name="controlcenter_find_device">Atrast zaud膿to ier墨ci</string>
+    <string name="controlcenter_fetch_activity_data">Sinhroniz膿t</string>
+    <string name="action_donate">Ziedot</string>
+    <string name="action_quit">Iziet</string>
+    <string name="action_debug">Atk募奴do拧ana</string>
+    <string name="action_settings">Iestat墨jumi</string>
+    <string name="live_activity_steps_per_minute_history">V膿sture par so募iem min奴t膿</string>
+    <string name="live_activity_total_steps">Kop膿jais so募u skaits</string>
+    <string name="live_activity_current_steps_per_minute">Pa拧reiz膿jie so募i / min</string>
+    <string name="live_activity_steps_history">So募u v膿sture</string>
+    <string name="prefs_find_phone">Atrast t膩lruni</string>
+    <string name="sonyswr12_settings_low_vibration_summary">Iesp膿jot zemu vibr膩cijas intensit膩ti josl膩</string>
+    <string name="sonyswr12_settings_stamina_summary">Ener模ijas taup墨拧anas re啪墨ms izsl膿dz periodisku autom膩tisku sirdsdarb墨bas m膿r墨拧anu, t膩d膿j膩di palielinot darba laiku</string>
+    <string name="sonyswr12_settings_alarm_interval_summary">Vied膩 trauksmes interv膩ls ir interv膩ls pirms instal膿t膩s trauksmes. 艩aj膩 interv膩l膩 ier墨ce m膿模ina noteikt viegl膩ko miega f膩zi, lai pamodin膩tu lietot膩ju</string>
+    <string name="sonyswr12_settings_title">Sony SWR12 iestat墨jumi</string>
+    <string name="sonyswr12_settings_low_vibration">Iesp膿jota zema vibr膩cija</string>
+    <string name="sonyswr12_settings_stamina">Iesl膿gts ener模ijas taup墨拧anas re啪墨ms</string>
+    <string name="sonyswr12_settings_alarm_interval">Vied膩 trauksmes interv膩ls min奴t膿s</string>
+</resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-ml/strings.xml b/app/src/main/res/values-ml/strings.xml
new file mode 100644
index 0000000..70a4a91
--- /dev/null
+++ b/app/src/main/res/values-ml/strings.xml
@@ -0,0 +1,89 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <string name="fw_upgrade_notice_amazfitcor2">啻ㄠ纯啻權祶啻權闯嗟佮礋嗟� 啻呧串啻距锤嗟嵿传啻苦幢嗟嵿幢嗟� 啻曕祴嗟� 2 嗟� %s 啻祰啻傕吹嗟嗋疮嗟� 啻囙祷啻膏祶啻编祶啻编淳嗟� 啻氞祮啻祶啻淳嗟� 啻祴啻曕祦啻ㄠ祶啻ㄠ祦.
+\n
+\n.Fw 啻疮嗟� 啻囙祷啻膏祶啻编祶啻编淳嗟� 啻氞祮啻祶啻祦啻ㄠ祶啻ㄠ祦啻掂祮啻ㄠ祶啻ㄠ祶 啻夃幢啻祶啻淳啻曕祶啻曕祦啻�, 啻呧搐啻苦川嗟佮炊嗟囙捶啻� .res 啻疮嗟�. .Fw 啻疮嗟� 啻囙祷啻膏祶啻编祶啻编淳嗟� 啻氞祮啻祶啻� 啻多祰啻粪磦 啻ㄠ纯啻權祶啻權闯嗟佮礋嗟� 啻掂淳啻氞祶啻氞祶 啻编祤啻祩啻熰祶啻熰祶 啻氞祮啻祶啻祦啻�
+\n
+\n啻曕祦啻编纯啻祶啻祶: 啻ㄠ纯啻權祶啻權稻 啻祦啻祶啻祶 啻囙祷啻膏祶啻编祶啻编淳嗟� 啻氞祮啻祶啻む搐嗟佮椽嗟嬥床嗟嗋疮啻距矗嗟嗋礄嗟嵿磿啻苦到 .res 啻囙祷啻膏祶啻编祶啻编淳嗟� 啻氞祮啻祶啻祰啻`祶啻熰搐啻苦床嗟嵿床.
+\n
+\n啻ㄠ纯啻權祶啻權闯嗟佮礋嗟� 啻膏祶啻掂川嗟嵿搐啻� 啻呧椽啻曕礋啻膏淳啻о祶啻搐啻纯嗟� 啻祶啻班祴啻膏锤嗟嵿锤嗟� 啻氞祮啻祶啻む祦!
+\n
+\n啻ㄠ纯啻權祶啻權闯嗟佮礋嗟� 啻夃椽啻曕窗啻`搐嗟嵿搐啻苦川嗟嵿幢嗟� 啻祰啻班祶 \"啻呧串啻距锤嗟嵿传啻苦幢嗟嵿幢嗟� 啻淳嗟秽础嗟� 2\" 啻嗋矗嗟嗋礄嗟嵿磿啻苦到, 啻祩嗟监矗嗟嵿矗啻淳啻祦啻� 啻呧岛啻膏祶啻编祶啻编祮啻膏祶啻编祶啻编祦啻氞祮啻祶啻む搐嗟�, 啻掄窗嗟佮椽啻曕祶啻粪祰 啻ㄠ纯啻權祶啻權稻 啻掄窗嗟� 啻祤啻编祶啻编祶啻膏祶_啻∴船嗟嵿床嗟嵿疮嗟� 啻祰啻傕吹嗟嗋疮嗟� 啻祶啻侧淳啻粪祶 啻氞祮啻祶啻祰啻`祶啻熰搐嗟佮矗嗟嵿礋嗟�</string>
+    <string name="fw_upgrade_notice_amazfitcor">啻ㄠ纯啻權祶啻權闯嗟佮礋嗟� 啻呧串啻距锤嗟嵿传啻苦幢嗟嵿幢嗟� 啻曕祴啻编纯嗟� %s 啻祰啻傕吹嗟嗋疮嗟� 啻囙祷啻膏祶啻编祶啻编淳嗟� 啻氞祮啻祶啻淳嗟� 啻祴啻曕祦啻ㄠ祶啻ㄠ祦.
+\n
+\n.Fw 啻疮嗟� 啻囙祷啻膏祶啻编祶啻编淳嗟� 啻氞祮啻祶啻祦啻ㄠ祶啻ㄠ祦啻掂祮啻ㄠ祶啻ㄠ祶 啻夃幢啻祶啻淳啻曕祶啻曕祦啻�, 啻呧搐啻苦川嗟佮炊嗟囙捶啻� .res 啻疮嗟�. .Fw 啻疮嗟� 啻囙祷啻膏祶啻编祶啻编淳嗟� 啻氞祮啻祶啻� 啻多祰啻粪磦 啻ㄠ纯啻權祶啻權闯嗟佮礋嗟� 啻掂淳啻氞祶啻氞祶 啻编祤啻祩啻熰祶啻熰祶 啻氞祮啻祶啻祦啻�
+\n
+\n啻曕祦啻编纯啻祶啻祶: 啻ㄠ纯啻權祶啻權稻 啻祦啻祶啻祶 啻囙祷啻膏祶啻编祶啻编淳嗟� 啻氞祮啻祶啻む搐嗟佮椽嗟嬥床嗟嗋疮啻距矗嗟嗋礄嗟嵿磿啻苦到 .res 啻囙祷啻膏祶啻编祶啻编淳嗟� 啻氞祮啻祶啻祰啻`祶啻熰搐啻苦床嗟嵿床.
+\n
+\n啻ㄠ纯啻權祶啻權闯嗟佮礋嗟� 啻膏祶啻掂川嗟嵿搐啻� 啻呧椽啻曕礋啻膏淳啻о祶啻搐啻纯嗟� 啻祶啻班祴啻膏锤嗟嵿锤嗟� 啻氞祮啻祶啻む祦!</string>
+    <string name="fw_upgrade_notice_amazfitbip_lite">啻ㄠ纯啻權祶啻權闯嗟佮礋嗟� 啻呧串啻距锤嗟嵿传啻苦幢嗟嵿幢嗟� 啻纯啻祶 啻侧祱啻编祶啻编纯嗟� %s 啻祰啻傕吹嗟嗋疮嗟� 啻囙祷啻膏祶啻编祶啻编淳嗟� 啻氞祮啻祶啻淳嗟� 啻祴啻曕祦啻ㄠ祶啻ㄠ祦.
+\n
+\n.Fw 啻疮嗟� 啻囙祷啻膏祶啻编祶啻编淳嗟� 啻氞祮啻祶啻祦啻ㄠ祶啻ㄠ祦啻掂祮啻ㄠ祶啻ㄠ祶 啻夃幢啻祶啻淳啻曕祶啻曕祦啻�, 啻呧搐啻苦川嗟佮炊嗟囙捶啻� .res 啻疮嗟�. .Fw 啻疮嗟� 啻囙祷啻膏祶啻编祶啻编淳嗟� 啻氞祮啻祶啻� 啻多祰啻粪磦 啻ㄠ纯啻權祶啻權闯嗟佮礋嗟� 啻掂淳啻氞祶啻氞祶 啻编祤啻祩啻熰祶啻熰祶 啻氞祮啻祶啻祦啻�
+\n
+\n啻曕祦啻编纯啻祶啻祶: 啻ㄠ纯啻權祶啻權稻 啻祦啻祶啻祶 啻囙祷啻膏祶啻编祶啻编淳嗟� 啻氞祮啻祶啻む搐嗟佮椽嗟嬥床嗟嗋疮啻距矗嗟嗋礄嗟嵿磿啻苦到 .res 啻囙祷啻膏祶啻编祶啻编淳嗟� 啻氞祮啻祶啻祰啻`祶啻熰搐啻苦床嗟嵿床.
+\n
+\n啻ㄠ纯啻權祶啻權闯嗟佮礋嗟� 啻膏祶啻掂川嗟嵿搐啻� 啻呧椽啻曕礋啻膏淳啻о祶啻搐啻纯嗟� 啻祶啻班祴啻膏锤嗟嵿锤嗟� 啻氞祮啻祶啻む祦!</string>
+    <string name="fw_upgrade_notice_amazfitbip">啻ㄠ纯啻權祶啻權闯嗟佮礋嗟� Amazfit Bip- 嗟� %s 啻祰啻傕吹嗟嗋疮嗟� 啻囙祷啻膏祶啻编祶啻编淳嗟� 啻氞祮啻祶啻淳嗟� 啻祴啻曕祦啻ㄠ祶啻ㄠ祦
+\n
+\n.Fw 啻疮啻侧祦啻� .res 啻疮啻侧祦啻� 啻掄礋嗟佮吹啻苦到 .gps 啻疮啻侧祦啻� 啻囙祷啻膏祶啻编祶啻编淳嗟� 啻氞祮啻祶啻祦啻ㄠ祶啻ㄠ祦啻掂祮啻ㄠ祶啻ㄠ祶 啻夃幢啻祶啻淳啻曕祶啻曕祦啻�. .Fw 啻疮嗟� 啻囙祷啻膏祶啻编祶啻编淳嗟� 啻氞祮啻祶啻� 啻多祰啻粪磦 啻ㄠ纯啻權祶啻權闯嗟佮礋嗟� 啻掂淳啻氞祶啻氞祶 啻编祤啻祩啻熰祶啻熰祶 啻氞祮啻祶啻祦啻�.
+\n
+\n啻曕祦啻编纯啻祶啻祶: 啻� 啻疮啻侧祦啻曕稻 啻祦啻祶啻祶 啻囙祷啻膏祶啻编祶啻编淳嗟� 啻氞祮啻祶啻� 啻疮啻侧祦啻曕稻啻曕祶啻曕祶 啻膏串啻距川啻淳啻`祮啻權祶啻曕纯嗟� 啻ㄠ纯啻權祶啻權稻 .res, .gps 啻庎川嗟嵿川啻苦吹 啻囙祷啻膏祶啻编祶啻编淳嗟� 啻氞祮啻祶啻祰啻`祶啻熰搐啻苦床嗟嵿床.
+\n
+\n啻ㄠ纯啻權祶啻權闯嗟佮礋嗟� 啻膏祶啻掂川嗟嵿搐啻� 啻呧椽啻曕礋啻膏淳啻о祶啻搐啻纯嗟� 啻祶啻班祴啻膏锤嗟嵿锤嗟� 啻氞祮啻祶啻む祦!</string>
+    <string name="fw_upgrade_notice">啻ㄠ纯啻權祶啻權稻 %s 啻囙祷啻膏祶啻编祶啻编淳嗟� 啻氞祮啻祶啻淳嗟� 啻祴啻曕祦啻ㄠ祶啻ㄠ祦.</string>
+    <string name="title_activity_fw_app_insaller">FW / 啻呧椽嗟嵿床啻苦磿嗟嵿磿嗟囙捶嗟� 啻囙祷啻膏祶啻编祶啻编淳啻赤导</string>
+    <string name="title_activity_calblacklist">啻祶啻侧淳啻曕祶啻曕祶鈥屶床啻苦锤嗟嵿幢嗟� 啻氞祮啻祶啻� 啻曕床啻`祶啻熰幢嗟佮磿嗟�</string>
+    <string name="whitelist_all_for_notifications">啻ㄠ祴啻熰祶啻熰纯啻纯啻曕祶啻曕祰啻粪川嗟佮磿嗟� 啻庎床嗟嵿床啻距磦 啻掂祱啻编祶啻编祶鈥屶床啻苦锤嗟嵿幢嗟嵿幢嗟� 啻氞祮啻祶啻祦啻�</string>
+    <string name="blacklist_all_for_notifications">啻ㄠ祴啻熰祶啻熰纯啻纯啻曕祶啻曕祰啻粪川嗟佮磿嗟� 啻庎床嗟嵿床啻距磦 啻祶啻侧淳啻曕祶啻曕祶鈥屶床啻苦锤嗟嵿幢嗟嵿幢啻苦到 啻夃稻啻祮啻熰祦啻む祶啻む祦啻�</string>
+    <string name="app_move_to_top">啻祦啻曕闯啻苦床嗟囙磿嗟嵿磿嗟� 啻ㄠ祤啻曕祶啻曕祦啻�</string>
+    <string name="app_configure">啻曕祴嗟亨传啻苦礂嗟� 啻氞祮啻祶啻祦啻�</string>
+    <string name="appmanager_weather_install_provider">Weather Notification 啻呧椽嗟嵿床啻苦磿嗟嵿磿嗟囙捶嗟� 啻囙祷啻膏祶啻编祶啻编淳嗟� 啻氞祮啻祶啻祦啻�</string>
+    <string name="appmanager_weather_deactivate">啻膏纯啻膏祶啻编祶啻编搐嗟嵿搐啻苦床嗟� 啻曕淳啻侧淳啻掂锤嗟嵿触啻� 啻呧椽嗟嵿床啻苦磿嗟嵿磿嗟囙捶嗟� 啻ㄠ纯嗟监礈嗟嵿礈嗟�啻掂串啻距磿嗟嵿磿嗟佮磿</string>
+    <string name="appmanager_weather_activate">啻膏纯啻膏祶啻编祶啻编搐嗟嵿搐啻苦床嗟� 啻曕淳啻侧淳啻掂锤嗟嵿触啻� 啻呧椽嗟嵿床啻苦磿嗟嵿磿嗟囙捶嗟� 啻膏礈嗟�啻掂串啻距磿嗟嵿磿嗟佮磿</string>
+    <string name="appmanager_hrm_activate">啻庎礆嗟嵿礆嗟嵿磫嗟尖�屶磶啻� 啻膏礈嗟�啻掂串啻距磿嗟嵿磿嗟佮磿</string>
+    <string name="appmanager_hrm_deactivate">啻庎礆嗟嵿礆嗟嵿磫嗟尖�屶磶啻� 啻ㄠ纯嗟监礈嗟嵿礈嗟�啻掂串啻距磿嗟嵿磿嗟佮磿</string>
+    <string name="appmanager_health_deactivate">啻ㄠ纯嗟监礈嗟嵿礈嗟�啻掂串啻距磿嗟嵿磿嗟佮磿</string>
+    <string name="appmanager_health_activate">啻膏礈嗟�啻掂串啻距磿嗟嵿磿嗟佮磿</string>
+    <string name="appmanager_app_openinstore">啻祮啻纯嗟� 啻嗋椽嗟嶁�屶锤嗟嵿幢嗟嵿幢嗟嬥幢啻苦到 啻む纯啻班疮嗟佮磿</string>
+    <string name="appmananger_app_reinstall">啻掂祤啻`祶啻熰祦啻� 啻囙祷啻膏祶啻编祶啻编淳嗟� 啻氞祮啻祶啻祦啻�</string>
+    <string name="appmananger_app_delete_cache">啻曕淳啻粪祮啻纯嗟� 啻ㄠ纯啻ㄠ祶啻ㄠ祶 啻囙床嗟嵿床啻距搐啻距磿嗟嵿磿嗟佮磿啻祦啻� 啻ㄠ祤啻曕祶啻曕磦啻氞祮啻祶啻祦啻曕疮嗟佮磦 啻氞祮啻祶啻祦啻�</string>
+    <string name="appmananger_app_delete">啻囙床嗟嵿床啻距搐啻距磿嗟嵿磿嗟佮磿</string>
+    <string name="appmanager_installed_watchfaces">啻囙祷啻膏祶啻编祶啻编淳嗟� 啻氞祮啻祶啻� 啻掂淳啻氞祶啻氞祶啻祰啻膏祦啻曕稻</string>
+    <string name="appmanager_installed_watchapps">啻囙祷啻膏祶啻编祶啻编淳嗟� 啻氞祮啻祶啻� 啻呧椽嗟嵿椽嗟嵿床啻苦磿嗟嵿磿嗟囙捶啻ㄠ祦啻曕稻</string>
+    <string name="appmanager_cached_watchapps_watchfaces">啻曕淳啻粪祮啻纯啻侧祦啻赤祶啻� 啻呧椽嗟嵿床啻苦磿嗟嵿磿嗟囙捶啻ㄠ祦啻曕稻</string>
+    <string name="title_activity_appmanager">啻呧椽嗟嵿床啻苦磿嗟嵿磿嗟囙捶嗟� 啻淳啻ㄠ祰啻溹导</string>
+    <string name="debugactivity_really_factoryreset">啻淳啻曕祶啻熰幢啻� 啻编祤啻膏祮啻编祶啻编纯啻ㄠ祦 啻掂纯啻︵祰啻串啻距磿嗟嵿磿啻苦疮啻距到 啻夃椽啻曕窗啻`搐嗟嵿搐啻苦到 啻ㄠ纯啻ㄠ祶啻ㄠ祶 啻庎床嗟嵿床啻� 啻∴淳啻编祶啻编疮嗟佮磦 啻囙床嗟嵿床啻距搐啻距磿嗟嵿磿嗟佮磦 (啻纯啻ㄠ祶啻む祦啻`疮嗟嶁�屶磿嗟嵿磿嗟佮川嗟嵿川嗟佮吹嗟嗋礄嗟嵿磿啻苦到).Xiaomi / Huami 啻夃椽啻曕窗啻`礄嗟嵿礄啻赤祦啻� 啻祶啻侧祩啻熰祩啻む祶啻む祶 MAC 啻掂纯啻侧淳啻膏搐嗟嵿搐嗟� 啻淳啻编祶啻编祦啻ㄠ祶啻ㄠ祦, 啻呧搐啻苦川啻距到 啻呧吹 啻椸淳啻∴祶鈥屶礈嗟嗋幢嗟嵿幢嗟嵿船嗟嵿窗啻苦础嗟嵿礈啻苦床嗟囙磿嗟嵿磿嗟� 啻掄窗嗟� 啻祦啻む纯啻� 啻夃椽啻曕窗啻`串啻距疮啻� 啻︵祪啻多祶啻串啻距磿嗟佮川嗟嵿川嗟�.</string>
+    <string name="debugactivity_really_factoryreset_title">啻淳啻曕祶啻熰幢啻� 啻编祤啻膏祮啻编祶啻编纯啻ㄠ祦 啻膏礈嗟嵿礈啻淳啻曕祶啻曕矗嗟� \?</string>
+    <string name="title_activity_debug">啻む祮啻编祶啻编祦啻曕稻 啻む纯啻班祦啻む祶啻む祦啻�</string>
+    <string name="controlcenter_calibrate_device">啻夃椽啻曕窗啻`磦 啻曕淳啻侧纯啻祶啻班祰啻编祶啻编祶 啻氞祮啻祶啻祦啻�</string>
+    <string name="controlcenter_snackbar_requested_screenshot">啻夃椽啻曕窗啻`搐嗟嵿搐啻苦川嗟嵿幢嗟� 啻膏祶啻曕祶啻班祤嗟秽捶嗟嬥礋嗟嵿礋嗟� 啻庎礋嗟佮磿嗟嵿磿嗟佮川嗟嵿川嗟�</string>
+    <string name="controlcenter_snackbar_connecting">啻川嗟嵿揣啻苦椽嗟嵿椽啻苦磿嗟嵿磿嗟佮川嗟嵿川嗟�</string>
+    <string name="controlcenter_snackbar_disconnecting">啻掂纯啻氞祶啻涏祰啻︵纯啻曕祶啻曕祦啻ㄠ祶啻ㄠ祦</string>
+    <string name="controlcenter_snackbar_need_longpress">啻掂纯啻氞祶啻涏祰啻︵纯啻曕祶啻曕祦啻ㄠ祶啻ㄠ搐啻苦川嗟� 啻曕淳嗟监础嗟� 啻︵祤嗟监礃啻ㄠ祰啻班磦 啻呧串嗟监搐嗟嵿搐嗟佮磿</string>
+    <string name="controlcenter_navigation_drawer_close">啻ㄠ淳啻掂纯啻椸祰啻粪祷 啻∴祶啻班祴啻导 啻呧礋啻祶鈥屶磿嗟嵿磿嗟佮磿</string>
+    <string name="controlcenter_navigation_drawer_open">啻ㄠ淳啻掂纯啻椸祰啻粪祷 啻∴祶啻班祴啻导 啻む祦啻编磿嗟嵿磿嗟佮磿</string>
+    <string name="controlcenter_set_alias">啻呧椽啻班川啻距串啻� 啻膏礈嗟嵿礈啻淳啻曕祶啻曕祦啻�</string>
+    <string name="controlcenter_delete_device_dialogmessage">啻囙搐嗟� 啻夃椽啻曕窗啻`搐嗟嵿搐嗟嗋疮嗟佮磦 啻川嗟嵿揣啻祶啻祮啻熰祶啻� 啻庎床嗟嵿床啻� 啻∴淳啻编祶啻编疮嗟嗋疮嗟佮磦 啻囙床嗟嵿床啻距搐啻距磿嗟嵿磿嗟佮磦!</string>
+    <string name="controlcenter_delete_device_name">啻囙床嗟嵿床啻距搐啻距磿嗟嵿磿嗟佮磿 %1$s</string>
+    <string name="controlcenter_delete_device">啻夃椽啻曕窗啻`磦 啻囙床嗟嵿床啻距搐啻距磿嗟嵿磿嗟佮磿</string>
+    <string name="controlcenter_disconnect">啻掂纯啻氞祶啻涏祰啻︵纯啻曕祶啻曕祦啻�</string>
+    <string name="controlcenter_connect">啻川嗟嵿揣啻苦椽嗟嵿椽啻苦磿嗟嵿磿嗟佮磿</string>
+    <string name="controlcenter_change_fm_frequency">啻庎传嗟嵿磶啻� 啻嗋吹嗟冟搐嗟嵿搐啻� 啻淳啻编祶啻编祦啻�</string>
+    <string name="controlcenter_change_led_color">LED 啻ㄠ纯啻编磦 啻淳啻编祶啻编祦啻�</string>
+    <string name="controlcenter_take_screenshot">啻膏祶啻曕祶啻班祤嗟� 啻粪祰啻距礋嗟嵿礋嗟� 啻庎礋嗟佮磿嗟嵿磿嗟佮磿</string>
+    <string name="controlcenter_find_device">啻ㄠ捶嗟嵿礋啻祶啻祮啻熰祶啻� 啻夃椽啻曕窗啻`磦 啻曕矗嗟嵿礋嗟嗋搐嗟嵿搐嗟佮磿</string>
+    <string name="controlcenter_fetch_activity_data">啻膏串啻曕淳啻侧纯啻曕串啻距磿嗟嵿磿嗟佮磿</string>
+    <string name="action_donate">啻膏磦啻淳啻掂川 啻氞祮啻祶啻祦啻�</string>
+    <string name="action_quit">啻祦啻编搐嗟嵿搐嗟� 啻曕礋啻曕祶啻曕祦啻�</string>
+    <string name="action_debug">啻む祮啻编祶啻编祦啻曕稻 啻む纯啻班祦啻む祶啻む祦啻�</string>
+    <string name="action_settings">啻曕祶啻班串嗟�啻曕窗啻`礄嗟嵿礄嗟�</string>
+    <string name="title_activity_controlcenter_generic">啻椸淳啻∴祶啻溹磿嗟嶁�屶幢嗟嵿幢嗟� 啻祶啻班纯啻∴祶啻溹祶</string>
+    <string name="application_name_generic">啻椸淳啻∴祶啻溹磿嗟嶁�屶幢嗟嵿幢嗟� 啻祶啻班纯啻∴祶啻溹祶</string>
+    <string name="sonyswr12_settings_low_vibration_summary">啻淳嗟秽础啻苦到 啻掂祱啻祶啻班祰啻粪川嗟嵿幢嗟� 啻曕祦啻编礊嗟嵿礊 啻む祤啻掂祶啻班搐 啻祶啻班吹嗟监搐嗟嵿搐啻ㄠ磿嗟嵿捶啻串啻距磿嗟嵿磿嗟佮磿</string>
+    <string name="sonyswr12_settings_stamina_summary">啻吹嗟� 啻膏祰啻掂纯啻傕礂嗟� 啻祴啻∴祶 啻灌祪啻︵疮啻纯啻熰纯啻祶啻纯啻ㄠ祶啻编祮 啻嗋川嗟佮磿啻距床啻苦磿 啻淳啻ㄠ祶啻む祶啻班纯啻� 啻呧闯啻掂祶 啻撪传嗟佮礆嗟嗋疮嗟嵿疮嗟佮川嗟嵿川啻む祶 啻祶啻班吹嗟监搐嗟嵿搐啻� 啻膏串啻磦 啻掂导啻︵祶啻о纯啻祶啻纯啻曕祶啻曕祦啻ㄠ祶啻ㄠ祦</string>
+    <string name="sonyswr12_settings_alarm_interval_summary">啻囙祷啻膏祶啻编祶啻编淳嗟� 啻氞祮啻祶啻� 啻呧床啻距幢啻む祶啻む纯啻ㄠ祶 啻祦啻祶啻祦啻赤祶啻� 啻囙礋啻掂祰啻赤疮啻距矗嗟� 啻膏祶啻淳嗟监礋嗟嵿礋嗟� 啻呧床啻距幢啻� 啻囙礋啻掂祰啻�. 啻� 啻囙礋啻掂祰啻� 啻夃椽啻曕窗啻`磦 啻夃椽啻祴啻曕祶啻む淳啻掂纯啻ㄠ祮 啻夃矗嗟监搐嗟嵿搐啻距祷 啻夃幢啻曕祶啻曕搐嗟嵿搐啻苦川嗟嵿幢嗟� 啻淳啻班磦 啻曕祦啻编礊嗟嵿礊 啻樴礋嗟嵿礋啻� 啻曕矗嗟嵿礋嗟嗋搐嗟嵿搐啻距祷 啻多祶啻班串啻苦磿嗟嵿磿嗟佮川嗟嵿川嗟�</string>
+    <string name="sonyswr12_settings_title">Sony SWR12 啻曕祶啻班串嗟�啻曕窗啻`礄嗟嵿礄嗟�</string>
+    <string name="sonyswr12_settings_low_vibration">啻曕祦啻编礊嗟嵿礊 啻掂祱啻祶啻班祰啻粪祷 啻祶啻班吹嗟监搐嗟嵿搐啻ㄠ磿嗟嵿捶啻串啻距磿嗟嵿磿啻�</string>
+    <string name="sonyswr12_settings_stamina">啻吹嗟� 啻膏祰啻掂纯啻傕礂嗟� 啻祴啻∴祶 啻撪矗啻距矗嗟�</string>
+    <string name="sonyswr12_settings_alarm_interval">啻纯啻ㄠ纯啻编祶啻编祦啻曕稻啻曕祶啻曕祦啻赤祶啻赤纯嗟� 啻膏祶啻淳嗟监礋嗟嵿礋嗟� 啻呧床啻距幢啻� 啻囙礋啻掂祰啻�</string>
+</resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-my/strings.xml b/app/src/main/res/values-my/strings.xml
new file mode 100644
index 0000000..9e76a7c
--- /dev/null
+++ b/app/src/main/res/values-my/strings.xml
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <string name="application_name_generic">Gadgetbridge</string>
+    <string name="title_activity_controlcenter_generic">Gadgetbridge</string>
+    <string name="action_settings">醼佱�横��斸�贯�娽�结��涐�斸��</string>
+    <string name="action_debug">醼会�曖�呩�贯�佱�横��醼�</string>
+    <string name="action_quit">醼戓�坚��醼贯�欋�娽��</string>
+    <string name="action_donate">醼溼�结�瘁�掅��斸�贯�羔�涐�斸��</string>
+    <string name="controlcenter_fetch_activity_data">醼♂�佱�横��斸�贯��醼���醼贯�溼倛醼曖�贯�涐�结��羔�欋�娽��</string>
+    <string name="controlcenter_find_device">醼贬�曖�横���醼贯�贬�斸�贬�炨��♂�涐���醼��涐�结��欋�娽��</string>
+    <string name="controlcenter_take_screenshot">醼呩��醼涐�勧�贯��醼��撫��愥�贯�曖�夺��涐����醼贯�欋�娽��</string>
+    <string name="controlcenter_change_led_color">醼♂�贬�涐��勧�贯�贬�会�曖��勧�贯�羔�欋�娽��</string>
+    <string name="controlcenter_change_fm_frequency">醼贬�涐�掅��氠���溼倛醼�勧�贯�羔�贬�会�曖��勧�贯�羔�溼�册�欋�娽��</string>
+    <string name="controlcenter_connect">醼佱�横��愥�贯�嗎��醼贯�欋��曖��会�曖���</string>
+    <string name="controlcenter_disconnect">醼♂�嗎��醼贯�♂�炨�坚�氠�贯�会�曖��醼贯�炨�坚��羔�炨�娽��</string>
+    <string name="controlcenter_delete_device">醼め�♂�涐���醼��栣�横��醼贯�欋�娽��</string>
+    <string name="controlcenter_delete_device_name">%1$s 醼�醼��栣�横��醼贯�欋�娽��</string>
+    <string name="controlcenter_delete_device_dialogmessage">醼戓���♂�涐��炨�娽�贯�佱�横��愥�贯�嗎��醼贯�戓��羔�贬�炨��♂�涐�倧醼结�勧�贯�丰�贬�掅�愥��♂��羔�溼�夺��羔��醼��栣�横��醼贯�曖�呩�贯�曖��欋�娽��</string>
+    <string name="controlcenter_snackbar_need_longpress">醼佱�横��愥�贯�嗎��醼贯�欋倛醼会�栣�愥�贯�贬�愥���醼贯�涐�斸�� 醼め��醼掅�贯��醼��栣��戓��羔�曖��</string>
+    <string name="controlcenter_snackbar_disconnecting">醼佱�横��愥�贯�嗎��醼贯�欋倛醼会�栣�愥�贯�贬�愥���醼贯�贬�斸�炨�娽��</string>
+    <string name="controlcenter_snackbar_connecting">醼佱�横��愥�贯�嗎��醼贯�贬�斸�曖��炨�娽�光��</string>
+    <string name="title_activity_debug">醼会�曖�呩�贯�佱�横��醼�</string>
+    <string name="debugactivity_really_factoryreset_title">醼♂�涐��♂��羔�溼�夺��羔��醼��欋�搬�溼�♂�愥���勧�贯�羔�会�曖�斸�贯�戓��羔�欋�娽�贯�欋�结�炨�佱�横��曖��炨�溼��竆?</string>
+    <string name="title_activity_appmanager">醼♂��醼贯�曖�溼��贬��醼羔�涐�结�勧�贯�羔��醼��呩��欋�夺�佱�斸�贯倲醼佱�坚�册�欋�娽��</string>
+    <string name="appmanager_installed_watchapps">醼炨�坚�勧�贯�羔�戓��羔�贬�炨�� 醼♂��醼贯�曖�溼��贬��醼羔�涐�结�勧�贯�羔�欋�横���</string>
+    <string name="appmanager_installed_watchfaces">醼炨�坚�勧�贯�羔�戓��羔�贬�炨�� 醼斸��涐��曖�夺��呩�夺�欋�横���</string>
+    <string name="appmananger_app_delete">醼栣�横��醼贯�欋�娽��</string>
+    <string name="appmananger_app_delete_cache">醼贬�掅�愥��欋�横��羔倧醼结�勧�贯�丰�♂�愥�搬�栣�横��醼贯�欋�娽��</string>
+    <string name="appmananger_app_reinstall">醼♂��醼贯�曖�溼��贬��醼羔�涐�结�勧�贯�羔�会�曖�斸�贯�溼�娽�贯�炨�坚�勧�贯�羔�涐�斸��</string>
+    <string name="appmanager_app_openinstore">Pebble 醼呩�愥���羔�愥�坚�勧�贯�涐�结��欋�娽��</string>
+    <string name="appmanager_health_activate">醼呩�愥�勧�贯�欋�娽��</string>
+    <string name="appmanager_health_deactivate">醼曖��愥�贯�欋�娽��</string>
+    <string name="appmanager_hrm_activate">HRM 醼呩�愥�勧�贯�欋�娽��</string>
+    <string name="appmanager_hrm_deactivate">HRM 醼曖��愥�贯�欋�娽��</string>
+    <string name="appmanager_weather_activate"/>
+    <string name="mode_configuration">醼♂�佱�横��醼贯�♂�溼��醼贯��醼坚�册�欋�横���</string>
+    <string name="save_configuration">醼♂�佱�横��醼贯�♂�溼��醼贯�欋�横��羔��醼��炨��欋�贯�羔�嗎�娽�贯�羔�欋�娽��</string>
+    <string name="appwidget_not_connected">醼欋�佱�横��愥�贯�嗎��醼贯�戓��羔�会�佱�勧�贯�羔�欋�涐�结��曖�� , 醼炨�愥��贬�曖�羔�佱�横��醼贯�欋�戓��羔�涐�贬�炨�羔�曖��</string>
+    <string name="mi2_prefs_button_press_broadcast_default_value" translatable="false">nodomain.freeyourgadget.gadgetbridge.ButtonPressed</string>
+    <string name="sonyswr12_settings_alarm_interval">醼欋��斸�呩�横�♂�愥�结�勧�横�羔�呩�欋�愥�横�斸�踞���羔�嗎�贬��横�炨�夺��醼坚��羔��醼��</string>
+    <string name="sonyswr12_settings_low_vibration_summary">醼愥��羔�濁���勧�横�羔�曖�贬��横�愥��斸�横�佱��欋�踞�亸醼斸��欋�丰�横��醼会�炨�贬��曖�坚�勧�横�羔�♂��羔��醼��佱�结�勧�丰�横�曖�坚��曖��</string>
+    <string name="sonyswr12_settings_stamina_summary">醼曖��濁��佱�会�结�贬�愥��炨�娽�丰�横�♂�斸�贬�栣�坚�勧�丰�横�斸�踞�溼��夺�羔�佱��斸�横�斸�踞��斸�横�羔��醼��♂�溼���♂�溼�会�贬���醼横�愥���勧�横�羔�愥��佱�坚�勧�横�羔��醼��曖��愥�横�戓��羔�炨�栣�坚�勧�丰�横�♂�溼��曖�横�佱�会��斸�横�愥���羔�溼��炨�娽��</string>
+    <string name="sonyswr12_settings_alarm_interval_summary">醼呩�欋�愥�横�斸�踞���羔�呩��醼横��醼坚��羔��醼�溼�炨�娽�横�愥�曖�横�嗎�勧�横�戓��羔�炨�娽�丰�横�斸�踞���羔�呩��醼横�欋�愥���勧�横�欋���醼坚��羔��醼�溼�栣�坚�呩�横�炨�娽�横亱 醼め��醼坚��羔��醼�溼�愥�结�勧�横�♂�炨��夺�羔�曖�坚��炨�搬�炨�娽�横�♂��曖�横�曖�会�贬��横�涐�斸�横�♂�溼�会�勧�横�欋�坚�斸�横�嗎��夺�羔�♂�嗎�勧�丰�横��醼��♂�炨��夺�羔�曖�坚��涐�斸�横�♂�炨��夺�羔�曖�坚��炨�娽��</string>
+    <string name="sonyswr12_settings_title">"Sony SWR12 醼佱�会��斸�横�娽�踞��佱�会��醼横�欋�会��� "</string>
+    <string name="sonyswr12_settings_low_vibration">醼愥��斸�横�佱��欋�踞��斸��欋�丰�横�炨�娽��</string>
+    <string name="sonyswr12_settings_stamina">醼曖��濁��佱�会�结�贬�愥��佱�坚�勧�横�羔�欋��掅�横��醼��栣�结�勧�丰�横�曖��</string>
+    <string name="find_lost_device_message">醼♂��羔�涐�踞��栣�结�贬�佱�坚�勧�横��%1$s\?</string>
+    <string name="controlcenter_power_off">醼曖��濁��曖��愥�横�涐�斸��</string>
+    <string name="controlcenter_power_off_confirm_title">醼曖��濁��曖��愥�横�涐�斸��</string>
+    <string name="application_name_banglejs_main">Bangle.js Gadgetbridge</string>
+    <string name="title_activity_controlcenter_banglejs_main">Bangle.js Gadgetbridge</string>
+    <string name="about_activity_title_banglejs_main">Bangle.js Gadgetbridge 醼♂��醼坚�贬��勧�横��</string>
+    <string name="gadgetbridge_running_banglejs_main">Bangle.js 醼栣�结�勧�横�丰�戓��羔�炨�娽��</string>
+    <string name="about_activity_title_banglejs_nopebble">Bangle.js Gadgetbridge 醼♂��醼坚�贬��勧�横��</string>
+    <string name="gadgetbridge_running_main_nightly">Nightly GB 醼栣�结�勧�横�丰�戓��羔�炨�娽��</string>
+    <string name="sony_button_mode_right">醼佱�溼��愥�横�欋��掅�� (醼娽��樶��醼�)</string>
+    <string name="sony_button_mode_off">醼曖��愥�横�炨�娽��</string>
+    <string name="sony_button_mode_ambient_sound_control">醼曖�愥�横�濁�斸�横�羔��醼会�勧�� 醼♂�炨�夺�戓��斸�横�羔�佱�会��曖�横�欋�踞��</string>
+    <string name="sony_button_mode_playback_control">醼曖�坚�斸�横�栣�结�勧�丰�横�佱�坚�勧�横�� 醼戓��斸�横�羔�佱�会��曖�横�涐�贬��</string>
+    <string name="sony_button_mode_volume_control">醼♂�炨�夺�戓��斸�横�羔�佱�会��曖�横�欋�踞��</string>
+    <string name="watchface_dialog_widget_timezone">醼♂�佱�会��斸�横�囜��斸��</string>
+    <string name="watchface_dialog_widget_timezone_duration">醼斸��涐��欋�坚�勧�横�斸���勧�横�呩�结�欋�横�羔��醼坚��佱�会��斸�� (醼呩��醼贯��醼斸�丰�横�♂�愥�结�勧�横��)</string>
+    <string name="watchface_dialog_widget_update_timeout">Update 醼溼��曖�横�佱�会��斸�横��醼�斸�横�嗎��夺�羔�欋�踞�� 醼欋��斸�呩�横�♂��� 醼炨�愥�横�欋�踞�愥�横�佱�坚�勧�横��</string>
+    <string name="watchface_dialog_widget_timeout_hide_text">醼呩��炨��羔�濁�踞�愥�横�戓��羔�涐�斸�横�佱�会��斸�� 醼炨�愥�横�欋�踞�愥�横�佱�坚�勧�横��</string>
+    <string name="controlcenter_navigation_drawer_close">醼♂�佱�会��醼横�曖�坚�佱�会��醼横�欋�会��羔�♂��� 醼曖��愥�横�欋�娽��</string>
+    <string name="application_name_banglejs_nopebble">Bangle.js Gadgetbridge</string>
+    <string name="about_activity_title_generic">Gadgetbridge 醼♂��醼坚�贬��勧�横��</string>
+    <string name="gadgetbridge_running_generic">Gadgetbridge 醼栣�结�勧�横�丰�戓��羔�炨�娽��</string>
+    <string name="title_activity_controlcenter_banglejs_nopebble">Bangle.js Gadgetbridge</string>
+    <string name="controlcenter_power_off_confirm_description">醼呩��醼横��醼�� 醼曖��濁��曖��愥�横�欋�踞�� 醼炨�贬�佱�会��溼��竆?</string>
+    <string name="controlcenter_set_alias">醼斸��欋�娽�� 醼曖�贬�羔�欋�娽��</string>
+    <string name="controlcenter_navigation_drawer_open">醼♂�佱�会��醼横�曖�坚�佱�会��醼横�欋�会��羔�♂��� 醼栣�结�勧�横�丰�欋�娽��</string>
+    <string name="about_activity_title_main_nightly">Gadgetbridge Nightly 醼♂��醼坚�贬��勧�横��</string>
+    <string name="watchface_dialog_widget_timeout_show_circle">醼♂�濁���勧�横�羔�♂��羔�曖�坚�涐�斸�� 醼♂�佱�会��斸�横�炨�愥�横�欋�踞�愥�横�佱�坚�勧�横��</string>
+    <string name="gadgetbridge_running_banglejs_nopebble">Bangle.js 醼栣�结�勧�横�丰�戓��羔�炨�娽��</string>
+    <string name="steps_streaks_total_steps_average_hint">醼愥�呩�横�斸�贬�丰�溼�会�踞�勧�� 醼呩��呩��曖�贬��勧�横�� 醼曖�会�欋�横�羔�欋�会�� %d 醼佱�坚�贬�溼�踞�欋��</string>
+    <string name="qhybrid_title_on_device_confirmation">醼呩��愥�横�佱�会��斸�横�呩��醼横�涐�斸�� 醼♂�愥�娽�横�曖�坚��佱�会��醼横�♂��� 醼栣�结�勧�横�丰�佱�坚�勧�横��</string>
+    <string name="watchface_widget_type_custom">醼呩��愥�横��醼坚����醼� 醼濁�呩�横�傖�会��醼�</string>
+</resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-nb-rNO/strings.xml b/app/src/main/res/values-nb-rNO/strings.xml
new file mode 100644
index 0000000..c3366ab
--- /dev/null
+++ b/app/src/main/res/values-nb-rNO/strings.xml
@@ -0,0 +1,1701 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <string name="action_settings">Innstillinger</string>
+    <string name="action_debug">Feilretting</string>
+    <string name="action_quit">Avslutt</string>
+    <string name="action_donate">Doner</string>
+    <string name="controlcenter_fetch_activity_data">Synkroniser</string>
+    <string name="controlcenter_find_device">Finn mistet enhet</string>
+    <string name="controlcenter_take_screenshot">Utf酶r skjermavbildning</string>
+    <string name="controlcenter_connect">Koble til鈥�</string>
+    <string name="controlcenter_disconnect">Koble fra</string>
+    <string name="controlcenter_delete_device">Slett enhet</string>
+    <string name="controlcenter_delete_device_name">Slett %1$s</string>
+    <string name="controlcenter_navigation_drawer_open">脜pne navigasjonsskuff</string>
+    <string name="controlcenter_navigation_drawer_close">Lukk navigasjonsskuf</string>
+    <string name="controlcenter_snackbar_need_longpress">Trykk lenge p氓 kortet for 氓 koble fra</string>
+    <string name="controlcenter_snackbar_disconnecting">Kobler fra</string>
+    <string name="controlcenter_snackbar_connecting">Kobler til鈥�</string>
+    <string name="controlcenter_snackbar_requested_screenshot">Utf酶rer skjermavbildning av enheten</string>
+    <string name="title_activity_debug">Feilretting</string>
+    <string name="title_activity_appmanager">Programbehandler</string>
+    <string name="appmanager_cached_watchapps_watchfaces">Programmer i hurtiglager</string>
+    <string name="appmanager_installed_watchapps">Installerte programmer</string>
+    <string name="appmanager_installed_watchfaces">Installerte urskiver</string>
+    <string name="appmananger_app_delete">Slett</string>
+    <string name="appmananger_app_delete_cache">Slett og fjern fra hurtiglager</string>
+    <string name="appmananger_app_reinstall">Reinstaller</string>
+    <string name="appmanager_app_openinstore">S酶k i Pebble-programbutikk</string>
+    <string name="appmanager_health_activate">Aktiver</string>
+    <string name="appmanager_health_deactivate">Deaktiver</string>
+    <string name="appmanager_hrm_activate">Skru p氓 HRM</string>
+    <string name="appmanager_hrm_deactivate">Skru av HRM</string>
+    <string name="appmanager_weather_activate">Skru p氓 systemv忙rprogram</string>
+    <string name="appmanager_weather_deactivate">Skru av systemv忙rprogram</string>
+    <string name="appmanager_weather_install_provider">Installer v忙rmeldingsprogrammet</string>
+    <string name="app_configure">Sett opp</string>
+    <string name="app_move_to_top">Flytt til toppen</string>
+    <string name="title_activity_calblacklist">Svartelistede kalendere</string>
+    <string name="title_activity_fw_app_insaller">Fastvare-/program-installerer</string>
+    <string name="fw_upgrade_notice">Du er i ferd med 氓 installere %s.</string>
+    <string name="application_name_generic">Gadgetbru</string>
+    <string name="title_activity_controlcenter_generic">Gadgetbru</string>
+    <string name="controlcenter_delete_device_dialogmessage">Dette vil slette enheten og all tilknyttet data!</string>
+    <string name="fw_upgrade_notice_amazfitbip">Du er i ferd med 氓 installere %s-fastvaren p氓 din Amazfit Bip.
+\n
+\nForsikre deg om at du installerer .fw-fila, s氓 .res-fila, og til sist gps-fila. Armb氓ndsuret ditt vil starte p氓 nytt etter at .fw-fila er installert.
+\n
+\nMerk: Du m氓 ikke installere .res og .gps hvis disse er akkurat de samme som dem som allerede er installert.
+\n
+\nFORTSETT P脜 EGEN RISIKO!</string>
+    <string name="fw_upgrade_notice_amazfitcor">Du er i ferd med 氓 installere %s-fastvaren p氓 din Amazfit Cor.
+\n
+\nForsikre deg om at du installerer .fw-fila, og s氓 .res-fila. B氓ndet ditt vil starte p氓 ny etter at .fw-fila er installert.
+\n
+\nMerk: Du m氓 ikke installere .res hvis den er akkurat de samme som den som allerede er installert.
+\n
+\nFORTSETT P脜 EGEN RISIKO!</string>
+    <string name="fw_multi_upgrade_notice">Du er i ferd med 氓 installere %1$s- og %2$s-fastvarene isteden for dem som er installert n氓 p氓 ditt Mi Band.</string>
+    <string name="miband_firmware_known">Denne fastvaren har blitt testet og er kompatibel med Gadgetbru.</string>
+    <string name="miband_firmware_unknown_warning">Denne fastvaren er utestet og er kanskje ikke kompatibel med Gadgetbru. 
+\n 
+\nDu FRAR脜DES fra 氓 skrive den til enheten!</string>
+    <string name="miband_firmware_suggest_whitelist">Hvis du fremdeles 酶nsker 氓 fortsette og ting fungerer etterp氓, fortell Gadgetbru-utviklerne at de skal hvitliste %s-fastvareversjonen.</string>
+    <string name="title_activity_settings">Innstillinger</string>
+    <string name="pref_header_general">Hovedinnstillinger</string>
+    <string name="pref_title_general_autoconnectonbluetooth">Koble til Gadgetbridge-enhet n氓r Bl氓tann sl氓s p氓</string>
+    <string name="pref_title_general_autostartonboot">Start automatisk</string>
+    <string name="pref_title_general_autoreconnect">Koble til igjen automatisk</string>
+    <string name="pref_title_audio_player">Foretrukken lydspiller</string>
+    <string name="pref_default">Forvalg</string>
+    <string name="pref_header_datetime">Dato og tid</string>
+    <string name="pref_title_datetime_syctimeonconnect">Synkroniser tid</string>
+    <string name="pref_summary_datetime_syctimeonconnect">Synkroniser tid til Gadgetbridge-enhet ved tilkobling, og n氓r tid eller tidssone endres p氓 Android-enheten</string>
+    <string name="pref_title_theme">Drakt</string>
+    <string name="pref_theme_light">Lys</string>
+    <string name="pref_theme_dark">M酶rk</string>
+    <string name="pref_title_language">Spr氓k</string>
+    <string name="pref_title_minimize_priority">Skjul Gadgetbro-merknaden</string>
+    <string name="pref_summary_minimize_priority_off">Ikonet i statusfeltet og merknaden p氓 l氓seskjermen vises</string>
+    <string name="pref_summary_minimize_priority_on">Ikonet i statusfeltet og merknaden p氓 l氓seskjermen er skjult</string>
+    <string name="pref_header_notifications">Merknader</string>
+    <string name="pref_title_notifications_repetitions">Gjentakelser</string>
+    <string name="pref_title_notifications_call">Telefonsamtaler</string>
+    <string name="pref_title_notifications_sms">SMS</string>
+    <string name="pref_title_notifications_pebblemsg">Pebble-meldinger</string>
+    <string name="pref_summary_notifications_pebblemsg">St酶tte for alle programmer som sender merknader til Pebble via PebbleKit.</string>
+    <string name="pref_title_notifications_generic">Generisk merknadsst酶tte</string>
+    <string name="pref_title_whenscreenon">鈥gs氓 n氓r skjermen er p氓</string>
+    <string name="pref_title_notification_filter">Ikke forstyrr</string>
+    <string name="pref_summary_notification_filter">Modus der u酶nskede merkander stoppes</string>
+    <string name="pref_title_transliteration">Translitterasjon</string>
+    <string name="pref_summary_transliteration">Skru p氓 hvis din enhet ikke har noen st酶tte for ditt spr氓ks skrift</string>
+    <string name="always">Alltid</string>
+    <string name="when_screen_off">N氓r skjermen er av</string>
+    <string name="never">Aldri</string>
+    <string name="pref_header_privacy">Personvern</string>
+    <string name="pref_title_call_privacy_mode">Ringepersonvernsmodus</string>
+    <string name="pref_call_privacy_mode_off">Vis navn og nummer</string>
+    <string name="pref_call_privacy_mode_name">Skjul navn, men vis nummer</string>
+    <string name="pref_call_privacy_mode_number">Skjul nummer, men vis navn</string>
+    <string name="pref_call_privacy_mode_complete">Skjul navn og nummer</string>
+    <string name="pref_blacklist_calendars">Svartelist kalendre</string>
+    <string name="pref_title_canned_replies">Svar</string>
+    <string name="pref_title_canned_messages_set">Oppdater p氓 enheten</string>
+    <string name="pref_header_development">Utviklervalg</string>
+    <string name="pref_title_development_miaddr">Mi Band-adresse</string>
+    <string name="pref_title_pebble_settings">Pebble-innstillinger</string>
+    <string name="pref_header_activitytrackers">Aktivitetssporere</string>
+    <string name="pref_title_pebble_activitytracker">Foretrukket aktivitetssporer</string>
+    <string name="pref_title_pebble_sync_health">Synkroniser Pebble-helse</string>
+    <string name="pref_title_pebble_sync_misfit">Misfit-synkronisering</string>
+    <string name="pref_title_pebble_sync_morpheuz">Morpheuz-synkronisering</string>
+    <string name="pref_title_enable_outgoing_call">St酶tte for utg氓ende anrop</string>
+    <string name="pref_summary_enable_outgoing_call">脜 skru av dette vil ogs氓 forhindre Pebble 2/LE fra 氓 vibrere under utg氓ende anrop</string>
+    <string name="pref_title_enable_pebblekit">Tillat tredjepartstilgang fra Android-programmer</string>
+    <string name="pref_summary_enable_pebblekit">Skru p氓 eksperimentell st酶te for Androidprogrammer som bruker PebbleKit</string>
+    <string name="pref_header_pebble_timeline">Pebble-tidslinje</string>
+    <string name="pref_title_sunrise_sunset">Soloppgang og solnedgang</string>
+    <string name="pref_summary_sunrise_sunset">Send soloppgang og solnedgangstider basert p氓 plasseringen til Pebble-tidslinjen</string>
+    <string name="pref_title_enable_calendar_sync">Synkroniser kalender</string>
+    <string name="pref_summary_enable_calendar_sync">Send kalenderhendelser til tidslinjen</string>
+    <string name="pref_title_autoremove_notifications">Fjern forkastede merknader automatisk</string>
+    <string name="pref_summary_autoremove_notifications">Merknader blir automatisk fjernet fra enheten n氓r de forkastes fra telefonen</string>
+    <string name="pref_title_pebble_privacy_mode">Personvernsmodus</string>
+    <string name="pref_pebble_privacy_mode_off">Normale merknader</string>
+    <string name="pref_pebble_privacy_mode_content">Vis merknadsteksten utenfor skjermen</string>
+    <string name="pref_pebble_privacy_mode_complete">Kun vis merknadsikoner</string>
+    <string name="pref_header_location">Plassering</string>
+    <string name="pref_title_location_aquire">Hent plassering</string>
+    <string name="pref_title_location_latitude">Breddegrad</string>
+    <string name="pref_title_location_longitude">Lengdegrad</string>
+    <string name="pref_title_location_keep_uptodate">Hold plasseringen oppdatert</string>
+    <string name="pref_summary_location_keep_uptodate">Pr酶v 氓 hent gjeldende plassering ved kj酶ring, bruk lagret plassering som andrevalg</string>
+    <string name="toast_enable_networklocationprovider">Skru p氓 nettverksplassering</string>
+    <string name="toast_aqurired_networklocation">plassering innhentet</string>
+    <string name="pref_title_pebble_forceprotocol">Tving gjennom merknadsprotokoll</string>
+    <string name="pref_summary_pebble_forceprotocol">Dette valget tvinger bruk av siste merknadskotroll avhengig av fastvareversjon. TA REDE P脜 HVA DU DRIVER P脜 MED!</string>
+    <string name="pref_title_pebble_forceuntested">Skru p氓 utestede funksjoner</string>
+    <string name="pref_summary_pebble_forceuntested">Skru p氓 utestede funksjoner. TA REDE P脜 HVA DU DRIVER P脜 MED!</string>
+    <string name="pref_title_pebble_forcele">Alltid foretrekk BLE</string>
+    <string name="pref_summary_pebble_forcele">Bruk eksperimentell Pebble LE-st酶tte for alle Pebble-klokker, istedenfor BT classic, som krever parring til ikke-LE f酶rst, og s氓 Pebble LE</string>
+    <string name="pref_title_pebble_mtu_limit">Pebble 2/LE GATT MTU-grense</string>
+    <string name="pref_summary_pebble_mtu_limit">Hvis din Pebble 2/Pebble LE ikke fungerer som forventet, pr酶v 氓 sette denne grensen for 氓 begrense MTU (gyldig rekkevidde 20鈥�512)</string>
+    <string name="pref_title_pebble_enable_bgjs">Skru p氓 bakgrunns-JS</string>
+    <string name="pref_summary_pebble_enable_bgjs">Tillater n氓r p氓skrudd, v忙rvarsel p氓 urskive, batteriinfo, osv.</string>
+    <string name="pref_title_pebble_reconnect_attempts">Fors酶k p氓 tilkoblingsgjenopprettelse</string>
+    <string name="pref_title_unit_system">Enheter</string>
+    <string name="pref_title_timeformat">Tidsformat</string>
+    <string name="prefs_title_all_day_heart_rate">Heldagspulsm氓ling</string>
+    <string name="preferences_hplus_settings">HPlus/Makibes-innstillinger</string>
+    <string name="not_connected">Ikke tilkoblet</string>
+    <string name="connecting">Kobler til</string>
+    <string name="connected">Tilkoblet</string>
+    <string name="unknown_state">Ukjent tilstand</string>
+    <string name="_unknown_">(ukjent)</string>
+    <string name="test">Test</string>
+    <string name="test_notification">Send testmerknad</string>
+    <string name="this_is_a_test_notification_from_gadgetbridge">Dette er en testmerknad fra Gadgetbru</string>
+    <string name="bluetooth_is_not_supported_">Bl氓tann st酶ttes ikke.</string>
+    <string name="bluetooth_is_disabled_">Bl氓tann avskrudd.</string>
+    <string name="tap_connected_device_for_app_mananger">Trykk p氓 tilkoblet enhet for programbehandler</string>
+    <string name="tap_connected_device_for_activity">Trykk p氓 tilkoblet enhet for aktivtet</string>
+    <string name="tap_connected_device_for_vibration">Trykk p氓 tilkoblet enhet for vibrasjon</string>
+    <string name="tap_a_device_to_connect">Trykk p氓 en enhet for 氓 koble til</string>
+    <string name="cannot_connect_bt_address_invalid_">Kan ikke koble til. Ugyldig Bl氓tannsadresse?</string>
+    <string name="gadgetbridge_running_generic">Gadgetbro kj酶rer</string>
+    <string name="installing_binary_d_d">Installerer bin忙rfil %1$d/%2$d</string>
+    <string name="installation_failed_">Installasjon mislyktes</string>
+    <string name="installation_successful">Installert</string>
+    <string name="firmware_install_warning">DU PR脴VER 脜 INSTALLERE EN FASTVARE, FORTSETT P脜 EGEN RISIKO.
+\n
+\n
+\nDENNE FASTVAREN ER FOR MASKINVAREVERSJON: %s</string>
+    <string name="app_install_info">Du er i ferd med 氓 installere f酶lgende program:
+\n
+\n
+\n%1$s version %2$s av %3$s
+\n</string>
+    <string name="n_a">I/T</string>
+    <string name="initialized">igangsatt</string>
+    <string name="appversion_by_creator">%1$s av %2$s</string>
+    <string name="title_activity_discovery">Enhetsoppdaging</string>
+    <string name="discovery_stop_scanning">Stopp skanning</string>
+    <string name="discovery_start_scanning">Start oppdagning</string>
+    <string name="action_discover">Koble til ny enhet</string>
+    <string name="device_with_rssi">%1$s (%2$s)</string>
+    <string name="title_activity_android_pairing">Parr enhet</string>
+    <string name="android_pairing_hint">Bruk parringsdialogen for Bl氓tann i Android til 氓 parre enheten.</string>
+    <string name="title_activity_mi_band_pairing">Parr ditt Mi Band</string>
+    <string name="pairing">Parrer med %s鈥�</string>
+    <string name="pairing_creating_bond_with">Oppretter tilknytning til %1$s (%2$s)</string>
+    <string name="pairing_unable_to_pair_with">Klarte ikke 氓 parre med %1$s (%2$s)</string>
+    <string name="pairing_in_progress">Tilknytning underveis: %1$s (%2$s)</string>
+    <string name="pairing_already_bonded">Allerede tilknyttet %1$s (%2$s), tilkobler鈥�</string>
+    <string name="message_cannot_pair_no_mac">Ingen MAC-adresse sendt, kan ikke parre.</string>
+    <string name="preferences_category_device_specific_settings">Enhetsspesifikke innstillinger</string>
+    <string name="preferences_miband_settings">Mi Band / Amazfit -innstillinger</string>
+    <string name="male">Mann</string>
+    <string name="female">Kvinne</string>
+    <string name="other">Annet</string>
+    <string name="left">Venstre</string>
+    <string name="right">H酶yre</string>
+    <string name="miband_pairing_using_dummy_userdata">Ingen gyldig brukerdata angitt, bruker testbrukerdata midlertidig.</string>
+    <string name="miband_pairing_tap_hint">N氓r ditt Mi Band vibrerer og blinker, trykk p氓 det et par ganger p氓 rad.</string>
+    <string name="appinstaller_install">Installer</string>
+    <string name="discovery_connected_devices_hint">Gj酶r enheten din oppdagbar. Enheter som er tilkoblet vil antagelig ikke bli oppdaget. Aktiver plassering (GPS bl. a.) p氓 Android 6+. Skru av Privacy Guard for Gadgetbru, siden det kan krasje og medf酶re omstart av telefonen. Hvis ingen enhet blir funnet etter et par minutter, pr酶v igjen etter 氓 ha startet din mobile enhet p氓 nytt.</string>
+    <string name="discovery_note">Merk:</string>
+    <string name="candidate_item_device_image">Enhetsavbildning</string>
+    <string name="miband_prefs_alias">Navn/alias</string>
+    <string name="pref_header_vibration_count">Vibrasjonsantall</string>
+    <string name="title_activity_sleepmonitor">S酶vnmonitor</string>
+    <string name="pref_write_logfiles">Skriv loggfiler</string>
+    <string name="initializing">Igangsetter</string>
+    <string name="busy_task_fetch_activity_data">Henter aktivitetsdato</string>
+    <string name="sleep_activity_date_range">Fra %1$s til %2$s</string>
+    <string name="prefs_wearside">Venstre- eller h酶yre-h氓nd?</string>
+    <string name="pref_screen_vibration_profile">Vibrasjonsprofil</string>
+    <string name="vibration_profile_staccato">Stakkato</string>
+    <string name="vibration_profile_short">Kort</string>
+    <string name="vibration_profile_medium">Middels</string>
+    <string name="vibration_profile_long">Lang</string>
+    <string name="vibration_profile_waterdrop">Vanndr氓pe</string>
+    <string name="vibration_profile_ring">Ring</string>
+    <string name="vibration_profile_alarm_clock">Alarmklokke</string>
+    <string name="miband_prefs_vibration">Vibrasjon</string>
+    <string name="vibration_try">Pr酶v</string>
+    <string name="pref_screen_notification_profile_sms">SMS-merknad</string>
+    <string name="pref_header_vibration_settings">Vibrasjonsinnstillinger</string>
+    <string name="pref_screen_notification_profile_generic">Generisk merknad</string>
+    <string name="pref_screen_notification_profile_email">E-postmerknad</string>
+    <string name="pref_screen_notification_profile_incoming_call">Innkommende ringemerknad</string>
+    <string name="pref_screen_notification_profile_generic_chat">Sludring</string>
+    <string name="pref_screen_notification_profile_generic_navigation">Navigasjon</string>
+    <string name="pref_screen_notification_profile_generic_social">Sosialt nettverk</string>
+    <string name="prefs_title_heartrate_measurement_interval">Heldagspulsm氓ling</string>
+    <string name="interval_one_minute">茅n gang i minuttet</string>
+    <string name="interval_five_minutes">hvert femte minutt</string>
+    <string name="interval_ten_minutes">hvert tiende minutt</string>
+    <string name="interval_thirty_minutes">hver halvtime</string>
+    <string name="interval_one_hour">茅n gang i timen</string>
+    <string name="stats_title">Fartssoner</string>
+    <string name="stats_x_axis_label">Totalt antall minutter</string>
+    <string name="stats_y_axis_label">Steg per minutt</string>
+    <string name="control_center_find_lost_device">Finn tapt enhet</string>
+    <string name="control_center_cancel_to_stop_vibration">Avbryt for 氓 stoppe vibrasjon.</string>
+    <string name="title_activity_charts">Aktivitet og s酶vn</string>
+    <string name="title_activity_set_alarm">Sett opp alarmer</string>
+    <string name="controlcenter_start_configure_alarms">Sett opp alarmer</string>
+    <string name="title_activity_alarm_details">Alarmdetaljer</string>
+    <string name="alarm_sun_short">S酶n</string>
+    <string name="alarm_mon_short">Man</string>
+    <string name="alarm_tue_short">Tir</string>
+    <string name="alarm_wed_short">Ons</string>
+    <string name="alarm_thu_short">Tor</string>
+    <string name="alarm_fri_short">Fre</string>
+    <string name="alarm_sat_short">L酶r</string>
+    <string name="alarm_smart_wakeup">Smart oppv氓kning</string>
+    <string name="user_feedback_miband_set_alarms_failed">Feil ved setting av alarm, pr酶v igjen.</string>
+    <string name="user_feedback_miband_set_alarms_ok">Alarmer sendt til enhet.</string>
+    <string name="chart_no_data_synchronize">Ingen data. Synkroniser enhet?</string>
+    <string name="user_feedback_miband_activity_data_transfer">I ferd med 氓 overf酶re %1$s med data fra %2$s</string>
+    <string name="miband_prefs_fitness_goal">Daglig stegm氓l</string>
+    <string name="dbaccess_error_executing">Feil under kj酶ring av \"%1$s\"</string>
+    <string name="controlcenter_start_activitymonitor">Din aktivitet (alfa)</string>
+    <string name="cannot_connect">Kan ikke koble til: %1$s</string>
+    <string name="installer_activity_unable_to_find_handler">Kan ikke finne en behandler for installasjon av denne fila.</string>
+    <string name="pbw_install_handler_unable_to_install">Klarte ikke 氓 installere gitt fil: %1$s</string>
+    <string name="pbw_install_handler_hw_revision_mismatch">Klarte ikke 氓 installere angitt fastvare: Den passer ikke maskinvareversjonen p氓 din Pebble.</string>
+    <string name="installer_activity_wait_while_determining_status">Vent mens installasjonsstatus fastsettes鈥�</string>
+    <string name="notif_battery_low_title">Lite batteri p氓 gadget!</string>
+    <string name="notif_battery_low_percent">%1$s batteri gjenst氓r: %2$s%%</string>
+    <string name="notif_battery_low_bigtext_last_charge_time">Siste oppladning: %s 
+\n</string>
+    <string name="notif_battery_low_bigtext_number_of_charges">Antall ladinger: %s</string>
+    <string name="sleepchart_your_sleep">S酶vn</string>
+    <string name="weeksleepchart_sleep_a_week">S酶vn per uke</string>
+    <string name="weeksleepchart_today_sleep_description">S酶vn i dag, m氓l: %1$s</string>
+    <string name="weekstepschart_steps_a_week">Steg per uke</string>
+    <string name="activity_sleepchart_activity_and_sleep">Aktivitet</string>
+    <string name="updating_firmware">Oppgraderer fastvare鈥�</string>
+    <string name="fwapp_install_device_not_ready">Kan ikke installere fil, enheten er ikke klar.</string>
+    <string name="installhandler_firmware_name">%1$s: %2$s %3$s</string>
+    <string name="miband_fwinstaller_compatible_version">Kompatibel versjon</string>
+    <string name="miband_fwinstaller_untested_version">Utestet versjon!</string>
+    <string name="fwappinstaller_connection_state">Tilkobling til enhet: %1$s</string>
+    <string name="pbw_installhandler_pebble_firmware">Pebble-fastvare %1$s</string>
+    <string name="pbwinstallhandler_correct_hw_revision">Korrekt maskinvareversjon</string>
+    <string name="pbwinstallhandler_incorrect_hw_revision">Det er ikke samsvar mellom maskinvarerevisjonene!</string>
+    <string name="pbwinstallhandler_app_item">%1$s (%2$s)</string>
+    <string name="updatefirmwareoperation_updateproblem_do_not_reboot">Problem med fastvareoverf酶ring. IKKE SKRU AV ditt Mi Band!</string>
+    <string name="updatefirmwareoperation_metadata_updateproblem">Problem ved overf酶ring av fastvaremetadata</string>
+    <string name="updatefirmwareoperation_update_complete">Fastvareinstallasjon fullf酶rt</string>
+    <string name="updatefirmwareoperation_update_complete_rebooting">Fastvareinstallasjon fullf酶rt, starter enhet p氓 nytt鈥�</string>
+    <string name="updatefirmwareoperation_write_failed">Fastvareskriving mislyktes</string>
+    <string name="chart_steps">Steg</string>
+    <string name="calories">Kalorier</string>
+    <string name="distance">Distanse</string>
+    <string name="clock">Klokke</string>
+    <string name="heart_rate">Puls</string>
+    <string name="battery">Batteri</string>
+    <string name="liveactivity_live_activity">Aktivitet i sanntid</string>
+    <string name="weeksteps_today_steps_description">Steg i dag, m氓l: %1$s</string>
+    <string name="pref_title_dont_ack_transfer">Ikke send bekreftelse p氓 aktivitetsdataoverf酶ring</string>
+    <string name="pref_summary_dont_ack_transfers">Hvis de ikke bekreftes til bandet, vil de ikke bli t酶mt. Nyttig hvis GB brukes sammen med andre programmer.</string>
+    <string name="pref_summary_keep_data_on_device">Beholder aktivitetsdata p氓 Mi Band-et, selv etter synkronisering. Nyttig hvis Gb brukes sammen med andre programmer.</string>
+    <string name="pref_title_low_latency_fw_update">Bruk lav-latens-modus for fastvareoppgraderinger</string>
+    <string name="pref_summary_low_latency_fw_update">Dette kan hjelpe p氓 enheter der fastvareoppgraderinger mislykkes.</string>
+    <string name="live_activity_steps_history">Steghistorikk</string>
+    <string name="live_activity_current_steps_per_minute">Gjeldende steg/min</string>
+    <string name="live_activity_total_steps">Totalt antall steg</string>
+    <string name="live_activity_steps_per_minute_history">Historisk, steg per minutt</string>
+    <string name="live_activity_start_your_activity">Start din aktivitet</string>
+    <string name="abstract_chart_fragment_kind_activity">Aktivitet</string>
+    <string name="abstract_chart_fragment_kind_light_sleep">Lett s酶vn</string>
+    <string name="abstract_chart_fragment_kind_deep_sleep">Dyp s酶vn</string>
+    <string name="abstract_chart_fragment_kind_not_worn">Ikke brukt</string>
+    <string name="device_not_connected">Ikke tilkoblet.</string>
+    <string name="user_feedback_all_alarms_disabled">Alle alarmer avskrudd</string>
+    <string name="pref_title_keep_data_on_device">Behold aktivitetsdata p氓 enhet</string>
+    <string name="miband_fwinstaller_incompatible_version">Ukompatibel fastvare</string>
+    <string name="fwinstaller_firmware_not_compatible_to_device">Fastvaren er ikke kompatibel med enheten</string>
+    <string name="miband_prefs_reserve_alarm_calendar">Alarmer 氓 holde av for kommende hendelser</string>
+    <string name="miband_prefs_hr_sleep_detection">Bruk pulsm氓ler for 氓 forbedre bestemmelse av s酶vnm酶nster</string>
+    <string name="miband2_prefs_dateformat">Datoformat</string>
+    <string name="dateformat_time">Tid</string>
+    <string name="dateformat_date_time">Tid og dato</string>
+    <string name="mi2_prefs_button_actions">Knappevalg</string>
+    <string name="mi2_prefs_button_actions_summary">Angi knappetrykkshandlinger</string>
+    <string name="mi2_prefs_button_press_count">Antall trykk p氓 knapp</string>
+    <string name="mi2_prefs_button_press_count_summary">Antall knappetrykk for 氓 utl酶se hendelse 1. Etterf酶lgende likt antall trykk utl酶ser hendelse 2, osv.</string>
+    <string name="mi2_prefs_button_press_broadcast">Kringkastingsmelding 氓 sende</string>
+    <string name="mi2_prefs_goal_notification">M氓lmerknad</string>
+    <string name="mi2_prefs_goal_notification_summary">B氓ndet vil vibrere n氓r daglig stegm氓l n氓s</string>
+    <string name="mi2_prefs_display_items">Vis elementer</string>
+    <string name="mi2_prefs_display_items_summary">Velg elementene som skal vises p氓 b氓ndskjermen</string>
+    <string name="mi2_prefs_rotate_wrist_to_switch_info">Roter h氓ndledd for 氓 bytte info</string>
+    <string name="mi2_prefs_do_not_disturb">Ikke forstyrr</string>
+    <string name="mi2_prefs_do_not_disturb_summary">B氓ndet mottar ikke merknader n氓r det er aktivt</string>
+    <string name="mi2_prefs_inactivity_warnings">Inaktivitetsadvarsler</string>
+    <string name="mi2_prefs_inactivity_warnings_summary">B氓ndet vil vibrere n氓r du har v忙rt inaktiv en stund</string>
+    <string name="mi2_prefs_inactivity_warnings_threshold">Inaktivitetsterskel (i minutter)</string>
+    <string name="mi2_prefs_inactivity_warnings_dnd_summary">Skru av inaktivitetsadvarsler for en tidsperiode</string>
+    <string name="mi2_prefs_do_not_disturb_start">Starttid</string>
+    <string name="mi2_prefs_do_not_disturb_end">Sluttid</string>
+    <string name="automatic">Automatisk</string>
+    <string name="simplified_chinese">Forenklet kinesisk</string>
+    <string name="traditional_chinese">Tradisjonell kinesisk</string>
+    <string name="english">Engelsk</string>
+    <string name="FetchActivityOperation_about_to_transfer_since">I ferd med 氓 overf酶re data siden %1$s</string>
+    <string name="waiting_for_reconnect">Venter p氓 ny tilkobling</string>
+    <string name="activity_prefs_about_you">Om deg</string>
+    <string name="activity_prefs_year_birth">F酶dsels氓r</string>
+    <string name="activity_prefs_gender">Kj酶nn</string>
+    <string name="activity_prefs_height_cm">H酶yde i cm</string>
+    <string name="activity_prefs_weight_kg">Vekt i kg</string>
+    <string name="authenticating">Identitetsbekrefter</string>
+    <string name="authentication_required">Identitetsbekreftelse kreves</string>
+    <string name="appwidget_text">Zzz</string>
+    <string name="add_widget">Legg til miniprogram</string>
+    <string name="activity_prefs_sleep_duration">Foretrukket s酶vn i timer</string>
+    <string name="appwidget_setting_alarm">En alarm ble satt for %1$02d:%2$02d</string>
+    <string name="device_hw">Maskinvarerevisjon: %1$s</string>
+    <string name="device_fw">Fastvareversjon: %1$s</string>
+    <string name="error_creating_directory_for_logfiles">Feil under opprettelse av mappe for loggfiler: %1$s</string>
+    <string name="DEVINFO_HR_VER">"HR: "</string>
+    <string name="updatefirmwareoperation_update_in_progress">Fastvareoppgradering underveis</string>
+    <string name="updatefirmwareoperation_firmware_not_sent">Fastvare ikke sendt</string>
+    <string name="charts_legend_heartrate">Puls</string>
+    <string name="live_activity_heart_rate">Puls</string>
+    <string name="pref_title_pebble_health_store_raw">Lagre r氓data i databasen</string>
+    <string name="pref_summary_pebble_health_store_raw">Hvis valgt blir dataen lagret \"som den er\", noe som 酶ker databasebruk for senere fortolkning.</string>
+    <string name="action_db_management">Databehandling</string>
+    <string name="title_activity_db_management">Databehandling</string>
+    <string name="dbmanagementactivity_exported_to">Eksporter til: %1$s</string>
+    <string name="dbmanagementactivity_error_exporting_db">Feil ved eksport av DB: %1$s</string>
+    <string name="dbmanagementactivity_error_exporting_shared">Feil ved eksport av innstilling: %1$s</string>
+    <string name="dbmanagementactivity_import_data_title">Importer data?</string>
+    <string name="dbmanagementactivity_overwrite_database_confirmation">Vil du virkelig overskrive gjeldende data? All n氓v忙rende aktivitetsdata (hvis noen) vil bli overskrevet.</string>
+    <string name="dbmanagementactivity_import_successful">Importert.</string>
+    <string name="dbmanagementactivity_error_importing_db">Feil ved import av DB: %1$s</string>
+    <string name="dbmanagementactivity_error_importing_shared">Feil ved import av innstilling: %1$s</string>
+    <string name="dbmanagementactivity_delete_activity_data_title">Slett aktivitetsdata?</string>
+    <string name="dbmanagementactivity_really_delete_entire_db">Vil du virkelig slette hele databasen? All din aktivitetsdata og informasjon om dine enheter vil g氓 tapt.</string>
+    <string name="dbmanagementactivity_database_successfully_deleted">Data slettet.</string>
+    <string name="dbmanagementactivity_db_deletion_failed">Databasesletting mislyktes.</string>
+    <string name="dbmanagementactivity_delete_old_activity_db">Slett gammel aktivitetsdatabase?</string>
+    <string name="dbmanagementactivity_delete_old_activitydb_confirmation">Vil du virkelig slette den gamle aktivitetsdatabasen? Aktivietetsdata som ikke ble importert vil g氓 tapt.</string>
+    <string name="dbmanagementactivity_old_activity_db_successfully_deleted">Gammel aktivitetsdata slettet.</string>
+    <string name="dbmanagementactivity_old_activity_db_deletion_failed">Sletting av gammel aktivitetsdatabase mislyktes.</string>
+    <string name="dbmanagementactivity_overwrite">Overskriv</string>
+    <string name="Cancel">Avbryt</string>
+    <string name="Delete">Slett</string>
+    <string name="title_activity_vibration">Vibrasjon</string>
+    <string name="title_activity_pebble_pairing">Pebble-parring</string>
+    <string name="pebble_pairing_hint">En parringslogg vil sprette opp p氓 din Android-enhet. Hvis ikke, se i navigasjonsskuffen, og godta parringsforesp酶rselen. Godta den ogs氓 p氓 din Pebble etterp氓.</string>
+    <string name="pref_title_setup_bt_pairing">Skru p氓 Bl氓tannsparring</string>
+    <string name="pref_summary_setup_bt_pairing">Skru dette av hvis du har problemer med 氓 koble til</string>
+    <string name="unit_metric">Metrisk</string>
+    <string name="unit_imperial">Imperial</string>
+    <string name="timeformat_24h">24t</string>
+    <string name="timeformat_am_pm">AM/PM</string>
+    <string name="pref_screen_notification_profile_alarm_clock">Ringeklokke</string>
+    <string name="StringUtils_sender">(%1$s)</string>
+    <string name="find_device_you_found_it">Du fant den!</string>
+    <string name="miband2_prefs_timeformat">Mi2: Tidsformat</string>
+    <string name="mi2_fw_installhandler_fw53_hint">Du m氓 installere versjon %1$s f酶r du installerer denne fastvaren!</string>
+    <string name="mi2_enable_text_notifications">Tekstmerknader</string>
+    <string name="mi2_enable_text_notifications_summary">Trenger fastvare &lt;=1.0.1.28 og Mili_pro.ft* installert.</string>
+    <string name="off">Av</string>
+    <string name="mi2_dnd_off">P氓</string>
+    <string name="mi2_dnd_automatic">Automatisk (s酶vnoppdagelse)</string>
+    <string name="mi2_dnd_scheduled">Planlagt (tidsperiode)</string>
+    <string name="discovery_attempting_to_pair">Pr酶ver 氓 parre med %1$s</string>
+    <string name="discovery_bonding_failed_immediately">Tilknytning til %1$s mislyktes umiddelbart.</string>
+    <string name="discovery_trying_to_connect_to">Pr酶ver 氓 koble til: %1$s</string>
+    <string name="discovery_enable_bluetooth">Skru p氓 Bl氓tann for 氓 oppdage enheter.</string>
+    <string name="discovery_successfully_bonded">Tilknyttet %1$s.</string>
+    <string name="discovery_pair_title">Parr med %1$s?</string>
+    <string name="discovery_pair_question">Velg \"Parr\" for 氓 parre enhetene dine. Hvis dette mislykkes, pr酶v igjen uten 氓 parre.</string>
+    <string name="discovery_yes_pair">Parr</string>
+    <string name="discovery_dont_pair">Ikke parr</string>
+    <string name="_pebble_watch_open_on_phone">脜pne p氓 Android-enhet</string>
+    <string name="_pebble_watch_mute">Forstum</string>
+    <string name="_pebble_watch_reply">Svar</string>
+    <string name="pref_title_weather">V忙r</string>
+    <string name="kind_firmware">Fastvare</string>
+    <string name="kind_invalid">Ugyldig data</string>
+    <string name="kind_font">Skrift</string>
+    <string name="kind_gps">GPS-fastvare</string>
+    <string name="kind_resources">Ressurser</string>
+    <string name="kind_watchface">Urskive</string>
+    <string name="devicetype_unknown">Ukjent enhet</string>
+    <string name="devicetype_test">Test enhet</string>
+    <string name="devicetype_pebble">Pebble</string>
+    <string name="devicetype_miband">Mi Band</string>
+    <string name="devicetype_miband2">Mi Band 2</string>
+    <string name="pref_title_charts_swipe">Skru p氓 venstre-/h酶yredragning i poengtavleaktiviteten</string>
+    <string name="pref_title_weather_location">V忙r-plassering (for LineageOS-v忙rtilbyder)</string>
+    <string name="pref_header_cannned_messages">Forh氓ndsutfylte meldinger</string>
+    <string name="pref_title_canned_reply_suffix">Vanlig endelse</string>
+    <string name="pref_title_canned_messages_dismisscall">Legg p氓 r酶ret</string>
+    <string name="pref_title_pebble_enable_applogs">Skru p氓 logging av klokkeprogram</string>
+    <string name="pref_summary_pebble_enable_applogs">Logger klokkeprogram i Gadgetbridge (krever omstart)</string>
+    <string name="pref_title_pebble_always_ack_pebblekit">Send ACK til PebbleKit</string>
+    <string name="pref_summary_pebble_always_ack_pebblekit">Meldinger sendt til eksterne tredjepartsprogrammer vil alltid bli anerkjent umiddelbart</string>
+    <string name="pref_title_screentime">Demp skjerm etter</string>
+    <string name="miband_prefs_device_time_offset_hours">Tidsjustering i timer (for oppdagelse av s酶vn for skiftarbeidere)</string>
+    <string name="mi2_prefs_button_press_broadcast_summary">Kringkastingsmelding sendt sammen med hendelsen. Parameter `button_id` legges til automatisk for hver melding.</string>
+    <string name="mi2_prefs_button_action">Skru p氓 knappehandling</string>
+    <string name="mi2_prefs_button_action_summary">Skru p氓 handling ved gitt antall knappetrykk</string>
+    <string name="mi2_prefs_button_action_vibrate">Skru p氓 b氓ndvibrasjon</string>
+    <string name="mi2_prefs_button_action_vibrate_summary">Skru p氓 knappetrykk ved knappetrykk</string>
+    <string name="mi2_prefs_button_press_count_max_delay">Maksimal forsinkelse mellom trykk</string>
+    <string name="mi2_prefs_button_press_count_max_delay_summary">Maksimal forsinkelse mellom knappetrykk i millisekunder</string>
+    <string name="mi2_prefs_activate_display_on_lift">Aktiver skjerm ved l酶fting</string>
+    <string name="activity_db_management_import_export_explanation">Eksport og import bruker f酶lgende sti (vist nedenfor) p氓 din enhet.
+\nDenne mappen er tilgjengelig for andre Android-programmer og din datamaskin.
+\nMerk at denne mappen og alle inneholdene filer slettes hvis du avinstallerer Gadgetbridge.
+\nDataen inkluderer:
+\nExport_preference - globale innstillinger
+\nExport_preference_MAC - enhetsspesifikke innstillinger
+\nGadgetbridge - enhets- og aktivitetsdatabase
+\nGadgetbride_date - database eksportert en gitt dato
+\n*.gpx - GPS-innspillinger
+\n*.log - loggfiler
+\nForvent 氓 finne dine eksporterte filer (eller plasser filene du 酶nsker 氓 importere) der:</string>
+    <string name="activity_db_management_merge_old_title">Sletting av foreldet database</string>
+    <string name="dbmanagementactivvity_cannot_access_export_path">F氓r ikke tilgang til eksportstien. Kontakt utviklerne.</string>
+    <string name="weather_notification_label">Forsikre deg om at denne drakten er if酶rt V忙rmeldingsprogrammet for 氓 f氓 varsel p氓 din Pebble.
+\n
+\nInget oppsett n酶dvendig.
+\n
+\nDu kan skru p氓 systemv忙rmeldingsprogrammet p氓 din Pebble fra programbehandling.
+\n
+\nSt酶ttede urskiver vil vise v忙r automatisk.</string>
+    <string name="activity_web_view">Web View-aktivitet</string>
+    <string name="kind_gps_almanac">GPS-almanakk</string>
+    <string name="kind_gps_cep">GPS-korrigering</string>
+    <string name="devicetype_amazfit_bip">Amazfit Bip</string>
+    <string name="devicetype_amazfit_cor">Amazfit Cor</string>
+    <string name="devicetype_vibratissimo">Vibratissimo</string>
+    <string name="devicetype_liveview">LiveView</string>
+    <string name="devicetype_hplus">HPlus</string>
+    <string name="devicetype_makibes_f68">Makibes F68</string>
+    <string name="devicetype_exrizu_k8">Exrizu K8</string>
+    <string name="devicetype_no1_f1">No.1 F1</string>
+    <string name="devicetype_teclast_h30">Teclast H30</string>
+    <string name="pref_header_auto_export">Automatisk eksport</string>
+    <string name="pref_title_auto_export_enabled">Automatisk eksport mislyktes</string>
+    <string name="pref_title_auto_export_location">Eksporteringsplassering</string>
+    <string name="pref_title_auto_export_interval">Eksporteringsintervall</string>
+    <string name="pref_summary_auto_export_interval">Eksporter hver %d time</string>
+    <string name="notif_export_failed_title">Eksport av database mislyktes. Sjekk innstillingene dine.</string>
+    <string name="choose_auto_export_location">Velg eksportplassering</string>
+    <string name="spanish">Spansk</string>
+    <string name="notification_channel_name">Generelt</string>
+    <string name="devicetype_xwatch">XWatch</string>
+    <string name="on">P氓</string>
+    <string name="controlcenter_start_activity_tracks">Dine aktivitetsspor</string>
+    <string name="activity_type_not_measured">Ikke m氓lt</string>
+    <string name="activity_type_activity">Aktivitet</string>
+    <string name="activity_type_light_sleep">Lett s酶vn</string>
+    <string name="activity_type_deep_sleep">Dyp s酶vn</string>
+    <string name="activity_type_not_worn">Enhet ikke b氓ret</string>
+    <string name="activity_type_running">Springing</string>
+    <string name="activity_type_walking">G氓ing</string>
+    <string name="activity_type_swimming">Sv酶mming</string>
+    <string name="activity_type_unknown">Ukjent aktivitet</string>
+    <string name="activity_summaries">Sportsaktiviteter</string>
+    <string name="activity_type_biking">Sykling</string>
+    <string name="activity_type_treadmill">Tredem酶lle</string>
+    <string name="select_all">Velg alle</string>
+    <string name="share">Del</string>
+    <string name="reset_index">Tilbakestill innhentingsdato</string>
+    <string name="menuitem_status">Status</string>
+    <string name="menuitem_activity">Treningshistorikk</string>
+    <string name="menuitem_weather">V忙r</string>
+    <string name="menuitem_alarm">Alarm</string>
+    <string name="menuitem_timer">Nedtelling</string>
+    <string name="menuitem_compass">Kompass</string>
+    <string name="menuitem_settings">Innstillinger</string>
+    <string name="menuitem_alipay">Alipay</string>
+    <string name="pref_title_pebble_gatt_clientonly">Kun GATT-klient</string>
+    <string name="pref_summary_pebble_gatt_clientonly">Dette er kun for Pebble 2 og eksperimentelt, pr酶v dette hvis du har tilkoblingsproblemer</string>
+    <string name="fw_upgrade_notice_miband3">Du er i ferd med 氓 installere %s-fastvaren p氓 din Mi Band 3.
+\n
+\nInstaller f酶rst .fw-fila, og dernest .res-fila. B氓ndet ditt vil starte p氓 nytt etter at du installerer .fw-fila.
+\n
+\nMerk: Du trenger ikke 氓 installere .res hvis du har akkurat den samme installert.
+\n
+\nFORTSETT P脜 EGEN RISIKO!</string>
+    <string name="devicetype_miband3">Mi Band 3</string>
+    <string name="devicetype_q8">Q8</string>
+    <string name="blacklist_all_for_notifications">Svartelist merknader fra alle disse</string>
+    <string name="whitelist_all_for_notifications">Hvitlist merknader fra alle disse</string>
+    <string name="pref_auto_fetch">Hent aktivitetsdata automatisk</string>
+    <string name="pref_auto_fetch_summary">Henting skjer ved skjermoppl氓sing, og fungerer kun hvis en l氓semekanisme er valgt!</string>
+    <string name="pref_auto_fetch_limit_fetches">Minimumstid mellom innhentinger</string>
+    <string name="pref_auto_fetch_limit_fetches_summary">Henter hvert %d minutt</string>
+    <string name="controlcenter_calibrate_device">Kalibrer enhet</string>
+    <string name="pref_title_notifications_timeout">Minimumstid mellom merknad</string>
+    <string name="prefs_screen_orientation">Skjermretning</string>
+    <string name="horizontal">Vannrett</string>
+    <string name="vertical">Loddrett</string>
+    <string name="watch9_pairing_tap_hint">N氓r din klokke vibrerer, rist enheten eller p氓 dens knapp.</string>
+    <string name="no_limit">Ingen grense</string>
+    <string name="seconds_5">5 sekunder</string>
+    <string name="seconds_10">10 sekunder</string>
+    <string name="seconds_20">20 sekunder</string>
+    <string name="seconds_30">30 sekunder</string>
+    <string name="minutes_1">1 minutt</string>
+    <string name="minutes_5">5 minutter</string>
+    <string name="minutes_10">10 minutter</string>
+    <string name="minutes_30">30 minutter</string>
+    <string name="you_slept">Du sov fra %1$s til %2$s</string>
+    <string name="you_did_not_sleep">Du sov ikke</string>
+    <string name="mi3_prefs_band_screen_unlock">Band-skjermoppl氓sing</string>
+    <string name="mi3_prefs_band_screen_unlock_summary">Dra oppover for 氓 l氓se opp band-ens skjerm</string>
+    <string name="mi3_prefs_night_mode">Nattmodus</string>
+    <string name="mi3_prefs_night_mode_summary">Senk band-lysstyrke automatisk om natten</string>
+    <string name="norwegian_bokmal">Norsk bokm氓l</string>
+    <string name="russian">Russisk</string>
+    <string name="german">Tysk</string>
+    <string name="italian">Italiensk</string>
+    <string name="french">Fransk</string>
+    <string name="polish">Polsk</string>
+    <string name="korean">Koreansk</string>
+    <string name="japanese">Japansk</string>
+    <string name="mi3_night_mode_sunset">Ved solnedgang</string>
+    <string name="devicetype_mykronoz_zetime">MyKronoz ZeTime</string>
+    <string name="devicetype_id115">ID115</string>
+    <string name="devicetype_watch9">Watch 9</string>
+    <string name="menuitem_notifications">Merknader</string>
+    <string name="menuitem_music">Musikk</string>
+    <string name="menuitem_more">Mer</string>
+    <string name="watch9_time_minutes">Minutter:</string>
+    <string name="watch9_time_hours">Timer:</string>
+    <string name="watch9_time_seconds">Sekunder:</string>
+    <string name="watch9_calibration_hint">Sett tiden din enhet viser deg akkurat n氓.</string>
+    <string name="watch9_calibration_button">Kalibrer</string>
+    <string name="title_activity_watch9_pairing">Watch 9-parring</string>
+    <string name="title_activity_watch9_calibration">Watch 9-kalibrering</string>
+    <string name="ok">OK</string>
+    <string name="share_log">Del logg</string>
+    <string name="share_log_warning">Ha i minnet at Gadgetbridge-loggfiler kan inneholde mye personlig info, inkludert, og ikke begrenset til helsedata, unike identifikatorer (som enhetens MAC-adresse), musikkpreferanser, osv. Overvei 氓 redigere filen for fjerning av denne info-en f酶r du sender filen til som offentlig feilrapport.</string>
+    <string name="warning">Advarsel!</string>
+    <string name="controlcenter_change_led_color">Endre LED-farge</string>
+    <string name="controlcenter_change_fm_frequency">Endre FM-frekvens</string>
+    <string name="devicetype_roidmi">Roidmi</string>
+    <string name="devicetype_roidmi3">Roidmi 3</string>
+    <string name="preferences_led_color">LED-farge</string>
+    <string name="preferences_fm_frequency">FM-frekvens</string>
+    <string name="pref_invalid_frequency_title">Ugyldig frekvens</string>
+    <string name="pref_invalid_frequency_message">Skriv inn en frekvens mellom 87.5 og 108.0</string>
+    <string name="pref_title_rtl">H酶yre-til-venstre</string>
+    <string name="pref_summary_rtl">Skru p氓 dette hvis din enhet ikke kan vise h酶yre til venstre-spr氓k</string>
+    <string name="pref_rtl_max_line_length">Maksimal linjelengde for h酶yre-til-venstre</string>
+    <string name="pref_rtl_max_line_length_summary">Strekker eller korter ned linjene h酶yre-til-venstre -tekst inndeles i</string>
+    <string name="notif_battery_low">%1$s batteri snart tomt</string>
+    <string name="notif_battery_low_extended">%1$s batteri snart tomt: %2$s</string>
+    <string name="lack_of_sleep">S酶vnmangel: %1$s</string>
+    <string name="overslept">Sovet for lenge: %1$s</string>
+    <string name="lack_of_step">Ikke nok steg: %1$d</string>
+    <string name="overstep">For mange steg: %1$d</string>
+    <string name="live_activity_max_heart_rate">N氓v忙rende / makspuls: %1$d /%2$d</string>
+    <string name="activity_prefs_charts">Poengtavleinnstillinger</string>
+    <string name="activity_prefs_chart_max_heart_rate">Makspuls</string>
+    <string name="activity_prefs_chart_min_heart_rate">Minimumspuls</string>
+    <string name="pref_title_contextual_arabic">Kontekstuell arabisk</string>
+    <string name="pref_summary_contextual_arabic">Skru p氓 dette for kontekstuell arabisk st酶tte</string>
+    <string name="preferences_rtl_settings">H酶yre-til-venstre -st酶tte</string>
+    <string name="no_data">Ingen data</string>
+    <string name="language_and_region_prefs">Spr氓k og stedsinnstillinger</string>
+    <string name="debugactivity_really_factoryreset_title">Vil du virkelig gjenopprette fabrikkinnstillinger\?</string>
+    <string name="debugactivity_really_factoryreset">Gjenoppretting til fabrikkinnstillinger vil slette all data fra den tilkoblede enheten (hvis st酶ttet). Xiaomi/Huami-enheter endrer ogs氓 MAC-adressen for Bl氓tann, og vil dermed framtre som nye enheter i Gadgetbridge.</string>
+    <string name="devicetype_casiogb6900">Casio GB-6900</string>
+    <string name="title_activity_notification_filter">Merknadsfilter</string>
+    <string name="edittext_notification_filter_words_hint">Skriv inn 酶nskede ord, ett ord per linje</string>
+    <string name="toast_notification_filter_saved_successfully">Merknadsfilter lagret</string>
+    <string name="filter_mode_none">Ikke filtrer</string>
+    <string name="filter_mode_whitelist">Vis n氓r ordene er 氓 finne p氓 hvitlisten</string>
+    <string name="filter_mode_blacklist">Blokker n氓r ordene er 氓 finne p氓 svartelisten</string>
+    <string name="filter_submode_at_least_one">Minst ett av ordene</string>
+    <string name="filter_submode_all">Hvert av ordene</string>
+    <string name="toast_notification_filter_words_empty_hint">Skriv inn minst ett ord</string>
+    <string name="filter_mode">Filtermodus</string>
+    <string name="mode_configuration">Modusoppsett</string>
+    <string name="save_configuration">Lagre oppsett</string>
+    <string name="appwidget_not_connected">Ikke tilkoblet, alarm ikke satt.</string>
+    <string name="activity_type_exercise">Trening</string>
+    <string name="prefs_disconnect_notification">Frakoblingsmerknad</string>
+    <string name="zetime_title_settings">ZeTime-innstilling</string>
+    <string name="zetime_analog_mode">Analogmodus</string>
+    <string name="zetime_analog_mode_hands">Kun visere</string>
+    <string name="zetime_activity_tracking">Aktivitetssporing</string>
+    <string name="zetime_handmove_display">H氓ndbevegelse</string>
+    <string name="zetime_calories_type">Kaloritype</string>
+    <string name="zetime_date_format">Datoformat</string>
+    <string name="zetime_date_format_1">脜脜/MM/DD</string>
+    <string name="zetime_date_format_2">DD/MM/脜脜</string>
+    <string name="zetime_date_format_3">MM/DD/脜脜</string>
+    <string name="zetime_prefs_inactivity_repetitions">Gjentagelser</string>
+    <string name="zetime_prefs_inactivity_mo">Mandag</string>
+    <string name="zetime_prefs_inactivity_tu">Tirsdag</string>
+    <string name="zetime_prefs_inactivity_we">Onsdag</string>
+    <string name="zetime_prefs_inactivity_th">Torsdag</string>
+    <string name="zetime_prefs_inactivity_fr">Fredag</string>
+    <string name="zetime_prefs_inactivity_sa">L酶rdag</string>
+    <string name="zetime_prefs_inactivity_su">S酶ndag</string>
+    <string name="zetime_title_alarm_signaling">Sett varslingstypen for alarmen</string>
+    <string name="zetime_signaling_none">Stille</string>
+    <string name="zetime_signaling_vibrate">Kontinuerlig vibrering</string>
+    <string name="zetime_signaling_beep">Kontinuerlig piping</string>
+    <string name="zetime_signaling_vibrate_beep">Kontinuerlig vibrering og piping</string>
+    <string name="zetime_signaling_vibrate_once">Vibrer 茅n gang</string>
+    <string name="zetime_signaling_vibrate_twice">Vibrer to ganger</string>
+    <string name="zetime_signaling_beep_once">Pip 茅n gang</string>
+    <string name="zetime_signaling_beep_twice">Pip To ganger</string>
+    <string name="zetime_signaling_vibrate_beep_once">Vibrer og pip 茅n gang</string>
+    <string name="pref_screen_notification_profile_missed_call">Varsel om tapt anrop</string>
+    <string name="pref_screen_notification_profile_calendar">Kalendermerknad</string>
+    <string name="pref_screen_notification_profile_inactivity">Inaktivitetsmerknad</string>
+    <string name="pref_screen_notification_profile_low_power">Advarsel om lavt batteriniv氓</string>
+    <string name="interval_fifteen_minutes">hvert kvarter</string>
+    <string name="interval_forty_five_minutes">hvert 45te minutt</string>
+    <string name="activity_prefs_calories_burnt">Daglig m氓l: Forbrente kalorier</string>
+    <string name="activity_prefs_distance_meters">Daglig m氓l: Distanse i meter</string>
+    <string name="activity_prefs_activetime_minutes">Daglig m氓l: Aktiv tid i minutter</string>
+    <string name="devicetype_miscale2">Mi Scale 2</string>
+    <string name="zetime_title_heartrate">Pulsinnstillinger</string>
+    <string name="zetime_title_screentime">Skjermaktivitetsvarighet, i sekunder</string>
+    <string name="zetime_title_heart_rate_alarm">Pulsalarm</string>
+    <string name="zetime_title_heart_rate_alarm_summary">Klokken vil advare deg n氓r pulsen overskrider grensene.</string>
+    <string name="zetime_heart_rate_alarm_enable">Aktiver pulsalarmen</string>
+    <string name="activity_prefs_alarm_max_heart_rate">Makspuls</string>
+    <string name="activity_prefs_alarm_min_heart_rate">Minimumspuls</string>
+    <string name="zetime_analog_mode_handsandsteps">Hender og trinn</string>
+    <string name="zetime_activity_tracking_summary">P氓slag av aktivitetssporing vil telle skrittene dine, osv.</string>
+    <string name="zetime_handmove_display_summary">Vri h氓ndleddet for 氓 skru p氓 eller av skjermen.</string>
+    <string name="zetime_calories_type_active">Kun aktivt forbrente kalorier</string>
+    <string name="zetime_calories_type_all">Aktivt og inaktivt -forbrente kalorier</string>
+    <string name="pref_screen_notification_profile_anti_loss">Advarsel om tap</string>
+    <string name="pref_title_support_voip_calls">Skru p氓 VoIP-programsamtaler</string>
+    <string name="title_activity_device_specific_settings">Enhetsspesifikke innstillinger</string>
+    <string name="pref_title_authkey">Identitetsbekreftelsesn酶kkel</string>
+    <string name="pref_summary_authkey">Endre identitetsbekreftelsesn酶kkelen til en vanlig n酶kkel p氓 alle Android-enhetene du 酶nsker 氓 koble til fra. Forrige forvalgte n酶kkel for alle enheter var 0123456789@ABCDE</string>
+    <string name="devicetype_bfh16">BFH-16</string>
+    <string name="fw_upgrade_notice_amazfitcor2">Du er i ferd med 氓 installere %s-fastvaren p氓 din Amazfit Cor 2.
+\n
+\nInstaller .fw-filen, og deretter .res-filen. Ditt b氓nd vil starte p氓 ny etter installering av .fw-filen.
+\n
+\nMerk: Du trenger ikke 氓 installere .res hvis denne er den samme versjon som den som finnes p氓 b氓ndet.
+\n
+\nFORTSETT P脜 EGEN RISIKO!
+\n
+\nHELT UTESTET, DU M脜 ANTAGELIG BRENNE INN FLASH A BEATS_W -FASTVARE HVIS ENHETSNAVNET DITT ER \"Amazfit Band 2\"</string>
+    <string name="dutch">Hollandsk</string>
+    <string name="turkish">Tyrkisk</string>
+    <string name="ukrainian">Ukrainsk</string>
+    <string name="arabic">Arabisk</string>
+    <string name="indonesian">Indonesisk</string>
+    <string name="thai">Thai</string>
+    <string name="vietnamese">Vietnamesisk</string>
+    <string name="portuguese">Portugisisk</string>
+    <string name="devicetype_amazfit_cor2">Amazfit Cor 2</string>
+    <string name="devicetype_miband4">Mi Band 4</string>
+    <string name="fw_upgrade_notice_miband4">Du er i ferd med 氓 installere %s-fastvaren p氓 ditt Mi Band 4.
+\n
+\nForsikre deg om at du installerer .fw-filen, og deretter .res-filen. B氓ndet vil startes p氓 ny etter installasjon av .fw-filen.
+\n
+\nMerk: Du trenger ikke 氓 installere .res-filen hvis den er samme som du hadde installert fra f酶r.
+\n
+\nFORTSETT P脜 EGEN RISIKO!</string>
+    <string name="prefs_hr_alarm_activity">Pulsalarm under sportsaktiviteter</string>
+    <string name="prefs_hr_alarm_low">Nedre grense</string>
+    <string name="prefs_hr_alarm_high">脴vre grense</string>
+    <string name="average">Gjennomsnitt: %1$s</string>
+    <string name="pref_header_charts">Poengtavleinnstillinger</string>
+    <string name="pref_title_charts_average">Vis gjennomsnitt i poengtavlene</string>
+    <string name="pref_title_charts_range">Poengtavleomr氓de</string>
+    <string name="pref_charts_range_on">Poengtavleomr氓de satt til 茅n m氓ned</string>
+    <string name="pref_charts_range_off">Poengtavleomr氓de satt til ei uke</string>
+    <string name="weekstepschart_steps_a_month">Steg per m氓ned</string>
+    <string name="weeksleepchart_sleep_a_month">S酶vn per m氓ned</string>
+    <string name="devicetype_mijia_lywsd02">Mijia-smartklokke</string>
+    <string name="menuitem_nfc">NFC</string>
+    <string name="pref_summary_expose_hr">Tillater andre programmer tilgang til HR-data i sanntid mens Gadgetbridge er tilkoblet</string>
+    <string name="pref_title_expose_hr">Tredjepartssanntidstilgang til HR</string>
+    <string name="pref_title_use_custom_font">Bruk egendefinert skrift</string>
+    <string name="pref_summary_use_custom_font">Skru p氓 dette hvis din enhet har egendefinert skrift-fastvare for emoji-st酶tte</string>
+    <string name="activity_db_management_autoexport_explanation">Automatisk eksportplassering for database satt til:</string>
+    <string name="activity_db_management_autoexport_label">Automatisk eksport</string>
+    <string name="activity_DB_ExportButton">Eksporter data</string>
+    <string name="activity_DB_import_button">Importer data</string>
+    <string name="activity_DB_test_export_button">Kj酶r automatisk eksport n氓</string>
+    <string name="activity_DB_delete_legacy_button">Slett gammel database</string>
+    <string name="activity_DB_empty_button">T酶m database</string>
+    <string name="activity_db_management_empty_DB">T酶m database</string>
+    <string name="activity_db_management_exportimport_label">Eksport og import</string>
+    <string name="activity_db_management_empty_db_warning">Advarsel! Ved 氓 trykke p氓 denne knappen vil du t酶mme databasen og begynne med ingenting.</string>
+    <string name="appwidget_sleep_alarm_widget_label">S酶vnalarm</string>
+    <string name="widget_listing_label">Status og alarmer</string>
+    <string name="widget_set_alarm_after">Sett alarm etter:</string>
+    <string name="widget_5_minutes">Fem minutter</string>
+    <string name="widget_10_minutes">Ti minutter</string>
+    <string name="widget_20_minutes">20 minutter</string>
+    <string name="widget_1_hour">脡n time</string>
+    <string name="activity_DB_test_export_message">Eksporterer database鈥�</string>
+    <string name="pref_display_add_device_fab">\"Koble til ny enhet\"-knapp</string>
+    <string name="pref_display_add_device_fab_on">Alltid synlig</string>
+    <string name="pref_display_add_device_fab_off">Kun synlig hvis ingen enhet er lagt til</string>
+    <plurals name="widget_alarm_target_hours">
+        <item quantity="one">%d time</item>
+        <item quantity="other">%d timer</item>
+    </plurals>
+    <string name="activity_error_no_app_for_gpx">For 氓 vise aktivitetsspor, installer et program som kan h氓ndtere GPX-filer.</string>
+    <string name="preferences_makibes_hr3_settings">Makibes HR3-innstillinger</string>
+    <string name="devicetype_makibes_hr3">Makibes HR3</string>
+    <string name="devicetype_amazfit_bip_lite">Amazfit Bip Lite</string>
+    <string name="prefs_find_phone">Finn telefon</string>
+    <string name="prefs_enable_find_phone">Skru p氓 芦Finn telefon禄</string>
+    <string name="prefs_find_phone_summary">Bruk b氓ndet ditt til 氓 spille av telefonens ringetone.</string>
+    <string name="prefs_find_phone_duration">Anropsvarighet i sekunder</string>
+    <string name="maximum_duration">Varighet</string>
+    <string name="discovery_need_to_enter_authkey">Denne enheten trenger en hemmelig identitetsbekreftelsesn酶kkel. Trykk lenge p氓 enheten for 氓 skrive den inn. Les wiki-en.</string>
+    <string name="fw_upgrade_notice_amazfitbip_lite">Du er i ferd med 氓 installere %s-fastvaren p氓 din Amazfit Bip Lite.
+\n
+\nForsikre deg om at du installerer .fw-fila, s氓 .res-fila, og til sist gps-fila. Armb氓ndsuret ditt vil starte p氓 nytt etter at .fw-fila er installert.
+\n
+\nMerk: Du trenger ikke 氓 installere .res-filen hvis den er akkurat den samme som den som allerede er installert.
+\n
+\nFORTSETT P脜 EGEN RISIKO!</string>
+    <string name="devicetype_amazfit_gtr">Amazfit GTR</string>
+    <string name="pref_chart_heartrate_color_red">R酶d</string>
+    <string name="pref_chart_heartrate_color_orange">Oransje</string>
+    <string name="pref_title_chart_heartrate_color">Pulsfarge</string>
+    <string name="fw_upgrade_notice_amazfitgtr">Du er i ferd med 氓 installere %s-fastvaren p氓 din Amazfit GTR.
+\n
+\nForsikre deg om at du installerer .fw-fila, s氓 .res-fila, og til sist gps-fila. Armb氓ndsuret ditt vil starte p氓 nytt etter at .fw-fila er installert.
+\n
+\nMerk: Du trenger ikke 氓 installere .res-filen hvis den er akkurat den samme som den som allerede er installert.
+\n
+\nFORTSETT P脜 EGEN RISIKO!</string>
+    <string name="pref_title_chart_sleep_rolling_24_hour">S酶vnperiode</string>
+    <string name="pref_chart_sleep_rolling_24_on">Siste 24 timer</string>
+    <string name="pref_chart_sleep_rolling_24_off">12:00 til 12:00</string>
+    <string name="devicetype_amazfit_gts">Amazfit GTS</string>
+    <string name="fw_upgrade_notice_amazfitgts">Du er i ferd med 氓 installere %s-fastvaren p氓 din Amazfit GTS.
+\n
+\nForsikre deg om at du installerer .fw-fila, s氓 .res-fila, og til sist gps-fila. Armb氓ndsuret ditt vil starte p氓 nytt etter at .fw-fila er installert.
+\n
+\nMerk: Du m氓 ikke installere .res og .gps hvis disse er akkurat de samme som dem som allerede er installert.
+\n
+\nFORTSETT P脜 EGEN RISIKO!</string>
+    <string name="mi2_prefs_button_press_broadcast_default_value" translatable="false">nodomain.freeyourgadget.gadgetbridge.ButtonPressed</string>
+    <string name="watch_not_connected">Klokke ikke tilkoblet</string>
+    <string name="qhybrid_vibration_strength">vibrasjonsstyrke:</string>
+    <string name="devicetype_banglejs">Bangle.js</string>
+    <string name="devicetype_qhybrid">Fossil Q Hybrid</string>
+    <string name="preferences_qhybrid_settings">Q Hybrid-innstillinger</string>
+    <string name="qhybrid_overwrite_buttons">overskriv taster</string>
+    <string name="qhybrid_buttons_overwrite_success">Knapper overskrevet</string>
+    <string name="qhybrid_buttons_overwrite_error">Kunne ikke overskrive knapper</string>
+    <string name="qhybrid_goal_in_steps">Stegm氓l</string>
+    <string name="qhybrid_time_shift">tidsforskyvning</string>
+    <string name="qhybrid_second_timezone_offset_relative_to_utc">antall sekunders tidssoneforskyvelse sett i forhold til UTC</string>
+    <string name="qhybrid_use_activity_hand_as_notification_counter">bruk aktivitetsviser som merknadsteller</string>
+    <string name="qhybrid_prompt_million_steps">Sett stegtelleren til 茅n million for 氓 aktivere dette.</string>
+    <string name="qhybrid_offset_timezone">forskyv tidssone med</string>
+    <string name="qhybrid_changes_delay_prompt">det kan ta en stund 氓 utf酶re endringer鈥�</string>
+    <string name="qhybrid_offset_time_by">forskyv tid med</string>
+    <string name="pref_disable_new_ble_scanning">Skru av ny BLE-skanning</string>
+    <string name="pref_summary_disable_new_ble_scanning">Velg dette hvis enheten du ikke finner enheten din under oppdagelse</string>
+    <string name="devicetype_y5">Y5</string>
+    <string name="prefs_button_single_press_action_selection_title">Handling for hendelse 1</string>
+    <string name="prefs_button_double_press_action_selection_title">Handling for hendelse 2</string>
+    <string name="prefs_button_triple_press_action_selection_title">Handling for hendelse 3</string>
+    <string name="prefs_button_variable_actions">Detaljerte knappetrykksinnstillinger</string>
+    <string name="prefs_button_long_press_action_selection_title">Handling for langt knappetrykk</string>
+    <string name="alarm_snooze">Slumre</string>
+    <string name="error_no_location_access">Plasseringstilgang m氓 innvilges og skrus p氓 for at skanning skal fungere</string>
+    <string name="devicetype_itag">iTag</string>
+    <string name="pref_title_allow_high_mtu">Tillat h酶y MTU</string>
+    <string name="pref_summary_allow_high_mtu">脴ker overf酶ringshastighet, men kan for氓rsake problemer p氓 noen Android-enheter.</string>
+    <string name="pref_summary_sync_calendar">Skru p氓 kalendervarsler, selv n氓r frakoblet.</string>
+    <string name="pref_title_sync_caldendar">Synkroniser kalenderhendelser</string>
+    <string name="hr_widget_heart_rate">Puls</string>
+    <string name="hr_widget_steps">Steg</string>
+    <string name="hr_widget_date">Dato</string>
+    <string name="hr_widget_active_minutes">Aktive minutter</string>
+    <string name="hr_widget_calories">Kalorier</string>
+    <string name="hr_widget_battery">Batteri</string>
+    <string name="hr_widget_weather">V忙r</string>
+    <string name="hr_widget_nothing">Ingenting</string>
+    <string name="devicetype_amazfit_bips">Amazfit Bip S</string>
+    <string name="hr_appname_wellness">Velv忙re</string>
+    <string name="hr_appname_workout">Trenings酶kt</string>
+    <string name="pref_title_custom_deviceicon">Vis enhetsspesifikt merknadsikon</string>
+    <string name="hr_appname_stopwatch">Stoppeklokke</string>
+    <string name="find_lost_device_you_found_it">Fant den!</string>
+    <string name="pref_title_vibration_strength">Vibrasjonsstyrke</string>
+    <string name="hr_appname_commute">Pendle</string>
+    <string name="notification_channel_high_priority_name">H酶y prioritet</string>
+    <string name="pref_summary_force_white_color_scheme">Nyttig hvis klokken din har m酶rke hender</string>
+    <string name="pref_title_force_white_color_scheme">Tving svart p氓 hvitt fargevalg</string>
+    <string name="find_my_phone_notification">Finn min telefon</string>
+    <string name="pref_title_relax_firmware_checks">Aktiver dette hvis du vil blinke en fastvare som ikke er beregnet p氓 enheten din (p氓 egen risiko)</string>
+    <string name="pref_summary_relax_firmware_checks">Slapp av firmwarekontroller</string>
+    <string name="pref_summary_custom_deviceicon">Vis et enhetsspesifikt Android-varslingsikon i stedet for Gadgetbridge-ikonet n氓r du er tilkoblet</string>
+    <string name="pref_qhybrid_title_widget_draw_circles">Tegn miniprogramssirkler</string>
+    <string name="pref_header_auto_fetch">Hent automatisk</string>
+    <string name="menuitem_eventreminder">Hendelsesp氓minnelse</string>
+    <string name="menuitem_hr">Puls</string>
+    <string name="pref_qhybrid_save_raw_activity_files">Lagre r氓filer fra aktivitet</string>
+    <string name="prefs_sensors_button_bp_calibration_sum">Trykk her for 氓 starte kalibrering</string>
+    <string name="prefs_sensors_button_bp_calibration">Kalibrering</string>
+    <string name="pref_header_device_spec_settings">Enhetsinnstillinger</string>
+    <string name="prefs_notifications_and_calls_reject">Knapp for 氓 ignorere/avsl氓</string>
+    <string name="prefs_notifications_and_calls_shake_reject">Rist h氓ndledd ignorer/avsl氓 samtale</string>
+    <string name="pref_summary_notifications_and_calls_title_reject">Av - ignorer, P氓 - avsl氓</string>
+    <string name="menuitem_pai">Personlig aktivitetssporing</string>
+    <string name="pref_title_sensors_altitude">H酶ydekalibrering</string>
+    <string name="title_activity_LenovoWatch_calibration">Watch X Plus-kalibrering</string>
+    <string name="pref_header_sensors_calibration">Sensorkalibrering</string>
+    <string name="power_mode_saving">Batterisparing</string>
+    <string name="power_mode_normal">Normalt</string>
+    <string name="power_mode_title">Klokkeeffektmodus</string>
+    <string name="devicetype_watchx">Watch X</string>
+    <string name="devicetype_watchxplus">Watch X Plus</string>
+    <string name="power_mode_watch">Kun klokke</string>
+    <string name="menuitem_workout">Trenings酶kt</string>
+    <string name="pref_sensors_bp_calibration_high">Blodtrykk SYSTOLISK (h酶yt)</string>
+    <string name="pref_sensors_bp_calibration_low">Blodtrykk DIASTOLISK (lavt)</string>
+    <string name="pref_sensors_bp_calibration">Blodtrykkskalibrering</string>
+    <string name="pref_header_notifications_and_calls">Merknader og samtaler</string>
+    <string name="pref_theme_system">System</string>
+    <string name="bip_prefs_shortcuts">Snarveier</string>
+    <string name="czesh">Tsjekkisk</string>
+    <string name="swedish">Svensk</string>
+    <string name="greek">Gresk</string>
+    <string name="hungarian">Ungarsk</string>
+    <string name="romanian">Rumensk</string>
+    <string name="menuitem_unknown">Ukjent</string>
+    <string name="fw_upgrade_notice_amazfit_trex">Du er i ferd med 氓 installere %s-fastvaren p氓 din Amazfit T-Rex.
+\n
+\nForsikre deg om at du installerer .fw-fila, s氓 .res-fila, og til sist gps-fila. Armb氓ndsuret ditt vil starte p氓 nytt etter at .fw-fila er installert.
+\n
+\nMerk: Du m氓 ikke installere .res og .gps hvis disse er akkurat de samme som dem som allerede er installert.
+\n
+\nFORTSETT P脜 EGEN RISIKO!</string>
+    <string name="hr_widget_last_notification">Siste merknad</string>
+    <string name="error_background_service">Klarte ikke 氓 starte bakgrunnstjeneste</string>
+    <string name="about_additional_device_support">Ytterligere enhetsst酶tte</string>
+    <string name="about_contributors">Bidragsytere</string>
+    <string name="about_activity_title_generic">Om Gadgetbridge</string>
+    <string name="about_title">Om</string>
+    <string name="menuitem_worldclock">Verdensklokke</string>
+    <string name="menuitem_stress">Stress</string>
+    <string name="menuitem_cycles">F酶lg m.-syklus</string>
+    <string name="menuitem_breathing">Pusting</string>
+    <string name="devicetype_miband5">Mi Band 5</string>
+    <string name="ascentSeconds">Stigende</string>
+    <string name="descentSeconds">Fall</string>
+    <string name="Activity">Aktivitet</string>
+    <string name="Speed">Hastighet</string>
+    <string name="Distance">Distanse</string>
+    <string name="Swimming">Sv酶mming</string>
+    <string name="Strokes">Tak</string>
+    <string name="minutes_km">min/km</string>
+    <string name="seconds_km">sek/km</string>
+    <string name="calories_unit">kcal</string>
+    <string name="activeSeconds">Aktiv</string>
+    <string name="devicetype_amazfit_trex">Amazfit T-Rex</string>
+    <string name="controlcenter_set_alias">Sett alias</string>
+    <string name="laps_unit">runder</string>
+    <string name="swim_style">disiplin</string>
+    <string name="seconds">sek</string>
+    <string name="strokes_unit">tak</string>
+    <string name="strokes_second">tak/s</string>
+    <string name="km_h">km/t</string>
+    <string name="meters_second">m/s</string>
+    <string name="steps_unit">steg</string>
+    <string name="cm">cm</string>
+    <string name="meters">m</string>
+    <string name="flatSeconds">Flatt</string>
+    <string name="laps">Runder</string>
+    <string name="averageLapPace">Gjennomsnittlig rundetid</string>
+    <string name="averageStrokesPerSecond">Gjennonsnittstak</string>
+    <string name="averageStrokeDistance">Gjennomsnittlig taklengde</string>
+    <string name="averageStride">Gjennomsnittlig steglengde</string>
+    <string name="averageKMPaceSeconds">Fart</string>
+    <string name="averageHR">Puls</string>
+    <string name="totalStride">Total steglengde</string>
+    <string name="maxPace">Raskeste fart</string>
+    <string name="minPace">Tregeste fart</string>
+    <string name="maxSpeed">Maksimum</string>
+    <string name="caloriesBurnt">Kalorier</string>
+    <string name="steps">Steg</string>
+    <string name="minAltitude">Minimum</string>
+    <string name="maxAltitude">Maksimalt</string>
+    <string name="descentMeters">Nedoverbakke</string>
+    <string name="ascentMeters">Oppoverbakke</string>
+    <string name="distanceMeters">Distanse</string>
+    <string name="activity_detail_show_gps_label">Vis GPS-spor</string>
+    <string name="activity_detail_duration_label">Varighet</string>
+    <string name="activity_detail_end_label">Slutt</string>
+    <string name="activity_detail_start_label">Start</string>
+    <string name="Steps">Steg</string>
+    <string name="activity_type_yoga">Yoga</string>
+    <string name="bip_prefs_shotcuts_summary">Velg snarveiene p氓 b氓nd-skjermen</string>
+    <string name="fw_upgrade_notice_miband5">Du er i ferd med 氓 installere %s-fastvaren p氓 ditt Mi Band 5.
+\n
+\nForsikre deg om at du installerer .fw-fila, s氓 .res-fila. B氓ndet ditt vil starte p氓 nytt etter at .fw-fila er installert.
+\n
+\nMerk: Du trenger ikke 氓 installere .res-filen hvis den er akkurat den samme som den som allerede er installert.
+\n
+\nFORTSETT P脜 EGEN RISIKO!</string>
+    <string name="Elevation">H酶yde</string>
+    <string name="prefs_events_forwarding_broadcast_title">Kringkast melding</string>
+    <string name="activity_type_swimming_openwater">Sv酶mming (i 氓pent vann)</string>
+    <string name="hebrew">Hebraisk</string>
+    <string name="pref_header_notifications_and_calls_callhandling">Anropsh氓ndtering</string>
+    <string name="pref_title_notifications_and_calls_repeat_on_misscall">Gjenta i X minutter</string>
+    <string name="activity_type_elliptical_trainer">Ellipsemaskin</string>
+    <string name="pref_header_statistics">Sportsaktivitetsstatistikk</string>
+    <string name="pref_header_filter">Sportsaktivitetsfilter</string>
+    <string name="prefs_events_forwarding_title">Enhetshandlinger</string>
+    <string name="km">km</string>
+    <string name="seconds_m">sek/m</string>
+    <string name="device_requires_key">N脴KKEL KREVES</string>
+    <string name="permission_granting_mandatory">Alle disse tilgangene er p氓krevd og ustabilitet kan oppst氓 hvis de ikke innvilges</string>
+    <string name="about_additional_contributions">Mange takk til alle ulistede bidragsytere for kode, oversettelser, st酶tte, id茅er, motivasjon, feilrapporter, penger鈥� 鉁�</string>
+    <string name="about_core_team_title">Kjernelag (sortert etter f酶rste kodebidrag)</string>
+    <string name="devicetype_pinetime_jf">PineTime (JF-fastvare)</string>
+    <string name="activity_summary_detail">Sportaktivitetsdetalj</string>
+    <string name="activity_type_jump_roping">Hoppetau</string>
+    <string name="activity_type_indoor_cycling">Innend酶rssykling</string>
+    <string name="devicetype_tlw64">TLW64</string>
+    <string name="activity_summary_yesterday">I g氓r</string>
+    <string name="activity_summary_today">I dag</string>
+    <string name="activity_summary_edit_name_title">Rediger etikett</string>
+    <string name="activity_filter_reset_filter">Tilbakestill filter</string>
+    <string name="activity_filter_date_to">Til</string>
+    <string name="activity_filter_date_from">Fra</string>
+    <string name="activity_filter_name_contains">Etikett</string>
+    <string name="activity_filter_filter_title">Filter</string>
+    <string name="breaststroke">Bryst</string>
+    <string name="backstroke">Rygg</string>
+    <string name="freestyle">Kr氓l</string>
+    <string name="medley">Medley</string>
+    <string name="activity_summaries_all_activities">Alle aktiviteter</string>
+    <string name="activity_filter_apply_filter">Legg til filter</string>
+    <string name="activity_summaries_statistics">Statistikk</string>
+    <string name="pref_check_permission_status">Sjekk tilgangsstatus</string>
+    <string name="error_background_service_reason_truncated">Kunne ikke starte bakgrunnstjeneste fordi 鈥�</string>
+    <string name="activity_type_badminton">Badminton</string>
+    <string name="activity_type_pingpong">Bordtennis</string>
+    <string name="activity_type_basketball">Basketball</string>
+    <string name="activity_type_cricket">Cricket</string>
+    <string name="activity_type_rowing_machine">Ro-maskin</string>
+    <string name="activity_type_soccer">Fotball</string>
+    <string name="sports_activity_quick_filter_last_week">Forrige uke</string>
+    <string name="sports_activity_quick_filter_this_week">Denne uken</string>
+    <string name="prefs_events_forwarding_summary">Bruk enhetshendelser for 氓 utl酶se handlinger og Android-kunngj酶ringer</string>
+    <string name="bpm">spm</string>
+    <string name="averageSpeed">Gjennomsnittshastighet</string>
+    <string name="sports_activity_quick_filter_30days">30 dager</string>
+    <string name="sports_activity_quick_filter_7days">7 dager</string>
+    <string name="sports_activity_quick_filter_last_month">Forrige m氓ned</string>
+    <string name="sports_activity_quick_filter_this_month">Denne m氓neden</string>
+    <string name="activity_filter_from_placeholder">uoversiktlig fortid</string>
+    <string name="sports_activity_quick_filter_select">Tidsperiode</string>
+    <string name="activity_summaries_all_devices">Alle enheter</string>
+    <string name="activity_filter_to_placeholder">i dag</string>
+    <string name="about_links">Lenker</string>
+    <string name="lefun_prefs_hydration_reminder_interval_title">Drikkep氓minnelseintervall (i minutter)</string>
+    <string name="lefun_prefs_interface_language_title">Grensesnittspr氓k</string>
+    <string name="pref_title_weather_summary">Brukt av LineageOS-v忙rtilbyderen, andre Android-versjoner m氓 bruke et program som 芦Ditt lokale v忙r禄. Mer info er 氓 finne p氓 Gadgetbridge-wiki-en.</string>
+    <string name="devicetype_sonyswr12">Sony SWR12</string>
+    <string name="sonyswr12_settings_title">Sony SWR12 innstillinger</string>
+    <string name="sonyswr12_settings_low_vibration">Lav vibrasjon aktivert</string>
+    <string name="sonyswr12_settings_stamina">Str酶msparingsmodus er p氓</string>
+    <string name="sonyswr12_settings_alarm_interval">Smart alarmintervall p氓 f氓 minutter</string>
+    <string name="sonyswr12_settings_low_vibration_summary">Aktiver lav vibrasjonsintensitet p氓 b氓ndet</string>
+    <string name="sonyswr12_settings_stamina_summary">Str酶msparingsmodus sl氓r av periodisk automatisk m氓ling av hjertefrekvensen og 酶ker dermed arbeidstiden</string>
+    <string name="sonyswr12_settings_alarm_interval_summary">Smart alarmintervall er intervall f酶r installert alarm. I dette intervallet pr酶ver enheten 氓 oppdage den letteste s酶vnfasen for 氓 v氓kne brukeren</string>
+    <string name="about_description_generic">Skyfri, gemenhetslig fri erstatning for den lukkede motsatsen for dine Android-slaveenheter.</string>
+    <string name="pref_check_permission_status_summary">Sjekk og foresp酶r tilganger selv om de ikke trengs umiddelbart. Skru av dette kun hvis enhetene dine ikke st酶tter noen av disse funksjonene. 脜 ikke innvilge tilganger kan for氓rsake problemer!</string>
+    <string name="companiondevice_pairing_details">Skrur p氓 slaveenhets-API (funker kun p氓 Android 8 og h酶yere, og tilknytningen m氓 settes opp p氓 ny med Gadgetbro), noe som 酶ker p氓liteligheten hvis tjenester m氓 startes p氓 ny i bakgrunnen</string>
+    <string name="error_version_check_extreme_caution">Advarsel: Kunne ikke sjekke versjonsinfo! Du b酶r ikke fortsette og versjonsnavnet 芦%s禄 ble sett.</string>
+    <string name="companiondevice_pairing">Tilknytning av slaveenhet</string>
+    <string name="error_exporting_device_preferences">Klarte ikke 氓 eksportere enhetsspesifikke innstillinger</string>
+    <string name="error_setting_alias">Klarte ikke 氓 sette alias:</string>
+    <string name="error_retrieving_devices_database">Klarte ikke 氓 hente enheter fra database</string>
+    <string name="swimStyle">Sv酶mmestil</string>
+    <string name="swolfIndex">SWOLF-indeks</string>
+    <string name="error_location_enabled_mandatory">Posisjon m氓 v忙re p氓sl氓tt for 氓 se etter enheter</string>
+    <string name="baseAltitude">Starth酶yde</string>
+    <string name="swolf_index">SWOLF-indeks</string>
+    <string name="prefs_events_forwarding_fellsleep">Ved dvalgang</string>
+    <string name="devicetype_nut_mini">Nut mini</string>
+    <string name="addto_filter">Legg til i filter</string>
+    <string name="prefs_events_forwarding_action_title">Kj酶r handling</string>
+    <string name="prefs_events_forwarding_startnonwear">N氓r ikke ikledt</string>
+    <string name="prefs_events_forwarding_wokeup">Ved oppv氓kning</string>
+    <string name="devicetype_lefun">LeFun</string>
+    <string name="activity_error_no_app_for_png">For 氓 dele denne skjermavbildningen m氓 du installere et program som kan 氓pne bildefiler.</string>
+    <string name="activity_prefs_chart_min_session_length">Minste aktivitetslengde (minutter)</string>
+    <string name="lefun_prefs_antilost_title">Tapsforhindring</string>
+    <string name="activity_filter_individual_items">Individuelt valgte elementer</string>
+    <string name="no">Nei</string>
+    <string name="yes">Ja</string>
+    <string name="error_background_service_reason">Kunne ikke starte bakgrunnstjenesten:</string>
+    <string name="require_location_provider">Posisjon m氓 skrus p氓</string>
+    <string name="ignore_bonded_devices_description">Ignorerer enheter ved skanning som allerede har blitt tilknyttet/parret</string>
+    <string name="ignore_bonded_devices">Ignorer tilknyttede enheter</string>
+    <string name="device_is_currently_bonded">ALLEREDE TILKNYTTET</string>
+    <string name="devicetype_sg2">LEMFO SG2</string>
+    <string name="activity_prefs_chart_max_idle_phase_length">Pauselengde for inndeling av aktiviteter (minutter)</string>
+    <string name="activity_prefs_chart_min_steps_per_minute">Minste antall steg per minutt f酶r aktivitet registreres</string>
+    <string name="activity_prefs_chart_min_steps_per_minute_for_run">Laveste antall steg f酶r l酶ping registreres</string>
+    <string name="lefun_prefs_antilost_summary">B氓ndet vil vibrere hvis telefonen mister tilkoblingen til b氓ndet</string>
+    <string name="lefun_prefs_hydration_reminder_summary">B氓ndet vil vibrere for 氓 minne deg p氓 氓 drikke vann</string>
+    <string name="lefun_prefs_hydration_reminder_title">Drikkep氓minnelse</string>
+    <string name="firmware_update_progress">Opplasting underveis
+\n%1d%% i %.2fkbps (gjennomsnitt %.2fkbps)
+\nDel %1d av %1d</string>
+    <string name="devicestatus_upload_failed">Opplastingen mislyktes</string>
+    <string name="devicestatus_upload_aborted">Opplasting avbrutt!</string>
+    <string name="devicestatus_upload_validating">Opplastingen bekreftes</string>
+    <string name="devicestatus_upload_completed">Opplasting fullf酶rt</string>
+    <string name="devicestatus_disconnected">Enhet frakoblet.</string>
+    <string name="devicestatus_disconnecting">Enhet kobler fra!</string>
+    <string name="devicestatus_upload_started">Opplasting startet</string>
+    <string name="devicestatus_upload_starting">Opplasting starter</string>
+    <string name="devicestatus_connecting">Enheten kobler til</string>
+    <string name="devicestatus_connected">Enhet tilkoblet</string>
+    <string name="charts_activity_list">Aktivitetsliste</string>
+    <string name="chart_get_active_and_synchronize">Utf酶r litt aktivitet og synkroniser enhet.</string>
+    <string name="chart_no_active_data">Ingen nylige aktiviteter.</string>
+    <string name="pref_title_device_spec_settings_show_raw_graph">Vis r氓-data p氓 aktivitetsdiagram</string>
+    <string name="pref_summary_device_spec_settings_title_force_time">Tving gjennom synkronisering av tid ved gjenopptatt tilkobling. Analoge visere kan vise feil tid!</string>
+    <string name="pref_title_device_spec_settings_force_time">Tving gjennom synkronisering av tid</string>
+    <string name="pref_summary_notifications_and_calls_title_shake_reject">Dupliserer klokkeknappehandling</string>
+    <string name="pref_summary_notifications_and_calls_enable_misscall">Gjenta hvert minutt</string>
+    <string name="pref_notifications_and_calls_enable_misscall">Varsle om tapt anrop</string>
+    <string name="prefs_notifications_and_calls_continious_ring">Merknad for oppringing</string>
+    <string name="pref_title_notifications_and_calls_repeat_on_call">Merknad for tapt anrop</string>
+    <string name="qhybrid_calibration_align_hint">Bruk knappene nedenfor for 氓 justere viserne til 12:00.</string>
+    <string name="gps_track">GPS-spor</string>
+    <string name="about_version">Versjon %s</string>
+    <string name="pref_title_lower_button_function_double">Nedre knapp dobbel</string>
+    <string name="pref_title_middle_button_function_double">Midtre knapp dobbel</string>
+    <string name="pref_title_upper_button_function_double">脴vre knapp dobbel</string>
+    <string name="pref_title_lower_button_function_long">Nedre knapp lang</string>
+    <string name="pref_title_middle_button_function_long">Midtre knapp lang</string>
+    <string name="pref_title_upper_button_function_long">脴vre knapp lang</string>
+    <string name="pref_title_lower_button_function_short">Nedre knapp kort</string>
+    <string name="pref_title_middle_button_function_short">Midtre knapp kort</string>
+    <string name="pref_title_upper_button_function_short">脴vre knapp kort</string>
+    <string name="menuitem_spo2">SpO2</string>
+    <string name="devicetype_amazfit_bips_lite">Amazfit Bip S Lite</string>
+    <string name="devicetype_amazfit_band5">Amazfit Band 5</string>
+    <string name="charts_legend_heartrate_average">Pulsgjennomsnitt</string>
+    <string name="activity_prefs_step_length_cm">Steglengde i cm</string>
+    <string name="charts_min_max_heartrate_popup">Laveste puls: %1$d
+\nH酶yeste puls: %2$d
+\nBevegelsesintensitet: %3$s</string>
+    <string name="find_lost_device_message">S酶k etter %1$s\?</string>
+    <string name="devicetype_amazfit_gtr2">Amazfit GTR 2</string>
+    <string name="menuitem_dnd">Ikke forstyrr</string>
+    <string name="menuitem_stopwatch">Stoppeklokke</string>
+    <string name="menuitem_alexa">Alexa</string>
+    <string name="menuitem_takephoto">Kamerafjernutl酶ser</string>
+    <string name="menuitem_mutephone">Forstum telefon</string>
+    <string name="menuitem_findphone">Finn telefon</string>
+    <string name="activity_list_summary_active_steps">Aktive steg</string>
+    <string name="movement_intensity">Bevegelsesintensitet</string>
+    <string name="activity_list_summary_activities">Aktiviteter</string>
+    <string name="activity_list_summary_intensity">Bevegelse
+\nIntensitet</string>
+    <string name="activity_list_summary_active_time">Aktiv tid</string>
+    <string name="activity_list_summary_distance">Distanse</string>
+    <string name="devicetype_casiogbx100">Casio GBX-100</string>
+    <string name="devicetype_amazfit_gts2">Amazfit GTS 2</string>
+    <string name="prefs_charts_tabs_summary">Synlige poengtavlefaner</string>
+    <string name="prefs_charts_tabs">Poengtavlefaner</string>
+    <string name="devicetype_amazfit_bipu">Amazfit Bip U</string>
+    <string name="menuitem_pomodoro">Pomodoro-sporer</string>
+    <string name="menuitem_sleep">S酶vn</string>
+    <string name="menuitem_goal">Aktivetsm氓l</string>
+    <string name="pref_summary_connected_advertisement">Gj酶r denne enheten oppdagbar via Bl氓tann selv n氓r tilkoblet</string>
+    <string name="pref_title_connected_advertisement">Synlig n氓r tilkoblet</string>
+    <string name="fw_upgrade_notice_amazfitvergel">Du er i ferd med 氓 installere %s-fastvaren p氓 din Amazfit Verge Lite.
+\n
+\nForsikre deg om at du installerer .fw-fila, og s氓 .res-fila, og til slutt .gps-filen. B氓ndet ditt vil starte p氓 ny etter at .fw-fila er installert.
+\n
+\nMerk: Du m氓 ikke installere .res og .gps hvis disse er akkurat de samme som dem som allerede er installert.
+\n
+\nFORTSETT P脜 EGEN RISIKO!</string>
+    <string name="discovery_entered_invalid_authkey">Den hemmelige identitetsn酶kkelen du skrev inn er ugyldig. Trykk lenge p氓 enheten for 氓 redigere.</string>
+    <string name="dbmanagementactivity_export_confirmation">Vil du virkelig eksportere data\? Tidligere eksportert aktivitetsdata (hvis noen) og innstillinger vil bli overskrevet.</string>
+    <string name="dbmanagementactivity_export_data_title">Eksporter data\?</string>
+    <string name="activity_DB_ShowContentButton">Vis eksporter/importer mappeinnhold</string>
+    <string name="activity_data_management_directory_content_title">Eksporter/importer mappeinnhold</string>
+    <string name="prefs_autoremove_message">Fjern SMS-merknader automatisk</string>
+    <string name="prefs_fake_ring_duration">Falsk kontinuerlig ringing</string>
+    <string name="prefs_operating_sounds">Tastetoner</string>
+    <string name="prefs_key_vibration">Tastevibrasjon</string>
+    <string name="prefs_autolight">Automatisk lys</string>
+    <string name="widget_settings_select_device_title">Velg enhet</string>
+    <string name="devicetype_amazfit_vergel">Amazfit Verge Lite</string>
+    <string name="devicetype_amazfit_bipupro">Amazfit Bip U Pro</string>
+    <string name="activity_db_management_clean_export_directory_text">Eksporterte filer i eksport/import-mappen er tilgjengelig for alle programmer p氓 enheten din. Du vil antagelig 酶nske 氓 fjerne disse filene etter synkronisering eller sikkerhetskopi. Forsikre deg om at du har en sikkerhetskopi f酶r du sletter dem. GPX-filer, undermapper og auto-eksportert databasefil (hvis den finnes) vil ikke slettes. Stien til eksport/import-mappen er:</string>
+    <string name="devicetype_amazfit_neo">Amazfit Neo</string>
+    <string name="dbmanagementactivity_export_finished">Sletting fullf酶rt</string>
+    <string name="activity_DB_clean_export_directory_warning_title">Slett filer i eksport/import-mappen\?</string>
+    <string name="activity_DB_clean_export_directory_warning_message">Vil du virkelig slette filer i eksport/import mappen\?</string>
+    <string name="activity_db_management_clean_export_directory_label">Slett filer i eksport/import-mappe</string>
+    <string name="dbmanagementactivity_error_cleaning_export_directory">Klarte ikke 氓 slette filer fra eksportmappe %1$s</string>
+    <string name="mi">mi</string>
+    <string name="minutes_mi">min/mi</string>
+    <string name="mi_h">mi/t</string>
+    <string name="ft">fot</string>
+    <string name="activity_type_strength_training">Styrketrening</string>
+    <string name="devicetype_zepp_e">Zepp E</string>
+    <string name="devicetype_amazfit_gts2_mini">Amazfit GTS 2 Mini</string>
+    <string name="fw_upgrade_notice_amazfitx">Du er i ferd med 氓 installere %s-fastvaren p氓 din Amazfit X.
+\n
+\nForsikre deg om at du installerer .fw-fila, s氓 .res-fila. B氓ndet ditt vil starte p氓 nytt etter at .fw-fila er installert.
+\n
+\nMerk: Du trenger ikke 氓 installere .res-filen hvis den er akkurat den samme som den som allerede er installert.
+\n
+\nFORTSETT P脜 EGEN RISIKO!</string>
+    <string name="devicetype_waspos">Wasp-os</string>
+    <string name="devicetype_amazfit_x">Amazfit X</string>
+    <string name="devicetype_amazfit_gts2e">Amazfit GTS 2e</string>
+    <string name="devicetype_amazfit_gtr2e">Amazfit GTR 2e</string>
+    <string name="battery_detail_activity_title">Batteri-info</string>
+    <string name="show_ongoing_activity">Vis oppsprett for p氓g氓ende aktivitet</string>
+    <string name="dialog_hide">Skjul</string>
+    <string name="calendar_year">脜r</string>
+    <string name="calendar_six_months">6 m氓neder</string>
+    <string name="calendar_three_months">3 m氓neder</string>
+    <string name="calendar_month">M氓ned</string>
+    <string name="calendar_two_weeks">To uker</string>
+    <string name="calendar_week">Uke</string>
+    <string name="calendar_day">Dag</string>
+    <string name="battery_level">Batteriniv氓</string>
+    <string name="fossil_hr_auth_failed">Identitetsbekreftelse mislyktes, begrenset funksjonalitet</string>
+    <string name="fossil_hr_unavailable_unauthed">Ikke tilgjengelig i uten identitetsbekreftelse</string>
+    <string name="fossil_hr_synced_activity_data">Synkroniserte aktivitetsdata</string>
+    <string name="fossil_hr_commute_processing">Behandler foresp酶rsel 鈥�</string>
+    <string name="qhybrid_title_apps">Programmer</string>
+    <string name="qhybrid_title_background_image">Bakgrunnsbilde</string>
+    <string name="qhybrid_title_calibration">Kalibrering</string>
+    <string name="qhybrid_title_apps_management">Programbehandling</string>
+    <string name="qhybrid_title_file_management">Filbehandling</string>
+    <string name="devicetype_um25">UM-25</string>
+    <string name="fossil_hr_warning_firmware_too_new">Noen funksjoner er skrudd av siden fastvaren p氓 klokken er for ny.</string>
+    <string name="prefs_sounds_summary">Sett opp n氓r enheten skal pipe</string>
+    <string name="prefs_sounds">Lyder</string>
+    <string name="fw_upgrade_notice_amazfitneo">Du er i ferd med 氓 installere %s-fastvaren p氓 din Amazfit Neo.
+\n
+\nOmstart av b氓ndet ditt vil bli utf酶rt etter installasjon av .fw-filen.
+\n
+\nFORTSETT P脜 EGEN RISIKO.</string>
+    <string name="menuitem_events">Hendelser</string>
+    <string name="menuitem_temperature">Temperatur</string>
+    <string name="menuitem_widgets">Miniprogrammer</string>
+    <string name="devicetype_miband6">Mi Band 6</string>
+    <string name="fw_upgrade_notice_miband6">Du er i ferd med 氓 installere %s-fastvaren p氓 ditt Mi Band 6.
+\n
+\nForsikre deg om at du installerer .fw-fila, og s氓 .res-fila. B氓ndet ditt vil starte p氓 ny etter at .fw-fila er installert.
+\n
+\nMerk: Du m氓 ikke installere .res hvis den er n酶yaktig lik den som allerede er installert.
+\n
+\nFORTSETT P脜 EGEN RISIKO!</string>
+    <string name="fossil_hr_commute_actions_explanation">Handlingene som settes opp her vil vises i pendler-programmet p氓 klokken din. Les wiki-en for info om hvordan intensjoner produsert av disse handlingene h氓ndteres.</string>
+    <string name="pref_summary_canned_messages_dismisscall">Avsl氓 anrop fra klokken med en SMS-melding</string>
+    <string name="pref_summary_canned_messages_set">Send meldingene satt opp nedenfor til enheten din</string>
+    <string name="qhybrid_summary_calibration">Kalibrer klokkeviserne</string>
+    <string name="qhybrid_summary_file_management">Last opp og ned filer</string>
+    <string name="pref_summary_physical_buttons">Sett opp funksjoner for fysiske knapper p氓 klokken</string>
+    <string name="kind_agps_bundle">AGPS-pakke</string>
+    <string name="qhybrid_calibration_100_steps">100 steg</string>
+    <string name="qhybrid_calibration_10_steps">10 steg</string>
+    <string name="qhybrid_calibration_1_step">1 steg</string>
+    <string name="qhybrid_calibration_clockwise">Med klokken</string>
+    <string name="qhybrid_calibration_counterclockwise">Mot klokken</string>
+    <string name="fossil_hr_new_action">Ny handling</string>
+    <string name="fossil_hr_new_action_cancel">avbryt</string>
+    <string name="fossil_hr_edit_action">Redigeringshandling</string>
+    <string name="fossil_hr_edit_action_delete">slett</string>
+    <string name="pref_title_physical_buttons">Fysiske knapper</string>
+    <string name="watchface_install_info">Du er i ferd med 氓 installere f酶lgende urskrive:
+\n
+\n%1$s
+\nVersjon %2$s av %3$s
+\n</string>
+    <string name="qhybrid_watchface_configuration_old_firmware">Urskiveoppsettsskjerm for klokker med fastvareversjon DN1.0.2.19r og lavere</string>
+    <string name="qhybrid_title_watchface">Urskive-oppsett</string>
+    <string name="notification_channel_low_battery_name">Lavt batteriniv氓</string>
+    <string name="notification_channel_transfer_name">Dataoverf酶ring</string>
+    <string name="devicetype_amazfit_trex_pro">Amazfit T-Rex Pro</string>
+    <string name="qhybrid_pref_summary_external_intents">Lar Android-programmer laste opp/overskrive filer</string>
+    <string name="qhybrid_pref_title_external_intents">Tillat farlige eksterne intensjoner</string>
+    <string name="pref_summary_developer_settings">Innstillinger og funksjonalitet brukt til utvikling</string>
+    <string name="pref_title_developer_settings">Utviklerinnstillinger</string>
+    <string name="qhybrid_pref_summary_actions">Handlinger for pendlerprogrammet</string>
+    <string name="qhybrid_pref_title_actions">Handlinger</string>
+    <string name="menuitem_barometer">Barometer</string>
+    <string name="pref_theme_black_background">Bruk svart bakgrunn i m酶rk drakt</string>
+    <string name="watchface_setting_desc_wrist_flick">(skru p氓 relativ bevegelse og sett alle verdiene til 0 for 氓 sl氓 av)</string>
+    <string name="watchface_setting_title_wrist_flick">H氓ndleddskast</string>
+    <string name="watchface_setting_display_refresh_partial">Delvis gjenoppfriskning (i minutter):</string>
+    <string name="watchface_setting_display_refresh_full">Full gjenoppfriskning (i minutter):</string>
+    <string name="watchface_setting_title_display_refresh_timeout">Tidsavbrudd for skjermsoppfriskning</string>
+    <string name="qhybrid_title_watchface_designer">Urskive-designer</string>
+    <string name="watchface_dialog_widget_preset_right">H酶yre</string>
+    <string name="watchface_dialog_widget_preset_left">Venstre</string>
+    <string name="watchface_dialog_widget_preset_bottom">Bunn</string>
+    <string name="watchface_dialog_widget_preset_top">Topp</string>
+    <string name="watchface_dialog_widget_presets">Posisjonsforh氓ndsinnstillinger:</string>
+    <string name="button_watchface_preview">Forh氓ndsvis p氓 klokke</string>
+    <string name="watchface_setting_wrist_flick_duration">Varighet (i ms):</string>
+    <string name="watchface_setting_wrist_flick_minute">Minutth氓nd (-360 til 360):</string>
+    <string name="watchface_setting_wrist_flick_move_relative">Beveg hendene relativt til tid:</string>
+    <string name="watchface_setting_wrist_flick_hour">Din h氓nd (-360 til 360)</string>
+    <string name="watchface_widget_type_heart_rate">Puls</string>
+    <string name="watchface_toast_settings_incomplete">Ufullstendige innstillinger. Miniprogram ikke innlastet</string>
+    <string name="gpx_receiver_activity_title">GPX-mottager Gadgetbridge</string>
+    <string name="devicetype_smaq2oss">SMA-Q2 OSS</string>
+    <string name="activity_summary_detail_clear_gpx_track">T酶m GPX-spor</string>
+    <string name="watchface_dialog_title_set_name">Velg urskive-navn</string>
+    <string name="button_watchface_settings">Urskive-innstillinger</string>
+    <string name="watchface_upload_failed">Kunne ikke laste opp urskive. Pr酶v igjen.</string>
+    <string name="watchface_cache_confirm_overwrite">En urskive med dette navnet finnes allerede i hurtiglageret. 脴nsker du 氓 overskrive den\?</string>
+    <string name="watchface_dialog_title_settings">Urskive-innstillinger</string>
+    <string name="watchface_dialog_widget_y_coordinate">Y-koordinat (maks. 240):</string>
+    <string name="watchface_dialog_widget_x_coordinate">X-koordinat (maks. 240):</string>
+    <string name="button_watchface_save_apply">Lagre og bruk</string>
+    <string name="gpx_receiver_overwrite_some_files">Fil(er) finnes allerede. Overskriv\?</string>
+    <string name="gpx_receiver_files_received">GPX-fil(er) mottatt:</string>
+    <string name="watchface_dialog_widget_color_black">Svart</string>
+    <string name="watchface_dialog_widget_color_white">Hvit</string>
+    <string name="watchface_dialog_widget_color">Farge:</string>
+    <string name="watchface_dialog_widget_type">Type:</string>
+    <string name="appmanager_app_edit">Rediger</string>
+    <string name="watchface_widget_type_steps">Steg</string>
+    <string name="watchface_widget_type_weather">V忙r</string>
+    <string name="watchface_widget_type_date">Dato</string>
+    <string name="watchface_dialog_title_add_widget">Legg til miniprogram</string>
+    <string name="button_watchface_add_widget">Legg til miniprogram</string>
+    <string name="button_watchface_select_image">Endre bakgrunnsbilde</string>
+    <string name="button_watchface_edit_name">Rediger navn</string>
+    <string name="activity_summary_detail_editing_gpx_track">Rediger lenket GPX-spor</string>
+    <string name="activity_summary_detail_select_gpx_track">Velg GPX-spor</string>
+    <string name="set">Sett</string>
+    <string name="pref_summary_huami_force_new_protocol">Skru p氓 dette hvis enheten ikke kobler til etter fastvareoppgradering</string>
+    <string name="devicetype_fitpro">FitPro</string>
+    <string name="heart_rate_result">M氓lingsresultater</string>
+    <string name="getting_heart_rate">M氓ling</string>
+    <string name="prefs_autoheartrate_interval">M氓lingsfrekvens</string>
+    <string name="prefs_autoheartrate">Automatisk puls</string>
+    <string name="watchface_widget_type_active_mins">Aktive minutter</string>
+    <string name="watchface_widget_type_2nd_tz">Andre tidssone</string>
+    <string name="watchface_widget_type_calories">Kalorier</string>
+    <string name="watchface_widget_type_battery">Batteri</string>
+    <string name="menuitem_flashlight">Lommelykt</string>
+    <string name="blood_pressure">Blodtrykk</string>
+    <string name="pref_title_ping_tone">Pingetone</string>
+    <string name="nothing_prefs_audiomode_title">Lydmodus</string>
+    <string name="check_all_applications">Velg alle programmer</string>
+    <string name="pref_title_notification_use_as">Bruk programlisten til 氓 鈥�</string>
+    <string name="pref_title_notification_use_as_deny">Nekt merknader fra valgte programmer</string>
+    <string name="pref_message_privacy_mode_off">Vis innhold</string>
+    <string name="pref_message_privacy_mode_complete">Skjul innhold</string>
+    <string name="pref_applications_settings">Programliste</string>
+    <string name="pref_header_notification_application_settings">Innstillinger per program</string>
+    <string name="pref_title_notification_use_as_allow">Tillat merknader fra valgte programmer</string>
+    <string name="title_activity_notification_management">Merknadsinnstillinger</string>
+    <string name="uncheck_all_applications">Fravelg alle programmer</string>
+    <string name="watchface_setting_title_power_saving">Energisparing</string>
+    <string name="pref_header_equalizer">Tonekontroll</string>
+    <string name="device_card_activity_card_title">Aktivitetsinfo p氓 enhetskort</string>
+    <string name="device_card_activity_card_title_summary">Velg aktivitet vist p氓 enhetskortet</string>
+    <string name="prefs_activity_in_device_card_title">Vis aktivitetsinfo p氓 enhetskort</string>
+    <string name="watchface_widget_type_custom">Egendefinert miniprogram</string>
+    <string name="sony_automatic_power_off_1_hour">1 time</string>
+    <string name="sony_automatic_power_off_3_hour">3 timer</string>
+    <string name="pref_header_other">Annet</string>
+    <string name="pref_header_system">System</string>
+    <string name="devicetype_sony_wh_1000xm3">Sony WH-1000XM3</string>
+    <string name="prefs_activity_in_device_card_title_summary">Vis n氓v忙rende steg, avstand eller s酶vn p氓 enhetsskjerm</string>
+    <string name="prefs_activity_in_device_card_sleep_title">S酶vn</string>
+    <string name="prefs_activity_in_device_card_sleep_title_summary">Vis sovetid</string>
+    <string name="prefs_activity_in_device_card_distance_title_summary">Avstand utregnet fra steg og steglengde (kan justeres i 芦Innstillinger 鈫� Om deg禄)</string>
+    <string name="prefs_activity_in_device_card_steps_title_summary">Vis totalt antall steg</string>
+    <string name="toast_app_must_not_be_selected">Programmet kan ikke v忙re valgt for 氓 kunne settes opp</string>
+    <string name="toast_app_must_be_selected">Programmet m氓 velges for 氓 settes opp</string>
+    <string name="devicetype_galaxybuds">Galaxy Buds</string>
+    <string name="watchface_widget_type_chance_rain">Mulighet for regn</string>
+    <string name="prefs_sleep_time_label">Definer sovetid</string>
+    <string name="prefs_sleep_time">Sovetider</string>
+    <string name="devicetype_nothingear1">Nothing Ear (1)</string>
+    <string name="devicetype_galaxybuds_live">Galaxy Buds Live</string>
+    <string name="prefs_sleep_time_summary">Angir tider der s酶vn registreres</string>
+    <string name="prefs_vibration_enable">Skru p氓 vibrasjoner</string>
+    <string name="prefs_notifications_enable">Skru p氓 merknader</string>
+    <string name="enable_vibrations_summary">Vibrasjoner for anrop, meldinger, merknader og mer</string>
+    <string name="enable_notifications_summary">Merknader for anrop, meldinger og mer</string>
+    <string name="pref_title_equalizer_soft">Myk</string>
+    <string name="pref_title_equalizer_dynamic">Dynamisk</string>
+    <string name="prefs_equalizer">Tonekontroll</string>
+    <string name="prefs_equalizer_summary">Skru p氓 eller av tonekontroll</string>
+    <string name="prefs_dolby_summary">Dolby-forh氓ndsinnstilling for tonekontroll</string>
+    <string name="pref_title_equalizer_clear">Klar</string>
+    <string name="pref_title_equalizer_trebble">Diskantforsterkning</string>
+    <string name="prefs_dolby_mode">Dolby-modus</string>
+    <string name="reminder_time_every_week">%1$s, hver uke</string>
+    <string name="reminder_time_every_month">%1$s, hver m氓ned</string>
+    <string name="reminder_time_every_year">%1$s, hvert 氓r</string>
+    <string name="reminder_once">脡n gang</string>
+    <string name="controlcenter_start_configure_reminders">Sett opp p氓minnelser</string>
+    <string name="reminder_repeat">Gjentagelse</string>
+    <string name="reminder_date">Dato</string>
+    <string name="reminder_time">Tid</string>
+    <string name="reminder_message">Melding</string>
+    <string name="reminder_time_once">%1$s, 茅n gang</string>
+    <string name="reminder_time_every_day">%1$s, hver dag</string>
+    <string name="title_activity_set_reminders">Sett opp p氓minnelser</string>
+    <string name="reminder_every_month">Hver m氓ned</string>
+    <string name="reminder_every_year">Hvert 氓r</string>
+    <string name="reminder_no_free_slots_description">Enheten har ingen ledige plasser for p氓minnelser (totalt antall plasser: %1$s)</string>
+    <string name="miband_prefs_reserve_reminder_calendar">Reserverte p氓minnelser for kommende hendelser</string>
+    <string name="nothing_prefs_inear_title">Oppdagelse av 酶rebruk</string>
+    <string name="prefs_autoheartrate_summary">Periodisk pulsm氓ling om dagen, og ogs氓 under s酶vn</string>
+    <string name="prefs_galaxy_buds_experimental">Eksperimentelt</string>
+    <string name="prefs_ambient_volume">Omgivelseslydstyrke</string>
+    <string name="prefs_ambient_voice_focus">Stemmefokus</string>
+    <string name="prefs_touch_lock">Pekel氓s</string>
+    <string name="pref_header_sony_ambient_sound_control">Lydomgivelseskontroll</string>
+    <string name="sony_ambient_sound_noise_cancelling">Lyd-kansellering</string>
+    <string name="sony_ambient_sound_wind_noise_reduction">Vindst酶y-reduksjon</string>
+    <string name="sony_ambient_sound_focus_voice">Framhev stemme</string>
+    <string name="sony_ambient_sound_level">Lydtrykksniv氓 for omgivelser</string>
+    <string name="sony_sound_position_front">Front</string>
+    <string name="sony_sound_position_front_left">Venstre front</string>
+    <string name="sony_sound_position_rear_left">Venstre bak</string>
+    <string name="sony_sound_position_rear_right">H酶yre bak</string>
+    <string name="sony_surround_mode">Kringlydsmodus</string>
+    <string name="sony_equalizer_preset_excited">Oppgl酶dd</string>
+    <string name="sony_equalizer_preset_mellow">Veik</string>
+    <string name="sony_warn_sbc_codec">Advarsel: Tonekontrollen, lydposisjon og kringlydsinnstillinger fungerer kun for SBC-lydkodeket.</string>
+    <string name="sony_equalizer_preset_treble_boost">Diskantforsterkning</string>
+    <string name="pref_header_sony_equalizer_preset_custom_1">Egendefinert forh氓ndsinnstilling 1</string>
+    <string name="sony_equalizer_band_1000">1k</string>
+    <string name="sony_equalizer_band_2500">2,5k</string>
+    <string name="reminder_delete_confirm_title">Slett p氓minnelse</string>
+    <string name="reminder_every_day">Hver dag</string>
+    <string name="reminder_delete_confirm_description">Slett p氓minnelsen\?</string>
+    <string name="sony_sound_position_off">Av</string>
+    <string name="reminder_no_free_slots_title">Ingen ledige plasser</string>
+    <string name="title_activity_reminder_details">P氓minnelsesdetaljer</string>
+    <string name="sony_ambient_sound_off">Av</string>
+    <string name="reminder_every_week">Hver uke</string>
+    <string name="pref_title_equalizer_normal">Normal</string>
+    <string name="prefs_game_mode">Spillmodus</string>
+    <string name="prefs_game_mode_summary">Kun hvis telefonen din st酶tter spillmodus</string>
+    <string name="sony_ambient_sound">Modus</string>
+    <string name="sony_ambient_sound_ambient_sound">Omgivelseslyd</string>
+    <string name="sony_sound_position">Lydposisjon</string>
+    <string name="sony_sound_position_front_right">H酶yre front</string>
+    <string name="sony_equalizer_band_400">400</string>
+    <string name="qhybrid_title_on_device_confirmation">Skru p氓 bekreftelse av parring fra enheten</string>
+    <string name="prefs_autoheartrate_measurement">Automatisk pulsm氓ling</string>
+    <string name="sony_equalizer_preset_relaxed">Avslappet</string>
+    <string name="pref_header_sony_equalizer_preset_custom_2">Egendefinert forh氓ndsinnstilling 2</string>
+    <string name="prefs_touch_lock_summary">Skru av pekehendelser</string>
+    <string name="nothing_prefs_inear_summary">Gjenoppta eller sett musikk p氓 pause avhengig av om du bruker 酶reproppene</string>
+    <string name="pref_title_message_privacy_mode">Privat meldingsmodus</string>
+    <string name="prefs_fm_preset_instructions">Trykk lenge for 氓 lagre forh氓ndsinnstilling</string>
+    <string name="qhybrid_summary_on_device_confirmation">Bekreftelse av parring p氓 enheten kan v忙re plagsomt. Du kan miste funksjonalitet ved 氓 skru det av.</string>
+    <string name="prefs_reserve_reminder_calendar_summary">Antall kalenderhendelser 氓 synkronisere</string>
+    <string name="prefs_fm_presets_presets">Forh氓ndsinnstillinger</string>
+    <string name="prefs_autoheartrate_sleep">Utf酶r m氓linger under s酶vn</string>
+    <string name="watchface_setting_power_saving_display">Kun vis oppdateringer n氓r b氓ret p氓 h氓ndleddet</string>
+    <string name="watchface_setting_power_saving_hands">Viserbevegelse kun n氓r b氓ret p氓 h氓ndleddet</string>
+    <string name="sony_automatic_power_off_when_taken_off">N氓r fjernet fra h氓ndledd</string>
+    <string name="sony_equalizer_preset_vocal">Vokal</string>
+    <string name="sony_touch_sensor">Pekesensorkontroll</string>
+    <string name="sony_notification_voice_guide">Merknader og stemmeanvisning</string>
+    <string name="pref_title_huami_force_new_protocol">Ny identitetbekreftelsesprotokoll</string>
+    <string name="watchface_dialog_widget_timezone">Tidssone:</string>
+    <string name="watchface_dialog_widget_timeout_show_circle">Vis sirkel ved tidsavbrudd:</string>
+    <string name="watchface_dialog_widget_update_timeout">Oppdateringstidsavbrudd i minutter:</string>
+    <string name="watchface_dialog_widget_timeout_hide_text">Skjul tekst ved tidsavbrudd:</string>
+    <string name="prefs_active_noise_cancelling">Aktiv lyd-kansellering</string>
+    <string name="prefs_active_noise_cancelling_summary">Blokkerer omgivelsesst酶y</string>
+    <string name="prefs_pressure_relief">Trykkforl酶sning med omgivelseslyd</string>
+    <string name="pressure_relief_summary">Forhindre f酶lelsen av trykk i 酶rene n氓r du ikke bruker aktiv lyd-kansellering</string>
+    <string name="sony_surround_mode_off">Av</string>
+    <string name="sony_surround_mode_arena">Arena</string>
+    <string name="sony_surround_mode_club">Klubb</string>
+    <string name="sony_surround_mode_outdoor_stage">Utend酶rs scene</string>
+    <string name="sony_surround_mode_concert_hall">Konserthall</string>
+    <string name="sony_equalizer_clear_bass">Klar bass</string>
+    <string name="sony_automatic_power_off_off">Ikke skru av</string>
+    <string name="sony_automatic_power_off_5_min">5 minutter</string>
+    <string name="sony_automatic_power_off_30_min">30 minutter</string>
+    <string name="sony_equalizer_band_16000">16k</string>
+    <string name="sony_equalizer_band_6300">6,3k</string>
+    <string name="sony_automatic_power_off">Skru av automatisk</string>
+    <string name="prefs_equalizer_preset">Tonekontroll-forh氓ndsinnstilling</string>
+    <string name="pref_title_equalizer_bass_boost">Bassforsterkning</string>
+    <string name="prefs_ambient_voice_summary">Framhev stemmer</string>
+    <string name="prefs_ambient_sound">Omgivelseslyd</string>
+    <string name="prefs_ambient_mode">Omgivelsesmodus</string>
+    <string name="prefs_left">Venstre</string>
+    <string name="prefs_right">H酶yre</string>
+    <string name="prefs_galaxy_touch_options">Peke-alternativer</string>
+    <string name="sony_equalizer">Tonekontroll</string>
+    <string name="sony_equalizer_preset_off">Av</string>
+    <string name="sony_equalizer_preset_bright">Lys</string>
+    <string name="sony_equalizer_preset_bass_boost">Bassforsterkning</string>
+    <string name="sony_equalizer_preset_speech">Tale</string>
+    <string name="sony_equalizer_preset_manual">Manuell</string>
+    <string name="sony_equalizer_preset_custom_1">Egendefinert 1</string>
+    <string name="sony_equalizer_preset_custom_2">Egendefinert 2</string>
+    <string name="battery_case">Batteriomslag</string>
+    <string name="left_earbud">Venstre 酶repropp</string>
+    <string name="right_earbud">H酶yre 酶repropp</string>
+    <string name="maxHR">Makspuls</string>
+    <string name="minStride">Minste steglengde</string>
+    <string name="averageCadence">Gjennomsnittlig takt</string>
+    <string name="maxCadence">Maksimal takt</string>
+    <string name="minCadence">Minste takt</string>
+    <string name="mi2_prefs_do_not_disturb_lift_wrist">Aktiver skjermvisning ved l酶ft under 芦Ikke forstyrr禄.</string>
+    <string name="averageAltitude">Gjennomsnittlig</string>
+    <string name="minSpeed">Minimum</string>
+    <string name="minHR">Laveste puls</string>
+    <string name="maxStride">Maksimal steglengde</string>
+    <string name="spm">steg/min</string>
+    <string name="pref_header_sony_equalizer_bands">B氓nd</string>
+    <string name="sony_audio_upsampling">Lydoppsampling</string>
+    <string name="discover_unsupported_devices">Oppdag ust酶ttede enheter</string>
+    <string name="discover_unsupported_devices_description">脜 skru p氓 dette viser alle oppdagede Bl氓tannsenheter ved skanning. Et kort trykk kopierer enhetsnavn og MAC-adresse til utklippstavlen. Trykk lenge for 氓 kj酶re 芦Legg til testenhet禄-dialogen.</string>
+    <string name="controlcenter_power_off">Sl氓 av</string>
+    <string name="devicetype_sony_wf_sp800n">Sony WF-SP800N</string>
+    <string name="devicetype_bose_qc35">Bose QC35</string>
+    <string name="devicetype_vesc">VESC</string>
+    <string name="ascentDistance">Oppoverbakke</string>
+    <string name="sony_button_mode_ambient_sound_control">Innstilling for omgivelseslyd</string>
+    <string name="activity_type_hiking">Turg氓ing</string>
+    <string name="sony_button_mode_left">Knappemodus (venstre)</string>
+    <string name="sony_button_mode_volume_control">Lydstyrkejustering</string>
+    <string name="controlcenter_power_off_confirm_description">Skru av enheten\?</string>
+    <string name="activity_prefs_discovery_pairing">Oppdagelse og parringsvalg</string>
+    <string name="controlcenter_power_off_confirm_title">Sl氓 av</string>
+    <string name="activity_type_climbing">Klatring</string>
+    <string name="sony_pause_when_taken_off">Sett p氓 pause n氓r hodetelefoner tas av</string>
+    <string name="sony_button_mode_off">Av</string>
+    <string name="sony_button_mode_playback_control">Avspillingskontroll</string>
+    <string name="add_test_device">Legg til testenhet</string>
+    <string name="descentDistance">Nedoverbakke</string>
+    <string name="sony_button_mode_right">Knappemodus (h酶yre)</string>
+    <string name="pref_device_action_broadcast">Send kringkasting</string>
+    <string name="sony_speak_to_chat_sensitivity_high">H酶y</string>
+    <string name="watchface_dialog_widget_width">Bredde:</string>
+    <string name="discovery_bluetooth_scan">Bl氓tannsskanning:</string>
+    <string name="discovery_bluetooth_le_scan">LE-Bl氓tannsskanning:</string>
+    <string name="sony_speak_to_chat_focus_on_voice">Fokuser p氓 stemmeregister</string>
+    <string name="sony_speak_to_chat_timeout">Tidsavbrudd</string>
+    <string name="sony_speak_to_chat_timeout_short">Kort (15s)</string>
+    <string name="sony_speak_to_chat_timeout_standard">Forvalg (30s)</string>
+    <string name="sony_connect_two_devices">Koble til to enheter samtidig</string>
+    <string name="pref_media_play">Medaavspilling</string>
+    <string name="pref_media_pause">Mediapause</string>
+    <string name="pref_media_playpause">Veksle avspilling</string>
+    <string name="pref_media_next">Neste spor</string>
+    <string name="pref_media_volumeup">Lydstyrke +</string>
+    <string name="pref_media_volumedown">Lydstyrke -</string>
+    <string name="pref_media_forward">Hopp forover</string>
+    <string name="pref_media_rewind">Hopp bakover</string>
+    <string name="distance_format_feet">### ft</string>
+    <string name="pref_activity_recognize_running">oppdag l酶ping</string>
+    <string name="pref_activity_recognize_biking">oppdag sykling</string>
+    <string name="pref_activity_recognize_walking">oppdag g氓ing</string>
+    <string name="pref_activity_recognize_rowing">oppdag roing</string>
+    <string name="pref_activity_recognition_mode_none">ingen</string>
+    <string name="menuitem_menu">Meny</string>
+    <string name="sony_speak_to_chat_timeout_long">Lang (1m)</string>
+    <string name="sony_speak_to_chat_timeout_off">Av</string>
+    <string name="device_unsupported">Ust酶ttet</string>
+    <string name="about_hash">Innsendelse %s</string>
+    <string name="sony_speak_to_chat_sensitivity_auto">Automatisk</string>
+    <string name="sony_speak_to_chat_sensitivity_low">Lav</string>
+    <string name="sony_speak_to_chat">Snakk-for-氓-sludre</string>
+    <string name="pref_activity_recognition_mode_auto">auto</string>
+    <string name="distance_format_meters">### m</string>
+    <string name="pref_button_action_disabled">Avskrudd</string>
+    <string name="pref_media_previous">Forrige spor</string>
+    <string name="distance_format_miles">###.# mi</string>
+    <string name="distance_format_kilometers">###.# km</string>
+    <string name="prefs_activity_recognition">Innstillinger for gjenkjenning av aktivitet</string>
+    <string name="pref_activity_recognition_mode_ask">sp酶r</string>
+    <string name="fossil_hr_button_config_info">Noen knapper kan ikke settes opp fordi funksjonene deres er hardkodet i armb氓ndsur-fastvaren.
+\n
+\nAdvarsel: Lang-trykk p氓 酶vre knapp n氓r en urskive fra det offisielle Fossil-programmet er installert vil ogs氓 veksle mellom visnings/skjuling av miniprogrammer.</string>
+    <string name="devicetype_domyos_t540">Domyos T540</string>
+    <string name="sony_speak_to_chat_sensitivity">Sensitivitet for oppdagelse av stemme</string>
+    <string name="start">Start</string>
+    <string name="sony_anc_optimizer_status_starting">Starter 鈥�</string>
+    <string name="pref_anc_optimizer_state_pressure">Lufttrykk</string>
+    <string name="pref_header_sony_anc_optimizer">Optimalisering av lydkansellering</string>
+    <string name="sony_anc_optimize_description">Klikk for 氓 starte optimalisering av lydkansellering.</string>
+    <string name="sony_anc_optimize_confirmation_title">Optimalisering av lydkansellering</string>
+    <string name="sony_anc_optimizer_status_not_running">Kj酶rer ikke</string>
+    <string name="sony_anc_optimizer_status_atmospheric_pressure">M氓ler lufttrykk 鈥�</string>
+    <string name="sony_anc_optimizer_status_wearing_condition">M氓ler hvordan bruksm氓te 鈥�</string>
+    <string name="unknown">Ukjent</string>
+    <string name="sony_anc_optimizer_status_finished">Fullf酶rer 鈥�</string>
+    <string name="audio_codec">Lydkodek</string>
+    <string name="sony_anc_optimizer_status_analyzing">Analyserer 鈥�</string>
+    <string name="sony_anc_optimize_confirmation_description">Bruk hodetelefonene som vanlig. Hvis m氓ten du bruker dem p氓 eller lufttrykket endrer seg kan du kj酶re optimalisatoren igjen.</string>
+    <string name="pref_header_sony_device_info">Enhetsinfo</string>
+    <string name="sony_anc_optimize_title">Optimaliser</string>
+    <string name="menuitem_nothing">Ingenting</string>
+    <string name="devicetype_amazfit_pop">Amazfit Pop</string>
+    <string name="devicetype_amazfit_pop_pro">Amazfit Pop Pro</string>
+    <string name="devicetype_sony_wh_1000xm4">Sony WH-1000XM4</string>
+    <string name="pref_device_action_fitness_app_control_start">Sporingsstart for trimprogram</string>
+    <string name="pref_device_action_fitness_app_control_stop">Sporingsstopp for trimprogram</string>
+    <string name="pref_title_opentracks_packagename">OpenTracks-pakkenavn</string>
+    <string name="pref_title_notifications_generic_settings">Android-merknadsinnstillinger</string>
+    <string name="pref_summary_opentracks_packagename">Brukt for start/stopp av GPS-sporingslagring i eksternt trimprogram.</string>
+    <string name="activity_db_management_autoexport_enabled_yes">Auto-eksport er p氓sl氓tt.</string>
+    <string name="activity_db_management_autoexport_enabled_no">Auto-eksport er avsl氓tt.</string>
+    <string name="autoExport_lastTime_label">Siste auto-eksport:%1$s</string>
+    <string name="activity_db_management_autoexport_scheduled_yes">Auto-eksport ble (opprinnelig) planlagt %1$s</string>
+    <string name="activity_db_management_autoexport_scheduled_no">Auto-eksport er ikke planlagt.</string>
+    <string name="activity_db_management_autoexport_location">Forsto ikke plassering. Antagelig er dette et tilgangsproblem i Android. Mest sannsynlig fungerer ikke auto-eksport n氓.</string>
+    <string name="watchface_dialog_pre_setting_position">forh氓ndsinnstiller posisjon til %s</string>
+    <string name="watchface_setting_light_up_on_notification">Sl氓 p氓 lys ved nye merknader</string>
+    <string name="pref_title_touch_ambient">Omgivelseslyd</string>
+    <string name="pref_balance">Balanse</string>
+    <string name="activity_type_outdoor_cycling">Utend酶rssykling</string>
+    <string name="activity_type_outdoor_running">Utend酶rsl酶ping</string>
+    <string name="prefs_active_noise_cancelling_level_low">Lavt</string>
+    <string name="pref_title_touch_spotify">Spotify</string>
+    <string name="prefs_active_noise_cancelling_level_high">H酶y</string>
+    <string name="pref_title_touch_voice_assistant">Stemmeassistent</string>
+    <string name="pref_title_touch_volume">Lydstyrke</string>
+    <string name="pref_title_touch_anc">Aktiv lydkansellering</string>
+    <string name="prefs_noise_control">St酶ykontroll</string>
+    <string name="pref_voice_detect_duration_5">5 sekunder</string>
+    <string name="pref_voice_detect_duration_10">10 sekunder</string>
+    <string name="pref_voice_detect_duration_15">15 sekunder</string>
+    <string name="normal">Normal</string>
+    <string name="sensitive">Sensitiv</string>
+    <string name="menuitem_email">E-post</string>
+    <string name="gadgetbridge_running_banglejs_nopebble">Bangle.js kj酶rer 鈥�</string>
+    <string name="pref_blacklist_calendars_summary">Svartelistede kalendere vil ikke synkroniseres til enheten</string>
+    <string name="pref_title_device_internet_access">Tillat tilgang til Internett</string>
+    <string name="pref_summary_device_internet_access">Tillat programmer p氓 denne enheten 氓 koble til Internett</string>
+    <string name="watchface_dialog_widget_cat_generic">Generisk</string>
+    <string name="watchface_dialog_title_widget">Miniprogramsinnstillinger</string>
+    <string name="watchface_dialog_widget_cat_custom_widget">Egendefinert miniprogram</string>
+    <string name="watchface_dialog_widget_background">Bakgrunn</string>
+    <string name="hybridhr_widget_bg_thin_circle">Tynn sirkel</string>
+    <string name="hybridhr_widget_bg_double_circle">Dobbel sirkel</string>
+    <string name="controlcenter_add_new_folder">Legg til ny mappe</string>
+    <string name="controlcenter_set_folder_title">Sett eller opprett ny mappe</string>
+    <string name="prefs_password">Passord</string>
+    <string name="prefs_password_enabled">Passord p氓skrudd</string>
+    <string name="connection_over_ble">Tilkobling over BLE</string>
+    <string name="notification_channel_gps">GPS-sporing</string>
+    <string name="quick_alarm_description">Alarm fra miniprogram</string>
+    <string name="prefs_activate_display_on_lift_sensitivity">Sensitivitet</string>
+    <string name="mi2_prefs_heart_rate_monitoring_alerts_summary">Sett opp pulsregistrering og varselsterskler</string>
+    <string name="icelandic">Islandsk</string>
+    <string name="quick_alarm">Hurtigalarm</string>
+    <string name="info_no_devices_connected">ingen enheter tilkoblet</string>
+    <string name="title_activity_world_clock_details">Verdensklokkedetaljer</string>
+    <string name="info_connected_count">%d enheter tilkoblet</string>
+    <string name="controlcenter_connected_fraction">Tilkoblet: %d/%d</string>
+    <string name="controlcenter_folder_name">Mappenavn:</string>
+    <string name="prefs_heartrate_alert_experimental_title">Pulsvarsel (eksperimentelt)</string>
+    <string name="watchface_dialog_widget_cat_position">Posisjon</string>
+    <string name="seconds_8">8 sekunder</string>
+    <string name="pref_header_calendar">Kalender</string>
+    <string name="pref_header_connection">Tilkobling</string>
+    <string name="pref_header_display">Skjerm</string>
+    <string name="pref_header_health">Helse</string>
+    <string name="pref_header_time">Tid</string>
+    <string name="pref_header_workout">Trening</string>
+    <string name="pref_workout_send_gps_summary">Send n氓v忙rende GPS-posisjon til b氓ndet under trenings酶kt</string>
+    <string name="seconds_6">6 sekunder</string>
+    <string name="seconds_12">12 sekunder</string>
+    <string name="seconds_13">13 sekunder</string>
+    <string name="seconds_14">14 sekunder</string>
+    <string name="seconds_15">15 sekunder</string>
+    <string name="prefs_screen_timeout">Skjerm-tidsavbrudd</string>
+    <string name="steps_streaks_since_date">Siden %s</string>
+    <string name="step_streak_ongoing">P氓g氓ende</string>
+    <string name="discovery_scanning_intensity">Skanningsintensitet</string>
+    <string name="spo2_off">Av</string>
+    <string name="seconds_9">9 sekunder</string>
+    <string name="seconds_11">11 sekunder</string>
+    <string name="smart">Smart</string>
+    <string name="mi2_dnd_always">Alltid</string>
+    <string name="activity_type_elliptical">Elliptisk</string>
+    <string name="activity_type_kickboxing">Kickboksing</string>
+    <string name="step_streak_longest">Lengste</string>
+    <string name="step_streak_total">Totalt</string>
+    <string name="supercars_lights_label">Lys</string>
+    <string name="supercars_turbo_speed_label">Turbo-hastighet</string>
+    <string name="world_clock_label">Etikett</string>
+    <string name="world_clock_delete_confirm_title">Slett 芦%1$s禄</string>
+    <string name="world_clock_timezone">Tidssone</string>
+    <string name="pref_workout_send_gps_title">Send GPS under trening</string>
+    <string name="kind_app">Program</string>
+    <string name="menuitem_unknown_app">Ukjent (%s)</string>
+    <string name="supercars_lights_blinking_label">Blinkende</string>
+    <string name="open_fw_installer_warning_title">Advarsel</string>
+    <string name="open_fw_installer_pick_file">Velg fil</string>
+    <string name="world_clock_no_free_slots_title">Ingen ledige plasser</string>
+    <string name="seconds_7">7 sekunder</string>
+    <string name="pref_title_third_party_app_device_settings">Tillat tredjepartsprogrammer 氓 endre innstillinger</string>
+    <string name="activity_type_dance">Dans</string>
+    <string name="activity_type_indoor_fitness">Innend酶rs-fitness</string>
+    <string name="menuitem_countdown">Nedtelling</string>
+    <string name="open_fw_installer_select_file">Velg en fil du 酶nsker 氓 laste opp til enheten: %s</string>
+    <string name="prefs_hourly_chime_summary">Klokken bil pipe 茅n gang i timen</string>
+    <string name="step_streaks_achievements_sharing_title">Stegm氓l</string>
+    <string name="step_streaks_achievements_sharing_message">Mine daglige stegm氓l</string>
+    <string name="steps_streaks_total_steps_hint">Totalt antall steg i hele serien</string>
+    <string name="steps_streaks_total_steps_hint_totals">Totalt antall skritt siden begynnelsen</string>
+    <string name="steps_streaks_achievement_rate">M氓l
+\ntakt</string>
+    <string name="steps_streaks_hint">Serier av sammenhengende dager uten forstyrrelse i stegm氓lsoppn氓else</string>
+    <string name="step_streak_average_steps_hint">Gjen. steg per dag i serien</string>
+    <string name="steps_streaks_average_steps">Gjen.
+\nsteg</string>
+    <string name="pref_header_heartrate_allday">Heldagsoverv氓kning</string>
+    <string name="pref_header_heartrate_sleep">S酶vn</string>
+    <string name="prefs_voice_detect">Stemmeoppdagelse</string>
+    <string name="steps_streaks_total_steps">Totalt antall
+\nsteg</string>
+    <string name="steps_streaks_total_days_hint_totals">Prosent av dager med oppn氓dde m氓l sammenlignet med alle dager med skritt</string>
+    <string name="steps_streaks">Stegserier</string>
+    <string name="steps_streaks_streak_days">Serie
+\nDager</string>
+    <string name="activity_prefs_summary_allow_bluetooth_intent_api">Tillater kontrollering av Bl氓tannstilkobling via intensjons-API</string>
+    <string name="activity_prefs_allow_bluetooth_intent_api">Intensjons-API for Bl氓tann</string>
+    <string name="prefs_hourly_chime">Timevist lydvarsel</string>
+    <string name="steps_streaks_total_steps_average_hint">Gjen. %d steg per dag.</string>
+    <string name="step_streak_days_hint">Antall sammenhengende dager med stegm氓lsoppn氓else</string>
+    <string name="pref_header_stress">Stress</string>
+</resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml
new file mode 100644
index 0000000..0f5c8a5
--- /dev/null
+++ b/app/src/main/res/values-nl/strings.xml
@@ -0,0 +1,3563 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources xmlns:tools="http://schemas.android.com/tools">
+    <string name="application_name_generic">Gadgetbridge</string>
+    <string name="title_activity_controlcenter_generic">Gadgetbridge</string>
+    <string name="action_settings">Instellingen</string>
+    <string name="action_debug">Debug</string>
+    <string name="action_quit">Sluit af</string>
+    <string name="action_donate">Doneer</string>
+    <string name="controlcenter_fetch_activity_data">Synchroniseer</string>
+    <string name="controlcenter_find_device">Zoek verloren apparaat</string>
+    <string name="controlcenter_take_screenshot">Screenshot maken</string>
+    <string name="controlcenter_disconnect">Verbinding verbreken</string>
+    <string name="controlcenter_delete_device">Verwijder apparaat</string>
+    <string name="controlcenter_delete_device_name">Verwijder %1$s</string>
+    <string name="controlcenter_delete_device_dialogmessage">Dit zal het apparaat en alle bijbehorende gegevens verwijderen!</string>
+    <string name="controlcenter_snackbar_need_longpress">Druk lang op de kaart om de verbinding te verbreken</string>
+    <string name="controlcenter_snackbar_disconnecting">Verbinding verbreken</string>
+    <string name="controlcenter_snackbar_connecting">Verbinden鈥�</string>
+    <string name="controlcenter_snackbar_requested_screenshot">Een screenshot maken van het apparaat</string>
+    <string name="title_activity_debug">Debug</string>
+    <string name="title_activity_appmanager">App-beheerder</string>
+    <string name="appmanager_cached_watchapps_watchfaces">Apps in de cache</string>
+    <string name="appmanager_installed_watchapps">Ge茂nstalleerde apps</string>
+    <string name="appmanager_installed_watchfaces">Ge茂nstalleerde wijzerplaten</string>
+    <string name="appmananger_app_delete">Verwijder</string>
+    <string name="appmananger_app_delete_cache">Verwijder en verwijder uit cache</string>
+    <string name="appmananger_app_reinstall">Herinstalleer</string>
+    <string name="appmanager_app_openinstore">Zoek in Pebble appstore</string>
+    <string name="appmanager_health_activate">Activeer</string>
+    <string name="appmanager_health_deactivate">Deactiveer</string>
+    <string name="appmanager_hrm_activate">Activeer HRM</string>
+    <string name="appmanager_hrm_deactivate">Deactiveer HRM</string>
+    <string name="appmanager_weather_activate">Activeer de systeem weer app</string>
+    <string name="appmanager_weather_install_provider">Installeer de app voor weer meldingen</string>
+    <string name="app_configure">Configureer</string>
+    <string name="app_move_to_top">Verplaats naar de top</string>
+    <string name="fw_upgrade_notice">U staat op het punt om firmware %s te installeren.</string>
+    <string name="fw_upgrade_notice_amazfitbip">U staat op het punt om de firmware %s te installeren op uw Amazfit Bip.
+\n
+\nZorg ervoor dat u het .fw-bestand installeert, daarna het .res-bestand en als laatste het .gps-bestand. Uw horloge wordt opnieuw gestart na het installeren van het .fw-bestand.
+\n
+\nOpmerking: U hoeft de .res niet te installeren als deze exact dezelfde is als degene die ervoor al was ge茂nstalleerd.
+\n
+\nGA VERDER OP EIGEN RISICO!</string>
+    <string name="fw_multi_upgrade_notice">U staat op het punt om de %1$s en %2$s firmware te installeren in plaats van degene die momenteel op uw Mi Band staat.</string>
+    <string name="miband_firmware_known">Deze firmware is getest en is compatibel met Gadgetbridge.</string>
+    <string name="miband_firmware_unknown_warning">Deze firmware is niet getest en is mogelijk niet compatibel met Gadgetbridge. 
+\n 
+\nU wordt AFGERADEN om deze te installeren!</string>
+    <string name="miband_firmware_suggest_whitelist">Als u nog steeds wilt doorgaan en uw apparaat blijft goed functioneren, vertel dan alstublieft de ontwikkelaars van Gadgetbridge om de %s firmware-versie op de goedgekeurde lijst te zetten.</string>
+    <string name="title_activity_settings">Instellingen</string>
+    <string name="pref_header_general">Algemene instellingen</string>
+    <string name="pref_title_general_autoconnectonbluetooth">Verbind met Gadgetbridge-apparaten wanneer Bluetooth wordt ingeschakeld</string>
+    <string name="pref_title_general_autostartonboot">Start automatisch</string>
+    <string name="pref_title_general_autoreconnect">Verbind automatisch opnieuw</string>
+    <string name="pref_title_audio_player">Gewenste audiospeler</string>
+    <string name="pref_default">Standaard</string>
+    <string name="pref_header_datetime">Datum en tijd</string>
+    <string name="pref_title_datetime_syctimeonconnect">Sync tijd</string>
+    <string name="pref_summary_datetime_syctimeonconnect">Synchroniseer de tijd naar alle apparaten wanneer u verbinding maakt, wanneer de tijd of tijdzone op uw Android-apparaat verandert en periodiek</string>
+    <string name="pref_title_theme">Thema</string>
+    <string name="pref_theme_light">Licht</string>
+    <string name="pref_theme_dark">Donker</string>
+    <string name="pref_title_language">Taal</string>
+    <string name="pref_title_minimize_priority">Verberg de Gadgetbridge-melding</string>
+    <string name="pref_summary_minimize_priority_off">Het pictogram in de statusbalk en de melding op het vergrendelingsscherm worden weergegeven</string>
+    <string name="pref_summary_minimize_priority_on">Het pictogram in de statusbalk en de melding op het vergrendelingsscherm zijn verborgen</string>
+    <string name="pref_header_notifications">Meldingen</string>
+    <string name="pref_title_notifications_repetitions">Herhalingen</string>
+    <string name="pref_title_notifications_call">Telefoongesprekken</string>
+    <string name="pref_title_notifications_sms">SMS</string>
+    <string name="pref_title_notifications_pebblemsg">Pebble-berichten</string>
+    <string name="pref_summary_notifications_pebblemsg">Ondersteuning voor apps die meldingen versturen naar de Pebble via PebbleKit.</string>
+    <string name="pref_title_notifications_generic">Algemene meldingondersteuning</string>
+    <string name="pref_title_whenscreenon">鈥� ook als het scherm is ingeschakeld</string>
+    <string name="pref_title_notification_filter">Niet Storen</string>
+    <string name="pref_summary_notification_filter">Blokkeer alle meldingen wanneer Niet Storen is ingeschakeld op de telefoon</string>
+    <string name="pref_title_transliteration">Transliteratie</string>
+    <string name="pref_summary_transliteration">Schakel dit in als uw apparaat geen ondersteuning heeft voor het lettertype van uw taal</string>
+    <string name="always">Altijd</string>
+    <string name="when_screen_off">Wanneer het scherm uitstaat</string>
+    <string name="never">Nooit</string>
+    <string name="pref_header_privacy">Privacy</string>
+    <string name="pref_title_call_privacy_mode">Privacymodus gesprekken</string>
+    <string name="pref_call_privacy_mode_off">Toon naam en nummer</string>
+    <string name="pref_call_privacy_mode_name">Verberg naam maar toon nummer</string>
+    <string name="pref_call_privacy_mode_number">Verberg nummer maar toon naam</string>
+    <string name="pref_call_privacy_mode_complete">Verberg naam en nummer</string>
+    <string name="pref_blacklist_calendars">Zwarte lijst Kalenders</string>
+    <string name="pref_title_canned_replies">Antwoorden</string>
+    <string name="pref_title_canned_reply_suffix">Vaak voorkomend achtervoegsel</string>
+    <string name="pref_title_canned_messages_dismisscall">Telefoongesprek afwijzen met SMS</string>
+    <string name="pref_title_canned_messages_set">Update op apparaat</string>
+    <string name="pref_header_development">Ontwikkelaarsopties</string>
+    <string name="pref_title_development_miaddr">Mi Band adres</string>
+    <string name="pref_title_pebble_settings">Pebble instellingen</string>
+    <string name="pref_header_activitytrackers">Activiteit trackers</string>
+    <string name="pref_title_pebble_activitytracker">Voorkeurs-activiteit tracker</string>
+    <string name="pref_title_pebble_sync_health">Synchroniseer Pebble Health</string>
+    <string name="pref_title_pebble_sync_misfit">Sync Misfit</string>
+    <string name="pref_title_pebble_sync_morpheuz">Sync Morpheuz</string>
+    <string name="pref_title_enable_outgoing_call">Ondersteun uitgaande oproepen</string>
+    <string name="pref_summary_enable_outgoing_call">Als u dit uitschakelt, zal de Pebble 2/LE ook stoppen met trillen bij uitgaande gesprekken</string>
+    <string name="pref_title_enable_pebblekit">Toegang van 3de partij Android Apps toestaan</string>
+    <string name="pref_summary_enable_pebblekit">Experimentele ondersteuning inschakelen voor Android apps die PebbleKit gebruiken</string>
+    <string name="pref_header_pebble_timeline">Pebble-tijdlijn</string>
+    <string name="pref_title_sunrise_sunset">Zonsopkomst en zonsondergang</string>
+    <string name="pref_summary_sunrise_sunset">Verstuur zonsopkomst- en zonsondergangstijden op basis van de locatie naar de Pebble-tijdlijn</string>
+    <string name="pref_title_enable_calendar_sync">Synchroniseer kalender</string>
+    <string name="pref_summary_enable_calendar_sync">Verstuur kalendergebeurtenissen naar de tijdlijn</string>
+    <string name="pref_title_pebble_privacy_mode">Privacymodus</string>
+    <string name="pref_pebble_privacy_mode_off">Normale meldingen</string>
+    <string name="pref_pebble_privacy_mode_complete">Toon alleen het meldingsicoon</string>
+    <string name="pref_header_location">Locatie</string>
+    <string name="pref_title_location_aquire">Verkrijg locatie</string>
+    <string name="pref_title_location_latitude">Breedtegraad</string>
+    <string name="pref_title_location_longitude">Lengtegraad</string>
+    <string name="pref_title_location_keep_uptodate">Houd de locatie bijgewerkt</string>
+    <string name="pref_summary_location_keep_uptodate">Probeer de huidige locatie te krijgen, gebruik de opgeslagen locatie als backup</string>
+    <string name="toast_enable_networklocationprovider">Activeer de netwerk locatie</string>
+    <string name="toast_aqurired_networklocation">locatie gevonden</string>
+    <string name="discovery_successfully_bonded">Gekoppeld met %1$s.</string>
+    <string name="discovery_pair_title">Koppel met %1$s?</string>
+    <string name="discovery_pair_question">Selecteer Koppel om uw apparaten te koppelen. Als dit mislukt, probeer het opnieuw zonder koppelen.</string>
+    <string name="discovery_yes_pair">Koppel</string>
+    <string name="discovery_dont_pair">Niet Koppelen</string>
+    <string name="_pebble_watch_open_on_phone">Open op Android-apparaat</string>
+    <string name="_pebble_watch_mute">Dempen</string>
+    <string name="_pebble_watch_reply">Antwoord</string>
+    <string name="installhandler_firmware_name">%1$s: %2$s %3$s</string>
+    <string name="controlcenter_connect">Verbinden鈥�</string>
+    <string name="controlcenter_navigation_drawer_open">Open navigatiescherm</string>
+    <string name="controlcenter_navigation_drawer_close">Sluit navigatiescherm</string>
+    <string name="appmanager_weather_deactivate">Deactiveer de systeem weer app</string>
+    <string name="title_activity_calblacklist">Kalenders op de zwarte lijst</string>
+    <string name="title_activity_fw_app_insaller">Firmware/Apps installeren</string>
+    <string name="fw_upgrade_notice_amazfitcor">U staat op het punt om de firmware %s te installeren op uw Amazfit Cor.
+\n
+\nZorg ervoor dat u het .fw-bestand installeert en daarna het .res-bestand. Uw band wordt opnieuw gestart na het installeren van het .fw-bestand.
+\n
+\nOpmerking: U hoeft de .res niet te installeren als deze exact dezelfde is als degene die ervoor al was ge茂nstalleerd.
+\n
+\nGA VERDER OP EIGEN RISICO!</string>
+    <string name="pref_title_charts_swipe">Activeer links/rechts vegen in grafiekschermen</string>
+    <string name="pref_title_weather">Weer</string>
+    <string name="pref_title_weather_location">Weerlocatie (voor LinageOS-weeraanbieder)</string>
+    <string name="pref_title_autoremove_notifications">Verwijder automatisch verworpen meldingen</string>
+    <string name="pref_summary_autoremove_notifications">Meldingen worden automatisch verwijderd op het apparaat als ze verwijderd worden op de telefoon</string>
+    <string name="pref_pebble_privacy_mode_content">Schuif de meldingstekst van het scherm</string>
+    <string name="pref_header_cannned_messages">Voorgedefinieerde berichten</string>
+    <string name="pref_title_pebble_forceprotocol">Verplicht meldingenprotocol</string>
+    <string name="pref_summary_pebble_forceprotocol">Deze optie forceert het gebruik van het nieuwste meldingenprotocol, afhankelijk van de firmware versie. WEET WAAR JE MEE BEZIG BENT!</string>
+    <string name="pref_title_pebble_forceuntested">Schakel niet geteste features in</string>
+    <string name="pref_summary_pebble_forceuntested">Schakel ongeteste features in. WEET WAAR JE MEE BEZIG BENT!</string>
+    <string name="pref_title_pebble_forcele">Geef altijd de voorkeur aan BLE</string>
+    <string name="pref_summary_pebble_forcele">Gebruik experimentele Pebble LE-ondersteuning voor alle Pebbels in plaats van BT classic. Dit vereist het eerst paren met een niet LE-versie en daarna met een Pebble LE</string>
+    <string name="pref_title_pebble_mtu_limit">Pebble 2/LE GATT MTU limiet</string>
+    <string name="pref_summary_pebble_mtu_limit">Als uw Pebble 2/Pebble LE niet werkt zoals verwacht, probeer deze instelling om de MTU te limiteren (geldig bereik: 20鈥�512)</string>
+    <string name="pref_title_pebble_enable_applogs">Logging van de horloge app inschakelen</string>
+    <string name="pref_summary_pebble_enable_applogs">Zorgt ervoor dat logs van horloge-apps gelogd zullen worden door Gadgetbridge (opnieuw verbinden vereist)</string>
+    <string name="pref_title_pebble_always_ack_pebblekit">Vroegtijdige ACK PebbleKit</string>
+    <string name="pref_summary_pebble_always_ack_pebblekit">Zorgt ervoor dat berichten die door apps van derden worden verzonden, altijd en onmiddellijk herkend zullen worden</string>
+    <string name="pref_title_pebble_enable_bgjs">Schakel achtergrond JS in</string>
+    <string name="pref_summary_pebble_enable_bgjs">Wanneer ingeschakeld, kunnen horloges het weer, batterij-informatie enz. weergeven.</string>
+    <string name="pref_title_pebble_reconnect_attempts">Pogingen tot opnieuw verbinden</string>
+    <string name="pref_title_unit_system">Eenheden</string>
+    <string name="pref_title_timeformat">Tijdsformaat</string>
+    <string name="pref_title_screentime">Scherm aan-tijd</string>
+    <string name="prefs_title_all_day_heart_rate">De hele dag hartslag meten</string>
+    <string name="preferences_hplus_settings">HPlus/Makibes instellingen</string>
+    <string name="not_connected">Niet verbonden</string>
+    <string name="connecting">Aan het verbinden</string>
+    <string name="connected">Verbonden</string>
+    <string name="unknown_state">Onbekende staat</string>
+    <string name="_unknown_">(onbekend)</string>
+    <string name="test">Test</string>
+    <string name="test_notification">Testmelding</string>
+    <string name="this_is_a_test_notification_from_gadgetbridge">Dit is een testmelding van Gadgetbridge</string>
+    <string name="bluetooth_is_not_supported_">Bluetooth is niet ondersteund.</string>
+    <string name="bluetooth_is_disabled_">Bluetooth is uitgeschakeld.</string>
+    <string name="tap_connected_device_for_app_mananger">Tap op het verbonden apparaat voor de App beheerder</string>
+    <string name="tap_connected_device_for_activity">Tap op het verbonden apparaat voor activiteitsweergave</string>
+    <string name="tap_connected_device_for_vibration">Tap op het verbonden apparaat voor vibratie</string>
+    <string name="tap_a_device_to_connect">Tap op een apparaat om te verbinden</string>
+    <string name="cannot_connect_bt_address_invalid_">Kan niet verbinden. Is het Bluetooth adres ongeldig?</string>
+    <string name="gadgetbridge_running_generic">Gadgetbridge loopt</string>
+    <string name="installing_binary_d_d">Binary installeren %1$d/%2$d</string>
+    <string name="installation_failed_">Installatie gefaald</string>
+    <string name="installation_successful">Ge茂nstalleerd</string>
+    <string name="firmware_install_warning">U PROBEERT EEN NIEUWE FIRMWARE TE INSTALLEREN, GA VERDER OP EIGEN RISICO.
+\n
+\n
+\nDeze firmware is voor HW-revisie: %s</string>
+    <string name="app_install_info">U staat op het punt om de volgende app te installeren:
+\n
+\n%1$s
+\nVersie %2$s door %3$s
+\n</string>
+    <string name="n_a">N/B</string>
+    <string name="initialized">Ge茂nitialiseerd</string>
+    <string name="appversion_by_creator">%1$s door %2$s</string>
+    <string name="title_activity_discovery">Naar apparaten scannen</string>
+    <string name="discovery_stop_scanning">Stop met scannen</string>
+    <string name="discovery_start_scanning">Start met scannen</string>
+    <string name="action_discover">Nieuw apparaat verbinden</string>
+    <string name="device_with_rssi">%1$s (%2$s)</string>
+    <string name="title_activity_android_pairing">Apparaat koppelen</string>
+    <string name="android_pairing_hint">Gebruik het Android Bluetooth-venster om het apparaat te koppelen.</string>
+    <string name="title_activity_mi_band_pairing">Koppel uw Mi Band</string>
+    <string name="pairing">Koppelen met %s鈥�</string>
+    <string name="pairing_creating_bond_with">Koppeling maken met %1$s (%2$s)</string>
+    <string name="pairing_unable_to_pair_with">Niet in staat om te koppelen met %1$s (%2$s)</string>
+    <string name="pairing_in_progress">Bezig met koppelen: %1$s (%2$s)</string>
+    <string name="pairing_already_bonded">Reeds gekoppeld met %1$s (%2$s), verbinden鈥�</string>
+    <string name="message_cannot_pair_no_mac">Geen MAC adres doorgegeven, kan niet koppelen.</string>
+    <string name="preferences_category_device_specific_settings">Apparaat-specifieke instellingen</string>
+    <string name="preferences_miband_settings">Mi Band / Amazfit instellingen</string>
+    <string name="male">Mannelijk</string>
+    <string name="female">Vrouwelijk</string>
+    <string name="other">Anders</string>
+    <string name="left">Links</string>
+    <string name="right">Rechts</string>
+    <string name="miband_pairing_using_dummy_userdata">Geen geldige gebruikers data ingegeven, voorlopig dummy gebruikersdata gebruiken.</string>
+    <string name="miband_pairing_tap_hint">Als uw Mi Band trilt en oplicht, tik een aantal keer na elkaar.</string>
+    <string name="appinstaller_install">Installeren</string>
+    <string name="discovery_connected_devices_hint">Maak uw apparaat zichtbaar. Apparaten die op dit moment verbonden zijn zullen waarschijnlijk niet zichtbaar zijn. Activeer locatie (b.v. GPS) op Android 6+. Schakel Privacy Guard voor Gadgetbridge uit, anders kan uw telefoon crashen en herstarten. Als er geen apparaat gevonden is na een paar minuten, probeer dan opnieuw na het opnieuw opstarten van uw telefoon.</string>
+    <string name="discovery_note">Opmerking:</string>
+    <string name="candidate_item_device_image">Apparaatafbeelding</string>
+    <string name="miband_prefs_alias">Naam / Alias</string>
+    <string name="pref_header_vibration_count">Aantal trillingen</string>
+    <string name="title_activity_sleepmonitor">Slaapmonitor</string>
+    <string name="pref_write_logfiles">Schrijf logbestanden</string>
+    <string name="initializing">Initialiseren</string>
+    <string name="busy_task_fetch_activity_data">Ophalen van activiteitsgegevens</string>
+    <string name="sleep_activity_date_range">Van %1$s aan %2$s</string>
+    <string name="prefs_wearside">Links of rechts dragen?</string>
+    <string name="pref_screen_vibration_profile">Vibratie profiel</string>
+    <string name="vibration_profile_staccato">Staccato</string>
+    <string name="vibration_profile_short">Kort</string>
+    <string name="vibration_profile_medium">Middel</string>
+    <string name="vibration_profile_long">Lang</string>
+    <string name="vibration_profile_waterdrop">Waterdruppel</string>
+    <string name="vibration_profile_ring">Bellen</string>
+    <string name="vibration_profile_alarm_clock">Alarm klok</string>
+    <string name="miband_prefs_vibration">Vibratie</string>
+    <string name="vibration_try">Proberen</string>
+    <string name="pref_screen_notification_profile_sms">SMS melding</string>
+    <string name="pref_header_vibration_settings">Vibratie instellingen</string>
+    <string name="pref_screen_notification_profile_generic">Generieke melding</string>
+    <string name="pref_screen_notification_profile_email">E-mail melding</string>
+    <string name="pref_screen_notification_profile_incoming_call">Inkomend gesprek melding</string>
+    <string name="pref_screen_notification_profile_generic_chat">Chat</string>
+    <string name="pref_screen_notification_profile_generic_navigation">Navigatie</string>
+    <string name="pref_screen_notification_profile_generic_social">Sociaal netwerk</string>
+    <string name="prefs_title_heartrate_measurement_interval">Hele dag HR-meting</string>
+    <string name="interval_one_minute">een keer per minuut</string>
+    <string name="interval_five_minutes">elke 5 minuten</string>
+    <string name="interval_ten_minutes">elke 10 minuten</string>
+    <string name="interval_thirty_minutes">elke 30 minuten</string>
+    <string name="interval_one_hour">een keer per uur</string>
+    <string name="stats_title">Snelheidszones</string>
+    <string name="stats_x_axis_label">Totaal aantal minuten</string>
+    <string name="stats_y_axis_label">Stappen per minuut</string>
+    <string name="control_center_find_lost_device">Zoek verloren apparaat</string>
+    <string name="control_center_cancel_to_stop_vibration">Annuleer om vibratie te stoppen.</string>
+    <string name="title_activity_charts">Activiteit en Slaap</string>
+    <string name="title_activity_set_alarm">Alarmen configureren</string>
+    <string name="controlcenter_start_configure_alarms">Alarmen configureren</string>
+    <string name="title_activity_alarm_details">Alarm details</string>
+    <string name="alarm_sun_short">Zo</string>
+    <string name="alarm_mon_short">Ma</string>
+    <string name="alarm_tue_short">Di</string>
+    <string name="alarm_wed_short">Wo</string>
+    <string name="alarm_thu_short">Do</string>
+    <string name="alarm_fri_short">Vr</string>
+    <string name="alarm_sat_short">Za</string>
+    <string name="alarm_smart_wakeup">Slim wekken</string>
+    <string name="user_feedback_miband_set_alarms_failed">Er is een fout opgetreden bij het instellen van de alarmen, probeer het opnieuw.</string>
+    <string name="user_feedback_miband_set_alarms_ok">Alarmen verzonden naar het apparaat.</string>
+    <string name="chart_no_data_synchronize">Geen data. Apparaat synchroniseren\?</string>
+    <string name="user_feedback_miband_activity_data_transfer">Klaar om %1$s data over te dragen van %2$s</string>
+    <string name="miband_prefs_fitness_goal">Dagelijkse stappen doel</string>
+    <string name="dbaccess_error_executing">Fout bij het uitvoeren van \'%1$s\'</string>
+    <string name="controlcenter_start_activitymonitor">Uw activiteit</string>
+    <string name="cannot_connect">Kan niet verbinden: %1$s</string>
+    <string name="installer_activity_unable_to_find_handler">Kan geen handler vinden om dit bestand te installeren.</string>
+    <string name="pbw_install_handler_unable_to_install">Kan het gegeven bestand niet installeren: %1$s</string>
+    <string name="pbw_install_handler_hw_revision_mismatch">Kan de geselecteerde firmware niet installeren: Het komt niet overeen met uw Pebble\'s hardware-revisie.</string>
+    <string name="installer_activity_wait_while_determining_status">Gelieve te wachten tijdens het bepalen van de installatie status鈥�</string>
+    <string name="notif_battery_low_title">Gadget batterij laag!</string>
+    <string name="notif_battery_low_percent">%1$s batterij resterend: %2$s%%</string>
+    <string name="notif_battery_low_bigtext_last_charge_time">Laatste lading: %s 
+\n</string>
+    <string name="notif_battery_low_bigtext_number_of_charges">Aantal ladingen: %s</string>
+    <string name="sleepchart_your_sleep">Slaap</string>
+    <string name="weeksleepchart_sleep_a_week">Slaap per week</string>
+    <string name="weeksleepchart_today_sleep_description">Slaap vandaag, doel: %1$s</string>
+    <string name="weekstepschart_steps_a_week">Stappen per week</string>
+    <string name="activity_sleepchart_activity_and_sleep">Activiteit</string>
+    <string name="updating_firmware">Firmware flashen鈥�</string>
+    <string name="fwapp_install_device_not_ready">Bestand kan niet ge茂nstalleerd worden, apparaat is niet gereed.</string>
+    <string name="miband_fwinstaller_compatible_version">Compatibele versie</string>
+    <string name="miband_fwinstaller_untested_version">Niet geteste versie!</string>
+    <string name="fwappinstaller_connection_state">Verbinding met apparaat: %1$s</string>
+    <string name="pbw_installhandler_pebble_firmware">Pebble Firmware %1$s</string>
+    <string name="pbwinstallhandler_correct_hw_revision">Correcte hardware revisie</string>
+    <string name="pbwinstallhandler_incorrect_hw_revision">Hardware revisie komt niet overeen!</string>
+    <string name="pbwinstallhandler_app_item">%1$s (%2$s)</string>
+    <string name="updatefirmwareoperation_updateproblem_do_not_reboot">Probleem met firmware-overdracht. START uw Mi Band NIET OPNIEUW OP!</string>
+    <string name="updatefirmwareoperation_metadata_updateproblem">Probleem met de firmware metadata-overdracht</string>
+    <string name="updatefirmwareoperation_update_complete">Firmware-installatie voltooid</string>
+    <string name="updatefirmwareoperation_update_complete_rebooting">Firmware-installatie voltooid, opnieuw opstarten鈥�</string>
+    <string name="updatefirmwareoperation_write_failed">Firmware flashen mislukt</string>
+    <string name="chart_steps">Stappen</string>
+    <string name="calories">Calorie毛n</string>
+    <string name="distance">Afstand</string>
+    <string name="clock">Klok</string>
+    <string name="heart_rate">Hartslag</string>
+    <string name="battery">Batterij</string>
+    <string name="liveactivity_live_activity">Live activiteit</string>
+    <string name="weeksteps_today_steps_description">Stappen vandaag, doel: %1$s</string>
+    <string name="pref_title_dont_ack_transfer">Bevestig de activiteitsdata transfer niet</string>
+    <string name="pref_summary_dont_ack_transfers">Als er niet bevestigd wordt naar de armband, zullen de activiteitgegevens niet gewist worden. Dit is nuttig als GB gebruikt wordt samen met andere apps.</string>
+    <string name="pref_summary_keep_data_on_device">Behoud activiteitsdata op het apparaat na synchronisatie. Dit kan nuttig zijn als GB gebruikt wordt samen met andere apps. Dit kan ervoor zorgen dat het apparaat vol raakt en/of stopt met synchroniseren.</string>
+    <string name="pref_title_low_latency_fw_update">Gebruik lage vertragingstijd modus voor flashen firmware</string>
+    <string name="pref_summary_low_latency_fw_update">Dit kan helpen op apparaten waar firmware flashen faalt.</string>
+    <string name="live_activity_steps_history">Stappen geschiedenis</string>
+    <string name="live_activity_current_steps_per_minute">Huidige stappen/min</string>
+    <string name="live_activity_total_steps">Totaal aantal stappen</string>
+    <string name="live_activity_steps_per_minute_history">Stappen per minuut geschiedenis</string>
+    <string name="live_activity_start_your_activity">Start uw activiteit</string>
+    <string name="abstract_chart_fragment_kind_activity">Activiteit</string>
+    <string name="abstract_chart_fragment_kind_light_sleep">Lichte slaap</string>
+    <string name="abstract_chart_fragment_kind_deep_sleep">Diepe slaap</string>
+    <string name="abstract_chart_fragment_kind_not_worn">Niet gedragen</string>
+    <string name="device_not_connected">Niet verbonden.</string>
+    <string name="user_feedback_all_alarms_disabled">Alle alarmen uitgeschakeld</string>
+    <string name="pref_title_keep_data_on_device">Houd activiteitsdata op het apparaat</string>
+    <string name="miband_fwinstaller_incompatible_version">Incompatibele firmware</string>
+    <string name="fwinstaller_firmware_not_compatible_to_device">Deze firmware is niet compatibel met het apparaat</string>
+    <string name="miband_prefs_reserve_alarm_calendar">Alarmen gereserveerd voor toekomstige gebeurtenissen</string>
+    <string name="miband_prefs_hr_sleep_detection">Gebruik hartslag sensor om slaap detectie te verbeteren</string>
+    <string name="miband_prefs_device_time_offset_hours">Apparaat-tijdsverschuiving in uren (voor het detecteren van slaap van werknemers in ploegendienst)</string>
+    <string name="miband2_prefs_dateformat">Datumformaat</string>
+    <string name="dateformat_time">Tijd</string>
+    <string name="dateformat_date_time"><![CDATA[Tijd & datum]]></string>
+    <string name="mi2_prefs_button_actions">Knoppen acties</string>
+    <string name="mi2_prefs_button_actions_summary">Specificeer actie voor knop druk</string>
+    <string name="mi2_prefs_button_press_count">Aantal knopdrukken</string>
+    <string name="mi2_prefs_button_press_count_summary">Aantal knopdrukken nodig om een Event 1 te sturen. Hetzelfde aantal knopdrukken geeft een Event 2 enz.</string>
+    <string name="mi2_prefs_button_press_broadcast">Uit te sturen bericht</string>
+    <string name="mi2_prefs_button_press_broadcast_summary">Uit te sturen bericht verdonden met een event. Parameter `button_id` wordt automatisch aan ieder bericht toegevoegd.</string>
+    <string name="mi2_prefs_button_action">Activeer knop actie</string>
+    <string name="mi2_prefs_button_action_summary">Activeer actie na een bepaald aantal knopdrukken</string>
+    <string name="mi2_prefs_button_action_vibrate">Schakel band vibratie in</string>
+    <string name="mi2_prefs_button_action_vibrate_summary">Schakel band vibratie in als een knopactie gedetecteerd wordt</string>
+    <string name="mi2_prefs_button_press_count_max_delay">Maximum tijdsvertraging tussen knopdrukken</string>
+    <string name="mi2_prefs_button_press_count_max_delay_summary">Maximum tijdsvertraging tussen knopdrukken in milliseconden</string>
+    <string name="mi2_prefs_goal_notification">Doel melding</string>
+    <string name="mi2_prefs_goal_notification_summary">De armband zal trillen als het doel voor het dagelijks aantal stappen bereikt is</string>
+    <string name="mi2_prefs_display_items">Toon items</string>
+    <string name="mi2_prefs_display_items_summary">Kies de items die weergegeven worden op het armbandscherm</string>
+    <string name="mi2_prefs_activate_display_on_lift">Activeer het display bij optilen</string>
+    <string name="mi2_prefs_rotate_wrist_to_switch_info">Draai pols om info te wisselen</string>
+    <string name="mi2_prefs_do_not_disturb">Niet storen</string>
+    <string name="mi2_prefs_do_not_disturb_summary">De armband zal geen meldingen krijgen als hij actief is</string>
+    <string name="mi2_prefs_inactivity_warnings">Inactiviteitswaarschuwingen</string>
+    <string name="mi2_prefs_inactivity_warnings_summary">De armband zal trillen als je een tijdje inactief bent</string>
+    <string name="mi2_prefs_inactivity_warnings_threshold">Inactiviteits-drempel (in minuten)</string>
+    <string name="mi2_prefs_inactivity_warnings_dnd_summary">Schakel de inactiviteits-waarschuwing uit voor een bepaald tijdsinterval</string>
+    <string name="mi2_prefs_do_not_disturb_start">Starttijd</string>
+    <string name="mi2_prefs_do_not_disturb_end">Eindtijd</string>
+    <string name="automatic">Automatisch</string>
+    <string name="simplified_chinese">Versimpeld Chinees</string>
+    <string name="traditional_chinese">Traditioneel Chinees</string>
+    <string name="english">Engels</string>
+    <string name="FetchActivityOperation_about_to_transfer_since">Op het punt om data over te zetten vanaf %1$s</string>
+    <string name="waiting_for_reconnect">Wachten om opnieuw te verbinden</string>
+    <string name="activity_prefs_about_you">Over u</string>
+    <string name="activity_prefs_year_birth">Geboortejaar</string>
+    <string name="activity_prefs_gender">Geslacht</string>
+    <string name="activity_prefs_height_cm">Hoogte in cm</string>
+    <string name="activity_prefs_weight_kg">Gewicht in kg</string>
+    <string name="authenticating">Authenticatie</string>
+    <string name="authentication_required">Authenticatie vereist</string>
+    <string name="appwidget_text">Zzz</string>
+    <string name="add_widget">Widget toevoegen</string>
+    <string name="activity_prefs_sleep_duration">Gewenste slaapduur in uren</string>
+    <string name="appwidget_setting_alarm">Een alarm is ingesteld voor %1$02d:%2$02d</string>
+    <string name="device_hw">Hardware revisie: %1$s</string>
+    <string name="device_fw">Firmware-versie: %1$s</string>
+    <string name="error_creating_directory_for_logfiles">Fout tijdens het aanmaken van map voor logbestanden: %1$s</string>
+    <string name="DEVINFO_HR_VER">"HR: "</string>
+    <string name="updatefirmwareoperation_update_in_progress">Flashen firmware</string>
+    <string name="updatefirmwareoperation_firmware_not_sent">Firmware niet verzonden</string>
+    <string name="charts_legend_heartrate">Hartslag</string>
+    <string name="live_activity_heart_rate">Hartslag</string>
+    <string name="pref_title_pebble_health_store_raw">Bewaar onbewerkte record in de database</string>
+    <string name="pref_summary_pebble_health_store_raw">Slaat de data op \"zoals ze is\", dit vergroot het gebruik van de database om latere interpretatie mogelijk te maken.</string>
+    <string name="action_db_management">Gegevensbeheer</string>
+    <string name="title_activity_db_management">Gegevensbeheer</string>
+    <string name="activity_db_management_import_export_explanation">De export-/importbewerkingen gebruiken het volgende pad (zie hieronder) naar een map op uw apparaat. Deze map is toegankelijk voor andere Android-apps en uw computer. Houd er rekening mee dat deze map en alle bestanden erin, worden verwijderd als u Gadgetbridge verwijdert. De gegevens omvatten:
+\nExport_preference - globale instellingen
+\nExport_preference_MAC - apparaatspecifieke instellingen
+\nGadgetbridge - apparaat- en activiteitendatabase
+\nGadgetbridge_date - database ge毛xporteerd op een datum
+\n* .gpx - GPS-opnames
+\n* .log - logbestanden
+\nUw ge毛xporteerde bestanden kunt u hier vinden (of plaats de bestanden die u wilt importeren hier):</string>
+    <string name="activity_db_management_merge_old_title">Legacy-database verwijderen</string>
+    <string name="dbmanagementactivvity_cannot_access_export_path">Export pad is niet toegankelijk. Neem contact op met de ontwikkelaars.</string>
+    <string name="dbmanagementactivity_exported_to">Ge毛xporteerd naar: %1$s</string>
+    <string name="dbmanagementactivity_error_exporting_db">Fout bij het exporteren DB: %1$s</string>
+    <string name="dbmanagementactivity_error_exporting_shared">Fout bij het exporteren voorkeur: %1$s</string>
+    <string name="dbmanagementactivity_import_data_title">Data importeren?</string>
+    <string name="dbmanagementactivity_overwrite_database_confirmation">Weet u zeker dat u de huidige gegevens wilt overschrijven? Al uw huidige activiteitsgegevens (indien aanwezig), apparaten en instellingen zullen verloren gaan.</string>
+    <string name="dbmanagementactivity_import_successful">Ge茂mporteerd.</string>
+    <string name="dbmanagementactivity_error_importing_db">Fout bij het importeren DB: %1$s</string>
+    <string name="dbmanagementactivity_error_importing_shared">Fout bij het importeren voorkeur: %1$s</string>
+    <string name="dbmanagementactivity_delete_activity_data_title">Verwijder activeitsgegevens?</string>
+    <string name="dbmanagementactivity_really_delete_entire_db">Weet u zeker dat u de volledige database wil verwijderen\? Al uw activiteitsgegevens en informatie over uw apparaten zullen verloren gaan.</string>
+    <string name="dbmanagementactivity_database_successfully_deleted">Gegevens verwijderd.</string>
+    <string name="dbmanagementactivity_db_deletion_failed">Verwijderen database mislukt.</string>
+    <string name="dbmanagementactivity_delete_old_activity_db">Oude activiteitsdatabase verwijderen?</string>
+    <string name="dbmanagementactivity_delete_old_activitydb_confirmation">Weet u zeker dat u de oude activiteitsdatabase wil verwijderen? Activiteitsgegevens die niet ge茂mporteerd zijn zullen verloren gaan.</string>
+    <string name="dbmanagementactivity_old_activity_db_successfully_deleted">Oude activiteitsgegevens verwijderd.</string>
+    <string name="dbmanagementactivity_old_activity_db_deletion_failed">Oude activiteitsdatabase verwijderen mislukt.</string>
+    <string name="dbmanagementactivity_overwrite">Overschrijven</string>
+    <string name="Cancel">Annuleren</string>
+    <string name="Delete">Verwijder</string>
+    <string name="title_activity_vibration">Vibratie</string>
+    <string name="title_activity_pebble_pairing">Pebble koppelen</string>
+    <string name="pebble_pairing_hint">Er zal een koppelingsdialoogvenster verschijnen op uw Android-apparaat. Als dat niet gebeurt, kijk dan tussen uw meldingen en accepteer het koppelingsverzoek. Accepteer het daarna ook op uw Pebble.</string>
+    <string name="weather_notification_label">Zorg ervoor dat deze skin is ingeschakeld in de Weermeldingen app om informatie over het weer op uw Pebble te krijgen. 
+\n 
+\nHiervoor is geen configuratie nodig. 
+\n 
+\nU kunt de systeemweer-app van uw Pebble vanuit het app-beheer inschakelen. 
+\n 
+\nOndersteunde wijzerplaten geven het weer automatisch weer.</string>
+    <string name="pref_title_setup_bt_pairing">Schakel Bluetooth koppeling in</string>
+    <string name="pref_summary_setup_bt_pairing">Deactiveer dit als u problemen hebt met verbinden</string>
+    <string name="unit_metric">Metrisch</string>
+    <string name="unit_imperial">Imperiaal</string>
+    <string name="timeformat_24h">24H</string>
+    <string name="timeformat_am_pm">AM/PM</string>
+    <string name="pref_screen_notification_profile_alarm_clock">Alarm klok</string>
+    <string name="activity_web_view">Webweergave activiteit</string>
+    <string name="StringUtils_sender">(%1$s)</string>
+    <string name="find_device_you_found_it">U hebt het gevonden!</string>
+    <string name="miband2_prefs_timeformat">Mi2: Tijdsformaat</string>
+    <string name="mi2_fw_installhandler_fw53_hint">U moet versie %1$s installeren voordat u deze firmware installeert!</string>
+    <string name="mi2_enable_text_notifications">Tekst meldingen</string>
+    <string name="mi2_enable_text_notifications_summary"><![CDATA[Vereist dat firmware >= 1.0.1.28 en Mili_pro.ft* ge茂nstalleerd is.]]></string>
+    <string name="off">Uit</string>
+    <string name="mi2_dnd_off">Uit</string>
+    <string name="mi2_dnd_automatic">Automatisch (slaap detectie)</string>
+    <string name="mi2_dnd_scheduled">Gepland (tijdsinterval)</string>
+    <string name="discovery_attempting_to_pair">Proberen te koppelen met %1$s</string>
+    <string name="discovery_bonding_failed_immediately">Koppelen met %1$s faalde onmiddellijk.</string>
+    <string name="discovery_trying_to_connect_to">Proberen te verbinden met: %1$s</string>
+    <string name="discovery_enable_bluetooth">Schakel Bluetooth in om apparaten te vinden.</string>
+    <string name="kind_firmware">Firmware</string>
+    <string name="kind_invalid">Ongeldige data</string>
+    <string name="kind_font">Lettertype</string>
+    <string name="kind_gps">GPS-firmware</string>
+    <string name="kind_gps_almanac">GPS Almanak</string>
+    <string name="kind_gps_cep">GPS Foutcorrectie</string>
+    <string name="kind_resources">Middelen</string>
+    <string name="kind_watchface">Wijzerplaat</string>
+    <string name="devicetype_unknown">Onbekend apparaat</string>
+    <string name="devicetype_test">Test Apparaat</string>
+    <string name="devicetype_pebble">Pebble</string>
+    <string name="devicetype_miband">Mi Band</string>
+    <string name="devicetype_miband2">Mi Band 2</string>
+    <string name="devicetype_amazfit_bip">Amazfit Bip</string>
+    <string name="devicetype_amazfit_cor">Amazfit Cor</string>
+    <string name="devicetype_vibratissimo">Virbatissimo</string>
+    <string name="devicetype_liveview">LiveView</string>
+    <string name="devicetype_hplus">HPlus</string>
+    <string name="devicetype_makibes_f68">Makibes F68</string>
+    <string name="devicetype_exrizu_k8">Exrizu K8</string>
+    <string name="devicetype_no1_f1">No.1 F1</string>
+    <string name="devicetype_teclast_h30">Teclast H30</string>
+    <string name="pref_header_auto_export">Automatisch exporteren</string>
+    <string name="pref_title_auto_export_enabled">Automatisch exporteren ingeschakeld</string>
+    <string name="pref_title_auto_export_location">Locatie exporteren</string>
+    <string name="pref_title_auto_export_interval">Interval voor exporteren</string>
+    <string name="pref_summary_auto_export_interval">Exporteer iedere %d uur</string>
+    <string name="notif_export_failed_title">Database exporteren mislukt! Controleer uw instellingen.</string>
+    <string name="spanish">Spaans</string>
+    <string name="choose_auto_export_location">Kies export locatie</string>
+    <string name="notification_channel_name">Algemeen</string>
+    <string name="blacklist_all_for_notifications">Zwarte lijst voor alle meldingen</string>
+    <string name="whitelist_all_for_notifications">Witte lijst voor alle meldingen</string>
+    <string name="fw_upgrade_notice_miband3">U staat op het punt om de %s-firmware op je Mi Band 3 te installeren.
+\n
+\nZorg ervoor dat u het .fw-bestand installeert, en daarna het .res-bestand. Uw band wordt opnieuw opgestart na het installeren van het .fw-bestand.
+\n
+\nOpmerking: U hoeft .res niet te installeren als het precies hetzelfde is als het eerder ge茂nstalleerde .res.
+\n
+\nGA VERDER OP EIGEN RISICO!</string>
+    <string name="pref_title_pebble_gatt_clientonly">Alleen GATT-cli毛nt</string>
+    <string name="pref_summary_pebble_gatt_clientonly">Dit is alleen voor Pebble 2 en experimenteel, probeer dit als je connectiviteitsproblemen hebt</string>
+    <string name="pref_auto_fetch">Activiteitsgegevens automatisch ophalen</string>
+    <string name="pref_auto_fetch_summary">Ophalen gebeurt bij het ontgrendelen van het scherm. Werkt alleen als er een vergrendelmechanisme is ingesteld!</string>
+    <string name="pref_auto_fetch_limit_fetches">Minimaal tijdsverloop tussen de ophaalbeurten</string>
+    <string name="pref_auto_fetch_limit_fetches_summary">Om de %d minuten ophalen</string>
+    <string name="on">Aan</string>
+    <string name="controlcenter_start_activity_tracks">Uw activiteit tracks</string>
+    <string name="activity_type_not_measured">Niet gemeten</string>
+    <string name="activity_type_activity">Activiteit</string>
+    <string name="activity_type_light_sleep">Lichte slaap</string>
+    <string name="activity_type_deep_sleep">Diepe slaap</string>
+    <string name="activity_type_not_worn">Apparaat niet gedragen</string>
+    <string name="activity_type_running">Hardlopen</string>
+    <string name="activity_type_walking">Wandelen</string>
+    <string name="activity_type_swimming">Zwemmen</string>
+    <string name="activity_type_unknown">Onbekend activiteit</string>
+    <string name="activity_summaries">Sportactiviteiten</string>
+    <string name="activity_type_biking">Fietsen</string>
+    <string name="activity_type_treadmill">Loopband</string>
+    <string name="select_all">Selecteer alles</string>
+    <string name="share">Delen</string>
+    <string name="devicetype_miband3">Mi Band 3</string>
+    <string name="devicetype_q8">Q8</string>
+    <string name="devicetype_xwatch">XWatch</string>
+    <string name="menuitem_status">Status</string>
+    <string name="menuitem_activity">Trainingsgeschiedenis</string>
+    <string name="menuitem_weather">Weer</string>
+    <string name="menuitem_alarm">Alarm</string>
+    <string name="menuitem_timer">Aftellen</string>
+    <string name="menuitem_compass">Kompas</string>
+    <string name="menuitem_settings">Instellingen</string>
+    <string name="menuitem_alipay">Alipay</string>
+    <string name="controlcenter_calibrate_device">Kalibreer apparaat</string>
+    <string name="prefs_screen_orientation">Scherm orientatie</string>
+    <string name="horizontal">Horizontaal</string>
+    <string name="vertical">Verticaal</string>
+    <string name="watch9_pairing_tap_hint">Wanneer het horloge trilt, schud het of druk op de knop.</string>
+    <string name="you_slept">%1$s - %2$s</string>
+    <string name="you_did_not_sleep">Je hebt niet geslapen</string>
+    <string name="mi3_prefs_band_screen_unlock">Band scherm vrijgeven</string>
+    <string name="mi3_prefs_band_screen_unlock_summary">Veeg omhoog om het scherm vrij te geven</string>
+    <string name="norwegian_bokmal">Noors (Bokm氓l)</string>
+    <string name="russian">Russisch</string>
+    <string name="german">Duits</string>
+    <string name="italian">Italiaans</string>
+    <string name="french">Frans</string>
+    <string name="polish">Pools</string>
+    <string name="korean">Koreaans</string>
+    <string name="japanese">Japans</string>
+    <string name="reset_index">Reset ophaaldatum</string>
+    <string name="devicetype_mykronoz_zetime">MyKronoz ZeTime</string>
+    <string name="menuitem_notifications">Meldingen</string>
+    <string name="controlcenter_change_led_color">Wijzig ledkleur</string>
+    <string name="controlcenter_change_fm_frequency">Wijzig FM-frequentie</string>
+    <string name="debugactivity_really_factoryreset_title">Weet U zeker dat u de fabrieksinstellingen wilt terugzetten\?</string>
+    <string name="debugactivity_really_factoryreset">Het terugzetten van de fabrieksinstellingen zal alle data van het verbonden apparaat verwijderen (indien ondersteund). Xiaomi/Huami-apparaten veranderen ook van Bluetooth MAC-adres, zodat deze zullen verschijnen als nieuwe apparaten in Gadgetbridge.</string>
+    <string name="pref_title_notifications_timeout">Minimum tijd tussen meldingen</string>
+    <string name="pref_title_rtl">Van rechts naar links</string>
+    <string name="pref_summary_rtl">Schakel dit in als uw apparaat geen talen van rechts naar links kan weergeven</string>
+    <string name="pref_rtl_max_line_length">Rechts naar links maximum regellengte</string>
+    <string name="pref_rtl_max_line_length_summary">Verlengt of verkort de regerls waarin tekst van rechts naar links verdeeld wordt</string>
+    <string name="notif_battery_low">%1$s batterijniveau laag</string>
+    <string name="notif_battery_low_extended">%1$s batterijniveau laag: %2$s</string>
+    <string name="lack_of_sleep">Tekort aan slaap: %1$s</string>
+    <string name="overslept">Overslapen: %1$s</string>
+    <string name="no_limit">Geen limiet</string>
+    <string name="seconds_5">5 seconden</string>
+    <string name="seconds_10">10 seconden</string>
+    <string name="seconds_20">20 seconden</string>
+    <string name="seconds_30">30 seconden</string>
+    <string name="minutes_1">1 minuut</string>
+    <string name="minutes_5">5 minuten</string>
+    <string name="minutes_10">10 minuten</string>
+    <string name="minutes_30">30 minuten</string>
+    <string name="lack_of_step">Tekort aan stappen: %1$d</string>
+    <string name="overstep">Overschrijding aantal stappen: %1$d</string>
+    <string name="live_activity_max_heart_rate">Huidige / Max hartslag: %1$d / %2$d</string>
+    <string name="mi3_prefs_night_mode">Nacht modus</string>
+    <string name="mi3_prefs_night_mode_summary">Verlaag \'s nachts automatisch de scherm helderheid</string>
+    <string name="activity_prefs_charts">Grafiekinstellingen</string>
+    <string name="activity_prefs_chart_max_heart_rate">Maximum hartslag</string>
+    <string name="activity_prefs_chart_min_heart_rate">Minimum hartslag</string>
+    <string name="ok">OK</string>
+    <string name="mi3_night_mode_sunset">Bij zonsondergang</string>
+    <string name="devicetype_id115">ID115</string>
+    <string name="devicetype_watch9">Watch 9</string>
+    <string name="devicetype_roidmi">Roidmi</string>
+    <string name="devicetype_roidmi3">Roidmi 3</string>
+    <string name="menuitem_music">Muziek</string>
+    <string name="menuitem_more">Meer</string>
+    <string name="watch9_time_minutes">Minuten:</string>
+    <string name="watch9_time_hours">Uren:</string>
+    <string name="watch9_time_seconds">Seconden:</string>
+    <string name="watch9_calibration_hint">Stel de tijd in die uw apparaat op dit moment weergeeft.</string>
+    <string name="watch9_calibration_button">Kalibreer</string>
+    <string name="title_activity_watch9_pairing">Watch 9 koppelen</string>
+    <string name="title_activity_watch9_calibration">Watch 9 kalibratie</string>
+    <string name="pref_title_contextual_arabic">Contextueel Arabisch</string>
+    <string name="pref_summary_contextual_arabic">Schakel dit in om contextueel Arabisch te ondersteunen</string>
+    <string name="preferences_rtl_settings">Rechts naar links ondersteuning</string>
+    <string name="share_log">Deel log</string>
+    <string name="share_log_warning">Hou er alsjeblieft rekening mee dat Gadgetbridge bestanden logt die veel persoonlijke informatie kunnen bevatten, inclusief maar niet gelimiteerd tot gezondheidsgegevens, unieke identificatiegegevens (zoals het MAC-adres van een apparaat), muziekvoorkeuren, enz. Overweeg het handmatig verwijderen van deze gegevens uit dit bestand alvorens deze te verzenden naar een publiek foutrapport.</string>
+    <string name="warning">Waarschuwing!</string>
+    <string name="no_data">Geen data</string>
+    <string name="preferences_led_color">Ledkleur</string>
+    <string name="preferences_fm_frequency">FM-frequentie</string>
+    <string name="pref_invalid_frequency_title">Ongeldige frequentie</string>
+    <string name="pref_invalid_frequency_message">Voer een frequentie in tussen 87,5 en 108,0</string>
+    <string name="language_and_region_prefs">Taal en regio instellingen</string>
+    <string name="devicetype_casiogb6900">Casio GB-6900</string>
+    <string name="title_activity_notification_filter">Meldingen filter</string>
+    <string name="edittext_notification_filter_words_hint">Voer gewenste woorden in, elk op een nieuwe regel</string>
+    <string name="toast_notification_filter_saved_successfully">Meldingen filter opgeslagen</string>
+    <string name="filter_mode_none">Niet filteren</string>
+    <string name="filter_mode_whitelist">Laat zien wanneer woorden voorkomen</string>
+    <string name="filter_mode_blacklist">Blokkeer wanneer woorden voorkomen</string>
+    <string name="filter_submode_at_least_one">Minstens een van de woorden</string>
+    <string name="filter_submode_all">Alle woorden</string>
+    <string name="toast_notification_filter_words_empty_hint">Voer minstens een woord in</string>
+    <string name="filter_mode">Filtermodus</string>
+    <string name="mode_configuration">Modus instellingen</string>
+    <string name="save_configuration">Configuratie opslaan</string>
+    <string name="appwidget_not_connected">Niet verbonden, alarm niet gezet.</string>
+    <string name="activity_type_exercise">Oefenen</string>
+    <string name="prefs_disconnect_notification">Verbroken verbinding melding</string>
+    <string name="zetime_title_settings">ZeTime instellingen</string>
+    <string name="zetime_title_heartrate">Hartslag instellingen</string>
+    <string name="zetime_title_screentime">Tijd dat scherm aan is in seconden</string>
+    <string name="zetime_title_heart_rate_alarm">Hartslag alarm</string>
+    <string name="zetime_title_heart_rate_alarm_summary">Het horloge waarschuwt je zodra je hartslag de grenswaarden overschrijdt.</string>
+    <string name="zetime_heart_rate_alarm_enable">Hartslagalarm inschakelen</string>
+    <string name="activity_prefs_alarm_max_heart_rate">Maximale hartslag</string>
+    <string name="activity_prefs_alarm_min_heart_rate">Minimale hartslag</string>
+    <string name="zetime_analog_mode">Analoge modus</string>
+    <string name="zetime_analog_mode_hands">Alleen wijzers</string>
+    <string name="zetime_analog_mode_handsandsteps">Wijzers en streepjes</string>
+    <string name="zetime_activity_tracking">Activiteit monitoring</string>
+    <string name="zetime_activity_tracking_summary">Schakel activiteit monitoring in, telt je stappen etc.</string>
+    <string name="zetime_handmove_display">Handbeweging</string>
+    <string name="zetime_handmove_display_summary">Draai je pols om het scherm te activeren of deactiveren.</string>
+    <string name="zetime_calories_type">Soort calorie毛n</string>
+    <string name="zetime_calories_type_active">Alleen calorie毛n verbrand gedurende activiteiten</string>
+    <string name="zetime_calories_type_all">Verbrande calorie毛n actief en inactief</string>
+    <string name="zetime_date_format">Datumformaat</string>
+    <string name="zetime_date_format_1">YY/MM/DD</string>
+    <string name="zetime_date_format_2">DD/MM/YY</string>
+    <string name="zetime_date_format_3">MM/DD/YY</string>
+    <string name="zetime_prefs_inactivity_repetitions">Herhalingen</string>
+    <string name="zetime_prefs_inactivity_mo">Maandag</string>
+    <string name="zetime_prefs_inactivity_tu">Dinsdag</string>
+    <string name="zetime_prefs_inactivity_we">Woensdag</string>
+    <string name="zetime_prefs_inactivity_th">Donderdag</string>
+    <string name="zetime_prefs_inactivity_fr">Vrijdag</string>
+    <string name="zetime_prefs_inactivity_sa">Zaterdag</string>
+    <string name="zetime_prefs_inactivity_su">Zondag</string>
+    <string name="zetime_title_alarm_signaling">Stel het type signalering voor het alarm in</string>
+    <string name="zetime_signaling_none">Stil</string>
+    <string name="zetime_signaling_vibrate">Continu trillen</string>
+    <string name="zetime_signaling_beep">Continu piepen</string>
+    <string name="zetime_signaling_vibrate_beep">Continu trillen en piepen</string>
+    <string name="zetime_signaling_vibrate_once">Eenmaal trillen</string>
+    <string name="zetime_signaling_vibrate_twice">Tweemaal trillen</string>
+    <string name="zetime_signaling_beep_once">Eenmaal piepen</string>
+    <string name="zetime_signaling_beep_twice">Tweemaal piepen</string>
+    <string name="zetime_signaling_vibrate_beep_once">Eenmaal trillen en piepen</string>
+    <string name="pref_screen_notification_profile_missed_call">Gemiste oproep melding</string>
+    <string name="pref_screen_notification_profile_calendar">Agenda melding</string>
+    <string name="pref_screen_notification_profile_inactivity">Inactiviteit melding</string>
+    <string name="pref_screen_notification_profile_low_power">Batterij laag waarschuwing</string>
+    <string name="pref_screen_notification_profile_anti_loss">Anti-verlies waarschuwing</string>
+    <string name="interval_fifteen_minutes">elke 15 minuten</string>
+    <string name="interval_forty_five_minutes">elke 45 minuten</string>
+    <string name="activity_prefs_calories_burnt">Dagelijks doel: calorie毛n verbrand</string>
+    <string name="activity_prefs_distance_meters">Dagelijks doel: afstand in meters</string>
+    <string name="activity_prefs_activetime_minutes">Dagelijks doel: tijd actief in minuten</string>
+    <string name="devicetype_miscale2">Mi Scale 2</string>
+    <string name="pref_title_support_voip_calls">Sta VoIP app calls toe</string>
+    <string name="title_activity_device_specific_settings">Apparaatspecifieke instellingen</string>
+    <string name="pref_title_authkey">Authenticatiesleutel</string>
+    <string name="pref_summary_authkey">Wijzig de authenticatiesleutel in een algemene sleutel op al uw Android-apparaten waarmee u verbinding wilt maken. De vorige standaardsleutel voor alle apparaten is 0123456789@ABCDE</string>
+    <string name="devicetype_bfh16">BFH-16</string>
+    <string name="fw_upgrade_notice_amazfitcor2">U staat op het punt om de firmware %s te installeren op uw Amazfit Cor 2.
+\n
+\nZorg ervoor dat u het .fw-bestand installeert, daarna het .res-bestand. Uw horloge wordt opnieuw gestart na het installeren van het .fw-bestand.
+\n
+\nOpmerking: U hoeft de .res niet te installeren als deze exact dezelfde is als degene die ervoor al was ge茂nstalleerd.
+\n
+\nGA VERDER OP EIGEN RISICO!
+\n
+\nVOLLEDIG ONGETEST, MISSCHIEN MOET JE EEN BEATS_W_FIRMWARE INSTALLEREN ALS JE DEVICE NAAM \"Amazfit Band 2\" IS</string>
+    <string name="dutch">Nederlands</string>
+    <string name="turkish">Turks</string>
+    <string name="ukrainian">Oekra茂ens</string>
+    <string name="arabic">Arabisch</string>
+    <string name="indonesian">Indonesisch</string>
+    <string name="thai">Thais</string>
+    <string name="vietnamese">Vietnamees</string>
+    <string name="portuguese">Portugees</string>
+    <string name="devicetype_amazfit_cor2">Amazfit Cor 2</string>
+    <string name="devicetype_miband4">Mi Band 4</string>
+    <string name="fw_upgrade_notice_miband4">U staat op het punt om de %s-firmware op je Mi Band 4 te installeren.
+\n
+\nZorg ervoor dat u het .fw-bestand installeert, en daarna het .res-bestand. Uw band wordt opnieuw opgestart na het installeren van het .fw-bestand.
+\n
+\nOpmerking: U hoeft .res niet te installeren als het precies hetzelfde is als het eerder ge茂nstalleerde .res.
+\n
+\nGA VERDER OP EIGEN RISICO!</string>
+    <string name="prefs_hr_alarm_activity">Hartslag alarm tijdens sportactiviteit</string>
+    <string name="prefs_hr_alarm_low">Ondergrens</string>
+    <string name="prefs_hr_alarm_high">Bovengrens</string>
+    <string name="average">Gemiddelde: %1$s</string>
+    <string name="pref_header_charts">Grafiekinstellingen</string>
+    <string name="pref_title_charts_average">Toon gemiddelden in de grafieken</string>
+    <string name="pref_title_charts_range">Grafiekbereik</string>
+    <string name="pref_charts_range_on">Grafiekbereik ingesteld op 1 maand</string>
+    <string name="pref_charts_range_off">Grafiekbereik ingesteld op een week</string>
+    <string name="weekstepschart_steps_a_month">Stappen per maand</string>
+    <string name="weeksleepchart_sleep_a_month">Slaap per maand</string>
+    <string name="devicetype_mijia_lywsd02">Mijia Smart Clock</string>
+    <string name="menuitem_nfc">NFC</string>
+    <string name="pref_summary_expose_hr">Sta andere apps toe HR-data te benaderen in realtime terwijl Gadgetbridge verbonden is</string>
+    <string name="pref_title_expose_hr">realtime HR-toegang door derden</string>
+    <string name="pref_title_use_custom_font">Gebruik aangepast lettertype</string>
+    <string name="pref_summary_use_custom_font">Selecteer dit als je apparaat aangepaste lettertypesoftware bevat met emoji-ondersteuning</string>
+    <string name="activity_db_management_autoexport_explanation">Database autoexport-locatie is ingesteld op:</string>
+    <string name="activity_db_management_autoexport_label">Auto Exporteren</string>
+    <string name="activity_DB_ExportButton">Exporteer gegevens</string>
+    <string name="activity_DB_import_button">Importeer gegevens</string>
+    <string name="activity_DB_test_export_button">Voer nu AutoExport uit</string>
+    <string name="activity_DB_test_export_message">Bezig database te exporteren鈥�</string>
+    <string name="activity_DB_delete_legacy_button">Verwijder oude DB</string>
+    <string name="activity_DB_empty_button">Database legen</string>
+    <string name="activity_db_management_empty_DB">Leeg Database</string>
+    <string name="activity_db_management_exportimport_label">Exporteer en importeer</string>
+    <string name="activity_db_management_empty_db_warning">Waarschuwing! Als je op deze knop drukt wis je je database en start je met een schone lei.</string>
+    <string name="appwidget_sleep_alarm_widget_label">Slaapalarm</string>
+    <string name="widget_listing_label">Status en alarmen</string>
+    <string name="widget_set_alarm_after">Zet alarm na:</string>
+    <string name="widget_5_minutes">5 minuten</string>
+    <string name="widget_10_minutes">10 minuten</string>
+    <string name="widget_20_minutes">20 minuten</string>
+    <string name="widget_1_hour">1 uur</string>
+    <plurals name="widget_alarm_target_hours">
+        <item quantity="one">%d uur</item>
+        <item quantity="other">%d uren</item>
+    </plurals>
+    <string name="pref_display_add_device_fab">Verbind nieuw apparaat knop</string>
+    <string name="pref_display_add_device_fab_on">Altijd zichtbaar</string>
+    <string name="pref_display_add_device_fab_off">Alleen zichtbaar als er geen apparaat toegevoegd is</string>
+    <string name="activity_error_no_app_for_gpx">Om de route van de activiteit te zien: installeer een app die GPX bestanden kan tonen.</string>
+    <string name="preferences_makibes_hr3_settings">Makibes HR3 instellingen</string>
+    <string name="devicetype_makibes_hr3">Makibes HR3</string>
+    <string name="devicetype_amazfit_bip_lite">Amazfit Bip Lite</string>
+    <string name="prefs_find_phone">Vind telefoon</string>
+    <string name="prefs_enable_find_phone">\\\'Vind telefoon\\\' inschakelen</string>
+    <string name="prefs_find_phone_summary">Gebruik je band om de beltoon van je telefoon af te spelen.</string>
+    <string name="prefs_find_phone_duration">Belduur in seconden</string>
+    <string name="maximum_duration">Duur</string>
+    <string name="discovery_need_to_enter_authkey">Dit apparaat heeft een geheime authenticatiesleutel nodig, druk lang op het apparaat om deze in te voeren. Lees de wiki.</string>
+    <string name="fw_upgrade_notice_amazfitbip_lite">U staat op het punt om de firmware %s te installeren op uw Amazfit Bip Lite.
+\n
+\nZorg ervoor dat u het .fw-bestand installeert, daarna het .res-bestand en als laatste het .gps-bestand. Uw horloge wordt opnieuw gestart na het installeren van het .fw-bestand.
+\n
+\nOpmerking: U hoeft de .res niet te installeren als deze exact dezelfde is als degene die ervoor al was ge茂nstalleerd.
+\n
+\nGA VERDER OP EIGEN RISICO!</string>
+    <string name="devicetype_amazfit_gtr">Amazfit GTR</string>
+    <string name="fw_upgrade_notice_amazfitgtr">U staat op het punt om de firmware %s te installeren op uw Amazfit GTR.
+\n
+\nZorg ervoor dat u het .fw-bestand installeert, daarna het .res-bestand en als laatste het .gps-bestand. Uw horloge wordt opnieuw gestart na het installeren van het .fw-bestand.
+\n
+\nOpmerking: U hoeft de .res niet te installeren als deze exact dezelfde is als degene die ervoor al was ge茂nstalleerd.
+\n
+\nGA VERDER OP EIGEN RISICO!</string>
+    <string name="pref_chart_heartrate_color_red">Rood</string>
+    <string name="pref_chart_heartrate_color_orange">Oranje</string>
+    <string name="pref_title_chart_heartrate_color">Kleur hartslag</string>
+    <string name="pref_title_chart_sleep_rolling_24_hour">Slaapbereik</string>
+    <string name="pref_chart_sleep_rolling_24_on">Afgelopen 24 uur</string>
+    <string name="pref_chart_sleep_rolling_24_off">Van middag tot middag</string>
+    <string name="devicetype_amazfit_gts">Amazfit GTS</string>
+    <string name="fw_upgrade_notice_amazfitgts">U staat op het punt om de firmware %s te installeren op uw Amazfit GTS.
+\n
+\nZorg ervoor dat u het .fw-bestand installeert, daarna het .res-bestand en als laatste het .gps-bestand. Uw horloge wordt opnieuw gestart na het installeren van het .fw-bestand.
+\n
+\nOpmerking: U hoeft de .res niet te installeren als deze exact dezelfde is als degene die ervoor al was ge茂nstalleerd.
+\n
+\nGA VERDER OP EIGEN RISICO!</string>
+    <string name="mi2_prefs_button_press_broadcast_default_value" translatable="false">nodomain.freeyourgadget.gadgetbridge.ButtonPressed</string>
+    <string name="devicetype_qhybrid">Fossil Q Hybrid</string>
+    <string name="preferences_qhybrid_settings">Q Hybrid instellingen</string>
+    <string name="watch_not_connected">Horloge niet verbonden</string>
+    <string name="qhybrid_vibration_strength">trillingssterkte:</string>
+    <string name="qhybrid_goal_in_steps">Doel in stappen</string>
+    <string name="qhybrid_time_shift">tijdsverschuiving</string>
+    <string name="qhybrid_second_timezone_offset_relative_to_utc">tweede tijdzone offset ten opzichte van UTC</string>
+    <string name="qhybrid_overwrite_buttons">knoppen overschrijven</string>
+    <string name="qhybrid_use_activity_hand_as_notification_counter">gebruik activiteitswijzer als notificatieteller</string>
+    <string name="qhybrid_prompt_million_steps">Zet het aantal stappen op een miljoen om dat te activeren.</string>
+    <string name="qhybrid_buttons_overwrite_success">Knoppen overschreven</string>
+    <string name="qhybrid_buttons_overwrite_error">Fout bij het overschrijven van knoppen</string>
+    <string name="qhybrid_offset_timezone">offset tijdzone met</string>
+    <string name="qhybrid_changes_delay_prompt">wijziging kan enkele seconden duren鈥�</string>
+    <string name="qhybrid_offset_time_by">offset tijdzone met</string>
+    <string name="pref_disable_new_ble_scanning">Nieuwe BLE-scanning uitschakelen</string>
+    <string name="pref_summary_disable_new_ble_scanning">Vink deze optie aan als uw apparaat niet kan worden gevonden tijdens discovery</string>
+    <string name="devicetype_banglejs">Bangle.js</string>
+    <string name="devicetype_y5">Y5</string>
+    <string name="prefs_button_single_press_action_selection_title">Event 1 actie</string>
+    <string name="prefs_button_double_press_action_selection_title">Event 2 actie</string>
+    <string name="prefs_button_triple_press_action_selection_title">Event 3 actie</string>
+    <string name="prefs_button_variable_actions">Gedetailleerde knopdrukinstellingen</string>
+    <string name="prefs_button_long_press_action_selection_title">Lange druk op de knop actie</string>
+    <string name="alarm_snooze">Sluimeren</string>
+    <string name="error_no_location_access">Voor een goede werking van het scannen moet de locatietoegang worden verleend en ingeschakeld</string>
+    <string name="devicetype_itag">iTag</string>
+    <string name="pref_title_allow_high_mtu">Hoge MTU toestaan</string>
+    <string name="pref_summary_allow_high_mtu">Verhoogt de overdrachtssnelheid, maar werkt mogelijk niet op sommige Android-apparaten.</string>
+    <string name="pref_summary_sync_calendar">Schakelt agendameldingen in, zelfs wanneer niet verbonden</string>
+    <string name="pref_title_sync_caldendar">Agenda-afspraken synchroniseren</string>
+    <string name="hr_widget_heart_rate">Hartslag</string>
+    <string name="hr_widget_steps">Stappen</string>
+    <string name="hr_widget_date">Datum</string>
+    <string name="hr_widget_active_minutes">Minuten actief</string>
+    <string name="hr_widget_calories">Calorie毛n</string>
+    <string name="hr_widget_battery">Batterij</string>
+    <string name="hr_widget_weather">Weer</string>
+    <string name="hr_widget_nothing">Niets</string>
+    <string name="find_lost_device_you_found_it">Gevonden!</string>
+    <string name="pref_title_force_white_color_scheme">Zwart op wit kleurenschema forceren</string>
+    <string name="pref_summary_force_white_color_scheme">Nuttig als je horloge donkere wijzers heeft</string>
+    <string name="find_my_phone_notification">Vind mijn telefoon</string>
+    <string name="notification_channel_high_priority_name">Hoge prioriteit</string>
+    <string name="pref_title_custom_deviceicon">Toon apparaatspecifieke meldingspictogram</string>
+    <string name="pref_summary_custom_deviceicon">Toon een apparaatspecifiek Android-meldpictogram in plaats van het Gadgetbridge-pictogram indien verbonden</string>
+    <string name="hr_appname_wellness">Welzijn</string>
+    <string name="hr_appname_workout">Training</string>
+    <string name="hr_appname_stopwatch">Stopwatch</string>
+    <string name="hr_appname_commute">Woon-werkverkeer</string>
+    <string name="pref_title_vibration_strength">Trillingssterkte</string>
+    <string name="devicetype_amazfit_bips">Amazfit Bip S</string>
+    <string name="pref_title_relax_firmware_checks">Schakel dit in als u een firmware wilt flashen die niet voor uw apparaat bedoeld is (op eigen risico)</string>
+    <string name="pref_summary_relax_firmware_checks">Minder strenge firmwarecontroles</string>
+    <string name="pref_header_auto_fetch">Automatisch ophalen</string>
+    <string name="pref_qhybrid_title_widget_draw_circles">Teken widget cirkels</string>
+    <string name="pref_qhybrid_save_raw_activity_files">Bewaar onbewerkte activiteitsbestanden</string>
+    <string name="menuitem_workout">Training</string>
+    <string name="menuitem_eventreminder">Gebeurtenis herinnering</string>
+    <string name="menuitem_hr">Hartslag</string>
+    <string name="menuitem_pai">PAI</string>
+    <string name="devicetype_watchxplus">Watch X Plus</string>
+    <string name="devicetype_watchx">Watch X</string>
+    <string name="power_mode_watch">Alleen horloge</string>
+    <string name="power_mode_saving">Energiebesparing</string>
+    <string name="power_mode_normal">Normaal</string>
+    <string name="power_mode_title">Horloge energie mode</string>
+    <string name="prefs_sensors_button_bp_calibration_sum">Druk hier om te beginnen met kalibreren</string>
+    <string name="prefs_sensors_button_bp_calibration">Kalibratie</string>
+    <string name="pref_sensors_bp_calibration_high">Bloeddruk SYSTOLISCH (hoog)</string>
+    <string name="pref_sensors_bp_calibration_low">Bloeddruk DIASTOLISCH (laag)</string>
+    <string name="pref_sensors_bp_calibration">Bloeddrukkalibratie</string>
+    <string name="pref_title_sensors_altitude">Hoogtekalibratie</string>
+    <string name="pref_header_sensors_calibration">Sensorkalibratie</string>
+    <string name="pref_summary_device_spec_settings_title_force_time">Forceer automatische tijdsynchronisatie bij opnieuw verbinden. Analoge wijzers kunnen een onjuiste tijd aangeven!</string>
+    <string name="pref_title_device_spec_settings_force_time">Forceer tijdsynchronisatie</string>
+    <string name="pref_header_device_spec_settings">Apparaatinstellingen</string>
+    <string name="pref_summary_notifications_and_calls_title_shake_reject">Dupliceert horlogeknop actie</string>
+    <string name="prefs_notifications_and_calls_shake_reject">Schud pols: negeer / weiger oproep</string>
+    <string name="pref_summary_notifications_and_calls_title_reject">Uit - negeren, Aan - weigeren</string>
+    <string name="prefs_notifications_and_calls_reject">Knop oproep negeren / weigeren</string>
+    <string name="pref_header_notifications_and_calls_callhandling">Gespreksafhandeling</string>
+    <string name="pref_title_notifications_and_calls_repeat_on_misscall">Herhaal gedurende X minuten</string>
+    <string name="pref_summary_notifications_and_calls_enable_misscall">Herhaalt elke minuut</string>
+    <string name="pref_notifications_and_calls_enable_misscall">Melding voor een gemist gesprek</string>
+    <string name="prefs_notifications_and_calls_continious_ring">Melding tijdens het bellen</string>
+    <string name="pref_title_notifications_and_calls_repeat_on_call">Herhaal oproepmelding</string>
+    <string name="pref_header_notifications_and_calls">Meldingen en oproepen</string>
+    <string name="title_activity_LenovoWatch_calibration">Watch X Plus-kalibratie</string>
+    <string name="pref_title_device_spec_settings_show_raw_graph">Toon onbewerkte gegevens op activiteitengrafiek</string>
+    <string name="hungarian">Hongaars</string>
+    <string name="romanian">Roemeens</string>
+    <string name="menuitem_unknown">Onbekend</string>
+    <string name="czesh">Tsjechisch</string>
+    <string name="swedish">Zweeds</string>
+    <string name="hebrew">Hebreeuws</string>
+    <string name="greek">Grieks</string>
+    <string name="bip_prefs_shotcuts_summary">Kies de snelkoppelingen op het scherm van de band</string>
+    <string name="bip_prefs_shortcuts">Snelkoppelingen</string>
+    <string name="pref_theme_system">Systeem</string>
+    <string name="hr_widget_last_notification">Laatste melding</string>
+    <string name="controlcenter_set_alias">Stel alias in</string>
+    <string name="fw_upgrade_notice_amazfit_trex">U staat op het punt om de firmware %s te installeren op uw Amazfit T-Rex. 
+\n 
+\nZorg ervoor dat u het .fw-bestand installeert, daarna het .res-bestand en als laatste het .gps-bestand. Uw horloge wordt opnieuw gestart na het installeren van het .fw-bestand. 
+\n 
+\nOpmerking: U hoeft de .res niet te installeren als deze exact dezelfde is als degene die ervoor al was ge茂nstalleerd. 
+\n 
+\nGA VERDER OP EIGEN RISICO!</string>
+    <string name="devicetype_amazfit_trex">Amazfit T-Rex</string>
+    <string name="about_additional_contributions">Hartelijk dank aan alle niet-vermelde bijdragers voor het bijdragen van code, vertalingen, ondersteuning, idee毛n, motivatie, foutrapporten, geld 鈥� 鉁�</string>
+    <string name="about_additional_device_support">Extra apparaatondersteuning</string>
+    <string name="about_contributors">Bijdragers</string>
+    <string name="about_core_team_title">Kernteam (in volgorde van eerste codebijdrage)</string>
+    <string name="about_description_generic">Cloudloze vrije vervanging voor de gesloten Android-applicaties van gadgetfabrikanten.</string>
+    <string name="about_activity_title_generic">Over Gadgetbridge</string>
+    <string name="about_title">Over</string>
+    <string name="pref_title_weather_summary">Gebruikt voor de LineageOS-weeraanbieder, andere Android versies moeten een app zoals \"Weather notification\" gebruiken. Meer informatie is te vinden in de Gadgetbridge wiki.</string>
+    <string name="menuitem_worldclock">Wereld Klok</string>
+    <string name="error_background_service_reason_truncated">Het starten van de achtergrondservice is mislukt omdat鈥�</string>
+    <string name="error_background_service_reason">Het starten van de achtergrondservice is mislukt door een probleem. Klik hier voor meer informatie.\n\nFout:</string>
+    <string name="error_background_service">Kan de achtergrondservice niet starten</string>
+    <string name="error_version_check_extreme_caution">LET OP: Fout bij het controleren van versie-informatie! Je moet niet doorgaan! Zag versienaam \"%s\"</string>
+    <string name="permission_granting_mandatory">Al deze toestemmingen zijn vereist en er kan instabiliteit ontstaan als ze niet worden verleend</string>
+    <string name="menuitem_stress">Spanning</string>
+    <string name="menuitem_cycles">Cyclus volgen</string>
+    <string name="menuitem_breathing">Ademhaling</string>
+    <string name="devicetype_miband5">Mi Band 5</string>
+    <string name="fw_upgrade_notice_miband5">U staat op het punt om de %s-firmware op je Mi Band 5 te installeren.
+\n
+\nZorg ervoor dat u het .fw-bestand installeert, en daarna het .res-bestand. Uw band wordt opnieuw opgestart na het installeren van het .fw-bestand.
+\n
+\nOpmerking: U hoeft .res niet te installeren als het precies hetzelfde is als het eerder ge茂nstalleerde .res.
+\n
+\nGA VERDER OP EIGEN RISICO!</string>
+    <string name="devicetype_tlw64">TLW64</string>
+    <string name="error_location_enabled_mandatory">Locatie moet zijn ingeschakeld om naar apparaten te scannen</string>
+    <string name="ignore_bonded_devices_description">Als u deze optie inschakelt, worden apparaten genegeerd die bij het scannen al zijn gekoppeld</string>
+    <string name="ignore_bonded_devices">Negeer gekoppelde apparaten</string>
+    <string name="error_retrieving_devices_database">Fout bij het ophalen van apparaten uit de database</string>
+    <string name="error_setting_alias">Fout bij instellen van alias:</string>
+    <string name="error_exporting_device_preferences">Fout bij het exporteren van apparaatspecifieke voorkeuren</string>
+    <string name="pref_check_permission_status_summary">Controleer en vraag om ontbrekende machtigingen, zelfs als ze mogelijk niet direct nodig zijn. Schakel dit alleen uit als uw apparaten geen van deze functies ondersteunen. Het niet verlenen van een toestemming kan problemen veroorzaken!</string>
+    <string name="pref_check_permission_status">Machtigingsstatus controleren</string>
+    <string name="device_requires_key">SLEUTEL VEREIST, LANG INDRUKKEN OM IN TE VOEREN</string>
+    <string name="device_is_currently_bonded">REEDS GEKOPPELD</string>
+    <string name="companiondevice_pairing_details">Schakelt de nieuwe CompanionDevice API-ondersteuning in (heeft alleen een effect op Android 8 of hoger), wat de betrouwbaarheid verhoogt als de service op de achtergrond opnieuw moet worden opgestart, vereist opnieuw koppelen met Gadgetbridge om effect te hebben</string>
+    <string name="companiondevice_pairing">Companion-apparaat koppelen</string>
+    <string name="require_location_provider">Locatie moet ingeschakeld zijn</string>
+    <string name="minAltitude">Minimaal</string>
+    <string name="maxAltitude">Maximaal</string>
+    <string name="activity_summary_yesterday">Gisteren</string>
+    <string name="activity_summary_today">Vandaag</string>
+    <string name="activity_summary_edit_name_title">Label bewerken</string>
+    <string name="activity_type_pingpong">Vingerpong</string>
+    <string name="activity_type_basketball">Basketbal</string>
+    <string name="activity_type_soccer">Voetbal</string>
+    <string name="activity_type_yoga">Yoga</string>
+    <string name="activity_type_elliptical_trainer">Elliptische Trainer</string>
+    <string name="activity_type_indoor_cycling">Binnenshuis fietsen</string>
+    <string name="activity_type_swimming_openwater">Zwemmen (Open water)</string>
+    <string name="sonyswr12_settings_title">Sony SWR12 instellingen</string>
+    <string name="sonyswr12_settings_low_vibration">Laag trillingsniveau ingeschakeld</string>
+    <string name="sonyswr12_settings_stamina">Energiebesparende modus is ingeschakeld</string>
+    <string name="sonyswr12_settings_alarm_interval">Slimme alarminterval in minuten</string>
+    <string name="sonyswr12_settings_low_vibration_summary">Schakel lage trillingsintensiteit op de band in</string>
+    <string name="sonyswr12_settings_stamina_summary">De energiebesparende modus schakelt de periodieke automatische hartslagmeting uit, waardoor de werktijd toeneemt</string>
+    <string name="sonyswr12_settings_alarm_interval_summary">Slimme alarminterval is het interval v贸贸r het ge茂nstalleerde alarm. In dit interval probeert het apparaat de lichtste slaapfase te detecteren om de gebruiker wakker te maken</string>
+    <string name="prefs_autoremove_message">SMS-meldingen automatisch verwijderen</string>
+    <string name="prefs_fake_ring_duration">Continu rinkelen nadoen</string>
+    <string name="prefs_operating_sounds">Bedieningsgeluiden</string>
+    <string name="prefs_key_vibration">Knoptrilling</string>
+    <string name="prefs_autolight">Automatisch licht</string>
+    <string name="qhybrid_calibration_align_hint">Gebruik de onderstaande knoppen om de wijzers in te stellen op 12:00.</string>
+    <string name="prefs_events_forwarding_action_title">Actie uitvoeren</string>
+    <string name="gps_track">GPS-track</string>
+    <string name="Distance">Afstand</string>
+    <string name="Swimming">Zwemmen</string>
+    <string name="Strokes">Slagen</string>
+    <string name="mi">mi</string>
+    <string name="km">km</string>
+    <string name="bpm">bpm</string>
+    <string name="minutes_km">min/km</string>
+    <string name="seconds_m">sec/m</string>
+    <string name="seconds_km">sec/km</string>
+    <string name="calories_unit">kcal</string>
+    <string name="laps_unit">rondes</string>
+    <string name="swim_style">zwemstijl</string>
+    <string name="swolf_index">SWOLF-index</string>
+    <string name="seconds">sec</string>
+    <string name="strokes_second">slg/s</string>
+    <string name="strokes_unit">slg</string>
+    <string name="minutes_mi">min/mi</string>
+    <string name="mi_h">mi/u</string>
+    <string name="km_h">km/u</string>
+    <string name="meters_second">m/s</string>
+    <string name="steps_unit">stappen</string>
+    <string name="ft">ft</string>
+    <string name="cm">cm</string>
+    <string name="meters">m</string>
+    <string name="averageSpeed">Gemiddelde snelheid</string>
+    <string name="baseAltitude">Basishoogte</string>
+    <string name="flatSeconds">Vlak</string>
+    <string name="descentSeconds">Dalend</string>
+    <string name="ascentSeconds">Stijgend</string>
+    <string name="laps">Rondes</string>
+    <string name="swimStyle">Zwemstijl</string>
+    <string name="swolfIndex">SWOLF</string>
+    <string name="fw_upgrade_notice_amazfitvergel">Je staat op het punt om de %s -firmware op je Amazfit Verge Lite te installeren.
+\n
+\nZorg ervoor dat je het .fw-bestand installeert, vervolgens het .res-bestand en tenslotte het .gps-bestand. Je horloge zal opnieuw opstarten nadat je het .fw-bestand hebt ge茂nstalleerd.
+\n
+\nOpmerking: je hoeft .res en .gps niet te installeren als deze bestanden exact hetzelfde zijn als de eerder ge茂nstalleerde bestanden.
+\n
+\nVERDERGAAN IS OP EIGEN RISICO!</string>
+    <string name="fw_upgrade_notice_amazfitx">Je staat op het punt om de %s -firmware op je Amazfit X te installeren.
+\n
+\nZorg ervoor dat u het .fw-bestand installeert en daarna het .res-bestand. Je band zal opnieuw opstarten na het installeren van het .fw-bestand.
+\n
+\nOpmerking: je hoeft .res niet te installeren als het exact hetzelfde is als de eerder ge茂nstalleerde versie.
+\n
+\nVERDERGAAN IS OP EIGEN RISICO!</string>
+    <string name="about_links">Websites</string>
+    <string name="about_version">Versie %s</string>
+    <string name="widget_settings_select_device_title">Selecteer apparaat</string>
+    <string name="menuitem_pomodoro">Pomodoro-tracker</string>
+    <string name="menuitem_sleep">Slaap</string>
+    <string name="menuitem_goal">Activiteitdoel</string>
+    <string name="menuitem_stopwatch">Stopwatch</string>
+    <string name="menuitem_dnd">Niet storen</string>
+    <string name="menuitem_alexa">Alexa</string>
+    <string name="menuitem_takephoto">Camera-afstandsbediening</string>
+    <string name="menuitem_mutephone">Telefoon dempen</string>
+    <string name="menuitem_findphone">Vind telefoon</string>
+    <string name="menuitem_spo2">SpO2</string>
+    <string name="devicetype_amazfit_vergel">Amazfit Verge Lite</string>
+    <string name="devicetype_waspos">Wasp-os</string>
+    <string name="devicetype_casiogbx100">Casio GBX-100</string>
+    <string name="devicetype_zepp_e">Zepp E</string>
+    <string name="devicetype_amazfit_x">Amazfit X</string>
+    <string name="devicetype_amazfit_gts2e">Amazfit GTS 2e</string>
+    <string name="devicetype_amazfit_gts2_mini">Amazfit GTS 2 Mini</string>
+    <string name="devicetype_amazfit_gts2">Amazfit GTS 2</string>
+    <string name="devicetype_amazfit_gtr2e">Amazfit GTR 2e</string>
+    <string name="devicetype_amazfit_gtr2">Amazfit GTR 2</string>
+    <string name="devicetype_amazfit_bipupro">Amazfit Bip U Pro</string>
+    <string name="devicetype_amazfit_bipu">Amazfit Bip U</string>
+    <string name="devicetype_amazfit_bips_lite">Amazfit Bip S Lite</string>
+    <string name="devicetype_amazfit_neo">Amazfit Neo</string>
+    <string name="devicetype_amazfit_band5">Amazfit Band 5</string>
+    <string name="activity_type_strength_training">Krachttraining</string>
+    <string name="dbmanagementactivity_export_finished">Verwijdering voltooid</string>
+    <string name="activity_db_management_clean_export_directory_text">Ge毛xporteerde bestanden in de export/import-directory zijn toegankelijk voor elke app op uw apparaat. Mogelijk wilt u deze bestanden verwijderen na synchronisatie of back-up. Zorg ervoor dat u een back-up hebt voordat u ze verwijdert. GPX-bestanden, submappen en automatisch ge毛xporteerde databasebestanden (indien aanwezig) worden niet verwijderd. Het pad naar de export/import-directory is:</string>
+    <string name="activity_DB_clean_export_directory_warning_message">Echt bestanden verwijderen in de export/import-directory\?</string>
+    <string name="activity_DB_clean_export_directory_warning_title">Bestanden verwijderen in de export/import-directory\?</string>
+    <string name="activity_db_management_clean_export_directory_label">Verwijder bestanden in de export/import-map</string>
+    <string name="activity_DB_ShowContentButton">Toon de inhoud van de export/import-map</string>
+    <string name="activity_data_management_directory_content_title">Directory-inhoud exporteren/importeren</string>
+    <string name="dbmanagementactivity_export_confirmation">Echt gegevens exporteren\? Eerder ge毛xporteerde activiteitsgegevens (indien aanwezig) en voorkeuren worden overschreven.</string>
+    <string name="dbmanagementactivity_export_data_title">Exporteer data\?</string>
+    <string name="dbmanagementactivity_error_cleaning_export_directory">Fout bij het wissen van bestanden uit de exportdirectory %1$s</string>
+    <string name="charts_legend_heartrate_average">Gemiddelde hartslag</string>
+    <string name="activity_prefs_step_length_cm">Stapgrootte in cm</string>
+    <string name="charts_min_max_heartrate_popup">Laagste hartslag: %1$d
+\nHoogste hartslag: %2$d
+\nIntensiteit beweging: %3$s</string>
+    <string name="movement_intensity">Intensiteit beweging</string>
+    <string name="show_ongoing_activity">Toon lopende activiteit pop-up</string>
+    <string name="dialog_hide">Verbergen</string>
+    <string name="activity_list_summary_activities">Activiteiten</string>
+    <string name="activity_list_summary_intensity">Intensiteit
+\nbeweging</string>
+    <string name="activity_list_summary_active_time">Actieve tijd</string>
+    <string name="activity_list_summary_distance">Afstand</string>
+    <string name="activity_list_summary_active_steps">Actieve stappen</string>
+    <string name="prefs_charts_tabs_summary">Zichtbare grafiekentabs</string>
+    <string name="prefs_charts_tabs">Grafiekentab</string>
+    <string name="discovery_entered_invalid_authkey">De ingevoerde authenticatiesleutel is niet correct! Druk lang op het apparaat om hem aan te passen.</string>
+    <string name="pref_summary_connected_advertisement">Maakt het apparaat vindbaar via Bluetooth zelfs wanneer het verbonden is</string>
+    <string name="pref_title_connected_advertisement">Zichtbaar wanneer verbonden</string>
+    <string name="calendar_year">Jaar</string>
+    <string name="calendar_six_months">6 maanden</string>
+    <string name="calendar_month">Maand</string>
+    <string name="calendar_two_weeks">Twee weken</string>
+    <string name="calendar_week">Week</string>
+    <string name="calendar_day">Dag</string>
+    <string name="battery_level">Batterijniveau</string>
+    <string name="battery_detail_activity_title">Batterij-informatie</string>
+    <string name="devicetype_nut_mini">Nut mini</string>
+    <string name="medley">Wisselslag</string>
+    <string name="backstroke">Rugslag</string>
+    <string name="freestyle">Vrije slag</string>
+    <string name="breaststroke">Schoolslag</string>
+    <string name="activity_filter_to_placeholder">vandaag</string>
+    <string name="activity_filter_from_placeholder">ver verleden</string>
+    <string name="activity_summaries_all_devices">Alle apparaten</string>
+    <string name="sports_activity_quick_filter_select">Tijdsperiode</string>
+    <string name="sports_activity_quick_filter_30days">30 dagen</string>
+    <string name="sports_activity_quick_filter_7days">7 dagen</string>
+    <string name="sports_activity_quick_filter_last_month">Vorige maand</string>
+    <string name="sports_activity_quick_filter_this_month">Deze maand</string>
+    <string name="sports_activity_quick_filter_last_week">Vorige week</string>
+    <string name="sports_activity_quick_filter_this_week">Deze week</string>
+    <string name="activity_summaries_all_activities">Alle activiteiten</string>
+    <string name="activity_summaries_statistics">Statistieken</string>
+    <string name="no">Nee</string>
+    <string name="yes">Ja</string>
+    <string name="activity_filter_individual_items">Los geselecteerde items</string>
+    <string name="addto_filter">Toevoegen aan filter</string>
+    <string name="activity_filter_apply_filter">Filter toepassen</string>
+    <string name="activity_filter_name_contains">Naam</string>
+    <string name="activity_filter_filter_title">Filter</string>
+    <string name="activity_filter_reset_filter">Reset Filter</string>
+    <string name="activity_filter_date_to">Tot</string>
+    <string name="activity_filter_date_from">Van</string>
+    <string name="pref_header_statistics">Activiteitstatistieken</string>
+    <string name="pref_header_filter">Activiteitenfilter</string>
+    <string name="prefs_events_forwarding_broadcast_title">Broadcast-bericht</string>
+    <string name="prefs_events_forwarding_startnonwear">Bij niet-dragen</string>
+    <string name="prefs_events_forwarding_wokeup">Bij wakker worden</string>
+    <string name="prefs_events_forwarding_fellsleep">Bij in slaap vallen</string>
+    <string name="prefs_events_forwarding_title">Apparaatacties</string>
+    <string name="prefs_events_forwarding_summary">Gebruik apparaatevenementen om acties en Android Broadcasts te starten</string>
+    <string name="activity_detail_show_gps_label">Toon GPS-route</string>
+    <string name="activity_detail_duration_label">Duur</string>
+    <string name="activity_detail_end_label">Eind</string>
+    <string name="activity_detail_start_label">Begin</string>
+    <string name="Steps">Stappen</string>
+    <string name="Activity">Activiteit</string>
+    <string name="Speed">Snelheid</string>
+    <string name="Elevation">Hoogte</string>
+    <string name="averageLapPace">Gemiddelde rondetempo</string>
+    <string name="averageStrokesPerSecond">Gemiddelde slagen</string>
+    <string name="averageStrokeDistance">Gemiddelde slaglengte</string>
+    <string name="averageStride">Gemiddelde paslengte</string>
+    <string name="averageKMPaceSeconds">Tempo</string>
+    <string name="averageHR">Hartslag</string>
+    <string name="totalStride">Totaal aantal stappen</string>
+    <string name="maxPace">Snelste tempo</string>
+    <string name="minPace">Langzaamste tempo</string>
+    <string name="maxSpeed">Maximum</string>
+    <string name="caloriesBurnt">Calorie毛n</string>
+    <string name="activeSeconds">Actief</string>
+    <string name="steps">Stappen</string>
+    <string name="descentMeters">Bergaf</string>
+    <string name="ascentMeters">Bergop</string>
+    <string name="distanceMeters">Afstand</string>
+    <string name="pref_title_lower_button_function_double">Onderknop dubbel</string>
+    <string name="pref_title_middle_button_function_double">Middenknop dubbel</string>
+    <string name="pref_title_upper_button_function_double">Bovenknop dubbel</string>
+    <string name="pref_title_lower_button_function_long">Onderknop lang</string>
+    <string name="pref_title_middle_button_function_long">Middenknop lang</string>
+    <string name="pref_title_upper_button_function_long">Bovenknop lang</string>
+    <string name="pref_title_lower_button_function_short">Onderknop kort</string>
+    <string name="pref_title_middle_button_function_short">Middenknop kort</string>
+    <string name="pref_title_upper_button_function_short">Bovenknop kort</string>
+    <string name="devicetype_lefun">Lefun</string>
+    <string name="devicetype_sg2">Lemfo SG2</string>
+    <string name="devicetype_pinetime_jf">PineTime (JF Firmware)</string>
+    <string name="activity_error_no_app_for_png">Installeer een geschikte app om de schermafbeelding te delen.</string>
+    <string name="activity_summary_detail">Activiteitdetails</string>
+    <string name="activity_type_badminton">Badminton</string>
+    <string name="activity_type_cricket">Cricket</string>
+    <string name="activity_type_rowing_machine">Roeitrainer</string>
+    <string name="activity_type_jump_roping">Touwtjespringen</string>
+    <string name="activity_prefs_chart_min_session_length">Minimale activiteitsduur (minuten)</string>
+    <string name="activity_prefs_chart_max_idle_phase_length">Onderbrekingsduur om activiteiten te scheiden (minuten)</string>
+    <string name="activity_prefs_chart_min_steps_per_minute">Minimale stappen per minuut voor detectie activiteit</string>
+    <string name="activity_prefs_chart_min_steps_per_minute_for_run">Minimale stappen per minuut voor detectie rennen</string>
+    <string name="lefun_prefs_hydration_reminder_interval_title">Interval herinnering om te drinken (in minuten)</string>
+    <string name="lefun_prefs_hydration_reminder_title">Herinnering om te drinken</string>
+    <string name="devicestatus_disconnected">Apparaat is losgekoppeld!</string>
+    <string name="devicestatus_disconnecting">Apparaat wordt losgekoppeld!</string>
+    <string name="lefun_prefs_interface_language_title">Schermtaal</string>
+    <string name="lefun_prefs_antilost_summary">De band zal trillen als het de verbinding met de telefoon kwijt raakt</string>
+    <string name="lefun_prefs_antilost_title">Anti-verlies</string>
+    <string name="lefun_prefs_hydration_reminder_summary">De band zal trillen om je te herinneren dat je wat moet drinken</string>
+    <string name="firmware_update_progress">Upload is bezig
+\n%1d%% met %.2fkbps (gemiddeld %.2fkbps)
+\nDeel %1d van %1d</string>
+    <string name="devicestatus_upload_failed">Upload is mislukt</string>
+    <string name="devicestatus_upload_aborted">Upload is afgebroken!</string>
+    <string name="devicestatus_upload_validating">Upload wordt gecontroleerd</string>
+    <string name="devicestatus_upload_completed">Upload is voltooid</string>
+    <string name="devicestatus_upload_started">Upload is gestart</string>
+    <string name="devicestatus_upload_starting">Upload wordt gestart</string>
+    <string name="devicestatus_connected">Apparaat is verbonden</string>
+    <string name="devicestatus_connecting">Apparaat maakt verbinding</string>
+    <string name="charts_activity_list">Activiteitenoverzicht</string>
+    <string name="chart_get_active_and_synchronize">Voer een activiteit uit en synchroniseer apparaat.</string>
+    <string name="chart_no_active_data">Geen activiteiten gedetecteerd.</string>
+    <string name="find_lost_device_message">Zoeken naar %1$s\?</string>
+    <string name="fossil_hr_auth_failed">Authenticatie mislukt, functionaliteit beperkt</string>
+    <string name="fossil_hr_unavailable_unauthed">Niet beschikbaar in ongeauthenticeerde modus</string>
+    <string name="fossil_hr_synced_activity_data">Activiteitsgegevens gesynchroniseerd</string>
+    <string name="fossil_hr_commute_processing">Aanvraag verwerken鈥�</string>
+    <string name="calendar_three_months">3 maanden</string>
+    <string name="qhybrid_title_calibration">Kalibratie</string>
+    <string name="qhybrid_title_apps_management">Apps-beheer</string>
+    <string name="qhybrid_title_file_management">Bestandsbeheer</string>
+    <string name="qhybrid_title_background_image">Achtergrondafbeelding</string>
+    <string name="qhybrid_title_apps">Apps</string>
+    <string name="devicetype_um25">UM-25</string>
+    <string name="fossil_hr_warning_firmware_too_new">Sommige functies zijn uitgeschakeld omdat de firmware van het horloge te nieuw is</string>
+    <string name="prefs_sounds_summary">Instellen wanneer het apparaat geluid maakt</string>
+    <string name="prefs_sounds">Geluiden</string>
+    <string name="fw_upgrade_notice_amazfitneo">U staat op het punt om de firmware %s te installeren op uw Amazfit Neo.
+\n
+\nUw horloge wordt opnieuw gestart na het installeren van het .fw-bestand.
+\n
+\nGA VERDER OP EIGEN RISICO!</string>
+    <string name="menuitem_temperature">Temperatuur</string>
+    <string name="menuitem_widgets">Widgets</string>
+    <string name="menuitem_events">Gebeurtenissen</string>
+    <string name="pref_summary_canned_messages_dismisscall">Inkomende telefoongesprekken afwijzen met een SMS-bericht</string>
+    <string name="pref_summary_canned_messages_set">Stuur de hieronder ingestelde berichten naar uw apparaat</string>
+    <string name="qhybrid_summary_calibration">Kalibreer de wijzers</string>
+    <string name="qhybrid_summary_file_management">Upload en download bestanden</string>
+    <string name="pref_summary_physical_buttons">Configureer de functionaliteit van de fysieke knoppen op het horloge</string>
+    <string name="pref_title_physical_buttons">Fysieke knoppen</string>
+    <string name="devicetype_miband6">Mi Band 6</string>
+    <string name="fw_upgrade_notice_miband6">U staat op het punt om de %s-firmware op je Mi Band 6 te installeren.
+\n
+\nZorg ervoor dat u het .fw-bestand installeert, en daarna het .res-bestand. Uw band wordt opnieuw opgestart na het installeren van het .fw-bestand.
+\n
+\nOpmerking: U hoeft .res niet te installeren als het precies hetzelfde is als het eerder ge茂nstalleerde .res.
+\n
+\nGA VERDER OP EIGEN RISICO!</string>
+    <string name="kind_agps_bundle">AGPS-bundel</string>
+    <string name="qhybrid_calibration_100_steps">100 stappen</string>
+    <string name="qhybrid_calibration_10_steps">10 stappen</string>
+    <string name="qhybrid_calibration_1_step">1 stap</string>
+    <string name="qhybrid_calibration_clockwise">Met de klok mee</string>
+    <string name="qhybrid_calibration_counterclockwise">Tegen de klok in</string>
+    <string name="fossil_hr_new_action">Nieuwe actie</string>
+    <string name="fossil_hr_new_action_cancel">annuleren</string>
+    <string name="fossil_hr_edit_action">Actie aanpassen</string>
+    <string name="fossil_hr_edit_action_delete">verwijder</string>
+    <string name="fossil_hr_commute_actions_explanation">De acties die hier ingesteld worden zullen zichtbaar worden in de Reistijd-app op uw horloge. Lees de wiki voor informatie over hoe om te gaan met de intents die door deze acties geproduceerd worden.</string>
+    <string name="watchface_install_info">U staat op het punt om de volgende wijzerplaat te installeren:
+\n
+\n%1$s
+\nVersie %2$s door %3$s
+\n</string>
+    <string name="qhybrid_watchface_configuration_old_firmware">Wijzerplaat-instellingen voor horloges met firmware-versie DN1.0.2.19r en lager</string>
+    <string name="qhybrid_title_watchface">Wijzerplaat instellen</string>
+    <string name="notification_channel_low_battery_name">Batterij bijna leeg</string>
+    <string name="notification_channel_transfer_name">Gegevensoverdracht</string>
+    <string name="qhybrid_pref_summary_external_intents">Staat andere Android-apps toe om bestanden te uploaden/overschrijven</string>
+    <string name="qhybrid_pref_title_external_intents">Gevaarlijke externe intents toestaan</string>
+    <string name="pref_summary_developer_settings">Instellingen en functionaliteit voor ontwikkelaars</string>
+    <string name="pref_title_developer_settings">Ontwikkelaarsinstellingen</string>
+    <string name="qhybrid_pref_summary_actions">Acties voor de Reistijd-app</string>
+    <string name="qhybrid_pref_title_actions">Acties</string>
+    <string name="devicetype_amazfit_trex_pro">Amazfit T-Rex Pro</string>
+    <string name="menuitem_barometer">Barometer</string>
+    <string name="pref_theme_black_background">Gebruik zwarte achtergrond in Donker thema</string>
+    <string name="set">Gebruik</string>
+    <string name="gpx_receiver_overwrite_some_files">Sommige bestanden bestaan al. Overschrijven\?</string>
+    <string name="gpx_receiver_files_received">GPX-bestand(en) ontvangen:</string>
+    <string name="gpx_receiver_activity_title">GPX-ontvanger Gadgetbridge</string>
+    <string name="activity_summary_detail_editing_gpx_track">Gekoppelde GPX-track bewerken</string>
+    <string name="activity_summary_detail_clear_gpx_track">GPX-track wissen</string>
+    <string name="activity_summary_detail_select_gpx_track">Selecteer GPX-track</string>
+    <string name="watchface_setting_wrist_flick_duration">Duur (in ms)</string>
+    <string name="watchface_setting_wrist_flick_minute">Minutenwijzer (-360 tot 360)</string>
+    <string name="watchface_setting_wrist_flick_hour">Uurwijzer (-360 tot 360)</string>
+    <string name="watchface_setting_wrist_flick_move_relative">Beweeg de wijzers relatief ten opzichte van de tijd</string>
+    <string name="watchface_setting_desc_wrist_flick">(om volledig uit te schakelen, schakel relatieve beweging in en zet alle waarden op 0)</string>
+    <string name="watchface_setting_title_wrist_flick">Polsbeweging</string>
+    <string name="watchface_setting_display_refresh_partial">Gedeeltelijk (in minuten)</string>
+    <string name="watchface_setting_display_refresh_full">Volledig (in minuten)</string>
+    <string name="watchface_setting_title_display_refresh_timeout">Time-out schermverversing</string>
+    <string name="watchface_dialog_title_settings">Wijzerplaat-instellingen</string>
+    <string name="watchface_dialog_widget_color_black">Zwart</string>
+    <string name="watchface_dialog_widget_color_white">Wit</string>
+    <string name="watchface_dialog_widget_color">Kleur</string>
+    <string name="qhybrid_title_watchface_designer">Wijzerplaat ontwerpen</string>
+    <string name="watchface_dialog_widget_preset_right">Rechts</string>
+    <string name="watchface_dialog_widget_preset_left">Links</string>
+    <string name="watchface_dialog_widget_preset_bottom">Onder</string>
+    <string name="watchface_dialog_widget_preset_top">Boven</string>
+    <string name="watchface_dialog_widget_presets">Voorinstellingen positie</string>
+    <string name="watchface_dialog_widget_y_coordinate">Y-co枚rdinaat (max. 240)</string>
+    <string name="watchface_dialog_widget_x_coordinate">X-co枚rdinaat (max. 240)</string>
+    <string name="watchface_dialog_widget_type">Type</string>
+    <string name="appmanager_app_edit">Bewerken</string>
+    <string name="button_watchface_save_apply">Opslaan en toepassen</string>
+    <string name="button_watchface_preview">Voorbeeld op horloge</string>
+    <string name="watchface_widget_type_heart_rate">Hartslag</string>
+    <string name="watchface_widget_type_steps">Stappen</string>
+    <string name="watchface_widget_type_weather">Weer</string>
+    <string name="watchface_widget_type_date">Datum</string>
+    <string name="watchface_dialog_title_add_widget">Widget toevoegen</string>
+    <string name="watchface_toast_settings_incomplete">Instellingen niet compleet, widget niet toegevoegd</string>
+    <string name="watchface_dialog_title_set_name">Naam aanpassen</string>
+    <string name="button_watchface_settings">Wijzerplaat-instellingen</string>
+    <string name="button_watchface_add_widget">Widget toevoegen</string>
+    <string name="button_watchface_select_image">Verander achtergrond</string>
+    <string name="button_watchface_edit_name">Naam aanpassen</string>
+    <string name="devicetype_smaq2oss">SMA-Q2 OSS</string>
+    <string name="watchface_upload_failed">Uploaden van de wijzerplaat is mislukt. Probeer het opnieuw.</string>
+    <string name="watchface_cache_confirm_overwrite">Er bestaat al een wijzerplaat met deze naam in de cache. Wil je het overschrijven\?</string>
+    <string name="heart_rate_result">Meetresultaat</string>
+    <string name="getting_heart_rate">Meten</string>
+    <string name="blood_pressure">Bloeddruk</string>
+    <string name="watchface_setting_power_saving_hands">Wijzers stilzetten wanneer niet gedragen</string>
+    <string name="watchface_setting_power_saving_display">Updates van het scherm uitschakelen wanneer niet gedragen</string>
+    <string name="watchface_setting_title_power_saving">Energiebesparing</string>
+    <string name="watchface_widget_type_chance_rain">Kans op regen</string>
+    <string name="watchface_widget_type_active_mins">Actieve minuten</string>
+    <string name="watchface_widget_type_2nd_tz">2e tijdzone</string>
+    <string name="watchface_widget_type_calories">Calorie毛n</string>
+    <string name="watchface_widget_type_battery">Batterij</string>
+    <string name="pref_summary_huami_force_new_protocol">Inschakelen als uw apparaat geen verbinding meer maakt na een firmware-upgrade</string>
+    <string name="pref_title_huami_force_new_protocol">Nieuw authenticatieprotocol</string>
+    <string name="menuitem_flashlight">Zaklamp</string>
+    <string name="prefs_autoheartrate_interval">Frequentie van metingen</string>
+    <string name="prefs_autoheartrate_sleep">Hartslag meten tijdens slaap</string>
+    <string name="prefs_autoheartrate_measurement">Automatische hartslagmetingen</string>
+    <string name="prefs_autoheartrate_summary">Periodieke hartslagmetingen tijdens dag en nacht</string>
+    <string name="prefs_autoheartrate">Automatische hartslagmeting</string>
+    <string name="prefs_vibration_enable">Trillen inschakelen</string>
+    <string name="enable_notifications_summary">Notificaties voor telefoongesprekken, berichten en meer</string>
+    <string name="enable_vibrations_summary">Trillen bij telefoongesprekken, berichten, notificaties en meer</string>
+    <string name="prefs_notifications_enable">Notificaties inschakelen</string>
+    <string name="prefs_sleep_time_summary">Geeft aan wanneer slaap is gedetecteerd</string>
+    <string name="prefs_sleep_time_label">Slaaptijden instellen</string>
+    <string name="prefs_sleep_time">Slaaptijden</string>
+    <string name="devicetype_fitpro">FitPro</string>
+    <string name="pref_title_ping_tone">Ping-geluid</string>
+    <string name="devicetype_nothingear1">Nothing Ear (1)</string>
+    <string name="nothing_prefs_inear_summary">Speel/pauzeer de muziek afhankelijk van of je de oordopjes draagt</string>
+    <string name="nothing_prefs_inear_title">In-oor-detectie</string>
+    <string name="nothing_prefs_audiomode_title">Audiomodus</string>
+    <string name="check_all_applications">Selecteer alle apps</string>
+    <string name="pref_header_notification_application_settings">App-instellingen</string>
+    <string name="toast_app_must_not_be_selected">App mag niet geselecteerd zijn om ingesteld te worden</string>
+    <string name="pref_title_notification_use_as_deny">Blokkeren van meldingen van geselecteerde apps</string>
+    <string name="pref_title_notification_use_as_allow">Toestaan van meldingen van geselecteerde apps</string>
+    <string name="title_activity_notification_management">Notificatie-instellingen</string>
+    <string name="pref_title_message_privacy_mode">Privacymodus berichten</string>
+    <string name="uncheck_all_applications">Deselecteer alle apps</string>
+    <string name="pref_title_notification_use_as">Gebruik de app-lijst voor鈥�</string>
+    <string name="pref_message_privacy_mode_off">Toon alle inhoud</string>
+    <string name="pref_message_privacy_mode_complete">Verberg alle inhoud</string>
+    <string name="pref_applications_settings">App-lijst</string>
+    <string name="toast_app_must_be_selected">App moet geselecteerd zijn om ingesteld te worden</string>
+    <string name="devicetype_galaxybuds">Galaxy Buds</string>
+    <string name="prefs_equalizer_preset">Equalizer-voorinstelling</string>
+    <string name="pref_title_equalizer_bass_boost">Basversterking</string>
+    <string name="pref_title_equalizer_soft">Zacht</string>
+    <string name="pref_title_equalizer_dynamic">Dynamisch</string>
+    <string name="pref_title_equalizer_clear">Helder</string>
+    <string name="pref_title_equalizer_trebble">Versterking hoge tonen</string>
+    <string name="prefs_dolby_mode">Dolby-modus</string>
+    <string name="prefs_equalizer">Equalizer</string>
+    <string name="prefs_equalizer_summary">Equalizer in- of uitschakelen</string>
+    <string name="prefs_dolby_summary">Dolby-voorinstelling voor equalizer</string>
+    <string name="prefs_game_mode">Spelmodus</string>
+    <string name="prefs_game_mode_summary">Alleen als je telefoon de spelmodus ondersteunt</string>
+    <string name="prefs_touch_lock">Aanrakingsvergrendeling</string>
+    <string name="prefs_ambient_mode">Omgevingsmodus</string>
+    <string name="prefs_touch_lock_summary">Aanraakgebeurtenissen uitschakelen</string>
+    <string name="prefs_galaxy_buds_experimental">Experimenteel</string>
+    <string name="prefs_ambient_volume">Omgevingsvolume</string>
+    <string name="prefs_ambient_voice_focus">Stemfocus</string>
+    <string name="prefs_ambient_sound">Omgevingsgeluid</string>
+    <string name="prefs_left">Links</string>
+    <string name="prefs_ambient_voice_summary">Laat stemmen opvallen</string>
+    <string name="prefs_right">Rechts</string>
+    <string name="prefs_galaxy_touch_options">Aanraakopties</string>
+    <string name="battery_case">Hoes</string>
+    <string name="left_earbud">Linker oordopje</string>
+    <string name="right_earbud">Rechter oordopje</string>
+    <string name="devicetype_sony_wh_1000xm3">Sony WH-1000XM3</string>
+    <string name="pref_title_equalizer_normal">Normaal</string>
+    <string name="prefs_active_noise_cancelling">Actieve ruisonderdrukking</string>
+    <string name="prefs_active_noise_cancelling_summary">Blokkeer omgevingsgeluid</string>
+    <string name="pressure_relief_summary">Verlicht druk op de oren door middel van omgevingsgeluid wanneer actieve ruisonderdrukking niet gebruikt wordt</string>
+    <string name="pref_header_sony_ambient_sound_control">Omgevingsgeluid</string>
+    <string name="sony_ambient_sound">Modus</string>
+    <string name="sony_ambient_sound_noise_cancelling">Ruisonderdrukking</string>
+    <string name="sony_ambient_sound_wind_noise_reduction">Windgeluidreductie</string>
+    <string name="sony_ambient_sound_ambient_sound">Omgevingsgeluid</string>
+    <string name="sony_ambient_sound_level">Volume omgevingsgeluid</string>
+    <string name="sony_sound_position">Geluidspositie</string>
+    <string name="sony_sound_position_off">Uit</string>
+    <string name="sony_sound_position_front_left">Linksvoor</string>
+    <string name="sony_sound_position_front_right">Rechtsvoor</string>
+    <string name="sony_sound_position_rear_left">Linksachter</string>
+    <string name="sony_sound_position_rear_right">Rechtsachter</string>
+    <string name="sony_surround_mode_arena">Arena</string>
+    <string name="sony_surround_mode_club">Club</string>
+    <string name="sony_surround_mode_outdoor_stage">Buitenpodium</string>
+    <string name="sony_surround_mode_concert_hall">Concertzaal</string>
+    <string name="sony_warn_sbc_codec">Waarschuwing: deze instellingen werken alleen voor de SBC-audiocodec.</string>
+    <string name="sony_equalizer">Equalizer</string>
+    <string name="sony_equalizer_preset_off">Uit</string>
+    <string name="sony_equalizer_preset_bright">Helder</string>
+    <string name="sony_equalizer_preset_excited">Opgewonden</string>
+    <string name="sony_equalizer_preset_vocal">Zang</string>
+    <string name="sony_equalizer_preset_treble_boost">Hoge tonen</string>
+    <string name="sony_equalizer_preset_manual">Handmatig</string>
+    <string name="pref_header_sony_equalizer_preset_custom_1">Aangepaste voorinstelling 1</string>
+    <string name="pref_header_sony_equalizer_preset_custom_2">Aangepaste voorinstelling 2</string>
+    <string name="sony_equalizer_band_400">400</string>
+    <string name="sony_equalizer_band_6300">6,3 k</string>
+    <string name="sony_equalizer_clear_bass">Heldere bas</string>
+    <string name="sony_touch_sensor">Aanraakbediening</string>
+    <string name="sony_notification_voice_guide">Meldingen en spraakgids</string>
+    <string name="sony_automatic_power_off">Automatisch uitschakelen</string>
+    <string name="sony_automatic_power_off_off">Niet uitschakelen</string>
+    <string name="sony_automatic_power_off_5_min">5 minuten</string>
+    <string name="sony_automatic_power_off_30_min">30 minuten</string>
+    <string name="watchface_widget_type_custom">Aangepaste widget</string>
+    <string name="prefs_pressure_relief">Verlicht druk op de oren</string>
+    <string name="sony_equalizer_preset_mellow">Zacht</string>
+    <string name="sony_equalizer_preset_custom_1">Aangepast 1</string>
+    <string name="sony_equalizer_band_1000">1k</string>
+    <string name="devicetype_galaxybuds_live">Galaxy Buds Live</string>
+    <string name="sony_ambient_sound_focus_voice">Versterk stemmen</string>
+    <string name="sony_equalizer_band_16000">16k</string>
+    <string name="sony_automatic_power_off_1_hour">1 uur</string>
+    <string name="sony_equalizer_preset_relaxed">Ontspannen</string>
+    <string name="pref_header_system">Systeem</string>
+    <string name="pref_header_equalizer">Equalizer</string>
+    <string name="sony_ambient_sound_off">Uit</string>
+    <string name="pref_header_other">Overige</string>
+    <string name="sony_sound_position_front">Voor</string>
+    <string name="sony_surround_mode">Surround-modus</string>
+    <string name="sony_surround_mode_off">Uit</string>
+    <string name="sony_equalizer_preset_bass_boost">Basversterking</string>
+    <string name="sony_equalizer_preset_speech">Spraak</string>
+    <string name="sony_equalizer_preset_custom_2">Aangepast 2</string>
+    <string name="sony_equalizer_band_2500">2,5k</string>
+    <string name="sony_automatic_power_off_3_hour">3 uur</string>
+    <string name="device_card_activity_card_title">Activiteitsgegevens op apparaatkaart</string>
+    <string name="device_card_activity_card_title_summary">Kies welke activiteiten weergegeven worden op de apparaatkaart</string>
+    <string name="prefs_activity_in_device_card_sleep_title">Slaap</string>
+    <string name="prefs_activity_in_device_card_distance_title_summary">Afstand wordt berekend aan de hand van stappen en staplengte (instelbaar in Instellingen - Over u)</string>
+    <string name="prefs_activity_in_device_card_title">Toon activiteitsgegevens op apparaatkaart</string>
+    <string name="prefs_activity_in_device_card_title_summary">Toon aantal stappen, afstand of hoeveelheid slaap op apparaatkaart</string>
+    <string name="prefs_activity_in_device_card_sleep_title_summary">Toon hoeveelheid slaap</string>
+    <string name="prefs_activity_in_device_card_steps_title_summary">Toon totaal aantal stappen</string>
+    <string name="sony_automatic_power_off_when_taken_off">Wanneer afgedaan</string>
+    <string name="prefs_fm_presets_presets">Voorinstellingen</string>
+    <string name="prefs_fm_preset_instructions">Druk lang op de knop om de voorinstelling op te slaan</string>
+    <string name="watchface_dialog_widget_timezone">Tijdzone</string>
+    <string name="watchface_dialog_widget_update_timeout">Update-time-out in minuten</string>
+    <string name="watchface_dialog_widget_timeout_hide_text">Tekst verbergen bij time-out</string>
+    <string name="watchface_dialog_widget_timeout_show_circle">Toon cirkel bij time-out</string>
+    <string name="qhybrid_title_on_device_confirmation">Koppelingsbevestiging op het apparaat inschakelen</string>
+    <string name="qhybrid_summary_on_device_confirmation">Koppelingsbevestigingen op het apparaat kunnen vervelend worden. Als u ze uitschakelt, verliest u mogelijk functionaliteit.</string>
+    <string name="controlcenter_start_configure_reminders">Herinneringen instellen</string>
+    <string name="reminder_repeat">Herhaal</string>
+    <string name="reminder_date">Datum</string>
+    <string name="reminder_time">Tijd</string>
+    <string name="reminder_message">Bericht</string>
+    <string name="reminder_time_once">%1$s, eenmaal</string>
+    <string name="reminder_time_every_day">%1$s, elke dag</string>
+    <string name="reminder_once">Eenmalig</string>
+    <string name="reminder_every_day">Elke dag</string>
+    <string name="reminder_every_week">Elke week</string>
+    <string name="reminder_every_month">Elke maand</string>
+    <string name="reminder_every_year">Elk jaar</string>
+    <string name="reminder_delete_confirm_title">Herinnering verwijderen</string>
+    <string name="reminder_no_free_slots_description">Het apparaat heeft geen vrije plaatsen voor herinneringen (totaal aantal plaatsen: %1$s)</string>
+    <string name="title_activity_reminder_details">Details</string>
+    <string name="miband_prefs_reserve_reminder_calendar">Herinneringen om te reserveren voor aankomende evenementen</string>
+    <string name="prefs_reserve_reminder_calendar_summary">Aantal agenda-afspraken dat wordt gesynchroniseerd</string>
+    <string name="reminder_time_every_month">%1$s, elke maand</string>
+    <string name="reminder_time_every_year">%1$s, elk jaar</string>
+    <string name="reminder_time_every_week">%1$s, elke week</string>
+    <string name="reminder_delete_confirm_description">Weet je zeker dat je de herinnering wilt verwijderen\?</string>
+    <string name="reminder_no_free_slots_title">Geen vrije plekken</string>
+    <string name="title_activity_set_reminders">Herinneringen instellen</string>
+    <string name="mi2_prefs_do_not_disturb_lift_wrist">Activeer het display bij het optillen tijdens Niet Storen</string>
+    <string name="maxHR">Max hartslag</string>
+    <string name="maxStride">Max paslengte</string>
+    <string name="averageCadence">Gemiddelde cadans</string>
+    <string name="minHR">Min hartslag</string>
+    <string name="minSpeed">Minimum</string>
+    <string name="averageAltitude">Gemiddeld</string>
+    <string name="minStride">Min paslengte</string>
+    <string name="minCadence">Min cadans</string>
+    <string name="spm">stappen/min</string>
+    <string name="maxCadence">Max cadans</string>
+    <string name="pref_header_sony_equalizer_bands">Banden</string>
+    <string name="sony_audio_upsampling">Audio-upsampling</string>
+    <string name="controlcenter_power_off">Uitschakelen</string>
+    <string name="devicetype_bose_qc35">Bose QC35</string>
+    <string name="devicetype_vesc">VESC</string>
+    <string name="sony_button_mode_off">Uit</string>
+    <string name="sony_button_mode_ambient_sound_control">Omgevingsgeluidregeling</string>
+    <string name="sony_button_mode_left">Knopmodus (links)</string>
+    <string name="controlcenter_power_off_confirm_title">Uitschakelen</string>
+    <string name="controlcenter_power_off_confirm_description">Weet u zeker dat u het apparaat wilt uitschakelen\?</string>
+    <string name="discover_unsupported_devices_description">Als u deze optie inschakelt, worden alle gedetecteerde Bluetooth-apparaten weergegeven tijdens het scannen. Kort tikken kopieert de apparaatnaam en het mac-adres naar het klembord. Als u lang drukt, wordt het dialoogvenster \'Testapparaat toevoegen\' geopend. Kan mogelijk het bevriezen van de app veroorzaken.</string>
+    <string name="sony_button_mode_right">Knopmodus (rechts)</string>
+    <string name="activity_prefs_discovery_pairing">Detectie- en koppelingsopties</string>
+    <string name="sony_pause_when_taken_off">Pauzeer wanneer de koptelefoon wordt afgezet</string>
+    <string name="sony_button_mode_volume_control">Volumeregeling</string>
+    <string name="add_test_device">Testapparaat toevoegen</string>
+    <string name="devicetype_sony_wf_sp800n">Sony WF-SP800N</string>
+    <string name="descentDistance">Afstand bergafwaarts</string>
+    <string name="discover_unsupported_devices">Ontdek niet-ondersteunde apparaten</string>
+    <string name="ascentDistance">Afstand bergopwaarts</string>
+    <string name="sony_button_mode_playback_control">Afspeelbediening</string>
+    <string name="activity_type_hiking">Wandeltocht</string>
+    <string name="activity_type_climbing">Klimmen</string>
+    <string name="pref_media_play">Media afspelen</string>
+    <string name="pref_button_action_disabled">Uitgeschakeld</string>
+    <string name="pref_media_playpause">Afspelen in-/uitschakelen</string>
+    <string name="pref_media_next">Volgende nummer</string>
+    <string name="pref_media_previous">Vorige nummer</string>
+    <string name="pref_media_volumeup">Volume omhoog</string>
+    <string name="pref_media_volumedown">Volume omlaag</string>
+    <string name="pref_media_rewind">Spring achteruit</string>
+    <string name="pref_device_action_broadcast">Stuur Broadcast</string>
+    <string name="distance_format_meters">###m</string>
+    <string name="distance_format_miles">###.#mi</string>
+    <string name="distance_format_feet">###ft</string>
+    <string name="pref_media_pause">Media pauzeren</string>
+    <string name="pref_media_forward">Spring vooruit</string>
+    <string name="about_hash">Commit %s</string>
+    <string name="distance_format_kilometers">###.#km</string>
+    <string name="pref_activity_recognize_rowing">herken roeien</string>
+    <string name="pref_activity_recognition_mode_none">geen</string>
+    <string name="menuitem_menu">Menu</string>
+    <string name="pref_activity_recognize_running">herken hardlopen</string>
+    <string name="pref_activity_recognize_biking">herken fietsen</string>
+    <string name="prefs_activity_recognition">Instellingen voor activiteitsherkenning</string>
+    <string name="pref_activity_recognize_walking">herken wandelen</string>
+    <string name="pref_activity_recognition_mode_auto">auto</string>
+    <string name="pref_activity_recognition_mode_ask">vragen</string>
+    <string name="fossil_hr_button_config_info">Sommige knoppen kunnen niet worden geconfigureerd omdat hun functies hard gecodeerd zijn in de horlogefirmware.
+\n
+\nWaarschuwing: lang indrukken van de bovenste knop wanneer een wijzerplaat van de offici毛le Fossil-app is ge茂nstalleerd, schakelt ook tussen tonen/verbergen van widgets.</string>
+    <string name="devicetype_domyos_t540">Domyos T540</string>
+    <string name="devicetype_sony_wh_1000xm4">Sony WH-1000XM4</string>
+    <string name="sony_speak_to_chat">Spreek-om-te-chatten</string>
+    <string name="sony_speak_to_chat_sensitivity">Gevoeligheid van stemdetectie</string>
+    <string name="sony_speak_to_chat_sensitivity_auto">Automatisch</string>
+    <string name="sony_speak_to_chat_sensitivity_high">Hoog</string>
+    <string name="sony_speak_to_chat_sensitivity_low">Laag</string>
+    <string name="sony_speak_to_chat_timeout_standard">Standaard (30s)</string>
+    <string name="watchface_dialog_widget_width">Widget-breedte (in pixels)</string>
+    <string name="discovery_bluetooth_scan">Bluetooth scan:</string>
+    <string name="discovery_bluetooth_le_scan">Bluetooth LE scan:</string>
+    <string name="sony_speak_to_chat_focus_on_voice">Focus op stem</string>
+    <string name="sony_speak_to_chat_timeout">Time-out</string>
+    <string name="sony_speak_to_chat_timeout_off">Uit</string>
+    <string name="sony_speak_to_chat_timeout_short">Kort (15s)</string>
+    <string name="sony_speak_to_chat_timeout_long">Lang (1m)</string>
+    <string name="sony_connect_two_devices">Met 2 apparaten tegelijk verbinden</string>
+    <string name="device_unsupported">NIET ONDERSTEUND</string>
+    <string name="devicetype_sonyswr12">Sony SWR12</string>
+    <string name="pref_header_sony_anc_optimizer">Ruisonderdrukking-optimalisator</string>
+    <string name="sony_anc_optimize_description">Klik om de optimalisatie voor ruisonderdrukking te starten.</string>
+    <string name="sony_anc_optimize_confirmation_title">Ruisonderdrukking-optimalisator</string>
+    <string name="pref_anc_optimizer_state_pressure">Luchtdruk</string>
+    <string name="sony_anc_optimizer_status_wearing_condition">Het meten van de draagconditie鈥�</string>
+    <string name="sony_anc_optimizer_status_atmospheric_pressure">Luchtdruk aan het meten鈥�</string>
+    <string name="sony_anc_optimizer_status_analyzing">Analyseren鈥�</string>
+    <string name="sony_anc_optimizer_status_finished">Afronden鈥�</string>
+    <string name="sony_anc_optimize_title">Optimaliseren</string>
+    <string name="pref_header_sony_device_info">Apparaatinformatie</string>
+    <string name="unknown">Onbekend</string>
+    <string name="start">Start</string>
+    <string name="audio_codec">Audio-codec</string>
+    <string name="sony_anc_optimize_confirmation_description">Gebruik de hoofdtelefoon zoals u gewend bent. Als de draagconditie of de atmosferische druk verandert, voert u de optimizer opnieuw uit.</string>
+    <string name="sony_anc_optimizer_status_not_running">Niet actief</string>
+    <string name="sony_anc_optimizer_status_starting">Starten鈥�</string>
+    <string name="devicetype_amazfit_pop_pro">Amazfit Pop Pro</string>
+    <string name="devicetype_amazfit_pop">Amazfit Pop</string>
+    <string name="menuitem_nothing">Niets</string>
+    <string name="pref_summary_opentracks_packagename">Wordt gebruikt voor het starten/stoppen van GPS-trackregistratie in externe fitness-app.</string>
+    <string name="pref_title_opentracks_packagename">OpenTracks-pakketnaam</string>
+    <string name="pref_device_action_fitness_app_control_start">Fitness-app-tracking starten</string>
+    <string name="pref_device_action_fitness_app_control_stop">Fitness-app-tracking stoppen</string>
+    <string name="pref_title_notifications_generic_settings">Instellingen voor Android-meldingen</string>
+    <string name="activity_db_management_autoexport_scheduled_no">AutoExport is niet gepland.</string>
+    <string name="activity_db_management_autoexport_location">Locatie was niet te begrijpen. Waarschijnlijk een probleem met het nieuwere Android-toestemmingssysteem. Hoogstwaarschijnlijk werkt automatisch exporteren nu niet.</string>
+    <string name="activity_db_management_autoexport_enabled_no">AutoExport is niet ingeschakeld.</string>
+    <string name="activity_db_management_autoexport_enabled_yes">AutoExport is ingeschakeld.</string>
+    <string name="activity_db_management_autoexport_scheduled_yes">AutoExport is (oorspronkelijk) gepland voor %1$s</string>
+    <string name="autoExport_lastTime_label">Laatste AutoExport: %1$s</string>
+    <string name="watchface_dialog_pre_setting_position">Positie ingesteld op %s</string>
+    <string name="watchface_setting_light_up_on_notification">Scherm oplichten bij nieuwe melding</string>
+    <string name="menuitem_email">E-mail</string>
+    <string name="pref_screen_notification_profile_event_reminder">Evenement-herinnering</string>
+    <string name="pref_screen_notification_profile_find_device">Zoek apparaat</string>
+    <string name="pref_screen_notification_idle_alerts">Inactiviteitswaarschuwingen</string>
+    <string name="pref_screen_vibration_patterns_title">Trillingspatronen</string>
+    <string name="pref_screen_vibration_patterns_summary">Configureer de trillingspatronen voor verschillende meldingen</string>
+    <string name="pref_world_clocks_summary">Klokken configureren voor andere tijdzones</string>
+    <string name="world_clock_delete_confirm_title">Verwijder \'%1$s\'</string>
+    <string name="world_clock_delete_confirm_description">Weet je zeker dat je de wereldklok wilt verwijderen\?</string>
+    <string name="world_clock_no_free_slots_title">Geen vrije plekken</string>
+    <string name="world_clock_no_free_slots_description">Het apparaat heeft geen vrije slots voor wereldklokken (totaal aantal slots: %1$s)</string>
+    <string name="world_clock_timezone">Tijdzone</string>
+    <string name="world_clock_label">Label</string>
+    <string name="title_activity_world_clock_details">Details wereldklok</string>
+    <string name="mi5_prefs_workout_activity_types">Soorten trainingsactiviteiten</string>
+    <string name="mi5_prefs_workout_activity_types_summary">Kies de activiteitstypes om op het workouts-scherm weer te geven</string>
+    <string name="normal">Normaal</string>
+    <string name="sensitive">Gevoelig</string>
+    <string name="activity_type_outdoor_cycling">Buiten fietsen</string>
+    <string name="activity_type_elliptical">Crosstrainer</string>
+    <string name="devicetype_sony_wf_1000xm3">Sony WF-1000XM3</string>
+    <string name="title_activity_controlcenter_main_nopebble">Gadgetbridge Nightly Geen Pebble</string>
+    <string name="activity_type_outdoor_running">Buiten hardlopen</string>
+    <string name="about_activity_title_main_nightly">Over Gadgetbridge Nightly</string>
+    <string name="application_name_main_nopebble">Gadgetbridge (Nightly, geen Pebble-provider)</string>
+    <string name="gadgetbridge_running_main_nopebble">Nightly NoPebble GB actief</string>
+    <string name="activity_type_freestyle">Freestyle</string>
+    <string name="pref_device_action_fitness_app_control_toggle">Fitness-app-tracking in-/uitschakelen</string>
+    <string name="application_name_banglejs_main">Bangle.js Gadgetbridge</string>
+    <string name="title_activity_controlcenter_banglejs_main">Bangle.js Gadgetbridge</string>
+    <string name="about_activity_title_banglejs_main">Over Bangle.js Gadgetbridge</string>
+    <string name="about_description_banglejs_main">Android-app voor Bangle.js gebouwd bovenop het Gadgetbridge-project, met toegevoegde internettoegang.
+\n
+\nVanwege het Google Play Store-beleid is een donatielink in de app zelf niet toegestaan, maar als je deze app leuk vindt, overweeg dan om te doneren via de Gadgetbridge-homepage hieronder.</string>
+    <string name="gadgetbridge_running_banglejs_main">Bangle.js actief</string>
+    <string name="application_name_banglejs_nopebble">Bangle.js Gadgetbridge</string>
+    <string name="title_activity_controlcenter_banglejs_nopebble">Bangle.js Gadgetbridge</string>
+    <string name="about_activity_title_banglejs_nopebble">Over Bangle.js Gadgetbridge</string>
+    <string name="about_description_banglejs_nopebble">Android-app voor Bangle.js gebouwd bovenop het Gadgetbridge-project, met toegevoegde internettoegang.
+\n
+\nVanwege het Google Play Store-beleid is een donatielink in de app zelf niet toegestaan, maar als je deze app leuk vindt, overweeg dan om te doneren via de Gadgetbridge-homepage hieronder.</string>
+    <string name="gadgetbridge_running_banglejs_nopebble">Bangle.js actief</string>
+    <string name="application_name_main_nightly">Gadgetbridge (Nightly)</string>
+    <string name="title_activity_controlcenter_main_nightly">Gadgetbridge Nightly</string>
+    <string name="about_description_main_nightly">Cloudless copyleft libre-vervanging voor closed source Android-gadget-apps van leveranciers. Nightly releases van Gadgetbridge. Het kan niet worden ge茂nstalleerd als je de Gadgetbridge- of de Pebble-app al hebt ge茂nstalleerd, vanwege een conflict in de Pebble-provider.</string>
+    <string name="gadgetbridge_running_main_nightly">Nightly GB actief</string>
+    <string name="about_activity_title_main_nopebble">Over Gadgetbridge Nightly No Pebble</string>
+    <string name="about_description_main_nopebble">Cloudloze copyleft libre-vervanging voor closed source Android-gadget-apps van leveranciers. Nightly releases van Gadgetbridge. Deze versie heeft de naam van de Pebble-provider hernoemd om conflicten te voorkomen, dus sommige Pebble-gerelateerde integraties zullen niet werken, maar het kan naast de bestaande Gadgetbridge-installatie worden ge茂nstalleerd.</string>
+    <string name="pref_world_clocks_title">Wereldklokken</string>
+    <string name="prefs_activate_display_on_lift_sensitivity">Gevoeligheid</string>
+    <string name="pref_title_touch_ambient">Omgevingsgeluid</string>
+    <string name="prefs_voice_detect_summary">Schakel omgevingsgeluid in en verlaag het volume automatisch nadat spraak is gedetecteerd</string>
+    <string name="pref_voice_detect_duration_15">15 seconden</string>
+    <string name="heartrate_bpm_112">112 bpm</string>
+    <string name="heartrate_bpm_120">120 bpm</string>
+    <string name="heartrate_bpm_125">125 bpm</string>
+    <string name="prefs_heartrate_alert_experimental_title">Hartslagwaarschuwing (experimenteel)</string>
+    <string name="prefs_heartrate_alert_threshold">Hartslagwaarschuwingsdrempel</string>
+    <string name="prefs_stress_monitoring_title">Stressmonitoring</string>
+    <string name="prefs_stress_monitoring_description">Controleer het stressniveau tijdens het rusten</string>
+    <string name="prefs_activity_monitoring_title">Monitoring van activiteiten</string>
+    <string name="prefs_activity_monitoring_description">Verhoogt automatisch de hartslagdetectiefrequentie wanneer de band lichaamsbeweging detecteert, om de nauwkeurigheid van de hartslagregistratie te verhogen.</string>
+    <string name="mi2_prefs_heart_rate_monitoring">Hartslagbewaking</string>
+    <string name="mi2_prefs_heart_rate_monitoring_summary">Hartslagmeting configureren</string>
+    <string name="mi2_prefs_heart_rate_monitoring_alerts_summary">Hartslagbewaking en waarschuwingsdrempels configureren</string>
+    <string name="prefs_seamless_connection_switch_title">Naadloze verbinding</string>
+    <string name="prefs_seamless_connection_switch_summary">Schakelt automatisch tussen gekoppelde apparaten</string>
+    <string name="prefs_ambient_volume_left">Omgevingsvolume links</string>
+    <string name="prefs_customize_ambient_sound_summary">Omgevingsgeluid aanpassen</string>
+    <string name="prefs_ambient_sound_during_call_summary">Eigen stem horen tijdens gesprekken</string>
+    <string name="prefs_ambient_settings_title">Opties voor omgevingsgeluid</string>
+    <string name="prefs_active_noise_cancelling_level">Actieve ruisonderdrukking</string>
+    <string name="prefs_active_noise_cancelling_level_high">Hoog</string>
+    <string name="prefs_active_noise_cancelling_level_low">Laag</string>
+    <string name="pref_title_touch_voice_assistant">Spraakassistent</string>
+    <string name="pref_title_touch_anc">Actieve ruisonderdrukking</string>
+    <string name="pref_title_touch_quick_ambient">Snel omgevingsgeluid</string>
+    <string name="pref_title_touch_spotify">Spotify</string>
+    <string name="pref_switch_noise_control">Schakel ruisonderdrukking</string>
+    <string name="prefs_noise_control_with_one_earbud">Ruisonderdrukking met 茅茅n oordopje</string>
+    <string name="prefs_noise_control_with_one_earbud_summary">Ruisonderdrukking toestaan bij gebruik van slechts 茅茅n oordopje</string>
+    <string name="pref_ambient_sound_tone">Omgevingsgeluidstoon</string>
+    <string name="pref_ambient_sound_tone_summary">Van zacht naar helder</string>
+    <string name="pref_balance">Balans</string>
+    <string name="pref_switch_controls_ambient_off">Omgeving 鈫愨啋 Uit</string>
+    <string name="prefs_noise_control">Ruisbeheersing</string>
+    <string name="prefs_voice_detect">Spraakdetectie</string>
+    <string name="prefs_double_tap_edge">Dubbeltik op rand</string>
+    <string name="prefs_double_tap_edge_summary">Detecteer dubbel tikken, zelfs als er niet op het touchpad is getikt</string>
+    <string name="prefs_voice_detect_duration">Einde na stilte voor:</string>
+    <string name="pref_voice_detect_duration_5">5 seconden</string>
+    <string name="pref_voice_detect_duration_10">10 seconden</string>
+    <string name="heartrate_bpm_105">105 bpm</string>
+    <string name="heartrate_bpm_110">110 bpm</string>
+    <string name="prefs_in_ear_detection_summary">Gesprekken voeren via uw oordopjes wanneer ze in uw oren zitten</string>
+    <string name="prefs_ambient_volume_right">Omgevingsvolume rechts</string>
+    <string name="heartrate_bpm_100">100 bpm</string>
+    <string name="devicetype_galaxybuds_pro">Galaxy Buds Pro</string>
+    <string name="prefs_ambient_sound_during_call_title">Omgevingsgeluid tijdens gesprekken</string>
+    <string name="heartrate_bpm_145">145 bpm</string>
+    <string name="prefs_heartrate_alert_experimental_description">Trillen wanneer de hartslag boven een bepaalde drempel komt, zonder enige duidelijke fysieke activiteit in de laatste 10 minuten. Deze functie is experimenteel en is niet uitgebreid getest.</string>
+    <string name="pref_switch_controls_anc_ambient">Ruisonderdrukking 鈫愨啋 Omgeving</string>
+    <string name="pref_title_touch_volume">Volume</string>
+    <string name="pref_switch_controls_anc_off">Ruisonderdrukking 鈫愨啋 Uit</string>
+    <string name="heartrate_bpm_130">130 bpm</string>
+    <string name="heartrate_bpm_150">150 bpm</string>
+    <string name="heartrate_bpm_135">135 bpm</string>
+    <string name="heartrate_bpm_140">140 bpm</string>
+    <string name="pref_summary_device_internet_access">Apps op dit apparaat toegang geven tot internet</string>
+    <string name="pref_summary_device_intents">Sta toe dat Bangle.js apps Android-intents verzenden en sta andere apps op Android (zoals Tasker) toe om gegevens naar Bangle.js te verzenden met de com.banglejs.uart.tx-intent. Heeft toestemming nodig om over andere apps te worden weergegeven om op de achtergrond te werken.</string>
+    <string name="pref_title_banglejs_text_bitmap">Tekst als afbeelding</string>
+    <string name="pref_title_device_internet_access">Internettoegang toestaan</string>
+    <string name="pref_summary_banglejs_text_bitmap">Als een woord niet kan worden weergegeven met het lettertype van het horloge, render het dan naar een afbeelding in Gadgetbridge en toon de afbeelding op het horloge</string>
+    <string name="pref_title_device_intents">Intents toestaan</string>
+    <string name="prefs_switch_control_left">Bediening links</string>
+    <string name="prefs_switch_control_right">Bediening rechts</string>
+    <string name="controlcenter_set_preferences">Voorkeuren instellen</string>
+    <string name="pref_explanation_authkey">Sommige apparaten hebben een speciale koppelingssleutel nodig voor de allereerste initialisatie van het apparaat. Tik hier voor meer details op de wiki.</string>
+    <string name="quick_alarm_description">Alarm van widget</string>
+    <string name="controlcenter_set_parent_folder">Map instellen</string>
+    <string name="permission_location">%1$s heeft toegang tot je locatie op de achtergrond nodig om verbonden te blijven met je horloge als het scherm is uitgeschakeld. \n \nKies \'%2$s\' in het volgende scherm, tik darna op \'Terug\' om terug te gaan naar %1$s.</string>
+    <string name="info_connected_count">%d apparaten verbonden</string>
+    <string name="autoconnect_from_device_summary">Breng een verbinding tot stand wanneer de verbinding tot stand wordt gebracht door een apparaat, zoals een hoofdtelefoon</string>
+    <string name="pref_workout_start_on_phone_title">Fitness-app tracking</string>
+    <string name="pref_workout_send_gps_title">GPS verzenden tijdens training</string>
+    <string name="notification_gps_title">Gadgetbridge GPS</string>
+    <string name="notification_gps_text">GPS-locatie verzenden naar %1$d-apparaten</string>
+    <string name="permission_notification_policy_access">%1$s heeft toegang nodig tot de instellingen voor Niet storen om ze op je horloge te respecteren wanneer het scherm van je telefoon is uitgeschakeld.
+\n
+\nTik op \'%2$s\' en vervolgens op \'%1$s\' en schakel \'Niet storen toestaan\' in, tik vervolgens op \'Terug\' om terug te keren naar %1$s.</string>
+    <string name="permission_notification_listener">%1$s heeft toegang tot meldingen nodig om ze op je horloge weer te geven wanneer het scherm van je telefoon is uitgeschakeld.
+\n
+\nTik op \'%2$s\' en vervolgens op \'%1$s\' en schakel \'Toegang tot meldingen toestaan\' in, tik vervolgens op \'Terug\' om terug te keren naar %1$s.</string>
+    <string name="discovery_scanning_intensity">Scanintensiteit</string>
+    <string name="pref_device_action_phone_gps_location_listener_stop">GPS-locatie Listener Stop</string>
+    <string name="pref_title_banglejs_webview_url">URL app-lader</string>
+    <string name="pref_summary_banglejs_webview_url">Als u een aangepaste app-lader wilt, plaatst u hier uw https://鈥�/android.html URL. Laat anders leeg voor https://banglejs.com/apps</string>
+    <string name="pref_workout_start_on_phone_summary">Start/stop het volgen van de fitness-app op de telefoon wanneer een GPS-training op de band wordt gestart</string>
+    <string name="pref_workout_send_gps_summary">Stuur de huidige GPS-locatie naar de band tijdens een training</string>
+    <string name="discovery_scanning_intensity_warning">Als uw apparaat bevriest of niet reageert, probeer dan de scanintensiteit op een lager niveau in te stellen. Als uw apparaat niet wordt ontdekt, probeer dan de intensiteit van het scannen op een hoger niveau in te stellen.</string>
+    <string name="pref_title_notification_delay_calls">Vertraging oproepmelding</string>
+    <string name="pref_summary_notification_delay_calls">Vertraging voor het verzenden van inkomende oproepmeldingen naar het apparaat, in seconden.</string>
+    <string name="prefs_password">Wachtwoord</string>
+    <string name="prefs_password_summary">Vergrendel de band met een wachtwoord wanneer deze van de pols wordt verwijderd</string>
+    <string name="prefs_password_enabled">Wachtwoord ingeschakeld</string>
+    <string name="prefs_password_4_digits_1_to_4_summary">Het wachtwoord moet 4 cijfers hebben, met de nummers 1 tot en met 4</string>
+    <string name="info_no_devices_connected">Geen apparaten aangesloten</string>
+    <string name="pref_blacklist_calendars_summary">Agenda\'s op de zwarte lijst worden niet gesynchroniseerd met het apparaat</string>
+    <string name="bengali">Bengaals</string>
+    <string name="czech">Tsjechisch</string>
+    <string name="estonian">Estisch</string>
+    <string name="extended_ascii">Uitgebreide ASCII</string>
+    <string name="icelandic">IJslands</string>
+    <string name="lithuanian">Litouws</string>
+    <string name="persian">Perzisch</string>
+    <string name="notification_channel_gps">GPS-tracking</string>
+    <string name="pref_explanation_authkey_new_protocol">Als u het bericht \"Update de app naar de nieuwste versie\" op de band krijgt, controleer de bovenstaande optie \"New Auth Protocol\". Tik hier voor meer details op de wiki.</string>
+    <string name="prefs_password_6_digits_0_to_9_summary">Het wachtwoord moet uit 6 cijfers bestaan, met alleen cijfers</string>
+    <string name="scandinavian">Scandinavisch</string>
+    <string name="ukranian">Oekra茂ens</string>
+    <string name="portuguese_br">Portugees (Brazili毛)</string>
+    <string name="portuguese_pt">Portugees (Portugal)</string>
+    <string name="quick_alarm">Snel alarm</string>
+    <string name="controlcenter_toggle_details">Details in-/uitschakelen</string>
+    <string name="controlcenter_connected_fraction">Verbonden: %d/%d</string>
+    <string name="error_setting_parent_folder">Fout bij instellen van map: %s</string>
+    <string name="error_deleting_device">Fout bij verwijderen van apparaat: %s</string>
+    <string name="controlcenter_folder_name">Naam van de map:</string>
+    <string name="controlcenter_add_new_folder">Nieuwe map toevoegen</string>
+    <string name="controlcenter_unset_folder">Loskoppelen van map</string>
+    <string name="controlcenter_set_folder_title">Map selecteren of aanmaken</string>
+    <string name="auto_reconnect_ble_title">Automatisch opnieuw verbinden met apparaat</string>
+    <string name="auto_reconnect_ble_summary">Probeer regelmatig proactief verbinding te maken met het apparaat</string>
+    <string name="connection_over_ble">Verbinding via BLE</string>
+    <string name="connection_over_bt_classic">Verbinding via klassiek Bluetooth</string>
+    <string name="autoconnect_from_device_title">Verbinden bij verbinding vanaf apparaat</string>
+    <string name="watchface_dialog_widget_timezone_duration">Zichtbaarheidsduur klok (in seconden)</string>
+    <string name="open_fw_installer_warning_title">Waarschuwing</string>
+    <string name="open_fw_installer_warning_text">Deze functie heeft het potentieel om uw apparaat onbruikbaar te maken. Dat gezegd hebbende, dit is nog nooit met een van de ontwikkelaars gebeurd door middel van flashen, maar onthoud dat u dit op eigen risico doet.</string>
+    <string name="open_fw_installer_getting_files_title">Het firmware-/app-bestand ophalen</string>
+    <string name="open_fw_installer_getting_files_text">Aangezien we de firmwarebestanden niet mogen distribueren, moet u de bestanden zelf ophalen. Dit betekent dat u naar de bestanden moet zoeken in apk-bestanden, online, in forums, op Amazfitwatchfaces (voor Miband/Amazfit-apparaten) enzovoort.</string>
+    <string name="open_fw_installer_connect_maximum_one_device">Verbind SLECHTS 脡脡N apparaat waarnaar u het bestand wilt verzenden.</string>
+    <string name="open_fw_installer_ensure_device_connected">Zorg ervoor dat het apparaat %s is verbonden</string>
+    <string name="appmanager_item_outdated">(verouderd)</string>
+    <string name="open_fw_installer_connect_minimum_one_device">Verbind TEN MINSTE 脡脡N apparaat waarnaar u het bestand wilt verzenden.</string>
+    <string name="open_fw_installer_info_text">Met de Firmware-/Wijzerplaat-/App-/Bestand-installer kunt u ondersteunde bestanden (firmware, wijzerplaten, toepassingen, GPS, bronnen, lettertypen...) naar het apparaat uploaden/installeren. Kijk voor meer informatie op de wiki: https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Firmware-Update</string>
+    <string name="open_fw_installer_pick_file">Selecteer bestand</string>
+    <string name="open_fw_installer_info_text_title">Bestandsinstallatie</string>
+    <string name="open_fw_installer_select_file">Selecteer een bestand dat u naar het apparaat wilt uploaden: %s</string>
+    <string name="appmanager_app_share">Delen</string>
+    <string name="hybridhr_widget_bg_double_circle">Dubbele cirkel</string>
+    <string name="watchface_dialog_widget_cat_generic">Algemeen</string>
+    <string name="watchface_dialog_widget_cat_position">Positie</string>
+    <string name="watchface_dialog_widget_cat_custom_widget">Aangepaste widget</string>
+    <string name="hybridhr_widget_bg_thin_circle">Dunne cirkel</string>
+    <string name="hybridhr_widget_bg_dashed_circle">Gestreepte cirkel</string>
+    <string name="pref_title_banglejs_txt_bitmap_size">Tekstafbeeldingsgrootte</string>
+    <string name="pref_summary_banglejs_txt_bitmap_size">Grootte die moet worden gebruikt voor bitmaptekstweergave</string>
+    <string name="watchface_dialog_widget_cat_2nd_tz_widget">2e tijdzone-widget</string>
+    <string name="watchface_dialog_title_widget">Widget-instellingen</string>
+    <string name="watchface_dialog_widget_background">Achtergrond</string>
+    <string name="share_log_not_enabled_message">U moet eerst het schrijven van logbestanden inschakelen in Instellingen - Logbestanden schrijven</string>
+    <string name="step_streak_total">Totaal</string>
+    <string name="steps_streaks_since_date">Sinds %s</string>
+    <string name="step_streak_average_steps_hint">Gemiddeld aantal stappen per dag van de reeks</string>
+    <string name="steps_streaks_total_steps_hint_totals">Totaal aantal stappen ooit geregistreerd</string>
+    <string name="steps_streaks_total_days_hint_totals">Percentage dagen met behaald doel in verhouding tot alle dagen met stappen</string>
+    <string name="step_streak_days_hint">Aantal opeenvolgende dagen waarop het stappendoel is bereikt</string>
+    <string name="steps_streaks_total_steps_hint">Totaal aantal stappen in de hele reeks</string>
+    <string name="steps_streaks_streak_days">Reeks-
+\ndagen</string>
+    <string name="steps_streaks">Stappenreeksen</string>
+    <string name="steps_streaks_achievement_rate">Prestatie-
+\npercentage</string>
+    <string name="steps_streaks_total_steps_average_hint">Totaal gemiddeld %d stappen per dag</string>
+    <string name="note">Opmerking</string>
+    <string name="watchface_setting_title_custom_events">Aangepaste gebeurtenissen</string>
+    <string name="step_streak_ongoing">Lopend</string>
+    <string name="steps_streaks_hint">Reeks opeenvolgende dagen zonder onderbreking waarbij het stappendoel wordt bereikt</string>
+    <string name="step_streak_longest">Langste</string>
+    <string name="steps_streaks_total_steps">Totaal
+\nstappen</string>
+    <string name="steps_streaks_average_steps">Gemiddelde
+\nstappen</string>
+    <string name="watchface_setting_button_toggle_widgets">Widgets in-/uitschakelen</string>
+    <string name="watchface_setting_button_toggle_backlight">Achtergrondverlichting inschakelen</string>
+    <string name="controlcenter_get_heartrate_measurement">Hartslagmeting verkrijgen</string>
+    <string name="pref_write_logfiles_not_available">Initialisatie van het loggen van bestanden is mislukt, het schrijven van logbestanden is momenteel niet beschikbaar. Start de app opnieuw om te proberen de logbestanden opnieuw te initialiseren.</string>
+    <string name="title_activity_controlcenter_banglejs_nightly">Bangle.js Gadgetbridge (Nightly)</string>
+    <string name="gadgetbridge_running_banglejs_nightly">Nightly Bangle.js actief</string>
+    <string name="devicetype_binary_sensor">Binaire sensor</string>
+    <string name="step_streaks_achievements_sharing_message">Mijn dagelijkse-stappenprestaties!</string>
+    <string name="step_streaks_achievements_sharing_title">Stappenprestaties</string>
+    <string name="prefs_hourly_chime">Uursignaal</string>
+    <string name="prefs_hourly_chime_summary">Het horloge piept eenmaal per uur</string>
+    <string name="application_name_banglejs_nightly">Bangle.js Gadgetbridge (Nightly)</string>
+    <string name="about_activity_title_banglejs_nightly">Over Bangle.js Gadgetbridge (Nightly)</string>
+    <string name="about_description_banglejs_nightly">Android-app voor Bangle.js, gebouwd bovenop het Gadgetbridge-project, met toegevoegde internettoegang.
+\n
+\nVanwege het Google Play Store-beleid is een donatielink in de app zelf niet toegestaan, maar als je deze app leuk vindt, overweeg dan om te doneren via de Gadgetbridge-homepage hieronder.</string>
+    <string name="fw_upgrade_notice_miband7">U staat op het punt de %s-firmware op uw Xiaomi Smart Band 7 te installeren.
+\n
+\nDe band zal opnieuw opstarten na het installeren van het .zip-bestand.
+\n
+\nVERDERGAAN IS OP EIGEN RISICO!</string>
+    <string name="pref_header_connection">Verbinding</string>
+    <string name="pref_header_calendar">Kalender</string>
+    <string name="pref_header_display">Weergave</string>
+    <string name="pref_header_health">Gezondheid</string>
+    <string name="pref_header_time">Tijd</string>
+    <string name="pref_header_workout">Training</string>
+    <string name="pref_summary_canned_replies">Reageer vanaf het horloge met vooraf ingestelde berichten</string>
+    <string name="pref_cache_weather">Weerinformatie in cache opslaan</string>
+    <string name="pref_cache_weather_summary">Weerinformatie wordt in de cache bewaard tijdens het opnieuw opstarten van de app.</string>
+    <string name="heartrate_bpm_40">40 bpm</string>
+    <string name="heartrate_bpm_45">45 bpm</string>
+    <string name="heartrate_bpm_50">50 bpm</string>
+    <string name="heartrate_bpm_115">115 bpm</string>
+    <string name="spo2_perc_80">80%</string>
+    <string name="spo2_perc_85">85%</string>
+    <string name="spo2_perc_90">90%</string>
+    <string name="spo2_off">Uit</string>
+    <string name="prefs_heartrate_alert_high_threshold">Waarschuwingsdrempel voor hoge hartslag</string>
+    <string name="prefs_heartrate_alert_low_threshold">Waarschuwingsdrempel voor lage hartslag</string>
+    <string name="prefs_relaxation_reminder_title">Ontspanningsherinnering</string>
+    <string name="prefs_relaxation_reminder_description">Tril de band om u op de hoogte te stellen als de stresswaarde hoger is dan 80</string>
+    <string name="prefs_spo2_monitoring_title">Bloedzuurstofbewaking</string>
+    <string name="prefs_spo2_monitoring_description">Controleer automatisch de bloedzuurstofniveaus gedurende de dag</string>
+    <string name="prefs_spo2_alert_threshold">SPO2-waarschuwingsdrempel</string>
+    <string name="seconds_6">6 seconden</string>
+    <string name="seconds_7">7 seconden</string>
+    <string name="seconds_8">8 seconden</string>
+    <string name="seconds_9">9 seconden</string>
+    <string name="seconds_11">11 seconden</string>
+    <string name="pref_sleep_breathing_quality_monitoring">Controle van de ademhalingskwaliteit tijdens de slaap</string>
+    <string name="prefs_screen_timeout">Time-out scherm</string>
+    <string name="smart">Slim</string>
+    <string name="mi2_dnd_always">Altijd</string>
+    <string name="devicetype_miband7">Xiaomi Smart Band 7</string>
+    <string name="menuitem_workout_history">Trainingsgeschiedenis</string>
+    <string name="menuitem_female_health">Vrouwelijke gezondheid</string>
+    <string name="menuitem_workout_status">Trainingsstatus</string>
+    <string name="permission_display_over_other_apps">%1$s heeft toestemming nodig om boven andere apps te worden weergegeven om Bangle.js-horloges activiteiten te laten starten via intents wanneer %1$s op de achtergrond is.
+\n
+\nDit kan worden gebruikt om een muziek-app te starten en een nummer af te spelen, en vele andere dingen.
+\n
+\nTik op \'%2$s\' en vervolgens op \'%1$s\' en schakel \'Weergave over andere apps toestaan\' in, tik vervolgens op \'Terug\' om terug te keren naar %1$s.
+\n
+\nOm te voorkomen dat %1$s om machtigingen vraagt, gaat u naar \'Instellingen\' en schakelt u \'Controleer machtigingsstatus\' uit.
+\n
+\nZorg ervoor dat u %1$s de machtigingen verleent die nodig zijn om te functioneren zoals u verwacht.</string>
+    <string name="pref_header_spo2">Bloedzuurstof</string>
+    <string name="pref_screen_brightness">Schermhelderheid</string>
+    <string name="pref_summary_screen_on_on_notifications">Schakel het scherm van de band in wanneer er een melding binnenkomt</string>
+    <string name="pref_title_screen_on_on_notifications">Scherm aan bij meldingen</string>
+    <string name="seconds_12">12 seconden</string>
+    <string name="seconds_14">14 seconden</string>
+    <string name="seconds_13">13 seconden</string>
+    <string name="prefs_always_on_display">Scherm altijd aan</string>
+    <string name="seconds_15">15 seconden</string>
+    <string name="menuitem_personal_activity_intelligence">Persoonlijke activiteitsintelligentie</string>
+    <string name="pref_header_heartrate_alerts">Hartslagwaarschuwingen</string>
+    <string name="pref_header_heartrate_allday">Bewaking gedurende de hele dag</string>
+    <string name="prefs_always_on_display_summary">Houd het scherm van de band altijd aan</string>
+    <string name="pref_header_stress">Stress</string>
+    <string name="menuitem_countdown">Aftellen</string>
+    <string name="pref_header_heartrate_sleep">Slaap</string>
+    <string name="fw_upgrade_notice_amazfit_gts3">U staat op het punt de %s-firmware op uw Amazfit GTS 3 te installeren.
+\n
+\nDe band zal opnieuw opstarten na het installeren van het .zip-bestand.
+\n
+\nVERDERGAAN IS OP EIGEN RISICO!</string>
+    <string name="pref_title_notifications_ignore_low_priority">Negeer meldingen met lage prioriteit</string>
+    <string name="pref_summary_notifications_ignore_low_priority">Stuur geen meldingen met lage en minimale prioriteit naar het horloge</string>
+    <string name="devicetype_flipper_zero">Flipper zero</string>
+    <string name="pref_title_notification_prefer_long_text">Voorkeur voor lange meldingstekst</string>
+    <string name="pref_summary_notification_prefer_long_text">Stuur, indien beschikbaar, de lange meldingstekst naar het apparaat</string>
+    <string name="kind_app">App</string>
+    <string name="activity_prefs_allow_bluetooth_intent_api">Bluetooth Intent API</string>
+    <string name="activity_prefs_summary_allow_bluetooth_intent_api">Sta het beheren van de Bluetooth-verbinding toe via de Intent API</string>
+    <string name="menuitem_unknown_app">Onbekend (%s)</string>
+    <string name="pref_title_overwrite_settings_on_connection">Instellingen overschrijven bij verbinding</string>
+    <string name="pref_summary_overwrite_settings_on_connection">Wanneer er verbinding met het apparaat gemaakt wordt, overschrijf alle instellingen erop.</string>
+    <string name="devicetype_amazfit_gts3">Amazfit GTS 3</string>
+    <string name="pref_title_third_party_app_device_settings">Toestaan dat apps van derden instellingen wijzigen</string>
+    <string name="dismiss">Afwijzen</string>
+    <string name="devicetype_amazfit_gtr3">Amazfit GTR 3</string>
+    <string name="activity_type_dance">Dansen</string>
+    <string name="activity_type_indoor_fitness">Binnenfitness</string>
+    <string name="activity_type_gymnastics">Gymnastiek</string>
+    <string name="activity_type_hiit">Intervaltraining met hoge intensiteit</string>
+    <string name="activity_type_core_training">Kerntraining</string>
+    <string name="activity_type_stretching">Rekken</string>
+    <string name="activity_type_stepper">Steps</string>
+    <string name="activity_type_pilates">Pilates</string>
+    <string name="activity_type_volleyball">Volleybal</string>
+    <string name="activity_type_table_tennis">Tafeltennis</string>
+    <string name="activity_type_bowling">Bowlen</string>
+    <string name="activity_type_boxing">Boksen</string>
+    <string name="activity_type_kickboxing">Kickboksen</string>
+    <string name="activity_type_street_dance">Street Dance</string>
+    <string name="activity_type_zumba">Zumba</string>
+    <string name="activity_type_indoor_ice_skating">Indoor schaatsen</string>
+    <string name="devicetype_super_cars">Shell Racing</string>
+    <string name="pref_summary_third_party_app_device_settings">Sta andere ge茂nstalleerde apps van derden toe om apparaatinstellingen aan te passen via intents.</string>
+    <string name="devicetype_amazfit_gtr_lite">Amazfit GTR Lite</string>
+    <string name="fw_upgrade_notice_amazfit_gtr3">U staat op het punt de %s-firmware op uw Amazfit GTR 3 te installeren.
+\n
+\nDe band zal opnieuw opstarten na het installeren van het .zip-bestand.
+\n
+\nVERDERGAAN IS OP EIGEN RISICO!</string>
+    <string name="watchface_setting_button_move_hands">Wijzers verplaatsen</string>
+    <string name="abstract_chart_fragment_kind_rem_sleep">REM-slaap</string>
+    <string name="HeartRateZones">Hartslagzones</string>
+    <string name="hrZoneNa">N.v.t</string>
+    <string name="hrZoneWarmUp">Opwarmen</string>
+    <string name="hrZoneFatBurn">Vetverbranding</string>
+    <string name="currentWorkoutLoad">Trainingsbelasting</string>
+    <string name="sports_activity_confirm_delete_description">Weet u zeker dat u %d activiteiten wilt verwijderen\?</string>
+    <string name="sports_activity_confirm_delete_title">Verwijder %d activiteiten</string>
+    <string name="supercars_lights_label">Lichten</string>
+    <string name="pref_header_authentication">Authenticatie</string>
+    <string name="supercars_lights_blinking_label">Knipperen</string>
+    <string name="hrZoneExtreme">Maximale inspanning</string>
+    <string name="hrZoneAnaerobic">Ana毛roob</string>
+    <string name="TrainingEffect">Trainingseffect</string>
+    <string name="aerobicTrainingEffect">Aeroob effect</string>
+    <string name="anaerobicTrainingEffect">Ana毛roob effect</string>
+    <string name="maximumOxygenUptake">Maximale zuurstofopname</string>
+    <string name="supercars_turbo_speed_label">Turbosnelheid</string>
+    <string name="hrZoneAerobic">Effici毛ntie</string>
+    <string name="custom">Aangepast</string>
+    <string name="single_band">Enkele band</string>
+    <string name="dual_band">Dubbele band</string>
+    <string name="gps">GPS</string>
+    <string name="gps_gnolass">GPS + GNOLASS</string>
+    <string name="gps_galileo">GPS + GALILEO</string>
+    <string name="speed_first">Snelheid eerst</string>
+    <string name="accuracy_first">Nauwkeurigheid eerst</string>
+    <string name="pref_header_offline_voice">Offline stem</string>
+    <string name="pref_workout_detection_categories_title">Trainingscategorie毛n</string>
+    <string name="pref_workout_detection_alert_summary">Melden wanneer een training wordt gedetecteerd</string>
+    <string name="pref_workout_detection_sensitivity">Gevoeligheid</string>
+    <string name="vibration_profile_default">Standaard</string>
+    <string name="pref_screen_notification_profile_schedule">Schema</string>
+    <string name="prefs_always_on_display_follow_watchface">Stijl volgt wijzerplaat</string>
+    <string name="accuracy">Nauwkeurigheid</string>
+    <string name="pref_gps_header">GPS</string>
+    <string name="all_satellites">Alle satellieten</string>
+    <string name="pref_workout_detection_alert_title">Waarschuwing</string>
+    <string name="low_power_gps">GPS met laag vermogen</string>
+    <string name="pref_cover_to_mute">Afdekken om te dempen</string>
+    <string name="pref_agps_header">AGPS</string>
+    <string name="pref_agps_expiry_reminder_enabled">AGPS-vervalherinnering</string>
+    <string name="pref_workout_detection_summary">Detecteer training automatisch</string>
+    <string name="balanced">Gebalanceerd</string>
+    <string name="power_saving">Energiebesparing</string>
+    <string name="gps_bds">GPS + BDS</string>
+    <string name="pref_header_sound_vibration">Geluid en trillingen</string>
+    <string name="pref_gps_mode_preset">GPS-modus</string>
+    <string name="pref_crown_vibration">Kroonvibratie</string>
+    <string name="offline_voice_respond_turn_wrist">Reageren bij het draaien van de pols</string>
+    <string name="offline_voice_respond_screen_on">Reageren wanneer scherm aan gaat</string>
+    <string name="pref_gps_combination">GPS-combinatie</string>
+    <string name="pref_gps_band">GPS-band</string>
+    <string name="pref_gps_satellite_search">Satelliet zoeken</string>
+    <string name="pref_vibrate_for_alert">Trillen voor waarschuwing</string>
+    <string name="pref_text_to_speech">Tekst naar spraak</string>
+    <string name="pref_alert_tone">Waarschuwingstoon</string>
+    <string name="pref_sleep_mode_smart_enable_summary">Schakel de slaapmodus automatisch in wanneer de band tijdens het slapen wordt gedragen</string>
+    <string name="offline_voice_response_during_screen_lighting">Reageren tijdens schermverlichting</string>
+    <string name="pref_agps_expiry_reminder_time">AGPS-vervalherinneringstijd</string>
+    <string name="pref_workout_detection_categories_summary">Trainingscategorie毛n die automatisch gedetecteerd kunnen worden</string>
+    <string name="pref_workout_detection_title">Trainingsdetectie</string>
+    <string name="pref_sleep_mode_sleep_screen_summary">Toon het slaapscherm wanneer het scherm wordt gewekt tijdens de slaapmodus, om afleiding te verminderen</string>
+    <string name="pref_sleep_mode_sleep_screen_title">Slaapscherm</string>
+    <string name="pref_sleep_mode_title">Slaapstand</string>
+    <string name="pref_sleep_mode_smart_enable_title">Slim inschakelen</string>
+    <string name="buttons_on_left">Knoppen links</string>
+    <string name="pref_enable_unsupported_settings_summary">Hierdoor krijgt u toegang tot alle beschikbare instellingen, zelfs als deze niet door het apparaat worden ondersteund. Dit kan instabiliteit en crashes op het apparaat veroorzaken.</string>
+    <string name="buttons_on_right">Knoppen rechts</string>
+    <string name="prefs_weardirection">Draagrichting</string>
+    <string name="pref_screen_notification_profile_todo_list">Takenlijst</string>
+    <string name="seconds_25">25 seconden</string>
+    <string name="pref_enable_unsupported_settings_title">Niet-ondersteunde instellingen inschakelen</string>
+    <string name="prefs_always_on_display_style">Stijl</string>
+    <string name="prefs_control_center">Bedieningspaneel</string>
+    <string name="pref_title_touch_spotify_galaxy_app">Spotify (alleen Galaxy Wearable-app)</string>
+    <string name="intent_api_allow_trigger_export_summary">Het activeren van database-export via Intent API toestaan</string>
+    <string name="world_clock_code">Code</string>
+    <string name="function_enabled">Ingeschakeld</string>
+    <string name="activity_prefs_goal_standing_time_minutes">Dagelijks doel: statijd in minuten</string>
+    <string name="menuitem_calendar">Kalender</string>
+    <string name="menuitem_membership_cards">Lidmaatschapskaarten</string>
+    <string name="menuitem_phone">Telefoon</string>
+    <string name="pref_title_upper_button_long_press_action">Bovenste knop lang indrukken actie</string>
+    <string name="pref_screen_auto_brightness_summary">Pas de helderheid van het scherm aan op basis van het omgevingslicht</string>
+    <string name="devicetype_sony_wh_1000xm2">Sony WH-1000XM2</string>
+    <string name="pref_chart_sleep_lines_limit">Hoeveel slaapsessieregels moeten worden weergegeven voordat ze worden gescrolld</string>
+    <string name="devicetype_sony_wf_1000xm4">Sony WH-1000XM4</string>
+    <string name="fw_upgrade_notice_amazfit_gtr4">U staat op het punt de %s-firmware op uw Amazfit GTR 4 te installeren.
+\n
+\nDe band zal opnieuw opstarten na het installeren van het .zip-bestand.
+\n
+\nVERDERGAAN IS OP EIGEN RISICO!</string>
+    <string name="menuitem_voice_memos">Spraakmemo\'s</string>
+    <string name="fw_upgrade_notice_amazfit_gts4">U staat op het punt de %s-firmware op uw Amazfit GTS 4 te installeren.
+\n
+\nDe band zal opnieuw opstarten na het installeren van het .zip-bestand.
+\n
+\nVERDERGAAN IS OP EIGEN RISICO!</string>
+    <string name="activity_type_indoor_walking">Binnen wandelen</string>
+    <string name="devicetype_amazfit_gts4">Amazfit GTS 4</string>
+    <string name="menuitem_todo">Te doen</string>
+    <string name="pref_title_lower_button_short_press_action">Onderste knop korte druk actie</string>
+    <string name="pref_header_intent_api">Intent API</string>
+    <string name="prefs_control_center_summary">Kies de items in de keuzelijst van het controlecentrum</string>
+    <string name="activity_prefs_target_weight_kg">Streefgewicht in kg</string>
+    <string name="activity_prefs_goal_fat_burn_time_minutes">Dagelijks doel: vetverbrandingstijd in minuten</string>
+    <string name="menuitem_sun_moon">Zon &amp; Maan</string>
+    <string name="menuitem_one_tap_measuring">Meten met 茅茅n tik</string>
+    <string name="menuitem_offline_voice">Offline stem</string>
+    <string name="elevationGain">Hoogtewinst</string>
+    <string name="elevationLoss">Hoogteverlies</string>
+    <string name="pref_title_touch_spotify_official_app">Spotify (alleen offici毛le app)</string>
+    <string name="prefs_touch_lock_buds2">Aanraakbediening</string>
+    <string name="intent_api_allow_activity_sync_title">Activiteitssynchronisatietrigger toestaan</string>
+    <string name="intent_api_allow_activity_sync_summary">Activeringssynchronisatie toestaan via Intent API</string>
+    <string name="intent_api_allow_trigger_export_title">Database-export toestaan</string>
+    <string name="intent_api_broadcast_export_title">Broadcast op database export</string>
+    <string name="intent_api_broadcast_export_summary">Een Intent uitzenden wanneer het exporteren van de database is voltooid</string>
+    <string name="devicetype_amazfit_gtr4">Amazfit GTR 4</string>
+    <string name="debugactivity_confirm_remove_device_preferences_title">Apparaatvoorkeuren verwijderen\?</string>
+    <string name="debugactivity_confirm_remove_device_preferences">Hiermee worden de apparaatvoorkeuren voor alle aangesloten apparaten gereset. Weet je het zeker\?</string>
+    <string name="fw_upgrade_notice_amazfit_gts4_mini">U staat op het punt de %s-firmware op uw Amazfit GTS 4 Mini te installeren.
+\n
+\nDe band zal opnieuw opstarten na het installeren van het .zip-bestand.
+\n
+\nVERDERGAAN IS OP EIGEN RISICO!</string>
+    <string name="pref_title_notification_media_ignores_application_list">App-lijst negeren voor medianotificaties</string>
+    <string name="pref_summary_notification_media_ignores_application_list">Verwerk mediameldingen v贸贸r de app-lijst. Als deze voorkeur niet is aangevinkt, moeten media-apps worden toegestaan in de app-lijst om mediabediening op het apparaat te laten werken.</string>
+    <string name="devicetype_amazfit_gts4_mini">Amazfit GTS 4 Mini</string>
+    <string name="menuitem_brightness">Helderheid</string>
+    <string name="menuitem_bluetooth">Bluetooth</string>
+    <string name="menuitem_wifi">Wi-Fi</string>
+    <string name="menuitem_theater_mode">Theatermodus</string>
+    <string name="menuitem_volume">Volume</string>
+    <string name="menuitem_screen_always_lit">Scherm altijd verlicht</string>
+    <string name="menuitem_lockscreen">Vergrendelscherm</string>
+    <string name="menuitem_eject_water">Water uitwerpen</string>
+    <string name="menuitem_unsupported">[NIET ONDERSTEUND] %s</string>
+    <string name="sony_speak_to_chat_sensitivity_standard">Standaard</string>
+    <string name="pref_screen_auto_brightness_title">Automatische helderheid</string>
+    <string name="devicetype_galaxybuds_2">Galaxy Buds2</string>
+    <string name="fw_upgrade_notice_amazfit_band7">U staat op het punt de %s-firmware op uw Amazfit Band 7 te installeren.
+\n
+\nDe band zal opnieuw opstarten na het installeren van het .zip-bestand.
+\n
+\nVERDERGAAN IS OP EIGEN RISICO!</string>
+    <string name="activity_type_pool_swimming">Zwembad zwemmen</string>
+    <string name="devicetype_amazfit_band7">Amazfit-band 7</string>
+    <string name="pref_huami_truncate_fetch_operation_timestamps_summary">Verkort de tijdstempels van de ophaalbewerking tot minuten. Schakel deze instelling uit om de tijdstempels binnen enkele seconden te behouden als u problemen ondervindt tijdens het ophalen van zeer korte trainingen.</string>
+    <string name="pref_huami_truncate_fetch_operation_timestamps_title">Tijdstempels voor ophaalbewerkingen inkorten</string>
+    <string name="sony_ambient_sound_control_button_modes">Instellingen knop omgevingsgeluid</string>
+    <string name="sony_ambient_sound_control_button_mode_nc_off">Ruisonderdrukking, uit</string>
+    <string name="devicetype_sony_linkbuds_s">Sony LinkBuds S</string>
+    <string name="sony_ambient_sound_control_button_mode_nc_as_off">Ruisonderdrukking, Omgevingsgeluid, Uit</string>
+    <string name="sony_ambient_sound_control_button_mode_nc_as">Ruisonderdrukking, omgevingsgeluid</string>
+    <string name="sony_ambient_sound_control_button_mode_as_off">Omgevingsgeluid, uit</string>
+    <string name="sony_quick_access_double_tap">Snelle toegang (dubbele tik)</string>
+    <string name="sony_quick_access_triple_tap">Snelle toegang (driedubbele tik)</string>
+    <string name="pref_agps_update_time">AGPS-updatetijd</string>
+    <string name="menuitem_wechat_pay">WeChat Pay</string>
+    <string name="pref_agps_expire_time">AGPS-vervaltijd</string>
+    <string name="appmanager_watchface_activate">Activeer</string>
+    <string name="appmanager_app_start">Start</string>
+    <string name="appmanager_app_download">Download naar cache</string>
+    <string name="appmanager_download_started">Downloaden van app gestart</string>
+    <string name="appmanager_downloaded_to_cache">%s gedownload naar cache</string>
+    <string name="appmanager_download_app_error">Fout bij het downloaden van de app</string>
+    <string name="devicetype_asteroidos">AsteroidOS</string>
+    <string name="fw_upgrade_notice_amazfit_trex2">U staat op het punt de %s-firmware op uw Amazfit T-Rex 2 te installeren.
+\n
+\nDe band zal opnieuw opstarten na het installeren van het .zip-bestand.
+\n
+\nVERDERGAAN IS OP EIGEN RISICO!</string>
+    <string name="devicetype_amazfit_trex_2">Amazfit T-Rex 2</string>
+    <string name="pref_workout_detection_ask_first">Vraag het mij eerst</string>
+    <string name="pref_workout_detection_ask_first_summary">Bevestiging op horloge vereisen bij het detecteren van deze training</string>
+    <string name="activity_type_rowing">Roeien</string>
+    <string name="pref_workout_detection_time">Actieve minuten voor detectie</string>
+    <string name="pref_workout_detection_enabled">Detectie ingeschakeld</string>
+    <string name="pref_workout_detection_enabled_summary">Schakel automatische detectie van deze training in</string>
+    <string name="pref_workout_detection_time_summary">Het aantal minuten dat de training aan de gang moet zijn voordat deze wordt gedetecteerd</string>
+    <string name="georgian">Georgisch</string>
+    <string name="devicetype_soflow_s06">SoFlow SO6</string>
+    <string name="devicetype_galaxybuds_2_pro">Galaxy Buds2 Pro</string>
+    <string name="pref_title_lock_unlock">Slot</string>
+    <string name="pref_camera_remote_title">Camera-afstandsbediening</string>
+    <string name="pref_camera_remote_summary">Hiermee kan het horloge de camera van de telefoon activeren</string>
+    <string name="pref_workout_keep_screen_on_title">Houd het scherm aan tijdens een training</string>
+    <string name="wifi_hotspot">Wi-Fi-hotspot</string>
+    <string name="wifi_hotspot_summary">Bedien de Wi-Fi-hotspot op het horloge</string>
+    <string name="wifi_hotspot_configuration">Wi-Fi Hotspot Configuratie</string>
+    <string name="wifi_ssid">SSID</string>
+    <string name="wifi_hotspot_status">Status Wi-Fi-hotspot</string>
+    <string name="wifi_hotspot_start_summary">Start de Wi-Fi-hotspot op het horloge</string>
+    <string name="wifi_hotspot_stop_summary">De Wi-Fi-hotspot op het horloge stoppen</string>
+    <string name="ftp_server">FTP-server</string>
+    <string name="ftp_server_summary">Beheer de FTP-server op het horloge</string>
+    <string name="ftp_server_status">Status FTP-server</string>
+    <string name="croatian">Kroatisch</string>
+    <string name="sony_speak_to_chat_summary">Schakel ruisonderdrukking automatisch uit wanneer je begint te praten.</string>
+    <string name="ftp_server_start_summary">Start de FTP-server op het horloge</string>
+    <string name="ftp_server_stop_summary">Stop de FTP-server op het horloge</string>
+    <string name="ftp_server_configuration">FTP-serverconfiguratie</string>
+    <string name="ftp_server_root_dir">Hoofdmap</string>
+    <string name="username">Gebruikersnaam</string>
+    <string name="stop">Stop</string>
+    <string name="address">Adres</string>
+    <string name="pref_workout_keep_screen_on_summary">Het scherm blijft aan tijdens een training en de helderheid wordt aangepast om continu real-time trainingsgegevens weer te geven</string>
+    <string name="status">Status</string>
+    <string name="yesterdays_activity">Activiteit van gisteren</string>
+    <string name="pref_morning_updates_categories_title">Categorie毛n ochtendupdates</string>
+    <string name="pref_title_notification_cache_while_disconnected">Onthouden wanneer buiten bereik</string>
+    <string name="pref_summary_notification_cache_while_disconnected">Stuur gemiste meldingen wanneer een apparaat opnieuw verbinding maakt nadat het buiten bereik is geweest</string>
+    <string name="pref_morning_updates_title">Ochtendupdates</string>
+    <string name="pref_morning_updates_summary">Geef elke ochtend updates weer</string>
+    <string name="pref_morning_updates_categories_summary">Lijst met categorie毛n om elke ochtend weer te geven</string>
+    <string name="pref_summary_receive_calls_watch">Voer en ontvang oproepen rechtstreeks op het horloge</string>
+    <string name="bluetooth_calls">Bluetooth-oproepen</string>
+    <string name="bluetooth_calls_pairing">Koppelen voor Bluetooth-oproepen</string>
+    <string name="bluetooth_calls_settings">Instellingen voor Bluetooth-oproepen</string>
+    <string name="pref_display_caller_title">Contactgegevens weergeven</string>
+    <string name="pref_display_caller_summary">Telefoonnummer of naam weergeven voor inkomende oproepen</string>
+    <string name="pref_pair_bluetooth_calls_title">Koppelen voor bluetooth-oproepen</string>
+    <string name="pref_pair_bluetooth_calls_summary">Klik hier om het koppelingsproces te starten</string>
+    <string name="pref_pair_bluetooth_calls_help_title">Bluetooth-oproepen ontvangen</string>
+    <string name="pref_pair_bluetooth_calls_help_summary">Om bluetooth-oproepen te ontvangen, moet u uw telefoon koppelen met het tweede bluetooth-apparaat van het horloge.</string>
+    <string name="pref_pair_bluetooth_calls_help_1">1. Tik op de onderstaande knop om het koppelingsproces te starten.</string>
+    <string name="pref_pair_bluetooth_calls_help_2">2. Ga naar de Bluetooth-instellingen van je telefoon en koppel met het nieuwe apparaat dat verschijnt (met soortgelijke naam als je huidige horloge, maar met een achtervoegsel, bijv. \"Amazfit GTR 4 - AFC8\").</string>
+    <string name="pref_pair_bluetooth_calls_help_3">3. Schakel de instelling voor Bluetooth-oproepen hieronder in.</string>
+    <string name="pref_pair_bluetooth_calls_help_warning">WAARSCHUWING: Als u Bluetooth-oproepen inschakelt zonder te koppelen met het tweede bluetooth-apparaat, werken oproepmeldingen mogelijk niet zoals verwacht.</string>
+    <string name="sony_button_mode_help_title">Knopmodi - Help</string>
+    <string name="pref_switch_controls_anc_ambient_off">Ruisonderdrukking 鈫愨啋 Omgeving 鈫愨啋 Uit</string>
+    <string name="sony_button_mode_help_summary">Een beschrijving van elke knopmodus</string>
+    <string name="single_tap">Enkele tik</string>
+    <string name="double_tap">Dubbeltik</string>
+    <string name="triple_tap">Driedubbele tik</string>
+    <string name="long_press">Lang indrukken</string>
+    <string name="quick_attention">Snelle aandacht</string>
+    <string name="continue_pressing">Blijf drukken</string>
+    <string name="menuitem_cards">Kaarten</string>
+    <string name="menuitem_mi_ai">MI AI</string>
+    <string name="zepp_os_watchface_simplicity_data">Eenvoudige gegevens</string>
+    <string name="zepp_os_watchface_vibrant">Levendig</string>
+    <string name="zepp_os_watchface_emerald_moonlight">Smaragdgroen maanlicht</string>
+    <string name="zepp_os_watchface_rotating_earth">Roterende aarde</string>
+    <string name="zepp_os_watchface_superposition">Superpositie</string>
+    <string name="zepp_os_watchface_multiple_data">Meerdere gegevens</string>
+    <string name="zepp_os_watchface_red_fantasy">Rode fantasie</string>
+    <string name="zepp_os_watchface_minimalist">Minimalistisch</string>
+    <string name="zepp_os_watchface_rush">Haast</string>
+    <string name="zepp_os_watchface_business_style">Zakelijke stijl</string>
+    <string name="prefs_shortcut_cards">Snelkoppelingskaarten</string>
+    <string name="prefs_shortcut_cards_summary">Snelkoppelingskaarten zichtbaar wanneer u naar rechts veegt op de wijzerplaat. Wanneer een app actief is, worden automatisch gegenereerde kaarten niet be茂nvloed door deze instelling.</string>
+    <string name="menuitem_forecast">Voorspelling</string>
+    <string name="menuitem_last_workout">Laatste training</string>
+    <string name="menuitem_total_workout">Totale training</string>
+    <string name="menuitem_vo2_max">VO鈧� Max</string>
+    <string name="menuitem_recommendation">Aanbeveling</string>
+    <string name="menuitem_aqi">Luchtkwaliteitsindex</string>
+    <string name="devicetype_amazfit_gtr3_pro">Amazfit GTR 3 Pro</string>
+    <string name="fw_upgrade_notice_amazfit_gtr3_pro">U staat op het punt de %s-firmware op uw Amazfit GTR 3 Pro te installeren.
+\n
+\nDe band zal opnieuw opstarten na het installeren van het .zip-bestand.
+\n
+\nVERDERGAAN IS OP EIGEN RISICO!</string>
+    <string name="devicetype_sony_wh_1000xm5">Sony WH-1000XM5</string>
+    <string name="fossil_hr_connection_not_confirmed">Verbinding niet bevestigd op horloge, gebruikt nu ongeauthenticeerde modus</string>
+    <string name="fossil_hr_pairing_successful">Koppelen met horloge gelukt</string>
+    <string name="fossil_hr_pairing_failed">Koppelen met horloge mislukt</string>
+    <string name="fossil_hr_confirmation_skipped">Bevestiging op apparaat overslaan</string>
+    <string name="fossil_hr_confirmation_timeout">Time-out voor bevestiging</string>
+    <string name="fossil_hr_confirm_connection">Bevestig alstublieft op het horloge</string>
+    <string name="debug_companion_show_associated">Toon Companion-apparaatkoppelingen</string>
+    <string name="debug_companion_pair_current">Koppel het huidige apparaat als Companion-apparaat</string>
+    <string name="activity_error_share_failed">Bestand delen is mislukt.</string>
+    <string name="activity_detail_share_gps_label">GPS-track delen</string>
+    <string name="activity_detail_share_raw_summary">Ruwe samenvatting delen</string>
+    <string name="activity_detail_share_raw_details">Deel onbewerkte details</string>
+    <string name="gpx_route_upload_failed">Gpx-route-upload mislukt</string>
+    <string name="gpx_route_upload_in_progress">Gpx-route uploaden</string>
+    <string name="gpx_route_upload_complete">Gpx-route uploaden voltooid</string>
+    <string name="kind_gpx_route">GPX-route</string>
+    <string name="dev_tools">Ontwikkeltools</string>
+    <string name="contact_no_free_slots_description">Het apparaat heeft geen vrije slots voor contacten (totaal slots: %1$s)</string>
+    <string name="title_activity_set_contacts">Contacten configureren</string>
+    <string name="pref_contacts_title">Contactpersonen</string>
+    <string name="pref_contacts_summary">Configureer contactpersonen op het horloge</string>
+    <string name="contact_delete_confirm_description">Weet u zeker dat u \'%1$s\' wilt verwijderen\?</string>
+    <string name="contact_delete_confirm_title">Contactpersoon verwijderen</string>
+    <string name="title_activity_contact_details">Contactpersoondetails</string>
+    <string name="contact_name">Naam</string>
+    <string name="contact_missing_number">Contactpersoonnummer is leeg</string>
+    <string name="contact_phone_number">Telefoonnummer</string>
+    <string name="contact_missing_name">De naam van de contactpersoon is leeg</string>
+    <string name="intent_api_allow_debug_commands_summary">Sta het activeren van debug-menucommando\'s toe via Intent API</string>
+    <string name="intent_api_allow_debug_commands_title">Debug-commando\'s toestaan</string>
+    <string name="pref_header_navigation">Navigatie</string>
+    <string name="pref_summary_navigation_forward">Instructies van navigatie-apps doorsturen naar het apparaat</string>
+    <string name="pref_title_navigation_forward">Stuur navigatie naar apparaat</string>
+    <string name="voice_service_package_title">Spraakservicepakket</string>
+    <string name="voice_service_package_summary">Applicatie die de service bevat die spraakopdrachten afhandelt</string>
+    <string name="voice_service_class_title">Spraakserviceklasse</string>
+    <string name="voice_service_class_summary">Volledig servicepad voor het verwerken van spraakopdrachten</string>
+    <string name="voice_service">Spraakservice</string>
+    <string name="pref_app_logs_summary">Logboeken van horloge-apps inschakelen</string>
+    <string name="pref_app_logs_start_summary">Loggen vanuit horloge-apps starten</string>
+    <string name="pref_app_logs_title">App-logboeken</string>
+    <string name="pref_app_logs_stop_summary">Loggen vanuit horloge-apps stoppen</string>
+    <string name="fwapp_install_device_not_supported">Bestand kan niet worden ge茂nstalleerd, apparaat wordt niet ondersteund.</string>
+    <string name="share_screenshot">Schermafbeelding delen</string>
+    <string name="screenshot_taken">Schermafbeelding gemaakt</string>
+    <string name="charts_legend_stress_average">Stress gemiddeld</string>
+    <string name="watchface_widget_type_uv_index">UV-index</string>
+    <string name="stress_relaxed">Ontspannen</string>
+    <string name="stress_mild">Mild</string>
+    <string name="stress_moderate">Matig</string>
+    <string name="stress_high">Hoog</string>
+    <string name="pref_summary_banglejs_phone_gps_network_only">Gebruik alleen de netwerkprovider om de locatie te bepalen. Dit vermindert het stroomverbruik ten koste van de nauwkeurigheid. Opnieuw verbinden is benodigd.</string>
+    <string name="pref_title_banglejs_phone_gps_update_interval">Update-interval GPS-gegevens</string>
+    <string name="action_changelog">Wijzigingenlijst</string>
+    <string name="pref_title_banglejs_phone_gps_enbale">Gebruik GPS-gegevens van telefoon</string>
+    <string name="pref_summary_banglejs_phone_gps_update_interval">Het interval voor hoe vaak de gps-positie wordt bijgewerkt, in ms</string>
+    <string name="pref_title_banglejs_phone_gps_network_only">Gebruik alleen het netwerk om de locatie te bepalen</string>
+    <string name="pref_summary_banglejs_phone_gps_enbale">Gebruik de GPS-gegevens van de telefoon om de GPS-gegevens van het Bangle-apparaat te overschrijven</string>
+    <string name="changelog_title">Wat is er nieuw</string>
+    <string name="description">Beschrijving</string>
+    <string name="preview_image">Voorbeeldafbeelding</string>
+    <string name="changelog_full_title">Wijzigingenlijst</string>
+    <string name="status_icon">Statuspictogram</string>
+    <string name="changelog_show_full">Meer鈥�</string>
+    <string name="changelog_ok_button">OK</string>
+    <string name="title">Titel</string>
+    <string name="num_min">%d min</string>
+    <string name="pai_total">Totaal</string>
+    <string name="pai_chart_per_week">PAI per week</string>
+    <string name="pai_plus_num">+%d</string>
+    <string name="pai_chart_per_month">PAI per maand</string>
+    <string name="pai_day">Dag-toename</string>
+    <string name="loyalty_cards_catima_package">Catima-pakketnaam</string>
+    <string name="loyalty_cards_sync_archived">Gearchiveerde kaarten synchroniseren</string>
+    <string name="loyalty_cards_sync_starred">Alleen kaarten met ster synchroniseren</string>
+    <string name="loyalty_cards_sync_title">Klantenkaarten synchroniseren</string>
+    <string name="loyalty_cards_catima_not_compatible">De ge茂nstalleerde Catima-versie is niet compatibel met Gadgetbridge. Update Catima en Gadgetbridge naar de nieuwste versies.</string>
+    <string name="loyalty_cards_catima">Catima</string>
+    <string name="loyalty_cards_open_catima">Open Catima</string>
+    <string name="loyalty_cards_catima_permissions_title">Ontbrekende machtigingen</string>
+    <string name="loyalty_cards_sync">Synchroniseren</string>
+    <string name="loyalty_cards">Klantenkaarten</string>
+    <string name="loyalty_cards_syncing">%d klantenkaarten synchroniseren met apparaat</string>
+    <string name="loyalty_cards_sync_groups_only">Alleen specifieke groepen synchroniseren</string>
+    <string name="loyalty_cards_install">Catima installeren</string>
+    <string name="loyalty_cards_sync_groups">Te synchroniseren groepen</string>
+    <string name="loyalty_cards_sync_summary">Tik om de kaarten met het horloge te synchroniseren</string>
+    <string name="loyalty_cards_catima_not_installed">Catima is nodig om de klantenkaarten te beheren</string>
+    <string name="loyalty_cards_install_catima_fail">Kan app store niet openen om Catima te installeren</string>
+    <string name="loyalty_cards_catima_permissions_summary">Gadgetbridge heeft leesrechten op Catima-kaarten nodig om ze te synchroniseren. Tik op deze knop om ze te verlenen.</string>
+    <string name="loyalty_cards_sync_options">Synchronisatie-opties</string>
+    <string name="pref_title_mb_intents">Broadcast Media Knop Intents Direct</string>
+    <string name="pref_summary_mb_intents">Inschakelen als mediabediening van apparaat niet werkt voor bepaalde apps</string>
+    <string name="devicetype_bohemic_smart_bracelet">Bohemic Smart Armband</string>
+    <string name="devicetype_vivomove_hr">Garmin Vivomove HR</string>
+    <string name="busy_task_fetch_sleep_respiratory_rate_data">Gegevens over slaapademhalingsfrequentie ophalen</string>
+    <string name="busy_task_fetch_sports_summaries">Sportoverzichten ophalen</string>
+    <string name="busy_task_fetch_debug_logs">Foutopsporingslogboeken ophalen</string>
+    <string name="busy_task_fetch_sports_details">Sportgegevens ophalen</string>
+    <string name="busy_task_fetch_stress_data">Stressgegevens ophalen</string>
+    <string name="busy_task_fetch_pai_data">PAI-gegevens ophalen</string>
+    <string name="busy_task_fetch_spo2_data">SpO2-gegevens ophalen</string>
+    <string name="busy_task_fetch_hr_data">Hartslaggegevens ophalen</string>
+    <string name="devicetype_casiogwb5600">Casio GW-B5600</string>
+    <string name="devicetype_casiogmwb5000">Casio GMW-B5000</string>
+    <string name="activity_prefs_goals">Doelen</string>
+    <string name="activity_type_windsurfing">Windsurfen</string>
+    <string name="activity_type_rugby">Rugby</string>
+    <string name="activity_type_baseball">Honkbal</string>
+    <string name="activity_type_handball">Handbal</string>
+    <string name="activity_type_tennis">Tennis</string>
+    <string name="activity_type_dancing">Dansen</string>
+    <string name="activity_type_snowboarding">Snowboarden</string>
+    <string name="activity_type_icehockey">IJshockey</string>
+    <string name="activity_type_iceskating">Schaatsen</string>
+    <string name="activity_type_other">Overige</string>
+    <string name="withings_calibration_text_activity_target">Lijn ten slotte de activiteitswijzer uit op 100%. Houd er rekening mee dat deze wijzer alleen met de klok mee beweegt.</string>
+    <string name="withings_bt_calibration_previous">Vorige</string>
+    <string name="withings_bt_calibration_next">Volgende</string>
+    <string name="activity_type_surfing">Surfen</string>
+    <string name="activity_type_kitesurfing">Kitesurfen</string>
+    <string name="activity_type_football">Voetbal</string>
+    <string name="activity_type_squash">Squash</string>
+    <string name="activity_type_weightlifting">Gewichtheffen</string>
+    <string name="error_no_bluetooth_connect">Bluetooth-verbindingstoegang moet worden verleend en ingeschakeld om het scannen goed te laten werken</string>
+    <string name="activity_type_skiing">Ski毛n</string>
+    <string name="activity_type_riding">Paardrijden</string>
+    <string name="activity_type_hockey">Hockey</string>
+    <string name="error_no_bluetooth_scan">Bluetooth-scantoegang moet worden verleend en ingeschakeld om het scannen correct te laten werken</string>
+    <string name="activity_type_golf">Golfen</string>
+    <string name="withings_steel_hr">Withings Steel HR</string>
+    <string name="withings_calibration_text_minutes">Gebruik nu de draaiknop om de minutenwijzer op de 12 uit te lijnen.</string>
+    <string name="permission_request">Met %1$s kunt u berichten en andere gegevens van Android naar uw apparaat verzenden. Om dit te doen, heeft het toestemming nodig om toegang te krijgen tot die gegevens, zonder deze toestemmingen werkt het mogelijk niet goed.
+\n
+\nU krijgt nu enkele Android-dialoogvensters te zien waarin om die machtigingen wordt gevraagd.
+\n
+\nTik op \'%2$s\' om door te gaan.</string>
+    <string name="withings_calibration_text_hours">Gebruik de onderstaande draaiknop om de uurwijzer op de 12 uit te lijnen.</string>
+    <string name="pref_theme_dynamic">Dynamische kleuren</string>
+    <string name="pref_canned_message">Bericht</string>
+    <string name="drag_handle">sleep-handvat</string>
+    <string name="find_my_phone_found_it">GEVONDEN</string>
+    <string name="preferences_miband_1_2_settings" tools:ignore="TypographyFractions">Mi Band 1/2 instellingen</string>
+    <string name="preferences_miband_1_2_warning">Waarschuwing: Deze voorkeuren gelden alleen voor de Mi Band 1 en 2.</string>
+    <string name="pref_activity_full_sync_trigger_title">Volledige synchronisatie</string>
+    <string name="pref_show_changelog">Toon wijzigingenoverzicht na starten</string>
+    <string name="pref_show_changelog_summary">Toon na een update het overzicht van de wijzigingen sinds de vorige versie</string>
+    <string name="pref_activity_full_sync_trigger_summary">Start het synchroniseren van alle activiteitsgegevens</string>
+    <string name="pref_activity_full_sync_trigger_warning">Hiermee zal een volledige synchronisatie starten van alle activiteitsgegevens van het apparaat. Dit kan enkele minuten duren.</string>
+    <string name="pref_theme_dynamic_colors_not_available_warning">Dynamische kleuren zijn niet beschikbaar op je apparaat, alleen Android 12+ ondersteunt deze functionaliteit. Gadgetbridge zal de standaard Material 3-kleuren gebruiken.</string>
+    <string name="pref_theme_dynamic_colors_explanation">Let op: voor het dynamische-kleurenthema moet je Achtergrond-gebaseerde Kleuren of Kleurenpalet in je Android 12+ weergaveinstellingen inschakelen. Als je dat niet doet, zal Gadgetbridge de standaard Material 3-kleuren gebruiken.</string>
+    <string name="menuitem_zepp_coach">Zepp Coach</string>
+    <string name="fw_upgrade_notice_amazfitbip3pro">U staat op het punt om de firmware %s te installeren op uw Amazfit Bip 3 Pro. 
+\n 
+\nZorg ervoor dat u het .fw-bestand installeert en daarna het .res-bestand. Uw horloge wordt opnieuw gestart na het installeren van het .fw-bestand. 
+\n 
+\nOpmerking: U hoeft de .res niet te installeren als deze exact dezelfde is als degene die ervoor al was ge茂nstalleerd. 
+\n 
+\nGA VERDER OP EIGEN RISICO!</string>
+    <string name="devicetype_amazfit_bip3_pro">Amazfit Bip 3 Pro</string>
+    <string name="fw_upgrade_notice_amazfit_cheetah_pro">U staat op het punt de %s-firmware op uw Amazfit Cheetah Pro te installeren.
+\n
+\nDe band zal opnieuw opstarten na het installeren van het .zip-bestand.
+\n
+\nVERDERGAAN IS OP EIGEN RISICO!</string>
+    <string name="device_experimental">EXPERIMENTEEL</string>
+    <string name="fw_upgrade_notice_zepp_os">U staat op het punt de %s-firmware op uw %s te installeren.
+\n
+\nDe band zal opnieuw opstarten na het installeren van het .zip-bestand.
+\n
+\nVERDERGAAN IS OP EIGEN RISICO!</string>
+    <string name="devicetype_amazfit_cheetah_square">Amazfit Cheetah (vierkant)</string>
+    <string name="devicetype_amazfit_cheetah_round">Amazfit Cheetah (rond)</string>
+    <string name="devicetype_amazfit_cheetah_pro">Amazfit Cheetah Pro</string>
+    <string name="devicetype_amazfit_bip5">Amazfit Bip 5</string>
+    <string name="devicetype_amazfit_trex_ultra">Amazfit T-Rex Ultra</string>
+    <string name="devicetype_amazfit_gtr_mini">Amazfit GTR Mini</string>
+    <string name="devicetype_amazfit_falcon">Amazfit Falcon</string>
+    <string name="info_menu_structure_set">Menustructuur-JSON ingesteld in GB</string>
+    <string name="info_fossil_rebuild_watchface_custom_menu">Update alstublieft uw wijzerplaat voor het aangepaste menu</string>
+    <string name="error_invalid_menu_structure">Ongeldige menustructuur-JSON</string>
+    <string name="button_open_menu_companion">Open begeleidende menu-app</string>
+    <string name="button_reset_menu_structure">Reset menustructuur</string>
+    <string name="info_menu_structure_removed">Menustructuur verwijderd</string>
+    <string name="error_menu_companion_not_installed">\'HR Menu Companion\' is waarschijnlijk niet ge茂nstalleerd</string>
+    <string name="info_menu_structure_contents">Menustructuur: %s</string>
+    <string name="spanish_es">Spaans (Spanje)</string>
+    <string name="spanish_us">Spaans (Verenigde Staten)</string>
+    <string name="english_in">Engels (India)</string>
+    <string name="english_us">Engels (Verenigde Staten)</string>
+    <string name="spanish_mx">Spaans (Mexico)</string>
+    <string name="english_au">Engels (Australi毛)</string>
+    <string name="english_ca">Engels (Canada)</string>
+    <string name="english_gb">Engels (Verenigd Koninkrijk)</string>
+    <string name="french_ca">Frans (Canada)</string>
+    <string name="french_fr">Frans (Frankrijk)</string>
+    <string name="updatefirmwareoperation_updateproblem_free_space">Het apparaat heeft niet genoeg vrije ruimte</string>
+    <string name="text_receiver_activity_title">Stuur tekst naar apparaat</string>
+    <string name="updatefirmwareoperation_failed_low_mtu">Huidige MTU van %1$d is te laag, activeer alstublieft Hoge MTU in de apparaatinstellingen en verbind het apparaat opnieuw.</string>
+    <string name="devicetype_miband2_hrx">Mi Band HRX</string>
+    <string name="new_discover_activity_description">Schakel het nieuwe scan-scherm in, die de problemen met het vinden van apparaten zou moeten oplossen. Schakel dit uit wanneer u problemen ondervindt met het vinden en koppelen van uw apparaat.</string>
+    <string name="new_discover_activity_title">Nieuw scan-scherm inschakelen</string>
+    <string name="prefs_wena3_receive_calls_title">Instellingen binnenkomend gesprek</string>
+    <string name="prefs_wena3_button_action_name_start_timer">Start timer</string>
+    <string name="prefs_wena3_menu_icon_hint">Instellingen wordt altijd aan het eind getoond</string>
+    <string name="prefs_wena3_notification_vibration_repetition_3">Drie keer</string>
+    <string name="prefs_wena3_button_action_name_qrio_lock">Qrio blokkeren</string>
+    <string name="prefs_wena3_auto_power_off_turn_off_time_item">Uitschakeltijd</string>
+    <string name="prefs_wena3_vibration_short">Kort</string>
+    <string name="prefs_wena3_home_icon_name_calories">Calori毛n</string>
+    <string name="prefs_home_icon_left_item">Links</string>
+    <string name="prefs_wena3_led_none">Geen LED</string>
+    <string name="prefs_wena3_vibration_step_down">Stap omlaag</string>
+    <string name="prefs_wena3_vibration_smart_item">Slimme trilling</string>
+    <string name="prefs_wena3_home_icon_name_riiiver">Riiiver</string>
+    <string name="prefs_wena3_hint_background_sync">Sta de Wena toe om periodiek Gadgetbridge te vragen om activiteitsgegevens te downloaden van het apparaat</string>
+    <string name="prefs_wena3_home_icon_name_suica">Suica-saldo</string>
+    <string name="prefs_wena3_vibration_strength_item_medium">Middelmatig</string>
+    <string name="prefs_wena3_title_activity">Activiteitsinstellingen</string>
+    <string name="prefs_wena3_vibration_continuous">Doorgaand</string>
+    <string name="prefs_wena3_home_icon_name_pedometer">Stappenteller</string>
+    <string name="prefs_wena3_home_icon_name_qrio">Qrio</string>
+    <string name="white">Wit</string>
+    <string name="prefs_wena3_home_icon_name_clock">Huidige tijd</string>
+    <string name="prefs_wena3_item_background_sync">Synchronisatie activiteiten op de achtergrond</string>
+    <string name="prefs_wena3_vibration_strength_item_weak">Zwak</string>
+    <string name="prefs_wena3_home_icon_title">Startscherm-iconen</string>
+    <string name="prefs_wena3_item_weather_statusbar">Weer in statusbalk</string>
+    <string name="temperature_scale_celsius">Celsius</string>
+    <string name="prefs_wena3_vibration_step_up">Stap omhoog</string>
+    <string name="prefs_home_icon_right_item">Rechts</string>
+    <string name="prefs_wena3_vibration_basic">Basis</string>
+    <string name="prefs_wena3_title_screen">Scherminstellingen</string>
+    <string name="prefs_wena3_item_large_font">Groter lettertype</string>
+    <string name="prefs_wena3_home_icon_name_energy">Energie</string>
+    <string name="prefs_wena3_notification_vibration_repetition_2">Twee keer</string>
+    <string name="prefs_wena3_auto_power_off_hint">Het apparaat zal automatisch uit- en inschakelen op het aangegeven schema</string>
+    <string name="prefs_wena3_hint_lift_wrist">Schakel het scherm in wanneer u naar uw pols kijkt</string>
+    <string name="prefs_wena3_hint_large_font">Gebruik een groter lettertype in de kalender, meldingen, etc.</string>
+    <string name="prefs_wena3_button_action_item_double">Dubbel indrukken</string>
+    <string name="prefs_wena3_vibration_rapid">Snel</string>
+    <string name="prefs_wena3_button_action_name_qrio_unlock">Qrio deblokkeren</string>
+    <string name="temperature_scale_cf">Temperatuurschaal</string>
+    <string name="prefs_wena3_notification_vibration_repetition_1">Eenmalig</string>
+    <string name="prefs_wena3_receive_calls_item">Ontvang telefoongesprekmeldingen</string>
+    <string name="prefs_wena3_hint_weather_statusbar">Toon het huidige weer in de hoek linksboven op het startscherm</string>
+    <string name="prefs_wena3_home_icon_name_wena_pay">Wena Pay</string>
+    <string name="prefs_wena3_button_action_item">Knopactie</string>
+    <string name="prefs_wena3_auto_power_off_turn_on_time_item">Inschakeltijd</string>
+    <string name="prefs_wena3_notification_default_call_vibration">Trilling binnenkomend telefoongesprek</string>
+    <string name="green">Groen</string>
+    <string name="yellow">Geel</string>
+    <string name="devicetype_sony_wena3">Sony Wena 3</string>
+    <string name="prefs_wena3_notification_default_call_led">LED-kleur binnenkomend telefoongesprek</string>
+    <string name="red">Rood</string>
+    <string name="cyan">Cyaan</string>
+    <string name="prefs_wena3_vibration_siren">Sirene</string>
+    <string name="temperature_scale_cf_summary">Selecteer welke temperatuurschaal het apparaat gebruikt.</string>
+    <string name="prefs_wena3_menu_icon_title">Menu-iconen</string>
+    <string name="prefs_wena3_notification_default_call_vibration_repetition">Herhaling trilling binnenkomend telefoongesprek</string>
+    <string name="prefs_wena3_notification_default_vibration">Trilling meldingen</string>
+    <string name="prefs_wena3_notification_vibration_repetition_0">Oneindig</string>
+    <string name="prefs_wena3_notification_vibration_repetition_4">Vier keer</string>
+    <string name="temperature_scale_fahrenheit">Fahrenheit</string>
+    <string name="prefs_wena3_vibration_strength_item">Trillingssterkte</string>
+    <string name="prefs_wena3_receive_calls_hint">Wanneer de Wena uitgeschakeld is krijgt u geen meldingen van telefoongesprekken</string>
+    <string name="prefs_wena3_button_action_item_long">Lang indrukken</string>
+    <string name="prefs_wena3_menu_icon_name_payment">Betaling</string>
+    <string name="prefs_wena3_status_page_title">Sortering statusscherm</string>
+    <string name="prefs_wena3_title_alarm">Alarminstellingen</string>
+    <string name="prefs_wena3_notification_default_vibration_repetition">Herhaling trilling meldingen</string>
+    <string name="prefs_home_icon_center_item">Midden</string>
+    <string name="prefs_wena3_hint_rich_design">Toon afgeronde rechthoeken rond iconen op het startscherm</string>
+    <string name="prefs_wena3_notification_default_led">LED-kleur meldingen</string>
+    <string name="prefs_wena3_auto_power_off_item">Automatisch uitschakelen</string>
+    <string name="prefs_wena3_home_icon_name_edy">Edy-saldo</string>
+    <string name="prefs_wena3_vibration_warning">Waarschuwing</string>
+    <string name="prefs_wena3_smart_alarm_margin_item">Marge slim alarm</string>
+    <string name="prefs_wena3_item_rich_design">Gebruik luxe ontwerp</string>
+    <string name="prefs_wena3_day_start_hour_item">Dag start om</string>
+    <string name="prefs_wena3_notification_per_app_settings_title">Per-app meldingsinstellingen</string>
+    <string name="prefs_wena3_vibration_none">Geen trilling</string>
+    <string name="purple">Paars</string>
+    <string name="prefs_wena3_vibration_strength_item_strong">Sterk</string>
+    <string name="prefs_wena3_vibration_triple">Driedubbel</string>
+    <string name="blue">Blauw</string>
+    <string name="prefs_wena3_button_action_name_activity_screen">Activiteitenscherm</string>
+    <string name="prefs_wena3_notification_settings_title">Meldingsinstellingen</string>
+    <string name="devicetype_sony_wf_1000xm5">Sony WF-1000XM5</string>
+    <string name="pref_message_privacy_mode_bodyonly">Verberg alleen tekst</string>
+    <string name="latvian">Lets</string>
+    <string name="pref_title_notifications_ignore_work_profile">Negeer meldingen uit het werkprofiel</string>
+    <string name="pref_summary_notifications_ignore_work_profile">Stuur geen meldingen van apps in het werkprofiel naar het apparaat</string>
+    <string name="common_symbols">Algemene symbolen</string>
+    <string name="zepp_os_watchface_pure_white">Puur wit</string>
+    <string name="call_rejection_method_ignore">Negeren (dempen)</string>
+    <string name="avgStrokeRate">Gemiddelde slagsnelheid</string>
+    <string name="zepp_os_watchface_the_ultima">Het uiterste</string>
+    <string name="zepp_os_watchface_city_of_speed">Stad van snelheid</string>
+    <string name="menuitem_headphone">Koptelefoon</string>
+    <string name="femometer_measurement_mode_normal">Normaal (60s-90s)</string>
+    <string name="call_rejection_method_reject">Afwijzen</string>
+    <string name="zepp_os_watchface_free_combination">Vrije combinatie</string>
+    <string name="menuitem_body_composition">Lichaamssamenstelling</string>
+    <string name="menuitem_zepp_pay">Zepp Pay</string>
+    <string name="pref_call_rejection_method_title">Telefoongesprek-afwijsmethode</string>
+    <string name="busy_task_fetch_statistics">Bezig met ophalen statistieken</string>
+    <string name="femometer_measurement_mode_quick">Snel (30s)</string>
+    <string name="zepp_os_watchface_starry_sky">Sterrenhemel</string>
+    <string name="devicetype_amazfit_balance">Amazfit Balance</string>
+    <string name="fossil_hr_nav_app_not_installed_notify_text">Navigatie gestart maar navigationApp is niet op het horloge ge茂nstalleerd. Installeer het via de App-beheerder.</string>
+    <string name="yard">yard</string>
+    <string name="strokes_minute">slagen/min</string>
+    <string name="femometer_measurement_mode_title">Meetmodus</string>
+    <string name="pref_call_rejection_method_summary">Welke actie wordt genomen wanneer een inkomend telefoongesprek wordt geweigerd op het horloge</string>
+    <string name="busy_task_fetch_temperature">Bezig met ophalen temperatuurgegevens</string>
+    <string name="femometer_measurement_mode_precise">Precies (3min)</string>
+    <string name="zepp_os_watchface_vast_sky">Enorme hemel</string>
+    <string name="menuitem_workout_shortcuts">Trainingssnelkoppelingen</string>
+    <string name="maxStrokeRate">Maximale slagsnelheid</string>
+    <string name="zepp_os_watchface_lightning_flash">Bliksemschicht</string>
+    <string name="laneLength">Baanlengte</string>
+    <string name="strokes">Totaal aantal slagen</string>
+    <string name="menuitem_apps_shortcuts">App-snelkoppelingen</string>
+    <string name="zepp_os_watchface_guider">Gids</string>
+    <string name="menuitem_readiness">Gereedheid</string>
+    <string name="menuitem_thermometer">Thermometer</string>
+    <string name="devicetype_femometer_vinca2">Femometer Vinca II</string>
+    <string name="fossil_hr_nav_app_not_installed_notify_title">Navigatie-app niet ge茂nstalleerd op horloge</string>
+    <string name="devicetype_amazfit_active_edge">Amazfit Active Edge</string>
+    <string name="charts_legend_spo2_average">Zuurstofsaturatie</string>
+    <string name="devicetype_amazfit_active">Amazfit Active</string>
+    <string name="pref_title_navigation_apps">Navigatie-apps</string>
+    <string name="pref_title_navigation_prefs">Navigatie-instellingen</string>
+    <string name="pref_navigation_app_gmaps">Google Maps</string>
+    <string name="pref_summary_prefix_notification_with_app">Plaats app-naam vooraan meldingen</string>
+    <string name="pref_summary_osmand_packagename">Selecteer met welke versie van OsmAnd verbonden moet worden</string>
+    <string name="pref_title_prefix_notification_with_app">App-naam in melding</string>
+    <string name="pref_title_osmand_packagename">OsmAnd-pakketnaam</string>
+    <string name="pref_navigation_app_osmand">OsmAnd(+)</string>
+    <string name="pref_title_casio_alert_other">Overige meldingen</string>
+    <string name="pref_title_casio_alert_calendar">Kalendermeldingen</string>
+    <string name="pref_summary_casio_alert_call">Tril/piep voor binnenkomende telefoongesprekken</string>
+    <string name="devicetype_miband8">Xiaomi Smart Band 8</string>
+    <string name="menuitem_running">Hardlopen</string>
+    <string name="pref_sleep_mode_schedule_title">Planning slaapmodus</string>
+    <string name="pref_title_casio_alert_call">Melding voor telefoongesprekken</string>
+    <string name="bedtime">Bedtijd</string>
+    <string name="pref_title_casio_alert_email">Melding voor nieuwe e-mail</string>
+    <string name="pref_summary_casio_alert_email">Tril/piep bij e-mailmeldingen</string>
+    <string name="menuitem_focus">Focus</string>
+    <string name="pref_summary_casio_alert_sms">Tril/piep voor SMS-meldingen</string>
+    <string name="pref_title_preview_message_in_title">Toon een preview van het bericht in de titel</string>
+    <string name="devicetype_redmiwatch3active">Redmi Watch 3 Active</string>
+    <string name="pref_summary_casio_alert_calendar">Tril/piep voor kalendermeldingen</string>
+    <string name="serial_number">Serienummer</string>
+    <string name="danish">Deens</string>
+    <string name="wake_up_time">Wektijd</string>
+    <string name="menuitem_stats">Statistieken</string>
+    <string name="pref_summary_casio_alert_other">Tril/piep voor meldingen in de categorie Overig</string>
+    <string name="devicetype_xiaomi_watch_lite">Xiaomi Watch Lite</string>
+    <string name="pref_summary_preview_message_in_title">Laat een preview van het bericht zien in de titel van een melding voor zover toegestaan door het apparaat</string>
+    <string name="pref_sleep_mode_schedule_summary">Stuur een melding en start slaapmodus op de ingestelde bedtijd. Op de ingestelde wektijd klinkt een alarmsignaal.</string>
+    <string name="pref_title_casio_alert_sms">Melding voor SMS</string>
+    <string name="menuitem_alerts">Meldingen</string>
+    <string name="devicetype_miband7pro">Xiaomi Smart Band 7 Pro</string>
+    <string name="devicetype_xiaomi_watch_s1_active">Xiaomi Watch S1 Active</string>
+    <string name="devicetype_mi_watch_color_sport">Mi Watch Color Sport</string>
+    <string name="devicetype_pixoo">Pixoo</string>
+    <string name="pref_summary_send_app_notifications">Stuur app-meldingen naar het apparaat</string>
+    <string name="pref_title_send_app_notifications">Stuur meldingen</string>
+    <string name="pref_device_action_dnd_on">Niet storen - Aan</string>
+    <string name="prefs_phone_silent_mode">Stiltestand telefoon</string>
+    <string name="prefs_wearmode">Draagwijze</string>
+    <string name="wearmode_pebble">Pebble (schoengesp)</string>
+    <string name="silent_mode_normal_vibrate">Normaal / Trillen</string>
+    <string name="pref_title_goal_secondary">Tweede doel</string>
+    <string name="pref_vitality_score_7_day_summary">Krijg een melding wanneer je vitaliteitsscore 30, 60 of 100 bereikt in de afgelopen 7 dagen</string>
+    <string name="pref_vitality_score_title">Vitaliteitsscore</string>
+    <string name="pref_device_action_dnd_alarms">Niet storen - Alleen alarmen</string>
+    <string name="silent_mode_vibrate_silent">Trillen / Stil</string>
+    <string name="pref_vitality_score_daily_summary">Krijg een melding wanneer je het dagelijks maximum aantal vitaliteitspunten hebt bereikt</string>
+    <string name="pref_device_action_dnd_priority">Niet storen - Alleen belangrijk</string>
+    <string name="not_set">Niet ingesteld</string>
+    <string name="standing_time">Tijd staand</string>
+    <string name="active_time">Tijd actief</string>
+    <string name="wearmode_band">Band (armband)</string>
+    <string name="wearmode_necklace">Ketting (om de nek)</string>
+    <string name="pref_vitality_score_daily_title">Dagelijkse voortgang</string>
+    <string name="pref_vitality_score_7_day_title">7-daagse voortgang</string>
+    <string name="pref_device_action_dnd_off">Niet storen - Uit</string>
+    <string name="silent_mode_normal_silent">Normaal / Stil</string>
+    <string name="activity_type_trail_run">Trail run</string>
+    <string name="widget_layout">Widget-layout</string>
+    <string name="widget_screen_no_free_slots_description">Het apparaat heeft geen vrije plekken voor widget-schermen (totaal aantal plekken: %1$s)</string>
+    <string name="widget_unknown_workout">Onbekende training - %s</string>
+    <string name="widget_move_up">Omhoog</string>
+    <string name="widget_screen_delete_confirm_description">Weet u zeker dat u \'%1$s\' wilt verwijderen?</string>
+    <string name="clap_hands_to_wakeup_device">Klap in handen voor scherm aan</string>"
+    <string name="activity_type_trekking">Wandeltocht</string>
+    <string name="widget_layout_two">2 widgets</string>
+    <string name="widget_screen_delete_confirm_title">Verwijder widget-scherm</string>
+    <string name="clap_hands_to_wakeup_device_summary">Klap nog een keer voor scherm uit</string>"
+    <string name="widget_layout_top_1_bot_2">1 boven, 2 onder</string>
+    <string name="devicetype_colacao23">ColaCao 2023</string>
+    <string name="widget_layout_top_2_bot_2">2 boven, 2 onder</string>
+    <string name="widget_screen_min_screens">Er moeten minimaal %1$s schermen zijn</string>
+    <string name="widget">Widget</string>
+    <string name="devicetype_redmi_smart_band_2">Redmi Smart Band 2</string>
+    <string name="widget_screen">Widget-scherm</string>
+    <string name="widget_move_down">Omlaag</string>
+    <string name="widget_layout_single">1 widget</string>
+    <string name="widget_subtype">Widget-subtype</string>
+    <string name="widget_screen_x">Scherm %s</string>
+    <string name="widget_missing_parts">Selecteer alle widgets</string>
+    <string name="widget_layout_top_2_bot_1">2 boven, 1 onder</string>
+    <string name="devicetype_colacao21">ColaCao 2021</string>
+    <string name="pixoo_power_saving_summary">Het scherm zal uitgaan nadat de microfoon een tijd geen geluid heeft opgevangen</string>
+    <string name="activity_type_wrestling">Worstelen</string>
+    <string name="devicetype_mijia_lywsd03">Mijia thermo- en hygrometer 2</string>
+    <string name="pref_title_fossil_hr_navigation_instructions">Navigatie-instructies</string>
+    <string name="pref_summary_fossil_hr_navigation_instructions">Configureer gedrag van navigatie-app op horloge</string>
+    <string name="pref_summary_fossil_hr_nav_foreground">Mag de navigatie-app automatisch naar de voorgrond komen als er een navigatie-instructie ontvangen wordt?</string>
+    <string name="pref_title_fossil_hr_nav_vibrate">Tril bij nieuwe instructie</string>
+    <string name="pref_title_fossil_hr_nav_foreground">Naar voorgrond</string>
+    <string name="prefs_device_name">Apparaatnaam</string>
+    <string name="pref_summary_fossil_hr_nav_vibrate">Moet het horloge trillen bij elke nieuwe of veranderde navigatie-instructie (alleen als de app op de voorgrond is)?</string>
+    <string name="devicetype_redmi_watch_2_lite">Redmi Watch 2 Lite</string>
+    <string name="notification_channel_connection_status_name">Connectie status</string>
+    <string name="devicetype_redmi_smart_band_pro">Redmi Smart Band Pro</string>
+    <string name="devicetype_nothingear2">Nothing Ear (2)</string>
+    <string name="do_not_disturb_lift_wrist_summary">Alleen wanneer scherm-aan-bij-oppakken is ingeschakeld</string>
+    <string name="pref_do_not_disturb_not_wear">Niet storen wanneer het apparaat niet gedragen wordt</string>
+    <string name="manual">Handmatig</string>
+    <string name="dnd_all_day">Hele dag</string>
+    <string name="devicetype_honor_band3">Honor Band 3</string>
+    <string name="devicetype_honor_band4">Honor Band 4</string>
+    <string name="devicetype_honor_band5">Honor Band 5</string>
+    <string name="devicetype_honor_band6">Honor Band 6</string>
+    <string name="devicetype_honor_band7">Honor Band 7</string>
+    <string name="devicetype_huawei_band_aw70">Huawei Band (AW70)</string>
+    <string name="devicetype_huawei_band6">Huawei Band 6</string>
+    <string name="devicetype_huawei_band7">Huawei Band 7</string>
+    <string name="devicetype_huawei_band8">Huawei Band 8</string>
+    <string name="devicetype_huawei_watch_gt">Huawei Watch GT</string>
+    <string name="devicetype_huawei_band4pro">Huawei Band 4 (Pro)</string>
+    <string name="devicetype_huawei_watchgt2">Huawei Watch GT 2 (Pro)</string>
+    <string name="devicetype_huawei_watchgt2e">Huawei Watch GT 2e</string>
+    <string name="devicetype_huawei_talk_band_b6">Huawei Talk Band B6</string>
+    <string name="devicetype_huawei_watchgt3">Huawei Watch GT 3 (Pro)</string>
+    <string name="prefs_active_noise_cancelling_light">Lichte actieve geluidsonderdrukking</string>
+    <string name="prefs_active_noise_cancelling_transparency">Transparantie</string>
+    <string name="huawei_alarm_smart_description">Verwijder het vinkje niet bij slim wakker worden.</string>
+    <string name="huawei_trusleep_title">HUAWEI TruSleep 鈩�</string>
+    <string name="huawei_trusleep_summary_light">Verbeterde slaapmonitoring</string>
+    <string name="pref_enable_call_accept">Telefoontjes accepteren inschakelen</string>
+    <string name="pref_enable_call_accept_summary">Het op het apparaat accepteren van binnenkomende telefoongespreken inschakelen</string>
+    <string name="pref_enable_call_reject">Telefoontjes weigeren inschakelen</string>
+    <string name="pref_enable_call_reject_summary">Het op het apparaat weigeren van binnenkomende telefoongespreken inschakelen</string>
+    <string name="pref_disable_find_phone_with_dnd">Vind mijn telefoon uitschakelen wanneer Niet Storen actief is</string>
+    <string name="pref_heartrate_automatic_enable">Automatische hartslagmeting inschakelen</string>
+    <string name="pref_spo_automatic_enable">Automatische bloedzuurstofmeting inschakelen</string>
+    <string name="prefs_disconnect_notification_summary">Melding op het apparaat bij verbroken Bluetooth-verbinding.</string>
+    <string name="devicetype_nothingearstick">Nothing Ear (Stick)</string>
+    <string name="prefs_workmode">Werkmodus</string>
+    <string name="huawei_alarm_event_description">Plaats geen vinkje bij slim wakker worden.</string>
+    <string name="huawei_trusleep_summary">Monitor uw slaapkwaliteit en adempatroon in realtime.
+\nAnalyseer uw slaappatronen en diagnosticeer 6 types slaapproblemen.</string>
+    <string name="pref_force_options">Opties forceren</string>
+    <string name="pref_force_options_summary">Sommige apparaten geven onterecht aan sommige opties niet te ondersteunen. Deze instellingen gebruikt worden om ze alsnog in te schakelen.
+\nGEBRUIK OP EIGEN RISICO
+\nLees de documentatie</string>
+    <string name="pref_force_smart_alarm">Forceer slim alarm</string>
+    <string name="pref_force_smart_alarm_summary">Forceer ondersteuning van slimme alarmen
+\nGEBRUIK OP EIGEN RISICO</string>
+    <string name="pref_title_general_reconnectonlytoconnected">Maak alleen opnieuw verbinding met verbonden apparaten</string>
+    <string name="pref_summary_general_reconnectonlytoconnected">Maak alleen opnieuw verbinding met verbonden apparaten, in plaats van met alle apparaten</string>
+    <string name="updatefirmwareoperation_updateproblem_low_battery">Het batterijniveau van het apparaat is te laag</string>
+    <string name="pref_force_wear_location">Forceer draaglocatie</string>
+    <string name="pref_force_dnd_support">Forceer ondersteuning voor Niet Storen</string>
+    <string name="huawei_ignore_wakeup_status_start">Negeer startstatus wakkerworden</string>
+    <string name="huawei_ignore_wakeup_status_start_description">Kan helpen voor correcte slaapdetectie. Direct zichtbaar in het dagelijkse activiteitenoverzicht.</string>
+    <string name="huawei_ignore_wakeup_status_end">Negeer eindstatus wakkerworden</string>
+    <string name="huawei_ignore_wakeup_status_end_description">Kan helpen voor correcte slaapdetectie. Direct zichtbaar in het dagelijkse activiteitenoverzicht.</string>
+    <string name="pref_force_wear_location_summary">Forceer ondersteuning voor draaglocatie
+\nGEBRUIK OP EIGEN RISICO</string>
+    <string name="pref_force_dnd_support_summary">Forceer ondersteuning voor Niet Storen
+\nGEBRUIK OP EIGEN RISICO</string>
+    <string name="huawei_reparse_workout_data">Trainingsgegevens opnieuw analyseren</string>
+    <string name="pref_summary_debug">Stuur een foutopsporingsverzoek naar het Huawei-apparaat</string>
+    <string name="pref_title_debug">Foutopsporingsverzoek</string>
+    <string name="huawei_reparse_workout_data_description">Dit zal alleen iets doen na bepaalde updates</string>
+    <string name="menuitem_buzzer_intensity">Zoemerintensiteit</string>
+    <string name="flatDistance">Afstand zonder hoogteverschil</string>
+    <string name="sony_protocol_v2">Versie 2</string>
+    <string name="sony_protocol_v3">Versie 3</string>
+    <string name="protocol_version">Protocolversie</string>
+    <string name="prefs_password_4_digits_0_to_9_summary">Het wachtwoord moet uit 4 cijfers bestaan</string>
+    <string name="devicetype_miband8pro">Xiaomi Smart Band 8 Pro</string>
+    <string name="devicetype_mijia_mho_c303">Mijia MHO-C303</string>
+    <string name="sony_protocol_v1">Versie 1</string>
+    <string name="devicetype_xiaomi_watch_s1">Xiaomi Watch S1</string>
+    <string name="devicetype_xiaomi_watch_s3">Xiaomi Watch S3</string>
+    <string name="devicetype_xiaomi_watch_s1_pro">Xiaomi Watch S1 Pro</string>
+    <string name="uploading_watchface">Wijzerplaat uploaden鈥�</string>
+    <string name="uploadwatchfaceoperation_in_progress">Wijzerplaat uploaden</string>
+    <string name="uploadwatchfaceoperation_complete">Wijzerplaat-installatie gelukt</string>
+    <string name="uploadwatchfaceoperation_failed">Wijzerplaat-installatie mislukt</string>
+    <string name="pref_force_connection_type_title">Forceer verbindingstype</string>
+    <string name="pref_force_connection_type_description">U kunt proberen het type verbinding te forceren indien het Gadgetbridge niet lukt om verbinding te maken</string>
+    <string name="pref_force_connection_type_auto">Automatisch</string>
+    <string name="pref_force_connection_type_ble">Bluetooth LE</string>
+    <string name="pref_force_connection_type_bt_classic">Bluetooth (klassiek)</string>
+    <string name="activity_info">Activiteitsinformatie</string>
+    <string name="heartrate_bpm_155">155 bpm</string>
+    <string name="heartrate_bpm_165">165 bpm</string>
+    <string name="heartrate_bpm_175">175 bpm</string>
+    <string name="heartrate_bpm_185">185 bpm</string>
+    <string name="heartrate_bpm_195">195 bpm</string>
+    <string name="heartrate_bpm_205">205 bpm</string>
+    <string name="hydration_dnd_summary">Schakel drinkherinneringen tijdelijk uit</string>
+    <string name="activity_type_indoor_running">Binnen hardlopen</string>
+    <string name="activity_type_mountain_hike">Bergwandelen</string>
+    <string name="activity_type_cross_country_running">Veldlopen</string>
+    <string name="activity_type_karate">Karate</string>
+    <string name="activity_type_fitness_exercises">Fitnessoefeningen</string>
+    <string name="activity_type_crossfit">Crossfit</string>
+    <string name="activity_type_jazz_dance">Jazz-dansen</string>
+    <string name="activity_type_latin_dance">Latin-dansen</string>
+    <string name="activity_type_jet_skiing">Jetski</string>
+    <string name="activity_type_skating">Skaten</string>
+    <string name="activity_type_ballet">Ballet</string>
+    <string name="activity_type_other_dance">Overige dansen</string>
+    <string name="activity_type_snow_sports">Sneeuwsporten</string>
+    <string name="devicetype_redmi_watch_2">Redmi Watch 2</string>
+    <string name="devicetype_nothing_cmf_watch_pro">CMF Watch Pro</string>
+    <string name="prefs_heartrate_alert_active_high_threshold">Meldingsdrempel maximale hartslag</string>
+    <string name="activity_type_cross_trainer">Crosstrainer</string>
+    <string name="activity_type_free_training">Vrije training</string>
+    <string name="activity_type_rower">Roeimachine</string>
+    <string name="activity_type_dynamic_cycle">Dynamisch fietsen</string>
+    <string name="activity_type_stair_stepper">Traploop-apparaat</string>
+    <string name="activity_type_functional_training">Functionele training</string>
+    <string name="activity_type_physical_training">Fysieke training</string>
+    <string name="activity_type_taekwondo">Taekwondo</string>
+    <string name="activity_type_fencing">Schermen</string>
+    <string name="activity_type_kendo">Kendo</string>
+    <string name="activity_type_horizontal_bar">Rekstok</string>
+    <string name="activity_type_parallel_bar">Brug met gelijke leggers</string>
+    <string name="activity_type_cooldown">Afkoelen</string>
+    <string name="activity_type_cross_training">Crosstraining</string>
+    <string name="activity_type_sit_ups">Sit-ups</string>
+    <string name="activity_type_fitness_gaming">Fitness-gamen</string>
+    <string name="activity_type_aerobic_exercise">Aerobics</string>
+    <string name="activity_type_rolling">Rollen</string>
+    <string name="activity_type_flexibility">Rekken</string>
+    <string name="activity_type_track_and_field">Atletiek</string>
+    <string name="activity_type_push_ups">Push-ups</string>
+    <string name="activity_type_battle_rope">Battle rope</string>
+    <string name="activity_type_smith_machine">Smith machine</string>
+    <string name="activity_type_pull_ups">Pull-ups</string>
+    <string name="activity_type_plank">Planken</string>
+    <string name="activity_type_javelin">Speerwerpen</string>
+    <string name="activity_type_long_jump">Verspringen</string>
+    <string name="activity_type_high_jump">Hoogspringen</string>
+    <string name="activity_type_trampoline">Trampoline</string>
+    <string name="activity_type_dumbbell">Dumbbell</string>
+    <string name="activity_type_belly_dance">Buikdansen</string>
+    <string name="activity_type_roller_skating">Rolschaatsen</string>
+    <string name="activity_type_martial_arts">Vechtsporten</string>
+    <string name="activity_type_tai_chi">Tai chi</string>
+    <string name="activity_type_hula_hooping">Hoelahoep</string>
+    <string name="activity_type_disc_sports">Discuswerpen</string>
+    <string name="activity_type_darts">Darten</string>
+    <string name="activity_type_archery">Boogschieten</string>
+    <string name="activity_type_horse_riding">Paardrijden</string>
+    <string name="activity_type_kite_flying">Kiten</string>
+    <string name="activity_type_swing">Schommelen</string>
+    <string name="activity_type_stairs">Traplopen</string>
+    <string name="activity_type_fishing">Vissen</string>
+    <string name="activity_type_hand_cycling">Handfietsen</string>
+    <string name="activity_type_mind_and_body">Geest en lichaam</string>
+    <string name="activity_type_kabaddi">Kabaddi</string>
+    <string name="activity_type_karting">Karten</string>
+    <string name="activity_type_billiards">Biljard</string>
+    <string name="activity_type_shuttlecock">Badminton</string>
+    <string name="activity_type_softball">Softbal</string>
+    <string name="activity_type_dodgeball">Trefbal</string>
+    <string name="activity_type_australian_football">Australisch football</string>
+    <string name="activity_type_pickleball">Pickleball</string>
+    <string name="activity_type_lacross">Lacrosse</string>
+    <string name="activity_type_shot">Shot</string>
+    <string name="activity_type_sailing">Zeilen</string>
+    <string name="activity_type_ice_hockey">IJshockey</string>
+    <string name="activity_type_curling">Curling</string>
+    <string name="activity_type_cross_country_skiing">Langlaufen</string>
+    <string name="activity_type_skateboarding">Skateboarden</string>
+    <string name="activity_type_rock_climbing">Rotsklimmen</string>
+    <string name="activity_type_hunting">Jagen</string>
+    <string name="activity_type_outdoor_walking">Buiten wandelen</string>
+    <string name="devicetype_sony_wi_sp600n">Sony WI-SP600N</string>
+    <string name="devicetype_honor_magicwatch2">Honor MagicWatch 2</string>
+    <string name="devicetype_redmiwatch3">Redmi Watch 3</string>
+    <string name="pref_test_features_title">Functies</string>
+    <string name="pref_test_features_summary">Beschikbare functies op dit testapparaat</string>
+    <string name="warning_missing_notification_permission">Kon permanente notificatie niet plaatsen vanwege missende permissie</string>
+    <string name="devicetype_huawei_watchfit">Huawei Watch Fit</string>
+    <string name="pref_wide_area_tap_title">Groot gebied aanraken</string>
+    <string name="weekstepschart_steps_a_week_or_month">Stappen per week/maand</string>
+    <string name="weeksleepchart_sleep_a_week_or_month">Slaap per week/maand</string>
+    <string name="devicetype_amazfit_bip3">Amazfit Bip 3</string>
+    <string name="devicetype_sony_linkbuds">Sony LinkBuds</string>
+    <string name="devicetype_huawei_watchgt4">Huawei Watch GT 4</string>
+    <string name="devicetype_huawei_watchultimate">Huawei Watch Ultimate</string>
+    <string name="auto_reconnect_ble_scan_title">Opnieuw verbinden via BLE-scan</string>
+    <string name="pref_wide_area_tap_summary">Herken aanraken tussen wangen en oren</string>
+    <string name="pref_adaptive_volume_control_title">Automatisch volume</string>
+    <string name="prompt_restart_gadgetbridge">Herstart Gadgetbridge om de functie te activeren.</string>
+    <string name="waiting_for_bluetooth">Wachten op Bluetooth鈥�</string>
+    <string name="auto_reconnect_ble_scan_summary">Wacht op apparaatscan in plaats van actief te verbinden</string>
+    <string name="fw_upgrade_notice_amazfitbip3">U staat op het punt om de firmware %s te installeren op uw Amazfit Bip 3.
+\n
+\nZorg ervoor dat u het .fw-bestand installeert en daarna het .res-bestand. Uw horloge wordt opnieuw gestart na het installeren van het .fw-bestand.
+\n
+\nOpmerking: U hoeft de .res niet te installeren als deze exact dezelfde is als degene die ervoor al was ge茂nstalleerd.
+\n
+\nGA VERDER OP EIGEN RISICO!</string>
+    <string name="busy_task_fetch_sports_details_interrupted">Ophalen van sportgegevens is onderbroken</string>
+    <string name="pref_adaptive_volume_control_summary">Verhoog volume automatisch bij veel omgevingsgeluid</string>
+    <string name="pref_header_sony_sound_control">Geluidsinstellingen</string>
+    <string name="error_scan_failed">Scan mislukt: %d</string>
+    <string name="scan_scanning_all_devices">Scannen naar alle apparaten</string>
+    <string name="scan_not_scanning">Niet aan het scannen</string>
+    <string name="scan_scanning_single_device">Scannen naar 1 apparaat</string>
+    <string name="scan_scanning_multiple_devices">Scannen naar %d apparaten</string>
+    <string name="notification_channel_scan_service_name">Scan-service</string>
+    <string name="device_state_waiting_scan">Wachten op apparaatscan</string>
+    <string name="devicetype_miband8active">Xiaomi Smart Band 8 Active</string>
+    <string name="alarm_smart_wakeup_interval_default">5 minuten</string>
+    <string name="alarm_smart_wakeup_interval">Interval slim wekken:</string>
+    <string name="companion_pairing_request_title">Companion-apparaat</string>
+    <string name="unbind_before_pair_title">Al gekoppeld</string>
+    <string name="unbind_before_pair_message">Dit apparaat is al gekoppeld aan de Android-instellingen, waardoor het koppelen op sommige apparaten kan mislukken.
+\n
+\nAls het toevoegen van het apparaat mislukt, verwijder het dan in de Android-instellingen en probeer het opnieuw.</string>
+    <string name="companion_pairing_request_description">Koppel je dit apparaat als Companion?
+\n
+\nDit wordt aanbevolen voor sommige functies zoals zoeken naar apparaten en biedt een betere verbinding.</string>
+    <string name="devicesetting_scannable_minimum_unseen_summary">Na te zijn gevonden, moet het apparaat deze tijd buiten bereik zijn om opnieuw gevonden te kunnen worden</string>
+    <string name="devicetype_scannable">Scanbaar apparaat</string>
+    <string name="state_scanned">Gescand</string>
+    <string name="devicesetting_scannable_debounce">Seconden zichtbaar na gescand</string>
+    <string name="devicesetting_scannable_minimum_unseen">Seconden afwezig na niet gescand</string>
+    <string name="devicesetting_scannable_rssi">Minimaal RSSI-niveau</string>
+    <string name="devicesetting_scannable_debounce_summary">Na te zijn gevonden, blijft het apparaat deze tijd actief als gescand en wordt verder genegeerd</string>
+    <string name="devicesetting_scannable_rssi_summary">Het minimale RSSI-niveau om gevonden te kunnen worden</string>
+    <string name="pref_force_enable_heartrate_support">Forceer hartslagmeting</string>
+    <string name="pref_force_enable_heartrate_support_summary">Forceer ondersteuning van hartslagmeting
+\nGEBRUIK OP EIGEN RISICO</string>
+    <string name="pref_force_enable_spo2_support">Forceer SpO2-ondersteuning</string>
+    <string name="pref_force_enable_spo2_support_summary">Forceer ondersteuning van bloedzuurstofmetingen
+\nGEBRUIK OP EIGEN RISICO</string>
+    <string name="error_showing_changelog">Fout bij tonen wijzigingenlijst</string>
+    <string name="activity_type_worn">Gedragen</string>
+    <string name="bottom_nav_dashboard">Dashboard</string>
+    <string name="pref_dashboard_first_title">Toon dashboard eerst</string>
+    <string name="pref_dashboard_first_summary">Toon het dashboard wanneer Gadgetbridge opstart, in plaats van het apparatenscherm</string>
+    <string name="pref_dashboard_cards_title">Widgets op kaarten</string>
+    <string name="pref_dashboard_widget_settings">Widget-instellingen</string>
+    <string name="pref_dashboard_widget_double_size_title">Dubbele grootte</string>
+    <string name="pref_dashboard_widget_show_legend_title">Toon legenda</string>
+    <string name="pref_dashboard_widget_goals_chart_title">Doelen-diagram</string>
+    <string name="pref_dashboard_devices_to_include">Apparaten</string>
+    <string name="pref_dashboard_all_devices_title">Alle apparaten</string>
+    <string name="pref_dashboard_select_devices_title">Selecteer apparaten...</string>
+    <string name="pref_dashboard_widgets_order_summary">Selecteer welke widgets op het dashboard getoond worden en in welke volgorde</string>
+    <string name="pref_dashboard_all_devices_summary">Combineer activiteitsgegevens van alle toegevoegde apparaten voor het dashboard</string>
+    <string name="pref_dashboard_widget_today_24h_summary">Toon de activiteit in een enkele 24-uurscirkel in plaats van een dubbele 12-uurscirkel</string>
+    <string name="pref_dashboard_widget_show_legend_summary">Toon een legenda onder de widget om de kleuren uit te leggen</string>
+    <string name="pref_dashboard_select_devices_summary">Combineer activiteitsgegevens van specifieke apparaten voor het dashboard</string>
+    <string name="pref_dashboard_widget_today_hr_interval_title">Interval hartslagmeting</string>
+    <string name="pref_dashboard_widget_today_hr_interval_summary">Het aantal minuten dat het diagram \'gedragen\' toont na elke succesvolle hartslagmeting</string>
+    <string name="pref_auto_reply_calls_summary">De telefoon neemt binnenkomende telefoongesprekken automatisch op</string>
+    <string name="pref_auto_reply_calls_title">Binnenkomende gesprekken automatisch opnemen</string>
+    <string name="dashboard_settings">Dashboard-instellingen</string>
+    <string name="bottom_nav_devices">Apparaten</string>
+    <string name="pref_dashboard_cards_summary">Toon de widgets op kaarten op het dashboard</string>
+    <string name="pref_dashboard_widget_today_title">Activiteit-diagram</string>
+    <string name="pref_dashboard_widget_today_24h_title">24-uursmodus</string>
+    <string name="pref_dashboard_widget_double_size_summary">Sta de widget toe om twee kolommen van het dashboard te gebruiken</string>
+    <string name="pref_auto_reply_calls_delay_summary">Aantal seconden waarna een binnenkomend telefoongesprek automatisch opgenomen wordt</string>
+    <string name="pref_auto_reply_calls_delay_title">Vertraging automatisch opnemen</string>
+    <string name="swolfAvg">Gemiddelde SWOLF</string>
+    <string name="swolfMax">Maximale SWOLF</string>
+    <string name="swolfMin">Minimale SWOLF</string>
+    <string name="stepRateSum">Opgetelde stapsnelheid</string>
+    <string name="stepRateAvg">Gemiddelde stapsnelheid</string>
+    <string name="stepLengthAvg">Gemiddelde staplengte</string>
+    <string name="groundContactTimeAvg">Gemiddelde contacttijd met grond</string>
+    <string name="impactAvg">Gemiddelde impact</string>
+    <string name="impactMax">Maximale impact</string>
+    <string name="swingAngleAvg">Gemiddelde zwaaihoek</string>
+    <string name="foreFootLandings">Voorvoetlandingen</string>
+    <string name="midFootLandings">Midvoetlandingen</string>
+    <string name="backFootLandings">Hiellandingen</string>
+    <string name="eversionAngleAvg">Gemiddelde eversiehoek</string>
+    <string name="eversionAngleMax">Maximale eversiehoek</string>
+    <string name="fmtPaceDistance">Tempo %d afstand</string>
+    <string name="fmtPaceType">Tempo %d type</string>
+    <string name="fmtPacePace">Tempo %d tempo</string>
+    <string name="fmtPaceCorrection">Tempo %d correctie</string>
+    <string name="fmtPaceTypeAverage">Tempotype %d gemiddelde</string>
+    <string name="unknownDataEncountered">Niet-herkende gegevens aangetroffen</string>
+    <string name="milliseconds">milliseconden</string>
+    <string name="degrees">graden</string>
+    <string name="Pace">Tempo</string>
+    <string name="RunningForm">Hardloopkwaliteit</string>
+    <string name="pref_header_audio">Audio</string>
+    <string name="pref_header_generic">Algemeen</string>
+    <string name="pref_title_notification_wake_on_open">Automatisch ontgrendelen</string>
+    <string name="pref_summary_notification_wake_on_open">Ontgrendel het scherm van het Android-apparaat wanneer de gadget een OPEN-commando stuurt. Werkt alleen wanneer vertrouwde ontgrendeling actief is.</string>
+    <string name="pref_speak_notifications_aloud_summary">Meldingen zullen opgelezen worden via de koptelefoon</string>
+    <string name="pref_speak_notifications_aloud_title">Meldingen oplezen</string>
+    <string name="devicetype_redmi_watch_4">Redmi Watch 4</string>
+    <string name="widget_name_untitled">Widget zonder titel (%1$s)</string>
+    <string name="pref_header_calls_and_notifications">Gesprekken en meldingen</string>
+    <string name="widget_layout_top_wide_bot_large">Breed boven, groot onder</string>
+    <string name="widget_layout_top_large_bot_wide">Groot boven, breed onder</string>
+    <string name="widget_name_colored_tile">%1$s (gekleurde titel)</string>
+    <string name="pref_dashboard_widget_today_upside_down_summary">Toon in 24-uursmodus middernacht onderaan en 12 uur bovenaan de cirkel</string>
+    <string name="sleepasandroid_settings">Sleep As Android</string>
+    <string name="pref_summary_bottom_navigation_bar_off">Wissel tussen de hoofdschermen alleen met horizontaal vegen</string>
+    <string name="pref_dashboard_widget_today_upside_down_title">Middernacht onderaan</string>
+    <string name="pref_sleepasandroid_device_title">Gekoppeld apparaat</string>
+    <string name="pref_sleepasandroid_device_summary">Selecteer apparaat als databron voor Sleep As Android</string>
+    <string name="pref_sleepasandroid_features_title">Functies</string>
+    <string name="pref_sleepasandroid_features_summary">Ondersteuning verschilt per apparaat</string>
+    <string name="pref_sleepasandroid_feat_alarms">Wekkers</string>
+    <string name="pref_sleepasandroid_slot_summary">Welk wekkerslot wordt gebruikt voor het instellen van wekkers</string>
+    <string name="pref_sleepasandroid_slot_title">Wekkerslot</string>
+    <string name="pref_sleepasandroid_feat_movement">Bewegingssensor</string>
+    <string name="pref_sleepasandroid_feat_heartrate">Hartslag</string>
+    <string name="pref_sleepasandroid_feat_spo2">SpO2</string>
+    <string name="pref_sleepasandroid_enable_summary">Integratie met Sleep As Android inschakelen</string>
+    <string name="alarm_slot_reset">Wekkerslot is teruggezet naar standaard</string>
+    <string name="pref_sleepasandroid_feat_notifications">Meldingen</string>
+    <string name="pref_sleepasandroid_feat_oximetry">Bloedzuurstof</string>
+    <string name="armenian">Armeens</string>
+    <string name="pref_title_bottom_navigation_bar">Navigatiebalk onderaan</string>
+    <string name="pref_summary_bottom_navigation_bar_on">Wissel tussen de hoofdschermen met de navigatiebalk of horizontaal vegen</string>
+    <string name="pref_title_huawei_account">Huawei-account</string>
+    <string name="serbian">Servisch</string>
+    <string name="pref_summary_huawei_account">Huawei-account dat gebruikt is tijdens het bindingsproces. Het hier instellen staat toe dat het apparaat gebonden kan worden zonder reset naar fabrieksinstellingen.</string>
+    <string name="watchface_resolution_doesnt_match">Resolutie van wijzerplaat komt niet overeen met het scherm van het apparaat. Wijzerplaat is %1$s en scherm is %2$s</string>
+    <string name="devicetype_garmin_vivosmart_5">Garmin V铆vosmart 5</string>
+    <string name="devicetype_huawei_watch4pro">Huawei Watch 4 (Pro)</string>
+    <string name="huawei_trusleep_warning">Waarschuwing: wanneer u dit inschakelt zal alle slaap getoond worden als lichte slaap in Gadgetbridge. Klik hier om dit te accepteren.</string>
+    <string name="pref_summary_garmin_default_reply_suffix">Wordt toegevoegd aanvullend aan de bestaande toevoegingen in Gadgetbridge</string>
+    <string name="pref_title_garmin_default_reply_suffix">Vaste toevoeging aan antwoord</string>
+    <string name="device_name_cycling_sensor">Fietssensor</string>
+    <string name="devicetype_cycling_sensor">Fietssnelheidsensor</string>
+    <string name="title_cycling">Fietsen</string>
+    <string name="pref_summary_wheel_diameter">Wieldiameter in inches. Meestal 29, 27,5 of 26.</string>
+    <string name="pref_title_wheel_diameter">Wieldiameter</string>
+    <string name="pref_summary_cycling_persistence_interval">Interval in seconden waarin de fietsgegevens naar de database geschreven moeten worden</string>
+    <string name="pref_title_cycling_persistence_interval">Volhoudings-interval</string>
+    <string name="chart_cycling_point_label_distance">Vandaag: %.1f km
+\nTotaal: %.1f km</string>
+    <string name="chart_cycling_point_label_speed">%.1f km/u</string>
+    <string name="toast_camera_photo_taken">Foto is genomen en opgeslagen in: %s</string>
+    <string name="folder_is_empty">Directory is leeg</string>
+    <string name="folder">Directory</string>
+    <string name="url">URL</string>
+    <string name="garmin_agps_local_file">Lokaal bestand</string>
+    <string name="preferences_qhybrid_settings_summary">Verouderde instellingen voor Fossil Q Hybrid horloges</string>
+    <string name="device_name_bicycle_sensor">Fietssensor</string>
+    <string name="pref_adaptive_noise_cancelling_title">Automatische ruisonderdrukking</string>
+    <string name="pref_speak_notifications_focus_exclusive_summary_off">Audio van andere apps zal op een lager volume afgespeeld worden tijdens het oplezen van meldingen</string>
+    <string name="pref_speak_notifications_focus_exclusive_title">Pauzeer audio van andere apps</string>
+    <string name="pref_speak_notifications_focus_exclusive_summary_on">Het afspelen van audio van andere apps zal gepauzeerd worden tijdens het oplezen van meldingen</string>
+    <string name="toast_setting_requires_reconnect">Deze instelling wordt actief na opnieuw verbinden</string>
+    <string name="pref_touch_tone_summary">Speelt een geluid wanneer het oordopje aangeraakt wordt</string>
+    <string name="pref_wearing_tone">Geluid bij indoen</string>
+    <string name="pref_wearing_tone_summary">Speelt een geluid wanneer het oordopje in het oor geplaatst wordt</string>
+    <string name="pref_touch_tone">Geluid bij aanraken</string>
+    <string name="devicetype_soundcore_liberty3_pro">Soundcore Liberty 3 Pro</string>
+    <string name="devicetype_huawei_watchfit2">Huawei Watch Fit 2</string>
+    <string name="devicetype_huawei_band9">Huawei Band 9</string>
+    <string name="devicetype_huawei_watchfit3">Huawei Watch Fit 3</string>
+    <string name="cyclingPowerMin">Minimale kracht bij fietsen</string>
+    <string name="cyclingPowerMax">Maximale kracht bij fietsen</string>
+    <string name="pref_adaptive_noise_cancelling_summary">Stel de sterkte van de ruisonderdrukking automatisch in afhankelijk van het omgevingsgeluid</string>
+    <string name="pref_agps_status">AGPS-status</string>
+    <string name="agps_status_missing">Afwezig</string>
+    <string name="agps_status_pending">Bezig</string>
+    <string name="agps_status_current">Actueel</string>
+    <string name="agps_status_error">Fout</string>
+    <string name="devicetype_amazfit_bip5_unity">Amazfit Bip 5 Unity</string>
+    <string name="devicetype_garmin_vivomove_hr">Garmin V铆vomove HR</string>
+    <string name="devicetype_garmin_vivomove_style">Garmin V铆vomove Style</string>
+    <string name="devicetype_garmin_vivoactive_4">Garmin V铆voactive 4</string>
+    <string name="devicetype_garmin_vivoactive_4s">Garmin V铆voactive 4S</string>
+    <string name="devicetype_garmin_vivoactive_5">Garmin V铆voactive 5</string>
+    <string name="cyclingPowerAverage">Gemiddelde kracht bij fietsen</string>
+    <string name="copied_to_clipboard">Gekopieerd naar het klembord</string>
+    <string name="open_camera">Open camera</string>
+    <string name="toast_camera_permission_required">Camera-permissie is vereist voor deze functie.</string>
+    <string name="toast_camera_support_required">Camera-ondersteuning is vereist voor deze functie.</string>
+    <string name="no_folder_selected">Geen directory geselecteerd</string>
+    <string name="garmin_agps_url_i">AGPS %1$d URL</string>
+    <string name="pref_garmin_agps_help">De onderstaande lijst bevat alle URL\'s die door het horloge opgevraagd worden voor AGPS-updates. U kunt een bestand van de telefoon selecteren dat naar het horloge wordt gestuurd wanneer het een update opvraagt.</string>
+    <string name="devicetype_garmin_venu_2_plus">Garmin Venu 2 Plus</string>
+    <string name="devicetype_garmin_venu_3">Garmin Venu 3</string>
+    <string name="devicetype_garmin_instinct_solar">Garmin Instinct Solar</string>
+    <string name="devicetype_garmin_instinct_2s">Garmin Instinct 2S</string>
+    <string name="devicetype_garmin_instinct_2_solar">Garmin Instinct 2 Solar</string>
+    <string name="devicetype_garmin_instinct_2_soltac">Garmin Instinct 2 SolTac</string>
+    <string name="devicetype_garmin_instinct_crossover">Garmin Instinct Crossover</string>
+    <string name="devicetype_garmin_forerunner_245">Garmin Forerunner 245</string>
+    <string name="devicetype_garmin_swim_2">Garmin Swim 2</string>
+    <string name="none">Geen</string>
+    <string name="devicetype_garmin_epix_pro">Garmin Epix Pro</string>
+    <string name="devicetype_garmin_instinct_2s_solar">Garmin Instinct 2S Solar</string>
+    <string name="devicetype_garmin_instinct_2x_solar">Garmin Instinct 2X Solar</string>
+    <string name="intent_api_broadcast_activity_sync_title">Stuur intent na synchronisatie</string>
+    <string name="intent_api_broadcast_activity_sync_summary">Stuur een broadcast intent zodra de activiteitssynchronisatie van een apparaat is voltooid</string>
+    <string name="devicetype_garmin_fenix_6_sapphire">Garmin Fenix 6 Sapphire</string>
+    <string name="loading">Laden鈥�</string>
+    <string name="activity_prefs_height_inches">Lengte in inches</string>
+    <string name="toggle_debug_mode">Schakel foutopsporingsmodus aan/uit</string>
+    <string name="share_debug_info">Deel foutopsporingsinformatie</string>
+    <string name="realtime_settings">Realtime-instellingen</string>
+    <string name="unsupported">Niet ondersteund</string>
+    <string name="min_val">Minimum: %d</string>
+    <string name="max_val">Maximum: %d</string>
+    <string name="devicetype_garmin_venu_3s">Garmin Venu 3S</string>
+    <string name="devicetype_garmin_fenix_7_pro">Garmin Fenix 7 Pro</string>
+    <string name="notif_battery_full_title">Gadget-batterij vol!</string>
+    <string name="notification_channel_full_battery_name">Volle batterij</string>
+    <string name="battery_low_notify_enabled">Melding bij lage batterijlading</string>
+    <string name="battery_percentage_str">%1$s%%</string>
+    <string name="battery_low_threshold">Niveau lage batterijlading</string>
+    <string name="battery_full_notify_enabled">Melding bij volle batterij</string>
+    <string name="default_percentage">Standaard (%1$d%%)</string>
+    <string name="notif_battery_full">%1$s batterij vol</string>
+    <string name="battery_i">Batterij %d</string>
+    <string name="show_in_notification">Toon in melding</string>
+    <string name="battery_full_threshold">Niveau volle batterij</string>
+    <string name="authentication_failed_check_key">Authenticatie mislukt, controleer authenticatiesleutel</string>
+    <string name="devicetype_garmin_fenix_7s">Garmin Fenix 7S</string>
+    <string name="devicetype_garmin_forerunner_255">Garmin Forerunner 255</string>
+    <string name="devicetype_garmin_forerunner_265">Garmin Forerunner 265</string>
+    <string name="devicetype_hama_fit6900">Hama Fit6900</string>
+    <string name="devicetype_garmin_venu_2">Garmin Venu 2</string>
+    <plurals name="amount_of_days">
+        <item quantity="one">%d dag</item>
+        <item quantity="other">%d dagen</item>
+    </plurals>
+    <string name="dashboard_calendar_month_goals_reached_title">% van stappendoel bereikt</string>
+    <string name="pref_fetch_unknown_files_title">Onbekende bestanden ophalen</string>
+    <string name="pref_fetch_unknown_files_summary">Haal ook onbekende activiteitsbestanden op van het apparaat. Deze zullen niet verwerkt worden, alleen opgeslagen op de telefoon.</string>
+    <string name="devicetype_moondrop_space_travel">Moondrop Space Travel</string>
+    <string name="moondrop_equalizer_preset_reference">Referentie</string>
+    <string name="moondrop_touch_action_anc_mode">Wissel actieve-ruisonderdrukking</string>
+    <string name="devicetype_garmin_fenix_5">Garmin Fenix 5</string>
+    <string name="devicetype_garmin_fenix_6">Garmin Fenix 6</string>
+    <string name="moondrop_touch_earbud_both">Allebei</string>
+    <string name="moondrop_touch_trigger">Activatie</string>
+    <string name="moondrop_touch_action_play_pause">Afspelen/Pauze</string>
+    <string name="moondrop_touch_action_call_pick_hang">Opnemen/Ophangen</string>
+    <string name="moondrop_touch_action_call_start">Start gesprek</string>
+    <string name="moondrop_touch_action_assistant">Activeer stemassistent</string>
+    <string name="devicetype_garmin_vivoactive_3">Garmin V铆voactive 3</string>
+    <string name="moondrop_equalizer_preset_basshead">Basliefhebber</string>
+    <string name="moondrop_equalizer_preset_monitor">Monitor</string>
+    <string name="moondrop_touch_earbud">Oordopje</string>
+    <string name="moondrop_touch_trigger_long_press_1s">Lang drukken (1s)</string>
+    <string name="moondrop_touch_trigger_long_press_3s">Lang drukken (3s)</string>
+    <string name="devicetype_garmin_forerunner_255s">Garmin Forerunner 255S</string>
+    <string name="devicetype_garmin_instinct">Garmin Instinct</string>
+    <string name="activity_type_beach_volleyball">Strandvolleybal</string>
+    <string name="activity_type_sepak_takraw">Sepak Takraw</string>
+    <string name="activity_type_luge">Rodelen</string>
+    <string name="activity_type_parachuting">Parachutespringen</string>
+    <string name="activity_type_gateball">Gateball</string>
+    <string name="activity_type_auto_racing">Autoracen</string>
+    <string name="activity_type_parkour">Parkour</string>
+    <string name="pref_title_notification_times_enabled">Meldingtijden</string>
+    <string name="pref_summary_notification_times_enabled">Stuur alleen meldingen tussen bepaalde tijden</string>
+    <string name="activity_type_tae_bo">Tae Bo</string>
+    <string name="activity_type_beach_soccer">Strandvoetbal</string>
+    <string name="devicetype_nothing_cmf_watch_pro_2">CMF Watch Pro 2</string>
+    <string name="proprietary_app_warning">Deze optie vereist de installatie van een niet-vrije app</string>
+    <string name="devicetype_huawei_watch3">Huawei Watch 3 (Pro)</string>
+    <string name="devicetype_garmin_vivomove_trend">Garmin V铆vomove Trend</string>
+    <string name="pref_battery_polling_enable">Batterijstatus opvragen inschakelen</string>
+    <string name="pref_battery_polling_interval">Interval batterijstatus opvragen</string>
+    <string name="pref_battery_polling_interval_format">elke %1$s minuten</string>
+    <string name="battery_polling_failed_start">Batterijstatus opvragen starten mislukt</string>
+    <string name="pref_battery_polling_configuration">Batterijstatus opvragen</string>
+    <string name="pref_battery_polling_summary">Dit gebeurt op basis van best-effort en kan om verschillende redenen uitgesteld worden</string>
+    <string name="devicetype_vivitar_hr_bp_monitor_activity_tracker">Vivitar HR &amp; BP Monitor Activity Tracker</string>
+    <string name="devicetype_garmin_forerunner_255_music">Garmin Forerunner 255 Music</string>
+    <string name="devicetype_nothing_cmf_buds_pro_2">CMF Buds Pro 2</string>
+    <string name="devicetype_garmin_forerunner_255s_music">Garmin Forerunner 255S Music</string>
+    <string name="devicetype_garmin_vivosport">Garmin V铆vosport</string>
+    <string name="minutes_60">60 minuten</string>
+    <string name="sleep_colored_stats_rem">REM</string>
+    <string name="sleep_colored_stats_deep_avg">Diep gemiddeld</string>
+    <string name="stats_empty_value">-</string>
+    <string name="stats_lowest_hr">Laagste HR</string>
+    <string name="hrv_status_day_avg">Daggemiddelde</string>
+    <string name="hrv_status_day_avg_legend">Daggemiddelde (ms)</string>
+    <string name="hrv_status_balanced">Gebalanceerd</string>
+    <string name="hrv_status_unbalanced">Uit balans</string>
+    <string name="minutes_20">20 minuten</string>
+    <string name="hrv_status_seven_days_avg">7-dagengemiddelde</string>
+    <string name="hrv_status_seven_days_avg_status">Status</string>
+    <string name="sleep_colored_stats_deep">Diep</string>
+    <string name="sleep_colored_stats_light">Licht</string>
+    <string name="sleep_colored_stats_light_avg">Licht gemiddeld</string>
+    <string name="sleep_colored_stats_rem_avg">REM gemiddeld</string>
+    <string name="find_my_phone_companion_warning">Companion-koppeling is vereist voor de telefoon-vinden-functionaliteit. Klik hier voor meer informatie.</string>
+    <string name="stats_highest_hr">Hoogste HR</string>
+    <string name="hrv_status">HRV-status</string>
+    <string name="hrv_status_low">Laag</string>
+    <string name="hrv_status_poor">Slecht</string>
+    <string name="hrv_status_last_night">Afgelopen nacht</string>
+    <string name="gps_glonass">GPS + GLONASS</string>
+    <string name="pref_header_hrv_status">HRV-status</string>
+    <string name="soundcore_equalizer_preset_voice">Stemgeluid</string>
+    <string name="soundcore_equalizer_preset_balanced">Gebalanceerd</string>
+    <string name="soundcore_equalizer_band5">Band 5</string>
+    <string name="soundcore_equalizer_band6">Band 6</string>
+    <string name="soundcore_equalizer_band7">Band 7</string>
+    <string name="transition">Overgang</string>
+    <string name="soundcore_voice_prompts">Meldingen met stem</string>
+    <string name="soundcore_button_brightness_low">Laag</string>
+    <string name="soundcore_equalizer_custom_summary">Configureer de parameters van de equalizer</string>
+    <string name="soundcore_equalizer_direction">Apparaatrichting</string>
+    <string name="soundcore_equalizer_reset_title">Reset naar standaard</string>
+    <string name="soundcore_equalizer_reset_summary">Herstel de standaardinstellingen van de equalizer</string>
+    <string name="soundcore_equalizer_frequency">Frequentie</string>
+    <string name="body_energy_legend_level">Energieniveau</string>
+    <string name="devicetype_garmin_fenix_5_plus">Garmin Fenix 5 Plus</string>
+    <string name="soundcore_button_brightness">Knoplichtsterkte</string>
+    <string name="soundcore_ldac_mode_title">LDAC-modus</string>
+    <string name="soundcore_ldac_mode_summary">Het inschakelen van LDAC zal de batterijduur verkorten en kan leiden tot instabiele verbinding</string>
+    <string name="soundcore_equalizer_preset_signature">soundcore Signature</string>
+    <string name="soundcore_equalizer_custom_title">Aanpassen鈥�</string>
+    <string name="soundcore_button_brightness_medium">Midden</string>
+    <string name="soundcore_adaptive_direction_title">Aanpassen aan richting</string>
+    <string name="soundcore_equalizer_preset_xtra_bass">Xtra Bass</string>
+    <string name="soundcore_equalizer_band8">Band 8</string>
+    <string name="body_energy_gained">Toegenomen</string>
+    <string name="body_energy_lost">Afgenomen</string>
+    <string name="devicetype_soundcore_motion300">Soundcore Motion 300</string>
+    <string name="body_energy">Lichaam-energie</string>
+    <string name="soundcore_button_brightness_high">Hoog</string>
+    <string name="soundcore_adaptive_direction_summary">Pas de equalizer voorinstelling automatisch aan gebaseerd op de richting van het apparaat</string>
+    <string name="soundcore_equalizer_preset">Voorinstelling</string>
+    <string name="soundcore_equalizer_direction_hanging">Hangend</string>
+    <string name="soundcore_equalizer_direction_standing">Staand</string>
+    <string name="soundcore_equalizer_direction_lying">Liggend</string>
+    <string name="soundcore_equalizer_band3">Band 3</string>
+    <string name="soundcore_equalizer_band4">Band 4</string>
+    <string name="soundcore_equalizer_band9">Band 9</string>
+    <string name="soundcore_equalizer_band1">Band 1</string>
+    <string name="soundcore_equalizer_band2">Band 2</string>
+    <string name="soundcore_equalizer_value">Waarde</string>
+    <string name="abstract_chart_fragment_kind_awake_sleep">Wakker</string>
+    <string name="hrv_status_seven_days_avg_long">7-dagengemiddelde</string>
+    <string name="hrv_status_unit">%1$d ms</string>
+    <string name="hrv_status_baseline">%1$d-%2$d ms</string>
+    <string name="hrv_status_baseline_label">Basiswaarde</string>
+    <string name="hrv_status_last_night_highest_5">Afgelopen nacht 5-minutengemiddelde</string>
+    <string name="devicetype_garmin_forerunner_965">Garmin Forerunner 965</string>
+    <string name="devicetype_huawei_watchgtrunner">Huawei Watch GT Runner</string>
+    <string name="activity_type_indoor_track">Binnenbaan</string>
+    <string name="activity_type_handcycling">Handfietsen</string>
+    <string name="activity_type_e_bike">E-Bike</string>
+    <string name="activity_type_bike_commute">Woon-werk-fietsen</string>
+    <string name="activity_type_american_football">American Football</string>
+    <string name="activity_type_training">Training</string>
+    <string name="activity_type_navigate">Navigeren</string>
+    <string name="activity_type_handcycling_indoor">Binnen handfietsen</string>
+    <string name="activity_type_transition">Overgang</string>
+    <string name="activity_type_fitness_equipment">Fitness-apparaten</string>
+    <string name="activity_type_platform_tennis">Platformtennis</string>
+    <string name="activity_type_cardio">Cardio</string>
+    <string name="activity_type_breathwork">Ademhalingsoefeningen</string>
+    <string name="activity_type_flying">Vliegen</string>
+    <string name="activity_type_paddling">Peddelen</string>
+    <string name="activity_type_hang_gliding">Deltavliegen</string>
+    <string name="activity_type_bouldering">Boulderen</string>
+    <string name="activity_type_sky_diving">Parachutespringen</string>
+    <string name="activity_type_stand_up_paddleboarding">Suppen</string>
+    <string name="activity_type_wakeboarding">Wakeboarden</string>
+    <string name="activity_type_floor_climbing">Vloerklimmen</string>
+    <string name="activity_type_tactical">Tactische sport</string>
+    <string name="activity_type_jumpmaster">Jumpmaster</string>
+    <string name="activity_type_marine">Watersport</string>
+    <string name="activity_type_racket">Racket</string>
+    <string name="activity_type_padel">Padel</string>
+    <string name="activity_type_health_snapshot">Momentopname gezondheid</string>
+    <string name="activity_type_video_gaming">Gamen</string>
+    <string name="activity_type_indoor_push_walk_speed">Binnen push-up - wandelen</string>
+    <string name="activity_type_push_walk_speed">Push-up - Wandelen</string>
+    <string name="activity_type_para_sport">Gehandicaptensport</string>
+    <string name="activity_type_ballroom_dance">Ballroomdans</string>
+    <string name="activity_type_artistic_swimming">Synchroonzwemmen</string>
+    <string name="activity_type_board_game">Bordspel</string>
+    <string name="activity_type_esports">E-sport</string>
+    <string name="activity_type_dragon_boat">Drakenboot</string>
+    <string name="activity_type_frisbee">Frisbee毛n</string>
+    <string name="activity_type_folk_dance">Volksdansen</string>
+    <string name="activity_type_hula_hoop">Hoelahoep</string>
+    <string name="activity_type_jai_alai">Pelota vasca</string>
+    <string name="activity_type_jujitsu">Jujitsu</string>
+    <string name="activity_type_hip_hop">Hiphop</string>
+    <string name="activity_type_judo">Judo</string>
+    <string name="activity_type_mass_gymnastics">Massagymnastiek</string>
+    <string name="activity_type_race_walking">Snelwandelen</string>
+    <string name="activity_type_pole_dance">Paaldansen</string>
+    <string name="activity_type_somatosensory_game">Somatosensorisch spel</string>
+    <string name="activity_type_spinning">Spinnen</string>
+    <string name="activity_type_water_polo">Waterpolo</string>
+    <string name="activity_type_tug_of_war">Touwtrekken</string>
+    <string name="activity_type_wall_ball">Amerikaans handbal</string>
+    <string name="activity_type_weiqi">Go</string>
+    <string name="activity_type_body_combat">Bodycombat</string>
+    <string name="cannot_upload_watchface_too_many_watchfaces_installed">Kan wijzerplaat niet uploaden, er zijn teveel wijzerplaten ge茂nstalleerd</string>
+    <string name="insufficient_space_for_upload">Te weinig ruimte voor upload</string>
+    <string name="activity_type_snowmobiling">Sneeuwscooter</string>
+    <string name="authentication_failed_negotiation">Authenticatiesleutel afspreken mislukt</string>
+    <string name="activity_type_xc_classic_ski">XC Classic Ski</string>
+    <string name="activity_type_multisport">Meerdere sporten</string>
+    <string name="activity_type_ice_skating">Schaatsen</string>
+    <string name="activity_type_water_skiing">Waterski毛n</string>
+    <string name="activity_type_kayaking">Kajakken</string>
+    <string name="activity_type_rafting">Raften</string>
+    <string name="activity_type_snowshoe">Sneeuwschoen</string>
+    <string name="activity_type_mountaineering">Alpinisme</string>
+    <string name="activity_type_table_football">Tafelvoetbal</string>
+    <string name="activity_type_canoeing">Kano毛n</string>
+    <string name="activity_type_bungee_jumping">Bungeejumpen</string>
+    <string name="activity_type_biathlon">Biathlon</string>
+    <string name="devicetype_garmin_venu">Garmin Venu</string>
+    <string name="activity_type_bocce">Jeu de boules</string>
+    <string name="activity_type_bridge">Bridgen</string>
+    <string name="activity_type_checkers">Dammen</string>
+    <string name="activity_type_motorcycling">Motorrijden</string>
+    <string name="activity_type_boating">Varen</string>
+    <string name="activity_type_driving">Rijden</string>
+    <string name="activity_type_inline_skating">Inlineskaten</string>
+    <string name="activity_type_climb_indoor">Binnen klimmen</string>
+    <string name="activity_type_sail_race">Zeilrace</string>
+    <string name="activity_type_sail_expedition">Zeilexpeditie</string>
+    <string name="activity_type_softball_slow_pitch">Recreatieve softball</string>
+    <string name="activity_type_shooting">Schieten</string>
+    <string name="activity_type_winter_sport">Wintersport</string>
+    <string name="activity_type_grinding">Grinden</string>
+    <string name="activity_type_racquetball">Racquetball</string>
+    <string name="activity_type_push_run_speed">Push-up - Rennen</string>
+    <string name="activity_type_indoor_push_run_speed">Binnen push-up - rennen</string>
+    <string name="activity_type_meditation">Mediteren</string>
+    <string name="activity_type_disc_golf">Diskgolf</string>
+    <string name="activity_type_ultimate_disc">Ultimate frisbee</string>
+    <string name="activity_type_team_sport">Teamsport</string>
+    <string name="activity_type_lacrosse">Lacrosse</string>
+    <string name="activity_type_water_tubing">Tubing</string>
+    <string name="activity_type_wakesurfing">Wakesurfen</string>
+    <string name="activity_type_mixed_martial_arts">Gemengde gevechtsporten</string>
+    <string name="activity_type_aerobic_combo">Aerobic combo</string>
+    <string name="activity_type_aerobics">Aerobics</string>
+    <string name="activity_type_air_walker">Crosstrainer</string>
+    <string name="activity_type_bmx">BMX</string>
+    <string name="activity_type_breaking">Breakdance</string>
+    <string name="activity_type_cardio_combat">Vechtcardio</string>
+    <string name="activity_type_chess">Schaken</string>
+    <string name="activity_type_finswimming">Vinzwemmen</string>
+    <string name="activity_type_flowriding">Flowboarden</string>
+    <string name="activity_type_futsal">Zaalvoetbal</string>
+    <string name="activity_type_hacky_sack">Hacky sack</string>
+    <string name="activity_type_modern_dance">Moderne dans</string>
+    <string name="activity_type_muay_thai">Thaiboksen</string>
+    <string name="activity_type_parallel_bars">Brug met gelijke liggers</string>
+    <string name="activity_type_shuffleboard">Sjoelen</string>
+    <string name="activity_type_snorkeling">Snorkelen</string>
+    <string name="activity_type_square_dance">Square dance</string>
+    <string name="activity_type_stair_climber">Stairclimber</string>
+    <string name="activity_type_free_sparring">Sparren</string>
+    <string name="activity_type_plaza_dancing">Pleindansen</string>
+    <string name="activity_type_laser_tag">Lasergamen</string>
+    <string name="activity_type_obstacle_race">Obstakelloop</string>
+    <string name="activity_type_billiard_pool">Pool</string>
+    <string name="activity_type_water_scooter">Waterscooter</string>
+    <string name="activity_type_bobsleigh">Bobslee毛n</string>
+    <string name="activity_type_sledding">Slee毛n</string>
+    <string name="activity_type_orienteering">Ori毛ntatieloop</string>
+    <string name="devicetype_garmin_venu_2s">Garmin Venu 2S</string>
+    <string name="hrv">HRV</string>
+    <string name="steps_distance_unit">%1$,.2f km</string>
+    <string name="devicetype_mismartscale">Mi Smart Scale 2</string>
+    <string name="devicetype_micompositionscale">Mi Body Composition Scale 2</string>
+    <string name="steps_avg">Stappen gemiddeld</string>
+    <string name="steps_total">Stappen totaal</string>
+    <string name="miscale_weight_unit_title">Gewichtseenheid</string>
+    <string name="miscale_weight_unit_summary">Ingestelde eenheid voor het weergeven van gewicht</string>
+    <string name="miscale_weight_unit_metric">Metrisch (kg)</string>
+    <string name="miscale_weight_unit_imperial">Amerikaans (lbs)</string>
+    <string name="miscale_weight_unit_chinese">Chinees (jin)</string>
+    <string name="miscale_small_objects_title">Kleine objecten</string>
+    <string name="miscale_small_objects_summary">Sla gemeten gewichten onder de 10kg op</string>
+    <string name="distance_avg">Afstand gemiddeld</string>
+    <string name="distance_total">Afstand totaal</string>
+    <string name="estimatedSweatLoss">Geschatte zweetverlies</string>
+    <string name="pref_time_sync">Automatische tijdsynchronisatie</string>
+    <string name="user_feedback_set_settings_ok">Instellingen verstuurd naar apparaat.</string>
+    <string name="minutes_2">2 minuten</string>
+    <string name="minutes_255">255 minuten</string>
+    <string name="dateformat_day_month">Dag, Maand</string>
+    <string name="dateformat_month_day">Maand, Dag</string>
+    <string name="busy_task_fetch_sleep_data">Slaapgegevens ophalen</string>
+    <string name="minutes_45">45 minuten</string>
+    <string name="minutes_3">3 minuten</string>
+    <string name="minutes_4">4 minuten</string>
+    <string name="minutes_6">6 minuten</string>
+    <string name="minutes_7">7 minuten</string>
+    <string name="minutes_8">8 minuten</string>
+    <string name="minutes_9">9 minuten</string>
+    <string name="minutes_15">15 minuten</string>
+    <string name="minutes_150">150 minuten</string>
+    <string name="minutes_180">180 minuten</string>
+    <string name="minutes_210">210 minuten</string>
+    <string name="minutes_240">240 minuten</string>
+    <string name="devicetype_colmi_r02">Colmi R02</string>
+    <string name="stress_average">Gemiddeld</string>
+    <string name="smart_ring_measurement_error_worn_incorrectly">Meetfout. Zijn de sensors van de ring correct ge枚rienteerd?</string>
+    <string name="smart_ring_measurement_error_unknown">Onbekende meetfout %d ontvangen van ring</string>
+    <string name="devicetype_mijia_xmwsdj04">Mijia temperatuur- en luchtvochtigheidsensor 2 (e-ink)</string>
+    <string name="devicetype_honor_watchgs3">Honor Watch GS 3</string>
+    <string name="prefs_light_duration_longer">Langere lichtduur</string>
+    <string name="sleep_colored_stats_awake_avg">Wakker gemiddeld</string>
+    <string name="pref_app_connection_duration">App-verbindingsduur</string>
+    <string name="lactateThresholdHeartRate">Lactaatdrempel-hartslag</string>
+    <string name="recoveryTime">Hersteltijd</string>
+    <string name="devicetype_garmin_fenix_5x_plus">Garmin Fenix 5X Plus</string>
+    <string name="devicetype_colmi_r03">Colmi R03</string>
+    <string name="devicetype_colmi_r06">Colmi R06</string>
+    <string name="target">Doel</string>
+    <string name="pref_header_sound">Geluid</string>
+    <string name="minutes_75">75 minuten</string>
+    <string name="minutes_90">90 minuten</string>
+    <string name="activity_type_rem_sleep">REM-slaap</string>
+    <string name="weight_kg">%1$.2f kg</string>
+    <string name="weight_lbs">%1$.2f lbs</string>
+    <string name="minutes_120">120 minuten</string>
+    <string name="menuitem_weight">Gewicht</string>
+    <string name="devicetype_garmin_forerunner_165">Garmin Forerunner 165</string>
+    <string name="devicetype_soundcore_liberty4_nc">Soundcore Liberty 4 NC</string>
+    <string name="workout_set_repetitions">%1d x</string>
+    <string name="workout_set_repetitions_weight_kg">%1d x %2$.2f kg</string>
+    <string name="workout_set_repetitions_weight_lbs">%1d x %2$.2f lbs</string>
+    <string name="pref_deprecated_media_control_summary">Stuur mediabesturingscommando\'s als toetsenbord-aanslagen in plaats van via de media controller.</string>
+    <string name="pref_deprecated_media_control_title">Verouderde mediabesturing</string>
+    <string name="prefs_summary_gatt_client_allow_gatt_interactions">Sta BLE-characteristic lees/schrijf- en verbindingscommando\'s toe</string>
+    <string name="prefs_title_gatt_client_api_package">BLE API-pakketnaam</string>
+    <string name="prefs_summary_gatt_client_api_package">Beperk BLE Intent API-communicatie tot deze app</string>
+    <string name="pref_continuous_skin_temperature_measurement_title">Continue huidtemperatuurmeting</string>
+    <string name="devicetype_ble_gatt_client">Algemene BLE GATT-client</string>
+    <string name="pref_continuous_skin_temperature_measurement_description">Termperatuurgegevens ophalen wordt momenteel niet ondersteund. Deze instelling schakelt alleen het continu meten in op het apparaat</string>
+    <string name="pref_header_deprecated_functionalities_warning">De volgende functies zijn verouderd en zullen in een toekomstige versie van de software verwijderd worden.
+\nAls het voor u nodig is om een van deze instellingen in te schakelen, neem dan contact op met het projectteam om dit te melden.</string>
+    <string name="prefs_title_gatt_client_allow_gatt_interactions">Sta GATT-interactie toe via de BLE Intent API</string>
+    <string name="prefs_title_gatt_client_notification_intents">Stuur GATT-notificaties via de BLE Intent API</string>
+    <string name="prefs_summary_gatt_client_notification_intents">Ontvang BLE characteristic-wijzigingen via Intents</string>
+    <string name="prefs_summary_gatt_client_device_state_updates">Ontvang BLE-verbindingsstatuswijzigingen via Intents</string>
+    <string name="prefs_title_ble_intent_api">BLE Intent API</string>
+    <string name="pref_header_deprecated_functionalities">Verouderde functies</string>
+    <string name="choose_device">Kies een apparaat</string>
+    <string name="no_supported_devices_found">Geen ondersteunde apparaten gevonden</string>
+    <string name="menuitem_stress_simple">Stress (simpel)</string>
+    <string name="menuitem_stress_segmented">Stress (gesegmenteerd)</string>
+    <string name="menuitem_stress_breakdown">Stress (opgesplitst)</string>
+    <string name="search">Zoeken</string>
+    <string name="pref_title_nagivation_apps">Navigatie-apps</string>
+    <string name="pref_header_external_integrations">Externe integraties</string>
+    <string name="pref_header_automations">Automatische taken</string>
+    <string name="pref_description_general">Start, taal, regio, locatie</string>
+    <string name="pref_description_about_you">Geboortedatum, geslacht, lengte, gewicht, doelen</string>
+    <string name="pref_description_notifications">App-meldingen, toestaan/weigeren</string>
+    <string name="pref_description_user_interface">Thema, hoofdscherm</string>
+    <string name="pref_description_dashboard">Widgets, gebruikte apparaten</string>
+    <string name="pref_description_automations">Automatische export, activiteitsgegevens</string>
+    <string name="pref_description_developer_options">Logbestanden, intent-API</string>
+    <string name="pref_description_deprecated_functionalities">Instellingen die in een toekomstige versie verwijderd zullen worden</string>
+    <string name="pref_header_main_screen">Hoofdscherm</string>
+    <string name="pref_title_user_interface">Gebruikersinterface</string>
+    <string name="activity_prefs_date_birth">Geboortedatum</string>
+    <string name="workoutSets">Combinaties</string>
+    <string name="workout_set_i">Combinatie %1d</string>
+    <string name="activity_db_management_import_from_zip">Importeer zip-bestand</string>
+    <string name="backup_restore_exporting">Exporteren naar zip-bestand鈥�</string>
+    <string name="backup_restore_exporting_preferences">Exporteren van instellingen鈥�</string>
+    <string name="backup_restore_exporting_database">Exporteren van database鈥�</string>
+    <string name="backup_restore_exporting_files">Exporteren van bestanden鈥�</string>
+    <string name="backup_restore_exporting_files_i_of_n">Exporteren van bestanden鈥� %1d van %2d</string>
+    <string name="backup_restore_exporting_finishing">Export afronden鈥�</string>
+    <string name="backup_restore_importing">Importeren van zip-bestand鈥�</string>
+    <string name="backup_restore_do_not_exit">%s Houd alstublieft dit scherm open totdat de bewerking is voltooid.</string>
+    <string name="backup_restore_importing_loading">Bestand laden鈥�</string>
+    <string name="backup_restore_importing_validating">Bestand verifi毛ren鈥�</string>
+    <string name="backup_restore_importing_files_i_of_n">Importeren van bestanden鈥� %1d van %2d</string>
+    <string name="backup_restore_importing_database">Importeren van database鈥�</string>
+    <string name="backup_restore_importing_preferences">Importeren van instellingen鈥�</string>
+    <string name="backup_restore_warning_files">%1d bestanden konden niet hersteld worden:
+\n%2s</string>
+    <string name="backup_restore_export_complete">Export afgerond</string>
+    <string name="backup_restore_import_complete">Import afgerond</string>
+    <string name="backup_restore_error_export">Export naar zip-bestand mislukt</string>
+    <string name="backup_restore_error_import">Import van zip-bestand mislukt</string>
+    <string name="backup_restore_abort_title">Afbreken</string>
+    <string name="backup_restore_abort_export_confirmation">De export afbreken? Het gedeeltelijke zip-bestand zal worden verwijderd.</string>
+    <string name="backup_restore_abort_import_confirmation">De import afbreken? Dit kan leiden tot een beschadigde of inconsistente database.</string>
+    <string name="backup_restore_restart_summary">%1s zal nu herstarten.</string>
+    <string name="activity_db_management_backup_restore_label">Back-up en herstel</string>
+    <string name="activity_db_management_export_to_zip">Exporteer zip-bestand</string>
+    <string name="activity_db_management_backup_restore_explanation">De export- en import-functies maken het mogelijk om alle Gadgetbridge-instellingen, apparaten en data te back-uppen of te migreren via een zip-bestand.
+\n
+\nHet importeren van een zip-bestand zal alle bestaande data, apparaten en instellingen verwijderen, en volledig vervangen met de back-up.</string>
+    <string name="backup_restore_restart_title">Herstart</string>
+    <string name="devicetype_honor_watchgspro">Honor Watch GS Pro</string>
+    <string name="devicetype_amazfit_trex_3">Amazfit T-Rex 3</string>
+    <string name="menuitem_map">Kaart</string>
+    <string name="menuitem_night_display">Nachtstand</string>
+    <string name="menuitem_heart_rate_push">Hartslag rapporteren</string>
+    <string name="label_distance_trip">Trip: %.1f km</string>
+    <string name="label_distance_total">Totaal: %.1f km</string>
+    <string name="error_no_cycling_sensor_found">geen fietssensor gevonden</string>
+    <string name="label_distance_total_mph">Totaal: %.1f mi</string>
+    <string name="label_distance_trip_mph">Trip: %.1f mi</string>
+    <string name="vo2max_running">Hardlopen VO鈧� Max</string>
+    <string name="thirty_days_timeline">30-dagentijdlijn</string>
+    <string name="bpm_value_unit">%1$d bpm</string>
+    <string name="devicetype_garmin_forerunner_265s">Garmin Forerunner 265S</string>
+    <string name="devicetype_garmin_enduro_3">Garmin Enduro 3</string>
+    <string name="devicetype_sony_wf_c500">Sony WF-C500</string>
+    <string name="vo2max_cycling">Fietsen VO鈧� Max</string>
+    <string name="hr_resting">In rust</string>
+    <string name="hr_maximum">Maximum</string>
+    <string name="hr_minimum">Minimum</string>
+    <string name="hr_average">Gemiddeld</string>
+    <string name="devicetype_garmin_forerunner_245_music">Garmin Forerunner 245 Music</string>
+    <string name="devicetype_garmin_forerunner_955">Garmin Forerunner 955</string>
+    <string name="devicetype_miband9">Xiaomi Smart Band 9</string>
+    <string name="devicetype_huawei_watchgt5">Huawei Watch GT 5 (Pro)</string>
+    <string name="devicetype_redmi_watch_5_active">Redmi Watch 5 Active</string>
+    <string name="pref_summary_sync_birthdays">Synchroniseer verjaardagen van contactpersonen naast reguliere agenda-afspraken</string>
+    <string name="devicetype_sony_wf_c700n">Sony WF-C700N</string>
+    <string name="app_crash_notification_title">%1s is gecrasht</string>
+    <string name="app_crash_share_stacktrace">Deel foutmelding</string>
+    <string name="contact_birthday">Verjaardag van %1s</string>
+    <string name="pref_title_sync_birthdays">Synchroniseer verjaardagen</string>
+    <string name="pref_crash_notification_title">Melding van crash</string>
+    <string name="pref_crash_notification_summary">Toon een melding met de fout wanneer de app crasht</string>
+    <string name="birthdays">Verjaardagen</string>
+    <string name="pref_dashboard_widget_today_time_indicator_title">Indicator van huidige tijd</string>
+    <string name="pref_dashboard_widget_today_yesterday_data_title">Gegevens van gisteren</string>
+    <string name="pref_dashboard_widget_today_yesterday_data_summary">Toon gegevens van gisteren met gedimde kleuren tussen de huidige tijd en middernacht</string>
+    <string name="pref_dashboard_widget_today_time_indicator_summary">Toon een indicator op de huidige tijd, als visuele scheiding tussen de gegevens van gisteren en vandaag</string>
+    <string name="pref_title_calendar_lookahead">Aantal toekomstige dagen</string>
+    <string name="pref_summary_calendar_lookahead">Synchroniseer %1s dagen aan kalenderevenementen</string>
+    <string name="workout_set_reps">Herhalingen</string>
+    <string name="devicetype_huawei_watchgtcyber">Huawei Watch GT Cyber</string>
+    <string name="hrZoneEasy">Makkelijk</string>
+    <string name="hrZoneThreshold">Drempel</string>
+    <string name="hrZoneMaximum">Maximaal</string>
+    <string name="activity_detail_share_json_details">Deel JSON-details</string>
+    <string name="number_selected_items">%1d geselecteerd</string>
+    <string name="paceCorrection">Correctie</string>
+    <string name="devicetype_garmin_fenix_8">Garmin Fenix 8</string>
+    <string name="devicetype_garmin_venu_sq">Garmin Venu Sq</string>
+    <string name="mijia_lywsd_comfort_upper">Maximum</string>
+    <string name="mijia_lywsd_comfort_lower">Minimum</string>
+    <string name="date_placeholders__date__time">%1s, %1s</string>
+    <string name="date_placeholders__start_time__end_time">%1s - %2s</string>
+    <string name="mijia_lywsd_comfort_level_summary">Stel de temperatuur- en luchtvochtigheidslimieten in voor de weergegeven emoji</string>
+    <string name="devicetype_garmin_venu_sq_2">Garmin Venu Sq 2</string>
+    <string name="devicetype_garmin_fenix_6s_sapphire">Garmin Fenix 6S Sapphire</string>
+    <string name="mijia_lywsd_comfort_level_title">Comfortniveau</string>
+    <string name="mijia_lywsd_comfort_temperature_title">Temperatuur (掳C)</string>
+    <string name="mijia_lywsd_comfort_temperature_summary">Aangeraden bereik: 19 - 27</string>
+    <string name="mijia_lywsd_comfort_humidity_title">Luchtvochtigheid (%)</string>
+    <string name="mijia_lywsd_comfort_humidity_summary">Aangeraden bereik: 20 - 85</string>
+    <string name="busy_task_fetch_hrv_data">HRV-gegevens ophalen</string>
+    <string name="pref_dnd_follow_phone_title">Volg niet-storenstand</string>
+    <string name="pref_dnd_follow_phone_summary">Wanneer \"niet storen\" wordt in- of uitgeschakeld op de telefoon, verander de instelling op het apparaat mee</string>
+    <string name="inactivity_warnings_minimum_steps_title">Minimum aantal stappen</string>
+    <string name="inactivity_warnings_minimum_steps_summary">Minimaal aantal stappen dat gezet moet worden binnen de minutendrempel</string>
+    <string name="prefs_hrv_monitoring_title">HRV-monitoring</string>
+    <string name="prefs_hrv_monitoring_description">Automatisch de hele dag de variabiliteit van hartslag in de gaten houden</string>
+    <string name="average_respiration_rate">Ademhalingsfrequentie</string>
+    <string name="max_respiration_rate">Maximale ademhalingsfrequentie</string>
+    <string name="min_respiration_rate">Minimale ademhalingsfrequentie</string>
+    <string name="hrv_sdrr">HRV SDRR</string>
+    <string name="hrv_rmssd">HRV RMSSD</string>
+    <string name="breaths_per_min">ademhaling/min</string>
+    <string name="fmtPaceAverage">Tempo gemiddeld</string>
+    <string name="milliseconds_ms">ms</string>
+    <string name="devicetype_huawei_watchd2">Huawei Watch D2</string>
+    <string name="devicetype_colmi_r10">Colmi R10</string>
+    <string name="devicetype_idasen">IKEA Idasen bureau</string>
+    <string name="idasen_control_button_mid">Midden</string>
+    <string name="idasen_control_button_sit">Zitten</string>
+    <string name="idasen_control_button_stand">Staan</string>
+    <string name="idasen_pref_mid_height">Middenpositie (in centimeters)</string>
+    <string name="idasen_pref_value_warning">De waarde moet tussen 62 en 126 cm zijn</string>
+    <string name="idasen_pref_sit_height">Zitpositie (in centimeters)</string>
+    <string name="idasen_pref_stand_height">Stapositie (in centimeters)</string>
+    <string name="music_upload_info">U staat op het punt om het volgende muziekbestand te uploaden:\n\n%1$s\nTitel: %2$s\nAlbum: %3$s\n</string>
+    <string name="devicetype_garmin_fenix_7">Garmin Fenix 7</string>
+    <string name="file_already_exists">Bestand bestaat al</string>
+    <string name="devicetype_garmin_forerunner_235">Garmin Forerunner 235</string>
+    <string name="devicetype_sony_wi_c100">Sony WI-C100</string>
+    <string name="devicetype_bandw_pseries">Bowers and Wilkins P series</string>
+    <string name="devicetype_garmin_fenix_6s_pro">Garmin Fenix 6S Pro</string>
+    <string name="pref_voice_passthrough_enabled_summary">Laat externe geluiden door naar uw oren</string>
+    <string name="devicetype_huawei_band3pro">Huawei Band 3 (Pro)</string>
+    <string name="pref_voice_passthrough_enabled">Doorlaten</string>
+    <string name="pref_voice_passthrough_level">Niveau van doorlaten</string>
+    <string name="devicetype_garmin_forerunner_620">Garmin Forerunner 620</string>
+    <string name="devicetype_oppo_enco_air">Oppo Enco Air</string>
+    <string name="devicetype_garmin_forerunner_55">Garmin Forerunner 55</string>
+    <string name="menuitem_calories_segmented">Calorie毛n (gesegmenteerd)</string>
+    <string name="active_calories">Actieve calorie毛n</string>
+    <string name="active_calories_short">Actief</string>
+    <string name="active_calories_goal">Doel actieve calorie毛n</string>
+    <string name="total_calories_burnt">Totaal verbrand</string>
+    <string name="activity_prefs_goal_active_calories_burnt">Dagelijks doel: calorie毛n actief verbrand</string>
+    <string name="redmi_buds_5_pro_transparency_strength">Transparatiesterkte</string>
+    <string name="redmi_buds_5_pro_transparency_regular">Normaal</string>
+    <string name="redmi_buds_5_pro_transparency_voice">Versterk stemmen</string>
+    <string name="redmi_buds_5_pro_transparency_ambient">Versterk omgevingsgeluiden</string>
+    <string name="devicetype_miband9pro">Xiaomi Smart Band 9 Pro</string>
+    <string name="respiratoryrate">Ademhalingssnelheid</string>
+    <string name="sleep_avg">Slaap gemiddeld</string>
+    <string name="highest">Hoogste</string>
+    <string name="redmi_buds_5_pro_anc_deep">Diep</string>
+    <string name="watchface_widget_type_sp02">SpO2</string>
+    <string name="redmi_buds_5_pro_anc_light">Licht</string>
+    <string name="lowest">Laagste</string>
+    <string name="devicetype_redmi_buds_5_pro">Redmi Buds 5 Pro</string>
+    <string name="menuitem_calories_active_goal">Calorie毛ndoel (actief)</string>
+    <string name="devicetype_oppo_enco_air2">Oppo Enco Air2</string>
+    <string name="redmi_buds_5_pro_anc_balanced">Gebalanceerd</string>
+    <string name="title_activity_musicmanager">Muziekbeheer</string>
+    <string name="pref_wear_sensor_summary">Detecteer wanneer het apparaat niet gedragen wordt</string>
+    <string name="pref_wear_sensor_title">Draagsensor</string>
+    <string name="devicetype_realme_buds_t110">Realme Buds T110</string>
+    <string name="devicetype_garmin_instinct_2">Garmin Instinct 2</string>
+</resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml
new file mode 100644
index 0000000..70d3f19
--- /dev/null
+++ b/app/src/main/res/values-pl/strings.xml
@@ -0,0 +1,3718 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources xmlns:tools="http://schemas.android.com/tools">
+    <string name="application_name_generic">Gadgetbridge</string>
+    <string name="title_activity_controlcenter_generic">Gadgetbridge</string>
+    <string name="action_settings">Ustawienia</string>
+    <string name="action_debug">Debuguj</string>
+    <string name="action_quit">Wyjd藕</string>
+    <string name="controlcenter_fetch_activity_data">Synchronizuj</string>
+    <string name="controlcenter_find_device">Znajd藕 zagubione urz膮dzenie</string>
+    <string name="controlcenter_take_screenshot">Wykonaj zrzut ekranu</string>
+    <string name="controlcenter_disconnect">Roz艂膮cz</string>
+    <string name="controlcenter_delete_device">Usu艅 urz膮dzenie</string>
+    <string name="controlcenter_delete_device_name">Usu艅 %1$s</string>
+    <string name="controlcenter_delete_device_dialogmessage">Spowoduje to usuni臋cie urz膮dzenia i wszystkich zwi膮zanych z nim danych!</string>
+    <string name="title_activity_debug">Debuguj</string>
+    <!--Strings related to AppManager-->
+    <string name="title_activity_appmanager">Zarz膮dzanie aplikacjami</string>
+    <string name="appmanager_cached_watchapps_watchfaces">Aplikacje w pami臋ci podr臋cznej</string>
+    <string name="appmanager_installed_watchapps">Zainstalowane aplikacje</string>
+    <string name="appmanager_installed_watchfaces">Zainstalowane tarcze</string>
+    <string name="appmananger_app_delete">Usu艅</string>
+    <string name="appmananger_app_delete_cache">Odinstaluj i usu艅 z pami臋ci podr臋cznej</string>
+    <string name="appmananger_app_reinstall">Zainstaluj ponownie</string>
+    <string name="appmanager_app_openinstore">Szukaj w Pebble AppStore</string>
+    <string name="appmanager_health_activate">Aktywuj</string>
+    <string name="appmanager_health_deactivate">Dezaktywuj</string>
+    <string name="appmanager_hrm_activate">Aktywuj HRM</string>
+    <string name="appmanager_hrm_deactivate">Dezaktywuj HRM</string>
+    <string name="app_configure">Konfiguruj</string>
+    <string name="app_move_to_top">Przenie艣 na g贸r臋</string>
+    <!--Strings related to FwAppInstaller-->
+    <string name="title_activity_fw_app_insaller">Instalator oprogramowania uk艂adowego/aplikacji</string>
+    <string name="fw_upgrade_notice">Zamierzasz zainstalowa膰 %s.</string>
+    <string name="miband_firmware_known">To oprogramowanie uk艂adowe zosta艂o przetestowane i jest ono kompatybilne z Gadgetbridge.</string>
+    <string name="miband_firmware_unknown_warning">To oprogramowanie uk艂adowe nie zosta艂o przetestowane i mo偶e by膰 niekompatybilne z Gadgetbridge.
+\n
+\nWgrywanie tego oprogramowania jest NIEZALECANE!</string>
+    <string name="miband_firmware_suggest_whitelist">Je艣li nadal chcesz kontynuowa膰 i po instalacji wszystko b臋dzie dzia艂a膰 prawid艂owo, prosz臋 powiadomi膰 programist贸w Gadgetbridge, aby dodali wersj臋 %s oprogramowania uk艂adowego do bia艂ej listy.</string>
+    <!--Strings related to Settings-->
+    <string name="title_activity_settings">Ustawienia</string>
+    <string name="pref_header_general">Ustawienia og贸lne</string>
+    <string name="pref_title_general_autoconnectonbluetooth">Po艂膮cz urz膮dzenia z Gadgetbridge, gdy Bluetooth jest w艂膮czony</string>
+    <string name="pref_title_general_autoreconnect">Automatycznie pon贸w po艂膮czenie</string>
+    <string name="pref_title_audio_player">Preferowany odtwarzacz muzyki</string>
+    <string name="pref_default">Domy艣lny</string>
+    <string name="pref_header_datetime">Data i godzina</string>
+    <string name="pref_title_datetime_syctimeonconnect">Synchronizuj czas</string>
+    <string name="pref_summary_datetime_syctimeonconnect">Synchronizuj czas urz膮dzenia lub urz膮dze艅 podczas 艂膮czenia, gdy czas albo strefa czasowa zmieni膮 si臋 na urz膮dzeniu Android, oraz okresowo</string>
+    <string name="pref_title_theme">Motyw</string>
+    <string name="pref_theme_light">Jasny</string>
+    <string name="pref_theme_dark">Ciemny</string>
+    <string name="pref_title_language">J臋zyk</string>
+    <string name="pref_header_notifications">Powiadomienia</string>
+    <string name="pref_title_notifications_repetitions">Powt贸rzenia</string>
+    <string name="pref_title_notifications_call">Rozmowy telefoniczne</string>
+    <string name="pref_title_notifications_sms">SMS</string>
+    <string name="pref_title_notifications_pebblemsg">Wiadomo艣ci Pebble</string>
+    <string name="pref_title_notifications_generic">Og贸lna obs艂uga powiadomie艅</string>
+    <string name="pref_title_whenscreenon">鈥ak偶e, gdy ekran jest w艂膮czony</string>
+    <string name="pref_title_notification_filter">Nie przeszkadza膰</string>
+    <string name="always">Zawsze</string>
+    <string name="when_screen_off">Gdy ekran jest wy艂膮czony</string>
+    <string name="never">Nigdy</string>
+    <string name="pref_header_cannned_messages">Szablonowe odpowiedzi</string>
+    <string name="pref_title_canned_replies">Odpowiedzi</string>
+    <string name="pref_title_canned_messages_set">Uaktualnij na urz膮dzeniu</string>
+    <string name="pref_header_development">Ustawienia programisty</string>
+    <string name="pref_title_development_miaddr">Adres Mi Band</string>
+    <string name="pref_title_pebble_settings">Ustawienia Pebble</string>
+    <string name="pref_header_activitytrackers">Monitorowanie aktywno艣ci</string>
+    <string name="pref_title_pebble_activitytracker">Preferowany monitor aktywno艣ci</string>
+    <string name="pref_title_pebble_sync_health">Synchronizuj Pebble Health</string>
+    <string name="pref_title_pebble_sync_misfit">Synchronizuj Misfit</string>
+    <string name="pref_title_pebble_sync_morpheuz">Synchronizuj Morpheuz</string>
+    <string name="pref_title_enable_pebblekit">Zezw贸l zewn臋trznym aplikacjom Android na dost臋p</string>
+    <string name="pref_summary_enable_pebblekit">W艂膮cz eksperymentaln膮 obs艂ug臋 aplikacji Android za pomoc膮 PebbleKit</string>
+    <string name="pref_title_sunrise_sunset">Wsch贸d i zach贸d s艂o艅ca</string>
+    <string name="pref_summary_sunrise_sunset">Wy艣lij na o艣 czasu Pebble czas wschodu i zachodu s艂o艅ca bazuj膮c na lokalizacji</string>
+    <string name="pref_header_location">Lokalizacja</string>
+    <string name="pref_title_location_aquire">Uzyskaj lokalizacj臋</string>
+    <string name="pref_title_location_latitude">Szeroko艣膰</string>
+    <string name="pref_title_location_longitude">D艂ugo艣膰</string>
+    <string name="pref_title_location_keep_uptodate">Utrzymuj aktualn膮 lokalizacj臋</string>
+    <string name="toast_enable_networklocationprovider">W艂膮cz us艂ug臋 lokalizacji sieciowej</string>
+    <string name="toast_aqurired_networklocation">Lokalizacja ustalona</string>
+    <string name="pref_title_pebble_forceprotocol">Wymu艣 protok贸艂 powiadomie艅</string>
+    <string name="pref_summary_pebble_forceprotocol">Ta opcja wymusza u偶ycie najnowszego protoko艂u powiadomie艅 w zale偶no艣ci od wersji oprogramowania. POWINIENE艢 WIEDZIE膯 CO ROBISZ!</string>
+    <string name="pref_title_pebble_forceuntested">W艂膮cz nieprzetestowane funkcje</string>
+    <string name="pref_summary_pebble_forceuntested">W艂膮cz nieprzetestowane funkcje. POWINIENE艢 WIEDZIE膯 CO ROBISZ!</string>
+    <string name="pref_title_pebble_forcele">Zawsze preferuj BLE</string>
+    <string name="pref_title_pebble_mtu_limit">Limit MTU Pebble 2/LE GATT</string>
+    <string name="pref_summary_pebble_mtu_limit">Je艣li Tw贸j Pebble 2/ Pebble LE nie dzia艂a jak nale偶y spr贸buj ustawi膰 ten limit MTU (zakres 20鈥�512)</string>
+    <string name="pref_title_pebble_enable_applogs">W艂膮cz logi aplikacji zegarka/opaski</string>
+    <string name="pref_title_pebble_reconnect_attempts">Pr贸by ponownego po艂膮czenia</string>
+    <string name="not_connected">Nie po艂膮czono</string>
+    <string name="connecting">艁膮czenie</string>
+    <string name="connected">Po艂膮czono</string>
+    <string name="unknown_state">Nieznany stan</string>
+    <string name="_unknown_">(nieznane)</string>
+    <string name="test">Testuj</string>
+    <string name="test_notification">Test powiadomie艅</string>
+    <string name="this_is_a_test_notification_from_gadgetbridge">To jest testowe powiadomienie z Gadgetbridge</string>
+    <string name="bluetooth_is_not_supported_">Bluetooth nie jest obs艂ugiwany.</string>
+    <string name="bluetooth_is_disabled_">Bluetooth jest wy艂膮czony.</string>
+    <string name="tap_connected_device_for_app_mananger">Kliknij po艂膮czone urz膮dzenie, aby uruchomi膰 menad偶er aplikacji</string>
+    <string name="tap_a_device_to_connect">Dotknij urz膮dzenie aby po艂膮czy膰</string>
+    <string name="cannot_connect_bt_address_invalid_">Nie mo偶na po艂膮czy膰. Nieprawid艂owy adres Bluetooth\?</string>
+    <string name="gadgetbridge_running_generic">Gadgetbridge jest uruchomiony</string>
+    <string name="installing_binary_d_d">Instalowanie binarki %1$d/%2$d</string>
+    <string name="installation_failed_">Instalacja nie powiod艂a si臋</string>
+    <string name="installation_successful">Zainstalowano</string>
+    <string name="firmware_install_warning">PR脫BUJESZ ZAINSTALOWA膯 OPROGRAMOWANIE UK艁ADOWE, KONTYNUUJ NA W艁ASNE RYZYKO.
+\n
+\n
+\n Te oprogramowanie uk艂adowe jest przeznaczone dla wersji HW: %s</string>
+    <string name="app_install_info">Zamierzasz zainstalowa膰 poni偶sz膮 aplikacj臋:
+\n
+\n%1$s
+\nWersja %2$s od %3$s
+\n</string>
+    <string name="n_a">Niedost臋pne</string>
+    <string name="initialized">rozpocz臋ty</string>
+    <string name="appversion_by_creator">%1$s przez %2$s</string>
+    <string name="title_activity_discovery">Wyszukiwanie urz膮dze艅</string>
+    <string name="discovery_stop_scanning">Przerwij wyszukiwanie</string>
+    <string name="discovery_start_scanning">Rozpocznij wyszukiwanie</string>
+    <string name="action_discover">Dodaj nowe urz膮dzenie</string>
+    <string name="device_with_rssi">%1$s (%2$s)</string>
+    <string name="title_activity_android_pairing">Sparuj urz膮dzenie</string>
+    <string name="android_pairing_hint">U偶yj parowania Bluetooth na Androidzie by sparowa膰 urz膮dzenie.</string>
+    <string name="title_activity_mi_band_pairing">Sparuj Mi Band</string>
+    <string name="pairing">Parowanie z %s鈥�</string>
+    <string name="message_cannot_pair_no_mac">Nie przekazano adresu MAC, nie mo偶na go sparowa膰.</string>
+    <string name="preferences_category_device_specific_settings">Ustawienia danego urz膮dzenia</string>
+    <string name="preferences_miband_settings">Ustawienia Mi Band i Amazfit</string>
+    <string name="male">M臋偶czyzna</string>
+    <string name="female">Kobieta</string>
+    <string name="other">Inne</string>
+    <string name="left">Lewa</string>
+    <string name="right">Prawa</string>
+    <string name="miband_pairing_using_dummy_userdata">Nie podano prawid艂owych danych u偶ytkownika, zostan膮 u偶yte dane zast臋pcze.</string>
+    <string name="miband_pairing_tap_hint">Gdy tw贸j Mi Band wibruje i b艂yska, dotknij jego ekran lub przycisk kilka razy pod rz膮d.</string>
+    <string name="appinstaller_install">Instaluj</string>
+    <string name="discovery_note">Uwaga:</string>
+    <string name="candidate_item_device_image">Obraz urz膮dzenia</string>
+    <string name="miband_prefs_alias">Imi臋/Pseudonim</string>
+    <string name="pref_header_vibration_count">Liczba wibracji</string>
+    <string name="title_activity_sleepmonitor">Monitor snu</string>
+    <string name="initializing">Inicjalizacja</string>
+    <string name="busy_task_fetch_activity_data">Pobieranie danych o aktywno艣ci</string>
+    <string name="sleep_activity_date_range">Od %1$s do %2$s</string>
+    <string name="prefs_wearside">Na kt贸rej r臋ce noszone jest urz膮dzenie\?</string>
+    <string name="pref_screen_vibration_profile">Profil wibracji</string>
+    <string name="vibration_profile_staccato">Wzorowane na Staccato</string>
+    <string name="vibration_profile_short">Kr贸tkie</string>
+    <string name="vibration_profile_medium">艢rednie</string>
+    <string name="vibration_profile_long">D艂ugie</string>
+    <string name="vibration_profile_waterdrop">Kropla wody</string>
+    <string name="vibration_profile_ring">Dzwonek</string>
+    <string name="vibration_profile_alarm_clock">Budzik</string>
+    <string name="miband_prefs_vibration">Wibracja</string>
+    <string name="pref_screen_notification_profile_sms">Powiadomienie SMS</string>
+    <string name="pref_header_vibration_settings">Ustawienia wibracji</string>
+    <string name="pref_screen_notification_profile_generic">Og贸lne powiadomienia</string>
+    <string name="pref_screen_notification_profile_email">Powiadomienia E-mail</string>
+    <string name="pref_screen_notification_profile_incoming_call">Powiadomienia o po艂膮czeniach przychodz膮cych</string>
+    <string name="pref_screen_notification_profile_generic_navigation">Nawigacja</string>
+    <string name="pref_screen_notification_profile_generic_social">Sie膰 spo艂eczno艣ciowa</string>
+    <string name="control_center_find_lost_device">Odnajd藕 zagubione urz膮dzenie</string>
+    <string name="control_center_cancel_to_stop_vibration">Anuluj by przerwa膰 wibracje.</string>
+    <string name="title_activity_charts">Aktywno艣膰 i Sen</string>
+    <string name="title_activity_set_alarm">Konfiguruj alarmy</string>
+    <string name="controlcenter_start_configure_alarms">Konfiguruj alarmy</string>
+    <string name="title_activity_alarm_details">Szczeg贸艂y alarmu</string>
+    <string name="alarm_sun_short">Nd</string>
+    <string name="alarm_mon_short">Pon</string>
+    <string name="alarm_tue_short">Wt</string>
+    <string name="alarm_wed_short">艢r</string>
+    <string name="alarm_thu_short">Czw</string>
+    <string name="alarm_fri_short">Pt</string>
+    <string name="alarm_sat_short">Sob</string>
+    <string name="alarm_smart_wakeup">Inteligentna pobudka</string>
+    <string name="user_feedback_miband_set_alarms_failed">B艂膮d podczas ustawiania alarmu, prosz臋 spr贸buj ponownie.</string>
+    <string name="user_feedback_miband_set_alarms_ok">Alarmy zapisane w urz膮dzeniu.</string>
+    <string name="chart_no_data_synchronize">Brak danych. Synchronizowa膰 urz膮dzenie?</string>
+    <string name="user_feedback_miband_activity_data_transfer">Zamierzam przes艂a膰 %1$s danych, rozpoczynaj膮c od %2$s</string>
+    <string name="miband_prefs_fitness_goal">Dzienny cel krok贸w</string>
+    <string name="dbaccess_error_executing">B艂膮d przetwarzania \'%1$s\' </string>
+    <string name="controlcenter_start_activitymonitor">Twoja aktywno艣膰</string>
+    <string name="cannot_connect">Nie mo偶na po艂膮czy膰: %1$s</string>
+    <string name="installer_activity_unable_to_find_handler">Nie mo偶na znale藕膰 procedury by zainstalowa膰 ten plik.</string>
+    <string name="pbw_install_handler_unable_to_install">Nie mo偶na zainstalowa膰 podanego pliku : %1$s</string>
+    <string name="pbw_install_handler_hw_revision_mismatch">Instalacja tego oprogramowania jest niemo偶liwa, poniewa偶 nie pasuje do wersji sprz臋towej twojego Pebble.</string>
+    <string name="installer_activity_wait_while_determining_status">Prosz臋 czeka膰 na okre艣lanie statusu instalacji鈥�</string>
+    <string name="notif_battery_low_title">Gad偶et prawie roz艂adowany!</string>
+    <string name="notif_battery_low_percent">%1$s pozosta艂o baterii: %2$s%%</string>
+    <string name="notif_battery_low_bigtext_last_charge_time">Ostatnie 艂adowanie: %s 
+\n</string>
+    <string name="notif_battery_low_bigtext_number_of_charges">Liczba 艂adowa艅: %s</string>
+    <string name="sleepchart_your_sleep">Sen</string>
+    <string name="weekstepschart_steps_a_week">Kroki (tyg.)</string>
+    <string name="activity_sleepchart_activity_and_sleep">Aktywno艣膰</string>
+    <string name="updating_firmware">Flashowanie oprogramowania鈥�</string>
+    <string name="fwapp_install_device_not_ready">Nie mo偶na zainstalowa膰聽pliku, urz膮dzenie nie jest gotowe.</string>
+    <string name="installhandler_firmware_name">%1$s: %2$s %3$s</string>
+    <string name="miband_fwinstaller_compatible_version">Zgodna wersja</string>
+    <string name="miband_fwinstaller_untested_version">Nietestowana wersja!</string>
+    <string name="fwappinstaller_connection_state">Po艂膮czenie z urz膮dzeniem: %1$s</string>
+    <string name="pbw_installhandler_pebble_firmware">Firmware Pebble  %1$s</string>
+    <string name="pbwinstallhandler_correct_hw_revision">Prawid艂owa wersja sprz臋tu</string>
+    <string name="pbwinstallhandler_incorrect_hw_revision">Niezgodno艣膰 wersji sprz臋tu!</string>
+    <string name="pbwinstallhandler_app_item">%1$s (%2$s)</string>
+    <string name="updatefirmwareoperation_updateproblem_do_not_reboot">Problem podczas przesy艂ania firmware. NIE RESTARTUJ swojego Mi Band!</string>
+    <string name="updatefirmwareoperation_metadata_updateproblem">Problem z transferem metadanych firmware\'u</string>
+    <string name="updatefirmwareoperation_update_complete">Instalacja firmware\'u zako艅czona</string>
+    <string name="updatefirmwareoperation_update_complete_rebooting">Instalacja firmware\'u zako艅czona, restartowanie urz膮dzenia鈥�</string>
+    <string name="updatefirmwareoperation_write_failed">Instalacja oprogramowania nie powiod艂a si臋</string>
+    <string name="chart_steps">Kroki</string>
+    <string name="liveactivity_live_activity">Obecna aktywno艣膰</string>
+    <string name="weeksteps_today_steps_description">Krok贸w dzi艣, cel: %1$s</string>
+    <string name="pref_title_dont_ack_transfer">Nie przesy艂aj danych aktywno艣ci ACK</string>
+    <string name="pref_summary_dont_ack_transfers">Je艣li transmisja danych nie zostanie potwierdzona przez opask臋, dane dotycz膮ce aktywno艣ci nie zostan膮 wyczyszczone. Przydatne, je艣li GB jest u偶ywany razem z innymi aplikacjami.</string>
+    <string name="pref_summary_keep_data_on_device">Dane o aktywno艣ciach b臋d膮 zachowane na urz膮dzeniu nawet po synchronizacji. Jest to przydatne, gdy Gadgetbridge jest u偶ywany razem z innymi aplikacjami. Mo偶e to spowodowa膰, 偶e zabraknie miejsca na urz膮dzeniu i/lub synchronizacja przestanie dzia艂a膰 poprawnie.</string>
+    <string name="pref_summary_low_latency_fw_update">Mo偶e pom贸c na urz膮dzeniach gdzie aktualizacja ko艅czy si臋 b艂臋dem.</string>
+    <string name="live_activity_steps_history">Historia krok贸w</string>
+    <string name="live_activity_current_steps_per_minute">Aktualnie krok贸w/min</string>
+    <string name="live_activity_total_steps">Krok贸w 艂膮cznie</string>
+    <string name="live_activity_steps_per_minute_history">Historia krok贸w na minut臋</string>
+    <string name="live_activity_start_your_activity">Rozpocznij aktywno艣膰</string>
+    <string name="abstract_chart_fragment_kind_activity">Aktywno艣膰</string>
+    <string name="abstract_chart_fragment_kind_light_sleep">P艂ytki sen</string>
+    <string name="abstract_chart_fragment_kind_deep_sleep">G艂臋boki sen</string>
+    <string name="abstract_chart_fragment_kind_not_worn">Nieza艂o偶one</string>
+    <string name="device_not_connected">Nie po艂膮czono.</string>
+    <string name="user_feedback_all_alarms_disabled">Wszystkie alarmy wy艂膮czono</string>
+    <string name="pref_title_keep_data_on_device">Zachowaj dane aktywno艣ci na urz膮dzeniu</string>
+    <string name="miband_fwinstaller_incompatible_version">Niekompatybilny firmware</string>
+    <string name="fwinstaller_firmware_not_compatible_to_device">Ten firmware nie jest kompatybilny z tym urz膮dzeniem</string>
+    <string name="miband_prefs_reserve_alarm_calendar">Alarmy zarezerwowane dla nadchodz膮cych zdarze艅</string>
+    <string name="miband_prefs_hr_sleep_detection">U偶yj czujnika t臋tna, aby poprawi膰 wykrywanie snu</string>
+    <string name="miband2_prefs_dateformat">Format daty</string>
+    <string name="dateformat_time">Czas</string>
+    <string name="waiting_for_reconnect">Oczekiwanie na ponowne po艂膮czenie</string>
+    <string name="activity_prefs_about_you">O Tobie</string>
+    <string name="activity_prefs_year_birth">Rok urodzenia</string>
+    <string name="activity_prefs_gender">P艂e膰</string>
+    <string name="activity_prefs_height_cm">Wzrost w cm</string>
+    <string name="activity_prefs_weight_kg">Waga w kg</string>
+    <string name="add_widget">Dodaj widget</string>
+    <string name="activity_prefs_sleep_duration">Preferowana d艂ugo艣膰 snu w godzinach</string>
+    <string name="device_hw">Wersja sprz臋tu: %1$s</string>
+    <string name="device_fw">Wersja oprogramowania: %1$s</string>
+    <string name="updatefirmwareoperation_update_in_progress">Aktualizowanie oprogramowania</string>
+    <string name="charts_legend_heartrate">Puls</string>
+    <string name="live_activity_heart_rate">T臋tno</string>
+    <string name="action_db_management">Zarz膮dzanie danymi</string>
+    <string name="title_activity_db_management">Zarz膮dzanie danymi</string>
+    <string name="dbmanagementactivity_exported_to">Wyeksportowano do: %1$s</string>
+    <string name="dbmanagementactivity_error_exporting_db">B艂膮d eksportu bazy: %1$s</string>
+    <string name="dbmanagementactivity_import_data_title">Zaimportowa膰?</string>
+    <string name="dbmanagementactivity_overwrite_database_confirmation">Czy naprawd臋 nadpisa膰 bie偶膮ce dane? Wszystkie Twoje dane dotycz膮ce aktywno艣ci (je艣li istniej膮), urz膮dzenia i preferencje zostan膮 nadpisane.</string>
+    <string name="dbmanagementactivity_error_importing_db">B艂膮d importu bazy: %1$s</string>
+    <string name="dbmanagementactivity_overwrite">Nadpisz</string>
+    <string name="Cancel">Anuluj</string>
+    <string name="Delete">Usu艅</string>
+    <!--Strings related to Vibration Activity-->
+    <string name="title_activity_vibration">Wibracje</string>
+    <!--Strings related to Pebble Pairing Activity-->
+    <string name="action_donate">Wspom贸偶</string>
+    <string name="controlcenter_snackbar_need_longpress">Naci艣nij i przytrzymaj, aby roz艂膮czy膰</string>
+    <string name="controlcenter_snackbar_disconnecting">Roz艂膮czanie</string>
+    <string name="controlcenter_snackbar_connecting">艁膮czenie鈥�</string>
+    <string name="controlcenter_snackbar_requested_screenshot">Wykonywanie zrzutu ekranu urz膮dzenia</string>
+    <string name="appmanager_weather_activate">Aktywuj systemow膮 aplikacj臋 pogody</string>
+    <string name="appmanager_weather_deactivate">Dezaktywuj systemow膮 aplikacj臋 pogody</string>
+    <string name="appmanager_weather_install_provider">Zainstaluj aplikacj臋 Weather Notification</string>
+    <string name="title_activity_calblacklist">Lista zablokowanych kalendarzy</string>
+    <string name="pref_title_general_autostartonboot">Uruchom automatycznie</string>
+    <string name="pref_title_minimize_priority">Ukryj powiadomienia Gadgetbridge</string>
+    <string name="pref_summary_minimize_priority_off">Ikona na pasku stanu i powiadomienia b臋d膮 wy艣wietlane na zablokowanym ekranie</string>
+    <string name="pref_summary_minimize_priority_on">Ikona na pasku stanu i powiadomienia b臋d膮 ukrywane na zablokowanym ekranie</string>
+    <string name="pref_summary_notification_filter">Blokuj wszystkie powiadomienia, gdy tryb \"Nie przeszkadza膰\" jest w艂膮czony w telefonie</string>
+    <string name="pref_title_transliteration">Transliteracja</string>
+    <string name="pref_summary_transliteration">W艂膮cz t臋 opcj臋, je艣li twoje urz膮dzenie nie obs艂uguje czcionki twojego j臋zyka</string>
+    <string name="pref_header_privacy">Prywatno艣膰</string>
+    <string name="pref_call_privacy_mode_off">Wy艣wietl nazw臋 i numer</string>
+    <string name="pref_call_privacy_mode_name">Ukryj nazw臋, ale wy艣wietl numer</string>
+    <string name="pref_call_privacy_mode_number">Ukryj numer, ale wy艣wietl nazw臋</string>
+    <string name="pref_call_privacy_mode_complete">Ukryj nazw臋 i numer</string>
+    <string name="pref_blacklist_calendars">Czarna lista kalendarzy</string>
+    <string name="pref_title_canned_reply_suffix">Wsp贸lny przyrostek</string>
+    <string name="pref_title_enable_outgoing_call">Wspieraj rozmowy wychodz膮ce</string>
+    <string name="pref_header_pebble_timeline">O艣 czasu Pebble</string>
+    <string name="pref_title_enable_calendar_sync">Synchronizuj kalendarz</string>
+    <string name="pref_summary_enable_calendar_sync">Wy艣lij zdarzenia z kalendarza na o艣 czasu</string>
+    <string name="pref_title_autoremove_notifications">Automatycznie usu艅 odrzucone powiadomienia</string>
+    <string name="pref_title_pebble_privacy_mode">Tryb prywatny</string>
+    <string name="pref_pebble_privacy_mode_off">Normalne powiadomienia</string>
+    <string name="pref_pebble_privacy_mode_complete">Poka偶 sam膮 ikon臋 powiadomie艅</string>
+    <string name="pref_title_pebble_enable_bgjs">W艂膮cz JS w tle</string>
+    <string name="pref_summary_pebble_enable_bgjs">Je艣li w艂膮czone, pozwala zegarkowi na wy艣wietlanie informacji o pogodzie, baterii itp.</string>
+    <string name="pref_title_unit_system">Jednostki</string>
+    <string name="pref_title_timeformat">Format czasu</string>
+    <string name="preferences_hplus_settings">Ustawienia HPlus/Makibes</string>
+    <string name="tap_connected_device_for_activity">Kliknij po艂膮czone urz膮dzenie, aby wy艣wietli膰 jego aktywno艣膰</string>
+    <string name="tap_connected_device_for_vibration">Kliknij po艂膮czone urz膮dzenie, aby zawibrowa艂o</string>
+    <string name="pairing_creating_bond_with">Tworzenie powi膮zania z %1$s (%2$s)</string>
+    <string name="pairing_unable_to_pair_with">Nie mo偶na sparowa膰 z %1$s (%2$s)</string>
+    <string name="pairing_in_progress">Trwa tworzenie powi膮zania: %1$s (%2$s)</string>
+    <string name="pairing_already_bonded">Ju偶 zwi膮zano z %1$s (%2$s), 艂膮czenie 鈥�</string>
+    <string name="pref_write_logfiles">Zapisuj logi</string>
+    <string name="vibration_try">Wypr贸buj</string>
+    <string name="pref_screen_notification_profile_generic_chat">Czat</string>
+    <string name="stats_title">Strefy pr臋dko艣ci</string>
+    <string name="stats_y_axis_label">Krok贸w na minut臋</string>
+    <string name="calories">Kalorie</string>
+    <string name="distance">Dystans</string>
+    <string name="clock">Zegar</string>
+    <string name="heart_rate">T臋tno</string>
+    <string name="battery">Bateria</string>
+    <string name="dateformat_date_time"><![CDATA[Data i czas]]></string>
+    <string name="mi2_prefs_button_actions">Akcje przycisku</string>
+    <string name="mi2_prefs_button_actions_summary">Wybierz akcj臋 po przyci艣ni臋ciu przycisku</string>
+    <string name="mi2_prefs_button_press_count">Licznik wci艣ni臋膰 przycisku</string>
+    <string name="mi2_prefs_button_press_count_summary">Ilo艣膰 przyci艣ni臋膰 w celu uruchomienia Zdarzenia 1. Kolejna taka sama liczba przyci艣ni臋膰 spowoduje utworzenie Zdarzenia 2 i tak dalej.</string>
+    <string name="mi2_prefs_button_action">Uruchom przycisk akcji</string>
+    <string name="mi2_prefs_button_action_vibrate">Uruchom wibracje opaski</string>
+    <string name="mi2_prefs_button_press_count_max_delay">Maksymalne op贸藕nienie pomi臋dzy wci艣ni臋ciami</string>
+    <string name="mi2_prefs_button_press_count_max_delay_summary">Maksymalne op贸藕nienie pomi臋dzy wci艣ni臋ciami w milisekundach</string>
+    <string name="mi2_prefs_goal_notification_summary">Opaska zawibruje po osi膮gni臋ciu dziennego celu krok贸w</string>
+    <string name="mi2_prefs_do_not_disturb">Tryb 鈥濶ie przeszkadza膰鈥�</string>
+    <string name="pref_summary_notifications_pebblemsg">Obs艂uga aplikacji wysy艂aj膮cych powiadomienia do Pebble za po艣rednictwem PebbleKit.</string>
+    <string name="pref_title_weather">Pogoda</string>
+    <string name="pref_title_canned_messages_dismisscall">Odrzucenie po艂膮czenia</string>
+    <string name="prefs_title_all_day_heart_rate">Ca艂odobowy pomiar t臋tna</string>
+    <string name="pref_header_auto_export">Automatyczny eksport</string>
+    <string name="pref_summary_auto_export_interval">Eksportuj co %d godzin(y)</string>
+    <string name="prefs_title_heartrate_measurement_interval">Ca艂odobowy pomiar t臋tna</string>
+    <string name="interval_one_minute">raz na minut臋</string>
+    <string name="interval_five_minutes">co 5 minut</string>
+    <string name="interval_ten_minutes">co 10 minut</string>
+    <string name="interval_thirty_minutes">co 30 minut</string>
+    <string name="interval_one_hour">raz na godzin臋</string>
+    <string name="activity_type_light_sleep">Sen p艂ytki</string>
+    <string name="activity_type_deep_sleep">Sen g艂臋boki</string>
+    <string name="activity_type_running">Bieganie</string>
+    <string name="activity_type_swimming">P艂ywanie</string>
+    <string name="activity_type_treadmill">Bie偶nia</string>
+    <string name="controlcenter_connect">Po艂膮cz鈥�</string>
+    <string name="controlcenter_navigation_drawer_open">Otw贸rz szuflad臋 nawigacji</string>
+    <string name="controlcenter_navigation_drawer_close">Zamknij szuflad臋 nawigacji</string>
+    <string name="controlcenter_calibrate_device">Skalibruj urz膮dzenie</string>
+    <string name="blacklist_all_for_notifications">Zablokuj wszystkie powiadomienia</string>
+    <string name="whitelist_all_for_notifications">Zezw贸l na wszystkie powiadomienia</string>
+    <string name="fw_upgrade_notice_amazfitbip">W艂a艣nie zamierzasz zainstalowa膰 oprogramowanie uk艂adowe %s na swoim Amazfit Bip.
+\n
+\nProsz臋 upewni膰 si臋, 偶e zainstalowany zosta艂 plik .fw, nast臋pnie plik .res i na ko艅cu plik .gps. Po zainstalowaniu pliku .fw Tw贸j zegarek zostanie ponownie uruchomiony.
+\n
+\nUwaga: Nie musisz instalowa膰 plik贸w .res i .gps, je艣li te pliki s膮 dok艂adnie takie same jak te, kt贸re zosta艂y wcze艣niej zainstalowane.
+\n
+\nKONTYNUUJ NA W艁ASNE RYZYKO!</string>
+    <string name="fw_upgrade_notice_amazfitcor">W艂a艣nie zamierzasz zainstalowa膰 oprogramowanie uk艂adowe %s na swoim Amazfit Cor.
+\n
+\nProsz臋 upewni膰 si臋, 偶e zainstalowany zosta艂 plik .fw, a nast臋pnie plik .res. Po zainstalowaniu pliku .fw Tw贸j zegarek zostanie ponownie uruchomiony.
+\n
+\nUwaga: Nie musisz instalowa膰 pliku .res, je艣li jest on dok艂adnie taki sam jak ten, kt贸ry zosta艂 wcze艣niej zainstalowany.
+\n
+\nKONTYNUUJ NA W艁ASNE RYZYKO!</string>
+    <string name="fw_upgrade_notice_miband3">W艂a艣nie zamierzasz zainstalowa膰 oprogramowanie uk艂adowe %s na swoim Mi Band 3.
+\n
+\nProsz臋 upewni膰 si臋, 偶e zainstalowany zosta艂 plik .fw, a nast臋pnie plik .res. Po zainstalowaniu pliku .fw Tw贸j zegarek zostanie ponownie uruchomiony.
+\n
+\nUwaga: Nie musisz instalowa膰 pliku .res, je艣li jest on dok艂adnie taki sam jak ten plik, kt贸ry zosta艂 wcze艣niej zainstalowany.
+\n
+\nKONTYNUUJ NA W艁ASNE RYZYKO!</string>
+    <string name="fw_multi_upgrade_notice">Zamierzasz zainstalowa膰 oprogramowanie uk艂adowe %1$s i %2$s, zamiast tego, kt贸re obecnie znajduje si臋 w Twoim Mi Band.</string>
+    <string name="pref_title_charts_swipe">W艂膮cz gesty przesuwania w lewo i w prawo na kartach aktywno艣ci</string>
+    <string name="pref_title_notifications_timeout">Minimalny czas pomi臋dzy powiadomieniami</string>
+    <string name="pref_title_call_privacy_mode">Tryb prywatno艣ci po艂膮cze艅 telefonicznych</string>
+    <string name="pref_title_weather_location">Lokalizacja pogody (dla dostawcy pogody LineageOS)</string>
+    <string name="pref_summary_enable_outgoing_call">Wy艂膮czenie tej opcji spowoduje r贸wnie偶, 偶e Pebble 2/LE przestanie wibrowa膰 przy po艂膮czeniach wychodz膮cych</string>
+    <string name="pref_summary_autoremove_notifications">Powiadomienia s膮 automatycznie usuwane z urz膮dzenia po usuni臋ciu z telefonu</string>
+    <string name="pref_pebble_privacy_mode_content">Przesu艅 tekst powiadomienia poza ekran</string>
+    <string name="pref_summary_location_keep_uptodate">Spr贸buj uzyska膰 lokalizacj臋 w czasie uruchomienia, u偶ywa przechowywanych lokalizacji jako pomocy</string>
+    <string name="controlcenter_change_led_color">Zmie艅 kolor LED</string>
+    <string name="controlcenter_change_fm_frequency">Zmie艅 cz臋stotliwo艣膰 FM</string>
+    <string name="pref_title_rtl">Od prawej do lewej</string>
+    <string name="pref_summary_rtl">W艂膮cz t臋 opcj臋, je艣li twoje urz膮dzenie nie obs艂uguje wy艣wietlania j臋zyk贸w z pisowni膮 od prawej do lewej</string>
+    <string name="pref_rtl_max_line_length">Maksymalna d艂ugo艣膰 wiersza</string>
+    <string name="pref_rtl_max_line_length_summary">Wyd艂u偶a lub skraca d艂ugo艣膰 wiersza pisanego od prawej do lewej</string>
+    <string name="pref_summary_pebble_forcele">U偶yj eksperymentalnego wsparcia Pebble LE (Low Energy), zamiast Bluetooth Classic, dla wszystkich typ贸w Pebble. Wymaga sparowania poprzez BT Classic, a nast臋pnie LE</string>
+    <string name="pref_title_pebble_gatt_clientonly">Tylko klient GATT</string>
+    <string name="pref_summary_pebble_gatt_clientonly">Ta eksperymentalna opcja przeznaczona jest wy艂膮cznie dla Pebble 2. Wypr贸buj j膮 je偶eli masz problemy z po艂膮czeniem</string>
+    <string name="pref_summary_pebble_enable_applogs">Spowoduje, 偶e logi z aplikacji zegarka b臋d膮 zapisywane przez Gadgetbridge (wymaga ponownego po艂膮czenia)</string>
+    <string name="pref_title_pebble_always_ack_pebblekit">Przedwczesne ACK PebbleKit</string>
+    <string name="pref_summary_pebble_always_ack_pebblekit">Spowoduje, 偶e wiadomo艣ci wysy艂ane do zewn臋trznych aplikacji b臋d膮 zawsze natychmiast potwierdzane</string>
+    <string name="pref_title_screentime">Czas w艂膮czenia ekranu</string>
+    <string name="prefs_screen_orientation">Orientacja ekranu</string>
+    <string name="pref_title_auto_export_enabled">Automatyczny eksport w艂膮czony</string>
+    <string name="pref_title_auto_export_location">Lokalizacja eksportu</string>
+    <string name="pref_auto_fetch">Automatycznie pobieraj dane o aktywno艣ci</string>
+    <string name="pref_auto_fetch_summary">Pobranie danych nast膮pi po odblokowaniu ekranu. Dzia艂a tylko je艣li blokada jest w艂膮czona!</string>
+    <string name="pref_auto_fetch_limit_fetches">Minimalny odst臋p czasu pomi臋dzy pobraniami</string>
+    <string name="pref_auto_fetch_limit_fetches_summary">Pobieranie co %d minut(y)</string>
+    <string name="horizontal">Poziomo</string>
+    <string name="vertical">Pionowo</string>
+    <string name="discovery_connected_devices_hint">Upewnij si臋, 偶e urz膮dzenie (opaska/smartwatch) jest wykrywalne. Aktualnie po艂膮czone urz膮dzenia prawdopodobnie nie b臋d膮 wykrywalne. Na Androidzie 6+ w艂膮cz dost臋p do lokalizacji (GPS). Wy艂膮cz Privacy Guard dla Gadgetbridge, poniewa偶 mog膮 ze sob膮 kolidowa膰 i zrestartuj telefon. Je偶eli urz膮dzenie nie zostanie wykryte w przeci膮gu paru minut, spr贸buj ponownie po ponownym uruchomieniu telefonu.</string>
+    <string name="watch9_pairing_tap_hint">Je艣li Tw贸j zegarek (opaska) wibruje, potrz膮艣nij nim lub wci艣nij na nim przycisk.</string>
+    <string name="stats_x_axis_label">Ca艂kowicie minut</string>
+    <string name="notif_battery_low">%1$s roz艂adowana bateria</string>
+    <string name="notif_battery_low_extended">%1$s roz艂adowana bateria: %2$s</string>
+    <string name="notif_export_failed_title">Eksport bazy danych nie powi贸d艂 si臋! Sprawd藕 swoje ustawienia.</string>
+    <string name="weeksleepchart_sleep_a_week">Sen (tyg.)</string>
+    <string name="weeksleepchart_today_sleep_description">Sen dzisiaj, cel: %1$s</string>
+    <string name="lack_of_sleep">Niedob贸r snu: %1$s</string>
+    <string name="no_limit">Bez ogranicze艅</string>
+    <string name="seconds_5">5 sekund</string>
+    <string name="seconds_10">10 sekund</string>
+    <string name="seconds_20">20 sekund</string>
+    <string name="seconds_30">30 sekund</string>
+    <string name="minutes_1">1 minuta</string>
+    <string name="minutes_5">5 minut</string>
+    <string name="minutes_10">10 minut</string>
+    <string name="minutes_30">30 minut</string>
+    <string name="lack_of_step">Pozosta艂o krok贸w: %1$d</string>
+    <string name="pref_title_low_latency_fw_update">U偶yj trybu o niskiej latencji przy aktualizacji firmware</string>
+    <string name="live_activity_max_heart_rate">Aktualny / Maks. t臋tno: %1$d / %2$d</string>
+    <string name="you_slept">%1$s - %2$s</string>
+    <string name="miband_prefs_device_time_offset_hours">Offset snu w godzinach (do detekcji snu pracownik贸w zmianowych)</string>
+    <string name="mi2_prefs_button_action_summary">W艂膮cz akcj臋 po naci艣ni臋ciu przycisku okre艣lon膮 ilo艣膰 razy</string>
+    <string name="mi2_prefs_button_action_vibrate_summary">W艂膮cz wibracj臋 zegarka (opaski) przy uruchomieniu akcji przycisku</string>
+    <string name="mi2_prefs_goal_notification">Powiadomienie o osi膮gni臋ciu celu</string>
+    <string name="mi2_prefs_display_items">Wy艣wietlane pozycje</string>
+    <string name="mi2_prefs_display_items_summary">Wybierz pozycje wy艣wietlane na ekranie urz膮dzenia</string>
+    <string name="mi2_prefs_activate_display_on_lift">Aktywuj ekran po podniesieniu nadgarstka</string>
+    <string name="mi2_prefs_rotate_wrist_to_switch_info">Przekr臋膰 nadgarstek, aby prze艂膮czy膰 informacje</string>
+    <string name="mi2_prefs_inactivity_warnings">Ostrze偶enia o nieaktywno艣ci</string>
+    <string name="mi2_prefs_inactivity_warnings_summary">Urz膮dzenie zawibruje, gdy b臋dziesz pozostawa膰 w bezruchu przez pewien czas</string>
+    <string name="mi2_prefs_inactivity_warnings_threshold">Okres nieaktywno艣ci (w minutach)</string>
+    <string name="mi2_prefs_inactivity_warnings_dnd_summary">Wy艂膮cz ostrze偶enie o nieaktywno艣ci dla zadanego przedzia艂u czasu</string>
+    <string name="mi3_prefs_night_mode">Tryb nocny</string>
+    <string name="mi3_prefs_night_mode_summary">Automatycznie zmniejsz jasno艣膰 wy艣wietlacza w nocy</string>
+    <string name="automatic">Automatycznie</string>
+    <string name="simplified_chinese">Chi艅ski (uproszczony)</string>
+    <string name="traditional_chinese">Chi艅ski (tradycyjny)</string>
+    <string name="english">Angielski</string>
+    <string name="spanish">Hiszpa艅ski</string>
+    <string name="russian">Rosyjski</string>
+    <string name="german">Niemiecki</string>
+    <string name="italian">W艂oski</string>
+    <string name="french">Francuski</string>
+    <string name="polish">Polski</string>
+    <string name="korean">Korea艅ski</string>
+    <string name="japanese">Japo艅ski</string>
+    <string name="activity_prefs_charts">Ustawienia wykres贸w</string>
+    <string name="activity_prefs_chart_max_heart_rate">T臋tno maksymalne</string>
+    <string name="activity_prefs_chart_min_heart_rate">T臋tno minimalne</string>
+    <string name="authenticating">Uwierzytelnianie</string>
+    <string name="authentication_required">Wymagane uwierzytelnienie</string>
+    <string name="appwidget_text">Zzz</string>
+    <string name="updatefirmwareoperation_firmware_not_sent">Firmware nie zosta艂o wys艂ane</string>
+    <string name="pref_title_pebble_health_store_raw">Przechowuj \"surowe\" rekordy w bazie danych</string>
+    <string name="pref_summary_pebble_health_store_raw">Zapisuje dane bez przetwarzania, zwi臋kszaj膮c u偶ycie bazy danych i umo偶liwiaj膮c ich p贸藕niejsz膮 interpretacj臋.</string>
+    <string name="dbmanagementactivvity_cannot_access_export_path">Brak dost臋pu do 艣cie偶ki eksportu. Prosimy o kontakt z developerami.</string>
+    <string name="dbmanagementactivity_import_successful">Zaimportowano.</string>
+    <string name="dbmanagementactivity_delete_activity_data_title">Skasowa膰 dane o aktywno艣ci\?</string>
+    <string name="dbmanagementactivity_really_delete_entire_db">Czy na pewno usun膮膰 ca艂膮 baz臋 danych\? Skasuje to wszystkie dane o aktywno艣ci i urz膮dzeniach.</string>
+    <string name="dbmanagementactivity_database_successfully_deleted">Dane zosta艂y usuni臋te.</string>
+    <string name="dbmanagementactivity_db_deletion_failed">Usuwanie bazy danych nie powiod艂o si臋.</string>
+    <string name="dbmanagementactivity_delete_old_activity_db">Czy skasowa膰 star膮 baz臋 danych aktywno艣ci\?</string>
+    <string name="dbmanagementactivity_delete_old_activitydb_confirmation">Czy na pewno skasowa膰 star膮 baz臋 danych aktywno艣ci\? Wszystkie dane, kt贸re nie zosta艂y zaimportowane zostan膮 utracone.</string>
+    <string name="dbmanagementactivity_old_activity_db_successfully_deleted">Stara baza danych aktywno艣ci zosta艂a usuni臋ta.</string>
+    <string name="dbmanagementactivity_old_activity_db_deletion_failed">Usuwanie starej bazy danych aktywno艣ci nie powiod艂o si臋.</string>
+    <string name="title_activity_pebble_pairing">Parowanie Pebble</string>
+    <string name="pebble_pairing_hint">Komunikat o parowaniu pojawi si臋 na Twoim urz膮dzeniu z Androidem. W przeciwnym wypadku sprawd藕 powiadomienia i zaakceptuj 偶膮danie parowania. Nast臋pnie zaakceptuj parowanie r贸wnie偶 na Twoim Pebble.</string>
+    <string name="pref_title_setup_bt_pairing">W艂膮cz parowanie Bluetooth</string>
+    <string name="pref_summary_setup_bt_pairing">Wy艂膮cz to je偶eli masz problemy z po艂膮czeniem</string>
+    <string name="unit_metric">Metryczne</string>
+    <string name="find_device_you_found_it">Znalezione!</string>
+    <string name="mi2_fw_installhandler_fw53_hint">Zanim wgrasz to firmware, musisz wgra膰 wersj臋 %1$s!</string>
+    <string name="mi2_enable_text_notifications">Powiadomienia tekstowe</string>
+    <string name="mi2_enable_text_notifications_summary">Wymaga firmware w wersji &gt;= 1.0.1.28 i zainstalowanego Mili_pro.ft*</string>
+    <string name="on">W艂.</string>
+    <string name="off">Wy艂.</string>
+    <string name="mi2_dnd_off">Wy艂.</string>
+    <string name="mi3_night_mode_sunset">O zachodzie s艂o艅ca</string>
+    <string name="mi2_dnd_automatic">Automatycznie (wykrywanie snu)</string>
+    <string name="mi2_dnd_scheduled">Zaplanowane (przedzia艂 czasu)</string>
+    <string name="discovery_attempting_to_pair">Pr贸ba sparowania z %1$s</string>
+    <string name="discovery_trying_to_connect_to">Pr贸ba po艂膮czenia z: %1$s</string>
+    <string name="discovery_enable_bluetooth">W艂膮cz Bluetooth, aby wykry膰 urz膮dzenia.</string>
+    <string name="discovery_pair_title">Sparowa膰 z %1$s\?</string>
+    <string name="discovery_yes_pair">Paruj</string>
+    <string name="discovery_dont_pair">Nie paruj</string>
+    <string name="_pebble_watch_open_on_phone">Otw贸rz na urz膮dzeniu z Androidem</string>
+    <string name="_pebble_watch_mute">Wycisz</string>
+    <string name="_pebble_watch_reply">Odpowiedz</string>
+    <string name="activity_type_not_measured">Nie zmierzono</string>
+    <string name="activity_type_activity">Aktywno艣膰</string>
+    <string name="activity_type_not_worn">Urz膮dzenie nie za艂o偶one</string>
+    <string name="activity_type_walking">Spacer</string>
+    <string name="activity_type_unknown">Nieznana aktywno艣膰</string>
+    <string name="activity_summaries">Aktywno艣ci sportowe</string>
+    <string name="activity_type_biking">Jazda na rowerze</string>
+    <string name="select_all">Zaznacz wszystko</string>
+    <string name="share">Udost臋pnij</string>
+    <string name="kind_invalid">Niepoprawne dane</string>
+    <string name="kind_font">Czcionka</string>
+    <string name="kind_gps">Firmware GPS</string>
+    <string name="kind_gps_almanac">Almanach GPS</string>
+    <string name="kind_gps_cep">Korekcja b艂臋d贸w GPS</string>
+    <string name="kind_resources">Zasoby</string>
+    <string name="kind_watchface">Tarcza zegarka</string>
+    <string name="devicetype_unknown">Nieznane urz膮dzenie</string>
+    <string name="choose_auto_export_location">Wybierz 艣cie偶k臋 do eksportu</string>
+    <string name="notification_channel_name">Og贸lne</string>
+    <string name="menuitem_more">Wi臋cej</string>
+    <string name="watch9_time_minutes">Minuty:</string>
+    <string name="watch9_time_hours">Godziny:</string>
+    <string name="watch9_time_seconds">Sekundy:</string>
+    <string name="watch9_calibration_button">Kalibruj</string>
+    <string name="share_log_warning">Prosimy pami臋ta膰, 偶e logi Gadgetbridge mog膮 zawiera膰 wiele informacji osobistych, w tym dane dotycz膮ce zdrowia, unikalne identyfikatory (takie jak adresy MAC urz膮dze艅), preferencje muzyczne itd. Rozwa偶 edycj臋 log贸w i usuni臋cie takich informacji, zanim wy艣lesz je przy zg艂aszaniu problemu.</string>
+    <string name="warning">Ostrze偶enie!</string>
+    <string name="no_data">Brak danych</string>
+    <string name="preferences_led_color">Kolor LED</string>
+    <string name="preferences_fm_frequency">Cz臋stotliwo艣膰 FM</string>
+    <string name="pref_invalid_frequency_title">Niew艂a艣ciwa cz臋stotliwo艣膰</string>
+    <string name="pref_invalid_frequency_message">Podaj cz臋stotliwo艣膰 z zakresu od 87.5 do 108.0 Mhz</string>
+    <string name="language_and_region_prefs">Ustawienia j臋zykowe i regionalne</string>
+    <string name="pref_title_auto_export_interval">Cz臋sto艣膰 eksportowania</string>
+    <string name="unit_imperial">imperialne</string>
+    <string name="debugactivity_really_factoryreset_title">Przywr贸ci膰 do ustawie艅 fabrycznych\?</string>
+    <string name="debugactivity_really_factoryreset">Powr贸t do ustawie艅 fabrycznych spowoduje usuni臋cie wszystkich danych z pod艂膮czonego urz膮dzenia (je艣li jest obs艂ugiwane). Urz膮dzenia Xiaomi/Huami zmieni膮 r贸wnie偶 adres MAC Bluetooth, wi臋c pojawi膮 si臋 dla Gadgetbridge jako nowe urz膮dzenia.</string>
+    <string name="overslept">Nadmiar snu: %1$s</string>
+    <string name="overstep">Przekroczenie: %1$d</string>
+    <string name="you_did_not_sleep">Nie spa艂e艣(-a艣)</string>
+    <string name="mi2_prefs_button_press_broadcast">Wiadomo艣膰 do rozg艂oszenia</string>
+    <string name="mi2_prefs_button_press_broadcast_summary">Wiadomo艣膰 broadcast nadawana wraz z zdarzeniem. Do ka偶dej wiadomo艣ci jest automatycznie dodawany parametr `button_id`.</string>
+    <string name="mi2_prefs_do_not_disturb_summary">Opaska nie b臋dzie wy艣wietla膰 powiadomie艅, gdy ten tryb jest aktywny</string>
+    <string name="mi2_prefs_do_not_disturb_start">Pocz膮tek</string>
+    <string name="mi2_prefs_do_not_disturb_end">Koniec</string>
+    <string name="mi3_prefs_band_screen_unlock">Odblokowanie ekranu opaski</string>
+    <string name="mi3_prefs_band_screen_unlock_summary">Przesu艅 w g贸r臋, aby odblokowa膰 ekran opaski</string>
+    <string name="norwegian_bokmal">Norweski Bokm氓l</string>
+    <string name="FetchActivityOperation_about_to_transfer_since">Przeniesienie danych z okresu %1$s</string>
+    <string name="appwidget_setting_alarm">Ustawienie alarmu na %1$02d:%2$02d</string>
+    <string name="error_creating_directory_for_logfiles">B艂膮d tworzenia folderu na pliki log: %1$s</string>
+    <string name="DEVINFO_HR_VER">"T臋tno: "</string>
+    <string name="filter_mode_whitelist">Pokazuj gdy zawiera s艂owa</string>
+    <string name="pref_chart_heartrate_color_red">Czerwony</string>
+    <string name="mi2_prefs_button_press_broadcast_default_value" translatable="false">nodomain.freeyourgadget.gadgetbridge.ButtonPressed</string>
+    <string name="timeformat_24h">24-godzinny</string>
+    <string name="timeformat_am_pm">12-godzinny</string>
+    <string name="pref_screen_notification_profile_alarm_clock">Budzik</string>
+    <string name="StringUtils_sender">(%1$s)</string>
+    <string name="miband2_prefs_timeformat">Mi2: Format czasu</string>
+    <string name="discovery_pair_question">Wybierz \"Paruj\" 偶eby sparowa膰 urz膮dzenia. Je艣li operacja si臋 nie powiedzie, spr贸buj ponownie bez parowania.</string>
+    <string name="kind_firmware">Firmware</string>
+    <string name="devicetype_test">Urz膮dzenie testowe</string>
+    <string name="devicetype_pebble">Pebble</string>
+    <string name="devicetype_miband">Mi Band</string>
+    <string name="devicetype_miband2">Mi Band 2</string>
+    <string name="devicetype_amazfit_bip">Amazfit Bip</string>
+    <string name="devicetype_amazfit_cor">Amazfit Cor</string>
+    <string name="devicetype_vibratissimo">Vibratissimo</string>
+    <string name="devicetype_liveview">LiveView</string>
+    <string name="devicetype_hplus">HPlus</string>
+    <string name="devicetype_makibes_f68">Makibes F68</string>
+    <string name="devicetype_exrizu_k8">Exrizu K8</string>
+    <string name="devicetype_no1_f1">No.1 F1</string>
+    <string name="devicetype_teclast_h30">Teclast H30</string>
+    <string name="devicetype_xwatch">XWatch</string>
+    <string name="menuitem_status">Status</string>
+    <string name="menuitem_activity">Historia trening贸w</string>
+    <string name="menuitem_weather">Pogoda</string>
+    <string name="menuitem_alarm">Budzik</string>
+    <string name="menuitem_timer">Odliczanie</string>
+    <string name="menuitem_compass">Kompas</string>
+    <string name="menuitem_settings">Ustawienia</string>
+    <string name="menuitem_alipay">Alipay</string>
+    <string name="devicetype_q8">Q8</string>
+    <string name="devicetype_miband3">Mi Band 3</string>
+    <string name="devicetype_mykronoz_zetime">MyKronoz ZeTime</string>
+    <string name="devicetype_id115">ID115</string>
+    <string name="menuitem_notifications">Powiadomienia</string>
+    <string name="menuitem_music">Muzyka</string>
+    <string name="devicetype_watch9">Watch 9</string>
+    <string name="watch9_calibration_hint">Ustaw czas, jaki pokazuj膮 wskaz贸wki zegarka.</string>
+    <string name="title_activity_watch9_pairing">Parowanie Watch 9</string>
+    <string name="title_activity_watch9_calibration">Kalibracja Lenovo Watch 9</string>
+    <string name="ok">OK</string>
+    <string name="devicetype_roidmi">Roidmi</string>
+    <string name="devicetype_roidmi3">Roidmi 3</string>
+    <string name="devicetype_casiogb6900">Casio GB-6900</string>
+    <string name="title_activity_notification_filter">Filtr powiadomie艅</string>
+    <string name="filter_mode_none">Nie filtruj</string>
+    <string name="filter_submode_at_least_one">Przynajmniej jedno ze s艂贸w</string>
+    <string name="filter_submode_all">Wszystkie s艂owa</string>
+    <string name="toast_notification_filter_words_empty_hint">Wprowad藕 przynajmniej jedno s艂owo</string>
+    <string name="save_configuration">Zapisz ustawienia</string>
+    <string name="activity_type_exercise">膯wiczenia</string>
+    <string name="prefs_disconnect_notification">Powiadomienie o roz艂膮czeniu</string>
+    <string name="zetime_title_settings">Ustawienia ZeTime</string>
+    <string name="zetime_title_heartrate">Ustawienia pomiaru t臋tna</string>
+    <string name="zetime_title_screentime">Czas pod艣wietlenia ekranu w sekundach</string>
+    <string name="zetime_title_heart_rate_alarm">Alarm o poziomie t臋tna</string>
+    <string name="zetime_title_heart_rate_alarm_summary">Zegarek ci臋 poinformuje, kiedy twoje t臋tno przekroczy limit.</string>
+    <string name="zetime_heart_rate_alarm_enable">W艂膮cz alarm o nieprawid艂owym poziomie t臋tna</string>
+    <string name="activity_prefs_alarm_max_heart_rate">Maksymalny poziom t臋tna</string>
+    <string name="activity_prefs_alarm_min_heart_rate">Minimalny poziom t臋tna</string>
+    <string name="zetime_analog_mode">Tryb analogowy</string>
+    <string name="zetime_analog_mode_hands">Tylko r臋ce</string>
+    <string name="zetime_analog_mode_handsandsteps">R臋ce i kroki</string>
+    <string name="zetime_activity_tracking">艢ledzenie aktywno艣ci</string>
+    <string name="zetime_handmove_display">Ruch r臋ki</string>
+    <string name="zetime_handmove_display_summary">Obr贸膰 nadgarstek, aby aktywowa膰 lub dezaktywowa膰 wy艣wietlacz.</string>
+    <string name="zetime_calories_type">Rodzaj kalorii</string>
+    <string name="zetime_date_format">Format daty</string>
+    <string name="zetime_date_format_1">YY/MM/DD</string>
+    <string name="zetime_date_format_2">DD/MM/YY</string>
+    <string name="zetime_date_format_3">MM/DD/YY</string>
+    <string name="zetime_prefs_inactivity_repetitions">Powt贸rzenia</string>
+    <string name="zetime_prefs_inactivity_mo">Poniedzia艂ek</string>
+    <string name="zetime_prefs_inactivity_tu">Wtorek</string>
+    <string name="zetime_prefs_inactivity_we">艢roda</string>
+    <string name="zetime_prefs_inactivity_th">Czwartek</string>
+    <string name="zetime_prefs_inactivity_fr">Pi膮tek</string>
+    <string name="zetime_prefs_inactivity_sa">Sobota</string>
+    <string name="zetime_prefs_inactivity_su">Niedziela</string>
+    <string name="zetime_signaling_none">Cichy</string>
+    <string name="zetime_signaling_vibrate">Ci膮g艂e wibracje</string>
+    <string name="zetime_signaling_beep">Ci膮g艂e brz臋czenie</string>
+    <string name="zetime_signaling_vibrate_beep">Ci膮g艂e wibracje i brz臋czenie</string>
+    <string name="zetime_signaling_vibrate_once">Wibruj raz</string>
+    <string name="zetime_signaling_vibrate_twice">Wibruj dwukrotnie</string>
+    <string name="pref_screen_notification_profile_missed_call">Powiadomienie o nieodebranych po艂膮czeniach</string>
+    <string name="pref_screen_notification_profile_calendar">Powiadomienie kalendarza</string>
+    <string name="pref_screen_notification_profile_inactivity">Powiadomienie o bezczynno艣ci</string>
+    <string name="pref_screen_notification_profile_low_power">Ostrze偶enie o niskim poziomie mocy</string>
+    <string name="pref_screen_notification_profile_anti_loss">Ostrze偶enie przed utrat膮</string>
+    <string name="interval_fifteen_minutes">co 15 minut</string>
+    <string name="interval_forty_five_minutes">co 45 minut</string>
+    <string name="activity_prefs_calories_burnt">Cel dzienny: spalone kalorie</string>
+    <string name="activity_prefs_distance_meters">Cel dzienny: odleg艂o艣膰 w metrach</string>
+    <string name="activity_prefs_activetime_minutes">Cel dzienny: czas aktywno艣ci w minutach</string>
+    <string name="devicetype_miscale2">Mi Scale 2</string>
+    <string name="title_activity_device_specific_settings">Ustawienia danego urz膮dzenia</string>
+    <string name="pref_title_authkey">Klucz autoryzacji</string>
+    <string name="devicetype_bfh16">BFH-16</string>
+    <string name="fw_upgrade_notice_amazfitcor2">W艂a艣nie zamierzasz zainstalowa膰 oprogramowanie uk艂adowe %s na swoim Amazfit Cor 2.
+\n
+\nProsz臋 upewni膰 si臋, 偶e zainstalowany zosta艂 plik .fw, a nast臋pnie plik .res. Po zainstalowaniu pliku .fw Tw贸j zegarek zostanie ponownie uruchomiony.
+\n
+\nUwaga: Nie musisz instalowa膰 pliku .res, je艣li jest on dok艂adnie taki sam jak ten, kt贸ry zosta艂 wcze艣niej zainstalowany.
+\n
+\nKONTYNUUJ NA W艁ASNE RYZYKO!
+\n
+\nKOMPLETNIE NIETESTOWANY, PRAWDOPODOBNIE B臉DZIESZ MUSIA艁 SFLASHOWA膯 OPROGRAMOWANIE UK艁ADOWE BEATS_W JE艢LI NAZWA TWOJEGO URZ膭DZENIA TO \"Amazfit Band 2\"</string>
+    <string name="dutch">Niderlandzki</string>
+    <string name="turkish">Turecki</string>
+    <string name="ukrainian">Ukrai艅ski</string>
+    <string name="arabic">Arabski</string>
+    <string name="indonesian">Indonezyjski</string>
+    <string name="thai">Tajski</string>
+    <string name="vietnamese">Wietnamski</string>
+    <string name="portuguese">Portugalski</string>
+    <string name="devicetype_amazfit_cor2">Amazfit Cor 2</string>
+    <string name="devicetype_miband4">Mi Band 4</string>
+    <string name="fw_upgrade_notice_miband4">W艂a艣nie zamierzasz zainstalowa膰 oprogramowanie uk艂adowe %s na swoim Mi Band 4.
+\n
+\nProsz臋 upewni膰 si臋, 偶e zainstalowany zosta艂 plik .fw, a nast臋pnie plik .res. Po zainstalowaniu pliku .fw Tw贸j zegarek zostanie ponownie uruchomiony.
+\n
+\nUwaga: Nie musisz instalowa膰 pliku .res, je艣li jest on dok艂adnie taki sam jak ten plik, kt贸ry zosta艂 wcze艣niej zainstalowany.
+\n
+\nKONTYNUUJ NA W艁ASNE RYZYKO!</string>
+    <string name="average">艢rednia: %1$s</string>
+    <string name="pref_header_charts">Ustawienia wykres贸w</string>
+    <string name="pref_title_charts_range">Zakres wykres贸w</string>
+    <string name="pref_charts_range_on">Zakres wykres贸w jest ustawiony na miesi膮c</string>
+    <string name="pref_charts_range_off">Zakres wykres贸w jest ustawiony na tydzie艅</string>
+    <string name="weekstepschart_steps_a_month">Kroki w miesi膮cu</string>
+    <string name="weeksleepchart_sleep_a_month">Sen w miesi膮cu</string>
+    <string name="devicetype_mijia_lywsd02">Mijia Smart Clock</string>
+    <string name="menuitem_nfc">NFC</string>
+    <string name="pref_summary_expose_hr">Umo偶liwia innym aplikacjom dost臋p do danych HR w czasie rzeczywistym, gdy Gadgetbridge jest pod艂膮czony</string>
+    <string name="pref_title_use_custom_font">U偶yj niestandardowej czcionki</string>
+    <string name="pref_summary_use_custom_font">W艂膮cz t臋 opcj臋, je艣li Twoje urz膮dzenie ma niestandardowy firmware czcionek do obs艂ugi emoji</string>
+    <string name="activity_db_management_autoexport_label">Automatyczny eksport</string>
+    <string name="activity_DB_ExportButton">Eksport danych</string>
+    <string name="activity_DB_import_button">Import danych</string>
+    <string name="activity_DB_test_export_message">Eksportowanie bazy danych鈥�</string>
+    <string name="activity_DB_delete_legacy_button">Usu艅 star膮 baz臋 danych</string>
+    <string name="activity_DB_empty_button">Opr贸偶nij baz臋 danych</string>
+    <string name="activity_db_management_empty_DB">Opr贸偶nienie bazy danych</string>
+    <string name="activity_db_management_exportimport_label">Eksport i Import</string>
+    <string name="activity_db_management_empty_db_warning">Ostrze偶enie! Naciskaj膮c ten przycisk wyczy艣cisz baz臋 danych i zaczniesz wszystko od nowa.</string>
+    <string name="widget_5_minutes">5 minut</string>
+    <string name="widget_10_minutes">10 minut</string>
+    <string name="widget_20_minutes">20 minut</string>
+    <string name="widget_1_hour">1 godzina</string>
+    <string name="pref_display_add_device_fab">Przycisk do po艂膮czenia z nowym urz膮dzeniem</string>
+    <string name="pref_display_add_device_fab_on">Zawsze widoczny</string>
+    <string name="pref_display_add_device_fab_off">Widoczny tylko wtedy, gdy nie dodano 偶adnego urz膮dzenia</string>
+    <string name="preferences_makibes_hr3_settings">Ustawienia Makibes HR3</string>
+    <string name="devicetype_makibes_hr3">Makibes HR3</string>
+    <string name="devicetype_amazfit_bip_lite">Amazfit Bip Lite</string>
+    <string name="prefs_find_phone">Znajd藕 telefon</string>
+    <string name="prefs_enable_find_phone">W艂膮cz \'Znajd藕 telefon\'</string>
+    <string name="prefs_find_phone_summary">U偶yj opaski, aby odtworzy膰 dzwonek telefonu.</string>
+    <string name="prefs_find_phone_duration">Czas trwania dzwonka w sekundach</string>
+    <string name="fw_upgrade_notice_amazfitbip_lite">W艂a艣nie zamierzasz zainstalowa膰 oprogramowanie uk艂adowe %s na swoim Amazfit Bip Lite.
+\n
+\nProsz臋 upewni膰 si臋, 偶e zainstalowany zosta艂 plik .fw, a nast臋pnie plik .res. Po zainstalowaniu pliku .fw Tw贸j zegarek zostanie uruchomiony ponownie.
+\n
+\nUwaga: Nie musisz instalowa膰 pliku .res , je艣li jest on dok艂adnie taki sam jak ten, kt贸ry zosta艂y zainstalowany wcze艣niej.
+\n
+\nKONTYNUUJ NA W艁ASNE RYZYKO!</string>
+    <string name="devicetype_amazfit_gtr">Amazfit GTR</string>
+    <string name="fw_upgrade_notice_amazfitgtr">W艂a艣nie zamierzasz zainstalowa膰 oprogramowanie uk艂adowe %s na swoim Amazfit GTR.
+\n
+\nProsz臋 upewni膰 si臋, 偶e zainstalowany zosta艂 plik .fw, nast臋pnie plik .res i na ko艅cu plik .gps. Po zainstalowaniu pliku .fw Tw贸j zegarek zostanie ponownie uruchomiony.
+\n
+\nUwaga: Nie musisz instalowa膰 plik贸w .res i .gps, je艣li te pliki s膮 dok艂adnie takie same jak te, kt贸re zosta艂y wcze艣niej zainstalowane.
+\n
+\nKONTYNUUJ NA W艁ASNE RYZYKO!</string>
+    <string name="pref_chart_heartrate_color_orange">Pomara艅czowy</string>
+    <string name="pref_title_chart_sleep_rolling_24_hour">Zakres snu</string>
+    <string name="pref_chart_sleep_rolling_24_on">Ostatnie 24 godziny</string>
+    <string name="pref_chart_sleep_rolling_24_off">Od po艂udnia do po艂udnia</string>
+    <string name="devicetype_amazfit_gts">Amazfit GTS</string>
+    <string name="fw_upgrade_notice_amazfitgts">W艂a艣nie zamierzasz zainstalowa膰 oprogramowanie uk艂adowe %s na swoim Amazfit GTS.
+\n
+\nProsz臋 upewni膰 si臋, 偶e zainstalowany zosta艂 plik .fw, nast臋pnie plik .res i na ko艅cu plik .gps. Po zainstalowaniu pliku .fw Tw贸j zegarek zostanie ponownie uruchomiony.
+\n
+\nUwaga: Nie musisz instalowa膰 plik贸w .res i .gps, je艣li te pliki s膮 dok艂adnie takie same jak te, kt贸re zosta艂y wcze艣niej zainstalowane.
+\n
+\nKONTYNUUJ NA W艁ASNE RYZYKO!</string>
+    <string name="devicetype_qhybrid">Fossil Q Hybrid</string>
+    <string name="preferences_qhybrid_settings">Ustawienia Q Hybrid</string>
+    <string name="pref_disable_new_ble_scanning">Wy艂膮cz nowe skanowania BLE</string>
+    <string name="pref_summary_disable_new_ble_scanning">Zaznacz t臋 opcj臋, je艣li urz膮dzenie nie mo偶e zosta膰 znalezione podczas wykrywania</string>
+    <string name="devicetype_banglejs">Bangle.js</string>
+    <string name="devicetype_y5">Y5</string>
+    <string name="alarm_snooze">Drzemka</string>
+    <string name="activity_db_management_import_export_explanation">Dzia艂anie importu/eksportu u偶ywa nast臋puj膮cej 艣cie偶ki (zobacz poni偶ej) do katalogu na urz膮dzeniu. Katalog ten jest dost臋pny dla innych aplikacji Androida i twojego komputera. Nale偶y pami臋ta膰, 偶e ten katalog i wszystkie zawarte w nim pliki zostan膮 usuni臋te, je艣li odinstalujesz Gadgetbridge.
+\n
+\nDane eksportu obejmuj膮:
+\n 鈥� Export_preference 鈥� ustawienia globalne
+\n 鈥� Export_preference_MAC 鈥� ustawienia danego urz膮dzenia
+\n 鈥� Gadgetbridge 鈥� baza danych urz膮dze艅 i aktywno艣ci
+\n 鈥� Gadgetbridge_date 鈥� baza danych wyeksportowana w danym dniu
+\n 鈥� Pliki z rozszerzeniem GPX 鈥� trasy GPS
+\n 鈥� Pliki z rozszerzeniem LOG 鈥� pliki rejestr贸w dziennika
+\n
+\nWyeksportowane pliki znajdziesz tutaj (jest to r贸wnie偶 katalog importu):</string>
+    <string name="activity_db_management_merge_old_title">Usuwanie starej bazy danych</string>
+    <string name="dbmanagementactivity_error_exporting_shared">B艂膮d podczas eksportowania preferencji: %1$s</string>
+    <string name="dbmanagementactivity_error_importing_shared">B艂膮d podczas importowania preferencji: %1$s</string>
+    <string name="weather_notification_label">Upewnij si臋, 偶e ta sk贸rka jest w艂膮czona w aplikacji \"Weather Notification\", aby uzyska膰 informacje o pogodzie na swoim Pebble.
+\n
+\nKonfiguracja nie jest tutaj wymagana.
+\n
+\nMo偶esz w艂膮czy膰 systemow膮 aplikacj臋 pogody swojego Pebble z poziomu zarz膮dzania aplikacj膮.
+\n
+\nObs艂ugiwane zegarki automatycznie wy艣wietl膮 pogod臋.</string>
+    <string name="discovery_bonding_failed_immediately">Wi膮zanie z %1$s natychmiast si臋 nie powiod艂o.</string>
+    <string name="discovery_successfully_bonded">Powi膮zano z %1$s.</string>
+    <string name="controlcenter_start_activity_tracks">艢lady aktywno艣ci</string>
+    <string name="share_log">Udost臋pnij log</string>
+    <string name="pref_title_contextual_arabic">Kontekstowy Arabski</string>
+    <string name="pref_summary_contextual_arabic">W艂膮cz t臋 opcj臋, aby obs艂ugiwa膰 kontekstowy j臋zyk arabski</string>
+    <string name="preferences_rtl_settings">Obs艂uga pisowni od prawej do lewej</string>
+    <string name="edittext_notification_filter_words_hint">Wprowad藕 偶膮dane wyrazy, ka偶dy w nowej linii</string>
+    <string name="toast_notification_filter_saved_successfully">Zapisano filtr powiadomie艅</string>
+    <string name="filter_mode_blacklist">Blokuj je艣li zawiera s艂owa</string>
+    <string name="filter_mode">Tryb filtrowania</string>
+    <string name="mode_configuration">Ustawienia trybu</string>
+    <string name="appwidget_not_connected">Nie po艂膮czono, alarm nieustawiony.</string>
+    <string name="zetime_calories_type_active">Tylko aktywnie spalone kalorie</string>
+    <string name="zetime_calories_type_all">Kalorie spalone aktywnie i nieaktywnie</string>
+    <string name="zetime_title_alarm_signaling">Ustaw typ sygnalizacji alarmu</string>
+    <string name="zetime_signaling_beep_once">Zabrz臋cz raz</string>
+    <string name="zetime_signaling_beep_twice">Zabrz臋cz dwa razy</string>
+    <string name="zetime_signaling_vibrate_beep_once">Wibracja i brz臋czenie raz</string>
+    <string name="pref_title_support_voip_calls">Uwzgl臋dnij rozmowy przez Internet (VoIP)</string>
+    <string name="pref_summary_authkey">Zmie艅 klucz autoryzacji na wsp贸lny dla wszystkich urz膮dze艅 z Androidem, z kt贸rych chcesz si臋 po艂膮czy膰. Dawniej, domy艣lnym kluczem dla wszystkich urz膮dze艅 by艂 0123456789@ABCDE</string>
+    <string name="prefs_hr_alarm_activity">Alarm t臋tna podczas aktywno艣ci sportowej</string>
+    <string name="prefs_hr_alarm_low">Dolny limit</string>
+    <string name="prefs_hr_alarm_high">G贸rny limit</string>
+    <string name="pref_title_charts_average">Poka偶 warto艣ci 艣rednie na wykresach</string>
+    <string name="pref_title_expose_hr">Dost臋p zewn臋trznych aplikacji do czujnika t臋tna</string>
+    <string name="activity_db_management_autoexport_explanation">Lokalizacja automatycznego eksportu bazy danych zosta艂a ustawiona na:</string>
+    <string name="activity_DB_test_export_button">Uruchom AutoEksport teraz</string>
+    <string name="appwidget_sleep_alarm_widget_label">Budzik</string>
+    <string name="widget_listing_label">Status i alarmy</string>
+    <string name="widget_set_alarm_after">Ustaw alarm za:</string>
+    <plurals name="widget_alarm_target_hours">
+        <item quantity="one">%d godzina</item>
+        <item quantity="few">%d godziny</item>
+        <item quantity="many">%d godzin</item>
+        <item quantity="other">%d godzin</item>
+    </plurals>
+    <string name="activity_error_no_app_for_gpx">Aby wy艣wietli膰 艣lad, zainstaluj aplikacj臋 obs艂uguj膮c膮 pliki GPX.</string>
+    <string name="maximum_duration">Czas trwania</string>
+    <string name="discovery_need_to_enter_authkey">To urz膮dzenie wymaga poufnego klucza autoryzacji, naci艣nij i przytrzymaj, aby go wprowadzi膰. Przeczytaj wiki, aby uzyska膰 wi臋cej informacji.</string>
+    <string name="pref_title_chart_heartrate_color">Kolor t臋tna</string>
+    <string name="watch_not_connected">Zegarek niepo艂膮czony</string>
+    <string name="qhybrid_vibration_strength">si艂a wibracji:</string>
+    <string name="qhybrid_goal_in_steps">Cel w krokach</string>
+    <string name="qhybrid_second_timezone_offset_relative_to_utc">przesuni臋cie drugiej strefy czasowej wzgl臋dem czasu UTC</string>
+    <string name="qhybrid_overwrite_buttons">nadpisanie przycisk贸w</string>
+    <string name="qhybrid_prompt_million_steps">Prosz臋 ustawi膰 liczb臋 krok贸w na milion, aby to aktywowa膰.</string>
+    <string name="qhybrid_buttons_overwrite_success">Przyciski zosta艂y nadpisane</string>
+    <string name="qhybrid_buttons_overwrite_error">B艂膮d podczas nadpisania przycisk贸w</string>
+    <string name="qhybrid_offset_timezone">przesuni臋cie strefy czasowej o</string>
+    <string name="qhybrid_changes_delay_prompt">zmiana mo偶e potrwa膰 kilka sekund鈥�</string>
+    <string name="qhybrid_offset_time_by">przesuni臋cie czasu o</string>
+    <string name="prefs_button_single_press_action_selection_title">Akcja zdarzenia 1</string>
+    <string name="prefs_button_double_press_action_selection_title">Akcja zdarzenia 2</string>
+    <string name="prefs_button_triple_press_action_selection_title">Akcja zdarzenia 3</string>
+    <string name="prefs_button_variable_actions">Szczeg贸艂owe ustawienia naci艣ni臋cia przycisku</string>
+    <string name="prefs_button_long_press_action_selection_title">Akcja przytrzymania przycisku</string>
+    <string name="activity_web_view">Przegl膮danie aktywno艣ci w sieci</string>
+    <string name="reset_index">Zresetuj dat臋 pobierania</string>
+    <string name="zetime_activity_tracking_summary">W艂膮czenie 艣ledzenia aktywno艣ci policzy twoje kroki i inne.</string>
+    <string name="qhybrid_time_shift">przesuni臋cie czasowe</string>
+    <string name="qhybrid_use_activity_hand_as_notification_counter">u偶yj r臋ki aktywno艣ci jako licznika powiadomie艅</string>
+    <string name="devicetype_itag">iTag</string>
+    <string name="error_no_location_access">Dost臋p do lokalizacji musi zosta膰 przyznany i w艂膮czony, aby skanowanie dzia艂a艂o poprawnie</string>
+    <string name="pref_summary_allow_high_mtu">Zwi臋ksza szybko艣膰 transferu, ale mo偶e nie dzia艂a膰 na niekt贸rych urz膮dzeniach z Androidem.</string>
+    <string name="pref_title_allow_high_mtu">Zezw贸l na wysoki MTU</string>
+    <string name="pref_summary_sync_calendar">W艂膮cza alerty kalendarza, nawet po roz艂膮czeniu</string>
+    <string name="pref_title_sync_caldendar">Synchronizuj wydarzenia kalendarza</string>
+    <string name="hr_widget_heart_rate">T臋tno</string>
+    <string name="hr_widget_steps">Kroki</string>
+    <string name="hr_widget_date">Data</string>
+    <string name="hr_widget_calories">Kalorie</string>
+    <string name="hr_widget_battery">Bateria</string>
+    <string name="hr_widget_weather">Pogoda</string>
+    <string name="hr_widget_nothing">Nic</string>
+    <string name="find_lost_device_you_found_it">Odnaleziono!</string>
+    <string name="find_my_phone_notification">Znajd藕 m贸j telefon</string>
+    <string name="pref_title_custom_deviceicon">Poka偶 specyficzn膮 dla urz膮dzenia ikon臋 powiadomienia</string>
+    <string name="hr_widget_active_minutes">Minuty aktywno艣ci</string>
+    <string name="pref_title_force_white_color_scheme">Wymu艣 schemat kolor贸w czarno na bia艂ym</string>
+    <string name="pref_summary_force_white_color_scheme">U偶yteczne, je艣li tw贸j zegarek ma ciemne wskaz贸wki</string>
+    <string name="notification_channel_high_priority_name">Wysoki priorytet</string>
+    <string name="pref_summary_custom_deviceicon">Poka偶 specyficzn膮 dla urz膮dzenia ikon臋 powiadomienia kiedy zostanie po艂膮czone zamiast ikony Gadgetbridge</string>
+    <string name="hr_appname_workout">Trening</string>
+    <string name="hr_appname_stopwatch">Stoper</string>
+    <string name="pref_title_vibration_strength">Si艂a wibracji</string>
+    <string name="devicetype_amazfit_bips">Amazfit Bip S</string>
+    <string name="pref_title_relax_firmware_checks">W艂膮cz to, je艣li chcesz sflashowa膰 oprogramowanie, kt贸re nie jest przeznaczone dla Twojego urz膮dzenia (na w艂asne ryzyko)</string>
+    <string name="pref_summary_relax_firmware_checks">Rozlu藕nij sprawdzanie oprogramowania sprz臋towego</string>
+    <string name="pref_theme_system">Zgodny z systemowym</string>
+    <string name="title_activity_LenovoWatch_calibration">Kalibracja Watch X Plus</string>
+    <string name="pref_header_auto_fetch">Automatyczne pobieranie</string>
+    <string name="power_mode_watch">Tylko zegarek</string>
+    <string name="power_mode_saving">Oszcz臋dzanie energii</string>
+    <string name="power_mode_normal">Normalny</string>
+    <string name="power_mode_title">Tryb zasilania zegarka</string>
+    <string name="prefs_sensors_button_bp_calibration_sum">Naci艣nij tu, aby rozpocz膮膰 kalibracj臋</string>
+    <string name="prefs_sensors_button_bp_calibration">Kalibracja</string>
+    <string name="pref_sensors_bp_calibration_high">Ci艣nienie krwi SYSTOLICZNE (wysokie)</string>
+    <string name="pref_sensors_bp_calibration_low">Ci艣nienie krwi DIASTOLICZNE (niskie)</string>
+    <string name="pref_sensors_bp_calibration">Kalibracja ci艣nienia krwi</string>
+    <string name="pref_title_sensors_altitude">Kalibracja wysoko艣ci</string>
+    <string name="pref_header_sensors_calibration">Kalibracja sensora</string>
+    <string name="pref_title_device_spec_settings_show_raw_graph">Poka偶 dane RAW na wykresie aktywno艣ci</string>
+    <string name="pref_summary_device_spec_settings_title_force_time">Wymu艣 automatyczn膮聽synchronizacj臋 czasu po ponownym po艂膮czeniu. Wskaz贸wki analogowe mog膮 pokazywa膰 nieprawid艂owy czas!</string>
+    <string name="pref_title_device_spec_settings_force_time">Wymu艣 synchronizacj臋聽czasu</string>
+    <string name="pref_header_device_spec_settings">Ustawienia urz膮dzenia</string>
+    <string name="pref_summary_notifications_and_calls_title_shake_reject">Duplikaty akcji przycisku zegarka</string>
+    <string name="prefs_notifications_and_calls_shake_reject">Wstrz膮艣nij nadgarstkiem aby zignorowa膰/odrzuci膰聽po艂膮czenie</string>
+    <string name="pref_summary_notifications_and_calls_title_reject">Wy艂膮czony - ignoruj, w艂膮czony - odrzu膰</string>
+    <string name="prefs_notifications_and_calls_reject">Przycisk ignoruj/odrzu膰聽po艂膮czenie</string>
+    <string name="pref_header_notifications_and_calls_callhandling">Obs艂uga po艂膮cze艅</string>
+    <string name="pref_title_notifications_and_calls_repeat_on_misscall">Powtarzaj co X minut</string>
+    <string name="pref_summary_notifications_and_calls_enable_misscall">Powtarzaj co minut臋</string>
+    <string name="pref_notifications_and_calls_enable_misscall">Powiadom o nieodebranym po艂膮czeniu</string>
+    <string name="prefs_notifications_and_calls_continious_ring">Powiadomienie podczas dzwonienia telefonu</string>
+    <string name="pref_title_notifications_and_calls_repeat_on_call">Powt贸rz powiadomienie o po艂膮czeniu</string>
+    <string name="pref_header_notifications_and_calls">Powiadomienia i po艂膮czenia</string>
+    <string name="pref_qhybrid_save_raw_activity_files">Zapisz nieprzetworzone pliki aktywno艣ci</string>
+    <string name="pref_qhybrid_title_widget_draw_circles">Narysuj ko艂a wid偶et贸w</string>
+    <string name="hr_appname_commute">Commute</string>
+    <string name="hr_appname_wellness">Wellness</string>
+    <string name="menuitem_workout">Trening</string>
+    <string name="menuitem_eventreminder">Przypomnienie o wydarzeniu</string>
+    <string name="menuitem_hr">T臋tno</string>
+    <string name="menuitem_pai">Personal Activity Intelligence</string>
+    <string name="devicetype_watchxplus">Watch X Plus</string>
+    <string name="devicetype_watchx">Watch X</string>
+    <string name="czesh">Czeski</string>
+    <string name="swedish">Szwedzki</string>
+    <string name="hebrew">Hebrajski</string>
+    <string name="greek">Grecki</string>
+    <string name="hungarian">W臋gierski</string>
+    <string name="romanian">Rumu艅ski</string>
+    <string name="menuitem_unknown">Nieznany</string>
+    <string name="bip_prefs_shotcuts_summary">Wybierz skr贸ty na wy艣wietlaczu opaski</string>
+    <string name="bip_prefs_shortcuts">Skr贸ty</string>
+    <string name="hr_widget_last_notification">Ostatnie powiadomienie</string>
+    <string name="controlcenter_set_alias">Ustaw w艂asn膮 nazw臋 (alias)</string>
+    <string name="about_contributors">Wsp贸艂tw贸rcy</string>
+    <string name="about_activity_title_generic">Informacje o Gadgetbridge</string>
+    <string name="devicetype_amazfit_trex">Amazfit T-Rex</string>
+    <string name="backstroke">Styl grzbietowy</string>
+    <string name="freestyle">Styl dowolny</string>
+    <string name="breaststroke">Styl klasyczny</string>
+    <string name="activity_filter_reset_filter">Resetuj filtr</string>
+    <string name="pref_header_statistics">Statystyki aktywno艣ci sportowej</string>
+    <string name="prefs_events_forwarding_wokeup">Po przebudzeniu si臋</string>
+    <string name="activity_detail_duration_label">Czas trwania</string>
+    <string name="Activity">Aktywno艣膰</string>
+    <string name="Speed">Pr臋dko艣膰</string>
+    <string name="bpm">bpm</string>
+    <string name="ascentSeconds">Rosn膮co</string>
+    <string name="averageLapPace">艢rednie tempo okr膮偶enia</string>
+    <string name="averageKMPaceSeconds">Tempo</string>
+    <string name="averageHR">T臋tno</string>
+    <string name="maxPace">Najszybsze tempo</string>
+    <string name="minPace">Najwolniejsze tempo</string>
+    <string name="maxSpeed">Maksimum</string>
+    <string name="minAltitude">Minimum</string>
+    <string name="maxAltitude">Maksimum</string>
+    <string name="descentMeters">Spadek</string>
+    <string name="ascentMeters">Pod g贸r臋</string>
+    <string name="distanceMeters">Dystans</string>
+    <string name="device_requires_key">WYMAGANY KLUCZ, D艁UGIE NACI艢NI臉CIE POWODUJE WEJ艢CIE</string>
+    <string name="require_location_provider">Lokalizacja musi by膰 w艂膮czona</string>
+    <string name="permission_granting_mandatory">Wszystkie te uprawnienia s膮 wymagane, a ich brak mo偶e spowodowa膰 niestabilno艣膰</string>
+    <string name="about_additional_device_support">Dodatkowe wsparcie dla urz膮dze艅</string>
+    <string name="menuitem_cycles">艢ledzenie cyklu</string>
+    <string name="menuitem_breathing">Oddychanie</string>
+    <string name="devicetype_pinetime_jf">PineTime (Firmware JF)</string>
+    <string name="activity_summary_detail">Szczeg贸艂y aktywno艣ci sportowej</string>
+    <string name="activity_type_jump_roping">Skakanka</string>
+    <string name="pref_title_weather_summary">U偶ywane przez dostawc臋 pogody LineageOS. Inne wersje systemu Android powinny u偶ywa膰 innej aplikacji, np. Weather notification. Wi臋cej informacji znajdziesz na stronie projektu Gadgetbridge.</string>
+    <string name="fw_upgrade_notice_miband5">W艂a艣nie zamierzasz zainstalowa膰 oprogramowanie uk艂adowe %s na swoim Mi Band 5.
+\n
+\nProsz臋 upewni膰 si臋, 偶e zainstalowany zosta艂 plik .fw, a nast臋pnie plik .res. Po zainstalowaniu pliku .fw Tw贸j zegarek zostanie ponownie uruchomiony.
+\n
+\nUwaga: Nie musisz instalowa膰 pliku .res, je艣li jest on dok艂adnie taki sam jak ten plik, kt贸ry zosta艂 wcze艣niej zainstalowany.
+\n
+\nKONTYNUUJ NA W艁ASNE RYZYKO!</string>
+    <string name="fw_upgrade_notice_amazfit_trex">W艂a艣nie zamierzasz zainstalowa膰 oprogramowanie uk艂adowe %s na swoim Amazfit T-Rex.
+\n
+\nProsz臋 upewni膰 si臋, 偶e zainstalowany zosta艂 plik .fw, nast臋pnie plik .res i na ko艅cu plik .gps. Po zainstalowaniu pliku .fw Tw贸j zegarek zostanie ponownie uruchomiony.
+\n
+\nUwaga: Nie musisz instalowa膰 plik贸w .res i .gps, je艣li te pliki s膮 dok艂adnie takie same jak te, kt贸re zosta艂y wcze艣niej zainstalowane.
+\n
+\nKONTYNUUJ NA W艁ASNE RYZYKO!</string>
+    <string name="activity_type_swimming_openwater">P艂ywanie (na wodach otwartych)</string>
+    <string name="activity_type_indoor_cycling">Kolarstwo halowe</string>
+    <string name="menuitem_worldclock">Czas na 艣wiecie</string>
+    <string name="menuitem_stress">Stres</string>
+    <string name="about_title">O aplikacji</string>
+    <string name="meters">m</string>
+    <string name="swim_style">styl p艂ywania</string>
+    <string name="laps_unit">okr膮偶enia</string>
+    <string name="activity_detail_end_label">Koniec</string>
+    <string name="activity_summaries_statistics">Statystyki</string>
+    <string name="activity_filter_apply_filter">Zastosuj filtr</string>
+    <string name="activity_filter_filter_title">Filtr</string>
+    <string name="activity_filter_date_from">Od</string>
+    <string name="activity_filter_date_to">Do</string>
+    <string name="activity_detail_show_gps_label">Poka偶 艣lad GPS</string>
+    <string name="Steps">Kroki</string>
+    <string name="Distance">Dystans</string>
+    <string name="Swimming">P艂ywanie</string>
+    <string name="km">km</string>
+    <string name="minutes_km">min/km</string>
+    <string name="calories_unit">kcal</string>
+    <string name="km_h">km/h</string>
+    <string name="meters_second">m/s</string>
+    <string name="cm">cm</string>
+    <string name="steps_unit">kroki</string>
+    <string name="laps">Okr膮偶enia</string>
+    <string name="caloriesBurnt">Kalorie</string>
+    <string name="steps">Kroki</string>
+    <string name="devicetype_tlw64">TLW64</string>
+    <string name="activity_type_yoga">Joga</string>
+    <string name="devicetype_miband5">Mi Band 5</string>
+    <string name="devicetype_sg2">Lemfo SG2</string>
+    <string name="activity_summary_yesterday">Wczoraj</string>
+    <string name="activity_summary_today">Dzisiaj</string>
+    <string name="medley">Zmieszany</string>
+    <string name="activity_filter_to_placeholder">dzisiaj</string>
+    <string name="sports_activity_quick_filter_30days">30 dni</string>
+    <string name="sports_activity_quick_filter_7days">7 dni</string>
+    <string name="no">Nie</string>
+    <string name="yes">Tak</string>
+    <string name="activity_detail_start_label">Pocz膮tek</string>
+    <string name="seconds_m">sek/m</string>
+    <string name="seconds_km">sek/km</string>
+    <string name="seconds">sek</string>
+    <string name="averageSpeed">艢rednia pr臋dko艣膰</string>
+    <string name="activeSeconds">Czas aktywno艣ci</string>
+    <string name="pref_check_permission_status">Sprawdzanie stanu uprawnie艅</string>
+    <string name="device_is_currently_bonded">JU呕 POWI膭ZANE</string>
+    <string name="activity_type_badminton">Badminton</string>
+    <string name="activity_type_basketball">Koszyk贸wka</string>
+    <string name="activity_type_soccer">Pi艂ka no偶na</string>
+    <string name="prefs_events_forwarding_title">Dzia艂ania dotycz膮ce urz膮dze艅</string>
+    <string name="averageStrokeDistance">艢rednia odleg艂o艣膰 ruchu</string>
+    <string name="totalStride">Ca艂kowity krok</string>
+    <string name="error_location_enabled_mandatory">Lokalizacja musi by膰 w艂膮czona w celu skanowania w poszukiwaniu urz膮dze艅</string>
+    <string name="ignore_bonded_devices_description">W艂膮czenie tej opcji spowoduje zignorowanie podczas skanowania urz膮dze艅, kt贸re zosta艂y ju偶 po艂膮czone/sparowane</string>
+    <string name="ignore_bonded_devices">Ignoruj po艂膮czone urz膮dzenia</string>
+    <string name="error_retrieving_devices_database">B艂膮d podczas pobierania urz膮dze艅 z bazy danych</string>
+    <string name="error_setting_alias">B艂膮d ustawienia aliasu:</string>
+    <string name="error_exporting_device_preferences">B艂膮d podczas eksportowania preferencji specyficznych dla urz膮dzenia</string>
+    <string name="pref_check_permission_status_summary">Sprawdzaj i popro艣 o brakuj膮ce uprawnienia, nawet je艣li nie s膮 one natychmiast potrzebne. Wy艂膮cz t臋 opcj臋 tylko wtedy, gdy Twoje urz膮dzenia faktycznie nie obs艂uguj膮 偶adnej z tych funkcji. Nieudzielenie pozwolenia mo偶e spowodowa膰 problemy!</string>
+    <string name="error_background_service_reason">Uruchomienie us艂ugi dzia艂aj膮cej w tle nie powiod艂o si臋 z powodu wyj膮tku 鈥� kliknij tutaj, aby uzyska膰 wi臋cej informacji.\n\nB艂膮d:</string>
+    <string name="error_background_service_reason_truncated">Uruchomienie us艂ugi w tle nie powiod艂o si臋, poniewa偶鈥�</string>
+    <string name="error_background_service">Nie uda艂o si臋 uruchomi膰 us艂ugi w tle</string>
+    <string name="companiondevice_pairing_details">W艂膮cza obs艂ug臋 nowego interfejsu API urz膮dze艅 towarzysz膮cych (CompanionDevice) (ma wp艂yw tylko na Android 8 lub nowszy), kt贸ry zwi臋ksza niezawodno艣膰, je艣li us艂uga b臋dzie musia艂a zosta膰 ponownie uruchomiona w tle, aby odnios艂o skutek wymaga to ponownego sparowania przy u偶yciu Gadgetbridge</string>
+    <string name="companiondevice_pairing">Parowanie urz膮dze艅 towarzysz膮cych (CompanionDevice Pairing)</string>
+    <string name="error_version_check_extreme_caution">UWAGA: B艂膮d przy sprawdzaniu informacji o wersji! Nie nale偶y kontynuowa膰! Nazwa wersji \"%s\"</string>
+    <string name="about_links">Linki</string>
+    <string name="about_additional_contributions">Dzi臋kujemy wszystkim nie notowanym na li艣cie wsp贸艂tw贸rcom za udost臋pnienie kodu, t艂umaczenia, wsparcie, pomys艂y, motywacj臋, zg艂oszenia b艂臋d贸w, pieni膮dze鈥� 鉁�</string>
+    <string name="about_core_team_title">Zesp贸艂 g艂贸wny (w kolejno艣ci od pierwszego wk艂adu w kod aplikacji)</string>
+    <string name="about_description_generic">Niewymagaj膮cy chmury wolny zamiennik dla zamkni臋to 藕r贸d艂owych aplikacji dostarczanych przez producent贸w gad偶et贸w na system Android.</string>
+    <string name="activity_summary_edit_name_title">Edytuj etykiet臋</string>
+    <string name="activity_type_pingpong">Ping-pong</string>
+    <string name="activity_type_cricket">Krykiet</string>
+    <string name="activity_type_rowing_machine">Ergometr wio艣larski</string>
+    <string name="activity_type_elliptical_trainer">Trener eliptyczny</string>
+    <string name="sonyswr12_settings_title">Ustawienia Sony SWR12</string>
+    <string name="sonyswr12_settings_low_vibration">W艂膮czono niski poziom wibracji</string>
+    <string name="sonyswr12_settings_stamina">Tryb oszcz臋dzania energii jest w艂膮czony</string>
+    <string name="sonyswr12_settings_alarm_interval">Inteligentny interwa艂 alarmu w minutach</string>
+    <string name="sonyswr12_settings_low_vibration_summary">W艂膮cz nisk膮 intensywno艣膰 wibracji na pa艣mie</string>
+    <string name="sonyswr12_settings_stamina_summary">Tryb oszcz臋dzania energii wy艂膮cza okresowe automatyczne pomiary t臋tna, co wyd艂u偶a czas pracy</string>
+    <string name="sonyswr12_settings_alarm_interval_summary">Interwa艂 inteligentnego alarmu to interwa艂 przed zainstalowanym alarmem. W tym interwale urz膮dzenie pr贸buje wykry膰 najl偶ejsz膮 faz臋 snu, aby obudzi膰 u偶ytkownika</string>
+    <string name="prefs_events_forwarding_fellsleep">Po za艣ni臋ciu</string>
+    <string name="lefun_prefs_hydration_reminder_summary">Opaska zawibruje, aby przypomnie膰 ci o piciu wody</string>
+    <string name="gps_track">艢cie偶ka GPS</string>
+    <string name="descentSeconds">Czas zej艣cia</string>
+    <string name="chart_no_active_data">Brak aktywno艣ci.</string>
+    <string name="qhybrid_calibration_align_hint">U偶yj poni偶szych przycisk贸w, aby ustawi膰 wskaz贸wki zegara na 12:00.</string>
+    <string name="sports_activity_quick_filter_select">Okres czasu</string>
+    <string name="addto_filter">Dodaj do filtru</string>
+    <string name="activity_filter_name_contains">Etykieta</string>
+    <string name="pref_header_filter">Filtr aktywno艣ci sportowych</string>
+    <string name="prefs_events_forwarding_startnonwear">Podczas nienoszenia</string>
+    <string name="Elevation">Wysoko艣膰</string>
+    <string name="strokes_unit">ruchy</string>
+    <string name="swimStyle">Styl p艂ywania</string>
+    <string name="averageStrokesPerSecond">艢rednia liczba ruch贸w</string>
+    <string name="pref_title_lower_button_function_double">Dwukrotne naci艣ni臋cie dolnego przycisku</string>
+    <string name="pref_title_middle_button_function_double">Dwukrotne naci艣ni臋cie 艣rodkowego przycisku</string>
+    <string name="pref_title_upper_button_function_double">Dwukrotne naci艣ni臋cie g贸rnego przycisku</string>
+    <string name="pref_title_lower_button_function_long">D艂ugie naci艣ni臋cie dolnego przycisku</string>
+    <string name="pref_title_middle_button_function_long">D艂ugie naci艣ni臋cie 艣rodkowego przycisku</string>
+    <string name="pref_title_upper_button_function_long">D艂ugie naci艣ni臋cie g贸rnego przycisku</string>
+    <string name="pref_title_lower_button_function_short">Kr贸tkie naci艣ni臋cie dolnego przycisku</string>
+    <string name="pref_title_middle_button_function_short">Kr贸tkie naci艣ni臋cie 艣rodkowego przycisku</string>
+    <string name="pref_title_upper_button_function_short">Kr贸tkie naci艣ni臋cie g贸rnego przycisku</string>
+    <string name="activity_error_no_app_for_png">Aby udost臋pni膰 ten zrzut ekranu, zainstaluj aplikacj臋, kt贸ra obs艂uguje pliki graficzne.</string>
+    <string name="activity_prefs_chart_min_session_length">Minimalna d艂ugo艣膰 aktywno艣ci (w minutach)</string>
+    <string name="activity_prefs_chart_min_steps_per_minute">Minimalna liczba krok贸w na minut臋, aby wykry膰 aktywno艣膰</string>
+    <string name="activity_prefs_chart_min_steps_per_minute_for_run">Minimalna liczba krok贸w na minut臋, aby wykry膰 bieganie</string>
+    <string name="lefun_prefs_antilost_title">Zapobieganie utracie</string>
+    <string name="activity_prefs_step_length_cm">D艂ugo艣膰 kroku w cm</string>
+    <string name="lefun_prefs_interface_language_title">J臋zyk interfejsu</string>
+    <string name="lefun_prefs_antilost_summary">Opaska zawibruje, je艣li telefon roz艂膮czy si臋 z opask膮</string>
+    <string name="lefun_prefs_hydration_reminder_interval_title">Okres przypominania o nawodnieniu (w minutach)</string>
+    <string name="charts_activity_list">Lista aktywno艣ci</string>
+    <string name="chart_get_active_and_synchronize">Wykonaj jak膮艣 czynno艣膰 i zsynchronizuj urz膮dzenie.</string>
+    <string name="devicestatus_disconnecting">Urz膮dzenie si臋 roz艂膮cza!</string>
+    <string name="devicestatus_upload_started">Rozpocz臋to przesy艂anie</string>
+    <string name="devicestatus_upload_starting">Rozpoczynanie przesy艂ania</string>
+    <string name="menuitem_spo2">Saturacja (SpO2)</string>
+    <string name="charts_legend_heartrate_average">艢rednie t臋tno</string>
+    <string name="charts_min_max_heartrate_popup">Najni偶sze t臋tno: %1$d
+\nNajwy偶sze t臋tno: %2$d
+\nIntensywno艣膰 ruchu: %3$s</string>
+    <string name="firmware_update_progress">Trwa przesy艂anie
+\n%1d%% na %.2fkbps (艣rednio %.2fkbps)
+\nCz臋艣膰 %1d z %1d</string>
+    <string name="devicestatus_upload_failed">Przesy艂anie nie powiod艂o si臋</string>
+    <string name="devicestatus_upload_aborted">Przesy艂anie zosta艂o przerwane!</string>
+    <string name="devicestatus_upload_completed">Przesy艂anie zosta艂o zako艅czone</string>
+    <string name="devicestatus_disconnected">Urz膮dzenie zosta艂o roz艂膮czone!</string>
+    <string name="devicestatus_connected">Urz膮dzenie jest po艂膮czone</string>
+    <string name="devicestatus_connecting">Urz膮dzenie si臋 艂膮czy</string>
+    <string name="strokes_second">ruchy/s</string>
+    <string name="swolfIndex">SWOLF</string>
+    <string name="devicetype_nut_mini">Nut mini</string>
+    <string name="activity_summaries_all_devices">Wszystkie urz膮dzenia</string>
+    <string name="sports_activity_quick_filter_last_month">Poprzedni miesi膮c</string>
+    <string name="sports_activity_quick_filter_this_month">Ten miesi膮c</string>
+    <string name="sports_activity_quick_filter_last_week">Poprzedni tydzie艅</string>
+    <string name="sports_activity_quick_filter_this_week">Ten tydzie艅</string>
+    <string name="activity_summaries_all_activities">Wszystkie aktywno艣ci</string>
+    <string name="Strokes">Ruchy</string>
+    <string name="swolf_index">indeks swolf</string>
+    <string name="lefun_prefs_hydration_reminder_title">Przypomnienie o nawodnieniu</string>
+    <string name="about_version">Wersja %s</string>
+    <string name="devicetype_lefun">Lefun</string>
+    <string name="devicetype_amazfit_bips_lite">Amazfit Bip S Lite</string>
+    <string name="devicetype_amazfit_band5">Amazfit Band 5</string>
+    <string name="flatSeconds">P艂asko</string>
+    <string name="averageStride">艢redni krok</string>
+    <string name="activity_filter_from_placeholder">odleg艂a przesz艂o艣膰</string>
+    <string name="activity_filter_individual_items">Indywidualnie wybrane elementy</string>
+    <string name="devicetype_amazfit_gtr2">Amazfit GTR 2</string>
+    <string name="activity_prefs_chart_max_idle_phase_length">D艂ugo艣膰 przerwy do oddzielenia aktywno艣ci (w minutach)</string>
+    <string name="devicestatus_upload_validating">Przes艂any plik jest sprawdzany</string>
+    <string name="find_lost_device_message">Szuka膰 %1$s\?</string>
+    <string name="menuitem_stopwatch">Stoper</string>
+    <string name="menuitem_dnd">Nie przeszkadza膰</string>
+    <string name="menuitem_alexa">Alexa</string>
+    <string name="menuitem_takephoto">Zdalna kamera</string>
+    <string name="menuitem_mutephone">Wycisz telefon</string>
+    <string name="menuitem_findphone">Znajd藕 telefon</string>
+    <string name="activity_list_summary_distance">Dystans</string>
+    <string name="devicetype_amazfit_gts2">Amazfit GTS 2</string>
+    <string name="activity_list_summary_active_steps">Kroki podczas aktywno艣ci</string>
+    <string name="movement_intensity">Intensywno艣膰 ruchu</string>
+    <string name="activity_list_summary_intensity">Intensywno艣膰
+\nRuchu</string>
+    <string name="activity_list_summary_active_time">Czas aktywno艣ci</string>
+    <string name="prefs_events_forwarding_action_title">Uruchom akcj臋</string>
+    <string name="devicetype_casiogbx100">Casio GBX-100</string>
+    <string name="activity_list_summary_activities">Aktywno艣ci</string>
+    <string name="prefs_charts_tabs_summary">Widoczne karty wykres贸w</string>
+    <string name="prefs_charts_tabs">Karty wykres贸w</string>
+    <string name="devicetype_amazfit_bipu">Amazfit Bip U</string>
+    <string name="menuitem_pomodoro">Minutnik Pomodoro</string>
+    <string name="menuitem_sleep">Sen</string>
+    <string name="baseAltitude">Bazowa wysoko艣膰</string>
+    <string name="menuitem_goal">Cel dotycz膮cy aktywno艣ci</string>
+    <string name="pref_title_connected_advertisement">Widoczne po pod艂膮czeniu</string>
+    <string name="pref_summary_connected_advertisement">Sprawia, 偶e urz膮dzenie jest wykrywalne przez Bluetooth, nawet po po艂膮czeniu</string>
+    <string name="fw_upgrade_notice_amazfitvergel">W艂a艣nie zamierzasz zainstalowa膰 oprogramowanie uk艂adowe %s na swoim Amazfit Verge Lite.
+\n
+\nProsz臋 upewni膰 si臋, 偶e zainstalowany zosta艂 plik .fw, nast臋pnie plik .res i na ko艅cu plik .gps. Po zainstalowaniu pliku .fw Tw贸j zegarek zostanie ponownie uruchomiony.
+\n
+\nUwaga: Nie musisz instalowa膰 plik贸w .res i .gps, je艣li te pliki s膮 dok艂adnie takie same jak te, kt贸re zosta艂y wcze艣niej zainstalowane.
+\n
+\nKONTYNUUJ NA W艁ASNE RYZYKO!</string>
+    <string name="devicetype_amazfit_vergel">Amazfit Verge Lite</string>
+    <string name="prefs_autoremove_message">Automatycznie usuwaj powiadomienia SMS</string>
+    <string name="discovery_entered_invalid_authkey">Wprowadzony klucz autoryzacji jest nieprawid艂owy! Naci艣nij i przytrzymaj, aby edytowa膰.</string>
+    <string name="prefs_events_forwarding_broadcast_title">Tre艣膰 emisji (broadcast)</string>
+    <string name="prefs_events_forwarding_summary">U偶ywanie zdarze艅 urz膮dzenia do wyzwalania dzia艂a艅 i emisji (broadcast) Androida</string>
+    <string name="activity_DB_ShowContentButton">Poka偶 zawarto艣膰 katalogu eksportu/importu</string>
+    <string name="activity_data_management_directory_content_title">Eksportuj/Importuj zawarto艣膰 katalogu</string>
+    <string name="prefs_fake_ring_duration">Fa艂szywe ci膮g艂e dzwonienie</string>
+    <string name="prefs_operating_sounds">D藕wi臋ki funkcjonowania</string>
+    <string name="prefs_key_vibration">Wibracja przycisk贸w</string>
+    <string name="prefs_autolight">Automatyczne pod艣wietlenie</string>
+    <string name="widget_settings_select_device_title">Wybierz urz膮dzenie</string>
+    <string name="devicetype_amazfit_bipupro">Amazfit Bip U Pro</string>
+    <string name="dbmanagementactivity_export_confirmation">Czy naprawd臋 wyeksportowa膰 dane\? Wyeksportowane wcze艣niej dane o aktywno艣ciach (je艣li takie istniej膮) i ustawienia zostan膮 nadpisane.</string>
+    <string name="dbmanagementactivity_export_data_title">Wyeksportowa膰 dane\?</string>
+    <string name="activity_db_management_clean_export_directory_text">Eksportowane pliki w katalogu eksportu/importu dost臋pne s膮 dla ka偶dej aplikacji na tym urz膮dzeniu. Mo偶esz chcie膰 usun膮膰 te dane po synchronizacji lub utworzeniu kopii zapasowej. Upewnij si臋, 偶e posiadasz kopi臋 zapasow膮 tych danych przed ich usuni臋ciem. Pliki GPX, podkatalogi i automatycznie wyeksportowane pliki bazy danych (je艣li istniej膮) nie zostan膮 usuni臋te.
+\n
+\n艢cie偶ka do katalogu eksportu/importu:</string>
+    <string name="activity_db_management_clean_export_directory_label">Usuwanie plik贸w z katalogu eksportu/importu</string>
+    <string name="dbmanagementactivity_export_finished">Usuwanie uko艅czone</string>
+    <string name="activity_DB_clean_export_directory_warning_message">Czy na pewno usun膮膰 pliki z katalogu eksportu/importu\?</string>
+    <string name="activity_DB_clean_export_directory_warning_title">Usun膮膰 pliki z katalogu eksportu/importu\?</string>
+    <string name="dbmanagementactivity_error_cleaning_export_directory">Wyst膮pi艂 b艂膮d podczas usuwania plik贸w z katalogu eksportu %1$s</string>
+    <string name="devicetype_amazfit_neo">Amazfit Neo</string>
+    <string name="minutes_mi">min/mil</string>
+    <string name="mi_h">mil/h</string>
+    <string name="ft">stopa</string>
+    <string name="mi">mi</string>
+    <string name="activity_type_strength_training">Trening si艂owy</string>
+    <string name="devicetype_zepp_e">Zepp E</string>
+    <string name="devicetype_amazfit_gts2_mini">Amazfit GTS 2 Mini</string>
+    <string name="devicetype_amazfit_gtr2e">Amazfit GTR 2e</string>
+    <string name="devicetype_waspos">Wasp-os</string>
+    <string name="devicetype_amazfit_gts2e">Amazfit GTS 2e</string>
+    <string name="battery_detail_activity_title">Informacje o baterii</string>
+    <string name="devicetype_amazfit_x">Amazfit X</string>
+    <string name="fw_upgrade_notice_amazfitx">W艂a艣nie zamierzasz zainstalowa膰 oprogramowanie uk艂adowe %s na Twoim Amazfit X.
+\n
+\nProsz臋 upewni膰 si臋, 偶e zainstalowany zosta艂 plik .fw, a nast臋pnie plik .res. Po zainstalowaniu pliku .fw Twoja opaska zostanie ponownie uruchomiona.
+\n
+\nUwaga: Nie musisz instalowa膰 pliku .res, je艣li jest on dok艂adnie taki sam jak plik, kt贸ry zosta艂 wcze艣niej zainstalowany.
+\n
+\nKONTYNUUJ NA W艁ASNE RYZYKO!</string>
+    <string name="calendar_year">Rok</string>
+    <string name="calendar_six_months">6 miesi臋cy</string>
+    <string name="calendar_month">Miesi膮c</string>
+    <string name="calendar_two_weeks">Dwa tygodnie</string>
+    <string name="calendar_week">Tydzie艅</string>
+    <string name="calendar_day">Dzie艅</string>
+    <string name="battery_level">Poziom baterii</string>
+    <string name="show_ongoing_activity">Poka偶 wyskakuj膮ce okienko aktywno艣ci</string>
+    <string name="dialog_hide">Ukryj</string>
+    <string name="calendar_three_months">3 miesi膮ce</string>
+    <string name="fossil_hr_auth_failed">Uwierzytelnianie nie powiod艂o si臋, ograniczona funkcjonalno艣膰</string>
+    <string name="fossil_hr_unavailable_unauthed">Niedost臋pne w trybie bez uwierzytelnienia</string>
+    <string name="fossil_hr_synced_activity_data">Zsynchronizowane dane dotycz膮ce aktywno艣ci</string>
+    <string name="fossil_hr_commute_processing">Przetwarzanie 偶膮dania鈥�</string>
+    <string name="qhybrid_title_calibration">Kalibracja</string>
+    <string name="qhybrid_title_apps_management">Zarz膮dzanie aplikacjami</string>
+    <string name="qhybrid_title_file_management">Zarz膮dzanie plikami</string>
+    <string name="qhybrid_title_background_image">Obraz t艂a</string>
+    <string name="qhybrid_title_apps">Aplikacje</string>
+    <string name="devicetype_um25">UM-25</string>
+    <string name="fossil_hr_warning_firmware_too_new">Niekt贸re funkcje s膮 wy艂膮czone, poniewa偶 oprogramowanie zegarka jest zbyt nowe</string>
+    <string name="prefs_sounds_summary">Skonfiguruj, kiedy urz膮dzenie b臋dzie emitowa膰 sygna艂 d藕wi臋kowy</string>
+    <string name="prefs_sounds">D藕wi臋ki</string>
+    <string name="fw_upgrade_notice_amazfitneo">W艂a艣nie zamierzasz zainstalowa膰 oprogramowanie uk艂adowe %s na Twoim Amazfit Neo
+\n
+\nPo zainstalowaniu pliku .fw Twoja opaska zostanie ponownie uruchomiona.
+\n
+\nKONTYNUUJ NA W艁ASNE RYZYKO!</string>
+    <string name="menuitem_events">Wydarzenia</string>
+    <string name="menuitem_widgets">Wid偶ety</string>
+    <string name="menuitem_temperature">Temperatura</string>
+    <string name="devicetype_miband6">Mi Band 6</string>
+    <string name="fw_upgrade_notice_miband6">W艂a艣nie zamierzasz zainstalowa膰 oprogramowanie uk艂adowe %s na swoim Mi Band 6.
+\n
+\nProsz臋 upewni膰 si臋, 偶e zainstalowany zosta艂 plik .fw, a nast臋pnie plik .res. Po zainstalowaniu pliku .fw Tw贸j zegarek zostanie ponownie uruchomiony.
+\n
+\nUwaga: Nie musisz instalowa膰 pliku .res, je艣li jest on dok艂adnie taki sam jak ten plik, kt贸ry zosta艂 wcze艣niej zainstalowany.
+\n
+\nKONTYNUUJ NA W艁ASNE RYZYKO!</string>
+    <string name="qhybrid_summary_calibration">Kalibracja wskaz贸wek zegarka</string>
+    <string name="pref_title_physical_buttons">Przyciski fizyczne</string>
+    <string name="pref_summary_canned_messages_set">Wy艣lij skonfigurowane poni偶ej wiadomo艣ci na swoje urz膮dzenie</string>
+    <string name="pref_summary_canned_messages_dismisscall">Odrzucaj po艂膮czenia na zegarku z wiadomo艣ci膮 SMS</string>
+    <string name="qhybrid_summary_file_management">Przesy艂anie i pobieranie plik贸w</string>
+    <string name="pref_summary_physical_buttons">Konfiguracja funkcji przycisk贸w fizycznych zegarka</string>
+    <string name="kind_agps_bundle">Pakiet AGPS</string>
+    <string name="qhybrid_calibration_100_steps">100 krok贸w</string>
+    <string name="qhybrid_calibration_10_steps">10 krok贸w</string>
+    <string name="qhybrid_calibration_1_step">1 krok</string>
+    <string name="qhybrid_calibration_clockwise">Zgodnie z ruchem wskaz贸wek zegara</string>
+    <string name="qhybrid_calibration_counterclockwise">Przeciwnie do ruchu wskaz贸wek zegara</string>
+    <string name="fossil_hr_new_action">Nowe dzia艂anie</string>
+    <string name="fossil_hr_new_action_cancel">anuluj</string>
+    <string name="fossil_hr_edit_action">Edytuj dzia艂anie</string>
+    <string name="fossil_hr_edit_action_delete">usu艅</string>
+    <string name="watchface_install_info">Zamierzasz zainstalowa膰 nast臋puj膮c膮 tarcz臋 zegarka:
+\n
+\n%1$s
+\nWersja %2$s od %3$s
+\n</string>
+    <string name="qhybrid_watchface_configuration_old_firmware">Ekran konfiguracji tarczy zegarka dla zegark贸w z oprogramowaniem uk艂adowym w wersji DN1.0.2.19r i ni偶szym</string>
+    <string name="fossil_hr_commute_actions_explanation">Dzia艂ania skonfigurowane w tym miejscu b臋d膮 wy艣wietlane na twoim zegarku w aplikacji Commute. Wi臋cej informacji znajdziesz na stronie projektu.</string>
+    <string name="qhybrid_title_watchface">Konfiguracja tarczy zegarka</string>
+    <string name="notification_channel_low_battery_name">Niski poziom na艂adowania baterii</string>
+    <string name="notification_channel_transfer_name">Transfer danych</string>
+    <string name="devicetype_amazfit_trex_pro">Amazfit T-Rex Pro</string>
+    <string name="qhybrid_pref_title_external_intents">Zezwalaj na niebezpieczne intencje zewn臋trzne</string>
+    <string name="qhybrid_pref_summary_actions">Dzia艂ania aplikacji Commute</string>
+    <string name="qhybrid_pref_title_actions">Dzia艂ania</string>
+    <string name="qhybrid_pref_summary_external_intents">Umo偶liwia innym aplikacjom Androida na przesy艂anie/nadpisywanie plik贸w</string>
+    <string name="pref_summary_developer_settings">Ustawienia i funkcje wykorzystywane przez programist贸w</string>
+    <string name="pref_title_developer_settings">Opcje programistyczne</string>
+    <string name="menuitem_barometer">Barometr</string>
+    <string name="pref_theme_black_background">U偶ywaj czarnego t艂a w ciemnym motywie</string>
+    <string name="activity_summary_detail_editing_gpx_track">Edycja po艂膮czonej 艣cie偶ki GPX</string>
+    <string name="set">Ustaw</string>
+    <string name="gpx_receiver_files_received">Otrzymane pliki GPX:</string>
+    <string name="gpx_receiver_overwrite_some_files">Niekt贸re pliki ju偶 istniej膮. Nadpisa膰\?</string>
+    <string name="gpx_receiver_activity_title">Odbiornik GPX Gadgetbridge</string>
+    <string name="activity_summary_detail_clear_gpx_track">Wyczy艣膰 tras臋 GPX</string>
+    <string name="activity_summary_detail_select_gpx_track">Wybierz tras臋 GPX</string>
+    <string name="watchface_dialog_widget_color">Kolor</string>
+    <string name="appmanager_app_edit">Edytuj</string>
+    <string name="button_watchface_save_apply">Zapisz i zastosuj</string>
+    <string name="watchface_widget_type_heart_rate">T臋tno</string>
+    <string name="watchface_widget_type_steps">Kroki</string>
+    <string name="watchface_widget_type_weather">Pogoda</string>
+    <string name="watchface_widget_type_date">Data</string>
+    <string name="watchface_dialog_title_add_widget">Dodaj widget</string>
+    <string name="button_watchface_add_widget">Dodaj widget</string>
+    <string name="button_watchface_select_image">Zmie艅 obraz t艂a</string>
+    <string name="button_watchface_edit_name">Edytuj nazw臋</string>
+    <string name="devicetype_smaq2oss">SMA-Q2 OSS</string>
+    <string name="watchface_setting_wrist_flick_minute">Wskaz贸wka minutowa (-360 do 360)</string>
+    <string name="watchface_setting_wrist_flick_hour">Wskaz贸wka godzinowa (-360 do 360)</string>
+    <string name="watchface_setting_display_refresh_partial">Cz臋艣ciowe od艣wie偶enie (w minutach)</string>
+    <string name="watchface_setting_display_refresh_full">Pe艂ne od艣wie偶enie (w minutach)</string>
+    <string name="watchface_dialog_widget_color_black">Czarny</string>
+    <string name="watchface_dialog_widget_color_white">Bia艂y</string>
+    <string name="watchface_dialog_widget_preset_right">Prawa</string>
+    <string name="watchface_dialog_widget_preset_left">Lewa</string>
+    <string name="watchface_dialog_widget_preset_bottom">D贸艂</string>
+    <string name="watchface_dialog_widget_preset_top">G贸ra</string>
+    <string name="watchface_setting_wrist_flick_move_relative">Przesuwanie wskaz贸wek wzgl臋dem czasu</string>
+    <string name="watchface_setting_title_display_refresh_timeout">Limit czasu od艣wie偶ania wy艣wietlacza</string>
+    <string name="watchface_upload_failed">Wgranie tarczy zegarka nieudane. Prosz臋 spr贸bowa膰 ponownie.</string>
+    <string name="watchface_cache_confirm_overwrite">Tarcza zegarka o tej nazwie istnieje ju偶 w pami臋ci podr臋cznej. Czy chcesz j膮 nadpisa膰\?</string>
+    <string name="watchface_setting_wrist_flick_duration">Czas trwania (w ms)</string>
+    <string name="watchface_setting_desc_wrist_flick">(aby ca艂kowicie wy艂膮czy膰, w艂膮cz ruch wzgl臋dny i ustaw wszystkie warto艣ci na 0)</string>
+    <string name="watchface_setting_title_wrist_flick">Ruch nadgarstkiem</string>
+    <string name="watchface_dialog_title_settings">Ustawienia tarczy zegarka</string>
+    <string name="qhybrid_title_watchface_designer">Projektant tarczy zegarka</string>
+    <string name="watchface_dialog_widget_presets">Ustawienia wst臋pne pozycji</string>
+    <string name="watchface_dialog_widget_y_coordinate">Wsp贸艂rz臋dna Y (maks. 240)</string>
+    <string name="watchface_dialog_widget_x_coordinate">Wsp贸艂rz臋dna X (maks. 240)</string>
+    <string name="watchface_dialog_widget_type">Rodzaj</string>
+    <string name="button_watchface_preview">Podgl膮d na zegarku</string>
+    <string name="watchface_toast_settings_incomplete">Ustawienia niekompletne, wid偶et nie zosta艂 dodany</string>
+    <string name="watchface_dialog_title_set_name">Ustaw nazw臋 tarczy zegarka</string>
+    <string name="button_watchface_settings">Ustawienia tarczy zegarka</string>
+    <string name="heart_rate_result">Wyniki pomiaru</string>
+    <string name="getting_heart_rate">Pomiar</string>
+    <string name="blood_pressure">Ci艣nienie krwi</string>
+    <string name="watchface_setting_title_power_saving">Oszcz臋dzanie energii</string>
+    <string name="watchface_widget_type_chance_rain">Szansa na deszcz</string>
+    <string name="watchface_widget_type_active_mins">Minuty aktywno艣ci</string>
+    <string name="watchface_widget_type_2nd_tz">Druga strefa czasowa</string>
+    <string name="watchface_widget_type_calories">Kalorie</string>
+    <string name="watchface_widget_type_battery">Bateria</string>
+    <string name="pref_summary_huami_force_new_protocol">W艂膮cz, je艣li urz膮dzenie nie 艂膮czy si臋 po aktualizacji oprogramowania uk艂adowego</string>
+    <string name="pref_title_huami_force_new_protocol">Nowy protok贸艂 uwierzytelnienia</string>
+    <string name="watchface_setting_power_saving_hands">Wy艂膮cz ruch wskaz贸wek po zdj臋ciu zegarka</string>
+    <string name="watchface_setting_power_saving_display">Wy艂膮cz aktualizacje wy艣wietlacza po zdj臋ciu zegarka</string>
+    <string name="menuitem_flashlight">Latarka</string>
+    <string name="pref_title_ping_tone">D藕wi臋k pingu</string>
+    <string name="prefs_sleep_time_summary">Okre艣la godziny, w kt贸rych rejestrowany jest sen</string>
+    <string name="prefs_sleep_time_label">Okre艣l godziny snu</string>
+    <string name="prefs_sleep_time">Godziny snu</string>
+    <string name="prefs_autoheartrate_interval">Cz臋stotliwo艣膰 pomiar贸w</string>
+    <string name="prefs_notifications_enable">W艂膮cz powiadomienia</string>
+    <string name="prefs_vibration_enable">W艂膮cz wibracje</string>
+    <string name="devicetype_fitpro">FitPro</string>
+    <string name="prefs_autoheartrate_summary">Okresowe pomiary t臋tna w ci膮gu dnia, a tak偶e podczas snu</string>
+    <string name="devicetype_nothingear1">Nothing Ear (1)</string>
+    <string name="prefs_autoheartrate_sleep">Wykonuj pomiary podczas snu</string>
+    <string name="prefs_autoheartrate_measurement">Automatyczne pomiary t臋tna</string>
+    <string name="prefs_autoheartrate">Automatyczny pomiar t臋tna</string>
+    <string name="enable_notifications_summary">Powiadomienia o po艂膮czeniach, wiadomo艣ciach i innych</string>
+    <string name="enable_vibrations_summary">Wibracje dla po艂膮cze艅, wiadomo艣ci, powiadomie艅 i innych</string>
+    <string name="nothing_prefs_inear_summary">Odtw贸rz/wstrzymaj muzyk臋 w zale偶no艣ci od tego, czy nosisz s艂uchawki</string>
+    <string name="nothing_prefs_inear_title">Wykrywanie w艂o偶enia s艂uchawki do ucha</string>
+    <string name="nothing_prefs_audiomode_title">Tryb audio</string>
+    <string name="pref_header_notification_application_settings">Ustawienia dla poszczeg贸lnych aplikacji</string>
+    <string name="pref_title_notification_use_as_allow">Zezw贸l na powiadomienia z wybranych aplikacji</string>
+    <string name="pref_applications_settings">Lista aplikacji</string>
+    <string name="uncheck_all_applications">Odznacz wszystkie aplikacje</string>
+    <string name="title_activity_notification_management">Ustawienia powiadomie艅</string>
+    <string name="check_all_applications">Zaznacz wszystkie aplikacje</string>
+    <string name="pref_title_notification_use_as">U偶yj listy aplikacji, aby鈥�</string>
+    <string name="pref_title_message_privacy_mode">Tryb prywatno艣ci wiadomo艣ci</string>
+    <string name="pref_message_privacy_mode_off">Wy艣wietl ca艂膮 zawarto艣膰</string>
+    <string name="pref_title_notification_use_as_deny">Odrzucaj powiadomienia z wybranych aplikacji</string>
+    <string name="pref_message_privacy_mode_complete">Ukryj ca艂膮 zawarto艣膰</string>
+    <string name="devicetype_galaxybuds">Galaxy Buds</string>
+    <string name="prefs_equalizer_preset">Ustawienia wst臋pne korektora d藕wi臋ku</string>
+    <string name="pref_title_equalizer_bass_boost">Podbicie bas贸w</string>
+    <string name="pref_title_equalizer_soft">Mi臋kkie</string>
+    <string name="pref_title_equalizer_trebble">Podbicie ton贸w wysokich</string>
+    <string name="prefs_dolby_mode">Tryb Dolby</string>
+    <string name="prefs_equalizer">Korektor d藕wi臋ku</string>
+    <string name="prefs_equalizer_summary">W艂膮cz lub wy艂膮cz korektor d藕wi臋ku</string>
+    <string name="prefs_dolby_summary">Wst臋pne ustawienia Dolby dla korektora d藕wi臋ku</string>
+    <string name="prefs_game_mode">Tryb gry</string>
+    <string name="prefs_touch_lock">Blokada dotykowa</string>
+    <string name="prefs_ambient_volume">G艂o艣no艣膰 otoczenia</string>
+    <string name="prefs_ambient_voice_summary">Spraw, by g艂os si臋 wyr贸偶nia艂</string>
+    <string name="prefs_ambient_sound">D藕wi臋k otoczenia</string>
+    <string name="prefs_ambient_mode">Tryb otoczenia</string>
+    <string name="prefs_left">Lewa</string>
+    <string name="prefs_ambient_voice_focus">Skupienie g艂osu</string>
+    <string name="prefs_right">Prawa</string>
+    <string name="prefs_galaxy_touch_options">Opcje dotykowe</string>
+    <string name="prefs_galaxy_buds_experimental">Eksperymentalne</string>
+    <string name="pref_title_equalizer_clear">Czyste</string>
+    <string name="toast_app_must_be_selected">Aplikacja musi by膰 wybrana, aby mog艂a by膰 skonfigurowana</string>
+    <string name="prefs_game_mode_summary">Tylko je艣li Tw贸j telefon obs艂uguje tryb gry</string>
+    <string name="prefs_touch_lock_summary">Wy艂膮cz zdarzenia dotykowe</string>
+    <string name="pref_title_equalizer_dynamic">Dynamiczne</string>
+    <string name="toast_app_must_not_be_selected">Aplikacja nie mo偶e by膰 wybrana, aby mog艂a by膰 skonfigurowana</string>
+    <string name="left_earbud">Lewa s艂uchawka</string>
+    <string name="battery_case">Etui 艂aduj膮ce</string>
+    <string name="right_earbud">Prawa s艂uchawka</string>
+    <string name="devicetype_galaxybuds_live">Galaxy Buds Live</string>
+    <string name="sony_automatic_power_off_1_hour">1 godzina</string>
+    <string name="sony_equalizer_band_400">400</string>
+    <string name="sony_equalizer_band_1000">1k</string>
+    <string name="sony_equalizer_band_2500">2.5k</string>
+    <string name="sony_equalizer_band_6300">6.3k</string>
+    <string name="sony_equalizer_band_16000">16k</string>
+    <string name="sony_automatic_power_off_30_min">30 minut</string>
+    <string name="sony_automatic_power_off_3_hour">3 godziny</string>
+    <string name="devicetype_sony_wh_1000xm3">Sony WH-1000XM3</string>
+    <string name="sony_automatic_power_off_5_min">5 minut</string>
+    <string name="reminder_date">Data</string>
+    <string name="reminder_time">Czas</string>
+    <string name="reminder_message">Wiadomo艣膰</string>
+    <string name="sony_equalizer_preset_speech">Mowa</string>
+    <string name="sony_equalizer_preset_bass_boost">Podbicie basu</string>
+    <string name="sony_surround_mode_club">Klub</string>
+    <string name="sony_equalizer_preset_vocal">Wokal</string>
+    <string name="pref_header_equalizer">Korektor d藕wi臋ku</string>
+    <string name="watchface_dialog_widget_timezone">Strefa czasowa</string>
+    <string name="sony_equalizer_preset_off">Wy艂膮czony</string>
+    <string name="sony_equalizer">Korektor d藕wi臋ku</string>
+    <string name="pref_activity_recognize_running">rozpoznaj bieganie</string>
+    <string name="pref_activity_recognition_mode_none">偶aden</string>
+    <string name="fossil_hr_button_config_info">Niekt贸rych przycisk贸w nie mo偶na skonfigurowa膰, poniewa偶 ich funkcje s膮 na sta艂e zakodowane w oprogramowaniu sprz臋towym zegarka.
+\n
+\nUwaga: d艂ugie naci艣ni臋cie g贸rnego przycisku po zainstalowaniu interfejsu zegarka z oficjalnej aplikacji Fossil spowoduje r贸wnie偶 prze艂膮czanie mi臋dzy pokazywaniem/ukrywaniem wid偶et贸w.</string>
+    <string name="sony_ambient_sound_off">Wy艂膮czony</string>
+    <string name="qhybrid_title_on_device_confirmation">W艂膮cz potwierdzenie parowania na urz膮dzeniu</string>
+    <string name="prefs_reserve_reminder_calendar_summary">Liczba wydarze艅 z kalendarza, kt贸re b臋d膮 synchronizowane</string>
+    <string name="sony_surround_mode">Tryb Surround</string>
+    <string name="reminder_time_every_month">%1$s, co miesi膮c</string>
+    <string name="discovery_bluetooth_scan">Skanowanie Bluetooth:</string>
+    <string name="discovery_bluetooth_le_scan">Skanowanie Bluetooth LE:</string>
+    <string name="title_activity_set_reminders">Skonfiguruj przypomnienia</string>
+    <string name="controlcenter_start_configure_reminders">Skonfiguruj przypomnienia</string>
+    <string name="reminder_time_every_week">%1$s, co tydzie艅</string>
+    <string name="reminder_time_every_year">%1$s, ka偶dego roku</string>
+    <string name="reminder_no_free_slots_title">Brak wolnych slot贸w</string>
+    <string name="miband_prefs_reserve_reminder_calendar">Przypomnienia na nadchodz膮ce wydarzenia</string>
+    <string name="mi2_prefs_do_not_disturb_lift_wrist">Aktywacja wy艣wietlacza po podniesieniu podczas \"Nie przeszkadza膰\"</string>
+    <string name="pref_title_equalizer_normal">Normalny</string>
+    <string name="prefs_pressure_relief">Odci膮偶enie ci艣nieniowe z d藕wi臋kiem otoczenia</string>
+    <string name="spm">kroki/min</string>
+    <string name="sony_equalizer_preset_mellow">艁agodny</string>
+    <string name="qhybrid_summary_on_device_confirmation">Potwierdzenia parowania na urz膮dzeniu mo偶e by膰 irytuj膮ce. Wy艂膮czenie go mo偶e spowodowa膰 utrat臋 funkcjonalno艣ci.</string>
+    <string name="sony_equalizer_preset_custom_1">Niestandardowy 1</string>
+    <string name="controlcenter_power_off">Wy艂膮cz</string>
+    <string name="controlcenter_power_off_confirm_title">Wy艂膮cz</string>
+    <string name="controlcenter_power_off_confirm_description">Czy na pewno chcesz wy艂膮czy膰 to urz膮dzenie\?</string>
+    <string name="prefs_activity_in_device_card_title">Poka偶 szczeg贸艂y aktywno艣ci na karcie urz膮dzenia</string>
+    <string name="prefs_activity_in_device_card_title_summary">Wy艣wietl aktualne kroki, dystans lub sen na karcie urz膮dzenia</string>
+    <string name="prefs_activity_in_device_card_distance_title_summary">Dystans jest obliczany na podstawie krok贸w i d艂ugo艣ci kroku (mo偶na ustawi膰 w Ustawienia - O Tobie)</string>
+    <string name="reminder_time_once">%1$s, raz</string>
+    <string name="reminder_once">Jednorazowo</string>
+    <string name="reminder_every_day">Codziennie</string>
+    <string name="reminder_every_week">Co tydzie艅</string>
+    <string name="reminder_every_month">Co miesi膮c</string>
+    <string name="reminder_every_year">Co rok</string>
+    <string name="reminder_delete_confirm_description">Czy na pewno chcesz usun膮膰 to przypomnienie\?</string>
+    <string name="title_activity_reminder_details">Szczeg贸艂y przypomnienia</string>
+    <string name="activity_prefs_discovery_pairing">Opcje wykrywania i parowania</string>
+    <string name="prefs_fm_preset_instructions">D艂ugie naci艣ni臋cie przycisku powoduje zapisanie ustawie艅</string>
+    <string name="prefs_fm_presets_presets">Ustawienia wst臋pne</string>
+    <string name="minHR">Minimalne t臋tno</string>
+    <string name="sony_sound_position_off">Wy艂膮czony</string>
+    <string name="sony_sound_position_front">Prz贸d</string>
+    <string name="pref_header_system">System</string>
+    <string name="pref_header_other">Inne</string>
+    <string name="devicetype_bose_qc35">Bose QC35</string>
+    <string name="devicetype_vesc">VESC</string>
+    <string name="maxHR">Maksymalne t臋tno</string>
+    <string name="sony_equalizer_preset_treble_boost">Wzmocnienie ton贸w wysokich</string>
+    <string name="activity_type_hiking">Piesze wycieczki</string>
+    <string name="add_test_device">Dodaj urz膮dzenie testowe</string>
+    <string name="ascentDistance">Dystans pod g贸r臋</string>
+    <string name="descentDistance">Dystans w d贸艂</string>
+    <string name="maxStride">Maksymalny krok</string>
+    <string name="activity_type_climbing">Wspinaczka</string>
+    <string name="devicetype_sonyswr12">Sony SWR12</string>
+    <string name="devicetype_sony_wf_sp800n">Sony WF-SP800N</string>
+    <string name="device_card_activity_card_title">Informacje o aktywno艣ci na karcie urz膮dzenia</string>
+    <string name="device_card_activity_card_title_summary">Wybierz, jakie szczeg贸艂y aktywno艣ci s膮 wy艣wietlane na karcie urz膮dzenia</string>
+    <string name="prefs_activity_in_device_card_steps_title_summary">Poka偶 wszystkie kroki</string>
+    <string name="prefs_activity_in_device_card_sleep_title">Sen</string>
+    <string name="prefs_activity_in_device_card_sleep_title_summary">Poka偶 czas trwania snu</string>
+    <string name="discover_unsupported_devices_description">W艂膮czenie tej opcji spowoduje wy艣wietlenie wszystkich wykrytych urz膮dze艅 Bluetooth podczas skanowania. Kr贸tkie dotkni臋cie skopiuje nazw臋 urz膮dzenia i adres mac do schowka. D艂ugie naci艣ni臋cie uruchomi okno dialogowe `Dodaj urz膮dzenie testowe`. Mo偶e powodowa膰 potencjalne problemy zwi膮zane z zawieszaniem si臋 aplikacji.</string>
+    <string name="sony_touch_sensor">Sterowanie czujnikiem dotykowym</string>
+    <string name="pref_header_sony_ambient_sound_control">Kontrola d藕wi臋ku otoczenia</string>
+    <string name="sony_ambient_sound">Tryb</string>
+    <string name="watchface_dialog_widget_width">Szeroko艣膰 wid偶etu (w pikselach)</string>
+    <string name="reminder_repeat">Powtarzanie</string>
+    <string name="reminder_time_every_day">%1$s, codziennie</string>
+    <string name="reminder_delete_confirm_title">Usu艅 przypomnienie</string>
+    <string name="reminder_no_free_slots_description">Urz膮dzenie nie ma wolnych slot贸w na przypomnienia (ca艂kowita liczba slot贸w: %1$s)</string>
+    <string name="devicetype_sony_wh_1000xm4">Sony WH-1000XM4</string>
+    <string name="about_hash">Zatwierdzenie %s</string>
+    <string name="device_unsupported">NIEOBS艁UGIWANE</string>
+    <string name="discover_unsupported_devices">Odkryj nieobs艂ugiwane urz膮dzenia</string>
+    <string name="averageAltitude">艢rednia</string>
+    <string name="minSpeed">Minimum</string>
+    <string name="minStride">Minimalny krok</string>
+    <string name="averageCadence">艢rednie tempo</string>
+    <string name="maxCadence">Maksymalne tempo</string>
+    <string name="minCadence">Minimalne tempo</string>
+    <string name="prefs_active_noise_cancelling">Aktywna redukcja szum贸w</string>
+    <string name="prefs_active_noise_cancelling_summary">Blokowanie ha艂as贸w z otoczenia</string>
+    <string name="pressure_relief_summary">Zapobiegaj uczuciu ucisku w uszach, gdy nie u偶ywasz aktywnej redukcji szum贸w</string>
+    <string name="sony_ambient_sound_noise_cancelling">Redukcja szum贸w</string>
+    <string name="sony_ambient_sound_wind_noise_reduction">Redukcja szumu wiatru</string>
+    <string name="sony_ambient_sound_ambient_sound">D藕wi臋k otoczenia</string>
+    <string name="sony_ambient_sound_focus_voice">Skup si臋 na g艂osie</string>
+    <string name="sony_ambient_sound_level">Poziom d藕wi臋ku otoczenia</string>
+    <string name="sony_sound_position">Pozycja d藕wi臋ku</string>
+    <string name="sony_sound_position_front_left">Przedni lewy</string>
+    <string name="sony_sound_position_front_right">Przedni prawy</string>
+    <string name="sony_sound_position_rear_left">Tylny lewy</string>
+    <string name="sony_sound_position_rear_right">Tylny prawy</string>
+    <string name="sony_surround_mode_off">Wy艂膮czony</string>
+    <string name="sony_surround_mode_arena">Arena</string>
+    <string name="sony_surround_mode_outdoor_stage">Scena plenerowa</string>
+    <string name="sony_surround_mode_concert_hall">Sala koncertowa</string>
+    <string name="sony_warn_sbc_codec">Ostrze偶enie: Ustawienia korektora, pozycji d藕wi臋ku i d藕wi臋ku przestrzennego dzia艂aj膮 tylko w przypadku kodeka audio SBC.</string>
+    <string name="sony_equalizer_preset_bright">Jasny</string>
+    <string name="sony_equalizer_preset_excited">Podekscytowany</string>
+    <string name="sony_equalizer_preset_relaxed">Zrelaksowany</string>
+    <string name="sony_equalizer_preset_manual">Niestandardowe</string>
+    <string name="sony_equalizer_preset_custom_2">Niestandardowy 2</string>
+    <string name="pref_header_sony_equalizer_bands">Pasma</string>
+    <string name="pref_header_sony_equalizer_preset_custom_1">Niestandardowe ustawienie 1</string>
+    <string name="pref_header_sony_equalizer_preset_custom_2">Niestandardowe ustawienie 2</string>
+    <string name="sony_equalizer_clear_bass">Czysty bas</string>
+    <string name="sony_audio_upsampling">Upsampling d藕wi臋ku</string>
+    <string name="sony_speak_to_chat_sensitivity">Czu艂o艣膰 wykrywania g艂osu</string>
+    <string name="sony_speak_to_chat_sensitivity_auto">Automatyczny</string>
+    <string name="sony_speak_to_chat_sensitivity_high">Wysoki</string>
+    <string name="sony_speak_to_chat_sensitivity_low">Niski</string>
+    <string name="sony_speak_to_chat_focus_on_voice">Skoncentruj si臋 na g艂osie</string>
+    <string name="sony_speak_to_chat">M贸w-aby-rozmawia膰</string>
+    <string name="sony_speak_to_chat_timeout">Limit czasu</string>
+    <string name="sony_speak_to_chat_timeout_off">Wy艂膮czony</string>
+    <string name="sony_speak_to_chat_timeout_short">Kr贸tki (15s)</string>
+    <string name="sony_speak_to_chat_timeout_standard">Standardowy (30s)</string>
+    <string name="sony_speak_to_chat_timeout_long">D艂ugi (1m)</string>
+    <string name="sony_connect_two_devices">Po艂膮cz do 2 urz膮dze艅 jednocze艣nie</string>
+    <string name="sony_notification_voice_guide">Powiadomienia i przewodnik g艂osowy</string>
+    <string name="sony_automatic_power_off">Automatyczne wy艂膮czanie</string>
+    <string name="sony_automatic_power_off_off">Nie wy艂膮czaj</string>
+    <string name="sony_automatic_power_off_when_taken_off">Po zdj臋ciu</string>
+    <string name="sony_pause_when_taken_off">Pauza po zdj臋ciu s艂uchawek</string>
+    <string name="sony_button_mode_left">Tryb przycisku (lewy)</string>
+    <string name="sony_button_mode_right">Tryb przycisku (Prawy)</string>
+    <string name="sony_button_mode_off">Wy艂膮czony</string>
+    <string name="sony_button_mode_ambient_sound_control">Kontrola d藕wi臋ku otoczenia</string>
+    <string name="sony_button_mode_playback_control">Sterowanie odtwarzaniem</string>
+    <string name="sony_button_mode_volume_control">Regulacja g艂o艣no艣ci</string>
+    <string name="watchface_widget_type_custom">Niestandardowy wid偶et</string>
+    <string name="watchface_dialog_widget_update_timeout">Limit czasu aktualizacji w minutach</string>
+    <string name="watchface_dialog_widget_timeout_hide_text">Ukryj tekst po przekroczeniu czasu</string>
+    <string name="watchface_dialog_widget_timeout_show_circle">Poka偶 k贸艂ko po przekroczeniu limitu czasu</string>
+    <string name="pref_button_action_disabled">Wy艂膮czony</string>
+    <string name="pref_media_play">Odtwarzanie multimedi贸w</string>
+    <string name="pref_media_pause">Pauza multimedi贸w</string>
+    <string name="pref_media_playpause">Prze艂膮czanie odtwarzania</string>
+    <string name="pref_media_next">Nast臋pna 艣cie偶ka</string>
+    <string name="pref_media_previous">Poprzednia 艣cie偶ka</string>
+    <string name="pref_media_volumeup">Zwi臋kszanie g艂o艣no艣ci</string>
+    <string name="pref_media_volumedown">Zmniejszanie g艂o艣no艣ci</string>
+    <string name="pref_media_forward">Przejd藕 do przodu</string>
+    <string name="pref_media_rewind">Przejd藕 do ty艂u</string>
+    <string name="pref_device_action_broadcast">Wy艣lij transmisj臋</string>
+    <string name="distance_format_meters">###m</string>
+    <string name="distance_format_kilometers">###.#km</string>
+    <string name="distance_format_miles">###.#mi</string>
+    <string name="distance_format_feet">###ft</string>
+    <string name="prefs_activity_recognition">Ustawienia rozpoznawania aktywno艣ci</string>
+    <string name="pref_activity_recognize_biking">rozpoznaj jazd臋 na rowerze</string>
+    <string name="pref_activity_recognize_walking">rozpoznaj chodzenie</string>
+    <string name="pref_activity_recognize_rowing">rozpoznaj wio艣larstwo</string>
+    <string name="pref_activity_recognition_mode_ask">zapytaj</string>
+    <string name="pref_activity_recognition_mode_auto">automatyczny</string>
+    <string name="menuitem_menu">Menu</string>
+    <string name="devicetype_domyos_t540">Domyos T540</string>
+    <string name="unknown">Nieznany</string>
+    <string name="devicetype_amazfit_pop_pro">Amazfit Pop Pro</string>
+    <string name="start">Start</string>
+    <string name="devicetype_amazfit_pop">Amazfit Pop</string>
+    <string name="audio_codec">Kodek audio</string>
+    <string name="pref_header_sony_device_info">Informacje o urz膮dzeniu</string>
+    <string name="pref_anc_optimizer_state_pressure">Ci艣nienie atmosferyczne</string>
+    <string name="sony_anc_optimizer_status_analyzing">Analizowanie鈥�</string>
+    <string name="sony_anc_optimize_title">Zoptymalizuj</string>
+    <string name="menuitem_nothing">Nic</string>
+    <string name="pref_header_sony_anc_optimizer">Optymalizator redukcji szum贸w</string>
+    <string name="pref_title_opentracks_packagename">Nazwa pakietu aplikacji OpenTracks</string>
+    <string name="pref_summary_opentracks_packagename">U偶ywane do uruchamiania/zatrzymywania rejestracji trasy GPS w zewn臋trznej aplikacji fitness.</string>
+    <string name="sony_anc_optimize_description">Kliknij, aby uruchomi膰 optymalizator redukcji szum贸w.</string>
+    <string name="sony_anc_optimize_confirmation_title">Optymalizator redukcji szum贸w</string>
+    <string name="sony_anc_optimizer_status_starting">Uruchamianie鈥�</string>
+    <string name="sony_anc_optimizer_status_not_running">Nieuruchomiony</string>
+    <string name="sony_anc_optimizer_status_atmospheric_pressure">Pomiar ci艣nienia atmosferycznego鈥�</string>
+    <string name="sony_anc_optimizer_status_finished">Ko艅czenie鈥�</string>
+    <string name="sony_anc_optimizer_status_wearing_condition">Mierzenie warunk贸w u偶ytkowania鈥�</string>
+    <string name="pref_device_action_fitness_app_control_start">Pocz膮tek 艣ledzenia aplikacji fitness</string>
+    <string name="pref_title_notifications_generic_settings">Ustawienia powiadomie艅 Androida</string>
+    <string name="autoExport_lastTime_label">Ostatni automatyczny eksport: %1$s</string>
+    <string name="activity_db_management_autoexport_enabled_yes">Automatyczny eksport jest w艂膮czony.</string>
+    <string name="activity_db_management_autoexport_enabled_no">Automatyczny eksport jest wy艂膮czony.</string>
+    <string name="activity_db_management_autoexport_scheduled_yes">Automatyczny eksport zosta艂 zaplanowany na %1$s</string>
+    <string name="activity_db_management_autoexport_scheduled_no">Automatyczny eksport nie zosta艂 zaplanowany.</string>
+    <string name="sony_anc_optimize_confirmation_description">U偶ywaj s艂uchawek tak, jak zwykle. Je艣li warunki u偶ytkowania lub ci艣nienie powietrza ulegn膮 zmianie, uruchom optymalizator ponownie.</string>
+    <string name="pref_device_action_fitness_app_control_stop">Zatrzymanie 艣ledzenia aplikacji fitness</string>
+    <string name="watchface_dialog_pre_setting_position">Wst臋pne ustawienie po艂o偶enia na %s</string>
+    <string name="activity_db_management_autoexport_location">Lokalizacja nie jest dost臋pna. Najprawdopodobniej jest to spowodowane nowym systemem uprawnie艅 Androida. Prawdopodobnie automatyczny eksport teraz nie dzia艂a.</string>
+    <string name="watchface_setting_light_up_on_notification">Pod艣wietl przy nowym powiadomieniu</string>
+    <string name="menuitem_email">E-mail</string>
+    <string name="pref_screen_notification_profile_find_device">Znajd藕 urz膮dzenie</string>
+    <string name="pref_screen_notification_profile_event_reminder">Przypomnienie o wydarzeniach</string>
+    <string name="pref_screen_notification_idle_alerts">Alerty o bezczynno艣ci</string>
+    <string name="pref_screen_vibration_patterns_title">Wzory wibracji</string>
+    <string name="pref_screen_vibration_patterns_summary">Skonfiguruj wzory wibracji dla r贸偶nych powiadomie艅</string>
+    <string name="devicetype_sony_wf_1000xm3">Sony WF-1000XM3</string>
+    <string name="devicetype_galaxybuds_pro">Galaxy Buds Pro</string>
+    <string name="about_activity_title_banglejs_main">O Bangle.js Gadgetbridge</string>
+    <string name="application_name_banglejs_main">Bangle.js Gadgetbridge</string>
+    <string name="title_activity_controlcenter_banglejs_main">Bangle.js Gadgetbridge</string>
+    <string name="gadgetbridge_running_banglejs_main">Bangle.js jest uruchomiony</string>
+    <string name="about_activity_title_banglejs_nopebble">O Bangle.js Gadgetbridge</string>
+    <string name="application_name_banglejs_nopebble">Bangle.js Gadgetbridge</string>
+    <string name="title_activity_controlcenter_banglejs_nopebble">Bangle.js Gadgetbridge</string>
+    <string name="gadgetbridge_running_banglejs_nopebble">Bangle.js jest uruchomiony</string>
+    <string name="pref_title_banglejs_text_bitmap">Tekst jako bitmapy</string>
+    <string name="pref_title_device_internet_access">Zezw贸l na dost臋p do internetu</string>
+    <string name="pref_summary_device_internet_access">Zezw贸l aplikacjom na tym urz膮dzeniu na dost臋p do Internetu</string>
+    <string name="heartrate_bpm_100">100 uderze艅/min</string>
+    <string name="world_clock_delete_confirm_title">Usu艅 \'%1$s\'</string>
+    <string name="world_clock_timezone">Strefa czasowa</string>
+    <string name="heartrate_bpm_105">105 uderze艅/min</string>
+    <string name="heartrate_bpm_110">110 uderze艅/min</string>
+    <string name="heartrate_bpm_112">112 bpm</string>
+    <string name="heartrate_bpm_120">120 uderze艅/min</string>
+    <string name="heartrate_bpm_125">125 uderze艅/min</string>
+    <string name="heartrate_bpm_130">130 uderze艅/min</string>
+    <string name="heartrate_bpm_135">135 uderze艅/min</string>
+    <string name="heartrate_bpm_140">140 uderze艅/min</string>
+    <string name="heartrate_bpm_145">145 uderze艅/min</string>
+    <string name="heartrate_bpm_150">150 uderze艅/min</string>
+    <string name="pref_title_touch_spotify">Spotify</string>
+    <string name="pref_voice_detect_duration_5">5 sekund</string>
+    <string name="pref_voice_detect_duration_10">10 sekund</string>
+    <string name="pref_voice_detect_duration_15">15 sekund</string>
+    <string name="open_fw_installer_pick_file">Wybierz plik</string>
+    <string name="prefs_activity_monitoring_title">Monitorowanie aktywno艣ci</string>
+    <string name="notification_channel_gps">艢ledzenie GPS</string>
+    <string name="world_clock_no_free_slots_title">Brak wolnych miejsc</string>
+    <string name="open_fw_installer_warning_title">Ostrze偶enie</string>
+    <string name="mi2_prefs_heart_rate_monitoring">Monitorowanie t臋tna</string>
+    <string name="prefs_password">Has艂o</string>
+    <string name="czech">Czeski</string>
+    <string name="pref_world_clocks_title">Czas na 艣wiecie</string>
+    <string name="pref_world_clocks_summary">Skonfiguruj zegary dla innych stref czasowych</string>
+    <string name="prefs_activate_display_on_lift_sensitivity">Czu艂o艣膰</string>
+    <string name="pref_title_notification_delay_calls">Op贸藕nienie powiadomienia o po艂膮czeniu</string>
+    <string name="appmanager_app_share">Udost臋pnij</string>
+    <string name="open_fw_installer_info_text_title">Instalator pliku</string>
+    <string name="open_fw_installer_ensure_device_connected">Upewnij si臋, 偶e urz膮dzenie %s jest po艂膮czone</string>
+    <string name="open_fw_installer_select_file">Wybierz plik, kt贸ry chcesz przes艂a膰 do urz膮dzenia: %s</string>
+    <string name="controlcenter_folder_name">Nazwa folderu:</string>
+    <string name="controlcenter_add_new_folder">Dodaj nowy folder</string>
+    <string name="pref_header_calendar">Kalendarz</string>
+    <string name="pref_header_health">Zdrowie</string>
+    <string name="pref_header_time">Czas</string>
+    <string name="heartrate_bpm_40">40 uderze艅/min</string>
+    <string name="heartrate_bpm_45">45 uderze艅/min</string>
+    <string name="heartrate_bpm_50">50 uderze艅/min</string>
+    <string name="spo2_perc_80">80%</string>
+    <string name="spo2_perc_85">85%</string>
+    <string name="spo2_perc_90">90%</string>
+    <string name="heartrate_bpm_115">115 uderze艅/min</string>
+    <string name="seconds_6">6 sekund</string>
+    <string name="seconds_7">7 sekund</string>
+    <string name="seconds_8">8 sekund</string>
+    <string name="seconds_9">9 sekund</string>
+    <string name="seconds_11">11 sekund</string>
+    <string name="seconds_12">12 sekund</string>
+    <string name="seconds_13">13 sekund</string>
+    <string name="seconds_14">14 sekund</string>
+    <string name="seconds_15">15 sekund</string>
+    <string name="menuitem_countdown">Odliczanie</string>
+    <string name="pref_header_stress">Stres</string>
+    <string name="pref_header_spo2">Tlen we krwi</string>
+    <string name="pref_screen_brightness">Jasno艣膰 ekranu</string>
+    <string name="prefs_hourly_chime_summary">Zegarek zabrz臋czy raz na godzin臋</string>
+    <string name="prefs_stress_monitoring_title">Monitorowanie stresu</string>
+    <string name="quick_alarm">Szybki alarm</string>
+    <string name="info_no_devices_connected">Brak po艂膮czonych urz膮dze艅</string>
+    <string name="watchface_dialog_widget_background">T艂o</string>
+    <string name="steps_streaks_since_date">Od %s</string>
+    <string name="spo2_off">Wy艂.</string>
+    <string name="watchface_setting_button_toggle_backlight">W艂膮cz pod艣wietlenie</string>
+    <string name="prefs_active_noise_cancelling_level_high">Wysokie</string>
+    <string name="info_connected_count">%d po艂膮czone urz膮dzenia</string>
+    <string name="connection_over_ble">Po艂膮czenie poprzez BLE</string>
+    <string name="watchface_dialog_widget_timezone_duration">Czas widoczno艣ci zegara (w sekundach)</string>
+    <string name="controlcenter_set_folder_title">Ustaw lub utw贸rz nowy folder</string>
+    <string name="prefs_active_noise_cancelling_level_low">Niskie</string>
+    <string name="watchface_dialog_widget_cat_position">Pozycja</string>
+    <string name="watchface_dialog_title_widget">Ustawienia wid偶etu</string>
+    <string name="open_fw_installer_info_text">Instalator oprogramowania uk艂adowego/tarcz/aplikacji/plik贸w pozwala na wgranie/instalacj臋 obs艂ugiwanych plik贸w (oprogramowanie uk艂adowe, tarcze, aplikacje, GPS, zasoby, czcionki...) do urz膮dzenia. Zobacz wi臋cej informacji w wiki: https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Firmware-Update</string>
+    <string name="pref_title_overwrite_settings_on_connection">Nadpisz ustawienia przy po艂膮czeniu</string>
+    <string name="pref_summary_overwrite_settings_on_connection">Podczas 艂膮czenia si臋 z opask膮 nadpisz wszystkie znajduj膮ce si臋 na niej ustawienia.</string>
+    <string name="prefs_spo2_monitoring_title">Monitorowanie tlenu we krwi</string>
+    <string name="world_clock_label">Etykieta</string>
+    <string name="prefs_heartrate_alert_low_threshold">Pr贸g alarmowy niskiego t臋tna</string>
+    <string name="prefs_relaxation_reminder_title">Przypomnienie o relaksie</string>
+    <string name="prefs_relaxation_reminder_description">Zawibruj opask膮 w celu powiadomienia o warto艣ci stresu wy偶szej ni偶 80</string>
+    <string name="prefs_spo2_monitoring_description">Automatyczne monitorowanie poziomu tlenu we krwi przez ca艂y dzie艅</string>
+    <string name="prefs_spo2_alert_threshold">Pr贸g alarmowy SPO2</string>
+    <string name="pref_sleep_breathing_quality_monitoring">Monitorowanie jako艣ci oddechu podczas snu</string>
+    <string name="fw_upgrade_notice_miband7">Zamierzasz zainstalowa膰 oprogramowanie uk艂adowe %s na swoim Xiaomi Smart Band 7.
+\n
+\nTwoja opaska uruchomi si臋 ponownie po zainstalowaniu pliku .zip.
+\n
+\nKONTYNUUJ NA W艁ASNE RYZYKO!</string>
+    <string name="open_fw_installer_connect_maximum_one_device">Prosz臋 pod艂膮czy膰 TYLKO JEDNO urz膮dzenie, do kt贸rego chcesz wys艂a膰 plik.</string>
+    <string name="pref_header_connection">Po艂膮czenie</string>
+    <string name="pref_header_display">Wy艣wietlanie</string>
+    <string name="pref_header_workout">膯wiczenia</string>
+    <string name="pref_summary_canned_replies">Odpowiadaj z zegarka za pomoc膮 zaprogramowanych wiadomo艣ci</string>
+    <string name="discovery_scanning_intensity_warning">W przypadku wyst膮pienia zamro偶enia lub braku reakcji, spr贸buj ustawi膰 intensywno艣膰 skanowania na ni偶szy poziom. Je艣li urz膮dzenie nie jest wykrywane, spr贸buj ustawi膰 intensywno艣膰 skanowania na wy偶szy poziom.</string>
+    <string name="pref_cache_weather">Przechowuj informacje o pogodzie w pami臋ci podr臋cznej</string>
+    <string name="pref_cache_weather_summary">Informacje o pogodzie b臋d膮 buforowane podczas ponownych uruchomie艅 aplikacji.</string>
+    <string name="pref_title_device_intents">Zezwalaj na intencje</string>
+    <string name="pref_workout_send_gps_title">Wy艣lij GPS podczas treningu</string>
+    <string name="pref_workout_start_on_phone_title">艢ledzenie aplikacji fitness</string>
+    <string name="world_clock_no_free_slots_description">Urz膮dzenie nie ma wolnych slot贸w dla zegar贸w 艣wiatowych (艂膮czne sloty: %1$s)</string>
+    <string name="pref_title_banglejs_webview_url">Adres URL programu 艂aduj膮cego aplikacje</string>
+    <string name="pref_summary_banglejs_webview_url">Je艣li chcesz mie膰 niestandardowy program 艂aduj膮cy aplikacje, umie艣膰 sw贸j adres URL https://.../android.html tutaj. W przeciwnym razie pozostaw puste miejsce dla https://banglejs.com/apps</string>
+    <string name="pref_summary_device_intents">Zezwalaj aplikacjom zegarkowym Bangle.js na wysy艂anie intencji Androida i zezwalaj innym aplikacjom na Androida (jak Tasker) na wysy艂anie danych do Bangle.js z intencj膮 com.banglejs.uart.tx. Wymaga pozwolenia na wy艣wietlanie nad innymi aplikacjami, aby dzia艂a膰 w tle.</string>
+    <string name="about_description_banglejs_main">Aplikacja towarzysz膮ca dla Bangle.js, zbudowana na bazie projektu Gadgetbridge, z dodanym dost臋pem do Internetu.
+\n
+\nZe wzgl臋du na polityk臋 Sklepu Google Play, nie mo偶emy umie艣ci膰 linku do darowizny w samej aplikacji, ale je艣li Ci si臋 ona podoba, rozwa偶 przekazanie darowizny poprzez stron臋 g艂贸wn膮 Gadgetbridge poni偶ej.</string>
+    <string name="application_name_main_nightly">Gadgetbridge (Nightly)</string>
+    <string name="title_activity_controlcenter_main_nightly">Gadgetbridge Nightly</string>
+    <string name="about_activity_title_main_nightly">Informacje o Gadgetbridge Nightly</string>
+    <string name="about_description_main_nightly">Niewymagaj膮cy chmury wolny zamiennik dla zamkni臋to 藕r贸d艂owych aplikacji dostarczanych przez producent贸w gad偶et贸w na system Android. Wczesne wydanie Gadgetbridge. Nie mo偶na go zainstalowa膰, je艣li masz ju偶 zainstalowan膮 aplikacj臋 Urz膮dzenia na ciele lub Pebble, ze wzgl臋du na konflikt w dostawcy Pebble.</string>
+    <string name="gadgetbridge_running_main_nightly">Gadgetbridge (Nightly) jest uruchomiony</string>
+    <string name="application_name_main_nopebble">Gadgetbridge (Nightly, No Pebble provider)</string>
+    <string name="title_activity_controlcenter_main_nopebble">Gadgetbridge Nightly NoPebble</string>
+    <string name="about_activity_title_main_nopebble">Informacje o Gadgetbridge Nightly NoPebble</string>
+    <string name="about_description_main_nopebble">Niewymagaj膮cy chmury wolny zamiennik dla zamkni臋to 藕r贸d艂owych aplikacji dostarczanych przez producent贸w gad偶et贸w na system Android. Wczesne wydanie Gadgetbridge. W tej wersji zmieniono nazw臋 dostawcy Pebble, aby zapobiec konfliktom, wi臋c niekt贸re integracje zwi膮zane z Pebble nie b臋d膮 dzia艂a膰, ale mo偶na j膮 zainstalowa膰 obok istniej膮cej instalacji Gadgetbridge.</string>
+    <string name="gadgetbridge_running_main_nopebble">Gadgetbridge (Nightly NoPebble) jest uruchomiona</string>
+    <string name="pref_summary_banglejs_text_bitmap">Je艣li s艂owo nie mo偶e by膰 wygenerowane za pomoc膮 czcionki zegarka, wygeneruj je do bitmapy w Gadgetbridge i wy艣wietl j膮 na zegarku</string>
+    <string name="open_fw_installer_warning_text">Ta funkcja ma potencja艂, aby pozostawi膰 z Twojego urz膮dzenia ceg艂臋. To powiedziawszy, taka sytuacja nigdy nie zdarzy艂a si臋 偶adnemu z programist贸w poprzez flashowanie, ale pami臋taj, 偶e robisz to na w艂asne ryzyko.</string>
+    <string name="open_fw_installer_getting_files_title">Pobieranie pliku z oprogramowaniem sprz臋towym/aplikacj膮</string>
+    <string name="discovery_scanning_intensity">Intensywno艣膰 skanowania</string>
+    <string name="title_activity_world_clock_details">Szczeg贸艂y zegara 艣wiatowego</string>
+    <string name="prefs_heartrate_alert_experimental_description">Zawibruj opask膮, gdy t臋tno przekracza pewien pr贸g, bez widocznej aktywno艣ci fizycznej w ci膮gu ostatnich 10 minut. Ta funkcja jest eksperymentalna i nie by艂a szeroko testowana.</string>
+    <string name="prefs_activity_monitoring_description">Automatycznie zwi臋kszaj cz臋stotliwo艣膰 wykrywania t臋tna, gdy opaska wykryje 膰wiczenia fizyczne, aby zwi臋kszy膰 dok艂adno艣膰 przechwytywania t臋tna.</string>
+    <string name="fw_upgrade_notice_amazfit_gts3">W艂a艣nie zamierzasz zainstalowa膰 oprogramowanie uk艂adowe %s na swoim Amazfit GTS 3.
+\n
+\nPo instalacji pliku .zip Twoja opaska zostanie ponownie uruchomiona.
+\n
+\nKONTYNUUJ NA W艁ASNE RYZYKO!</string>
+    <string name="pref_title_notifications_ignore_low_priority">Ignoruj powiadomienia o niskim priorytecie</string>
+    <string name="pref_summary_notifications_ignore_low_priority">Nie wysy艂aj do zegarka powiadomie艅 o niskim i minimalnym priorytecie</string>
+    <string name="pref_title_notification_prefer_long_text">Preferuj d艂ugi tekst powiadomienia</string>
+    <string name="pref_summary_notification_prefer_long_text">Je艣li jest dost臋pny, wy艣lij d艂ugi tekst powiadomienia do urz膮dzenia</string>
+    <string name="pref_title_screen_on_on_notifications">Ekran w艂膮czony przy powiadomieniach</string>
+    <string name="pref_summary_screen_on_on_notifications">W艂膮cz ekran opaski, gdy nadejdzie powiadomienie</string>
+    <string name="pref_workout_send_gps_summary">Wy艣lij aktualn膮 lokalizacj臋 GPS do opaski podczas treningu</string>
+    <string name="controlcenter_get_heartrate_measurement">Uzyskaj pomiar t臋tna</string>
+    <string name="open_fw_installer_getting_files_text">Poniewa偶 nie mo偶emy rozpowszechnia膰 plik贸w oprogramowania sprz臋towego, b臋dziesz musia艂 zdoby膰 je samodzielnie. Oznacza to, 偶e b臋dziesz musia艂 szuka膰 plik贸w w plikach apk, online, na forach, na Amazfitwatchfaces (dla urz膮dze艅 Miband/Amazfit) i tak dalej.</string>
+    <string name="open_fw_installer_connect_minimum_one_device">Prosz臋 pod艂膮czy膰 PRZYNAJMNIEJ JEDNO urz膮dzenie, do kt贸rego chcesz wys艂a膰 plik.</string>
+    <string name="pref_write_logfiles_not_available">Inicjalizacja logowania plik贸w nie powiod艂a si臋, zapisywanie plik贸w dziennika jest obecnie niedost臋pne. Uruchom ponownie aplikacj臋, aby spr贸bowa膰 ponownie zainicjowa膰 pliki dziennika.</string>
+    <string name="application_name_banglejs_nightly">Bangle.js Gadgetbridge (Nightly)</string>
+    <string name="title_activity_controlcenter_banglejs_nightly">Bangle.js Gadgetbridge (Nightly)</string>
+    <string name="about_activity_title_banglejs_nightly">O Bangle.js Gadgetbridge (Nightly)</string>
+    <string name="about_description_banglejs_nightly">Aplikacja towarzysz膮ca dla Bangle.js, zbudowana na bazie projektu Gadgetbridge, z dodanym dost臋pem do Internetu.
+\n
+\nZe wzgl臋du na polityk臋 Sklepu Google Play, nie mo偶emy umie艣ci膰 linku do darowizny w samej aplikacji, ale je艣li Ci si臋 ona podoba, rozwa偶 przekazanie darowizny poprzez stron臋 g艂贸wn膮 Gadgetbridge poni偶ej.</string>
+    <string name="gadgetbridge_running_banglejs_nightly">Bangle.js jest uruchomiony (Nightly)</string>
+    <string name="pref_summary_notification_delay_calls">Op贸藕nienie przed wys艂aniem powiadomie艅 o po艂膮czeniach przychodz膮cych do urz膮dzenia, w sekundach.</string>
+    <string name="world_clock_delete_confirm_description">Czy na pewno chcesz usun膮膰 zegar 艣wiatowy\?</string>
+    <string name="prefs_heartrate_alert_experimental_title">Alarm t臋tna (eksperymentalny)</string>
+    <string name="prefs_heartrate_alert_threshold">Pr贸g alarmowy t臋tna</string>
+    <string name="pref_blacklist_calendars_summary">Zablokowane kalendarze nie b臋d膮 synchronizowane z urz膮dzeniem</string>
+    <string name="pref_explanation_authkey_new_protocol">Je艣li otrzymasz na opasce komunikat \"Zaktualizuj aplikacj臋 do najnowszej wersji\", upewnij si臋, 偶e sprawdzisz \"Nowy protok贸艂 uwierzytelniania\" powy偶ej. Naci艣nij tutaj, aby uzyska膰 wi臋cej szczeg贸艂贸w w wiki.</string>
+    <string name="pref_workout_start_on_phone_summary">Rozpocznij/zatrzymaj 艣ledzenie aplikacji fitness w telefonie po rozpocz臋ciu treningu GPS w opasce</string>
+    <string name="pref_explanation_authkey">Niekt贸re urz膮dzenia wymagaj膮 specjalnego klucza parowania przy pierwszej inicjalizacji urz膮dzenia. Naci艣nij tutaj, aby uzyska膰 wi臋cej szczeg贸艂贸w w wiki.</string>
+    <string name="appmanager_item_outdated">(nieaktualne)</string>
+    <string name="pref_title_banglejs_txt_bitmap_size">Rozmiar Bitmapy Tekstu</string>
+    <string name="pref_summary_banglejs_txt_bitmap_size">Rozmiar u偶ywany do renderowania tekstu bitmapowego</string>
+    <string name="prefs_heartrate_alert_high_threshold">Pr贸g alarmowy wysokiego t臋tna</string>
+    <string name="prefs_stress_monitoring_description">Monitorowanie poziomu stresu podczas odpoczynku</string>
+    <string name="controlcenter_set_preferences">Ustaw preferencje</string>
+    <string name="controlcenter_toggle_details">Poka偶 szczeg贸艂y</string>
+    <string name="controlcenter_connected_fraction">Po艂膮czono: %d/%d</string>
+    <string name="activity_type_elliptical">Eliptyczna</string>
+    <string name="error_deleting_device">B艂膮d przy usuwaniu urz膮dzenia: %s</string>
+    <string name="notification_gps_title">Gadgetbridge GPS</string>
+    <string name="menuitem_unknown_app">Nieznane (%s)</string>
+    <string name="devicetype_binary_sensor">Czujnik binarny</string>
+    <string name="notification_gps_text">Wysy艂anie lokalizacji GPS do %1$d urz膮dze艅</string>
+    <string name="menuitem_workout_history">Historia trening贸w</string>
+    <string name="menuitem_female_health">Zdrowie kobiety</string>
+    <string name="menuitem_workout_status">Status treningu</string>
+    <string name="share_log_not_enabled_message">Musisz najpierw w艂膮czy膰 rejestrowanie w Ustawieniach - Zapisywanie plik贸w dziennika</string>
+    <string name="menuitem_personal_activity_intelligence">Inteligencja dzia艂a艅 osobistych (PAI)</string>
+    <string name="permission_display_over_other_apps">%1$s potrzebuje uprawnie艅 do wy艣wietlania nad innymi aplikacjami, aby zegarki Bangle.js mog艂y rozpoczyna膰 dzia艂ania za pomoc膮 intencji, gdy %1$s dzia艂a w tle.
+\n
+\nMo偶na go u偶y膰 do uruchomienia aplikacji muzycznej i odtworzenia utworu oraz wielu innych rzeczy.
+\n
+\nWybierz 鈥�%2$s鈥�, a nast臋pnie 鈥�%1$s鈥� i w艂膮cz opcj臋 鈥瀂ezwalaj na wy艣wietlanie w innych aplikacjach鈥�, a nast臋pnie dotknij 鈥濿stecz鈥�, aby powr贸ci膰 do %1$s.
+\n
+\nAby przesta膰 pyta膰 %1$s o uprawnienia, przejd藕 do 鈥濽stawie艅鈥� i odznacz 鈥濻prawd藕 stan uprawnie艅鈥�.
+\n
+\nUpewnij si臋, 偶e przyzna艂e艣 %1$s uprawnienia potrzebne do dzia艂ania zgodnie z oczekiwaniami.</string>
+    <string name="watchface_dialog_widget_cat_generic">Generyczny</string>
+    <string name="watchface_dialog_widget_cat_2nd_tz_widget">Drugi wid偶et strefy czasowej</string>
+    <string name="prefs_double_tap_edge_summary">Wykrywaj podw贸jne dotkni臋cie, nawet je艣li nie zosta艂o dotkni臋te na p艂ytce dotykowej</string>
+    <string name="connection_over_bt_classic">Po艂膮czenie przez Bluetooth (klasyczne)</string>
+    <string name="autoconnect_from_device_title">Pod艂膮czenie przy po艂膮czeniu z urz膮dzenia</string>
+    <string name="autoconnect_from_device_summary">Nawi膮偶 po艂膮czenie, gdy po艂膮czenie jest inicjowane przez urz膮dzenie, np. s艂uchawki</string>
+    <string name="prefs_always_on_display">Ekran zawsze w艂膮czony</string>
+    <string name="prefs_password_4_digits_1_to_4_summary">Has艂o musi sk艂ada膰 si臋 z 4 znak贸w, z u偶yciem cyfr od 1 do 4</string>
+    <string name="prefs_password_6_digits_0_to_9_summary">Has艂o musi mie膰 6 znak贸w, z u偶yciem tylko cyfr</string>
+    <string name="prefs_screen_timeout">Czas trwania ekranu</string>
+    <string name="portuguese_pt">Portugalski (Portugalia)</string>
+    <string name="mi2_dnd_always">Zawsze</string>
+    <string name="smart">Inteligentny</string>
+    <string name="activity_type_outdoor_cycling">Kolarstwo</string>
+    <string name="devicetype_miband7">Xiaomi Smart Band 7</string>
+    <string name="prefs_ambient_volume_left">G艂o艣no艣膰 otoczenia Lewo</string>
+    <string name="auto_reconnect_ble_title">Automatyczne ponowne po艂膮czenie z urz膮dzeniem</string>
+    <string name="step_streaks_achievements_sharing_title">Osi膮gni臋cia krok贸w</string>
+    <string name="prefs_hourly_chime">Godzinny dzwonek</string>
+    <string name="pref_header_heartrate_sleep">Sen</string>
+    <string name="pref_header_heartrate_allday">Ca艂odzienny monitoring</string>
+    <string name="pref_header_heartrate_alerts">Alerty t臋tna</string>
+    <string name="prefs_ambient_sound_during_call_summary">S艂ysz wyra藕nie w艂asny g艂os podczas po艂膮cze艅</string>
+    <string name="step_streak_ongoing">W toku</string>
+    <string name="note">Uwaga</string>
+    <string name="watchface_setting_button_toggle_widgets">Prze艂膮czanie wid偶et贸w</string>
+    <string name="hybridhr_widget_bg_thin_circle">Cienki okr膮g</string>
+    <string name="hybridhr_widget_bg_double_circle">Podw贸jny okr膮g</string>
+    <string name="hybridhr_widget_bg_dashed_circle">Przerywany okr膮g</string>
+    <string name="prefs_in_ear_detection_summary">Odtwarzanie rozm贸w przez s艂uchawki, gdy s膮 w uszach</string>
+    <string name="pref_device_action_phone_gps_location_listener_stop">Zatrzymanie LocationListener GPS</string>
+    <string name="error_setting_parent_folder">B艂膮d ustawienia folderu nadrz臋dnego: %s</string>
+    <string name="step_streak_longest">Najd艂u偶sza</string>
+    <string name="steps_streaks">Seria krok贸w</string>
+    <string name="auto_reconnect_ble_summary">Co jaki艣 czas pr贸buj po艂膮czy膰 si臋 z urz膮dzeniem</string>
+    <string name="steps_streaks_hint">Seria nast臋puj膮cych po sobie dni bez przerwy z osi膮gni臋ciem celu krok贸w</string>
+    <string name="step_streak_total">艁膮cznie</string>
+    <string name="steps_streaks_total_steps">Krok贸w
+\n艂膮cznie</string>
+    <string name="steps_streaks_streak_days">Ilo艣膰
+\ndni</string>
+    <string name="mi2_prefs_heart_rate_monitoring_summary">Konfiguruj monitorowanie t臋tna</string>
+    <string name="portuguese_br">Portugalski (Brazylia)</string>
+    <string name="pref_device_action_fitness_app_control_toggle">Prze艂膮cz 艣ledzenie aplikacji fitness</string>
+    <string name="mi2_prefs_heart_rate_monitoring_alerts_summary">Konfiguruj monitorowanie t臋tna i prog贸w alarmowych</string>
+    <string name="watchface_setting_title_custom_events">W艂asne wydarzenia</string>
+    <string name="prefs_customize_ambient_sound_summary">Dostosuj d藕wi臋k otoczenia</string>
+    <string name="prefs_ambient_sound_during_call_title">D藕wi臋k otoczenia podczas po艂膮czenia</string>
+    <string name="prefs_ambient_volume_right">G艂o艣no艣膰 otoczenia Prawo</string>
+    <string name="prefs_ambient_settings_title">Opcje d藕wi臋ku otoczenia</string>
+    <string name="pref_title_touch_voice_assistant">Asystent g艂osowy</string>
+    <string name="prefs_switch_control_right">Prawy prze艂膮cznik steruj膮cy</string>
+    <string name="pref_title_touch_anc">Aktywna redukcja ha艂asu</string>
+    <string name="pref_title_touch_quick_ambient">Szybki d藕wi臋k otoczenia</string>
+    <string name="steps_streaks_achievement_rate">Wska藕nik
+\nosi膮gni臋cia</string>
+    <string name="step_streak_average_steps_hint">艢rednia ilo艣膰 krok贸w na dzie艅 serii</string>
+    <string name="steps_streaks_total_steps_hint_totals">Ca艂kowita liczba kiedykolwiek zarejestrowanych krok贸w</string>
+    <string name="steps_streaks_average_steps">艢rednia
+\nkrok贸w</string>
+    <string name="step_streak_days_hint">Liczba kolejnych dni z osi膮gni臋tym celem krok贸w</string>
+    <string name="steps_streaks_total_steps_hint">Ca艂kowita liczba krok贸w w ca艂ej serii</string>
+    <string name="steps_streaks_total_days_hint_totals">Odsetek dni z osi膮gni臋tym celem w stosunku do wszystkich dni z krokami</string>
+    <string name="steps_streaks_total_steps_average_hint">Ca艂kowita 艣rednia %d krok贸w na dzie艅</string>
+    <string name="prefs_seamless_connection_switch_title">Bezproblemowy prze艂膮cznik po艂膮cze艅</string>
+    <string name="prefs_seamless_connection_switch_summary">Automatycznie prze艂膮cza s艂uchawki pomi臋dzy sparowanymi urz膮dzeniami</string>
+    <string name="prefs_noise_control_with_one_earbud_summary">Zezw贸l na kontrol臋 ha艂asu przy u偶yciu tylko jednej s艂uchawki</string>
+    <string name="pref_ambient_sound_tone">Ton d藕wi臋ku otoczenia</string>
+    <string name="prefs_noise_control_with_one_earbud">Kontrola ha艂asu za pomoc膮 jednej s艂uchawki</string>
+    <string name="pref_ambient_sound_tone_summary">Od mi臋kkiego do czystego</string>
+    <string name="pref_balance">Balans</string>
+    <string name="pref_switch_controls_anc_ambient">T艂umienie ha艂asu 鈫愨啋 Otoczenie</string>
+    <string name="prefs_password_summary">Zablokuj opask臋 za pomoc膮 has艂a po zdj臋ciu z nadgarstka</string>
+    <string name="mi5_prefs_workout_activity_types_summary">Wybierz typy aktywno艣ci, kt贸re maj膮 by膰 wy艣wietlane na ekranie trening贸w</string>
+    <string name="activity_type_outdoor_running">Bieganie na 艣wie偶ym powietrzu</string>
+    <string name="quick_alarm_description">Alarm z wid偶etu</string>
+    <string name="controlcenter_set_parent_folder">Ustaw folder nadrz臋dny</string>
+    <string name="activity_type_freestyle">Freestyle</string>
+    <string name="prefs_switch_control_left">Lewy prze艂膮cznik steruj膮cy</string>
+    <string name="estonian">Esto艅ski</string>
+    <string name="normal">Normalny</string>
+    <string name="kind_app">Aplikacja</string>
+    <string name="devicetype_amazfit_gts3">Amazfit GTS 3</string>
+    <string name="extended_ascii">Rozszerzone ASCII</string>
+    <string name="icelandic">Islandzki</string>
+    <string name="lithuanian">Litewski</string>
+    <string name="persian">Perski</string>
+    <string name="scandinavian">Skandynawski</string>
+    <string name="permission_notification_listener">%1$s potrzebuje dost臋pu do powiadomie艅, aby m贸c je wy艣wietla膰 na zegarku, gdy ekran telefonu jest wy艂膮czony.
+\n
+\nProsz臋 dotkn膮膰 \'%2$s\', a nast臋pnie \'%1$s\' i w艂膮czy膰 \'Pozw贸l na dost臋p do powiadomie艅\', a nast臋pnie dotkn膮膰 \'Wr贸膰\', aby powr贸ci膰 do %1$s.</string>
+    <string name="permission_notification_policy_access">%1$s potrzebuje dost臋pu do ustawie艅 Nie przeszkadza膰, aby m贸c je honorowa膰 na zegarku, gdy ekran telefonu jest wy艂膮czony.
+\n
+\nProsz臋 dotkn膮膰 \"%2$s\", a nast臋pnie \"%1$s\" i w艂膮czy膰 \"Pozw贸l na zmian臋 ustawie艅 trybu Nie Przeszkadza膰\", a nast臋pnie dotkn膮膰 \"Wr贸膰\", aby powr贸ci膰 do %1$s.</string>
+    <string name="pref_title_touch_volume">G艂o艣no艣膰</string>
+    <string name="pref_switch_controls_anc_off">T艂umienie ha艂asu 鈫愨啋 Wy艂膮czone</string>
+    <string name="sensitive">Wra偶liwy</string>
+    <string name="permission_location">%1$s potrzebuje dost臋pu do Twojej lokalizacji w tle, aby m贸c pozosta膰 po艂膮czonym z zegarkiem, nawet gdy ekran jest wy艂膮czony.\n\nWybierz \'%2$s\' na poni偶szym ekranie, a nast臋pnie stuknij \'Wstecz\', aby powr贸ci膰 do %1$s.</string>
+    <string name="controlcenter_unset_folder">Nieustawiony folder</string>
+    <string name="pref_switch_controls_ambient_off">Otoczenie 鈫愨啋 Wy艂膮czone</string>
+    <string name="prefs_noise_control">Kontrola ha艂asu</string>
+    <string name="prefs_voice_detect">Wykrywanie g艂osu</string>
+    <string name="prefs_voice_detect_summary">W艂膮cz d藕wi臋k otoczenia i obni偶 g艂o艣no艣膰 odtwarzania automatycznie po wykryciu g艂osu</string>
+    <string name="prefs_voice_detect_duration">Zako艅cz po ciszy przez:</string>
+    <string name="prefs_active_noise_cancelling_level">Poziom aktywnej redukcji ha艂asu</string>
+    <string name="step_streaks_achievements_sharing_message">Moje codzienne osi膮gni臋cia krokowe!</string>
+    <string name="bengali">Bengalski</string>
+    <string name="prefs_always_on_display_summary">Utrzymuj wy艣wietlacz opaski zawsze w艂膮czonym</string>
+    <string name="prefs_password_enabled">Has艂o w艂膮czone</string>
+    <string name="mi5_prefs_workout_activity_types">Rodzaje aktywno艣ci treningowej</string>
+    <string name="ukranian">Ukrai艅ski</string>
+    <string name="watchface_dialog_widget_cat_custom_widget">W艂asny wid偶et</string>
+    <string name="pref_title_touch_ambient">D藕wi臋k otoczenia</string>
+    <string name="pref_switch_noise_control">Prze艂膮cznik kontroli ha艂asu</string>
+    <string name="prefs_double_tap_edge">Podw贸jne stukni臋cie kraw臋dzi</string>
+    <string name="devicetype_flipper_zero">Flipper zero</string>
+    <string name="activity_prefs_allow_bluetooth_intent_api">API Bluetooth Intent</string>
+    <string name="activity_prefs_summary_allow_bluetooth_intent_api">Pozw贸l na sterowanie po艂膮czeniem Bluetooth za pomoc膮 Intent API</string>
+    <string name="activity_type_gymnastics">Gimnastyka</string>
+    <string name="activity_type_stepper">Chodzik</string>
+    <string name="activity_type_volleyball">Siatk贸wka</string>
+    <string name="activity_type_table_tennis">Tenis sto艂owy</string>
+    <string name="pref_summary_third_party_app_device_settings">Pozw贸l aplikacjom firm trzecich na ustawienie urz膮dzenia wed艂ug ich potrzeb.</string>
+    <string name="activity_type_hiit">Interwa艂 po mi臋dzy treningami o wysokiej intensywno艣ci</string>
+    <string name="activity_type_bowling">Kr臋gle</string>
+    <string name="activity_type_pilates">Pilates</string>
+    <string name="activity_type_boxing">Boks</string>
+    <string name="activity_type_kickboxing">Kickboxing</string>
+    <string name="supercars_lights_label">Klasyczne</string>
+    <string name="supercars_turbo_speed_label">Turbo Speed</string>
+    <string name="dismiss">Odrzu膰</string>
+    <string name="fw_upgrade_notice_amazfit_gtr3">W艂a艣nie zamierzasz zainstalowa膰 oprogramowanie uk艂adowe %s na Twoim Amazfit GTR3.
+\n
+\nUrz膮dzenie uruchomi si臋 ponownie po instalacji pliku .zip.
+\n
+\nKONTYNUUJ NA W艁ASNE RYZYKO!</string>
+    <string name="pref_title_third_party_app_device_settings">Pozw贸l aplikacjom firm trzecich na zmian臋 ustawie艅</string>
+    <string name="activity_type_dance">Taniec</string>
+    <string name="activity_type_indoor_fitness">膯wiczenia w domu</string>
+    <string name="activity_type_core_training">Podstawowe 膰wiczenia</string>
+    <string name="activity_type_stretching">Rozci膮ganie</string>
+    <string name="activity_type_street_dance">Street dance</string>
+    <string name="activity_type_zumba">Zumba</string>
+    <string name="activity_type_indoor_ice_skating">艁y偶wy na obiekcie krytym</string>
+    <string name="devicetype_amazfit_gtr3">Amazfit GTR3 3</string>
+    <string name="devicetype_super_cars">Shell Racing</string>
+    <string name="devicetype_amazfit_gtr_lite">Amazfit GTR Lite</string>
+    <string name="watchface_setting_button_move_hands">Poruszaj r臋koma</string>
+    <string name="hrZoneWarmUp">Rozgrzewka</string>
+    <string name="hrZoneFatBurn">Spalanie t艂uszczu</string>
+    <string name="elevationGain">Wzrost wysoko艣ci</string>
+    <string name="elevationLoss">Spadek wysoko艣ci</string>
+    <string name="HeartRateZones">Strefy t臋tna</string>
+    <string name="pref_header_authentication">Uwierzytelnianie</string>
+    <string name="abstract_chart_fragment_kind_rem_sleep">Sen w fazie REM</string>
+    <string name="TrainingEffect">Efekt treningu</string>
+    <string name="currentWorkoutLoad">Obci膮偶enie treningu</string>
+    <string name="maximumOxygenUptake">Maksymalny pob贸r tlenu</string>
+    <string name="devicetype_sony_wh_1000xm2">Sony WH-1000XM2</string>
+    <string name="pref_chart_sleep_lines_limit">Ile wierszy sesji snu pokaza膰 przed ich przewini臋ciem</string>
+    <string name="supercars_lights_blinking_label">Mruganie</string>
+    <string name="hrZoneAnaerobic">Anaerobowa</string>
+    <string name="devicetype_sony_wf_1000xm4">Sony WF-1000XM4</string>
+    <string name="hrZoneNa">Brak</string>
+    <string name="hrZoneAerobic">Aerobowa</string>
+    <string name="sports_activity_confirm_delete_title">Usu艅 %d aktywno艣ci</string>
+    <string name="sports_activity_confirm_delete_description">Czy na pewno chcesz usun膮膰 %d aktywno艣ci\?</string>
+    <string name="accuracy">Dok艂adno艣膰</string>
+    <string name="all_satellites">Wszystkie satelity</string>
+    <string name="gps_bds">GPS + BDS</string>
+    <string name="speed_first">Najpierw pr臋dko艣膰</string>
+    <string name="debugactivity_confirm_remove_device_preferences_title">Usun膮膰 preferencje urz膮dzenia\?</string>
+    <string name="debugactivity_confirm_remove_device_preferences">Usunie to wszystkie preferencje dla wszystkich pod艂膮czonych urz膮dze艅, kontynuowa膰\?</string>
+    <string name="pref_vibrate_for_alert">Wibracje dla alert贸w</string>
+    <string name="appmanager_app_start">Start</string>
+    <string name="appmanager_downloaded_to_cache">Pobrano %s do cache</string>
+    <string name="appmanager_watchface_activate">Aktywne</string>
+    <string name="appmanager_download_started">Rozpocz臋to pobieranie aplikacji</string>
+    <string name="fw_upgrade_notice_amazfit_gts4">Zamierzasz zainstalowa膰 oprogramowanie uk艂adowe %s na Twoim Amazfit GTS 4.
+\n
+\nOpaska uruchomi si臋 ponownie po instalacji *.zip.
+\n
+\nKONTYNUUJ NA W艁ASNE RYZYKO!</string>
+    <string name="pref_title_notification_media_ignores_application_list">Lista ignorowanych powiadomie艅 ze stron spo艂eczno艣ciowych</string>
+    <string name="pref_summary_notification_media_ignores_application_list">Przetwarzaj powiadomienia multimedialne przed list膮 aplikacji. Je艣li ta preferencja nie jest zaznaczona, aplikacje multimedialne musz膮 by膰 dozwolone na li艣cie aplikacji, aby elementy steruj膮ce multimediami dzia艂a艂y na urz膮dzeniu.</string>
+    <string name="pref_workout_detection_categories_summary">Automatyczne wykrywanie kategorii 膰wicze艅</string>
+    <string name="world_clock_code">Kod</string>
+    <string name="activity_prefs_goal_standing_time_minutes">Cel dzienny: czas stania [min]</string>
+    <string name="offline_voice_response_during_screen_lighting">Reaguj podczas rozja艣niania ekranu</string>
+    <string name="pref_agps_header">AGPS</string>
+    <string name="offline_voice_respond_screen_on">Reaguj podczas w艂膮czania ekranu</string>
+    <string name="pref_workout_detection_ask_first_summary">Wymagaj potwierdzenia na zegarku po wykryciu tego 膰wiczenia</string>
+    <string name="pref_workout_detection_time_summary">Ilo艣膰 minut, przez kt贸re 膰wiczenie musi trwa膰, zanim zostanie wykryte</string>
+    <string name="pref_sleep_mode_smart_enable_summary">Automatycznie w艂膮czaj tryb u艣pienia podczas noszenia opaski w trakcie snu</string>
+    <string name="prefs_always_on_display_style">Styl</string>
+    <string name="pref_huami_truncate_fetch_operation_timestamps_summary">Skr贸膰 sygnatury czasowe operacji pobierania do minut. Wy艂膮cz to ustawienie, aby zachowa膰 sygnatury czasowe w sekundach, je艣li napotkasz problemy podczas pobierania bardzo kr贸tkich trening贸w.</string>
+    <string name="devicetype_sony_linkbuds_s">Sony LinkBuds S</string>
+    <string name="menuitem_phone">Telefon</string>
+    <string name="pref_title_upper_button_long_press_action">Akcja d艂ugiego naci艣ni臋cia dla g贸rnego przycisku</string>
+    <string name="sony_quick_access_triple_tap">Szybki dost臋p (potr贸jne dotkni臋cie)</string>
+    <string name="pref_title_touch_spotify_galaxy_app">Spotyfi (wy艂膮cznie aplikacja dla Wearable Galaxy)</string>
+    <string name="intent_api_allow_activity_sync_title">Zezw贸l na wyzwalacz synchronizacji aktywno艣ci</string>
+    <string name="intent_api_allow_activity_sync_summary">Zezwalaj na wyzwalanie synchronizacji aktywno艣ci przez Intent API</string>
+    <string name="intent_api_broadcast_export_title">Rozg艂aszaj przy eksporcie bazy danych</string>
+    <string name="intent_api_broadcast_export_summary">Rozg艂aszaj intencj臋 po zako艅czeniu eksportu bazy danych</string>
+    <string name="pref_title_lock_unlock">Blokuj</string>
+    <string name="devicetype_soflow_s06">SoFlow SO6</string>
+    <string name="appmanager_app_download">Pobierz do cache</string>
+    <string name="appmanager_download_app_error">B艂膮d pobierania aplikacji</string>
+    <string name="pref_agps_update_time">Czas aktualizacji AGPS</string>
+    <string name="pref_workout_detection_categories_title">Kategorie 膰wicze艅</string>
+    <string name="pref_agps_expire_time">Czas wyga艣ni臋cia AGPS</string>
+    <string name="pref_workout_detection_alert_summary">Powiadom, gdy 膰wiczenie zostanie wykryte</string>
+    <string name="pref_workout_detection_sensitivity">Czu艂o艣膰</string>
+    <string name="menuitem_sun_moon">S艂o艅ce i ksi臋偶yc</string>
+    <string name="menuitem_calendar">Kalendarz</string>
+    <string name="menuitem_todo">Do zrobienia</string>
+    <string name="menuitem_voice_memos">Notatki g艂osowe</string>
+    <string name="sony_ambient_sound_control_button_mode_as_off">D藕wi臋k otoczenia, wy艂</string>
+    <string name="sony_quick_access_double_tap">Szybki dost臋p (dwukrotne dotkni臋cie)</string>
+    <string name="prefs_touch_lock_buds2">Sterowanie dotykowe</string>
+    <string name="vibration_profile_default">Domy艣lny</string>
+    <string name="activity_prefs_target_weight_kg">Docelowa waga [kg]</string>
+    <string name="menuitem_wechat_pay">WeChat Pay</string>
+    <string name="function_enabled">W艂膮czono</string>
+    <string name="anaerobicTrainingEffect">Efekt anerobowy</string>
+    <string name="pref_title_touch_spotify_official_app">Spotify (wy艂膮cznie oficjalna aplikacja)</string>
+    <string name="sony_ambient_sound_control_button_mode_nc_as_off">Redukcja szum贸w, d藕wi臋k otoczenia, wy艂</string>
+    <string name="sony_ambient_sound_control_button_mode_nc_as">Redukcja szum贸w, d藕wi臋k otoczenia</string>
+    <string name="sony_ambient_sound_control_button_mode_nc_off">Redukcja szum贸w, wy艂</string>
+    <string name="pref_screen_auto_brightness_title">Automatyczna jasno艣膰</string>
+    <string name="intent_api_allow_trigger_export_summary">Zezwalaj na wyzwalanie eksportu bazy danych przez Intent API</string>
+    <string name="devicetype_asteroidos">AsteroidOS</string>
+    <string name="georgian">Gruzi艅ski</string>
+    <string name="activity_prefs_goal_fat_burn_time_minutes">Cel dzienny: spalanie t艂uszczu [min]</string>
+    <string name="activity_type_pool_swimming">P艂ywanie w basenie</string>
+    <string name="activity_type_rowing">Wios艂owanie</string>
+    <string name="devicetype_amazfit_gts4_mini">Amazfit GTS 4 Mini</string>
+    <string name="devicetype_amazfit_gtr4">Amazfit GTR 4</string>
+    <string name="devicetype_amazfit_trex_2">Amazfit T-Rex 2</string>
+    <string name="devicetype_amazfit_band7">Amazfit Band 7</string>
+    <string name="menuitem_membership_cards">kart cz艂onkowskie</string>
+    <string name="menuitem_wifi">Wi-Fi</string>
+    <string name="menuitem_lockscreen">Ekran blokady</string>
+    <string name="menuitem_eject_water">Wylej wod臋</string>
+    <string name="hrZoneExtreme">Ekstremalna</string>
+    <string name="balanced">Zbalansowany</string>
+    <string name="power_saving">Oszcz臋dzanie energii</string>
+    <string name="custom">W艂asne</string>
+    <string name="single_band">Pojedyncza opaska</string>
+    <string name="dual_band">Podw贸jna opaska</string>
+    <string name="low_power_gps">GPS niskiego zasilania</string>
+    <string name="gps">GPS</string>
+    <string name="gps_gnolass">GPS + GNOLASS</string>
+    <string name="gps_galileo">GPS + GALILEO</string>
+    <string name="accuracy_first">Najpierw dok艂adno艣膰</string>
+    <string name="buttons_on_left">Przyciski po lewej stronie</string>
+    <string name="pref_header_offline_voice">G艂os offline</string>
+    <string name="devicetype_galaxybuds_2_pro">Galaxy Buds2 Pro</string>
+    <string name="sony_ambient_sound_control_button_modes">Tryby przycisk贸w sterowania d藕wi臋kiem otoczenia</string>
+    <string name="pref_screen_auto_brightness_summary">Dostosuj jasno艣膰 ekranu do 艣wiat艂a otoczenia</string>
+    <string name="intent_api_allow_trigger_export_title">Zezw贸l na eksport bazy danych</string>
+    <string name="pref_workout_detection_enabled">W艂膮czono wykrywanie</string>
+    <string name="pref_workout_detection_enabled_summary">W艂膮cz automatyczne wykrywanie dla tego 膰wiczenia</string>
+    <string name="pref_workout_detection_ask_first">Zapytaj mnie</string>
+    <string name="pref_workout_detection_time">Ilo艣膰 minut aktywno艣膰 tego 膰wiczenia przed wykryciem</string>
+    <string name="pref_sleep_mode_title">Tryb snu</string>
+    <string name="pref_sleep_mode_sleep_screen_title">Ekran u艣pienia</string>
+    <string name="pref_sleep_mode_sleep_screen_summary">Poka偶 ekran u艣pienia podczas wybudzania ekranu w trybie u艣pienia, aby zmniejszy膰 rozproszenie uwagi</string>
+    <string name="buttons_on_right">Przyciski po prawej stronie</string>
+    <string name="prefs_weardirection">Kierunek noszenia</string>
+    <string name="seconds_25">25 sek</string>
+    <string name="pref_huami_truncate_fetch_operation_timestamps_title">Skr贸膰 sygnatury czasowe operacji pobierania</string>
+    <string name="pref_enable_unsupported_settings_title">W艂膮cz niewspierane ustawienia</string>
+    <string name="pref_title_lower_button_short_press_action">Akcja kr贸tkiego naci艣ni臋cia dla dolnego przycisku</string>
+    <string name="prefs_control_center">Centrum kontroli</string>
+    <string name="aerobicTrainingEffect">Efekt aerobowy</string>
+    <string name="sony_speak_to_chat_sensitivity_standard">Standardowa</string>
+    <string name="fw_upgrade_notice_amazfit_trex2">Zamierzasz zainstalowa膰 oprogramowanie uk艂adowe %s na Twoim Amazfit T-Rex 2.
+\n
+\nOpaska uruchomi si臋 ponownie po instalacji *.zip.
+\n
+\nKONTYNUUJ NA W艁ASNE RYZYKO!</string>
+    <string name="pref_header_sound_vibration">D藕wi臋ki i wibracje</string>
+    <string name="pref_header_intent_api">Interfejs API intencji</string>
+    <string name="pref_text_to_speech">Tekst na mow臋</string>
+    <string name="pref_workout_detection_alert_title">Alarm</string>
+    <string name="offline_voice_respond_turn_wrist">Reaguj podczas obracania nadgarstka</string>
+    <string name="pref_agps_expiry_reminder_enabled">Powiadomienie o wygasaniu AGPS</string>
+    <string name="pref_sleep_mode_smart_enable_title">W艂膮cz Smart</string>
+    <string name="pref_screen_notification_profile_schedule">Harmonogram</string>
+    <string name="devicetype_amazfit_gts4">Amazfit GTS 4</string>
+    <string name="menuitem_one_tap_measuring">Pomiar jednorazowy</string>
+    <string name="menuitem_theater_mode">Tryb teatralny</string>
+    <string name="menuitem_volume">G艂o艣no艣膰</string>
+    <string name="menuitem_unsupported">[Niewspierane] %s</string>
+    <string name="menuitem_screen_always_lit">Ekran zawsze pod艣wietlony</string>
+    <string name="menuitem_brightness">Jasno艣膰</string>
+    <string name="menuitem_offline_voice">G艂os offline</string>
+    <string name="activity_type_indoor_walking">Spacer wewn膮trz</string>
+    <string name="menuitem_bluetooth">Bluetooth</string>
+    <string name="pref_gps_header">GPS</string>
+    <string name="pref_gps_mode_preset">Tryb GPS</string>
+    <string name="pref_gps_band">GPS opaski</string>
+    <string name="fw_upgrade_notice_amazfit_gts4_mini">Zamierzasz zainstalowa膰 oprogramowanie uk艂adowe %s na Twoim Amazfit GTS 4 mini.
+\n
+\nOpaska uruchomi si臋 ponownie po instalacji *.zip.
+\n
+\nKONTYNUUJ NA W艁ASNE RYZYKO!</string>
+    <string name="fw_upgrade_notice_amazfit_gtr4">Zamierzasz zainstalowa膰 oprogramowanie uk艂adowe %s na Twoim Amazfit GTR 4.
+\n
+\nOpaska uruchomi si臋 ponownie po instalacji *.zip.
+\n
+\nKONTYNUUJ NA W艁ASNE RYZYKO!</string>
+    <string name="pref_agps_expiry_reminder_time">Czas powiadamiania o wygasaniu AGPS</string>
+    <string name="pref_workout_detection_title">Kierunek 膰wicze艅</string>
+    <string name="pref_enable_unsupported_settings_summary">Umo偶liwi to dost臋p do wszystkich dost臋pnych ustawie艅, nawet je艣li nie s膮 obs艂ugiwane przez urz膮dzenie. Mo偶e to spowodowa膰 niestabilno艣膰 i awarie urz膮dzenia.</string>
+    <string name="prefs_always_on_display_follow_watchface">Styl wed艂ug tarczy zegarka</string>
+    <string name="prefs_control_center_summary">Wybierz elementy z rozwijanego menu centrum kontroli</string>
+    <string name="devicetype_galaxybuds_2">Galaxy Buds2</string>
+    <string name="fw_upgrade_notice_amazfit_band7">Zamierzasz zainstalowa膰 oprogramowanie uk艂adowe %s na Twoim Amazfit Band 7.
+\n
+\nOpaska uruchomi si臋 ponownie po instalacji *.zip.
+\n
+\nKONTYNUUJ NA W艁ASNE RYZYKO!</string>
+    <string name="pref_gps_combination">Mieszanie GPS</string>
+    <string name="pref_gps_satellite_search">Wyszukiwanie satelit</string>
+    <string name="pref_crown_vibration">Wibracja koronki</string>
+    <string name="pref_alert_tone">D藕wi臋k alertu</string>
+    <string name="pref_cover_to_mute">Przykryj, aby wyciszy膰</string>
+    <string name="pref_workout_detection_summary">Automatyczny kierunek 膰wicze艅</string>
+    <string name="pref_screen_notification_profile_todo_list">Lista Do zrobienia</string>
+    <string name="fw_upgrade_notice_amazfit_gtr3_pro">W艂a艣nie zamierzasz zainstalowa膰 oprogramowanie uk艂adowe %s na Twoim Amazfit GTR 3 Pro.
+\n
+\nUrz膮dzenie uruchomi si臋 ponownie po instalacji pliku .zip.
+\n
+\nKONTYNUUJ NA W艁ASNE RYZYKO!</string>
+    <string name="pref_summary_receive_calls_watch">Otrzymuj i wykonuj po艂膮czenia bezpo艣rednio na zegarku</string>
+    <string name="pref_display_caller_title">Poka偶 informacje o kontakcie</string>
+    <string name="bluetooth_calls">Po艂膮czenia przez Bluetooth</string>
+    <string name="action_changelog">Lista zmian</string>
+    <string name="title_activity_contact_details">Szczeg贸艂y kontaktu</string>
+    <string name="charts_legend_stress_average">艢redni poziom stresu</string>
+    <string name="pai_plus_num">+%d</string>
+    <string name="num_min">%d min</string>
+    <string name="devicetype_amazfit_gtr3_pro">Amazfit GTR 3 Pro</string>
+    <string name="menuitem_forecast">Prognoza</string>
+    <string name="stress_relaxed">Zreleksowany</string>
+    <string name="kind_gpx_route">艢lad GPX</string>
+    <string name="preferences_miband_1_2_settings" tools:ignore="TypographyFractions">Ustawienia Mi Band 1/2</string>
+    <string name="croatian">Chorwacki</string>
+    <string name="stop">Zatrzymaj</string>
+    <string name="devicetype_casiogmwb5000">Casio GMW-B5000</string>
+    <string name="long_press">D艂ugie naci艣ni臋cie</string>
+    <string name="pref_camera_remote_title">Zdalna kamera</string>
+    <string name="devicetype_vivomove_hr">Garmin Vivomove HR</string>
+    <string name="menuitem_recommendation">Rekomendacje</string>
+    <string name="yesterdays_activity">Wczorajsza aktywno艣膰</string>
+    <string name="pref_show_changelog">Poka偶 list臋 zmian po uruchomieniu</string>
+    <string name="wifi_hotspot_configuration">Konfiguracja hotspotu Wi-Fi</string>
+    <string name="wifi_ssid">SSID</string>
+    <string name="wifi_hotspot_status">Stan hotspotu Wi-Fi</string>
+    <string name="address">Adres</string>
+    <string name="activity_prefs_goals">Cele</string>
+    <string name="status">Stan</string>
+    <string name="screenshot_taken">Wykonano zrzut ekranu</string>
+    <string name="activity_detail_share_gps_label">Udost臋pnij tras臋 GPS</string>
+    <string name="dev_tools">Narz臋dzia deweloperskie</string>
+    <string name="activity_type_rugby">Rugby</string>
+    <string name="activity_type_dancing">Taniec</string>
+    <string name="share_screenshot">Udost臋pnij zrzut ekranu</string>
+    <string name="devicetype_bohemic_smart_bracelet">Bohemic Smart Bracelet</string>
+    <string name="wifi_hotspot">Hotspot Wi-Fi</string>
+    <string name="ftp_server">Serwer FTP</string>
+    <string name="ftp_server_status">Stan serwera FTP</string>
+    <string name="pref_header_navigation">Nawigacja</string>
+    <string name="activity_type_surfing">Serfowanie</string>
+    <string name="activity_type_football">Pi艂ka no偶na</string>
+    <string name="devicetype_amazfit_bip3_pro">Amazfit Bip 3 Pro</string>
+    <string name="watchface_widget_type_uv_index">Indeks promieniowania UV</string>
+    <string name="stress_high">Wysoki</string>
+    <string name="username">Nazw膮 u偶ytkownika</string>
+    <string name="pref_morning_updates_title">Poranne aktualizacje</string>
+    <string name="contact_delete_confirm_title">Usu艅 kontakt</string>
+    <string name="devicetype_sony_wh_1000xm5">Sony WH-1000XM5</string>
+    <string name="menuitem_aqi">Indeks jako艣ci powietrza</string>
+    <string name="pref_contacts_title">Kontakty</string>
+    <string name="activity_type_golf">Golf</string>
+    <string name="ftp_server_configuration">Konfiguracja serwera FTP</string>
+    <string name="activity_type_tennis">Tenis</string>
+    <string name="fossil_hr_confirm_connection">Potwierd藕 na zegarku</string>
+    <string name="stress_moderate">艢redni</string>
+    <string name="activity_type_hockey">Hokej</string>
+    <string name="activity_type_icehockey">Hokej na lodzie</string>
+    <string name="activity_type_iceskating">艁y偶wiarstwo na lodzie</string>
+    <string name="activity_type_other">Inne</string>
+    <string name="devicetype_amazfit_cheetah_pro">Amazfit Cheetah Pro</string>
+    <string name="devicetype_amazfit_bip5">Amazfit Bip 5</string>
+    <string name="devicetype_amazfit_trex_ultra">Amazfit T-Rex Ultra</string>
+    <string name="devicetype_amazfit_gtr_mini">Amazfit GTR Mini</string>
+    <string name="devicetype_amazfit_falcon">Amazfit Falcon</string>
+    <string name="devicetype_casiogwb5600">Casio GW-B5600</string>
+    <string name="activity_type_weightlifting">Podnoszenie ci臋偶ar贸w</string>
+    <string name="devicetype_amazfit_cheetah_square">Amazfit Cheetah (Square)</string>
+    <string name="devicetype_amazfit_cheetah_round">Amazfit Cheetah (Round)</string>
+    <string name="device_experimental">EKSPERYMENTALNE</string>
+    <string name="pref_summary_banglejs_phone_gps_enbale">U偶yj danych GPS telefonu, aby nadpisa膰 dane GPS urz膮dzenia bangle</string>
+    <string name="pref_summary_mb_intents">W艂膮cz, je艣li kontrola no艣nik贸w urz膮dzenia nie dzia艂a dla niekt贸rych aplikacji</string>
+    <string name="pref_pair_bluetooth_calls_help_summary">Aby odbiera膰 po艂膮czenia Bluetooth, nale偶y sparowa膰 telefon z drug膮 instancj膮 zegarka.</string>
+    <string name="pref_pair_bluetooth_calls_summary">Naci艣nij tu, aby rozpocz膮膰 proces parowania</string>
+    <string name="pref_display_caller_summary">Wy艣wietl numer telefonu lub imi臋 dla po艂膮cze艅 przychodz膮cych</string>
+    <string name="pref_pair_bluetooth_calls_help_1">1. Naci艣nij poni偶szy przycisk, aby rozpocz膮膰 proces parowania.</string>
+    <string name="pref_title_banglejs_phone_gps_enbale">Korzystanie z danych GPS telefonu</string>
+    <string name="pref_pair_bluetooth_calls_help_warning">OSTRZE呕ENIE: Je艣li w艂膮czysz po艂膮czenia Bluetooth bez parowania z drug膮 instancj膮, powiadomienia o po艂膮czeniach mog膮 nie dzia艂a膰 zgodnie z oczekiwaniami.</string>
+    <string name="pref_theme_dynamic">Dynamiczne kolory</string>
+    <string name="pref_pair_bluetooth_calls_help_3">3. W艂膮cz poni偶sze ustawienie po艂膮cze艅 Bluetooth.</string>
+    <string name="pref_pair_bluetooth_calls_title">Paruj dla po艂膮cze艅 bluetooth</string>
+    <string name="pref_title_mb_intents">Bezpo艣rednie nadawanie intencji przycisk贸w multimedialnych</string>
+    <string name="pref_summary_banglejs_phone_gps_update_interval">Cz臋stotliwo艣膰 aktualizacji pozycji GPS, w ms</string>
+    <string name="fw_upgrade_notice_amazfitbip3pro">W艂a艣nie zamierzasz zainstalowa膰 oprogramowanie uk艂adowe %s na swoim Amazfit Bip 3 Pro.
+\n
+\nProsz臋 upewni膰 si臋, 偶e zainstalowany zosta艂 plik .fw, nast臋pnie plik .res i na ko艅cu plik .gps. Po zainstalowaniu pliku .fw Tw贸j zegarek zostanie ponownie uruchomiony.
+\n
+\nUwaga: Nie musisz instalowa膰 plik贸w .res i .gps, je艣li te pliki s膮 dok艂adnie takie same jak te, kt贸re zosta艂y wcze艣niej zainstalowane.
+\n
+\nKONTYNUUJ NA W艁ASNE RYZYKO!</string>
+    <string name="pref_title_banglejs_phone_gps_network_only">U偶ywaj sieci tylko do okre艣lania lokalizacji</string>
+    <string name="pref_camera_remote_summary">Umo偶liwia zegarkowi wyzwalanie aparatu telefonu</string>
+    <string name="bluetooth_calls_settings">Ustawienia po艂膮cze艅 Bluetooth</string>
+    <string name="bluetooth_calls_pairing">Parowanie po艂膮cze艅 Bluetooth</string>
+    <string name="fw_upgrade_notice_amazfit_cheetah_pro">W艂a艣nie zamierzasz zainstalowa膰 oprogramowanie uk艂adowe %s na Twoim Amazfit Cheetah Pro
+\n
+\nTw贸j zegarek zostanie ponownie uruchomiony po instalacji pliku .zip
+\n
+\nKONTYNUUJ NA W艁ASNE RYZYKO!</string>
+    <string name="pref_title_notification_cache_while_disconnected">Pami臋膰 podr臋czna poza zasi臋giem</string>
+    <string name="pref_summary_banglejs_phone_gps_network_only">Do okre艣lenia lokalizacji u偶ywaj tylko dostawcy sieci. Zmniejsza to zu偶ycie energii kosztem dok艂adno艣ci. Konieczne jest ponowne po艂膮czenie.</string>
+    <string name="pref_title_banglejs_phone_gps_update_interval">Interwa艂 aktualizacji danych GPS</string>
+    <string name="fw_upgrade_notice_zepp_os">W艂a艣nie zamierzasz zainstalowa膰 oprogramowanie uk艂adowe %s na Twoim %s.
+\n
+\nTw贸j zegarek zostanie ponownie uruchomiony po instalacji pliki .zip.
+\n
+\nKONTYNUUJ NA W艁ASNE RYZYKO!</string>
+    <string name="pref_summary_notification_cache_while_disconnected">Wysy艂anie nieodebranych powiadomie艅, gdy urz膮dzenie po艂膮czy si臋 ponownie po utracie zasi臋gu</string>
+    <string name="pref_pair_bluetooth_calls_help_2">2. Przejd藕 do ustawie艅 bluetooth telefonu i sparuj z nowym urz膮dzeniem, kt贸re si臋 pojawi (nazwa podobna do obecnego zegarka, ale z przyrostkiem, np. \"Amazfit GTR 4 - AFC8\").</string>
+    <string name="pref_canned_message">Wiadomo艣膰</string>
+    <string name="pref_pair_bluetooth_calls_help_title">Jak odbiera膰 po艂膮czenia bluetooth</string>
+    <string name="sony_button_mode_help_title">Tryby przycisk贸w - Pomoc</string>
+    <string name="prefs_wena3_receive_calls_title">Ustawienia po艂膮cze艅 przychodz膮cych</string>
+    <string name="pref_summary_navigation_forward">Przesy艂aj instrukcje z aplikacji nawigacyjnych na zegarek</string>
+    <string name="prefs_wena3_button_action_name_start_timer">Uruchom licznik aktywno艣ci</string>
+    <string name="pref_activity_full_sync_trigger_summary">Uruchom pe艂n膮 synchronizacj臋 wszystkich danych dotycz膮cych aktywno艣ci</string>
+    <string name="withings_calibration_text_activity_target">Na koniec ustaw r臋k臋 aktywno艣ci na 100%. Nale偶y pami臋ta膰, 偶e ta r臋ka porusza si臋 wy艂膮cznie zgodnie z ruchem wskaz贸wek zegara.</string>
+    <string name="contact_missing_name">Nazwa kontaktu jest pusta</string>
+    <string name="prefs_wena3_menu_icon_hint">Ikona Ustawienia jest zawsze wy艣wietlana na ko艅cu</string>
+    <string name="prefs_wena3_notification_vibration_repetition_3">3 razy</string>
+    <string name="debug_companion_show_associated">Poka偶 powi膮zane urz膮dzenia towarzysz膮ce</string>
+    <string name="busy_task_fetch_debug_logs">Pobieranie dziennik贸w debugowania</string>
+    <string name="contact_missing_number">Numer kontaktowy jest pusty</string>
+    <string name="status_icon">Ikona stanu</string>
+    <string name="title">Tytu艂</string>
+    <string name="busy_task_fetch_spo2_data">Pobieranie danych SpO2</string>
+    <string name="french_ca">Francuski (Kanada)</string>
+    <string name="double_tap">Podw贸jne dotkni臋cie</string>
+    <string name="prefs_wena3_button_action_name_qrio_lock">Blokada Qrio</string>
+    <string name="pref_app_logs_stop_summary">Zatrzymaj rejestrowanie dziennik贸w aplikacji zegarka</string>
+    <string name="prefs_wena3_auto_power_off_turn_off_time_item">Czas wy艂膮czenia</string>
+    <string name="prefs_wena3_vibration_short">Kr贸tkie</string>
+    <string name="info_menu_structure_contents">Struktura menu: %s</string>
+    <string name="activity_type_squash">Squash</string>
+    <string name="fossil_hr_confirmation_timeout">Up艂yn膮艂 limit czasu potwierdzenia, Kontynuowanie</string>
+    <string name="updatefirmwareoperation_updateproblem_free_space">Na urz膮dzeniu nie ma wystarczaj膮cej ilo艣ci wolnego miejsca</string>
+    <string name="busy_task_fetch_sports_summaries">Pobieranie podsumowa艅 sportowych</string>
+    <string name="prefs_wena3_home_icon_name_calories">Kalorie</string>
+    <string name="loyalty_cards_install">Zainstaluj Catim臋</string>
+    <string name="prefs_home_icon_left_item">Do lewej</string>
+    <string name="prefs_wena3_led_none">Bez LED</string>
+    <string name="spanish_es">Hiszpa艅ski (Hiszpania)</string>
+    <string name="activity_type_skiing">Narciarstwo</string>
+    <string name="activity_error_share_failed">Udost臋pnianie pliku nie powiod艂o si臋.</string>
+    <string name="pref_morning_updates_categories_title">Kategorie porannych aktualizacji</string>
+    <string name="updatefirmwareoperation_failed_low_mtu">Bie偶膮ce MTU %1$d jest zbyt niskie. W艂膮cz wysokie MTU w ustawieniach urz膮dzenia i od艂膮cz/pod艂膮cz ponownie urz膮dzenie.</string>
+    <string name="loyalty_cards_sync_title">Synchronizuj karty lojalno艣ciowe</string>
+    <string name="menuitem_zepp_coach">Trener Zepp</string>
+    <string name="prefs_wena3_vibration_step_down">Zmniejszaj膮ce si臋</string>
+    <string name="ftp_server_summary">Kontroluj serwer FTP na zegarku</string>
+    <string name="pai_chart_per_week">PAI na tydzie艅</string>
+    <string name="prefs_wena3_vibration_smart_item">Inteligentne wibracje</string>
+    <string name="prefs_wena3_home_icon_name_riiiver">Riiiver</string>
+    <string name="pref_message_privacy_mode_bodyonly">Ukryj tylko g艂贸wn膮 tre艣膰</string>
+    <string name="prefs_wena3_hint_background_sync">Pozw贸l urz膮dzeniu Wena okresowo prosi膰 Gadgetbridge o pobranie danych o aktywno艣ci z urz膮dzenia</string>
+    <string name="pref_morning_updates_categories_summary">Lista kategorii wy艣wietlana ka偶dego ranka</string>
+    <string name="english_in">Angielski (Indie)</string>
+    <string name="prefs_wena3_home_icon_name_suica">R贸wnowaga Suica</string>
+    <string name="zepp_os_watchface_business_style">Styl biznesowy</string>
+    <string name="prefs_wena3_vibration_strength_item_medium">艢rednie</string>
+    <string name="zepp_os_watchface_emerald_moonlight">Szmaragdowe 艣wiat艂o ksi臋偶yca</string>
+    <string name="preferences_miband_1_2_warning">Ostrze偶enie: te preferencje dotycz膮 tylko opasek Mi Band 1 i 2.</string>
+    <string name="prefs_wena3_title_activity">Ustawienia aktywno艣ci</string>
+    <string name="loyalty_cards_catima">Catima</string>
+    <string name="pref_workout_keep_screen_on_title">Utrzymuj ekran w艂膮czony podczas treningu</string>
+    <string name="pai_chart_per_month">PAI miesi臋cznie</string>
+    <string name="error_invalid_menu_structure">Nieprawid艂owa struktura menu JSON</string>
+    <string name="pref_switch_controls_anc_ambient_off">Redukcja szum贸w 鈫� 鈫� Otoczenie 鈫� 鈫� Wy艂</string>
+    <string name="busy_task_fetch_hr_data">Pobieranie danych o t臋tnie</string>
+    <string name="pref_app_logs_start_summary">Rozpocznij rejestrowanie dziennik贸w aplikacji zegarka</string>
+    <string name="prefs_wena3_vibration_continuous">Ci膮g艂e</string>
+    <string name="contact_no_free_slots_description">Urz膮dzenie nie posiada wolnych miejsc na styki (ca艂kowita liczba miejsc: %1$s)</string>
+    <string name="pref_workout_keep_screen_on_summary">Ekran pozostanie w艂膮czony podczas treningu, a jasno艣膰 zostanie dostosowana tak, aby stale wy艣wietla膰 dane treningu w czasie rzeczywistym</string>
+    <string name="prefs_wena3_home_icon_name_pedometer">Liczba krok贸w</string>
+    <string name="contact_delete_confirm_description">Czy na pewno chcesz usun膮膰 \'%1$s\'\?</string>
+    <string name="fossil_hr_connection_not_confirmed">Po艂膮czenie nie zosta艂o potwierdzone na zegarku, przy u偶yciu trybu nieuwierzytelnionego</string>
+    <string name="wifi_hotspot_summary">Kontroluj hotspot Wi-Fi na zegarku</string>
+    <string name="prefs_wena3_home_icon_name_qrio">Qrio</string>
+    <string name="zepp_os_watchface_vibrant">Wibruj膮cy</string>
+    <string name="devicetype_miband2_hrx">Mi Band HRX</string>
+    <string name="menuitem_total_workout">Totalny trening</string>
+    <string name="fossil_hr_confirmation_skipped">Pomijanie potwierdzenia na urz膮dzeniu</string>
+    <string name="find_my_phone_found_it">ZNALAZ艁EM</string>
+    <string name="new_discover_activity_description">W艂膮cz wykrywanie nowej aktywno艣ci, kt贸ra powinna rozwi膮za膰 problemy z wykrywaniem urz膮dze艅. Wy艂膮cz t臋 opcj臋, je艣li napotkasz jakiekolwiek problemy podczas wyszukiwania lub parowania z urz膮dzeniem.</string>
+    <string name="error_no_bluetooth_scan">Aby skanowanie dzia艂a艂o prawid艂owo, nale偶y przyzna膰 i w艂膮czy膰 dost臋p do skanowania Bluetooth</string>
+    <string name="activity_type_snowboarding">Snowboard</string>
+    <string name="busy_task_fetch_stress_data">Pobieranie danych o obci膮偶eniu</string>
+    <string name="pref_app_logs_summary">W艂膮cz dzienniki aplikacji zegarka</string>
+    <string name="white">Bia艂y</string>
+    <string name="activity_type_riding">Jazda konna</string>
+    <string name="prefs_wena3_home_icon_name_clock">Obecny czas</string>
+    <string name="voice_service_package_title">Pakiet us艂ug g艂osowych</string>
+    <string name="prefs_wena3_item_background_sync">Synchronizacja danych dotycz膮cych aktywno艣ci w tle</string>
+    <string name="stress_mild">艁agodny</string>
+    <string name="prefs_wena3_vibration_strength_item_weak">S艂abe</string>
+    <string name="description">Opis</string>
+    <string name="prefs_wena3_home_icon_title">Ikony ekranu g艂贸wnego</string>
+    <string name="pref_show_changelog_summary">Wy艣wietl dziennik zmian od ostatniej wersji po aktualizacji Gadgetbridge</string>
+    <string name="busy_task_fetch_sleep_respiratory_rate_data">Pobieranie danych dotycz膮cych cz臋sto艣ci oddech贸w podczas snu</string>
+    <string name="debug_companion_pair_current">Sparuj bie偶膮ce urz膮dzenie jako towarzysza</string>
+    <string name="pref_morning_updates_summary">Wy艣wietlaj aktualizacje ka偶dego ranka</string>
+    <string name="menuitem_cards">Karty</string>
+    <string name="prefs_wena3_item_weather_statusbar">Pogoda w pasku stanu</string>
+    <string name="loyalty_cards_sync_groups_only">Synchronizuj tylko okre艣lone grupy</string>
+    <string name="loyalty_cards_catima_permissions_summary">Gadgetbridge potrzebuje uprawnie艅 do odczytu kart Catima, aby je zsynchronizowa膰. Dotknij tego przycisku, aby je przyzna膰.</string>
+    <string name="zepp_os_watchface_red_fantasy">Czerwona fantazja</string>
+    <string name="prefs_shortcut_cards">Karty skr贸t贸w</string>
+    <string name="temperature_scale_celsius">Skala Celciusza</string>
+    <string name="prefs_wena3_vibration_step_up">Zwi臋kszaj膮ce si臋</string>
+    <string name="prefs_home_icon_right_item">Do prawej</string>
+    <string name="title_activity_set_contacts">Konfiguracja kontakt贸w</string>
+    <string name="prefs_wena3_vibration_basic">Podstawowe</string>
+    <string name="quick_attention">Szybka uwaga</string>
+    <string name="loyalty_cards_sync_archived">Synchronizuj zarchiwizowane karty</string>
+    <string name="loyalty_cards_sync_groups">Grupy do synchronizacji</string>
+    <string name="prefs_wena3_title_screen">Ustawienia wy艣wietlacza</string>
+    <string name="ftp_server_root_dir">Katalog g艂贸wny</string>
+    <string name="prefs_wena3_item_large_font">Wi臋kszy rozmiar czcionki</string>
+    <string name="gpx_route_upload_in_progress">Przesy艂anie trasy gpx</string>
+    <string name="fossil_hr_pairing_failed">Parowanie z zegarkiem nie powiod艂o si臋</string>
+    <string name="english_au">Angielski (Australia)</string>
+    <string name="prefs_wena3_home_icon_name_energy">Energia cia艂a</string>
+    <string name="permission_request">%1$s umo偶liwia wysy艂anie wiadomo艣ci i innych danych z Androida na Twoje urz膮dzenie. Aby to zrobi膰, wymagany jest dost臋p do tych danych i bez niego mo偶e nie dzia艂a膰 poprawnie.
+\n
+\nZostanie wy艣wietlonych kilka okien dialogowych Androida z pro艣b膮 o te uprawnienia.
+\n
+\nAby kontynuowa膰, kliknij \'%2$s\'.</string>
+    <string name="loyalty_cards_catima_package">Nazwa pakietu Catima</string>
+    <string name="prefs_wena3_notification_vibration_repetition_2">Dwukrotnie</string>
+    <string name="prefs_wena3_auto_power_off_hint">Urz膮dzenie wy艂膮czy si臋 i w艂膮czy automatycznie wed艂ug okre艣lonego harmonogramu</string>
+    <string name="menuitem_vo2_max">Maks VO鈧�</string>
+    <string name="prefs_wena3_hint_lift_wrist">W艂膮cz wy艣wietlacz, patrz膮c na nadgarstek</string>
+    <string name="button_reset_menu_structure">Resetuj struktur臋 menu</string>
+    <string name="contact_name">Nazwa</string>
+    <string name="pref_title_navigation_forward">Wy艣lij nawigacj臋 do zegarka</string>
+    <string name="changelog_ok_button">Ok</string>
+    <string name="sony_button_mode_help_summary">Opis ka偶dego trybu przycisku</string>
+    <string name="drag_handle">Przeci膮gnij uchwyt</string>
+    <string name="prefs_wena3_hint_large_font">Zwi臋ksz rozmiar czcionki w kalendarzu, powiadomieniach itp.</string>
+    <string name="loyalty_cards_sync_summary">Kliknij, aby zsynchronizowa膰 karty z zegarkiem</string>
+    <string name="prefs_wena3_button_action_item_double">Podw贸jne naci艣ni臋cie</string>
+    <string name="loyalty_cards_open_catima">Otw贸rz Catim臋</string>
+    <string name="prefs_wena3_vibration_rapid">Szybkie</string>
+    <string name="text_receiver_activity_title">Wy艣lij SMS-a na urz膮dzenie</string>
+    <string name="prefs_wena3_button_action_name_qrio_unlock">Blokada Qrio</string>
+    <string name="error_menu_companion_not_installed">Prawdopodobnie nie zainstalowano aplikacji 鈥濰R Menu Companion鈥�</string>
+    <string name="temperature_scale_cf">Skala temperatury</string>
+    <string name="menuitem_last_workout">Ostatni trening</string>
+    <string name="voice_service_package_summary">Aplikacja zawieraj膮ca us艂ug臋 obs艂uguj膮c膮 polecenia g艂osowe</string>
+    <string name="prefs_wena3_notification_vibration_repetition_1">Jednorazowo</string>
+    <string name="prefs_wena3_receive_calls_item">Otrzymuj powiadomienia o po艂膮czeniach</string>
+    <string name="voice_service_class_summary">Pe艂na 艣cie偶ka us艂ugi obs艂uguj膮cej polecenia g艂osowe</string>
+    <string name="prefs_wena3_hint_weather_statusbar">Poka偶 ikon臋 aktualnych warunk贸w w lewym g贸rnym rogu ekranu g艂贸wnego</string>
+    <string name="busy_task_fetch_sports_details">Pobieranie szczeg贸艂贸w sportowych</string>
+    <string name="activity_detail_share_raw_summary">Udost臋pnij surowe podsumowanie</string>
+    <string name="prefs_wena3_home_icon_name_wena_pay">Wena Pay</string>
+    <string name="gpx_route_upload_failed">Przesy艂anie trasy Gpx nie powiod艂o si臋</string>
+    <string name="ftp_server_start_summary">Uruchom serwer FTP na zegarku</string>
+    <string name="pref_activity_full_sync_trigger_warning">Spowoduje to pe艂n膮 synchronizacj臋 wszystkich danych dotycz膮cych aktywno艣ci z urz膮dzenia. Uko艅czenie mo偶e zaj膮膰 kilka minut.</string>
+    <string name="pai_day">Dzienny wzrost</string>
+    <string name="voice_service">Us艂uga g艂osowa</string>
+    <string name="prefs_wena3_button_action_item">Akcja przycisku</string>
+    <string name="prefs_wena3_auto_power_off_turn_on_time_item">Czas wznowienia dzia艂ania</string>
+    <string name="changelog_title">Nowo艣ci</string>
+    <string name="pref_app_logs_title">Dzienniki aplikacji</string>
+    <string name="withings_calibration_text_minutes">Teraz u偶yj pokr臋t艂a, aby ustawi膰 wskaz贸wk臋 minutow膮 na godzinie 12.</string>
+    <string name="loyalty_cards_catima_permissions_title">Brak uprawnie艅</string>
+    <string name="loyalty_cards">Karty lojalno艣ciowe</string>
+    <string name="new_discover_activity_title">W艂膮cz wykrywanie nowej aktywno艣ci</string>
+    <string name="prefs_wena3_notification_default_call_vibration">Wibracja po艂膮czenia przychodz膮cego</string>
+    <string name="activity_type_baseball">Baseball</string>
+    <string name="green">Zielony</string>
+    <string name="gpx_route_upload_complete">Przesy艂anie trasy Gpx zosta艂o zako艅czone</string>
+    <string name="zepp_os_watchface_superposition">Superpozycja</string>
+    <string name="yellow">呕贸艂ty</string>
+    <string name="devicetype_sony_wena3">Sony Wena 3</string>
+    <string name="prefs_wena3_notification_default_call_led">Kolor diody LED po艂膮czenia przychodz膮cego</string>
+    <string name="pref_activity_full_sync_trigger_title">Pe艂na synchronizacja</string>
+    <string name="loyalty_cards_syncing">Synchronizowanie %d kart lojalno艣ciowych z urz膮dzeniem</string>
+    <string name="french_fr">Francuski (Francja)</string>
+    <string name="wifi_hotspot_stop_summary">Zatrzymaj hotspot Wi-Fi na zegarku</string>
+    <string name="intent_api_allow_debug_commands_title">Zezwalaj na polecenia debugowania</string>
+    <string name="fwapp_install_device_not_supported">Nie mo偶na zainstalowa膰 pliku, urz膮dzenie nie jest obs艂ugiwane.</string>
+    <string name="pref_theme_dynamic_colors_explanation">Uwaga: aby uzyska膰 motyw dynamicznych kolor贸w, musisz w艂膮czy膰 opcj臋 Kolory tapety lub Paleta kolor贸w w ustawieniach wygl膮du Androida 12+. Je艣li tego nie zrobisz, Gadgetbridge u偶yje domy艣lnych kolor贸w Material 3.</string>
+    <string name="zepp_os_watchface_simplicity_data">Proste dane</string>
+    <string name="red">Czerwony</string>
+    <string name="voice_service_class_title">Klasa us艂ug g艂osowych</string>
+    <string name="cyan">Jasnoniebieski</string>
+    <string name="wifi_hotspot_start_summary">Uruchom hotspot Wi-Fi na zegarku</string>
+    <string name="prefs_wena3_vibration_siren">Syrena</string>
+    <string name="temperature_scale_cf_summary">Wybierz, czy urz膮dzenie ma u偶ywa膰 skali Celsjusza czy Fahrenheita.</string>
+    <string name="prefs_wena3_menu_icon_title">Ikony Menu</string>
+    <string name="withings_bt_calibration_next">Dalej</string>
+    <string name="spanish_mx">Hiszpa艅ski (Meksyk)</string>
+    <string name="loyalty_cards_sync_starred">Synchronizuj tylko karty oznaczone gwiazdk膮</string>
+    <string name="changelog_full_title">Lista zmian</string>
+    <string name="pref_contacts_summary">Skonfiguruj kontakty na zegarku</string>
+    <string name="loyalty_cards_install_catima_fail">Nie uda艂o si臋 otworzy膰 sklepu z aplikacjami, aby zainstalowa膰 Catim臋</string>
+    <string name="preview_image">Miniaturka</string>
+    <string name="loyalty_cards_sync">Synchronizuj</string>
+    <string name="english_gb">Angielski (Wielka Brytania)</string>
+    <string name="prefs_wena3_notification_default_call_vibration_repetition">Powt贸rzenie wibracji po艂膮czenia przychodz膮cego</string>
+    <string name="intent_api_allow_debug_commands_summary">Zezwalaj na uruchamianie polece艅 menu debugowania za po艣rednictwem interfejsu Intent API</string>
+    <string name="loyalty_cards_sync_options">Opcje synchronizacji</string>
+    <string name="prefs_wena3_notification_default_vibration">Wibracja powiadomie艅</string>
+    <string name="loyalty_cards_catima_not_compatible">Zainstalowana wersja Catimy nie jest kompatybilna z Gadgetbridge. Zaktualizuj Catima i Gadgetbridge do najnowszych wersji.</string>
+    <string name="prefs_wena3_notification_vibration_repetition_0">W spos贸b nieokre艣lony</string>
+    <string name="activity_detail_share_raw_details">Udost臋pnij surowe szczeg贸艂y</string>
+    <string name="activity_type_windsurfing">Windsurfing</string>
+    <string name="prefs_wena3_notification_vibration_repetition_4">4 razy</string>
+    <string name="activity_type_handball">Pi艂ka r臋czna</string>
+    <string name="temperature_scale_fahrenheit">Skala Fahrenheita</string>
+    <string name="prefs_wena3_vibration_strength_item">Si艂a wibracji</string>
+    <string name="english_us">Angielski (Stany Zjednoczone)</string>
+    <string name="pref_theme_dynamic_colors_not_available_warning">Dynamiczne kolory nie s膮 dost臋pne na Twoim urz膮dzeniu, tylko Android 12+ obs艂uguje t臋 funkcj臋. Gadgetbridge u偶yje domy艣lnych kolor贸w Material 3.</string>
+    <string name="withings_bt_calibration_previous">Wstecz</string>
+    <string name="ftp_server_stop_summary">Zatrzymaj serwer FTP na zegarku</string>
+    <string name="changelog_show_full">Wi臋cej鈥�</string>
+    <string name="spanish_us">Hiszpa艅ski (Stany Zjednoczone)</string>
+    <string name="prefs_wena3_receive_calls_hint">Je艣li ta opcja jest wy艂膮czona, nie b臋dziesz otrzymywa膰 powiadomie艅 o po艂膮czeniach przychodz膮cych na urz膮dzeniu Wena</string>
+    <string name="zepp_os_watchface_multiple_data">Wiele danych</string>
+    <string name="prefs_wena3_button_action_item_long">D艂ugie naci艣ni臋cie</string>
+    <string name="prefs_wena3_menu_icon_name_payment">P艂atno艣ci</string>
+    <string name="latvian">艁otewski</string>
+    <string name="prefs_wena3_status_page_title">Strona stanu zam贸wie艅</string>
+    <string name="single_tap">Pojedyncze dotkni臋cie</string>
+    <string name="prefs_wena3_title_alarm">Ustawienia alarm贸w</string>
+    <string name="info_menu_structure_removed">Struktura menu usuni臋ta</string>
+    <string name="prefs_wena3_notification_default_vibration_repetition">Powt贸rzenie wibracji powiadomienia</string>
+    <string name="prefs_home_icon_center_item">W 艣rodku</string>
+    <string name="zepp_os_watchface_rotating_earth">Obracaj膮ca si臋 Ziemia</string>
+    <string name="activity_type_kitesurfing">Kitesurfing</string>
+    <string name="prefs_wena3_hint_rich_design">Dodaje zaokr膮glone prostok膮ty wok贸艂 ikon ekranu g艂贸wnego</string>
+    <string name="info_fossil_rebuild_watchface_custom_menu">Przebuduj tarcz臋 zegarka, aby uzyska膰 niestandardowe menu</string>
+    <string name="withings_steel_hr">Withings Steel HR</string>
+    <string name="pai_total">Ca艂kowite</string>
+    <string name="zepp_os_watchface_minimalist">Minimalistyczny</string>
+    <string name="loyalty_cards_catima_not_installed">Catima jest potrzebna do zarz膮dzania kartami lojalno艣ciowymi</string>
+    <string name="prefs_wena3_notification_default_led">Kolor diody powiadomie艅</string>
+    <string name="sony_speak_to_chat_summary">Wy艂膮cz automatycznie redukcj臋 szum贸w, gdy zaczniesz m贸wi膰.</string>
+    <string name="prefs_wena3_auto_power_off_item">Automatyczny wy艂膮cznik</string>
+    <string name="prefs_wena3_home_icon_name_edy">R贸wnowaga Edy</string>
+    <string name="prefs_wena3_vibration_warning">Ostrze偶enie</string>
+    <string name="prefs_wena3_smart_alarm_margin_item">Margines inteligentnego alarmu</string>
+    <string name="menuitem_mi_ai">MI AI</string>
+    <string name="contact_phone_number">Numer telefonu</string>
+    <string name="fossil_hr_pairing_successful">Parowanie z zegarkiem powiod艂o si臋</string>
+    <string name="prefs_wena3_item_rich_design">U偶yj bogatego projektu</string>
+    <string name="english_ca">Angielski (Kanada)</string>
+    <string name="prefs_wena3_day_start_hour_item">Dzie艅 zaczyna si臋 o</string>
+    <string name="error_no_bluetooth_connect">Aby skanowanie dzia艂a艂o prawid艂owo, nale偶y przyzna膰 i w艂膮czy膰 dost臋p do po艂膮czenia Bluetooth</string>
+    <string name="prefs_shortcut_cards_summary">Karty skr贸t贸w widoczne po przesuni臋ciu palcem w prawo na tarczy zegarka. To ustawienie nie ma wp艂ywu na automatycznie generowane karty, gdy aplikacja jest uruchomiona.</string>
+    <string name="zepp_os_watchface_rush">Po艣piech</string>
+    <string name="info_menu_structure_set">Struktura menu JSON ustawiona w GB</string>
+    <string name="busy_task_fetch_pai_data">Pobieranie danych PAI</string>
+    <string name="prefs_wena3_notification_per_app_settings_title">Ustawienia powiadomie艅 dla poszczeg贸lnych aplikacji</string>
+    <string name="withings_calibration_text_hours">U偶yj poni偶szego pokr臋t艂a, aby ustawi膰 wskaz贸wk臋 godzinow膮 na godzinie 12.</string>
+    <string name="prefs_wena3_vibration_none">Brak wibracji</string>
+    <string name="purple">Fioletowy</string>
+    <string name="prefs_wena3_vibration_strength_item_strong">Mocne</string>
+    <string name="prefs_wena3_vibration_triple">Potr贸jne</string>
+    <string name="blue">Niebieski</string>
+    <string name="prefs_wena3_button_action_name_activity_screen">Ekran aktywno艣ci</string>
+    <string name="prefs_wena3_notification_settings_title">Ustawienia powiadomie艅</string>
+    <string name="button_open_menu_companion">Otw贸rz menu towarzysz膮ce aplikacji</string>
+    <string name="devicetype_sony_wf_1000xm5">Sony WF-1000XM5</string>
+    <string name="continue_pressing">Ci膮g艂e naciskanie</string>
+    <string name="triple_tap">Potr贸jne dotkni臋cie</string>
+    <string name="pref_title_notifications_ignore_work_profile">Ignoruj powiadomienia z profilu s艂u偶bowego</string>
+    <string name="pref_summary_notifications_ignore_work_profile">Nie wysy艂aj powiadomie艅 z profilu s艂u偶bowego do zegarka</string>
+    <string name="common_symbols">Znaki wsp贸lne</string>
+    <string name="zepp_os_watchface_pure_white">Czysty bia艂y</string>
+    <string name="call_rejection_method_ignore">Ignoruj (cisza)</string>
+    <string name="avgStrokeRate">艢rednia cz臋stotliwo艣膰 uderze艅</string>
+    <string name="zepp_os_watchface_the_ultima">Ostateczna</string>
+    <string name="zepp_os_watchface_city_of_speed">Miejska pr臋dko艣膰</string>
+    <string name="menuitem_headphone">S艂uchawki</string>
+    <string name="femometer_measurement_mode_normal">tryb normalny (60~90s)</string>
+    <string name="call_rejection_method_reject">Odrzu膰</string>
+    <string name="zepp_os_watchface_free_combination">Dowolna kombinacja</string>
+    <string name="menuitem_body_composition">Sk艂ad cia艂a</string>
+    <string name="menuitem_zepp_pay">Zepp Pay</string>
+    <string name="pref_call_rejection_method_title">Metoda odrzucenia po艂膮czenia</string>
+    <string name="busy_task_fetch_statistics">Pobieram statystyki</string>
+    <string name="femometer_measurement_mode_quick">Tryb szybki (30s)</string>
+    <string name="zepp_os_watchface_starry_sky">Gwia藕dziste niebo</string>
+    <string name="devicetype_amazfit_balance">Amazfit Balance</string>
+    <string name="fossil_hr_nav_app_not_installed_notify_text">Nawigacja zosta艂a uruchomiona, ale na zegarku nie zainstalowano aplikacji do nawigacji. Zainstaluj j膮 z Mened偶era aplikacji.</string>
+    <string name="yard">jard</string>
+    <string name="strokes_minute">uderze艅/min</string>
+    <string name="femometer_measurement_mode_title">Tryb pomiaru</string>
+    <string name="pref_call_rejection_method_summary">Dzia艂anie jakie jest podejmowane, gdy po艂膮czenie przychodz膮ce zostanie odrzucone przez zegarek</string>
+    <string name="busy_task_fetch_temperature">Pobieram dane o temperaturze</string>
+    <string name="femometer_measurement_mode_precise">Tryb dok艂adny (3min)</string>
+    <string name="zepp_os_watchface_vast_sky">Ogromne Niebo</string>
+    <string name="menuitem_workout_shortcuts">Skr贸ty treningowe</string>
+    <string name="maxStrokeRate">Maksymalna cz臋stotliwo艣膰 uderze艅</string>
+    <string name="zepp_os_watchface_lightning_flash">B艂yskawica</string>
+    <string name="laneLength">D艂ugo艣膰 pasa</string>
+    <string name="strokes">Suma uderze艅</string>
+    <string name="menuitem_apps_shortcuts">Skr贸ty do aplikacji</string>
+    <string name="zepp_os_watchface_guider">Przewodnik</string>
+    <string name="menuitem_readiness">Gotowo艣膰</string>
+    <string name="menuitem_thermometer">Termometr</string>
+    <string name="devicetype_femometer_vinca2">Femometer Vinca II</string>
+    <string name="fossil_hr_nav_app_not_installed_notify_title">Aplikacja do nawigacji nie jest zainstalowana na zegarku</string>
+    <string name="devicetype_amazfit_active_edge">Amazfit Active Edge</string>
+    <string name="charts_legend_spo2_average">艢rednia zawarto艣膰 tlenu we krwi</string>
+    <string name="devicetype_amazfit_active">Amazfit Active</string>
+    <string name="pref_title_navigation_apps">Aplikacje nawigacyjne</string>
+    <string name="pref_title_navigation_prefs">Preferencje nawigacji</string>
+    <string name="pref_navigation_app_gmaps">Mapy Google</string>
+    <string name="pref_summary_prefix_notification_with_app">Tytu艂 powiadomienia poprzed藕 nazw膮 aplikacji 藕r贸d艂owej</string>
+    <string name="pref_summary_osmand_packagename">S艂u偶y do wyboru wersji OsmAnd, z kt贸r膮 chcesz si臋 po艂膮czy膰</string>
+    <string name="pref_title_prefix_notification_with_app">Nazwa aplikacji w powiadomieniu</string>
+    <string name="pref_title_osmand_packagename">Nazwa pakietu OsmAnd</string>
+    <string name="pref_navigation_app_osmand">OsmAnd(+)</string>
+    <string name="pref_title_casio_alert_other">Alert dla pozosta艂ych powiadomie艅</string>
+    <string name="pref_title_casio_alert_calendar">Alert powiadomie艅 z kalendarza</string>
+    <string name="devicetype_mi_watch_color_sport">Mi Watch Color Sport</string>
+    <string name="pref_summary_casio_alert_call">Alert (wibracja/beep) dla po艂膮cze艅 przychodz膮cych</string>
+    <string name="devicetype_miband8">Xiaomi Smart Band 8</string>
+    <string name="menuitem_running">Bieganie</string>
+    <string name="pref_sleep_mode_schedule_title">Harmonogram snu</string>
+    <string name="pref_title_casio_alert_call">Alert dla po艂膮cze艅 przychodz膮cych</string>
+    <string name="bedtime">Pora snu</string>
+    <string name="pref_title_casio_alert_email">Alert powiadomie艅 email</string>
+    <string name="pref_summary_casio_alert_email">Alert (wibracja/beep) dla powiadomie艅 email</string>
+    <string name="menuitem_focus">Skupienie</string>
+    <string name="pref_summary_casio_alert_sms">Alert (wibracja/beep) dla powiadomie艅 SMS (wiadomo艣ci tekstowych)</string>
+    <string name="pref_title_preview_message_in_title">Poka偶 podgl膮d wiadomo艣ci w tytule</string>
+    <string name="devicetype_redmiwatch3active">Redmi Watch 3 Active</string>
+    <string name="pref_summary_casio_alert_calendar">Alert (wibracja/beep) dla powiadomie艅 z kalendarza</string>
+    <string name="serial_number">Numer seryjny</string>
+    <string name="danish">Du艅ski</string>
+    <string name="wake_up_time">Pobudka</string>
+    <string name="devicetype_miband7pro">Xiaomi Smart Band 7 Pro</string>
+    <string name="menuitem_stats">Statystyki</string>
+    <string name="pref_summary_casio_alert_other">Alert (wibracja/beep) dla pozosta艂ych powiadomie艅</string>
+    <string name="devicetype_xiaomi_watch_lite">Xiaomi Watch Lite</string>
+    <string name="pref_summary_preview_message_in_title">Poka偶 podgl膮d wiadomo艣ci w tytule powiadomienia, je偶eli udzielone jest odpowiednie uprawnienie</string>
+    <string name="pref_sleep_mode_schedule_summary">Wy艣lij przypomnienie i przed snem wejd藕 w tryb u艣pienia. O zaplanowanej godzinie pobudki zabrzmi alarm.</string>
+    <string name="pref_title_casio_alert_sms">Alert powiadomie艅 SMS</string>
+    <string name="menuitem_alerts">Alerty</string>
+    <string name="devicetype_xiaomi_watch_s1_active">Xiaomi Watch S1 Active</string>
+    <string name="devicetype_pixoo">Pixoo</string>
+    <string name="pref_title_goal_secondary">Drugi punkt</string>
+    <string name="pref_summary_send_app_notifications">Wysy艂aj powiadomienia do urz膮dzenia</string>
+    <string name="wearmode_band">Opaska (na nadgarstek)</string>
+    <string name="pref_title_send_app_notifications">Wysy艂aj powiadomienia</string>
+    <string name="activity_type_trail_run">Bieg szlakiem</string>
+    <string name="widget_layout">Uk艂ad wid偶etu</string>
+    <string name="widget_screen_no_free_slots_description">Urz膮dzenie nie ma wolnych miejsc na ekrany wid偶et贸w (艂膮czna liczba miejsc: %1$s)</string>
+    <string name="pref_device_action_dnd_on">Nie przeszkadza膰 鈥� W艂膮czone</string>
+    <string name="prefs_phone_silent_mode">Tryb cichy telefonu</string>
+    <string name="widget_unknown_workout">Nieznany trening - %s</string>
+    <string name="prefs_wearmode">Tryb noszenia</string>
+    <string name="wearmode_pebble">Pebble (klamra do but贸w)</string>
+    <string name="widget_move_up">Przesu艅 w g贸r臋</string>
+    <string name="widget_screen_delete_confirm_description">Czy na pewno chcesz usun膮膰 \'%1$s\'?</string>
+    <string name="clap_hands_to_wakeup_device">Kla艣nij d艂o艅mi, aby wybudzi膰 ekran</string>"
+    <string name="silent_mode_normal_vibrate">Normalny / Wibracje</string>
+    <string name="pref_vitality_score_7_day_summary">Otrzymuj powiadomienie, gdy Tw贸j wynik witalno艣ci osi膮gnie 30, 60 lub 100 w ci膮gu ostatnich 7 dni</string>
+    <string name="activity_type_trekking">Trekking</string>
+    <string name="pref_vitality_score_title">Wynik witalno艣ci</string>
+    <string name="widget_layout_two">2 wid偶ety</string>
+    <string name="widget_screen_delete_confirm_title">Usu艅 ekran wid偶etu</string>
+    <string name="pref_device_action_dnd_alarms">Nie przeszkadza膰 鈥� tylko alarmy</string>
+    <string name="clap_hands_to_wakeup_device_summary">Ponownie kla艣ni臋cie spowoduje wygaszenie ekranu</string>"
+    <string name="widget_layout_top_1_bot_2">1 g贸ra, 2 d贸艂</string>
+    <string name="devicetype_colacao23">ColaCao 2023</string>
+    <string name="widget_layout_top_2_bot_2">2 g贸ra, 2 d贸艂</string>
+    <string name="devicetype_mijia_lywsd03">Czujnik temperatury i wilgotno艣ci Mijia 2</string>
+    <string name="widget_screen_min_screens">Musi by膰 co najmniej %1$s ekran贸w</string>
+    <string name="silent_mode_vibrate_silent">Wibracje / Cichy</string>
+    <string name="widget">Wid偶et</string>
+    <string name="pref_vitality_score_daily_summary">Otrzymuj powiadomienie, gdy osi膮gniesz maksymaln膮 liczb臋 punkt贸w witalno艣ci na dany dzie艅</string>
+    <string name="devicetype_redmi_smart_band_2">Redmi Smart Band 2</string>
+    <string name="widget_screen">Ekran wid偶etu</string>
+    <string name="widget_move_down">Przesu艅 w d贸艂</string>
+    <string name="widget_layout_single">1 wid偶et</string>
+    <string name="pref_device_action_dnd_priority">Nie przeszkadza膰 鈥� tylko priorytetowe</string>
+    <string name="not_set">Nie ustawiono</string>
+    <string name="widget_subtype">Podtyp wid偶etu</string>
+    <string name="standing_time">Czas stania</string>
+    <string name="active_time">Czas aktywno艣ci</string>
+    <string name="widget_screen_x">Ekran %s</string>
+    <string name="widget_missing_parts">Wybierz wszystkie wid偶ety</string>
+    <string name="widget_layout_top_2_bot_1">2 g贸ra, 1 d贸艂</string>
+    <string name="devicetype_colacao21">ColaCao 2021</string>
+    <string name="wearmode_necklace">Necklace (pasek na szyj臋)</string>
+    <string name="pixoo_power_saving_summary">Ekran wy艂膮czy si臋, gdy mikrofon wykryje przez chwil臋 cisz臋</string>
+    <string name="pref_vitality_score_daily_title">Dzienny post臋p</string>
+    <string name="pref_vitality_score_7_day_title">7-dniowy post臋p</string>
+    <string name="pref_device_action_dnd_off">Nie przeszkadza膰 鈥� wy艂膮czone</string>
+    <string name="silent_mode_normal_silent">Normalny / Cichy</string>
+    <string name="activity_type_wrestling">Zapasy</string>
+    <string name="pref_title_fossil_hr_navigation_instructions">Instrukcje nawigacji</string>
+    <string name="pref_summary_fossil_hr_navigation_instructions">Konfiguracja zachowania aplikacji do nawigacji w zegarku</string>
+    <string name="pref_summary_fossil_hr_nav_foreground">Okre艣l, czy aplikacja nawigacyjna powinna automatycznie wy艣wietla膰 si臋 na pierwszym planie po otrzymaniu instrukcji nawigacyjnych</string>
+    <string name="pref_title_fossil_hr_nav_vibrate">Wibruj przy nowej instrukcji</string>
+    <string name="pref_title_fossil_hr_nav_foreground">Przejd藕 do pracy na pierwszym planie</string>
+    <string name="prefs_device_name">Nazwa urz膮dzenia</string>
+    <string name="pref_summary_fossil_hr_nav_vibrate">Wibracje zegarka przy ka偶dej nowej lub zmienionej instrukcji nawigacji (tylko gdy aplikacja jest na pierwszym planie)</string>
+    <string name="devicetype_redmi_watch_2_lite">Redmi Watch 2 Lite</string>
+    <string name="notification_channel_connection_status_name">Stan po艂膮czenia</string>
+    <string name="devicetype_redmi_smart_band_pro">Redmi Smart Band Pro</string>
+    <string name="devicetype_nothingear2">Nothing Ear (2)</string>
+    <string name="devicetype_nothingearstick">Nothing Ear (Stick)</string>
+    <string name="prefs_active_noise_cancelling_light">Lekka aktywna redukcja szum贸w</string>
+    <string name="prefs_active_noise_cancelling_transparency">Przezroczyste</string>
+    <string name="devicetype_huawei_talk_band_b6">Huawei Talk Band B6</string>
+    <string name="devicetype_huawei_watchgt2e">Huawei Watch GT 2e</string>
+    <string name="devicetype_honor_band4">Honor Band 4</string>
+    <string name="devicetype_honor_band5">Honor Band 5</string>
+    <string name="devicetype_honor_band7">Honor Band 7</string>
+    <string name="devicetype_huawei_watchgt2">Huawei Watch GT 2 (Pro)</string>
+    <string name="devicetype_huawei_band4pro">Huawei Band 4 (Pro)</string>
+    <string name="devicetype_huawei_watch_gt">Huawei Watch GT</string>
+    <string name="devicetype_huawei_band8">Huawei Band 8</string>
+    <string name="devicetype_huawei_band7">Huawei Band 7</string>
+    <string name="pref_summary_debug">Wy艣lij 偶膮danie debugowania do urz膮dzenia Huawei</string>
+    <string name="pref_title_debug">呕膮danie debugowania</string>
+    <string name="huawei_trusleep_title">HUAWEI TruSleep 鈩�</string>
+    <string name="devicetype_huawei_watchgt3">Huawei Watch GT 3 (Pro)</string>
+    <string name="devicetype_huawei_band6">Huawei Band 6</string>
+    <string name="devicetype_honor_band3">Honor Band 3</string>
+    <string name="devicetype_honor_band6">Honor Band 6</string>
+    <string name="devicetype_huawei_band_aw70">Huawei Band (AW70)</string>
+    <string name="pref_spo_automatic_enable">W艂膮cz automatyczne mierzenie SpO2</string>
+    <string name="manual">R臋cznie</string>
+    <string name="pref_title_general_reconnectonlytoconnected">Po艂膮cz si臋 ponownie tylko z po艂膮czonymi urz膮dzeniami</string>
+    <string name="pref_summary_general_reconnectonlytoconnected">Po艂膮cz si臋 ponownie tylko z po艂膮czonymi urz膮dzeniami, zamiast ponownego 艂膮czenia z wszystkimi urz膮dzeniami</string>
+    <string name="huawei_reparse_workout_data_description">Ta opcja b臋dzie dzia艂a膰 dopiero w nowszej wersji aplikacji</string>
+    <string name="updatefirmwareoperation_updateproblem_low_battery">Poziom baterii urz膮dzenia jest zbyt niski</string>
+    <string name="dnd_all_day">Ca艂y dzie艅</string>
+    <string name="pref_force_options_summary">Niekt贸re urz膮dzenia fa艂szywie twierdz膮, 偶e nie maj膮 wsparcia dla pewnych funkcji. Tej opcji mo偶na u偶y膰 by w艂膮czy膰 je pomimo tego.
+\nKONTYNUUJ NA W艁ASNE RYZYKO!
+\nPrzeczytaj wiki</string>
+    <string name="prefs_disconnect_notification_summary">Powiadomienie na urz膮dzeniu, po roz艂膮czeniu Bluetooth.</string>
+    <string name="pref_force_options">Wymu艣 opcje</string>
+    <string name="pref_do_not_disturb_not_wear">Nie przeszkadza膰, gdy nie noszone</string>
+    <string name="prefs_workmode">Tryb pracy</string>
+    <string name="huawei_alarm_smart_description">Nie odznaczaj inteligentnego wybudzania.</string>
+    <string name="huawei_alarm_event_description">Nie zaznaczaj inteligentnego wybudzania.</string>
+    <string name="huawei_trusleep_summary">Monitoruj jako艣膰 snu i spos贸b oddychania w czasie rzeczywistym.
+\nPrzeanalizuj swoje wzorce snu i dok艂adnie zdiagnozuj 6 rodzaj贸w problem贸w ze snem.</string>
+    <string name="pref_force_smart_alarm">Wymu艣 inteligentny alarm</string>
+    <string name="huawei_ignore_wakeup_status_start">Ignoruj stan rozpocz臋cia wybudzania</string>
+    <string name="huawei_ignore_wakeup_status_start_description">Mo偶e pom贸c w prawid艂owym wykrywaniu snu. Widoczne natychmiast w widoku codziennych aktywno艣ci.</string>
+    <string name="huawei_ignore_wakeup_status_end">Ignoruj stan zako艅czenia wybudzania</string>
+    <string name="huawei_ignore_wakeup_status_end_description">Mo偶e pom贸c w prawid艂owym wykrywaniu snu. Widoczne natychmiast w widoku codziennych aktywno艣ci.</string>
+    <string name="huawei_reparse_workout_data">Przeanalizuj ponownie dane treningu</string>
+    <string name="pref_force_smart_alarm_summary">Wymu艣 obs艂ug臋 inteligentnych alarm贸w.
+\nU呕YWAJ NA W艁ASNE RYZYKO</string>
+    <string name="pref_force_wear_location">Wymu艣 lokalizacj臋 zu偶ycia</string>
+    <string name="pref_force_wear_location_summary">Wymuszone wsparcie lokalizacji zu偶ycia.
+\nU呕YWAJ NA W艁ASNE RYZYKO</string>
+    <string name="menuitem_buzzer_intensity">Intensywno艣膰 brz臋czyka</string>
+    <string name="do_not_disturb_lift_wrist_summary">Jedynie gdy jest w艂膮czona opcja: podnie艣, aby wybudzi膰 ekran</string>
+    <string name="pref_enable_call_accept_summary">W艂膮cz odbieranie po艂膮cze艅 z poziomu urz膮dzenia</string>
+    <string name="pref_enable_call_accept">W艂膮cz odbieranie po艂膮cze艅</string>
+    <string name="pref_enable_call_reject">W艂膮cz odrzucanie po艂膮cze艅</string>
+    <string name="pref_enable_call_reject_summary">W艂膮cz odrzucanie po艂膮cze艅 z poziomu urz膮dzenia</string>
+    <string name="pref_heartrate_automatic_enable">W艂膮cz automatyczny pomiar t臋tna</string>
+    <string name="pref_disable_find_phone_with_dnd">Wyl膮cz funkcj臋 Znajd藕 m贸j telefon, gdy tryb Nie przeszkadza膰 jest w艂膮czony</string>
+    <string name="pref_force_dnd_support_summary">Wymu艣 tryb Nie Przeszkadza膰. \nU呕YWAJ NA W艁ASNE RYZYKO</string>
+    <string name="pref_force_dnd_support">Wymu艣 wspacie dla trybu Nie przeszkadza膰</string>
+    <string name="huawei_trusleep_summary_light">Poprawione monitorowanie snu</string>
+    <string name="prefs_password_4_digits_0_to_9_summary">Has艂o musi sk艂ada膰 si臋 z 4 znak贸w i zawiera膰 jedynie cyfry</string>
+    <string name="flatDistance">P艂aski dystans</string>
+    <string name="sony_protocol_v1">Wersja 1</string>
+    <string name="sony_protocol_v2">Wersja 2</string>
+    <string name="sony_protocol_v3">Wersja 3</string>
+    <string name="devicetype_xiaomi_watch_s1">Xiaomi Watch S1</string>
+    <string name="protocol_version">Wersja protoko艂u</string>
+    <string name="devicetype_xiaomi_watch_s3">Xiaomi Watch S3</string>
+    <string name="devicetype_xiaomi_watch_s1_pro">Xiaomi Watch S1 Pro</string>
+    <string name="uploadwatchfaceoperation_failed">Instalacja tarczy zegarka nie powiod艂a si臋</string>
+    <string name="uploadwatchfaceoperation_complete">Instalacja tarczy zegarka zako艅czona</string>
+    <string name="uploadwatchfaceoperation_in_progress">Wysy艂anie tarczy zegarka</string>
+    <string name="uploading_watchface">Wysy艂anie tarczy zegarka鈥�</string>
+    <string name="pref_force_connection_type_title">Wymu艣 typ po艂膮czenia</string>
+    <string name="devicetype_miband8pro">Xiaomi Smart Band 8 Pro</string>
+    <string name="devicetype_mijia_mho_c303">Mijia MHO-C303</string>
+    <string name="activity_info">Info o aktywno艣ci</string>
+    <string name="pref_force_connection_type_description">Mo偶esz spr贸bowa膰 wymusi膰 typ po艂膮czenia na wypadek, gdyby urz膮dzenie nie odpowiedzia艂o na Gadgetbridge</string>
+    <string name="pref_force_connection_type_auto">Automatycznie</string>
+    <string name="pref_force_connection_type_ble">Bluetooth LE</string>
+    <string name="pref_force_connection_type_bt_classic">Bluetooth Classic</string>
+    <string name="heartrate_bpm_155">155 uderze艅/min</string>
+    <string name="heartrate_bpm_165">165 uderze艅/min</string>
+    <string name="heartrate_bpm_175">175 uderze艅/min</string>
+    <string name="heartrate_bpm_185">185 uderze艅/min</string>
+    <string name="heartrate_bpm_195">195 uderze艅/min</string>
+    <string name="heartrate_bpm_205">205 uderze艅/min</string>
+    <string name="activity_type_cross_trainer">Orbitrek</string>
+    <string name="activity_type_rower">Ergometr wio艣larski</string>
+    <string name="activity_type_crossfit">Crossfit</string>
+    <string name="activity_type_physical_training">Trening fizyczny</string>
+    <string name="activity_type_karate">Karate</string>
+    <string name="activity_type_fitness_gaming">Gra fitness</string>
+    <string name="activity_type_aerobic_exercise">膯wiczenia aerobowe</string>
+    <string name="activity_type_push_ups">Pompki</string>
+    <string name="activity_type_pull_ups">Podci膮ganie si臋</string>
+    <string name="activity_type_plank">Plank</string>
+    <string name="activity_type_trampoline">Trampolina</string>
+    <string name="activity_type_jazz_dance">Taniec jazzowy</string>
+    <string name="activity_type_latin_dance">Taniec latynoski</string>
+    <string name="activity_type_ballet">Balet</string>
+    <string name="activity_type_other_dance">Inny taniec</string>
+    <string name="activity_type_roller_skating">Jazda na rolkach</string>
+    <string name="activity_type_martial_arts">Sztuki walki</string>
+    <string name="activity_type_tai_chi">Tai chi</string>
+    <string name="activity_type_hula_hooping">Hula hop</string>
+    <string name="activity_type_disc_sports">Sporty z dyskiem</string>
+    <string name="activity_type_darts">Rzutki</string>
+    <string name="activity_type_archery">艁ucznictwo</string>
+    <string name="activity_type_horse_riding">Jazda konno</string>
+    <string name="activity_type_kite_flying">Puszczanie latawca</string>
+    <string name="activity_type_stairs">Schody</string>
+    <string name="activity_type_fishing">W臋dkarstwo</string>
+    <string name="activity_type_horizontal_bar">Dr膮偶ek</string>
+    <string name="activity_type_parallel_bar">Por臋cz symetryczna</string>
+    <string name="activity_type_flexibility">Rozci膮ganie</string>
+    <string name="activity_type_kabaddi">Kabaddi</string>
+    <string name="activity_type_karting">Karting</string>
+    <string name="activity_type_billiards">Bilard</string>
+    <string name="activity_type_australian_football">Futbol australijski</string>
+    <string name="activity_type_pickleball">Pickleball</string>
+    <string name="activity_type_sailing">呕eglarstwo</string>
+    <string name="activity_type_ice_hockey">Hokej na lodzie</string>
+    <string name="activity_type_snow_sports">Sporty zimowe</string>
+    <string name="activity_type_skateboarding">Jazda na deskorolce</string>
+    <string name="activity_type_rock_climbing">Wspinaczka ska艂kowa</string>
+    <string name="activity_type_dodgeball">Dwa ognie</string>
+    <string name="devicetype_sony_wi_sp600n">Sony WI-SP600N</string>
+    <string name="devicetype_honor_magicwatch2">Honor MagicWatch 2</string>
+    <string name="devicetype_nothing_cmf_watch_pro">CMF Watch Pro</string>
+    <string name="activity_type_outdoor_walking">Spacer na zewn膮trz</string>
+    <string name="hydration_dnd_summary">Wy艂膮cz przypomnienia o nawadnianiu dla zadanego przedzia艂u czasu</string>
+    <string name="prefs_heartrate_alert_active_high_threshold">Pr贸g alarmowy t臋tna podczas intensywnej aktywno艣ci</string>
+    <string name="activity_type_indoor_running">Bieg wewn膮trz</string>
+    <string name="activity_type_mountain_hike">W臋dr贸wka g贸rska</string>
+    <string name="activity_type_fitness_exercises">Trening fitness</string>
+    <string name="activity_type_taekwondo">Taekwondo</string>
+    <string name="activity_type_fencing">Szermierka</string>
+    <string name="activity_type_kendo">Kendo</string>
+    <string name="activity_type_hunting">Polowanie</string>
+    <string name="activity_type_sit_ups">Przysiady</string>
+    <string name="activity_type_softball">Softball</string>
+    <string name="devicetype_redmiwatch3">Redmi Watch 3</string>
+    <string name="devicetype_redmi_watch_2">Redmi Watch 2</string>
+    <string name="pref_test_features_title">Funkcje</string>
+    <string name="alarm_smart_wakeup_interval_default">5 minut</string>
+    <string name="activity_type_cross_country_running">Bieg prze艂ajowy</string>
+    <string name="activity_type_belly_dance">Taniec brzucha</string>
+    <string name="alarm_smart_wakeup_interval">Inteligentny interwa艂 budzenia:</string>
+    <string name="devicetype_huawei_watchfit">Huawei Watch Fit</string>
+    <string name="temperature">Temperatura</string>
+    <string name="activity_type_long_jump">Skok w dal</string>
+    <string name="activity_type_high_jump">Skok wzwy偶</string>
+    <string name="devicetype_miband8active">Xiaomi Smart Band 8 Active</string>
+    <string name="minutes_20">20 minut</string>
+    <string name="sleep_colored_stats_deep">G艂臋boki</string>
+    <string name="sleep_colored_stats_light">P艂ytki</string>
+    <string name="sleep_colored_stats_deep_avg">艢rednia g艂臋bokiegio</string>
+    <string name="sleep_colored_stats_light_avg">艢rednia p艂ytkiego</string>
+    <string name="sleep_colored_stats_rem_avg">艢rednia REM</string>
+    <string name="stats_empty_value">-</string>
+    <string name="pref_header_audio">Audio</string>
+    <string name="minutes_60">60 minut</string>
+    <string name="sleep_colored_stats_rem">REM</string>
+    <string name="proprietary_app_warning">Ta funkcja wymaga instalacji wlasno艣ciowej aplikacji</string>
+    <string name="gps_glonass">GPS + GLONASS</string>
+    <string name="devicetype_amazfit_bip5_unity">Amazfit Bip 5 Unity</string>
+    <string name="pref_title_notification_times_enabled">Czas powiadomie艅</string>
+    <string name="pref_summary_notification_times_enabled">Wysy艂aj powiadomienia jedynie o okre艣lonej porze</string>
+    <string name="pref_agps_status">Status AGPS</string>
+    <string name="agps_status_error">B艂膮d</string>
+    <string name="activity_type_luge">Saneczkarstwo</string>
+    <string name="show_in_notification">Wy艣wietlaj w powiadomieniach</string>
+    <string name="devicetype_garmin_epix_pro">Garmin Epix Pro</string>
+    <string name="stats_lowest_hr">Najni偶sze t臋tno</string>
+    <string name="stats_highest_hr">Najwy偶sze t臋tno</string>
+    <string name="devicetype_garmin_fenix_6">Garmin Fenix 6</string>
+    <string name="devicetype_garmin_fenix_5">Garmin Fenix 5</string>
+    <string name="transition">Przej艣cie</string>
+    <string name="activity_type_tae_bo">Tae Bo</string>
+    <string name="activity_type_beach_soccer">Pi艂ka no偶na pla偶owa</string>
+    <string name="activity_type_beach_volleyball">Siatk贸wka pla偶owa</string>
+    <string name="activity_type_gateball">Gateball</string>
+    <string name="activity_type_parachuting">Spadochroniarstwo</string>
+    <string name="activity_type_parkour">Parkour</string>
+    <string name="activity_type_video_gaming">Gry komputerowe</string>
+    <string name="activity_type_bmx">BMX</string>
+    <string name="activity_type_board_game">Gra planszowa</string>
+    <string name="activity_type_bocce">Bocce</string>
+    <string name="activity_type_checkers">Warcaby</string>
+    <string name="activity_type_chess">Szachy</string>
+    <string name="activity_type_dragon_boat">Smocze 艂odzie</string>
+    <string name="activity_type_esports">Esport</string>
+    <string name="activity_type_finswimming">P艂ywanie w p艂etwach</string>
+    <string name="activity_type_flowriding">Sztuczna fala</string>
+    <string name="devicetype_garmin_vivomove_hr">Garmin V铆vomove HR</string>
+    <string name="devicetype_garmin_vivomove_style">Garmin V铆vomove Style</string>
+    <string name="devicetype_garmin_fenix_5_plus">Garmin Fenix 5 Plus</string>
+    <string name="weekstepschart_steps_a_week_or_month">Kroki w tygodniu/miesi膮cu</string>
+    <string name="fw_upgrade_notice_amazfitbip3">W艂a艣nie zamierzasz zainstalowa膰 oprogramowanie uk艂adowe %s na swoim Amazfit Bip 3.
+\n
+\nProsz臋 upewni膰 si臋, 偶e zainstalowany zosta艂 plik .fw, a nast臋pnie plik .res. Po zainstalowaniu pliku .fw Tw贸j zegarek zostanie ponownie uruchomiony.
+\n
+\nUwaga: Nie musisz instalowa膰 pliku .res , je艣li jest on dok艂adnie taki sam jak ten, kt贸ry zosta艂y zainstalowany wcze艣niej.
+\n
+\nKONTYNUUJ NA W艁ASNE RYZYKO!</string>
+    <string name="activity_type_frisbee">Frisbee</string>
+    <string name="battery_low_notify_enabled">Powiadom o niskim poziomie na艂adowania baterii</string>
+    <string name="activity_type_folk_dance">Taniec ludowy</string>
+    <string name="hrv_status">Status zmienno艣ci t臋tna</string>
+    <string name="devicetype_garmin_venu_3s">Garmin Venu 3S</string>
+    <string name="copied_to_clipboard">Skopiowano do schowka</string>
+    <string name="loading">艁adowanie鈥�</string>
+    <string name="weeksleepchart_sleep_a_week_or_month">Sen w tygodniu/miesi膮cu</string>
+    <string name="share_debug_info">Udost臋pnij informacje z debugowania</string>
+    <string name="serbian">Serbski</string>
+    <string name="armenian">Ormia艅ski</string>
+    <string name="devicetype_garmin_vivomove_trend">Garmin V铆vomove Trend</string>
+    <string name="swolfMin">Minimalny swolf</string>
+    <string name="notif_battery_full_title">Gad偶et na艂adowany!</string>
+    <string name="url">URL</string>
+    <string name="authentication_failed_check_key">Uwierzytelnienie nie powiod艂o si臋, prosz臋 sprawd藕 klucz autoryzacji</string>
+    <string name="battery_full_notify_enabled">Powiadom o pe艂nym na艂adowaniu baterii</string>
+    <string name="battery_full_threshold">Pr贸g pe艂nego na艂adowania baterii</string>
+    <string name="battery_low_threshold">Pr贸g niskiego poziomu na艂adowania baterii</string>
+    <string name="find_my_phone_companion_warning">Do znalezienia telefonu wymagane jest parowanie towarzysz膮ce. Dotknij tutaj by dowiedziedzie膰 si臋 wi臋cej.</string>
+    <string name="busy_task_fetch_sports_details_interrupted">Pobieranie szczeg贸艂贸w sportowych zosta艂o zak艂贸cone</string>
+    <string name="notif_battery_full">%1$s bateria na艂adowana</string>
+    <string name="activity_type_sepak_takraw">Sepak Takraw</string>
+    <string name="battery_percentage_str">%1$s%%</string>
+    <string name="default_percentage">Domy艣lnie (%1$d%%)</string>
+    <string name="activity_prefs_height_inches">Wzrost w calach</string>
+    <string name="swolfAvg">艢redni swolf</string>
+    <string name="swolfMax">Maksymalny swolf</string>
+    <string name="activity_type_cooldown">Cooldown</string>
+    <string name="devicetype_amazfit_bip3">Amazfit Bip 3</string>
+    <string name="devicetype_garmin_venu_2_plus">Garmin Venu 2 Plus</string>
+    <string name="devicetype_garmin_venu_3">Garmin Venu 3</string>
+    <string name="devicetype_garmin_venu_2">Garmin Venu 2</string>
+    <string name="activity_type_curling">Curling</string>
+    <string name="activity_type_auto_racing">Wy艣cigi samochodowe</string>
+    <string name="activity_type_shuttlecock">Zo艣ka</string>
+    <string name="activity_type_lacross">Lacrosse</string>
+    <string name="activity_type_stair_stepper">Stepper</string>
+    <string name="activity_type_functional_training">Trening funkcjonalny</string>
+    <string name="activity_type_smith_machine">Maszyna Smitha</string>
+    <string name="activity_type_handcycling">Handbike</string>
+    <string name="activity_type_e_bike">Rower elektryczny</string>
+    <string name="activity_type_bike_commute">Podr贸偶 rowerem</string>
+    <string name="activity_type_handcycling_indoor">Handbike wewn膮trz</string>
+    <string name="activity_type_fitness_equipment">Akcesoria treningowe</string>
+    <string name="activity_type_platform_tennis">Platform Tennis</string>
+    <string name="activity_type_american_football">Futbol ameryka艅ski</string>
+    <string name="activity_type_training">Trening</string>
+    <string name="activity_type_cardio">Cardio</string>
+    <string name="activity_type_mountaineering">Alpinizm</string>
+    <string name="activity_type_paddling">Wios艂owanie</string>
+    <string name="activity_type_motorcycling">Jazda na motocyklu</string>
+    <string name="activity_type_boating">P艂ywanie 艂odzi膮</string>
+    <string name="activity_type_driving">Jazda samochodem</string>
+    <string name="activity_type_hang_gliding">Lotniarstwo</string>
+    <string name="activity_type_inline_skating">Wrotkarstwo</string>
+    <string name="activity_type_climb_indoor">Wspinaczka wewn膮trz</string>
+    <string name="activity_type_bouldering">Bouldering</string>
+    <string name="activity_type_sail_race">Regaty 偶eglarskie</string>
+    <string name="activity_type_sail_expedition">Wyprawa 偶eglarska</string>
+    <string name="activity_type_ice_skating">Jazda na 艂y偶wach</string>
+    <string name="activity_type_sky_diving">Skydiving</string>
+    <string name="activity_type_snowmobiling">Skuter 艣nie偶ny</string>
+    <string name="activity_type_stand_up_paddleboarding">Stand up paddle</string>
+    <string name="activity_type_wakeboarding">Wakeboarding</string>
+    <string name="activity_type_kayaking">Kajakarstwo</string>
+    <string name="activity_type_water_skiing">Narciarstwo wodne</string>
+    <string name="activity_type_rafting">Rafting</string>
+    <string name="activity_type_jumpmaster">Jumpmaster</string>
+    <string name="activity_type_softball_slow_pitch">Slowpitch</string>
+    <string name="activity_type_shooting">Strzelectwo</string>
+    <string name="activity_type_winter_sport">Sporty zimowe</string>
+    <string name="activity_type_grinding">Grinding</string>
+    <string name="activity_type_health_snapshot">Health Snapshot</string>
+    <string name="activity_type_padel">Padel</string>
+    <string name="activity_type_racquetball">Racquetball</string>
+    <string name="activity_type_meditation">Medytacja</string>
+    <string name="activity_type_disc_golf">Disc Golf</string>
+    <string name="activity_type_team_sport">Sport dru偶ynowy</string>
+    <string name="activity_type_wakesurfing">Wakesurfing</string>
+    <string name="activity_type_mixed_martial_arts">R贸偶ne sztuki walki</string>
+    <string name="activity_type_aerobics">Aerobik</string>
+    <string name="activity_type_air_walker">Air walker</string>
+    <string name="activity_type_aerobic_combo">Aerobic combo</string>
+    <string name="activity_type_artistic_swimming">P艂ywanie synchroniczne</string>
+    <string name="activity_type_ballroom_dance">Taniec towarzyski</string>
+    <string name="activity_type_breaking">Breaking</string>
+    <string name="activity_type_bridge">Bryd偶</string>
+    <string name="activity_type_cardio_combat">Cardio combat</string>
+    <string name="activity_type_futsal">Futsal</string>
+    <string name="activity_type_hacky_sack">Footbag</string>
+    <string name="activity_type_hip_hop">Hip-hop</string>
+    <string name="activity_type_hula_hoop">Hula hoop</string>
+    <string name="activity_type_jai_alai">Jai alai</string>
+    <string name="activity_type_judo">Judo</string>
+    <string name="activity_type_jujitsu">Jujitsu</string>
+    <string name="activity_type_mass_gymnastics">Gimnastyka masowa</string>
+    <string name="activity_type_modern_dance">Taniec nowoczesny</string>
+    <string name="activity_type_muay_thai">Muay thai</string>
+    <string name="activity_type_parallel_bars">Por臋cze symetryczne</string>
+    <string name="activity_type_pole_dance">Taniec na rurze</string>
+    <string name="activity_type_race_walking">Ch贸d sportowy</string>
+    <string name="activity_type_skating">艁y偶wiarstwo</string>
+    <string name="activity_type_para_sport">Sporty niepe艂nosprawnych</string>
+    <string name="activity_type_lacrosse">Lacrosse</string>
+    <string name="activity_type_swing">Swing</string>
+    <string name="activity_type_dumbbell">Hantle</string>
+    <string name="activity_type_hand_cycling">Handbike</string>
+    <string name="activity_type_cross_country_skiing">Biegi narciarskie</string>
+    <string name="activity_type_jet_skiing">Narciarstwo wodne</string>
+    <string name="activity_type_cross_training">Trening obwodowy</string>
+    <string name="activity_type_battle_rope">Lina treningowa</string>
+    <string name="pref_wide_area_tap_summary">Rozpoznawaj naci艣ni臋cia pomi臋dzy policzkami i uszami</string>
+    <string name="devicetype_moondrop_space_travel">Moondrop Space Travel</string>
+    <string name="notification_channel_full_battery_name">Na艂adowana bateria</string>
+    <string name="devicetype_hama_fit6900">Hama Fit6900</string>
+    <string name="devicetype_garmin_fenix_6_sapphire">Garmin Fenix 6 Sapphire</string>
+    <string name="pref_adaptive_noise_cancelling_summary">Ustawiaj si艂臋 aktywnej redukcji ha艂asu automatycznie w zale偶no艣ci od poziomu g艂o艣no艣ci otoczenia</string>
+    <string name="devicetype_garmin_instinct_2s_solar">Garmin Instinct 2S Solar</string>
+    <string name="devicetype_garmin_instinct_2x_solar">Garmin Instinct 2X Solar</string>
+    <string name="devicetype_nothing_cmf_watch_pro_2">CMF Watch Pro 2</string>
+    <string name="pref_wide_area_tap_title">Naci艣ni臋cie na du偶ym obszarze</string>
+    <string name="activity_type_water_tubing">Tubing</string>
+    <string name="activity_type_shuffleboard">Shuffleboard</string>
+    <string name="activity_type_snorkeling">Snorkeling</string>
+    <string name="activity_type_somatosensory_game">Gra somatosensoryczna</string>
+    <string name="activity_type_spinning">Spinning</string>
+    <string name="activity_type_square_dance">Taniec synchroniczny</string>
+    <string name="activity_type_stair_climber">Schody treningowe</string>
+    <string name="activity_type_table_football">Pi艂karzyki</string>
+    <string name="activity_type_tug_of_war">Przeci膮ganie liny</string>
+    <string name="activity_type_wall_ball">Wall ball</string>
+    <string name="activity_type_water_polo">Wodne polo</string>
+    <string name="activity_type_weiqi">Go</string>
+    <string name="hrv_status_unbalanced">Niezbalansowany</string>
+    <string name="body_energy_lost">Stracono</string>
+    <string name="hrv_status_day_avg_legend">艢rednia dzienna (ms)</string>
+    <string name="hrv_status_balanced">Zbalansowany</string>
+    <string name="devicetype_garmin_forerunner_265">Garmin Forerunner 265</string>
+    <string name="pref_header_hrv_status">Status zmienno艣ci t臋tna</string>
+    <string name="warning_missing_notification_permission">Nie uda艂o si臋 wys艂a膰 powiadomienia ze wzgl臋du na brakuj膮ce uprawnienie</string>
+    <string name="body_energy_legend_level">Poziom energii cia艂a</string>
+    <string name="devicetype_garmin_instinct_2s">Garmin Instinct 2S</string>
+    <string name="devicetype_garmin_instinct_2_solar">Garmin Instinct 2 Solar</string>
+    <string name="devicetype_garmin_instinct_2_soltac">Garmin Instinct 2 SolTac</string>
+    <string name="devicetype_soundcore_motion300">Soundcore Motion 300</string>
+    <string name="devicetype_garmin_vivoactive_3">Garmin V铆voactive 3</string>
+    <string name="devicetype_garmin_vivosmart_5">Garmin V铆vosmart 5</string>
+    <string name="pref_force_enable_heartrate_support">Wymu艣 wsparcie dla pomiaru t臋tna</string>
+    <string name="activity_type_free_sparring">Wolny sparing</string>
+    <string name="activity_type_body_combat">Body combat</string>
+    <string name="activity_type_laser_tag">Laser tag</string>
+    <string name="activity_type_billiard_pool">Pool bilard</string>
+    <string name="activity_type_canoeing">Kajakarstwo kanadyjk膮</string>
+    <string name="activity_type_water_scooter">Skuter wodny</string>
+    <string name="activity_type_bungee_jumping">Skok na bungee</string>
+    <string name="activity_type_orienteering">Bieg na orientacj臋</string>
+    <string name="hrv_status_day_avg">艢rednia dzienna</string>
+    <string name="hrv_status_baseline">%1$d-%2$d ms</string>
+    <string name="body_energy_gained">Zyskano</string>
+    <string name="devicetype_garmin_vivoactive_4">Garmin V铆voactive 4</string>
+    <string name="devicetype_garmin_forerunner_965">Garmin Forerunner 965</string>
+    <string name="devicetype_huawei_watchultimate">Huawei Watch Ultimate</string>
+    <string name="huawei_trusleep_warning">Uwaga: w艂膮czenie tej funkcji sparawi, 偶e Gadgetbridge b臋dzie wy艣wietla艂 ka偶dy sen jako p艂ytki sen! Kliknij tutaj, by to zaakceptowa膰.</string>
+    <string name="devicetype_garmin_venu">Garmin Venu</string>
+    <string name="devicetype_soundcore_liberty3_pro">Soundcore Liberty 3 Pro</string>
+    <string name="cyclingPowerAverage">艢rednia moc rowerowa</string>
+    <string name="cyclingPowerMin">Minimalna moc rowerowa</string>
+    <string name="pref_adaptive_volume_control_summary">Automatycznie zwi臋kszaj g艂o艣no艣膰, gdy d藕wi臋ki otoczenia s膮 g艂o艣ne</string>
+    <string name="body_energy">Energia cia艂a</string>
+    <string name="activity_type_obstacle_race">Bieg przeszkodowy</string>
+    <string name="activity_type_bobsleigh">Bobslej</string>
+    <string name="activity_type_sledding">Jazda na sankach</string>
+    <string name="activity_type_biathlon">Biathlon</string>
+    <string name="pref_force_enable_spo2_support">Wymu艣 wsparcie dla SpO2</string>
+    <string name="pref_adaptive_volume_control_title">Automatyczna kontrola g艂o艣no艣ci</string>
+    <string name="devicetype_garmin_forerunner_255s">Garmin Forerunner 255S</string>
+    <string name="devicetype_garmin_vivoactive_5">Garmin V铆voactive 5</string>
+    <string name="devicetype_sony_linkbuds">Sony LinkBuds</string>
+    <string name="devicetype_huawei_band9">Huawei Band 9</string>
+    <string name="preferences_qhybrid_settings_summary">Stare ustawienia dla zegark贸w Q hybrid</string>
+    <string name="stepLengthAvg">艢rednia d艂ugo艣膰 kroku</string>
+    <string name="RunningForm">Styl biegania</string>
+    <string name="pref_header_sony_sound_control">Kontrola d藕wi臋ku</string>
+    <string name="pref_force_enable_spo2_support_summary">Wymu艣 wsparcie dla SpO2.
+\nKONTYNUUJ NA W艁ASNE RYZYKO</string>
+    <string name="devicetype_huawei_watch3">Huawei Watch 3 (Pro)</string>
+    <string name="devicetype_garmin_instinct">Garmin Instinct</string>
+    <string name="devicetype_garmin_instinct_crossover">Garmin Instinct Crossover</string>
+    <string name="devicetype_garmin_forerunner_245">Garmin Forerunner 245</string>
+    <string name="devicetype_garmin_swim_2">Garmin Swim 2</string>
+    <string name="devicetype_huawei_watchgt4">Huawei Watch GT 4</string>
+    <string name="devicetype_huawei_watchfit2">Huawei Watch Fit 2</string>
+    <string name="pref_adaptive_noise_cancelling_title">Automatyczna kontrola aktywnej redukcji ha艂asu</string>
+    <string name="pref_force_enable_heartrate_support_summary">Wymu艣 wsparcie dla pomiaru t臋tna.
+\nKONTYNUUJ NA W艁ASNE RYZYKO</string>
+    <string name="widget_layout_top_wide_bot_large">Szeroki wy偶ej, du偶y ni偶ej</string>
+    <string name="widget_layout_top_large_bot_wide">Du偶y wy偶ej, szeroki ni偶ej</string>
+    <string name="widget_name_colored_tile">%1$s (kolorowa p艂ytka)</string>
+    <string name="widget_name_untitled">Nienazwany Wid偶et (%1$s)</string>
+    <string name="devicetype_vivitar_hr_bp_monitor_activity_tracker">Vivitar HR &amp; BP Monitor Activity Tracker</string>
+    <string name="milliseconds">milisekundy</string>
+    <string name="devicetype_nothing_cmf_buds_pro_2">CMF Buds Pro 2</string>
+    <string name="battery_i">Bateria %d</string>
+    <string name="devicetype_huawei_watchfit3">Huawei Watch Fit 3</string>
+    <string name="groundContactTimeAvg">艢redni czas kontaktu z pod艂o偶em</string>
+    <string name="devicetype_huawei_watch4pro">Huawei Watch 4 Pro</string>
+    <string name="devicetype_garmin_fenix_7_pro">Garmin Fenix 7 Pro</string>
+    <string name="devicetype_garmin_fenix_7s">Garmin Fenix 7S</string>
+    <string name="devicetype_redmi_watch_4">Redmi Watch 4</string>
+    <string name="devicetype_garmin_forerunner_255_music">Garmin Forerunner 255 Music</string>
+    <string name="devicetype_garmin_forerunner_255s_music">Garmin Forerunner 255S Music</string>
+    <string name="devicetype_garmin_vivoactive_4s">Garmin V铆voactive 4S</string>
+    <string name="cyclingPowerMax">Maksymalna moc rowerowa</string>
+    <string name="devicetype_garmin_forerunner_255">Garmin Forerunner 255</string>
+    <string name="devicetype_huawei_watchgtrunner">Huawei Watch GT Runner</string>
+    <string name="hrv_status_low">Niski</string>
+    <string name="hrv_status_poor">S艂aby</string>
+    <string name="hrv_status_unit">%1$d ms</string>
+    <string name="hrv_status_baseline_label">Linia bazowa</string>
+    <string name="unknownDataEncountered">Napotkano nieznane dane</string>
+    <string name="devicetype_garmin_vivosport">Garmin V铆vosport</string>
+    <string name="degrees">stopnie</string>
+    <string name="Pace">Tempo</string>
+    <string name="devicetype_garmin_instinct_solar">Garmin Instinct Solar</string>
+    <string name="hrv_status_seven_days_avg">艢rednia z 7 dni</string>
+    <string name="hrv_status_seven_days_avg_status">Status</string>
+    <string name="hrv_status_last_night">Ostatnia noc</string>
+    <string name="hrv_status_seven_days_avg_long">艢rednia z 7 dni</string>
+    <string name="dashboard_calendar_month_goals_reached_title">% uzyskania dziennego celu krok贸w</string>
+    <string name="pref_sleepasandroid_feat_heartrate">T臋tno</string>
+    <plurals name="amount_of_days">
+        <item quantity="one">%d dzie艅</item>
+        <item quantity="few">%d dni</item>
+        <item quantity="many">%d dni</item>
+        <item quantity="other">%d dni</item>
+    </plurals>
+    <string name="prompt_restart_gadgetbridge">Prosz臋 zrestartowa膰 GB, by akcja odnios艂a efekt.</string>
+    <string name="pref_speak_notifications_focus_exclusive_summary_off">G艂o艣no艣膰 d藕wi臋ku z innych aplikacji zostanie obni偶ona podczas czytania powiadomienia</string>
+    <string name="sleepasandroid_settings">Sleep As Android</string>
+    <string name="unbind_before_pair_title">Ju偶 powi膮zano</string>
+    <string name="pref_dashboard_cards_summary">Rysuje karty wok贸艂 wid偶et贸w na pulpicie</string>
+    <string name="pref_dashboard_widget_show_legend_title">Wy艣wietlaj legend臋</string>
+    <string name="bottom_nav_devices">Urz膮dzenia</string>
+    <string name="pref_test_features_summary">W艂膮czone funkcje dla tego urz膮dzenia testowego</string>
+    <string name="device_state_waiting_scan">Czekanie na skan urz膮dzenia</string>
+    <string name="auto_reconnect_ble_scan_title">Po艂膮cz si臋 ponownie poprzez skan BLE</string>
+    <string name="scan_not_scanning">Nie skanuj臋</string>
+    <string name="pref_dashboard_widget_today_24h_title">Tryb 24 godzinny</string>
+    <string name="pref_dashboard_first_summary">Pokazuj pulpit po uruchomieniu Gadgetbridge, zamiast ekranu urz膮dze艅</string>
+    <string name="pref_dashboard_widget_double_size_summary">Pozw贸l wid偶etowi zaj膮膰 dwie kolumny na pulpicie</string>
+    <string name="pref_dashboard_widget_today_24h_summary">Pokazuj aktywno艣膰 w pojedy艅czym 24h okr臋gu zamiast podw贸jnego 12h okr臋gu</string>
+    <string name="pref_auto_reply_calls_delay_title">Automatyczne op贸藕nienie odpowiedzi</string>
+    <string name="pref_dashboard_select_devices_title">Wybierz urz膮dzenia...</string>
+    <string name="scan_scanning_all_devices">Skanowanie wszystkich urz膮dze艅</string>
+    <string name="companion_pairing_request_title">Urz膮dzenie towarzysz膮ce</string>
+    <string name="dashboard_settings">Ustawienia pulpitu</string>
+    <string name="pref_dashboard_widget_today_hr_interval_summary">Ilo艣膰 minut na wykresie pokazuj膮cych warto艣膰 \"za艂o偶one\" po ka偶dym udanym pomiarze t臋tna</string>
+    <string name="pref_auto_reply_calls_summary">Telefon b臋dzie automatycznie odbiera艂 przychodz膮ce po艂膮czenia</string>
+    <string name="pref_header_calls_and_notifications">Po艂膮czenia i powiadomienia</string>
+    <string name="pref_sleepasandroid_feat_notifications">Powiadomienia</string>
+    <string name="scan_scanning_single_device">Skanowanie 1 urz膮dzenia</string>
+    <string name="pref_sleepasandroid_feat_alarms">Alarmy</string>
+    <string name="pref_auto_reply_calls_title">Automatycznie odpowiadaj na po艂膮czenia telefoniczne</string>
+    <string name="pref_dashboard_cards_title">Pokazuj wid偶ety na kartach</string>
+    <string name="pref_dashboard_widget_today_title">Wykres aktywno艣ci</string>
+    <string name="pref_dashboard_widget_double_size_title">Podw贸jny rozmiar</string>
+    <string name="bottom_nav_dashboard">Pulpit</string>
+    <string name="pref_dashboard_widget_settings">Ustawienia wid偶et贸w</string>
+    <string name="pref_title_bottom_navigation_bar">Dolny pasek nawigacyjny</string>
+    <string name="pref_sleepasandroid_feat_spo2">SPO2</string>
+    <string name="pref_dashboard_widget_goals_chart_title">Wykres cel贸w</string>
+    <string name="pref_dashboard_widgets_order_summary">Wybierz kt贸re wid偶ety w艂膮czy膰 oraz kolejno艣膰 wy艣wietlania ich na pulpicie</string>
+    <string name="pref_dashboard_all_devices_title">Wszystkie urz膮dzenia</string>
+    <string name="pref_dashboard_widget_show_legend_summary">Wy艣wietlaj legend臋 poni偶ej wid偶etu wyja艣niaj膮c膮 kolory</string>
+    <string name="activity_type_worn">Za艂o偶one</string>
+    <string name="pref_summary_bottom_navigation_bar_off">Prze艂膮czaj si臋 pomi臋dzy ekranami g艂贸wnymi jedynie za pomoc膮 przesuwania w poziomie</string>
+    <string name="pref_sleepasandroid_feat_oximetry">Oksymetria</string>
+    <string name="pref_title_huawei_account">Konto Huawei</string>
+    <string name="pref_auto_reply_calls_delay_summary">Liczba sekund po ilu po艂膮czenie jest automatycznie odbierane</string>
+    <string name="pref_speak_notifications_focus_exclusive_summary_on">D藕wi臋k z innych aplikacji zostanie zatrzymany podczas czytania powiadomienia</string>
+    <string name="pref_summary_bottom_navigation_bar_on">Prze艂膮czaj si臋 pomi臋dzy ekranami g艂贸wnymi za pomoc膮 paska nawigacyjnego lub przesuwania w poziomie</string>
+    <string name="pref_dashboard_first_title">Pokazuj pulpit na pocz膮tku</string>
+    <string name="pref_sleepasandroid_device_title">Urz膮dzenie dostawca</string>
+    <string name="pref_sleepasandroid_features_summary">Wsparcie r贸偶ni si臋 dla poszczeg贸lnych urz膮dze艅</string>
+    <string name="pref_dashboard_widget_today_hr_interval_title">Interwa艂 t臋tna</string>
+    <string name="pref_sleepasandroid_device_summary">Wybierz urz膮dzenie dostarczaj膮ce dane do Sleep As Android</string>
+    <string name="waiting_for_bluetooth">Czekanie na Bluetooth鈥�</string>
+    <string name="unbind_before_pair_message">To urz膮dzenie jest ju偶 powi膮zane w ustawieniach Androida, co mo偶e uniemo偶liwi膰 parowanie niekt贸rych urz膮dze艅.
+\n
+\nJe艣li nie uda si臋 doda膰 tego urz膮dzenia, usu艅 je w ustawieniach Androida i spr贸buj ponownie.</string>
+    <string name="pref_dashboard_all_devices_summary">Zsumuj dane o aktywno艣ci ze wszystkich dodanych urz膮dze艅 do wykres贸w na pulpicie</string>
+    <string name="pref_dashboard_select_devices_summary">Zsumuj dane o aktywno艣ci z wybranych urz膮dze艅 do wykres贸w na pulpicie</string>
+    <string name="pref_speak_notifications_aloud_summary">Powiadomienia b臋d膮 czytane na g艂os w s艂uchawkach</string>
+    <string name="pref_speak_notifications_aloud_title">Czytaj powiadomienia na g艂os</string>
+    <string name="pref_speak_notifications_focus_exclusive_title">Zatrzymaj d藕wi臋k z innych aplikacji</string>
+    <string name="pref_dashboard_widget_today_upside_down_title">P贸艂noc na dole</string>
+    <string name="pref_sleepasandroid_enable_summary">W艂膮cz integracj臋 Sleep As Android</string>
+    <string name="pref_sleepasandroid_features_title">Funkcje</string>
+    <string name="error_scan_failed">Skan nie powi贸d艂 si臋: %d</string>
+    <string name="scan_scanning_multiple_devices">Skanowanie %d urz膮dze艅</string>
+    <string name="pref_sleepasandroid_feat_movement">Akcelerometr</string>
+    <string name="intent_api_broadcast_activity_sync_title">Rozg艂aszaj przy zako艅czeniu synchronizacji aktywno艣ci</string>
+    <string name="devicetype_scannable">Skanowalne urz膮dzenie</string>
+    <string name="pref_dashboard_devices_to_include">Urz膮dzenia do uwzgl臋dnienia</string>
+    <string name="intent_api_broadcast_activity_sync_summary">Rozg艂aszaj intencj臋 po zako艅czeniu sychronizacji aktywno艣ci dla jakiegokolwiek urz膮dzenia</string>
+    <string name="companion_pairing_request_description">Sparowa膰 to urz膮dzenie jako towarzysz膮ce?
+\n
+\nJest to rekomendowane dla takich funkcji, jak znajd藕 urz膮dzenie oraz oferuje lepsze po艂膮czenie.</string>
+    <string name="state_scanned">Zeskanowano</string>
+    <string name="pref_dashboard_widget_today_upside_down_summary">W trybie 24 godzinnym, rysuj p贸艂noc na dole, a po艂udnie na g贸rze wykresu</string>
+    <string name="garmin_agps_local_file">Plik lokalny</string>
+    <string name="folder">Folder</string>
+    <string name="no_folder_selected">Nie wybrano folderu</string>
+    <string name="insufficient_space_for_upload">Niewystarczaj膮ce miejsce na przesy艂</string>
+    <string name="watchface_resolution_doesnt_match">Rozdzielczo艣膰 tarczy jest niedopasowana do rozdzielczo艣ci ekranu urz膮dzenia. Wymiary tarczy to %1$s, a ekranu urz膮dzenia to %2$s</string>
+    <string name="min_val">Minimum: %d</string>
+    <string name="max_val">Maximum: %d</string>
+    <string name="unsupported">Niewspierane</string>
+    <string name="smart_ring_measurement_error_unknown">Otrzymano nieznany b艂膮d pomiaru %d z pier艣cienia</string>
+    <string name="cannot_upload_watchface_too_many_watchfaces_installed">Nie mo偶na wys艂a膰 tarczy, zainstalowano zbyt wiele tarczy</string>
+    <string name="pref_summary_huawei_account">Konto Huawei u偶ywane w procesie parowania. Ustawienie go pozwala na parowanie bez przywracania do ustawie艅 fabrycznych.</string>
+    <string name="pref_fetch_unknown_files_title">Pobieraj nieznane pliki</string>
+    <string name="pref_fetch_unknown_files_summary">Pobieraj dane o nieznanych aktywno艣ciach z zegarka. Nie b臋d膮 one przetwarzane, ale zostan膮 zapisane na telefonie.</string>
+    <string name="folder_is_empty">Folder jest pusty</string>
+    <string name="pref_title_notification_wake_on_open">Automatycznie wybud偶 i odblokuj</string>
+    <string name="pref_summary_notification_wake_on_open">Wybud藕 i odblokuj urz膮dzenie z systemem Android kiedy gad偶et wy艣le sygna艂 OTW脫RZ. Musi by膰 w stanie zaufanym.</string>
+    <string name="estimatedSweatLoss">Szacowana utrata potu</string>
+    <string name="pref_touch_tone">D藕wi臋k dotyku</string>
+    <string name="pref_time_sync">Automatyczna synchronizacja czasu</string>
+    <string name="recoveryTime">Czas regeneracji</string>
+    <string name="lactateThresholdHeartRate">T臋tno progu mleczanowego</string>
+    <string name="hrv">Zmienno艣膰 t臋tna</string>
+    <string name="abstract_chart_fragment_kind_awake_sleep">Obudzony</string>
+    <string name="pref_wearing_tone">D藕wi臋k noszenia</string>
+    <string name="busy_task_fetch_sleep_data">Pobieranie danych o 艣nie</string>
+    <string name="user_feedback_set_settings_ok">Ustawienia zosta艂y wys艂ane do urz膮dzenia.</string>
+    <string name="minutes_2">2 minuty</string>
+    <string name="minutes_3">3 minuty</string>
+    <string name="minutes_4">4 minuty</string>
+    <string name="minutes_6">6 minut</string>
+    <string name="minutes_7">7 minut</string>
+    <string name="minutes_8">8 minut</string>
+    <string name="minutes_9">9 minut</string>
+    <string name="minutes_15">15 minut</string>
+    <string name="minutes_45">45 minut</string>
+    <string name="minutes_75">75 minut</string>
+    <string name="minutes_90">90 minut</string>
+    <string name="minutes_120">120 minut</string>
+    <string name="minutes_150">150 minut</string>
+    <string name="minutes_180">180 minut</string>
+    <string name="minutes_210">210 minut</string>
+    <string name="stress_average">艢redni</string>
+    <string name="sleep_colored_stats_awake_avg">Obudzony 艣redni</string>
+    <string name="pref_header_sound">D藕wi臋k</string>
+    <string name="choose_device">Wybierz urz膮dzenie</string>
+    <string name="no_supported_devices_found">Nie znaleziono wspieranych urz膮dze艅</string>
+    <string name="search">Szukaj</string>
+    <string name="activity_type_rem_sleep">Sen REM</string>
+    <string name="pref_title_nagivation_apps">Aplikacje do nawigacji</string>
+    <string name="pref_header_external_integrations">Integracje zewn臋trzne</string>
+    <string name="pref_header_automations">Automatyzacja</string>
+    <string name="pref_description_general">Uruchomienie, j臋zyk, region, lokalizacja</string>
+    <string name="pref_description_about_you">Data urodzenia, p艂e膰, wzrost, waga, cele</string>
+    <string name="pref_description_notifications">Powiadomienia aplikacji, dozwolone/zabronione</string>
+    <string name="pref_description_user_interface">Motyw, ekran g艂贸wny</string>
+    <string name="pref_description_dashboard">Wid偶ety, uwzgl臋dnione urz膮dzenia</string>
+    <string name="pref_description_automations">Automatyczny eksport, automatyczne pobieranie</string>
+    <string name="pref_description_developer_options">Logi, API Intencji</string>
+    <string name="pref_description_deprecated_functionalities">Ustawienia kt贸re zostan膮 usuni臋te w przysz艂ej wersji</string>
+    <string name="pref_header_main_screen">Ekran g艂贸wny</string>
+    <string name="pref_title_user_interface">Interfejs u偶ytkownika</string>
+    <string name="dateformat_day_month">Dzie艅, miesi膮c</string>
+    <string name="minutes_240">240 minut</string>
+    <string name="activity_prefs_date_birth">Data urodzenia</string>
+    <string name="minutes_255">255 minut</string>
+    <string name="dateformat_month_day">Miesi膮c, dzie艅</string>
+    <string name="soundcore_equalizer_frequency">Cz臋stotliwo艣膰</string>
+    <string name="garmin_agps_url_i">AGPS %1$d URL</string>
+    <string name="soundcore_equalizer_value">Warto艣膰</string>
+    <string name="pref_battery_polling_configuration">Konfiguracja sondowania baterii</string>
+    <string name="toast_camera_permission_required">Ta funkcja wymaga uprawnienia do aparatu.</string>
+    <string name="toast_camera_support_required">Ta funkcja wymaga wsparcia dla aparatu.</string>
+    <string name="toast_camera_photo_taken">Wykonano zdj臋cie i zapisano je w: %s</string>
+    <string name="chart_cycling_point_label_distance">Dzisiaj: %.1f km
+\nCa艂kowicie: %.1f km</string>
+    <string name="chart_cycling_point_label_speed">%.1f km/h</string>
+    <string name="devicetype_garmin_venu_2s">Garmin Venu 2S</string>
+    <string name="toast_setting_requires_reconnect">Ta opcja zadzia艂a dopiero po ponownym pod艂膮czeniu</string>
+    <string name="pref_title_wheel_diameter">艢rednica ko艂a</string>
+    <string name="pref_summary_wheel_diameter">艢rednica ko艂a w calach. Typowo 29, 27,5 lub 26.</string>
+    <string name="activity_type_navigate">Nawigowanie</string>
+    <string name="activity_type_multisport">Multisport</string>
+    <string name="steps_total">Ca艂kowita liczba krok贸w</string>
+    <string name="open_camera">Uruchom aparat</string>
+    <string name="devicetype_colmi_r06">Colmi R06</string>
+    <string name="devicetype_micompositionscale">Mi Body Composition Scale 2</string>
+    <string name="devicetype_mijia_xmwsdj04">Czujnik temperatury i wilgotno艣ci Mijia 2 (E-ink)</string>
+    <string name="devicetype_honor_watchgs3">Honor Watch GS 3</string>
+    <string name="steps_avg">艢rednia liczba krok贸w</string>
+    <string name="distance_avg">艢redni dystans</string>
+    <string name="distance_total">Ca艂kowity dystans</string>
+    <string name="menuitem_weight">Waga</string>
+    <string name="activity_type_mind_and_body">Body &amp; Mind</string>
+    <string name="weight_kg">%1$.2f kg</string>
+    <string name="battery_polling_failed_start">Nie uda艂o si臋 zacz膮膰 sonda偶u baterii</string>
+    <string name="devicetype_mismartscale">Mi Smart Scale 2</string>
+    <string name="steps_distance_unit">%1$,.2f km</string>
+    <string name="weight_lbs">%1$.2f funty</string>
+    <string name="target">Cel</string>
+    <string name="miscale_weight_unit_summary">Ustaw jednostk臋 masy dla wy艣wietlanych pomiar贸w</string>
+    <string name="miscale_weight_unit_metric">Metryczna (kg)</string>
+    <string name="miscale_weight_unit_imperial">Imperialna (lbs)</string>
+    <string name="miscale_weight_unit_chinese">Chi艅ska (jin)</string>
+    <string name="workout_set_repetitions">%1d x</string>
+    <string name="workout_set_repetitions_weight_kg">%1d x %2$.2f kg</string>
+    <string name="workout_set_repetitions_weight_lbs">%1d x %2$.2f funty</string>
+    <string name="pref_battery_polling_interval">Interwa艂 sondowania baterii</string>
+    <string name="pref_header_deprecated_functionalities">Przestarza艂e funkcje</string>
+    <string name="pref_header_deprecated_functionalities_warning">Nast臋puj膮ce funkcje s膮 przestarza艂e i nied艂ugo zostan膮 usuni臋te z programu.
+\nJe艣li musisz w艂膮czy膰 jedn膮 z nich prosimy o kontakt z zespo艂em projektu.</string>
+    <string name="pref_deprecated_media_control_title">Przestarza艂a kontrola medi贸w</string>
+    <string name="devicetype_soundcore_liberty4_nc">Soundcore Liberty 4 NC</string>
+    <string name="devicetype_colmi_r02">Colmi R02</string>
+    <string name="menuitem_stress_simple">Stres (prosty)</string>
+    <string name="menuitem_stress_segmented">Stres (podzielony na segmenty)</string>
+    <string name="pref_battery_polling_interval_format">Co %1$s minuty / minut</string>
+    <string name="miscale_weight_unit_title">Jednostka masy</string>
+    <string name="devicetype_garmin_fenix_5x_plus">Garmin Fenix 5X Plus</string>
+    <string name="devicetype_colmi_r03">Colmi R03</string>
+    <string name="pref_battery_polling_enable">W艂膮cz sondowanie baterii</string>
+    <string name="devicetype_garmin_forerunner_165">Garmin Forerunner 165</string>
+    <string name="activity_db_management_backup_restore_label">Kopia zapasowa i przywracanie</string>
+    <string name="activity_db_management_export_to_zip">Eksportuj zip</string>
+    <string name="activity_db_management_import_from_zip">Importuj zip</string>
+    <string name="soundcore_equalizer_custom_title">Personalizuj鈥�</string>
+    <string name="pref_continuous_skin_temperature_measurement_title">Ci膮g艂y pomiar temperatury sk贸ry</string>
+    <string name="pref_continuous_skin_temperature_measurement_description">Pobieranie temperatury jest obecnie niewspierane. Ta opcja jedynie w艂膮cza ci膮g艂y pomiar na urz膮dzeniu</string>
+    <string name="devicesetting_scannable_debounce_summary">Po zeskanowaniu urz膮dzenie b臋dzie uznawane za zeskanowane i ignorowane przez okre艣lon膮 d艂ugo艣膰 czasu</string>
+    <string name="devicesetting_scannable_rssi_summary">Minimalny pr贸g RSSI dla wykrycia</string>
+    <string name="devicesetting_scannable_rssi">Minimalny pr贸g RSSI</string>
+    <string name="backup_restore_exporting_preferences">Eksportowanie preferencji鈥�</string>
+    <string name="backup_restore_exporting_database">Eksportowanie bazy danych鈥�</string>
+    <string name="backup_restore_do_not_exit">%s Nie wy艂膮czaj tego ekranu dop贸ki operacja nie dobiegnie ko艅ca.</string>
+    <string name="backup_restore_importing_loading">艁adowanie pliku鈥�</string>
+    <string name="backup_restore_importing_validating">Sprawdzanie pliku鈥�</string>
+    <string name="backup_restore_importing_files_i_of_n">Importowanie plik贸w鈥� %1d z %2d</string>
+    <string name="pref_battery_polling_summary">Dali艣my z siebie wszystko, dane mog膮 by膰 op贸藕nione z wielu powod贸w</string>
+    <string name="error_showing_changelog">B艂膮d w wy艣wietlaniu listy zmian</string>
+    <string name="prefs_title_gatt_client_notification_intents">Rozg艂aszaj powiadomienia GATT poprzez API intencji BLE</string>
+    <string name="backup_restore_exporting_finishing">Ko艅czenie eksportu鈥�</string>
+    <string name="backup_restore_exporting_files_i_of_n">Eksportowanie plik贸w... %1d z %2d</string>
+    <string name="backup_restore_import_complete">Import zako艅czony</string>
+    <string name="backup_restore_abort_import_confirmation">Przerwa膰 import? Mo偶e to spowodowa膰 uszkodzenie danych lub nieprawid艂owo艣ci w bazie danych.</string>
+    <string name="activity_db_management_backup_restore_explanation">Operacje importu/eksportu pozwalaj膮 ci na zapisanie kopii zapasowej ustawie艅 Gadgetbridge, urz膮dze艅 oraz danych w pliku zip, lub przywr贸cenie ich z takiego pliku.
+\n
+\nImport z pliku skasuje wszystkie dane, urz膮dzenia i preferencje, zast臋puj膮c je ca艂kowicie danymi z kopii zapasowej.</string>
+    <string name="backup_restore_exporting">Eksportowanie do zip鈥�</string>
+    <string name="backup_restore_exporting_files">Eksportowanie plik贸w鈥�</string>
+    <string name="backup_restore_importing">Importowanie z zip鈥�</string>
+    <string name="backup_restore_importing_database">Importowanie bazy danych鈥�</string>
+    <string name="backup_restore_importing_preferences">Importowanie preferencji鈥�</string>
+    <string name="backup_restore_warning_files">Nie uda艂o si臋 przywr贸ci膰 %1d plik贸w:
+\n%2s</string>
+    <string name="backup_restore_export_complete">Eksport zako艅czony</string>
+    <string name="backup_restore_error_export">Eksport do zip nie powi贸d艂 si臋</string>
+    <string name="backup_restore_error_import">Import z zip nie powi贸d艂 si臋</string>
+    <string name="prefs_title_ble_intent_api">API intencji BLE</string>
+    <string name="backup_restore_restart_title">Restart</string>
+    <string name="backup_restore_restart_summary">%1s si臋 zrestartuje.</string>
+    <string name="backup_restore_abort_title">Przerwij</string>
+    <string name="backup_restore_abort_export_confirmation">Przerwa膰 operacj臋? Cz臋艣ciowy plik zip zostanie usuni臋ty.</string>
+    <string name="pref_touch_tone_summary">Gra d藕wi臋k przy dotkni臋ciu s艂uchawki</string>
+    <string name="pref_wearing_tone_summary">Gra d藕wi臋k podczas zak艂adania s艂uchawki</string>
+    <string name="pref_app_connection_duration">Czas po艂膮czenia aplikacji</string>
+    <string name="smart_ring_measurement_error_worn_incorrectly">B艂膮d pomiaru. Czy czujniki pier艣cienia s膮 prawid艂owo ustawione?</string>
+    <string name="miscale_small_objects_summary">Zapisuj wag臋 przedmiot贸w l偶ejszych ni偶 10 kg</string>
+    <string name="miscale_small_objects_title">Ma艂e przedmioty</string>
+    <string name="devicetype_miband9">Xiaomi Smart Band 9</string>
+    <string name="devicetype_amazfit_trex_3">Amazfit T-Rex 3</string>
+    <string name="devicetype_garmin_forerunner_245_music">Garmin Forerunner 245 Music</string>
+    <string name="devicetype_garmin_forerunner_265s">Garmin Forerunner 265S</string>
+    <string name="devicetype_sony_wf_c500">Sony WF-C500</string>
+    <string name="devicetype_redmi_watch_5_active">Redmi Watch 5 Active</string>
+    <string name="menuitem_map">Mapa</string>
+    <string name="prefs_light_duration_longer">D艂u偶sze pod艣wietlanie</string>
+    <string name="vo2max_cycling">VO鈧� Max podczas kolarstwa</string>
+    <string name="soundcore_equalizer_reset_summary">Przywraca wszystkie pasma korektora do ustawie艅 domy艣lnych</string>
+    <string name="soundcore_equalizer_band1">Pasmo 1</string>
+    <string name="soundcore_equalizer_band2">Pasmo 2</string>
+    <string name="soundcore_equalizer_band3">Pasmo 3</string>
+    <string name="soundcore_equalizer_band4">Pasmo 4</string>
+    <string name="soundcore_equalizer_band6">Pasmo 6</string>
+    <string name="soundcore_equalizer_band7">Pasmo 7</string>
+    <string name="soundcore_equalizer_band8">Pasmo 8</string>
+    <string name="pref_title_garmin_default_reply_suffix">U偶ywaj predefiniowanego przyrostka odpowiedzi</string>
+    <string name="toggle_debug_mode">W艂膮cz tryb debugowania</string>
+    <string name="activity_type_track_and_field">Lekkoatletyka</string>
+    <string name="vo2max_running">VO鈧� Max podczas biegu</string>
+    <string name="thirty_days_timeline">O艣 czasowa z 30 dni</string>
+    <string name="moondrop_touch_action_play_pause">Graj/Zatrzymaj</string>
+    <string name="moondrop_touch_trigger_long_press_1s">D艂ugie przyci艣ni臋cie (1s)</string>
+    <string name="soundcore_equalizer_band5">Pasmo 5</string>
+    <string name="auto_reconnect_ble_scan_summary">Czekaj na skan urz膮dzenia zamiast pr贸b po艂膮czenia si臋 na 艣lepo</string>
+    <string name="pref_garmin_agps_help">Lista poni偶ej zawiera wszystkie URLe, o kt贸re 偶膮da艂 zegarek w celu aktualizacji AGPS. Mo偶esz wybra膰 plik z pami臋ci telefonu, kt贸ry zostanie wys艂any, gdy zegarek za偶膮da aktualizacji.</string>
+    <string name="activity_type_dynamic_cycle">Kolarstwo dynamiczne</string>
+    <string name="activity_type_indoor_track">Tor wewn膮trz</string>
+    <string name="activity_type_breathwork">Kontrolowanie oddechu</string>
+    <string name="activity_type_floor_climbing">Wspinaczka po pi臋trach</string>
+    <string name="activity_type_racket">Rakieta</string>
+    <string name="activity_type_ultimate_disc">Ultimate</string>
+    <string name="soundcore_equalizer_direction">Pozycja urz膮dzenia</string>
+    <string name="soundcore_equalizer_direction_hanging">Wisi</string>
+    <string name="notification_channel_scan_service_name">Us艂uga skanowania</string>
+    <string name="soundcore_equalizer_band9">Pasmo 9</string>
+    <string name="soundcore_button_brightness_high">Wysoka</string>
+    <string name="pref_sleepasandroid_slot_title">Miejsca na alarm</string>
+    <string name="pref_sleepasandroid_slot_summary">Kt贸rego miejsca u偶y膰 podczas ustawiania alarmu</string>
+    <string name="alarm_slot_reset">Miejsce na alarm zosta艂o ustawione jako domy艣lne</string>
+    <string name="menuitem_night_display">Wy艣wietlanie nocne</string>
+    <string name="activity_type_free_training">Wolny trening</string>
+    <string name="activity_type_javelin">Rzut oszczepem</string>
+    <string name="agps_status_missing">Brak</string>
+    <string name="agps_status_pending">W trakcie</string>
+    <string name="agps_status_current">Aktualny</string>
+    <string name="hr_resting">W spoczynku</string>
+    <string name="hr_maximum">Maksymalne</string>
+    <string name="hr_minimum">Minimalne</string>
+    <string name="hr_average">艢rednie</string>
+    <string name="activity_type_shot">Strzelectwo</string>
+    <string name="activity_type_snowshoe">Bieg po 艣niegu</string>
+    <string name="activity_type_plaza_dancing">Taniec na placu</string>
+    <string name="hrv_status_last_night_highest_5">艢rednia z 5 maksymalnych minut ostatniej nocy</string>
+    <string name="bpm_value_unit">%1$d bpm</string>
+    <string name="devicetype_garmin_enduro_3">Garmin Enduro 3</string>
+    <string name="devicetype_garmin_forerunner_955">Garmin Forerunner 955</string>
+    <string name="devicetype_honor_watchgspro">Honor Watch GS Pro</string>
+    <string name="menuitem_stress_breakdown">Stres (szczeg贸艂owy)</string>
+    <string name="workoutSets">Powt贸rzenia</string>
+    <string name="workout_set_i">Powt贸rzenie %1d</string>
+    <string name="moondrop_equalizer_preset_monitor">Monitor</string>
+    <string name="moondrop_touch_earbud_both">Obie</string>
+    <string name="moondrop_touch_earbud">S艂uchawki</string>
+    <string name="moondrop_equalizer_preset_basshead">Basshead</string>
+    <string name="moondrop_touch_action_call_start">Rozpocznij po艂膮czenie</string>
+    <string name="moondrop_touch_action_assistant">Aktywuj asystenta g艂osowego</string>
+    <string name="moondrop_touch_action_call_pick_hang">Odbierz/Odrzu膰 po艂膮czenie</string>
+    <string name="moondrop_touch_trigger_long_press_3s">D艂ugie przyci艣ni臋cie (3s)</string>
+    <string name="soundcore_voice_prompts">Polecenia g艂osowe</string>
+    <string name="soundcore_button_brightness">Jasno艣膰 przycisku</string>
+    <string name="soundcore_button_brightness_low">Niska</string>
+    <string name="soundcore_button_brightness_medium">艢rednia</string>
+    <string name="soundcore_ldac_mode_title">Tryb LDAC</string>
+    <string name="soundcore_ldac_mode_summary">W艂膮czenie LDAC zwi臋kszy zu偶ycie baterii i mo偶e spowodowa膰 niestabilno艣膰 po艂膮czenia</string>
+    <string name="soundcore_adaptive_direction_title">Adaptacyjny kierunek</string>
+    <string name="soundcore_equalizer_preset_voice">G艂os</string>
+    <string name="soundcore_adaptive_direction_summary">Dopasuj ustawienia korektora automatycznie na podstawie kierunku urz膮dzenia</string>
+    <string name="soundcore_equalizer_preset_xtra_bass">Xtra Bass</string>
+    <string name="soundcore_equalizer_preset_balanced">Zbalansowany</string>
+    <string name="soundcore_equalizer_direction_standing">Stoi</string>
+    <string name="soundcore_equalizer_direction_lying">Le偶y</string>
+    <string name="soundcore_equalizer_reset_title">Przywr贸膰 do domy艣lnych</string>
+    <string name="label_distance_trip">Podr贸偶: %.1f km</string>
+    <string name="label_distance_total">Ca艂kowicie: %.1f km</string>
+    <string name="label_distance_trip_mph">Podr贸偶: %.1f mi</string>
+    <string name="label_distance_total_mph">Ca艂kowicie: %.1f mi</string>
+    <string name="none">呕adne</string>
+    <string name="devicesetting_scannable_minimum_unseen_summary">Po zeskanowaniu, urz膮dzenie nie mo偶e by膰 kontaktowane przez t膮 okre艣lon膮 d艂ugo艣膰 czasu przed ponownym zarejestrowaniem</string>
+    <string name="pref_summary_cycling_persistence_interval">Interwa艂 w sekundach, jak cz臋sto obecne dane o je藕dzie powinny by膰 zapisywane w bazy danych</string>
+    <string name="pref_deprecated_media_control_summary">Wysy艂a polecenia kontroli medi贸w w postaci zdarze艅 kluczowych, zamiast kontrolera medi贸w.</string>
+    <string name="prefs_title_gatt_client_allow_gatt_interactions">Zezw贸l na interakcj臋 GATT poprzez API intencji BLE</string>
+    <string name="prefs_summary_gatt_client_device_state_updates">Otrzymuj informacje o zmianach stanu po艂膮czenia BLE poprzez intencje</string>
+    <string name="soundcore_equalizer_custom_summary">Konfiguruj parametryczne ustawienia korektora</string>
+    <string name="devicetype_ble_gatt_client">Og贸lny klient BLE GATT</string>
+    <string name="moondrop_touch_action_anc_mode">W艂膮cz aktywn膮 redukcj臋 szum贸w</string>
+    <string name="devicesetting_scannable_minimum_unseen">Minimalny czas bez kontaktu (sekundy)</string>
+    <string name="device_name_bicycle_sensor">Czujnik rowerowy</string>
+    <string name="devicetype_cycling_sensor">Czujnik pr臋dko艣ci do roweru</string>
+    <string name="title_cycling">Kolarstwo</string>
+    <string name="pref_title_sync_birthdays">Synchronizuj urodziny</string>
+    <string name="contact_birthday">Urodziny: %1s</string>
+    <string name="birthdays">Urodziny</string>
+    <string name="pref_dnd_follow_phone_summary">Gdy tryb 鈥瀊ie przeszkadza膰鈥� jest w艂膮czany, lub wy艂膮czany w telefonie, automatycznie prze艂膮cz go r贸wnie偶 na urz膮dzeniu</string>
+    <string name="date_placeholders__date__time">%1s, %1s</string>
+    <string name="date_placeholders__start_time__end_time">%1s - %2s</string>
+    <string name="pref_dnd_follow_phone_title">艢led藕 ustawienia trybu 鈥瀗ie przeszkadza膰鈥� w telefonie</string>
+    <string name="pref_title_calendar_lookahead">Liczba kolejnych dni</string>
+    <string name="pref_summary_calendar_lookahead">Synchronizuj wydarzenia z kalendarza do %1s dni</string>
+    <string name="breaths_per_min">oddechy/min</string>
+    <string name="milliseconds_ms">ms</string>
+    <string name="inactivity_warnings_minimum_steps_title">Minimalna ilo艣膰 krok贸w</string>
+    <string name="prefs_hrv_monitoring_description">Automatycznie monitoruj zmienno艣膰 t臋tna w ci膮gu dnia</string>
+    <string name="pref_dashboard_widget_today_yesterday_data_title">Dane z wczoraj</string>
+    <string name="realtime_settings">Ustawienia w czasie rzeczywistym</string>
+    <string name="pref_voice_passthrough_enabled_summary">Pozw贸l, aby d藕wi臋ki zewn臋trzne dociera艂y do Twoich uszu</string>
+    <string name="mijia_lywsd_comfort_lower">Dolna granica</string>
+    <string name="pref_summary_sync_birthdays">Synchronizuj urodziny kontakt贸w z wydarzeniami w kalendarzu</string>
+    <string name="music_upload_info">Zamierzasz przes艂a膰 nast臋puj膮cy plik muzyczny:\n%1$s\nTytu艂 utworu: %2$s\nAlbum: %3$s\n</string>
+    <string name="authentication_failed_negotiation">Negocjowanie klucza uwierzytelniaj膮cego nie powiod艂o si臋</string>
+    <string name="stepRateSum">Suma szybko艣ci kroku</string>
+    <string name="idasen_pref_value_warning">Warto艣膰 musi mie艣ci膰 si臋 w przedziale 62鈥�126 cm</string>
+    <string name="prefs_hrv_monitoring_title">Monitorowanie HRV</string>
+    <string name="busy_task_fetch_hrv_data">Pobieranie danych HRV</string>
+    <string name="mijia_lywsd_comfort_level_summary">Skonfiguruj limity temperatury i wilgotno艣ci wy艣wietlanych emoji</string>
+    <string name="pref_crash_notification_title">Powiadom o awarii</string>
+    <string name="pref_crash_notification_summary">Gdy aplikacja ulegnie awarii, wy艣wietl powiadomienie z b艂臋dem</string>
+    <string name="max_respiration_rate">Maksymalna cz臋sto艣膰 oddech贸w</string>
+    <string name="pref_wear_sensor_summary">Wykryj, kiedy urz膮dzenie nie jest noszone</string>
+    <string name="file_already_exists">Plik ju偶 istnieje</string>
+    <string name="pref_dashboard_widget_today_time_indicator_title">Wska藕nik aktualnego czasu</string>
+    <string name="pref_header_generic">Og贸lny</string>
+    <string name="average_respiration_rate">Cz臋stotliwo艣膰 oddychania</string>
+    <string name="min_respiration_rate">Minimalna cz臋sto艣膰 oddech贸w</string>
+    <string name="mijia_lywsd_comfort_humidity_summary">Zalecany zakres: 20 - 85</string>
+    <string name="mijia_lywsd_comfort_temperature_title">Temperatura (掳C)</string>
+    <string name="mijia_lywsd_comfort_temperature_summary">Zalecany zakres: 19 - 27</string>
+    <string name="mijia_lywsd_comfort_humidity_title">Wilgotno艣膰 (%)</string>
+    <string name="inactivity_warnings_minimum_steps_summary">Minimalna liczba krok贸w, kt贸re nale偶y wykona膰 w minutach progowych</string>
+    <string name="mijia_lywsd_comfort_upper">G贸rna granica</string>
+    <string name="app_crash_notification_title">%1s uleg艂 awarii</string>
+    <string name="app_crash_share_stacktrace">Udost臋pnij b艂膮d</string>
+    <string name="hrZoneThreshold">Pr贸g</string>
+    <string name="hrZoneMaximum">Maksymalny</string>
+    <string name="workout_set_reps">Powt贸rzenia</string>
+    <string name="pref_dashboard_widget_today_yesterday_data_summary">Poka偶 wczorajsze dane przyciemnione pomi臋dzy bie偶膮c膮 godzin膮 a p贸艂noc膮</string>
+    <string name="stepRateAvg">艢rednia liczba krok贸w</string>
+    <string name="activity_detail_share_json_details">Udost臋pnij szczeg贸艂y JSON</string>
+    <string name="pref_dashboard_widget_today_time_indicator_summary">Poka偶 wska藕nik w bie偶膮cym czasie, aby wizualnie oddzieli膰 dane od wczoraj i dzisiaj</string>
+    <string name="hrv_rmssd">HRV RMSSD</string>
+    <string name="devicetype_colmi_r10">Colmi R10</string>
+    <string name="fmtPaceAverage">Tempo 艣rednie</string>
+    <string name="devicetype_garmin_fenix_6s_pro">Garmin Fenix 6S Pro</string>
+    <string name="soundcore_equalizer_preset">Wst臋pnie ustawione</string>
+    <string name="fmtPaceType">Typ tempa %d</string>
+    <string name="prefs_summary_gatt_client_allow_gatt_interactions">Umo偶liwia wysy艂anie charakterystycznych dla BLE polece艅 odczytu/zapisu i po艂膮czenia</string>
+    <string name="prefs_title_gatt_client_api_package">Pakiet API BLE</string>
+    <string name="pref_voice_passthrough_level">Poziom przej艣cia</string>
+    <string name="prefs_summary_gatt_client_notification_intents">Otrzymuj zmiany charakterystyczne dla BLE poprzez Intencje</string>
+    <string name="prefs_summary_gatt_client_api_package">Ogranicz komunikacj臋 BLE Intent API do tego pakietu</string>
+    <string name="devicetype_garmin_forerunner_55">Garmin Forerunner 55</string>
+    <string name="devicetype_garmin_forerunner_620">Garmin Forerunner 620</string>
+    <string name="devicetype_huawei_band3pro">Huawei Band 3 (Pro)</string>
+    <string name="devicetype_oppo_enco_air">Oppo Enco Air</string>
+    <string name="pref_voice_passthrough_enabled">Przej艣cie</string>
+    <string name="devicetype_garmin_venu_sq_2">Garmin Venu Sq 2</string>
+    <string name="devicetype_garmin_fenix_7">Garmin Fenix 7</string>
+    <string name="devicetype_garmin_fenix_8">Garmin Fenix 8</string>
+    <string name="devicetype_idasen">IKEA Idasen Desk</string>
+    <string name="impactAvg">艢redni wp艂yw</string>
+    <string name="eversionAngleAvg">艢redni k膮t wywini臋cia</string>
+    <string name="pref_wear_sensor_title">No艣 czujnik</string>
+    <string name="idasen_pref_mid_height">Wysoko艣膰 w pozycji 艣rodkowej (w centymetrach)</string>
+    <string name="idasen_pref_sit_height">Wysoko艣膰 w pozycji siedz膮cej (w centymetrach)</string>
+    <string name="error_no_cycling_sensor_found">nie znaleziono czujnika jazdy na rowerze</string>
+    <string name="devicetype_sony_wf_c700n">Sony WF-C700N</string>
+    <string name="devicetype_garmin_fenix_6s_sapphire">Garmin Fenix 6S Sapphire</string>
+    <string name="hrv_sdrr">HRV SDRR</string>
+    <string name="fmtPaceTypeAverage">Typ tempa %d 艣rednia</string>
+    <string name="fmtPaceCorrection">Korekta tempa %d</string>
+    <string name="mijia_lywsd_comfort_level_title">Poziom komfortu</string>
+    <string name="activity_type_rolling">Rolki</string>
+    <string name="activity_type_xc_classic_ski">Narciarstwo klasyczne</string>
+    <string name="pref_summary_garmin_default_reply_suffix">Do艂膮czany jako dodatek do sufiksu ustawionego w Gadgetbridge</string>
+    <string name="devicetype_huawei_watchgtcyber">Huawei Watch GT Cyber</string>
+    <string name="hrZoneEasy">艁atwy</string>
+    <string name="eversionAngleMax">Maksymalny k膮t wywr贸cenia</string>
+    <string name="devicetype_garmin_forerunner_235">Garmin Forerunner 235</string>
+    <string name="devicetype_sony_wi_c100">Sony WI-C100</string>
+    <string name="devicetype_huawei_watchd2">Huawei Watch D2</string>
+    <string name="devicetype_huawei_watchgt5">Huawei Watch GT 5 (Pro)</string>
+    <string name="impactMax">Maksymalny wp艂yw</string>
+    <string name="fmtPaceDistance">Tempo %d dystansu</string>
+    <string name="swingAngleAvg">艢redni k膮t wychylenia</string>
+    <string name="fmtPacePace">Tempo %d tempa</string>
+    <string name="moondrop_equalizer_preset_reference">Odniesienie</string>
+    <string name="devicesetting_scannable_debounce">Limit czasu mo偶liwego do zeskanowania odbicia (sekundy)</string>
+    <string name="device_name_cycling_sensor">Czujnik rowerowy</string>
+    <string name="paceCorrection">Korekta</string>
+    <string name="number_selected_items">Wybrano %1d</string>
+    <string name="devicetype_garmin_venu_sq">Garmin Venu Sq</string>
+    <string name="devicetype_bandw_pseries">Bowers and Wilkins P series</string>
+    <string name="menuitem_heart_rate_push">Pomiar t臋tna</string>
+    <string name="idasen_control_button_mid">MID</string>
+    <string name="idasen_control_button_sit">SIT</string>
+    <string name="soundcore_equalizer_preset_signature">soundcore Signature</string>
+    <string name="pref_title_cycling_persistence_interval">Interwa艂 trwa艂o艣ci</string>
+    <string name="active_calories">Aktywne kalorie</string>
+    <string name="active_calories_short">Aktywne</string>
+    <string name="total_calories_burnt">Ca艂kowicie spalone</string>
+    <string name="activity_prefs_goal_active_calories_burnt">Cel dzienny: aktywne spalone kalorie</string>
+    <string name="pref_developer_add_test_activities_summary">Wype艂nij baz臋 danych testami pozornymi</string>
+    <string name="music_delete">Usu艅 piosenk臋</string>
+    <string name="music_all_songs">Wszystkie piosenki</string>
+    <string name="music_error">Wyst膮pi艂 b艂膮d</string>
+    <string name="activity_type_flying">Flying</string>
+    <string name="midFootLandings">Mid foot landings</string>
+    <string name="foreFootLandings">Fore foot landings</string>
+    <string name="backFootLandings">Back foot landings</string>
+    <string name="moondrop_touch_trigger">Spust</string>
+    <string name="activity_type_transition">Transition</string>
+    <string name="activity_type_tactical">Tactical</string>
+    <string name="activity_type_push_run_speed">Push - Run Speed</string>
+    <string name="activity_type_indoor_push_run_speed">Indoor Push - Run Speed</string>
+    <string name="activity_type_indoor_push_walk_speed">Indoor Push - Walk Speed</string>
+    <string name="first_start_intro_welcome_to">Witamy w</string>
+    <string name="first_start_overview_title">Przegl膮d</string>
+    <string name="first_start_overview_devices">Widok urz膮dze艅 pokazuje wszystkie skonfigurowane urz膮dzenia i ich status, a tak偶e zapewnia dost臋p do funkcji charakterystycznych dla danego urz膮dzenia, takich jak szczeg贸艂owe wykresy, ustawienia, aplikacje i alarmy.</string>
+    <string name="first_start_open_source_title">Otwarte 藕r贸d艂o</string>
+    <string name="first_start_permissions_title">Uprawnienia</string>
+    <string name="first_start_permissions_request_all_button">Popro艣 o wszystkie uprawnienia</string>
+    <string name="first_start_permissions_request_button">Wymagany</string>
+    <string name="first_start_get_started_title">Zacznij teraz</string>
+    <string name="first_start_get_started_add_first_device_button">Dodaj pierwsze urz膮dzenie</string>
+    <string name="first_start_get_started_restore_button">Przywr贸膰 kopi臋 zapasow膮</string>
+    <string name="permission_manage_dnd_summary">Zmiana zasad powiadomie艅 DND</string>
+    <string name="permission_displayover_title">Wy艣wietlaj nad innymi aplikacjami</string>
+    <string name="permission_fine_location_title">Dok艂adna lokalizacja</string>
+    <string name="permission_fine_location_summary">Skanowanie urz膮dze艅 Bluetooth</string>
+    <string name="permission_background_location_title">Lokalizacja w tle</string>
+    <string name="permission_bluetooth_title">Bluetooth</string>
+    <string name="title_activity_musicmanager">Mened偶er muzyczny</string>
+    <string name="activity_type_push_walk_speed">Push - Walk Speed</string>
+    <string name="pref_music_management_title">Zarz膮dzaj muzyk膮</string>
+    <string name="pref_music_management_summary">Zarz膮dzaj muzyk膮 na zegarku</string>
+    <string name="music_delete_confirm_description">Czy na pewno chcesz usun膮膰 \'%1$s\'?</string>
+    <string name="music_add_to_playlist">Dodaj do playlisty</string>
+    <string name="music_delete_from_playlist">Usu艅 z listy odtwarzania</string>
+    <string name="music_huawei_device_info">Obs艂ugiwane formaty: %1$s\nPami臋膰 masowa zegarka: %2$d MB</string>
+    <string name="devicetype_realme_buds_t110">Realme Buds T110</string>
+    <string name="first_start_overview_desc">Gadgetbridge ma dwa g艂贸wne widoki, ka偶dy z nich ma sw贸j w艂asny cel.</string>
+    <string name="first_start_overview_dashboard">Pulpit nawigacyjny pozwala szybko zorientowa膰 si臋, jak sobie radzisz dzisiaj. Widok kalendarza pokazuje status Twoich cel贸w na przestrzeni ca艂ego miesi膮ca.</string>
+    <string name="first_start_open_source_text">Gadgetbridge to aplikacja typu open source. Jest rozwijana przez spo艂eczno艣膰, dla spo艂eczno艣ci.\n\nKa偶dy mo偶e si臋 przyczyni膰 do rozwoju poprzez kod, dokumentacj臋, testowanie i darowizny.\n\nGadgetbridge nie zawiera 偶adnych reklam ani 艣ledzenia. Przechowuje Twoje dane lokalnie na Twoim urz膮dzeniu z Androidem, wi臋c jest w 100% przyjazna dla prywatno艣ci.\n\nOdwied藕 nasz膮 stron臋 internetow膮, aby uzyska膰 wi臋cej informacji, dokumentacj臋 i linki do naszych kana艂贸w komunikacji.</string>
+    <string name="active_calories_goal">Aktywny cel</string>
+    <string name="activity_type_marine">Marine</string>
+    <string name="devicetype_garmin_instinct_2">Garmin Instinct 2</string>
+    <string name="menuitem_calories_segmented">Kalorie (segmentowane)</string>
+    <string name="menuitem_calories_active_goal">Cel kaloryczny (aktywny)</string>
+    <string name="first_start_intro_tag_line">Uwolnij si臋 od zastrze偶onych aplikacji i us艂ug w chmurze oferowanych przez producent贸w gad偶et贸w.</string>
+    <string name="idasen_control_button_stand">STAND</string>
+    <string name="pref_developer_add_test_activities_title">Dodaj aktywno艣ci testowe</string>
+    <string name="music_new_playlist">Nowa lista odtwarzania</string>
+    <string name="music_rename_playlist">Zmie艅 nazw臋 listy odtwarzania</string>
+    <string name="first_start_welcome_title">Witamy</string>
+    <string name="first_start_permissions_desc">Gadgetbridge potrzebuje wielu uprawnie艅, aby wykonywa膰 wszystkie swoje funkcje. Przejrzyj uprawnienia i ich cele poni偶ej.</string>
+    <string name="first_start_get_started_desc">Aby rozpocz膮膰, dodaj pierwsze urz膮dzenie bezpo艣rednio z tego ekranu, przywr贸膰 kopi臋 zapasow膮 lub zacznij od czystej bazy danych.</string>
+    <string name="first_start_get_started_go_to_app_button">Przejd藕 do aplikacji</string>
+    <string name="permission_manage_dnd_title">Zarz膮dzaj funkcj膮 Nie przeszkadza膰</string>
+    <string name="permission_notifications_summary">Przekazywanie powiadomie艅 do pod艂膮czonych gad偶et贸w</string>
+    <string name="permission_displayover_summary">U偶ywany przez Bangle.js do uruchamiania aplikacji i innych funkcji w telefonie</string>
+    <string name="permission_background_location_summary">Skanowanie urz膮dze艅 Bluetooth w tle i wysy艂anie lokalizacji do wybranych gad偶et贸w</string>
+    <string name="permission_bluetooth_summary">艁膮czenie si臋 z urz膮dzeniami Bluetooth</string>
+    <string name="idasen_pref_stand_height">Wysoko艣膰 pozycji stoj膮cej (w centymetrach)</string>
+    <string name="permission_bluetooth_admin_title">Administrator Bluetooth</string>
+    <string name="permission_bluetooth_scan_title">Skanowanie Bluetooth</string>
+    <string name="permission_bluetooth_scan_summary">Skanowanie w poszukiwaniu nowych urz膮dze艅 Bluetooth</string>
+    <string name="permission_bluetooth_connect_title">Po艂膮czenie Bluetooth</string>
+    <string name="permission_bluetooth_connect_summary">艁膮czenie si臋 z ju偶 sparowanymi urz膮dzeniami Bluetooth</string>
+    <string name="permission_post_notification_title">Wysy艂anie powiadomie艅</string>
+    <string name="permission_internet_access_title">Dost臋p do Internetu</string>
+    <string name="permission_internet_access_summary">Synchronizacja z zasobami online</string>
+    <string name="permission_contacts_title">Kontakty</string>
+    <string name="permission_calendar_summary">Wysy艂anie kalendarza do gad偶et贸w</string>
+    <string name="permission_receive_sms_title">Odbieranie SMS</string>
+    <string name="permission_receive_sms_summary">Przekazywanie wiadomo艣ci SMS do gad偶et贸w</string>
+    <string name="permission_send_sms_title">Wy艣lij SMS</string>
+    <string name="permission_read_call_log_summary">Przekierowywanie rejestru po艂膮cze艅 do gad偶et贸w</string>
+    <string name="permission_read_phone_state_title">Odczyt stanu telefonu</string>
+    <string name="permission_read_phone_state_summary">Odczytywanie statusu trwaj膮cych po艂膮cze艅</string>
+    <string name="permission_call_phone_title">Po艂膮czenia telefoniczne</string>
+    <string name="permission_call_phone_summary">Inicjowanie po艂膮cze艅 telefonicznych z gad偶et贸w</string>
+    <string name="permission_process_outgoing_calls_title">Przetwarzanie po艂膮cze艅 wychodz膮cych</string>
+    <string name="permission_process_outgoing_calls_summary">Odczyt numeru po艂膮czenia wychodz膮cego w celu wy艣wietlenia go na gad偶ecie</string>
+    <string name="permission_answer_phone_calls_title">Odbieranie po艂膮cze艅 telefonicznych</string>
+    <string name="permission_answer_phone_calls_summary">Odbieranie po艂膮cze艅 telefonicznych z gad偶et贸w</string>
+    <string name="permission_external_storage_title">Pami臋膰 zewn臋trzna</string>
+    <string name="permission_external_storage_summary">Korzystanie z obraz贸w, dzwonk贸w, plik贸w aplikacji i innych</string>
+    <string name="permission_query_all_packages_title">Zapytaj o wszystkie pakiety</string>
+    <string name="permission_query_all_packages_summary">Odczytywanie nazw i ikon wszystkich zainstalowanych aplikacji</string>
+    <string name="permission_bluetooth_admin_summary">Odkrywanie i parowanie urz膮dze艅 Bluetooth</string>
+    <string name="permission_post_notification_summary">Wysy艂anie bie偶膮cych powiadomie艅, dzi臋ki kt贸rym us艂uga dzia艂a</string>
+    <string name="permission_contacts_summary">Wysy艂anie kontakt贸w do gad偶et贸w</string>
+    <string name="permission_send_sms_summary">Wysy艂anie SMS-贸w (szablonowych odpowiedzi) z gad偶et贸w</string>
+    <string name="permission_calendar_title">Kalendarz</string>
+    <string name="permission_read_call_log_title">Odczyt rejestru po艂膮cze艅</string>
+    <string name="devicetype_oppo_enco_air2">Oppo Enco Air2</string>
+    <string name="devicetype_miband9pro">Xiaomi Smart Band 9 Pro</string>
+    <string name="redmi_buds_5_pro_transparency_ambient">Wzmocnij d藕wi臋ki otoczenia</string>
+    <string name="redmi_buds_5_pro_equalizer_band_62">62 Hz</string>
+    <string name="redmi_buds_5_pro_equalizer_neg4">-4 dB</string>
+    <string name="redmi_buds_5_pro_equalizer_neg2">-2 dB</string>
+    <string name="redmi_buds_5_pro_equalizer_neg3">-3 dB</string>
+    <string name="redmi_buds_5_pro_equalizer_2">2 dB</string>
+    <string name="redmi_buds_5_pro_equalizer_3">3 dB</string>
+    <string name="devicetype_redmi_watch_5_lite">Redmi Watch 5 Lite</string>
+    <string name="jumps_minute">skoki/min</string>
+    <string name="jumps_unit">skoki</string>
+    <string name="Jumps">Skoki</string>
+    <string name="watchface_widget_type_sp02">SpO2</string>
+    <string name="redmi_buds_5_pro_double_connection">Podw贸jne po艂膮czenie</string>
+    <string name="redmi_buds_5_pro_adaptive_sound_description">Dostosowuje d藕wi臋k do kszta艂tu ucha i otoczenia</string>
+    <string name="redmi_buds_5_pro_double_connection_description">Umo偶liwia jednoczesne pod艂膮czenie s艂uchawek do dw贸ch urz膮dze艅</string>
+    <string name="redmi_buds_5_pro_equalizer_preset_treble">Wzmocnij sopran</string>
+    <string name="redmi_buds_5_pro_equalizer_band_500">500 Hz</string>
+    <string name="redmi_buds_5_pro_equalizer_1">1 dB</string>
+    <string name="respiratoryrate">Cz臋stotliwo艣膰 oddech贸w</string>
+    <string name="sleep_avg">艢rednia snu</string>
+    <string name="lowest">Najni偶szy</string>
+    <string name="highest">Najwy偶szy</string>
+    <string name="devicetype_redmi_buds_5_pro">Redmi Buds 5 Pro</string>
+    <string name="redmi_buds_5_pro_combo_transparency_off">Przezroczysto艣膰 / Wy艂.</string>
+    <string name="redmi_buds_5_pro_equalizer_preset_standard">Standardowy</string>
+    <string name="redmi_buds_5_pro_equalizer_preset_voice">Wzmocnij g艂os</string>
+    <string name="redmi_buds_5_pro_equalizer_band_1k">1 kHz</string>
+    <string name="redmi_buds_5_pro_equalizer_band_2k">2 kHz</string>
+    <string name="redmi_buds_5_pro_equalizer_band_16k">16 kHz</string>
+    <string name="redmi_buds_5_pro_equalizer_neg6">-6 dB</string>
+    <string name="redmi_buds_5_pro_equalizer_5">5 dB</string>
+    <string name="redmi_buds_5_pro_equalizer_4">4 dB</string>
+    <string name="music_multiselect_limit">Osi膮gni臋to limit wyboru wi臋cej ni偶 jednego elementu</string>
+    <string name="devicetype_garmin_fenix_7x">Garmin Fenix 7X</string>
+    <string name="avgJumpRate">艢rednia szybko艣膰 skoku</string>
+    <string name="maxJumpRate">Maksymalna szybko艣膰 skoku</string>
+    <string name="totalJumps">艁膮czna liczba skok贸w</string>
+    <string name="redmi_buds_5_pro_adaptive_sound">D藕wi臋k adaptacyjny</string>
+    <string name="redmi_buds_5_pro_combo_anc_transparency">ANC / Przejrzysto艣膰</string>
+    <string name="redmi_buds_5_pro_anc_light">Lekki</string>
+    <string name="redmi_buds_5_pro_anc_deep">G艂臋boki</string>
+    <string name="redmi_buds_5_pro_transparency_strength">Si艂a przejrzysto艣ci</string>
+    <string name="redmi_buds_5_pro_transparency_regular">Regularny</string>
+    <string name="redmi_buds_5_pro_transparency_voice">Ulepsz g艂osy</string>
+    <string name="redmi_buds_5_pro_combo_anc_off">ANC / Wy艂.</string>
+    <string name="redmi_buds_5_pro_equalizer_6">6 dB</string>
+    <string name="sleep_score">Ocena jako艣ci snu</string>
+    <string name="sleep_score_value">Wynik: %1d</string>
+    <string name="devicetype_colmi_r09">Colmi R09</string>
+    <string name="redmi_buds_5_pro_equalizer_preset_bass">Wzmocnij bas</string>
+    <string name="redmi_buds_5_pro_equalizer_preset_custom">W艂asne</string>
+    <string name="redmi_buds_5_pro_equalizer_band_125">125 Hz</string>
+    <string name="redmi_buds_5_pro_equalizer_band_250">250 Hz</string>
+    <string name="music_delete_multiple_confirm_description">Czy na pewno chcesz usun膮膰 \'%1$d\' utwor贸w?</string>
+    <string name="devicetype_huawei_band2pro">Huawei Band 2 (Pro)</string>
+    <string name="redmi_buds_5_pro_anc_balanced">Zr贸wnowa偶ony</string>
+    <string name="redmi_buds_5_pro_equalizer_band_4k">4 kHz</string>
+    <string name="redmi_buds_5_pro_equalizer_band_8k">8 kHz</string>
+    <string name="redmi_buds_5_pro_equalizer_band_12k">12 kHz</string>
+    <string name="redmi_buds_5_pro_equalizer_neg5">-5 dB</string>
+    <string name="redmi_buds_5_pro_equalizer_neg1">-1 dB</string>
+    <string name="redmi_buds_5_pro_equalizer_zero">0 dB</string>
+    <string name="devicetype_huawei_freebuds_5i">Huawei FreeBuds 5i</string>
+    <string name="redmi_buds_5_pro_combo_all">ANC / Przezroczysto艣膰 / Wy艂.</string>
+    <string name="garmin_intensity_minutes">Minuty intensywnej aktywno艣ci</string>
+    <string name="stepRateMax">Maksymalna szybko艣膰 kroku</string>
+    <string name="weekly_total">Ca艂kowity tygodniowy</string>
+    <string name="devicetype_casioecbs100">Casio ECB-S100</string>
+    <string name="discharge_interval_1">Interwa艂 roz艂adowania 1</string>
+    <string name="discharge_interval_2">Interwa艂 roz艂adowania 2</string>
+    <string name="discharge_interval_3">Interwa艂 roz艂adowania 3</string>
+    <string name="discharge_interval_4">Interwa艂 roz艂adowania 4</string>
+    <string name="discharge_interval_5">Interwa艂 roz艂adowania 5</string>
+    <string name="power_w">Moc w W</string>
+    <string name="manual_discharge_summary">kiedy wy艂膮czone, to zak艂ada inteligentne roz艂adowanie kontrolowane przez zewn臋trzny miernik energii (nieobs艂ugiwane przez Gadgetbridge)</string>
+    <string name="manual_discharge">R臋czne odst臋py mi臋dzy roz艂adowaniami</string>
+    <string name="summary_battery_discharge_intervals_set">Wy艣lij konfiguracj臋 poni偶ej do urz膮dzenia</string>
+    <string name="battery_minimum_charge">Minimalny poziom na艂adowania baterii w %</string>
+    <string name="battery_allow_pass_though_summary">Po w艂膮czeniu tej opcji akumulator mo偶na 艂adowa膰 podczas roz艂adowywania</string>
+    <string name="about_build_details_copied_to_clipboard">Szczeg贸艂y kompilacji skopiowano do schowka</string>
+    <string name="devicetype_marstek_b2500">Marstek B2500</string>
+    <string name="battery_discharge_intervals">Interwa艂y roz艂adowywania akumulatora</string>
+    <string name="battery_allow_pass_through">Zezwalaj na przepustowo艣膰 baterii</string>
+</resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml
new file mode 100644
index 0000000..0b1feb3
--- /dev/null
+++ b/app/src/main/res/values-pt-rBR/strings.xml
@@ -0,0 +1,2542 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources xmlns:tools="http://schemas.android.com/tools">
+    <string name="application_name_generic">Gadgetbridge</string>
+    <string name="title_activity_controlcenter_generic">Gadgetbridge</string>
+    <string name="action_settings">Configura莽玫es</string>
+    <string name="action_debug">Depurar</string>
+    <string name="action_quit">Sair</string>
+    <string name="controlcenter_fetch_activity_data">Sincronizar</string>
+    <string name="controlcenter_find_device">Buscar dispositivo perdido</string>
+    <string name="controlcenter_take_screenshot">Print da tela</string>
+    <string name="controlcenter_disconnect">Desconectar</string>
+    <string name="controlcenter_delete_device">Excluir dispositivo</string>
+    <string name="controlcenter_delete_device_name">Excluir %1$s</string>
+    <string name="controlcenter_delete_device_dialogmessage">Isto vai excluir o dispositivo e todos os dados associados!</string>
+    <string name="title_activity_debug">Depurar</string>
+    <!--Strings related to AppManager-->
+    <string name="title_activity_appmanager">Gerenciador de aplicativos</string>
+    <string name="appmanager_cached_watchapps_watchfaces">Aplicativos em cache</string>
+    <string name="appmanager_installed_watchapps">Apps instalados</string>
+    <string name="appmanager_installed_watchfaces">Mostradores instalados</string>
+    <string name="appmananger_app_delete">Excluir</string>
+    <string name="appmananger_app_delete_cache">Excluir e remover do cache</string>
+    <string name="appmananger_app_reinstall">Reinstalar</string>
+    <string name="appmanager_app_openinstore">Pesquisar na loja Pebble</string>
+    <string name="appmanager_health_activate">Ativar</string>
+    <string name="appmanager_health_deactivate">Desativar</string>
+    <string name="appmanager_hrm_activate">Ativar HRM</string>
+    <string name="appmanager_hrm_deactivate">Desativar HRM</string>
+    <string name="appmanager_weather_activate">Ativar o aplicativo de clima do sistema</string>
+    <string name="appmanager_weather_deactivate">Desativar o aplicativo de clima do sistema</string>
+    <string name="appmanager_weather_install_provider">Instalar o aplicativo de notifica莽茫o de clima</string>
+    <string name="app_configure">Configurar</string>
+    <string name="app_move_to_top">Mover para o topo</string>
+    <!--Strings related to CalBlacklist-->
+    <!--Strings related to FwAppInstaller-->
+    <string name="title_activity_fw_app_insaller">Instalador de FW/aplicativo</string>
+    <string name="fw_upgrade_notice">Voc锚 est谩 prestes a instalar o %s.</string>
+    <string name="fw_multi_upgrade_notice">Voc锚 est谩 prestes a instalar os firmwares %1$s e %2$s no lugar dos que est茫o atualmente no seu Mi Band.</string>
+    <string name="miband_firmware_known">O firmware foi testado e 茅 compat铆vel com Gadgetbridge.</string>
+    <string name="miband_firmware_unknown_warning">Este firmware n茫o foi testado e pode n茫o ser compat铆vel com o Gadgetbridge.
+\n
+\nN脙O 脡 RECOMENDADO que voc锚 instale-o!</string>
+    <string name="miband_firmware_suggest_whitelist">Se voc锚 ainda desejar continuar e tudo funcionar corretamente, por favor, diga aos desenvolvedores do Gadgetbridge para inclu铆rem a vers茫o de firmware %s na lista.</string>
+    <!--Strings related to Settings-->
+    <string name="title_activity_settings">Configura莽玫es</string>
+    <string name="pref_header_general">Configura莽玫es gerais</string>
+    <string name="pref_title_general_autoconnectonbluetooth">Conecta o dispositivo Gadgetbridge quando o Bluetooth estiver ligado</string>
+    <string name="pref_title_general_autostartonboot">Iniciar automaticamente</string>
+    <string name="pref_title_general_autoreconnect">Reconectar automaticamente</string>
+    <string name="pref_title_audio_player">Prefer锚ncia de player de m煤sica</string>
+    <string name="pref_default">Padr茫o</string>
+    <string name="pref_header_datetime">Data e hora</string>
+    <string name="pref_title_datetime_syctimeonconnect">Hor谩rio de sincroniza莽茫o</string>
+    <string name="pref_summary_datetime_syctimeonconnect">Hor谩rio de sincroniza莽茫o para quando o dispositivo Gadgetbridge estiver conectando ou quando o fuso hor谩rio mudar no dispositivo Android</string>
+    <string name="pref_title_theme">Tema</string>
+    <string name="pref_theme_light">Claro</string>
+    <string name="pref_theme_dark">Escuro</string>
+    <string name="pref_title_language">Idioma</string>
+    <string name="pref_title_minimize_priority">Ocultar notifica莽茫o do Gadgetbridge</string>
+    <string name="pref_summary_minimize_priority_off">Exibir 铆cone na barra de status e nas notifica莽玫es na tela inicial</string>
+    <string name="pref_summary_minimize_priority_on">O 铆cone na barra de status e as notifica莽玫es na tela de bloqueio ficam ocultos</string>
+    <string name="pref_header_notifications">Notifica莽玫es</string>
+    <string name="pref_title_notifications_repetitions">Repeti莽玫es</string>
+    <string name="pref_title_notifications_call">Chamadas de telefone</string>
+    <string name="pref_title_notifications_sms">SMS</string>
+    <string name="pref_title_notifications_pebblemsg">Mensagens do Pebble</string>
+    <string name="pref_summary_notifications_pebblemsg">Suporte a aplicativos que enviam notifica莽玫es ao Pebble pelo PebbleKit.</string>
+    <string name="pref_title_notifications_generic">Suporte a notifica莽玫es gen茅ricas</string>
+    <string name="pref_title_whenscreenon">鈥uando a tela estiver ligada</string>
+    <string name="pref_title_notification_filter">N茫o perturbar</string>
+    <string name="pref_summary_notification_filter">Bloqueia todas as notifica莽玫es quando o modo N茫o pertubar est谩 ativo no telefone</string>
+    <string name="pref_title_transliteration">Translitera莽茫o</string>
+    <string name="pref_summary_transliteration">Ative isso se seu dispositivo n茫o tiver suporte 脿 fonte do seu idioma</string>
+    <string name="always">Sempre</string>
+    <string name="when_screen_off">Quando a tela estiver desligada</string>
+    <string name="never">Nunca</string>
+    <string name="pref_header_privacy">Privacidade</string>
+    <string name="pref_title_call_privacy_mode">Modo de privacidade de chamadas</string>
+    <string name="pref_call_privacy_mode_off">Exibir nome e n煤mero</string>
+    <string name="pref_call_privacy_mode_name">Ocultar nome, mas exibir n煤mero</string>
+    <string name="pref_call_privacy_mode_complete">Ocultar nome e n煤mero</string>
+    <string name="pref_header_cannned_messages">Mensagens predefinidas</string>
+    <string name="pref_title_canned_replies">Respostas</string>
+    <string name="pref_title_canned_reply_suffix">Sufixo comum</string>
+    <string name="pref_title_canned_messages_dismisscall">Chamadas ignoradas</string>
+    <string name="pref_title_canned_messages_set">Atualiza莽茫o no dispositivo</string>
+    <string name="pref_header_development">Op莽玫es de desenvolvedor</string>
+    <string name="pref_title_development_miaddr">Endere莽o da Mi Band</string>
+    <string name="pref_title_pebble_settings">Configura莽玫es de Pebble</string>
+    <string name="pref_header_activitytrackers">Monitores de atividade</string>
+    <string name="pref_title_pebble_activitytracker">Prefer锚ncia de monitor de atividades</string>
+    <string name="pref_title_pebble_sync_health">Sincronizar com Pebble Health</string>
+    <string name="pref_title_pebble_sync_misfit">Sincronizar com Misfit</string>
+    <string name="pref_title_pebble_sync_morpheuz">Sincronizar com Morpheuz</string>
+    <string name="pref_title_enable_outgoing_call">Suporte a chamadas originadas</string>
+    <string name="pref_summary_enable_outgoing_call">Desabilitar isso tamb茅m impedir谩 o Pebble 2/LE de vibrar ao originar chamadas</string>
+    <string name="pref_title_enable_pebblekit">Permitir acesso a aplicativos de terceiros</string>
+    <string name="pref_summary_enable_pebblekit">Habilitar suporte experimental a aplicativos de Android que usem PebbleKit</string>
+    <string name="pref_title_sunrise_sunset">Nascer e por do sol</string>
+    <string name="pref_summary_sunrise_sunset">Envia hor谩rios de nascer e por do sol baseado na localiza莽茫o para a linha do tempo do Pebble</string>
+    <string name="pref_title_autoremove_notifications">Remover automaticamente as notifica莽玫es ignoradas</string>
+    <string name="pref_summary_autoremove_notifications">Notifica莽玫es s茫o automaticamente removidas do dispositivo quando ignoradas no telefone</string>
+    <string name="pref_title_pebble_privacy_mode">Modo de privacidade</string>
+    <string name="pref_pebble_privacy_mode_off">Notifica莽玫es normais</string>
+    <string name="pref_pebble_privacy_mode_content">Deslocar texto de notifica莽玫es que extrapolar a tela </string>
+    <string name="pref_pebble_privacy_mode_complete">Mostrar apenas o 铆cone de notifica莽玫es</string>
+    <string name="pref_header_location">Localiza莽茫o</string>
+    <string name="pref_title_location_aquire">Obter localiza莽茫o</string>
+    <string name="pref_title_location_latitude">Latitude</string>
+    <string name="pref_title_location_longitude">Longitude</string>
+    <string name="pref_title_location_keep_uptodate">Manter localiza莽茫o atualizada</string>
+    <string name="pref_summary_location_keep_uptodate">Tenta obter a localiza莽茫o atual em tempo real, use a localiza莽茫o armazenada como reserva</string>
+    <string name="toast_enable_networklocationprovider">Por favor, ative a localiza莽茫o de rede</string>
+    <string name="toast_aqurired_networklocation">localiza莽茫o obtida</string>
+    <string name="pref_title_pebble_forceprotocol">For莽ar o protocolo de notifica莽茫o</string>
+    <string name="pref_summary_pebble_forceprotocol">Esta op莽茫o for莽a o uso do protocolo de notifica莽茫o mais recente dependendo da vers茫o do firmware. FA脟A ISSO APENAS SE SOUBER O QUE EST脕 FAZENDO!</string>
+    <string name="pref_title_pebble_forceuntested">Habilitar recursos n茫o certificados</string>
+    <string name="pref_summary_pebble_forceuntested">Habilitar recursos n茫o testados. FA脟A ISSO APENAS SE SOUBER O QUE EST脕 FAZENDO!</string>
+    <string name="pref_title_pebble_forcele">Sempre preferir BLE</string>
+    <string name="pref_summary_pebble_forcele">Usa suporte experimental a Pebble LE em vez do Bluetooth cl谩ssico. Requer ter nenhum LE pareado para, em seguida, parear o Pebble LE</string>
+    <string name="pref_title_pebble_mtu_limit">Limite do MTU de GATT do Pebble 2/LE</string>
+    <string name="pref_summary_pebble_mtu_limit">Se seu Pebble 2/Pebble LE n茫o funciona como esperado, tente esta configura莽茫o para limitar o MTU (faixa v谩lida 20鈥�512)</string>
+    <string name="pref_title_pebble_enable_applogs">Ativar log do aplicativo de monitoramento</string>
+    <string name="pref_summary_pebble_enable_applogs">Faz com que os registros log de aplicativos de monitoramento sejam registrados pelo Gadgetbridge (exige reconex茫o)</string>
+    <string name="pref_title_pebble_always_ack_pebblekit">ACK prematuro ao PebbleKit</string>
+    <string name="pref_summary_pebble_always_ack_pebblekit">Faz com que as mensagens enviadas para aplicativos de terceiros sejam reconhecidas sempre e imediatamente</string>
+    <string name="pref_title_pebble_reconnect_attempts">Tentativas de reconex茫o</string>
+    <string name="pref_title_unit_system">Unidades</string>
+    <string name="pref_title_timeformat">Formato da hora</string>
+    <string name="pref_title_screentime">Dura莽茫o da tela ligada</string>
+    <string name="prefs_title_all_day_heart_rate">Medi莽茫o da frequ锚ncia card铆aca ao longo do dia</string>
+    <string name="preferences_hplus_settings">Configura莽玫es de HPlus/Makibes</string>
+    <string name="not_connected">Desconectado</string>
+    <string name="connecting">Conectando</string>
+    <string name="connected">Conectado</string>
+    <string name="unknown_state">Estado desconhecido</string>
+    <string name="_unknown_">(desconhecido)</string>
+    <string name="test">Teste</string>
+    <string name="test_notification">Notifica莽玫es teste</string>
+    <string name="this_is_a_test_notification_from_gadgetbridge">Esta 茅 uma notifica莽茫o teste do Gadgetbridge</string>
+    <string name="bluetooth_is_not_supported_">Bluetooth n茫o suportado.</string>
+    <string name="bluetooth_is_disabled_">Bluetooth desabilitado.</string>
+    <string name="tap_connected_device_for_app_mananger">Toque no dispositivo conectado para o gerenciador de aplicativos</string>
+    <string name="tap_connected_device_for_activity">Toque no dispositivo conectado para atividade</string>
+    <string name="tap_connected_device_for_vibration">Toque no dispositivo conectado para vibra莽茫o</string>
+    <string name="tap_a_device_to_connect">Toque num dispositivo para ligar</string>
+    <string name="cannot_connect_bt_address_invalid_">N茫o foi poss铆vel conectar. Endere莽o de Bluetooth inv谩lido\?</string>
+    <string name="gadgetbridge_running_generic">Gadgetbridge em execu莽茫o</string>
+    <string name="installing_binary_d_d">Instalando bin谩rio %1$d/%2$d</string>
+    <string name="installation_failed_">Instala莽茫o falhou</string>
+    <string name="installation_successful">Instalado</string>
+    <string name="firmware_install_warning">VOC脢 EST脕 TENTANDO INSTALAR UM FIRMWARE, A SEU PR脫PRIO RISCO.
+\n
+\n
+\n Este firmware 茅 para HW Revis茫o: %s</string>
+    <string name="app_install_info">Voc锚 est谩 prestes a instalar o seguinte aplicativo:
+\n
+\n%1$s
+\nVers茫o %2$s por %3$s
+\n</string>
+    <string name="n_a">N/A</string>
+    <string name="initialized">inicializado</string>
+    <string name="appversion_by_creator">%1$s de %2$s</string>
+    <string name="title_activity_discovery">Descoberta de dispositivo</string>
+    <string name="discovery_stop_scanning">Parar com a busca</string>
+    <string name="discovery_start_scanning">Iniciar descoberta</string>
+    <string name="action_discover">Conectar novo dispositivo</string>
+    <string name="device_with_rssi">%1$s (%2$s)</string>
+    <string name="title_activity_android_pairing">Parear dispositivo</string>
+    <string name="android_pairing_hint">Use a caixa de di谩logo Android Bluetooth para parear o dispositivo.</string>
+    <string name="title_activity_mi_band_pairing">Parear sua Mi Band</string>
+    <string name="pairing">Pareando com %s鈥�</string>
+    <string name="pairing_creating_bond_with">Criar v铆nculo com %1$s (%2$s)</string>
+    <string name="pairing_unable_to_pair_with">N茫o 茅 poss铆vel parear com %1$s (%2$s)</string>
+    <string name="pairing_in_progress">Vincula莽茫o em andamento: %1$s (%2$s)</string>
+    <string name="pairing_already_bonded">J谩 ligado com %1$s (%2$s), conectando鈥�</string>
+    <string name="message_cannot_pair_no_mac">Nenhum endere莽o MAC passado, n茫o 茅 poss铆vel parear.</string>
+    <string name="preferences_category_device_specific_settings">Configura莽玫es espec铆ficas do dispositivo</string>
+    <string name="preferences_miband_settings">Configura莽玫es de Mi Band / Amazfit</string>
+    <string name="male">Masculino</string>
+    <string name="female">Feminino</string>
+    <string name="other">Outro</string>
+    <string name="left">Esquerda</string>
+    <string name="right">Direita</string>
+    <string name="miband_pairing_using_dummy_userdata">Nenhum dado v谩lido informado, usando dados fict铆cios por enquanto.</string>
+    <string name="miband_pairing_tap_hint">Quando a sua Mi Band vibrar e piscar, toque nele algumas vezes seguidas.</string>
+    <string name="appinstaller_install">Instalar</string>
+    <string name="discovery_note">Nota:</string>
+    <string name="candidate_item_device_image">Imagem do dispositivo</string>
+    <string name="miband_prefs_alias">Nome/Apelido</string>
+    <string name="pref_header_vibration_count">Quantidade de vibra莽玫es</string>
+    <string name="title_activity_sleepmonitor">Monitor de sono</string>
+    <string name="pref_write_logfiles">Escrever arquivos de registro</string>
+    <string name="initializing">Inicializando</string>
+    <string name="busy_task_fetch_activity_data">Coletando dados de atividade</string>
+    <string name="sleep_activity_date_range">De %1$s a %2$s</string>
+    <string name="prefs_wearside">Dispositivo na esquerda ou direita?</string>
+    <string name="pref_screen_vibration_profile">Perfil de vibra莽茫o</string>
+    <string name="vibration_profile_staccato">Destacado</string>
+    <string name="vibration_profile_short">Pequeno</string>
+    <string name="vibration_profile_medium">M茅dio</string>
+    <string name="vibration_profile_long">Longo</string>
+    <string name="vibration_profile_waterdrop">Pingo d\'谩gua</string>
+    <string name="vibration_profile_ring">Toque</string>
+    <string name="vibration_profile_alarm_clock">Alarme</string>
+    <string name="miband_prefs_vibration">Vibra莽茫o</string>
+    <string name="vibration_try">Experimente</string>
+    <string name="pref_screen_notification_profile_sms">Notifica莽茫o SMS</string>
+    <string name="pref_header_vibration_settings">Configura莽玫es de vibra莽玫es</string>
+    <string name="pref_screen_notification_profile_generic">Notifica莽茫o gen茅rica</string>
+    <string name="pref_screen_notification_profile_email">Notifica莽茫o de e-mail</string>
+    <string name="pref_screen_notification_profile_incoming_call">Notifica莽茫o de chamada recebida</string>
+    <string name="pref_screen_notification_profile_generic_chat">Bate-papo</string>
+    <string name="pref_screen_notification_profile_generic_navigation">Navega莽茫o</string>
+    <string name="pref_screen_notification_profile_generic_social">Rede social</string>
+    <string name="control_center_find_lost_device">Encontrar dispositivo perdido</string>
+    <string name="control_center_cancel_to_stop_vibration">Cancele para parar a vibra莽茫o.</string>
+    <string name="title_activity_charts">Atividade e sono</string>
+    <string name="title_activity_set_alarm">Configurar alarmes</string>
+    <string name="controlcenter_start_configure_alarms">Configurar alarmes</string>
+    <string name="title_activity_alarm_details">Detalhes do alarme</string>
+    <string name="alarm_sun_short">Dom</string>
+    <string name="alarm_mon_short">Seg</string>
+    <string name="alarm_tue_short">Ter</string>
+    <string name="alarm_wed_short">Qua</string>
+    <string name="alarm_thu_short">Qui</string>
+    <string name="alarm_fri_short">Sex</string>
+    <string name="alarm_sat_short">S谩b</string>
+    <string name="alarm_smart_wakeup">Despertar inteligente</string>
+    <string name="user_feedback_miband_set_alarms_failed">Houve um erro ao definir os alarmes. Por favor, tente novamente.</string>
+    <string name="user_feedback_miband_set_alarms_ok">Alarmes enviado para o dispositivo.</string>
+    <string name="chart_no_data_synchronize">Sem data. Sincronizar com dispositivo?</string>
+    <string name="user_feedback_miband_activity_data_transfer">Transferir %1$s dados a partir de %2$s</string>
+    <string name="miband_prefs_fitness_goal">Meta di谩ria de passos</string>
+    <string name="dbaccess_error_executing">Erro executando \'%1$s\'</string>
+    <string name="controlcenter_start_activitymonitor">Sua atividade (ALFA)</string>
+    <string name="cannot_connect">Imposs铆vel conectar: %1$s</string>
+    <string name="installer_activity_unable_to_find_handler">N茫o foi poss铆vel encontrar um manipulador para instalar o arquivo.</string>
+    <string name="pbw_install_handler_unable_to_install">Imposs铆vel instalar o arquivo fornecido: %1$s</string>
+    <string name="pbw_install_handler_hw_revision_mismatch">N茫o 茅 poss铆vel instalar o firmware fornecido: ele n茫o corresponde 脿 revis茫o do hardware do seu Pebble.</string>
+    <string name="installer_activity_wait_while_determining_status">Aguarde enquanto 茅 determinado o status da instala莽茫o鈥�</string>
+    <string name="notif_battery_low_title">Gadget com bateria baixa!</string>
+    <string name="notif_battery_low_percent">%1$s bateria a: %2$s%%</string>
+    <string name="notif_battery_low_bigtext_last_charge_time">脷ltima carga: %s \n</string>
+    <string name="notif_battery_low_bigtext_number_of_charges">N煤mero de cargas: %s</string>
+    <string name="sleepchart_your_sleep">Sono</string>
+    <string name="weekstepschart_steps_a_week">Passos por semana</string>
+    <string name="activity_sleepchart_activity_and_sleep">Atividade</string>
+    <string name="updating_firmware">Instalando firmware鈥�</string>
+    <string name="fwapp_install_device_not_ready">Arquivo n茫o pode ser instalado, o dispositivo n茫o est谩 pronto.</string>
+    <string name="installhandler_firmware_name">%1$s: %2$s %3$s</string>
+    <string name="miband_fwinstaller_compatible_version">Vers茫o compat铆vel</string>
+    <string name="miband_fwinstaller_untested_version">Vers茫o n茫o testada!</string>
+    <string name="fwappinstaller_connection_state">Conex茫o com o dispositivo: %1$s</string>
+    <string name="pbw_installhandler_pebble_firmware">Pebble Firmware %1$s</string>
+    <string name="pbwinstallhandler_correct_hw_revision">Corrigir a revis茫o do hardware</string>
+    <string name="pbwinstallhandler_incorrect_hw_revision">Incompatibilidade de revis茫o de hardware!</string>
+    <string name="pbwinstallhandler_app_item">%1$s (%2$s)</string>
+    <string name="updatefirmwareoperation_updateproblem_do_not_reboot">Problema com a transfer锚ncia do firmware. N脙O REINICIAR sua Mi Band!</string>
+    <string name="updatefirmwareoperation_metadata_updateproblem">Problemas ao transferir os metadados do firmware</string>
+    <string name="updatefirmwareoperation_update_complete">Instala莽茫o do firmware conclu铆da</string>
+    <string name="updatefirmwareoperation_update_complete_rebooting">Instala莽茫o do firmware conclu铆da, reiniciando o dispositivo鈥�</string>
+    <string name="updatefirmwareoperation_write_failed">Falha ao instalar o firmware</string>
+    <string name="chart_steps">Passos</string>
+    <string name="liveactivity_live_activity">Atividade ao vivo</string>
+    <string name="weeksteps_today_steps_description">Passos hoje, meta: %1$s</string>
+    <string name="pref_title_dont_ack_transfer">N茫o reconhecer transfer锚ncia de dados da atividade</string>
+    <string name="pref_summary_dont_ack_transfers">Se n茫o reconhecido para a pulseira, dados de atividade n茫o ser茫o apagados. 脷til se GB for usado em conjunto com outros aplicativos.</string>
+    <string name="pref_summary_keep_data_on_device">Vai manter os dados de atividade no Mi Band mesmo ap贸s a sincroniza莽茫o. 脷til se GB for usado em conjunto com outros aplicativos.</string>
+    <string name="pref_title_low_latency_fw_update">Usar o modo de baixa lat锚ncia para instala莽茫o de firmware</string>
+    <string name="pref_summary_low_latency_fw_update">Isso pode ajudar em dispositivos no caso de eventual falha na instala莽茫o de firmware.</string>
+    <string name="live_activity_steps_history">Hist贸rico de passos</string>
+    <string name="live_activity_current_steps_per_minute">Passos/min atuais</string>
+    <string name="live_activity_total_steps">Total de passos</string>
+    <string name="live_activity_steps_per_minute_history">Hist贸rico de passos por minuto</string>
+    <string name="live_activity_start_your_activity">Iniciar sua atividade</string>
+    <string name="abstract_chart_fragment_kind_activity">Atividade</string>
+    <string name="abstract_chart_fragment_kind_light_sleep">Sono leve</string>
+    <string name="abstract_chart_fragment_kind_deep_sleep">Sono profundo</string>
+    <string name="abstract_chart_fragment_kind_not_worn">N茫o usado</string>
+    <string name="device_not_connected">Desconectado.</string>
+    <string name="user_feedback_all_alarms_disabled">Todos os alarmes desabilitados</string>
+    <string name="pref_title_keep_data_on_device">Manter dados de atividade no dispositivo</string>
+    <string name="miband_fwinstaller_incompatible_version">Firmware incompat铆vel</string>
+    <string name="fwinstaller_firmware_not_compatible_to_device">Este firmware n茫o 茅 compat铆vel com seu dispositivo</string>
+    <string name="miband_prefs_reserve_alarm_calendar">Alarmes reservados para pr贸ximos eventos</string>
+    <string name="miband_prefs_hr_sleep_detection">Usar o sensor de frequ锚ncia card铆aca para melhorar a detec莽茫o do sono</string>
+    <string name="miband_prefs_device_time_offset_hours">Desvio do tempo do dispositivo em horas (para detectar o sono dos trabalhadores por turnos)</string>
+    <string name="miband2_prefs_dateformat">Formato da data</string>
+    <string name="dateformat_time">Hora</string>
+    <string name="dateformat_date_time">Hor谩rio &amp; data</string>
+    <string name="mi2_prefs_activate_display_on_lift">Ativar exibi莽茫o ao levantar</string>
+    <string name="FetchActivityOperation_about_to_transfer_since">Sobre para transferir dados desde %1$s</string>
+    <string name="waiting_for_reconnect">Aguardando para reconectar</string>
+    <string name="activity_prefs_about_you">Sobre voc锚</string>
+    <string name="activity_prefs_year_birth">Ano de nascimento</string>
+    <string name="activity_prefs_gender">G锚nero</string>
+    <string name="activity_prefs_height_cm">Altura em cm</string>
+    <string name="activity_prefs_weight_kg">Peso em kg</string>
+    <string name="authenticating">Autenticando</string>
+    <string name="authentication_required">Autentica莽茫o necess谩ria</string>
+    <string name="appwidget_text">Zzz</string>
+    <string name="add_widget">Adicionar widget</string>
+    <string name="activity_prefs_sleep_duration">Prefer锚ncia de dura莽茫o de sono em horas</string>
+    <string name="appwidget_setting_alarm">Um alarme foi definido para %1$02d:%2$02d</string>
+    <string name="device_hw">Hardware revis茫o: %1$s</string>
+    <string name="device_fw">Firmware vers茫o: %1$s</string>
+    <string name="error_creating_directory_for_logfiles">Erro ao criar o diret贸rio para arquivos de log: %1$s</string>
+    <string name="DEVINFO_HR_VER">"FC: "</string>
+    <string name="updatefirmwareoperation_update_in_progress">Instalando firmware</string>
+    <string name="updatefirmwareoperation_firmware_not_sent">Firmware n茫o enviado</string>
+    <string name="charts_legend_heartrate">Frequ锚ncia card铆aca</string>
+    <string name="live_activity_heart_rate">Frequ锚ncia card铆aca</string>
+    <string name="pref_title_pebble_health_store_raw">Armazenar registro bruto no banco de dados</string>
+    <string name="pref_summary_pebble_health_store_raw">Armazena os dados \"como est茫o\", aumentando o uso do banco de dados para permitir posterior interpreta莽茫o.</string>
+    <string name="action_db_management">Gerenciamento de dados</string>
+    <string name="title_activity_db_management">Gerenciamento de dados</string>
+    <string name="activity_db_management_import_export_explanation">As opera莽玫es de exporta莽茫o/importa莽茫o usam o seguinte caminho (veja abaixo) para um diret贸rio em seu dispositivo. Este diret贸rio 茅 acess铆vel a outros aplicativos Android e seu computador. Observe que este diret贸rio e todos os arquivos que cont锚m s茫o exclu铆dos se voc锚 desinstalar o Gadgetbridge. Os dados incluem:
+\n Export_preference - configura莽玫es globais
+\n Export_preference_MAC - configura莽玫es espec铆ficas do dispositivo
+\n Gadgetbridge - banco de dados de dispositivos e atividades
+\n Gadgetbridge_date - banco de dados exportado em uma data
+\n *.gpx - grava莽玫es deGPS
+\n *.log - arquivos de log
+\nEspere encontrar seus arquivos exportados (ou colocar os arquivos que deseja importar) l谩:</string>
+    <string name="dbmanagementactivvity_cannot_access_export_path">N茫o 茅 poss铆vel acessar o caminho de exporta莽茫o. Entre em contato com os desenvolvedores.</string>
+    <string name="dbmanagementactivity_exported_to">Exportado para: %1$s</string>
+    <string name="dbmanagementactivity_error_exporting_db">Erro ao exportar BD: %1$s</string>
+    <string name="dbmanagementactivity_import_data_title">Importar dados?</string>
+    <string name="dbmanagementactivity_overwrite_database_confirmation">Realmente sobrescrever os dados atuais? Todos os seus dados atuais de atividade (se houver) e prefer锚ncias ser茫o substitu铆dos.</string>
+    <string name="dbmanagementactivity_import_successful">Importado.</string>
+    <string name="dbmanagementactivity_error_importing_db">Erro ao importar BD: %1$s</string>
+    <string name="dbmanagementactivity_delete_activity_data_title">Excluir dados de atividade\?</string>
+    <string name="dbmanagementactivity_really_delete_entire_db">Realmente excluir o banco de dados inteiro\? Todos os seus dados de atividade e informa莽玫es sobre seus dispositivos ser茫o perdidos.</string>
+    <string name="dbmanagementactivity_database_successfully_deleted">Dados exclu铆dos.</string>
+    <string name="dbmanagementactivity_db_deletion_failed">Falha na exclus茫o do banco de dados.</string>
+    <string name="dbmanagementactivity_delete_old_activity_db">Excluir o banco de dados de atividades antigo\?</string>
+    <string name="dbmanagementactivity_delete_old_activitydb_confirmation">Realmente excluir o banco de dados de atividades antigo\? Os dados de atividade que n茫o foram importados ser茫o perdidos.</string>
+    <string name="dbmanagementactivity_old_activity_db_successfully_deleted">Os dados de atividades antigos foram exclu铆dos.</string>
+    <string name="dbmanagementactivity_old_activity_db_deletion_failed">Falha na exclus茫o do banco de dados da atividades antigo.</string>
+    <string name="dbmanagementactivity_overwrite">Sobrepor</string>
+    <string name="Cancel">Cancelar</string>
+    <string name="Delete">Excluir</string>
+    <!--Strings related to Vibration Activity-->
+    <string name="title_activity_vibration">Vibra莽茫o</string>
+    <!--Strings related to Pebble Pairing Activity-->
+    <string name="title_activity_pebble_pairing">Pareamento do Pebble</string>
+    <string name="pebble_pairing_hint">Um di谩logo de pareamento vai aparecer no seu dispositivo Android. Se isso n茫o acontecer, procure na gaveta de notifica莽茫o e aceite o pedido de pareamento. Tamb茅m aceite-o no seu Pebble em seguida.</string>
+    <string name="weather_notification_label">Certifique-se de que esta capa est谩 ativada no aplicativo Notifica莽茫o de Clima para obter informa莽玫es sobre o tempo em seu Pebble.
+\n
+\nN茫o 茅 necess谩rio configurar aqui.
+\n
+\nVoc锚 pode ativar o aplicativo de tempo do sistema de seu Pebble do gerenciamento de aplicativos.
+\n
+\nOs mostradores suportados mostrar茫o o tempo automaticamente.</string>
+    <string name="pref_title_setup_bt_pairing">Ativar pareamento Bluetooth</string>
+    <string name="pref_summary_setup_bt_pairing">Desative isso se tiver problemas de conex茫o</string>
+    <string name="unit_metric">Metro</string>
+    <string name="unit_imperial">Imperial</string>
+    <string name="timeformat_24h">24h</string>
+    <string name="timeformat_am_pm">am/pm</string>
+    <string name="pref_screen_notification_profile_alarm_clock">Alarme</string>
+    <string name="action_donate">Doar</string>
+    <string name="controlcenter_connect">Conectar鈥�</string>
+    <string name="controlcenter_navigation_drawer_open">Abrir gaveta de navega莽茫o</string>
+    <string name="controlcenter_navigation_drawer_close">Fechar gaveta de navega莽茫o</string>
+    <string name="controlcenter_snackbar_need_longpress">Pressione e segure o cart茫o para desconectar</string>
+    <string name="controlcenter_snackbar_disconnecting">Desconectando</string>
+    <string name="controlcenter_snackbar_connecting">Conectando鈥�</string>
+    <string name="controlcenter_snackbar_requested_screenshot">Tirar captura de tela do dispositivo</string>
+    <string name="title_activity_calblacklist">Blacklist do Calend谩rio</string>
+    <string name="fw_upgrade_notice_amazfitbip">Voc锚 est谩 prestes a instalar o firmware %s no seu Amazfit Bip.
+\n
+\nCertifique-se de instalar o firmware .fw, depois o arquivo .res e finalmente o arquivo .gps. Seu rel贸gio ser谩 reiniciado ap贸s a instala莽茫o do arquivo .fw.
+\n
+\nNota: voc锚 n茫o precisa instalar .res e .gps se esses arquivos forem exatamente os mesmos que os instalados anteriormente.
+\n
+\nPROSSIGA POR SUA CONTA E RISCO!</string>
+    <string name="fw_upgrade_notice_amazfitcor">Voc锚 est谩 prestes a instalar o firmware %s no seu Amazfit Cor.
+\n
+\nCertifique-se de instalar o arquivo .fw, e depois disso o arquivo .res. Sua pulseira ser谩 reiniciada ap贸s a instala莽茫o do arquivo .fw.
+\n
+\nNota: voc锚 n茫o precisa instalar .res se for exatamente o mesmo que o instalado anteriormente.
+\n
+\nPROSSIGA AO SEU PR脫PRIO RISCO!</string>
+    <string name="pref_title_charts_swipe">Ativar deslizar para a esquerda/direita no gr谩fico de atividades</string>
+    <string name="pref_call_privacy_mode_number">Ocultar n煤mero, mas exibir nome</string>
+    <string name="pref_blacklist_calendars">Calend谩rios na lista negra</string>
+    <string name="pref_title_weather">Clima</string>
+    <string name="pref_title_weather_location">Localiza莽茫o clim谩tica (para provedor de clina do LineageOS)</string>
+    <string name="pref_title_enable_calendar_sync">Sincronizar calend谩rio</string>
+    <string name="pref_title_pebble_enable_bgjs">Habilitar JS em segundo plano</string>
+    <string name="pref_summary_pebble_enable_bgjs">Quando habilitado, permite que os watchfaces mostrem o clima, informa莽玫es da bateria, etc.</string>
+    <string name="pref_header_auto_export">Exporta莽茫o autom谩tica</string>
+    <string name="pref_title_auto_export_enabled">Exporta莽茫o autom谩tica habilitada</string>
+    <string name="pref_title_auto_export_location">Localiza莽茫o da exporta莽茫o</string>
+    <string name="pref_title_auto_export_interval">Intervalo de exporta莽茫o</string>
+    <string name="pref_summary_auto_export_interval">Exportar a cada %d hora(s)</string>
+    <string name="discovery_connected_devices_hint">Permita que seu dispositivo seja detectado. Os dispositivos j谩 pareados n茫o ser茫o detectados. Para o Android 6 ou superior, 茅 necess谩rio habilitar a localiza莽茫o de GPS. Desative o Privacy Guard para o Gadgetbridge, pois o telefone poder谩 travar ou reiniciar caso esteja ativo. Se nenhum dispositivo for encontrado ap贸s alguns minutos, tente novamente ap贸s reiniciar seu dispositivo m贸vel.</string>
+    <string name="prefs_title_heartrate_measurement_interval">Medi莽茫o di谩ria de frequ锚ncia card铆aca</string>
+    <string name="interval_one_minute">a cada minuto</string>
+    <string name="interval_five_minutes">a cada 5 minutos</string>
+    <string name="interval_ten_minutes">a cada 10 minutos</string>
+    <string name="interval_thirty_minutes">a cada 30 minutos</string>
+    <string name="interval_one_hour">a cada hora</string>
+    <string name="stats_title">脕reas de velocidade</string>
+    <string name="stats_x_axis_label">Minutos totais</string>
+    <string name="stats_y_axis_label">Passos por minuto</string>
+    <string name="notif_export_failed_title">A exporta莽茫o do banco de dados falhou! Por favor, verifique suas configura莽玫es.</string>
+    <string name="weeksleepchart_sleep_a_week">Sono por semana</string>
+    <string name="weeksleepchart_today_sleep_description">Sono hoje, meta: %1$s</string>
+    <string name="calories">Calorias</string>
+    <string name="distance">Dist芒ncia</string>
+    <string name="clock">Rel贸gio</string>
+    <string name="heart_rate">Frequ锚ncia card铆aca</string>
+    <string name="battery">Bateria</string>
+    <string name="mi2_prefs_button_actions">A莽玫es do bot茫o</string>
+    <string name="mi2_prefs_button_actions_summary">Especifica a莽玫es no pressionamento do bot茫o</string>
+    <string name="mi2_prefs_button_press_count">Contagem de acionamento do bot茫o</string>
+    <string name="mi2_prefs_button_press_count_summary">N煤mero de pressionamentos de bot茫o para acionar um Evento 1. A mesma quantidade de pressionamentos subsequentes cria o Evento 2 e assim por diante.</string>
+    <string name="mi2_prefs_button_press_broadcast">Mensagem para enviar</string>
+    <string name="mi2_prefs_button_press_broadcast_summary">Mensagem de transmiss茫o enviada com o evento. O par芒metro `button_id` 茅 adicionado automaticamente a cada mensagem.</string>
+    <string name="pref_summary_enable_calendar_sync">Envia eventos do calend谩rio para linha do tempo</string>
+    <string name="mi2_prefs_button_action">Ativar bot茫o de a莽茫o</string>
+    <string name="mi2_prefs_button_action_vibrate">Ativar vibra莽茫o da pulseira</string>
+    <string name="notification_channel_name">Geral</string>
+    <string name="controlcenter_change_led_color">Alterar cor do LED</string>
+    <string name="controlcenter_change_fm_frequency">Alterar frequ锚ncia FM</string>
+    <string name="controlcenter_calibrate_device">Calibrar Dispositivo</string>
+    <string name="blacklist_all_for_notifications">Bloquear todas as notifica莽玫es</string>
+    <string name="whitelist_all_for_notifications">Desbloquear todas as notifica莽玫es</string>
+    <string name="fw_upgrade_notice_miband3">Voc锚 est谩 prestes a instalar o firmware %s na sua Mi Band 3. 
+\n 
+\nCertifique-se de instalar o arquivo .fw, e depois o arquivo .res. Sua pulseira ser谩 reiniciada ap贸s instalar o arquivo .fw. 
+\n 
+\nNota: Voc锚 n茫o precisa instalar .res se esse arquivo 茅 exatamente o mesmo instalado anteriormente. 
+\n 
+\nPROSSIGA POR SUA CONTA E RISCO!</string>
+    <string name="pref_title_notifications_timeout">Tempo m铆nimo entre notifica莽玫es</string>
+    <string name="pref_title_rtl">Direita para a esquerda</string>
+    <string name="pref_summary_rtl">Ative isto se seu dispositivo n茫o consegue mostrar idiomas com a escrita da direita para a esquerda</string>
+    <string name="debugactivity_really_factoryreset_title">Gostaria realmente de restaurar as configura莽玫es de f谩brica\?</string>
+    <string name="pref_header_pebble_timeline">Linha do tempo do Pebble</string>
+    <string name="mi2_prefs_goal_notification">Notifica莽茫o de meta atingida</string>
+    <string name="mi2_prefs_goal_notification_summary">A pulseira vai vibrar quando meta di谩ria de passos for atingida</string>
+    <string name="mi2_prefs_display_items">Exibir itens</string>
+    <string name="mi2_prefs_display_items_summary">Escolha os itens exibidos na tela da pulseira</string>
+    <string name="mi2_prefs_rotate_wrist_to_switch_info">Girar o pulso para alternar informa莽玫es</string>
+    <string name="mi2_prefs_do_not_disturb">N茫o perturbar</string>
+    <string name="mi2_prefs_do_not_disturb_summary">A pulseira n茫o vai receber notifica莽玫es enquanto ativo</string>
+    <string name="mi2_prefs_inactivity_warnings">Avisos de inatividade</string>
+    <string name="mi2_prefs_inactivity_warnings_summary">A pulseira vai vibrar quando voc锚 estiver inativo por um tempo</string>
+    <string name="mi2_prefs_inactivity_warnings_threshold">Limite de inatividade (em minutos)</string>
+    <string name="mi2_prefs_inactivity_warnings_dnd_summary">Desabilita avisos de inatividade por um intervalo de tempo</string>
+    <string name="mi2_prefs_do_not_disturb_start">Hor谩rio de in铆cio</string>
+    <string name="mi2_prefs_do_not_disturb_end">Hor谩rio de t茅rmino</string>
+    <string name="activity_db_management_merge_old_title">Exclus茫o de banco de dados legado</string>
+    <string name="dbmanagementactivity_error_exporting_shared">Erro ao exportar prefer锚ncias: %1$s</string>
+    <string name="dbmanagementactivity_error_importing_shared">Erro ao importar prefer锚ncias: %1$s</string>
+    <string name="StringUtils_sender">(%1$s)</string>
+    <string name="find_device_you_found_it">Voc锚 encontrou ele!</string>
+    <string name="miband2_prefs_timeformat">Mi2: Formato da hora</string>
+    <string name="mi2_fw_installhandler_fw53_hint">Voc锚 precisa instalar a vers茫o %1$s antes de instalar esse firmware!</string>
+    <string name="mi2_enable_text_notifications">Notifica莽玫es de texto</string>
+    <string name="mi2_enable_text_notifications_summary">Precisa do firmware &gt;= 1.0.1.28 e Mili_pro.ft* instalados.</string>
+    <string name="off">Desligado</string>
+    <string name="mi2_dnd_off">Desligado</string>
+    <string name="mi2_dnd_automatic">Autom谩tico (detec莽茫o de sono)</string>
+    <string name="mi2_dnd_scheduled">Agendado (intervalo de tempo)</string>
+    <string name="discovery_attempting_to_pair">Tentando parear com %1$s</string>
+    <string name="discovery_bonding_failed_immediately">V铆nculo com %1$s falhou imediatamente.</string>
+    <string name="discovery_trying_to_connect_to">Tente se conectar a: %1$s</string>
+    <string name="discovery_enable_bluetooth">Ative o Bluetooth para encontrar dispositivos.</string>
+    <string name="discovery_successfully_bonded">Vinculado a %1$s.</string>
+    <string name="discovery_pair_title">Parear com %1$s\?</string>
+    <string name="discovery_pair_question">Selecione Parear para parear seus dispositivos. Se isso falhar, tente novamente sem pareamento.</string>
+    <string name="discovery_yes_pair">Parear</string>
+    <string name="discovery_dont_pair">N茫o parear</string>
+    <string name="mi2_prefs_button_action_summary">Ativa a莽茫o em n煤mero especificado de pressionamentos do bot茫o</string>
+    <string name="mi2_prefs_button_action_vibrate_summary">Ativa vibra莽茫o da pulseira quando a a莽茫o do bot茫o 茅 disparada</string>
+    <string name="mi2_prefs_button_press_count_max_delay">Atraso m谩ximo entre pressionamentos</string>
+    <string name="mi2_prefs_button_press_count_max_delay_summary">Atraso m谩ximo entre pressionamentos do bot茫o em milissegundos</string>
+    <string name="_pebble_watch_open_on_phone">Abrir no Android</string>
+    <string name="_pebble_watch_mute">Sem som</string>
+    <string name="_pebble_watch_reply">Responder</string>
+    <string name="activity_web_view">Web View Atividades</string>
+    <string name="automatic">Autom谩tico</string>
+    <string name="simplified_chinese">Chin锚s simplificado</string>
+    <string name="traditional_chinese">Chin锚s tradicional</string>
+    <string name="english">Ingl锚s</string>
+    <string name="kind_firmware">Firmware</string>
+    <string name="kind_invalid">Dados inv谩lidos</string>
+    <string name="kind_font">Fonte</string>
+    <string name="kind_gps">Firmware do GPS</string>
+    <string name="kind_gps_almanac">GPS Almanac</string>
+    <string name="kind_gps_cep">Corre莽茫o de erro de GPS</string>
+    <string name="kind_resources">Recursos</string>
+    <string name="kind_watchface">Mostrador</string>
+    <string name="devicetype_unknown">Dispositivo desconhecido</string>
+    <string name="devicetype_test">Dispositivo de teste</string>
+    <string name="devicetype_pebble">Pebble</string>
+    <string name="devicetype_miband">Mi Band</string>
+    <string name="devicetype_miband2">Mi Band 2</string>
+    <string name="devicetype_amazfit_bip">Amazfit Bip</string>
+    <string name="devicetype_amazfit_cor">Amazfit Cor</string>
+    <string name="devicetype_vibratissimo">Vibratissimo</string>
+    <string name="devicetype_liveview">LiveView</string>
+    <string name="devicetype_hplus">HPlus</string>
+    <string name="devicetype_makibes_f68">Makibes F68</string>
+    <string name="devicetype_exrizu_k8">Exrizu K8</string>
+    <string name="devicetype_no1_f1">No.1 F1</string>
+    <string name="devicetype_teclast_h30">Teclast H30</string>
+    <string name="choose_auto_export_location">Escolha a localiza莽茫o da exporta莽茫o</string>
+    <string name="spanish">Espanhol</string>
+    <string name="devicetype_xwatch">XWatch</string>
+    <string name="on">Ligado</string>
+    <string name="controlcenter_start_activity_tracks">Monitoramentos de suas atividades</string>
+    <string name="activity_type_not_measured">N茫o medida</string>
+    <string name="activity_type_activity">Atividade</string>
+    <string name="activity_type_light_sleep">Sono leve</string>
+    <string name="activity_type_deep_sleep">Sono profundo</string>
+    <string name="activity_type_not_worn">Dispositivo n茫o usado</string>
+    <string name="activity_type_running">Corrida</string>
+    <string name="activity_type_walking">Caminhada</string>
+    <string name="activity_type_swimming">Nata莽茫o</string>
+    <string name="activity_type_unknown">Atividade desconhecida</string>
+    <string name="activity_summaries">Atividades de esporte</string>
+    <string name="activity_type_biking">Ciclismo</string>
+    <string name="activity_type_treadmill">Esteira</string>
+    <string name="select_all">Selecionar todos</string>
+    <string name="share">Compartilhar</string>
+    <string name="reset_index">Redefinir data de coleta</string>
+    <string name="menuitem_status">Status</string>
+    <string name="menuitem_activity">Hist贸rico de treinos</string>
+    <string name="menuitem_weather">Clima</string>
+    <string name="menuitem_alarm">Alarme</string>
+    <string name="menuitem_timer">Contagem Regressiva</string>
+    <string name="menuitem_compass">B煤ssola</string>
+    <string name="menuitem_settings">Configura莽玫es</string>
+    <string name="menuitem_alipay">Alipay</string>
+    <string name="pref_title_pebble_gatt_clientonly">Cliente GATT apenas</string>
+    <string name="pref_summary_pebble_gatt_clientonly">Isso 茅 para Pebble 2 apenas e experimental, tente isso se voc锚 tiver problemas de conectividade</string>
+    <string name="devicetype_q8">Q8</string>
+    <string name="devicetype_miband3">Mi Band 3</string>
+    <string name="pref_auto_fetch">Coletar automaticamente os dados de atividade</string>
+    <string name="pref_auto_fetch_summary">A coleta acontece ao bloquear a tela. S贸 funciona se um mecanismo de bloqueio estiver definido!</string>
+    <string name="pref_auto_fetch_limit_fetches">Tempo m铆nimo entre coletas</string>
+    <string name="pref_auto_fetch_limit_fetches_summary">Coleta a cada %d minutos</string>
+    <string name="devicetype_mykronoz_zetime">MyKronoz ZeTime</string>
+    <string name="russian">Russo</string>
+    <string name="prefs_screen_orientation">Orienta莽茫o da tela</string>
+    <string name="horizontal">Horizontal</string>
+    <string name="vertical">Vertical</string>
+    <string name="devicetype_id115">ID115</string>
+    <string name="menuitem_notifications">Notifica莽玫es</string>
+    <string name="menuitem_more">Mais</string>
+    <string name="menuitem_music">M煤sica</string>
+    <string name="watch9_pairing_tap_hint">Quando seu rel贸gio vibrar, agite o dispositivo ou pressione seu bot茫o.</string>
+    <string name="devicetype_watch9">Watch 9</string>
+    <string name="watch9_time_minutes">Minutos:</string>
+    <string name="watch9_time_hours">Horas:</string>
+    <string name="watch9_time_seconds">Segundos:</string>
+    <string name="watch9_calibration_hint">Defina o hor谩rio que seu dispositivo est谩 mostrando para voc锚 agora mesmo.</string>
+    <string name="watch9_calibration_button">Calibrar</string>
+    <string name="title_activity_watch9_pairing">Pareamento de Watch 9</string>
+    <string name="title_activity_watch9_calibration">Calibra莽茫o de Watch 9</string>
+    <string name="mi3_prefs_band_screen_unlock">Desbloqueio de tela da pulseira</string>
+    <string name="mi3_prefs_band_screen_unlock_summary">Deslize para cima para desbloquear a tela da pulseira</string>
+    <string name="german">Alem茫o</string>
+    <string name="italian">Italiano</string>
+    <string name="french">Franc锚s</string>
+    <string name="polish">Polon锚s</string>
+    <string name="korean">Coreano</string>
+    <string name="japanese">Japon锚s</string>
+    <string name="you_slept">Voc锚 dormir de %1$s para %2$s</string>
+    <string name="you_did_not_sleep">Voc锚 n茫o dormiu</string>
+    <string name="norwegian_bokmal">Bokm氓l noruegu锚s</string>
+    <string name="no_limit">Sem limite</string>
+    <string name="seconds_5">5 segundos</string>
+    <string name="seconds_10">10 segundos</string>
+    <string name="seconds_20">20 segundos</string>
+    <string name="seconds_30">30 segundos</string>
+    <string name="minutes_1">1 minuto</string>
+    <string name="minutes_5">5 minutos</string>
+    <string name="minutes_10">10 minutos</string>
+    <string name="minutes_30">30 minutos</string>
+    <string name="mi3_prefs_night_mode">Modo noturno</string>
+    <string name="mi3_prefs_night_mode_summary">Reduz o brilho da tela da pulseira automaticamente 脿 noite</string>
+    <string name="mi3_night_mode_sunset">No por do sol</string>
+    <string name="ok">OK</string>
+    <string name="share_log">Compartilhar log</string>
+    <string name="share_log_warning">Lembre-se de arquivos log do Gadgetbridge que podem conter muitas informa莽玫es pessoais, incluindo, mas n茫o se limitando a, dados de sa煤de, identificadores 煤nicos (como o endere莽o MAC de um dispositivo), prefer锚ncias de m煤sica etc. Considere editar o arquivo e remover essas informa莽玫es antes de envi谩-lo para um relat贸rio p煤blico dos problemas.</string>
+    <string name="warning">Aten莽茫o!</string>
+    <string name="devicetype_roidmi">Roidmi</string>
+    <string name="devicetype_roidmi3">Roidmi 3</string>
+    <string name="preferences_led_color">Cor do LED</string>
+    <string name="preferences_fm_frequency">Frequ锚ncia FM</string>
+    <string name="pref_invalid_frequency_title">Frequ锚ncia inv谩lida</string>
+    <string name="pref_invalid_frequency_message">Insira uma frequ锚ncia entre 87.5 e 108.0</string>
+    <string name="activity_prefs_charts">Configura莽玫es do gr谩fico</string>
+    <string name="activity_prefs_chart_max_heart_rate">Frequ锚ncia card铆aca m谩xima</string>
+    <string name="activity_prefs_chart_min_heart_rate">Frequ锚ncia card铆aca m铆nima</string>
+    <string name="notif_battery_low">%1$s com bateria baixa</string>
+    <string name="notif_battery_low_extended">%1$s com bateria baixa: %2$s</string>
+    <string name="pref_rtl_max_line_length">Tamanho m谩ximo de linha RTL</string>
+    <string name="pref_rtl_max_line_length_summary">Alonga ou encurta as linhas nas quais um texto com escrita da direita para a esquerda (RTL) 茅 separado</string>
+    <string name="pref_title_contextual_arabic">脕rabe contextual</string>
+    <string name="pref_summary_contextual_arabic">Habilite isso para ter suporte a 谩rabe contextual</string>
+    <string name="preferences_rtl_settings">Suporte a RTL (direita para a esquerda)</string>
+    <string name="language_and_region_prefs">Configura莽玫es de idioma e regi茫o</string>
+    <string name="lack_of_sleep">Car锚ncia de sono: %1$s</string>
+    <string name="overslept">Dormiu demais: %1$s</string>
+    <string name="lack_of_step">Car锚ncia de passos: %1$d</string>
+    <string name="overstep">Passos excedentes: %1$d</string>
+    <string name="no_data">Nenhum dado</string>
+    <string name="live_activity_max_heart_rate">Frequ锚ncia card铆aca atual / m谩x: %1$d / %2$d</string>
+    <string name="debugactivity_really_factoryreset">Restaurar as configura莽玫es de f谩brica vai excluir todos os dados do dispositivo conectado (se houver suporte). Dispositivos Xiami/Huami tamb茅m mudam o endere莽o MAC do Bluetooth, ent茫o eles aparecem como novos dispositivos no Gadgetbridge.</string>
+    <string name="devicetype_casiogb6900">Casio GB-6900</string>
+    <string name="title_activity_notification_filter">Filtro de notifica莽茫o</string>
+    <string name="edittext_notification_filter_words_hint">Insira as palavras desejadas, nova linha para cada</string>
+    <string name="toast_notification_filter_saved_successfully">Filtro de notifica莽茫o salvo</string>
+    <string name="filter_mode_none">N茫o filtrar</string>
+    <string name="filter_mode_whitelist">Mostrar quando contiver as palavras</string>
+    <string name="filter_mode_blacklist">Bloquear quando contiver as palavras</string>
+    <string name="filter_submode_at_least_one">Pelo menos uma das palavras</string>
+    <string name="filter_submode_all">Todas as palavras</string>
+    <string name="toast_notification_filter_words_empty_hint">Insira pelo menos uma palavra</string>
+    <string name="filter_mode">Modo do filtro</string>
+    <string name="mode_configuration">Configura莽茫o do modo</string>
+    <string name="save_configuration">Salvar configura莽茫o</string>
+    <string name="appwidget_not_connected">N茫o conectado, alarme n茫o definido.</string>
+    <string name="activity_type_exercise">Exerc铆cio</string>
+    <string name="prefs_disconnect_notification">Notifica莽茫o de desconex茫o</string>
+    <string name="zetime_title_settings">Configura莽玫es de Zetime</string>
+    <string name="zetime_title_heartrate">Configura莽玫es de frequ锚ncia card铆aca</string>
+    <string name="zetime_title_screentime">Dura莽茫o da tela ligada em segundos</string>
+    <string name="zetime_title_heart_rate_alarm">Alarme de frequ锚ncia card铆aca</string>
+    <string name="zetime_title_heart_rate_alarm_summary">O rel贸gio vai avisar voc锚 quando sua frequ锚ncia card铆aca exceder os limites.</string>
+    <string name="zetime_heart_rate_alarm_enable">Ativar o alarme de frequ锚ncia card铆aca</string>
+    <string name="activity_prefs_alarm_max_heart_rate">Frequ锚ncia card铆aca m谩xima</string>
+    <string name="activity_prefs_alarm_min_heart_rate">Frequ锚ncia card铆aca m铆nima</string>
+    <string name="zetime_analog_mode">Modo anal贸gico</string>
+    <string name="zetime_analog_mode_hands">Apenas m茫os</string>
+    <string name="zetime_analog_mode_handsandsteps">M茫os e passos</string>
+    <string name="zetime_activity_tracking">Monitoramento de atividade</string>
+    <string name="zetime_activity_tracking_summary">Ao ativar o monitoramento de atividade, os passos ser茫o contados etc.</string>
+    <string name="zetime_handmove_display">Movimento da m茫o</string>
+    <string name="zetime_handmove_display_summary">Gire seu pulso para ativar ou desativar a tela.</string>
+    <string name="zetime_calories_type">Tipo de calorias</string>
+    <string name="zetime_calories_type_active">S贸 ativar calorias queimadas</string>
+    <string name="zetime_calories_type_all">Calorias queimadas ativas e inativas</string>
+    <string name="zetime_date_format">Formato da data</string>
+    <string name="zetime_date_format_1">AA/MM/DD</string>
+    <string name="zetime_date_format_2">DD/MM/AA</string>
+    <string name="zetime_date_format_3">MM/DD/AA</string>
+    <string name="zetime_prefs_inactivity_repetitions">Repeti莽玫es</string>
+    <string name="zetime_prefs_inactivity_mo">Segunda-feira</string>
+    <string name="zetime_prefs_inactivity_tu">Ter莽a-feira</string>
+    <string name="zetime_prefs_inactivity_we">Quarta-feira</string>
+    <string name="zetime_prefs_inactivity_th">Quinta-feira</string>
+    <string name="zetime_prefs_inactivity_fr">Sexta-feira</string>
+    <string name="zetime_prefs_inactivity_sa">S谩bado</string>
+    <string name="zetime_prefs_inactivity_su">Domingo</string>
+    <string name="zetime_title_alarm_signaling">Definir tipo de sinaliza莽茫o para o alarme</string>
+    <string name="zetime_signaling_none">Sil锚ncio</string>
+    <string name="zetime_signaling_vibrate">Vibra莽茫o cont铆nua</string>
+    <string name="zetime_signaling_beep">Bipe cont铆nuo</string>
+    <string name="zetime_signaling_vibrate_beep">Vibra莽茫o e bipe cont铆nuos</string>
+    <string name="zetime_signaling_vibrate_once">Vibrar uma vez</string>
+    <string name="zetime_signaling_vibrate_twice">Vibrar duas vezes</string>
+    <string name="zetime_signaling_beep_once">Bipar uma vez</string>
+    <string name="zetime_signaling_beep_twice">Bipar duas vezes</string>
+    <string name="zetime_signaling_vibrate_beep_once">Vibrar e bipar uma vez</string>
+    <string name="pref_screen_notification_profile_missed_call">Notifica莽茫o de liga莽茫o perdida</string>
+    <string name="pref_screen_notification_profile_calendar">Notifica莽茫o de calend谩rio</string>
+    <string name="pref_screen_notification_profile_inactivity">Notifica莽茫o de inatividade</string>
+    <string name="pref_screen_notification_profile_low_power">Aviso de bateria baixa</string>
+    <string name="pref_screen_notification_profile_anti_loss">Aviso contra perda</string>
+    <string name="interval_fifteen_minutes">a cada 15 minutos</string>
+    <string name="interval_forty_five_minutes">a cada 45 minutos</string>
+    <string name="activity_prefs_calories_burnt">Meta di谩ria: calorias queimadas</string>
+    <string name="activity_prefs_distance_meters">Meta di谩ria: dist芒ncia em metros</string>
+    <string name="activity_prefs_activetime_minutes">Meta di谩ria: tempo ativo em minutos</string>
+    <string name="devicetype_miscale2">Mi Scale 2</string>
+    <string name="pref_title_support_voip_calls">Ativar chamadas de aplicativo VoIP</string>
+    <string name="title_activity_device_specific_settings">Configura莽玫es espec铆ficas do dispositivo</string>
+    <string name="pref_title_authkey">Chave de autentica莽茫o</string>
+    <string name="pref_summary_authkey">Altere a chave de autentica莽茫o para um chave comum em todos os dispositivos Android a partir dos quais voc锚 gostaria de se conectar. A chave padr茫o anterior para todos dispositivos 茅 0123456789@ABCDE</string>
+    <string name="devicetype_bfh16">BFH-16</string>
+    <string name="fw_upgrade_notice_amazfitcor2">Voc锚 est谩 prestes a instalar o firmware %s no seu Amazfit Cor 2.
+\n
+\nCertifique-se de instalar o arquivo .fw, e depois disso o arquivo .res. Sua pulseira ser谩 reiniciada ap贸s a instala莽茫o do arquivo .fw.
+\n
+\nNota: voc锚 n茫o precisa instalar .res se for exatamente o mesmo que o instalado anteriormente.
+\n
+\nPROSSIGA AO SEU PR脫PRIO RISCO!
+\n
+\nCOMPLETAMENTE N脙O TESTADO, PROVAVELMENTE VOC脢 PRECISA INSTALAR UM FIRMWARE BEATS_W SE O NOME DO SEU DISPOSITIVO FOR \"Amazfit Band 2\"</string>
+    <string name="dutch">Holand锚s</string>
+    <string name="turkish">Turco</string>
+    <string name="ukrainian">Ucraniano</string>
+    <string name="arabic">脕rabe</string>
+    <string name="indonesian">Indon茅sio</string>
+    <string name="thai">Tailand锚s</string>
+    <string name="vietnamese">Vietnamita</string>
+    <string name="portuguese">Portugu锚s</string>
+    <string name="devicetype_amazfit_cor2">Amazfit Cor 2</string>
+    <string name="devicetype_miband4">Mi Band 4</string>
+    <string name="fw_upgrade_notice_miband4">Voc锚 est谩 prestes a instalar o firmware %s na sua Mi Band 4.
+\n
+\nCertifique-se de instalar o arquivo .fw e, ap贸s, o arquivo .res. Sua pulseira vai reiniciar ap贸s instalar o arquivo .fw.
+\n
+\nNota: Voc锚 n茫o tem que instalar .res se ele for exatamente o mesmo arquivo que o instalado anteriormente.
+\n
+\nPROSSIGA POR SUA CONTA E RISCO!</string>
+    <string name="prefs_hr_alarm_activity">Alarme de frequ锚ncia card铆aca durante atividades esportivas</string>
+    <string name="prefs_hr_alarm_low">Limite baixo</string>
+    <string name="prefs_hr_alarm_high">Limite alto</string>
+    <string name="average">M茅dia: %1$s</string>
+    <string name="pref_header_charts">Configura莽玫es dos gr谩ficos</string>
+    <string name="pref_title_charts_average">Mostrar m茅dias nos gr谩ficos</string>
+    <string name="pref_title_charts_range">Intervalo de gr谩ficos</string>
+    <string name="pref_charts_range_on">O intervalo de gr谩ficos est谩 definido para um M锚s</string>
+    <string name="pref_charts_range_off">O intervalo de gr谩ficos est谩 definido para uma Semana</string>
+    <string name="weekstepschart_steps_a_month">Passos por m锚s</string>
+    <string name="weeksleepchart_sleep_a_month">Sono por m锚s</string>
+    <string name="devicetype_mijia_lywsd02">Rel贸gio inteligente Mijia</string>
+    <string name="menuitem_nfc">NFC</string>
+    <string name="pref_summary_expose_hr">Permite que outros aplicativos acessem os dados de FC em tempo real enquanto Gadgetbridge est谩 conectado</string>
+    <string name="pref_title_expose_hr">Acesso por terceiros a FC em tempo real</string>
+    <string name="pref_title_use_custom_font">Usar fonte personalizada</string>
+    <string name="pref_summary_use_custom_font">Habilite isso se seu dispositivo possui uma firmware de fonte personalizado para suporte a emoji</string>
+    <string name="activity_db_management_autoexport_explanation">Localiza莽茫o de autoexporta莽茫o do banco de dados foi definida para:</string>
+    <string name="activity_db_management_autoexport_label">Autoexportar</string>
+    <string name="activity_DB_ExportButton">Exportar dados</string>
+    <string name="activity_DB_import_button">Importar dados</string>
+    <string name="activity_DB_test_export_button">Executar autoexporta莽茫o agora</string>
+    <string name="activity_DB_test_export_message">Exportando banco de dados鈥�</string>
+    <string name="activity_DB_delete_legacy_button">Excluir BD antigo</string>
+    <string name="activity_DB_empty_button">Esvaziar banco de dados</string>
+    <string name="activity_db_management_empty_DB">Esvaziar banco de dados</string>
+    <string name="activity_db_management_exportimport_label">Exportar e importar</string>
+    <string name="activity_db_management_empty_db_warning">Aten莽茫o! Ao pressionar esse bot茫o, voc锚 vai apagar seus banco de dados e iniciar do zero.</string>
+    <string name="appwidget_sleep_alarm_widget_label">Alarme do sono</string>
+    <string name="widget_listing_label">Status e alarmes</string>
+    <string name="widget_set_alarm_after">Definir alarme ap贸s:</string>
+    <string name="widget_5_minutes">5 minutos</string>
+    <string name="widget_10_minutes">10 minutos</string>
+    <string name="widget_20_minutes">20 minutos</string>
+    <string name="widget_1_hour">1 hora</string>
+    <string name="pref_display_add_device_fab">Conectar novo bot茫o de dispositivo</string>
+    <string name="pref_display_add_device_fab_on">Sempre vis铆vel</string>
+    <string name="pref_display_add_device_fab_off">Vis铆vel apenas se nenhum dispositivo estiver adicionado</string>
+    <plurals name="widget_alarm_target_hours">
+        <item quantity="one">%d hora</item>
+        <item quantity="many">%d horas</item>
+        <item quantity="other">%d horas</item>
+    </plurals>
+    <string name="activity_error_no_app_for_gpx">Para visualizar o rastreamento de atividade, instale um aplicativo que consegue manipular arquivos GPX.</string>
+    <string name="preferences_makibes_hr3_settings">Configura莽玫es de Makibes HR3</string>
+    <string name="devicetype_makibes_hr3">Makibes HR3</string>
+    <string name="devicetype_amazfit_bip_lite">Amazfit Bip Lite</string>
+    <string name="prefs_find_phone">Encontrar telefone</string>
+    <string name="prefs_enable_find_phone">Ativar \"Encontrar telefone\"</string>
+    <string name="prefs_find_phone_summary">Use sua pulseira para reproduzir o toque sonoro do seu celular.</string>
+    <string name="prefs_find_phone_duration">Dura莽茫o do toque sonoro em segundos</string>
+    <string name="maximum_duration">Dura莽茫o</string>
+    <string name="discovery_need_to_enter_authkey">Esse dispositivo precisa de sua chave de autentica莽茫o secreta, realize pressionamento longo no dispositivo para inseri-lo. Leia o wiki.</string>
+    <string name="fw_upgrade_notice_amazfitbip_lite">Voc锚 est谩 prestes a instalar o firmware %s no seu Amazfit Bip Lite.
+\n
+\nCertifique-se de instalar o arquivo .fw, e depois o arquivo .res. Seu rel贸gio ser谩 reiniciado ap贸s a instala莽茫o do arquivo .fw.
+\n
+\nNota: voc锚 n茫o precisa instalar .res se esse arquivo for exatamente o mesmo que os instalados anteriormente.
+\n
+\nPROSSIGA POR SUA CONTA E RISCO!</string>
+    <string name="devicetype_amazfit_gtr">Amazfit GTR</string>
+    <string name="fw_upgrade_notice_amazfitgtr">Voc锚 est谩 prestes a instalar o firmware %s no seu Amazfit GTR.
+\n
+\nCertifique-se de instalar o arquivo .fw, depois disso o arquivo .res e finalmente o arquivo .gps. Seu rel贸gio ser谩 reiniciado ap贸s a instala莽茫o do arquivo .fw.
+\n
+\nNota: voc锚 n茫o precisa instalar .res e .gps se esses arquivos forem exatamente os mesmos que os instalados anteriormente.
+\n
+\nPROSSIGA AO SEU PR脫PRIO RISCO!</string>
+    <string name="pref_chart_heartrate_color_red">Vermelho</string>
+    <string name="pref_chart_heartrate_color_orange">Laranja</string>
+    <string name="pref_title_chart_heartrate_color">Cor da frequ锚ncia card铆aca</string>
+    <string name="pref_title_chart_sleep_rolling_24_hour">Intervalo de sono</string>
+    <string name="pref_chart_sleep_rolling_24_on">脷ltimas 24 horas</string>
+    <string name="pref_chart_sleep_rolling_24_off">Meio-dia ao meio-dia</string>
+    <string name="devicetype_amazfit_gts">Amazfit GTS</string>
+    <string name="fw_upgrade_notice_amazfitgts">Voc锚 est谩 prestes a instalar o firmware %s no seu Amazfit GTS.
+\n
+\nCertifique-se de instalar o arquivo .fw, depois disso o arquivo .res e finalmente o arquivo .gps. Seu rel贸gio ser谩 reiniciado ap贸s a instala莽茫o do arquivo .fw.
+\n
+\nNota: voc锚 n茫o precisa instalar .res e .gps se esses arquivos forem exatamente os mesmos que os instalados anteriormente.
+\n
+\nPROSSIGA AO SEU PR脫PRIO RISCO!</string>
+    <string name="mi2_prefs_button_press_broadcast_default_value" translatable="false">nodomain.freeyourgadget.gadgetbridge.ButtonPressed</string>
+    <string name="devicetype_qhybrid">Fossil Q Hybrid</string>
+    <string name="preferences_qhybrid_settings">Configura莽玫es de Q Hybrid</string>
+    <string name="watch_not_connected">Rel贸gio n茫o conectado</string>
+    <string name="qhybrid_vibration_strength">for莽a da vibra莽茫o:</string>
+    <string name="qhybrid_goal_in_steps">Meta em passos</string>
+    <string name="qhybrid_time_shift">deslocamento de tempo</string>
+    <string name="qhybrid_second_timezone_offset_relative_to_utc">segundo deslocamento de fuso hor谩rio relativo a UTC</string>
+    <string name="qhybrid_overwrite_buttons">sobrescrever bot玫es</string>
+    <string name="qhybrid_use_activity_hand_as_notification_counter">usar m茫o de atividade como contador da notifica莽茫o</string>
+    <string name="qhybrid_prompt_million_steps">Defina a contagem de passos para um milh茫o para ativ谩-lo.</string>
+    <string name="qhybrid_buttons_overwrite_success">Bot玫es sobrescritos</string>
+    <string name="qhybrid_buttons_overwrite_error">Erro ao sobrescrever bot玫es</string>
+    <string name="qhybrid_offset_timezone">deslocar fuso hor谩rio por</string>
+    <string name="qhybrid_changes_delay_prompt">a altera莽茫o pode levar alguns segundos鈥�</string>
+    <string name="qhybrid_offset_time_by">deslocar tempo por</string>
+    <string name="pref_disable_new_ble_scanning">Desabilitar nova varredura de BLE</string>
+    <string name="pref_summary_disable_new_ble_scanning">Marque essa op莽茫o se seu dispositivo n茫o pode ser encontrado durante descobertas</string>
+    <string name="devicetype_banglejs">Bangle.js</string>
+    <string name="devicetype_y5">Y5</string>
+    <string name="prefs_button_single_press_action_selection_title">A莽茫o do Evento 1</string>
+    <string name="prefs_button_double_press_action_selection_title">A莽茫o do Evento 2</string>
+    <string name="prefs_button_triple_press_action_selection_title">A莽茫o do Evento 3</string>
+    <string name="prefs_button_variable_actions">Configura莽玫es detalhado de pressionamento de bot茫o</string>
+    <string name="prefs_button_long_press_action_selection_title">A莽茫o do bot茫o de pressionamento longo</string>
+    <string name="alarm_snooze">Adiar</string>
+    <string name="error_no_location_access">Acesso a localiza莽茫o deve ser concedido e habilitado para varredura funcionar adequadamente</string>
+    <string name="devicetype_itag">iTag</string>
+    <string name="pref_title_allow_high_mtu">Permitir MTU alto</string>
+    <string name="pref_summary_allow_high_mtu">Aumenta velocidade de transfer锚ncia, mas pode n茫o funcionar em alguns dispositivos Android.</string>
+    <string name="pref_summary_sync_calendar">Possibilita alertas de calend谩rio, mesmo quando desconectado</string>
+    <string name="pref_title_sync_caldendar">Sincronizar eventos de calend谩rio</string>
+    <string name="hr_widget_heart_rate">Frequ锚ncia card铆aca</string>
+    <string name="hr_widget_steps">Passos</string>
+    <string name="hr_widget_date">Data</string>
+    <string name="hr_widget_active_minutes">Minutos ativo</string>
+    <string name="hr_widget_calories">Calorias</string>
+    <string name="hr_widget_battery">Bateria</string>
+    <string name="hr_widget_weather">Clima</string>
+    <string name="hr_widget_nothing">Nada</string>
+    <string name="find_lost_device_you_found_it">Encontrei!</string>
+    <string name="pref_title_force_white_color_scheme">For莽ar preto no esquema de cores branco</string>
+    <string name="pref_summary_force_white_color_scheme">脷til se seu rel贸gio possui ponteiros escuros</string>
+    <string name="find_my_phone_notification">Localizar meu telefone</string>
+    <string name="notification_channel_high_priority_name">Alta prioridade</string>
+    <string name="pref_title_custom_deviceicon">Mostrar 铆cone de notifica莽茫o espec铆fica do dispositivo</string>
+    <string name="pref_summary_custom_deviceicon">Mostra o 铆cone de notifica莽茫o Android espec铆fico de um dispositivo em vez do 铆cone do Gadgetbridge quando conectado</string>
+    <string name="hr_appname_wellness">Bem-estar</string>
+    <string name="hr_appname_workout">Treino</string>
+    <string name="hr_appname_stopwatch">Cron么metro</string>
+    <string name="hr_appname_commute">Comutar</string>
+    <string name="pref_title_vibration_strength">Intensidade da vibra莽茫o</string>
+    <string name="devicetype_amazfit_bips">Amazfit Bip S</string>
+    <string name="pref_title_relax_firmware_checks">Habilite isso se voc锚 deseja instalar um firmware n茫o destinado para seu dispositivo (a seu pr贸prio risco)</string>
+    <string name="pref_summary_relax_firmware_checks">Verifica莽玫es de firmware relaxadas</string>
+    <string name="pref_header_auto_fetch">Coletar automaticamente</string>
+    <string name="pref_qhybrid_title_widget_draw_circles">Desenhar c铆rculos de widget</string>
+    <string name="pref_qhybrid_save_raw_activity_files">Salvar arquivos n茫o tratados de atividade</string>
+    <string name="menuitem_workout">Treino</string>
+    <string name="menuitem_eventreminder">Lembrete de eventos</string>
+    <string name="menuitem_hr">Frequ锚ncia card铆aca</string>
+    <string name="menuitem_pai">PAI</string>
+    <string name="devicetype_watchxplus">Watch X Plus</string>
+    <string name="power_mode_watch">Somente o rel贸gio</string>
+    <string name="devicetype_watchx">Watch X</string>
+    <string name="power_mode_saving">Economia de energia</string>
+    <string name="power_mode_normal">Normal</string>
+    <string name="power_mode_title">Modo de energia do rel贸gio</string>
+    <string name="prefs_sensors_button_bp_calibration_sum">Pressione aqui para iniciar a calibra莽茫o</string>
+    <string name="prefs_sensors_button_bp_calibration">Calibra莽茫o</string>
+    <string name="pref_sensors_bp_calibration_high">Press茫o arterial SIST脫LICA (alta)</string>
+    <string name="pref_sensors_bp_calibration_low">Press茫o arterial DIAST脫LICA (baixa)</string>
+    <string name="pref_sensors_bp_calibration">Calibra莽茫o da press茫o arterial</string>
+    <string name="pref_title_sensors_altitude">Calibra莽茫o de altitude</string>
+    <string name="pref_header_sensors_calibration">Calibra莽茫o do sensor</string>
+    <string name="pref_title_device_spec_settings_show_raw_graph">Mostrar dados RAW no gr谩fico de atividade</string>
+    <string name="pref_summary_device_spec_settings_title_force_time">For莽a a sincroniza莽茫o autom谩tica do tempo na reconex茫o. Os ponteiros anal贸gicos podem mostrar tempo incorreto!</string>
+    <string name="pref_title_device_spec_settings_force_time">For莽ar sincronizar o tempo</string>
+    <string name="pref_header_device_spec_settings">Configura莽玫es do dispositivo</string>
+    <string name="pref_summary_notifications_and_calls_title_shake_reject">Duplica a a莽茫o do bot茫o do rel贸gio</string>
+    <string name="prefs_notifications_and_calls_shake_reject">Sacudir pulso ignorar/rejeitar chamada</string>
+    <string name="pref_summary_notifications_and_calls_title_reject">Desligado - ignorar, Ligado - rejeitar</string>
+    <string name="prefs_notifications_and_calls_reject">Bot茫o ignorar/rejeitar chamada</string>
+    <string name="pref_header_notifications_and_calls_callhandling">Tratamento de chamadas</string>
+    <string name="pref_title_notifications_and_calls_repeat_on_misscall">Repetir por X minutos</string>
+    <string name="pref_summary_notifications_and_calls_enable_misscall">Repete a cada minuto</string>
+    <string name="pref_notifications_and_calls_enable_misscall">Notificar chamada perdida</string>
+    <string name="prefs_notifications_and_calls_continious_ring">Notifica莽茫o durante o toque do telefone</string>
+    <string name="pref_title_notifications_and_calls_repeat_on_call">Repetir notifica莽茫o de chamadas</string>
+    <string name="pref_header_notifications_and_calls">Notifica莽玫es e chamadas</string>
+    <string name="title_activity_LenovoWatch_calibration">Calibra莽茫o de Watch X Plus</string>
+    <string name="hungarian">H煤ngaro</string>
+    <string name="romanian">Romeno</string>
+    <string name="pref_theme_system">Sistemas</string>
+    <string name="czesh">Tcheco</string>
+    <string name="swedish">Sueco</string>
+    <string name="hebrew">Hebreu</string>
+    <string name="greek">Grego</string>
+    <string name="menuitem_unknown">Desconhecido</string>
+    <string name="bip_prefs_shotcuts_summary">Escolha os atalhos na tela da pulseira</string>
+    <string name="bip_prefs_shortcuts">Atalhos</string>
+    <string name="hr_widget_last_notification">脷ltima notifica莽茫o</string>
+    <string name="controlcenter_set_alias">Definir apelido</string>
+    <string name="fw_upgrade_notice_amazfit_trex">Voc锚 est谩 prestes a instalar o firmware %s no seu Amazit T-Rex. 
+\n 
+\nCertifique-se de instalar o arquivo .fw, depois o arquivo .res e finalmente o arquivo .gps. Seu rel贸gio ser谩 reiniciado ap贸s a instala莽茫o do arquivo .fw. 
+\n 
+\nNota: voc锚 n茫o precisa instalar .res e .gps se esse arquivo for exatamente o mesmo que os instalados anteriormente. 
+\n 
+\nPROSSIGA POR SUA CONTA E RISCO!</string>
+    <string name="devicetype_amazfit_trex">Amazfit T-Rex</string>
+    <string name="about_additional_contributions">Muito obrigado a todos os contribuidores n茫o listados aqui por contribui莽茫o com c贸digo, tradu莽茫o, suporte, ideias, motiva莽茫o, relat贸rios de erros, dinheiro鈥� 鉁�</string>
+    <string name="about_additional_device_support">Suporte a dispositivos adicionais</string>
+    <string name="about_contributors">Contribuidores</string>
+    <string name="about_core_team_title">Equipe Core (na ordem da primeira contribui莽茫o de c贸digo)</string>
+    <string name="about_description_generic">Um substituto livre, protegido por copyleft, sem acesso a nuvem para os aplicativos Android de c贸digo fechado dos fornecedores de gadgets.</string>
+    <string name="about_activity_title_generic">Sobre o Gadgetbridge</string>
+    <string name="about_title">Sobre</string>
+    <string name="pref_title_weather_summary">Usado para o provedor do clima do LineageOS, outras vers玫es do Android precisam usar um aplicativo como o \"Notifica莽茫o de clima\". Encontrar谩 mais informa莽玫es no wiki do Gadgetbridge.</string>
+    <string name="menuitem_worldclock">Rel贸gio mundial</string>
+    <string name="error_version_check_extreme_caution">CUIDADO: Erro ao verificar informa莽茫o da vers茫o! Voc锚 n茫o deve continuar! Encontrei o nome de vers茫o \"%s\"</string>
+    <string name="permission_granting_mandatory">Todas estas permiss玫es s茫o necess谩rias e instabilidade pode ocorrer se n茫o concedidas</string>
+    <string name="error_background_service_reason_truncated">A inicializa莽茫o do servi莽o de segundo plano porque鈥�</string>
+    <string name="fw_upgrade_notice_miband5">Voc锚 est谩 prestes a instalar o firmware %s na sua Mi Band 5.
+\n
+\nCertifique-se de instalar o arquivo .fw e, ap贸s, o arquivo .res. Sua pulseira vai reiniciar ap贸s instalar o arquivo .fw.
+\n
+\nNota: Voc锚 n茫o tem que instalar .res se ele for exatamente o mesmo arquivo que o instalado anteriormente.
+\n
+\nPROSSIGA POR SUA CONTA E RISCO!</string>
+    <string name="error_background_service_reason">A inicializa莽茫o do servi莽o de segundo plano falhou por causa de uma exce莽茫o. Erro:</string>
+    <string name="error_background_service">Falha ao iniciar o servi莽o de segundo plano</string>
+    <string name="menuitem_stress">Estresse</string>
+    <string name="menuitem_cycles">Acompanhamento de ciclo</string>
+    <string name="menuitem_breathing">Respira莽茫o</string>
+    <string name="devicetype_miband5">Mi Band 5</string>
+    <string name="devicetype_tlw64">TLW64</string>
+    <string name="error_location_enabled_mandatory">A localiza莽茫o deve ser ligada para procurar dispositivos</string>
+    <string name="ignore_bonded_devices_description">Habilitar esta op莽茫o ignorar谩 dispositivos que j谩 foram associados/emparelhados durante a varredura</string>
+    <string name="ignore_bonded_devices">Ignorar dispositivos associados</string>
+    <string name="error_retrieving_devices_database">Erro ao obter dispositivos do banco de dados</string>
+    <string name="error_setting_alias">Erro ao definir um alias:</string>
+    <string name="error_exporting_device_preferences">Erro ao exportar as prefer锚ncias espec铆ficas do dispositivo</string>
+    <string name="pref_check_permission_status_summary">Verifique e pe莽a permiss玫es ausentes mesmo quando elas podem n茫o ser necess谩rias no exato momento. Desabilite isso somente se seus dispositivos realmente n茫o suportarem nenhum desses recursos. N茫o conceder uma permiss茫o pode causar problemas!</string>
+    <string name="pref_check_permission_status">Verificar status de permiss茫o</string>
+    <string name="device_requires_key">CHAVE NECESS脕RIA</string>
+    <string name="device_is_currently_bonded">J脕 ASSOCIADO</string>
+    <string name="companiondevice_pairing_details">Habilita o novo suporte 脿 API CompanionDevice (tem efeito apenas em Android 8 ou mais novo) que vai aumentar a confiabilidade se o servi莽o precisar ser reiniciado em segundo plano, requer novo emparelhamento usando o Gadgetbridge para ter um efeito</string>
+    <string name="companiondevice_pairing">Pareamento de CompanionDevice</string>
+    <string name="require_location_provider">Localiza莽茫o deve estar habilitada</string>
+    <string name="devicetype_pinetime_jf">PineTime (JF Firmware)</string>
+    <string name="averageStrokeDistance">Dist芒ncia m茅dia das bra莽adas</string>
+    <string name="averageStride">M茅dia da caminhada</string>
+    <string name="averageKMPaceSeconds">Ritmo</string>
+    <string name="averageHR">Frequ锚ncia card铆aca</string>
+    <string name="totalStride">Total da caminhada</string>
+    <string name="maxPace">Ritmo mais r谩pido</string>
+    <string name="minPace">Ritmo mais lento</string>
+    <string name="maxSpeed">M谩xima</string>
+    <string name="caloriesBurnt">Calorias</string>
+    <string name="activeSeconds">Ativos</string>
+    <string name="steps">Passos</string>
+    <string name="minAltitude">M铆nima</string>
+    <string name="maxAltitude">M谩xima</string>
+    <string name="descentMeters">Descida</string>
+    <string name="ascentMeters">Subida</string>
+    <string name="distanceMeters">Dist芒ncia</string>
+    <string name="activity_summary_detail">Detalhes de atividade de esporte</string>
+    <string name="medley">Medley</string>
+    <string name="backstroke">Costas</string>
+    <string name="freestyle">Livre</string>
+    <string name="breaststroke">Nado de peito</string>
+    <string name="activity_summaries_all_activities">Todas atividades</string>
+    <string name="activity_summaries_statistics">Estat铆sticas</string>
+    <string name="activity_filter_apply_filter">Aplicar filtro</string>
+    <string name="activity_filter_name_contains">R贸tulo</string>
+    <string name="activity_filter_filter_title">Filtro</string>
+    <string name="activity_filter_reset_filter">Redefinir filtro</string>
+    <string name="activity_filter_date_to">At茅</string>
+    <string name="activity_filter_date_from">De</string>
+    <string name="pref_header_statistics">Estat铆sticas de atividades esportivas</string>
+    <string name="pref_header_filter">Filtro de atividades esportivas</string>
+    <string name="prefs_events_forwarding_action_title">Executar a莽茫o</string>
+    <string name="prefs_events_forwarding_broadcast_title">Transmitir mensagem</string>
+    <string name="prefs_events_forwarding_startnonwear">Quando n茫o usado</string>
+    <string name="prefs_events_forwarding_wokeup">Ao acordar</string>
+    <string name="prefs_events_forwarding_fellsleep">Ao dormir</string>
+    <string name="prefs_events_forwarding_title">A莽玫es do dispositivo</string>
+    <string name="prefs_events_forwarding_summary">Usar eventos do dispositivo para disparar a莽玫es e transmiss玫es do Android</string>
+    <string name="activity_detail_show_gps_label">Mostrar Rastro GPS</string>
+    <string name="activity_detail_duration_label">Dura莽茫o</string>
+    <string name="activity_detail_end_label">Fim</string>
+    <string name="activity_detail_start_label">In铆cio</string>
+    <string name="Steps">Passos</string>
+    <string name="Activity">Atividade</string>
+    <string name="Speed">Velocidade</string>
+    <string name="Elevation">Eleva莽茫o</string>
+    <string name="Distance">Dist芒ncia</string>
+    <string name="Swimming">Nata莽茫o</string>
+    <string name="Strokes">Bra莽adas</string>
+    <string name="km">km</string>
+    <string name="bpm">bpm</string>
+    <string name="minutes_km">min/km</string>
+    <string name="seconds_m">seg/m</string>
+    <string name="seconds_km">seg/km</string>
+    <string name="calories_unit">kcal</string>
+    <string name="laps_unit">voltas</string>
+    <string name="swolf_index">swolf 铆ndice</string>
+    <string name="seconds">seg</string>
+    <string name="strokes_unit">br莽</string>
+    <string name="strokes_second">br莽/s</string>
+    <string name="km_h">km/h</string>
+    <string name="meters_second">m/s</string>
+    <string name="steps_unit">passos</string>
+    <string name="cm">cm</string>
+    <string name="meters">m</string>
+    <string name="flatSeconds">Plano</string>
+    <string name="ascentSeconds">Acendente</string>
+    <string name="laps">Voltas</string>
+    <string name="averageLapPace">Ritmo m茅dio das voltas</string>
+    <string name="averageStrokesPerSecond">M茅dia das bra莽adas</string>
+    <string name="devicetype_sg2">Lemfo SG2</string>
+    <string name="activity_summary_yesterday">Ontem</string>
+    <string name="activity_summary_today">Hoje</string>
+    <string name="activity_summary_edit_name_title">Editar r贸tulo</string>
+    <string name="activity_type_yoga">Ioga</string>
+    <string name="activity_type_jump_roping">Corda de pular</string>
+    <string name="activity_type_elliptical_trainer">Treinador El铆ptico</string>
+    <string name="activity_type_indoor_cycling">Ciclismo indoor</string>
+    <string name="activity_type_swimming_openwater">Nata莽茫o (脕guas abertas)</string>
+    <string name="activity_summaries_all_devices">Todos dispositivos</string>
+    <string name="sports_activity_quick_filter_select">Per铆odo de tempo</string>
+    <string name="sports_activity_quick_filter_30days">30 dias</string>
+    <string name="sports_activity_quick_filter_7days">7 dias</string>
+    <string name="sports_activity_quick_filter_last_month">M锚s anterior</string>
+    <string name="sports_activity_quick_filter_this_month">Este m锚s</string>
+    <string name="sports_activity_quick_filter_last_week">Semana passada</string>
+    <string name="sports_activity_quick_filter_this_week">Esta semana</string>
+    <string name="no">N茫o</string>
+    <string name="yes">Sim</string>
+    <string name="activity_filter_individual_items">Itens selecionados individualmente</string>
+    <string name="addto_filter">Adicionar ao filtro</string>
+    <string name="swim_style">estilo de nata莽茫o</string>
+    <string name="averageSpeed">Velocidade m茅dia</string>
+    <string name="baseAltitude">Eleva莽茫o base</string>
+    <string name="swimStyle">nata莽茫oEstilo</string>
+    <string name="swolfIndex">swolf脥ndice</string>
+    <string name="activity_type_badminton">Badminton</string>
+    <string name="activity_type_pingpong">Ping pong</string>
+    <string name="activity_type_basketball">Basquete</string>
+    <string name="activity_type_cricket">Cr铆quete</string>
+    <string name="activity_type_rowing_machine">M谩quina de remo</string>
+    <string name="activity_type_soccer">Futebol</string>
+    <string name="activity_filter_to_placeholder">hoje</string>
+    <string name="activity_filter_from_placeholder">passado distante</string>
+    <string name="about_links">Links</string>
+    <string name="sonyswr12_settings_title">Configura莽玫es de Sony SWR12</string>
+    <string name="sonyswr12_settings_low_vibration">Baixa vibra莽茫o habilitada</string>
+    <string name="sonyswr12_settings_stamina">O modo de economia de energia est谩 ativado</string>
+    <string name="sonyswr12_settings_alarm_interval">Intervalo de alarme inteligente em minutos</string>
+    <string name="sonyswr12_settings_low_vibration_summary">Ativar baixa intensidade de vibra莽茫o na banda</string>
+    <string name="sonyswr12_settings_stamina_summary">O modo de economia de energia desativa a medi莽茫o autom谩tica peri贸dica da frequ锚ncia card铆aca, aumentando assim o tempo de trabalho</string>
+    <string name="sonyswr12_settings_alarm_interval_summary">O intervalo de alarme inteligente 茅 o intervalo antes do alarme instalado. Neste intervalo, o dispositivo est谩 tentando detectar a fase mais leve do sono para acordar o usu谩rio</string>
+    <string name="qhybrid_calibration_align_hint">Use os bot玫es abaixo para alinhar os ponteiros do rel贸gio para as 12:00.</string>
+    <string name="devicetype_nut_mini">Nut mini</string>
+    <string name="gps_track">Rastro GPS</string>
+    <string name="descentSeconds">Descendente</string>
+    <string name="about_version">Vers茫o %s</string>
+    <string name="pref_title_lower_button_function_double">Bot茫o inferior duplo</string>
+    <string name="pref_title_middle_button_function_double">Bot茫o central duplo</string>
+    <string name="pref_title_upper_button_function_double">Bot茫o superior duplo</string>
+    <string name="pref_title_lower_button_function_long">Bot茫o inferior longo</string>
+    <string name="pref_title_middle_button_function_long">Bot茫o central longo</string>
+    <string name="pref_title_upper_button_function_long">Bot茫o superior longo</string>
+    <string name="pref_title_lower_button_function_short">Bot茫o inferior curto</string>
+    <string name="pref_title_middle_button_function_short">Bot茫o central curto</string>
+    <string name="pref_title_upper_button_function_short">Bot茫o superior curto</string>
+    <string name="menuitem_pomodoro">Rastreador Pomodoro</string>
+    <string name="menuitem_sleep">Sono</string>
+    <string name="menuitem_goal">Meta da atividade</string>
+    <string name="menuitem_stopwatch">Cron么metro</string>
+    <string name="menuitem_alexa">Alexa</string>
+    <string name="menuitem_takephoto">Tirar uma foto</string>
+    <string name="menuitem_mutephone">Silenciar telefone</string>
+    <string name="menuitem_findphone">Encontrar telefone</string>
+    <string name="menuitem_spo2">SpO2</string>
+    <string name="devicetype_lefun">Lefun</string>
+    <string name="devicetype_casiogbx100">Casio GBX-100</string>
+    <string name="devicetype_amazfit_gts2">Amazfit GTS 2</string>
+    <string name="devicetype_amazfit_gtr2">Amazfit GTR 2</string>
+    <string name="devicetype_amazfit_bipu">Amazfit Bip U</string>
+    <string name="devicetype_amazfit_bips_lite">Amazfit Bip S Lite</string>
+    <string name="devicetype_amazfit_band5">Amazfit Band 5</string>
+    <string name="activity_error_no_app_for_png">Para compartilhar esta captura de tela, instale um aplicativo que pode manipular arquivos de imagens.</string>
+    <string name="charts_legend_heartrate_average">Frequ锚ncia card铆aca m茅dia</string>
+    <string name="activity_prefs_chart_min_session_length">Tamanho m铆nimo de atividade (minutos)</string>
+    <string name="activity_prefs_chart_max_idle_phase_length">Tamanho da pausa para separar atividades (minutos)</string>
+    <string name="activity_prefs_step_length_cm">Tamanho do passo em cm</string>
+    <string name="activity_prefs_chart_min_steps_per_minute">Passos m铆nimos por minuto para detectar atividade</string>
+    <string name="activity_prefs_chart_min_steps_per_minute_for_run">Passos m铆nimos por minuto para detectar corrida</string>
+    <string name="lefun_prefs_interface_language_title">Idioma da interface</string>
+    <string name="lefun_prefs_antilost_summary">A pulseira vai vibrar se seu telefone se desconectar da pulseira</string>
+    <string name="lefun_prefs_antilost_title">Antiperda</string>
+    <string name="lefun_prefs_hydration_reminder_interval_title">Intervalo de lembrete de hidrata莽茫o (em minutos)</string>
+    <string name="lefun_prefs_hydration_reminder_summary">A pulseira vai vibrar para lhe lembrar de beber 谩gua</string>
+    <string name="lefun_prefs_hydration_reminder_title">Lembrete de hidrata莽茫o</string>
+    <string name="charts_min_max_heartrate_popup">Frequ锚ncia card铆aca mais baixa: %1$d
+\nFrequ锚ncia card铆aca mais alta: %2$d
+\nIntensidade do movimento: %3$s</string>
+    <string name="movement_intensity">Intensidade do movimento</string>
+    <string name="firmware_update_progress">O upload est谩 em progresso
+\n%1d%% a %.2fkbps (m茅dia de %.2fkbps)
+\nParte %1d de %1d</string>
+    <string name="devicestatus_upload_failed">O upload falhou</string>
+    <string name="devicestatus_upload_aborted">O upload foi abortado!</string>
+    <string name="devicestatus_upload_validating">O upload est谩 sendo validado</string>
+    <string name="devicestatus_upload_completed">O upload foi conclu铆do</string>
+    <string name="devicestatus_disconnected">O dispositivo foi desconectado!</string>
+    <string name="devicestatus_disconnecting">O dispositivo est谩 desconectando!</string>
+    <string name="devicestatus_upload_started">O upload foi iniciado</string>
+    <string name="devicestatus_upload_starting">O upload est谩 iniciando</string>
+    <string name="devicestatus_connected">O dispositivo est谩 conectado</string>
+    <string name="devicestatus_connecting">O dispositivo est谩 conectando</string>
+    <string name="activity_list_summary_activities">Atividades</string>
+    <string name="activity_list_summary_intensity">Movimento
+\nIntensidade</string>
+    <string name="activity_list_summary_active_time">Tempo ativo</string>
+    <string name="activity_list_summary_distance">Dist芒ncia</string>
+    <string name="activity_list_summary_active_steps">Passos ativos</string>
+    <string name="charts_activity_list">Lista de atividades</string>
+    <string name="prefs_charts_tabs_summary">Abas de gr谩ficos vis铆veis</string>
+    <string name="prefs_charts_tabs">Abas de gr谩ficos</string>
+    <string name="chart_get_active_and_synchronize">Fa莽a alguma atividade e sincronize o dispositivo.</string>
+    <string name="chart_no_active_data">Nenhuma atividade detectada.</string>
+    <string name="find_lost_device_message">Buscar por %1$s\?</string>
+    <string name="menuitem_dnd">DND</string>
+    <string name="pref_summary_connected_advertisement">Torna o dispositivo descobr铆vel via Bluetooth mesmo quando conectado</string>
+    <string name="pref_title_connected_advertisement">Vis铆vel enquanto conectado</string>
+    <string name="prefs_autoremove_message">Remover automaticamente notifica莽玫es SMS</string>
+    <string name="prefs_fake_ring_duration">Toque cont铆nuo falso</string>
+    <string name="prefs_operating_sounds">Sons de opera莽玫es</string>
+    <string name="prefs_key_vibration">Vibra莽茫o de tecla</string>
+    <string name="prefs_autolight">Luz autom谩tica</string>
+    <string name="discovery_entered_invalid_authkey">A chave de autentica莽茫o secreta 茅 inv谩lida! Fa莽a um pressionamento longo no dispositivo para editar.</string>
+    <string name="devicetype_amazfit_vergel">Amazfit Verge Lite</string>
+    <string name="fw_upgrade_notice_amazfitvergel">Voc锚 est谩 prestes a instalar o firmware %s no seu Amazfit Verge Lite.
+\n
+\nCertifique-se de instalar o arquivo .fw, depois disso o arquivo .res e finalmente o arquivo .gps. Sua pulseira ser谩 reiniciada ap贸s a instala莽茫o do arquivo .fw.
+\n
+\nNota: voc锚 n茫o precisa instalar .res e .gps se esses forem exatamente os mesmos que os instalados anteriormente.
+\n
+\nPROSSIGA AO SEU PR脫PRIO RISCO!</string>
+    <string name="activity_DB_ShowContentButton">Mostrar Conte煤do do diret贸rio de exporta莽茫o/importa莽茫o</string>
+    <string name="activity_data_management_directory_content_title">Conte煤do do diret贸rio de exporta莽茫o/importa莽茫o</string>
+    <string name="dbmanagementactivity_export_confirmation">Realmente exportar dados\? Dados de atividades exportados anteriormente (se houver) e prefer锚ncias ser茫o substitu铆dos.</string>
+    <string name="dbmanagementactivity_export_data_title">Exportar dados\?</string>
+    <string name="dbmanagementactivity_error_cleaning_export_directory">Erro ao apagar arquivos de diret贸rio externo %1$s</string>
+    <string name="battery_detail_activity_title">Informa莽玫es da bateria</string>
+    <string name="watchface_setting_title_display_refresh_timeout">Tempo limite de atualiza莽茫o da tela</string>
+    <string name="watchface_dialog_widget_type">Tipo:</string>
+    <string name="appmanager_app_edit">Editar</string>
+    <string name="devicetype_nothingear1">Nothing Ear (1)</string>
+    <string name="prefs_autoheartrate_measurement">Medi莽玫es autom谩ticas de frequ锚ncia card铆aca</string>
+    <string name="prefs_autoheartrate">Frequ锚ncia card铆aca autom谩tica</string>
+    <string name="enable_notifications_summary">Notifica莽玫es para chamadas, mensagens e mais</string>
+    <string name="enable_vibrations_summary">Vibra莽玫es para chamadas, mensagens, notifica莽玫es e mais</string>
+    <string name="prefs_notifications_enable">Ativar notifica莽玫es</string>
+    <string name="prefs_vibration_enable">Ativar vibra莽玫es</string>
+    <string name="prefs_sleep_time_summary">Especifica hor谩rios quando o sono 茅 registrado</string>
+    <string name="prefs_sleep_time_label">Definir horas de sono</string>
+    <string name="prefs_sleep_time">Hor谩rios de sono</string>
+    <string name="watchface_setting_power_saving_hands">Desativar movimento das m茫os enquanto fora do pulso</string>
+    <string name="watchface_setting_power_saving_display">Desativar atualiza莽玫es da tela enquanto fora do pulso</string>
+    <string name="watchface_upload_failed">O envio do mostrador falhou. Tente novamente.</string>
+    <string name="watchface_cache_confirm_overwrite">J谩 existe um mostrador com este nome no cache. Voc锚 deseja sobrescrev锚-lo\?</string>
+    <string name="watchface_setting_wrist_flick_move_relative">Mover as m茫os em rela莽茫o ao tempo:</string>
+    <string name="watchface_setting_desc_wrist_flick">(para desativar completamente, ative o movimento relativo e defina todos os valores para 0)</string>
+    <string name="watchface_setting_title_wrist_flick">Movimento de pulso</string>
+    <string name="watchface_setting_display_refresh_partial">Atualiza莽茫o parcial (em minutos):</string>
+    <string name="watchface_setting_display_refresh_full">Atualiza莽茫o completa (em minutos):</string>
+    <string name="watchface_dialog_title_settings">Configura莽玫es do mostrador</string>
+    <string name="watchface_dialog_widget_color_black">Preta</string>
+    <string name="watchface_dialog_widget_color_white">Branca</string>
+    <string name="watchface_dialog_widget_color">Cor:</string>
+    <string name="watchface_dialog_widget_preset_left">Esquerda</string>
+    <string name="watchface_dialog_widget_preset_bottom">Baixo</string>
+    <string name="watchface_dialog_widget_preset_top">Topo</string>
+    <string name="watchface_dialog_widget_x_coordinate">Coordenada X (m谩x. 240):</string>
+    <string name="button_watchface_save_apply">Salvar e aplicar</string>
+    <string name="button_watchface_preview">Visualiza莽茫o no rel贸gio</string>
+    <string name="watchface_widget_type_heart_rate">Frequ锚ncia card铆aca</string>
+    <string name="watchface_dialog_title_add_widget">Adicionar widget</string>
+    <string name="watchface_toast_settings_incomplete">Configura莽玫es incompletas, widget n茫o adicionado</string>
+    <string name="qhybrid_title_watchface">Configura莽茫o do mostrador</string>
+    <string name="watchface_install_info">Voc锚 est谩 prestes a instalar o seguinte mostrador:
+\n
+\n%1$s
+\nVers茫o %2$s por %3$s
+\n</string>
+    <string name="button_watchface_add_widget">Adicionar widget</string>
+    <string name="button_watchface_edit_name">Editar nome</string>
+    <string name="devicetype_amazfit_trex_pro">Amazfit T-Rex Pro</string>
+    <string name="qhybrid_pref_summary_external_intents">Permitir que outros aplicativos Android enviem/sobrescrevam arquivos</string>
+    <string name="qhybrid_pref_title_external_intents">Permitir inten莽玫es externas perigosas</string>
+    <string name="pref_summary_developer_settings">Configura莽玫es e funcionalidade usada por desenvolvedores</string>
+    <string name="pref_title_developer_settings">Configura莽玫es de desenvolvedor</string>
+    <string name="qhybrid_pref_summary_actions">A莽玫es para o aplicativo Commute</string>
+    <string name="qhybrid_pref_title_actions">A莽玫es</string>
+    <string name="fossil_hr_commute_actions_explanation">As a莽玫es configuradas aqui aparecer茫o no aplicativo Commute do seu rel贸gio. Leia a wiki para obter informa莽玫es sobre como lidar com as inten莽玫es produzidas por essas a莽玫es.</string>
+    <string name="pref_summary_canned_messages_set">Enviar mensagens configuradas abaixo para seu dispositivo</string>
+    <string name="pref_summary_physical_buttons">Configure a funcionalidade dos bot玫es f铆sicos no rel贸gio</string>
+    <string name="pref_title_physical_buttons">Bot玫es f铆sicos</string>
+    <string name="fossil_hr_warning_firmware_too_new">Algumas fun莽玫es est茫o desativadas porque o firmware do rel贸gio 茅 novo demais</string>
+    <string name="qhybrid_title_calibration">Calibra莽茫o</string>
+    <string name="qhybrid_title_file_management">Gerenciamento de arquivos</string>
+    <string name="fossil_hr_auth_failed">Autentica莽茫o falhou, funcionalidade limitada</string>
+    <string name="fossil_hr_unavailable_unauthed">N茫o dispon铆vel no modo n茫o autenticado</string>
+    <string name="fossil_hr_synced_activity_data">Dados de atividade sincronizados</string>
+    <string name="fossil_hr_commute_processing">Processando solicita莽茫o鈥�</string>
+    <string name="gpx_receiver_overwrite_some_files">Um ou mais arquivos recebidos j谩 existem. Sobrescrever\?</string>
+    <string name="widget_settings_select_device_title">Selecionar dispositivo</string>
+    <string name="menuitem_barometer">Bar么metro</string>
+    <string name="menuitem_temperature">Temperatura</string>
+    <string name="menuitem_widgets">Widgets</string>
+    <string name="devicetype_amazfit_neo">Amazfit Neo</string>
+    <string name="devicetype_miband6">Mi Band 6</string>
+    <string name="kind_agps_bundle">Pacote AGPS</string>
+    <string name="activity_summary_detail_editing_gpx_track">Editando trilha GPX vinculada</string>
+    <string name="activity_summary_detail_clear_gpx_track">Apagar trilha GPX</string>
+    <string name="activity_summary_detail_select_gpx_track">Selecionar trilha GPX</string>
+    <string name="activity_type_strength_training">Treinamento de for莽a</string>
+    <string name="dbmanagementactivity_export_finished">Exclus茫o finalizada</string>
+    <string name="activity_db_management_clean_export_directory_text">Os arquivos exportados no diret贸rio de exporta莽茫o/importa莽茫o podem ser acessados por qualquer aplicativo em seu dispositivo. Voc锚 pode querer remover esses arquivos ap贸s a sincroniza莽茫o ou backup. Certifique-se de ter um backup antes de exclu铆-los. Os arquivos GPX, subdiret贸rios e arquivos de banco de dados exportados automaticamente (se houver) n茫o ser茫o exclu铆dos. O caminho para o diret贸rio de exporta莽茫o/importa莽茫o 茅:</string>
+    <string name="activity_DB_clean_export_directory_warning_message">Deseja realmente excluir arquivos do diret贸rio de exporta莽茫o/importa莽茫o\?</string>
+    <string name="activity_DB_clean_export_directory_warning_title">Excluir arquivos no diret贸rio de exporta莽茫o/importa莽茫o\?</string>
+    <string name="activity_db_management_clean_export_directory_label">Excluir arquivos no diret贸rio de exporta莽茫o/importa莽茫o</string>
+    <string name="set">Definir</string>
+    <string name="heart_rate_result">Resultados da medi莽茫o</string>
+    <string name="getting_heart_rate">Medi莽茫o</string>
+    <string name="blood_pressure">Press茫o arterial</string>
+    <string name="prefs_sounds_summary">Configure quando o dispositivo vai bipar</string>
+    <string name="prefs_sounds">Sons</string>
+    <string name="show_ongoing_activity">Mostrar pop-up de atividade em andamento</string>
+    <string name="dialog_hide">Ocultar</string>
+    <string name="pref_summary_huami_force_new_protocol">Ative se seus dispositivo n茫o se conecta mais ap贸s uma atualiza莽茫o de firmware</string>
+    <string name="pref_title_huami_force_new_protocol">Novo protocolo de autentica莽茫o</string>
+    <string name="pref_title_ping_tone">Tom do ping</string>
+    <string name="pref_theme_black_background">Usar plano de fundo preto no tema escuro</string>
+    <string name="button_watchface_select_image">Alterar imagem de plano de fundo</string>
+    <string name="qhybrid_title_background_image">Imagem de plano de fundo</string>
+    <string name="fw_upgrade_notice_amazfitneo">Voc锚 est谩 prestes a instalar o firmware %s no seu Amazfit Neo.
+\n
+\nSua pulseira ser谩 reiniciada ap贸s a instala莽茫o do arquivo .fw.
+\n
+\nPROSSIGA AO SEU PR脫PRIO RISCO!</string>
+    <string name="fw_upgrade_notice_amazfitx">Voc锚 est谩 prestes a instalar o firmware %s no seu Amazfit X.
+\n
+\nCertifique-se de instalar o arquivo .fw e depois disso o arquivo .res. Sua pulseira ser谩 reiniciada ap贸s a instala莽茫o do arquivo .fw.
+\n
+\nNota: voc锚 n茫o precisa instalar .res se esse for exatamente o mesmo que o instalado anteriormente.
+\n
+\nPROSSIGA AO SEU PR脫PRIO RISCO!</string>
+    <string name="fw_upgrade_notice_miband6">Voc锚 est谩 prestes a instalar o firmware %s no seu Mi Band 6.
+\n
+\nCertifique-se de instalar o arquivo .fw e depois disso o arquivo .res. Sua pulseira ser谩 reiniciada ap贸s a instala莽茫o do arquivo .fw.
+\n
+\nNota: voc锚 n茫o precisa instalar .res se esse for exatamente o mesmo que o instalado anteriormente.
+\n
+\nPROSSIGA AO SEU PR脫PRIO RISCO!</string>
+    <string name="calendar_year">Ano</string>
+    <string name="calendar_six_months">6 meses</string>
+    <string name="calendar_three_months">3 meses</string>
+    <string name="calendar_month">M锚s</string>
+    <string name="calendar_two_weeks">Duas semanas</string>
+    <string name="calendar_week">Semana</string>
+    <string name="calendar_day">Dia</string>
+    <string name="battery_level">N铆vel da bateria</string>
+    <string name="prefs_autoheartrate_interval">Frequ锚ncia das medi莽玫es</string>
+    <string name="prefs_autoheartrate_sleep">Fazer medi莽玫es durante o sono</string>
+    <string name="prefs_autoheartrate_summary">Medi莽玫es peri贸dicas de frequ锚ncia card铆aca durante o dia e tamb茅m enquanto dorme</string>
+    <string name="watchface_setting_title_power_saving">Economia de energia</string>
+    <string name="watchface_widget_type_chance_rain">Chance de chover</string>
+    <string name="watchface_widget_type_active_mins">Minutos ativos</string>
+    <string name="watchface_widget_type_2nd_tz">2潞 fuso hor谩rio</string>
+    <string name="watchface_widget_type_calories">Calorias</string>
+    <string name="watchface_widget_type_battery">Bateria</string>
+    <string name="watchface_setting_wrist_flick_duration">Dura莽茫o (em ms):</string>
+    <string name="watchface_setting_wrist_flick_minute">Ponteiro dos minutos (-360 a 360):</string>
+    <string name="watchface_setting_wrist_flick_hour">Ponteiro das horas (-360 a 360):</string>
+    <string name="qhybrid_title_watchface_designer">Designer do mostrador</string>
+    <string name="watchface_dialog_widget_preset_right">Direita</string>
+    <string name="watchface_dialog_widget_presets">Predefini莽玫es de posi莽茫o:</string>
+    <string name="watchface_dialog_widget_y_coordinate">Coordenada Y (m谩x. 240):</string>
+    <string name="watchface_widget_type_steps">Passos</string>
+    <string name="watchface_widget_type_weather">Clima</string>
+    <string name="watchface_widget_type_date">Data</string>
+    <string name="watchface_dialog_title_set_name">Definir nome do mostrador</string>
+    <string name="button_watchface_settings">Configura莽玫es do mostrador</string>
+    <string name="qhybrid_watchface_configuration_old_firmware">Tela de configura莽茫o do mostrador para rel贸gios com o firmware vers茫o DN1.0.2.19r ou inferior</string>
+    <string name="qhybrid_calibration_100_steps">100 passos</string>
+    <string name="qhybrid_calibration_10_steps">10 passos</string>
+    <string name="qhybrid_calibration_1_step">1 passo</string>
+    <string name="qhybrid_calibration_clockwise">Sentido hor谩rio</string>
+    <string name="qhybrid_calibration_counterclockwise">Sentido anti-hor谩rio</string>
+    <string name="fossil_hr_new_action">Nova a莽茫o</string>
+    <string name="fossil_hr_new_action_cancel">cancelar</string>
+    <string name="fossil_hr_edit_action">Editar a莽茫o</string>
+    <string name="fossil_hr_edit_action_delete">excluir</string>
+    <string name="pref_summary_canned_messages_dismisscall">Dispensar chamadas a partir do rel贸gio com uma mensagem SMS</string>
+    <string name="qhybrid_summary_calibration">Calibre os ponteiros do rel贸gio</string>
+    <string name="qhybrid_summary_file_management">Enviar e baixar arquivos</string>
+    <string name="qhybrid_title_apps_management">Gerenciamento de aplicativos</string>
+    <string name="qhybrid_title_apps">Aplicativos</string>
+    <string name="mi">mi</string>
+    <string name="minutes_mi">min/mi</string>
+    <string name="mi_h">mi/h</string>
+    <string name="ft">ft</string>
+    <string name="gpx_receiver_files_received">Arquivo(s) GPX recebido:</string>
+    <string name="gpx_receiver_activity_title">Receptor GPX Gadgetbridge</string>
+    <string name="menuitem_flashlight">Lanterna</string>
+    <string name="menuitem_events">Eventos</string>
+    <string name="notification_channel_low_battery_name">Bateria baixa</string>
+    <string name="notification_channel_transfer_name">Transfer锚ncia de dados</string>
+    <string name="devicetype_fitpro">FitPro</string>
+    <string name="devicetype_smaq2oss">SMA-Q2 OSS</string>
+    <string name="devicetype_waspos">Wasp-os</string>
+    <string name="devicetype_um25">UM-25</string>
+    <string name="devicetype_zepp_e">Zepp E</string>
+    <string name="devicetype_amazfit_x">Amazfit X</string>
+    <string name="devicetype_amazfit_gts2e">Amazfit GTS 2e</string>
+    <string name="devicetype_amazfit_gts2_mini">Amazfit GTS 2 Mini</string>
+    <string name="devicetype_amazfit_gtr2e">Amazfit GTR 2e</string>
+    <string name="devicetype_amazfit_bipupro">Amazfit Bip U Pro</string>
+    <string name="nothing_prefs_inear_summary">Reproduzir/pausar a m煤sica dependendo se voc锚 usa os fones de ouvido</string>
+    <string name="nothing_prefs_inear_title">Dete莽茫o no ouvido</string>
+    <string name="nothing_prefs_audiomode_title">Modo de 谩udio</string>
+    <string name="pref_title_notification_use_as">Usar a lista de aplicativos para鈥�</string>
+    <string name="pref_title_notification_use_as_allow">Permitir notifica莽玫es de aplicativos selecionados</string>
+    <string name="pref_title_message_privacy_mode">Modo de privacidade de mensagens</string>
+    <string name="pref_message_privacy_mode_off">Exibir todo o conte煤do</string>
+    <string name="pref_message_privacy_mode_complete">Ocultar todo o conte煤do</string>
+    <string name="title_activity_notification_management">Configura莽玫es de notifica莽茫o</string>
+    <string name="check_all_applications">Marcar todos os aplicativos</string>
+    <string name="uncheck_all_applications">Desmarcar todos os aplicativos</string>
+    <string name="pref_title_notification_use_as_deny">Negar notifica莽玫es dos aplicativos selecionados</string>
+    <string name="pref_header_notification_application_settings">Configura莽玫es por aplicativo</string>
+    <string name="pref_applications_settings">Lista de aplicativos</string>
+    <string name="toast_app_must_not_be_selected">O aplicativo n茫o deve ser selecionado para ser configurado</string>
+    <string name="toast_app_must_be_selected">O aplicativo deve ser selecionado para ser configurado</string>
+    <string name="pref_title_equalizer_bass_boost">Aumento de graves</string>
+    <string name="pref_title_equalizer_soft">Suave</string>
+    <string name="pref_title_equalizer_clear">Limpo</string>
+    <string name="pref_title_equalizer_trebble">Aumento de agudos</string>
+    <string name="prefs_dolby_mode">Modo Dolby</string>
+    <string name="prefs_equalizer_summary">Ativa ou desativa equalizador</string>
+    <string name="prefs_game_mode_summary">Apenas se o seu telefone tem suporte ao modo jogo</string>
+    <string name="prefs_touch_lock">Bloqueio de toques</string>
+    <string name="prefs_touch_lock_summary">Desativa eventos de toque</string>
+    <string name="prefs_galaxy_buds_experimental">Experimental</string>
+    <string name="prefs_ambient_volume">Volume ambiente</string>
+    <string name="prefs_ambient_voice_focus">Foco de voz</string>
+    <string name="prefs_ambient_voice_summary">Faz a voz se destacar</string>
+    <string name="prefs_ambient_sound">Som ambiente</string>
+    <string name="prefs_ambient_mode">Modo ambiente</string>
+    <string name="prefs_left">Esquerda</string>
+    <string name="prefs_right">Direita</string>
+    <string name="prefs_galaxy_touch_options">Op莽玫es de toque</string>
+    <string name="prefs_equalizer">Equalizador</string>
+    <string name="devicetype_galaxybuds">Galaxy Buds</string>
+    <string name="prefs_equalizer_preset">Predefini莽玫es do equalizador</string>
+    <string name="pref_title_equalizer_dynamic">Din芒mico</string>
+    <string name="prefs_game_mode">Modo jogo</string>
+    <string name="prefs_dolby_summary">Predefini莽茫o de Dolby para equalizador</string>
+    <string name="left_earbud">Fone de ouvido esquerdo</string>
+    <string name="right_earbud">Fone de ouvido direito</string>
+    <string name="prefs_activity_in_device_card_sleep_title">Sono</string>
+    <string name="prefs_activity_in_device_card_sleep_title_summary">Mostra a dura莽茫o do sono</string>
+    <string name="prefs_activity_in_device_card_distance_title_summary">A dist芒ncia 茅 calculada a partir de passos e comprimento do passo (ajust谩vel em Configura莽玫es - Sobre voc锚)</string>
+    <string name="prefs_activity_in_device_card_steps_title_summary">Mostra o total de passos</string>
+    <string name="pref_header_other">Outros</string>
+    <string name="pref_header_system">Sistema</string>
+    <string name="pref_header_equalizer">Equalizador</string>
+    <string name="devicetype_sony_wh_1000xm3">Sony WH-1000XM3</string>
+    <string name="devicetype_galaxybuds_live">Galaxy Buds Live</string>
+    <string name="pref_title_equalizer_normal">Normal</string>
+    <string name="prefs_pressure_relief">Al铆vio de press茫o com som ambiente</string>
+    <string name="prefs_active_noise_cancelling_summary">Bloqueia ru铆dos do entorno</string>
+    <string name="pressure_relief_summary">Evita a sensa莽茫o de press茫o nos ouvidos quando n茫o estiver usando o cancelamento de ru铆do ativo</string>
+    <string name="prefs_active_noise_cancelling">Cancelamento de ru铆do ativo</string>
+    <string name="pref_header_sony_ambient_sound_control">Controle de som ambiente</string>
+    <string name="sony_ambient_sound">Modo</string>
+    <string name="sony_ambient_sound_off">Desligado</string>
+    <string name="sony_ambient_sound_wind_noise_reduction">Redu莽茫o de ru铆do do vento</string>
+    <string name="sony_ambient_sound_ambient_sound">Som ambiente</string>
+    <string name="sony_ambient_sound_focus_voice">Foco na voz</string>
+    <string name="sony_sound_position">Posi莽茫o do som</string>
+    <string name="sony_sound_position_off">Desligada</string>
+    <string name="sony_sound_position_front">Frente</string>
+    <string name="sony_sound_position_front_right">Frente direita</string>
+    <string name="sony_sound_position_rear_left">Traseira esquerda</string>
+    <string name="sony_sound_position_rear_right">Traseira direita</string>
+    <string name="sony_surround_mode_off">Desligado</string>
+    <string name="sony_surround_mode_arena">Arena</string>
+    <string name="sony_surround_mode_club">Club</string>
+    <string name="sony_surround_mode_outdoor_stage">Cen谩rio externo</string>
+    <string name="sony_surround_mode_concert_hall">Teatro</string>
+    <string name="sony_warn_sbc_codec">Aviso: As configura莽玫es de equalizador, posi莽茫o de 谩udio e surround s贸 funcionam com o codec de 谩udio SBC.</string>
+    <string name="sony_equalizer_preset_off">Desligado</string>
+    <string name="sony_equalizer_preset_bright">Brilhante</string>
+    <string name="sony_equalizer_preset_excited">Animado</string>
+    <string name="sony_equalizer_preset_mellow">Suave</string>
+    <string name="sony_equalizer_preset_relaxed">Relaxado</string>
+    <string name="sony_equalizer_preset_vocal">Vocal</string>
+    <string name="sony_equalizer_preset_treble_boost">Amplifica莽茫o de agudo</string>
+    <string name="sony_equalizer_preset_speech">Discurso</string>
+    <string name="sony_equalizer_preset_manual">Manual</string>
+    <string name="sony_equalizer_preset_custom_1">Personalizado 1</string>
+    <string name="pref_header_sony_equalizer_preset_custom_2">Predefini莽茫o personalizada 2</string>
+    <string name="sony_equalizer_band_400">400</string>
+    <string name="sony_equalizer_band_1000">1000</string>
+    <string name="pref_header_sony_equalizer_preset_custom_1">Predefini莽茫o personalizada 1</string>
+    <string name="sony_equalizer_band_2500">2500</string>
+    <string name="sony_equalizer_band_6300">6300</string>
+    <string name="sony_equalizer_band_16000">16000</string>
+    <string name="sony_equalizer_clear_bass">Baixo limpo</string>
+    <string name="sony_touch_sensor">Controle com sensor de toque</string>
+    <string name="sony_notification_voice_guide">Notifica莽玫es &amp; guia de voz</string>
+    <string name="sony_automatic_power_off">Desligar automaticamente</string>
+    <string name="sony_automatic_power_off_off">N茫o desligar</string>
+    <string name="sony_automatic_power_off_5_min">5 minutos</string>
+    <string name="sony_automatic_power_off_30_min">30 minutos</string>
+    <string name="sony_automatic_power_off_1_hour">1 hora</string>
+    <string name="sony_automatic_power_off_3_hour">3 horas</string>
+    <string name="watchface_widget_type_custom">Widget personalizado</string>
+    <string name="device_card_activity_card_title_summary">Escolha quais detalhes de atividades s茫o exibidos no cart茫o do dispositivo</string>
+    <string name="device_card_activity_card_title">Informa莽玫es de atividades no cart茫o do dispositivo</string>
+    <string name="prefs_activity_in_device_card_title_summary">Mostra os passos atuais, dist芒ncia ou hiberna莽茫o no cart茫o do dispositivo</string>
+    <string name="sony_equalizer">Equalizador</string>
+    <string name="sony_ambient_sound_level">N铆vel de som ambiente</string>
+    <string name="sony_sound_position_front_left">Frente esquerda</string>
+    <string name="sony_equalizer_preset_custom_2">Personalizado 2</string>
+    <string name="sony_ambient_sound_noise_cancelling">Cancelamento de ru铆do</string>
+    <string name="sony_surround_mode">Modo surround</string>
+    <string name="sony_equalizer_preset_bass_boost">Amplifica莽茫o de grave</string>
+    <string name="battery_case">Caixa de bateria</string>
+    <string name="prefs_activity_in_device_card_title">Mostrar Informa莽玫es de atividades no cart茫o do dispositivo</string>
+    <string name="sony_automatic_power_off_when_taken_off">Quando retirados os fones</string>
+    <string name="prefs_fm_preset_instructions">Pressione longamente o bot茫o para armazenar a predefini莽茫o</string>
+    <string name="prefs_fm_presets_presets">Predefini莽玫es</string>
+    <string name="watchface_dialog_widget_timezone">Fuso hor谩rio:</string>
+    <string name="watchface_dialog_widget_update_timeout">Tempo limite de atualiza莽茫o em minutos:</string>
+    <string name="watchface_dialog_widget_timeout_hide_text">Ocultar texto ao exceder o tempo limite:</string>
+    <string name="watchface_dialog_widget_timeout_show_circle">Mostrar c铆rculo ao exceder o tempo limite:</string>
+    <string name="controlcenter_power_off_confirm_description">Tem a certeza que deseja desligar o dispositivo\?</string>
+    <string name="reminder_time_every_week">%1$s, todas as semanas</string>
+    <string name="reminder_time_every_month">%1$s, todos os meses</string>
+    <string name="reminder_once">Uma vez</string>
+    <string name="reminder_time_every_day">%1$s, todos os dias</string>
+    <string name="reminder_time_every_year">%1$s, todos os anos</string>
+    <string name="reminder_every_week">Todas as semanas</string>
+    <string name="reminder_every_day">Todos os dias</string>
+    <string name="reminder_every_month">Todos os meses</string>
+    <string name="reminder_every_year">Todos os anos</string>
+    <string name="reminder_delete_confirm_title">Excluir lembrete</string>
+    <string name="unknown">Desconhecido</string>
+    <string name="controlcenter_power_off">Desligar</string>
+    <string name="controlcenter_power_off_confirm_title">Desligar</string>
+    <string name="title_activity_set_reminders">Configurar lembretes</string>
+    <string name="controlcenter_start_configure_reminders">Configurar lembretes</string>
+    <string name="reminder_repeat">Repetir</string>
+    <string name="reminder_date">Data</string>
+    <string name="reminder_time">Hora</string>
+    <string name="reminder_message">Mensagem</string>
+    <string name="pref_title_notifications_generic_settings">Configura莽玫es de notifica莽茫o de Android</string>
+    <string name="reminder_time_once">%1$s, uma vez</string>
+    <string name="reminder_delete_confirm_description">Tem certeza de que deseja excluir o lembrete\?</string>
+    <string name="reminder_no_free_slots_title">N茫o h谩 posi莽茫o livre</string>
+    <string name="discovery_bluetooth_scan">Busca por Bluetooth:</string>
+    <string name="discovery_bluetooth_le_scan">Busca por Bluetooth LE:</string>
+    <string name="world_clock_timezone">Fuso hor谩rio</string>
+    <string name="world_clock_no_free_slots_description">O dispositivo n茫o tem espa莽o para rel贸gios mundiais (total de espa莽os: %1$s)</string>
+    <string name="about_activity_title_banglejs_nopebble">Sobre Bangle.js Gadgetbridge</string>
+    <string name="about_description_main_nopebble">Substituto para aplicativos n茫o open source de dispositivos Android dos fabricantes, sem necessitar de nuvem e possui c贸digo livre. Atualiza莽玫es Nightly (di谩rias) de Gadgetbridge. Essa vers茫o tem o provedor Pebble renomeado para prevenir conflitos, ent茫o algumas integra莽玫es do Pebble n茫o funcionar茫o, mas pode ser instalado junto com Gadgetbridge j谩 existente.</string>
+    <string name="gadgetbridge_running_banglejs_nopebble">Bangle.js em execu莽茫o</string>
+    <string name="about_description_main_nightly">Substituto para aplicativos n茫o open source de dispositivos Android dos fabricantes, sem necessitar de nuvem e c贸digo livre. Atualiza莽玫es Nightly (di谩rias) de Gadgetbridge. N茫o pode ser instalado se voc锚 j谩 tem ou o aplicativo Gadgetbridge ou Pebble, devido a um conflito no provedor Pebble.</string>
+    <string name="world_clock_no_free_slots_title">N茫o h谩 espa莽o</string>
+    <string name="about_description_banglejs_nopebble">App de Android para Bangle.js construida sobre o projeto Gadgetbridge, com acesso a Internet adicionado.
+\n
+\nDevido a pol铆ticas da Google Play Store, n茫o estamos autorizados a um link para doa莽玫es no pr贸prio aplicativo, por茅m se voc锚 gostou deste aplicativo por favor considere fazer uma doa莽茫o atrav茅s da p谩gina do Gadgetbridge mostrada abaixo.</string>
+    <string name="about_description_banglejs_main">App de Android para Bangle.js construida sobre o projeto Gadgetbridge, com acesso a Internet adicionado.
+\n
+\nDevido a pol铆ticas da Google Play Store, n茫o estamos autorizados a um link para doa莽玫es no pr贸prio aplicativo, por茅m se voc锚 gostou deste aplicativo por favor considere fazer uma doa莽茫o atrav茅s da p谩gina do Gadgetbridge mostrada abaixo.</string>
+    <string name="application_name_banglejs_main">Bangle.js Gadgetbridge</string>
+    <string name="title_activity_controlcenter_banglejs_main">Bangle.js Gadgetbridge</string>
+    <string name="about_activity_title_banglejs_main">Sobre Bangle.js Gadgetbridge</string>
+    <string name="gadgetbridge_running_banglejs_main">Bangle.js em execu莽茫o</string>
+    <string name="application_name_banglejs_nopebble">Bangle.js Gadgetbridge</string>
+    <string name="title_activity_controlcenter_banglejs_nopebble">Bangle.js Gadgetbridge</string>
+    <string name="application_name_main_nightly">Gadgetbridge (Nightly)</string>
+    <string name="title_activity_controlcenter_main_nightly">Gadgetbridge Nightly</string>
+    <string name="about_activity_title_main_nightly">Sobre Gadgetbridge Nightly</string>
+    <string name="gadgetbridge_running_main_nightly">Nightly GB em execu莽茫o</string>
+    <string name="application_name_main_nopebble">Gadgetbridge (Nightly, Sem provedor Pebble)</string>
+    <string name="title_activity_controlcenter_main_nopebble">Gadgetbridge Nightly Sem Pebble</string>
+    <string name="about_activity_title_main_nopebble">Sobre Gadgetbridge Nightly Sem Pebble</string>
+    <string name="gadgetbridge_running_main_nopebble">Nightly GB Sem Pebble em execu莽茫o</string>
+    <string name="reminder_no_free_slots_description">O dispositivo n茫o tem espa莽o para lembretes (total de espa莽os: %1$s)</string>
+    <string name="world_clock_delete_confirm_title">Excluir \'%1$s\'</string>
+    <string name="world_clock_delete_confirm_description">Tem certeza que deseja excluir o rel贸gio mundial\?</string>
+    <string name="world_clock_label">Etiqueta</string>
+    <string name="accuracy">Precis茫o</string>
+    <string name="balanced">Equilibrado</string>
+    <string name="about_description_banglejs_nightly">Aplicativo companheiro Android para Bangle.js constru铆do em cima do projeto Gadgetbridge, com acesso adicional 脿 Internet.
+\n
+\nDevido 脿s pol铆ticas Google Play Store, n茫o somos permitidos ter um link de doa莽茫o no pr贸prio aplicativo, mas se voc锚 gosta deste aplicativo, por favor, considere doar atrav茅s da p谩gina inicial do Gadgetbridge abaixo.</string>
+    <string name="single_band">Single Band</string>
+    <string name="about_activity_title_banglejs_nightly">Sobre o Bangle.js Gadgetbridge (Nightly)</string>
+    <string name="power_saving">Economia de energia</string>
+    <string name="gadgetbridge_running_banglejs_nightly">Nightly Bangle.js em execu莽茫o</string>
+    <string name="controlcenter_get_heartrate_measurement">Obter medi莽茫o da frequ锚ncia card铆aca</string>
+    <string name="title_activity_controlcenter_banglejs_nightly">Bangle.js Gadgetbridge (Nightly)</string>
+    <string name="action_changelog">Registro de mudan莽as</string>
+    <string name="custom">Personalizado</string>
+    <string name="application_name_banglejs_nightly">Bangle.js Gadgetbridge (Nightly)</string>
+    <string name="pref_header_health">Sa煤de</string>
+    <string name="error_showing_changelog">Erro ao exibir o registro de mudan莽as</string>
+    <string name="appmanager_watchface_activate">Ativar</string>
+    <string name="pref_header_audio">脕udio</string>
+    <string name="pref_header_generic">Gen茅rico</string>
+    <string name="pref_title_mb_intents">Transmitir diretamente as inten莽玫es dos bot玫es de m铆dia</string>
+    <string name="pref_summary_mb_intents">Ative se o controle de m铆dia do dispositivo n茫o estiver funcionando para determinados aplicativos</string>
+    <string name="pref_theme_dynamic">Cores din芒micas</string>
+    <string name="pref_show_changelog_summary">Exibir o registro de mudan莽as desde a 煤ltima vers茫o ap贸s a atualiza莽茫o do Gadgetbridge</string>
+    <string name="accuracy_first">Precis茫o primeiro</string>
+    <string name="speed_first">Velocidade primeiro</string>
+    <string name="pref_show_changelog">Mostrar registro de mudan莽as na inicializa莽茫o</string>
+    <string name="steps_streaks">Sequ锚ncia de passos</string>
+    <string name="step_streak_total">Total</string>
+    <string name="steps_streaks_average_steps">M茅dia de
+\npassos</string>
+    <string name="steps_streaks_achievement_rate">Taxa de
+\nconquista</string>
+    <string name="steps_streaks_total_steps_hint_totals">N煤mero total de passos j谩 registrados</string>
+    <string name="step_streaks_achievements_sharing_title">Conquistas de passos</string>
+    <string name="changelog_show_full">Mais鈥�</string>
+    <string name="uploadwatchfaceoperation_in_progress">Carregando o watchface</string>
+    <string name="steps_streaks_total_steps_hint">N煤mero total de passos em toda a sequ锚ncia</string>
+    <string name="steps_streaks_total_steps_average_hint">M茅dia total de %d passos por dia</string>
+    <string name="step_streak_days_hint">N煤mero de dias consecutivos em que a meta de passos foi atingida</string>
+    <string name="steps_streaks_hint">Sequ锚ncia de dias consecutivos sem interrup莽茫o com metas de passos sendo alcan莽adas</string>
+    <string name="uploading_watchface">Carregando o watchface鈥�</string>
+    <string name="step_streak_longest">Mais longo</string>
+    <string name="appmanager_app_share">Compartilhar</string>
+    <string name="debugactivity_confirm_remove_device_preferences_title">Remover as prefer锚ncias do dispositivo?</string>
+    <string name="debugactivity_confirm_remove_device_preferences">Isso redefinir谩 as prefer锚ncias do dispositivo para todos os dispositivos conectados. Tem certeza?</string>
+    <string name="pref_header_calendar">Calend谩rio</string>
+    <string name="pref_title_general_reconnectonlytoconnected">Reconectar somente a dispositivos conectados</string>
+    <string name="pref_summary_general_reconnectonlytoconnected">Reconectar somente a dispositivos conectados, em vez de reconectar a todos os dispositivos</string>
+    <string name="steps_streaks_total_steps">Passos
+\ntotais</string>
+    <string name="steps_streaks_streak_days">Dias
+\nseguidos</string>
+    <string name="fw_upgrade_notice_amazfitbip3pro">Voc锚 est谩 prestes a instalar o firmware %s no seu Amazfit Bip 3 Pro.
+\n
+\nCertifique-se de instalar o arquivo .fw, e depois o arquivo .res. Seu rel贸gio ser谩 reiniciado ap贸s a instala莽茫o do arquivo .fw.
+\n
+\nNota: voc锚 n茫o precisa instalar .res se esse arquivo for exatamente o mesmo que os instalados anteriormente.
+\n
+\nPROSSIGA POR SUA CONTA E RISCO!</string>
+    <string name="open_fw_installer_select_file">Selecione um arquivo que deseja enviar para o dispositivo: %s</string>
+    <string name="pref_header_display">Exibir</string>
+    <string name="step_streak_average_steps_hint">M茅dia de passos por dia da sequ锚ncia</string>
+    <string name="appmanager_app_start">Iniciar</string>
+    <string name="appmanager_app_download">Download para o cache</string>
+    <string name="appmanager_item_outdated">(desatualizado)</string>
+    <string name="appmanager_download_started">Download do aplicativo iniciado</string>
+    <string name="pref_header_connection">Conex茫o</string>
+    <string name="step_streak_ongoing">Em andamento</string>
+    <string name="pref_title_navigation_prefs">Prefer锚ncias de navega莽茫o</string>
+    <string name="pref_sleepasandroid_features_title">Recursos</string>
+    <string name="pref_sleepasandroid_feat_heartrate">Frequ锚ncia card铆aca</string>
+    <string name="pref_summary_huawei_account">Conta da Huawei usada no processo de emparelhamento. Sua configura莽茫o permite emparelhar sem redefini莽茫o de f谩brica.</string>
+    <string name="pref_header_time">Hora</string>
+    <string name="steps_streaks_total_days_hint_totals">Porcentagem de dias com meta atingida em rela莽茫o a todos os dias com passos</string>
+    <string name="pref_cache_weather">Informa莽玫es sobre o clima em cache</string>
+    <string name="pref_cache_weather_summary">As informa莽玫es do clima ser茫o armazenadas em cache durante as reinicializa莽玫es do aplicativo.</string>
+    <string name="dual_band">Dual Band</string>
+    <string name="steps_streaks_since_date">Desde %s</string>
+    <string name="step_streaks_achievements_sharing_message">Minhas conquistas di谩rias de passos!</string>
+    <string name="watchface_resolution_doesnt_match">A resolu莽茫o do watchface n茫o corresponde 脿 tela do dispositivo. Watchface 茅 %1$s a tela do dispositivo 茅 %2$s</string>
+    <string name="pref_title_huawei_account">Conta da Huawei</string>
+    <string name="appmanager_downloaded_to_cache">Download de %s para o cache</string>
+    <string name="appmanager_download_app_error">Erro no download do aplicativo</string>
+    <string name="open_fw_installer_connect_minimum_one_device">Conecte pelo menos um dispositivo para o qual voc锚 deseja enviar o arquivo.</string>
+    <string name="pref_header_sound_vibration">Som e vibra莽茫o</string>
+    <string name="open_fw_installer_connect_maximum_one_device">Conecte APENAS UM dispositivo para o qual voc锚 deseja enviar o arquivo.</string>
+    <string name="pref_header_offline_voice">Voz offline</string>
+    <string name="open_fw_installer_ensure_device_connected">Certifique-se de que o dispositivo %s esteja conectado</string>
+    <string name="fw_upgrade_notice_amazfitbip3">Voc锚 est谩 prestes a instalar o firmware %s no seu Amazfit Bip 3.
+\n
+\nCertifique-se de instalar o arquivo .fw, e depois o arquivo .res. Seu rel贸gio ser谩 reiniciado ap贸s a instala莽茫o do arquivo .fw.
+\n
+\nNota: voc锚 n茫o precisa instalar .res se esse arquivo for exatamente o mesmo que os instalados anteriormente.
+\n
+\nPROSSIGA POR SUA CONTA E RISCO!</string>
+    <string name="fw_upgrade_notice_miband7">Voc锚 est谩 prestes a instalar o firmware %s no seu Xiaomi Smart Band 7.
+\n
+\nCertifique-se de instalar o arquivo .fw, e depois o arquivo .res. Seu rel贸gio ser谩 reiniciado ap贸s a instala莽茫o do arquivo .fw.
+\n
+\nPROSSIGA POR SUA CONTA E RISCO!</string>
+    <string name="uploadwatchfaceoperation_complete">Instala莽茫o do Watchface conclu铆da</string>
+    <string name="uploadwatchfaceoperation_failed">Falha na instala莽茫o do Watchface</string>
+    <string name="fw_upgrade_notice_amazfit_gts4">Voc锚 est谩 prestes a instalar o firmware %s no seu Amazfit GTS4.
+\n
+\nSeu rel贸gio ir谩 reiniciar ap贸s instalar o arquivo .zip.
+\n
+\nSIGA POR SUA CONTA EM RISCO!</string>
+    <string name="open_fw_installer_getting_files_text">J谩 que n茫o podemos distribuir arquivos de firmware, voc锚 ter谩 que fazer isso voc锚 mesmo. Isso significa que voc锚 precisa primeiro buscar pelos arquivos online, em forums, na Amazfitwatchfaces (para Miband/Amazfit) etc.</string>
+    <string name="open_fw_installer_getting_files_title">Recuperando o arquivo do firmware/app</string>
+    <string name="open_fw_installer_pick_file">Escolha um Arquivo</string>
+    <string name="pref_title_notification_delay_calls">Delay de notifica莽茫o de chamada</string>
+    <string name="pref_summary_notification_delay_calls">Tempo antes de enviar notifica莽玫es de chamadas para o dispositivo, em segundos.</string>
+    <string name="gps_gnolass">GPS + GLONASS</string>
+    <string name="gps_galileo">GPS + GALILEO</string>
+    <string name="all_satellites">Todos os Sat茅lites</string>
+    <string name="open_fw_installer_warning_title">Aviso</string>
+    <string name="open_fw_installer_warning_text">Esta funcionalidade pode quebrar o seu aparelho. Dito isso, este fato nunca aconteceu com nenhum dos desenvolvedores, mas lembre-se que voc锚 est谩 por sua conta em risco.</string>
+    <string name="low_power_gps">GPS de Baixa Pot锚ncia</string>
+    <string name="gps">GPS</string>
+    <string name="gps_bds">GPS + BDS</string>
+    <string name="open_fw_installer_info_text_title">Instalador de arquivos</string>
+    <string name="open_fw_installer_info_text">O instalador de Firmware/Tema/App/Arquivo permite o upload/instala莽茫o de arquivos suportados (firmware, temas, aplica莽玫es, GPS, recursos, fontes...) para o dispositivo. Veja outras informa莽玫es na wiki: https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Firmware-Update</string>
+    <string name="fw_upgrade_notice_amazfit_gts3">Voc锚 est谩 prestes a instalar o firmware %s no seu Amazfit GTS 3.
+\n
+\nSeu rel贸gio ir谩 reiniciar ap贸s instalar o arquivo .zip.
+\n
+\nSIGA POR SUA CONTA EM RISCO!</string>
+    <string name="pref_dashboard_cards_summary">Desenhe cart玫es ao redor dos widgets no painel</string>
+    <plurals name="amount_of_days">
+        <item quantity="one">%d dia</item>
+        <item quantity="many">%d dias</item>
+        <item quantity="other">%d dias</item>
+    </plurals>
+    <string name="pref_summary_bottom_navigation_bar_off">Alternar entre as telas principais usando apenas o deslizamento horizontal</string>
+    <string name="pref_title_bottom_navigation_bar">Barra de navega莽茫o inferior</string>
+    <string name="bottom_nav_dashboard">Painel</string>
+    <string name="pref_dashboard_widget_today_hr_interval_title">Intervalo de frequ锚ncia card铆aca</string>
+    <string name="pref_dashboard_widget_double_size_summary">Permitir que o widget ocupe duas colunas no painel</string>
+    <string name="pref_summary_bottom_navigation_bar_on">Alternar entre as telas principais usando a barra de navega莽茫o ou o deslizamento horizontal</string>
+    <string name="pref_dashboard_widget_today_hr_interval_summary">A quantidade de minutos que o gr谩fico mostra \"usado\" ap贸s cada medi莽茫o bem-sucedida da frequ锚ncia card铆aca</string>
+    <string name="pref_header_authentication">Autentica莽茫o</string>
+    <string name="pref_title_opentracks_packagename">Nome do pacote do OpenTracks</string>
+    <string name="pref_title_navigation_forward">Enviar navega莽茫o para o rel贸gio</string>
+    <string name="mi2_prefs_heart_rate_monitoring_alerts_summary">Configure o monitoramento da frequ锚ncia card铆aca e os limites de alerta</string>
+    <string name="pref_dashboard_all_devices_title">Todos os dispositivos</string>
+    <string name="pref_dashboard_widget_today_upside_down_summary">No modo 24 horas, desenhe a meia-noite na parte inferior e o meio-dia na parte superior do gr谩fico</string>
+    <string name="pref_summary_opentracks_packagename">Usado para iniciar/parar a grava莽茫o da trilha GPS no aplicativo externo de condicionamento f铆sico.</string>
+    <string name="pref_summary_navigation_forward">Encaminhar instru莽玫es de aplicativos de navega莽茫o para o rel贸gio</string>
+    <string name="pref_title_navigation_apps">Aplicativos de navega莽茫o</string>
+    <string name="activity_info">Informa莽玫es sobre a atividade</string>
+    <string name="dashboard_calendar_month_goals_reached_title">% da meta de passos atingida</string>
+    <string name="pref_dashboard_widget_show_legend_summary">Mostrar uma legenda abaixo do widget explicando as cores</string>
+    <string name="menuitem_calendar">Calend谩rio</string>
+    <string name="pref_dashboard_first_title">Mostrar o painel primeiro</string>
+    <string name="pref_dashboard_first_summary">Mostrar o painel quando o Gadgetbridge for iniciado, em vez da tela de dispositivos</string>
+    <string name="pref_dashboard_cards_title">Mostrar widgets nos cart玫es</string>
+    <string name="pref_dashboard_widget_settings">Configura莽玫es do widget</string>
+    <string name="pref_dashboard_widget_today_title">Gr谩fico de atividades</string>
+    <string name="bottom_nav_devices">Dispositivos</string>
+    <string name="dashboard_settings">Configura莽玫es do painel</string>
+    <string name="pref_dashboard_widget_show_legend_title">Mostrar legenda</string>
+    <string name="pref_dashboard_widget_goals_chart_title">Gr谩fico de metas</string>
+    <string name="pref_dashboard_devices_to_include">Dispositivos a serem inclu铆dos</string>
+    <string name="pref_dashboard_all_devices_summary">Combine os dados de atividade de todos os dispositivos adicionados para obter os totais no painel</string>
+    <string name="pref_dashboard_widget_today_upside_down_title">Meia-noite na parte inferior</string>
+    <string name="pref_dashboard_widget_today_24h_title">Modo de 24 horas</string>
+    <string name="pref_dashboard_select_devices_title">Selecionar dispositivos...</string>
+    <string name="mi2_prefs_heart_rate_monitoring">Monitoramento da frequ锚ncia card铆aca</string>
+    <string name="mi2_prefs_heart_rate_monitoring_summary">Configurar o monitoramento da frequ锚ncia card铆aca</string>
+    <string name="pref_dashboard_widget_double_size_title">Tamanho duplo</string>
+    <string name="pref_dashboard_widgets_order_summary">Selecione quais widgets est茫o ativados e em que ordem eles s茫o exibidos no painel</string>
+    <string name="pref_dashboard_widget_today_24h_summary">Mostre a atividade em um 煤nico c铆rculo de 24 horas em vez de um c铆rculo duplo de 12 horas</string>
+    <string name="controlcenter_set_parent_folder">Definir pasta principal</string>
+    <string name="pref_dashboard_select_devices_summary">Combine dados de atividade de dispositivos espec铆ficos para obter os totais no painel</string>
+    <string name="pref_title_osmand_packagename">Nome do pacote do OsmAnd</string>
+    <string name="pref_summary_osmand_packagename">Usado para selecionar a vers茫o do OsmAnd 脿 qual se conectar</string>
+    <string name="info_no_devices_connected">Nenhum dispositivo conectado</string>
+    <string name="fw_upgrade_notice_amazfit_trex2">Voc锚 est谩 prestes a instalar o firmware %s no seu Amazfit T-Rex 2.
+\n
+\nSua pulseira ser谩 reiniciada ap贸s a instala莽茫o do arquivo .zip.
+\n
+\nPROSSIGA POR SUA PR脫PRIA CONTA E RISCO!</string>
+    <string name="fw_upgrade_notice_amazfit_gtr3">Voc锚 est谩 prestes a instalar o firmware %s no seu Amazfit GTR 3.
+\n
+\nSua pulseira ser谩 reiniciada ap贸s a instala莽茫o do arquivo .zip.
+\n
+\nPROSSIGA POR SUA PR脫PRIA CONTA E RISCO!</string>
+    <string name="green">Verde</string>
+    <string name="title_activity_reminder_details">Detalhes de lembrete</string>
+    <string name="devicetype_honor_band5">Honor Band 5</string>
+    <string name="devicetype_huawei_band_aw70">Huawei Band (AW70)</string>
+    <string name="devicetype_huawei_band6">Huawei Band 6</string>
+    <string name="devicetype_redmi_watch_4">Redmi Watch 4</string>
+    <string name="pref_voice_detect_duration_10">10 segundos</string>
+    <string name="devicetype_redmiwatch3">Redmi Watch 3</string>
+    <string name="devicetype_huawei_band8">Huawei Band 8</string>
+    <string name="devicetype_huawei_band9">Huawei Band 9</string>
+    <string name="prefs_wena3_vibration_strength_item">For莽a de Vibra莽茫o</string>
+    <string name="devicetype_amazfit_bip3">Amazfit Bip 3</string>
+    <string name="pref_contacts_title">Contatos</string>
+    <string name="devicetype_amazfit_balance">Amazfit Balance</string>
+    <string name="red">Vermelho</string>
+    <string name="prefs_phone_silent_mode">Modo silencioso do telefone</string>
+    <string name="devicetype_garmin_instinct">Garmin Instinct</string>
+    <string name="devicetype_garmin_instinct_2s_solar">Garmin Instinct 2S Solar</string>
+    <string name="sony_button_mode_volume_control">Controle de Volume</string>
+    <string name="huawei_trusleep_title">HUAWEI TruSleep 鈩�</string>
+    <string name="prefs_wena3_item_rich_design">Use um design avan莽ado</string>
+    <string name="pref_header_calls_and_notifications">Chamadas e notifica莽玫es</string>
+    <string name="pref_speak_notifications_aloud_title">Falar as notifica莽玫es em voz alta</string>
+    <string name="prompt_restart_gadgetbridge">Reinicie o GB para que entre em vigor.</string>
+    <string name="pref_auto_reply_calls_summary">O telefone atender谩 automaticamente as chamadas telef么nicas recebidas</string>
+    <string name="battery_full_notify_enabled">Notificar quando a bateria estiver cheia</string>
+    <string name="share_debug_info">Compartilhar informa莽玫es de depura莽茫o</string>
+    <string name="toggle_debug_mode">Alternar modo de depura莽茫o</string>
+    <string name="battery_low_threshold">Limite de bateria baixa</string>
+    <string name="heartrate_bpm_100">100 bpm</string>
+    <string name="heartrate_bpm_130">130 bpm</string>
+    <string name="heartrate_bpm_135">135 bpm</string>
+    <string name="heartrate_bpm_145">145 bpm</string>
+    <string name="heartrate_bpm_155">155 bpm</string>
+    <string name="heartrate_bpm_165">165 bpm</string>
+    <string name="heartrate_bpm_175">175 bpm</string>
+    <string name="heartrate_bpm_205">205 bpm</string>
+    <string name="spo2_perc_90">90%</string>
+    <string name="contact_delete_confirm_description">Tem certeza de que deseja excluir \'%1$s\'?</string>
+    <string name="contact_no_free_slots_description">O dispositivo n茫o tem slots livres para contatos (total de slots: %1$s)</string>
+    <string name="title_activity_contact_details">Detalhes de contato</string>
+    <string name="world_clock_code">C贸digo</string>
+    <string name="prefs_spo2_monitoring_title">Monitoramento de oxig锚nio no sangue</string>
+    <string name="seconds_6">6 segundos</string>
+    <string name="abstract_chart_fragment_kind_rem_sleep">Sono REM</string>
+    <string name="silent_mode_normal_vibrate">Normal / Vibrar</string>
+    <string name="silent_mode_normal_silent">Normal / Silencioso</string>
+    <string name="prefs_device_name">Nome do dispositivo</string>
+    <string name="prefs_password">Senha</string>
+    <string name="prefs_password_6_digits_0_to_9_summary">A senha deve ter 6 d铆gitos, usando apenas n煤meros</string>
+    <string name="prefs_activate_display_on_lift_sensitivity">Sensibilidade</string>
+    <string name="prefs_screen_timeout">Tempo limite de tela</string>
+    <string name="french_fr">Franc锚s (Fran莽a)</string>
+    <string name="devicetype_amazfit_cheetah_pro">Amazfit Cheetah Pro</string>
+    <string name="prefs_wena3_title_screen">Configura莽玫es de Exibi莽茫o</string>
+    <string name="prefs_wena3_hint_large_font">Aumentar o tamanho da fonte no calend谩rio, nas notifica莽玫es, etc.</string>
+    <string name="pref_navigation_app_gmaps">Google Maps</string>
+    <string name="pref_title_overwrite_settings_on_connection">Sobrescrever configura莽玫es na conex茫o</string>
+    <string name="toast_camera_support_required">O suporte da c芒mera 茅 necess谩rio para essa fun莽茫o.</string>
+    <string name="loading">Carregando鈥�</string>
+    <string name="title_activity_set_contacts">Configurar contatos</string>
+    <string name="pref_contacts_summary">Configurar contatos no rel贸gio</string>
+    <string name="prefs_relaxation_reminder_title">Lembrete de relaxamento</string>
+    <string name="fw_upgrade_notice_amazfit_gts4_mini">Voc锚 est谩 prestes a instalar o firmware %s no seu Amazfit GTS 4 Mini.
+\n
+\nSua pulseira ser谩 reiniciada ap贸s a instala莽茫o do arquivo .zip.
+\n
+\nPROSSIGA POR SUA PR脫PRIA CONTA E RISCO!</string>
+    <string name="fw_upgrade_notice_amazfit_gtr3_pro">Voc锚 est谩 prestes a instalar o firmware %s no seu Amazfit GTR 3 Pro.
+\n
+\nSua pulseira ser谩 reiniciada ap贸s a instala莽茫o do arquivo .zip.
+\n
+\nPROSSIGA POR SUA PR脫PRIA CONTA E RISCO!</string>
+    <string name="fw_upgrade_notice_amazfit_gtr4">Voc锚 est谩 prestes a instalar o firmware %s no seu Amazfit GTR 4.
+\n
+\nSua pulseira ser谩 reiniciada ap贸s a instala莽茫o do arquivo .zip.
+\n
+\nPROSSIGA POR SUA PR脫PRIA CONTA E RISCO!</string>
+    <string name="fw_upgrade_notice_amazfit_band7">Voc锚 est谩 prestes a instalar o firmware %s no seu Amazfit Band 7.
+\n
+\nSua pulseira ser谩 reiniciada ap贸s a instala莽茫o do arquivo .zip.
+\n
+\nPROSSIGA POR SUA PR脫PRIA CONTA E RISCO!</string>
+    <string name="devicetype_amazfit_gts4_mini">Amazfit GTS 4 Mini</string>
+    <string name="devicetype_amazfit_gts4">Amazfit GTS 4</string>
+    <string name="devicetype_galaxybuds_pro">Galaxy Buds Pro</string>
+    <string name="devicetype_garmin_epix_pro">Garmin Epix Pro</string>
+    <string name="devicetype_garmin_fenix_5">Garmin Fenix 5</string>
+    <string name="devicetype_garmin_fenix_6">Garmin Fenix 6</string>
+    <string name="devicetype_garmin_vivosmart_5">Garmin V铆vosmart 5</string>
+    <string name="prefs_wena3_vibration_strength_item_medium">M茅dio</string>
+    <string name="prefs_wena3_vibration_smart_item">Vibra莽茫o Inteligente</string>
+    <string name="prefs_wena3_item_large_font">Tamanho da Fonte Maior</string>
+    <string name="pref_pair_bluetooth_calls_summary">Clique aqui para iniciar o processo de pareamento</string>
+    <string name="pref_blacklist_calendars_summary">Os calend谩rios na lista negra n茫o ser茫o sincronizados com o dispositivo</string>
+    <string name="activity_db_management_autoexport_scheduled_yes">A exporta莽茫o autom谩tica foi (originalmente) agendada para %1$s.</string>
+    <string name="battery_full_threshold">Limite de bateria cheia</string>
+    <string name="femometer_measurement_mode_precise">Modo Preciso (3min)</string>
+    <string name="devicetype_honor_band3">Honor Band 3</string>
+    <string name="devicetype_honor_band4">Honor Band 4</string>
+    <string name="devicetype_honor_band7">Honor Band 7</string>
+    <string name="devicetype_honor_magicwatch2">Honor MagicWatch 2</string>
+    <string name="devicetype_huawei_watch4pro">Huawei Watch 4 Pro</string>
+    <string name="purple">Roxo</string>
+    <string name="folder">Pasta</string>
+    <string name="pref_sleepasandroid_feat_alarms">Alarmes</string>
+    <string name="pref_sleepasandroid_slot_title">Slot de alarmes</string>
+    <string name="pref_sleepasandroid_slot_summary">Qual slot de alarme usar ao definir alarmes</string>
+    <string name="alarm_slot_reset">O slot de alarme foi definido como padr茫o</string>
+    <string name="pref_screen_vibration_patterns_title">Padr玫es de vibra莽茫o</string>
+    <string name="activity_prefs_target_weight_kg">Peso desejado em kg</string>
+    <string name="devicetype_miband7">Xiaomi Smart Band 7</string>
+    <string name="devicetype_amazfit_gtr4">Amazfit GTR 4</string>
+    <string name="devicetype_amazfit_gtr_mini">Amazfit GTR Mini</string>
+    <string name="devicetype_redmi_smart_band_2">Redmi Smart Band 2</string>
+    <string name="pref_speak_notifications_focus_exclusive_summary_off">O volume de reprodu莽茫o de outros aplicativos ser谩 reduzido enquanto a notifica莽茫o estiver sendo falada</string>
+    <string name="prefs_wena3_title_alarm">Configura莽玫es de Alarme</string>
+    <string name="devicetype_huawei_watchultimate">Huawei Watch Ultimate</string>
+    <string name="devicetype_redmi_watch_2">Redmi Watch 2</string>
+    <string name="activity_detail_share_gps_label">Compartilhar Rastro GPS</string>
+    <string name="pref_voice_detect_duration_5">5 segundos</string>
+    <string name="prefs_home_icon_left_item">Esquerda</string>
+    <string name="activity_type_worn">Vestido</string>
+    <string name="pref_summary_notifications_ignore_work_profile">N茫o enviar notifica莽玫es de aplicativos no perfil de trabalho para o rel贸gio</string>
+    <string name="pref_display_caller_summary">Exibir o n煤mero de telefone ou o nome nas chamadas recebidas</string>
+    <string name="bluetooth_calls">Chamadas Bluetooth</string>
+    <string name="bluetooth_calls_pairing">Pareamento de chamadas Bluetooth</string>
+    <string name="bluetooth_calls_settings">Configura莽玫es de chamadas Bluetooth</string>
+    <string name="pref_pair_bluetooth_calls_title">Parear para chamadas Bluetooth</string>
+    <string name="pref_pair_bluetooth_calls_help_title">Como receber chamadas via bluetooth</string>
+    <string name="pref_summary_receive_calls_watch">Fa莽a e receba chamadas diretamente no rel贸gio</string>
+    <string name="pref_pair_bluetooth_calls_help_1">1. Toque no bot茫o abaixo para iniciar o processo de pareamento.</string>
+    <string name="pref_pair_bluetooth_calls_help_warning">AVISO: Se voc锚 ativar as chamadas por Bluetooth sem parear com a segunda inst芒ncia, as notifica莽玫es de chamadas poder茫o n茫o funcionar como esperado.</string>
+    <string name="pref_header_navigation">Navega莽茫o</string>
+    <string name="pref_title_casio_alert_other">Alerta para outras notifica莽玫es</string>
+    <string name="pref_title_casio_alert_sms">Alerta para notifica莽玫es por SMS</string>
+    <string name="pref_title_goal_secondary">Meta secund谩ria</string>
+    <string name="spanish_mx">Espanhol (M茅xico)</string>
+    <string name="spanish_us">Espanhol (Estados Unidos)</string>
+    <string name="persian">Persa</string>
+    <string name="weeksleepchart_sleep_a_week_or_month">Sono por semana/m锚s</string>
+    <string name="autoExport_lastTime_label">脷ltima exporta莽茫o autom谩tica: %1$s</string>
+    <string name="devicetype_miband8active">Xiaomi Smart Band 8 Active</string>
+    <string name="devicetype_amazfit_active_edge">Amazfit Active Edge</string>
+    <string name="devicetype_amazfit_cheetah_square">Amazfit Cheetah (Square)</string>
+    <string name="menuitem_volume">Volume</string>
+    <string name="prefs_wena3_vibration_strength_item_strong">Forte</string>
+    <string name="heartrate_bpm_45">45 bpm</string>
+    <string name="heartrate_bpm_140">140 bpm</string>
+    <string name="prefs_password_4_digits_1_to_4_summary">A senha deve ter 4 d铆gitos, usando os n煤meros de 1 a 4</string>
+    <string name="english_in">Ingl锚s (脥ndia)</string>
+    <string name="english_us">Ingl锚s (Estados Unidos)</string>
+    <string name="devicetype_amazfit_gtr_lite">Amazfit GTR Lite</string>
+    <string name="devicetype_casiogmwb5000">Casio GMW-B5000</string>
+    <string name="dismiss">Dispensar</string>
+    <string name="femometer_measurement_mode_title">Modo de medi莽茫o</string>
+    <string name="femometer_measurement_mode_quick">Modo R谩pido (30s)</string>
+    <string name="femometer_measurement_mode_normal">Modo Normal (60s-90s)</string>
+    <string name="heartrate_bpm_120">120 bpm</string>
+    <string name="pref_world_clocks_summary">Configurar rel贸gios para outros fusos hor谩rios</string>
+    <string name="contact_delete_confirm_title">Excluir contato</string>
+    <string name="prefs_reserve_reminder_calendar_summary">N煤mero de eventos de calend谩rio que ser茫o sincronizados</string>
+    <string name="pref_title_notifications_ignore_low_priority">Ignorar notifica莽玫es de baixa prioridade</string>
+    <string name="pref_summary_notifications_ignore_low_priority">N茫o enviar notifica莽玫es de prioridade baixa e m铆nima para o rel贸gio</string>
+    <string name="pref_summary_notification_prefer_long_text">Se dispon铆vel, envie o texto longo da notifica莽茫o para o dispositivo</string>
+    <string name="pref_title_notification_prefer_long_text">Preferir um texto de notifica莽茫o longo</string>
+    <string name="pref_title_banglejs_phone_gps_enbale">Usar dados de GPS do telefone</string>
+    <string name="pref_canned_message">Mensagem</string>
+    <string name="pref_title_device_internet_access">Permitir Acesso 脿 Internet</string>
+    <string name="pref_summary_device_internet_access">Permitir que os aplicativos deste dispositivo acessem a Internet</string>
+    <string name="pref_display_caller_title">Mostrar informa莽玫es de contato</string>
+    <string name="pref_title_banglejs_phone_gps_update_interval">Intervalo de atualiza莽茫o de dados do GPS</string>
+    <string name="pref_summary_canned_replies">Responder a partir do rel贸gio usando mensagens predefinidas</string>
+    <string name="heartrate_bpm_125">125 bpm</string>
+    <string name="bengali">Bengal锚s</string>
+    <string name="devicetype_miband8">Xiaomi Smart Band 8</string>
+    <string name="devicetype_amazfit_gts3">Amazfit GTS 3</string>
+    <string name="devicetype_amazfit_gtr3_pro">Amazfit GTR 3 Pro</string>
+    <string name="devicetype_amazfit_bip3_pro">Amazfit Bip 3 Pro</string>
+    <string name="devicetype_amazfit_bip5">Amazfit Bip 5</string>
+    <string name="devicetype_amazfit_bip5_unity">Amazfit Bip 5 Unity</string>
+    <string name="devicetype_amazfit_trex_2">Amazfit T-Rex 2</string>
+    <string name="devicetype_amazfit_trex_ultra">Amazfit T-Rex Ultra</string>
+    <string name="devicetype_amazfit_falcon">Amazfit Falcon</string>
+    <string name="devicetype_amazfit_pop">Amazfit Pop</string>
+    <string name="devicetype_amazfit_pop_pro">Amazfit Pop Pro</string>
+    <string name="devicetype_garmin_venu_3">Garmin Venu 3</string>
+    <string name="devicetype_garmin_vivomove_style">Garmin V铆vomove Style</string>
+    <string name="devicetype_huawei_watchgt4">Huawei Watch GT 4</string>
+    <string name="icelandic">Island锚s</string>
+    <string name="ukranian">Ucraniano</string>
+    <string name="devicetype_huawei_band7">Huawei Band 7</string>
+    <string name="devicetype_garmin_forerunner_265">Garmin Forerunner 265</string>
+    <string name="pref_workout_detection_sensitivity">Sensibilidade</string>
+    <string name="pref_screen_notification_profile_find_device">Localizar dispositivo</string>
+    <string name="prefs_home_icon_center_item">Centro</string>
+    <string name="prefs_home_icon_right_item">Direita</string>
+    <string name="devicetype_garmin_forerunner_255s">Garmin Forerunner 255S</string>
+    <string name="devicetype_honor_band6">Honor Band 6</string>
+    <string name="devicetype_xiaomi_watch_lite">Xiaomi Watch Lite</string>
+    <string name="devicetype_redmiwatch3active">Redmi Watch 3 Active</string>
+    <string name="devicetype_huawei_watchfit2">Huawei Watch Fit 2</string>
+    <string name="devicetype_redmi_smart_band_pro">Redmi Smart Band Pro</string>
+    <string name="devicetype_huawei_watch_gt">Huawei Watch GT</string>
+    <string name="devicetype_hama_fit6900">Hama Fit6900</string>
+    <string name="devicetype_galaxybuds_2_pro">Galaxy Buds2 Pro</string>
+    <string name="white">Branco</string>
+    <string name="pref_navigation_app_osmand">OsmAnd(+)</string>
+    <string name="devicetype_xiaomi_watch_s3">Xiaomi Watch S3</string>
+    <string name="devicetype_xiaomi_watch_s1_active">Xiaomi Watch S1 Active</string>
+    <string name="pref_force_connection_type_auto">Autom谩tico</string>
+    <string name="notification_channel_connection_status_name">Status da conex茫o</string>
+    <string name="pref_test_features_title">Recursos</string>
+    <string name="pref_test_features_summary">Recursos habilitados para esse dispositivo de teste</string>
+    <string name="pref_auto_reply_calls_title">Atender chamadas telef么nicas automaticamente</string>
+    <string name="pref_auto_reply_calls_delay_summary">N煤mero de segundos ap贸s os quais a chamada 茅 automaticamente atendida</string>
+    <string name="pref_sleepasandroid_feat_notifications">Notifica莽玫es</string>
+    <string name="pref_title_garmin_default_reply_suffix">Usar sufixo de resposta predefinido</string>
+    <string name="url">URL</string>
+    <string name="copied_to_clipboard">Copiado para a 谩rea de transfer锚ncia</string>
+    <string name="battery_low_notify_enabled">Notificar quando a bateria estiver fraca</string>
+    <string name="show_in_notification">Mostrar em notifica莽茫o</string>
+    <string name="devicetype_garmin_fenix_6_sapphire">Garmin Fenix 6 Sapphire</string>
+    <string name="devicetype_garmin_venu_3s">Garmin Venu 3S</string>
+    <string name="folder_is_empty">A pasta est谩 vazia</string>
+    <string name="pref_speak_notifications_focus_exclusive_title">Pausar o 谩udio de outros aplicativos</string>
+    <string name="pref_speak_notifications_focus_exclusive_summary_on">A reprodu莽茫o de outros aplicativos ser谩 pausada enquanto a notifica莽茫o estiver sendo falada</string>
+    <string name="chart_cycling_point_label_distance">Hoje: %.1f km
+\nTotal: %.1f km</string>
+    <string name="pref_title_cycling_persistence_interval">Intervalo de persist锚ncia</string>
+    <string name="prefs_password_4_digits_0_to_9_summary">A senha deve ter 4 d铆gitos, usando apenas n煤meros</string>
+    <string name="english_ca">Ingl锚s (Canad谩)</string>
+    <string name="portuguese_pt">Portugu锚s (Portugal)</string>
+    <string name="activity_prefs_goal_fat_burn_time_minutes">Meta di谩ria: tempo de queima de gordura em minutos</string>
+    <string name="devicetype_huawei_band4pro">Huawei Band 4 (Pro)</string>
+    <string name="devicetype_huawei_watchgt2">Huawei Watch GT 2 (Pro)</string>
+    <string name="devicetype_huawei_watchgt2e">Huawei Watch GT 2e</string>
+    <string name="devicetype_huawei_watchfit">Huawei Watch Fit</string>
+    <string name="devicetype_huawei_watchgt3">Huawei Watch GT 3 (Pro)</string>
+    <string name="devicetype_huawei_watchfit3">Huawei Watch Fit 3</string>
+    <string name="devicetype_huawei_talk_band_b6">Huawei Talk Band B6</string>
+    <string name="pref_title_touch_volume">Volume</string>
+    <string name="blue">Azul</string>
+    <string name="widget_move_up">Mover para cima</string>
+    <string name="widget_move_down">Mover para baixo</string>
+    <string name="widget_missing_parts">Por favor, selecione todos os widgets</string>
+    <string name="pref_title_fossil_hr_nav_vibrate">Vibrar ao receber novas instru莽玫es</string>
+    <string name="toast_camera_permission_required">A permiss茫o da c芒mera 茅 necess谩ria para essa fun莽茫o.</string>
+    <string name="unsupported">N茫o suportado</string>
+    <string name="max_val">M谩ximo: %d</string>
+    <string name="min_val">M铆nimo: %d</string>
+    <string name="none">Nenhum</string>
+    <string name="toast_camera_photo_taken">A foto foi tirada e salva em: %s</string>
+    <string name="no_folder_selected">Nenhuma pasta selecionada</string>
+    <string name="serbian">S茅rvio</string>
+    <string name="activity_prefs_goal_standing_time_minutes">Meta di谩ria: tempo em p茅 em minutos</string>
+    <string name="devicetype_casiogwb5600">Casio GW-B5600</string>
+    <string name="pref_summary_fossil_hr_nav_foreground">Se o aplicativo de navega莽茫o deve vir automaticamente para o primeiro plano quando receber uma instru莽茫o de navega莽茫o</string>
+    <string name="pref_speak_notifications_aloud_summary">As notifica莽玫es ser茫o lidas em voz alta pelos fones de ouvido</string>
+    <string name="pref_sleepasandroid_features_summary">O suporte difere de dispositivo para dispositivo</string>
+    <string name="pref_sleepasandroid_feat_movement">Aceler么metro</string>
+    <string name="pref_sleepasandroid_feat_oximetry">Oximetria</string>
+    <string name="pref_sleepasandroid_feat_spo2">SPO2</string>
+    <string name="fw_upgrade_notice_amazfit_cheetah_pro">Voc锚 est谩 prestes a instalar o firmware %s no seu Amazfit Cheetah Pro.
+\n
+\nSua pulseira ser谩 reiniciada ap贸s a instala莽茫o do arquivo .zip.
+\n
+\nPROSSIGA POR SUA PR脫PRIA CONTA E RISCO!</string>
+    <string name="pref_title_banglejs_phone_gps_network_only">Usar a rede apenas para determinar a localiza莽茫o</string>
+    <string name="fw_upgrade_notice_zepp_os">Voc锚 est谩 prestes a instalar o firmware %s no seu %s.
+\n
+\nSua pulseira ser谩 reiniciada ap贸s a instala莽茫o do arquivo .zip.
+\n
+\nPROSSIGA POR SUA PR脫PRIA CONTA E RISCO!</string>
+    <string name="heartrate_bpm_105">105 bpm</string>
+    <string name="heartrate_bpm_110">110 bpm</string>
+    <string name="heartrate_bpm_40">40 bpm</string>
+    <string name="heartrate_bpm_50">50 bpm</string>
+    <string name="heartrate_bpm_115">115 bpm</string>
+    <string name="devicetype_garmin_instinct_2x_solar">Garmin Instinct 2X Solar</string>
+    <string name="prefs_wena3_title_activity">Configura莽玫es de Atividade</string>
+    <string name="pref_summary_fossil_hr_nav_vibrate">Se o rel贸gio deve vibrar a cada instru莽茫o de navega莽茫o nova ou alterada (somente quando o aplicativo estiver em primeiro plano)</string>
+    <string name="garmin_agps_local_file">Arquivo local</string>
+    <string name="notif_battery_full">%1$s bateria cheia</string>
+    <string name="seconds_7">7 segundos</string>
+    <string name="seconds_8">8 segundos</string>
+    <string name="seconds_9">9 segundos</string>
+    <string name="miband_prefs_reserve_reminder_calendar">Lembretes para reserva de eventos futuros</string>
+    <string name="pref_enable_unsupported_settings_title">Ativar configura莽玫es n茫o suportadas</string>
+    <string name="prefs_password_enabled">Senha Habilitada</string>
+    <string name="silent_mode_vibrate_silent">Vibrar / Silencioso</string>
+    <string name="english_au">Ingl锚s (Austr谩lia)</string>
+    <string name="english_gb">Ingl锚s (Reino Unido)</string>
+    <string name="spanish_es">Espanhol (Espanha)</string>
+    <string name="georgian">Georgiano</string>
+    <string name="latvian">Let茫o</string>
+    <string name="scandinavian">Escandinavo</string>
+    <string name="french_ca">Franc锚s (Canad谩)</string>
+    <string name="portuguese_br">Portugu锚s (Brasil)</string>
+    <string name="activity_prefs_goals">Metas</string>
+    <string name="activity_db_management_autoexport_enabled_no">A exporta莽茫o autom谩tica n茫o est谩 ativada.</string>
+    <string name="activity_db_management_autoexport_enabled_yes">A exporta莽茫o autom谩tica est谩 ativada.</string>
+    <string name="pref_title_notifications_ignore_work_profile">Ignorar notifica莽玫es de perfil de trabalho</string>
+    <string name="prefs_wena3_notification_settings_title">Configura莽玫es de Notifica莽茫o</string>
+    <string name="prefs_wena3_button_action_name_activity_screen">Tela de Atividade</string>
+    <string name="authentication_failed_check_key">Falha na autentica莽茫o, verifique a chave de autentica莽茫o</string>
+    <string name="devicetype_miband7pro">Xiaomi Smart Band 7 Pro</string>
+    <string name="devicetype_amazfit_cheetah_round">Amazfit Cheetah (Round)</string>
+    <string name="devicetype_amazfit_gtr3">Amazfit GTR 3</string>
+    <string name="devicetype_amazfit_band7">Amazfit Band 7</string>
+    <string name="devicetype_garmin_vivomove_hr">Garmin V铆vomove HR</string>
+    <string name="offline_voice_respond_screen_on">Responder quando a tela estiver ligada</string>
+    <string name="devicetype_garmin_fenix_7_pro">Garmin Fenix 7 Pro</string>
+    <string name="devicetype_garmin_fenix_7s">Garmin Fenix 7S</string>
+    <string name="pref_voice_detect_duration_15">15 segundos</string>
+    <string name="prefs_wena3_home_icon_name_clock">Hora Atual</string>
+    <string name="devicetype_xiaomi_watch_s1">Xiaomi Watch S1</string>
+    <string name="battery_percentage_str">%1$s%%</string>
+    <string name="default_percentage">Padr茫o (%1$d%%)</string>
+    <string name="armenian">Arm锚nio</string>
+    <string name="pref_title_send_app_notifications">Enviar notifica莽玫es</string>
+    <string name="pref_summary_send_app_notifications">Enviar notifica莽玫es de aplicativos para o dispositivo</string>
+    <string name="pref_title_preview_message_in_title">Mostrar uma pr茅via da mensagem no t铆tulo</string>
+    <string name="pref_summary_preview_message_in_title">Mostra uma pr茅via da mensagem no t铆tulo de uma notifica莽茫o, conforme permitido pelo dispositivo</string>
+    <string name="pref_title_casio_alert_calendar">Alerta para notifica莽玫es de calend谩rio</string>
+    <string name="pref_summary_casio_alert_calendar">Alerta (vibra莽茫o/bip) para notifica莽玫es de calend谩rio</string>
+    <string name="pref_summary_casio_alert_call">Alerta (vibra莽茫o/bip) para chamadas recebidas</string>
+    <string name="pref_title_casio_alert_call">Alerta para chamadas recebidas</string>
+    <string name="pref_title_casio_alert_email">Alerta para notifica莽玫es por e-mail</string>
+    <string name="pref_summary_casio_alert_email">Alerta (vibra莽茫o/bip) para notifica莽玫es por e-mail</string>
+    <string name="pref_summary_casio_alert_sms">Alerta (vibrar/bipar) para notifica莽玫es de SMS (mensagem de texto)</string>
+    <string name="pref_summary_casio_alert_other">Alerta (vibrar/bipar) para notifica莽玫es em outra categoria</string>
+    <string name="devicetype_galaxybuds_2">Galaxy Buds2</string>
+    <string name="changelog_title">O que h谩 de novo</string>
+    <string name="serial_number">N煤mero de s茅rie</string>
+    <string name="seconds_11">11 segundos</string>
+    <string name="seconds_12">12 segundos</string>
+    <string name="seconds_13">13 segundos</string>
+    <string name="seconds_14">14 segundos</string>
+    <string name="seconds_25">25 segundos</string>
+    <string name="manual">Manual</string>
+    <string name="croatian">Croata</string>
+    <string name="czech">Tcheco</string>
+    <string name="estonian">Estoniano</string>
+    <string name="lithuanian">Lituano</string>
+    <string name="cyan">Ciano</string>
+    <string name="devicetype_garmin_forerunner_255">Garmin Forerunner 255</string>
+    <string name="permission_request">O %1$s permite que voc锚 envie mensagens e outros dados do Android para o seu dispositivo. Para fazer isso, ele precisa de permiss茫o para acessar esses dados e, sem ela, pode n茫o funcionar corretamente.
+\n
+\nAgora voc锚 ver谩 algumas caixas de di谩logo do Android solicitando essas permiss玫es.
+\n
+\nToque em \'%2$s\' para continuar.</string>
+    <string name="yellow">Amarelo</string>
+    <string name="prefs_wena3_vibration_strength_item_weak">Fraca</string>
+    <string name="prefs_wena3_item_weather_statusbar">Clima na Barra de Status</string>
+    <string name="devicetype_miband2_hrx">Mi Band HRX</string>
+    <string name="devicetype_miband8pro">Xiaomi Smart Band 8 Pro</string>
+    <string name="devicetype_amazfit_active">Amazfit Active</string>
+    <string name="agps_status_error">Erro</string>
+    <string name="pref_morning_updates_title">Atualiza莽玫es matinais</string>
+    <string name="pref_workout_detection_alert_title">Alerta</string>
+    <string name="pref_workout_detection_ask_first">Pergunte-me primeiro</string>
+    <string name="pref_workout_detection_enabled">Detec莽茫o habilitada</string>
+    <string name="pref_screen_notification_profile_event_reminder">Lembrete de evento</string>
+    <string name="pref_world_clocks_title">Rel贸gios mundiais</string>
+    <string name="heartrate_bpm_150">150 bpm</string>
+    <string name="heartrate_bpm_185">185 bpm</string>
+    <string name="heartrate_bpm_195">195 bpm</string>
+    <string name="spo2_perc_80">80%</string>
+    <string name="spo2_perc_85">85%</string>
+    <string name="spo2_off">Desativado</string>
+    <string name="function_enabled">Habilitado</string>
+    <string name="title_activity_world_clock_details">Detalhes do Rel贸gio Mundial</string>
+    <string name="alarm_smart_wakeup_interval_default">5 minutos</string>
+    <string name="prefs_stress_monitoring_description">Monitorar o n铆vel de estresse durante o repouso</string>
+    <string name="seconds_15">15 segundos</string>
+    <string name="danish">Dinamarqu锚s</string>
+    <string name="weekstepschart_steps_a_week_or_month">Passos por semana/m锚s</string>
+    <string name="devicetype_garmin_forerunner_245">Garmin Forerunner 245</string>
+    <string name="devicetype_garmin_instinct_2_soltac">Garmin Instinct 2 SolTac</string>
+    <string name="devicetype_garmin_instinct_solar">Garmin Instinct Solar</string>
+    <string name="devicetype_garmin_venu_2_plus">Garmin Venu 2 Plus</string>
+    <string name="devicetype_garmin_instinct_2s">Garmin Instinct 2S</string>
+    <string name="devicetype_garmin_instinct_2_solar">Garmin Instinct 2 Solar</string>
+    <string name="devicetype_garmin_vivoactive_4">Garmin V铆voactive 4</string>
+    <string name="devicetype_garmin_vivoactive_4s">Garmin V铆voactive 4S</string>
+    <string name="devicetype_garmin_vivoactive_5">Garmin V铆voactive 5</string>
+    <string name="devicetype_garmin_instinct_crossover">Garmin Instinct Crossover</string>
+    <string name="devicetype_garmin_swim_2">Garmin Swim 2</string>
+    <string name="devicetype_garmin_venu_2">Garmin Venu 2</string>
+    <string name="pref_force_connection_type_description">Voc锚 pode tentar for莽ar o tipo de conex茫o caso seu dispositivo n茫o responda ao Gadgetbridge</string>
+    <string name="devicetype_xiaomi_watch_s1_pro">Xiaomi Watch S1 Pro</string>
+    <string name="pref_force_connection_type_title">For莽ar tipo de conex茫o</string>
+    <string name="not_set">N茫o definido</string>
+    <string name="busy_task_fetch_debug_logs">Coletando dados de registros de depura莽茫o</string>
+    <string name="busy_task_fetch_sports_summaries">Coletando dados de resumos esportivos</string>
+    <string name="maxHR">Frequ锚ncia card铆aca m谩xima</string>
+    <string name="currentWorkoutLoad">Carga de treino</string>
+    <string name="changelog_full_title">Registro de mudan莽as</string>
+    <string name="widget_layout_single">1 widget</string>
+    <string name="error_setting_parent_folder">Erro ao definir a pasta principal: %s</string>
+    <string name="menuitem_unsupported">[SEM SUPORTE] %s</string>
+    <string name="controlcenter_set_preferences">Definir prefer锚ncias</string>
+    <string name="huawei_reparse_workout_data">Reprocessar dados do treino</string>
+    <string name="pref_workout_detection_title">Detec莽茫o de treino</string>
+    <string name="pref_workout_detection_ask_first_summary">Exigir confirma莽茫o no rel贸gio ao detectar esse treino</string>
+    <string name="pref_workout_detection_time">Minutos ativos antes da detec莽茫o</string>
+    <string name="busy_task_fetch_temperature">Coletando dados de temperatura</string>
+    <string name="busy_task_fetch_statistics">Coletando estat铆sticas</string>
+    <string name="pai_chart_per_month">PAI por m锚s</string>
+    <string name="devicetype_sonyswr12">Sony SWR12</string>
+    <string name="pref_workout_detection_alert_summary">Notificar quando um treino for detectado</string>
+    <string name="devicetype_sony_wf_1000xm3">Sony WF-1000XM3</string>
+    <string name="devicetype_sony_wf_1000xm4">Sony WF-1000XM4</string>
+    <string name="pref_enable_call_reject_summary">Ativar a rejei莽茫o de chamadas do dispositivo</string>
+    <string name="pref_vitality_score_daily_title">Progresso di谩rio</string>
+    <string name="widget_layout">Layout do widget</string>
+    <string name="pref_force_connection_type_bt_classic">Bluetooth Classic</string>
+    <string name="pref_title_fossil_hr_navigation_instructions">Instru莽玫es de navega莽茫o</string>
+    <string name="device_name_cycling_sensor">Sensor de ciclismo</string>
+    <string name="device_name_bicycle_sensor">Sensor de bicicleta</string>
+    <string name="waiting_for_bluetooth">Aguardando o bluetooth鈥�</string>
+    <string name="realtime_settings">Configura莽玫es em tempo real</string>
+    <string name="devicetype_cycling_sensor">Sensor de velocidade de ciclismo</string>
+    <string name="pref_fetch_unknown_files_title">Colete arquivos desconhecidos</string>
+    <string name="busy_task_fetch_sports_details_interrupted">A coleta de dados de detalhes esportivos foi interrompida</string>
+    <string name="pref_enable_unsupported_settings_summary">Isso permitir谩 o acesso a todas as configura莽玫es dispon铆veis, mesmo que n茫o sejam compat铆veis com o dispositivo. Isso pode causar instabilidade e falhas no dispositivo.</string>
+    <string name="activity_db_management_autoexport_scheduled_no">A exporta莽茫o autom谩tica n茫o foi programada.</string>
+    <string name="activity_type_indoor_walking">Caminhada em ambiente fechado</string>
+    <string name="activity_type_outdoor_walking">Caminhada ao ar livre</string>
+    <string name="menuitem_last_workout">脷ltimo treino</string>
+    <string name="pref_title_debug">Solicita莽茫o de depura莽茫o</string>
+    <string name="busy_task_fetch_stress_data">Coletando dados de estresse</string>
+    <string name="pref_fetch_unknown_files_summary">Colete arquivos de atividade desconhecidos do rel贸gio. Eles n茫o ser茫o processados, mas ser茫o salvos no telefone.</string>
+    <string name="contact_missing_number">O n煤mero de contato est谩 vazio</string>
+    <string name="error_deleting_device">Erro ao excluir o dispositivo: %s</string>
+    <string name="widget_subtype">Subtipo de widget</string>
+    <string name="note">Nota</string>
+    <string name="hrZoneAerobic">Aer贸bico</string>
+    <string name="minHR">Frequ锚ncia card铆aca m铆nima</string>
+    <string name="fossil_hr_pairing_successful">Pareamento bem-sucedido com o rel贸gio</string>
+    <string name="changelog_ok_button">OK</string>
+    <string name="mi5_prefs_workout_activity_types">Tipos de atividades de treino</string>
+    <string name="mi5_prefs_workout_activity_types_summary">Escolha os tipos de atividade a serem exibidos na tela de treino</string>
+    <string name="menuitem_unknown_app">Desconhecido (%s)</string>
+    <string name="minSpeed">M铆nimo</string>
+    <string name="qhybrid_title_on_device_confirmation">Ativar a confirma莽茫o de pareamento no dispositivo</string>
+    <string name="huawei_trusleep_warning">Aviso: ativar isso impedir谩 que o sono apare莽a no Gadgetbridge! Clique aqui se voc锚 aceita isso.</string>
+    <string name="pref_enable_call_accept_summary">Ativar o recebimento de chamadas do dispositivo</string>
+    <string name="connection_over_ble">Conex茫o via BLE</string>
+    <string name="devicetype_asteroidos">AsteroidOS</string>
+    <string name="contact_missing_name">O nome do contato est谩 vazio</string>
+    <string name="widget_screen">Tela de Widget</string>
+    <string name="widget_layout_top_2_bot_2">2 superiores, 2 inferiores</string>
+    <string name="menuitem_workout_status">Status do treino</string>
+    <string name="widget_layout_top_2_bot_1">2 superiores, 1 inferior</string>
+    <string name="pref_screen_auto_brightness_summary">Ajuste o brilho da tela de acordo com a luz ambiente</string>
+    <string name="preferences_miband_1_2_settings" tools:ignore="TypographyFractions">Configura莽玫es da Mi Band 1/2</string>
+    <string name="device_unsupported">SEM SUPORTE</string>
+    <string name="pref_media_volumeup">Aumentar Volume</string>
+    <string name="pref_force_options">For莽ar op莽玫es</string>
+    <string name="info_connected_count">%d dispositivos conectados</string>
+    <string name="description">Descri莽茫o</string>
+    <string name="loyalty_cards_sync_options">Op莽玫es de Sincroniza莽茫o</string>
+    <string name="pref_title_fossil_hr_nav_foreground">Venha para o primeiro plano</string>
+    <string name="pref_auto_reply_calls_delay_title">Atraso de resposta autom谩tica</string>
+    <string name="busy_task_fetch_hr_data">Coletando dados de frequ锚ncia card铆aca</string>
+    <string name="busy_task_fetch_sleep_respiratory_rate_data">Coletando dados de frequ锚ncia respirat贸ria do sono</string>
+    <string name="quick_alarm">Alarme r谩pido</string>
+    <string name="text_receiver_activity_title">Enviar texto para o dispositivo</string>
+    <string name="maxCadence">Cad锚ncia m谩xima</string>
+    <string name="controlcenter_folder_name">Nome da pasta:</string>
+    <string name="pref_title_prefix_notification_with_app">Nome do aplicativo na notifica莽茫o</string>
+    <string name="widget_screen_no_free_slots_description">O dispositivo n茫o tem slots livres para telas de widgets (total de slots: %1$s)</string>
+    <string name="pref_vitality_score_7_day_summary">Receba uma notifica莽茫o quando sua pontua莽茫o de vitalidade atingir 30, 60 ou 100 nos 煤ltimos 7 dias</string>
+    <string name="controlcenter_add_new_folder">Adicionar nova pasta</string>
+    <string name="gpx_route_upload_complete">Carregamento da rota Gpx conclu铆do</string>
+    <string name="menuitem_workout_history">Hist贸rico de treinos</string>
+    <string name="pixoo_power_saving_summary">A tela ser谩 desligada depois que o microfone detectar sil锚ncio por algum tempo</string>
+    <string name="activity_type_aerobic_exercise">Exerc铆cio aer贸bico</string>
+    <string name="pref_media_volumedown">Diminuir Volume</string>
+    <string name="connection_over_bt_classic">Conex茫o via Bluetooth classic</string>
+    <string name="pref_summary_third_party_app_device_settings">Permitir que outros aplicativos de terceiros instalados definam as configura莽玫es do dispositivo por meio de inten莽玫es</string>
+    <string name="title">T铆tulo</string>
+    <string name="pref_workout_detection_summary">Detectar o treino automaticamente</string>
+    <string name="pref_morning_updates_categories_title">Categorias de atualiza莽玫es matinais</string>
+    <string name="clap_hands_to_wakeup_device">Bata palmas para ligar a tela</string>
+    <string name="clap_hands_to_wakeup_device_summary">Bater palmas novamente desligar谩 a tela</string>
+    <string name="username">Nome de usu谩rio</string>
+    <string name="devicetype_sony_wh_1000xm2">Sony WH-1000XM2</string>
+    <string name="devicetype_sony_wh_1000xm5">Sony WH-1000XM5</string>
+    <string name="devicetype_sony_linkbuds_s">Sony LinkBuds S</string>
+    <string name="devicetype_sony_wf_sp800n">Sony WF-SP800N</string>
+    <string name="devicetype_sony_linkbuds">Sony LinkBuds</string>
+    <string name="menuitem_total_workout">Treino total</string>
+    <string name="menuitem_workout_shortcuts">Atalhos de treino</string>
+    <string name="averageAltitude">M茅dio</string>
+    <string name="minCadence">Cad锚ncia m铆nima</string>
+    <string name="anaerobicTrainingEffect">Efeito Anaer贸bico</string>
+    <string name="controlcenter_connected_fraction">Conectado: %d/%d</string>
+    <string name="contact_name">Nome</string>
+    <string name="pref_theme_dynamic_colors_explanation">Observa莽茫o: para o tema de cores din芒micas, 茅 necess谩rio ativar as cores do papel de parede ou a paleta de cores nas configura莽玫es de apar锚ncia do Android 12+. Se voc锚 n茫o fizer isso, o Gadgetbridge usar谩 as cores padr茫o do Material 3.</string>
+    <string name="prefs_wena3_vibration_warning">Aviso</string>
+    <string name="pref_vitality_score_daily_summary">Receba uma notifica莽茫o quando voc锚 atingir o n煤mero m谩ximo de pontos de vitalidade do dia</string>
+    <string name="widget_layout_top_wide_bot_large">Amplo acima, grande abaixo</string>
+    <string name="widget_layout_two">2 widgets</string>
+    <string name="unbind_before_pair_message">Este dispositivo j谩 est谩 vinculado nas configura莽玫es do Android, o que pode fazer com que o pareamento falhe em alguns dispositivos.
+\n
+\nSe a adi莽茫o do dispositivo falhar, remova-o nas configura莽玫es do Android e tente novamente.</string>
+    <string name="pref_summary_garmin_default_reply_suffix">Anexado al茅m do sufixo definido em Gadgetbridge</string>
+    <string name="pref_garmin_agps_help">A lista abaixo cont茅m todos os URLs solicitados pelo rel贸gio para atualiza莽玫es do AGPS. Voc锚 pode selecionar um arquivo do armazenamento do telefone que ser谩 enviado ao rel贸gio quando ele solicitar uma atualiza莽茫o.</string>
+    <string name="hrZoneAnaerobic">Anaer贸bico</string>
+    <string name="activity_detail_share_raw_details">Compartilhar detalhes brutos</string>
+    <string name="pref_title_wheel_diameter">Di芒metro da roda</string>
+    <string name="pref_summary_cycling_persistence_interval">Intervalo em segundos em que os dados de ciclagem atuais devem ser gravados no banco de dados</string>
+    <string name="activity_detail_share_raw_summary">Compartilhar resumo bruto</string>
+    <string name="sony_anc_optimizer_status_analyzing">Analisando鈥�</string>
+    <string name="aerobicTrainingEffect">Efeito Aer贸bico</string>
+    <string name="pref_button_action_disabled">Desabilitado</string>
+    <string name="widget_layout_top_1_bot_2">1 superior, 2 inferiores</string>
+    <string name="widget_screen_min_screens">Deve haver um m铆nimo de %1$s telas</string>
+    <string name="widget_screen_delete_confirm_description">Tem certeza de que deseja excluir \'%1$s\'?</string>
+    <string name="widget_unknown_workout">Treino desconhecido - %s</string>
+    <string name="pref_force_connection_type_ble">Bluetooth LE</string>
+    <string name="unbind_before_pair_title">J谩 vinculado</string>
+    <string name="chart_cycling_point_label_speed">%.1f km/h</string>
+    <string name="open_camera">Abrir c芒mera</string>
+    <string name="title_cycling">Ciclismo</string>
+    <string name="toast_setting_requires_reconnect">Essa configura莽茫o entrar谩 em vigor ap贸s uma reconex茫o</string>
+    <string name="garmin_agps_url_i">URL do AGPS %1$d</string>
+    <string name="averageCadence">Cad锚ncia m茅dia</string>
+    <string name="intent_api_allow_debug_commands_title">Permitir comandos de depura莽茫o</string>
+    <string name="fossil_hr_pairing_failed">Falha no pareamento com o rel贸gio</string>
+    <string name="widget_layout_top_large_bot_wide">Grande em cima, largo embaixo</string>
+    <string name="widget_name_untitled">Widget sem t铆tulo (%1$s)</string>
+    <string name="sleepasandroid_settings">Sleep As Android</string>
+    <string name="pref_sleepasandroid_enable_summary">Habilitar a integra莽茫o do Sleep as Android</string>
+    <string name="pref_sleepasandroid_device_title">Dispositivo provedor</string>
+    <string name="pref_sleepasandroid_device_summary">Selecione o dispositivo como provedor de dados Sleep As Android</string>
+    <string name="pref_header_workout">Treino</string>
+    <string name="pref_screen_vibration_patterns_summary">Configure os padr玫es de vibra莽茫o para diferentes notifica莽玫es</string>
+    <string name="pref_header_heartrate_allday">Monitoramento durante todo o dia</string>
+    <string name="devicetype_vesc">VESC</string>
+    <string name="devicetype_sony_wena3">Sony Wena 3</string>
+    <string name="pref_enable_call_accept">Habilitar o recebimento de chamadas</string>
+    <string name="pref_enable_call_reject">Ativar a rejei莽茫o de chamadas</string>
+    <string name="intent_api_allow_trigger_export_title">Permitir a exporta莽茫o do banco de dados</string>
+    <string name="contact_phone_number">N煤mero de telefone</string>
+    <string name="pref_theme_dynamic_colors_not_available_warning">As cores din芒micas n茫o est茫o dispon铆veis em seu dispositivo, apenas o Android 12+ suporta essa funcionalidade. O Gadgetbridge usar谩 as cores padr茫o do Material 3.</string>
+    <string name="notif_battery_full_title">Bateria do gadget cheia!</string>
+    <string name="pai_chart_per_week">PAI por semana</string>
+    <string name="yesterdays_activity">Atividade de Ontem</string>
+    <string name="devicetype_domyos_t540">Domyos T540</string>
+    <string name="pref_camera_remote_summary">Permite que o rel贸gio acione a c芒mera do telefone</string>
+    <string name="devicetype_mijia_mho_c303">Mijia MHO-C303</string>
+    <string name="devicetype_sony_wi_sp600n">Sony WI-SP600N</string>
+    <string name="devicetype_sony_wf_1000xm5">Sony WF-1000XM5</string>
+    <string name="quick_alarm_description">Alarme do widget</string>
+    <string name="devicetype_pixoo">Pixoo</string>
+    <string name="qhybrid_summary_on_device_confirmation">As confirma莽玫es de pareamento no dispositivo podem ser irritantes. Desativ谩-las pode fazer com que voc锚 perca a funcionalidade.</string>
+    <string name="devicetype_bose_qc35">Bose QC35</string>
+    <string name="pref_workout_detection_time_summary">O n煤mero de minutos que o treino deve estar em andamento antes de ser detectado</string>
+    <string name="gpx_route_upload_failed">Falha no carregamento da rota Gpx</string>
+    <string name="pref_summary_fossil_hr_navigation_instructions">Configurar o comportamento do aplicativo de navega莽茫o no rel贸gio</string>
+    <string name="pref_call_rejection_method_title">M茅todo de rejei莽茫o de chamada</string>
+    <string name="activity_type_indoor_running">Corrida em ambiente fechado</string>
+    <string name="activity_type_outdoor_running">Corrida ao ar livre</string>
+    <string name="devicetype_sony_wh_1000xm4">Sony WH-1000XM4</string>
+    <string name="address">Endere莽o</string>
+    <string name="fossil_hr_confirm_connection">Por favor, confirme em seu rel贸gio</string>
+    <string name="pref_workout_send_gps_title">Enviar GPS durante o treino</string>
+    <string name="pref_workout_start_on_phone_summary">Iniciar/parar o rastreamento do aplicativo de condicionamento f铆sico no telefone quando um treino com GPS for iniciado na pulseira</string>
+    <string name="pref_workout_send_gps_summary">Enviar a localiza莽茫o GPS atual para a pulseira durante um treino</string>
+    <string name="pref_workout_keep_screen_on_title">Mantenha a tela ligada durante o treino</string>
+    <string name="pref_workout_keep_screen_on_summary">A tela permanecer谩 ligada durante o treino e o brilho ser谩 ajustado para exibir continuamente os dados do treino em tempo real</string>
+    <string name="pref_workout_detection_categories_title">Categorias de treino</string>
+    <string name="pref_workout_detection_categories_summary">Categorias de treino para detectar automaticamente</string>
+    <string name="pref_workout_detection_enabled_summary">Ativar a detec莽茫o autom谩tica desse treino</string>
+    <string name="pref_morning_updates_categories_summary">Lista de categorias a serem exibidas todas as manh茫s</string>
+    <string name="pref_morning_updates_summary">Mostrar atualiza莽玫es toda manh茫</string>
+    <string name="preferences_miband_1_2_warning">Aviso: Essas prefer锚ncias se aplicam apenas 脿s Mi Bands 1 e 2.</string>
+    <string name="busy_task_fetch_sports_details">Coletando dados de detalhes esportivos</string>
+    <string name="busy_task_fetch_pai_data">Coletando dados de PAI</string>
+    <string name="busy_task_fetch_spo2_data">Coletando dados de SpO2</string>
+    <string name="prefs_stress_monitoring_title">Monitoramento de estresse</string>
+    <string name="devicetype_soflow_s06">SoFlow SO6</string>
+    <string name="prefs_wena3_receive_calls_title">Configura莽玫es de Chamadas Recebidas</string>
+    <string name="pref_vitality_score_7_day_title">Progresso de 7 dias</string>
+    <string name="pref_vitality_score_title">Pontua莽茫o de vitalidade</string>
+    <string name="pref_title_third_party_app_device_settings">Permitir que aplicativos de terceiros alterem as configura莽玫es</string>
+    <string name="huawei_reparse_workout_data_description">Isso s贸 funcionar谩 ap贸s determinadas atualiza莽玫es</string>
+    <string name="controlcenter_set_folder_title">Definir ou criar uma nova pasta</string>
+    <string name="devicetype_flipper_zero">Flipper zero</string>
+    <string name="widget">Widget</string>
+    <string name="widget_screen_delete_confirm_title">Excluir tela de widget</string>
+    <string name="devicetype_mi_watch_color_sport">Mi Watch Color Sport</string>
+    <string name="pref_summary_device_intents">Permitir que os aplicativos de rel贸gio Bangle.js enviem Android Intents e que outros aplicativos no Android (como o Tasker) enviem dados para o Bangle.js com o com.banglejs.uart.tx Intent. Precisa de permiss茫o para ser exibido sobre outros aplicativos para funcionar em segundo plano.</string>
+    <string name="intent_api_allow_activity_sync_title">Permitir o acionamento da sincroniza莽茫o de atividades</string>
+    <string name="activity_type_hunting">Ca莽a</string>
+    <string name="activity_type_surfing">Surfar</string>
+    <string name="activity_type_gymnastics">Gin谩stica</string>
+    <string name="prefs_wena3_day_start_hour_item">O dia Come莽a Em</string>
+    <string name="pref_anc_optimizer_state_pressure">Press茫o atmosf茅rica</string>
+    <string name="intent_api_broadcast_activity_sync_summary">Anunciar uma inten莽茫o quando a sincroniza莽茫o de atividades for conclu铆da em qualquer dispositivo</string>
+    <string name="pref_title_notification_wake_on_open">Despertar e desbloquear automaticamente</string>
+    <string name="devicetype_garmin_vivoactive_3">Garmin V铆voactive 3</string>
+    <string name="activity_prefs_allow_bluetooth_intent_api">API de inten莽茫o de Bluetooth</string>
+    <string name="intent_api_allow_activity_sync_summary">Permitir o acionamento da sincroniza莽茫o de atividades por meio da API de inten莽茫o</string>
+    <string name="intent_api_allow_debug_commands_summary">Permitir o acionamento de comandos do menu de depura莽茫o por meio da API de Inten莽茫o</string>
+    <string name="pref_title_notification_cache_while_disconnected">Cache enquanto estiver fora do alcance</string>
+    <string name="pref_summary_banglejs_phone_gps_network_only">Use apenas o provedor de rede para determinar a localiza莽茫o. Isso reduz o consumo de energia ao custo da precis茫o. 脡 necess谩ria uma reconex茫o.</string>
+    <string name="pref_summary_banglejs_phone_gps_enbale">Use os dados de GPS do telefone para substituir os dados de GPS do dispositivo bangle</string>
+    <string name="pref_header_intent_api">API de Inten莽玫es (Intent API)</string>
+    <string name="pref_summary_banglejs_phone_gps_update_interval">O intervalo de atualiza莽茫o da posi莽茫o GPS, em ms</string>
+    <string name="pref_header_sony_device_info">Informa莽玫es do dispositivo</string>
+    <string name="pref_title_device_intents">Permitir Inten莽茫o</string>
+    <string name="permission_display_over_other_apps">%1$s precisa de permiss茫o para ser exibido sobre outros aplicativos a fim de permitir que os rel贸gios Bangle.js iniciem atividades por meio de inten莽玫es quando %1$s estiver em segundo plano.
+\n
+\nIsso pode ser usado para iniciar um aplicativo de m煤sica e reproduzir uma m煤sica e muitas outras coisas.
+\n
+\nToque em \"%2$s\" e depois em \"%1$s\" e ative a op莽茫o \"Permitir sobrepor outros aplicativos\", depois toque em \"Voltar\" para retornar a %1$s.
+\n
+\nPara que o %1$s pare de solicitar permiss玫es, v谩 para \"Configura莽玫es\" e desmarque \"Verificar status da permiss茫o\".
+\n
+\nCertifique-se de conceder ao %1$s as permiss玫es necess谩rias para que ele funcione como voc锚 espera.</string>
+    <string name="activity_type_climbing">Escalada</string>
+    <string name="devicetype_redmi_watch_2_lite">Redmi Watch 2 Lite</string>
+    <string name="pref_pair_bluetooth_calls_help_summary">Para receber chamadas por bluetooth, 茅 necess谩rio parear o telefone com uma segunda inst芒ncia do rel贸gio.</string>
+    <string name="pref_summary_notification_cache_while_disconnected">Envie notifica莽玫es perdidas quando um dispositivo se reconectar ap贸s ficar fora do alcance</string>
+    <string name="prefs_wena3_home_icon_name_calories">Calorias</string>
+    <string name="pref_summary_notification_wake_on_open">Desperta e desbloqueia o dispositivo Android quando o gadget envia uma resposta OPEN de volta. Precisa estar em um estado confi谩vel.</string>
+    <string name="pref_summary_wheel_diameter">Di芒metro da roda em polegadas. Normalmente 29, 27,5 ou 26.</string>
+    <string name="intent_api_allow_trigger_export_summary">Permitir o acionamento da exporta莽茫o do banco de dados por meio da API de Inten莽茫o</string>
+    <string name="intent_api_broadcast_export_title">Anunciar na exporta莽茫o do banco de dados</string>
+    <string name="intent_api_broadcast_export_summary">Anunciar uma inten莽茫o quando a exporta莽茫o do banco dados finalizar</string>
+    <string name="prefs_wena3_item_background_sync">Sincroniza莽茫o de dados de atividade em segundo plano</string>
+    <string name="intent_api_broadcast_activity_sync_title">Anunciar no t茅rmino da sincroniza莽茫o da atividade</string>
+    <string name="withings_bt_calibration_previous">Anterior</string>
+    <string name="prefs_wena3_auto_power_off_hint">O dispositivo ser谩 desligado e ligado automaticamente no hor谩rio especificado</string>
+    <string name="activity_type_volleyball">Voleibol</string>
+    <string name="activity_type_table_tennis">T锚nis de Mesa</string>
+    <string name="activity_type_bowling">Boliche</string>
+    <string name="activity_type_rower">Remador</string>
+    <string name="activity_type_street_dance">Dan莽a de Rua</string>
+    <string name="activity_prefs_summary_allow_bluetooth_intent_api">Permitir o controle da conex茫o Bluetooth por meio da API de inten莽茫o</string>
+    <string name="warning_missing_notification_permission">N茫o foi poss铆vel publicar a notifica莽茫o em andamento devido 脿 falta de permiss茫o</string>
+    <string name="loyalty_cards_catima_permissions_title">Permiss玫es faltando</string>
+    <string name="withings_bt_calibration_next">Pr贸ximo</string>
+    <string name="activity_type_pool_swimming">Nata莽茫o em Piscina</string>
+    <string name="pref_call_rejection_method_summary">Que a莽茫o 茅 tomada quando uma chamada recebida 茅 rejeitada pelo rel贸gio</string>
+    <string name="activity_type_mountain_hike">Caminhada na montanha</string>
+    <string name="activity_type_cross_trainer">Cross trainer</string>
+    <string name="activity_type_hiking">Caminhada</string>
+    <string name="prefs_activity_monitoring_title">Monitoramento de atividade</string>
+    <string name="prefs_wena3_menu_icon_name_payment">Pagamento</string>
+    <string name="devicetype_nothing_cmf_watch_pro">CMF Watch Pro</string>
+    <string name="activity_type_rowing">Remo</string>
+    <string name="activity_type_outdoor_cycling">Ciclismo ao ar livre</string>
+    <string name="activity_type_fishing">Pesca</string>
+    <string name="devicetype_nothing_cmf_watch_pro_2">CMF Watch Pro 2</string>
+    <string name="impactMax">Impacto m谩ximo</string>
+    <string name="activity_type_parachuting">Paraquedismo</string>
+    <string name="devicetype_colacao23">ColaCao 2023</string>
+    <string name="activity_type_stairs">Escadas</string>
+    <string name="activity_type_karate">Karat锚</string>
+    <string name="activity_type_karting">Karting</string>
+    <string name="activity_type_dance">Dan莽a</string>
+    <string name="menuitem_forecast">Previs茫o</string>
+    <string name="screenshot_taken">Captura de tela feita</string>
+    <string name="share_screenshot">Compartilhar captura de tela</string>
+    <string name="menuitem_recommendation">Recomenda莽茫o</string>
+    <string name="devicetype_mijia_lywsd03">Mijia Sensor de Temperatura e Umidade 2</string>
+    <string name="impactAvg">Impacto m茅dio</string>
+    <string name="activity_type_boxing">Boxe</string>
+    <string name="activity_type_golf">Golfe</string>
+    <string name="devicetype_colacao21">ColaCao 2021</string>
+    <string name="activity_type_weightlifting">Levantamento de peso</string>
+    <string name="activity_type_dancing">Dan莽ando</string>
+    <string name="activity_type_australian_football">Futebol australiano</string>
+    <string name="activity_type_rock_climbing">Escalada em rocha</string>
+    <string name="activity_type_snow_sports">Esportes na neve</string>
+    <string name="menuitem_thermometer">Term么metro</string>
+    <string name="devicetype_garmin_vivomove_trend">Garmin V铆vomove Trend</string>
+    <string name="activity_type_pilates">Pilates</string>
+    <string name="activity_type_ballet">Bal茅</string>
+    <string name="activity_type_rugby">R煤gbi</string>
+    <string name="activity_type_football">Futebol</string>
+    <string name="activity_type_tennis">T锚nis</string>
+    <string name="activity_type_martial_arts">Artes marciais</string>
+    <string name="devicetype_garmin_vivosport">Garmin V铆vosport</string>
+    <string name="devicetype_nothingear2">Nothing Ear (2)</string>
+    <string name="devicetype_nothingearstick">Nothing Ear (Stick)</string>
+    <string name="activity_error_share_failed">Falha no compartilhamento do arquivo.</string>
+    <string name="updatefirmwareoperation_updateproblem_low_battery">A bateria do dispositivo est谩 muito fraca</string>
+    <string name="permission_notification_listener">%1$s precisa de acesso 脿s Notifica莽玫es para exibi-las no rel贸gio quando a tela do telefone estiver desligada.
+\n
+\nToque em \'%2$s\' e depois em \'%1$s\' e ative \'Permitir acesso a notifica莽玫es\' e, em seguida, toque em \'Voltar\' para retornar a %1$s.</string>
+    <string name="pref_summary_notification_times_enabled">Enviar notifica莽玫es somente entre hor谩rios espec铆ficos</string>
+    <string name="updatefirmwareoperation_updateproblem_free_space">O dispositivo n茫o tem espa莽o livre suficiente</string>
+    <string name="activity_db_management_autoexport_location">N茫o foi poss铆vel entender a localiza莽茫o. Provavelmente um problema do sistema de permiss茫o mais recente do Android. Muito provavelmente, a exporta莽茫o autom谩tica n茫o est谩 funcionando agora.</string>
+    <string name="pref_battery_polling_interval_format">a cada %1$s minutos</string>
+    <string name="proprietary_app_warning">Esse recurso requer a instala莽茫o de um aplicativo propriet谩rio</string>
+    <string name="permission_location">%1$s precisa de acesso 脿 sua localiza莽茫o em segundo plano para permitir que ele permane莽a conectado ao seu rel贸gio mesmo quando a tela estiver desligada. \n \nToque em \'%2$s\' para concordar.</string>
+    <string name="fwapp_install_device_not_supported">O arquivo n茫o pode ser instalado, o dispositivo n茫o 茅 compat铆vel.</string>
+    <string name="moondrop_touch_action_call_start">Iniciar uma liga莽茫o</string>
+    <string name="moondrop_touch_action_assistant">Iniciar o Assistente de Voz</string>
+    <string name="activity_type_darts">Dardos</string>
+    <string name="activity_type_archery">Arco e Flecha</string>
+    <string name="descentDistance">Dist芒ncia de descida</string>
+    <string name="pref_screen_auto_brightness_title">Brilho autom谩tico</string>
+    <string name="elevationGain">Ganho de eleva莽茫o</string>
+    <string name="prefs_control_center">Centro de Controle</string>
+    <string name="activity_type_trail_run">Corrida em trilha</string>
+    <string name="elevationLoss">Descida</string>
+    <string name="flatDistance">Dist芒ncia Plana</string>
+    <string name="add_test_device">Adicionar um dispositivo de teste</string>
+    <string name="battery_polling_failed_start">Falha ao iniciar a captura de dados da bateria</string>
+    <string name="moondrop_touch_action_anc_mode">Ativar o modo de cancelamento de ru铆do</string>
+    <string name="protocol_version">Vers茫o do Protocolo</string>
+    <string name="devicetype_scannable">Dispositivo escane谩vel</string>
+    <string name="spm">passos/min</string>
+    <string name="state_scanned">Escaneado</string>
+    <string name="activity_type_flexibility">Flexibilidade</string>
+    <string name="moondrop_touch_action_call_pick_hang">Atender a liga莽茫o</string>
+    <string name="pref_header_deprecated_functionalities">Funcionalidades descontinuadas</string>
+    <string name="distance_total">Dist芒ncia Total</string>
+    <string name="distance_avg">M茅dia de Dist芒ncia</string>
+    <string name="pref_deprecated_media_control_title">Controle de m铆dia descontinuado</string>
+    <string name="pref_crash_notification_summary">Quando o aplicativo falhar, exiba uma notifica莽茫o com o erro</string>
+    <string name="pref_header_deprecated_functionalities_warning">As seguintes funcionalidades foram descontinuadas e ser茫o removidas em breve do software.\nSe precisar ativar uma das configura莽玫es a seguir, entre em contato com a equipe do projeto.</string>
+    <string name="steps_avg">M茅dia de Passos</string>
+    <string name="steps_total">Total de Passos</string>
+    <string name="controlcenter_toggle_details">Mostrar/ocultar detalhes</string>
+    <string name="pref_title_notification_media_ignores_application_list">As notifica莽玫es de m铆dia ignoram a lista de aplicativos</string>
+    <string name="pref_summary_notification_media_ignores_application_list">Processar notifica莽玫es de m铆dia antes da lista de aplicativos. Se essa prefer锚ncia estiver desmarcada, os aplicativos de m铆dia precisar茫o ser permitidos na lista de aplicativos para que os controles de m铆dia funcionem no dispositivo.</string>
+    <string name="pref_deprecated_media_control_summary">Envie comandos de controle de m铆dia como eventos chave em vez do controlador de m铆dia.</string>
+    <string name="no_supported_devices_found">Nenhum dispositivo compat铆vel encontrado</string>
+    <string name="pref_crash_notification_title">Notificar em caso de falha</string>
+    <string name="pref_description_general">Inicializa莽茫o, idioma, regi茫o, localiza莽茫o</string>
+    <string name="pref_title_user_interface">Interface do usu谩rio</string>
+    <string name="pref_description_about_you">Data de nascimento, g锚nero, altura, peso, metas</string>
+    <string name="pref_header_main_screen">Tela principal</string>
+    <string name="pref_description_user_interface">Tema, tela principal</string>
+    <string name="pref_description_deprecated_functionalities">Configura莽玫es que ser茫o removidas em uma vers茫o futura</string>
+    <string name="activity_prefs_date_birth">Data de nascimento</string>
+    <string name="menuitem_stress_simple">Estresse (simples)</string>
+    <string name="menuitem_stress_segmented">Estresse (segmentado)</string>
+    <string name="menuitem_stress_breakdown">Estresse (colapso)</string>
+    <string name="backup_restore_exporting">Exportando para zip鈥�</string>
+    <string name="backup_restore_warning_files">%1d falha ao restaurar os arquivos:  \n%2s</string>
+    <string name="number_selected_items">%1d selecionado</string>
+    <string name="backup_restore_importing_files_i_of_n">Importando arquivos... %1d de %2d</string>
+    <string name="inactivity_warnings_minimum_steps_summary">Quantidade m铆nima de passos que precisam ser dados durante os minutos de limite</string>
+    <string name="backup_restore_exporting_finishing">Finalizando exporta莽茫o鈥�</string>
+    <string name="backup_restore_abort_title">Abortar</string>
+    <string name="hr_maximum">M谩xima</string>
+    <string name="hr_minimum">M铆nima</string>
+    <string name="hr_average">M茅dia</string>
+    <string name="file_already_exists">O arquivo j谩 existe</string>
+    <string name="activity_db_management_backup_restore_label">Backup e restaura莽茫o</string>
+    <string name="activity_db_management_export_to_zip">Exportar zip</string>
+    <string name="backup_restore_import_complete">Importa莽茫o conclu铆da</string>
+    <string name="backup_restore_importing_database">Importando banco de dados鈥�</string>
+    <string name="backup_restore_importing_preferences">Importando prefer锚ncias鈥�</string>
+    <string name="pref_dashboard_widget_today_yesterday_data_title">Dados de ontem</string>
+    <string name="activity_db_management_import_from_zip">Importar zip</string>
+    <string name="backup_restore_error_export">Falha ao exportar para zip</string>
+    <string name="backup_restore_importing">Importando de um arquivo zip鈥�</string>
+    <string name="inactivity_warnings_minimum_steps_title">Quantidade m铆nima de passos</string>
+    <string name="backup_restore_exporting_files">Exportando arquivos鈥�</string>
+    <string name="backup_restore_restart_summary">%1s ser谩 reiniciado agora.</string>
+    <string name="backup_restore_exporting_preferences">Exportando prefer锚ncias鈥�</string>
+    <string name="backup_restore_exporting_database">Exportando banco de dados鈥�</string>
+    <string name="backup_restore_exporting_files_i_of_n">Exportando arquivos... %1d of %2d</string>
+    <string name="activity_db_management_backup_restore_explanation">As opera莽玫es de importa莽茫o/exporta莽茫o permitem migrar ou fazer backup de todas as configura莽玫es, dispositivos e dados do Gadgetbridge de e para um arquivo zip.\n\nA importa莽茫o de um arquivo remover谩 todos os dados, dispositivos e prefer锚ncias existentes, substituindo-os completamente pelo backup.</string>
+    <string name="backup_restore_export_complete">Exporta莽茫o conclu铆da</string>
+    <string name="backup_restore_importing_loading">Carregando arquivo鈥�</string>
+    <string name="backup_restore_do_not_exit">%s Mantenha esta tela aberta at茅 que a opera莽茫o seja conclu铆da.</string>
+    <string name="backup_restore_restart_title">Reiniciar</string>
+    <string name="backup_restore_error_import">Falha na importa莽茫o do zip</string>
+    <string name="backup_restore_abort_export_confirmation">Abortar a exporta莽茫o? O arquivo zip parcial ser谩 exclu铆do.</string>
+    <string name="backup_restore_importing_validating">Validando arquivo鈥�</string>
+    <string name="pref_dashboard_widget_today_yesterday_data_summary">Exibir dados de ontem em tom mais suave entre a hora atual e a meia-noite</string>
+    <string name="hr_resting">Repouso</string>
+    <string name="discover_unsupported_devices_description">A ativa莽茫o dessa op莽茫o exibir谩 todos os dispositivos bluetooth descobertos durante a procura. Um toque curto copiar谩 o nome do dispositivo e o endere莽o MAC para a 谩rea de transfer锚ncia. Um toque longo abrir谩 a caixa de di谩logo `Adicionar dispositivo de teste`. Pode causar poss铆veis problemas de congelamento do aplicativo.</string>
+    <string name="pref_dashboard_widget_today_time_indicator_summary">Mostrar um indicador na hora atual, para separar visualmente os dados de ontem e de hoje</string>
+    <string name="pref_dashboard_widget_today_time_indicator_title">Indicador de hora atual</string>
+    <string name="pref_description_developer_options">Registros, API de Inten莽茫o (Intent API)</string>
+    <string name="backup_restore_abort_import_confirmation">Abortar a importa莽茫o? Isso pode resultar em um banco de dados corrompido ou inconsistente.</string>
+    <string name="devicetype_huawei_watchd2">Huawei Watch D2</string>
+    <string name="prefs_hrv_monitoring_description">Monitora automaticamente a variabilidade da frequ锚ncia card铆aca ao longo do dia</string>
+    <string name="devicetype_colmi_r10">Colmi R10</string>
+    <string name="devicetype_honor_watchgspro">Honor Watch GS Pro</string>
+    <string name="devicetype_honor_watchgs3">Honor Watch GS 3</string>
+    <string name="devicetype_garmin_forerunner_255s_music">Garmin Forerunner 255S Music</string>
+    <string name="sony_speak_to_chat_sensitivity_auto">Autom谩tico</string>
+    <string name="devicetype_huawei_watchgt5">Huawei Watch GT 5 (Pro)</string>
+    <string name="auto_reconnect_ble_title">Reconex茫o autom谩tica com dispositivo</string>
+    <string name="prefs_spo2_monitoring_description">Monitora automaticamente os n铆veis de oxig锚nio no sangue ao longo do dia</string>
+    <string name="devicetype_garmin_fenix_6s_pro">Garmin Fenix 6S Pro</string>
+    <string name="devicetype_garmin_forerunner_265s">Garmin Forerunner 265S</string>
+    <string name="minutes_6">6 minutos</string>
+    <string name="prefs_activity_monitoring_description">Aumenta automaticamente a frequ锚ncia de detec莽茫o da frequ锚ncia card铆aca quando a pulseira detecta exerc铆cios f铆sicos, para aumentar a precis茫o da captura da frequ锚ncia card铆aca.</string>
+    <string name="minutes_2">2 minutos</string>
+    <string name="minutes_3">3 minutos</string>
+    <string name="minutes_4">4 minutos</string>
+    <string name="minutes_120">120 minutos</string>
+    <string name="minutes_15">15 minutos</string>
+    <string name="minutes_180">180 minutos</string>
+    <string name="minutes_20">20 minutos</string>
+    <string name="minutes_240">240 minutos</string>
+    <string name="minutes_255">255 minutos</string>
+    <string name="minutes_45">45 minutos</string>
+    <string name="minutes_60">60 minutos</string>
+    <string name="minutes_75">75 minutos</string>
+    <string name="minutes_7">7 minutos</string>
+    <string name="minutes_8">8 minutos</string>
+    <string name="minutes_90">90 minutos</string>
+    <string name="minutes_9">9 minutos</string>
+    <string name="devicetype_miband9">Xiaomi Smart Band 9</string>
+    <string name="devicetype_garmin_venu_2s">Garmin Venu 2S</string>
+    <string name="devicetype_garmin_fenix_6s_sapphire">Garmin Fenix 6S Sapphire</string>
+    <string name="devicetype_garmin_fenix_7">Garmin Fenix 7</string>
+    <string name="devicetype_huawei_watch3">Huawei Watch 3 (Pro)</string>
+    <string name="devicetype_garmin_venu">Garmin Venu</string>
+    <string name="devicetype_garmin_enduro_3">Garmin Enduro 3</string>
+    <string name="devicetype_garmin_forerunner_165">Garmin Forerunner 165</string>
+    <string name="devicetype_colmi_r02">Colmi R02</string>
+    <string name="devicetype_colmi_r03">Colmi R03</string>
+    <string name="devicetype_colmi_r06">Colmi R06</string>
+    <string name="devicetype_garmin_fenix_5_plus">Garmin Fenix 5 Plus</string>
+    <string name="devicetype_garmin_fenix_5x_plus">Garmin Fenix 5X Plus</string>
+    <string name="devicetype_garmin_venu_sq_2">Garmin Venu Sq 2</string>
+    <string name="minutes_210">210 minutos</string>
+    <string name="devicetype_huawei_watchgtcyber">Huawei Watch GT Cyber</string>
+    <string name="devicetype_garmin_forerunner_235">Garmin Forerunner 235</string>
+    <string name="devicetype_sony_wi_c100">Sony WI-C100</string>
+    <string name="minutes_150">150 minutos</string>
+    <string name="devicetype_huawei_watchgtrunner">Huawei Watch GT Runner</string>
+    <string name="devicetype_garmin_fenix_8">Garmin Fenix 8</string>
+    <string name="devicetype_garmin_forerunner_245_music">Garmin Forerunner 245 Music</string>
+    <string name="devicetype_garmin_forerunner_955">Garmin Forerunner 955</string>
+    <string name="devicetype_garmin_forerunner_965">Garmin Forerunner 965</string>
+    <string name="devicetype_garmin_forerunner_255_music">Garmin Forerunner 255 Music</string>
+    <string name="devicetype_garmin_venu_sq">Garmin Venu Sq</string>
+    <string name="smart_ring_measurement_error_unknown">Erro de medi莽茫o desconhecido %d recebido do anel</string>
+    <string name="smart_ring_measurement_error_worn_incorrectly">Erro de medi莽茫o. Os sensores do anel est茫o orientados corretamente?</string>
+</resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml
new file mode 100644
index 0000000..b5c6ad2
--- /dev/null
+++ b/app/src/main/res/values-pt/strings.xml
@@ -0,0 +1,2383 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources xmlns:tools="http://schemas.android.com/tools">
+    <string name="application_name_generic">Gadgetbridge</string>
+    <string name="title_activity_controlcenter_generic">Gadgetbridge</string>
+    <string name="action_settings">Defini莽玫es</string>
+    <string name="action_debug">Depurar</string>
+    <string name="action_quit">Sair</string>
+    <string name="controlcenter_fetch_activity_data">Sincronizar</string>
+    <string name="controlcenter_find_device">Encontrar dispositivo perdido</string>
+    <string name="controlcenter_take_screenshot">Captura de ecr茫</string>
+    <string name="controlcenter_change_led_color">Mudar cor do LED</string>
+    <string name="controlcenter_change_fm_frequency">Mudar frequ锚ncia FM</string>
+    <string name="controlcenter_disconnect">Desconectar</string>
+    <string name="controlcenter_delete_device">Apagar aparelho</string>
+    <string name="controlcenter_delete_device_name">Apagar %1$s</string>
+    <string name="controlcenter_delete_device_dialogmessage">Isto ir谩 apagar o dispositivo e todos os dados associados!</string>
+    <string name="controlcenter_navigation_drawer_open">Abrir gaveta de navega莽茫o</string>
+    <string name="controlcenter_navigation_drawer_close">Fechar gaveta de navega莽茫o</string>
+    <string name="controlcenter_snackbar_need_longpress">Pressione longo no cart茫o para desconectar</string>
+    <string name="controlcenter_snackbar_disconnecting">A desconectar</string>
+    <string name="controlcenter_snackbar_connecting">A conectar鈥�</string>
+    <string name="controlcenter_snackbar_requested_screenshot">Capturar o ecr茫 do aparelho</string>
+    <string name="title_activity_debug">Depurar</string>
+    <!--Strings related to AppManager-->
+    <string name="title_activity_appmanager">Gestor de apps</string>
+    <string name="appmanager_cached_watchapps_watchfaces">Aplica莽玫es em cache</string>
+    <string name="appmanager_installed_watchapps">Aplica莽玫es instaladas</string>
+    <string name="appmanager_installed_watchfaces">Temas instalados</string>
+    <string name="appmananger_app_delete">Apagar</string>
+    <string name="appmananger_app_delete_cache">Apagar e remover da cache</string>
+    <string name="appmananger_app_reinstall">Reinstalar</string>
+    <string name="appmanager_app_openinstore">Procurar na loja Pebble</string>
+    <string name="appmanager_health_activate">Ativar</string>
+    <string name="appmanager_health_deactivate">Desativar</string>
+    <string name="appmanager_hrm_activate">Ativar monitor de ritmo card铆aco</string>
+    <string name="appmanager_hrm_deactivate">Desativar monitor de ritmo card铆aco</string>
+    <string name="appmanager_weather_activate">Ativar aplica莽茫o de meteorologia do sistema</string>
+    <string name="appmanager_weather_deactivate">Desativar aplica莽茫o de meteorologia do sistema</string>
+    <string name="appmanager_weather_install_provider">Instalar a aplica莽茫o de notifica莽茫o de meteorologia</string>
+    <string name="app_configure">Configurar</string>
+    <string name="app_move_to_top">Mover para o topo</string>
+    <!--Strings related to FwAppInstaller-->
+    <string name="title_activity_fw_app_insaller">Instalador de FW/app</string>
+    <string name="fw_upgrade_notice">Est谩 prestes a instalar o %s.</string>
+    <string name="fw_multi_upgrade_notice">Est谩s prestes a instalar os firmwares %1$s e %2$s em vez dos que est茫o atualmente na sua Mi Band.</string>
+    <string name="miband_firmware_known">O firmware foi testado e 茅 compat铆vel com o Gadgetbridge.</string>
+    <string name="miband_firmware_unknown_warning">Este firmware n茫o foi testado e pode n茫o ser compat铆vel com o Gadgetbridge.
+\n
+\nN脙O 脡 RECOMENDADO que o instale!</string>
+    <string name="miband_firmware_suggest_whitelist">Se ainda assim quiser continuar e tudo funcionar corretamente, por favor informe os programadores do Gadgetbridge para permitir o firmware vers茫o: %s</string>
+    <!--Strings related to Settings-->
+    <string name="title_activity_settings">Configura莽玫es</string>
+    <string name="pref_header_general">Configura莽玫es gerais</string>
+    <string name="pref_title_general_autoconnectonbluetooth">Ligar aos aparelhos Gadgetbridge quando o Bluetooth estiver ligado</string>
+    <string name="pref_title_general_autostartonboot">Iniciar automaticamente</string>
+    <string name="pref_title_general_autoreconnect">Religar automaticamente</string>
+    <string name="pref_title_audio_player">Aplica莽茫o de m煤sica preferida</string>
+    <string name="pref_default">Padr茫o</string>
+    <string name="pref_header_datetime">Data e Hora</string>
+    <string name="pref_title_datetime_syctimeonconnect">Sincronizar hora</string>
+    <string name="pref_summary_datetime_syctimeonconnect">Sincronizar a hora com os aparelhos Gadgetbridge quando conecta, quando a hora ou fuso hor谩rio forem alterados no Android, e periodicamente</string>
+    <string name="pref_title_theme">Tema</string>
+    <string name="pref_theme_light">Claro</string>
+    <string name="pref_theme_dark">Escuro</string>
+    <string name="pref_title_language">Idioma</string>
+    <string name="pref_title_minimize_priority">Ocultar notifica莽玫es do Gadgetbridge</string>
+    <string name="pref_summary_minimize_priority_off">Exibir 铆cone na barra de estados e nas notifica莽玫es do ecr茫 de bloqueio</string>
+    <string name="pref_summary_minimize_priority_on">Ocultar 铆cone na barra de estados e nas notifica莽玫es do ecr茫 de bloqueio</string>
+    <string name="pref_header_notifications">Notifica莽玫es</string>
+    <string name="pref_title_notifications_repetitions">Repeti莽玫es</string>
+    <string name="pref_title_notifications_call">Chamadas telef贸nicas</string>
+    <string name="pref_title_notifications_sms">SMS</string>
+    <string name="pref_title_notifications_pebblemsg">Mensagens do Pebble</string>
+    <string name="pref_summary_notifications_pebblemsg">Suportar aplica莽玫es que enviam notifica莽玫es pelo PebbleKit.</string>
+    <string name="pref_title_notifications_generic">Suportar notifica莽玫es gen茅ricas</string>
+    <string name="pref_title_whenscreenon">鈥� e quando o ecr茫 estiver ligado</string>
+    <string name="pref_title_notification_filter">N茫o incomodar</string>
+    <string name="pref_summary_notification_filter">Bloqueia todas as notifica莽玫es quando o modo N茫o Incomodar est谩 ativo no telem贸vel</string>
+    <string name="pref_title_transliteration">Translitera莽茫o</string>
+    <string name="pref_summary_transliteration">Ativar apenas se o seu aparelho n茫o suportar caracteres da sua l铆ngua (Atualmente apenas Cir铆lico)</string>
+    <string name="always">Sempre</string>
+    <string name="when_screen_off">Quando o ecr茫 estiver desligado</string>
+    <string name="never">Nunca</string>
+    <string name="pref_header_privacy">Privacidade</string>
+    <string name="pref_title_call_privacy_mode">Modo de chamada privada</string>
+    <string name="pref_call_privacy_mode_off">Mostrar nome e n煤mero</string>
+    <string name="pref_call_privacy_mode_name">Ocultar nome e mostrar n煤mero</string>
+    <string name="pref_call_privacy_mode_number">Ocultar n煤mero e mostrar nome</string>
+    <string name="pref_call_privacy_mode_complete">Ocultar nome e n煤mero</string>
+    <string name="pref_header_cannned_messages">Hist贸rico de mensagens</string>
+    <string name="pref_title_canned_replies">Respostas</string>
+    <string name="pref_title_canned_reply_suffix">Sufixo comum</string>
+    <string name="pref_title_canned_messages_dismisscall">Chamadas recusadas</string>
+    <string name="pref_title_canned_messages_set">Atualizar no dispositivo</string>
+    <string name="pref_header_development">Op莽玫es de programador</string>
+    <string name="pref_title_development_miaddr">Endere莽o do Mi Band</string>
+    <string name="pref_title_pebble_settings">Configura莽玫es Pebble</string>
+    <string name="pref_header_activitytrackers">Monitores de atividade</string>
+    <string name="pref_title_pebble_activitytracker">Monitor de atividades preferido</string>
+    <string name="pref_title_pebble_sync_health">Sincronizar com Pebble Health</string>
+    <string name="pref_title_pebble_sync_misfit">Sincronizar com Misfit</string>
+    <string name="pref_title_pebble_sync_morpheuz">Sincronizar com Morpheuz</string>
+    <string name="pref_title_enable_outgoing_call">Suportar chamadas efetuadas</string>
+    <string name="pref_summary_enable_outgoing_call">Desactivar isto tamb茅m ir谩 fazer com que o Pebble 2/LE n茫o vibre durante as chamadas efetuadas</string>
+    <string name="pref_title_enable_pebblekit">Permitir acesso por apps Android de terceiros</string>
+    <string name="pref_summary_enable_pebblekit">Ativar suporte experimental a apps Android que usem PebbleKit</string>
+    <string name="pref_title_sunrise_sunset">Despertar e p么r-do-sol</string>
+    <string name="pref_summary_sunrise_sunset">Enviar despertar e p么r-do-sol baseado na localiza莽茫o do Pebble</string>
+    <string name="pref_title_autoremove_notifications">Remover automaticamente as notifica莽玫es ignoradas</string>
+    <string name="pref_summary_autoremove_notifications">Notifica莽玫es s茫o automaticamente removidas do dispositivo quando removidas no telem贸vel</string>
+    <string name="pref_title_pebble_privacy_mode">Modo de privacidade</string>
+    <string name="pref_pebble_privacy_mode_off">Notifica莽玫es</string>
+    <string name="pref_pebble_privacy_mode_content">Deslocar texto de notifica莽玫es que extravasar o ecr茫</string>
+    <string name="pref_pebble_privacy_mode_complete">Apenas mostrar 铆cone de notifica莽玫es</string>
+    <string name="pref_header_location">Localiza莽茫o</string>
+    <string name="pref_title_location_aquire">Obter localiza莽茫o</string>
+    <string name="pref_title_location_latitude">Latitude</string>
+    <string name="pref_title_location_longitude">Longitude</string>
+    <string name="pref_title_location_keep_uptodate">Mantenha a localiza莽茫o atualizada</string>
+    <string name="pref_summary_location_keep_uptodate">Tente obter a localiza莽茫o online, use dados armazenados como alternativa</string>
+    <string name="toast_enable_networklocationprovider">Por favor, ative a localiza莽茫o de rede</string>
+    <string name="toast_aqurired_networklocation">localiza莽茫o obtida</string>
+    <string name="pref_title_pebble_forceprotocol">For莽ar protocolo de notifica莽茫o</string>
+    <string name="pref_summary_pebble_forceprotocol">Esta op莽茫o for莽a o uso do protocolo de notifica莽茫o mais recente dependendo da vers茫o do firmware. FA脟A ISSO APENAS SE SOUBER O QUE EST脕 FAZENDO!</string>
+    <string name="pref_title_pebble_forceuntested">Permitir recursos n茫o certificados</string>
+    <string name="pref_summary_pebble_forceuntested">Ativar recursos n茫o testados. FA脟A ISSO APENAS SE SOUBER O QUE EST脕 FAZENDO!</string>
+    <string name="pref_title_pebble_forcele">Sempre preferir BLE</string>
+    <string name="pref_summary_pebble_forcele">Usa suporte experimental a Pebble LE em vez do Bluetooth cl谩ssico. Requer ter nenhum LE pareado para, em seguida, parear o Pebble LE</string>
+    <string name="pref_title_pebble_mtu_limit">Pebble 2/LE GATT MTU limite</string>
+    <string name="pref_summary_pebble_mtu_limit">Se o seu Pebble2/Pebble LE n茫o funciona como esperado, ative esta op莽茫o para limitar o tamanho das transfer锚ncias (intervalo v谩lido 茅 20鈥�512)</string>
+    <string name="pref_title_pebble_enable_applogs">Ativar registo da app do aparelho</string>
+    <string name="pref_summary_pebble_enable_applogs">Causa que os registos das aplica莽玫es do rel贸gio sejam guardados pelo Gadgetbridge (necessita de religa莽茫o)</string>
+    <string name="pref_title_pebble_always_ack_pebblekit">Antecipar confirma莽玫es do PebbleKit</string>
+    <string name="pref_summary_pebble_always_ack_pebblekit">Ir谩 fazer com que as mensagens enviadas para aplica莽玫es de terceiros sejam sempre imediatamente confirmadas</string>
+    <string name="pref_title_pebble_reconnect_attempts">Tentativas de reconex茫o</string>
+    <string name="pref_title_unit_system">Unidades</string>
+    <string name="pref_title_timeformat">Formato da hora</string>
+    <string name="pref_title_screentime">Dura莽茫o do ecr茫</string>
+    <string name="prefs_title_all_day_heart_rate">Medi莽茫o Cont铆nua de Ritmo Card铆aco</string>
+    <string name="preferences_hplus_settings">Configura莽玫es de HPlus/Makibes</string>
+    <string name="not_connected">Desconectado</string>
+    <string name="connecting">Conectando</string>
+    <string name="connected">Conectado</string>
+    <string name="unknown_state">Estado desconhecido</string>
+    <string name="_unknown_">(desconhecido)</string>
+    <string name="test">Teste</string>
+    <string name="test_notification">Notifica莽玫es teste</string>
+    <string name="this_is_a_test_notification_from_gadgetbridge">Esta 茅 uma notifica莽茫o teste do Gadgetbridge</string>
+    <string name="bluetooth_is_not_supported_">Bluetooth n茫o suportado.</string>
+    <string name="bluetooth_is_disabled_">O Bluetooth est谩 desligado.</string>
+    <string name="tap_connected_device_for_app_mananger">Toque no aparelho conectado para o gestor de apps</string>
+    <string name="tap_connected_device_for_activity">Toque no aparelho conectado para atividade</string>
+    <string name="tap_connected_device_for_vibration">Toque no aparelho ligado para o fazer vibrar</string>
+    <string name="tap_a_device_to_connect">Toque num aparelho para ligar</string>
+    <string name="cannot_connect_bt_address_invalid_">N茫o foi poss铆vel conectar. Endere莽o de Bluetooth inv谩lido\?</string>
+    <string name="gadgetbridge_running_generic">Gadgetbridge a executar</string>
+    <string name="installing_binary_d_d">Instalando bin谩rio %1$d/%2$d</string>
+    <string name="installation_failed_">A instala莽茫o falhou</string>
+    <string name="installation_successful">Instalado</string>
+    <string name="firmware_install_warning">EST脕 A TENTAR INSTALAR UM FIRMWARE, CONTINUE 脌 SUA RESPONSABILIDADE.\n\n\n Este firmware 茅 para a Vers茫o de HW: %s</string>
+    <string name="app_install_info">Est谩 prestes a instalar a seguinte aplica莽茫o:
+\n
+\n%1$s
+\nVers茫o %2$s por %3$s
+\n</string>
+    <string name="n_a">N/D</string>
+    <string name="initialized">inicializado</string>
+    <string name="appversion_by_creator">%1$s por %2$s</string>
+    <string name="title_activity_discovery">Descoberta de aparelho</string>
+    <string name="discovery_stop_scanning">Parar com a busca</string>
+    <string name="discovery_start_scanning">Iniciar descoberta</string>
+    <string name="action_discover">Conectar novo aparelho</string>
+    <string name="device_with_rssi">%1$s (%2$s)</string>
+    <string name="title_activity_android_pairing">Parear aparelho</string>
+    <string name="android_pairing_hint">Use a caixa de di谩logo Android Bluetooth para parear o aparelho.</string>
+    <string name="title_activity_mi_band_pairing">Emparelhar com a sua Mi Band</string>
+    <string name="pairing">A emparelhar com %s鈥�</string>
+    <string name="pairing_creating_bond_with">A criar liga莽茫o com %1$s (%2$s)</string>
+    <string name="pairing_unable_to_pair_with">N茫o foi poss铆vel emparelhar com %1$s (%2$s)</string>
+    <string name="pairing_in_progress">Associa莽茫o em progresso: %1$s (%2$s)</string>
+    <string name="pairing_already_bonded">J谩 associado com %1$s (%2$s), a ligar鈥�</string>
+    <string name="message_cannot_pair_no_mac">Nenhum endere莽o MAC passado, n茫o 茅 poss铆vel parear.</string>
+    <string name="preferences_category_device_specific_settings">Configura莽玫es espec铆ficas do aparelho</string>
+    <string name="preferences_miband_settings">Configura莽玫es de Mi Band / Amazfit</string>
+    <string name="male">Masculino</string>
+    <string name="female">Feminino</string>
+    <string name="other">Outro</string>
+    <string name="left">Esquerda</string>
+    <string name="right">Direita</string>
+    <string name="miband_pairing_using_dummy_userdata">Sem dados de utilizador. Por enquanto ser茫o usados dados fict铆cios.</string>
+    <string name="miband_pairing_tap_hint">Quando a sia Mi Band vibrar e piscar, d锚-lhe alguns toques seguidos.</string>
+    <string name="appinstaller_install">Instalar</string>
+    <string name="discovery_connected_devices_hint">Fa莽a o seu aparelho vis铆vel. 脡 improv谩vel que os aparelho atualmente ligados sejam apresentados. Ative a localiza莽茫o (i.e, GPS) no Android 6+. Desative tamb茅m a Prote莽茫o de Privacidade para o Gadgetbridge, pois esta poder谩 bloquear e levar ao rein铆cio do seu telefone. Se depois de alguns minutos n茫o for encontrado qualquer aparelho, tente outra vez ap贸s reiniciar o telefone.</string>
+    <string name="discovery_note">Nota:</string>
+    <string name="candidate_item_device_image">Imagem do aparelho</string>
+    <string name="miband_prefs_alias">Nome/Apelido</string>
+    <string name="pref_header_vibration_count">Quantidade de vibra莽玫es</string>
+    <string name="title_activity_sleepmonitor">Monitor de sono</string>
+    <string name="pref_write_logfiles">Escrever ficheiros de registo</string>
+    <string name="initializing">A inicializar</string>
+    <string name="busy_task_fetch_activity_data">Coletando dados de atividade</string>
+    <string name="sleep_activity_date_range">De %1$s at茅 %2$s</string>
+    <string name="prefs_wearside">De que lado a usa?</string>
+    <string name="pref_screen_vibration_profile">Perfil de vibra莽茫o</string>
+    <string name="vibration_profile_staccato">Destacado</string>
+    <string name="vibration_profile_short">Pequeno</string>
+    <string name="vibration_profile_medium">M茅dio</string>
+    <string name="vibration_profile_long">Longo</string>
+    <string name="vibration_profile_waterdrop">Gota de 脕gua</string>
+    <string name="vibration_profile_ring">Toque</string>
+    <string name="vibration_profile_alarm_clock">Alarme</string>
+    <string name="miband_prefs_vibration">Vibra莽茫o</string>
+    <string name="vibration_try">Tente</string>
+    <string name="pref_screen_notification_profile_sms">Notifica莽茫o SMS</string>
+    <string name="pref_header_vibration_settings">Configura莽玫es de vibra莽玫es</string>
+    <string name="pref_screen_notification_profile_generic">Notifica莽茫o gen茅rica</string>
+    <string name="pref_screen_notification_profile_email">Notifica莽茫o de e-mail</string>
+    <string name="pref_screen_notification_profile_incoming_call">Notifica莽茫o de chamada recebida</string>
+    <string name="pref_screen_notification_profile_generic_chat">Conversas</string>
+    <string name="pref_screen_notification_profile_generic_navigation">Navega莽茫o</string>
+    <string name="pref_screen_notification_profile_generic_social">Rede social</string>
+    <string name="control_center_find_lost_device">Encontrar aparelho perdido</string>
+    <string name="control_center_cancel_to_stop_vibration">Cancele para parar a vibra莽茫o.</string>
+    <string name="title_activity_charts">Atividade e sono</string>
+    <string name="title_activity_set_alarm">Configurar alarmes</string>
+    <string name="title_activity_set_reminders">Configurar Lembretes</string>
+    <string name="controlcenter_start_configure_alarms">Configurar alarmes</string>
+    <string name="controlcenter_start_configure_reminders">Configurar lembretes</string>
+    <string name="title_activity_alarm_details">Detalhes do alarme</string>
+    <string name="title_activity_reminder_details">Detalhes do lembrete</string>
+    <string name="reminder_time_once">%1$s, uma vez</string>
+    <string name="reminder_time_every_day">%1$s, todos os dias</string>
+    <string name="reminder_time_every_week">%1$s, todas as semanas</string>
+    <string name="reminder_time_every_month">%1$s, todos os meses</string>
+    <string name="reminder_time_every_year">%1$s, todos os anos</string>
+    <string name="reminder_once">Uma vez</string>
+    <string name="reminder_every_day">Todos os dias</string>
+    <string name="reminder_every_week">Todas as semanas</string>
+    <string name="reminder_every_month">Todos os meses</string>
+    <string name="reminder_every_year">Todos os anos</string>
+    <string name="reminder_delete_confirm_title">Apagar lembrete</string>
+    <string name="reminder_delete_confirm_description">De certeza que pretende apagar o lembrete?</string>
+    <string name="alarm_sun_short">Dom</string>
+    <string name="alarm_mon_short">Seg</string>
+    <string name="alarm_tue_short">Ter</string>
+    <string name="alarm_wed_short">Qua</string>
+    <string name="alarm_thu_short">Qui</string>
+    <string name="alarm_fri_short">Sex</string>
+    <string name="alarm_sat_short">Sab</string>
+    <string name="alarm_smart_wakeup">Despertar inteligente</string>
+    <string name="user_feedback_miband_set_alarms_failed">Houve um erro ao definir os alarmes, por favor, tente novamente.</string>
+    <string name="user_feedback_miband_set_alarms_ok">Alarmes enviados ao aparelho.</string>
+    <string name="chart_no_data_synchronize">Sem dados. Sincronizar com o aparelho\?</string>
+    <string name="user_feedback_miband_activity_data_transfer">Prestes a transferir %1$s de dados desde %2$s</string>
+    <string name="miband_prefs_fitness_goal">Meta di谩ria de passos</string>
+    <string name="dbaccess_error_executing">Erro ao executar \'%1$s\'</string>
+    <string name="controlcenter_start_activitymonitor">A sua atividade (ALPHA)</string>
+    <string name="cannot_connect">N茫o foi poss铆vel ligar: %1$s</string>
+    <string name="installer_activity_unable_to_find_handler">N茫o foi poss铆vel encontrar um manipulador para instalar o ficheiro.</string>
+    <string name="pbw_install_handler_unable_to_install">N茫o foi poss铆vel instalar o ficheiro: %1$s</string>
+    <string name="pbw_install_handler_hw_revision_mismatch">N茫o 茅 poss铆vel instalar o firmware fornecido: ele n茫o corresponde 脿 revis茫o do hardware do seu Pebble.</string>
+    <string name="installer_activity_wait_while_determining_status">Aguarde enquanto se determina o estado da instala莽茫o鈥�</string>
+    <string name="notif_battery_low_title">Aparelho com bateria baixa!</string>
+    <string name="notif_battery_low_percent">%1$s bateria restante: %2$s%%</string>
+    <string name="notif_battery_low_bigtext_last_charge_time">脷ltima carga: %s \n</string>
+    <string name="notif_battery_low_bigtext_number_of_charges">N煤mero de cargas: %s</string>
+    <string name="sleepchart_your_sleep">Sono</string>
+    <string name="weeksleepchart_sleep_a_week">Dormir por semana</string>
+    <string name="weeksleepchart_today_sleep_description">Sono hoje, objetivo: %1$s</string>
+    <string name="weekstepschart_steps_a_week">Passos por semana</string>
+    <string name="activity_sleepchart_activity_and_sleep">Atividade</string>
+    <string name="updating_firmware">A atualizar firmware鈥�</string>
+    <string name="fwapp_install_device_not_ready">O ficheiro n茫o pode ser instalado, o aparelho n茫o est谩 pronto.</string>
+    <string name="installhandler_firmware_name">%1$s: %2$s %3$s</string>
+    <string name="miband_fwinstaller_compatible_version">Vers茫o compat铆vel</string>
+    <string name="miband_fwinstaller_untested_version">Vers茫o n茫o testada!</string>
+    <string name="fwappinstaller_connection_state">Liga莽茫o do aparelho: %1$s</string>
+    <string name="pbw_installhandler_pebble_firmware">Firmware da Pebble: %1$s</string>
+    <string name="pbwinstallhandler_correct_hw_revision">Revis茫o de hardware correta</string>
+    <string name="pbwinstallhandler_incorrect_hw_revision">Revis茫o de hardware inv谩lida!</string>
+    <string name="pbwinstallhandler_app_item">%1$s (%2$s)</string>
+    <string name="updatefirmwareoperation_updateproblem_do_not_reboot">Ocorreu um erro com a transfer锚ncia do firmware. N脙O REINICIE a sua Mi Band!</string>
+    <string name="updatefirmwareoperation_metadata_updateproblem">Problemas ao transferir os metadados do firmware</string>
+    <string name="updatefirmwareoperation_update_complete">Instala莽茫o do Firmware completa</string>
+    <string name="updatefirmwareoperation_update_complete_rebooting">Instala莽茫o do firmware completa, a reiniciar o aparelho鈥�</string>
+    <string name="updatefirmwareoperation_write_failed">Falha ao instalar o firmware</string>
+    <string name="chart_steps">Passos</string>
+    <string name="calories">Calorias</string>
+    <string name="distance">Dist芒ncia</string>
+    <string name="clock">Rel贸gio</string>
+    <string name="heart_rate">Ritmo card铆aco</string>
+    <string name="battery">Bateria</string>
+    <string name="liveactivity_live_activity">Atividade ao vivo</string>
+    <string name="weeksteps_today_steps_description">Passos hoje, objetivo: %1$s</string>
+    <string name="pref_title_dont_ack_transfer">N茫o reconhecer transfer锚ncia de dados da atividade</string>
+    <string name="pref_summary_dont_ack_transfers">Se n茫o reconhecido para a pulseira, dados de atividade n茫o ser茫o apagados. 脷til se GB for usado em conjunto com outras apps.</string>
+    <string name="pref_summary_keep_data_on_device">Ir谩 manter os dados na Mi Band mesmo ap贸s a sincroniza莽茫o. 脷til se o GB 茅 utilizado em simult芒neo com outras aplica莽玫es.</string>
+    <string name="pref_title_low_latency_fw_update">Usar o modo de baixa lat锚ncia para instala莽茫o de firmware</string>
+    <string name="pref_summary_low_latency_fw_update">Isso pode ajudar em aparelhos no caso de eventual falha na instala莽茫o de firmware.</string>
+    <string name="live_activity_steps_history">Hist贸rico de passos</string>
+    <string name="live_activity_current_steps_per_minute">Passos/min atuais</string>
+    <string name="live_activity_total_steps">Total de passos</string>
+    <string name="live_activity_steps_per_minute_history">Hist贸rico de passos por minuto</string>
+    <string name="live_activity_start_your_activity">Inicie a sua atividade</string>
+    <string name="abstract_chart_fragment_kind_activity">Atividade</string>
+    <string name="abstract_chart_fragment_kind_light_sleep">Sono leve</string>
+    <string name="abstract_chart_fragment_kind_deep_sleep">Sono profundo</string>
+    <string name="abstract_chart_fragment_kind_not_worn">N茫o usado</string>
+    <string name="device_not_connected">Desligado.</string>
+    <string name="user_feedback_all_alarms_disabled">Todos os alarmes desligados</string>
+    <string name="pref_title_keep_data_on_device">Manter dados de atividade no aparelho</string>
+    <string name="miband_fwinstaller_incompatible_version">Firmware incompat铆vel</string>
+    <string name="fwinstaller_firmware_not_compatible_to_device">Este firmware n茫o 茅 compat铆vel com o seu aparelho</string>
+    <string name="miband_prefs_reserve_alarm_calendar">Alarmes reservados para eventos pr贸ximos</string>
+    <string name="miband_prefs_hr_sleep_detection">Usar o sensor de frequ锚ncia card铆aca para melhorar a detec莽茫o do sono</string>
+    <string name="miband_prefs_device_time_offset_hours">Compensa莽茫o da hora do aparelho em horas (para detetar o sono de trabalhadores por turnos)</string>
+    <string name="miband2_prefs_dateformat">Formato da data</string>
+    <string name="dateformat_time">Hora</string>
+    <string name="dateformat_date_time">Hor谩rio &amp; data</string>
+    <string name="mi2_prefs_goal_notification">Notifica莽茫o de objectivo</string>
+    <string name="mi2_prefs_goal_notification_summary">A pulseira vai vibrar quando o objectivo di谩rio de passos for atingido</string>
+    <string name="mi2_prefs_display_items">Items do ecr茫</string>
+    <string name="mi2_prefs_display_items_summary">Escolher os items a mostrar no ecr茫</string>
+    <string name="mi2_prefs_activate_display_on_lift">Ativar ecr茫 do aparelho quando o levantar</string>
+    <string name="mi2_prefs_rotate_wrist_to_switch_info">Rodar o pulso para mudar de ecr茫</string>
+    <string name="mi2_prefs_do_not_disturb">N茫o incomodar</string>
+    <string name="mi2_prefs_do_not_disturb_summary">A pulseira n茫o recebe notifica莽玫es enquanto activo</string>
+    <string name="mi2_prefs_do_not_disturb_start">Hora de in铆cio</string>
+    <string name="mi2_prefs_do_not_disturb_end">Hora de fim</string>
+    <string name="mi2_prefs_inactivity_warnings">Avisos de inactividade</string>
+    <string name="mi2_prefs_inactivity_warnings_summary">A pulseira ir谩 vibrar quando estiver inactivo durante algum tempo</string>
+    <string name="mi2_prefs_inactivity_warnings_threshold">Limite de inactividade (em minutos)</string>
+    <string name="mi2_prefs_inactivity_warnings_dnd_summary">Desativa avisos de inatividade por um intervalo de tempo</string>
+    <string name="FetchActivityOperation_about_to_transfer_since">Prestes a transferir dados desde %1$s</string>
+    <string name="waiting_for_reconnect">Aguardando para reconectar</string>
+    <string name="activity_prefs_about_you">Sobre voc锚</string>
+    <string name="activity_prefs_year_birth">Ano de nascimento</string>
+    <string name="activity_prefs_gender">G茅nero</string>
+    <string name="activity_prefs_height_cm">Altura em cm</string>
+    <string name="activity_prefs_weight_kg">Peso em kg</string>
+    <string name="authenticating">Autenticando</string>
+    <string name="authentication_required">Autentica莽茫o necess谩ria</string>
+    <string name="appwidget_text">Zzz</string>
+    <string name="add_widget">Adicionar widget</string>
+    <string name="activity_prefs_sleep_duration">Preferir dura莽茫o de sono em horas</string>
+    <string name="appwidget_setting_alarm">Alarme definido para as %1$02d:%2$02d</string>
+    <string name="device_hw">Hardware revis茫o: %1$s</string>
+    <string name="device_fw">Firmware vers茫o: %1$s</string>
+    <string name="error_creating_directory_for_logfiles">Erro ao criar o diret贸rio para os ficheiros de registo: %1$s</string>
+    <string name="DEVINFO_HR_VER">"FC: "</string>
+    <string name="updatefirmwareoperation_update_in_progress">Instalando firmware</string>
+    <string name="updatefirmwareoperation_firmware_not_sent">Firmware n茫o enviado</string>
+    <string name="charts_legend_heartrate">Frequ锚ncia card铆aca</string>
+    <string name="live_activity_heart_rate">Frequ锚ncia card铆aca</string>
+    <string name="pref_title_pebble_health_store_raw">Armazenar registo n茫o processado na base de dados</string>
+    <string name="pref_summary_pebble_health_store_raw">Armazena os dados \"como est茫o\", aumentando o uso do banco de dados para permitir posterior interpreta莽茫o.</string>
+    <string name="action_db_management">Gest茫o de dados</string>
+    <string name="title_activity_db_management">Gest茫o de dados</string>
+    <string name="activity_db_management_import_export_explanation">As opera莽玫es de exporta莽茫o/importa莽茫o usam o seguinte caminho (veja abaixo) para um diret贸rio no seu aparelho. Este diret贸rio 茅 acess铆vel a outras apps Android e o seu computador. Observe que este diret贸rio e todos os ficheiros que cont锚m s茫o apagados se desinstalar o Gadgetbridge. Os dados incluem:
+\n Export_preference - configura莽玫es globais
+\n Export_preference_MAC - configura莽玫es espec铆ficas do aparelho
+\n Gadgetbridge - banco de dados de aparelhos e atividades
+\n Gadgetbridge_date - banco de dados exportado numa data
+\n *.gpx - grava莽玫es deGPS
+\n *.log - ficheiros de log
+\nEspere encontrar os seus ficheiros exportados (ou p么r os ficheiros que deseja importar) l谩:</string>
+    <string name="activity_db_management_merge_old_title">Apagar o banco de dados legado</string>
+    <string name="dbmanagementactivvity_cannot_access_export_path">N茫o foi poss铆vel aceder ao caminho de exporta莽茫o. Por favor contacte os programadores.</string>
+    <string name="dbmanagementactivity_exported_to">Exportado para: %1$s</string>
+    <string name="dbmanagementactivity_error_exporting_db">Erro ao exportar a Base de Dados: %1$s</string>
+    <string name="dbmanagementactivity_import_data_title">Importar dados?</string>
+    <string name="dbmanagementactivity_overwrite_database_confirmation">Realmente sobrescrever os dados atuais? Todos os seus dados atuais de atividade (se houver) e prefer锚ncias ser茫o substitu铆dos.</string>
+    <string name="dbmanagementactivity_import_successful">Importado.</string>
+    <string name="dbmanagementactivity_error_importing_db">Erro ao importar a Base de Dados: %1$s</string>
+    <string name="dbmanagementactivity_delete_activity_data_title">Apagar os dados de atividade?</string>
+    <string name="dbmanagementactivity_really_delete_entire_db">Apagar realmente toda a base de dados\? Todos os seus dados de atividade e informa莽茫o sobre os seus aparelhos ir谩 perder-se.</string>
+    <string name="dbmanagementactivity_database_successfully_deleted">Dados apagados.</string>
+    <string name="dbmanagementactivity_db_deletion_failed">N茫o foi poss铆vel apagar a base de dados.</string>
+    <string name="dbmanagementactivity_delete_old_activity_db">Apagar os dados de atividade antigos?</string>
+    <string name="dbmanagementactivity_delete_old_activitydb_confirmation">Apagar realmente a base de dados antiga? Dados de atividade n茫o importados ir茫o perder-se.</string>
+    <string name="dbmanagementactivity_old_activity_db_successfully_deleted">Os dados de atividades antigos foram apagados.</string>
+    <string name="dbmanagementactivity_old_activity_db_deletion_failed">N茫o foi poss铆vel apagar a atividade antiga da base de dados.</string>
+    <string name="dbmanagementactivity_overwrite">Escrever por cima</string>
+    <string name="Cancel">Cancelar</string>
+    <string name="Delete">Apagar</string>
+    <!--Strings related to Vibration Activity-->
+    <string name="title_activity_vibration">Vibra莽茫o</string>
+    <!--Strings related to Pebble Pairing Activity-->
+    <string name="title_activity_pebble_pairing">Pareamento do Pebble</string>
+    <string name="pebble_pairing_hint">Um di谩logo de pareamento vai aparecer no seu aparelho Android. Se isso n茫o acontecer, procure na gaveta de notifica莽茫o e aceite o pedido de pareamento. Tamb茅m aceite-o no seu Pebble em seguida.</string>
+    <string name="weather_notification_label">Garanta que este tema se encontra ativado na App de Meteorologia para obter informa莽茫o meteorol贸gica atualizada no seu Pebble.
+\n
+\nN茫o 茅 necess谩ria qualquer configura莽茫o aqui.
+\n
+\nPode ativar a app nativa de meteorologia do seu Pebble atrav茅s da gest茫o de apps.
+\n
+\nTemas de rel贸gio ir茫o apresentar a informa莽茫o meteorol贸gica automaticamente.</string>
+    <string name="pref_title_setup_bt_pairing">Ativar o emparelhamento Bluetooth</string>
+    <string name="pref_summary_setup_bt_pairing">Desative isto caso tenha problemas na liga莽茫o</string>
+    <string name="unit_metric">M茅trico</string>
+    <string name="unit_imperial">Imperial</string>
+    <string name="timeformat_24h">24H</string>
+    <string name="timeformat_am_pm">Manh茫/Tarde</string>
+    <string name="pref_screen_notification_profile_alarm_clock">Alarme</string>
+    <string name="StringUtils_sender">(%1$s)</string>
+    <string name="find_device_you_found_it">Encontrado!</string>
+    <string name="miband2_prefs_timeformat">Mi2: Formato da hora</string>
+    <string name="mi2_fw_installhandler_fw53_hint">Deve instalar a vers茫o %1$s antes de instalar este firmware!</string>
+    <string name="mi2_enable_text_notifications">Notifica莽玫es de texto</string>
+    <string name="mi2_enable_text_notifications_summary"><![CDATA[Necessita de firmware >= 1.0.1.28 e Mili_pro.ft* instalado.]]></string>
+    <string name="off">Desligado</string>
+    <string name="mi2_dnd_off">Desligado</string>
+    <string name="mi2_dnd_automatic">Autom谩tico (dete莽茫o de sono)</string>
+    <string name="mi2_dnd_scheduled">Agendado (intervalo de tempo)</string>
+    <string name="discovery_attempting_to_pair">A tentar emparelhar com %1$s</string>
+    <string name="discovery_enable_bluetooth">Ative o Bluetooth para encontrar os aparelhos.</string>
+    <string name="discovery_pair_title">Emparelhar com %1$s?</string>
+    <string name="discovery_yes_pair">Emparelhar</string>
+    <string name="discovery_dont_pair">N茫o emparelhar</string>
+    <string name="action_donate">Doar</string>
+    <string name="controlcenter_connect">Conectar鈥�</string>
+    <string name="title_activity_calblacklist">Calend谩rios bloqueados</string>
+    <string name="fw_upgrade_notice_amazfitbip">Est谩 prestes a instalar o firmware %s na sua Amazfit Bip.
+\n
+\nPor favor garanta que instala o firmware .fw, o ficheiro .res e finalmente o ficheiro .gps. O seu rel贸gio vai reiniciar depois de instalar o ficheiro .fw.
+\n
+\nNota: N茫o necessita de instalar .res e .gps se os ficheiros forem os mesmos da 煤ltima instala莽茫o.
+\n
+\nCONTINUE A SEU PR脫PRIO RISCO!</string>
+    <string name="fw_upgrade_notice_amazfitcor">Est谩 prestes a instalar o firmware %s no seu Amazfit Cor.
+\n
+\nCertifique-se de instalar o ficheiro .fw e depois disso o ficheiro .res. A sua pulseira ser谩 reiniciada ap贸s a instala莽茫o do ficheiro .fw.
+\n
+\nNota: n茫o precisa instalar .res se for exatamente o mesmo que o instalado anteriormente.
+\n
+\nPROSSIGA AO SEU PR脫PRIO RISCO!</string>
+    <string name="pref_title_charts_swipe">Ativar deslizar para esquerda/direita nos gr谩ficos de atividade</string>
+    <string name="pref_title_weather">Meteorologia</string>
+    <string name="pref_title_weather_location">Localiza莽茫o clim谩tica (para provedor de clina do LineageOS)</string>
+    <string name="pref_blacklist_calendars">Bloquear Calend谩rios</string>
+    <string name="controlcenter_calibrate_device">Calibrar aparelho</string>
+    <string name="blacklist_all_for_notifications">Bloquear todas as notifica莽玫es</string>
+    <string name="whitelist_all_for_notifications">Desbloquear todas as notifica莽玫es</string>
+    <string name="fw_upgrade_notice_miband3">Est谩 prestes a instalar o firmware %s na sua Mi Band 3.
+\n
+\nCertifique-se de instalar o ficheiro .fw e depois o ficheiro .res. A sua pulseira ser谩 reiniciada ap贸s instalar o ficheiro .fw.
+\n
+\nNota: N茫o precisa instalar .res se esse ficheiro 茅 exatamente o mesmo instalado anteriormente.
+\n
+\nPROSSIGA POR SUA CONTA PR脫PRIA E RISCO!</string>
+    <string name="pref_title_enable_calendar_sync">Sincronizar calend谩rio</string>
+    <string name="pref_title_pebble_gatt_clientonly">Apenas cliente GATT</string>
+    <string name="pref_summary_pebble_gatt_clientonly">Usado apenas para o Pebble 2, experimente se estiver a ter problemas de liga莽茫o</string>
+    <string name="prefs_screen_orientation">Orienta莽茫o do ecr茫</string>
+    <string name="pref_header_auto_export">Exporta莽茫o autom谩tica</string>
+    <string name="pref_title_auto_export_enabled">Exporta莽茫o autom谩tica ativa</string>
+    <string name="pref_title_auto_export_location">Exportar localiza莽茫o</string>
+    <string name="pref_title_auto_export_interval">Intervalo de exporta莽茫o</string>
+    <string name="pref_summary_auto_export_interval">Exportar a cada %d horas</string>
+    <string name="horizontal">Horizontal</string>
+    <string name="vertical">Vertical</string>
+    <string name="watch9_pairing_tap_hint">Quando o seu rel贸gio vibrar, agite-o ou carregue no bot茫o.</string>
+    <string name="interval_one_minute">uma vez por minuto</string>
+    <string name="interval_five_minutes">a cada 5 minutos</string>
+    <string name="interval_ten_minutes">a cada 10 minutos</string>
+    <string name="interval_thirty_minutes">a cada 30 minutos</string>
+    <string name="interval_one_hour">1 vez por hora</string>
+    <string name="stats_title">Zonas de velocidade</string>
+    <string name="stats_x_axis_label">Minutos totais</string>
+    <string name="stats_y_axis_label">Passos por minuto</string>
+    <string name="notif_export_failed_title">Exporta莽茫o da base de dados falhou. Por favor, verifique as configura莽玫es.</string>
+    <string name="mi2_prefs_button_actions">A莽玫es do bot茫o</string>
+    <string name="mi2_prefs_button_actions_summary">Especificar a莽玫es ao carregar no bot茫o</string>
+    <string name="mi3_prefs_band_screen_unlock">Desbloqueio do ecr茫 da pulseira</string>
+    <string name="automatic">Autom谩tico</string>
+    <string name="simplified_chinese">Chin锚s Simplificado</string>
+    <string name="traditional_chinese">Chin锚s Tradicional</string>
+    <string name="english">Ingl锚s</string>
+    <string name="norwegian_bokmal">Bokm氓l Noruegu锚s</string>
+    <string name="spanish">Espanhol</string>
+    <string name="russian">Russo</string>
+    <string name="german">Alem茫o</string>
+    <string name="italian">Italiano</string>
+    <string name="french">Franc锚s</string>
+    <string name="polish">Polaco</string>
+    <string name="korean">Coreano</string>
+    <string name="japanese">Japon锚s</string>
+    <string name="dbmanagementactivity_error_exporting_shared">Erro ao exportar configura莽茫o: %1$s</string>
+    <string name="dbmanagementactivity_error_importing_shared">Erro ao importar configura莽茫o: %1$s</string>
+    <string name="_pebble_watch_mute">Silenciar</string>
+    <string name="_pebble_watch_reply">Responder</string>
+    <string name="activity_type_not_measured">N茫o medido</string>
+    <string name="activity_type_activity">Atividade</string>
+    <string name="activity_type_light_sleep">Sono leve</string>
+    <string name="activity_type_deep_sleep">Sono profundo</string>
+    <string name="activity_type_not_worn">Aparelho n茫o usado</string>
+    <string name="activity_type_running">Corrida</string>
+    <string name="activity_type_walking">Caminhada</string>
+    <string name="activity_type_swimming">Nata莽茫o</string>
+    <string name="activity_type_unknown">Atividade desconhecida</string>
+    <string name="activity_summaries">Atividades de desporte</string>
+    <string name="activity_type_biking">Ciclismo</string>
+    <string name="activity_type_treadmill">Passadeira</string>
+    <string name="select_all">Selecionar todos</string>
+    <string name="share">Partilhar</string>
+    <string name="kind_firmware">Firmware</string>
+    <string name="kind_font">Fonte de texto</string>
+    <string name="kind_gps">Firmware GPS</string>
+    <string name="kind_gps_almanac">Almanac GPS</string>
+    <string name="kind_gps_cep">Corre莽茫o de erro GPS</string>
+    <string name="kind_resources">Recursos</string>
+    <string name="devicetype_unknown">Aparelho desconhecido</string>
+    <string name="devicetype_test">Aparelho de Teste</string>
+    <string name="devicetype_pebble">Pebble</string>
+    <string name="notification_channel_name">Geral</string>
+    <string name="menuitem_status">Estado</string>
+    <string name="menuitem_notifications">Notifica莽玫es</string>
+    <string name="menuitem_activity">Hist贸rico de treino</string>
+    <string name="menuitem_weather">Meteorologia</string>
+    <string name="menuitem_alarm">Alarme</string>
+    <string name="menuitem_timer">Temporizador</string>
+    <string name="menuitem_compass">B煤ssola</string>
+    <string name="menuitem_settings">Configura莽玫es</string>
+    <string name="menuitem_alipay">Alipay</string>
+    <string name="menuitem_music">M煤sica</string>
+    <string name="menuitem_more">Mais</string>
+    <string name="watch9_time_minutes">Minutos:</string>
+    <string name="watch9_time_hours">Horas:</string>
+    <string name="watch9_time_seconds">Segundos:</string>
+    <string name="watch9_calibration_button">Calibrar</string>
+    <string name="title_activity_watch9_pairing">Emparelhamento do Watch 9</string>
+    <string name="title_activity_watch9_calibration">Calibra莽茫o do Watch 9</string>
+    <!-- LED Color -->
+    <string name="preferences_led_color">Cor do LED</string>
+    <!-- FM transmitters -->
+    <string name="preferences_fm_frequency">Frequ锚ncia FM</string>
+    <string name="pref_invalid_frequency_title">Frequ锚ncia inv谩lida</string>
+    <string name="pref_invalid_frequency_message">Por favor introduza uma frequ锚ncia entre 87.5 e 108.0</string>
+    <string name="debugactivity_really_factoryreset_title">Repor as configura莽玫es de f谩brica\?</string>
+    <string name="pref_title_notifications_timeout">Tempo m铆nimo entre notifica莽玫es</string>
+    <string name="pref_title_rtl">Da direita para a esquerda</string>
+    <string name="pref_summary_rtl">Ative isto se o seu aparelho pode mostrar idiomas que se escrevem da direita para a esquerda</string>
+    <string name="pref_rtl_max_line_length">Comprimento m谩ximo da linha da direita para a esquerda</string>
+    <string name="pref_rtl_max_line_length_summary">Alonga ou encurta as linhas da direita para a esquerda em que o texto 茅 cortado</string>
+    <string name="pref_summary_enable_calendar_sync">Enviar eventos do calend谩rio para a linha do tempo</string>
+    <string name="pref_title_pebble_enable_bgjs">Ativar JS em segundo plano</string>
+    <string name="pref_summary_pebble_enable_bgjs">Quando ativado, permite que as mostradores mostrem o clima, informa莽玫es da bateria, etc.</string>
+    <string name="prefs_title_heartrate_measurement_interval">Medi莽茫o di谩ria dos batimentos card铆acos</string>
+    <string name="mi2_prefs_button_press_count">Contagem de acionamento do bot茫o</string>
+    <string name="mi2_prefs_button_press_count_summary">Quantidade de pressionamentos de bot茫o para acionar um Evento 1. A mesma quantidade de pressionamentos subsequentes cria o Evento 2 e assim por diante.</string>
+    <string name="mi2_prefs_button_press_broadcast">Mensagem para enviar</string>
+    <string name="mi2_prefs_button_press_broadcast_summary">Mensagem de transmiss茫o enviada com o evento. O par芒metro `button_id` 茅 adicionado automaticamente a cada mensagem.</string>
+    <string name="mi2_prefs_button_action">Ativar bot茫o de a莽茫o</string>
+    <string name="mi2_prefs_button_action_vibrate">Ativar vibra莽茫o da pulseira</string>
+    <string name="debugactivity_really_factoryreset">Efetuar uma reposi莽茫o de f谩brica apagar todos os dados dos aparelhos ligados (caso seja suportado). Al茅m disso, aparelhos Xiaomi/Huami mudam os endere莽os MAC, aparecendo como aparelhos novos para o Gadgetbridge.</string>
+    <string name="mi2_prefs_button_press_broadcast_default_value" translatable="false">nodomain.freeyourgadget.gadgetbridge.ButtonPressed</string>
+    <plurals name="widget_alarm_target_hours">
+        <item quantity="one">%d hora</item>
+        <item quantity="many">%d horas</item>
+        <item quantity="other">%d horas</item>
+    </plurals>
+    <string name="activity_db_management_empty_db_warning">Aten莽茫o! Pressionando este bot茫o, ir谩 limpar o seu banco de dados e come莽ar do zero.</string>
+    <string name="activity_db_management_empty_DB">Esvaziar a Base de Dados</string>
+    <string name="activity_DB_test_export_message">Exportar a base de dados鈥�</string>
+    <string name="activity_db_management_autoexport_explanation">O local de exporta莽茫o autom谩tica da base de dados foi definido para:</string>
+    <string name="menuitem_worldclock">Rel贸gio Mundial</string>
+    <string name="pref_title_weather_summary">Usado para o provedor do clima do LineageOS, outras vers玫es do Android precisam usar uma app como o \"Notifica莽茫o de clima\". Encontrar谩 mais informa莽玫es no wiki do Gadgetbridge.</string>
+    <string name="about_additional_contributions">Muito obrigado a todos os contribuidores n茫o listados aqui por contribui莽茫o com c贸digo, transdu莽茫o, suporte, ideias, motiva莽茫o, relat贸rios de erros, dinheiro鈥� 鉁�</string>
+    <string name="about_additional_device_support">Suporte a aparelhos adicionais</string>
+    <string name="about_contributors">Contribuidores</string>
+    <string name="about_core_team_title">Equipa Core (na ordem da primeira contribui莽茫o de c贸digo)</string>
+    <string name="about_description_generic">Um substituto livre, protegido por copyleft, sem acesso a nuvem para as apps Android de c贸digo fechado dos fornecedores de gadgets.</string>
+    <string name="about_activity_title_generic">Sobre o Gadgetbridge</string>
+    <string name="about_title">Sobre</string>
+    <string name="fw_upgrade_notice_amazfit_trex">Est谩 prestes a instalar o firmware %s no seu Amazit T-Rex. 
+\n 
+\nCertifique-se de instalar o ficheiro .fw, depois o ficheiro .res e finalmente o ficheiro .gps. Seu rel贸gio ser谩 reiniciado ap贸s a instala莽茫o do ficheiro .fw. 
+\n 
+\nNota: n茫o precisa instalar .res e .gps se esse ficheiro for exatamente o mesmo que os instalados anteriormente. 
+\n 
+\nPROSSIGA POR SUA CONTA E RISCO!</string>
+    <string name="devicetype_amazfit_trex">Amazfit T-Rex</string>
+    <string name="controlcenter_set_alias">Definir Alias</string>
+    <string name="hr_widget_last_notification">脷ltima notifica莽茫o</string>
+    <string name="menuitem_unknown">Desconhecido</string>
+    <string name="bip_prefs_shotcuts_summary">Escolha os atalhos no ecr茫 da pulseira</string>
+    <string name="bip_prefs_shortcuts">Atalhos</string>
+    <string name="czesh">Tcheco</string>
+    <string name="swedish">Sueco</string>
+    <string name="hebrew">Hebreu</string>
+    <string name="greek">Grego</string>
+    <string name="pref_theme_system">Sistema</string>
+    <string name="hungarian">H煤ngaro</string>
+    <string name="romanian">Romeno</string>
+    <string name="pref_title_device_spec_settings_show_raw_graph">Mostrar dados RAW no gr谩fico de atividade</string>
+    <string name="devicetype_watchxplus">Watch X Plus</string>
+    <string name="devicetype_watchx">Watch X</string>
+    <string name="power_mode_watch">Somente o rel贸gio</string>
+    <string name="power_mode_saving">Economia de energia</string>
+    <string name="power_mode_normal">Normal</string>
+    <string name="power_mode_title">Modo de energia do rel贸gio</string>
+    <string name="prefs_sensors_button_bp_calibration_sum">Pressione aqui para iniciar a calibra莽茫o</string>
+    <string name="prefs_sensors_button_bp_calibration">Calibra莽茫o</string>
+    <string name="pref_sensors_bp_calibration_high">Press茫o Arterial SIST脫LICA (alta)</string>
+    <string name="pref_sensors_bp_calibration_low">Press茫o Arterial DIAST脫LICA (baixa)</string>
+    <string name="pref_sensors_bp_calibration">Calibra莽茫o da Press茫o Arterial</string>
+    <string name="pref_title_sensors_altitude">Calibra莽茫o de altitude</string>
+    <string name="pref_header_sensors_calibration">Calibra莽茫o do Sensor</string>
+    <string name="pref_summary_device_spec_settings_title_force_time">For莽a a sincroniza莽茫o autom谩tica do tempo na reconex茫o. Os ponteiros anal贸gicos podem mostrar tempo incorreto!</string>
+    <string name="pref_title_device_spec_settings_force_time">For莽ar sincronizar o tempo</string>
+    <string name="pref_header_device_spec_settings">Configura莽玫es do aparelho</string>
+    <string name="pref_summary_notifications_and_calls_title_shake_reject">Duplica a a莽茫o do bot茫o do rel贸gio</string>
+    <string name="prefs_notifications_and_calls_shake_reject">Sacudir pulso ignorar/rejeitar chamada</string>
+    <string name="pref_summary_notifications_and_calls_title_reject">Desligado - ignorar, Ligado - rejeitar</string>
+    <string name="prefs_notifications_and_calls_reject">Bot茫o ignorar/rejeitar chamada</string>
+    <string name="pref_header_notifications_and_calls_callhandling">Tratamento de chamadas</string>
+    <string name="pref_title_notifications_and_calls_repeat_on_misscall">Repetir por X minutos</string>
+    <string name="pref_summary_notifications_and_calls_enable_misscall">Repete a cada minuto</string>
+    <string name="pref_notifications_and_calls_enable_misscall">Notificar chamada perdida</string>
+    <string name="prefs_notifications_and_calls_continious_ring">Notifica莽茫o durante o toque do telefone</string>
+    <string name="pref_title_notifications_and_calls_repeat_on_call">Repetir notifica莽茫o de chamadas</string>
+    <string name="pref_header_notifications_and_calls">Notifica莽玫es e Chamadas</string>
+    <string name="title_activity_LenovoWatch_calibration">Calibra莽茫o de Watch X Plus</string>
+    <string name="menuitem_workout">Treino</string>
+    <string name="menuitem_eventreminder">Lembrete de eventos</string>
+    <string name="menuitem_hr">Frequ锚ncia card铆aca</string>
+    <string name="menuitem_pai">PAI</string>
+    <string name="pref_qhybrid_save_raw_activity_files">Gravar ficheiros n茫o tratados de atividade</string>
+    <string name="pref_qhybrid_title_widget_draw_circles">Desenhar c铆rculos de widget</string>
+    <string name="pref_header_auto_fetch">Coletar automaticamente</string>
+    <string name="pref_title_relax_firmware_checks">Ative isto se deseja instalar um firmware n茫o destinado para o seu aparelho (a seu pr贸prio risco)</string>
+    <string name="pref_summary_relax_firmware_checks">Verifica莽玫es relaxadas de firmware</string>
+    <string name="devicetype_amazfit_bips">Amazfit Bip S</string>
+    <string name="pref_title_vibration_strength">Intensidade da vibra莽茫o</string>
+    <string name="hr_appname_commute">Comutar</string>
+    <string name="hr_appname_stopwatch">Cron么metro</string>
+    <string name="hr_appname_workout">Treino</string>
+    <string name="hr_appname_wellness">Bem-estar</string>
+    <string name="pref_summary_custom_deviceicon">Mostra o 铆cone de notifica莽茫o Android espec铆fico de um aparelho em vez do 铆cone do Gadgetbridge quando conectado</string>
+    <string name="pref_title_custom_deviceicon">Mostrar 铆cone de notifica莽茫o espec铆fica do aparelho</string>
+    <string name="notification_channel_high_priority_name">Alta prioridade</string>
+    <string name="find_my_phone_notification">Localizar meu telefone</string>
+    <string name="pref_summary_force_white_color_scheme">脷til se seu rel贸gio possui ponteiros escuros</string>
+    <string name="pref_title_force_white_color_scheme">For莽ar preto no esquema de cores branco</string>
+    <string name="find_lost_device_you_found_it">Encontrei!</string>
+    <string name="hr_widget_nothing">Nada</string>
+    <string name="hr_widget_weather">Clima</string>
+    <string name="hr_widget_battery">Bateria</string>
+    <string name="hr_widget_calories">Calorias</string>
+    <string name="hr_widget_active_minutes">Minutos ativo</string>
+    <string name="hr_widget_date">Data</string>
+    <string name="hr_widget_steps">Passos</string>
+    <string name="hr_widget_heart_rate">Frequ锚ncia card铆aca</string>
+    <string name="pref_title_sync_caldendar">Sincronizar eventos de calend谩rio</string>
+    <string name="pref_summary_sync_calendar">Possibilita alertas de calend谩rio, mesmo quando desconectado</string>
+    <string name="pref_summary_allow_high_mtu">Aumenta velocidade de transfer锚ncia, mas pode n茫o funcionar em alguns aparelhos Android.</string>
+    <string name="pref_title_allow_high_mtu">Permitir MTU alto</string>
+    <string name="devicetype_itag">iTag</string>
+    <string name="error_no_location_access">Acesso 脿 localiza莽茫o deve ser concedido e ativado para a varredura funcionar adequadamente</string>
+    <string name="alarm_snooze">Adiar</string>
+    <string name="prefs_button_long_press_action_selection_title">A莽茫o do bot茫o quando pressionado longo</string>
+    <string name="prefs_button_variable_actions">Configura莽玫es detalhados de pressionamento de bot茫o</string>
+    <string name="prefs_button_triple_press_action_selection_title">A莽茫o do Evento 3</string>
+    <string name="prefs_button_double_press_action_selection_title">A莽茫o do Evento 2</string>
+    <string name="prefs_button_single_press_action_selection_title">A莽茫o do Evento 1</string>
+    <string name="devicetype_y5">Y5</string>
+    <string name="devicetype_banglejs">Bangle.js</string>
+    <string name="pref_summary_disable_new_ble_scanning">Marque essa op莽茫o se seu aparelho n茫o pode ser encontrado durante descobertas</string>
+    <string name="pref_disable_new_ble_scanning">Desativar nova varredura de BLE</string>
+    <string name="qhybrid_offset_time_by">deslocar tempo por</string>
+    <string name="qhybrid_changes_delay_prompt">a altera莽茫o pode levar alguns segundos鈥�</string>
+    <string name="qhybrid_offset_timezone">deslocar fuso hor谩rio por</string>
+    <string name="qhybrid_buttons_overwrite_error">Erro ao sobrescrever bot玫es</string>
+    <string name="qhybrid_buttons_overwrite_success">Bot玫es sobrescritos</string>
+    <string name="qhybrid_prompt_million_steps">Defina a contagem de passos para um milh茫o para ativ谩-lo.</string>
+    <string name="qhybrid_use_activity_hand_as_notification_counter">usar m茫o de atividade como contador da notifica莽茫o</string>
+    <string name="qhybrid_overwrite_buttons">sobrescrever bot玫es</string>
+    <string name="qhybrid_second_timezone_offset_relative_to_utc">segundo deslocamento de fuso hor谩rio relativo a UTC</string>
+    <string name="qhybrid_time_shift">deslocamento de tempo</string>
+    <string name="qhybrid_goal_in_steps">Meta em passos</string>
+    <string name="qhybrid_vibration_strength">for莽a da vibra莽茫o:</string>
+    <string name="watch_not_connected">Rel贸gio n茫o conectado</string>
+    <string name="preferences_qhybrid_settings">Configura莽玫es de Q Hybrid</string>
+    <string name="devicetype_qhybrid">Fossil Q Hybrid</string>
+    <string name="fw_upgrade_notice_amazfitgts">Est谩 prestes a instalar o firmware %s no seu Amazfit GTS. 
+\n 
+\nCertifique-se de instalar o ficheiro .fw, depois o ficheiro .res e finalmente o ficheiro .gps. Seu rel贸gio ser谩 reiniciado ap贸s a instala莽茫o do ficheiro .fw. 
+\n 
+\nNota: n茫o precisa instalar .res e .gps se esses ficheiros forem exatamente os mesmos que os instalados anteriormente. 
+\n 
+\nPROSSIGA AO SEU PR脫PRIO RISCO!</string>
+    <string name="devicetype_amazfit_gts">Amazfit GTS</string>
+    <string name="pref_chart_sleep_rolling_24_off">Meio-dia ao meio-dia</string>
+    <string name="pref_chart_sleep_rolling_24_on">脷ltimas 24 horas</string>
+    <string name="pref_title_chart_sleep_rolling_24_hour">Intervalo de sono</string>
+    <string name="pref_title_chart_heartrate_color">Cor da frequ锚ncia card铆aca</string>
+    <string name="pref_chart_heartrate_color_orange">Laranja</string>
+    <string name="pref_chart_heartrate_color_red">Vermelho</string>
+    <string name="fw_upgrade_notice_amazfitgtr">Est谩 prestes a instalar o firmware %s no seu Amazfit GTR. 
+\n 
+\nCertifique-se de instalar o ficheiro .fw, depois o ficheiro .res e finalmente o ficheiro .gps. Seu rel贸gio ser谩 reiniciado ap贸s a instala莽茫o do ficheiro .fw. 
+\n 
+\nNota: n茫o precisa instalar .res e .gps se esses ficheiros forem exatamente os mesmos que os instalados anteriormente. 
+\n 
+\nPROSSIGA AO SEU PR脫PRIO RISCO!</string>
+    <string name="devicetype_amazfit_gtr">Amazfit GTR</string>
+    <string name="fw_upgrade_notice_amazfitbip_lite">Est谩 prestes a instalar o firmware %s no seu Amazit Bip Lite. 
+\n 
+\nCertifique-se de instalar o ficheiro .fw e depois o ficheiro .res. Seu rel贸gio ser谩 reiniciado ap贸s a instala莽茫o do ficheiro .fw. 
+\n 
+\nNota: n茫o precisa instalar .res se esse ficheiro for exatamente o mesmo que os instalados anteriormente. 
+\n 
+\nPROSSIGA POR SUA CONTA E RISCO!</string>
+    <string name="discovery_need_to_enter_authkey">Este aparelho precisa da sua chave de autentica莽茫o secreta, pressione longo no aparelho para inseri-lo. Leia o wiki.</string>
+    <string name="maximum_duration">Dura莽茫o</string>
+    <string name="prefs_find_phone_duration">Dura莽茫o do toque sonoro em segundos</string>
+    <string name="prefs_find_phone_summary">Use sua pulseira para reproduzir o toque sonoro do seu telem贸vel.</string>
+    <string name="prefs_enable_find_phone">Ativar \"Encontrar telefone\"</string>
+    <string name="prefs_find_phone">Encontrar telefone</string>
+    <string name="devicetype_amazfit_bip_lite">Amazfit Bip Lite</string>
+    <string name="devicetype_makibes_hr3">Makibes HR3</string>
+    <string name="preferences_makibes_hr3_settings">Configura莽玫es de Makibes HR3</string>
+    <string name="activity_error_no_app_for_gpx">Para visualizar o rastreamento de atividade, instale uma app que consegue manipular ficheiros GPX.</string>
+    <string name="pref_display_add_device_fab_off">Vis铆vel apenas se nenhum aparelho estiver adicionado</string>
+    <string name="pref_display_add_device_fab_on">Sempre vis铆vel</string>
+    <string name="pref_display_add_device_fab">Conectar novo bot茫o de aparelho</string>
+    <string name="widget_1_hour">1 hora</string>
+    <string name="widget_20_minutes">20 minutos</string>
+    <string name="widget_10_minutes">10 minutos</string>
+    <string name="widget_5_minutes">5 minutos</string>
+    <string name="widget_set_alarm_after">Definir alarme ap贸s:</string>
+    <string name="widget_listing_label">Estado e Alarmes</string>
+    <string name="appwidget_sleep_alarm_widget_label">Alarme de Sono</string>
+    <string name="activity_db_management_exportimport_label">Exportar e Importar</string>
+    <string name="activity_DB_empty_button">Esvaziar banco de dados</string>
+    <string name="activity_DB_delete_legacy_button">Apagar BD antigo</string>
+    <string name="activity_DB_test_export_button">Executar AutoExporta莽茫o Agora</string>
+    <string name="activity_DB_import_button">Importar dados</string>
+    <string name="activity_DB_ExportButton">Exportar dados</string>
+    <string name="activity_db_management_autoexport_label">AutoExportar</string>
+    <string name="pref_summary_use_custom_font">Ative isto se o seu aparelho possui uma firmware de letra personalizada para suporte de emoji</string>
+    <string name="pref_title_use_custom_font">Usar letra personalizada</string>
+    <string name="pref_title_expose_hr">Acesso por terceiros a FC em tempo real</string>
+    <string name="pref_summary_expose_hr">Permite que outras aplica莽玫es acessem os dados de FC em tempo real enquanto Gadgetbridge est谩 conectado</string>
+    <string name="menuitem_nfc">NFC</string>
+    <string name="devicetype_mijia_lywsd02">Rel贸gio inteligente Mijia</string>
+    <string name="weeksleepchart_sleep_a_month">Sono por m锚s</string>
+    <string name="weekstepschart_steps_a_month">Passos por m锚s</string>
+    <string name="pref_charts_range_off">O intervalo de gr谩ficos est谩 definido para uma Semana</string>
+    <string name="pref_charts_range_on">O intervalo de gr谩ficos est谩 definido para um M锚s</string>
+    <string name="pref_title_charts_range">Intervalo de gr谩ficos</string>
+    <string name="pref_title_charts_average">Mostrar m茅dias nos gr谩ficos</string>
+    <string name="pref_header_charts">Configura莽玫es dos gr谩ficos</string>
+    <string name="average">M茅dia: %1$s</string>
+    <string name="prefs_hr_alarm_high">Limite alto</string>
+    <string name="prefs_hr_alarm_low">Limite baixo</string>
+    <string name="prefs_hr_alarm_activity">Alarme de frequ锚ncia card铆aca durante atividades desportivas</string>
+    <string name="fw_upgrade_notice_miband4">Est谩 prestes a instalar o firmware %s na sua Mi Band 4. 
+\n 
+\nCertifique-se de instalar o ficheiro .fw e depois o ficheiro .res. Sua pulseira vai reiniciar ap贸s instalar o ficheiro .fw. 
+\n 
+\nNota: n茫o tem que instalar .res se ele for exatamente o mesmo ficheiro que o instalado anteriormente. 
+\n 
+\nPROSSIGA POR SUA CONTA E RISCO!</string>
+    <string name="devicetype_miband4">Mi Band 4</string>
+    <string name="devicetype_amazfit_cor2">Amazfit Cor 2</string>
+    <string name="portuguese">Portugu锚s</string>
+    <string name="vietnamese">Vietnamita</string>
+    <string name="thai">Tailand锚s</string>
+    <string name="indonesian">Indon茅sio</string>
+    <string name="arabic">脕rabe</string>
+    <string name="ukrainian">Ucraniano</string>
+    <string name="turkish">Turco</string>
+    <string name="dutch">Holand锚s</string>
+    <string name="fw_upgrade_notice_amazfitcor2">Est谩 prestes a instalar o firmware %s no seu Amazfit Cor 2. 
+\n 
+\nCertifique-se de instalar o ficheiro .fw e depois o ficheiro .res. Sua pulseira ser谩 reiniciada ap贸s a instala莽茫o do ficheiro .fw. 
+\n 
+\nNota: n茫o precisa instalar .res se for exatamente o mesmo que o instalado anteriormente. 
+\n 
+\nPROSSIGA AO SEU PR脫PRIO RISCO! 
+\n 
+\nCOMPLETAMENTE N脙O TESTADO, PROVAVELMENTE PRECISA INSTALAR UM FIRMWARE BEATS_W SE O NOME DO SEU APARELHO FOR \"Amazfit Band 2\"</string>
+    <string name="devicetype_bfh16">BFH-16</string>
+    <string name="pref_summary_authkey">Altere a chave de autentica莽茫o para uma chave comum em todos os aparelhos Android dos quais gostaria de se conectar. A chave padr茫o anterior para todos aparelhos 茅 0123456789@ABCDE</string>
+    <string name="pref_title_authkey">Chave de autentica莽茫o</string>
+    <string name="title_activity_device_specific_settings">Configura莽玫es espec铆ficas do aparelho</string>
+    <string name="pref_title_support_voip_calls">Ativar chamadas de app VoIP</string>
+    <string name="devicetype_miscale2">Mi Scale 2</string>
+    <string name="activity_prefs_activetime_minutes">Meta di谩ria: tempo ativo em minutos</string>
+    <string name="activity_prefs_distance_meters">Meta di谩ria: dist芒ncia em metros</string>
+    <string name="activity_prefs_calories_burnt">Meta di谩ria: calorias queimadas</string>
+    <string name="interval_forty_five_minutes">a cada 45 minutos</string>
+    <string name="interval_fifteen_minutes">a cada 15 minutos</string>
+    <string name="pref_screen_notification_profile_anti_loss">Aviso contra perda</string>
+    <string name="pref_screen_notification_profile_low_power">Aviso de bateria baixa</string>
+    <string name="pref_screen_notification_profile_inactivity">Notifica莽茫o de inatividade</string>
+    <string name="pref_screen_notification_profile_calendar">Notifica莽茫o de calend谩rio</string>
+    <string name="pref_screen_notification_profile_missed_call">Notifica莽茫o de telefonema perdido</string>
+    <string name="zetime_signaling_vibrate_beep_once">Vibrar e bipar uma vez</string>
+    <string name="zetime_signaling_beep_twice">Bipar duas vezes</string>
+    <string name="zetime_signaling_beep_once">Bipar uma vez</string>
+    <string name="zetime_signaling_vibrate_twice">Vibrar duas vezes</string>
+    <string name="zetime_signaling_vibrate_once">Vibrar uma vez</string>
+    <string name="zetime_signaling_vibrate_beep">Vibra莽茫o e bipe cont铆nuos</string>
+    <string name="zetime_signaling_beep">Bipe cont铆nuo</string>
+    <string name="zetime_signaling_vibrate">Vibra莽茫o cont铆nua</string>
+    <string name="zetime_signaling_none">Sil锚ncio</string>
+    <string name="zetime_title_alarm_signaling">Definir tipo de sinaliza莽茫o para o alarme</string>
+    <string name="zetime_prefs_inactivity_su">Domingo</string>
+    <string name="zetime_prefs_inactivity_sa">S谩bado</string>
+    <string name="zetime_prefs_inactivity_fr">Sexta-feira</string>
+    <string name="zetime_prefs_inactivity_th">Quinta-feira</string>
+    <string name="zetime_prefs_inactivity_we">Quarta-feira</string>
+    <string name="zetime_prefs_inactivity_tu">Ter莽a-feira</string>
+    <string name="zetime_prefs_inactivity_mo">Segunda-feira</string>
+    <string name="zetime_prefs_inactivity_repetitions">Repeti莽玫es</string>
+    <string name="zetime_date_format_3">MM/DD/AA</string>
+    <string name="zetime_date_format_2">DD/MM/AA</string>
+    <string name="zetime_date_format_1">AA/MM/DD</string>
+    <string name="zetime_date_format">Formato da data</string>
+    <string name="zetime_calories_type_all">Calorias queimadas ativas e inativas</string>
+    <string name="zetime_calories_type_active">S贸 ativar calorias queimadas</string>
+    <string name="zetime_calories_type">Tipo de calorias</string>
+    <string name="zetime_handmove_display_summary">Gire seu pulso para ativar ou desativar o ecr茫.</string>
+    <string name="zetime_handmove_display">Movimento da m茫o</string>
+    <string name="zetime_activity_tracking_summary">Ao ativar o monitoramento de atividade, os passos ser茫o contados, etc.</string>
+    <string name="zetime_activity_tracking">Monitoramento de atividade</string>
+    <string name="zetime_analog_mode_handsandsteps">M茫os e passos</string>
+    <string name="zetime_analog_mode_hands">Apenas m茫os</string>
+    <string name="zetime_analog_mode">Modo anal贸gico</string>
+    <string name="activity_prefs_alarm_min_heart_rate">Frequ锚ncia card铆aca m铆nima</string>
+    <string name="activity_prefs_alarm_max_heart_rate">Frequ锚ncia card铆aca m谩xima</string>
+    <string name="zetime_heart_rate_alarm_enable">Ativar o alarme de frequ锚ncia card铆aca</string>
+    <string name="zetime_title_heart_rate_alarm_summary">O rel贸gio vai avisar-lo quando sua frequ锚ncia card铆aca exceder os limites.</string>
+    <string name="zetime_title_heart_rate_alarm">Alarme de frequ锚ncia card铆aca</string>
+    <string name="zetime_title_screentime">Dura莽茫o do ecr茫 ligado em segundos</string>
+    <string name="zetime_title_heartrate">Configura莽玫es de frequ锚ncia card铆aca</string>
+    <string name="zetime_title_settings">Configura莽玫es de Zetime</string>
+    <string name="prefs_disconnect_notification">Notifica莽茫o de desconex茫o</string>
+    <string name="activity_type_exercise">Exerc铆cio</string>
+    <string name="appwidget_not_connected">N茫o conectado, alarme n茫o definido.</string>
+    <string name="save_configuration">Gravar Configura莽茫o</string>
+    <string name="mode_configuration">Configura莽茫o do modo</string>
+    <string name="filter_mode">Modo de Filtro</string>
+    <string name="toast_notification_filter_words_empty_hint">Insira pelo menos uma palavra</string>
+    <string name="filter_submode_all">Todas as palavras</string>
+    <string name="filter_submode_at_least_one">Pelo menos uma das palavras</string>
+    <string name="filter_mode_blacklist">Bloquear quando contiver as palavras</string>
+    <string name="filter_mode_whitelist">Mostrar quando contiver as palavras</string>
+    <string name="filter_mode_none">N茫o filtrar</string>
+    <string name="toast_notification_filter_saved_successfully">Filtro de notifica莽茫o gravado</string>
+    <string name="edittext_notification_filter_words_hint">Insira as palavras desejadas, nova linha para cada uma</string>
+    <string name="title_activity_notification_filter">Filtro de notifica莽茫o</string>
+    <string name="devicetype_casiogb6900">Casio GB-6900</string>
+    <string name="live_activity_max_heart_rate">Frequ锚ncia card铆aca atual / m谩x: %1$d / %2$d</string>
+    <string name="no_data">Nenhuns dados</string>
+    <string name="overstep">Passos excedentes: %1$d</string>
+    <string name="lack_of_step">Car锚ncia de passos: %1$d</string>
+    <string name="overslept">Dormiu demais: %1$s</string>
+    <string name="lack_of_sleep">Car锚ncia de sono: %1$s</string>
+    <string name="language_and_region_prefs">Configura莽玫es de idioma e regi茫o</string>
+    <string name="preferences_rtl_settings">Suporte a RTL (direita para a esquerda)</string>
+    <string name="pref_summary_contextual_arabic">Ative isto para ter suporte a 谩rabe contextual</string>
+    <string name="pref_title_contextual_arabic">脕rabe contextual</string>
+    <string name="notif_battery_low_extended">%1$s com bateria baixa: %2$s</string>
+    <string name="notif_battery_low">%1$s com bateria baixa</string>
+    <string name="activity_prefs_chart_min_heart_rate">Frequ锚ncia card铆aca m铆nima</string>
+    <string name="activity_prefs_chart_max_heart_rate">Frequ锚ncia card铆aca m谩xima</string>
+    <string name="activity_prefs_charts">Configura莽玫es do gr谩fico</string>
+    <string name="devicetype_roidmi3">Roidmi 3</string>
+    <string name="devicetype_roidmi">Roidmi</string>
+    <string name="warning">Aten莽茫o!</string>
+    <string name="share_log_warning">Lembre-se de ficheiros log do Gadgetbridge que podem conter muitas informa莽玫es pessoais, incluindo, mas n茫o se limitando a, dados de sa煤de, identificadores 煤nicos (como o endere莽o MAC de um aparelho), prefer锚ncias de m煤sica etc. Considere editar o ficheiro e remover essas informa莽玫es antes de envi谩-lo para um relat贸rio p煤blico dos problemas.</string>
+    <string name="share_log">Compartilhar log</string>
+    <string name="ok">OK</string>
+    <string name="mi3_night_mode_sunset">No por do sol</string>
+    <string name="mi3_prefs_night_mode_summary">Reduz o brilho do ecr茫 da pulseira automaticamente 脿 noite</string>
+    <string name="mi3_prefs_night_mode">Modo noturno</string>
+    <string name="minutes_30">30 minutos</string>
+    <string name="minutes_10">10 minutos</string>
+    <string name="minutes_5">5 minutos</string>
+    <string name="minutes_1">1 minuto</string>
+    <string name="seconds_30">30 segundos</string>
+    <string name="seconds_20">20 segundos</string>
+    <string name="seconds_10">10 segundos</string>
+    <string name="seconds_5">5 segundos</string>
+    <string name="no_limit">Sem limite</string>
+    <string name="you_did_not_sleep">N茫o dormiu</string>
+    <string name="you_slept">Dormiu de %1$s at茅 %2$s</string>
+    <string name="mi3_prefs_band_screen_unlock_summary">Deslize para cima para desbloquear o ecr茫 da pulseira</string>
+    <string name="watch9_calibration_hint">Defina o hor谩rio que seu aparelho est谩 mostrando para si agora mesmo.</string>
+    <string name="devicetype_watch9">Watch 9</string>
+    <string name="devicetype_id115">ID115</string>
+    <string name="devicetype_mykronoz_zetime">MyKronoz ZeTime</string>
+    <string name="pref_auto_fetch_limit_fetches_summary">Coleta a cada %d minutos</string>
+    <string name="pref_auto_fetch_limit_fetches">Tempo m铆nimo entre coletas</string>
+    <string name="pref_auto_fetch_summary">A coleta acontece ao bloquear o ecr茫. S贸 funciona se um mecanismo de bloqueio estiver definido!</string>
+    <string name="pref_auto_fetch">Coletar os dados de atividade automaticamente</string>
+    <string name="devicetype_miband3">Mi Band 3</string>
+    <string name="devicetype_q8">Q8</string>
+    <string name="reset_index">Redefinir data de coleta</string>
+    <string name="controlcenter_start_activity_tracks">Monitoramentos de suas atividades</string>
+    <string name="on">Ligado</string>
+    <string name="devicetype_xwatch">XWatch</string>
+    <string name="choose_auto_export_location">Escolha a localiza莽茫o da exporta莽茫o</string>
+    <string name="devicetype_teclast_h30">Teclast H30</string>
+    <string name="devicetype_no1_f1">No.1 F1</string>
+    <string name="devicetype_exrizu_k8">Exrizu K8</string>
+    <string name="devicetype_makibes_f68">Makibes F68</string>
+    <string name="devicetype_hplus">HPlus</string>
+    <string name="devicetype_liveview">LiveView</string>
+    <string name="devicetype_vibratissimo">Vibratissimo</string>
+    <string name="devicetype_amazfit_cor">Amazfit Cor</string>
+    <string name="devicetype_amazfit_bip">Amazfit Bip</string>
+    <string name="devicetype_miband2">Mi Band 2</string>
+    <string name="devicetype_miband">Mi Band</string>
+    <string name="kind_watchface">Mostrador</string>
+    <string name="kind_invalid">Dados inv谩lidos</string>
+    <string name="activity_web_view">Atividades Web View</string>
+    <string name="_pebble_watch_open_on_phone">Abrir no aparelho de Android</string>
+    <string name="mi2_prefs_button_press_count_max_delay_summary">Atraso m谩ximo entre pressionamentos do bot茫o em milissegundos</string>
+    <string name="mi2_prefs_button_press_count_max_delay">Atraso m谩ximo entre pressionamentos</string>
+    <string name="mi2_prefs_button_action_vibrate_summary">Ativa vibra莽茫o da pulseira quando a a莽茫o do bot茫o 茅 disparada</string>
+    <string name="mi2_prefs_button_action_summary">Ativa a莽茫o em n煤mero especificado de pressionamentos do bot茫o</string>
+    <string name="discovery_pair_question">Selecione Parear para parear seus aparelhos. Se isso falhar, tente novamente sem pareamento.</string>
+    <string name="discovery_successfully_bonded">Vinculado a %1$s.</string>
+    <string name="discovery_trying_to_connect_to">Tentar conectar-se a: %1$s</string>
+    <string name="discovery_bonding_failed_immediately">V铆nculo com %1$s falhou imediatamente.</string>
+    <string name="pref_header_pebble_timeline">Linha do tempo do Pebble</string>
+    <string name="activity_summaries_statistics">Estatisticas</string>
+    <string name="km">km</string>
+    <string name="activity_filter_apply_filter">Aplicar filtro</string>
+    <string name="activity_filter_filter_title">Filtro</string>
+    <string name="activity_filter_reset_filter">Reiniciar filtro</string>
+    <string name="seconds_m">seg/m</string>
+    <string name="activity_detail_duration_label">Dura莽茫o</string>
+    <string name="activity_detail_end_label">Fim</string>
+    <string name="activity_detail_start_label">Inicio</string>
+    <string name="Steps">Passos</string>
+    <string name="Activity">Actitivade</string>
+    <string name="Speed">Velocidade</string>
+    <string name="Distance">Dist芒ncia</string>
+    <string name="Swimming">Nata莽茫o</string>
+    <string name="bpm">bpm</string>
+    <string name="minutes_km">min/km</string>
+    <string name="seconds_km">seg/km</string>
+    <string name="calories_unit">kcal</string>
+    <string name="laps_unit">voltas</string>
+    <string name="seconds">seg</string>
+    <string name="strokes_unit">ritmo</string>
+    <string name="strokes_second">num/s</string>
+    <string name="km_h">km/h</string>
+    <string name="meters_second">m/s</string>
+    <string name="steps_unit">passos</string>
+    <string name="cm">cm</string>
+    <string name="meters">m</string>
+    <string name="averageStride">M茅dia da caminhada</string>
+    <string name="averageKMPaceSeconds">Ritmo</string>
+    <string name="averageHR">Frequ锚ncia card铆aca</string>
+    <string name="totalStride">Total da caminhada</string>
+    <string name="maxPace">Ritmo mais r谩pido</string>
+    <string name="minPace">Ritmo mais lento</string>
+    <string name="maxSpeed">M谩xima</string>
+    <string name="caloriesBurnt">Calorias</string>
+    <string name="activeSeconds">Ativo</string>
+    <string name="steps">Passos</string>
+    <string name="minAltitude">M铆nima</string>
+    <string name="maxAltitude">M谩xima</string>
+    <string name="descentMeters">Descida</string>
+    <string name="ascentMeters">Subida</string>
+    <string name="distanceMeters">Dist芒ncia</string>
+    <string name="activity_summary_detail">Detalhes de atividade de desporto</string>
+    <string name="devicetype_pinetime_jf">PineTime (JF Firmware)</string>
+    <string name="error_location_enabled_mandatory">A localiza莽茫o deve ser ligada para procurar aparelhos</string>
+    <string name="companiondevice_pairing_details">Ativa o novo suporte 脿 API CompanionDevice (tem efeito apenas em Android 8 ou mais novo) que vai aumentar a confiabilidade se o servi莽o precisar ser reiniciado em segundo plano, requer novo emparelhamento usando o Gadgetbridge para ter um efeito</string>
+    <string name="companiondevice_pairing">Pareamento de CompanionDevice</string>
+    <string name="require_location_provider">Localiza莽茫o deve estar ativada</string>
+    <string name="ignore_bonded_devices_description">Ativar esta op莽茫o ignorar谩 aparelhos que j谩 foram associados/emparelhados durante a varredura</string>
+    <string name="ignore_bonded_devices">Ignorar aparelhos associados</string>
+    <string name="error_retrieving_devices_database">Erro ao obter aparelhos do banco de dados</string>
+    <string name="error_setting_alias">Erro ao definir um alias:</string>
+    <string name="error_exporting_device_preferences">Erro ao exportar as prefer锚ncias espec铆ficas do aparelho</string>
+    <string name="pref_check_permission_status_summary">Verifique e pe莽a permiss玫es ausentes mesmo quando elas podem n茫o ser necess谩rias no exato momento. Desative isso somente se seus aparelhos realmente n茫o suportarem nenhum desses recursos. N茫o conceder uma permiss茫o pode causar problemas!</string>
+    <string name="pref_check_permission_status">Verificar estado de permiss茫o</string>
+    <string name="device_requires_key">CHAVE NECESS脕RIA</string>
+    <string name="device_is_currently_bonded">J脕 ASSOCIADO</string>
+    <string name="devicetype_tlw64">TLW64</string>
+    <string name="menuitem_stress">Estresse</string>
+    <string name="menuitem_cycles">Rastreio de Ciclos</string>
+    <string name="menuitem_breathing">Respira莽茫o</string>
+    <string name="devicetype_miband5">Mi Band 5</string>
+    <string name="fw_upgrade_notice_miband5">Est谩 prestes a instalar o firmware %s na sua Mi Band 5.
+\n
+\nCertifique-se de instalar o ficheiro .fw e, ap贸s, o ficheiro .res. A sua pulseira vai reiniciar ap贸s instalar o ficheiro .fw.
+\n
+\nNota: n茫o tem que instalar .res se ele for exatamente o mesmo que o instalado anteriormente.
+\n
+\nPROSSIGA POR SUA CONTA E RISCO!</string>
+    <string name="error_background_service_reason">A inicializa莽茫o do servi莽o de plano de fundo falhou por causa de uma exce莽茫o. Erro:</string>
+    <string name="error_background_service_reason_truncated">A inicializa莽茫o do servi莽o de plano de fundo porque鈥�</string>
+    <string name="error_background_service">Falha ao iniciar o servi莽o de plano de fundo</string>
+    <string name="error_version_check_extreme_caution">CUIDADO: erro ao verificar informa莽茫o da vers茫o! N茫o deve continuar! Encontrei o nome de vers茫o \"%s\"</string>
+    <string name="permission_granting_mandatory">Todas estas permiss玫es s茫o necess谩rias e instabilidade pode ocorrer se n茫o concedidas</string>
+    <string name="about_links">Liga莽玫es</string>
+    <string name="activity_filter_to_placeholder">hoje</string>
+    <string name="activity_filter_from_placeholder">passado distante</string>
+    <string name="activity_type_badminton">Badminton</string>
+    <string name="activity_type_pingpong">Ping pong</string>
+    <string name="activity_type_basketball">Basquetbol</string>
+    <string name="activity_type_cricket">Cr铆quete</string>
+    <string name="activity_type_rowing_machine">M谩quina de remo</string>
+    <string name="activity_type_soccer">Futebol</string>
+    <string name="activity_summaries_all_devices">Todos aparelhos</string>
+    <string name="sports_activity_quick_filter_select">Per铆odo</string>
+    <string name="sports_activity_quick_filter_30days">30 dias</string>
+    <string name="sports_activity_quick_filter_7days">7 dias</string>
+    <string name="sports_activity_quick_filter_last_month">M锚s anterior</string>
+    <string name="sports_activity_quick_filter_this_month">Este m锚s</string>
+    <string name="sports_activity_quick_filter_last_week">Semana passada</string>
+    <string name="sports_activity_quick_filter_this_week">Esta semana</string>
+    <string name="no">N茫o</string>
+    <string name="yes">Sim</string>
+    <string name="activity_filter_individual_items">Itens selecionados individualmente</string>
+    <string name="addto_filter">Adicionar ao filtro</string>
+    <string name="averageSpeed">Velocidade m茅dia</string>
+    <string name="baseAltitude">Eleva莽茫o base</string>
+    <string name="devicetype_sg2">Lemfo SG2</string>
+    <string name="activity_summaries_all_activities">Todas atividades</string>
+    <string name="activity_summary_yesterday">Ontem</string>
+    <string name="activity_summary_today">Hoje</string>
+    <string name="activity_filter_name_contains">R贸tulo</string>
+    <string name="activity_summary_edit_name_title">Editar r贸tulo</string>
+    <string name="pref_header_statistics">Estat铆sticas de atividades desportivas</string>
+    <string name="medley">Medley</string>
+    <string name="backstroke">Costas</string>
+    <string name="freestyle">Livre</string>
+    <string name="breaststroke">Nado de peito</string>
+    <string name="activity_filter_date_to">At茅</string>
+    <string name="activity_filter_date_from">De</string>
+    <string name="pref_header_filter">Filtro de atividades desportivas</string>
+    <string name="activity_type_yoga">Ioga</string>
+    <string name="activity_type_jump_roping">Corda de pular</string>
+    <string name="activity_type_elliptical_trainer">Treinador el铆ptico</string>
+    <string name="activity_type_indoor_cycling">Ciclismo interno</string>
+    <string name="activity_type_swimming_openwater">Nata莽茫o (脕guas abertas)</string>
+    <string name="prefs_events_forwarding_action_title">Executar a莽茫o</string>
+    <string name="prefs_events_forwarding_broadcast_title">Emitir mensagem</string>
+    <string name="prefs_events_forwarding_startnonwear">Quando n茫o usado</string>
+    <string name="prefs_events_forwarding_wokeup">Ao acordar</string>
+    <string name="prefs_events_forwarding_fellsleep">Ao dormir</string>
+    <string name="prefs_events_forwarding_title">A莽玫es do aparelho</string>
+    <string name="prefs_events_forwarding_summary">Usar eventos do aparelho para disparar a莽玫es e transmiss玫es do Android</string>
+    <string name="activity_detail_show_gps_label">Mostrar rastro GPS</string>
+    <string name="Elevation">Eleva莽茫o</string>
+    <string name="Strokes">Bra莽adas</string>
+    <string name="swim_style">estilo de nata莽茫o</string>
+    <string name="swolf_index">swolf 铆ndice</string>
+    <string name="flatSeconds">Plano</string>
+    <string name="ascentSeconds">Acendente</string>
+    <string name="laps">Voltas</string>
+    <string name="swimStyle">Estilo de nata莽茫o</string>
+    <string name="swolfIndex">脥ndice de SWOLF</string>
+    <string name="averageLapPace">Ritmo m茅dio de voltas</string>
+    <string name="averageStrokesPerSecond">M茅dia de bra莽adas</string>
+    <string name="averageStrokeDistance">Dist芒ncia m茅dia das bra莽adas</string>
+    <string name="sonyswr12_settings_title">Configura莽玫es de Sony SWR12</string>
+    <string name="sonyswr12_settings_low_vibration">Baixa vibra莽茫o habilitada</string>
+    <string name="sonyswr12_settings_stamina">O modo de economia de energia est谩 ativado</string>
+    <string name="sonyswr12_settings_alarm_interval">Intervalo de alarme inteligente em minutos</string>
+    <string name="sonyswr12_settings_low_vibration_summary">Ativar baixa intensidade de vibra莽茫o na banda</string>
+    <string name="sonyswr12_settings_stamina_summary">O modo de economia de energia desativa a medi莽茫o autom谩tica peri贸dica da frequ锚ncia card铆aca, aumentando assim o tempo de trabalho</string>
+    <string name="sonyswr12_settings_alarm_interval_summary">O intervalo de alarme inteligente 茅 o intervalo antes do alarme instalado. Neste intervalo, o aparelho tenta detetar a fase mais leve do sono para acordar o utilizador</string>
+    <string name="calendar_year">Ano</string>
+    <string name="calendar_six_months">6 meses</string>
+    <string name="calendar_three_months">3 meses</string>
+    <string name="calendar_month">M锚s</string>
+    <string name="calendar_two_weeks">Duas semanas</string>
+    <string name="calendar_week">Semana</string>
+    <string name="calendar_day">Dia</string>
+    <string name="battery_level">N铆vel de bateria</string>
+    <string name="battery_detail_activity_title">Informa莽茫o da bateria</string>
+    <string name="find_lost_device_message">Procurar %1$s\?</string>
+    <string name="qhybrid_title_calibration">Calibra莽茫o</string>
+    <string name="prefs_autoremove_message">Remover automaticamente notifica莽玫es SMS</string>
+    <string name="prefs_fake_ring_duration">Toque cont铆nuo falso</string>
+    <string name="prefs_operating_sounds">Sons de opera莽玫es</string>
+    <string name="prefs_key_vibration">Vibra莽茫o de tecla</string>
+    <string name="prefs_autolight">Luz autom谩tica</string>
+    <string name="qhybrid_calibration_align_hint">Use os bot玫es abaixo para alinhar as m茫os do rel贸gio 脿s 12:00.</string>
+    <string name="devicetype_nut_mini">Nut mini</string>
+    <string name="gps_track">Rastro GPS</string>
+    <string name="descentSeconds">Descendente</string>
+    <string name="about_version">Vers茫o %s</string>
+    <string name="pref_title_lower_button_function_double">Bot茫o inferior duplo</string>
+    <string name="pref_title_middle_button_function_double">Bot茫o central duplo</string>
+    <string name="pref_title_upper_button_function_double">Bot茫o superior duplo</string>
+    <string name="pref_title_lower_button_function_long">Bot茫o inferior longo</string>
+    <string name="pref_title_middle_button_function_long">Bot茫o central longo</string>
+    <string name="pref_title_upper_button_function_long">Bot茫o superior longo</string>
+    <string name="pref_title_lower_button_function_short">Bot茫o inferior curto</string>
+    <string name="pref_title_middle_button_function_short">Bot茫o central curto</string>
+    <string name="pref_title_upper_button_function_short">Bot茫o superior curto</string>
+    <string name="menuitem_pomodoro">Rastreador Pomodoro</string>
+    <string name="menuitem_sleep">Sono</string>
+    <string name="menuitem_goal">Meta da atividade</string>
+    <string name="menuitem_stopwatch">Cron么metro</string>
+    <string name="menuitem_dnd">DND</string>
+    <string name="menuitem_alexa">Alexa</string>
+    <string name="menuitem_takephoto">Controlo remoto de camera</string>
+    <string name="menuitem_mutephone">Silenciar telefone</string>
+    <string name="menuitem_findphone">Encontrar telefone</string>
+    <string name="menuitem_spo2">SpO2</string>
+    <string name="devicetype_lefun">Lefun</string>
+    <string name="devicetype_amazfit_vergel">Amazfit Verge Lite</string>
+    <string name="devicetype_casiogbx100">Casio GBX-100</string>
+    <string name="devicetype_amazfit_gts2">Amazfit GTS 2</string>
+    <string name="devicetype_amazfit_gtr2">Amazfit GTR 2</string>
+    <string name="devicetype_amazfit_bipu">Amazfit Bip U</string>
+    <string name="devicetype_amazfit_bips_lite">Amazfit Bip S Lite</string>
+    <string name="devicetype_amazfit_band5">Amazfit Band 5</string>
+    <string name="activity_error_no_app_for_png">Para compartilhar esta captura de ecr茫, instale uma app que pode manipular ficheiros de imagens.</string>
+    <string name="activity_DB_ShowContentButton">Mostrar Exportar/Importar conte煤do do diret贸rio</string>
+    <string name="activity_data_management_directory_content_title">Exportar/Importar conte煤do do diret贸rio</string>
+    <string name="dbmanagementactivity_export_confirmation">Realmente exportar dados\? Dados de atividades exportados anteriormente (se houver) e prefer锚ncias ser茫o substitu铆dos.</string>
+    <string name="dbmanagementactivity_export_data_title">Exportar dados\?</string>
+    <string name="dbmanagementactivity_error_cleaning_export_directory">Erro ao apagar ficheiros de diret贸rio externo %1$s</string>
+    <string name="charts_legend_heartrate_average">Frequ锚ncia card铆aca m茅dia</string>
+    <string name="activity_prefs_chart_min_session_length">Tamanho m铆nimo de atividade (minutos)</string>
+    <string name="activity_prefs_chart_max_idle_phase_length">Tamanho da pausa para separar atividades (minutos)</string>
+    <string name="activity_prefs_chart_min_steps_per_minute">Passos m铆nimos por minuto para detetar atividade</string>
+    <string name="activity_prefs_chart_min_steps_per_minute_for_run">Passos m铆nimos por minuto para detetar corrida</string>
+    <string name="activity_prefs_step_length_cm">Tamanho do passo em cm</string>
+    <string name="lefun_prefs_interface_language_title">Idioma da interface</string>
+    <string name="lefun_prefs_antilost_summary">A pulseira vai vibrar se o seu telefone se desconectar da pulseira</string>
+    <string name="lefun_prefs_antilost_title">Antiperda</string>
+    <string name="lefun_prefs_hydration_reminder_interval_title">Intervalo de lembrete de hidrata莽茫o (em minutos)</string>
+    <string name="lefun_prefs_hydration_reminder_summary">A pulseira vai vibrar para lhe fazer lembrar de beber 谩gua</string>
+    <string name="lefun_prefs_hydration_reminder_title">Lembrete de hidrata莽茫o</string>
+    <string name="charts_min_max_heartrate_popup">Frequ锚ncia card铆aca mais baixa: %1$d
+\nFrequ锚ncia card铆aca mais alta: %2$d
+\nIntensidade do movimento: %3$s</string>
+    <string name="movement_intensity">Intensidade do movimento</string>
+    <string name="firmware_update_progress">O envio est谩 em progresso
+\n%1d%% a %.2fkbps (m茅dia de %.2fkbps)
+\nParte %1d de %1d</string>
+    <string name="devicestatus_upload_failed">O envio falhou</string>
+    <string name="devicestatus_upload_aborted">O envio foi abortado!</string>
+    <string name="devicestatus_upload_validating">O envio est谩 a ser validado</string>
+    <string name="devicestatus_upload_completed">O envio foi conclu铆do</string>
+    <string name="devicestatus_disconnected">O aparelho foi desconectado!</string>
+    <string name="devicestatus_disconnecting">O aparelho est谩 desconectando!</string>
+    <string name="devicestatus_upload_started">O envio foi iniciado</string>
+    <string name="devicestatus_upload_starting">O envio est谩 a iniciar</string>
+    <string name="devicestatus_connected">O aparelho est谩 conectado</string>
+    <string name="devicestatus_connecting">O aparelho est谩 conectando</string>
+    <string name="activity_list_summary_activities">Atividades</string>
+    <string name="activity_list_summary_intensity">Movimento
+\nIntensidade</string>
+    <string name="activity_list_summary_active_time">Tempo ativo</string>
+    <string name="activity_list_summary_distance">Dist芒ncia</string>
+    <string name="activity_list_summary_active_steps">Passos ativos</string>
+    <string name="charts_activity_list">Lista de atividades</string>
+    <string name="prefs_charts_tabs_summary">Abas de gr谩ficos vis铆veis</string>
+    <string name="prefs_charts_tabs">Abas de gr谩ficos</string>
+    <string name="chart_get_active_and_synchronize">Fa莽a alguma atividade e sincronize o aparelho.</string>
+    <string name="chart_no_active_data">Nenhuma atividade detectada.</string>
+    <string name="discovery_entered_invalid_authkey">A chave de autentica莽茫o secreta 茅 inv谩lida! Fa莽a um pressionamento longo no aparelho para editar.</string>
+    <string name="pref_summary_connected_advertisement">Torna o aparelho descobr铆vel via Bluetooth mesmo quando conectado</string>
+    <string name="pref_title_connected_advertisement">Vis铆vel enquanto conectado</string>
+    <string name="fw_upgrade_notice_amazfitvergel">Est谩 prestes a instalar o firmware %s no seu Amazfit Verge Lite.
+\n
+\nCertifique-se de instalar o ficheiro .fw, depois disso o ficheiro .res e finalmente o ficheiro .gps. A sua pulseira ser谩 reiniciada ap贸s a instala莽茫o do ficheiro .fw.
+\n
+\nNota: n茫o precisa instalar .res e .gps se esses forem exatamente os mesmos que os instalados anteriormente.
+\n
+\nPROSSIGA AO SEU PR脫PRIO RISCO!</string>
+    <string name="pref_header_sony_ambient_sound_control">Controlo Som Ambiente</string>
+    <string name="sony_ambient_sound">Modo</string>
+    <string name="sony_ambient_sound_off">Desligado</string>
+    <string name="sony_ambient_sound_noise_cancelling">Cancelamento de ru铆do</string>
+    <string name="sony_ambient_sound_wind_noise_reduction">Redu莽茫o de ru铆do do vento</string>
+    <string name="sony_ambient_sound_ambient_sound">Som ambiente</string>
+    <string name="sony_ambient_sound_focus_voice">Foco na voz</string>
+    <string name="sony_ambient_sound_level">N铆vel de som ambiente</string>
+    <string name="sony_sound_position">Posi莽茫o do som</string>
+    <string name="sony_sound_position_off">Desligado</string>
+    <string name="sony_sound_position_front">Frente</string>
+    <string name="sony_sound_position_front_left">Frente Esquerda</string>
+    <string name="sony_sound_position_front_right">Frente Direita</string>
+    <string name="sony_sound_position_rear_left">Traseira Esquerda</string>
+    <string name="sony_sound_position_rear_right">Traseira Direita</string>
+    <string name="sony_surround_mode">Modo Surround</string>
+    <string name="sony_surround_mode_off">Desligado</string>
+    <string name="sony_surround_mode_arena">Arena</string>
+    <string name="sony_surround_mode_club">Discoteca</string>
+    <string name="sony_surround_mode_outdoor_stage">Palco exterior</string>
+    <string name="sony_surround_mode_concert_hall">Sala de concertos</string>
+    <string name="sony_warn_sbc_codec">Aviso: As preferencias de equalizador, modo surround e posi莽茫o do som s贸 funcionam com o codec de audio SBC.</string>
+    <string name="sony_equalizer">Equalizador</string>
+    <string name="sony_equalizer_preset_off">Desligado</string>
+    <string name="sony_equalizer_preset_bright">Brilhante</string>
+    <string name="sony_equalizer_preset_excited">Excitado</string>
+    <string name="sony_equalizer_preset_mellow">Suave</string>
+    <string name="sony_equalizer_preset_relaxed">Relaxado</string>
+    <string name="sony_equalizer_preset_vocal">Vocal</string>
+    <string name="sony_equalizer_preset_treble_boost">Amplifica莽茫o de agudos</string>
+    <string name="sony_equalizer_preset_bass_boost">Amplifica莽茫o de graves</string>
+    <string name="sony_equalizer_preset_speech">Discurso</string>
+    <string name="sony_equalizer_preset_manual">Manual</string>
+    <string name="sony_equalizer_preset_custom_1">Pessoal 1</string>
+    <string name="sony_equalizer_preset_custom_2">Pessoal 2</string>
+    <string name="pref_header_sony_equalizer_preset_custom_1">Predefini莽茫o Pessoal 1</string>
+    <string name="pref_header_sony_equalizer_preset_custom_2">Predefini莽茫o Pessoal 2</string>
+    <string name="sony_equalizer_band_400">400</string>
+    <string name="sony_equalizer_band_1000">1k</string>
+    <string name="sony_equalizer_band_2500">2.5k</string>
+    <string name="sony_equalizer_band_6300">6.3k</string>
+    <string name="sony_equalizer_band_16000">16k</string>
+    <string name="sony_equalizer_clear_bass">Graves Claros</string>
+    <string name="sony_touch_sensor">Controlo com sensor t谩til</string>
+    <string name="sony_notification_voice_guide">Notifica莽玫es &amp; Guia de voz</string>
+    <string name="sony_automatic_power_off">Desligar automaticamente</string>
+    <string name="sony_automatic_power_off_off">N茫o desligar</string>
+    <string name="sony_automatic_power_off_5_min">5 minutos</string>
+    <string name="sony_automatic_power_off_30_min">30 minutos</string>
+    <string name="sony_automatic_power_off_1_hour">1 hora</string>
+    <string name="sony_automatic_power_off_3_hour">3 horas</string>
+    <string name="sony_automatic_power_off_when_taken_off">Quando retirar os auscultadores</string>
+    <string name="sony_button_mode_off">Desligado</string>
+    <string name="sony_button_mode_ambient_sound_control">Controlo Som Ambiente</string>
+    <string name="sony_button_mode_playback_control">Controlo da reprodu莽茫o</string>
+    <string name="sony_button_mode_volume_control">Controlo do Volume</string>
+    <string name="uncheck_all_applications">Desmarcar todos as aplica莽玫es</string>
+    <string name="fw_upgrade_notice_amazfitx">Est谩 prestes a instalar o firmware %s no seu Amazfit X.
+\n
+\nCertifique-se de instalar o ficheiro .fw e depois disso o ficheiro .res. A sua pulseira ser谩 reiniciada ap贸s a instala莽茫o do ficheiro .fw.
+\n
+\nNota: n茫o precisa instalar .res se esse for exatamente o mesmo que o instalado anteriormente.
+\n
+\nPROSSIGA AO SEU PR脫PRIO RISCO!</string>
+    <string name="pref_theme_black_background">Usar plano de fundo preto no tema escuro</string>
+    <string name="pref_title_ping_tone">Tom do ping</string>
+    <string name="pref_header_notification_application_settings">Configura莽玫es por aplica莽茫o</string>
+    <string name="pref_title_notification_use_as">Usar a lista de aplica莽玫es para鈥�</string>
+    <string name="pref_title_notification_use_as_deny">Negar notifica莽玫es das aplica莽玫es selecionadas</string>
+    <string name="pref_title_notification_use_as_allow">Permitir notifica莽玫es de apps selecionadas</string>
+    <string name="pref_title_message_privacy_mode">Modo de privacidade de mensagens</string>
+    <string name="pref_message_privacy_mode_off">Exibir todo o conte煤do</string>
+    <string name="pref_message_privacy_mode_complete">Ocultar todo o conte煤do</string>
+    <string name="pref_applications_settings">Lista de aplica莽玫es</string>
+    <string name="pref_title_huami_force_new_protocol">Novo protocolo de autentica莽茫o</string>
+    <string name="pref_summary_huami_force_new_protocol">Ative se o seu aparelho n茫o se conecta mais ap贸s uma atualiza莽茫o de firmware</string>
+    <string name="dialog_hide">Ocultar</string>
+    <string name="prefs_sounds_summary">Configure quando o aparelho vai bipar</string>
+    <string name="blood_pressure">Press茫o arterial</string>
+    <string name="getting_heart_rate">Medi莽茫o</string>
+    <string name="heart_rate_result">Resultados da medi莽茫o</string>
+    <string name="activity_db_management_clean_export_directory_label">Excluir ficheiros no diret贸rio de exporta莽茫o/importa莽茫o</string>
+    <string name="activity_DB_clean_export_directory_warning_title">Excluir ficheiros no diret贸rio de exporta莽茫o/importa莽茫o\?</string>
+    <string name="activity_DB_clean_export_directory_warning_message">Deseja realmente excluir ficheiros do diret贸rio de exporta莽茫o/importa莽茫o\?</string>
+    <string name="activity_db_management_clean_export_directory_text">Os ficheiros exportados no diret贸rio de exporta莽茫o/importa莽茫o podem ser acedidos por qualquer app no seu aparelho. Pode remover esses ficheiros ap贸s a sincroniza莽茫o ou backup. Certifique-se de ter um backup antes de exclu铆-los. Os ficheiros GPX, subdiret贸rios e ficheiros de base de dados exportados automaticamente (se houver) n茫o ser茫o exclu铆dos. O caminho ao diret贸rio de exporta莽茫o/importa莽茫o 茅:</string>
+    <string name="dbmanagementactivity_export_finished">Exclus茫o finalizada</string>
+    <string name="activity_type_strength_training">Treinamento de for莽a</string>
+    <string name="activity_summary_detail_select_gpx_track">Selecionar trilha GPX</string>
+    <string name="activity_summary_detail_clear_gpx_track">Apagar trilha GPX</string>
+    <string name="activity_summary_detail_editing_gpx_track">Editando trilha GPX vinculada</string>
+    <string name="kind_agps_bundle">Pacote AGPS</string>
+    <string name="devicetype_miband6">Mi Band 6</string>
+    <string name="devicetype_amazfit_neo">Amazfit Neo</string>
+    <string name="devicetype_amazfit_bipupro">Amazfit Bip U Pro</string>
+    <string name="devicetype_amazfit_gtr2e">Amazfit GTR 2e</string>
+    <string name="devicetype_amazfit_gts2_mini">Amazfit GTS 2 Mini</string>
+    <string name="devicetype_amazfit_x">Amazfit X</string>
+    <string name="devicetype_zepp_e">Zepp E</string>
+    <string name="devicetype_um25">UM-25</string>
+    <string name="devicetype_waspos">Wasp-os</string>
+    <string name="devicetype_smaq2oss">SMA-Q2 OSS</string>
+    <string name="notification_channel_low_battery_name">Bateria baixa</string>
+    <string name="menuitem_barometer">Bar么metro</string>
+    <string name="menuitem_flashlight">Lanterna</string>
+    <string name="toast_app_must_not_be_selected">A app n茫o deve estar selecionada para ser configurada</string>
+    <string name="toast_app_must_be_selected">A app deve ser selecionada para ser configurada</string>
+    <string name="gpx_receiver_activity_title">Receptor GPX Gadgetbridge</string>
+    <string name="minutes_mi">min/mi</string>
+    <string name="fossil_hr_commute_processing">Processando solicita莽茫o鈥�</string>
+    <string name="fossil_hr_synced_activity_data">Dados de atividade sincronizados</string>
+    <string name="fossil_hr_unavailable_unauthed">N茫o dispon铆vel no modo n茫o autenticado</string>
+    <string name="fossil_hr_auth_failed">Autentica莽茫o falhou, funcionalidade limitada</string>
+    <string name="qhybrid_title_background_image">Imagem de plano de fundo</string>
+    <string name="fossil_hr_warning_firmware_too_new">Algumas fun莽玫es est茫o desativadas porque o firmware do rel贸gio 茅 novo demais</string>
+    <string name="pref_title_physical_buttons">Bot玫es f铆sicos</string>
+    <string name="pref_summary_physical_buttons">Configure a funcionalidade dos bot玫es f铆sicos no rel贸gio</string>
+    <string name="qhybrid_summary_file_management">Enviar e descarregar ficheiros</string>
+    <string name="qhybrid_summary_calibration">Calibre os ponteiros do rel贸gio</string>
+    <string name="pref_summary_canned_messages_set">Enviar mensagens configuradas abaixo ao seu aparelho</string>
+    <string name="pref_summary_canned_messages_dismisscall">Dispensar chamadas a partir do rel贸gio com uma mensagem SMS</string>
+    <string name="fossil_hr_commute_actions_explanation">As a莽玫es configuradas aqui aparecer茫o na app Commute do seu rel贸gio. Leia a wiki para obter informa莽玫es sobre como lidar com as inten莽玫es produzidas por essas a莽玫es.</string>
+    <string name="fossil_hr_edit_action_delete">excluir</string>
+    <string name="fossil_hr_edit_action">Editar a莽茫o</string>
+    <string name="fossil_hr_new_action_cancel">cancelar</string>
+    <string name="fossil_hr_new_action">Nova a莽茫o</string>
+    <string name="qhybrid_calibration_counterclockwise">Sentido anti-hor谩rio</string>
+    <string name="qhybrid_calibration_clockwise">Sentido hor谩rio</string>
+    <string name="qhybrid_calibration_1_step">1 passo</string>
+    <string name="qhybrid_calibration_10_steps">10 passos</string>
+    <string name="qhybrid_calibration_100_steps">100 passos</string>
+    <string name="qhybrid_pref_summary_actions">A莽玫es para a app Commute</string>
+    <string name="pref_title_developer_settings">Configura莽玫es de programador</string>
+    <string name="pref_summary_developer_settings">Configura莽玫es e funcionalidade usada por programadores</string>
+    <string name="qhybrid_pref_title_external_intents">Permitir inten莽玫es externas perigosas</string>
+    <string name="qhybrid_pref_summary_external_intents">Permitir que outras apps do Android enviem/sobrescrevam ficheiros</string>
+    <string name="devicetype_amazfit_trex_pro">Amazfit T-Rex Pro</string>
+    <string name="watchface_dialog_title_set_name">Definir nome do mostrador</string>
+    <string name="watchface_toast_settings_incomplete">Configura莽玫es incompletas, widget n茫o adicionado</string>
+    <string name="watchface_dialog_title_add_widget">Adicionar widget</string>
+    <string name="watchface_widget_type_date">Data</string>
+    <string name="watchface_widget_type_weather">Clima</string>
+    <string name="watchface_widget_type_steps">Passos</string>
+    <string name="watchface_widget_type_heart_rate">Frequ锚ncia card铆aca</string>
+    <string name="watchface_dialog_widget_presets">Predefini莽玫es de posi莽茫o:</string>
+    <string name="watchface_dialog_widget_preset_top">Topo</string>
+    <string name="watchface_dialog_widget_preset_bottom">Baixo</string>
+    <string name="watchface_dialog_widget_preset_left">Esquerda</string>
+    <string name="watchface_upload_failed">O envio do mostrador falhou. Tente novamente.</string>
+    <string name="watchface_widget_type_battery">Bateria</string>
+    <string name="watchface_widget_type_calories">Calorias</string>
+    <string name="watchface_widget_type_chance_rain">Chance de chover</string>
+    <string name="watchface_setting_title_power_saving">Economia de energia</string>
+    <string name="watchface_setting_power_saving_hands">Desativar movimento das m茫os enquanto fora do pulso</string>
+    <string name="prefs_sleep_time">Hor谩rios de sono</string>
+    <string name="prefs_sleep_time_label">Definir horas de sono</string>
+    <string name="prefs_sleep_time_summary">Especifica hor谩rios quando o sono 茅 registado</string>
+    <string name="prefs_vibration_enable">Ativar vibra莽玫es</string>
+    <string name="prefs_autoheartrate">Frequ锚ncia card铆aca autom谩tica</string>
+    <string name="prefs_autoheartrate_interval">Frequ锚ncia das medi莽玫es</string>
+    <string name="nothing_prefs_audiomode_title">Modo de 谩udio</string>
+    <string name="prefs_equalizer_preset">Predefini莽玫es do equalizador</string>
+    <string name="pref_title_equalizer_bass_boost">Aumento de graves</string>
+    <string name="pref_title_equalizer_soft">Suave</string>
+    <string name="prefs_equalizer_summary">Ativa ou desativa equalizador</string>
+    <string name="prefs_dolby_summary">Predefini莽茫o de Dolby para equalizador</string>
+    <string name="prefs_game_mode">Modo jogo</string>
+    <string name="prefs_game_mode_summary">Apenas se o seu telefone tem suporte ao modo jogo</string>
+    <string name="prefs_touch_lock">Bloqueio de toques</string>
+    <string name="prefs_touch_lock_summary">Desativa eventos de toque</string>
+    <string name="prefs_galaxy_buds_experimental">Experimental</string>
+    <string name="prefs_ambient_volume">Volume ambiente</string>
+    <string name="prefs_ambient_voice_focus">Foco de voz</string>
+    <string name="prefs_ambient_voice_summary">Fa莽a a voz se destacar</string>
+    <string name="prefs_ambient_sound">Som ambiente</string>
+    <string name="prefs_ambient_mode">Modo ambiente</string>
+    <string name="left_earbud">Fone de ouvido esquerdo</string>
+    <string name="right_earbud">Fone de ouvido direito</string>
+    <string name="prefs_sounds">Sons</string>
+    <string name="menuitem_events">Eventos</string>
+    <string name="fw_upgrade_notice_amazfitneo">Est谩 prestes a instalar o firmware %s no seu Amazfit Neo.
+\n
+\nA sa pulseira ser谩 reiniciada ap贸s a instala莽茫o do ficheiro .fw.
+\n
+\nPROSSIGA AO SEU PR脫PRIO RISCO!</string>
+    <string name="button_watchface_save_apply">Gravar e aplicar</string>
+    <string name="appmanager_app_edit">Editar</string>
+    <string name="watchface_dialog_widget_type">Tipo:</string>
+    <string name="watchface_dialog_widget_x_coordinate">Coordenada X (m谩x. 240):</string>
+    <string name="ft">ft</string>
+    <string name="devicetype_fitpro">FitPro</string>
+    <string name="menuitem_temperature">Temperatura</string>
+    <string name="menuitem_widgets">Widgets</string>
+    <string name="widget_settings_select_device_title">Selecionar aparelho</string>
+    <string name="mi_h">mi/h</string>
+    <string name="qhybrid_title_watchface">Configura莽茫o do mostrador</string>
+    <string name="qhybrid_title_apps">Apps</string>
+    <string name="qhybrid_title_file_management">Gest茫o de ficheiros</string>
+    <string name="qhybrid_title_apps_management">Gest茫o de apps</string>
+    <string name="watchface_setting_title_wrist_flick">Movimento de pulso</string>
+    <string name="watchface_setting_desc_wrist_flick">(para desativar completamente, ative o movimento relativo e defina todos os valores para 0)</string>
+    <string name="prefs_notifications_enable">Ativar notifica莽玫es</string>
+    <string name="enable_vibrations_summary">Vibra莽玫es para chamadas, mensagens, notifica莽玫es e mais</string>
+    <string name="enable_notifications_summary">Notifica莽玫es para chamadas, mensagens e mais</string>
+    <string name="mi">mi</string>
+    <string name="button_watchface_edit_name">Editar nome</string>
+    <string name="button_watchface_select_image">Alterar imagem de plano de fundo</string>
+    <string name="button_watchface_add_widget">Adicionar widget</string>
+    <string name="button_watchface_settings">Configura莽玫es do mostrador</string>
+    <string name="button_watchface_preview">Visualiza莽茫o no rel贸gio</string>
+    <string name="watchface_dialog_widget_y_coordinate">Coordenada Y (m谩x. 240):</string>
+    <string name="qhybrid_title_watchface_designer">Designer do mostrador</string>
+    <string name="watchface_dialog_widget_color">Cor:</string>
+    <string name="watchface_dialog_title_settings">Configura莽玫es do mostrador</string>
+    <string name="watchface_setting_display_refresh_full">Atualiza莽茫o completa (em minutos):</string>
+    <string name="watchface_setting_display_refresh_partial">Atualiza莽茫o parcial (em minutos):</string>
+    <string name="watchface_setting_power_saving_display">Desativar atualiza莽玫es da ecr茫 enquanto fora do pulso</string>
+    <string name="prefs_autoheartrate_summary">Medi莽玫es peri贸dicas de frequ锚ncia card铆aca durante o dia e tamb茅m enquanto dorme</string>
+    <string name="prefs_autoheartrate_sleep">Fazer medi莽玫es durante o sono</string>
+    <string name="prefs_autoheartrate_measurement">Medi莽玫es autom谩ticas de frequ锚ncia card铆aca</string>
+    <string name="fw_upgrade_notice_miband6">Est谩 prestes a instalar o firmware %s no seu Mi Band 6.
+\n
+\nCertifique-se de instalar o ficheiro .fw e depois disso o ficheiro .res. A sua pulseira ser谩 reiniciada ap贸s a instala莽茫o do ficheiro .fw.
+\n
+\nNota: n茫o precisa instalar .res se esse for exatamente o mesmo que o instalado anteriormente.
+\n
+\nPROSSIGA AO SEU PR脫PRIO RISCO!</string>
+    <string name="watchface_install_info">Est谩 prestes a instalar o seguinte mostrador:
+\n
+\n%1$s
+\nVers茫o %2$s por %3$s
+\n</string>
+    <string name="show_ongoing_activity">Mostrar pop-up de atividade em andamento</string>
+    <string name="notification_channel_transfer_name">Transfer锚ncia de dados</string>
+    <string name="devicetype_amazfit_gts2e">Amazfit GTS 2e</string>
+    <string name="watchface_dialog_widget_color_white">Branca</string>
+    <string name="watchface_dialog_widget_color_black">Preta</string>
+    <string name="qhybrid_watchface_configuration_old_firmware">Ecr茫 de configura莽茫o do mostrador para rel贸gios com o firmware vers茫o DN1.0.2.19r ou inferior</string>
+    <string name="gpx_receiver_files_received">Ficheiro(s) GPX recebido:</string>
+    <string name="gpx_receiver_overwrite_some_files">Um ou mais ficheiros recebidos j谩 existem. Sobrescrever\?</string>
+    <string name="watchface_setting_wrist_flick_move_relative">Mover as m茫os em rela莽茫o ao tempo:</string>
+    <string name="watchface_setting_wrist_flick_hour">Ponteiro das horas (-360 a 360):</string>
+    <string name="watchface_setting_wrist_flick_minute">Ponteiro dos minutos (-360 a 360):</string>
+    <string name="watchface_setting_wrist_flick_duration">Dura莽茫o (em ms):</string>
+    <string name="watchface_cache_confirm_overwrite">J谩 existe um mostrador com este nome no cache. Deseja sobrescrev锚-lo\?</string>
+    <string name="devicetype_galaxybuds">Galaxy Buds</string>
+    <string name="nothing_prefs_inear_summary">Reproduzir/pausar a m煤sica dependendo se usa os auscultadores</string>
+    <string name="nothing_prefs_inear_title">Dete莽茫o no ouvido</string>
+    <string name="pref_title_equalizer_dynamic">Din芒mico</string>
+    <string name="pref_title_equalizer_clear">Limpo</string>
+    <string name="pref_title_equalizer_trebble">Aumento de agudos</string>
+    <string name="prefs_dolby_mode">Modo Dolby</string>
+    <string name="prefs_equalizer">Equalizador</string>
+    <string name="prefs_left">Esquerda</string>
+    <string name="prefs_right">Direita</string>
+    <string name="prefs_galaxy_touch_options">Op莽玫es de toque</string>
+    <string name="qhybrid_pref_title_actions">A莽玫es</string>
+    <string name="devicetype_nothingear1">Nothing Ear (1)</string>
+    <string name="title_activity_notification_management">Configura莽玫es de notifica莽茫o</string>
+    <string name="check_all_applications">Marcar todos as aplica莽玫es</string>
+    <string name="set">Definir</string>
+    <string name="watchface_dialog_widget_preset_right">Direita</string>
+    <string name="watchface_widget_type_2nd_tz">2潞 fuso hor谩rio</string>
+    <string name="watchface_widget_type_active_mins">Minutos ativos</string>
+    <string name="controlcenter_power_off_confirm_description">Tem a certeza que pretende desligar o dispositivo\?</string>
+    <string name="controlcenter_power_off">Desligar</string>
+    <string name="controlcenter_power_off_confirm_title">Desligar</string>
+    <string name="pref_activity_recognize_walking">reconhecer caminhada</string>
+    <string name="pref_activity_recognize_rowing">reconhecer remada</string>
+    <string name="menuitem_menu">Menu</string>
+    <string name="reminder_repeat">Repetir</string>
+    <string name="prefs_reserve_reminder_calendar_summary">N煤mero de eventos de calend谩rio que ser茫o sincronizados</string>
+    <string name="reminder_date">Data</string>
+    <string name="reminder_time">Hora</string>
+    <string name="ascentDistance">Dist芒ncia em subida</string>
+    <string name="averageAltitude">M茅dia</string>
+    <string name="maxCadence">Cad锚ncia m谩xima</string>
+    <string name="pref_title_equalizer_normal">Normal</string>
+    <string name="sony_pause_when_taken_off">Pausar quando os headphones s茫o removidos</string>
+    <string name="pref_media_volumedown">Reduzir Volume</string>
+    <string name="about_hash">Commit %s</string>
+    <string name="descentDistance">Dist芒ncia em descida</string>
+    <string name="minHR">Frequ锚ncia card铆aca m铆nima</string>
+    <string name="minCadence">Cad锚ncia m铆nima</string>
+    <string name="prefs_active_noise_cancelling">Cancelamento Ativo de Ru铆do</string>
+    <string name="averageCadence">Cad锚ncia m茅dia</string>
+    <string name="prefs_activity_recognition">Defini莽玫es de reconhecimento de atividade</string>
+    <string name="sony_button_mode_left">Modo do bot茫o (esquerdo)</string>
+    <string name="sony_button_mode_right">Modo do bot茫o (direito)</string>
+    <string name="pref_activity_recognize_biking">reconhecer ciclismo</string>
+    <string name="fossil_hr_button_config_info">Alguns bot玫es n茫o podem ser configurados porque as suas fun莽玫es est茫o pre-configuradas na firmware do rel贸gio.
+\n
+\nAviso: pressionar longo no bot茫o de cima quando o mostrador da aplica莽茫o oficial Fossil est谩 instalada vai tamb茅m alternar entre mostrar/ocultar widgets.</string>
+    <string name="qhybrid_summary_on_device_confirmation">Confirma莽玫es de emparelhamento no dispositivo podem tornar-se irritantes. Desactivar pode causar perda de funcionalidade.</string>
+    <string name="pref_media_previous">Faixa Anterior</string>
+    <string name="pref_media_volumeup">Aumentar Volume</string>
+    <string name="distance_format_miles">###.#mi</string>
+    <string name="pref_activity_recognize_running">reconhecer corrida</string>
+    <string name="pref_activity_recognition_mode_ask">perguntar</string>
+    <string name="pref_activity_recognition_mode_auto">autom谩tico</string>
+    <string name="pref_activity_recognition_mode_none">nenhum</string>
+    <string name="watchface_dialog_widget_update_timeout">Tempo de actualiza莽茫o em minutos</string>
+    <string name="prefs_activity_in_device_card_title">Mostrar informa莽玫es de atividade no cart茫o do dispositivo</string>
+    <string name="prefs_activity_in_device_card_title_summary">Mostrar passos, dist芒ncia ou sono atual no cart茫o do dispositivo</string>
+    <string name="prefs_activity_in_device_card_sleep_title">Sono</string>
+    <string name="prefs_activity_in_device_card_sleep_title_summary">Mostrar dura莽茫o do sono</string>
+    <string name="prefs_activity_in_device_card_distance_title_summary">A dist芒ncia 茅 calculada em fun莽茫o do n煤mero de passos e tamanho do passo (ajust谩vel em Defini莽玫es - Sobre voc锚)</string>
+    <string name="prefs_activity_in_device_card_steps_title_summary">Mostar passos totais</string>
+    <string name="pref_header_equalizer">Equalizador</string>
+    <string name="reminder_message">Mensagem</string>
+    <string name="reminder_no_free_slots_title">Sem posi莽玫es livres</string>
+    <string name="reminder_no_free_slots_description">O dispositivo n茫o tem mais posi莽玫es livres para lembretes (total de posi莽玫es: %1$s)</string>
+    <string name="miband_prefs_reserve_reminder_calendar">Lembretes a reservar para eventos de calend谩rio futuros</string>
+    <string name="mi2_prefs_do_not_disturb_lift_wrist">Activar ecr茫 do aparelho ao levantar durante modo N茫o Incomodar</string>
+    <string name="activity_prefs_discovery_pairing">Defini莽玫es de busca e emparelhamento</string>
+    <string name="prefs_fm_preset_instructions">Pressione longo no bot茫o para guardar predefini莽茫o</string>
+    <string name="devicetype_galaxybuds_live">Galaxy Buds Live</string>
+    <string name="pressure_relief_summary">Evita o sentimento de press茫o nos ouvidos enquanto n茫o estiver a usar o Calcelamento de Ru铆do Ativo</string>
+    <string name="devicetype_bose_qc35">Bose QC35</string>
+    <string name="devicetype_vesc">VESC</string>
+    <string name="spm">passos/min</string>
+    <string name="add_test_device">Adicionar dispositivo de teste</string>
+    <string name="devicetype_sony_wh_1000xm3">Sony WH-1000XM3</string>
+    <string name="devicetype_sony_wf_sp800n">Sony WF-SP800N</string>
+    <string name="activity_type_climbing">Escalada</string>
+    <string name="prefs_fm_presets_presets">Predefini莽玫es</string>
+    <string name="discover_unsupported_devices">Mostrar dispositivos n茫o suportados</string>
+    <string name="device_card_activity_card_title">Informa莽玫es de atividade no cart茫o do dispositivo</string>
+    <string name="device_card_activity_card_title_summary">Escolher que detalhes de actividade s茫o mostrados no cart茫o do dispositivo</string>
+    <string name="pref_header_other">Outros</string>
+    <string name="pref_header_system">Sistema</string>
+    <string name="devicetype_domyos_t540">Domyos T540</string>
+    <string name="discover_unsupported_devices_description">Activar esta op莽茫o ir谩 mostrar todos os dispositivos bluetooth encontrados durante a procura. Pressionar o dispositivo ir谩 copiar o sei nome e endere莽o mac para o clipboard. Pressionar longo ir谩 lan莽ar o di谩logo de \"Adicionar dispositivo de teste\".</string>
+    <string name="pref_button_action_disabled">Desactivado</string>
+    <string name="pref_media_next">Faixa Seguinte</string>
+    <string name="pref_media_rewind">Saltar para tr谩s</string>
+    <string name="distance_format_feet">###ft</string>
+    <string name="minSpeed">M铆nimo</string>
+    <string name="maxHR">Frequ锚ncia card铆aca m谩xima</string>
+    <string name="maxStride">Dist芒ncia de passo m谩x</string>
+    <string name="minStride">Dist芒ncia de passo min</string>
+    <string name="watchface_setting_title_display_refresh_timeout">Tempo de actualiza莽茫o do ecr茫</string>
+    <string name="prefs_pressure_relief">Al铆vio de press茫o com som ambiente</string>
+    <string name="prefs_active_noise_cancelling_summary">Bloqueia ru铆dos do redor</string>
+    <string name="watchface_dialog_widget_timezone">Fuso hor谩rio:</string>
+    <string name="qhybrid_title_on_device_confirmation">Ativar confirma莽茫o de emparelhamento no dispositivo</string>
+    <string name="watchface_widget_type_custom">Widget customizada</string>
+    <string name="watchface_dialog_widget_timeout_hide_text">Ocultar texto ao terminar o tempo:</string>
+    <string name="watchface_dialog_widget_timeout_show_circle">Mostrar c铆rculo ao terminar o tempo:</string>
+    <string name="pref_media_forward">Saltar para a frente</string>
+    <string name="pref_device_action_broadcast">Enviar transmiss茫o</string>
+    <string name="distance_format_meters">###m</string>
+    <string name="distance_format_kilometers">###.#km</string>
+    <string name="pref_header_sony_equalizer_bands">Bandas</string>
+    <string name="battery_case">Caixa com bateria</string>
+    <string name="activity_type_hiking">Caminhada longa</string>
+    <string name="pref_media_playpause">Alterar reprodu莽茫o</string>
+    <string name="pref_media_play">Reproduzir media</string>
+    <string name="pref_media_pause">Pausar media</string>
+    <string name="sony_audio_upsampling">Aumentar resolu莽茫o do 谩udio</string>
+    <string name="devicetype_sony_wh_1000xm4">Sony WH-1000XM4</string>
+    <string name="sony_speak_to_chat_focus_on_voice">Foco em voz</string>
+    <string name="sony_speak_to_chat_timeout_off">Desligado</string>
+    <string name="sony_speak_to_chat_timeout_short">Curto (15s)</string>
+    <string name="sony_speak_to_chat_timeout_standard">Normal (30s)</string>
+    <string name="sony_speak_to_chat_timeout_long">Longo (1m)</string>
+    <string name="sony_connect_two_devices">Ligar a 2 dispositivos em simult芒neo</string>
+    <string name="discovery_bluetooth_le_scan">Busca Bluetooth LE:</string>
+    <string name="sony_speak_to_chat_sensitivity_low">Baixa</string>
+    <string name="discovery_bluetooth_scan">Busca Bluetooth:</string>
+    <string name="device_unsupported">N脙O SUPORTADO</string>
+    <string name="sony_speak_to_chat">Falar-para-conversar</string>
+    <string name="sony_speak_to_chat_sensitivity">Sensibilidade de dete莽茫o de voz</string>
+    <string name="sony_speak_to_chat_sensitivity_high">Alta</string>
+    <string name="sony_speak_to_chat_sensitivity_auto">Autom谩tica</string>
+    <string name="pref_header_sony_device_info">Informa莽玫es do aparelho</string>
+    <string name="audio_codec">Codec de 谩udio</string>
+    <string name="sony_speak_to_chat_timeout">Tempo limite</string>
+    <string name="watchface_dialog_widget_width">Largura:</string>
+    <string name="devicetype_sonyswr12">Sony SWR12</string>
+    <string name="unknown">Desconhecido</string>
+    <string name="start">Come莽ar</string>
+    <string name="sony_anc_optimize_title">Optimizar</string>
+    <string name="pref_header_sony_anc_optimizer">Optimizador de cancelamento de ru铆do</string>
+    <string name="devicetype_amazfit_pop">Amazfit Pop</string>
+    <string name="devicetype_amazfit_pop_pro">Amazfit Pop Pro</string>
+    <string name="sony_anc_optimize_description">Clique para come莽ar o optimizador de cancelamento de ru铆do.</string>
+    <string name="sony_anc_optimize_confirmation_title">Optimizador de cancelamento de ru铆do</string>
+    <string name="pref_anc_optimizer_state_pressure">Press茫o atmosf茅rica</string>
+    <string name="sony_anc_optimize_confirmation_description">Coloque os headphones como os utilizaria normalmente. Se as condi莽玫es de utiliza莽茫o ou press茫o atmosf茅rica alterarem, execute o optimizador novamente.</string>
+    <string name="title_activity_controlcenter_banglejs_main">Bangle.js Gadgetbridge</string>
+    <string name="about_activity_title_banglejs_main">Acerca de Bangle.js Gadgetbridge</string>
+    <string name="application_name_banglejs_main">Bangle.js Gadgetbridge</string>
+    <string name="fw_upgrade_notice_miband7">Est谩s prestes a instalar a firmware %s na tua Xiaomi Smart Band 7.
+\n
+\nA pulseira vai reiniciar depois de instalar o ficheiro .zip.
+\n
+\nPROSSIGA POR SUA CONTA E RISCO!</string>
+    <string name="gadgetbridge_running_banglejs_nightly">Nightly Bangle.js em execu莽茫o</string>
+    <string name="about_description_banglejs_main">Aplica莽茫o Android para Bangle.js, construida com base no projecto Gadgetbridge, com acesso 脿 Internet.
+\n
+\nDevido 脿s pol铆ticas da Google Play Store, n茫o nos 茅 permitido incluir links para doa莽玫es dentro da aplica莽茫o. No entanto, se gostas desta aplica莽茫o, por favor considera fazer uma doa莽茫o atrav茅s da p谩gina do Gadgetbridge abaixo.</string>
+    <string name="gadgetbridge_running_banglejs_main">Bangle.js em execu莽茫o</string>
+    <string name="application_name_main_nightly">Gadgetbridge (Nightly)</string>
+    <string name="title_activity_controlcenter_main_nightly">Gadgetbridge Nightly</string>
+    <string name="about_activity_title_main_nightly">Acerca de Gadgetbridge Nightly</string>
+    <string name="application_name_banglejs_nightly">Bangle.js Gadgetbridge (Nightly)</string>
+    <string name="title_activity_controlcenter_banglejs_nightly">Bangle.js Gadgetbridge (Nightly)</string>
+    <string name="about_activity_title_banglejs_nightly">Acerca de Bangle.js Gadgetbridge (Nightly)</string>
+    <string name="about_description_banglejs_nightly">Aplica莽茫o Android para Bangle.js, construida com base no projecto Gadgetbridge, com acesso 脿 Internet.
+\n
+\nDevido 脿s pol铆ticas da Google Play Store, n茫o nos 茅 permitido incluir links para doa莽玫es dentro da aplica莽茫o. No entanto, se gostas desta aplica莽茫o, por favor considera fazer uma doa莽茫o atrav茅s da p谩gina do Gadgetbridge abaixo.</string>
+    <string name="appmanager_app_share">Partilhar</string>
+    <string name="appmanager_watchface_activate">Ativar</string>
+    <string name="appmanager_app_start">In铆cio</string>
+    <string name="pref_header_workout">Treino</string>
+    <string name="watchface_dialog_widget_cat_custom_widget">Widget personalizado</string>
+    <string name="world_clock_timezone">Fuso hor谩rio</string>
+    <string name="spo2_off">Desligado</string>
+    <string name="world_clock_delete_confirm_title">Apagar \'%1$s\'</string>
+    <string name="normal">Normal</string>
+    <string name="pref_header_heartrate_sleep">Sono</string>
+    <string name="menuitem_nothing">Nada</string>
+    <string name="pref_header_time">Hora</string>
+    <string name="menuitem_countdown">Contagem Regressiva</string>
+    <string name="hrZoneNa">N/A</string>
+    <string name="status">Estado</string>
+    <string name="mi2_dnd_always">Sempre</string>
+    <string name="activity_type_freestyle">Livre</string>
+    <string name="gadgetbridge_running_main_nopebble">Nightly GB Sem Pebble em execu莽茫o</string>
+    <string name="pref_title_notifications_generic_settings">Configura莽玫es de notifica莽茫o de Android</string>
+    <string name="czech">Tcheco</string>
+    <string name="about_description_main_nightly">Substituto para apps n茫o open source de aparelhos Android dos fabricantes, sem necessitar de nuvem e c贸digo-aberto. Atualiza莽玫es Nightly (di谩rias) de Gadgetbridge. N茫o pode ser instalado se j谩 tem a app Gadgetbridge ou Pebble, devido a um conflito no provedor Pebble.</string>
+    <string name="gadgetbridge_running_main_nightly">Nightly GB em execu莽茫o</string>
+    <string name="application_name_main_nopebble">Gadgetbridge (Nightly, Sem provedor Pebble)</string>
+    <string name="title_activity_controlcenter_main_nopebble">Gadgetbridge Nightly Sem Pebble</string>
+    <string name="about_activity_title_main_nopebble">Sobre Gadgetbridge Nightly Sem Pebble</string>
+    <string name="about_description_main_nopebble">Substituto para apps n茫o de c贸digo-aberto de aparelhos Android dos fabricantes, sem necessitar de nuvem e possui c贸digo livre. Atualiza莽玫es Nightly (di谩rias) de Gadgetbridge. Essa vers茫o tem o provedor Pebble renomeado para prevenir conflitos, ent茫o algumas integra莽玫es do Pebble n茫o funcionar茫o, mas pode ser instalado com Gadgetbridge j谩 existente.</string>
+    <string name="vibration_profile_default">Padr茫o</string>
+    <string name="world_clock_delete_confirm_description">Tem certeza que deseja apagar o rel贸gio mundial\?</string>
+    <string name="world_clock_no_free_slots_title">N茫o h谩 espa莽o</string>
+    <string name="world_clock_no_free_slots_description">O aparelho n茫o tem espa莽o para rel贸gios mundiais (total de espa莽os: %1$s)</string>
+    <string name="world_clock_label">Etiqueta</string>
+    <string name="pref_title_touch_anc">Cancelamento de ru铆do ativo</string>
+    <string name="pref_title_touch_ambient">Som ambiente</string>
+    <string name="pref_voice_detect_duration_5">5 segundos</string>
+    <string name="pref_voice_detect_duration_10">10 segundos</string>
+    <string name="pref_header_stress">Estresse</string>
+    <string name="accuracy">Precis茫o</string>
+    <string name="balanced">Equilibrado</string>
+    <string name="gps">GPS</string>
+    <string name="power_saving">Poupan莽a de energia</string>
+    <string name="pref_header_calendar">Calend谩rio</string>
+    <string name="all_satellites">Todos os sat茅lites</string>
+    <string name="controlcenter_get_heartrate_measurement">Medir batimento card铆aco</string>
+    <string name="open_fw_installer_pick_file">Selecionar ficheiro</string>
+    <string name="gps_gnolass">GPS + GNOLASS</string>
+    <string name="gps_bds">GPS + BDS</string>
+    <string name="custom">Personalizado</string>
+    <string name="gps_galileo">GPS + GALILEO</string>
+    <string name="temperature">Temperatura</string>
+    <string name="pref_dashboard_widget_today_upside_down_title">Meia-noite na parte inferior</string>
+    <string name="pref_sleepasandroid_enable_summary">Ativar a integra莽茫o do Sleep as Android</string>
+    <string name="pref_title_huawei_account">Conta da Huawei</string>
+    <string name="fw_upgrade_notice_amazfit_gtr3_pro">Est谩 prestes a instalar o firmware %s no seu Amazfit GTR 3 Para o.
+\n
+\nA sua pulseira ser谩 reiniciada ap贸s a instala莽茫o do ficheiro .zip.
+\n
+\nPROSSIGA POR SUA PR脫PRIA CONTA E RISCO!</string>
+    <string name="bluetooth_calls_pairing">Pareamento de chamadas Bluetooth</string>
+    <string name="spo2_perc_90">90%</string>
+    <string name="pref_header_health">Sa煤de</string>
+    <string name="permission_request">O %1$s permite que envie mensagens e outros dados do Android para o seu dispositivo. Para faz锚-lo, precisa de permiss茫o para aceder esses dados e, sem ela, pode n茫o funcionar corretamente.
+\n
+\nAgora ver谩 algumas caixas de di谩logo do Android solicitando essas permiss玫es.
+\n
+\nToque em \'%2$s\' para continuar.</string>
+    <string name="widget_layout">Layout do widget</string>
+    <string name="pref_theme_dynamic">Cores din芒micas</string>
+    <string name="connection_over_ble">Conex茫o via BLE</string>
+    <string name="pref_call_rejection_method_title">M茅todo de rejei莽茫o de chamada</string>
+    <string name="pref_cache_weather">Informa莽玫es sobre o clima em cache</string>
+    <string name="pref_blacklist_calendars_summary">Os calend谩rios na lista negra n茫o ser茫o sincronizados com o dispositivo</string>
+    <string name="pref_title_touch_volume">Volume</string>
+    <string name="pref_call_rejection_method_summary">Que a莽茫o 茅 tomada quando uma chamada recebida 茅 rejeitada pelo rel贸gio</string>
+    <string name="pref_title_osmand_packagename">Nome do pacote do OsmAnd</string>
+    <string name="widget_name_untitled">Widget sem t铆tulo (%1$s)</string>
+    <string name="pref_title_fossil_hr_nav_foreground">Venha para o primeiro plano</string>
+    <string name="open_fw_installer_info_text_title">Instalador de ficheiros</string>
+    <string name="pref_title_notification_delay_calls">Delay de notifica莽茫o de chamada</string>
+    <string name="error_setting_parent_folder">Erro ao definir a pasta principal: %s</string>
+    <string name="white">Branco</string>
+    <string name="widget_subtype">Subtipo de widget</string>
+    <string name="intent_api_broadcast_activity_sync_summary">Anunciar uma inten莽茫o quando a sincroniza莽茫o de atividades for conclu铆da em qualquer dispositivo</string>
+    <string name="fossil_hr_pairing_failed">Falha no pareamento com o rel贸gio</string>
+    <string name="error_showing_changelog">Erro ao exibir o registo de mudan莽as</string>
+    <string name="pref_dashboard_cards_title">Mostrar widgets nos cart玫es</string>
+    <string name="pref_dashboard_widget_show_legend_title">Mostrar legenda</string>
+    <string name="pref_auto_reply_calls_delay_title">Atraso de resposta autom谩tica</string>
+    <string name="pref_dashboard_widget_show_legend_summary">Mostrar uma legenda abaixo do widget explicando as cores</string>
+    <string name="toast_setting_requires_reconnect">Essa configura莽茫o entrar谩 em vigor ap贸s uma reconex茫o</string>
+    <string name="url">URL</string>
+    <string name="garmin_agps_local_file">Ficheiro local</string>
+    <string name="widget">Widget</string>
+    <string name="pref_title_fossil_hr_nav_vibrate">Vibrar ao receber novas instru莽玫es</string>
+    <string name="steps_streaks_achievement_rate">Taxa de
+\nconquista</string>
+    <string name="intent_api_allow_debug_commands_title">Permitir comandos de depura莽茫o</string>
+    <string name="pref_title_fossil_hr_navigation_instructions">Instru莽玫es de navega莽茫o</string>
+    <string name="pref_force_connection_type_bt_classic">Bluetooth Classic</string>
+    <string name="pref_dashboard_cards_summary">Desenhe cart玫es ao redor dos widgets no painel</string>
+    <string name="pref_dashboard_widget_settings">Configura莽玫es do widget</string>
+    <string name="pref_title_bottom_navigation_bar">Barra de navega莽茫o inferior</string>
+    <string name="pref_summary_bottom_navigation_bar_on">Alternar entre os ecr茫s principais usando a barra de navega莽茫o ou o deslizamento horizontal</string>
+    <string name="pref_summary_bottom_navigation_bar_off">Alternar entre os ecr茫s principais usando apenas o deslizamento horizontal</string>
+    <string name="pref_header_audio">脕udio</string>
+    <string name="pref_pair_bluetooth_calls_help_title">Como receber chamadas via bluetooth</string>
+    <string name="pref_title_casio_alert_calendar">Alerta para notifica莽玫es de calend谩rio</string>
+    <string name="femometer_measurement_mode_title">Modo de medi莽茫o</string>
+    <string name="busy_task_fetch_hr_data">A coletar dados de frequ锚ncia card铆aca</string>
+    <string name="pref_screen_notification_profile_event_reminder">Lembrete de evento</string>
+    <string name="world_clock_code">C贸digo</string>
+    <string name="silent_mode_vibrate_silent">Vibrar / Silencioso</string>
+    <string name="yesterdays_activity">Atividade de Ontem</string>
+    <string name="pref_summary_opentracks_packagename">Usado para iniciar/parar a grava莽茫o da trilha GPS na app externa de condicionamento f铆sico.</string>
+    <string name="prefs_wena3_vibration_strength_item_weak">Fraca</string>
+    <string name="prefs_wena3_home_icon_name_clock">Hora Atual</string>
+    <string name="pref_force_connection_type_ble">Bluetooth LE</string>
+    <string name="pref_screen_auto_brightness_summary">Ajuste o brilho do ecr茫 de acordo com a luz ambiente</string>
+    <string name="pref_vitality_score_daily_title">Progresso di谩rio</string>
+    <string name="widget_screen">Ecr茫 de Widget</string>
+    <string name="menuitem_total_workout">Treino total</string>
+    <string name="withings_bt_calibration_next">Pr贸ximo</string>
+    <string name="english_au">Ingl锚s (Austr谩lia)</string>
+    <string name="spanish_es">Espanhol (Espanha)</string>
+    <string name="spanish_mx">Espanhol (M茅xico)</string>
+    <string name="pref_dashboard_widget_today_24h_title">Modo de 24 horas</string>
+    <string name="activity_detail_share_gps_label">Partilhar Rastro GPS</string>
+    <string name="fossil_hr_confirm_connection">Por favor, confirme no seu rel贸gio</string>
+    <string name="busy_task_fetch_sports_details_interrupted">A coleta de dados de pormenores esportivos foi interrompida</string>
+    <string name="busy_task_fetch_debug_logs">A coletar dados de registos de depura莽茫o</string>
+    <string name="pref_world_clocks_summary">Configurar rel贸gios para outros fusos hor谩rios</string>
+    <string name="contact_no_free_slots_description">O dispositivo n茫o tem slots livres para contatos (total de slots: %1$s)</string>
+    <string name="contact_delete_confirm_description">Tem certeza de que deseja apagar \'%1$s\'?</string>
+    <string name="prefs_relaxation_reminder_title">Lembrete de relaxamento</string>
+    <string name="prefs_activate_display_on_lift_sensitivity">Sensibilidade</string>
+    <string name="open_fw_installer_getting_files_title">A recuperar o ficheiro do firmware/app</string>
+    <string name="prefs_wena3_auto_power_off_item">Desligar autom谩ticamente</string>
+    <string name="aerobicTrainingEffect">Efeito Aer贸bico</string>
+    <string name="anaerobicTrainingEffect">Efeito Anaer贸bico</string>
+    <string name="notification_channel_connection_status_name">Estado da conex茫o</string>
+    <string name="pref_dashboard_widget_today_24h_summary">Mostre a atividade num 煤nico c铆rculo de 24 horas em vez de um c铆rculo duplo de 12 horas</string>
+    <string name="pref_title_garmin_default_reply_suffix">Usar sufixo de resposta predefinido</string>
+    <string name="pref_dashboard_widget_today_upside_down_summary">No modo 24 horas, desenhe a meia-noite na parte inferior e o meio-dia na parte superior do gr谩fico</string>
+    <string name="pref_sleepasandroid_device_summary">Selecione o dispositivo como provedor de dados Sleep As Android</string>
+    <string name="open_fw_installer_getting_files_text">J谩 que n茫o podemos distribuir ficheiros de firmware, ter谩 que faz锚-lo voc锚 mesmo. Isto significa que precisa primeiro procurar ficheiros online, em forums, na Amazfitwatchfaces (para Miband/Amazfit) etc.</string>
+    <string name="open_fw_installer_select_file">Selecione um ficheiro que deseja enviar para o dispositivo: %s</string>
+    <string name="open_fw_installer_connect_maximum_one_device">Conecte APENAS UM dispositivo para o qual deseja enviar o ficheiro.</string>
+    <string name="open_fw_installer_ensure_device_connected">Certifique-se de que o dispositivo %s esteja conectado</string>
+    <string name="pref_header_connection">Conex茫o</string>
+    <string name="pref_header_display">Exibir</string>
+    <string name="pref_screen_notification_profile_find_device">Localizar dispositivo</string>
+    <string name="pref_workout_detection_alert_summary">Notificar quando um treino for detetado</string>
+    <string name="pref_dashboard_all_devices_title">Todos os dispositivos</string>
+    <string name="toast_camera_support_required">O suporte da c芒mara 茅 necess谩rio para essa fun莽茫o.</string>
+    <string name="menuitem_workout_history">Hist贸rico de treinos</string>
+    <string name="intent_api_allow_trigger_export_title">Permitir a exporta莽茫o do banco de dados</string>
+    <string name="contact_missing_number">O n煤mero de contacto est谩 vazio</string>
+    <string name="prefs_home_icon_right_item">Direita</string>
+    <string name="pref_vitality_score_7_day_title">Progresso de 7 dias</string>
+    <string name="pref_vitality_score_7_day_summary">Receba uma notifica莽茫o quando a sua pontua莽茫o de vitalidade atingir 30, 60 ou 100 nos 煤ltimos 7 dias</string>
+    <string name="widget_screen_min_screens">Deve haver um m铆nimo de %1$s ecr茫s</string>
+    <string name="widget_layout_top_1_bot_2">1 superior, 2 inferiores</string>
+    <string name="icelandic">Island锚s</string>
+    <string name="steps_streaks_total_steps_hint_totals">Quantidade total de passos j谩 registados</string>
+    <string name="pref_show_changelog_summary">Exibir o registo de mudan莽as desde a 煤ltima vers茫o ap贸s a atualiza莽茫o do Gadgetbridge</string>
+    <string name="prefs_wena3_day_start_hour_item">O dia come莽a 脿s</string>
+    <string name="serial_number">N煤mero de s茅rie</string>
+    <string name="warning_missing_notification_permission">N茫o foi poss铆vel publicar a notifica莽茫o em andamento devido 脿 falta de permiss茫o</string>
+    <string name="activity_type_worn">Vestido</string>
+    <string name="pref_title_notification_wake_on_open">Despertar e desbloquear automaticamente</string>
+    <string name="pref_summary_notification_wake_on_open">Desperta e desbloqueia o dispositivo Android quando o gadget envia uma resposta OPEN de volta. Precisa estar num estado confi谩vel.</string>
+    <string name="pref_display_caller_summary">Exibir o n煤mero de telefone ou o nome nas chamadas recebidas</string>
+    <string name="pref_pair_bluetooth_calls_title">Parear para chamadas Bluetooth</string>
+    <string name="pref_pair_bluetooth_calls_summary">Clique aqui para iniciar o processo de pareamento</string>
+    <string name="pref_title_casio_alert_sms">Alerta para notifica莽玫es por SMS</string>
+    <string name="clap_hands_to_wakeup_device">Bata palmas para ligar a ecr茫</string>
+    <string name="seconds_6">6 segundos</string>
+    <string name="weeksleepchart_sleep_a_week_or_month">Sono por semana/m锚s</string>
+    <string name="weekstepschart_steps_a_week_or_month">Passos por semana/m锚s</string>
+    <string name="intent_api_allow_activity_sync_summary">Permitir o acionamento da sincroniza莽茫o de atividades por meio da API de inten莽茫o</string>
+    <string name="bottom_nav_devices">Dispositivos</string>
+    <string name="pref_dashboard_devices_to_include">Dispositivos a serem inclu铆dos</string>
+    <string name="activity_detail_share_raw_details">Partilhar pormenores brutos</string>
+    <string name="controlcenter_folder_name">Nome da pasta:</string>
+    <string name="steps_streaks_total_steps">Passos
+\ntotais</string>
+    <string name="steps_streaks_total_days_hint_totals">Percentagem de dias com meta atingida em rela莽茫o a todos os dias com passos</string>
+    <string name="step_streak_days_hint">Quantidade de dias consecutivos em que a meta de passos foi atingida</string>
+    <string name="activity_prefs_allow_bluetooth_intent_api">API de inten莽茫o de Bluetooth</string>
+    <string name="prefs_home_icon_left_item">Esquerda</string>
+    <string name="prefs_wena3_receive_calls_title">Configura莽玫es de Chamadas Recebidas</string>
+    <string name="pref_title_prefix_notification_with_app">Nome do app na notifica莽茫o</string>
+    <string name="pref_summary_osmand_packagename">Usado para selecionar a vers茫o do OsmAnd 脿 qual se conectar</string>
+    <string name="pref_title_navigation_prefs">Prefer锚ncias de navega莽茫o</string>
+    <string name="pref_title_navigation_apps">Apps de navega莽茫o</string>
+    <string name="widget_screen_no_free_slots_description">O dispositivo n茫o tem slots livres para ecr茫 de widgets (total de slots: %1$s)</string>
+    <string name="widget_layout_top_large_bot_wide">Grande em cima, largo embaixo</string>
+    <string name="uploadwatchfaceoperation_failed">Falha na instala莽茫o do mostrador de rel贸gio</string>
+    <string name="pref_test_features_title">Recursos</string>
+    <string name="prompt_restart_gadgetbridge">Reinicie o GB para que entre em vigor.</string>
+    <string name="pref_dashboard_widget_double_size_summary">Permitir que o widget ocupe duas colunas no painel</string>
+    <string name="pref_dashboard_select_devices_summary">Combine dados de atividade de dispositivos espec铆ficos para obter os totais no painel</string>
+    <plurals name="amount_of_days">
+        <item quantity="one">%d dia</item>
+        <item quantity="many">%d dias</item>
+        <item quantity="other">%d dias</item>
+    </plurals>
+    <string name="dashboard_calendar_month_goals_reached_title">% da meta de passos atingida</string>
+    <string name="pref_auto_reply_calls_delay_summary">Quantidade de segundos ap贸s os quais a chamada 茅 automaticamente atendida</string>
+    <string name="pref_header_calls_and_notifications">Chamadas e notifica莽玫es</string>
+    <string name="pref_summary_garmin_default_reply_suffix">Anexado al茅m do sufixo definido em Gadgetbridge</string>
+    <string name="pref_sleepasandroid_features_title">Recursos</string>
+    <string name="pref_sleepasandroid_feat_notifications">Notifica莽玫es</string>
+    <string name="pref_sleepasandroid_feat_spo2">SPO2</string>
+    <string name="pref_speak_notifications_focus_exclusive_summary_off">O volume de reprodu莽茫o de outros aplica莽玫es ser谩 reduzido enquanto a notifica莽茫o estiver a ser falada</string>
+    <string name="pref_summary_huawei_account">Conta da Huawei usada no processo de emparelhamento. A sua configura莽茫o permite emparelhar sem redefini莽茫o de f谩brica.</string>
+    <string name="watchface_resolution_doesnt_match">A resolu莽茫o do mostrador de rel贸gio n茫o corresponde ao ecr茫 do dispositivo. Mostrador de rel贸gio 茅 %1$s, o ecr茫 do dispositivo 茅 %2$s</string>
+    <string name="pref_summary_wheel_diameter">Di芒metro da roda em polegadas. Normalmente 29, 27,5 ou 26.</string>
+    <string name="chart_cycling_point_label_distance">Hoje: %.1f km
+\nTotal: %.1f km</string>
+    <string name="realtime_settings">Configura莽玫es em tempo real</string>
+    <string name="pref_fetch_unknown_files_title">Colete ficheiros desconhecidos</string>
+    <string name="pref_fetch_unknown_files_summary">Colete ficheiros de atividade desconhecidos do rel贸gio. Eles n茫o ser茫o processados, mas ser茫o gravados no telefone.</string>
+    <string name="pref_summary_canned_replies">Responder a partir do rel贸gio usando mensagens predefinidas</string>
+    <string name="pref_workout_detection_alert_title">Alerta</string>
+    <string name="pref_workout_detection_time">Minutos ativos antes da detec莽茫o</string>
+    <string name="username">Nome de utilizador</string>
+    <string name="purple">Roxo</string>
+    <string name="waiting_for_bluetooth">A aguardar o bluetooth鈥�</string>
+    <string name="debugactivity_confirm_remove_device_preferences_title">Remover as prefer锚ncias do dispositivo?</string>
+    <string name="debugactivity_confirm_remove_device_preferences">Isto redefinir谩 as prefer锚ncias do dispositivo para todos os dispositivos conectados. Tem certeza?</string>
+    <string name="activity_type_pool_swimming">Nata莽茫o em Piscina</string>
+    <string name="activity_type_bowling">Boliche</string>
+    <string name="mi5_prefs_workout_activity_types_summary">Escolha os tipos de atividade a serem exibidos no ecr茫 de treino</string>
+    <string name="pref_title_general_reconnectonlytoconnected">Reconectar somente a dispositivos conectados</string>
+    <string name="pref_summary_general_reconnectonlytoconnected">Reconectar somente a dispositivos conectados, em vez de reconectar a todos os dispositivos</string>
+    <string name="pref_title_notifications_ignore_work_profile">Ignorar notifica莽玫es de perfil de trabalho</string>
+    <string name="description">Descri莽茫o</string>
+    <string name="fw_upgrade_notice_amazfitbip3">Est谩 prestes a instalar o firmware %s no seu Amazfit Bip 3.
+\n
+\nCertifique-se de instalar o ficheiro .fw e depois o ficheiro .res. O seu rel贸gio ser谩 reiniciado ap贸s a instala莽茫o do ficheiro .fw.
+\n
+\nNota: n茫o precisa instalar .res se esse ficheiro for exatamente o mesmo que o anteriormente instalado.
+\n
+\nPROSSIGA POR SUA CONTA E RISCO!</string>
+    <string name="pref_summary_banglejs_phone_gps_network_only">Use apenas o provedor de rede para determinar a localiza莽茫o. Isto reduz o consumo de energia ao custo da precis茫o. 脡 necess谩ria uma reconex茫o.</string>
+    <string name="femometer_measurement_mode_precise">Modo Preciso (3min)</string>
+    <string name="femometer_measurement_mode_normal">Modo Normal (60s-90s)</string>
+    <string name="clap_hands_to_wakeup_device_summary">Bater palmas novamente desligar谩 a ecr茫</string>
+    <string name="pixoo_power_saving_summary">O ecr茫 ser谩 desligado depois que o microfone detetar sil锚ncio por algum tempo</string>
+    <string name="prefs_stress_monitoring_title">Monitoramento de estresse</string>
+    <string name="pref_title_third_party_app_device_settings">Permitir que apps de terceiros alterem as configura莽玫es</string>
+    <string name="mi2_prefs_heart_rate_monitoring">Monitoramento da frequ锚ncia card铆aca</string>
+    <string name="prefs_device_name">Nome do dispositivo</string>
+    <string name="prefs_screen_timeout">Tempo limite de ecr茫</string>
+    <string name="danish">Dinamarqu锚s</string>
+    <string name="fw_upgrade_notice_amazfitbip3pro">Est谩 prestes a instalar o firmware %s no seu Amazfit Bip 3 Para o.
+\n
+\nCertifique-se de instalar o ficheiro .fw e depois o ficheiro .res. O seu rel贸gio ser谩 reiniciado ap贸s a instala莽茫o do ficheiro .fw.
+\n
+\nNota: n茫o precisa instalar .res se esse ficheiro for exatamente o mesmo que o anteriormente instalado .
+\n
+\nPROSSIGA POR SUA CONTA E RISCO!</string>
+    <string name="open_fw_installer_warning_title">Aviso</string>
+    <string name="pref_header_sound_vibration">Som e vibra莽茫o</string>
+    <string name="pref_header_offline_voice">Voz offline</string>
+    <string name="pref_summary_banglejs_phone_gps_enbale">Use os dados de GPS do telefone para substituir os dados de GPS do dispositivo bangle</string>
+    <string name="pref_title_banglejs_phone_gps_enbale">Usar dados de GPS do telefone</string>
+    <string name="pref_morning_updates_categories_summary">Lista de categorias a serem exibidas todas as manh茫s</string>
+    <string name="pref_summary_receive_calls_watch">Fa莽a e receba chamadas diretamente no rel贸gio</string>
+    <string name="dismiss">Dispensar</string>
+    <string name="prefs_wena3_notification_vibration_repetition_3">3 vezes</string>
+    <string name="pref_sleepasandroid_feat_alarms">Alarmes</string>
+    <string name="share_debug_info">Partilhar informa莽玫es de depura莽茫o</string>
+    <string name="pref_voice_detect_duration_15">15 segundos</string>
+    <string name="pref_header_heartrate_allday">Monitoramento durante todo o dia</string>
+    <string name="huawei_reparse_workout_data">Reprocessar dados do treino</string>
+    <string name="pref_dashboard_widget_today_hr_interval_summary">A quantidade de minutos que o gr谩fico mostra \"usado\" ap贸s cada medi莽茫o bem-sucedida da frequ锚ncia card铆aca</string>
+    <string name="pref_dashboard_widget_today_hr_interval_title">Intervalo de frequ锚ncia card铆aca</string>
+    <string name="huawei_reparse_workout_data_description">Isto s贸 funcionar谩 ap贸s determinadas atualiza莽玫es</string>
+    <string name="info_no_devices_connected">Nenhum dispositivo conectado</string>
+    <string name="controlcenter_set_parent_folder">Definir pasta principal</string>
+    <string name="menuitem_calendar">Calend谩rio</string>
+    <string name="pref_workout_start_on_phone_summary">Iniciar/parar o rasteamento da app de condicionamento f铆sico no telefone quando um treino com GPS for iniciado na pulseira</string>
+    <string name="pref_workout_detection_title">Detec莽茫o de treino</string>
+    <string name="seconds_15">15 segundos</string>
+    <string name="cyan">Ciano</string>
+    <string name="prefs_wena3_item_rich_design">Use um design avan莽ado</string>
+    <string name="widget_move_up">Mover para cima</string>
+    <string name="activity_detail_share_raw_summary">Partilhar resumo bruto</string>
+    <string name="steps_streaks_average_steps">M茅dia de
+\npassos</string>
+    <string name="device_name_bicycle_sensor">Sensor de bicicleta</string>
+    <string name="prefs_wena3_vibration_short">Pequeno</string>
+    <string name="pref_dashboard_widgets_order_summary">Selecione quais widgets est茫o ativados e em que ordem eles s茫o exibidos no painel</string>
+    <string name="folder">Pasta</string>
+    <string name="copied_to_clipboard">Copiado para a 谩rea de transfer锚ncia</string>
+    <string name="pref_sleepasandroid_device_title">Dispositivo provedor</string>
+    <string name="toast_camera_photo_taken">A foto foi tirada e gravada em: %s</string>
+    <string name="pref_header_generic">Gen茅rico</string>
+    <string name="pref_title_mb_intents">Transmitir diretamente as inten莽玫es dos bot玫es de m铆dia</string>
+    <string name="pref_summary_mb_intents">Ative se o controle de m茅dia do dispositivo n茫o funcionar para determinadas aplica莽玫es</string>
+    <string name="pref_summary_notification_delay_calls">Tempo antes de enviar notifica莽玫es de chamadas para o dispositivo, em segundos.</string>
+    <string name="bluetooth_calls">Chamadas Bluetooth</string>
+    <string name="bluetooth_calls_settings">Configura莽玫es de chamadas Bluetooth</string>
+    <string name="pref_display_caller_title">Mostrar informa莽玫es de contato</string>
+    <string name="pref_pair_bluetooth_calls_help_1">1. Toque no bot茫o abaixo para iniciar o processo de pareamento.</string>
+    <string name="pref_pair_bluetooth_calls_help_warning">AVISO: Se ativar as chamadas por Bluetooth sem parear com a segunda inst芒ncia, as notifica莽玫es de chamadas poder茫o n茫o funcionar como esperado.</string>
+    <string name="pref_title_device_internet_access">Permitir Acesso 脿 Internet</string>
+    <string name="pref_screen_vibration_patterns_title">Padr玫es de vibra莽茫o</string>
+    <string name="activity_db_management_autoexport_enabled_yes">A exporta莽茫o autom谩tica est谩 ativada.</string>
+    <string name="activity_db_management_autoexport_enabled_no">A exporta莽茫o autom谩tica n茫o est谩 ativada.</string>
+    <string name="activity_type_outdoor_running">Corrida ao ar livre</string>
+    <string name="activity_type_indoor_running">Corrida em ambiente fechado</string>
+    <string name="device_name_cycling_sensor">Sensor de ciclismo</string>
+    <string name="pref_header_navigation">Navega莽茫o</string>
+    <string name="prefs_password_4_digits_0_to_9_summary">A palavra-passe deve ter 4 d铆gitos, usando apenas n煤meros</string>
+    <string name="latvian">Let茫o</string>
+    <string name="persian">Persa</string>
+    <string name="ukranian">Ucraniano</string>
+    <string name="pref_show_changelog">Mostrar registo de mudan莽as na inicializa莽茫o</string>
+    <string name="action_changelog">Registo de mudan莽as</string>
+    <string name="low_power_gps">GPS de Baixa Pot锚ncia</string>
+    <string name="speed_first">Velocidade primeiro</string>
+    <string name="accuracy_first">Precis茫o primeiro</string>
+    <string name="stress_relaxed">Relaxado</string>
+    <string name="pref_force_options">For莽ar op莽玫es</string>
+    <string name="prefs_wena3_vibration_smart_item">Vibra莽茫o Inteligente</string>
+    <string name="pref_garmin_agps_help">A lista abaixo cont茅m todos os URLs solicitados pelo rel贸gio para atualiza莽玫es do AGPS. Pode selecionar um ficheiro do armazenamento do telefone que ser谩 enviado ao rel贸gio quando ele solicitar uma atualiza莽茫o.</string>
+    <string name="serbian">S茅rvio</string>
+    <string name="armenian">Arm锚nio</string>
+    <string name="activity_prefs_goals">Metas</string>
+    <string name="activity_prefs_target_weight_kg">Peso desejado em kg</string>
+    <string name="activity_prefs_goal_standing_time_minutes">Meta di谩ria: tempo em p茅 em minutos</string>
+    <string name="activity_prefs_goal_fat_burn_time_minutes">Meta di谩ria: tempo de queima de gordura em minutos</string>
+    <string name="activity_type_hunting">Ca莽a</string>
+    <string name="note">Nota</string>
+    <string name="quick_alarm">Alarme r谩pido</string>
+    <string name="pref_enable_call_accept_summary">Ativar o recebimento de chamadas do dispositivo</string>
+    <string name="contact_name">Nome</string>
+    <string name="contact_missing_name">O nome do contato est谩 vazio</string>
+    <string name="changelog_ok_button">OK</string>
+    <string name="pref_theme_dynamic_colors_not_available_warning">As cores din芒micas n茫o est茫o dispon铆veis no seu dispositivo, apenas o Android 12+ suporta essa funcionalidade. O Gadgetbridge usar谩 as cores padr茫o do Material 3.</string>
+    <string name="green">Verde</string>
+    <string name="prefs_wena3_auto_power_off_hint">O dispositivo ser谩 desligado e ligado automaticamente no hor谩rio especificado</string>
+    <string name="menuitem_running">Corrida</string>
+    <string name="widget_move_down">Mover para baixo</string>
+    <string name="widget_missing_parts">Por favor, selecione todos os widgets</string>
+    <string name="widget_unknown_workout">Treino desconhecido - %s</string>
+    <string name="pref_summary_fossil_hr_navigation_instructions">Configurar o comportamento da app de navega莽茫o no rel贸gio</string>
+    <string name="uploadwatchfaceoperation_complete">Instala莽茫o do mostrador de rel贸gio conclu铆da</string>
+    <string name="bottom_nav_dashboard">Painel</string>
+    <string name="pref_dashboard_first_title">Mostrar o painel primeiro</string>
+    <string name="pref_dashboard_first_summary">Mostrar o painel quando o Gadgetbridge for iniciado, em vez do ecr茫 de dispositivos</string>
+    <string name="pref_dashboard_widget_today_title">Gr谩fico de atividades</string>
+    <string name="pref_dashboard_all_devices_summary">Combine os dados de atividade de todos os dispositivos adicionados para obter os totais no painel</string>
+    <string name="pref_sleepasandroid_features_summary">O suporte difere de dispositivo para dispositivo</string>
+    <string name="pref_sleepasandroid_feat_movement">Aceler么metro</string>
+    <string name="pref_sleepasandroid_feat_heartrate">Frequ锚ncia card铆aca</string>
+    <string name="pref_sleepasandroid_feat_oximetry">Oximetria</string>
+    <string name="fw_upgrade_notice_amazfit_cheetah_pro">Est谩 prestes a instalar o firmware %s no seu Amazfit Cheetah Para o.
+\n
+\nA sua pulseira ser谩 reiniciada ap贸s a instala莽茫o do ficheiro .zip.
+\n
+\nPROSSIGA POR SUA PR脫PRIA CONTA E RISCO!</string>
+    <string name="fw_upgrade_notice_zepp_os">Est谩 prestes a instalar o firmware %s no seu %s.
+\n
+\nA sua pulseira ser谩 reiniciada ap贸s a instala莽茫o do ficheiro .zip.
+\n
+\nPROSSIGA POR SUA PR脫PRIA CONTA E RISCO!</string>
+    <string name="pref_pair_bluetooth_calls_help_summary">Para receber chamadas por bluetooth, 茅 necess谩rio parear o telefone com uma segunda inst芒ncia do rel贸gio.</string>
+    <string name="prefs_wena3_vibration_warning">Aviso</string>
+    <string name="info_connected_count">%d dispositivos conectados</string>
+    <string name="error_deleting_device">Erro ao apagar o dispositivo: %s</string>
+    <string name="fossil_hr_pairing_successful">Pareamento bem-sucedido com o rel贸gio</string>
+    <string name="title">T铆tulo</string>
+    <string name="yellow">Amarelo</string>
+    <string name="prefs_wena3_title_screen">Configura莽玫es de Exibi莽茫o</string>
+    <string name="prefs_wena3_item_background_sync">Sincroniza莽茫o de dados de atividade em segundo plano</string>
+    <string name="prefs_wena3_item_large_font">Tamanho da Fonte Maior</string>
+    <string name="prefs_wena3_vibration_strength_item_medium">M茅dio</string>
+    <string name="pref_force_connection_type_description">Pode tentar for莽ar o tipo de conex茫o caso o seu dispositivo n茫o responda ao Gadgetbridge</string>
+    <string name="pref_force_connection_type_auto">Autom谩tico</string>
+    <string name="pref_test_features_summary">Recursos ativados para esse dispositivo de teste</string>
+    <string name="uploadwatchfaceoperation_in_progress">A carregar o mostrador de rel贸gio</string>
+    <string name="pref_sleepasandroid_slot_summary">Qual slot de alarme usar ao definir alarmes</string>
+    <string name="alarm_slot_reset">O slot de alarme foi definido como padr茫o</string>
+    <string name="none">Nenhum</string>
+    <string name="unsupported">N茫o suportado</string>
+    <string name="min_val">M铆nimo: %d</string>
+    <string name="battery_percentage_str">%1$s%%</string>
+    <string name="devicetype_huawei_watchfit2">Huawei Watch Fit 2</string>
+    <string name="menuitem_last_workout">脷ltimo treino</string>
+    <string name="menuitem_volume">Volume</string>
+    <string name="hrZoneAerobic">Aer贸bico</string>
+    <string name="huawei_trusleep_warning">Aviso: ativar isto impedir谩 que o sono apare莽a no Gadgetbridge! Clique aqui se o aceita.</string>
+    <string name="pref_title_opentracks_packagename">Nome do pacote do OpenTracks</string>
+    <string name="pref_summary_navigation_forward">Encaminhar instru莽玫es de apps de navega莽茫o para o rel贸gio</string>
+    <string name="pref_enable_call_accept">Ativar o recebimento de chamadas</string>
+    <string name="controlcenter_add_new_folder">Adicionar nova pasta</string>
+    <string name="activity_prefs_summary_allow_bluetooth_intent_api">Permitir o controle da conex茫o Bluetooth por meio da API de inten莽茫o</string>
+    <string name="intent_api_allow_activity_sync_title">Permitir o acionamento da sincroniza莽茫o de atividades</string>
+    <string name="intent_api_allow_trigger_export_summary">Permitir o acionamento da exporta莽茫o do banco de dados por meio da API de Inten莽茫o</string>
+    <string name="intent_api_broadcast_export_title">Anunciar na exporta莽茫o do banco de dados</string>
+    <string name="intent_api_broadcast_activity_sync_title">Anunciar no t茅rmino da sincroniza莽茫o da atividade</string>
+    <string name="intent_api_allow_debug_commands_summary">Permitir o acionamento de comandos do menu de depura莽茫o por meio da API de Inten莽茫o</string>
+    <string name="pref_title_debug">Solicita莽茫o de depura莽茫o</string>
+    <string name="address">Endere莽o</string>
+    <string name="changelog_title">O que h谩 de novo</string>
+    <string name="contact_phone_number">N煤mero de telefone</string>
+    <string name="loyalty_cards_sync_options">Op莽玫es de Sincroniza莽茫o</string>
+    <string name="loyalty_cards_catima_permissions_title">Permiss玫es faltando</string>
+    <string name="withings_bt_calibration_previous">Anterior</string>
+    <string name="prefs_wena3_button_action_name_activity_screen">Ecr茫 de Atividade</string>
+    <string name="prefs_wena3_notification_settings_title">Configura莽玫es de Notifica莽茫o</string>
+    <string name="prefs_wena3_notification_vibration_repetition_1">Uma vez</string>
+    <string name="widget_screen_delete_confirm_title">Apagar ecr茫 de widget</string>
+    <string name="pref_speak_notifications_aloud_title">Falar as notifica莽玫es em voz alta</string>
+    <string name="devicetype_cycling_sensor">Sensor de velocidade de ciclismo</string>
+    <string name="title_cycling">Ciclismo</string>
+    <string name="chart_cycling_point_label_speed">%.1f km/h</string>
+    <string name="open_camera">Abrir c芒mara</string>
+    <string name="no_folder_selected">Nenhuma pasta selecionada</string>
+    <string name="busy_task_fetch_pai_data">A coletar dados de PAI</string>
+    <string name="notif_battery_full_title">Pilha do gadget cheia!</string>
+    <string name="notif_battery_full">%1$s pilha cheia</string>
+    <string name="pai_chart_per_week">PAI por semana</string>
+    <string name="seconds_14">14 segundos</string>
+    <string name="seconds_25">25 segundos</string>
+    <string name="english_ca">Ingl锚s (Canad谩)</string>
+    <string name="english_gb">Ingl锚s (Reino Unido)</string>
+    <string name="activity_type_mountain_hike">Caminhada na montanha</string>
+    <string name="activity_type_street_dance">Dan莽a de Rua</string>
+    <string name="activity_type_other">Outros</string>
+    <string name="prefs_wena3_home_icon_name_calories">Calorias</string>
+    <string name="prefs_wena3_menu_icon_name_payment">Pagamento</string>
+    <string name="prefs_wena3_menu_icon_title">脥cones de menus</string>
+    <string name="dashboard_settings">Configura莽玫es do painel</string>
+    <string name="menuitem_workout_status">Estado do treino</string>
+    <string name="menuitem_unknown_app">Desconhecido (%s)</string>
+    <string name="hrZoneAnaerobic">Anaer贸bico</string>
+    <string name="steps_streaks_total_steps_hint">Quantidade total de passos em toda a sequ锚ncia</string>
+    <string name="step_streaks_achievements_sharing_title">Conquistas de passos</string>
+    <string name="changelog_full_title">Registo de mudan莽as</string>
+    <string name="changelog_show_full">Mais鈥�</string>
+    <string name="pref_navigation_app_osmand">OsmAnd(+)</string>
+    <string name="pref_speak_notifications_aloud_summary">As notifica莽玫es ser茫o lidas em voz alta pelos fones de ouvido</string>
+    <string name="garmin_agps_url_i">URL do AGPS %1$d</string>
+    <string name="authentication_failed_check_key">Falha na autentica莽茫o, verifique a chave de autentica莽茫o</string>
+    <string name="activity_type_taekwondo">Taekwondo</string>
+    <string name="activity_type_aerobic_exercise">Exerc铆cio aer贸bico</string>
+    <string name="activity_type_outdoor_walking">Caminhada ao ar livre</string>
+    <string name="activity_type_indoor_walking">Caminhada em ambiente fechado</string>
+    <string name="activity_type_surfing">Surfar</string>
+    <string name="activity_type_gymnastics">Gin谩stica</string>
+    <string name="activity_type_volleyball">Voleibol</string>
+    <string name="activity_type_table_tennis">T茅nis de Mesa</string>
+    <string name="unbind_before_pair_message">Este dispositivo j谩 est谩 vinculado nas configura莽玫es do Android, o que pode fazer com que o pareamento falhe em alguns dispositivos.
+\n
+\nSe a adi莽茫o do dispositivo falhar, remova-o nas configura莽玫es do Android e tente novamente.</string>
+    <string name="pref_dashboard_widget_double_size_title">Tamanho duplo</string>
+    <string name="pref_workout_detection_sensitivity">Sensibilidade</string>
+    <string name="busy_task_fetch_spo2_data">A coletar dados de SpO2</string>
+    <string name="busy_task_fetch_temperature">A coletar dados de temperatura</string>
+    <string name="busy_task_fetch_stress_data">A coletar dados de estresse</string>
+    <string name="seconds_7">7 segundos</string>
+    <string name="autoExport_lastTime_label">脷ltima exporta莽茫o autom谩tica: %1$s</string>
+    <string name="pref_enable_call_reject">Ativar a rejei莽茫o de chamadas</string>
+    <string name="pref_enable_call_reject_summary">Ativar a rejei莽茫o de chamadas do dispositivo</string>
+    <string name="controlcenter_set_preferences">Definir prefer锚ncias</string>
+    <string name="step_streak_longest">Mais longo</string>
+    <string name="prefs_wena3_title_alarm">Configura莽玫es de Alarme</string>
+    <string name="prefs_wena3_vibration_strength_item">For莽a de Vibra莽茫o</string>
+    <string name="pref_summary_fossil_hr_nav_foreground">Se a app de navega莽茫o deve vir automaticamente para o primeiro plano quando receber uma instru莽茫o de navega莽茫o</string>
+    <string name="pref_summary_fossil_hr_nav_vibrate">Se o rel贸gio deve vibrar a cada instru莽茫o de navega莽茫o nova ou alterada (somente quando a app estiver em primeiro plano)</string>
+    <string name="uploading_watchface">A carregar o mostrador de rel贸gio鈥�</string>
+    <string name="activity_info">Informa莽玫es sobre a atividade</string>
+    <string name="pref_dashboard_widget_goals_chart_title">Gr谩fico de metas</string>
+    <string name="pref_dashboard_select_devices_title">Selecionar dispositivos...</string>
+    <string name="pref_speak_notifications_focus_exclusive_title">Pausar o 谩udio de outras aplica莽玫es</string>
+    <string name="pref_speak_notifications_focus_exclusive_summary_on">A reprodu莽茫o de outras aplica莽玫es ser谩 pausada enquanto a notifica莽茫o estiver a ser falada</string>
+    <string name="max_val">M谩ximo: %d</string>
+    <string name="show_in_notification">Mostrar em notifica莽茫o</string>
+    <string name="battery_low_notify_enabled">Notificar quando a pilha estiver fraca</string>
+    <string name="battery_low_threshold">Limite de pilha baixa</string>
+    <string name="battery_full_notify_enabled">Notificar quando a pilha estiver cheia</string>
+    <string name="menuitem_workout_shortcuts">Atalhos de treino</string>
+    <string name="pref_title_send_app_notifications">Enviar notifica莽玫es</string>
+    <string name="pref_summary_send_app_notifications">Enviar notifica莽玫es de apps para o dispositivo</string>
+    <string name="pref_title_overwrite_settings_on_connection">Sobrescrever configura莽玫es na conex茫o</string>
+    <string name="pref_theme_dynamic_colors_explanation">Observa莽茫o: para o tema de cores din芒micas, 茅 necess谩rio ativar as cores do papel de parede ou a paleta de cores nas configura莽玫es de apar锚ncia do Android 12+. Se n茫o fizer isso, o Gadgetbridge usar谩 as cores padr茫o do Material 3.</string>
+    <string name="pref_title_preview_message_in_title">Mostrar uma pr茅via da mensagem no t铆tulo</string>
+    <string name="pref_summary_preview_message_in_title">Mostra uma pr茅via da mensagem no t铆tulo de uma notifica莽茫o, conforme permitido pelo dispositivo</string>
+    <string name="pref_title_casio_alert_other">Alerta para outras notifica莽玫es</string>
+    <string name="pai_chart_per_month">PAI por m锚s</string>
+    <string name="gpx_route_upload_complete">Carregamento da rota Gpx conclu铆do</string>
+    <string name="bengali">Bengal锚s</string>
+    <string name="prefs_wena3_title_activity">Configura莽玫es de Atividade</string>
+    <string name="widget_layout_single">1 widget</string>
+    <string name="seconds_8">8 segundos</string>
+    <string name="seconds_9">9 segundos</string>
+    <string name="seconds_11">11 segundos</string>
+    <string name="seconds_12">12 segundos</string>
+    <string name="activity_type_rower">Remador</string>
+    <string name="menuitem_unsupported">[SEM SUPORTE] %s</string>
+    <string name="text_receiver_activity_title">Enviar texto para o dispositivo</string>
+    <string name="red">Vermelho</string>
+    <string name="prefs_wena3_hint_large_font">Aumentar o tamanho da fonte no calend谩rio, nas notifica莽玫es, etc.</string>
+    <string name="appmanager_app_download">Descarga para o cache</string>
+    <string name="appmanager_item_outdated">(desatualizado)</string>
+    <string name="appmanager_download_started">Iniciado Descarga da app</string>
+    <string name="appmanager_downloaded_to_cache">Descarga de %s para o cache</string>
+    <string name="appmanager_download_app_error">Erro no descarga da app</string>
+    <string name="fw_upgrade_notice_amazfit_gts3">Est谩 prestes a instalar o firmware %s no seu Amazfit GTS 3.
+\n
+\nO seu rel贸gio ir谩 reiniciar ap贸s instalar o ficheiro .zip.
+\n
+\nSIGA POR SUA PR脫PRIA CONTA E RISCO!</string>
+    <string name="fw_upgrade_notice_amazfit_gts4_mini">Est谩 prestes a instalar o firmware %s no seu Amazfit GTS 4 Mini.
+\n
+\nA sua pulseira ser谩 reiniciada ap贸s a instala莽茫o do ficheiro .zip.
+\n
+\nPROSSIGA POR SUA PR脫PRIA CONTA E RISCO!</string>
+    <string name="fw_upgrade_notice_amazfit_gtr3">Est谩 prestes a instalar o firmware %s no seu Amazfit GTR 3.
+\n
+\nA sua pulseira ser谩 reiniciada ap贸s a instala莽茫o do ficheiro .zip.
+\n
+\nPROSSIGA POR SUA PR脫PRIA CONTA E RISCO!</string>
+    <string name="fw_upgrade_notice_amazfit_gtr4">Est谩 prestes a instalar o firmware %s no seu Amazfit GTR 4.
+\n
+\nA sua pulseira ser谩 reiniciada ap贸s a instala莽茫o do ficheiro .zip.
+\n
+\nPROSSIGA POR SUA PR脫PRIA CONTA E RISCO!</string>
+    <string name="fw_upgrade_notice_amazfit_trex2">Est谩 prestes a instalar o firmware %s no seu Amazfit T-Rex 2.
+\n
+\nA sua pulseira ser谩 reiniciada ap贸s a instala莽茫o do ficheiro .zip.
+\n
+\nPROSSIGA POR SUA PR脫PRIA CONTA E RISCO!</string>
+    <string name="fw_upgrade_notice_amazfit_band7">Est谩 prestes a instalar o firmware %s no seu Amazfit Band 7.
+\n
+\nA sua pulseira ser谩 reiniciada ap贸s a instala莽茫o do ficheiro .zip.
+\n
+\nPROSSIGA POR SUA PR脫PRIA CONTA E RISCO!</string>
+    <string name="open_fw_installer_info_text">O instalador de Firmware/Tema/App/Ficheiro permite o envio/instala莽茫o de ficheiros suportados (firmware, temas, aplica莽玫es, GPS, recursos, fontes...) para o dispositivo. Veja outras informa莽玫es na wiki: https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Firmware-Update</string>
+    <string name="open_fw_installer_warning_text">Esta funcionalidade pode quebrar o seu aparelho. Dito isto, este fato nunca aconteceu com nenhum dos programadores, mas lembre-se que est谩 por sua conta e risco.</string>
+    <string name="open_fw_installer_connect_minimum_one_device">Conecte pelo menos um dispositivo para o qual deseja enviar o ficheiro.</string>
+    <string name="pref_title_notifications_ignore_low_priority">Ignorar notifica莽玫es de baixa prioridade</string>
+    <string name="pref_summary_notifications_ignore_low_priority">N茫o enviar notifica莽玫es de prioridade baixa e m铆nima para o rel贸gio</string>
+    <string name="pref_summary_notifications_ignore_work_profile">N茫o enviar notifica莽玫es de apps no perfil de trabalho para o rel贸gio</string>
+    <string name="pref_title_notification_prefer_long_text">Preferir um texto de notifica莽茫o longo</string>
+    <string name="pref_summary_notification_prefer_long_text">Se dispon铆vel, envie o texto longo da notifica莽茫o para o dispositivo</string>
+    <string name="pref_title_notification_cache_while_disconnected">Cache enquanto estiver fora do alcance</string>
+    <string name="pref_summary_notification_cache_while_disconnected">Envie notifica莽玫es perdidas quando um dispositivo se reconectar ap贸s ficar fora do alcance</string>
+    <string name="pref_title_banglejs_phone_gps_network_only">Usar a rede apenas para determinar a localiza莽茫o</string>
+    <string name="pref_title_banglejs_phone_gps_update_interval">Intervalo de atualiza莽茫o de dados do GPS</string>
+    <string name="pref_summary_banglejs_phone_gps_update_interval">O intervalo de atualiza莽茫o da posi莽茫o GPS, em ms</string>
+    <string name="preferences_miband_1_2_warning">Aviso: Essas prefer锚ncias se aplicam apenas 脿s Mi Bands 1 e 2.</string>
+    <string name="pref_canned_message">Mensagem</string>
+    <string name="pref_header_intent_api">API de Inten莽玫es (Intent API)</string>
+    <string name="pref_header_authentication">Autentica莽茫o</string>
+    <string name="pref_summary_casio_alert_calendar">Alerta (vibra莽茫o/bip) para notifica莽玫es de calend谩rio</string>
+    <string name="pref_title_casio_alert_call">Alerta para chamadas recebidas</string>
+    <string name="pref_summary_casio_alert_call">Alerta (vibra莽茫o/bip) para chamadas recebidas</string>
+    <string name="pref_title_casio_alert_email">Alerta para notifica莽玫es por e-mail</string>
+    <string name="pref_summary_casio_alert_email">Alerta (vibra莽茫o/bip) para notifica莽玫es por e-mail</string>
+    <string name="pref_summary_casio_alert_sms">Alerta (vibrar/bipar) para notifica莽玫es de SMS (mensagem de texto)</string>
+    <string name="pref_summary_casio_alert_other">Alerta (vibrar/bipar) para notifica莽玫es em outra categoria</string>
+    <string name="pref_summary_device_internet_access">Permitir que os apps deste dispositivo acedem a Internet</string>
+    <string name="pref_title_device_intents">Permitir Inten莽茫o</string>
+    <string name="pref_summary_device_intents">Permitir que as apps de rel贸gio Bangle.js enviem Android Intents e que outras apps no Android (como o Tasker) enviem dados para o Bangle.js com o com.banglejs.uart.tx Intent. Precisa de permiss茫o para ser exibido sobre outras apps para funcionar em segundo plano.</string>
+    <string name="femometer_measurement_mode_quick">Modo R谩pido (30s)</string>
+    <string name="pref_gps_header">GPS</string>
+    <string name="offline_voice_respond_screen_on">Responder quando o ecr茫 estiver ligado</string>
+    <string name="pref_morning_updates_summary">Mostrar atualiza莽玫es toda manh茫</string>
+    <string name="pref_workout_start_on_phone_title">Rasteamento da app de condicionamento f铆sico</string>
+    <string name="pref_workout_send_gps_title">Enviar GPS durante o treino</string>
+    <string name="pref_workout_send_gps_summary">Enviar a localiza莽茫o GPS atual para a pulseira durante um treino</string>
+    <string name="pref_workout_keep_screen_on_summary">O ecr茫 permanecer谩 ligado durante o treino e o brilho ser谩 ajustado para exibir continuamente os dados do treino em tempo real</string>
+    <string name="pref_workout_detection_summary">Detetar o treino automaticamente</string>
+    <string name="pref_workout_detection_enabled_summary">Ativar a detec莽茫o autom谩tica desse treino</string>
+    <string name="pref_workout_detection_ask_first">Pergunte-me primeiro</string>
+    <string name="pref_workout_detection_ask_first_summary">Exigir confirma莽茫o no rel贸gio ao detetar esse treino</string>
+    <string name="pref_workout_detection_time_summary">Quantidade de minutos que o treino deve estar em andamento antes de ser detetado</string>
+    <string name="preferences_miband_1_2_settings" tools:ignore="TypographyFractions">Configura莽玫es da Mi Band 1/2</string>
+    <string name="pref_screen_vibration_patterns_summary">Configure os padr玫es de vibra莽茫o para diferentes notifica莽玫es</string>
+    <string name="spo2_perc_80">80%</string>
+    <string name="spo2_perc_85">85%</string>
+    <string name="pref_world_clocks_title">Rel贸gios mundiais</string>
+    <string name="pref_contacts_title">Contatos</string>
+    <string name="pref_contacts_summary">Configurar contatos no rel贸gio</string>
+    <string name="contact_delete_confirm_title">Apagar contato</string>
+    <string name="function_enabled">Ativado</string>
+    <string name="title_activity_contact_details">Pormenores de contato</string>
+    <string name="alarm_smart_wakeup_interval_default">5 minutos</string>
+    <string name="prefs_spo2_monitoring_title">Monitoramento de oxig锚nio no sangue</string>
+    <string name="prefs_activity_monitoring_title">Monitoramento de atividade</string>
+    <string name="gpx_route_upload_failed">Falha no carregamento da rota Gpx</string>
+    <string name="pref_enable_unsupported_settings_title">Ativar configura莽玫es n茫o suportadas</string>
+    <string name="abstract_chart_fragment_kind_rem_sleep">Sono REM</string>
+    <string name="pref_enable_unsupported_settings_summary">Isto permitir谩 o acesso a todas as configura莽玫es dispon铆veis, mesmo que n茫o sejam compat铆veis com o dispositivo. Isto pode causar instabilidade e falhas no dispositivo.</string>
+    <string name="mi5_prefs_workout_activity_types">Tipos de atividades de treino</string>
+    <string name="manual">Manual</string>
+    <string name="english_in">Ingl锚s (脥ndia)</string>
+    <string name="english_us">Ingl锚s (Estados Unidos)</string>
+    <string name="spanish_us">Espanhol (Estados Unidos)</string>
+    <string name="georgian">Georgiano</string>
+    <string name="croatian">Croata</string>
+    <string name="estonian">Estoniano</string>
+    <string name="lithuanian">Lituano</string>
+    <string name="scandinavian">Escandinavo</string>
+    <string name="portuguese_pt">Portugu锚s (Portugal)</string>
+    <string name="activity_db_management_autoexport_scheduled_no">A exporta莽茫o autom谩tica n茫o foi programada.</string>
+    <string name="quick_alarm_description">Alarme do widget</string>
+    <string name="permission_display_over_other_apps">%1$s precisa de permiss茫o para ser exibido sobre outras apps a fim de permitir que os rel贸gios Bangle.js iniciem atividades por meio de inten莽玫es quando %1$s estiver em segundo plano.
+\n
+\nIsto pode ser usado para iniciar uma apps de m煤sica e reproduzir uma m煤sica e muitas outras coisas.
+\n
+\nToque em \"%2$s\" e depois em \"%1$s\" e ative a op莽茫o \"Permitir sobrepor outras apps\", depois toque em \"Voltar\" para retornar a %1$s.
+\n
+\nPara que o %1$s pare de solicitar permiss玫es, v谩 para \"Configura莽玫es\" e desmarque \"Verificar estado da permiss茫o\".
+\n
+\nCertifique-se de conceder ao %1$s as permiss玫es necess谩rias para que ele funcione como espera.</string>
+    <string name="controlcenter_connected_fraction">Conectado: %d/%d</string>
+    <string name="controlcenter_set_folder_title">Definir ou criar uma nova pasta</string>
+    <string name="steps_streaks_since_date">Desde %s</string>
+    <string name="step_streak_average_steps_hint">M茅dia de passos por dia da sequ锚ncia</string>
+    <string name="steps_streaks_total_steps_average_hint">M茅dia total de %d passos por dia</string>
+    <string name="step_streaks_achievements_sharing_message">As minhas conquistas di谩rias de passos!</string>
+    <string name="intent_api_broadcast_export_summary">Anunciar uma inten莽茫o quando a exporta莽茫o do banco dados finalizar</string>
+    <string name="prefs_wena3_item_weather_statusbar">Clima na Barra de Estado</string>
+    <string name="prefs_wena3_vibration_strength_item_strong">Forte</string>
+    <string name="widget_layout_top_2_bot_1">2 superiores, 1 inferior</string>
+    <string name="widget_layout_two">2 widgets</string>
+    <string name="widget_layout_top_wide_bot_large">Amplo acima, grande abaixo</string>
+    <string name="pref_force_connection_type_title">For莽ar tipo de conex茫o</string>
+    <string name="unbind_before_pair_title">J谩 vinculado</string>
+    <string name="pref_sleepasandroid_slot_title">Slot de alarmes</string>
+    <string name="toast_camera_permission_required">A permiss茫o da c芒mara 茅 necess谩ria para essa fun莽茫o.</string>
+    <string name="folder_is_empty">A pasta est谩 vazia</string>
+    <string name="loading">A carregar鈥�</string>
+    <string name="toggle_debug_mode">Alternar modo de depura莽茫o</string>
+    <string name="battery_full_threshold">Limite de pilha cheia</string>
+    <string name="default_percentage">Padr茫o (%1$d%%)</string>
+    <string name="pref_title_goal_secondary">Meta secund谩ria</string>
+    <string name="pref_morning_updates_categories_title">Categorias de atualiza莽玫es matinais</string>
+    <string name="title_activity_set_contacts">Configurar contatos</string>
+    <string name="title_activity_world_clock_details">Pormenores do Rel贸gio Mundial</string>
+    <string name="pref_title_navigation_forward">Enviar navega莽茫o para o rel贸gio</string>
+    <string name="prefs_home_icon_center_item">Centro</string>
+    <string name="prefs_wena3_notification_vibration_repetition_4">4 vezes</string>
+    <string name="blue">Azul</string>
+    <string name="Pace">Ritmo</string>
+    <string name="pref_camera_remote_summary">Permite que o rel贸gio acione a c芒mara do telefone</string>
+    <string name="agps_status_error">Erro</string>
+    <string name="pref_morning_updates_title">Atualiza莽玫es matinais</string>
+    <string name="pref_workout_keep_screen_on_title">Manter e ecr茫 ligado durante o treino</string>
+    <string name="pref_workout_detection_categories_title">Categorias de treino</string>
+    <string name="pref_workout_detection_categories_summary">Categorias de treino para detetar automaticamente</string>
+    <string name="pref_workout_detection_enabled">Detec莽茫o ativada</string>
+    <string name="busy_task_fetch_sports_summaries">A coletar dados de resumos desportivos</string>
+    <string name="busy_task_fetch_sports_details">A coletar dados de pormenores desportivos</string>
+    <string name="busy_task_fetch_sleep_respiratory_rate_data">A coletar dados de frequ锚ncia respirat贸ria do sono</string>
+    <string name="busy_task_fetch_statistics">A coletar estat铆sticas</string>
+    <string name="prefs_stress_monitoring_description">Monitorar o n铆vel de estresse durante o repouso</string>
+    <string name="seconds_13">13 segundos</string>
+    <string name="mi2_prefs_heart_rate_monitoring_summary">Configurar o monitoramento da frequ锚ncia card铆aca</string>
+    <string name="prefs_phone_silent_mode">Modo silencioso do telefone</string>
+    <string name="silent_mode_normal_vibrate">Normal / Vibrar</string>
+    <string name="silent_mode_normal_silent">Normal / Silencioso</string>
+    <string name="prefs_password">Palavra-passe</string>
+    <string name="prefs_password_4_digits_1_to_4_summary">A palavra-passe deve ter 4 d铆gitos, usando os n煤meros de 1 a 4</string>
+    <string name="prefs_password_6_digits_0_to_9_summary">A palavra-passe deve ter 6 d铆gitos, usando apenas n煤meros</string>
+    <string name="mi2_prefs_heart_rate_monitoring_alerts_summary">Configure o monitoramento da frequ锚ncia card铆aca e os limites de alerta</string>
+    <string name="prefs_password_enabled">Palavra-passe Ativada</string>
+    <string name="french_ca">Franc锚s (Canad谩)</string>
+    <string name="french_fr">Franc锚s (Fran莽a)</string>
+    <string name="portuguese_br">Portugu锚s (Brasil)</string>
+    <string name="active_time">Tempo ativo</string>
+    <string name="currentWorkoutLoad">Carga de treino</string>
+    <string name="sony_anc_optimizer_status_analyzing">A analisar鈥�</string>
+    <string name="steps_streaks_streak_days">Dias
+\nseguidos</string>
+    <string name="connection_over_bt_classic">Conex茫o via Bluetooth classic</string>
+    <string name="steps_streaks">Sequ锚ncia de passos</string>
+    <string name="steps_streaks_hint">Sequ锚ncia de dias consecutivos sem interrup莽茫o com metas de passos sendo alcan莽adas</string>
+    <string name="step_streak_ongoing">Em andamento</string>
+    <string name="step_streak_total">Total</string>
+    <string name="widget_screen_delete_confirm_description">Tem certeza de que deseja apagar \'%1$s\'?</string>
+    <string name="widget_layout_top_2_bot_2">2 superiores, 2 inferiores</string>
+    <string name="not_set">N茫o definido</string>
+    <string name="pref_vitality_score_title">Pontua莽茫o de vitalidade</string>
+    <string name="pref_vitality_score_daily_summary">Receba uma notifica莽茫o quando atingir o n煤mero m谩ximo de pontos de vitalidade do dia</string>
+    <string name="pref_auto_reply_calls_summary">O telefone atender谩 automaticamente as chamadas telef么nicas recebidas</string>
+    <string name="pref_auto_reply_calls_title">Atender chamadas telef么nicas automaticamente</string>
+    <string name="pref_title_wheel_diameter">Di芒metro da roda</string>
+    <string name="pref_summary_cycling_persistence_interval">Intervalo em segundos em que os dados de ciclagem atuais devem ser gravados no banco de dados</string>
+    <string name="pref_title_cycling_persistence_interval">Intervalo de persist锚ncia</string>
+    <string name="fw_upgrade_notice_amazfit_gts4">Est谩 prestes a instalar o firmware %s no seu Amazfit GTS4.
+\n
+\nO seu rel贸gio ir谩 reiniciar ap贸s instalar o ficheiro .zip.
+\n
+\nSIGA POR SUA PR脫PRIA CONTA E RISCO!</string>
+    <string name="pref_cache_weather_summary">As informa莽玫es do clima ser茫o armazenadas em cache durante as reinicializa莽玫es da aplica莽茫o.</string>
+    <string name="share_screenshot">Partilhar captura de ecr茫</string>
+    <string name="impactMax">Impacto m谩ximo</string>
+    <string name="flatDistance">Dist芒ncia Plana</string>
+    <string name="fwapp_install_device_not_supported">O ficheiro n茫o pode ser instalado, o dispositivo n茫o 茅 compat铆vel.</string>
+    <string name="updatefirmwareoperation_updateproblem_free_space">O dispositivo n茫o tem espa莽o livre suficiente</string>
+    <string name="updatefirmwareoperation_updateproblem_low_battery">A pilha do dispositivo est谩 muito fraca</string>
+    <string name="moondrop_touch_action_call_pick_hang">Atender a liga莽茫o</string>
+    <string name="pref_battery_polling_interval_format">a cada %1$s minutos</string>
+    <string name="minutes_20">20 minutos</string>
+    <string name="sleep_colored_stats_light">Claro</string>
+    <string name="activity_error_share_failed">Falha no compartilhamento do ficheiro.</string>
+    <string name="activity_type_flexibility">Flexibilidade</string>
+    <string name="activity_type_outdoor_cycling">Ciclismo ao ar livre</string>
+    <string name="watchface_dialog_title_widget">Configura莽玫es do widget</string>
+    <string name="hrv_status_seven_days_avg_status">Estado</string>
+    <string name="devicetype_mijia_lywsd03">Mijia Sensor de Temperatura e Humidade 2</string>
+    <string name="protocol_version">Vers茫o do Protocolo</string>
+    <string name="pref_screen_auto_brightness_title">Brilho autom谩tico</string>
+    <string name="menuitem_thermometer">Term么metro</string>
+    <string name="activity_type_rock_climbing">Escalada em rocha</string>
+    <string name="permission_notification_listener">%1$s precisa de acesso 脿s Notifica莽玫es para exibi-las no rel贸gio quando o ecr茫 do telefone estiver desligada.
+\n
+\nToque em \'%2$s\' e depois em \'%1$s\' e ative \'Permitir acesso a notifica莽玫es\' e, em seguida, toque em \'Voltar\' para retornar a %1$s.</string>
+    <string name="impactAvg">Impacto m茅dio</string>
+    <string name="moondrop_touch_action_call_start">Iniciar uma liga莽茫o</string>
+    <string name="pai_total">Total</string>
+    <string name="soundcore_equalizer_preset_balanced">Equilibrado</string>
+    <string name="proprietary_app_warning">Este recurso requer a instala莽茫o de uma app propriet谩ria</string>
+    <string name="elevationLoss">Descida</string>
+    <string name="activity_type_darts">Dardos</string>
+    <string name="activity_type_archery">Arco e Flecha</string>
+    <string name="activity_type_stairs">Escadas</string>
+    <string name="activity_type_snow_sports">Desportos na neve</string>
+    <string name="activity_type_dancing">Dan莽ar</string>
+    <string name="state_scanned">Escaneado</string>
+    <string name="elevationGain">Ganho de eleva莽茫o</string>
+    <string name="menuitem_forecast">Previs茫o</string>
+    <string name="moondrop_touch_action_assistant">Iniciar o Assistente de Voz</string>
+    <string name="soundcore_button_brightness_medium">M茅dio</string>
+    <string name="watchface_dialog_widget_cat_generic">Gen茅rico</string>
+    <string name="activity_db_management_autoexport_location">N茫o foi poss铆vel perceber a localiza莽茫o. Provavelmente um problema do sistema de permiss茫o mais recente do Android. Muito provavelmente, a exporta莽茫o autom谩tica n茫o funciona no momento.</string>
+    <string name="activity_type_golf">Golfe</string>
+    <string name="screenshot_taken">Captura de ecr茫 feita</string>
+    <string name="prefs_control_center">Centro de Controle</string>
+    <string name="activity_type_ballet">Bal茅</string>
+    <string name="activity_type_tennis">T茅nis</string>
+    <string name="activity_type_trail_run">Corrida em trilha</string>
+    <string name="menuitem_offline_voice">Voz offline</string>
+    <string name="activity_type_martial_arts">Artes marciais</string>
+    <string name="activity_type_fishing">Pesca</string>
+    <string name="activity_type_australian_football">Futebol australiano</string>
+    <string name="activity_type_rugby">R煤gbi</string>
+    <string name="activity_type_rowing">Remo</string>
+    <string name="activity_type_boxing">Boxe</string>
+    <string name="menuitem_recommendation">Recomenda莽茫o</string>
+    <string name="activity_type_karate">Karat锚</string>
+    <string name="stress_average">M茅dio</string>
+    <string name="activity_type_dance">Dan莽a</string>
+    <string name="devicetype_scannable">Dispositivo escane谩vel</string>
+    <string name="activity_type_football">Futebol</string>
+    <string name="activity_type_parachuting">Paraquedismo</string>
+    <string name="pref_summary_notification_times_enabled">Enviar notifica莽玫es somente entre hor谩rios espec铆ficos</string>
+    <string name="activity_type_rem_sleep">Sono REM</string>
+    <string name="activity_type_weightlifting">Levantamento de peso</string>
+    <string name="battery_polling_failed_start">Falha ao iniciar a captura de dados da pilha</string>
+    <string name="pref_title_nagivation_apps">Apps de navega莽茫o</string>
+    <string name="permission_location">%1$s precisa de acesso 脿 sua localiza莽茫o em segundo plano para permitir que ele permane莽a conectado ao seu rel贸gio mesmo quando o ecr茫 estiver desligada. \n \nToque em \'%2$s\' para concordar.</string>
+    <string name="moondrop_touch_action_anc_mode">Ativar o modo de cancelamento de ru铆do</string>
+    <string name="pref_title_banglejs_txt_bitmap_size">Tamanho dos bitmaps de texto</string>
+    <string name="pref_title_notification_times_enabled">Horas de notifica莽茫o</string>
+    <string name="pref_title_banglejs_text_bitmap">Texto como bitmaps</string>
+    <string name="pref_summary_banglejs_txt_bitmap_size">Tamanho a usar para renderiza莽茫o de bitmaps de texto</string>
+    <string name="pref_summary_banglejs_text_bitmap">Se uma palavra n茫o puder ser renderiszada com a fonte do rel贸gio, ser谩 renderizada para um bitmap no Gadgetbridge e mostrada no rel贸gio</string>
+    <string name="pref_message_privacy_mode_bodyonly">Esconder apenas o corpo</string>
+    <string name="pref_header_sound">Som</string>
+    <string name="search">Procurar</string>
+    <string name="single_band">Frequ锚ncia 脷nica</string>
+    <string name="dual_band">Frequ锚ncia Dupla</string>
+    <string name="gps_glonass">GPS + GLONASS</string>
+    <string name="pref_header_external_integrations">Integra莽玫es externas</string>
+    <string name="pref_header_automations">Automa莽玫es</string>
+    <string name="pref_description_general">Arranque, idioma, regi茫o, localiza莽茫o</string>
+    <string name="pref_description_about_you">Ano de nascimento, g茅nero, altura, peso, metas</string>
+    <string name="pref_description_notifications">Notifica莽玫es de apps, bloqueio/desbloqueio</string>
+    <string name="pref_description_user_interface">Tema, ecr茫 principal</string>
+    <string name="pref_description_dashboard">Widgets, dispositivos a incluir</string>
+    <string name="pref_description_automations">Exporta莽茫o / coleta autom谩tica</string>
+    <string name="pref_description_developer_options">Logs, Intent API</string>
+    <string name="pref_description_deprecated_functionalities">Defini莽玫es que ser茫o removidas numa vers茫o futura</string>
+    <string name="pref_header_main_screen">Ecr茫 principal</string>
+    <string name="pref_title_banglejs_webview_url">URL do app loader</string>
+    <string name="pref_summary_banglejs_webview_url">Para utilizar um app loader diferente, introduza a sua https://鈥�/android.html URL aqui. Deixe em branco para https://banglejs.com/apps</string>
+</resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml
new file mode 100644
index 0000000..9dad987
--- /dev/null
+++ b/app/src/main/res/values-ro/strings.xml
@@ -0,0 +1,1181 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources xmlns:tools="http://schemas.android.com/tools">
+    <string name="application_name_generic">Gadgetbridge</string>
+    <string name="title_activity_controlcenter_generic">Gadgetbridge</string>
+    <string name="action_settings">Set膬ri</string>
+    <string name="action_debug">Depanare</string>
+    <string name="action_quit">Ie葯ire</string>
+    <string name="action_donate">Doneaz膬</string>
+    <string name="controlcenter_fetch_activity_data">Sincronizeaz膬</string>
+    <string name="controlcenter_find_device">G膬se葯te dispozitivul pierdut</string>
+    <string name="controlcenter_connect">Se conecteaz膬鈥�</string>
+    <string name="controlcenter_disconnect">Deconecteaz膬</string>
+    <string name="controlcenter_delete_device">葮terge dispozitivul</string>
+    <string name="controlcenter_delete_device_name">葮terge %1$s</string>
+    <string name="controlcenter_delete_device_dialogmessage">Aceast膬 op葲iune va 葯terge dispozitivul 葯i toate datele asociate cu acesta!</string>
+    <string name="controlcenter_navigation_drawer_open">Deschide meniul de naviga葲ie</string>
+    <string name="controlcenter_navigation_drawer_close">脦nchide meniul de naviga葲ie</string>
+    <string name="controlcenter_snackbar_need_longpress">Apas膬 lung pe card pentru a deconecta</string>
+    <string name="title_activity_appmanager">Gestionare aplica葲ii</string>
+    <string name="appmananger_app_reinstall">Reinstaleaz膬</string>
+    <string name="appmanager_health_activate">Activeaz膬</string>
+    <string name="appmanager_health_deactivate">Dezactiveaz膬</string>
+    <string name="appmanager_hrm_activate">Activeaz膬 m膬surarea pulsului</string>
+    <string name="appmanager_hrm_deactivate">Dezactiveaz膬 m膬surarea pulsului</string>
+    <string name="appmanager_weather_activate">Activeaz膬 Aplica葲ia meteo de sistem</string>
+    <string name="appmanager_weather_deactivate">Dezactiveaz膬 aplica葲ia Meteo de sistem</string>
+    <string name="app_configure">Configureaz膬</string>
+    <string name="blacklist_all_for_notifications">Blocheaz膬 toate notific膬rile</string>
+    <string name="whitelist_all_for_notifications">Permite toate notific膬rile</string>
+    <string name="title_activity_calblacklist">Calendare blocate</string>
+    <string name="title_activity_fw_app_insaller">Instalare firmware</string>
+    <string name="fw_multi_upgrade_notice">E葯ti pe punctul de a instala firmware-ul %1$s si %2$s, 脦n locul celor deja instalate pe Mi Band.</string>
+    <string name="miband_firmware_known">Firmware-ul a fost testat 葯i este compatibil cu Gadgetbridge.</string>
+    <string name="title_activity_settings">Set膬ri</string>
+    <string name="pref_header_general">Set膬ri generale</string>
+    <string name="pref_title_general_autoconnectonbluetooth">Conectare la dispozitivele ad膬ugate c芒nd Bluetooth este activat</string>
+    <string name="pref_title_general_autostartonboot">Porne葯te automat</string>
+    <string name="pref_title_general_autoreconnect">Reconectare automat膬</string>
+    <string name="pref_default">Implicit</string>
+    <string name="pref_header_datetime">Dat膬 葯i or膬</string>
+    <string name="pref_title_datetime_syctimeonconnect">Sincronizare timp</string>
+    <string name="pref_title_theme">Tema</string>
+    <string name="pref_theme_light">Luminoas膬</string>
+    <string name="pref_theme_dark">脦ntunecat膬</string>
+    <string name="pref_title_language">Limba</string>
+    <string name="pref_header_notifications">Notific膬ri</string>
+    <string name="pref_title_notifications_call">Apeluri telefonice</string>
+    <string name="pref_title_notifications_sms">SMS</string>
+    <string name="pref_title_notifications_generic">Suport notific膬ri generice</string>
+    <string name="pref_title_whenscreenon">鈥e asemenea, c芒nd ecranul este pornit</string>
+    <string name="always">Mereu</string>
+    <string name="when_screen_off">C芒nd ecranul este oprit</string>
+    <string name="never">Niciodat膬</string>
+    <string name="pref_call_privacy_mode_off">Afi葯eaz膬 numele 葯i num膬rul</string>
+    <string name="pref_call_privacy_mode_name">Ascunde numele, afi葯eaz膬 num膬rul</string>
+    <string name="pref_call_privacy_mode_number">Afi葯eaz膬 numele, ascunde num膬rul</string>
+    <string name="pref_call_privacy_mode_complete">Ascunde numele si num膬rul</string>
+    <string name="pref_title_weather">Meteo</string>
+    <string name="controlcenter_take_screenshot">Creeaz膬 o captur膬 ecran</string>
+    <string name="controlcenter_snackbar_disconnecting">Se deconecteaz膬</string>
+    <string name="controlcenter_snackbar_connecting">Se conecteaz膬鈥�</string>
+    <string name="controlcenter_snackbar_requested_screenshot">Creare captur膬 de ecran</string>
+    <string name="title_activity_debug">Depanare</string>
+    <string name="appmanager_installed_watchapps">Aplica葲ii instalate</string>
+    <string name="appmanager_installed_watchfaces">Cadrane instalate</string>
+    <string name="appmananger_app_delete">葮terge</string>
+    <string name="appmanager_app_openinstore">Caut膬 in magazinul Pebble</string>
+    <string name="appmanager_weather_install_provider">Instaleaz膬 aplica葲ia Notific膬ri Meteo</string>
+    <string name="app_move_to_top">Mut膬 la 卯nceput</string>
+    <string name="fw_upgrade_notice">E葯ti pe punctul de a instala firmware-ul %s.</string>
+    <string name="fw_upgrade_notice_amazfitbip">E葯ti pe punctul de a instala firmware-ul %s pe Amazfit Bip.
+\n
+\nAi grij膬 s膬 instalezi fi葯ierul .fw, apoi fi葯ierul .res, 葯i 卯n final fi葯ierul .gps. Ceasul va reporni dup膬 ce se instaleaz膬 fi葯ierul .fw.
+\n
+\nNot膬: Nu este necesar s膬 instalezi fi葯ierele .res si .gps daca aceste fi葯iere sunt identice cu cele instalate anterior.
+\n
+\nCONTINU膫 PE PROPRIUL RISCUL!</string>
+    <string name="fw_upgrade_notice_amazfitcor">E葯ti pe punctul de a instala firmware-ul %s pe Amazfit Cor.
+\n
+\nAi grij膬 s膬 instalezi fi葯ierul .fw, si apoi fi葯ierul .res. Ceasul va reporni dup膬 ce se instaleaz膬 fi葯ierul .fw.
+\n
+\nNot膬: Nu este necesar s膬 instalezi fi葯ierul .res dac膬 acest fi葯ier este identic cu cel instalat anterior.
+\n
+\nCONTINU膫 PE PROPRIUL RISCUL!</string>
+    <string name="fw_upgrade_notice_miband3">Esti pe punctul de a instala firmware-ul %s pe Mi Band 3.
+\n
+\nAi grija sa instalezi fisierul .fw, si apoi fisierul .res. Ceasul va reporni dupa ce se instaleaza fisierul .fw.
+\n
+\nNota: Nu este necesar sa instalezi fisierul .res daca acest fisier este identic cu cel instalat anterior.
+\n
+\nNETESTAT, V-AR PUTEA BLOCA ECHIPAMENTUL, CONTINUATI PE RISCUL DUMNEAVOASTRA!</string>
+    <string name="miband_firmware_unknown_warning">Acest firmware nu a fost testat 葯i ar putea s膬 nu fie compatibil cu Gadgetbridge.
+\n
+\nNU ESTE RECOMANDAT膫 instalarea lui!</string>
+    <string name="pref_header_development">Op葲iuni dezvoltator</string>
+    <string name="pref_title_development_miaddr">Adresa Mi Band</string>
+    <string name="pref_title_pebble_settings">Set膬ri Pebble</string>
+    <string name="pref_title_pebble_sync_misfit">Sincronizare Misfit</string>
+    <string name="pref_summary_enable_outgoing_call">Dac膬 dezactivezi aceast膬 op葲iune, Pebble 2/LE nu va vibra nici pentru apelurile efectuate</string>
+    <string name="pref_pebble_privacy_mode_off">Notific膬ri normale</string>
+    <string name="pref_title_location_latitude">Latitudine</string>
+    <string name="pref_title_location_longitude">Longitudine</string>
+    <string name="menuitem_activity">Activitate</string>
+    <string name="menuitem_weather">Meteo</string>
+    <string name="menuitem_alarm">Alarm膬</string>
+    <string name="menuitem_timer">Cronometru</string>
+    <string name="menuitem_compass">Busol膬</string>
+    <string name="menuitem_settings">Set膬ri</string>
+    <string name="menuitem_alipay">Alipay</string>
+    <string name="controlcenter_change_led_color">Modific膬 culoarea LED-ului</string>
+    <string name="controlcenter_change_fm_frequency">Modific膬 frecven葲a FM</string>
+    <string name="controlcenter_calibrate_device">Calibrare dispozitiv</string>
+    <string name="pref_title_audio_player">Player muzical preferat</string>
+    <string name="pref_title_minimize_priority">Ascunde notificarea Gadgetbridge</string>
+    <string name="pref_summary_minimize_priority_off">Icoana din bara de stare 葯i notificarea din ecranul de blocare sunt afi葯ate</string>
+    <string name="pref_summary_minimize_priority_on">Pictograma din bara de stare 葯i notificarea din ecranul de blocare sunt ascunse</string>
+    <string name="pref_title_notifications_repetitions">Repet膬ri</string>
+    <string name="pref_title_notifications_timeout">Timp minim 卯ntre notific膬ri</string>
+    <string name="pref_title_notifications_pebblemsg">Mesaje Pebble</string>
+    <string name="pref_title_notification_filter">Nu Deranja</string>
+    <string name="pref_summary_notification_filter">Blocheaz膬 toate notific膬rile c芒nd telefonul este 卯n modul Nu deranja</string>
+    <string name="pref_title_rtl">De la dreapta la st芒nga</string>
+    <string name="pref_header_privacy">Confiden葲ialitate</string>
+    <string name="pref_title_canned_replies">R膬spunsuri</string>
+    <string name="filter_submode_at_least_one">Cel pu葲in unul dintre cuvinte</string>
+    <string name="filter_submode_all">Toate cuvintele</string>
+    <string name="save_configuration">Salvare configura葲ie</string>
+    <string name="appwidget_not_connected">Deconectat(膬), alarma nu este setat膬.</string>
+    <string name="mi2_prefs_button_press_broadcast_default_value" translatable="false">nodomain.freeyourgadget.gadgetbridge.ButtonPressed</string>
+    <string name="sonyswr12_settings_title">Set膬ri Sony SWR12</string>
+    <string name="sonyswr12_settings_stamina">Modul de economisire a energiei este activat</string>
+    <string name="sonyswr12_settings_alarm_interval">Interval de alarm膬 inteligent膬 卯n minute</string>
+    <string name="sonyswr12_settings_low_vibration_summary">Activeaz膬 vibra葲ia slab膬 pe br膬葲ar膬</string>
+    <string name="sonyswr12_settings_stamina_summary">Modul de economisire a energiei dezactiveaz膬 m膬surarea automat膬 periodic膬 a ritmului cardiac, cresc芒nd astfel timpul de func葲ionare</string>
+    <string name="sonyswr12_settings_alarm_interval_summary">Intervalul de alarm膬 inteligent膬 este intervalul 卯nainte de alarma instalat膬. 脦n acest interval, dispozitivul 卯ncearc膬 s膬 detecteze cea mai u葯oar膬 faz膬 de somn pentru a trezi utilizatorul</string>
+    <string name="sonyswr12_settings_low_vibration">Vibra葲ie sc膬zut膬 activat膬</string>
+    <string name="prefs_activity_in_device_card_distance_title_summary">Distan葲a este calculat膬 din num膬rul de pa葯i 葯i valoarea lungimii pa葯ilor (reglabile 卯n Set膬ri - Despre tine)</string>
+    <string name="application_name_main_nopebble">Gadgetbridge (Nightly, f膬r膬 funizor Pebble)</string>
+    <string name="controlcenter_power_off">Oprire</string>
+    <string name="accuracy">Precizie</string>
+    <string name="title_activity_controlcenter_main_nightly">Gadgetbridge Nightly</string>
+    <string name="dual_band">Dou膬 benzi</string>
+    <string name="about_activity_title_banglejs_main">Despre Bangle.js Gadgetbridge</string>
+    <string name="calendar_three_months">3 luni</string>
+    <string name="title_activity_controlcenter_main_nopebble">Gadgetbridge Nightly f膬r膬 Pebble</string>
+    <string name="title_activity_controlcenter_banglejs_main">Bangle.js Gadgetbridge</string>
+    <string name="balanced">Echilibrat</string>
+    <string name="prefs_activity_in_device_card_sleep_title">Somn</string>
+    <string name="controlcenter_set_alias">Seteaz膬 un pseudonim</string>
+    <string name="all_satellites">To葲i sateli葲ii</string>
+    <string name="accuracy_first">Precizia are prioritate</string>
+    <string name="about_activity_title_main_nopebble">Despre Gadgetbridge Nightly f膬r膬 Pebble</string>
+    <string name="calendar_week">S膬pt膬m芒n膬</string>
+    <string name="gps">GPS</string>
+    <string name="calendar_six_months">6 luni</string>
+    <string name="controlcenter_power_off_confirm_description">E葯ti sigur c膬 dore葯ti s膬 opre葯ti acest dispozitiv?</string>
+    <string name="calendar_day">Zi</string>
+    <string name="device_card_activity_card_title">Informa葲ii despre activitate pe cardul dispozitivului</string>
+    <string name="about_activity_title_main_nightly">Despre Gadgetbridge Nightly</string>
+    <string name="single_band">Band膬 simpl膬</string>
+    <string name="gadgetbridge_running_main_nopebble">Nightly NoPebble GB ruleaz膬</string>
+    <string name="calendar_two_weeks">Dou膬 s膬pt膬m芒ni</string>
+    <string name="about_activity_title_banglejs_nightly">Despre Bangle.js Gadgetbridge (Nightly)</string>
+    <string name="calendar_year">An</string>
+    <string name="power_saving">Economisirea energiei</string>
+    <string name="find_lost_device_message">Cau葲i %1$s?</string>
+    <string name="gadgetbridge_running_banglejs_nightly">Nightly Bangle.js ruleaz膬</string>
+    <string name="controlcenter_get_heartrate_measurement">M膬surarea pulsului</string>
+    <string name="gadgetbridge_running_banglejs_main">Bangle.js ruleaz膬</string>
+    <string name="prefs_activity_in_device_card_steps_title_summary">Arat膬 num膬rul total de pa葯i</string>
+    <string name="title_activity_controlcenter_banglejs_nightly">Bangle.js Gadgetbridge (Nightly)</string>
+    <string name="prefs_activity_in_device_card_sleep_title_summary">Arat膬 durata somnului</string>
+    <string name="battery_level">Nivelul bateriei</string>
+    <string name="application_name_banglejs_main">Bangle.js Gadgetbridge</string>
+    <string name="calendar_month">Lun膬</string>
+    <string name="prefs_activity_in_device_card_title">Arat膬 informa葲ii despre activitate pe cardul dispozitivului</string>
+    <string name="battery_detail_activity_title">Informa葲ii despre baterie</string>
+    <string name="debugactivity_confirm_remove_device_preferences_title">Elimini preferin葲ele dispozitivului?</string>
+    <string name="device_card_activity_card_title_summary">Alege ce detalii vor fii afi葯ate pe cardul dispozitivului</string>
+    <string name="about_description_generic">脦nlocuitor sub o licen葲膬 copyleft pentru aplica葲iile cu surs膬 卯nchis膬 Android, de administrare a unor dispozitive portabile.</string>
+    <string name="gps_gnolass">GPS + GNOLASS</string>
+    <string name="gadgetbridge_running_main_nightly">Nightly GB este pornit</string>
+    <string name="action_changelog">Jurnal de modific膬ri</string>
+    <string name="about_activity_title_generic">Despre Gadgetbridge</string>
+    <string name="gadgetbridge_running_generic">Gadgetbridge este pornit</string>
+    <string name="controlcenter_power_off_confirm_title">Oprire</string>
+    <string name="gps_bds">GPS + BDS</string>
+    <string name="low_power_gps">GPS cu consum mic</string>
+    <string name="custom">Personalizat</string>
+    <string name="speed_first">Viteza are prioritate</string>
+    <string name="prefs_activity_in_device_card_title_summary">Afi葯eaz膬 num膬rul de pa葯i, distan葲a sau somnul pe cardul dispozitivului</string>
+    <string name="application_name_banglejs_nightly">Bangle.js Gadgetbridge (Nightly)</string>
+    <string name="application_name_main_nightly">Gadgetbridge (Nightly)</string>
+    <string name="gps_galileo">GPS + GALILEO</string>
+    <string name="debugactivity_really_factoryreset_title">Chiar dore葯ti o restaurare a set膬rilor din fabric膬?</string>
+    <string name="devicetype_xiaomi_watch_s1_active">Xiaomi Watch S1 Active</string>
+    <string name="fw_upgrade_notice_miband7">E葯ti pe cale s膬 instalezi firmware-ul %s pe Xiaomi Smart Band 7.
+\n
+\nDup膬 instalarea fi葯ierului .zip, br膬葲ara va reporni.
+\n
+\nAC葰IONEZI PE PROPRIU RISC!</string>
+    <string name="fw_upgrade_notice_amazfit_gts3">E葯ti pe cale s膬 instalezi firmware-ul %s pe Amazfit GTS 3.
+\n
+\nDup膬 instalarea fi葯ierului .zip, br膬葲ara va reporni.
+\n
+\nAC葰IONEZI PE PROPRIU RISC!</string>
+    <string name="fw_upgrade_notice_amazfit_gts4">E葯ti pe cale s膬 instalezi firmware-ul %s pe Amazfit GTS 4.
+\n
+\nDup膬 instalarea fi葯ierului .zip, br膬葲ara va reporni.
+\n
+\nAC葰IONEZI PE PROPRIU RISC!</string>
+    <string name="pref_header_display">Afi葯are</string>
+    <string name="pref_header_workout">Antrenamente</string>
+    <string name="vibration_profile_staccato">Staccato</string>
+    <string name="femometer_measurement_mode_title">Modul de m膬surare</string>
+    <string name="candidate_item_device_image">Imaginea dispozitivului</string>
+    <string name="controlcenter_start_configure_reminders">Configurare memento-uri</string>
+    <string name="heartrate_bpm_40">40 de b膬t膬i/min</string>
+    <string name="spo2_perc_80">80%</string>
+    <string name="pref_header_cannned_messages">Mesaje salvate</string>
+    <string name="pref_summary_enable_calendar_sync">Afi葯eaz膬 evenimentele din calendar pe cronologia pebble</string>
+    <string name="pref_title_pebble_mtu_limit">Limit膬 GATT MTU pentru Pebble 2/LE</string>
+    <string name="power_mode_watch">Doar ceasul</string>
+    <string name="zetime_date_format">Formatul datei</string>
+    <string name="pref_agps_header">AGPS</string>
+    <string name="pref_title_support_voip_calls">Activare apeluri VoIP</string>
+    <string name="pref_title_notification_times_enabled">Timpul de notificare</string>
+    <string name="pref_summary_banglejs_txt_bitmap_size">Dimensiunea de utilizat pentru redarea textului bitmap</string>
+    <string name="pref_title_banglejs_text_bitmap">Text ca bitmap-uri</string>
+    <string name="pref_summary_banglejs_text_bitmap">Dac膬 cuv芒ntul nu poate fi afi葯at 卯n fontul ceasului, se converte葯te 卯ntr-un bitmap 卯n Gadgetbridge 葯i se afi葯eaz膬 bitmap-ul pe ceas</string>
+    <string name="pref_message_privacy_mode_off">Afi葯eaz膬 tot con葲inutul</string>
+    <string name="pref_title_weather_summary">Folosit pentru furnizorul de vreme LineageOS, alte versiuni Android trebuie s膬 utilizeze o aplica葲ie precum 鈥濶otificare meteo鈥�. G膬se葯ti mai multe informa葲ii 卯n wiki Gadgetbridge.</string>
+    <string name="pref_header_pebble_timeline">Cronologie Pebble</string>
+    <string name="pref_title_sunrise_sunset">R膬s膬rit 葯i apus</string>
+    <string name="pref_title_enable_pebblekit">Permite accesul la aplica葲ii ter葲e</string>
+    <string name="pref_summary_enable_pebblekit">Activeaz膬 suportul experimental pentru aplica葲iile Android folosind PebbleKit</string>
+    <string name="pref_title_casio_alert_other">Alert膬 pentru alte notific膬ri</string>
+    <string name="pref_summary_location_keep_uptodate">Se 卯ncearc膬 ob葲inerea loca葲iei actuale 卯n timpul rul膬rii, utilizeaz膬 loca葲ia stocat膬 ca alternativ膬</string>
+    <string name="pref_title_timeformat">Format de timp</string>
+    <string name="pref_title_device_spec_settings_force_time">Sincronizarea for葲at膬 a timpului</string>
+    <string name="zetime_analog_mode">Modul analogic</string>
+    <string name="zetime_date_format_2">ZZ/LL/AA</string>
+    <string name="zetime_prefs_inactivity_repetitions">Repet膬ri</string>
+    <string name="pref_explanation_authkey_new_protocol">Dac膬 prime葯ti pe band膬 mesajul 鈥濧ctualizeaz膬 aplica葲ia la cea mai recent膬 versiune鈥�, asigur膬-te c膬 ai verificat 鈥濸rotocolul de autentificare nou鈥� de mai sus. Atinge aici pentru mai multe detalii 卯n wiki.</string>
+    <string name="bluetooth_is_not_supported_">Bluetooth nu este acceptat.</string>
+    <string name="find_my_phone_notification">G膬se葯te telefonul</string>
+    <string name="sleep_activity_date_range">Dela %1$s p芒n膬 la %2$s</string>
+    <string name="prefs_wearside">Por葲i pe m芒na st芒ng膬 sau dreapt膬?</string>
+    <string name="busy_task_fetch_sleep_respiratory_rate_data">Preluare date privind frecven葲a respiratorie 卯n somn</string>
+    <string name="busy_task_fetch_temperature">Preluare date de temperatur膬</string>
+    <string name="busy_task_fetch_statistics">Preluare statistici</string>
+    <string name="spo2_off">Oprit</string>
+    <string name="control_center_cancel_to_stop_vibration">Anuleaz膬 pentru a opri vibra葲ia.</string>
+    <string name="heartrate_bpm_145">145 b膬t膬i/min</string>
+    <string name="heartrate_bpm_185">185 b膬t膬i/min</string>
+    <string name="spo2_perc_85">85%</string>
+    <string name="about_description_banglejs_main">Aplica葲ie 卯nso葲itoare pentru Bangle.js construit膬 pe baza proiectului Gadgetbridge, cu ad膬ugarea accesului la internet.
+\n
+\nDin cauza politicilor Magazinului Google Play, nu ne este permis un link pentru dona葲ii 卯n aplica葲ia 卯n sine, dar dac膬 卯葲i place aceast膬 aplica葲ie, doneaz膬 prin intermediul paginii de pornire Gadgetbridge de mai jos.</string>
+    <string name="pref_header_audio">Audio</string>
+    <string name="pref_header_health">S膬n膬tate</string>
+    <string name="pref_summary_datetime_syctimeonconnect">Sincronizeaz膬 ora pe dispozitivele ad膬ugate: la conectare, atunci c芒nd ora sau fusul orar se schimb膬 pe dispozitivul Android 葯i periodic</string>
+    <string name="pref_pair_bluetooth_calls_help_1">1. Clic pe butonul de mai jos pentru a 卯ncepe procesul de asociere.</string>
+    <string name="pref_title_banglejs_phone_gps_update_interval">Rata de actualizare GPS</string>
+    <string name="pref_applications_settings">Lista aplica葲iilor</string>
+    <string name="pref_title_enable_calendar_sync">Sincronizare calendar</string>
+    <string name="pref_title_custom_deviceicon">Afi葯eaz膬 pictograma de notificare specific膬 dispozitivului</string>
+    <string name="pref_title_pebble_forcele">Prefer膬 卯ntotdeauna BLE</string>
+    <string name="pref_title_expose_hr">Acces la monitorizarea pulsului</string>
+    <string name="pref_display_add_device_fab">Buton de conectare a dispozitivului nou</string>
+    <string name="preferences_hplus_settings">Set膬ri HPlus/Makibes</string>
+    <string name="pref_summary_notifications_and_calls_title_reject">Oprit - ignor膬, Activat - respinge</string>
+    <string name="prefs_notifications_and_calls_shake_reject">Agitarea 卯ncheieturii m芒inii ignor膬/respinge apelul</string>
+    <string name="pref_header_sensors_calibration">Calibrarea senzorului</string>
+    <string name="pref_sensors_bp_calibration_low">Tensiune arterial膬 DIASTOL膫 (sc膬zut膬)</string>
+    <string name="power_mode_normal">Normal</string>
+    <string name="appinstaller_install">Instaleaz膬</string>
+    <string name="open_fw_installer_pick_file">Alege fi葯ierul</string>
+    <string name="zetime_date_format_1">AA/LL/ZZ</string>
+    <string name="zetime_date_format_3">LL/ZZ/AA</string>
+    <string name="device_with_rssi">%1$s (%2$s)</string>
+    <string name="female">Femeie</string>
+    <string name="title_activity_sleepmonitor">Monitorizare somn</string>
+    <string name="pref_screen_notification_profile_missed_call">Notificare apel pierdut</string>
+    <string name="about_description_main_nightly">O alternativ膬 gratuit膬 鈥瀎膬r膬 nor鈥� la aplica葲iile 卯nchise de la produc膬torii de gadgeturi. Versiuni 鈥濶ightly鈥� ale Gadgetbridge. Acestea nu pot fi instalate dac膬 aplica葲ia Gadgetbridge sau Pebble este deja instalat膬, din cauza unui conflict cu furnizorul Pebble.</string>
+    <string name="appmananger_app_delete_cache">葮terge 葯i elimin膬 din cache</string>
+    <string name="busy_task_fetch_sports_summaries">Preluarea rezumatelor sportive</string>
+    <string name="heartrate_bpm_140">140 b膬t膬i/min</string>
+    <string name="heartrate_bpm_130">130 b膬t膬i/min</string>
+    <string name="open_fw_installer_warning_title">Avertisment</string>
+    <string name="pref_title_canned_messages_set">Actualizare pe dispozitiv</string>
+    <string name="pref_title_pebble_sync_morpheuz">Sincronizare cu Morpheuz</string>
+    <string name="busy_task_fetch_sports_details_interrupted">Preluarea detaliilor sportive a fost 卯ntrerupt膬</string>
+    <string name="vibration_profile_alarm_clock">Ceas cu alarm膬</string>
+    <string name="reminder_every_month">脦n fiecare lun膬</string>
+    <string name="pref_world_clocks_summary">Configureaz膬 ceasurile pentru alte fusuri orare</string>
+    <string name="pref_contacts_title">Contacte</string>
+    <string name="title_activity_notification_management">Set膬ri de notificare</string>
+    <string name="fw_upgrade_notice_amazfitneo">E葯ti pe cale s膬 instalezi firmware-ul %s pe Amazfit Neo.
+\n
+\nDup膬 instalarea fi葯ierului .fw, br膬葲ara va reporni.
+\n
+\nAC葰IONEZI PE PROPRIU RISC!</string>
+    <string name="open_fw_installer_getting_files_title">Ob葲inerea fi葯ierului firmware/aplica葲ie</string>
+    <string name="open_fw_installer_info_text_title">Instalator de fi葯iere</string>
+    <string name="pref_header_intent_api">Intent API</string>
+    <string name="zetime_title_alarm_signaling">Seteaz膬 tipul de semnalizare pentru alarm膬</string>
+    <string name="zetime_signaling_none">Silen葲ios</string>
+    <string name="prefs_hr_alarm_high">Limita superioar膬</string>
+    <string name="initialized">Ini葲ializat</string>
+    <string name="about_description_banglejs_nightly">Aplica葲ie 卯nso葲itoare pentru Bangle.js construit膬 pe baza proiectului Gadgetbridge, cu ad膬ugarea accesului la internet.
+\n
+\nDin cauza politicilor Magazinului Google Play, nu ne este permis un link pentru dona葲ii 卯n aplica葲ia 卯n sine, dar dac膬 卯葲i place aceast膬 aplica葲ie, doneaz膬 prin intermediul paginii de pornire Gadgetbridge de mai jos.</string>
+    <string name="pref_header_other">Altele</string>
+    <string name="pref_header_system">Sistem</string>
+    <string name="pref_title_notification_wake_on_open">Trezire 葯i deblocare automat膬</string>
+    <string name="pref_summary_notification_wake_on_open">Trezire 葯i deblocare dispozitiv Android c芒nd gadgetul trimite 卯napoi un r膬spuns DESCHIS. Trebuie s膬 fie 卯ntr-o stare de 卯ncredere.</string>
+    <string name="pref_pair_bluetooth_calls_help_summary">Pentru a primi apeluri Bluetooth, trebuie s膬 asociezi telefonul cu un al doilea ceas.</string>
+    <string name="pref_summary_banglejs_phone_gps_network_only">Determin膬 loca葲ia numai de c膬tre furnizorul de internet. Acest lucru reduce consumul de energie cu pre葲ul unei precizii reduse. Necesit膬 reconectare.</string>
+    <string name="pref_summary_banglejs_phone_gps_update_interval">Rata de actualizare a pozi葲iei GPS, 卯n ms</string>
+    <string name="pref_summary_rtl">Activeaz膬 dac膬 dispozitivul nu afi葯eaz膬 scrierea de la dreapta la st芒nga</string>
+    <string name="pref_rtl_max_line_length_summary">Prelunge葯te sau scurteaz膬 liniile 卯n care textul de la dreapta la st芒nga este separat</string>
+    <string name="pref_summary_canned_replies">R膬spunde de pe ceas folosind mesaje prestabilite</string>
+    <string name="pref_title_canned_messages_dismisscall">Apeluri respinse</string>
+    <string name="pref_header_authentication">Autentificare</string>
+    <string name="pref_header_activitytrackers">Urm膬rirea activit膬葲ii</string>
+    <string name="pref_title_pebble_activitytracker">Monitorul de activitate preferat</string>
+    <string name="heartrate_bpm_45">45 de b膬t膬i/min</string>
+    <string name="prefs_title_heartrate_measurement_interval">M膬surarea pulsului pe parcursul zilei</string>
+    <string name="pref_screen_notification_profile_event_reminder">Memento de eveniment</string>
+    <string name="pref_screen_notification_profile_find_device">C膬utarea dispozitivului</string>
+    <string name="interval_one_minute">o dat膬 pe minut</string>
+    <string name="interval_thirty_minutes">la fiecare 30 de minute</string>
+    <string name="pref_message_privacy_mode_bodyonly">Ascunde numai corpul</string>
+    <string name="pref_sensors_bp_calibration">Calibrarea tensiunii arteriale</string>
+    <string name="prefs_sensors_button_bp_calibration">Calibrare</string>
+    <string name="zetime_signaling_vibrate_beep_once">Vibreaz膬 葯i bip o dat膬</string>
+    <string name="message_cannot_pair_no_mac">Nu a fost transmis膬 nicio adres膬 MAC, nu se poate asocia.</string>
+    <string name="miband_prefs_alias">Nume/Pseudonim</string>
+    <string name="pref_screen_notification_profile_generic_social">Re葲ele sociale</string>
+    <string name="appmanager_app_share">Distribuie</string>
+    <string name="pref_rtl_max_line_length">Lungimea maxim膬 a liniei de la dreapta la st芒nga</string>
+    <string name="debugactivity_confirm_remove_device_preferences">Aceasta va reseta preferin葲ele dispozitivului pentru toate dispozitivele conectate. E葯ti sigur?</string>
+    <string name="appmanager_cached_watchapps_watchfaces">Aplica葲ii 卯n cache</string>
+    <string name="open_fw_installer_select_file">Alege fi葯ierul pe care dore葯ti s膬 卯l 卯ncarci pe dispozitiv: %s</string>
+    <string name="open_fw_installer_connect_maximum_one_device">Conecteaz膬 NUMAI UN dispozitiv c膬tre care dore葯ti s膬 trimi葲i fi葯ierul.</string>
+    <string name="open_fw_installer_ensure_device_connected">Asigur膬-te c膬 dispozitivul %s este conectat</string>
+    <string name="pref_title_pebble_privacy_mode">Modul de confiden葲ialitate</string>
+    <string name="pref_title_unit_system">Unit膬葲i</string>
+    <string name="reminder_no_free_slots_title">Nu sunt slot-uri libere</string>
+    <string name="pref_screen_vibration_profile">Profil de vibra葲ie</string>
+    <string name="pref_screen_notification_profile_todo_list">Lista de sarcini</string>
+    <string name="interval_forty_five_minutes">la fiecare 45 de minute</string>
+    <string name="pref_blacklist_calendars">Calendare 卯n lista neagr膬</string>
+    <string name="pref_summary_use_custom_font">Activeaz膬 aceast膬 op葲iune dac膬 dispozitivul re un font personalizat pentru a accepta emoji</string>
+    <string name="zetime_calories_type">Tip de calorii</string>
+    <string name="pref_title_banglejs_txt_bitmap_size">Dimensiunea bitmap a textului</string>
+    <string name="pref_title_notification_use_as">Utilizeaz膬 lista de aplica葲ii pentru a鈥�</string>
+    <string name="pref_summary_transliteration">Activeaz膬 dac膬 dispozitivul nu are un font pentru limba ta</string>
+    <string name="pref_title_screentime">Durata de func葲ionare a ecranului</string>
+    <string name="miband_firmware_suggest_whitelist">Dac膬 instalezi acest firmware 葯i descoperi c膬 func葲ioneaz膬 f膬r膬 probleme, anun葲膬 dezvoltatorii Gadgetbridge. Ei vor marca aceast膬 versiune de firmware (%s) ca fiind compatibil膬.</string>
+    <string name="pref_theme_black_background">Utilizare fundal negru 卯n Tema 卯ntunecat膬</string>
+    <string name="pref_summary_notification_delay_calls">脦nt芒rziere 卯nainte de a trimite notific膬ri privind apelurile primite c膬tre dispozitiv, 卯n secunde.</string>
+    <string name="pref_title_canned_reply_suffix">Sufix comun</string>
+    <string name="activity_prefs_alarm_min_heart_rate">Puls minim</string>
+    <string name="pref_screen_notification_profile_calendar">Notificare calendar</string>
+    <string name="reminder_time">Timp</string>
+    <string name="pref_pair_bluetooth_calls_help_2">2. Acceseaz膬 set膬rile Bluetooth de pe telefon 葯i asociaz膬 cu noul dispozitiv care apare 卯n list膬 (nume similar cu ceasul actual, dar cu un sufix, de exemplu 鈥濧mazfit GTR 4 - AFC8鈥�).</string>
+    <string name="pref_title_ping_tone">Ton de apel</string>
+    <string name="pref_title_notifications_generic_settings">Set膬ri de notificare Android</string>
+    <string name="pref_summary_notifications_ignore_work_profile">Nu trimite notific膬rile aplica葲iilor din profilul t膬u de serviciu c膬tre ceas</string>
+    <string name="pref_header_notification_application_settings">Set膬ri pentru fiecare aplica葲ie</string>
+    <string name="pref_title_banglejs_phone_gps_enbale">Utilizeaz膬 GPS-ul telefonului</string>
+    <string name="pref_title_casio_alert_calendar">Alerte din calendar</string>
+    <string name="pref_summary_casio_alert_calendar">Notificare despre evenimentele din calendar prin vibra葲ie/bip</string>
+    <string name="pref_summary_autoremove_notifications">Notific膬rile sunt eliminate automat de pe dispozitiv atunci c芒nd sunt respinse pe telefon</string>
+    <string name="pref_title_location_aquire">Ob葲ine date despre loca葲ie</string>
+    <string name="pref_title_pebble_gatt_clientonly">Numai client GATT</string>
+    <string name="pref_summary_pebble_gatt_clientonly">Acesta este doar pentru Pebble 2 葯i experimental, 卯ncearc膬 acest lucru dac膬 ai probleme de conectivitate</string>
+    <string name="pref_title_pebble_enable_bgjs">Activare JS 卯n fundal</string>
+    <string name="pref_title_allow_high_mtu">Permite MTU mare</string>
+    <string name="pref_summary_connected_advertisement">Face dispozitivul detectabil prin Bluetooth atunci c芒nd este deja conectat</string>
+    <string name="pref_summary_overwrite_settings_on_connection">La conectarea la band膬, se suprascriu toate set膬rile de pe ea.</string>
+    <string name="pref_title_vibration_strength">Intensitate vibra葲ii</string>
+    <string name="pref_title_huami_force_new_protocol">Noul protocol de autorizare</string>
+    <string name="prefs_notifications_and_calls_reject">Butonul ignor膬/respinge apelurile primite</string>
+    <string name="pref_sensors_bp_calibration_high">Tensiune arterial膬 SISTOLIC膫 (ridicat膬)</string>
+    <string name="zetime_analog_mode_hands">Doar m芒inile</string>
+    <string name="zetime_prefs_inactivity_mo">Luni</string>
+    <string name="zetime_signaling_beep">Bip continuu</string>
+    <string name="zetime_prefs_inactivity_tu">Mar葲i</string>
+    <string name="zetime_signaling_vibrate_once">Vibreaz膬 o dat膬</string>
+    <string name="pref_summary_authkey">Schimb膬 cheia de autentificare cu o cheie comun膬 pe toate dispozitivele Android pe care dore葯ti s膬 le conectezi. Cheia implicit膬 anterioar膬 pentru toate dispozitivele este 0123456789@ABCDE</string>
+    <string name="pref_gps_band">Band膬 GPS</string>
+    <string name="pref_gps_mode_preset">Modul GPS</string>
+    <string name="pref_gps_combination">GPS combinat</string>
+    <string name="offline_voice_respond_screen_on">R膬spuns c芒nd ecranul este pornit</string>
+    <string name="pref_morning_updates_summary">Afi葯eaz膬 actualiz膬ri 卯n fiecare diminea葲膬</string>
+    <string name="appversion_by_creator">%1$s de la %2$s</string>
+    <string name="pairing_in_progress">Asociere 卯n curs: %1$s (%2$s)</string>
+    <string name="pairing_already_bonded">Deja asociat cu %1$s (%2$s), se conecteaz膬鈥�</string>
+    <string name="initializing">Ini葲ializare</string>
+    <string name="prefs_wearmode">Mod de purtare</string>
+    <string name="vibration_profile_default">Implicit</string>
+    <string name="vibration_profile_medium">Mediu</string>
+    <string name="miband_prefs_vibration">Vibrare</string>
+    <string name="pref_screen_notification_idle_alerts">Alerte de inactivitate</string>
+    <string name="heartrate_bpm_50">50 de b膬t膬i/min</string>
+    <string name="heartrate_bpm_150">150 b膬t膬i/min</string>
+    <string name="heartrate_bpm_155">155 b膬t膬i/min</string>
+    <string name="reminder_time_every_week">%1$s, 卯n fiecare s膬pt膬m芒n膬</string>
+    <string name="controlcenter_start_configure_alarms">Configureaz膬 alarmele</string>
+    <string name="reminder_repeat">Repetare</string>
+    <string name="reminder_time_every_month">%1$s, 卯n fiecare lun膬</string>
+    <string name="reminder_once">O dat膬</string>
+    <string name="reminder_every_year">脦n fiecare an</string>
+    <string name="reminder_every_week">脦n fiecare s膬pt膬m芒n膬</string>
+    <string name="pref_title_mb_intents">Difuzeaz膬 direct inten葲iile butonului media</string>
+    <string name="bluetooth_calls">Apeluri Bluetooth</string>
+    <string name="bluetooth_calls_pairing">Asocierea apelurilor Bluetooth</string>
+    <string name="pref_summary_notifications_and_calls_enable_misscall">Se repet膬 卯n fiecare minut</string>
+    <string name="clap_hands_to_wakeup_device_summary">B膬taia repetat膬 va stinge ecranul</string>
+    <string name="clap_hands_to_wakeup_device">Bate din palme pentru a activa ecranul</string>
+    <string name="pixoo_power_saving_summary">Ecranul se va opri dup膬 ce microfonul detecteaz膬 lini葯te pentru un timp</string>
+    <string name="pref_title_authkey">Cheie de autorizare</string>
+    <string name="pref_screen_notification_profile_generic">Notificare generic膬</string>
+    <string name="pref_header_vibration_settings">Set膬ri de vibra葲ii</string>
+    <string name="interval_five_minutes">la fiecare 5 minute</string>
+    <string name="spo2_perc_90">90%</string>
+    <string name="interval_one_hour">o dat膬 pe or膬</string>
+    <string name="stats_title">Zone de vitez膬</string>
+    <string name="stats_x_axis_label">Total minute</string>
+    <string name="stats_y_axis_label">Pa葯i pe minut</string>
+    <string name="control_center_find_lost_device">G膬se葯te dispozitivul pierdut</string>
+    <string name="title_activity_charts">Activitate 葯i somn</string>
+    <string name="proprietary_app_warning">Aceast膬 caracteristic膬 necesit膬 instalarea unei aplica葲ii proprietare</string>
+    <string name="pref_header_calendar">Calendar</string>
+    <string name="pref_header_connection">Conexiune</string>
+    <string name="pref_header_generic">General</string>
+    <string name="pref_header_sound_vibration">Sunete 葯i vibra葲ii</string>
+    <string name="pref_header_offline_voice">Voce offline</string>
+    <string name="pref_header_time">Timp</string>
+    <string name="pref_header_equalizer">Egalizator</string>
+    <string name="pref_summary_notification_cache_while_disconnected">Trimite notific膬ri ratate c芒nd dispozitivul se reconecteaz膬 dup膬 ce a ie葯it 卯n raza de ac葲iune</string>
+    <string name="n_a">Indisponibil</string>
+    <string name="preferences_miband_1_2_warning">Avertisment: aceste preferin葲e se aplic膬 numai pentru Mi Band 1 葯i 2.</string>
+    <string name="busy_task_fetch_sports_details">Preluarea detaliilor sportive</string>
+    <string name="busy_task_fetch_pai_data">Preluare date PAI</string>
+    <string name="busy_task_fetch_spo2_data">Preluarea datelor SpO2</string>
+    <string name="vibration_profile_ring">Sonerie</string>
+    <string name="heartrate_bpm_110">110 b膬t膬i/min</string>
+    <string name="pref_screen_vibration_patterns_summary">Configureaz膬 modelele de vibra葲ii pentru diferite notific膬ri</string>
+    <string name="heartrate_bpm_115">115 b膬t膬i/min</string>
+    <string name="heartrate_bpm_120">120 b膬t膬i/min</string>
+    <string name="title_activity_set_contacts">Configurare contacte</string>
+    <string name="debugactivity_really_factoryreset">O resetare din fabric膬 va 葯terge toate datele de pe dispozitivul conectat (dac膬 este acceptat). Dispozitivele Xiaomi/Huami schimb膬, de asemenea, adresa MAC Bluetooth, a葯a c膬 apar ca dispozitive noi pentru Gadgetbridge.</string>
+    <string name="appmanager_watchface_activate">Activeaz膬</string>
+    <string name="appmanager_app_download">Descarc膬 卯n cache</string>
+    <string name="appmanager_app_start">Porne葯te</string>
+    <string name="appmanager_item_outdated">(卯nvechit)</string>
+    <string name="appmanager_download_started">Desc膬rcarea aplica葲iei a 卯nceput</string>
+    <string name="appmanager_downloaded_to_cache">Desc膬rcat 卯n cache: %s</string>
+    <string name="appmanager_download_app_error">Eroare la desc膬rcarea aplica葲iei</string>
+    <string name="check_all_applications">Bifeaz膬 toate aplica葲iile</string>
+    <string name="uncheck_all_applications">Debifeaz膬 toate aplica葲iile</string>
+    <string name="fw_upgrade_notice_amazfitbip_lite">E葯ti pe cale s膬 instalezi firmware-ul %s pe Amazfit Bip Lite.
+\n
+\nAsigur膬-te c膬 instalezi fi葯ierul .fw, iar dup膬 aceea fi葯ierul .res. Ceasul se va reporni dup膬 instalarea fi葯ierului .fw.
+\n
+\nNot膬: Nu trebuie s膬 instalezi .res dac膬 este exact acela葯i cu cel instalat anterior.
+\n
+\nAC葰IONEZI PE PROPRIU RISC!</string>
+    <string name="fw_upgrade_notice_amazfit_gtr4">E葯ti pe cale s膬 instalezi firmware-ul %s pe Amazfit GTR 4.
+\n
+\nDup膬 instalarea fi葯ierului .zip, br膬葲ara va reporni.
+\n
+\nAC葰IONEZI PE PROPRIU RISC!</string>
+    <string name="fw_upgrade_notice_amazfit_trex2">E葯ti pe cale s膬 instalezi firmware-ul %s pe Amazfit T-Rex 2.
+\n
+\nDup膬 instalarea fi葯ierului .zip, br膬葲ara va reporni.
+\n
+\nAC葰IONEZI PE PROPRIU RISC!</string>
+    <string name="fw_upgrade_notice_amazfit_band7">E葯ti pe cale s膬 instalezi firmware-ul %s pe Amazfit Band 7.
+\n
+\nDup膬 instalarea fi葯ierului .zip, br膬葲ara va reporni.
+\n
+\nAC葰IONEZI PE PROPRIU RISC!</string>
+    <string name="open_fw_installer_connect_minimum_one_device">Conecteaz膬 CEL PU葰IN UN dispozitiv c膬tre care dore葯ti s膬 trimi葲i fi葯ierul.</string>
+    <string name="fw_upgrade_notice_amazfit_gtr3_pro">E葯ti pe cale s膬 instalezi firmware-ul %s pe Amazfit GTR 3 Pro.
+\n
+\nDup膬 instalarea fi葯ierului .zip, br膬葲ara va reporni.
+\n
+\nAC葰IONEZI PE PROPRIU RISC!</string>
+    <string name="pref_write_logfiles">Scrie fi葯iere jurnal</string>
+    <string name="pref_cache_weather">Memoreaz膬 卯n cache informa葲iile despre vreme</string>
+    <string name="watch9_pairing_tap_hint">C芒nd ceasul vibreaz膬, scutur膬 dispozitivul sau apas膬 butonul acestuia.</string>
+    <string name="fw_upgrade_notice_amazfit_cheetah_pro">E葯ti pe cale s膬 instalezi firmware-ul %s pe Amazfit Cheetah Pro.
+\n
+\nDup膬 instalarea fi葯ierului .zip, br膬葲ara va reporni.
+\n
+\nAC葰IONEZI PE PROPRIU RISC!</string>
+    <string name="vibration_profile_short">Scurt</string>
+    <string name="fw_upgrade_notice_zepp_os">E葯ti pe cale s膬 instalezi firmware-ul %s pe %s.
+\n
+\nDup膬 instalarea fi葯ierului .zip, br膬葲ara va reporni.
+\n
+\nAC葰IONEZI PE PROPRIU RISC!</string>
+    <string name="wearmode_band">Band膬 (br膬葲ar膬)</string>
+    <string name="pref_summary_casio_alert_email">Alert膬 (vibrare/bip) pentru notific膬ri prin e-mail</string>
+    <string name="pref_title_casio_alert_call">Alert膬 pentru apeluri primite</string>
+    <string name="pref_time_sync">Sincronizare automat膬 a timpului</string>
+    <string name="pref_summary_custom_deviceicon">Afi葯eaz膬 pictograma de notificare Android specific膬 dispozitivului 卯n loc de pictograma Gadgetbridge c芒nd este conectat</string>
+    <string name="pref_summary_casio_alert_call">Alert膬 (vibra葲ie/bip) pentru apelurile primite</string>
+    <string name="pref_title_casio_alert_email">Alert膬 pentru notific膬ri prin e-mail</string>
+    <string name="pref_title_casio_alert_sms">Alert膬 pentru notific膬ri prin SMS</string>
+    <string name="pref_title_autoremove_notifications">Elimin膬 automat notific膬rile respinse</string>
+    <string name="pref_title_pebble_forceuntested">Activeaz膬 func葲iile netestate</string>
+    <string name="toast_aqurired_networklocation">Loca葲ie ob葲inut膬</string>
+    <string name="pref_title_pebble_forceprotocol">Protocol for葲at de notificare</string>
+    <string name="pref_summary_pebble_forceprotocol">Aceast膬 op葲iune for葲eaz膬 utilizarea celui mai recent protocol de notificare 卯n func葲ie de versiunea firmware-ului. PORNE葮TE DAC膫 葮TII EXACT DE CE FACI ASTA!</string>
+    <string name="pref_title_sync_caldendar">Sincronizeaz膬 evenimentele din calendar</string>
+    <string name="prefs_sensors_button_bp_calibration_sum">Apas膬 aici pentru a 卯ncepe calibrarea</string>
+    <string name="power_mode_saving">Economisire energie</string>
+    <string name="pref_title_device_spec_settings_show_raw_graph">Afi葯eaz膬 datele RAW pe graficul de activitate</string>
+    <string name="pref_title_sensors_altitude">Calibrarea altitudinii</string>
+    <string name="prefs_screen_orientation">Orientarea ecranului</string>
+    <string name="zetime_calories_type_active">Doar calorii arse activ</string>
+    <string name="zetime_calories_type_all">Calorii arse activ 葯i inactiv</string>
+    <string name="zetime_activity_tracking">Urm膬rirea activit膬葲ii</string>
+    <string name="zetime_activity_tracking_summary">Activarea urm膬ririi activit膬葲ii, va num膬ra pa葯ii 葯i a葯a mai departe.</string>
+    <string name="zetime_handmove_display">Mi葯carea m芒inii</string>
+    <string name="zetime_handmove_display_summary">Rote葯te 卯ncheietura m芒inii pentru a activa sau dezactiva afi葯ajul.</string>
+    <string name="zetime_prefs_inactivity_su">Duminic膬</string>
+    <string name="zetime_signaling_vibrate">Vibra葲ie continu膬</string>
+    <string name="zetime_signaling_vibrate_beep">Vibra葲ii 葯i bipuri continue</string>
+    <string name="zetime_signaling_vibrate_twice">Vibreaz膬 de dou膬 ori</string>
+    <string name="zetime_signaling_beep_once">Bip o dat膬</string>
+    <string name="pref_cache_weather_summary">Informa葲iile meteo vor fi stocate 卯n cache la repornirea aplica葲iei.</string>
+    <string name="busy_task_fetch_sleep_data">Preluare date despre somn</string>
+    <string name="prefs_weardirection">Direc葲ia de purtare</string>
+    <string name="vibration_profile_waterdrop">Pic膬tur膬 de ap膬</string>
+    <string name="heartrate_bpm_135">135 b膬t膬i/min</string>
+    <string name="title_activity_set_reminders">Configurare memento-uri</string>
+    <string name="pref_title_preview_message_in_title">Afi葯eaz膬 o previzualizare a mesajului 卯n titlu</string>
+    <string name="male">B膬rbat</string>
+    <string name="pref_title_goal_secondary">Scop suplimentar</string>
+    <string name="discovery_bluetooth_scan">Scanare Bluetooth:</string>
+    <string name="discovery_bluetooth_le_scan">Scanare Bluetooth LE:</string>
+    <string name="action_discover">Conecteaz膬 un dispozitiv nou</string>
+    <string name="find_my_phone_companion_warning">Asocierea 卯nso葲itorului este necesar膬 pentru g膬sirea telefonului. Click aici pentru mai multe informa葲ii.</string>
+    <string name="bluetooth_is_disabled_">Bluetooth dezactivat.</string>
+    <string name="discovery_start_scanning">脦ncepe c膬utarea</string>
+    <string name="title_activity_mi_band_pairing">Asociaz膬 Mi Band</string>
+    <string name="android_pairing_hint">Utilizeaz膬 dialogul de asociere Bluetooth Android pentru a asocia dispozitivul.</string>
+    <string name="pairing">Se asociaz膬 cu %s鈥�</string>
+    <string name="pairing_creating_bond_with">Crearea unei leg膬turi cu %1$s (%2$s)</string>
+    <string name="pairing_unable_to_pair_with">Imposibil de asociat cu %1$s (%2$s)</string>
+    <string name="buttons_on_right">Butoanele 卯n dreapta</string>
+    <string name="preferences_category_device_specific_settings">Set膬ri specifice dispozitivului</string>
+    <string name="preferences_miband_1_2_settings" tools:ignore="TypographyFractions">Set膬ri Mi Band 1/2</string>
+    <string name="vertical">Vertical</string>
+    <string name="buttons_on_left">Butoanele 卯n st芒nga</string>
+    <string name="discovery_note">Not膬:</string>
+    <string name="pref_header_sound">Sunet</string>
+    <string name="pref_summary_general_reconnectonlytoconnected">Reconectare numai la dispozitivele conectate 葯i nu la toate la r芒nd</string>
+    <string name="pref_summary_mb_intents">Activeaz膬 dac膬 controlul media al dispozitivului nu func葲ioneaz膬 pentru anumite aplica葲ii</string>
+    <string name="pref_theme_system">Sistem</string>
+    <string name="pref_theme_dynamic">Culori dinamice</string>
+    <string name="pref_title_notification_delay_calls">脦nt芒rziere de notificare a apelurilor</string>
+    <string name="pref_summary_receive_calls_watch">Efectueaz膬 葯i prime葯te apeluri direct de pe ceas</string>
+    <string name="bluetooth_calls_settings">Set膬ri de apelare Bluetooth</string>
+    <string name="pref_display_caller_title">Afi葯are informa葲ii de contact</string>
+    <string name="pref_display_caller_summary">Afi葯eaz膬 num膬rul de telefon sau numele pentru apelurile primite</string>
+    <string name="pref_pair_bluetooth_calls_title">Asociere pentru apeluri bluetooth</string>
+    <string name="pref_pair_bluetooth_calls_summary">Clic aici pentru a 卯ncepe procesul de conectare</string>
+    <string name="pref_pair_bluetooth_calls_help_title">Cum s膬 prime葯ti apeluri prin Bluetooth</string>
+    <string name="pref_pair_bluetooth_calls_help_3">3. Activeaz膬 mai jos apelarea prin Bluetooth.</string>
+    <string name="pref_pair_bluetooth_calls_help_warning">AVERTISMENT: Dac膬 activezi apelarea Bluetooth f膬r膬 a asocia un al doilea dispozitiv, este posibil ca notific膬rile de apel s膬 nu func葲ioneze ca de obicei.</string>
+    <string name="pref_summary_notifications_pebblemsg">Pentru aplica葲iile care trimit notific膬ri c膬tre Pebble folosind PebbleKit.</string>
+    <string name="pref_title_notifications_ignore_low_priority">Ignor膬 notific膬rile cu prioritate sc膬zut膬</string>
+    <string name="pref_summary_notifications_ignore_low_priority">Nu trimite notific膬ri cu prioritate sc膬zut膬 sau minim膬 spre ceas</string>
+    <string name="pref_title_notifications_ignore_work_profile">Ignor膬 notific膬rile profilului de serviciu</string>
+    <string name="pref_title_notification_prefer_long_text">Prefer textul de notificare lung</string>
+    <string name="pref_summary_notification_prefer_long_text">Dac膬 este disponibil, trimite o notificare cu text lung c膬tre dispozitiv</string>
+    <string name="pref_title_notification_cache_while_disconnected">Memoreaz膬 卯n cache c芒nd nu mai este din raz膬</string>
+    <string name="pref_summary_notification_times_enabled">Trimite notific膬ri numai 卯n acest interval de timp</string>
+    <string name="pref_title_notification_media_ignores_application_list">Lista aplica葲iilor cu notific膬ri media ignorate</string>
+    <string name="pref_title_notification_use_as_deny">脦mpiedic膬 aplica葲iile selectate s膬 afi葯eze notific膬ri</string>
+    <string name="pref_title_notification_use_as_allow">Permite notific膬ri de la aplica葲iile selectate</string>
+    <string name="pref_summary_banglejs_phone_gps_enbale">Utilizeaz膬 geo-localizarea telefonului pentru a suprascrie datele de geo-localizare de pe dispozitivul portabil</string>
+    <string name="pref_title_banglejs_phone_gps_network_only">Utilizeaz膬 re葲eaua numai pentru a determina loca葲ia</string>
+    <string name="pref_title_banglejs_webview_url">Adresa URL a aplica葲iei de 卯nc膬rcare</string>
+    <string name="pref_title_call_privacy_mode">Confiden葲ialitate apeluri</string>
+    <string name="pref_blacklist_calendars_summary">Calendarele din lista neagr膬 nu vor fi sincronizate cu dispozitivul</string>
+    <string name="pref_canned_message">Mesaje</string>
+    <string name="pref_title_pebble_sync_health">Sincronizare cu Pebble Health</string>
+    <string name="pref_title_enable_outgoing_call">Suport pentru apeluri de ie葯ire</string>
+    <string name="pref_summary_sunrise_sunset">Afi葯eaz膬 orele r膬s膬ritului 葯i apusului 卯n func葲ie de loca葲ie pe cronologia Pebble</string>
+    <string name="pref_summary_preview_message_in_title">Afi葯eaz膬 mesajul 卯n antetul notific膬rilor 卯n func葲ie de set膬rile de confiden葲ialitate ale dispozitivului</string>
+    <string name="pref_summary_casio_alert_other">Alert膬 (vibrare/bip) pentru notific膬ri din alt膬 categorie</string>
+    <string name="pref_title_screen_on_on_notifications">Pornire ecran la notific膬ri</string>
+    <string name="pref_summary_screen_on_on_notifications">Porne葯te ecranul ceasului c芒nd sose葯te o notificare</string>
+    <string name="pref_title_send_app_notifications">Trimite notific膬ri</string>
+    <string name="pref_summary_send_app_notifications">Trimite notific膬rile aplica葲iilor c膬tre dispozitiv</string>
+    <string name="pref_pebble_privacy_mode_content">Mut膬 textul de notificare dincolo de limitele ecranului</string>
+    <string name="pref_pebble_privacy_mode_complete">Afi葯eaz膬 numai pictograma de notificare</string>
+    <string name="pref_header_location">Loca葲ie</string>
+    <string name="pref_header_navigation">Navigare</string>
+    <string name="pref_title_location_keep_uptodate">P膬streaz膬 loca葲ia actualizat膬</string>
+    <string name="toast_enable_networklocationprovider">Activeaz膬 localizarea 卯n re葲ea</string>
+    <string name="pref_summary_pebble_forceuntested">Activeaz膬 func葲iile care nu au fost testate. PORNE葮TE PE PROPRIU RISC!</string>
+    <string name="pref_summary_pebble_always_ack_pebblekit">Mesajele trimise c膬tre aplica葲ii externe ter葲e vor fi permise 卯ntotdeauna 葯i imediat</string>
+    <string name="pref_summary_pebble_mtu_limit">Dac膬 Pebble 2/Pebble LE nu func葲ioneaz膬 conform a葯tept膬rilor, 卯ncearc膬 aceast膬 setare pentru a limita MTU (interval valid 20鈥�512)</string>
+    <string name="pref_summary_pebble_enable_bgjs">C芒nd este activat膬, interfa葲a ceasului poate afi葯a informa葲ii despre vreme, despre baterie etc.</string>
+    <string name="pref_title_pebble_reconnect_attempts">脦ncerc膬ri de reconectare</string>
+    <string name="pref_summary_expose_hr">Permite altor aplica葲ii s膬 acceseze datele senzorului de puls 卯n timp ce sunt conectate prin Gadgetbridge</string>
+    <string name="pref_title_connected_advertisement">Vizibil atunci c芒nd este conectat</string>
+    <string name="pref_title_use_custom_font">Utilizeaz膬 font personalizat</string>
+    <string name="pref_summary_allow_high_mtu">M膬re葯te viteza de transfer, dar este posibil s膬 nu func葲ioneze pe unele dispozitive Android.</string>
+    <string name="pref_title_overwrite_settings_on_connection">Suprascrie set膬rile la conexiune</string>
+    <string name="pref_title_device_internet_access">Permite accesul la internet</string>
+    <string name="pref_summary_device_internet_access">Permite aplica葲iilor de pe acest dispozitiv s膬 acceseze internetul</string>
+    <string name="pref_summary_sync_calendar">Activeaz膬 alertele din calendar, chiar 葯i atunci c芒nd este deconectat</string>
+    <string name="pref_display_add_device_fab_on">Vizibil mereu</string>
+    <string name="pref_summary_huami_force_new_protocol">Activeaz膬 dac膬 dispozitivul nu se mai conecteaz膬 dup膬 o actualizare a firmware-ului</string>
+    <string name="prefs_title_all_day_heart_rate">M膬surarea pulsului pe tot parcursul zilei</string>
+    <string name="pref_title_notifications_and_calls_repeat_on_call">Repet膬 notific膬rile despre apel</string>
+    <string name="prefs_notifications_and_calls_continious_ring">Notificare 卯n timpul soneriei telefonului</string>
+    <string name="pref_notifications_and_calls_enable_misscall">Notificare pentru apel pierdut</string>
+    <string name="pref_title_notifications_and_calls_repeat_on_misscall">Se repet膬 la fiecare X minute</string>
+    <string name="pref_header_notifications_and_calls_callhandling">Apel 卯n a葯teptare</string>
+    <string name="pref_summary_notifications_and_calls_title_shake_reject">Duplic膬 ac葲iunea butonului ceasului</string>
+    <string name="pref_header_device_spec_settings">Set膬rile dispozitivului</string>
+    <string name="pref_summary_device_spec_settings_title_force_time">Sincronizare automat膬 for葲at膬 a orei la reconectare. Acele analogice pot indica timpul gre葯it!</string>
+    <string name="zetime_title_heartrate">Set膬ri ale pulsului</string>
+    <string name="zetime_title_screentime">Durata afi葯膬rii 卯n secunde</string>
+    <string name="zetime_title_heart_rate_alarm">Alarm膬 de puls</string>
+    <string name="zetime_title_heart_rate_alarm_summary">Ceasul va avertiza c芒nd pulsul dep膬葯e葯te limitele.</string>
+    <string name="activity_prefs_alarm_max_heart_rate">Puls maxim</string>
+    <string name="zetime_analog_mode_handsandsteps">M芒inile 葯i pa葯ii</string>
+    <string name="zetime_prefs_inactivity_we">Miercuri</string>
+    <string name="zetime_prefs_inactivity_th">Joi</string>
+    <string name="zetime_prefs_inactivity_fr">Vineri</string>
+    <string name="zetime_prefs_inactivity_sa">S芒mb膬t膬</string>
+    <string name="zetime_signaling_beep_twice">Bip de dou膬 ori</string>
+    <string name="title_activity_device_specific_settings">Set膬ri specifice dispozitivului</string>
+    <string name="pref_explanation_authkey">Unele dispozitive necesit膬 o cheie de asociere special膬 pentru prima ini葲ializare a dispozitivului. Atinge aici pentru mai multe detalii 卯n wiki.</string>
+    <string name="prefs_hr_alarm_activity">Alarma de puls 卯n timpul activit膬葲ii sportive</string>
+    <string name="prefs_hr_alarm_low">Limit膬 inferioar膬</string>
+    <string name="pref_gps_header">GPS</string>
+    <string name="pref_gps_satellite_search">C膬utare sateli葲i</string>
+    <string name="pref_crown_vibration">Vibra葲ia coroanei</string>
+    <string name="pref_alert_tone">Ton de alert膬</string>
+    <string name="title_activity_discovery">C膬utare dispozitiv</string>
+    <string name="discovery_stop_scanning">Opre葯te scanarea</string>
+    <string name="title_activity_android_pairing">Asociaz膬 dispozitivul</string>
+    <string name="horizontal">Orizontal</string>
+    <string name="pref_touch_tone">Ton de atingere</string>
+    <string name="pref_touch_tone_summary">Red膬 un ton c芒nd se ating c膬葯tile</string>
+    <string name="pref_wearing_tone_summary">Red膬 un ton c芒nd este introdus膬 casca</string>
+    <string name="pref_cover_to_mute">Acoper膬 pentru a dezactiva sunetul</string>
+    <string name="pref_vibrate_for_alert">Vibrare pentru alert膬</string>
+    <string name="pref_text_to_speech">Sintetizator de vorbire</string>
+    <string name="offline_voice_respond_turn_wrist">R膬spuns la 卯ntoarcerea 卯ncheieturii m芒inii</string>
+    <string name="offline_voice_response_during_screen_lighting">R膬spuns 卯n timpul ilumin膬rii ecranului</string>
+    <string name="pref_agps_expiry_reminder_enabled">Memento de expirare AGPS</string>
+    <string name="pref_agps_expiry_reminder_time">Ora de memento a expir膬rii AGPS</string>
+    <string name="pref_agps_expire_time">Ora de expirare AGPS</string>
+    <string name="pref_camera_remote_title">Telecomanda camerei</string>
+    <string name="pref_camera_remote_summary">Permite ceasului s膬 declan葯eze camera telefonului</string>
+    <string name="pref_workout_start_on_phone_summary">Porne葯te/opre葯te urm膬rirea aplica葲iei de fitness pe telefon c芒nd 卯ncepe un antrenament GPS pe band膬</string>
+    <string name="discovery_connected_devices_hint">F膬 dispozitivul detectabil. Este posibil ca dispozitivele conectate 卯n prezent s膬 nu fie descoperite. Activeaz膬 loca葲ia (de exemplu, GPS) pe Android 6+. Dezactiveaz膬 Privacy Guard pentru Gadgetbridge, deoarece se poate bloca 葯i reporni telefonul. Dac膬 nu se g膬se葯te niciun dispozitiv dup膬 c芒teva minute, 卯ncearc膬 din nou dup膬 repornirea dispozitivului mobil.</string>
+    <string name="pref_header_vibration_count">Num膬r de vibra葲ii</string>
+    <string name="pref_write_logfiles_not_available">Ini葲ializarea 卯nregistr膬rii fi葯ierelor a e葯uat, scrierea fi葯ierelor jurnal nu este disponibil膬 momentan. Reporne葯te aplica葲ia pentru a 卯ncerca s膬 ini葲ializezi din nou fi葯ierele jurnal.</string>
+    <string name="busy_task_fetch_activity_data">Preluare date de activitate</string>
+    <string name="busy_task_fetch_debug_logs">Se preiau jurnalele de depanare</string>
+    <string name="busy_task_fetch_stress_data">Preluarea datelor de stres</string>
+    <string name="busy_task_fetch_hr_data">Preluare date privind pulsul</string>
+    <string name="vibration_profile_long">Lung</string>
+    <string name="vibration_try">脦ncearc膬</string>
+    <string name="pref_screen_notification_profile_sms">Notificare SMS</string>
+    <string name="pref_screen_notification_profile_email">Notificare e-mail</string>
+    <string name="pref_screen_notification_profile_incoming_call">Notificare pentru apeluri primite</string>
+    <string name="pref_screen_notification_profile_generic_chat">Conversa葲ie</string>
+    <string name="pref_screen_vibration_patterns_title">Modele de vibra葲ii</string>
+    <string name="heartrate_bpm_105">105 b膬t膬i/min</string>
+    <string name="heartrate_bpm_165">165 b膬t膬i/min</string>
+    <string name="heartrate_bpm_175">175 b膬t膬i/min</string>
+    <string name="heartrate_bpm_195">195 b膬t膬i/min</string>
+    <string name="heartrate_bpm_205">205 b膬t膬i/min</string>
+    <string name="title_activity_set_alarm">Configureaz膬 alarmele</string>
+    <string name="pref_world_clocks_title">Ceasuri mondiale</string>
+    <string name="pref_contacts_summary">Configureaz膬 contactele pe ceas</string>
+    <string name="reminder_date">Dat膬</string>
+    <string name="reminder_delete_confirm_description">Sigur dore葯ti s膬 葯tergi memento-ul?</string>
+    <string name="agps_status_current">Actual</string>
+    <string name="agps_status_error">Eroare</string>
+    <string name="pref_workout_start_on_phone_title">Urm膬rirea aplica葲iei de fitness</string>
+    <string name="pref_agps_status">Stare AGPS</string>
+    <string name="agps_status_missing">Lips膬</string>
+    <string name="agps_status_pending">脦n a葯teptare</string>
+    <string name="tap_connected_device_for_app_mananger">Atinge dispozitivul conectat pentru Managerul de aplica葲ii</string>
+    <string name="cannot_connect_bt_address_invalid_">Nu se poate conecta. Adresa Bluetooth nevalid膬?</string>
+    <string name="installation_failed_">Instalarea a e葯uat</string>
+    <string name="tap_connected_device_for_activity">Atinge dispozitivul conectat pentru activitate</string>
+    <string name="tap_connected_device_for_vibration">Atinge dispozitivul conectat pentru vibra葲ie</string>
+    <string name="tap_a_device_to_connect">Atinge un dispozitiv pentru a-l conecta</string>
+    <string name="installing_binary_d_d">Instalarea fi葯ierului binar %1$d/%2$d</string>
+    <string name="left">St芒nga</string>
+    <string name="wearmode_pebble">Pebble (dispozitiv de fixare a pantofilor)</string>
+    <string name="wearmode_necklace">Colier (dantel膬 卯n jurul g芒tului)</string>
+    <string name="miband_pairing_tap_hint">C芒nd Mi Band vibreaz膬 葯i clipe葯te, atinge-l de mai multe ori.</string>
+    <string name="discovery_entered_invalid_authkey">Cheia secret膬 de autentificare pe care ai introdus-o este nevalid膬! Apas膬 lung pe dispozitiv pentru a edita.</string>
+    <string name="discovery_need_to_enter_authkey">Acest dispozitiv are nevoie de o cheie secret膬 de autentificare, apas膬 lung pe dispozitiv pentru a o introduce. Cite葯te wiki.</string>
+    <string name="heartrate_bpm_100">100 de b膬t膬i/min</string>
+    <string name="interval_ten_minutes">la fiecare 10 minute</string>
+    <string name="interval_fifteen_minutes">la fiecare 15 minute</string>
+    <string name="heartrate_bpm_125">125 b膬t膬i/min</string>
+    <string name="reminder_time_once">%1$s, o dat膬</string>
+    <string name="reminder_time_every_year">%1$s, 卯n fiecare an</string>
+    <string name="reminder_every_day">脦n fiecare zi</string>
+    <string name="reminder_delete_confirm_title">葮terge memento-ul</string>
+    <string name="contact_delete_confirm_title">葮terge contactul</string>
+    <string name="pref_summary_notification_media_ignores_application_list">Proceseaz膬 notific膬rile media 卯nainte de lista de aplica葲ii. Dac膬 aceast膬 preferin葲膬 nu este bifat膬, aplica葲iile media trebuie s膬 fie permise 卯n lista de aplica葲ii pentru ca controalele media s膬 func葲ioneze pe dispozitiv.</string>
+    <string name="about_description_main_nopebble">O alternativ膬 gratuit膬 鈥瀎膬r膬 nor鈥� la aplica葲iile 卯nchise de la produc膬torii de gadgeturi. Versiuni 鈥濶ightly鈥� ale Gadgetbridge. 脦n aceast膬 versiune, furnizorul Pebble a fost redenumit pentru a preveni conflictele, a葯a c膬 unele dintre integr膬rile Pebble nu vor func葲iona, dar poate fi instalat 卯mpreun膬 cu instala葲iile Gadgetbridge existente.</string>
+    <string name="open_fw_installer_info_text">Firmware/Watchface/App/File Installer permite 卯nc膬rcarea/instalarea fi葯ierelor acceptate (firmware, watchface, aplica葲ii, GPS, resurse, fonturi...) pe dispozitiv. Vezi mai multe informa葲ii 卯n wiki: https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Firmware-Update</string>
+    <string name="fw_upgrade_notice_amazfit_gts4_mini">E葯ti pe cale s膬 instalezi firmware-ul %s pe Amazfit GTS 4 Mini.
+\n
+\nDup膬 instalarea fi葯ierului .zip, br膬葲ara va reporni.
+\n
+\nAC葰IONEZI PE PROPRIU RISC!</string>
+    <string name="fw_upgrade_notice_amazfit_gtr3">E葯ti pe cale s膬 instalezi firmware-ul %s pe Amazfit GTR 3.
+\n
+\nDup膬 instalarea fi葯ierului .zip, br膬葲ara va reporni.
+\n
+\nAC葰IONEZI PE PROPRIU RISC!</string>
+    <string name="open_fw_installer_warning_text">Aceast膬 caracteristic膬 poate deteriora dispozitivul. Cu toate acestea, acest lucru nu s-a 卯nt芒mplat niciodat膬 cu niciunul dintre dezvoltatori din cauza instal膬rilor firmware, dar aminte葯te-葲i c膬 faci acest lucru pe propriul risc.</string>
+    <string name="open_fw_installer_getting_files_text">Deoarece este posibil s膬 nu distribuim fi葯ierele firmware, va trebui s膬 ob葲ii fi葯ierele singur. Aceasta 卯nseamn膬 c膬 va trebui s膬 cau葲i fi葯ierele 卯n fi葯ierele apk, online, pe forumuri, pe Amazfitwatchfaces (pentru dispozitive Miband/Amazfit) 葯i a葯a mai departe.</string>
+    <string name="pref_title_general_reconnectonlytoconnected">Reconectare numai la dispozitivele conectate</string>
+    <string name="pref_summary_banglejs_webview_url">Dac膬 dore葯ti s膬 utilizezi un program de desc膬rcare de aplica葲ii personalizat, pune linkul https://鈥�/android.html aici. 脦n caz contrar, las膬 necompletat pentru https://banglejs.com/apps</string>
+    <string name="pref_title_transliteration">Transliterare</string>
+    <string name="pref_title_message_privacy_mode">Modul de confiden葲ialitate a mesajelor</string>
+    <string name="pref_message_privacy_mode_complete">Ascunde tot con葲inutul</string>
+    <string name="pref_title_weather_location">Loca葲ia meteo (pentru furnizorul de vreme LineageOS)</string>
+    <string name="pref_summary_casio_alert_sms">Alert膬 (vibra葲ie/bip) pentru notific膬ri prin SMS (mesaj text)</string>
+    <string name="pref_summary_pebble_forcele">Utilizeaz膬 suportul experimental Pebble LE pentru toate Pebble 卯n loc de BT clasic, necesit膬 asociere 鈥濸ebble LE鈥� dup膬 ce un non-LE a fost asociat</string>
+    <string name="installation_successful">Instalat</string>
+    <string name="other">Altul</string>
+    <string name="right">Dreapta</string>
+    <string name="miband_pairing_using_dummy_userdata">Nu s-au furnizat date valide de utilizator, se utilizeaz膬 date de utilizator fictive pentru moment.</string>
+    <string name="pref_screen_notification_profile_low_power">Avertizare baterie desc膬rcat膬</string>
+    <string name="pref_screen_notification_profile_generic_navigation">Navigare</string>
+    <string name="pref_screen_notification_profile_inactivity">Notificare de inactivitate</string>
+    <string name="pref_screen_notification_profile_anti_loss">Avertisment anti-pierdere</string>
+    <string name="pref_screen_notification_profile_schedule">Program</string>
+    <string name="reminder_message">Mesaj</string>
+    <string name="reminder_time_every_day">%1$s, 卯n fiecare zi</string>
+    <string name="reminder_no_free_slots_description">Dispozitivul nu are slot-uri libere pentru memento-uri (slot-uri totale: %1$s)</string>
+    <string name="pref_display_add_device_fab_off">Vizibil numai dac膬 nu este ad膬ugat niciun dispozitiv</string>
+    <string name="title_activity_LenovoWatch_calibration">Calibrarea Watch X Plus</string>
+    <string name="pref_header_notifications_and_calls">Notific膬ri 葯i apeluri</string>
+    <string name="pref_wearing_tone">Semnal de purtare</string>
+    <string name="pref_agps_update_time">Ora actualiz膬rii AGPS</string>
+    <string name="pref_morning_updates_title">Actualiz膬ri de diminea葲膬</string>
+    <string name="gps_glonass">GPS + GLONASS</string>
+    <string name="lack_of_sleep">Lipsa somnului: %1$s</string>
+    <string name="prefs_sounds">Sunete</string>
+    <string name="devicestatus_upload_aborted">脦nc膬rcarea a fost anulat膬!</string>
+    <string name="devicestatus_upload_started">脦nc膬rcarea a 卯nceput</string>
+    <string name="devicestatus_disconnecting">Dispozitivul se deconecteaz膬!</string>
+    <string name="activity_list_summary_activities">Activit膬葲i</string>
+    <string name="activity_list_summary_intensity">Intensitate
+\nmi葯care</string>
+    <string name="prefs_sounds_summary">Configureaz膬 c芒nd dispozitivul va emite un bip</string>
+    <string name="devicestatus_upload_starting">脦nc膬rcarea 卯ncepe</string>
+    <string name="devicestatus_connecting">Dispozitivul se conecteaz膬</string>
+    <string name="fwapp_install_device_not_ready">Fi葯ierul nu poate fi instalat, dispozitivul nu este preg膬tit.</string>
+    <string name="installhandler_firmware_name">%1$s: %2$s %3$s</string>
+    <string name="miband_fwinstaller_compatible_version">Versiune compatibil膬</string>
+    <string name="overslept">Somn excesiv: %1$s</string>
+    <string name="devicestatus_upload_completed">脦nc膬rcarea a fost finalizat膬</string>
+    <string name="devicestatus_upload_failed">脦nc膬rcarea a e葯uat</string>
+    <string name="devicestatus_upload_validating">脦nc膬rcarea este 卯n curs de validare</string>
+    <string name="dialog_hide">Ascunde</string>
+    <string name="show_ongoing_activity">Afi葯eaz膬 o fereastr膬 pop-up cu activitatea 卯n curs</string>
+    <string name="devicestatus_connected">Dispozitivul este conectat</string>
+    <string name="devicestatus_disconnected">Dispozitivul s-a deconectat!</string>
+    <string name="updating_firmware">Actualizare firmware鈥�</string>
+    <string name="fwapp_install_device_not_supported">Fi葯ierul nu poate fi instalat, dispozitivul nu este acceptat.</string>
+    <string name="charts_activity_list">Lista de activit膬葲i</string>
+    <string name="watchface_dialog_widget_color_black">Negru</string>
+    <string name="watchface_dialog_widget_type">Tip</string>
+    <string name="unknown">Necunoscut</string>
+    <string name="watchface_widget_type_date">Dat膬</string>
+    <string name="watchface_dialog_widget_x_coordinate">Coordonata X (max. 240)</string>
+    <string name="femometer_measurement_mode_precise">Mod precis (3 min)</string>
+    <string name="pref_title_auto_export_enabled">Export automat activat</string>
+    <string name="pref_auto_fetch">Preluare automat膬 a datelor despre activitate</string>
+    <string name="pref_auto_fetch_summary">Preluarea are loc la deblocarea ecranului. Func葲ioneaz膬 numai dac膬 este setat un mecanism de blocare!</string>
+    <string name="pref_auto_fetch_limit_fetches">Timp minim 卯ntre prelu膬ri</string>
+    <string name="pref_auto_fetch_limit_fetches_summary">Preluare la fiecare %d minute</string>
+    <string name="pref_disable_new_ble_scanning">Dezactiveaz膬 noua scanare BLE</string>
+    <string name="calories">Calorii</string>
+    <string name="pref_title_dont_ack_transfer">Nu confirma transmiterea datelor de activitate</string>
+    <string name="button_watchface_settings">Set膬ri cadran</string>
+    <string name="zepp_os_watchface_minimalist">Minimalist</string>
+    <string name="activity_list_summary_distance">Distan葲膬</string>
+    <string name="minutes_20">20 de minute</string>
+    <string name="minutes_60">60 de minute</string>
+    <string name="pref_enable_unsupported_settings_title">Activeaz膬 set膬rile neacceptate</string>
+    <string name="pref_title_third_party_app_device_settings">Permite aplica葲iilor ter葲e s膬 modifice set膬rile</string>
+    <string name="sleep_colored_stats_deep_avg">Mediu profund</string>
+    <string name="sleep_colored_stats_light_avg">Mediu u葯or</string>
+    <string name="stats_empty_value">-</string>
+    <string name="stats_lowest_hr">Cel mai mic puls</string>
+    <string name="stats_highest_hr">Cel mai mare puls</string>
+    <string name="femometer_measurement_mode_quick">Modul rapid (30s)</string>
+    <string name="pref_workout_detection_title">Detectarea antrenamentului</string>
+    <string name="prefs_charts_tabs">File de diagrame</string>
+    <string name="clock">Ceas</string>
+    <string name="abstract_chart_fragment_kind_light_sleep">Somn u葯or</string>
+    <string name="you_slept">%1$s - %2$s</string>
+    <string name="charts_min_max_heartrate_popup">Cel mai mic puls: %1$d
+\nCel mai mare puls: %2$d
+\nIntensitatea mi葯c膬rii: %3$s</string>
+    <string name="watchface_setting_desc_wrist_flick">(pentru a dezactiva complet, activeaz膬 mi葯carea relativ膬 葯i seteaz膬 toate valorile la 0)</string>
+    <string name="notif_battery_low_title">Bateria gadgetului sc膬zut膬!</string>
+    <string name="watchface_dialog_widget_timeout_hide_text">Ascunde textul la expirarea timpului</string>
+    <string name="alarm_fri_short">Vin</string>
+    <string name="fw_upgrade_notice_amazfitgtr">E葯ti pe cale s膬 instalezi firmware-ul %s pe Amazfit GTR.
+\n
+\nAsigur膬-te c膬 instalezi fi葯ierul .fw, iar dup膬 aceea fi葯ierul .res. Ceasul se va reporni dup膬 instalarea fi葯ierului .fw.
+\n
+\nNot膬: Nu trebuie s膬 instalezi .res dac膬 este exact acela葯i cu cel instalat anterior.
+\n
+\nAC葰IONEZI PE PROPRIU RISC!</string>
+    <string name="transition">Tranzi葲ie</string>
+    <string name="world_clock_no_free_slots_title">Nu sunt slot-uri libere</string>
+    <string name="alarm_mon_short">Lun</string>
+    <string name="user_feedback_miband_set_alarms_failed">A ap膬rut o eroare la setarea alarmelor, 卯ncearc膬 din nou.</string>
+    <string name="prefs_find_phone_duration">Durata soneriei 卯n secunde</string>
+    <string name="prefs_find_phone_summary">Folose葯te br膬葲ara pentru a reda tonul de apel al telefonului.</string>
+    <string name="watchface_dialog_widget_preset_right">Dreapta</string>
+    <string name="watchface_widget_type_weather">Meteo</string>
+    <string name="watchface_dialog_widget_color">Culoare</string>
+    <string name="watchface_widget_type_battery">Baterie</string>
+    <string name="watchface_setting_power_saving_display">Nu se actualizeaz膬 informa葲iile de pe ecran c芒nd dispozitivul este scos</string>
+    <string name="watchface_widget_type_steps">Pa葯i</string>
+    <string name="button_watchface_edit_name">Editeaz膬 numele</string>
+    <string name="uploadwatchfaceoperation_complete">Instalarea cadranului ceasului a fost finalizat膬</string>
+    <string name="prefs_enable_find_phone">Activeaz膬 鈥濭膬se葯te telefonul鈥�</string>
+    <string name="title_activity_alarm_details">Detalii alarm膬</string>
+    <string name="title_activity_reminder_details">Detalii memento</string>
+    <string name="you_did_not_sleep">Nu ai dormit</string>
+    <string name="live_activity_current_steps_per_minute">Pa葯i actuali pe minut</string>
+    <string name="minutes_30">30 de minute</string>
+    <string name="watchface_setting_wrist_flick_minute">Ac minute (de la -360 la 360)</string>
+    <string name="test">Testare</string>
+    <string name="test_notification">Notificare de testare</string>
+    <string name="cannot_connect">Nu se poate conecta: %1$s</string>
+    <string name="miband_prefs_hr_sleep_detection">Utilizeaz膬 senzorul de puls pentru a 卯mbun膬t膬葲i detectarea somnului</string>
+    <string name="pbwinstallhandler_app_item">%1$s (%2$s)</string>
+    <string name="world_clock_label">Etichet膬</string>
+    <string name="chart_get_active_and_synchronize">F膬 ceva activitate 葯i sincronizeaz膬 dispozitivul.</string>
+    <string name="prefs_activity_monitoring_title">Monitorizarea activit膬葲ii</string>
+    <string name="liveactivity_live_activity">Activitate curent膬</string>
+    <string name="activity_summary_today">Ast膬zi</string>
+    <string name="kind_watchface">Cadran</string>
+    <string name="minHR">Puls minim</string>
+    <string name="watchface_setting_display_refresh_partial">Re卯mprosp膬tare par葲ial膬 (卯n minute)</string>
+    <string name="watchface_widget_type_chance_rain">葮anse de ploaie</string>
+    <string name="power_mode_title">Modul de alimentare ceas</string>
+    <string name="pref_workout_send_gps_summary">Trimite loca葲ia GPS curent膬 c膬tre band膬 卯n timpul unui antrenament</string>
+    <string name="pref_sleep_mode_sleep_screen_title">Ecran de repaus</string>
+    <string name="world_clock_code">Cod</string>
+    <string name="title_activity_contact_details">Detalii contact</string>
+    <string name="title_activity_world_clock_details">Detalii ceas mondial</string>
+    <string name="alarm_sun_short">Dum</string>
+    <string name="alarm_tue_short">Mar</string>
+    <string name="user_feedback_miband_activity_data_transfer">Urmeaz膬 transferarea %1$s de date 卯ncep芒nd de la %2$s</string>
+    <string name="prefs_heartrate_alert_high_threshold">Pragul maxim de alert膬 a pulsului</string>
+    <string name="prefs_spo2_monitoring_description">Monitorizeaz膬 automat nivelul de oxigen din s芒nge pe parcursul zilei</string>
+    <string name="prefs_spo2_monitoring_title">Monitorizarea oxigenului din s芒nge</string>
+    <string name="dbaccess_error_executing">Eroare la executarea 鈥�%1$s鈥�</string>
+    <string name="installer_activity_unable_to_find_handler">Imposibil de g膬sit un manipulator pentru a instala acest fi葯ier.</string>
+    <string name="controlcenter_start_activitymonitor">Activitatea ta</string>
+    <string name="weeksleepchart_today_sleep_description">Somn ast膬zi, obiectiv: %1$s</string>
+    <string name="weeksleepchart_sleep_a_week">Somn s膬pt膬m芒nal</string>
+    <string name="chart_steps">Pa葯i</string>
+    <string name="prefs_find_phone">G膬se葯te telefonul</string>
+    <string name="prefs_shortcut_cards_summary">Carduri de comand膬 rapid膬 v膬zute c芒nd glisezi spre dreapta pe cadranul ceasului. C芒nd ruleaz膬 o aplica葲ie, cardurile generate automat nu sunt afectate de aceast膬 setare.</string>
+    <string name="pref_dashboard_widget_today_24h_title">Mod 24 ore</string>
+    <string name="cannot_upload_watchface_too_many_watchfaces_installed">Nu se poate 卯nc膬rca cadranul de ceas, sunt instalate prea multe cadrane de ceas</string>
+    <string name="prefs_disconnect_notification">Notificare de deconectare</string>
+    <string name="not_connected">Nu este conectat</string>
+    <string name="qhybrid_watchface_configuration_old_firmware">Ecran de configurare a cadranului pentru ceasuri cu versiunea de firmware DN1.0.2.19r 葯i anterioar膬</string>
+    <string name="pref_workout_detection_categories_title">Categorii de antrenament</string>
+    <string name="pref_summary_disable_new_ble_scanning">Bifeaz膬 aceast膬 op葲iune dac膬 dispozitivul nu poate fi g膬sit 卯n timpul descoperirii</string>
+    <string name="alarm_smart_wakeup">Trezire inteligent膬</string>
+    <string name="alarm_snooze">Am芒nare</string>
+    <string name="minutes_1">1 minut</string>
+    <string name="pref_huami_truncate_fetch_operation_timestamps_title">Reducerea marcajelor de timp ale opera葲iunilor de preluare</string>
+    <string name="live_activity_total_steps">Pa葯i total</string>
+    <string name="pref_summary_third_party_app_device_settings">Permite altor aplica葲ii ter葲e instalate s膬 seteze set膬rile dispozitivului prin inten葲ii.</string>
+    <string name="DEVINFO_HR_VER">"HR: "</string>
+    <string name="watchface_dialog_widget_cat_position">Pozi葲ie</string>
+    <string name="button_watchface_save_apply">Salveaz膬 葯i aplic膬</string>
+    <string name="watchface_dialog_widget_cat_2nd_tz_widget">Al doilea widget de fus orar</string>
+    <string name="watchface_setting_light_up_on_notification">Aprinde la o nou膬 notificare</string>
+    <string name="world_clock_delete_confirm_description">Sigur dore葯ti s膬 葯tergi ceasul mondial?</string>
+    <string name="alarm_sat_short">S芒m</string>
+    <string name="pbw_installhandler_pebble_firmware">Firmware Pebble %1$s</string>
+    <string name="miband_fwinstaller_untested_version">Versiune netestat膬!</string>
+    <string name="fwappinstaller_connection_state">Conexiune la dispozitiv: %1$s</string>
+    <string name="updatefirmwareoperation_updateproblem_low_battery">Bateria dispozitivului este prea sc膬zut膬</string>
+    <string name="updatefirmwareoperation_metadata_updateproblem">Problem膬 cu transferul de metadate a firmware-ului</string>
+    <string name="updatefirmwareoperation_updateproblem_free_space">Dispozitivul nu are suficient spa葲iu liber</string>
+    <string name="updatefirmwareoperation_update_complete_rebooting">Instalarea firmware-ului s-a 卯ncheiat, dispozitivul se reporne葯te鈥�</string>
+    <string name="live_activity_steps_history">Istoricul pa葯ilor</string>
+    <string name="prefs_always_on_display_follow_watchface">Stilul urmeaz膬 cadranul de ceas</string>
+    <string name="pref_dashboard_widget_today_title">Diagrama de activitate</string>
+    <string name="zepp_os_watchface_vibrant">Vibrant</string>
+    <string name="watchface_widget_type_uv_index">Indice UV</string>
+    <string name="weekstepschart_steps_a_week">Pa葯i pe s膬pt膬m芒n膬</string>
+    <string name="zepp_os_watchface_multiple_data">Date multiple</string>
+    <string name="button_watchface_add_widget">Adaug膬 widget</string>
+    <string name="watchface_widget_type_heart_rate">Puls</string>
+    <string name="pref_sleepasandroid_feat_heartrate">Puls</string>
+    <string name="watchface_dialog_widget_timezone_duration">Durata vizibilit膬葲ii ceasului (卯n secunde)</string>
+    <string name="info_fossil_rebuild_watchface_custom_menu">Reconstruie葯te cadranul ceasului pentru un meniu personalizat</string>
+    <string name="pref_dashboard_widget_today_hr_interval_title">Interval de m膬surare a pulsului</string>
+    <string name="watchface_setting_title_display_refresh_timeout">Timpul de a葯teptare pentru re卯mprosp膬tarea afi葯ajului</string>
+    <string name="watchface_setting_button_toggle_widgets">Comutare widget-uri</string>
+    <string name="watchface_cache_confirm_overwrite">Un cadran de ceas cu acest nume exist膬 deja 卯n cache. Dore葯ti s膬-l suprascrii?</string>
+    <string name="watchface_upload_failed">脦nc膬rcarea cadranului de ceas a e葯uat. 脦ncearc膬 din nou.</string>
+    <string name="pai_chart_per_month">PAI pe lun膬</string>
+    <string name="hrv">HRV</string>
+    <string name="pai_plus_num">+%d</string>
+    <string name="seconds_9">9 secunde</string>
+    <string name="seconds_7">7 secunde</string>
+    <string name="seconds_8">8 secunde</string>
+    <string name="sleep_colored_stats_light">U葯or</string>
+    <string name="watchface_widget_type_active_mins">Minute de activitate</string>
+    <string name="watchface_setting_title_power_saving">Economisire energie</string>
+    <string name="watchface_dialog_widget_background">Fundal</string>
+    <string name="notif_export_failed_title">Exportul bazei de date a e葯uat! Verific膬 set膬rile.</string>
+    <string name="notif_battery_full_title">Bateria este 卯nc膬rcat膬!</string>
+    <string name="sleepchart_your_sleep">Somn</string>
+    <string name="notif_battery_full">Bateria %1$s este 卯nc膬rcat膬 complet</string>
+    <string name="pai_chart_per_week">PAI pe s膬pt膬m芒n膬</string>
+    <string name="blood_pressure">Tensiunea arterial膬</string>
+    <string name="no_limit">F膬r膬 limite</string>
+    <string name="seconds_5">5 secunde</string>
+    <string name="heart_rate">Puls</string>
+    <string name="getting_heart_rate">M膬surare</string>
+    <string name="heart_rate_result">Rezultatele m膬sur膬torilor</string>
+    <string name="movement_intensity">Intensitatea mi葯c膬rii</string>
+    <string name="seconds_6">6 secunde</string>
+    <string name="live_activity_steps_per_minute_history">Istoricul pa葯ilor pe minut</string>
+    <string name="abstract_chart_fragment_kind_rem_sleep">Somn REM</string>
+    <string name="pref_title_keep_data_on_device">P膬streaz膬 datele despre activitate pe dispozitiv</string>
+    <string name="miband_fwinstaller_incompatible_version">Firmware incompatibil</string>
+    <string name="pref_sleep_breathing_quality_monitoring">Monitorizarea calit膬葲ii respira葲iei 卯n somn</string>
+    <string name="miband_prefs_device_time_offset_hours">Compensarea timpului dispozitivului 卯n ore (pentru detectarea somnului lucr膬torilor 卯n ture)</string>
+    <string name="miband2_prefs_dateformat">Formatul datei</string>
+    <string name="dateformat_date_time">Timp 葯i dat膬</string>
+    <string name="prefs_disconnect_notification_summary">Notificare pe dispozitiv c芒nd este deconectat de la Bluetooth.</string>
+    <string name="mi2_prefs_button_actions">Ac葲iunile butonului</string>
+    <string name="weather_notification_label">Asigur膬-te c膬 acest skin este activat 卯n aplica葲ia Notificare vreme pentru a ob葲ine informa葲ii despre vreme pe Pebble.
+\n
+\nNu este necesar膬 nicio configura葲ie aici.
+\n
+\nPo葲i activa aplica葲ia meteo de sistem a Pebble din gestionarea aplica葲iei.
+\n
+\nCadranele de ceas acceptate vor afi葯a automat vremea.</string>
+    <string name="button_watchface_select_image">Schimb膬 imaginea de fundal</string>
+    <string name="watchface_widget_type_calories">Calorii</string>
+    <string name="watchface_widget_type_2nd_tz">al 2-lea fus orar</string>
+    <string name="averageHR">Puls</string>
+    <string name="qhybrid_title_watchface">Configurarea cadranului ceasului</string>
+    <string name="watchface_widget_type_custom">Widget personalizat</string>
+    <string name="watchface_dialog_widget_timezone">Fus orar</string>
+    <string name="watchface_dialog_title_settings">Set膬ri cadran</string>
+    <string name="watchface_dialog_title_widget">Set膬ri widget</string>
+    <string name="zepp_os_watchface_simplicity_data">Date simple</string>
+    <string name="zetime_title_settings">Set膬ri ZeTime</string>
+    <string name="zetime_heart_rate_alarm_enable">Activeaz膬 alarma de puls</string>
+    <string name="pref_sleep_mode_smart_enable_summary">Activeaz膬 automat modul de repaus dac膬 br膬葲ara este purtat膬 卯n timp ce dormi</string>
+    <string name="pref_header_auto_export">Export automat de date</string>
+    <string name="pref_title_auto_export_location">Loca葲ie de export</string>
+    <string name="pref_title_auto_export_interval">Interval de export</string>
+    <string name="pref_summary_auto_export_interval">Export la fiecare %d ore</string>
+    <string name="contact_delete_confirm_description">Sigur dore葯ti s膬 葯tergi 鈥�%1$s鈥�?</string>
+    <string name="function_enabled">Activat</string>
+    <string name="world_clock_no_free_slots_description">Dispozitivul nu are slot-uri libere pentru ceasurile mondiale (slot-uri totale: %1$s)</string>
+    <string name="alarm_wed_short">Mie</string>
+    <string name="user_feedback_set_settings_ok">Set膬ri trimise pe dispozitiv.</string>
+    <string name="miband_prefs_fitness_goal">Obiectiv zilnic pentru pa葯i</string>
+    <string name="world_clock_timezone">Fus orar</string>
+    <string name="alarm_thu_short">Joi</string>
+    <string name="battery">Baterie</string>
+    <string name="seconds_12">12 secunde</string>
+    <string name="seconds_15">15 secunde</string>
+    <string name="distance">Distan葲膬</string>
+    <string name="seconds_20">20 de secunde</string>
+    <string name="minutes_2">2 minute</string>
+    <string name="minutes_3">3 minute</string>
+    <string name="minutes_4">4 minute</string>
+    <string name="minutes_6">6 minute</string>
+    <string name="minutes_180">180 de minute</string>
+    <string name="minutes_210">210 minute</string>
+    <string name="minutes_240">240 minute</string>
+    <string name="minutes_255">255 minute</string>
+    <string name="minutes_7">7 minute</string>
+    <string name="minutes_8">8 minute</string>
+    <string name="minutes_9">9 minute</string>
+    <string name="minutes_15">15 minute</string>
+    <string name="minutes_45">45 de minute</string>
+    <string name="minutes_75">75 de minute</string>
+    <string name="minutes_90">90 de minute</string>
+    <string name="minutes_120">120 de minute</string>
+    <string name="minutes_150">150 de minute</string>
+    <string name="watchface_setting_wrist_flick_duration">Durat膬 (卯n ms)</string>
+    <string name="watchface_setting_title_custom_events">Evenimente personalizate</string>
+    <string name="stress_average">Medie</string>
+    <string name="live_activity_max_heart_rate">Frecven葲a cardiac膬 actual膬/maxim膬: %1$d / %2$d</string>
+    <string name="uploadwatchfaceoperation_in_progress">Se 卯ncarc膬 cadranul de ceas</string>
+    <string name="zepp_os_watchface_rush">Grab膬</string>
+    <string name="zepp_os_watchface_business_style">Stil de afaceri</string>
+    <string name="uploading_watchface">Se 卯ncarc膬 cadranul de ceas鈥�</string>
+    <string name="uploadwatchfaceoperation_failed">Instalarea cadranului ceasului a e葯uat</string>
+    <string name="sleep_colored_stats_awake_avg">Mediu treaz</string>
+    <string name="watchface_dialog_widget_y_coordinate">Coordonata Y (max. 240)</string>
+    <string name="watchface_dialog_widget_preset_left">St芒nga</string>
+    <string name="qhybrid_title_watchface_designer">Designer cadran de ceas</string>
+    <string name="watchface_resolution_doesnt_match">Rezolu葲ia cadranului ceasului nu se potrive葯te cu ecranul dispozitivului. Cadranul ceasului este %1$s. Ecranul dispozitivului este %2$s</string>
+    <string name="alarm_smart_wakeup_interval_default">5 minute</string>
+    <string name="alarm_smart_wakeup_interval">Interval de trezire inteligent膬:</string>
+    <string name="notif_battery_low_percent">%1$s baterie r膬mas膬: %2$s%%</string>
+    <string name="notif_battery_low_bigtext_last_charge_time">Ultima 卯nc膬rcare: %s
+\n</string>
+    <string name="notif_battery_low_bigtext_number_of_charges">Num膬r de 卯nc膬rc膬ri: %s</string>
+    <string name="watchface_setting_button_toggle_backlight">Pornire iluminare</string>
+    <string name="watchface_setting_display_refresh_full">Re卯mprosp膬tare complet膬 (卯n minute)</string>
+    <string name="watchface_setting_title_wrist_flick">Mi葯carea 卯ncheieturii m芒inii</string>
+    <string name="watchface_setting_wrist_flick_move_relative">Mi葯carea acelor 卯n raport cu timpul</string>
+    <string name="watchface_setting_wrist_flick_hour">Ac or膬 (de la -360 la 360)</string>
+    <string name="weeksteps_today_steps_description">Pa葯i ast膬zi, obiectiv: %1$s</string>
+    <string name="overstep">Dep膬葯ire: %1$d</string>
+    <string name="app_install_info">E葯ti pe cale s膬 instalezi urm膬toarea aplica葲ie:
+\n
+\n%1$s
+\nVersiunea %2$s de la %3$s
+\n</string>
+    <string name="firmware_install_warning">脦NCERCI S膫 INSTALEZI UN FIRMWARE, AC葰IONEZI PE PROPRIU RISC.
+\n
+\n
+\nAcest firmware este pentru Revizia HW: %s</string>
+    <string name="activity_list_summary_active_time">Timp de activitate</string>
+    <string name="user_feedback_miband_set_alarms_ok">Alarme trimise pe dispozitiv.</string>
+    <string name="prefs_spo2_alert_threshold">Pragul de alert膬 SPO2</string>
+    <string name="num_min">%d minute</string>
+    <string name="activity_sleepchart_activity_and_sleep">Activitate</string>
+    <string name="activity_list_summary_active_steps">Pa葯i activi</string>
+    <string name="maxHR">Puls maxim</string>
+    <string name="pref_header_hrv_status">Starea HRV</string>
+    <string name="hrv_status">Starea HRV</string>
+    <string name="updatefirmwareoperation_updateproblem_do_not_reboot">Problem膬 cu transferul de firmware. NU REPORNI Mi Band-ul!</string>
+    <string name="updatefirmwareoperation_update_complete">Instalarea firmware-ului este finalizat膬</string>
+    <string name="watchface_dialog_widget_preset_top">Sus</string>
+    <string name="watchface_dialog_widget_preset_bottom">Jos</string>
+    <string name="watchface_dialog_widget_cat_generic">General</string>
+    <string name="watchface_dialog_widget_cat_custom_widget">Widget personalizat</string>
+    <string name="watchface_dialog_widget_presets">Preset膬ri pozi葲ie</string>
+    <string name="pref_title_device_intents">Permite inten葲ii</string>
+    <string name="pref_summary_device_intents">Permite aplica葲iilor de ceas Bangle.js s膬 trimit膬 inten葲ii Android 葯i permite altor aplica葲ii de pe Android (cum ar fi Tasker) s膬 trimit膬 date c膬tre Bangle.js cu inten葲ia com.banglejs.uart.tx. Este necesar膬 permisiunea de afi葯are peste alte aplica葲ii pentru a func葲iona 卯n fundal.</string>
+    <string name="femometer_measurement_mode_normal">Mod normal (60s-90s)</string>
+    <string name="preferences_makibes_hr3_settings">Set膬ri Makibes HR3</string>
+    <string name="pref_morning_updates_categories_title">Categorii actualiz膬ri de diminea葲膬</string>
+    <string name="pref_morning_updates_categories_summary">List膬 de categorii de afi葯at 卯n fiecare diminea葲膬</string>
+    <string name="pref_workout_send_gps_title">Trimite GPS 卯n timpul antrenamentului</string>
+    <string name="pref_workout_keep_screen_on_title">P膬streaz膬 ecranul pornit 卯n timpul unui antrenament</string>
+    <string name="pref_workout_detection_summary">Detecteaz膬 automat antrenamentul</string>
+    <string name="pref_workout_detection_categories_summary">Categoriile de antrenamente de detectat automat</string>
+    <string name="pref_workout_detection_alert_title">Alert膬</string>
+    <string name="pref_workout_detection_alert_summary">Notific膬 c芒nd este detectat un antrenament</string>
+    <string name="pref_workout_detection_sensitivity">Sensibilitate</string>
+    <string name="pref_workout_detection_enabled_summary">Activeaz膬 detectarea automat膬 a acestui antrenament</string>
+    <string name="pref_workout_detection_ask_first">脦ntreab膬-m膬 mai 卯nt芒i</string>
+    <string name="pref_workout_detection_ask_first_summary">Solicit膬 confirmarea pe ceas c芒nd se detecteaz膬 acest antrenament</string>
+    <string name="pref_workout_detection_time">Minute de activitate 卯nainte de detectare</string>
+    <string name="pref_workout_detection_time_summary">Cantitatea de minute 卯n care antrenamentul trebuie s膬 fie 卯n curs 卯nainte de a-l detecta</string>
+    <string name="pref_sleep_mode_title">Modul de repaus</string>
+    <string name="pref_sleep_mode_smart_enable_title">Activare inteligent膬</string>
+    <string name="pref_header_auto_fetch">Preluare automat膬</string>
+    <string name="connecting">Se conecteaz膬</string>
+    <string name="_unknown_">(necunoscut)</string>
+    <string name="this_is_a_test_notification_from_gadgetbridge">Aceasta este o notificare de testare de la Gadgetbridge</string>
+    <string name="watchface_install_info">E葯ti pe cale s膬 instalezi urm膬torul cadran de ceas:
+\n
+\n%1$s
+\nVersiunea %2$s de la %3$s
+\n</string>
+    <string name="pref_sleep_mode_sleep_screen_summary">Afi葯eaz膬 ecranul de repaus c芒nd se treze葯te ecranul 卯n timpul somnului, pentru a reduce distrac葲ia</string>
+    <string name="average">Medie: %1$s</string>
+    <string name="abstract_chart_fragment_kind_awake_sleep">Treaz</string>
+    <string name="sleep_colored_stats_deep">Ad芒nc</string>
+    <string name="sleep_colored_stats_rem">REM</string>
+    <string name="sleep_colored_stats_rem_avg">Mediu REM</string>
+    <string name="contact_no_free_slots_description">Dispozitivul nu are slot-uri libere pentru persoane de contact (slot-uri totale: %1$s)</string>
+    <string name="world_clock_delete_confirm_title">葮terge \'%1$s\'</string>
+    <string name="prefs_heartrate_alert_experimental_title">Alert膬 de puls (experimental)</string>
+    <string name="watchface_dialog_widget_color_white">Alb</string>
+    <string name="watchface_dialog_widget_width">L膬葲imea widget-ului (卯n pixeli)</string>
+    <string name="watchface_setting_power_saving_hands">Dezactiveaz膬 mi葯c膬rile m芒inii c芒nd cureaua este 卯ndep膬rtat膬</string>
+    <string name="watchface_dialog_widget_timeout_show_circle">Afi葯are cerc la expirarea timpului</string>
+    <string name="pref_workout_detection_enabled">Detectare activat膬</string>
+    <string name="pref_workout_keep_screen_on_summary">Ecranul va r膬m芒ne pornit 卯n timpul unui antrenament, iar luminozitatea va fi ajustat膬 pentru a afi葯a continuu date de antrenament 卯n timp real</string>
+    <string name="connected">Conectat</string>
+    <string name="unknown_state">Stare necunoscut膬</string>
+    <string name="prefs_heartrate_alert_threshold">Pragul de alert膬 a pulsului</string>
+    <string name="prefs_relaxation_reminder_description">Vibreaz膬 banda pentru a te anun葲a dac膬 valoarea stresului este mai mare de 80</string>
+    <string name="prefs_activity_monitoring_description">Cre葯te automat frecven葲a de detectare a pulsului atunci c芒nd banda detecteaz膬 exerci葲ii fizice, pentru a cre葯te acurate葲ea ob葲inerii pulsului.</string>
+    <string name="notif_battery_low">Bateria %1$s este desc膬rcat膬</string>
+    <string name="notif_battery_low_extended">Bateria %1$s desc膬rcat膬: %2$s</string>
+    <string name="prefs_charts_tabs_summary">File vizibile ale diagramelor</string>
+    <string name="seconds_10">10 secunde</string>
+    <string name="seconds_11">11 secunde</string>
+    <string name="seconds_13">13 secunde</string>
+    <string name="seconds_14">14 secunde</string>
+    <string name="seconds_25">25 de secunde</string>
+    <string name="seconds_30">30 de secunde</string>
+    <string name="minutes_5">5 minute</string>
+    <string name="minutes_10">10 minute</string>
+    <string name="abstract_chart_fragment_kind_deep_sleep">Somn profund</string>
+    <string name="abstract_chart_fragment_kind_not_worn">Nu a fost purtat</string>
+    <string name="device_not_connected">Nu este conectat.</string>
+    <string name="user_feedback_all_alarms_disabled">Toate alarmele sunt dezactivate</string>
+    <string name="live_activity_start_your_activity">脦ncepe activitatea</string>
+    <string name="abstract_chart_fragment_kind_activity">Activitate</string>
+    <string name="dateformat_time">Timp</string>
+    <string name="activity_filter_to_placeholder">ast膬zi</string>
+    <string name="fw_upgrade_notice_amazfitbip3">E葯ti pe cale s膬 instalezi firmware-ul %s pe Amazfit Bip 3.
+\n
+\nAsigur膬-te c膬 instalezi fi葯ierul .fw, iar dup膬 aceea fi葯ierul .res. Ceasul se va reporni dup膬 instalarea fi葯ierului .fw.
+\n
+\nNot膬: Nu trebuie s膬 instalezi .res dac膬 este exact acela葯i cu cel instalat anterior.
+\n
+\nAC葰IONEZI PE PROPRIU RISC!</string>
+    <string name="lack_of_step">Lipsa pa葯ilor: %1$d</string>
+    <string name="fw_upgrade_notice_amazfitbip3pro">E葯ti pe cale s膬 instalezi firmware-ul %s pe Amazfit Bip 3 Pro.
+\n
+\nAsigur膬-te c膬 instalezi fi葯ierul .fw, iar dup膬 aceea fi葯ierul .res. Ceasul se va reporni dup膬 instalarea fi葯ierului .fw.
+\n
+\nNot膬: Nu trebuie s膬 instalezi .res dac膬 este exact acela葯i cu cel instalat anterior.
+\n
+\nAC葰IONEZI PE PROPRIU RISC!</string>
+    <string name="watchface_dialog_title_set_name">Seteaz膬 numele cadranului ceasului</string>
+    <string name="button_watchface_preview">Previzualizare pe ceas</string>
+    <string name="fw_upgrade_notice_amazfitgts">E葯ti pe cale s膬 instalezi firmware-ul %s pe Amazfit GTS.
+\n
+\nAsigur膬-te c膬 instalezi fi葯ierul .fw, iar dup膬 aceea fi葯ierul .res. Ceasul se va reporni dup膬 instalarea fi葯ierului .fw.
+\n
+\nNot膬: Nu trebuie s膬 instalezi .res dac膬 este exact acela葯i cu cel instalat anterior.
+\n
+\nAC葰IONEZI PE PROPRIU RISC!</string>
+    <string name="fw_upgrade_notice_amazfit_trex">E葯ti pe cale s膬 instalezi firmware-ul %s pe Amazfit T-Rex.
+\n
+\nAten葲ie: mai 卯nt芒i instaleaz膬 fi葯ierul .fw, apoi fi葯ierul .res 葯i abia apoi fi葯ierul .gps. Dup膬 instalarea .fw, ceasul se va reporni.
+\n
+\nNot膬: nu trebuie s膬 instalezi .res 葯i .gps dac膬 aceste fi葯iere sunt exact acelea葯i cu cele instalate anterior.
+\n
+\nAC葰IONEZI PE PROPRIU RISC!</string>
+    <string name="chart_no_active_data">Nu au fost detectate activit膬葲i.</string>
+    <string name="chart_no_data_synchronize">F膬r膬 date. Sincronizezi dispozitivul?</string>
+    <string name="prefs_heartrate_alert_experimental_description">Vibrare atunci c芒nd pulsul dep膬葯e葯te un prag, f膬r膬 activitate fizic膬 evident膬 卯n ultimele 10 minute. Aceast膬 caracteristic膬 este experimental膬 葯i nu a fost testat膬 pe larg.</string>
+    <string name="prefs_stress_monitoring_title">Monitorizarea stresului</string>
+    <string name="prefs_stress_monitoring_description">Monitorizarea nivelului de stres 卯n timpul odihnei</string>
+    <string name="prefs_relaxation_reminder_title">Memento de relaxare</string>
+    <string name="pref_dashboard_widget_today_24h_summary">Afi葯eaz膬 activitatea 卯ntr-un singur cerc de 24 de ore 卯n loc de un cerc dublu de 12 ore</string>
+</resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml
new file mode 100644
index 0000000..eddf604
--- /dev/null
+++ b/app/src/main/res/values-ru/strings.xml
@@ -0,0 +1,3680 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources xmlns:tools="http://schemas.android.com/tools">
+    <string name="application_name_generic">Gadgetbridge</string>
+    <string name="title_activity_controlcenter_generic">Gadgetbridge</string>
+    <string name="action_settings">袧邪褋褌褉芯泄泻懈</string>
+    <string name="action_debug">袨褌谢邪写泻邪</string>
+    <string name="action_quit">袙褘褏芯写</string>
+    <string name="controlcenter_fetch_activity_data">小懈薪褏褉芯薪懈蟹懈褉芯胁邪褌褜</string>
+    <string name="controlcenter_find_device">袧邪泄褌懈 褍褋褌褉芯泄褋褌胁芯</string>
+    <string name="controlcenter_take_screenshot">小写械谢邪褌褜 褋薪懈屑芯泻 褝泻褉邪薪邪</string>
+    <string name="controlcenter_disconnect">袨褌泻谢褞褔懈褌褜</string>
+    <string name="controlcenter_delete_device">校写邪谢懈褌褜 褍褋褌褉芯泄褋褌胁芯</string>
+    <string name="controlcenter_delete_device_name">校写邪谢懈褌褜 %1$s</string>
+    <string name="controlcenter_delete_device_dialogmessage">校褋褌褉芯泄褋褌胁芯 懈 胁褋褟 褋胁褟蟹邪薪薪邪褟 褋 薪懈屑 懈薪褎芯褉屑邪褑懈褟 斜褍写褍褌 褍写邪谢械薪褘!</string>
+    <string name="controlcenter_navigation_drawer_open">袨褌泻褉褘褌褜 锌邪薪械谢褜 薪邪胁懈谐邪褑懈懈</string>
+    <string name="controlcenter_navigation_drawer_close">袟邪泻褉褘褌褜 锌邪薪械谢褜 薪邪胁懈谐邪褑懈懈</string>
+    <string name="controlcenter_snackbar_need_longpress">袛芯谢谐芯械 薪邪卸邪褌懈械 芯褌泻谢褞褔懈褌 褍褋褌褉芯泄褋褌胁芯</string>
+    <string name="controlcenter_snackbar_disconnecting">袪邪蟹褗械写懈薪械薪懈械</string>
+    <string name="controlcenter_snackbar_connecting">小芯械写懈薪械薪懈械鈥�</string>
+    <string name="controlcenter_snackbar_requested_screenshot">袛械谢邪械褌褋褟 褋薪懈屑芯泻 褍褋褌褉芯泄褋褌胁邪</string>
+    <string name="title_activity_debug">袨褌谢邪写泻邪</string>
+    <!--Strings related to AppManager-->
+    <string name="title_activity_appmanager">校锌褉邪胁谢械薪懈械 锌褉懈谢芯卸械薪懈械屑</string>
+    <string name="appmanager_cached_watchapps_watchfaces">袩褉懈谢芯卸械薪懈褟 胁 泻械褕械</string>
+    <string name="appmanager_installed_watchapps">校褋褌邪薪芯胁谢械薪薪褘械 锌褉懈谢芯卸械薪懈褟</string>
+    <string name="appmanager_installed_watchfaces">校褋褌邪薪芯胁谢械薪薪褘械 褑懈褎械褉斜谢邪褌褘</string>
+    <string name="appmananger_app_delete">校写邪谢懈褌褜</string>
+    <string name="appmananger_app_delete_cache">校写邪谢懈褌褜 懈 芯褔懈褋褌懈褌褜 泻械褕</string>
+    <string name="appmananger_app_reinstall">袩械褉械褍褋褌邪薪芯胁懈褌褜</string>
+    <string name="appmanager_app_openinstore">袠褋泻邪褌褜 胁 屑邪谐邪蟹懈薪械 Pebble</string>
+    <string name="appmanager_health_activate">袗泻褌懈胁懈褉芯胁邪褌褜</string>
+    <string name="appmanager_health_deactivate">袛械邪泻褌懈胁懈褉芯胁邪褌褜</string>
+    <string name="appmanager_hrm_activate">袙泻谢褞褔懈褌褜 锌褍谢褜褋芯屑械褌褉</string>
+    <string name="appmanager_hrm_deactivate">袙褘泻谢褞褔懈褌褜 锌褍谢褜褋芯屑械褌褉</string>
+    <string name="appmanager_weather_activate">袙泻谢褞褔懈褌褜 褋懈褋褌械屑薪褘泄 锌褉芯谐薪芯蟹 锌芯谐芯写褘</string>
+    <string name="appmanager_weather_deactivate">袙褘泻谢褞褔懈褌褜 褋懈褋褌械屑薪褘泄 锌褉芯谐薪芯蟹 锌芯谐芯写褘</string>
+    <string name="appmanager_weather_install_provider">校褋褌邪薪芯胁懈褌褜 褍胁械写芯屑谢械薪懈褟 写谢褟 锌褉芯谐薪芯蟹邪 锌芯谐芯写褘</string>
+    <string name="app_configure">袧邪褋褌褉芯懈褌褜</string>
+    <string name="app_move_to_top">袩械褉械屑械褋褌懈褌褜 薪邪胁械褉褏</string>
+    <!--Strings related to CalBlacklist-->
+    <!--Strings related to FwAppInstaller-->
+    <string name="title_activity_fw_app_insaller">校褋褌邪薪芯胁泻邪 锌褉芯褕懈胁泻懈 懈 锌褉懈谢芯卸械薪懈泄</string>
+    <string name="fw_upgrade_notice">袙褘 褋芯斜懈褉邪械褌械褋褜 褍褋褌邪薪芯胁懈褌褜 锌褉芯褕懈胁泻褍 %s.</string>
+    <string name="fw_multi_upgrade_notice">袙褘 褋芯斜懈褉邪械褌械褋褜 褍褋褌邪薪芯胁懈褌褜 锌褉芯褕懈胁泻懈 %1$s 懈 %2$s 胁屑械褋褌芯 褌械泻褍褖械泄 薪邪 胁邪褕械屑 Mi Band.</string>
+    <string name="miband_firmware_known">协褌邪 锌褉芯褕懈胁泻邪 斜褘谢邪 锌褉芯胁械褉械薪邪 懈 芯薪邪 褋芯胁屑械褋褌懈屑邪 褋 Gadgetbridge.</string>
+    <string name="miband_firmware_unknown_warning">协褌邪 锌褉芯褕懈胁泻邪 薪械 锌褉芯褌械褋褌懈褉芯胁邪薪邪 懈 屑芯卸械褌 斜褘褌褜 薪械褋芯胁屑械褋褌懈屑邪 褋 Gadgetbridge.
+\n
+\n袧袝 袪袝袣袨袦袝袧袛校袝孝小携 褍褋褌邪薪邪胁谢懈胁邪褌褜 械褢!</string>
+    <string name="miband_firmware_suggest_whitelist">袝褋谢懈 胁褘 褍褋褌邪薪芯胁懈褌械 褝褌褍 锌褉芯褕懈胁泻褍 懈 褍斜械写懈褌械褋褜, 褔褌芯 胁褋褢 褉邪斜芯褌邪械褌 斜械蟹 褋斜芯械胁, 锌芯卸邪谢褍泄褋褌邪, 褋芯芯斜褖懈褌械 芯斜 褝褌芯屑 褉邪蟹褉邪斜芯褌褔懈泻邪屑 Gadgetbridge. 袨薪懈 锌芯屑械褌褟褌 褝褌褍 胁械褉褋懈褞 锌褉芯褕懈胁泻懈 (%s) 泻邪泻 褋芯胁屑械褋褌懈屑褍褞.</string>
+    <!--Strings related to Settings-->
+    <string name="title_activity_settings">袧邪褋褌褉芯泄泻懈</string>
+    <string name="pref_header_general">袨斜褖懈械 薪邪褋褌褉芯泄泻懈</string>
+    <string name="pref_title_general_autoconnectonbluetooth">袩芯写泻谢褞褔邪褌褜褋褟 泻 写芯斜邪胁谢械薪薪褘屑 褍褋褌褉芯泄褋褌胁邪屑 锌褉懈 胁泻谢褞褔械薪懈懈 Bluetooth</string>
+    <string name="pref_title_general_autostartonboot">袟邪锌褍褋泻邪褌褜 邪胁褌芯屑邪褌懈褔械褋泻懈</string>
+    <string name="pref_title_general_autoreconnect">袩械褉械锌芯写泻谢褞褔邪褌褜褋褟 邪胁褌芯屑邪褌懈褔械褋泻懈</string>
+    <string name="pref_title_audio_player">袩褉械写锌芯褔褌懈褌械谢褜薪褘泄 屑褍蟹褘泻邪谢褜薪褘泄 锌谢械械褉</string>
+    <string name="pref_default">袩芯 褍屑芯谢褔邪薪懈褞</string>
+    <string name="pref_header_datetime">袛邪褌邪 懈 胁褉械屑褟</string>
+    <string name="pref_title_datetime_syctimeonconnect">小懈薪褏褉芯薪懈蟹懈褉芯胁邪褌褜 胁褉械屑褟</string>
+    <string name="pref_summary_datetime_syctimeonconnect">小懈薪褏褉芯薪懈蟹懈褉芯胁邪褌褜 胁褉械屑褟 薪邪 写芯斜邪胁谢械薪薪褘褏 褍褋褌褉芯泄褋褌胁邪褏 锌褉懈 锌芯写泻谢褞褔械薪懈懈, 褍褋褌邪薪芯胁泻械 胁褉械屑械薪懈 懈谢懈 褔邪褋芯胁芯谐芯 锌芯褟褋邪 懈 锌械褉懈芯写懈褔械褋泻懈</string>
+    <string name="pref_title_theme">孝械屑邪</string>
+    <string name="pref_theme_light">小胁械褌谢邪褟</string>
+    <string name="pref_theme_dark">孝褢屑薪邪褟</string>
+    <string name="pref_title_language">携蟹褘泻</string>
+    <string name="pref_title_minimize_priority">小泻褉褘胁邪褌褜 褍胁械写芯屑谢械薪懈械 Gadgetbridge</string>
+    <string name="pref_summary_minimize_priority_off">袩芯泻邪蟹褘胁邪械褌 蟹薪邪褔芯泻 胁 褋褌褉芯泻械 褋芯褋褌芯褟薪懈褟 懈 褍胁械写芯屑谢械薪懈械 薪邪 褝泻褉邪薪械 斜谢芯泻懈褉芯胁泻懈</string>
+    <string name="pref_summary_minimize_priority_on">袧械 锌芯泻邪蟹褘胁邪械褌 蟹薪邪褔芯泻 胁 褋褌褉芯泻械 褋芯褋褌芯褟薪懈褟 懈 褍胁械写芯屑谢械薪懈械 薪邪 褝泻褉邪薪械 斜谢芯泻懈褉芯胁泻懈</string>
+    <string name="pref_header_notifications">校胁械写芯屑谢械薪懈褟</string>
+    <string name="pref_title_notifications_repetitions">袩芯胁褌芯褉褘</string>
+    <string name="pref_title_notifications_call">孝械谢械褎芯薪薪褘械 蟹胁芯薪泻懈</string>
+    <string name="pref_title_notifications_sms">SMS-褋芯芯斜褖械薪懈褟</string>
+    <string name="pref_title_notifications_pebblemsg">小芯芯斜褖械薪懈褟 Pebble</string>
+    <string name="pref_summary_notifications_pebblemsg">袛谢褟 锌褉懈谢芯卸械薪懈泄, 泻芯褌芯褉褘械 芯褌锌褉邪胁谢褟褞褌 褍胁械写芯屑谢械薪懈褟 薪邪 Pebble 褋 锌芯屑芯褖褜褞 PebbleKit.</string>
+    <string name="pref_title_notifications_generic">袛芯褋褌褍锌 泻 褍胁械写芯屑谢械薪懈褟屑</string>
+    <string name="pref_title_whenscreenon">...褌邪泻卸械 锌褉懈 胁泻谢褞褔械薪薪芯屑 褝泻褉邪薪械</string>
+    <string name="pref_title_notification_filter">袧械 斜械褋锌芯泻芯懈褌褜</string>
+    <string name="pref_summary_notification_filter">袘谢芯泻懈褉芯胁邪褌褜 胁褋械 褍胁械写芯屑谢械薪懈褟, 泻芯谐写邪 褌械谢械褎芯薪 胁 褉械卸懈屑械 \"袧械 斜械褋锌芯泻芯懈褌褜\"</string>
+    <string name="pref_title_transliteration">孝褉邪薪褋谢懈褌械褉邪褑懈褟</string>
+    <string name="always">袙褋械谐写邪</string>
+    <string name="pref_summary_transliteration">袙泻谢褞褔懈褌械, 械褋谢懈 薪邪 褍褋褌褉芯泄褋褌胁械 薪械褌 褕褉懈褎褌邪 写谢褟 胁邪褕械谐芯 褟蟹褘泻邪</string>
+    <string name="when_screen_off">袣芯谐写邪 褝泻褉邪薪 胁褘泻谢褞褔械薪</string>
+    <string name="never">袧懈泻芯谐写邪</string>
+    <string name="pref_header_privacy">袣芯薪褎懈写械薪褑懈邪谢褜薪芯褋褌褜</string>
+    <string name="pref_title_call_privacy_mode">袣芯薪褎懈写械薪褑懈邪谢褜薪芯褋褌褜 胁褘蟹芯胁芯胁</string>
+    <string name="pref_call_privacy_mode_off">袩芯泻邪蟹褘胁邪褌褜 懈屑褟 懈 薪芯屑械褉</string>
+    <string name="pref_call_privacy_mode_name">小泻褉褘胁邪褌褜 懈屑褟, 薪芯 锌芯泻邪蟹褘胁邪褌褜 薪芯屑械褉</string>
+    <string name="pref_call_privacy_mode_number">小泻褉褘胁邪褌褜 薪芯屑械褉, 薪芯 锌芯泻邪蟹褘胁邪褌褜 懈屑褟</string>
+    <string name="pref_call_privacy_mode_complete">小泻褉褘胁邪褌褜 懈屑褟 懈 薪芯屑械褉</string>
+    <string name="pref_header_cannned_messages">小芯褏褉邪薪褢薪薪褘械 褋芯芯斜褖械薪懈褟</string>
+    <string name="pref_title_canned_replies">袨褌胁械褌褘</string>
+    <string name="pref_title_canned_reply_suffix">袨斜褖懈泄 褋褍褎褎懈泻褋</string>
+    <string name="pref_title_canned_messages_dismisscall">袨褌泻谢芯薪褢薪薪褘械 蟹胁芯薪泻懈</string>
+    <string name="pref_title_canned_messages_set">袨斜薪芯胁懈褌褜 薪邪 褍褋褌褉芯泄褋褌胁械</string>
+    <string name="pref_header_development">袧邪褋褌褉芯泄泻懈 写谢褟 褉邪蟹褉邪斜芯褌褔懈泻芯胁</string>
+    <string name="pref_title_development_miaddr">袗写褉械褋 Mi Band</string>
+    <string name="pref_title_pebble_settings">袧邪褋褌褉芯泄泻懈 Pebble</string>
+    <string name="pref_header_activitytrackers">袨褌褋谢械卸懈胁邪薪懈械 邪泻褌懈胁薪芯褋褌懈</string>
+    <string name="pref_title_pebble_activitytracker">袩褉械写锌芯褔懈褌邪械屑褘泄 褌褉械泻械褉 邪泻褌懈胁薪芯褋褌懈</string>
+    <string name="pref_title_pebble_sync_health">小懈薪褏褉芯薪懈蟹邪褑懈褟 褋 Pebble Health</string>
+    <string name="pref_title_pebble_sync_misfit">小懈薪褏褉芯薪懈蟹邪褑懈褟 褋 Misfit</string>
+    <string name="pref_title_pebble_sync_morpheuz">小懈薪褏褉芯薪懈蟹邪褑懈褟 褋 Morpheuz</string>
+    <string name="pref_title_enable_outgoing_call">袩芯写写械褉卸泻邪 懈褋褏芯写褟褖懈褏 胁褘蟹芯胁芯胁</string>
+    <string name="pref_summary_enable_outgoing_call">袩褉懈 芯褌泻谢褞褔械薪懈懈 写邪薪薪芯谐芯 锌褍薪泻褌邪, Pebble 2/LE 褌邪泻卸械 薪械 斜褍写械褌 胁懈斜褉懈褉芯胁邪褌褜 锌褉懈 懈褋褏芯写褟褖懈褏 胁褘蟹芯胁邪褏</string>
+    <string name="pref_title_enable_pebblekit">袪邪蟹褉械褕懈褌褜 写芯褋褌褍锌 锌芯褋褌芯褉芯薪薪懈屑 锌褉懈谢芯卸械薪懈褟屑</string>
+    <string name="pref_summary_enable_pebblekit">袛芯斜邪胁懈褌褜 褝泻褋锌械褉懈屑械薪褌邪谢褜薪褍褞 锌芯写写械褉卸泻褍 锌褉懈谢芯卸械薪懈泄, 懈褋锌芯谢褜蟹褍褞褖懈褏 PebbleKit</string>
+    <string name="pref_title_sunrise_sunset">袙芯褋褏芯写 懈 蟹邪泻邪褌 褋芯谢薪褑邪</string>
+    <string name="pref_summary_sunrise_sunset">袩芯泻邪蟹褘胁邪褌褜 胁褉械屑褟 胁芯褋褏芯写邪 懈 蟹邪褏芯写邪 褋芯谢薪褑邪 胁 蟹邪胁懈褋懈屑芯褋褌懈 芯褌 屑械褋褌芯锌芯谢芯卸械薪懈褟 薪邪 胁褉械屑械薪薪芯泄 褕泻邪谢械 Pebble</string>
+    <string name="pref_title_enable_calendar_sync">小懈薪褏褉芯薪懈蟹邪褑懈褟 泻邪谢械薪写邪褉褟</string>
+    <string name="pref_summary_enable_calendar_sync">袨褌芯斜褉邪卸邪褌褜 褋芯斜褘褌懈褟 泻邪谢械薪写邪褉褟 薪邪 胁褉械屑械薪薪芯泄 褕泻邪谢械 pebble</string>
+    <string name="pref_title_autoremove_notifications">袗胁褌芯屑邪褌懈褔械褋泻芯械 褍写邪谢械薪懈械 芯褌泻谢芯薪褢薪薪褘褏 褍胁械写芯屑谢械薪懈泄</string>
+    <string name="pref_summary_autoremove_notifications">校胁械写芯屑谢械薪懈褟 邪胁褌芯屑邪褌懈褔械褋泻懈 褍写邪谢褟褞褌褋褟 褋 褍褋褌褉芯泄褋褌胁邪 锌褉懈 蟹邪泻褉褘褌懈懈 褋 褌械谢械褎芯薪邪</string>
+    <string name="pref_title_pebble_privacy_mode">袪械卸懈屑 泻芯薪褎懈写械薪褑懈邪谢褜薪芯褋褌懈</string>
+    <string name="pref_pebble_privacy_mode_off">袨斜褘褔薪褘械 褍胁械写芯屑谢械薪懈褟</string>
+    <string name="pref_pebble_privacy_mode_content">袩械褉械薪芯褋 褌械泻褋褌邪 褍胁械写芯屑谢械薪懈褟, 胁褘褏芯写褟褖懈屑 蟹邪 谐褉邪薪懈褑褘 褝泻褉邪薪邪</string>
+    <string name="pref_pebble_privacy_mode_complete">袩芯泻邪蟹邪褌褜 褌芯谢褜泻芯 蟹薪邪褔芯泻 褍胁械写芯屑谢械薪懈褟</string>
+    <string name="pref_header_location">袦械褋褌芯锌芯谢芯卸械薪懈械</string>
+    <string name="pref_title_location_aquire">袩芯谢褍褔懈褌褜 写邪薪薪褘械 芯 屑械褋褌芯锌芯谢芯卸械薪懈懈</string>
+    <string name="pref_title_location_latitude">楔懈褉芯褌邪</string>
+    <string name="pref_title_location_longitude">袛芯谢谐芯褌邪</string>
+    <string name="pref_title_location_keep_uptodate">袨斜薪芯胁谢褟褌褜 屑械褋褌芯锌芯谢芯卸械薪懈械</string>
+    <string name="pref_summary_location_keep_uptodate">袩芯锌褉芯斜褍泄褌械 锌芯谢褍褔懈褌褜 褌械泻褍褖械械 屑械褋褌芯锌芯谢芯卸械薪懈械 胁芯 胁褉械屑褟 胁褘锌芯谢薪械薪懈褟, 懈褋锌芯谢褜蟹褍泄褌械 褋芯褏褉邪薪械薪薪芯械 屑械褋褌芯锌芯谢芯卸械薪懈械 胁 泻邪褔械褋褌胁械 褉械蟹械褉胁薪芯谐芯</string>
+    <string name="toast_enable_networklocationprovider">袙泻谢褞褔懈褌械 屑械褋褌芯锌芯谢芯卸械薪懈械 锌芯 褋械褌懈</string>
+    <string name="toast_aqurired_networklocation">袦械褋褌芯褉邪褋锌芯谢芯卸械薪懈械 芯锌褉械写械谢械薪芯</string>
+    <string name="pref_title_pebble_forceprotocol">袩褉懈薪褍写懈褌械谢褜薪褘泄 锌褉芯褌芯泻芯谢 褍胁械写芯屑谢械薪懈泄</string>
+    <string name="pref_summary_pebble_forceprotocol">协褌邪 薪邪褋褌褉芯泄泻邪 锌褉懈薪褍写懈褌械谢褜薪芯 懈褋锌芯谢褜蟹褍械褌 褋邪屑褘泄 薪芯胁褘泄 锌褉芯褌芯泻芯谢 褍胁械写芯屑谢械薪懈泄 (蟹邪胁懈褋懈褌 芯褌 胁械褉褋懈懈 锌褉芯褕懈胁泻懈). 袙袣袥挟效袗袡孝袝, 袝小袥袠 孝袨效袧袨 袟袧袗袝孝袝, 袟袗效袝袦 袙蝎 协孝袨 袛袝袥袗袝孝袝!</string>
+    <string name="pref_title_pebble_forceuntested">袙泻谢褞褔懈褌褜 薪械锌褉芯胁械褉械薪薪褘械 褎褍薪泻褑懈懈</string>
+    <string name="pref_summary_pebble_forceuntested">袙泻谢褞褔懈褌褜 褎褍薪泻褑懈懈, 薪械 锌褉芯褏芯写懈胁褕懈械 锌褉芯胁械褉泻褍. 袙袣袥挟效袗袡孝袝 袧袗 小袙袨袡 小孝袪袗啸 袠 袪袠小袣!</string>
+    <string name="pref_title_pebble_forcele">袙褋械谐写邪 芯褌写邪胁邪褌褜 锌褉械写锌芯褔褌械薪懈械 BLE</string>
+    <string name="pref_summary_pebble_forcele">袠褋锌芯谢褜蟹芯胁邪褌褜 褝泻褋锌械褉懈屑械薪褌邪谢褜薪褍褞 锌芯写写械褉卸泻褍 Pebble LE 写谢褟 胁褋械褏 Pebble 胁屑械褋褌芯 BT classic, 褌褉械斜褍械褌 褋芯锌褉褟卸械薪懈褟 芦Pebble LE禄 锌芯褋谢械 褌芯谐芯, 泻邪泻 斜褘谢 锌芯写泻谢褞褔械薪 薪械 LE</string>
+    <string name="pref_title_pebble_mtu_limit">袨谐褉邪薪懈褔械薪懈械 GATT MTU 写谢褟 Pebble 2/LE</string>
+    <string name="pref_summary_pebble_mtu_limit">袝褋谢懈 胁邪褕 Pebble 2/Pebble LE 褉邪斜芯褌邪械褌 薪械泻芯褉褉械泻褌薪芯, 锌芯锌褉芯斜褍泄褌械 锌褉懈 锌芯屑芯褖懈 写邪薪薪芯泄 薪邪褋褌褉芯泄泻懈 芯谐褉邪薪懈褔懈褌褜 MTU (写芯锌褍褋褌懈屑褘泄 写懈邪锌邪蟹芯薪 芯褌 20 写芯 512)</string>
+    <string name="pref_title_pebble_enable_applogs">袙泻谢褞褔懈褌褜 胁械写械薪懈械 谢芯谐芯胁 锌褉懈谢芯卸械薪懈泄</string>
+    <string name="pref_summary_pebble_enable_applogs">袥芯谐懈 锌褉懈谢芯卸械薪懈泄 斜褍写褍褌 胁械褋褌懈褋褜 胁 Gadgetbridge (褌褉械斜褍械褌褋褟 锌械褉械锌芯写泻谢褞褔械薪懈械)</string>
+    <string name="pref_title_pebble_always_ack_pebblekit">袩褉械卸写械胁褉械屑械薪薪芯械 ACK PebbleKit</string>
+    <string name="pref_summary_pebble_always_ack_pebblekit">小芯芯斜褖械薪懈褟, 芯褌锌褉邪胁谢褟械屑褘械 胁薪械褕薪懈屑 褋褌芯褉芯薪薪懈屑 锌褉懈谢芯卸械薪懈褟屑, 斜褍写褍褌 胁褋械谐写邪 懈 薪械屑械写谢械薪薪芯 褉邪蟹褉械褕械薪褘</string>
+    <string name="pref_title_pebble_reconnect_attempts">袩芯锌褘褌泻懈 锌械褉械锌芯写泻谢褞褔械薪懈褟</string>
+    <string name="pref_title_unit_system">袝写懈薪懈褑褘</string>
+    <string name="pref_title_timeformat">肖芯褉屑邪褌 胁褉械屑械薪懈</string>
+    <string name="pref_title_screentime">袩褉芯写芯谢卸懈褌械谢褜薪芯褋褌褜 褉邪斜芯褌褘 褝泻褉邪薪邪</string>
+    <string name="prefs_title_all_day_heart_rate">袟邪屑械褉 褋械褉写械褔薪芯谐芯 褉懈褌屑邪 薪邪 锌褉芯褌褟卸械薪懈懈 胁褋械谐芯 写薪褟</string>
+    <string name="preferences_hplus_settings">袧邪褋褌褉芯泄泻懈 HPlus/Makibes</string>
+    <string name="not_connected">袧械褌 褋芯械写懈薪械薪懈褟</string>
+    <string name="connecting">小芯械写懈薪械薪懈械</string>
+    <string name="connected">小芯械写懈薪械薪芯</string>
+    <string name="unknown_state">袧械懈蟹胁械褋褌薪芯械 褋芯褋褌芯褟薪懈械</string>
+    <string name="_unknown_">(薪械懈蟹胁械褋褌薪芯)</string>
+    <string name="test">孝械褋褌</string>
+    <string name="test_notification">孝械褋褌芯胁芯械 褍胁械写芯屑谢械薪懈械</string>
+    <string name="this_is_a_test_notification_from_gadgetbridge">协褌芯 褌械褋褌芯胁芯械 褍胁械写芯屑谢械薪懈械 芯褌 Gadgetbridge</string>
+    <string name="bluetooth_is_not_supported_">Bluetooth 薪械 锌芯写写械褉卸懈胁邪械褌褋褟.</string>
+    <string name="bluetooth_is_disabled_">Bluetooth 芯褌泻谢褞褔褢薪.</string>
+    <string name="tap_connected_device_for_app_mananger">袣芯褋薪懈褌械褋褜 锌芯写泻谢褞褔褢薪薪芯谐芯 褍褋褌褉芯泄褋褌胁邪 写谢褟 胁褘蟹芯胁邪 校锌褉邪胁谢械薪懈褟 锌褉懈谢芯卸械薪懈褟屑懈</string>
+    <string name="tap_connected_device_for_activity">袣芯褋薪懈褌械褋褜 锌芯写泻谢褞褔褢薪薪芯谐芯 褍褋褌褉芯泄褋褌胁邪 写谢褟 锌芯泻邪蟹邪 袗泻褌懈胁薪芯褋褌械泄</string>
+    <string name="tap_connected_device_for_vibration">袣芯褋薪懈褌械褋褜 锌芯写泻谢褞褔褢薪薪芯谐芯 褍褋褌褉芯泄褋褌胁邪 写谢褟 袙懈斜褉邪褑懈懈</string>
+    <string name="tap_a_device_to_connect">袣芯褋薪懈褌械褋褜 褍褋褌褉芯泄褋褌胁邪 写谢褟 褋芯械写懈薪械薪懈褟</string>
+    <string name="cannot_connect_bt_address_invalid_">袧械 褍写邪谢芯褋褜 褋芯械写懈薪懈褌褜褋褟. 袧械胁械褉薪褘泄 邪写褉械褋 Bluetooth\?</string>
+    <string name="gadgetbridge_running_generic">Gadgetbridge 蟹邪锌褍褖械薪</string>
+    <string name="installing_binary_d_d">褍褋褌邪薪芯胁泻邪 斜懈薪邪褉薪芯谐芯 褎邪泄谢邪 %1$d/%2$d</string>
+    <string name="installation_failed_">校褋褌邪薪芯胁泻邪 薪械 褍写邪谢邪褋褜</string>
+    <string name="installation_successful">校褋褌邪薪芯胁谢械薪芯</string>
+    <string name="firmware_install_warning">袙蝎 袩蝎孝袗袝孝袝小鞋 校小孝袗袧袨袙袠孝鞋 袩袪袨楔袠袙袣校, 袩袪袨袛袨袥袞袗袡孝袝 袧袗 小袙袨袡 小孝袪袗啸 袠 袪袠小袣.\n\n\n 协褌邪 锌褉芯褕懈胁泻邪 写谢褟 褉械胁懈蟹懈懈 褍褋褌褉芯泄褋褌胁邪: %s</string>
+    <string name="app_install_info">袙褘 褋芯斜懈褉邪械褌械褋褜 褍褋褌邪薪芯胁懈褌褜 锌褉懈谢芯卸械薪懈械:
+\n
+\n%1$s
+\n袙械褉褋懈褟 %2$s 芯褌 %3$s
+\n</string>
+    <string name="n_a">袧械写芯褋褌褍锌薪芯</string>
+    <string name="initialized">袠薪懈褑懈邪谢懈蟹懈褉芯胁邪薪芯</string>
+    <string name="appversion_by_creator">%1$s 芯褌 %2$s</string>
+    <string name="title_activity_discovery">袩芯懈褋泻 褍褋褌褉芯泄褋褌胁邪</string>
+    <string name="discovery_stop_scanning">袨褋褌邪薪芯胁懈褌褜 锌芯懈褋泻 褍褋褌褉芯泄褋褌胁</string>
+    <string name="discovery_start_scanning">校屑薪褘泄 锌芯懈褋泻</string>
+    <string name="action_discover">袩芯写泻谢褞褔懈褌褜褋褟 泻 薪芯胁芯屑褍 褍褋褌褉芯泄褋褌胁褍</string>
+    <string name="device_with_rssi">%1$s (%2$s)</string>
+    <string name="title_activity_android_pairing">小芯锌褉褟卸械薪懈械 褍褋褌褉芯泄褋褌胁</string>
+    <string name="android_pairing_hint">袛谢褟 褋芯锌褉褟卸械薪懈褟 褍褋褌褉芯泄褋褌胁 懈褋锌芯谢褜蟹褍泄褌械 写懈邪谢芯谐 Android.</string>
+    <string name="title_activity_mi_band_pairing">小芯锌褉褟卸械薪懈械 胁邪褕械谐芯 Mi Band</string>
+    <string name="pairing">小芯锌褉褟卸械薪懈械 褋 %s鈥�</string>
+    <string name="pairing_creating_bond_with">袩褉懈胁褟蟹褘胁邪褞褋褜 泻 %1$s (%2$s)</string>
+    <string name="pairing_unable_to_pair_with">袧械胁芯蟹屑芯卸薪芯 胁褘锌芯谢薪懈褌褜 褋芯锌褉褟卸械薪懈械 褋 %1$s (%2$s)</string>
+    <string name="pairing_in_progress">袙褘锌芯谢薪褟械褌褋褟 锌褉懈胁褟蟹泻邪: %1$s (%2$s)</string>
+    <string name="pairing_already_bonded">校卸械 锌褉懈胁褟蟹邪薪 泻 %1$s (%2$s), 胁褘锌芯谢薪褟械褌褋褟 褋芯械写懈薪械薪懈械鈥�</string>
+    <string name="message_cannot_pair_no_mac">MAC-邪写褉械褋邪 薪械褌, 褋芯锌褉褟卸械薪懈械 薪械 褍写邪谢芯褋褜.</string>
+    <string name="preferences_category_device_specific_settings">袧邪褋褌褉芯泄泻懈 褍褋褌褉芯泄褋褌胁邪</string>
+    <string name="preferences_miband_settings">袧邪褋褌褉芯泄泻懈 Mi Band 懈 Amazfit</string>
+    <string name="male">袦褍卸褔懈薪邪</string>
+    <string name="female">袞械薪褖懈薪邪</string>
+    <string name="other">袛褉褍谐芯泄</string>
+    <string name="left">褋谢械胁邪</string>
+    <string name="right">褋锌褉邪胁邪</string>
+    <string name="miband_pairing_using_dummy_userdata">袧械 锌褉械写芯褋褌邪胁谢械薪芯 写械泄褋褌胁懈褌械谢褜薪褘褏 写邪薪薪褘褏 锌芯谢褜蟹芯胁邪褌械谢褟. 袠褋锌芯谢褜蟹褍褞褌褋褟 写邪薪薪褘械 锌芯-褍屑芯谢褔邪薪懈褞.</string>
+    <string name="miband_pairing_tap_hint">袣芯谐写邪 胁邪褕 Mi Band 胁懈斜褉懈褉褍械褌 懈 屑懈谐邪械褌, 锌芯褋褌褍褔懈褌械 锌芯 薪械屑褍 薪械褋泻芯谢褜泻芯 褉邪蟹.</string>
+    <string name="appinstaller_install">校褋褌邪薪芯胁懈褌褜</string>
+    <string name="discovery_connected_devices_hint">小写械谢邪泄褌械 褋胁芯械 褍褋褌褉芯泄褋褌胁芯 胁懈写懈屑褘屑. 袙 薪邪褋褌芯褟褖械械 胁褉械屑褟 锌芯写泻谢褞褔械薪薪褘械 褍褋褌褉芯泄褋褌胁邪, 褋泻芯褉械械 胁褋械谐芯, 薪械 斜褍写褍褌 芯斜薪邪褉褍卸械薪褘. 袗泻褌懈胁懈褉褍泄褌械 屑械褋褌芯锌芯谢芯卸械薪懈械 (薪邪锌褉懈屑械褉, GPS) 薪邪 Android 6+. 袨褌泻谢褞褔懈褌械 Privacy Guard 写谢褟 Gadgetbridge, 褌邪泻 泻邪泻 褝褌芯 屑芯卸械褌 锌褉懈胁械褋褌懈 泻 褋斜芯褞 懈 锌械褉械蟹邪谐褉褍蟹泻械 胁邪褕械谐芯 褌械谢械褎芯薪邪. 袝褋谢懈 褔械褉械蟹 薪械褋泻芯谢褜泻芯 屑懈薪褍褌 褍褋褌褉芯泄褋褌胁芯 薪械 芯斜薪邪褉褍卸械薪芯, 锌芯胁褌芯褉懈褌械 锌芯锌褘褌泻褍 锌芯褋谢械 锌械褉械蟹邪谐褉褍蟹泻懈 屑芯斜懈谢褜薪芯谐芯 褍褋褌褉芯泄褋褌胁邪.</string>
+    <string name="discovery_note">袟邪屑械褌泻邪:</string>
+    <string name="candidate_item_device_image">袠蟹芯斜褉邪卸械薪懈械 褍褋褌褉芯泄褋褌胁邪</string>
+    <string name="miband_prefs_alias">袠屑褟/锌褋械胁写芯薪懈屑</string>
+    <string name="pref_header_vibration_count">袣芯谢懈褔械褋褌胁芯 胁懈斜褉邪褑懈泄</string>
+    <string name="title_activity_sleepmonitor">袗薪邪谢懈蟹 褎邪蟹 褋薪邪</string>
+    <string name="pref_write_logfiles">小芯褏褉邪薪褟褌褜 褎邪泄谢褘 卸褍褉薪邪谢邪</string>
+    <string name="initializing">袟邪锌褍褋泻邪械褌褋褟</string>
+    <string name="busy_task_fetch_activity_data">袩芯谢褍褔械薪懈械 写邪薪薪褘褏 邪泻褌懈胁薪芯褋褌懈</string>
+    <string name="sleep_activity_date_range">袨褌 %1$s 写芯 %2$s</string>
+    <string name="prefs_wearside">袧芯褋懈褌械 薪邪 谢械胁芯泄 懈谢懈 锌褉邪胁芯泄 褉褍泻械?</string>
+    <string name="pref_screen_vibration_profile">袩褉芯褎懈谢褜 薪邪褋褌褉芯械泻 胁懈斜褉邪褑懈懈</string>
+    <string name="vibration_profile_staccato">小褌邪泻泻邪褌芯</string>
+    <string name="vibration_profile_short">袣芯褉芯褌泻懈泄</string>
+    <string name="vibration_profile_medium">小褉械写薪懈泄</string>
+    <string name="vibration_profile_long">袛谢懈薪薪褘泄</string>
+    <string name="vibration_profile_waterdrop">袣邪锌谢褟 胁芯写褘</string>
+    <string name="vibration_profile_ring">袟胁芯薪芯泻</string>
+    <string name="vibration_profile_alarm_clock">袘褍写懈谢褜薪懈泻</string>
+    <string name="miband_prefs_vibration">袙懈斜褉芯</string>
+    <string name="vibration_try">袩芯锌褉芯斜芯胁邪褌褜</string>
+    <string name="pref_screen_notification_profile_sms">SMS-褍胁械写芯屑谢械薪懈械</string>
+    <string name="pref_header_vibration_settings">袧邪褋褌褉芯泄泻懈 胁懈斜褉邪褑懈懈</string>
+    <string name="pref_screen_notification_profile_generic">袨斜褖懈械 褍胁械写芯屑谢械薪懈褟</string>
+    <string name="pref_screen_notification_profile_email">校胁械写芯屑谢械薪懈褟 褝谢械泻褌褉芯薪薪芯泄 锌芯褔褌褘</string>
+    <string name="pref_screen_notification_profile_incoming_call">校胁械写芯屑谢械薪懈褟 芯 胁褏芯写褟褖懈褏 蟹胁芯薪泻邪褏</string>
+    <string name="pref_screen_notification_profile_generic_chat">效邪褌</string>
+    <string name="pref_screen_notification_profile_generic_navigation">袧邪胁懈谐邪褑懈褟</string>
+    <string name="pref_screen_notification_profile_generic_social">小芯褑懈邪谢褜薪褘械 褋械褌懈</string>
+    <string name="control_center_find_lost_device">袩芯懈褋泻 锌芯褌械褉褟薪薪芯谐芯 褍褋褌褉芯泄褋褌胁邪</string>
+    <string name="control_center_cancel_to_stop_vibration">袨褌屑械薪懈褌械 写谢褟 锌褉械泻褉邪褖械薪懈褟 胁懈斜褉邪褑懈懈.</string>
+    <string name="title_activity_charts">袗泻褌懈胁薪芯褋褌褜 懈 褋芯薪</string>
+    <string name="title_activity_set_alarm">袧邪褋褌褉芯懈褌褜 斜褍写懈谢褜薪懈泻</string>
+    <string name="controlcenter_start_configure_alarms">袧邪褋褌褉芯懈褌褜 斜褍写懈谢褜薪懈泻懈</string>
+    <string name="title_activity_alarm_details">袧邪褋褌褉芯泄泻懈 斜褍写懈谢褜薪懈泻邪</string>
+    <string name="alarm_sun_short">袙褋</string>
+    <string name="alarm_mon_short">袩薪</string>
+    <string name="alarm_tue_short">袙褌</string>
+    <string name="alarm_wed_short">小褉</string>
+    <string name="alarm_thu_short">效褌</string>
+    <string name="alarm_fri_short">袩褌</string>
+    <string name="alarm_sat_short">小斜</string>
+    <string name="alarm_smart_wakeup">校屑薪芯械 锌褉芯斜褍卸写械薪懈械</string>
+    <string name="user_feedback_miband_set_alarms_failed">袩褉懈 薪邪褋褌褉芯泄泻械 斜褍写懈谢褜薪懈泻邪 锌褉芯懈蟹芯褕谢邪 芯褕懈斜泻邪, 锌芯锌褉芯斜褍泄褌械 械褖褢 褉邪蟹.</string>
+    <string name="user_feedback_miband_set_alarms_ok">袘褍写懈谢褜薪懈泻懈 芯褌锌褉邪胁谢械薪褘 薪邪 褍褋褌褉芯泄褋褌胁芯.</string>
+    <string name="chart_no_data_synchronize">袧械褌 写邪薪薪褘褏. 小懈薪褏褉芯薪懈蟹懈褉芯胁邪褌褜 褍褋褌褉芯泄褋褌胁芯?</string>
+    <string name="user_feedback_miband_activity_data_transfer">袘褍写械褌 锌械褉械写邪薪芯 %1$s 写邪薪薪褘褏, 薪邪褔懈薪邪褟 褋 %2$s</string>
+    <string name="miband_prefs_fitness_goal">袝卸械写薪械胁薪邪褟 褑械谢褜 褕邪谐芯胁</string>
+    <string name="dbaccess_error_executing">袩褉芯懈蟹芯褕谢邪 芯褕懈斜泻邪 锌褉懈 胁褘锌芯谢薪械薪懈懈 \'%1$s\'</string>
+    <string name="controlcenter_start_activitymonitor">袙邪褕邪 邪泻褌懈胁薪芯褋褌褜</string>
+    <string name="cannot_connect">袩芯写泻谢褞褔懈褌褜褋褟 薪械 褍写邪谢芯褋褜: %1$s</string>
+    <string name="installer_activity_unable_to_find_handler">袧械 褍写邪谢芯褋褜 薪邪泄褌懈 芯斜褉邪斜芯褌褔懈泻 写谢褟 褍褋褌邪薪芯胁泻懈 褝褌芯谐芯 褎邪泄谢邪.</string>
+    <string name="pbw_install_handler_unable_to_install">袧械 褍写邪谢芯褋褜 褍褋褌邪薪芯胁懈褌褜 写邪薪薪褘泄 褎邪泄谢: %1$s</string>
+    <string name="pbw_install_handler_hw_revision_mismatch">袧械 褍写邪谢芯褋褜 褍褋褌邪薪芯胁懈褌褜 锌褉芯褕懈胁泻褍: 芯薪邪 薪械 褋芯胁锌邪写邪械褌 褋 胁械褉褋懈械泄 胁邪褕械谐芯 褍褋褌褉芯泄褋褌胁邪 Pebble.</string>
+    <string name="installer_activity_wait_while_determining_status">袩芯卸邪谢褍泄褋褌邪 锌芯写芯卸写懈褌械, 锌褉芯胁械褉褟械褌褋褟 褋褌邪褌褍褋 褍褋褌邪薪芯胁泻懈鈥�</string>
+    <string name="notif_battery_low_title">袧懈蟹泻懈泄 蟹邪褉褟写 褍褋褌褉芯泄褋褌胁邪!</string>
+    <string name="notif_battery_low_percent">%1$s 芯褋褌邪谢芯褋褜 蟹邪褉褟写邪: %2$s%%</string>
+    <string name="notif_battery_low_bigtext_last_charge_time">袩芯褋谢械写薪懈泄 褉邪蟹 褍褋褌褉芯泄褋褌胁芯 蟹邪褉褟卸邪谢芯褋褜: %s \n</string>
+    <string name="notif_battery_low_bigtext_number_of_charges">袣芯谢懈褔械褋褌胁芯 褑懈泻谢芯胁 锌械褉械蟹邪褉褟写泻懈: %s</string>
+    <string name="sleepchart_your_sleep">小芯薪</string>
+    <string name="weeksleepchart_sleep_a_week">小芯薪 蟹邪 薪械写械谢褞</string>
+    <string name="weeksleepchart_today_sleep_description">小芯薪 褋械谐芯写薪褟, 褑械谢褜: %1$s</string>
+    <string name="weekstepschart_steps_a_week">楔邪谐芯胁 胁 薪械写械谢褞</string>
+    <string name="activity_sleepchart_activity_and_sleep">袗泻褌懈胁薪芯褋褌褜</string>
+    <string name="lack_of_sleep">袧械写芯褋褘锌: %1$s</string>
+    <string name="overslept">袩械褉械褋褘锌: %1$s</string>
+    <string name="updating_firmware">袨斜薪芯胁谢械薪懈械 锌褉芯褕懈胁泻懈鈥�</string>
+    <string name="fwapp_install_device_not_ready">肖邪泄谢 薪械 屑芯卸械褌 斜褘褌褜 褍褋褌邪薪芯胁谢械薪, 褍褋褌褉芯泄褋褌胁芯 薪械 谐芯褌芯胁芯.</string>
+    <string name="installhandler_firmware_name">%1$s: %2$s %3$s</string>
+    <string name="miband_fwinstaller_compatible_version">小芯胁屑械褋褌懈屑邪褟 胁械褉褋懈褟</string>
+    <string name="miband_fwinstaller_untested_version">袧械 锌褉芯褌械褋褌懈褉芯胁邪薪薪邪褟 胁械褉褋懈褟!</string>
+    <string name="fwappinstaller_connection_state">袩芯写泻谢褞褔械薪懈械 泻 褍褋褌褉芯泄褋褌胁褍: %1$s</string>
+    <string name="pbw_installhandler_pebble_firmware">袙械褉褋懈褟 锌褉芯褕懈胁泻懈 Pebble: %1$s</string>
+    <string name="pbwinstallhandler_correct_hw_revision">袣芯褉褉械泻褌薪邪褟 褉械胁懈蟹懈褟 褍褋褌褉芯泄褋褌胁邪</string>
+    <string name="pbwinstallhandler_incorrect_hw_revision">袪械胁懈蟹懈褟 褍褋褌褉芯泄褋褌胁邪 薪械 褋芯胁锌邪写邪械褌!</string>
+    <string name="pbwinstallhandler_app_item">%1$s (%2$s)</string>
+    <string name="updatefirmwareoperation_updateproblem_do_not_reboot">袩褉芯斜谢械屑邪 褋 锌械褉械写邪褔械泄 锌褉芯褕懈胁泻懈. 袧袝 袩袝袪袝袟袗袚袪校袞袗袡孝袝 胁邪褕 Mi Band!</string>
+    <string name="updatefirmwareoperation_metadata_updateproblem">袩褉芯斜谢械屑邪 褋 锌械褉械写邪褔械泄 屑械褌邪写邪薪薪褘褏 锌褉芯褕懈胁泻懈</string>
+    <string name="updatefirmwareoperation_update_complete">校褋褌邪薪芯胁泻邪 锌褉芯褕懈胁泻懈 蟹邪胁械褉褕械薪邪</string>
+    <string name="updatefirmwareoperation_update_complete_rebooting">校褋褌邪薪芯胁泻邪 锌褉芯褕懈胁泻懈 蟹邪胁械褉褕械薪邪, 褍褋褌褉芯泄褋褌胁芯 锌械褉械蟹邪谐褉褍卸邪械褌褋褟鈥�</string>
+    <string name="updatefirmwareoperation_write_failed">袟邪锌懈褋褜 锌褉芯褕懈胁泻懈 蟹邪胁械褉褕懈谢邪褋褜 薪械褍写邪褔械泄</string>
+    <string name="chart_steps">楔邪谐懈</string>
+    <string name="calories">袣邪谢芯褉懈懈</string>
+    <string name="distance">袪邪褋褋褌芯褟薪懈械</string>
+    <string name="liveactivity_live_activity">孝械泻褍褖邪褟 邪泻褌懈胁薪芯褋褌褜</string>
+    <string name="weeksteps_today_steps_description">楔邪谐芯胁 褋械谐芯写薪褟, 褑械谢褜: %1$s</string>
+    <string name="lack_of_step">袧械 褏胁邪褌邪械褌: %1$d 褕邪谐芯胁</string>
+    <string name="overstep">袩褉芯泄写械薪芯 薪邪 %1$d 褕邪谐芯胁 斜芯谢褜褕械</string>
+    <string name="pref_title_dont_ack_transfer">袧械 锌芯写褌胁械褉卸写邪褌褜 锌械褉械写邪褔褍 写邪薪薪褘褏 芯斜 邪泻褌懈胁薪芯褋褌懈</string>
+    <string name="pref_summary_dont_ack_transfers">袝褋谢懈 写邪薪薪褘械 芯斜 邪泻褌懈胁薪芯褋褌懈 薪械 斜褍写褍褌 锌械褉械写邪薪褘 薪邪 褍褋褌褉芯泄褋褌胁芯, 芯薪芯 薪械 斜褍写械褌 芯褔懈褖械薪芯. 袩芯谢械蟹薪芯, 械褋谢懈 GB 懈褋锌芯谢褜蟹褍械褌褋褟 褋 写褉褍谐懈屑懈 锌褉懈谢芯卸械薪懈褟屑懈.</string>
+    <string name="pref_summary_keep_data_on_device">小芯褏褉邪薪褟褌褜 写邪薪薪褘械 芯斜 邪泻褌懈胁薪芯褋褌懈 薪邪 褍褋褌褉芯泄褋褌胁械 写邪卸械 锌芯褋谢械 褋懈薪褏褉芯薪懈蟹邪褑懈懈. 袩芯谢械蟹薪芯, 械褋谢懈 GB 懈褋锌芯谢褜蟹褍械褌褋褟 褋芯胁屑械褋褌薪芯 褋 写褉褍谐懈屑懈 锌褉懈谢芯卸械薪懈褟屑懈. 协褌芯 屑芯卸械褌 锌褉懈胁械褋褌懈 泻 褌芯屑褍, 褔褌芯 薪邪 褔邪褋邪褏 蟹邪泻芯薪褔懈褌褋褟 屑械褋褌芯 懈/懈谢懈 芯薪懈 锌械褉械褋褌邪薪褍褌 褋懈薪褏褉芯薪懈蟹懈褉芯胁邪褌褜褋褟 写芯谢卸薪褘屑 芯斜褉邪蟹芯屑.</string>
+    <string name="pref_title_low_latency_fw_update">袠褋锌芯谢褜蟹芯胁邪褌褜 褉械卸懈屑 褋 薪懈蟹泻懈屑懈 蟹邪写械褉卸泻邪屑懈 写谢褟 芯斜薪芯胁谢械薪懈褟 锌褉芯褕懈胁泻懈</string>
+    <string name="pref_summary_low_latency_fw_update">协褌芯 屑芯卸械褌 锌褉懈谐芯写懈褌褜褋褟 胁 褋谢褍褔邪械 薪械褍写邪褔薪芯谐芯 芯斜薪芯胁谢械薪懈褟 锌褉芯褕懈胁泻懈.</string>
+    <string name="live_activity_steps_history">袠褋褌芯褉懈褟 褕邪谐芯胁</string>
+    <string name="live_activity_current_steps_per_minute">孝械泻褍褖懈械 褕邪谐懈 胁 屑懈薪褍褌褍</string>
+    <string name="live_activity_total_steps">袙褋械谐芯 褕邪谐芯胁</string>
+    <string name="live_activity_steps_per_minute_history">袠褋褌芯褉懈褟 褕邪谐芯胁 蟹邪 屑懈薪褍褌褍</string>
+    <string name="live_activity_start_your_activity">袧邪褔薪懈褌械 胁邪褕褍 邪泻褌懈胁薪芯褋褌褜</string>
+    <string name="live_activity_max_heart_rate">孝械泻褍褖邪褟 / 屑邪泻褋懈屑邪谢褜薪邪褟 效小小: %1$d / %2$d</string>
+    <string name="abstract_chart_fragment_kind_activity">袗泻褌懈胁薪芯褋褌褜</string>
+    <string name="abstract_chart_fragment_kind_light_sleep">袘褘褋褌褉褘泄 褋芯薪</string>
+    <string name="abstract_chart_fragment_kind_deep_sleep">袚谢褍斜芯泻懈泄 褋芯薪</string>
+    <string name="abstract_chart_fragment_kind_not_worn">袧械 薪芯褋懈谢芯褋褜</string>
+    <string name="you_slept">%1$s - %2$s</string>
+    <string name="you_did_not_sleep">袙褘 薪械 褋锌邪谢懈</string>
+    <string name="device_not_connected">袧械 锌芯写泻谢褞褔械薪.</string>
+    <string name="user_feedback_all_alarms_disabled">袙褋械 斜褍写懈谢褜薪懈泻懈 芯褌泻谢褞褔械薪褘</string>
+    <string name="pref_title_keep_data_on_device">啸褉邪薪懈褌褜 写邪薪薪褘械 芯斜 邪泻褌懈胁薪芯褋褌懈 薪邪 褍褋褌褉芯泄褋褌胁械</string>
+    <string name="miband_fwinstaller_incompatible_version">袧械褋芯胁屑械褋褌懈屑邪褟 锌褉芯褕懈胁泻邪</string>
+    <string name="fwinstaller_firmware_not_compatible_to_device">协褌邪 锌褉芯褕懈胁泻邪 薪械 褋芯胁屑械褋褌懈屑邪 褋 褍褋褌褉芯泄褋褌胁芯屑</string>
+    <string name="miband_prefs_reserve_alarm_calendar">袪械蟹械褉胁薪褘械 褋懈谐薪邪谢褘 写谢褟 锌褉械写褋褌芯褟褖懈褏 褋芯斜褘褌懈泄</string>
+    <string name="miband_prefs_hr_sleep_detection">袠褋锌芯谢褜蟹芯胁邪褌褜 写邪褌褔懈泻 褋械褉写褑械斜懈械薪懈褟 写谢褟 褍谢褍褔褕械薪懈褟 芯褌褋谢械卸懈胁邪薪懈褟 褋薪邪</string>
+    <string name="miband_prefs_device_time_offset_hours">小屑械褖械薪懈械 胁褉械屑械薪懈 胁 褔邪褋邪褏 (写谢褟 褌械褏, 泻褌芯 褉邪斜芯褌邪械褌 锌芯 薪芯褔邪屑)</string>
+    <string name="miband2_prefs_dateformat">肖芯褉屑邪褌 写邪褌褘</string>
+    <string name="dateformat_time">袙褉械屑褟</string>
+    <string name="dateformat_date_time"><![CDATA[Time & date]]></string>
+    <string name="mi2_prefs_activate_display_on_lift">袗泻褌懈胁懈褉芯胁邪褌褜 褝泻褉邪薪 锌芯写薪褟褌懈械屑 褉褍泻懈</string>
+    <string name="FetchActivityOperation_about_to_transfer_since">袚芯褌芯胁 泻 锌械褉械写邪褔械 写邪薪薪褘褏 褋 %1$s</string>
+    <string name="waiting_for_reconnect">袨卸懈写邪薪懈械 锌械褉械锌芯写泻谢褞褔械薪懈褟</string>
+    <string name="activity_prefs_about_you">袙邪褕懈 写邪薪薪褘械</string>
+    <string name="activity_prefs_year_birth">袚芯写 褉芯卸写械薪懈褟</string>
+    <string name="activity_prefs_gender">袩芯谢</string>
+    <string name="activity_prefs_height_cm">袪芯褋褌 胁 褋屑</string>
+    <string name="activity_prefs_weight_kg">袙械褋 胁 泻谐</string>
+    <string name="authenticating">袗褍褌械薪褌懈褎懈泻邪褑懈褟</string>
+    <string name="authentication_required">孝褉械斜褍械褌褋褟 邪褍褌械薪褌懈褎懈泻邪褑懈褟</string>
+    <string name="appwidget_text">啸褉褉</string>
+    <string name="add_widget">袛芯斜邪胁懈褌褜 胁懈写卸械褌</string>
+    <string name="activity_prefs_sleep_duration">袞械谢邪械屑邪褟 锌褉芯写芯谢卸懈褌械谢褜薪芯褋褌褜 褋薪邪</string>
+    <string name="appwidget_setting_alarm">袘褍写懈谢褜薪懈泻 斜褘谢 褍褋褌邪薪芯胁谢械薪 薪邪 %1$02d:%2$02d</string>
+    <string name="device_hw">袙械褉褋懈褟 褍褋褌褉芯泄褋褌胁邪: %1$s</string>
+    <string name="device_fw">袙械褉褋懈褟 锌褉芯褕懈胁泻懈: %1$s</string>
+    <string name="error_creating_directory_for_logfiles">袨褕懈斜泻邪 褋芯蟹写邪薪懈褟 泻邪褌邪谢芯谐邪 写谢褟 谢芯谐-褎邪泄谢芯胁: %1$s</string>
+    <string name="DEVINFO_HR_VER">"袙械褉褋懈褟 褍褋褌褉芯泄褋褌胁邪: "</string>
+    <string name="updatefirmwareoperation_update_in_progress">袨斜薪芯胁谢械薪懈械 锌褉芯褕懈胁泻懈</string>
+    <string name="updatefirmwareoperation_firmware_not_sent">袩褉芯褕懈胁泻邪 薪械 芯褌锌褉邪胁谢械薪邪</string>
+    <string name="charts_legend_heartrate">袩褍谢褜褋</string>
+    <string name="live_activity_heart_rate">袩褍谢褜褋</string>
+    <string name="pref_title_pebble_health_store_raw">啸褉邪薪懈褌褜 薪械芯斜褉邪斜芯褌邪薪薪褘械 蟹邪锌懈褋懈 胁 斜邪蟹械 写邪薪薪褘褏</string>
+    <string name="pref_summary_pebble_health_store_raw">袝褋谢懈 褎谢邪卸芯泻 褍褋褌邪薪芯胁谢械薪, 写邪薪薪褘械 褋芯褏褉邪薪褟褞褌褋褟 泻邪泻 械褋褌褜. 袙 写邪谢褜薪械泄褕械屑 懈褏 屑芯卸薪芯 斜褍写械褌 芯斜褉邪斜邪褌褘胁邪褌褜. 袨斜褉邪褌懈褌械 胁薪懈屑邪薪懈械: 胁 褝褌芯屑 褋谢褍褔邪械 斜邪蟹邪 写邪薪薪褘褏 斜褍写械褌 蟹邪薪懈屑邪褌褜 斜芯谢褜褕械 屑械褋褌邪.</string>
+    <string name="action_db_management">校锌褉邪胁谢械薪懈械 写邪薪薪褘屑懈</string>
+    <string name="title_activity_db_management">校锌褉邪胁谢械薪懈械 写邪薪薪褘屑懈</string>
+    <string name="activity_db_management_import_export_explanation">袛谢褟 芯锌械褉邪褑懈泄 褝泻褋锌芯褉褌邪/懈屑锌芯褉褌邪 懈褋锌芯谢褜蟹褍械褌褋褟 褋谢械写褍褞褖懈泄 锌褍褌褜 (褋屑. 薪懈卸械) 泻 写懈褉械泻褌芯褉懈懈 薪邪 胁邪褕械屑 褍褋褌褉芯泄褋褌胁械. 协褌邪 写懈褉械泻褌芯褉懈褟 写芯褋褌褍锌薪邪 写褉褍谐懈屑 锌褉懈谢芯卸械薪懈褟屑 Android 懈 胁邪褕械屑褍 泻芯屑锌褜褞褌械褉褍. 袨斜褉邪褌懈褌械 胁薪懈屑邪薪懈械, 褔褌芯 褝褌邪 写懈褉械泻褌芯褉懈褟 懈 胁褋械 褋芯写械褉卸邪褖懈械褋褟 胁 薪械泄 褎邪泄谢褘 褍写邪谢褟褞褌褋褟 锌褉懈 褍写邪谢械薪懈懈 Gadgetbridge. 袛邪薪薪褘械 胁泻谢褞褔邪褞褌 胁 褋械斜褟:
+\n Export_preference - 谐谢芯斜邪谢褜薪褘械 薪邪褋褌褉芯泄泻懈
+\n Export_preference_MAC - 薪邪褋褌褉芯泄泻懈 写谢褟 泻芯薪泻褉械褌薪芯谐芯 褍褋褌褉芯泄褋褌胁邪
+\n Gadgetbridge - 斜邪蟹邪 写邪薪薪褘褏 褍褋褌褉芯泄褋褌胁 懈 邪泻褌懈胁薪芯褋褌械泄
+\n Gadgetbridge_date - 斜邪蟹邪 写邪薪薪褘褏, 褝泻褋锌芯褉褌懈褉芯胁邪薪薪邪褟 胁 芯锌褉械写械谢械薪薪褍褞 写邪褌褍
+\n *.gpx - 蟹邪锌懈褋懈 GPS
+\n *.log - 褎邪泄谢褘 卸褍褉薪邪谢邪
+\n袨卸懈写邪泄褌械 薪邪泄褌懈 褝泻褋锌芯褉褌懈褉芯胁邪薪薪褘械 褎邪泄谢褘 (懈谢懈 锌芯屑械褋褌懈褌褜 褎邪泄谢褘, 泻芯褌芯褉褘械 胁褘 褏芯褌懈褌械 懈屑锌芯褉褌懈褉芯胁邪褌褜) 褌邪屑:</string>
+    <string name="activity_db_management_merge_old_title">校写邪谢械薪懈械 褍褋褌邪褉械胁褕械泄 斜邪蟹褘 写邪薪薪褘褏</string>
+    <string name="dbmanagementactivvity_cannot_access_export_path">袧械褌 写芯褋褌褍锌邪 泻 锌褍褌懈 褝泻褋锌芯褉褌邪. 袨斜褉邪褌懈褌械褋褜, 锌芯卸邪谢褍泄褋褌邪, 泻 褉邪蟹褉邪斜芯褌褔懈泻邪屑.</string>
+    <string name="dbmanagementactivity_exported_to">协泻褋锌芯褉褌懈褉芯胁邪薪芯 胁: %1$s</string>
+    <string name="dbmanagementactivity_error_exporting_db">袨褕懈斜泻邪 褝泻褋锌芯褉褌邪 斜邪蟹褘 写邪薪薪褘褏: %1$s</string>
+    <string name="dbmanagementactivity_error_exporting_shared">袨褕懈斜泻邪 褝泻褋锌芯褉褌邪 薪邪褋褌褉芯械泻: %1$s</string>
+    <string name="dbmanagementactivity_import_data_title">袠屑锌芯褉褌懈褉芯胁邪褌褜 写邪薪薪褘械?</string>
+    <string name="dbmanagementactivity_overwrite_database_confirmation">袙褘 褍胁械褉械薪褘, 褔褌芯 褏芯褌懈褌械 锌械褉械蟹邪锌懈褋邪褌褜 褌械泻褍褖懈械 写邪薪薪褘械? 袙褋褟 褌械泻褍褖邪褟 邪泻褌懈胁薪芯褋褌褜 (械褋谢懈 芯薪邪 械褋褌褜), 褍褋褌褉芯泄褋褌胁邪 懈 薪邪褋褌褉芯泄泻懈 锌褉懈谢芯卸械薪懈褟 斜褍写褍褌 锌械褉械蟹邪锌懈褋邪薪褘.</string>
+    <string name="dbmanagementactivity_import_successful">袠屑锌芯褉褌懈褉芯胁邪薪芯.</string>
+    <string name="dbmanagementactivity_error_importing_db">袨褕懈斜泻邪 懈屑锌芯褉褌邪 斜邪蟹褘 写邪薪薪褘褏: %1$s</string>
+    <string name="dbmanagementactivity_error_importing_shared">袨褕懈斜泻邪 懈屑锌芯褉褌邪 薪邪褋褌褉芯械泻: %1$s</string>
+    <string name="dbmanagementactivity_delete_activity_data_title">校写邪谢懈褌褜 写邪薪薪褘械 锌芯 胁邪褕械泄 邪泻褌懈胁薪芯褋褌懈?</string>
+    <string name="dbmanagementactivity_really_delete_entire_db">袛械泄褋褌胁懈褌械谢褜薪芯 褍写邪谢懈褌褜 胁褋褞 斜邪蟹褍 写邪薪薪褘褏? 袙褋械 写邪薪薪褘械 芯 胁邪褕械泄 邪泻褌懈胁薪芯褋褌懈 懈 懈薪褎芯褉屑邪褑懈褟 芯 胁邪褕懈褏 褍褋褌褉芯泄褋褌胁邪褏 斜褍写褍褌 褍褌械褉褟薪褘.</string>
+    <string name="dbmanagementactivity_database_successfully_deleted">袛邪薪薪褘械 褍写邪谢械薪褘.</string>
+    <string name="dbmanagementactivity_db_deletion_failed">校写邪谢懈褌褜 斜邪蟹褍 写邪薪薪褘褏 薪械 锌芯谢褍褔懈谢芯褋褜.</string>
+    <string name="dbmanagementactivity_delete_old_activity_db">校写邪谢懈褌褜 写邪薪薪褘械 锌芯 锌褉械写褘写褍褖械泄 邪泻褌懈胁薪芯褋褌懈?</string>
+    <string name="dbmanagementactivity_delete_old_activitydb_confirmation">孝芯褔薪芯 褍写邪谢懈褌褜 褋褌邪褉褘械 写邪薪薪褘械 芯斜 邪泻褌懈胁薪芯褋褌懈? 袙褋械 薪械 懈屑锌芯褉褌懈褉芯胁邪薪薪褘械 写邪薪薪褘械 芯斜 邪泻褌懈胁薪芯褋褌懈 斜褍写褍褌 褍褌械褉褟薪褘.</string>
+    <string name="dbmanagementactivity_old_activity_db_successfully_deleted">校褋褌邪褉械胁褕懈械 写邪薪薪褘械 邪泻褌懈胁薪芯褋褌懈 褍写邪谢械薪褘.</string>
+    <string name="dbmanagementactivity_old_activity_db_deletion_failed">校写邪谢懈褌褜 褍褋褌邪褉械胁褕懈械 写邪薪薪褘械 锌芯 胁邪褕械泄 邪泻褌懈胁薪芯褋褌懈 薪械 锌芯谢褍褔懈谢芯褋褜.</string>
+    <string name="dbmanagementactivity_overwrite">袩械褉械蟹邪锌懈褋邪褌褜</string>
+    <string name="Cancel">袨褌屑械薪邪</string>
+    <string name="Delete">校写邪谢懈褌褜</string>
+    <!--Strings related to Vibration Activity-->
+    <string name="title_activity_vibration">袙懈斜褉邪褑懈褟</string>
+    <!--Strings related to Pebble Pairing Activity-->
+    <string name="title_activity_pebble_pairing">小芯锌褉褟卸械薪懈械 褋 Pebble</string>
+    <string name="pebble_pairing_hint">袧邪 胁邪褕械屑 Android 褍褋褌褉芯泄褋褌胁械 写芯谢卸薪芯 锌芯褟胁懈褌褜褋褟 胁褋锌谢褘胁邪褞褖械械 芯泻薪芯 褋 蟹邪锌褉芯褋芯屑 薪邪 褋芯锌褉褟卸械薪懈械 褍褋褌褉芯泄褋褌胁. 袝褋谢懈 褝褌芯谐芯 薪械 锌褉芯懈蟹芯褕谢芯, 蟹邪谐谢褟薪懈褌械 胁 褟褖懈泻 褍胁械写芯屑谢械薪懈泄 懈 锌褉懈屑懈褌械 蟹邪锌褉芯褋 薪邪 褋芯锌褉褟卸械薪懈械. 袩芯褋谢械 褝褌芯谐芯 锌褉懈屑懈褌械 蟹邪锌褉芯褋 褋芯锌褉褟卸械薪懈褟 薪邪 胁邪褕械屑 褍褋褌褉芯泄褋褌胁械 Pebble.</string>
+    <string name="weather_notification_label">效褌芯斜褘 锌芯谢褍褔邪褌褜 懈薪褎芯褉屑邪褑懈褞 芯 锌芯谐芯写械 薪邪 胁邪褕械屑 Pebble, 褍斜械写懈褌械褋褜, 褔褌芯 胁褘斜褉邪谢懈 褝褌褍 褌械屑褍 胁 锌褉懈谢芯卸械薪懈懈 芦校胁械写芯屑谢械薪懈械 芯 锌芯谐芯写械禄.
+\n
+\n袛芯锌芯谢薪懈褌械谢褜薪邪褟 薪邪褋褌褉芯泄泻邪 薪械 薪褍卸薪邪.
+\n
+\n袙泻谢褞褔懈褌褜 锌褉懈谢芯卸械薪懈械 锌芯谐芯写褘 薪邪 胁邪褕械屑 Pebble 屑芯卸薪芯 懈蟹 \"校锌褉邪胁谢械薪懈褟 锌褉懈谢芯卸械薪懈褟屑懈\".
+\n
+\n袩芯写写械褉卸懈胁邪械屑褘械 褑懈褎械褉斜谢邪褌褘 锌芯泻邪卸褍褌 锌芯谐芯写褍 邪胁褌芯屑邪褌懈褔械褋泻懈.</string>
+    <string name="pref_title_setup_bt_pairing">小芯锌褉褟卸械薪懈械 锌芯 Bluetooth</string>
+    <string name="pref_summary_setup_bt_pairing">袨褌泻谢褞褔懈褌械, 械褋谢懈 胁邪屑 薪械 褍写邪谢芯褋褜 锌芯写泻谢褞褔懈褌褜褋褟</string>
+    <string name="unit_metric">袦械褌褉懈褔械褋泻邪褟 褋懈褋褌械屑邪 屑械褉</string>
+    <string name="unit_imperial">袗薪谐谢懈泄褋泻邪褟 褋懈褋褌械屑邪 屑械褉</string>
+    <string name="timeformat_24h">24 褔</string>
+    <string name="timeformat_am_pm">12 褔</string>
+    <string name="pref_screen_notification_profile_alarm_clock">袘褍写懈谢褜薪懈泻</string>
+    <string name="StringUtils_sender">(%1$s)</string>
+    <string name="find_device_you_found_it">袧邪褕褢谢褋褟!</string>
+    <string name="miband2_prefs_timeformat">Mi2: 肖芯褉屑邪褌 胁褉械屑械薪懈</string>
+    <string name="mi2_fw_installhandler_fw53_hint">袛芯 褌芯谐芯 泻邪泻 褍褋褌邪薪芯胁懈褌褜 褝褌褍 锌褉芯褕懈胁泻褍, 薪褍卸薪芯 褍褋褌邪薪芯胁懈褌褜 锌褉械写褘写褍褖褍褞 胁械褉褋懈褞 %1$s!</string>
+    <string name="mi2_enable_text_notifications">孝械泻褋褌芯胁褘械 褍胁械写芯屑谢械薪懈褟</string>
+    <string name="mi2_enable_text_notifications_summary"><![CDATA[孝褉械斜褍械褌褋褟 锌褉芯褕懈胁泻邪 >= 1.0.1.28 懈 褍褋褌邪薪芯胁谢械薪薪褘泄 Mili_pro.ft* .]]></string>
+    <string name="off">袙褘泻谢.</string>
+    <string name="discovery_attempting_to_pair">袩芯锌褘褌泻邪 褋芯锌褉褟卸械薪懈褟 褋 %1$s</string>
+    <string name="discovery_bonding_failed_immediately">袧械 褍写邪谢芯褋褜 胁褘锌芯谢薪懈褌褜 锌褉懈胁褟蟹泻褍 泻 %1$s.</string>
+    <string name="discovery_trying_to_connect_to">袩芯锌褘褌泻邪 褋芯械写懈薪械薪懈褟 褋: %1$s</string>
+    <string name="discovery_enable_bluetooth">袙泻谢褞褔懈褌褜 Bluetooth 写谢褟 芯斜薪邪褉褍卸械薪懈褟 褍褋褌褉芯泄褋褌胁.</string>
+    <string name="discovery_successfully_bonded">小胁褟蟹邪薪芯 褋 %1$s.</string>
+    <string name="discovery_pair_title">袙褘锌芯谢薪懈褌褜 褋芯锌褉褟卸械薪懈械 褋 %1$s ?</string>
+    <string name="discovery_pair_question">袙褘斜械褉懈褌械 \"小芯锌褉褟卸械薪懈械\" 写谢褟 褋芯锌褉褟卸械薪懈褟 胁邪褕懈褏 褍褋褌褉芯泄褋褌胁. 袝褋谢懈 薪械 锌芯谢褍褔懈谢芯褋褜, 锌芯锌褉芯斜褍泄褌械 褋薪芯胁邪 斜械蟹 褋芯锌褉褟卸械薪懈褟.</string>
+    <string name="discovery_yes_pair">小芯锌褉褟卸械薪懈械</string>
+    <string name="discovery_dont_pair">袧械 胁褘锌芯谢薪褟褌褜 褋芯锌褉褟卸械薪懈械</string>
+    <string name="action_donate">袩芯卸械褉褌胁芯胁邪薪懈褟</string>
+    <string name="controlcenter_connect">袩芯写泻谢褞褔懈褌褜鈥�</string>
+    <string name="title_activity_calblacklist">袟邪斜谢芯泻懈褉芯胁邪薪薪褘械 泻邪谢械薪写邪褉懈</string>
+    <string name="fw_upgrade_notice_amazfitbip">袙褘 褋芯斜懈褉邪械褌械褋褜 褍褋褌邪薪芯胁懈褌褜 锌褉芯褕懈胁泻褍 %s 薪邪 胁邪褕 Amazfit Bip.
+\n
+\n袘褍写褜褌械 胁薪懈屑邪褌械谢褜薪褘: 褋薪邪褔邪谢邪 褍褋褌邪薪芯胁懈褌械 褎邪泄谢 .fw, 锌芯褌芯屑 .res, 懈 蟹邪褌械屑 .gps. 袩芯褋谢械 褍褋褌邪薪芯胁泻懈 .fw 胁邪褕懈 褔邪褋褘 斜褍写褍褌 锌械褉械蟹邪谐褉褍卸械薪褘.
+\n
+\n袩褉懈屑械褔邪薪懈械: 械褋谢懈 褎邪泄谢褘 .gps 懈 .res 褌邪泻懈械 卸械, 泻邪泻 胁 褌械泻褍褖械泄 胁械褉褋懈懈, 懈褏 薪械 薪褍卸薪芯 锌械褉械褍褋褌邪薪邪胁谢懈胁邪褌褜.
+\n
+\n袙蝎 袛袝袡小孝袙校袝孝袝 袧袗 小袙袨袡 小孝袪袗啸 袠 袪袠小袣!</string>
+    <string name="fw_upgrade_notice_amazfitcor">袙褘 褋芯斜懈褉邪械褌械褋褜 褍褋褌邪薪芯胁懈褌褜 锌褉芯褕懈胁泻褍 %s 薪邪 胁邪褕 Amazfit Cor.
+\n
+\n袘褍写褜褌械 胁薪懈屑邪褌械谢褜薪褘: 褋薪邪褔邪谢邪 褍褋褌邪薪芯胁懈褌械 褎邪泄谢 .fw, 蟹邪褌械屑 .res. 袩芯褋谢械 褍褋褌邪薪芯胁泻懈 .fw 胁邪褕 斜褉邪褋谢械褌 斜褍写械褌 锌械褉械蟹邪谐褉褍卸械薪.
+\n
+\n袩褉懈屑械褔邪薪懈械: 械褋谢懈 褎邪泄谢 懈 .res 褌邪泻芯泄 卸械, 泻邪泻 胁 褌械泻褍褖械泄 胁械褉褋懈懈, 械谐芯 薪械 薪褍卸薪芯 锌械褉械褍褋褌邪薪邪胁谢懈胁邪褌褜.
+\n
+\n袙蝎 袛袝袡小孝袙校袝孝袝 袧袗 小袙袨袡 小孝袪袗啸 袠 袪袠小袣!</string>
+    <string name="pref_title_charts_swipe">袙泻谢褞褔懈褌褜 卸械褋褌褘 \"锌褉芯胁械褋褌懈 薪邪锌褉邪胁芯 懈 薪邪谢械胁芯\" 胁 谐褉邪褎懈泻邪褏 邪泻褌懈胁薪芯褋褌懈</string>
+    <string name="pref_blacklist_calendars">袟邪斜谢芯泻懈褉芯胁邪薪薪褘械 泻邪谢械薪写邪褉懈</string>
+    <string name="pref_header_pebble_timeline">袙褉械屑械薪薪芯泄 谐褉邪褎懈泻 Pebble</string>
+    <string name="pref_title_pebble_enable_bgjs">袙泻谢褞褔懈褌褜 JS 胁 褎芯薪芯胁芯屑 褉械卸懈屑械</string>
+    <string name="pref_summary_pebble_enable_bgjs">袝褋谢懈 褝褌邪 褎褍薪泻褑懈褟 胁泻谢褞褔械薪邪, 薪邪 懈薪褌械褉褎械泄褋械 褔邪褋芯胁 屑芯卸薪芯 芯褌芯斜褉邪卸邪褌褜 锌芯谐芯写褍, 懈薪褎芯褉屑邪褑懈褞 芯 蟹邪褉褟写械 斜邪褌邪褉械懈 懈 褌. 写.</string>
+    <string name="activity_web_view">袙械斜-芯褌褔褢褌 邪泻褌懈胁薪芯褋褌懈</string>
+    <string name="mi2_dnd_off">袙褘泻谢.</string>
+    <string name="mi2_dnd_automatic">袗胁褌芯屑邪褌懈褔械褋泻懈 (芯锌褉械写械谢褟械褌 褉械卸懈屑 褋薪邪)</string>
+    <string name="mi2_dnd_scheduled">袟邪锌谢邪薪懈褉芯胁邪薪芯 (锌褉芯屑械卸褍褌芯泻 胁褉械屑械薪懈)</string>
+    <string name="_pebble_watch_open_on_phone">袨褌泻褉褘褌褜 胁 褍褋褌褉芯泄褋褌胁械</string>
+    <string name="_pebble_watch_mute">袨褌泻谢褞褔懈褌褜 蟹胁褍泻</string>
+    <string name="_pebble_watch_reply">袨褌胁械褌懈褌褜</string>
+    <string name="stats_title">袟芯薪褘 褋泻芯褉芯褋褌懈</string>
+    <string name="stats_x_axis_label">袙褋械谐芯 屑懈薪褍褌</string>
+    <string name="stats_y_axis_label">楔邪谐芯胁 胁 屑懈薪褍褌褍</string>
+    <string name="clock">效邪褋褘</string>
+    <string name="heart_rate">袩褍谢褜褋</string>
+    <string name="battery">袟邪褉褟写 斜邪褌邪褉械懈</string>
+    <string name="mi2_prefs_button_actions">袛械泄褋褌胁懈褟 泻薪芯锌泻懈</string>
+    <string name="mi2_prefs_button_actions_summary">袧邪褋褌褉芯泄褌械 写械泄褋褌胁懈褟 锌褉懈 薪邪卸邪褌懈懈 薪邪 泻薪芯锌泻褍</string>
+    <string name="mi2_prefs_button_press_count">袣芯谢-胁芯 薪邪卸邪褌懈泄 薪邪 泻薪芯锌泻褍</string>
+    <string name="mi2_prefs_button_press_count_summary">袣芯谢懈褔械褋褌胁芯 薪邪卸邪褌懈泄, 薪械芯斜褏芯写懈屑芯械 写谢褟 谐械薪械褉邪褑懈懈 小芯斜褘褌懈褟 1. 袝褖褢 褋褌芯谢褜泻芯 卸械 薪邪卸邪褌懈泄 锌芯褌褉械斜褍械褌褋褟 写谢褟 谐械薪械褉邪褑懈懈 小芯斜褘褌懈褟 2 懈 褌.写.</string>
+    <string name="mi2_prefs_button_press_broadcast">小芯芯斜褖械薪懈械 写谢褟 褌褉邪薪褋谢褟褑懈懈</string>
+    <string name="mi2_prefs_button_press_broadcast_summary">袨褌锌褉邪胁谢褟褌褜 褋芯芯斜褖械薪懈械 锌褉懈 薪邪褋褌褍锌谢械薪懈懈 褋芯斜褘褌懈褟. 袩邪褉邪屑械褌褉 `button_id` 写芯斜邪胁谢褟械褌褋褟 泻 泻邪卸写芯屑褍 褋芯芯斜褖械薪懈褞 邪胁褌芯屑邪褌懈褔械褋泻懈.</string>
+    <string name="mi2_prefs_button_action">袙泻谢褞褔懈褌褜 写械泄褋褌胁懈褟 写谢褟 泻薪芯锌泻懈</string>
+    <string name="mi2_prefs_button_action_summary">袙泻谢褞褔懈褌褜 写械泄褋褌胁懈褟 薪邪 蟹邪写邪薪薪芯械 泻芯谢-胁芯 薪邪卸邪褌懈泄 泻薪芯锌泻懈</string>
+    <string name="mi2_prefs_button_action_vibrate">袙泻谢褞褔懈褌褜 胁懈斜褉芯-芯褌泻谢懈泻 斜褉邪褋谢械褌邪</string>
+    <string name="mi2_prefs_button_action_vibrate_summary">袙泻谢褞褔懈褌褜 胁懈斜褉芯-芯褌泻谢懈泻 斜褉邪褋谢械褌邪 胁 芯褌胁械褌 薪邪 懈褋锌芯谢薪械薪懈械 写械泄褋褌胁懈褟 锌褉懈 薪邪卸邪褌懈懈</string>
+    <string name="mi2_prefs_button_press_count_max_delay">袦邪泻褋懈屑邪谢褜薪邪褟 蟹邪写械褉卸泻邪 屑械卸写褍 薪邪卸邪褌懈褟屑懈</string>
+    <string name="mi2_prefs_button_press_count_max_delay_summary">袦邪泻褋懈屑邪谢褜薪邪褟 蟹邪写械褉卸泻邪 屑械卸写褍 薪邪卸邪褌懈褟屑懈 胁 屑懈谢谢懈褋械泻褍薪写邪褏</string>
+    <string name="mi2_prefs_goal_notification">校胁械写芯屑谢械薪懈褟 芯 写芯褋褌懈卸械薪懈懈 褑械谢懈</string>
+    <string name="mi2_prefs_goal_notification_summary">袘褉邪褋谢械褌 蟹邪胁懈斜褉懈褉褍械褌, 泻芯谐写邪 斜褍写械褌 胁褘锌芯谢薪械薪邪 写薪械胁薪邪褟 薪芯褉屑邪 褕邪谐芯胁</string>
+    <string name="mi2_prefs_display_items">协谢械屑械薪褌褘 屑械薪褞</string>
+    <string name="mi2_prefs_display_items_summary">袙褘斜械褉懈褌械, 褔褌芯 锌芯泻邪蟹褘胁邪褌褜 薪邪 褝泻褉邪薪械 斜褉邪褋谢械褌邪</string>
+    <string name="mi2_prefs_rotate_wrist_to_switch_info">袩械褉械泻谢褞褔邪褌褜 懈薪褎芯褉屑邪褑懈褞 锌芯胁芯褉芯褌芯屑 蟹邪锌褟褋褌褜褟</string>
+    <string name="mi2_prefs_do_not_disturb">袧械 斜械褋锌芯泻芯懈褌褜</string>
+    <string name="mi2_prefs_do_not_disturb_summary">袘褉邪褋谢械褌 薪械 斜褍写械褌 锌芯谢褍褔邪褌褜 褍胁械写芯屑谢械薪懈褟, 写邪卸械 械褋谢懈 胁泻谢褞褔褢薪</string>
+    <string name="mi2_prefs_inactivity_warnings">袧邪锌芯屑懈薪邪薪懈褟 芯 薪懈蟹泻芯泄 邪泻褌懈胁薪芯褋褌懈</string>
+    <string name="mi2_prefs_inactivity_warnings_summary">袣芯谐写邪 胁褘 屑邪谢芯 写胁懈谐邪械褌械褋褜, 斜褉邪褋谢械褌 胁褉械屑褟 芯褌 胁褉械屑械薪懈 胁懈斜褉懈褉褍械褌</string>
+    <string name="mi2_prefs_inactivity_warnings_threshold">袩械褉懈芯写 薪懈蟹泻芯泄 邪泻褌懈胁薪芯褋褌懈 (胁 屑懈薪褍褌邪褏)</string>
+    <string name="mi2_prefs_inactivity_warnings_dnd_summary">袨褌泻谢褞褔懈褌褜 褍胁械写芯屑谢械薪懈褟 芯 薪懈蟹泻芯泄 邪泻褌懈胁薪芯褋褌懈 胁芯 胁褉械屑褟 褉械卸懈屑邪 \"袧械 斜械褋锌芯泻芯懈褌褜\"</string>
+    <string name="mi2_prefs_do_not_disturb_start">袧邪褔邪谢褜薪芯械 胁褉械屑褟</string>
+    <string name="mi2_prefs_do_not_disturb_end">袙褉械屑褟 芯泻芯薪褔邪薪懈褟</string>
+    <string name="automatic">袗胁褌芯屑邪褌懈褔械褋泻懈</string>
+    <string name="simplified_chinese">袣懈褌邪泄褋泻懈泄 褍锌褉芯褖褢薪薪褘泄</string>
+    <string name="traditional_chinese">袣懈褌邪泄褋泻懈泄 褌褉邪写懈褑懈芯薪薪褘泄</string>
+    <string name="english">袗薪谐谢懈泄褋泻懈泄 褟蟹褘泻</string>
+    <string name="prefs_title_heartrate_measurement_interval">袟邪屑械褉 锌褍谢褜褋邪 薪邪 锌褉芯褌褟卸械薪懈懈 写薪褟</string>
+    <string name="interval_one_minute">褉邪蟹 胁 屑懈薪褍褌褍</string>
+    <string name="interval_five_minutes">泻邪卸写褘械 5 屑懈薪褍褌</string>
+    <string name="interval_ten_minutes">泻邪卸写褘械 10 屑懈薪褍褌</string>
+    <string name="interval_thirty_minutes">泻邪卸写褘械 30 屑懈薪褍褌</string>
+    <string name="interval_one_hour">褉邪蟹 胁 褔邪褋</string>
+    <string name="pref_title_weather">袩芯谐芯写邪</string>
+    <string name="notif_export_failed_title">袨褕懈斜泻邪 褝泻褋锌芯褉褌邪 斜邪蟹褘 写邪薪薪褘褏! 袩芯卸邪谢褍泄褋褌邪, 锌褉芯胁械褉褜褌械 薪邪褋褌褉芯泄泻懈.</string>
+    <string name="activity_type_light_sleep">袘褘褋褌褉褘泄 褋芯薪</string>
+    <string name="activity_type_deep_sleep">袚谢褍斜芯泻懈泄 褋芯薪</string>
+    <string name="activity_type_not_worn">校褋褌褉芯泄褋褌胁芯 薪械 薪邪写械褌芯</string>
+    <string name="activity_type_running">袘械谐</string>
+    <string name="activity_type_walking">袩褉芯谐褍谢泻邪</string>
+    <string name="activity_type_swimming">袩谢邪胁邪薪懈械</string>
+    <string name="kind_font">楔褉懈褎褌</string>
+    <string name="kind_watchface">笑懈褎械褉斜谢邪褌</string>
+    <string name="menuitem_status">小褌邪褌褍褋</string>
+    <string name="menuitem_weather">袩芯谐芯写邪</string>
+    <string name="menuitem_compass">袣芯屑锌邪褋</string>
+    <string name="menuitem_settings">袧邪褋褌褉芯泄泻懈</string>
+    <string name="blacklist_all_for_notifications">袟邪斜谢芯泻懈褉芯胁邪褌褜 胁褋械</string>
+    <string name="whitelist_all_for_notifications">袪邪蟹斜谢芯泻懈褉芯胁邪褌褜 胁褋械</string>
+    <string name="fw_upgrade_notice_miband3">袙褘 褋芯斜懈褉邪械褌械褋褜 褍褋褌邪薪芯胁懈褌褜 锌褉芯褕懈胁泻褍 %s 薪邪 胁邪褕 Mi Band 3.
+\n
+\n袘褍写褜褌械 胁薪懈屑邪褌械谢褜薪褘: 胁薪邪褔邪谢械 褍褋褌邪薪芯胁懈褌械 褎邪泄谢 .fw, 蟹邪褌械屑 .res. 袩芯褋谢械 褍褋褌邪薪芯胁泻懈 .fw 胁邪褕懈 褔邪褋褘 斜褍写褍褌 锌械褉械蟹邪谐褉褍卸械薪褘.
+\n
+\n袩褉懈屑械褔邪薪懈械: 械褋谢懈 胁械褉褋懈褟 褎邪泄谢邪 .res 褋芯胁锌邪写邪械褌 褋 褍褋褌邪薪芯胁谢械薪薪芯泄, 械谐芯 薪械 薪褍卸薪芯 锌械褉械褍褋褌邪薪邪胁谢懈胁邪褌褜.
+\n
+\n袙蝎 袛袝袡小孝袙校袝孝袝 袧袗 小袙袨袡 小孝袪袗啸 袠 袪袠小袣!</string>
+    <string name="pref_title_weather_location">袦械褋褌芯锌芯谢芯卸械薪懈械 锌芯谐芯写褘 (写谢褟 锌褉芯胁邪泄写械褉邪 锌芯谐芯写褘 LineageOS)</string>
+    <string name="pref_title_pebble_gatt_clientonly">袩芯写泻谢褞褔邪褌褜褋褟 褌芯谢褜泻芯 胁 褉械卸懈屑械 GATT-泻谢懈械薪褌邪</string>
+    <string name="pref_summary_pebble_gatt_clientonly">协泻褋锌械褉懈屑械薪褌邪谢褜薪褘械 薪邪褋褌褉芯泄泻懈 褌芯谢褜泻芯 写谢褟 Pebble 2. 袩芯锌褉芯斜褍泄褌械, 械褋谢懈 褋胁褟蟹褜 薪械 芯褔械薪褜</string>
+    <string name="pref_header_auto_export">袗胁褌芯屑邪褌懈褔械褋泻懈泄 褝泻褋锌芯褉褌 写邪薪薪褘褏</string>
+    <string name="pref_title_auto_export_enabled">袙泻谢褞褔懈褌褜 邪胁褌芯屑邪褌懈褔械褋泻懈泄 褝泻褋锌芯褉褌</string>
+    <string name="pref_title_auto_export_location">袩褍褌褜 写谢褟 褝泻褋锌芯褉褌邪 写邪薪薪褘褏</string>
+    <string name="pref_title_auto_export_interval">袠薪褌械褉胁邪谢 褝泻褋锌芯褉褌邪</string>
+    <string name="pref_summary_auto_export_interval">协泻褋锌芯褉褌 泻邪卸写褘械 %d 褔邪褋芯胁</string>
+    <string name="spanish">袠褋锌邪薪褋泻懈泄</string>
+    <string name="russian">袪褍褋褋泻懈泄</string>
+    <string name="activity_type_not_measured">袧械 懈蟹屑械褉褟谢芯褋褜</string>
+    <string name="activity_type_activity">袗泻褌懈胁薪芯褋褌褜</string>
+    <string name="activity_type_unknown">袧械懈蟹胁械褋褌薪邪褟 邪泻褌懈胁薪芯褋褌褜</string>
+    <string name="activity_type_treadmill">袘械谐芯胁邪褟 写芯褉芯卸泻邪</string>
+    <string name="kind_gps">袩褉芯褕懈胁泻邪 GPS</string>
+    <string name="kind_resources">袪械褋褍褉褋褘</string>
+    <string name="devicetype_unknown">袧械懈蟹胁械褋褌薪芯械 褍褋褌褉芯泄褋褌胁芯</string>
+    <string name="devicetype_pebble">Pebble</string>
+    <string name="devicetype_amazfit_bip">Amazfit Bip</string>
+    <string name="devicetype_amazfit_cor">Amazfit Cor</string>
+    <string name="menuitem_alipay">Alipay</string>
+    <string name="on">袙泻谢.</string>
+    <string name="select_all">袙褘斜褉邪褌褜 胁褋械</string>
+    <string name="share">袩芯写械谢懈褌褜褋褟</string>
+    <string name="kind_firmware">袩褉芯褕懈胁泻邪</string>
+    <string name="menuitem_alarm">袘褍写懈谢褜薪懈泻</string>
+    <string name="menuitem_timer">袨斜褉邪褌薪褘泄 芯褌褋褔褢褌</string>
+    <string name="activity_type_biking">袙械谢芯锌褉芯谐褍谢泻邪</string>
+    <string name="devicetype_test">孝械褋褌芯胁芯械 褍褋褌褉芯泄褋褌胁芯</string>
+    <string name="devicetype_miband">Mi Band</string>
+    <string name="devicetype_miband2">Mi Band 2</string>
+    <string name="devicetype_miband3">Mi Band 3</string>
+    <string name="devicetype_vibratissimo">Vibratissimo</string>
+    <string name="devicetype_liveview">LiveView</string>
+    <string name="devicetype_hplus">HPlus</string>
+    <string name="devicetype_makibes_f68">Makibes F68</string>
+    <string name="devicetype_exrizu_k8">Exrizu K8</string>
+    <string name="devicetype_q8">Q8</string>
+    <string name="devicetype_teclast_h30">Teclast H30</string>
+    <string name="devicetype_xwatch">XWatch</string>
+    <string name="devicetype_mykronoz_zetime">MyKronoz ZeTime</string>
+    <string name="prefs_screen_orientation">袨褉懈械薪褌邪褑懈褟 褝泻褉邪薪邪</string>
+    <string name="pref_auto_fetch">袩芯谢褍褔邪褌褜 写邪薪薪褘械 邪泻褌懈胁薪芯褋褌懈 邪胁褌芯屑邪褌懈褔械褋泻懈</string>
+    <string name="pref_auto_fetch_summary">袩芯谢褍褔邪褌褜 写邪薪薪褘械 邪泻褌懈胁薪芯褋褌懈 锌褉懈 褉邪蟹斜谢芯泻懈褉芯胁泻械. 袪邪斜芯褌邪械褌 褌芯谢褜泻芯 锌褉懈 薪邪褋褌褉芯械薪薪芯泄 斜谢芯泻懈褉芯胁泻械 褝泻褉邪薪邪!</string>
+    <string name="pref_auto_fetch_limit_fetches">袦懈薪懈屑邪谢褜薪褘泄 懈薪褌械褉胁邪谢</string>
+    <string name="pref_auto_fetch_limit_fetches_summary">袟邪斜懈褉邪械褌 写邪薪薪褘械 泻邪卸写褘械 %d 屑懈薪褍褌</string>
+    <string name="horizontal">袚芯褉懈蟹芯薪褌邪谢褜薪芯</string>
+    <string name="vertical">袙械褉褌懈泻邪谢褜薪芯</string>
+    <string name="controlcenter_start_activity_tracks">袨褌褋谢械卸懈胁邪薪懈械 邪泻褌懈胁薪芯褋褌懈</string>
+    <string name="activity_summaries">小锌芯褉褌懈胁薪邪褟 邪泻褌懈胁薪芯褋褌褜</string>
+    <string name="reset_index">小斜褉芯褋懈褌褜 写邪褌褍 锌芯谢褍褔械薪懈褟</string>
+    <string name="kind_invalid">袧械写芯锌褍褋褌懈屑褘泄 褎芯褉屑邪褌 写邪薪薪褘褏</string>
+    <string name="kind_gps_almanac">袛邪薪薪褘械 GPS</string>
+    <string name="kind_gps_cep">袩芯锌褉邪胁泻邪 芯褕懈斜泻懈 GPS</string>
+    <string name="devicetype_no1_f1">No.1 F1</string>
+    <string name="devicetype_id115">ID115</string>
+    <string name="choose_auto_export_location">袣褍写邪 褋芯褏褉邪薪褟褌褜 写邪薪薪褘械 锌褉懈 褝泻褋锌芯褉褌械</string>
+    <string name="notification_channel_name">袨斜褖懈泄</string>
+    <string name="menuitem_activity">袠褋褌芯褉懈褟 褌褉械薪懈褉芯胁芯泻</string>
+    <string name="menuitem_notifications">校胁械写芯屑谢械薪懈褟</string>
+    <string name="menuitem_more">袝褖褢</string>
+    <string name="menuitem_music">袦褍蟹褘泻邪</string>
+    <string name="controlcenter_calibrate_device">袨褌泻邪谢懈斜褉芯胁邪褌褜 褍褋褌褉芯泄褋褌胁芯</string>
+    <string name="watch9_pairing_tap_hint">袣芯谐写邪 褍褋褌褉芯泄褋褌胁芯 蟹邪胁懈斜褉懈褉褍械褌, 胁褋褌褉褟褏薪懈褌械 械谐芯 懈谢懈 薪邪卸屑懈褌械 薪邪 泻薪芯锌泻褍.</string>
+    <string name="devicetype_watch9">Watch 9</string>
+    <string name="watch9_time_minutes">袦懈薪褍褌:</string>
+    <string name="watch9_time_hours">效邪褋芯胁:</string>
+    <string name="watch9_time_seconds">小械泻褍薪写:</string>
+    <string name="watch9_calibration_hint">袙褘褋褌邪胁褜褌械 褌械泻褍褖械械 胁褉械屑褟 (褋胁械褉褜褌械褋褜 褋 褋懈褋褌械屑薪褘屑).</string>
+    <string name="watch9_calibration_button">袣邪谢懈斜褉芯胁泻邪</string>
+    <string name="title_activity_watch9_pairing">小芯锌褉褟卸械薪懈械 Watch 9</string>
+    <string name="title_activity_watch9_calibration">袣邪谢懈斜褉芯胁泻邪 Watch 9</string>
+    <string name="mi3_prefs_band_screen_unlock">袪邪蟹斜谢芯泻懈褉芯胁泻邪 褝泻褉邪薪邪</string>
+    <string name="mi3_prefs_band_screen_unlock_summary">小屑邪褏薪懈褌械 胁胁械褉褏 写谢褟 褉邪蟹斜谢芯泻懈褉芯胁泻懈 褝泻褉邪薪邪 斜褉邪褋谢械褌邪</string>
+    <string name="german">袧械屑械褑泻懈泄</string>
+    <string name="italian">袠褌邪谢褜褟薪褋泻懈泄</string>
+    <string name="french">肖褉邪薪褑褍蟹褋泻懈泄</string>
+    <string name="polish">袩芯谢褜褋泻懈泄</string>
+    <string name="korean">袣芯褉械泄褋泻懈泄</string>
+    <string name="japanese">携锌芯薪褋泻懈泄</string>
+    <string name="norwegian_bokmal">袧芯褉胁械卸褋泻懈泄 (谢懈褌械褉邪褌褍褉薪褘泄)</string>
+    <string name="share_log">袩芯写械谢懈褌褜褋褟 谢芯谐-褎邪泄谢芯屑</string>
+    <string name="share_log_warning">肖邪泄谢褘 卸褍褉薪邪谢邪 (log-褎邪泄谢褘) Gadgetbridge 屑芯谐褍褌 褋芯写械褉卸邪褌褜 褉邪蟹谢懈褔薪褍褞 谢懈褔薪褍褞 懈薪褎芯褉屑邪褑懈褞, 褌邪泻褍褞 泻邪泻 褍薪懈泻邪谢褜薪褘械 懈写械薪褌懈褎懈泻邪褌芯褉褘 (薪邪锌褉懈屑械褉, MAC-邪写褉械褋 褍褋褌褉芯泄褋褌胁邪), 懈薪褎芯褉屑邪褑懈褞 芯 屑褍蟹褘泻邪谢褜薪褘褏 锌褉械写锌芯褔褌械薪懈褟褏 懈 褌.写. 袪械泻芯屑械薪写褍械褌褋褟 褉械写邪泻褌懈褉芯胁邪薪懈械 褎邪泄谢邪 卸褍褉薪邪谢邪 懈 褍写邪谢械薪懈械 谢懈褔薪芯泄 懈薪褎芯褉屑邪褑懈懈 锌械褉械写 芯褌锌褉邪胁泻芯泄 胁 锌褍斜谢懈褔薪芯屑 芯褌褔械褌械 芯 锌褉芯斜谢械屑械.</string>
+    <string name="warning">袙薪懈屑邪薪懈械!</string>
+    <string name="no_data">袧械褌 写邪薪薪褘褏</string>
+    <string name="pref_title_notifications_timeout">袦懈薪懈屑邪谢褜薪褘泄 懈薪褌械褉胁邪谢 屑械卸写褍 褍胁械写芯屑谢械薪懈褟屑懈</string>
+    <string name="no_limit">袘械蟹 芯谐褉邪薪懈褔械薪懈泄</string>
+    <string name="seconds_5">5 褋械泻褍薪写</string>
+    <string name="seconds_10">10 褋械泻褍薪写</string>
+    <string name="seconds_20">20 褋械泻褍薪写</string>
+    <string name="seconds_30">30 褋械泻褍薪写</string>
+    <string name="minutes_1">1 屑懈薪褍褌邪</string>
+    <string name="minutes_5">5 屑懈薪褍褌</string>
+    <string name="minutes_10">10 屑懈薪褍褌</string>
+    <string name="minutes_30">30 屑懈薪褍褌</string>
+    <string name="mi3_prefs_night_mode">袧芯褔薪芯泄 褉械卸懈屑</string>
+    <string name="mi3_prefs_night_mode_summary">袗胁褌芯屑邪褌懈褔械褋泻懈 褋薪懈卸邪褌褜 褟褉泻芯褋褌褜 褝泻褉邪薪邪 褍褋褌褉芯泄褋褌胁邪 锌芯 薪芯褔邪屑</string>
+    <string name="mi3_night_mode_sunset">袧邪 蟹邪泻邪褌械</string>
+    <string name="ok">袩褉懈薪褟褌褜</string>
+    <string name="controlcenter_change_led_color">袠蟹屑械薪懈褌褜 褑胁械褌 褋胁械褌芯写懈芯写邪</string>
+    <string name="controlcenter_change_fm_frequency">袠蟹屑械薪懈褌褜 FM-褔邪褋褌芯褌褍</string>
+    <string name="devicetype_roidmi">Roidmi</string>
+    <string name="devicetype_roidmi3">Roidmi 3</string>
+    <string name="preferences_led_color">笑胁械褌 懈蟹谢褍褔械薪懈褟 褋胁械褌芯写懈芯写邪</string>
+    <string name="preferences_fm_frequency">效邪褋褌芯褌邪 校袣袙</string>
+    <string name="pref_invalid_frequency_title">袧械写芯锌褍褋褌懈屑邪褟 褔邪褋褌芯褌邪</string>
+    <string name="pref_invalid_frequency_message">袙褘斜械褉懈褌械, 锌芯卸邪谢褍泄褋褌邪, 褔邪褋褌芯褌褍 胁 懈薪褌械褉胁邪谢械 87.5鈥�108</string>
+    <string name="language_and_region_prefs">携蟹褘泻 懈 褉械谐懈芯薪邪谢褜薪褘械 薪邪褋褌褉芯泄泻懈</string>
+    <string name="notif_battery_low">袗泻泻褍屑褍谢褟褌芯褉 %1$s 锌芯褔褌懈 褉邪蟹褉褟卸械薪</string>
+    <string name="notif_battery_low_extended">袗泻泻褍屑褍谢褟褌芯褉 %1$s 锌芯褔褌懈 褉邪蟹褉褟卸械薪: %2$s</string>
+    <string name="activity_prefs_charts">袧邪褋褌褉芯泄泻懈 褋褌邪褌懈褋褌懈泻懈 邪泻褌懈胁薪芯褋褌懈</string>
+    <string name="activity_prefs_chart_max_heart_rate">袦邪泻褋懈屑邪谢褜薪褘泄 锌褍谢褜褋</string>
+    <string name="activity_prefs_chart_min_heart_rate">袦懈薪懈屑邪谢褜薪褘泄 锌褍谢褜褋</string>
+    <string name="pref_title_rtl">小锌褉邪胁邪 薪邪谢械胁芯</string>
+    <string name="pref_summary_rtl">袙泻谢褞褔懈褌械, 械褋谢懈 褍褋褌褉芯泄褋褌胁芯 薪械 芯褌芯斜褉邪卸邪械褌 锌懈褋褜屑芯 褋锌褉邪胁邪 薪邪谢械胁芯</string>
+    <string name="pref_rtl_max_line_length">袛谢懈薪邪 褋褌褉芯泻懈 锌褉懈 胁胁芯写械 褋锌褉邪胁邪 薪邪谢械胁芯</string>
+    <string name="pref_rtl_max_line_length_summary">袠蟹屑械薪懈褌褜 写谢懈薪褍 褋褌褉芯泻 胁 褌械泻褋褌械 褋 锌懈褋褜屑芯屑 褋锌褉邪胁邪 薪邪谢械胁芯</string>
+    <string name="pref_title_contextual_arabic">袣芯薪褌械泻褋褌薪褘械 褎芯褉屑褘 写谢褟 邪褉邪斜褋泻芯谐芯 褟蟹褘泻邪</string>
+    <string name="pref_summary_contextual_arabic">袙泻谢褞褔懈褌褜 锌芯写写械褉卸泻褍 泻芯薪褌械泻褋褌薪褘褏 褎芯褉屑 邪褉邪斜褋泻芯谐芯 褟蟹褘泻邪</string>
+    <string name="preferences_rtl_settings">袩芯写写械褉卸泻邪 胁胁芯写邪 褋锌褉邪胁邪 薪邪谢械胁芯</string>
+    <string name="prefs_disconnect_notification">校胁械写芯屑谢械薪懈械 芯斜 芯褌泻谢褞褔械薪懈懈</string>
+    <string name="activity_type_exercise">校锌褉邪卸薪械薪懈褟</string>
+    <string name="devicetype_casiogb6900">Casio GB-6900</string>
+    <string name="title_activity_notification_filter">肖懈谢褜褌褉 褍胁械写芯屑谢械薪懈泄</string>
+    <string name="filter_submode_at_least_one">袦懈薪懈屑褍屑 芯写薪芯 懈蟹 褋谢芯胁</string>
+    <string name="filter_submode_all">袙褋械 褋谢芯胁邪</string>
+    <string name="toast_notification_filter_words_empty_hint">袙胁械写懈褌械 褏芯褌褟 斜褘 芯写薪芯 褋谢芯胁芯</string>
+    <string name="debugactivity_really_factoryreset_title">小斜褉芯褋懈褌褜 写芯 蟹邪胁芯写褋泻懈褏 薪邪褋褌褉芯械泻\?</string>
+    <string name="filter_mode">袪械卸懈屑 褎懈谢褜褌褉邪褑懈懈</string>
+    <string name="save_configuration">小芯褏褉邪薪懈褌褜 泻芯薪褎懈谐褍褉邪褑懈褞</string>
+    <string name="appwidget_not_connected">袧械 锌芯写泻谢褞褔械薪芯, 斜褍写懈谢褜薪懈泻 薪械 褍褋褌邪薪芯胁谢械薪芯.</string>
+    <string name="mi2_prefs_button_press_broadcast_default_value" translatable="false">nodomain.freeyourgadget.gadgetbridge.ButtonPressed</string>
+    <string name="debugactivity_really_factoryreset">小斜褉芯褋 薪邪褋褌褉芯械泻 褍写邪谢懈褌 胁褋械 写邪薪薪褘械 褋 褝褌芯谐芯 褍褋褌褉芯泄褋褌胁邪 (械褋谢懈 锌芯写写械褉卸懈胁邪械褌褋褟). 校褋褌褉芯泄褋褌胁邪 Xiaomi 懈 Huami 锌芯屑械薪褟褞褌 MAC-邪写褉械褋 Bluetooth, 锌芯褝褌芯屑褍 胁 Gadgetbridge 芯薪懈 芯褌芯斜褉邪卸邪褞褌褋褟 泻邪泻 薪芯胁褘械 褍褋褌褉芯泄褋褌胁邪.</string>
+    <string name="zetime_date_format_1">袚袚/袦袦/袛袛</string>
+    <string name="zetime_date_format_2">袛袛/袦袦/袚袚</string>
+    <string name="zetime_date_format_3">袦袦/袛袛/袚袚</string>
+    <string name="zetime_prefs_inactivity_mo">袩芯薪械写械谢褜薪懈泻</string>
+    <string name="zetime_prefs_inactivity_tu">袙褌芯褉薪懈泻</string>
+    <string name="zetime_prefs_inactivity_we">小褉械写邪</string>
+    <string name="zetime_prefs_inactivity_th">效械褌胁械褉谐</string>
+    <string name="zetime_prefs_inactivity_fr">袩褟褌薪懈褑邪</string>
+    <string name="zetime_prefs_inactivity_sa">小褍斜斜芯褌邪</string>
+    <string name="zetime_prefs_inactivity_su">袙芯褋泻褉械褋械薪褜械</string>
+    <string name="pref_screen_notification_profile_missed_call">校胁械写芯屑谢械薪懈褟 芯 锌褉芯锌褍褖械薪薪褘褏 蟹胁芯薪泻邪褏</string>
+    <string name="pref_screen_notification_profile_calendar">校胁械写芯屑谢械薪懈褟 泻邪谢械薪写邪褉褟</string>
+    <string name="pref_screen_notification_profile_inactivity">校胁械写芯屑谢械薪懈褟 芯 斜械蟹写械泄褋褌胁懈懈</string>
+    <string name="pref_screen_notification_profile_low_power">袩褉械写褍锌褉械卸写械薪懈械 芯 薪懈蟹泻芯屑 蟹邪褉褟写械</string>
+    <string name="average">袙 褋褉械写薪械屑: %1$s</string>
+    <string name="pref_summary_expose_hr">袪邪蟹褉械褕邪械褌 写褉褍谐懈屑 锌褉懈谢芯卸械薪懈褟屑 写芯褋褌褍锌 泻 写邪褌褔懈泻褍 褋械褉写褑械斜懈械薪懈褟 锌褉懈 邪泻褌懈胁薪芯屑 褋芯械写懈薪械薪懈懈 褔械褉械蟹 Gadgetbridge</string>
+    <string name="pref_title_expose_hr">袛芯褋褌褍锌 泻 锌褍谢褜褋芯屑械褌褉褍</string>
+    <string name="pref_display_add_device_fab">袣薪芯锌泻邪 锌芯写泻谢褞褔械薪懈褟 薪芯胁芯谐芯 褍褋褌褉芯泄褋褌胁邪</string>
+    <string name="pref_display_add_device_fab_on">袨褌芯斜褉邪卸邪械褌褋褟 胁褋械谐写邪</string>
+    <string name="pref_display_add_device_fab_off">袨褌芯斜褉邪卸邪械褌褋褟 锌褉懈 芯褌褋褍褌褋褌胁懈懈 写芯斜邪胁谢械薪薪褘褏 褍褋褌褉芯泄褋褌胁</string>
+    <string name="prefs_button_single_press_action_selection_title">袪械邪泻褑懈褟 薪邪 小芯斜褘褌懈械 1</string>
+    <string name="prefs_button_double_press_action_selection_title">袪械邪泻褑懈褟 薪邪 小芯斜褘褌懈械 2</string>
+    <string name="prefs_button_triple_press_action_selection_title">袪械邪泻褑懈褟 薪邪 小芯斜褘褌懈械 3</string>
+    <string name="prefs_button_variable_actions">袛械褌邪谢褜薪邪褟 薪邪褋褌褉芯泄泻邪 褉械邪泻褑懈懈 薪邪 薪邪卸邪褌懈械 泻薪芯锌泻懈</string>
+    <string name="prefs_button_long_press_action_selection_title">袪械邪泻褑懈褟 薪邪 写芯谢谐芯械 薪邪卸邪褌懈械</string>
+    <string name="edittext_notification_filter_words_hint">袙胁械写懈褌械 薪褍卸薪褘械 褋谢芯胁邪 锌芯 芯写薪芯屑褍 胁 褋褌褉芯泻械</string>
+    <string name="toast_notification_filter_saved_successfully">肖懈谢褜褌褉 褍胁械写芯屑谢械薪懈泄 褋芯褏褉邪薪褢薪</string>
+    <string name="filter_mode_none">袧械 褎懈谢褜褌褉芯胁邪褌褜</string>
+    <string name="filter_mode_whitelist">袩芯泻邪蟹褘胁邪褌褜, 泻芯谐写邪 褋谢芯胁邪 薪邪褏芯写褟褌褋褟 胁 褋锌懈褋泻械</string>
+    <string name="filter_mode_blacklist">袘谢芯泻懈褉芯胁邪褌褜, 泻芯谐写邪 懈蟹 褋锌懈褋泻邪 褋锌懈褋泻械</string>
+    <string name="mode_configuration">袪械卸懈屑 泻芯薪褎懈谐褍褉邪褑懈懈</string>
+    <string name="zetime_title_settings">袧邪褋褌褉芯泄泻懈 ZeTime</string>
+    <string name="zetime_title_heartrate">袧邪褋褌褉芯泄泻懈 锌褍谢褜褋邪</string>
+    <string name="zetime_title_screentime">袩褉芯写芯谢卸懈褌械谢褜薪芯褋褌褜 芯褌芯斜褉邪卸械薪懈褟 胁 褋械泻褍薪写邪褏</string>
+    <string name="zetime_title_heart_rate_alarm">袩褍谢褜褋芯胁芯泄 褋懈谐薪邪谢</string>
+    <string name="zetime_title_heart_rate_alarm_summary">效邪褋褘 锌褉械写褍锌褉械写褟褌 胁邪褋, 泻芯谐写邪 褔邪褋褌芯褌邪 锌褍谢褜褋邪 锌褉械胁褘褋懈褌 写芯锌褍褋褌懈屑褘械 蟹薪邪褔械薪懈褟.</string>
+    <string name="zetime_heart_rate_alarm_enable">袙泻谢褞褔懈褌褜 锌褍谢褜褋芯胁芯泄 褋懈谐薪邪谢</string>
+    <string name="activity_prefs_alarm_max_heart_rate">袦邪泻褋懈屑邪谢褜薪褘泄 锌褍谢褜褋</string>
+    <string name="activity_prefs_alarm_min_heart_rate">袦懈薪懈屑邪谢褜薪褘泄 锌褍谢褜褋</string>
+    <string name="zetime_analog_mode">袗薪邪谢芯谐芯胁褘泄 褉械卸懈屑</string>
+    <string name="zetime_analog_mode_hands">孝芯谢褜泻芯 褉褍泻懈</string>
+    <string name="zetime_analog_mode_handsandsteps">袪褍泻懈 懈 褕邪谐懈</string>
+    <string name="zetime_activity_tracking">袨褌褋谢械卸懈胁邪薪懈械 邪泻褌懈胁薪芯褋褌懈</string>
+    <string name="zetime_activity_tracking_summary">袙泻谢褞褔械薪懈械 芯褌褋谢械卸懈胁邪薪懈褟 邪泻褌懈胁薪芯褋褌懈, 斜褍写械褌 褋褔懈褌邪褌褜 胁邪褕懈 褕邪谐懈 懈 褌邪泻 写邪谢械械.</string>
+    <string name="zetime_handmove_display">袛胁懈卸械薪懈械 褉褍泻懈</string>
+    <string name="zetime_handmove_display_summary">袩芯胁械褉薪懈褌械 蟹邪锌褟褋褌褜械, 褔褌芯斜褘 邪泻褌懈胁懈褉芯胁邪褌褜 懈谢懈 胁褘泻谢褞褔懈褌褜 褝泻褉邪薪.</string>
+    <string name="zetime_calories_type">孝懈锌 泻邪谢芯褉懈泄</string>
+    <string name="zetime_calories_type_active">孝芯谢褜泻芯 邪泻褌懈胁薪褘械 褋卸懈谐邪械屑褘械 泻邪谢芯褉懈懈</string>
+    <string name="zetime_calories_type_all">袗泻褌懈胁薪褘械 懈 薪械邪泻褌懈胁薪褘械 褋卸懈谐邪械屑褘械 泻邪谢芯褉懈懈</string>
+    <string name="zetime_date_format">肖芯褉屑邪褌 写邪褌褘</string>
+    <string name="zetime_prefs_inactivity_repetitions">袩芯胁褌芯褉械薪懈褟</string>
+    <string name="zetime_title_alarm_signaling">校褋褌邪薪芯胁懈褌褜 褌懈锌 褋懈谐薪邪谢懈蟹邪褑懈懈 写谢褟 斜褍写懈谢褜薪懈泻邪</string>
+    <string name="zetime_signaling_none">孝懈褕懈薪邪</string>
+    <string name="zetime_signaling_vibrate">袧械锌褉械褉褘胁薪邪褟 胁懈斜褉邪褑懈褟</string>
+    <string name="zetime_signaling_beep">袧械锌褉械褉褘胁薪褘泄 蟹胁褍泻芯胁芯泄 褋懈谐薪邪谢</string>
+    <string name="zetime_signaling_vibrate_beep">袧械锌褉械褉褘胁薪邪褟 胁懈斜褉邪褑懈褟 懈 蟹胁褍泻芯胁芯泄 褋懈谐薪邪谢</string>
+    <string name="zetime_signaling_vibrate_once">袙懈斜褉懈褉芯胁邪褌褜 械写懈薪芯卸写褘</string>
+    <string name="zetime_signaling_vibrate_twice">袙懈斜褉懈褉芯胁邪褌褜 写胁邪卸写褘</string>
+    <string name="zetime_signaling_beep_once">小懈谐薪邪谢懈蟹懈褉芯胁邪褌褜 械写懈薪芯卸写褘</string>
+    <string name="zetime_signaling_beep_twice">小懈谐薪邪谢懈蟹懈褉芯胁邪褌褜 写胁邪卸写褘</string>
+    <string name="zetime_signaling_vibrate_beep_once">袙懈斜褉懈褉芯胁邪褌褜 懈 褋懈谐薪邪谢懈蟹懈褉芯胁邪褌褜 械写懈薪芯卸写褘</string>
+    <string name="pref_screen_notification_profile_anti_loss">袩褉械写褍锌褉械卸写械薪懈械 芯 锌芯褌械褉褟褏</string>
+    <string name="interval_fifteen_minutes">泻邪卸写褘械 15 屑懈薪褍褌</string>
+    <string name="interval_forty_five_minutes">泻邪卸写褘械 45 屑懈薪褍褌</string>
+    <string name="activity_prefs_calories_burnt">袝卸械写薪械胁薪邪褟 褑械谢褜: 褋芯卸卸械薪薪褘械 泻邪谢芯褉懈懈</string>
+    <string name="activity_prefs_distance_meters">袝卸械写薪械胁薪邪褟 褑械谢褜: 写懈褋褌邪薪褑懈褟 胁 屑械褌褉邪褏</string>
+    <string name="activity_prefs_activetime_minutes">袝卸械写薪械胁薪邪褟 褑械谢褜: 胁褉械屑褟 邪泻褌懈胁薪芯褋褌懈 胁 屑懈薪褍褌邪褏</string>
+    <string name="devicetype_miscale2">Mi Scale 2</string>
+    <string name="pref_title_support_voip_calls">袙泻谢褞褔懈褌褜 VoIP-蟹胁芯薪泻懈</string>
+    <string name="title_activity_device_specific_settings">小锌械褑懈褎懈褔薪褘械 写谢褟 褍褋褌褉芯泄褋褌胁邪 薪邪褋褌褉芯泄泻懈</string>
+    <string name="pref_title_authkey">袣谢褞褔 邪胁褌芯褉懈蟹邪褑懈懈</string>
+    <string name="pref_summary_authkey">袠蟹屑械薪懈褌械 泻谢褞褔 邪胁褌芯褉懈蟹邪褑懈懈 薪邪 芯斜褖懈泄 泻谢褞褔 薪邪 胁褋械褏 胁邪褕懈褏 Android-褍褋褌褉芯泄褋褌胁邪褏, 褋 泻芯褌芯褉褘褏 胁褘 褏芯褌械谢懈 斜褘 锌芯写泻谢褞褔懈褌褜褋褟. 袩褉械写褘写褍褖懈泄 泻谢褞褔 锌芯 褍屑芯谢褔邪薪懈褞 写谢褟 胁褋械褏 褍褋褌褉芯泄褋褌胁 0123456789@ABCDE</string>
+    <string name="devicetype_bfh16">BFH-16</string>
+    <string name="fw_upgrade_notice_amazfitcor2">袙褘 褋芯斜懈褉邪械褌械褋褜 褍褋褌邪薪芯胁懈褌褜 锌褉芯褕懈胁泻褍 %s 薪邪 胁邪褕 Amazfit Cor 2.
+\n
+\n袘褍写褜褌械 胁薪懈屑邪褌械谢褜薪褘: 胁薪邪褔邪谢械 褍褋褌邪薪芯胁懈褌械 褎邪泄谢 .fw, 蟹邪褌械屑 .res. 袩芯褋谢械 褍褋褌邪薪芯胁泻懈 .fw 胁邪褕 斜褉邪褋谢械褌 斜褍写械褌 锌械褉械蟹邪谐褉褍卸械薪.
+\n
+\n袩褉懈屑械褔邪薪懈械: 械褋谢懈 胁械褉褋懈褟 褎邪泄谢邪 .res 褋芯胁锌邪写邪械褌 褋 褍褋褌邪薪芯胁谢械薪薪芯泄, 械谐芯 薪械 薪褍卸薪芯 锌械褉械褍褋褌邪薪邪胁谢懈胁邪褌褜.
+\n
+\n袙蝎 袛袝袡小孝袙校袝孝袝 袧袗 小袙袨袡 小孝袪袗啸 袠 袪袠小袣!
+\n
+\n袩袨袥袧袨小孝鞋挟 袧袝 袩袪袨孝袝小孝袠袪袨袙袗袧袨. 袙袨袟袦袨袞袧袨, 袧校袞袧袨 袩袪袨楔袠孝鞋 袩袪袨楔袠袙袣校 BEATS_W, 袝小袥袠 袠袦携 袙袗楔袝袚袨 校小孝袪袨袡小孝袙袗 \"Amazfit Band 2\"</string>
+    <string name="dutch">袚芯谢谢邪薪写褋泻懈泄</string>
+    <string name="turkish">孝褍褉械褑泻懈泄</string>
+    <string name="ukrainian">校泻褉邪懈薪褋泻懈泄</string>
+    <string name="arabic">袗褉邪斜褋泻懈泄</string>
+    <string name="indonesian">袠薪写芯薪械蟹懈泄褋泻懈泄</string>
+    <string name="thai">孝邪泄褋泻懈泄</string>
+    <string name="vietnamese">袙褜械褌薪邪屑褋泻懈泄</string>
+    <string name="portuguese">袩芯褉褌褍谐邪谢褜褋泻懈泄</string>
+    <string name="devicetype_amazfit_cor2">Amazfit Cor 2</string>
+    <string name="devicetype_miband4">Mi Band 4</string>
+    <string name="fw_upgrade_notice_miband4">袙褘 褋芯斜懈褉邪械褌械褋褜 褍褋褌邪薪芯胁懈褌褜 锌褉芯褕懈胁泻褍 %s 薪邪 胁邪褕 Mi Band 4.
+\n
+\n袘褍写褜褌械 胁薪懈屑邪褌械谢褜薪褘: 褋薪邪褔邪谢邪 褍褋褌邪薪芯胁懈褌械 褎邪泄谢 .fw, 蟹邪褌械屑 .res. 袩芯褋谢械 褍褋褌邪薪芯胁泻懈 .fw 胁邪褕 斜褉邪褋谢械褌 斜褍写械褌 锌械褉械蟹邪谐褉褍卸械薪.
+\n
+\n袩褉懈屑械褔邪薪懈械: 械褋谢懈 胁械褉褋懈褟 褎邪泄谢邪 .res 褋芯胁锌邪写邪械褌 褋 褍褋褌邪薪芯胁谢械薪薪芯泄, 械谐芯 薪械 薪褍卸薪芯 锌械褉械褍褋褌邪薪邪胁谢懈胁邪褌褜.
+\n
+\n袙蝎 袛袝袡小孝袙校袝孝袝 袧袗 小袙袨袡 小孝袪袗啸 袠 袪袠小袣!</string>
+    <string name="prefs_hr_alarm_activity">小懈谐薪邪谢 锌褍谢褜褋芯屑械褌褉邪 胁 褌械褔械薪懈械 蟹邪薪褟褌懈褟 褋锌芯褉褌芯屑</string>
+    <string name="prefs_hr_alarm_low">袧懈卸薪懈泄 锌褉械写械谢</string>
+    <string name="prefs_hr_alarm_high">袙械褉褏薪懈泄 锌褉械写械谢</string>
+    <string name="pref_header_charts">袧邪褋褌褉芯泄泻懈 袚褉邪褎懈泻芯胁</string>
+    <string name="pref_title_charts_average">袩芯泻邪蟹褘胁邪褌褜 褋褉械写薪懈械 蟹薪邪褔械薪懈褟 薪邪 谐褉邪褎懈泻邪褏</string>
+    <string name="pref_title_charts_range">袛懈邪锌邪蟹芯薪 谐褉邪褎懈泻芯胁</string>
+    <string name="pref_charts_range_on">袛懈邪锌邪蟹芯薪 谐褉邪褎懈泻芯胁 胁 褉邪屑泻邪褏 屑械褋褟褑邪</string>
+    <string name="pref_charts_range_off">袛懈邪锌邪蟹芯薪 谐褉邪褎懈泻芯胁 胁 褉邪屑泻邪褏 薪械写械谢懈</string>
+    <string name="weekstepschart_steps_a_month">袗泻褌懈胁薪芯褋褌褜 蟹邪 屑械褋褟褑</string>
+    <string name="weeksleepchart_sleep_a_month">小芯薪 蟹邪 屑械褋褟褑</string>
+    <string name="devicetype_mijia_lywsd02">Mijia Smart Clock</string>
+    <string name="menuitem_nfc">NFC</string>
+    <string name="pref_title_use_custom_font">袩芯谢褜蟹芯胁邪褌械谢褜褋泻懈泄 褕褉懈褎褌</string>
+    <string name="pref_summary_use_custom_font">袙泻谢褞褔懈褌械 锌褉懈 薪邪谢懈褔懈懈 薪邪 褍褋褌褉芯泄褋褌胁械 锌芯谢褜蟹芯胁邪褌械谢褜褋泻芯谐芯 褕褉懈褎褌邪 写谢褟 锌芯写写械褉卸泻懈 褝屑芯写蟹懈</string>
+    <string name="activity_db_management_autoexport_explanation">袦械褋褌芯锌芯谢芯卸械薪懈械 邪胁褌芯-褝泻褋锌芯褉褌邪 斜邪蟹褘 写邪薪薪褘褏 褍褋褌邪薪芯胁谢械薪芯:</string>
+    <string name="activity_db_management_autoexport_label">袗胁褌芯-褝泻褋锌芯褉褌</string>
+    <string name="activity_DB_ExportButton">协泻褋锌芯褉褌 写邪薪薪褘褏</string>
+    <string name="activity_DB_import_button">袠屑锌芯褉褌 写邪薪薪褘褏</string>
+    <string name="activity_DB_test_export_button">袟邪锌褍褋褌懈褌褜 袗胁褌芯-褝泻褋锌芯褉褌 褋械泄褔邪褋</string>
+    <string name="activity_DB_test_export_message">协泻褋锌芯褉褌懈褉芯胁邪薪懈械 斜邪蟹褘 写邪薪薪褘褏鈥�</string>
+    <string name="activity_DB_delete_legacy_button">校写邪谢懈褌褜 褋褌邪褉褍褞 斜邪蟹褍 写邪薪薪褘褏</string>
+    <string name="activity_DB_empty_button">袨褔懈褋褌懈褌褜 斜邪蟹褍 写邪薪薪褘褏</string>
+    <string name="activity_db_management_empty_DB">小斜褉芯褋 斜邪蟹褘 写邪薪薪褘褏</string>
+    <string name="activity_db_management_exportimport_label">协泻褋锌芯褉褌 懈 袠屑锌芯褉褌</string>
+    <string name="activity_db_management_empty_db_warning">袙薪懈屑邪薪懈械! 袧邪卸邪胁 褝褌褍 泻薪芯锌泻褍, 胁褘 褋芯褌褉械褌械 胁邪褕褍 斜邪蟹褍 写邪薪薪褘褏 懈 薪邪褔薪械褌械 褋 薪褍谢褟.</string>
+    <string name="appwidget_sleep_alarm_widget_label">小懈谐薪邪谢懈蟹邪褑懈褟 褋薪邪</string>
+    <string name="widget_listing_label">小褌邪褌褍褋 懈 斜褍写懈谢褜薪懈泻懈</string>
+    <string name="widget_set_alarm_after">校褋褌邪薪芯胁懈褌褜 褋懈谐薪邪谢 锌芯褋谢械:</string>
+    <string name="widget_5_minutes">5 屑懈薪褍褌</string>
+    <string name="widget_10_minutes">10 屑懈薪褍褌</string>
+    <string name="widget_20_minutes">20 屑懈薪褍褌</string>
+    <string name="widget_1_hour">1 褔邪褋</string>
+    <plurals name="widget_alarm_target_hours">
+        <item quantity="one">%d 褔邪褋</item>
+        <item quantity="few">%d 褔邪褋邪</item>
+        <item quantity="many">%d 褔邪褋芯胁</item>
+        <item quantity="other">%d 褔邪褋芯胁</item>
+    </plurals>
+    <string name="activity_error_no_app_for_gpx">袛谢褟 锌褉芯褋屑芯褌褉邪 褌褉邪褋褋懈褉芯胁泻懈 邪泻褌懈胁薪芯褋褌懈 褍褋褌邪薪芯胁懈褌械 锌褉懈谢芯卸械薪懈械, 泻芯褌芯褉芯械 屑芯卸械褌 褉邪斜芯褌邪褌褜 褋 GPX 褎邪泄谢邪屑懈.</string>
+    <string name="preferences_makibes_hr3_settings">袧邪褋褌褉芯泄泻懈 Makibes HR3</string>
+    <string name="devicetype_makibes_hr3">Makibes HR3</string>
+    <string name="devicetype_amazfit_bip_lite">Amazfit Bip Lite</string>
+    <string name="prefs_find_phone">袧邪泄褌懈 褌械谢械褎芯薪</string>
+    <string name="prefs_enable_find_phone">袙泻谢褞褔懈褌褜 \'袧邪泄褌懈 褌械谢械褎芯薪\'</string>
+    <string name="prefs_find_phone_summary">袠褋锌芯谢褜蟹芯胁邪褌褜 胁邪褕 斜褉邪褋谢械褌 写谢褟 锌褉芯懈谐褉褘胁邪薪懈褟 褉懈薪谐褌芯薪芯胁.</string>
+    <string name="prefs_find_phone_duration">袩褉芯写芯谢卸懈褌械谢褜薪芯褋褌褜 蟹胁芯薪泻邪 胁 褋械泻褍薪写邪褏</string>
+    <string name="maximum_duration">袩褉芯写芯谢卸懈褌械谢褜薪芯褋褌褜</string>
+    <string name="discovery_need_to_enter_authkey">袛谢褟 褝褌芯谐芯 褍褋褌褉芯泄褋褌胁邪 褌褉械斜褍械褌褋褟 泻谢褞褔 邪胁褌芯褉懈蟹邪褑懈懈. 袠褋锌芯谢褜蟹褍泄褌械 写谢懈褌械谢褜薪芯械 薪邪卸邪褌懈械 写谢褟 胁褏芯写邪 胁 褍褋褌褉芯泄褋褌胁芯. 效懈褌邪泄褌械 芯斜 褝褌芯屑 胁 袙懈泻懈.</string>
+    <string name="fw_upgrade_notice_amazfitbip_lite">袙褘 褋芯斜懈褉邪械褌械褋褜 褍褋褌邪薪芯胁懈褌褜 锌褉芯褕懈胁泻褍 %s 薪邪 胁邪褕 Amazfit Bip Lite.
+\n
+\n袘褍写褜褌械 胁薪懈屑邪褌械谢褜薪褘: 褋薪邪褔邪谢邪 褍褋褌邪薪芯胁懈褌械 褎邪泄谢 .fw, 蟹邪褌械屑 .res. 袩芯褋谢械 褍褋褌邪薪芯胁泻懈 .fw 胁邪褕懈 褔邪褋褘 斜褍写褍褌 锌械褉械蟹邪谐褉褍卸械薪褘.
+\n
+\n袩褉懈屑械褔邪薪懈械: 械褋谢懈 褎邪泄谢 懈 .res 褌邪泻芯泄 卸械, 泻邪泻 胁 褌械泻褍褖械泄 胁械褉褋懈懈, 械谐芯 薪械 薪褍卸薪芯 锌械褉械褍褋褌邪薪邪胁谢懈胁邪褌褜.
+\n
+\n袙蝎 袛袝袡小孝袙校袝孝袝 袧袗 小袙袨袡 小孝袪袗啸 袠 袪袠小袣!</string>
+    <string name="devicetype_amazfit_gtr">Amazfit GTR</string>
+    <string name="fw_upgrade_notice_amazfitgtr">袙褘 褋芯斜懈褉邪械褌械褋褜 褍褋褌邪薪芯胁懈褌褜 锌褉芯褕懈胁泻褍 %s 薪邪 胁邪褕 Amazfit GTR.
+\n
+\n袘褍写褜褌械 胁薪懈屑邪褌械谢褜薪褘: 胁薪邪褔邪谢械 褍褋褌邪薪芯胁懈褌械 褎邪泄谢 .fw, 蟹邪褌械屑 .res. 袩芯褋谢械 褍褋褌邪薪芯胁泻懈 .fw 胁邪褕懈 褔邪褋褘 斜褍写褍褌 锌械褉械蟹邪谐褉褍卸械薪褘.
+\n
+\n袩褉懈屑械褔邪薪懈械: 械褋谢懈 胁械褉褋懈褟 褎邪泄谢邪 .res 褋芯胁锌邪写邪械褌 褋 褍褋褌邪薪芯胁谢械薪薪芯泄, 械谐芯 薪械 薪褍卸薪芯 锌械褉械褍褋褌邪薪邪胁谢懈胁邪褌褜.
+\n
+\n袙蝎 袛袝袡小孝袙校袝孝袝 袧袗 小袙袨袡 小孝袪袗啸 袠 袪袠小袣!</string>
+    <string name="pref_chart_heartrate_color_red">袣褉邪褋薪褘泄</string>
+    <string name="pref_chart_heartrate_color_orange">袨褉邪薪卸械胁褘泄</string>
+    <string name="pref_title_chart_heartrate_color">笑胁械褌 锌褍谢褜褋邪</string>
+    <string name="pref_title_chart_sleep_rolling_24_hour">袛懈邪锌邪蟹芯薪 褋薪邪</string>
+    <string name="pref_chart_sleep_rolling_24_on">袩芯褋谢械写薪懈械 24 褔邪褋邪</string>
+    <string name="pref_chart_sleep_rolling_24_off">小 锌芯谢褍写薪褟 写芯 锌芯谢褍写薪褟</string>
+    <string name="devicetype_amazfit_gts">Amazfit GTS</string>
+    <string name="fw_upgrade_notice_amazfitgts">袙褘 褋芯斜懈褉邪械褌械褋褜 褍褋褌邪薪芯胁懈褌褜 锌褉芯褕懈胁泻褍 %s 薪邪 胁邪褕 Amazfit GTS.
+\n
+\n袘褍写褜褌械 胁薪懈屑邪褌械谢褜薪褘: 褋锌械褉胁邪 褍褋褌邪薪芯胁懈褌械 褎邪泄谢 .fw, 蟹邪褌械屑 .res. 袩芯褋谢械 褍褋褌邪薪芯胁泻懈 .fw 胁邪褕懈 褔邪褋褘 斜褍写褍褌 锌械褉械蟹邪谐褉褍卸械薪褘.
+\n
+\n袩褉懈屑械褔邪薪懈械: 械褋谢懈 胁械褉褋懈褟 褎邪泄谢邪 .res 褋芯胁锌邪写邪械褌 褋 褍褋褌邪薪芯胁谢械薪薪芯泄, 械谐芯 薪械 薪褍卸薪芯 锌械褉械褍褋褌邪薪邪胁谢懈胁邪褌褜.
+\n
+\n袙蝎 袛袝袡小孝袙校袝孝袝 袧袗 小袙袨袡 小孝袪袗啸 袠 袪袠小袣!</string>
+    <string name="devicetype_qhybrid">Fossil Q Hybrid</string>
+    <string name="preferences_qhybrid_settings">袧邪褋褌褉芯泄泻懈 Q Hybrid</string>
+    <string name="watch_not_connected">效邪褋褘 薪械 锌芯写褋芯械写懈薪械薪褘</string>
+    <string name="qhybrid_vibration_strength">褋懈谢邪 胁懈斜褉邪褑懈懈:</string>
+    <string name="qhybrid_goal_in_steps">笑械谢褜 胁 褕邪谐邪褏</string>
+    <string name="qhybrid_time_shift">褋写胁懈谐 锌芯 胁褉械屑械薪懈</string>
+    <string name="qhybrid_second_timezone_offset_relative_to_utc">褋屑械褖械薪懈械 胁褌芯褉芯谐芯 褔邪褋芯胁芯谐芯 锌芯褟褋邪 芯褌薪芯褋懈褌械谢褜薪芯 UTC</string>
+    <string name="qhybrid_overwrite_buttons">泻薪芯锌泻懈 锌械褉械蟹邪锌懈褋懈</string>
+    <string name="qhybrid_use_activity_hand_as_notification_counter">懈褋锌芯谢褜蟹芯胁邪褌褜 邪泻褌懈胁薪褍褞 褉褍泻褍 胁 泻邪褔械褋褌胁械 褋褔械褌褔懈泻邪 褍胁械写芯屑谢械薪懈泄</string>
+    <string name="qhybrid_prompt_million_steps">袩芯卸邪谢褍泄褋褌邪, 褍褋褌邪薪芯胁懈褌械 褋褔械褌褔懈泻 褕邪谐芯胁 薪邪 屑懈谢谢懈芯薪, 褔褌芯斜褘 邪泻褌懈胁懈褉芯胁邪褌褜 褝褌芯.</string>
+    <string name="qhybrid_buttons_overwrite_success">袣薪芯锌泻懈 锌械褉械蟹邪锌懈褋邪薪褘</string>
+    <string name="qhybrid_buttons_overwrite_error">袨褕懈斜泻邪 锌械褉械蟹邪锌懈褋懈 泻薪芯锌芯泻</string>
+    <string name="qhybrid_offset_timezone">褋屑械褖械薪懈械 褔邪褋芯胁芯谐芯 锌芯褟褋邪 薪邪</string>
+    <string name="qhybrid_changes_delay_prompt">懈蟹屑械薪械薪懈褟 屑芯谐褍褌 蟹邪薪褟褌褜 薪械褋泻芯谢褜泻芯 褋械泻褍薪写鈥�</string>
+    <string name="qhybrid_offset_time_by">褋屑械褖械薪懈械 胁褉械屑械薪懈 薪邪</string>
+    <string name="pref_disable_new_ble_scanning">袨褌泻谢褞褔懈褌褜 薪芯胁芯械 BLE 褋泻邪薪懈褉芯胁邪薪懈械</string>
+    <string name="pref_summary_disable_new_ble_scanning">袙泻谢褞褔懈褌械, 械褋谢懈 褍褋褌褉芯泄褋褌胁芯 薪械 胁懈写薪芯 泻邪泻 写芯褋褌褍锌薪芯械</string>
+    <string name="devicetype_banglejs">Bangle.js</string>
+    <string name="devicetype_y5">Y5</string>
+    <string name="alarm_snooze">袨褌泻谢邪写褘胁邪薪懈械 斜褍写懈谢褜薪懈泻邪</string>
+    <string name="error_no_location_access">袛芯褋褌褍锌 泻 屑械褋褌芯锌芯谢芯卸械薪懈褞 写芯谢卸械薪 斜褘褌褜 褉邪蟹褉械褕械薪 懈 胁泻谢褞褔械薪 写谢褟 泻芯褉褉械泻褌薪芯泄 褉邪斜芯褌褘 锌芯懈褋泻邪</string>
+    <string name="devicetype_itag">iTag</string>
+    <string name="pref_title_allow_high_mtu">袪邪蟹褉械褕懈褌褜 胁褘褋芯泻懈泄 MTU</string>
+    <string name="pref_summary_allow_high_mtu">校胁械谢懈褔懈胁邪械褌 褋泻芯褉芯褋褌褜 锌械褉械写邪褔懈 写邪薪薪褘褏, 薪芯 屑芯卸械褌 薪械 褉邪斜芯褌邪褌褜 薪邪 薪械泻芯褌芯褉褘褏 Android-褍褋褌褉芯泄褋褌胁邪褏.</string>
+    <string name="pref_summary_sync_calendar">袩芯谢褍褔械薪懈械 芯锌芯胁械褖械薪懈泄 泻邪谢械薪写邪褉褟 写邪卸械 锌褉懈 芯褌褋褍褌褋褌胁懈懈 锌芯写泻谢褞褔械薪懈褟</string>
+    <string name="pref_title_sync_caldendar">小懈薪褏褉芯薪懈蟹邪褑懈褟 褋芯斜褘褌懈泄 泻邪谢械薪写邪褉褟</string>
+    <string name="find_my_phone_notification">袧邪泄褌懈 褌械谢械褎芯薪</string>
+    <string name="pref_title_custom_deviceicon">袩芯泻邪蟹褘胁邪褌褜 褋芯斜褋褌胁械薪薪褍褞 懈泻芯薪泻褍 褍褋褌褉芯泄褋褌胁邪</string>
+    <string name="pref_summary_custom_deviceicon">袩褉懈 锌芯写泻谢褞褔械薪懈懈 锌芯泻邪蟹褘胁邪褌褜 胁 褍胁械写芯屑谢械薪懈懈 褋芯斜褋褌胁械薪薪褍褞 懈泻芯薪泻褍 褍褋褌褉芯泄褋褌胁邪 胁屑械褋褌芯 懈泻芯薪泻懈 Gadgetbridge</string>
+    <string name="pref_theme_system">小懈褋褌械屑薪邪褟</string>
+    <string name="error_background_service_reason">袧械 褍写邪谢芯褋褜 蟹邪锌褍褋褌懈褌褜 褎芯薪芯胁褍褞 褋谢褍卸斜褍 懈蟹-蟹邪 懈褋泻谢褞褔械薪懈褟. 袧邪卸屑懈褌械 写谢褟 锌芯谢褍褔械薪懈褟 锌芯写褉芯斜薪芯褋褌械泄.\n\n袨褕懈斜泻邪:</string>
+    <string name="error_background_service_reason_truncated">袧械 褍写邪械褌褋褟 蟹邪锌褍褋褌懈褌褜 褎芯薪芯胁褍褞 褋谢褍卸斜褍 懈蟹-蟹邪鈥�</string>
+    <string name="error_background_service">袧械 褍写邪谢芯褋褜 蟹邪锌褍褋褌懈褌褜 褎芯薪芯胁褍褞 褋谢褍卸斜褍</string>
+    <string name="error_version_check_extreme_caution">袙袧袠袦袗袧袠袝: 袨褕懈斜泻邪 锌褉懈 锌褉芯胁械褉泻械 懈薪褎芯褉屑邪褑懈懈 芯 胁械褉褋懈懈! 袧械 锌褉芯写芯谢卸邪泄褌械! 袨斜薪邪褉褍卸械薪薪芯械 懈屑褟 胁械褉褋懈懈 \"%s\"</string>
+    <string name="permission_granting_mandatory">袙褋械 褝褌懈 褉邪蟹褉械褕械薪懈褟 薪械芯斜褏芯写懈屑褘, 懈 械褋谢懈 芯薪懈 薪械 斜褍写褍褌 胁褘写邪薪褘, 锌褉懈谢芯卸械薪懈械 屑芯卸械褌 褉邪斜芯褌邪褌褜 薪械褋褌邪斜懈谢褜薪芯</string>
+    <string name="about_additional_contributions">袘芯谢褜褕芯械 褋锌邪褋懈斜芯 胁褋械屑 褍褔邪褋褌薪懈泻邪屑, 薪械 胁薪械褋褢薪薪褘屑 胁 褋锌懈褋芯泻, 蟹邪 锌褉械写芯褋褌邪胁谢械薪薪褘泄 泻芯写, 锌械褉械胁芯写褘, 锌芯写写械褉卸泻褍, 懈写械懈, 屑芯褌懈胁邪褑懈褞, 芯褌褔械褌褘 芯斜 芯褕懈斜泻邪褏, 写械薪褜谐懈鈥� 鉁�</string>
+    <string name="about_additional_device_support">袛芯锌芯谢薪懈褌械谢褜薪邪褟 锌芯写写械褉卸泻邪 褍褋褌褉芯泄褋褌胁</string>
+    <string name="about_contributors">小芯邪胁褌芯褉褘</string>
+    <string name="about_core_team_title">袨褋薪芯胁薪邪褟 泻芯屑邪薪写邪 (胁 锌芯褉褟写泻械 锌芯褋褌褍锌谢械薪懈褟 锌械褉胁芯谐芯 泻芯写邪)</string>
+    <string name="about_description_generic">\"袘械蟹芯斜谢邪褔薪邪褟\" 褋胁芯斜芯写薪邪褟 邪谢褜褌械褉薪邪褌懈胁邪 蟹邪泻褉褘褌褘屑 锌褉懈谢芯卸械薪懈褟屑 芯褌 锌褉芯懈蟹胁芯写懈褌械谢械泄 谐邪写卸械褌芯胁.</string>
+    <string name="about_activity_title_generic">袨 Gadgetbridge</string>
+    <string name="about_title">袨 锌褉芯谐褉邪屑屑械</string>
+    <string name="hr_widget_last_notification">袩芯褋谢械写薪械械 褍胁械写芯屑谢械薪懈械</string>
+    <string name="pref_qhybrid_save_raw_activity_files">小芯褏褉邪薪褟褌褜 薪械芯斜褉邪斜芯褌邪薪薪褘械 褎邪泄谢褘 邪泻褌懈胁薪芯褋褌懈</string>
+    <string name="pref_qhybrid_title_widget_draw_circles">袪懈褋芯胁邪褌褜 泻褉褍谐懈 胁懈写卸械褌芯胁</string>
+    <string name="hr_appname_commute">袩芯械蟹写泻懈</string>
+    <string name="hr_appname_stopwatch">小械泻褍薪写芯屑械褉</string>
+    <string name="hr_appname_workout">孝褉械薪懈褉芯胁泻邪</string>
+    <string name="hr_appname_wellness">袘谢邪谐芯锌芯谢褍褔懈械</string>
+    <string name="hr_widget_nothing">袧懈褔械谐芯</string>
+    <string name="hr_widget_weather">袩芯谐芯写邪</string>
+    <string name="hr_widget_battery">袘邪褌邪褉械褟</string>
+    <string name="hr_widget_calories">袣邪谢芯褉懈懈</string>
+    <string name="hr_widget_active_minutes">袦懈薪褍褌褘 邪泻褌懈胁薪芯褋褌懈</string>
+    <string name="hr_widget_date">袛邪褌邪</string>
+    <string name="hr_widget_steps">楔邪谐懈</string>
+    <string name="hr_widget_heart_rate">袩褍谢褜褋</string>
+    <string name="menuitem_worldclock">袦懈褉芯胁芯械 袙褉械屑褟</string>
+    <string name="menuitem_unknown">袧械懈蟹胁械褋褌薪芯</string>
+    <string name="menuitem_workout">孝褉械薪懈褉芯胁泻邪</string>
+    <string name="menuitem_eventreminder">袧邪锌芯屑懈薪邪薪懈械 芯 褋芯斜褘褌懈懈</string>
+    <string name="menuitem_hr">袩褍谢褜褋</string>
+    <string name="menuitem_pai">PAI</string>
+    <string name="menuitem_stress">小褌褉械褋褋</string>
+    <string name="menuitem_cycles">袨褌褋谢械卸懈胁邪薪懈械 褑懈泻谢邪</string>
+    <string name="menuitem_breathing">袛褘褏邪薪懈械</string>
+    <string name="notification_channel_high_priority_name">袙褘褋芯泻懈泄 锌褉懈芯褉懈褌械褌</string>
+    <string name="devicetype_watchxplus">Watch X Plus</string>
+    <string name="devicetype_watchx">Watch X</string>
+    <string name="devicetype_amazfit_bips">Amazfit Bip S</string>
+    <string name="devicetype_amazfit_trex">Amazfit T-Rex</string>
+    <string name="devicetype_miband5">Mi Band 5</string>
+    <string name="find_lost_device_you_found_it">袧邪褕械谢!</string>
+    <string name="czesh">效械褕褋泻懈泄</string>
+    <string name="swedish">楔胁械写褋泻懈泄</string>
+    <string name="hebrew">袠胁褉懈褌</string>
+    <string name="greek">袚褉械褔械褋泻懈泄</string>
+    <string name="hungarian">袙械薪谐械褉褋泻懈泄</string>
+    <string name="romanian">袪褍屑褘薪褋泻懈泄</string>
+    <string name="pref_summary_force_white_color_scheme">袩芯谢械蟹薪芯, 械褋谢懈 褍 胁邪褕懈褏 褔邪褋芯胁 褌械屑薪褘械 褋褌褉械谢泻懈</string>
+    <string name="pref_title_force_white_color_scheme">校褋褌邪薪芯胁懈褌褜 锌褉懈薪褍写懈褌械谢褜薪芯 褑胁械褌芯胁褍褞 褋褏械屑褍 \"褔褢褉薪褘屑 锌芯 斜械谢芯屑褍\"</string>
+    <string name="bip_prefs_shotcuts_summary">袙褘斜褉邪褌褜 褟褉谢褘泻懈 薪邪 褝泻褉邪薪械 斜褉邪褋谢械褌邪</string>
+    <string name="bip_prefs_shortcuts">携褉谢褘泻懈</string>
+    <string name="pref_header_auto_fetch">袩芯谢褍褔邪褌褜 邪胁褌芯屑邪褌懈褔械褋泻懈</string>
+    <string name="power_mode_watch">孝芯谢褜泻芯 褔邪褋褘</string>
+    <string name="power_mode_saving">协薪械褉谐芯褋斜械褉械卸械薪懈械</string>
+    <string name="power_mode_normal">袧芯褉屑邪谢褜薪褘泄</string>
+    <string name="power_mode_title">袪械卸懈屑 锌懈褌邪薪懈褟 褔邪褋芯胁</string>
+    <string name="prefs_sensors_button_bp_calibration_sum">袧邪卸屑懈褌械, 褔褌芯斜褘 薪邪褔邪褌褜 泻邪谢懈斜褉芯胁泻褍</string>
+    <string name="prefs_sensors_button_bp_calibration">袣邪谢懈斜褉芯胁泻邪</string>
+    <string name="pref_sensors_bp_calibration_high">小懈褋褌芯谢懈褔械褋泻芯械 (胁械褉褏薪械械) 邪褉褌械褉懈邪谢褜薪芯械 写邪胁谢械薪懈械</string>
+    <string name="pref_sensors_bp_calibration_low">袛懈邪褋褌芯谢懈褔械褋泻芯械 (薪懈卸薪械械) 邪褉褌械褉懈邪谢褜薪芯械 写邪胁谢械薪懈械</string>
+    <string name="pref_sensors_bp_calibration">袣邪谢懈斜褉芯胁泻邪 邪褉褌械褉懈邪谢褜薪芯谐芯 写邪胁谢械薪懈褟</string>
+    <string name="pref_title_sensors_altitude">袣邪谢懈斜褉芯胁泻邪 胁褘褋芯褌褘</string>
+    <string name="pref_header_sensors_calibration">袣邪谢懈斜褉芯胁泻邪 写邪褌褔懈泻芯胁</string>
+    <string name="pref_title_device_spec_settings_show_raw_graph">袩芯泻邪蟹邪褌褜 RAW-写邪薪薪褘械 薪邪 谐褉邪褎懈泻械 邪泻褌懈胁薪芯褋褌懈</string>
+    <string name="pref_summary_device_spec_settings_title_force_time">袩褉懈薪褍写懈褌械谢褜薪邪褟 邪胁褌芯屑邪褌懈褔械褋泻邪褟 褋懈薪褏褉芯薪懈蟹邪褑懈褟 胁褉械屑械薪懈 锌褉懈 锌芯胁褌芯褉薪芯屑 锌芯写泻谢褞褔械薪懈懈. 袗薪邪谢芯谐芯胁褘械 褋褌褉械谢泻懈 屑芯谐褍褌 锌芯泻邪蟹褘胁邪褌褜 薪械锌褉邪胁懈谢褜薪芯械 胁褉械屑褟!</string>
+    <string name="pref_title_device_spec_settings_force_time">袩褉懈薪褍写懈褌械谢褜薪芯 褋懈薪褏褉芯薪懈蟹懈褉芯胁邪褌褜 胁褉械屑褟</string>
+    <string name="pref_header_device_spec_settings">袩邪褉邪屑械褌褉褘 褍褋褌褉芯泄褋褌胁邪</string>
+    <string name="pref_summary_notifications_and_calls_title_shake_reject">袛褍斜谢懈褉褍械褌 写械泄褋褌胁懈械 泻薪芯锌泻懈 褔邪褋芯胁</string>
+    <string name="prefs_notifications_and_calls_shake_reject">袠谐薪芯褉懈褉芯胁邪褌褜/芯褌泻谢芯薪懈褌褜 胁褘蟹芯胁 锌芯 胁褋褌褉褟褏懈胁邪薪懈褞</string>
+    <string name="pref_summary_notifications_and_calls_title_reject">袙褘泻谢褞褔懈褌褜 - 懈谐薪芯褉懈褉芯胁邪褌褜, 袙泻谢 - 芯褌泻谢芯薪褟褌褜</string>
+    <string name="prefs_notifications_and_calls_reject">袣薪芯锌泻邪 懈谐薪芯褉懈褉芯胁邪薪懈褟/褋斜褉芯褋邪 胁褏芯写褟褖械谐芯 胁褘蟹芯胁邪</string>
+    <string name="pref_header_notifications_and_calls_callhandling">校写械褉卸邪薪懈械 胁褘蟹芯胁邪</string>
+    <string name="pref_title_notifications_and_calls_repeat_on_misscall">袩芯胁褌芯褉褟褌褜 写谢褟 n\'芯谐芯 泻芯谢懈褔械褋褌胁邪 屑懈薪褍褌</string>
+    <string name="pref_summary_notifications_and_calls_enable_misscall">袩芯胁褌芯褉褟褌褜 泻邪卸写褍褞 屑懈薪褍褌褍</string>
+    <string name="pref_notifications_and_calls_enable_misscall">校胁械写芯屑谢褟褌褜 芯 锌褉芯锌褍褖械薪薪褘褏 胁褘蟹芯胁邪褏</string>
+    <string name="prefs_notifications_and_calls_continious_ring">袪邪蟹褉械褕懈褌褜 褍胁械写芯屑谢械薪懈褟 胁芯 胁褉械屑褟 蟹胁芯薪泻邪</string>
+    <string name="title_activity_LenovoWatch_calibration">袣邪谢懈斜褉芯胁泻邪 褔邪褋芯胁 Watch X Plus</string>
+    <string name="pref_title_weather_summary">袠褋锌芯谢褜蟹褍械褌褋褟 写谢褟 锌褉芯胁邪泄写械褉邪 锌芯谐芯写褘 LineageOS, 写褉褍谐懈械 胁械褉褋懈懈 Android 写芯谢卸薪褘 懈褋锌芯谢褜蟹芯胁邪褌褜 锌褉懈谢芯卸械薪懈械 褌懈锌邪 \"校胁械写芯屑谢械薪懈械 芯 锌芯谐芯写械\". 袛芯锌芯谢薪懈褌械谢褜薪褍褞 懈薪褎芯褉屑邪褑懈褞 屑芯卸薪芯 薪邪泄褌懈 胁 胁懈泻懈-锌褉芯械泻褌械 Gadgetbridge.</string>
+    <string name="fw_upgrade_notice_miband5">袙褘 褋芯斜懈褉邪械褌械褋褜 褍褋褌邪薪芯胁懈褌褜 锌褉芯褕懈胁泻褍 %s 薪邪 褋胁芯泄 Mi Band 5.
+\n
+\n袘褍写褜褌械 胁薪懈屑邪褌械谢褜薪褘: 褋薪邪褔邪谢邪 褍褋褌邪薪芯胁懈褌械 .fw 褎邪泄谢, 懈 褌芯谢褜泻芯 锌芯褌芯屑 .res 褎邪泄谢. 袩芯褋谢械 褍褋褌邪薪芯胁泻懈 .fw 胁邪褕 斜褉邪褋谢械褌 斜褍写械褌 锌械褉械蟹邪谐褉褍卸械薪.
+\n
+\n袩褉懈屑械褔邪薪懈械: 薪械 薪褍卸薪芯 褍褋褌邪薪邪胁谢懈胁邪褌褜 .res, 械褋谢懈 芯薪 薪懈褔械屑 薪械 芯褌谢懈褔邪械褌褋褟 芯褌 褍褋褌邪薪芯胁谢械薪薪芯谐芯 褉邪薪械械.
+\n
+\n袙蝎 袛袝袡小孝袙校袝孝袝 袧袗 小袙袨袡 小孝袪袗啸 袠 袪袠小袣!</string>
+    <string name="fw_upgrade_notice_amazfit_trex">袙褘 褋芯斜懈褉邪械褌械褋褜 褍褋褌邪薪芯胁懈褌褜 锌褉芯褕懈胁泻褍 %s 薪邪 褋胁芯泄 Amazfit T-Rex.
+\n
+\n袘褍写褜褌械 胁薪懈屑邪褌械谢褜薪褘: 褋薪邪褔邪谢邪 褍褋褌邪薪芯胁懈褌械 .fw 褎邪泄谢, 锌芯褌芯屑 .res 褎邪泄谢, 懈 褌芯谢褜泻芯 锌芯褌芯屑 .gps 褎邪泄谢. 袩芯褋谢械 褍褋褌邪薪芯胁泻懈 .fw 胁邪褕懈 褔邪褋褘 斜褍写褍褌 锌械褉械蟹邪谐褉褍卸械薪褘.
+\n
+\n袩褉懈屑械褔邪薪懈械: 薪械 薪褍卸薪芯 褍褋褌邪薪邪胁谢懈胁邪褌褜 .res 懈 .gps, 械褋谢懈 芯薪懈 薪懈褔械屑 薪械 芯褌谢懈褔邪褞褌褋褟 芯褌 褍褋褌邪薪芯胁谢械薪薪褘褏 褉邪薪械械.
+\n
+\n袙蝎 袛袝袡小孝袙校袝孝袝 袧袗 小袙袨袡 小孝袪袗啸 袠 袪袠小袣!</string>
+    <string name="controlcenter_set_alias">校褋褌邪薪芯胁懈褌褜 锌褋械胁写芯薪懈屑</string>
+    <string name="pref_title_notifications_and_calls_repeat_on_call">袩芯胁褌芯褉褟褌褜 褍胁械写芯屑谢械薪懈褟 芯 蟹胁芯薪泻械</string>
+    <string name="pref_header_notifications_and_calls">校胁械写芯屑谢械薪懈褟 懈 蟹胁芯薪泻懈</string>
+    <string name="pref_title_vibration_strength">袦芯褖薪芯褋褌褜 胁懈斜褉邪褑懈懈</string>
+    <string name="pref_title_relax_firmware_checks">袙泻谢褞褔懈褌械 褝褌褍 褎褍薪泻褑懈褞, 械褋谢懈 褏芯褌懈褌械 褍褋褌邪薪芯胁懈褌褜 锌褉芯褕懈胁泻褍, 薪械 锌褉械写薪邪蟹薪邪褔械薪薪褍褞 写谢褟 胁邪褕械谐芯 褍褋褌褉芯泄褋褌胁邪 (薪邪 胁邪褕 褋褌褉邪褏 懈 褉懈褋泻)</string>
+    <string name="pref_summary_relax_firmware_checks">袨褋谢邪斜懈褌褜 锌褉芯胁械褉泻懈 锌褉芯褕懈胁泻懈</string>
+    <string name="swolf_index">swolf 懈薪写械泻褋</string>
+    <string name="ascentSeconds">袩芯写褗褢屑</string>
+    <string name="prefs_events_forwarding_startnonwear">袩褉懈 芯褌褋褍褌褋褌胁懈懈 薪芯褕械薪懈褟</string>
+    <string name="swimStyle">小褌懈谢褜 锌谢邪胁邪薪懈褟</string>
+    <string name="Strokes">袙蟹屑邪褏懈</string>
+    <string name="strokes_unit">胁蟹屑</string>
+    <string name="strokes_second">胁蟹屑/褋</string>
+    <string name="averageStrokesPerSecond">小褉械写薪械械 泻芯谢懈褔械褋褌胁芯 胁蟹屑邪褏芯胁</string>
+    <string name="prefs_events_forwarding_action_title">袙褘锌芯谢薪懈褌褜 写械泄褋褌胁懈械</string>
+    <string name="prefs_events_forwarding_wokeup">袩褉懈 锌褉芯斜褍卸写械薪懈懈</string>
+    <string name="prefs_events_forwarding_fellsleep">袩褉懈 蟹邪褋褘锌邪薪懈懈</string>
+    <string name="prefs_events_forwarding_title">袛械泄褋褌胁懈褟 褋 褍褋褌褉芯泄褋褌胁芯屑</string>
+    <string name="prefs_events_forwarding_summary">袠褋锌芯谢褜蟹褍泄褌械 褋芯斜褘褌懈褟 褍褋褌褉芯泄褋褌胁邪 写谢褟 蟹邪锌褍褋泻邪 写械泄褋褌胁懈泄 懈 褌褉邪薪褋谢褟褑懈泄 Android</string>
+    <string name="activity_detail_show_gps_label">袩芯泻邪蟹邪褌褜 GPS-褌褉械泻</string>
+    <string name="activity_detail_duration_label">袛谢懈褌械谢褜薪芯褋褌褜</string>
+    <string name="activity_detail_end_label">袣芯薪械褑</string>
+    <string name="activity_detail_start_label">袧邪褔邪谢芯</string>
+    <string name="Steps">楔邪谐懈</string>
+    <string name="Activity">袗泻褌懈胁薪芯褋褌褜</string>
+    <string name="Speed">小泻芯褉芯褋褌褜</string>
+    <string name="Distance">袪邪褋褋褌芯褟薪懈械</string>
+    <string name="Swimming">袩谢邪胁邪薪懈械</string>
+    <string name="bpm">褍写/屑懈薪</string>
+    <string name="minutes_km">屑懈薪/泻屑</string>
+    <string name="seconds_m">褋械泻/屑</string>
+    <string name="seconds_km">褋械泻/泻屑</string>
+    <string name="calories_unit">泻泻邪谢</string>
+    <string name="laps_unit">泻褉褍谐芯胁</string>
+    <string name="swim_style">褋褌懈谢褜 锌谢邪胁邪薪懈褟</string>
+    <string name="seconds">褋械泻</string>
+    <string name="km_h">泻屑/褔</string>
+    <string name="meters_second">屑/褋</string>
+    <string name="steps_unit">褕邪谐芯胁</string>
+    <string name="cm">褋屑</string>
+    <string name="meters">屑</string>
+    <string name="laps">袣褉褍谐芯胁</string>
+    <string name="averageLapPace">小褉械写薪懈泄 褌械屑锌 泻褉褍谐邪</string>
+    <string name="averageStride">小褉械写薪懈泄 褕邪谐</string>
+    <string name="averageStrokeDistance">小褉械写薪械械 褉邪褋褋褌芯褟薪懈械 褏芯写邪</string>
+    <string name="averageKMPaceSeconds">孝械屑锌</string>
+    <string name="averageHR">袩褍谢褜褋</string>
+    <string name="totalStride">袨斜褖懈泄 褕邪谐</string>
+    <string name="maxPace">袘褘褋褌褉褘泄 褌械屑锌</string>
+    <string name="minPace">袦械写谢械薪薪褘泄 褌械屑锌</string>
+    <string name="maxSpeed">袦邪泻褋懈屑邪谢褜薪邪褟</string>
+    <string name="caloriesBurnt">袣邪谢芯褉懈泄</string>
+    <string name="activeSeconds">袗泻褌懈胁薪芯</string>
+    <string name="steps">楔邪谐芯胁</string>
+    <string name="minAltitude">袦懈薪懈屑邪谢褜薪邪褟</string>
+    <string name="maxAltitude">袦邪泻褋懈屑邪谢褜薪邪褟</string>
+    <string name="descentMeters">小 谐芯褉褘</string>
+    <string name="ascentMeters">袙 谐芯褉褍</string>
+    <string name="distanceMeters">袪邪褋褋褌芯褟薪懈械</string>
+    <string name="error_location_enabled_mandatory">袛谢褟 锌芯懈褋泻邪 褍褋褌褉芯泄褋褌胁 薪械芯斜褏芯写懈屑芯 胁泻谢褞褔懈褌褜 芯锌褉械写械谢械薪懈械 屑械褋褌芯锌芯谢芯卸械薪懈褟</string>
+    <string name="ignore_bonded_devices_description">袙泻谢褞褔械薪懈械 褝褌芯谐芯 锌邪褉邪屑械褌褉邪 斜褍写械褌 懈谐薪芯褉懈褉芯胁邪褌褜 褍褋褌褉芯泄褋褌胁邪, 泻芯褌芯褉褘械 褍卸械 斜褘谢懈 褋胁褟蟹邪薪褘/褋芯锌褉褟卸械薪褘 锌褉懈 褋泻邪薪懈褉芯胁邪薪懈懈</string>
+    <string name="ignore_bonded_devices">袠谐薪芯褉懈褉芯胁邪褌褜 褋胁褟蟹邪薪薪褘械 褍褋褌褉芯泄褋褌胁邪</string>
+    <string name="error_retrieving_devices_database">袨褕懈斜泻邪 锌褉懈 锌芯谢褍褔械薪懈懈 褍褋褌褉芯泄褋褌胁 懈蟹 斜邪蟹褘 写邪薪薪褘褏</string>
+    <string name="error_setting_alias">袨褕懈斜泻邪 薪邪褋褌褉芯泄泻懈 锌褋械胁写芯薪懈屑邪:</string>
+    <string name="error_exporting_device_preferences">袨褕懈斜泻邪 锌褉懈 褝泻褋锌芯褉褌械 褋锌械褑懈褎懈褔薪褘褏 薪邪褋褌褉芯械泻 褍褋褌褉芯泄褋褌胁邪</string>
+    <string name="pref_check_permission_status_summary">袩褉芯胁械褉褟褌褜 懈 蟹邪锌褉邪褕懈胁邪褌褜 薪械写芯褋褌邪褞褖懈械 褉邪蟹褉械褕械薪懈褟, 写邪卸械 械褋谢懈 胁 薪懈褏 薪械 胁褋械谐写邪 械褋褌褜 薪械芯斜褏芯写懈屑芯褋褌褜. 袨褌泻谢褞褔懈褌械, 械褋谢懈 胁邪褕懈 褍褋褌褉芯泄褋褌胁邪 写械泄褋褌胁懈褌械谢褜薪芯 薪械 锌芯写写械褉卸懈胁邪褞褌 薪懈 芯写薪褍 懈蟹 褝褌懈褏 褎褍薪泻褑懈泄. 袨褌褋褍褌褋褌胁懈械 褉邪蟹褉械褕械薪懈泄 屑芯卸械褌 胁褘蟹胁邪褌褜 锌褉芯斜谢械屑褘 胁 褉邪斜芯褌械!</string>
+    <string name="pref_check_permission_status">袩褉芯胁械褉褟褌褜 褋褌邪褌褍褋 褉邪蟹褉械褕械薪懈泄</string>
+    <string name="device_requires_key">袧袝袨袘啸袨袛袠袦 袣袥挟效, 袛袨袥袚袨袝 袟袗袞袗孝袠袝 袛袥携 袙啸袨袛袗</string>
+    <string name="device_is_currently_bonded">校袞袝 小袙携袟袗袧蝎</string>
+    <string name="companiondevice_pairing_details">袙泻谢褞褔邪械褌 锌芯写写械褉卸泻褍 薪芯胁芯谐芯 API CompanionDevice (褉邪斜芯褌邪械褌 褌芯谢褜泻芯 薪邪 Android 8 懈谢懈 胁褘褕械), 褔褌芯 锌芯胁褘褋懈褌 薪邪写械卸薪芯褋褌褜, 械褋谢懈 褋谢褍卸斜褍 薪械芯斜褏芯写懈屑芯 锌械褉械蟹邪锌褍褋褌懈褌褜 胁 褎芯薪芯胁芯屑 褉械卸懈屑械, 写谢褟 写芯褋褌懈卸械薪懈褟 褝褎褎械泻褌邪 褌褉械斜褍械褌褋褟 锌芯胁褌芯褉薪芯械 褋芯锌褉褟卸械薪懈械 褋 锌芯屑芯褖褜褞 Gadgetbridge</string>
+    <string name="companiondevice_pairing">小芯锌褉褟卸械薪懈械 褋 CompanionDevice</string>
+    <string name="require_location_provider">袦械褋褌芯锌芯谢芯卸械薪懈械 写芯谢卸薪芯 斜褘褌褜 胁泻谢褞褔械薪芯</string>
+    <string name="devicetype_pinetime_jf">PineTime (袩褉芯褕懈胁泻邪 JF)</string>
+    <string name="devicetype_tlw64">TLW64</string>
+    <string name="activity_summary_detail">袩芯写褉芯斜薪芯褋褌懈 褋锌芯褉褌懈胁薪芯泄 写械褟褌械谢褜薪芯褋褌懈</string>
+    <string name="activity_type_yoga">袡芯谐邪</string>
+    <string name="activity_type_jump_roping">小泻邪泻邪谢泻邪</string>
+    <string name="activity_type_elliptical_trainer">协谢谢懈锌褌懈褔械褋泻懈泄 褌褉械薪邪卸械褉</string>
+    <string name="activity_type_indoor_cycling">袙械谢芯褋锌芯褉褌 胁 锌芯屑械褖械薪懈懈</string>
+    <string name="activity_type_swimming_openwater">袩谢邪胁邪薪懈械 (袨褌泻褉褘褌邪褟 胁芯写邪)</string>
+    <string name="prefs_events_forwarding_broadcast_title">孝褉邪薪褋谢褟褑懈芯薪薪芯械 褋芯芯斜褖械薪懈械</string>
+    <string name="Elevation">袙芯蟹胁褘褕械薪薪芯褋褌褜</string>
+    <string name="flatSeconds">袩谢芯褋泻懈械</string>
+    <string name="swolfIndex">SWOLF</string>
+    <string name="activity_filter_apply_filter">袩褉懈屑械薪懈褌褜 褎懈谢褜褌褉</string>
+    <string name="activity_filter_filter_title">肖懈谢褜褌褉</string>
+    <string name="activity_filter_reset_filter">小斜褉芯褋懈褌褜 褎懈谢褜褌褉</string>
+    <string name="activity_filter_date_to">袛芯</string>
+    <string name="activity_filter_date_from">小</string>
+    <string name="pref_header_filter">肖懈谢褜褌褉 褋锌芯褉褌懈胁薪褘褏 邪泻褌懈胁薪芯褋褌械泄</string>
+    <string name="medley">袣芯屑锌谢械泻褋薪芯械</string>
+    <string name="backstroke">袧邪 褋锌懈薪械</string>
+    <string name="freestyle">肖褉懈褋褌邪泄谢</string>
+    <string name="breaststroke">袘褉邪褋褋</string>
+    <string name="activity_summaries_statistics">小褌邪褌懈褋褌懈泻邪</string>
+    <string name="km">泻屑</string>
+    <string name="pref_header_statistics">小褌邪褌懈褋褌懈泻邪 褋锌芯褉褌懈胁薪褘褏 邪泻褌懈胁薪芯褋褌械泄</string>
+    <string name="activity_filter_name_contains">袦械褌泻邪</string>
+    <string name="activity_summary_edit_name_title">袩褉邪胁懈褌褜 屑械褌泻褍</string>
+    <string name="activity_summaries_all_activities">袙褋械 邪泻褌懈胁薪芯褋褌懈</string>
+    <string name="activity_summary_yesterday">袙褔械褉邪</string>
+    <string name="activity_summary_today">小械谐芯写薪褟</string>
+    <string name="devicetype_sg2">Lemfo SG2</string>
+    <string name="activity_summaries_all_devices">袙褋械 褍褋褌褉芯泄褋褌胁邪</string>
+    <string name="sports_activity_quick_filter_select">袩械褉懈芯写 胁褉械屑械薪懈</string>
+    <string name="sports_activity_quick_filter_30days">30 写薪械泄</string>
+    <string name="sports_activity_quick_filter_7days">7 写薪械泄</string>
+    <string name="sports_activity_quick_filter_last_month">袩褉芯褕谢褘泄 屑械褋褟褑</string>
+    <string name="sports_activity_quick_filter_this_month">协褌芯褌 屑械褋褟褑</string>
+    <string name="sports_activity_quick_filter_last_week">袩褉械写褘写褍褖邪褟 薪械写械谢褟</string>
+    <string name="sports_activity_quick_filter_this_week">协褌邪 薪械写械谢褟</string>
+    <string name="no">袧械褌</string>
+    <string name="yes">袛邪</string>
+    <string name="activity_filter_individual_items">袠薪写懈胁懈写褍邪谢褜薪芯 胁褘斜褉邪薪薪褘械 褝谢械屑械薪褌褘</string>
+    <string name="addto_filter">袛芯斜邪胁懈褌褜 胁 褎懈谢褜褌褉</string>
+    <string name="averageSpeed">小褉械写薪褟褟 褋泻芯褉芯褋褌褜</string>
+    <string name="baseAltitude">袘邪蟹芯胁邪褟 胁褘褋芯褌邪</string>
+    <string name="activity_type_badminton">袘邪写屑懈薪褌芯薪</string>
+    <string name="activity_type_pingpong">袩懈薪谐-锌芯薪谐</string>
+    <string name="activity_type_basketball">袘邪褋泻械褌斜芯谢</string>
+    <string name="activity_type_cricket">袣褉懈泻械褌</string>
+    <string name="activity_type_rowing_machine">袚褉械斜薪芯泄 褌褉械薪邪卸械褉</string>
+    <string name="activity_type_soccer">肖褍褌斜芯谢</string>
+    <string name="activity_filter_from_placeholder">写邪谢褢泻芯械 锌褉芯褕谢芯械</string>
+    <string name="activity_filter_to_placeholder">褋械谐芯写薪褟</string>
+    <string name="about_links">小褋褘谢泻懈</string>
+    <string name="activity_prefs_chart_min_session_length">袦懈薪懈屑邪谢褜薪邪褟 锌褉芯写芯谢卸懈褌械谢褜薪芯褋褌褜 邪泻褌懈胁薪芯褋褌械泄 (屑懈薪褍褌褘)</string>
+    <string name="activity_prefs_chart_max_idle_phase_length">袩褉芯写芯谢卸懈褌械谢褜薪芯褋褌褜 锌邪褍蟹褘 写谢褟 褉邪蟹写械谢械薪懈褟 邪泻褌懈胁薪芯褋褌械泄 (屑懈薪褍褌褘)</string>
+    <string name="activity_prefs_chart_min_steps_per_minute">袦懈薪懈屑邪谢褜薪褘械 褕邪谐懈 胁 屑懈薪褍褌褍 写谢褟 芯斜薪邪褉褍卸械薪懈褟 邪泻褌懈胁薪芯褋褌懈</string>
+    <string name="activity_prefs_chart_min_steps_per_minute_for_run">袦懈薪懈屑邪谢褜薪褘械 褕邪谐懈 胁 屑懈薪褍褌褍 写谢褟 芯斜薪邪褉褍卸械薪懈褟 斜械谐邪</string>
+    <string name="charts_activity_list">小锌懈褋芯泻 邪泻褌懈胁薪芯褋褌械泄</string>
+    <string name="chart_get_active_and_synchronize">小写械谢邪泄褌械 薪械泻芯褌芯褉褍褞 邪泻褌懈胁薪芯褋褌褜 懈 褋懈薪褏褉芯薪懈蟹懈褉褍泄褌械 褍褋褌褉芯泄褋褌胁芯.</string>
+    <string name="chart_no_active_data">袧懈泻邪泻芯泄 邪泻褌懈胁薪芯褋褌懈 薪械 芯斜薪邪褉褍卸械薪芯.</string>
+    <string name="lefun_prefs_hydration_reminder_interval_title">袠薪褌械褉胁邪谢 薪邪锌芯屑懈薪邪薪懈褟 芯 谐懈写褉邪褌邪褑懈懈 (胁 屑懈薪褍褌邪褏)</string>
+    <string name="lefun_prefs_hydration_reminder_title">袧邪锌芯屑懈薪邪薪懈械 芯 谐懈写褉邪褌邪褑懈懈</string>
+    <string name="lefun_prefs_antilost_summary">袘褉邪褋谢械褌 斜褍写械褌 胁懈斜褉懈褉芯胁邪褌褜, 械褋谢懈 胁邪褕 褌械谢械褎芯薪 芯褌泻谢褞褔懈褌褋褟 芯褌 斜褉邪褋谢械褌邪</string>
+    <string name="lefun_prefs_hydration_reminder_summary">袘褉邪褋谢械褌 斜褍写械褌 胁懈斜褉懈褉芯胁邪褌褜, 薪邪锌芯屑懈薪邪褟 胁邪屑 芯 锌懈褌褜械 胁芯写褘</string>
+    <string name="devicetype_lefun">Lefun</string>
+    <string name="lefun_prefs_interface_language_title">携蟹褘泻 懈薪褌械褉褎械泄褋邪</string>
+    <string name="lefun_prefs_antilost_title">袟邪褖懈褌邪 芯褌 锌芯褌械褉懈</string>
+    <string name="devicetype_sonyswr12">Sony SWR12</string>
+    <string name="sonyswr12_settings_title">袧邪褋褌褉芯泄泻懈 Sony SWR12</string>
+    <string name="sonyswr12_settings_low_vibration">小谢邪斜邪褟 胁懈斜褉邪褑懈褟</string>
+    <string name="sonyswr12_settings_stamina">袪械卸懈屑邪 褝薪械褉谐芯褋斜械褉械卸械薪懈褟 胁泻谢褞褔械薪</string>
+    <string name="sonyswr12_settings_alarm_interval">袠薪褌械褉胁邪谢 褍屑薪芯谐芯 斜褍写懈谢褜薪懈泻邪 胁 屑懈薪褍褌邪褏</string>
+    <string name="sonyswr12_settings_low_vibration_summary">袙泻谢褞褔懈褌褜 褋谢邪斜褍褞 胁懈斜褉邪褑懈懈 薪邪 斜褉邪褋谢械褌械</string>
+    <string name="sonyswr12_settings_stamina_summary">袪械卸懈屑 褝薪械褉谐芯褋斜械褉械卸械薪懈褟 芯褌泻谢褞褔邪械褌 锌械褉懈芯写懈褔械褋泻懈械 蟹邪屑械褉褘 锌褍谢褜褋邪, 锌褉芯写谢械胁邪褟 胁褉械屑褟 褉邪斜芯褌褘</string>
+    <string name="sonyswr12_settings_alarm_interval_summary">袠薪褌械褉胁邪谢 褍屑薪芯谐芯 斜褍写懈谢褜薪懈泻邪 - 褝褌芯 懈薪褌械褉胁邪谢 写芯 褍褋褌邪薪芯胁谢械薪薪芯谐芯 斜褍写懈谢褜薪懈泻邪. 袙 褝褌芯屑 懈薪褌械褉胁邪谢械 褍褋褌褉芯泄褋褌胁芯 锌褘褌邪械褌褋褟 芯锌褉械写械谢懈褌褜 褋邪屑褍褞 谢械谐泻褍褞 褎邪蟹褍 褋薪邪, 褔褌芯斜褘 褉邪蟹斜褍写懈褌褜 锌芯谢褜蟹芯胁邪褌械谢褟</string>
+    <string name="devicetype_nut_mini">Nut mini</string>
+    <string name="activity_error_no_app_for_png">效褌芯斜褘 锌芯写械谢懈褌褜褋褟 褝褌懈屑 褋薪懈屑泻芯屑, 褍褋褌邪薪芯胁懈褌械 锌褉懈谢芯卸械薪懈械, 褋锌芯褋芯斜薪芯械 芯斜褉邪斜邪褌褘胁邪褌褜 褎邪泄谢褘 懈蟹芯斜褉邪卸械薪懈泄.</string>
+    <string name="descentSeconds">袩芯 褍斜褘胁邪薪懈褞</string>
+    <string name="gps_track">GPS-褌褉械泻</string>
+    <string name="about_version">袙械褉褋懈褟 %s</string>
+    <string name="devicetype_amazfit_bips_lite">Amazfit Bip S Lite</string>
+    <string name="devicetype_amazfit_band5">Amazfit Band 5</string>
+    <string name="charts_legend_heartrate_average">小褉械写薪褟褟 褔邪褋褌芯褌邪 锌褍谢褜褋邪</string>
+    <string name="activity_prefs_step_length_cm">袛谢懈薪邪 褕邪谐邪 胁 褋屑</string>
+    <string name="charts_min_max_heartrate_popup">小邪屑邪褟 薪懈蟹泻邪褟 褔邪褋褌芯褌邪 锌褍谢褜褋邪: %1$d
+\n小邪屑邪褟 胁褘褋芯泻邪褟 褔邪褋褌芯褌邪 锌褍谢褜褋邪: %2$d
+\n袠薪褌械薪褋懈胁薪芯褋褌褜 写胁懈卸械薪懈褟: %3$s</string>
+    <string name="firmware_update_progress">袟邪谐褉褍蟹泻邪 胁 锌褉芯褑械褋褋械
+\n%1d%% 锌褉懈 %.2f泻斜懈褌/褋 (胁 褋褉械写薪械屑 %.2f泻斜懈褌/褋)
+\n效邪褋褌褜 %1d 懈蟹 %1d</string>
+    <string name="devicestatus_upload_failed">袟邪谐褉褍蟹泻邪 薪械 褍写邪谢邪褋褜</string>
+    <string name="devicestatus_upload_aborted">袟邪谐褉褍蟹泻邪 芯褌屑械薪械薪邪!</string>
+    <string name="devicestatus_upload_validating">袟邪谐褉褍蟹泻邪 锌褉芯褏芯写懈褌 锌褉芯胁械褉泻褍</string>
+    <string name="devicestatus_upload_completed">袟邪谐褉褍蟹泻邪 蟹邪胁械褉褕械薪邪</string>
+    <string name="devicestatus_disconnected">校褋褌褉芯泄褋褌胁芯 芯褌泻谢褞褔械薪芯!</string>
+    <string name="devicestatus_disconnecting">校褋褌褉芯泄褋褌胁芯 芯褌泻谢褞褔邪械褌褋褟!</string>
+    <string name="devicestatus_upload_starting">袟邪谐褉褍蟹泻邪 薪邪褔懈薪邪械褌褋褟</string>
+    <string name="devicestatus_connected">校褋褌褉芯泄褋褌胁芯 锌芯写泻谢褞褔械薪芯</string>
+    <string name="devicestatus_connecting">校褋褌褉芯泄褋褌胁芯 锌芯写泻谢褞褔邪械褌褋褟</string>
+    <string name="find_lost_device_message">袠褋泻邪褌褜 %1$s\?</string>
+    <string name="menuitem_dnd">DND</string>
+    <string name="menuitem_alexa">Alexa</string>
+    <string name="menuitem_takephoto">校写邪谢械薪薪芯械 褍锌褉邪胁谢械薪懈械 泻邪屑械褉芯泄</string>
+    <string name="menuitem_mutephone">袨褌泻谢褞褔械薪懈械 蟹胁褍泻邪</string>
+    <string name="menuitem_spo2">SpO2</string>
+    <string name="devicetype_casiogbx100">Casio GBX-100</string>
+    <string name="devicetype_amazfit_gts2">Amazfit GTS 2</string>
+    <string name="activity_list_summary_active_time">袙褉械屑褟 邪泻褌懈胁薪芯褋褌懈</string>
+    <string name="activity_list_summary_distance">袪邪褋褋褌芯褟薪懈械</string>
+    <string name="activity_list_summary_active_steps">袗泻褌懈胁薪褘械 褕邪谐懈</string>
+    <string name="prefs_charts_tabs_summary">袙懈写懈屑褘械 胁泻谢邪写泻懈 写懈邪谐褉邪屑屑</string>
+    <string name="prefs_charts_tabs">袙泻谢邪写泻懈 写懈邪谐褉邪屑屑</string>
+    <string name="activity_list_summary_intensity">袠薪褌械薪褋懈胁薪芯褋褌褜
+\n写胁懈卸械薪懈褟</string>
+    <string name="menuitem_stopwatch">小械泻褍薪写芯屑械褉</string>
+    <string name="menuitem_findphone">袧邪泄褌懈 褌械谢械褎芯薪</string>
+    <string name="devicetype_amazfit_gtr2">Amazfit GTR 2</string>
+    <string name="movement_intensity">袠薪褌械薪褋懈胁薪芯褋褌褜 写胁懈卸械薪懈褟</string>
+    <string name="devicestatus_upload_started">袟邪谐褉褍蟹泻邪 薪邪褔邪谢邪褋褜</string>
+    <string name="activity_list_summary_activities">袗泻褌懈胁薪芯褋褌懈</string>
+    <string name="menuitem_sleep">小芯薪</string>
+    <string name="devicetype_amazfit_bipu">Amazfit Bip U</string>
+    <string name="pref_title_upper_button_function_double">袛胁芯泄薪芯械 薪邪卸邪褌懈械 薪邪 胁械褉褏薪褞褞 泻薪芯锌泻褍</string>
+    <string name="pref_title_lower_button_function_double">袛胁芯泄薪芯械 薪邪卸邪褌懈械 薪邪 薪懈卸薪褞褞 泻薪芯锌泻褍</string>
+    <string name="pref_title_middle_button_function_double">袛胁芯泄薪芯械 薪邪卸邪褌懈械 薪邪 褋褉械写薪褞褞 泻薪芯锌泻褍</string>
+    <string name="pref_title_lower_button_function_long">袛谢懈薪薪芯械 薪邪卸邪褌懈械 薪邪 薪懈卸薪褞褞 泻薪芯锌泻褍</string>
+    <string name="pref_title_middle_button_function_long">袛谢懈薪薪芯械 薪邪卸邪褌懈械 薪邪 褋褉械写薪褞褞 泻薪芯锌泻褍</string>
+    <string name="pref_title_upper_button_function_long">袛谢懈薪薪芯械 薪邪卸邪褌懈械 薪邪 胁械褉褏薪褞褞 泻薪芯锌泻褍</string>
+    <string name="pref_title_lower_button_function_short">袣芯褉芯褌泻芯械 薪邪卸邪褌懈械 薪邪 薪懈卸薪褞褞 泻薪芯锌泻褍</string>
+    <string name="pref_title_middle_button_function_short">袣芯褉芯褌泻芯械 薪邪卸邪褌懈械 薪邪 褋褉械写薪褞褞 泻薪芯锌泻褍</string>
+    <string name="pref_title_upper_button_function_short">袣芯褉芯褌泻芯械 薪邪卸邪褌懈械 薪邪 胁械褉褏薪褞褞 泻薪芯锌泻褍</string>
+    <string name="menuitem_goal">笑械谢褜</string>
+    <string name="menuitem_pomodoro">孝褉械泻械褉 Pomodoro</string>
+    <string name="pref_summary_connected_advertisement">袛械谢邪械褌 褍褋褌褉芯泄褋褌胁芯 写芯褋褌褍锌薪褘屑 写谢褟 芯斜薪邪褉褍卸械薪懈褟 锌芯 Bluetooth, 泻芯谐写邪 芯薪芯 褍卸械 锌芯写泻谢褞褔械薪芯</string>
+    <string name="pref_title_connected_advertisement">袙懈写械薪 锌褉懈 锌芯写泻谢褞褔械薪懈懈</string>
+    <string name="prefs_autoremove_message">袗胁褌芯屑邪褌懈褔械褋泻懈 褍写邪谢褟褌褜 SMS-褍胁械写芯屑谢械薪懈褟</string>
+    <string name="prefs_fake_ring_duration">袩芯写写械谢褜薪褘泄 薪械锌褉械褉褘胁薪褘泄 蟹胁芯薪芯泻</string>
+    <string name="prefs_key_vibration">袙懈斜褉邪褑懈褟 泻谢邪胁懈褕</string>
+    <string name="prefs_autolight">袗胁褌芯屑邪褌懈褔械褋泻懈泄 褋胁械褌</string>
+    <string name="devicetype_amazfit_vergel">Amazfit Verge Lite</string>
+    <string name="discovery_entered_invalid_authkey">袙胁械写褢薪薪褘泄 泻谢褞褔 邪褍褌械薪褌懈褎懈泻邪褑懈懈 薪械写械泄褋褌胁懈褌械谢械薪! 校写械褉卸懈胁邪泄褌械 写谢褟 懈蟹屑械薪械薪懈褟.</string>
+    <string name="fw_upgrade_notice_amazfitvergel">袙褘 褋芯斜懈褉邪械褌械褋褜 褍褋褌邪薪芯胁懈褌褜 锌褉芯褕懈胁泻褍 %s 薪邪 褋胁芯泄 Amazfit Verge Lite.
+\n
+\n袘褍写褜褌械 胁薪懈屑邪褌械谢褜薪褘: 褋薪邪褔邪谢邪 褍褋褌邪薪芯胁懈褌械 褎邪泄谢 .fw, 蟹邪褌械屑 褎邪泄谢 .res 懈, 薪邪泻芯薪械褑, 褎邪泄谢 .gps. 袩芯褋谢械 褍褋褌邪薪芯胁泻懈 .fw 胁邪褕懈 褔邪褋褘 斜褍写褍褌 锌械褉械蟹邪谐褉褍卸械薪褘.
+\n
+\n袩褉懈屑械褔邪薪懈械: 薪械 薪褍卸薪芯 褍褋褌邪薪邪胁谢懈胁邪褌褜 .res 懈 .gps, 械褋谢懈 褝褌懈 褎邪泄谢褘 褌芯褔薪芯 褌邪泻懈械 卸械, 泻邪泻 褉邪薪械械 褍褋褌邪薪芯胁谢械薪薪褘械.
+\n
+\n袙蝎 袛袝袡小孝袙校袝孝袝 袧袗 小袙袨袡 小孝袪袗啸 袠 袪袠小袣!</string>
+    <string name="qhybrid_calibration_align_hint">袠褋锌芯谢褜蟹褍泄褌械 泻薪芯锌泻懈 薪懈卸械, 褔褌芯斜褘 褍褋褌邪薪芯胁懈褌褜 褋褌褉械谢泻懈 薪邪 12:00.</string>
+    <string name="dbmanagementactivity_export_confirmation">袛械泄褋褌胁懈褌械谢褜薪芯 褝泻褋锌芯褉褌懈褉芯胁邪褌褜 写邪薪薪褘械\? 袪邪薪械械 褝泻褋锌芯褉褌懈褉芯胁邪薪薪褘械 写邪薪薪褘械 芯 邪泻褌懈胁薪芯褋褌懈 (械褋谢懈 褌邪泻芯胁褘械 懈屑械褞褌褋褟) 懈 薪邪褋褌褉芯泄泻懈 斜褍写褍褌 锌械褉械蟹邪锌懈褋邪薪褘.</string>
+    <string name="dbmanagementactivity_export_data_title">协泻褋锌芯褉褌 写邪薪薪褘褏\?</string>
+    <string name="prefs_operating_sounds">袪邪斜芯褔懈械 蟹胁褍泻懈</string>
+    <string name="widget_settings_select_device_title">袙褘斜械褉懈褌械 褍褋褌褉芯泄褋褌胁芯</string>
+    <string name="devicetype_amazfit_bipupro">Amazfit Bip U Pro</string>
+    <string name="dbmanagementactivity_export_finished">校写邪谢械薪懈械 蟹邪胁械褉褕械薪芯</string>
+    <string name="activity_db_management_clean_export_directory_text">协泻褋锌芯褉褌懈褉芯胁邪薪薪褘械 褎邪泄谢褘 胁 泻邪褌邪谢芯谐械 褝泻褋锌芯褉褌邪/懈屑锌芯褉褌邪 写芯褋褌褍锌薪褘 写谢褟 谢褞斜芯谐芯 锌褉懈谢芯卸械薪懈褟 薪邪 胁邪褕械屑 褍褋褌褉芯泄褋褌胁械. 袙褘 屑芯卸械褌械 褍写邪谢懈褌褜 褝褌懈 褎邪泄谢褘 锌芯褋谢械 褋懈薪褏褉芯薪懈蟹邪褑懈懈 懈谢懈 褉械蟹械褉胁薪芯谐芯 泻芯锌懈褉芯胁邪薪懈褟. 袩械褉械写 褍写邪谢械薪懈械屑 褍斜械写懈褌械褋褜, 褔褌芯 褍 胁邪褋 械褋褌褜 褉械蟹械褉胁薪邪褟 泻芯锌懈褟. 肖邪泄谢褘 GPX, 锌芯写泻邪褌邪谢芯谐懈 懈 邪胁褌芯屑邪褌懈褔械褋泻懈 褝泻褋锌芯褉褌懈褉褍械屑褘泄 褎邪泄谢 斜邪蟹褘 写邪薪薪褘褏 (械褋谢懈 芯薪 褋褍褖械褋褌胁褍械褌) 薪械 斜褍写褍褌 褍写邪谢械薪褘. 袩褍褌褜 泻 泻邪褌邪谢芯谐褍 褝泻褋锌芯褉褌邪/懈屑锌芯褉褌邪:</string>
+    <string name="activity_DB_clean_export_directory_warning_message">袛械泄褋褌胁懈褌械谢褜薪芯 褍写邪谢懈褌褜 褎邪泄谢褘 胁 泻邪褌邪谢芯谐械 褝泻褋锌芯褉褌邪/懈屑锌芯褉褌邪\?</string>
+    <string name="activity_DB_clean_export_directory_warning_title">校写邪谢懈褌褜 褎邪泄谢褘 胁 泻邪褌邪谢芯谐械 褝泻褋锌芯褉褌邪/懈屑锌芯褉褌邪\?</string>
+    <string name="activity_db_management_clean_export_directory_label">校写邪谢懈褌褜 褎邪泄谢褘 胁 泻邪褌邪谢芯谐械 褝泻褋锌芯褉褌邪/懈屑锌芯褉褌邪</string>
+    <string name="activity_DB_ShowContentButton">袩芯泻邪蟹邪褌褜 褋芯写械褉卸懈屑芯械 泻邪褌邪谢芯谐邪 褝泻褋锌芯褉褌邪/懈屑锌芯褉褌邪</string>
+    <string name="activity_data_management_directory_content_title">协泻褋锌芯褉褌/懈屑锌芯褉褌 褋芯写械褉卸懈屑芯谐芯 泻邪褌邪谢芯谐邪</string>
+    <string name="dbmanagementactivity_error_cleaning_export_directory">袨褕懈斜泻邪 锌褉懈 褍写邪谢械薪懈懈 褎邪泄谢芯胁 懈蟹 泻邪褌邪谢芯谐邪 褝泻褋锌芯褉褌邪 %1$s</string>
+    <string name="mi">屑懈谢褜</string>
+    <string name="minutes_mi">屑懈薪褍褌/屑懈谢褜</string>
+    <string name="mi_h">屑懈谢褜/褔邪褋</string>
+    <string name="ft">褎褍褌芯胁</string>
+    <string name="devicetype_waspos">Wasp-os</string>
+    <string name="devicetype_zepp_e">Zepp E</string>
+    <string name="devicetype_amazfit_gts2e">Amazfit GTS 2e</string>
+    <string name="devicetype_amazfit_gts2_mini">Amazfit GTS 2 Mini</string>
+    <string name="devicetype_amazfit_gtr2e">Amazfit GTR 2e</string>
+    <string name="devicetype_amazfit_neo">Amazfit Neo</string>
+    <string name="activity_type_strength_training">小懈谢芯胁芯泄 褌褉械薪懈薪谐</string>
+    <string name="battery_detail_activity_title">袠薪褎芯褉屑邪褑懈褟 芯 斜邪褌邪褉械械</string>
+    <string name="fw_upgrade_notice_amazfitx">袙褘 褋芯斜懈褉邪械褌械褋褜 褍褋褌邪薪芯胁懈褌褜 锌褉芯褕懈胁泻褍 %s 薪邪 褋胁芯泄 Amazfit X.
+\n
+\n袘褍写褜褌械 胁薪懈屑邪褌械谢褜薪褘: 芯斜褟蟹邪褌械谢褜薪芯 褍褋褌邪薪芯胁懈褌械 褎邪泄谢 .fw, 邪 蟹邪褌械屑 褎邪泄谢 .res. 袩芯褋谢械 褍褋褌邪薪芯胁泻懈 .fw 胁邪褕 斜褉邪褋谢械褌 斜褍写械褌 锌械褉械蟹邪谐褉褍卸械薪.
+\n
+\n袩褉懈屑械褔邪薪懈械: 薪械 薪褍卸薪芯 褍褋褌邪薪邪胁谢懈胁邪褌褜 .res, 械褋谢懈 芯薪 褌芯褔薪芯 褌邪泻芯泄 卸械, 泻邪泻 褉邪薪械械 褍褋褌邪薪芯胁谢械薪薪褘泄.
+\n
+\n袙蝎 袛袝袡小孝袙校袝孝袝 袧袗 小袙袨袡 小孝袪袗啸 袠 袪袠小袣!</string>
+    <string name="devicetype_amazfit_x">Amazfit X</string>
+    <string name="calendar_year">袚芯写</string>
+    <string name="calendar_six_months">6 屑械褋褟褑械胁</string>
+    <string name="calendar_month">袦械褋褟褑</string>
+    <string name="calendar_two_weeks">袛胁械 薪械写械谢懈</string>
+    <string name="calendar_week">袧械写械谢褟</string>
+    <string name="calendar_day">袛械薪褜</string>
+    <string name="battery_level">校褉芯胁械薪褜 蟹邪褉褟写邪 斜邪褌邪褉械懈</string>
+    <string name="show_ongoing_activity">袩芯泻邪蟹邪褌褜 胁褋锌谢褘胁邪褞褖械械 芯泻薪芯 褋 褌械泻褍褖械泄 邪泻褌懈胁薪芯褋褌褜褞</string>
+    <string name="dialog_hide">小泻褉褘褌褜</string>
+    <string name="fossil_hr_warning_firmware_too_new">袧械泻芯褌芯褉褘械 褎褍薪泻褑懈懈 芯褌泻谢褞褔械薪褘, 锌芯褌芯屑褍 褔褌芯 锌褉芯褕懈胁泻邪 褔邪褋芯胁 褋谢懈褕泻芯屑 薪芯胁邪褟</string>
+    <string name="qhybrid_title_calibration">袣邪谢懈斜褉芯胁泻邪</string>
+    <string name="qhybrid_title_apps_management">校锌褉邪胁谢械薪懈械 锌褉懈谢芯卸械薪懈褟屑懈</string>
+    <string name="qhybrid_title_file_management">校锌褉邪胁谢械薪懈械 褎邪泄谢邪屑懈</string>
+    <string name="qhybrid_title_background_image">肖芯薪芯胁芯械 懈蟹芯斜褉邪卸械薪懈械</string>
+    <string name="qhybrid_title_apps">袩褉懈谢芯卸械薪懈褟</string>
+    <string name="fossil_hr_auth_failed">袨褕懈斜泻邪 邪褍褌械薪褌懈褎懈泻邪褑懈懈, 芯谐褉邪薪懈褔械薪薪邪褟 褎褍薪泻褑懈芯薪邪谢褜薪芯褋褌褜</string>
+    <string name="fossil_hr_unavailable_unauthed">袧械写芯褋褌褍锌薪芯 胁 褉械卸懈屑械 斜械蟹 邪褍褌械薪褌懈褎懈泻邪褑懈懈</string>
+    <string name="fossil_hr_synced_activity_data">小懈薪褏褉芯薪懈蟹懈褉芯胁邪薪薪褘械 写邪薪薪褘械 芯斜 邪泻褌懈胁薪芯褋褌懈</string>
+    <string name="fossil_hr_commute_processing">袨斜褉邪斜芯褌泻邪 蟹邪锌褉芯褋邪鈥�</string>
+    <string name="devicetype_um25">UM-25</string>
+    <string name="calendar_three_months">3 屑械褋褟褑邪</string>
+    <string name="fw_upgrade_notice_miband6">袙褘 褋芯斜懈褉邪械褌械褋褜 褍褋褌邪薪芯胁懈褌褜 锌褉芯褕懈胁泻褍 %s 薪邪 褋胁芯泄 Mi Band 6.
+\n
+\n袘褍写褜褌械 胁薪懈屑邪褌械谢褜薪褘: 褋薪邪褔邪谢邪 褍褋褌邪薪芯胁懈褌械 .fw 褎邪泄谢, 懈 褌芯谢褜泻芯 蟹邪褌械屑 .res 褎邪泄谢. 袩芯褋谢械 褍褋褌邪薪芯胁泻懈 .fw 胁邪褕 斜褉邪褋谢械褌 斜褍写械褌 锌械褉械蟹邪谐褉褍卸械薪.
+\n
+\n袩褉懈屑械褔邪薪懈械: 薪械 薪褍卸薪芯 褍褋褌邪薪邪胁谢懈胁邪褌褜 .res 械褋谢懈 芯薪 薪懈褔械屑 薪械 芯褌谢懈褔邪褞褌褋褟 芯褌 褍褋褌邪薪芯胁谢械薪薪褘褏 褉邪薪械械.
+\n
+\n袙蝎 袛袝袡小孝袙校袝孝袝 袧袗 小袙袨袡 小孝袪袗啸 袠 袪袠小袣!</string>
+    <string name="devicetype_amazfit_trex_pro">Amazfit T-Rex Pro</string>
+    <string name="qhybrid_pref_summary_external_intents">袩芯蟹胁芯谢褟械褌 写褉褍谐懈屑 锌褉懈谢芯卸械薪懈褟屑 Android 蟹邪谐褉褍卸邪褌褜/锌械褉械蟹邪锌懈褋褘胁邪褌褜 褎邪泄谢褘</string>
+    <string name="qhybrid_pref_title_external_intents">袪邪蟹褉械褕懈褌褜 芯锌邪褋薪褘械 胁薪械褕薪懈械 薪邪屑械褉械薪懈褟</string>
+    <string name="pref_summary_developer_settings">袧邪褋褌褉芯泄泻懈 懈 褎褍薪泻褑懈芯薪邪谢, 懈褋锌芯谢褜蟹褍械屑褘泄 褉邪蟹褉邪斜芯褌褔懈泻邪屑懈</string>
+    <string name="pref_title_developer_settings">袧邪褋褌褉芯泄泻懈 褉邪蟹褉邪斜芯褌褔懈泻邪</string>
+    <string name="qhybrid_pref_summary_actions">袛械泄褋褌胁懈褟 写谢褟 锌褉懈谢芯卸械薪懈褟 Commute</string>
+    <string name="qhybrid_pref_title_actions">袛械泄褋褌胁懈褟</string>
+    <string name="qhybrid_watchface_configuration_old_firmware">协泻褉邪薪 薪邪褋褌褉芯泄泻懈 褑懈褎械褉斜谢邪褌邪 写谢褟 褔邪褋芯胁 褋 胁械褉褋懈械泄 锌褉芯褕懈胁泻懈 DN1.0.2.19r 懈 薪懈卸械</string>
+    <string name="qhybrid_calibration_100_steps">100 褕邪谐芯胁</string>
+    <string name="qhybrid_calibration_10_steps">10 褕邪谐芯胁</string>
+    <string name="qhybrid_calibration_1_step">1 褕邪谐</string>
+    <string name="qhybrid_calibration_clockwise">袩芯 褔邪褋芯胁芯泄 褋褌褉械谢泻械</string>
+    <string name="qhybrid_calibration_counterclockwise">袩褉芯褌懈胁 褔邪褋芯胁芯泄 褋褌褉械谢泻懈</string>
+    <string name="fossil_hr_new_action">袧芯胁芯械 写械泄褋褌胁懈械</string>
+    <string name="fossil_hr_new_action_cancel">芯褌屑械薪懈褌褜</string>
+    <string name="fossil_hr_edit_action">袪械写邪泻褌懈褉芯胁邪褌褜 写械泄褋褌胁懈械</string>
+    <string name="fossil_hr_edit_action_delete">褍写邪谢懈褌褜</string>
+    <string name="fossil_hr_commute_actions_explanation">袛械泄褋褌胁懈褟, 薪邪褋褌褉芯械薪薪褘械 蟹写械褋褜, 斜褍写褍褌 芯褌芯斜褉邪卸邪褌褜褋褟 胁 锌褉懈谢芯卸械薪懈懈 Commute 薪邪 褔邪褋邪褏. 袠薪褎芯褉屑邪褑懈褞 芯 褌芯屑, 泻邪泻 芯斜褉邪斜邪褌褘胁邪褌褜 薪邪屑械褉械薪懈褟, 褋芯蟹写邪胁邪械屑褘械 褝褌懈屑懈 写械泄褋褌胁懈褟屑懈, 褔懈褌邪泄褌械 胁 胁懈泻懈.</string>
+    <string name="pref_summary_canned_messages_dismisscall">袨褌泻谢芯薪懈褌褜 蟹胁芯薪泻懈 褋 褔邪褋芯胁 SMS-褋芯芯斜褖械薪懈械屑</string>
+    <string name="pref_summary_canned_messages_set">袨褌锌褉邪胁泻邪 薪邪 褍褋褌褉芯泄褋褌胁芯 褋芯芯斜褖械薪懈泄, 薪邪褋褌褉芯械薪薪褘褏 薪懈卸械</string>
+    <string name="qhybrid_summary_calibration">袣邪谢懈斜褉芯胁泻邪 褋褌褉械谢芯泻 褔邪褋芯胁</string>
+    <string name="qhybrid_summary_file_management">袟邪谐褉褍蟹泻邪 懈 褋泻邪褔懈胁邪薪懈械 褎邪泄谢芯胁</string>
+    <string name="pref_summary_physical_buttons">袧邪褋褌褉芯懈褌褜 褎褍薪泻褑懈芯薪邪谢褜薪芯褋褌褜 褎懈蟹懈褔械褋泻懈褏 泻薪芯锌芯泻 薪邪 褔邪褋邪褏</string>
+    <string name="pref_title_physical_buttons">肖懈蟹懈褔械褋泻懈械 泻薪芯锌泻懈</string>
+    <string name="qhybrid_title_watchface">袧邪褋褌褉芯泄泻邪 褑懈褎械褉斜谢邪褌邪</string>
+    <string name="menuitem_barometer">袘邪褉芯屑械褌褉</string>
+    <string name="menuitem_temperature">孝械屑锌械褉邪褌褍褉邪</string>
+    <string name="menuitem_widgets">袙懈写卸械褌褘</string>
+    <string name="menuitem_events">小芯斜褘褌懈褟</string>
+    <string name="notification_channel_low_battery_name">袧懈蟹泻懈泄 蟹邪褉褟写 斜邪褌邪褉械懈</string>
+    <string name="notification_channel_transfer_name">袩械褉械写邪褔邪 写邪薪薪褘褏</string>
+    <string name="devicetype_miband6">Mi Band 6</string>
+    <string name="kind_agps_bundle">袩邪泻械褌 AGPS</string>
+    <string name="prefs_sounds_summary">袧邪褋褌褉芯懈褌褜, 泻芯谐写邪 褍褋褌褉芯泄褋褌胁芯 斜褍写械褌 锌懈褖邪褌褜</string>
+    <string name="prefs_sounds">袟胁褍泻懈</string>
+    <string name="watchface_install_info">袙褘 褋芯斜懈褉邪械褌械褋褜 褍褋褌邪薪芯胁懈褌褜 褋谢械写褍褞褖懈泄 褑懈褎械褉斜谢邪褌:
+\n
+\n%1$s
+\n袙械褉褋懈褟 %2$s 芯褌 %3$s
+\n</string>
+    <string name="fw_upgrade_notice_amazfitneo">袙褘 褋芯斜懈褉邪械褌械褋褜 褍褋褌邪薪芯胁懈褌褜 锌褉芯褕懈胁泻褍 %s 薪邪 袙邪褕 Amazfit Neo.
+\n
+\n袩芯褋谢械 褍褋褌邪薪芯胁泻懈 褎邪泄谢邪 .fw 胁邪褕 斜褉邪褋谢械褌 斜褍写械褌 锌械褉械蟹邪谐褉褍卸械薪.
+\n
+\n袙蝎 袛袝袡小孝袙校袝孝袝 袧袗 小袙袨袡 小孝袪袗啸 袠 袪袠小袣!</string>
+    <string name="watchface_setting_wrist_flick_duration">袩褉芯写芯谢卸懈褌械谢褜薪芯褋褌褜 (胁 屑褋)</string>
+    <string name="watchface_setting_wrist_flick_minute">袦懈薪褍褌薪邪褟 褋褌褉械谢泻邪 (芯褌 -360 写芯 360)</string>
+    <string name="watchface_setting_wrist_flick_hour">效邪褋芯胁邪褟 褋褌褉械谢泻邪 (芯褌 -360 写芯 360)</string>
+    <string name="watchface_setting_wrist_flick_move_relative">袩械褉械屑械褖械薪懈械 褋褌褉械谢芯泻 芯褌薪芯褋懈褌械谢褜薪芯 胁褉械屑械薪懈</string>
+    <string name="watchface_setting_desc_wrist_flick">(褔褌芯斜褘 芯褌泻谢褞褔懈褌褜 锌芯谢薪芯褋褌褜褞, 胁泻谢褞褔懈褌械 芯褌薪芯褋懈褌械谢褜薪芯械 写胁懈卸械薪懈械 懈 褍褋褌邪薪芯胁懈褌械 胁褋械 蟹薪邪褔械薪懈褟 薪邪 0)</string>
+    <string name="watchface_setting_title_wrist_flick">袛胁懈卸械薪懈械 蟹邪锌褟褋褌褜械屑</string>
+    <string name="watchface_setting_display_refresh_partial">效邪褋褌懈褔薪芯械 芯斜薪芯胁谢械薪懈械 (胁 屑懈薪褍褌邪褏)</string>
+    <string name="watchface_setting_display_refresh_full">袩芯谢薪芯械 芯斜薪芯胁谢械薪懈械 (胁 屑懈薪褍褌邪褏)</string>
+    <string name="watchface_setting_title_display_refresh_timeout">袙褉械屑褟 芯卸懈写邪薪懈褟 芯斜薪芯胁谢械薪懈褟 写懈褋锌谢械褟</string>
+    <string name="watchface_dialog_title_settings">袧邪褋褌褉芯泄泻懈 褑懈褎械褉斜谢邪褌邪</string>
+    <string name="watchface_dialog_widget_color_black">效械褉薪褘泄</string>
+    <string name="watchface_dialog_widget_color_white">袘械谢褘泄</string>
+    <string name="watchface_dialog_widget_color">笑胁械褌</string>
+    <string name="qhybrid_title_watchface_designer">袛懈蟹邪泄薪械褉 褑懈褎械褉斜谢邪褌芯胁</string>
+    <string name="watchface_dialog_widget_preset_right">小锌褉邪胁邪</string>
+    <string name="watchface_dialog_widget_preset_left">小谢械胁邪</string>
+    <string name="watchface_dialog_widget_preset_bottom">小薪懈蟹褍</string>
+    <string name="watchface_dialog_widget_preset_top">小胁械褉褏褍</string>
+    <string name="watchface_dialog_widget_presets">袩褉械写褍褋褌邪薪芯胁泻懈 锌芯谢芯卸械薪懈褟</string>
+    <string name="watchface_dialog_widget_y_coordinate">袣芯芯褉写懈薪邪褌邪 Y (屑邪泻褋. 240)</string>
+    <string name="watchface_dialog_widget_x_coordinate">袣芯芯褉写懈薪邪褌邪 X (屑邪泻褋. 240)</string>
+    <string name="watchface_dialog_widget_type">孝懈锌</string>
+    <string name="appmanager_app_edit">袪械写邪泻褌懈褉芯胁邪褌褜</string>
+    <string name="button_watchface_save_apply">小芯褏褉邪薪懈褌褜 懈 锌褉懈屑械薪懈褌褜</string>
+    <string name="button_watchface_preview">袩褉械写胁邪褉懈褌械谢褜薪褘泄 锌褉芯褋屑芯褌褉 薪邪 褔邪褋邪褏</string>
+    <string name="watchface_widget_type_heart_rate">效邪褋褌芯褌邪 褋械褉写褑械斜懈械薪懈褟</string>
+    <string name="watchface_widget_type_steps">楔邪谐懈</string>
+    <string name="watchface_widget_type_weather">袩芯谐芯写邪</string>
+    <string name="watchface_widget_type_date">袛邪褌邪</string>
+    <string name="watchface_dialog_title_add_widget">袛芯斜邪胁懈褌褜 胁懈写卸械褌</string>
+    <string name="watchface_toast_settings_incomplete">袧邪褋褌褉芯泄泻懈 薪械 蟹邪胁械褉褕械薪褘, 胁懈写卸械褌 薪械 写芯斜邪胁谢械薪</string>
+    <string name="watchface_dialog_title_set_name">袟邪写邪褌褜 懈屑褟 褑懈褎械褉斜谢邪褌邪</string>
+    <string name="button_watchface_settings">袧邪褋褌褉芯泄泻懈 褑懈褎械褉斜谢邪褌邪</string>
+    <string name="button_watchface_add_widget">袛芯斜邪胁懈褌褜 胁懈写卸械褌</string>
+    <string name="button_watchface_select_image">袠蟹屑械薪懈褌褜 褎芯薪芯胁芯械 懈蟹芯斜褉邪卸械薪懈械</string>
+    <string name="button_watchface_edit_name">袠蟹屑械薪懈褌褜 懈屑褟</string>
+    <string name="gpx_receiver_overwrite_some_files">袧械泻芯褌芯褉褘泄 褎邪泄谢(褘) 褍卸械 褋褍褖械褋褌胁褍械褌. 袩械褉械蟹邪锌懈褋邪褌褜\?</string>
+    <string name="gpx_receiver_files_received">袩芯谢褍褔械薪 褎邪泄谢(褘) GPX:</string>
+    <string name="gpx_receiver_activity_title">GPX-锌褉懈械屑薪懈泻 Gadgetbridge</string>
+    <string name="devicetype_smaq2oss">SMA-Q2 OSS</string>
+    <string name="activity_summary_detail_editing_gpx_track">袪械写邪泻褌懈褉芯胁邪薪懈械 褋胁褟蟹邪薪薪芯谐芯 GPX 褌褉械泻邪</string>
+    <string name="activity_summary_detail_clear_gpx_track">袨褔懈褋褌懈褌褜 GPX 褌褉械泻</string>
+    <string name="activity_summary_detail_select_gpx_track">袙褘斜械褉懈褌械 GPX 褌褉械泻</string>
+    <string name="set">校褋褌邪薪芯胁懈褌褜</string>
+    <string name="pref_theme_black_background">袠褋锌芯谢褜蟹芯胁邪褌褜 褔械褉薪褘泄 褎芯薪 胁 褌械屑薪芯泄 褌械屑械</string>
+    <string name="watchface_upload_failed">袧械 褍写邪谢芯褋褜 蟹邪谐褉褍蟹懈褌褜 褑懈褎械褉斜谢邪褌. 袩芯卸邪谢褍泄褋褌邪, 锌芯锌褉芯斜褍泄褌械 械褖械 褉邪蟹.</string>
+    <string name="watchface_cache_confirm_overwrite">笑懈褎械褉斜谢邪褌 褋 褌邪泻懈屑 懈屑械薪械屑 褍卸械 褋褍褖械褋褌胁褍械褌 胁 泻械褕械. 袙褘 褏芯褌懈褌械 械谐芯 锌械褉械蟹邪锌懈褋邪褌褜\?</string>
+    <string name="heart_rate_result">袪械蟹褍谢褜褌邪褌褘 蟹邪屑械褉芯胁</string>
+    <string name="getting_heart_rate">袠蟹屑械褉械薪懈械</string>
+    <string name="blood_pressure">袗褉褌械褉懈邪谢褜薪芯械 写邪胁谢械薪懈械</string>
+    <string name="pref_title_huami_force_new_protocol">袧芯胁褘泄 锌褉芯褌芯泻芯谢 邪胁褌芯褉懈蟹邪褑懈懈</string>
+    <string name="devicetype_fitpro">FitPro</string>
+    <string name="menuitem_flashlight">肖芯薪邪褉懈泻</string>
+    <string name="pref_title_ping_tone">袟胁褍泻芯胁芯泄 褋懈谐薪邪谢</string>
+    <string name="pref_header_notification_application_settings">袧邪褋褌褉芯泄泻懈 写谢褟 泻邪卸写芯谐芯 锌褉懈谢芯卸械薪懈褟</string>
+    <string name="pref_title_notification_use_as_allow">袪邪蟹褉械褕懈褌褜 褍胁械写芯屑谢械薪懈褟 芯褌 胁褘斜褉邪薪薪褘褏 锌褉懈谢芯卸械薪懈泄</string>
+    <string name="pref_message_privacy_mode_complete">小泻褉褘褌褜 胁褋褢 褋芯写械褉卸懈屑芯械</string>
+    <string name="pref_summary_huami_force_new_protocol">袙泻谢褞褔懈褌械, 械褋谢懈 褍褋褌褉芯泄褋褌胁芯 锌械褉械褋褌邪谢芯 锌芯写泻谢褞褔邪褌褜褋褟 锌芯褋谢械 芯斜薪芯胁谢械薪懈褟 锌褉芯褕懈胁泻懈</string>
+    <string name="toast_app_must_not_be_selected">袩褉懈谢芯卸械薪懈械 薪械 写芯谢卸薪芯 斜褘褌褜 胁褘斜褉邪薪芯 写谢褟 薪邪褋褌褉芯泄泻懈</string>
+    <string name="title_activity_notification_management">袧邪褋褌褉芯泄泻懈 褍胁械写芯屑谢械薪懈泄</string>
+    <string name="pref_title_message_privacy_mode">袪械卸懈屑 泻芯薪褎懈写械薪褑懈邪谢褜薪芯褋褌懈 褋芯芯斜褖械薪懈泄</string>
+    <string name="pref_message_privacy_mode_off">袨褌芯斜褉邪卸邪褌褜 胁褋褢 褋芯写械褉卸懈屑芯械</string>
+    <string name="pref_applications_settings">小锌懈褋芯泻 锌褉懈谢芯卸械薪懈泄</string>
+    <string name="controlcenter_power_off">袙褘泻谢褞褔懈褌褜</string>
+    <string name="controlcenter_power_off_confirm_title">袙褘泻谢褞褔懈褌褜</string>
+    <string name="controlcenter_power_off_confirm_description">袙褘 褍胁械褉械薪褘, 褔褌芯 褏芯褌懈褌械 胁褘泻谢褞褔懈褌褜 斜褉邪褋谢械褌/褔邪褋褘\?</string>
+    <string name="add_test_device">袛芯斜邪胁懈褌褜 褌械褋褌芯胁褘械 褔邪褋褘/斜褉邪褋谢械褌</string>
+    <string name="device_card_activity_card_title_summary">袙褘斜械褉懈褌械, 泻邪泻褍褞 袙邪褕褍 邪泻褌懈胁薪芯褋褌褜 斜褍写械褌 芯褌芯斜褉邪卸邪褌褜 泻邪褉褌芯褔泻邪 褍褋褌褉芯泄褋褌胁邪 胁 Gadgetbridge</string>
+    <string name="prefs_activity_in_device_card_title">袩芯泻邪蟹邪褌褜/袨褌泻谢褞褔懈褌褜 (褕邪谐懈, 褉邪褋褋褌芯褟薪懈械, 褋芯薪)</string>
+    <string name="prefs_activity_in_device_card_sleep_title">小芯薪</string>
+    <string name="prefs_activity_in_device_card_sleep_title_summary">袩芯泻邪蟹褘胁邪褌褜 锌褉芯写芯谢卸懈褌械谢褜薪芯褋褌褜 褋薪邪</string>
+    <string name="device_card_activity_card_title">袠薪褎芯褉屑邪褑懈褟 芯 写械泄褋褌胁懈褟褏 薪邪 泻邪褉褌械 褍褋褌褉芯泄褋褌胁邪</string>
+    <string name="pref_header_equalizer">协泻胁邪谢邪泄蟹械褉</string>
+    <string name="pref_media_volumeup">校胁械谢懈褔械薪懈械 谐褉芯屑泻芯褋褌懈</string>
+    <string name="pref_media_volumedown">校屑械薪褜褕械薪懈械 谐褉芯屑泻芯褋褌懈</string>
+    <string name="watchface_dialog_widget_timezone">效邪褋芯胁芯泄 锌芯褟褋</string>
+    <string name="watchface_dialog_widget_timeout_hide_text">小锌褉褟褌邪褌褜 褌械泻褋褌 锌芯 胁褉械屑械薪懈</string>
+    <string name="sony_button_mode_volume_control">袣芯薪褌褉芯谢褜 谐褉芯屑泻芯褋褌懈</string>
+    <string name="watchface_widget_type_custom">袩芯谢褜蟹芯胁邪褌械谢褜褋泻懈泄 胁懈写卸械褌</string>
+    <string name="pref_media_play">袧邪褔邪褌褜 胁芯褋锌褉芯懈蟹胁械写械薪懈械</string>
+    <string name="prefs_activity_in_device_card_steps_title_summary">袩芯泻邪蟹邪褌褜 芯斜褖械械 泻芯谢懈褔械褋褌胁芯 褕邪谐芯胁</string>
+    <string name="pref_media_next">小谢械写褍褞褖懈泄</string>
+    <string name="watchface_dialog_widget_update_timeout">袙褉械屑褟 芯卸懈写邪薪懈褟 芯斜薪芯胁谢械薪懈褟 胁 屑懈薪褍褌邪褏</string>
+    <string name="sony_automatic_power_off_1_hour">1 褔邪褋</string>
+    <string name="sony_automatic_power_off_3_hour">3 褔邪褋邪</string>
+    <string name="pref_button_action_disabled">袨褌泻谢褞褔械薪</string>
+    <string name="sony_equalizer">协泻胁邪谢邪泄蟹械褉</string>
+    <string name="reminder_time_every_month">%1$s, 泻邪卸写褘泄 屑械褋褟褑</string>
+    <string name="reminder_time_every_week">%1$s, 泻邪卸写褍褞 薪械写械谢褞</string>
+    <string name="prefs_activity_in_device_card_title_summary">袩芯泻邪蟹褘胁邪褌褜 褕邪谐懈, 锌褉芯泄写械薪薪芯械 褉邪褋褋褌芯褟薪懈械 懈 锌褉芯写芯谢卸懈褌械谢褜薪芯褋褌褜 褋薪邪 薪邪 泻邪褉褌芯褔泻械 褍褋褌褉芯泄褋褌胁邪</string>
+    <string name="title_activity_set_reminders">袧邪褋褌褉芯懈褌褜 薪邪锌芯屑懈薪邪薪懈褟</string>
+    <string name="pref_header_other">袛褉褍谐芯械</string>
+    <string name="controlcenter_start_configure_reminders">袧邪褋褌褉芯懈褌褜 薪邪锌芯屑懈薪邪薪懈褟</string>
+    <string name="title_activity_reminder_details">袧邪褋褌褉芯泄泻懈 薪邪锌芯屑懈薪邪薪懈褟</string>
+    <string name="prefs_activity_in_device_card_distance_title_summary">袪邪褋褋褌芯褟薪懈械 胁褘褔懈褋谢褟械褌褋褟 锌芯 泻芯谢懈褔械褋褌胁褍 褕邪谐芯胁 懈 褕懈褉懈薪械 褕邪谐邪 (薪邪褋褌褉邪懈胁邪械褌褋褟 胁 褉邪蟹写械谢械 袧邪褋褌褉芯泄泻懈 - About you)</string>
+    <string name="pref_title_notification_use_as">袠褋锌芯谢褜蟹芯胁邪褌褜 褋锌懈褋芯泻 锌褉懈谢芯卸械薪懈泄, 褔褌芯斜褘鈥�</string>
+    <string name="reminder_repeat">袩芯胁褌芯褉</string>
+    <string name="reminder_date">袛邪褌邪</string>
+    <string name="reminder_time">袙褉械屑褟</string>
+    <string name="reminder_message">小芯芯斜褖械薪懈械</string>
+    <string name="reminder_time_every_day">%1$s, 械卸械写薪械胁薪芯</string>
+    <string name="reminder_time_once">%1$s, 芯写懈薪 褉邪蟹</string>
+    <string name="reminder_time_every_year">%1$s, 泻邪卸写褘泄 谐芯写</string>
+    <string name="reminder_once">袨写懈薪 褉邪蟹</string>
+    <string name="reminder_every_day">袣邪卸写褘泄 写械薪褜</string>
+    <string name="reminder_every_week">袣邪卸写褍褞 薪械写械谢褞</string>
+    <string name="reminder_every_month">袣邪卸写褘泄 屑械褋褟褑</string>
+    <string name="reminder_every_year">袣邪卸写褘泄 谐芯写</string>
+    <string name="reminder_delete_confirm_title">校写邪谢懈褌褜 薪邪锌芯屑懈薪邪薪懈械</string>
+    <string name="reminder_delete_confirm_description">袙褘 褍胁械褉械薪褘 褔褌芯 褏芯褌懈褌械 褍写邪谢懈褌褜 褝褌芯 薪邪锌芯屑懈薪邪薪懈械\?</string>
+    <string name="reminder_no_free_slots_title">袧械褌 褋胁芯斜芯写薪褘褏 褋谢芯褌芯胁</string>
+    <string name="reminder_no_free_slots_description">袧械褌 褋胁芯斜芯写薪褘褏 褋谢芯褌芯胁 写谢褟 薪邪锌芯屑懈薪邪薪懈泄 (胁褋械谐芯 褋谢芯褌芯胁: %1$s)</string>
+    <string name="pref_header_system">小懈褋褌械屑邪</string>
+    <string name="sony_surround_mode_concert_hall">袣芯薪褑械褉褌薪褘泄 蟹邪谢</string>
+    <string name="nothing_prefs_audiomode_title">袗褍写懈芯褉械卸懈屑</string>
+    <string name="sony_automatic_power_off_30_min">30 屑懈薪褍褌</string>
+    <string name="devicetype_galaxybuds_live">Galaxy Buds Live</string>
+    <string name="sony_speak_to_chat_timeout_standard">小褌邪薪写邪褉褌薪褘泄 (30褋)</string>
+    <string name="sony_speak_to_chat_timeout_short">袣芯褉芯褌泻懈泄 (15褋)</string>
+    <string name="sony_speak_to_chat_timeout_off">袙褘泻谢</string>
+    <string name="distance_format_kilometers">###.#泻屑</string>
+    <string name="distance_format_feet">###褎褌</string>
+    <string name="distance_format_meters">###屑</string>
+    <string name="sony_surround_mode_off">袙褘泻谢</string>
+    <string name="averageCadence">小褉械写薪懈泄 泻邪写械薪褋</string>
+    <string name="sony_speak_to_chat_timeout_long">袛芯谢谐懈泄 (1屑)</string>
+    <string name="sony_ambient_sound">袪械卸懈屑</string>
+    <string name="sony_equalizer_band_6300">6,3泻</string>
+    <string name="sony_equalizer_preset_bright">携褉泻懈泄</string>
+    <string name="sony_sound_position_off">袙褘泻谢</string>
+    <string name="sony_anc_optimizer_status_analyzing">袗薪邪谢懈蟹鈥�</string>
+    <string name="sony_surround_mode_club">袣谢褍斜</string>
+    <string name="sony_surround_mode_arena">袗褉械薪邪</string>
+    <string name="sony_ambient_sound_noise_cancelling">袩芯写邪胁谢械薪懈械 褕褍屑邪</string>
+    <string name="devicetype_galaxybuds">Galaxy Buds</string>
+    <string name="prefs_autoheartrate_interval">效邪褋褌芯褌邪 蟹邪屑械褉芯胁</string>
+    <string name="prefs_notifications_enable">袙泻谢褞褔懈褌褜 褍胁械写芯屑谢械薪懈褟</string>
+    <string name="sony_ambient_sound_off">袙褘泻谢.</string>
+    <string name="prefs_vibration_enable">袙泻谢褞褔懈褌褜 胁懈斜褉邪褑懈褞</string>
+    <string name="pref_header_sony_device_info">袠薪褎芯褉屑邪褑懈褟 芯斜 褍褋褌褉芯泄褋褌胁械</string>
+    <string name="audio_codec">袗褍写懈芯泻芯写械泻</string>
+    <string name="watchface_setting_title_power_saving">协薪械褉谐芯褋斜械褉械卸械薪懈械</string>
+    <string name="watchface_widget_type_calories">袣邪谢芯褉懈懈</string>
+    <string name="watchface_widget_type_battery">袘邪褌邪褉械褟</string>
+    <string name="minSpeed">袦懈薪懈屑邪谢褜薪芯械</string>
+    <string name="unknown">袧械懈蟹胁械褋褌薪芯</string>
+    <string name="sony_button_mode_off">袙褘泻谢</string>
+    <string name="devicetype_amazfit_pop">Amazfit Pop</string>
+    <string name="devicetype_sony_wh_1000xm4">Sony WH-1000XM4</string>
+    <string name="devicetype_amazfit_pop_pro">Amazfit Pop Pro</string>
+    <string name="devicetype_sony_wf_sp800n">Sony WF-SP800N</string>
+    <string name="devicetype_domyos_t540">Domyos T540</string>
+    <string name="spm">褕邪谐芯胁/屑懈薪</string>
+    <string name="sony_equalizer_band_1000">1泻</string>
+    <string name="devicetype_sony_wh_1000xm3">Sony WH-1000XM3</string>
+    <string name="prefs_dolby_mode">袪械卸懈屑 Dolby</string>
+    <string name="prefs_sleep_time">袙褉械屑褟 褋薪邪</string>
+    <string name="sony_equalizer_preset_off">袙褘泻谢</string>
+    <string name="sony_automatic_power_off_5_min">5 屑懈薪褍褌</string>
+    <string name="devicetype_bose_qc35">Bose QC35</string>
+    <string name="devicetype_vesc">VESC</string>
+    <string name="about_hash">袣芯屑屑懈褌 %s</string>
+    <string name="sony_equalizer_clear_bass">效懈褋褌褘泄 斜邪褋</string>
+    <string name="sony_equalizer_band_16000">16泻</string>
+    <string name="sony_equalizer_band_2500">2,5泻</string>
+    <string name="sony_equalizer_band_400">400</string>
+    <string name="menuitem_menu">袦械薪褞</string>
+    <string name="watchface_dialog_widget_width">楔懈褉懈薪邪 胁懈写卸械褌邪 (胁 锌懈泻褋械谢褟褏)</string>
+    <string name="prefs_game_mode">袠谐褉芯胁芯泄 褉械卸懈屑</string>
+    <string name="prefs_equalizer">协泻胁邪谢邪泄蟹械褉</string>
+    <string name="application_name_banglejs_main">Bangle.js Gadgetbridge</string>
+    <string name="title_activity_controlcenter_banglejs_main">Bangle.js Gadgetbridge</string>
+    <string name="application_name_banglejs_nopebble">Bangle.js Gadgetbridge</string>
+    <string name="about_activity_title_banglejs_nopebble">袨 Bangle.js Gadgetbridge</string>
+    <string name="application_name_main_nightly">Gadgetbridge (薪芯褔薪邪褟 褋斜芯褉泻邪)</string>
+    <string name="gadgetbridge_running_banglejs_nopebble">Bangle.js 蟹邪锌褍褖械薪</string>
+    <string name="about_description_banglejs_nopebble">袩褉懈谢芯卸械薪懈械-泻芯屑锌邪薪褜芯薪 写谢褟 Bangle.js 锌芯褋褌褉芯械薪芯 薪邪 芯褋薪芯胁械 锌褉芯械泻褌邪 Gadgetbridge, 褋 写芯斜邪胁谢械薪薪褘屑 写芯褋褌褍锌芯屑 胁 袠薪褌械褉薪械褌.
+\n
+\n袙 褋芯芯褌胁械褌褋褌胁懈懈 褋 锌芯谢懈褌懈泻芯泄 Google Play Store, 屑褘 薪械 屑芯卸械屑 褉邪蟹屑械褋褌懈褌褜 褋褋褘谢泻褍 写谢褟 锌芯卸械褉褌胁芯胁邪薪懈泄 胁 褋邪屑芯屑 锌褉懈谢芯卸械薪懈懈, 薪芯 械褋谢懈 胁邪屑 薪褉邪胁懈褌褋褟 褝褌芯 锌褉懈谢芯卸械薪懈械, 锌芯卸邪谢褍泄褋褌邪, 褉邪褋褋屑芯褌褉懈褌械 胁芯蟹屑芯卸薪芯褋褌褜 锌芯卸械褉褌胁芯胁邪薪懈褟 褔械褉械蟹 写芯屑邪褕薪褞褞 褋褌褉邪薪懈褑褍 Gadgetbridge 薪懈卸械.</string>
+    <string name="about_activity_title_main_nightly">袨 薪芯褔薪芯泄 褋斜芯褉泻械 Gadgetbridge</string>
+    <string name="application_name_main_nopebble">Gadgetbridge (袧芯褔薪邪褟 褋斜芯褉泻邪, 斜械蟹 锌褉芯胁邪泄写械褉邪 Pebble)</string>
+    <string name="about_description_main_nightly">\"袘械蟹芯斜谢邪褔薪邪褟\" 褋胁芯斜芯写薪邪褟 邪谢褜褌械褉薪邪褌懈胁邪 蟹邪泻褉褘褌褘屑 锌褉懈谢芯卸械薪懈褟屑 芯褌 锌褉芯懈蟹胁芯写懈褌械谢械泄 谐邪写卸械褌芯胁. \"袧芯褔薪褘械\" 褋斜芯褉泻懈 Gadgetbridge. 袧芯褔薪褘械 褋斜芯褉泻懈 Gadgetbridge. 袠褏 薪械谢褜蟹褟 褍褋褌邪薪芯胁懈褌褜, 械褋谢懈 锌褉懈谢芯卸械薪懈械 Gadgetbridge 懈谢懈 Pebble 褍卸械 褍褋褌邪薪芯胁谢械薪芯, 懈蟹-蟹邪 泻芯薪褎谢懈泻褌邪 锌褉芯胁邪泄写械褉邪 Pebble.</string>
+    <string name="title_activity_controlcenter_main_nopebble">Gadgetbridge 袧芯褔薪芯泄 斜械蟹 Pebble</string>
+    <string name="about_description_main_nopebble">\"袘械蟹芯斜谢邪褔薪邪褟\" 褋胁芯斜芯写薪邪褟 邪谢褜褌械褉薪邪褌懈胁邪 蟹邪泻褉褘褌褘屑 锌褉懈谢芯卸械薪懈褟屑 芯褌 锌褉芯懈蟹胁芯写懈褌械谢械泄 谐邪写卸械褌芯胁. \"袧芯褔薪褘械\" 褋斜芯褉泻懈 Gadgetbridge. 袙 褝褌芯泄 胁械褉褋懈懈 锌褉芯胁邪泄写械褉 Pebble 锌械褉械懈屑械薪芯胁邪薪 写谢褟 锌褉械写芯褌胁褉邪褖械薪懈褟 泻芯薪褎谢懈泻褌芯胁, 褌邪泻 褔褌芯 薪械泻芯褌芯褉褘械 懈蟹 懈薪褌械谐褉邪褑懈泄 褋 Pebble 褉邪斜芯褌邪褌褜 薪械 斜褍写褍褌, 蟹邪褌芯 械褢 屑芯卸薪芯 褍褋褌邪薪芯胁懈褌褜 褉褟写芯屑 褋 褍卸械 懈屑械褞褖懈屑懈褋褟 褍褋褌邪薪芯胁泻邪屑懈 Gadgetbridge.</string>
+    <string name="about_description_banglejs_main">袩褉懈谢芯卸械薪懈械-泻芯屑锌邪薪褜芯薪 写谢褟 Bangle.js 锌芯褋褌褉芯械薪芯 薪邪 芯褋薪芯胁械 锌褉芯械泻褌邪 Gadgetbridge, 褋 写芯斜邪胁谢械薪薪褘屑 写芯褋褌褍锌芯屑 胁 袠薪褌械褉薪械褌.
+\n
+\n袙 褋芯芯褌胁械褌褋褌胁懈懈 褋 锌芯谢懈褌懈泻芯泄 Google Play Store, 屑褘 薪械 屑芯卸械屑 褉邪蟹屑械褋褌懈褌褜 褋褋褘谢泻褍 写谢褟 锌芯卸械褉褌胁芯胁邪薪懈泄 胁 褋邪屑芯屑 锌褉懈谢芯卸械薪懈懈, 薪芯 械褋谢懈 胁邪屑 薪褉邪胁懈褌褋褟 褝褌芯 锌褉懈谢芯卸械薪懈械, 锌芯卸邪谢褍泄褋褌邪, 褉邪褋褋屑芯褌褉懈褌械 胁芯蟹屑芯卸薪芯褋褌褜 锌芯卸械褉褌胁芯胁邪薪懈褟 褔械褉械蟹 写芯屑邪褕薪褞褞 褋褌褉邪薪懈褑褍 Gadgetbridge 薪懈卸械.</string>
+    <string name="about_activity_title_banglejs_main">袨 Bangle.js Gadgetbridge</string>
+    <string name="gadgetbridge_running_banglejs_main">Bangle.js 胁褘锌芯谢薪褟械褌褋褟</string>
+    <string name="title_activity_controlcenter_banglejs_nopebble">Bangle.js Gadgetbridge</string>
+    <string name="title_activity_controlcenter_main_nightly">袧芯褔薪邪褟 褋斜芯褉泻邪 Gadgetbridge</string>
+    <string name="gadgetbridge_running_main_nightly">Nightly GB 蟹邪锌褍褖械薪</string>
+    <string name="about_activity_title_main_nopebble">袨 薪芯褔薪芯泄 褋斜芯褉泻械 Gadgetbridge 斜械蟹 Pebble</string>
+    <string name="pref_header_connection">小芯械写懈薪械薪懈械</string>
+    <string name="pref_header_display">袛懈褋锌谢械泄</string>
+    <string name="pref_header_health">袟写芯褉芯胁褜械</string>
+    <string name="pref_header_time">袙褉械屑褟</string>
+    <string name="pref_header_workout">孝褉械薪懈褉芯胁泻懈</string>
+    <string name="pref_summary_canned_replies">袨褌胁械褔邪褌褜 褋 褔邪褋芯胁 锌褉懈 锌芯屑芯褖懈 锌褉械写褍褋褌邪薪芯胁谢械薪薪褘褏 褋芯芯斜褖械薪懈泄</string>
+    <string name="pref_cache_weather">袣褝褕懈褉芯胁邪褌褜 写邪薪薪褘械 芯 锌芯谐芯写械</string>
+    <string name="open_fw_installer_getting_files_title">袩芯谢褍褔械薪懈械 褎邪泄谢邪 锌褉芯褕懈胁泻懈/锌褉懈谢芯卸械薪懈褟</string>
+    <string name="open_fw_installer_info_text_title">校褋褌邪薪芯胁褖懈泻 褎邪泄谢芯胁</string>
+    <string name="open_fw_installer_ensure_device_connected">校斜械写懈褌械褋褜, 褔褌芯 褍褋褌褉芯泄褋褌胁芯 %s 锌芯写泻谢褞褔械薪芯</string>
+    <string name="pref_chart_sleep_lines_limit">小泻芯谢褜泻芯 褋褌褉芯泻 褋械邪薪褋邪 褋薪邪 锌芯泻邪蟹褘胁邪褌褜 锌械褉械写 懈褏 锌褉芯泻褉褍褌泻芯泄</string>
+    <string name="pref_summary_device_internet_access">袪邪蟹褉械褕懈褌褜 锌褉懈谢芯卸械薪懈褟屑 薪邪 褝褌芯屑 褍褋褌褉芯泄褋褌胁械 写芯褋褌褍锌 泻 袠薪褌械褉薪械褌褍</string>
+    <string name="pref_cache_weather_summary">袛邪薪薪褘械 芯 锌芯谐芯写械 斜褍写褍褌 泻褝褕懈褉芯胁邪褌褜褋褟 胁芯 胁褉械屑褟 锌械褉械蟹邪锌褍褋泻邪 锌褉懈谢芯卸械薪懈褟.</string>
+    <string name="activity_prefs_discovery_pairing">袩邪褉邪屑械褌褉褘 芯斜薪邪褉褍卸械薪懈褟 懈 褋芯锌褉褟卸械薪懈褟</string>
+    <string name="permission_display_over_other_apps">%1$s 薪械芯斜褏芯写懈屑芯 褉邪蟹褉械褕械薪懈械 薪邪 芯褌芯斜褉邪卸械薪懈械 锌芯胁械褉褏 写褉褍谐懈褏 锌褉懈谢芯卸械薪懈泄, 褔褌芯斜褘 褔邪褋褘 Bangle.js 屑芯谐谢懈 胁褘锌芯谢薪褟褌褜 写械泄褋褌胁懈褟 褔械褉械蟹 薪邪屑械褉械薪懈褟, 泻芯谐写邪 %1$s 薪邪褏芯写懈褌褋褟 胁 褎芯薪芯胁芯屑 褉械卸懈屑械.
+\n
+\n协褌芯 屑芯卸薪芯 懈褋锌芯谢褜蟹芯胁邪褌褜 写谢褟 蟹邪锌褍褋泻邪 锌褉懈谢芯卸械薪懈褟 胁芯褋锌褉芯懈蟹胁械写械薪懈褟 懈 锌褉芯褋谢褍褕懈胁邪薪懈褟 屑褍蟹褘泻懈, 邪 褌邪泻卸械 写谢褟 屑薪芯谐懈褏 写褉褍谐懈褏 写械泄褋褌胁懈泄.
+\n
+\n袩芯卸邪谢褍泄褋褌邪, 薪邪卸屑懈褌械 芦%2$s禄, 蟹邪褌械屑 芦%1$s禄 懈 胁泻谢褞褔懈褌械 芦袩芯胁械褉褏 写褉褍谐懈褏 锌褉懈谢芯卸械薪懈泄禄, 蟹邪褌械屑 薪邪卸屑懈褌械 芦袧邪蟹邪写禄 褔褌芯斜褘 胁械褉薪褍褌褜褋褟 泻 %1$s.
+\n
+\n效褌芯斜褘 锌褉械泻褉邪褌懈褌褜 蟹邪锌褉芯褋褘 褉邪蟹褉械褕械薪懈泄 芯褌 %1$s, 锌械褉械泄写懈褌械 胁 袧邪褋褌褉芯泄泻懈 懈 胁褘泻谢褞褔懈褌械 芦袩褉芯胁械褉褟褌褜 褋褌邪褌褍褋 褉邪蟹褉械褕械薪懈泄禄.
+\n
+\n袛谢褟 泻芯褉褉械泻褌薪芯泄 褉邪斜芯褌褘 %1$s 写芯谢卸薪褘 斜褘褌褜 胁褘写邪薪褘 胁褋械 薪械芯斜褏芯写懈屑褘械 褉邪蟹褉械褕械薪懈褟.</string>
+    <string name="permission_notification_listener">袛谢褟 芯褌芯斜褉邪卸械薪懈褟 褍胁械写芯屑谢械薪懈泄 薪邪 褔邪褋邪褏 锌褉懈 胁褘泻谢褞褔械薪薪芯屑 褝泻褉邪薪械 %1$s 褌褉械斜褍械褌褋褟 写芯褋褌褍锌 泻 校胁械写芯屑谢械薪懈褟屑.
+\n
+\n袩芯卸邪谢褍泄褋褌邪, 薪邪卸屑懈褌械 芦%2$s禄, 蟹邪褌械屑 芦%1$s禄 懈 胁泻谢褞褔懈褌械 芦袛芯褋褌褍锌 泻 褍胁械写芯屑谢械薪懈褟屑禄, 蟹邪褌械屑 薪邪卸屑懈褌械 芦袧邪蟹邪写禄, 褔褌芯斜褘 胁械褉薪褍褌褜褋褟 泻 %1$s.</string>
+    <string name="fw_upgrade_notice_miband7">袙褘 褋芯斜懈褉邪械褌械褋褜 褍褋褌邪薪芯胁懈褌褜 锌褉芯褕懈胁泻褍 %s 薪邪 褋胁芯泄 Xiaomi Smart Band 7.
+\n
+\n袩芯褋谢械 褍褋褌邪薪芯胁泻懈 褎邪泄谢邪 .zip 胁邪褕 斜褉邪褋谢械褌 斜褍写械褌 锌械褉械蟹邪谐褉褍卸械薪.
+\n
+\n袙蝎 袛袝袡小孝袙校袝孝袝 袧袗 小袙袨袡 小孝袪袗啸 袠 袪袠小袣!</string>
+    <string name="open_fw_installer_warning_title">袩褉械写褍锌褉械卸写械薪懈械</string>
+    <string name="open_fw_installer_warning_text">协褌邪 褎褍薪泻褑懈褟 屑芯卸械褌 锌褉懈胁械褋褌懈 泻 锌芯谢芯屑泻械 胁邪褕械谐芯 褍褋褌褉芯泄褋褌胁邪. 袨写薪邪泻芯 褌邪泻芯谐芯 薪懈泻芯谐写邪 薪械 褋谢褍褔邪谢芯褋褜 薪懈 褋 芯写薪懈屑 懈蟹 褉邪蟹褉邪斜芯褌褔懈泻芯胁 懈蟹-蟹邪 锌械褉械锌褉芯褕懈胁泻懈, 薪芯 锌芯屑薪懈褌械, 褔褌芯 胁褘 写械谢邪械褌械 褝褌芯 薪邪 褋胁芯泄 褋褌褉邪褏 懈 褉懈褋泻.</string>
+    <string name="pref_header_calendar">袣邪谢械薪写邪褉褜</string>
+    <string name="pref_title_screen_on_on_notifications">袙泻谢褞褔械薪懈械 褝泻褉邪薪邪 锌褉懈 褍胁械写芯屑谢械薪懈懈</string>
+    <string name="pref_header_authentication">袗褍褌械薪褌懈褎懈泻邪褑懈褟</string>
+    <string name="pref_summary_screen_on_on_notifications">袙泻谢褞褔邪褌褜 褝泻褉邪薪 褔邪褋芯胁, 泻芯谐写邪 锌芯褋褌褍锌邪械褌 芯锌芯胁械褖械薪懈械</string>
+    <string name="dismiss">袨褌泻谢芯薪懈褌褜</string>
+    <string name="pref_title_notification_use_as_deny">袟邪锌褉械褌懈褌褜 胁褘斜褉邪薪薪褘屑 锌褉懈谢芯卸械薪懈褟屑 锌芯泻邪蟹褘胁邪褌褜 褍胁械写芯屑谢械薪懈褟</string>
+    <string name="permission_notification_policy_access">%1$s 薪械芯斜褏芯写懈屑芯 锌褉械写芯褋褌邪胁懈褌褜 写芯褋褌褍锌 泻 褎褍薪泻褑懈懈 袧械 斜械褋锌芯泻芯懈褌褜 写谢褟 褌芯谐芯, 褔褌芯斜褘 斜褘谢邪 胁芯蟹屑芯卸薪芯褋褌褜 褔褌械薪懈褟 薪邪 褔邪褋邪褏, 泻芯谐写邪 褝泻褉邪薪 褌械谢械褎芯薪邪 胁褘泻谢褞褔械薪.
+\n
+\n袩芯卸邪谢褍泄褋褌邪, 薪邪卸屑懈褌械 芦%2$s禄, 蟹邪褌械屑 芦%1$s禄 懈 胁泻谢褞褔懈褌械 芦袧械 斜械褋锌芯泻芯懈褌褜禄, 蟹邪褌械屑 薪邪卸屑懈褌械 芦袧邪蟹邪写禄, 褔褌芯斜褘 胁械褉薪褍褌褜褋褟 泻 %1$s.</string>
+    <string name="pref_title_banglejs_text_bitmap">孝械泻褋褌 胁 胁懈写械 褉邪褋褌褉芯胁褘褏 懈蟹芯斜褉邪卸械薪懈泄</string>
+    <string name="open_fw_installer_pick_file">袙褘斜褉邪褌褜 褎邪泄谢</string>
+    <string name="pref_title_device_internet_access">袪邪蟹褉械褕懈褌褜 写芯褋褌褍锌 胁 袠薪褌械褉薪械褌</string>
+    <string name="open_fw_installer_info_text">袩褉芯谐褉邪屑屑邪 褍褋褌邪薪芯胁泻懈 袩褉芯褕懈胁泻懈/效邪褋芯胁芯谐芯 懈薪褌械褉褎械泄褋邪/袩褉懈谢芯卸械薪懈褟/肖邪泄谢邪 锌芯蟹胁芯谢褟械褌 蟹邪谐褉褍卸邪褌褜/褍褋褌邪薪邪胁谢懈胁邪褌褜 锌芯写写械褉卸懈胁邪械屑褘械 褎邪泄谢褘 (锌褉芯褕懈胁泻褍, 褔邪褋芯胁褘械 懈薪褌械褉褎械泄褋褘, 锌褉懈谢芯卸械薪懈褟, GPS, 褉械褋褍褉褋褘, 褕褉懈褎褌褘...) 薪邪 褍褋褌褉芯泄褋褌胁芯. 袛芯锌芯谢薪懈褌械谢褜薪褍褞 懈薪褎芯褉屑邪褑懈褞 屑芯卸薪芯 薪邪泄褌懈 胁 胁懈泻懈: https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Firmware-Update</string>
+    <string name="fw_upgrade_notice_amazfit_gtr3">袙褘 褋芯斜懈褉邪械褌械褋褜 褍褋褌邪薪芯胁懈褌褜 锌褉芯褕懈胁泻褍 %s 薪邪 褋胁芯泄 Amazfit GTR 3.
+\n
+\n袩芯褋谢械 褍褋褌邪薪芯胁泻懈 褎邪泄谢邪 .zip 胁邪褕 斜褉邪褋谢械褌 斜褍写械褌 锌械褉械蟹邪谐褉褍卸械薪.
+\n
+\n袙蝎 袛袝袡小孝袙校袝孝袝 袧袗 小袙袨袡 小孝袪袗啸 袠 袪袠小袣!</string>
+    <string name="open_fw_installer_select_file">袙褘斜械褉懈褌械 褎邪泄谢, 泻芯褌芯褉褘泄 胁褘 褏芯褌懈褌械 蟹邪谐褉褍蟹懈褌褜 薪邪 褍褋褌褉芯泄褋褌胁芯: %s</string>
+    <string name="open_fw_installer_connect_minimum_one_device">袩芯卸邪谢褍泄褋褌邪, 锌芯写泻谢褞褔懈褌械 袣袗袣 袦袠袧袠袦校袦 袨袛袧袨 褍褋褌褉芯泄褋褌胁芯, 薪邪 泻芯褌芯褉芯械 胁褘 褏芯褌懈褌械 芯褌锌褉邪胁懈褌褜 褎邪泄谢.</string>
+    <string name="open_fw_installer_connect_maximum_one_device">袩芯卸邪谢褍泄褋褌邪, 锌芯写泻谢褞褔邪泄褌械 孝袨袥鞋袣袨 袨袛袧袨 褍褋褌褉芯泄褋褌胁芯, 薪邪 泻芯褌芯褉芯械 胁褘 褏芯褌懈褌械 芯褌锌褉邪胁懈褌褜 褎邪泄谢.</string>
+    <string name="pref_summary_banglejs_text_bitmap">袝褋谢懈 褋谢芯胁芯 薪械 屑芯卸械褌 斜褘褌褜 芯褌芯斜褉邪卸械薪芯 褕褉懈褎褌芯屑 褔邪褋芯胁, 锌褉械芯斜褉邪蟹芯胁褘胁邪褌褜 械谐芯 胁 褉邪褋褌褉芯胁芯械 懈蟹芯斜褉邪卸械薪懈械 胁 Gadgetbridge 懈 芯褌芯斜褉邪蟹懈褌褜 褉邪褋褌褉芯胁芯械 懈蟹芯斜褉邪卸械薪懈械 薪邪 褔邪褋邪褏</string>
+    <string name="fw_upgrade_notice_amazfit_gts3">袙褘 褋芯斜懈褉邪械褌械褋褜 褍褋褌邪薪芯胁懈褌褜 锌褉芯褕懈胁泻褍 %s 薪邪 褋胁芯泄 Amazfit GTS 3.
+\n
+\n袩芯褋谢械 褍褋褌邪薪芯胁泻懈 褎邪泄谢邪 .zip 胁邪褕 斜褉邪褋谢械褌 斜褍写械褌 锌械褉械蟹邪谐褉褍卸械薪.
+\n
+\n袙蝎 袛袝袡小孝袙校袝孝袝 袧袗 小袙袨袡 小孝袪袗啸 袠 袪袠小袣!</string>
+    <string name="pref_title_notifications_ignore_low_priority">袠谐薪芯褉懈褉芯胁邪褌褜 褍胁械写芯屑谢械薪懈褟 褋 薪懈蟹泻懈屑 锌褉懈芯褉懈褌械褌芯屑</string>
+    <string name="pref_summary_notifications_ignore_low_priority">袧械 芯褌锌褉邪胁谢褟褌褜 薪邪 褔邪褋褘 褍胁械写芯屑谢械薪懈褟 褋 薪懈蟹泻懈屑 懈 屑懈薪懈屑邪谢褜薪褘屑 锌褉懈芯褉懈褌械褌芯屑</string>
+    <string name="pref_title_notification_prefer_long_text">袩褉械写锌芯褔懈褌邪褌褜 写谢懈薪薪褘泄 褌械泻褋褌 褍胁械写芯屑谢械薪懈褟</string>
+    <string name="pref_summary_notification_prefer_long_text">袝褋谢懈 写芯褋褌褍锌薪芯, 芯褌锌褉邪胁谢褟褌褜 写谢懈薪薪褘泄 褌械泻褋褌 褍胁械写芯屑谢械薪懈褟 薪邪 褍褋褌褉芯泄褋褌胁芯</string>
+    <string name="uncheck_all_applications">小薪褟褌褜 胁褘斜芯褉 褋芯 胁褋械褏 锌褉懈谢芯卸械薪懈泄</string>
+    <string name="appmanager_item_outdated">(褍褋褌邪褉械谢芯)</string>
+    <string name="pref_title_banglejs_webview_url">URL-邪写褉械褋 蟹邪谐褉褍蟹褔懈泻邪 锌褉懈谢芯卸械薪懈褟</string>
+    <string name="check_all_applications">袨褌屑械褌懈褌褜 胁褋械 锌褉懈谢芯卸械薪懈褟</string>
+    <string name="pref_title_opentracks_packagename">袧邪蟹胁邪薪懈械 锌邪泻械褌邪 OpenTracks</string>
+    <string name="permission_location">袩褉懈谢芯卸械薪懈械 %1$s 薪褍卸写邪械褌褋褟 胁 写芯褋褌褍锌械 泻 胁邪褕械屑褍 屑械褋褌芯锌芯谢芯卸械薪懈褞 胁 褎芯薪芯胁芯屑 褉械卸懈屑械, 褔褌芯斜褘 芯褋褌邪胁邪褌褜褋褟 薪邪 褋胁褟蟹懈 褋 胁邪褕懈屑懈 褔邪褋邪屑懈 写邪卸械 锌褉懈 胁褘泻谢褞褔械薪薪芯屑 褝泻褉邪薪械.\n\n\n袩芯卸邪谢褍泄褋褌邪, 胁褘斜械褉懈褌械 \"%2$s\" 薪邪 褋谢械写褍褞褖械屑 褝泻褉邪薪械, 蟹邪褌械屑 薪邪卸屑懈褌械 \"袧邪蟹邪写\", 褔褌芯斜褘 胁械褉薪褍褌褜褋褟 泻 %1$s.</string>
+    <string name="pref_summary_opentracks_packagename">袠褋锌芯谢褜蟹褍械褌褋褟 写谢褟 蟹邪锌褍褋泻邪/芯褋褌邪薪芯胁泻懈 蟹邪锌懈褋懈 GPS-褌褉械泻邪 胁芯 胁薪械褕薪械屑 褎懈褌薪械褋-锌褉懈谢芯卸械薪懈懈.</string>
+    <string name="pref_title_notification_delay_calls">袟邪写械褉卸泻邪 褍胁械写芯屑谢械薪懈褟 芯 胁褘蟹芯胁械</string>
+    <string name="pref_title_notifications_generic_settings">袧邪褋褌褉芯泄泻懈 褍胁械写芯屑谢械薪懈泄 Android</string>
+    <string name="pref_summary_notification_delay_calls">袟邪写械褉卸泻邪 锌械褉械写 芯褌锌褉邪胁泻芯泄 褍胁械写芯屑谢械薪懈泄 芯 胁褏芯写褟褖懈褏 胁褘蟹芯胁邪褏 薪邪 褍褋褌褉芯泄褋褌胁芯, 胁 褋械泻褍薪写邪褏.</string>
+    <string name="open_fw_installer_getting_files_text">袩芯褋泻芯谢褜泻褍 褎邪泄谢褘 锌褉芯褕懈胁泻懈 屑褘 薪械 屑芯卸械屑 褉邪褋锌褉芯褋褌褉邪薪褟褌褜, 胁邪屑 锌褉懈写械褌褋褟 锌芯谢褍褔懈褌褜 懈褏 褋邪屑芯褋褌芯褟褌械谢褜薪芯. 协褌芯 芯蟹薪邪褔邪械褌, 褔褌芯 胁邪屑 锌褉懈写械褌褋褟 懈褋泻邪褌褜 褎邪泄谢褘 胁 apk-褎邪泄谢邪褏, 胁 袠薪褌械褉薪械褌械, 薪邪 褎芯褉褍屑邪褏, 薪邪 Amazfitwatchfaces (写谢褟 褍褋褌褉芯泄褋褌胁 Miband/Amazfit) 懈 褌邪泻 写邪谢械械.</string>
+    <string name="pref_title_banglejs_txt_bitmap_size">袪邪蟹屑械褉 褉邪褋褌褉芯胁芯谐芯 懈蟹芯斜褉邪卸械薪懈褟 褌械泻褋褌邪</string>
+    <string name="pref_summary_banglejs_txt_bitmap_size">袪邪蟹屑械褉 褉邪褋褌褉芯胁褘褏 懈蟹芯斜褉邪卸械薪懈泄 写谢褟 芯褌褉懈褋芯胁泻懈 褌械泻褋褌邪</string>
+    <string name="pref_blacklist_calendars_summary">袣邪谢械薪写邪褉懈 懈蟹 褔械褉薪芯谐芯 褋锌懈褋泻邪 薪械 斜褍写褍褌 褋懈薪褏褉芯薪懈蟹懈褉芯胁邪薪褘 褋 褍褋褌褉芯泄褋褌胁芯屑</string>
+    <string name="pref_title_overwrite_settings_on_connection">袩械褉械蟹邪锌懈褋褘胁邪褌褜 薪邪褋褌褉芯泄泻懈 锌褉懈 锌芯写泻谢褞褔械薪懈懈</string>
+    <string name="pref_summary_overwrite_settings_on_connection">袩褉懈 锌芯写泻谢褞褔械薪懈懈 泻 褔邪褋邪屑 锌械褉械蟹邪锌懈褋褘胁邪褌褜 薪邪 薪懈褏 胁褋械 薪邪褋褌褉芯泄泻懈.</string>
+    <string name="activity_type_hiking">袩械褕械褏芯写薪褘泄 褌褍褉懈蟹屑</string>
+    <string name="pref_title_equalizer_clear">效懈褋褌褘泄</string>
+    <string name="left_earbud">袥械胁褘泄 薪邪褍褕薪懈泻</string>
+    <string name="watchface_widget_type_2nd_tz">2-芯泄 褔邪褋芯胁芯泄 锌芯褟褋</string>
+    <string name="watchface_widget_type_chance_rain">袙械褉芯褟褌薪芯褋褌褜 写芯卸写褟</string>
+    <string name="watchface_setting_power_saving_display">袧械 芯斜薪芯胁谢褟褌褜 懈薪褎芯褉屑邪褑懈褞 薪邪 褝泻褉邪薪械, 泻芯谐写邪 褍褋褌褉芯泄褋褌胁芯 褋薪褟褌芯</string>
+    <string name="pref_title_equalizer_normal">袧芯褉屑邪谢褜薪褘泄</string>
+    <string name="pref_title_equalizer_bass_boost">校褋懈谢械薪懈械 斜邪褋芯胁</string>
+    <string name="enable_vibrations_summary">袙懈斜褉邪褑懈褟 锌褉懈 锌芯褋褌褍锌谢械薪懈懈 蟹胁芯薪泻芯胁, 褋芯芯斜褖械薪懈泄, 芯锌芯胁械褖械薪懈泄 懈 锌褉芯褔械谐芯</string>
+    <string name="enable_notifications_summary">袨锌芯胁械褖械薪懈械 锌褉懈 锌芯褋褌褍锌谢械薪懈懈 蟹胁芯薪泻芯胁, 褋芯芯斜褖械薪懈泄 懈 锌褉芯褔械谐芯</string>
+    <string name="prefs_autoheartrate_summary">袩械褉懈芯写懈褔械褋泻懈械 蟹邪屑械褉褘 褔邪褋褌芯褌褘 褋械褉写褑械斜懈械薪懈褟 薪邪 锌褉芯褌褟卸械薪懈懈 写薪褟 懈 胁芯 胁褉械屑褟 褋薪邪</string>
+    <string name="prefs_equalizer_summary">袙泻谢褞褔懈褌褜 懈谢懈 胁褘泻谢褞褔懈褌褜 褝泻胁邪谢邪泄蟹械褉</string>
+    <string name="prefs_ambient_voice_summary">袙褘写械谢懈褌褜 谐芯谢芯褋</string>
+    <string name="prefs_active_noise_cancelling">袗泻褌懈胁薪芯械 锌芯写邪胁谢械薪懈械 褕褍屑邪</string>
+    <string name="prefs_left">袥械胁褘泄</string>
+    <string name="prefs_active_noise_cancelling_summary">袩芯写邪胁谢褟褌褜 褕褍屑 芯泻褉褍卸邪褞褖械泄 褋褉械写褘</string>
+    <string name="right_earbud">袩褉邪胁褘泄 薪邪褍褕薪懈泻</string>
+    <string name="prefs_right">袩褉邪胁褘泄</string>
+    <string name="toast_app_must_be_selected">效褌芯斜褘 褋泻芯薪褎懈谐褍褉懈褉芯胁邪褌褜 锌褉懈谢芯卸械薪懈械, 胁褘斜械褉懈褌械 械谐芯</string>
+    <string name="pref_title_equalizer_soft">袦褟谐泻懈泄</string>
+    <string name="discover_unsupported_devices_description">袝褋谢懈 胁泻谢褞褔懈褌褜 褝褌芯褌 锌邪褉邪屑械褌褉, 胁芯 胁褉械屑褟 褋泻邪薪懈褉芯胁邪薪懈褟 斜褍写褍褌 锌芯泻邪蟹邪薪褘 胁褋械 芯斜薪邪褉褍卸械薪薪褘械 Bluetooth-褍褋褌褉芯泄褋褌胁邪. 袣芯褉芯褌泻芯械 泻邪褋邪薪懈械 锌褉懈胁械写械褌 泻 泻芯锌懈褉芯胁邪薪懈褞 懈屑械薪懈 褍褋褌褉芯泄褋褌胁邪 懈 mac-邪写褉械褋邪 胁 斜褍褎械褉 芯斜屑械薪邪. 袛谢懈褌械谢褜薪芯械 薪邪卸邪褌懈械 芯褌泻褉芯械褌 写懈邪谢芯谐芯胁芯械 芯泻薪芯 \"袛芯斜邪胁懈褌褜 褌械褋褌芯胁芯械 褍褋褌褉芯泄褋褌胁芯\". 袦芯卸械褌 锌褉懈胁械褋褌懈 泻 锌芯褌械薪褑懈邪谢褜薪褘屑 锌褉芯斜谢械屑邪屑 褋 蟹邪胁懈褋邪薪懈械屑 锌褉懈谢芯卸械薪懈泄.</string>
+    <string name="discovery_scanning_intensity">袠薪褌械薪褋懈胁薪芯褋褌褜 褋泻邪薪懈褉芯胁邪薪懈褟</string>
+    <string name="discovery_scanning_intensity_warning">袝褋谢懈 薪邪斜谢褞写邪械褌褋褟 蟹邪胁懈褋邪薪懈械 懈谢懈 芯褌褋褍褌褋褌胁懈械 褉械邪泻褑懈懈, 锌芯锌褉芯斜褍泄褌械 褍褋褌邪薪芯胁懈褌褜 斜芯谢械械 薪懈蟹泻懈泄 褍褉芯胁械薪褜 懈薪褌械薪褋懈胁薪芯褋褌懈 褋泻邪薪懈褉芯胁邪薪懈褟. 袝褋谢懈 褍褋褌褉芯泄褋褌胁芯 薪械 芯斜薪邪褉褍卸懈胁邪械褌褋褟, 锌芯锌褉芯斜褍泄褌械 褍褋褌邪薪芯胁懈褌褜 懈薪褌械薪褋懈胁薪芯褋褌褜 褋泻邪薪懈褉芯胁邪薪懈褟 薪邪 斜芯谢械械 胁褘褋芯泻懈泄 褍褉芯胁械薪褜.</string>
+    <string name="accuracy">孝芯褔薪芯褋褌褜</string>
+    <string name="balanced">小斜邪谢邪薪褋懈褉芯胁邪薪薪褘泄</string>
+    <string name="power_saving">协薪械褉谐芯褋斜械褉械卸械薪懈械</string>
+    <string name="custom">袩芯谢褜蟹芯胁邪褌械谢褜褋泻懈泄</string>
+    <string name="single_band">袨写薪芯锌芯谢芯褋薪褘泄</string>
+    <string name="dual_band">袛胁褍褏锌芯谢芯褋薪褘泄</string>
+    <string name="low_power_gps">GPS 褋 薪懈蟹泻懈屑 褝薪械褉谐芯锌芯褌褉械斜谢械薪懈械屑</string>
+    <string name="all_satellites">袙褋械 褋锌褍褌薪懈泻懈</string>
+    <string name="speed_first">袩褉懈芯褉懈褌械褌 褋泻芯褉芯褋褌懈</string>
+    <string name="accuracy_first">袩褉懈芯褉懈褌械褌 褌芯褔薪芯褋褌懈</string>
+    <string name="fw_upgrade_notice_amazfit_gtr4">袙褘 褋芯斜懈褉邪械褌械褋褜 褍褋褌邪薪芯胁懈褌褜 锌褉芯褕懈胁泻褍 %s 薪邪 褋胁芯泄 Amazfit GTR 4.
+\n
+\n袩芯褋谢械 褍褋褌邪薪芯胁泻懈 褎邪泄谢邪 .zip 胁邪褕 斜褉邪褋谢械褌 斜褍写械褌 锌械褉械蟹邪谐褉褍卸械薪.
+\n
+\n袙蝎 袛袝袡小孝袙校袝孝袝 袧袗 小袙袨袡 小孝袪袗啸 袠 袪袠小袣!</string>
+    <string name="pref_header_sound_vibration">袟胁褍泻 懈 胁懈斜褉邪褑懈褟</string>
+    <string name="pref_header_offline_voice">袚芯谢芯褋 胁 邪胁褌芯薪芯屑薪芯屑 褉械卸懈屑械</string>
+    <string name="pref_gps_combination">袣芯屑斜懈薪懈褉芯胁邪薪薪褘泄 GPS</string>
+    <string name="pref_gps_satellite_search">袩芯懈褋泻 褋锌褍褌薪懈泻芯胁</string>
+    <string name="pref_alert_tone">小懈谐薪邪谢 芯锌芯胁械褖械薪懈褟</string>
+    <string name="pref_text_to_speech">小懈薪褌械蟹邪褌芯褉 褉械褔懈</string>
+    <string name="offline_voice_respond_turn_wrist">袪械邪泻褑懈褟 锌褉懈 锌芯胁芯褉芯褌械 蟹邪锌褟褋褌褜褟</string>
+    <string name="discovery_bluetooth_scan">小泻邪薪懈褉芯胁邪薪懈械 Bluetooth 褍褋褌褉芯泄褋褌胁:</string>
+    <string name="activity_type_climbing">小泻邪谢芯谢邪蟹邪薪懈械</string>
+    <string name="pref_vibrate_for_alert">袙懈斜褉邪褑懈褟 写谢褟 芯锌芯胁械褖械薪懈褟</string>
+    <string name="about_description_banglejs_nightly">袩褉懈谢芯卸械薪懈械-泻芯屑锌邪薪褜芯薪 写谢褟 Android 写谢褟 Bangle.js, 锌芯褋褌褉芯械薪薪芯械 薪邪 芯褋薪芯胁械 锌褉芯械泻褌邪 Gadgetbridge, 褋 写芯锌芯谢薪懈褌械谢褜薪褘屑 写芯褋褌褍锌芯屑 胁 袠薪褌械褉薪械褌.
+\n
+\n袙 褋芯芯褌胁械褌褋褌胁懈懈 褋 锌芯谢懈褌懈泻芯泄 Google Play Store, 屑褘 薪械 屑芯卸械屑 褉邪蟹屑械褋褌懈褌褜 褋褋褘谢泻褍 写谢褟 锌芯卸械褉褌胁芯胁邪薪懈泄 胁 褋邪屑芯屑 锌褉懈谢芯卸械薪懈懈, 薪芯 械褋谢懈 胁邪屑 薪褉邪胁懈褌褋褟 褝褌芯 锌褉懈谢芯卸械薪懈械, 锌芯卸邪谢褍泄褋褌邪, 褉邪褋褋屑芯褌褉懈褌械 胁芯蟹屑芯卸薪芯褋褌褜 锌芯卸械褉褌胁芯胁邪薪懈褟 褔械褉械蟹 写芯屑邪褕薪褞褞 褋褌褉邪薪懈褑褍 Gadgetbridge 薪懈卸械.</string>
+    <string name="pref_gps_mode_preset">袪械卸懈屑 GPS</string>
+    <string name="pref_gps_band">袛懈邪锌邪蟹芯薪 GPS</string>
+    <string name="start">袧邪褔邪褌褜</string>
+    <string name="maxHR">袦邪泻褋懈屑邪谢褜薪褘泄 锌褍谢褜褋</string>
+    <string name="minHR">袦懈薪懈屑邪谢褜薪褘泄 锌褍谢褜褋</string>
+    <string name="prefs_autoheartrate_measurement">袗胁褌芯蟹邪屑械褉褘 褔邪褋褌芯褌褘 褋械褉写褑械斜懈械薪懈褟</string>
+    <string name="prefs_autoheartrate_sleep">袩褉芯胁芯写懈褌褜 蟹邪屑械褉褘 胁芯 胁褉械屑褟 褋薪邪</string>
+    <string name="prefs_galaxy_touch_options">袧邪褋褌褉芯泄泻懈 泻邪褋邪薪懈泄</string>
+    <string name="discovery_bluetooth_le_scan">小泻邪薪懈褉芯胁邪薪懈械 Bluetooth LE 褍褋褌褉芯泄褋褌胁:</string>
+    <string name="discover_unsupported_devices">袨斜薪邪褉褍卸械薪懈械 薪械锌芯写写械褉卸懈胁邪械屑褘褏 褍褋褌褉芯泄褋褌胁</string>
+    <string name="averageAltitude">小褉械写薪械械</string>
+    <string name="prefs_game_mode_summary">孝芯谢褜泻芯 械褋谢懈 胁邪褕 褌械谢械褎芯薪 锌芯写写械褉卸懈胁邪械褌 懈谐褉芯胁芯泄 褉械卸懈屑</string>
+    <string name="prefs_galaxy_buds_experimental">协泻褋锌械褉懈屑械薪褌邪谢褜薪褘械</string>
+    <string name="sony_ambient_sound_wind_noise_reduction">小薪懈卸械薪懈械 褕褍屑邪 芯褌 胁械褌褉邪</string>
+    <string name="controlcenter_get_heartrate_measurement">袠蟹屑械褉懈褌褜 褔邪褋褌芯褌褍 锌褍谢褜褋邪</string>
+    <string name="prefs_fm_preset_instructions">袧邪卸屑懈褌械 懈 褍写械褉卸懈胁邪泄褌械 泻薪芯锌泻褍 写谢褟 褋芯褏褉邪薪械薪懈褟 锌褉械写褍褋褌邪薪芯胁泻懈</string>
+    <string name="device_unsupported">袧袝 袩袨袛袛袝袪袞袠袙袗袝孝小携</string>
+    <string name="miband_prefs_reserve_reminder_calendar">袪械蟹械褉胁懈褉芯胁邪薪懈械 薪邪锌芯屑懈薪邪薪懈泄 写谢褟 锌褉械写褋褌芯褟褖懈褏 褋芯斜褘褌懈泄</string>
+    <string name="prefs_reserve_reminder_calendar_summary">袣芯谢懈褔械褋褌胁芯 褋芯斜褘褌懈泄 泻邪谢械薪写邪褉褟, 泻芯褌芯褉褘械 斜褍写褍褌 褋懈薪褏褉芯薪懈蟹懈褉芯胁邪薪褘</string>
+    <string name="mi2_prefs_do_not_disturb_lift_wrist">袙泻谢褞褔邪褌褜 写懈褋锌谢械泄 锌褉懈 锌芯写褗械屑械 褍褋褌褉芯泄褋褌胁邪, 泻芯谐写邪 褉械卸懈屑 \"袧械 斜械褋锌芯泻芯懈褌褜\" 胁泻谢褞褔械薪</string>
+    <string name="devicetype_nothingear1">袧械褔械谐芯 锌褉芯褋谢褍褕懈胁邪褌褜 (1)</string>
+    <string name="prefs_equalizer_preset">袩褉械写褍褋褌邪薪芯胁泻懈 褝泻胁邪谢邪泄蟹械褉邪</string>
+    <string name="pref_title_equalizer_trebble">校褋懈谢械薪懈械 袙效</string>
+    <string name="pref_title_equalizer_dynamic">袛懈薪邪屑懈褔薪褘泄</string>
+    <string name="appmanager_app_share">袩芯写械谢懈褌褜褋褟</string>
+    <string name="spo2_off">袙褘泻谢.</string>
+    <string name="pref_workout_send_gps_title">袨褌锌褉邪胁泻邪 GPS 胁芯 胁褉械屑褟 褌褉械薪懈褉芯胁泻懈</string>
+    <string name="pref_workout_start_on_phone_summary">袟邪锌褍褋泻/芯褋褌邪薪芯胁泻邪 芯褌褋谢械卸懈胁邪薪懈褟 褎懈褌薪械褋-锌褉懈谢芯卸械薪懈褟 薪邪 褌械谢械褎芯薪械 锌褉懈 蟹邪锌褍褖械薪薪芯泄 GPS-褌褉械薪懈褉芯胁泻械 薪邪 斜褉邪褋谢械褌械</string>
+    <string name="pref_screen_notification_idle_alerts">袨锌芯胁械褖械薪懈褟 芯 锌褉芯褋褌芯械</string>
+    <string name="heartrate_bpm_140">140 褍写/屑懈薪</string>
+    <string name="heartrate_bpm_145">145 褍写/屑懈薪</string>
+    <string name="world_clock_no_free_slots_title">袧械褌 褋胁芯斜芯写薪褘褏 褟褔械械泻</string>
+    <string name="heartrate_bpm_130">130 褍写/屑懈薪</string>
+    <string name="pref_gps_header">GPS</string>
+    <string name="pref_workout_send_gps_summary">袨褌锌褉邪胁泻邪 褌械泻褍褖械谐芯 屑械褋褌芯锌芯谢芯卸械薪懈褟 GPS 薪邪 斜褉邪褋谢械褌 胁芯 胁褉械屑褟 褌褉械薪懈褉芯胁泻懈</string>
+    <string name="pref_workout_detection_title">袨斜薪邪褉褍卸械薪懈械 褌褉械薪懈褉芯胁泻懈</string>
+    <string name="pref_workout_detection_summary">袗胁褌芯屑邪褌懈褔械褋泻芯械 芯斜薪邪褉褍卸械薪懈械 褌褉械薪懈褉芯胁泻懈</string>
+    <string name="pref_workout_detection_categories_title">袣邪褌械谐芯褉懈懈 褌褉械薪懈褉芯胁芯泻</string>
+    <string name="pref_workout_detection_categories_summary">袣邪褌械谐芯褉懈懈 褌褉械薪懈褉芯胁芯泻 写谢褟 邪胁褌芯屑邪褌懈褔械褋泻芯谐芯 芯斜薪邪褉褍卸械薪懈褟</string>
+    <string name="pref_workout_detection_alert_title">袨锌芯胁械褖械薪懈械</string>
+    <string name="pref_workout_detection_sensitivity">效褍胁褋褌胁懈褌械谢褜薪芯褋褌褜</string>
+    <string name="pref_sleep_mode_sleep_screen_title">协泻褉邪薪 褋薪邪</string>
+    <string name="pref_sleep_mode_smart_enable_title">校屑薪芯械 胁泻谢褞褔械薪懈械</string>
+    <string name="pref_sleep_mode_smart_enable_summary">袗胁褌芯屑邪褌懈褔械褋泻芯械 胁泻谢褞褔械薪懈械 褋锌褟褖械谐芯 褉械卸懈屑邪, 械褋谢懈 胁芯 胁褉械屑褟 褋薪邪 芯写械褌 斜褉邪褋谢械褌</string>
+    <string name="pref_sleep_mode_title">小锌褟褖懈泄 褉械卸懈屑</string>
+    <string name="buttons_on_left">袣薪芯锌泻懈 褋谢械胁邪</string>
+    <string name="buttons_on_right">袣薪芯锌泻懈 褋锌褉邪胁邪</string>
+    <string name="prefs_weardirection">袧邪锌褉邪胁谢械薪懈械 薪芯褕械薪懈褟</string>
+    <string name="vibration_profile_default">袩芯 褍屑芯谢褔邪薪懈褞</string>
+    <string name="pref_screen_vibration_patterns_summary">袧邪褋褌褉芯泄泻邪 锌褉芯褎懈谢械泄 胁懈斜褉邪褑懈懈 写谢褟 褉邪蟹谢懈褔薪褘褏 褍胁械写芯屑谢械薪懈泄</string>
+    <string name="heartrate_bpm_135">135 褍写/屑懈薪</string>
+    <string name="heartrate_bpm_150">150 褍写/屑懈薪</string>
+    <string name="spo2_perc_80">80%</string>
+    <string name="spo2_perc_85">85%</string>
+    <string name="world_clock_delete_confirm_title">校写邪谢懈褌褜 \'%1$s\'</string>
+    <string name="pref_screen_notification_profile_schedule">袪邪褋锌懈褋邪薪懈械</string>
+    <string name="pref_screen_vibration_patterns_title">袩褉芯褎懈谢懈 胁懈斜褉邪褑懈懈</string>
+    <string name="heartrate_bpm_40">40 褍写/屑懈薪</string>
+    <string name="pref_explanation_authkey">袧械泻芯褌芯褉褘械 褍褋褌褉芯泄褋褌胁邪 褌褉械斜褍褞褌 褋锌械褑懈邪谢褜薪芯谐芯 泻谢褞褔邪 褋芯锌褉褟卸械薪懈褟 写谢褟 锌械褉胁芯泄 懈薪懈褑懈邪谢懈蟹邪褑懈懈 褍褋褌褉芯泄褋褌胁邪. 袧邪卸屑懈褌械 蟹写械褋褜, 褔褌芯斜褘 褍蟹薪邪褌褜 锌芯写褉芯斜薪芯褋褌懈 薪邪 胁懈泻懈.</string>
+    <string name="pref_screen_notification_profile_todo_list">小锌懈褋芯泻 写械谢</string>
+    <string name="spo2_perc_90">90%</string>
+    <string name="pref_explanation_authkey_new_protocol">袝褋谢懈 胁褘 薪邪 斜褉邪褋谢械褌械 锌芯谢褍褔邪械褌械 褋芯芯斜褖械薪懈械 \"袨斜薪芯胁懈褌褜 锌褉懈谢芯卸械薪懈械 写芯 锌芯褋谢械写薪械泄 胁械褉褋懈懈\", 褍斜械写懈褌械褋褜, 褔褌芯 锌褉芯胁械褉懈谢懈 \"袧芯胁褘泄 锌褉芯褌芯泻芯谢 邪胁褌芯褉懈蟹邪褑懈懈\" 胁褘褕械. 袧邪卸屑懈褌械 蟹写械褋褜, 褔褌芯斜褘 褍蟹薪邪褌褜 锌芯写褉芯斜薪芯褋褌懈 薪邪 胁懈泻懈.</string>
+    <string name="pref_screen_notification_profile_find_device">袩芯懈褋泻 褍褋褌褉芯泄褋褌胁邪</string>
+    <string name="pref_workout_detection_alert_summary">校胁械写芯屑谢械薪懈械 锌褉懈 芯斜薪邪褉褍卸械薪懈懈 褌褉械薪懈褉芯胁泻懈</string>
+    <string name="heartrate_bpm_120">120 褍写/屑懈薪</string>
+    <string name="pref_screen_notification_profile_event_reminder">袧邪锌芯屑懈薪邪薪懈械 芯 褋芯斜褘褌懈懈</string>
+    <string name="heartrate_bpm_115">115 褍写/屑懈薪</string>
+    <string name="pref_sleep_mode_sleep_screen_summary">袩芯泻邪蟹褘胁邪褌褜 小锌褟褖懈泄 褝泻褉邪薪 锌褉懈 锌褉芯斜褍卸写械薪懈懈 胁 褉械卸懈屑械 褋薪邪 写谢褟 褋薪懈卸械薪懈褟 芯褌胁谢械褔械薪懈褟</string>
+    <string name="heartrate_bpm_100">100 褍写/屑懈薪</string>
+    <string name="heartrate_bpm_105">105 褍写/屑懈薪</string>
+    <string name="world_clock_delete_confirm_description">袙褘 褍胁械褉械薪褘, 褔褌芯 褏芯褌懈褌械 褍写邪谢懈褌褜 屑懈褉芯胁褘械 褔邪褋褘\?</string>
+    <string name="heartrate_bpm_45">45 褍写/屑懈薪</string>
+    <string name="heartrate_bpm_50">50 褍写/屑懈薪</string>
+    <string name="pref_write_logfiles_not_available">袠薪懈褑懈邪谢懈蟹邪褑懈褟 卸褍褉薪邪谢芯胁 褉械谐懈褋褌褉邪褑懈懈 褎邪泄谢芯胁 薪械 褍写邪谢邪褋褜, 蟹邪锌懈褋褜 褎邪泄谢芯胁 卸褍褉薪邪谢芯胁 胁 薪邪褋褌芯褟褖械械 胁褉械屑褟 薪械写芯褋褌褍锌薪邪. 袩械褉械蟹邪锌褍褋褌懈褌械 锌褉懈谢芯卸械薪懈械 写谢褟 锌芯胁褌芯褉薪芯泄 锌芯锌褘褌泻懈 懈薪懈褑懈邪谢懈蟹邪褑懈懈 褎邪泄谢芯胁 卸褍褉薪邪谢邪.</string>
+    <string name="pref_world_clocks_title">袦懈褉芯胁褘械 褔邪褋褘</string>
+    <string name="pref_world_clocks_summary">袧邪褋褌褉芯泄泻邪 褔邪褋芯胁 写谢褟 写褉褍谐懈褏 褔邪褋芯胁褘褏 锌芯褟褋芯胁</string>
+    <string name="heartrate_bpm_110">110 褍写/屑懈薪</string>
+    <string name="heartrate_bpm_125">125 褍写/屑懈薪</string>
+    <string name="appmanager_app_start">袧邪褔邪褌褜</string>
+    <string name="appmanager_download_started">小泻邪褔懈胁邪薪懈械 锌褉懈谢芯卸械薪懈褟 薪邪褔邪褌芯</string>
+    <string name="appmanager_downloaded_to_cache">小泻邪褔邪薪芯 胁 泻褝褕: %s</string>
+    <string name="appmanager_download_app_error">袨褕懈斜泻邪 锌褉懈 褋泻邪褔懈胁邪薪懈懈 锌褉懈谢芯卸械薪懈褟</string>
+    <string name="prefs_stress_monitoring_description">袧邪斜谢褞写械薪懈械 蟹邪 褍褉芯胁薪械屑 褋褌褉械褋褋邪 锌褉懈 芯褌写褘褏械</string>
+    <string name="prefs_spo2_monitoring_description">袗胁褌芯屑邪褌懈褔械褋泻芯械 薪邪斜谢褞写械薪懈械 蟹邪 褍褉芯胁薪械屑 泻懈褋谢芯褉芯写邪 胁 泻褉芯胁懈 胁 褌械褔械薪懈械 写薪褟</string>
+    <string name="prefs_activity_monitoring_title">袧邪斜谢褞写械薪懈械 蟹邪 邪泻褌懈胁薪芯褋褌褜褞</string>
+    <string name="prefs_activity_monitoring_description">袗胁褌芯屑邪褌懈褔械褋泻懈 锌芯胁褘褕邪褌褜 褔邪褋褌芯褌褍 蟹邪屑械褉芯胁 锌褍谢褜褋邪 锌褉懈 芯斜薪邪褉褍卸械薪懈懈 褎懈蟹懈褔械褋泻芯泄 邪泻褌懈胁薪芯褋褌懈 写谢褟 锌芯胁褘褕械薪懈褟 褌芯褔薪芯褋褌懈 蟹邪屑械褉芯胁.</string>
+    <string name="seconds_6">6 褋械泻褍薪写</string>
+    <string name="prefs_control_center">笑械薪褌褉 褍锌褉邪胁谢械薪懈褟</string>
+    <string name="prefs_password_4_digits_1_to_4_summary">袩邪褉芯谢褜 写芯谢卸械薪 褋芯褋褌芯褟褌褜 懈蟹 4 褑懈褎褉, 芯褌 1 写芯 4</string>
+    <string name="mi2_prefs_heart_rate_monitoring_alerts_summary">袧邪褋褌褉芯泄泻邪 芯褌褋谢械卸懈胁邪薪懈褟 褋械褉写褑械斜懈械薪懈褟 懈 锌芯褉芯谐邪 锌褉械写褍锌褉械卸写械薪懈褟</string>
+    <string name="czech">效械褕褋泻懈泄</string>
+    <string name="estonian">协褋褌芯薪褋泻懈泄</string>
+    <string name="extended_ascii">袪邪褋褕懈褉械薪薪褘泄 ASCII</string>
+    <string name="lithuanian">袥懈褌芯胁褋泻懈泄</string>
+    <string name="ukranian">校泻褉邪薪褋泻懈泄</string>
+    <string name="activity_prefs_target_weight_kg">袞械谢邪械屑褘泄 胁械褋 胁 泻谐</string>
+    <string name="activity_type_freestyle">小胁芯斜芯写薪褘泄 褋褌懈谢褜</string>
+    <string name="activity_type_pool_swimming">袩谢邪胁邪薪懈械 胁 斜邪褋褋械泄薪械</string>
+    <string name="activity_type_indoor_fitness">肖懈褌薪械褋 胁 锌芯屑械褖械薪懈懈</string>
+    <string name="activity_type_gymnastics">袚懈屑薪邪褋褌懈泻邪</string>
+    <string name="activity_type_stretching">袪邪褋褌褟卸泻邪</string>
+    <string name="activity_type_volleyball">袙芯谢械泄斜芯谢</string>
+    <string name="activity_type_kickboxing">袣懈泻斜芯泻褋懈薪谐</string>
+    <string name="activity_type_street_dance">校谢懈褔薪褘械 褌邪薪褑褘</string>
+    <string name="menuitem_email">协谢. 锌芯褔褌邪</string>
+    <string name="menuitem_countdown">袨斜褉邪褌薪褘泄 芯褌褋褔械褌</string>
+    <string name="menuitem_workout_status">小芯褋褌芯褟薪懈械 褌褉械薪懈褉芯胁泻懈</string>
+    <string name="menuitem_voice_memos">袚芯谢芯褋芯胁褘械 蟹邪屑械褌泻懈</string>
+    <string name="menuitem_sun_moon">小芯谢薪褑械 懈 袥褍薪邪</string>
+    <string name="menuitem_phone">孝械谢械褎芯薪</string>
+    <string name="menuitem_lockscreen">协泻褉邪薪 斜谢芯泻懈褉芯胁泻懈</string>
+    <string name="prefs_password_6_digits_0_to_9_summary">袩邪褉芯谢褜 写芯谢卸械薪 褋芯褋褌芯褟褌褜 懈蟹 6 褑懈褎褉</string>
+    <string name="prefs_password_summary">袘谢芯泻懈褉芯胁泻邪 斜褉邪褋谢械褌邪 锌邪褉芯谢械屑 锌褉懈 褋薪褟褌懈懈 褋 蟹邪锌褟褋褌褜褟</string>
+    <string name="activity_type_hiit">袠薪褌械薪褋懈胁薪邪褟 懈薪褌械褉胁邪谢褜薪邪褟 褌褉械薪懈褉芯胁泻邪</string>
+    <string name="prefs_fm_presets_presets">袩褉械写褍褋褌邪薪芯胁泻懈</string>
+    <string name="note">袩褉懈屑械褔邪薪懈械</string>
+    <string name="menuitem_screen_always_lit">袧械 胁褘泻谢褞褔邪褌褜 褝泻褉邪薪</string>
+    <string name="menuitem_theater_mode">袪械卸懈屑 \"孝械邪褌褉\"</string>
+    <string name="prefs_password_enabled">袩邪褉芯谢褜 胁泻谢褞褔械薪</string>
+    <string name="menuitem_todo">小锌懈褋芯泻 写械谢</string>
+    <string name="function_enabled">袙泻谢褞褔械薪芯</string>
+    <string name="world_clock_code">袣芯写</string>
+    <string name="prefs_stress_monitoring_title">袧邪斜谢褞写械薪懈械 蟹邪 褍褉芯胁薪械屑 褋褌褉械褋褋邪</string>
+    <string name="menuitem_calendar">袣邪谢械薪写邪褉褜</string>
+    <string name="prefs_heartrate_alert_high_threshold">袩芯褉芯谐 芯锌芯胁械褖械薪懈褟 芯 胁褘褋芯泻芯泄 褔邪褋褌芯褌械 褋械褉写褑械斜懈械薪懈褟</string>
+    <string name="activity_type_outdoor_cycling">袙械谢芯褋锌芯褉褌 薪邪 芯褌泻褉褘褌芯屑 胁芯蟹写褍褏械</string>
+    <string name="activity_type_core_training">袨褋薪芯胁薪褘械 褌褉械薪懈褉芯胁泻懈</string>
+    <string name="activity_type_table_tennis">袧邪褋褌芯谢褜薪褘泄 褌械薪薪懈褋</string>
+    <string name="activity_type_bowling">袘芯褍谢懈薪谐</string>
+    <string name="activity_type_boxing">袘芯泻褋</string>
+    <string name="notification_channel_gps">GPS-芯褌褋谢械卸懈胁邪薪懈械</string>
+    <string name="menuitem_workout_history">袠褋褌芯褉懈褟 褌褉械薪懈褉芯胁芯泻</string>
+    <string name="prefs_heartrate_alert_threshold">袩芯褉芯谐 芯锌芯胁械褖械薪懈褟 芯 褔邪褋褌芯褌械 褋械褉写褑械斜懈械薪懈褟</string>
+    <string name="prefs_spo2_monitoring_title">袧邪斜谢褞写械薪懈械 蟹邪 褍褉芯胁薪械屑 泻懈褋谢芯褉芯写邪 胁 泻褉芯胁懈</string>
+    <string name="prefs_relaxation_reminder_title">袧邪锌芯屑懈薪邪薪懈械 芯斜 芯褌写褘褏械</string>
+    <string name="activity_type_outdoor_running">袘械谐 薪邪 褋胁械卸械屑 胁芯蟹写褍褏械</string>
+    <string name="activity_type_indoor_walking">啸芯写褜斜邪 胁 锌芯屑械褖械薪懈懈</string>
+    <string name="menuitem_one_tap_measuring">袟邪屑械褉 芯写薪懈屑 薪邪卸邪褌懈械屑</string>
+    <string name="seconds_8">8 褋械泻褍薪写</string>
+    <string name="seconds_9">9 褋械泻褍薪写</string>
+    <string name="activity_type_elliptical">协谢谢懈锌褌懈褔械褋泻懈泄</string>
+    <string name="seconds_13">13 褋械泻褍薪写</string>
+    <string name="seconds_14">14 褋械泻褍薪写</string>
+    <string name="seconds_7">7 褋械泻褍薪写</string>
+    <string name="seconds_11">11 褋械泻褍薪写</string>
+    <string name="seconds_12">12 褋械泻褍薪写</string>
+    <string name="world_clock_label">袦械褌泻邪</string>
+    <string name="prefs_heartrate_alert_experimental_title">袨锌芯胁械褖械薪懈械 芯 褔邪褋褌芯褌械 锌褍谢褜褋邪 (褝泻褋锌械褉懈屑械薪褌邪谢褜薪芯)</string>
+    <string name="activity_type_dance">孝邪薪械褑</string>
+    <string name="menuitem_unknown_app">袧械懈蟹胁械褋褌薪芯 (%s)</string>
+    <string name="icelandic">袠褋谢邪薪写褋泻懈泄</string>
+    <string name="menuitem_brightness">携褉泻芯褋褌褜</string>
+    <string name="debugactivity_confirm_remove_device_preferences_title">校写邪谢懈褌褜 薪邪褋褌褉芯泄泻懈 褍褋褌褉芯泄褋褌胁邪\?</string>
+    <string name="debugactivity_confirm_remove_device_preferences">协褌芯 锌褉懈胁械写械褌 泻 褋斜褉芯褋褍 薪邪褋褌褉芯械泻 褍褋褌褉芯泄褋褌胁邪 写谢褟 胁褋械褏 锌芯写泻谢褞褔褢薪薪褘褏 褍褋褌褉芯泄褋褌胁. 袙褘 褍胁械褉械薪褘\?</string>
+    <string name="bengali">袘械薪谐邪谢褜褋泻懈泄</string>
+    <string name="world_clock_no_free_slots_description">校褋褌褉芯泄褋褌胁芯 薪械 懈屑械械褌 褋胁芯斜芯写薪褘褏 褋谢芯褌芯胁 写谢褟 屑懈褉芯胁褘褏 褔邪褋芯胁 (胁褋械谐芯 褋谢芯褌芯胁: %1$s)</string>
+    <string name="world_clock_timezone">效邪褋芯胁芯泄 锌芯褟褋</string>
+    <string name="title_activity_world_clock_details">袩芯写褉芯斜薪芯褋褌懈 屑懈褉芯胁褘褏 褔邪褋芯胁</string>
+    <string name="prefs_heartrate_alert_experimental_description">袙懈斜褉邪褑懈褟 斜褉邪褋谢械褌邪 锌褉懈 锌褉械胁褘褕械薪懈懈 锌芯褉芯谐芯胁芯谐芯 蟹薪邪褔械薪懈褟 锌褍谢褜褋邪 斜械蟹 褟胁薪芯泄 褎懈蟹懈褔械褋泻芯泄 邪泻褌懈胁薪芯褋褌懈 胁 褌械褔械薪懈械 锌芯褋谢械写薪懈褏 10 屑懈薪褍褌. 协褌邪 褎褍薪泻褑懈褟 褟胁谢褟械褌褋褟 褝泻褋锌械褉懈屑械薪褌邪谢褜薪芯泄 懈 薪械 锌褉芯褏芯写懈谢邪 褌褖邪褌械谢褜薪芯谐芯 褌械褋褌懈褉芯胁邪薪懈褟.</string>
+    <string name="prefs_heartrate_alert_low_threshold">袩芯褉芯谐 芯锌芯胁械褖械薪懈褟 芯 薪懈蟹泻芯泄 褔邪褋褌芯褌械 褋械褉写褑械斜懈械薪懈褟</string>
+    <string name="pref_huami_truncate_fetch_operation_timestamps_title">小芯泻褉邪褖械薪懈械 胁褉械屑械薪薪褘褏 屑械褌芯泻 芯锌械褉邪褑懈泄 胁褘斜芯褉泻懈</string>
+    <string name="seconds_15">15 褋械泻褍薪写</string>
+    <string name="seconds_25">25 褋械泻褍薪写</string>
+    <string name="prefs_password">袩邪褉芯谢褜</string>
+    <string name="portuguese_br">袩芯褉褌褍谐邪谢褜褋泻懈泄 (袘褉邪蟹懈谢懈褟)</string>
+    <string name="portuguese_pt">袩芯褉褌褍谐邪谢褜褋泻懈泄 (袩芯褉褌褍谐邪谢懈褟)</string>
+    <string name="pref_workout_start_on_phone_title">袨褌褋谢械卸懈胁邪薪懈械 褎懈褌薪械褋-锌褉懈谢芯卸械薪懈泄</string>
+    <string name="persian">袩械褉褋懈写褋泻懈泄</string>
+    <string name="scandinavian">小泻邪薪写懈薪邪胁褋泻懈泄</string>
+    <string name="watchface_dialog_widget_cat_custom_widget">袩芯谢褜蟹芯胁邪褌械谢褜褋泻懈泄 胁懈写卸械褌</string>
+    <string name="watchface_dialog_title_widget">袧邪褋褌褉芯泄泻懈 胁懈写卸械褌邪</string>
+    <string name="nothing_prefs_inear_title">袨斜薪邪褉褍卸械薪懈械 褍褏邪</string>
+    <string name="sony_sound_position_front_left">袩械褉械写薪懈泄 谢械胁褘泄</string>
+    <string name="sony_anc_optimizer_status_starting">袟邪锌褍褋泻鈥�</string>
+    <string name="sony_sound_position_front">袩械褉械写</string>
+    <string name="appmanager_watchface_activate">袙泻谢褞褔懈褌褜</string>
+    <string name="appmanager_app_download">小泻邪褔邪褌褜 胁 泻褝褕</string>
+    <string name="pref_enable_unsupported_settings_title">袙泻谢褞褔懈褌褜 薪械锌芯写写械褉卸懈胁邪械屑褘械 薪邪褋褌褉芯泄泻懈</string>
+    <string name="mi2_prefs_heart_rate_monitoring_summary">袧邪褋褌褉芯懈褌褜 芯褌褋谢械卸懈胁邪薪懈械 褋械褉写械褔薪芯谐芯 褉懈褌屑邪</string>
+    <string name="prefs_always_on_display">协泻褉邪薪 胁褋械谐写邪 胁泻谢褞褔械薪</string>
+    <string name="prefs_always_on_display_summary">袙褋械谐写邪 写械褉卸邪褌褜 褝泻褉邪薪 褍褋褌褉芯泄褋褌胁邪 胁泻谢褞褔械薪薪褘屑</string>
+    <string name="prefs_activate_display_on_lift_sensitivity">效褍胁褋褌胁懈褌械谢褜薪芯褋褌褜</string>
+    <string name="activity_type_zumba">袟褍屑斜邪</string>
+    <string name="devicetype_amazfit_gts4">Amazfit GTS 4</string>
+    <string name="menuitem_unsupported">[袧袝 袩袨袛袛袝袪袞袠袙袗袝孝小携] %s</string>
+    <string name="watchface_dialog_widget_background">肖芯薪</string>
+    <string name="prefs_sleep_time_label">校泻邪蟹邪褌褜 褔邪褋褘 褋薪邪</string>
+    <string name="prefs_in_ear_detection_summary">袙芯褋锌褉芯懈蟹胁芯写懈褌褜 蟹胁芯薪泻懈 褔械褉械蟹 薪邪褍褕薪懈泻懈, 泻芯谐写邪 芯薪懈 薪邪褏芯写褟褌褋褟 胁 褍褕邪褏</string>
+    <string name="pref_title_touch_spotify_official_app">Spotify (褌芯谢褜泻芯 芯褎懈褑懈邪谢褜薪芯械 锌褉懈谢芯卸械薪懈械)</string>
+    <string name="pref_title_touch_spotify_galaxy_app">Spotify (褌芯谢褜泻芯 锌褉懈芯卸械薪懈械 Galaxy Wearable)</string>
+    <string name="pref_header_spo2">袣懈褋谢芯褉芯写 胁 泻褉芯胁懈</string>
+    <string name="pref_header_sony_anc_optimizer">袨锌褌懈屑懈蟹邪褑懈褟 锌芯写邪胁谢械薪懈褟 褕褍屑邪</string>
+    <string name="sony_anc_optimize_description">袧邪卸屑懈褌械 写谢褟 芯锌褌懈屑懈蟹邪褑懈懈 锌芯写邪胁谢械薪懈褟 褕褍屑邪.</string>
+    <string name="sony_sound_position_rear_left">袟邪写薪懈泄 谢械胁褘泄</string>
+    <string name="sony_anc_optimizer_status_not_running">袧械 蟹邪锌褍褖械薪芯</string>
+    <string name="sony_sound_position_rear_right">袟邪写薪懈泄 锌褉邪胁褘泄</string>
+    <string name="sony_surround_mode">袪械卸懈屑 芯斜褗械屑薪芯谐芯 蟹胁褍褔邪薪懈褟</string>
+    <string name="sony_warn_sbc_codec">袙薪懈屑邪薪懈械: 褝泻胁邪谢邪泄蟹械褉, 邪褍写懈芯 锌芯蟹懈褑懈褟 懈 薪邪褋褌褉芯泄泻懈 芯斜褗械屑薪芯褋褌懈 褉邪斜芯褌邪褞褌 褌芯谢褜泻芯 褋 SBC 泻芯写械泻芯屑.</string>
+    <string name="menuitem_nothing">袧懈褔械谐芯</string>
+    <string name="menuitem_volume">袚褉芯屑泻芯褋褌褜</string>
+    <string name="pref_title_upper_button_long_press_action">袛械泄褋褌胁懈械 锌褉懈 写芯谢谐芯屑 薪邪卸邪褌懈懈 薪邪 胁械褉褏薪褞褞 泻薪芯锌泻褍</string>
+    <string name="pref_header_heartrate_allday">袨褌褋谢械卸懈胁邪薪懈械 胁 褌械褔械薪懈械 胁褋械谐芯 写薪褟</string>
+    <string name="pref_voice_detect_duration_10">10 褋械泻褍薪写</string>
+    <string name="pref_header_heartrate_sleep">小芯薪</string>
+    <string name="pref_switch_controls_anc_off">袩芯写邪胁谢械薪懈械 鈫愨啋 袙褘泻谢褞褔械薪芯</string>
+    <string name="pref_agps_update_time">袙褉械屑褟 芯斜薪芯胁谢械薪懈褟 AGPS</string>
+    <string name="mi2_dnd_always">袙褋械谐写邪</string>
+    <string name="sony_anc_optimizer_status_atmospheric_pressure">袠蟹屑械褉械薪懈械 邪褌屑芯褋褎械褉薪芯谐芯 写邪胁谢械薪懈褟鈥�</string>
+    <string name="prefs_relaxation_reminder_description">袘褉邪褋谢械褌 斜褍写械褌 胁懈斜褉懈褉芯胁邪褌褜, 泻芯谐写邪 褍褉芯胁械薪褜 褋褌褉械褋褋邪 锌芯写薪懈屑械褌褋褟 胁褘褕械 80</string>
+    <string name="hybridhr_widget_bg_dashed_circle">袩褍薪泻褌懈褉薪褘泄 泻褉褍谐</string>
+    <string name="pref_voice_detect_duration_15">15 褋械泻褍薪写</string>
+    <string name="activity_type_pilates">袩懈谢邪褌械褋</string>
+    <string name="share_log_not_enabled_message">小薪邪褔邪谢邪 薪械芯斜褏芯写懈屑芯 胁泻谢褞褔懈褌褜 谢芯谐谐懈褉芯胁邪薪懈械 胁 袧邪褋褌褉芯泄泻邪褏 - 袟邪锌懈褋褘胁邪褌褜 谢芯谐懈</string>
+    <string name="hybridhr_widget_bg_thin_circle">孝芯薪泻懈泄 泻褉褍谐</string>
+    <string name="hybridhr_widget_bg_double_circle">袛胁芯泄薪芯泄 泻褉褍谐</string>
+    <string name="gps">GPS</string>
+    <string name="gps_bds">GPS + BDS</string>
+    <string name="gps_gnolass">GPS + 袚袥袨袧袗小小</string>
+    <string name="gps_galileo">GPS + GALILEO</string>
+    <string name="pref_agps_header">AGPS</string>
+    <string name="fw_upgrade_notice_amazfit_gts4">袙褘 褋芯斜懈褉邪械褌械褋褜 褍褋褌邪薪芯胁懈褌褜 锌褉芯褕懈胁泻褍 %s 薪邪 褋胁芯泄 Amazfit GTS 4.
+\n
+\n袩芯褋谢械 褍褋褌邪薪芯胁泻懈 .zip 褎邪泄谢邪 胁邪褕 斜褉邪褋谢械褌 斜褍写械褌 锌械褉械蟹邪谐褉褍卸械薪.
+\n
+\n袙蝎 袛袝袡小孝袙校袝孝袝 袧袗 小袙袨袡 小孝袪袗啸 袠 袪袠小袣!</string>
+    <string name="sony_anc_optimize_confirmation_title">袨锌褌懈屑懈蟹邪褑懈褟 锌芯写邪胁谢械薪懈褟 褕褍屑邪</string>
+    <string name="pref_balance">袘邪谢邪薪褋</string>
+    <string name="pref_voice_detect_duration_5">5 褋械泻褍薪写</string>
+    <string name="mi2_prefs_heart_rate_monitoring">袨褌褋谢械卸懈胁邪薪懈械 褋械褉写褑械斜懈械薪懈褟</string>
+    <string name="pref_title_third_party_app_device_settings">袪邪蟹褉械褕懈褌褜 褋褌芯褉芯薪薪懈屑 锌褉懈谢芯卸械薪懈褟屑 懈蟹屑械薪褟褌褜 薪邪褋褌褉芯泄泻懈</string>
+    <string name="sony_anc_optimize_title">袨锌褌懈屑懈蟹懈褉芯胁邪褌褜</string>
+    <string name="prefs_active_noise_cancelling_level">校褉芯胁械薪褜 邪泻褌懈胁薪芯谐芯 锌芯写邪胁谢械薪懈褟 褕褍屑邪</string>
+    <string name="prefs_active_noise_cancelling_level_high">袙褘褋芯泻懈泄</string>
+    <string name="prefs_active_noise_cancelling_level_low">袧懈蟹泻懈泄</string>
+    <string name="hrZoneFatBurn">小卸懈谐邪薪懈械 卸懈褉邪</string>
+    <string name="pref_title_touch_anc">袗泻褌懈胁薪芯械 锌芯写邪胁谢械薪懈械 褕褍屑邪</string>
+    <string name="pref_title_touch_voice_assistant">袚芯谢芯褋芯胁芯泄 邪褋褋懈褋褌械薪褌</string>
+    <string name="pref_header_stress">小褌褉械褋褋</string>
+    <string name="pref_header_heartrate_alerts">袨锌芯胁械褖械薪懈褟 芯 褔邪褋褌芯褌械 褋械褉写褑械斜懈械薪懈褟</string>
+    <string name="watchface_setting_button_toggle_widgets">袩械褉械泻谢褞褔懈褌褜 胁懈写卸械褌褘</string>
+    <string name="watchface_setting_button_toggle_backlight">袙泻谢褞褔懈褌褜 锌芯写褋胁械褌泻褍</string>
+    <string name="pref_title_touch_volume">袚褉芯屑泻芯褋褌褜</string>
+    <string name="devicetype_binary_sensor">袘懈薪邪褉薪褘泄 写邪褌褔懈泻</string>
+    <string name="pref_header_intent_api">Intent API</string>
+    <string name="offline_voice_respond_screen_on">袨褌胁械褔邪褌褜, 泻芯谐写邪 褝泻褉邪薪 胁泻谢褞褔械薪</string>
+    <string name="kind_app">袩褉懈谢芯卸械薪懈械</string>
+    <string name="pref_title_lower_button_short_press_action">袛械泄褋褌胁懈械 锌褉懈 斜褘褋褌褉芯屑 薪邪卸邪褌懈懈 薪邪 薪懈卸薪褞褞 泻薪芯锌泻褍</string>
+    <string name="devicetype_miband7">Xiaomi Smart Band 7</string>
+    <string name="devicetype_amazfit_gts3">Amazfit GTS 3</string>
+    <string name="devicetype_amazfit_gtr4">Amazfit GTR 4</string>
+    <string name="pref_cover_to_mute">袧邪泻褉褘褌褜 写谢褟 芯褌泻谢褞褔械薪懈褟 蟹胁褍泻邪</string>
+    <string name="fw_upgrade_notice_amazfit_band7">袙褘 褋芯斜懈褉邪械褌械褋褜 褍褋褌邪薪芯胁懈褌褜 锌褉芯褕懈胁泻褍 %s 薪邪 褋胁芯泄 Amazfit Band 7.
+\n
+\n袩芯褋谢械 褍褋褌邪薪芯胁泻懈 .zip 褎邪泄谢邪 胁邪褕 斜褉邪褋谢械褌 斜褍写械褌 锌械褉械蟹邪谐褉褍卸械薪.
+\n
+\n袙蝎 袛袝袡小孝袙校袝孝袝 袧袗 小袙袨袡 小孝袪袗啸 袠 袪袠小袣!</string>
+    <string name="fw_upgrade_notice_amazfit_gts4_mini">袙褘 褋芯斜懈褉邪械褌械褋褜 褍褋褌邪薪芯胁懈褌褜 锌褉芯褕懈胁泻褍 %s 薪邪 褋胁芯泄 Amazfit GTS 4 Mini.
+\n
+\n袩芯褋谢械 褍褋褌邪薪芯胁泻懈 .zip 褎邪泄谢邪 胁邪褕 斜褉邪褋谢械褌 斜褍写械褌 锌械褉械蟹邪谐褉褍卸械薪.
+\n
+\n袙蝎 袛袝袡小孝袙校袝孝袝 袧袗 小袙袨袡 小孝袪袗啸 袠 袪袠小袣!</string>
+    <string name="pref_enable_unsupported_settings_summary">协褌芯 芯褌泻褉芯械褌 写芯褋褌褍锌 泻芯 胁褋械屑 写芯褋褌褍锌薪褘屑 薪邪褋褌褉芯泄泻邪屑, 写邪卸械 褌械屑, 泻芯褌芯褉褘械 薪械 锌芯写写械褉卸懈胁邪褞褌褋褟 胁邪褕懈屑 褍褋褌褉芯泄褋褌胁芯屑. 协褌芯 屑芯卸械褌 锌褉懈胁械褋褌懈 泻 薪械褋褌邪斜懈谢褜薪芯泄 褉邪斜芯褌械 懈 褋斜芯褟屑 褍褋褌褉芯泄褋褌胁邪.</string>
+    <string name="prefs_seamless_connection_switch_summary">袩械褉械泻谢褞褔邪褌褜褋褟 屑械卸写褍 褍褋褌褉芯泄褋褌胁邪屑懈 邪胁褌芯屑邪褌懈褔械褋泻懈</string>
+    <string name="sony_sound_position_front_right">袩械褉械写薪懈泄 锌褉邪胁褘泄</string>
+    <string name="pref_summary_third_party_app_device_settings">袪邪蟹褉械褕懈褌褜 锌褉芯褔懈屑 褋褌芯褉芯薪薪懈屑 锌褉懈谢芯卸械薪懈褟屑 褍褋褌邪薪邪胁谢懈胁邪褌褜 薪邪褋褌褉芯泄泻懈 褍褋褌褉芯泄褋褌胁邪 锌芯 intents.</string>
+    <string name="devicetype_amazfit_gts4_mini">Amazfit GTS 4 Mini</string>
+    <string name="devicetype_amazfit_gtr_lite">Amazfit GTR Lite</string>
+    <string name="devicetype_sony_wf_1000xm3">Sony WF-1000XM3</string>
+    <string name="devicetype_sony_wf_1000xm4">Sony WF-1000XM4</string>
+    <string name="devicetype_sony_linkbuds_s">Sony LinkBuds S</string>
+    <string name="devicetype_galaxybuds_pro">Galaxy Buds Pro</string>
+    <string name="devicetype_galaxybuds_2">Galaxy Buds2</string>
+    <string name="devicetype_super_cars">Shell Racing</string>
+    <string name="error_setting_parent_folder">袨褕懈斜泻邪 蟹邪写邪薪懈褟 褉芯写懈褌械谢褜褋泻芯泄 锌邪锌泻懈: %s</string>
+    <string name="autoconnect_from_device_summary">袩芯写泻谢褞褔邪褌褜褋褟 胁 褋谢褍褔邪械 泻芯谐写邪 褋芯械写懈薪械薪懈械 懈薪懈褑懈懈褉芯胁邪薪芯 褍褋褌褉芯泄褋褌胁芯屑, 薪邪锌褉懈屑械褉, 薪邪褍褕薪懈泻邪屑懈</string>
+    <string name="devicetype_sony_wh_1000xm2">Sony WH-1000XM2</string>
+    <string name="info_no_devices_connected">袧械褌 锌芯写泻谢褞褔械薪薪褘褏 褍褋褌褉芯泄褋褌胁</string>
+    <string name="controlcenter_set_parent_folder">袟邪写邪褌褜 褉芯写懈褌械谢褜褋泻褍褞 锌邪锌泻褍</string>
+    <string name="controlcenter_set_folder_title">校泻邪卸懈褌械 锌邪锌泻褍 懈谢懈 褋芯蟹写邪泄褌械 薪芯胁褍褞</string>
+    <string name="autoconnect_from_device_title">袩芯写泻谢褞褔邪褌褜褋褟 锌褉懈 锌芯写泻谢褞褔械薪懈懈 褋 褍褋褌褉芯泄褋褌胁邪</string>
+    <string name="pref_title_device_intents">袪邪蟹褉械褕懈褌褜 懈褋锌芯谢褜蟹芯胁邪薪懈械 Intent</string>
+    <string name="devicetype_flipper_zero">Flipper zero</string>
+    <string name="gadgetbridge_running_main_nopebble">Nightly NoPebble GB 蟹邪锌褍褖械薪</string>
+    <string name="controlcenter_set_preferences">袟邪写邪褌褜 薪邪褋褌褉芯泄泻懈</string>
+    <string name="error_deleting_device">袨褕懈斜泻邪 褍写邪谢械薪懈褟 褍褋褌褉芯泄褋褌胁邪: %s</string>
+    <string name="controlcenter_add_new_folder">袛芯斜邪胁懈褌褜 薪芯胁褍褞 锌邪锌泻褍</string>
+    <string name="controlcenter_unset_folder">小斜褉芯褋懈褌褜 锌邪锌泻褍</string>
+    <string name="auto_reconnect_ble_title">袗胁褌芯屑邪褌懈褔械褋泻芯械 锌械褉械锌芯写泻谢褞褔械薪懈械</string>
+    <string name="title_activity_controlcenter_banglejs_nightly">Bangle.js Gadgetbridge (Nightly)</string>
+    <string name="about_activity_title_banglejs_nightly">袨 Bangle.js Gadgetbridge (Nightly)</string>
+    <string name="gadgetbridge_running_banglejs_nightly">Nightly Bangle.js 蟹邪锌褍褖械薪</string>
+    <string name="connection_over_bt_classic">袩芯写泻谢褞褔械薪懈械 褔械褉械蟹 芯斜褘褔薪褘泄 Bluetooth</string>
+    <string name="devicetype_asteroidos">AsteroidOS</string>
+    <string name="devicetype_amazfit_gtr3">Amazfit GTR 3</string>
+    <string name="info_connected_count">%d 褍褋褌褉芯泄褋褌胁 锌芯写泻谢褞褔械薪芯</string>
+    <string name="controlcenter_toggle_details">袩芯泻邪蟹邪褌褜 锌芯写褉芯斜薪芯褋褌懈</string>
+    <string name="controlcenter_folder_name">袠屑褟 锌邪锌泻懈:</string>
+    <string name="connection_over_ble">袩芯写泻谢褞褔械薪懈械 锌芯 BLE</string>
+    <string name="auto_reconnect_ble_summary">袩械褉懈芯写懈褔械褋泻懈 锌褘褌邪褌褜褋褟 锌械褉械锌芯写泻谢褞褔懈褌褜褋褟</string>
+    <string name="application_name_banglejs_nightly">Bangle.js Gadgetbridge (Nightly)</string>
+    <string name="devicetype_amazfit_band7">Amazfit Band 7</string>
+    <string name="activity_db_management_autoexport_enabled_yes">袗胁褌芯-褝泻褋锌芯褉褌 胁泻谢褞褔械薪.</string>
+    <string name="activity_db_management_autoexport_scheduled_no">袗胁褌芯-褝泻褋锌芯褉褌 薪械 斜褘谢 蟹邪锌谢邪薪懈褉芯胁邪薪.</string>
+    <string name="activity_prefs_allow_bluetooth_intent_api">Bluetooth Intent API</string>
+    <string name="menuitem_wifi">Wi-Fi</string>
+    <string name="autoExport_lastTime_label">袩芯褋谢械写薪懈泄 邪胁褌芯-褝泻褋锌芯褉褌: %1$s</string>
+    <string name="activity_db_management_autoexport_scheduled_yes">袗胁褌芯-褝泻褋锌芯褉褌 斜褘谢 懈蟹薪邪褔邪谢褜薪芯 蟹邪锌谢邪薪懈褉芯胁邪薪 薪邪 %1$s</string>
+    <string name="activity_db_management_autoexport_location">袧械 褍写邪谢芯褋褜 芯锌褉械写械谢懈褌褜 屑械褋褌芯锌芯谢芯卸械薪懈械. 袙芯蟹屑芯卸薪芯, 褝褌邪 锌褉芯斜谢械屑邪 褋胁褟蟹邪薪邪 褋 薪芯胁芯泄 褋懈褋褌械屑芯泄 褉邪蟹褉械褕械薪懈泄 胁 Android. 小泻芯褉械械 胁褋械谐芯, 邪胁褌芯褝泻褋锌芯褉褌 褋械泄褔邪褋 薪械 褉邪斜芯褌邪械褌.</string>
+    <string name="activity_db_management_autoexport_enabled_no">袗胁褌芯-褝泻褋锌芯褉褌 薪械 胁泻谢褞褔械薪.</string>
+    <string name="menuitem_bluetooth">Bluetooth</string>
+    <string name="prefs_screen_timeout">袙褉械屑褟 芯卸懈写邪薪懈褟 褝泻褉邪薪邪</string>
+    <string name="pref_workout_detection_enabled">袨斜薪邪褉褍卸械薪懈械 胁泻谢褞褔械薪芯</string>
+    <string name="fw_upgrade_notice_amazfit_trex2">袙褘 褋芯斜懈褉邪械褌械褋褜 褍褋褌邪薪芯胁懈褌褜 锌褉芯褕懈胁泻褍 %s 薪邪 褋胁芯泄 Amazfit T-Rex 2.
+\n
+\n袩芯褋谢械 褍褋褌邪薪芯胁泻懈 .zip 褎邪泄谢邪 胁邪褕 斜褉邪褋谢械褌 斜褍写械褌 锌械褉械蟹邪谐褉褍卸械薪.
+\n
+\n袙蝎 袛袝袡小孝袙校袝孝袝 袧袗 小袙袨袡 小孝袪袗啸 袠 袪袠小袣!</string>
+    <string name="pref_workout_detection_enabled_summary">袙泻谢褞褔懈褌褜 邪胁褌芯屑邪褌懈褔械褋泻芯械 芯斜薪邪褉褍卸械薪懈械 褝褌芯泄 褌褉械薪懈褉芯胁泻懈</string>
+    <string name="pref_workout_detection_ask_first">小薪邪褔邪谢邪 褋锌褉芯褋懈褌械 屑械薪褟</string>
+    <string name="pref_camera_remote_title">校写邪谢械薪薪邪褟 褎芯褌芯褋褗械屑泻邪</string>
+    <string name="fw_upgrade_notice_amazfit_gtr3_pro">袙褘 褋芯斜懈褉邪械褌械褋褜 褍褋褌邪薪芯胁懈褌褜 锌褉芯褕懈胁泻褍 %s 薪邪 袙邪褕 Amazfit GTR 3 Pro.
+\n
+\n袩芯褋谢械 褍褋褌邪薪芯胁泻懈 褎邪泄谢邪 .zip 胁邪褕 斜褉邪褋谢械褌 斜褍写械褌 锌械褉械蟹邪谐褉褍卸械薪.
+\n
+\n袙蝎 袛袝袡小孝袙校袝孝袝 袧袗 小袙袨袡 小孝袪袗啸 袠 袪袠小袣!</string>
+    <string name="abstract_chart_fragment_kind_rem_sleep">REM 褎邪蟹邪 褋薪邪</string>
+    <string name="pref_summary_receive_calls_watch">小芯胁械褉褕邪泄褌械 懈 锌褉懈薪懈屑邪泄褌械 蟹胁芯薪泻懈 锌褉褟屑芯 褋 褔邪褋芯胁</string>
+    <string name="pref_display_caller_title">袩芯泻邪蟹邪褌褜 泻芯薪褌邪泻褌薪褍褞 懈薪褎芯褉屑邪褑懈褞</string>
+    <string name="pref_display_caller_summary">袨褌芯斜褉邪卸邪褌褜 薪芯屑械褉 褌械谢械褎芯薪邪/懈屑褟 蟹胁芯薪褟褖械谐芯</string>
+    <string name="prefs_spo2_alert_threshold">袩芯褉芯谐 锌褉械写褍锌褉械卸写械薪懈褟 SPO2</string>
+    <string name="pref_pair_bluetooth_calls_help_3">3. 袙泻谢褞褔懈褌械 胁芯蟹屑芯卸薪芯褋褌褜 蟹胁芯薪泻芯胁 锌芯 Bluetooth 薪懈卸械.</string>
+    <string name="bluetooth_calls">袟胁芯薪泻懈 锌芯 Bluetooth</string>
+    <string name="bluetooth_calls_pairing">袩褉懈胁褟蟹泻邪 Bluetooth 蟹胁芯薪泻芯胁</string>
+    <string name="pref_pair_bluetooth_calls_title">袩褉懈胁褟蟹邪褌褜 写谢褟 蟹胁芯薪泻芯胁 锌芯 Bluetooth</string>
+    <string name="pref_pair_bluetooth_calls_summary">袧邪卸屑懈褌械 褋褞写邪, 褔褌芯斜褘 薪邪褔邪褌褜 锌褉芯褑械褋褋 锌褉懈胁褟蟹泻懈</string>
+    <string name="pref_pair_bluetooth_calls_help_summary">效褌芯斜褘 锌芯谢褍褔邪褌褜 蟹胁芯薪泻懈 锌芯 Bluetooth, 胁邪屑 褌褉械斜褍械褌褋褟 锌褉懈胁褟蟹邪褌褜 胁邪褕 褌械谢械褎芯薪 泻 胁褌芯褉芯屑褍 褝泻蟹械屑锌谢褟褉褍 褔邪褋芯胁.</string>
+    <string name="pref_pair_bluetooth_calls_help_2">2. 袟邪泄写懈褌械 胁 薪邪褋褌褉芯泄泻懈 Bluetooth 薪邪 胁邪褕械屑 褌械谢械褎芯薪械 懈 胁褘锌芯谢薪懈褌械 褋芯锌褉褟卸械薪懈械 褋 薪芯胁褘屑 褍褋褌褉芯泄褋褌胁芯屑 泻芯褌芯褉褘泄 锌芯褟胁懈褌褋褟 胁 褋锌懈褋泻械(锌芯褏芯卸械械 薪邪蟹胁邪薪懈械 泻邪泻 褍 胁邪褕懈褏 褌械泻褍褖懈褏 褔邪褋芯胁, 薪芯 褋 褋褍褎褎懈泻褋芯屑, 薪邪锌褉懈屑械褉 \"Amazfit GTR 4 - AFC8\").</string>
+    <string name="pref_pair_bluetooth_calls_help_warning">袙袧袠袦袗袧袠袝: 袝褋谢懈 胁褘 胁泻谢褞褔懈褌械 蟹胁芯薪泻懈 锌芯 Bluetooth 斜械蟹 褋芯锌褉褟卸械薪懈褟 胁褌芯褉芯谐芯 褝泻蟹械屑锌谢褟褉邪, 褍胁械写芯屑谢械薪懈褟 芯 蟹胁芯薪泻邪褏 屑芯谐褍褌 褉邪斜芯褌邪褌褜 薪械 褌邪泻, 泻邪泻 芯斜褘褔薪芯.</string>
+    <string name="pref_summary_device_intents">袪邪蟹褉械褕懈褌褜 Bangle.js 锌褉懈谢芯卸械薪懈褟屑 芯褌锌褉邪胁谢褟褌褜 袠薪褌械薪褌褘 Android, 懈 褉邪蟹褉械褕懈褌褜 写褉褍谐懈屑 锌褉懈谢芯卸械薪懈褟屑 薪邪 Android(泻邪泻 Tasker) 芯褌锌褉邪胁谢褟褌褜 写邪薪薪褘械 薪邪 Bangle.js 褔械褉械蟹 懈薪褌械薪褌 com.banglejs.uart.tx. 孝褉械斜褍械褌 褉邪蟹褉械褕械薪懈械 薪邪 芯褌芯斜褉邪卸械薪懈械 锌芯胁械褉褏 写褉褍谐懈褏 锌褉懈谢芯卸械薪懈泄 写谢褟 褉邪斜芯褌褘 胁 褎芯薪械.</string>
+    <string name="pref_title_notification_cache_while_disconnected">袣褝褕懈褉芯胁邪褌褜 泻芯谐写邪 胁薪械 褉邪写懈褍褋邪</string>
+    <string name="pref_summary_banglejs_webview_url">袝褋谢懈 胁褘 褏芯褌懈褌械 懈褋锌芯谢褜蟹芯胁邪褌褜 锌芯谢褜蟹芯胁邪褌械谢褜褋泻懈泄 蟹邪谐褉褍蟹褔懈泻 锌褉懈谢芯卸械薪懈褟, 锌芯屑械褋褌懈褌械 褋褞写邪 褋胁芯褞 https://鈥�/android.html 褋褋褘谢泻褍. 袙 锌褉芯褌懈胁薪芯屑 褋谢褍褔邪械 芯褋褌邪胁褜褌械 锌褍褋褌褘屑 写谢褟 https://banglejs.com/apps</string>
+    <string name="pref_header_navigation">袧邪胁懈谐邪褑懈褟</string>
+    <string name="action_changelog">袞褍褉薪邪谢 懈蟹屑械薪械薪懈泄</string>
+    <string name="pref_pair_bluetooth_calls_help_1">1. 袧邪卸屑懈褌械 薪邪 泻薪芯锌泻褍 薪懈卸械, 褔褌芯斜褘 薪邪褔邪褌褜 锌褉芯褑械褋褋 锌褉懈胁褟蟹泻懈.</string>
+    <string name="pref_summary_notification_cache_while_disconnected">袨褌锌褉邪胁谢褟褌褜 锌褉芯锌褍褖械薪薪褘械 褍胁械写芯屑谢械薪懈褟 泻芯谐写邪 褍褋褌褉芯泄褋褌胁芯 锌械褉械锌芯写泻谢褞褔邪械褌褋褟 锌芯褋谢械 薪邪褏芯卸写械薪懈褟 胁薪械 褉邪写懈褍褋邪</string>
+    <string name="pref_summary_banglejs_phone_gps_enbale">袠褋锌芯谢褜蟹芯胁邪褌褜 谐械芯谢芯泻邪褑懈褞 褌械谢械褎芯薪邪 写谢褟 锌械褉械蟹邪锌懈褋懈 写邪薪薪褘褏 芯 谐械芯谢芯泻邪褑懈懈 薪邪 斜褉邪褋谢械褌械</string>
+    <string name="pref_title_banglejs_phone_gps_network_only">袠褋锌芯谢褜蟹芯胁邪褌褜 懈薪褌械褉薪械褌 褌芯谢褜泻芯 写谢褟 芯锌褉械写械谢械薪懈褟 屑械褋褌芯锌芯谢芯卸械薪懈褟</string>
+    <string name="pref_title_banglejs_phone_gps_update_interval">效邪褋褌芯褌邪 芯斜薪芯胁谢械薪懈褟 写邪薪薪褘褏 GPS</string>
+    <string name="pref_summary_banglejs_phone_gps_update_interval">效邪褋褌芯褌邪 芯斜薪芯胁谢械薪懈褟 锌芯谢芯卸械薪懈褟 GPS 胁 屑褋</string>
+    <string name="bluetooth_calls_settings">袧邪褋褌褉芯泄泻懈 蟹胁芯薪泻芯胁 锌芯 Bluetooth</string>
+    <string name="pref_pair_bluetooth_calls_help_title">袣邪泻 锌芯谢褍褔邪褌褜 蟹胁芯薪泻懈 锌芯 Bluetooth</string>
+    <string name="pref_title_banglejs_phone_gps_enbale">袠褋锌芯谢褜蟹芯胁邪褌褜 GPS 褌械谢械褎芯薪邪</string>
+    <string name="pref_summary_banglejs_phone_gps_network_only">袨锌褉械写械谢褟褌褜 屑械褋褌芯锌芯谢芯卸械薪懈械 褌芯谢褜泻芯 锌芯 锌褉芯胁邪泄写械褉褍 懈薪褌械褉薪械褌邪. 协褌芯 锌芯蟹胁芯谢褟械褌 褋薪懈蟹懈褌褜 褝薪械褉谐芯锌芯褌褉械斜谢械薪懈械 褑械薪芯泄 褋薪懈卸械薪懈褟 褌芯褔薪芯褋褌懈. 孝褉械斜褍械褌褋褟 锌械褉械锌芯写泻谢褞褔械薪懈械.</string>
+    <string name="pref_crown_vibration">袙懈斜褉邪褑懈褟 泻芯褉芯薪泻懈</string>
+    <string name="pref_agps_expiry_reminder_enabled">袧邪锌芯屑懈薪邪薪懈械 芯斜 懈褋褌械褔械薪懈懈 褋褉芯泻邪 写械泄褋褌胁懈褟 AGPS</string>
+    <string name="pref_summary_notification_media_ignores_application_list">袨斜褉邪斜邪褌褘胁邪褌褜 屑械写懈邪 褍胁械写芯屑谢械薪懈褟 锌械褉械写 褋锌懈褋泻芯屑 锌褉懈谢芯卸械薪懈泄. 袝褋谢懈 褝褌芯褌 锌邪褉邪屑械褌褉 胁褘泻谢褞褔械薪, 屑械写懈邪 锌褉懈谢芯卸械薪懈褟 写芯谢卸薪褘 斜褘褌褜 褉邪蟹褉械褕械薪褘 胁 褋锌懈褋泻械 锌褉懈谢芯卸械薪懈泄, 褔褌芯斜褘 褝谢械屑械薪褌褘 褍锌褉邪胁谢械薪懈褟 屑褍谢褜褌懈屑械写懈邪 褉邪斜芯褌邪谢懈 薪邪 褍褋褌褉芯泄褋褌胁械.</string>
+    <string name="pref_title_notification_media_ignores_application_list">小锌懈褋芯泻 锌褉懈谢芯卸械薪懈泄 褋 懈谐薪芯褉懈褉褍械屑褘屑懈 屑械写懈邪 褍胁械写芯屑谢械薪懈褟屑懈</string>
+    <string name="offline_voice_response_during_screen_lighting">袪械邪谐懈褉芯胁邪褌褜 锌褉懈 胁泻谢褞褔械薪薪芯屑 褝泻褉邪薪械</string>
+    <string name="pref_agps_expiry_reminder_time">袙褉械屑褟 薪邪锌芯屑懈薪邪薪懈褟 芯斜 懈褋褌械褔械薪懈懈 褋褉芯泻邪 邪泻褌褍邪谢褜薪芯褋褌懈 AGPS</string>
+    <string name="pref_camera_remote_summary">袩芯蟹胁芯谢褟械褌 褔邪褋邪屑 胁泻谢褞褔邪褌褜 泻邪屑械褉褍 褌械谢械褎芯薪邪</string>
+    <string name="pref_morning_updates_title">袨斜薪芯胁谢械薪懈褟 锌芯 褍褌褉邪屑</string>
+    <string name="pref_morning_updates_summary">袨褌芯斜褉邪卸邪械褌 芯斜薪芯胁谢械薪懈褟 泻邪卸写芯械 褍褌褉芯</string>
+    <string name="pref_morning_updates_categories_title">袣邪褌械谐芯褉懈懈 芯斜薪芯胁谢械薪懈泄 锌芯 褍褌褉邪屑</string>
+    <string name="pref_morning_updates_categories_summary">小锌懈褋芯泻 泻邪褌械谐芯褉懈泄, 芯褌芯斜褉邪卸邪褞褖懈褏褋褟 泻邪卸写芯械 褍褌褉芯</string>
+    <string name="pref_sleep_breathing_quality_monitoring">袨褌褋谢械卸懈胁邪薪懈械 泻邪褔械褋褌胁邪 写褘褏邪薪懈褟 胁芯 褋薪械</string>
+    <string name="smart">校屑薪褘泄</string>
+    <string name="charts_legend_stress_average">小褉械写薪懈泄 褍褉芯胁械薪褜 褋褌褉械褋褋邪</string>
+    <string name="gpx_route_upload_complete">袟邪谐褉褍蟹泻邪 GPX 屑邪褉褕褉褍褌邪 蟹邪胁械褉褕械薪邪</string>
+    <string name="gpx_route_upload_in_progress">袟邪谐褉褍蟹泻邪 GPX 屑邪褉褕褉褍褌邪</string>
+    <string name="gpx_route_upload_failed">袧械 褍写邪谢芯褋褜 蟹邪谐褉褍蟹懈褌褜 GPX 屑邪褉褕褉褍褌</string>
+    <string name="devicetype_sony_wh_1000xm5">Sony WH-1000XM5</string>
+    <string name="pref_workout_keep_screen_on_title">袨褋褌邪胁谢褟褌褜 褝泻褉邪薪 胁泻谢褞褔械薪薪褘屑 胁芯 胁褉械屑褟 褌褉械薪懈褉芯胁泻懈</string>
+    <string name="georgian">袚褉褍蟹懈薪褋泻懈泄</string>
+    <string name="devicetype_amazfit_trex_2">Amazfit T-Rex 2</string>
+    <string name="pref_contacts_title">袣芯薪褌邪泻褌褘</string>
+    <string name="contact_delete_confirm_title">校写邪谢懈褌褜 泻芯薪褌邪泻褌</string>
+    <string name="activity_prefs_goal_standing_time_minutes">袝卸械写薪械胁薪邪褟 褑械谢褜: 胁褉械屑褟 褋褌芯褟 胁 屑懈薪褍褌邪褏</string>
+    <string name="prefs_always_on_display_style">小褌懈谢褜</string>
+    <string name="croatian">啸芯褉胁邪褌褋泻懈泄</string>
+    <string name="activity_prefs_goal_fat_burn_time_minutes">袝卸械写薪械胁薪邪褟 褑械谢褜: 胁褉械屑褟 褋卸懈谐邪薪懈褟 卸懈褉邪 胁 屑懈薪褍褌邪褏</string>
+    <string name="status">小褌邪褌褍褋</string>
+    <string name="normal">袧芯褉屑邪谢褜薪褘泄</string>
+    <string name="devicetype_amazfit_gtr3_pro">Amazfit GTR 3 Pro</string>
+    <string name="menuitem_forecast">袩褉芯谐薪芯蟹</string>
+    <string name="menuitem_last_workout">袩芯褋谢械写薪褟褟 褌褉械薪懈褉芯胁泻邪</string>
+    <string name="mi5_prefs_workout_activity_types">孝懈锌褘 褌褉械薪懈褉芯胁芯泻</string>
+    <string name="mi5_prefs_workout_activity_types_summary">袙褘斜械褉懈褌械 褌懈锌褘 邪泻褌懈胁薪芯褋褌械泄 写谢褟 芯褌芯斜褉邪卸械薪懈褟 薪邪 褝泻褉邪薪械 褌褉械薪懈褉芯胁芯泻</string>
+    <string name="stop">小褌芯锌</string>
+    <string name="pref_workout_keep_screen_on_summary">协泻褉邪薪 斜褍写械褌 芯褋褌邪胁邪褌褜褋褟 胁泻谢褞褔褢薪薪褘屑 胁芯 胁褉械屑褟 褌褉械薪懈褉芯胁泻懈, 邪 褟褉泻芯褋褌褜 斜褍写械褌 薪邪褋褌褉芯械薪邪 写谢褟 芯褌芯斜褉邪卸械薪懈褟 写邪薪薪褘褏 芯 褌褉械薪懈褉芯胁泻械 胁 褉械卸懈屑械 褉械邪谢褜薪芯谐芯 胁褉械屑械薪懈</string>
+    <string name="pref_workout_detection_time">袦懈薪褍褌褘 邪泻褌懈胁薪芯褋褌懈 锌械褉械写 芯斜薪邪褉褍卸械薪懈械屑</string>
+    <string name="pref_workout_detection_time_summary">袣芯谢懈褔械褋褌胁芯 屑懈薪褍褌, 胁 褌械褔械薪懈械 泻芯褌芯褉褘褏 褌褉械薪懈褉芯胁泻邪 写芯谢卸薪邪 锌褉芯写芯谢卸邪褌褜褋褟, 锌褉械卸写械 褔械屑 芯薪邪 斜褍写械褌 芯斜薪邪褉褍卸械薪邪</string>
+    <string name="pref_contacts_summary">袧邪褋褌褉芯懈褌褜 泻芯薪褌邪泻褌褘 薪邪 褔邪褋邪褏</string>
+    <string name="contact_delete_confirm_description">袙褘 褌芯褔薪芯 褏芯褌懈褌械 褍写邪谢懈褌褜 泻芯薪褌邪泻褌 芦%1$s禄?</string>
+    <string name="title_activity_contact_details">袣芯薪褌邪泻褌薪邪褟 懈薪褎芯褉屑邪褑懈褟</string>
+    <string name="fwapp_install_device_not_supported">肖邪泄谢 薪械 屑芯卸械褌 斜褘褌褜 褍褋褌邪薪芯胁谢械薪, 褍褋褌褉芯泄褋褌胁芯 薪械 锌芯写写械褉卸懈胁邪械褌褋褟.</string>
+    <string name="activity_error_share_failed">袧械 褍写邪谢芯褋褜 锌芯写械谢懈褌褜褋褟 褎邪泄谢芯屑.</string>
+    <string name="share_screenshot">袩芯写械谢懈褌褜褋褟 褋薪懈屑泻芯屑 褝泻褉邪薪邪</string>
+    <string name="screenshot_taken">小泻褉懈薪褕芯褌 褋写械谢邪薪</string>
+    <string name="menuitem_vo2_max">袦邪泻褋. VO鈧�</string>
+    <string name="menuitem_recommendation">袪械泻芯屑械薪写邪褑懈褟</string>
+    <string name="pref_workout_detection_ask_first_summary">孝褉械斜芯胁邪褌褜 锌芯写褌胁械褉卸写械薪懈褟 薪邪 褔邪褋邪褏 锌褉懈 芯斜薪邪褉褍卸械薪懈懈 褌褉械薪懈褉芯胁泻懈</string>
+    <string name="title_activity_set_contacts">袧邪褋褌褉芯懈褌褜 泻芯薪褌邪泻褌褘</string>
+    <string name="contact_no_free_slots_description">袧邪 褍褋褌褉芯泄褋褌胁械 薪械褌 褋胁芯斜芯写薪褘褏 褋谢芯褌芯胁 写谢褟 泻芯薪褌邪泻褌芯胁 (胁褋械谐芯 褋谢芯褌芯胁: %1$s)</string>
+    <string name="prefs_control_center_summary">袙褘斜械褉懈褌械 褝谢械屑械薪褌褘 胁 胁褘锌邪写邪褞褖械屑 褋锌懈褋泻械 褑械薪褌褉邪 褍锌褉邪胁谢械薪懈褟</string>
+    <string name="activity_type_indoor_ice_skating">袣邪褌邪薪懈械 薪邪 泻芯薪褜泻邪褏 胁 锌芯屑械褖械薪懈懈</string>
+    <string name="menuitem_aqi">袠薪写械泻褋 泻邪褔械褋褌胁邪 胁芯蟹写褍褏邪</string>
+    <string name="pref_agps_expire_time">袙褉械屑褟 懈褋褌械褔械薪懈褟 AGPS</string>
+    <string name="menuitem_wechat_pay">WeChat Pay</string>
+    <string name="notification_gps_title">Gadgetbridge GPS</string>
+    <string name="notification_gps_text">袨褌锌褉邪胁泻邪 GPS 屑械褋褌芯锌芯谢芯卸械薪懈褟 薪邪 褍褋褌褉芯泄褋褌胁邪 %1$d</string>
+    <string name="prefs_shortcut_cards">袣邪褉褌芯褔泻懈 斜褘褋褌褉芯谐芯 写芯褋褌褍锌邪</string>
+    <string name="kind_gpx_route">GPX 袦邪褉褕褉褍褌</string>
+    <string name="activity_type_rowing">袚褉械斜谢褟</string>
+    <string name="menuitem_female_health">袞械薪褋泻芯械 蟹写芯褉芯胁褜械</string>
+    <string name="menuitem_total_workout">袨斜褖邪褟 褌褉械薪懈褉芯胁泻邪</string>
+    <string name="menuitem_cards">袣邪褉褌芯褔泻懈</string>
+    <string name="menuitem_personal_activity_intelligence">袩械褉褋芯薪邪谢褜薪褘泄 袠薪写械泻褋 袗泻褌懈胁薪芯褋褌懈</string>
+    <string name="menuitem_mi_ai">MI AI</string>
+    <string name="menuitem_eject_water">袙褘褌芯谢泻薪褍褌褜 胁芯写褍</string>
+    <string name="menuitem_membership_cards">效谢械薪褋泻懈械 泻邪褉褌芯褔泻懈</string>
+    <string name="sensitive">效褍胁褋褌胁懈褌械谢褜薪褘泄</string>
+    <string name="zepp_os_watchface_red_fantasy">Red Fantasy</string>
+    <string name="ascentDistance">袪邪褋褋褌芯褟薪懈械 胁 谐芯褉褍</string>
+    <string name="hrZoneNa">袧/袛</string>
+    <string name="hrZoneExtreme">协泻褋褌褉械屑邪谢褜薪邪褟</string>
+    <string name="aerobicTrainingEffect">袗褝褉芯斜薪褘泄 褝褎褎械泻褌</string>
+    <string name="elevationGain">校胁械谢懈褔械薪懈械 胁褘褋芯褌褘</string>
+    <string name="hrZoneAerobic">袗褝褉芯斜薪邪褟</string>
+    <string name="zepp_os_watchface_rush">小锌械褕泻邪</string>
+    <string name="descentDistance">袪邪褋褋褌芯褟薪懈械 褋 谐芯褉褘</string>
+    <string name="elevationLoss">校屑械薪褜褕械薪懈械 胁褘褋芯褌褘</string>
+    <string name="HeartRateZones">袟芯薪褘 褋械褉写械褔薪芯谐芯 褉懈褌屑邪</string>
+    <string name="hrZoneAnaerobic">袗薪邪褝褉芯斜薪邪褟</string>
+    <string name="pref_huami_truncate_fetch_operation_timestamps_summary">小芯泻褉邪褌懈褌械 胁褉械屑械薪薪褘械 屑械褌泻懈 芯锌械褉邪褑懈懈 胁褘斜芯褉泻懈 写芯 屑懈薪褍褌. 袨褌泻谢褞褔懈褌械 褝褌芯褌 锌邪褉邪屑械褌褉, 褔褌芯斜褘 褋芯褏褉邪薪懈褌褜 胁褉械屑械薪薪褘械 屑械褌泻懈 胁 褋械泻褍薪写邪褏, 械褋谢懈 褍 胁邪褋 胁芯蟹薪懈泻薪褍褌 锌褉芯斜谢械屑褘 锌褉懈 锌芯谢褍褔械薪懈懈 芯褔械薪褜 泻芯褉芯褌泻懈褏 褌褉械薪懈褉芯胁芯泻.</string>
+    <string name="zepp_os_watchface_multiple_data">袧械褋泻芯谢褜泻芯 写邪薪薪褘褏</string>
+    <string name="zepp_os_watchface_minimalist">袦懈薪懈屑邪谢懈褋褌懈褔薪褘泄</string>
+    <string name="zepp_os_watchface_simplicity_data">袩褉芯褋褌褘械 写邪薪薪褘械</string>
+    <string name="zepp_os_watchface_business_style">袛械谢芯胁芯泄</string>
+    <string name="zepp_os_watchface_emerald_moonlight">小胁械褌 谢褍薪褘 袠蟹褍屑褉褍写薪褘泄</string>
+    <string name="zepp_os_watchface_rotating_earth">袟械屑谢褟 泻褉褍褌懈褌褋褟</string>
+    <string name="zepp_os_watchface_superposition">小褍锌械褉锌芯蟹懈褑懈褟</string>
+    <string name="yesterdays_activity">袙褔械褉邪褕薪褟褟 邪泻褌懈胁薪芯褋褌褜</string>
+    <string name="anaerobicTrainingEffect">袗薪邪褝褉芯斜薪褘泄 褝褎褎械泻褌</string>
+    <string name="currentWorkoutLoad">孝褉械薪懈褉芯胁芯褔薪邪褟 薪邪谐褉褍蟹泻邪</string>
+    <string name="maximumOxygenUptake">袦邪泻褋懈屑邪谢褜薪芯械 锌芯谐谢芯褖械薪懈械 泻懈褋谢芯褉芯写邪</string>
+    <string name="zepp_os_watchface_vibrant">携褉泻懈泄</string>
+    <string name="TrainingEffect">协褎褎械泻褌 褌褉械薪懈褉芯胁泻懈</string>
+    <string name="quick_alarm">袘褘褋褌褉芯械 芯锌芯胁械褖械薪懈械</string>
+    <string name="quick_alarm_description">袨锌芯胁械褖械薪懈械 懈蟹 胁懈写卸械褌邪</string>
+    <string name="hrZoneWarmUp">袪邪蟹屑懈薪泻邪</string>
+    <string name="sports_activity_confirm_delete_title">校写邪谢懈褌褜 %d 邪泻褌懈胁薪芯褋褌械泄</string>
+    <string name="pai_chart_per_month">PAI 胁 屑械褋褟褑</string>
+    <string name="pai_plus_num">+%d</string>
+    <string name="num_min">%d 屑懈薪褍褌</string>
+    <string name="pai_chart_per_week">PAI 胁 薪械写械谢褞</string>
+    <string name="prefs_shortcut_cards_summary">袣邪褉褌芯褔泻懈 褟褉谢褘泻芯胁 胁懈写薪褘 锌褉懈 褋胁邪泄锌械 胁锌褉邪胁芯 薪邪 褑懈褎械褉斜谢邪褌械. 袣芯谐写邪 锌褉懈谢芯卸械薪懈械 蟹邪锌褍褖械薪芯, 邪胁褌芯-褋谐械薪械褉懈褉芯胁邪薪薪褘械 泻邪褉褌芯褔泻懈 薪械 蟹邪褌褉芯薪褍褌褘 写邪薪薪芯泄 薪邪褋褌褉芯泄泻芯泄.</string>
+    <string name="activity_detail_share_gps_label">袩芯写械谢懈褌褜褋褟 GPS-褌褉械泻芯屑</string>
+    <string name="activity_detail_share_raw_summary">袩芯写械谢懈褌褜褋褟 袧械芯斜褉邪斜芯褌邪薪薪芯泄 小胁芯写泻芯泄</string>
+    <string name="activity_detail_share_raw_details">袩芯写械谢懈褌褜褋褟 袧械芯斜褉邪斜芯褌邪薪薪褘屑懈 袛械褌邪谢褟屑懈</string>
+    <string name="dev_tools">袠薪褋褌褉褍屑械薪褌褘 袪邪蟹褉邪斜芯褌褔懈泻邪</string>
+    <string name="prefs_always_on_display_follow_watchface">小褌懈谢褜 泻邪泻 薪邪 褑懈褎械褉斜谢邪褌械</string>
+    <string name="sports_activity_confirm_delete_description">袙褘 褌芯褔薪芯 褏芯褌懈褌械 褍写邪谢懈褌褜 %d 邪泻褌懈胁薪芯褋褌懈(-械泄)?</string>
+    <string name="menuitem_offline_voice">袨褎褎谢邪泄薪 谐芯谢芯褋</string>
+    <string name="pref_title_mb_intents">孝褉邪薪褋谢懈褉芯胁邪褌褜 袠薪褌械薪褌褘 袦械写懈邪-泻薪芯锌芯泻 袧邪锌褉褟屑褍褞</string>
+    <string name="pref_summary_mb_intents">袙泻谢褞褔懈褌械, 械褋谢懈 褍锌褉邪胁谢械薪懈械 屑械写懈邪 褍褋褌褉芯泄褋褌胁邪 薪械 褉邪斜芯褌邪械褌 写谢褟 芯锌褉械写械谢褢薪薪褘褏 锌褉懈谢芯卸械薪懈泄</string>
+    <string name="minCadence">袦懈薪懈屑邪谢褜薪邪褟 褔邪褋褌芯褌邪</string>
+    <string name="busy_task_fetch_stress_data">袩芯谢褍褔械薪懈械 写邪薪薪褘褏 芯 褋褌褉械褋褋械</string>
+    <string name="busy_task_fetch_sleep_respiratory_rate_data">袩芯谢褍褔械薪懈械 写邪薪薪褘褏 芯 褔邪褋褌芯褌械 写褘褏邪薪懈褟 胁芯 褋薪械</string>
+    <string name="fossil_hr_confirm_connection">袩芯卸邪谢褍泄褋褌邪, 锌芯写褌胁械褉写懈褌械 褋 褔邪褋芯胁</string>
+    <string name="busy_task_fetch_sports_details">袩芯谢褍褔械薪懈械 锌芯写褉芯斜薪芯褋褌械泄 褋锌芯褉褌邪</string>
+    <string name="step_streaks_achievements_sharing_message">袦芯懈 写薪械胁薪褘械 写芯褋褌懈卸械薪懈褟 锌芯 褕邪谐邪屑!</string>
+    <string name="prefs_hourly_chime">袝卸械褔邪褋薪褘械 泻褍褉邪薪褌褘</string>
+    <string name="prefs_hourly_chime_summary">效邪褋褘 斜褍写褍褌 锌懈褖邪褌褜 褉邪蟹 胁 褔邪褋</string>
+    <string name="activity_prefs_summary_allow_bluetooth_intent_api">袩芯蟹胁芯谢褟械褌 泻芯薪褌褉芯谢懈褉芯胁邪褌褜 褋芯械写懈薪械薪懈械 Bluetooth 褔械褉械蟹 API Intent</string>
+    <string name="ftp_server_status">小褌邪褌褍褋 褋械褉胁械褉邪 FTP</string>
+    <string name="fossil_hr_connection_not_confirmed">小芯械写懈薪械薪懈械 薪械 斜褘谢芯 锌芯写褌胁械褉卸写械薪芯 褋 褔邪褋芯胁. 袘褍写械褌 懈褋锌芯谢褜蟹芯胁邪薪 褉械卸懈屑 斜械蟹 邪褍褌械薪褌懈褎懈泻邪褑懈懈</string>
+    <string name="preferences_miband_1_2_settings" tools:ignore="TypographyFractions">袧邪褋褌褉芯泄泻懈 Mi Band 1/2</string>
+    <string name="preferences_miband_1_2_warning">袙薪懈屑邪薪懈械: 协褌懈 薪邪褋褌褉芯泄泻懈 斜褍写褍褌 锌褉懈屑械薪械薪褘 褌芯谢褜泻芯 泻 Mi Band 1 懈 2.</string>
+    <string name="steps_streaks_since_date">小 %s</string>
+    <string name="steps_streaks_total_steps_average_hint">袙褋械谐芯 胁 褋褉械写薪械屑 %d 褕邪谐芯胁 胁 写械薪褜</string>
+    <string name="wifi_hotspot_status">小褌邪褌褍褋 褌芯褔泻懈 写芯褋褌褍锌邪</string>
+    <string name="ftp_server_start_summary">袟邪锌褍褋泻邪泄褌械 褋械褉胁械褉 褋 褔邪褋芯胁</string>
+    <string name="ftp_server_root_dir">袣芯褉薪械胁邪褟 锌邪锌泻邪</string>
+    <string name="ftp_server_stop_summary">袨褋褌邪薪邪胁谢懈胁邪泄褌械 褋械褉胁械褉 褋 褔邪褋芯胁</string>
+    <string name="description">袨锌懈褋邪薪懈械</string>
+    <string name="steps_streaks_streak_days">袩芯写褉褟写
+\n袛薪械泄</string>
+    <string name="ftp_server_configuration">袣芯薪褎懈谐褍褉邪褑懈褟 褋械褉胁械褉邪 FTP</string>
+    <string name="steps_streaks_total_steps_hint">袙褋械谐芯 褕邪谐芯胁 蟹邪 褏芯卸写械薪懈械 胁 写薪懈 锌芯写褉褟写</string>
+    <string name="step_streak_ongoing">孝械泻褍褖懈泄</string>
+    <string name="controlcenter_connected_fraction">袩芯写泻谢褞褔械薪芯: %d/%d</string>
+    <string name="ftp_server">小械褉胁械褉 FTP</string>
+    <string name="ftp_server_summary">袣芯薪褌褉芯谢懈褉褍泄褌械 褋械褉胁械褉 FTP 褋 褔邪褋芯胁</string>
+    <string name="address">袗写褉械褋</string>
+    <string name="fossil_hr_confirmation_timeout">袠褋褌械泻谢芯 胁褉械屑褟 芯卸懈写邪薪懈褟 锌芯写泻谢褞褔械薪懈褟. 袩褉芯写芯谢卸邪械屑</string>
+    <string name="contact_missing_name">袠屑褟 泻芯薪褌邪泻褌邪 薪械 蟹邪锌芯谢薪械薪芯</string>
+    <string name="title">袧邪蟹胁邪薪懈械</string>
+    <string name="steps_streaks_total_steps">袙褋械谐芯
+\n褕邪谐芯胁</string>
+    <string name="step_streak_longest">袧邪懈斜芯谢褜褕懈泄</string>
+    <string name="step_streaks_achievements_sharing_title">袛芯褋褌懈卸械薪懈褟 锌芯 褕邪谐邪屑</string>
+    <string name="fossil_hr_pairing_successful">小芯锌褉褟卸械薪懈械 褋 褔邪褋邪屑懈 褍褋锌械褕薪芯</string>
+    <string name="fossil_hr_pairing_failed">小芯锌褉褟卸械薪懈械 褋 褔邪褋邪屑懈 薪械 褍写邪谢芯褋褜</string>
+    <string name="contact_name">袠屑褟</string>
+    <string name="contact_phone_number">袧芯屑械褉 褌械谢械褎芯薪邪</string>
+    <string name="contact_missing_number">孝械谢械褎芯薪 薪械 褍泻邪蟹邪薪</string>
+    <string name="step_streak_average_steps_hint">楔邪谐芯胁 胁 褋褉械写薪械屑 蟹邪 写薪懈 锌芯写褉褟写</string>
+    <string name="busy_task_fetch_debug_logs">袩芯谢褍褔械薪懈械 芯褌谢邪写芯褔薪芯谐芯 卸褍褉薪邪谢邪</string>
+    <string name="activity_prefs_goals">笑械谢懈</string>
+    <string name="steps_streaks_average_steps">袙 褋褉械写薪械屑
+\n褕邪谐芯胁</string>
+    <string name="wifi_hotspot">孝芯褔泻邪 写芯褋褌褍锌邪 Wi-Fi</string>
+    <string name="username">袠屑褟 锌芯谢褜蟹芯胁邪褌械谢褟</string>
+    <string name="step_streak_total">袙褋械谐芯</string>
+    <string name="steps_streaks_achievement_rate">袛芯褋褌懈卸械薪懈褟
+\n褉械泄褌懈薪谐</string>
+    <string name="steps_streaks_total_steps_hint_totals">袙褋械谐芯 蟹邪褉械谐懈褋褌褉懈褉芯胁邪薪芯 褕邪谐芯胁</string>
+    <string name="wifi_hotspot_configuration">袧邪褋褌褉芯泄泻懈 孝袛 Wi-Fi</string>
+    <string name="wifi_ssid">SSID</string>
+    <string name="status_icon">袟薪邪褔芯泻 褋褌邪褌褍褋邪</string>
+    <string name="loyalty_cards_install">校褋褌邪薪芯胁懈褌褜 Catima</string>
+    <string name="loyalty_cards_sync_groups_only">小懈薪褏褉芯薪懈蟹懈褉芯胁邪褌褜 褌芯谢褜泻芯 胁褘斜褉邪薪薪褘械 谐褉褍锌锌褘</string>
+    <string name="pref_app_logs_start_summary">袧邪褔邪褌褜 胁械写械薪懈械 卸褍褉薪邪谢邪 懈蟹 锌褉懈谢芯卸械薪懈泄 褔邪褋芯胁</string>
+    <string name="pref_app_logs_stop_summary">袨褋褌邪薪芯胁懈褌褜 胁械写械薪懈械 卸褍褉薪邪谢邪</string>
+    <string name="pref_switch_controls_anc_ambient_off">袩芯写邪胁谢械薪懈械 鈫愨啋 袟胁褍泻 芯泻褉褍卸械薪懈褟 鈫愨啋 袙褘泻谢</string>
+    <string name="activity_type_surfing">小褢褉褎懈薪谐</string>
+    <string name="activity_type_windsurfing">袙懈薪写褋褢褉褎懈薪谐</string>
+    <string name="activity_type_kitesurfing">袣邪泄褌懈薪谐</string>
+    <string name="activity_type_football">肖褍褌斜芯谢</string>
+    <string name="activity_type_rugby">袪械谐斜懈</string>
+    <string name="activity_type_baseball">袘械泄褋斜芯谢</string>
+    <string name="activity_type_tennis">孝械薪薪懈褋</string>
+    <string name="activity_type_squash">小泻胁芯褕</string>
+    <string name="intent_api_allow_activity_sync_summary">袩芯蟹胁芯谢褟械褌 邪泻褌懈胁懈褉芯胁邪褌褜 褋懈薪褏褉芯薪懈蟹邪褑懈褞 邪泻褌懈胁薪芯褋褌懈 褔械褉械蟹 API Intent</string>
+    <string name="pref_canned_message">小芯芯斜褖械薪懈械</string>
+    <string name="fossil_hr_button_config_info">袧械 胁褋械 泻薪芯锌泻懈 屑芯谐褍褌 斜褘褌褜 薪邪褋褌褉芯械薪褘, 褌邪泻 泻邪泻 懈褏 写械泄褋褌胁懈褟 蟹邪斜懈褌褘 胁 袩袨 褔邪褋芯胁.
+\n
+\n袙薪懈屑邪薪懈械: 械褋谢懈 褍褋褌邪薪芯胁谢械薪芯 芯褎懈褑懈邪谢褜薪芯械 锌褉懈谢芯卸械薪懈械 Fossil, 写芯谢谐芯械 蟹邪卸邪褌懈械 胁械褉褏薪械泄 泻薪芯锌泻懈 褌邪泻卸械 斜褍写械褌 锌械褉械泻谢褞褔邪褌褜 芯褌芯斜褉邪卸械薪懈械 胁懈写卸械褌芯胁.</string>
+    <string name="busy_task_fetch_sports_summaries">袩芯谢褍褔械薪懈械 褋胁芯写泻懈 锌芯 褋锌芯褉褌褍</string>
+    <string name="busy_task_fetch_pai_data">袩芯谢褍褔械薪懈械 写邪薪薪褘褏 懈薪写械泻褋邪 褎懈蟹. 邪泻褌懈胁薪芯褋褌懈</string>
+    <string name="busy_task_fetch_spo2_data">袩芯谢褍褔械薪懈械 写邪薪薪褘褏 芯 泻懈褋谢芯褉芯写械 胁 泻褉芯胁懈</string>
+    <string name="busy_task_fetch_hr_data">袩芯谢褍褔械薪懈械 写邪薪薪褘褏 芯 褋械褉褑械斜懈械薪懈懈</string>
+    <string name="pref_app_logs_title">袞褍褉薪邪谢 锌褉懈谢芯卸械薪懈褟</string>
+    <string name="pref_app_logs_summary">袙泻谢褞褔懈褌褜 胁械写械薪懈械 卸褍褉薪邪谢邪 懈蟹 锌褉懈谢芯卸械薪懈褟 褔邪褋芯胁</string>
+    <string name="loyalty_cards_sync_groups">小懈薪褏褉芯薪懈蟹懈褉褍械屑褘械 谐褉褍锌锌褘</string>
+    <string name="steps_streaks_total_days_hint_totals">袩褉芯褑械薪褌 写薪械泄 褋 写芯褋褌懈谐薪褍褌芯泄 褑械谢褜褞 胁 褋芯芯褌薪芯褕械薪懈懈 泻芯 胁褋械屑 写薪褟屑 褋 褕邪谐邪屑懈</string>
+    <string name="step_streak_days_hint">袣芯谢懈褔械褋褌胁芯 写薪械泄 褋 写芯褋褌懈谐薪褍褌芯泄 褑械谢褜褞 褕邪谐芯胁 锌芯写褉褟写</string>
+    <string name="intent_api_allow_activity_sync_title">袗泻褌懈胁邪褑懈褟 褋懈薪褏. 邪泻褌懈胁薪芯褋褌懈 锌芯 褋芯斜褘褌懈褞</string>
+    <string name="changelog_full_title">小锌懈褋芯泻 懈蟹屑械薪械薪懈泄</string>
+    <string name="changelog_ok_button">袨泻</string>
+    <string name="changelog_title">效褌芯 薪芯胁芯谐芯</string>
+    <string name="prefs_noise_control">校锌褉邪胁谢械薪懈械 褍褉芯胁薪械屑 褕褍屑邪</string>
+    <string name="sony_button_mode_right">袪械卸懈屑 泻薪芯锌泻懈 (锌褉邪胁芯泄)</string>
+    <string name="sony_equalizer_preset_custom_1">小胁芯泄 1</string>
+    <string name="sony_speak_to_chat_sensitivity_standard">袧芯褉屑邪谢褜薪邪褟</string>
+    <string name="sony_pause_when_taken_off">袩邪褍蟹邪 锌褉懈 褋薪褟褌懈懈 薪邪褍褕薪懈泻芯胁</string>
+    <string name="sony_speak_to_chat_summary">袗胁褌芯屑邪褌懈褔械褋泻懈 芯褌泻谢褞褔邪褌褜 锌芯写邪胁谢械薪懈械 褕褍屑邪, 泻芯谐写邪 袙褘 薪邪褔懈薪邪械褌械 谐芯胁芯褉懈褌褜.</string>
+    <string name="sony_speak_to_chat_sensitivity">效褍胁褋褌胁懈褌械谢褜薪芯褋褌褜 邪泻褌懈胁邪褑懈懈 锌芯 谐芯谢芯褋褍</string>
+    <string name="sony_speak_to_chat_timeout">袨谐褉邪薪懈褔械薪懈械 胁褉械屑械薪懈</string>
+    <string name="sony_speak_to_chat_sensitivity_high">袙褘褋芯泻邪褟</string>
+    <string name="sony_speak_to_chat_sensitivity_low">袧懈蟹泻邪褟</string>
+    <string name="sony_automatic_power_off_when_taken_off">袩褉懈 褋薪褟褌懈懈</string>
+    <string name="sony_button_mode_left">袪械卸懈屑 泻薪芯锌泻懈 (袥械胁芯泄)</string>
+    <string name="pref_anc_optimizer_state_pressure">袗褌屑芯褋褎械褉薪芯械 写邪胁谢械薪懈械</string>
+    <string name="sony_equalizer_preset_custom_2">小胁芯泄 2</string>
+    <string name="sony_automatic_power_off">袗胁褌芯屑邪褌懈褔械褋泻芯械 胁褘泻谢褞褔械薪懈械</string>
+    <string name="sony_automatic_power_off_off">袧械 胁褘泻谢褞褔邪褌褜</string>
+    <string name="sony_surround_mode_outdoor_stage">袨褌泻褉褘褌邪褟 褋褑械薪邪</string>
+    <string name="stress_mild">袧械斜芯谢褜褕芯泄</string>
+    <string name="sony_anc_optimizer_status_wearing_condition">袠蟹屑械褉械薪懈械 褍褋谢芯胁懈泄 薪芯褕械薪懈褟鈥�</string>
+    <string name="sony_anc_optimizer_status_finished">袟邪胁械褉褕械薪懈械鈥�</string>
+    <string name="sony_equalizer_preset_speech">袪械褔褜</string>
+    <string name="sony_equalizer_preset_manual">袙褉褍褔薪褍褞</string>
+    <string name="sony_speak_to_chat_sensitivity_auto">袗胁褌芯屑邪褌懈褔械褋泻懈</string>
+    <string name="stress_moderate">校屑械褉械薪薪褘泄</string>
+    <string name="stress_high">袙褘褋芯泻懈泄</string>
+    <string name="pai_total">袙褋械谐芯</string>
+    <string name="pref_theme_dynamic">袗写邪锌褌懈胁薪褘械 褑胁械褌邪</string>
+    <string name="devicetype_casiogmwb5000">Casio GMW-B5000</string>
+    <string name="watchface_widget_type_uv_index">袠薪写械泻褋 校肖-懈蟹谢褍褔械薪懈褟</string>
+    <string name="pref_show_changelog">袩芯泻邪蟹褘胁邪褌褜 褋锌懈褋芯泻 懈蟹屑械薪械薪懈泄 锌褉懈 蟹邪锌褍褋泻械</string>
+    <string name="devicetype_galaxybuds_2_pro">Galaxy Buds2 Pro</string>
+    <string name="activity_type_handball">袚邪薪写斜芯谢</string>
+    <string name="activity_type_weightlifting">孝褟卸褢谢邪褟 邪褌谢械褌懈泻邪</string>
+    <string name="activity_type_dancing">孝邪薪褑褘</string>
+    <string name="activity_type_skiing">袥褘卸懈</string>
+    <string name="activity_type_snowboarding">小薪芯褍斜芯褉写懈薪谐</string>
+    <string name="activity_type_riding">袙械褉褏芯胁邪褟 械蟹写邪</string>
+    <string name="activity_type_hockey">啸芯泻泻械泄</string>
+    <string name="devicetype_casiogwb5600">Casio GW-B5600</string>
+    <string name="watchface_dialog_widget_cat_2nd_tz_widget">袙懈写卸械褌 胁褌芯褉懈褔薪芯谐芯 褔邪褋芯胁芯谐芯 锌芯褟褋邪</string>
+    <string name="activity_type_icehockey">啸芯泻泻械泄 褋 褕邪泄斜芯泄</string>
+    <string name="activity_type_iceskating">袣芯薪褜泻懈</string>
+    <string name="activity_type_other">袛褉褍谐芯械</string>
+    <string name="error_no_bluetooth_connect">袛谢褟 泻芯褉褉械泻褌薪芯泄 褉邪斜芯褌褘 褋泻邪薪懈褉芯胁邪薪懈褟 褌褉械斜褍械褌褋褟 褉邪蟹褉械褕械薪懈械 薪邪 褋芯械写懈薪械薪懈械 锌芯 Bluetooth</string>
+    <string name="activity_type_golf">袚芯谢褜褎</string>
+    <string name="devicetype_amazfit_bip3_pro">Amazfit Bip 3 Pro</string>
+    <string name="error_no_bluetooth_scan">袛谢褟 泻芯褉褉械泻褌薪芯泄 褉邪斜芯褌褘 褋泻邪薪懈褉芯胁邪薪懈褟 褌褉械斜褍械褌褋褟 褉邪蟹褉械褕械薪懈械 薪邪 褋泻邪薪懈褉芯胁邪薪懈械 Bluetooth</string>
+    <string name="pref_show_changelog_summary">袨褌芯斜褉邪卸邪褌褜 褋锌懈褋芯泻 懈蟹屑械薪械薪懈泄 褋 锌褉械写褘写褍褖械泄 胁械褉褋懈懈 锌芯褋谢械 芯斜薪芯胁谢械薪懈褟 Gadgetbridge</string>
+    <string name="watchface_dialog_widget_cat_position">袩芯蟹懈褑懈褟</string>
+    <string name="watchface_widget_type_active_mins">袦懈薪褍褌褘 邪泻褌懈胁薪芯褋褌懈</string>
+    <string name="watchface_dialog_widget_cat_generic">袨斜褘褔薪褘泄</string>
+    <string name="prefs_dolby_summary">袩褉械写褍褋褌邪薪芯胁泻邪 Dolby 写谢褟 褝泻胁邪谢邪泄蟹械褉邪</string>
+    <string name="prefs_touch_lock_buds2">小械薪褋芯褉薪芯械 褍锌褉邪胁谢械薪懈械</string>
+    <string name="prefs_seamless_connection_switch_title">袩谢邪胁薪邪褟 褋屑械薪邪 锌芯写泻谢褞褔械薪懈褟</string>
+    <string name="prefs_touch_lock_summary">袨褌泻谢褞褔懈褌褜 泻邪褋邪薪懈褟</string>
+    <string name="prefs_touch_lock">小械薪褋芯褉薪邪褟 斜谢芯泻懈褉芯胁泻邪</string>
+    <string name="prefs_double_tap_edge">袣褉邪泄 写胁芯泄薪芯谐芯 泻邪褋邪薪懈褟</string>
+    <string name="pref_media_pause">袩邪褍蟹邪 胁芯褋锌褉芯懈蟹胁械写械薪懈褟</string>
+    <string name="sony_ambient_sound_control_button_mode_nc_off">袩芯写邪胁谢械薪懈械, 袙褘泻谢</string>
+    <string name="pref_media_playpause">袩械褉械泻谢褞褔械薪懈械 胁芯褋锌褉芯懈蟹胁械写械薪懈褟</string>
+    <string name="sony_ambient_sound_control_button_mode_as_off">袟胁褍泻 芯泻褉褍卸械薪懈褟, 袙褘泻谢</string>
+    <string name="fw_upgrade_notice_amazfit_cheetah_pro">袙褘 褋芯斜懈褉邪械褌械褋褜 褍褋褌邪薪芯胁懈褌褜 锌褉芯褕懈胁泻褍 %s 薪邪 袙邪褕 Amazfit Cheetah Pro.
+\n
+\n袩芯褋谢械 褍褋褌邪薪芯胁泻懈 .zip 胁邪褕 斜褉邪褋谢械褌 斜褍写械褌 锌械褉械蟹邪谐褉褍卸械薪.
+\n
+\n袙蝎 袛袝袡小孝袙校袝孝袝 袧袗 小袙袨袡 小孝袪袗啸 袠 袪袠小袣!</string>
+    <string name="fw_upgrade_notice_zepp_os">袙褘 褋芯斜懈褉邪械褌械褋褜 褍褋褌邪薪芯胁懈褌褜 锌褉芯褕懈胁泻褍 %s 薪邪 袙邪褕 %s.
+\n
+\n袩芯褋谢械 褍褋褌邪薪芯胁泻懈 褎邪泄谢邪 .zip 胁邪褕懈 褔邪褋褘 斜褍写褍褌 锌械褉械蟹邪谐褉褍卸械薪褘.
+\n
+\n袙蝎 袛袝袡小孝袙校袝孝袝 袧袗 小袙袨袡 小孝袪袗啸 袠 袪袠小袣!</string>
+    <string name="fw_upgrade_notice_amazfitbip3pro">袙褘 褋芯斜懈褉邪械褌械褋褜 褍褋褌邪薪芯胁懈褌褜 锌褉芯褕懈胁泻褍 %s 薪邪 袙邪褕 Amazfit Bip 3 Pro.
+\n
+\n袘褍写褜褌械 胁薪懈屑邪褌械谢褜薪褘: 褋锌械褉胁邪 褍褋褌邪薪芯胁懈褌械 褎邪泄谢 .fw, 蟹邪褌械屑 .res. 袩芯褋谢械 褍褋褌邪薪芯胁泻懈 .fw 胁邪褕懈 褔邪褋褘 斜褍写褍褌 锌械褉械蟹邪谐褉褍卸械薪褘.
+\n
+\n袩褉懈屑械褔邪薪懈械: 薪械 薪褍卸薪芯 褍褋褌邪薪邪胁谢懈胁邪褌褜 褝褌芯褌 卸械 .res, 械褋谢懈 芯薪 褍卸械 斜褘谢 褍褋褌邪薪芯胁谢械薪.
+\n
+\n袙蝎 袛袝袡小孝袙校袝孝袝 袧袗 小袙袨袡 小孝袪袗啸 袠 袪袠小袣!</string>
+    <string name="prefs_ambient_volume">袚褉芯屑泻芯褋褌褜 芯泻褉褍卸械薪懈褟</string>
+    <string name="pref_ambient_sound_tone">孝芯薪邪谢褜薪芯褋褌褜 蟹胁褍泻芯胁 芯泻褉褍卸械薪懈褟</string>
+    <string name="prefs_ambient_mode">袪械卸懈屑 褋谢褘褕懈屑芯褋褌懈 芯泻褉褍卸械薪懈褟</string>
+    <string name="prefs_pressure_relief">小斜褉芯褋 写邪胁谢械薪懈褟 褋 芯泻褉褍卸邪褞褖懈屑 蟹胁褍泻芯屑</string>
+    <string name="pref_ambient_sound_tone_summary">袨褌 屑褟谐泻芯谐芯 泻 褔褢褌泻芯屑褍</string>
+    <string name="prefs_ambient_volume_right">袚褉芯屑泻芯褋褌褜 芯泻褉褍卸械薪懈褟 小锌褉邪胁邪</string>
+    <string name="prefs_ambient_settings_title">袧邪褋褌褉芯泄泻懈 蟹胁褍泻邪 芯泻褉褍卸械薪懈褟</string>
+    <string name="prefs_customize_ambient_sound_summary">袧邪褋褌褉芯懈褌褜 蟹胁褍泻 芯泻褉褍卸械薪懈褟</string>
+    <string name="pref_title_touch_ambient">袟胁褍泻 芯泻褉褍卸械薪懈褟</string>
+    <string name="prefs_ambient_volume_left">袚褉芯屑泻芯褋褌褜 芯泻褉褍卸械薪懈褟 小谢械胁邪</string>
+    <string name="prefs_ambient_sound">袟胁褍泻 芯泻褉褍卸械薪懈褟</string>
+    <string name="pref_switch_controls_anc_ambient">袩芯写邪胁谢械薪懈械 鈫愨啋 袟胁褍泻 芯泻褉褍卸械薪懈褟</string>
+    <string name="prefs_ambient_sound_during_call_title">袟胁褍泻懈 芯泻褉褍卸械薪懈褟 胁芯 胁褉械屑褟 蟹胁芯薪泻邪</string>
+    <string name="pref_switch_controls_ambient_off">袟胁褍泻 芯泻褉褍卸械薪懈褟 鈫愨啋 袙褘泻谢</string>
+    <string name="sony_button_mode_ambient_sound_control">校锌褉邪胁谢械薪懈械 蟹胁褍泻芯屑 芯泻褉褍卸械薪懈褟</string>
+    <string name="find_my_phone_found_it">袧袗楔衼袥 袝袚袨</string>
+    <string name="voice_service_package_title">袧邪斜芯褉 谐芯谢芯褋芯胁芯泄 褋谢褍卸斜褘</string>
+    <string name="voice_service_package_summary">袩褉懈谢芯卸械薪懈械 褋芯 褋谢褍卸斜芯泄 芯斜褉邪斜芯褌泻懈 谐芯谢芯褋芯胁褘褏 泻芯屑邪薪写</string>
+    <string name="voice_service_class_title">袣谢邪褋褋 谐芯谢芯褋芯胁芯泄 褋谢褍卸斜褘</string>
+    <string name="prefs_voice_detect_summary">袗胁褌芯屑邪褌懈褔械褋泻懈 胁泻谢褞褔懈褌褜 蟹胁褍泻懈 芯泻褉褍卸械薪懈褟 懈 褋薪懈蟹懈褌褜 谐褉芯屑泻芯褋褌褜 胁芯褋锌褉芯懈蟹胁械写械薪懈褟 锌芯褋谢械 芯斜薪邪褉褍卸械薪懈褟 谐芯谢芯褋邪</string>
+    <string name="supercars_lights_label">袨谐薪懈</string>
+    <string name="debug_companion_show_associated">袨褌芯斜褉邪卸邪褌褜 褋胁褟蟹邪薪薪褘械 褍褋褌褉芯泄褋褌胁邪-褋锌褍褌薪懈泻懈</string>
+    <string name="sony_ambient_sound_ambient_sound">袟胁褍泻 芯泻褉褍卸械薪懈褟</string>
+    <string name="loyalty_cards_sync_archived">小懈薪褏褉芯薪懈蟹懈褉芯胁邪褌褜 邪泻褌懈胁懈褉芯胁邪薪薪褘械 泻邪褉褌褘</string>
+    <string name="loyalty_cards_sync_summary">袧邪卸屑懈褌械 写谢褟 褋懈薪褏褉芯薪懈蟹邪褑懈懈 泻邪褉褌 褋 褔邪褋邪屑懈</string>
+    <string name="loyalty_cards_catima_package">袠屑褟 薪邪斜芯褉邪 Catima</string>
+    <string name="loyalty_cards_install_catima_fail">袧械 褍写邪谢芯褋褜 芯褌泻褉褘褌褜 屑邪谐邪蟹懈薪 锌褉懈谢芯卸械薪懈泄 写谢褟 褍褋褌邪薪芯胁泻懈 Catima</string>
+    <string name="loyalty_cards_sync_title">小懈薪褏褉芯薪懈蟹懈褉芯胁邪褌褜 斜芯薪褍褋薪褘械 泻邪褉褌褘</string>
+    <string name="loyalty_cards_sync_options">袧邪褋褌褉芯泄泻懈 褋懈薪褏褉芯薪懈蟹邪褑懈懈</string>
+    <string name="loyalty_cards_sync">小懈薪褏褉芯薪懈蟹邪褑懈褟</string>
+    <string name="loyalty_cards_catima">Catima</string>
+    <string name="supercars_lights_blinking_label">袦懈谐邪薪懈械</string>
+    <string name="sony_ambient_sound_control_button_mode_nc_as">袩芯写邪胁谢械薪懈械, 袟胁褍泻 芯泻褉褍卸械薪懈褟</string>
+    <string name="wifi_hotspot_summary">校锌褉邪胁谢褟泄褌械 褌芯褔泻芯泄 写芯褋褌褍锌邪 Wi-Fi 褋 褔邪褋芯胁</string>
+    <string name="sony_ambient_sound_control_button_mode_nc_as_off">袩芯写邪胁谢械薪懈械, 袟胁褍泻 芯泻褉褍卸械薪懈褟, 袙褘泻谢</string>
+    <string name="wifi_hotspot_start_summary">袟邪锌褍褋泻邪泄褌械 褌芯褔泻褍 写芯褋褌褍锌邪 褋 褔邪褋芯胁</string>
+    <string name="voice_service_class_summary">袩芯谢薪褘泄 锌褍褌褜 写芯 褋谢褍卸斜褘 芯斜褉邪斜芯褌泻懈 谐芯谢芯褋芯胁褘褏 泻芯屑邪薪写</string>
+    <string name="loyalty_cards_catima_permissions_summary">袛谢褟 褋懈薪褏褉芯薪懈蟹邪褑懈懈 泻邪褉褌 Catima, Gadgetbridge 褌褉械斜褍褞褌褋褟 褉邪蟹褉械褕械薪懈褟 薪邪 懈褏 褔褌械薪懈械. 袧邪卸屑懈褌械 褝褌褍 泻薪芯锌泻褍, 褔褌芯斜褘 胁褘写邪褌褜 懈褏.</string>
+    <string name="withings_calibration_text_minutes">孝械锌械褉褜 懈褋锌芯谢褜蟹褍泄褌械 褑懈褎械褉斜谢邪褌 写谢褟 褍褋褌邪薪芯胁泻懈 屑懈薪褍褌薪芯泄 褋褌褉械谢泻懈 薪邪 12.</string>
+    <string name="pref_theme_dynamic_colors_not_available_warning">袗写邪锌褌懈胁薪褘械 褑胁械褌邪 薪械写芯褋褌褍锌薪褘 薪邪 袙邪褕械屑 褍褋褌褉芯泄褋褌胁械. 协褌芯褌 褎褍薪泻褑懈芯薪邪谢 锌芯写写械褉卸懈胁邪械褌褋褟 薪邪 Android 12 懈 胁褘褕械. Gadgetbridge 斜褍写械褌 懈褋锌芯谢褜蟹芯胁邪褌褜 褑胁械褌邪 Material 3 锌芯 褍屑芯谢褔邪薪懈褞.</string>
+    <string name="pref_header_sony_ambient_sound_control">校锌褉邪胁谢械薪懈械 蟹胁褍泻芯屑 芯泻褉褍卸械薪懈褟</string>
+    <string name="sony_ambient_sound_level">校褉芯胁械薪褜 蟹胁褍泻邪 芯泻褉褍卸械薪懈褟</string>
+    <string name="sony_ambient_sound_control_button_modes">袪械卸懈屑褘 泻薪芯锌芯泻 褍锌褉邪胁谢械薪懈褟 蟹胁褍泻芯屑 芯泻褉褍卸械薪懈褟</string>
+    <string name="wifi_hotspot_stop_summary">袨褋褌邪薪邪胁谢懈胁邪泄褌械 褌芯褔泻褍 写芯褋褌褍锌邪 褋 褔邪褋芯胁</string>
+    <string name="fossil_hr_confirmation_skipped">袩褉芯锌褍褋泻 泻芯薪褎懈谐褍褉邪褑懈懈 褍褋褌褉芯泄褋褌胁邪</string>
+    <string name="debug_companion_pair_current">袩褉懈胁褟蟹邪褌褜 褌械泻褍褖械械 褍褋褌褉芯泄褋褌胁芯 泻邪泻 褋锌褍褌薪懈泻</string>
+    <string name="preview_image">袦懈薪懈邪褌褞褉邪</string>
+    <string name="withings_bt_calibration_previous">袩褉械写褘写褍褖邪褟</string>
+    <string name="pref_theme_dynamic_colors_explanation">袩褉懈屑械褔邪薪懈械: 褔褌芯斜褘 懈褋锌芯谢褜蟹芯胁邪褌褜 褌械屑褍 邪写邪锌褌懈胁薪褘褏 褑胁械褌芯胁, 袙邪屑 薪械芯斜褏芯写懈屑芯 胁泻谢褞褔懈褌褜 笑胁械褌 芯斜芯械胁 懈谢懈 袩邪谢懈褌褉褍 芯斜芯械胁 胁 薪邪褋褌褉芯泄泻邪褏 胁薪械褕薪械谐芯 胁懈写邪 薪邪 Android 12+. 袝褋谢懈 芯薪芯 斜褍写械褌 胁褘泻谢褞褔械薪芯, Gadgetbridge 斜褍写械褌 懈褋锌芯谢褜蟹芯胁邪褌褜 褑胁械褌邪 Material 3 锌芯 褍屑芯谢褔邪薪懈褞.</string>
+    <string name="pref_activity_full_sync_trigger_summary">袟邪锌褍褋褌懈褌褜 锌芯谢薪褍褞 褋懈薪褏褉芯薪懈蟹邪褑懈褞 写邪薪薪褘褏 芯 胁褋械褏 邪泻褌懈胁薪芯褋褌褟褏</string>
+    <string name="voice_service">小谢褍卸斜邪 谐芯谢芯褋邪</string>
+    <string name="loyalty_cards_sync_starred">小懈薪褏褉芯薪懈蟹懈褉芯胁邪褌褜 褌芯谢褜泻芯 泻邪褉褌褘 胁 懈蟹斜褉邪薪薪芯屑</string>
+    <string name="loyalty_cards_catima_not_compatible">校褋褌邪薪芯胁谢械薪薪邪褟 胁械褉褋懈褟 Catima 薪械褋芯胁屑械褋褌懈屑邪 褋 Gadgetbridge. 袩芯卸邪谢褍泄褋褌邪, 芯斜薪芯胁懈褌械 芯斜邪 锌褉懈谢芯卸械薪懈褟 写芯 锌芯褋谢械写薪懈褏 胁械褉褋懈泄.</string>
+    <string name="loyalty_cards_open_catima">袨褌泻褉褘褌褜 Catima</string>
+    <string name="loyalty_cards">袘芯薪褍褋薪褘械 泻邪褉褌褘</string>
+    <string name="loyalty_cards_syncing">小懈薪褏褉芯薪懈蟹邪褑懈褟 %d 斜芯薪褍褋薪褘褏 泻邪褉褌 薪邪 褍褋褌褉芯泄褋褌胁芯</string>
+    <string name="withings_calibration_text_hours">袩芯卸邪谢褍泄褋褌邪, 懈褋锌芯谢褜蟹褍泄褌械 褑懈褎械褉斜谢邪褌 薪懈卸械 写谢褟 褍褋褌邪薪芯胁泻懈 褔邪褋芯胁芯泄 褋褌褉械谢泻懈 薪邪 12.</string>
+    <string name="withings_calibration_text_activity_target">袧邪泻芯薪械褑, 褍褋褌邪薪芯胁懈褌械 褋褌褉械谢泻褍 邪泻褌懈胁薪芯褋褌懈 薪邪 100%. 袩芯卸邪谢褍泄褋褌邪, 褍褔褌懈褌械, 褔褌芯 芯薪邪 写胁懈卸械褌褋褟 褌芯谢褜泻芯 胁 芯写薪芯屑 薪邪锌褉邪胁谢械薪懈懈.</string>
+    <string name="withings_bt_calibration_next">小谢械写褍褞褖邪褟</string>
+    <string name="pref_activity_full_sync_trigger_title">袩芯谢薪邪褟 褋懈薪褏褉芯薪懈蟹邪褑懈褟</string>
+    <string name="info_menu_structure_set">小褌褉褍泻褌褍褉邪 屑械薪褞 JSON, 蟹邪写邪薪薪邪褟 胁 袚袘</string>
+    <string name="button_open_menu_companion">袨褌泻褉褘褌褜 屑械薪褞 锌褉懈谢芯卸械薪懈褟-褋锌褍褌薪懈泻邪</string>
+    <string name="error_menu_companion_not_installed">小泻芯褉械械 胁褋械谐芯, 芦小锌褍褌薪懈泻 屑械薪褞 HR禄鈥� 薪械 褍褋褌邪薪芯胁谢械薪芯</string>
+    <string name="loyalty_cards_catima_not_installed">袛谢褟 褍锌褉邪胁谢械薪懈褟 斜芯薪褍褋薪褘屑懈 泻邪褉褌邪屑懈 褌褉械斜褍械褌褋褟 Catima</string>
+    <string name="loyalty_cards_catima_permissions_title">袨褌褋褍褌褋褌胁褍褞褖懈械 褉邪蟹褉械褕械薪懈褟</string>
+    <string name="error_invalid_menu_structure">袧械泻芯褉褉械泻褌薪邪褟 褋褌褉褍泻褌褍褉邪 屑械薪褞 JSON</string>
+    <string name="button_reset_menu_structure">小斜褉芯褋懈褌褜 褋褌褉褍泻褌褍褉褍 屑械薪褞</string>
+    <string name="info_menu_structure_contents">小褌褉褍泻褌褍褉邪 屑械薪褞: %s</string>
+    <string name="info_fossil_rebuild_watchface_custom_menu">袩芯卸邪谢褍泄褋褌邪, 锌械褉械褋芯斜械褉懈褌械 褋胁芯泄 褑懈褎械褉斜谢邪褌 写谢褟 锌芯谢褜蟹芯胁邪褌械谢褜褋泻芯谐芯 屑械薪褞</string>
+    <string name="pref_activity_full_sync_trigger_warning">袟邪锌褍褋褌懈褌 锌芯谢薪褍褞 褋懈薪褏褉芯薪懈蟹邪褑懈褞 写邪薪薪褘褏 胁褋械褏 邪泻褌懈胁薪芯褋褌械泄 褋 褍褋褌褉芯泄褋褌胁邪. 协褌芯 屑芯卸械褌 蟹邪薪褟褌褜 薪械褋泻芯谢褜泻芯 屑懈薪褍褌.</string>
+    <string name="info_menu_structure_removed">小褌褉褍泻褌褍褉邪 屑械薪褞 褍写邪谢械薪邪</string>
+    <string name="pref_switch_noise_control">袠蟹屑械薪懈褌褜 褍褉芯胁械薪褜 锌芯写邪胁谢械薪懈褟 褕褍屑邪</string>
+    <string name="pref_title_touch_spotify">Spotify</string>
+    <string name="prefs_noise_control_with_one_earbud">袩芯写邪胁谢械薪懈械 褕褍屑邪 褋 芯写薪懈屑 薪邪褍褕薪懈泻芯屑</string>
+    <string name="nothing_prefs_inear_summary">袩邪褍蟹邪/胁芯褋锌褉. 屑褍蟹褘泻懈 胁 蟹邪胁懈褋懈屑芯褋褌懈 芯褌 锌褉懈褋褍褌褋褌胁懈褟 胁 褍褕邪褏 薪邪褍褕薪懈泻芯胁</string>
+    <string name="prefs_noise_control_with_one_earbud_summary">袩芯蟹胁芯谢褟械褌 懈褋锌芯谢褜蟹芯胁邪褌褜 锌芯写邪胁谢械薪懈械 褕褍屑邪 褋 芯写薪懈屑 薪邪褍褕薪懈泻芯屑</string>
+    <string name="sony_notification_voice_guide">校胁械写芯屑谢械薪懈褟 懈 袚芯谢芯褋芯胁芯械 小芯锌褉芯胁芯卸写械薪懈械</string>
+    <string name="pressure_relief_summary">袩褉械写芯褌胁褉邪褖械薪懈械 芯褖褍褖械薪懈褟 写邪胁谢械薪懈褟 胁 褍褕邪褏 斜械蟹 懈褋锌芯谢褜蟹芯胁邪薪懈褟 邪泻褌懈胁薪芯谐芯 锌芯写邪胁谢械薪懈褟 褕褍屑邪</string>
+    <string name="stress_relaxed">袪邪褋褋谢邪斜谢械薪薪褘泄</string>
+    <string name="sony_connect_two_devices">袩芯写泻谢褞褔械薪懈械 泻 2 褍褋褌褉芯泄褋褌胁邪屑 芯写薪芯胁褉械屑械薪薪芯</string>
+    <string name="sony_audio_upsampling">袩芯胁褘褕械薪懈械 褔邪褋褌芯褌褘 写懈褋泻褉械褌懈蟹邪褑懈懈</string>
+    <string name="long_press">袛芯谢谐芯械 蟹邪卸邪褌懈械</string>
+    <string name="continue_pressing">袩褉芯写芯谢卸邪泄褌械 薪邪卸懈屑邪褌褜</string>
+    <string name="sony_quick_access_double_tap">袘褘褋褌褉褘泄 写芯褋褌褍锌 (写胁芯泄薪芯械 薪邪卸邪褌懈械)</string>
+    <string name="pref_screen_auto_brightness_title">袗胁褌芯锌芯写褋褌褉芯泄泻邪 褟褉泻芯褋褌懈</string>
+    <string name="pref_screen_auto_brightness_summary">袗胁褌芯屑邪褌懈褔械褋泻邪褟 锌芯写褋褌褉芯泄泻邪 褟褉泻芯褋褌懈 泻 芯褋胁械褖褢薪薪芯褋褌懈 芯泻褉褍卸械薪懈褟</string>
+    <string name="qhybrid_summary_on_device_confirmation">袩芯写褌胁械褉卸写械薪懈褟 褋芯锌褉褟卸械薪懈褟 薪邪 褍褋褌褉芯泄褋褌胁械 屑芯谐褍褌 斜褘褌褜 褉邪蟹写褉邪卸邪褞褖懈屑懈. 袠褏 胁褘泻谢褞褔械薪懈械 屑芯卸械褌 锌褉懈胁械褋褌懈 泻 锌芯褌械褉械 褎褍薪泻褑懈芯薪邪谢邪.</string>
+    <string name="watchface_dialog_widget_timezone_duration">袙褉械屑褟 芯褌芯斜褉邪卸械薪懈褟 褔邪褋芯胁 (胁 褋械泻褍薪写邪褏)</string>
+    <string name="qhybrid_title_on_device_confirmation">袙泻谢褞褔懈褌褜 锌芯写褌胁械褉卸写械薪懈械 褋芯锌褉褟卸械薪懈褟 薪邪 褍褋褌褉芯泄褋褌胁械</string>
+    <string name="pref_screen_brightness">携褉泻芯褋褌褜 褝泻褉邪薪邪</string>
+    <string name="single_tap">袨写薪芯 薪邪卸邪褌懈械</string>
+    <string name="double_tap">袛胁芯泄薪芯械 薪邪卸邪褌懈械</string>
+    <string name="triple_tap">孝褉芯泄薪芯械 薪邪卸邪褌懈械</string>
+    <string name="watchface_dialog_widget_timeout_show_circle">袨褌芯斜褉邪卸邪褌褜 泻褉褍谐 锌芯 懈褋褌械褔械薪懈褞</string>
+    <string name="sony_quick_access_triple_tap">袘褘褋褌褉褘泄 写芯褋褌褍锌 (褌褉芯泄薪芯械 薪邪卸邪褌懈械)</string>
+    <string name="prefs_ambient_voice_focus">袣芯薪褑械薪褌褉邪褑懈褟 薪邪 谐芯谢芯褋械</string>
+    <string name="sony_sound_position">袪邪褋锌芯谢芯卸械薪懈械 蟹胁褍泻邪</string>
+    <string name="sony_equalizer_preset_bass_boost">校褋懈谢械薪懈械 斜邪褋邪</string>
+    <string name="sony_touch_sensor">小械薪褋芯褉薪芯械 褍锌褉邪胁谢械薪懈械</string>
+    <string name="sony_button_mode_playback_control">校锌褉邪胁谢械薪懈械 胁芯褋锌褉芯懈蟹胁械写械薪懈械屑</string>
+    <string name="watchface_setting_light_up_on_notification">袩芯写褋胁械褔懈胁邪褌褜 锌褉懈 薪芯胁芯屑 褍胁械写芯屑谢械薪懈懈</string>
+    <string name="intent_api_broadcast_export_summary">孝褉邪薪褋谢懈褉芯胁邪褌褜 intent 锌芯 蟹邪胁械褉褕械薪懈褞 褝泻褋锌芯褉褌邪 斜邪蟹褘 写邪薪薪褘褏</string>
+    <string name="pref_title_navigation_forward">袨褌锌褉邪胁懈褌褜 薪邪胁懈谐邪褑懈褞 薪邪 褔邪褋褘</string>
+    <string name="intent_api_allow_debug_commands_summary">袩芯蟹胁芯谢褟械褌 邪泻褌懈胁懈褉芯胁邪褌褜 泻芯屑邪薪写褘 芯褌谢邪写芯褔薪芯谐芯 屑械薪褞 褔械褉械蟹 API Intent</string>
+    <string name="supercars_turbo_speed_label">孝褍褉斜芯-褋泻芯褉芯褋褌褜</string>
+    <string name="english_gb">袗薪谐谢懈泄褋泻懈泄 (袙械谢懈泻芯斜褉懈褌邪薪懈褟)</string>
+    <string name="english_in">袗薪谐谢懈泄褋泻懈泄 (袠薪写懈褟)</string>
+    <string name="english_us">袗薪谐谢懈泄褋泻懈泄 (小楔袗)</string>
+    <string name="spanish_es">袠褋锌邪薪褋泻懈泄 (袠褋锌邪薪懈褟)</string>
+    <string name="spanish_us">袠褋锌邪薪褋泻懈泄 (小楔袗)</string>
+    <string name="spanish_mx">袠褋锌邪薪褋泻懈泄 (袦械泻褋懈泻邪)</string>
+    <string name="french_ca">肖褉邪薪褑褍蟹褋泻懈泄 (袣邪薪邪写邪)</string>
+    <string name="french_fr">肖褉邪薪褑褍蟹褋泻懈泄 (肖褉邪薪褑懈褟)</string>
+    <string name="pref_device_action_phone_gps_location_listener_stop">袨褋褌邪薪芯胁泻邪 褋谢褍卸斜褘 褉邪褋锌芯谢芯卸械薪懈褟 GPS</string>
+    <string name="distance_format_miles">###,# 屑懈谢褜</string>
+    <string name="steps_streaks_hint">袘械褋锌褉械褉褘胁薪邪褟 锌芯褋谢械写芯胁邪褌械谢褜薪芯褋褌褜 写薪械泄 褋 写芯褋褌懈卸械薪懈械屑 褑械谢懈 锌芯 褕邪谐邪屑</string>
+    <string name="intent_api_broadcast_export_title">孝褉邪薪褋谢褟褑懈褟 锌芯 褝泻褋锌芯褉褌褍 斜邪蟹褘 写邪薪薪褘褏</string>
+    <string name="intent_api_allow_trigger_export_summary">袩芯蟹胁芯谢褟械褌 邪泻褌懈胁懈褉芯胁邪褌褜 褝泻褋锌芯褉褌 斜邪蟹褘 写邪薪薪褘褏 褔械褉械蟹 API Intent</string>
+    <string name="pref_summary_navigation_forward">袨褌锌褉邪胁泻邪 褍泻邪蟹邪薪懈泄 薪邪胁懈谐邪褑懈芯薪薪褘褏 锌褉懈谢芯卸械薪懈泄 薪邪 褔邪褋褘</string>
+    <string name="intent_api_allow_debug_commands_title">袪邪蟹褉械褕懈褌褜 芯褌谢邪写芯褔薪褘械 泻芯屑邪薪写褘</string>
+    <string name="intent_api_allow_trigger_export_title">袪邪蟹褉械褕懈褌褜 褝泻褋锌芯褉褌 袘袛</string>
+    <string name="steps_streaks">袪械泻芯褉写褘 褕邪谐芯胁</string>
+    <string name="english_au">袗薪谐谢懈泄褋泻懈泄 (袗胁褋褌褉邪谢懈褟)</string>
+    <string name="english_ca">袗薪谐谢懈泄褋泻懈泄 (袣邪薪邪写邪)</string>
+    <string name="changelog_show_full">袛邪谢褜褕械鈥�</string>
+    <string name="sony_button_mode_help_summary">袨锌懈褋邪薪懈械 褉械卸懈屑芯胁 泻邪卸写芯泄 泻薪芯锌泻懈</string>
+    <string name="pref_header_sony_equalizer_preset_custom_2">小胁芯褟 锌褉械写褍褋褌邪薪芯胁泻邪 2</string>
+    <string name="permission_request">%1$s 锌芯蟹胁芯谢褟械褌 芯褌锌褉邪胁谢褟褌褜 褋芯芯斜褖械薪懈褟 懈 写褉褍谐褍褞 懈薪褎芯褉屑邪褑懈褞 褋 Android 薪邪 袙邪褕械 褍褋褌褉芯泄褋褌胁芯. 袛谢褟 褝褌芯谐芯 械屑褍 薪械芯斜褏芯写懈屑褘 褉邪蟹褉械褕械薪懈褟 薪邪 写芯褋褌褍锌 泻 褝褌芯泄 懈薪褎芯褉屑邪褑懈懈. 袘械蟹 薪懈褏 薪懈褔械谐芯 薪芯褉屑邪谢褜薪芯 褉邪斜芯褌邪褌褜 薪械 斜褍写械褌.
+\n
+\n袙邪屑 斜褍写褍褌 胁褘胁械写械薪褘 薪械褋泻芯谢褜泻芯 写懈邪谢芯谐芯胁 褋 蟹邪锌褉芯褋邪屑懈 薪邪 褝褌懈 褉邪蟹褉械褕械薪懈褟.
+\n
+\n袧邪卸屑懈褌械 芦%2$s禄, 褔褌芯斜褘 锌褉芯写芯谢卸懈褌褜.</string>
+    <string name="devicetype_vivomove_hr">Garmin Vivomove HR</string>
+    <string name="pref_device_action_broadcast">袨褌锌褉邪胁懈褌褜 褌褉邪薪褋谢褟褑懈褞</string>
+    <string name="prefs_ambient_sound_during_call_summary">袨褌褔褢褌谢懈胁芯 褋谢褘褕邪褌褜 褋胁芯泄 谐芯谢芯褋 胁芯 胁褉械屑褟 蟹胁芯薪泻芯胁</string>
+    <string name="devicetype_amazfit_bip5">Amazfit Bip 5</string>
+    <string name="devicetype_amazfit_trex_ultra">Amazfit T-Rex Ultra</string>
+    <string name="devicetype_amazfit_gtr_mini">Amazfit GTR Mini</string>
+    <string name="device_experimental">协袣小袩袝袪袠袦袝袧孝袗袥鞋袧袨</string>
+    <string name="devicetype_amazfit_cheetah_pro">Amazfit Cheetah Pro</string>
+    <string name="devicetype_amazfit_cheetah_round">Amazfit Cheetah (泻褉褍谐谢褘械)</string>
+    <string name="watchface_setting_title_custom_events">袩芯谢褜蟹芯胁邪褌械谢褜褋泻懈械 褋芯斜褘褌懈褟</string>
+    <string name="menuitem_zepp_coach">Zepp Coach</string>
+    <string name="sony_speak_to_chat_focus_on_voice">袣芯薪褑械薪褌褉邪褑懈褟 薪邪 谐芯谢芯褋械</string>
+    <string name="sony_button_mode_help_title">袪械卸懈屑褘 泻薪芯锌芯泻 - 袩芯屑芯褖褜</string>
+    <string name="withings_steel_hr">Withings Steel HR</string>
+    <string name="devicetype_amazfit_cheetah_square">Amazfit Cheetah Square (泻胁邪写褉邪褌薪褘械)</string>
+    <string name="devicetype_amazfit_falcon">Amazfit Falcon</string>
+    <string name="maxCadence">袦邪泻褋懈屑褍屑 褕邪谐芯胁 胁 屑懈薪褍褌褍</string>
+    <string name="minStride">袦懈薪懈屑邪谢褜薪邪褟 写谢懈薪邪 褕邪谐邪</string>
+    <string name="prefs_autoheartrate">袗胁褌芯蟹邪屑械褉褘 褋械褉写褑械斜懈械薪懈褟</string>
+    <string name="prefs_voice_detect">袗泻褌懈胁邪褑懈褟 锌芯 谐芯谢芯褋褍</string>
+    <string name="sony_ambient_sound_focus_voice">袣芯薪褑械薪褌褉邪褑懈褟 薪邪 谐芯谢芯褋械</string>
+    <string name="maxStride">袦邪泻褋懈屑邪谢褜薪邪褟 写谢懈薪邪 褕邪谐邪</string>
+    <string name="prefs_double_tap_edge_summary">袟邪褋褔懈褌褘胁邪褌褜 写胁芯泄薪芯械 泻邪褋邪薪懈械, 写邪卸械 械褋谢懈 芯薪芯 斜褘谢芯 锌芯 褝泻褉邪薪褍</string>
+    <string name="prefs_sleep_time_summary">袙褉械屑褟, 泻芯谐写邪 褋芯斜懈褉邪械褌褋褟 懈薪褎芯褉屑邪褑懈褟 芯 褋薪械</string>
+    <string name="prefs_voice_detect_duration">袟邪胁械褉褕懈褌褜 锌芯褋谢械 褌懈褕懈薪褘 薪邪 锌褉芯褌褟卸械薪懈懈:</string>
+    <string name="pai_day">袩褉懈褉芯褋褌 蟹邪 写械薪褜</string>
+    <string name="sony_anc_optimize_confirmation_description">袠褋锌芯谢褜蟹褍泄褌械 薪邪褍褕薪懈泻懈 锌芯-芯斜褘褔薪芯屑褍. 袝褋谢懈 褍褋谢芯胁懈褟 薪芯褕械薪懈褟 懈谢懈 邪褌屑芯褋褎械褉薪芯械 写邪胁谢械薪懈械 懈蟹屑械薪褟褌褋褟, 蟹邪锌褍褋褌懈褌械 芯锌褌懈屑懈蟹邪褑懈褞 械褖褢 褉邪蟹.</string>
+    <string name="sony_speak_to_chat">袪邪蟹谐芯胁芯褉 锌褉懈 薪芯褕械薪懈懈 谐邪褉薪懈褌褍褉褘</string>
+    <string name="pref_header_sony_equalizer_preset_custom_1">小胁芯褟 锌褉械写褍褋褌邪薪芯胁泻邪 1</string>
+    <string name="sony_equalizer_preset_vocal">袙芯泻邪谢</string>
+    <string name="pref_media_forward">袩褉芯锌褍褋褌懈褌褜 胁锌械褉褢写</string>
+    <string name="updatefirmwareoperation_updateproblem_free_space">袧邪 褍褋褌褉芯泄褋褌胁械 薪械写芯褋褌邪褌芯褔薪芯 褋胁芯斜芯写薪芯谐芯 屑械褋褌邪</string>
+    <string name="quick_attention">袘褘褋褌褉芯械 锌褉懈胁谢械褔械薪懈械 胁薪懈屑邪薪懈褟</string>
+    <string name="pref_media_rewind">袩褉芯锌褍褋褌懈褌褜 薪邪蟹邪写</string>
+    <string name="sony_equalizer_preset_relaxed">袪邪褋褋谢邪斜谢械薪薪褘泄</string>
+    <string name="pref_media_previous">袩褉械写褘写褍褖懈泄</string>
+    <string name="devicetype_soflow_s06">SoFlow SO6</string>
+    <string name="watchface_dialog_pre_setting_position">锌褉械写褍褋褌邪薪芯胁泻邪 锌芯蟹懈褑懈懈 薪邪 %s</string>
+    <string name="devicetype_bohemic_smart_bracelet">校屑薪褘泄 斜褉邪褋谢械褌 Bohemic</string>
+    <string name="text_receiver_activity_title">袨褌锌褉邪胁懈褌褜 褌械泻褋褌 薪邪 褍褋褌褉芯泄褋褌胁芯</string>
+    <string name="activity_type_stepper">小褌械锌锌械褉</string>
+    <string name="battery_case">袟邪褉褟写薪褘泄 褎褍褌谢褟褉</string>
+    <string name="prefs_wena3_notification_vibration_repetition_3">孝褉懈卸写褘</string>
+    <string name="prefs_wena3_auto_power_off_turn_off_time_item">袙褉械屑褟 胁褘泻谢褞褔械薪懈褟</string>
+    <string name="prefs_wena3_vibration_short">袣芯褉芯褌泻邪褟</string>
+    <string name="prefs_wena3_home_icon_name_calories">袣邪谢芯褉懈懈</string>
+    <string name="prefs_wena3_led_none">袘械蟹 懈薪写懈泻邪褌芯褉邪</string>
+    <string name="updatefirmwareoperation_failed_low_mtu">孝械泻褍褖邪褟 MTU %1$d 褋谢懈褕泻芯屑 薪懈蟹泻邪褟. 袩芯卸邪谢褍泄褋褌邪, 胁泻谢褞褔懈褌械 胁褘褋芯泻褍褞 MTU 胁 薪邪褋褌褉芯泄泻邪褏 褍褋褌褉芯泄褋褌胁邪 懈 锌械褉械锌芯写泻谢褞褔懈褌械 械谐芯.</string>
+    <string name="prefs_wena3_home_icon_name_riiiver">Riiiver</string>
+    <string name="prefs_wena3_vibration_continuous">袩褉芯写芯谢卸懈褌械谢褜薪邪褟</string>
+    <string name="prefs_wena3_home_icon_name_pedometer">袣芯谢-胁芯 褕邪谐芯胁</string>
+    <string name="temperature_scale_celsius">袩芯 笑械谢褜褋懈褞</string>
+    <string name="prefs_wena3_vibration_basic">袩褉芯褋褌邪褟</string>
+    <string name="prefs_wena3_notification_vibration_repetition_2">袛胁邪卸写褘</string>
+    <string name="prefs_wena3_vibration_rapid">袘褘褋褌褉邪褟</string>
+    <string name="temperature_scale_cf">袝写懈薪懈褑邪 懈蟹屑械褉械薪懈褟 褌械屑锌械褉邪褌褍褉褘</string>
+    <string name="prefs_wena3_notification_vibration_repetition_1">袝写懈薪芯卸写褘</string>
+    <string name="prefs_wena3_home_icon_name_wena_pay">Wena Pay</string>
+    <string name="prefs_wena3_button_action_item">袛械泄褋褌胁懈械 泻薪芯锌泻懈</string>
+    <string name="prefs_wena3_notification_default_call_vibration">袙懈斜褉邪褑懈褟 锌褉懈 胁褏芯写褟褖懈褏 蟹胁芯薪泻邪褏</string>
+    <string name="prefs_wena3_notification_default_call_led">笑胁械褌 懈薪写懈泻邪褌芯褉邪 锌褉懈 胁褏芯写褟褖械屑 蟹胁芯薪泻械</string>
+    <string name="prefs_wena3_vibration_siren">小懈褉械薪邪</string>
+    <string name="temperature_scale_cf_summary">楔泻邪谢邪 懈蟹屑械褉械薪懈褟 褌械屑锌械褉邪褌褍褉褘, 懈褋锌芯谢褜蟹褍械屑邪褟 褍褋褌褉芯泄褋褌胁芯屑.</string>
+    <string name="prefs_wena3_notification_default_call_vibration_repetition">袩芯胁褌芯褉械薪懈褟 胁懈斜褉邪褑懈懈 锌褉懈 胁褏芯写褟褖懈褏</string>
+    <string name="prefs_wena3_notification_vibration_repetition_0">袩芯褋褌芯褟薪薪芯</string>
+    <string name="prefs_wena3_notification_vibration_repetition_4">效械褌褘褉械卸写褘</string>
+    <string name="temperature_scale_fahrenheit">袩芯 肖邪褉械薪谐械泄褌褍</string>
+    <string name="prefs_wena3_menu_icon_name_payment">袣邪褉褌褘 芯锌谢邪褌褘</string>
+    <string name="prefs_wena3_notification_default_led">笑胁械褌 懈薪写懈泻邪褌芯褉邪 褍胁械写芯屑谢械薪懈泄</string>
+    <string name="prefs_wena3_auto_power_off_item">袗胁褌芯胁褘泻谢褞褔械薪懈械</string>
+    <string name="prefs_wena3_vibration_warning">袩褉械写褍锌褉械卸写械薪懈械</string>
+    <string name="prefs_wena3_notification_per_app_settings_title">袧邪褋褌褉芯泄泻懈 褍胁械写芯屑谢械薪懈泄 芯褌 锌褉懈谢芯卸械薪懈泄</string>
+    <string name="prefs_wena3_vibration_none">袘械蟹 胁懈斜褉邪褑懈懈</string>
+    <string name="prefs_wena3_vibration_triple">孝褉芯泄薪邪褟</string>
+    <string name="prefs_wena3_notification_settings_title">袧邪褋褌褉芯泄泻懈 褍胁械写芯屑谢械薪懈泄</string>
+    <string name="prefs_wena3_receive_calls_title">袧邪褋褌褉芯泄泻懈 胁褏芯写褟褖懈褏 蟹胁芯薪泻芯胁</string>
+    <string name="prefs_wena3_button_action_name_start_timer">袟邪锌褍褋褌懈褌褜 褌邪泄屑械褉</string>
+    <string name="prefs_wena3_menu_icon_hint">袠泻芯薪泻邪 \"袧邪褋褌褉芯泄泻懈\" 胁褋械谐写邪 芯褌芯斜褉邪卸邪械褌褋褟 胁 泻芯薪褑械 屑械薪褞</string>
+    <string name="prefs_wena3_button_action_name_qrio_lock">袟邪泻褉褘褌褜 蟹邪屑芯泻 Qrio</string>
+    <string name="prefs_home_icon_left_item">袥械胁褘泄</string>
+    <string name="prefs_wena3_vibration_step_down">袧懈褋褏芯写褟褖邪褟</string>
+    <string name="prefs_wena3_vibration_smart_item">小屑邪褉褌-胁懈斜褉邪褑懈褟</string>
+    <string name="prefs_wena3_hint_background_sync">袪邪蟹褉械褕懈褌褜 Wena 锌械褉懈芯写懈褔械褋泻懈 锌褉芯褋懈褌褜 Gadgetbridge 褋泻邪褔邪褌褜 薪邪泻芯锌谢械薪薪褍褞 懈褋褌芯褉懈褞 邪泻褌懈胁薪芯褋褌懈 褋 褍褋褌褉芯泄褋褌胁邪</string>
+    <string name="prefs_wena3_home_icon_name_suica">袘邪谢邪薪褋 薪邪 Suica</string>
+    <string name="prefs_wena3_vibration_strength_item_medium">小褉械写薪械</string>
+    <string name="prefs_wena3_title_activity">袧邪褋褌褉芯泄泻懈 邪泻褌懈胁薪芯褋褌懈</string>
+    <string name="prefs_wena3_home_icon_name_qrio">Qrio</string>
+    <string name="white">袘械谢褘泄</string>
+    <string name="prefs_wena3_home_icon_name_clock">孝械泻褍褖械械 胁褉械屑褟</string>
+    <string name="prefs_wena3_item_background_sync">小懈薪褏褉芯薪懈蟹邪褑懈褟 邪泻褌懈胁薪芯褋褌懈 胁 褎芯薪械</string>
+    <string name="prefs_wena3_vibration_strength_item_weak">小谢邪斜芯</string>
+    <string name="prefs_wena3_home_icon_title">袟薪邪褔泻懈 薪邪 谐谢邪胁薪芯屑 褝泻褉邪薪械</string>
+    <string name="prefs_wena3_item_weather_statusbar">袩芯谐芯写邪 胁 褋褌邪褌褍褋-斜邪褉械</string>
+    <string name="prefs_wena3_vibration_step_up">袙芯褋褏芯写褟褖邪褟</string>
+    <string name="prefs_home_icon_right_item">袩褉邪胁褘泄</string>
+    <string name="prefs_wena3_title_screen">袧邪褋褌褉芯泄泻懈 芯褌芯斜褉邪卸械薪懈褟</string>
+    <string name="prefs_wena3_item_large_font">袣褉褍锌薪褘泄 褕褉懈褎褌</string>
+    <string name="prefs_wena3_home_icon_name_energy">协薪械褉谐懈褟</string>
+    <string name="prefs_wena3_auto_power_off_hint">校褋褌褉芯泄褋褌胁芯 斜褍写械褌 胁褘泻谢褞褔邪褌褜褋褟 懈 胁泻谢褞褔邪褌褜褋褟 邪胁褌芯屑邪褌懈褔械褋泻懈 胁 蟹邪写邪薪薪芯械 胁褉械屑褟</string>
+    <string name="prefs_wena3_hint_lift_wrist">袙泻谢褞褔邪褌褜 褝泻褉邪薪 锌褉懈 锌芯胁芯褉芯褌械 蟹邪锌褟褋褌褜褟</string>
+    <string name="prefs_wena3_hint_large_font">校胁械谢懈褔懈胁邪械褌 褉邪蟹屑械褉 褌械泻褋褌邪 胁 泻邪谢械薪写邪褉械, 褍胁械写芯屑谢械薪懈褟褏, 懈 褌.写.</string>
+    <string name="prefs_wena3_button_action_item_double">袛胁芯泄薪芯械 薪邪卸邪褌懈械</string>
+    <string name="prefs_wena3_button_action_name_qrio_unlock">袨褌泻褉褘褌褜 蟹邪屑芯泻 Qrio</string>
+    <string name="prefs_wena3_receive_calls_item">袩褉懈薪懈屑邪褌褜 胁褏芯写褟褖懈械 蟹胁芯薪泻懈</string>
+    <string name="prefs_wena3_hint_weather_statusbar">袩芯泻邪蟹褘胁邪褌褜 懈泻芯薪泻褍 褌械泻褍褖械泄 锌芯谐芯写褘 胁 谢械胁芯屑 胁械褉褏薪械屑 褍谐谢褍 谐谢邪胁薪芯谐芯 褝泻褉邪薪邪</string>
+    <string name="prefs_wena3_auto_power_off_turn_on_time_item">袙褉械屑褟 胁泻谢褞褔械薪懈褟</string>
+    <string name="green">袟械谢褢薪褘泄</string>
+    <string name="yellow">袞褢谢褌褘泄</string>
+    <string name="red">袣褉邪褋薪褘泄</string>
+    <string name="cyan">袘懈褉褞蟹芯胁褘泄</string>
+    <string name="prefs_wena3_menu_icon_title">袟薪邪褔泻懈 屑械薪褞</string>
+    <string name="prefs_wena3_notification_default_vibration">袙懈斜褉邪褑懈褟 写谢褟 褍胁械写芯屑谢械薪懈泄</string>
+    <string name="prefs_wena3_vibration_strength_item">小懈谢邪 胁懈斜褉邪褑懈懈</string>
+    <string name="prefs_wena3_receive_calls_hint">袝褋谢懈 胁褘泻谢褞褔械薪芯, 褌芯 胁褏芯写褟褖懈械 蟹胁芯薪泻懈 薪械 斜褍写褍褌 芯褌芯斜褉邪卸邪褌褜褋褟 薪邪 Wena</string>
+    <string name="prefs_wena3_button_action_item_long">袧邪卸邪褌懈械 褋 褍写械褉卸邪薪懈械屑</string>
+    <string name="prefs_wena3_status_page_title">袩芯褉褟写芯泻 褋褌褉邪薪懈褑 褋褌邪褌褍褋邪</string>
+    <string name="prefs_wena3_title_alarm">袧邪褋褌褉芯泄泻懈 斜褍写懈谢褜薪懈泻邪</string>
+    <string name="prefs_wena3_notification_default_vibration_repetition">袩芯胁褌芯褉 胁懈斜褉邪褑懈懈 锌褉懈 褍胁械写芯屑谢械薪懈褟褏</string>
+    <string name="prefs_home_icon_center_item">小褉械写薪懈泄</string>
+    <string name="prefs_wena3_hint_rich_design">袛芯斜邪胁谢褟械褌 锌褉褟屑芯褍谐芯谢褜薪褘泄 褎芯薪 胁芯泻褉褍谐 懈泻芯薪芯泻 薪邪 谐谢邪胁薪芯屑 褝泻褉邪薪械</string>
+    <string name="prefs_wena3_home_icon_name_edy">袘邪谢邪薪褋 薪邪 Edy</string>
+    <string name="prefs_wena3_smart_alarm_margin_item">袟邪锌邪褋 写谢褟 褍屑薪芯谐芯 锌褉芯斜褍卸写械薪懈褟</string>
+    <string name="prefs_wena3_item_rich_design">袗谢褜褌械褉薪邪褌懈胁薪褘泄 写懈蟹邪泄薪</string>
+    <string name="prefs_wena3_day_start_hour_item">袧邪褔懈薪邪褌褜 写械薪褜 胁</string>
+    <string name="purple">肖懈芯谢械褌芯胁褘泄</string>
+    <string name="prefs_wena3_vibration_strength_item_strong">小懈谢褜薪芯</string>
+    <string name="blue">小懈薪懈泄</string>
+    <string name="prefs_wena3_button_action_name_activity_screen">袨褌芯斜褉邪卸械薪懈械 邪泻褌懈胁薪芯褋褌懈</string>
+    <string name="pref_device_action_fitness_app_control_start">袧邪褔邪谢芯 芯褌褋谢械卸懈胁邪薪懈褟 褎懈褌薪械褋-锌褉懈谢芯卸械薪懈械屑</string>
+    <string name="devicetype_miband2_hrx">Mi Band HRX (斜械蟹 写邪褌褔懈泻邪 效小小)</string>
+    <string name="new_discover_activity_description">袙泻谢褞褔懈褌褜 薪芯胁芯械 写械泄褋褌胁懈械 芯斜薪邪褉褍卸械薪懈褟, 泻芯褌芯褉芯械 写芯谢卸薪芯 褍褋褌褉邪薪懈褌褜 锌褉芯斜谢械屑褘 褋 芯斜薪邪褉褍卸械薪懈械屑 褍褋褌褉芯泄褋褌胁. 袨褌泻谢褞褔懈褌械 褝褌褍 褎褍薪泻褑懈褞, 械褋谢懈 褍 胁邪褋 胁芯蟹薪懈泻薪褍褌 泻邪泻懈械-谢懈斜芯 锌褉芯斜谢械屑褘 锌褉懈 锌芯懈褋泻械 懈谢懈 褋芯锌褉褟卸械薪懈懈 褋 胁邪褕懈屑 褍褋褌褉芯泄褋褌胁芯屑.</string>
+    <string name="drag_handle">锌械褉械褌邪褋泻懈胁邪薪懈械 褝谢械屑械薪褌芯胁</string>
+    <string name="new_discover_activity_title">袙泻谢褞褔懈褌褜 薪芯胁芯械 芯斜薪邪褉褍卸械薪懈械 邪泻褌懈胁薪芯褋褌懈</string>
+    <string name="devicetype_sony_wena3">Sony Wena 3</string>
+    <string name="watchface_setting_power_saving_hands">袙褘泻谢褞褔懈褌褜 写胁懈卸械薪懈褟 褉褍泻 锌褉懈 褋薪褟褌芯屑 褉械屑械褕泻械</string>
+    <string name="pref_device_action_fitness_app_control_stop">袨褋褌邪薪芯胁泻邪 芯褌褋谢械卸懈胁邪薪懈褟 褎懈褌薪械褋-锌褉懈谢芯卸械薪懈械屑</string>
+    <string name="pref_header_sony_equalizer_bands">袛懈邪锌邪蟹芯薪褘</string>
+    <string name="devicetype_sony_wf_1000xm5">Sony WF-1000XM5</string>
+    <string name="sony_equalizer_preset_mellow">袦褟谐泻懈泄</string>
+    <string name="sony_equalizer_preset_excited">袙蟹胁芯谢薪芯胁邪薪薪褘泄</string>
+    <string name="pref_title_lock_unlock">袟邪斜谢芯泻懈褉芯胁邪褌褜</string>
+    <string name="sony_equalizer_preset_treble_boost">袩芯胁褘褕械薪懈械 胁褘褋芯泻懈褏 褔邪褋褌芯褌</string>
+    <string name="pref_device_action_fitness_app_control_toggle">袩械褉械泻谢褞褔懈褌褜 芯褌褋谢械卸懈胁邪薪懈械 褎懈褌薪械褋-锌褉懈谢芯卸械薪懈褟</string>
+    <string name="prefs_switch_control_right">袩械褉械泻谢褞褔懈褌褜 褍锌褉邪胁谢械薪懈械 褋锌褉邪胁邪</string>
+    <string name="prefs_switch_control_left">袩械褉械泻谢褞褔懈褌褜 褍锌褉邪胁谢械薪懈械 褋谢械胁邪</string>
+    <string name="watchface_setting_button_move_hands">袛胁懈卸械薪懈械 褉褍泻</string>
+    <string name="pref_title_touch_quick_ambient">袘褘褋褌褉褘械 薪邪褋褌褉芯泄泻懈 蟹胁褍泻邪 芯泻褉褍卸械薪懈褟</string>
+    <string name="pref_message_privacy_mode_bodyonly">小泻褉褘褌褜 褌芯谢褜泻芯 褌械谢芯</string>
+    <string name="latvian">袥邪褌褘褕褋泻懈泄</string>
+    <string name="common_symbols">袨斜褘褔薪褘械 褋懈屑胁芯谢褘</string>
+    <string name="pref_title_notifications_ignore_work_profile">袠谐薪芯褉懈褉芯胁邪褌褜 褍胁械写芯屑谢械薪懈褟 褉邪斜芯褔械谐芯 锌褉芯褎懈谢褟</string>
+    <string name="pref_summary_notifications_ignore_work_profile">袧械 芯褌锌褉邪胁谢褟褌褜 褍胁械写芯屑谢械薪懈褟 锌褉懈谢芯卸械薪懈泄 懈蟹 褉邪斜芯褔械谐芯 锌褉芯褎懈谢褟 薪邪 褔邪褋褘</string>
+    <string name="zepp_os_watchface_pure_white">携褉泻芯-斜械谢褘泄</string>
+    <string name="zepp_os_watchface_the_ultima">校谢褜褌懈屑邪</string>
+    <string name="zepp_os_watchface_city_of_speed">袚芯褉芯写 小泻芯褉芯褋褌懈</string>
+    <string name="menuitem_headphone">袧邪褍褕薪懈泻</string>
+    <string name="femometer_measurement_mode_normal">袧芯褉屑邪谢褜薪褘泄 (60-90 褋械泻.)</string>
+    <string name="zepp_os_watchface_free_combination">小胁芯斜芯写薪邪褟 泻芯屑斜懈薪邪褑懈褟</string>
+    <string name="menuitem_body_composition">小芯褋褌邪胁 褌械谢邪</string>
+    <string name="menuitem_zepp_pay">Zepp Pay</string>
+    <string name="femometer_measurement_mode_quick">袘褘褋褌褉褘泄 (30 褋械泻.)</string>
+    <string name="zepp_os_watchface_starry_sky">袟胁褢蟹写薪芯械 薪械斜芯</string>
+    <string name="devicetype_amazfit_balance">Amazfit Balance</string>
+    <string name="femometer_measurement_mode_title">袪械卸懈屑 懈蟹屑械褉械薪懈褟</string>
+    <string name="femometer_measurement_mode_precise">孝芯褔薪褘泄 (3 屑懈薪.)</string>
+    <string name="zepp_os_watchface_vast_sky">袧械芯斜褗褟褌薪芯械 薪械斜芯</string>
+    <string name="menuitem_workout_shortcuts">携褉谢褘泻懈 褌褉械薪懈褉芯胁芯泻</string>
+    <string name="zepp_os_watchface_lightning_flash">小胁械褉泻邪薪懈械 屑芯谢薪懈懈</string>
+    <string name="menuitem_apps_shortcuts">携褉谢褘泻懈 锌褉懈谢芯卸械薪懈泄</string>
+    <string name="zepp_os_watchface_guider">袪褍泻芯胁芯写褋褌胁芯</string>
+    <string name="menuitem_readiness">袚芯褌芯胁薪芯褋褌褜</string>
+    <string name="menuitem_thermometer">孝械褉屑芯屑械褌褉</string>
+    <string name="devicetype_femometer_vinca2">Femometer Vinca II</string>
+    <string name="fossil_hr_nav_app_not_installed_notify_text">袧邪胁懈谐邪褑懈褟 斜褘谢邪 薪邪褔邪褌邪, 薪芯 navigationApp 薪械 褍褋褌邪薪芯胁谢械薪芯 薪邪 褔邪褋邪褏. 袩芯卸邪谢褍泄褋褌邪, 褍褋褌邪薪芯胁懈褌械 械谐芯 胁 屑械薪械写卸械褉械 锌褉懈谢芯卸械薪懈泄.</string>
+    <string name="fossil_hr_nav_app_not_installed_notify_title">袩褉懈谢芯卸械薪懈械 写谢褟 薪邪胁懈谐邪褑懈懈 薪械 褍褋褌邪薪芯胁谢械薪芯 薪邪 褔邪褋邪褏</string>
+    <string name="call_rejection_method_ignore">袠谐薪芯褉懈褉芯胁邪褌褜 (锌褉械泻褉邪褌懈褌褜 褍胁械写芯屑谢械薪懈械)</string>
+    <string name="call_rejection_method_reject">袨褌泻谢芯薪懈褌褜</string>
+    <string name="pref_call_rejection_method_title">小锌芯褋芯斜 芯褌泻谢芯薪械薪懈褟 胁褘蟹芯胁邪</string>
+    <string name="busy_task_fetch_statistics">袩芯谢褍褔械薪懈械 褋褌邪褌懈褋褌懈泻懈</string>
+    <string name="yard">写胁芯褉</string>
+    <string name="pref_call_rejection_method_summary">袣邪泻芯械 写械泄褋褌胁懈械 薪械芯斜褏芯写懈屑芯 褋芯胁械褉褕懈褌褜 锌褉懈 芯褌泻谢芯薪械薪懈懈 胁褘蟹芯胁邪 褋 褔邪褋芯胁</string>
+    <string name="busy_task_fetch_temperature">袩芯谢褍褔械薪懈械 褌械屑锌械褉邪褌褍褉褘</string>
+    <string name="laneLength">袛谢懈薪邪 写芯褉芯卸泻懈</string>
+    <string name="devicetype_amazfit_active_edge">Amazfit Active Edge</string>
+    <string name="charts_legend_spo2_average">小褉械写薪懈泄 褍褉芯胁械薪褜 泻懈褋谢芯褉芯写邪 胁 泻褉芯胁懈</string>
+    <string name="devicetype_amazfit_active">Amazfit Active</string>
+    <string name="pref_title_navigation_apps">袧邪胁懈谐邪褑懈芯薪薪褘械 锌褉懈谢芯卸械薪懈褟</string>
+    <string name="pref_title_navigation_prefs">袧邪褋褌褉芯泄泻懈 薪邪胁懈谐邪褑懈懈</string>
+    <string name="pref_navigation_app_gmaps">Google 袣邪褉褌褘</string>
+    <string name="pref_summary_prefix_notification_with_app">袧邪褔懈薪邪褌褜 蟹邪谐芯谢芯胁芯泻 褍胁械写芯屑谢械薪懈褟 褋 薪邪蟹胁邪薪懈褟 锌褉懈谢芯卸械薪懈褟-懈褋褌芯褔薪懈泻邪</string>
+    <string name="pref_summary_osmand_packagename">袠褋锌芯谢褜蟹褍械褌褋褟 写谢褟 胁褘斜芯褉邪 懈褋锌芯谢褜蟹褍械屑芯泄 胁械褉褋懈懈 OsmAnd</string>
+    <string name="pref_title_prefix_notification_with_app">袧邪蟹胁邪薪懈械 锌褉懈谢芯卸械薪懈褟 胁 褍胁械写芯屑谢械薪懈懈</string>
+    <string name="pref_title_osmand_packagename">袠屑褟 锌邪泻械褌邪 OsmAnd</string>
+    <string name="pref_navigation_app_osmand">OsmAnd(+)</string>
+    <string name="pref_title_casio_alert_other">袨锌芯胁械褖械薪懈褟 锌褉芯褔懈褏 褍胁械写芯屑谢械薪懈泄</string>
+    <string name="pref_title_casio_alert_calendar">袨锌芯胁械褖械薪懈褟 泻邪谢械薪写邪褉褟</string>
+    <string name="pref_summary_casio_alert_call">袨锌芯胁械褖邪褌褜 芯 胁褏芯写褟褖懈褏 蟹胁芯薪泻邪褏 胁懈斜褉邪褑懈械泄/锌懈褖邪薪懈械屑</string>
+    <string name="devicetype_miband8">Xiaomi Smart Band 8</string>
+    <string name="menuitem_running">袘械谐</string>
+    <string name="pref_sleep_mode_schedule_title">袪邪褋锌懈褋邪薪懈械 褋锌褟褖械谐芯 褉械卸懈屑邪</string>
+    <string name="pref_title_casio_alert_call">袨锌芯胁械褖械薪懈褟 芯 胁褏芯写褟褖懈褏</string>
+    <string name="bedtime">袧邪褔邪谢芯 褋薪邪</string>
+    <string name="pref_title_casio_alert_email">袨锌芯胁械褖械薪懈褟 褝谢. 锌芯褔褌褘</string>
+    <string name="pref_summary_casio_alert_email">袨锌芯胁械褖邪褌褜 芯 薪芯胁褘褏 锌懈褋褜屑邪褏 胁懈斜褉邪褑懈械泄/锌懈褖邪薪懈械屑</string>
+    <string name="menuitem_focus">袣芯薪褑械薪褌褉邪褑懈褟</string>
+    <string name="pref_summary_casio_alert_sms">袨锌芯胁械褖邪褌褜 芯 锌芯谢褍褔械薪薪褘褏 SMS (褌械泻褋褌芯胁褘褏) 褋芯芯斜褖械薪懈褟褏 胁懈斜褉邪褑懈械泄/锌懈褖邪薪懈械屑</string>
+    <string name="pref_title_preview_message_in_title">袩芯泻邪蟹褘胁邪褌褜 褋芯芯斜褖械薪懈械 胁 蟹邪谐芯谢芯胁泻械</string>
+    <string name="devicetype_redmiwatch3active">Redmi Watch 3 Active</string>
+    <string name="pref_summary_casio_alert_calendar">袨锌芯胁械褖邪褌褜 芯 褋芯斜褘褌懈褟褏 泻邪谢械薪写邪褉褟 胁懈斜褉邪褑懈械泄/锌懈褖邪薪懈械屑</string>
+    <string name="serial_number">小械褉懈泄薪褘泄 薪芯屑械褉</string>
+    <string name="danish">袛邪褌褋泻懈泄</string>
+    <string name="wake_up_time">袩褉芯斜褍卸写械薪懈械</string>
+    <string name="menuitem_stats">小褌邪褌懈褋褌懈泻邪</string>
+    <string name="pref_summary_casio_alert_other">袨锌芯胁械褖邪褌褜 锌褉懈 锌褉芯褔懈褏 褍胁械写芯屑谢械薪懈褟褏 胁懈斜褉邪褑懈械泄/锌懈褖邪薪懈械屑</string>
+    <string name="devicetype_xiaomi_watch_lite">Xiaomi Watch Lite</string>
+    <string name="pref_summary_preview_message_in_title">袩芯泻邪蟹褘胁邪褌褜 褋芯芯斜褖械薪懈械 胁 蟹邪谐芯谢芯胁泻械 褍胁械写芯屑谢械薪懈褟 胁 褋芯芯褌胁械褌褋褌胁懈懈 褋 薪邪褋褌褉芯泄泻邪屑懈 泻芯薪褎懈写械薪褑懈邪谢褜薪芯褋褌懈 褍褋褌褉芯泄褋褌胁邪</string>
+    <string name="pref_sleep_mode_schedule_summary">袨褌锌褉邪胁褜褌械 薪邪锌芯屑懈薪邪薪懈械 懈 锌械褉械泄写懈褌械 胁 褉械卸懈屑 褋薪邪 锌械褉械写 褋薪芯屑. 袙 蟹邪锌谢邪薪懈褉芯胁邪薪薪芯械 胁褉械屑褟 锌褉芯斜褍卸写械薪懈褟 锌褉芯蟹胁褍褔懈褌 褋懈谐薪邪谢 斜褍写懈谢褜薪懈泻邪.</string>
+    <string name="pref_title_casio_alert_sms">袨锌芯胁械褖械薪懈褟 SMS-褋芯芯斜褖械薪懈泄</string>
+    <string name="menuitem_alerts">袨锌芯胁械褖械薪懈褟</string>
+    <string name="devicetype_miband7pro">Xiaomi Smart Band 7 Pro</string>
+    <string name="devicetype_xiaomi_watch_s1_active">Xiaomi Watch S1 Active</string>
+    <string name="devicetype_mi_watch_color_sport">Mi Watch Color Sports</string>
+    <string name="devicetype_pixoo">Pixoo</string>
+    <string name="pref_summary_send_app_notifications">袩械褉械写邪胁邪褌褜 褍胁械写芯屑谢械薪懈褟 锌褉懈谢芯卸械薪懈泄 薪邪 褍褋褌褉芯泄褋褌胁芯</string>
+    <string name="pref_title_send_app_notifications">袩械褉械写邪胁邪褌褜 褍胁械写芯屑谢械薪懈褟</string>
+    <string name="activity_type_trail_run">孝褉械泄谢褉邪薪薪懈薪谐</string>
+    <string name="widget_layout">袦邪泻械褌 胁懈写卸械褌邪</string>
+    <string name="widget_screen_no_free_slots_description">袧邪 褍褋褌褉芯泄褋褌胁械 薪械 芯褋褌邪谢芯褋褜 褋胁芯斜芯写薪褘褏 屑械褋褌 锌芯写 褝泻褉邪薪褘 胁懈写卸械褌芯胁 (胁褋械谐芯: %1$s)</string>
+    <string name="pref_device_action_dnd_on">袧械 斜械褋锌芯泻芯懈褌褜 - 袙泻谢褞褔械薪芯</string>
+    <string name="prefs_phone_silent_mode">袘械蟹蟹胁褍褔薪褘泄 褉械卸懈屑 褌械谢械褎芯薪邪</string>
+    <string name="widget_unknown_workout">袧械懈蟹胁械褋褌薪褘泄 褌懈锌 褌褉械薪懈褉芯胁泻懈 - %s</string>
+    <string name="prefs_wearmode">孝懈锌 薪芯褕械薪懈褟</string>
+    <string name="wearmode_pebble">袣邪屑械褕械泻 (蟹邪褋褌械卸泻邪 写谢褟 芯斜褍胁懈)</string>
+    <string name="widget_move_up">袩械褉械屑械褋褌懈褌褜 胁褘褕械</string>
+    <string name="widget_screen_delete_confirm_description">袙褘 褌芯褔薪芯 褏芯褌懈褌械 褍写邪谢懈褌褜 芦%1$s禄?</string>
+    <string name="silent_mode_normal_vibrate">袧芯褉屑邪谢褜薪褘泄 / 袧邪 胁懈斜褉邪褑懈懈</string>
+    <string name="pref_title_goal_secondary">袛芯锌芯谢薪懈褌械谢褜薪邪褟 褑械谢褜</string>
+    <string name="pref_vitality_score_7_day_summary">袨褌锌褉邪胁谢褟褌褜 褍胁械写芯屑谢械薪懈械, 泻芯谐写邪 褍褉芯胁械薪褜 卸懈蟹薪械薪薪芯泄 褝薪械褉谐懈懈 蟹邪 锌芯褋谢械写薪懈械 7 写薪械泄 写芯褋褌懈谐邪械褌 30, 60 懈谢懈 100</string>
+    <string name="activity_type_trekking">袚芯褉薪褘泄 锌芯褏芯写</string>
+    <string name="pref_vitality_score_title">袘邪谢谢 卸懈蟹薪械褋锌芯褋芯斜薪芯褋褌懈</string>
+    <string name="widget_layout_two">2 胁懈写卸械褌邪</string>
+    <string name="widget_screen_delete_confirm_title">校写邪谢懈褌褜 褝泻褉邪薪 胁懈写卸械褌邪</string>
+    <string name="pref_device_action_dnd_alarms">袧械 斜械褋锌芯泻芯懈褌褜 - 孝芯谢褜泻芯 斜褍写懈谢褜薪懈泻</string>
+    <string name="widget_layout_top_1_bot_2">1 薪邪胁械褉褏褍, 2 胁薪懈蟹褍</string>
+    <string name="widget_layout_top_2_bot_2">2 薪邪胁械褉褏褍, 2 胁薪懈蟹褍</string>
+    <string name="widget_screen_min_screens">袛芯谢卸薪芯 斜褘褌褜 屑懈薪懈屑褍屑 %1$s 褝泻褉邪薪芯胁</string>
+    <string name="silent_mode_vibrate_silent">袧邪 胁懈斜褉邪褑懈懈 / 袘械蟹蟹胁褍褔薪褘泄</string>
+    <string name="widget">袙懈写卸械褌</string>
+    <string name="pref_vitality_score_daily_summary">袩芯谢褍褔懈褌械 褍胁械写芯屑谢械薪懈械, 泻芯谐写邪 胁褘 写芯褋褌懈谐薪械褌械 屑邪泻褋懈屑邪谢褜薪芯谐芯 泻芯谢懈褔械褋褌胁邪 芯褔泻芯胁 卸懈蟹薪械薪薪芯泄 褋懈谢褘 蟹邪 写械薪褜</string>
+    <string name="devicetype_redmi_smart_band_2">Redmi Smart Band 2</string>
+    <string name="widget_screen">协泻褉邪薪 胁懈写卸械褌邪</string>
+    <string name="widget_move_down">袩械褉械屑械褋褌懈褌褜 薪懈卸械</string>
+    <string name="widget_layout_single">1 胁懈写卸械褌</string>
+    <string name="pref_device_action_dnd_priority">袧械 斜械褋锌芯泻芯懈褌褜 - 孝芯谢褜泻芯 胁邪卸薪褘械</string>
+    <string name="not_set">袧械 蟹邪写邪薪芯</string>
+    <string name="widget_subtype">袩芯写褌懈锌 胁懈写卸械褌邪</string>
+    <string name="standing_time">袙褉械屑褟 褋褌芯褟</string>
+    <string name="active_time">袙褉械屑褟 邪泻褌懈胁薪芯褋褌懈</string>
+    <string name="wearmode_band">袧邪褉褍褔薪褘泄 斜褉邪褋谢械褌</string>
+    <string name="widget_screen_x">协泻褉邪薪 %s</string>
+    <string name="widget_missing_parts">袙褘斜械褉懈褌械 胁褋械 胁懈写卸械褌褘</string>
+    <string name="widget_layout_top_2_bot_1">2 薪邪胁械褉褏褍, 1 胁薪懈蟹褍</string>
+    <string name="wearmode_necklace">袣芯谢褜械 (褕薪褍褉芯泻 薪邪 褕械械)</string>
+    <string name="pref_vitality_score_daily_title">袩褉芯谐褉械褋褋 褋械谐芯写薪褟</string>
+    <string name="pref_vitality_score_7_day_title">袩褉芯谐褉械褋褋 蟹邪 7 写薪械泄</string>
+    <string name="pref_device_action_dnd_off">袧械 斜械褋锌芯泻芯懈褌褜 - 袙褘泻谢褞褔械薪芯</string>
+    <string name="silent_mode_normal_silent">袧芯褉屑邪谢褜薪褘泄 / 袘械蟹蟹胁褍褔薪褘泄</string>
+    <string name="activity_type_wrestling">袘芯褉褜斜邪</string>
+    <string name="devicetype_colacao23">ColaCao 2023</string>
+    <string name="devicetype_colacao21">ColaCao 2021</string>
+    <string name="clap_hands_to_wakeup_device">袗泻褌懈胁懈褉芯胁邪褌褜 褝泻褉邪薪 褏谢芯锌泻芯屑 褉褍泻邪屑懈</string>"
+    <string name="clap_hands_to_wakeup_device_summary">袩芯胁褌芯褉薪褘泄 褏谢芯锌芯泻 锌芯谐邪褋懈褌 械谐芯</string>"
+    <string name="pixoo_power_saving_summary">协泻褉邪薪 锌芯谐邪褋薪械褌 锌芯褋谢械 锌褉芯写芯谢卸懈褌械谢褜薪芯泄 褌懈褕懈薪褘, 蟹邪屑械褔械薪薪芯泄 屑懈泻褉芯褎芯薪芯屑</string>
+    <string name="devicetype_mijia_lywsd03">Mijia Temperature and Humidity Sensor 2</string>
+    <string name="pref_title_fossil_hr_navigation_instructions">校泻邪蟹邪薪懈褟 锌芯 薪邪胁懈谐邪褑懈懈</string>
+    <string name="pref_summary_fossil_hr_navigation_instructions">袧邪褋褌褉芯泄泻懈 胁褋褌褉芯械薪薪芯谐芯 锌褉懈谢芯卸械薪懈褟 薪邪胁懈谐邪褑懈懈</string>
+    <string name="pref_summary_fossil_hr_nav_foreground">袛芯谢卸薪芯 谢懈 锌芯褟胁谢褟褌褜褋褟 锌褉懈谢芯卸械薪懈械 薪邪胁懈谐邪褑懈懈 锌褉懈 锌芯谢褍褔械薪懈懈 薪邪胁懈谐邪褑懈芯薪薪褘褏 褍泻邪蟹邪薪懈泄</string>
+    <string name="pref_title_fossil_hr_nav_vibrate">袙懈斜褉懈褉芯胁邪褌褜 锌褉懈 薪芯胁褘褏 褍泻邪蟹邪薪懈褟褏</string>
+    <string name="pref_title_fossil_hr_nav_foreground">袗胁褌芯锌芯褟胁谢械薪懈械</string>
+    <string name="prefs_device_name">袠屑褟 褍褋褌褉芯泄褋褌胁邪</string>
+    <string name="pref_summary_fossil_hr_nav_vibrate">袛芯谢卸薪褘 谢懈 褔邪褋褘 胁懈斜褉懈褉芯胁邪褌褜 锌褉懈 锌芯谢褍褔械薪懈懈 懈谢懈 懈蟹屑械薪械薪懈懈 薪邪胁懈谐邪褑懈芯薪薪褘褏 褍泻邪蟹邪薪懈泄 (泻芯谐写邪 锌褉懈谢芯卸械薪懈械 锌芯泻邪蟹邪薪芯)</string>
+    <string name="devicetype_redmi_watch_2_lite">Redmi Watch 2 Lite</string>
+    <string name="notification_channel_connection_status_name">小芯褋褌芯褟薪懈械 褋芯械写懈薪械薪懈褟</string>
+    <string name="devicetype_redmi_smart_band_pro">Redmi Smart Band Pro</string>
+    <string name="devicetype_nothingear2">Nothing Ear (2)</string>
+    <string name="devicetype_nothingearstick">Nothing Ear (Stick)</string>
+    <string name="prefs_active_noise_cancelling_light">袥褢谐泻芯械 邪泻褌懈胁薪芯械 锌芯写邪胁谢械薪懈械 褕褍屑邪</string>
+    <string name="prefs_active_noise_cancelling_transparency">袩褉芯蟹褉邪褔薪芯褋褌褜</string>
+    <string name="devicetype_honor_band5">Honor Band 5</string>
+    <string name="devicetype_huawei_watch_gt">Huawei Watch GT</string>
+    <string name="devicetype_huawei_band4pro">Huawei Band 4 Pro</string>
+    <string name="devicetype_huawei_watchgt2">Huawei Watch GT 2 Pro</string>
+    <string name="devicetype_huawei_watchgt2e">Huawei Watch GT 2e</string>
+    <string name="devicetype_huawei_talk_band_b6">Huawei Talk Band B6</string>
+    <string name="devicetype_huawei_watchgt3">Huawei Watch GT 3 Pro</string>
+    <string name="prefs_disconnect_notification_summary">校胁械写芯屑谢械薪懈械 薪邪 褍褋褌褉芯泄褋褌胁械 锌褉懈 芯褌褋芯械写懈薪械薪懈懈 芯褌 Bluetooth.</string>
+    <string name="do_not_disturb_lift_wrist_summary">孝芯谢褜泻芯 械褋谢懈 胁泻谢褞褔械薪邪 邪泻褌懈胁邪褑懈褟 锌芯 锌芯写薪褟褌懈褞 褉褍泻懈</string>
+    <string name="pref_do_not_disturb_not_wear">\"袧械 斜械褋锌芯泻芯懈褌褜\", 泻芯谐写邪 褍褋褌褉芯泄褋褌胁芯 褋薪褟褌芯</string>
+    <string name="manual">袙褉褍褔薪褍褞</string>
+    <string name="devicetype_honor_band4">Honor Band 4</string>
+    <string name="devicetype_honor_band6">Honor Band 6</string>
+    <string name="devicetype_honor_band7">Honor Band 7</string>
+    <string name="devicetype_huawei_band_aw70">Huawei Band (AW70)</string>
+    <string name="dnd_all_day">袙械褋褜 写械薪褜</string>
+    <string name="devicetype_honor_band3">Honor Band 3</string>
+    <string name="devicetype_huawei_band8">Huawei Band 8</string>
+    <string name="devicetype_huawei_band6">Huawei Band 6</string>
+    <string name="devicetype_huawei_band7">Huawei Band 7</string>
+    <string name="prefs_workmode">袪械卸懈屑 褉邪斜芯褌褘</string>
+    <string name="huawei_alarm_smart_description">袧械 芯褌泻谢褞褔邪褌褜 \"褍屑薪褘泄 锌芯写褗褢屑\".</string>
+    <string name="huawei_alarm_event_description">袧械 胁泻谢褞褔邪褌褜 \"褍屑薪褘泄 锌芯写褗褢屑\".</string>
+    <string name="huawei_trusleep_title">HUAWEI TruSleep 鈩�</string>
+    <string name="huawei_trusleep_summary">袨褌褋谢械卸懈胁邪薪懈械 泻邪褔械褋褌胁邪 褋薪邪 懈 褏邪褉邪泻褌械褉邪 写褘褏邪薪懈褟 胁 褉械邪谢褜薪芯屑 胁褉械屑械薪懈.
+\n袗薪邪谢懈蟹 蟹邪泻芯薪芯屑械褉薪芯褋褌械泄 褋薪邪 懈 褌芯褔薪褘泄 写懈邪谐薪芯蟹 6 褌懈锌芯胁 锌褉芯斜谢械屑 褋芯 褋薪芯屑.</string>
+    <string name="pref_title_general_reconnectonlytoconnected">袩械褉械锌芯写泻谢褞褔邪褌褜褋褟 褌芯谢褜泻芯 泻 锌芯写泻谢褞褔械薪薪褘屑 褍褋褌褉芯泄褋褌胁邪屑</string>
+    <string name="pref_activity_recognize_biking">褉邪褋锌芯蟹薪邪胁邪褌褜 械蟹写褍 薪邪 胁械谢芯褋懈锌械写械</string>
+    <string name="pref_activity_recognize_walking">褉邪褋锌芯蟹薪邪胁邪褌褜 褏芯写褜斜褍</string>
+    <string name="pref_activity_recognition_mode_ask">褋锌褉邪褕懈胁邪褌褜</string>
+    <string name="pref_activity_recognition_mode_auto">邪胁褌芯屑邪褌懈褔械褋泻懈</string>
+    <string name="pref_enable_call_accept">袩褉懈薪褟褌懈械 褌械谢. 胁褘蟹芯胁芯胁</string>
+    <string name="pref_enable_call_accept_summary">袙泻谢褞褔懈褌褜 锌褉懈薪褟褌懈械 胁褘蟹芯胁芯胁 褋 褍褋褌褉芯泄褋褌胁邪</string>
+    <string name="pref_enable_call_reject">袨褌泻谢芯薪械薪懈械 褌械谢. 胁褘蟹芯胁芯胁</string>
+    <string name="pref_heartrate_automatic_enable">袗胁褌芯蟹邪屑械褉褘 褔邪褋褌芯褌褘 褋械褉写褑械斜懈械薪懈褟</string>
+    <string name="pref_spo_automatic_enable">袗胁褌芯蟹邪屑械褉褘 褍褉芯胁薪褟 泻懈褋谢芯褉芯写邪 胁 泻褉芯胁懈</string>
+    <string name="pref_force_options">肖芯褉褋懈褉芯胁邪薪懈械 芯锌褑懈泄</string>
+    <string name="pref_force_smart_alarm">袩芯写写械褉卸泻邪 褍屑薪芯谐芯 斜褍写懈谢褜薪懈泻邪</string>
+    <string name="pref_force_smart_alarm_summary">肖芯褉褋懈褉芯胁邪褌褜 锌芯写写械褉卸泻褍 褍屑薪褘褏 斜褍写懈谢褜薪懈泻芯胁.
+\n袠小袩袨袥鞋袟校袡孝袝 袧袗 小袙袨袡 袪袠小袣</string>
+    <string name="pref_force_wear_location">袩芯写写械褉卸泻邪 屑械褋褌邪 薪芯褕械薪懈褟</string>
+    <string name="pref_force_wear_location_summary">肖芯褉褋懈褉芯胁邪褌褜 锌芯写写械褉卸泻褍 褉邪褋锌芯谢芯卸械薪懈褟 薪芯褕械薪懈褟 褍褋褌褉芯泄褋褌胁邪.
+\n袠小袩袨袥鞋袟校袡孝袝 袧袗 小袙袨袡 袪袠小袣</string>
+    <string name="pref_force_dnd_support">袩芯写写械褉卸泻邪 \"薪械 斜械褋锌芯泻芯懈褌褜\"</string>
+    <string name="huawei_ignore_wakeup_status_start">袠谐薪芯褉懈褉芯胁邪褌褜 褋褌邪褌褍褋 薪邪褔邪谢邪 胁褋褌邪胁邪薪懈褟</string>
+    <string name="huawei_ignore_wakeup_status_start_description">袦芯卸械褌 锌芯屑芯褔褜 懈褋锌褉邪胁懈褌褜 褉邪褋锌芯蟹薪邪薪懈械 褋薪邪. 小褉邪蟹褍 胁懈写懈屑芯 胁 芯斜蟹芯褉械 写薪械胁薪褘褏 邪泻褌懈胁薪芯褋褌械泄.</string>
+    <string name="huawei_ignore_wakeup_status_end">袠谐薪芯褉懈褉芯胁邪褌褜 褋褌邪褌褍褋 芯泻芯薪褔邪薪懈褟 胁褋褌邪胁邪薪懈褟</string>
+    <string name="huawei_reparse_workout_data">袩芯胁褌芯褉薪褘泄 邪薪邪谢懈蟹 写邪薪薪褘褏 褌褉械薪懈褉芯胁泻懈</string>
+    <string name="pref_summary_debug">袨褌锌褉邪胁懈褌褜 蟹邪锌褉芯褋 芯褌谢邪写泻懈 薪邪 褍褋褌褉芯泄褋褌胁芯 Huawei</string>
+    <string name="pref_title_debug">袟邪锌褉芯褋 芯褌谢邪写泻懈</string>
+    <string name="huawei_trusleep_summary_light">校谢褍褔褕械薪薪芯械 芯褌褋谢械卸懈胁邪薪懈械 褋薪邪</string>
+    <string name="pref_activity_recognition_mode_none">薪械褌</string>
+    <string name="prefs_activity_recognition">袧邪褋褌褉芯泄泻懈 褉邪褋锌芯蟹薪邪薪懈褟 邪泻褌懈胁薪芯褋褌懈</string>
+    <string name="pref_activity_recognize_running">褉邪褋锌芯蟹薪邪胁邪褌褜 斜械谐</string>
+    <string name="pref_activity_recognize_rowing">褉邪褋锌芯蟹薪邪胁邪褌褜 谐褉械斜谢褞</string>
+    <string name="pref_enable_call_reject_summary">袙泻谢褞褔懈褌褜 芯褌泻谢芯薪械薪懈械 胁褘蟹芯胁芯胁 褋 褍褋褌褉芯泄褋褌胁邪</string>
+    <string name="pref_disable_find_phone_with_dnd">袟邪锌褉械褌懈褌褜 \"薪邪泄褌懈 屑芯泄 褌械谢械褎芯薪\" 锌褉懈 胁泻谢褞褔械薪薪芯屑 褉械卸懈屑械 \"薪械 斜械褋锌芯泻芯懈褌褜\"</string>
+    <string name="pref_force_options_summary">袧械泻芯褌芯褉褘械 褍褋褌褉芯泄褋褌胁邪 谢芯卸薪芯 锌芯屑械褔邪褞褌 褋械斜褟 泻邪泻 薪械 懈屑械褞褖懈械 锌芯写写械褉卸泻懈 褝褌懈褏 芯锌褑懈泄. 袙 褝褌懈褏 薪邪褋褌褉芯泄泻邪褏 懈褏 屑芯卸薪芯 锌芯锌褘褌邪褌褜褋褟 胁泻谢褞褔懈褌褜.
+\n袠小袩袨袥鞋袟校袡孝袝 袧袗 小袙袨袡 袪袠小袣
+\n袟邪 锌芯屑芯褖褜褞 芯斜褉邪褖邪泄褌械褋褜 泻 袙懈泻懈</string>
+    <string name="pref_force_dnd_support_summary">肖芯褉褋懈褉芯胁邪褌褜 锌芯写写械褉卸泻褍 褉械卸懈屑邪 \"薪械 斜械褋锌芯泻芯懈褌褜\".
+\n袠小袩袨袥鞋袟校袡孝袝 袧袗 小袙袨袡 袪袠小袣</string>
+    <string name="huawei_ignore_wakeup_status_end_description">袦芯卸械褌 锌芯屑芯褔褜 懈褋锌褉邪胁懈褌褜 褉邪褋锌芯蟹薪邪薪懈械 褋薪邪. 小褉邪蟹褍 胁懈写懈屑芯 胁 芯斜蟹芯褉械 写薪械胁薪褘褏 邪泻褌懈胁薪芯褋褌械泄.</string>
+    <string name="huawei_reparse_workout_data_description">协褌邪 芯锌褑懈褟 锌芯胁谢懈褟械褌 薪邪 褔褌芯-褌芯 锌芯褋谢械 薪械泻芯褌芯褉褘褏 芯斜薪芯胁谢械薪懈泄</string>
+    <string name="pref_summary_general_reconnectonlytoconnected">袩械褉械锌芯写泻谢褞褔邪褌褜褋褟 褌芯谢褜泻芯 泻 锌芯写泻谢褞褔械薪薪褘屑 褍褋褌褉芯泄褋褌胁邪屑, 邪 薪械 泻芯 胁褋械屑 锌芯写褉褟写</string>
+    <string name="updatefirmwareoperation_updateproblem_low_battery">袟邪褉褟写 斜邪褌邪褉械懈 褋谢懈褕泻芯屑 薪懈蟹芯泻</string>
+    <string name="menuitem_buzzer_intensity">袦芯褖薪芯褋褌褜 蟹胁褍泻芯懈蟹谢褍褔邪褌械谢褟</string>
+    <string name="prefs_password_4_digits_0_to_9_summary">袩邪褉芯谢褜 写芯谢卸械薪 褋芯褋褌芯褟褌褜 懈蟹 4 褑懈褎褉</string>
+    <string name="devicetype_xiaomi_watch_s1_pro">Xiaomi Watch S1 Pro</string>
+    <string name="uploadwatchfaceoperation_complete">校褋褌邪薪芯胁泻邪 褑懈褎械褉斜谢邪褌邪 蟹邪胁械褉褕械薪邪</string>
+    <string name="uploadwatchfaceoperation_failed">校褋褌邪薪芯胁泻邪 褑懈褎械褉斜谢邪褌邪 薪械 褍写邪谢邪褋褜</string>
+    <string name="pref_force_connection_type_title">袩褉懈薪褍写懈褌械谢褜薪邪褟 褍褋褌邪薪芯胁泻邪 褌懈锌邪 褋芯械写懈薪械薪懈褟</string>
+    <string name="pref_force_connection_type_auto">袗胁褌芯屑邪褌懈褔械褋泻懈泄</string>
+    <string name="pref_force_connection_type_ble">Bluetooth LE (薪懈蟹泻芯械 褝薪械褉谐芯锌芯褌褉械斜谢械薪懈械)</string>
+    <string name="pref_force_connection_type_bt_classic">Bluetooth Classic</string>
+    <string name="activity_info">袠薪褎芯褉屑邪褑懈褟 芯斜 邪泻褌懈胁薪芯褋褌懈</string>
+    <string name="sony_protocol_v1">袙械褉褋懈褟 1</string>
+    <string name="sony_protocol_v3">袙械褉褋懈褟 3</string>
+    <string name="protocol_version">袙械褉褋懈褟 锌褉芯褌芯泻芯谢邪</string>
+    <string name="sony_protocol_v2">袙械褉褋懈褟 2</string>
+    <string name="flatDistance">袩谢芯褋泻芯械 褉邪褋褋褌芯褟薪懈械</string>
+    <string name="devicetype_xiaomi_watch_s1">Xiaomi Watch S1</string>
+    <string name="devicetype_xiaomi_watch_s3">Xiaomi Watch S3</string>
+    <string name="uploading_watchface">袟邪谐褉褍蟹泻邪 褑懈褎械褉斜谢邪褌邪鈥�</string>
+    <string name="uploadwatchfaceoperation_in_progress">袟邪谐褉褍蟹泻邪 褑懈褎械褉斜谢邪褌邪</string>
+    <string name="pref_force_connection_type_description">袙芯蟹屑芯卸薪芯, 胁邪屑 褍写邪褌褜褋褟 蟹邪褋褌邪胁懈褌褜 褍褋褌褉芯泄褋褌胁芯 褉邪斜芯褌邪褌褜 褋 Gadgetbridge, 锌褉懈薪褍写懈褌械谢褜薪芯 胁褘褋褌邪胁懈胁 薪褍卸薪褘泄 褌懈锌 褋芯械写懈薪械薪懈褟</string>
+    <string name="devicetype_miband8pro">Xiaomi Smart Band 8 Pro</string>
+    <string name="devicetype_mijia_mho_c303">Mijia MHO-C303</string>
+    <string name="devicetype_sony_wi_sp600n">Sony WI-SP600N</string>
+    <string name="heartrate_bpm_155">155 褍写/屑</string>
+    <string name="heartrate_bpm_165">165 褍写/屑</string>
+    <string name="prefs_heartrate_alert_active_high_threshold">袩芯褉芯谐 锌褉械写褍锌褉械卸写械薪懈褟 芯 锌芯胁褘褕械薪薪芯屑 褋械褉写褑械斜懈械薪懈懈</string>
+    <string name="activity_type_fitness_exercises">肖懈褌薪械褋-褍锌褉邪卸薪械薪懈褟</string>
+    <string name="activity_type_physical_training">肖懈蟹懈褔械褋泻邪褟 锌芯写谐芯褌芯胁泻邪</string>
+    <string name="activity_type_taekwondo">孝褏褝泻胁芯薪写芯</string>
+    <string name="activity_type_fencing">肖械褏褌芯胁邪薪懈械</string>
+    <string name="activity_type_kendo">袣褝薪写芯</string>
+    <string name="activity_type_sit_ups">袩芯写褗褢屑 褌褍谢芯胁懈褖邪</string>
+    <string name="activity_type_pull_ups">袩芯写褌褟谐懈胁邪薪懈褟</string>
+    <string name="activity_type_plank">袩谢邪薪泻邪</string>
+    <string name="activity_type_javelin">袣芯锌褜械</string>
+    <string name="activity_type_long_jump">袩褉褘卸泻懈 胁 写谢懈薪褍</string>
+    <string name="activity_type_trampoline">孝褉邪屑锌谢懈薪</string>
+    <string name="activity_type_ballet">袘邪谢械褌</string>
+    <string name="activity_type_martial_arts">袘芯械胁褘械 懈褋泻褍褋褋褌胁邪</string>
+    <string name="activity_type_tai_chi">孝邪泄-褔懈</string>
+    <string name="heartrate_bpm_175">175 褍写/屑</string>
+    <string name="heartrate_bpm_185">185 褍写/屑</string>
+    <string name="heartrate_bpm_195">195 褍写/屑</string>
+    <string name="heartrate_bpm_205">205 褍写/屑</string>
+    <string name="hydration_dnd_summary">袙褘泻谢褞褔懈褌褜 薪邪锌芯屑懈薪邪薪懈褟 芯斜 褍锌芯褌褉械斜谢械薪懈懈 胁芯写褘 薪邪 胁褉械屑械薪薪芯泄 锌褉芯屑械卸褍褌芯泻</string>
+    <string name="activity_type_indoor_running">袘械谐 胁 锌芯屑械褖械薪懈懈</string>
+    <string name="activity_type_karate">袣邪褉邪褌械</string>
+    <string name="activity_type_push_ups">袨褌卸懈屑邪薪懈褟</string>
+    <string name="activity_type_high_jump">袩褉褘卸泻懈 胁 胁褘褋芯褌褍</string>
+    <string name="activity_type_hula_hooping">袨斜褉褍褔</string>
+    <string name="devicetype_nothing_cmf_watch_pro">CMF Watch Pro</string>
+    <string name="activity_type_hunting">袨褏芯褌邪</string>
+    <string name="activity_type_fishing">袪褘斜邪谢泻邪</string>
+    <string name="devicetype_redmi_watch_2">Redmi Watch 2</string>
+    <string name="activity_type_karting">袣邪褉褌懈薪谐</string>
+    <string name="activity_type_billiards">袘懈谢褜褟褉写</string>
+    <string name="activity_type_dodgeball">袙褘褕懈斜邪谢褘</string>
+    <string name="activity_type_softball">小芯褎褌斜芯谢</string>
+    <string name="activity_type_pickleball">袩懈泻谢斜芯谢</string>
+    <string name="warning_missing_notification_permission">袧械胁芯蟹屑芯卸薪芯 芯褌锌褉邪胁懈褌褜 褌械泻褍褖械械 褍胁械写芯屑谢械薪懈械 懈蟹-蟹邪 芯褌褋褍褌褋褌胁懈褟 褉邪蟹褉械褕械薪懈褟</string>
+    <string name="devicetype_huawei_watchfit">Huawei Watch Fit</string>
+    <string name="pref_test_features_title">袙芯蟹屑芯卸薪芯褋褌懈</string>
+    <string name="pref_test_features_summary">袙芯蟹屑芯卸薪芯褋褌懈, 胁泻谢褞褔械薪薪褘械 写谢褟 褝褌芯谐芯 褌械褋褌芯胁芯谐芯 褍褋褌褉芯泄褋褌胁邪</string>
+    <string name="devicetype_redmiwatch3">Redmi Watch 3</string>
+    <string name="alarm_smart_wakeup_interval_default">5 屑懈薪褍褌</string>
+    <string name="alarm_smart_wakeup_interval">袠薪褌械褉胁邪谢 褍屑薪芯谐芯 锌褉芯斜褍卸写械薪懈褟:</string>
+    <string name="auto_reconnect_ble_scan_summary">袨卸懈写邪褌褜 邪胁褌芯锌芯懈褋泻 褍褋褌褉芯泄褋褌胁 胁屑械褋褌芯 胁褘锌芯谢薪械薪懈褟 褋谢械锌褘褏 锌芯锌褘褌芯泻 锌械褉械锌芯写泻谢褞褔械薪懈褟</string>
+    <string name="prompt_restart_gadgetbridge">袩械褉械蟹邪锌褍褋褌懈褌械 锌褉懈谢芯卸械薪懈械 写谢褟 锌褉懈屑械薪械薪懈褟 懈蟹屑械薪械薪懈泄.</string>
+    <string name="waiting_for_bluetooth">袨卸懈写邪薪懈械 Bluetooth鈥�</string>
+    <string name="device_state_waiting_scan">袨卸懈写邪薪懈械 锌芯懈褋泻邪 褍褋褌褉芯泄褋褌胁</string>
+    <string name="auto_reconnect_ble_scan_title">袩械褉械锌芯写泻谢褞褔械薪懈械 锌褉懈 褋泻邪薪懈褉芯胁邪薪懈懈 BLE</string>
+    <string name="error_scan_failed">袩芯懈褋泻 薪械 褍写邪谢褋褟: %d</string>
+    <string name="devicetype_miband8active">Xiaomi Smart Band 8 Active</string>
+    <string name="temperature">孝械屑锌械褉邪褌褍褉邪</string>
+    <string name="pref_header_sony_sound_control">校锌褉邪胁谢械薪懈械 蟹胁褍泻芯屑</string>
+    <string name="pref_adaptive_volume_control_summary">袗胁褌芯屑邪褌懈褔械褋泻懈 锌芯胁褘褕邪褌褜 谐褉芯屑泻芯褋褌褜 胁 褕褍屑薪褘褏 屑械褋褌邪褏</string>
+    <string name="pref_adaptive_volume_control_title">袗写邪锌褌懈胁薪邪褟 薪邪褋褌褉芯泄泻邪 谐褉芯屑泻芯褋褌懈</string>
+    <string name="devicetype_amazfit_bip3">Amazfit Bip 3</string>
+    <string name="devicetype_sony_linkbuds">Sony LinkBuds</string>
+    <string name="pref_wide_area_tap_summary">袪邪褋锌芯蟹薪邪胁邪褌褜 泻邪褋邪薪懈褟 屑械卸写褍 褖械泻芯泄 懈 褍褏芯屑</string>
+    <string name="pref_wide_area_tap_title">楔懈褉芯泻芯锌芯谢芯褋薪褘泄 泻褉邪薪</string>
+    <string name="fw_upgrade_notice_amazfitbip3">袙褘 褋芯斜懈褉邪械褌械褋褜 褍褋褌邪薪芯胁懈褌褜 锌褉芯褕懈胁泻褍 %s 薪邪 胁邪褕 Amazfit Bip 3.
+\n
+\n袘褍写褜褌械 胁薪懈屑邪褌械谢褜薪褘: 褋薪邪褔邪谢邪 褍褋褌邪薪芯胁懈褌械 褎邪泄谢 .fw, 蟹邪褌械屑 .res. 袩芯褋谢械 褍褋褌邪薪芯胁泻懈 .fw 胁邪褕懈 褔邪褋褘 斜褍写褍褌 锌械褉械蟹邪谐褉褍卸械薪褘.
+\n
+\n袩褉懈屑械褔邪薪懈械: 械褋谢懈 褎邪泄谢 懈 .res 褌邪泻芯泄 卸械, 泻邪泻 胁 褌械泻褍褖械泄 胁械褉褋懈懈, 械谐芯 薪械 薪褍卸薪芯 锌械褉械褍褋褌邪薪邪胁谢懈胁邪褌褜.
+\n
+\n袙蝎 袛袝袡小孝袙校袝孝袝 袧袗 小袙袨袡 小孝袪袗啸 袠 袪袠小袣!</string>
+    <string name="weekstepschart_steps_a_week_or_month">楔邪谐芯胁 蟹邪 薪械写械谢褞/屑械褋褟褑</string>
+    <string name="weeksleepchart_sleep_a_week_or_month">小芯薪 蟹邪 薪械写械谢褞/屑械褋褟褑</string>
+    <string name="devicetype_huawei_watchgt4">Huawei Watch GT 4</string>
+    <string name="devicetype_huawei_watchultimate">Huawei Watch Ultimate</string>
+    <string name="busy_task_fetch_sports_details_interrupted">袩芯谢褍褔械薪懈械 锌芯写褉芯斜薪芯褋褌械泄 芯 褋锌芯褉褌械 斜褘谢芯 锌褉械褉胁邪薪芯</string>
+    <string name="pref_force_enable_heartrate_support">袩芯写写械褉卸泻邪 懈蟹屑械褉械薪懈褟 锌褍谢褜褋邪</string>
+    <string name="pref_force_enable_spo2_support_summary">肖芯褉褋懈褉芯胁邪褌褜 锌芯写写械褉卸泻褍 懈蟹屑械褉械薪懈褟 褍褉芯胁薪褟 泻懈褋谢芯褉芯写邪 胁 泻褉芯胁懈.
+\n袠小袩袨袥鞋袟校袡孝袝 袧袗 小袙袨袡 袪袠小袣</string>
+    <string name="pref_force_enable_heartrate_support_summary">肖芯褉褋懈褉芯胁邪褌褜 锌芯写写械褉卸泻褍 懈蟹屑械褉械薪懈褟 锌褍谢褜褋邪.
+\n袠小袩袨袥鞋袟校袡孝袝 袧袗 小袙袨袡 袪袠小袣</string>
+    <string name="pref_force_enable_spo2_support">袩芯写写械褉卸泻邪 蟹邪屑械褉芯胁 SpO2</string>
+    <string name="pref_dashboard_widget_double_size_summary">袪邪蟹褉械褕懈褌械 胁懈写卸械褌褍 蟹邪薪懈屑邪褌褜 写胁械 泻芯谢芯薪泻懈 薪邪 锌褉懈斜芯褉薪芯泄 锌邪薪械谢懈</string>
+    <string name="pref_dashboard_widget_show_legend_summary">袩芯泻邪蟹褘胁邪褌褜 蟹薪邪褔械薪懈褟 褑胁械褌芯胁 锌芯写 胁懈写卸械褌芯屑</string>
+    <string name="pref_dashboard_select_devices_title">袙褘斜械褉懈褌械 褍褋褌褉芯泄褋褌胁邪...</string>
+    <string name="pref_dashboard_widgets_order_summary">袙褘斜械褉懈褌械, 泻邪泻懈械 胁懈写卸械褌褘 斜褍写褍褌 胁泻谢褞褔械薪褘 懈 胁 泻邪泻芯屑 锌芯褉褟写泻械 芯薪懈 斜褍写褍褌 芯褌芯斜褉邪卸邪褌褜褋褟 薪邪 锌褉懈斜芯褉薪芯泄 锌邪薪械谢懈</string>
+    <string name="pref_dashboard_widget_today_24h_summary">袩芯泻邪蟹褘胁邪褌褜 邪泻褌懈胁薪芯褋褌褜 胁 芯写薪芯屑 24-褔邪褋芯胁芯屑 泻褉褍卸泻械 胁屑械褋褌芯 写胁芯泄薪芯谐芯 12褔</string>
+    <string name="pref_auto_reply_calls_title">袩褉懈薪懈屑邪褌褜 蟹胁芯薪泻懈 邪胁褌芯屑邪褌懈褔械褋泻懈</string>
+    <string name="pref_auto_reply_calls_delay_title">袟邪写械褉卸泻邪 锌械褉械写 邪胁褌芯锌褉懈褢屑芯屑</string>
+    <string name="pref_auto_reply_calls_summary">孝械谢械褎芯薪 斜褍写械褌 邪胁褌芯屑邪褌懈褔械褋泻懈 锌褉懈薪懈屑邪褌褜 胁褏芯写褟褖懈械 蟹胁芯薪泻懈</string>
+    <string name="pref_auto_reply_calls_delay_summary">袙褉械屑褟 胁 褋械泻褍薪写邪褏 写芯 褌芯谐芯, 泻邪泻 蟹胁芯薪芯泻 斜褍写械褌 邪胁褌芯屑邪褌懈褔械褋泻懈 锌褉懈薪褟褌</string>
+    <string name="pref_header_generic">袨斜褖懈泄</string>
+    <string name="pref_header_audio">袗褍写懈芯</string>
+    <string name="activity_type_free_training">小胁芯斜芯写薪邪褟 褌褉械薪懈褉芯胁泻邪</string>
+    <string name="activity_type_dynamic_cycle">袛懈薪邪屑懈褔械褋泻懈泄 褑懈泻谢</string>
+    <string name="pref_dashboard_widget_today_hr_interval_title">袠薪褌械褉胁邪谢 蟹邪屑械褉芯胁 锌褍谢褜褋邪</string>
+    <string name="pref_speak_notifications_aloud_title">袩褉芯懈蟹薪芯褋懈褌褜 褍胁械写芯屑谢械薪懈褟 胁褋谢褍褏</string>
+    <string name="pref_header_calls_and_notifications">袙褘蟹芯胁褘 懈 褍胁械写芯屑谢械薪懈褟</string>
+    <string name="pref_dashboard_widget_show_legend_title">袩芯泻邪蟹褘胁邪褌褜 芯斜芯蟹薪邪褔械薪懈褟</string>
+    <string name="armenian">袗褉屑褟薪褋泻懈泄</string>
+    <string name="devicetype_redmi_watch_4">Redmi Watch 4</string>
+    <string name="pref_title_notification_wake_on_open">袗胁褌芯锌褉芯斜褍卸写械薪懈械 懈 褉邪蟹斜谢芯泻懈褉芯胁泻邪</string>
+    <string name="pref_summary_notification_wake_on_open">袩褉芯斜褍卸写邪褌褜 懈 褉邪蟹斜谢芯泻懈褉芯胁邪褌褜 Android-褍褋褌褉芯泄褋褌胁芯 泻芯谐写邪 薪芯褋懈屑芯械 褍褋褌褉芯泄褋褌胁芯 芯褌胁械褔邪械褌 褋 OPEN. 袧褍卸薪芯 斜褘褌褜 胁 写芯胁械褉械薪薪芯屑 褋芯褋褌芯褟薪懈懈.</string>
+    <string name="pref_sleepasandroid_features_title">肖褍薪泻褑懈懈</string>
+    <string name="pref_sleepasandroid_features_summary">袩芯写写械褉卸泻邪 胁邪褉褜懈褉褍械褌褋褟 胁 蟹邪胁懈褋懈屑芯褋褌懈 芯褌 褍褋褌褉芯泄褋褌胁邪</string>
+    <string name="pref_sleepasandroid_feat_movement">袗泻褋械谢械褉芯屑械褌褉</string>
+    <string name="pref_sleepasandroid_feat_heartrate">袩褍谢褜褋</string>
+    <string name="pref_sleepasandroid_feat_spo2">SpO2</string>
+    <string name="pref_title_bottom_navigation_bar">袧懈卸薪褟褟 锌邪薪械谢褜 薪邪胁懈谐邪褑懈懈</string>
+    <string name="pref_summary_bottom_navigation_bar_on">袩械褉械泻谢褞褔邪褌褜褋褟 屑械卸写褍 芯褋薪芯胁薪褘屑懈 褝泻褉邪薪邪屑懈 褔械褉械蟹 锌邪薪械谢褜 薪邪胁懈谐邪褑懈懈 懈谢懈 卸械褋褌邪屑懈</string>
+    <string name="pref_sleepasandroid_feat_notifications">校胁械写芯屑谢械薪懈褟</string>
+    <string name="pref_summary_bottom_navigation_bar_off">袩械褉械泻谢褞褔邪褌褜褋褟 屑械卸写褍 芯褋薪芯胁薪褘屑懈 褝泻褉邪薪邪屑懈 褌芯谢褜泻芯 锌芯 卸械褋褌邪屑</string>
+    <string name="serbian">小械褉斜褋泻懈泄</string>
+    <string name="pref_title_huawei_account">校褔褢褌薪邪褟 蟹邪锌懈褋褜 Huawei</string>
+    <string name="pref_summary_huawei_account">袠褋锌芯谢褜蟹褍械褌褋褟 写谢褟 褋芯锌褉褟卸械薪懈褟 斜械蟹 褋斜褉芯褋邪 褍褋褌褉芯泄褋褌胁邪.</string>
+    <string name="bottom_nav_devices">校褋褌褉芯泄褋褌胁邪</string>
+    <string name="pref_dashboard_all_devices_title">袙褋械 褍褋褌褉芯泄褋褌胁邪</string>
+    <string name="pref_dashboard_widget_double_size_title">袛胁芯泄薪芯泄 褉邪蟹屑械褉</string>
+    <string name="watchface_resolution_doesnt_match">袪邪蟹屑械褉 褑懈褎械褉斜谢邪褌邪 薪械 褋芯胁锌邪写邪械褌 褋 褉邪蟹屑械褉芯屑 褝泻褉邪薪邪. 袪邪蟹褉械褕械薪懈械 褑懈褎械褉斜谢邪褌邪: %1$s, 褉邪蟹褉械褕械薪懈械 褝泻褉邪薪邪: %2$s</string>
+    <string name="companion_pairing_request_description">袩褉懈胁褟蟹邪褌褜 褍褋褌褉芯泄褋褌胁芯 泻邪泻 褋芯锌褍褌褋褌胁褍褞褖械械?
+\n
+\n协褌芯 褉械泻芯屑械薪写褍械褌褋褟 写谢褟 褉邪斜芯褌褘 褎褍薪泻褑懈懈 锌芯懈褋泻邪 褍褋褌褉芯泄褋褌胁邪 懈 褍谢褍褔褕械薪懈褟 泻邪褔械褋褌胁邪 褋芯械写懈薪械薪懈褟.</string>
+    <string name="devicetype_huawei_watchfit2">Huawei Watch Fit 2</string>
+    <string name="devicesetting_scannable_minimum_unseen">袦懈薪. 胁褉械屑褟 芯褌褋褍褌褋褌胁懈褟 (胁 褋械泻.)</string>
+    <string name="unbind_before_pair_title">校卸械 锌褉懈胁褟蟹邪薪芯</string>
+    <string name="pref_dashboard_widget_today_title">袚褉邪褎懈泻 邪泻褌懈胁薪芯褋褌懈</string>
+    <string name="devicesetting_scannable_rssi">袦懈薪. 锌芯褉芯谐 RSSI</string>
+    <string name="unbind_before_pair_message">协褌芯 褍褋褌褉芯泄褋褌胁芯 褍卸械 锌褉懈胁褟蟹邪薪芯 褔械褉械蟹 薪邪褋褌褉芯泄泻懈 Android. 袠蟹-蟹邪 褝褌芯谐芯 薪械泻芯褌芯褉褘械 褍褋褌褉芯泄褋褌胁邪 薪械 锌芯谢褍褔懈褌褋褟 锌褉懈胁褟蟹邪褌褜 胁 GB.
+\n
+\n袝褋谢懈 写芯斜邪胁谢械薪懈械 薪械 褍写邪褢褌褋褟, 芯褌胁褟卸懈褌械 褍褋褌褉芯泄褋褌胁芯 胁 薪邪褋褌褉芯泄泻邪褏 Android 懈 锌芯锌褉芯斜褍泄褌械 械褖褢 褉邪蟹.</string>
+    <string name="companion_pairing_request_title">袩褉懈斜芯褉-泻芯屑锌邪薪褜芯薪</string>
+    <string name="dashboard_settings">袧邪褋褌褉芯泄泻懈 锌褉懈斜芯褉薪芯泄 锌邪薪械谢懈</string>
+    <string name="error_showing_changelog">袧械 褍写邪谢芯褋褜 芯褌泻褉褘褌褜 卸褍褉薪邪谢 懈蟹屑械薪械薪懈泄</string>
+    <string name="activity_type_worn">袧芯褕械薪懈械</string>
+    <string name="pref_dashboard_widget_today_24h_title">24褔 褋褌懈谢褜</string>
+    <string name="bottom_nav_dashboard">袩褉懈斜芯褉薪邪褟 锌邪薪械谢褜</string>
+    <string name="notification_channel_scan_service_name">小谢褍卸斜邪 褋泻邪薪懈褉芯胁邪薪懈褟</string>
+    <string name="huawei_trusleep_warning">袩褉械写褍锌褉械卸写械薪懈械: 胁泻谢褞褔械薪懈械 褝褌芯泄 褎褍薪泻褑懈懈 锌褉懈胁械写褢褌 泻 芯褌芯斜褉邪卸械薪懈褞 胁褋械谐芯 褋薪邪 胁 Gatgetbride 泻邪泻 斜褘褋褌褉芯谐芯! 袧邪卸屑懈褌械, 械褋谢懈 褋芯谐谢邪褋薪褘.</string>
+    <string name="activity_type_mountain_hike">袚芯褉薪邪褟 锌褉芯谐褍谢泻邪</string>
+    <string name="pref_touch_tone_summary">袩褉芯懈谐褉褘胁邪褌褜 褋懈谐薪邪谢 锌褉懈 泻邪褋邪薪懈懈 泻 薪邪褍褕薪懈泻褍</string>
+    <string name="pref_wearing_tone_summary">袩褉芯懈谐褉褘胁邪褌褜 褋懈谐薪邪谢, 泻芯谐写邪 胁褋褌邪胁谢械薪 薪邪褍褕薪懈泻</string>
+    <string name="pref_touch_tone">小懈谐薪邪谢 泻邪褋邪薪懈褟</string>
+    <string name="pref_wearing_tone">小懈谐薪邪谢 薪芯褕械薪懈褟</string>
+    <string name="devicetype_soundcore_liberty3_pro">Soundcore Liberty 3 Pro</string>
+    <string name="devicetype_huawei_band9">Huawei Band 9</string>
+    <string name="devicetype_huawei_watchfit3">Huawei Watch Fit 3</string>
+    <string name="devicetype_huawei_watch4pro">Huawei Watch 4 (Pro)</string>
+    <string name="preferences_qhybrid_settings_summary">小褌邪褉褘械 薪邪褋褌褉芯泄泻懈 写谢褟 褔邪褋芯胁 Q Hybrid</string>
+    <string name="pref_adaptive_noise_cancelling_title">袗写邪锌褌懈胁薪芯械 锌芯写邪胁谢械薪懈械 褕褍屑邪</string>
+    <string name="pref_adaptive_noise_cancelling_summary">袟邪写邪胁邪褌褜 褍褉芯胁械薪褜 邪泻褌懈胁薪芯谐芯 锌芯写邪胁谢械薪懈褟 褕褍屑邪 胁 蟹邪胁懈褋懈屑芯褋褌懈 芯褌 谐褉芯屑泻芯褋褌懈 芯泻褉褍卸械薪懈褟</string>
+    <string name="pref_title_wheel_diameter">袛懈邪屑械褌褉 泻芯谢械褋邪</string>
+    <string name="pref_summary_wheel_diameter">袛懈邪屑械褌褉 泻芯谢械褋邪 胁 写褞泄屑邪褏. 袨斜褘褔薪芯 29, 27,5 懈谢懈 26.</string>
+    <string name="chart_cycling_point_label_speed">%.1f 泻屑/褔</string>
+    <string name="chart_cycling_point_label_distance">小械谐芯写薪褟: %.1f 泻屑
+\n袙褋械谐芯: %.1f 泻屑</string>
+    <string name="devicetype_amazfit_bip5_unity">Amazfit Bip 5 Unity</string>
+    <string name="pref_speak_notifications_focus_exclusive_summary_on">袙芯褋锌褉芯懈蟹胁械写械薪懈械 胁 写褉褍谐懈褏 锌褉懈谢芯卸械薪懈褟褏 斜褍写械褌 锌褉懈芯褋褌邪薪芯胁谢械薪芯 锌褉懈 锌褉芯懈蟹薪械褋械薪懈懈 褍胁械写芯屑谢械薪懈褟</string>
+    <string name="pref_speak_notifications_focus_exclusive_title">袩褉懈芯褋褌邪薪邪胁谢懈胁邪褌褜 胁芯褋锌褉芯懈蟹胁械写械薪懈械</string>
+    <string name="pref_speak_notifications_focus_exclusive_summary_off">袚褉芯屑泻芯褋褌褜 胁芯褋锌褉芯懈蟹胁械写械薪懈褟 胁 写褉褍谐懈褏 锌褉懈谢芯卸械薪懈褟褏 斜褍写械褌 褋薪懈卸械薪邪 锌褉懈 锌褉芯懈蟹薪械褋械薪懈懈 褍胁械写芯屑谢械薪懈褟</string>
+    <string name="toast_setting_requires_reconnect">协褌芯褌 锌邪褉邪屑械褌褉 胁褋褌褍锌懈褌 胁 写械泄褋褌胁懈械 锌褉懈 褋谢械写褍褞褖械屑 褋芯械写懈薪械薪懈懈</string>
+    <string name="toast_camera_permission_required">袛谢褟 褝褌芯泄 胁芯蟹屑芯卸薪芯褋褌懈 褌褉械斜褍械褌褋褟 写芯褋褌褍锌 泻 泻邪屑械褉械.</string>
+    <string name="open_camera">袨褌泻褉褘褌褜 泻邪屑械褉褍</string>
+    <string name="toast_camera_support_required">袛谢褟 褝褌芯泄 胁芯蟹屑芯卸薪芯褋褌懈 褌褉械斜褍械褌褋褟 锌芯写写械褉卸泻邪 泻邪屑械褉褘.</string>
+    <string name="toast_camera_photo_taken">小薪懈屑芯泻 斜褘谢 褋写械谢邪薪 懈 褋芯褏褉邪薪褢薪 胁 %s</string>
+    <string name="activity_type_cross_trainer">袣褉芯褋褋-褌褉械薪邪卸械褉</string>
+    <string name="activity_type_stair_stepper">袥械褋褌薪懈褔薪褘泄 褕邪谐芯屑械褉</string>
+    <string name="activity_type_crossfit">袣褉芯褋褋褎懈褌</string>
+    <string name="activity_type_darts">袛褉芯褌懈泻懈</string>
+    <string name="activity_type_hand_cycling">袪褍褔薪芯泄 胁械谢芯褋懈锌械写</string>
+    <string name="activity_type_kite_flying">袟邪锌褍褋泻 胁芯蟹写褍褕薪芯谐芯 蟹屑械褟</string>
+    <string name="activity_type_swing">袣邪褔械谢懈</string>
+    <string name="activity_type_stairs">袥械褋褌薪懈褑邪</string>
+    <string name="activity_type_australian_football">袗胁褋褌褉邪谢懈泄褋泻懈泄 褎褍褌斜芯谢</string>
+    <string name="activity_type_lacross">袥褟泻褉芯褋褋</string>
+    <string name="activity_type_outdoor_walking">袩褉芯谐褍谢泻懈 薪邪 褋胁械卸械屑 胁芯蟹写褍褏械</string>
+    <string name="activity_type_skating">袣邪褌邪薪懈械</string>
+    <string name="activity_type_rock_climbing">小泻邪谢芯谢邪蟹邪薪懈械</string>
+    <string name="activity_type_rower">袚褉械斜谢褟</string>
+    <string name="activity_type_cross_country_running">袘械谐 锌芯 锌械褉械褋械褔械薪薪芯泄 屑械褋褌薪芯褋褌懈</string>
+    <string name="activity_type_functional_training">肖褍薪泻褑懈芯薪邪谢褜薪邪褟 褌褉械薪懈褉芯胁泻邪</string>
+    <string name="activity_type_jazz_dance">袛卸邪蟹芯胁褘泄 褌邪薪械褑</string>
+    <string name="activity_type_latin_dance">袥邪褌懈薪褋泻懈泄 褌邪薪械褑</string>
+    <string name="activity_type_other_dance">袛褉褍谐芯泄 褌邪薪械褑</string>
+    <string name="activity_type_roller_skating">袪芯谢懈泻芯胁褘械 泻芯薪褜泻懈</string>
+    <string name="activity_type_disc_sports">袛懈褋泻芯胁褘泄 褋锌芯褉褌</string>
+    <string name="activity_type_horse_riding">袙械褉褏芯胁邪褟 械蟹写邪</string>
+    <string name="activity_type_horizontal_bar">袚芯褉懈蟹芯薪褌邪谢褜薪邪褟 锌谢邪薪泻邪</string>
+    <string name="activity_type_parallel_bar">袩邪褉邪谢谢械谢褜薪邪褟 锌谢邪薪泻邪</string>
+    <string name="activity_type_belly_dance">孝邪薪械褑 卸懈胁芯褌邪</string>
+    <string name="activity_type_shot">袙褘褋褌褉械谢</string>
+    <string name="pref_agps_status">小芯褋褌芯褟薪懈械 AGPS</string>
+    <string name="agps_status_missing">袩褉芯锌邪卸邪</string>
+    <string name="agps_status_pending">袨卸懈写邪薪懈械</string>
+    <string name="agps_status_current">孝械泻褍褖懈泄</string>
+    <string name="agps_status_error">袨褕懈斜泻邪</string>
+    <string name="activity_type_cooldown">袨褋褌褘胁邪薪懈械</string>
+    <string name="activity_type_cross_training">袣褉芯褋褋-褌褉械薪懈薪谐</string>
+    <string name="activity_type_fitness_gaming">袠谐褉芯胁芯泄 褎懈褌薪械褋</string>
+    <string name="activity_type_aerobic_exercise">袗褝褉芯斜薪褘械 褍锌褉邪卸薪械薪懈褟</string>
+    <string name="activity_type_rolling">小泻邪褌褘胁邪薪懈械</string>
+    <string name="activity_type_flexibility">袚懈斜泻芯褋褌褜</string>
+    <string name="activity_type_battle_rope">袘芯械胁芯泄 泻邪薪邪褌</string>
+    <string name="activity_type_smith_machine">袦邪褕懈薪邪 小屑懈褌邪</string>
+    <string name="activity_type_dumbbell">袚邪薪褌械谢褜</string>
+    <string name="activity_type_archery">小褌褉械谢褜斜邪 懈蟹 谢褍泻邪</string>
+    <string name="activity_type_kabaddi">袣邪斜邪写写懈</string>
+    <string name="activity_type_shuttlecock">效械谢薪芯泻</string>
+    <string name="activity_type_sailing">袩邪褉褍褋薪懈泻</string>
+    <string name="activity_type_jet_skiing">袣邪褌邪薪懈械 薪邪 谐懈写褉芯褑懈泻谢邪褏</string>
+    <string name="activity_type_ice_hockey">啸芯泻泻械泄 薪邪 谢褜写褍</string>
+    <string name="activity_type_curling">袣械褉谢懈薪谐</string>
+    <string name="activity_type_snow_sports">小薪械卸薪褘泄 褋锌芯褉褌</string>
+    <string name="activity_type_skateboarding">小泻械泄褌斜芯褉写懈薪谐</string>
+    <string name="devicetype_garmin_vivomove_hr">Garmin V铆vomove HR</string>
+    <string name="devicetype_garmin_vivomove_style">Garmin V铆vomove Style</string>
+    <string name="devicetype_garmin_venu_3">Garmin Venu 3</string>
+    <string name="devicetype_garmin_instinct_solar">Garmin Instinct Solar</string>
+    <string name="devicetype_garmin_instinct_2_soltac">Garmin Instinct 2 SolTac</string>
+    <string name="devicetype_garmin_instinct_2s">Garmin Instinct 2S</string>
+    <string name="devicetype_garmin_instinct_2_solar">Garmin Instinct 2 Solar</string>
+    <string name="devicetype_garmin_forerunner_245">Garmin Forerunner 245</string>
+    <string name="devicetype_garmin_swim_2">Garmin Swim 2</string>
+    <string name="activity_type_track_and_field">袥械谐泻邪褟 邪褌谢械褌懈泻邪</string>
+    <string name="activity_type_mind_and_body">袪邪蟹褍屑 懈 褌械谢芯</string>
+    <string name="activity_type_cross_country_skiing">袣邪褌邪薪懈械 薪邪 斜械谐芯胁褘褏 谢褘卸邪褏</string>
+    <string name="devicetype_garmin_instinct_crossover">Garmin Instinct Crossover</string>
+    <string name="devicetype_garmin_vivoactive_4">Garmin V铆voactive 4</string>
+    <string name="swolfAvg">小褉械写薪懈泄 胁芯谢泻</string>
+    <string name="devicetype_garmin_vivoactive_5">Garmin V铆voactive 5</string>
+    <string name="devicetype_garmin_vivoactive_4s">Garmin V铆voactive 4S</string>
+    <string name="devicetype_honor_magicwatch2">Honor MagicWatch 2</string>
+    <string name="avgStrokeRate">小褉械写薪褟褟 褋泻芯褉芯褋褌褜 懈薪褋褍谢褜褌邪</string>
+    <string name="maxStrokeRate">袦邪泻褋懈屑邪谢褜薪邪褟 褋泻芯褉芯褋褌褜 褏芯写邪</string>
+    <string name="strokes">袙褋械谐芯 褍写邪褉芯胁</string>
+    <string name="swolfMax">袦邪泻褋懈屑邪谢褜薪褘泄 胁芯谢泻</string>
+    <string name="swolfMin">袦懈薪懈屑邪谢褜薪褘泄 胁芯谢泻</string>
+    <string name="stepRateSum">小褍屑屑邪 褕邪谐芯胁芯泄 褋泻芯褉芯褋褌懈</string>
+    <string name="stepRateAvg">小褉械写薪褟褟 褋泻芯褉芯褋褌褜 褕邪谐邪</string>
+    <string name="stepLengthAvg">小褉械写薪褟褟 写谢懈薪邪 褕邪谐邪</string>
+    <string name="groundContactTimeAvg">小褉械写薪械械 胁褉械屑褟 泻芯薪褌邪泻褌邪 褋 蟹械屑谢械泄</string>
+    <string name="impactAvg">小褉械写薪械械 胁芯蟹写械泄褋褌胁懈械</string>
+    <string name="impactMax">袦邪泻褋懈屑邪谢褜薪芯械 胁芯蟹写械泄褋褌胁懈械</string>
+    <string name="cyclingPowerMax">袦邪泻褋懈屑邪谢褜薪邪褟 屑芯褖薪芯褋褌褜 褑懈泻谢邪</string>
+    <string name="pref_dashboard_cards_summary">袪懈褋褍泄褌械 泻邪褉褌芯褔泻懈 胁芯泻褉褍谐 胁懈写卸械褌芯胁 薪邪 锌褉懈斜芯褉薪芯泄 锌邪薪械谢懈</string>
+    <string name="pref_sleepasandroid_feat_alarms">袘褍写懈谢褜薪懈泻</string>
+    <string name="pref_sleepasandroid_device_summary">袙褘斜械褉懈褌械 褍褋褌褉芯泄褋褌胁芯 胁 泻邪褔械褋褌胁械 锌芯褋褌邪胁褖懈泻邪 写邪薪薪褘褏 胁 褉械卸懈屑械 褋薪邪 写谢褟 Android</string>
+    <string name="alarm_slot_reset">小谢芯褌 褋懈谐薪邪谢懈蟹邪褑懈懈 褍褋褌邪薪芯胁谢械薪 锌芯 褍屑芯谢褔邪薪懈褞</string>
+    <string name="devicetype_scannable">小泻邪薪懈褉褍械屑芯械 褍褋褌褉芯泄褋褌胁芯</string>
+    <string name="state_scanned">小泻邪薪懈褉芯胁邪薪懈械</string>
+    <string name="devicesetting_scannable_debounce">孝邪泄屑邪褍褌 芯褌泻谢褞褔械薪懈褟 褋泻邪薪懈褉芯胁邪薪懈褟 (褋械泻褍薪写褘)</string>
+    <string name="devicesetting_scannable_debounce_summary">袩芯褋谢械 褋泻邪薪懈褉芯胁邪薪懈褟 褍褋褌褉芯泄褋褌胁芯 斜褍写械褌 褋褔懈褌邪褌褜褋褟 芯褌褋泻邪薪懈褉芯胁邪薪薪褘屑 懈 懈谐薪芯褉懈褉芯胁邪褌褜褋褟 胁 褌械褔械薪懈械 褍泻邪蟹邪薪薪芯谐芯 胁褉械屑械薪懈</string>
+    <string name="devicesetting_scannable_rssi_summary">袦懈薪懈屑邪谢褜薪褘泄 锌芯褉芯谐 RSSI 写谢褟 芯斜薪邪褉褍卸械薪懈褟</string>
+    <string name="widget_layout_top_large_bot_wide">袘芯谢褜褕芯泄 胁胁械褉褏褍, 褕懈褉芯泻懈泄 胁薪懈蟹褍</string>
+    <string name="widget_name_colored_tile">%1$s (褑胁械褌薪邪褟 锌谢懈褌泻邪)</string>
+    <string name="pref_sleepasandroid_feat_oximetry">袨泻褋懈屑械褌褉懈褟</string>
+    <string name="device_name_bicycle_sensor">袙械谢芯褋懈锌械写薪褘泄 写邪褌褔懈泻</string>
+    <string name="device_name_cycling_sensor">袛邪褌褔懈泻 胁械谢芯褋懈锌械写邪</string>
+    <string name="devicetype_cycling_sensor">袛邪褌褔懈泻 褋泻芯褉芯褋褌懈 胁械谢芯褋懈锌械写邪</string>
+    <string name="pref_summary_cycling_persistence_interval">袠薪褌械褉胁邪谢 胁 褋械泻褍薪写邪褏, 泻芯谐写邪 写邪薪薪褘械 芯 褌械泻褍褖械屑 褑懈泻谢械 写芯谢卸薪褘 斜褘褌褜 蟹邪锌懈褋邪薪褘 胁 斜邪蟹褍 写邪薪薪褘褏</string>
+    <string name="pref_title_cycling_persistence_interval">袠薪褌械褉胁邪谢 锌芯褋褌芯褟薪褋褌胁邪</string>
+    <string name="pref_dashboard_devices_to_include">校褋褌褉芯泄褋褌胁邪, 胁泻谢褞褔邪褞褖懈械</string>
+    <string name="pref_dashboard_select_devices_summary">袨斜褗械写懈薪懈褌械 写邪薪薪褘械 芯斜 邪泻褌懈胁薪芯褋褌懈 褋 芯锌褉械写械谢械薪薪褘褏 褍褋褌褉芯泄褋褌胁 写谢褟 锌芯谢褍褔械薪懈褟 懈褌芯谐芯胁褘褏 锌芯泻邪蟹邪褌械谢械泄 薪邪 锌褉懈斜芯褉薪芯泄 锌邪薪械谢懈</string>
+    <string name="pref_dashboard_all_devices_summary">袨斜褗械写懈薪懈褌械 写邪薪薪褘械 芯斜 邪泻褌懈胁薪芯褋褌懈 褋芯 胁褋械褏 写芯斜邪胁谢械薪薪褘褏 褍褋褌褉芯泄褋褌胁 写谢褟 锌芯谢褍褔械薪懈褟 懈褌芯谐芯胁褘褏 锌芯泻邪蟹邪褌械谢械泄 薪邪 锌褉懈斜芯褉薪芯泄 锌邪薪械谢懈</string>
+    <string name="pref_dashboard_widget_today_upside_down_summary">袙 褉械卸懈屑械 24 褔邪褋邪 薪邪褉懈褋褍泄褌械 锌芯谢薪芯褔褜 胁薪懈蟹褍, 邪 锌芯谢写械薪褜 - 胁胁械褉褏褍 谐褉邪褎懈泻邪</string>
+    <string name="sleepasandroid_settings">Sleep As Android</string>
+    <string name="pref_sleepasandroid_slot_summary">袣邪泻芯泄 褋谢芯褌 斜褍写懈谢褜薪懈泻邪 懈褋锌芯谢褜蟹芯胁邪褌褜 锌褉懈 褍褋褌邪薪芯胁泻械 斜褍写懈谢褜薪懈泻芯胁</string>
+    <string name="pref_sleepasandroid_slot_title">小谢芯褌 写谢褟 褋懈谐薪邪谢懈蟹邪褑懈懈</string>
+    <string name="swingAngleAvg">小褉械写薪懈泄 褍谐芯谢 锌芯胁芯褉芯褌邪</string>
+    <string name="foreFootLandings">袩芯褋邪写泻邪 锌械褉械写薪械泄 薪芯谐懈</string>
+    <string name="midFootLandings">袩褉懈蟹械屑谢械薪懈械 薪邪 褋械褉械写懈薪褍 褋褌芯锌褘</string>
+    <string name="backFootLandings">袩褉懈蟹械屑谢械薪懈械 薪邪 蟹邪写薪褞褞 薪芯谐褍</string>
+    <string name="eversionAngleAvg">小褉械写薪懈泄 褍谐芯谢 胁褘胁芯褉邪褔懈胁邪薪懈褟</string>
+    <string name="eversionAngleMax">袦邪泻褋懈屑邪谢褜薪褘泄 褍谐芯谢 胁褘胁芯褉邪褔懈胁邪薪懈褟</string>
+    <string name="fmtPaceDistance">孝械屑锌 %d 褉邪褋褋褌芯褟薪懈械</string>
+    <string name="fmtPaceType">孝械屑锌 %d 褌懈锌</string>
+    <string name="fmtPacePace">孝械屑锌 %d 褌械屑锌</string>
+    <string name="fmtPaceCorrection">孝械屑锌 %d 泻芯褉褉械泻褑懈懈</string>
+    <string name="fmtPaceTypeAverage">孝懈锌 褌械屑锌邪 %d 褋褉械写薪械械</string>
+    <string name="unknownDataEncountered">袩芯谢褍褔械薪褘 薪械懈蟹胁械褋褌薪褘械 写邪薪薪褘械</string>
+    <string name="cyclingPowerAverage">小褉械写薪褟褟 屑芯褖薪芯褋褌褜 褑懈泻谢邪</string>
+    <string name="cyclingPowerMin">袦懈薪懈屑邪谢褜薪邪褟 屑芯褖薪芯褋褌褜 褑懈泻谢邪</string>
+    <string name="strokes_minute">褋褌褉芯泻邪/屑懈薪</string>
+    <string name="milliseconds">屑懈谢谢懈褋械泻褍薪写褘</string>
+    <string name="degrees">谐褉邪写褍褋褘</string>
+    <string name="Pace">孝械屑锌</string>
+    <string name="RunningForm">肖芯褉屑邪 斜械谐邪</string>
+    <string name="widget_layout_top_wide_bot_large">楔懈褉芯泻懈泄 胁胁械褉褏褍, 斜芯谢褜褕芯泄 胁薪懈蟹褍</string>
+    <string name="widget_name_untitled">袙懈写卸械褌 斜械蟹 薪邪蟹胁邪薪懈褟 (%1$s)</string>
+    <string name="scan_not_scanning">袧械 褋泻邪薪懈褉褍械褌褋褟</string>
+    <string name="scan_scanning_all_devices">小泻邪薪懈褉芯胁邪薪懈械 胁褋械褏 褍褋褌褉芯泄褋褌胁</string>
+    <string name="scan_scanning_single_device">小泻邪薪懈褉芯胁邪薪懈械 1 褍褋褌褉芯泄褋褌胁邪</string>
+    <string name="scan_scanning_multiple_devices">小泻邪薪懈褉芯胁邪薪懈械 %d 褍褋褌褉芯泄褋褌胁</string>
+    <string name="pref_dashboard_first_title">小薪邪褔邪谢邪 锌芯泻邪卸懈褌械 锌褉懈斜芯褉薪褍褞 锌邪薪械谢褜</string>
+    <string name="pref_dashboard_first_summary">袩芯泻邪蟹褘胁邪泄褌械 锌褉懈斜芯褉薪褍褞 锌邪薪械谢褜 锌褉懈 蟹邪锌褍褋泻械 Gadgetbridge 胁屑械褋褌芯 褝泻褉邪薪邪 褍褋褌褉芯泄褋褌胁</string>
+    <string name="pref_dashboard_cards_title">袩芯泻邪蟹褘胁邪泄褌械 胁懈写卸械褌褘 薪邪 泻邪褉褌芯褔泻邪褏</string>
+    <string name="pref_dashboard_widget_settings">袧邪褋褌褉芯泄泻懈 胁懈写卸械褌邪</string>
+    <string name="pref_dashboard_widget_goals_chart_title">孝邪斜谢懈褑邪 褑械谢械泄</string>
+    <string name="pref_dashboard_widget_today_hr_interval_summary">袣芯谢懈褔械褋褌胁芯 屑懈薪褍褌, 胁 褌械褔械薪懈械 泻芯褌芯褉褘褏 薪邪 谐褉邪褎懈泻械 芯褌芯斜褉邪卸邪械褌褋褟 \"薪芯褕械薪懈械\" 锌芯褋谢械 泻邪卸写芯谐芯 褍褋锌械褕薪芯谐芯 蟹邪屑械褉邪 锌褍谢褜褋邪</string>
+    <string name="pref_speak_notifications_aloud_summary">校胁械写芯屑谢械薪懈褟 斜褍写褍褌 褔懈褌邪褌褜褋褟 胁褋谢褍褏 褔械褉械蟹 薪邪褍褕薪懈泻懈</string>
+    <string name="pref_summary_garmin_default_reply_suffix">袩褉懈谢邪谐邪褌械谢褜薪芯械 胁 写芯锌芯谢薪械薪懈械 泻 褋褍褎褎懈泻褋褍, 褍褋褌邪薪芯胁谢械薪薪芯屑褍 胁 袚邪写卸械褌斜褉懈写卸械</string>
+    <string name="pref_dashboard_widget_today_upside_down_title">袩芯谢薪芯褔褜 薪邪 写薪械</string>
+    <string name="pref_sleepasandroid_enable_summary">袙泻谢褞褔懈褌械 褉械卸懈屑 褋薪邪 泻邪泻 懈薪褌械谐褉邪褑懈褞 Android</string>
+    <string name="pref_sleepasandroid_device_title">校褋褌褉芯泄褋褌胁芯 锌芯褋褌邪胁褖懈泻邪</string>
+    <string name="title_cycling">袙械谢芯褋懈锌械写</string>
+    <string name="none">袨褌褋褍褌褋褌胁褍械褌</string>
+    <string name="folder">袩邪锌泻邪</string>
+    <string name="url">URL</string>
+    <string name="garmin_agps_local_file">袥芯泻邪谢褜薪褘泄 褎邪泄谢</string>
+    <string name="copied_to_clipboard">小泻芯锌懈褉芯胁邪薪芯 胁 斜褍褎械褉 芯斜屑械薪邪</string>
+    <string name="devicesetting_scannable_minimum_unseen_summary">袩芯褋谢械 褋泻邪薪懈褉芯胁邪薪懈褟 褍褋褌褉芯泄褋褌胁芯 写芯谢卸薪芯 芯褋褌邪胁邪褌褜褋褟 薪械蟹邪屑械褔械薪薪褘屑 胁 褌械褔械薪懈械 芯锌褉械写械谢械薪薪芯谐芯 胁褉械屑械薪懈, 锌褉械卸写械 褔械屑 械谐芯 褋薪芯胁邪 蟹邪褉械谐懈褋褌褉懈褉褍褞褌</string>
+    <string name="pref_title_garmin_default_reply_suffix">袠褋锌芯谢褜蟹褍泄褌械 锌褉械写芯锌褉械写械谢械薪薪褘泄 褋褍褎褎懈泻褋 芯褌胁械褌邪</string>
+    <string name="garmin_agps_url_i">AGPS URL %1$d</string>
+    <string name="pref_garmin_agps_help">袙 锌褉懈胁械写械薪薪芯屑 薪懈卸械 褋锌懈褋泻械 褋芯写械褉卸邪褌褋褟 胁褋械 URL-邪写褉械褋邪, 蟹邪锌褉邪褕懈胁邪械屑褘械 褔邪褋邪屑懈 写谢褟 芯斜薪芯胁谢械薪懈褟 AGPS. 袙褘 屑芯卸械褌械 胁褘斜褉邪褌褜 褎邪泄谢 懈蟹 褏褉邪薪懈谢懈褖邪 褌械谢械褎芯薪邪, 泻芯褌芯褉褘泄 斜褍写械褌 芯褌锌褉邪胁谢械薪 薪邪 褔邪褋褘 锌褉懈 蟹邪锌褉芯褋械 芯斜薪芯胁谢械薪懈褟.</string>
+    <string name="no_folder_selected">袧械 胁褘斜褉邪薪邪 锌邪锌泻邪</string>
+    <string name="folder_is_empty">袩邪锌泻邪 锌褍褋褌邪褟</string>
+    <string name="devicetype_garmin_epix_pro">Garmin Epix Pro</string>
+    <string name="devicetype_garmin_instinct_2s_solar">Garmin Instinct 2S Solar</string>
+    <string name="devicetype_garmin_vivosmart_5">Garmin Vivosmart 5</string>
+    <string name="devicetype_garmin_fenix_6_sapphire">Garmin Fenix 6 Sapphire</string>
+    <string name="activity_prefs_height_inches">袪芯褋褌 胁 写褞泄屑邪褏</string>
+    <string name="loading">袟邪谐褉褍蟹泻邪鈥�</string>
+    <string name="min_val">袦懈薪懈屑褍屑: %d</string>
+    <string name="toggle_debug_mode">袙泻谢褞褔懈褌褜 芯褌谢邪写芯褔薪褘泄 褉械卸懈屑</string>
+    <string name="share_debug_info">袩芯写械谢懈褌褜褋褟 芯褌谢邪写芯褔薪芯泄 懈薪褎芯褉屑邪褑懈械泄</string>
+    <string name="devicetype_garmin_instinct_2x_solar">Garmin Instinct 2X Solar</string>
+    <string name="unsupported">袧械 锌芯写写械褉卸懈胁邪械褌褋褟</string>
+    <string name="max_val">袦邪泻褋懈屑褍屑: %d</string>
+    <string name="devicetype_garmin_venu_2_plus">Garmin Venu 2 Plus</string>
+    <string name="notif_battery_full_title">袘邪褌邪褉械褟 蟹邪褉褟卸械薪邪!</string>
+    <string name="notif_battery_full">%1$s 斜邪褌邪褉械褟 蟹邪褉褟卸械薪邪</string>
+    <string name="authentication_failed_check_key">袗褍褌械薪褌懈褎懈泻邪褑懈褟 薪械 褍写邪谢邪褋褜, 锌褉芯胁械褉褜褌械 泻谢褞褔</string>
+    <string name="devicetype_garmin_fenix_7s">Garmin Fenix 7S</string>
+    <string name="devicetype_garmin_fenix_7_pro">Garmin Fenix 7 Pro</string>
+    <string name="devicetype_garmin_forerunner_255">Garmin Forerunner 255</string>
+    <string name="notification_channel_full_battery_name">袩芯谢薪邪褟 斜邪褌邪褉械褟</string>
+    <string name="battery_i">袘邪褌邪褉械褟 %d</string>
+    <string name="battery_percentage_str">%1$s%%</string>
+    <string name="battery_full_notify_enabled">校胁械写芯屑谢褟褌褜 芯斜 芯泻芯薪褔邪薪懈懈 蟹邪褉褟写泻懈</string>
+    <string name="devicetype_garmin_venu_3s">Garmin Venu 3S</string>
+    <string name="devicetype_garmin_forerunner_265">Garmin Forerunner 265</string>
+    <string name="default_percentage">袩芯 褍屑芯谢褔邪薪懈褞 (%1$d%%)</string>
+    <string name="show_in_notification">袩芯泻邪蟹褘胁邪褌褜 胁 褍胁械写芯屑谢械薪懈懈</string>
+    <string name="battery_low_notify_enabled">校胁械写芯屑谢褟褌褜 芯 薪懈蟹泻芯屑 蟹邪褉褟写械 斜邪褌邪褉械懈</string>
+    <string name="battery_full_threshold">袩芯褉芯谐 芯泻芯薪褔邪薪懈褟 蟹邪褉褟写泻懈</string>
+    <string name="battery_low_threshold">袩芯褉芯谐 薪懈蟹泻芯谐芯 蟹邪褉褟写邪</string>
+    <string name="pref_fetch_unknown_files_title">小泻邪褔邪褌褜 薪械懈蟹胁械褋褌薪褘械 褎邪泄谢褘</string>
+    <string name="pref_fetch_unknown_files_summary">小芯褏褉邪薪懈褌褜 薪械懈蟹胁械褋褌薪褘械 褎邪泄谢褘 邪泻褌懈胁薪芯褋褌懈 褋 褔邪褋芯胁 薪邪 褝褌芯屑 褍褋褌褉芯泄褋褌胁械. 袨薪懈 薪械 斜褍写褍褌 芯斜褉邪斜芯褌邪薪褘.</string>
+    <string name="devicetype_garmin_venu_2">Garmin Venu 2</string>
+    <string name="devicetype_hama_fit6900">Hama Fit6900</string>
+    <plurals name="amount_of_days">
+        <item quantity="one">%d 写械薪褜</item>
+        <item quantity="few">%d 写薪褟</item>
+        <item quantity="many">%d 写薪械泄</item>
+        <item quantity="other">%d 写薪械泄</item>
+    </plurals>
+    <string name="dashboard_calendar_month_goals_reached_title">% 写芯褋褌懈谐薪褍褌芯 芯褌 褑械谢懈 褕邪谐芯胁</string>
+    <string name="devicetype_garmin_forerunner_255s">Garmin Forerunner 255S</string>
+    <string name="devicetype_garmin_instinct">Garmin Instinct</string>
+    <string name="moondrop_touch_earbud_both">袨斜邪</string>
+    <string name="moondrop_touch_trigger">小褉邪斜邪褌褘胁邪褌褜 薪邪</string>
+    <string name="moondrop_touch_action_play_pause">袙芯褋锌褉芯懈蟹胁械写械薪懈械/袩邪褍蟹邪</string>
+    <string name="moondrop_touch_action_call_pick_hang">袩褉懈薪褟褌褜/蟹邪胁械褉褕懈褌褜 胁褘蟹芯胁</string>
+    <string name="moondrop_touch_action_call_start">袧邪褔邪褌褜 胁褘蟹芯胁</string>
+    <string name="moondrop_touch_action_assistant">袙褘蟹芯胁 谐芯谢芯褋芯胁芯谐芯 锌芯屑芯褖薪懈泻邪</string>
+    <string name="moondrop_touch_trigger_long_press_1s">袛芯谢谐芯械 泻邪褋邪薪懈械 (1 褋械泻)</string>
+    <string name="moondrop_touch_trigger_long_press_3s">袛芯谢谐芯械 泻邪褋邪薪懈械 (3 褋械泻)</string>
+    <string name="devicetype_moondrop_space_travel">Moondrop Space Travel</string>
+    <string name="moondrop_touch_action_anc_mode">袠蟹屑械薪懈褌褜 褉械卸懈屑 锌芯写邪胁谢械薪懈褟 褕褍屑邪</string>
+    <string name="devicetype_garmin_fenix_5">Garmin Fenix 5</string>
+    <string name="devicetype_garmin_fenix_6">Garmin Fenix 6</string>
+    <string name="devicetype_garmin_vivoactive_3">Garmin V铆voactive 3</string>
+    <string name="realtime_settings">袧邪褋褌褉芯泄泻懈 胁 褉械邪谢褜薪芯屑 胁褉械屑械薪懈</string>
+    <string name="proprietary_app_warning">协褌邪 褎褍薪泻褑懈褟 褌褉械斜褍械褌 褍褋褌邪薪芯胁泻褍 锌褉芯锌褉懈械褌邪褉薪芯谐芯 锌褉懈谢芯卸械薪懈褟</string>
+    <string name="devicetype_garmin_vivomove_trend">Garmin V铆vomove Trend</string>
+    <string name="devicetype_garmin_vivosport">Garmin V铆vosport</string>
+    <string name="pref_battery_polling_summary">协褌芯 薪邪懈谢褍褔褕邪褟 锌芯锌褘褌泻邪, 懈 胁芯蟹屑芯卸薪芯 芯薪邪 蟹邪褌褟薪械褌褋褟 锌芯 薪械褋泻芯谢褜泻懈屑 锌褉懈褔懈薪邪屑</string>
+    <string name="pref_battery_polling_interval_format">褔械褉械蟹 泻邪卸写褘械 %1$s 屑懈薪褍褌</string>
+    <string name="pref_title_notification_times_enabled">袙褉械屑褟 褍胁械写芯屑谢械薪懈泄</string>
+    <string name="activity_type_parkour">袩邪褉泻褍褉</string>
+    <string name="devicetype_nothing_cmf_watch_pro_2">CMF Watch Pro 2</string>
+    <string name="pref_summary_notification_times_enabled">袨褌锌褉邪胁谢褟褌褜 褍胁械写芯屑谢械薪懈褟 褌芯谢褜泻芯 胁 褝褌芯屑 胁褉械屑械薪薪芯屑 写懈邪锌邪蟹芯薪械</string>
+    <string name="minutes_20">20 屑懈薪褍褌</string>
+    <string name="stats_empty_value">-</string>
+    <string name="cannot_upload_watchface_too_many_watchfaces_installed">袟邪谐褉褍蟹泻邪 褑懈褎械褉斜谢邪褌邪 薪械胁芯蟹屑芯卸薪邪. 校褋褌邪薪芯胁谢械薪芯 褋谢懈褕泻芯屑 屑薪芯谐芯 褑懈褎械褉斜谢邪褌芯胁</string>
+    <string name="insufficient_space_for_upload">袧械写芯褋褌邪褌芯褔薪芯 屑械褋褌邪 写谢褟 蟹邪谐褉褍蟹泻懈</string>
+    <string name="gps_glonass">GPS + 袚袥袨袧袗小小</string>
+    <string name="minutes_60">60 屑懈薪褍褌</string>
+    <string name="hrv_status">小芯褋褌芯褟薪懈械 袙小袪</string>
+    <string name="sleep_colored_stats_deep">袚谢褍斜芯泻懈泄</string>
+    <string name="sleep_colored_stats_light">袥褢谐泻懈泄</string>
+    <string name="pref_header_hrv_status">小芯褋褌芯褟薪懈械 袙小袪</string>
+    <string name="minutes_7">7 屑懈薪褍褌</string>
+    <string name="minutes_2">2 屑懈薪褍褌褘</string>
+    <string name="minutes_6">6 屑懈薪褍褌</string>
+    <string name="minutes_9">9 屑懈薪褍褌</string>
+    <string name="minutes_15">15 屑懈薪褍褌</string>
+    <string name="minutes_45">45 屑懈薪褍褌</string>
+    <string name="minutes_150">150 屑懈薪褍褌</string>
+    <string name="minutes_180">180 屑懈薪褍褌</string>
+    <string name="minutes_210">210 屑懈薪褍褌</string>
+    <string name="minutes_240">240 屑懈薪褍褌</string>
+    <string name="minutes_255">255 屑懈薪褍褌</string>
+    <string name="dateformat_month_day">袦械褋褟褑, 写械薪褜</string>
+    <string name="dateformat_day_month">袛械薪褜, 屑械褋褟褑</string>
+    <string name="minutes_8">8 屑懈薪褍褌</string>
+    <string name="minutes_120">120 屑懈薪褍褌</string>
+    <string name="minutes_3">3 屑懈薪褍褌褘</string>
+    <string name="minutes_4">4 屑懈薪褍褌褘</string>
+    <string name="minutes_75">75 屑懈薪褍褌</string>
+    <string name="minutes_90">90 屑懈薪褍褌</string>
+    <string name="activity_type_softball_slow_pitch">袦械写谢械薪薪褘泄 褋芯褎褌斜芯谢</string>
+    <string name="activity_type_stand_up_paddleboarding">小邪锌褋褢褉褎懈薪谐</string>
+    <string name="activity_type_video_gaming">袙懈写械芯懈谐褉褘</string>
+    <string name="activity_type_aerobics">袗褝褉芯斜懈泻邪</string>
+    <string name="activity_type_aerobic_combo">袗褝褉芯斜懈泻邪 泻芯屑斜芯</string>
+    <string name="stats_lowest_hr">袦懈薪懈屑邪谢褜薪褘泄 锌褍谢褜褋</string>
+    <string name="activity_type_beach_volleyball">袩谢褟卸薪褘泄 胁芯谢械泄斜芯谢</string>
+    <string name="activity_type_indoor_track">袣褉褘褌邪褟 袛芯褉芯卸泻邪</string>
+    <string name="activity_type_handcycling">啸褝薪写斜邪泄泻</string>
+    <string name="activity_type_e_bike">协谢械泻褌褉芯胁械谢芯褋懈锌械写</string>
+    <string name="transition">袩械褉械褏芯写</string>
+    <string name="activity_type_parachuting">袩邪褉邪褕褞褌薪褘泄 褋锌芯褉褌</string>
+    <string name="activity_type_auto_racing">袗胁褌芯褋锌芯褉褌</string>
+    <string name="activity_type_training">孝褉械薪懈褉芯胁泻邪</string>
+    <string name="activity_type_fitness_equipment">袨斜芯褉褍写芯胁邪薪懈械 写谢褟 褎懈褌薪械褋邪</string>
+    <string name="activity_type_american_football">袗屑械褉懈泻邪薪褋泻懈泄 褎褍褌斜芯谢</string>
+    <string name="activity_type_snowshoe">小薪械谐芯褋褌褍锌褘</string>
+    <string name="activity_type_rafting">袪邪褎褌懈薪谐</string>
+    <string name="activity_type_shooting">小褌褉械谢褜斜邪</string>
+    <string name="activity_type_winter_sport">袟懈屑薪懈泄 褋锌芯褉褌</string>
+    <string name="activity_type_team_sport">袣芯屑邪薪写薪褘泄 褋锌芯褉褌</string>
+    <string name="activity_type_lacrosse">袥褟泻褉芯褋褋</string>
+    <string name="authentication_failed_negotiation">袧械 褍写邪谢芯褋褜 褋芯谐谢邪褋芯胁邪褌褜 泻谢褞褔 邪褍褌械薪褌懈褎懈泻邪褑懈懈</string>
+    <string name="hrv">袙小袪</string>
+    <string name="activity_type_mixed_martial_arts">小屑械褕邪薪薪褘械 斜芯械胁褘械 懈褋泻褍褋褋褌胁邪</string>
+    <string name="pref_time_sync">袗胁褌芯屑邪褌懈褔械褋泻邪褟 褋懈薪褏褉芯薪懈蟹邪褑懈褟 胁褉械屑械薪懈</string>
+    <string name="activity_type_sky_diving">小泻邪泄写邪泄胁懈薪谐</string>
+    <string name="activity_type_snowmobiling">小薪械谐芯褏芯写</string>
+    <string name="activity_type_rem_sleep">REM 褎邪蟹邪 褋薪邪</string>
+    <string name="activity_type_air_walker">袙芯蟹写褍褕薪褘泄 褏芯写</string>
+    <string name="activity_type_artistic_swimming">袗褉褌懈褋褌懈褔械褋泻芯械 锌谢邪胁邪薪懈械</string>
+    <string name="find_my_phone_companion_warning">袛谢褟 褎褍薪泻褑懈懈 锌芯懈褋泻邪 褌械谢械褎芯薪邪 褌褉械斜褍械褌褋褟 褋芯锌褉褟卸械薪懈械 褋 泻芯屑锌邪薪褜芯薪芯屑. 袧邪卸屑懈褌械 蟹写械褋褜 写谢褟 锌芯谢褍褔械薪懈褟 写芯锌芯谢薪懈褌械谢褜薪芯泄 懈薪褎芯褉屑邪褑懈懈.</string>
+    <string name="abstract_chart_fragment_kind_awake_sleep">袩褉芯斜褍卸写械薪懈褟</string>
+    <string name="sleep_colored_stats_rem">REM</string>
+    <string name="stats_highest_hr">袦邪泻褋懈屑邪谢褜薪褘泄 锌褍谢褜褋</string>
+    <string name="activity_type_tae_bo">孝邪泄 袘芯</string>
+    <string name="activity_type_gateball">袚械泄褌斜芯谢</string>
+    <string name="pref_header_sound">袟胁褍泻</string>
+    <string name="activity_type_grinding">袠薪褌械薪褋懈胁薪邪褟 褌褉械薪懈褉芯胁泻邪</string>
+    <string name="choose_device">袙褘斜械褉械褌械 褍褋褌褉芯泄褋褌胁芯</string>
+    <string name="no_supported_devices_found">袧械 薪邪泄写械薪芯 锌芯写写械褉卸懈胁邪械屑褘褏 写械胁邪泄褋芯胁</string>
+    <string name="busy_task_fetch_sleep_data">袩芯谢褍褔械薪懈械 写邪薪薪褘褏 芯 褋薪械</string>
+    <string name="search">袩芯懈褋泻</string>
+    <string name="hr_resting">袨褌写褘褏</string>
+    <string name="hr_maximum">袦邪泻褋懈屑邪谢褜薪褘泄</string>
+    <string name="hr_minimum">袦懈薪懈屑邪谢褜薪褘泄</string>
+    <string name="hr_average">小褉械写薪懈泄</string>
+    <string name="stress_average">小褉械写薪懈泄</string>
+    <string name="activity_type_sepak_takraw">小械锌邪泻褌邪泻褉邪褍</string>
+    <string name="activity_type_bike_commute">袩芯械蟹写泻邪 薪邪 胁械谢芯褋懈锌械写械</string>
+    <string name="activity_type_wakeboarding">袙械泄泻斜芯褉写懈薪谐</string>
+    <string name="activity_type_water_skiing">袙芯写薪褘械 谢褘卸懈</string>
+    <string name="activity_type_kayaking">袣邪褟泻懈薪谐</string>
+    <string name="activity_type_tactical">孝邪泻褌懈泻邪</string>
+    <string name="activity_type_floor_climbing">袩芯写褗械屑 锌芯 褝褌邪卸邪屑</string>
+    <string name="activity_type_health_snapshot">袨褑械薪泻邪 蟹写芯褉芯胁褜褟</string>
+    <string name="activity_type_marine">袩谢邪胁邪薪褜械 胁 屑芯褉械</string>
+    <string name="activity_type_racket">袪邪泻械褌泻邪</string>
+    <string name="activity_type_padel">袩邪写械谢</string>
+    <string name="activity_type_racquetball">袪邪泻械褌斜芯谢</string>
+    <string name="activity_type_meditation">袦械写懈褌邪褑懈褟</string>
+    <string name="activity_type_para_sport">袩邪褉邪褋锌芯褉褌</string>
+    <string name="activity_type_disc_golf">袛懈褋泻-谐芯谢褜褎</string>
+    <string name="activity_type_ultimate_disc">袗谢褌懈屑邪褌 褎褉懈褋斜懈</string>
+    <string name="activity_type_wakesurfing">袙械泄泻褋褢褉褎</string>
+    <string name="activity_type_ballroom_dance">小锌芯褉褌懈胁薪褘泄 斜邪谢褜薪褘泄 褌邪薪械褑</string>
+    <string name="pref_title_nagivation_apps">袩褉懈谢芯卸械薪懈褟 写谢褟 薪邪胁懈谐邪褑懈懈</string>
+    <string name="pref_header_external_integrations">袙薪械褕薪懈械 懈薪褌械谐褉邪褑懈懈</string>
+    <string name="pref_header_automations">袗胁褌芯屑邪褌懈蟹邪褑懈褟</string>
+    <string name="pref_description_general">袟邪锌褍褋泻, 褟蟹褘泻, 褉械谐懈芯薪, 屑械褋褌芯锌芯谢芯卸械薪懈械</string>
+    <string name="pref_description_about_you">袛邪褌邪 褉芯卸写械薪懈褟, 锌芯谢, 褉芯褋褌, 胁械褋, 褑械谢懈</string>
+    <string name="pref_description_notifications">校胁械写芯屑谢械薪懈褟, 斜械谢褘泄 褋锌懈褋芯泻/褔褢褉薪褘泄 褋锌懈褋芯泻</string>
+    <string name="pref_description_user_interface">孝械屑邪, 谐谢邪胁薪褘泄 褝泻褉邪薪</string>
+    <string name="pref_description_dashboard">袙懈写卸械褌褘, 褍褋褌褉芯泄褋褌胁邪 写谢褟 写芯斜邪胁谢械薪懈褟</string>
+    <string name="pref_description_automations">袗胁褌芯屑邪褌懈褔械褋泻懈泄 褝泻褋锌芯褉褌, 邪胁褌芯屑邪褌懈褔械褋泻芯械 锌芯谢褍褔械薪懈械</string>
+    <string name="pref_description_developer_options">袥芯谐懈, Intent API</string>
+    <string name="pref_description_deprecated_functionalities">袧邪褋褌褉芯泄泻懈, 泻芯褌芯褉褘械 斜褍写褍褌 褍写邪谢械薪褘 胁 斜褍写褍褖懈褏 胁械褉褋懈褟褏</string>
+    <string name="pref_header_main_screen">袚谢邪胁薪褘泄 褝泻褉邪薪</string>
+    <string name="pref_title_user_interface">袩芯谢褜蟹芯胁邪褌械谢褜褋泻懈泄 懈薪褌械褉褎械泄褋</string>
+    <string name="user_feedback_set_settings_ok">袧邪泄褋褌褉芯泻懈 芯褌锌褉邪胁谢械薪褘 薪邪 褍褋褌褉芯泄褋褌胁芯.</string>
+    <string name="activity_type_beach_soccer">袩谢褟卸薪褘泄 褎褍褌斜芯谢</string>
+    <string name="activity_prefs_date_birth">袛邪褌邪 褉芯卸写械薪懈褟</string>
+    <string name="activity_type_luge">小邪薪薪褘泄 褋锌芯褉褌</string>
+    <string name="pref_title_sync_birthdays">小懈薪褏褉芯薪懈蟹邪褑懈褟 写薪械泄 褉芯卸写械薪懈褟</string>
+    <string name="pref_summary_sync_birthdays">小懈薪褏褉芯薪懈蟹懈褉芯胁邪褌褜 写薪懈 褉芯卸写械薪懈褟 泻芯薪褌邪泻褌芯胁 胁屑械褋褌械 褋 褋芯斜褘褌懈褟屑懈 泻邪谢械薪写邪褉褟</string>
+    <string name="devicetype_sony_wf_c500">Sony WF-C500</string>
+    <string name="recoveryTime">袙褉械屑褟 胁芯褋褋褌邪薪芯胁谢械薪懈褟</string>
+    <string name="devicetype_huawei_watchgt5">Huawei Watch GT 5 (Pro)</string>
+    <string name="miscale_weight_unit_title">袝写懈薪懈褑邪 胁械褋邪</string>
+    <string name="miscale_weight_unit_metric">袦械褌褉懈褔械褋泻邪褟 (泻谐)</string>
+    <string name="birthdays">袛薪懈 褉芯卸写械薪懈褟</string>
+    <string name="devicetype_sony_wf_c700n">Sony WF-C700N</string>
+    <string name="devicetype_honor_watchgs3">Honor Watch GS 3</string>
+    <string name="devicetype_honor_watchgspro">Honor Watch GS Pro</string>
+    <string name="devicetype_redmi_watch_5_active">Redmi Watch 5 Active</string>
+    <string name="contact_birthday">袛械薪褜 褉芯卸写械薪懈褟 %1s</string>
+    <string name="body_energy_gained">袧邪斜褉邪薪芯</string>
+    <string name="body_energy_lost">袩芯褌械褉褟薪芯</string>
+    <string name="hrv_status_seven_days_avg">小褉械写薪械械 蟹邪 7 写薪械泄</string>
+    <string name="hrv_status_seven_days_avg_status">小芯褋褌芯褟薪懈械</string>
+    <string name="hrv_status_last_night">袩褉芯褕谢芯泄 薪芯褔褜褞</string>
+    <string name="miscale_small_objects_summary">小芯褏褉邪薪褟褌褜 胁械褋 芯斜褗械泻褌芯胁 屑械薪褜褕械 10 泻谐</string>
+    <string name="activity_type_biathlon">袘懈邪褌谢芯薪</string>
+    <string name="hrv_status_day_avg">小褉械写薪械械 蟹邪 写械薪褜</string>
+    <string name="hrv_status_low">袧懈蟹泻懈泄</string>
+    <string name="devicetype_soundcore_liberty4_nc">Soundcore Liberty 4 NC</string>
+    <string name="devicetype_huawei_watchgtrunner">Huawei Watch GT Runner</string>
+    <string name="devicetype_huawei_watch3">Huawei Watch 3 (Pro)</string>
+    <string name="menuitem_map">袣邪褉褌邪</string>
+    <string name="menuitem_weight">袙械褋</string>
+    <string name="hrv_status_poor">袩谢芯褏芯泄</string>
+    <string name="hrv_status_balanced">小斜邪谢邪薪褋懈褉芯胁邪薪薪芯械</string>
+    <string name="hrv_status_unbalanced">袧械褋斜邪谢邪薪褋懈褉芯胁邪薪薪芯械</string>
+    <string name="devicetype_soundcore_motion300">Soundcore Motion 300</string>
+    <string name="activity_type_table_football">袧邪褋褌芯谢褜薪褘泄 褎褍褌斜芯谢</string>
+    <string name="idasen_control_button_stand">小孝袨携</string>
+    <string name="idasen_control_button_sit">小袠袛携</string>
+    <string name="idasen_pref_value_warning">袟薪邪褔械薪懈械 写芯谢卸薪芯 斜褘褌褜 胁 锌褉械写械谢邪褏 62-126 褋屑</string>
+    <string name="music_upload_info">袙褘 芯褌锌褉邪胁谢褟械褌械 褎邪泄谢 泻芯屑锌芯蟹懈褑懈懈:\n\n%1$s\n袧邪蟹胁邪薪懈械: %2$s\n袗谢褜斜芯屑: %3$s\n</string>
+    <string name="smart_ring_measurement_error_worn_incorrectly">袠蟹屑械褉械薪懈械 薪械 褍写邪谢芯褋褜. 袩褉芯胁械褉褜褌械 锌褉邪胁懈谢褜薪芯褋褌褜 褉邪褋锌芯谢芯卸械薪懈褟 褋械薪褋芯褉芯胁.</string>
+    <string name="file_already_exists">协褌芯褌 褎邪泄谢 褍卸械 褋褍褖械褋褌胁褍械褌</string>
+    <string name="mijia_lywsd_comfort_humidity_summary">袪械泻芯屑械薪写褍械屑褘泄 写懈邪锌邪蟹芯薪: 20 - 85</string>
+    <string name="mijia_lywsd_comfort_lower">袧懈卸薪褟褟 谐褉邪薪懈褑邪</string>
+    <string name="battery_polling_failed_start">袧械 褍写邪谢芯褋褜 薪邪褔邪褌褜 芯锌褉芯褋 斜邪褌邪褉械懈</string>
+    <string name="mijia_lywsd_comfort_level_title">校褉芯胁械薪褜 泻芯屑褎芯褉褌邪</string>
+    <string name="mijia_lywsd_comfort_level_summary">袧邪褋褌褉芯泄褌械 褍褉芯胁薪懈 褌械屑锌械褉邪褌褍褉褘 懈 胁谢邪卸薪芯褋褌懈 写谢褟 芯褌芯斜褉邪卸邪械屑褘褏 褝屑芯写蟹懈</string>
+    <string name="number_selected_items">%1d 胁褘斜褉邪薪芯</string>
+    <string name="redmi_buds_5_pro_equalizer_neg6">-6 写袘</string>
+    <string name="redmi_buds_5_pro_equalizer_neg5">-5 写袘</string>
+    <string name="redmi_buds_5_pro_equalizer_5">5 写袘</string>
+    <string name="redmi_buds_5_pro_equalizer_3">3 写袘</string>
+    <string name="redmi_buds_5_pro_equalizer_4">4 写袘</string>
+    <string name="redmi_buds_5_pro_equalizer_6">6 写袘</string>
+    <string name="soundcore_equalizer_frequency">效邪褋褌芯褌邪</string>
+    <string name="soundcore_equalizer_value">袟薪邪褔械薪懈械</string>
+    <string name="miscale_small_objects_title">袧械斜芯谢褜褕懈械 锌褉械写屑械褌褘</string>
+    <string name="mijia_lywsd_comfort_humidity_title">袙谢邪卸薪芯褋褌褜 (%)</string>
+    <string name="mijia_lywsd_comfort_temperature_summary">袪械泻芯屑械薪写褍械屑褘泄 写懈邪锌邪蟹芯薪: 19 - 27</string>
+    <string name="mijia_lywsd_comfort_temperature_title">孝械屑锌械褉邪褌褍褉邪 (掳C)</string>
+    <string name="pref_dnd_follow_phone_title">小芯芯褌胁械褌褋褌胁芯胁邪褌褜 褉械卸懈屑褍 芦薪械 斜械褋锌芯泻芯懈褌褜禄 褍褋褌褉芯泄褋褌胁邪</string>
+    <string name="music_new_playlist">小芯蟹写邪褌褜 锌谢械泄谢懈褋褌</string>
+    <string name="music_rename_playlist">袩械褉械懈屑械薪芯胁邪褌褜 锌谢械泄谢懈褋褌</string>
+    <string name="permission_manage_dnd_title">校锌褉邪胁谢械薪懈械 褉械卸懈屑芯屑 芦薪械 斜械褋锌芯泻芯懈褌褜禄</string>
+    <string name="date_placeholders__date__time">%1s %1s</string>
+    <string name="miscale_weight_unit_summary">校泻邪卸懈褌械 械写懈薪懈褑褍 屑邪褋褋褘 写谢褟 芯褌芯斜褉邪卸械薪懈褟 蟹邪屑械褉芯胁</string>
+    <string name="redmi_buds_5_pro_equalizer_neg3">-3 写袘</string>
+    <string name="redmi_buds_5_pro_equalizer_neg4">-4 写袘</string>
+    <string name="redmi_buds_5_pro_equalizer_neg2">-2 写袘</string>
+    <string name="redmi_buds_5_pro_equalizer_2">2 写袘</string>
+    <string name="redmi_buds_5_pro_equalizer_neg1">-1 写袘</string>
+    <string name="redmi_buds_5_pro_equalizer_zero">0 写袘</string>
+    <string name="redmi_buds_5_pro_equalizer_1">1 写袘</string>
+    <string name="first_start_permissions_title">袪邪蟹褉械褕械薪懈褟</string>
+    <string name="first_start_overview_title">袨斜蟹芯褉</string>
+    <string name="mijia_lywsd_comfort_upper">袙械褉褏薪褟褟 谐褉邪薪懈褑邪</string>
+    <string name="date_placeholders__start_time__end_time">%1s - %2s</string>
+    <string name="pref_music_management_title">校锌褉邪胁谢械薪懈械 屑褍蟹褘泻芯泄</string>
+    <string name="pref_music_management_summary">校锌褉邪胁谢械薪懈械 褋芯褏褉邪薪褢薪薪芯泄 屑褍蟹褘泻芯泄</string>
+    <string name="music_delete_confirm_description">袙褘 褌芯褔薪芯 褏芯褌懈褌械 褍写邪谢懈褌褜 芦%1$s禄?</string>
+    <string name="music_add_to_playlist">袛芯斜邪胁懈褌褜 胁 锌谢械泄谢懈褋褌</string>
+    <string name="music_delete_from_playlist">校写邪谢懈褌褜 懈蟹 锌谢械泄谢懈褋褌邪</string>
+    <string name="music_delete">校写邪谢懈褌褜 褌褉械泻</string>
+    <string name="music_all_songs">袙褋械 褌褉械泻懈</string>
+    <string name="devicetype_colmi_r10">Colmi R10</string>
+    <string name="devicetype_garmin_venu_2s">Garmin Venu 2S</string>
+    <string name="devicetype_vivitar_hr_bp_monitor_activity_tracker">袦芯薪懈褌芯褉 邪泻褌懈胁薪芯褋褌懈 Vivitar HR &amp; BP Tracker</string>
+    <string name="devicetype_garmin_fenix_6s_pro">Garmin Fenix 6S Pro</string>
+    <string name="active_calories">袗泻褌懈胁薪褘械 泻邪谢芯褉懈懈</string>
+    <string name="active_calories_short">袗泻褌懈胁薪芯</string>
+    <string name="activity_prefs_goal_active_calories_burnt">袝卸械写薪械胁薪邪褟 褑械谢褜: 邪泻褌懈胁薪芯械 褋卸懈谐邪薪懈械 泻邪谢芯褉懈泄</string>
+    <string name="activity_type_motorcycling">袦芯褌芯褑懈泻谢</string>
+    <string name="activity_type_boating">袣邪褌邪薪懈械 薪邪 谢芯写泻械</string>
+    <string name="activity_type_hang_gliding">袛械谢褜褌邪锌谢邪薪械褉懈蟹屑</string>
+    <string name="activity_type_paddling">袚褉械斜谢褟</string>
+    <string name="activity_type_folk_dance">袧邪褉芯写薪褘泄 褌邪薪械褑</string>
+    <string name="activity_type_free_sparring">小胁芯斜芯写薪褘泄 褋锌邪褉褉懈薪谐</string>
+    <string name="devicetype_garmin_forerunner_255s_music">Garmin Forerunner 255S Music</string>
+    <string name="devicetype_mijia_xmwsdj04">袛邪褌褔懈泻 褌械屑锌械褉邪褌褍褉褘 懈 胁谢邪卸薪芯褋褌懈 Mijia 2 (E-ink)</string>
+    <string name="devicetype_sony_wi_c100">Sony WI-C100</string>
+    <string name="menuitem_calories_segmented">袣邪谢芯褉懈懈 (褋械谐屑械薪褌懈褉芯胁邪薪芯)</string>
+    <string name="menuitem_calories_active_goal">笑械谢褜 锌芯 泻邪谢芯褉懈褟屑 (邪泻褌懈胁薪芯)</string>
+    <string name="devicetype_bandw_pseries">小械褉懈褟 Bowers 懈 Wilkins P</string>
+    <string name="devicetype_huawei_band3pro">Huawei Band 3 (Pro)</string>
+    <string name="activity_type_indoor_push_run_speed">袨褌卸懈屑邪薪懈械 胁 锌芯屑械褖械薪懈懈 - 褋泻芯褉芯褋褌褜 斜械谐邪</string>
+    <string name="activity_type_bmx">BMX</string>
+    <string name="activity_type_futsal">肖褍褌蟹邪谢</string>
+    <string name="activity_type_hip_hop">啸懈锌-褏芯锌</string>
+    <string name="activity_type_billiard_pool">袘邪褋褋械泄薪</string>
+    <string name="activity_type_water_scooter">袙芯写薪褘泄 褋泻褍褌械褉</string>
+    <string name="hrv_status_day_avg_legend">小褉械写薪械褋褍褌芯褔薪芯械 蟹薪邪褔械薪懈械 (屑褋)</string>
+    <string name="sleep_colored_stats_awake_avg">袩褉芯斜褍卸写械薪懈械 AVG</string>
+    <string name="sleep_colored_stats_rem_avg">REM AVG</string>
+    <string name="activity_type_bocce">啸芯泻泻械泄 褋 屑褟褔芯屑</string>
+    <string name="activity_type_hula_hoop">袨斜褉褍褔</string>
+    <string name="devicetype_miband9pro">Xiaomi Smart Band 9 Pro</string>
+    <string name="devicetype_garmin_instinct_2">Garmin Instinct 2</string>
+    <string name="devicetype_garmin_forerunner_255_music">Garmin Forerunner 255 Music</string>
+    <string name="activity_type_hacky_sack">袦械褕芯褔械泻 褋 褏械泻泻懈</string>
+    <string name="activity_type_judo">袛蟹褞写芯</string>
+    <string name="activity_type_shuffleboard">楔邪褏屑邪褌薪邪褟 写芯褋泻邪</string>
+    <string name="activity_type_snorkeling">袩谢邪胁邪薪懈械 褋 屑邪褋泻芯泄 懈 褌褉褍斜泻芯泄</string>
+    <string name="activity_type_stair_climber">袥械褋褌薪懈褔薪褘泄 邪谢褜锌懈薪懈褋褌</string>
+    <string name="activity_type_cardio">袣邪褉写懈芯</string>
+    <string name="activity_type_platform_tennis">孝械薪薪懈褋 薪邪 锌谢邪褌褎芯褉屑械</string>
+    <string name="activity_type_board_game">袧邪褋褌芯谢褜薪邪褟 懈谐褉邪</string>
+    <string name="sleep_colored_stats_deep_avg">袚谢褍斜芯泻懈泄 AVG</string>
+    <string name="respiratoryrate">效邪褋褌芯褌邪 写褘褏邪薪懈褟</string>
+    <string name="active_calories_goal">袗泻褌懈胁薪邪褟 褑械谢褜</string>
+    <string name="total_calories_burnt">袙褋械谐芯 褋谐芯褉械谢芯</string>
+    <string name="sleep_avg">小芯薪 AVG</string>
+    <string name="lowest">小邪屑褘泄 薪懈蟹泻懈泄</string>
+    <string name="highest">小邪屑褘泄 胁褘褋芯泻懈泄</string>
+    <string name="activity_type_navigate">袧邪胁懈谐邪褑懈褟</string>
+    <string name="activity_type_xc_classic_ski">袣谢邪褋褋懈褔械褋泻懈械 谢褘卸懈 XC</string>
+    <string name="activity_type_mountaineering">袗谢褜锌懈薪懈蟹屑</string>
+    <string name="activity_type_driving">袙芯卸写械薪懈械</string>
+    <string name="activity_type_sail_race">袩邪褉褍褋薪邪褟 谐芯薪泻邪</string>
+    <string name="activity_type_push_run_speed">孝芯谢褔芯泻 - 褋泻芯褉芯褋褌褜 斜械谐邪</string>
+    <string name="activity_type_jai_alai">袪邪泄褋泻懈泄 褍谐芯谢芯泻</string>
+    <string name="activity_type_jujitsu">袛卸懈褍-写卸懈褌褋褍</string>
+    <string name="activity_type_muay_thai">袦褍邪泄-褌邪泄</string>
+    <string name="activity_type_water_polo">袙芯写薪芯械 锌芯谢芯</string>
+    <string name="activity_type_weiqi">袙褝泄褑懈</string>
+    <string name="activity_type_plaza_dancing">孝邪薪褑褘 薪邪 锌谢芯褖邪写懈</string>
+    <string name="activity_type_laser_tag">袥邪蟹械褉薪褘泄 褌械谐</string>
+    <string name="activity_type_canoeing">袣邪薪芯褝</string>
+    <string name="hrv_status_last_night_highest_5">袩褉芯褕谢芯泄 薪芯褔褜褞 5-屑懈薪褍褌薪褘泄 屑邪泻褋懈屑邪谢褜薪褘泄 锌芯泻邪蟹邪褌械谢褜</string>
+    <string name="body_energy_legend_level">校褉芯胁械薪褜 褝薪械褉谐懈懈 褌械谢邪</string>
+    <string name="devicetype_miband9">Xiaomi Smart Band 9</string>
+    <string name="devicetype_garmin_fenix_7">Garmin Fenix 7</string>
+    <string name="devicetype_garmin_forerunner_55">Garmin Forerunner 55</string>
+    <string name="devicetype_garmin_forerunner_235">Garmin Forerunner 235</string>
+    <string name="devicetype_garmin_forerunner_265s">Garmin Forerunner 265S</string>
+    <string name="devicetype_garmin_forerunner_620">Garmin Forerunner 620</string>
+    <string name="devicetype_idasen">小褌芯谢 IKEA Idasen</string>
+    <string name="devicetype_huawei_watchd2">Huawei Watch D2</string>
+    <string name="devicetype_redmi_buds_5_pro">Redmi Buds 5 Pro</string>
+    <string name="menuitem_heart_rate_push">袧邪卸邪褌懈械 薪邪 锌褍谢褜褋</string>
+    <string name="menuitem_night_display">袧芯褔薪芯泄 写懈褋锌谢械泄</string>
+    <string name="busy_task_fetch_hrv_data">袩芯谢褍褔械薪懈械 写邪薪薪褘褏 HRV</string>
+    <string name="pref_title_calendar_lookahead">袣芯谢懈褔械褋褌胁芯 写薪械泄 胁锌械褉械写</string>
+    <string name="pref_summary_calendar_lookahead">小懈薪褏褉芯薪懈蟹邪褑懈褟 写芯 %1s 写薪械泄 褋芯斜褘褌懈泄 泻邪谢械薪写邪褉褟</string>
+    <string name="sleep_colored_stats_light_avg">袥械谐泻懈泄 AVG</string>
+    <string name="activity_type_esports">协褋锌芯褉褌</string>
+    <string name="activity_type_wall_ball">袧邪褋褌械薪薪褘泄 屑褟褔</string>
+    <string name="devicetype_garmin_forerunner_165">Garmin Forerunner 165</string>
+    <string name="devicetype_garmin_fenix_5x_plus">Garmin Fenix 5X Plus</string>
+    <string name="activity_type_dragon_boat">袥芯写泻邪 写褉邪泻芯薪邪</string>
+    <string name="activity_type_jumpmaster">袩褉褘卸泻芯胁褘泄 屑邪褋褌械褉</string>
+    <string name="activity_type_push_walk_speed">孝芯谢褔芯泻 - 褋泻芯褉芯褋褌褜 褏芯写褜斜褘</string>
+    <string name="activity_type_tug_of_war">袩械褉械褌褟谐懈胁邪薪懈械 泻邪薪邪褌邪</string>
+    <string name="activity_type_obstacle_race">袚芯薪泻懈 褋 锌褉械锌褟褌褋褌胁懈褟屑懈</string>
+    <string name="devicetype_colmi_r06">Colmi R06</string>
+    <string name="menuitem_stress_simple">小褌褉械褋褋 (锌褉芯褋褌芯泄)</string>
+    <string name="devicetype_garmin_venu">Garmin Venu</string>
+    <string name="activity_type_spinning">小锌懈薪薪懈薪谐</string>
+    <string name="activity_type_race_walking">小锌芯褉褌懈胁薪邪褟 褏芯写褜斜邪</string>
+    <string name="activity_type_somatosensory_game">小芯屑邪褌芯褋械薪褋芯褉薪邪褟 懈谐褉邪</string>
+    <string name="activity_type_square_dance">袣胁邪写褉邪褌薪褘泄 褌邪薪械褑</string>
+    <string name="activity_type_body_combat">袘芯械胁芯械 褌械谢芯</string>
+    <string name="activity_type_bungee_jumping">袩褉褘卸泻懈 褋 褌邪褉蟹邪薪泻懈</string>
+    <string name="title_activity_musicmanager">袦褍蟹褘泻邪谢褜薪褘泄 屑械薪械写卸械褉</string>
+    <string name="activity_type_transition">袩械褉械褏芯写</string>
+    <string name="activity_type_breathwork">袛褘褏邪褌械谢褜薪邪褟 褉邪斜芯褌邪</string>
+    <string name="activity_type_handcycling_indoor">袙械谢芯褋锌芯褉褌 胁 锌芯屑械褖械薪懈懈</string>
+    <string name="activity_type_multisport">袦褍谢褜褌懈褋锌芯褉褌</string>
+    <string name="activity_type_bouldering">小泻邪谢芯谢邪蟹薪褘泄 褋锌芯褉褌</string>
+    <string name="activity_type_sail_expedition">协泻褋锌械写懈褑懈褟 锌芯写 锌邪褉褍褋芯屑</string>
+    <string name="activity_type_inline_skating">袣邪褌邪薪懈械 薪邪 泻芯薪褜泻邪褏</string>
+    <string name="activity_type_ice_skating">袣邪褌邪薪懈械 薪邪 泻芯薪褜泻邪褏</string>
+    <string name="activity_type_climb_indoor">袙芯褋褏芯卸写械薪懈械 胁 锌芯屑械褖械薪懈懈</string>
+    <string name="activity_type_water_tubing">袙芯写褟薪褘械 褌褉褍斜泻懈</string>
+    <string name="activity_type_chess">楔邪褏屑邪褌褘</string>
+    <string name="activity_type_flowriding">袩械褉械褌械泻邪薪懈械</string>
+    <string name="activity_type_modern_dance">小芯胁褉械屑械薪薪褘泄 褌邪薪械褑</string>
+    <string name="activity_type_bobsleigh">袘芯斜褋谢械泄</string>
+    <string name="hrv_status_seven_days_avg_long">小褉械写薪械械 蟹薪邪褔械薪懈械 蟹邪 7 写薪械泄</string>
+    <string name="hrv_status_unit">%1$d 屑褋</string>
+    <string name="activity_type_sledding">袣邪褌邪薪懈械 薪邪 褋邪薪褟褏</string>
+    <string name="activity_type_mass_gymnastics">袦邪褋褋芯胁邪褟 谐懈屑薪邪褋褌懈泻邪</string>
+    <string name="activity_type_orienteering">袨褉懈械薪褌懈褉芯胁邪薪懈械</string>
+    <string name="bpm_value_unit">%1$d bpm</string>
+    <string name="steps_distance_unit">%1$,.2f 泻屑</string>
+    <string name="hrv_status_baseline">%1$d-%2$d 屑褋</string>
+    <string name="hrv_status_baseline_label">袘邪蟹芯胁邪褟 谢懈薪懈褟</string>
+    <string name="devicetype_amazfit_trex_3">Amazfit T-Rex 3</string>
+    <string name="devicetype_garmin_enduro_3">Garmin Enduro 3</string>
+    <string name="devicetype_huawei_watchgtcyber">Huawei Watch GT Cyber</string>
+    <string name="activity_type_indoor_push_walk_speed">袨褌卸懈屑邪薪懈械 胁 锌芯屑械褖械薪懈懈 - 褋泻芯褉芯褋褌褜 褏芯写褜斜褘</string>
+    <string name="activity_type_breaking">袙蟹谢芯屑</string>
+    <string name="activity_type_bridge">袦芯褋褌</string>
+    <string name="activity_type_cardio_combat">袣邪褉写懈芯 斜芯泄</string>
+    <string name="activity_type_checkers">楔邪褕泻懈</string>
+    <string name="activity_type_finswimming">袩谢邪胁邪薪懈械 薪邪 褎懈薪泻邪褏</string>
+    <string name="activity_type_frisbee">肖褉懈褋斜懈</string>
+    <string name="activity_type_flying">袥械褌邪褌褜</string>
+    <string name="devicetype_colmi_r02">Colmi R02</string>
+    <string name="devicetype_mismartscale">Mi Smart Scale 2</string>
+    <string name="devicetype_micompositionscale">Mi 袙械褋褘 写谢褟 芯锌褉械写械谢械薪懈褟 褋芯褋褌邪胁邪 褌械谢邪 2</string>
+    <string name="devicetype_colmi_r03">Colmi R03</string>
+    <string name="menuitem_stress_breakdown">小褌褉械褋褋 (锌褉芯斜芯泄)</string>
+    <string name="menuitem_stress_segmented">小褌褉械褋褋 (褋械谐屑械薪褌懈褉芯胁邪薪薪褘泄)</string>
+    <string name="devicetype_garmin_forerunner_245_music">Garmin Forerunner 245 Music</string>
+    <string name="activity_type_parallel_bars">袩邪褉邪谢谢械谢褜薪褘械 斜褉褍褋褜褟</string>
+    <string name="activity_type_pole_dance">孝邪薪械褑 薪邪 褕械褋褌械</string>
+    <string name="devicetype_garmin_fenix_8">Garmin Fenix 8</string>
+    <string name="devicetype_garmin_fenix_5_plus">Garmin Fenix 5 Plus</string>
+    <string name="devicetype_garmin_venu_sq">Garmin Venu Sq</string>
+    <string name="devicetype_garmin_venu_sq_2">Garmin Venu Sq 2</string>
+    <string name="devicetype_garmin_fenix_6s_sapphire">Garmin Fenix 6S Sapphire</string>
+    <string name="devicetype_garmin_forerunner_955">Garmin Forerunner 955</string>
+    <string name="devicetype_garmin_forerunner_965">Garmin Forerunner 965</string>
+    <string name="milliseconds_ms">屑褋</string>
+    <string name="average_respiration_rate">小泻芯褉芯褋褌褜 写褘褏邪薪懈褟</string>
+    <string name="max_respiration_rate">袦邪泻褋懈屑邪谢褜薪邪褟 褋泻芯褉芯褋褌褜 写褘褏邪薪懈褟</string>
+    <string name="idasen_control_button_mid">MID</string>
+    <string name="estimatedSweatLoss">袪邪褋褔械褌薪邪褟 锌芯褌械褉褟 锌芯褌邪</string>
+    <string name="soundcore_equalizer_preset">袩褉械写褍褋褌邪薪芯胁泻邪</string>
+    <string name="soundcore_button_brightness_high">袙褘褋芯泻懈泄</string>
+    <string name="soundcore_adaptive_direction_summary">袗胁褌芯屑邪褌懈褔械褋泻邪褟 薪邪褋褌褉芯泄泻邪 褝泻胁邪谢邪泄蟹械褉邪 胁 蟹邪胁懈褋懈屑芯褋褌懈 芯褌 薪邪锌褉邪胁谢械薪懈褟 写胁懈卸械薪懈褟 褍褋褌褉芯泄褋褌胁邪</string>
+    <string name="soundcore_equalizer_band2">袚褉褍锌锌邪 2</string>
+    <string name="soundcore_equalizer_band3">袚褉褍锌锌邪 3</string>
+    <string name="soundcore_equalizer_band4">袚褉褍锌锌邪 4</string>
+    <string name="soundcore_equalizer_band5">袚褉褍锌锌邪 5</string>
+    <string name="soundcore_equalizer_band8">袚褉褍锌锌邪 8</string>
+    <string name="soundcore_equalizer_band9">袚褉褍锌锌邪 9</string>
+    <string name="miscale_weight_unit_imperial">袠屑锌械褉褋泻懈泄 (褎褍薪褌褘)</string>
+    <string name="pref_battery_polling_enable">袙泻谢褞褔懈褌褜 芯锌褉芯褋 斜邪褌邪褉械懈</string>
+    <string name="intent_api_broadcast_activity_sync_summary">袪邪褋褋褘谢泻邪 薪邪屑械褉械薪懈泄 锌褉懈 蟹邪胁械褉褕械薪懈懈 褋懈薪褏褉芯薪懈蟹邪褑懈懈 邪泻褌懈胁薪芯褋褌懈 写谢褟 谢褞斜芯谐芯 褍褋褌褉芯泄褋褌胁邪</string>
+    <string name="activity_detail_share_json_details">袩芯写械谢懈褌褜褋褟 锌芯写褉芯斜薪芯泄 懈薪褎芯褉屑邪褑懈械泄 JSON</string>
+    <string name="idasen_pref_stand_height">袙褘褋芯褌邪 锌芯谢芯卸械薪懈褟 褋褌芯泄泻懈 (胁 褋邪薪褌懈屑械褌褉邪褏)</string>
+    <string name="devicetype_oppo_enco_air2">Oppo Enco Air2</string>
+    <string name="devicetype_realme_buds_t110">Realme Buds T110</string>
+    <string name="vo2max_cycling">袙械谢芯褋锌芯褉褌 VO鈧� 袦邪泻褋</string>
+    <string name="redmi_buds_5_pro_anc_balanced">小斜邪谢邪薪褋懈褉芯胁邪薪薪褘泄</string>
+    <string name="redmi_buds_5_pro_anc_light">小胁械褌</string>
+    <string name="redmi_buds_5_pro_anc_deep">袚谢褍斜芯泻懈泄</string>
+    <string name="redmi_buds_5_pro_transparency_strength">袩褉芯褔薪芯褋褌褜 锌褉芯蟹褉邪褔薪芯褋褌懈</string>
+    <string name="redmi_buds_5_pro_transparency_regular">袨斜褘褔薪褘泄</string>
+    <string name="redmi_buds_5_pro_transparency_voice">校谢褍褔褕懈褌褜 谐芯谢芯褋邪</string>
+    <string name="redmi_buds_5_pro_transparency_ambient">校谢褍褔褕械薪懈械 芯泻褉褍卸邪褞褖懈褏 蟹胁褍泻芯胁</string>
+    <string name="redmi_buds_5_pro_combo_anc_off">ANC / 胁褘泻谢褞褔械薪</string>
+    <string name="redmi_buds_5_pro_combo_anc_transparency">ANC / 袩褉芯蟹褉邪褔薪芯褋褌褜</string>
+    <string name="redmi_buds_5_pro_combo_all">ANC / 袩褉芯蟹褉邪褔薪芯褋褌褜 / 袙褘泻谢褞褔械薪芯</string>
+    <string name="redmi_buds_5_pro_double_connection">袛胁芯泄薪芯械 褋芯械写懈薪械薪懈械</string>
+    <string name="redmi_buds_5_pro_double_connection_description">袩芯蟹胁芯谢褟械褌 薪邪褍褕薪懈泻邪屑 锌芯写泻谢褞褔邪褌褜褋褟 泻 写胁褍屑 褍褋褌褉芯泄褋褌胁邪屑 芯写薪芯胁褉械屑械薪薪芯</string>
+    <string name="redmi_buds_5_pro_adaptive_sound_description">袪械谐褍谢懈褉褍械褌 蟹胁褍泻 胁 蟹邪胁懈褋懈屑芯褋褌懈 芯褌 褎芯褉屑褘 褍褏邪 懈 芯泻褉褍卸邪褞褖械泄 褋褉械写褘</string>
+    <string name="redmi_buds_5_pro_equalizer_preset_standard">小褌邪薪写邪褉褌</string>
+    <string name="redmi_buds_5_pro_equalizer_preset_bass">校褋懈谢械薪懈械 斜邪褋邪</string>
+    <string name="redmi_buds_5_pro_equalizer_preset_voice">校谢褍褔褕械薪懈械 谐芯谢芯褋邪</string>
+    <string name="redmi_buds_5_pro_equalizer_band_250">250 袚褑</string>
+    <string name="redmi_buds_5_pro_equalizer_band_4k">4 泻袚褑</string>
+    <string name="redmi_buds_5_pro_equalizer_band_8k">8 泻袚褑</string>
+    <string name="redmi_buds_5_pro_equalizer_band_12k">12 泻袚褑</string>
+    <string name="soundcore_equalizer_custom_summary">袧邪褋褌褉芯泄泻邪 锌邪褉邪屑械褌褉芯胁 锌邪褉邪屑械褌褉懈褔械褋泻芯谐芯 褝泻胁邪谢邪泄蟹械褉邪</string>
+    <string name="soundcore_equalizer_band7">袚褉褍锌锌邪 7</string>
+    <string name="miscale_weight_unit_chinese">袣懈褌邪泄褋泻懈泄 (褑蟹懈薪褜)</string>
+    <string name="backup_restore_exporting_preferences">协泻褋锌芯褉褌 锌褉械写锌芯褔褌械薪懈泄鈥�</string>
+    <string name="target">笑械谢褜</string>
+    <string name="devicetype_oppo_enco_air">Oppo Enco Air</string>
+    <string name="min_respiration_rate">袦懈薪懈屑邪谢褜薪邪褟 褋泻芯褉芯褋褌褜 写褘褏邪薪懈褟</string>
+    <string name="body_energy">协薪械褉谐懈褟 褌械谢邪</string>
+    <string name="soundcore_voice_prompts">袚芯谢芯褋芯胁褘械 锌芯写褋泻邪蟹泻懈</string>
+    <string name="soundcore_equalizer_preset_voice">袚芯谢芯褋</string>
+    <string name="idasen_pref_sit_height">袙褘褋芯褌邪 锌芯谢芯卸械薪懈褟 褋懈写褟 (胁 褋邪薪褌懈屑械褌褉邪褏)</string>
+    <string name="pref_wear_sensor_summary">袨锌褉械写械谢懈褌械, 泻芯谐写邪 褍褋褌褉芯泄褋褌胁芯 薪械 薪邪写械褌芯</string>
+    <string name="pref_wear_sensor_title">袛邪褌褔懈泻 懈蟹薪芯褋邪</string>
+    <string name="activity_db_management_backup_restore_label">袪械蟹械褉胁薪芯械 泻芯锌懈褉芯胁邪薪懈械 懈 胁芯褋褋褌邪薪芯胁谢械薪懈械</string>
+    <string name="lactateThresholdHeartRate">袥邪泻褌邪褌薪褘泄 锌芯褉芯谐 效邪褋褌芯褌邪 褋械褉写械褔薪褘褏 褋芯泻褉邪褖械薪懈泄</string>
+    <string name="moondrop_touch_earbud">袧邪褍褕薪懈泻懈</string>
+    <string name="prefs_summary_gatt_client_notification_intents">袩芯谢褍褔械薪懈械 懈蟹屑械薪械薪懈泄 褏邪褉邪泻褌械褉懈褋褌懈泻 BLE 褔械褉械蟹 薪邪屑械褉械薪懈褟</string>
+    <string name="distance_avg">袪邪褋褋褌芯褟薪懈械 AVG</string>
+    <string name="pref_voice_passthrough_level">校褉芯胁械薪褜 锌褉芯褏芯写邪</string>
+    <string name="vo2max_running">袘械谐 VO鈧� 袦邪泻褋</string>
+    <string name="redmi_buds_5_pro_equalizer_band_125">125 袚褑</string>
+    <string name="redmi_buds_5_pro_equalizer_band_500">500 袚褑</string>
+    <string name="redmi_buds_5_pro_equalizer_band_1k">1 泻袚褑</string>
+    <string name="redmi_buds_5_pro_equalizer_band_2k">2 泻袚褑</string>
+    <string name="soundcore_equalizer_direction">袧邪锌褉邪胁谢械薪懈械 褍褋褌褉芯泄褋褌胁邪</string>
+    <string name="soundcore_equalizer_reset_title">小斜褉芯褋 薪邪褋褌褉芯械泻 锌芯 褍屑芯谢褔邪薪懈褞</string>
+    <string name="idasen_pref_mid_height">袙褘褋芯褌邪 褋褉械写薪械谐芯 锌芯谢芯卸械薪懈褟 (胁 褋邪薪褌懈屑械褌褉邪褏)</string>
+    <string name="steps_avg">楔邪谐懈 AVG</string>
+    <string name="devicetype_nothing_cmf_buds_pro_2">CMF Buds Pro 2</string>
+    <string name="weight_lbs">%1$.2f lbs</string>
+    <string name="soundcore_button_brightness">袣薪芯锌泻邪 携褉泻芯褋褌褜</string>
+    <string name="soundcore_equalizer_preset_signature">褋邪褍薪写泻芯褉 袩芯写锌懈褋褜</string>
+    <string name="soundcore_equalizer_direction_hanging">袩芯写胁械褋薪芯泄</string>
+    <string name="pref_deprecated_media_control_summary">袨褌锌褉邪胁谢褟泄褌械 泻芯屑邪薪写褘 褍锌褉邪胁谢械薪懈褟 屑褍谢褜褌懈屑械写懈邪 胁 胁懈写械 褋芯斜褘褌懈泄 泻谢邪胁懈褕 胁屑械褋褌芯 泻芯薪褌褉芯谢谢械褉邪 屑褍谢褜褌懈屑械写懈邪.</string>
+    <string name="activity_db_management_import_from_zip">袠屑锌芯褉褌 zip</string>
+    <string name="weight_kg">%1$.2f 泻谐</string>
+    <string name="soundcore_equalizer_band1">袚褉褍锌锌邪 1</string>
+    <string name="pref_deprecated_media_control_title">校褋褌邪褉械胁褕懈泄 褝谢械屑械薪褌 褍锌褉邪胁谢械薪懈褟 屑褍谢褜褌懈屑械写懈邪</string>
+    <string name="fmtPaceAverage">小褉械写薪懈泄 褌械屑锌</string>
+    <string name="pref_crash_notification_title">校胁械写芯屑谢械薪懈械 芯 褋斜芯械</string>
+    <string name="app_crash_notification_title">%1s 锌芯褌械褉锌械谢 泻褉邪褏</string>
+    <string name="app_crash_share_stacktrace">袨褕懈斜泻邪 芯斜屑械薪邪</string>
+    <string name="steps_total">楔邪谐懈 袙褋械谐芯</string>
+    <string name="hrZoneEasy">袥械谐泻芯</string>
+    <string name="hrZoneThreshold">袩芯褉芯谐</string>
+    <string name="hrv_sdrr">HRV SDRR</string>
+    <string name="moondrop_equalizer_preset_monitor">袦芯薪懈褌芯褉</string>
+    <string name="soundcore_equalizer_direction_lying">袥芯卸褜</string>
+    <string name="pref_app_connection_duration">袩褉芯写芯谢卸懈褌械谢褜薪芯褋褌褜 褋芯械写懈薪械薪懈褟 褋 锌褉懈谢芯卸械薪懈械屑</string>
+    <string name="hrZoneMaximum">袦邪泻褋懈屑褍屑</string>
+    <string name="redmi_buds_5_pro_equalizer_band_62">62 袚褑</string>
+    <string name="redmi_buds_5_pro_equalizer_band_16k">16 泻袚褑</string>
+    <string name="activity_db_management_export_to_zip">协泻褋锌芯褉褌 zip</string>
+    <string name="devicetype_colmi_r09">Colmi R09</string>
+    <string name="pref_voice_passthrough_enabled">袩褉芯褏芯写薪芯泄</string>
+    <string name="pref_voice_passthrough_enabled_summary">袩芯蟹胁芯谢褟褞褌 胁薪械褕薪懈屑 蟹胁褍泻邪屑 锌褉芯褏芯写懈褌褜 胁 褍褕懈</string>
+    <string name="redmi_buds_5_pro_equalizer_preset_custom">袩芯谢褜蟹芯胁邪褌械谢褜褋泻芯械</string>
+    <string name="soundcore_equalizer_reset_summary">袙械褉薪懈褌械 胁褋械 锌芯谢芯褋褘 褝泻胁邪谢邪泄蟹械褉邪 泻 薪邪褋褌褉芯泄泻邪屑 锌芯 褍屑芯谢褔邪薪懈褞</string>
+    <string name="soundcore_equalizer_band6">袚褉褍锌锌邪 6</string>
+    <string name="backup_restore_exporting">协泻褋锌芯褉褌 胁 zip鈥�</string>
+    <string name="workout_set_repetitions_weight_lbs">%1d x %2$.2f lbs</string>
+    <string name="soundcore_ldac_mode_title">袪械卸懈屑 LDAC</string>
+    <string name="soundcore_ldac_mode_summary">袙泻谢褞褔械薪懈械 LDAC 褋芯泻褉邪褌懈褌 胁褉械屑褟 褉邪斜芯褌褘 芯褌 邪泻泻褍屑褍谢褟褌芯褉邪 懈 屑芯卸械褌 锌褉懈胁械褋褌懈 泻 薪械褋褌邪斜懈谢褜薪芯褋褌懈 褋芯械写懈薪械薪懈褟</string>
+    <string name="soundcore_equalizer_direction_standing">小褌芯褟</string>
+    <string name="intent_api_broadcast_activity_sync_title">孝褉邪薪褋谢褟褑懈褟 芯 蟹邪胁械褉褕械薪懈懈 褋懈薪褏褉芯薪懈蟹邪褑懈懈 邪泻褌懈胁薪芯褋褌懈</string>
+    <string name="prefs_title_gatt_client_allow_gatt_interactions">袪邪蟹褉械褕懈褌褜 胁蟹邪懈屑芯写械泄褋褌胁懈械 褋 GATT 褔械褉械蟹 BLE Intent API</string>
+    <string name="activity_db_management_backup_restore_explanation">袨锌械褉邪褑懈懈 懈屑锌芯褉褌邪/褝泻褋锌芯褉褌邪 锌芯蟹胁芯谢褟褞褌 锌械褉械薪芯褋懈褌褜 懈谢懈 褋芯蟹写邪胁邪褌褜 褉械蟹械褉胁薪褘械 泻芯锌懈懈 胁褋械褏 薪邪褋褌褉芯械泻, 褍褋褌褉芯泄褋褌胁 懈 写邪薪薪褘褏 Gadgetbridge 胁 zip-褎邪泄谢 懈 懈蟹 薪械谐芯.\n\n袠屑锌芯褉褌 褎邪泄谢邪 锌褉懈胁械写械褌 泻 褍写邪谢械薪懈褞 胁褋械褏 褋褍褖械褋褌胁褍褞褖懈褏 写邪薪薪褘褏, 褍褋褌褉芯泄褋褌胁 懈 薪邪褋褌褉芯械泻, 锌芯谢薪芯褋褌褜褞 蟹邪屑械薪懈胁 懈褏 褉械蟹械褉胁薪芯泄 泻芯锌懈械泄.</string>
+    <string name="sleep_score">袨褑械薪泻邪 褋薪邪</string>
+    <string name="sleep_score_value">袨褑械薪泻邪: %1d</string>
+    <string name="devicetype_huawei_band2pro">Huawei Band 2 (Pro)</string>
+    <string name="hrv_rmssd">HRV RMSSD</string>
+    <string name="paceCorrection">袣芯褉褉械泻褑懈褟</string>
+    <string name="thirty_days_timeline">30-写薪械胁薪褘泄 谐褉邪褎懈泻</string>
+    <string name="redmi_buds_5_pro_combo_transparency_off">袩褉芯蟹褉邪褔薪芯褋褌褜 / 袙褘泻谢褞褔械薪芯</string>
+    <string name="pref_crash_notification_summary">袩褉懈 褋斜芯械 锌褉懈谢芯卸械薪懈褟 胁褘胁械写懈褌械 褍胁械写芯屑谢械薪懈械 褋 芯褕懈斜泻芯泄</string>
+    <string name="soundcore_equalizer_custom_title">袧邪褋褌褉邪懈胁邪泄褌械鈥�</string>
+    <string name="prefs_summary_gatt_client_allow_gatt_interactions">袩芯蟹胁芯谢褟械褌 芯褌锌褉邪胁谢褟褌褜 泻芯屑邪薪写褘 褔褌械薪懈褟/蟹邪锌懈褋懈 懈 锌芯写泻谢褞褔械薪懈褟 褏邪褉邪泻褌械褉懈褋褌懈泻 BLE</string>
+    <string name="redmi_buds_5_pro_adaptive_sound">袗写邪锌褌懈胁薪褘泄 蟹胁褍泻</string>
+    <string name="breaths_per_min">胁写芯褏懈/屑懈薪</string>
+    <string name="prefs_light_duration_longer">袘芯谢褜褕邪褟 锌褉芯写芯谢卸懈褌械谢褜薪芯褋褌褜 芯褋胁械褖械薪懈褟</string>
+    <string name="watchface_widget_type_sp02">SpO2</string>
+    <string name="redmi_buds_5_pro_equalizer_preset_treble">校褋懈谢械薪懈械 胁褘褋芯泻懈褏 褔邪褋褌芯褌</string>
+    <string name="pref_battery_polling_configuration">袣芯薪褎懈谐褍褉邪褑懈褟 芯锌褉芯褋邪 斜邪褌邪褉械懈</string>
+    <string name="soundcore_button_brightness_low">袧懈蟹泻懈泄</string>
+    <string name="soundcore_button_brightness_medium">小褉械写薪懈泄</string>
+    <string name="pref_developer_add_test_activities_title">袛芯斜邪胁褜褌械 褌械褋褌芯胁褘械 屑械褉芯锌褉懈褟褌懈褟</string>
+    <string name="pref_developer_add_test_activities_summary">袧邪锌芯谢薪懈褌械 斜邪蟹褍 写邪薪薪褘褏 褎懈泻褌懈胁薪褘屑懈 褌械褋褌芯胁褘屑懈 蟹邪写邪薪懈褟屑懈</string>
+    <string name="smart_ring_measurement_error_unknown">袧械懈蟹胁械褋褌薪邪褟 芯褕懈斜泻邪 懈蟹屑械褉械薪懈褟 %d, 锌芯谢褍褔械薪薪邪褟 芯褌 泻芯谢褜褑邪</string>
+    <string name="prefs_title_ble_intent_api">BLE Intent API</string>
+    <string name="pref_battery_polling_interval">袠薪褌械褉胁邪谢 芯锌褉芯褋邪 斜邪褌邪褉械懈</string>
+    <string name="prefs_title_gatt_client_notification_intents">袩械褉械写邪褔邪 褍胁械写芯屑谢械薪懈泄 芯 薪邪屑械褉械薪懈褟褏 GATT 褔械褉械蟹 BLE Intent API</string>
+    <string name="workout_set_reps">袩芯胁褌芯褉褘</string>
+    <string name="workoutSets">袧邪斜芯褉褘</string>
+    <string name="soundcore_equalizer_preset_xtra_bass">袘邪褋 Xtra</string>
+    <string name="distance_total">袪邪褋褋褌芯褟薪懈械 袙褋械谐芯</string>
+    <string name="workout_set_i">校褋褌邪薪芯胁懈褌褜 %1d</string>
+    <string name="workout_set_repetitions">%1d x</string>
+    <string name="workout_set_repetitions_weight_kg">%1d x %2$.2f 泻谐</string>
+    <string name="pref_continuous_skin_temperature_measurement_title">袧械锌褉械褉褘胁薪芯械 懈蟹屑械褉械薪懈械 褌械屑锌械褉邪褌褍褉褘 泻芯卸懈</string>
+    <string name="pref_header_deprecated_functionalities">校褌褉邪褔械薪薪褘械 褎褍薪泻褑懈芯薪邪谢褜薪褘械 胁芯蟹屑芯卸薪芯褋褌懈</string>
+    <string name="prefs_title_gatt_client_api_package">袩邪泻械褌 BLE API</string>
+    <string name="prefs_summary_gatt_client_api_package">袨谐褉邪薪懈褔褜褌械 胁蟹邪懈屑芯写械泄褋褌胁懈械 BLE Intent API 褝褌懈屑 锌邪泻械褌芯屑</string>
+    <string name="prefs_summary_gatt_client_device_state_updates">袩芯谢褍褔械薪懈械 懈蟹屑械薪械薪懈泄 褋芯褋褌芯褟薪懈褟 BLE-褋芯械写懈薪械薪懈褟 褔械褉械蟹 薪邪屑械褉械薪懈褟</string>
+    <string name="soundcore_adaptive_direction_title">袗写邪锌褌懈胁薪芯械 薪邪锌褉邪胁谢械薪懈械</string>
+    <string name="soundcore_equalizer_preset_balanced">小斜邪谢邪薪褋懈褉芯胁邪薪薪褘泄</string>
+    <string name="pref_header_deprecated_functionalities_warning">小谢械写褍褞褖懈械 褎褍薪泻褑懈懈 褍褋褌邪褉械谢懈 懈 斜褍写褍褌 褍写邪谢械薪褘 懈蟹 锌褉芯谐褉邪屑屑薪芯谐芯 芯斜械褋锌械褔械薪懈褟 胁 斜谢懈卸邪泄褕械械 胁褉械屑褟.\n\n袝褋谢懈 胁邪屑 薪褍卸薪芯 胁泻谢褞褔懈褌褜 芯写薪褍 懈蟹 褋谢械写褍褞褖懈褏 薪邪褋褌褉芯械泻, 芯斜褟蟹邪褌械谢褜薪芯 褋胁褟卸懈褌械褋褜 褋 泻芯屑邪薪写芯泄 锌褉芯械泻褌邪.</string>
+    <string name="devicetype_ble_gatt_client">袨斜褖懈泄 泻谢懈械薪褌 BLE GATT</string>
+    <string name="moondrop_equalizer_preset_basshead">袘邪褋褋芯谐芯谢芯胁褘泄</string>
+    <string name="moondrop_equalizer_preset_reference">小褋褘谢泻邪</string>
+    <string name="permission_bluetooth_scan_title">小泻邪薪懈褉芯胁邪薪懈械 锌芯 Bluetooth</string>
+    <string name="permission_bluetooth_admin_title">袗写屑懈薪懈褋褌褉邪褌芯褉 Bluetooth</string>
+    <string name="permission_bluetooth_connect_summary">袩芯写泻谢褞褔械薪懈械 泻 褍卸械 褋芯锌褉褟卸械薪薪褘屑 褍褋褌褉芯泄褋褌胁邪屑 Bluetooth</string>
+    <string name="permission_contacts_title">袣芯薪褌邪泻褌褘</string>
+    <string name="permission_contacts_summary">袨褌锌褉邪胁泻邪 泻芯薪褌邪泻褌芯胁 薪邪 谐邪写卸械褌褘</string>
+    <string name="permission_send_sms_title">袨褌锌褉邪胁懈褌褜 SMS</string>
+    <string name="permission_send_sms_summary">袨褌锌褉邪胁泻邪 SMS (泻芯薪褋械褉胁懈褉芯胁邪薪薪褘泄 芯褌胁械褌) 褋 谐邪写卸械褌芯胁</string>
+    <string name="permission_read_call_log_summary">袩械褉械邪写褉械褋邪褑懈褟 卸褍褉薪邪谢邪 胁褘蟹芯胁芯胁 薪邪 谐邪写卸械褌褘</string>
+    <string name="permission_read_phone_state_title">小褔懈褌褘胁邪薪懈械 褋芯褋褌芯褟薪懈褟 褌械谢械褎芯薪邪</string>
+    <string name="pref_dashboard_widget_today_yesterday_data_summary">袩芯泻邪蟹褘胁邪褌褜 写邪薪薪褘械 蟹邪 胁褔械褉邪褕薪懈泄 写械薪褜, 蟹邪褌械屑薪械薪薪褘械 屑械卸写褍 褌械泻褍褖懈屑 胁褉械屑械薪械屑 懈 锌芯谢褍薪芯褔褜褞</string>
+    <string name="prefs_hrv_monitoring_title">袦芯薪懈褌芯褉懈薪谐 HRV</string>
+    <string name="inactivity_warnings_minimum_steps_summary">袦懈薪懈屑邪谢褜薪芯械 泻芯谢懈褔械褋褌胁芯 褕邪谐芯胁, 泻芯褌芯褉芯械 薪械芯斜褏芯写懈屑芯 褋写械谢邪褌褜 胁 褌械褔械薪懈械 锌芯褉芯谐芯胁褘褏 屑懈薪褍褌</string>
+    <string name="backup_restore_exporting_files">协泻褋锌芯褉褌 褎邪泄谢芯胁鈥�</string>
+    <string name="backup_restore_exporting_files_i_of_n">协泻褋锌芯褉褌 褎邪泄谢芯胁... %1d 懈蟹 %2d</string>
+    <string name="backup_restore_exporting_finishing">袟邪胁械褉褕械薪懈械 褝泻褋锌芯褉褌邪鈥�</string>
+    <string name="backup_restore_importing">袠屑锌芯褉褌 懈蟹 zip鈥�</string>
+    <string name="backup_restore_importing_files_i_of_n">袠屑锌芯褉褌 褎邪泄谢芯胁... %1d 懈蟹 %2d</string>
+    <string name="backup_restore_importing_database">袠屑锌芯褉褌 斜邪蟹褘 写邪薪薪褘褏鈥�</string>
+    <string name="pref_dashboard_widget_today_time_indicator_title">袠薪写懈泻邪褌芯褉 褌械泻褍褖械谐芯 胁褉械屑械薪懈</string>
+    <string name="music_huawei_device_info">袩芯写写械褉卸懈胁邪械屑褘械 褎芯褉屑邪褌褘: %1$s\n\n袨斜褗械屑 锌邪屑褟褌懈: %2$d 袦袘</string>
+    <string name="first_start_open_source_title">袨褌泻褉褘褌褘泄 懈褋褏芯写薪褘泄 泻芯写</string>
+    <string name="permission_manage_dnd_summary">袠蟹屑械薪械薪懈械 锌芯谢懈褌懈泻懈 褍胁械写芯屑谢械薪懈泄 DND</string>
+    <string name="permission_fine_location_summary">小泻邪薪懈褉芯胁邪薪懈械 写谢褟 褍褋褌褉芯泄褋褌胁 Bluetooth</string>
+    <string name="permission_background_location_title">肖芯薪芯胁芯械 褉邪褋锌芯谢芯卸械薪懈械</string>
+    <string name="permission_displayover_summary">袠褋锌芯谢褜蟹褍械褌褋褟 Bangle.js 写谢褟 蟹邪锌褍褋泻邪 锌褉懈谢芯卸械薪懈泄 懈 写褉褍谐懈褏 褎褍薪泻褑懈泄 薪邪 胁邪褕械屑 褌械谢械褎芯薪械</string>
+    <string name="permission_post_notification_title">袩芯褔褌芯胁褘械 褍胁械写芯屑谢械薪懈褟</string>
+    <string name="permission_post_notification_summary">袪邪蟹屑械褖械薪懈械 锌芯褋褌芯褟薪薪褘褏 褍胁械写芯屑谢械薪懈泄, 泻芯褌芯褉褘械 锌芯写写械褉卸懈胁邪褞褌 褉邪斜芯褌褍 褋谢褍卸斜褘</string>
+    <string name="permission_internet_access_title">袛芯褋褌褍锌 胁 袠薪褌械褉薪械褌</string>
+    <string name="permission_internet_access_summary">小懈薪褏褉芯薪懈蟹邪褑懈褟 褋 芯薪谢邪泄薪-褉械褋褍褉褋邪屑懈</string>
+    <string name="permission_calendar_summary">袨褌锌褉邪胁泻邪 泻邪谢械薪写邪褉褟 薪邪 谐邪写卸械褌褘</string>
+    <string name="permission_receive_sms_title">袩芯谢褍褔械薪懈械 SMS</string>
+    <string name="permission_receive_sms_summary">袩械褉械褋褘谢泻邪 SMS-褋芯芯斜褖械薪懈泄 薪邪 谐邪写卸械褌褘</string>
+    <string name="permission_read_call_log_title">效褌械薪懈械 卸褍褉薪邪谢邪 胁褘蟹芯胁芯胁</string>
+    <string name="permission_call_phone_summary">袠薪懈褑懈懈褉芯胁邪薪懈械 褌械谢械褎芯薪薪褘褏 蟹胁芯薪泻芯胁 褋 谐邪写卸械褌芯胁</string>
+    <string name="label_distance_total_mph">袠褌芯谐芯: %.1f 屑懈谢褜</string>
+    <string name="error_no_cycling_sensor_found">袛邪褌褔懈泻 褑懈泻谢懈褔薪芯褋褌懈 薪械 芯斜薪邪褉褍卸械薪</string>
+    <string name="prefs_hrv_monitoring_description">袗胁褌芯屑邪褌懈褔械褋泻懈泄 屑芯薪懈褌芯褉懈薪谐 胁邪褉懈邪斜械谢褜薪芯褋褌懈 褋械褉写械褔薪芯谐芯 褉懈褌屑邪 胁 褌械褔械薪懈械 写薪褟</string>
+    <string name="backup_restore_restart_summary">孝械锌械褉褜 %1s 斜褍写械褌 锌械褉械蟹邪锌褍褖械薪.</string>
+    <string name="permission_read_phone_state_summary">效褌械薪懈械 褋褌邪褌褍褋邪 褌械泻褍褖懈褏 胁褘蟹芯胁芯胁</string>
+    <string name="permission_notifications_summary">袩械褉械褋褘谢泻邪 褍胁械写芯屑谢械薪懈泄 薪邪 锌芯写泻谢褞褔械薪薪褘械 谐邪写卸械褌褘</string>
+    <string name="permission_fine_location_title">袩褉械泻褉邪褋薪芯械 褉邪褋锌芯谢芯卸械薪懈械</string>
+    <string name="permission_bluetooth_title">Bluetooth</string>
+    <string name="permission_bluetooth_summary">袩芯写泻谢褞褔械薪懈械 泻 褍褋褌褉芯泄褋褌胁邪屑 Bluetooth</string>
+    <string name="permission_process_outgoing_calls_title">袨斜褉邪斜芯褌泻邪 懈褋褏芯写褟褖懈褏 胁褘蟹芯胁芯胁</string>
+    <string name="permission_process_outgoing_calls_summary">效褌械薪懈械 薪芯屑械褉邪 懈褋褏芯写褟褖械谐芯 胁褘蟹芯胁邪 写谢褟 芯褌芯斜褉邪卸械薪懈褟 械谐芯 薪邪 谐邪写卸械褌械</string>
+    <string name="permission_answer_phone_calls_title">袨褌胁械褔邪泄褌械 薪邪 褌械谢械褎芯薪薪褘械 蟹胁芯薪泻懈</string>
+    <string name="permission_answer_phone_calls_summary">袨褌胁械褌褘 薪邪 褌械谢械褎芯薪薪褘械 蟹胁芯薪泻懈 褋 谐邪写卸械褌芯胁</string>
+    <string name="permission_external_storage_title">袙薪械褕薪械械 褏褉邪薪懈谢懈褖械</string>
+    <string name="permission_external_storage_summary">袠褋锌芯谢褜蟹芯胁邪薪懈械 懈蟹芯斜褉邪卸械薪懈泄, 屑械谢芯写懈泄, 褎邪泄谢芯胁 锌褉懈谢芯卸械薪懈泄 懈 屑薪芯谐芯谐芯 写褉褍谐芯谐芯</string>
+    <string name="first_start_overview_desc">袙 袚邪写卸械褌斜褉懈写卸械 械褋褌褜 写胁邪 芯褋薪芯胁薪褘褏 胁懈写邪, 泻邪卸写褘泄 懈蟹 泻芯褌芯褉褘褏 懈屑械械褌 褋胁芯械 锌褉械写薪邪蟹薪邪褔械薪懈械.</string>
+    <string name="first_start_get_started_add_first_device_button">袛芯斜邪胁褜褌械 锌械褉胁芯械 褍褋褌褉芯泄褋褌胁芯</string>
+    <string name="music_delete_multiple_confirm_description">袙褘 褍胁械褉械薪褘, 褔褌芯 褏芯褌懈褌械 褍写邪谢懈褌褜 \'%1$d\' 锌械褋械薪?</string>
+    <string name="first_start_overview_dashboard">袩褉懈斜芯褉薪邪褟 锌邪薪械谢褜 锌芯蟹胁芯谢褟械褌 斜褘褋褌褉芯 锌芯谢褍褔懈褌褜 锌褉械写褋褌邪胁谢械薪懈械 芯 褌芯屑, 泻邪泻 芯斜褋褌芯褟褌 胁邪褕懈 写械谢邪 薪邪 褋械谐芯写薪褟褕薪懈泄 写械薪褜. 袙 泻邪谢械薪写邪褉械 芯褌芯斜褉邪卸邪械褌褋褟 褋芯褋褌芯褟薪懈械 胁邪褕懈褏 褑械谢械泄 蟹邪 褑械谢褘泄 屑械褋褟褑.</string>
+    <string name="first_start_overview_devices">袙 芯泻薪械 褍褋褌褉芯泄褋褌胁 芯褌芯斜褉邪卸邪褞褌褋褟 胁褋械 薪邪褋褌褉芯械薪薪褘械 褍褋褌褉芯泄褋褌胁邪 懈 懈褏 褋芯褋褌芯褟薪懈械, 邪 褌邪泻卸械 写芯褋褌褍锌 泻 褎褍薪泻褑懈褟屑 泻芯薪泻褉械褌薪芯谐芯 褍褋褌褉芯泄褋褌胁邪, 褌邪泻懈屑 泻邪泻 锌芯写褉芯斜薪褘械 谐褉邪褎懈泻懈, 薪邪褋褌褉芯泄泻懈, 锌褉懈谢芯卸械薪懈褟 懈 褋懈谐薪邪谢褘 褌褉械胁芯谐懈.</string>
+    <string name="first_start_permissions_request_button">袟邪锌褉芯褋</string>
+    <string name="first_start_get_started_desc">效褌芯斜褘 薪邪褔邪褌褜 褉邪斜芯褌褍, 写芯斜邪胁褜褌械 褋胁芯械 锌械褉胁芯械 褍褋褌褉芯泄褋褌胁芯 锌褉褟屑芯 褋 褝褌芯谐芯 褝泻褉邪薪邪, 胁芯褋褋褌邪薪芯胁懈褌械 褉械蟹械褉胁薪褍褞 泻芯锌懈褞 懈谢懈 薪邪褔薪懈褌械 褋 褔懈褋褌芯泄 斜邪蟹褘 写邪薪薪褘褏.</string>
+    <string name="first_start_get_started_title">袧邪褔邪褌褜</string>
+    <string name="permission_displayover_title">袨褌芯斜褉邪卸械薪懈械 锌芯胁械褉褏 写褉褍谐懈褏 锌褉懈谢芯卸械薪懈泄</string>
+    <string name="permission_background_location_summary">小泻邪薪懈褉芯胁邪薪懈械 褍褋褌褉芯泄褋褌胁 Bluetooth 胁 褎芯薪芯胁芯屑 褉械卸懈屑械 懈 芯褌锌褉邪胁泻邪 写邪薪薪褘褏 芯 屑械褋褌芯锌芯谢芯卸械薪懈懈 薪邪 芯锌褉械写械谢械薪薪褘械 谐邪写卸械褌褘</string>
+    <string name="permission_bluetooth_admin_summary">袨斜薪邪褉褍卸械薪懈械 懈 褋芯锌褉褟卸械薪懈械 褍褋褌褉芯泄褋褌胁 Bluetooth</string>
+    <string name="permission_calendar_title">袣邪谢械薪写邪褉褜</string>
+    <string name="permission_call_phone_title">袩芯蟹胁芯薪懈褌械 锌芯 褌械谢械褎芯薪褍</string>
+    <string name="permission_query_all_packages_title">袟邪锌褉芯褋 胁褋械褏 锌邪泻械褌芯胁</string>
+    <string name="first_start_welcome_title">袛芯斜褉芯 锌芯卸邪谢芯胁邪褌褜</string>
+    <string name="first_start_intro_welcome_to">袛芯斜褉芯 锌芯卸邪谢芯胁邪褌褜 胁</string>
+    <string name="music_error">袩褉芯懈蟹芯褕谢邪 芯褕懈斜泻邪</string>
+    <string name="first_start_get_started_restore_button">袙芯褋褋褌邪薪芯胁谢械薪懈械 褉械蟹械褉胁薪芯泄 泻芯锌懈懈</string>
+    <string name="first_start_get_started_go_to_app_button">袩械褉械泄写懈褌械 胁 锌褉懈谢芯卸械薪懈械</string>
+    <string name="music_multiselect_limit">袥懈屑懈褌 薪邪 胁褘斜芯褉 斜芯谢械械 褔械屑 芯写薪芯谐芯 褌芯胁邪褉邪 写芯褋褌懈谐薪褍褌</string>
+    <string name="backup_restore_do_not_exit">%s 袩芯卸邪谢褍泄褋褌邪, 写械褉卸懈褌械 褝褌芯褌 褝泻褉邪薪 芯褌泻褉褘褌褘屑 写芯 蟹邪胁械褉褕械薪懈褟 芯锌械褉邪褑懈懈.</string>
+    <string name="backup_restore_importing_loading">袟邪谐褉褍蟹泻邪 褎邪泄谢邪鈥�</string>
+    <string name="backup_restore_importing_validating">袩褉芯胁械褉泻邪 褎邪泄谢邪鈥�</string>
+    <string name="backup_restore_warning_files">袧械 褍写邪谢芯褋褜 胁芯褋褋褌邪薪芯胁懈褌褜 %1d 褎邪泄谢芯胁: \n\n%2s</string>
+    <string name="backup_restore_abort_import_confirmation">袩褉械褉胁邪褌褜 懈屑锌芯褉褌? 协褌芯 屑芯卸械褌 锌褉懈胁械褋褌懈 泻 锌芯胁褉械卸写械薪懈褞 懈谢懈 薪械褋芯谐谢邪褋芯胁邪薪薪芯褋褌懈 斜邪蟹褘 写邪薪薪褘褏.</string>
+    <string name="backup_restore_import_complete">袠屑锌芯褉褌 蟹邪胁械褉褕械薪</string>
+    <string name="backup_restore_error_export">协泻褋锌芯褉褌 胁 zip 薪械 褍写邪谢褋褟</string>
+    <string name="backup_restore_export_complete">协泻褋锌芯褉褌 蟹邪胁械褉褕械薪</string>
+    <string name="backup_restore_error_import">袠屑锌芯褉褌 懈蟹 zip 薪械 褍写邪谢褋褟</string>
+    <string name="backup_restore_abort_title">袩褉械褉胁邪褌褜</string>
+    <string name="backup_restore_abort_export_confirmation">袩褉械褉胁邪褌褜 褝泻褋锌芯褉褌? 效邪褋褌懈褔薪褘泄 zip-褎邪泄谢 斜褍写械褌 褍写邪谢械薪.</string>
+    <string name="backup_restore_restart_title">袩械褉械蟹邪锌褍褋褌懈褌械</string>
+    <string name="label_distance_trip">袩芯械蟹写泻邪: %.1f 泻屑</string>
+    <string name="pref_dashboard_widget_today_time_indicator_summary">袩芯泻邪蟹褘胁邪泄褌械 懈薪写懈泻邪褌芯褉 胁 褌械泻褍褖械屑 胁褉械屑械薪懈, 褔褌芯斜褘 胁懈蟹褍邪谢褜薪芯 褉邪蟹写械谢懈褌褜 写邪薪薪褘械 蟹邪 胁褔械褉邪褕薪懈泄 懈 褋械谐芯写薪褟褕薪懈泄 写械薪褜</string>
+    <string name="first_start_permissions_request_all_button">袟邪锌褉芯褋懈褌械 胁褋械 褉邪蟹褉械褕械薪懈褟</string>
+    <string name="permission_bluetooth_scan_summary">小泻邪薪懈褉芯胁邪薪懈械 薪邪 薪邪谢懈褔懈械 薪芯胁褘褏 褍褋褌褉芯泄褋褌胁 Bluetooth</string>
+    <string name="permission_bluetooth_connect_title">袩芯写泻谢褞褔械薪懈械 Bluetooth</string>
+    <string name="permission_query_all_packages_summary">效褌械薪懈械 薪邪蟹胁邪薪懈泄 懈 蟹薪邪褔泻芯胁 胁褋械褏 褍褋褌邪薪芯胁谢械薪薪褘褏 锌褉懈谢芯卸械薪懈泄</string>
+    <string name="pref_dashboard_widget_today_yesterday_data_title">袛邪薪薪褘械 蟹邪 胁褔械褉邪褕薪懈泄 写械薪褜</string>
+    <string name="inactivity_warnings_minimum_steps_title">袦懈薪懈屑邪谢褜薪芯械 泻芯谢懈褔械褋褌胁芯 褕邪谐芯胁</string>
+    <string name="backup_restore_importing_preferences">袠屑锌芯褉褌 锌褉械写锌芯褔褌械薪懈泄鈥�</string>
+    <string name="first_start_permissions_desc">Gadgetbridge 薪褍卸写邪械褌褋褟 胁 斜芯谢褜褕芯屑 泻芯谢懈褔械褋褌胁械 褉邪蟹褉械褕械薪懈泄 写谢褟 胁褘锌芯谢薪械薪懈褟 胁褋械褏 褋胁芯懈褏 褎褍薪泻褑懈泄. 袨蟹薪邪泻芯屑褜褌械褋褜 褋 褝褌懈屑懈 褉邪蟹褉械褕械薪懈褟屑懈 懈 懈褏 薪邪蟹薪邪褔械薪懈械屑 薪懈卸械.</string>
+    <string name="first_start_intro_tag_line">袨褋胁芯斜芯写懈褌械褋褜 芯褌 锌褉芯锌褉懈械褌邪褉薪褘褏 锌褉懈谢芯卸械薪懈泄 懈 芯斜谢邪褔薪褘褏 褋械褉胁懈褋芯胁 锌褉芯懈蟹胁芯写懈褌械谢械泄 谐邪写卸械褌芯胁.</string>
+    <string name="backup_restore_exporting_database">协泻褋锌芯褉褌 斜邪蟹褘 写邪薪薪褘褏鈥�</string>
+    <string name="pref_dnd_follow_phone_summary">袝褋谢懈 DND 胁泻谢褞褔械薪 懈谢懈 芯褌泻谢褞褔械薪 薪邪 褌械谢械褎芯薪械, 邪胁褌芯屑邪褌懈褔械褋泻懈 锌械褉械泻谢褞褔懈褌械 械谐芯 懈 薪邪 褍褋褌褉芯泄褋褌胁械</string>
+    <string name="first_start_open_source_text">Gadgetbridge - 褝褌芯 锌褉懈谢芯卸械薪懈械 褋 芯褌泻褉褘褌褘屑 懈褋褏芯写薪褘屑 泻芯写芯屑. 袨薪芯 褉邪蟹褉邪斜邪褌褘胁邪械褌褋褟 褋芯芯斜褖械褋褌胁芯屑 写谢褟 褋芯芯斜褖械褋褌胁邪.\n\n袥褞斜芯泄 卸械谢邪褞褖懈泄 屑芯卸械褌 胁薪械褋褌懈 褋胁芯泄 胁泻谢邪写 胁 褋芯蟹写邪薪懈械 泻芯写邪, 写芯泻褍屑械薪褌邪褑懈懈, 褌械褋褌懈褉芯胁邪薪懈械 懈 锌芯卸械褉褌胁芯胁邪薪懈褟.\n\nGadgetbridge 薪械 褋芯写械褉卸懈褌 褉械泻谢邪屑褘 懈 褋谢械卸械薪懈褟. 袨薪 褏褉邪薪懈褌 胁邪褕懈 写邪薪薪褘械 谢芯泻邪谢褜薪芯 薪邪 胁邪褕械屑 Android-褍褋褌褉芯泄褋褌胁械, 锌芯褝褌芯屑褍 芯薪 薪邪 100% 斜械蟹芯锌邪褋械薪 写谢褟 泻芯薪褎懈写械薪褑懈邪谢褜薪芯褋褌懈.\n\n袩芯褋械褌懈褌械 薪邪褕 褋邪泄褌, 褔褌芯斜褘 锌芯谢褍褔懈褌褜 写芯锌芯谢薪懈褌械谢褜薪褍褞 懈薪褎芯褉屑邪褑懈褞, 写芯泻褍屑械薪褌邪褑懈褞 懈 褋褋褘谢泻懈 薪邪 薪邪褕懈 泻邪薪邪谢褘 褋胁褟蟹懈.</string>
+    <string name="label_distance_total">袠褌芯谐芯: %.1f 泻屑</string>
+    <string name="label_distance_trip_mph">袩芯械蟹写泻邪: %.1f 屑懈谢褜</string>
+</resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml
new file mode 100644
index 0000000..59d7c36
--- /dev/null
+++ b/app/src/main/res/values-sk/strings.xml
@@ -0,0 +1,566 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources xmlns:tools="http://schemas.android.com/tools">
+    <string name="application_name_generic">Gadgetbridge</string>
+    <string name="title_activity_controlcenter_generic">Gadgetbridge</string>
+    <string name="action_settings">Nastavenia</string>
+    <string name="action_debug">Ladenie</string>
+    <string name="action_quit">Ukon膷i钮</string>
+    <string name="action_donate">Podpori钮</string>
+    <string name="controlcenter_fetch_activity_data">Na膷铆ta钮 d谩ta</string>
+    <string name="controlcenter_find_device">N谩js钮 zariadenie</string>
+    <string name="controlcenter_take_screenshot">Urobi钮 sn铆mku obrazovky</string>
+    <string name="controlcenter_disconnect">Odpoji钮</string>
+    <string name="controlcenter_delete_device">Odstr谩ni钮 zariadenie</string>
+    <string name="controlcenter_delete_device_name">Odstr谩ni钮 %1$s</string>
+    <string name="controlcenter_delete_device_dialogmessage">Odstr谩ni zariadenie a vyma啪e v拧etky d谩ta!</string>
+    <string name="controlcenter_navigation_drawer_open">Otvori钮 naviga膷n煤 li拧tu</string>
+    <string name="controlcenter_navigation_drawer_close">Zavrie钮 naviga膷n煤 li拧tu</string>
+    <string name="controlcenter_snackbar_need_longpress">Podr啪te kartu dlh拧ie pre odpojenie</string>
+    <string name="controlcenter_snackbar_disconnecting">Odp谩janie</string>
+    <string name="controlcenter_snackbar_connecting">Prip谩janie</string>
+    <string name="controlcenter_snackbar_requested_screenshot">Vytvorenie sn铆mky obrazovky zariadenia</string>
+    <string name="title_activity_debug">Ladenie</string>
+    <string name="title_activity_appmanager">Mana啪茅r aplik谩ci铆</string>
+    <string name="appmanager_cached_watchapps_watchfaces">Aplik谩cie v cache</string>
+    <string name="appmanager_installed_watchapps">Nain拧talovan茅 aplik谩cie</string>
+    <string name="appmanager_installed_watchfaces">Nain拧talovan茅 cifern铆ky</string>
+    <string name="appmananger_app_delete">Odstr谩ni钮</string>
+    <string name="appmananger_app_delete_cache">Odstr谩ni钮 z cache</string>
+    <string name="appmananger_app_reinstall">Nain拧talova钮 znovu</string>
+    <string name="appmanager_app_openinstore">H木ada钮 v Pebble Appstore</string>
+    <string name="appmanager_health_activate">Aktivova钮</string>
+    <string name="appmanager_health_deactivate">Deaktivova钮</string>
+    <string name="appmanager_hrm_activate">Zapn煤钮 meranie tepu</string>
+    <string name="appmanager_hrm_deactivate">Vypn煤钮 meranie tepu</string>
+    <string name="appmanager_weather_activate">Aktivova钮 syst茅mov煤 aplik谩ciu pre po膷asie</string>
+    <string name="appmanager_weather_deactivate">Deaktivova钮 syst茅mov煤 aplik谩ciu pre po膷asie</string>
+    <string name="appmanager_weather_install_provider">Nain拧talova钮 aplik谩ciu pre upozornenia na po膷asie</string>
+    <string name="app_configure">Nastavi钮</string>
+    <string name="app_move_to_top">Posun煤钮 hore</string>
+    <string name="title_activity_calblacklist">Zak谩zan茅 upozornenia Kalend谩ra</string>
+    <string name="title_activity_fw_app_insaller">In拧tal谩tor FW/Apl</string>
+    <string name="fw_upgrade_notice">Nain拧taluje firmware %s do Mi Band namiesto existuj煤ceho.</string>
+    <string name="fw_upgrade_notice_amazfitbip">Chyst谩te sa nain拧talova钮 firmv茅r %s na va拧om Amazfit Bip.
+\n
+\nPros铆m nezabudnite nain拧talova钮 s煤bor .fw, potom s煤bor .res a nakoniec s煤bor .gps. Va拧e hodinky sa po nain拧talovan铆 s煤boru .fw re拧tartuj煤.
+\n
+\nPozn谩mka: Nemus铆te in拧talova钮 .res a .gps, ak s煤 tieto s煤bory presne rovnak茅 ako tie, ktor茅 boli nain拧talovan茅 sk么r.
+\n
+\nROB脥TE TO NA VLASTN脡 RIZIKO!</string>
+    <string name="fw_upgrade_notice_amazfitcor">Chyst谩te sa nain拧talova钮 firmv茅r %s do v谩拧ho Amazfit Cor.
+\n
+\nPros铆m, uistite sa, 啪e ste nain拧talovali s煤bor .fw a potom s煤bor .res. Va拧e hodinky sa po in拧tal谩cii s煤boru .fw re拧tartuj煤.
+\n
+\nPozn谩mka: Nemus铆te in拧talova钮 s煤bor .res, ak je rovnak媒 ako nain拧talovan媒 predt媒m.
+\n
+\nNETESTOVAN脡, M脭沤E UM艛TVI扭 VA艩E ZARIADENIE, ROB脥TE TO NA VLASTN脡 RIZIKO!</string>
+    <string name="fw_multi_upgrade_notice">Nain拧taluje firmv茅r %1$s a %2$s do Mi Band miesto existuj煤ceho.</string>
+    <string name="miband_firmware_known">Tento firmware bol testovan媒 a je kompatibiln媒 s Gadgetbridge.</string>
+    <string name="miband_firmware_unknown_warning">Tento firmware nebol testovan媒 a nemus铆 by钮 kompatibiln媒 s Gadgetbridge.
+\n
+\nNedoporu膷ujeme ho nahr谩va钮 do Mi Band!</string>
+    <string name="miband_firmware_suggest_whitelist">Ak budete pokra膷ova钮 a v拧etko bude dobre fungova钮 aj potom, upozornite v媒voj谩rov Gadgetbridge, aby ozna膷ili tento firmv茅r: %s ako vysk煤拧an媒</string>
+    <string name="title_activity_settings">Nastavenia</string>
+    <string name="pref_header_general">Z谩kladn茅 nastavenia</string>
+    <string name="pref_title_general_autoconnectonbluetooth">Pripoji钮 zariadenie ak je BT zapnut媒</string>
+    <string name="pref_title_general_autostartonboot">Sp煤拧钮a钮 automaticky</string>
+    <string name="pref_title_general_autoreconnect">Automatick茅 op盲tovn茅 pripojenie</string>
+    <string name="pref_title_audio_player">Preferovan媒 prehr谩va膷 zvukov</string>
+    <string name="pref_default">Predvolen茅</string>
+    <string name="pref_title_charts_swipe">Zap. potiahnutie v木avo/vpravo v grafoch aktiv铆t</string>
+    <string name="pref_header_datetime">D谩tum a 膷as</string>
+    <string name="pref_title_datetime_syctimeonconnect">Synchronizova钮 膷as pri pripojen铆</string>
+    <string name="pref_summary_datetime_syctimeonconnect">Synchronizova钮 膷as pri pripojen铆 k zariadeniu a aj pri zmene 膷asu alebo 膷asov茅ho p谩sma v Androide</string>
+    <string name="pref_title_theme">T茅ma aplik谩cie</string>
+    <string name="pref_theme_light">Svetl谩</string>
+    <string name="pref_theme_dark">Tmav谩</string>
+    <string name="pref_title_language">Jazyk</string>
+    <string name="pref_title_minimize_priority">Skry钮 upozornenia Gadgetbridge</string>
+    <string name="pref_summary_minimize_priority_off">Zobrazi钮 ikonu v stavovom riadku a upozornenia na uzamknutej obrazovke</string>
+    <string name="pref_summary_minimize_priority_on">V stavovom riadku na obrazovke uzamknutia nebude zobrazen谩 ikona a upozornenie</string>
+    <string name="pref_header_notifications">Upozornenia</string>
+    <string name="pref_title_notifications_repetitions">Opakovanie</string>
+    <string name="pref_title_notifications_call">Hovory</string>
+    <string name="pref_title_notifications_sms">SMS</string>
+    <string name="pref_title_notifications_pebblemsg">Spr谩vy Pebble</string>
+    <string name="pref_summary_notifications_pebblemsg">Podpora pre aplik谩cie, ktor茅 posielaj煤 upozornenia do hodiniek Pebble cez PebbleKit.</string>
+    <string name="pref_title_notifications_generic">Pr铆stup k upozorneniam</string>
+    <string name="pref_title_whenscreenon">Upozornenia aj pri zapnutej obrazovke</string>
+    <string name="pref_title_notification_filter">Re啪im Neru拧i钮</string>
+    <string name="pref_summary_notification_filter">Zak谩za钮 odosielanie nechcen媒ch upozornen铆 v re啪ime Neru拧i钮</string>
+    <string name="pref_title_transliteration">N谩hrada diakritiky</string>
+    <string name="pref_summary_transliteration">Toto zapnite, ak va拧e zariadenie nepodporuje p铆sma (znaky s diakritikou) pre v谩s jazyk</string>
+    <string name="always">V啪dy</string>
+    <string name="when_screen_off">Pri vypnutej obrazovke</string>
+    <string name="never">Nikdy</string>
+    <string name="pref_header_privacy">S煤kromie</string>
+    <string name="pref_title_call_privacy_mode">Ochrana s煤kromia pri hovore</string>
+    <string name="pref_call_privacy_mode_off">Zobrazi钮 meno a 膷铆slo</string>
+    <string name="pref_call_privacy_mode_name">Skry钮 meno, zobrazi钮 膷铆slo</string>
+    <string name="pref_call_privacy_mode_number">Skry钮 膷铆slo, zobrazi钮 meno</string>
+    <string name="pref_call_privacy_mode_complete">Skry钮 meno aj 膷铆slo</string>
+    <string name="pref_title_weather">Po膷asie</string>
+    <string name="pref_title_weather_location">Poloha pre po膷asie (CM/LOS)</string>
+    <string name="pref_blacklist_calendars">Zak谩zan茅 kalend谩re</string>
+    <string name="pref_header_cannned_messages">Potla膷en茅 spr谩vy</string>
+    <string name="pref_title_canned_replies">Odpovede</string>
+    <string name="pref_title_canned_reply_suffix">Be啪n谩 pr铆pona</string>
+    <string name="pref_title_canned_messages_dismisscall">Zamietnut茅 hovory</string>
+    <string name="pref_title_canned_messages_set">Aktualizova钮 na Pebble</string>
+    <string name="pref_header_development">V媒voj谩rske nastavenia</string>
+    <string name="pref_title_development_miaddr">Adresa Mi Band</string>
+    <string name="pref_title_pebble_settings">Nastavenia Pebble</string>
+    <string name="pref_header_activitytrackers">Sledova膷e aktivity</string>
+    <string name="pref_title_pebble_activitytracker">Preferovan媒 sledova膷 aktivity</string>
+    <string name="pref_title_pebble_sync_health">Synchronizova钮 Pebble Health</string>
+    <string name="pref_title_pebble_sync_misfit">Synchronizova钮 Misfit</string>
+    <string name="pref_title_pebble_sync_morpheuz">Synchronizova钮 Morpheuz</string>
+    <string name="pref_title_enable_outgoing_call">Podpora odch谩dzj煤cich hovorov</string>
+    <string name="pref_summary_enable_outgoing_call">Vypnut铆m tie啪 prestan煤 u Pebble 2/LE vibr谩cie u odch谩dzaj煤cich hovorov</string>
+    <string name="pref_title_enable_pebblekit">Povoli钮 pr铆stup tret铆ch str谩n k aplik谩cii Android</string>
+    <string name="pref_summary_enable_pebblekit">Povoli钮 experiment谩lnu podporu pre aplik谩cie Androidu cez PebbleKit</string>
+    <string name="pref_header_pebble_timeline">膶asov谩 os Pebble</string>
+    <string name="pref_title_sunrise_sunset">V媒chod a z谩pad slnka</string>
+    <string name="pref_summary_sunrise_sunset">Posiela钮 膷asy v媒chodu a z谩padu slnka pod木a umiestnenia do Pebble 膷asovej osi</string>
+    <string name="pref_title_enable_calendar_sync">Synchroniz谩cia kalend谩ra</string>
+    <string name="pref_summary_enable_calendar_sync">Odosiela钮 udalosti kalend谩ra na 膷asov煤 os</string>
+    <string name="pref_title_autoremove_notifications">Automaticky odstra艌ova钮 skryt茅 upozornenia</string>
+    <string name="pref_summary_autoremove_notifications">Upozornenia bud煤 z Pebble odstr谩nen茅 automaticky, ak boli skryt茅 na zariaden铆 s Androidom</string>
+    <string name="pref_title_pebble_privacy_mode">Re啪im s煤kromia</string>
+    <string name="pref_pebble_privacy_mode_off">Be啪n茅 upozornenia</string>
+    <string name="pref_pebble_privacy_mode_content">Posun煤钮 text upozornenia mimo obrazovku</string>
+    <string name="pref_pebble_privacy_mode_complete">Zobrazi钮 iba ikonu notifik谩cia</string>
+    <string name="pref_header_location">Poloha</string>
+    <string name="pref_title_location_aquire">Zisti钮 polohu</string>
+    <string name="pref_title_location_latitude">Z. 拧铆rka</string>
+    <string name="pref_title_location_longitude">Z. d暮啪ka</string>
+    <string name="pref_title_location_keep_uptodate">Aktualizova钮 polohu</string>
+    <string name="pref_summary_location_keep_uptodate">Pri spusten铆 zisti钮 aktu谩lnu polohu, ulo啪en煤 polohu pou啪i钮 ako z谩lo啪n煤</string>
+    <string name="toast_enable_networklocationprovider">Povo木te zis钮ovanie polohy pod木a siete</string>
+    <string name="toast_aqurired_networklocation">Poloha zisten谩</string>
+    <string name="pref_title_pebble_forceprotocol">Vyn煤ti钮 protokol upozornen铆</string>
+    <string name="pref_summary_pebble_forceprotocol">T谩to vo木ba vyn煤ti pou啪itie najnov拧ieho protokolu upozornen铆 pod木a verzie firmware. ZAPNITE IBA AK VIETE, 膶O ROB脥TE!</string>
+    <string name="pref_title_pebble_forceuntested">Povoli钮 netestovan茅 funkcie</string>
+    <string name="pref_summary_pebble_forceuntested">Povol铆 funkcie, ktor茅 neboli testovan茅. ZAPNITE IBA AK VIETE, 膶O ROB脥TE!</string>
+    <string name="pref_title_pebble_forcele">V啪dy preferova钮 BLE</string>
+    <string name="pref_summary_pebble_forcele">Pou啪i钮 experiment谩lnu podporu Pebble LE pre v拧etky Pebble zariadenia namiesto klasick茅ho BT. Vy啪aduje sp谩rovanie Pebble LE \" po pripojen铆 bez LE</string>
+    <string name="pref_title_pebble_mtu_limit">Pebble 2/LE GATT MTU ohrani膷enie</string>
+    <string name="pref_summary_pebble_mtu_limit">Ak va拧e Pebble 2/Pebble LE nepracuje spr谩vne, sk煤ste toto nastavenie pre omezenie MTU (povolen媒 rozsah 20鈥�512)</string>
+    <string name="pref_title_pebble_enable_applogs">Zapn煤钮 logovanie Watch App</string>
+    <string name="pref_summary_pebble_enable_applogs">Sp么sob铆, 啪e logy z aplik谩ci铆 v hodink谩ch bud煤 zaznamen谩van茅 aplik谩ciou Gadgetbridge (vy啪aduje op盲tovn茅 pripojenie)</string>
+    <string name="pref_title_pebble_always_ack_pebblekit">Pred膷asn茅 potvrdenie (ACK) pre PebbleKit</string>
+    <string name="pref_summary_pebble_always_ack_pebblekit">Sp么sob铆, 啪e spr谩vy odosielan茅 extern媒m aplik谩ci谩m tret铆ch str谩n bud煤 v啪dy a okam啪ite potvrden茅</string>
+    <string name="pref_title_pebble_enable_bgjs">Zapn煤钮 JS na pozad铆</string>
+    <string name="pref_summary_pebble_enable_bgjs">Ak je zapnut茅, umo啪艌uje hodin谩m zobrazova钮 po膷asie, 煤daje o bat茅rii at膹.</string>
+    <string name="pref_title_pebble_reconnect_attempts">Pokusov o znovupripojenie</string>
+    <string name="pref_title_unit_system">Mern茅 jednotky</string>
+    <string name="pref_title_timeformat">Form谩t 膷asu</string>
+    <string name="pref_title_screentime">膶as zapnutia obrazovky</string>
+    <string name="prefs_title_all_day_heart_rate">Celodenn茅 meranie tepu</string>
+    <string name="preferences_hplus_settings">Nastavenia HPlus/Makibes</string>
+    <string name="pref_header_auto_export">Automatick媒 export</string>
+    <string name="pref_title_auto_export_enabled">Zapnut媒/Vypnut媒</string>
+    <string name="pref_title_auto_export_location">Prie膷inok pre ulo啪enie</string>
+    <string name="pref_title_auto_export_interval">膶asov媒 interval</string>
+    <string name="pref_summary_auto_export_interval">Exportova钮 ka啪d茅 %d hodiny</string>
+    <string name="not_connected">Nepripojen茅</string>
+    <string name="connecting">Prip谩janie</string>
+    <string name="connected">Pripojen茅</string>
+    <string name="unknown_state">Nezn谩my stav</string>
+    <string name="_unknown_">(nezn谩me)</string>
+    <string name="test">Test</string>
+    <string name="test_notification">Test upozornen铆</string>
+    <string name="this_is_a_test_notification_from_gadgetbridge">Toto je upozornenie z Gadgetbridge</string>
+    <string name="bluetooth_is_not_supported_">BT nie je podporovan媒.</string>
+    <string name="bluetooth_is_disabled_">BT je vypnut媒.</string>
+    <string name="tap_connected_device_for_app_mananger">Dotknite sa pripojen茅ho zariadenia pre zobrazenie spr谩vcu aplik谩ci铆</string>
+    <string name="tap_connected_device_for_activity">Dotknite sa pripojen茅ho zariadenia pre zobrazenie aktiv铆t</string>
+    <string name="tap_connected_device_for_vibration">Dotknite sa pripojen茅ho zariadenia pre vibr谩cie</string>
+    <string name="tap_a_device_to_connect">Dotknite sa pripojen茅ho zariadenia pre pripojenie</string>
+    <string name="cannot_connect_bt_address_invalid_">Nie je mo啪n茅 pripoji钮. Je BT adresa v poriadku?</string>
+    <string name="gadgetbridge_running_generic">Gadgetbridge je spusten媒</string>
+    <string name="installing_binary_d_d">Instalujem bin谩rny s煤bor %1$d/%2$d</string>
+    <string name="installation_failed_">In拧tal谩cia zlyhala</string>
+    <string name="installation_successful">In拧tal谩cia bola 煤spe拧n谩</string>
+    <string name="firmware_install_warning">CHCETE IN艩TALOVA扭 FIRMWARE, POKRA膶UJTE NA VLASTN脡 RIZIKO.
+\n
+\n
+\nTento firmware je pre rev铆ziu HW: %s</string>
+    <string name="app_install_info">In拧talujete aplik谩ciu:
+\n
+\n
+\n%1$s Verzia %2$s od %3$s
+\n</string>
+    <string name="n_a">Nedostupn茅</string>
+    <string name="initialized">Spusten茅</string>
+    <string name="appversion_by_creator">%1$s od %2$s</string>
+    <string name="title_activity_discovery">Vyh木ad谩vanie zariadenia</string>
+    <string name="discovery_stop_scanning">Zastavi钮 h木adanie</string>
+    <string name="discovery_start_scanning">Spusti钮 h木adanie</string>
+    <string name="action_discover">Pripoji钮 nov茅 zariadenia</string>
+    <string name="device_with_rssi">%1$s (%2$s)</string>
+    <string name="title_activity_android_pairing">P谩rova钮 zariadenie</string>
+    <string name="android_pairing_hint">Pou啪ite p谩rovanie BT Androidu pre sp谩rovanie so zariaden铆m.</string>
+    <string name="title_activity_mi_band_pairing">P谩rova钮 Mi Band</string>
+    <string name="pairing">P谩rujem s %s鈥�</string>
+    <string name="pairing_creating_bond_with">Vytv谩ram v盲zbu s %1$s (%2$s)</string>
+    <string name="pairing_unable_to_pair_with">Nie je mo啪n茅 sp谩rova钮 s %1$s (%2$s)</string>
+    <string name="pairing_in_progress">Vytv谩ram v盲zbu: %1$s (%2$s)</string>
+    <string name="pairing_already_bonded">V盲zba s %1$s (%2$s) u啪 existuje, prep谩jam鈥�</string>
+    <string name="message_cannot_pair_no_mac">Adresa MAC nepri拧la, nem么啪em sp谩rova钮.</string>
+    <string name="preferences_category_device_specific_settings">艩pecifick茅 nastavenia zariaden铆</string>
+    <string name="preferences_miband_settings">Nastavenia Mi Band / Amazfit</string>
+    <string name="male">Mu啪</string>
+    <string name="female">沤ena</string>
+    <string name="other">In茅</string>
+    <string name="left">慕avej</string>
+    <string name="right">Pravej</string>
+    <string name="miband_pairing_using_dummy_userdata">U啪铆vate木sk茅 d谩ta nie s煤 platn茅, teraz pou啪铆vam predvolen茅.</string>
+    <string name="miband_pairing_tap_hint">Ke膹 Mi Band zavibruje a blikne, dotknite sa ho nieko木kokr谩t po sebe.</string>
+    <string name="appinstaller_install">Nain拧talova钮</string>
+    <string name="discovery_connected_devices_hint">Nastavte svoje zariadenie pre vyh木adanie. U啪 pripojen茅 zariadenia nebud煤 vyh木adan茅. Zapnite lokaliz谩ciu (GPS) na mobiloch s Androidom 6 a vy拧拧铆m. Vypnite str谩啪enie s煤kromia pre Gadgetbridge, preto啪e m么啪e sp么sobi钮 nestabilitu mobilu. Ak nie je zariadenie vyh木adan茅 v priebehu nieko木k媒ch min煤t, sk煤ste to znovu po re拧tarte mobilu.</string>
+    <string name="discovery_note">Pozn谩mka:</string>
+    <string name="candidate_item_device_image">Obraz zariadenia</string>
+    <string name="miband_prefs_alias">N谩zov/p艡ezd铆vka</string>
+    <string name="pref_header_vibration_count">Po膷et vibr谩ci铆</string>
+    <string name="title_activity_sleepmonitor">Sledovanie sp谩nku</string>
+    <string name="pref_write_logfiles">Zap铆sova钮 logy</string>
+    <string name="initializing">Sp煤拧钮anie</string>
+    <string name="busy_task_fetch_activity_data">Na膷铆tavanie 煤dajov o aktivite</string>
+    <string name="sleep_activity_date_range">Od %1$s do %2$s</string>
+    <string name="prefs_wearside">Na ktorej ruke nos铆te?</string>
+    <string name="pref_screen_vibration_profile">Profil vibr谩ci铆</string>
+    <string name="vibration_profile_staccato">Staccato</string>
+    <string name="vibration_profile_short">Kr谩tk茅</string>
+    <string name="vibration_profile_medium">Stredn茅</string>
+    <string name="vibration_profile_long">Dlh茅</string>
+    <string name="vibration_profile_waterdrop">Kvapka</string>
+    <string name="vibration_profile_ring">Zvonenie</string>
+    <string name="vibration_profile_alarm_clock">Bud铆k</string>
+    <string name="miband_prefs_vibration">Vibr谩cie</string>
+    <string name="vibration_try">Vysk煤拧a钮</string>
+    <string name="pref_screen_notification_profile_sms">Upozornenia na SMS</string>
+    <string name="pref_header_vibration_settings">Nastavenia vibr谩ci铆</string>
+    <string name="pref_screen_notification_profile_generic">Be啪n茅 upozornenia</string>
+    <string name="pref_screen_notification_profile_email">Upozornenia na emaily</string>
+    <string name="pref_screen_notification_profile_incoming_call">Upozornenia na hovory</string>
+    <string name="pref_screen_notification_profile_generic_chat">Rozhovory (chat)</string>
+    <string name="pref_screen_notification_profile_generic_navigation">Navig谩cia</string>
+    <string name="pref_screen_notification_profile_generic_social">Soci谩lne siete</string>
+    <string name="prefs_title_heartrate_measurement_interval">Celodenn茅 meranie tepu</string>
+    <string name="interval_one_minute">Raz za min煤tu</string>
+    <string name="interval_five_minutes">Ka啪d媒ch 5 min煤t</string>
+    <string name="interval_ten_minutes">Ka啪d媒ch 10 min煤t</string>
+    <string name="interval_thirty_minutes">Ka啪d媒ch 30 min煤t</string>
+    <string name="interval_one_hour">Raz za hodinu</string>
+    <string name="stats_title">Z贸ny r媒chlosti</string>
+    <string name="stats_x_axis_label">Min煤t celkovo</string>
+    <string name="stats_y_axis_label">Krokov za min煤tu</string>
+    <string name="control_center_find_lost_device">N谩js钮 straten茅 zariadenie</string>
+    <string name="control_center_cancel_to_stop_vibration">Zru拧it pro zastaven铆 vibrac铆.</string>
+    <string name="title_activity_charts">Va拧e aktivity</string>
+    <string name="title_activity_set_alarm">Nastavi钮 bud铆ky</string>
+    <string name="controlcenter_start_configure_alarms">Nastavenie bud铆kov</string>
+    <string name="title_activity_alarm_details">Detaily budenia</string>
+    <string name="alarm_sun_short">Ne</string>
+    <string name="alarm_mon_short">Po</string>
+    <string name="alarm_tue_short">Ut</string>
+    <string name="alarm_wed_short">St</string>
+    <string name="alarm_thu_short">艩t</string>
+    <string name="alarm_fri_short">Pi</string>
+    <string name="alarm_sat_short">So</string>
+    <string name="alarm_smart_wakeup">Inteligentn茅 buzdenie</string>
+    <string name="user_feedback_miband_set_alarms_failed">Nepodarilo sa nastavi钮 bud铆k, sk煤ste to znova!</string>
+    <string name="user_feedback_miband_set_alarms_ok">Bud铆k bol odoslan媒 do zariadenia!</string>
+    <string name="chart_no_data_synchronize">Ch媒baj煤 d谩ta, m谩m ich stiahnu钮?</string>
+    <string name="user_feedback_miband_activity_data_transfer">Stiahnu钮 d谩ta %1$s z %2$s</string>
+    <string name="miband_prefs_fitness_goal">Cie木ov媒 po膷et krokov za de艌</string>
+    <string name="dbaccess_error_executing">Chyba spustenia \'%1$s\'</string>
+    <string name="controlcenter_start_activitymonitor">Va拧a aktivita (pokusn谩)</string>
+    <string name="cannot_connect">Ned谩 sa pripoji钮 k: %1$s</string>
+    <string name="installer_activity_unable_to_find_handler">Nem么啪em n谩js钮 program na in拧tal谩ciu tohto s煤boru.</string>
+    <string name="pbw_install_handler_unable_to_install">Nepodarilo sa nain拧talova钮 s煤bor: %1$s</string>
+    <string name="pbw_install_handler_hw_revision_mismatch">Nemo啪no nain拧talova钮 vybran媒 firmware: nezhoduje sa HW rev铆ziou v谩拧ho Pebble.</string>
+    <string name="installer_activity_wait_while_determining_status">Vy膷kajte na zistenie stavu in拧tal谩cie鈥�</string>
+    <string name="notif_battery_low_title">N铆zka 煤rove艌 nabitia bat茅rie v zariaden铆!</string>
+    <string name="notif_battery_low_percent">%1$s zost谩va v bat茅rii: %2$s%%</string>
+    <string name="notif_battery_low_bigtext_last_charge_time">Posledn茅 nabitie: %s 
+\n</string>
+    <string name="notif_battery_low_bigtext_number_of_charges">Po膷et nabit铆: %s</string>
+    <string name="notif_export_failed_title">Export datab谩zy zlyhal! Skontrolujte svoje nastavenia.</string>
+    <string name="sleepchart_your_sleep">V谩拧 sp谩nok</string>
+    <string name="weeksleepchart_sleep_a_week">Sp谩nok za t媒啪de艌</string>
+    <string name="weeksleepchart_today_sleep_description">Sp谩nok dnes, cie木: %1$s</string>
+    <string name="weekstepschart_steps_a_week">Kroky za t媒啪de艌</string>
+    <string name="activity_sleepchart_activity_and_sleep">Va拧a aktivita a sp谩nok</string>
+    <string name="updating_firmware">Nahr谩vam firmv茅r鈥�</string>
+    <string name="fwapp_install_device_not_ready">S煤bor sa ned谩 nain拧talova钮, zariadenie nie je pripraven茅.</string>
+    <string name="installhandler_firmware_name">%1$s: %2$s %3$s</string>
+    <string name="miband_fwinstaller_compatible_version">Kompatibiln谩 verzia</string>
+    <string name="miband_fwinstaller_untested_version">Netestovan谩 verzia!</string>
+    <string name="fwappinstaller_connection_state">P艡ipojen茅 k zariadeniu: %1$s</string>
+    <string name="pbw_installhandler_pebble_firmware">Pebble firmv茅r %1$s</string>
+    <string name="pbwinstallhandler_correct_hw_revision">Spr谩vn谩 HW rev铆zia</string>
+    <string name="pbwinstallhandler_incorrect_hw_revision">Rev铆z铆a HW nie je spr谩vna!</string>
+    <string name="pbwinstallhandler_app_item">%1$s (%2$s)</string>
+    <string name="updatefirmwareoperation_updateproblem_do_not_reboot">Pri prenose firmv茅ru nastali 钮a啪kosti. Nere拧tartujte svoj Mi Band!</string>
+    <string name="updatefirmwareoperation_metadata_updateproblem">Probl茅m pri prenose metad谩t firmv茅ru</string>
+    <string name="updatefirmwareoperation_update_complete">In拧tal谩cia firmv茅ru je kompletn谩</string>
+    <string name="updatefirmwareoperation_update_complete_rebooting">In拧tal谩cia firmv茅ru je kompletn谩, re拧tartujem zariadenie鈥�</string>
+    <string name="updatefirmwareoperation_write_failed">Z谩pis firmv茅ru zlyhal</string>
+    <string name="chart_steps">Krokov</string>
+    <string name="calories">Kal贸ri铆</string>
+    <string name="distance">Vzdialenos钮</string>
+    <string name="clock">Meranie 膷asu</string>
+    <string name="heart_rate">Tep srdca</string>
+    <string name="battery">Bat茅ria</string>
+    <string name="liveactivity_live_activity">S煤膷asn谩 aktivita</string>
+    <string name="weeksteps_today_steps_description">Krokov dnes, cie木: %1$s</string>
+    <string name="pref_title_dont_ack_transfer">Nepotvrdzova钮 prenos d谩t o aktivite</string>
+    <string name="pref_summary_dont_ack_transfers">Pokia木 nie je prenos d谩t potvrden媒 n谩ramkom, potom nebud煤 vymazan茅. To je u啪ite膷n茅, pokia木 je GB pou啪铆van媒 dohromady s inou aplik谩ciou.</string>
+    <string name="pref_summary_keep_data_on_device">Zachov谩 d谩ta o aktivite v Mi Band aj po synchroniz谩cii. To je u啪ite膷n茅, ak je GB pou啪铆van媒 dohromady s inou aplik谩ciou.</string>
+    <string name="pref_title_low_latency_fw_update">Pou啪i钮 re啪im z n铆zkym oneskoren铆m pri aktualiz谩cii firmv茅ru</string>
+    <string name="pref_summary_low_latency_fw_update">M么啪e to pom么c钮 u zariadenia, kde aktualiz谩cia firmv茅ru zlyhala</string>
+    <string name="live_activity_steps_history">Hist贸ria krokov</string>
+    <string name="live_activity_current_steps_per_minute">Dnes krokov/min</string>
+    <string name="live_activity_total_steps">Celkom krokov</string>
+    <string name="live_activity_steps_per_minute_history">Hist贸ria krokov za min煤tu</string>
+    <string name="live_activity_start_your_activity">Za膷a钮 aktivitu</string>
+    <string name="abstract_chart_fragment_kind_activity">Aktivita</string>
+    <string name="abstract_chart_fragment_kind_light_sleep">慕ahk媒 sp谩nok</string>
+    <string name="abstract_chart_fragment_kind_deep_sleep">Hlbok媒 sp谩nok</string>
+    <string name="abstract_chart_fragment_kind_not_worn">Nenosen茅</string>
+    <string name="device_not_connected">Nepripojen茅.</string>
+    <string name="user_feedback_all_alarms_disabled">V拧etky alarmy vypnut茅</string>
+    <string name="pref_title_keep_data_on_device">Zachova钮 d谩ta o aktivite v zariaden铆</string>
+    <string name="miband_fwinstaller_incompatible_version">Nekompatibiln媒 firmv茅r</string>
+    <string name="fwinstaller_firmware_not_compatible_to_device">Tento firmware nie je kompatibiln媒 s t媒mto zariaden铆m</string>
+    <string name="miband_prefs_reserve_alarm_calendar">Alarmy pre nadch谩dzaj煤ce udalosti</string>
+    <string name="miband_prefs_hr_sleep_detection">Pou啪i钮 sledovanie tepu pre zlep拧enie detekcie sp谩nku</string>
+    <string name="miband_prefs_device_time_offset_hours">膶asov媒 posun zariadenia v hodin谩ch (pre zis钮ovanie sp谩nku v pracuj煤cich na smeny)</string>
+    <string name="miband2_prefs_dateformat">Mi2: form谩t datumu</string>
+    <string name="dateformat_time">膶as</string>
+    <string name="dateformat_date_time">膶as a d谩tum</string>
+    <string name="mi2_prefs_button_actions">Akcie tla膷idla</string>
+    <string name="mi2_prefs_button_actions_summary">Nastavenie akci铆 po stla膷en铆 tla膷idla Mi Band 2</string>
+    <string name="mi2_prefs_button_press_count">Po膷et stla膷en铆 tla膷idla</string>
+    <string name="mi2_prefs_button_press_count_summary">Po膷et stla膷en铆 tla膷idla na spustenie vysielania spr谩v</string>
+    <string name="mi2_prefs_button_press_broadcast">Vysielanie spr谩v</string>
+    <string name="mi2_prefs_button_press_broadcast_summary">Vysielanie spr谩vy po definovanom po膷te stla膷enia tla膷idla</string>
+    <string name="mi2_prefs_button_action">Zapn煤钮 akciu tla膷idla</string>
+    <string name="mi2_prefs_button_action_summary">Zapn煤钮 akciu na zadan媒 po膷et stla膷enia tla膷idla</string>
+    <string name="mi2_prefs_button_action_vibrate">Zapn煤钮 vibrovanie n谩ramku</string>
+    <string name="mi2_prefs_button_action_vibrate_summary">Zapn煤钮 vibr谩cie n谩ramku pri stla膷en铆 tla膷idla</string>
+    <string name="mi2_prefs_button_press_count_max_delay">Maxim谩lne oneskorenie medzi stla膷eniami</string>
+    <string name="mi2_prefs_button_press_count_max_delay_summary">Maxim谩lne oneskorenie medzi stla膷en铆m tla膷idla v milisekund谩ch</string>
+    <string name="mi2_prefs_goal_notification">Upozornenie na cie木</string>
+    <string name="mi2_prefs_goal_notification_summary">N谩ramok zavibruje pri dosiahnut铆 cie木a denn茅ho po膷tu krokov</string>
+    <string name="mi2_prefs_display_items">Zobrazenie polo啪iek</string>
+    <string name="mi2_prefs_display_items_summary">Vyberte polo啪ky zobrazen茅 na obrazovke n谩ramku</string>
+    <string name="mi2_prefs_activate_display_on_lift">Zapn煤钮 obrazovku pri zdvihnut铆</string>
+    <string name="mi2_prefs_rotate_wrist_to_switch_info">Oto膷en铆m z谩p盲stia prepn煤钮 typ inform谩cie</string>
+    <string name="mi2_prefs_do_not_disturb">Neru拧i钮</string>
+    <string name="mi2_prefs_do_not_disturb_summary">N谩ramok nebude prij铆ma钮 upozornenia, pokia木 je akt铆vny re啪im Neru拧i钮</string>
+    <string name="mi2_prefs_inactivity_warnings">Upozornenia na ne膷innos钮</string>
+    <string name="mi2_prefs_inactivity_warnings_summary">N谩ramok zavibruje, ke膹 budete zadan媒 膷as bez pohybu</string>
+    <string name="mi2_prefs_inactivity_warnings_threshold">Limit 膷asu bez pohybu (v min煤tach)</string>
+    <string name="mi2_prefs_inactivity_warnings_dnd_summary">Vypnutie upozornen铆 na ne膷innos钮 po膷as zadan茅ho 膷asov茅ho intervalu</string>
+    <string name="mi2_prefs_do_not_disturb_start">Od</string>
+    <string name="mi2_prefs_do_not_disturb_end">Do</string>
+    <string name="automatic">Automaticky</string>
+    <string name="simplified_chinese">Zjednodu拧en谩 膶铆n拧tina</string>
+    <string name="traditional_chinese">Tradi膷n谩 膶铆s拧tina</string>
+    <string name="english">Angli膷tina</string>
+    <string name="spanish">艩paniel膷ina</string>
+    <string name="FetchActivityOperation_about_to_transfer_since">Pren谩拧am d谩ta od %1$s</string>
+    <string name="waiting_for_reconnect">膶akanie na op盲tovn茅 pripojenie</string>
+    <string name="activity_prefs_about_you">Va拧e osobn茅 煤daje</string>
+    <string name="activity_prefs_year_birth">Rok narodenia</string>
+    <string name="activity_prefs_gender">Pohlavie</string>
+    <string name="activity_prefs_height_cm">V媒拧ka v cm</string>
+    <string name="activity_prefs_weight_kg">V谩ha v kg</string>
+    <string name="authenticating">Overovanie</string>
+    <string name="authentication_required">Vy啪iadan茅 overenie</string>
+    <string name="appwidget_text">Sp铆鈥�</string>
+    <string name="add_widget">Prida钮 miniaplik谩ciu</string>
+    <string name="activity_prefs_sleep_duration">Preferovan谩 doba sp谩nku v hodin谩ch</string>
+    <string name="appwidget_setting_alarm">Bud铆k nastaven媒 na %1$02d:%2$02d</string>
+    <string name="device_hw">Rev铆zia HW: %1$s</string>
+    <string name="device_fw">Verzia FW: %1$s</string>
+    <string name="error_creating_directory_for_logfiles">Chyba pri vytv谩ran铆 adres谩ra pre logy: %1$s</string>
+    <string name="DEVINFO_HR_VER">"Tep: "</string>
+    <string name="updatefirmwareoperation_update_in_progress">Prebieha aktualiz谩cia firmv茅ru</string>
+    <string name="updatefirmwareoperation_firmware_not_sent">Firmv茅r nebol odoslan媒</string>
+    <string name="charts_legend_heartrate">Tep srdca</string>
+    <string name="live_activity_heart_rate">Tep srdca</string>
+    <string name="pref_title_pebble_health_store_raw">Ulo啪i钮 hrub媒 z谩znam do datab谩zy</string>
+    <string name="pref_summary_pebble_health_store_raw">Ak je za拧krtnut茅, tak s煤 d谩ta ulo啪en茅 v p么vodnej podobe pre neskor拧ie vyhodnotenie. Datab谩za bude v tom pr铆pade v盲膷拧ia!</string>
+    <string name="action_db_management">Spr谩va datab谩zy</string>
+    <string name="title_activity_db_management">Spr谩va datab谩zy</string>
+    <string name="activity_db_management_import_export_explanation">Pre oper谩cie s datab谩zou sa pou啪ije nasleduj煤ca cesta v zariaden铆.
+\nT谩to cesta je dostupn谩 aj pre ostatn茅 aplik谩cie Androidu a aj v谩拧 po膷铆ta膷.
+\nExportovan煤 datab谩zu (alebo miesto pre importovan煤 datab谩zu) n谩jdete tu:</string>
+    <string name="activity_db_management_merge_old_title">Vymaza钮 p么vodn煤 datab谩zu</string>
+    <string name="dbmanagementactivvity_cannot_access_export_path">Nemo啪no prist煤pi钮 na zadan煤 cestu. Kontaktujte v媒voj谩rov.</string>
+    <string name="dbmanagementactivity_exported_to">Exportovan茅 do: %1$s</string>
+    <string name="dbmanagementactivity_error_exporting_db">Chyba pri exporte DB: %1$s</string>
+    <string name="dbmanagementactivity_error_exporting_shared">Chyba pri exporte preferenci铆: %1$s</string>
+    <string name="dbmanagementactivity_import_data_title">Importova钮 d谩ta?</string>
+    <string name="dbmanagementactivity_overwrite_database_confirmation">Naozaj chcete prep铆sa钮 aktu谩lnu datab谩zu? V拧etky ulo啪en茅 d谩ta o aktivite sa stratia.</string>
+    <string name="dbmanagementactivity_import_successful">Import bol 煤spe拧n媒.</string>
+    <string name="dbmanagementactivity_error_importing_db">Chyba pri importe DB: %1$s</string>
+    <string name="dbmanagementactivity_error_importing_shared">Chyba pri importe preferenci铆: %1$s</string>
+    <string name="dbmanagementactivity_delete_activity_data_title">Vymaza钮 d谩ta o aktivit谩ch?</string>
+    <string name="dbmanagementactivity_really_delete_entire_db">Naozaj chcete vymaza钮 cel煤 datab谩zu? V拧etky d谩ta o aktivit谩ch a inform谩cie o zariadeniach sa stratia.</string>
+    <string name="dbmanagementactivity_database_successfully_deleted">D谩ta boli 煤spe拧ne vymazan茅.</string>
+    <string name="dbmanagementactivity_db_deletion_failed">Mazanie d谩t zlyhalo.</string>
+    <string name="dbmanagementactivity_delete_old_activity_db">Vymaza钮 p么vodn煤 datab谩zu?</string>
+    <string name="dbmanagementactivity_delete_old_activitydb_confirmation">Naozaj chcete vyzmaza钮 p么vodn煤 datab谩zu? D谩ta o aktivit谩ch, ktor谩 neboli importovan茅 sa stratia.</string>
+    <string name="dbmanagementactivity_old_activity_db_successfully_deleted">P么vodn谩 datab谩za bola vymazan谩.</string>
+    <string name="dbmanagementactivity_old_activity_db_deletion_failed">Mazanie p么vodnej datab谩zy zlyhalo.</string>
+    <string name="dbmanagementactivity_overwrite">Pprep铆sa钮</string>
+    <string name="Cancel">Zru拧i钮</string>
+    <string name="Delete">Odstr谩ni钮</string>
+    <string name="title_activity_vibration">Vibr谩cie</string>
+    <string name="title_activity_pebble_pairing">P谩rov谩nie Pebble</string>
+    <string name="pebble_pairing_hint">Dial贸g p谩rovania by sa mal objavi钮 na va拧om zariaden铆 s Androidom. Ak sa tak nestane, skontrolujte panel upozornen铆 a potvr膹te p谩rovanie. Potom potvr膹te p谩rovanie na Pebble</string>
+    <string name="weather_notification_label">Uistite sa, 啪e tento skin je povolen媒 v aplik谩cii pre upozornenia stavu po膷asia pre z铆skavanie inform谩ci铆 z v谩拧ho Pebble.
+\n
+\nNie je potrebn茅 ni膷 nastavova钮.
+\n
+\nAplik谩ciu pre po膷asie Pebble je mo啪n茅 povoli钮 v spr谩ve aplik谩ci铆.
+\n
+\nPodporovan茅 cifern铆ky uk谩啪u po膷asie automaticky.</string>
+    <string name="pref_title_setup_bt_pairing">Zapn煤钮 BT p谩rovanie</string>
+    <string name="pref_summary_setup_bt_pairing">Toto vypnite v pr铆pade probl茅mov s pripojen铆m</string>
+    <string name="unit_metric">Metrick茅</string>
+    <string name="unit_imperial">Imperi谩lne</string>
+    <string name="timeformat_24h">24h</string>
+    <string name="timeformat_am_pm">dop./odp.</string>
+    <string name="pref_screen_notification_profile_alarm_clock">Bud铆k</string>
+    <string name="activity_web_view">Aktivity Webov媒 poh木ad</string>
+    <string name="StringUtils_sender">(%1$s)</string>
+    <string name="find_device_you_found_it">N谩jden茅!</string>
+    <string name="miband2_prefs_timeformat">Form谩t 膷asu Mi2</string>
+    <string name="mi2_fw_installhandler_fw53_hint">Pred in拧tal谩ciou tohto firmv茅ru mus铆te nain拧talova钮 verziu %1$s!</string>
+    <string name="mi2_enable_text_notifications">Textov茅 upozornenia</string>
+    <string name="mi2_enable_text_notifications_summary">Vy啪aduje nain拧talovan媒 s煤bor fontu &gt;= 1.0.1.28 and Mili_pro.ft*.</string>
+    <string name="off">Vyp.</string>
+    <string name="mi2_dnd_off">Vyp.</string>
+    <string name="mi2_dnd_automatic">Automaticky (detekcia sp谩nku)</string>
+    <string name="mi2_dnd_scheduled">Napl谩novan茅 (膷asov媒 interval)</string>
+    <string name="discovery_attempting_to_pair">Pokus o sp谩rovanie s %1$s</string>
+    <string name="discovery_bonding_failed_immediately">Spojenie s %1$s okam啪ite zlyhalo.</string>
+    <string name="discovery_trying_to_connect_to">Poku拧am sa spoji钮 s: %1$s</string>
+    <string name="discovery_enable_bluetooth">Ak chcete n谩js钮 zariadenia, povo木te funkciu Bluetooth.</string>
+    <string name="discovery_successfully_bonded">Spojen茅 s %1$s.</string>
+    <string name="discovery_pair_title">P谩rova钮 s %1$s?</string>
+    <string name="discovery_pair_question">Ak chcete sp谩rova钮 svoje zariadenia, kliknite na P谩rova钮. Ak to zlyh谩, sk煤ste to znovu bez p谩rovania.</string>
+    <string name="discovery_yes_pair">P谩rova钮</string>
+    <string name="discovery_dont_pair">Nep谩rova钮</string>
+    <string name="_pebble_watch_open_on_phone">Otvori钮 na telef贸ne</string>
+    <string name="_pebble_watch_mute">Stlmi钮</string>
+    <string name="_pebble_watch_reply">Znovu</string>
+    <string name="kind_firmware">Firmv茅r</string>
+    <string name="kind_invalid">Neplatn茅 d谩ta</string>
+    <string name="kind_font">P铆smo</string>
+    <string name="kind_gps">GPS Firmv茅r</string>
+    <string name="kind_gps_almanac">Almanach GPS</string>
+    <string name="kind_gps_cep">Korekcia ch媒b GPS</string>
+    <string name="kind_resources">Zdroje</string>
+    <string name="kind_watchface">Cifern铆k</string>
+    <string name="devicetype_unknown">Nezn谩me zariadenie</string>
+    <string name="devicetype_test">Testovacie zariadenie</string>
+    <string name="devicetype_pebble">Pebble</string>
+    <string name="devicetype_miband">Mi Band</string>
+    <string name="devicetype_miband2">Mi Band 2</string>
+    <string name="devicetype_amazfit_bip">Amazfit Bip</string>
+    <string name="devicetype_amazfit_cor">Amazfit Cor</string>
+    <string name="devicetype_vibratissimo">Vibratissimo</string>
+    <string name="devicetype_liveview">LiveView</string>
+    <string name="devicetype_hplus">HPlus</string>
+    <string name="devicetype_makibes_f68">Makibes F68</string>
+    <string name="devicetype_exrizu_k8">Exrizu K8</string>
+    <string name="devicetype_no1_f1">No.1 F1</string>
+    <string name="devicetype_teclast_h30">Teclast H30</string>
+    <string name="devicetype_xwatch">XWatch</string>
+    <string name="choose_auto_export_location">Vyberte umiestnenie exportu</string>
+    <string name="notification_channel_name">Gadgetbridge upozornenia</string>
+    <string name="controlcenter_connect">Pripoji钮</string>
+    <string name="on">Zap.</string>
+    <string name="mi2_prefs_button_press_broadcast_default_value" translatable="false">nodomain.freeyourgadget.gadgetbridge.ButtonPressed</string>
+    <string name="sonyswr12_settings_title">Nastavenia Sony SWR12</string>
+    <string name="sonyswr12_settings_low_vibration">N铆zke vibr谩cie s煤 povolen茅</string>
+    <string name="sonyswr12_settings_stamina">Re啪im 煤spory energie je zapnut媒</string>
+    <string name="sonyswr12_settings_alarm_interval">Interval inteligentn茅ho alarmu v min煤tach</string>
+    <string name="sonyswr12_settings_low_vibration_summary">Povo木te n铆zku intenzitu vibr谩ci铆 na n谩ramku</string>
+    <string name="sonyswr12_settings_stamina_summary">Re啪im 煤spory energie vypne pravideln茅 automatick茅 meranie srdcovej frekvencie, 膷铆m sa pred暮啪i pracovn谩 doba</string>
+    <string name="sonyswr12_settings_alarm_interval_summary">Interval inteligentn茅ho alarmu je interval pred nain拧talovan媒m alarmom. V tomto intervale sa zariadenie pok煤拧a zisti钮 naj木ah拧iu f谩zu sp谩nku, aby sa pou啪铆vate木 zobudil</string>
+    <string name="controlcenter_change_led_color">Zmena farby LED</string>
+    <string name="pref_show_changelog">Zobrazi钮 zoznam zmien pri 拧tarte aplik谩cie</string>
+    <string name="about_activity_title_generic">O aplik谩cii Gadgetbridge</string>
+    <string name="pref_title_bottom_navigation_bar">Spodn谩 naviga膷n谩 li拧ta</string>
+    <string name="title_activity_notification_management">Nastavenie ozn谩men铆</string>
+    <string name="pref_check_permission_status">Kontrolova钮 stav opr谩vnen铆</string>
+    <string name="activity_info">Inform谩cie o aktivite</string>
+    <string name="action_changelog">Zoznam zmien</string>
+    <string name="pref_title_general_reconnectonlytoconnected">Znovu sa pripoji钮 iba k pripojen媒m zariadeniam</string>
+    <string name="pref_header_connection">Pripojenie</string>
+    <string name="pref_world_clocks_title">Svetov茅 hodiny</string>
+    <string name="pref_title_prefix_notification_with_app">N谩zov aplik谩cie v ozn谩men铆</string>
+    <string name="title_activity_device_specific_settings">艩pecifick茅 nastavenia zariaden铆</string>
+    <string name="pref_cache_weather">Ukladanie inform谩ci铆 o po膷as铆 do mezipam盲te</string>
+    <string name="activity_summaries_all_devices">V拧etky zariadenia</string>
+    <string name="devicesetting_scannable_rssi">Minim谩lna hranica RSSI</string>
+    <string name="bottom_nav_devices">Zariadenia</string>
+    <string name="info_no_devices_connected">Nie s煤 pripojen茅 啪iadne zariadenia</string>
+    <string name="info_connected_count">Pripojen茅 zariadenia: %d</string>
+    <string name="devicestatus_disconnecting">Zariadenie sa odp谩ja!</string>
+    <string name="devicestatus_upload_starting">Nahr谩vanie za膷铆na</string>
+    <string name="devicestatus_upload_aborted">Nahr谩vanie bolo preru拧en茅!</string>
+    <string name="devicesetting_scannable_minimum_unseen">Minim谩lny 膷as nevidenia (sekundy)</string>
+    <string name="pref_dashboard_devices_to_include">Zahrnut茅 zariadenia</string>
+    <string name="pref_dashboard_all_devices_title">V拧etky zariadenia</string>
+    <string name="devicestatus_connecting">Zariadenie sa prip谩ja</string>
+    <string name="devicestatus_upload_failed">Do拧lo k chybe pri nahr谩van铆</string>
+    <string name="devicestatus_upload_completed">Nahr谩vanie ukon膷en茅</string>
+    <string name="pref_dashboard_select_devices_title">Vybra钮 zariadenia...</string>
+    <string name="scan_scanning_all_devices">Skenovanie v拧etk媒ch zariaden铆</string>
+    <string name="devicesetting_scannable_debounce">膶asov媒 limit odozvy skenovania (sekundy)</string>
+    <string name="devicestatus_disconnected">Zariadenie sa odpojilo!</string>
+    <string name="devicestatus_upload_started">Nahr谩vanie za膷alo</string>
+    <string name="devicestatus_upload_validating">Kontrola nahrat媒ch 煤dajov</string>
+    <string name="devicestatus_connected">Zariadenie je pripojen茅</string>
+    <plurals name="amount_of_days" tools:ignore="MissingQuantity">
+        <item quantity="one">%d de艌</item>
+        <item quantity="few">%d dni</item>
+        <item quantity="other">%d dn铆</item>
+    </plurals>
+    <string name="find_lost_device_message">Vyh木ada钮 %1$s?</string>
+    <string name="battery_detail_activity_title">Inform谩cia o bat茅rii</string>
+    <string name="calendar_month">Mesiac</string>
+    <string name="calendar_six_months">6 mesiacov</string>
+    <string name="calendar_year">Rok</string>
+    <string name="auto_reconnect_ble_title">Automatick茅 op盲tovn茅 pripojenie k zariadeniu</string>
+    <string name="activity_type_exercise">Cvi膷enie</string>
+    <string name="sports_activity_quick_filter_30days">30 dn铆</string>
+    <string name="calendar_two_weeks">Dva t媒啪dne</string>
+    <string name="connection_over_ble">Pripojenie cez BLE</string>
+    <string name="charts_activity_list">Zoznam aktiv铆t</string>
+    <string name="activity_type_worn">Nosen茅</string>
+    <string name="sports_activity_quick_filter_7days">7 dn铆</string>
+    <string name="calendar_three_months">3 mesiace</string>
+    <string name="pref_header_calendar">Kalend谩r</string>
+    <string name="activity_type_fitness_exercises">Fitness cvi膷enie</string>
+    <string name="dashboard_calendar_month_goals_reached_title">Dosaiahnut茅 %% cie木a krokov</string>
+    <string name="maximum_duration">D暮啪ka</string>
+    <string name="calendar_week">T媒啪de艌</string>
+    <string name="calendar_day">De艌</string>
+    <string name="qhybrid_goal_in_steps">Cie木 v krokoch</string>
+    <string name="battery_full_threshold">Hranica pln茅ho nabitia</string>
+    <string name="show_in_notification">Zobrazi钮 v ozn谩men铆</string>
+    <string name="battery_low_notify_enabled">Ozn谩menie pri n铆zkej 煤rovni bat茅rie</string>
+    <string name="battery_low_threshold">Hranica n铆zkej 煤rovne bat茅rie</string>
+    <string name="battery_full_notify_enabled">Ozn谩menie pri plnom nabit铆</string>
+    <string name="activity_type_not_worn">Zariadenie nepou啪铆van茅</string>
+</resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-sr/strings.xml b/app/src/main/res/values-sr/strings.xml
new file mode 100644
index 0000000..0a278e6
--- /dev/null
+++ b/app/src/main/res/values-sr/strings.xml
@@ -0,0 +1,2016 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources xmlns:tools="http://schemas.android.com/tools">
+    <string name="find_lost_device_message">袛邪 谢懈 褌褉邪卸懈褌懈 %1$s\?</string>
+    <string name="controlcenter_disconnect">袩褉械泻懈薪懈 胁械蟹褍</string>
+    <string name="controlcenter_navigation_drawer_close">袟邪褌胁芯褉懈 懈褋褑褉褌邪胁邪谢芯 薪邪胁芯褣械褮邪</string>
+    <string name="controlcenter_snackbar_need_longpress">袛褍谐芯 锌褉懈褌懈褋薪懈褌械 泻邪褉褌懈褑褍 褉邪写懈 锌褉械泻懈写邪 胁械蟹械</string>
+    <string name="controlcenter_snackbar_connecting">袩芯胁械蟹懈胁邪褮械鈥�</string>
+    <string name="controlcenter_snackbar_requested_screenshot">校褋谢懈泻邪胁邪屑 蟹邪褋谢芯薪 褍褉械褣邪褬邪</string>
+    <string name="controlcenter_calibrate_device">袘邪卸写邪褉懈 褍褉械褣邪褬</string>
+    <string name="controlcenter_get_heartrate_measurement">袠蟹屑械褉懈褌械 褋褉褑械</string>
+    <string name="device_card_activity_card_title">袩芯写邪褑懈 芯 写械褬褋褌胁懈屑邪 薪邪 泻邪褉褌懈褑懈 褍褉械褣邪褬邪</string>
+    <string name="device_card_activity_card_title_summary">袠蟹邪斜械褉懈褌械 泻芯褬械 锌芯褬械写懈薪芯褋褌懈 写械褬褋褌邪胁邪 褯械 褋械 锌褉懈泻邪蟹懈胁邪褌懈 薪邪 泻邪褉褌懈褑懈 褍褉械褣邪褬邪</string>
+    <string name="prefs_activity_in_device_card_title">袩褉懈泻邪蟹褍褬 锌芯写邪褌泻械 写械褬褋褌邪胁邪 薪邪 泻邪褉褌懈褑懈 褍褉械褣邪褬邪</string>
+    <string name="prefs_activity_in_device_card_sleep_title_summary">袩褉懈泻邪蟹褍褬 褌褉邪褬邪褮械 褋锌邪胁邪褮邪</string>
+    <string name="prefs_activity_in_device_card_steps_title_summary">袩褉懈泻邪蟹褍褬 褍泻褍锌邪薪 斜褉芯褬 泻芯褉邪泻邪</string>
+    <string name="battery_level">袩芯锌褍褮械薪芯褋褌 斜邪褌械褉懈褬械</string>
+    <string name="calendar_week">小械写屑懈褑邪</string>
+    <string name="calendar_two_weeks">小胁械 褋械写屑懈褑械</string>
+    <string name="calendar_six_months">6 屑械褋械褑懈</string>
+    <string name="calendar_year">袚芯写懈薪邪</string>
+    <string name="title_activity_debug">袩褉芯胁械褉邪 斜褍斜邪</string>
+    <string name="title_activity_appmanager">校锌褉邪胁薪懈泻 锌褉芯谐褉邪屑邪</string>
+    <string name="appmanager_installed_watchapps">校谐褉邪褣械薪懈 锌褉芯谐褉邪屑懈</string>
+    <string name="appmanager_installed_watchfaces">校谐褉邪褣械薪邪 谢懈褑邪 褔邪褋芯胁薪懈泻邪</string>
+    <string name="appmananger_app_delete_cache">袠蟹斜褉懈褕懈 懈 褍泻谢芯薪懈 懈蟹 屑械褣褍褋锌褉械屑懈褕褌邪</string>
+    <string name="appmanager_app_openinstore">孝褉邪卸懈 褍 锌褉芯写邪胁薪懈褑懈 锌褉芯谐褉邪屑邪 袩械斜谢邪</string>
+    <string name="appmanager_health_activate">袩芯泻褉械薪懈</string>
+    <string name="appmanager_hrm_activate">袩芯泻褉械薪懈 啸袪袦</string>
+    <string name="appmanager_hrm_deactivate">袠褋泻褭褍褔懈 啸袪袦</string>
+    <string name="appmanager_weather_activate">袩芯泻褉械薪懈 锌褉芯谐褉邪屑 胁褉械屑械薪褋泻械 锌褉芯谐薪芯蟹械 褋泻谢芯锌邪</string>
+    <string name="appmanager_weather_install_provider">校谐褉邪写懈 锌褉芯谐褉邪屑 蟹邪 胁械褋褌懈 芯 胁褉械屑械薪褍</string>
+    <string name="app_move_to_top">袩褉械屑械褋褌懈 薪邪 胁褉褏</string>
+    <string name="title_activity_notification_management">袩芯褋褌邪胁泻械 芯斜邪胁械褕褌械褮邪</string>
+    <string name="blacklist_all_for_notifications">袠褋泻褭褍褔懈 褋胁械 懈蟹 芯斜邪胁械褕褌械褮邪</string>
+    <string name="check_all_applications">袨蟹薪邪褔懈 褋胁械 锌褉芯谐褉邪屑械</string>
+    <string name="uncheck_all_applications">袨写蟹薪邪褔懈 褋胁械 锌褉芯谐褉邪屑械</string>
+    <string name="title_activity_calblacklist">袠褋泻褭褍褔械薪懈 泻邪谢械薪写邪褉懈</string>
+    <string name="action_debug">袪邪蟹斜褍斜邪褌懈</string>
+    <string name="controlcenter_power_off">袠褋泻褭褍褔懈</string>
+    <string name="action_quit">袧邪锌褍褋褌懈</string>
+    <string name="action_donate">袩褉懈谢芯卸懈褌械</string>
+    <string name="controlcenter_fetch_activity_data">校褋泻谢邪写懈</string>
+    <string name="controlcenter_find_device">袩褉芯薪邪褣懈 懈蟹谐褍斜褭械薪懈 褍褉械褣邪褬</string>
+    <string name="controlcenter_take_screenshot">校褋谢懈泻邪褬 蟹邪褋谢芯薪</string>
+    <string name="controlcenter_power_off_confirm_title">袠褋泻褭褍褔懈</string>
+    <string name="controlcenter_power_off_confirm_description">袛邪 谢懈 褋褌械 褋懈谐褍褉薪懈 写邪 卸械谢懈褌械 懈褋泻褭褍褔懈褌懈 褍褉械褣邪褬\?</string>
+    <string name="controlcenter_change_led_color">袩褉芯屑械薪懈 斜芯褬褍 褋胁械褌谢械褯械 写懈芯写械</string>
+    <string name="controlcenter_change_fm_frequency">袩褉芯屑械薪懈 褍褔械褋褌邪谢芯褋褌 褉邪写懈褬邪</string>
+    <string name="controlcenter_delete_device">袠蟹斜褉懈褕懈 褍褉械褣邪褬</string>
+    <string name="controlcenter_delete_device_dialogmessage">袨胁芯 褯械 懈蟹斜褉懈褋邪褌懈 褍褉械褣邪褬 懈 褋胁械 屑褍 胁械蟹邪薪械 锌芯写邪褌泻械!</string>
+    <string name="controlcenter_connect">袩芯胁械卸懈鈥�</string>
+    <string name="controlcenter_navigation_drawer_open">袨褌胁芯褉懈 懈褋褑褉褌邪胁邪谢芯 薪邪胁芯褣械褮邪</string>
+    <string name="prefs_activity_in_device_card_title_summary">袩褉懈泻邪蟹褍褬 褌褉械薪褍褌薪械 泻芯褉邪泻械, 褉邪褋褌芯褬邪褮邪 懈谢懈 褋锌邪胁邪褮械 薪邪 泻邪褉褌懈褑懈 褍褉械褣邪褬邪</string>
+    <string name="prefs_activity_in_device_card_sleep_title">小锌邪胁邪褮械</string>
+    <string name="controlcenter_delete_device_name">袠蟹斜褉懈褕懈 %1$s</string>
+    <string name="controlcenter_snackbar_disconnecting">袩褉械泻懈薪懈 胁械蟹褍</string>
+    <string name="battery_detail_activity_title">袩芯写邪褑懈 芯 斜邪褌械褉懈褬懈</string>
+    <string name="controlcenter_set_alias">袩芯写械褋懈 薪邪写懈屑邪泻</string>
+    <string name="prefs_activity_in_device_card_distance_title_summary">袪邪褋褌芯褬邪褮械 褋械 褉邪褔褍薪邪 懈蟹 斜褉芯褬邪 懈 写褍卸懈薪械 泻芯褉邪泻邪 (锌芯写械褋懈胁芯 褍 袩芯褋褌邪胁泻邪屑邪 - 袨 袙邪屑邪)</string>
+    <string name="calendar_month">袦械褋械褑</string>
+    <string name="debugactivity_really_factoryreset_title">袟邪懈褋褌邪 胁褉邪褌懈褌懈 薪邪 锌芯褔械褌薪械 胁褉械写薪芯褋褌懈\?</string>
+    <string name="appmananger_app_delete">袠蟹斜褉懈褕懈</string>
+    <string name="appmanager_app_share">袛械谢懈</string>
+    <string name="appmanager_health_deactivate">袠褋泻褭褍褔懈</string>
+    <string name="app_configure">袩芯写械褋懈</string>
+    <string name="appmanager_item_outdated">(蟹邪褋褌邪褉械谢芯)</string>
+    <string name="appmanager_weather_deactivate">袠褋泻褭褍褔懈 锌褉芯谐褉邪屑 胁褉械屑械薪褋泻械 锌褉芯谐薪芯蟹械 褋泻谢芯锌邪</string>
+    <string name="whitelist_all_for_notifications">袨写芯斜褉懈 褋胁械 褍 芯斜邪胁械褕褌械褮懈屑邪</string>
+    <string name="calendar_day">袛邪薪</string>
+    <string name="calendar_three_months">3 屑械褋械褑邪</string>
+    <string name="appmananger_app_reinstall">袩芯薪芯胁芯 褍谐褉邪写懈</string>
+    <string name="debugactivity_really_factoryreset">袙褉邪褯邪褮械 薪邪 锌芯褔械褌薪械 胁褉械写薪芯褋褌懈 褍褉械褣邪褬邪 褯械 懈蟹斜褉懈褋邪褌懈 褋胁械 锌芯写邪褌泻械 褋邪 锌芯胁械蟹邪薪芯谐 褍褉械褣邪褬邪 (邪泻芯 褬械 褌芯 锌芯写褉卸邪薪芯). 楔邪芯屑懈褬械胁懈/啸褍邪屑懈褬械胁懈 褍褉械褣邪褬懈 褌邪泻芯褣械 屑械褮邪褬褍 邪写褉械褋褍 袦袗笑, 褌邪泻芯 写邪 懈蟹谐谢械写邪褬褍 泻邪芯 薪芯胁懈 褍褉械褣邪褬懈 褍 锌褉芯谐褉邪屑褍.</string>
+    <string name="appmanager_cached_watchapps_watchfaces">袩褉芯谐褉邪屑懈 褍 屑械褣褍褋锌褉械屑懈褕褌褍</string>
+    <string name="pref_title_notifications_sms">小袦小</string>
+    <string name="open_fw_installer_connect_minimum_one_device">袦芯谢懈屑, 锌芯胁械卸懈褌械 袘袗袪 袌袝袛袗袧 褍褉械褣邪褬 泻芯屑械 卸械谢懈褌械 写邪 芯褌锌褉械屑懈褌械 写邪褌芯褌械泻褍.</string>
+    <string name="pref_header_display">袩褉懈泻邪蟹</string>
+    <string name="pref_title_notifications_call">袩芯蟹懈胁懈</string>
+    <string name="open_fw_installer_getting_files_title">袛芯斜邪胁褭邪褮械 写邪褌芯褌械泻械 写褉邪屑邪/锌褉芯谐褉邪屑邪</string>
+    <string name="open_fw_installer_warning_title">校锌芯蟹芯褉械褮械</string>
+    <string name="fw_upgrade_notice_amazfitbip">校谐褉邪写懈褯械褌械 写褉邪屑 %s 薪邪 褋胁芯屑 袗屑邪蟹褎懈褌邪 袘懈锌褍.
+\n
+\n校褌胁褉写懈褌械 写邪 褍谐褉邪褣褍褬械褌械 写邪褌芯褌械泻褍 .fw, 蟹邪褌懈屑 写邪褌芯褌械泻褍 .res, 懈 薪邪 泻芯薪褑褍 写邪褌芯褌械泻褍 .gps . 袙邪褕 褋邪褌 褯械 褋械 锌芯薪芯胁芯 锌芯泻褉械薪褍褌懈 薪邪泻芯薪 褍谐褉邪写褮械 写邪褌芯褌械泻械 .fw.
+\n
+\n袩褉懈屑械写斜邪: 薪械 屑芯褉邪 写邪 褍谐褉邪写懈褌械 写邪褌芯褌械泻械 .res 懈 .gps 邪泻芯 褋褍 褌械 写邪褌芯褌械泻械 懈褋褌芯胁械褌薪械 泻邪芯 芯薪械 泻芯褬械 褋褍 锌褉械褌褏芯写薪芯 褍谐褉邪褣械薪械.
+\n
+\n袧袗小孝袗袙袎袗孝袝 袧袗 小袨袩小孝袙袝袧校 袨袛袚袨袙袨袪袧袨小孝!</string>
+    <string name="open_fw_installer_info_text_title">校谐褉邪褣懈胁邪褔 写邪褌芯褌械泻械</string>
+    <string name="pref_header_calendar">袣邪谢械薪写邪褉</string>
+    <string name="open_fw_installer_select_file">袨写邪斜械褉懈褌械 写邪褌芯褌械泻褍 泻芯褬褍 卸械谢懈褌械 写邪 芯褌锌褉械屑懈褌械 薪邪 褍褉械褣邪褬: %s</string>
+    <string name="pref_header_connection">袙械蟹邪</string>
+    <string name="pref_header_health">袟写褉邪胁褭械</string>
+    <string name="pref_header_time">袙褉械屑械</string>
+    <string name="pref_header_notifications">袨斜邪胁械褕褌械褮邪</string>
+    <string name="pref_title_ping_tone">袟胁褍泻 锌懈薪谐邪</string>
+    <string name="pref_title_notification_delay_calls">袨写谐芯写邪 锌芯蟹懈胁薪芯谐 芯斜邪胁械褕褌械褮邪</string>
+    <string name="pref_summary_notification_delay_calls">袨写谐芯写邪 锌褉械 褋谢邪褮邪 芯斜邪胁械褕褌械褮邪 芯 锌芯蟹懈胁褍 薪邪 褍褉械褣邪褬, 褍 褋械泻褍薪写邪屑邪.</string>
+    <string name="pref_title_support_voip_calls">袨屑芯谐褍褯懈 锌褉芯谐褉邪屑械 蟹邪 褉邪蟹谐芯胁芯褉 锌褉械泻芯 懈薪褌械褉薪械褌邪</string>
+    <string name="pref_title_notification_prefer_long_text">袪邪写懈褬械 褕邪褭懈 写褍谐邪褔泻邪 锌懈褋邪薪邪 芯斜邪胁械褕褌械褮邪</string>
+    <string name="pref_title_notification_filter">袧械 褍蟹薪械屑懈褉邪胁邪褬</string>
+    <string name="pref_header_notification_application_settings">袩芯褬械写懈薪邪褔薪械 锌芯褋褌邪胁泻械 锌褉芯谐褉邪屑邪</string>
+    <string name="pref_title_notification_use_as">袣芯褉懈褋褌懈 褋锌懈褋邪泻 锌褉芯谐褉邪屑邪 褉邪写懈鈥�</string>
+    <string name="pref_title_notification_use_as_allow">袛芯蟹胁芯褭邪胁邪褮械 芯斜邪胁械褕褌械褮邪 芯蟹薪邪褔械薪懈褏 锌褉芯谐褉邪屑邪</string>
+    <string name="pref_title_message_privacy_mode">袩褉懈胁邪褌薪芯褋褌 锌芯褉褍泻邪</string>
+    <string name="pref_message_privacy_mode_off">袩褉懈泻邪蟹褍褬 褋邪胁 褋邪写褉卸邪褬</string>
+    <string name="pref_summary_banglejs_text_bitmap">袗泻芯 谢懈 褉械褔 薪械 屑芯卸械 斜懈褌懈 锌褉懈泻邪蟹邪薪邪 薪邪 褋谢芯胁芯谢懈泻褍 褔邪褋芯胁薪懈泻邪, 锌褉械褌胁芯褉懈 褬械 褍 斜懈褌屑邪锌褍 泻褉芯蟹 锌褉芯谐褉邪屑 懈 锌褉懈泻邪卸懈 斜懈褌屑邪锌褍 薪邪 褔邪褋芯胁薪懈泻褍</string>
+    <string name="pref_summary_rtl">袨屑芯谐褍褯懈褌械 芯胁芯 邪泻芯 谢懈 袙邪褕 褍褉械褣邪褬 薪械 锌芯写褉卸邪胁邪 褬械蟹懈泻械 锌懈褋邪褮邪 蟹写械褋薪邪-薪邪谢械胁芯</string>
+    <string name="pref_title_rtl">袟写械褋薪邪-薪邪谢械胁芯</string>
+    <string name="when_screen_off">袣邪写邪 褬械 蟹邪褋谢芯薪 懈褋泻褭褍褔械薪</string>
+    <string name="pref_call_privacy_mode_number">小邪泻褉懈褬 斜褉芯褬, 邪谢懈 锌褉懈泻邪蟹褍褬 懈屑械</string>
+    <string name="pref_title_canned_messages_dismisscall">袨褌泻邪蟹懈胁邪褮械 锌芯蟹懈胁邪</string>
+    <string name="pref_title_canned_messages_set">袨褋胁械卸懈 薪邪 褍褉械褣邪褬褍</string>
+    <string name="pref_title_sunrise_sunset">袠蟹谢邪蟹邪泻 懈 蟹邪谢邪蟹邪泻 褋褍薪褑邪</string>
+    <string name="pref_title_enable_calendar_sync">校褋泻谢邪写懈 泻邪谢械薪写邪褉</string>
+    <string name="pref_title_pebble_forceprotocol">袩褉懈褋懈谢懈 锌芯褋褌褍锌邪泻 芯斜邪胁械褕褌械褮邪</string>
+    <string name="pref_summary_custom_deviceicon">袩褉懈泻邪蟹褍褬 芯写褉械褣械薪褍 褋谢懈褔懈褑褍 芯斜邪胁械褕褌械褮邪 袗薪写褉芯懈写邪 褍屑械褋褌芯 褋谢懈褔懈褑褍 锌褉芯谐褉邪屑邪 褍 锌芯胁械蟹懈胁邪褮褍</string>
+    <string name="pref_summary_location_keep_uptodate">袩芯泻褍褕邪褬 写邪 写芯斜邪胁褭邪褕 褌褉械薪褍褌薪懈 锌芯谢芯卸邪褬 锌褉懈 褉邪写褍, 泻芯褉懈褋褌懈 褋邪褔褍胁邪薪懈 锌芯谢芯卸邪褬 蟹邪 蟹邪屑械薪褍</string>
+    <string name="pref_title_pebble_forceuntested">袨屑芯谐褍褯懈 薪械锌褉芯胁械褉械薪械 屑芯谐褍褯薪芯褋褌懈</string>
+    <string name="pref_title_connected_advertisement">袙懈写褭懈胁 锌褉懈 锌芯胁械蟹邪薪芯褋褌懈</string>
+    <string name="pref_title_pebble_enable_applogs">袨屑芯谐褍褯懈 写薪械胁薪懈泻 锌褉芯谐褉邪屑邪 褔邪褋芯胁薪懈泻邪</string>
+    <string name="pref_title_pebble_enable_bgjs">袨屑芯谐褍褯懈 袌小 褍 锌芯蟹邪写懈薪懈</string>
+    <string name="pref_summary_use_custom_font">袨屑芯谐褍褯懈褌械 芯胁芯 邪泻芯 谢懈 袙邪褕 褍褉械褣邪褬 懈屑邪 褋芯锌褋褌胁械薪懈 写褉邪屑 褋谢芯胁芯谢懈泻邪 蟹邪 锌芯写褉褕泻褍 械屑芯褬邪</string>
+    <string name="pref_title_device_spec_settings_show_raw_graph">袩褉懈泻邪蟹褍褬 褋懈褉芯胁械 锌芯写邪褌泻械 薪邪 褑褉褌械卸褍 写械褬褋褌胁邪</string>
+    <string name="pref_display_add_device_fab">袛褍谐屑械 蟹邪 锌芯胁械蟹懈胁邪褮械 薪芯胁芯谐 褍褉械褣邪褬邪</string>
+    <string name="pref_header_notifications_and_calls">袨斜邪胁械褕褌械褮邪 懈 锌芯蟹懈胁懈</string>
+    <string name="pref_title_notifications_and_calls_repeat_on_call">袩芯薪芯胁懈 褋胁邪 芯斜邪胁械褕褌械褮邪</string>
+    <string name="pref_summary_notifications_and_calls_title_reject">袠褋泻褭褍褔械薪芯 鈥� 蟹邪薪械屑邪褉懈, 校泻褭褍褔械薪芯 鈥� 芯写斜懈</string>
+    <string name="prefs_sensors_button_bp_calibration_sum">袩褉懈褌懈褋薪懈褌械 芯胁写械 蟹邪 锌芯褔械褌邪泻 斜邪卸写邪褉械褮邪</string>
+    <string name="pref_header_device_spec_settings">袩芯褋褌邪胁泻械 褍褉械褣邪褬邪</string>
+    <string name="pref_header_sensors_calibration">袘邪卸写邪褉械褮械 褔褍谢邪</string>
+    <string name="pref_sensors_bp_calibration">袘邪卸写邪褉械褮械 泻褉胁薪芯谐 锌褉懈褌懈褋泻邪</string>
+    <string name="prefs_sensors_button_bp_calibration">袘邪卸写邪褉械褮械</string>
+    <string name="about_description_generic">袧械芯斜谢邪褔懈薪褋泻邪 谢械胁芯褍屑薪芯卸懈胁邪 褋谢芯斜芯写薪邪 蟹邪屑械薪邪 蟹邪 薪械褋谢芯斜芯写薪械 锌褉芯谐褉邪屑械 锌褉芯写邪胁邪褑邪 褋锌褉邪胁懈褑邪 袗薪写褉芯懈写邪.</string>
+    <string name="action_settings">袩芯褋褌邪胁泻械</string>
+    <string name="title_activity_fw_app_insaller">校谐褉邪褣懈胁邪褔 写褉邪屑邪/锌褉芯谐褉邪屑邪</string>
+    <string name="fw_upgrade_notice">校谐褉邪写懈褯械褌械 %s.</string>
+    <string name="pref_title_notifications_timeout">袧邪褬屑邪褮械 胁褉械屑械 懈蟹屑械褣褍 芯斜邪胁械褕褌械褮邪</string>
+    <string name="pref_title_notifications_generic">袨锌褕褌邪 锌芯写褉褕泻邪 芯斜邪胁械褕褌械褮邪</string>
+    <string name="pref_title_notifications_generic_settings">袩芯褋褌邪胁泻械 芯斜邪胁械褕褌械褮邪 袗薪写褉芯懈写邪</string>
+    <string name="pref_title_whenscreenon">鈥ρ傂靶盒狙捫� 泻邪写邪 褬械 蟹邪褋谢芯薪 褍泻褭褍褔械薪</string>
+    <string name="pref_title_notifications_ignore_low_priority">袟邪薪械屑邪褉懈 屑邪褮械 斜懈褌薪邪 芯斜邪胁械褕褌械褮邪</string>
+    <string name="pref_summary_notifications_ignore_low_priority">袧械 褕邪褭懈 薪械斜懈褌薪邪 懈 屑邪褮械 斜懈褌薪邪 芯斜邪胁械褕褌械褮邪 褔邪褋芯胁薪懈泻褍</string>
+    <string name="pref_summary_notification_prefer_long_text">袗泻芯 谢懈 褬械 写芯褋褌褍锌薪芯, 褕邪褭懈 写褍谐邪褔泻邪 芯斜邪胁械褕褌械褮邪 褍褉械褣邪褬褍</string>
+    <string name="pref_summary_notification_filter">袟邪褍褋褌邪胁懈 褋胁邪 芯斜邪胁械褕褌械褮邪 写芯泻 褬械 薪邪 褌械谢械蟹胁芯薪褍 褍泻褭褍褔械薪芯 薪械 褍蟹薪械屑懈褉邪胁邪褬</string>
+    <string name="pref_title_notification_use_as_deny">袟邪斜褉邪褮懈胁邪褮械 芯斜邪胁械褕褌械褮邪 芯蟹薪邪褔械薪懈褏 锌褉芯谐褉邪屑邪</string>
+    <string name="pref_title_transliteration">袩褉械褋谢芯胁褭邪胁邪褮械</string>
+    <string name="pref_summary_transliteration">袨屑芯谐褍褯懈褌械 芯胁芯 邪泻芯 袙邪屑 褍褉械褣邪褬 薪械屑邪 锌芯写褉褕泻褍 蟹邪 褋谢芯胁芯谢懈泻 袙邪褕械谐 褬械蟹懈泻邪</string>
+    <string name="pref_title_banglejs_text_bitmap">袩懈褋屑芯 泻邪芯 斜懈褌屑邪锌械</string>
+    <string name="pref_title_banglejs_txt_bitmap_size">袙械谢懈褔懈薪邪 锌懈褋屑邪 斜懈褌屑邪锌械</string>
+    <string name="always">校胁械泻</string>
+    <string name="never">袧懈泻邪写邪</string>
+    <string name="pref_header_privacy">袩褉懈胁邪褌薪芯褋褌</string>
+    <string name="pref_call_privacy_mode_off">袩褉懈泻邪蟹褍褬 懈屑械 懈 斜褉芯褬</string>
+    <string name="pref_title_call_privacy_mode">袩褉懈胁邪褌薪芯褋褌 锌芯蟹懈胁邪</string>
+    <string name="pref_call_privacy_mode_name">小邪泻褉懈褬 懈屑械, 邪谢懈 锌褉懈泻邪蟹褍褬 斜褉芯褬</string>
+    <string name="pref_call_privacy_mode_complete">小邪泻褉懈褬 懈屑械 懈 斜褉芯褬</string>
+    <string name="pref_message_privacy_mode_complete">小邪泻褉懈褬 褋邪胁 褋邪写褉卸邪褬</string>
+    <string name="pref_title_weather">袙褉械屑械</string>
+    <string name="pref_title_weather_location">袩芯谢芯卸邪褬 胁褉械屑械薪邪 (蟹邪 写芯斜邪胁褭邪褔邪 胁褉械屑械薪邪 袥懈薪懈邪谐袨小-邪)</string>
+    <string name="pref_applications_settings">小锌懈褋邪泻 锌褉芯谐褉邪屑邪</string>
+    <string name="pref_title_canned_replies">袨写谐芯胁芯褉懈</string>
+    <string name="pref_summary_canned_replies">袨写谐芯胁芯褉懈 褋邪 褔邪褋芯胁薪懈泻邪 锌褉懈锌褉械屑褭械薪懈屑 锌芯褉褍泻邪屑邪</string>
+    <string name="pref_title_canned_reply_suffix">校芯斜懈褔邪褬械薪懈 薪邪褋褌邪胁邪泻</string>
+    <string name="pref_header_development">袦芯谐褍褯薪芯褋褌懈 褉邪蟹胁芯褬邪</string>
+    <string name="pref_summary_enable_calendar_sync">楔邪褭懈 写芯谐邪褣邪褬械 泻邪谢械薪写邪褉邪 胁褉械屑械薪褋泻芯褬 谢懈薪懈褬懈</string>
+    <string name="pref_title_custom_deviceicon">袩褉懈泻邪蟹褍褬 芯写褉械褣械薪褍 褋谢懈褔懈褑褍 芯斜邪胁械褕褌械褮邪 褍褉械褣邪褬邪</string>
+    <string name="pref_title_autoremove_notifications">小邪屑芯褍泻谢芯薪懈 芯褌泻邪蟹邪薪邪 芯斜邪胁械褕褌械褮邪</string>
+    <string name="pref_summary_autoremove_notifications">袨斜邪胁械褕褌械褮邪 褋械 褋邪屑芯褋褌邪谢薪芯 褍泻谢邪褮邪褬褍 褋邪 褍褉械褣邪褬邪 泻邪写 褋械 芯褌泻邪卸褍 褋邪 褌械谢械蟹胁芯薪邪</string>
+    <string name="pref_title_screen_on_on_notifications">校泻褭褍褔懈胁邪褮械 蟹邪褋谢芯薪邪 锌褉懈 芯斜邪胁械褕褌械褮懈屑邪</string>
+    <string name="pref_summary_screen_on_on_notifications">校泻褭褍褔懈 蟹邪褋谢芯薪 褍褉械褣邪褬邪 泻邪写邪 褋褌懈谐薪械 芯斜邪胁械褕褌械褮械</string>
+    <string name="pref_title_pebble_privacy_mode">袩褉懈胁邪褌薪懈 褉邪写</string>
+    <string name="pref_pebble_privacy_mode_off">校芯斜懈褔邪褬械薪邪 芯斜邪胁械褕褌械褮邪</string>
+    <string name="pref_pebble_privacy_mode_content">袦邪泻薪懈 锌懈褋屑芯 芯斜邪胁械褕褌械褮邪 褋邪 蟹邪褋谢芯薪邪</string>
+    <string name="pref_pebble_privacy_mode_complete">袩褉懈泻邪蟹褍褬 褋邪屑芯 褋谢懈褔懈褑褍 芯斜邪胁械褕褌械褮邪</string>
+    <string name="pref_header_location">袩芯谢芯卸邪褬</string>
+    <string name="pref_title_location_aquire">袛芯斜邪胁懈 锌芯谢芯卸邪褬</string>
+    <string name="pref_title_location_latitude">袙懈褋懈薪邪</string>
+    <string name="pref_title_location_longitude">楔懈褉懈薪邪</string>
+    <string name="pref_title_location_keep_uptodate">袨褋胁械卸邪胁邪褬 锌芯谢芯卸邪褬</string>
+    <string name="toast_enable_networklocationprovider">袦芯谢懈屑, 芯屑芯谐褍褯懈褌械 屑褉械卸薪懈 锌芯谢芯卸邪褬</string>
+    <string name="toast_aqurired_networklocation">写芯斜邪胁褭械薪 褬械 锌芯谢芯卸邪褬</string>
+    <string name="pref_summary_pebble_forceprotocol">袨胁邪 屑芯谐褍褯薪芯褋褌 锌褉懈褋懈褭邪胁邪 褍锌芯褌褉械斜褍 薪邪褬薪芯胁懈褬械谐 锌芯褋褌褍锌泻邪 褍 蟹邪胁懈褋薪芯褋褌懈 薪邪 懈蟹写邪褮械 写褉邪屑邪. 袩袪袨袙袝袪袠孝袝 楔孝袗 袪袗袛袠孝袝!</string>
+    <string name="pref_summary_pebble_forceuntested">袨屑芯谐褍褯懈 薪械锌褉芯胁械褉械薪械 屑芯谐褍褯薪芯褋褌懈. 袩袪袨袙袝袪袠孝袝 楔孝袗 袪袗袛袠孝袝!</string>
+    <string name="pref_title_pebble_forcele">校胁械泻 写邪褬 锌褉械写薪芯褋褌 袘袥袝-褍</string>
+    <string name="pref_summary_pebble_enable_bgjs">袣邪写邪 褬械 芯屑芯谐褍褯械薪芯, 写芯蟹胁芯褭邪胁邪 褔邪褋芯胁薪懈泻褍 锌褉懈泻邪蟹 胁褉械屑械薪邪, 锌芯写邪褌邪泻邪 芯 斜邪褌械褉懈褬懈, 懈褌写.</string>
+    <string name="pref_title_pebble_reconnect_attempts">袩芯泻褍褕邪褬懈 锌褉械锌芯胁械蟹懈胁邪褮邪</string>
+    <string name="pref_summary_connected_advertisement">效懈薪懈 褍褉械褣邪褬 锌褉芯薪邪谢邪卸褭懈胁懈屑 锌褉械泻芯 袘谢褍褌褍褌邪 褔邪泻 懈 泻邪写邪 褬械 锌芯胁械蟹邪薪</string>
+    <string name="pref_title_use_custom_font">袣芯褉懈褋褌懈 锌褉芯懈蟹胁芯褭薪懈 褋谢芯胁芯谢懈泻</string>
+    <string name="pref_title_allow_high_mtu">袨屑芯谐褍褯懈 胁懈褋芯泻懈 袦孝校</string>
+    <string name="pref_title_overwrite_settings_on_connection">袩褉械锌懈褕懈 锌芯褋褌邪胁泻械 锌褉懈 锌芯胁械蟹懈胁邪褮褍</string>
+    <string name="pref_summary_overwrite_settings_on_connection">袩褉懈 锌芯胁械蟹懈胁邪褮褍 褋邪 褍褉械褣邪褬械屑, 锌褉械斜褉懈褕懈 褋胁械 锌芯褋褌邪胁泻械 薪邪 褮械屑褍.</string>
+    <string name="pref_summary_allow_high_mtu">校斜褉蟹邪胁邪 斜褉蟹懈薪褍 锌褉械薪芯褋邪, 邪谢懈 屑芯卸写邪 薪械褯械 褉邪斜芯褌懈褌懈 薪邪 薪械泻懈屑 褍褉械褣邪褬懈屑邪 袗薪写褉芯懈写邪.</string>
+    <string name="pref_title_device_internet_access">袛芯蟹胁芯谢懈 锌褉懈褋褌褍锌 褍褉械褣邪褬褍</string>
+    <string name="pref_summary_device_internet_access">袨屑芯谐褍褯邪胁邪 锌褉芯谐褉邪屑懈屑邪 薪邪 芯胁芯屑 褍褉械褣邪褬褍 锌褉懈褋褌褍锌 褍褉械褣邪褬褍</string>
+    <string name="pref_summary_sync_calendar">校泻褭褍褔褍褬械 谢邪褉屑械 泻邪谢械薪写邪褉邪, 褔邪泻 懈 锌褉懈谢懈泻芯屑 薪械锌芯胁械蟹邪薪芯褋褌懈</string>
+    <string name="pref_title_sync_caldendar">校褋泻谢邪写懈 写芯谐邪褣邪褬械 泻邪谢械薪写邪褉邪</string>
+    <string name="pref_summary_relax_firmware_checks">袨谢邪斜懈 锌褉芯胁械褉械 写褉邪屑邪</string>
+    <string name="pref_title_relax_firmware_checks">袨屑芯谐褍褯懈褌械 芯胁芯 邪泻芯 卸械谢懈褌械 写邪 褍褌懈褋泻邪褌械 写褉邪屑 泻芯褬懈 薪懈褬械 薪邪屑械褮械薪 袙邪褕械屑 褍褉械褣邪褬褍 (薪邪 褋芯锌褋褌胁械薪懈 褉懈蟹懈泻)</string>
+    <string name="pref_title_vibration_strength">袌邪褔懈薪邪 褌褉械褕褮械</string>
+    <string name="pref_display_add_device_fab_on">校胁械泻 胁懈写褭懈胁芯</string>
+    <string name="pref_display_add_device_fab_off">袙懈写褭懈胁芯 褋邪屑芯 邪泻芯 谢懈 褍褉械褣邪褬 薪懈褬械 写芯写邪褌</string>
+    <string name="pref_title_huami_force_new_protocol">袧芯胁懈 锌芯褋褌褍锌邪泻 锌褉懈褬邪胁械</string>
+    <string name="pref_summary_huami_force_new_protocol">袨屑芯谐褍褯懈 邪泻芯 谢懈 褋械 袙邪褕 褍褉械褣邪褬 薪械 锌芯胁械蟹褍褬械 薪邪泻芯薪 薪邪写谐褉邪写褮械 写褉邪屑邪</string>
+    <string name="pref_title_unit_system">袌械写懈薪懈褑械</string>
+    <string name="pref_title_timeformat">袨斜谢懈泻 胁褉械屑械薪邪</string>
+    <string name="pref_title_screentime">孝褉邪褬邪褮械 褍泻褭褍褔械薪芯谐 蟹邪褋谢芯薪邪</string>
+    <string name="prefs_title_all_day_heart_rate">笑械谢芯写薪械胁薪芯 屑械褉械褮械 褋褉褔邪薪懈褏 斜懈谢邪</string>
+    <string name="prefs_notifications_and_calls_continious_ring">袨斜邪胁械褕褌械褮邪 褌芯泻芯屑 蟹胁芯褮邪胁械 褌械谢械蟹胁芯薪邪</string>
+    <string name="pref_notifications_and_calls_enable_misscall">袨斜邪胁械褋褌懈 芯 锌褉芯锌褍褕褌械薪懈屑 锌芯蟹懈胁懈屑邪</string>
+    <string name="pref_summary_notifications_and_calls_enable_misscall">袩芯薪邪胁褭邪褬 褋胁邪泻芯谐 屑懈薪褍褌邪</string>
+    <string name="pref_title_notifications_and_calls_repeat_on_misscall">袩芯薪芯胁懈 褋胁邪泻懈褏 啸 屑懈薪褍褌邪</string>
+    <string name="pref_header_notifications_and_calls_callhandling">袪褍泻芯胁邪褮械 锌芯蟹懈胁芯屑</string>
+    <string name="prefs_notifications_and_calls_reject">袛褍谐屑械 蟹邪 蟹邪薪械屑邪褉懈胁邪褮械/芯写斜懈褬邪褮械 锌芯蟹懈胁邪</string>
+    <string name="prefs_notifications_and_calls_shake_reject">孝褉械褕褮邪 蟹谐谢芯斜芯屑 蟹邪薪械屑邪褉褍褬械/芯写斜懈褬邪 锌芯蟹懈胁</string>
+    <string name="pref_summary_notifications_and_calls_title_shake_reject">校写胁芯褋褌褉褍褔懈 褉邪写褮械 写褍谐屑械褌邪 褔邪褋芯胁薪懈泻邪</string>
+    <string name="pref_title_device_spec_settings_force_time">袩褉懈褋懈谢懈 胁褉械屑械 褍褋泻谢邪褣懈胁邪褮邪</string>
+    <string name="pref_summary_device_spec_settings_title_force_time">袩褉懈褋懈谢懈 褋邪屑芯褋褌邪谢薪芯 褍褋泻谢邪褣懈胁邪褮械 胁褉械屑械薪邪 锌褉懈 锌褉械锌芯胁械蟹懈胁邪褮褍. 袗薪邪谢芯谐薪械 褉褍泻械 屑芯谐褍 锌褉懈泻邪蟹邪褌懈 薪械褌邪褔薪芯 胁褉械屑械!</string>
+    <string name="pref_title_sensors_altitude">袘邪卸写邪褉械褮械 胁懈褋懈薪械</string>
+    <string name="prefs_screen_orientation">校褋屑械褉械褮械 蟹邪褋谢芯薪邪</string>
+    <string name="open_fw_installer_connect_maximum_one_device">袦芯谢懈屑, 锌芯胁械卸懈褌械 小袗袦袨 袌袝袛袗袧 褍褉械褣邪褬 泻芯屑械 卸械谢懈褌械 写邪 芯褌锌褉械屑懈褌械 写邪褌芯褌械泻褍.</string>
+    <string name="open_fw_installer_ensure_device_connected">校褌胁褉写懈褌械 写邪 褬械 褍褉械褣邪褬 %s 锌芯胁械蟹邪薪</string>
+    <string name="title_activity_settings">袩芯褋褌邪胁泻械</string>
+    <string name="pref_header_general">袨锌褕褌械 锌芯褋褌邪胁泻械</string>
+    <string name="pref_header_other">袪邪蟹薪芯</string>
+    <string name="pref_header_system">小泻谢芯锌</string>
+    <string name="pref_header_workout">袙械卸斜邪</string>
+    <string name="pref_header_equalizer">校褬械写薪邪褔邪胁邪褔</string>
+    <string name="pref_title_general_autostartonboot">袩芯泻褉械薪懈 褋邪屑芯褋褌邪谢薪芯</string>
+    <string name="pref_title_general_autoreconnect">袩芯薪芯胁芯 锌芯泻褉械薪懈 褋邪屑芯褋褌邪谢薪芯</string>
+    <string name="pref_title_audio_player">袨屑懈褭械薪懈 蟹胁褍褔薪懈 懈蟹胁芯褣邪褔</string>
+    <string name="pref_default">袩芯写褉邪蟹褍屑械胁邪薪芯</string>
+    <string name="pref_title_datetime_syctimeonconnect">校褋泻谢邪写懈 胁褉械屑械</string>
+    <string name="pref_header_datetime">袛邪薪 懈 胁褉械屑械</string>
+    <string name="pref_title_theme">孝械屑邪</string>
+    <string name="pref_theme_light">小胁械褌谢邪</string>
+    <string name="pref_theme_dark">孝邪屑薪邪</string>
+    <string name="pref_theme_system">小泻谢芯锌</string>
+    <string name="pref_theme_black_background">袣芯褉懈褋褌懈 褑褉薪褍 锌芯蟹邪写懈薪褍 褍 褌邪屑薪芯褬 褌械屑懈</string>
+    <string name="pref_title_language">袌械蟹懈泻</string>
+    <string name="pref_summary_minimize_priority_off">小谢懈褔懈褑邪 褍 褌褉邪褑懈 褋褌邪褮邪 懈 芯斜邪胁械褕褌械褮邪 褍 蟹邪泻褭褍褔邪薪芯屑 蟹邪褋谢芯薪褍 褋械 锌褉懈泻邪蟹褍褬褍</string>
+    <string name="pref_summary_minimize_priority_on">小谢懈褔懈褑邪 褍 褌褉邪褑懈 褋褌邪褮邪 懈 芯斜邪胁械褕褌械褮邪 褍 蟹邪泻褭褍褔邪薪芯屑 蟹邪褋谢芯薪褍 褋褍 褋泻褉懈胁械薪懈</string>
+    <string name="pref_title_notifications_repetitions">袩芯薪邪胁褭邪褮械</string>
+    <string name="open_fw_installer_pick_file">袨蟹薪邪褔懈褌械 写邪褌芯褌械泻褍</string>
+    <string name="widget_5_minutes">5 屑懈薪褍褌邪</string>
+    <string name="hr_widget_active_minutes">袦懈薪褍褌懈 泻褉械褌邪褮邪</string>
+    <string name="activeSeconds">袩芯泻褉械薪褍褌芯</string>
+    <string name="sports_activity_quick_filter_7days">7 写邪薪邪</string>
+    <string name="addto_filter">袛芯写邪褬 锌褉芯锌褍褋薪懈泻褍</string>
+    <string name="qhybrid_calibration_1_step">1 泻芯褉邪泻</string>
+    <string name="watchface_dialog_widget_cat_2nd_tz_widget">写褉褍谐邪 胁褉械屑械薪褋泻邪 芯斜谢邪褋褌</string>
+    <string name="sony_equalizer_band_400">400</string>
+    <string name="sony_automatic_power_off_1_hour">1 褋邪褌</string>
+    <string name="sony_automatic_power_off_3_hour">3 褋邪褌邪</string>
+    <string name="sony_equalizer_band_1000">1泻</string>
+    <string name="seconds_5">5 褋械泻褍薪写懈</string>
+    <string name="sports_activity_quick_filter_30days">30 写邪薪邪</string>
+    <string name="watchface_widget_type_2nd_tz">写褉褍谐邪 胁褉械屑械薪褋泻邪 芯斜谢邪褋褌</string>
+    <string name="steps_streaks_achievement_rate">小褌芯锌邪
+\n写芯褋褌懈谐薪褍褯邪</string>
+    <string name="abstract_chart_fragment_kind_activity">袛械褬褋褌胁芯</string>
+    <string name="activity_list_summary_active_steps">孝褉械薪褍褌薪懈 泻芯褉邪褑懈</string>
+    <string name="title_activity_charts">袘褍写薪芯褋褌 懈 褋邪薪</string>
+    <string name="prefs_activity_monitoring_title">袧邪写谐谢械写邪褮械 写械褬褋褌邪胁邪</string>
+    <string name="activity_summaries_all_activities">小胁邪 写械褬褋褌胁邪</string>
+    <string name="pref_header_heartrate_allday">笑械谢芯写薪械胁薪芯 薪邪写谐谢械写邪褮械</string>
+    <string name="activity_summaries_all_devices">小胁懈 褍褉械褣邪褬懈</string>
+    <string name="pref_title_touch_anc">袩芯泻褉械薪褍褌芯 锌芯薪懈褕褌邪胁邪褮械 斜褍泻械</string>
+    <string name="watchface_widget_type_active_mins">袦懈薪褍褌懈 泻褉械褌邪褮邪</string>
+    <string name="miband_prefs_reserve_alarm_calendar">袥邪褉屑械 蟹邪 褔褍胁邪褮械 斜谢懈卸褮懈褏 写芯谐邪褣邪褬邪</string>
+    <string name="user_feedback_all_alarms_disabled">小胁械 谢邪褉屑械 褋褍 芯薪械屑芯谐褍褯械薪械</string>
+    <string name="vibration_profile_alarm_clock">袘褍写懈谢薪懈泻</string>
+    <string name="title_activity_alarm_details">袩芯写邪褑懈 芯 谢邪褉屑懈</string>
+    <string name="prefs_activity_recognition">袩芯褋褌邪胁泻械 锌褉械锌芯蟹薪邪胁邪褮邪 写械褬褋褌邪胁邪</string>
+    <string name="timeformat_24h">24效</string>
+    <string name="pref_screen_notification_profile_alarm_clock">袘褍写懈谢薪懈泻</string>
+    <string name="controlcenter_add_new_folder">袛芯写邪褬 薪芯胁褍 褎邪褋褑懈泻谢褍</string>
+    <string name="activity_type_activity">袛械褬褋褌胁芯</string>
+    <string name="add_test_device">袛芯写邪褬 锌褉芯斜薪懈 褍褉械褣邪褬</string>
+    <string name="menuitem_alarm">袥邪褉屑邪</string>
+    <string name="menuitem_goal">笑懈褭 写械褬褋褌胁邪</string>
+    <string name="filter_submode_all">小胁械 褉械褔懈</string>
+    <string name="add_widget">袛芯写邪褬 褋锌褉邪胁懈褑褍</string>
+    <string name="quick_alarm_description">袥邪褉屑邪 懈蟹 褋锌褉邪胁懈褑械</string>
+    <string name="widget_10_minutes">10 屑懈薪褍褌邪</string>
+    <string name="widget_1_hour">1 褋邪褌</string>
+    <string name="widget_20_minutes">20 屑懈薪褍褌邪</string>
+    <string name="about_additional_device_support">袛芯写邪褌薪邪 锌芯写褉褕泻邪 褍褉械褣邪褬邪</string>
+    <string name="Activity">袛械褬褋褌胁芯</string>
+    <string name="qhybrid_calibration_100_steps">100 泻芯褉邪泻邪</string>
+    <string name="qhybrid_calibration_10_steps">10 泻芯褉邪泻邪</string>
+    <string name="qhybrid_pref_title_actions">袛械褬褋褌胁邪</string>
+    <string name="button_watchface_add_widget">袛芯写邪褬 褋锌褉邪胁懈褑褍</string>
+    <string name="prefs_active_noise_cancelling_level">小褌褍锌邪褮 锌芯泻褉械薪褍褌芯谐 锌芯薪懈褕褌邪胁邪褮邪 斜褍泻械</string>
+    <string name="prefs_active_noise_cancelling">袩芯泻褉械薪褍褌芯 锌芯薪懈褕褌邪胁邪褮械 斜褍泻械</string>
+    <string name="pref_voice_detect_duration_15">15 褋械泻褍薪写懈</string>
+    <string name="pref_voice_detect_duration_5">5 褋械泻褍薪写懈</string>
+    <string name="pref_voice_detect_duration_10">10 褋械泻褍薪写懈</string>
+    <string name="sony_equalizer_band_6300">6.3泻</string>
+    <string name="sony_equalizer_band_2500">2.5泻</string>
+    <string name="sony_equalizer_band_16000">16泻</string>
+    <string name="sony_automatic_power_off_5_min">5 屑懈薪褍褌邪</string>
+    <string name="sony_automatic_power_off_30_min">30 屑懈薪褍褌邪</string>
+    <string name="pref_header_activitytrackers">袩褉邪褌懈芯褑懈 写械褬褋褌邪胁邪</string>
+    <string name="pref_title_enable_pebblekit">袛芯蟹胁芯谢懈 锌褉懈褋褌褍锌 锌褉芯谐褉邪屑邪 袗薪写褉芯懈写邪 褌褉械褯械 褋褌褉邪薪械</string>
+    <string name="pref_title_expose_hr">袩褉懈褋褌褍锌 褌褉械褯械 褋褌褉邪薪械 胁褉械屑械薪褍</string>
+    <string name="zetime_activity_tracking">袩褉邪褯械褮械 写械褬褋褌胁邪</string>
+    <string name="zetime_calories_type_all">小邪谐芯褉械谢械 泻邪谢芯褉懈褬械 泻褉械褌邪褮邪 懈 薪械泻褉械褌邪褮邪</string>
+    <string name="user_feedback_miband_set_alarms_ok">袥邪褉屑械 褋褍 锌芯褋谢邪褌械 薪邪 褍褉械褣邪褬.</string>
+    <string name="activity_sleepchart_activity_and_sleep">袛械褬褋褌胁芯</string>
+    <string name="charts_activity_list">小锌懈褋邪泻 写械褬褋褌邪胁邪</string>
+    <string name="activity_list_summary_active_time">袙褉械屑械 写械褬褋褌胁邪</string>
+    <string name="activity_list_summary_activities">袛械褬褋褌胁邪</string>
+    <string name="seconds_6">6 褋械泻褍薪写懈</string>
+    <string name="seconds_7">7 褋械泻褍薪写懈</string>
+    <string name="seconds_10">10 褋械泻褍薪写懈</string>
+    <string name="seconds_11">11 褋械泻褍薪写懈</string>
+    <string name="seconds_12">12 褋械泻褍薪写懈</string>
+    <string name="seconds_13">13 褋械泻褍薪写懈</string>
+    <string name="seconds_14">14 褋械泻褍薪写懈</string>
+    <string name="seconds_15">15 褋械泻褍薪写懈</string>
+    <string name="seconds_20">20 褋械泻褍薪写懈</string>
+    <string name="minutes_1">1 屑懈薪褍褌</string>
+    <string name="seconds_30">30 褋械泻褍薪写懈</string>
+    <string name="minutes_5">5 屑懈薪褍褌邪</string>
+    <string name="minutes_10">10 屑懈薪褍褌邪</string>
+    <string name="minutes_30">30 屑懈薪褍褌邪</string>
+    <string name="mi2_prefs_activate_display_on_lift">袩芯泻褉械薪懈 蟹邪褋谢芯薪 锌芯写懈蟹邪褮械屑</string>
+    <string name="mi2_prefs_do_not_disturb_lift_wrist">袩芯泻褉械薪懈 蟹邪褋谢芯薪 锌芯写懈蟹邪褮械屑 褌芯泻芯屑 薪械 褍蟹薪械屑懈褉邪胁邪褮邪</string>
+    <string name="activity_prefs_about_you">袨 袙邪屑邪</string>
+    <string name="sony_button_mode_ambient_sound_control">校锌褉邪胁褭邪褮械 芯泻芯谢薪懈屑 蟹胁褍泻芯屑</string>
+    <string name="watchface_dialog_widget_timeout_hide_text">小泻褉懈褬 锌懈褋屑芯 锌褉懈 懈褋褌械泻褍 胁褉械屑械薪邪</string>
+    <string name="steps_streaks_hint">袧懈蟹 褍蟹邪褋褌芯锌薪懈褏 写邪薪邪 斜械蟹 锌褉械泻懈写邪 褋邪 懈褋锌褍褮械薪懈屑 褑懈褭械屑 褍 泻芯褉邪褑懈屑邪</string>
+    <string name="pref_device_action_fitness_app_control_toggle">袩褉械泻懈写邪褔 锌褉芯谐褉邪屑邪 斜械谢械卸械褮邪 锌褍褌邪褮械 胁械卸斜邪褮邪</string>
+    <string name="pref_activity_recognize_running">锌褉械锌芯蟹薪邪褬 褌褉褔邪褮械</string>
+    <string name="menuitem_menu">袠蟹斜芯褉薪懈泻</string>
+    <string name="pref_activity_recognition_mode_auto">褋邪屑芯褋褌邪谢薪芯</string>
+    <string name="pref_activity_recognition_mode_ask">锌懈褌邪褬</string>
+    <string name="pref_activity_recognize_rowing">锌褉械锌芯蟹薪邪褬 胁械褋谢邪褮械</string>
+    <string name="pref_activity_recognition_mode_none">薪懈褕褌邪</string>
+    <string name="connection_over_ble">袙械蟹邪 锌褉械泻芯 斜谢邪谐芯谐 袘谢褍褌褍褌邪</string>
+    <string name="auto_reconnect_ble_summary">袩芯泻褍褕邪胁邪褬 胁械蟹褍 褋邪 褍褉械褣邪褬械屑 锌芯胁褉械屑械薪芯</string>
+    <string name="auto_reconnect_ble_title">小邪屑芯锌芯胁械卸懈 褋械 褋邪 褍褉械褣邪褬械屑</string>
+    <string name="autoconnect_from_device_summary">校褋锌芯褋褌邪胁懈褌械 胁械蟹褍 泻邪写邪 褬械 胁械蟹邪 锌芯蟹胁邪薪邪 褍褉械褣邪褬械屑, 泻邪芯 褋谢褍褕邪谢懈褑邪屑邪</string>
+    <string name="autoconnect_from_device_title">袩芯胁械卸懈 褋械 锌芯蟹懈胁芯屑 褋邪 褍褉械褣邪褬邪</string>
+    <string name="connection_over_bt_classic">袙械蟹邪 锌褉械泻芯 袘谢褍褌褍褌邪 芯斜懈褔薪邪</string>
+    <string name="steps_streaks">袩褍褌邪褮械 泻芯褉邪泻邪</string>
+    <string name="step_streak_ongoing">孝褉械薪褍褌薪懈</string>
+    <string name="steps_streaks_total_steps">校泻褍锌薪懈
+\n泻芯褉邪褑懈</string>
+    <string name="steps_streaks_streak_days">袛邪薪懈 褋邪
+\n锌褍褌邪褮芯屑</string>
+    <string name="step_streak_total">校泻褍锌薪芯</string>
+    <string name="step_streak_longest">袧邪褬写褍卸懈</string>
+    <string name="steps_streaks_total_days_hint_totals">袩芯褋褌芯褌邪泻 写邪薪邪 褋邪 写芯褋褌懈谐薪褍褌懈屑 褑懈褭械屑 褍 芯写薪芯褋褍 薪邪 褋胁械 写邪薪械 褋邪 斜褉芯褬邪薪懈屑 泻芯褉邪褑懈屑邪</string>
+    <string name="steps_streaks_since_date">袨写 %s</string>
+    <string name="steps_streaks_total_steps_hint_totals">校泻褍锌邪薪 斜褉芯褬 蟹邪斜械谢械卸械薪懈褏 泻芯褉邪泻邪 懈泻邪写</string>
+    <string name="step_streak_average_steps_hint">袩褉芯褋械褔薪懈 写薪械胁薪懈 泻芯褉邪褑懈 锌褍褌邪褮械</string>
+    <string name="step_streak_days_hint">袘褉芯褬 褍蟹邪褋褌芯锌薪懈褏 写邪薪邪 褋邪 懈褋锌褍褮械薪懈屑 褑懈褭械屑 斜褉芯褬邪 泻芯褉邪泻邪</string>
+    <string name="steps_streaks_total_steps_average_hint">校泻褍锌薪懈 锌褉芯褋械褔薪懈 写薪械胁薪懈 泻芯褉邪褑懈 %d</string>
+    <string name="prefs_hourly_chime_summary">效邪褋芯胁薪懈泻 褯械 锌懈褋薪褍褌懈 褋胁邪泻芯谐 褋邪褌邪</string>
+    <string name="sony_equalizer">校褬械写薪邪褔懈胁邪褔</string>
+    <string name="sony_button_mode_volume_control">校锌褉邪胁褭邪褮械 谐谢邪褋薪芯褯芯屑</string>
+    <string name="sony_button_mode_playback_control">校锌褉邪胁褭邪褮械 锌芯薪芯胁薪懈屑 懈蟹胁芯褣械褮械屑</string>
+    <string name="sony_equalizer_preset_speech">袚芯胁芯褉</string>
+    <string name="sony_speak_to_chat_sensitivity_high">袙懈褋芯泻芯</string>
+    <string name="sony_speak_to_chat_sensitivity_auto">小邪屑芯褋褌邪谢薪芯</string>
+    <string name="sony_speak_to_chat_timeout">袠褋褌械泻谢芯 胁褉械屑械</string>
+    <string name="sony_speak_to_chat_sensitivity_low">袧懈褋泻芯</string>
+    <string name="pref_media_rewind">袩褉械褋泻芯褔懈 薪邪蟹邪写</string>
+    <string name="pref_activity_recognize_biking">锌褉械锌芯蟹薪邪褬 锌械写邪谢邪褮械 褌芯褔泻邪</string>
+    <string name="step_streaks_achievements_sharing_title">袛芯褋褌懈谐薪褍褯邪 泻芯褉邪褔邪褮械屑</string>
+    <string name="sony_equalizer_preset_vocal">袚谢邪褋芯胁薪芯</string>
+    <string name="sony_equalizer_preset_relaxed">袨锌褍褕褌械薪芯</string>
+    <string name="sony_equalizer_preset_mellow">袘谢邪谐芯</string>
+    <string name="sony_equalizer_preset_excited">校蟹斜褍褣械薪芯</string>
+    <string name="sony_speak_to_chat_sensitivity">袨褋械褌褭懈胁芯褋褌 锌褉械锌芯蟹薪邪胁邪褮邪 谐谢邪褋邪</string>
+    <string name="sony_speak_to_chat">袚芯胁芯褉-蟹邪-褯邪褋泻邪褮械</string>
+    <string name="sony_equalizer_preset_manual">袪褍褔薪芯</string>
+    <string name="pref_screen_auto_brightness_summary">袩褉懈谢邪谐芯写懈 芯褋胁械褌褭械褮械 蟹邪褋谢芯薪邪 芯泻芯谢薪芯屑 褋胁械褌谢褍</string>
+    <string name="pref_screen_auto_brightness_title">小邪屑芯褋褌邪谢薪芯 芯褋胁械褌褭械褮械</string>
+    <string name="watchface_dialog_widget_timeout_show_circle">袩褉懈泻邪卸懈 泻褉褍谐 锌褉懈 懈褋褌械泻褍 胁褉械屑械薪邪</string>
+    <string name="watchface_dialog_widget_update_timeout">袨褋胁械卸懈 胁褉械屑械 懈褋褌械泻邪 褍 屑懈薪褍褌懈屑邪</string>
+    <string name="watchface_dialog_widget_timezone_duration">孝褉邪褬邪褮械 胁懈写薪芯褋褌懈 褔邪褋芯胁薪懈泻邪 (褍 褋械泻褍薪写邪屑邪)</string>
+    <string name="pref_screen_brightness">袨褋胁械褌褭械褮械 蟹邪褋谢芯薪邪</string>
+    <string name="pref_media_volumedown">小屑邪褮懈 谐谢邪褋薪芯褯褍</string>
+    <string name="pref_media_volumeup">袩芯胁械褯邪褬 谐谢邪褋薪芯褯褍</string>
+    <string name="pref_media_previous">袩褉械褌褏芯写薪邪 褌褉邪泻邪</string>
+    <string name="pref_media_playpause">袩褉械泻懈写邪褔 懈蟹胁芯褣械褮邪 懈蟹薪芯胁邪</string>
+    <string name="pref_media_pause">袟邪褋褌邪薪懈 懈蟹胁芯褣械褮械</string>
+    <string name="pref_media_play">袠蟹胁芯写懈</string>
+    <string name="pref_button_action_disabled">袨薪械屑芯谐褍褯械薪芯</string>
+    <string name="pref_activity_recognize_walking">锌褉械锌芯蟹薪邪褬 褏芯写邪褮械</string>
+    <string name="pref_device_action_phone_gps_location_listener_stop">袟邪褍褋褌邪胁懈 锌褉懈屑邪褔邪 锌芯谢芯卸邪褬邪 袚袩小-邪</string>
+    <string name="pref_media_forward">袩褉械褋泻芯褔懈 薪邪锌褉械写</string>
+    <string name="steps_streaks_average_steps">袩褉芯褋械褔薪懈
+\n泻芯褉邪褑懈</string>
+    <string name="step_streaks_achievements_sharing_message">袦芯褬邪 写薪械胁薪邪 写芯褋褌懈谐薪褍褯邪 泻芯褉邪褔邪褮械屑!</string>
+    <string name="steps_streaks_total_steps_hint">校泻褍锌薪懈 斜褉芯褬 泻芯褉邪泻邪 褍 褑械谢芯褬 锌褍褌邪褮懈</string>
+    <string name="controlcenter_set_folder_title">袩芯褋褌邪胁懈 懈谢懈 褋褌胁芯褉懈 薪芯胁褍 褎邪褋褑懈泻谢褍</string>
+    <string name="controlcenter_unset_folder">袪邪蟹写械褋懈 褎邪褋褑懈泻谢褍</string>
+    <string name="watchface_dialog_widget_timezone">袙褉械屑械薪褋泻邪 芯斜谢邪褋褌</string>
+    <string name="sony_equalizer_preset_bright">小胁械褌谢芯</string>
+    <string name="pref_media_next">小谢械写械褯邪 褌褉邪泻邪</string>
+    <string name="watchface_widget_type_custom">袩褉芯懈蟹胁芯褭薪邪 褋锌褉邪胁懈褑邪</string>
+    <string name="sony_button_mode_off">袠褋泻褭褍褔懈</string>
+    <string name="controlcenter_set_preferences">袩芯褋褌邪胁懈 褋胁芯褬褋褌胁邪</string>
+    <string name="controlcenter_set_parent_folder">袩芯褋褌邪胁懈 褉芯写懈褌械褭褋泻褍 褎邪褋褑懈泻谢褍</string>
+    <string name="pref_summary_opentracks_packagename">袣芯褉懈褋褌懈 褋械 蟹邪 锌芯泻褉械褌邪褮械/蟹邪褍褋褌邪胁褭邪褮械 褋薪懈屑邪褮邪 褋褌邪蟹械 袚袩小-邪 褍 褋锌芯褭褮械屑 锌褉芯谐褉邪屑褍 胁械卸斜邪褮邪.</string>
+    <string name="pref_title_opentracks_packagename">袠屑械 锌邪泻械褌邪 袨锌械薪褌褉邪泻邪</string>
+    <string name="info_connected_count">%d 褍褉械褣邪褬邪 褬械 锌芯胁械蟹邪薪芯</string>
+    <string name="info_no_devices_connected">薪械屑邪 锌芯胁械蟹邪薪懈褏 褍褉械褣邪褬邪</string>
+    <string name="watchface_setting_light_up_on_notification">袨褋胁械褌谢懈 锌褉懈 薪芯胁芯屑 芯斜邪胁械褕褌械褮褍</string>
+    <string name="watchface_dialog_pre_setting_position">锌褉械褌锌芯褋褌邪胁懈 锌芯谢芯卸邪褬 薪邪 %s</string>
+    <string name="controlcenter_folder_name">袠屑械 褎邪褋褑懈泻谢械:</string>
+    <string name="error_deleting_device">袛械褋懈谢邪 褋械 谐褉械褕泻邪 锌褉懈 斜褉懈褋邪褮褍 褍褉械褣邪褬邪: %s</string>
+    <string name="error_setting_parent_folder">袛械褋懈谢邪 褋械 谐褉械褕泻邪 锌褉懈 锌芯褋褌邪胁褭邪褮褍 褉芯写懈褌械褭褋泻械 褎邪褋褑懈泻谢械: %s</string>
+    <string name="controlcenter_connected_fraction">袩芯胁械蟹邪薪: %d/%d</string>
+    <string name="controlcenter_toggle_details">袩褉械泻懈写邪褔 锌褉懈泻邪蟹邪 锌芯褬械写懈薪芯褋褌懈</string>
+    <string name="supercars_lights_blinking_label">孝褉械锌褌邪褮械</string>
+    <string name="supercars_lights_label">小胁械褌谢邪</string>
+    <string name="supercars_turbo_speed_label">孝褍褉斜芯-斜褉蟹懈薪邪</string>
+    <string name="accuracy">孝邪褔薪芯褋褌</string>
+    <string name="balanced">校褉邪胁薪芯褌械卸械薪邪</string>
+    <string name="power_saving">效褍胁邪褮械 褋薪邪谐械</string>
+    <string name="single_band">袌械写薪邪 胁械蟹邪</string>
+    <string name="low_power_gps">袚袩小 薪懈褋泻械 褋薪邪谐械</string>
+    <string name="gps">袚袩小</string>
+    <string name="gps_bds">袚袩小 + 袘袛小</string>
+    <string name="gps_galileo">袚袩小 + 袚袗袥袠袥袝袨</string>
+    <string name="all_satellites">小胁懈 褋邪褌械谢懈褌懈</string>
+    <string name="speed_first">袩褉胁械薪褋褌胁械薪芯 斜褉蟹懈薪邪</string>
+    <string name="accuracy_first">袩褉胁械薪褋褌胁械薪芯 褌邪褔薪芯褋褌</string>
+    <string name="pref_header_sound_vibration">袟胁褍泻 懈 褌褉械褕褮邪</string>
+    <string name="pref_header_offline_voice">袚谢邪褋 胁邪薪 屑褉械卸械</string>
+    <string name="pref_title_pebble_settings">袩芯褋褌邪胁泻械 袩械斜谢邪</string>
+    <string name="pref_title_pebble_activitytracker">袨屑懈褭械薪懈 锌褉邪褌懈谢邪褑 写械褬褋褌邪胁邪</string>
+    <string name="power_mode_watch">小邪屑芯 褔邪褋芯胁薪懈泻</string>
+    <string name="zetime_title_heart_rate_alarm_summary">效邪褋芯胁薪懈泻 褯械 袙邪褋 褍锌芯蟹芯褉懈褌懈 谢邪褉屑芯屑 邪泻芯 褋褉褑械 写芯褋械谐薪械 谐褉邪薪懈褑械.</string>
+    <string name="zetime_heart_rate_alarm_enable">袨屑芯谐褍褯懈 谢邪褉屑褍 褋褉褔邪薪懈褏 屑械褉械褮邪</string>
+    <string name="zetime_activity_tracking_summary">校泻褭褍褔懈胁邪褮械 锌褉邪褯械褮邪, 斜褉芯褬邪褯械 袙邪屑 褋械 泻芯褉邪褑懈 懈 褋谢懈褔薪芯.</string>
+    <string name="zetime_calories_type">袙褉褋褌邪 泻邪谢芯褉懈褬邪</string>
+    <string name="zetime_calories_type_active">袣邪谢芯褉懈褬械 褋邪谐芯褉械谢械 褉邪写芯屑</string>
+    <string name="zetime_date_format">袨斜谢懈泻 写邪褌褍屑邪</string>
+    <string name="zetime_title_alarm_signaling">袩芯褋褌邪胁懈 胁褉褋褌褍 芯谐谢邪褕邪胁邪褮邪 谢邪褉屑械</string>
+    <string name="zetime_signaling_vibrate">袧械锌褉械泻懈写薪邪 褌褉械褕褮邪</string>
+    <string name="zetime_signaling_beep">袧械锌褉械泻懈写薪芯 锌懈褋泻邪褮械</string>
+    <string name="zetime_signaling_vibrate_once">袟邪褌褉械褋懈 褬械写薪芯屑</string>
+    <string name="title_activity_device_specific_settings">袧邪褉芯褔懈褌械 锌芯褋褌邪胁泻械 褍褉械褣邪褬邪</string>
+    <string name="pref_title_authkey">袣褭褍褔 锌褉懈褬邪胁械</string>
+    <string name="prefs_hr_alarm_activity">袥邪褉屑邪 褋褉褔邪薪懈褏 芯褌泻褍褑邪褬邪 锌褉懈谢懈泻芯屑 褋锌芯褉褌褋泻懈褏 写械褬褋褌邪胁邪</string>
+    <string name="prefs_hr_alarm_low">袧懈褋泻邪 谐褉邪薪懈褑邪</string>
+    <string name="pref_gps_header">袚袩小</string>
+    <string name="pref_gps_mode_preset">袧邪褔懈薪 袚袩小-邪</string>
+    <string name="pref_gps_satellite_search">袩褉械褌褉邪谐邪 褋邪褌械谢懈褌邪</string>
+    <string name="pref_crown_vibration">孝褉械褕褮邪 泻褉褍薪械</string>
+    <string name="pref_alert_tone">袟胁褍泻 谢邪褉屑械</string>
+    <string name="pref_cover_to_mute">袩褉械泻褉懈褬 蟹邪 屑褍泻</string>
+    <string name="pref_vibrate_for_alert">袟邪褌褉械褋懈 蟹邪 谢邪褉屑褍</string>
+    <string name="pref_text_to_speech">袩懈褋屑芯 褍 谐芯胁芯褉</string>
+    <string name="offline_voice_respond_turn_wrist">袨写邪蟹芯胁懈 褋械 薪邪 芯泻褉械褌邪褮械 蟹谐谢芯斜邪</string>
+    <string name="offline_voice_respond_screen_on">袨写邪蟹芯胁懈 褋械 锌褉懈 褍泻褭褍褔械褮褍 蟹邪褋谢芯薪邪</string>
+    <string name="offline_voice_response_during_screen_lighting">袨写邪蟹芯胁懈 褋械 锌褉懈谢懈泻芯屑 芯褋胁械褌褭邪胁邪褮邪 蟹邪褋谢芯薪邪</string>
+    <string name="pref_agps_header">袗袚袩小</string>
+    <string name="pref_agps_expiry_reminder_enabled">袩芯写褋械褌薪懈泻 懈褋褌械泻邪 袗袩袚小-邪</string>
+    <string name="pref_workout_detection_title">袩褉械锌芯蟹薪邪胁邪褮械 胁械卸斜邪褮邪</string>
+    <string name="pref_workout_detection_summary">袩褉械锌芯蟹薪邪褬 胁械卸斜邪褮械 褋邪屑芯褋褌邪谢薪芯</string>
+    <string name="pref_workout_detection_categories_title">袙褉褋褌械 胁械卸斜邪褮邪</string>
+    <string name="pref_workout_detection_alert_title">袥邪褉屑邪</string>
+    <string name="pref_header_auto_export">小邪屑芯褋褌邪谢薪懈 懈蟹胁芯蟹</string>
+    <string name="pref_title_auto_export_enabled">小邪屑芯褋褌邪谢薪懈 懈蟹胁芯蟹 褬械 芯屑芯谐褍褯械薪</string>
+    <string name="pref_title_auto_export_location">袦械褋褌芯 懈蟹胁芯蟹邪</string>
+    <string name="pref_title_auto_export_interval">袦械褣褍胁褉械屑械 懈蟹胁芯蟹邪</string>
+    <string name="pref_header_auto_fetch">小邪屑芯褋褌邪谢薪芯 写芯斜邪胁懈</string>
+    <string name="pref_auto_fetch">小邪屑芯褋褌邪谢薪芯 写芯斜邪胁懈 锌芯写邪褌泻械 写械褬褋褌邪胁邪</string>
+    <string name="not_connected">袧懈褬械 锌芯胁械蟹邪薪</string>
+    <string name="connecting">袩芯胁械蟹懈胁邪褮械</string>
+    <string name="connected">袩芯胁械蟹邪薪</string>
+    <string name="bluetooth_is_disabled_">袘谢褍褌褍褌 褬械 芯薪械屑芯谐褍褯械薪.</string>
+    <string name="tap_connected_device_for_app_mananger">孝邪锌薪懈褌械 锌芯胁械蟹邪薪懈 褍褉械褣邪褬 蟹邪 褍锌褉邪胁薪懈泻邪 锌褉芯谐褉邪屑邪</string>
+    <string name="installation_failed_">校谐褉邪写褮邪 薪懈褬械 褍褋锌械谢邪</string>
+    <string name="installation_successful">校谐褉邪褣械薪芯</string>
+    <string name="pairing">校锌邪褉褍褬械屑 褋邪 %s鈥�</string>
+    <string name="female">袞械薪褋泻芯</string>
+    <string name="other">小褉械写褮械</string>
+    <string name="buttons_on_left">袛褍谐屑邪写 薪邪 谢械胁芯</string>
+    <string name="buttons_on_right">袛褍谐屑邪写 薪邪谢械胁芯</string>
+    <string name="miband_prefs_alias">袠屑械/薪邪写懈屑械</string>
+    <string name="pref_header_vibration_count">袘褉芯褬邪褮械 褌褉械褕褮懈</string>
+    <string name="watch9_pairing_tap_hint">袣邪写邪 袙邪屑 褋邪褌 褌褉械褋械, 褌褉械褋懈褌械 褍褉械褣邪褬 懈谢懈 锌褉懈褌懈褋薪懈褌械 写褍谐屑械.</string>
+    <string name="pref_write_logfiles">袩懈褕懈 写薪械胁薪懈泻</string>
+    <string name="pref_screen_notification_profile_calendar">袨斜邪胁械褕褌械褮邪 泻邪谢械薪写邪褉邪</string>
+    <string name="pref_screen_notification_profile_inactivity">袨斜邪胁械褕褌械褮邪 屑懈褉芯胁邪褮邪</string>
+    <string name="pref_screen_notification_profile_low_power">校锌芯蟹芯褉械褮械 锌褉邪蟹薪械 斜邪褌械褉懈褬械</string>
+    <string name="pref_screen_notification_profile_schedule">袪邪褋锌芯褉械写</string>
+    <string name="pref_screen_notification_profile_todo_list">小锌懈褋邪泻 芯斜邪胁械蟹邪</string>
+    <string name="pref_screen_notification_profile_event_reminder">袩芯写褋械褌薪懈泻 写芯谐邪褣邪褬邪</string>
+    <string name="pref_screen_notification_profile_find_device">袩褉芯薪邪褣懈 褍褉械褣邪褬</string>
+    <string name="interval_one_minute">褋胁邪泻芯谐 屑懈薪褍褌邪</string>
+    <string name="interval_five_minutes">褋胁邪泻懈褏 5 屑懈薪褍褌邪</string>
+    <string name="spo2_off">袠褋泻褭褍褔械薪芯</string>
+    <string name="stats_title">袘褉蟹懈薪褋泻械 芯斜谢邪褋褌懈</string>
+    <string name="stats_x_axis_label">校泻褍锌薪芯 屑懈薪褍褌邪</string>
+    <string name="control_center_find_lost_device">袩褉芯薪邪褣懈 懈蟹谐褍斜褭械薪懈 褍褉械褣邪褬</string>
+    <string name="control_center_cancel_to_stop_vibration">袨褌泻邪卸懈 懈谢懈 蟹邪褍褋褌邪胁懈 褌褉械褕褮褍.</string>
+    <string name="title_activity_set_alarm">袩芯写械褋懈 谢邪褉屑械</string>
+    <string name="controlcenter_start_configure_alarms">袩芯写械褋懈 谢邪褉屑械</string>
+    <string name="controlcenter_start_configure_reminders">袩芯写械褋懈 锌芯写褋械褌薪懈泻械</string>
+    <string name="reminder_repeat">袩芯薪芯胁懈</string>
+    <string name="reminder_date">袛邪褌褍屑</string>
+    <string name="reminder_time">袙褉械屑械</string>
+    <string name="reminder_message">袩芯褉褍泻邪</string>
+    <string name="reminder_time_every_month">%1$s, 褋胁邪泻芯谐 屑械褋械褑邪</string>
+    <string name="reminder_time_every_year">%1$s, 褋胁邪泻械 谐芯写懈薪械</string>
+    <string name="reminder_once">袌械写薪芯屑</string>
+    <string name="reminder_no_free_slots_title">袧械屑邪 锌褉邪蟹薪懈褏 屑械褋褌邪</string>
+    <string name="world_clock_no_free_slots_title">袧械屑邪 褋谢芯斜芯写薪懈褏 屑械褋褌邪</string>
+    <string name="world_clock_label">袨蟹薪邪泻邪</string>
+    <string name="title_activity_reminder_details">袩芯褬械写懈薪芯褋褌懈 锌芯写褋械褌薪懈泻邪</string>
+    <string name="zetime_analog_mode_hands">小邪屑芯 褉褍泻械</string>
+    <string name="activity_prefs_alarm_max_heart_rate">袧邪褬胁懈褕懈 芯褌泻褍褑邪褬懈 褋褉褑邪</string>
+    <string name="zetime_analog_mode">袗薪邪谢芯谐薪懈 褉邪写</string>
+    <string name="zetime_handmove_display">袩芯泻褉械褌懈 褉褍泻褍</string>
+    <string name="zetime_prefs_inactivity_th">效械褌胁褉褌邪泻</string>
+    <string name="zetime_prefs_inactivity_repetitions">袩芯薪邪胁褭邪褮邪</string>
+    <string name="zetime_prefs_inactivity_fr">袩械褌邪泻</string>
+    <string name="zetime_prefs_inactivity_mo">袩芯薪械写械褭邪泻</string>
+    <string name="zetime_prefs_inactivity_we">小褉械写邪</string>
+    <string name="zetime_prefs_inactivity_tu">校褌芯褉薪懈泻</string>
+    <string name="pref_workout_detection_alert_summary">袨斜邪胁械褋褌懈 邪泻芯 谢懈 褬械 胁械卸斜邪褮械 锌褉械锌芯蟹薪邪褌芯</string>
+    <string name="zetime_prefs_inactivity_su">袧械写械褭邪</string>
+    <string name="zetime_signaling_vibrate_beep">袧械锌褉械泻懈写薪邪 褌褉械褕褮邪 懈 锌懈褋泻邪褮械</string>
+    <string name="zetime_signaling_beep_once">袩懈褋薪懈 褬械写薪芯屑</string>
+    <string name="zetime_signaling_beep_twice">袩懈褋薪懈 写胁邪锌褍褌</string>
+    <string name="zetime_signaling_vibrate_twice">袟邪褌褉械褋懈 写胁邪锌褍褌</string>
+    <string name="custom">袩褉芯懈蟹胁芯褭薪邪</string>
+    <string name="pref_gps_band">袨锌褋械谐 袚袩小-邪</string>
+    <string name="zetime_signaling_vibrate_beep_once">袟邪褌褉械褋懈 懈 锌懈褋薪懈 褬械写薪芯屑</string>
+    <string name="pref_gps_combination">袩褉械锌谢械褌 袚袩小-邪</string>
+    <string name="dual_band">袛胁芯褋褌褉褍泻邪 胁械蟹邪</string>
+    <string name="gps_gnolass">袚袩小 + 袚袥袨袧袗小</string>
+    <string name="power_mode_saving">效褍胁邪褮械 褋薪邪谐械</string>
+    <string name="activity_prefs_alarm_min_heart_rate">袧邪褬薪懈卸懈 芯褌泻褍褑邪褬懈 褋褉褑邪</string>
+    <string name="zetime_prefs_inactivity_sa">小褍斜芯褌邪</string>
+    <string name="pref_title_enable_outgoing_call">袩芯写褉卸懈 芯写谢邪蟹薪械 锌芯蟹懈胁械</string>
+    <string name="pref_workout_detection_categories_summary">袙褉褋褌械 胁械卸斜邪褮邪 蟹邪 褋邪屑芯褉邪褋锌芯蟹薪邪胁邪褮械</string>
+    <string name="pref_workout_detection_sensitivity">袨褋械褌褭懈胁芯褋褌</string>
+    <string name="busy_task_fetch_activity_data">袛芯斜邪胁褭邪屑 锌芯写邪褌泻械 芯 写械褬褋褌胁懈屑邪</string>
+    <string name="pref_sleep_mode_title">小锌邪胁邪褮械</string>
+    <string name="pref_sleep_mode_sleep_screen_title">校褋锌邪胁邪褬 蟹邪褋谢芯薪</string>
+    <string name="pref_sleep_mode_smart_enable_title">袩邪屑械褌薪芯 褍泻褭褍褔懈胁邪褮械</string>
+    <string name="preferences_category_device_specific_settings">袧邪褉芯褔懈褌械 锌芯褋褌邪胁泻械 褍褉械褣邪褬邪</string>
+    <string name="pref_sleep_mode_smart_enable_summary">小邪屑芯褋褌邪谢薪芯 芯屑芯谐褍褯懈 褋锌邪胁邪褮械 褍褉械褣邪褬邪 锌褉懈 薪芯褕械褮褍 褍褉械褣邪褬邪 褌芯泻芯屑 褋锌邪胁邪褮邪</string>
+    <string name="pref_summary_auto_export_interval">袠蟹胁械蟹懈 褋褌胁邪泻懈褏 %d 褔邪褋邪/芯胁邪</string>
+    <string name="pref_auto_fetch_summary">袛芯斜邪胁褭邪 褋械 薪邪泻芯薪 芯褌泻褭褍褔邪胁邪褮邪 蟹邪褋谢芯薪邪. 袪邪写懈 褋邪屑芯 邪泻芯 褬械 锌芯褋褌邪胁褭械薪芯 蟹邪泻褭褍褔邪胁邪褮械!</string>
+    <string name="pref_auto_fetch_limit_fetches">袧邪褬屑邪褮械 胁褉械屑械 懈蟹屑械褣褍 写芯斜邪胁褭邪褮邪</string>
+    <string name="find_my_phone_notification">袩褉芯薪邪褣懈 屑芯褬 褍褉械褣邪褬</string>
+    <string name="cannot_connect_bt_address_invalid_">袧懈褋邪屑 褍褋锌械芯 写邪 褋械 锌芯胁械卸械屑. 袛邪 谢懈 褬械 邪写褉械褋邪 袘谢褍褌褍褌邪 薪械胁邪卸械褯邪\?</string>
+    <string name="pairing_in_progress">校锌邪褉懈胁邪褮械 褍 褌芯泻褍: %1$s (%2$s)</string>
+    <string name="pairing_already_bonded">袙械褯 褬械 褍锌邪褉械薪%1$s (%2$s), 锌芯胁械蟹褍褬械屑鈥�</string>
+    <string name="test_notification">袨斜邪胁械褕褌械褮邪 锌褉芯斜械</string>
+    <string name="this_is_a_test_notification_from_gadgetbridge">袨胁芯 褬械 锌褉芯斜邪 芯斜邪胁械褕褌械褮邪 袚械褵械褌斜褉胁薪邪</string>
+    <string name="pairing_unable_to_pair_with">袧懈褋邪屑 褍褋锌械芯 写邪 褋械 褍锌邪褉懈屑 褋邪 %1$s %2$s</string>
+    <string name="pref_cache_weather_summary">袩芯写邪褑懈 芯 胁褉械屑械薪褍 褯械 斜懈褌懈 蟹邪褏胁邪褯械薪懈 泻褉芯蟹 锌褉械锌芯泻褉械褌邪褮械 锌褉芯谐褉邪屑邪.</string>
+    <string name="vibration_profile_waterdrop">袣邪锌</string>
+    <string name="pref_screen_notification_profile_email">袨斜邪胁械褕褌械褮邪 械-锌芯褕褌械</string>
+    <string name="stats_y_axis_label">袣芯褉邪泻邪 褍 屑懈薪褍褌褍</string>
+    <string name="bluetooth_is_not_supported_">袘谢褍褌褍褌 薪懈褬械 锌芯写褉卸邪薪.</string>
+    <string name="installing_binary_d_d">校谐褉邪褣褍褬械 斜懈薪邪褉 %1$d/%2$d</string>
+    <string name="left">袥械胁芯</string>
+    <string name="pref_cache_weather">袩芯写邪褑懈 芯 蟹邪褏胁邪褌褍 胁褉械屑械薪邪</string>
+    <string name="title_activity_discovery">袩褉芯薪邪谢邪卸械褮械 褍褉械褣邪褬邪</string>
+    <string name="title_activity_sleepmonitor">袧邪写蟹芯褉薪懈泻 褋锌邪胁邪褮邪</string>
+    <string name="reminder_time_once">%1$s, 褬械写薪芯屑</string>
+    <string name="vibration_profile_short">袣褉邪褌泻芯</string>
+    <string name="vibration_profile_ring">袟胁芯薪芯</string>
+    <string name="pref_screen_notification_profile_generic">袨锌褕褌邪 芯斜邪胁械褕褌械褮邪</string>
+    <string name="pref_screen_notification_profile_incoming_call">袨斜邪胁械褕褌械褮邪 写芯谢邪蟹薪懈褏 锌芯蟹懈胁邪</string>
+    <string name="pref_screen_notification_profile_missed_call">袨斜邪胁械褕褌械褮邪 锌褉芯锌褍褕褌械薪懈褏 锌芯蟹懈胁邪</string>
+    <string name="candidate_item_device_image">小谢懈泻邪 褍褉械褣邪褬邪</string>
+    <string name="sleep_activity_date_range">袨写 %1$s 写芯 %2$s</string>
+    <string name="world_clock_delete_confirm_title">袠蟹斜褉懈褕懈 鈥�%1$s鈥�</string>
+    <string name="prefs_wearside">袧芯褕褮邪 薪邪 谢械胁芯褬 懈谢懈 写械褋薪芯褬 褉褍褑懈\?</string>
+    <string name="prefs_weardirection">袩褉械锌芯蟹薪邪胁邪褮械 薪芯褕褮械</string>
+    <string name="vibration_profile_default">袩芯写褉邪蟹褍屑械胁邪薪芯</string>
+    <string name="vibration_profile_medium">小褉械写褮械</string>
+    <string name="pref_header_vibration_settings">袩芯褋褌邪胁泻械 褌褉械褕褮械</string>
+    <string name="reminder_time_every_day">%1$s, 褋胁邪泻芯谐 写邪薪邪</string>
+    <string name="vibration_profile_staccato">袨褌械谐薪褍褌芯</string>
+    <string name="vibration_profile_long">袛褍谐邪褔泻芯</string>
+    <string name="vibration_try">袩褉芯斜邪褬</string>
+    <string name="pref_screen_notification_profile_sms">袨斜邪胁械褕褌械褮邪 小袦小-邪</string>
+    <string name="interval_one_hour">褋胁邪泻芯谐 褋邪褌邪</string>
+    <string name="reminder_time_every_week">%1$s, 褋胁邪泻械 褋械写屑懈褑械</string>
+    <string name="pref_title_notifications_pebblemsg">袩芯褉褍泻械 袩械斜谢邪</string>
+    <string name="pref_blacklist_calendars">袨褌泻邪蟹邪薪懈 泻邪谢械薪写邪褉懈</string>
+    <string name="pref_blacklist_calendars_summary">袨褌泻邪蟹邪薪懈 泻邪谢械薪写邪褉懈 褋械 薪械褯械 褍褋泻谢邪褣懈胁邪褌懈 褋邪 褍褉械褣邪褬械屑</string>
+    <string name="pref_header_cannned_messages">袩芯褏褉邪褮械薪械 锌芯褉褍泻械</string>
+    <string name="pref_header_authentication">袩褉懈褬邪胁邪</string>
+    <string name="power_mode_title">袧邪褔懈薪 褉邪褋锌芯褉械写邪 褋薪邪谐械 褔邪褋芯胁薪懈泻邪</string>
+    <string name="power_mode_normal">袨斜懈褔薪芯</string>
+    <string name="pref_sensors_bp_calibration_low">袛懈邪褋褌芯谢薪懈 泻褉胁薪懈 锌褉懈褌懈褋邪泻 (薪懈蟹邪泻)</string>
+    <string name="pref_sensors_bp_calibration_high">小懈褋褌芯谢薪懈 泻褉胁薪懈 锌褉懈褌懈褋邪泻 (胁懈褋芯泻)</string>
+    <string name="zetime_title_heartrate">袩芯褋褌邪胁泻械 褋褉褔邪薪懈褏 屑械褉械褮邪</string>
+    <string name="zetime_title_screentime">孝褉邪褬邪褮械 褍 褋械泻褍薪写邪屑邪 蟹邪褋谢芯薪邪</string>
+    <string name="zetime_title_heart_rate_alarm">袥邪褉屑邪 褋褉褔邪薪懈褏 屑械褉械褮邪</string>
+    <string name="zetime_analog_mode_handsandsteps">袪褍泻械 懈 泻芯褉邪褑懈</string>
+    <string name="zetime_handmove_display_summary">袨泻褉械薪懈褌械 蟹谐谢芯斜 写邪 褍泻褭褍褔懈褌械 懈谢懈 懈褋泻褭褍褔懈褌械 蟹邪褋谢芯薪.</string>
+    <string name="zetime_signaling_none">孝懈褏芯</string>
+    <string name="prefs_hr_alarm_high">袙懈褋芯泻邪 谐褉邪薪懈褑邪</string>
+    <string name="unknown_state">袧械锌芯蟹薪邪褌芯 褋褌邪褮械</string>
+    <string name="_unknown_">(薪械锌芯蟹薪邪褌芯)</string>
+    <string name="unknown">袧械锌芯蟹薪邪褌芯</string>
+    <string name="test">袩褉芯斜邪</string>
+    <string name="tap_connected_device_for_activity">孝邪锌薪懈褌械 锌芯胁械蟹邪薪懈 褍褉械褣邪褬 褉邪写懈 写械褬褋褌邪胁邪</string>
+    <string name="tap_connected_device_for_vibration">孝邪锌薪懈褌械 锌芯胁械蟹邪薪懈 褍褉械褣邪褬 褉邪写懈 褌褉械褕褮械</string>
+    <string name="tap_a_device_to_connect">孝邪锌薪懈褌械 褍褉械褣邪褬 褉邪写懈 锌芯胁械蟹懈胁邪褮邪</string>
+    <string name="action_discover">袩芯胁械卸懈 薪芯胁懈 褍褉械褣邪褬</string>
+    <string name="discovery_stop_scanning">袟邪褍褋褌邪胁懈 锌褉械褌褉邪谐褍</string>
+    <string name="discovery_start_scanning">袟邪锌芯褔薪懈 锌褉芯薪邪谢邪卸械褮械</string>
+    <string name="discovery_bluetooth_scan">袩褉械褌褉邪谐邪 袘谢褍褌褍褌邪:</string>
+    <string name="discovery_bluetooth_le_scan">袩褉械褌褉邪谐邪 斜谢邪谐芯谐 袘谢褍褌褍褌邪:</string>
+    <string name="android_pairing_hint">袣芯褉懈褋褌懈 锌褉芯蟹芯褉褔械 褍锌邪褉懈胁邪褮邪 袘谢褍褌褍褌芯屑 袗薪写褉芯懈写邪 褉邪写懈 褍锌邪褉懈胁邪褮邪 褍褉械褣邪褬邪.</string>
+    <string name="title_activity_android_pairing">校锌邪褉懈 褍褉械褣邪褬</string>
+    <string name="pairing_creating_bond_with">袩褉邪胁懈屑 胁械蟹褍 褋邪 %1$s %2$s</string>
+    <string name="male">袦褍褕泻芯</string>
+    <string name="right">袛械褋薪芯</string>
+    <string name="horizontal">袙芯写芯褉邪胁薪芯</string>
+    <string name="vertical">校褋锌褉邪胁薪芯</string>
+    <string name="appinstaller_install">校谐褉邪写懈</string>
+    <string name="discovery_note">袩褉懈屑械写斜邪:</string>
+    <string name="pref_screen_vibration_profile">袩褉械褋械泻 褌褉械褕褮械</string>
+    <string name="miband_prefs_vibration">孝褉械褕褮邪</string>
+    <string name="pref_screen_notification_profile_generic_chat">袐邪褋泻邪褮械</string>
+    <string name="pref_screen_notification_profile_generic_navigation">袧邪胁芯褣械褮械</string>
+    <string name="pref_screen_notification_profile_generic_social">袛褉褍褕褌胁械薪械 屑褉械卸械</string>
+    <string name="interval_fifteen_minutes">褋胁邪泻懈褏 15 屑懈薪褍褌邪</string>
+    <string name="interval_thirty_minutes">褋胁邪泻懈褏 30 屑懈薪褍褌邪</string>
+    <string name="interval_ten_minutes">褋胁邪泻懈褏 10 屑懈薪褍褌邪</string>
+    <string name="interval_forty_five_minutes">褋胁邪泻懈褏 45 屑懈薪褍褌邪</string>
+    <string name="title_activity_set_reminders">袩芯写械褋懈 锌芯写褋械褌薪懈泻械</string>
+    <string name="pref_world_clocks_title">小胁械褌褋泻懈 褔邪褋芯胁薪懈褑懈</string>
+    <string name="reminder_delete_confirm_title">袠蟹斜褉懈褕懈 锌芯写褋械褌薪懈泻</string>
+    <string name="reminder_delete_confirm_description">袛邪 谢懈 褋褌械 褋懈谐褍褉薪懈 写邪 卸械谢懈褌械 懈蟹斜褉懈褋邪褌懈 锌芯写褋械褌薪懈泻\?</string>
+    <string name="reminder_every_day">小胁邪泻芯写薪械胁薪芯</string>
+    <string name="reminder_every_week">小械写屑懈褔薪芯</string>
+    <string name="reminder_every_month">袦械褋械褔薪芯</string>
+    <string name="reminder_every_year">袚芯写懈褕褮械</string>
+    <string name="world_clock_delete_confirm_description">袛邪 谢懈 褋褌械 褋懈谐褍褉薪懈 写邪 卸械谢懈褌械 懈蟹斜褉懈褋邪褌懈 褋胁械褌褋泻懈 褔邪褋芯胁薪懈泻\?</string>
+    <string name="reminder_no_free_slots_description">校褉械褣邪褬 薪械屑邪 锌褉邪蟹薪懈褏 屑械褋褌邪 蟹邪 锌芯写褋械褌薪懈泻械 (褍泻褍锌薪芯 屑械褋褌邪: %1$s)</string>
+    <string name="world_clock_timezone">袙褉械屑械薪褋泻邪 芯斜谢邪褋褌</string>
+    <string name="pref_title_general_autoconnectonbluetooth">袩芯胁械卸懈 褋械 褋邪 褍褉械褣邪褬械屑(懈屑邪) 袚械褵械褌斜褉胁薪邪 泻邪写邪 褬械 袘谢褍褌褍褌 褍泻褭褍褔械薪</string>
+    <string name="pref_summary_datetime_syctimeonconnect">校褋泻谢邪写懈 胁褉械屑械 褋邪 褍褉械褣邪褬械屑(懈屑邪) 袚械褵械褌斜褉胁薪邪 锌褉懈 锌芯胁械蟹懈胁邪褮褍, 泻邪写邪 褋械 胁褉械屑械 懈谢懈 胁褉械屑械薪褋泻邪 芯斜谢邪褋褌 锌褉芯屑械薪械 薪邪 褍褉械褣邪褬褍 袗薪写褉芯懈写邪, 懈 锌芯胁褉械屑械薪芯</string>
+    <string name="pref_title_minimize_priority">小邪泻褉懈褬 芯斜邪胁械褕褌械褮邪 袚械褵械褌斜褉胁薪邪</string>
+    <string name="gadgetbridge_running_generic">袚械褵械褌斜褉胁薪芯 写械谢褍褬械</string>
+    <string name="application_name_generic">袚械褵械褌斜褉胁薪芯</string>
+    <string name="title_activity_controlcenter_generic">袚械褵械褌斜褉胁薪芯</string>
+    <string name="about_activity_title_generic">袨 袚械褵械褌斜褉胁薪褍</string>
+    <string name="application_name_banglejs_main">袘邪薪谐谢.褬褋 袚械褵械褌斜褉胁薪芯</string>
+    <string name="title_activity_controlcenter_banglejs_main">袘邪薪谐谢.褬褋 袚械褵械褌斜褉胁薪芯</string>
+    <string name="about_activity_title_banglejs_main">袨 袘邪薪谐谢.褬褋 袚械褵械褌斜褉胁薪褍</string>
+    <string name="about_activity_title_main_nightly">袨 袚械褵械褌斜褉胁薪褍 薪芯褯薪芯屑</string>
+    <string name="application_name_main_nightly">袚械褵械褌斜褉胁薪芯 (薪芯褯薪芯)</string>
+    <string name="title_activity_controlcenter_main_nightly">袚械褵械褌斜褉胁薪芯 薪芯褯薪芯</string>
+    <string name="pref_title_development_miaddr">袗写褉械褋邪 袦懈 斜邪薪写邪</string>
+    <string name="pref_title_pebble_sync_health">校褋泻谢邪写懈 蟹写褉邪胁褭械 袩械斜谢邪</string>
+    <string name="pref_title_pebble_sync_misfit">校褋泻谢邪写懈 袦懈褋褎懈褌</string>
+    <string name="gadgetbridge_running_banglejs_main">袘邪薪谐谢.褬褋 写械褬褋褌胁褍褬械</string>
+    <string name="application_name_banglejs_nightly">袘邪薪谐谢.褬褋 袚械褵械褌斜褉胁薪芯 (薪芯褯薪芯)</string>
+    <string name="title_activity_controlcenter_banglejs_nightly">袘邪薪谐谢.褬褋 袚械褵械褌斜褉胁薪芯 (薪芯褯薪芯)</string>
+    <string name="about_activity_title_banglejs_nightly">袨 袘邪薪谐谢褍.褬褋 袚械褵械褌斜褉胁薪褍 (薪芯褯薪芯屑)</string>
+    <string name="gadgetbridge_running_banglejs_nightly">袧芯褯薪懈 袘邪薪谐谢.褬褋 写械褬褋褌胁褍褬械</string>
+    <string name="title_activity_controlcenter_main_nopebble">袚械褵械褌斜褉胁薪芯 薪芯褯薪芯 斜械蟹 袩械斜谢邪</string>
+    <string name="about_activity_title_main_nopebble">袨 袚械褵械褌斜褉胁薪褍 薪芯褯薪芯屑 斜械蟹 袩械斜谢邪</string>
+    <string name="gadgetbridge_running_main_nopebble">袧芯褯薪懈 斜械蟹袩械斜谢邪 袚袘 写械褬褋褌胁褍褬械</string>
+    <string name="gadgetbridge_running_main_nightly">袧芯褯薪芯 袚袘 写械褬褋褌胁褍褬械</string>
+    <string name="application_name_main_nopebble">袚械褵械褌斜褉胁薪芯 (薪芯褯薪芯, 薪械屑邪 写芯斜邪胁褭邪褔邪 袩械斜谢邪)</string>
+    <string name="about_description_banglejs_main">袗薪写褉芯懈写芯胁 写褉褍卸斜械薪懈褔泻懈 锌褉芯谐褉邪屑 蟹邪 袘邪薪谐谢械.褬褋 薪邪锌褉邪胁褭械薪 锌芯胁褉褏邪 锌褉芯褬械泻褌邪 袚械褵械褌斜褉胁薪邪, 褋邪 写芯写邪褌懈屑 锌褉懈褋褌褍锌芯屑 袠薪褌械褉薪械褌褍.
+\n
+\n袟斜芯谐 褋屑械褉薪懈褑邪 袚褍谐谢芯胁械 锌谢械褬 锌褉芯写邪胁薪懈褑械, 薪懈褬械 薪邪屑 写芯蟹胁芯褭械薪邪 胁械蟹邪 蟹邪 锌褉懈谢芯谐械 褍 褋邪屑芯屑 锌褉芯谐褉邪屑褍, 邪谢懈, 邪泻芯 袙邪屑 褋械 褋胁懈褣邪 芯胁邪褬 锌褉芯谐褉邪屑, 屑芯谢懈屑, 锌褉懈谢芯卸懈褌械 薪邪 屑邪褌懈褔薪褍 褋褌褉邪薪懈褑褍 袚械褵械褌斜褉胁薪邪 懈褋锌芯写.</string>
+    <string name="about_description_banglejs_nightly">袗薪写褉芯懈写芯胁 写褉褍卸斜械薪懈褔泻懈 锌褉芯谐褉邪屑 蟹邪 袘邪薪谐谢械.褬褋 薪邪锌褉邪胁褭械薪 锌芯胁褉褏邪 锌褉芯褬械泻褌邪 袚械褵械褌斜褉胁薪邪, 褋邪 写芯写邪褌懈屑 锌褉懈褋褌褍锌芯屑 袠薪褌械褉薪械褌褍.
+\n
+\n袟斜芯谐 褋屑械褉薪懈褑邪 袚褍谐谢芯胁械 锌谢械褬 锌褉芯写邪胁薪懈褑械, 薪懈褬械 薪邪屑 写芯蟹胁芯褭械薪邪 胁械蟹邪 蟹邪 锌褉懈谢芯谐械 褍 褋邪屑芯屑 锌褉芯谐褉邪屑褍, 邪谢懈, 邪泻芯 袙邪屑 褋械 褋胁懈褣邪 芯胁邪褬 锌褉芯谐褉邪屑, 屑芯谢懈屑, 锌褉懈谢芯卸懈褌械 薪邪 屑邪褌懈褔薪褍 褋褌褉邪薪懈褑褍 袚械褵械褌斜褉胁薪邪 懈褋锌芯写.</string>
+    <string name="about_description_main_nightly">小谢芯斜芯写薪邪 谢械胁芯褍屑薪芯卸懈胁邪 斜械蟹芯斜谢邪褔薪邪 蟹邪屑械薪邪 蟹邪 锌褉芯谐褉邪屑械 袗薪写褉芯懈写邪 蟹邪褌胁芯褉械薪懈褏 懈蟹胁芯褉邪 芯写 锌褉芯懈蟹胁芯褣邪褔邪. 袧芯褯薪芯 懈蟹写邪褮械 袚械褵械褌斜褉胁薪邪. 袧械 屑芯卸械 褋械 褍谐褉邪写懈褌懈 褍泻芯谢懈泻芯 胁械褯 懈屑邪褌械 褍谐褉邪褣械薪芯 袚械褵械褌斜褉胁薪芯 懈谢懈 锌褉芯谐褉邪屑 袩械斜谢, 蟹斜芯谐 褋褍泻芯斜邪 褍 写芯斜邪胁褭邪褔褍 袩械斜谢邪.</string>
+    <string name="about_description_main_nopebble">小谢芯斜芯写薪邪 谢械胁芯褍屑薪芯卸懈胁邪 斜械蟹芯斜谢邪褔薪邪 蟹邪屑械薪邪 蟹邪 锌褉芯谐褉邪屑械 袗薪写褉芯懈写邪 蟹邪褌胁芯褉械薪懈褏 懈蟹胁芯褉邪 芯写 锌褉芯懈蟹胁芯褣邪褔邪. 袧芯褯薪芯 懈蟹写邪褮械 袚械褵械褌斜褉胁薪邪. 袨胁芯 懈蟹写邪褮械 懈屑邪 锌褉械懈屑械薪芯胁邪薪芯谐 写芯斜邪胁褭邪褔邪 袩械斜谢邪 褉邪写懈 褋锌褉械褔邪胁邪褮邪 褋褍泻芯斜邪, 褌邪泻芯 写邪 薪械泻械 芯写薪芯褋薪械 胁械蟹械 薪械褯械 褉邪写懈褌懈, 邪谢懈 褋械 屑芯卸械 褍谐褉邪写懈褌懈 锌芯褉械写 褍谐褉邪褣械薪芯谐 袚械褵械褌斜褉胁薪邪.</string>
+    <string name="pref_summary_notifications_pebblemsg">袩芯写褉褕泻邪 锌褉芯谐褉邪屑邪 泻芯褬懈 褕邪褭褍 芯斜邪胁械褕褌械褮邪 袩械斜谢褍 袩械斜谢芯胁懈屑 泻懈褌芯屑.</string>
+    <string name="debugactivity_confirm_remove_device_preferences_title">袛邪 谢懈 褍泻谢芯薪懈褌懈 锌芯褋褌邪胁泻械 褍褉械褣邪褬邪\?</string>
+    <string name="debugactivity_confirm_remove_device_preferences">袨胁芯 褯械 褋胁懈屑 锌芯胁械蟹邪薪懈屑 褍褉械褣邪褬懈屑邪 胁褉邪褌懈褌懈 锌芯褋褌邪胁泻械 薪邪 锌芯褔械褌薪械. 袛邪 谢懈 褋褌械 褋懈谐褍褉薪懈\?</string>
+    <string name="pref_title_notification_media_ignores_application_list">小锌懈褋邪泻 锌褉芯谐褉邪屑邪 蟹邪 蟹邪薪械屑邪褉懈胁邪褮械 芯斜邪胁械褕褌械褮邪 芯 屑械写懈褬懈屑邪</string>
+    <string name="function_enabled">袨屑芯谐褍褯械薪芯</string>
+    <string name="hungarian">袦邪褣邪褉褋泻懈</string>
+    <string name="pref_world_clocks_summary">袩芯写械褋懈褌械 褔邪褋芯胁薪懈泻械 褉邪蟹谢懈褔懈褌懈褏 胁褉械屑械薪褋泻懈褏 芯斜谢邪褋褌懈</string>
+    <string name="alarm_sun_short">袧械写</string>
+    <string name="alarm_mon_short">袩芯薪</string>
+    <string name="alarm_tue_short">校褌芯</string>
+    <string name="prefs_heartrate_alert_experimental_description">孝褉械褋械 褍褉械褣邪褬 泻邪写邪 芯褌泻褍褑邪褬懈 褋褉褑邪 锌褉械褣褍 锌褉邪谐, 斜械蟹 斜懈谢芯 泻芯褬懈褏 芯褔懈谐谢械写薪懈褏 褌械谢械褋薪懈褏 写械褬褋褌邪胁邪 褍 锌芯褋谢械写褮懈褏 10 屑懈薪褍褌邪. 袨胁芯 褬械 芯锌懈褌薪邪 屑芯谐褍褯薪芯褋褌, 懈 薪懈褬械 胁邪褭邪薪芯 懈褋锌懈褌邪薪邪.</string>
+    <string name="cannot_connect">袧懈褋邪屑 褍褋锌械芯 写邪 褋械 锌芯胁械卸械屑: %1$s</string>
+    <string name="installer_activity_unable_to_find_handler">袧懈褋邪屑 褍褋锌械芯 写邪 锌褉芯薪邪褣械屑 褉褍泻芯胁邪芯褑邪 蟹邪 褍谐褉邪写褮褍 芯胁械 写邪褌芯褌械泻械.</string>
+    <string name="dialog_hide">小邪泻褉懈褬</string>
+    <string name="miband_fwinstaller_untested_version">袧械锌褉芯胁械褉械薪芯 懈蟹写邪褮械!</string>
+    <string name="pbwinstallhandler_app_item">%1$s (%2$s)</string>
+    <string name="seconds_25">25 褋械泻褍薪写懈</string>
+    <string name="live_activity_max_heart_rate">孝褉械薪褍褌薪懈 / 薪邪褬胁懈褕懈 褋褉褔邪薪懈 芯褌泻褍褑邪褬懈: %1$d / %2$d</string>
+    <string name="dateformat_date_time">袙褉械屑械 懈 写邪褌褍屑</string>
+    <string name="prefs_enable_find_phone">校泻褭褍褔懈 鈥炐熝�芯薪邪褣懈 褌械谢械褎芯薪鈥�</string>
+    <string name="prefs_find_phone_duration">孝褉邪褬邪褮械 蟹胁芯薪邪 褍 褋械泻褍薪写邪屑邪</string>
+    <string name="miband2_prefs_dateformat">袨斜谢懈泻 写邪褌褍屑邪</string>
+    <string name="dateformat_time">袙褉械屑械</string>
+    <string name="mi2_prefs_button_press_broadcast">袩褉械薪械褋懈 锌芯褉褍泻褍 蟹邪 褋谢邪褮械</string>
+    <string name="mi2_prefs_button_press_count_summary">袘褉芯褬 锌褉懈褌懈褋邪泻邪 写褍谐屑邪写懈 蟹邪 芯泻懈写邪褮械 写芯谐邪褣邪褬邪 1. 袧邪褋褌邪胁褭邪褮械屑 懈褋褌芯谐 斜褉芯褬邪 锌褉懈褌懈褋邪泻邪 褋械 芯泻懈写邪 写芯谐邪褣邪褬 2, 懈 褌邪泻芯 写邪褭械.</string>
+    <string name="mi2_prefs_button_press_count_max_delay">袧邪褬胁械褯邪 芯写谐芯写邪 懈蟹屑械褣褍 锌褉懈褌懈褋邪泻邪</string>
+    <string name="mi2_prefs_button_action_vibrate">袨屑芯谐褍褯懈 褌褉械褕褮褍 褍褉械褣邪褬邪</string>
+    <string name="mi2_prefs_button_action_vibrate_summary">袨屑芯谐褍褯懈 褌褉械褕褮褍 褍褉械褣邪褬邪 薪邪 芯泻懈写邪褮械 写械褬褋褌胁邪 写褍谐屑邪写懈</string>
+    <string name="mi2_prefs_button_press_count_max_delay_summary">袧邪褬胁械褯邪 芯写谐芯写邪 懈蟹屑械褣褍 锌褉懈褌懈褋邪泻邪 写褍谐屑邪写懈 褍 屑懈谢懈褋械泻褍薪写邪屑邪</string>
+    <string name="mi2_prefs_button_action_summary">袨屑芯谐褍褯懈 写械褬褋褌胁芯 薪邪 芯写褉械褣械薪 斜褉芯褬 锌褉懈褌懈褋邪泻邪 写褍谐屑械褌邪</string>
+    <string name="mi2_prefs_display_items">袩褉懈泻邪蟹褍褬 褋褌邪胁泻械</string>
+    <string name="mi2_prefs_goal_notification">袨斜邪胁械褕褌械褮邪 芯 褑懈褭褍</string>
+    <string name="mi2_prefs_goal_notification_summary">校褉械褣邪褬 褯械 褌褉械褋褌懈 泻邪写邪 褋械 写薪械胁薪懈 褑懈褭 泻芯褉邪泻邪 写芯褋械谐薪械</string>
+    <string name="mi2_prefs_display_items_summary">袠蟹邪斜械褉懈褌械 褋褌邪胁泻械 蟹邪 锌褉懈泻邪蟹 薪邪 蟹邪褋谢芯薪褍 褍褉械褣邪褬邪</string>
+    <string name="mi2_prefs_rotate_wrist_to_switch_info">袨泻褉械褯懈褌械 蟹谐谢芯斜 褉邪写懈 锌褉芯屑械薪械 芯斜邪胁械褕褌械褮邪</string>
+    <string name="mi2_prefs_heart_rate_monitoring_summary">袩芯写械褋懈褌械 薪邪写谐谢械写邪褮械 泻褍褑邪褮邪 褋褉褑邪</string>
+    <string name="mi2_prefs_heart_rate_monitoring">袧邪写谐谢械写邪褮械 芯褌泻褍褑邪褬邪 褋褉褑邪</string>
+    <string name="mi3_prefs_night_mode_summary">小邪屑芯褋褌邪谢薪芯 褍屑邪褮懈 芯褋胁械褌褭械褮械 蟹邪褋谢芯薪邪 褍褉械褣邪褬邪 薪芯褯褍</string>
+    <string name="mi2_prefs_do_not_disturb_start">袙褉械屑械 锌芯褔械褌泻邪</string>
+    <string name="mi2_prefs_do_not_disturb_end">袙褉械屑械 泻芯薪褑邪</string>
+    <string name="prefs_password_4_digits_1_to_4_summary">袥芯蟹懈薪泻邪 屑芯褉邪 褋邪写褉卸邪褌懈 褔械褌懈褉懈 斜褉芯褬邪, 泻芯褉懈褕褯械褮械屑 斜褉芯褬械胁邪 芯写 1 写芯 4</string>
+    <string name="prefs_control_center">袧邪写蟹芯褉薪芯 褋褉械写懈褕褌械</string>
+    <string name="bip_prefs_shotcuts_summary">袠蟹邪斜械褉懈褌械 锌褉械褔懈褑械 薪邪 蟹邪褋谢芯薪褍 褍褉械褣邪褬邪</string>
+    <string name="mi5_prefs_workout_activity_types">袙褉褋褌械 写械褬褋褌邪胁邪</string>
+    <string name="simplified_chinese">袩芯褬械写薪芯褋褌邪胁褭械薪懈 袣懈薪械褋泻懈</string>
+    <string name="german">袧械屑邪褔薪懈</string>
+    <string name="persian">袩邪褉褋懈褬褋泻懈</string>
+    <string name="scandinavian">小泻邪薪写懈薪邪胁褋泻懈</string>
+    <string name="ukranian">校泻褉邪褬懈薪褋泻懈</string>
+    <string name="italian">袠褌邪谢懈褬邪薪褋泻懈</string>
+    <string name="french">肖褉邪薪褑褍褋泻懈</string>
+    <string name="polish">袩芯褭褋泻懈</string>
+    <string name="korean">袣芯褉械褬褋泻懈</string>
+    <string name="swedish">小胁械薪褋泻懈</string>
+    <string name="pref_disable_new_ble_scanning">袨薪械屑芯谐褍褯懈 薪芯胁褍 锌褉械褌褉邪谐褍 袘袥袝</string>
+    <string name="pref_summary_disable_new_ble_scanning">袨蟹薪邪褔懈褌械 芯胁芯 邪泻芯 褍褉械褣邪褬 薪械 屑芯卸械 斜懈褌懈 锌褉芯薪邪褣械薪 褌芯泻芯屑 锌褉械褌褉邪谐械</string>
+    <string name="world_clock_code">袣芯写</string>
+    <string name="pref_screen_notification_profile_anti_loss">校锌芯蟹芯褉械褮械 锌褉芯褌懈胁 谐褍斜褭械褮邪</string>
+    <string name="prefs_title_heartrate_measurement_interval">笑械谢芯写薪械胁薪芯 屑械褉械褮械 芯褌泻褍褑邪褬邪 褋褉褑邪</string>
+    <string name="pref_screen_notification_idle_alerts">袥邪褉屑械 薪械泻褉械褌邪褮邪</string>
+    <string name="alarm_smart_wakeup">袩邪屑械褌薪芯 斜褍褣械褮械</string>
+    <string name="chart_no_active_data">袧械屑邪 锌褉械锌芯蟹薪邪褌懈褏 写械褬褋褌邪胁邪.</string>
+    <string name="chart_get_active_and_synchronize">袛械褬褋褌胁褍褬褌械 懈 褍褋泻谢邪写懈褌械 褍褉械褣邪褬.</string>
+    <string name="prefs_heartrate_alert_threshold">袩褉邪谐 谢邪褉屑械 芯褌泻褍褑邪褬邪 褋褉褑邪</string>
+    <string name="prefs_stress_monitoring_description">袧邪写谐谢械写邪褮械 锌褉邪谐邪 褋褌褉械褋邪 锌褉懈谢懈泻芯屑 芯写屑芯褉邪</string>
+    <string name="prefs_spo2_monitoring_title">袧邪写谐谢械写邪褮械 蟹邪褋懈褯械薪芯褋褌懈 泻懈褋械芯薪懈泻邪 褍 泻褉胁懈</string>
+    <string name="prefs_spo2_monitoring_description">小邪屑芯褋褌邪谢薪芯 薪邪写谐谢械写邪褬 蟹邪褋懈褯械薪芯褋褌 泻懈褋械芯薪懈泻邪 褍 泻褉胁懈 褌芯泻芯屑 写邪薪邪</string>
+    <string name="dbaccess_error_executing">袛械褋懈谢邪 褋械 谐褉械褕泻邪 锌褉懈 懈蟹胁褉褕邪胁邪褮褍 鈥�%1$s鈥�</string>
+    <string name="prefs_charts_tabs">袥懈褋褌芯胁懈 褌邪斜械谢邪</string>
+    <string name="sleepchart_your_sleep">小锌邪胁邪褮械</string>
+    <string name="weeksleepchart_sleep_a_week">小械写屑懈褔薪芯 褋锌邪胁邪褮械</string>
+    <string name="show_ongoing_activity">袩褉懈泻邪蟹褍褬 懈褋泻邪泻邪褮械 褌械泻褍褯械谐 写械褬褋褌胁邪</string>
+    <string name="devicestatus_connecting">校褉械褣邪褬 褬械 锌芯胁械蟹邪薪</string>
+    <string name="devicestatus_connected">校褉械褣邪褬 褬械 锌芯胁械蟹邪薪</string>
+    <string name="devicestatus_upload_starting">袩芯褔懈褮械 芯褌锌褉械屑邪褮械</string>
+    <string name="devicestatus_disconnecting">校褉械褣邪褬 谐褍斜懈 胁械蟹褍!</string>
+    <string name="devicestatus_disconnected">校褉械褣邪褬 褬械 懈蟹谐褍斜懈芯 胁械蟹褍!</string>
+    <string name="devicestatus_upload_completed">袨褌锌褉械屑邪褮械 褬械 芯泻芯薪褔邪薪芯</string>
+    <string name="devicestatus_upload_validating">袨褌锌褉械屑邪褮械 褬械 锌褉芯胁械褉械薪芯</string>
+    <string name="devicestatus_upload_aborted">袨褌锌褉械屑邪褮械 褬械 芯褌泻邪蟹邪薪芯!</string>
+    <string name="devicestatus_upload_failed">袨褌锌褉械屑邪褮械 薪懈褬械 褍褋锌械谢芯</string>
+    <string name="fwapp_install_device_not_ready">袛邪褌芯褌械泻邪 薪械 屑芯卸械 斜懈褌懈 褍谐褉邪褣械薪芯, 褍褉械褣邪褬 薪懈褬械 褋锌褉械屑邪薪.</string>
+    <string name="miband_fwinstaller_compatible_version">袨写谐芯胁邪褉邪褬褍褯械 懈蟹写邪褮械</string>
+    <string name="heart_rate_result">袧邪谢邪蟹懈 屑械褉械褮邪</string>
+    <string name="no_limit">袧械屑邪 谐褉邪薪懈褑械</string>
+    <string name="blood_pressure">袣褉胁薪懈 锌褉懈褌懈褋邪泻</string>
+    <string name="getting_heart_rate">袦械褉械褮械</string>
+    <string name="seconds_8">8 褋械泻褍薪写懈</string>
+    <string name="overstep">袩褉械泻芯褉邪褔械褮械: %1$d</string>
+    <string name="pref_enable_unsupported_settings_title">袨屑芯谐褍褯懈 薪械锌芯写褉卸邪薪械 锌芯褋褌邪胁泻械</string>
+    <string name="pref_title_third_party_app_device_settings">袨屑芯谐褍褯懈 锌褉芯谐褉邪屑懈屑邪 褌褉械褯懈褏 褋褌褉邪薪邪 写邪 屑械褮邪褬褍 锌芯褋褌邪胁泻械</string>
+    <string name="abstract_chart_fragment_kind_light_sleep">袛褉械屑邪褮械</string>
+    <string name="abstract_chart_fragment_kind_deep_sleep">袛褍斜芯泻芯 褋锌邪胁邪褮械</string>
+    <string name="abstract_chart_fragment_kind_rem_sleep">小锌邪胁邪褮械 褋邪 斜褉蟹懈屑 锌芯泻褉械褌懈屑邪 芯褔懈褬褍</string>
+    <string name="abstract_chart_fragment_kind_not_worn">袧懈褬械 薪芯褕械薪</string>
+    <string name="english">袝薪谐谢械褋泻懈</string>
+    <string name="russian">袪褍褋泻懈</string>
+    <string name="icelandic">袠褋谢邪薪写褋泻懈</string>
+    <string name="lithuanian">袥懈褌胁邪薪褋泻懈</string>
+    <string name="japanese">袌邪锌邪薪褋泻懈</string>
+    <string name="turkish">孝褍褉泻褋泻懈</string>
+    <string name="ukrainian">校泻褉邪褬懈薪褋泻懈</string>
+    <string name="greek">袚褉褔泻懈</string>
+    <string name="czesh">效械褕泻懈</string>
+    <string name="pref_workout_start_on_phone_title">袩褉邪褯械褮械 锌褉芯谐褉邪屑邪 蟹邪 胁械卸斜邪褮械</string>
+    <string name="alarm_thu_short">效械褌</string>
+    <string name="alarm_sat_short">小褍斜</string>
+    <string name="alarm_snooze">袨写谢芯卸懈</string>
+    <string name="prefs_heartrate_alert_experimental_title">校锌芯蟹芯褉械褮械 芯褌泻褍褑邪褬邪 褋褉褑邪 (锌褉芯斜薪芯)</string>
+    <string name="prefs_heartrate_alert_low_threshold">袩褉邪谐 谢邪褉屑械 薪懈褋泻懈褏 芯褌泻褍褑邪褬邪 褋褉褑邪</string>
+    <string name="prefs_stress_monitoring_title">袧邪写谐谢械写邪褮械 褋褌褉械褋邪</string>
+    <string name="weekstepschart_steps_a_week">小械写屑懈褔薪懈 泻芯褉邪褑懈</string>
+    <string name="activity_list_summary_distance">袛邪褭懈薪邪</string>
+    <string name="battery">袘邪褌械褉懈褬邪</string>
+    <string name="device_not_connected">袧懈褬械 锌芯胁械蟹邪薪.</string>
+    <string name="pref_title_keep_data_on_device">袟邪写褉卸邪胁邪褬 锌芯写邪褌泻械 芯 写械褋褌胁懈屑邪 薪邪 褍褉械褣邪褬褍</string>
+    <string name="prefs_find_phone">袩褉芯薪邪褣懈 褌械谢械褎芯薪</string>
+    <string name="mi2_prefs_button_actions">袛械褬褋褌胁邪 写褍谐屑邪写懈</string>
+    <string name="mi2_prefs_inactivity_warnings_summary">校褉械褣邪褬 褯械 褌褉械褋褌懈 泻邪写邪 褋械 薪械 泻褉械褯械褌械 薪械泻芯 胁褉械屑械</string>
+    <string name="mi2_prefs_inactivity_warnings">校锌芯蟹芯褉械褮邪 芯 薪械泻褉械褌邪褮褍</string>
+    <string name="prefs_password">袥芯蟹懈薪泻邪</string>
+    <string name="prefs_password_summary">袟邪泻褭褍褔邪褬褌械 褍褉械褣邪褬 谢芯蟹懈薪泻芯屑 泻邪写邪 褋械 褍泻谢芯薪懈 褋邪 蟹谐谢芯斜邪</string>
+    <string name="prefs_password_enabled">袥芯蟹懈薪泻邪 褬械 芯屑芯谐褍褯械薪邪</string>
+    <string name="prefs_always_on_display">校胁械泻 薪邪 蟹邪褋谢芯薪褍</string>
+    <string name="prefs_password_6_digits_0_to_9_summary">袥芯蟹懈薪泻邪 屑芯褉邪 懈屑邪褌懈 6 斜褉芯褬械胁邪, 泻芯褉懈褕褯械褮械屑 褋邪屑芯 斜褉芯褬械胁邪</string>
+    <string name="mi3_prefs_night_mode">袧芯褯薪懈 褉邪写</string>
+    <string name="mi3_prefs_band_screen_unlock_summary">袨斜褉懈褕懈褌械 褉邪写懈 芯褌泻褭褍褔邪胁邪褮邪 蟹邪褋谢芯薪邪 褍褉械褣邪褬邪</string>
+    <string name="traditional_chinese">孝褉邪写懈褑懈芯薪邪谢薪懈 袣懈薪械褋泻懈</string>
+    <string name="activity_prefs_year_birth">袚芯写懈薪邪 褉芯褣械褮邪</string>
+    <string name="activity_prefs_gender">袩芯谢</string>
+    <string name="activity_prefs_weight_kg">孝械卸懈薪邪 褍 泻谐</string>
+    <string name="pref_workout_start_on_phone_summary">袩芯泻褉械薪懈/蟹邪褍褋褌邪胁懈 锌褉邪褯械褮械 锌褉芯谐褉邪屑邪 蟹邪 胁械卸斜邪褮械 薪邪 褌械谢械褎芯薪褍 泻邪写邪 褋械 薪邪 褍褉械褣邪褬褍 锌芯泻褉械薪械 胁械卸斜邪褮械 褍蟹 袚袩小</string>
+    <string name="pref_workout_send_gps_title">袩芯褕邪褭懈 袚袩小 褌芯泻芯屑 胁械卸斜邪褮邪</string>
+    <string name="pref_auto_fetch_limit_fetches_summary">袛芯斜邪胁褭邪褮械 褋胁邪泻懈褏 %d 屑懈薪褍褌邪</string>
+    <string name="discovery_scanning_intensity_warning">校泻芯谢懈泻芯 锌褉懈屑械褌懈褌械 泻芯褔械褮械 懈谢懈 薪械芯写蟹懈胁薪芯褋褌, 锌芯泻褍褕邪褬褌械 写邪 褋屑邪褮懈褌械 褬邪褔懈薪褍 锌褉械褌褉邪谐械. 袗泻芯 褋械 褍褉械褣邪褬 薪械 锌褉芯薪邪褣械, 锌芯泻褍褕邪褬褌械 写邪 锌芯胁械褯邪褌械 褬邪褔懈薪褍 锌褉械褌褉邪谐械.</string>
+    <string name="discovery_scanning_intensity">袌邪褔懈薪邪 锌褉械褌褉邪谐械</string>
+    <string name="pref_enable_unsupported_settings_summary">袨胁芯 褯械 芯屑芯谐褍褯懈褌懈 锌褉懈褋褌褍芯 褋胁懈屑 写芯褋褌褍锌薪懈屑 锌芯褋褌邪胁泻邪屑邪, 褔邪泻 褍泻芯谢懈泻芯 薪懈褋褍 锌芯写褉卸邪薪械 芯写 褍褉械褣邪褬邪. 袦芯卸械 褍蟹褉芯泻芯胁邪褌懈 薪械锌芯褋褌芯褬邪薪芯褋褌 懈 芯褌泻邪蟹械 薪邪 褍褉械褣邪褬褍.</string>
+    <string name="live_activity_steps_history">袩芯胁械褋褌 泻芯褉邪泻邪</string>
+    <string name="live_activity_current_steps_per_minute">孝褉械薪褍褌薪懈 泻芯褉邪褑懈 锌芯 屑懈薪褍褌褍</string>
+    <string name="live_activity_start_your_activity">袩芯褔薪懈褌械 褋胁芯褬邪 写械褬褋褌胁邪</string>
+    <string name="live_activity_steps_per_minute_history">袩芯胁械褋褌 泻芯褉邪泻邪 锌芯 屑懈薪褍褌懈屑邪</string>
+    <string name="you_slept">%1$s _%2$s</string>
+    <string name="you_did_not_sleep">袧懈褋褌械 褋锌邪胁邪谢懈</string>
+    <string name="charts_min_max_heartrate_popup">袧邪褬薪懈卸懈 芯褌泻褍褑邪褬懈 褋褉褑邪: %1$d
+\n袧邪褬胁懈褕懈 芯褌泻褍褑邪褬懈 褋褉褑邪: %2$d
+\n袌邪褔懈薪邪 泻褉械褌邪褮邪: %3$s</string>
+    <string name="mi2_prefs_button_press_broadcast_summary">袩褉械薪芯褋懈 褋谢邪褮械 锌芯褉褍泻械 褋邪 写芯谐邪褣邪褬械屑. 袨写褉械写薪懈褑邪 鈥瀊utton_id鈥� 褋械 写芯写邪褬械 褋邪屑芯褋褌邪谢薪芯 薪邪 褋胁邪泻褍 锌芯褉褍泻褍.</string>
+    <string name="mi2_prefs_button_action">袨屑芯谐褍褯懈 写械褬褋褌胁芯 写褍谐屑邪写懈</string>
+    <string name="mi2_prefs_button_actions_summary">袨写褉械写懈褌械 写械褬褋褌胁邪 锌褉懈褌懈褋泻邪 写褍谐屑邪写懈</string>
+    <string name="mi2_prefs_button_press_count">袘褉芯褬邪褮械 锌褉懈褌懈褋泻邪 写褍谐屑邪写懈</string>
+    <string name="mi2_prefs_inactivity_warnings_threshold">袙褉械屑械 薪械泻褉械褌邪褮邪 (褍 屑懈薪褍褌懈屑邪)</string>
+    <string name="mi2_prefs_inactivity_warnings_dnd_summary">袨薪械屑芯谐褍褯懈 褍锌芯蟹芯褉械褮邪 芯 薪械泻褉械褌邪褮褍 蟹邪 芯写褉械褣械薪芯 胁褉械屑械</string>
+    <string name="mi2_prefs_do_not_disturb_summary">校褉械褣邪褬 薪械褯械 锌褉懈屑邪褌懈 芯斜邪胁械褕褌械褮邪 写芯泻 褬械 褍泻褭褍褔械薪芯</string>
+    <string name="mi2_prefs_heart_rate_monitoring_alerts_summary">袩芯写械褋懈褌械 薪邪写谐谢械写邪褮械 泻褍褑邪褮邪 褋褉褑邪 懈 褉邪褋锌芯薪 谢邪褉屑懈</string>
+    <string name="bip_prefs_shortcuts">袩褉械褔懈褑械</string>
+    <string name="prefs_activate_display_on_lift_sensitivity">袨褋械褌褭懈胁芯褋褌</string>
+    <string name="lefun_prefs_hydration_reminder_title">袩芯写褋械褌薪懈泻 卸械褣懈</string>
+    <string name="lefun_prefs_hydration_reminder_summary">校褉械褣邪褬 褯械 褋械 褌褉械褋褌懈 写邪 袙邪褋 锌芯写褋械褌懈 写邪 锌懈褬械褌械 胁芯写褍</string>
+    <string name="lefun_prefs_antilost_title">袩褉芯褌懈胁 谐褍斜褭械褮邪</string>
+    <string name="lefun_prefs_antilost_summary">校褉械褣邪褬 褯械 褋械 褌褉械褋褌懈 邪泻芯 褋邪 褌械谢械褎芯薪芯屑 懈蟹谐褍斜懈 胁械蟹褍</string>
+    <string name="lefun_prefs_interface_language_title">袌械蟹懈泻 褋褍褔械褭邪</string>
+    <string name="automatic">小邪屑芯褋褌邪谢薪芯</string>
+    <string name="bengali">袘械薪谐邪谢褋泻懈</string>
+    <string name="czech">效械褕泻懈</string>
+    <string name="estonian">袝褋褌芯薪褋泻懈</string>
+    <string name="romanian">袪褍屑褍薪褋泻懈</string>
+    <string name="indonesian">袠薪写芯薪械蟹懈褬褋泻懈</string>
+    <string name="vietnamese">袙懈褬械褌薪邪屑褋泻懈</string>
+    <string name="portuguese">袩芯褉褌褍谐邪谢褋泻懈</string>
+    <string name="portuguese_br">袩芯褉褌褍谐邪谢褋泻懈 (袘褉邪蟹懈谢褋泻懈)</string>
+    <string name="portuguese_pt">袩芯褉褌褍谐邪谢褋泻懈 (袩芯褉褌褍谐邪谢褋泻懈)</string>
+    <string name="waiting_for_reconnect">效械泻邪屑 锌芯胁械蟹懈胁邪褮械</string>
+    <string name="activity_prefs_height_cm">袙懈褋懈薪邪 褍 褑屑</string>
+    <string name="activity_prefs_target_weight_kg">笑懈褭薪邪 褌械卸懈薪邪 褍 泻谐</string>
+    <string name="activity_prefs_step_length_cm">袛褍卸懈薪邪 泻芯褉邪泻邪 褍 褑屑</string>
+    <string name="pref_header_charts">袩芯褋褌邪胁泻械 褌邪斜械谢邪</string>
+    <string name="pref_title_charts_swipe">袨屑芯谐褍褯懈</string>
+    <string name="clock">效邪褋芯胁薪懈泻</string>
+    <string name="pref_workout_send_gps_summary">楔邪褭懈 褌褉械薪褍褌薪懈 锌芯谢芯卸邪褬 袚袩小-邪 薪邪 褍褉械褣邪褬 褌芯泻芯屑 胁械卸斜邪褮邪</string>
+    <string name="title_activity_world_clock_details">袩芯褬械写懈薪芯褋褌懈 褋胁械褌褋泻芯谐 褔邪褋芯胁薪懈泻邪</string>
+    <string name="controlcenter_start_activitymonitor">袙邪褕邪 写械褬褋褌胁邪</string>
+    <string name="notif_battery_low_bigtext_last_charge_time">袟邪写褮邪 写芯锌褍薪邪: %s
+\n</string>
+    <string name="notif_battery_low_bigtext_number_of_charges">袘褉芯褬 写芯锌褍薪邪: %s</string>
+    <string name="notif_battery_low">%1$s</string>
+    <string name="notif_battery_low_extended">%1$s 锌褉邪蟹薪邪 斜邪褌械褉懈褬邪: %2$s</string>
+    <string name="notif_export_failed_title">袠蟹胁芯蟹 斜邪蟹械 锌芯写邪褌邪泻邪 薪懈褬械 褍褋锌械芯! 袦芯谢懈屑, 锌褉芯胁械褉懈褌械 锌芯褋褌邪胁泻械.</string>
+    <string name="prefs_charts_tabs_summary">袙懈写褭懈胁懈 谢懈褋褌芯胁懈 褌邪斜械谢邪</string>
+    <string name="prefs_sounds">袟胁褍褑懈</string>
+    <string name="prefs_sounds_summary">袩芯写械褋懈褌械 泻邪写邪 褯械 褍褉械褣邪褬 锌懈褋薪褍褌懈</string>
+    <string name="overslept">袩褉械褌械褉邪薪芯 褋锌邪胁邪褮械: %1$s</string>
+    <string name="chart_steps">袣芯褉邪褑懈</string>
+    <string name="calories">袣邪谢芯褉懈褬械</string>
+    <string name="distance">袛邪褭懈薪邪</string>
+    <string name="heart_rate">袨褌泻褍褑邪褬懈 褋褉褑邪</string>
+    <string name="liveactivity_live_activity">袛械褬褋褌胁邪 褍卸懈胁芯</string>
+    <string name="weeksteps_today_steps_description">袣芯褉邪泻邪 写邪薪邪褋, 褑懈褭: %1$s</string>
+    <string name="lack_of_step">袧械写芯褋褌邪褌邪泻 泻芯褉邪泻邪: %1$d</string>
+    <string name="pref_summary_third_party_app_device_settings">袨屑芯谐褍褯懈 芯褋褌邪谢懈屑 褍谐褉邪褣械薪懈屑 锌褉芯谐褉邪屑懈屑邪 褌褉械褯懈褏 褋褌褉邪薪邪 写邪 屑械褮邪褬褍 锌芯褋褌邪胁泻械 褍褉械褣邪褬邪.</string>
+    <string name="mi2_prefs_do_not_disturb">袧械 褍蟹薪械屑懈褉邪胁邪褬</string>
+    <string name="prefs_screen_timeout">袙褉械屑械 褍泻褭褍褔械薪芯褋褌懈 蟹邪褋谢芯薪邪</string>
+    <string name="extended_ascii">袩褉芯褕懈褉械薪懈 袗小袣袠袠</string>
+    <string name="alarm_wed_short">小褉械</string>
+    <string name="alarm_fri_short">袩械褌</string>
+    <string name="user_feedback_miband_set_alarms_failed">袛械褋懈谢邪 褋械 谐褉械褕泻邪 锌褉懈 锌芯褋褌邪胁褭邪褮褍 谢邪褉屑械, 屑芯谢懈屑, 锌芯泻褍褕邪褬褌械 锌芯薪芯胁芯.</string>
+    <string name="chart_no_data_synchronize">袧械屑邪 锌芯写邪褌邪泻邪. 校褋泻谢邪写懈褌懈 褍褉械褣邪褬\?</string>
+    <string name="user_feedback_miband_activity_data_transfer">袩褉械薪械褯褍 %1$s 锌芯写邪褌邪泻邪 锌芯褔械胁褕懈 芯写 %2$s</string>
+    <string name="miband_prefs_fitness_goal">袛薪械胁薪懈 褑懈褭 斜褉芯褬邪 泻芯褉邪泻邪</string>
+    <string name="prefs_relaxation_reminder_description">孝褉械褋懈 褍褉械褣邪褬 褉邪写懈 芯斜邪胁械褕褌邪胁邪褮邪 邪泻芯 褬械 胁褉械写薪芯褋褌 褋褌褉械褋邪 胁械褯邪 芯写 80</string>
+    <string name="prefs_activity_monitoring_description">小邪屑芯褋褌邪谢薪芯 褍胁械褯邪褬 锌褉邪谐 屑械褉械褮邪 芯褌泻褍褑邪褬邪 褋褉褑邪 泻邪写邪 褍褉械褣邪褬 锌褉械锌芯蟹薪邪 褌械谢械褋薪芯 胁械卸斜邪褮械, 褉邪写懈 锌芯胁械褯邪褮邪 褌邪褔薪芯褋褌懈 屑械褉械褮邪 芯褌泻褍褑邪褬邪 褋褉褑邪.</string>
+    <string name="prefs_relaxation_reminder_title">袩芯写褋械褌薪懈泻 蟹邪 芯锌褍褕褌邪褮械</string>
+    <string name="prefs_heartrate_alert_high_threshold">袩褉邪谐 谢邪褉屑械 胁懈褋芯泻懈褏 芯褌泻褍褑邪褬邪 褋褉褑邪</string>
+    <string name="prefs_spo2_alert_threshold">袩褉邪谐 谢邪褉屑械 SPO2</string>
+    <string name="pbw_install_handler_unable_to_install">袧懈褋邪屑 褍褋锌械芯 写邪 褍谐褉邪写懈屑 写邪褌芯褌械泻褍: %1$s</string>
+    <string name="notif_battery_low_title">袘邪褌械褉懈褬邪 褍褉械褣邪褬邪 褬械 锌褉邪蟹薪邪!</string>
+    <string name="notif_battery_low_percent">%1$s 锌褉械芯褋褌邪谢芯 斜邪褌械褉懈褬械: %2$s%%</string>
+    <string name="weeksleepchart_today_sleep_description">小锌邪胁邪褮械 写邪薪邪褋, 褑懈褭: %1$s</string>
+    <string name="activity_list_summary_intensity">袌邪褔懈薪邪
+\n泻褉械褌邪褮邪</string>
+    <string name="lack_of_sleep">袧械写芯褋褌邪褌邪泻 褋锌邪胁邪褮邪: %1$s</string>
+    <string name="devicestatus_upload_started">袨褌锌褉械屑邪褮械 褬械 锌芯褔械谢芯</string>
+    <string name="firmware_update_progress">袨褌锌褉械屑邪褮械 薪邪锌褉械写褍褬械
+\n%1d%% 薪邪 %.2fkbps (锌褉芯褋械褔薪芯 %.2fkbps)
+\n袛械芯 %1d 芯写 %1d</string>
+    <string name="installhandler_firmware_name">%1$s: %2$s %3$s</string>
+    <string name="fwappinstaller_connection_state">袙械蟹邪 褋邪 褍褉械褣邪褬械屑: %1$s</string>
+    <string name="movement_intensity">袌邪褔懈薪邪 泻褉械褌邪褮邪</string>
+    <string name="seconds_9">9 褋械泻褍薪写懈</string>
+    <string name="average">袩褉芯褋械褔薪芯: %1$s</string>
+    <string name="live_activity_total_steps">校泻褍锌薪懈 泻芯褉邪褑懈</string>
+    <string name="prefs_find_phone_summary">袣芯褉懈褋褌懈褌械 褌械谢械褎芯薪 蟹邪 懈蟹胁芯褣械褮械 芯屑懈褭械薪芯谐 蟹胁芯薪邪.</string>
+    <string name="prefs_disconnect_notification">袨斜邪胁械褕褌械褮械 芯 锌褉械泻懈写褍 胁械蟹械</string>
+    <string name="mi3_prefs_band_screen_unlock">袨褌泻褭褍褔邪胁邪褮械 蟹邪褋谢芯薪邪 褍褉械褣邪褬邪</string>
+    <string name="spanish">楔锌邪薪褋泻懈</string>
+    <string name="arabic">袗褉邪锌褋泻懈</string>
+    <string name="installer_activity_wait_while_determining_status">袦芯谢懈屑, 褋邪褔械泻邪褬褌械 写芯泻 芯写褉械写懈屑 褋褌邪褮械 褍谐褉邪写褮械鈥�</string>
+    <string name="discovery_pair_question">袠蟹邪斜械褉懈褌械 褍锌邪褉懈 褉邪写懈 褍锌邪褉懈胁邪褮邪 褋胁芯谐 褍褉械褣邪褬邪. 校泻芯谢懈泻芯 薪械 褍褋锌械, 锌芯泻褍褕邪褬褌械 锌芯薪芯胁芯 斜械蟹 褍锌邪褉懈胁邪褮邪.</string>
+    <string name="weekstepschart_steps_a_month">袦械褋械褔薪懈 泻芯褉邪褑懈</string>
+    <string name="weeksleepchart_sleep_a_month">袦械褋械褔薪芯 褋锌邪胁邪褮械</string>
+    <string name="pref_title_chart_sleep_rolling_24_hour">袨锌褋械谐 褋锌邪胁邪褮邪</string>
+    <string name="pref_chart_sleep_rolling_24_on">袩芯褋谢械写褮懈褏 24 褔邪褋邪</string>
+    <string name="activity_prefs_chart_min_steps_per_minute_for_run">袧邪褬屑邪褮懈 斜褉芯褬 泻芯褉邪泻邪 褍 屑懈薪褍褌褍 褉邪写懈 锌褉械锌芯蟹薪邪胁邪褮邪 褌褉褔邪褮邪</string>
+    <string name="pref_chart_sleep_rolling_24_off">袨写 锌芯写薪械胁邪 写芯 锌芯写薪械胁邪</string>
+    <string name="activity_prefs_chart_min_steps_per_minute">袧邪褬屑邪褮懈 斜褉芯褬 泻芯褉邪泻邪 褍 屑懈薪褍褌褍 褉邪写懈 锌褉械锌芯蟹薪邪胁邪褮邪 写械褬褋褌胁芯胁邪褮邪</string>
+    <string name="activity_prefs_chart_max_idle_phase_length">袛褍卸懈薪邪 芯写屑芯褉邪 褉邪写懈 芯写胁邪褬邪褮邪 写械褬褋褌邪胁邪 (褍 屑懈薪褍褌懈屑邪)</string>
+    <string name="activity_prefs_chart_min_session_length">袧邪褬屑邪褮邪 写褍卸懈薪邪 写械褬褋褌胁邪 (褍 屑懈薪褍褌懈屑邪)</string>
+    <string name="authentication_required">袧械芯锌褏芯写薪邪 褬械 锌褉懈褬邪胁邪</string>
+    <string name="activity_prefs_sleep_duration">袞械褭械薪芯 褌褉邪褬邪褮械 褋锌邪胁邪褮邪 褍 褔邪褋芯胁懈屑邪</string>
+    <string name="device_hw">袠蟹写邪褮械 褌胁褉写芯褌胁芯褉懈薪邪: %1$s</string>
+    <string name="device_fw">袠蟹写邪褮械 写褉邪屑邪: %1$s</string>
+    <string name="charts_legend_heartrate">袘褉芯褬 褋褉褔邪薪懈褏 芯褌泻褍褑邪褬邪</string>
+    <string name="live_activity_heart_rate">袘褉芯褬 褋褉褔邪薪懈褏 芯褌泻褍褑邪褬邪</string>
+    <string name="charts_legend_heartrate_average">袩褉芯褋械褔邪薪 斜褉芯褬 褋褉褔邪薪懈褏 芯褌泻褍褑邪褬邪</string>
+    <string name="action_db_management">校锌褉邪胁褭邪褮械 锌芯写邪褑懈屑邪</string>
+    <string name="find_lost_device_you_found_it">袧邪褣械薪 褬械!</string>
+    <string name="on">校泻褭褍褔械薪芯</string>
+    <string name="off">袠褋泻褭褍褔械薪芯</string>
+    <string name="normal">袨斜懈褔薪芯</string>
+    <string name="sensitive">袨褋械褌褭懈胁芯</string>
+    <string name="mi2_dnd_off">袠褋泻褭褍褔械薪芯</string>
+    <string name="mi2_dnd_always">袙邪蟹写邪</string>
+    <string name="maximum_duration">孝褉邪褬邪褮械</string>
+    <string name="_pebble_watch_mute">校屑褍泻薪懈</string>
+    <string name="discovery_dont_pair">袧械 褍锌邪褉褍褬</string>
+    <string name="activity_type_light_sleep">袥邪泻芯 褋锌邪胁邪褮械</string>
+    <string name="activity_type_not_worn">校褉械褣邪褬 薪懈褬械 薪芯褕械薪</string>
+    <string name="activity_type_running">孝褉褔邪褮械</string>
+    <string name="activity_type_outdoor_running">小锌芯褭褮械 褌褉褔邪褮械</string>
+    <string name="activity_type_walking">啸芯写邪褮械</string>
+    <string name="activity_type_freestyle">小谢芯斜芯写薪芯</string>
+    <string name="activity_type_outdoor_cycling">小锌芯褭邪褕褮邪 胁芯卸褮邪 褌芯褔泻邪</string>
+    <string name="activity_type_jump_roping">袩褉械褋泻邪泻邪褮械 泻芯薪芯锌褑邪</string>
+    <string name="activity_type_pingpong">小褌芯薪芯褌械薪懈褋</string>
+    <string name="activity_type_volleyball">袨写斜芯褬泻邪</string>
+    <string name="activity_type_table_tennis">小褌芯薪芯褌械薪懈褋</string>
+    <string name="activity_summary_edit_name_title">校褉械写懈 薪邪褌锌懈褋</string>
+    <string name="activity_type_unknown">袧械锌芯蟹薪邪褌芯 写械褬褋褌胁芯</string>
+    <string name="activity_summary_today">袛邪薪邪褋</string>
+    <string name="activity_summary_yesterday">袌褍褔械</string>
+    <string name="select_all">袠蟹邪斜械褉懈 褋胁械</string>
+    <string name="pref_chart_heartrate_color_red">笑褉胁械薪邪</string>
+    <string name="error_creating_directory_for_logfiles">袛械褋懈谢邪 褋械 谐褉械褕泻邪 锌褉懈谢懈泻芯屑 褋褌胁邪褉邪褮邪 褎邪褋褑懈泻谢械 写薪械胁薪懈泻邪: %1$s</string>
+    <string name="dbmanagementactivity_error_exporting_db">袛械褋懈谢邪 褋械 谐褉械褕泻邪 锌褉懈谢懈泻芯屑 懈蟹胁芯蟹邪 褏褉邪薪懈谢懈褕褌邪 锌芯写邪褌邪泻邪: %1$s</string>
+    <string name="pref_summary_pebble_health_store_raw">啸褉邪薪懈 锌芯写邪褌泻械 鈥炐盒靶盒残� 褬械褋褍鈥�, 锌芯胁械褯邪胁邪褬褍褯懈 褍锌芯褌褉械斜褍 褏褉邪薪懈谢懈褕褌邪 锌芯写邪褌邪泻邪 蟹邪 泻邪褋薪懈褬械 褌褍屑邪褔械褮械.</string>
+    <string name="title_activity_db_management">校锌褉邪胁褭邪褮械 锌芯写邪褑懈屑邪</string>
+    <string name="dbmanagementactivity_exported_to">袠蟹胁械蟹械薪芯 褍: %1$s</string>
+    <string name="dbmanagementactivity_error_cleaning_export_directory">袛械褋懈谢邪 褋械 谐褉械褕泻邪 锌褉懈谢懈泻芯屑 斜褉懈褋邪褮邪 写邪褌芯褌械泻邪 懈蟹 懈蟹胁芯蟹薪械 褎邪褋褑懈泻谢械 %1$s</string>
+    <string name="dbmanagementactivity_old_activity_db_deletion_failed">袧懈褋邪屑 褍褋锌械芯 写邪 懈蟹斜褉懈褕械屑 褋褌邪褉芯 褏褉邪薪懈谢懈褕褌械 锌芯写邪褌邪泻邪.</string>
+    <string name="dbmanagementactivity_import_successful">校胁械蟹械薪芯.</string>
+    <string name="dbmanagementactivity_error_importing_db">袛械褋懈谢邪 褋械 谐褉械褕泻邪 锌褉懈谢懈泻芯屑 褍胁芯蟹邪 褏褉邪薪懈谢懈褕褌邪 锌芯写邪褌邪泻邪: %1$s</string>
+    <string name="dbmanagementactivity_delete_old_activitydb_confirmation">袛邪 谢懈 蟹邪懈褋褌邪 卸械谢懈褌械 写邪 懈蟹斜褉懈褕械褌械 褋褌邪褉芯 褏褉邪薪懈谢懈褕褌械 锌芯写邪褌邪泻邪\? 袩芯写邪褑懈 芯 写械褬褋褌胁懈屑邪 泻芯褬懈 薪懈褋褍 褍胁械蟹械薪懈 褯械 斜懈褌懈 懈蟹谐褍斜褭械薪懈.</string>
+    <string name="title_activity_vibration">孝褉械褕褮邪</string>
+    <string name="smart">袩邪屑械褌薪芯</string>
+    <string name="discovery_enable_bluetooth">袨屑芯谐褍褯懈 袘谢褍褌褍褌 褉邪写懈 芯褌泻褉懈胁邪褮邪 褍褉械褣邪褬邪.</string>
+    <string name="discovery_successfully_bonded">袩褉懈泻邪褔械薪 薪邪 %1$s.</string>
+    <string name="discovery_pair_title">袛邪 谢懈 褍锌邪褉懈褌懈 褋邪%1$s\?</string>
+    <string name="activity_type_hiking">袩谢邪薪懈薪邪褉械褮械</string>
+    <string name="activity_type_not_measured">袧懈褬械 屑械褉械薪芯</string>
+    <string name="activity_type_indoor_cycling">校薪褍褌褉邪褕褮邪 胁芯卸褮邪 褌芯褔泻邪</string>
+    <string name="activity_type_badminton">袘邪写屑懈薪褌芯薪</string>
+    <string name="activity_type_strength_training">袙械卸斜邪褮械 褋薪邪谐械</string>
+    <string name="activity_type_stretching">袪邪褋褌械蟹邪褮械</string>
+    <string name="share">袛械谢懈</string>
+    <string name="devicetype_unknown">袧械锌芯蟹薪邪褌懈 褍褉械褣邪褬</string>
+    <string name="initializing">袩芯泻褉械褌邪褮械</string>
+    <string name="spo2_perc_85">85%</string>
+    <string name="miband_prefs_reserve_reminder_calendar">袩芯写褋械褌薪懈褑懈 蟹邪 褔褍胁邪褮械 锌褉懈斜谢懈卸邪胁邪褬褍褯懈褏 写芯谐邪褣邪褬邪</string>
+    <string name="prefs_reserve_reminder_calendar_summary">袘褉芯褬 写芯谐邪褣邪褬邪 泻邪谢械薪写邪褉邪 泻芯褬懈 褯械 斜懈褌懈 褍褋泻谢邪褣械薪</string>
+    <string name="miband_prefs_hr_sleep_detection">袣芯褉懈褋褌懈 锌褉懈褬械屑薪懈泻 褋褉褔邪褬薪懈褏 芯褌泻褍褑邪褬邪 蟹邪 锌芯斜芯褭褕邪褮械 锌褉械锌芯蟹薪邪胁邪褮邪 褋锌邪胁邪褮邪</string>
+    <string name="miband_prefs_device_time_offset_hours">袪邪蟹屑邪泻 胁褉械屑械薪邪 褍 褔邪褋芯胁懈屑邪 褍褉械褣邪褬邪 (褉邪写懈 锌褉械锌芯蟹薪邪胁邪褮邪 褋锌邪胁邪褮邪 褋屑械薪褋泻懈褏 褉邪写薪懈泻邪)</string>
+    <string name="prefs_always_on_display_follow_watchface">袠蟹谐谢械写 锌褉邪褌懈 谢懈褑械 褋邪褌邪</string>
+    <string name="pref_summary_force_white_color_scheme">袣芯褉懈褋薪芯 邪泻芯 谢懈 褋邪褌 懈屑邪 褌邪屑邪薪 泻邪懈褕</string>
+    <string name="mi5_prefs_workout_activity_types_summary">袠蟹邪斜械褉懈褌械 胁褉褋褌械 写械褬褋褌邪胁邪 蟹邪 锌褉懈泻邪蟹 薪邪 蟹邪褋谢芯薪褍 胁械卸斜邪褮邪</string>
+    <string name="pref_title_force_white_color_scheme">袩褉懈褋懈谢懈 芯斜褉邪蟹邪褑 斜芯褬邪 褑褉薪褍 薪邪 斜械谢芯屑</string>
+    <string name="hebrew">啸械斜褉械褬褋泻懈</string>
+    <string name="pref_title_charts_average">袩褉懈泻邪蟹褍褬 锌褉芯褋械泻械 褍 褌邪斜谢懈褑邪屑邪</string>
+    <string name="activity_prefs_goal_standing_time_minutes">袛薪械胁薪懈 褑懈褭: 胁褉械屑械 褋褌邪褬邪褮邪 褍 屑懈薪褍褌懈屑邪</string>
+    <string name="activity_db_management_merge_old_title">袨斜褉懈褕懈 蟹邪褋褌邪褉械谢芯 褏褉邪薪懈谢懈褕褌械 锌芯写邪褌邪泻邪</string>
+    <string name="dbmanagementactivity_import_data_title">袛邪 谢懈 褍胁械褋褌懈 锌芯写邪褌泻械\?</string>
+    <string name="dbmanagementactivity_error_exporting_shared">袛械褋懈谢邪 褋械 谐褉械褕泻邪 锌褉懈谢懈泻芯屑 懈蟹胁芯蟹邪 锌芯褋褌邪胁泻懈: %1$s</string>
+    <string name="dbmanagementactivity_delete_old_activity_db">袛邪 谢懈 写邪 懈蟹斜褉懈褕械屑 褋褌邪褉芯 褏褉邪薪懈谢懈褕褌械 锌芯写邪褌邪泻邪\?</string>
+    <string name="autoExport_lastTime_label">袩芯褋谢械写褮懈 褋邪屑芯褋褌邪谢薪懈 懈蟹胁芯蟹: %1$s</string>
+    <string name="activity_db_management_autoexport_enabled_yes">小邪屑芯褋褌邪谢薪懈 懈蟹胁芯蟹 褬械 芯屑芯谐褍褯械薪.</string>
+    <string name="activity_type_basketball">袣芯褕邪褉泻邪</string>
+    <string name="pref_screen_vibration_patterns_title">袨斜褉邪褋褑懈 褌褉械褕褮械</string>
+    <string name="world_clock_no_free_slots_description">校褉械褣邪褬 薪械屑邪 褋谢芯斜芯写薪懈褏 屑械褋褌邪 蟹邪 褋胁械褌褋泻械 褔邪褋芯胁薪懈泻械 (褍泻褍锌薪芯 屑械褋褌邪: %1$s)</string>
+    <string name="pref_charts_range_on">袨锌褋械谐 褌邪斜谢懈褑械 褬械 锌芯褋褌邪胁褭械薪 薪邪 屑械褋械褔薪芯</string>
+    <string name="pref_charts_range_off">袨锌褋械谐 褌邪斜谢懈褑械 褬械 锌芯褋褌邪胁褭械薪 薪邪 褋械写屑懈褔薪芯</string>
+    <string name="pref_chart_sleep_lines_limit">袣芯谢懈泻芯 谢懈薪懈褬邪 褋械写薪懈褑邪 褋锌邪胁邪褮邪 锌褉懈泻邪蟹懈胁邪褌懈 锌褉械 褮懈褏芯胁芯谐 锌褉械胁谢邪褔械褮邪</string>
+    <string name="activity_prefs_calories_burnt">袛薪械胁薪懈 褑懈褭: 锌芯褌褉芯褕械薪械 泻邪谢芯褉懈褬械</string>
+    <string name="activity_prefs_distance_meters">袛薪械胁薪懈 褑懈褭: 锌褉械褣械薪懈 锌褍褌 褍 屑械褌褉懈屑邪</string>
+    <string name="activity_prefs_activetime_minutes">袛薪械胁薪懈 褑懈褭: 胁褉械屑械 写械谢邪褮邪 褍 屑懈薪褍褌懈屑邪</string>
+    <string name="activity_prefs_goal_fat_burn_time_minutes">袛薪械胁薪懈 褑懈褭: 胁褉械屑械 褋泻懈写邪褮邪 褋邪谢邪 褍 屑懈薪褍褌懈屑邪</string>
+    <string name="pref_title_pebble_health_store_raw">小邪褔褍胁邪褬 褋懈褉芯胁械 锌芯写邪褌泻械 褍 褏褉邪薪懈谢懈褕褌褍 锌芯写邪褌邪泻邪</string>
+    <string name="dbmanagementactivvity_cannot_access_export_path">袧懈褋邪屑 褍褋锌械芯 写邪 懈蟹胁械蟹械屑 锌褍褌邪褮褍. 袦芯谢懈屑 芯斜褉邪褌懈褌械 褋械 褉邪蟹胁懈褬邪褔懈屑邪 锌褉芯谐褉邪屑邪.</string>
+    <string name="dbmanagementactivity_export_data_title">袛邪 谢懈 懈蟹胁械褋褌懈 锌芯写邪褌泻械\?</string>
+    <string name="dbmanagementactivity_overwrite_database_confirmation">袛邪 谢懈 卸械谢懈褌械 蟹邪懈褋褌邪 懈蟹斜褉懈褋邪褌懈 褌褉械薪褍褌薪械 锌芯写邪褌泻械? 袙邪褕懈 褌褉械薪褍褌薪懈 锌芯写邪褑懈 写械褬褋褌邪胁邪 (褍泻芯谢懈泻芯 懈褏 懈屑邪), 褍褉械褣邪褬邪 懈 锌芯褋褌邪胁泻懈 褯械 斜懈褌懈 锌褉械锌懈褋邪薪懈.</string>
+    <string name="dbmanagementactivity_export_confirmation">袛邪 谢懈 蟹邪懈褋褌邪 卸械谢懈褌械 懈蟹胁械褋褌懈 锌芯写邪褌泻械\? 袩褉械褌褏芯写薪芯 懈蟹胁械蟹械薪懈 锌芯写邪褑懈 芯 写械褬褋褌胁懈屑邪 (褍泻芯谢懈泻芯 懈褏 懈屑邪) 褯械 斜懈褌懈 锌褉械锌懈褋邪薪懈.</string>
+    <string name="dbmanagementactivity_error_importing_shared">袛械褋懈谢邪 褋械 谐褉械褕泻邪 锌褉懈谢懈泻芯屑 褍胁芯蟹邪 锌芯褋褌邪胁泻懈: %1$s</string>
+    <string name="dbmanagementactivity_delete_activity_data_title">袛邪 谢懈 蟹邪懈褋褌邪 写邪 懈蟹斜褉懈褕械屑 锌芯写邪褌泻械 芯 写械褬褋褌胁懈屑邪\?</string>
+    <string name="dbmanagementactivity_really_delete_entire_db">袛邪 谢懈 蟹邪懈褋褌邪 卸械谢懈褌械 写邪 懈蟹斜褉懈褕械薪械 褑械谢芯泻褍锌薪褍 锌芯褏褉邪薪褍 锌芯写邪褌邪泻邪\? 小胁邪 袙邪褕邪 写械褬褋褌胁邪 懈 锌芯写邪褑懈 芯 袙邪褕械屑 褍褉械褣邪褬褍 褯械 斜懈褌懈 懈蟹谐褍斜褭械薪懈.</string>
+    <string name="dbmanagementactivity_old_activity_db_successfully_deleted">小褌邪褉懈 锌芯写邪褑懈 芯 写械褬褋褌胁懈屑邪 褋褍 懈蟹斜褉懈褋邪薪懈.</string>
+    <string name="_pebble_watch_reply">袨写谐芯胁芯褉懈</string>
+    <string name="find_device_you_found_it">袧邪褕谢懈 褋褌械 谐邪!</string>
+    <string name="mi2_enable_text_notifications">袩懈褋邪薪邪 芯斜邪胁械褕褌械褮邪</string>
+    <string name="discovery_yes_pair">校锌邪褉懈</string>
+    <string name="_pebble_watch_open_on_phone">袨褌胁芯褉懈 薪邪 褍褉械褣邪褬褍 袗薪写褉芯懈写邪</string>
+    <string name="controlcenter_start_activity_tracks">袩褉邪褯械褮械 袙邪褕懈褏 写械褬褋褌邪胁邪</string>
+    <string name="activity_type_deep_sleep">袛褍斜芯泻芯 褋锌邪胁邪褮械</string>
+    <string name="activity_type_indoor_walking">校薪褍褌褉邪褕褮械 褏芯写邪褮械</string>
+    <string name="activity_type_climbing">袩械褮邪褮械</string>
+    <string name="activity_type_swimming">袩谢懈胁邪褮械</string>
+    <string name="activity_type_pool_swimming">袩谢懈胁邪褮械 褍 斜邪褋械薪褍</string>
+    <string name="activity_type_swimming_openwater">袩谢懈胁邪褮械 (胁芯写邪 薪邪 芯褌胁芯褉械薪芯屑)</string>
+    <string name="activity_type_soccer">袧芯谐芯屑械褌</string>
+    <string name="activity_type_dance">袩谢械褋</string>
+    <string name="activity_type_bowling">袣褍谐谢邪褮械</string>
+    <string name="activity_type_boxing">袩械褋薪懈褔械褮械</string>
+    <string name="activity_type_kickboxing">袣懈泻斜芯泻褋</string>
+    <string name="activity_type_street_dance">校谢懈褔薪懈 锌谢械褋</string>
+    <string name="activity_type_exercise">袙械卸斜邪褮械</string>
+    <string name="kind_font">小谢芯胁芯谢懈泻</string>
+    <string name="kind_app">袩褉芯谐褉邪屑</string>
+    <string name="devicetype_test">袩褉芯斜薪懈 褍褉械褣邪褬</string>
+    <string name="spo2_perc_90">90%</string>
+    <string name="prefs_always_on_display_summary">校胁械泻 蟹邪写褉卸邪胁邪褬 蟹邪褋谢芯薪 褍褉械褣邪褬邪 褍泻褭褍褔械薪懈屑</string>
+    <string name="activity_prefs_charts">袩芯褋褌邪胁泻械 褌邪斜谢懈褑械</string>
+    <string name="activity_prefs_chart_min_heart_rate">袧邪褬屑邪褮懈 斜褉芯褬 芯褌泻褍褑邪褬邪 褋褉褑邪</string>
+    <string name="pref_title_charts_range">袨锌褋械谐 褌邪斜谢懈褑邪</string>
+    <string name="pref_title_chart_heartrate_color">袘芯褬邪 褋褉褔邪薪懈褏 芯褌泻褍褑邪褬邪</string>
+    <string name="authenticating">袩褉懈褬邪胁邪</string>
+    <string name="dbmanagementactivity_overwrite">袩褉械锌懈褕懈</string>
+    <string name="start">袩芯褔薪懈</string>
+    <string name="set">袩芯褋褌邪胁懈</string>
+    <string name="pref_sleep_breathing_quality_monitoring">袣邪泻胁芯褯邪 薪邪写谐谢械写邪褮邪 写懈褋邪褮邪 锌褉懈 褋锌邪胁邪褮褍</string>
+    <string name="prefs_always_on_display_style">袠蟹谐谢械写</string>
+    <string name="prefs_control_center_summary">袠蟹邪斜械褉懈褌械 褋褌邪胁泻械 蟹邪 锌褉懈泻邪蟹 褍 锌邪写邪褬褍褯械屑 褍锌褉邪胁褭邪褔泻芯屑 褑械薪褌褉褍</string>
+    <string name="lefun_prefs_hydration_reminder_interval_title">袦械褣褍胁褉械屑械 芯写褋械褌薪懈泻邪 蟹邪 锌懈褬械褮械 褌械褔薪芯褋褌懈 (褍 屑懈薪褍褌懈屑邪)</string>
+    <string name="activity_prefs_discovery_pairing">袦芯谐褍褯薪芯褋褌懈 芯褌泻褉懈胁邪褮邪 懈 褍锌邪褉懈胁邪褮邪</string>
+    <string name="activity_prefs_chart_max_heart_rate">袧邪褬胁懈褕懈 斜褉芯褬 芯褌泻褍褑邪褬邪 褋褉褑邪</string>
+    <string name="pref_chart_heartrate_color_orange">袩芯屑芯褉邪薪褵邪褋褌邪</string>
+    <string name="dbmanagementactivity_database_successfully_deleted">袩芯写邪褑懈 褋褍 懈蟹斜褉懈褋邪薪懈.</string>
+    <string name="dbmanagementactivity_db_deletion_failed">袧懈褋邪屑 褍褋锌械芯 写邪 懈蟹斜褉懈褕械屑 褏褉邪薪懈谢懈褕褌械 锌芯写邪褌邪泻邪.</string>
+    <string name="activity_db_management_autoexport_enabled_no">小邪屑芯褋褌邪谢薪懈 懈蟹胁芯蟹 薪懈褬械 芯屑芯谐褍褯械薪.</string>
+    <string name="activity_db_management_autoexport_explanation">袦械褋褌芯 褋邪屑芯褋褌邪谢薪芯谐 褔褍胁邪褮邪 褏褉邪薪懈谢懈褕褌邪 锌芯写邪褌邪泻邪 褬械 锌芯褋褌邪胁褭械薪芯 薪邪:</string>
+    <string name="activity_DB_import_button">校胁械蟹懈 锌芯写邪褌泻械</string>
+    <string name="activity_DB_ExportButton">袠蟹胁械蟹懈 锌芯写邪褌泻械</string>
+    <string name="Delete">袠蟹斜褉懈褕懈</string>
+    <string name="Cancel">袨褌泻邪卸懈</string>
+    <string name="ok">校 褉械写褍</string>
+    <string name="pref_screen_vibration_patterns_summary">袩芯写械褋懈褌械 芯斜褉邪褋褑械 褌褉械褕褮械 蟹邪 褉邪蟹谢懈褔懈褌邪 芯斜邪胁械褕褌械褮邪</string>
+    <string name="spo2_perc_80">80%</string>
+    <string name="dismiss">袨锌芯蟹芯胁懈</string>
+    <string name="dbmanagementactivity_export_finished">袘褉懈褋邪褮械 褬械 蟹邪胁褉褕械薪芯</string>
+    <string name="timeformat_am_pm">袩褉袩/袩芯袩</string>
+    <string name="about_core_team_title">袛褉褍卸斜邪 褬械蟹谐褉邪 (锌芯褉械写邪泻 芯写 锌褉胁芯谐 写芯锌褉懈薪芯褋懈芯褑邪 泻芯写邪)</string>
+    <string name="about_links">袙械蟹械</string>
+    <string name="distanceMeters">袪邪蟹屑邪泻</string>
+    <string name="ascentMeters">校蟹斜褉写芯</string>
+    <string name="averageKMPaceSeconds">袘褉蟹懈薪邪</string>
+    <string name="activity_web_view">袛械褬褋褌胁芯 锌芯谐谢械写芯屑 袙械斜邪</string>
+    <string name="discovery_attempting_to_pair">袩芯泻褍褕邪胁邪屑 褍锌邪褉懈胁邪褮械 褋邪 %1$s</string>
+    <string name="discovery_bonding_failed_immediately">袩芯胁械蟹懈胁邪褮械 褋邪 %1$s 芯写屑邪褏 薪懈褬械 褍褋锌械谢芯.</string>
+    <string name="mi2_dnd_scheduled">袟邪泻邪蟹邪薪芯 (胁褉械屑械薪褋泻懈 褉邪蟹屑邪泻)</string>
+    <string name="discovery_trying_to_connect_to">袩芯泻褍褕邪胁邪屑 写邪 褋械 锌芯胁械卸械屑 褋邪: %1$s</string>
+    <string name="activity_summary_detail">袩芯褬械写懈薪芯褋褌懈 褋锌芯褉褌褋泻懈褏 写械褬褋褌邪胁邪</string>
+    <string name="activity_summaries">小锌芯褉褌褋泻邪 写械褬褋褌胁邪</string>
+    <string name="activity_summary_detail_editing_gpx_track">校褉械褣懈胁邪褮械 锌芯胁械蟹邪薪械 锌褍褌邪褮械 袚袩袠泻褋-邪</string>
+    <string name="activity_error_no_app_for_png">袪邪写懈 写械褭械褮邪 芯胁械 褋谢懈泻械 蟹邪褋谢芯薪邪, 褍谐褉邪写懈褌械 锌褉芯谐褉邪屑 泻芯褬懈 屑芯卸械 褉褍泻芯胁邪褌懈 写邪褌芯褌械泻邪屑邪 褋谢懈泻邪.</string>
+    <string name="reset_index">袩芯薪懈褕褌懈 写邪褌褍屑 蟹邪褏胁邪褌邪</string>
+    <string name="notification_channel_low_battery_name">袘邪褌械褉懈褬邪 褬械 锌褉邪蟹薪邪</string>
+    <string name="notification_channel_gps">袘械谢械卸械褮械 锌褍褌邪褮械 袚袩袠泻褋-邪</string>
+    <string name="notification_gps_title">袚袩小 袚械褵械褌斜褉胁薪邪</string>
+    <string name="menuitem_nothing">袧懈褕褌邪</string>
+    <string name="menuitem_status">小褌邪褮械</string>
+    <string name="menuitem_notifications">袨斜邪胁械褕褌械褮邪</string>
+    <string name="menuitem_activity">袩芯胁械褋褌 胁械卸斜邪褮邪</string>
+    <string name="menuitem_weather">袙褉械屑械</string>
+    <string name="menuitem_breathing">袛懈褋邪褮械</string>
+    <string name="menuitem_cycles">袘械谢械卸械褮械 屑械褋械褔薪懈褑械</string>
+    <string name="menuitem_timer">袘褉芯褬邪褮械 懈褋褌械泻邪 胁褉械屑械薪邪</string>
+    <string name="menuitem_compass">袣芯屑锌邪褋</string>
+    <string name="menuitem_settings">袩芯褋褌邪胁泻械</string>
+    <string name="menuitem_music">袦褍蟹懈泻邪</string>
+    <string name="menuitem_spo2">小锌袨2</string>
+    <string name="menuitem_pai">袩袗袠</string>
+    <string name="menuitem_hr">袘褉芯褬 褋褉褔邪薪懈褏 芯褌泻褍褑邪褬邪</string>
+    <string name="menuitem_takephoto">袛邪褭懈薪褋泻芯 褍锌褉邪胁褭邪褮械 泻邪屑械褉芯屑</string>
+    <string name="menuitem_mutephone">校屑褍泻薪懈 褌械谢械褎芯薪</string>
+    <string name="menuitem_events">袛芯谐邪褣邪褬懈</string>
+    <string name="menuitem_sleep">小锌邪胁邪褮械</string>
+    <string name="menuitem_flashlight">小胁械褌谢芯</string>
+    <string name="menuitem_countdown">袘褉芯褬邪褔 懈褋褌械泻邪 胁褉械屑械薪邪</string>
+    <string name="menuitem_personal_activity_intelligence">袦褍写褉芯褋褌 谢懈褔薪懈褏 写械褬褋褌邪胁邪</string>
+    <string name="menuitem_female_health">袞械薪褋泻芯 蟹写褉邪胁褭械</string>
+    <string name="menuitem_barometer">袦械褉邪褔 锌褉懈褌懈褋泻邪</string>
+    <string name="watch9_time_hours">小邪褌懈:</string>
+    <string name="watch9_time_seconds">小械泻褍薪写懈:</string>
+    <string name="watch9_calibration_hint">袩芯褋褌邪胁懈褌械 胁褉械屑械 褋胁芯谐 褍褉械褣邪褬邪 泻芯褬械 袙邪屑 褋械 锌褉械写褋褌邪胁褭邪 褋邪写邪.</string>
+    <string name="watch9_calibration_button">袘邪卸写邪褉懈</string>
+    <string name="preferences_rtl_settings">袩芯写褉褕泻邪 褋鈥� 写械褋薪邪 薪邪 谢械胁芯</string>
+    <string name="prefs_fm_preset_instructions">袛褍谐芯 锌褉懈褌懈褋薪懈褌械 写褍谐屑械 蟹邪 褔褍胁邪褮械 锌褉械褌锌芯褋褌邪胁泻懈</string>
+    <string name="title_activity_notification_filter">袩褉芯锌褍褋薪懈泻 芯斜邪胁械褕褌械褮邪</string>
+    <string name="filter_mode_none">袧械 泻芯褉懈褋褌懈 锌褉芯锌褍褋薪懈泻</string>
+    <string name="filter_mode">袧邪褔懈薪 锌褉芯锌褍褕褌邪褮邪</string>
+    <string name="qhybrid_overwrite_buttons">锌褉械锌懈褕懈 写褍谐屑邪写</string>
+    <string name="qhybrid_use_activity_hand_as_notification_counter">泻芯褉懈褋褌懈 写械褬褋褌胁械薪褍 褉褍泻褍 泻邪芯 斜褉芯褬邪褔 芯斜邪胁械褕褌械褮邪</string>
+    <string name="qhybrid_prompt_million_steps">袦芯谢懈屑, 锌芯褋褌邪胁懈褌械 斜褉芯褬邪褔 泻芯褉邪泻邪 薪邪 屑懈谢懈芯薪 褉邪写懈 锌芯泻褉械褌邪褮邪 褌芯谐邪.</string>
+    <string name="about_contributors">袛芯锌褉懈薪芯褋懈芯褑懈</string>
+    <string name="hrZoneFatBurn">孝芯锌褭械褮械 褋邪谢邪</string>
+    <string name="hrZoneWarmUp">袟邪谐褉械胁邪褮械</string>
+    <string name="Steps">袣芯褉邪褑懈</string>
+    <string name="prefs_events_forwarding_title">袛械褬褋褌胁邪 褍褉械褣邪褬邪</string>
+    <string name="sports_activity_quick_filter_last_week">袩褉械褌褏芯写薪械 褋械写屑懈褑械</string>
+    <string name="sports_activity_confirm_delete_title">袠蟹斜褉懈褕懈 %d 写械褬褋褌胁邪</string>
+    <string name="qhybrid_title_apps_management">校锌褉邪胁褭邪褮械 锌褉芯谐褉邪屑懈屑邪</string>
+    <string name="qhybrid_title_calibration">袘邪卸写邪褉械褮械</string>
+    <string name="qhybrid_title_file_management">校锌褉邪胁褭邪褮械 锌芯写邪褑懈屑邪</string>
+    <string name="companiondevice_pairing">校锌邪褉懈胁邪褮械 锌褉芯锌褉邪褌薪芯谐 褍褉械褣邪褬邪</string>
+    <string name="elevationGain">校褋锌芯薪</string>
+    <string name="elevationLoss">小锌褍褕褌邪褮械</string>
+    <string name="descentDistance">袪邪蟹屑邪泻 薪懈蟹斜褉写芯</string>
+    <string name="descentSeconds">小懈谢邪蟹薪芯</string>
+    <string name="qhybrid_title_apps">袩褉芯谐褉邪屑懈</string>
+    <string name="activity_db_management_autoexport_label">小邪屑芯褋褌邪谢薪懈 懈蟹胁芯蟹</string>
+    <string name="activity_type_gymnastics">袚懈屑薪邪褋褌懈泻邪</string>
+    <string name="menuitem_workout">袙械卸斜邪褮械</string>
+    <string name="prefs_button_single_press_action_selection_title">袪邪写褮邪 写芯谐邪褣邪褬邪 1</string>
+    <string name="prefs_button_double_press_action_selection_title">袪邪写褮邪 写芯谐邪褣邪褬邪 2</string>
+    <string name="gpx_receiver_files_received">袩褉懈屑褭械薪邪 褬械 写邪褌芯褌械泻邪(械) 袚袩袠泻褋-邪:</string>
+    <string name="Speed">袘褉蟹懈薪邪</string>
+    <string name="prefs_fake_ring_duration">袥邪卸薪芯 薪械锌褉械泻懈写薪芯 蟹胁芯褮械褮械</string>
+    <string name="initialized">锌芯泻褉械薪褍褌芯</string>
+    <string name="title_activity_mi_band_pairing">校锌邪褉懈褌械 褋胁芯褬 袦懈 袘械薪写</string>
+    <string name="activity_db_management_autoexport_scheduled_no">小邪屑芯褋褌邪谢薪懈 懈蟹胁芯蟹 薪懈褬械 蟹邪泻邪蟹邪薪.</string>
+    <string name="activity_db_management_clean_export_directory_label">袠蟹斜褉懈褕懈 写邪褌芯褌械泻械 懈蟹 褎邪褋褑懈泻谢械 懈蟹胁芯蟹邪/褍胁芯蟹邪</string>
+    <string name="activity_DB_clean_export_directory_warning_message">袛邪 谢懈 蟹邪懈褋褌邪 卸械谢懈褌械 写邪 懈蟹斜褉懈褕械褌械 写邪褌芯褌械泻械 懈蟹 褎邪褋褑懈泻谢械 懈蟹胁芯蟹邪/褍胁芯蟹邪\?</string>
+    <string name="pref_title_setup_bt_pairing">袨屑芯谐褍褯懈 褍锌邪褉懈胁邪褮械 袘谢褍褌褍褌芯屑</string>
+    <string name="unit_imperial">袠屑锌械褉懈褬邪谢薪芯</string>
+    <string name="StringUtils_sender">(%1$s)</string>
+    <string name="mi3_night_mode_sunset">袩褉懈 蟹邪谢邪褋泻褍 褋褍薪褑邪</string>
+    <string name="mi2_dnd_automatic">小邪屑芯褋褌邪谢薪芯 (锌褉械锌芯蟹薪邪胁邪褮械 褋锌邪胁邪褮邪)</string>
+    <string name="notification_channel_high_priority_name">袙邪卸薪芯</string>
+    <string name="menuitem_worldclock">小胁械褌褋泻懈 褔邪褋芯胁薪懈泻</string>
+    <string name="menuitem_pomodoro">袩褉邪褯械褮械 袩芯屑芯写芯褉邪</string>
+    <string name="menuitem_widgets">小锌褉邪胁懈褑械</string>
+    <string name="menuitem_temperature">孝芯锌谢芯褌邪</string>
+    <string name="menuitem_calendar">袣邪谢械薪写邪褉</string>
+    <string name="menuitem_todo">袨斜邪胁械蟹械</string>
+    <string name="menuitem_voice_memos">袚谢邪褋芯胁薪械 斜械谢械褕泻械</string>
+    <string name="menuitem_sun_moon">小褍薪褑械 懈 屑械褋械褑</string>
+    <string name="menuitem_theater_mode">袧邪褔懈薪 褉邪写邪 锌褉械写褋褌邪胁械</string>
+    <string name="menuitem_volume">袚谢邪褋薪芯褯邪</string>
+    <string name="menuitem_screen_always_lit">袟邪褋谢芯薪 褍胁械泻 芯褋胁械褌褭械薪</string>
+    <string name="menuitem_brightness">袨褋胁械褌褭械褮械</string>
+    <string name="menuitem_bluetooth">袘谢褍褌褍褌</string>
+    <string name="menuitem_wifi">袘械卸懈褔薪邪 胁械蟹邪</string>
+    <string name="menuitem_lockscreen">袟邪褋谢芯薪 蟹邪泻褭褍褔邪胁邪褮邪</string>
+    <string name="menuitem_eject_water">袠蟹斜邪褑懈 胁芯写褍</string>
+    <string name="menuitem_unsupported">[袧袝袩袨袛袪袞袗袧袨] %s</string>
+    <string name="title_activity_watch9_pairing">校锌邪褉懈胁邪褮械 效邪褋芯胁薪懈泻邪 9</string>
+    <string name="title_activity_watch9_calibration">袘邪卸写邪褉械褮械 效邪褋芯胁薪懈泻邪 9</string>
+    <string name="share_log">袛薪械胁薪懈泻 写械褭械褮邪</string>
+    <string name="share_log_not_enabled_message">袦芯褉邪褌械 锌褉胁芯 芯屑芯谐褍褯懈褌懈 胁芯褣械褮械 写薪械胁薪懈泻邪 褍 锌芯褋褌邪胁泻邪屑邪 - 锌懈褕懈 写邪褌芯褌械泻械 写薪械胁薪懈泻邪</string>
+    <string name="warning">校锌芯蟹芯褉械褮械!</string>
+    <string name="note">袩褉懈屑械写斜邪</string>
+    <string name="no_data">袧械屑邪 锌芯写邪褌邪泻邪</string>
+    <string name="preferences_led_color">袘芯褬邪 褋胁械褌谢芯褋薪械 写懈芯写械</string>
+    <string name="preferences_fm_frequency">校褔械褋褌邪谢芯褋褌 褉邪写懈褬邪</string>
+    <string name="pref_invalid_frequency_title">袧械锌褉邪胁懈谢薪邪 褍褔械褋褌邪谢芯褋褌</string>
+    <string name="toast_app_must_be_selected">袩褉芯谐褉邪屑 屑芯褉邪 斜懈褌懈 芯蟹薪邪褔械薪 写邪 斜懈 斜懈芯 锌芯写械褕械薪</string>
+    <string name="edittext_notification_filter_words_hint">校薪械褋懈 卸械褭械薪械 褉械褔懈, 薪芯胁邪 谢懈薪懈褬邪 蟹邪 褋胁邪泻褍</string>
+    <string name="filter_mode_whitelist">袩褉懈泻邪蟹褍褬 泻邪写邪 褋褍 褉械褔懈 褋邪写褉卸邪薪械</string>
+    <string name="filter_mode_blacklist">袟邪斜褉邪薪械 泻邪写邪 褋褍 褉械褔懈 褋邪写褉卸邪薪械</string>
+    <string name="filter_submode_at_least_one">袧邪褬屑邪褮械 褬械写薪邪 褉械褔</string>
+    <string name="save_configuration">小邪褔褍胁邪褬 锌芯写械褕邪胁邪褮邪</string>
+    <string name="appwidget_not_connected">袧懈褬械 锌芯胁械蟹邪薪芯, 谢邪褉屑邪 薪懈褬械 锌芯褋褌邪胁褭械薪邪.</string>
+    <string name="widget_settings_select_device_title">袠蟹邪斜械褉懈褌械 褍褉械褣邪褬</string>
+    <string name="appwidget_sleep_alarm_widget_label">袥邪褉屑邪 褋锌邪胁邪褮邪</string>
+    <string name="widget_listing_label">小褌邪褮械 懈 谢邪褉屑械</string>
+    <string name="quick_alarm">袘褉蟹邪 谢邪褉屑邪</string>
+    <string name="watch_not_connected">效邪褋芯胁薪懈泻 薪懈褬械 锌芯胁械蟹邪薪</string>
+    <string name="qhybrid_buttons_overwrite_error">袛械褋懈谢邪 褋械 谐褉械褕泻邪 锌褉懈谢懈泻芯屑 锌褉械锌懈褋懈胁邪褮邪 写褍谐屑邪写懈</string>
+    <string name="qhybrid_buttons_overwrite_success">袛褍谐屑邪写 褬械 锌褉械锌懈褋邪薪邪</string>
+    <string name="qhybrid_offset_timezone">锌芯屑邪泻薪懈 胁褉械屑械薪褋泻褍 芯斜谢邪褋褌 蟹邪</string>
+    <string name="qhybrid_changes_delay_prompt">懈蟹屑械薪懈 褯械 斜懈褌懈 锌芯褌褉械斜薪芯 薪械泻芯谢懈泻芯 褋械泻褍薪写懈鈥�</string>
+    <string name="qhybrid_offset_time_by">锌芯屑邪泻薪懈 胁褉械屑械 蟹邪</string>
+    <string name="hr_widget_calories">袣邪谢芯褉懈褬械</string>
+    <string name="hr_widget_nothing">袧懈褕褌邪</string>
+    <string name="hr_appname_workout">袙械卸斜邪褮械</string>
+    <string name="hr_appname_stopwatch">小褌芯锌械褉懈褑邪</string>
+    <string name="pref_title_upper_button_long_press_action">袛械褬褋褌胁芯 写褍谐芯谐 锌褉懈褌懈褋泻邪 谐芯褉褮械谐 写褍谐屑械褌邪</string>
+    <string name="pref_title_lower_button_short_press_action">袛械褬褋褌胁芯 写褍谐芯谐 锌褉懈褌懈褋泻邪 写芯褮械谐 写褍谐屑械褌邪</string>
+    <string name="pref_title_upper_button_function_short">袣褉邪褌泻芯 谐芯褉褮械 写褍谐屑械</string>
+    <string name="pref_title_middle_button_function_short">袣褉邪褌泻芯 褋褉械写褮械 写褍谐屑械</string>
+    <string name="pref_qhybrid_title_widget_draw_circles">袠褋褑褉褌邪胁邪褬 泻褉褍谐芯胁械 褋锌褉邪胁懈褑械</string>
+    <string name="pref_qhybrid_save_raw_activity_files">小邪褔褍胁邪褬 褋懈褉芯胁械 写邪褌芯褌械泻械 写械褬褋褌邪胁邪</string>
+    <string name="gpx_receiver_activity_title">袩褉懈褬械屑薪懈泻 袚袩袠泻褋-邪 袚械褵械褌斜褉胁薪邪</string>
+    <string name="permission_granting_mandatory">小胁邪 芯胁邪 芯胁谢邪褕褯械褮邪 褋褍 薪械芯锌褏芯写薪邪 懈 屑芯卸械 写芯褯懈 写芯 薪械褋褌邪斜懈谢薪芯褋褌懈 邪泻芯 薪懈褋褍 芯写芯斜褉械薪邪</string>
+    <string name="error_background_service">袧懈褋邪屑 褍褋锌械芯 写邪 锌芯泻褉械薪械屑 锌芯蟹邪写懈薪褋泻褍 褍褋谢褍谐褍</string>
+    <string name="error_background_service_reason_truncated">袩芯泻褉械褌邪褮械 锌芯蟹邪写懈薪褋泻械 褍褋谢褍谐械 薪懈褬械 褍褋锌械谢芯 懈蟹 褉邪蟹谢芯谐邪鈥�</string>
+    <string name="device_is_currently_bonded">袙袝袐 袌袝 袩袨袙袝袟袗袧</string>
+    <string name="device_unsupported">袧袝袩袨袛袪袞袗袧袨</string>
+    <string name="error_background_service_reason">袧懈褋邪屑 褍褋锌械芯 写邪 锌芯泻褉械薪械屑 锌芯蟹邪写懈薪褋泻褍 褍褋谢褍谐褍 蟹斜芯谐 懈蟹褍蟹械褌泻邪. 袚褉械褕泻邪:</string>
+    <string name="pref_check_permission_status">袩褉芯胁械褉懈 褋褌邪褮械 芯胁谢邪褕褯械褮邪</string>
+    <string name="ignore_bonded_devices">袟邪薪械屑邪褉懈 锌芯胁械蟹邪薪械 褍褉械褣邪褬械</string>
+    <string name="ignore_bonded_devices_description">袨屑芯谐褍褯邪胁邪褮械 芯胁芯谐邪 褯械 蟹邪薪械屑邪褉懈褌懈 锌芯胁械蟹邪薪械 褍褉械褣邪褬械 泻芯褬懈 褋褍 胁械褯 褍锌邪褉械薪懈 锌褉懈谢懈泻芯屑 锌褉械褌褉邪谐械</string>
+    <string name="discover_unsupported_devices">袨褌泻褉懈褬 薪械锌芯写褉卸邪薪械 褍褉械褣邪褬械</string>
+    <string name="sonyswr12_settings_low_vibration_summary">袨屑芯谐褍褯懈 褋谢邪斜褍 褌褉械褕褮褍 薪邪 褉褍褔薪芯屑 褍褉械褣邪褬褍</string>
+    <string name="sonyswr12_settings_stamina">效褍胁邪褮械 褋薪邪谐械 褬械 褍泻褭褍褔械薪芯</string>
+    <string name="caloriesBurnt">袣邪谢芯褉懈褬械</string>
+    <string name="maxSpeed">袧邪褬胁懈褕械</string>
+    <string name="laps">袣褉褍谐芯胁懈</string>
+    <string name="ascentSeconds">校蟹谢邪蟹薪芯</string>
+    <string name="flatSeconds">袪邪胁薪芯</string>
+    <string name="averageSpeed">袩褉芯褋械褔薪邪 斜褉蟹懈薪邪</string>
+    <string name="laps_unit">泻褉褍谐芯胁懈</string>
+    <string name="activity_detail_duration_label">孝褉邪褬邪褮械</string>
+    <string name="activity_filter_date_from">袨写</string>
+    <string name="activity_filter_date_to">袛芯</string>
+    <string name="prefs_autoremove_message">小邪屑芯褋褌邪谢薪芯 褍泻谢芯薪懈 芯斜邪胁械褕褌械褮邪 小袦小-邪</string>
+    <string name="message_cannot_pair_no_mac">袧懈褬械 锌褉芯褋谢械褣械薪邪 袦袗笑-邪 邪写褉械褋邪, 薪懈褋邪屑 褍褋锌械芯 写邪 褍锌邪褉懈屑.</string>
+    <string name="activity_db_management_autoexport_scheduled_yes">小邪屑芯褋褌邪谢薪懈 懈蟹胁芯蟹 褬械 (懈蟹胁芯褉薪芯) 蟹邪泻邪蟹邪薪 蟹邪 %1$s</string>
+    <string name="activity_data_management_directory_content_title">小邪写褉卸邪褬 褎邪褋褑懈泻谢械 懈蟹胁芯蟹邪/褍胁芯蟹邪</string>
+    <string name="activity_db_management_autoexport_location">袧懈褋邪屑 褍褋锌械芯 写邪 褉邪蟹褍屑械屑 锌芯谢芯卸邪褬. 袧邪褬胁械褉芯胁邪褌薪懈褬械 褬械 锌芯 褋褉械写懈 薪芯胁懈褬懈 褋泻谢芯锌 芯胁谢邪褕褯械褮邪 袗薪写褉芯懈写邪. 袧邪褬胁械褉芯胁邪褌薪懈褬械, 褋邪屑芯褋褌邪谢薪懈 懈蟹胁芯蟹 褋邪写邪 薪械 褉邪写懈.</string>
+    <string name="activity_DB_delete_legacy_button">袠蟹斜褉懈褕懈 褋褌邪褉芯 褏褉邪薪懈谢懈褕褌械 锌芯写邪褌邪泻邪</string>
+    <string name="activity_DB_clean_export_directory_warning_title">袛邪 谢懈 懈蟹斜褉懈褋邪褌懈 写邪褌芯褌械泻械 懈蟹 褎邪褋褑懈泻谢械 懈蟹胁芯蟹邪/褍胁芯蟹邪\?</string>
+    <string name="activity_DB_ShowContentButton">袩褉懈泻邪卸懈 褋邪写褉卸邪褬 褎邪褋褑懈泻谢械 懈蟹胁芯蟹邪/褍胁芯蟹邪</string>
+    <string name="unit_metric">袦械褌褉懈褔泻懈</string>
+    <string name="activity_db_management_clean_export_directory_text">袠蟹胁械蟹械薪械 写邪褌芯褌械泻械 褍 褎邪褋褑懈泻谢懈 懈蟹胁芯蟹邪/褍胁芯蟹邪 褋褍 写芯褋褌褍锌薪械 泻褉芯蟹 斜懈谢芯 泻芯褬懈 锌褉芯谐褉邪屑 薪邪 袙邪褕械屑 褍褉械褣邪褬褍. 袦芯卸写邪 卸械谢懈褌械 写邪 褍泻谢芯薪懈褌械 芯胁械 写邪褌芯褌械泻械 薪邪泻芯薪 褍褋泻谢邪褣懈胁邪褮邪 懈谢懈 锌芯褏褉邪褮懈胁邪褮邪 褍屑薪芯褕泻邪. 校胁械褉懈褌械 褋械 写邪 懈屑邪褌械 锌芯褏褉邪褮械薪懈 褍屑薪芯卸邪泻 锌褉械 褮懈褏芯胁芯谐 斜褉懈褋邪褮邪. 袛邪褌芯褌械泻械 袚袩袠泻褋-邪, 锌芯写褎邪褋褑懈泻谢械 懈 褋邪屑芯褋褌邪谢薪芯 懈蟹胁械蟹械薪械 写邪褌芯褌械泻械 锌芯褏褉邪薪械 (褍泻芯谢懈泻芯 锌芯褋褌芯褬械) 薪械褯械 斜懈褌懈 斜褉懈褋邪薪械. 袩褍褌邪褮邪 泻邪 褎邪褋褑懈泻谢懈 懈蟹胁芯蟹邪/褍胁芯蟹邪 褬械:</string>
+    <string name="pref_summary_setup_bt_pairing">袠褋泻褭褍褔懈褌械 芯胁芯 褍泻芯谢懈泻芯 懈屑邪褌械 锌芯褌械褕泻芯褯邪 褋邪 锌芯胁械蟹懈胁邪褮械屑</string>
+    <string name="activity_summary_detail_select_gpx_track">袠蟹邪斜械褉懈褌械 锌褍褌邪褮褍 袚袩袠泻褋-邪</string>
+    <string name="activity_summary_detail_clear_gpx_track">袨褔懈褋褌懈 锌褍褌邪褮褍 袚袩袠泻褋-邪</string>
+    <string name="activity_error_no_app_for_gpx">袪邪写懈 锌褉械谐谢械写邪 锌褍褌邪褮械 写械褬褋褌胁邪, 褍谐褉邪写懈褌械 锌褉芯谐褉邪屑 泻芯褬懈 屑芯卸械 褉褍泻芯胁邪褌懈 写邪褌芯褌械泻邪屑邪 袚袩袠泻褋-邪.</string>
+    <string name="kind_invalid">袧械懈褋锌褉邪胁邪薪 锌芯写邪褌邪泻</string>
+    <string name="menuitem_unknown_app">袧械锌芯蟹薪邪褌芯 (%s)</string>
+    <string name="pref_invalid_frequency_message">袦芯谢懈屑, 褍薪械褋懈褌械 褍褔械褋褌邪谢芯褋褌 懈蟹屑械褣褍 87.5 懈 108.0</string>
+    <string name="language_and_region_prefs">袌械蟹懈泻 懈 芯斜谢邪褋薪械 锌芯褋褌邪胁泻械</string>
+    <string name="menuitem_nfc">袧肖笑</string>
+    <string name="menuitem_stress">小褌褉械褋</string>
+    <string name="menuitem_email">袝-锌芯褕褌邪</string>
+    <string name="menuitem_workout_status">小褌邪褮械 胁械卸斜邪褮邪</string>
+    <string name="menuitem_workout_history">袩芯胁械褋褌 胁械卸斜邪褮邪</string>
+    <string name="menuitem_membership_cards">袣邪褉褌懈褑械 褍写褉褍卸械褮邪</string>
+    <string name="menuitem_phone">孝械谢械褎芯薪</string>
+    <string name="toast_app_must_not_be_selected">袩褉芯谐褉邪屑 薪械 褋屑械 斜懈褌懈 芯蟹薪邪褔械薪 写邪 斜懈 斜懈芯 锌芯写械褕械薪</string>
+    <string name="share_log_warning">袦芯谢懈屑, 蟹薪邪褬褌械 写邪 写邪褌芯褌械泻械 袚械褵械褌斜褉懈褵邪 屑芯谐褍 褋邪写褉卸邪褌懈 写芯褋褌邪 谢懈褔薪懈褏 锌芯写邪褌邪泻邪, 褍泻褭褍褔褍褬褍褯懈, 邪谢懈 薪械 懈 褋邪屑芯, 锌芯写邪褌泻械 芯 蟹写褉邪胁褭褍, 谢懈褔薪械 锌芯写邪褌泻械 (泻邪芯 褕褌芯 褋褍 邪写褉械褋械 袦袗笑-邪), 屑褍蟹懈褔泻懈 褍泻褍褋, 懈褌写. 袪邪蟹屑懈褋谢懈褌械 芯 褍褉械褣懈胁邪褮褍 写邪褌芯褌械泻械 懈 褍泻谢邪褮邪褮褍 芯胁懈褏 锌芯写邪褌邪泻邪 锌褉械 褋谢邪褮邪 写邪褌芯褌械泻械 褍 褬邪胁薪懈 懈蟹胁械褕褌邪褬.</string>
+    <string name="toast_notification_filter_words_empty_hint">袦芯谢懈屑, 褍薪械褋懈 薪邪褬屑邪褮械 褬械写薪褍 褉械褔</string>
+    <string name="mode_configuration">袩芯褋褌邪胁泻械 薪邪褔懈薪邪</string>
+    <string name="appwidget_setting_alarm">袩芯褋褌邪胁泻邪 谢邪褉屑械 蟹邪 %1$02d:%2$02d</string>
+    <string name="qhybrid_vibration_strength">褋薪邪谐邪 褌褉械褕褮械:</string>
+    <string name="qhybrid_time_shift">胁褉械屑械薪褋泻懈 锌芯屑邪泻</string>
+    <string name="qhybrid_goal_in_steps">笑懈褭 褍 泻芯褉邪褑懈屑邪</string>
+    <string name="qhybrid_second_timezone_offset_relative_to_utc">锌芯屑邪泻 写褉褍谐械 胁褉械屑械薪褋泻械 芯斜谢邪褋褌懈 褍 芯写薪芯褋褍 薪邪 芯斜谢邪褋薪芯 胁褉械屑械</string>
+    <string name="pref_title_middle_button_function_long">袛褍谐芯 褋褉械写褮械 写褍谐屑械</string>
+    <string name="hr_widget_battery">袘邪褌械褉懈褬械</string>
+    <string name="hr_widget_weather">袙褉械屑械</string>
+    <string name="pref_title_upper_button_function_long">袛褍谐芯 谐芯褉褮械 写褍谐屑械</string>
+    <string name="hr_widget_last_notification">袩芯褋谢械写褮械 芯斜邪胁械褕褌械褮械</string>
+    <string name="pref_title_lower_button_function_double">袛胁芯褋褌褉褍泻芯 写芯褮械 写褍谐屑械</string>
+    <string name="about_title">袨 锌褉芯谐褉邪屑褍</string>
+    <string name="about_version">袠蟹写邪褮械 %s</string>
+    <string name="gpx_receiver_overwrite_some_files">袧械泻邪 写邪褌芯褌械泻邪(械) 胁械褯 锌芯褋褌芯褬懈. 袛邪 谢懈 褬械 锌褉械锌懈褋邪褌懈\?</string>
+    <string name="about_additional_contributions">袙械谢懈泻邪 褏胁邪谢邪 薪械锌芯屑械薪褍褌懈屑 写芯锌褉懈薪芯褋懈芯褑懈屑邪 泻芯写邪, 锌褉械胁芯写邪, 锌芯写褉褕泻械, 蟹邪屑懈褋谢懈, 锌芯写褋褌褉械泻邪, 锌褉懈褬邪胁褭懈胁邪褮邪 谐褉械褕邪泻邪, 薪芯胁褑邪鈥� 鉁�</string>
+    <string name="require_location_provider">袩芯谢芯卸邪褬 屑芯褉邪 斜懈褌懈 芯写芯斜褉械薪</string>
+    <string name="sports_activity_quick_filter_this_month">袨胁芯谐 屑械褋械褑邪</string>
+    <string name="discover_unsupported_devices_description">袨屑芯谐褍褯邪胁邪褮械 芯胁芯谐邪 褯械 锌褉懈泻邪蟹邪褌懈 褋胁械 芯褌泻褉懈胁械薪械 褍褉械褣邪褬械 斜谢褍褌褍褌邪 锌褉懈谢懈泻芯屑 锌褉械褌褉邪谐械. 袣褉邪褌邪泻 褌邪锌 褯械 褍屑薪芯卸懈褌懈 懈屑械 褍褉械褣邪褬邪 懈 邪写褉械褋褍 屑邪褑-邪 褍 褋锌褉械屑懈褕褌械 懈褋械褔邪泻邪. 袛褍谐懈 锌褉懈褌懈褋邪泻 褯械 锌芯泻褉械薪褍褌懈 锌褉芯蟹芯褉褔械 鈥炐斝拘葱把� 锌褉芯斜薪懈 褍褉械褣邪褬鈥�.</string>
+    <string name="ascentDistance">袪邪蟹屑邪泻 褍蟹斜褉写芯</string>
+    <string name="descentMeters">袧懈蟹斜褉写芯</string>
+    <string name="averageAltitude">袩褉芯褋械泻</string>
+    <string name="Distance">袪邪蟹屑邪泻</string>
+    <plurals name="widget_alarm_target_hours">
+        <item quantity="one">%d 褋邪褌</item>
+        <item quantity="few">%d 褋邪褌邪</item>
+        <item quantity="other">%d 褋邪褌懈</item>
+    </plurals>
+    <string name="n_a">袧械屑邪</string>
+    <string name="device_with_rssi">%1$s (%2$s)</string>
+    <string name="prefs_fm_presets_presets">袩褉械褌锌芯褋褌邪胁泻械</string>
+    <string name="pref_title_upper_button_function_double">袛胁芯褋褌褉褍泻芯 谐芯褉褮械 写褍谐屑械</string>
+    <string name="about_hash">袨褌锌褉械屑懈 %s</string>
+    <string name="steps">袣芯褉邪褑懈</string>
+    <string name="no">袧械</string>
+    <string name="activity_DB_test_export_button">袩芯泻褉械薪懈 褋邪屑芯褋褌邪谢薪懈 懈蟹胁芯蟹 褋邪写邪</string>
+    <string name="activity_DB_test_export_message">袠蟹胁芯蟹懈屑 褏褉邪薪懈谢懈褕褌械 锌芯写邪褌邪泻邪鈥�</string>
+    <string name="activity_db_management_empty_db_warning">校锌芯蟹芯褉械褮械! 袩褉懈褌懈褋泻芯屑 薪邪 芯胁芯 写褍谐屑械 褯械褌械 懈蟹斜褉懈褋邪褌懈 褋胁芯褬械 褏褉邪薪懈谢懈褕褌械 锌芯写邪褌邪泻邪 懈 锌芯褔械褌懈 懈蟹薪芯胁邪.</string>
+    <string name="menuitem_more">袌芯褕</string>
+    <string name="menuitem_findphone">袩褉芯薪邪褣懈 褌械谢械褎芯薪</string>
+    <string name="menuitem_alexa">袗谢械泻褋邪</string>
+    <string name="menuitem_dnd">袛袧袛</string>
+    <string name="menuitem_stopwatch">小褌芯锌械褉懈褑邪</string>
+    <string name="watch9_time_minutes">袦懈薪褍褌邪:</string>
+    <string name="widget_set_alarm_after">袩芯褋褌邪胁懈 谢邪褉屑褍 薪邪泻芯薪:</string>
+    <string name="hr_widget_heart_rate">小褉褔邪薪懈 芯褌泻褍褑邪褬懈</string>
+    <string name="hr_widget_steps">袣芯褉邪褑懈</string>
+    <string name="appwidget_text">袟蟹蟹</string>
+    <string name="hr_widget_date">袛邪褌褍屑</string>
+    <string name="pref_title_lower_button_function_short">袣褉邪褌泻芯 写芯褮械 写褍谐屑械</string>
+    <string name="pref_title_middle_button_function_double">袛胁芯褋褌褉褍泻芯 褋褉械写褮械 写褍谐屑械</string>
+    <string name="prefs_button_triple_press_action_selection_title">袪邪写褮邪 写芯谐邪褣邪褬邪 3</string>
+    <string name="prefs_button_variable_actions">袩芯褋褌邪胁泻械 锌褉懈褌懈褋邪泻邪 写褍谐屑邪写懈</string>
+    <string name="prefs_button_long_press_action_selection_title">袪邪写褮邪 写褍谐芯谐 锌褉懈褌懈褋泻邪 写褍谐屑械褌邪</string>
+    <string name="maxAltitude">袧邪褬胁懈褕械</string>
+    <string name="minAltitude">袧邪褬薪懈卸械</string>
+    <string name="Elevation">校褋锌芯薪</string>
+    <string name="yes">袛邪</string>
+    <string name="activity_summaries_statistics">小褌邪褌懈褋褌懈泻邪</string>
+    <string name="sports_activity_quick_filter_this_week">袨胁械 褋械写屑懈褑械</string>
+    <string name="activity_filter_from_placeholder">锌褉械褣械薪懈 褉邪蟹屑邪泻</string>
+    <string name="sports_activity_confirm_delete_description">袛邪 谢懈 褋褌械 褋懈谐褍褉薪懈 写邪 卸械谢懈褌械 懈蟹斜褉懈褋邪褌懈 %d 写械褬褋褌胁邪\?</string>
+    <string name="activity_filter_to_placeholder">写邪薪邪褋</string>
+    <string name="prefs_operating_sounds">袪邪写薪懈 蟹胁褍褑懈</string>
+    <string name="fossil_hr_commute_processing">袨斜褉邪褣褍褬械屑 蟹邪褏褌械胁鈥�</string>
+    <string name="fossil_hr_synced_activity_data">袩芯写邪褑懈 褍褋泻谢邪褣械薪懈褏 写械褬褋褌邪胁邪</string>
+    <string name="notification_channel_transfer_name">袩褉械薪芯褋 锌芯写邪褌邪泻邪</string>
+    <string name="notification_gps_text">楔邪褭械屑 锌芯谢芯卸邪褬 袚袩袠泻褋-邪 薪邪 褍褉械褣邪褬 %1$d</string>
+    <string name="menuitem_eventreminder">袩芯写褋械褌薪懈泻 写芯谐邪褣邪褬邪</string>
+    <string name="menuitem_unknown">袧械锌芯蟹薪邪褌芯</string>
+    <string name="appversion_by_creator">%1$s 锌芯 %2$s</string>
+    <string name="activity_DB_empty_button">袠褋锌褉邪蟹薪懈 褏褉邪薪懈谢懈褕褌械 锌芯写邪褌邪泻邪</string>
+    <string name="activity_db_management_empty_DB">袠褋锌褉邪蟹薪懈 褏褉邪薪懈谢懈褕褌械 锌芯写邪褌邪泻邪</string>
+    <string name="activity_db_management_exportimport_label">袠蟹胁芯蟹 懈 褍胁芯蟹</string>
+    <string name="devicetype_binary_sensor">袘褉芯褬褔邪薪懈 锌褉懈褬械屑薪懈泻</string>
+    <string name="choose_auto_export_location">袠蟹邪斜械褉懈褌械 锌芯谢芯卸邪褬 懈蟹胁芯蟹邪</string>
+    <string name="notification_channel_name">袨锌褕褌械</string>
+    <string name="menuitem_one_tap_measuring">袦械褉械褮械 褬械写薪懈屑 褌邪锌芯屑</string>
+    <string name="menuitem_offline_voice">袚谢邪褋 胁邪薪 屑褉械卸械</string>
+    <string name="toast_notification_filter_saved_successfully">小邪褔褍胁邪薪 褬械 锌褉芯锌褍褋薪懈泻 芯斜邪胁械褕褌械褮邪</string>
+    <string name="pref_title_lower_button_function_long">袛褍谐芯 写芯褮械 写褍谐屑械</string>
+    <string name="error_no_location_access">袩褉懈褋褌褍锌 锌芯谢芯卸邪褬褍 屑芯褉邪 斜懈褌懈 芯写芯斜褉械薪 懈 芯屑芯谐褍褯械薪 褉邪写懈 锌褉械褌褉邪谐械 懈 锌褉邪胁懈谢薪芯谐 褉邪写邪</string>
+    <string name="error_location_enabled_mandatory">袩芯谢芯卸邪褬 屑芯褉邪 斜懈褌懈 褍泻褭褍褔械薪 褉邪写懈 锌褉械褌褉邪谐械 褍褉械褣邪褬邪</string>
+    <string name="sonyswr12_settings_low_vibration">袛褍谐邪 褌褉械褕褮邪 褬械 芯屑芯谐褍褯械薪邪</string>
+    <string name="sonyswr12_settings_stamina_summary">效褍胁邪褮械 褋薪邪谐械 懈褋泻褭褍褔褍褬械 锌芯胁褉械屑械薪芯 褋邪屑芯褋褌邪谢薪芯 屑械褉械褮械 褋褉褔邪薪懈褏 褍褉械褣邪褬邪 懈 锌褉芯写褍卸褍褬械 胁褉械屑械 褉邪写邪</string>
+    <string name="activity_detail_start_label">袩芯褔械褌邪泻</string>
+    <string name="activity_detail_end_label">袣褉邪褬</string>
+    <string name="sports_activity_quick_filter_last_month">袩褉械褌褏芯写薪芯谐 屑械褋械褑邪</string>
+    <string name="sports_activity_quick_filter_select">袙褉械屑械薪褋泻懈 褉邪蟹屑邪泻</string>
+    <string name="prefs_autolight">小邪屑芯褋褌邪谢薪芯 褋胁械褌谢芯</string>
+    <string name="prefs_key_vibration">袛褍谐屑械 褌褉械褕褮邪</string>
+    <string name="qhybrid_title_background_image">袩芯蟹邪写懈薪褋泻邪 褋谢懈泻邪</string>
+    <string name="minSpeed">袧邪褬薪懈卸械</string>
+    <string name="watchface_setting_display_refresh_partial">袛械谢懈屑懈褔薪芯 芯褋胁械卸械褮械 (褍 屑懈薪褍褌懈屑邪)</string>
+    <string name="prefs_voice_detect">袩褉械锌芯蟹薪邪胁邪褮械 谐谢邪褋邪</string>
+    <string name="pref_summary_canned_messages_set">袩芯褕邪褭懈 锌芯写械褕械薪褍 锌芯褉褍泻褍 懈褋锌芯写 薪邪 褍褉械褣邪褬</string>
+    <string name="pref_summary_canned_messages_dismisscall">袨褌泻邪卸懈 锌芯蟹懈胁械 褋邪褌芯屑 锌芯褉褍泻芯屑 小袦小-邪</string>
+    <string name="watchface_setting_title_custom_events">袩褉芯懈蟹胁芯褭薪懈 写芯谐邪褣邪褬懈</string>
+    <string name="watchface_setting_title_wrist_flick">孝褉蟹邪褬 蟹谐谢芯斜邪</string>
+    <string name="nothing_prefs_inear_title">袩褉械锌芯蟹薪邪胁邪褮械 褍胁邪</string>
+    <string name="pref_header_spo2">袣懈褋械芯薪懈泻 褍 褌械谢褍</string>
+    <string name="pref_title_physical_buttons">小褌胁邪褉薪邪 写褍谐屑邪写</string>
+    <string name="qhybrid_summary_file_management">袨褌锌褉械屑懈 懈 锌褉械褍蟹屑懈 写邪褌芯褌械泻械</string>
+    <string name="fossil_hr_edit_action_delete">懈蟹斜褉懈褕懈</string>
+    <string name="pref_title_developer_settings">袩芯褋褌邪胁泻械 褉邪蟹胁懈褬邪褔邪</string>
+    <string name="button_watchface_settings">小褍褔械褭械 褔邪褋芯胁薪懈泻邪</string>
+    <string name="watchface_widget_type_date">袛邪褌褍屑</string>
+    <string name="button_watchface_save_apply">小邪褔褍胁邪褬 懈 锌褉懈屑械薪懈</string>
+    <string name="button_watchface_preview">袩褉械谐谢械写邪褬 薪邪 褔邪褋芯胁薪懈泻褍</string>
+    <string name="watchface_dialog_widget_x_coordinate">袩芯谢芯卸邪褬 啸 (薪邪褬胁懈褕械 240)</string>
+    <string name="watchface_dialog_widget_preset_left">袥械胁芯</string>
+    <string name="watchface_dialog_widget_preset_right">袛械褋薪芯</string>
+    <string name="watchface_setting_display_refresh_full">袩褍薪芯 芯褋胁械卸械褮械 (褍 屑懈薪褍褌懈屑邪)</string>
+    <string name="watchface_widget_type_calories">袣邪谢芯褉懈褬械</string>
+    <string name="prefs_vibration_enable">袨屑芯谐褍褯懈 褌褉械褕褮褍</string>
+    <string name="prefs_sleep_time_summary">袨写褉械写懈 胁褉械屑械 泻邪写邪 褋械 褋锌邪胁邪褮械 锌褉械锌芯蟹薪邪褬械</string>
+    <string name="prefs_autoheartrate_measurement">小邪屑芯褋褌邪谢薪芯 屑械褉械褮械 褋褉褔邪薪懈褏 芯褌泻褍褑邪褬邪</string>
+    <string name="prefs_autoheartrate_sleep">袦械褉懈 锌褉懈谢懈泻芯屑 褋锌邪胁邪褮邪</string>
+    <string name="pref_title_equalizer_soft">袧械卸薪芯</string>
+    <string name="pref_title_equalizer_dynamic">袞懈胁芯</string>
+    <string name="prefs_active_noise_cancelling_level_low">袧懈褋泻芯</string>
+    <string name="prefs_active_noise_cancelling_level_high">袙懈褋芯泻芯</string>
+    <string name="prefs_right">袛械褋薪芯</string>
+    <string name="prefs_noise_control">校锌褉邪胁褭邪褮械 斜褍泻芯屑</string>
+    <string name="pref_header_sony_device_info">袩芯写邪褑懈 褍褉械褣邪褬邪</string>
+    <string name="activity_filter_filter_title">袩褉芯锌褍褋薪懈泻</string>
+    <string name="fossil_hr_edit_action">校褉械写懈 写械褬褋褌胁芯</string>
+    <string name="prefs_equalizer">校褬械写薪邪褔懈胁邪褔 蟹胁褍泻邪</string>
+    <string name="prefs_events_forwarding_summary">袣芯褉懈褋褌懈 写芯谐邪褣邪褬械 褍褉械褣邪褬邪 蟹邪 芯泻懈写邪褮械 褉邪写褮懈 懈 芯褌锌褉械屑邪褮械 袗薪写褉芯懈写邪</string>
+    <string name="prefs_events_forwarding_wokeup">袩褉懈 斜褍褣械褮褍</string>
+    <string name="prefs_events_forwarding_broadcast_title">袨褌锌褉械屑懈 锌芯褉褍泻褍</string>
+    <string name="prefs_events_forwarding_action_title">袛械褬褋褌胁芯 褌褉褔邪褮邪</string>
+    <string name="pref_header_filter">袩褉芯锌褍褋薪懈泻 褋锌芯褉褌褋泻懈褏 写械褬褋褌邪胁邪</string>
+    <string name="activity_filter_reset_filter">袩芯薪懈褕褌懈 锌褉芯锌褍褋薪懈泻</string>
+    <string name="activity_filter_name_contains">袨蟹薪邪泻邪</string>
+    <string name="pref_summary_physical_buttons">袩芯写械褋懈褌械 褍谢芯谐褍 褋褌胁邪褉薪懈褏 写褍谐屑邪写懈 薪邪 褔邪褋芯胁薪懈泻褍</string>
+    <string name="qhybrid_summary_calibration">袠蟹斜邪卸写邪褉懈 褉褍褔泻械 褔邪褋芯胁薪懈泻邪</string>
+    <string name="fossil_hr_new_action">袧芯胁芯 写械褬褋褌胁芯</string>
+    <string name="qhybrid_calibration_counterclockwise">小褍锌褉芯褌薪芯 芯写 泻褉械褌邪褮邪 泻邪蟹邪褭泻懈</string>
+    <string name="button_watchface_edit_name">校褉械写懈 懈屑械</string>
+    <string name="button_watchface_select_image">袠蟹屑械薪懈 锌芯蟹邪写懈薪褋泻褍 褋谢懈泻褍</string>
+    <string name="watchface_widget_type_weather">袙褉械屑械薪褋泻懈 褍褋谢芯胁懈</string>
+    <string name="watchface_setting_button_toggle_widgets">袩褉械泻懈写邪褔 褋锌褉邪胁懈褑邪</string>
+    <string name="watchface_setting_button_toggle_backlight">校泻褭褍褔懈 锌芯蟹邪写懈薪褋泻芯 褋胁械褌谢芯</string>
+    <string name="watchface_setting_button_move_hands">袩芯屑械褉邪褬 褉褍泻械</string>
+    <string name="enable_vibrations_summary">孝褉械褕褮邪 芯 锌芯蟹懈胁懈屑邪, 锌芯褉褍泻邪屑邪, 芯斜邪胁械褕褌械褮懈屑邪 懈 芯褋褌邪谢芯屑</string>
+    <string name="prefs_equalizer_summary">袨屑芯谐褍褯懈 懈谢懈 芯薪械屑芯谐褍褯懈 褍褬械写薪邪褔懈胁邪褔 蟹胁褍泻邪</string>
+    <string name="pref_header_heartrate_sleep">小锌邪胁邪褮械</string>
+    <string name="prefs_left">袥械胁芯</string>
+    <string name="averageHR">小褉褔邪薪芯 斜懈谢芯</string>
+    <string name="activity_detail_show_gps_label">袩褉懈泻邪蟹褍褬 锌褍褌邪褮褍 袚袩袠泻褋-邪</string>
+    <string name="gps_track">袩褍褌邪褮邪 袚袩袠泻褋-邪</string>
+    <string name="activity_filter_individual_items">袩芯褬械写懈薪邪褔薪芯 芯蟹薪邪褔械薪械 褋褌邪胁泻械</string>
+    <string name="appmanager_app_edit">校褉械写懈</string>
+    <string name="watchface_dialog_widget_cat_generic">袨锌褕褌械</string>
+    <string name="watchface_dialog_widget_preset_top">袙褉褏</string>
+    <string name="watchface_dialog_widget_preset_bottom">袛薪芯</string>
+    <string name="watchface_widget_type_chance_rain">袠蟹谐谢械写懈 蟹邪 泻懈褕褍</string>
+    <string name="nothing_prefs_inear_summary">袠蟹胁械写懈/蟹邪褋褌邪薪懈 屑褍蟹懈泻褍 褍 蟹邪胁懈褋薪芯褋褌懈 芯写 褌芯谐邪 写邪 谢懈 薪芯褋懈褌械 褍褕薪械 斜褍斜懈褑械</string>
+    <string name="prefs_galaxy_buds_experimental">袩褉芯斜薪芯</string>
+    <string name="watchface_dialog_widget_cat_position">袩芯谢芯卸邪褬</string>
+    <string name="devicetype_fitpro">肖懈褌锌褉芯</string>
+    <string name="prefs_events_forwarding_startnonwear">袩褉懈 薪械薪芯褕械褮褍</string>
+    <string name="activity_filter_apply_filter">袩褉懈屑械薪懈 锌褉芯锌褍褋薪懈泻</string>
+    <string name="fossil_hr_new_action_cancel">芯褌泻邪卸懈</string>
+    <string name="watchface_dialog_title_set_name">袩芯褋褌邪胁懈 懈屑械 褋谢懈泻械 褋褍褔械褭邪 褔邪褋芯胁薪懈泻邪</string>
+    <string name="watchface_dialog_widget_type">袙褉褋褌邪</string>
+    <string name="watchface_dialog_widget_y_coordinate">袩芯谢芯卸邪褬 校 (薪邪褬胁懈褕械 240)</string>
+    <string name="watchface_dialog_widget_color">袘芯褬邪</string>
+    <string name="watchface_dialog_widget_color_white">袘械谢邪</string>
+    <string name="watchface_dialog_title_settings">袩芯褋褌邪胁泻械 褋谢懈褔械 褋褍褔械褭邪 褔邪褋芯胁薪懈泻邪</string>
+    <string name="watchface_dialog_title_widget">袩芯褋褌邪胁泻械 褋锌褉邪胁懈褑械</string>
+    <string name="watchface_dialog_widget_width">楔懈褉懈薪邪 褋锌褉邪胁懈褑械 (褍 褌邪褔泻邪屑邪)</string>
+    <string name="watchface_setting_title_display_refresh_timeout">袩褉懈泻邪蟹褍褬 胁褉械屑械 芯褋胁械卸邪胁邪褮邪</string>
+    <string name="watchface_setting_wrist_flick_move_relative">袩芯屑械褉邪褬 褉褍泻械 褍 芯写薪芯褋褍 薪邪 胁褉械屑械</string>
+    <string name="watchface_dialog_widget_background">袩芯蟹邪写懈薪邪</string>
+    <string name="hybridhr_widget_bg_thin_circle">孝邪薪泻邪 泻褉褍卸薪懈褑邪</string>
+    <string name="hybridhr_widget_bg_double_circle">袛胁芯泻褉褍卸薪懈褑邪</string>
+    <string name="hybridhr_widget_bg_dashed_circle">孝邪褔泻邪褋褌邪 泻褉褍卸薪懈褑邪</string>
+    <string name="prefs_notifications_enable">袨屑芯谐褍褯懈 芯斜邪胁械褕褌械褮邪</string>
+    <string name="prefs_autoheartrate">小邪屑芯褋褌邪谢薪芯 屑械褉械褮械 褋褉褔邪薪懈褏 芯褌泻褍褑邪褬邪</string>
+    <string name="prefs_autoheartrate_interval">校褔械褋褌邪谢芯褋褌 屑械褉械褮邪</string>
+    <string name="nothing_prefs_audiomode_title">袧邪褔懈薪 芯蟹胁褍褔械褮邪</string>
+    <string name="pref_title_equalizer_clear">效懈褋褌芯</string>
+    <string name="pref_title_equalizer_trebble">袩芯褬邪褔邪褮械 胁懈褋芯泻懈褏</string>
+    <string name="prefs_dolby_summary">袨斜褉邪蟹邪褑 褍褬械写薪邪褔懈胁邪褔邪 写芯谢斜懈褬邪</string>
+    <string name="prefs_game_mode">袧邪褔懈薪 懈谐褉械</string>
+    <string name="pref_title_touch_volume">袚谢邪褋薪芯褯邪</string>
+    <string name="pref_header_stress">小褌褉械褋</string>
+    <string name="qhybrid_calibration_clockwise">校 锌褉邪胁褑褍 泻褉械褌邪褮邪 泻邪蟹邪褭泻懈</string>
+    <string name="watchface_widget_type_steps">袣芯褉邪褑懈</string>
+    <string name="watchface_widget_type_heart_rate">小褉褔邪薪懈 芯褌泻褍褑邪褬懈</string>
+    <string name="watchface_dialog_widget_cat_custom_widget">袩褉芯懈蟹胁芯褭薪邪 褋锌褉邪胁懈褑邪</string>
+    <string name="pref_summary_developer_settings">袩芯褋褌邪胁泻械 懈 屑芯谐褍褯薪芯褋褌懈 泻芯褉懈褕褯械薪械 泻芯写 褉邪蟹胁懈褬邪褔邪 锌褉芯谐褉邪屑邪</string>
+    <string name="watchface_dialog_widget_presets">袨斜褉邪褋褑懈 锌芯谢芯卸邪褬薪懈</string>
+    <string name="qhybrid_title_watchface_designer">校褉械写薪懈泻 褋谢懈泻械 褋褍褔械褭邪 褔邪褋芯胁薪懈泻邪</string>
+    <string name="watchface_dialog_widget_color_black">笑褉薪邪</string>
+    <string name="watchface_widget_type_battery">袘邪褌械褉懈褬邪</string>
+    <string name="watchface_setting_title_power_saving">校褕褌械写邪 薪邪锌邪褬邪褮邪</string>
+    <string name="prefs_sleep_time">袙褉械屑械 褋锌邪胁邪褮邪</string>
+    <string name="prefs_sleep_time_label">袨写褉械写懈 褔邪褋芯胁械 褋锌邪胁邪褮邪</string>
+    <string name="prefs_in_ear_detection_summary">袨褌锌褉械屑邪褬 锌芯蟹懈胁械 泻褉芯蟹 褍褕薪械 斜褍斜懈褑械 泻邪写邪 褋械 薪邪谢邪蟹械 褍 褍褕懈屑邪</string>
+    <string name="prefs_equalizer_preset">袨斜褉邪褋褑懈 屑械褕邪褔邪 蟹胁褍泻邪</string>
+    <string name="pref_title_equalizer_normal">袨斜懈褔薪芯</string>
+    <string name="prefs_dolby_mode">袛芯谢斜懈</string>
+    <string name="prefs_touch_lock">袟邪泻褭褍褔邪胁邪褮械 写芯写懈褉芯屑</string>
+    <string name="pref_title_equalizer_bass_boost">袩芯褬邪褔邪褮械 写褍斜芯泻懈褏</string>
+    <string name="prefs_game_mode_summary">小邪屑芯 褍泻芯谢懈泻芯 褌械谢械褎芯薪 锌芯写褉卸邪胁邪 懈谐褉邪褮械</string>
+    <string name="prefs_touch_lock_summary">袨薪械屑芯谐褍褯懈 写芯谐邪褣邪褬械 写芯写懈褉芯屑</string>
+    <string name="fossil_hr_commute_actions_explanation">袪邪写褮邪 芯胁写械 锌芯写械褕械薪邪 褯械 褋械 褬邪胁褭邪褌懈 褍 锌褉芯谐褉邪屑褍 袣芯屑褍褌械 薪邪 褔邪褋芯胁薪懈泻褍. 袩褉芯褔懈褌邪褬褌械 胁懈泻懈 褉邪写懈 胁懈褕械 锌芯写邪褌邪泻邪 泻邪泻芯 褍锌褉邪胁褭邪褌懈 褋邪写褉卸邪褬械屑 芯胁懈褏 褉邪写褮懈.</string>
+    <string name="prefs_events_forwarding_fellsleep">袩褉懈 褍褋锌邪胁褭懈胁邪褮褍</string>
+    <string name="pref_header_statistics">小褌邪褌懈褋褌懈泻邪 褋锌芯褉褌褋泻懈褏 写械褬褋褌邪胁邪</string>
+    <string name="watchface_setting_desc_wrist_flick">(褉邪写懈 锌芯褌锌褍薪芯谐 芯薪械屑芯谐褍褯邪胁邪褮邪, 芯屑芯谐褍褯懈褌械 芯写薪芯褋薪芯 泻褉械褌邪褮械 懈 锌芯写械褋懈褌械 胁褉械写薪芯褋褌 薪邪 0)</string>
+    <string name="enable_notifications_summary">袨斜邪胁械褕褌械褮邪 芯 锌芯蟹懈胁懈屑邪, 锌芯褉褍泻邪屑邪 懈 芯褋褌邪谢芯屑</string>
+    <string name="prefs_autoheartrate_summary">袩芯胁褉械屑械薪芯 屑械褉械褮械 褋褉褔邪薪懈褏 芯褌泻褍褑邪褬邪 褌芯泻芯屑 写邪薪邪 懈 锌褉懈谢懈泻芯屑 褋锌邪胁邪褮邪</string>
+    <string name="fw_upgrade_notice_miband6">校谐褉邪写懈褯械褌械 写褉邪屑 %s 薪邪 褋胁芯褬 袦懈 袘邪薪写 6.
+\n
+\n袦芯谢懈屑, 褍谐褉邪写懈褌械 写邪褌芯褌械泻褍 .fw, 懈 薪邪泻芯薪 褌芯谐邪 写邪褌芯褌械泻褍 .res. 袙邪褕 褋邪褌 褯械 褋械 锌褉械锌芯泻褉械薪褍褌懈 薪邪泻芯薪 褍谐褉邪写褮械 写邪褌芯褌械泻械 .fw.
+\n
+\n袩褉懈屑械写斜邪: 袧械 屑芯褉邪 写邪 褍谐褉邪写懈褌械 写邪褌芯褌械泻褍 .res 褍泻芯谢懈泻芯 褬械 懈褋褌邪 泻邪芯 锌褉械褌褏芯写薪芯 褍谐褉邪褣械薪邪.
+\n
+\n袧袗小孝袗袙袠孝袝 袧袗 小袨袩小孝袙袝袧校 袨袛袚袨袙袨袪袧袨小孝!</string>
+    <string name="fw_upgrade_notice_miband4">校谐褉邪写懈褯械褌械 写褉邪屑 %s 薪邪 褋胁芯褬 袦懈 袘邪薪写 4.
+\n
+\n袦芯谢懈屑, 褍谐褉邪写懈褌械 写邪褌芯褌械泻褍 .fw, 懈 薪邪泻芯薪 褌芯谐邪 写邪褌芯褌械泻褍 .res. 袙邪褕 褋邪褌 褯械 褋械 锌褉械锌芯泻褉械薪褍褌懈 薪邪泻芯薪 褍谐褉邪写褮械 写邪褌芯褌械泻械 .fw.
+\n
+\n袩褉懈屑械写斜邪: 袧械 屑芯褉邪 写邪 褍谐褉邪写懈褌械 写邪褌芯褌械泻褍 .res 褍泻芯谢懈泻芯 褬械 懈褋褌邪 泻邪芯 锌褉械褌褏芯写薪芯 褍谐褉邪褣械薪邪.
+\n
+\n袧袗小孝袗袙袠孝袝 袧袗 小袨袩小孝袙袝袧校 袨袛袚袨袙袨袪袧袨小孝!</string>
+    <string name="fw_upgrade_notice_miband7">校谐褉邪写懈褯械褌械 写褉邪屑 %s 薪邪 褋胁芯褬 袦懈 袘邪薪写 7.
+\n
+\n袙邪褕邪 薪邪褉褍泻胁懈褑邪 褯械 褋械 锌褉械锌芯泻褉械薪褍褌懈 薪邪泻芯薪 褍谐褉邪写褮械 写邪褌芯褌械泻械 .zip.
+\n
+\n袧袗小孝袗袙袠孝袝 袧袗 小袨袩小孝袙袝袧校 袨袛袚袨袙袨袪袧袨小孝!</string>
+    <string name="fw_upgrade_notice_miband5">校谐褉邪写懈褯械褌械 写褉邪屑 %s 薪邪 褋胁芯褬 袦懈 袘邪薪写 5.
+\n
+\n袦芯谢懈屑, 褍谐褉邪写懈褌械 写邪褌芯褌械泻褍 .fw, 懈 薪邪泻芯薪 褌芯谐邪 写邪褌芯褌械泻褍 .res. 袙邪褕 褋邪褌 褯械 褋械 锌褉械锌芯泻褉械薪褍褌懈 薪邪泻芯薪 褍谐褉邪写褮械 写邪褌芯褌械泻械 .fw.
+\n
+\n袩褉懈屑械写斜邪: 袧械 屑芯褉邪 写邪 褍谐褉邪写懈褌械 写邪褌芯褌械泻褍 .res 褍泻芯谢懈泻芯 褬械 懈褋褌邪 泻邪芯 锌褉械褌褏芯写薪芯 褍谐褉邪褣械薪邪.
+\n
+\n袧袗小孝袗袙袠孝袝 袧袗 小袨袩小孝袙袝袧校 袨袛袚袨袙袨袪袧袨小孝!</string>
+    <string name="fw_upgrade_notice_amazfitvergel">校谐褉邪写懈褯械褌械 写褉邪屑 %s 薪邪 褋胁芯褬 袗屑械褬蟹褎懈褌 袙械褉谐械 袥邪褬褌.
+\n
+\n袦芯谢懈屑, 褍谐褉邪写懈褌械 写邪褌芯褌械泻褍 .fw, 懈 薪邪泻芯薪 褌芯谐邪 写邪褌芯褌械泻褍 .res. 袙邪褕 褋邪褌 褯械 褋械 锌褉械锌芯泻褉械薪褍褌懈 薪邪泻芯薪 褍谐褉邪写褮械 写邪褌芯褌械泻械 .fw.
+\n
+\n袩褉懈屑械写斜邪: 袧械 屑芯褉邪 写邪 褍谐褉邪写懈褌械 写邪褌芯褌械泻褍 .res 褍泻芯谢懈泻芯 褬械 懈褋褌邪 泻邪芯 锌褉械褌褏芯写薪芯 褍谐褉邪褣械薪邪.
+\n
+\n袧袗小孝袗袙袠孝袝 袧袗 小袨袩小孝袙袝袧校 袨袛袚袨袙袨袪袧袨小孝!</string>
+    <string name="fw_upgrade_notice_amazfitgts">校谐褉邪写懈褯械褌械 写褉邪屑 %s 薪邪 褋胁芯褬 袗屑械褬蟹褎懈褌 袚孝小.
+\n
+\n袦芯谢懈屑, 褍谐褉邪写懈褌械 写邪褌芯褌械泻褍 .fw, 懈 薪邪泻芯薪 褌芯谐邪 写邪褌芯褌械泻褍 .res. 袙邪褕 褋邪褌 褯械 褋械 锌褉械锌芯泻褉械薪褍褌懈 薪邪泻芯薪 褍谐褉邪写褮械 写邪褌芯褌械泻械 .fw.
+\n
+\n袩褉懈屑械写斜邪: 袧械 屑芯褉邪 写邪 褍谐褉邪写懈褌械 写邪褌芯褌械泻褍 .res 褍泻芯谢懈泻芯 褬械 懈褋褌邪 泻邪芯 锌褉械褌褏芯写薪芯 褍谐褉邪褣械薪邪.
+\n
+\n袧袗小孝袗袙袠孝袝 袧袗 小袨袩小孝袙袝袧校 袨袛袚袨袙袨袪袧袨小孝!</string>
+    <string name="fw_upgrade_notice_amazfit_band7">校谐褉邪写懈褯械褌械 写褉邪屑 %s 薪邪 褋胁芯褬 袗屑械褬蟹褎懈褌 斜邪薪写 7.
+\n
+\n袙邪褕邪 薪邪褉褍泻胁懈褑邪 褯械 褋械 锌褉械锌芯泻褉械薪褍褌懈 薪邪泻芯薪 褍谐褉邪写褮械 写邪褌芯褌械泻械 .zip.
+\n
+\n袧袗小孝袗袙袠孝袝 袧袗 小袨袩小孝袙袝袧校 袨袛袚袨袙袨袪袧袨小孝!</string>
+    <string name="miband_firmware_suggest_whitelist">校泻芯谢懈泻芯 懈锌邪泻 卸械谢懈褌械 写邪 薪邪褋褌邪胁懈褌械 懈 褋胁械 褉邪写懈 泻邪泻芯 褌褉械斜邪 薪邪泻芯薪 褌芯谐邪, 屑芯谢懈屑, 褬邪胁懈褌械 锌褉芯谐褉邪屑械褉懈屑邪 袚械褵械褌袘褉胁薪邪 写邪 芯写芯斜褉械 薪邪 褋锌懈褋泻褍 懈蟹写邪褮械 写褉邪屑邪 %s.</string>
+    <string name="fw_upgrade_notice_amazfitcor2">校谐褉邪写懈褯械褌械 写褉邪屑 %s 薪邪 褋胁芯褬 袗屑械褬蟹褎懈褌 袣芯褉 2. \n \n袦芯谢懈屑, 褍谐褉邪写懈褌械 写邪褌芯褌械泻褍 .fw, 懈 薪邪泻芯薪 褌芯谐邪 写邪褌芯褌械泻褍 .res. 袙邪褕 褋邪褌 褯械 褋械 锌褉械锌芯泻褉械薪褍褌懈 薪邪泻芯薪 褍谐褉邪写褮械 写邪褌芯褌械泻械 .fw. \n \n袩褉懈屑械写斜邪: 袧械 屑芯褉邪 写邪 褍谐褉邪写懈褌械 写邪褌芯褌械泻褍 .res 褍泻芯谢懈泻芯 褬械 懈褋褌邪 泻邪芯 锌褉械褌褏芯写薪芯 褍谐褉邪褣械薪邪. \n \n袧袗小孝袗袙袠孝袝 袧袗 小袨袩小孝袙袝袧校 袨袛袚袨袙袨袪袧袨小孝! \n \n袩袨孝袩校袧袨 袌袝 袧袝袩袪袨袙袝袪袝袧 袩袨小孝校袩袗袣, 袧袗袌袙袝袪袨袙袗孝袧袠袌袝 孝袪袝袘袗 袛袗 校袚袪袗袛袠孝袝 袛袪袗袦 BEATS_W 袗袣袨 袥袠 袌袝 袠袦袝 校袪袝袀袗袌袗 鈥濧mazfit Band 2鈥�</string>
+    <string name="open_fw_installer_info_text">校谐褉邪褣懈胁邪褔 写褉邪屑邪/褋褍褔械褭邪/锌褉芯谐褉邪屑邪/写邪褌芯褌械泻械 芯屑芯谐褍褯邪胁邪 芯褌锌褉械屑邪褮械/褍谐褉邪写褮褍 锌芯写褉卸邪薪懈褏 写邪褌芯褌械泻邪 (写褉邪屑邪, 褋褍褔械褭邪, 锌褉芯谐褉邪屑邪, 袚袩小-邪, 懈蟹胁芯褉邪, 褋谢芯胁芯谢懈泻邪鈥�) 薪邪 褍褉械褣邪褬. 袦芯卸械褌械 锌芯谐谢械写邪褌懈 芯锌褕懈褉薪懈褬械 薪邪 胁懈泻懈褬褍: https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Firmware-Update</string>
+    <string name="pref_summary_enable_outgoing_call">袨薪械屑芯谐褍褯邪胁邪褮械 芯胁芯谐邪 褯械 褌邪泻芯褣械 蟹邪褍褋褌邪胁懈褌懈 褌褉械褕褮褍 袩械斜谢邪 2/袥袝 锌褉懈 芯写谢邪蟹薪懈屑 锌芯蟹懈胁懈屑邪</string>
+    <string name="fw_upgrade_notice_amazfitcor">校谐褉邪写懈褯械褌械 写褉邪屑 %s 薪邪 褋胁芯褬 袗屑械褬蟹褎懈褌 袣芯褉.
+\n
+\n袦芯谢懈屑, 褍谐褉邪写懈褌械 写邪褌芯褌械泻褍 .fw, 懈 薪邪泻芯薪 褌芯谐邪 写邪褌芯褌械泻褍 .res. 袙邪褕 褋邪褌 褯械 褋械 锌褉械锌芯泻褉械薪褍褌懈 薪邪泻芯薪 褍谐褉邪写褮械 写邪褌芯褌械泻械 .fw.
+\n
+\n袩褉懈屑械写斜邪: 袧械 屑芯褉邪 写邪 褍谐褉邪写懈褌械 写邪褌芯褌械泻褍 .res 褍泻芯谢懈泻芯 褬械 懈褋褌邪 泻邪芯 锌褉械褌褏芯写薪芯 褍谐褉邪褣械薪邪.
+\n
+\n袧袗小孝袗袙袠孝袝 袧袗 小袨袩小孝袙袝袧校 袨袛袚袨袙袨袪袧袨小孝!</string>
+    <string name="fw_upgrade_notice_amazfit_gtr3">校谐褉邪写懈褯械褌械 写褉邪屑 %s 薪邪 褋胁芯褬 袗屑械褬蟹褎懈褌 袚孝袪 3.
+\n
+\n袙邪褕邪 薪邪褉褍泻胁懈褑邪 褯械 褋械 锌褉械锌芯泻褉械薪褍褌懈 薪邪泻芯薪 褍谐褉邪写褮械 写邪褌芯褌械泻械 .zip.
+\n
+\n袧袗小孝袗袙袠孝袝 袧袗 小袨袩小孝袙袝袧校 袨袛袚袨袙袨袪袧袨小孝!</string>
+    <string name="fw_upgrade_notice_amazfit_gts3">校谐褉邪写懈褯械褌械 写褉邪屑 %s 薪邪 褋胁芯褬 袗屑械褬蟹褎懈褌 袚孝小 3.
+\n
+\n袙邪褕邪 薪邪褉褍泻胁懈褑邪 褯械 褋械 锌褉械锌芯泻褉械薪褍褌懈 薪邪泻芯薪 褍谐褉邪写褮械 写邪褌芯褌械泻械 .zip.
+\n
+\n袧袗小孝袗袙袠孝袝 袧袗 小袨袩小孝袙袝袧校 袨袛袚袨袙袨袪袧袨小孝!</string>
+    <string name="fw_upgrade_notice_amazfitneo">校谐褉邪写懈褯械褌械 写褉邪屑 %s 薪邪 褋胁芯褬 袗屑械褬蟹褎懈褌 袧械芯.
+\n
+\n袙邪褕邪 薪邪褉褍泻胁懈褑邪 褯械 褋械 锌褉械锌芯泻褉械薪褍褌懈 薪邪泻芯薪 褍谐褉邪写褮械 写邪褌芯褌械泻械 .fw.
+\n
+\n袧袗小孝袗袙袠孝袝 袧袗 小袨袩小孝袙袝袧校 袨袛袚袨袙袨袪袧袨小孝!</string>
+    <string name="fw_upgrade_notice_amazfitx">校谐褉邪写懈褯械褌械 写褉邪屑 %s 薪邪 褋胁芯褬 袗屑械褬蟹褎懈褌 袠泻褋.
+\n
+\n袦芯谢懈屑, 褍谐褉邪写懈褌械 写邪褌芯褌械泻褍 .fw, 懈 薪邪泻芯薪 褌芯谐邪 写邪褌芯褌械泻褍 .res. 袙邪褕邪 薪邪褉褍泻胁懈褑邪 褯械 褋械 锌褉械锌芯泻褉械薪褍褌懈 薪邪泻芯薪 褍谐褉邪写褮械 写邪褌芯褌械泻械 .fw.
+\n
+\n袩褉懈屑械写斜邪: 袧械 屑芯褉邪 写邪 褍谐褉邪写懈褌械 写邪褌芯褌械泻褍 .res 褍泻芯谢懈泻芯 褬械 懈褋褌邪 泻邪芯 锌褉械褌褏芯写薪芯 褍谐褉邪褣械薪邪.
+\n
+\n袧袗小孝袗袙袠孝袝 袧袗 小袨袩小孝袙袝袧校 袨袛袚袨袙袨袪袧袨小孝!</string>
+    <string name="pref_title_pebble_sync_morpheuz">校褋泻谢邪写懈 袦芯褉斜械褍蟹</string>
+    <string name="pref_header_pebble_timeline">袙褉械屑械薪褋泻邪 谢懈薪懈褬邪 袩械斜谢邪</string>
+    <string name="pref_summary_sunrise_sunset">袩芯褕邪褭懈 胁褉械屑械薪邪 懈蟹谢邪褋泻邪 懈 蟹邪谢邪褋泻邪 小褍薪褑邪 蟹邪褋薪芯胁邪薪邪 薪邪 锌芯谢芯卸邪褬褍 薪邪 胁褉械屑械薪褋泻褍 谢懈薪懈褬褍 袩械斜谢邪</string>
+    <string name="fw_upgrade_notice_amazfit_gts4">校谐褉邪写懈褯械褌械 写褉邪屑 %s 薪邪 褋胁芯褬 袗屑械褬蟹褎懈褌 袚孝小 4.
+\n
+\n袙邪褕邪 薪邪褉褍泻胁懈褑邪 褯械 褋械 锌褉械锌芯泻褉械薪褍褌懈 薪邪泻芯薪 褍谐褉邪写褮械 写邪褌芯褌械泻械 .zip.
+\n
+\n袧袗小孝袗袙袠孝袝 袧袗 小袨袩小孝袙袝袧校 袨袛袚袨袙袨袪袧袨小孝!</string>
+    <string name="open_fw_installer_getting_files_text">袩芯褕褌芯 薪械 屑芯卸械屑芯 褉邪蟹写械褭懈胁邪褌懈 写邪褌芯褌械泻械 写褉邪屑邪, 屑芯褉邪 写邪 懈褏 写芯斜邪胁懈褌械 褋邪屑懈. 孝芯 蟹薪邪褔懈 写邪 褌褉械斜邪 写邪 锌褉械褌褉邪卸懈褌械 写邪褌芯褌械泻械 褍 写邪褌芯褌械泻邪屑邪 apk, 褎芯褉褍屑懈屑邪, 薪邪 鈥濧mazfitwatchfaces鈥� (蟹邪 褍褉械褣邪褬械 袦懈斜邪薪写邪/袗屑械褬蟹褎懈褌邪) 懈 褌邪泻芯 写邪褭械.</string>
+    <string name="pref_summary_banglejs_txt_bitmap_size">袙械谢懈褔懈薪邪 锌褉懈泻邪蟹邪 锌懈褋屑邪 斜懈褌屑邪锌械</string>
+    <string name="pref_title_banglejs_webview_url">袗写褉械褋邪 锌褍褮邪褔邪 锌褉芯谐褉邪屑邪</string>
+    <string name="pref_summary_banglejs_webview_url">袗泻芯 卸械谢懈褌械 锌褉芯懈蟹胁芯褭薪懈 锌褍褮邪褔 锌褉芯谐褉邪屑邪 褍褋屑械褉懈褌械 褋胁芯褬褍 邪写褉械褋褍 https://鈥�/android.html 芯胁写械. 袠薪邪褔械 芯褋褌邪胁懈褌械 锌褉邪蟹薪懈屑 https://banglejs.com/apps</string>
+    <string name="pref_rtl_max_line_length">袧邪褬胁械褯邪 写褍卸懈薪邪 褋鈥櫺葱笛佇叫�-薪邪-谢械胁芯</string>
+    <string name="pref_title_weather_summary">袣芯褉懈褕褯械薪芯 蟹邪 锌芯谐芯薪 胁褉械屑械薪褋泻懈褏 锌褉懈谢懈泻邪 薪邪 袥懈薪械邪谐械袨小-褍, 写褉褍谐芯 懈蟹写邪褮械 袗薪写褉芯懈写邪 泻芯褬械 褬械 锌芯褌褉械斜薪芯 褉邪写懈 泻芯褉懈褕褯械褮邪 锌褉芯谐褉邪屑邪 泻邪芯 褕褌芯 褬械 鈥炐捬�械屑械薪褋泻邪 芯斜邪胁械褕褌械褮邪鈥�. 袩褉芯薪邪褣懈褌械 芯锌褕懈褉薪懈褬械 锌芯写邪褌泻械 薪邪 胁懈泻懈褬褍 袚械褵械褌袘褉胁薪邪.</string>
+    <string name="pref_summary_pebble_forcele">袣芯褉懈褋褌懈 芯锌懈褌薪褍 锌芯写褉褕泻褍 袩械斜谢邪 袥袝 蟹邪 褋胁械 袩械斜谢械, 褍屑械褋褌芯 袘孝 褍芯斜懈褔邪褬械薪芯谐. 袟邪褏褌械胁邪 褍锌邪褉懈胁邪褮械 蟹邪 薪械-袥袝 锌褉械褌褏芯写薪芯, 锌邪 蟹邪褌懈屑 薪邪 袩械斜谢 袥袝</string>
+    <string name="fw_upgrade_notice_miband3">校谐褉邪写懈褯械褌械 写褉邪屑 %s 薪邪 褋胁芯褬 袦懈 袘邪薪写 3.
+\n
+\n袦芯谢懈屑, 褍谐褉邪写懈褌械 写邪褌芯褌械泻褍 .fw, 懈 薪邪泻芯薪 褌芯谐邪 写邪褌芯褌械泻褍 .res. 袙邪褕 褋邪褌 褯械 褋械 锌褉械锌芯泻褉械薪褍褌懈 薪邪泻芯薪 褍谐褉邪写褮械 写邪褌芯褌械泻械 .fw.
+\n
+\n袩褉懈屑械写斜邪: 袧械 屑芯褉邪 写邪 褍谐褉邪写懈褌械 写邪褌芯褌械泻褍 .res 褍泻芯谢懈泻芯 褬械 懈褋褌邪 泻邪芯 锌褉械褌褏芯写薪芯 褍谐褉邪褣械薪邪.
+\n
+\n袧袗小孝袗袙袠孝袝 袧袗 小袨袩小孝袙袝袧校 袨袛袚袨袙袨袪袧袨小孝!</string>
+    <string name="fw_upgrade_notice_amazfit_trex">校谐褉邪写懈褯械褌械 写褉邪屑 %s 薪邪 褋胁芯褬 袗屑械褬蟹褎懈褌 孝-袪械泻褋.
+\n
+\n袦芯谢懈屑, 褍谐褉邪写懈褌械 写邪褌芯褌械泻褍 .fw, 懈 薪邪泻芯薪 褌芯谐邪 写邪褌芯褌械泻褍 .res. 袙邪褕 褋邪褌 褯械 褋械 锌褉械锌芯泻褉械薪褍褌懈 薪邪泻芯薪 褍谐褉邪写褮械 写邪褌芯褌械泻械 .fw.
+\n
+\n袩褉懈屑械写斜邪: 袧械 屑芯褉邪 写邪 褍谐褉邪写懈褌械 写邪褌芯褌械泻褍 .res 褍泻芯谢懈泻芯 褬械 懈褋褌邪 泻邪芯 锌褉械褌褏芯写薪芯 褍谐褉邪褣械薪邪.
+\n
+\n袧袗小孝袗袙袠孝袝 袧袗 小袨袩小孝袙袝袧校 袨袛袚袨袙袨袪袧袨小孝!</string>
+    <string name="fw_upgrade_notice_amazfitgtr">校谐褉邪写懈褯械褌械 写褉邪屑 %s 薪邪 褋胁芯褬 袗屑械褬蟹褎懈褌 袚孝袪.
+\n
+\n袦芯谢懈屑, 褍谐褉邪写懈褌械 写邪褌芯褌械泻褍 .fw, 懈 薪邪泻芯薪 褌芯谐邪 写邪褌芯褌械泻褍 .res. 袙邪褕 褋邪褌 褯械 褋械 锌褉械锌芯泻褉械薪褍褌懈 薪邪泻芯薪 褍谐褉邪写褮械 写邪褌芯褌械泻械 .fw.
+\n
+\n袩褉懈屑械写斜邪: 袧械 屑芯褉邪 写邪 褍谐褉邪写懈褌械 写邪褌芯褌械泻褍 .res 褍泻芯谢懈泻芯 褬械 懈褋褌邪 泻邪芯 锌褉械褌褏芯写薪芯 褍谐褉邪褣械薪邪.
+\n
+\n袧袗小孝袗袙袠孝袝 袧袗 小袨袩小孝袙袝袧校 袨袛袚袨袙袨袪袧袨小孝!</string>
+    <string name="fw_upgrade_notice_amazfitbip_lite">校谐褉邪写懈褯械褌械 写褉邪屑 %s 薪邪 褋胁芯褬 褍褉械褣邪褬 袗屑械褬蟹褎懈褌 袘懈褌 谢懈褌械.
+\n
+\n袦芯谢懈屑, 褍谐褉邪写懈褌械 写邪褌芯褌械泻褍 .fw, 懈 薪邪泻芯薪 褌芯谐邪 写邪褌芯褌械泻褍 .res. 袙邪褕 褋邪褌 褯械 褋械 锌褉械锌芯泻褉械薪褍褌懈 薪邪泻芯薪 褍谐褉邪写褮械 写邪褌芯褌械泻械 .fw.
+\n
+\n袩褉懈屑械写斜邪: 袧械 屑芯褉邪 写邪 褍谐褉邪写懈褌械 写邪褌芯褌械泻褍 .res 褍泻芯谢懈泻芯 褬械 懈褋褌邪 泻邪芯 锌褉械褌褏芯写薪芯 褍谐褉邪褣械薪邪.
+\n
+\n袧袗小孝袗袙袠孝袝 袧袗 小袨袩小孝袙袝袧校 袨袛袚袨袙袨袪袧袨小孝!</string>
+    <string name="fw_upgrade_notice_amazfit_gtr4">校谐褉邪写懈褯械褌械 写褉邪屑 %s 薪邪 褋胁芯褬 袗屑械褬蟹褎懈褌 袚孝袪 4.
+\n
+\n袙邪褕邪 薪邪褉褍泻胁懈褑邪 褯械 褋械 锌褉械锌芯泻褉械薪褍褌懈 薪邪泻芯薪 褍谐褉邪写褮械 写邪褌芯褌械泻械 .zip.
+\n
+\n袧袗小孝袗袙袠孝袝 袧袗 小袨袩小孝袙袝袧校 袨袛袚袨袙袨袪袧袨小孝!</string>
+    <string name="fw_multi_upgrade_notice">校谐褉邪写懈褯械褌械 写褉邪屑芯胁械 %1$s 懈 %2$s, 褍屑械褋褌芯 芯薪懈褏 泻芯褬懈 褋褍 褌褉械薪褍褌薪芯 薪邪 袙邪褕械屑 袦懈 袘邪薪写褍.</string>
+    <string name="miband_firmware_known">袨胁邪褬 写褉邪屑 褯械 斜懈褌懈 锌褉芯胁械褉械薪 懈 锌芯蟹薪邪褌芯 褬械 写邪 褬械 褍褋泻谢邪褣械薪 褋邪 袚械褵械褌袘褉胁薪芯屑.</string>
+    <string name="pref_rtl_max_line_length_summary">袩褉芯写褍卸邪胁邪 懈谢懈 褋泻褉邪褯褍褬械 谢懈薪懈褬械 锌懈褋屑邪 褋鈥櫺葱笛佇叫�-薪邪-谢械胁芯 薪邪 泻芯褬械 褬械 锌芯写械褭械薪芯</string>
+    <string name="miband_firmware_unknown_warning">袛褉邪屑 褬械 薪械锌褉芯胁械褉械薪 懈 薪械 屑芯卸械 斜懈褌懈 褋邪谐谢邪褋邪薪 褋邪 袚械褵械褌袘褉胁薪芯屑.
+\n
+\n袨斜械褋褏褉邪斜褉褍褬械屑芯 袙邪褋 写邪 谐邪 薪械 褍谐褉邪褣褍褬械褌械!</string>
+    <string name="open_fw_installer_warning_text">袨胁邪 屑芯谐褍褯薪芯褋褌 薪芯褋懈 芯锌邪褋薪芯褋褌 写邪 褌褉邪褬薪芯 芯薪械褋锌芯褋芯斜懈 褍褉械褣邪褬. 孝芯 褬械褋褌, 褌芯 褋械 薪懈褬械 写械褕邪胁邪谢芯 薪懈泻芯屑 芯写 褉邪蟹胁懈褬邪褔邪 泻褉芯蟹 锌芯褋褌褍锌邪泻, 邪谢懈, 蟹邪锌邪屑褌懈褌械 写邪 芯胁芯 褉邪写懈褌械 薪邪 褋芯锌褋褌胁械薪褍 芯写谐芯胁芯褉薪芯褋褌.</string>
+    <string name="pref_summary_notification_media_ignores_application_list">袨斜褉邪褣褍褬械 芯斜邪胁械褕褌械褮邪 屑械写懈褬邪 锌褉械 褋锌懈褋泻邪 锌褉芯谐褉邪屑邪. 袗泻芯 谢懈 褬械 芯胁邪 锌芯褋褌邪胁泻邪 薪械芯蟹薪邪褔械薪邪, 锌褉芯谐褉邪屑懈 屑械写懈褬邪 屑芯褉邪褬褍 斜懈褌懈 芯写芯斜褉械薪懈 褍 锌褉芯谐褉邪屑褋泻芯屑 褋锌懈褋泻褍 写邪 斜懈 屑械写懈褬懈 褉邪写懈谢懈 薪邪 褍褉械褣邪褬褍.</string>
+    <string name="pref_summary_enable_pebblekit">袨屑芯谐褍褯懈 芯锌懈褌薪褍 锌芯写褉褕泻褍 蟹邪 锌褉芯谐褉邪屑械 袗薪写褉芯懈写邪 泻芯褉懈褕褯械褮械屑 袩械斜谢袣懈褌邪</string>
+    <string name="pref_explanation_authkey_new_protocol">校泻芯谢懈泻芯 写芯斜懈褬械褌械 锌芯褉褍泻褍 鈥炐澬靶葱逞�邪写懈 锌褉芯谐褉邪屑 薪邪 薪邪褬薪芯胁懈褬械 懈蟹写邪褮械鈥� 薪邪 薪邪褉褍泻胁懈褑懈, 芯斜邪胁械蟹薪芯 芯蟹薪邪褔懈褌械 懈蟹薪邪写 鈥炐澬拘残� 薪邪褔懈薪 锌褉懈褬邪胁械鈥�. 孝邪锌薪懈褌械 芯胁写械 蟹邪 芯锌褕懈褉薪懈褬邪 褋邪蟹薪邪褮邪 褋邪 胁懈泻懈褬邪.</string>
+    <string name="pref_explanation_authkey">袧械泻懈 褍褉械褣邪褬懈 蟹邪褏褌械胁邪褬褍 薪邪褉芯褔懈褌褍 谢芯蟹懈薪泻褍 蟹邪 褍锌邪褉懈胁邪褮械 褉邪写懈 芯胁械褉邪胁邪褮邪 锌褉胁芯谐 锌芯泻褉械褌邪褮邪 薪邪 褍褉械褣邪褬褍. 孝邪锌薪懈褌械 芯胁写械 蟹邪 芯锌褕懈褉薪懈褬邪 褋邪蟹薪邪褮邪 褋邪 胁懈泻懈褬邪.</string>
+    <string name="pref_write_logfiles_not_available">袩芯泻褉械褌邪褮械 写薪械胁薪懈泻邪 写邪褌芯褌械泻械 薪懈褬械 褍褋锌械谢芯, 锌懈褋邪褮械 写薪械胁薪懈泻邪 薪懈褬械 褌褉械薪褍褌薪芯 写芯褋褌褍锌薪芯. 袩芯薪芯胁芯 锌芯泻褉械薪懈褌械 锌褉芯谐褉邪屑 褉邪写懈 锌芯泻褍褕邪褬邪 写邪 褋械 锌芯薪芯胁芯 锌芯泻褉械薪械 写薪械胁薪懈泻 写邪褌芯褌械泻械.</string>
+    <string name="zetime_date_format_3">袦袦/袛袛/袚袚</string>
+    <string name="miband_pairing_using_dummy_userdata">袧懈褋褍 锌褉懈谢芯卸械薪懈 胁邪褭邪薪懈 泻芯褉懈褋薪懈褔泻懈 锌芯写邪褑懈, 泻芯褉懈褋褌懈屑 谢邪卸薪械 泻芯褉懈褋薪懈褔泻械 锌芯写邪褌泻械 蟹邪 褋邪写.</string>
+    <string name="miband_pairing_tap_hint">袣邪写邪 袙邪屑 袦懈 袘邪薪写 褌褉械褋械 懈 卸屑懈谐邪, 褌邪锌薪懈褌械 薪械泻芯谢懈泻芯 锌褍褌邪.</string>
+    <string name="pref_summary_authkey">袠蟹屑械薪懈褌械 谢芯蟹懈薪泻褍 锌褉懈褬邪胁械 褍 芯锌褕褌褍 谢芯蟹懈薪泻褍 薪邪 褋胁懈屑 袙邪褕懈屑 褍褉械褣邪褬懈屑邪 褋邪 泻芯褬懈褏 斜懈 卸械谢械谢懈 写邪 褋械 锌芯胁械蟹褍褬械褌械. 袩褉械褌褏芯写薪邪 锌芯写褉邪蟹褍屑械胁邪薪邪 谢芯蟹懈薪泻邪 蟹邪 褋胁械 褍褉械褣邪褬械 褬械 0123456789@ABCDE</string>
+    <string name="zetime_date_format_2">袛袛/袦袦/袚袚</string>
+    <string name="discovery_connected_devices_hint">袧邪褔懈薪懈褌械 褋胁芯褬 褍褉械褣邪褬 锌褉芯薪邪谢邪卸褭懈胁懈屑. 孝褉械薪褍褌薪芯 锌芯胁械蟹邪薪懈 褍褉械褣邪褬懈 胁械褉芯胁邪褌薪芯 薪械褯械 斜懈褌懈 芯褌泻褉懈胁邪薪懈. 校泻褭褍褔懈褌械 锌芯谢芯卸邪褬 (薪锌褉. 袚袩小) 薪邪 袗薪写褉芯懈写褍 6+. 袨薪械屑芯谐褍褯懈褌械 褔褍胁邪褉邪 锌褉懈胁邪褌薪芯褋褌懈 蟹邪 袚械褵械褌斜褉胁薪芯, 蟹邪褌芯 褕褌芯 褋械 屑芯卸械 褋褉褍褕懈褌懈 褍泻芯谢懈泻芯 锌褉械锌芯泻褉械褯械褌械 褌械谢械褎芯薪. 校泻芯谢懈泻芯 薪械 斜褍写械 锌褉芯薪邪褣械薪 褍褉械褣邪褬 薪邪泻芯薪 薪械泻芯谢懈泻芯 屑懈薪褍褌邪, 锌芯泻褍褕邪褬褌械 锌芯薪芯胁芯 薪邪泻芯薪 锌芯薪芯胁薪芯谐 锌芯泻褉械褌邪褮邪 褋胁芯谐 屑芯斜懈谢薪芯谐 褍褉械褣邪褬邪.</string>
+    <string name="discovery_need_to_enter_authkey">袨胁芯屑 褍褉械褣邪褬褍 褬械 锌芯褌褉械斜薪邪 褌邪褬薪邪 谢芯蟹懈薪泻邪, 写褍谐芯 锌褉懈褌懈褋薪懈褌械 褍褉械褣邪褬 褉邪写懈 褮械薪芯谐 褍薪芯褋邪. 袩褉芯褔懈褌邪褬褌械 胁懈泻懈.</string>
+    <string name="pref_agps_expiry_reminder_time">袙褉械屑械 锌芯写褋械褌薪懈泻邪 懈褋褌械泻邪 袗袚袩小-邪</string>
+    <string name="pref_sleep_mode_sleep_screen_summary">袩褉懈泻邪蟹褍褬 蟹邪褋谢芯薪 褋锌邪胁邪褮邪 锌褉懈 褌懈褑邪褮褍 蟹邪褋谢芯薪邪 褌芯泻芯屑 褋锌邪胁邪褮邪, 褉邪写懈 屑邪褮械谐 褍蟹薪械屑懈褉邪胁邪褮邪</string>
+    <string name="preferences_miband_settings">袩芯褋褌邪胁泻械 袦懈 袘邪薪写邪 / 袗屑械褬蟹褎懈褌邪</string>
+    <string name="discovery_entered_invalid_authkey">孝邪褬薪邪 谢芯蟹懈薪泻邪 泻芯褬褍 褋褌械 褍薪械谢懈 褬械 锌芯谐褉械褕薪邪! 袛褍谐芯 锌褉懈褌懈褋薪懈褌械 褍褉械褣邪褬 褉邪写懈 褮械薪芯谐 褍褉械褣懈胁邪褮邪.</string>
+    <string name="devicetype_miband2">袦懈 袘邪薪写 2</string>
+    <string name="devicetype_miband5">袦懈 袘邪薪写 5</string>
+    <string name="devicetype_watch9">袙邪褔 9</string>
+    <string name="devicetype_amazfit_gtr_lite">袗屑械褬蟹褎懈褌 袚孝袪 袥邪褬褌</string>
+    <string name="devicetype_amazfit_trex">袗屑械褬蟹褎懈褌 孝-褉械泻褋</string>
+    <string name="devicetype_vibratissimo">袙懈斜褉邪褌懈褋懈屑芯</string>
+    <string name="devicetype_zepp_e">袟械锌锌 袝</string>
+    <string name="devicetype_watchx">袙邪褔 袠泻褋</string>
+    <string name="kind_watchface">小褍褔械褭械</string>
+    <string name="kind_resources">袠屑械褑懈</string>
+    <string name="devicetype_amazfit_band5">袗屑械褬蟹褎懈褌 袘邪薪写 5</string>
+    <string name="devicetype_amazfit_gts3">袗屑械褬蟹褎懈褌 袚孝小 3</string>
+    <string name="devicetype_miband7">楔邪芯屑懈 小屑邪褉褌 袘邪薪写 7</string>
+    <string name="devicetype_amazfit_gtr3">袗屑械褬蟹褎懈褌 袚孝袪 3</string>
+    <string name="devicetype_amazfit_cor">袗屑械褬蟹褎懈褌 袣芯褉</string>
+    <string name="devicetype_amazfit_cor2">袗屑械褬蟹褎懈褌 袣芯褉 2</string>
+    <string name="devicetype_amazfit_gtr">袗屑械褬蟹褎懈褌 袚孝袪</string>
+    <string name="devicetype_amazfit_bips_lite">袗屑械褬蟹褎懈褌 袘懈锌 小 袥邪褬褌</string>
+    <string name="devicetype_amazfit_bipu">袗屑械褬蟹褎懈褌 袘懈锌 校</string>
+    <string name="devicetype_amazfit_pop_pro">袗屑械褬蟹褎懈褌 袩芯锌 袩褉芯</string>
+    <string name="devicetype_amazfit_gtr2">袗屑械褬蟹褎懈褌 袚孝袪 2</string>
+    <string name="devicetype_um25">校袦-25</string>
+    <string name="devicetype_liveview">袥邪褬胁袙褬褍</string>
+    <string name="devicetype_hplus">啸袩谢褍褋</string>
+    <string name="devicetype_teclast_h30">孝械泻谢邪褋褌 啸30</string>
+    <string name="devicetype_q8">袣褍8</string>
+    <string name="devicetype_no1_f1">袧芯.1 肖1</string>
+    <string name="devicetype_xwatch">啸袙邪褔</string>
+    <string name="devicetype_watchxplus">袙邪褔 袠泻褋 袩谢褍褋</string>
+    <string name="devicetype_roidmi">袪芯懈写屑懈</string>
+    <string name="devicetype_miscale2">袦懈 小泻邪谢械 2</string>
+    <string name="devicetype_bfh16">袘肖啸-16</string>
+    <string name="devicetype_casiogbx100">袣邪褋懈芯 袚袘袠泻褋-100</string>
+    <string name="devicetype_itag">懈孝邪谐</string>
+    <string name="devicetype_mijia_lywsd02">袦懈褬懈邪 小屑邪褉褌 袣谢芯泻</string>
+    <string name="pbwinstallhandler_incorrect_hw_revision">袠蟹写邪褮械 褌胁褉写芯褌胁芯褉懈薪邪 褋械 薪械 褋谢邪卸械!</string>
+    <string name="pbwinstallhandler_correct_hw_revision">孝邪褔薪芯 懈蟹写邪褮械 褌胁褉写芯褌胁芯褉懈薪邪</string>
+    <string name="pref_summary_pebble_always_ack_pebblekit">校蟹褉芯泻芯胁邪褯械 写邪 锌芯褉褍泻械 泻芯褬械 薪懈褋褍 锌芯褋谢邪褌械 锌褉芯谐褉邪屑懈屑邪 褌褉械褯械 褋褌褉邪薪械 写邪 斜褍写褍 芯写屑邪褏 懈 褍胁械泻 锌褉懈屑械褯械薪械</string>
+    <string name="pref_summary_pebble_gatt_clientonly">袨胁芯 褬械 蟹邪 袩械斜谢 2 褋邪屑芯 懈 芯锌懈褌薪芯 褬械, 锌芯泻褍褕邪褬褌械 芯胁芯 邪泻芯 懈屑邪褌械 锌芯褌械褕泻芯褯邪 褋邪 锌芯胁械蟹懈胁邪褮械屑</string>
+    <string name="pref_summary_pebble_enable_applogs">校蟹褉芯泻芯胁邪褯械 写薪械胁薪懈泻 懈蟹 锌褉芯谐褉邪屑邪 褔邪褋芯胁薪懈泻邪 写邪 斜褍写械 蟹邪胁械写械薪 锌褉械泻芯 袚械褵械褌斜褉胁薪邪 (蟹邪褏褌械胁邪 锌芯薪芯胁薪芯 锌芯泻褉械褌邪褮械)</string>
+    <string name="pref_summary_expose_hr">袨屑芯谐褍褯邪胁邪 写褉褍谐懈屑 锌褉芯谐褉邪屑懈屑邪 写邪 锌褉懈褋褌褍锌械 锌芯写邪褑懈屑邪 啸袪 锌褉邪胁芯胁褉械屑械薪芯 写芯泻 褬械 袚械褵械褌斜褉胁薪芯 锌芯胁械蟹邪薪芯</string>
+    <string name="pref_title_pebble_mtu_limit">袨谐褉邪薪懈褔械褮械 袦孝校-邪 袩械斜谢邪 2/袥袝 袚袗孝孝</string>
+    <string name="pref_summary_pebble_mtu_limit">袗泻芯 谢懈 袙邪褕 袩械斜谢 2/袩械斜谢 袥袝 薪械 褉邪写懈 芯褔械泻懈胁邪薪芯, 锌芯泻褍褕邪褬褌械 芯胁褍 锌芯褋褌邪胁泻褍 褉邪写懈 芯谐褉邪薪懈褔邪胁邪褮邪 袦孝校-邪 (懈褋锌褉邪胁邪薪 芯锌褋械谐 褬械 20-512)</string>
+    <string name="devicetype_miband6">袦懈 袘邪薪写 6</string>
+    <string name="devicetype_miband4">袦懈 袘邪薪写 4</string>
+    <string name="devicetype_miband3">袦懈 袘邪薪写 3</string>
+    <string name="devicetype_miband">袦懈 袘邪薪写</string>
+    <string name="devicetype_pebble">袩械斜谢</string>
+    <string name="devicetype_amazfit_gts4">袗屑械褬蟹褎懈褌 袚孝小 4</string>
+    <string name="devicetype_amazfit_gts4_mini">袗屑械褬蟹褎懈褌 袚孝小 4 袦懈薪懈</string>
+    <string name="devicetype_amazfit_gtr4">袗屑械褬蟹褎懈褌 袚孝袪 4</string>
+    <string name="devicetype_amazfit_neo">袗屑械褬蟹褎懈褌 袧械芯</string>
+    <string name="devicetype_amazfit_bip">袗屑械褬蟹褎懈褌 袘懈锌</string>
+    <string name="devicetype_amazfit_bip_lite">袗屑械褬蟹褎懈褌 袘懈锌 袥邪褬褌</string>
+    <string name="devicetype_amazfit_bips">袗屑械褬蟹褎懈褌 袘懈锌 小</string>
+    <string name="devicetype_amazfit_bipupro">袗屑械褬蟹褎懈褌 袘懈锌 校 袩褉芯</string>
+    <string name="devicetype_amazfit_pop">袗屑械褬蟹褎懈褌 袩芯锌</string>
+    <string name="devicetype_amazfit_gtr2e">袗屑械褬蟹褎懈褌 袚孝袪 2械</string>
+    <string name="devicetype_amazfit_gts2">袗屑械褬蟹褎懈褌 袚孝小 2</string>
+    <string name="devicetype_amazfit_gts2_mini">袗屑械褬蟹褎懈褌 袚孝小 2 袦懈薪懈</string>
+    <string name="devicetype_amazfit_gts2e">袗屑械褬蟹褎懈褌 袚孝小 2械</string>
+    <string name="devicetype_amazfit_x">袗屑械褬蟹褎懈褌 袠泻褋</string>
+    <string name="devicetype_makibes_f68">袦邪泻懈斜械褋 肖68</string>
+    <string name="devicetype_exrizu_k8">袝泻褋褉懈蟹褍 袣8</string>
+    <string name="devicetype_qhybrid">肖芯褋懈谢 袣褍 啸懈斜褉懈写</string>
+    <string name="devicetype_mykronoz_zetime">袦懈袣褉芯薪芯蟹 袟械孝邪褬屑</string>
+    <string name="devicetype_id115">袠袛115</string>
+    <string name="fw_upgrade_notice_amazfit_gts4_mini">校谐褉邪写懈褯械褌械 写褉邪屑 %s 薪邪 褋胁芯褬 袗屑械褬蟹褎懈褌 袚孝小 4 袦懈薪懈.
+\n
+\n袙邪褕邪 薪邪褉褍泻胁懈褑邪 褯械 褋械 锌芯薪芯胁芯 锌芯泻褉械薪褍褌懈 薪邪泻芯薪 褍谐褉邪写褮械 写邪褌芯褌械泻械 .zip.
+\n
+\n袧袗小孝袗袙袎袗孝袝 袧袗 小袨袩小孝袙袝袧校 袨袛袚袨袙袨袪袧袨小孝!</string>
+    <string name="pref_title_pebble_gatt_clientonly">小邪屑芯 泻芯褉懈褋薪懈泻 袚袗孝孝-邪</string>
+    <string name="devicetype_roidmi3">袪芯懈写屑懈 3</string>
+    <string name="devicetype_y5">校5</string>
+    <string name="devicetype_casiogb6900">袣邪褋懈芯 袚袘-6900</string>
+    <string name="pref_title_pebble_always_ack_pebblekit">袩褉械褉邪薪懈 袩械斜谢芯胁泻懈褌 袗笑袣</string>
+    <string name="devicetype_amazfit_band7">袗屑械褬蟹褎懈褌 袘邪薪写 7</string>
+    <string name="pref_title_notification_times_enabled">袙褉械屑械薪邪 芯斜邪胁械褕褌械褮邪</string>
+    <string name="pref_summary_notification_times_enabled">楔邪褭懈 芯斜邪胁械褕褌械褮邪 褋邪屑芯 褍 芯写褉械褣械薪邪 胁褉械屑械薪邪</string>
+    <string name="pref_header_audio">袟胁褍泻</string>
+    <string name="pref_header_generic">袨锌褕褌械</string>
+    <string name="pref_theme_dynamic">袞懈胁械 斜芯褬械</string>
+    <string name="pref_title_banglejs_phone_gps_enbale">袣芯褉懈褋褌懈 锌芯写邪褌泻械 袚袩小-邪 褌械谢械褎芯薪邪</string>
+    <string name="pref_title_banglejs_phone_gps_update_interval">袦械褣褍胁褉械屑械 芯褋胁械卸邪胁邪褮邪 锌芯写邪褌邪泻邪 袚袩小-邪</string>
+    <string name="search">孝褉邪卸懈</string>
+    <string name="appmanager_downloaded_to_cache">袩褉械褍蟹械褌芯 褬械 %s 褍 蟹邪褏胁邪褌</string>
+    <string name="pref_pair_bluetooth_calls_help_2">2. 袠写懈褌械 薪邪 锌芯褋褌邪胁泻械 袘谢褍褌褍褌邪 褌械谢械褎芯薪邪 懈 褍锌邪褉懈褌械 薪芯胁懈 褍褉械褣邪褬 泻芯褬懈 褯械 褋械 锌芯褬邪胁懈褌懈 (褋谢懈褔薪芯 懈屑械 褌褉械薪褍褌薪芯屑 褋邪褌褍, 邪谢懈 褋邪 薪邪褋褌邪胁泻芯屑, 薪锌褉. 鈥濧mazfit GTR 4 - AFC8鈥�.</string>
+    <string name="pref_display_caller_title">袩褉懈泻邪蟹褍褬 锌芯写邪褌泻械 芯 锌芯蟹薪邪薪懈泻褍</string>
+    <string name="pref_summary_banglejs_phone_gps_enbale">袣芯褉懈褋褌懈 锌芯写邪褌泻械 袚袩小-邪 褌械谢械褎芯薪邪 褉邪写懈 锌褉械锌懈褋懈胁邪褮邪 锌芯写邪褌邪泻邪 袚袩小-邪 褍褉械褣邪褬邪 斜邪薪谐谢邪</string>
+    <string name="pref_summary_receive_calls_watch">袨褋褌胁邪褉懈 懈 锌褉懈屑懈 锌芯蟹懈胁械 薪械锌芯褋褉械写薪芯 薪邪 褋邪褌褍</string>
+    <string name="bluetooth_calls_pairing">校锌邪褉懈胁邪褮械 锌芯蟹懈胁邪 袘谢褍褌褍褌芯屑</string>
+    <string name="pref_pair_bluetooth_calls_help_3">3. 袨屑芯谐褍褯懈褌械 锌芯褋褌邪胁泻械 锌芯蟹懈胁邪 袘谢褍褌褍褌芯屑 懈褋锌芯写.</string>
+    <string name="pref_pair_bluetooth_calls_help_warning">校袩袨袟袨袪袝袏袝: 校泻芯谢懈泻芯 芯屑芯谐褍褯懈褌械 锌芯蟹懈胁械 斜械蟹 褍锌邪褉懈胁邪褮邪 褋邪 写芯写邪褌薪芯屑 褋褌邪胁泻芯屑, 芯斜邪胁械褕褌械褮邪 锌芯蟹懈胁邪 屑芯卸写邪 薪械褯械 褉邪写懈褌懈 芯褔械泻懈胁邪薪芯.</string>
+    <string name="pref_summary_notifications_ignore_work_profile">袧械 褕邪褭懈 芯斜邪胁械褕褌械褮邪 懈蟹 锌褉芯谐褉邪屑邪 褉邪写薪芯屑 薪邪谢芯谐褍 褋邪褌邪</string>
+    <string name="pref_title_notification_cache_while_disconnected">袟邪褏胁邪褌懈 锌褉懈谢懈泻芯屑 锌褉械谢邪褋泻邪 芯锌褋械谐邪</string>
+    <string name="action_changelog">袛薪械胁薪懈泻 懈蟹屑械薪邪</string>
+    <string name="pref_title_general_reconnectonlytoconnected">袩褉械锌芯胁械卸懈 褋械 褋邪屑芯 褋邪 锌芯胁械蟹邪薪懈屑 褍褉械褣邪褬懈屑邪</string>
+    <string name="pref_summary_general_reconnectonlytoconnected">袩褉械锌芯胁械卸懈 褋械 褋邪屑芯 褋邪 锌芯胁械蟹邪薪懈屑 褍褉械褣邪褬懈屑邪 褍屑械褋褌芯 锌褉械锌芯胁械蟹懈胁邪褮懈屑邪 褋邪 褋胁懈屑 褍褉械褣邪褬懈屑邪</string>
+    <string name="pref_title_notification_wake_on_open">小邪屑芯斜褍褣械褮械 懈 芯褌泻褭褍褔邪胁邪褮械</string>
+    <string name="pref_summary_notification_wake_on_open">袩褉芯斜褍写懈 懈 芯褌泻褭褍褔邪褬 褍褉械褣邪褬 袗薪写褉芯懈写邪 泻邪写邪 褋锌褉邪胁懈褑邪 锌芯褕邪褭械 芯写蟹懈胁 袨孝袙袨袪袠. 小褌邪褮械 褬械 锌芯褌褉械斜薪芯 写邪 斜褍写械 锌芯胁械褉褭懈胁芯.</string>
+    <string name="bluetooth_calls">袩芯蟹懈胁懈 斜谢褍褌褍褌芯屑</string>
+    <string name="bluetooth_calls_settings">袩芯褋褌邪胁泻械 锌芯蟹懈胁邪 袘谢褍褌褍褌芯屑</string>
+    <string name="pref_display_caller_summary">袩褉懈泻邪蟹褍褬 懈屑械 懈 斜褉芯褬 褌械谢械褎芯薪邪 锌褉懈 写芯谢邪蟹薪懈屑 锌芯蟹懈胁懈屑邪</string>
+    <string name="pref_summary_notification_cache_while_disconnected">袩芯褕邪褭懈 锌褉芯锌褍褕褌械薪邪 芯斜邪胁械褕褌械褮邪 锌褉懈谢懈泻芯屑 锌褉械锌芯胁械蟹懈胁邪褮邪 褍褉械褣邪褬邪 薪邪泻芯薪 锌褉械褋褌邪薪泻邪 懈蟹谢邪蟹邪 懈蟹 芯锌褋械谐邪</string>
+    <string name="pref_title_banglejs_phone_gps_network_only">袣芯褉懈褋褌懈 懈褋泻褭褍褔懈胁芯 屑褉械卸褍 褉邪写懈 芯写褉械褣懈胁邪褮邪 锌芯谢芯卸邪褬邪</string>
+    <string name="pref_summary_banglejs_phone_gps_network_only">袣芯褉懈褋褌懈 褋邪屑芯 写芯褋褌邪胁褭邪褔邪 屑褉械卸械 褉邪写懈 芯写褉械褣懈胁邪褮邪 锌芯谢芯卸邪褬邪. 孝芯 褋屑邪褮褍褬械 锌芯褌褉芯褕褮褍 邪谢懈 褋屑邪褮褍褬械 褌邪褔薪芯褋褌. 袧械芯锌褏芯写薪芯 褬械 锌芯薪芯胁薪芯 锌芯胁械蟹懈胁邪褮械.</string>
+    <string name="busy_task_fetch_sports_summaries">袛芯斜邪胁褭邪褮械 褋锌芯褉褌褋泻懈褏 褋邪卸械褌邪泻邪</string>
+    <string name="busy_task_fetch_sports_details">袛芯斜邪胁褭邪褮械 褋锌芯褉褌褋泻懈褏 锌芯褬械写懈薪芯褋褌懈</string>
+    <string name="pref_canned_message">袩芯褉褍泻邪</string>
+    <string name="pref_time_sync">小邪屑芯褋褌邪谢薪芯 胁褉械屑械 褍褋泻邪褣懈胁邪褮邪</string>
+    <string name="pref_header_sound">袟胁褍泻</string>
+    <string name="pref_pair_bluetooth_calls_title">校锌邪褉懈 褉邪写懈 锌芯蟹懈胁邪 袘谢褍褌褍褌芯屑</string>
+    <string name="pref_pair_bluetooth_calls_summary">袣谢懈泻薪懈褌械 芯胁写械 褉邪写懈 锌芯泻褉械褌邪褮邪 锌芯褋褌褍锌泻邪 褍锌邪褉懈胁邪褮邪</string>
+    <string name="pref_pair_bluetooth_calls_help_title">袣邪泻芯 锌褉懈屑懈褌懈 锌芯蟹懈胁械 袘谢褍褌褍褌芯屑</string>
+    <string name="pref_pair_bluetooth_calls_help_summary">袪邪写懈 锌褉懈屑邪褮邪 锌芯蟹懈胁邪 袘谢褍褌褍褌芯屑 褌褉械斜邪 写邪 褍锌邪褉懈褌械 褋胁芯褬 褌械谢械褎芯薪芯屑 褋邪 写芯写邪褌薪芯屑 褋褌邪胁泻芯屑 褋邪褌邪.</string>
+    <string name="pref_pair_bluetooth_calls_help_1">1. 孝邪锌薪懈褌械 薪邪 写褍谐屑械 懈褋锌芯写 褉邪写懈 锌芯泻褉械褌邪褮邪 锌芯褋褌褍锌泻邪 褍锌邪褉懈胁邪褮邪.</string>
+    <string name="gps_glonass">袚袩小 + 袚袥袨袧袗小小</string>
+    <string name="appmanager_watchface_activate">袩芯泻褉械薪懈</string>
+    <string name="appmanager_app_start">袩芯褔薪懈</string>
+    <string name="appmanager_app_download">袩褉械褍蟹屑懈 褍 蟹邪褏胁邪褌</string>
+    <string name="appmanager_download_started">袩褉械褍蟹懈屑邪褮械 锌褉芯谐褉邪屑邪 褬械 锌芯褔械谢芯</string>
+    <string name="appmanager_download_app_error">袛械褋懈谢邪 褋械 褬械 谐褉械褕泻邪 锌褉械褍蟹懈屑邪褮邪 锌褉芯谐褉邪屑邪</string>
+    <string name="pref_summary_mb_intents">袨屑芯谐褍褯懈 褍泻芯谢懈泻芯 褍锌褉邪胁褭邪褮械 屑械写懈褬懈屑邪 薪械 褉邪写懈 褍 芯写褉械褣械薪懈屑 锌褉芯谐褉邪屑懈屑邪</string>
+    <string name="pref_title_nagivation_apps">袩褉芯谐褉邪屑懈 薪邪胁芯褣械褮邪</string>
+    <string name="pref_header_external_integrations">小锌芯褭褮械 褍褋褉械写褋褉械褣懈胁邪褮械</string>
+    <string name="pref_header_automations">小邪屑芯锌芯泻褉械褌邪褮邪</string>
+    <string name="pref_description_general">袩芯褔械褌邪泻, 褬械蟹懈泻, 芯斜谢邪褋褌, 锌芯谢芯卸邪褬</string>
+    <string name="pref_description_about_you">袛邪薪 褉芯褣械褮邪, 锌芯谢, 胁懈褋懈薪邪, 褌械卸懈薪邪, 褑懈褭械胁懈</string>
+    <string name="pref_description_notifications">袨斜邪胁械褕褌械褮邪 锌褉芯谐褉邪屑邪, 褋锌懈褋泻芯胁懈 锌芯卸械褭薪芯谐/薪械锌芯卸械褭薪芯谐</string>
+    <string name="pref_description_user_interface">袩褉械写屑械褌, 谐谢邪胁薪懈 蟹邪褋谢芯薪</string>
+    <string name="pref_description_dashboard">小锌褉邪胁懈褑械, 褍褉械褣邪褬懈 蟹邪 芯斜褍褏胁邪褌</string>
+    <string name="pref_description_automations">小邪屑芯褋褌邪谢薪懈 懈蟹胁芯蟹, 褋邪屑芯蟹邪褏胁邪褌</string>
+    <string name="pref_description_deprecated_functionalities">袩芯褋褌邪胁泻械 泻芯褬械 褯械 斜懈褌懈 褍泻谢芯褮械薪械 褍 斜褍写褍褯懈屑 懈蟹写邪褮懈屑邪</string>
+    <string name="pref_header_main_screen">袚谢邪胁薪懈 蟹邪褋谢芯薪</string>
+    <string name="pref_title_user_interface">袣芯褉懈褋薪懈褔泻芯 褋褍褔械褭械</string>
+    <string name="pref_title_notifications_ignore_work_profile">袟邪薪械屑邪褉懈 芯斜邪胁械褕褌械褮邪 芯 褉邪写薪芯屑 薪邪谢芯谐褍</string>
+    <string name="pref_message_privacy_mode_bodyonly">小邪泻褉懈褬 褋邪屑芯 褌械谢芯</string>
+    <string name="pref_show_changelog_summary">袩褉懈泻邪蟹褍褬 写薪械胁薪懈泻 懈蟹屑械薪邪 锌芯褔械胁褕懈 芯写 锌芯褋谢械写褮械谐 懈蟹写邪褮邪 薪邪泻芯薪 薪邪写谐褉邪写褮械 袚械褵械褌斜褉胁薪邪</string>
+    <string name="pref_show_changelog">袩褉懈泻邪蟹褍褬 写薪械胁薪懈泻 懈蟹屑械薪邪 锌褉懈 锌芯泻褉械褌邪褮褍</string>
+    <string name="pref_sleep_mode_schedule_summary">楔邪褭懈 锌芯写褋械褌薪懈泻 懈 褍褣懈 褍 薪邪褔懈薪 褉邪写邪 褋锌邪胁邪褮邪 褍 胁褉械屑械 谢械谐邪褮邪. 袧邪 蟹邪泻邪蟹邪薪芯屑 胁褉械屑械薪褍 褍褋褌邪褬邪褮邪, 谢邪褉屑邪 斜褍褣械褮邪 褯械 蟹胁芯薪懈褌懈.</string>
+    <string name="bottom_nav_dashboard">校锌褉邪胁褭邪褔泻邪 锌芯谢懈褑邪</string>
+    <string name="pref_header_deprecated_functionalities_warning">小谢械写械褯械 屑芯谐褍褯薪芯褋褌懈 褋褍 蟹邪褋褌邪褉械谢械 懈 斜懈褯械 褍泻谢芯褮械薪械 褍褋泻芯褉芯 懈蟹 屑械泻芯褌胁芯褉懈薪邪.
+\n校泻芯谢懈泻芯 褌褉械斜邪 写邪 芯屑芯谐褍褯懈褌械 薪械泻褍 芯写 褋谢械写械褯懈褏 屑芯谐褍褯薪芯褋褌懈 锌芯胁械卸懈褌械 褋械 褋邪 薪械泻懈屑 懈蟹 写褉褍卸懈薪械 褉邪蟹胁懈褬邪褔邪 锌褉芯谐褉邪屑邪.</string>
+    <string name="pref_deprecated_media_control_title">袟邪褋褌邪褉械谢芯 褍锌褉邪胁褭邪褮械 屑械写懈褬懈屑邪</string>
+    <string name="dashboard_settings">袩芯褋褌邪胁泻械 褍锌褉邪胁褭邪褔泻械 锌芯谢懈褑械</string>
+    <string name="pref_deprecated_media_control_summary">袩芯褕邪褭懈 薪邪褉械写斜械 褍锌褉邪胁褭邪褮邪 屑械写懈褬懈屑邪 泻邪芯 泻褭褍褔薪械 写芯谐邪褣邪褬械 褍屑械褋褌芯 褍锌褉邪胁褭邪褔邪 屑械写懈褬懈屑邪.</string>
+    <string name="pref_dashboard_cards_title">袩褉懈泻邪蟹褍褬 褋锌褉邪胁懈褑械 薪邪 泻邪褉褌懈褑邪屑邪</string>
+    <string name="pref_dashboard_cards_summary">袠褋褑褉褌邪胁邪褬 泻邪褉褌懈褑械 芯泻芯谢芯 褋锌褉邪胁懈褑邪 褍锌褉邪胁褭邪褔泻械 锌芯谢懈褑械</string>
+    <string name="pref_dashboard_widget_today_title">笑褉褌械卸 写械褬褋褌邪胁邪</string>
+    <string name="pref_dashboard_widget_today_24h_title">24-褉芯褔邪褋芯胁薪芯</string>
+    <string name="pref_dashboard_widget_double_size_title">袛胁芯褋褌褉褍泻邪 胁械谢懈褔懈薪邪</string>
+    <string name="pref_dashboard_widget_show_legend_title">袩褉懈泻邪蟹褍褬 谢械谐械薪写褍</string>
+    <string name="pref_dashboard_widget_goals_chart_title">笑褉褌械卸 褑懈褭械胁邪</string>
+    <string name="pref_dashboard_devices_to_include">校褉械褣邪褬懈 蟹邪 芯斜褍褏胁邪褌</string>
+    <string name="pref_dashboard_all_devices_summary">小褬械写懈褮褍褬 写械褬褋褌胁械薪械 锌芯写邪褌泻械 懈蟹 褋胁懈褏 写芯写邪褌懈褏 褍褉械褣邪褬邪 褍泻褍锌薪芯 薪邪 褍锌褉邪胁褭邪褔泻芯褬 锌芯谢懈褑懈</string>
+    <string name="pref_dashboard_widget_today_hr_interval_title">袦械褣褍胁褉械屑械 芯褌泻褍褑邪褬邪 褋褉褑邪</string>
+    <string name="dashboard_calendar_month_goals_reached_title">% 褍褋锌械褕薪芯褋褌懈 写芯 褑懈褭薪芯谐 斜褉芯褬邪 泻芯褉邪泻邪</string>
+    <string name="pref_dashboard_widget_today_hr_interval_summary">袣芯谢懈褔懈薪邪 屑懈薪褍褌邪 薪邪 褑褉褌械卸褍 锌褉懈泻邪蟹褍褬械 鈥炐叫狙埿敌叫锯�� 薪邪泻芯薪 褋胁邪泻芯谐 褍褋锌械褕薪芯谐 屑械褉械褮邪 褋褉褔邪薪懈褏 芯褌泻褍褑邪褬邪</string>
+    <string name="pref_title_navigation_forward">楔邪褭懈 薪邪胁芯褣械褮械 薪邪 褋邪褌</string>
+    <string name="pref_dashboard_first_title">袩褉胁芯 锌褉懈泻邪卸懈 褍锌褉邪胁褭邪褔泻褍 锌芯谢懈褑褍</string>
+    <string name="pref_dashboard_first_summary">袩褉懈泻邪蟹褍褬 褍锌褉邪胁褭邪褔泻褍 锌芯谢懈褑褍 锌褉懈谢懈泻芯屑 锌芯泻褉械褌邪褮邪 袚械褵械褌斜褉胁薪邪, 褍屑械褋褌芯 蟹邪褋谢芯薪邪 褍褉械褣邪褬邪</string>
+    <string name="pref_dashboard_widget_today_24h_summary">袩褉懈泻邪蟹褍褬 写械褬褋褌胁邪 褍 褬械写薪芯屑 24-褉芯褔邪褋芯胁薪芯屑 泻褉褍谐褍 褍屑械褋褌芯 褍 写胁邪 12-褔邪褋芯胁薪芯屑 泻褉褍谐褍</string>
+    <string name="pref_dashboard_all_devices_title">小胁懈 褍褉械褣邪褬懈</string>
+    <string name="pref_dashboard_select_devices_title">袨蟹薪邪褔懈褌械 褍褉械褣邪褬械...</string>
+    <string name="pref_dashboard_widget_double_size_summary">袨屑芯谐褍褯懈 褋锌褉邪胁懈褑懈 写邪 蟹邪褍蟹懈屑邪 写胁邪 褋褌褍斜褑邪 薪邪 褍锌褉邪胁褭邪褔泻芯褬 锌芯谢懈褑懈</string>
+    <string name="pref_dashboard_widget_show_legend_summary">袩褉懈泻邪蟹褍褬 谢械谐械薪写褍 懈褋锌芯写 褋锌褉邪胁懈褑械 泻芯褬邪 芯斜褬邪褕褮邪胁邪 斜芯褬械</string>
+    <string name="pref_dashboard_widget_today_upside_down_title">袩芯薪芯褯 薪邪 写薪褍</string>
+    <string name="pref_dashboard_widget_today_upside_down_summary">校 24-褉芯褔邪褋芯胁薪芯屑 锌褉懈泻邪蟹褍, 懈褋褑褉褌邪胁邪褬 锌芯薪芯褯 薪邪 写薪褍, 锌谢邪写薪械 薪邪 胁褉褏褍 褑褉褌械卸邪</string>
+    <string name="pref_title_send_app_notifications">楔邪褭懈 芯斜邪胁械褕褌械褮邪</string>
+    <string name="pref_summary_send_app_notifications">楔邪褭懈 芯斜邪胁械褕褌械褮邪 锌褉芯谐褉邪屑邪 薪邪 褍褉械褣邪褬</string>
+    <string name="pref_dashboard_widgets_order_summary">袨蟹薪邪褔懈褌械 泻芯褬械 褋锌褉邪胁懈褑械 褋褍 芯屑芯谐褍褯械薪械 懈 褍 泻芯褬械屑 褉械写芯褋谢械写褍 写邪 褋械 锌褉懈泻邪蟹褍褬褍 薪邪 褍锌褉邪胁褭邪褔泻芯褬 锌芯谢懈褑懈</string>
+    <string name="pref_dashboard_widget_settings">袩芯褋褌邪胁泻械 褋锌褉邪胁懈褑械</string>
+    <string name="text_receiver_activity_title">袩芯褕邪褭懈 锌懈褋屑芯 薪邪 褍褉械褣邪褬</string>
+    <string name="permission_request">%1$s 芯屑芯谐褍褯邪胁邪 褋谢邪褮械 锌芯褉褍泻邪 懈 芯褋褌邪谢懈褏 锌芯写邪褌邪泻邪 褋邪 袗薪写褉芯懈写邪 薪邪 褍褉械褣邪褬. 袪邪写懈 褌芯谐邪 褬械 锌芯褌褉械斜薪芯 写芯蟹胁芯谢懈褌懈 锌褉懈褋褌褍锌 锌芯写邪褑懈屑邪 斜械蟹 泻芯褬懈褏 屑芯卸写邪 薪械褯械 褉邪写懈褌懈 胁邪褭邪薪芯.
+\n
+\n袩褉械写褋褌邪胁懈褯械 袙邪屑 褋械 薪械泻芯谢懈泻芯 锌褉芯蟹芯褉褔懈褯邪 袗薪写褉芯懈写邪 褋邪 蟹邪褏褌械胁芯屑 蟹邪 写芯蟹胁芯褭邪胁邪褮械.
+\n
+\n袦芯谢懈屑, 褌邪锌薪懈褌械 鈥�%2$s鈥� 褉邪写懈 薪邪褋褌邪胁泻邪.</string>
+    <string name="pref_summary_debug">楔邪褭懈 蟹邪褏褌械胁 蟹邪 懈褋锌褉邪胁泻褍 谐褉械褕邪泻邪 薪邪 褍褉械褣邪褬 啸褍邪胁械褬</string>
+    <string name="pref_dashboard_select_devices_summary">小褬械写懈褮褍褬 写械褬褋褌胁械薪械 锌芯写邪褌泻械 懈蟹 薪邪褉芯褔懈褌懈褏 褍褉械褣邪褬邪 褍泻褍锌薪芯 薪邪 褍褉械褣懈胁邪褔泻芯褬 锌芯谢懈褑懈</string>
+    <string name="silent_mode_normal_silent">袨斜懈褔薪芯 / 褌懈褏芯</string>
+    <string name="sony_anc_optimize_confirmation_description">袣芯褉懈褋褌懈褌械 褋谢褍褕邪谢懈褑械 泻邪芯 褕褌芯 斜懈 懈 芯斜懈褔薪芯. 校泻芯谢懈泻芯 褍褋谢芯胁懈 薪芯褕械褮邪 懈谢懈 锌褉懈褌懈褋邪泻 胁邪蟹写褍褏邪 芯写褋褌褍锌械, 锌芯泻褉械薪懈褌械 锌褉懈谢邪谐芯褣邪胁邪褮械 锌芯薪芯胁芯.</string>
+    <string name="silent_mode_normal_vibrate">袨斜懈褔薪芯 / 褌褉械褕褮邪</string>
+    <string name="femometer_measurement_mode_normal">袨斜懈褔邪薪 薪邪褔懈薪 (60s-90s)</string>
+    <string name="device_name_bicycle_sensor">袩褉懈褬械屑薪懈泻 斜懈褑懈泻谢邪</string>
+    <string name="femometer_measurement_mode_quick">袘褉蟹懈 薪邪褔懈薪 (30褋)</string>
+    <string name="preferences_miband_1_2_settings" tools:ignore="TypographyFractions">袩芯褋褌邪胁泻械 袦懈 袘邪薪写邪 1/2</string>
+    <string name="preferences_miband_1_2_warning">校锌芯蟹芯褉械褮械: 芯胁械 锌芯褋褌邪胁泻械 褋械 锌褉懈屑械褮褍褬褍 褋邪屑芯 薪邪 袦懈 袘邪薪写 1 懈 2.</string>
+    <string name="pref_summary_casio_alert_email">袥邪褉屑邪 (褌褉械褕褮邪/锌懈褋邪泻) 芯斜邪胁械褕褌械褮邪 械-锌芯褕褌械</string>
+    <string name="pref_summary_casio_alert_call">袥邪褉屑邪 (褌褉械褕褮邪/锌懈褋邪泻) 写芯谢邪蟹薪懈褏 锌芯蟹懈胁邪</string>
+    <string name="pref_title_casio_alert_call">袥邪褉屑邪 写芯谢邪蟹薪芯谐 锌芯蟹懈胁邪</string>
+    <string name="pref_title_casio_alert_email">袥邪褉屑邪 芯斜邪胁械褕褌械褮邪 械-锌芯褕褌械</string>
+    <string name="pref_title_sync_birthdays">校褋泻谢邪写懈 褉芯褣械薪写邪薪械</string>
+    <string name="choose_device">袠蟹邪斜械褉懈褌械 褍褉械褣邪褬</string>
+    <string name="no_supported_devices_found">袧械屑邪 锌褉芯薪邪褣械薪懈褏 锌芯写褉卸邪薪懈褏 褍褉械褣邪褬邪</string>
+    <string name="heartrate_bpm_135">135 斜锌屑</string>
+    <string name="heartrate_bpm_140">140 斜锌屑</string>
+    <string name="heartrate_bpm_150">150 斜锌屑</string>
+    <string name="busy_task_fetch_sports_details_interrupted">袛芯斜邪胁褭邪褮械 锌芯写邪褌邪泻邪 芯 褋锌芯褉褌褍 褬械 锌褉械泻懈薪褍褌芯</string>
+    <string name="heartrate_bpm_50">50 斜锌屑</string>
+    <string name="heartrate_bpm_130">130 斜锌屑</string>
+    <string name="heartrate_bpm_185">185 斜锌屑</string>
+    <string name="heartrate_bpm_120">120 斜锌屑</string>
+    <string name="heartrate_bpm_175">175 斜锌屑</string>
+    <string name="heartrate_bpm_195">195 斜锌屑</string>
+    <string name="pref_contacts_title">袩芯蟹薪邪薪懈褑懈</string>
+    <string name="heartrate_bpm_165">165 斜锌屑</string>
+    <string name="heartrate_bpm_205">205 斜锌屑</string>
+    <string name="pref_summary_sync_birthdays">校褋泻谢邪写懈 褉芯褣械薪写邪薪械 锌芯蟹薪邪薪懈泻邪 褍蟹 写芯谐邪褣邪褬械 泻邪谢械薪写邪褉邪</string>
+    <string name="heartrate_bpm_145">145 斜锌屑</string>
+    <string name="pref_agps_update_time">袙褉械屑械 芯褋胁械卸邪胁邪褮邪 袗袚袩小-邪</string>
+    <string name="pref_agps_expire_time">袙褉械屑械 懈褋褌械泻邪 袗袚袩小-邪</string>
+    <string name="pref_morning_updates_title">袌褍褌邪褉褮邪 芯褋胁械卸械褮邪</string>
+    <string name="pref_workout_keep_screen_on_summary">袟邪褋谢芯薪 褯械 斜懈褌懈 褍泻褭褍褔械薪 锌褉懈谢懈泻芯屑 胁械卸斜邪褮邪, 邪 芯褋胁械褌褭械褮械 褯械 斜懈褌懈 锌褉懈谢邪谐芯褣械薪芯 褋褌邪谢薪芯屑 锌褉懈泻邪蟹褍 褌械泻褍褯懈褏 锌芯写邪褌邪泻邪 芯 胁械卸斜邪褮褍</string>
+    <string name="heartrate_bpm_45">45 斜锌屑</string>
+    <string name="pref_header_navigation">袧邪胁芯褣械褮械</string>
+    <string name="pref_camera_remote_title">袛邪褭懈薪褋泻懈 褍锌褉邪胁褭邪褔 泻邪屑械褉械</string>
+    <string name="pref_summary_preview_message_in_title">袩褉懈泻邪蟹褍褬 锌褉械谐谢械写 锌芯褉褍泻械 褍 薪邪褋谢芯胁褍 芯斜邪胁械褕褌械褮邪 芯写芯斜褉械薪芯谐 褍褉械褣邪褬械屑</string>
+    <string name="pixoo_power_saving_summary">袟邪褋谢芯薪 褯械 褋械 懈褋泻褭褍褔懈褌懈 薪邪泻芯薪 褕褌芯 屑懈泻褉芯褎芯薪 褍褌胁褉写懈 褌懈褕懈薪褍 薪械泻芯 胁褉械屑械</string>
+    <string name="clap_hands_to_wakeup_device">袩褭械褋薪懈 褕邪泻邪屑邪 褉邪写懈 褍泻褭褍褔懈胁邪褮邪 蟹邪褋谢芯薪邪</string>
+    <string name="busy_task_fetch_temperature">袛芯斜邪胁褭邪屑 锌芯写邪褌泻械 芯 褌芯锌谢芯褌懈</string>
+    <string name="pref_contacts_summary">袩芯写械褋懈 锌芯蟹薪邪薪懈泻械 薪邪 褋邪褌褍</string>
+    <string name="heartrate_bpm_40">40 斜锌屑</string>
+    <string name="proprietary_app_warning">袨胁邪 屑芯谐褍褯薪芯褋褌 褬械 褍褋谢芯胁褭械薪邪 褍谐褉邪写褮芯屑 胁谢邪褋薪懈褔泻芯谐 锌褉芯谐褉邪屑邪</string>
+    <string name="femometer_measurement_mode_precise">孝邪褔邪薪 薪邪褔懈薪 (3屑懈薪)</string>
+    <string name="clap_hands_to_wakeup_device_summary">袩褭械褋泻邪褮械 锌芯薪芯胁芯 褯械 懈褋泻褭褍褔懈褌懈 蟹邪褋谢芯薪</string>
+    <string name="pref_wearing_tone">袟胁褍泻 薪芯褕褮械</string>
+    <string name="pref_wearing_tone_summary">袨写褋胁懈褉邪 蟹胁褍泻 锌褉懈谢懈泻芯屑 褍屑械褌邪褮邪 褋谢褍褕邪谢懈褑械</string>
+    <string name="pref_workout_detection_ask_first_summary">袟邪褏褌械胁邪 锌芯褌胁褉写褍 薪邪 褋邪褌褍 锌褉懈谢懈泻芯屑 锌褉械锌芯蟹薪邪胁邪褮邪 胁械卸斜械</string>
+    <string name="pref_workout_detection_time">袦懈薪褍褌懈 写械褬褋褌胁邪 锌褉械 锌褉械锌芯蟹薪邪胁邪褮邪</string>
+    <string name="pref_workout_detection_time_summary">袣芯谢懈褔懈薪邪 屑懈薪褍褌邪 蟹邪 胁械卸斜邪褮械 锌褉械 褮械谐芯胁芯谐 锌褉械锌芯蟹薪邪胁邪褮邪</string>
+    <string name="pref_morning_updates_categories_title">袙褉褋褌械 褬褍褌邪褉褮懈褏 芯褋胁械卸械褮邪</string>
+    <string name="pref_morning_updates_categories_summary">小锌懈褋邪泻 胁褉褋褌邪 蟹邪 锌褉懈泻邪蟹 褋胁邪泻芯谐 褬褍褌褉邪</string>
+    <string name="busy_task_fetch_debug_logs">袛芯斜邪胁褭邪屑 写薪械胁薪懈泻 懈褋锌褉邪胁泻懈 谐褉械褕邪泻邪</string>
+    <string name="heartrate_bpm_100">100 斜锌屑</string>
+    <string name="heartrate_bpm_105">105 斜锌屑</string>
+    <string name="heartrate_bpm_110">110 斜锌屑</string>
+    <string name="heartrate_bpm_115">115 斜锌屑</string>
+    <string name="heartrate_bpm_125">125 斜锌屑</string>
+    <string name="title_activity_set_contacts">袩芯写械褋懈 锌芯蟹薪邪薪懈泻械</string>
+    <string name="prefs_wearmode">袧邪褔懈薪 薪芯褕褮械</string>
+    <string name="pref_summary_casio_alert_calendar">袥邪褉屑邪 (褌褉械褕褮邪/锌懈褋邪泻) 芯斜邪胁械褕褌械褮邪 泻邪谢械薪写邪褉邪</string>
+    <string name="pref_title_goal_secondary">小谢械写械褯懈 褑懈褭</string>
+    <string name="pref_touch_tone">袟胁褍泻 写芯写懈褉邪</string>
+    <string name="pref_touch_tone_summary">袨写褋胁懈褉邪 蟹胁褍泻 锌褉懈谢懈泻芯屑 写芯写懈褉邪 褋谢褍褕邪谢懈褑械</string>
+    <string name="agps_status_current">袪邪写懈</string>
+    <string name="agps_status_error">袚褉械褕泻邪</string>
+    <string name="pref_morning_updates_summary">袩褉懈泻邪蟹褍褬 芯褋胁械卸械褮邪 褋胁邪泻芯谐邪 褬褍褌褉邪</string>
+    <string name="pref_workout_detection_enabled">袩褉械锌芯蟹薪邪胁邪褮械 褬械 芯屑芯谐褍褯械薪芯</string>
+    <string name="pref_workout_detection_enabled_summary">袨屑芯谐褍褯懈 褋邪屑芯锌褉械锌芯蟹薪邪胁邪褮械 芯胁芯谐 胁械卸斜邪褮邪</string>
+    <string name="pref_workout_detection_ask_first">袩褉械褌褏芯写薪芯 屑械 锌懈褌邪褬</string>
+    <string name="busy_task_fetch_spo2_data">袛芯斜邪胁褭邪屑 锌芯写邪褌泻械 芯 小锌袨2</string>
+    <string name="busy_task_fetch_hr_data">袛芯斜邪胁褭邪屑 锌芯写邪褌泻械 芯 芯褌泻褍褑邪褬懈屑邪 褋褉褑邪</string>
+    <string name="busy_task_fetch_sleep_data">袛芯斜邪胁褭邪屑 锌芯写邪褌泻械 芯 褋锌邪胁邪褮褍</string>
+    <string name="busy_task_fetch_sleep_respiratory_rate_data">袛芯斜邪胁褭邪屑 锌芯写邪褌泻械 芯 斜褉蟹懈薪懈 写懈褋邪褮邪 褍 褋薪褍</string>
+    <string name="busy_task_fetch_statistics">袛芯斜邪胁褭邪屑 锌芯写邪褌泻械</string>
+    <string name="pref_title_preview_message_in_title">袩褉懈泻邪蟹褍褬 锌褉械谐谢械写 锌芯褉褍泻械 褍 薪邪褋谢芯胁褍</string>
+    <string name="pref_summary_casio_alert_other">袥邪褉屑邪 (褌褉械褕褮邪/锌懈褋邪泻) 芯斜邪胁械褕褌械褮邪 芯褋褌邪谢懈褏 胁褉褋褌邪</string>
+    <string name="pref_title_casio_alert_sms">袥邪褉屑邪 芯斜邪胁械褕褌械褮邪 小袦小-邪</string>
+    <string name="pref_summary_casio_alert_sms">袥邪褉屑邪 (褌褉械褕褮邪/锌懈褋邪泻) 芯斜邪胁械褕褌械褮邪 小袦小-邪 (锌懈褋邪薪懈褏 锌芯褉褍泻邪)</string>
+    <string name="pref_title_casio_alert_other">袥邪褉屑邪 芯褋褌邪谢懈褏 芯斜邪胁械褕褌械褮邪</string>
+    <string name="pref_camera_remote_summary">袨屑芯谐褍褯邪胁邪 褋邪褌褍 写邪 芯泻懈薪械 泻邪屑械褉褍 褌械谢械褎芯薪邪</string>
+    <string name="heartrate_bpm_155">155 斜锌屑</string>
+    <string name="zetime_date_format_1">袚袚/袦袦/袛袛</string>
+    <string name="pref_workout_keep_screen_on_title">袛褉卸懈 蟹邪褋谢芯薪 褍泻褭褍褔械薪懈屑 锌褉懈谢懈泻芯屑 胁械卸斜邪褮邪</string>
+    <string name="pref_title_casio_alert_calendar">袥邪褉屑邪 芯斜邪胁械褕褌械褮邪 泻邪谢械薪写邪褉邪</string>
+    <string name="pref_agps_status">小褌邪褮械 袗袚袩小-邪</string>
+    <string name="agps_status_missing">袧械写芯褋褌邪褬械</string>
+    <string name="agps_status_pending">袧邪 褔械泻邪褮褍</string>
+    <string name="busy_task_fetch_stress_data">袛芯斜邪胁褭邪屑 锌芯写邪褌泻械 芯 褋褌褉械褋褍</string>
+    <string name="title_activity_contact_details">袩芯写邪褑懈 芯 锌芯蟹薪邪薪懈泻褍</string>
+    <string name="contact_delete_confirm_title">袠蟹斜褉懈褕懈 锌芯蟹薪邪薪懈泻邪</string>
+    <string name="contact_delete_confirm_description">袛邪 谢懈 锌芯褍蟹写邪薪芯 卸械谢懈褌械 懈蟹斜褉懈褋邪褌懈 鈥�%1$s鈥�?</string>
+    <string name="femometer_measurement_mode_title">袧邪褔懈薪 屑械褉械褮邪</string>
+    <string name="busy_task_fetch_pai_data">袛芯斜邪胁褭邪屑 锌芯写邪褌泻械 芯 袩袗袠</string>
+    <string name="sleep_colored_stats_rem">袪袝袦</string>
+    <string name="armenian">袌械褉屑械薪褋泻懈</string>
+    <string name="alarm_smart_wakeup_interval">袦械褣褍胁褉械屑械 锌邪屑械褌薪芯谐 斜褍褣械褮邪:</string>
+    <string name="gpx_route_upload_failed">袧懈褋邪屑 褍褋锌械芯 芯褌锌褉械屑懈褌懈 锌褍褌邪褮褍 袚袩袠泻褋-邪</string>
+    <string name="minutes_2">2 屑懈薪褍褌邪</string>
+    <string name="minutes_3">3 屑懈薪褍褌邪</string>
+    <string name="minutes_4">4 屑懈薪褍褌邪</string>
+    <string name="minutes_20">20 屑懈薪褍褌邪</string>
+    <string name="minutes_60">60 屑懈薪褍褌邪</string>
+    <string name="stress_average">袩褉芯褋械褔薪芯</string>
+    <string name="sleep_colored_stats_light">袥邪泻</string>
+    <string name="abstract_chart_fragment_kind_awake_sleep">袘褍写邪薪</string>
+    <string name="sleep_colored_stats_deep">袛褍斜芯泻</string>
+    <string name="silent_mode_vibrate_silent">孝褉械褕褮邪 / 褌懈褏芯</string>
+    <string name="prefs_phone_silent_mode">孝懈褏懈 褉邪写 褌械谢械褎芯薪邪</string>
+    <string name="prefs_device_name">袠屑械 褍褉械褣邪褬邪</string>
+    <string name="prefs_password_4_digits_0_to_9_summary">袥芯蟹懈薪泻邪 屑芯褉邪 懈屑邪褌懈 4 斜褉芯褬邪, 泻芯褉懈褋褌械褯懈 褋邪屑芯 斜褉芯褬械胁械</string>
+    <string name="do_not_disturb_lift_wrist_summary">小邪屑芯 褍泻芯谢懈泻芯 褬械 芯屑芯谐褍褯械薪芯 褍泻褭褍褔懈胁邪褮械 蟹邪褋谢芯薪邪 锌褉懈 锌芯写懈蟹邪褮褍</string>
+    <string name="english_au">袝薪谐谢械褋泻懈 (袗褍褋褌褉邪谢懈褬邪)</string>
+    <string name="english_gb">袝薪谐谢械褋泻懈 (校褬械写懈褮械薪芯 泻褉邪褭械胁褋褌胁芯)</string>
+    <string name="english_us">袝薪谐谢械褋泻懈 (小褬械写懈褮械薪械 写褉卸邪胁械)</string>
+    <string name="spanish_us">楔锌邪薪褋泻懈 (小褬械写懈褮械薪械 写褉卸邪胁械)</string>
+    <string name="common_symbols">校芯斜懈褔邪褬械薪懈 蟹薪邪褑懈</string>
+    <string name="latvian">袥械褌芯薪褋泻懈</string>
+    <string name="serbian">小褉斜褋泻懈</string>
+    <string name="weekstepschart_steps_a_week_or_month">小械写屑懈褔薪懈/屑械褋械褔薪懈 泻芯褉邪褑懈</string>
+    <string name="weeksleepchart_sleep_a_week_or_month">小械写屑懈褔薪芯/屑械褋械褔薪芯 褋锌邪胁邪褮械</string>
+    <string name="charts_legend_stress_average">袩褉芯褋械褔邪薪 褋褌褉械褋</string>
+    <string name="charts_legend_spo2_average">袩褉芯褋械褔邪薪 泻懈褋械芯薪懈泻 褍 泻褉胁懈</string>
+    <string name="standing_time">袙褉械屑械 屑懈褉芯胁邪褮邪</string>
+    <string name="activity_type_stair_stepper">袩械褮邪褮械 褋褌械锌械薪懈褑邪屑邪</string>
+    <string name="activity_type_horizontal_bar">袙芯写芯褉邪胁薪邪 褌褉邪泻邪</string>
+    <string name="activity_type_parallel_bar">校锌芯褉械写薪邪 褌褉邪泻邪</string>
+    <string name="activity_type_cooldown">啸谢邪褣械褮械</string>
+    <string name="activity_type_sit_ups">孝褉斜褍褕褮邪褑懈</string>
+    <string name="activity_type_pull_ups">袟谐懈斜芯胁懈</string>
+    <string name="activity_type_high_jump">小泻芯泻 褍 胁懈褋鈥�</string>
+    <string name="activity_type_belly_dance">孝褉斜褍褕薪懈 锌谢械褋</string>
+    <string name="activity_type_horse_riding">袌邪褏邪褮械 泻芯褮邪</string>
+    <string name="activity_type_archery">小褌褉械褭邪褕褌胁芯</string>
+    <string name="fwapp_install_device_not_supported">袧懈褋邪屑 褍褋锌械芯 褍谐褉邪写懈褌懈 写邪褌芯褌械泻褍, 褍褉械褣邪褬 薪懈褬械 锌芯写褉卸邪薪.</string>
+    <string name="gpx_route_upload_complete">袨褌锌褉械屑邪褮械 锌褍褌邪褮械 袚袩袠泻褋邪 褬械 蟹邪胁褉褕械薪芯</string>
+    <string name="prefs_shortcut_cards">袣邪褉褌懈褑械 锌褉械褔懈褑邪</string>
+    <string name="activity_type_push_ups">小泻谢械泻芯胁懈</string>
+    <string name="activity_type_long_jump">小泻芯泻 褍 写邪褭鈥�</string>
+    <string name="activity_type_javelin">袕懈谢懈褌</string>
+    <string name="activity_type_ballet">袘邪谢械褌</string>
+    <string name="alarm_smart_wakeup_interval_default">5 屑懈薪褍褌邪</string>
+    <string name="updatefirmwareoperation_updateproblem_free_space">校褉械褣邪褬 薪械屑邪 写芯胁芯褭薪芯 褋谢芯斜芯写薪芯谐 屑械褋褌邪</string>
+    <string name="english_in">袝薪谐谢械褋泻懈 (袠薪写懈褬邪)</string>
+    <string name="dateformat_day_month">袛邪薪, 屑械褋械褑</string>
+    <string name="miband2_prefs_timeformat">袦懈2: 芯斜谢懈泻 胁褉械屑械薪邪</string>
+    <string name="dnd_all_day">笑械谢懈 写邪薪</string>
+    <string name="activity_type_rem_sleep">小锌邪胁邪褮械 袪袝袦</string>
+    <string name="activity_type_indoor_running">孝褉褔邪褮械 褍薪褍褌褉邪</string>
+    <string name="activity_type_karate">袣邪褉邪褌械</string>
+    <string name="activity_type_kendo">袣械薪写芯</string>
+    <string name="spanish_es">楔锌邪薪褋泻懈 (楔锌邪薪懈褬邪)</string>
+    <string name="manual">袪褍褔薪芯</string>
+    <string name="spanish_mx">楔锌邪薪褋泻懈 (袦械泻褋懈泻芯)</string>
+    <string name="activity_prefs_height_inches">袙懈褋懈薪邪 褍 懈薪褔懈屑邪</string>
+    <string name="updatefirmwareoperation_updateproblem_low_battery">袘邪褌械褉懈褬邪 褬械 锌褉械胁懈褕械 锌褉邪蟹薪邪</string>
+    <string name="english_ca">袝薪谐谢械褋泻懈 (袣邪薪邪写邪)</string>
+    <string name="active_time">袙褉械屑械 写械褬褋褌胁邪</string>
+    <string name="pai_chart_per_month">屑械褋械褔薪懈 袩袗袠</string>
+    <string name="pai_plus_num">+%d</string>
+    <string name="notif_battery_full">斜邪褌械褉懈褬邪 %1$s 褬械 锌褍薪邪</string>
+    <string name="notif_battery_full_title">袘邪褌械褉懈褬邪 褋锌褉邪胁懈褑械 褬械 锌褍薪邪!</string>
+    <string name="authentication_failed_check_key">袧械褍褋锌械谢邪 褬械 锌褉懈褬邪胁邪, 屑芯谢懈屑 锌褉芯胁械褉懈褌械 泻褭褍褔</string>
+    <string name="pai_chart_per_week">褋械写屑懈褔薪懈 袩袗袠</string>
+    <string name="gpx_route_upload_in_progress">袨褌锌褉械屑邪屑 锌褍褌邪褮褍 袚袩袠泻褋邪</string>
+    <string name="activity_prefs_goals">笑懈褭械胁懈</string>
+    <string name="pref_do_not_disturb_not_wear">袧械 褍蟹薪械屑懈褉邪胁邪褬 泻邪写邪 褋械 薪械 薪芯褋懈</string>
+    <string name="stop">小褌邪薪懈</string>
+    <string name="activity_type_dumbbell">孝褉邪锌褍谢懈薪邪</string>
+    <string name="prefs_shortcut_cards_summary">袣邪褉褌懈褑械 锌褉械褔懈褑邪 泻芯褬械 褋械 锌褉懈泻邪蟹褍褬褍 锌褉懈谢懈泻芯屑 锌褉械胁谢邪褔械褮邪 褍写械褋薪芯 薪邪 谢懈褑褍 褋邪褌邪. 袣邪写邪 锌褉芯谐褉邪屑 褉邪写懈, 锌芯褋褌邪胁泻邪 薪械屑邪 褍褌懈褑邪褬邪 薪邪 褋邪屑芯褋褌胁芯褉械薪械 泻邪褉褌懈褑械.</string>
+    <string name="georgian">袚褉褍蟹懈薪褋泻懈</string>
+    <string name="num_min">%d 屑懈薪</string>
+    <string name="contact_no_free_slots_description">袨胁邪褬 褍褉械褣邪褬 薪械屑邪 褋谢芯斜芯写薪懈褏 屑械褋褌邪 蟹邪 锌芯蟹薪邪薪懈泻械 (褍泻褍锌薪芯 屑械褋褌邪: %1$s)</string>
+    <string name="prefs_heartrate_alert_active_high_threshold">袟邪写褉褕泻邪 谢邪褉屑械 斜褉蟹芯谐 褉邪写邪 褋褉褑邪</string>
+    <string name="user_feedback_set_settings_ok">袩芯褋褌邪胁泻械 褋褍 锌芯褋谢邪褌械 薪邪 褍褉械褣邪褬.</string>
+    <string name="hr_resting">袨写屑邪褉邪褮械</string>
+    <string name="hr_maximum">袧邪褬胁懈褕械</string>
+    <string name="hr_minimum">袧邪褬薪懈卸械</string>
+    <string name="hr_average">袩褉芯褋械褔薪芯</string>
+    <string name="dateformat_month_day">袦械褋械褑, 写邪薪</string>
+    <string name="minutes_240">240 屑懈薪褍褌邪</string>
+    <string name="minutes_255">255 屑懈薪褍褌邪</string>
+    <string name="minutes_6">6 屑懈薪褍褌邪</string>
+    <string name="minutes_7">7 屑懈薪褍褌邪</string>
+    <string name="minutes_8">8 屑懈薪褍褌邪</string>
+    <string name="minutes_9">9 屑懈薪褍褌邪</string>
+    <string name="minutes_15">15 屑懈薪褍褌邪</string>
+    <string name="minutes_45">45 屑懈薪褍褌邪</string>
+    <string name="minutes_75">75 屑懈薪褍褌邪</string>
+    <string name="minutes_90">90 屑懈薪褍褌邪</string>
+    <string name="minutes_120">120 屑懈薪褍褌邪</string>
+    <string name="minutes_150">150 屑懈薪褍褌邪</string>
+    <string name="minutes_180">180 屑懈薪褍褌邪</string>
+    <string name="minutes_210">210 屑懈薪褍褌邪</string>
+    <string name="status">小褌邪褮械</string>
+    <string name="activity_prefs_date_birth">袛邪薪 褉芯褣械褮邪</string>
+    <string name="pref_title_calendar_lookahead">袘褉芯褬 薪邪写芯谢邪蟹械褯懈褏 写邪薪邪</string>
+    <string name="pref_summary_calendar_lookahead">校褋泻谢邪写懈 写芯 %1s</string>
+</resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml
new file mode 100644
index 0000000..1662e30
--- /dev/null
+++ b/app/src/main/res/values-sv/strings.xml
@@ -0,0 +1,371 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <string name="application_name_generic">Gadgetbridge</string>
+    <string name="title_activity_controlcenter_generic">Gadgetbridge</string>
+    <string name="action_settings">Inst盲llningar</string>
+    <string name="action_quit">Avsluta</string>
+    <string name="action_donate">Donera</string>
+    <string name="controlcenter_fetch_activity_data">Synkronisera</string>
+    <string name="controlcenter_find_device">Hitta f枚rsvunnen enhet</string>
+    <string name="controlcenter_take_screenshot">Sk盲rmdump</string>
+    <string name="controlcenter_change_led_color">脛ndra LED-f盲rg</string>
+    <string name="controlcenter_change_fm_frequency">脛ndra FM-frekvens</string>
+    <string name="controlcenter_connect">Anslut鈥�</string>
+    <string name="controlcenter_disconnect">Koppla ifr氓n</string>
+    <string name="controlcenter_delete_device">Ta bort enhet</string>
+    <string name="controlcenter_delete_device_name">Ta bort %1$s</string>
+    <string name="controlcenter_delete_device_dialogmessage">Detta kommer att ta bort enheten och radera all tillh枚rande information!</string>
+    <string name="controlcenter_navigation_drawer_open">脰ppna navigationspanelen</string>
+    <string name="controlcenter_navigation_drawer_close">St盲ng navigationspanelen</string>
+    <string name="controlcenter_snackbar_need_longpress">Tryck l盲nge p氓 kortet f枚r att koppla ifr氓n</string>
+    <string name="controlcenter_snackbar_disconnecting">Kopplar ifr氓n</string>
+    <string name="controlcenter_snackbar_connecting">Ansluter鈥�</string>
+    <string name="controlcenter_snackbar_requested_screenshot">Tar en sk盲rmdump av enheten</string>
+    <string name="controlcenter_calibrate_device">Kalibrera enhet</string>
+    <string name="title_activity_debug">Avlusa</string>
+    <string name="debugactivity_really_factoryreset_title">Vill du verkligen 氓terst盲lla till fabriksinst盲llningar\?</string>
+    <string name="debugactivity_really_factoryreset">En 氓terst盲llning till fabriksinst盲llningar kommer att radera all data fr氓n den anslutna enheten (om detta st枚ds). Enheter fr氓n Xiaomi/Huami kommer ocks氓 att f氓 nya/盲ndrade Bl氓tands- och MAC-adresser, och kommer att synas som nya enheter i Gadgetbridge.</string>
+    <string name="title_activity_appmanager">Apphanterare</string>
+    <string name="appmanager_cached_watchapps_watchfaces">Appar i cachen</string>
+    <string name="appmanager_installed_watchapps">Installerade appar</string>
+    <string name="appmanager_installed_watchfaces">Installerade urtavlor</string>
+    <string name="appmananger_app_delete">Ta bort</string>
+    <string name="appmananger_app_delete_cache">Ta bort och rensa fr氓n cacheminnet</string>
+    <string name="appmananger_app_reinstall">Installera om</string>
+    <string name="appmanager_app_openinstore">S枚k i Pebble appstore</string>
+    <string name="appmanager_health_activate">Aktivera</string>
+    <string name="appmanager_health_deactivate">Avaktivera</string>
+    <string name="appmanager_weather_activate">Aktivera systemets v盲derapp</string>
+    <string name="appmanager_weather_deactivate">Avaktivera systemets v盲derapp</string>
+    <string name="appmanager_weather_install_provider">Installera v盲der appen</string>
+    <string name="app_move_to_top">L盲gg 枚verst</string>
+    <string name="blacklist_all_for_notifications">Svartlista alla f枚r aviseringar</string>
+    <string name="title_activity_calblacklist">Svartlistade kalendrar</string>
+    <string name="title_activity_fw_app_insaller">FW/App-installerare</string>
+    <string name="pref_chart_heartrate_color_red">R枚d</string>
+    <string name="mi2_prefs_button_press_broadcast_default_value" translatable="false">nodomain.freeyourgadget.gadgetbridge.ButtonPressed</string>
+    <string name="appmanager_hrm_activate">Aktivera pulsm盲tare</string>
+    <string name="appmanager_hrm_deactivate">Avaktivera pulsm盲tare</string>
+    <string name="app_configure">Konfigurera</string>
+    <string name="sonyswr12_settings_title">Inst盲llningar Sony SWR12</string>
+    <string name="sonyswr12_settings_low_vibration">L氓g vibration aktiverad</string>
+    <string name="sonyswr12_settings_stamina">Energisparl盲ge 盲r p氓</string>
+    <string name="sonyswr12_settings_alarm_interval">Smart larmintervall p氓 n氓gra minuter</string>
+    <string name="sonyswr12_settings_low_vibration_summary">Aktivera l氓g vibrationsintensitet p氓 armbandet</string>
+    <string name="sonyswr12_settings_stamina_summary">Energisparl盲get st盲nger av periodisk automatisk m盲tning av hj盲rtfrekvensen, vilket 枚kar arbetstiden</string>
+    <string name="sonyswr12_settings_alarm_interval_summary">Smart alarmintervall 盲r intervall f枚re installerat larm. I detta intervall f枚rs枚ker enheten att uppt盲cka den l盲ttaste fasen av s枚mn f枚r att vakna anv盲ndare</string>
+    <string name="devicetype_galaxybuds_2"/>
+    <string name="about_activity_title_generic">Om Gadgetbridge</string>
+    <string name="about_activity_title_main_nightly">Om Gadgetbridge Nightly</string>
+    <string name="gadgetbridge_running_banglejs_main">Bangle.js k枚rs</string>
+    <string name="about_description_main_nightly">Fri mjukvara utan molnlagring som ers盲tter produkttillverkares propriet盲ra androidappar. Nattliga sl盲pp av Gadgetbridge. Den kan inte installeras om du redan har antingen Gadgetbridge eller Pebble-appen installerad, p氓 grund av en konflikt i Pebble-leverant枚ren.</string>
+    <string name="about_description_main_nopebble">Fri mjukvara utan molnlagring som ers盲tter produkttillverkares propriet盲ra androidappar. Nattliga utgivningar av Gadgetbridge. Den h盲r versionen har bytt namn p氓 Pebble-leverant枚ren f枚r att f枚rhindra konflikter, s氓 vissa Pebble-relaterade integrationer kommer inte att fungera, men den kan installeras tillsammans med en befintlig Gadgetbridge-installation.</string>
+    <string name="all_satellites">Alla satelliter</string>
+    <string name="find_lost_device_message">S枚k efter %1$s\?</string>
+    <string name="speed_first">Hastighet f枚rst</string>
+    <string name="controlcenter_power_off">St盲ng av</string>
+    <string name="controlcenter_power_off_confirm_title">St盲ng av</string>
+    <string name="prefs_activity_in_device_card_sleep_title">S枚mn</string>
+    <string name="accuracy">noggrannhet</string>
+    <string name="balanced">Balanserad</string>
+    <string name="power_saving">Energibesparing</string>
+    <string name="custom">Anpassad</string>
+    <string name="single_band">Enkelband</string>
+    <string name="dual_band">Dubbelband</string>
+    <string name="low_power_gps">Str枚msn氓l GPS</string>
+    <string name="accuracy_first">Noggrannhet f枚rst</string>
+    <string name="gadgetbridge_running_generic">Gadgetbridge k枚rs</string>
+    <string name="about_activity_title_banglejs_main">Om Bangle.js Gadgetbridge</string>
+    <string name="about_description_banglejs_main">F枚ljeslagarapp f枚r Bangle.js byggd p氓 Gadgetbridge projektet, med Internet氓tkomst.
+\n
+\nP氓 grund av Google Play Butikens policyer f氓r vi inte ha en donationsl盲nk i sj盲lva appen. Men om du gillar den h盲r appen kan du 枚verv盲ga att donera via Gadgetbridges hemsida nedan.</string>
+    <string name="about_description_generic">Fri mjukvara utan molnlagring som ers盲tter produkttillverkares propriet盲ra androidappar.</string>
+    <string name="gadgetbridge_running_main_nightly">Nightly GB k枚rs</string>
+    <string name="about_activity_title_main_nopebble">Om Gadgetbridge Nightly No Pebble</string>
+    <string name="prefs_activity_in_device_card_title">Visa aktivitetsinformation p氓 enhetskortet</string>
+    <string name="gadgetbridge_running_main_nopebble">Nightly NoPebble GB k枚rs</string>
+    <string name="controlcenter_power_off_confirm_description">脛r du s盲ker p氓 att du vill st盲nga av enheten\?</string>
+    <string name="device_card_activity_card_title">Aktivitetsinformation p氓 enhetskortet</string>
+    <string name="device_card_activity_card_title_summary">V盲lj vilken aktivitetsinformation som ska visas p氓 enhetskortet</string>
+    <string name="controlcenter_set_alias">Ange alias</string>
+    <string name="action_debug">Fels枚k</string>
+    <string name="controlcenter_get_heartrate_measurement">Utf枚r pulsm盲tning</string>
+    <string name="prefs_activity_in_device_card_title_summary">Visa aktuella steg, avst氓nd eller vilol盲ge p氓 enhetskortet</string>
+    <string name="about_activity_title_banglejs_nightly">Om Bangle.js Gadgetbridge (Nightly)</string>
+    <string name="about_description_banglejs_nightly">F枚ljeslagarapp f枚r Bangle.js byggd p氓 Gadgetbridge projektet, med Internet氓tkomst.
+\n
+\nP氓 grund av Google Play Butikens policyer f氓r vi inte ha en donationsl盲nk i sj盲lva appen. Men om du gillar den h盲r appen kan du 枚verv盲ga att donera via Gadgetbridges hemsida nedan.</string>
+    <string name="gadgetbridge_running_banglejs_nightly">Nightly Bangle.js k枚rs</string>
+    <string name="appmanager_watchface_activate">Aktivera</string>
+    <string name="appmanager_app_download">Ladda ner till chache</string>
+    <string name="appmanager_download_started">Appnedladdning p氓b枚rjad</string>
+    <string name="appmanager_downloaded_to_cache">Ladda ned %s till cacheminne</string>
+    <string name="appmanager_download_app_error">Fel vid appnedladdning</string>
+    <string name="title_activity_notification_management">Aviseringsinst盲llningar</string>
+    <string name="fw_upgrade_notice_amazfitbip">Du h氓ller p氓 att installera %s firmware p氓 din Amazfit Bip.
+\n
+\nSe till att installera .fw-filen, sedan .res-filen och slutligen .gps-filen. Din klocka kommer att starta om efter installation av .fw-filen.
+\n
+\nObs: Du beh枚ver inte installera .res och .gps om dessa filer 盲r exakt samma som de som tidigare installerats.
+\n
+\nFORTS脛TT P脜 EGEN RISK!</string>
+    <string name="fw_upgrade_notice_amazfitgtr">Du 盲r p氓 v盲g att installera %s firmware p氓 din Amazfit GTR.
+\n
+\nSe till att installera .fw-filen, sedan .res-filen och slutligen .gps-filen. Din klocka kommer att starta om efter installation av .fw-filen.
+\n
+\nObs: Du beh枚ver inte installera .res och .gps om dessa filer 盲r exakt samma som de som tidigare installerats.
+\n
+\nFORTS脛TT P脜 EGEN RISK!</string>
+    <string name="fw_upgrade_notice_amazfitvergel">Du 盲r p氓 v盲g att installera %s firmware p氓 din Amazfit Verge Lite.
+\n
+\nSe till att installera .fw-filen, sedan .res-filen och slutligen .gps-filen. Din klocka kommer att starta om efter installation av .fw-filen.
+\n
+\nObs: Du beh枚ver inte installera .res och .gps om dessa filer 盲r exakt samma som de som tidigare installerats.
+\n
+\nFORTS脛TT P脜 EGEN RISK!</string>
+    <string name="fw_upgrade_notice_miband4">Du 盲r p氓 v盲g att installera %s firmware p氓 din Mi Band 4.
+\n
+\nSe till att installera .fw-filen och efter det .res-filen. Ditt band kommer att starta om efter installation av .fw-filen.
+\n
+\nObs: Du beh枚ver inte installera .res om det 盲r exakt samma som det tidigare installerade.
+\n
+\nFORTS脛TT P脜 EGEN RISK!</string>
+    <string name="fw_upgrade_notice_miband6">Du 盲r p氓 v盲g att installera %s firmware p氓 din Mi Band 6.
+\n
+\nSe till att installera .fw-filen och efter det .res-filen. Ditt band kommer att starta om efter installation av .fw-filen.
+\n
+\nObs: Du beh枚ver inte installera .res om det 盲r exakt samma som det tidigare installerade.
+\n
+\nFORTS脛TT P脜 EGEN RISK!</string>
+    <string name="fw_upgrade_notice_amazfit_gts3">Du 盲r p氓 v盲g att installera %s firmware p氓 din Amazfit GTS 3.
+\n
+\nDitt band kommer att starta om efter installation av .zip-filen.
+\n
+\nFORTS脛TT P脜 EGEN RISK!</string>
+    <string name="fw_upgrade_notice_amazfit_gts4_mini">Du 盲r p氓 v盲g att installera %s firmware p氓 din Amazfit GTS 4 Mini.
+\n
+\nDitt band kommer att starta om efter installation av .zip-filen.
+\n
+\nFORTS脛TT P脜 EGEN RISK!</string>
+    <string name="fw_upgrade_notice_amazfit_gtr3">Du 盲r p氓 v盲g att installera %s firmware p氓 din Amazfit GTR 3.
+\n
+\nDitt band kommer att starta om efter installation av .zip-filen.
+\n
+\nFORTS脛TT P脜 EGEN RISK!</string>
+    <string name="fw_upgrade_notice_amazfit_gtr4">Du 盲r p氓 v盲g att installera %s firmware p氓 din Amazfit GTR 4.
+\n
+\nDitt band kommer att starta om efter installation av .zip-filen.
+\n
+\nFORTS脛TT P脜 EGEN RISK!</string>
+    <string name="fw_upgrade_notice_amazfitx">Du 盲r p氓 v盲g att installera %s firmware p氓 din Amazfit X.
+\n
+\nSe till att installera .fw-filen och efter det .res-filen. Ditt band kommer att starta om efter installation av .fw-filen.
+\n
+\nObs: Du beh枚ver inte installera .res om det 盲r exakt samma som det tidigare installerade.
+\n
+\nFORTS脛TT P脜 EGEN RISK!</string>
+    <string name="fw_multi_upgrade_notice">Du 盲r p氓 v盲g att installera %1$s och %2$s firmware, ist盲llet f枚r de som f枚r n盲rvarande finns p氓 ditt Mi Band.</string>
+    <string name="miband_firmware_suggest_whitelist">Om du fortfarande vill forts盲tta och saker och ting forts盲tter att fungera korrekt efter氓t, v盲nligen ber盲tta f枚r Gadgetbridge-utvecklarna att vitlista %s firmwareversion.</string>
+    <string name="miband_firmware_known">Detta firmware har testats och 盲r k盲nt f枚r att vara kompatibelt med Gadgetbridge.</string>
+    <string name="calendar_three_months">Tre m氓nader</string>
+    <string name="fw_upgrade_notice_amazfitcor">Du 盲r p氓 v盲g att installera %s firmware p氓 din Amazfit Cor.
+\n
+\nSe till att installera .fw-filen och efter det .res-filen. Ditt band kommer att starta om efter installation av .fw-filen.
+\n
+\nObs: Du beh枚ver inte installera .res om det 盲r exakt samma som det tidigare installerade.
+\n
+\nFORTS脛TT P脜 EGEN RISK!</string>
+    <string name="fw_upgrade_notice_miband7">Du 盲r p氓 v盲g att installera %s firmware p氓 ditt Xiaomi Smart Band 7.
+\n
+\nDitt band kommer att starta om efter installation av .zip-filen.
+\n
+\nFORTS脛TT P脜 EGEN RISK!</string>
+    <string name="prefs_activity_in_device_card_sleep_title_summary">Visa s枚mnl盲ngd</string>
+    <string name="prefs_activity_in_device_card_steps_title_summary">Visa totalt antal steg</string>
+    <string name="calendar_year">脜r</string>
+    <string name="check_all_applications">Markera alla applikationer</string>
+    <string name="uncheck_all_applications">Avmarker alla applikationer</string>
+    <string name="calendar_day">Dag</string>
+    <string name="calendar_week">Vecka</string>
+    <string name="calendar_two_weeks">Tv氓 veckor</string>
+    <string name="fw_upgrade_notice_amazfitbip_lite">Du 盲r p氓 v盲g att installera %s firmware p氓 din Amazfit Bip Lite.
+\n
+\nSe till att installera .fw-filen och efter det .res-filen. Din klocka kommer att starta om efter installation av .fw-filen.
+\n
+\nObs: Du beh枚ver inte installera .res om det 盲r exakt samma som det tidigare installerade.
+\n
+\nFORTS脛TT P脜 EGEN RISK!</string>
+    <string name="fw_upgrade_notice">Du h氓ller p氓 att installera %s.</string>
+    <string name="fw_upgrade_notice_amazfit_trex">Du h氓ller p氓 att installera %s firmware p氓 din Amazfit T-Rex.
+\n
+\nSe till att installera .fw-filen, sedan .res-filen och slutligen .gps-filen. Din klocka kommer att starta om efter installation av .fw-filen.
+\n
+\nObs: Du beh枚ver inte installera .res och .gps om dessa filer 盲r exakt samma som de som tidigare installerats.
+\n
+\nFORTS脛TT P脜 EGEN RISK!</string>
+    <string name="fw_upgrade_notice_amazfit_gts4">Du 盲r p氓 v盲g att installera %s firmware p氓 din Amazfit GTS 4.
+\n
+\nDitt band kommer att starta om efter installation av .zip-filen.
+\n
+\nFORTS脛TT P脜 EGEN RISK!</string>
+    <string name="battery_level">Batteriniv氓</string>
+    <string name="calendar_month">M氓nad</string>
+    <string name="calendar_six_months">Sex m氓nader</string>
+    <string name="appmanager_item_outdated">(utdaterad)</string>
+    <string name="battery_detail_activity_title">Batteristatus</string>
+    <string name="prefs_activity_in_device_card_distance_title_summary">Avst氓nd ber盲knas fr氓n stegen och stegens l盲ngd (justeras i Inst盲llningar - Om)</string>
+    <string name="whitelist_all_for_notifications">Vitlista alla f枚r aviseringar</string>
+    <string name="fw_upgrade_notice_amazfitcor2">Du 盲r p氓 v盲g att installera %s firmware p氓 din Amazfit Cor 2.
+\n
+\nSe till att installera .fw-filen och efter det .res-filen. Ditt band kommer att starta om efter installation av .fw-filen.
+\n
+\nObs: Du beh枚ver inte installera .res om det 盲r exakt samma som det tidigare installerade.
+\n
+\nFORTS脛TT P脜 EGEN RISK!
+\n
+\nHELT OTESTAD, F脰RMODLIGT M脜STE DU FLASH EN BEATS_W FIRMWARE OM DIN ENHET 脛R \"Amazfit Band 2\"</string>
+    <string name="fw_upgrade_notice_amazfitgts">Du h氓ller p氓 att installera %s firmware p氓 din Amazfit GTS.
+\n
+\nSe till att installera .fw-filen, sedan .res-filen och slutligen .gps-filen. Din klocka kommer att starta om efter installation av .fw-filen.
+\n
+\nObs: Du beh枚ver inte installera .res och .gps om dessa filer 盲r exakt samma som de som tidigare installerats.
+\n
+\nFORTS脛TT P脜 EGEN RISK!</string>
+    <string name="fw_upgrade_notice_miband3">Du 盲r p氓 v盲g att installera %s firmware p氓 din Mi Band 3.
+\n
+\nSe till att installera .fw-filen och efter det .res-filen. Ditt band kommer att starta om efter installation av .fw-filen.
+\n
+\nObs: Du beh枚ver inte installera .res om det 盲r exakt samma som det tidigare installerade.
+\n
+\nFORTS脛TT P脜 EGEN RISK!</string>
+    <string name="fw_upgrade_notice_miband5">Du 盲r p氓 v盲g att installera %s firmware p氓 din Mi Band 5.
+\n
+\nSe till att installera .fw-filen och efter det .res-filen. Ditt band kommer att starta om efter installation av .fw-filen.
+\n
+\nObs: Du beh枚ver inte installera .res om det 盲r exakt samma som det tidigare installerade.
+\n
+\nFORTS脛TT P脜 EGEN RISK!</string>
+    <string name="miband_firmware_unknown_warning">Detta firmware 盲r otestat och kanske inte kompatibelt med Gadgetbridge.
+\n
+\nDu AVR脜DS fr氓n att \"flasha\" det!</string>
+    <string name="fw_upgrade_notice_amazfit_gtr3_pro">Du 盲r p氓 v盲g att installera %s firmware p氓 din Amazfit GTR 3 Pro.
+\n
+\nDitt band kommer att starta om efter installation av .zip-filen.
+\n
+\nFORTS脛TT P脜 EGEN RISK!</string>
+    <string name="fw_upgrade_notice_amazfitneo">Du 盲r p氓 v盲g att installera %s firmware p氓 din Amazfit Neo.
+\n
+\nDitt band kommer att starta om efter installation av .fw-filen.
+\n
+\nFORTS脛TT P脜 EGEN RISK!</string>
+    <string name="fw_upgrade_notice_amazfit_trex2">Du 盲r p氓 v盲g att installera %s firmware p氓 din Amazfit T-Rex 2.
+\n
+\nDitt band kommer att starta om efter installation av .zip-filen.
+\n
+\nFORTS脛TT P脜 EGEN RISK!</string>
+    <string name="appmanager_app_share">Dela</string>
+    <string name="debugactivity_confirm_remove_device_preferences_title">Ta bort enhetsinst盲llningar\?</string>
+    <string name="debugactivity_confirm_remove_device_preferences">Det h盲r kommer 氓terst盲lla enhetsinst盲llningarna f枚r all anslutna enheter. 脛r du s盲ker\?</string>
+    <string name="fw_upgrade_notice_amazfit_band7">Du 盲r p氓 v盲g att installera %s firmware p氓 din Amazfit Band 7.
+\n
+\nDitt band kommer att starta om efter installation av .zip-filen.
+\n
+\nFORTS脛TT P脜 EGEN RISK!</string>
+    <string name="open_fw_installer_connect_minimum_one_device">Anslut MINST EN enhet som du vill skicka filen till.</string>
+    <string name="pref_default">Standard</string>
+    <string name="pref_header_time">Tid</string>
+    <string name="application_name_main_nopebble">Gadgetbridge (Nightly, Ingen Pebble-leverant枚r)</string>
+    <string name="pref_title_general_autoreconnect">脜teranslut automatiskt</string>
+    <string name="pref_summary_minimize_priority_off">Ikonen i statusf盲ltet och p氓 l氓ssk盲rmen visas</string>
+    <string name="pref_title_notifications_call">Telefonsamtal</string>
+    <string name="pref_title_datetime_syctimeonconnect">Synkronisera tid</string>
+    <string name="pref_theme_dark">M枚rk</string>
+    <string name="pref_summary_mb_intents">Aktivera om enhetens mediakontroll inte fungerar f枚r vissa appar</string>
+    <string name="pref_title_notifications_repetitions">Upprepningar</string>
+    <string name="title_activity_controlcenter_main_nightly">Gadgetbridge Nightly</string>
+    <string name="pref_title_theme">Tema</string>
+    <string name="prefs_wena3_led_none">Ingen LED</string>
+    <string name="appmanager_app_start">Start</string>
+    <string name="open_fw_installer_info_text_title">Filinstallerare</string>
+    <string name="pref_pair_bluetooth_calls_help_summary">F枚r att kunna ta emot bluetooth-samtal m氓ste du para ihop din telefon med en andra instans av klockan.</string>
+    <string name="pref_header_calendar">Kalender</string>
+    <string name="pref_pair_bluetooth_calls_summary">Klicka h盲r f枚r att starta ihopparningsprocessen</string>
+    <string name="pref_blacklist_calendars">Svartlista kalendrar</string>
+    <string name="open_fw_installer_connect_maximum_one_device">Anslut ENDAST EN enhet som du vill skicka filen till.</string>
+    <string name="open_fw_installer_getting_files_text">Eftersom vi inte alltid kan distribuera mjukvarufilerna m氓ste du skaffa filerna sj盲lv. Det betyder att du kommer att beh枚va s枚ka efter filerna i apk-filer, online, p氓 forum, p氓 Amazfitwatchfaces (f枚r Miband/Amazfit-enheter) och s氓 vidare.</string>
+    <string name="pref_header_system">System</string>
+    <string name="pref_title_notification_delay_calls">F枚rdr枚jning av samtalsaviseringar</string>
+    <string name="pref_display_caller_summary">Visa telefonnummer eller namn f枚r inkommande samtal</string>
+    <string name="pref_pair_bluetooth_calls_help_1">1. Tryck p氓 knappen nedan f枚r att starta ihopparningsprocessen.</string>
+    <string name="title_activity_controlcenter_main_nopebble">Gadgetbridge Nightly Utan Pebble</string>
+    <string name="pref_header_cannned_messages">F枚rfyllda meddelanden</string>
+    <string name="title_activity_controlcenter_banglejs_main">Bangle.js Gadgetbridge</string>
+    <string name="pref_theme_light">Ljus</string>
+    <string name="pref_pair_bluetooth_calls_help_warning">VARNING: Om du aktiverar bluetooth-samtal utan att para ihop med den andra instansen kanske samtalsaviseringar inte fungerar som f枚rv盲ntat.</string>
+    <string name="pref_theme_dynamic">Dynamiska f盲rger</string>
+    <string name="pref_title_audio_player">F枚redragen Audioplayer</string>
+    <string name="open_fw_installer_warning_title">Varning</string>
+    <string name="pref_summary_datetime_syctimeonconnect">Synkronisera tid till Gadgetbridge-enhet vid anslutning, och n盲r tid eller tidszon 盲ndras p氓 Android-enheten</string>
+    <string name="pref_pair_bluetooth_calls_help_3">3. Aktivera inst盲llningen f枚r Bluetooth-samtal nedan.</string>
+    <string name="pref_header_other">Annat</string>
+    <string name="gps">GPS</string>
+    <string name="pref_pair_bluetooth_calls_title">Para ihop f枚r bluetooth-samtal</string>
+    <string name="pref_title_mb_intents">Broadcast Media knappen uppst氓r direkt</string>
+    <string name="pref_theme_black_background">Anv盲nd svart bakgrund i m枚rkt tema</string>
+    <string name="pref_header_workout">Tr盲ning</string>
+    <string name="open_fw_installer_getting_files_title">H盲mtar firmware/app filen</string>
+    <string name="pref_header_equalizer">Equalizer</string>
+    <string name="fw_upgrade_notice_amazfitbip3pro">Du 盲r p氓 v盲g att installera mjukvaran %s p氓 din Amazfit Bip 3 Pro.
+\n
+\nSe till att installera .fw-filen och efter det .res-filen. Din klocka kommer att starta om efter installationen av .fw-filen.
+\n
+\nObs: Du beh枚ver inte installera .res om exakt samma var installerad tidigare.
+\n
+\nFORTS脛TT P脜 EGEN RISK!</string>
+    <string name="pref_header_sound_vibration">Ljud &amp; Vibration</string>
+    <string name="pref_header_health">H盲lsa</string>
+    <string name="bluetooth_calls_settings">Bluetooth-samtalsinst盲llningar</string>
+    <string name="pref_header_connection">Anslutning</string>
+    <string name="bluetooth_calls_pairing">Bluetooth-samtalsparning</string>
+    <string name="pref_header_general">Allm盲nna Inst盲llningar</string>
+    <string name="pref_summary_receive_calls_watch">Utf枚r och ta emot samtal direkt p氓 klockan</string>
+    <string name="pref_header_notifications">Aviseringar</string>
+    <string name="pref_title_minimize_priority">D枚lj Gadgetbridge-avisering</string>
+    <string name="fw_upgrade_notice_amazfit_cheetah_pro">Du 盲r p氓 v盲g att installera mjukvaran %s p氓 din Amazfit Cheetah Pro.
+\n
+\nDitt armband kommer att starta om efter installationen av .zip-filen.
+\n
+\nFORTS脛TT P脜 EGEN RISK!</string>
+    <string name="pref_blacklist_calendars_summary">Svartlistade kalendrar synkroniseras inte med enheten</string>
+    <string name="pref_theme_system">System</string>
+    <string name="title_activity_settings">Inst盲llningar</string>
+    <string name="open_fw_installer_pick_file">V盲lj fil</string>
+    <string name="title_activity_controlcenter_banglejs_nightly">Bangle.js Gadgetbridge (Nightly)</string>
+    <string name="pref_summary_notification_delay_calls">F枚rdr枚j innan aviseringar skickas om inkommande samtal till enheten, i sekunder.</string>
+    <string name="open_fw_installer_warning_text">Denna funktion kan potentiellt orsaka att din enhet inte kan starta. Med det sagt, detta har aldrig h盲nt n氓gon av utvecklarna vid flashning, kom ih氓g att detta 盲r p氓 egen risk.</string>
+    <string name="application_name_banglejs_main">Bangle.js Gadgetbridge</string>
+    <string name="fw_upgrade_notice_zepp_os">Du 盲r p氓 v盲g att installera mjukvaran %s p氓 din %s.
+\n
+\nDitt klocka kommer att starta om efter installationen av .zip-filen.
+\n
+\nFORTS脛TT P脜 EGEN RISK!</string>
+    <string name="pref_title_general_autoconnectonbluetooth">Anslut till Gadgetbridge-enheten n盲r Bluetooth aktiveras</string>
+    <string name="pref_title_general_autostartonboot">Starta automatiskt</string>
+    <string name="pref_title_language">Spr氓k</string>
+    <string name="open_fw_installer_select_file">V盲lj filen som ska 枚verf枚ras till enheten: %s</string>
+    <string name="pref_summary_minimize_priority_on">Ikonen i statusf盲ltet och p氓 l氓ssk盲rmen 盲r d枚ljd</string>
+    <string name="pref_display_caller_title">Visa kontaktinformation</string>
+    <string name="gps_gnolass">GPS + GNOLASS</string>
+    <string name="action_changelog">脛ndringslogg</string>
+    <string name="pref_pair_bluetooth_calls_help_2">2. G氓 till telefonens bluetooth-inst盲llningar och koppla ihop med den nya enheten som dyker upp (liknande namn p氓 din nuvarande klocka, men med ett suffix, t.ex. \"Amazfit GTR 4 - AFC8\".</string>
+    <string name="open_fw_installer_ensure_device_connected">Se till att enheten %s 盲r ansluten</string>
+    <string name="bluetooth_calls">Bluetooth-samtal</string>
+    <string name="pref_header_display">Sk盲rm</string>
+    <string name="pref_pair_bluetooth_calls_help_title">Hur man tar emot bluetooth-samtal</string>
+    <string name="gps_bds">GPS + BDS</string>
+    <string name="application_name_banglejs_nightly">Bangle.js Gadgetbridge Nightly</string>
+    <string name="application_name_main_nightly">Gadgetbridge (Nightly)</string>
+    <string name="pref_header_datetime">Datum och Tid</string>
+    <string name="gps_galileo">GPS + GALILEO</string>
+    <string name="pref_time_sync"></string>
+</resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-ta/strings.xml b/app/src/main/res/values-ta/strings.xml
new file mode 100644
index 0000000..a6b3dae
--- /dev/null
+++ b/app/src/main/res/values-ta/strings.xml
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources></resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-th/strings.xml b/app/src/main/res/values-th/strings.xml
new file mode 100644
index 0000000..3a3868f
--- /dev/null
+++ b/app/src/main/res/values-th/strings.xml
@@ -0,0 +1,138 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <string name="about_activity_title_generic">喙�喔佮傅喙堗涪喔о竵喔编笟 Gadgetbridge</string>
+    <string name="application_name_generic">Gadgetbridge</string>
+    <string name="gadgetbridge_running_generic">Gadgetbridge 喔佮赋喔ム副喔囙笚喔赤竾喔侧笝</string>
+    <string name="about_description_generic">喙佮腑喔涏箑喔福喔掂箘喔∴箞喔竾喔о笝喔ム复喔傕釜喔脆笚喔樴复喙� 喙勦浮喙堗笧喔多箞喔囙福喔班笟喔氞竸喔ム覆喔о笖喙� 喔赋喔福喔编笟喙冟笂喙夃笚喔斷箒喔椸笝喙佮腑喔涏笀喔侧竵喔溹腹喙夃笢喔ム复喔曕笚喔掂箞喙勦浮喙堗箑喔涏复喔斷箑喔溹涪喙傕竸喙夃笖喔复喔福喔�</string>
+    <string name="title_activity_controlcenter_generic">Gadgetbridge</string>
+    <string name="about_activity_title_banglejs_main">喙�喔佮傅喙堗涪喔о竵喔编笟 Bangle.js Gadgetbridge</string>
+    <string name="gadgetbridge_running_banglejs_main">Bangle.js 喔佮赋喔ム副喔囙笚喔赤竾喔侧笝喔涪喔灌箞</string>
+    <string name="application_name_banglejs_nightly">Bangle.js Gadgetbridge (Nightly)</string>
+    <string name="title_activity_controlcenter_banglejs_nightly">Bangle.js Gadgetbridge (Nightly)</string>
+    <string name="gadgetbridge_running_banglejs_nightly">Nightly Bangle.js 喔佮赋喔ム副喔囙笚喔赤竾喔侧笝喔涪喔灌箞</string>
+    <string name="about_activity_title_main_nightly">喙�喔佮傅喙堗涪喔о竵喔编笟 Gadgetbridge 喔`父喙堗笝喔椸笖喔腑喔�</string>
+    <string name="title_activity_controlcenter_main_nightly">Gadgetbridge Nightly</string>
+    <string name="gadgetbridge_running_main_nightly">Nightly GB 喔佮赋喔ム副喔囙笚喔赤竾喔侧笝喔涪喔灌箞</string>
+    <string name="application_name_main_nopebble">Gadgetbridge (Nightly 喙傕笖喔⑧箘喔∴箞喔∴傅 Pebble provider)</string>
+    <string name="title_activity_controlcenter_main_nopebble">Gadgetbridge Nightly 喙佮笟喔氞箘喔∴箞喔∴傅 Pebble喔�</string>
+    <string name="about_activity_title_main_nopebble">About Gadgetbridge Nightly No Pebble</string>
+    <string name="action_settings">喔佮覆喔`笗喔编箟喔囙竸喙堗覆</string>
+    <string name="action_debug">喔斷傅喔氞副喙夃竵</string>
+    <string name="action_quit">喔腑喔�</string>
+    <string name="action_donate">喔氞福喔脆笀喔侧竸</string>
+    <string name="action_changelog">喔涏福喔班抚喔编笗喔脆竵喔侧福喙�喔涏弗喔掂箞喔⑧笝喙佮笡喔ム竾</string>
+    <string name="controlcenter_fetch_activity_data">喔嬥复喔囙箓喔勦福喙勦笝喔嬥箤</string>
+    <string name="controlcenter_find_device">喔覆喔父喔涏竵喔`笓喙�</string>
+    <string name="find_lost_device_message">喔覆 %1$s?</string>
+    <string name="controlcenter_take_screenshot">喔氞副喔權笚喔多竵喔犩覆喔炧斧喔權箟喔侧笀喔�</string>
+    <string name="controlcenter_power_off">喔涏复喔斷腑喔膏笡喔佮福喔撪箤</string>
+    <string name="controlcenter_power_off_confirm_title">喔涏复喔斷腑喔膏笡喔佮福喔撪箤</string>
+    <string name="controlcenter_change_led_color">喙�喔涏弗喔掂箞喔⑧笝喔傅 LED</string>
+    <string name="controlcenter_power_off_confirm_description">喙佮笝喙堗箖喔堗斧喔`阜喔抚喙堗覆喔堗赴喔涏复喔斷腑喔膏笡喔佮福喔撪箤?</string>
+    <string name="controlcenter_change_fm_frequency">喙�喔涏弗喔掂箞喔⑧笝喔勦抚喔侧浮喔栢傅喙� FM</string>
+    <string name="controlcenter_connect">喙�喔娻阜喙堗腑喔∴笗喙堗腑鈥�</string>
+    <string name="controlcenter_disconnect">喔曕副喔斷竵喔侧福喙�喔娻阜喙堗腑喔∴笗喙堗腑</string>
+    <string name="controlcenter_delete_device_name">喔ム笟 %1$s</string>
+    <string name="controlcenter_delete_device_dialogmessage">喔權傅喙堗笀喔班箑喔涏箛喔權竵喔侧福喔ム笟喔父喔涏竵喔`笓喙屶箒喔ム赴喔傕箟喔浮喔灌弗喔椸傅喙堗箑喔佮傅喙堗涪喔о競喙夃腑喔囙笚喔编箟喔囙斧喔∴笖!</string>
+    <string name="controlcenter_set_alias">喔曕副喙夃竾喔娻阜喙堗腑喙�喔`傅喔⑧竵</string>
+    <string name="controlcenter_navigation_drawer_open">喙�喔涏复喔斷弗喔脆箟喔權笂喔编竵喔佮覆喔`笝喔赤笚喔侧竾</string>
+    <string name="controlcenter_navigation_drawer_close">喔涏复喔斷弗喔脆箟喔權笂喔编竵喔佮覆喔`笝喔赤笚喔侧竾</string>
+    <string name="controlcenter_snackbar_need_longpress">喙佮笗喔班竵喔侧福喙屶笖喔勦箟喔侧竾喙勦抚喙夃箑喔炧阜喙堗腑喔曕副喔斷竵喔侧福喙�喔娻阜喙堗腑喔∴笗喙堗腑</string>
+    <string name="controlcenter_snackbar_disconnecting">喔佮赋喔ム副喔囙笗喔编笖喔佮覆喔`箑喔娻阜喙堗腑喔∴笗喙堗腑</string>
+    <string name="controlcenter_snackbar_connecting">喔佮赋喔ム副喔囙箑喔娻傅喙堗腑喔∴笗喙堗腑鈥�</string>
+    <string name="controlcenter_snackbar_requested_screenshot">喔栢箞喔侧涪喔犩覆喔炧斧喔權箟喔侧笀喔競喔竾喔父喔涏竵喔`笓喙屶笝喔掂箟</string>
+    <string name="controlcenter_calibrate_device">喔涏福喔编笟喙�喔椸傅喔⑧笟喔父喔涏竵喔`笓喙�</string>
+    <string name="controlcenter_get_heartrate_measurement">喔о副喔斷腑喔编笗喔`覆喔娻傅喔炧笀喔`競喔竾喔副喔о箖喔�</string>
+    <string name="accuracy">喔勦抚喔侧浮喙佮浮喙堗笝喔⑧赋</string>
+    <string name="balanced">喔浮喔斷父喔�</string>
+    <string name="power_saving">喔涏福喔班斧喔⑧副喔斷笧喔ム副喔囙竾喔侧笝</string>
+    <string name="custom">喔⑧阜喔權涪喔编笝</string>
+    <string name="low_power_gps">GPS 喔炧弗喔编竾喔囙覆喔權笗喙堗赋</string>
+    <string name="all_satellites">喔斷覆喔о箑喔椸傅喔⑧浮喔椸副喙夃竾喔浮喔�</string>
+    <string name="speed_first">喔勦抚喔侧浮喙�喔`箛喔о竵喙堗腑喔�</string>
+    <string name="accuracy_first">喙佮浮喙堗笝喔⑧赋喔佮箞喔笝</string>
+    <string name="device_card_activity_card_title">喔傕箟喔浮喔灌弗喔佮复喔堗竵喔`福喔∴笟喔權竵喔侧福喙屶笖喔傕腑喔囙腑喔膏笡喔佮福喔撪箤</string>
+    <string name="device_card_activity_card_title_summary">喙�喔ム阜喔竵喔о箞喔侧笀喔班箖喔箟喔佮复喔堗竵喔`福喔∴箖喔斷箒喔笖喔囙笟喔權竵喔侧福喙屶笖喔傕腑喔囙腑喔膏笡喔佮福喔撪箤</string>
+    <string name="prefs_activity_in_device_card_distance_title_summary">喔`赴喔⑧赴喔椸覆喔囙笀喔班笘喔灌竵喔勦赋喔權抚喔權笀喔侧竵喔堗赋喔權抚喔權箒喔ム赴喔`赴喔⑧赴喔佮箟喔侧抚(喔涏福喔编笟喙勦笖喙夃箖喔權竵喔侧福喔曕箟喙夃竾喔勦箞喔� - 喙�喔佮傅喙堗涪喔о竵喔编笟喔勦父喔�)</string>
+    <string name="prefs_activity_in_device_card_steps_title_summary">喙佮釜喔斷竾喔堗赋喔權抚喔權竵喙夃覆喔о笚喔编箟喔囙斧喔∴笖</string>
+    <string name="battery_detail_activity_title">喔傕箟喔浮喔灌弗喙佮笟喔曕箑喔曕腑喔`傅喙�</string>
+    <string name="battery_level">喔`赴喔斷副喔氞箒喔氞笗喙�喔曕腑喔`傅喙�</string>
+    <string name="calendar_day">喔о副喔�</string>
+    <string name="calendar_week">喔副喔涏笖喔侧斧喙�</string>
+    <string name="calendar_two_weeks">喔腑喔囙釜喔编笡喔斷覆喔箤</string>
+    <string name="calendar_month">喙�喔斷阜喔笝</string>
+    <string name="calendar_three_months">喔覆喔∴箑喔斷阜喔笝</string>
+    <string name="calendar_six_months">喔竵喙�喔斷阜喔笝</string>
+    <string name="calendar_year">喔涏傅</string>
+    <string name="title_activity_debug">喔斷傅喔氞副喙夃竵</string>
+    <string name="debugactivity_really_factoryreset_title">喔勦阜喔權竸喙堗覆喙�喔`阜喙堗浮喔曕箟喔權笀喔侧竵喙傕福喔囙竾喔侧笝?</string>
+    <string name="debugactivity_confirm_remove_device_preferences_title">喔ム笟喔佮覆喔`笗喔编箟喔囙竸喙堗覆喔傕腑喔囙腑喔膏笡喔佮福喔撪箤?</string>
+    <string name="about_activity_title_banglejs_nightly">喙�喔佮傅喙堗涪喔о竵喔编笟 Bangle.js Gadgetbridge (喔`父喙堗笝喔椸笖喔腑喔�)</string>
+    <string name="about_description_main_nightly">喙佮腑喔涏箑喔福喔掂箘喔∴箞喔竾喔о笝喔ム复喔傕釜喔脆笚喔樴复喙� 喙勦浮喙堗笧喔多箞喔囙福喔班笟喔氞竸喔ム覆喔о笖喙� 喔赋喔福喔编笟喙冟笂喙夃笚喔斷箒喔椸笝喙佮腑喔涏箒喔佮箛喔斷箑喔堗箛喔曕笗喙堗覆喔囙箚喔堗覆喔佮笢喔灌箟喔溹弗喔脆笗喔椸傅喙堗箘喔∴箞喙�喔涏复喔斷箑喔溹涪喙傕竸喙夃笖喔复喔福喔� 喙�喔о腑喔`箤喔娻副喙堗笝喔椸笖喔腑喔氞競喔竾 Gadgetbridge 喙勦浮喙堗釜喔侧浮喔侧福喔栢笗喔脆笖喔曕副喙夃竾喔勦腹喙堗竵喔编笝喙勦笖喙夃斧喔侧竵喔勦父喔撪浮喔掂箒喔笡 Gadgetbridge 喔福喔粪腑 Pebble 喔曕复喔斷笗喔编箟喔囙腑喔⑧腹喙堗箒喔ム箟喔� 喙�喔權阜喙堗腑喔囙笀喔侧竵喔佮覆喔`競喔编笖喙佮涪喙夃竾喔佮副喔權競喔竾 Pebble provider</string>
+    <string name="controlcenter_delete_device">喔ム笟喔父喔涏竵喔`笓喙�</string>
+    <string name="prefs_activity_in_device_card_title">喙佮釜喔斷竾喔傕箟喔浮喔灌弗喔佮复喔堗竵喔`福喔∴笟喔權竵喔侧福喙屶笖喔傕腑喔囙腑喔膏笡喔佮福喔撪箤</string>
+    <string name="prefs_activity_in_device_card_title_summary">喙佮釜喔斷竾喔堗赋喔權抚喔權竵喙夃覆喔о箑喔斷复喔權笡喔编笀喔堗父喔氞副喔� 喔`赴喔⑧赴喔椸覆喔囙斧喔`阜喔竵喔侧福喔權腑喔權斧喔ム副喔氞笟喔權竵喔侧福喙屶笖喔傕腑喔囙腑喔膏笡喔佮福喔撪箤</string>
+    <string name="prefs_activity_in_device_card_sleep_title">喔佮覆喔`笝喔笝</string>
+    <string name="prefs_activity_in_device_card_sleep_title_summary">喙佮釜喔斷竾喔`赴喔⑧赴喙�喔о弗喔侧竵喔侧福喔權腑喔�</string>
+    <string name="about_description_banglejs_main">喙佮腑喔� Android 喔椸傅喙堗箖喔娻箟喔勦腹喙堗竵喔编笟 Bangle.js 喔椸傅喙堗釜喔`箟喔侧竾喔傕付喙夃笝喔∴覆喔堗覆喔佮箓喔勦福喔囙竵喔侧福 Gadgetbridge 喔傅喔佮笚喔� 喙傕笖喔⑧箑喔炧阜喙堗浮喙�喔娻阜喙堗腑喔∴笗喙堗腑喔复喔權箑喔曕腑喔`箤喙�喔權箛喔曕箑喔傕箟喔侧浮喔�
+\n
+\n喙�喔權阜喙堗腑喔囙笀喔侧竵喔權箓喔⑧笟喔侧涪喔傕腑喔� Google Play 喙�喔`覆喔堗付喔囙箘喔∴箞喔覆喔∴覆喔`笘喙�喔炧阜喙堗浮喔娻箞喔竾喔佮覆喔`笟喔`复喔堗覆喔勦箖喔權箒喔笡喙勦笖喙夃箓喔斷涪喔曕福喔� 喙佮笗喙堗笘喙夃覆喔勦父喔撪笂喔笟喙佮腑喔涏笝喔掂箟喙傕笡喔`笖喔炧复喔堗覆喔`笓喔侧笟喔`复喔堗覆喔勦笢喙堗覆喔權斧喔權箟喔侧斧喔ム副喔佮競喔竾 Gadgetbridge 喔斷箟喔侧笝喔ム箞喔侧竾喔權傅喙�</string>
+    <string name="about_description_banglejs_nightly">喙佮腑喔� Android 喔椸傅喙堗箖喔娻箟喔勦腹喙堗竵喔编笟 Bangle.js 喔椸傅喙堗釜喔`箟喔侧竾喔傕付喙夃笝喔∴覆喔堗覆喔佮箓喔勦福喔囙竵喔侧福 Gadgetbridge 喔傅喔佮笚喔� 喙傕笖喔⑧箑喔炧阜喙堗浮喙�喔娻阜喙堗腑喔∴笗喙堗腑喔复喔權箑喔曕腑喔`箤喙�喔權箛喔曕箑喔傕箟喔侧浮喔�
+\n
+\n喙�喔權阜喙堗腑喔囙笀喔侧竵喔權箓喔⑧笟喔侧涪喔傕腑喔� Google Play 喙�喔`覆喔堗付喔囙箘喔∴箞喔覆喔∴覆喔`笘喙�喔炧阜喙堗浮喔娻箞喔竾喔佮覆喔`笟喔`复喔堗覆喔勦箖喔權箒喔笡喙勦笖喙夃箓喔斷涪喔曕福喔� 喙佮笗喙堗笘喙夃覆喔勦父喔撪笂喔笟喙佮腑喔涏笝喔掂箟喙傕笡喔`笖喔炧复喔堗覆喔`笓喔侧笟喔`复喔堗覆喔勦笢喙堗覆喔權斧喔權箟喔侧斧喔ム副喔佮競喔竾 Gadgetbridge 喔斷箟喔侧笝喔ム箞喔侧竾喔權傅喙�</string>
+    <string name="debugactivity_really_factoryreset">喔佮覆喔`竸喔粪笝喔勦箞喔侧箓喔`竾喔囙覆喔權笀喔班弗喔氞競喙夃腑喔∴腹喔ム笚喔膏竵喔涪喙堗覆喔囙競喔竾喔父喔涏竵喔`笓喙� Xiaomi/Huami (喔椸傅喙堗福喔竾喔`副喔�) 喙佮弗喔班涪喔编竾喙�喔涏弗喔掂箞喔⑧笝喔椸傅喙堗腑喔⑧腹喙� Bluetooth MAC address 喔夃赴喔權副喙夃笝 Gadgetbridge 喔堗赴喔∴腑喔囙箑喔箛喔權箑喔涏箛喔權腑喔膏笡喔佮福喔撪箤喙冟斧喔∴箞</string>
+    <string name="application_name_banglejs_main">Bangle.js Gadgetbridge</string>
+    <string name="title_activity_controlcenter_banglejs_main">Bangle.js Gadgetbridge</string>
+    <string name="application_name_main_nightly">Gadgetbridge (喔`父喙堗笝喔椸笖喔腑喔�)</string>
+    <string name="about_links">喔ム复喔囙竵喙�</string>
+    <string name="single_band">喔娻箞喔竾喔勦抚喔侧浮喔栢傅喙堗箑喔斷傅喙堗涪喔�</string>
+    <string name="dual_band">喔娻箞喔竾喔勦抚喔侧浮喔栢傅喙堗竸喔灌箞</string>
+    <string name="debugactivity_confirm_remove_device_preferences">喔權傅喙堗笀喔班箑喔涏箛喔權竵喔侧福喔`傅喙�喔嬥箛喔曕竵喔侧福喔曕副喙夃竾喔勦箞喔侧競喔竾喔椸父喔佮腑喔膏笡喔佮福喔撪箤喔椸傅喙堗箑喔娻阜喙堗腑喔∴笗喙堗腑喔涪喔灌箞 喙佮笝喙堗箖喔堗斧喔`阜喔�?</string>
+    <string name="about_description_main_nopebble">喙佮腑喔涏箑喔福喔掂箘喔∴箞喔竾喔о笝喔ム复喔傕釜喔脆笚喔樴复喙� 喙勦浮喙堗笧喔多箞喔囙福喔班笟喔氞竸喔ム覆喔о笖喙� 喔赋喔福喔编笟喙冟笂喙夃笚喔斷箒喔椸笝喙佮腑喔涏箒喔佮箛喔斷箑喔堗箛喔曕笗喙堗覆喔囙箚喔堗覆喔佮笢喔灌箟喔溹弗喔脆笗喔椸傅喙堗箘喔∴箞喙�喔涏复喔斷箑喔溹涪喙傕竸喙夃笖喔复喔福喔� 喙�喔о腑喔`箤喔娻副喙堗笝喔椸笖喔腑喔氞競喔竾 Gadgetbridge 喔∴傅 Pebble provider 喔椸傅喙堗笘喔灌竵喙�喔涏弗喔掂箞喔⑧笝喔娻阜喙堗腑喙�喔炧阜喙堗腑喔涏箟喔竾喔佮副喔權竵喔侧福喔傕副喔斷箒喔⑧箟喔囙竵喔编笝 喔夃赴喔權副喙夃笝喔佮覆喔`笚喔赤竾喔侧笝喔`箞喔о浮喔佮副喔氞笩喔掂箑喔堗腑喔`箤喔氞覆喔囙腑喔⑧箞喔侧竾喔傕腑喔� Pebble 喔覆喔堗箖喔娻箟喙勦浮喙堗箘喔斷箟 喙佮笗喙堗釜喔侧浮喔侧福喔栢笗喔脆笖喔曕副喙夃竾喙佮腑喔涏笝喔掂箟喔勦腹喙堗竵喔编笟喙佮腑喔� Gadgetbridge 喔椸傅喙堗浮喔掂腑喔⑧腹喙堗箒喔ム箟喔о箘喔斷箟</string>
+    <string name="appmanager_installed_watchapps">喙佮腑喔涏笚喔掂箞喔曕复喔斷笗喔编箟喔囙箒喔ム箟喔�</string>
+    <string name="appmanager_installed_watchfaces">喔笝喙夃覆喔涏副喔斷笝喔侧脯喔脆竵喔侧笚喔掂箞喔曕复喔斷笗喔编箟喔囙箒喔ム箟喔�</string>
+    <string name="about_core_team_title">喔溹腹喙夃福喙堗抚喔∴笧喔编笒喔權覆喔弗喔编竵喙�(喔曕副喙夃竾喙佮笗喙堗箑喔`阜喙堗浮喔炧副喔掄笝喔侧箒喔`竵喙�)</string>
+    <string name="gadgetbridge_running_main_nopebble">Nightly NoPebble GB 喔佮赋喔ム副喔囙笚喔赤竾喔侧笝</string>
+    <string name="appmanager_app_share">喙佮笂喔`箤</string>
+    <string name="title_activity_notification_management">喔佮覆喔`笗喔编箟喔囙竸喙堗覆喔佮覆喔`箒喔堗箟喔囙箑喔曕阜喔笝</string>
+    <string name="pref_title_notifications_call">喙�喔∴阜喙堗腑喔∴傅喔覆喔⑧箑喔傕箟喔�</string>
+    <string name="pref_title_notifications_ignore_low_priority">喙勦浮喙堗釜喔權箖喔堗竵喔侧福喙佮笀喙夃竾喙�喔曕阜喔笝喔勦抚喔侧浮喔赋喔勦副喔嵿笗喙堗赋</string>
+    <string name="pref_title_notifications_generic_settings">喔佮覆喔`笗喔编箟喔囙竸喙堗覆喔佮覆喔`箒喔堗箟喔囙箑喔曕阜喔笝喔傕腑喔� Android</string>
+    <string name="pref_title_notifications_generic">喔佮覆喔`福喔竾喔`副喔氞竵喔侧福喙佮笀喙夃竾喙�喔曕阜喔笝喔椸副喙堗抚喙勦笡</string>
+    <string name="pref_title_whenscreenon">...喙佮弗喔班箑喔∴阜喙堗腑喔笝喙夃覆喔堗腑喙�喔涏复喔斷腑喔⑧腹喙�</string>
+    <string name="pref_summary_notifications_ignore_low_priority">喙勦浮喙堗笗喙夃腑喔囙釜喙堗竾喔佮覆喔`箒喔堗箟喔囙箑喔曕阜喔笝喔椸傅喙堗浮喔掂竸喔о覆喔∴釜喔赤竸喔编笉喔曕箞喔赤笗喙堗覆喔囙箚喙勦笡喔⑧副喔囙腑喔膏笡喔佮福喔撪箤</string>
+    <string name="pref_title_support_voip_calls">喔佮覆喔`箓喔椸福喔堗覆喔佮福喔班笟喔� VoIP</string>
+    <string name="pref_summary_notification_prefer_long_text">喔覆喔佮箑喔涏箛喔權箘喔涏箘喔斷箟 喙冟斧喙夃釜喙堗竾喔佮覆喔`箒喔堗箟喔囙箑喔曕阜喔笝喔椸傅喙堗浮喔掂競喙夃腑喔勦抚喔侧浮喔⑧覆喔о箚 喙勦笡喔⑧副喔囙腑喔膏笡喔佮福喔撪箤</string>
+    <string name="pref_summary_notifications_ignore_work_profile">喙勦浮喙堗笗喙夃腑喔囙釜喙堗竾喔佮覆喔`箒喔堗箟喔囙箑喔曕阜喔笝喔堗覆喔佮箒喔笡喔椸傅喙堗腑喔⑧腹喙堗箖喔權箓喔涏福喙勦笩喔ム箤喔囙覆喔權箘喔涏涪喔编竾喔父喔涏竵喔`笓喙�</string>
+    <string name="pref_title_notification_cache_while_disconnected">喙佮竸喔娻竵喔侧福喙佮笀喔编竾喙�喔曕阜喔笝喙勦抚喙�</string>
+    <string name="pref_summary_notification_cache_while_disconnected">喔箞喔囙竵喔侧福喙佮笀喙夃竾喙�喔曕阜喔笝喔椸傅喙堗笧喔ム覆喔斷箘喔� 喙�喔∴阜喙堗腑喔父喔涏竵喔`笓喙屶竵喔ム副喔氞浮喔侧箑喔娻阜喙堗腑喔∴笗喙堗腑喔傅喔佮竸喔`副喙夃竾喔弗喔编竾喔堗覆喔佮箘喔∴箞喔涪喔灌箞喙冟笝喔`赴喔⑧赴喔佮覆喔`箑喔娻阜喙堗腑喔∴笗喙堗腑</string>
+    <string name="pref_title_notification_prefer_long_text">喔箞喔囙竵喔侧福喙佮笀喙夃竾喙�喔曕阜喔笝喔傕箟喔竸喔о覆喔∴涪喔侧抚喙�</string>
+    <string name="never">喙勦浮喙堗箑喔ム涪</string>
+    <string name="always">喔曕弗喔笖喙�喔о弗喔�</string>
+    <string name="pref_title_notifications_ignore_work_profile">喙勦浮喙堗釜喔權箖喔堗竵喔侧福喙佮笀喙夃竾喙�喔曕阜喔笝喔堗覆喔佮箓喔涏福喙勦笩喔ム箤喔囙覆喔�</string>
+    <string name="pref_title_ping_tone">喙�喔傅喔⑧竾喔曕腑喔權笡喔脆竾喔父喔涏竵喔`笓喙�</string>
+    <string name="pref_title_notification_delay_calls">喔笝喙堗抚喔囙竵喔侧福喙佮笀喙夃竾喙�喔曕阜喔笝喔佮覆喔`箓喔椸福</string>
+    <string name="pref_summary_notification_delay_calls">喔笝喙堗抚喔囙箘喔о箟喔佮箞喔笝喔堗赴喔箞喔囙竵喔侧福喙勦笡喙佮笀喙夃竾喙�喔曕阜喔笝喙勦笡喔⑧副喔囙腑喔膏笡喔佮福喔撪箤 喔笝喙堗抚喔⑧箑喔涏箛喔權抚喔脆笝喔侧笚喔�</string>
+    <string name="about_contributors">喔溹腹喙夃福喙堗抚喔∴笧喔编笒喔權覆</string>
+    <string name="about_additional_device_support">喔溹腹喙夃釜喔權副喔氞釜喔權父喔權竵喔侧福喔`腑喔囙福喔编笟喔父喔涏竵喔`笓喙屶箑喔炧阜喙堗浮喙�喔曕复喔�</string>
+    <string name="about_additional_contributions">喔傕腑喔傕腑喔氞竸喔膏笓喔溹腹喙堗福喙堗抚喔∴笧喔编笒喔權覆喔椸父喔佮笚喙堗覆喔權笚喔掂箞喔娻箞喔о涪喔佮副喔權箒喔佮箟喙勦競喙傕竸喙夃笖, 喙佮笡喔ム笭喔侧俯喔�, 喔娻箞喔о涪喙�喔弗喔粪腑, 喔腑喔佮箘喔箑喔斷傅喔�, 喙冟斧喙夃竵喔赤弗喔编竾喙冟笀, 喙佮笀喙夃竾喔氞副喙夃竵, 喙佮弗喔班笟喔`复喔堗覆喔勦箑喔囙复喔�... 鉁�</string>
+    <string name="action_discover">喙�喔娻阜喙堗腑喔∴笗喙堗腑喔父喔涏竵喔`笓喙屶箖喔浮喙�</string>
+    <string name="bluetooth_is_disabled_">喔氞弗喔灌笚喔灌笜喔栢腹喔佮笡喔脆笖喙冟笂喙夃竾喔侧笝</string>
+    <string name="permission_notification_listener">%1$s 喔曕箟喔竾喔佮覆喔`釜喔脆笚喔樴复喙屶竵喔侧福喙�喔傕箟喔侧笘喔多竾喔佮覆喔`箒喔堗箟喔囙箑喔曕阜喔笝喙�喔炧阜喙堗腑喙冟斧喙夃箒喔笖喔囙竵喔侧福喙佮笀喙夃竾喙�喔曕阜喔笝喔曕箞喔侧竾喙嗋箘喔涏涪喔编竾喔父喔涏竵喔`笓喙屶競喔竾喔勦父喔撪箑喔∴阜喙堗腑喔笝喙夃覆喔堗腑喙傕笚喔`辅喔编笧喔椸箤喔涏复喔斷腑喔⑧腹喙�
+\n
+\n喔佮福喔膏笓喔侧箒喔曕赴 \'%2$s\' 喔堗覆喔佮笝喔编箟喔� \'%1$s\' 喙佮弗喔班箑喔涏复喔斷箖喔娻箟 \'喔笝喔膏笉喔侧笗喔佮覆喔`箑喔傕箟喔侧笘喔多竾喔佮覆喔`箒喔堗箟喔囙箑喔曕阜喔笝\', 喔堗覆喔佮笝喔编箟喔權箒喔曕赴 \'喔佮弗喔编笟\' 喔佮弗喔编笟喙勦笡喔⑧副喔� %1$s.</string>
+    <string name="pref_dashboard_first_title">喙佮釜喔斷竾喙佮笖喔娻笟喔福喙屶笖喔佮箞喔笝</string>
+    <string name="pref_title_general_autoconnectonbluetooth">喙�喔娻阜喙堗腑喔∴笗喙堗腑喔佮副喔氞腑喔膏笡喔佮福喔撪箤喔椸傅喙堗箑喔勦涪喙冟笂喙夃竵喔编笟 Gadget bridge 喙�喔∴阜喙堗腑喙�喔涏复喔斷笟喔ム腹喔椸腹喔�</string>
+    <string name="discovery_note">喔浮喔侧涪喙�喔笗喔�:</string>
+    <string name="discovery_dont_pair">喔涪喙堗覆喔堗副喔氞竸喔灌箞</string>
+    <string name="discovery_attempting_to_pair">喔炧涪喔侧涪喔侧浮喔堗副喔氞竸喔灌箞喔父喔涏竵喔`笓喙屶竵喔编笟 %1$s</string>
+    <string name="activity_prefs_discovery_pairing">喔曕副喔о箑喔ム阜喔竵喔佮覆喔`竸喙夃笝喔炧笟喙佮弗喔班竵喔侧福喔堗副喔氞竸喔灌箞</string>
+    <string name="discovery_yes_pair">喔堗副喔氞竸喔灌箞</string>
+    <string name="bottom_nav_dashboard">喙佮笖喔娻笟喔福喙屶笖</string>
+    <string name="discovery_stop_scanning">喔涪喔膏笖喔箒喔佮笝</string>
+    <string name="discovery_pair_title">喔堗副喔氞竸喔灌箞喔佮副喔� %1$s?</string>
+    <string name="discovery_bluetooth_scan">喔父喔涏竵喔`笓喙屶笟喔ム腹喔椸腹喔樴笚喔掂箞喔炧笟:</string>
+    <string name="menuitem_bluetooth">喔氞弗喔灌笚喔灌笜</string>
+    <string name="discovery_bluetooth_le_scan">喔父喔涏竵喔`笓喙屶笟喔ム腹喔椸腹喔� LE 喔椸傅喙堗笧喔�:</string>
+    <string name="discovery_connected_devices_hint">喔曕副喙夃竾喔勦箞喔侧腑喔膏笡喔佮福喔撪箤喔傕腑喔囙竸喔膏笓喙冟斧喙夃竸喙夃笝喔炧笟喙勦笖喙�, 喔父喔涏竵喔`笓喙屶笚喔掂箞喙�喔娻阜喙堗腑喔∴笗喙堗腑喔涪喔灌箞喙冟笝喔涏副喔堗笀喔膏笟喔编笝喔∴副喔佮笀喔班竸喙夃笝喔炧笟喙勦浮喙堗竸喙堗腑喔⑧箘喔斷箟, 喙�喔涏复喔斷箖喔娻箟喔囙覆喔權笗喔赤箒喔笝喙堗竾(喙�喔娻箞喔� GPS)喔氞笝 Android 6 喔傕付喙夃笝喙勦笡, 喔涏复喔斷箖喔娻箟喔囙覆喔權笩喔掂箑喔堗腑喔`箤 Privacy Guard 喔佮副喔� Gadgetbridge 喙�喔炧福喔侧赴喔覆喔堗笀喔班笚喔赤箖喔箟喙佮腑喔涏箒喔勦福喔娻箒喔ム箟喔о福喔掂釜喔曕覆喔`箤喔椸腑喔膏笡喔佮福喔撪箤喔傕腑喔囙竸喔膏笓, 喔覆喔佮笢喙堗覆喔權箘喔涏釜喔编竵喔炧副喔佮箒喔ム箟喔о箘喔∴箞喔炧笟喔父喔涏竵喔`笓喙� 喙冟斧喙夃弗喔竾喙冟斧喔∴箞喔傅喔佮竸喔`副喙夃竾喔弗喔编竾喔堗覆喔佮福喔掂釜喔曕覆喔`箤喔椸腑喔膏笡喔佮福喔撪箤喔娻腑喔囙竸喔膏笓</string>
+</resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml
new file mode 100644
index 0000000..e07a316
--- /dev/null
+++ b/app/src/main/res/values-tr/strings.xml
@@ -0,0 +1,3546 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources xmlns:tools="http://schemas.android.com/tools">
+    <string name="Cancel">陌ptal</string>
+    <string name="DEVINFO_HR_VER">"Kalp ritmi: "</string>
+    <string name="Delete">Sil</string>
+    <string name="FetchActivityOperation_about_to_transfer_since">%1$s tarihinden itibaren verileri aktarmak 眉zere</string>
+    <string name="StringUtils_sender">(%1$s)</string>
+    <string name="_pebble_watch_mute">Sessiz</string>
+    <string name="_pebble_watch_open_on_phone">Android ayg谋t谋nda a莽</string>
+    <string name="_pebble_watch_reply">Yan谋tla</string>
+    <string name="_unknown_">(bilinmeyen)</string>
+    <string name="abstract_chart_fragment_kind_activity">Etkinlik</string>
+    <string name="abstract_chart_fragment_kind_deep_sleep">Derin uyku</string>
+    <string name="abstract_chart_fragment_kind_light_sleep">Hafif uyku</string>
+    <string name="abstract_chart_fragment_kind_not_worn">Giyilmedi</string>
+    <string name="action_db_management">Veri y枚netimi</string>
+    <string name="action_debug">Hata Ay谋klama</string>
+    <string name="action_discover">Yeni ayg谋t ba臒la</string>
+    <string name="action_donate">Ba臒谋艧 Yap</string>
+    <string name="action_quit">脟谋k谋艧</string>
+    <string name="action_settings">Ayarlar</string>
+    <string name="activity_DB_ExportButton">Verileri D谋艧a Aktar</string>
+    <string name="activity_DB_delete_legacy_button">Eski veri taban谋n谋 sil</string>
+    <string name="activity_DB_empty_button">Veri Taban谋n谋 Bo艧alt</string>
+    <string name="activity_DB_import_button">Verileri 陌莽e Aktar</string>
+    <string name="activity_DB_test_export_button">Otomatik D谋艧a Aktarmay谋 Ba艧lat</string>
+    <string name="activity_DB_test_export_message">Veri taban谋 d谋艧a aktar谋l谋yor鈥�</string>
+    <string name="activity_db_management_autoexport_explanation">Veri taban谋 otomatik d谋艧a aktarma konumu:</string>
+    <string name="activity_db_management_autoexport_label">Otomatik D谋艧a Aktarma</string>
+    <string name="activity_db_management_empty_DB">Veri taban谋n谋 bo艧alt</string>
+    <string name="activity_db_management_empty_db_warning">Dikkat! Bu d眉臒meye basarak veri taban谋n谋z谋 silecek ve s谋f谋rdan ba艧layacaks谋n谋z.</string>
+    <string name="activity_db_management_exportimport_label">D谋艧a ve 陌莽e Aktar</string>
+    <string name="activity_db_management_import_export_explanation">D谋艧a/i莽e aktarma i艧lemleri, ayg谋t谋n谋zdaki bir dizine giden a艧a臒谋daki yolu kullan谋r. Bu dizine di臒er Android uygulamalar谋 ve bilgisayar谋n谋z taraf谋ndan eri艧ilebilir. Gadgetbridge\'i kald谋r谋rsan谋z, bu dizinin ve i莽erdi臒i t眉m dosyalar谋n silinece臒ini unutmay谋n. Veriler 艧unlar谋 i莽erir:
+\n Export_preference - genel ayarlar
+\n Export_preference_MAC - ayg谋ta 枚zel ayarlar
+\n Gadgetbridge - ayg谋t ve etkinlik veri taban谋
+\n Gadgetbridge_date - bir tarihte d谋艧a aktar谋lan veri taban谋
+\n * .gpx - GPS kay谋tlar谋
+\n * .log - g眉nl眉k dosyalar谋
+\nD谋艧a aktar谋lan dosyalar谋n谋z谋 orada bulmay谋 (veya i莽e aktarmak istedi臒iniz dosyalar谋 oraya yerle艧tirmeyi) bekleyin:</string>
+    <string name="activity_db_management_merge_old_title">Eski veri taban谋n谋 silme</string>
+    <string name="activity_error_no_app_for_gpx">Etkinlik izlemesini g枚r眉nt眉lemek i莽in GPX dosyalar谋n谋 i艧leyebilen bir uygulama kurun.</string>
+    <string name="activity_prefs_about_you">Hakk谋n谋zda</string>
+    <string name="activity_prefs_activetime_minutes">G眉nl眉k hedef: dakika cinsinden etkin zaman</string>
+    <string name="activity_prefs_alarm_max_heart_rate">En y眉ksek kalp ritmi</string>
+    <string name="activity_prefs_alarm_min_heart_rate">En d眉艧眉k kalp ritmi</string>
+    <string name="activity_prefs_calories_burnt">G眉nl眉k hedef: harcanan kalori</string>
+    <string name="activity_prefs_chart_max_heart_rate">En y眉ksek kalp ritmi</string>
+    <string name="activity_prefs_chart_min_heart_rate">En d眉艧眉k kalp ritmi</string>
+    <string name="activity_prefs_charts">脟izelge ayarlar谋</string>
+    <string name="activity_prefs_distance_meters">G眉nl眉k hedef: metre cinsinden mesafe</string>
+    <string name="activity_prefs_gender">Cinsiyet</string>
+    <string name="activity_prefs_height_cm">Boy (cm)</string>
+    <string name="activity_prefs_sleep_duration">Tercih edilen uyku s眉resi (saat cinsinden)</string>
+    <string name="activity_prefs_weight_kg">A臒谋rl谋k (kg)</string>
+    <string name="activity_prefs_year_birth">Do臒um y谋l谋</string>
+    <string name="activity_sleepchart_activity_and_sleep">Etkinlik</string>
+    <string name="activity_summaries">Spor Etkinlikleri</string>
+    <string name="activity_type_activity">Etkinlik</string>
+    <string name="activity_type_biking">Bisiklet</string>
+    <string name="activity_type_deep_sleep">Derin uyku</string>
+    <string name="activity_type_exercise">Egzersiz</string>
+    <string name="activity_type_light_sleep">Hafif uyku</string>
+    <string name="activity_type_not_measured">脰l莽眉lmedi</string>
+    <string name="activity_type_not_worn">Ayg谋t giyilmedi</string>
+    <string name="activity_type_running">Ko艧u</string>
+    <string name="activity_type_swimming">Y眉zme</string>
+    <string name="activity_type_treadmill">Ko艧u band谋</string>
+    <string name="activity_type_unknown">Bilinmeyen etkinlik</string>
+    <string name="activity_type_walking">Y眉r眉me</string>
+    <string name="activity_web_view">Etkinlik Web G枚r眉n眉m眉</string>
+    <string name="add_widget">Widget ekle</string>
+    <string name="alarm_fri_short">Cum</string>
+    <string name="alarm_mon_short">Pzt</string>
+    <string name="alarm_sat_short">Cts</string>
+    <string name="alarm_smart_wakeup">Ak谋ll谋 uyand谋rma</string>
+    <string name="alarm_snooze">Ertele</string>
+    <string name="alarm_sun_short">Paz</string>
+    <string name="alarm_thu_short">Per</string>
+    <string name="alarm_tue_short">Sal</string>
+    <string name="alarm_wed_short">脟ar</string>
+    <string name="always">Her zaman</string>
+    <string name="android_pairing_hint">Ayg谋t谋 e艧le艧tirmek i莽in Android Bluetooth e艧le艧tirme ileti艧imini kullan.</string>
+    <string name="app_configure">Yap谋land谋r</string>
+    <string name="app_install_info">A艧a臒谋daki uygulamay谋 kurmak 眉zeresiniz:
+\n
+\n%1$s
+\nS眉r眉m %2$s, %3$s taraf谋ndan
+\n</string>
+    <string name="app_move_to_top">Yukar谋 ta艧谋</string>
+    <string name="application_name_generic">Gadgetbridge</string>
+    <string name="appinstaller_install">Kur</string>
+    <string name="appmanager_app_openinstore">Pebble uygulama ma臒azas谋nda ara</string>
+    <string name="appmanager_cached_watchapps_watchfaces">脰nbellekteki uygulamalar</string>
+    <string name="appmanager_health_activate">Etkinle艧tir</string>
+    <string name="appmanager_health_deactivate">Devre d谋艧谋 b谋rak</string>
+    <string name="appmanager_hrm_activate">Kalp ritmi izlemeyi etkinle艧tir</string>
+    <string name="appmanager_hrm_deactivate">Kalp ritmi izlemeyi devre d谋艧谋 b谋rak</string>
+    <string name="appmanager_installed_watchapps">Kurulu uygulamalar</string>
+    <string name="appmanager_installed_watchfaces">Kurulu saat aray眉zleri</string>
+    <string name="appmanager_weather_activate">Sistem hava durumu uygulamas谋n谋 etkinle艧tir</string>
+    <string name="appmanager_weather_deactivate">Sistem hava durumu uygulamas谋n谋 devre d谋艧谋 b谋rak</string>
+    <string name="appmanager_weather_install_provider">Hava durumu bildirim uygulamas谋 kur</string>
+    <string name="appmananger_app_delete">Sil</string>
+    <string name="appmananger_app_delete_cache">Sil ve 枚nbellekten kald谋r</string>
+    <string name="appmananger_app_reinstall">Yeniden kur</string>
+    <string name="appversion_by_creator">%1$s, %2$s taraf谋ndan</string>
+    <string name="appwidget_not_connected">Ba臒l谋 de臒il, alarm kurulmad谋.</string>
+    <string name="appwidget_setting_alarm">%1$02d:%2$02d i莽in alarm kur</string>
+    <string name="appwidget_sleep_alarm_widget_label">Uyku Alarm谋</string>
+    <string name="appwidget_text">Zzz</string>
+    <string name="arabic">Arap莽a</string>
+    <string name="authenticating">Kimlik do臒rulan谋yor</string>
+    <string name="authentication_required">Kimlik do臒rulama gerekiyor</string>
+    <string name="automatic">Otomatik</string>
+    <string name="average">Ortalama: %1$s</string>
+    <string name="battery">Pil</string>
+    <string name="blacklist_all_for_notifications">Bildirimler i莽in hepsini kara listeye al</string>
+    <string name="bluetooth_is_disabled_">Bluetooth devre d谋艧谋.</string>
+    <string name="bluetooth_is_not_supported_">Bluetooth desteklenmiyor.</string>
+    <string name="busy_task_fetch_activity_data">Etkinlik verileri getiriliyor</string>
+    <string name="calories">Kalori</string>
+    <string name="candidate_item_device_image">Ayg谋t g枚r眉nt眉s眉</string>
+    <string name="cannot_connect">Ba臒lanam谋yor: %1$s</string>
+    <string name="cannot_connect_bt_address_invalid_">Ba臒lanam谋yor. Bluetooth adresi ge莽ersiz mi\?</string>
+    <string name="chart_no_data_synchronize">Veri yok! Ayg谋t e艧zamanlans谋n m谋\?</string>
+    <string name="chart_steps">Ad谋m</string>
+    <string name="charts_legend_heartrate">Kalp ritmi</string>
+    <string name="choose_auto_export_location">D谋艧a aktarma konumunu se莽</string>
+    <string name="clock">Saat</string>
+    <string name="connected">Ba臒land谋</string>
+    <string name="connecting">Ba臒lan谋yor</string>
+    <string name="control_center_cancel_to_stop_vibration">Titre艧imi durdurmak i莽in iptal et.</string>
+    <string name="control_center_find_lost_device">Kay谋p ayg谋t谋 bul</string>
+    <string name="controlcenter_calibrate_device">Ayg谋t谋 Kalibre Et</string>
+    <string name="controlcenter_change_fm_frequency">FM Frekans谋n谋 De臒i艧tir</string>
+    <string name="controlcenter_change_led_color">LED Rengini De臒i艧tir</string>
+    <string name="controlcenter_connect">Ba臒lan鈥�</string>
+    <string name="controlcenter_delete_device">Ayg谋t谋 Sil</string>
+    <string name="controlcenter_delete_device_dialogmessage">Ayg谋t ve t眉m ili艧kili verileri silinecek!</string>
+    <string name="controlcenter_delete_device_name">%1$s Sil</string>
+    <string name="controlcenter_disconnect">Ba臒lant谋y谋 Kes</string>
+    <string name="controlcenter_fetch_activity_data">E艧zamanla</string>
+    <string name="controlcenter_find_device">Kay谋p ayg谋t谋 bul</string>
+    <string name="controlcenter_navigation_drawer_close">Gezinme 莽ekmecesini kapat</string>
+    <string name="controlcenter_navigation_drawer_open">Gezinme 莽ekmecesini a莽</string>
+    <string name="controlcenter_snackbar_connecting">Ba臒lan谋yor鈥�</string>
+    <string name="controlcenter_snackbar_disconnecting">Ba臒lant谋 Kesiliyor</string>
+    <string name="controlcenter_snackbar_need_longpress">Ba臒lant谋y谋 kesmek i莽in karta uzun bas谋n</string>
+    <string name="controlcenter_snackbar_requested_screenshot">Ayg谋t谋n ekran g枚r眉nt眉s眉 al谋n谋yor</string>
+    <string name="controlcenter_start_activity_tracks">Etkinlik izlemeleriniz</string>
+    <string name="controlcenter_start_activitymonitor">Etkinli臒iniz</string>
+    <string name="controlcenter_start_configure_alarms">Alarmlar谋 yap谋land谋r</string>
+    <string name="controlcenter_take_screenshot">Ekran G枚r眉nt眉s眉 Al</string>
+    <string name="dateformat_date_time"><![CDATA[Saat ve tarih]]></string>
+    <string name="dateformat_time">Saat</string>
+    <string name="dbaccess_error_executing">\'%1$s\' 莽al谋艧t谋r谋l谋rken hata olu艧tu</string>
+    <string name="dbmanagementactivity_database_successfully_deleted">Veriler silindi.</string>
+    <string name="dbmanagementactivity_db_deletion_failed">Veri taban谋n谋 silme ba艧ar谋s谋z oldu.</string>
+    <string name="dbmanagementactivity_delete_activity_data_title">Etkinlik Verileri Silinsin mi\?</string>
+    <string name="dbmanagementactivity_delete_old_activity_db">Eski Etkinlik Veri Taban谋 Silinsin mi\?</string>
+    <string name="dbmanagementactivity_delete_old_activitydb_confirmation">Eski etkinlik veri taban谋 ger莽ekten silinsin mi\? 陌莽e aktar谋lmayan etkinlik verileri kaybolacak.</string>
+    <string name="dbmanagementactivity_error_exporting_db">Veri taban谋 d谋艧a aktar谋l谋rken hata olu艧tu: %1$s</string>
+    <string name="dbmanagementactivity_error_exporting_shared">Tercih d谋艧a aktar谋l谋rken hata olu艧tu: %1$s</string>
+    <string name="dbmanagementactivity_error_importing_db">Veri taban谋 i莽e aktar谋l谋rken hata olu艧tu: %1$s</string>
+    <string name="dbmanagementactivity_error_importing_shared">Tercih i莽e aktar谋l谋rken hata olu艧tu: %1$s</string>
+    <string name="dbmanagementactivity_exported_to">D谋艧a aktar谋ld谋: %1$s</string>
+    <string name="dbmanagementactivity_import_data_title">Veriler 陌莽e Aktar谋ls谋n m谋\?</string>
+    <string name="dbmanagementactivity_import_successful">陌莽e aktar谋ld谋.</string>
+    <string name="dbmanagementactivity_old_activity_db_deletion_failed">Eski etkinlik veri taban谋n谋n silinmesi ba艧ar谋s谋z oldu.</string>
+    <string name="dbmanagementactivity_old_activity_db_successfully_deleted">Eski etkinlik verileri silindi.</string>
+    <string name="dbmanagementactivity_overwrite">脺zerine Yaz</string>
+    <string name="dbmanagementactivity_overwrite_database_confirmation">Ge莽erli verilerin ger莽ekten 眉zerine yaz谋ls谋n m谋? T眉m etkinlik verilerinizin (varsa), ayg谋tlar谋n谋z谋n ve tercihlerinizin 眉zerine yaz谋lacak.</string>
+    <string name="dbmanagementactivity_really_delete_entire_db">Veri taban谋n谋n tamam谋 ger莽ekten silinsin mi\? T眉m etkinlik verileriniz ve ayg谋tlar谋n谋zla ilgili bilgileriniz kaybolacak.</string>
+    <string name="dbmanagementactivvity_cannot_access_export_path">D谋艧a aktarma yoluna eri艧ilemiyor. L眉tfen geli艧tiricilerle ileti艧ime ge莽in.</string>
+    <string name="debugactivity_really_factoryreset">Fabrika ayarlar谋na s谋f谋rlama yap谋lmas谋 ba臒l谋 ayg谋ttaki t眉m verileri (destekleniyorsa) silecektir. Xiaomi/Huami ayg谋tlar谋 ayr谋ca Bluetooth MAC adresini de臒i艧tirir, bu nedenle Gadgetbridge taraf谋ndan yeni bir ayg谋t olarak g枚r眉l眉rler.</string>
+    <string name="debugactivity_really_factoryreset_title">Ger莽ekten fabrika ayarlar谋na s谋f谋rlans谋n m谋\?</string>
+    <string name="device_fw">脺r眉n yaz谋l谋m谋 s眉r眉m眉: %1$s</string>
+    <string name="device_hw">Donan谋m revizyonu: %1$s</string>
+    <string name="device_not_connected">Ba臒lanmad谋.</string>
+    <string name="device_with_rssi">%1$s (%2$s)</string>
+    <string name="devicetype_amazfit_bip">AmazFit Bip</string>
+    <string name="devicetype_amazfit_bip_lite">AmazFit Bip Lite</string>
+    <string name="devicetype_amazfit_bips">AmazFit Bip S</string>
+    <string name="devicetype_amazfit_cor">AmazFit Cor</string>
+    <string name="devicetype_amazfit_cor2">AmazFit Cor 2</string>
+    <string name="devicetype_amazfit_gtr">AmazFit GTR</string>
+    <string name="devicetype_amazfit_gts">AmazFit GTS</string>
+    <string name="devicetype_banglejs">Bangle.js</string>
+    <string name="devicetype_bfh16">BFH-16</string>
+    <string name="devicetype_casiogb6900">Casio GB-6900</string>
+    <string name="devicetype_exrizu_k8">Exrizu K8</string>
+    <string name="devicetype_hplus">HPlus</string>
+    <string name="devicetype_id115">ID115</string>
+    <string name="devicetype_itag">iTag</string>
+    <string name="devicetype_liveview">LiveView</string>
+    <string name="devicetype_makibes_f68">Makibes F68</string>
+    <string name="devicetype_makibes_hr3">Makibes HR3</string>
+    <string name="devicetype_miband">Mi Band</string>
+    <string name="devicetype_miband2">Mi Band 2</string>
+    <string name="devicetype_miband3">Mi Band 3</string>
+    <string name="devicetype_miband4">Mi Band 4</string>
+    <string name="devicetype_mijia_lywsd02">Mijia Smart Clock</string>
+    <string name="devicetype_miscale2">Mi Scale 2</string>
+    <string name="devicetype_mykronoz_zetime">MyKronoz ZeTime</string>
+    <string name="devicetype_no1_f1">No.1 F1</string>
+    <string name="devicetype_pebble">Pebble</string>
+    <string name="devicetype_q8">Q8</string>
+    <string name="devicetype_qhybrid">Fossil Q Hybrid</string>
+    <string name="devicetype_roidmi">Roidmi</string>
+    <string name="devicetype_roidmi3">Roidmi 3</string>
+    <string name="devicetype_teclast_h30">Teclast H30</string>
+    <string name="devicetype_test">Test Ayg谋t谋</string>
+    <string name="devicetype_unknown">Bilinmeyen Ayg谋t</string>
+    <string name="devicetype_vibratissimo">Vibratissimo</string>
+    <string name="devicetype_watch9">Watch 9</string>
+    <string name="devicetype_xwatch">XWatch</string>
+    <string name="devicetype_y5">Y5</string>
+    <string name="discovery_attempting_to_pair">%1$s ile e艧le艧meye 莽al谋艧谋l谋yor</string>
+    <string name="discovery_bonding_failed_immediately">%1$s ile etkile艧im an谋nda ba艧ar谋s谋z oldu.</string>
+    <string name="discovery_connected_devices_hint">Ayg谋t谋n谋z谋 bulunabilir hale getirin. 艦u anda ba臒l谋 ayg谋tlar muhtemelen ke艧fedilmeyecek. Android 6+ i莽in konumu (枚rn. GPS) etkinle艧tirin. Gadgetbridge i莽in Gizlilik Korumas谋n谋 (Privacy Guard) devre d谋艧谋 b谋rak谋n, 莽眉nk眉 莽枚kebilir ve telefonunuzu yeniden ba艧latabilir. Birka莽 dakika sonra hi莽bir ayg谋t bulunmazsa, mobil ayg谋t谋n谋z谋 yeniden ba艧latt谋ktan sonra tekrar deneyin.</string>
+    <string name="discovery_dont_pair">E艧le艧tirme</string>
+    <string name="discovery_enable_bluetooth">Ayg谋tlar谋 bulmak i莽in Bluetooth\'u etkinle艧tir.</string>
+    <string name="discovery_need_to_enter_authkey">Bu ayg谋t i莽in gizli bir kimlik do臒rulama anahtar谋 gerekiyor, girmek i莽in ayg谋ta uzun bas谋n. Wiki sayfas谋n谋 okuyun.</string>
+    <string name="discovery_note">Not:</string>
+    <string name="discovery_pair_question">Ayg谋tlar谋n谋z谋 e艧le艧tirmek i莽in E艧le艧tir se莽ene臒ini se莽in. Bu ba艧ar谋s谋z olursa, e艧le艧tirme olmadan tekrar deneyin.</string>
+    <string name="discovery_pair_title">%1$s ile e艧le艧tirilsin mi?</string>
+    <string name="discovery_start_scanning">Taramay谋 ba艧lat</string>
+    <string name="discovery_stop_scanning">Taramay谋 durdur</string>
+    <string name="discovery_successfully_bonded">Ba臒lanacak: %1$s.</string>
+    <string name="discovery_trying_to_connect_to">Ba臒lanmaya 莽al谋艧谋yor: %1$s</string>
+    <string name="discovery_yes_pair">E艧le艧tir</string>
+    <string name="distance">Mesafe</string>
+    <string name="dutch">Felemenk莽e</string>
+    <string name="edittext_notification_filter_words_hint">Her bir s枚zc眉臒眉 yeni bir sat谋ra girin</string>
+    <string name="english">陌ngilizce</string>
+    <string name="error_creating_directory_for_logfiles">G眉nl眉k dosyalar谋 i莽in dizin olu艧turulurken hata: %1$s</string>
+    <string name="error_no_location_access">Taraman谋n d眉zg眉n 莽al谋艧mas谋 i莽in konum eri艧imi verilmeli ve etkinle艧tirilmelidir</string>
+    <string name="female">Kad谋n</string>
+    <string name="filter_mode">Filtreleme Modu</string>
+    <string name="filter_mode_blacklist">S枚zc眉kler bulundu臒unda engelle</string>
+    <string name="filter_mode_none">Filtreleme yap谋lmas谋n</string>
+    <string name="filter_mode_whitelist">S枚zc眉kler bulundu臒unda g枚ster</string>
+    <string name="filter_submode_all">S枚zc眉klerin t眉m眉</string>
+    <string name="filter_submode_at_least_one">S枚zc眉klerden en az biri</string>
+    <string name="find_device_you_found_it">Buldunuz!</string>
+    <string name="find_lost_device_you_found_it">Buldunuz!</string>
+    <string name="find_my_phone_notification">Telefonumu bul</string>
+    <string name="firmware_install_warning">B陌R 脺R脺N YAZILIMI KURMAYA 脟ALI艦IYORSUNUZ, 陌LERLEMEN陌Z DURUMUNDA R陌SK S陌ZE A陌TT陌R.
+\n
+\n
+\nBu 眉r眉n yaz谋l谋m谋 艧u donan谋m revizyonu i莽indir: %s</string>
+    <string name="french">Frans谋zca</string>
+    <string name="fw_multi_upgrade_notice">艦u anda Mi Band\'谋n谋zda bulunanlar谋n yerine %1$s ve %2$s 眉r眉n yaz谋l谋m谋n谋 kurmak 眉zeresiniz.</string>
+    <string name="fw_upgrade_notice">%s kurmak 眉zeresiniz.</string>
+    <string name="fw_upgrade_notice_amazfitbip">AmazFit Bip ayg谋t谋n谋za %s 眉r眉n yaz谋l谋m谋n谋 kurmak 眉zeresiniz.
+\n
+\nL眉tfen .fw dosyas谋n谋, ard谋ndan .res dosyas谋n谋 ve son olarak .gps dosyas谋n谋 kurdu臒unuzdan emin olun. Saatiniz .fw dosyas谋n谋 kurduktan sonra yeniden ba艧lat谋lacak.
+\n
+\nNot: Bu dosyalar, daha 枚nce kurulanlarla tamamen ayn谋ysa, .res ve .gps dosyalar谋n谋 kurman谋z gerekmez.
+\n
+\n陌LERLEMEN陌Z DURUMUNDA R陌SK S陌ZE A陌TT陌R!</string>
+    <string name="fw_upgrade_notice_amazfitbip_lite">AmazFit Bip Lite ayg谋t谋n谋za %s 眉r眉n yaz谋l谋m谋n谋 kurmak 眉zeresiniz.
+\n
+\nL眉tfen 枚nce .fw dosyas谋n谋, sonra .res dosyas谋n谋 kurun. Saatiniz .fw dosyas谋n谋 kurduktan sonra yeniden ba艧lat谋lacak.
+\n
+\nNot: Daha 枚nce kurulanla tamamen ayn谋ysa .res dosyas谋n谋 kurman谋z gerekmez.
+\n
+\n陌LERLEMEN陌Z DURUMUNDA R陌SK S陌ZE A陌TT陌R!</string>
+    <string name="fw_upgrade_notice_amazfitcor">AmazFit Core ayg谋t谋n谋za %s 眉r眉n yaz谋l谋m谋n谋 kurmak 眉zeresiniz.
+\n
+\nL眉tfen 枚nce .fw dosyas谋n谋, sonra .res dosyas谋n谋 kurun. Bilekli臒iniz .fw dosyas谋n谋 kurduktan sonra yeniden ba艧lat谋lacak.
+\n
+\nNot: Daha 枚nce kurulanla tamamen ayn谋ysa .res dosyas谋n谋 kurman谋z gerekmez.
+\n
+\n陌LERLEMEN陌Z DURUMUNDA R陌SK S陌ZE A陌TT陌R!</string>
+    <string name="fw_upgrade_notice_amazfitcor2">AmazFit Core 2 ayg谋t谋n谋za %s 眉r眉n yaz谋l谋m谋n谋 kurmak 眉zeresiniz.
+\n
+\nL眉tfen 枚nce .fw dosyas谋n谋, sonra .res dosyas谋n谋 kurun. Bilekli臒iniz .fw dosyas谋n谋 kurduktan sonra yeniden ba艧lat谋lacak.
+\n
+\nNot: Daha 枚nce kurulanla tamamen ayn谋ysa .res dosyas谋n谋 kurman谋z gerekmez.
+\n
+\n陌LERLEMEN陌Z DURUMUNDA R陌SK S陌ZE A陌TT陌R!
+\n
+\nTAM OLARAK TEST ED陌LMEM陌艦T陌R, AYGITINIZIN ADI \"Amazfit Band 2\" 陌SE BEATS_W 脺R脺N YAZILIMINI KURMANIZ GEREKEB陌L陌R</string>
+    <string name="fw_upgrade_notice_amazfitgtr">AmazFit GTR ayg谋t谋n谋za %s 眉r眉n yaz谋l谋m谋n谋 kurmak 眉zeresiniz.
+\n
+\nL眉tfen 枚nce .fw dosyas谋n谋, sonra .res dosyas谋n谋, ve son olarak .gps dosyas谋n谋 kurun. Saatiniz .fw dosyas谋n谋 kurduktan sonra yeniden ba艧lat谋lacak.
+\n
+\nNot: Bu dosyalar 枚nceden kurulanlarla tamamen ayn谋ysa .res ve .gps dosyalar谋n谋 kurman谋z gerekmez.
+\n
+\n陌LERLEMEN陌Z DURUMUNDA R陌SK S陌ZE A陌TT陌R!</string>
+    <string name="fw_upgrade_notice_amazfitgts">AmazFit GTS ayg谋t谋n谋za %s 眉r眉n yaz谋l谋m谋n谋 kurmak 眉zeresiniz.
+\n
+\nL眉tfen 枚nce .fw dosyas谋n谋, sonra .res dosyas谋n谋, ve son olarak .gps dosyas谋n谋 kurun. Saatiniz .fw dosyas谋n谋 kurduktan sonra yeniden ba艧lat谋lacak.
+\n
+\nNot: Bu dosyalar 枚nceden kurulanlarla tamamen ayn谋ysa .res ve .gps dosyalar谋n谋 kurman谋z gerekmez.
+\n
+\n陌LERLEMEN陌Z DURUMUNDA R陌SK S陌ZE A陌TT陌R!</string>
+    <string name="fw_upgrade_notice_miband3">Mi Band 3 ayg谋t谋n谋za %s 眉r眉n yaz谋l谋m谋n谋 kurmak 眉zeresiniz.
+\n
+\nL眉tfen 枚nce .fw dosyas谋n谋, sonra .res dosyas谋n谋 kurun. Bilekli臒iniz .fw dosyas谋n谋 kurduktan sonra yeniden ba艧lat谋lacak.
+\n
+\nNot: Daha 枚nce kurulanla tamamen ayn谋ysa .res dosyas谋n谋 kurman谋z gerekmez.
+\n
+\n陌LERLEMEN陌Z DURUMUNDA R陌SK S陌ZE A陌TT陌R!</string>
+    <string name="fw_upgrade_notice_miband4">Mi Band 4 ayg谋t谋n谋za %s 眉r眉n yaz谋l谋m谋n谋 kurmak 眉zeresiniz.
+\n
+\nL眉tfen 枚nce .fw dosyas谋n谋, sonra .res dosyas谋n谋 kurun. Bilekli臒iniz .fw dosyas谋n谋 kurduktan sonra yeniden ba艧lat谋lacak.
+\n
+\nNot: Daha 枚nce kurulanla tamamen ayn谋ysa .res dosyas谋n谋 kurman谋z gerekmez.
+\n
+\n陌LERLEMEN陌Z DURUMUNDA R陌SK S陌ZE A陌TT陌R!</string>
+    <string name="fwapp_install_device_not_ready">Dosya kurulam谋yor, ayg谋t haz谋r de臒il.</string>
+    <string name="fwappinstaller_connection_state">Ayg谋ta ba臒lant谋: %1$s</string>
+    <string name="fwinstaller_firmware_not_compatible_to_device">Bu 眉r眉n yaz谋l谋m谋 bu ayg谋tla uyumlu de臒il</string>
+    <string name="gadgetbridge_running_generic">Gadgetbridge 莽al谋艧谋yor</string>
+    <string name="german">Almanca</string>
+    <string name="heart_rate">Kalp ritmi</string>
+    <string name="horizontal">Yatay</string>
+    <string name="hr_appname_commute">陌艧e Gidip Gelme</string>
+    <string name="hr_appname_stopwatch">Kronometre</string>
+    <string name="hr_appname_wellness">Sa臒l谋k</string>
+    <string name="hr_appname_workout">Egzersiz</string>
+    <string name="hr_widget_active_minutes">Etkin dakikalar</string>
+    <string name="hr_widget_battery">Pil</string>
+    <string name="hr_widget_calories">Kalori</string>
+    <string name="hr_widget_date">Tarih</string>
+    <string name="hr_widget_heart_rate">Kalp ritmi</string>
+    <string name="hr_widget_nothing">Hi莽bir 艦ey</string>
+    <string name="hr_widget_steps">Ad谋m</string>
+    <string name="hr_widget_weather">Hava Durumu</string>
+    <string name="icon_placeholder">Simge</string>
+    <string name="indonesian">Endonezce</string>
+    <string name="initialized">ba艧lat谋ld谋</string>
+    <string name="initializing">Ba艧lat谋l谋yor</string>
+    <string name="installation_failed_">Kurulum ba艧ar谋s谋z oldu</string>
+    <string name="installation_successful">Kuruldu</string>
+    <string name="installer_activity_unable_to_find_handler">Bu dosyay谋 kurmak i莽in bir i艧leyici bulunam谋yor.</string>
+    <string name="installer_activity_wait_while_determining_status">Kurulum durumu belirlenirken l眉tfen bekleyin鈥�</string>
+    <string name="installhandler_firmware_name">%1$s: %2$s %3$s</string>
+    <string name="installing_binary_d_d">%1$d/%2$d kuruluyor</string>
+    <string name="interval_fifteen_minutes">her 15 dakikada bir</string>
+    <string name="interval_five_minutes">her 5 dakikada bir</string>
+    <string name="interval_forty_five_minutes">her 45 dakikada bir</string>
+    <string name="interval_one_hour">saatte 1 defa</string>
+    <string name="interval_one_minute">dakikada 1 defa</string>
+    <string name="interval_ten_minutes">her 10 dakikada bir</string>
+    <string name="interval_thirty_minutes">her 30 dakikada bir</string>
+    <string name="italian">陌talyanca</string>
+    <string name="japanese">Japonca</string>
+    <string name="kind_firmware">脺r眉n yaz谋l谋m谋</string>
+    <string name="kind_font">Yaz谋 tipi</string>
+    <string name="kind_gps">GPS 脺r眉n Yaz谋l谋m谋</string>
+    <string name="kind_gps_almanac">GPS Almanak</string>
+    <string name="kind_gps_cep">GPS Hata D眉zeltme</string>
+    <string name="kind_invalid">Ge莽ersiz veri</string>
+    <string name="kind_resources">Kaynaklar</string>
+    <string name="kind_watchface">Saat Aray眉z眉</string>
+    <string name="korean">Korece</string>
+    <string name="lack_of_sleep">Eksik uyku: %1$s</string>
+    <string name="lack_of_step">Eksik ad谋m say谋s谋: %1$d</string>
+    <string name="language_and_region_prefs">Dil ve b枚lge ayarlar谋</string>
+    <string name="left">Sol</string>
+    <string name="live_activity_current_steps_per_minute">艦u anki ad谋m say谋s谋/dakika</string>
+    <string name="live_activity_heart_rate">Kalp ritmi</string>
+    <string name="live_activity_max_heart_rate">艦u anki / En y眉ksek kalp ritmi: %1$d / %2$d</string>
+    <string name="live_activity_start_your_activity">Etkinli臒inizi ba艧lat谋n</string>
+    <string name="live_activity_steps_history">Ad谋m say谋s谋 ge莽mi艧i</string>
+    <string name="live_activity_steps_per_minute_history">Dakikada ad谋m say谋s谋 ge莽mi艧i</string>
+    <string name="live_activity_total_steps">Toplam ad谋m say谋s谋</string>
+    <string name="liveactivity_live_activity">Canl谋 etkinlik</string>
+    <string name="male">Erkek</string>
+    <string name="maximum_duration">S眉re</string>
+    <string name="menuitem_activity">Egzersiz Ge莽mi艧i</string>
+    <string name="menuitem_alarm">Alarm</string>
+    <string name="menuitem_alipay">Alipay</string>
+    <string name="menuitem_compass">Pusula</string>
+    <string name="menuitem_more">Daha Fazla</string>
+    <string name="menuitem_music">M眉zik</string>
+    <string name="menuitem_nfc">NFC</string>
+    <string name="menuitem_notifications">Bildirimler</string>
+    <string name="menuitem_settings">Ayarlar</string>
+    <string name="menuitem_status">Durum</string>
+    <string name="menuitem_timer">Geri Say谋m</string>
+    <string name="menuitem_weather">Hava Durumu</string>
+    <string name="message_cannot_pair_no_mac">MAC adresi verilmedi, e艧le艧tirilemiyor.</string>
+    <string name="mi2_dnd_automatic">Otomatik (uyku alg谋lama)</string>
+    <string name="mi2_dnd_off">Kapal谋</string>
+    <string name="mi2_dnd_scheduled">Zamanland谋 (zaman aral谋臒谋)</string>
+    <string name="mi2_enable_text_notifications">Metin bildirimleri</string>
+    <string name="mi2_enable_text_notifications_summary"><![CDATA[1.0.1.28 veya 眉st眉 bir 眉r眉n yaz谋l谋m谋 ve Mili_pro.ft* kurulu olmas谋 gerekiyor.]]></string>
+    <string name="mi2_fw_installhandler_fw53_hint">Bu 眉r眉n yaz谋l谋m谋n谋 kurmadan 枚nce %1$s s眉r眉m眉n眉 kurmal谋s谋n谋z!</string>
+    <string name="mi2_prefs_activate_display_on_lift">Kald谋rma an谋nda ekran谋 etkinle艧tir</string>
+    <string name="mi2_prefs_button_action">D眉臒me eylemini etkinle艧tir</string>
+    <string name="mi2_prefs_button_action_summary">Belirtilen say谋da d眉臒meye bas谋ld谋臒谋nda eylemi etkinle艧tir</string>
+    <string name="mi2_prefs_button_action_vibrate">Bileklik titre艧imini etkinle艧tir</string>
+    <string name="mi2_prefs_button_action_vibrate_summary">Tetiklenen d眉臒me eyleminde bileklik titre艧imini etkinle艧tir</string>
+    <string name="mi2_prefs_button_actions">D眉臒me eylemleri</string>
+    <string name="mi2_prefs_button_actions_summary">D眉臒me basma eylemlerini belirtin</string>
+    <string name="mi2_prefs_button_press_broadcast">G枚nderilecek yay谋nlama mesaj谋</string>
+    <string name="mi2_prefs_button_press_broadcast_default_value">nodomain.freeyourgadget.gadgetbridge.ButtonPressed</string>
+    <string name="mi2_prefs_button_press_broadcast_summary">Etkinlikle birlikte g枚nderilen yay谋nlama mesaj谋. `button_id` parametresi otomatik olarak her mesaja eklenir.</string>
+    <string name="mi2_prefs_button_press_count">D眉臒me basma say谋s谋</string>
+    <string name="mi2_prefs_button_press_count_max_delay">Basmalar aras谋ndaki azami gecikme</string>
+    <string name="mi2_prefs_button_press_count_max_delay_summary">Milisaniye cinsinden basmalar aras谋ndaki azami gecikme</string>
+    <string name="mi2_prefs_button_press_count_summary">Etkinlik 1\'i tetiklemek i莽in gerekli d眉臒me basma say谋s谋. Sonraki ayn谋 miktarda basmalar Etkinlik 2\'yi tetikler ve bu b枚yle devam eder.</string>
+    <string name="mi2_prefs_display_items">G枚r眉nt眉lenecek 枚geler</string>
+    <string name="mi2_prefs_display_items_summary">Bileklik ekran谋nda g枚r眉nt眉lenen 枚geleri se莽in</string>
+    <string name="mi2_prefs_do_not_disturb">Rahats谋z Etme</string>
+    <string name="mi2_prefs_do_not_disturb_end">Biti艧 zaman谋</string>
+    <string name="mi2_prefs_do_not_disturb_start">Ba艧lama zaman谋</string>
+    <string name="mi2_prefs_do_not_disturb_summary">Etkinken bileklik bildirim almayacak</string>
+    <string name="mi2_prefs_goal_notification">Hedefe ula艧ma bildirimi</string>
+    <string name="mi2_prefs_goal_notification_summary">G眉nl眉k ad谋m say谋s谋 hedefine ula艧谋ld谋臒谋nda bileklik titreyecek</string>
+    <string name="mi2_prefs_inactivity_warnings">Hareketsizlik uyar谋lar谋</string>
+    <string name="mi2_prefs_inactivity_warnings_dnd_summary">Belirli zaman aras谋nda Hareketsizlik Uyar谋s谋 vermesin</string>
+    <string name="mi2_prefs_inactivity_warnings_summary">Belirli s眉re hareketsiz kald谋臒谋n谋zda bileklik titreyecek</string>
+    <string name="mi2_prefs_inactivity_warnings_threshold">Hareketsizlik s谋n谋r谋 (dakika olarak)</string>
+    <string name="mi2_prefs_rotate_wrist_to_switch_info">Bilgileri de臒i艧tirmek i莽in bile臒i d枚nd眉r</string>
+    <string name="mi3_night_mode_sunset">G眉n bat谋m谋nda</string>
+    <string name="mi3_prefs_band_screen_unlock">Bileklik ekran kilidini a莽ma</string>
+    <string name="mi3_prefs_band_screen_unlock_summary">Bileklik ekran kilidini a莽mak i莽in yukar谋 kayd谋r</string>
+    <string name="mi3_prefs_night_mode">Gece modu</string>
+    <string name="mi3_prefs_night_mode_summary">Geceleri bilekli臒in ekran parlakl谋臒谋n谋 otomatik olarak azalt</string>
+    <string name="miband2_prefs_dateformat">Tarih bi莽imi</string>
+    <string name="miband2_prefs_timeformat">Mi2: Zaman bi莽imi</string>
+    <string name="miband_firmware_known">Bu 眉r眉n yaz谋l谋m谋 test edilmi艧tir ve Gadgetbridge ile uyumlu oldu臒u bilinmektedir.</string>
+    <string name="miband_firmware_suggest_whitelist">Her 艧ey d眉zg眉n 莽al谋艧谋yorsa, l眉tfen Gadgetbridge geli艧tiricilerine %s 眉r眉n yaz谋l谋m谋 s眉r眉m眉n眉 beyaz listeye eklemelerini s枚yleyin.</string>
+    <string name="miband_firmware_unknown_warning">Bu 眉r眉n yaz谋l谋m谋 test edilmemi艧tir ve Gadgetbridge ile uyumlu olmayabilir.
+\n
+\nKurulmas谋 TAVS陌YE ED陌LMEZ!</string>
+    <string name="miband_fwinstaller_compatible_version">Uyumlu s眉r眉m</string>
+    <string name="miband_fwinstaller_incompatible_version">Uyumsuz 眉r眉n yaz谋l谋m谋</string>
+    <string name="miband_fwinstaller_untested_version">Test edilmemi艧 s眉r眉m!</string>
+    <string name="miband_pairing_tap_hint">Bilekli臒iniz titre艧ip yan谋p s枚nd眉臒眉nde, arka arkaya birka莽 kez dokunun.</string>
+    <string name="miband_pairing_using_dummy_userdata">Ge莽erli kullan谋c谋 verisi verilmedi, 艧imdilik model kullan谋c谋 verisi kullan谋l谋yor.</string>
+    <string name="miband_prefs_alias">Ad/Takma ad</string>
+    <string name="miband_prefs_device_time_offset_hours">Saat cinsinden ayg谋t zaman谋 farkl谋l谋臒谋 (vardiyal谋 莽al谋艧anlar谋n uykusunu alg谋lamak i莽in)</string>
+    <string name="miband_prefs_fitness_goal">G眉nl眉k ad谋m say谋s谋 hedefi</string>
+    <string name="miband_prefs_hr_sleep_detection">Uyku alg谋lamas谋n谋 iyile艧tirmek i莽in kalp ritmi sens枚r眉n眉 kullan</string>
+    <string name="miband_prefs_reserve_alarm_calendar">Gelecek etkinlikler i莽in ayr谋lacak alarmlar</string>
+    <string name="miband_prefs_vibration">Titre艧im</string>
+    <string name="minutes_1">1 dakika</string>
+    <string name="minutes_10">10 dakika</string>
+    <string name="minutes_30">30 dakika</string>
+    <string name="minutes_5">5 dakika</string>
+    <string name="mode_configuration">Mod Yap谋land谋rmas谋</string>
+    <string name="n_a">YOK</string>
+    <string name="never">Asla</string>
+    <string name="no_data">Veri yok</string>
+    <string name="no_limit">S谋n谋rs谋z</string>
+    <string name="norwegian_bokmal">Bokm氓l Norve莽莽e</string>
+    <string name="not_connected">Ba臒l谋 de臒il</string>
+    <string name="notif_battery_low">%1$s pil d眉艧眉k</string>
+    <string name="notif_battery_low_bigtext_last_charge_time">Son 艧arj: %s
+\n</string>
+    <string name="notif_battery_low_bigtext_number_of_charges">艦arj say谋s谋: %s</string>
+    <string name="notif_battery_low_extended">%1$s pil d眉艧眉k: %2$s</string>
+    <string name="notif_battery_low_percent">%1$s pil kald谋: %2$s%%</string>
+    <string name="notif_battery_low_title">Ayg谋t pili d眉艧眉k!</string>
+    <string name="notif_export_failed_title">Veri taban谋n谋 d谋艧a aktarma ba艧ar谋s谋z oldu! L眉tfen ayarlar谋n谋z谋 g枚zden ge莽irin.</string>
+    <string name="notification_channel_high_priority_name">Y眉ksek 枚ncelik</string>
+    <string name="notification_channel_name">Genel</string>
+    <string name="off">Kapal谋</string>
+    <string name="ok">Tamam</string>
+    <string name="on">A莽谋k</string>
+    <string name="other">Di臒er</string>
+    <string name="overslept">Fazla uyku: %1$s</string>
+    <string name="overstep">Fazla ad谋m: %1$d</string>
+    <string name="pairing">%s ile e艧le艧tiriliyor鈥�</string>
+    <string name="pairing_already_bonded">%1$s (%2$s) ile zaten etkile艧imde, ba臒lan谋yor鈥�</string>
+    <string name="pairing_creating_bond_with">%1$s (%2$s) ile etkile艧im ba艧l谋yor</string>
+    <string name="pairing_in_progress">Etkile艧im s眉r眉yor: %1$s (%2$s)</string>
+    <string name="pairing_unable_to_pair_with">%1$s (%2$s) ile e艧le艧tirilemiyor</string>
+    <string name="pbw_install_handler_hw_revision_mismatch">Verilen 眉r眉n yaz谋l谋m谋 kurulam谋yor: Pebble ayg谋t谋n谋z谋n donan谋m revizyonuyla e艧le艧miyor.</string>
+    <string name="pbw_install_handler_unable_to_install">Verilen dosya kurulam谋yor: %1$s</string>
+    <string name="pbw_installhandler_pebble_firmware">Pebble S眉r眉m眉 %1$s</string>
+    <string name="pbwinstallhandler_app_item">%1$s (%2$s)</string>
+    <string name="pbwinstallhandler_correct_hw_revision">Donan谋m revizyonu do臒ru</string>
+    <string name="pbwinstallhandler_incorrect_hw_revision">Donan谋m revizyonu e艧le艧miyor!</string>
+    <string name="pebble_pairing_hint">Android ayg谋t谋n谋zda bir e艧le艧me ileti艧im kutusu a莽谋lacak. A莽谋lmazsa, bildirim 莽ekmecesine bak谋n ve e艧le艧tirme iste臒ini kabul edin. Daha sonra Pebble 眉zerinde kabul edin.</string>
+    <string name="polish">Leh莽e</string>
+    <string name="portuguese">Portekizce</string>
+    <string name="pref_auto_fetch">Etkinlik verilerini otomatik getir</string>
+    <string name="pref_auto_fetch_limit_fetches">Getirmeler aras谋ndaki asgari s眉re</string>
+    <string name="pref_auto_fetch_limit_fetches_summary">Her %d dakikada bir getirir</string>
+    <string name="pref_auto_fetch_summary">Getirme, ekran kilidi a莽谋ld谋臒谋nda ger莽ekle艧ir. Yaln谋zca bir kilit mekanizmas谋 ayarlanm谋艧sa 莽al谋艧谋r!</string>
+    <string name="pref_blacklist_calendars">Kara Liste Takvimleri</string>
+    <string name="pref_button_action_disabled">Engellenmi艧</string>
+    <string name="pref_button_action_disabled_value">Bilinmeyen</string>
+    <string name="pref_call_privacy_mode_complete">Ad ve numaray谋 gizle</string>
+    <string name="pref_call_privacy_mode_name">Ad谋 gizle ama numaray谋 g枚ster</string>
+    <string name="pref_call_privacy_mode_number">Numaray谋 gizle ama ad谋 g枚ster</string>
+    <string name="pref_call_privacy_mode_off">Ad ve numaray谋 g枚ster</string>
+    <string name="pref_chart_heartrate_color_orange">Turuncu</string>
+    <string name="pref_chart_heartrate_color_red">K谋rm谋z谋</string>
+    <string name="pref_chart_sleep_rolling_24_off">脰臒leden 枚臒leye</string>
+    <string name="pref_chart_sleep_rolling_24_on">Son 24 saat</string>
+    <string name="pref_charts_range_off">Haftal谋k ortalama 莽izelgesi</string>
+    <string name="pref_charts_range_on">Ayl谋k ortalama 莽izelgesi</string>
+    <string name="pref_default">脰ntan谋ml谋</string>
+    <string name="pref_disable_new_ble_scanning">Yeni BLE (BlueTooth D眉艧眉k Enerji) taramas谋n谋 devre d谋艧谋 b谋rak</string>
+    <string name="pref_display_add_device_fab">Yeni ayg谋t ba臒la d眉臒mesi</string>
+    <string name="pref_display_add_device_fab_off">Yaln谋zca hi莽bir ayg谋t eklenmedi臒inde g枚r眉lebilir</string>
+    <string name="pref_display_add_device_fab_on">Her zaman g枚r眉lebilir</string>
+    <string name="pref_header_activitytrackers">Etkinlik izleyiciler</string>
+    <string name="pref_header_auto_export">Otomatik d谋艧a aktar</string>
+    <string name="pref_header_auto_fetch">Otomatik getir</string>
+    <string name="pref_header_cannned_messages">Mesaj 艧ablonlar谋</string>
+    <string name="pref_header_charts">脟izelge Ayarlar谋</string>
+    <string name="pref_header_datetime">Tarih ve Saat</string>
+    <string name="pref_header_development">Geli艧mi艧 se莽enekler</string>
+    <string name="pref_header_general">Genel Ayarlar</string>
+    <string name="pref_header_location">Konum</string>
+    <string name="pref_header_notifications">Bildirimler</string>
+    <string name="pref_header_pebble_timeline">Pebble zaman 莽izelgesi</string>
+    <string name="pref_header_privacy">Gizlilik</string>
+    <string name="pref_header_vibration_count">Titre艧im say谋s谋</string>
+    <string name="pref_header_vibration_settings">Titreme ayarlar谋</string>
+    <string name="pref_invalid_frequency_message">L眉tfen 87.5 ve 108.0 aras谋nda bir frekans girin</string>
+    <string name="pref_invalid_frequency_title">Ge莽ersiz frekans</string>
+    <string name="pref_media_forward">Sonrakine Ge莽</string>
+    <string name="pref_media_forward_value">SONRAK陌</string>
+    <string name="pref_media_next">Sonraki Medya</string>
+    <string name="pref_media_next_value">SONRAK陌</string>
+    <string name="pref_media_pause">Medya Duraklatma</string>
+    <string name="pref_media_pause_value">DURAKLAT</string>
+    <string name="pref_media_play">Medya Oynat</string>
+    <string name="pref_media_play_value">OYNAT</string>
+    <string name="pref_media_playpause">Oynatmay谋 a莽/kapat</string>
+    <string name="pref_media_playpause_value">A脟KAPAT</string>
+    <string name="pref_media_previous">脰nceki Medya</string>
+    <string name="pref_media_previous_value">脰NCEK陌</string>
+    <string name="pref_media_rewind">Geriye Atla</string>
+    <string name="pref_media_rewind_value">GER陌SAR</string>
+    <string name="pref_media_volumedown">Sesi D眉艧眉r</string>
+    <string name="pref_media_volumedown_value">SESD脺艦脺R</string>
+    <string name="pref_media_volumeup">Sesi Y眉kselt</string>
+    <string name="pref_media_volumeup_value">SESY脺KSELT</string>
+    <string name="pref_pebble_privacy_mode_complete">Yaln谋zca bildirim simgesini g枚ster</string>
+    <string name="pref_pebble_privacy_mode_content">Bildirim metnini ekran d谋艧谋na kayd谋r</string>
+    <string name="pref_pebble_privacy_mode_off">Normal bildirimler</string>
+    <string name="pref_rtl_max_line_length">Sa臒dan Sola En Fazla Sat谋r Uzunlu臒u</string>
+    <string name="pref_rtl_max_line_length_summary">Sa臒dan sola metinlerin ayr谋ld谋臒谋 sat谋rlar谋 uzat谋r veya k谋salt谋r</string>
+    <string name="pref_screen_notification_profile_alarm_clock">Alarm saati</string>
+    <string name="pref_screen_notification_profile_anti_loss">Kay谋p 枚nleme uyar谋s谋</string>
+    <string name="pref_screen_notification_profile_calendar">Takvim bildirimi</string>
+    <string name="pref_screen_notification_profile_email">E-Posta bildirimi</string>
+    <string name="pref_screen_notification_profile_generic">Genel bildirim</string>
+    <string name="pref_screen_notification_profile_generic_chat">Sohbet</string>
+    <string name="pref_screen_notification_profile_generic_navigation">Navigasyon</string>
+    <string name="pref_screen_notification_profile_generic_social">Sosyal a臒</string>
+    <string name="pref_screen_notification_profile_inactivity">Hareketsizlik bildirimi</string>
+    <string name="pref_screen_notification_profile_incoming_call">Gelen 莽a臒r谋 bildirimi</string>
+    <string name="pref_screen_notification_profile_low_power">D眉艧眉k pil uyar谋s谋</string>
+    <string name="pref_screen_notification_profile_missed_call">Cevaps谋z 莽a臒r谋 bildirimi</string>
+    <string name="pref_screen_notification_profile_sms">SMS bildirimi</string>
+    <string name="pref_screen_vibration_profile">Titre艧im profili</string>
+    <string name="pref_summary_allow_high_mtu">Aktar谋m h谋z谋n谋 art谋r谋r, ancak baz谋 Android ayg谋tlarda 莽al谋艧mayabilir.</string>
+    <string name="pref_summary_authkey">Kimlik do臒rulama anahtar谋n谋, ba臒lanmak istedi臒iniz t眉m Android ayg谋tlar谋n谋zda ortak bir anahtar olarak de臒i艧tirin. T眉m ayg谋tlar i莽in 枚nceki 枚ntan谋ml谋 anahtar: 0123456789@ABCDE</string>
+    <string name="pref_summary_auto_export_interval">Her %d saatte bir d谋艧a aktar</string>
+    <string name="pref_summary_autoremove_notifications">Telefondan kapat谋ld谋臒谋nda bildirimler ayg谋ttan otomatik olarak kald谋r谋l谋r</string>
+    <string name="pref_summary_contextual_arabic">Arap莽a i莽eri臒i desteklemek i莽in bunu etkinle艧tirin</string>
+    <string name="pref_summary_custom_deviceicon">Ba臒land谋臒谋nda Gadgetbridge simgesi yerine ayg谋ta 枚zel bir Android bildirim simgesi g枚ster</string>
+    <string name="pref_summary_datetime_syctimeonconnect">Ba臒lan谋rken, Android ayg谋t谋nda saat veya saat dilimi de臒i艧ti臒inde ve d眉zenli aral谋klarla saati Gadgetbridge ayg谋t(lar)谋yla e艧zamanla</string>
+    <string name="pref_summary_disable_new_ble_scanning">Tarama s谋ras谋nda ayg谋t谋n谋z bulunam谋yorsa bu se莽ene臒i i艧aretleyin</string>
+    <string name="pref_summary_dont_ack_transfers">Bilekli臒e onay (ACK) g枚nderilmezse, etkinlik verileri temizlenmez. GB di臒er uygulamalarla birlikte kullan谋l谋yorsa kullan谋艧l谋d谋r.</string>
+    <string name="pref_summary_enable_calendar_sync">Takvim etkinliklerini zaman 莽izelgesine g枚nder</string>
+    <string name="pref_summary_enable_outgoing_call">Bunun devre d谋艧谋 b谋rak谋lmas谋, Pebble 2/LE ayg谋t谋n谋n giden aramalarda titremesini durduracakt谋r</string>
+    <string name="pref_summary_enable_pebblekit">PebbleKit kullan谋rken Android uygulamalar谋 i莽in deneysel deste臒i etkinle艧tirin</string>
+    <string name="pref_summary_expose_hr">Gadgetbridge ba臒l谋yken di臒er uygulamalar谋n kalp ritmi verilerine ger莽ek zamanl谋 eri艧mesine izin verir</string>
+    <string name="pref_summary_force_white_color_scheme">Saatinizin koyu renkli ibreleri varsa kullan谋艧l谋d谋r</string>
+    <string name="pref_summary_keep_data_on_device">E艧zamanlamadan sonra bile etkinlik verilerini ayg谋t 眉zerinde tutar. GB di臒er uygulamalarla birlikte kullan谋l谋yorsa kullan谋艧l谋d谋r. Bu, saatin alan谋n谋n bitmesine ve/veya d眉zg眉n e艧zamanlaman谋n durmas谋na neden olabilir.</string>
+    <string name="pref_summary_location_keep_uptodate">Ge莽erli konumu 莽al谋艧ma zaman谋nda almaya 莽al谋艧, depolanan konumu yedek olarak kullan</string>
+    <string name="pref_summary_low_latency_fw_update">Bu, 眉r眉n yaz谋l谋m谋 kurulmas谋n谋n ba艧ar谋s谋z oldu臒u ayg谋tlarda yard谋mc谋 olabilir.</string>
+    <string name="pref_summary_minimize_priority_off">Durum 莽ubu臒unda simge ve kilit ekran谋nda bildirim g枚sterilir</string>
+    <string name="pref_summary_minimize_priority_on">Durum 莽ubu臒unda simge ve kilit ekran谋nda bildirim gizlenir</string>
+    <string name="pref_summary_notification_filter">Telefonda Rahats谋z Etme 枚zelli臒i etkinle艧tirildi臒inde t眉m bildirimleri engelle</string>
+    <string name="pref_summary_notifications_pebblemsg">PebbleKit 眉zerinden Pebble ayg谋t谋na bildirim g枚nderen uygulamalar i莽in destek.</string>
+    <string name="pref_summary_pebble_always_ack_pebblekit">Harici 3. taraf uygulamalar谋na g枚nderilen mesajlar谋n her zaman ve hemen onaylanmas谋na neden olur</string>
+    <string name="pref_summary_pebble_enable_applogs">Saat uygulamalar谋ndaki g眉nl眉klerin Gadgetbridge taraf谋ndan g眉nl眉臒e kaydedilmesine neden olur (yeniden ba臒lanma gerekir)</string>
+    <string name="pref_summary_pebble_enable_bgjs">Etkinle艧tirildi臒inde, saat aray眉zlerinin hava durumu, pil bilgisi vs. g枚stermesine izin verir.</string>
+    <string name="pref_summary_pebble_forcele">Klasik BT yerine t眉m Pebble\'ler i莽in deneysel Pebble LE deste臒i kullan谋n. Bu 枚nce LE olmayan ile ve daha sonra Pebble LE ile e艧le艧tirme gerektirir</string>
+    <string name="pref_summary_pebble_forceprotocol">Bu se莽enek, 眉r眉n yaz谋l谋m谋 s眉r眉m眉ne ba臒l谋 olarak en son bildirim protokol眉n眉 kullanmaya zorlar. NE YAPTI臑INIZI B陌L陌N!</string>
+    <string name="pref_summary_pebble_forceuntested">Test edilmemi艧 枚zellikleri etkinle艧tir. NE YAPTI臑INIZI B陌L陌N!</string>
+    <string name="pref_summary_pebble_gatt_clientonly">Bu yaln谋zca Pebble 2 i莽indir ve deneyseldir, ba臒lant谋 sorunlar谋n谋z varsa bunu deneyin</string>
+    <string name="pref_summary_pebble_health_store_raw">Verileri \"oldu臒u gibi\" saklar, daha sonra yorumlanabilmesi i莽in veri taban谋 kullan谋m谋n谋 art谋r谋r.</string>
+    <string name="pref_summary_pebble_mtu_limit">E臒er Pebble 2/Pebble LE beklendi臒i gibi 莽al谋艧mazsa, MTU s谋n谋rlamas谋 i莽in bu ayar谋 deneyin (ge莽erli aral谋k 20鈥�512)</string>
+    <string name="pref_summary_relax_firmware_checks">脺r眉n yaz谋l谋m谋 denetimlerini gev艧et</string>
+    <string name="pref_summary_rtl">Ayg谋t谋n谋z sa臒dan sola dilleri g枚steremiyorsa bunu etkinle艧tirin</string>
+    <string name="pref_summary_setup_bt_pairing">Ba臒lanma konusunda sorun ya艧谋yorsan谋z bunu devre d谋艧谋 b谋rak谋n</string>
+    <string name="pref_summary_sunrise_sunset">Pebble zaman 莽izelgesine konuma g枚re g眉n do臒umu ve g眉n bat谋m谋 saatlerini g枚nder</string>
+    <string name="pref_summary_sync_calendar">Ba臒lant谋 kesildi臒inde bile takvim uyar谋lar谋n谋 etkinle艧tirir</string>
+    <string name="pref_summary_transliteration">Ayg谋t谋n谋zda dilinize ait yaz谋 tipi deste臒i yoksa bunu etkinle艧tirin</string>
+    <string name="pref_summary_use_custom_font">Ayg谋t谋n谋z, Emoji deste臒i i莽in 枚zel bir yaz谋 tipi yaz谋l谋m谋na sahipse bunu etkinle艧tirin</string>
+    <string name="pref_theme_dark">Koyu</string>
+    <string name="pref_theme_light">A莽谋k</string>
+    <string name="pref_theme_value_dark">karanl谋k</string>
+    <string name="pref_theme_value_light">ayd谋nl谋k</string>
+    <string name="pref_title_allow_high_mtu">Y眉ksek MTU\'ya izin ver</string>
+    <string name="pref_title_audio_player">Tercih Edilen Ses Oynat谋c谋</string>
+    <string name="pref_title_authkey">Kimlik Do臒rulama Anahtar谋</string>
+    <string name="pref_title_auto_export_enabled">Otomatik d谋艧a aktarma etkinle艧tirildi</string>
+    <string name="pref_title_auto_export_interval">D谋艧a aktarma aral谋臒谋</string>
+    <string name="pref_title_auto_export_location">D谋艧a aktarma konumu</string>
+    <string name="pref_title_autoremove_notifications">Kapat谋lan bildirimleri otomatik kald谋r</string>
+    <string name="pref_title_call_privacy_mode">脟a臒r谋 gizlilik modu</string>
+    <string name="pref_title_canned_messages_dismisscall">脟a臒r谋 Kapatma</string>
+    <string name="pref_title_canned_messages_set">Ayg谋tta g眉ncelleme</string>
+    <string name="pref_title_canned_replies">Yan谋tlar</string>
+    <string name="pref_title_canned_reply_suffix">Ortak son ek</string>
+    <string name="pref_title_chart_heartrate_color">Kalp ritmi rengi</string>
+    <string name="pref_title_chart_sleep_rolling_24_hour">Uyku aral谋臒谋</string>
+    <string name="pref_title_charts_average">脟izelgelerde ortalamalar谋 g枚ster</string>
+    <string name="pref_title_charts_range">脟izelge Aral谋臒谋</string>
+    <string name="pref_title_charts_swipe">Etkinlik 莽izelgelerinde sola/sa臒a kayd谋rmay谋 etkinle艧tir</string>
+    <string name="pref_title_contextual_arabic">Arap莽a 陌莽erik</string>
+    <string name="pref_title_custom_deviceicon">Ayg谋ta 枚zel bildirim simgesini g枚ster</string>
+    <string name="pref_title_datetime_syctimeonconnect">Saati e艧zamanla</string>
+    <string name="pref_title_development_miaddr">Mi Band adresi</string>
+    <string name="pref_title_dont_ack_transfer">Etkinlik veri aktar谋m谋n谋 ACK (onaylama) yapma</string>
+    <string name="pref_title_enable_calendar_sync">Takvimi e艧zamanla</string>
+    <string name="pref_title_enable_outgoing_call">Giden 莽a臒r谋lar谋 destekle</string>
+    <string name="pref_title_enable_pebblekit">3. taraf Android Uygulama eri艧imine izin ver</string>
+    <string name="pref_title_expose_hr">3. taraf ger莽ek zamanl谋 kalp ritmi eri艧imi</string>
+    <string name="pref_title_force_white_color_scheme">Beyaz 眉zerinde siyah renk 艧emas谋n谋 zorla</string>
+    <string name="pref_title_general_autoconnectonbluetooth">Bluetooth a莽谋kken Gadgetbridge ayg谋t(lar)谋na ba臒lan</string>
+    <string name="pref_title_general_autoreconnect">Otomatik yeniden ba臒lan</string>
+    <string name="pref_title_general_autostartonboot">Otomatik ba艧lat</string>
+    <string name="pref_title_keep_data_on_device">Etkinlik verilerini ayg谋tta sakla</string>
+    <string name="pref_title_language">Dil</string>
+    <string name="pref_title_location_aquire">Konumu al</string>
+    <string name="pref_title_location_keep_uptodate">Konumu g眉ncel tut</string>
+    <string name="pref_title_location_latitude">Enlem</string>
+    <string name="pref_title_location_longitude">Boylam</string>
+    <string name="pref_title_low_latency_fw_update">脺r眉n yaz谋l谋m谋 kurarken d眉艧眉k gecikme modu kullan</string>
+    <string name="pref_title_minimize_priority">Gadgetbridge bildirimini gizle</string>
+    <string name="pref_title_notification_filter">Rahats谋z Etme</string>
+    <string name="pref_title_notifications_call">Telefon 脟a臒r谋lar谋</string>
+    <string name="pref_title_notifications_generic">Genel bildirim deste臒i</string>
+    <string name="pref_title_notifications_pebblemsg">Pebble Mesajlar谋</string>
+    <string name="pref_title_notifications_repetitions">Tekrarlar</string>
+    <string name="pref_title_notifications_sms">SMS</string>
+    <string name="pref_title_notifications_timeout">Bildirimler aras谋ndaki asgari s眉re</string>
+    <string name="pref_title_pebble_activitytracker">Tercih edilen etkinlik izleyici</string>
+    <string name="pref_title_pebble_always_ack_pebblekit">Erken ACK PebbleKit</string>
+    <string name="pref_title_pebble_enable_applogs">陌zleme uygulamas谋 kaydetmeyi etkinle艧tir</string>
+    <string name="pref_title_pebble_enable_bgjs">Arka plan JS\'sini etkinle艧tir</string>
+    <string name="pref_title_pebble_forcele">Her zaman BLE (Bluetooth D眉艧眉k Enerji) tercih et</string>
+    <string name="pref_title_pebble_forceprotocol">Bildirim protokol眉n眉 zorla</string>
+    <string name="pref_title_pebble_forceuntested">Test edilmemi艧 枚zellikleri etkinle艧tir</string>
+    <string name="pref_title_pebble_gatt_clientonly">Yaln谋zca GATT istemcisi</string>
+    <string name="pref_title_pebble_health_store_raw">Ham kayd谋 veri taban谋nda depola</string>
+    <string name="pref_title_pebble_mtu_limit">Pebble 2/LE GATT MTU s谋n谋r谋</string>
+    <string name="pref_title_pebble_privacy_mode">Gizlilik modu</string>
+    <string name="pref_title_pebble_reconnect_attempts">Yeniden ba臒lanma denemeleri</string>
+    <string name="pref_title_pebble_settings">Pebble ayarlar谋</string>
+    <string name="pref_title_pebble_sync_health">Pebble Health E艧zamanlamas谋</string>
+    <string name="pref_title_pebble_sync_misfit">Misfit E艧zamanlamas谋</string>
+    <string name="pref_title_pebble_sync_morpheuz">Morpheuz E艧zamanlamas谋</string>
+    <string name="pref_title_relax_firmware_checks">Ayg谋t谋n谋z i莽in tasarlanmam谋艧 bir 眉r眉n yaz谋l谋m谋 kurmak istiyorsan谋z bunu etkinle艧tirin (sorumluluk size aittir)</string>
+    <string name="pref_title_rtl">Sa臒dan Sola</string>
+    <string name="pref_title_screentime">Ekran a莽谋k s眉resi</string>
+    <string name="pref_title_setup_bt_pairing">Bluetooth e艧le艧tirmeyi etkinle艧tir</string>
+    <string name="pref_title_sunrise_sunset">G眉n do臒umu ve g眉n bat谋m谋</string>
+    <string name="pref_title_support_voip_calls">VoIP 莽a臒r谋lar谋n谋 etkinle艧tir</string>
+    <string name="pref_title_sync_caldendar">Takvim etkinliklerini e艧zamanla</string>
+    <string name="pref_title_theme">Tema</string>
+    <string name="pref_title_timeformat">Zaman bi莽imi</string>
+    <string name="pref_title_transliteration">Karakter 莽evirisi</string>
+    <string name="pref_title_unit_system">Birimler</string>
+    <string name="pref_title_use_custom_font">脰zel yaz谋 tipi kullan</string>
+    <string name="pref_title_vibration_strength">Titre艧im g眉c眉</string>
+    <string name="pref_title_weather">Hava Durumu</string>
+    <string name="pref_title_weather_location">Hava durumu konumu (LineageOS hava durumu sa臒lay谋c谋s谋 i莽in)</string>
+    <string name="pref_title_whenscreenon">鈥yr谋ca ekran a莽谋kken</string>
+    <string name="pref_write_logfiles">G眉nl眉k dosyalar谋n谋 yaz</string>
+    <string name="preferences_category_device_specific_settings">Ayg谋ta 枚zel ayarlar</string>
+    <string name="preferences_fm_frequency">FM Frekans谋</string>
+    <string name="preferences_hplus_settings">HPlus/Makibes ayarlar谋</string>
+    <string name="preferences_led_color">LED Rengi</string>
+    <string name="preferences_makibes_hr3_settings">Makibes HR3 ayarlar谋</string>
+    <string name="preferences_miband_settings">Mi Band / AmazFit ayarlar谋</string>
+    <string name="preferences_qhybrid_settings">Q Hybrid Ayarlar谋</string>
+    <string name="preferences_rtl_settings">Sa臒dan Sola Deste臒i</string>
+    <string name="prefs_button_double_press_action_selection_title">Etkinlik 2 eylemi</string>
+    <string name="prefs_button_long_press_action_selection_title">Uzun basma eylemi</string>
+    <string name="prefs_button_single_press_action_selection_title">Etkinlik 1 eylemi</string>
+    <string name="prefs_button_triple_press_action_selection_title">Etkinlik 3 eylemi</string>
+    <string name="prefs_button_variable_actions">Ayr谋nt谋l谋 basma eylemi ayarlar谋</string>
+    <string name="prefs_disconnect_notification">Ba臒lant谋 kesilme bildirimi</string>
+    <string name="prefs_enable_find_phone">\'Telefonu bul\' 枚zelli臒ini a莽</string>
+    <string name="prefs_find_phone">Telefonu bul</string>
+    <string name="prefs_find_phone_duration">Saniye cinsinden zil s眉resi</string>
+    <string name="prefs_find_phone_summary">Telefonunuzun zilini 莽almak i莽in bilekli臒inizi kullan谋n.</string>
+    <string name="prefs_hr_alarm_activity">Spor etkinli臒i s谋ras谋nda kalp ritmi alarm谋</string>
+    <string name="prefs_hr_alarm_high">脺st s谋n谋r</string>
+    <string name="prefs_hr_alarm_low">Alt s谋n谋r</string>
+    <string name="prefs_screen_orientation">Ekran y枚n眉</string>
+    <string name="prefs_title_all_day_heart_rate">T眉m g眉n kalp ritmi 枚l莽眉m眉</string>
+    <string name="prefs_title_heartrate_measurement_interval">T眉m g眉n kalp ritmi 枚l莽眉m眉</string>
+    <string name="prefs_wearside">Sola m谋 sa臒a m谋 giyiyorsunuz\?</string>
+    <string name="qhybrid_buttons_overwrite_error">D眉臒melerin 眉zerine yaz谋l谋rken hata olu艧tu</string>
+    <string name="qhybrid_buttons_overwrite_success">D眉臒melerin 眉zerine yaz谋ld谋</string>
+    <string name="qhybrid_changes_delay_prompt">de臒i艧iklik birka莽 saniye s眉rebilir鈥�</string>
+    <string name="qhybrid_goal_in_steps">Ad谋m say谋s谋 hedefi</string>
+    <string name="qhybrid_offset_time_by">saati kayd谋r</string>
+    <string name="qhybrid_offset_timezone">saat dilimini kayd谋r</string>
+    <string name="qhybrid_overwrite_buttons">d眉臒melerin 眉zerine yaz</string>
+    <string name="qhybrid_prompt_million_steps">Etkinle艧tirmek i莽in l眉tfen ad谋m say谋s谋n谋 bir milyona ayarlay谋n.</string>
+    <string name="qhybrid_second_timezone_offset_relative_to_utc">UTC olarak ikinci saat dilimi fark谋</string>
+    <string name="qhybrid_time_shift">zaman de臒i艧imi</string>
+    <string name="qhybrid_use_activity_hand_as_notification_counter">etkinlik elini bildirim sayac谋 olarak kullan</string>
+    <string name="qhybrid_vibration_strength">titre艧im g眉c眉:</string>
+    <string name="reset_index">Getirme tarihini s谋f谋rla</string>
+    <string name="right">Sa臒</string>
+    <string name="russian">Rus莽a</string>
+    <string name="save_configuration">Yap谋land谋rmay谋 Kaydet</string>
+    <string name="seconds_10">10 saniye</string>
+    <string name="seconds_20">20 saniye</string>
+    <string name="seconds_30">30 saniye</string>
+    <string name="seconds_5">5 saniye</string>
+    <string name="select_all">T眉m眉n眉 se莽</string>
+    <string name="share">Payla艧</string>
+    <string name="share_log">G眉nl眉臒眉 payla艧</string>
+    <string name="share_log_warning">Gadgetbridge\'in sa臒l谋k verileri, benzersiz tan谋mlay谋c谋lar (bir ayg谋t谋n MAC adresi gibi), m眉zik tercihleri vb. dahil ancak bunlarla s谋n谋rl谋 olmamak 眉zere bir莽ok ki艧isel bilgiyi g眉nl眉k dosyalar谋na kaydetti臒ini l眉tfen unutmay谋n. Dosyay谋 herkese a莽谋k sorun bildirimi olarak g枚ndermeden 枚nce bu dosyay谋 d眉zenlemeyi ve bu bilgileri kald谋rmay谋 g枚z 枚n眉nde bulundurun.</string>
+    <string name="simplified_chinese">Basitle艧tirilmi艧 脟ince</string>
+    <string name="sleep_activity_date_range">Saat %1$s ile %2$s aras谋</string>
+    <string name="sleepchart_your_sleep">Uyku</string>
+    <string name="spanish">陌spanyolca</string>
+    <string name="stats_title">H谋z b枚lgeleri</string>
+    <string name="stats_x_axis_label">Toplam dakika</string>
+    <string name="stats_y_axis_label">Dakikada ad谋m say谋s谋</string>
+    <string name="tap_a_device_to_connect">Ba臒lanmak i莽in bir ayg谋ta dokunun</string>
+    <string name="tap_connected_device_for_activity">Etkinlik i莽in ba臒l谋 ayg谋ta hafif莽e dokunun</string>
+    <string name="tap_connected_device_for_app_mananger">Uygulama y枚neticisi i莽in ba臒l谋 ayg谋ta hafif莽e dokunun</string>
+    <string name="tap_connected_device_for_vibration">Titre艧im i莽in ba臒l谋 ayg谋ta hafif莽e dokunun</string>
+    <string name="test">Test</string>
+    <string name="test_notification">Test bildirimi</string>
+    <string name="thai">Tayca</string>
+    <string name="this_is_a_test_notification_from_gadgetbridge">Bu Gadgetbridge 眉zerinden bir test bildirimidir</string>
+    <string name="timeformat_24h">24 saat</string>
+    <string name="timeformat_am_pm">脰脰/脰S</string>
+    <string name="title_activity_alarm_details">Alarm ayr谋nt谋lar谋</string>
+    <string name="title_activity_android_pairing">Ayg谋t e艧le艧tir</string>
+    <string name="title_activity_appmanager">Uygulama Y枚neticisi</string>
+    <string name="title_activity_calblacklist">Kara Listeye Al谋nan Takvimler</string>
+    <string name="title_activity_charts">Etkinlik ve Uyku</string>
+    <string name="title_activity_controlcenter_generic">Gadgetbridge</string>
+    <string name="title_activity_db_management">Veri y枚netimi</string>
+    <string name="title_activity_debug">Hata Ay谋klama</string>
+    <string name="title_activity_device_specific_settings">Ayg谋ta 枚zel ayarlar</string>
+    <string name="title_activity_discovery">Ayg谋t arama</string>
+    <string name="title_activity_fw_app_insaller">脺r眉n yaz谋l谋m谋/uygulama kurucu</string>
+    <string name="title_activity_mi_band_pairing">Mi Band\'谋n谋z谋 e艧le艧tirin</string>
+    <string name="title_activity_notification_filter">Bildirim Filtresi</string>
+    <string name="title_activity_pebble_pairing">Pebble E艧le艧tirme</string>
+    <string name="title_activity_set_alarm">Alarmlar谋 yap谋land谋r</string>
+    <string name="title_activity_settings">Ayarlar</string>
+    <string name="title_activity_sleepmonitor">Uyku izleme</string>
+    <string name="title_activity_vibration">Titre艧im</string>
+    <string name="title_activity_watch9_calibration">Watch 9 kalibrasyonu</string>
+    <string name="title_activity_watch9_pairing">Watch 9 e艧le艧tirme</string>
+    <string name="toast_aqurired_networklocation">konum al谋nd谋</string>
+    <string name="toast_enable_networklocationprovider">L眉tfen a臒 konumunu etkinle艧tirin</string>
+    <string name="toast_notification_filter_saved_successfully">Bildirim filtresi kaydedildi</string>
+    <string name="toast_notification_filter_words_empty_hint">L眉tfen en az bir s枚zc眉k girin</string>
+    <string name="traditional_chinese">Geleneksel 脟ince</string>
+    <string name="turkish">T眉rk莽e</string>
+    <string name="ukrainian">Ukraynaca</string>
+    <string name="unit_imperial">陌ngiliz birimleri</string>
+    <string name="unit_metric">Metrik</string>
+    <string name="unknown_state">Bilinmeyen durum</string>
+    <string name="updatefirmwareoperation_firmware_not_sent">脺r眉n yaz谋l谋m谋 g枚nderilmedi</string>
+    <string name="updatefirmwareoperation_metadata_updateproblem">脺r眉n yaz谋l谋m谋 眉st veri aktar谋m谋yla ilgili sorun</string>
+    <string name="updatefirmwareoperation_update_complete">脺r眉n yaz谋l谋m谋 kurulumu tamamland谋</string>
+    <string name="updatefirmwareoperation_update_complete_rebooting">脺r眉n yaz谋l谋m谋 kurulumu tamamland谋, ayg谋t yeniden ba艧lat谋l谋yor鈥�</string>
+    <string name="updatefirmwareoperation_update_in_progress">脺r眉n yaz谋l谋m谋 kuruluyor</string>
+    <string name="updatefirmwareoperation_updateproblem_do_not_reboot">脺r眉n yaz谋l谋m谋 aktar谋m谋yla ilgili sorun. Mi Band\'谋n谋z谋 YEN陌DEN BA艦LATMAYIN!</string>
+    <string name="updatefirmwareoperation_write_failed">脺r眉n yaz谋l谋m谋 kurulmas谋 ba艧ar谋s谋z oldu</string>
+    <string name="updating_firmware">脺r眉n yaz谋l谋m谋 kuruluyor鈥�</string>
+    <string name="user_feedback_all_alarms_disabled">B眉t眉n alarmlar devre d谋艧谋</string>
+    <string name="user_feedback_miband_activity_data_transfer">Veri aktar谋m谋 durumu: %1$s / %2$s</string>
+    <string name="user_feedback_miband_set_alarms_failed">Alarmlar ayarlan谋rken bir hata olu艧tu, l眉tfen tekrar deneyin.</string>
+    <string name="user_feedback_miband_set_alarms_ok">Alarmlar ayg谋ta g枚nderildi.</string>
+    <string name="vertical">Dikey</string>
+    <string name="vibration_profile_alarm_clock">Alarm saati</string>
+    <string name="vibration_profile_long">Uzun</string>
+    <string name="vibration_profile_medium">Orta</string>
+    <string name="vibration_profile_ring">Zil sesi</string>
+    <string name="vibration_profile_short">K谋sa</string>
+    <string name="vibration_profile_staccato">Kesik kesik</string>
+    <string name="vibration_profile_waterdrop">Su damlas谋</string>
+    <string name="vibration_try">Dene</string>
+    <string name="vietnamese">Vietnamca</string>
+    <string name="waiting_for_reconnect">Yeniden ba臒lanmak i莽in bekliyor</string>
+    <string name="warning">Dikkat!</string>
+    <string name="watch9_calibration_button">Kalibrasyon</string>
+    <string name="watch9_calibration_hint">Ayg谋t谋n谋z谋n 艧u anda size g枚sterdi臒i saati ayarlay谋n.</string>
+    <string name="watch9_pairing_tap_hint">Saatiniz titre艧ti臒inde ayg谋t谋 sallay谋n veya d眉臒mesine bas谋n.</string>
+    <string name="watch9_time_hours">Saat:</string>
+    <string name="watch9_time_minutes">Dakika:</string>
+    <string name="watch9_time_seconds">Saniye:</string>
+    <string name="watch_not_connected">Saat ba臒lanmad谋</string>
+    <string name="weather_notification_label">Pebble 眉zerinde hava durumu bilgisi almak i莽in bu d谋艧 g枚r眉n眉m眉n Hava Durumu Bildirimi uygulamas谋nda etkinle艧tirildi臒inden emin olun.
+\n
+\nBurada herhangi bir yap谋land谋rma gerekmez.
+\n
+\nPebble sistem hava durumu uygulamas谋n谋 uygulama y枚netiminden etkinle艧tirebilirsiniz.
+\n
+\nDesteklenen saat y眉zleri hava durumunu otomatik olarak g枚sterir.</string>
+    <string name="weeksleepchart_sleep_a_month">Ayl谋k uyku</string>
+    <string name="weeksleepchart_sleep_a_week">Haftal谋k uyku</string>
+    <string name="weeksleepchart_today_sleep_description">Bug眉nk眉 uyku, hedef: %1$s</string>
+    <string name="weeksteps_today_steps_description">Bug眉nk眉 ad谋m say谋s谋, hedef: %1$s</string>
+    <string name="weekstepschart_steps_a_month">Ayl谋k ad谋m say谋s谋</string>
+    <string name="weekstepschart_steps_a_week">Haftal谋k ad谋m say谋s谋</string>
+    <string name="when_screen_off">Ekran kapal谋 oldu臒unda</string>
+    <string name="whitelist_all_for_notifications">Bildirimler i莽in hepsini beyaz listeye al</string>
+    <string name="widget_10_minutes">10 dakika</string>
+    <string name="widget_1_hour">1 saat</string>
+    <string name="widget_20_minutes">20 dakika</string>
+    <string name="widget_5_minutes">5 dakika</string>
+    <string name="widget_listing_label">Durum ve Alarmlar</string>
+    <string name="widget_set_alarm_after">Alarm kur:</string>
+    <string name="you_did_not_sleep">Uyumad谋n谋z</string>
+    <string name="you_slept">%1$s - %2$s</string>
+    <string name="zetime_activity_tracking">Etkinlik izleme</string>
+    <string name="zetime_activity_tracking_summary">Etkinlik izlemenin a莽谋lmas谋 ad谋mlar谋n谋z谋 sayacakt谋r.</string>
+    <string name="zetime_analog_mode">Analog modu</string>
+    <string name="zetime_analog_mode_hands">Yaln谋zca eller</string>
+    <string name="zetime_analog_mode_handsandsteps">Eller ve ad谋mlar</string>
+    <string name="zetime_calories_type">Kalori t眉r眉</string>
+    <string name="zetime_calories_type_active">Yaln谋zca etkin harcanan kaloriler</string>
+    <string name="zetime_calories_type_all">Etkin ve etkin de臒ilken harcanan kaloriler</string>
+    <string name="zetime_date_format">Tarih bi莽imi</string>
+    <string name="zetime_date_format_1">YY/AA/GG</string>
+    <string name="zetime_date_format_2">GG/AA/YY</string>
+    <string name="zetime_date_format_3">AA/GG/YY</string>
+    <string name="zetime_handmove_display">El hareketi</string>
+    <string name="zetime_handmove_display_summary">Ekran谋 a莽mak veya kapatmak i莽in bile臒inizi d枚nd眉r眉n.</string>
+    <string name="zetime_heart_rate_alarm_enable">Kalp ritmi alarm谋n谋 etkinle艧tir</string>
+    <string name="zetime_prefs_inactivity_fr">Cuma</string>
+    <string name="zetime_prefs_inactivity_mo">Pazartesi</string>
+    <string name="zetime_prefs_inactivity_repetitions">Tekrarlar</string>
+    <string name="zetime_prefs_inactivity_sa">Cumartesi</string>
+    <string name="zetime_prefs_inactivity_su">Pazar</string>
+    <string name="zetime_prefs_inactivity_th">Per艧embe</string>
+    <string name="zetime_prefs_inactivity_tu">Sal谋</string>
+    <string name="zetime_prefs_inactivity_we">脟ar艧amba</string>
+    <string name="zetime_signaling_beep">S眉rekli bip sesi</string>
+    <string name="zetime_signaling_beep_once">1 defa bip sesi</string>
+    <string name="zetime_signaling_beep_twice">2 defa bip sesi</string>
+    <string name="zetime_signaling_none">Sessiz</string>
+    <string name="zetime_signaling_vibrate">S眉rekli titre艧im</string>
+    <string name="zetime_signaling_vibrate_beep">S眉rekli titre艧im ve bip sesi</string>
+    <string name="zetime_signaling_vibrate_beep_once">1 defa titre艧im ve bip sesi</string>
+    <string name="zetime_signaling_vibrate_once">1 defa titre艧im</string>
+    <string name="zetime_signaling_vibrate_twice">2 defa titre艧im</string>
+    <string name="zetime_title_alarm_signaling">Alarm i莽in sinyal t眉r眉n眉 ayarla</string>
+    <string name="zetime_title_heart_rate_alarm">Kalp ritmi alarm谋</string>
+    <string name="zetime_title_heart_rate_alarm_summary">Kalp ritminiz s谋n谋r谋 a艧arsa, saat sizi uyaracakt谋r.</string>
+    <string name="zetime_title_heartrate">Kalp ritmi ayarlar谋</string>
+    <string name="zetime_title_screentime">Saniye cinsinden ekran a莽谋k s眉resi</string>
+    <string name="zetime_title_settings">ZeTime ayarlar谋</string>
+    <plurals name="widget_alarm_target_hours">
+        <item quantity="one">%d saat</item>
+        <item quantity="other">%d saat</item>
+    </plurals>
+    <string name="pref_qhybrid_title_widget_draw_circles">Widget 莽emberleri 莽iz</string>
+    <string name="title_activity_LenovoWatch_calibration">Watch X Plus kalibrasyonu</string>
+    <string name="pref_qhybrid_save_raw_activity_files">Ham etkinlik dosyalar谋n谋 kaydet</string>
+    <string name="menuitem_workout">Egzersiz</string>
+    <string name="menuitem_eventreminder">Etkinlik Hat谋rlat谋c谋</string>
+    <string name="menuitem_hr">Kalp Ritmi</string>
+    <string name="menuitem_pai">PAI</string>
+    <string name="devicetype_watchxplus">Watch X Plus</string>
+    <string name="devicetype_watchx">Watch X</string>
+    <string name="power_mode_watch">Yaln谋zca saat</string>
+    <string name="power_mode_saving">G眉莽 tasarrufu</string>
+    <string name="power_mode_normal">Normal</string>
+    <string name="power_mode_title">Saat g眉莽 modu</string>
+    <string name="prefs_sensors_button_bp_calibration_sum">Kalibrasyona ba艧lamak i莽in buraya bas谋n</string>
+    <string name="prefs_sensors_button_bp_calibration">Kalibrasyon</string>
+    <string name="pref_sensors_bp_calibration_high">Sistolik Kan Bas谋nc谋 (Y眉ksek Tansiyon)</string>
+    <string name="pref_sensors_bp_calibration_low">Diyastolik Kan Bas谋nc谋 (D眉艧眉k Tansiyon)</string>
+    <string name="pref_sensors_bp_calibration">Kan Bas谋nc谋 Kalibrasyonu</string>
+    <string name="pref_title_sensors_altitude">Y眉kseklik (irtifa) kalibrasyonu</string>
+    <string name="pref_header_sensors_calibration">Sens枚rler Kalibrasyonu</string>
+    <string name="pref_title_device_spec_settings_show_raw_graph">Etkinlik grafi臒inde RAW verilerini g枚ster</string>
+    <string name="pref_summary_device_spec_settings_title_force_time">Yeniden ba臒land谋臒谋nda saati e艧zamanlamay谋 zorla. Analog saatler yanl谋艧 zaman g枚sterebilir!</string>
+    <string name="pref_title_device_spec_settings_force_time">Saati e艧zamanlamay谋 zorla</string>
+    <string name="pref_header_device_spec_settings">Ayg谋t ayarlar谋</string>
+    <string name="pref_summary_notifications_and_calls_title_shake_reject">Saat d眉臒mesi eylemini kopyalar</string>
+    <string name="prefs_notifications_and_calls_shake_reject">Bile臒i sallayarak 莽a臒r谋y谋 yok say/reddet</string>
+    <string name="prefs_notifications_and_calls_reject">D眉臒meyle 莽a臒r谋y谋 yok say/reddet</string>
+    <string name="pref_summary_notifications_and_calls_title_reject">Kapal谋 - yok say, A莽谋k - reddet</string>
+    <string name="pref_header_notifications_and_calls_callhandling">脟a臒r谋 Y枚netimi</string>
+    <string name="pref_title_notifications_and_calls_repeat_on_misscall">X dakikada tekrarla</string>
+    <string name="pref_summary_notifications_and_calls_enable_misscall">Her dakikada tekrarlar</string>
+    <string name="pref_notifications_and_calls_enable_misscall">Cevaps谋z 莽a臒r谋 bildirimi</string>
+    <string name="prefs_notifications_and_calls_continious_ring">Telefon 莽alarken bildirim</string>
+    <string name="pref_title_notifications_and_calls_repeat_on_call">脟a臒r谋 bildirimini tekrarla</string>
+    <string name="pref_header_notifications_and_calls">Bildirimler ve 脟a臒r谋lar</string>
+    <string name="pref_theme_system">Sistem</string>
+    <string name="czesh">脟ek莽e</string>
+    <string name="swedish">陌sve莽莽e</string>
+    <string name="hebrew">陌branice</string>
+    <string name="greek">Yunanca</string>
+    <string name="hungarian">Macarca</string>
+    <string name="romanian">Rumence</string>
+    <string name="menuitem_unknown">Bilinmeyen</string>
+    <string name="bip_prefs_shotcuts_summary">Bileklik ekran谋ndaki k谋sayollar谋 se莽in</string>
+    <string name="bip_prefs_shortcuts">K谋sa yollar</string>
+    <string name="hr_widget_last_notification">Son bildirim</string>
+    <string name="controlcenter_set_alias">Takma Ad Ayarla</string>
+    <string name="fw_upgrade_notice_amazfit_trex">Amazfit T-Rex ayg谋t谋n谋za %s 眉r眉n yaz谋l谋m谋n谋 kurmak 眉zeresiniz.
+\n
+\nL眉tfen 枚nce .fw dosyas谋n谋, sonra .res dosyas谋n谋, ve son olarak .gps dosyas谋n谋 kurun. Saatiniz .fw dosyas谋n谋 kurduktan sonra yeniden ba艧lat谋lacak.
+\n
+\nNot: Bu dosyalar 枚nceden kurulanlarla tamamen ayn谋ysa .res ve .gps dosyalar谋n谋 kurman谋z gerekmez.
+\n
+\n陌LERLEMEN陌Z DURUMUNDA R陌SK S陌ZE A陌TT陌R!</string>
+    <string name="devicetype_amazfit_trex">Amazfit T-Rex</string>
+    <string name="about_additional_contributions">Listelenmeyen herkese kod, 莽eviri, destek, fikirler, motivasyon, hata raporlar谋, para鈥� ile katk谋da bulunduklar谋 i莽in 莽ok te艧ekk眉rler 鉁�</string>
+    <string name="about_additional_device_support">Ek ayg谋t deste臒i</string>
+    <string name="about_contributors">Katk谋da bulunanlar</string>
+    <string name="about_core_team_title">脟ekirdek Ekip (ilk kod katk谋da bulunma s谋ras谋na g枚re)</string>
+    <string name="about_description_generic">Sat谋c谋lar谋n kapal谋 kaynakl谋 Android ayg谋t uygulamalar谋n谋n yerine bulut gerektirmeyen copyleft 枚zg眉r alternatif.</string>
+    <string name="about_activity_title_generic">Gadgetbridge hakk谋nda</string>
+    <string name="about_title">Hakk谋nda</string>
+    <string name="pref_title_weather_summary">LineageOS hava durumu sa臒lay谋c谋s谋 i莽in kullan谋l谋r, di臒er Android s眉r眉mleri \"Weather notification\" gibi bir uygulama kullanmal谋d谋r. Gadgetbridge wiki sayfas谋nda daha fazla bilgi bulabilirsiniz.</string>
+    <string name="menuitem_worldclock">D眉nya Saati</string>
+    <string name="error_version_check_extreme_caution">D陌KKAT: S眉r眉m bilgileri denetlenirken hata olu艧tu! Devam etmemelisiniz! G枚r眉len s眉r眉m ad谋 \"%s\"</string>
+    <string name="permission_granting_mandatory">T眉m bu izinler gereklidir ve izin verilmezse karars谋zl谋k olu艧abilir</string>
+    <string name="error_background_service_reason_truncated">Arka plan hizmetini ba艧latma ba艧ar谋s谋z oldu, 莽眉nk眉鈥�</string>
+    <string name="error_background_service_reason">Bir istisna nedeniyle arka plan hizmetini ba艧latma ba艧ar谋s谋z oldu. Hata:</string>
+    <string name="error_background_service">Arka plan hizmeti ba艧lat谋lamad谋</string>
+    <string name="menuitem_stress">Stres</string>
+    <string name="menuitem_cycles">D枚ng眉 陌zleme</string>
+    <string name="menuitem_breathing">Nefes Alma</string>
+    <string name="devicetype_miband5">Mi Band 5</string>
+    <string name="fw_upgrade_notice_miband5">Mi Band 5 ayg谋t谋n谋za %s 眉r眉n yaz谋l谋m谋n谋 kurmak 眉zeresiniz.
+\n
+\nL眉tfen 枚nce .fw dosyas谋n谋, sonra .res dosyas谋n kurun. Bilekli臒iniz .fw dosyas谋n谋 kurduktan sonra yeniden ba艧lat谋lacak.
+\n
+\nNot: Daha 枚nce kurulanla tamamen ayn谋ysa .res dosyas谋n谋 kurman谋z gerekmez.
+\n
+\n陌LERLEMEN陌Z DURUMUNDA R陌SK S陌ZE A陌TT陌R!</string>
+    <string name="devicetype_tlw64">TLW64</string>
+    <string name="companiondevice_pairing_details">Hizmetin arka planda yeniden ba艧lat谋lmas谋 gerekiyorsa g眉venilirli臒i art谋racak yeni CompanionDevice API deste臒ini etkinle艧tirir (yaln谋zca Android 8 veya 眉zerinde bir etkisi vard谋r), etkili olmas谋 i莽in Gadgetbridge kullanarak yeniden e艧le艧tirme gerektirir</string>
+    <string name="ignore_bonded_devices_description">Bu se莽ene臒in etkinle艧tirilmesi, tarama s谋ras谋nda 枚nceden etkile艧imde/e艧le艧tirilmi艧 olan ayg谋tlar谋 yok sayar</string>
+    <string name="ignore_bonded_devices">Etkile艧imde olan ayg谋tlar谋 yok say</string>
+    <string name="device_is_currently_bonded">ZATEN ETK陌LE艦陌MDE</string>
+    <string name="device_requires_key">ANAHTAR GEREKL陌, G陌RMEK 陌脟陌N UZUN BASIN</string>
+    <string name="error_location_enabled_mandatory">Ayg谋tlar谋 taramak i莽in konumun a莽谋k olmas谋 gerekmektedir</string>
+    <string name="error_retrieving_devices_database">Veri taban谋ndan ayg谋tlar al谋n谋rken hata olu艧tu</string>
+    <string name="error_setting_alias">Takma ad ayarlan谋rken hata olu艧tu:</string>
+    <string name="error_exporting_device_preferences">Ayg谋ta 枚zel tercihler d谋艧a aktar谋l谋rken hata olu艧tu</string>
+    <string name="pref_check_permission_status_summary">An谋nda gerekmediklerinde bile eksik izinleri denetleyin ve isteyin. Bunu yaln谋zca ayg谋tlar谋n谋z bu 枚zelliklerin hi莽birini desteklemiyorsa devre d谋艧谋 b谋rak谋n. 陌zin verilmemesi sorunlara neden olabilir!</string>
+    <string name="pref_check_permission_status">陌zin durumunu denetle</string>
+    <string name="companiondevice_pairing">CompanionDevice E艧le艧tirme</string>
+    <string name="require_location_provider">Konum etkinle艧tirilmelidir</string>
+    <string name="devicetype_pinetime_jf">PineTime (JF 脺r眉n Yaz谋l谋m谋)</string>
+    <string name="ascentSeconds">Artan</string>
+    <string name="descentSeconds">Azalma</string>
+    <string name="activity_detail_show_gps_label">GPS 陌zlemesini G枚ster</string>
+    <string name="Elevation">Y眉kseklik</string>
+    <string name="Strokes">Kula莽</string>
+    <string name="bpm">kalp at谋艧谋/dakika</string>
+    <string name="strokes_unit">kula莽</string>
+    <string name="strokes_second">kula莽/s</string>
+    <string name="flatSeconds">D眉z</string>
+    <string name="swimStyle">Y眉zme Tarz谋</string>
+    <string name="swolfIndex">SWOLF</string>
+    <string name="averageStrokesPerSecond">Ortalama Kula莽</string>
+    <string name="averageStrokeDistance">Ortalama Kula莽 Uzunlu臒u</string>
+    <string name="Activity">Etkinlik</string>
+    <string name="activity_detail_end_label">Biti艧</string>
+    <string name="activity_detail_start_label">Ba艧lang谋莽</string>
+    <string name="activity_detail_duration_label">S眉re</string>
+    <string name="Steps">Ad谋m</string>
+    <string name="Speed">H谋z</string>
+    <string name="Distance">Mesafe</string>
+    <string name="Swimming">Y眉zme</string>
+    <string name="minutes_km">dak/km</string>
+    <string name="seconds_km">sn/km</string>
+    <string name="laps_unit">tur</string>
+    <string name="swim_style">y眉zme tarz谋</string>
+    <string name="swolf_index">swolf indeksi</string>
+    <string name="seconds">sn</string>
+    <string name="km_h">km/sa</string>
+    <string name="meters_second">m/s</string>
+    <string name="steps_unit">ad谋m</string>
+    <string name="cm">cm</string>
+    <string name="meters">m</string>
+    <string name="laps">Tur</string>
+    <string name="averageLapPace">Ortalama Tur H谋z谋</string>
+    <string name="averageStride">Ortalama Ad谋m</string>
+    <string name="averageKMPaceSeconds">H谋z</string>
+    <string name="averageHR">Kalp ritmi</string>
+    <string name="totalStride">Toplam ad谋m</string>
+    <string name="maxPace">En Y眉ksek H谋z</string>
+    <string name="maxSpeed">Azami</string>
+    <string name="activeSeconds">Etkin</string>
+    <string name="minAltitude">Asgari</string>
+    <string name="descentMeters">Yoku艧 a艧a臒谋</string>
+    <string name="ascentMeters">Yoku艧 yukar谋</string>
+    <string name="minPace">En D眉艧眉k H谋z</string>
+    <string name="caloriesBurnt">Kalori</string>
+    <string name="steps">Ad谋m</string>
+    <string name="maxAltitude">Azami</string>
+    <string name="distanceMeters">Mesafe</string>
+    <string name="activity_summary_detail">Spor Etkinli臒i Ayr谋nt谋s谋</string>
+    <string name="calories_unit">kcal</string>
+    <string name="seconds_m">sn/m</string>
+    <string name="prefs_events_forwarding_startnonwear">Giyilmedi臒inde</string>
+    <string name="prefs_events_forwarding_wokeup">Uyand谋臒谋nda</string>
+    <string name="prefs_events_forwarding_fellsleep">Uykuya Dalarken</string>
+    <string name="prefs_events_forwarding_title">Ayg谋t eylemleri</string>
+    <string name="prefs_events_forwarding_summary">Eylemleri ve Android yay谋nlar谋n谋 tetiklemek i莽in ayg谋t olaylar谋n谋 kullan</string>
+    <string name="activity_type_yoga">Yoga</string>
+    <string name="activity_type_jump_roping">陌p Atlama</string>
+    <string name="activity_type_swimming_openwater">Y眉zme (A莽谋k su)</string>
+    <string name="activity_type_elliptical_trainer">Eliptik Spor Aleti</string>
+    <string name="activity_type_indoor_cycling">Kondisyon Bisikleti</string>
+    <string name="prefs_events_forwarding_action_title">Eylemi 莽al谋艧t谋r</string>
+    <string name="prefs_events_forwarding_broadcast_title">Mesaj谋 yay谋nla</string>
+    <string name="activity_filter_apply_filter">Filtreyi Uygula</string>
+    <string name="activity_filter_date_to">Biti艧</string>
+    <string name="activity_filter_date_from">Ba艧lang谋莽</string>
+    <string name="pref_header_filter">Spor Etkinlikleri Filtresi</string>
+    <string name="activity_filter_filter_title">Filtre</string>
+    <string name="activity_filter_reset_filter">Filtreyi S谋f谋rla</string>
+    <string name="activity_summaries_statistics">陌statistikler</string>
+    <string name="km">km</string>
+    <string name="medley">Kar谋艧谋k</string>
+    <string name="backstroke">S谋rt眉st眉</string>
+    <string name="freestyle">Serbest stil</string>
+    <string name="breaststroke">Kurba臒alama</string>
+    <string name="activity_summary_edit_name_title">Etiketi d眉zenle</string>
+    <string name="pref_header_statistics">Spor Etkinlikleri 陌statistikleri</string>
+    <string name="activity_filter_name_contains">Etiket</string>
+    <string name="activity_summaries_all_activities">T眉m Etkinlikler</string>
+    <string name="devicetype_sg2">Lemfo SG2</string>
+    <string name="activity_summary_yesterday">D眉n</string>
+    <string name="activity_summary_today">Bug眉n</string>
+    <string name="activity_filter_to_placeholder">bug眉n</string>
+    <string name="activity_filter_from_placeholder">uzak ge莽mi艧</string>
+    <string name="activity_summaries_all_devices">T眉m ayg谋tlar</string>
+    <string name="sports_activity_quick_filter_select">Zaman dilimi</string>
+    <string name="sports_activity_quick_filter_30days">30 g眉n</string>
+    <string name="sports_activity_quick_filter_7days">7 g眉n</string>
+    <string name="sports_activity_quick_filter_last_month">脰nceki ay</string>
+    <string name="sports_activity_quick_filter_this_month">Bu ay</string>
+    <string name="sports_activity_quick_filter_last_week">脰nceki hafta</string>
+    <string name="sports_activity_quick_filter_this_week">Bu hafta</string>
+    <string name="no">Hay谋r</string>
+    <string name="yes">Evet</string>
+    <string name="activity_filter_individual_items">Tek tek se莽ilen 枚geler</string>
+    <string name="addto_filter">Filtreye ekle</string>
+    <string name="averageSpeed">Ortalama H谋z</string>
+    <string name="baseAltitude">Temel Y眉kseklik</string>
+    <string name="about_links">Ba臒lant谋lar</string>
+    <string name="activity_type_badminton">Badminton</string>
+    <string name="activity_type_pingpong">Masa Tenisi</string>
+    <string name="activity_type_basketball">Basketbol</string>
+    <string name="activity_type_cricket">Kriket</string>
+    <string name="activity_type_rowing_machine">K眉rek Makinesi</string>
+    <string name="activity_type_soccer">Futbol</string>
+    <string name="activity_prefs_chart_min_session_length">En d眉艧眉k etkinlik uzunlu臒u (dakika)</string>
+    <string name="activity_prefs_chart_max_idle_phase_length">Etkinlikleri ay谋rmak i莽in duraklama uzunlu臒u (dakika)</string>
+    <string name="activity_prefs_chart_min_steps_per_minute">Etkinlik alg谋lamak i莽in dakikada en az ad谋m say谋s谋</string>
+    <string name="activity_prefs_chart_min_steps_per_minute_for_run">脟al谋艧may谋 alg谋lamak i莽in dakikada en az ad谋m say谋s谋</string>
+    <string name="charts_activity_list">Etkinlik listesi</string>
+    <string name="chart_get_active_and_synchronize">Biraz etkinlik yap谋n ve ayg谋t谋 e艧zamanlay谋n.</string>
+    <string name="chart_no_active_data">Etkinlik alg谋lanmad谋.</string>
+    <string name="lefun_prefs_antilost_summary">Telefonunuzun bileklikle ba臒lant谋s谋 kesilirse bileklik titreyecek</string>
+    <string name="devicetype_lefun">Lefun</string>
+    <string name="lefun_prefs_interface_language_title">Aray眉z dili</string>
+    <string name="lefun_prefs_antilost_title">Kay谋p 枚nleme</string>
+    <string name="lefun_prefs_hydration_reminder_interval_title">S谋v谋 alma hat谋rlatma aral谋臒谋 (dakika olarak)</string>
+    <string name="lefun_prefs_hydration_reminder_summary">Bileklik su i莽menizi hat谋rlatmak i莽in titreyecek</string>
+    <string name="lefun_prefs_hydration_reminder_title">S谋v谋 alma hat谋rlat谋c谋</string>
+    <string name="devicetype_sonyswr12">Sony SWR12</string>
+    <string name="sonyswr12_settings_title">Sony SWR12 Ayarlar谋</string>
+    <string name="sonyswr12_settings_low_vibration">D眉艧眉k titre艧im etkin</string>
+    <string name="sonyswr12_settings_alarm_interval">Dakikalar i莽inde ak谋ll谋 alarm aral谋臒谋</string>
+    <string name="sonyswr12_settings_low_vibration_summary">Bileklik 眉zerinde d眉艧眉k titre艧im yo臒unlu臒u sa臒lay谋n</string>
+    <string name="sonyswr12_settings_stamina_summary">G眉莽 tasarrufu modu, kalp ritminin d眉zenli aral谋klarla otomatik 枚l莽眉m眉n眉 kapat谋r ve b枚ylece 莽al谋艧ma s眉resini uzat谋r</string>
+    <string name="sonyswr12_settings_alarm_interval_summary">Ak谋ll谋 alarm aral谋臒谋, kurulu alarmdan 枚nceki aral谋kt谋r. Bu aral谋kta ayg谋t, kullan谋c谋y谋 uyand谋rmak i莽in uykunun en hafif a艧amas谋n谋 tespit etmeye 莽al谋艧谋r</string>
+    <string name="sonyswr12_settings_stamina">G眉莽 tasarrufu modu a莽谋k</string>
+    <string name="devicetype_nut_mini">Nut mini</string>
+    <string name="activity_error_no_app_for_png">Bu ekran g枚r眉nt眉s眉n眉 payla艧mak i莽in g枚r眉nt眉 dosyalar谋n谋 i艧leyebilen bir uygulama kurun.</string>
+    <string name="pref_title_lower_button_function_double">Alt D眉臒me 莽ift</string>
+    <string name="pref_title_middle_button_function_double">Orta D眉臒me 莽ift</string>
+    <string name="pref_title_upper_button_function_double">脺st D眉臒me 莽ift</string>
+    <string name="pref_title_lower_button_function_long">Alt D眉臒me uzun</string>
+    <string name="pref_title_middle_button_function_long">Orta D眉臒me uzun</string>
+    <string name="pref_title_upper_button_function_long">脺st D眉臒me uzun</string>
+    <string name="pref_title_lower_button_function_short">Alt D眉臒me k谋sa</string>
+    <string name="pref_title_middle_button_function_short">Orta D眉臒me k谋sa</string>
+    <string name="pref_title_upper_button_function_short">脺st D眉臒me k谋sa</string>
+    <string name="firmware_update_progress">Y眉kleme devam ediyor
+\n%%%1d, %.2fkbps h谋z谋nda (ortalama %.2fkbps)
+\nB枚l眉m %1d / %1d</string>
+    <string name="devicestatus_upload_failed">Y眉kleme ba艧ar谋s谋z oldu</string>
+    <string name="devicestatus_upload_aborted">Y眉kleme iptal edildi!</string>
+    <string name="devicestatus_upload_validating">Y眉kleme do臒rulan谋yor</string>
+    <string name="devicestatus_upload_completed">Y眉kleme tamamland谋</string>
+    <string name="devicestatus_disconnecting">Ayg谋t谋n ba臒lant谋s谋 kesiliyor!</string>
+    <string name="devicestatus_disconnected">Ayg谋t谋n ba臒lant谋s谋 kesildi!</string>
+    <string name="devicestatus_upload_started">Y眉kleme ba艧lat谋ld谋</string>
+    <string name="devicestatus_upload_starting">Y眉kleme ba艧lat谋l谋yor</string>
+    <string name="devicestatus_connected">Ayg谋t ba臒land谋</string>
+    <string name="devicestatus_connecting">Ayg谋t ba臒lan谋yor</string>
+    <string name="activity_prefs_step_length_cm">Santimetre cinsinden ad谋m uzunlu臒u</string>
+    <string name="devicetype_amazfit_band5">Amazfit Band 5</string>
+    <string name="menuitem_spo2">SpO2</string>
+    <string name="qhybrid_calibration_align_hint">Saatin ibrelerini 12:00\'a hizalamak i莽in a艧a臒谋daki d眉臒meleri kullan谋n.</string>
+    <string name="gps_track">GPS izleme</string>
+    <string name="about_version">S眉r眉m %s</string>
+    <string name="devicetype_amazfit_bips_lite">Amazfit Bip S Lite</string>
+    <string name="charts_legend_heartrate_average">Kalp ritmi ortalamas谋</string>
+    <string name="charts_min_max_heartrate_popup">En d眉艧眉k kalp ritmi: %1$d
+\nEn y眉ksek kalp ritmi: %2$d
+\nHareket yo臒unlu臒u: %3$s</string>
+    <string name="find_lost_device_message">%1$s arans谋n m谋\?</string>
+    <string name="devicetype_amazfit_gtr2">Amazfit GTR 2</string>
+    <string name="menuitem_stopwatch">Kronometre</string>
+    <string name="menuitem_dnd">Rahats谋z Etme</string>
+    <string name="menuitem_alexa">Alexa</string>
+    <string name="menuitem_takephoto">Kamera Kumandas谋</string>
+    <string name="menuitem_mutephone">Telefonun Sesini Kapat</string>
+    <string name="menuitem_findphone">Telefonu Bul</string>
+    <string name="movement_intensity">Hareket yo臒unlu臒u</string>
+    <string name="activity_list_summary_activities">Etkinlikler</string>
+    <string name="activity_list_summary_intensity">Hareket
+\nYo臒unlu臒u</string>
+    <string name="activity_list_summary_active_time">Etkin zaman</string>
+    <string name="activity_list_summary_distance">Mesafe</string>
+    <string name="activity_list_summary_active_steps">Etkin ad谋mlar</string>
+    <string name="devicetype_amazfit_gts2">Amazfit GTS 2</string>
+    <string name="devicetype_casiogbx100">Casio GBX-100</string>
+    <string name="prefs_charts_tabs_summary">G枚r眉lebilir 莽izelge sekmeleri</string>
+    <string name="prefs_charts_tabs">脟izelge sekmeleri</string>
+    <string name="menuitem_pomodoro">Pomodoro 陌zleyici</string>
+    <string name="menuitem_sleep">Uyku</string>
+    <string name="menuitem_goal">Etkinlik Hedefi</string>
+    <string name="devicetype_amazfit_bipu">Amazfit Bip U</string>
+    <string name="pref_summary_connected_advertisement">Ba臒l谋 oldu臒unda bile ayg谋t谋 Bluetooth arac谋l谋臒谋yla bulunabilir hale getirir</string>
+    <string name="pref_title_connected_advertisement">Ba臒l谋yken g枚r眉lebilir</string>
+    <string name="prefs_autoremove_message">SMS bildirimlerini otomatik olarak kald谋r</string>
+    <string name="prefs_fake_ring_duration">Sahte s眉rekli zil</string>
+    <string name="prefs_operating_sounds">脟al谋艧ma Sesleri</string>
+    <string name="prefs_key_vibration">Tu艧 Titre艧imi</string>
+    <string name="prefs_autolight">Otomatik 谋艧谋k</string>
+    <string name="discovery_entered_invalid_authkey">Girdi臒iniz gizli kimlik do臒rulama anahtar谋 ge莽ersiz! D眉zenlemek i莽in ayg谋ta uzun bas谋n.</string>
+    <string name="devicetype_amazfit_vergel">Amazfit Verge Lite</string>
+    <string name="fw_upgrade_notice_amazfitvergel">Amazfit Verge Lite ayg谋t谋n谋za %s 眉r眉n yaz谋l谋m谋n谋 kurmak 眉zeresiniz.
+\n
+\nL眉tfen 枚nce .fw dosyas谋n谋, sonra .res dosyas谋n谋, ve son olarak .gps dosyas谋n谋 kurun. Saatiniz .fw dosyas谋n谋 kurduktan sonra yeniden ba艧lat谋lacak.
+\n
+\nNot: Bu dosyalar 枚nceden kurulanlarla tamamen ayn谋ysa .res ve .gps dosyalar谋n谋 kurman谋z gerekmez.
+\n
+\n陌LERLEMEN陌Z DURUMUNDA R陌SK S陌ZE A陌TT陌R!</string>
+    <string name="activity_DB_ShowContentButton">D谋艧a/陌莽e aktarma dizini i莽eri臒ini g枚ster</string>
+    <string name="activity_data_management_directory_content_title">Dizin i莽eri臒ini d谋艧a/i莽e aktar</string>
+    <string name="widget_settings_select_device_title">Ayg谋t se莽</string>
+    <string name="dbmanagementactivity_export_confirmation">Veriler ger莽ekten d谋艧ar谋 aktar谋ls谋n m谋\? 脰nceden d谋艧a aktar谋lan etkinlik verilerinizin (varsa) ve tercihlerinizin 眉zerine yaz谋lacak.</string>
+    <string name="dbmanagementactivity_export_data_title">Veriler D谋艧a Aktar谋ls谋n m谋\?</string>
+    <string name="devicetype_amazfit_bipupro">Amazfit Bip U Pro</string>
+    <string name="dbmanagementactivity_export_finished">Silme i艧lemi tamamland谋</string>
+    <string name="activity_db_management_clean_export_directory_text">D谋艧a/陌莽e aktarma dizinindeki d谋艧a aktar谋lan dosyalara, ayg谋t谋n谋zdaki herhangi bir uygulama taraf谋ndan eri艧ilebilir. E艧zamanlama veya yedeklemeden sonra bu dosyalar谋 kald谋rmak isteyebilirsiniz. Silmeden 枚nce bir yede臒ini ald谋臒谋n谋zdan emin olun. GPX dosyalar谋, alt dizinler ve otomatik d谋艧a aktar谋lan veri taban谋 dosyas谋 (varsa) silinmeyecektir. D谋艧a/陌莽e aktarma dizininin yolu 艧udur:</string>
+    <string name="activity_DB_clean_export_directory_warning_message">D谋艧a/陌莽e aktarma dizinindeki dosyalar ger莽ekten silinsin mi\?</string>
+    <string name="activity_DB_clean_export_directory_warning_title">D谋艧a/陌莽e aktarma dizinindeki dosyalar silinsin mi\?</string>
+    <string name="activity_db_management_clean_export_directory_label">D谋艧a/陌莽e aktarma dizinindeki dosyalar谋 sil</string>
+    <string name="dbmanagementactivity_error_cleaning_export_directory">%1$s d谋艧a aktarma dizininden dosyalar谋 silerken hata olu艧tu</string>
+    <string name="devicetype_amazfit_neo">Amazfit Neo</string>
+    <string name="activity_type_strength_training">Kuvvet Antrenman谋</string>
+    <string name="mi_h">mi/sa</string>
+    <string name="mi">mi</string>
+    <string name="minutes_mi">dak/mi</string>
+    <string name="ft">ft</string>
+    <string name="devicetype_amazfit_gts2_mini">Amazfit GTS 2 Mini</string>
+    <string name="devicetype_zepp_e">Zepp E</string>
+    <string name="devicetype_amazfit_gtr2e">Amazfit GTR 2e</string>
+    <string name="devicetype_waspos">Wasp-os</string>
+    <string name="battery_detail_activity_title">Pil bilgisi</string>
+    <string name="devicetype_amazfit_gts2e">Amazfit GTS 2e</string>
+    <string name="devicetype_amazfit_x">Amazfit X</string>
+    <string name="fw_upgrade_notice_amazfitx">AmazFit X ayg谋t谋n谋za %s 眉r眉n yaz谋l谋m谋n谋 kurmak 眉zeresiniz.
+\n
+\nL眉tfen 枚nce .fw dosyas谋n谋, sonra .res dosyas谋n谋 kurun. Bilekli臒iniz .fw dosyas谋n谋 kurduktan sonra yeniden ba艧lat谋lacak.
+\n
+\nNot: Daha 枚nce kurulanla tamamen ayn谋ysa .res dosyas谋n谋 kurman谋z gerekmez.
+\n
+\n陌LERLEMEN陌Z DURUMUNDA R陌SK S陌ZE A陌TT陌R!</string>
+    <string name="calendar_year">Y谋l</string>
+    <string name="calendar_six_months">6 ay</string>
+    <string name="calendar_month">Ay</string>
+    <string name="calendar_two_weeks">陌ki hafta</string>
+    <string name="calendar_week">Hafta</string>
+    <string name="calendar_day">G眉n</string>
+    <string name="battery_level">Pil seviyesi</string>
+    <string name="show_ongoing_activity">Devam eden etkinlik a莽谋l谋r penceresini g枚ster</string>
+    <string name="dialog_hide">Gizle</string>
+    <string name="calendar_three_months">3 ay</string>
+    <string name="fossil_hr_auth_failed">Kimlik do臒rulama ba艧ar谋s谋z oldu, s谋n谋rl谋 i艧levsellik</string>
+    <string name="fossil_hr_unavailable_unauthed">Kimli臒i do臒rulanmam谋艧 modda kullan谋lamaz</string>
+    <string name="fossil_hr_synced_activity_data">Etkinlik verileri e艧zamanland谋</string>
+    <string name="fossil_hr_commute_processing">陌stek i艧leniyor鈥�</string>
+    <string name="qhybrid_title_calibration">Kalibrasyon</string>
+    <string name="qhybrid_title_apps_management">Uygulama y枚netimi</string>
+    <string name="qhybrid_title_file_management">Dosya y枚netimi</string>
+    <string name="qhybrid_title_background_image">Arka plan g枚r眉nt眉s眉</string>
+    <string name="qhybrid_title_apps">Uygulamalar</string>
+    <string name="devicetype_um25">UM-25</string>
+    <string name="fossil_hr_warning_firmware_too_new">Saatin 眉r眉n yaz谋l谋m谋 莽ok yeni oldu臒u i莽in baz谋 i艧levler devre d谋艧谋 b谋rak谋ld谋</string>
+    <string name="prefs_sounds_summary">Ayg谋t谋n ne zaman bip sesi 莽谋karaca臒谋n谋 yap谋land谋r谋n</string>
+    <string name="prefs_sounds">Ses</string>
+    <string name="fw_upgrade_notice_amazfitneo">AmazFit Neo ayg谋t谋n谋za %s 眉r眉n yaz谋l谋m谋n谋 kurmak 眉zeresiniz.
+\n
+\nBilekli臒iniz .fw dosyas谋n谋 kurduktan sonra yeniden ba艧lat谋lacak.
+\n
+\n陌LERLEMEN陌Z DURUMUNDA R陌SK S陌ZE A陌TT陌R!</string>
+    <string name="menuitem_temperature">S谋cakl谋k</string>
+    <string name="menuitem_widgets">Widget\'lar</string>
+    <string name="menuitem_events">Etkinlikler</string>
+    <string name="fw_upgrade_notice_miband6">Mi Band 6 ayg谋t谋n谋za %s 眉r眉n yaz谋l谋m谋n谋 kurmak 眉zeresiniz.
+\n
+\nL眉tfen 枚nce .fw dosyas谋n谋, sonra .res dosyas谋n谋 kurun. Bilekli臒iniz .fw dosyas谋n谋 kurduktan sonra yeniden ba艧lat谋lacak.
+\n
+\nNot: Daha 枚nce kurulanla tamamen ayn谋ysa .res dosyas谋n谋 kurman谋z gerekmez.
+\n
+\n陌LERLEMEN陌Z DURUMUNDA R陌SK S陌ZE A陌TT陌R!</string>
+    <string name="devicetype_miband6">Mi Band 6</string>
+    <string name="pref_summary_canned_messages_dismisscall">脟a臒r谋lar谋 SMS mesaj谋yla saatten kapat谋n</string>
+    <string name="pref_summary_canned_messages_set">A艧a臒谋da yap谋land谋r谋lan mesajlar谋 ayg谋t谋n谋za g枚nderin</string>
+    <string name="qhybrid_summary_calibration">Saatin ibrelerini kalibre edin</string>
+    <string name="qhybrid_summary_file_management">Dosyalar谋 kar艧谋ya y眉kleyin ve indirin</string>
+    <string name="pref_summary_physical_buttons">Saatteki fiziksel d眉臒melerin i艧levlerini yap谋land谋r谋n</string>
+    <string name="pref_title_physical_buttons">Fiziksel d眉臒meler</string>
+    <string name="kind_agps_bundle">AGPS Paketi</string>
+    <string name="fossil_hr_new_action">Yeni eylem</string>
+    <string name="fossil_hr_new_action_cancel">iptal</string>
+    <string name="fossil_hr_edit_action">Eylemi d眉zenle</string>
+    <string name="fossil_hr_edit_action_delete">sil</string>
+    <string name="fossil_hr_commute_actions_explanation">Burada yap谋land谋r谋lan eylemler, saatinizdeki 陌艧e Gidip Gelme uygulamas谋nda g枚r眉necektir. Bu eylemlerin 眉retti臒i ama莽lar谋n nas谋l ele al谋naca臒谋 hakk谋nda bilgi i莽in wiki sayfas谋n谋 okuyun.</string>
+    <string name="qhybrid_calibration_100_steps">100 ad谋m</string>
+    <string name="qhybrid_calibration_10_steps">10 ad谋m</string>
+    <string name="qhybrid_calibration_1_step">1 ad谋m</string>
+    <string name="qhybrid_calibration_clockwise">Saat y枚n眉nde</string>
+    <string name="qhybrid_calibration_counterclockwise">Saat y枚n眉n眉n tersine</string>
+    <string name="watchface_install_info">A艧a臒谋daki saat aray眉z眉n眉 kurmak 眉zeresiniz:
+\n
+\n%1$s
+\nS眉r眉m %2$s, %3$s taraf谋ndan
+\n</string>
+    <string name="qhybrid_watchface_configuration_old_firmware">DN1.0.2.19r ve daha d眉艧眉k 眉r眉n yaz谋l谋m谋 s眉r眉m眉ne sahip saatler i莽in saat aray眉z眉 yap谋land谋rma ekran谋</string>
+    <string name="qhybrid_title_watchface">Saat aray眉z眉 yap谋land谋rmas谋</string>
+    <string name="notification_channel_low_battery_name">D眉艧眉k pil</string>
+    <string name="notification_channel_transfer_name">Veri aktar谋m谋</string>
+    <string name="qhybrid_pref_summary_external_intents">Di臒er Android uygulamalar谋n谋n dosya y眉klemesine/眉zerine yazmas谋na izin verir</string>
+    <string name="qhybrid_pref_title_external_intents">Tehlikeli harici ama莽lara izin ver</string>
+    <string name="pref_summary_developer_settings">Geli艧tiriciler taraf谋ndan kullan谋lan ayarlar ve i艧levler</string>
+    <string name="pref_title_developer_settings">Geli艧tirici ayarlar谋</string>
+    <string name="qhybrid_pref_summary_actions">陌艧e Gidip Gelme uygulamas谋 i莽in eylemler</string>
+    <string name="qhybrid_pref_title_actions">Eylemler</string>
+    <string name="devicetype_amazfit_trex_pro">Amazfit T-Rex Pro</string>
+    <string name="menuitem_barometer">Barometre</string>
+    <string name="pref_theme_black_background">Koyu temada siyah arka plan kullan</string>
+    <string name="button_watchface_preview">Saat 眉zerinde 枚n izle</string>
+    <string name="set">Ayarla</string>
+    <string name="watchface_upload_failed">Saat aray眉z眉n眉n kar艧谋ya y眉klenmesi ba艧ar谋s谋z oldu. L眉tfen tekrar deneyin.</string>
+    <string name="watchface_cache_confirm_overwrite">脰nbellekte bu ada sahip bir saat aray眉z眉 zaten var. 脺zerine yazmak istiyor musunuz\?</string>
+    <string name="watchface_setting_wrist_flick_duration">S眉re (ms cinsinden)</string>
+    <string name="watchface_setting_wrist_flick_minute">Dakika ibresi (-360 ila 360)</string>
+    <string name="watchface_setting_wrist_flick_hour">Saat ibresi (-360 ila 360)</string>
+    <string name="watchface_setting_wrist_flick_move_relative">陌breleri zamana g枚receli hareket ettir</string>
+    <string name="watchface_setting_desc_wrist_flick">(tamamen devre d谋艧谋 b谋rakmak i莽in, g枚receli hareketi etkinle艧tirin ve t眉m de臒erleri 0\'a ayarlay谋n)</string>
+    <string name="watchface_setting_title_wrist_flick">Bilek hareketi</string>
+    <string name="watchface_setting_display_refresh_partial">K谋smi yenileme (dakika olarak)</string>
+    <string name="watchface_setting_display_refresh_full">Tam yenileme (dakika olarak)</string>
+    <string name="watchface_setting_title_display_refresh_timeout">Ekran yenileme zaman a艧谋m谋</string>
+    <string name="watchface_dialog_title_settings">Saat aray眉z眉 ayarlar谋</string>
+    <string name="watchface_dialog_widget_color_black">Siyah</string>
+    <string name="watchface_dialog_widget_color_white">Beyaz</string>
+    <string name="watchface_dialog_widget_color">Renk</string>
+    <string name="qhybrid_title_watchface_designer">Saat aray眉z眉 tasar谋mc谋s谋</string>
+    <string name="watchface_dialog_widget_preset_right">Sa臒</string>
+    <string name="watchface_dialog_widget_preset_left">Sol</string>
+    <string name="watchface_dialog_widget_preset_bottom">A艧a臒谋</string>
+    <string name="watchface_dialog_widget_preset_top">Yukar谋</string>
+    <string name="watchface_dialog_widget_presets">Konum 枚n ayarlar谋</string>
+    <string name="watchface_dialog_widget_y_coordinate">Y koordinat谋 (en fazla 240)</string>
+    <string name="watchface_dialog_widget_x_coordinate">X koordinat谋 (en fazla 240)</string>
+    <string name="watchface_dialog_widget_type">T眉r</string>
+    <string name="appmanager_app_edit">D眉zenle</string>
+    <string name="button_watchface_save_apply">Kaydet ve uygula</string>
+    <string name="watchface_widget_type_heart_rate">Kalp ritmi</string>
+    <string name="watchface_widget_type_weather">Hava durumu</string>
+    <string name="watchface_widget_type_steps">Ad谋m</string>
+    <string name="watchface_widget_type_date">Tarih</string>
+    <string name="watchface_dialog_title_add_widget">Widget ekle</string>
+    <string name="watchface_toast_settings_incomplete">Ayarlar eksik, widget eklenmedi</string>
+    <string name="watchface_dialog_title_set_name">Saat aray眉z眉 ad谋n谋 ayarla</string>
+    <string name="button_watchface_settings">Saat aray眉z眉 ayarlar谋</string>
+    <string name="button_watchface_add_widget">Widget ekle</string>
+    <string name="button_watchface_select_image">Arka plan g枚r眉nt眉s眉n眉 de臒i艧tir</string>
+    <string name="button_watchface_edit_name">Ad谋 d眉zenle</string>
+    <string name="gpx_receiver_overwrite_some_files">Baz谋 dosya(lar) zaten var. 脺zerine yaz谋ls谋n m谋\?</string>
+    <string name="gpx_receiver_files_received">Al谋nan GPX dosya(lar)谋:</string>
+    <string name="gpx_receiver_activity_title">GPX Al谋c谋 Gadgetbridge</string>
+    <string name="devicetype_smaq2oss">SMA-Q2 OSS</string>
+    <string name="activity_summary_detail_editing_gpx_track">Ba臒lant谋l谋 GPX izlemesini d眉zenleme</string>
+    <string name="activity_summary_detail_clear_gpx_track">GPX izlemesini temizle</string>
+    <string name="activity_summary_detail_select_gpx_track">GPX izlemesi se莽</string>
+    <string name="heart_rate_result">脰l莽眉m sonu莽lar谋</string>
+    <string name="getting_heart_rate">脰l莽眉l眉yor</string>
+    <string name="blood_pressure">Kan bas谋nc谋</string>
+    <string name="watchface_setting_power_saving_hands">Giyilmedi臒inde el hareketlerini devre d谋艧谋 b谋rak</string>
+    <string name="watchface_setting_power_saving_display">Giyilmedi臒inde ekran g眉ncellemelerini devre d谋艧谋 b谋rak</string>
+    <string name="watchface_setting_title_power_saving">G眉莽 tasarrufu</string>
+    <string name="watchface_widget_type_chance_rain">Ya臒mur ya臒ma olas谋l谋臒谋</string>
+    <string name="watchface_widget_type_active_mins">Etkin dakikalar</string>
+    <string name="watchface_widget_type_2nd_tz">2. saat dilimi</string>
+    <string name="watchface_widget_type_calories">Kalori</string>
+    <string name="watchface_widget_type_battery">Pil</string>
+    <string name="pref_summary_huami_force_new_protocol">Ayg谋t谋n谋z bir 眉r眉n yaz谋l谋m谋 y眉kseltmesinden sonra art谋k ba臒lanm谋yorsa etkinle艧tirin</string>
+    <string name="pref_title_huami_force_new_protocol">Yeni Kimlik Do臒rulama Protokol眉</string>
+    <string name="menuitem_flashlight">El Feneri</string>
+    <string name="prefs_autoheartrate_interval">脰l莽眉m s谋kl谋臒谋</string>
+    <string name="prefs_autoheartrate_sleep">Uyku s谋ras谋nda 枚l莽眉m yap</string>
+    <string name="prefs_autoheartrate_measurement">Otomatik kalp ritmi 枚l莽眉mleri</string>
+    <string name="prefs_autoheartrate_summary">G眉n i莽inde ve ayr谋ca uykudayken d眉zenli aral谋klarla kalp ritmi 枚l莽眉mleri</string>
+    <string name="prefs_autoheartrate">Otomatik Kalp Ritmi</string>
+    <string name="enable_notifications_summary">脟a臒r谋lar, mesajlar ve daha fazlas谋 i莽in bildirimler</string>
+    <string name="enable_vibrations_summary">脟a臒r谋lar, mesajlar, bildirimler ve daha fazlas谋 i莽in titre艧imler</string>
+    <string name="prefs_notifications_enable">Bildirimleri etkinle艧tir</string>
+    <string name="prefs_vibration_enable">Titre艧imleri etkinle艧tir</string>
+    <string name="prefs_sleep_time_summary">Uykunun kaydedildi臒i zamanlar谋 belirtir</string>
+    <string name="prefs_sleep_time_label">Uyku saatlerini tan谋mla</string>
+    <string name="prefs_sleep_time">Uyku zamanlar谋</string>
+    <string name="devicetype_fitpro">FitPro</string>
+    <string name="pref_title_ping_tone">Ping sesi</string>
+    <string name="devicetype_nothingear1">Nothing Ear (1)</string>
+    <string name="nothing_prefs_inear_summary">Kulakl谋klar谋 takman谋za ba臒l谋 olarak m眉zi臒i oynat/duraklat</string>
+    <string name="nothing_prefs_inear_title">Kulak i莽i alg谋lama</string>
+    <string name="nothing_prefs_audiomode_title">Ses modu</string>
+    <string name="pref_applications_settings">Uygulamalar listesi</string>
+    <string name="pref_title_notification_use_as">Uygulamalar listesini kullanarak鈥�</string>
+    <string name="toast_app_must_not_be_selected">Yap谋land谋rmak i莽in uygulama se莽ili olmamal谋d谋r</string>
+    <string name="pref_header_notification_application_settings">Uygulama ba艧谋na ayarlar</string>
+    <string name="title_activity_notification_management">Bildirim ayarlar谋</string>
+    <string name="check_all_applications">T眉m uygulamalar谋 i艧aretle</string>
+    <string name="uncheck_all_applications">T眉m uygulamalar谋n i艧aretini kald谋r</string>
+    <string name="pref_title_notification_use_as_deny">Se莽ili uygulamalardan gelen bildirimleri reddet</string>
+    <string name="pref_title_notification_use_as_allow">Se莽ili uygulamalardan gelen bildirimlere izin ver</string>
+    <string name="pref_title_message_privacy_mode">Mesaj gizlilik modu</string>
+    <string name="pref_message_privacy_mode_off">T眉m i莽eri臒i g枚r眉nt眉le</string>
+    <string name="pref_message_privacy_mode_complete">T眉m i莽eri臒i gizle</string>
+    <string name="toast_app_must_be_selected">Yap谋land谋rmak i莽in uygulama se莽ili olmal谋d谋r</string>
+    <string name="prefs_equalizer_preset">Dengeleyici 脰n Ayar谋</string>
+    <string name="pref_title_equalizer_bass_boost">Bas g眉莽lendirme</string>
+    <string name="pref_title_equalizer_clear">Temiz</string>
+    <string name="pref_title_equalizer_trebble">Tiz g眉莽lendirme</string>
+    <string name="prefs_dolby_mode">Dolby Modu</string>
+    <string name="prefs_equalizer">Dengeleyici</string>
+    <string name="prefs_dolby_summary">Dengeleyici i莽in Dolby 枚n ayar谋</string>
+    <string name="prefs_game_mode">Oyun modu</string>
+    <string name="prefs_game_mode_summary">Yaln谋zca telefonunuz oyun modunu destekliyorsa</string>
+    <string name="prefs_ambient_volume">Ortam ses seviyesi</string>
+    <string name="prefs_ambient_voice_focus">Ses Odaklama</string>
+    <string name="prefs_ambient_voice_summary">Sesi 枚ne 莽谋kar</string>
+    <string name="prefs_ambient_sound">Ortam Sesi</string>
+    <string name="prefs_ambient_mode">Ortam Modu</string>
+    <string name="prefs_left">Sol</string>
+    <string name="prefs_right">Sa臒</string>
+    <string name="prefs_galaxy_touch_options">Dokunma Se莽enekleri</string>
+    <string name="prefs_galaxy_buds_experimental">Deneysel</string>
+    <string name="devicetype_galaxybuds">Galaxy Buds</string>
+    <string name="pref_title_equalizer_soft">Yumu艧ak</string>
+    <string name="pref_title_equalizer_dynamic">Dinamik</string>
+    <string name="prefs_equalizer_summary">Dengeleyiciyi etkinle艧tirin veya devre d谋艧谋 b谋rak谋n</string>
+    <string name="prefs_touch_lock">Dokunma Kilidi</string>
+    <string name="prefs_touch_lock_summary">Dokunma etkinliklerini devre d谋艧谋 b谋rak谋n</string>
+    <string name="battery_case">Pil kutusu</string>
+    <string name="left_earbud">Sol kulakl谋k</string>
+    <string name="right_earbud">Sa臒 kulakl谋k</string>
+    <string name="devicetype_galaxybuds_live">Galaxy Buds Live</string>
+    <string name="pref_title_equalizer_normal">Normal</string>
+    <string name="prefs_active_noise_cancelling">Etkin G眉r眉lt眉 Engelleme</string>
+    <string name="prefs_active_noise_cancelling_summary">脟evredeki sesleri engelleyin</string>
+    <string name="sony_sound_position_off">Kapal谋</string>
+    <string name="sony_sound_position_front">脰n</string>
+    <string name="sony_warn_sbc_codec">Uyar谋: Bu ayarlar yaln谋zca SBC ses kod 莽枚z眉c眉 i莽in 莽al谋艧谋r.</string>
+    <string name="sony_equalizer">Dengeleyici</string>
+    <string name="sony_equalizer_preset_off">Kapal谋</string>
+    <string name="sony_equalizer_preset_bright">Parlak</string>
+    <string name="sony_equalizer_preset_excited">Heyecanl谋</string>
+    <string name="sony_equalizer_preset_mellow">Yumu艧ak</string>
+    <string name="sony_equalizer_preset_bass_boost">Bas G眉莽lendirme</string>
+    <string name="sony_equalizer_preset_speech">Konu艧ma</string>
+    <string name="sony_equalizer_preset_manual">El ile</string>
+    <string name="pref_header_sony_equalizer_preset_custom_1">脰zel 脰n Ayar 1</string>
+    <string name="sony_equalizer_band_400">400</string>
+    <string name="sony_equalizer_band_2500">2.5b</string>
+    <string name="watchface_widget_type_custom">脰zel widget</string>
+    <string name="pref_header_other">Di臒er</string>
+    <string name="pressure_relief_summary">Etkin G眉r眉lt眉 Engelleme kullan谋lmad谋臒谋nda kulaklarda bas谋n莽 hissini 枚nleyin</string>
+    <string name="pref_header_system">Sistem</string>
+    <string name="pref_header_equalizer">Dengeleyici</string>
+    <string name="devicetype_sony_wh_1000xm3">Sony WH-1000XM3</string>
+    <string name="prefs_pressure_relief">Ortam sesi ile bas谋n莽 rahatlatma</string>
+    <string name="sony_equalizer_preset_vocal">Vokal</string>
+    <string name="pref_header_sony_ambient_sound_control">Ortam Sesi Denetimi</string>
+    <string name="sony_ambient_sound">Mod</string>
+    <string name="sony_sound_position">Ses Konumu</string>
+    <string name="sony_sound_position_rear_right">Arka Sa臒</string>
+    <string name="sony_ambient_sound_noise_cancelling">G眉r眉lt眉 Engelleme</string>
+    <string name="sony_ambient_sound_off">Kapal谋</string>
+    <string name="sony_ambient_sound_wind_noise_reduction">R眉zgar G眉r眉lt眉s眉 Azaltma</string>
+    <string name="sony_ambient_sound_ambient_sound">Ortam Sesi</string>
+    <string name="sony_ambient_sound_focus_voice">Sese Odaklan</string>
+    <string name="sony_ambient_sound_level">Ortam Sesi Seviyesi</string>
+    <string name="sony_surround_mode_off">Kapal谋</string>
+    <string name="sony_surround_mode_concert_hall">Konser Salonu</string>
+    <string name="sony_automatic_power_off_off">Kapatma</string>
+    <string name="sony_automatic_power_off_5_min">5 dakika</string>
+    <string name="sony_sound_position_front_right">脰n Sa臒</string>
+    <string name="sony_surround_mode_arena">Arena</string>
+    <string name="sony_surround_mode_club">Kul眉p</string>
+    <string name="sony_equalizer_preset_relaxed">Rahat</string>
+    <string name="pref_header_sony_equalizer_preset_custom_2">脰zel 脰n Ayar 2</string>
+    <string name="sony_equalizer_clear_bass">Temiz Bas</string>
+    <string name="sony_sound_position_front_left">脰n Sol</string>
+    <string name="sony_sound_position_rear_left">Arka Sol</string>
+    <string name="sony_surround_mode">脟evre Modu</string>
+    <string name="sony_equalizer_preset_custom_2">脰zel 2</string>
+    <string name="sony_equalizer_band_6300">6.3b</string>
+    <string name="sony_automatic_power_off">Otomatik Kapanma</string>
+    <string name="sony_automatic_power_off_30_min">30 dakika</string>
+    <string name="sony_automatic_power_off_1_hour">1 saat</string>
+    <string name="sony_automatic_power_off_3_hour">3 saat</string>
+    <string name="sony_surround_mode_outdoor_stage">A莽谋k Sahne</string>
+    <string name="sony_equalizer_preset_treble_boost">Tiz G眉莽lendirme</string>
+    <string name="sony_equalizer_preset_custom_1">脰zel 1</string>
+    <string name="sony_equalizer_band_16000">16b</string>
+    <string name="sony_equalizer_band_1000">1b</string>
+    <string name="sony_touch_sensor">Dokunma sens枚r眉 denetimi</string>
+    <string name="sony_notification_voice_guide">Bildirimler ve Sesli Rehber</string>
+    <string name="prefs_activity_in_device_card_title_summary">Ayg谋t kart谋nda ge莽erli ad谋mlar谋, mesafeyi veya uykuyu g枚ster</string>
+    <string name="prefs_activity_in_device_card_sleep_title">Uyku</string>
+    <string name="prefs_activity_in_device_card_sleep_title_summary">Uyku s眉resini g枚ster</string>
+    <string name="prefs_activity_in_device_card_steps_title_summary">Toplam ad谋mlar谋 g枚ster</string>
+    <string name="device_card_activity_card_title">Ayg谋t kart谋ndaki etkinlik bilgileri</string>
+    <string name="device_card_activity_card_title_summary">Ayg谋t kart谋nda hangi etkinlik ayr谋nt谋lar谋n谋n g枚r眉nt眉lenece臒ini se莽in</string>
+    <string name="prefs_activity_in_device_card_title">Ayg谋t kart谋nda etkinlik bilgilerini g枚ster</string>
+    <string name="prefs_activity_in_device_card_distance_title_summary">Mesafe, ad谋mlardan ve ad谋m uzunlu臒undan hesaplan谋r (Ayarlar - Hakk谋n谋zda b枚l眉m眉nden ayarlanabilir)</string>
+    <string name="sony_automatic_power_off_when_taken_off">脟谋kar谋ld谋臒谋nda</string>
+    <string name="prefs_fm_presets_presets">脰n ayarlar</string>
+    <string name="prefs_fm_preset_instructions">脰n ayar谋 saklamak i莽in d眉臒meye uzun bas谋n</string>
+    <string name="watchface_dialog_widget_timezone">Saat dilimi</string>
+    <string name="watchface_dialog_widget_timeout_show_circle">Zaman a艧谋m谋nda 莽ember g枚ster</string>
+    <string name="watchface_dialog_widget_timeout_hide_text">Zaman a艧谋m谋nda metni gizle</string>
+    <string name="watchface_dialog_widget_update_timeout">Dakika cinsinden g眉ncelleme zaman a艧谋m谋</string>
+    <string name="qhybrid_summary_on_device_confirmation">Ayg谋t 眉zerinde e艧le艧tirme onaylar谋 can s谋k谋c谋 olabilir. Bunlar谋 devre d谋艧谋 b谋rakmak, i艧levsellik kayb谋na neden olabilir.</string>
+    <string name="qhybrid_title_on_device_confirmation">Ayg谋t 眉zerinde e艧le艧tirme onay谋n谋 etkinle艧tir</string>
+    <string name="title_activity_set_reminders">Hat谋rlat谋c谋lar谋 yap谋land谋r</string>
+    <string name="controlcenter_start_configure_reminders">Hat谋rlat谋c谋lar谋 yap谋land谋r</string>
+    <string name="reminder_repeat">Tekrarla</string>
+    <string name="reminder_time_once">%1$s, bir defa</string>
+    <string name="reminder_time_every_day">%1$s, her g眉n</string>
+    <string name="reminder_time_every_week">%1$s, her hafta</string>
+    <string name="reminder_every_day">Her g眉n</string>
+    <string name="reminder_every_week">Her hafta</string>
+    <string name="reminder_every_month">Her ay</string>
+    <string name="reminder_every_year">Her y谋l</string>
+    <string name="reminder_delete_confirm_title">Hat谋rlat谋c谋y谋 sil</string>
+    <string name="reminder_no_free_slots_title">Bo艧 yuva yok</string>
+    <string name="reminder_no_free_slots_description">Ayg谋tta hat谋rlat谋c谋lar i莽in bo艧 yuva yok (toplam yuva: %1$s)</string>
+    <string name="title_activity_reminder_details">Hat谋rlat谋c谋 ayr谋nt谋lar谋</string>
+    <string name="miband_prefs_reserve_reminder_calendar">Gelecek etkinlikler i莽in ayr谋lacak hat谋rlat谋c谋lar</string>
+    <string name="prefs_reserve_reminder_calendar_summary">E艧zamanlanacak takvim etkinliklerinin say谋s谋</string>
+    <string name="reminder_time">Saat</string>
+    <string name="reminder_time_every_month">%1$s, her ay</string>
+    <string name="reminder_date">Tarih</string>
+    <string name="reminder_message">Mesaj</string>
+    <string name="reminder_time_every_year">%1$s, her y谋l</string>
+    <string name="reminder_once">Bir defa</string>
+    <string name="reminder_delete_confirm_description">Hat谋rlat谋c谋y谋 silmek istedi臒inizden emin misiniz\?</string>
+    <string name="mi2_prefs_do_not_disturb_lift_wrist">Rahats谋z Etme s谋ras谋nda kald谋r谋ld谋臒谋nda ekran谋 etkinle艧tir</string>
+    <string name="maxHR">En Y眉ksek Kalp Ritmi</string>
+    <string name="averageAltitude">Ortalama</string>
+    <string name="minSpeed">Asgari</string>
+    <string name="minHR">En D眉艧眉k Kalp Ritmi</string>
+    <string name="maxCadence">En Y眉ksek Tempo</string>
+    <string name="maxStride">En Uzun Ad谋m</string>
+    <string name="minStride">En K谋sa Ad谋m</string>
+    <string name="averageCadence">Ortalama Tempo</string>
+    <string name="minCadence">En D眉艧眉k Tempo</string>
+    <string name="spm">ad谋m/dak</string>
+    <string name="pref_header_sony_equalizer_bands">Bant</string>
+    <string name="sony_audio_upsampling">Ses 脺st 脰rnekleme</string>
+    <string name="sony_button_mode_right">D眉臒me Modu (Sa臒)</string>
+    <string name="sony_button_mode_playback_control">Oynat谋m Denetimi</string>
+    <string name="discover_unsupported_devices_description">Bu se莽ene臒in etkinle艧tirilmesi, tarama s谋ras谋nda ke艧fedilen t眉m bluetooth ayg谋tlar谋n谋 g枚r眉nt眉leyecektir. K谋sa dokunma, ayg谋t ad谋n谋 ve MAC adresini panoya kopyalayacakt谋r. Uzun bas谋ld谋臒谋nda, `Test ayg谋t谋 ekle` ileti艧im kutusu a莽谋lacakt谋r. Olas谋 uygulama donma sorunlar谋na neden olabilir.</string>
+    <string name="controlcenter_power_off_confirm_description">Ayg谋t谋 kapatmak istedi臒inizden emin misiniz\?</string>
+    <string name="controlcenter_power_off_confirm_title">Kapat</string>
+    <string name="devicetype_sony_wf_sp800n">Sony WF-SP800N</string>
+    <string name="controlcenter_power_off">Kapat</string>
+    <string name="activity_prefs_discovery_pairing">Ke艧if ve E艧le艧tirme se莽enekleri</string>
+    <string name="add_test_device">Test ayg谋t谋 ekle</string>
+    <string name="discover_unsupported_devices">Desteklenmeyen ayg谋tlar谋 ke艧fet</string>
+    <string name="sony_button_mode_off">Kapal谋</string>
+    <string name="sony_pause_when_taken_off">Kulakl谋klar 莽谋kar谋ld谋臒谋nda duraklat</string>
+    <string name="sony_button_mode_left">D眉臒me Modu (Sol)</string>
+    <string name="sony_button_mode_ambient_sound_control">Ortam Sesi Denetimi</string>
+    <string name="sony_button_mode_volume_control">Ses Denetimi</string>
+    <string name="descentDistance">Yoku艧 a艧a臒谋 mesafe</string>
+    <string name="ascentDistance">Yoku艧 yukar谋 mesafe</string>
+    <string name="devicetype_vesc">VESC</string>
+    <string name="devicetype_bose_qc35">Bose QC35</string>
+    <string name="activity_type_hiking">Do臒a Y眉r眉y眉艧眉</string>
+    <string name="activity_type_climbing">T谋rmanma</string>
+    <string name="distance_format_meters">###m</string>
+    <string name="distance_format_miles">###.#mil</string>
+    <string name="pref_activity_recognize_running">ko艧may谋 tan谋</string>
+    <string name="pref_activity_recognize_rowing">k眉rek 莽ekmeyi tan谋</string>
+    <string name="prefs_activity_recognition">Etkinlik tan谋ma ayarlar谋</string>
+    <string name="pref_activity_recognize_walking">y眉r眉meyi tan谋</string>
+    <string name="distance_format_kilometers">###.#km</string>
+    <string name="pref_activity_recognize_biking">bisiklet s眉rmeyi tan谋</string>
+    <string name="pref_activity_recognition_mode_auto">otomatik</string>
+    <string name="fossil_hr_button_config_info">Baz谋 d眉臒meler, i艧levleri saatin 眉r眉n yaz谋l谋m谋nda kodlanm谋艧 oldu臒undan yap谋land谋r谋lamaz.
+\n
+\nUyar谋: Resmi Fossil uygulamas谋ndan bir saat aray眉z眉 kuruldu臒unda 眉st d眉臒meye uzun basmak, widget\'lar谋 g枚sterme/gizleme aras谋nda ge莽i艧 yapacakt谋r.</string>
+    <string name="pref_activity_recognition_mode_none">hi莽biri</string>
+    <string name="pref_activity_recognition_mode_ask">sor</string>
+    <string name="menuitem_menu">Men眉</string>
+    <string name="about_hash">Kay谋t %s</string>
+    <string name="devicetype_domyos_t540">Domyos T540</string>
+    <string name="pref_device_action_broadcast">Yay谋n G枚nder</string>
+    <string name="distance_format_feet">###ft</string>
+    <string name="devicetype_sony_wh_1000xm4">Sony WH-1000XM4</string>
+    <string name="sony_speak_to_chat_sensitivity">Ses Alg谋lama Hassasiyeti</string>
+    <string name="discovery_bluetooth_le_scan">Bluetooth LE taramas谋:</string>
+    <string name="discovery_bluetooth_scan">Bluetooth taramas谋:</string>
+    <string name="sony_speak_to_chat">Sohbet i莽in konu艧</string>
+    <string name="sony_speak_to_chat_sensitivity_auto">Otomatik</string>
+    <string name="sony_speak_to_chat_sensitivity_high">Y眉ksek</string>
+    <string name="sony_speak_to_chat_sensitivity_low">D眉艧眉k</string>
+    <string name="watchface_dialog_widget_width">Widget geni艧li臒i (piksel olarak)</string>
+    <string name="device_unsupported">DESTEKLENM陌YOR</string>
+    <string name="sony_speak_to_chat_focus_on_voice">Sese Odaklan</string>
+    <string name="sony_speak_to_chat_timeout">Zaman a艧谋m谋</string>
+    <string name="sony_speak_to_chat_timeout_off">Kapal谋</string>
+    <string name="sony_speak_to_chat_timeout_long">Uzun (1dak)</string>
+    <string name="sony_connect_two_devices">Ayn谋 anda 2 ayg谋ta ba臒lan</string>
+    <string name="sony_speak_to_chat_timeout_short">K谋sa (15sn)</string>
+    <string name="sony_speak_to_chat_timeout_standard">Standart (30sn)</string>
+    <string name="pref_header_sony_device_info">Ayg谋t Bilgileri</string>
+    <string name="audio_codec">Ses Kod 脟枚z眉c眉</string>
+    <string name="unknown">Bilinmeyen</string>
+    <string name="start">Ba艧la</string>
+    <string name="sony_anc_optimize_title">陌yile艧tir</string>
+    <string name="sony_anc_optimize_confirmation_title">G眉r眉lt眉 Engelleme 陌yile艧tirici</string>
+    <string name="pref_anc_optimizer_state_pressure">Atmosfer bas谋nc谋</string>
+    <string name="sony_anc_optimizer_status_wearing_condition">Takma durumu 枚l莽眉l眉yor鈥�</string>
+    <string name="sony_anc_optimizer_status_analyzing">脟枚z眉mleniyor鈥�</string>
+    <string name="sony_anc_optimize_confirmation_description">Kulakl谋klar谋 normalde yapt谋臒谋n谋z gibi kullan谋n. Takma durumu veya atmosfer bas谋nc谋 de臒i艧irse, iyile艧tiriciyi yeniden 莽al谋艧t谋r谋n.</string>
+    <string name="pref_header_sony_anc_optimizer">G眉r眉lt眉 Engelleme 陌yile艧tirici</string>
+    <string name="sony_anc_optimizer_status_starting">Ba艧lat谋l谋yor鈥�</string>
+    <string name="sony_anc_optimize_description">G眉r眉lt眉 engelleme iyile艧tiriciyi ba艧latmak i莽in t谋klay谋n.</string>
+    <string name="sony_anc_optimizer_status_not_running">脟al谋艧m谋yor</string>
+    <string name="sony_anc_optimizer_status_atmospheric_pressure">Atmosfer bas谋nc谋 枚l莽眉l眉yor鈥�</string>
+    <string name="sony_anc_optimizer_status_finished">Bitiriliyor鈥�</string>
+    <string name="devicetype_amazfit_pop_pro">Amazfit Pop Pro</string>
+    <string name="devicetype_amazfit_pop">Amazfit Pop</string>
+    <string name="menuitem_nothing">Hi莽bir 艦ey</string>
+    <string name="pref_title_opentracks_packagename">OpenTracks paket ad谋</string>
+    <string name="pref_summary_opentracks_packagename">Harici fitness uygulamas谋nda GPS izleme kayd谋n谋 ba艧latmak/durdurmak i莽in kullan谋l谋r.</string>
+    <string name="pref_device_action_fitness_app_control_stop">Fitness Uygulamas谋 陌zlemesini Durdur</string>
+    <string name="pref_device_action_fitness_app_control_start">Fitness Uygulamas谋 陌zlemesini Ba艧lat</string>
+    <string name="pref_title_notifications_generic_settings">Android bildirim ayarlar谋</string>
+    <string name="autoExport_lastTime_label">Son Otomatik D谋艧a Aktarma: %1$s</string>
+    <string name="activity_db_management_autoexport_enabled_no">Otomatik d谋艧a aktarma etkin de臒il.</string>
+    <string name="activity_db_management_autoexport_scheduled_yes">Otomatik d谋艧a aktarma (ba艧lang谋莽ta) %1$s i莽in zamanland谋</string>
+    <string name="activity_db_management_autoexport_scheduled_no">Otomatik d谋艧a aktarma zamanlanmad谋.</string>
+    <string name="activity_db_management_autoexport_enabled_yes">Otomatik d谋艧a aktarma etkin.</string>
+    <string name="activity_db_management_autoexport_location">Konum anla艧谋lamad谋. Bu, muhtemelen daha yeni bir Android izin sistemi sorunudur. B眉y眉k olas谋l谋kla, otomatik d谋艧a aktarma 艧u anda 莽al谋艧m谋yor.</string>
+    <string name="watchface_dialog_pre_setting_position">konumu %s olarak 枚n ayarlama</string>
+    <string name="watchface_setting_light_up_on_notification">Yeni bildirimde 谋艧谋臒谋 a莽</string>
+    <string name="menuitem_email">E-posta</string>
+    <string name="gadgetbridge_running_banglejs_main">Bangle.js 莽al谋艧谋yor</string>
+    <string name="application_name_banglejs_nopebble">Bangle.js Gadgetbridge</string>
+    <string name="title_activity_controlcenter_banglejs_nopebble">Bangle.js Gadgetbridge</string>
+    <string name="about_activity_title_banglejs_nopebble">Bangle.js Gadgetbridge Hakk谋nda</string>
+    <string name="about_activity_title_main_nightly">Gadgetbridge Gecelik Hakk谋nda</string>
+    <string name="about_description_main_nightly">Sat谋c谋lar谋n kapal谋 kaynakl谋 Android ayg谋t uygulamalar谋n谋n yerine bulut gerektirmeyen copyleft 枚zg眉r alternatif. Gadgetbridge gecelik s眉r眉mleri. Pebble sa臒lay谋c谋s谋ndaki bir 莽ak谋艧ma nedeniyle Gadgetbridge veya Pebble uygulamas谋 zaten kuruluysa bu kurulamaz.</string>
+    <string name="gadgetbridge_running_main_nightly">Gecelik GB 莽al谋艧谋yor</string>
+    <string name="title_activity_controlcenter_main_nopebble">Gadgetbridge Gecelik Pebble Yok</string>
+    <string name="about_activity_title_main_nopebble">Gadgetbridge Gecelik Pebble Yok Hakk谋nda</string>
+    <string name="gadgetbridge_running_main_nopebble">Gecelik PebbleYok GB 莽al谋艧谋yor</string>
+    <string name="title_activity_controlcenter_banglejs_main">Bangle.js Gadgetbridge</string>
+    <string name="about_activity_title_banglejs_main">Bangle.js Gadgetbridge Hakk谋nda</string>
+    <string name="about_description_banglejs_main">Bangle.js i莽in Gadgetbridge projesinin 眉zerine in艧a edilmi艧, internet eri艧imi eklenmi艧 Android yard谋mc谋 uygulamas谋.
+\n
+\nGoogle Play Store politikalar谋 nedeniyle, uygulaman谋n kendisinde bir ba臒谋艧 ba臒lant谋s谋na izin verilmiyor, ancak bu uygulamay谋 be臒endiyseniz l眉tfen a艧a臒谋daki Gadgetbridge ana sayfas谋 眉zerinden ba臒谋艧 yapmay谋 d眉艧眉n眉n.</string>
+    <string name="title_activity_controlcenter_main_nightly">Gadgetbridge Gecelik</string>
+    <string name="application_name_main_nopebble">Gadgetbridge (Gecelik, Pebble sa臒lay谋c谋s谋 yok)</string>
+    <string name="application_name_banglejs_main">Bangle.js Gadgetbridge</string>
+    <string name="about_description_main_nopebble">Sat谋c谋lar谋n kapal谋 kaynakl谋 Android ayg谋t uygulamalar谋n谋n yerine bulut gerektirmeyen copyleft 枚zg眉r alternatif. Gadgetbridge gecelik s眉r眉mleri. Bu s眉r眉mde, 莽ak谋艧malar谋 枚nlemek i莽in Pebble sa臒lay谋c谋s谋n谋n ad谋 de臒i艧tirilmi艧tir, bu nedenle Pebble ile ilgili baz谋 b眉t眉nle艧meler 莽al谋艧mayacakt谋r, ancak mevcut bir Gadgetbridge kurulumunun yan谋na kurulabilir.</string>
+    <string name="about_description_banglejs_nopebble">Bangle.js i莽in Gadgetbridge projesinin 眉zerine in艧a edilmi艧, internet eri艧imi eklenmi艧 Android yard谋mc谋 uygulamas谋.
+\n
+\nGoogle Play Store politikalar谋 nedeniyle, uygulaman谋n kendisinde bir ba臒谋艧 ba臒lant谋s谋na izin verilmiyor, ancak bu uygulamay谋 be臒endiyseniz l眉tfen a艧a臒谋daki Gadgetbridge ana sayfas谋 眉zerinden ba臒谋艧 yapmay谋 d眉艧眉n眉n.</string>
+    <string name="gadgetbridge_running_banglejs_nopebble">Bangle.js 莽al谋艧谋yor</string>
+    <string name="application_name_main_nightly">Gadgetbridge (Gecelik)</string>
+    <string name="prefs_activate_display_on_lift_sensitivity">Hassasiyet</string>
+    <string name="normal">Normal</string>
+    <string name="sensitive">Hassas</string>
+    <string name="pref_device_action_fitness_app_control_toggle">Fitness Uygulamas谋 陌zlemesini De臒i艧tir</string>
+    <string name="pref_world_clocks_title">D眉nya Saatleri</string>
+    <string name="pref_world_clocks_summary">Di臒er saat dilimleri i莽in saatleri yap谋land谋r谋n</string>
+    <string name="world_clock_delete_confirm_title">\'%1$s\' sil</string>
+    <string name="world_clock_delete_confirm_description">D眉nya saatini silmek istedi臒inizden emin misiniz\?</string>
+    <string name="world_clock_no_free_slots_title">Bo艧 yuva yok</string>
+    <string name="title_activity_world_clock_details">D眉nya saati ayr谋nt谋lar谋</string>
+    <string name="world_clock_no_free_slots_description">Ayg谋tta d眉nya saatleri i莽in bo艧 yuva yok (toplam yuva: %1$s)</string>
+    <string name="world_clock_timezone">Saat Dilimi</string>
+    <string name="world_clock_label">Etiket</string>
+    <string name="activity_type_freestyle">Serbest Stil</string>
+    <string name="activity_type_elliptical">Eliptik Bisiklet</string>
+    <string name="mi5_prefs_workout_activity_types">Egzersiz Etkinlik T眉rleri</string>
+    <string name="activity_type_outdoor_cycling">A莽谋k Havada Bisiklet S眉rme</string>
+    <string name="mi5_prefs_workout_activity_types_summary">Egzersizler ekran谋nda g枚r眉nt眉lenecek etkinlik t眉rlerini se莽in</string>
+    <string name="activity_type_outdoor_running">A莽谋k Havada Ko艧u</string>
+    <string name="pref_screen_notification_idle_alerts">Bo艧ta Kalma Uyar谋lar谋</string>
+    <string name="pref_screen_vibration_patterns_title">Titre艧im Modelleri</string>
+    <string name="pref_screen_notification_profile_event_reminder">Etkinlik hat谋rlat谋c谋</string>
+    <string name="pref_screen_notification_profile_find_device">Ayg谋t bul</string>
+    <string name="pref_screen_vibration_patterns_summary">Farkl谋 bildirimler i莽in titre艧im modellerini yap谋land谋r谋n</string>
+    <string name="devicetype_sony_wf_1000xm3">Sony WF-1000XM3</string>
+    <string name="pref_ambient_sound_tone">Ortam Sesi Tonu</string>
+    <string name="prefs_noise_control_with_one_earbud">Bir kulak ile g眉r眉lt眉 denetimi</string>
+    <string name="pref_switch_controls_ambient_off">Ortam 鈫愨啋 Kapal谋</string>
+    <string name="pref_switch_controls_anc_ambient">G眉r眉lt眉 engelleme 鈫愨啋 Ortam</string>
+    <string name="pref_switch_controls_anc_off">G眉r眉lt眉 engelleme 鈫愨啋 Kapal谋</string>
+    <string name="prefs_noise_control">G眉r眉lt眉 denetimi</string>
+    <string name="prefs_voice_detect">Ses alg谋lama</string>
+    <string name="pref_voice_detect_duration_5">5 saniye</string>
+    <string name="heartrate_bpm_100">100 kalp at谋艧谋/dakika</string>
+    <string name="heartrate_bpm_105">105 kalp at谋艧谋/dakika</string>
+    <string name="heartrate_bpm_120">120 kalp at谋艧谋/dakika</string>
+    <string name="heartrate_bpm_130">130 kalp at谋艧谋/dakika</string>
+    <string name="heartrate_bpm_135">135 kalp at谋艧谋/dakika</string>
+    <string name="heartrate_bpm_140">140 kalp at谋艧谋/dakika</string>
+    <string name="heartrate_bpm_145">145 kalp at谋艧谋/dakika</string>
+    <string name="heartrate_bpm_150">150 kalp at谋艧谋/dakika</string>
+    <string name="prefs_heartrate_alert_experimental_title">Kalp ritmi uyar谋s谋 (deneysel)</string>
+    <string name="prefs_heartrate_alert_experimental_description">Son 10 dakika i莽inde herhangi bir belirgin fiziksel etkinlik olmaks谋z谋n, kalp ritmi bir e艧i臒i a艧t谋臒谋nda bilekli臒i titret. Bu 枚zellik deneyseldir ve kapsaml谋 bir 艧ekilde test edilmemi艧tir.</string>
+    <string name="prefs_stress_monitoring_title">Stres izleme</string>
+    <string name="prefs_stress_monitoring_description">Dinlenirken stres seviyesini izleyin</string>
+    <string name="prefs_activity_monitoring_title">Etkinlik izleme</string>
+    <string name="prefs_activity_monitoring_description">Kalp ritmi yakalama do臒rulu臒unu art谋rmak i莽in bileklik fiziksel egzersiz alg谋lad谋臒谋nda kalp ritmi alg谋lama s谋kl谋臒谋n谋 otomatik olarak artt谋r.</string>
+    <string name="prefs_in_ear_detection_summary">Tak谋l谋ysa aramalar谋 kulakl谋klar谋n谋zdan oynat谋n</string>
+    <string name="prefs_seamless_connection_switch_title">Kesintisiz ba臒lant谋 de臒i艧imi</string>
+    <string name="prefs_ambient_volume_left">Ortam Ses Seviyesi Sol</string>
+    <string name="prefs_ambient_sound_during_call_title">Arama s谋ras谋nda ortam sesi</string>
+    <string name="prefs_ambient_sound_during_call_summary">Aramalar s谋ras谋nda kendi sesinizi net bir 艧ekilde duyun</string>
+    <string name="prefs_active_noise_cancelling_level">Etkin G眉r眉lt眉 Engelleme Seviyesi</string>
+    <string name="prefs_active_noise_cancelling_level_high">Y眉ksek</string>
+    <string name="prefs_active_noise_cancelling_level_low">D眉艧眉k</string>
+    <string name="prefs_switch_control_left">Denetimi sola de臒i艧tir</string>
+    <string name="prefs_switch_control_right">Denetimi sa臒a de臒i艧tir</string>
+    <string name="pref_title_touch_voice_assistant">Sesli Yard谋mc谋</string>
+    <string name="pref_title_touch_anc">Etkin G眉r眉lt眉 Engelleme</string>
+    <string name="pref_title_touch_quick_ambient">H谋zl谋 Ortam Sesi</string>
+    <string name="pref_title_touch_volume">Ses Seviyesi</string>
+    <string name="pref_title_touch_ambient">Ortam Sesi</string>
+    <string name="pref_title_touch_spotify">Spotify</string>
+    <string name="pref_switch_noise_control">G眉r眉lt眉 Denetimini De臒i艧tir</string>
+    <string name="prefs_noise_control_with_one_earbud_summary">Yaln谋zca bir kulakl谋k kullan谋rken g眉r眉lt眉 denetimine izin ver</string>
+    <string name="pref_ambient_sound_tone_summary">Yumu艧aktan Temize</string>
+    <string name="pref_balance">Denge</string>
+    <string name="prefs_voice_detect_summary">Ses alg谋land谋ktan sonra ortam sesini etkinle艧tir ve oynatmay谋 otomatik olarak azalt</string>
+    <string name="prefs_double_tap_edge">脟ift dokunma kenar谋</string>
+    <string name="prefs_double_tap_edge_summary">Dokunmatik y眉zeye dokunulmad谋臒谋nda bile 莽ift t谋klamay谋 alg谋la</string>
+    <string name="prefs_voice_detect_duration">艦u kadar sessiz kald谋ktan sonra sonland谋r:</string>
+    <string name="pref_voice_detect_duration_10">10 saniye</string>
+    <string name="pref_voice_detect_duration_15">15 saniye</string>
+    <string name="heartrate_bpm_110">110 kalp at谋艧谋/dakika</string>
+    <string name="heartrate_bpm_112">112 kalp at谋艧谋/dakika</string>
+    <string name="heartrate_bpm_125">125 kalp at谋艧谋/dakika</string>
+    <string name="prefs_heartrate_alert_threshold">Kalp ritmi uyar谋 e艧i臒i</string>
+    <string name="mi2_prefs_heart_rate_monitoring">Kalp Ritmi 陌zleme</string>
+    <string name="devicetype_galaxybuds_pro">Galaxy Buds Pro</string>
+    <string name="prefs_customize_ambient_sound_summary">Ortam Sesini 脰zelle艧tir</string>
+    <string name="prefs_ambient_settings_title">Ortam Sesi Se莽enekleri</string>
+    <string name="mi2_prefs_heart_rate_monitoring_summary">Kalp ritmi izlemeyi yap谋land谋r谋n</string>
+    <string name="mi2_prefs_heart_rate_monitoring_alerts_summary">Kalp ritmi izlemeyi ve uyar谋 e艧iklerini yap谋land谋r谋n</string>
+    <string name="prefs_seamless_connection_switch_summary">Kulakl谋klar谋 e艧le艧tirilen ayg谋tlar aras谋nda otomatik olarak de臒i艧tirir</string>
+    <string name="prefs_ambient_volume_right">Ortam Ses Seviyesi Sa臒</string>
+    <string name="pref_title_banglejs_text_bitmap">Bit E艧lem Olarak Metin</string>
+    <string name="pref_summary_device_intents">Bangle.js saat uygulamalar谋n谋n Android Ama莽lar谋 g枚ndermesine ve Android\'deki di臒er uygulamalar谋n (Tasker gibi) com.banglejs.uart.tx Amac谋 ile Bangle.js\'ye veri g枚ndermesine izin ver. Arka planda 莽al谋艧mak i莽in di臒er uygulamalar 眉zerinde g枚r眉nt眉leme izni gerekir.</string>
+    <string name="pref_title_device_intents">Ama莽lara 陌zin Ver</string>
+    <string name="pref_summary_device_internet_access">Bu ayg谋ttaki uygulamalar谋n internete eri艧mesine izin ver</string>
+    <string name="pref_summary_banglejs_text_bitmap">Bir s枚zc眉k saatin yaz谋 tipi kullan谋larak g枚r眉nt眉lenemiyorsa, onu Gadgetbridge\'de bir bit e艧leme d枚n眉艧t眉r ve bit e艧lemi saatte g枚r眉nt眉le</string>
+    <string name="pref_title_device_internet_access">陌nternet Eri艧imine 陌zin Ver</string>
+    <string name="pref_workout_start_on_phone_title">Fitness uygulamas谋 izlemesi</string>
+    <string name="pref_workout_start_on_phone_summary">Bileklikte bir GPS egzersizi ba艧lat谋ld谋臒谋nda telefonda fitness uygulamas谋 izlemeyi ba艧lat/durdur</string>
+    <string name="pref_workout_send_gps_summary">Egzersiz s谋ras谋nda ge莽erli GPS konumunu bilekli臒e g枚nder</string>
+    <string name="notification_channel_gps">GPS izleme</string>
+    <string name="notification_gps_title">Gadgetbridge GPS</string>
+    <string name="notification_gps_text">GPS konumu %1$d ayg谋ta g枚nderiliyor</string>
+    <string name="pref_device_action_phone_gps_location_listener_stop">GPS Konumu Dinleyiciyi Durdur</string>
+    <string name="pref_workout_send_gps_title">Egzersiz s谋ras谋nda GPS g枚nder</string>
+    <string name="permission_notification_listener">%1$s, telefonunuzun ekran谋 kapal谋yken saatinizde g枚r眉nt眉lemek i莽in Bildirimlere eri艧meye ihtiya莽 duyuyor.
+\n
+\nL眉tfen 枚nce \'%2$s\', sonra \'%1$s\' d眉臒mesine dokunun ve \'Bildirim Eri艧imine 陌zin Ver\' se莽ene臒ini etkinle艧tirin, ard谋ndan %1$s\'e d枚nmek i莽in \'Geri\' d眉臒mesine dokunun.</string>
+    <string name="permission_notification_policy_access">%1$s, telefonunuzun ekran谋 kapal谋yken saatinizde bu ayarlar谋 yerine getirebilmek i莽in Rahats谋z Etme ayarlar谋na eri艧meye ihtiya莽 duyuyor.
+\n
+\nL眉tfen 枚nce \'%2$s\', sonra \'%1$s\' d眉臒mesine dokunun ve \'Rahats谋z Etmeye 陌zin Ver\' se莽ene臒ini etkinle艧tirin, ard谋ndan %1$s\'e d枚nmek i莽in \'Geri\' d眉臒mesine dokunun.</string>
+    <string name="discovery_scanning_intensity">Tarama yo臒unlu臒u</string>
+    <string name="discovery_scanning_intensity_warning">Donma veya yan谋t alamama ya艧谋yorsan谋z, tarama yo臒unlu臒unu daha d眉艧眉k bir seviyeye ayarlamay谋 deneyin. Ayg谋t谋n谋z bulunam谋yorsa, tarama yo臒unlu臒unu daha y眉ksek bir seviyeye ayarlamay谋 deneyin.</string>
+    <string name="quick_alarm">H谋zl谋 alarm</string>
+    <string name="quick_alarm_description">Widget alarm谋</string>
+    <string name="portuguese_pt">Portekizce (Portekiz)</string>
+    <string name="portuguese_br">Portekizce (Brezilya)</string>
+    <string name="pref_title_banglejs_webview_url">Uygulama y眉kleyici URL\'si</string>
+    <string name="pref_summary_banglejs_webview_url">脰zel bir uygulama y眉kleyicisi istiyorsan谋z https://鈥�/android.html URL\'nizi buraya koyun. Aksi takdirde https://banglejs.com/apps i莽in bo艧 b谋rak谋n</string>
+    <string name="permission_location">%1$s ekran谋n谋z kapal谋yken bile saatinize ba臒l谋 kalmas谋n谋 sa臒lamak i莽in arka planda konumunuza eri艧meye ihtiya莽 duyuyor. \n \nKabul etmek i莽in l眉tfen \'%2$s\' d眉臒mesine dokunun.</string>
+    <string name="info_no_devices_connected">Ba臒l谋 ayg谋t yok</string>
+    <string name="info_connected_count">%d ayg谋t ba臒l谋</string>
+    <string name="estonian">Estonca</string>
+    <string name="lithuanian">Litvanyaca</string>
+    <string name="persian">Fars莽a</string>
+    <string name="ukranian">Ukraynaca</string>
+    <string name="scandinavian">陌skandinavca</string>
+    <string name="bengali">Bengalce</string>
+    <string name="extended_ascii">Geni艧letilmi艧 ASCII</string>
+    <string name="czech">脟ek莽e</string>
+    <string name="icelandic">陌zlandaca</string>
+    <string name="pref_explanation_authkey">Baz谋 ayg谋tlar, ayg谋t谋n ilk kez ba艧lat谋lmas谋 i莽in 枚zel bir e艧le艧tirme anahtar谋 gerektirir. Wiki sayfas谋nda daha fazla ayr谋nt谋 i莽in buraya dokunun.</string>
+    <string name="pref_explanation_authkey_new_protocol">Bileklikte \"Uygulamay谋 en son s眉r眉me g眉ncelleyin\" mesaj谋 al谋rsan谋z, yukar谋daki \"Yeni Kimlik Do臒rulama Protokol眉\" se莽ene臒ini i艧aretledi臒inizden emin olun. Wiki sayfas谋nda daha fazla ayr谋nt谋 i莽in buraya dokunun.</string>
+    <string name="prefs_password">Parola</string>
+    <string name="prefs_password_summary">Bilekten 莽谋kar谋ld谋臒谋nda bilekli臒i bir parolayla kilitleyin</string>
+    <string name="prefs_password_4_digits_1_to_4_summary">Parola 1\'den 4\'e kadar rakamlardan olu艧mal谋 ve 4 basamakl谋 olmal谋d谋r</string>
+    <string name="prefs_password_6_digits_0_to_9_summary">Parola 6 basamakl谋 olmal谋 ve yaln谋zca rakamlar kullan谋lmal谋d谋r</string>
+    <string name="controlcenter_add_new_folder">Yeni klas枚r ekle</string>
+    <string name="controlcenter_set_parent_folder">脺st klas枚r眉 ayarla</string>
+    <string name="controlcenter_set_preferences">Tercihleri ayarla</string>
+    <string name="controlcenter_connected_fraction">Ba臒land谋: %d/%d</string>
+    <string name="controlcenter_toggle_details">Ayr谋nt谋lar谋 a莽/kapat</string>
+    <string name="error_setting_parent_folder">脺st klas枚r ayarlan谋rken hata olu艧tu: %s</string>
+    <string name="error_deleting_device">Ayg谋t silinirken hata olu艧tu: %s</string>
+    <string name="controlcenter_folder_name">Klas枚r ad谋:</string>
+    <string name="controlcenter_unset_folder">Klas枚r眉n ayar谋n谋 kald谋r</string>
+    <string name="controlcenter_set_folder_title">Yeni klas枚r ayarla veya olu艧tur</string>
+    <string name="pref_title_notification_delay_calls">脟a臒r谋 bildirim gecikmesi</string>
+    <string name="pref_summary_notification_delay_calls">Gelen 莽a臒r谋 bildirimlerini ayg谋ta g枚ndermeden 枚nceki saniye cinsinden gecikme.</string>
+    <string name="pref_blacklist_calendars_summary">Kara listeye al谋nan takvimler ayg谋tla e艧zamanlanmayacak</string>
+    <string name="prefs_password_enabled">Parola Etkin</string>
+    <string name="auto_reconnect_ble_title">Ayg谋ta otomatik yeniden ba臒lan</string>
+    <string name="auto_reconnect_ble_summary">Temkinli olarak d眉zenli aral谋klarla ayg谋ta ba臒lanmaya 莽al谋艧</string>
+    <string name="connection_over_ble">BLE 眉zerinden ba臒lant谋</string>
+    <string name="connection_over_bt_classic">Klasik Bluetooth 眉zerinden ba臒lant谋</string>
+    <string name="autoconnect_from_device_title">Ayg谋ttan ba臒lant谋da ba臒lan</string>
+    <string name="autoconnect_from_device_summary">Kulakl谋k gibi bir ayg谋t taraf谋ndan ba臒lant谋 ba艧lat谋ld谋臒谋nda ba臒lant谋 kur</string>
+    <string name="watchface_dialog_widget_timezone_duration">Saat g枚r眉n眉rl眉k s眉resi (saniye cinsinden)</string>
+    <string name="open_fw_installer_warning_title">Uyar谋</string>
+    <string name="open_fw_installer_pick_file">Dosya se莽</string>
+    <string name="open_fw_installer_connect_maximum_one_device">L眉tfen dosyay谋 g枚ndermek istedi臒iniz YALNIZCA B陌R ayg谋t谋 ba臒lay谋n.</string>
+    <string name="open_fw_installer_select_file">Ayg谋ta y眉klemek istedi臒iniz bir dosya se莽in: %s</string>
+    <string name="open_fw_installer_info_text">脺r眉n Yaz谋l谋m谋/Saat Aray眉z眉/Uygulama/Dosya Kurucusu, desteklenen dosyalar谋 (眉r眉n yaz谋l谋m谋, saat aray眉zleri, uygulamalar, GPS, kaynaklar, yaz谋 tipleri...) ayg谋ta y眉klemenizi/kurman谋z谋 sa臒lar. Wiki sayfas谋nda daha fazla bilgi edinin: https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Firmware-Update</string>
+    <string name="open_fw_installer_warning_text">Bu 枚zelli臒in ayg谋t谋n谋z谋 kullan谋lamaz duruma getirme olas谋l谋臒谋 vard谋r. Bununla birlikte, kurulum s谋ras谋nda hi莽bir geli艧tiricinin ba艧谋na b枚yle bir 艧ey gelmemi艧tir, ancak bunu kendi sorumlulu臒unuzda yapt谋臒谋n谋z谋 unutmay谋n.</string>
+    <string name="open_fw_installer_getting_files_title">脺r眉n yaz谋l谋m谋/uygulama dosyas谋n谋 edinme</string>
+    <string name="open_fw_installer_info_text_title">Dosya Kurucusu</string>
+    <string name="open_fw_installer_connect_minimum_one_device">L眉tfen dosyay谋 g枚ndermek istedi臒iniz EN AZ B陌R ayg谋t谋 ba臒lay谋n.</string>
+    <string name="open_fw_installer_ensure_device_connected">%s ayg谋t谋n谋n ba臒l谋 oldu臒undan emin olun</string>
+    <string name="open_fw_installer_getting_files_text">脺r眉n yaz谋l谋m谋 dosyalar谋n谋 da臒谋tamayaca臒谋m谋z i莽in, dosyalar谋 kendiniz edinmeniz gerekecektir. Bu, dosyalar谋 apk dosyalar谋nda, 莽evrim i莽i olarak, forumlarda, Amazfitwatchfaces\'de (Miband/Amazfit ayg谋tlar谋 i莽in) vb. araman谋z gerekti臒i anlam谋na gelir.</string>
+    <string name="appmanager_item_outdated">(g眉ncel de臒il)</string>
+    <string name="appmanager_app_share">Payla艧</string>
+    <string name="watchface_dialog_widget_cat_custom_widget">脰zel widget</string>
+    <string name="watchface_dialog_widget_cat_generic">Genel</string>
+    <string name="watchface_dialog_title_widget">Widget ayarlar谋</string>
+    <string name="pref_title_banglejs_txt_bitmap_size">Metin Bit E艧lemleri Boyutu</string>
+    <string name="hybridhr_widget_bg_thin_circle">陌nce 莽ember</string>
+    <string name="pref_summary_banglejs_txt_bitmap_size">Bit e艧lem metin olu艧turma i莽in kullan谋lacak boyut</string>
+    <string name="watchface_dialog_widget_cat_2nd_tz_widget">2. saat dilimi widget\'谋</string>
+    <string name="watchface_dialog_widget_cat_position">Konum</string>
+    <string name="watchface_dialog_widget_background">Arka plan</string>
+    <string name="hybridhr_widget_bg_double_circle">脟ift 莽ember</string>
+    <string name="hybridhr_widget_bg_dashed_circle">Kesikli 莽ember</string>
+    <string name="watchface_setting_title_button_toggle_widgets">Widget\'lar谋 de臒i艧tir</string>
+    <string name="watchface_setting_button_toggle_widgets">Widget\'lar谋 a莽/kapat</string>
+    <string name="steps_streaks_total_steps">Toplam
+\nad谋m</string>
+    <string name="steps_streaks">Ad谋m serisi</string>
+    <string name="steps_streaks_total_steps_hint_totals">艦imdiye kadar kaydedilen toplam ad谋m say谋s谋</string>
+    <string name="steps_streaks_hint">Ad谋m hedefine ula艧谋lan kesintisiz ard谋艧谋k g眉nler serisi</string>
+    <string name="step_streak_ongoing">Devam eden</string>
+    <string name="step_streak_total">Toplam</string>
+    <string name="step_streak_longest">En uzun</string>
+    <string name="steps_streaks_streak_days">Seri
+\nG眉nler</string>
+    <string name="step_streak_average_steps_hint">Serinin g眉nl眉k ortalama ad谋m say谋s谋</string>
+    <string name="steps_streaks_average_steps">Ortalama
+\nad谋m</string>
+    <string name="steps_streaks_achievement_rate">Ba艧ar谋
+\noran谋</string>
+    <string name="steps_streaks_total_days_hint_totals">Ad谋m at谋lan t眉m g眉nlere g枚re hedefe ula艧谋lan g眉nlerin y眉zdesi</string>
+    <string name="steps_streaks_since_date">%s\'den beri</string>
+    <string name="step_streak_days_hint">Ad谋m hedefine ula艧谋lan ard谋艧谋k g眉n say谋s谋</string>
+    <string name="steps_streaks_total_steps_hint">T眉m serideki toplam ad谋m say谋s谋</string>
+    <string name="steps_streaks_total_steps_average_hint">G眉nl眉k toplam ortalama %d ad谋m</string>
+    <string name="share_log_not_enabled_message">脰nce Ayarlar - G眉nl眉k dosyalar谋n谋 yaz b枚l眉m眉nde g眉nl眉k kayd谋n谋 etkinle艧tirmelisiniz</string>
+    <string name="note">Not</string>
+    <string name="watchface_setting_title_custom_events">脰zel etkinlikler</string>
+    <string name="watchface_setting_button_toggle_backlight">Arka 谋艧谋臒谋 a莽</string>
+    <string name="controlcenter_get_heartrate_measurement">Kalp ritmi 枚l莽眉m眉n眉 al</string>
+    <string name="pref_write_logfiles_not_available">Dosya g眉nl眉臒眉 ba艧lat谋lamad谋, g眉nl眉k dosyalar谋n谋n yaz谋lmas谋 艧u anda kullan谋lam谋yor. G眉nl眉k dosyalar谋n谋 tekrar ba艧latmay谋 denemek i莽in uygulamay谋 yeniden ba艧lat谋n.</string>
+    <string name="devicetype_binary_sensor">陌kili sens枚r</string>
+    <string name="application_name_banglejs_nightly">Bangle.js Gadgetbridge (Gecelik)</string>
+    <string name="title_activity_controlcenter_banglejs_nightly">Bangle.js Gadgetbridge (Gecelik)</string>
+    <string name="about_activity_title_banglejs_nightly">Bangle.js Gadgetbridge (Gecelik) Hakk谋nda</string>
+    <string name="gadgetbridge_running_banglejs_nightly">Gecelik Bangle.js 莽al谋艧谋yor</string>
+    <string name="about_description_banglejs_nightly">Bangle.js i莽in Gadgetbridge projesinin 眉zerine in艧a edilmi艧, internet eri艧imi eklenmi艧 Android yard谋mc谋 uygulamas谋.
+\n
+\nGoogle Play Store politikalar谋 nedeniyle, uygulaman谋n kendisinde bir ba臒谋艧 ba臒lant谋s谋na izin verilmiyor, ancak bu uygulamay谋 be臒endiyseniz l眉tfen a艧a臒谋daki Gadgetbridge ana sayfas谋 眉zerinden ba臒谋艧 yapmay谋 d眉艧眉n眉n.</string>
+    <string name="prefs_hourly_chime">Saatlik zil</string>
+    <string name="prefs_hourly_chime_summary">Saat, saatte bir kez bip sesi 莽谋karacak</string>
+    <string name="step_streaks_achievements_sharing_message">G眉nl眉k ad谋m ba艧ar谋lar谋m!</string>
+    <string name="step_streaks_achievements_sharing_title">Ad谋m Ba艧ar谋lar谋</string>
+    <string name="permission_display_over_other_apps">%1$s, %1$s arka plandayken Bangle.js saatlerinin ama莽lar arac谋l谋臒谋yla etkinlikleri ba艧latmas谋na izin vermek i莽in di臒er uygulamalar 眉zerinde g枚r眉nt眉leme iznine ihtiya莽 duyuyor.
+\n
+\nBu, bir m眉zik uygulamas谋n谋 ba艧latmak ve bir 艧ark谋 莽almak ve di臒er bir莽ok 艧ey i莽in kullan谋labilir.
+\n
+\nL眉tfen 枚nce \'%2$s\', sonra \'%1$s\' d眉臒mesine dokunun ve \'Di臒er uygulamalar 眉zerinden g枚r眉nt眉lemeye izin ver\' se莽ene臒ini etkinle艧tirin, ard谋ndan %1$s\'e d枚nmek i莽in \'Geri\' d眉臒mesine dokunun.
+\n
+\n%1$s i莽in izin istenmesini durdurmak i莽in \'Ayarlar\'a gidin ve \'陌zin durumunu denetle\' se莽ene臒inin i艧aretini kald谋r谋n.
+\n
+\n%1$s\'e bekledi臒iniz gibi 莽al谋艧mas谋 i莽in gereken izinleri verdi臒inizden emin olun.</string>
+    <string name="pref_header_calendar">Takvim</string>
+    <string name="pref_header_health">Sa臒l谋k</string>
+    <string name="pref_header_workout">Egzersiz</string>
+    <string name="pref_title_screen_on_on_notifications">Bildirimlerde ekran谋 a莽</string>
+    <string name="pref_summary_screen_on_on_notifications">Bir bildirim geldi臒inde bilekli臒in ekran谋n谋 a莽</string>
+    <string name="pref_cache_weather">Hava durumu bilgilerini 枚nbelle臒e al</string>
+    <string name="heartrate_bpm_40">40 kalp at谋艧谋/dakika</string>
+    <string name="heartrate_bpm_45">45 kalp at谋艧谋/dakika</string>
+    <string name="heartrate_bpm_50">50 kalp at谋艧谋/dakika</string>
+    <string name="heartrate_bpm_115">115 kalp at谋艧谋/dakika</string>
+    <string name="spo2_perc_80">%80</string>
+    <string name="spo2_perc_85">%85</string>
+    <string name="spo2_perc_90">%90</string>
+    <string name="prefs_relaxation_reminder_title">Rahatlama hat谋rlat谋c谋s谋</string>
+    <string name="seconds_6">6 saniye</string>
+    <string name="pref_sleep_breathing_quality_monitoring">Uykuda solunum kalitesi izleme</string>
+    <string name="prefs_always_on_display_summary">Bilekli臒in ekran谋n谋 her zaman a莽谋k tut</string>
+    <string name="smart">Ak谋ll谋</string>
+    <string name="mi2_dnd_always">Her zaman</string>
+    <string name="pref_header_heartrate_sleep">Uyku</string>
+    <string name="pref_header_heartrate_allday">T眉m g眉n izleme</string>
+    <string name="pref_header_heartrate_alerts">Kalp ritmi uyar谋lar谋</string>
+    <string name="pref_header_stress">Stres</string>
+    <string name="pref_header_spo2">Kan Oksijeni</string>
+    <string name="pref_header_time">Zaman</string>
+    <string name="pref_summary_canned_replies">脰nceden ayarlanan mesajlar谋 kullanarak saatten yan谋tlay谋n</string>
+    <string name="fw_upgrade_notice_miband7">Xiaomi Smart Band 7 ayg谋t谋n谋za %s 眉r眉n yaz谋l谋m谋n谋 kurmak 眉zeresiniz.
+\n
+\nBilekli臒iniz .zip dosyas谋n谋 kurduktan sonra yeniden ba艧lat谋lacak.
+\n
+\n陌LERLEMEN陌Z DURUMUNDA R陌SK S陌ZE A陌TT陌R!</string>
+    <string name="pref_header_connection">Ba臒lant谋</string>
+    <string name="pref_cache_weather_summary">Hava durumu bilgileri, uygulama yeniden ba艧latmalar谋nda 枚nbelle臒e al谋nacakt谋r.</string>
+    <string name="prefs_heartrate_alert_low_threshold">D眉艧眉k kalp ritmi uyar谋 e艧i臒i</string>
+    <string name="prefs_spo2_alert_threshold">SPO2 uyar谋 e艧i臒i</string>
+    <string name="menuitem_workout_status">Egzersiz Durumu</string>
+    <string name="pref_header_display">Ekran</string>
+    <string name="prefs_heartrate_alert_high_threshold">Y眉ksek kalp ritmi uyar谋 e艧i臒i</string>
+    <string name="menuitem_workout_history">Egzersiz Ge莽mi艧i</string>
+    <string name="spo2_off">Kapal谋</string>
+    <string name="seconds_15">15 saniye</string>
+    <string name="prefs_spo2_monitoring_title">Kan Oksijen 陌zleme</string>
+    <string name="prefs_spo2_monitoring_description">G眉n boyunca kandaki oksijen seviyesini otomatik olarak izle</string>
+    <string name="prefs_relaxation_reminder_description">Stres de臒eri 80\'den y眉ksekse bilgilendirmek i莽in band谋 titret</string>
+    <string name="seconds_7">7 saniye</string>
+    <string name="seconds_8">8 saniye</string>
+    <string name="seconds_9">9 saniye</string>
+    <string name="seconds_11">11 saniye</string>
+    <string name="seconds_12">12 saniye</string>
+    <string name="seconds_13">13 saniye</string>
+    <string name="seconds_14">14 saniye</string>
+    <string name="prefs_screen_timeout">Ekran Zaman A艧谋m谋</string>
+    <string name="prefs_always_on_display">Ekran Her Zaman A莽谋k</string>
+    <string name="pref_screen_brightness">Ekran Parlakl谋臒谋</string>
+    <string name="devicetype_miband7">Xiaomi Smart Band 7</string>
+    <string name="menuitem_countdown">Geri Say谋m</string>
+    <string name="menuitem_female_health">Kad谋n Sa臒l谋臒谋</string>
+    <string name="menuitem_personal_activity_intelligence">Ki艧isel Etkinlik Bilgileri</string>
+    <string name="pref_summary_notifications_ignore_low_priority">D眉艧眉k ve asgari 枚ncelikli bildirimleri saate g枚nderme</string>
+    <string name="pref_title_notifications_ignore_low_priority">D眉艧眉k 枚ncelikli bildirimleri yok say</string>
+    <string name="devicetype_flipper_zero">Flipper zero</string>
+    <string name="activity_prefs_allow_bluetooth_intent_api">Bluetooth Ama莽 API\'si</string>
+    <string name="activity_prefs_summary_allow_bluetooth_intent_api">Ama莽 API\'si arac谋l谋臒谋yla Bluetooth ba臒lant谋s谋n谋 denetlemeye izin verin</string>
+    <string name="fw_upgrade_notice_amazfit_gts3">Amazfit GTS 3 ayg谋t谋n谋za %s 眉r眉n yaz谋l谋m谋n谋 kurmak 眉zeresiniz. 
+\n 
+\nBilekli臒iniz .zip dosyas谋n谋 kurduktan sonra yeniden ba艧lat谋lacak. 
+\n 
+\n陌LERLEMEN陌Z DURUMUNDA R陌SK S陌ZE A陌TT陌R!</string>
+    <string name="devicetype_amazfit_gts3">Amazfit GTS 3</string>
+    <string name="pref_title_notification_prefer_long_text">Uzun bildirim metnini tercih et</string>
+    <string name="pref_summary_notification_prefer_long_text">Kullan谋labilirse, ayg谋ta uzun bildirim metni g枚nder</string>
+    <string name="menuitem_unknown_app">Bilinmeyen (%s)</string>
+    <string name="kind_app">Uygulama</string>
+    <string name="pref_title_overwrite_settings_on_connection">Ba臒lant谋da ayarlar谋n 眉zerine yaz</string>
+    <string name="pref_summary_overwrite_settings_on_connection">Bilekli臒e ba臒lan谋rken, onun t眉m ayarlar谋n谋n 眉zerine yaz.</string>
+    <string name="dismiss">Kapat</string>
+    <string name="devicetype_amazfit_gtr3">Amazfit GTR 3</string>
+    <string name="fw_upgrade_notice_amazfit_gtr3">Amazfit GTR 3 ayg谋t谋n谋za %s 眉r眉n yaz谋l谋m谋n谋 kurmak 眉zeresiniz.
+\n
+\nBilekli臒iniz .zip dosyas谋n谋 kurduktan sonra yeniden ba艧lat谋lacak.
+\n
+\n陌LERLEMEN陌Z DURUMUNDA R陌SK S陌ZE A陌TT陌R!</string>
+    <string name="pref_title_third_party_app_device_settings">3. taraf uygulamalar谋n ayarlar谋 de臒i艧tirmesine izin ver</string>
+    <string name="pref_summary_third_party_app_device_settings">Kurulu di臒er 3. taraf uygulamalar谋n ayg谋t ayarlar谋n谋 niyetler arac谋l谋臒谋yla de臒i艧tirmesine izin verin.</string>
+    <string name="activity_type_dance">Dans</string>
+    <string name="activity_type_indoor_fitness">陌莽 Mekan Fitness</string>
+    <string name="activity_type_gymnastics">Jimnastik</string>
+    <string name="activity_type_hiit">Y眉ksek Yo臒unluklu Aral谋kl谋 Antrenman</string>
+    <string name="activity_type_core_training">脟ekirdek Antrenman谋</string>
+    <string name="activity_type_stretching">Germe</string>
+    <string name="activity_type_stepper">Ad谋m</string>
+    <string name="activity_type_pilates">Pilates</string>
+    <string name="activity_type_volleyball">Voleybol</string>
+    <string name="activity_type_table_tennis">Masa Tenisi</string>
+    <string name="activity_type_kickboxing">Kickboks</string>
+    <string name="activity_type_bowling">Bovling</string>
+    <string name="activity_type_boxing">Boks</string>
+    <string name="activity_type_street_dance">Sokak Dans谋</string>
+    <string name="activity_type_indoor_ice_skating">Kapal谋 Alan Buz Pateni</string>
+    <string name="activity_type_zumba">Zumba</string>
+    <string name="devicetype_super_cars">Shell Yar谋艧谋</string>
+    <string name="devicetype_amazfit_gtr_lite">Amazfit GTR Lite</string>
+    <string name="watchface_setting_button_move_hands">陌breleri hareket ettir</string>
+    <string name="supercars_turbo_speed_label">Turbo H谋z</string>
+    <string name="supercars_lights_label">I艧谋klar</string>
+    <string name="abstract_chart_fragment_kind_rem_sleep">REM uykusu</string>
+    <string name="sports_activity_confirm_delete_title">%d etkinli臒i sil</string>
+    <string name="pref_header_authentication">Kimlik do臒rulama</string>
+    <string name="sports_activity_confirm_delete_description">%d etkinli臒i silmek istedi臒inizden emin misiniz\?</string>
+    <string name="supercars_lights_blinking_label">Yan谋p S枚nme</string>
+    <string name="hrZoneWarmUp">Is谋nma</string>
+    <string name="TrainingEffect">Antrenman Etkisi</string>
+    <string name="aerobicTrainingEffect">Aerobik Etkisi</string>
+    <string name="anaerobicTrainingEffect">Anaerobik Etkisi</string>
+    <string name="currentWorkoutLoad">Egzersiz Y眉k眉</string>
+    <string name="maximumOxygenUptake">Azami Oksijen Al谋m谋</string>
+    <string name="HeartRateZones">Kalp Ritmi B枚lgeleri</string>
+    <string name="hrZoneNa">YOK</string>
+    <string name="hrZoneAerobic">Aerobik</string>
+    <string name="hrZoneFatBurn">Ya臒 Yak谋m谋</string>
+    <string name="hrZoneAnaerobic">Anaerobik</string>
+    <string name="hrZoneExtreme">A艧谋r谋</string>
+    <string name="elevationGain">Y眉kseklik kazanc谋</string>
+    <string name="elevationLoss">Y眉kseklik kayb谋</string>
+    <string name="pref_chart_sleep_lines_limit">Kayd谋rmadan 枚nce ka莽 uyku oturumu sat谋r谋n谋n g枚sterilece臒i</string>
+    <string name="devicetype_sony_wh_1000xm2">Sony WH-1000XM2</string>
+    <string name="devicetype_sony_wf_1000xm4">Sony WF-1000XM4</string>
+    <string name="low_power_gps">D眉艧眉k G眉莽l眉 GPS</string>
+    <string name="gps">GPS</string>
+    <string name="gps_gnolass">GPS + GLONASS</string>
+    <string name="gps_galileo">GPS + GALILEO</string>
+    <string name="all_satellites">T眉m Uydular</string>
+    <string name="single_band">Tek Bant</string>
+    <string name="dual_band">脟ift Bant</string>
+    <string name="pref_header_sound_vibration">Ses ve Titre艧im</string>
+    <string name="pref_header_offline_voice">脟evrim D谋艧谋 Ses</string>
+    <string name="pref_gps_header">GPS</string>
+    <string name="pref_gps_mode_preset">GPS Modu</string>
+    <string name="pref_gps_band">GPS Band谋</string>
+    <string name="pref_gps_combination">GPS Birle艧imi</string>
+    <string name="pref_gps_satellite_search">Uydu Arama</string>
+    <string name="pref_crown_vibration">Ta莽 Titre艧imi</string>
+    <string name="pref_alert_tone">Uyar谋 Sesi</string>
+    <string name="pref_cover_to_mute">Sessize Almak i莽in Kapat</string>
+    <string name="pref_vibrate_for_alert">Uyar谋 i莽in Titret</string>
+    <string name="pref_text_to_speech">Metin Okuma</string>
+    <string name="offline_voice_respond_turn_wrist">Bile臒i 莽evirdi臒inde yan谋t ver</string>
+    <string name="offline_voice_respond_screen_on">Ekran a莽谋ld谋臒谋nda yan谋t ver</string>
+    <string name="pref_agps_header">AGPS</string>
+    <string name="pref_agps_expiry_reminder_enabled">AGPS Sona Erme Hat谋rlat谋c谋s谋</string>
+    <string name="pref_agps_expiry_reminder_time">AGPS Sona Erme Hat谋rlatma S眉resi</string>
+    <string name="pref_workout_detection_title">Egzersiz Alg谋lama</string>
+    <string name="pref_workout_detection_summary">Egzersizi otomatik olarak alg谋la</string>
+    <string name="pref_workout_detection_categories_title">Egzersiz Kategorileri</string>
+    <string name="pref_workout_detection_alert_title">Uyar谋</string>
+    <string name="pref_workout_detection_sensitivity">Hassasiyet</string>
+    <string name="pref_sleep_mode_title">Uyku Modu</string>
+    <string name="pref_sleep_mode_sleep_screen_title">Uyku Ekran谋</string>
+    <string name="pref_sleep_mode_sleep_screen_summary">Dikkat da臒谋n谋kl谋臒谋n谋 azaltmak i莽in uyku modunda ekran谋 uyand谋r谋rken uyku ekran谋n谋 g枚ster</string>
+    <string name="pref_sleep_mode_smart_enable_title">Ak谋ll谋 Etkinle艧tir</string>
+    <string name="pref_sleep_mode_smart_enable_summary">Uyku s谋ras谋nda bilekli臒i takarken uyku modunu otomatik olarak etkinle艧tir</string>
+    <string name="buttons_on_left">D眉臒meler solda</string>
+    <string name="buttons_on_right">D眉臒meler sa臒da</string>
+    <string name="prefs_weardirection">Giyme y枚n眉</string>
+    <string name="vibration_profile_default">脰ntan谋ml谋</string>
+    <string name="pref_screen_notification_profile_schedule">Zamanla</string>
+    <string name="seconds_25">25 saniye</string>
+    <string name="pref_enable_unsupported_settings_title">Desteklenmeyen ayarlar谋 etkinle艧tir</string>
+    <string name="prefs_always_on_display_style">G枚r眉n眉m</string>
+    <string name="prefs_control_center_summary">Denetim merkezi a莽谋l谋r men眉s眉ndeki 枚geleri se莽in</string>
+    <string name="activity_prefs_target_weight_kg">kg olarak hedef a臒谋rl谋k</string>
+    <string name="activity_prefs_goal_standing_time_minutes">G眉nl眉k hedef: dakika cinsinden ayakta durma s眉resi</string>
+    <string name="activity_prefs_goal_fat_burn_time_minutes">G眉nl眉k hedef: dakika cinsinden ya臒 yakma s眉resi</string>
+    <string name="activity_type_indoor_walking">Kapal谋 Alanda Y眉r眉y眉艧</string>
+    <string name="activity_type_pool_swimming">Havuzda Y眉zme</string>
+    <string name="menuitem_calendar">Takvim</string>
+    <string name="menuitem_todo">Yap谋lacaklar</string>
+    <string name="menuitem_voice_memos">Sesli Notlar</string>
+    <string name="menuitem_sun_moon">G眉ne艧 ve Ay</string>
+    <string name="menuitem_one_tap_measuring">Tek Dokunu艧la 脰l莽眉m</string>
+    <string name="menuitem_offline_voice">脟evrim D谋艧谋 Ses</string>
+    <string name="menuitem_screen_always_lit">Ekran Her Zaman I艧谋kl谋</string>
+    <string name="menuitem_brightness">Parlakl谋k</string>
+    <string name="menuitem_bluetooth">Bluetooth</string>
+    <string name="menuitem_wifi">Wi-Fi</string>
+    <string name="menuitem_lockscreen">Kilit Ekran谋</string>
+    <string name="menuitem_eject_water">Suyu 脟谋kar</string>
+    <string name="menuitem_unsupported">[DESTEKLENM陌YOR] %s</string>
+    <string name="pref_title_upper_button_long_press_action">脺st d眉臒meye uzun basma eylemi</string>
+    <string name="pref_title_lower_button_short_press_action">Alt d眉臒meye uzun basma eylemi</string>
+    <string name="sony_speak_to_chat_sensitivity_standard">Standart</string>
+    <string name="pref_screen_auto_brightness_title">Otomatik Parlakl谋k</string>
+    <string name="pref_screen_auto_brightness_summary">Ekran parlakl谋臒谋n谋 ortam 谋艧谋臒谋na g枚re ayarla</string>
+    <string name="balanced">Dengeli</string>
+    <string name="power_saving">G眉莽 Tasarrufu</string>
+    <string name="gps_bds">GPS + BDS</string>
+    <string name="fw_upgrade_notice_amazfit_gtr4">Amazfit GTR 4 ayg谋t谋n谋za %s 眉r眉n yaz谋l谋m谋n谋 kurmak 眉zeresiniz.
+\n
+\nBilekli臒iniz .zip dosyas谋n谋 kurduktan sonra yeniden ba艧lat谋lacak.
+\n
+\n陌LERLEMEN陌Z DURUMUNDA R陌SK S陌ZE A陌TT陌R!</string>
+    <string name="offline_voice_response_during_screen_lighting">Ekran ayd谋nlatmas谋 s谋ras谋nda yan谋t ver</string>
+    <string name="accuracy">Do臒ruluk</string>
+    <string name="custom">脰zel</string>
+    <string name="speed_first">脰nce H谋z</string>
+    <string name="accuracy_first">脰nce Do臒ruluk</string>
+    <string name="prefs_always_on_display_follow_watchface">G枚r眉n眉m, saat aray眉z眉yle ayn谋</string>
+    <string name="menuitem_volume">Ses Seviyesi</string>
+    <string name="pref_workout_detection_alert_summary">Egzersiz alg谋land谋臒谋nda bildir</string>
+    <string name="pref_workout_detection_categories_summary">Otomatik olarak alg谋lanacak egzersiz kategorileri</string>
+    <string name="menuitem_phone">Telefon</string>
+    <string name="pref_screen_notification_profile_todo_list">Yap谋lacaklar Listesi</string>
+    <string name="pref_enable_unsupported_settings_summary">Bu, ayg谋t taraf谋ndan desteklenmese bile kullan谋labilir t眉m ayarlara eri艧imi etkinle艧tirecektir. Bu, ayg谋tta karars谋zl谋臒a ve 莽枚kmelere neden olabilir.</string>
+    <string name="menuitem_membership_cards">脺yelik Kartlar谋</string>
+    <string name="prefs_control_center">Denetim Merkezi</string>
+    <string name="menuitem_theater_mode">Tiyatro Modu</string>
+    <string name="devicetype_amazfit_gtr4">Amazfit GTR 4</string>
+    <string name="pref_huami_truncate_fetch_operation_timestamps_summary">Getirme i艧lemi zaman damgalar谋n谋 dakikalara k谋salt谋n. 脟ok k谋sa egzersizleri getirirken sorunlarla kar艧谋la艧谋rsan谋z, zaman damgalar谋n谋 saniye cinsinden tutmak i莽in bu ayar谋 devre d谋艧谋 b谋rak谋n.</string>
+    <string name="pref_huami_truncate_fetch_operation_timestamps_title">Getirme i艧lemi zaman damgalar谋n谋 k谋salt</string>
+    <string name="pref_title_notification_media_ignores_application_list">Medya bildirimleri uygulama listesini yok sayar</string>
+    <string name="pref_summary_notification_media_ignores_application_list">Medya bildirimlerini uygulama listesinden 枚nce i艧le. Bu tercih i艧aretli de臒ilse, medya denetimlerinin ayg谋tta 莽al谋艧mas谋 i莽in uygulama listesinde medya uygulamalar谋na izin verilmesi gerekir.</string>
+    <string name="debugactivity_confirm_remove_device_preferences_title">Ayg谋t tercihleri kald谋r谋ls谋n m谋\?</string>
+    <string name="debugactivity_confirm_remove_device_preferences">Bu, ba臒l谋 t眉m ayg谋tlar i莽in ayg谋t tercihlerini s谋f谋rlayacak. Emin misiniz\?</string>
+    <string name="devicetype_amazfit_band7">Amazfit Band 7</string>
+    <string name="fw_upgrade_notice_amazfit_band7">Amazfit Band 7 ayg谋t谋n谋za %s 眉r眉n yaz谋l谋m谋n谋 kurmak 眉zeresiniz.
+\n
+\nBilekli臒iniz .zip dosyas谋n谋 kurduktan sonra yeniden ba艧lat谋lacak.
+\n
+\n陌LERLEMEN陌Z DURUMUNDA R陌SK S陌ZE A陌TT陌R!</string>
+    <string name="function_enabled">Etkin</string>
+    <string name="world_clock_code">Kod</string>
+    <string name="pref_title_touch_spotify_official_app">Spotify (yaln谋zca resmi uygulama)</string>
+    <string name="pref_title_touch_spotify_galaxy_app">Spotify (yaln谋zca Galaxy Wearable uygulamas谋)</string>
+    <string name="devicetype_galaxybuds_2">Galaxy Buds2</string>
+    <string name="prefs_touch_lock_buds2">Dokunma denetimleri</string>
+    <string name="fw_upgrade_notice_amazfit_gts4">Amazfit GTS 4 ayg谋t谋n谋za %s 眉r眉n yaz谋l谋m谋n谋 kurmak 眉zeresiniz.
+\n
+\nBilekli臒iniz .zip dosyas谋n谋 kurduktan sonra yeniden ba艧lat谋lacak.
+\n
+\n陌LERLEMEN陌Z DURUMUNDA R陌SK S陌ZE A陌TT陌R!</string>
+    <string name="devicetype_amazfit_gts4">Amazfit GTS 4</string>
+    <string name="fw_upgrade_notice_amazfit_gts4_mini">Amazfit GTS 4 Mini ayg谋t谋n谋za %s 眉r眉n yaz谋l谋m谋n谋 kurmak 眉zeresiniz.
+\n
+\nBilekli臒iniz .zip dosyas谋n谋 kurduktan sonra yeniden ba艧lat谋lacak.
+\n
+\n陌LERLEMEN陌Z DURUMUNDA R陌SK S陌ZE A陌TT陌R!</string>
+    <string name="devicetype_amazfit_gts4_mini">Amazfit GTS 4 Mini</string>
+    <string name="devicetype_sony_linkbuds_s">Sony LinkBuds S</string>
+    <string name="pref_header_intent_api">Ama莽 API\'si</string>
+    <string name="intent_api_allow_activity_sync_title">Etkinlik e艧zamanlama tetikleyicisine izin ver</string>
+    <string name="intent_api_allow_activity_sync_summary">Ama莽 API\'si arac谋l谋臒谋yla etkinlik e艧zamanlamas谋n谋n tetiklenmesine izin verin</string>
+    <string name="intent_api_allow_trigger_export_title">Veri taban谋 d谋艧a aktarmaya izin ver</string>
+    <string name="intent_api_allow_trigger_export_summary">Ama莽 API\'si arac谋l谋臒谋yla veri taban谋 d谋艧a aktarman谋n tetiklenmesine izin verin</string>
+    <string name="intent_api_broadcast_export_title">Veri taban谋 d谋艧a aktarmada yay谋n g枚nder</string>
+    <string name="intent_api_broadcast_export_summary">Veri taban谋 d谋艧a aktarma i艧lemi tamamland谋臒谋nda bir ama莽 yay谋nlay谋n</string>
+    <string name="sony_ambient_sound_control_button_mode_nc_off">G眉r眉lt眉 Engelleme, Kapal谋</string>
+    <string name="sony_ambient_sound_control_button_mode_as_off">Ortam Sesi, Kapal谋</string>
+    <string name="sony_quick_access_double_tap">H谋zl谋 Eri艧im (脟ift Dokunu艧)</string>
+    <string name="sony_ambient_sound_control_button_modes">Ortam Sesi Denetim D眉臒mesi Modlar谋</string>
+    <string name="sony_ambient_sound_control_button_mode_nc_as_off">G眉r眉lt眉 Engelleme, Ortam Sesi, Kapal谋</string>
+    <string name="sony_ambient_sound_control_button_mode_nc_as">G眉r眉lt眉 Engelleme, Ortam Sesi</string>
+    <string name="sony_quick_access_triple_tap">H谋zl谋 Eri艧im (脺莽 Dokunu艧)</string>
+    <string name="menuitem_wechat_pay">WeChat Pay</string>
+    <string name="pref_agps_update_time">AGPS G眉ncelleme Zaman谋</string>
+    <string name="pref_agps_expire_time">AGPS Sona Erme Zaman谋</string>
+    <string name="appmanager_watchface_activate">Etkinle艧tir</string>
+    <string name="appmanager_app_start">Ba艧lat</string>
+    <string name="appmanager_app_download">脰nbelle臒e indir</string>
+    <string name="appmanager_downloaded_to_cache">%s 枚nbelle臒e indirildi</string>
+    <string name="appmanager_download_started">Uygulama indirme ba艧lad谋</string>
+    <string name="appmanager_download_app_error">Uygulama indirilirken hata olu艧tu</string>
+    <string name="devicetype_asteroidos">AsteroidOS</string>
+    <string name="devicetype_amazfit_trex_2">Amazfit T-Rex 2</string>
+    <string name="fw_upgrade_notice_amazfit_trex2">Amazfit T-Rex 2 ayg谋t谋n谋za %s 眉r眉n yaz谋l谋m谋n谋 kurmak 眉zeresiniz.
+\n
+\nBilekli臒iniz .zip dosyas谋n谋 kurduktan sonra yeniden ba艧lat谋lacak.
+\n
+\n陌LERLEMEN陌Z DURUMUNDA R陌SK S陌ZE A陌TT陌R!</string>
+    <string name="pref_workout_detection_ask_first_summary">Bu egzersizi alg谋larken saat 眉zerinden onay iste</string>
+    <string name="pref_workout_detection_enabled_summary">Bu egzersizin otomatik olarak alg谋lanmas谋n谋 etkinle艧tir</string>
+    <string name="pref_workout_detection_time">Alg谋lamadan 枚nceki etkin dakikalar</string>
+    <string name="pref_workout_detection_enabled">Alg谋lama etkin</string>
+    <string name="pref_workout_detection_ask_first">脰nce bana sor</string>
+    <string name="pref_workout_detection_time_summary">Egzersizin alg谋lanmadan 枚nce devam etmesi gereken dakika miktar谋</string>
+    <string name="activity_type_rowing">K眉rek 脟ekme</string>
+    <string name="georgian">G眉rc眉ce</string>
+    <string name="devicetype_galaxybuds_2_pro">Galaxy Buds2 Pro</string>
+    <string name="devicetype_soflow_s06">SoFlow SO6</string>
+    <string name="pref_title_lock_unlock">Kilitle</string>
+    <string name="pref_camera_remote_summary">Saatin telefonun kameras谋n谋 tetiklemesine izin verir</string>
+    <string name="pref_camera_remote_title">Kamera Kumandas谋</string>
+    <string name="pref_workout_keep_screen_on_summary">Egzersiz s谋ras谋nda ekran a莽谋k kalacak ve ger莽ek zamanl谋 egzersiz verilerini s眉rekli olarak g枚r眉nt眉lemek i莽in parlakl谋k ayarlanacakt谋r</string>
+    <string name="pref_workout_keep_screen_on_title">Egzersiz s谋ras谋nda ekran谋 a莽谋k tut</string>
+    <string name="status">Durum</string>
+    <string name="wifi_hotspot">Wi-Fi Eri艧im Noktas谋</string>
+    <string name="wifi_hotspot_summary">Saatteki Wi-Fi eri艧im noktas谋n谋 denetleyin</string>
+    <string name="wifi_hotspot_configuration">Wi-Fi Eri艧im Noktas谋 Yap谋land谋rmas谋</string>
+    <string name="wifi_ssid">SSID</string>
+    <string name="wifi_hotspot_status">Wi-Fi Eri艧im Noktas谋 Durumu</string>
+    <string name="wifi_hotspot_start_summary">Saatteki Wi-Fi eri艧im noktas谋n谋 ba艧lat谋n</string>
+    <string name="wifi_hotspot_stop_summary">Saatteki Wi-Fi eri艧im noktas谋n谋 durdurun</string>
+    <string name="ftp_server">FTP Sunucusu</string>
+    <string name="ftp_server_summary">Saatteki FTP sunucusunu denetleyin</string>
+    <string name="ftp_server_start_summary">Saatteki FTP sunucusunu ba艧lat谋n</string>
+    <string name="ftp_server_stop_summary">Saatteki FTP sunucusunu durdurun</string>
+    <string name="address">Adres</string>
+    <string name="username">Kullan谋c谋 Ad谋</string>
+    <string name="ftp_server_configuration">FTP Sunucusu Yap谋land谋rmas谋</string>
+    <string name="stop">Durdur</string>
+    <string name="ftp_server_root_dir">K枚k Dizin</string>
+    <string name="ftp_server_status">FTP Sunucusu Durumu</string>
+    <string name="croatian">H谋rvat莽a</string>
+    <string name="sony_speak_to_chat_summary">Konu艧maya ba艧lad谋臒谋n谋zda g眉r眉lt眉 engellemeyi otomatik olarak kapat谋n.</string>
+    <string name="pref_morning_updates_title">Sabah G眉ncellemeleri</string>
+    <string name="pref_morning_updates_summary">Her sabah g眉ncellemeleri g枚ster</string>
+    <string name="pref_morning_updates_categories_title">Sabah G眉ncellemeleri Kategorileri</string>
+    <string name="pref_morning_updates_categories_summary">Her sabah g枚r眉nt眉lenecek kategorilerin listesi</string>
+    <string name="yesterdays_activity">D眉nk眉 Etkinlik</string>
+    <string name="bluetooth_calls_pairing">Bluetooth arama e艧le艧tirme</string>
+    <string name="bluetooth_calls_settings">Bluetooth arama ayarlar谋</string>
+    <string name="pref_display_caller_title">Ki艧i bilgilerini g枚ster</string>
+    <string name="pref_display_caller_summary">Gelen aramalar i莽in telefon numaras谋n谋 veya ad谋 g枚r眉nt眉le</string>
+    <string name="pref_pair_bluetooth_calls_title">Bluetooth aramalar谋 i莽in e艧le艧tir</string>
+    <string name="pref_pair_bluetooth_calls_help_summary">Bluetooth aramalar谋n谋 alabilmek i莽in telefonunuzu saatin ikinci bir 枚rne臒iyle e艧le艧tirmeniz gerekir.</string>
+    <string name="pref_pair_bluetooth_calls_help_2">2. Telefonunuzun bluetooth ayarlar谋na gidin ve g枚r眉necek yeni ayg谋tla e艧le艧tirin (ge莽erli saatinize benzer bir ad, ancak bir son ek ile, 枚rne臒in \"Amazfit GTR 4 - AFC8\").</string>
+    <string name="pref_pair_bluetooth_calls_help_3">3. A艧a臒谋daki Bluetooth arama ayar谋n谋 etkinle艧tirin.</string>
+    <string name="pref_pair_bluetooth_calls_help_warning">UYARI: 陌kinci 枚rnekle e艧le艧tirme yapmadan bluetooth aramalar谋n谋 etkinle艧tirirseniz, arama bildirimleri beklendi臒i gibi 莽al谋艧mayabilir.</string>
+    <string name="pref_pair_bluetooth_calls_help_1">1. E艧le艧tirme i艧lemini ba艧latmak i莽in a艧a臒谋daki d眉臒meye dokunun.</string>
+    <string name="pref_summary_receive_calls_watch">Aramalar谋 do臒rudan saat 眉zerinden ger莽ekle艧tirin ve al谋n</string>
+    <string name="pref_pair_bluetooth_calls_help_title">Bluetooth aramalar谋 nas谋l al谋n谋r</string>
+    <string name="bluetooth_calls">Bluetooth aramalar谋</string>
+    <string name="pref_pair_bluetooth_calls_summary">E艧le艧tirme i艧lemini ba艧latmak i莽in buraya t谋klay谋n</string>
+    <string name="french_ca">Frans谋zca (Kanada)</string>
+    <string name="fw_upgrade_notice_amazfitbip3pro">Amazfit Bip 3 Pro ayg谋t谋n谋za %s 眉r眉n yaz谋l谋m谋n谋 kurmak 眉zeresiniz.
+\n
+\nL眉tfen 枚nce .fw dosyas谋n谋, sonra .res dosyas谋n谋 kurun. Saatiniz .fw dosyas谋n谋 kurduktan sonra yeniden ba艧lat谋lacak.
+\n
+\nNot: Daha 枚nce kurulanla tamamen ayn谋ysa .res dosyas谋n谋 kurman谋z gerekmez.
+\n
+\n陌LERLEMEN陌Z DURUMUNDA R陌SK S陌ZE A陌TT陌R!</string>
+    <string name="french_fr">Frans谋zca (Fransa)</string>
+    <string name="fwapp_install_device_not_supported">Dosya kurulamad谋, ayg谋t desteklenmiyor.</string>
+    <string name="action_changelog">De臒i艧iklik g眉nl眉臒眉</string>
+    <string name="title_activity_contact_details">Ki艧i ayr谋nt谋lar谋</string>
+    <string name="num_min">%d dak</string>
+    <string name="preferences_miband_1_2_settings" tools:ignore="TypographyFractions">Mi Band 1/2 ayarlar谋</string>
+    <string name="contact_delete_confirm_title">Ki艧iyi sil</string>
+    <string name="pai_chart_per_week">Haftal谋k PAI</string>
+    <string name="pref_title_notification_cache_while_disconnected">Kapsama alan谋 d谋艧谋ndayken 枚nbelle臒e al</string>
+    <string name="pref_summary_general_reconnectonlytoconnected">T眉m ayg谋tlara yeniden ba臒lanmak yerine yaln谋zca ba臒l谋 ayg谋tlara yeniden ba臒lan</string>
+    <string name="pref_title_general_reconnectonlytoconnected">Yaln谋zca ba臒l谋 ayg谋tlara yeniden ba臒lan</string>
+    <string name="pref_summary_notification_cache_while_disconnected">Bir ayg谋t kapsama alan谋 d谋艧谋nda kald谋ktan sonra yeniden ba臒land谋臒谋nda ka莽谋r谋lan bildirimleri g枚nder</string>
+    <string name="pref_summary_banglejs_phone_gps_enbale">Bangle ayg谋t谋n谋n GPS verilerinin 眉zerine yazmak i莽in telefonun GPS verilerini kullan</string>
+    <string name="pref_canned_message">Mesaj</string>
+    <string name="pref_title_casio_alert_other">Di臒er bildirimler i莽in uyar</string>
+    <string name="pref_title_preview_message_in_title">Ba艧l谋kta mesaj谋n 枚n izlemesini g枚ster</string>
+    <string name="pref_title_casio_alert_call">Gelen 莽a臒r谋lar i莽in uyar</string>
+    <string name="femometer_measurement_mode_precise">Hassas Mod (3 dak)</string>
+    <string name="pref_contacts_summary">Saatte ki艧ileri yap谋land谋r谋n</string>
+    <string name="updatefirmwareoperation_updateproblem_free_space">Ayg谋tta yeterli bo艧 alan yok</string>
+    <string name="busy_task_fetch_statistics">陌statistikler getiriliyor</string>
+    <string name="pai_plus_num">+%d</string>
+    <string name="pai_chart_per_month">Ayl谋k PAI</string>
+    <string name="pref_theme_dynamic">De臒i艧ken renkler</string>
+    <string name="pref_title_banglejs_phone_gps_enbale">Telefon GPS verilerini kullan</string>
+    <string name="pref_summary_banglejs_phone_gps_network_only">Konumu belirlemek i莽in yaln谋zca a臒 sa臒lay谋c谋s谋n谋 kullan谋n. Bu, do臒rulu臒u etkileyebilir ancak g眉莽 t眉ketimini azalt谋r. Yeniden ba臒lant谋 gereklidir.</string>
+    <string name="pref_title_banglejs_phone_gps_update_interval">GPS verisi g眉ncelleme aral谋臒谋</string>
+    <string name="pref_summary_banglejs_phone_gps_update_interval">GPS konumunun ne s谋kl谋kta g眉ncellenece臒i, ms cinsinden</string>
+    <string name="pref_message_privacy_mode_bodyonly">Yaln谋zca g枚vdeyi gizle</string>
+    <string name="pref_summary_preview_message_in_title">Ayg谋t谋n izin verdi臒i 枚l莽眉de bir bildirimin ba艧l谋臒谋nda mesaj谋n 枚n izlemesini g枚sterir</string>
+    <string name="pref_title_casio_alert_calendar">Takvim bildirimleri i莽in uyar</string>
+    <string name="pref_summary_casio_alert_calendar">Takvim bildirimleri i莽in uyar (titre艧im/bip sesi)</string>
+    <string name="pref_title_casio_alert_email">E-posta bildirimleri i莽in uyar</string>
+    <string name="pref_summary_casio_alert_email">E-posta bildirimleri i莽in uyar (titre艧im/bip sesi)</string>
+    <string name="pref_title_casio_alert_sms">SMS bildirimleri i莽in uyar</string>
+    <string name="pref_summary_casio_alert_sms">SMS (k谋sa mesaj) bildirimleri i莽in uyar (titre艧im/bip sesi)</string>
+    <string name="pref_summary_casio_alert_other">Di臒er kategorideki bildirimler i莽in uyar (titre艧im/bip sesi)</string>
+    <string name="pref_summary_casio_alert_call">Gelen 莽a臒r谋lar i莽in uyar (titre艧im/bip sesi)</string>
+    <string name="pref_header_navigation">Navigasyon</string>
+    <string name="busy_task_fetch_pai_data">PAI verileri getiriliyor</string>
+    <string name="busy_task_fetch_spo2_data">SpO2 verileri getiriliyor</string>
+    <string name="busy_task_fetch_hr_data">Kalp ritmi verileri getiriliyor</string>
+    <string name="busy_task_fetch_sleep_respiratory_rate_data">Uyku solunum h谋z谋 verileri getiriliyor</string>
+    <string name="busy_task_fetch_temperature">S谋cakl谋k verileri getiriliyor</string>
+    <string name="fw_upgrade_notice_amazfit_gtr3_pro">Amazfit GTR 3 Pro ayg谋t谋n谋za %s 眉r眉n yaz谋l谋m谋n谋 kurmak 眉zeresiniz.
+\n
+\nBilekli臒iniz .zip dosyas谋n谋 kurduktan sonra yeniden ba艧lat谋lacak.
+\n
+\n陌LERLEMEN陌Z DURUMUNDA R陌SK S陌ZE A陌TT陌R!</string>
+    <string name="busy_task_fetch_stress_data">Stres verileri getiriliyor</string>
+    <string name="title_activity_set_contacts">Ki艧ileri yap谋land谋r</string>
+    <string name="pref_title_mb_intents">Medya D眉臒mesi Ama莽lar谋n谋 Do臒rudan Yay谋nla</string>
+    <string name="pref_summary_mb_intents">Ayg谋t medya denetimi belirli uygulamalar i莽in 莽al谋艧m谋yorsa etkinle艧tirin</string>
+    <string name="pref_title_banglejs_phone_gps_network_only">Konumu belirlemek i莽in yaln谋zca a臒谋 kullan</string>
+    <string name="pixoo_power_saving_summary">Mikrofon bir s眉re sessizlik alg谋lad谋ktan sonra ekran kapanacakt谋r</string>
+    <string name="clap_hands_to_wakeup_device">Ekran谋 a莽mak i莽in el 莽谋rp</string>
+    <string name="clap_hands_to_wakeup_device_summary">Tekrar el 莽谋rpmak ekran谋 kapatacakt谋r</string>
+    <string name="busy_task_fetch_sports_summaries">Spor 枚zetleri getiriliyor</string>
+    <string name="busy_task_fetch_sports_details">Spor ayr谋nt谋lar谋 getiriliyor</string>
+    <string name="busy_task_fetch_debug_logs">Hata ay谋klama g眉nl眉kleri getiriliyor</string>
+    <string name="prefs_wearmode">Giyme modu</string>
+    <string name="pref_contacts_title">Ki艧iler</string>
+    <string name="contact_delete_confirm_description">\'%1$s\' ki艧isini silmek istedi臒inizden emin misiniz?</string>
+    <string name="fw_upgrade_notice_amazfit_cheetah_pro">Amazfit Cheetah Pro ayg谋t谋n谋za %s 眉r眉n yaz谋l谋m谋n谋 kurmak 眉zeresiniz.
+\n
+\nBilekli臒iniz .zip dosyas谋n谋 kurduktan sonra yeniden ba艧lat谋lacak.
+\n
+\n陌LERLEMEN陌Z DURUMUNDA R陌SK S陌ZE A陌TT陌R!</string>
+    <string name="fw_upgrade_notice_zepp_os">%s 眉r眉n yaz谋l谋m谋n谋 %s ayg谋t谋n谋za kurmak 眉zeresiniz.
+\n
+\nSaatiniz .zip dosyas谋n谋 kurduktan sonra yeniden ba艧lat谋lacak.
+\n
+\n陌LERLEMEN陌Z DURUMUNDA R陌SK S陌ZE A陌TT陌R!</string>
+    <string name="wearmode_pebble">Pebble (ayakkab谋 tokas谋)</string>
+    <string name="wearmode_band">Bileklik (kol bilekli臒i)</string>
+    <string name="wearmode_necklace">Kolye (boyun ask谋s谋)</string>
+    <string name="pref_title_notifications_ignore_work_profile">陌艧 profili bildirimlerini yok say</string>
+    <string name="pref_summary_notifications_ignore_work_profile">陌艧 profilindeki uygulamalardan saate bildirim g枚nderme</string>
+    <string name="pref_title_send_app_notifications">Bildirim g枚nder</string>
+    <string name="pref_summary_send_app_notifications">Uygulama bildirimlerini ayg谋ta g枚nder</string>
+    <string name="gpx_route_upload_failed">GPX g眉zergah谋 y眉klenmesi ba艧ar谋s谋z oldu</string>
+    <string name="gpx_route_upload_complete">GPX g眉zergah谋 y眉klenmesi tamamland谋</string>
+    <string name="heartrate_bpm_155">155 kalp at谋艧谋/dakika</string>
+    <string name="heartrate_bpm_165">165 kalp at谋艧谋/dakika</string>
+    <string name="heartrate_bpm_175">175 kalp at谋艧谋/dakika</string>
+    <string name="heartrate_bpm_185">185 kalp at谋艧谋/dakika</string>
+    <string name="heartrate_bpm_195">195 kalp at谋艧谋/dakika</string>
+    <string name="heartrate_bpm_205">205 kalp at谋艧谋/dakika</string>
+    <string name="contact_no_free_slots_description">Ayg谋tta ki艧iler i莽in bo艧 yuva yok (toplam yuva: %1$s)</string>
+    <string name="prefs_heartrate_alert_active_high_threshold">Y眉ksek etkinlik kalp ritmi uyar谋 e艧i臒i</string>
+    <string name="updatefirmwareoperation_updateproblem_low_battery">Ayg谋t谋n pili 莽ok d眉艧眉k</string>
+    <string name="gpx_route_upload_in_progress">GPX g眉zergah谋 y眉kleniyor</string>
+    <string name="pref_title_goal_secondary">陌kincil hedef</string>
+    <string name="femometer_measurement_mode_title">脰l莽眉m modu</string>
+    <string name="femometer_measurement_mode_quick">H谋zl谋 Mod (30 sn)</string>
+    <string name="femometer_measurement_mode_normal">Normal Mod (60sn-90sn)</string>
+    <string name="preferences_miband_1_2_warning">Uyar谋: Bu tercihler yaln谋zca Mi Band 1 ve 2 i莽in ge莽erlidir.</string>
+    <string name="devicetype_casiogmwb5000">Casio GMW-B5000</string>
+    <string name="devicetype_colacao23">ColaCao 2023</string>
+    <string name="devicetype_sony_wh_1000xm5">Sony WH-1000XM5</string>
+    <string name="devicetype_huawei_band6">Huawei Band 6</string>
+    <string name="devicetype_huawei_band8">Huawei Band 8</string>
+    <string name="devicetype_huawei_watch_gt">Huawei Watch GT</string>
+    <string name="devicetype_huawei_band4pro">Huawei Band 4 (Pro)</string>
+    <string name="devicetype_huawei_watchgt2">Huawei Watch GT 2 (Pro)</string>
+    <string name="devicetype_miband7pro">Xiaomi Smart Band 7 Pro</string>
+    <string name="activity_type_surfing">S枚rf yapma</string>
+    <string name="activity_type_kitesurfing">U莽urtma s枚rf眉</string>
+    <string name="devicetype_redmi_watch_2">Redmi Watch 2</string>
+    <string name="menuitem_thermometer">Termometre</string>
+    <string name="menuitem_cards">Kartlar</string>
+    <string name="menuitem_vo2_max">En Y眉ksek VO鈧�</string>
+    <string name="device_experimental">DENEYSEL</string>
+    <string name="devicetype_nothingearstick">Nothing Ear (Stick)</string>
+    <string name="prefs_active_noise_cancelling_transparency">艦effafl谋k</string>
+    <string name="stress_mild">Hafif</string>
+    <string name="quick_attention">脟abuk Dikkat</string>
+    <string name="withings_steel_hr">Withings Steel HR</string>
+    <string name="pref_device_action_dnd_priority">Rahats谋z etme - Yaln谋zca 枚ncelikli</string>
+    <string name="pref_device_action_dnd_off">Rahats谋z etme - Kapal谋</string>
+    <string name="pref_device_action_dnd_on">Rahats谋z etme - A莽谋k</string>
+    <string name="huawei_trusleep_summary_light">Geli艧tirilmi艧 uyku izleme</string>
+    <string name="devicetype_redmi_smart_band_pro">Redmi Smart Band Pro</string>
+    <string name="devicetype_bohemic_smart_bracelet">Bohemic Smart Bracelet</string>
+    <string name="zepp_os_watchface_multiple_data">脟oklu Veri</string>
+    <string name="zepp_os_watchface_lightning_flash">艦im艧ek 莽akmas谋</string>
+    <string name="pref_show_changelog">Ba艧lang谋莽ta de臒i艧iklik g眉nl眉臒眉n眉 g枚ster</string>
+    <string name="laneLength">Kulvar Uzunlu臒u</string>
+    <string name="activity_detail_share_raw_details">Ham Ayr谋nt谋lar谋 Payla艧</string>
+    <string name="error_no_bluetooth_connect">Taraman谋n d眉zg眉n 莽al谋艧mas谋 i莽in Bluetooth ba臒lant谋 eri艧imi verilmeli ve etkinle艧tirilmelidir</string>
+    <string name="strokes">Toplam Kula莽</string>
+    <string name="prefs_phone_silent_mode">Telefon Sessiz Modu</string>
+    <string name="silent_mode_normal_vibrate">Normal / Titret</string>
+    <string name="prefs_shortcut_cards_summary">Saat aray眉z眉nde sa臒a kayd谋r谋ld谋臒谋nda g枚r眉len k谋sayol kartlar谋. Bir uygulama 莽al谋艧谋rken, otomatik olarak olu艧turulan kartlar bu ayardan etkilenmez.</string>
+    <string name="latvian">Letonca</string>
+    <string name="active_time">Etkin zaman</string>
+    <string name="standing_time">Ayakta durma s眉resi</string>
+    <string name="activity_type_icehockey">Buz hokeyi</string>
+    <string name="activity_type_riding">At binme</string>
+    <string name="share_screenshot">Ekran g枚r眉nt眉s眉n眉 payla艧</string>
+    <string name="zepp_os_watchface_business_style">陌艧 Tarz谋</string>
+    <string name="triple_tap">脺莽 Dokunu艧</string>
+    <string name="pref_summary_navigation_forward">Navigasyon uygulamalar谋ndaki talimatlar谋 saate ilet</string>
+    <string name="continue_pressing">Basmaya Devam Et</string>
+    <string name="devicetype_sony_wena3">Sony Wena 3</string>
+    <string name="zepp_os_watchface_vibrant">Canl谋</string>
+    <string name="activity_type_rugby">Ragbi</string>
+    <string name="activity_type_squash">Squash (duvar tenisi)</string>
+    <string name="devicetype_amazfit_gtr3_pro">Amazfit GTR 3 Pro</string>
+    <string name="devicetype_amazfit_gtr_mini">Amazfit GTR Mini</string>
+    <string name="devicetype_femometer_vinca2">Femometer Vinca II</string>
+    <string name="zepp_os_watchface_red_fantasy">K谋rm谋z谋 Fantezi</string>
+    <string name="zepp_os_watchface_emerald_moonlight">Z眉mr眉t Ay I艧谋臒谋</string>
+    <string name="zepp_os_watchface_guider">K谋lavuz</string>
+    <string name="zepp_os_watchface_free_combination">Serbest birle艧im</string>
+    <string name="zepp_os_watchface_pure_white">Saf beyaz</string>
+    <string name="pref_show_changelog_summary">Gadgetbridge g眉ncellendikten sonra son s眉r眉mden bu yana yap谋lan de臒i艧iklik g眉nl眉臒眉n眉 g枚r眉nt眉le</string>
+    <string name="menuitem_zepp_coach">Zepp Coach</string>
+    <string name="dev_tools">Geli艧tirici Ara莽lar谋</string>
+    <string name="menuitem_forecast">Tahmin</string>
+    <string name="error_no_bluetooth_scan">Taraman谋n d眉zg眉n 莽al谋艧mas谋 i莽in Bluetooth tarama eri艧imi verilmeli ve etkinle艧tirilmelidir</string>
+    <string name="activity_detail_share_raw_summary">Ham 脰zeti Payla艧</string>
+    <string name="prefs_workmode">陌艧 Modu</string>
+    <string name="pref_enable_call_accept">脟a臒r谋lar谋 kabul etmeyi etkinle艧tir</string>
+    <string name="activity_type_football">Futbol</string>
+    <string name="single_tap">Tek Dokunu艧</string>
+    <string name="silent_mode_vibrate_silent">Titret / Sessiz</string>
+    <string name="do_not_disturb_lift_wrist_summary">Yaln谋zca kald谋r谋ld谋臒谋nda ekran谋 a莽 枚zelli臒i etkinle艧tirilmi艧se</string>
+    <string name="activity_type_snowboarding">Kar kaya臒谋</string>
+    <string name="devicetype_amazfit_trex_ultra">Amazfit T-Rex Ultra</string>
+    <string name="devicetype_huawei_band7">Huawei Band 7</string>
+    <string name="pref_enable_call_accept_summary">Ayg谋ttan gelen 莽a臒r谋lar谋 kabul etmeyi etkinle艧tirin</string>
+    <string name="pref_force_smart_alarm">Ak谋ll谋 alarm谋 zorla</string>
+    <string name="pref_force_wear_location">Giyme konumunu zorla</string>
+    <string name="english_ca">陌ngilizce (Kanada)</string>
+    <string name="english_gb">陌ngilizce (Birle艧ik Krall谋k)</string>
+    <string name="english_in">陌ngilizce (Hindistan)</string>
+    <string name="english_us">陌ngilizce (ABD)</string>
+    <string name="activity_type_weightlifting">A臒谋rl谋k kald谋rma</string>
+    <string name="screenshot_taken">Ekran g枚r眉nt眉s眉 al谋nd谋</string>
+    <string name="devicetype_amazfit_cheetah_square">Amazfit Cheetah (Kare)</string>
+    <string name="devicetype_amazfit_falcon">Amazfit Falcon</string>
+    <string name="zepp_os_watchface_simplicity_data">Sadelik Verisi</string>
+    <string name="zepp_os_watchface_rotating_earth">D枚nen D眉nya</string>
+    <string name="permission_request">%1$s, Android\'den ayg谋t谋n谋za mesaj ve di臒er verileri g枚ndermenizi sa臒lar. Bunu yapmak i莽in bu verilere eri艧im izni gerekir ve bu izin olmadan d眉zg眉n 莽al谋艧mayabilir.
+\n
+\n艦imdi size bu izinleri isteyen birka莽 Android ileti艧im kutusu sunulacak.
+\n
+\nDevam etmek i莽in l眉tfen \'%2$s\' se莽ene臒ine dokunun.</string>
+    <string name="activity_detail_share_gps_label">GPS 陌zlemesini Payla艧</string>
+    <string name="pai_day">G眉nl眉k art谋艧</string>
+    <string name="double_tap">脟ift Dokunu艧</string>
+    <string name="spanish_us">陌spanyolca (ABD)</string>
+    <string name="devicetype_amazfit_bip5">Amazfit Bip 5</string>
+    <string name="menuitem_zepp_pay">Zepp Pay</string>
+    <string name="stress_moderate">Orta</string>
+    <string name="pref_disable_find_phone_with_dnd">Rahats谋z etme 枚zelli臒i etkin oldu臒unda telefonumu bul 枚zelli臒ini devre d谋艧谋 b谋rak</string>
+    <string name="pref_force_dnd_support">Rahats谋z etme deste臒ini zorla</string>
+    <string name="pref_force_dnd_support_summary">Rahats谋z etme deste臒ini zorla.
+\nKULLANMANIZ DURUMUNDA R陌SK S陌ZE A陌TT陌R</string>
+    <string name="common_symbols">Yayg谋n Semboller</string>
+    <string name="activity_type_windsurfing">R眉zgar s枚rf眉</string>
+    <string name="devicetype_honor_band4">Honor Band 4</string>
+    <string name="devicetype_honor_band5">Honor Band 5</string>
+    <string name="menuitem_recommendation">Tavsiye</string>
+    <string name="menuitem_workout_shortcuts">Egzersiz k谋sayollar谋</string>
+    <string name="menuitem_apps_shortcuts">Uygulama k谋sayollar谋</string>
+    <string name="menuitem_mi_ai">MI AI</string>
+    <string name="menuitem_headphone">Kulakl谋k</string>
+    <string name="zepp_os_watchface_city_of_speed">H谋z 艧ehri</string>
+    <string name="zepp_os_watchface_starry_sky">Y谋ld谋zl谋 g枚ky眉z眉</string>
+    <string name="zepp_os_watchface_the_ultima">Nihai</string>
+    <string name="maxStrokeRate">En Y眉ksek Kula莽 H谋z谋</string>
+    <string name="strokes_minute">kula莽/dak</string>
+    <string name="sony_protocol_v1">S眉r眉m 1</string>
+    <string name="sony_protocol_v2">S眉r眉m 2</string>
+    <string name="sony_protocol_v3">S眉r眉m 3</string>
+    <string name="protocol_version">Protokol S眉r眉m眉</string>
+    <string name="long_press">Uzun Bas</string>
+    <string name="huawei_trusleep_title">HUAWEI TruSleep 鈩�</string>
+    <string name="pref_title_navigation_forward">Navigasyonu saate g枚nder</string>
+    <string name="pref_enable_call_reject_summary">Ayg谋ttan gelen 莽a臒r谋lar谋 reddetmeyi etkinle艧tirin</string>
+    <string name="pref_force_smart_alarm_summary">Ak谋ll谋 alarm deste臒ini zorla.
+\nKULLANMANIZ DURUMUNDA RI虈SK SI虈ZE AI虈TTI虈R</string>
+    <string name="pref_force_options_summary">Baz谋 ayg谋tlar yanl谋艧l谋kla baz谋 se莽enekleri desteklemedi臒ini belirtir. Bu ayarlar yine de onlar谋 etkinle艧tirmek i莽in kullan谋labilir.
+\nKULLANMANIZ DURUMUNDA R陌SK S陌ZE A陌TT陌R
+\nWiki sayfas谋n谋 okuyun</string>
+    <string name="huawei_ignore_wakeup_status_end_description">Do臒ru uyku alg谋lamas谋na yard谋mc谋 olabilir. G眉nl眉k etkinlikler g枚r眉n眉m眉nde hemen g枚r眉lebilir.</string>
+    <string name="huawei_reparse_workout_data">Egzersiz verilerini yeniden ayr谋艧t谋r</string>
+    <string name="huawei_reparse_workout_data_description">Bu yaln谋zca belirli g眉ncellemelerden sonra bir 艧eyler yapacakt谋r</string>
+    <string name="intent_api_allow_debug_commands_title">Hata Ay谋klama Komutlar谋na 陌zin Ver</string>
+    <string name="intent_api_allow_debug_commands_summary">Ama莽 API\'si arac谋l谋臒谋yla hata ay谋klama men眉s眉 komutlar谋n谋n tetiklenmesine izin ver</string>
+    <string name="text_receiver_activity_title">Ayg谋ta metin g枚nder</string>
+    <string name="danish">Danca</string>
+    <string name="pref_do_not_disturb_not_wear">Giyilmedi臒inde rahats谋z etme</string>
+    <string name="manual">El ile</string>
+    <string name="spanish_mx">陌spanyolca (Meksika)</string>
+    <string name="activity_prefs_goals">Hedefler</string>
+    <string name="charts_legend_stress_average">Stres ortalamas谋</string>
+    <string name="charts_legend_spo2_average">Kan oksijeni ortalamas谋</string>
+    <string name="activity_type_fishing">Bal谋k tutma</string>
+    <string name="activity_type_mind_and_body">Zihin ve beden</string>
+    <string name="activity_type_kabaddi">Kabaddi</string>
+    <string name="activity_type_billiards">Bilardo</string>
+    <string name="activity_type_shuttlecock">T眉ytop</string>
+    <string name="activity_type_ice_hockey">Buz hokeyi</string>
+    <string name="activity_type_curling">K枚rling</string>
+    <string name="activity_type_skiing">Kayak</string>
+    <string name="activity_type_hockey">Hokey</string>
+    <string name="activity_type_golf">Golf</string>
+    <string name="activity_type_trekking">Do臒a y眉r眉y眉艧眉</string>
+    <string name="activity_type_wrestling">G眉re艧</string>
+    <string name="devicetype_amazfit_bip3_pro">Amazfit Bip 3 Pro</string>
+    <string name="devicetype_vivomove_hr">Garmin Vivomove HR</string>
+    <string name="devicetype_casiogwb5600">Casio GW-B5600</string>
+    <string name="devicetype_mijia_lywsd03">Mijia S谋cakl谋k ve Nem Sens枚r眉 2</string>
+    <string name="devicetype_mijia_mho_c303">Mijia MHO-C303</string>
+    <string name="devicetype_colacao21">ColaCao 2021</string>
+    <string name="devicetype_sony_wi_sp600n">Sony WI-SP600N</string>
+    <string name="devicetype_honor_band3">Honor Band 3</string>
+    <string name="devicetype_honor_band6">Honor Band 6</string>
+    <string name="devicetype_honor_band7">Honor Band 7</string>
+    <string name="devicetype_redmiwatch3">Redmi Watch 3</string>
+    <string name="menuitem_aqi">Hava Kalitesi G枚stergesi</string>
+    <string name="menuitem_last_workout">Son egzersiz</string>
+    <string name="menuitem_total_workout">Toplam egzersiz</string>
+    <string name="menuitem_buzzer_intensity">Sesli Uyar谋 艦iddeti</string>
+    <string name="zepp_os_watchface_superposition">s眉per konum</string>
+    <string name="avgStrokeRate">Ortalama Kula莽 H谋z谋</string>
+    <string name="yard">yarda</string>
+    <string name="sony_button_mode_help_summary">Her d眉臒me modunun a莽谋klamas谋</string>
+    <string name="pref_device_action_dnd_alarms">Rahats谋z etme - Yaln谋zca alarmlar</string>
+    <string name="pref_enable_call_reject">脟a臒r谋lar谋 reddetmeyi etkinle艧tir</string>
+    <string name="pref_spo_automatic_enable">Otomatik SpO2 枚l莽眉m眉n眉 etkinle艧tir</string>
+    <string name="pref_force_options">Se莽enekleri zorla</string>
+    <string name="alarm_smart_wakeup_interval">Ak谋ll谋 uyand谋rma aral谋臒谋:</string>
+    <string name="alarm_smart_wakeup_interval_default">5 dakika</string>
+    <string name="silent_mode_normal_silent">Normal / Sessiz</string>
+    <string name="prefs_password_4_digits_0_to_9_summary">Parola 4 haneli olmal谋 ve yaln谋zca rakamlar kullan谋lmal谋d谋r</string>
+    <string name="activity_type_horizontal_bar">Barfiks</string>
+    <string name="activity_type_hand_cycling">El bisikleti</string>
+    <string name="activity_type_iceskating">Buz pateni</string>
+    <string name="activity_type_other">Di臒er</string>
+    <string name="watchface_widget_type_uv_index">UV g枚stergesi</string>
+    <string name="devicetype_nothingear2">Nothing Ear (2)</string>
+    <string name="devicetype_nothing_cmf_watch_pro">CMF Watch Pro</string>
+    <string name="prefs_active_noise_cancelling_light">Hafif Etkin G眉r眉lt眉 Engelleme</string>
+    <string name="huawei_trusleep_summary">Uyku kalitenizi ve solunum d眉zeninizi ger莽ek zamanl谋 olarak izleyin.
+\nUyku d眉zeninizi inceleyin ve 6 t眉r uyku problemini do臒ru bir 艧ekilde te艧his edin.</string>
+    <string name="stress_high">Y眉ksek</string>
+    <string name="pai_total">Toplam</string>
+    <string name="sony_button_mode_help_title">D眉臒me Modlar谋 - Yard谋m</string>
+    <string name="updatefirmwareoperation_failed_low_mtu">Ge莽erli MTU de臒eri (%1$d) 莽ok d眉艧眉k, l眉tfen ayg谋t ayarlar谋nda y眉ksek MTU\'yu etkinle艧tirin ve ayg谋t谋n ba臒lant谋s谋n谋 kesin/yeniden ba臒lay谋n.</string>
+    <string name="prefs_disconnect_notification_summary">BT ba臒lant谋s谋 kesildi臒inde ayg谋tta bildirim.</string>
+    <string name="hydration_dnd_summary">Bir zaman aral谋臒谋 i莽in s谋v谋 alma uyar谋lar谋n谋 devre d谋艧谋 b谋rak</string>
+    <string name="prefs_shortcut_cards">K谋sayol Kartlar谋</string>
+    <string name="english_au">陌ngilizce (Avustralya)</string>
+    <string name="dnd_all_day">T眉m g眉n</string>
+    <string name="activity_type_cross_country_running">A莽谋k arazi ko艧usu</string>
+    <string name="activity_type_karate">Karate</string>
+    <string name="activity_type_fencing">Eskrim</string>
+    <string name="activity_type_kendo">Kendo</string>
+    <string name="activity_type_indoor_running">Kapal谋 Alanda Ko艧u</string>
+    <string name="activity_type_mountain_hike">Da臒 Y眉r眉y眉艧眉</string>
+    <string name="activity_type_cross_trainer">脟apraz antrenman</string>
+    <string name="activity_type_free_training">Serbest antrenman</string>
+    <string name="activity_type_rower">K眉rek</string>
+    <string name="activity_type_dynamic_cycle">Dinamik bisiklet</string>
+    <string name="activity_type_stair_stepper">Merdiven basama臒谋</string>
+    <string name="activity_type_fitness_exercises">Fitness egzersizleri</string>
+    <string name="activity_type_crossfit">Crossfit</string>
+    <string name="activity_type_functional_training">陌艧levsel antrenman</string>
+    <string name="activity_type_physical_training">Fiziksel antrenman</string>
+    <string name="activity_type_taekwondo">Tekvando</string>
+    <string name="activity_type_parallel_bar">Paralel bar</string>
+    <string name="activity_type_cooldown">So臒uma</string>
+    <string name="activity_type_cross_training">脟apraz antrenman</string>
+    <string name="activity_type_sit_ups">Mekik</string>
+    <string name="activity_type_fitness_gaming">Fitness oyunu</string>
+    <string name="activity_type_track_and_field">Pist ve saha</string>
+    <string name="activity_type_push_ups">艦谋nav</string>
+    <string name="activity_type_battle_rope">Sava艧 halat谋</string>
+    <string name="activity_type_aerobic_exercise">Aerobik egzersiz</string>
+    <string name="activity_type_rolling">Yuvarlama</string>
+    <string name="activity_type_flexibility">Esneklik</string>
+    <string name="activity_type_kite_flying">U莽urtma u莽urma</string>
+    <string name="activity_type_swing">Sal谋ncak</string>
+    <string name="activity_type_stairs">Merdiven</string>
+    <string name="activity_type_smith_machine">Smith makinesi</string>
+    <string name="activity_type_pull_ups">Barfiks 莽ekme</string>
+    <string name="activity_type_plank">Plank</string>
+    <string name="activity_type_javelin">Cirit</string>
+    <string name="activity_type_long_jump">Uzun atlama</string>
+    <string name="activity_type_high_jump">Y眉ksek atlama</string>
+    <string name="activity_type_trampoline">Trambolin</string>
+    <string name="activity_type_dumbbell">Damb谋l</string>
+    <string name="activity_type_belly_dance">G枚bek dans谋</string>
+    <string name="activity_type_jazz_dance">Caz dans谋</string>
+    <string name="activity_type_latin_dance">Latin dans谋</string>
+    <string name="activity_type_ballet">Bale</string>
+    <string name="activity_type_other_dance">Di臒er dans</string>
+    <string name="activity_type_roller_skating">Tekerlekli paten</string>
+    <string name="activity_type_martial_arts">D枚v眉艧 sanatlar谋</string>
+    <string name="activity_type_tai_chi">Tai chi</string>
+    <string name="activity_type_hula_hooping">Hulahop</string>
+    <string name="activity_type_disc_sports">Disk sporlar谋</string>
+    <string name="activity_type_darts">Dart</string>
+    <string name="activity_type_archery">Ok莽uluk</string>
+    <string name="activity_type_horse_riding">At binme</string>
+    <string name="activity_type_karting">Karting</string>
+    <string name="activity_type_sailing">Yelken</string>
+    <string name="activity_type_jet_skiing">Jet ski</string>
+    <string name="activity_type_skating">Paten</string>
+    <string name="activity_type_softball">Softbol</string>
+    <string name="activity_type_dodgeball">Yakantop</string>
+    <string name="activity_type_australian_football">Avustralya futbolu</string>
+    <string name="activity_type_pickleball">Pickleball</string>
+    <string name="activity_type_lacross">Lakros</string>
+    <string name="activity_type_shot">G眉lle atma</string>
+    <string name="activity_type_cross_country_skiing">K谋r kaya臒谋</string>
+    <string name="activity_type_snow_sports">Kar sporlar谋</string>
+    <string name="activity_type_skateboarding">Kaykay yapma</string>
+    <string name="activity_type_rock_climbing">Kaya t谋rman谋艧谋</string>
+    <string name="activity_type_hunting">Avc谋l谋k</string>
+    <string name="activity_type_outdoor_walking">A莽谋k Havada Y眉r眉y眉艧</string>
+    <string name="activity_error_share_failed">Dosya payla艧谋m谋 ba艧ar谋s谋z oldu.</string>
+    <string name="kind_gpx_route">GPX G眉zergah谋</string>
+    <string name="devicetype_miband8">Xiaomi Smart Band 8</string>
+    <string name="devicetype_miband8pro">Xiaomi Smart Band 8 Pro</string>
+    <string name="devicetype_amazfit_balance">Amazfit Balance</string>
+    <string name="devicetype_amazfit_active">Amazfit Active</string>
+    <string name="devicetype_amazfit_active_edge">Amazfit Active Edge</string>
+    <string name="devicetype_amazfit_cheetah_round">Amazfit Cheetah (Yuvarlak)</string>
+    <string name="devicetype_amazfit_cheetah_pro">Amazfit Cheetah Pro</string>
+    <string name="huawei_alarm_smart_description">Ak谋ll谋 uyand谋rma onay kutusunun i艧aretini kald谋rma.</string>
+    <string name="devicetype_huawei_watchgt2e">Huawei Watch GT 2e</string>
+    <string name="devicetype_huawei_talk_band_b6">Huawei Talk Band B6</string>
+    <string name="devicetype_huawei_watchgt3">Huawei Watch GT 3 (Pro)</string>
+    <string name="devicetype_honor_magicwatch2">Honor MagicWatch 2</string>
+    <string name="devicetype_huawei_band_aw70">Huawei Band (AW70)</string>
+    <string name="devicetype_xiaomi_watch_lite">Xiaomi Watch Lite</string>
+    <string name="devicetype_redmiwatch3active">Redmi Watch 3 Active</string>
+    <string name="devicetype_redmi_smart_band_2">Redmi Smart Band 2</string>
+    <string name="flatDistance">D眉z mesafe</string>
+    <string name="prefs_device_name">Ayg谋t ad谋</string>
+    <string name="spanish_es">陌spanyolca (陌spanya)</string>
+    <string name="activity_type_baseball">Beyzbol</string>
+    <string name="activity_type_handball">Hentbol</string>
+    <string name="activity_type_tennis">Tenis</string>
+    <string name="activity_type_dancing">Dans etme</string>
+    <string name="activity_type_trail_run">Patika ko艧usu</string>
+    <string name="devicetype_sony_wf_1000xm5">Sony WF-1000XM5</string>
+    <string name="stress_relaxed">Rahat</string>
+    <string name="devicetype_redmi_watch_2_lite">Redmi Watch 2 Lite</string>
+    <string name="pref_switch_controls_anc_ambient_off">G眉r眉lt眉 engelleme 鈫愨啋 Ortam 鈫愨啋 Kapal谋</string>
+    <string name="devicetype_huawei_watchfit">Huawei Watch Fit</string>
+    <string name="menuitem_readiness">Haz谋r olma durumu</string>
+    <string name="menuitem_body_composition">V眉cut yap谋s谋</string>
+    <string name="zepp_os_watchface_rush">Acele</string>
+    <string name="zepp_os_watchface_minimalist">Sade</string>
+    <string name="zepp_os_watchface_vast_sky">U莽suz G枚ky眉z眉</string>
+    <string name="pref_heartrate_automatic_enable">Otomatik kalp ritmi 枚l莽眉m眉n眉 etkinle艧tir</string>
+    <string name="pref_force_wear_location_summary">Giyme konumu deste臒ini zorla.
+\nKULLANMANIZ DURUMUNDA R陌SK S陌ZE A陌TT陌R</string>
+    <string name="huawei_ignore_wakeup_status_start_description">Do臒ru uyku alg谋lamas谋na yard谋mc谋 olabilir. G眉nl眉k etkinlikler g枚r眉n眉m眉nde hemen g枚r眉lebilir.</string>
+    <string name="huawei_ignore_wakeup_status_start">Uyand谋rma ba艧lang谋莽 durumunu yok say</string>
+    <string name="huawei_ignore_wakeup_status_end">Uyand谋rma biti艧 durumunu yok say</string>
+    <string name="huawei_alarm_event_description">Ak谋ll谋 uyand谋rma onay kutusunu i艧aretleme.</string>
+    <string name="voice_service_class_summary">Sesli komutlar谋 i艧leyen hizmetin tam yolu</string>
+    <string name="contact_phone_number">Telefon numaras谋</string>
+    <string name="status_icon">Durum simgesi</string>
+    <string name="changelog_ok_button">Tamam</string>
+    <string name="loyalty_cards_catima_package">Catima paket ad谋</string>
+    <string name="pref_app_logs_title">Uygulama g眉nl眉kleri</string>
+    <string name="loyalty_cards_sync_groups_only">Yaln谋zca belirli gruplar谋 e艧zamanla</string>
+    <string name="loyalty_cards_sync_archived">Ar艧ivlenen kartlar谋 e艧zamanla</string>
+    <string name="loyalty_cards_sync_groups">E艧zamanlanacak gruplar</string>
+    <string name="debug_companion_pair_current">Ge莽erli ayg谋t谋 yard谋mc谋 olarak e艧le艧tir</string>
+    <string name="description">A莽谋klama</string>
+    <string name="contact_missing_number">Ki艧i numaras谋 bo艧</string>
+    <string name="pref_title_debug">Hata ay谋klama iste臒i</string>
+    <string name="pref_summary_debug">Huawei ayg谋t谋na hata ay谋klama iste臒i g枚nder</string>
+    <string name="fossil_hr_connection_not_confirmed">Ba臒lant谋 saat 眉zerinde onaylanmad谋, kimli臒i do臒rulanmayan mod kullan谋l谋yor</string>
+    <string name="debug_companion_show_associated">陌li艧kili yard谋mc谋 ayg谋tlar谋 g枚ster</string>
+    <string name="contact_missing_name">Ki艧i ad谋 bo艧</string>
+    <string name="voice_service_package_title">Ses hizmeti paketi</string>
+    <string name="voice_service_class_title">Ses hizmeti s谋n谋f谋</string>
+    <string name="voice_service">Ses Hizmeti</string>
+    <string name="loyalty_cards_sync_starred">Yaln谋zca y谋ld谋zlanan kartlar谋 e艧zamanla</string>
+    <string name="changelog_title">Yenilikler</string>
+    <string name="loyalty_cards_install">Catima\'y谋 kur</string>
+    <string name="loyalty_cards_install_catima_fail">Catima\'y谋 kurmak i莽in uygulama ma臒azas谋 a莽谋lamad谋</string>
+    <string name="pref_app_logs_summary">Saat uygulamalar谋ndan g眉nl眉kleri etkinle艧tir</string>
+    <string name="pref_app_logs_start_summary">Saat uygulamalar谋ndan g眉nl眉kleri ba艧lat</string>
+    <string name="pref_app_logs_stop_summary">Saat uygulamalar谋ndan g眉nl眉kleri durdur</string>
+    <string name="changelog_show_full">Daha fazla鈥�</string>
+    <string name="fossil_hr_confirm_connection">L眉tfen saatinizden onaylay谋n</string>
+    <string name="fossil_hr_pairing_successful">Saatle e艧le艧tirme ba艧ar谋l谋</string>
+    <string name="fossil_hr_pairing_failed">Saatle e艧le艧tirme ba艧ar谋s谋z</string>
+    <string name="fossil_hr_confirmation_skipped">Ayg谋t 眉zerinde onay atlan谋yor</string>
+    <string name="fossil_hr_confirmation_timeout">Onay zaman a艧谋m谋, devam ediliyor</string>
+    <string name="contact_name">Ad</string>
+    <string name="voice_service_package_summary">Sesli komutlar谋 i艧leyen hizmeti i莽eren uygulama</string>
+    <string name="title">Ba艧l谋k</string>
+    <string name="preview_image">脰n izleme g枚rseli</string>
+    <string name="changelog_full_title">De臒i艧iklik g眉nl眉臒眉</string>
+    <string name="devicetype_xiaomi_watch_s1_active">Xiaomi Watch S1 Active</string>
+    <string name="pref_force_connection_type_ble">Bluetooth LE</string>
+    <string name="find_my_phone_found_it">BULUNDU</string>
+    <string name="error_menu_companion_not_installed">\'HR Menu Companion\' muhtemelen kurulu de臒il</string>
+    <string name="red">K谋rm谋z谋</string>
+    <string name="pref_theme_dynamic_colors_explanation">Not: De臒i艧ken renkler temas谋 i莽in Android 12 ve 眉zeri g枚r眉n眉m ayarlar谋n谋zda Duvar Ka臒谋d谋 Renkleri veya Renk Paletini etkinle艧tirmeniz gerekir. Etkinle艧tirmezseniz, Gadgetbridge 枚ntan谋ml谋 Material 3 renklerini kullanacak.</string>
+    <string name="prefs_wena3_item_large_font">Daha B眉y眉k Yaz谋 Tipi Boyutu</string>
+    <string name="prefs_wena3_button_action_item_double">脟ift Bas</string>
+    <string name="prefs_wena3_button_action_item_long">Uzun Bas</string>
+    <string name="prefs_wena3_notification_vibration_repetition_0">S眉resiz</string>
+    <string name="prefs_wena3_vibration_short">K谋sa</string>
+    <string name="widget">Widget</string>
+    <string name="widget_screen_no_free_slots_description">Ayg谋tta widget ekranlar谋 i莽in bo艧 yuva yok (toplam yuva: %1$s)</string>
+    <string name="pref_sleep_mode_schedule_summary">Yatma vaktinde bir hat谋rlatma g枚nder ve uyku moduna ge莽. Ayarlanan uyanma saatinde, uyand谋rma alarm谋 莽alacakt谋r.</string>
+    <string name="devicetype_xiaomi_watch_s1">Xiaomi Watch S1</string>
+    <string name="pref_title_fossil_hr_nav_foreground">脰n plana gel</string>
+    <string name="pref_summary_fossil_hr_nav_foreground">Navigasyon uygulamas谋n谋n bir navigasyon talimat谋 ald谋臒谋nda otomatik olarak 枚n plana gelip gelmeyece臒i</string>
+    <string name="uploadwatchfaceoperation_in_progress">Saat aray眉z眉 y眉kleniyor</string>
+    <string name="uploadwatchfaceoperation_complete">Saat aray眉z眉 kurulumu tamamland谋</string>
+    <string name="pref_title_fossil_hr_nav_vibrate">Yeni talimatta titre艧im</string>
+    <string name="pref_summary_fossil_hr_nav_vibrate">Saatin her yeni veya de臒i艧tirilen navigasyon talimat谋nda titreyip titremeyece臒i (yaln谋zca uygulama 枚n plandayken)</string>
+    <string name="pref_force_connection_type_description">Ayg谋t谋n谋z谋n Gadgetbridge\'e yan谋t vermemesi durumunda ba臒lant谋 t眉r眉n眉 zorlamay谋 deneyebilirsiniz</string>
+    <string name="pref_test_features_title">脰zellikler</string>
+    <string name="pref_test_features_summary">Bu test ayg谋t谋 i莽in etkinle艧tirilen 枚zellikler</string>
+    <string name="uploadwatchfaceoperation_failed">Saat aray眉z眉 kurulumu ba艧ar谋s谋z</string>
+    <string name="pref_force_connection_type_title">Ba臒lant谋 t眉r眉n眉 zorla</string>
+    <string name="loyalty_cards_sync_title">Sadakat Kartlar谋n谋 E艧zamanla</string>
+    <string name="loyalty_cards_syncing">%d sadakat kart谋 ayg谋ta e艧zamanlan谋yor</string>
+    <string name="pref_activity_full_sync_trigger_summary">T眉m etkinlik verilerinin tam e艧zamanlamas谋n谋 tetikle</string>
+    <string name="pref_activity_full_sync_trigger_title">Tam e艧zamanlama</string>
+    <string name="info_menu_structure_set">GB cinsinden men眉 yap谋s谋 JSON k眉mesi</string>
+    <string name="widget_screen">Widget Ekran谋</string>
+    <string name="widget_missing_parts">L眉tfen t眉m widget\'lar谋 se莽in</string>
+    <string name="prefs_wena3_menu_icon_name_payment">脰deme</string>
+    <string name="pref_title_navigation_apps">Navigasyon uygulamalar谋</string>
+    <string name="pref_navigation_app_osmand">OsmAnd(+)</string>
+    <string name="wake_up_time">Uyan</string>
+    <string name="widget_screen_min_screens">En az %1$s ekran olmal谋</string>
+    <string name="widget_layout_single">1 widget</string>
+    <string name="withings_bt_calibration_next">Sonraki</string>
+    <string name="prefs_home_icon_right_item">Sa臒</string>
+    <string name="prefs_wena3_vibration_strength_item_medium">Orta</string>
+    <string name="devicetype_mi_watch_color_sport">Mi Watch Color Sport</string>
+    <string name="loyalty_cards_sync_options">E艧zamanlama Se莽enekleri</string>
+    <string name="prefs_wena3_title_screen">Ekran Ayarlar谋</string>
+    <string name="prefs_wena3_hint_background_sync">Wena\'n谋n d眉zenli aral谋klarla Gadgetbridge\'in ayg谋ttan etkinlik verilerini indirmesini istemesine izin ver</string>
+    <string name="prefs_wena3_hint_weather_statusbar">Ana ekran谋n sol 眉st k枚艧esinde 艧u anki durum simgesini g枚ster</string>
+    <string name="prefs_wena3_item_weather_statusbar">Durum 脟ubu臒unda Hava Durumu</string>
+    <string name="prefs_wena3_title_alarm">Alarm Ayarlar谋</string>
+    <string name="prefs_wena3_notification_default_vibration">Bildirim Titre艧imi</string>
+    <string name="prefs_wena3_notification_default_call_led">Gelen 脟a臒r谋 LED Rengi</string>
+    <string name="menuitem_alerts">Uyar谋lar</string>
+    <string name="pref_sleep_mode_schedule_title">Uyku Modu Zamanlamas谋</string>
+    <string name="menuitem_running">脟al谋艧谋yor</string>
+    <string name="pref_vitality_score_7_day_summary">Son 7 g眉n i莽inde canl谋l谋k puan谋n谋z 30, 60 veya 100\'e ula艧t谋臒谋nda bir bildirim al谋n</string>
+    <string name="pref_summary_fossil_hr_navigation_instructions">Saat 眉zerinde navigasyon uygulamas谋 davran谋艧谋n谋 yap谋land谋r</string>
+    <string name="loyalty_cards_open_catima">Catima\'y谋 a莽</string>
+    <string name="loyalty_cards">Sadakat Kartlar谋</string>
+    <string name="drag_handle">s眉r眉kleme kolu</string>
+    <string name="prefs_wena3_hint_lift_wrist">Bile臒inize bakt谋臒谋n谋zda ekran谋 a莽</string>
+    <string name="prefs_wena3_title_activity">Etkinlik Ayarlar谋</string>
+    <string name="devicetype_pixoo">Pixoo</string>
+    <string name="pref_vitality_score_title">Canl谋l谋k Puan谋</string>
+    <string name="pref_vitality_score_7_day_title">7 g眉nl眉k ilerleme</string>
+    <string name="widget_layout_top_2_bot_2">2 眉st, 2 alt</string>
+    <string name="prefs_wena3_button_action_name_activity_screen">Etkinlik Ekran谋</string>
+    <string name="loyalty_cards_catima_permissions_title">陌zinler eksik</string>
+    <string name="button_open_menu_companion">Men眉 yard谋mc谋 uygulamas谋n谋 a莽</string>
+    <string name="prefs_home_icon_left_item">Sol</string>
+    <string name="button_reset_menu_structure">Men眉 yap谋s谋n谋 s谋f谋rla</string>
+    <string name="cyan">Camg枚be臒i</string>
+    <string name="blue">Mavi</string>
+    <string name="purple">Mor</string>
+    <string name="white">Beyaz</string>
+    <string name="prefs_wena3_item_background_sync">Arka Plan Etkinlik Veri E艧zamanlamas谋</string>
+    <string name="prefs_wena3_vibration_smart_item">Ak谋ll谋 Titre艧im</string>
+    <string name="prefs_wena3_home_icon_name_wena_pay">Wena Pay</string>
+    <string name="prefs_wena3_menu_icon_hint">Ayarlar simgesi her zaman en sonda g枚sterilir</string>
+    <string name="prefs_wena3_auto_power_off_item">Otomatik Kapanma</string>
+    <string name="prefs_wena3_auto_power_off_turn_off_time_item">Kapanma zaman谋</string>
+    <string name="prefs_wena3_auto_power_off_turn_on_time_item">A莽谋lma zaman谋</string>
+    <string name="prefs_wena3_button_action_item">D眉臒me Eylemi</string>
+    <string name="prefs_wena3_button_action_name_start_timer">Etkin zamanlay谋c谋y谋 ba艧lat</string>
+    <string name="prefs_wena3_receive_calls_hint">Kapat谋l谋rsa, Wena\'da gelen 莽a臒r谋lardan bildirim almazs谋n谋z</string>
+    <string name="prefs_wena3_notification_default_vibration_repetition">Bildirim Titre艧imi Tekrar谋</string>
+    <string name="prefs_wena3_notification_vibration_repetition_1">Bir defa</string>
+    <string name="prefs_wena3_notification_default_call_vibration">Gelen 脟a臒r谋 Titre艧imi</string>
+    <string name="fossil_hr_nav_app_not_installed_notify_title">Navigasyon uygulamas谋 saatte kurulu de臒il</string>
+    <string name="info_menu_structure_removed">Men眉 yap谋s谋 kald谋r谋ld谋</string>
+    <string name="prefs_wena3_vibration_warning">Uyar谋</string>
+    <string name="prefs_wena3_led_none">LED Yok</string>
+    <string name="temperature_scale_cf">S谋cakl谋k 枚l莽e臒i</string>
+    <string name="loyalty_cards_catima_not_installed">Sadakat kartlar谋n谋 y枚netmek i莽in Catima gereklidir</string>
+    <string name="loyalty_cards_sync_summary">Kartlar谋 saate e艧zamanlamak i莽in dokunun</string>
+    <string name="loyalty_cards_catima_not_compatible">Kurulu Catima s眉r眉m眉 Gadgetbridge ile uyumlu de臒il. L眉tfen Catima ve Gadgetbridge\'i en son s眉r眉mlere g眉ncelleyin.</string>
+    <string name="loyalty_cards_sync">E艧zamanla</string>
+    <string name="loyalty_cards_catima">Catima</string>
+    <string name="loyalty_cards_catima_permissions_summary">Gadgetbridge\'in Catima kartlar谋n谋 e艧zamanlamak i莽in okuma izinlerine ihtiyac谋 vard谋r. 陌zinleri vermek i莽in bu d眉臒meye dokunun.</string>
+    <string name="withings_bt_calibration_previous">脰nceki</string>
+    <string name="withings_calibration_text_minutes">艦imdi yelkovan谋 12\'ye hizalamak i莽in kadran谋 kullan谋n.</string>
+    <string name="info_menu_structure_contents">Men眉 yap谋s谋: %s</string>
+    <string name="yellow">Sar谋</string>
+    <string name="prefs_home_icon_center_item">Orta</string>
+    <string name="green">Ye艧il</string>
+    <string name="prefs_wena3_item_rich_design">Zengin Tasar谋m Kullan</string>
+    <string name="prefs_wena3_hint_rich_design">Ana ekran simgelerinin etraf谋na yuvarlat谋lm谋艧 dikd枚rtgenler ekler</string>
+    <string name="prefs_wena3_vibration_strength_item_weak">Zay谋f</string>
+    <string name="prefs_wena3_home_icon_title">Ana Ekran Simgeleri</string>
+    <string name="prefs_wena3_vibration_strength_item">Titre艧im G眉c眉</string>
+    <string name="prefs_wena3_home_icon_name_clock">Ge莽erli Saat</string>
+    <string name="prefs_wena3_status_page_title">Durum Sayfas谋 S谋ralamas谋</string>
+    <string name="prefs_wena3_notification_vibration_repetition_4">D枚rt defa</string>
+    <string name="pref_summary_osmand_packagename">Ba臒lan谋lacak OsmAnd s眉r眉m眉n眉 se莽mek i莽in kullan谋l谋r</string>
+    <string name="prefs_wena3_vibration_step_down">Al莽alan</string>
+    <string name="pref_navigation_app_gmaps">Google Haritalar</string>
+    <string name="menuitem_stats">陌statistikler</string>
+    <string name="widget_screen_delete_confirm_description">\'%1$s\' 枚gesini silmek istedi臒inizden emin misiniz?</string>
+    <string name="widget_layout_top_1_bot_2">1 眉st, 2 alt</string>
+    <string name="widget_layout">Widget yerle艧imi</string>
+    <string name="widget_unknown_workout">Bilinmeyen egzersiz - %s</string>
+    <string name="pref_title_fossil_hr_navigation_instructions">Navigasyon talimatlar谋</string>
+    <string name="notification_channel_connection_status_name">Ba臒lant谋 Durumu</string>
+    <string name="prefs_wena3_vibration_strength_item_strong">G眉莽l眉</string>
+    <string name="prefs_wena3_home_icon_name_qrio">Qrio</string>
+    <string name="prefs_wena3_home_icon_name_edy">Edy bakiyesi</string>
+    <string name="prefs_wena3_home_icon_name_energy">V眉cut Enerjisi</string>
+    <string name="prefs_wena3_home_icon_name_suica">Suica bakiyesi</string>
+    <string name="prefs_wena3_home_icon_name_calories">Kalori</string>
+    <string name="prefs_wena3_day_start_hour_item">G眉n Ba艧lang谋c谋</string>
+    <string name="prefs_wena3_auto_power_off_hint">Ayg谋t belirtilen zamanlarda otomatik olarak kapanacak ve a莽谋lacakt谋r</string>
+    <string name="widget_move_down">A艧a臒谋 ta艧谋</string>
+    <string name="widget_screen_x">Ekran %s</string>
+    <string name="widget_move_up">Yukar谋 ta艧谋</string>
+    <string name="pref_theme_dynamic_colors_not_available_warning">De臒i艧ken renkler ayg谋t谋n谋zda kullan谋labilir de臒il, yaln谋zca Android 12 ve 眉st眉 bu i艧levi destekler. Gadgetbridge 枚ntan谋ml谋 Material 3 renklerini kullanacak.</string>
+    <string name="prefs_wena3_smart_alarm_margin_item">Ak谋ll谋 Alarm Fark谋</string>
+    <string name="prefs_wena3_notification_settings_title">Bildirim Ayarlar谋</string>
+    <string name="prefs_wena3_notification_vibration_repetition_3">脺莽 defa</string>
+    <string name="prefs_wena3_notification_default_call_vibration_repetition">Gelen 脟a臒r谋 Titre艧imi Tekrar谋</string>
+    <string name="prefs_wena3_vibration_continuous">S眉rekli</string>
+    <string name="prefs_wena3_vibration_basic">Basit</string>
+    <string name="prefs_wena3_notification_vibration_repetition_2">陌ki defa</string>
+    <string name="call_rejection_method_reject">Reddet</string>
+    <string name="call_rejection_method_ignore">Yoksay (sessiz)</string>
+    <string name="serial_number">Seri Numaras谋</string>
+    <string name="prefs_wena3_hint_large_font">Takvim, bildirimler vb. yerlerde yaz谋 tipi boyutunu art谋r.</string>
+    <string name="prefs_wena3_notification_per_app_settings_title">Uygulama Ba艧谋 Bildirim Ayarlar谋</string>
+    <string name="prefs_wena3_notification_default_led">Bildirim LED Rengi</string>
+    <string name="prefs_wena3_vibration_rapid">H谋zl谋</string>
+    <string name="prefs_wena3_vibration_triple">脺莽l眉</string>
+    <string name="prefs_wena3_vibration_siren">Siren</string>
+    <string name="fossil_hr_nav_app_not_installed_notify_text">Navigasyon ba艧lat谋ld谋 ancak navigasyon uygulamas谋 saatte kurulu de臒il. L眉tfen Uygulama Y枚neticisi\'nden kurun.</string>
+    <string name="pref_title_prefix_notification_with_app">Bildirimdeki uygulama ad谋</string>
+    <string name="pref_summary_prefix_notification_with_app">Bildirim ba艧l谋臒谋n谋n 枚n眉ne kaynak uygulaman谋n ad谋n谋 ekle</string>
+    <string name="pref_title_osmand_packagename">OsmAnd paket ad谋</string>
+    <string name="devicetype_xiaomi_watch_s3">Xiaomi Watch S3</string>
+    <string name="devicetype_xiaomi_watch_s1_pro">Xiaomi Watch S1 Pro</string>
+    <string name="pref_vitality_score_daily_summary">G眉n i莽in en y眉ksek canl谋l谋k puan谋 say谋s谋na ula艧t谋臒谋n谋zda bir bildirim al谋n</string>
+    <string name="pref_vitality_score_daily_title">G眉nl眉k ilerleme</string>
+    <string name="widget_screen_delete_confirm_title">Widget ekran谋n谋 sil</string>
+    <string name="uploading_watchface">Saat aray眉z眉 y眉kleniyor鈥�</string>
+    <string name="pref_force_connection_type_bt_classic">Bluetooth Klasik</string>
+    <string name="pref_force_connection_type_auto">Otomatik</string>
+    <string name="not_set">Ayarlanmad谋</string>
+    <string name="prefs_wena3_menu_icon_title">Men眉 Simgeleri</string>
+    <string name="prefs_wena3_receive_calls_title">Gelen 脟a臒r谋 Ayarlar谋</string>
+    <string name="withings_calibration_text_hours">Akrebi 12\'ye hizalamak i莽in l眉tfen a艧a臒谋daki kadran谋 kullan谋n.</string>
+    <string name="withings_calibration_text_activity_target">Son olarak etkinlik ibresini %100\'e hizalay谋n. L眉tfen bu ibrenin yaln谋zca saat y枚n眉nde hareket etti臒ini unutmay谋n.</string>
+    <string name="pref_activity_full_sync_trigger_warning">Bu, ayg谋ttaki t眉m etkinlik verilerinin tam bir e艧zamanlamas谋n谋 tetikleyecektir. Tamamlanmas谋 birka莽 dakika s眉rebilir.</string>
+    <string name="prefs_wena3_home_icon_name_pedometer">Ad谋m Say谋s谋</string>
+    <string name="prefs_wena3_home_icon_name_riiiver">Riiiver</string>
+    <string name="prefs_wena3_button_action_name_qrio_unlock">Qrio Killit A莽</string>
+    <string name="prefs_wena3_button_action_name_qrio_lock">Qrio Kilitle</string>
+    <string name="prefs_wena3_receive_calls_item">脟a臒r谋 Bildirimlerini Al</string>
+    <string name="prefs_wena3_vibration_step_up">Y眉kselen</string>
+    <string name="temperature_scale_cf_summary">Ayg谋t谋n Santigrat veya Fahrenhayt 枚l莽e臒inden hangisini kullanaca臒谋n谋 se莽in.</string>
+    <string name="temperature_scale_fahrenheit">Fahrenhayt</string>
+    <string name="pref_call_rejection_method_title">脟a臒r谋 reddetme y枚ntemi</string>
+    <string name="pref_call_rejection_method_summary">Gelen bir 莽a臒r谋 saatten reddedildi臒inde ger莽ekle艧tirilecek eylem</string>
+    <string name="pref_title_navigation_prefs">Navigasyon tercihleri</string>
+    <string name="menuitem_focus">Odak</string>
+    <string name="widget_layout_top_2_bot_1">2 眉st, 1 alt</string>
+    <string name="widget_subtype">Widget Alt T眉r眉</string>
+    <string name="widget_layout_two">2 widget</string>
+    <string name="temperature_scale_celsius">Santigrat</string>
+    <string name="bedtime">Yatma Zaman谋</string>
+    <string name="prefs_wena3_vibration_none">Titre艧im Yok</string>
+    <string name="warning_missing_notification_permission">Eksik izin nedeniyle devam eden bildirim g枚nderilemedi</string>
+    <string name="error_invalid_menu_structure">Ge莽ersiz men眉 yap谋s谋 JSON</string>
+    <string name="info_fossil_rebuild_watchface_custom_menu">脰zel men眉 i莽in l眉tfen saat aray眉z眉n眉z眉 yeniden olu艧turun</string>
+    <string name="devicetype_miband2_hrx">Mi Band HRX</string>
+    <string name="activity_info">Etkinlik bilgileri</string>
+    <string name="device_state_waiting_scan">Ayg谋t taramas谋 bekleniyor</string>
+    <string name="auto_reconnect_ble_scan_title">BLE taramas谋 ile yeniden ba臒lan</string>
+    <string name="auto_reconnect_ble_scan_summary">K枚r ba臒lant谋 denemeleri yerine ayg谋t taramas谋 i莽in bekle</string>
+    <string name="prompt_restart_gadgetbridge">Etkili olmas谋 i莽in l眉tfen GB\'yi yeniden ba艧lat谋n.</string>
+    <string name="waiting_for_bluetooth">Bluetooth bekleniyor鈥�</string>
+    <string name="error_scan_failed">Tarama ba艧ar谋s谋z: %d</string>
+    <string name="scan_not_scanning">Taranm谋yor</string>
+    <string name="scan_scanning_all_devices">T眉m ayg谋tlar taran谋yor</string>
+    <string name="scan_scanning_single_device">1 ayg谋t taran谋yor</string>
+    <string name="scan_scanning_multiple_devices">%d ayg谋t taran谋yor</string>
+    <string name="notification_channel_scan_service_name">Tarama hizmeti</string>
+    <string name="devicetype_miband8active">Xiaomi Smart Band 8 Active</string>
+    <string name="temperature">S谋cakl谋k</string>
+    <string name="pref_wide_area_tap_summary">Yanaklar ve kulaklar aras谋ndaki dokunu艧lar谋 tan谋</string>
+    <string name="pref_wide_area_tap_title">Geni艧 alan dokunu艧u</string>
+    <string name="pref_adaptive_volume_control_summary">Ortam sesi y眉ksek oldu臒unda sesi otomatik olarak art谋r</string>
+    <string name="pref_adaptive_volume_control_title">Uyarlanabilir ses seviyesi denetimi</string>
+    <string name="devicetype_sony_linkbuds">Sony LinkBuds</string>
+    <string name="pref_header_sony_sound_control">Ses Denetimi</string>
+    <string name="devicetype_amazfit_bip3">Amazfit Bip 3</string>
+    <string name="fw_upgrade_notice_amazfitbip3">Amazfit Bip 3 ayg谋t谋n谋za %s 眉r眉n yaz谋l谋m谋n谋 kurmak 眉zeresiniz.
+\n
+\nL眉tfen 枚nce .fw dosyas谋n谋, sonra .res dosyas谋n谋 kurun. Saatiniz .fw dosyas谋n谋 kurduktan sonra yeniden ba艧lat谋lacak.
+\n
+\nNot: Daha 枚nce kurulanla tamamen ayn谋ysa .res dosyas谋n谋 kurman谋z gerekmez.
+\n
+\n陌LERLEMEN陌Z DURUMUNDA R陌SK S陌ZE A陌TT陌R!</string>
+    <string name="weeksleepchart_sleep_a_week_or_month">Haftal谋k/ayl谋k uyku</string>
+    <string name="weekstepschart_steps_a_week_or_month">Haftal谋k/ayl谋k ad谋m</string>
+    <string name="devicetype_huawei_watchgt4">Huawei Watch GT 4</string>
+    <string name="busy_task_fetch_sports_details_interrupted">Spor bilgilerinin getirilmesi yar谋da kesildi</string>
+    <string name="devicetype_huawei_watchultimate">Huawei Watch Ultimate</string>
+    <string name="companion_pairing_request_title">Yard谋mc谋 ayg谋t</string>
+    <string name="companion_pairing_request_description">Bu ayg谋t yard谋mc谋 olarak e艧le艧tirilsin mi?
+\n
+\nBu, ayg谋t bulma gibi baz谋 i艧levler i莽in tavsiye edilir ve daha iyi bir ba臒lant谋 sa臒lar.</string>
+    <string name="unbind_before_pair_title">Zaten ba臒l谋</string>
+    <string name="unbind_before_pair_message">Bu ayg谋t Android ayarlar谋nda zaten ba臒l谋, bu da baz谋 ayg谋tlar i莽in e艧le艧tirmenin ba艧ar谋s谋z olmas谋na neden olabilir.
+\n
+\nAyg谋t谋 eklemek ba艧ar谋s谋z olursa, l眉tfen Android ayarlar谋ndan kald谋r谋n ve tekrar deneyin.</string>
+    <string name="devicesetting_scannable_debounce">Taranabilir hata giderme zaman a艧谋m谋 (saniye)</string>
+    <string name="devicesetting_scannable_rssi">En d眉艧眉k RSSI e艧i臒i</string>
+    <string name="devicesetting_scannable_debounce_summary">Tarand谋ktan sonra ayg谋t tarand谋 olarak kal谋r ve belirtilen s眉re boyunca yok say谋l谋r</string>
+    <string name="devicetype_scannable">Taranabilir ayg谋t</string>
+    <string name="state_scanned">Tarand谋</string>
+    <string name="devicesetting_scannable_minimum_unseen">En k谋sa g枚r眉lmeme s眉resi (saniye)</string>
+    <string name="devicesetting_scannable_minimum_unseen_summary">Tarand谋ktan sonra, ayg谋t谋n tekrar kaydedilmeden 枚nce bu s眉re boyunca g枚r眉lmemesi gerekir</string>
+    <string name="devicesetting_scannable_rssi_summary">Alg谋lama i莽in en d眉艧眉k RSSI e艧i臒i</string>
+    <string name="swolfAvg">Ortalama swolf</string>
+    <string name="swolfMax">En y眉ksek swolf</string>
+    <string name="swolfMin">En d眉艧眉k swolf</string>
+    <string name="stepRateSum">Ad谋m h谋z谋 toplam谋</string>
+    <string name="stepRateAvg">Ortalama ad谋m h谋z谋</string>
+    <string name="stepLengthAvg">Ortalama Ad谋m Uzunlu臒u</string>
+    <string name="groundContactTimeAvg">Ortalama yerle temas s眉resi</string>
+    <string name="impactAvg">Ortalama etki</string>
+    <string name="impactMax">En y眉ksek etki</string>
+    <string name="swingAngleAvg">Ortalama sal谋n谋m a莽谋s谋</string>
+    <string name="foreFootLandings">脰n ayak ini艧i</string>
+    <string name="midFootLandings">Orta ayak ini艧i</string>
+    <string name="backFootLandings">Arka ayak ini艧i</string>
+    <string name="eversionAngleAvg">Ortalama ters d枚nme a莽谋s谋</string>
+    <string name="eversionAngleMax">En y眉ksek ters d枚nme a莽谋s谋</string>
+    <string name="fmtPaceDistance">H谋z %d mesafe</string>
+    <string name="fmtPaceType">H谋z %d t眉r</string>
+    <string name="fmtPacePace">H谋z %d h谋z</string>
+    <string name="fmtPaceCorrection">H谋z %d d眉zeltme</string>
+    <string name="fmtPaceTypeAverage">H谋z T眉r眉 %d ortalama</string>
+    <string name="unknownDataEncountered">Bilinmeyen veriyle kar艧谋la艧谋ld谋</string>
+    <string name="milliseconds">milisaniye</string>
+    <string name="degrees">derece</string>
+    <string name="Pace">H谋z</string>
+    <string name="RunningForm">Ko艧u Bi莽imi</string>
+    <string name="pref_force_enable_heartrate_support">Kalp ritmi deste臒ini zorla</string>
+    <string name="pref_force_enable_heartrate_support_summary">Kalp ritmi deste臒ini etkinle艧tirmeye zorla.
+\nKULLANMANIZ DURUMUNDA RI虈SK SI虈ZE AI虈TTI虈R</string>
+    <string name="pref_force_enable_spo2_support">SpO2 deste臒ini zorla</string>
+    <string name="pref_force_enable_spo2_support_summary">SpO2 deste臒ini etkinle艧tirmeye zorla.
+\nKULLANMANIZ DURUMUNDA RI虈SK SI虈ZE AI虈TTI虈R</string>
+    <string name="activity_type_worn">Giyildi</string>
+    <string name="pref_dashboard_cards_title">Kartlarda widget\'lar谋 g枚ster</string>
+    <string name="error_showing_changelog">De臒i艧iklik g眉nl眉臒眉 g枚sterilirken hata olu艧tu</string>
+    <string name="dashboard_settings">G枚sterge tablosu ayarlar谋</string>
+    <string name="pref_dashboard_cards_summary">G枚sterge tablosunda widget\'lar谋n etraf谋na kartlar 莽iz</string>
+    <string name="pref_dashboard_first_title">脰nce g枚sterge tablosunu g枚ster</string>
+    <string name="pref_dashboard_first_summary">Gadgetbridge ba艧lad谋臒谋nda ayg谋tlar ekran谋 yerine g枚sterge tablosunu g枚ster</string>
+    <string name="pref_dashboard_widget_settings">Widget ayarlar谋</string>
+    <string name="pref_dashboard_widget_today_title">Etkinlik 莽izelgesi</string>
+    <string name="pref_dashboard_all_devices_title">T眉m ayg谋tlar</string>
+    <string name="pref_dashboard_widget_today_hr_interval_title">Kalp ritmi aral谋臒谋</string>
+    <string name="pref_dashboard_widget_double_size_summary">Widget\'谋n g枚sterge tablosunda iki s眉tun kaplamas谋na izin ver</string>
+    <string name="pref_dashboard_widget_show_legend_summary">Widget\'谋n alt谋nda renkleri a莽谋klayan bir a莽谋klama g枚ster</string>
+    <string name="pref_dashboard_all_devices_summary">G枚sterge tablosundaki toplamlar i莽in eklenen t眉m ayg谋tlardan gelen etkinlik verilerini birle艧tir</string>
+    <string name="bottom_nav_dashboard">G枚sterge tablosu</string>
+    <string name="bottom_nav_devices">Ayg谋tlar</string>
+    <string name="pref_dashboard_widget_today_24h_title">24 saat modu</string>
+    <string name="pref_dashboard_widget_double_size_title">陌ki kat谋 boyut</string>
+    <string name="pref_dashboard_widget_goals_chart_title">Hedefler 莽izelgesi</string>
+    <string name="pref_dashboard_devices_to_include">Dahil edilecek ayg谋tlar</string>
+    <string name="pref_dashboard_select_devices_title">Ayg谋tlar谋 se莽...</string>
+    <string name="pref_dashboard_widgets_order_summary">Hangi widget\'lar谋n etkinle艧tirilece臒ini ve g枚sterge tablosunda hangi s谋rada g枚r眉nt眉lenece臒ini se莽in</string>
+    <string name="pref_dashboard_widget_today_24h_summary">Etkinli臒i iki tane 12 saatlik daire yerine bir tane 24 saatlik daire i莽inde g枚ster</string>
+    <string name="pref_dashboard_select_devices_summary">G枚sterge tablosundaki toplamlar i莽in belirli ayg谋tlardan gelen etkinlik verilerini birle艧tir</string>
+    <string name="pref_dashboard_widget_today_hr_interval_summary">Her ba艧ar谋l谋 kalp ritmi 枚l莽眉m眉nden sonra grafi臒in \'yorgun\' olarak g枚sterdi臒i dakika miktar谋</string>
+    <string name="pref_dashboard_widget_show_legend_title">A莽谋klamalar谋 g枚ster</string>
+    <string name="pref_auto_reply_calls_summary">Telefon gelen aramalar谋 otomatik olarak yan谋tlayacakt谋r</string>
+    <string name="pref_auto_reply_calls_delay_summary">Araman谋n otomatik olarak yan谋tlanaca臒谋 saniye say谋s谋</string>
+    <string name="pref_auto_reply_calls_title">Telefon aramalar谋n谋 otomatik yan谋tla</string>
+    <string name="pref_auto_reply_calls_delay_title">Otomatik Yan谋t Gecikmesi</string>
+    <string name="pref_header_audio">Ses</string>
+    <string name="pref_header_generic">Genel</string>
+    <string name="pref_title_notification_wake_on_open">Otomatik uyand谋r ve kilidi a莽</string>
+    <string name="pref_summary_notification_wake_on_open">Ayg谋t bir OPEN (A脟) yan谋t谋 g枚nderdi臒inde Android ayg谋t谋n谋 uyand谋r谋n ve kilidini a莽谋n. G眉venilir bir durumda olmas谋 gerekir.</string>
+    <string name="pref_speak_notifications_aloud_summary">Bildirimler kulakl谋klardan y眉ksek sesle okunacakt谋r</string>
+    <string name="pref_speak_notifications_aloud_title">Bildirimleri Sesli Oku</string>
+    <string name="devicetype_redmi_watch_4">Redmi Watch 4</string>
+    <string name="widget_layout_top_wide_bot_large">Yukar谋da geni艧, a艧a臒谋da b眉y眉k</string>
+    <string name="widget_layout_top_large_bot_wide">Yukar谋da b眉y眉k, a艧a臒谋da geni艧</string>
+    <string name="widget_name_colored_tile">%1$s (renkli d枚艧eme)</string>
+    <string name="widget_name_untitled">Ba艧l谋ks谋z widget (%1$s)</string>
+    <string name="pref_header_calls_and_notifications">脟a臒r谋lar ve bildirimler</string>
+    <string name="armenian">Ermenice</string>
+    <string name="pref_summary_bottom_navigation_bar_off">Yaln谋zca yatay kayd谋rma kullanarak ana ekranlar aras谋nda ge莽i艧 yap谋n</string>
+    <string name="pref_summary_bottom_navigation_bar_on">Gezinme 莽ubu臒unu veya yatay kayd谋rmay谋 kullanarak ana ekranlar aras谋nda ge莽i艧 yap谋n</string>
+    <string name="pref_title_bottom_navigation_bar">Alt gezinme 莽ubu臒u</string>
+    <string name="pref_dashboard_widget_today_upside_down_title">Gece yar谋s谋 altta</string>
+    <string name="pref_dashboard_widget_today_upside_down_summary">24 saat modunda, gece yar谋s谋n谋 grafi臒in alt k谋sm谋na, g眉n ortas谋n谋 ise grafi臒in 眉st k谋sm谋na 莽iz</string>
+    <string name="pref_sleepasandroid_features_summary">Destek ayg谋ttan ayg谋ta farkl谋l谋k g枚sterir</string>
+    <string name="pref_sleepasandroid_feat_alarms">Alarmlar</string>
+    <string name="pref_sleepasandroid_slot_title">Alarm yuvas谋</string>
+    <string name="alarm_slot_reset">Alarm yuvas谋 枚ntan谋ml谋 olarak ayarland谋</string>
+    <string name="pref_sleepasandroid_feat_heartrate">Kalp ritmi</string>
+    <string name="pref_sleepasandroid_feat_oximetry">Oksimetri</string>
+    <string name="pref_sleepasandroid_feat_spo2">SPO2</string>
+    <string name="sleepasandroid_settings">Sleep As Android</string>
+    <string name="pref_sleepasandroid_slot_summary">Alarmlar谋 ayarlarken hangi alarm yuvas谋n谋n kullan谋laca臒谋</string>
+    <string name="pref_sleepasandroid_feat_notifications">Bildirimler</string>
+    <string name="pref_sleepasandroid_feat_movement">陌vme枚l莽er</string>
+    <string name="pref_sleepasandroid_enable_summary">Sleep As Android b眉t眉nle艧mesini etkinle艧tir</string>
+    <string name="pref_sleepasandroid_device_title">Sa臒lay谋c谋 ayg谋t</string>
+    <string name="pref_sleepasandroid_device_summary">Sleep As Android veri sa臒lay谋c谋s谋 olarak ayg谋t谋 se莽in</string>
+    <string name="pref_sleepasandroid_features_title">脰zellikler</string>
+    <string name="pref_summary_huawei_account">E艧le艧tirme i艧leminde kullan谋lan Huawei hesab谋. Ayarlanmas谋, fabrika ayarlar谋na s谋f谋rlamadan e艧le艧tirmeyi sa臒lar.</string>
+    <string name="pref_title_huawei_account">Huawei Hesab谋</string>
+    <string name="serbian">S谋rp莽a</string>
+    <string name="watchface_resolution_doesnt_match">Saat aray眉z眉 莽枚z眉n眉rl眉臒眉 ayg谋t ekran谋yla e艧le艧miyor. Saat aray眉z眉 %1$s, ayg谋t ekran谋 %2$s</string>
+    <string name="cyclingPowerMin">En d眉艧眉k bisiklet g眉c眉</string>
+    <string name="cyclingPowerAverage">Ortalama bisiklet g眉c眉</string>
+    <string name="cyclingPowerMax">En y眉ksek bisiklet g眉c眉</string>
+    <string name="devicetype_huawei_watchfit2">Huawei Watch Fit 2</string>
+    <string name="huawei_trusleep_warning">Uyar谋: Bunu etkinle艧tirmek t眉m uykunun Gadgetbridge\'de hafif uyku olarak g枚r眉nmesine neden olacakt谋r! Bunu kabul ediyorsan谋z buraya t谋klay谋n.</string>
+    <string name="pref_touch_tone_summary">Kulakl谋臒a dokunuldu臒unda bir ses 莽alar</string>
+    <string name="pref_wearing_tone">Takma Sesi</string>
+    <string name="devicetype_soundcore_liberty3_pro">Soundcore Liberty 3 Pro</string>
+    <string name="pref_touch_tone">Dokunma Sesi</string>
+    <string name="pref_wearing_tone_summary">Kulakl谋k tak谋ld谋臒谋nda bir ses 莽alar</string>
+    <string name="pref_adaptive_noise_cancelling_title">Uyarlanabilir ANC</string>
+    <string name="pref_adaptive_noise_cancelling_summary">Ortam ses seviyesine ba臒l谋 olarak ANC\'nin g眉c眉n眉 otomatik olarak ayarla</string>
+    <string name="devicetype_huawei_band9">Huawei Band 9</string>
+    <string name="devicetype_huawei_watchfit3">Huawei Watch Fit 3</string>
+    <string name="devicetype_huawei_watch4pro">Huawei Watch 4 (Pro)</string>
+    <string name="preferences_qhybrid_settings_summary">Q Hybrid saatler i莽in eski ayarlar</string>
+    <string name="device_name_cycling_sensor">D枚ng眉 sens枚r眉</string>
+    <string name="devicetype_cycling_sensor">D枚ng眉 h谋z谋 sens枚r眉</string>
+    <string name="title_cycling">Bisiklet s眉rme</string>
+    <string name="pref_summary_wheel_diameter">陌n莽 cinsinden tekerlek 莽ap谋. Genellikle 29, 27.5 veya 26.</string>
+    <string name="pref_title_wheel_diameter">Tekerlek 莽ap谋</string>
+    <string name="pref_title_cycling_persistence_interval">S眉reklilik aral谋臒谋</string>
+    <string name="chart_cycling_point_label_distance">Bug眉n: %.1f km
+\nToplam: %.1f km</string>
+    <string name="chart_cycling_point_label_speed">%.1f km/sa</string>
+    <string name="device_name_bicycle_sensor">Bisiklet sens枚r眉</string>
+    <string name="pref_summary_cycling_persistence_interval">O anki bisiklet verilerinin veri taban谋na yaz谋lmas谋 gereken saniye cinsinden aral谋k</string>
+    <string name="pref_speak_notifications_focus_exclusive_summary_off">Bildirim konu艧ulurken di臒er uygulamalar谋n oynatma ses seviyesi d眉艧眉r眉lecektir</string>
+    <string name="open_camera">Kameray谋 A莽</string>
+    <string name="toast_camera_permission_required">Bu i艧lev i莽in kamera izni gereklidir.</string>
+    <string name="toast_camera_support_required">Bu i艧lev i莽in kamera deste臒i gereklidir.</string>
+    <string name="pref_speak_notifications_focus_exclusive_title">Di臒er uygulamalar谋n sesini duraklat</string>
+    <string name="toast_setting_requires_reconnect">Bu ayar yeniden ba臒land谋ktan sonra ge莽erli olacak</string>
+    <string name="toast_camera_photo_taken">Foto臒raf 莽ekildi ve kaydedildi: %s</string>
+    <string name="pref_speak_notifications_focus_exclusive_summary_on">Bildirim konu艧ulurken di臒er uygulamalardan oynatma duraklat谋lacakt谋r</string>
+    <string name="devicetype_amazfit_bip5_unity">Amazfit Bip 5 Unity</string>
+    <string name="pref_agps_status">AGPS Durumu</string>
+    <string name="agps_status_current">Ge莽erli</string>
+    <string name="agps_status_error">Hata</string>
+    <string name="devicetype_garmin_venu_3">Garmin Venu 3</string>
+    <string name="devicetype_garmin_instinct_solar">Garmin Instinct Solar</string>
+    <string name="devicetype_garmin_instinct_2s">Garmin Instinct 2S</string>
+    <string name="devicetype_garmin_instinct_2_soltac">Garmin Instinct 2 SolTac</string>
+    <string name="devicetype_garmin_instinct_crossover">Garmin Instinct Crossover</string>
+    <string name="devicetype_garmin_forerunner_245">Garmin Forerunner 245</string>
+    <string name="devicetype_garmin_swim_2">Garmin Swim 2</string>
+    <string name="devicetype_garmin_vivoactive_4">Garmin V铆voactive 4</string>
+    <string name="none">Yok</string>
+    <string name="garmin_agps_url_i">AGPS %1$d URL\'si</string>
+    <string name="no_folder_selected">Klas枚r se莽ilmedi</string>
+    <string name="folder_is_empty">Klas枚r bo艧</string>
+    <string name="folder">Klas枚r</string>
+    <string name="url">URL</string>
+    <string name="devicetype_garmin_vivomove_hr">Garmin V铆vomove HR</string>
+    <string name="agps_status_missing">Eksik</string>
+    <string name="agps_status_pending">Bekliyor</string>
+    <string name="devicetype_garmin_vivomove_style">Garmin V铆vomove Style</string>
+    <string name="devicetype_garmin_instinct_2_solar">Garmin Instinct 2 Solar</string>
+    <string name="devicetype_garmin_vivoactive_4s">Garmin V铆voactive 4S</string>
+    <string name="devicetype_garmin_vivoactive_5">Garmin V铆voactive 5</string>
+    <string name="garmin_agps_local_file">Yerel dosya</string>
+    <string name="pref_summary_garmin_default_reply_suffix">Gadgetbridge\'de ayarlanan son eke ek olarak eklenir</string>
+    <string name="pref_title_garmin_default_reply_suffix">脰nceden tan谋mlanan yan谋t son ekini kullan</string>
+    <string name="pref_garmin_agps_help">A艧a臒谋daki liste, saat taraf谋ndan AGPS g眉ncellemeleri i莽in istenen t眉m URL\'leri i莽erir. Telefonun depolama alan谋ndan, g眉ncelleme istedi臒inde saate g枚nderilecek bir dosya se莽ebilirsiniz.</string>
+    <string name="copied_to_clipboard">Panoya kopyaland谋</string>
+    <string name="devicetype_garmin_venu_2_plus">Garmin Venu 2 Plus</string>
+    <string name="devicetype_garmin_vivosmart_5">Garmin V铆vosmart 5</string>
+    <string name="devicetype_garmin_instinct_2s_solar">Garmin Instinct 2S Solar</string>
+    <string name="devicetype_garmin_epix_pro">Garmin Epix Pro</string>
+    <string name="devicetype_garmin_instinct_2x_solar">Garmin Instinct 2X Solar</string>
+    <string name="intent_api_broadcast_activity_sync_title">Etkinlik e艧zamanlamas谋 bitti臒inde yay谋n g枚nder</string>
+    <string name="intent_api_broadcast_activity_sync_summary">Herhangi bir ayg谋t i莽in etkinlik e艧zamanlamas谋 bitti臒inde bir ama莽 yay谋nlay谋n</string>
+    <string name="devicetype_garmin_fenix_6_sapphire">Garmin Fenix 6 Sapphire</string>
+    <string name="loading">Y眉kleniyor鈥�</string>
+    <string name="toggle_debug_mode">Hata ay谋klama modunu a莽/kapat</string>
+    <string name="share_debug_info">Hata ay谋klama bilgilerini payla艧</string>
+    <string name="realtime_settings">Ger莽ek zaman ayarlar谋</string>
+    <string name="min_val">En d眉艧眉k: %d</string>
+    <string name="activity_prefs_height_inches">陌n莽 cinsinden y眉kseklik</string>
+    <string name="unsupported">Desteklenmiyor</string>
+    <string name="max_val">En y眉ksek: %d</string>
+    <string name="devicetype_garmin_venu_3s">Garmin Venu 3S</string>
+    <string name="devicetype_garmin_fenix_7_pro">Garmin Fenix 7 Pro</string>
+    <string name="notif_battery_full_title">Ayg谋t pili dolu!</string>
+    <string name="notif_battery_full">%1$s pili dolu</string>
+    <string name="battery_i">Pil %d</string>
+    <string name="devicetype_garmin_fenix_7s">Garmin Fenix 7S</string>
+    <string name="notification_channel_full_battery_name">Pil dolu</string>
+    <string name="battery_full_threshold">Pil dolu e艧i臒i</string>
+    <string name="default_percentage">脰ntan谋ml谋 (%%%1$d)</string>
+    <string name="battery_percentage_str">%%%1$s</string>
+    <string name="authentication_failed_check_key">Kimlik do臒rulama ba艧ar谋s谋z oldu, l眉tfen kimlik do臒rulama anahtar谋n谋 g枚zden ge莽irin</string>
+    <string name="show_in_notification">Bildirimde g枚ster</string>
+    <string name="battery_low_notify_enabled">D眉艧眉k pil durumunda bildir</string>
+    <string name="battery_low_threshold">D眉艧眉k pil e艧i臒i</string>
+    <string name="battery_full_notify_enabled">Pil dolu oldu臒unda bildir</string>
+    <string name="devicetype_garmin_forerunner_255">Garmin Forerunner 255</string>
+    <string name="devicetype_garmin_forerunner_265">Garmin Forerunner 265</string>
+    <string name="devicetype_hama_fit6900">Hama Fit6900</string>
+    <string name="devicetype_garmin_venu_2">Garmin Venu 2</string>
+    <string name="pref_fetch_unknown_files_title">Bilinmeyen dosyalar谋 al</string>
+    <string name="pref_fetch_unknown_files_summary">Bilinmeyen etkinlik dosyalar谋n谋 saatten al谋n. Bunlar i艧lenmeyecek, ancak telefona kaydedilecektir.</string>
+    <string name="dashboard_calendar_month_goals_reached_title">Ula艧谋lan ad谋m hedefi y眉zdesi</string>
+    <plurals name="amount_of_days">
+        <item quantity="one">%d g眉n</item>
+        <item quantity="other">%d g眉n</item>
+    </plurals>
+    <string name="devicetype_garmin_forerunner_255s">Garmin Forerunner 255S</string>
+    <string name="devicetype_garmin_instinct">Garmin Instinct</string>
+    <string name="devicetype_moondrop_space_travel">Moondrop Space Travel</string>
+    <string name="moondrop_touch_earbud">Kulaki莽i kulakl谋k</string>
+    <string name="moondrop_touch_earbud_both">Her ikisi</string>
+    <string name="moondrop_touch_trigger">Tetik</string>
+    <string name="moondrop_touch_action_play_pause">Oynat/Duraklat</string>
+    <string name="moondrop_touch_action_call_pick_hang">脟a臒r谋 Yan谋tla/Kapat</string>
+    <string name="moondrop_touch_action_call_start">Arama Ba艧lat</string>
+    <string name="moondrop_touch_action_assistant">Sesli Yard谋mc谋y谋 Tetikle</string>
+    <string name="moondrop_touch_trigger_long_press_1s">Uzun Bas (1sn)</string>
+    <string name="moondrop_touch_trigger_long_press_3s">Uzun Bas (3sn)</string>
+    <string name="moondrop_equalizer_preset_reference">Referans</string>
+    <string name="moondrop_equalizer_preset_basshead">Bas kafa</string>
+    <string name="moondrop_equalizer_preset_monitor">Ekran</string>
+    <string name="moondrop_touch_action_anc_mode">Etkin G眉r眉lt眉 Engelleme Modunu De臒i艧tir</string>
+    <string name="devicetype_garmin_fenix_6">Garmin Fenix 6</string>
+    <string name="devicetype_garmin_fenix_5">Garmin Fenix 5</string>
+    <string name="devicetype_garmin_vivoactive_3">Garmin V铆voactive 3</string>
+    <string name="pref_battery_polling_enable">Pil sorgulamas谋n谋 etkinle艧tir</string>
+    <string name="proprietary_app_warning">Bu 枚zellik, 枚zg眉r olmayan bir uygulaman谋n kurulmas谋n谋 gerektirir</string>
+    <string name="pref_battery_polling_configuration">Pil sorgulama yap谋land谋rmas谋</string>
+    <string name="pref_battery_polling_summary">Bu en iyi 莽abad谋r ve 莽e艧itli nedenlerle gecikebilir</string>
+    <string name="battery_polling_failed_start">Pil sorgulamas谋 ba艧lat谋lamad谋</string>
+    <string name="devicetype_garmin_vivomove_trend">Garmin V铆vomove Trend</string>
+    <string name="pref_battery_polling_interval">Pil sorgulama aral谋臒谋</string>
+    <string name="pref_battery_polling_interval_format">her %1$s dakika</string>
+    <string name="devicetype_garmin_vivosport">Garmin V铆vosport</string>
+    <string name="devicetype_nothing_cmf_watch_pro_2">CMF Watch Pro 2</string>
+    <string name="activity_type_tae_bo">Tae Bo</string>
+    <string name="activity_type_beach_soccer">Plaj Futbolu</string>
+    <string name="activity_type_beach_volleyball">Plaj Voleybolu</string>
+    <string name="activity_type_gateball">Gateball</string>
+    <string name="activity_type_sepak_takraw">Sepak Takraw</string>
+    <string name="activity_type_luge">K谋zak</string>
+    <string name="activity_type_auto_racing">Otomobil yar谋艧谋</string>
+    <string name="activity_type_parachuting">Para艧眉tle atlama</string>
+    <string name="activity_type_parkour">Parkur</string>
+    <string name="pref_summary_notification_times_enabled">Yaln谋zca belirli zamanlar aras谋nda bildirim g枚nder</string>
+    <string name="pref_title_notification_times_enabled">Bildirim zamanlar谋</string>
+    <string name="devicetype_huawei_watch3">Huawei Watch 3 (Pro)</string>
+    <string name="devicetype_vivitar_hr_bp_monitor_activity_tracker">Vivitar Kalp Ritmi ve Kan Bas谋nc谋 陌zleme Etkinlik Takip Ayg谋t谋</string>
+    <string name="devicetype_nothing_cmf_buds_pro_2">CMF Buds Pro 2</string>
+    <string name="devicetype_garmin_forerunner_255s_music">Garmin Forerunner 255S Music</string>
+    <string name="devicetype_garmin_forerunner_255_music">Garmin Forerunner 255 Music</string>
+    <string name="devicetype_huawei_watchgtrunner">Huawei Watch GT Runner</string>
+    <string name="find_my_phone_companion_warning">Telefon bulmak i莽in yard谋mc谋 e艧le艧tirme gereklidir. Daha fazla bilgi i莽in buraya t谋klay谋n.</string>
+    <string name="abstract_chart_fragment_kind_awake_sleep">Uyan谋k</string>
+    <string name="sleep_colored_stats_deep">Derin</string>
+    <string name="sleep_colored_stats_rem">REM</string>
+    <string name="sleep_colored_stats_light_avg">Hafif AVG</string>
+    <string name="sleep_colored_stats_rem_avg">REM AVG</string>
+    <string name="stats_empty_value">-</string>
+    <string name="stats_lowest_hr">En d眉艧眉k kalp ritmi</string>
+    <string name="stats_highest_hr">En y眉ksek kalp ritmi</string>
+    <string name="sleep_colored_stats_deep_avg">Derin AVG</string>
+    <string name="sleep_colored_stats_light">Hafif</string>
+    <string name="hrv_status">Kalp Ritmi De臒i艧kenli臒i Durumu</string>
+    <string name="hrv_status_poor">Zay谋f</string>
+    <string name="hrv_status_last_night">D眉n gece</string>
+    <string name="hrv_status_last_night_highest_5">D眉n gece 5-dak en y眉ksek ortalama</string>
+    <string name="hrv_status_seven_days_avg_long">7 g眉nl眉k ortalama</string>
+    <string name="hrv_status_unit">%1$d ms</string>
+    <string name="hrv_status_baseline">%1$d-%2$d ms</string>
+    <string name="hrv_status_baseline_label">Alt s谋n谋r</string>
+    <string name="hrv_status_day_avg_legend">G眉nl眉k ortalama (ms)</string>
+    <string name="hrv_status_day_avg">G眉nl眉k ortalama</string>
+    <string name="pref_header_hrv_status">Kalp Ritmi De臒i艧kenli臒i Durumu</string>
+    <string name="hrv_status_low">D眉艧眉k</string>
+    <string name="hrv_status_seven_days_avg">7 g眉nl眉k ortalama</string>
+    <string name="hrv_status_seven_days_avg_status">Durum</string>
+    <string name="hrv_status_balanced">Dengeli</string>
+    <string name="hrv_status_unbalanced">Dengesiz</string>
+    <string name="devicetype_garmin_forerunner_965">Garmin Forerunner 965</string>
+    <string name="gps_glonass">GPS + GLONASS</string>
+    <string name="devicetype_garmin_fenix_5_plus">Garmin Fenix 5 Plus</string>
+    <string name="soundcore_equalizer_band7">Bant 7</string>
+    <string name="soundcore_button_brightness_medium">Orta</string>
+    <string name="soundcore_button_brightness_high">Y眉ksek</string>
+    <string name="soundcore_ldac_mode_title">LDAC Modu</string>
+    <string name="soundcore_ldac_mode_summary">LDAC\'nin etkinle艧tirilmesi pil 枚mr眉n眉 k谋salt谋r ve ba臒lant谋 karars谋zl谋臒谋na yol a莽abilir</string>
+    <string name="soundcore_equalizer_preset_xtra_bass">Fazladan Bas</string>
+    <string name="soundcore_equalizer_preset_balanced">Dengeli</string>
+    <string name="soundcore_equalizer_custom_summary">Parametreli dengeleyici ayarlar谋n谋 yap谋land谋r</string>
+    <string name="soundcore_equalizer_custom_title">脰zelle艧tir鈥�</string>
+    <string name="soundcore_equalizer_reset_summary">T眉m dengeleyici bantlar谋n谋 枚ntan谋ml谋 ayarlara d枚nd眉r</string>
+    <string name="soundcore_equalizer_direction_hanging">As谋l谋</string>
+    <string name="soundcore_equalizer_band3">Bant 3</string>
+    <string name="soundcore_equalizer_band8">Bant 8</string>
+    <string name="soundcore_equalizer_band9">Bant 9</string>
+    <string name="soundcore_equalizer_band1">Bant 1</string>
+    <string name="soundcore_equalizer_band5">Bant 5</string>
+    <string name="soundcore_equalizer_band6">Bant 6</string>
+    <string name="soundcore_equalizer_band4">Bant 4</string>
+    <string name="soundcore_equalizer_value">De臒er</string>
+    <string name="minutes_20">20 dakika</string>
+    <string name="minutes_60">60 dakika</string>
+    <string name="devicetype_soundcore_motion300">Soundcore Motion 300</string>
+    <string name="soundcore_voice_prompts">Sesli Uyar谋lar</string>
+    <string name="soundcore_button_brightness">D眉臒me Parlakl谋臒谋</string>
+    <string name="soundcore_button_brightness_low">D眉艧眉k</string>
+    <string name="soundcore_adaptive_direction_title">Uyarlanabilir Y枚n</string>
+    <string name="soundcore_adaptive_direction_summary">Dengeleyici 枚n ayar谋n谋 ayg谋t y枚n眉ne g枚re otomatik olarak ayarla</string>
+    <string name="soundcore_equalizer_preset">脰n ayar</string>
+    <string name="soundcore_equalizer_preset_signature">Soundcore 陌mzas谋</string>
+    <string name="soundcore_equalizer_preset_voice">Ses</string>
+    <string name="soundcore_equalizer_direction">Ayg谋t Y枚n眉</string>
+    <string name="soundcore_equalizer_direction_standing">Ayakta</string>
+    <string name="soundcore_equalizer_direction_lying">Uzan谋yor</string>
+    <string name="soundcore_equalizer_reset_title">脰ntan谋ml谋 de臒ere s谋f谋rla</string>
+    <string name="soundcore_equalizer_band2">Bant 2</string>
+    <string name="soundcore_equalizer_frequency">Frekans</string>
+    <string name="body_energy_gained">Kazan谋lan</string>
+    <string name="body_energy_lost">Kaybedilen</string>
+    <string name="body_energy_legend_level">V眉cut Enerji Seviyesi</string>
+    <string name="body_energy">V眉cut Enerjisi</string>
+    <string name="transition">Ge莽i艧</string>
+    <string name="activity_type_e_bike">E-Bisiklet</string>
+    <string name="activity_type_transition">Ge莽i艧</string>
+    <string name="activity_type_platform_tennis">Platform Tenisi</string>
+    <string name="activity_type_training">Antrenman</string>
+    <string name="activity_type_cardio">Kardiyo</string>
+    <string name="activity_type_breathwork">Nefes 脟al谋艧mas谋</string>
+    <string name="activity_type_flying">U莽ma</string>
+    <string name="activity_type_boating">Teknecilik</string>
+    <string name="activity_type_hang_gliding">Plan枚rle U莽ma</string>
+    <string name="activity_type_inline_skating">S谋ral谋 Paten</string>
+    <string name="activity_type_climb_indoor">Kapal谋 Alan T谋rman谋艧谋</string>
+    <string name="activity_type_bouldering">Kaya T谋rman谋艧谋</string>
+    <string name="activity_type_sail_race">Yelken Yar谋艧谋</string>
+    <string name="activity_type_sail_expedition">Yelken Seferi</string>
+    <string name="activity_type_sky_diving">G枚ky眉z眉 Dal谋艧谋</string>
+    <string name="activity_type_snowshoe">Kar Ayakkab谋s谋</string>
+    <string name="activity_type_stand_up_paddleboarding">Ayakta K眉rek S枚rf眉</string>
+    <string name="activity_type_water_skiing">Su Kaya臒谋</string>
+    <string name="activity_type_grinding">Ta艧lama</string>
+    <string name="activity_type_health_snapshot">Sa臒l谋k Anl谋k G枚r眉nt眉s眉</string>
+    <string name="activity_type_marine">Denizcilik</string>
+    <string name="activity_type_video_gaming">Video Oyunu</string>
+    <string name="activity_type_racket">Raket</string>
+    <string name="activity_type_padel">Padel Tenisi</string>
+    <string name="activity_type_racquetball">Raket Topu</string>
+    <string name="activity_type_push_walk_speed">陌tme - H谋zl谋 Y眉r眉me</string>
+    <string name="activity_type_push_run_speed">陌tme - H谋zl谋 Ko艧ma</string>
+    <string name="activity_type_meditation">Meditasyon</string>
+    <string name="activity_type_disc_golf">Disk Golf眉</string>
+    <string name="activity_type_ultimate_disc">Ultimate Disk</string>
+    <string name="activity_type_lacrosse">Lakros</string>
+    <string name="activity_type_water_tubing">Suda Kayma</string>
+    <string name="activity_type_wakesurfing">Dalga S枚rf眉</string>
+    <string name="activity_type_aerobic_combo">Aerobik kar谋艧谋m谋</string>
+    <string name="activity_type_air_walker">Hava y眉r眉y眉艧眉</string>
+    <string name="activity_type_artistic_swimming">Artistik y眉zme</string>
+    <string name="activity_type_board_game">Masa oyunu</string>
+    <string name="activity_type_breaking">K谋rma</string>
+    <string name="activity_type_bridge">K枚pr眉</string>
+    <string name="activity_type_checkers">Dama</string>
+    <string name="activity_type_esports">Elektronik spor</string>
+    <string name="activity_type_futsal">Salon futbolu (futsal)</string>
+    <string name="activity_type_hip_hop">Hip-hop</string>
+    <string name="activity_type_jai_alai">Jai alai</string>
+    <string name="activity_type_jujitsu">Jujutsu</string>
+    <string name="activity_type_modern_dance">脟a臒da艧 Dans</string>
+    <string name="activity_type_muay_thai">Muay thai</string>
+    <string name="activity_type_parallel_bars">Paralel 莽ubuk</string>
+    <string name="activity_type_shuffleboard">Disk 陌teleme Oyunu</string>
+    <string name="activity_type_somatosensory_game">Somatosensoriyel oyun</string>
+    <string name="activity_type_wakeboarding">Su Kaya臒谋 (Wakeboarding)</string>
+    <string name="activity_type_navigate">Gezinme</string>
+    <string name="activity_type_indoor_track">Kapal谋 Alan Pisti</string>
+    <string name="activity_type_handcycling">El Bisikleti</string>
+    <string name="activity_type_bike_commute">Bisiklet Yolculu臒u</string>
+    <string name="activity_type_handcycling_indoor">Kapal谋 Alanda El Bisikleti</string>
+    <string name="activity_type_multisport">脟oklu Spor</string>
+    <string name="activity_type_fitness_equipment">Spor Aletleri</string>
+    <string name="activity_type_american_football">Amerikan Futbolu</string>
+    <string name="activity_type_xc_classic_ski">Klasik Kros Kaya臒谋</string>
+    <string name="activity_type_mountaineering">Da臒c谋l谋k</string>
+    <string name="activity_type_paddling">K眉rekle 陌lerletme</string>
+    <string name="activity_type_driving">S眉r眉艧</string>
+    <string name="activity_type_motorcycling">Motosiklet莽ilik</string>
+    <string name="activity_type_snowmobiling">Kar Motosikleti</string>
+    <string name="activity_type_ice_skating">Buz Pateni</string>
+    <string name="activity_type_rafting">Rafting</string>
+    <string name="activity_type_kayaking">Kano</string>
+    <string name="activity_type_tactical">Taktik</string>
+    <string name="activity_type_jumpmaster">Ba艧 Para艧眉t莽眉</string>
+    <string name="activity_type_floor_climbing">Zemin T谋rman谋艧谋</string>
+    <string name="activity_type_softball_slow_pitch">Softbol Yava艧 At谋艧</string>
+    <string name="activity_type_winter_sport">K谋艧 Sporu</string>
+    <string name="activity_type_shooting">At谋艧</string>
+    <string name="activity_type_indoor_push_walk_speed">Kapal谋 Alanda 陌tme - H谋zl谋 Y眉r眉me</string>
+    <string name="activity_type_indoor_push_run_speed">Kapal谋 Alanda 陌tme - H谋zl谋 Ko艧ma</string>
+    <string name="activity_type_para_sport">Engelli Sporu</string>
+    <string name="activity_type_ballroom_dance">Salon dans谋</string>
+    <string name="activity_type_bocce">Bocce (Bilye)</string>
+    <string name="activity_type_team_sport">Tak谋m Sporu</string>
+    <string name="activity_type_bmx">BMX</string>
+    <string name="activity_type_cardio_combat">Kardiyo d枚v眉艧眉</string>
+    <string name="activity_type_chess">Satran莽</string>
+    <string name="activity_type_dragon_boat">Ejderha kay谋臒谋</string>
+    <string name="activity_type_mixed_martial_arts">Kar谋艧谋k D枚v眉艧 Sanatlar谋</string>
+    <string name="activity_type_aerobics">Aerobik</string>
+    <string name="activity_type_finswimming">Paletli Y眉zme</string>
+    <string name="activity_type_folk_dance">Halk oyunlar谋</string>
+    <string name="activity_type_flowriding">Ak谋艧 s眉r眉艧眉</string>
+    <string name="activity_type_frisbee">Frizbi</string>
+    <string name="activity_type_hacky_sack">脟uval</string>
+    <string name="activity_type_hula_hoop">Hulahop</string>
+    <string name="activity_type_judo">Judo</string>
+    <string name="activity_type_mass_gymnastics">Toplu jimnastik</string>
+    <string name="activity_type_pole_dance">Direk dans谋</string>
+    <string name="activity_type_race_walking">Y眉r眉y眉艧 yar谋艧谋</string>
+    <string name="activity_type_snorkeling">艦norkelle Y眉zme</string>
+    <string name="activity_type_spinning">D枚nd眉rme</string>
+    <string name="activity_type_square_dance">Kare dans谋</string>
+    <string name="activity_type_stair_climber">Merdiven t谋rmanma</string>
+    <string name="activity_type_table_football">Masa futbolu</string>
+    <string name="activity_type_water_polo">Su topu</string>
+    <string name="activity_type_tug_of_war">Halat 莽ekme</string>
+    <string name="activity_type_wall_ball">Duvar topu</string>
+    <string name="activity_type_weiqi">Go (Weiqi)</string>
+    <string name="cannot_upload_watchface_too_many_watchfaces_installed">Saat aray眉z眉 y眉klenemiyor, 莽ok fazla saat aray眉z眉 kurulu</string>
+    <string name="insufficient_space_for_upload">Y眉kleme i莽in yeterli alan yok</string>
+    <string name="activity_type_free_sparring">Serbest 莽al谋艧ma</string>
+    <string name="activity_type_body_combat">V眉cut sava艧谋</string>
+    <string name="activity_type_plaza_dancing">Plaza dans谋</string>
+    <string name="activity_type_laser_tag">Laser tag</string>
+    <string name="activity_type_canoeing">Kano sporu</string>
+    <string name="activity_type_water_scooter">Su motoru</string>
+    <string name="activity_type_bobsleigh">Bobsled</string>
+    <string name="activity_type_sledding">K谋zak kayma</string>
+    <string name="activity_type_biathlon">Biatlon</string>
+    <string name="activity_type_bungee_jumping">Bungee jumping</string>
+    <string name="activity_type_orienteering">Y枚n bulma</string>
+    <string name="activity_type_obstacle_race">Engelli ko艧u</string>
+    <string name="activity_type_billiard_pool">Havuz</string>
+    <string name="authentication_failed_negotiation">Kimlik do臒rulama anahtar谋 anla艧mas谋 ba艧ar谋s谋z oldu</string>
+    <string name="devicetype_garmin_venu">Garmin Venu</string>
+    <string name="devicetype_garmin_venu_2s">Garmin Venu 2S</string>
+    <string name="estimatedSweatLoss">Tahmini Ter Kayb谋</string>
+    <string name="miscale_weight_unit_summary">G枚r眉nt眉lenen 枚l莽眉mler i莽in a臒谋rl谋k birimini ayarla</string>
+    <string name="miscale_weight_unit_metric">Metrik (kg)</string>
+    <string name="devicetype_mismartscale">Mi Smart Scale 2</string>
+    <string name="miscale_weight_unit_title">A臒谋rl谋k Birimi</string>
+    <string name="miscale_weight_unit_imperial">陌ngiliz (paunt)</string>
+    <string name="miscale_small_objects_title">K眉莽眉k Nesneler</string>
+    <string name="hrv">Kalp Ritmi De臒i艧kenli臒i</string>
+    <string name="devicetype_micompositionscale">Mi Body Composition Scale 2</string>
+    <string name="miscale_small_objects_summary">A臒谋rl谋臒谋 10 kg\'dan az nesneleri kaydet</string>
+    <string name="miscale_weight_unit_chinese">脟in (jin)</string>
+    <string name="steps_distance_unit">%1$,.2f km</string>
+    <string name="steps_avg">Ortalama Ad谋m Say谋s谋</string>
+    <string name="distance_total">Toplam Mesafe</string>
+    <string name="steps_total">Toplam Ad谋m Say谋s谋</string>
+    <string name="distance_avg">Ortalama Mesafe</string>
+    <string name="pref_time_sync">Otomatik saat e艧zamanlamas谋</string>
+    <string name="minutes_45">45 dakika</string>
+    <string name="busy_task_fetch_sleep_data">Uyku verileri getiriliyor</string>
+    <string name="minutes_2">2 dakika</string>
+    <string name="minutes_3">3 dakika</string>
+    <string name="minutes_9">9 dakika</string>
+    <string name="minutes_15">15 dakika</string>
+    <string name="minutes_75">75 dakika</string>
+    <string name="minutes_90">90 dakika</string>
+    <string name="minutes_120">120 dakika</string>
+    <string name="minutes_150">150 dakika</string>
+    <string name="dateformat_day_month">G眉n, Ay</string>
+    <string name="devicetype_colmi_r03">Colmi R03</string>
+    <string name="smart_ring_measurement_error_unknown">Y眉z眉kten bilinmeyen %d 枚l莽眉m hatas谋 al谋nd谋</string>
+    <string name="minutes_4">4 dakika</string>
+    <string name="user_feedback_set_settings_ok">Ayarlar ayg谋ta g枚nderildi.</string>
+    <string name="minutes_6">6 dakika</string>
+    <string name="minutes_210">210 dakika</string>
+    <string name="minutes_240">240 dakika</string>
+    <string name="devicetype_colmi_r02">Colmi R02</string>
+    <string name="devicetype_colmi_r06">Colmi R06</string>
+    <string name="minutes_7">7 dakika</string>
+    <string name="minutes_8">8 dakika</string>
+    <string name="minutes_180">180 dakika</string>
+    <string name="minutes_255">255 dakika</string>
+    <string name="stress_average">Ortalama</string>
+    <string name="dateformat_month_day">Ay, G眉n</string>
+    <string name="prefs_light_duration_longer">Daha uzun 谋艧谋k s眉resi</string>
+    <string name="pref_app_connection_duration">Uygulama ba臒lant谋 s眉resi</string>
+    <string name="smart_ring_measurement_error_worn_incorrectly">脰l莽眉m hatas谋. Y眉z眉臒眉n sens枚rleri do臒ru konumland谋r谋ld谋 m谋?</string>
+    <string name="devicetype_honor_watchgs3">Honor Watch GS 3</string>
+    <string name="devicetype_mijia_xmwsdj04">Mijia S谋cakl谋k ve Nem Sens枚r眉 2 (E-ink)</string>
+    <string name="sleep_colored_stats_awake_avg">Uyan谋k ortalamas谋</string>
+    <string name="activity_type_rem_sleep">REM uykusu</string>
+    <string name="devicetype_garmin_fenix_5x_plus">Garmin Fenix 5X Plus</string>
+    <string name="lactateThresholdHeartRate">Laktat E艧i臒i Kalp Ritmi</string>
+    <string name="recoveryTime">陌yile艧me S眉resi</string>
+    <string name="weight_kg">%1$.2f kg</string>
+    <string name="weight_lbs">%1$.2f paunt</string>
+    <string name="target">Hedef</string>
+    <string name="menuitem_weight">A臒谋rl谋k</string>
+    <string name="pref_header_sound">Ses</string>
+    <string name="workoutSets">Set</string>
+    <string name="workout_set_i">Set %1d</string>
+    <string name="workout_set_repetitions">%1d x</string>
+    <string name="workout_set_repetitions_weight_kg">%1d x %2$.2f kg</string>
+    <string name="workout_set_repetitions_weight_lbs">%1d x %2$.2f paunt</string>
+    <string name="no_supported_devices_found">Desteklenen ayg谋t bulunamad谋</string>
+    <string name="menuitem_stress_segmented">Stres (b枚l眉mlenmi艧)</string>
+    <string name="menuitem_stress_breakdown">Stres (莽枚k眉艧)</string>
+    <string name="menuitem_stress_simple">Stres (basit)</string>
+    <string name="choose_device">Ayg谋t se莽</string>
+    <string name="devicetype_soundcore_liberty4_nc">Soundcore Liberty 4 NC</string>
+    <string name="search">Ara</string>
+    <string name="pref_header_deprecated_functionalities">Kullan谋mdan kald谋r谋lan i艧levler</string>
+    <string name="pref_header_deprecated_functionalities_warning">A艧a臒谋daki i艧levler kullan谋mdan kald谋r谋ld谋 ve yak谋nda yaz谋l谋mdan kald谋r谋lacak.
+\nA艧a臒谋daki ayarlardan birini etkinle艧tirmeniz gerekiyorsa, proje tak谋m谋yla ileti艧ime ge莽in.</string>
+    <string name="pref_deprecated_media_control_title">Kullan谋mdan kald谋r谋lan medya denetimi</string>
+    <string name="pref_deprecated_media_control_summary">Medya denetim komutlar谋n谋 medya denetleyicisi yerine tu艧 olaylar谋 olarak g枚nder.</string>
+    <string name="pref_title_nagivation_apps">Navigasyon uygulamalar谋</string>
+    <string name="pref_header_automations">Otomatikle艧tirmeler</string>
+    <string name="pref_description_automations">Otomatik d谋艧a aktar, otomatik getir</string>
+    <string name="pref_description_developer_options">G眉nl眉kler, Ama莽 API\'si</string>
+    <string name="pref_description_deprecated_functionalities">Gelecekteki bir s眉r眉mde kald谋r谋lacak ayarlar</string>
+    <string name="pref_header_main_screen">Ana ekran</string>
+    <string name="pref_title_user_interface">Kullan谋c谋 aray眉z眉</string>
+    <string name="pref_header_external_integrations">Harici B眉t眉nle艧meler</string>
+    <string name="pref_description_general">Ba艧lang谋莽, dil, b枚lge, konum</string>
+    <string name="pref_description_about_you">Do臒um tarihi, cinsiyet, boy, kilo, hedefler</string>
+    <string name="pref_description_notifications">Uygulama bildirimleri, beyaz liste/kara liste</string>
+    <string name="pref_description_user_interface">Tema, ana ekran</string>
+    <string name="pref_description_dashboard">Widget\'lar, dahil edilecek ayg谋tlar</string>
+    <string name="activity_prefs_date_birth">Do臒um tarihi</string>
+    <string name="devicetype_garmin_forerunner_165">Garmin Forerunner 165</string>
+    <string name="prefs_title_gatt_client_allow_gatt_interactions">BLE Ama莽 API\'si arac谋l谋臒谋yla GATT etkile艧imine izin ver</string>
+    <string name="prefs_summary_gatt_client_device_state_updates">Ama莽lar arac谋l谋臒谋yla BLE ba臒lant谋 durumu de臒i艧ikliklerini al</string>
+    <string name="prefs_title_gatt_client_api_package">BLE API paketi</string>
+    <string name="prefs_title_ble_intent_api">BLE Ama莽 API</string>
+    <string name="pref_continuous_skin_temperature_measurement_description">S谋cakl谋k al谋m谋 艧u anda desteklenmiyor. Bu ayar yaln谋zca ayg谋tta s眉rekli 枚l莽眉m眉 etkinle艧tirir</string>
+    <string name="pref_continuous_skin_temperature_measurement_title">S眉rekli cilt s谋cakl谋臒谋 枚l莽眉m眉</string>
+    <string name="devicetype_ble_gatt_client">Genel BLE GATT 陌stemcisi</string>
+    <string name="prefs_title_gatt_client_notification_intents">BLE Ama莽 API\'si arac谋l谋臒谋yla GATT bildirim ama莽lar谋n谋 yay谋nla</string>
+    <string name="prefs_summary_gatt_client_allow_gatt_interactions">BLE 枚zellik okuma/yazma ve ba臒lanma komutlar谋n谋n g枚nderilmesine izin ver</string>
+    <string name="prefs_summary_gatt_client_api_package">BLE Ama莽 API ileti艧imini bu paketle k谋s谋tla</string>
+    <string name="prefs_summary_gatt_client_notification_intents">Ama莽lar arac谋l谋臒谋yla BLE 枚zellik de臒i艧ikliklerini al</string>
+    <string name="backup_restore_exporting">Zip olarak d谋艧a aktar谋l谋yor鈥�</string>
+    <string name="backup_restore_exporting_preferences">Tercihler d谋艧a aktar谋l谋yor鈥�</string>
+    <string name="backup_restore_exporting_database">Veri taban谋 d谋艧a aktar谋l谋yor鈥�</string>
+    <string name="backup_restore_exporting_files">Dosyalar d谋艧a aktar谋l谋yor鈥�</string>
+    <string name="backup_restore_exporting_files_i_of_n">Dosyalar d谋艧a aktar谋l谋yor鈥� %1d / %2d</string>
+    <string name="backup_restore_exporting_finishing">D谋艧a aktarma tamamlan谋yor鈥�</string>
+    <string name="backup_restore_importing">Zip dosyas谋ndan i莽e aktar谋l谋yor鈥�</string>
+    <string name="backup_restore_do_not_exit">%s L眉tfen i艧lem bitene kadar bu ekran谋 a莽谋k tutun.</string>
+    <string name="backup_restore_importing_loading">Dosya y眉kleniyor鈥�</string>
+    <string name="backup_restore_importing_validating">Dosya do臒rulan谋yor鈥�</string>
+    <string name="backup_restore_warning_files">%1d dosya geri y眉klenemedi:
+\n%2s</string>
+    <string name="backup_restore_restart_title">Yeniden ba艧lat</string>
+    <string name="activity_db_management_backup_restore_label">Yedekle ve Geri Y眉kle</string>
+    <string name="activity_db_management_export_to_zip">Zip olarak d谋艧a aktar</string>
+    <string name="activity_db_management_import_from_zip">Zip dosyas谋ndan i莽e aktar</string>
+    <string name="activity_db_management_backup_restore_explanation">陌莽e/d谋艧a aktarma i艧lemleri, t眉m Gadgetbridge ayarlar谋n谋, ayg谋tlar谋n谋 ve verilerini bir zip dosyas谋na ta艧谋man谋za veya bu dosyadan yedeklemenize olanak tan谋r.
+\n
+\nBir dosyay谋 i莽e aktarmak, var olan t眉m verileri, ayg谋tlar谋 ve tercihleri kald谋racak ve bunlar谋 tamamen yedekle de臒i艧tirecektir.</string>
+    <string name="backup_restore_importing_files_i_of_n">Dosyalar i莽e aktar谋l谋yor鈥� %1d / %2d</string>
+    <string name="backup_restore_importing_database">Veri taban谋 i莽e aktar谋l谋yor鈥�</string>
+    <string name="backup_restore_importing_preferences">Tercihler i莽e aktar谋l谋yor鈥�</string>
+    <string name="backup_restore_export_complete">D谋艧a aktarma tamamland谋</string>
+    <string name="backup_restore_import_complete">陌莽e aktarma tamamland谋</string>
+    <string name="backup_restore_abort_import_confirmation">陌莽e aktarma iptal edilsin mi? Bu, bozuk veya tutars谋z bir veri taban谋na yol a莽abilir.</string>
+    <string name="backup_restore_error_export">Zip olarak d谋艧a aktar谋lamad谋</string>
+    <string name="backup_restore_error_import">Zip dosyas谋ndan i莽e aktar谋lamad谋</string>
+    <string name="backup_restore_abort_title">陌ptal et</string>
+    <string name="backup_restore_abort_export_confirmation">D谋艧a aktarma iptal edilsin mi? K谋smi zip dosyas谋 silinecek.</string>
+    <string name="backup_restore_restart_summary">%1s 艧imdi yeniden ba艧lat谋lacak.</string>
+    <string name="devicetype_honor_watchgspro">Honor Watch GS Pro</string>
+    <string name="devicetype_amazfit_trex_3">Amazfit T-Rex 3</string>
+    <string name="menuitem_map">Harita</string>
+    <string name="menuitem_heart_rate_push">Kalp Ritmi 陌tme</string>
+    <string name="menuitem_night_display">Gece Ekran谋</string>
+    <string name="label_distance_trip">Yolculuk: %.1f km</string>
+    <string name="label_distance_total">Toplam: %.1f km</string>
+    <string name="label_distance_total_mph">Toplam: %.1f mil</string>
+    <string name="error_no_cycling_sensor_found">d枚ng眉 sens枚r眉 bulunamad谋</string>
+    <string name="label_distance_trip_mph">Yolculuk: %.1f mil</string>
+    <string name="devicetype_garmin_forerunner_245_music">Garmin Forerunner 245 Music</string>
+    <string name="hr_resting">Dinlenirken</string>
+    <string name="hr_maximum">En y眉ksek</string>
+    <string name="hr_minimum">En d眉艧眉k</string>
+    <string name="hr_average">Ortalama</string>
+    <string name="devicetype_garmin_enduro_3">Garmin Enduro 3</string>
+    <string name="bpm_value_unit">%1$d kalp at谋艧谋/dakika</string>
+    <string name="devicetype_garmin_forerunner_955">Garmin Forerunner 955</string>
+    <string name="devicetype_sony_wf_c500">Sony WF-C500</string>
+    <string name="vo2max_running">En Y眉ksek Ko艧u VO鈧� De臒eri</string>
+    <string name="vo2max_cycling">En Y眉ksek Bisiklet S眉rme VO鈧� De臒eri</string>
+    <string name="thirty_days_timeline">30 G眉nl眉k Zaman 脟izelgesi</string>
+    <string name="devicetype_garmin_forerunner_265s">Garmin Forerunner 265S</string>
+    <string name="devicetype_redmi_watch_5_active">Redmi Watch 5 Active</string>
+    <string name="devicetype_miband9">Xiaomi Smart Band 9</string>
+    <string name="devicetype_huawei_watchgt5">Huawei Watch GT 5 (Pro)</string>
+    <string name="contact_birthday">%1s do臒um g眉n眉</string>
+    <string name="pref_summary_sync_birthdays">Ki艧i do臒um g眉nlerini takvim etkinlikleriyle birlikte e艧zamanla</string>
+    <string name="pref_title_sync_birthdays">Do臒um g眉nlerini e艧zamanla</string>
+    <string name="birthdays">Do臒um g眉nleri</string>
+    <string name="devicetype_sony_wf_c700n">Sony WF-C700N</string>
+    <string name="pref_crash_notification_title">脟枚kme durumunda bildir</string>
+    <string name="pref_crash_notification_summary">Uygulama 莽枚kt眉臒眉nde, hatay谋 i莽eren bir bildirim g枚r眉nt眉le</string>
+    <string name="app_crash_notification_title">%1s 莽枚kt眉</string>
+    <string name="app_crash_share_stacktrace">Hatay谋 payla艧</string>
+    <string name="pref_dashboard_widget_today_yesterday_data_summary">Ge莽erli saat ile gece yar谋s谋 aras谋nda d眉ne ait verileri karart谋lm谋艧 olarak g枚ster</string>
+    <string name="pref_dashboard_widget_today_yesterday_data_title">D眉nk眉 veriler</string>
+    <string name="pref_dashboard_widget_today_time_indicator_title">Ge莽erli zaman g枚stergesi</string>
+    <string name="pref_dashboard_widget_today_time_indicator_summary">D眉n ve bug眉ne ait verileri g枚rsel olarak ay谋rmak i莽in ge莽erli zamanda bir g枚sterge g枚ster</string>
+    <string name="pref_summary_calendar_lookahead">%1s g眉ne kadar takvim etkinliklerini e艧zamanla</string>
+    <string name="pref_title_calendar_lookahead">Gelecek g眉n say谋s谋</string>
+    <string name="hrZoneEasy">Kolay</string>
+    <string name="hrZoneThreshold">E艧ik</string>
+    <string name="hrZoneMaximum">En y眉ksek</string>
+    <string name="workout_set_reps">Tekrarlar</string>
+    <string name="devicetype_huawei_watchgtcyber">Huawei Watch GT Cyber</string>
+    <string name="paceCorrection">D眉zeltme</string>
+    <string name="activity_detail_share_json_details">JSON Ayr谋nt谋lar谋n谋 Payla艧</string>
+    <string name="number_selected_items">%1d se莽ildi</string>
+    <string name="devicetype_garmin_fenix_8">Garmin Fenix 8</string>
+    <string name="devicetype_garmin_venu_sq">Garmin Venu Sq</string>
+    <string name="date_placeholders__start_time__end_time">%1s - %2s</string>
+    <string name="mijia_lywsd_comfort_temperature_summary">Tavsiye edilen aral谋k: 19 - 27</string>
+    <string name="mijia_lywsd_comfort_level_title">Rahatl谋k Seviyesi</string>
+    <string name="mijia_lywsd_comfort_level_summary">G枚r眉nt眉lenen emoji i莽in s谋cakl谋k ve nem s谋n谋rlar谋n谋 yap谋land谋r谋n</string>
+    <string name="mijia_lywsd_comfort_humidity_title">Nem (%)</string>
+    <string name="date_placeholders__date__time">%1s, %1s</string>
+    <string name="mijia_lywsd_comfort_temperature_title">S谋cakl谋k (掳C)</string>
+    <string name="mijia_lywsd_comfort_humidity_summary">Tavsiye edilen aral谋k: 20 - 85</string>
+    <string name="mijia_lywsd_comfort_lower">Alt S谋n谋r</string>
+    <string name="mijia_lywsd_comfort_upper">脺st S谋n谋r</string>
+    <string name="devicetype_garmin_venu_sq_2">Garmin Venu Sq 2</string>
+    <string name="devicetype_garmin_fenix_6s_sapphire">Garmin Fenix 6S Sapphire</string>
+    <string name="inactivity_warnings_minimum_steps_title">Asgari ad谋m say谋s谋</string>
+    <string name="inactivity_warnings_minimum_steps_summary">E艧ik dakikalar谋 boyunca at谋lmas谋 gereken asgari ad谋m say谋s谋</string>
+    <string name="prefs_hrv_monitoring_title">Kalp Ritmi De臒i艧kenli臒i izleme</string>
+    <string name="prefs_hrv_monitoring_description">G眉n boyunca kalp ritmi de臒i艧kenli臒ini otomatik olarak izle</string>
+    <string name="busy_task_fetch_hrv_data">Kalp Ritmi De臒i艧kenli臒i verileri al谋n谋yor</string>
+    <string name="pref_dnd_follow_phone_title">Telefon Rahats谋z Etme ayar谋n谋 takip et</string>
+    <string name="pref_dnd_follow_phone_summary">Telefonda Rahats谋z Etme etkinle艧tirildi臒inde veya devre d谋艧谋 b谋rak谋ld谋臒谋nda, ayg谋tta da otomatik olarak ge莽i艧 yap</string>
+    <string name="average_respiration_rate">Solunum H谋z谋</string>
+    <string name="hrv_rmssd">Kalp Ritmi De臒i艧kenli臒i RMSSD</string>
+    <string name="milliseconds_ms">ms</string>
+    <string name="max_respiration_rate">En Y眉ksek Solunum H谋z谋</string>
+    <string name="min_respiration_rate">En D眉艧眉k Solunum H谋z谋</string>
+    <string name="hrv_sdrr">Kalp Ritmi De臒i艧kenli臒i SDDR</string>
+    <string name="breaths_per_min">nefes/dak</string>
+    <string name="fmtPaceAverage">H谋z ortalamas谋</string>
+    <string name="devicetype_huawei_watchd2">Huawei Watch D2</string>
+    <string name="devicetype_idasen">IKEA Idasen Desk</string>
+    <string name="devicetype_colmi_r10">Colmi R10</string>
+    <string name="idasen_control_button_mid">ORTA</string>
+    <string name="idasen_control_button_sit">OTUR</string>
+    <string name="idasen_control_button_stand">AYAKTA</string>
+    <string name="idasen_pref_mid_height">Orta konum y眉ksekli臒i (santimetre cinsinden)</string>
+    <string name="idasen_pref_stand_height">Ayakta konumu y眉ksekli臒i (santimetre cinsinden)</string>
+    <string name="idasen_pref_value_warning">De臒er 62 - 126 cm aras谋nda olmal谋d谋r</string>
+    <string name="idasen_pref_sit_height">Otur konumu y眉ksekli臒i (santimetre cinsinden)</string>
+    <string name="active_calories">Aktif kalori</string>
+    <string name="active_calories_short">Aktif</string>
+    <string name="music_upload_info">You are about to upload the following music file:\n\n%1$s\nSong title: %2$s\nAlbum: %3$s\n</string>
+    <string name="active_calories_goal">Aktif hedef</string>
+    <string name="sleep_avg">Uyku ortalamas谋</string>
+    <string name="lowest">En d眉艧眉k</string>
+    <string name="sleep_score">Uyku skoru</string>
+    <string name="sleep_score_value">Skor: %1d</string>
+    <string name="title_activity_musicmanager">M眉zik Y枚neticisi</string>
+    <string name="highest">En y眉ksek</string>
+    <string name="total_calories_burnt">Toplam yak谋lan</string>
+</resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml
new file mode 100644
index 0000000..f3a10ee
--- /dev/null
+++ b/app/src/main/res/values-uk/strings.xml
@@ -0,0 +1,2958 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources xmlns:tools="http://schemas.android.com/tools">
+    <string name="application_name_generic">Gadgetbridge</string>
+    <string name="title_activity_controlcenter_generic">Gadgetbridge</string>
+    <string name="action_settings">袧邪谢邪褕褌褍胁邪薪薪褟</string>
+    <string name="action_debug">袧邪谢邪谐芯写卸械薪薪褟</string>
+    <string name="action_quit">袙懈泄褌懈</string>
+    <string name="controlcenter_fetch_activity_data">小懈薪褏褉芯薪褨蟹褍胁邪褌懈</string>
+    <string name="controlcenter_find_device">袟薪邪泄褌懈 蟹邪谐褍斜谢械薪懈泄 锌褉懈褋褌褉褨泄</string>
+    <string name="controlcenter_take_screenshot">袟褉芯斜懈褌懈 蟹薪褨屑芯泻 械泻褉邪薪邪</string>
+    <string name="controlcenter_disconnect">袙褨写\'褦写薪邪褌懈</string>
+    <string name="controlcenter_delete_device">袙懈写邪谢懈褌懈 锌褉懈褋褌褉褨泄</string>
+    <string name="controlcenter_delete_device_name">袙懈写邪谢懈褌懈 %1$s</string>
+    <string name="controlcenter_delete_device_dialogmessage">笑械 胁懈写邪谢懈褌褜 锌褉懈褋褌褉褨泄 褌邪 胁褋褨 锌芯胁\'褟蟹邪薪褨 蟹 薪懈屑 写邪薪褨!</string>
+    <string name="title_activity_debug">袧邪谢邪谐芯写卸械薪薪褟</string>
+    <!--Strings related to AppManager-->
+    <string name="title_activity_appmanager">袦械薪械写卸械褉 蟹邪褋褌芯褋褍薪泻褨胁</string>
+    <string name="appmanager_installed_watchapps">袙褋褌邪薪芯胁谢械薪褨 蟹邪褋褌芯褋褍薪泻懈</string>
+    <string name="appmananger_app_delete">袙懈写邪谢懈褌懈</string>
+    <string name="appmananger_app_delete_cache">袙懈写邪谢懈褌懈 褌邪 锌褉懈斜褉邪褌懈 蟹 泻械褕褍</string>
+    <string name="appmananger_app_reinstall">袩械褉械胁褋褌邪薪芯胁懈褌懈</string>
+    <string name="appmanager_app_openinstore">袩芯褕褍泻 胁 屑邪谐邪蟹懈薪褨 蟹邪褋褌芯褋褍薪泻褨胁 Pebble</string>
+    <string name="appmanager_health_activate">校胁褨屑泻薪褍褌懈</string>
+    <string name="appmanager_health_deactivate">袙懈屑泻薪褍褌懈</string>
+    <string name="appmanager_hrm_activate">校胁褨屑泻薪褍褌懈 HRM</string>
+    <string name="appmanager_hrm_deactivate">袙懈屑泻薪褍褌懈 HRM</string>
+    <string name="app_configure">袧邪谢邪褕褌褍胁邪褌懈</string>
+    <!--Strings related to FwAppInstaller-->
+    <string name="title_activity_fw_app_insaller">袙褋褌邪薪芯胁谢械薪薪褟 屑褨泻褉芯锌褉芯谐褉邪屑 褌邪 蟹邪褋褌芯褋褍薪泻褨胁</string>
+    <string name="fw_upgrade_notice">袙懈 蟹斜懈褉邪褦褌械褋褟 胁褋褌邪薪芯胁懈褌懈 %s.</string>
+    <string name="fw_multi_upgrade_notice">袙懈 蟹斜懈褉邪褦褌械褋褟 胁褋褌邪薪芯胁懈褌懈 屑褨泻褉芯锌褉芯谐褉邪屑褍 %1$s 褌邪 %2$s 蟹邪屑褨褋褌褜 锌芯褌芯褔薪芯褩 写谢褟 胁邪褕芯谐芯 Mi Band.</string>
+    <string name="miband_firmware_known">笑褟 屑褨泻褉芯锌褉芯谐褉邪屑邪 斜褍谢邪 锌褉芯褌械褋褌芯胁邪薪邪 薪邪 褋褍屑褨褋薪褨褋褌褜 蟹 Gadgetbridge.</string>
+    <string name="miband_firmware_unknown_warning">笑褟 屑褨泻褉芯锌褉芯谐褉邪屑邪 薪械 斜褍谢邪 锌褉芯褌械褋褌芯胁邪薪邪 薪邪 褋褍屑褨褋薪褨褋褌褜 蟹 Gadgetbridge.
+\n
+\n袙邪屑 袧袝 袪袝袣袨袦袝袧袛校袆孝鞋小携 褩褩 胁褋褌邪薪芯胁谢褞胁邪褌懈!</string>
+    <string name="miband_firmware_suggest_whitelist">携泻褖芯 胁懈 胁褋械 褖械 褏芯褔械褌械 锌褉芯写芯胁卸懈褌懈 褌邪 锌褨褋谢褟 褑褜芯谐芯 胁褋械 斜褍写械 锌褉邪褑褞胁邪褌懈 薪邪谢械卸薪懈屑 褔懈薪芯屑, 斜褍写褜 谢邪褋泻邪, 锌芯胁褨写芯屑褌械 褉芯蟹褉芯斜薪懈泻褨胁 Gadgetbridge, 褖芯斜 胁芯薪懈 写芯写邪谢懈 褑褞 胁械褉褋褨褞 屑褨泻褉芯锌褉芯谐褉邪屑懈 %s 写芯 斜褨谢芯谐芯 褋锌懈褋泻褍.</string>
+    <!--Strings related to Settings-->
+    <string name="title_activity_settings">袧邪谢邪褕褌褍胁邪薪薪褟</string>
+    <string name="pref_header_general">袟邪谐邪谢褜薪褨 薪邪谢邪褕褌褍胁邪薪薪褟</string>
+    <string name="pref_title_general_autoconnectonbluetooth">袟\'褦写薪褍胁邪褌懈褋褟 蟹 Gadgetbridge 锌褨褋谢褟 褍胁褨屑泻薪械薪薪褟 Bluetooth</string>
+    <string name="pref_title_general_autostartonboot">袟邪锌褍褋泻邪褌懈褋褟 邪胁褌芯屑邪褌懈褔薪芯</string>
+    <string name="pref_title_general_autoreconnect">袗胁褌芯屑邪褌懈褔薪械 锌械褉械蟹\'褦写薪邪薪薪褟</string>
+    <string name="pref_title_audio_player">袘邪卸邪薪懈泄 邪褍写褨芯锌褉芯谐褉邪胁邪褔</string>
+    <string name="pref_default">孝懈锌芯胁芯</string>
+    <string name="pref_header_datetime">袛邪褌邪 泄 褔邪褋</string>
+    <string name="pref_title_datetime_syctimeonconnect">小懈薪褏褉芯薪褨蟹褍胁邪褌懈 褔邪褋</string>
+    <string name="pref_summary_datetime_syctimeonconnect">小懈薪褏褉芯薪褨蟹褍胁邪褌懈 褔邪褋 蟹 锌褉懈褋褌褉芯褟屑懈 Gadgetbridge 锌褨写 褔邪褋 蟹\'褦写薪邪薪薪褟, 锌褨褋谢褟 蟹屑褨薪懈 褔邪褋褍 邪斜芯 褔邪褋芯胁芯谐芯 锌芯褟褋褍 胁 褋懈褋褌械屑褨 Android, 邪 褌邪泻芯卸 锌械褉褨芯写懈褔薪芯</string>
+    <string name="pref_title_theme">孝械屑邪</string>
+    <string name="pref_theme_light">小胁褨褌谢邪</string>
+    <string name="pref_theme_dark">孝械屑薪邪</string>
+    <string name="pref_title_language">袦芯胁邪</string>
+    <string name="pref_header_notifications">小锌芯胁褨褖械薪薪褟</string>
+    <string name="pref_title_notifications_repetitions">袩芯胁褌芯褉械薪薪褟</string>
+    <string name="pref_title_notifications_call">孝械谢械褎芯薪薪褨 胁懈泻谢懈泻懈</string>
+    <string name="pref_title_notifications_sms">SMS</string>
+    <string name="pref_title_notifications_pebblemsg">袩芯胁褨写芯屑谢械薪薪褟 Pebble</string>
+    <string name="pref_title_notifications_generic">袩褨写褌褉懈屑泻邪 褋锌芯胁褨褖械薪褜</string>
+    <string name="pref_title_whenscreenon">鈥π叫靶惭栄傃� 泻芯谢懈 械泻褉邪薪 褍胁褨屑泻薪械薪芯</string>
+    <string name="pref_title_notification_filter">袧械 褌褍褉斜褍胁邪褌懈</string>
+    <string name="always">袟邪胁卸写懈</string>
+    <string name="when_screen_off">袣芯谢懈 械泻褉邪薪 胁懈屑泻薪褍褌芯</string>
+    <string name="never">袧褨泻芯谢懈</string>
+    <string name="pref_header_privacy">袩褉懈胁邪褌薪褨褋褌褜</string>
+    <string name="pref_title_canned_replies">袙褨写锌芯胁褨写褨</string>
+    <string name="pref_title_canned_reply_suffix">袟邪谐邪谢褜薪懈泄 褋褍褎褨泻褋</string>
+    <string name="pref_header_development">袩邪褉邪屑械褌褉懈 写谢褟 褉芯蟹褉芯斜薪懈泻褨胁</string>
+    <string name="pref_title_development_miaddr">袗写褉械褋邪 Mi Band</string>
+    <string name="pref_title_pebble_settings">袧邪谢邪褕褌褍胁邪薪薪褟 Pebble</string>
+    <string name="pref_header_activitytrackers">袙褨写褋褌械卸褍胁邪褔 邪泻褌懈胁薪芯褋褌褨</string>
+    <string name="pref_title_pebble_activitytracker">袘邪卸邪薪懈泄 胁褨写褋褌械卸褍胁邪褔 邪泻褌懈胁薪芯褋褌褨</string>
+    <string name="pref_title_pebble_sync_health">小懈薪褏褉芯薪褨蟹褍胁邪褌懈 Pebble Health</string>
+    <string name="pref_title_pebble_sync_misfit">小懈薪褏褉芯薪褨蟹褍胁邪褌懈 Misfit</string>
+    <string name="pref_title_pebble_sync_morpheuz">小懈薪褏褉芯薪褨蟹褍胁邪褌懈 Morpheuz</string>
+    <string name="pref_title_enable_pebblekit">袛芯蟹胁芯谢懈褌懈 写芯褋褌褍锌 褋褌芯褉芯薪薪褨屑 蟹邪褋褌芯褋褍薪泻邪屑</string>
+    <string name="pref_summary_enable_pebblekit">袙懈泻芯褉懈褋褌芯胁褍胁邪褌懈 械泻褋锌械褉懈屑械薪褌邪谢褜薪褍 锌褨写褌褉懈屑泻褍 蟹邪褋褌芯褋褍薪泻褨胁 Android 蟹邪 写芯锌芯屑芯谐芯褞 PebbleKit</string>
+    <string name="pref_title_sunrise_sunset">小褏褨写 褌邪 蟹邪褏褨写 褋芯薪褑褟</string>
+    <string name="pref_summary_sunrise_sunset">袧邪写褋懈谢邪褌懈 褔邪褋 褋褏芯写褍 泄 蟹邪褏芯写褍 褋芯薪褑褟 薪邪 芯褋薪芯胁褨 屑褨褋褑械蟹薪邪褏芯写卸械薪薪褟 薪邪 褔邪褋芯胁褍 褕泻邪谢褍 Pebble</string>
+    <string name="pref_header_location">袪芯蟹褌邪褕褍胁邪薪薪褟</string>
+    <string name="pref_title_location_latitude">楔懈褉芯褌邪</string>
+    <string name="pref_title_location_longitude">袛芯胁谐芯褌邪</string>
+    <string name="pref_title_pebble_forceprotocol">袩褉懈屑褍褋芯胁懈泄 锌褉芯褌芯泻芯谢 褋锌芯胁褨褖械薪褜</string>
+    <string name="pref_summary_pebble_forceprotocol">笑械泄 锌邪褉邪屑械褌褉 锌褉懈屑褍褕褍褦 胁懈泻芯褉懈褋褌芯胁褍胁邪褌懈 芯褋褌邪薪薪褨泄 锌褉芯褌芯泻芯谢 褋锌芯胁褨褖械薪褜, 褟泻懈泄 蟹邪谢械卸懈褌褜 胁褨写 胁械褉褋褨褩 屑褨泻褉芯锌褉芯谐褉邪屑懈. 袙袦袠袣袗袡孝袝 袧袗 袙袥袗小袧袠袡 袪袠袟袠袣!</string>
+    <string name="pref_title_pebble_forceuntested">校胁褨屑泻薪褍褌懈 薪械锌械褉械胁褨褉械薪褨 褎褍薪泻褑褨褩</string>
+    <string name="pref_summary_pebble_forceuntested">校胁褨屑泻薪褍褌懈 薪械锌械褉械胁褨褉械薪褨 褎褍薪泻褑褨褩. 袙袦袠袣袗袡孝袝 袧袗 袙袥袗小袧袠袡 袪袠袟袠袣!</string>
+    <string name="pref_title_pebble_reconnect_attempts">小锌褉芯斜懈 锌械褉械蟹\'褦写薪邪薪薪褟</string>
+    <string name="pref_title_timeformat">肖芯褉屑邪褌 褔邪褋褍</string>
+    <string name="not_connected">袧械 锌褨写\'褦写薪邪薪芯</string>
+    <string name="connecting">袟\'褦写薪邪薪薪褟</string>
+    <string name="connected">袩褨写\'褦写薪邪薪芯</string>
+    <string name="unknown_state">袧械胁褨写芯屑懈泄 褋褌邪薪</string>
+    <string name="_unknown_">(薪械胁褨写芯屑芯)</string>
+    <string name="test">孝械褋褌褍胁邪薪薪褟</string>
+    <string name="test_notification">孝械褋褌芯胁械 褋锌芯胁褨褖械薪薪褟</string>
+    <string name="this_is_a_test_notification_from_gadgetbridge">笑械 褌械褋褌芯胁械 褋锌芯胁褨褖械薪薪褟 胁褨写 Gadgetbridge</string>
+    <string name="bluetooth_is_not_supported_">Bluetooth 薪械 锌褨写褌褉懈屑褍褦褌褜褋褟.</string>
+    <string name="bluetooth_is_disabled_">Bluetooth 胁懈屑泻薪褍褌芯.</string>
+    <string name="cannot_connect_bt_address_invalid_">袧械 胁写邪谢芯褋褟 蟹\'褦写薪邪褌懈褋褟. 袧械锌褉邪胁懈谢褜薪邪 邪写褉械褋邪 Bluetooth?</string>
+    <string name="gadgetbridge_running_generic">Gadgetbridge 蟹邪锌褍褖械薪芯</string>
+    <string name="installing_binary_d_d">袙褋褌邪薪芯胁谢械薪薪褟 写胁褨泄泻芯胁芯谐芯 褎邪泄谢褍 %1$d/%2$d</string>
+    <string name="installation_failed_">袧械 胁写邪谢芯褋褟 胁褋褌邪薪芯胁懈褌懈</string>
+    <string name="installation_successful">袙褋褌邪薪芯胁谢械薪芯</string>
+    <string name="firmware_install_warning">袙袠 袧袗袦袗袚袗袆孝袝小携 袙小孝袗袧袨袙袠孝袠 袦袉袣袪袨袩袪袨袚袪袗袦校, 袩袪袨袛袨袙袞校袡孝袝 袧袗 袙袥袗小袧袠袡 袪袠袟袠袣.
+\n
+\n
+\n笑褟 屑褨泻褉芯锌褉芯谐褉邪屑邪 写谢褟 邪锌邪褉邪褌薪芯谐芯 蟹邪斜械蟹锌械褔械薪薪褟 胁械褉褋褨褩: %s</string>
+    <string name="app_install_info">袙懈 蟹斜懈褉邪褦褌械褋褟 胁褋褌邪薪芯胁懈褌懈 褑械泄 蟹邪褋褌芯褋褍薪芯泻:
+\n
+\n%1$s
+\n袙械褉褋褨褟 %2$s 胁褨写 %3$s
+\n</string>
+    <string name="n_a">袧械写芯褋褌褍锌薪芯</string>
+    <string name="initialized">褨薪褨褑褨邪谢褨蟹芯胁邪薪芯</string>
+    <string name="appversion_by_creator">%1$s 胁褨写 %2$s</string>
+    <string name="title_activity_discovery">袙懈褟胁谢械薪薪褟 锌褉懈褋褌褉芯褞</string>
+    <string name="discovery_stop_scanning">袩褉懈锌懈薪懈褌懈 胁懈褟胁谢械薪薪褟</string>
+    <string name="discovery_start_scanning">袩芯褔邪褌懈 胁懈褟胁谢械薪薪褟</string>
+    <string name="action_discover">袟\'褦写薪邪褌懈 薪芯胁懈泄 锌褉懈褋褌褉褨泄</string>
+    <string name="device_with_rssi">%1$s (%2$s)</string>
+    <string name="title_activity_android_pairing">小褌胁芯褉械薪薪褟 锌邪褉懈 蟹 锌褉懈褋褌褉芯褦屑</string>
+    <string name="android_pairing_hint">袙懈泻芯褉懈褋褌芯胁褍泄褌械 写褨邪谢芯谐芯胁械 胁褨泻薪芯 Bluetooth, 褖芯斜 褋褌胁芯褉懈褌懈 锌邪褉褍 蟹 锌褉懈褋褌褉芯褦屑.</string>
+    <string name="title_activity_mi_band_pairing">小褌胁芯褉械薪薪褟 锌邪褉懈 蟹 胁邪褕懈屑 Mi Band</string>
+    <string name="message_cannot_pair_no_mac">袧械屑邪褦 MAC-邪写褉械褋褍, 薪械 胁写邪谢芯褋褟 褋褌胁芯褉懈褌懈 锌邪褉褍.</string>
+    <string name="preferences_category_device_specific_settings">小锌械褑懈褎褨褔薪褨 薪邪谢邪褕褌褍胁邪薪薪褟 锌褉懈褋褌褉芯褞</string>
+    <string name="preferences_miband_settings">袧邪谢邪褕褌褍胁邪薪薪褟 Mi Band / Amazfit</string>
+    <string name="male">效芯谢芯胁褨褔邪</string>
+    <string name="female">袞褨薪芯褔邪</string>
+    <string name="other">袉薪褕邪</string>
+    <string name="left">袥褨胁褨泄</string>
+    <string name="right">袩褉邪胁褨泄</string>
+    <string name="miband_pairing_using_dummy_userdata">袧械写芯褋褌邪褌薪褜芯 写褨泄褋薪懈褏 写邪薪懈褏 锌褉芯 泻芯褉懈褋褌褍胁邪褔邪, 薪邪褉邪蟹褨 胁懈泻芯褉懈褋褌芯胁褍褞褌褜褋褟 褍褋褌邪谢械薪褨 写邪薪褨 锌褉芯 泻芯褉懈褋褌褍胁邪褔邪.</string>
+    <string name="miband_pairing_tap_hint">袣芯谢懈 胁邪褕 Mi Band 胁褨斜褉褍胁邪褌懈屑械 泄 斜谢懈屑邪褌懈屑械, 褌芯褉泻薪褨褌褜褋褟 泄芯谐芯 泻褨谢褜泻邪 褉邪蟹褨胁.</string>
+    <string name="appinstaller_install">校褋褌邪薪芯胁懈褌懈</string>
+    <string name="discovery_note">袩褉懈屑褨褌泻邪:</string>
+    <string name="candidate_item_device_image">袟芯斜褉邪卸械薪薪褟 锌褉懈褋褌褉芯褞</string>
+    <string name="miband_prefs_alias">袉屑\'褟 邪斜芯 锌褋械胁写芯薪褨屑</string>
+    <string name="pref_header_vibration_count">袣褨谢褜泻褨褋褌褜 胁褨斜褉邪褑褨泄</string>
+    <string name="title_activity_sleepmonitor">小锌芯褋褌械褉械卸械薪薪褟 蟹邪 褋薪芯屑</string>
+    <string name="initializing">袉薪褨褑褨邪谢褨蟹邪褑褨褟</string>
+    <string name="busy_task_fetch_activity_data">袨褌褉懈屑邪薪薪褟 写邪薪懈褏 锌褉芯 写褨褟谢褜薪褨褋褌褜</string>
+    <string name="sleep_activity_date_range">袙褨写 %1$s 写芯 %2$s</string>
+    <string name="prefs_wearside">袧邪 褟泻褨泄 褉褍褑褨 薪芯褋懈褌械?</string>
+    <string name="pref_screen_vibration_profile">袩褉芯褎褨谢褜 胁褨斜褉邪褑褨褩</string>
+    <string name="vibration_profile_staccato">小褌邪泻邪褌芯</string>
+    <string name="vibration_profile_short">袣芯褉芯褌泻懈泄</string>
+    <string name="vibration_profile_medium">小械褉械写薪褨泄</string>
+    <string name="vibration_profile_long">孝褉懈胁邪谢懈泄</string>
+    <string name="vibration_profile_waterdrop">袣褉邪锌谢褟 胁芯写懈</string>
+    <string name="vibration_profile_ring">袛蟹胁褨薪芯泻</string>
+    <string name="vibration_profile_alarm_clock">袘褍写懈谢褜薪懈泻</string>
+    <string name="miband_prefs_vibration">袙褨斜褉邪褑褨褟</string>
+    <string name="pref_screen_notification_profile_sms">SMS 褋锌芯胁褨褖械薪薪褟</string>
+    <string name="pref_header_vibration_settings">袧邪谢邪褕褌褍胁邪薪薪褟 胁褨斜褉邪褑褨褩</string>
+    <string name="pref_screen_notification_profile_generic">袟邪谐邪谢褜薪褨 褋锌芯胁褨褖械薪薪褟</string>
+    <string name="pref_screen_notification_profile_incoming_call">小锌芯胁褨褖械薪薪褟 胁褏褨写薪芯谐芯 胁懈泻谢懈泻褍</string>
+    <string name="pref_screen_notification_profile_generic_chat">效邪褌</string>
+    <string name="pref_screen_notification_profile_generic_navigation">袧邪胁褨谐邪褑褨褟</string>
+    <string name="pref_screen_notification_profile_generic_social">小芯褑褨邪谢褜薪褨 屑械褉械卸褨</string>
+    <string name="control_center_find_lost_device">袟薪邪泄褌懈 蟹邪谐褍斜谢械薪懈泄 锌褉懈褋褌褉褨泄</string>
+    <string name="control_center_cancel_to_stop_vibration">小泻邪褋褍泄褌械, 褖芯斜 锌褉懈锌懈薪懈褌懈 胁褨斜褉邪褑褨褞.</string>
+    <string name="title_activity_charts">袛褨褟谢褜薪褨褋褌褜 褌邪 褋芯薪</string>
+    <string name="title_activity_set_alarm">袧邪谢邪褕褌褍胁邪褌懈 斜褍写懈谢褜薪懈泻</string>
+    <string name="controlcenter_start_configure_alarms">袧邪谢邪褕褌褍胁邪褌懈 斜褍写懈谢褜薪懈泻懈</string>
+    <string name="title_activity_alarm_details">袩芯写褉芯斜懈褑褨 斜褍写懈谢褜薪懈泻邪</string>
+    <string name="alarm_sun_short">袧写</string>
+    <string name="alarm_mon_short">袩薪</string>
+    <string name="alarm_tue_short">袙褌</string>
+    <string name="alarm_wed_short">小褉</string>
+    <string name="alarm_thu_short">效褌</string>
+    <string name="alarm_fri_short">袩褌</string>
+    <string name="alarm_sat_short">小斜</string>
+    <string name="alarm_smart_wakeup">袪芯蟹褍屑薪械 锌褉芯斜褍写卸械薪薪褟</string>
+    <string name="user_feedback_miband_set_alarms_failed">袙懈薪懈泻谢邪 锌芯屑懈谢泻邪 锌褨写 褔邪褋 胁褋褌邪薪芯胁谢械薪薪褟 斜褍写懈谢褜薪懈泻褨胁, 褋锌褉芯斜褍泄褌械 褖械 褉邪蟹.</string>
+    <string name="user_feedback_miband_set_alarms_ok">袘褍写懈谢褜薪懈泻 薪邪写褨褋谢邪薪芯 薪邪 锌褉懈褋褌褉褨泄.</string>
+    <string name="chart_no_data_synchronize">袛邪薪褨 胁褨写褋褍褌薪褨. 小懈薪褏褉芯薪褨蟹褍胁邪褌懈 锌褉懈褋褌褉褨泄?</string>
+    <string name="user_feedback_miband_activity_data_transfer">袘褍写械 锌械褉械写邪薪芯 %1$s 写邪薪懈褏, 锌芯褔懈薪邪褞褔懈 蟹 %2$s</string>
+    <string name="miband_prefs_fitness_goal">些芯写械薪薪邪 屑械褌邪 泻褉芯泻褨胁</string>
+    <string name="dbaccess_error_executing">袩芯屑懈谢泻邪 胁懈泻芯薪邪薪薪褟 \'%1$s\'</string>
+    <string name="controlcenter_start_activitymonitor">袙邪褕邪 写褨褟谢褜薪褨褋褌褜</string>
+    <string name="cannot_connect">袧械屑芯卸谢懈胁芯 蟹\'褦写薪邪褌懈褋褟: %1$s</string>
+    <string name="installer_activity_unable_to_find_handler">袧械屑芯卸谢懈胁芯 蟹薪邪泄褌懈 芯斜褉芯斜薪懈泻 锌芯写褨泄 写谢褟 胁褋褌邪薪芯胁谢械薪薪褟 褑褜芯谐芯 褎邪泄谢褍.</string>
+    <string name="pbw_install_handler_unable_to_install">袧械屑芯卸谢懈胁芯 胁褋褌邪薪芯胁懈褌懈 褑械泄 褎邪泄谢: %1$s</string>
+    <string name="pbw_install_handler_hw_revision_mismatch">袧械屑芯卸谢懈胁芯 胁褋褌邪薪芯胁懈褌懈 褑褞 屑褨泻褉芯锌褉芯谐褉邪屑褍: 胁芯薪邪 薪械 胁褨写锌芯胁褨写邪褦 胁械褉褋褨褩 邪锌邪褉邪褌薪芯谐芯 蟹邪斜械蟹锌械褔械薪薪褟 胁邪褕芯谐芯 Pebble.</string>
+    <string name="installer_activity_wait_while_determining_status">袘褍写褜 谢邪褋泻邪, 蟹邪褔械泻邪泄褌械, 褌褉懈胁邪褦 胁懈蟹薪邪褔械薪薪褟 褋褌邪薪褍 胁褋褌邪薪芯胁谢械薪薪褟鈥�</string>
+    <string name="notif_battery_low_title">袗泻褍屑褍谢褟褌芯褉 锌褉懈褋褌褉芯褞 褉芯蟹褉褟写卸械薪懈泄!</string>
+    <string name="notif_battery_low_percent">%1$s 蟹邪褉褟写褍 谢懈褕懈谢芯褋褜: %2$s%%</string>
+    <string name="notif_battery_low_bigtext_last_charge_time">袨褋褌邪薪薪褦 蟹邪褉褟写卸邪薪薪褟: %s
+\n</string>
+    <string name="notif_battery_low_bigtext_number_of_charges">袣褨谢褜泻褨褋褌褜 蟹邪褉褟写卸邪薪褜: %s</string>
+    <string name="sleepchart_your_sleep">小芯薪</string>
+    <string name="weekstepschart_steps_a_week">袣褉芯泻褨胁 蟹邪 褌懈卸写械薪褜</string>
+    <string name="activity_sleepchart_activity_and_sleep">袛褨褟谢褜薪褨褋褌褜</string>
+    <string name="updating_firmware">袟邪胁邪薪褌邪卸械薪薪褟 屑褨泻褉芯锌褉芯谐褉邪屑懈鈥�</string>
+    <string name="fwapp_install_device_not_ready">袧械屑芯卸谢懈胁芯 胁褋褌邪薪芯胁懈褌懈 褎邪泄谢, 锌褉懈褋褌褉褨泄 薪械 谐芯褌芯胁懈泄.</string>
+    <string name="installhandler_firmware_name">%1$s: %2$s %3$s</string>
+    <string name="miband_fwinstaller_compatible_version">小褍屑褨褋薪邪 胁械褉褋褨褟</string>
+    <string name="miband_fwinstaller_untested_version">袧械锌械褉械胁褨褉械薪邪 胁械褉褋褨褟!</string>
+    <string name="fwappinstaller_connection_state">袟\'褦写薪邪薪薪褟 蟹 锌褉懈褋褌褉芯褦屑: %1$s</string>
+    <string name="pbw_installhandler_pebble_firmware">袦褨泻褉芯锌褉芯谐褉邪屑邪 Pebble %1$s</string>
+    <string name="pbwinstallhandler_correct_hw_revision">袩褉邪胁懈谢褜薪邪 胁械褉褋褨褟 邪锌邪褉邪褌薪芯谐芯 蟹邪斜械蟹锌械褔械薪薪褟</string>
+    <string name="pbwinstallhandler_incorrect_hw_revision">袙械褉褋褨褩 邪锌邪褉邪褌薪芯谐芯 蟹邪斜械蟹锌械褔械薪薪褟 薪械 蟹斜褨谐邪褞褌褜褋褟!</string>
+    <string name="pbwinstallhandler_app_item">%1$s (%2$s)</string>
+    <string name="updatefirmwareoperation_updateproblem_do_not_reboot">袙懈薪懈泻谢懈 锌褉芯斜谢械屑邪 锌褨写 褔邪褋 锌械褉械写邪胁邪薪薪褟 屑褨泻褉芯锌褉芯谐褉邪屑懈. 袧袝 袩袝袪袝袟袗袙袗袧孝袗袞校袡孝袝 褋胁褨泄 Mi Band!</string>
+    <string name="updatefirmwareoperation_metadata_updateproblem">袩褉芯斜谢械屑邪 锌械褉械写邪褔褨 屑械褌邪写邪薪懈褏 屑褨泻褉芯锌褉芯谐褉邪屑懈</string>
+    <string name="updatefirmwareoperation_update_complete">袙褋褌邪薪芯胁谢械薪薪褟 屑褨泻褉芯锌褉芯谐褉邪屑懈 蟹邪胁械褉褕械薪芯</string>
+    <string name="updatefirmwareoperation_update_complete_rebooting">袙褋褌邪薪芯胁谢械薪薪褟 屑褨泻褉芯锌褉芯谐褉邪屑懈 蟹邪胁械褉褕械薪芯, 锌械褉械蟹邪胁邪薪褌邪卸械薪薪褟 锌褉懈褋褌褉芯褞鈥�</string>
+    <string name="updatefirmwareoperation_write_failed">袧械 胁写邪谢芯褋褟 胁褋褌邪薪芯胁懈褌懈 屑褨泻褉芯锌褉芯谐褉邪屑褍</string>
+    <string name="chart_steps">袣褉芯泻懈</string>
+    <string name="liveactivity_live_activity">袞懈褌褌褦胁邪 写褨褟谢褜薪褨褋褌褜</string>
+    <string name="weeksteps_today_steps_description">袣褉芯泻褨胁 褋褜芯谐芯写薪褨, 屑械褌邪: %1$s</string>
+    <string name="pref_title_dont_ack_transfer">袧械 锌械褉械写邪胁邪褌懈 写邪薪褨 锌褉芯 写褨褟谢褜薪褨褋褌褜</string>
+    <string name="pref_summary_dont_ack_transfers">携泻褖芯 写邪薪褨 锌褉芯 写褨褟谢褜薪褨褋褌褜 薪械 斜褍写褍褌褜 锌械褉械写邪薪褨 薪邪 斜褉邪褋谢械褌, 胁芯薪懈 薪械 斜褍写褍褌褜 芯褔懈褖械薪褨. 袣芯褉懈褋薪芯, 褟泻褖芯 GB 胁懈泻芯褉懈褋褌芯胁褍褦褌褜褋褟 褉邪蟹芯屑 蟹 褨薪褕懈屑懈 蟹邪褋褌芯褋褍薪泻邪屑懈.</string>
+    <string name="pref_summary_keep_data_on_device">袛邪薪褨 锌褉芯 写褨褟谢褜薪褨褋褌褜 蟹斜械褉褨谐邪褌懈屑褍褌褜褋褟 薪邪 锌褉懈褋褌褉芯褩 薪邪胁褨褌褜 锌褨褋谢褟 褋懈薪褏褉芯薪褨蟹邪褑褨褩. 袣芯褉懈褋薪芯, 褟泻褖芯 GB 胁懈泻芯褉懈褋褌芯胁褍褦褌褜褋褟 褉邪蟹芯屑 蟹 褨薪褕懈屑懈 蟹邪褋褌芯褋褍薪泻邪屑懈. 笑械 屑芯卸械 锌褉懈蟹胁械褋褌懈 写芯 褌芯谐芯, 褖芯 谐芯写懈薪薪懈泻 蟹邪锌芯胁薪懈褌褜褋褟 泄/邪斜芯 锌械褉械褋褌邪薪械 薪邪谢械卸薪懈屑 褔懈薪芯屑 褋懈薪褏褉芯薪褨蟹褍胁邪褌懈褋褟.</string>
+    <string name="live_activity_steps_history">袉褋褌芯褉褨褟 泻褉芯泻褨胁</string>
+    <string name="live_activity_current_steps_per_minute">袩芯褌芯褔薪褨 泻褉芯泻懈/褏胁</string>
+    <string name="live_activity_total_steps">袟邪谐邪谢芯屑 泻褉芯泻褨胁</string>
+    <string name="live_activity_steps_per_minute_history">袉褋褌芯褉褨褟 泻褉芯泻褨胁 蟹邪 褏胁懈谢懈薪褍</string>
+    <string name="live_activity_start_your_activity">袩芯褔薪褨褌褜 胁邪褕褍 写褨褟谢褜薪褨褋褌褜</string>
+    <string name="abstract_chart_fragment_kind_activity">袛褨褟谢褜薪褨褋褌褜</string>
+    <string name="abstract_chart_fragment_kind_light_sleep">楔胁懈写泻懈泄 褋芯薪</string>
+    <string name="abstract_chart_fragment_kind_deep_sleep">袚谢懈斜芯泻懈泄 褋芯薪</string>
+    <string name="abstract_chart_fragment_kind_not_worn">袧械 芯写褟谐薪械薪芯</string>
+    <string name="device_not_connected">袧械 锌褨写\'褦写薪邪薪芯.</string>
+    <string name="user_feedback_all_alarms_disabled">袙褋褨 斜褍写懈谢褜薪懈泻懈 胁懈屑泻薪械薪芯</string>
+    <string name="pref_title_keep_data_on_device">袥懈褕邪褌懈 写邪薪褨 锌褉芯 写褨褟谢褜薪褨褋褌褜 薪邪 锌褉懈褋褌褉芯褩</string>
+    <string name="miband_fwinstaller_incompatible_version">袦褨泻褉芯锌褉芯谐褉邪屑邪 薪械褋褍屑褨褋薪邪</string>
+    <string name="fwinstaller_firmware_not_compatible_to_device">笑褟 屑褨泻褉芯锌褉芯谐褉邪屑邪 薪械褋褍屑褨褋薪邪 蟹 褑懈屑 锌褉懈褋褌褉芯褦屑</string>
+    <string name="waiting_for_reconnect">袨褔褨泻褍胁邪薪薪褟 薪邪 锌械褉械蟹\'褦写薪邪薪薪褟</string>
+    <string name="activity_prefs_about_you">袩褉芯 胁邪褋</string>
+    <string name="activity_prefs_year_birth">袪褨泻 薪邪褉芯写卸械薪薪褟</string>
+    <string name="activity_prefs_gender">小褌邪褌褜</string>
+    <string name="activity_prefs_height_cm">袟褉褨褋褌 褍 褋邪薪褌懈屑械褌褉邪褏</string>
+    <string name="activity_prefs_weight_kg">袙邪谐邪 褍 泻褨谢芯谐褉邪屑邪褏</string>
+    <string name="add_widget">袛芯写邪褌懈 胁褨写卸械褌</string>
+    <string name="device_hw">袙械褉褋褨褟 邪锌邪褉邪褌薪芯谐芯 蟹邪斜械蟹锌械褔械薪薪褟: %1$s</string>
+    <string name="device_fw">袙械褉褋褨褟 屑褨泻褉芯锌褉芯谐褉邪屑懈: %1$s</string>
+    <string name="Cancel">小泻邪褋褍胁邪褌懈</string>
+    <string name="Delete">袙懈写邪谢懈褌懈</string>
+    <!--Strings related to Vibration Activity-->
+    <string name="title_activity_vibration">袙褨斜褉邪褑褨褟</string>
+    <!--Strings related to Pebble Pairing Activity-->
+    <string name="timeformat_24h">24 谐芯写.</string>
+    <string name="timeformat_am_pm">袛袩/袩袩</string>
+    <string name="action_donate">袛芯锌芯屑芯谐褌懈</string>
+    <string name="controlcenter_connect">袟\'褦写薪邪薪薪褟鈥�</string>
+    <string name="controlcenter_snackbar_connecting">袟\'褦写薪邪薪薪褟鈥�</string>
+    <string name="notification_channel_name">袟邪谐邪谢褜薪褨</string>
+    <string name="controlcenter_navigation_drawer_open">袙褨写泻褉懈褌懈 锌邪薪械谢褜 薪邪胁褨谐邪褑褨褩</string>
+    <string name="controlcenter_navigation_drawer_close">袟邪泻褉懈褌懈 锌邪薪械谢褜 薪邪胁褨谐邪褑褨褩</string>
+    <string name="controlcenter_snackbar_disconnecting">袙褨写\'褦写薪邪薪薪褟</string>
+    <string name="controlcenter_snackbar_need_longpress">袟邪褌懈褋薪褨褌褜, 褖芯斜 胁褨写\'褦写薪邪褌懈</string>
+    <string name="controlcenter_snackbar_requested_screenshot">袪芯斜懈褌褜褋褟 蟹薪褨屑芯泻 械泻褉邪薪邪 锌褉懈褋褌褉芯褞</string>
+    <string name="appmanager_cached_watchapps_watchfaces">袣械褕芯胁邪薪褨 蟹邪褋褌芯褋褍薪泻懈</string>
+    <string name="appmanager_installed_watchfaces">袙褋褌邪薪芯胁谢械薪褨 褑懈褎械褉斜谢邪褌懈</string>
+    <string name="appmanager_weather_activate">校胁褨屑泻薪褍褌懈 褋懈褋褌械屑薪懈泄 蟹邪褋褌芯褋褍薪芯泻 锌芯谐芯写懈</string>
+    <string name="appmanager_weather_deactivate">袙懈屑泻薪褍褌懈 褋懈褋褌械屑薪懈泄 蟹邪褋褌芯褋褍薪芯泻 锌芯谐芯写懈</string>
+    <string name="appmanager_weather_install_provider">袙褋褌邪薪芯胁懈褌懈 蟹邪褋褌芯褋褍薪芯泻 Weather Notification</string>
+    <string name="app_move_to_top">袩械褉械褋褍薪褍褌懈 胁谐芯褉褍</string>
+    <string name="blacklist_all_for_notifications">袟邪斜谢芯泻褍胁邪褌懈 胁褋褨 褋锌芯胁褨褖械薪薪褟</string>
+    <string name="whitelist_all_for_notifications">袛芯蟹胁芯谢懈褌懈 胁褋褨 褋锌芯胁褨褖械薪薪褟</string>
+    <string name="title_activity_calblacklist">效芯褉薪懈泄 褋锌懈褋芯泻 泻邪谢械薪写邪褉褨胁</string>
+    <string name="fw_upgrade_notice_amazfitbip">袙懈 蟹斜懈褉邪褦褌械褋褟 胁褋褌邪薪芯胁懈褌懈 屑褨泻褉芯锌褉芯谐褉邪屑褍 %s 薪邪 胁邪褕 Amazfit Bip.
+\n
+\n小锌芯褔邪褌泻褍 胁褋褌邪薪芯胁褨褌褜 褎邪泄谢 .fw, 锌芯褌褨屑 .res, 邪 胁 泻褨薪褑褨 .gps. 袙邪褕 谐芯写懈薪薪懈泻 锌械褉械蟹邪胁邪薪褌邪卸懈褌褜褋褟 锌褨褋谢褟 胁褋褌邪薪芯胁谢械薪薪褟 褎邪泄谢褍 .fw.
+\n
+\n袩褉懈屑褨褌泻邪: 胁邪屑 薪械 锌芯褌褉褨斜薪芯 胁褋褌邪薪芯胁谢褞胁邪褌懈 褎邪泄谢懈 .res 褌邪 .gps, 褟泻褖芯 胁芯薪懈 褌邪泻褨 卸 褋邪屑褨, 褖芯 斜褍谢懈 胁褋褌邪薪芯胁谢械薪褨 褉邪薪褨褕械.
+\n
+\n袩袪袨袛袨袙袞校袡孝袝 袧袗 袙袥袗小袧袠袡 袪袠袟袠袣!</string>
+    <string name="fw_upgrade_notice_amazfitcor">袙懈 蟹斜懈褉邪褦褌械褋褟 胁褋褌邪薪芯胁懈褌懈 屑褨泻褉芯锌褉芯谐褉邪屑褍 %s 薪邪 胁邪褕 Amazfit Cor.
+\n
+\n小锌芯褔邪褌泻褍 胁褋褌邪薪芯胁褨褌褜 褎邪泄谢 .fw, 锌芯褌褨屑 .res. 袙邪褕 谐芯写懈薪薪懈泻 锌械褉械蟹邪胁邪薪褌邪卸懈褌褜褋褟 锌褨褋谢褟 胁褋褌邪薪芯胁谢械薪薪褟 褎邪泄谢褍 .fw.
+\n
+\n袩褉懈屑褨褌泻邪: 胁邪屑 薪械 锌芯褌褉褨斜薪芯 胁褋褌邪薪芯胁谢褞胁邪褌懈 褎邪泄谢 .res, 褟泻褖芯 胁褨薪 褌邪泻懈泄 褋邪屑懈泄, 褟泻 褉邪薪褨褕械 胁褋褌邪薪芯胁谢械薪懈泄.
+\n
+\n袩袪袨袛袨袙袞校袡孝袝 袧袗 袙袥袗小袧袠袡 袪袠袟袠袣!</string>
+    <string name="fw_upgrade_notice_miband3">袙懈 蟹斜懈褉邪褦褌械褋褟 胁褋褌邪薪芯胁懈褌懈 屑褨泻褉芯锌褉芯谐褉邪屑褍 %s 薪邪 胁邪褕 Mi Band 3.
+\n
+\n小锌芯褔邪褌泻褍 胁褋褌邪薪芯胁褨褌褜 褎邪泄谢 .fw, 锌芯褌褨屑 .res. 袙邪褕 谐芯写懈薪薪懈泻 锌械褉械蟹邪胁邪薪褌邪卸褌褜褋褟 锌褨褋谢褟 胁褋褌邪薪芯胁谢械薪薪褟 褎邪泄谢褍 .fw.
+\n
+\n袩褉懈屑褨褌泻邪: 胁邪屑 薪械 锌芯褌褉褨斜薪芯 胁褋褌邪薪芯胁谢褞胁邪褌懈 .res, 褟泻褖芯 胁褨薪 褌邪泻懈泄 褋邪屑懈泄, 褟泻 褉邪薪褨褕械 胁褋褌邪薪芯胁谢械薪懈泄.
+\n
+\n袩袪袨袛袨袙袞校袡孝袝 袧袗 袙袥袗小袧袠袡 袪袠袟袠袣!</string>
+    <string name="pref_title_charts_swipe">校胁褨屑泻薪褍褌懈 屑芯卸谢懈胁褨褋褌褜 谐芯褉褌邪薪薪褟 谢褨胁芯褉褍褔/锌褉邪胁芯褉褍褔 褍 写褨邪谐褉邪屑邪褏 写褨褟谢褜薪芯褋褌褨</string>
+    <string name="pref_title_minimize_priority">小褏芯胁邪褌懈 褋锌芯胁褨褖械薪薪褟 Gadgetbridge</string>
+    <string name="pref_summary_minimize_priority_off">袩褨泻褌芯谐褉邪屑邪 胁 褉褟写泻褍 褋褌邪薪褍 褌邪 褋锌芯胁褨褖械薪薪褟 薪邪 械泻褉邪薪褨 斜谢芯泻褍胁邪薪薪褟 胁褨写芯斜褉邪卸邪褞褌褜褋褟</string>
+    <string name="pref_summary_minimize_priority_on">袩褨泻褌芯谐褉邪屑邪 胁 褉褟写泻褍 褋褌邪薪褍 褌邪 褋锌芯胁褨褖械薪薪褟 薪邪 械泻褉邪薪褨 斜谢芯泻褍胁邪薪薪褟 薪械 胁褨写芯斜褉邪卸邪褞褌褜褋褟</string>
+    <string name="pref_summary_notifications_pebblemsg">袩褨写褌褉懈屑泻邪 蟹邪褋褌芯褋褍薪泻褨胁, 褟泻褨 薪邪写褋懈谢邪褞褌褜 褋锌芯胁褨褖械薪薪褟 薪邪 Pebble 褔械褉械蟹 PebbleKit.</string>
+    <string name="pref_summary_notification_filter">袘谢芯泻褍胁邪褌懈 胁褋褨 褋锌芯胁褨褖械薪薪褟, 褟泻褖芯 薪邪 褌械谢械褎芯薪褨 胁胁褨屑泻薪械薪芯 褉械卸懈屑 \"袧械 褌褍褉斜褍胁邪褌懈\"</string>
+    <string name="pref_title_transliteration">孝褉邪薪褋谢褨褌械褉邪褑褨褟</string>
+    <string name="pref_summary_transliteration">校胁褨屑泻薪褨褌褜 褑械, 褟泻褖芯 胁邪褕 锌褉懈褋褌褉褨泄 薪械 锌褨写褌褉懈屑褍褦 褕褉懈褎褌 胁邪褕芯褩 屑芯胁懈</string>
+    <string name="pref_title_call_privacy_mode">袪械卸懈屑 锌褉懈胁邪褌薪芯褋褌褨 写谢褟 胁懈泻谢懈泻褨胁</string>
+    <string name="pref_call_privacy_mode_off">袩芯泻邪蟹褍胁邪褌懈 褨屑\'褟 褌邪 薪芯屑械褉</string>
+    <string name="pref_call_privacy_mode_name">袩褉懈褏芯胁褍胁邪褌懈 褨屑\'褟, 邪谢械 锌芯泻邪蟹褍胁邪褌懈 薪芯屑械褉</string>
+    <string name="pref_call_privacy_mode_number">袩褉懈褏芯胁褍胁邪褌懈 薪芯屑械褉, 邪谢械 锌芯泻邪蟹褍胁邪褌懈 褨屑\'褟</string>
+    <string name="pref_call_privacy_mode_complete">袩褉懈褏芯胁褍胁邪褌懈 褨屑\'褟 褌邪 薪芯屑械褉</string>
+    <string name="pref_title_weather">袩芯谐芯写邪</string>
+    <string name="pref_title_weather_location">袪芯蟹褌邪褕褍胁邪薪薪褟 锌芯谐芯写懈 (写谢褟 LineageOS)</string>
+    <string name="pref_blacklist_calendars">袟邪斜谢芯泻芯胁邪薪褨 泻邪谢械薪写邪褉褨</string>
+    <string name="pref_header_cannned_messages">袟邪谐芯褌芯胁谢械薪褨 锌芯胁褨写芯屑谢械薪薪褟</string>
+    <string name="pref_title_canned_messages_dismisscall">袙褨写褏懈谢械薪褨 胁懈泻谢懈泻懈</string>
+    <string name="pref_title_canned_messages_set">袨薪芯胁懈褌懈 薪邪 锌褉懈褋褌褉芯褩</string>
+    <string name="pref_title_enable_outgoing_call">袩褨写褌褉懈屑泻邪 胁懈褏褨写薪懈褏 胁懈泻谢懈泻褨胁</string>
+    <string name="pref_summary_enable_outgoing_call">袙懈屑泻薪械薪薪褟 褑褜芯谐芯 锌邪褉邪屑械褌褉邪 锌褉懈蟹胁械写械 写芯 锌褉懈锌懈薪械薪薪褟 胁褨斜褉邪褑褨褩 薪邪 Pebble 2/LE 锌褉懈 胁懈褏褨写薪懈褏 胁懈泻谢懈泻邪褏</string>
+    <string name="pref_header_pebble_timeline">效邪褋芯胁邪 褕泻邪谢邪 Pebble</string>
+    <string name="pref_title_enable_calendar_sync">小懈薪褏褉芯薪褨蟹褍胁邪褌懈 泻邪谢械薪写邪褉</string>
+    <string name="pref_summary_enable_calendar_sync">袧邪写褋懈谢邪褌懈 锌芯写褨褩 泻邪谢械薪写邪褉褟 写芯 褔邪褋芯胁芯褩 褕泻邪谢懈</string>
+    <string name="pref_auto_fetch_summary">袨褌褉懈屑褍胁邪薪薪褟 胁褨写斜褍胁邪褦褌褜褋褟 锌褨写 褔邪褋 褉芯蟹斜谢芯泻褍胁邪薪薪褟 械泻褉邪薪邪. 袩褉邪褑褞褦 谢懈褕械 褟泻褖芯 薪邪谢邪褕褌芯胁邪薪械 斜谢芯泻褍胁邪薪薪褟!</string>
+    <string name="notif_export_failed_title">袧械 胁写邪谢芯褋褟 械泻褋锌芯褉褌褍胁邪褌懈 斜邪蟹褍 写邪薪懈褏! 袘褍写褜 谢邪褋泻邪, 锌械褉械胁褨褉褌械 胁邪褕褨 薪邪谢邪褕褌褍胁邪薪薪褟.</string>
+    <string name="miband2_prefs_dateformat">肖芯褉屑邪褌 写邪褌懈</string>
+    <string name="DEVINFO_HR_VER">"袩褍谢褜褋: "</string>
+    <string name="menuitem_alipay">Alipay</string>
+    <string name="controlcenter_calibrate_device">袙褨写泻邪谢褨斜褉褍胁邪褌懈 锌褉懈褋褌褉褨泄</string>
+    <string name="pref_title_notifications_timeout">袦褨薪褨屑邪谢褜薪懈泄 褔邪褋 屑褨卸 褋锌芯胁褨褖械薪薪褟屑懈</string>
+    <string name="pref_title_autoremove_notifications">袗胁褌芯屑邪褌懈褔薪芯 胁懈写邪谢褟褌懈 胁褨写褏懈谢械薪褨 褋锌芯胁褨褖械薪薪褟</string>
+    <string name="pref_summary_autoremove_notifications">小锌芯胁褨褖械薪薪褟 邪胁褌芯屑邪褌懈褔薪芯 胁懈写邪谢褟褞褌褜褋褟 蟹 锌褉懈褋褌褉芯褞, 泻芯谢懈 胁芯薪懈 胁褨写褏懈谢械薪褨 薪邪 褌械谢械褎芯薪褨</string>
+    <string name="pref_title_pebble_privacy_mode">袪械卸懈屑 锌褉懈胁邪褌薪芯褋褌褨</string>
+    <string name="pref_pebble_privacy_mode_off">袟胁懈褔邪泄薪褨 褋锌芯胁褨褖械薪薪褟</string>
+    <string name="pref_pebble_privacy_mode_complete">袩芯泻邪蟹褍胁邪褌懈 谢懈褕械 锌褨泻褌芯谐褉邪屑褍 褋锌芯胁褨褖械薪薪褟</string>
+    <string name="pref_title_location_aquire">袙懈蟹薪邪褔懈褌懈 屑褨褋褑械锌械褉械斜褍胁邪薪薪褟</string>
+    <string name="pref_title_location_keep_uptodate">袨薪芯胁谢褞胁邪褌懈 屑褨褋褑械锌械褉械斜褍胁邪薪薪褟</string>
+    <string name="pref_summary_location_keep_uptodate">袩褉芯斜褍胁邪褌懈 芯褌褉懈屑邪褌懈 锌芯褌芯褔薪械 屑褨褋褑械锌械褉械斜褍胁邪薪薪褟 锌褨写 褔邪褋 褉芯斜芯褌懈, 褍 胁懈锌邪写泻褍 薪械胁写邪褔褨 蟹邪褋褌芯褋芯胁褍褦褌褜褋褟 蟹斜械褉械卸械薪械 屑褨褋褑械锌械褉械斜褍胁邪薪薪褟, 褟泻 褉械蟹械褉胁薪械</string>
+    <string name="toast_enable_networklocationprovider">校胁褨屑泻薪褨褌褜 屑械褉械卸薪械 褉芯蟹褌邪褕褍胁邪薪薪褟</string>
+    <string name="toast_aqurired_networklocation">屑褨褋褑械锌械褉械斜褍胁邪薪薪褟 胁懈蟹薪邪褔械薪芯</string>
+    <string name="pref_title_pebble_enable_applogs">校胁褨屑泻薪褍褌懈 卸褍褉薪邪谢 蟹邪褋褌芯褋褍薪泻褨胁 谐芯写懈薪薪懈泻邪</string>
+    <string name="pref_title_pebble_enable_bgjs">校胁褨屑泻薪褍褌懈 褎芯薪芯胁懈泄 JS</string>
+    <string name="prefs_title_all_day_heart_rate">袙懈屑褨褉褞胁邪薪薪褟 锌褍谢褜褋褍 锌褉芯褌褟谐芯屑 写薪褟</string>
+    <string name="prefs_screen_orientation">袨褉褨褦薪褌邪褑褨褟 械泻褉邪薪邪</string>
+    <string name="pref_title_auto_export_enabled">袗胁褌芯屑邪褌懈褔薪懈泄 械泻褋锌芯褉褌 褍胁褨屑泻薪械薪芯</string>
+    <string name="tap_a_device_to_connect">孝芯褉泻薪褨褌褜褋褟 锌褉懈褋褌褉芯褞 写谢褟 蟹\'褦写薪邪薪薪褟</string>
+    <string name="horizontal">袚芯褉懈蟹芯薪褌邪谢褜薪邪</string>
+    <string name="vertical">袙械褉褌懈泻邪谢褜薪邪</string>
+    <string name="vibration_try">小锌褉芯斜褍胁邪褌懈</string>
+    <string name="pref_screen_notification_profile_email">小锌芯胁褨褖械薪薪褟 械-锌芯褕褌懈</string>
+    <string name="interval_one_minute">褉邪蟹 薪邪 褏胁懈谢懈薪褍</string>
+    <string name="interval_five_minutes">泻芯卸薪褨 5 褏胁懈谢懈薪</string>
+    <string name="interval_ten_minutes">泻芯卸薪褨 10 褏胁懈谢懈薪</string>
+    <string name="interval_thirty_minutes">泻芯卸薪褨 30 褏胁懈谢懈薪</string>
+    <string name="interval_one_hour">褉邪蟹 薪邪 谐芯写懈薪褍</string>
+    <string name="stats_y_axis_label">袣褉芯泻褨胁 蟹邪 褏胁懈谢懈薪褍</string>
+    <string name="calories">袣邪谢芯褉褨褩</string>
+    <string name="distance">袙褨写褋褌邪薪褜</string>
+    <string name="clock">袚芯写懈薪薪懈泻</string>
+    <string name="heart_rate">袩褍谢褜褋</string>
+    <string name="battery">袗泻褍屑褍谢褟褌芯褉</string>
+    <string name="no_limit">袘械蟹 芯斜屑械卸械薪褜</string>
+    <string name="seconds_5">5 褋械泻褍薪写</string>
+    <string name="seconds_10">10 褋械泻褍薪写</string>
+    <string name="seconds_20">20 褋械泻褍薪写</string>
+    <string name="seconds_30">30 褋械泻褍薪写</string>
+    <string name="minutes_1">1 褏胁懈谢懈薪邪</string>
+    <string name="minutes_5">5 褏胁懈谢懈薪</string>
+    <string name="minutes_10">10 褏胁懈谢懈薪</string>
+    <string name="minutes_30">30 褏胁懈谢懈薪</string>
+    <string name="pref_summary_low_latency_fw_update">笑械 屑芯卸械 写芯锌芯屑芯谐褌懈 薪邪 锌褉懈褋褌褉芯褟褏, 薪邪 褟泻懈褏 薪械 胁写邪褦褌褜褋褟 胁褋褌邪薪芯胁懈褌懈 屑褨泻褉芯锌褉芯谐褉邪屑褍.</string>
+    <string name="you_did_not_sleep">袙懈 薪械 褋锌邪谢懈</string>
+    <string name="miband_prefs_reserve_alarm_calendar">袘褍写懈谢褜薪懈泻懈 写谢褟 褉械蟹械褉胁褍胁邪薪薪褟 屑邪泄斜褍褌薪褨褏 锌芯写褨泄</string>
+    <string name="miband_prefs_hr_sleep_detection">袙懈泻芯褉懈褋褌芯胁褍胁邪褌懈 写邪褌褔懈泻 锌褍谢褜褋褍 写谢褟 锌芯谢褨锌褕械薪薪褟 胁懈褟胁谢械薪薪褟 褋薪褍</string>
+    <string name="dateformat_time">效邪褋</string>
+    <string name="dateformat_date_time"><![CDATA[袛邪褌邪 泄 褔邪褋]]></string>
+    <string name="pref_title_unit_system">袨写懈薪懈褑褨 胁懈屑褨褉褞胁邪薪薪褟</string>
+    <string name="tap_connected_device_for_vibration">孝芯褉泻薪褨褌褜褋褟 锌褨写\'褦写薪邪薪芯谐芯 锌褉懈褋褌褉芯褞 写谢褟 胁褨斜褉邪褑褨褩</string>
+    <string name="pairing">小褌胁芯褉械薪薪褟 锌邪褉懈 蟹 %s鈥�</string>
+    <string name="pairing_unable_to_pair_with">袧械 胁写邪谢芯褋褜 褋褌胁芯褉懈褌懈 锌邪褉褍 蟹 %1$s (%2$s)</string>
+    <string name="pairing_in_progress">袩芯胁\'褟蟹褍胁邪薪薪褟 胁 锌褉芯褑械褋褨: %1$s (%2$s)</string>
+    <string name="watch9_pairing_tap_hint">袣芯谢懈 胁邪褕 谐芯写懈薪薪懈泻 蟹邪胁褨斜褉褍褦, 锌芯褌褉褟褋褨褌褜 泄芯谐芯 邪斜芯 薪邪褌懈褋薪褨褌褜 薪邪 泻薪芯锌泻褍.</string>
+    <string name="stats_x_axis_label">袙褋褜芯谐芯 褏胁懈谢懈薪</string>
+    <string name="weeksleepchart_sleep_a_week">小芯薪 蟹邪 褌懈卸写械薪褜</string>
+    <string name="weeksleepchart_today_sleep_description">小芯薪 褋褜芯谐芯写薪褨, 屑械褌邪: %1$s</string>
+    <string name="controlcenter_change_led_color">袟屑褨薪懈褌懈 泻芯谢褨褉 褋胁褨褌谢芯写褨芯写邪</string>
+    <string name="controlcenter_change_fm_frequency">袟屑褨薪懈褌懈 褔邪褋褌芯褌褍 FM</string>
+    <string name="debugactivity_really_factoryreset_title">袛褨泄褋薪芯 褋泻懈薪褍褌懈 薪邪谢邪褕褌褍胁邪薪薪褟\?</string>
+    <string name="pref_title_rtl">小锌褉邪胁邪 薪邪谢褨胁芯</string>
+    <string name="pref_pebble_privacy_mode_content">袟屑褨褖褍胁邪褌懈 褌械泻褋褌 褋锌芯胁褨褖械薪薪褟, 褟泻懈泄 胁懈褏芯写懈褌褜 蟹邪 屑械卸褨 械泻褉邪薪邪</string>
+    <string name="dbmanagementactivity_import_data_title">袉屑锌芯褉褌褍胁邪褌懈 写邪薪褨\?</string>
+    <string name="dbmanagementactivity_import_successful">袉屑锌芯褉褌芯胁邪薪芯.</string>
+    <string name="dbmanagementactivity_error_importing_db">袩芯屑懈谢泻邪 褨屑锌芯褉褌褍胁邪薪薪褟 斜邪蟹懈 写邪薪懈褏: %1$s</string>
+    <string name="dbmanagementactivity_error_importing_shared">袩芯屑懈谢泻邪 褨屑锌芯褉褌褍胁邪薪薪褟 薪邪谢邪褕褌褍胁邪薪褜: %1$s</string>
+    <string name="dbmanagementactivity_delete_activity_data_title">袙懈写邪谢懈褌懈 写邪薪褨 锌褉芯 写褨褟谢褜薪褨褋褌褜?</string>
+    <string name="dbmanagementactivity_database_successfully_deleted">袛邪薪褨 胁懈写邪谢械薪芯.</string>
+    <string name="dbmanagementactivity_db_deletion_failed">袧械 胁写邪谢芯褋褟 胁懈写邪谢懈褌懈 斜邪蟹褍 写邪薪懈褏.</string>
+    <string name="dbmanagementactivity_overwrite">袩械褉械蟹邪锌懈褋邪褌懈</string>
+    <string name="ok">袛芯斜褉械</string>
+    <string name="unit_metric">袦械褌褉懈褔薪褨</string>
+    <string name="unit_imperial">袉屑锌械褉褋褜泻褨</string>
+    <string name="pref_screen_notification_profile_alarm_clock">袘褍写懈谢褜薪懈泻</string>
+    <string name="StringUtils_sender">(%1$s)</string>
+    <string name="find_device_you_found_it">袟薪邪泄写械薪芯!</string>
+    <string name="miband2_prefs_timeformat">Mi2: 肖芯褉屑邪褌 褔邪褋褍</string>
+    <string name="mi2_enable_text_notifications">孝械泻褋褌芯胁褨 褋锌芯胁褨褖械薪薪褟</string>
+    <string name="mi3_night_mode_sunset">袣芯谢懈 褋褍褌械薪褨褦</string>
+    <string name="mi2_dnd_automatic">袗胁褌芯屑邪褌懈褔薪芯 (胁懈褟胁谢械薪薪褟 褋薪褍)</string>
+    <string name="mi2_dnd_scheduled">袟邪锌谢邪薪芯胁邪薪芯 (褔邪褋芯胁懈泄 锌褉芯屑褨卸芯泻)</string>
+    <string name="_pebble_watch_reply">袙褨写锌芯胁褨褋褌懈</string>
+    <string name="activity_type_light_sleep">楔胁懈写泻懈泄 褋芯薪</string>
+    <string name="activity_type_deep_sleep">袚谢懈斜芯泻懈泄 褋芯薪</string>
+    <string name="activity_type_swimming">袩谢邪胁邪薪薪褟</string>
+    <string name="activity_type_unknown">袧械胁褨写芯屑邪 写褨褟谢褜薪褨褋褌褜</string>
+    <string name="activity_summaries">小锌芯褉褌懈胁薪邪 写褨褟谢褜薪褨褋褌褜</string>
+    <string name="activity_type_biking">袙械谢芯锌褉芯谐褍谢褟薪泻邪</string>
+    <string name="activity_type_treadmill">袘褨谐芯胁邪 写芯褉褨卸泻邪</string>
+    <string name="activity_type_exercise">袙锌褉邪胁懈</string>
+    <string name="select_all">袙懈斜褉邪褌懈 胁褋械</string>
+    <string name="share">袩芯写褨谢懈褌懈褋褟</string>
+    <string name="kind_firmware">袦褨泻褉芯锌褉芯谐褉邪屑邪</string>
+    <string name="kind_invalid">袧械写褨泄褋薪褨 写邪薪褨</string>
+    <string name="kind_font">楔褉懈褎褌</string>
+    <string name="kind_gps">袦褨泻褉芯锌褉芯谐褉邪屑邪 GPS</string>
+    <string name="kind_gps_almanac">袗谢褜屑邪薪邪褏 GPS</string>
+    <string name="kind_resources">袪械褋褍褉褋懈</string>
+    <string name="devicetype_unknown">袧械胁褨写芯屑懈泄 锌褉懈褋褌褉褨泄</string>
+    <string name="devicetype_test">孝械褋褌芯胁懈泄 锌褉懈褋褌褉褨泄</string>
+    <string name="devicetype_pebble">Pebble</string>
+    <string name="devicetype_miband">Mi Band</string>
+    <string name="devicetype_miband2">Mi Band 2</string>
+    <string name="devicetype_miband3">Mi Band 3</string>
+    <string name="devicetype_amazfit_bip">Amazfit Bip</string>
+    <string name="devicetype_amazfit_cor">Amazfit Cor</string>
+    <string name="devicetype_vibratissimo">Vibratissimo</string>
+    <string name="devicetype_liveview">LiveView</string>
+    <string name="devicetype_hplus">HPlus</string>
+    <string name="devicetype_makibes_f68">Makibes F68</string>
+    <string name="devicetype_exrizu_k8">Exrizu K8</string>
+    <string name="devicetype_q8">Q8</string>
+    <string name="devicetype_no1_f1">No.1 F1</string>
+    <string name="devicetype_teclast_h30">Teclast H30</string>
+    <string name="devicetype_xwatch">XWatch</string>
+    <string name="devicetype_mykronoz_zetime">MyKronoz ZeTime</string>
+    <string name="devicetype_id115">ID115</string>
+    <string name="devicetype_watch9">Watch 9</string>
+    <string name="devicetype_roidmi">Roidmi</string>
+    <string name="devicetype_roidmi3">Roidmi 3</string>
+    <string name="devicetype_casiogb6900">Casio GB-6900</string>
+    <string name="choose_auto_export_location">袨斜褉邪褌懈 褌械泻褍 写谢褟 械泻褋锌芯褉褌褍</string>
+    <string name="menuitem_status">小褌邪薪</string>
+    <string name="menuitem_notifications">小锌芯胁褨褖械薪薪褟</string>
+    <string name="menuitem_activity">袉褋褌芯褉褨褟 褌褉械薪褍胁邪薪褜</string>
+    <string name="menuitem_weather">袩芯谐芯写邪</string>
+    <string name="menuitem_alarm">袘褍写懈谢褜薪懈泻</string>
+    <string name="menuitem_timer">孝邪泄屑械褉</string>
+    <string name="menuitem_compass">袣芯屑锌邪褋</string>
+    <string name="menuitem_settings">袧邪谢邪褕褌褍胁邪薪薪褟</string>
+    <string name="menuitem_music">袦褍蟹懈泻邪</string>
+    <string name="menuitem_more">袛芯写邪褌泻芯胁芯</string>
+    <string name="watch9_time_minutes">啸胁懈谢懈薪:</string>
+    <string name="watch9_time_hours">袚芯写懈薪:</string>
+    <string name="watch9_time_seconds">小械泻褍薪写:</string>
+    <string name="watch9_calibration_button">袣邪谢褨斜褉褍胁邪褌懈</string>
+    <string name="share_log">袩芯写褨谢懈褌懈褋褟 蟹胁褨褌芯屑</string>
+    <string name="warning">校胁邪谐邪!</string>
+    <string name="no_data">袧械屑邪褦 写邪薪懈褏</string>
+    <string name="preferences_led_color">袣芯谢褨褉 褋胁褨褌谢芯写褨芯写邪</string>
+    <string name="preferences_fm_frequency">FM 褔邪褋褌芯褌邪</string>
+    <string name="pref_invalid_frequency_title">袧械写褨泄褋薪邪 褔邪褋褌芯褌邪</string>
+    <string name="pref_invalid_frequency_message">袘褍写褜 谢邪褋泻邪, 胁胁械写褨褌褜 褔邪褋褌芯褌褍 蟹 写褨邪锌邪蟹芯薪褍 87.5 鈥� 108.0</string>
+    <string name="language_and_region_prefs">袦芯胁邪 褌邪 褉械谐褨芯薪邪谢褜薪褨 薪邪谢邪褕褌褍胁邪薪薪褟</string>
+    <string name="title_activity_notification_filter">肖褨谢褜褌褉 褋锌芯胁褨褖械薪褜</string>
+    <string name="toast_notification_filter_words_empty_hint">袙胁械写褨褌褜 锌褉懈薪邪泄屑薪褨 芯写薪械 褋谢芯胁芯</string>
+    <string name="filter_mode">袪械卸懈屑 褎褨谢褜褌褉邪褑褨褩</string>
+    <string name="save_configuration">袟斜械褉械谐褌懈 泻芯薪褎褨谐褍褉邪褑褨褞</string>
+    <string name="pref_title_pebble_forcele">袟邪胁卸写懈 薪邪写邪胁邪褌懈 锌械褉械胁邪谐褍 BLE</string>
+    <string name="pref_title_screentime">孝褉懈胁邪谢褨褋褌褜 褉芯斜芯褌懈 械泻褉邪薪邪</string>
+    <string name="preferences_hplus_settings">袧邪谢邪褕褌褍胁邪薪薪褟 HPlus/Makibes</string>
+    <string name="zetime_title_settings">袧邪谢邪褕褌褍胁邪薪薪褟 ZeTime</string>
+    <string name="zetime_title_heartrate">袧邪谢邪褕褌褍胁邪薪薪褟 胁懈屑褨褉褞胁邪薪薪褟 锌褍谢褜褋褍</string>
+    <string name="zetime_title_screentime">孝褉懈胁邪谢褨褋褌褜 褉芯斜芯褌懈 械泻褉邪薪邪 胁 褋械泻褍薪写邪褏</string>
+    <string name="activity_prefs_alarm_max_heart_rate">袦邪泻褋懈屑邪谢褜薪懈泄 锌褍谢褜褋</string>
+    <string name="activity_prefs_alarm_min_heart_rate">袦褨薪褨屑邪谢褜薪懈泄 锌褍谢褜褋</string>
+    <string name="zetime_analog_mode">袗薪邪谢芯谐芯胁懈泄 褉械卸懈屑</string>
+    <string name="zetime_analog_mode_hands">袥懈褕械 胁褉褍褔薪褍</string>
+    <string name="zetime_date_format">肖芯褉屑邪褌 写邪褌懈</string>
+    <string name="zetime_date_format_1">袪袪/袦袦/袛袛</string>
+    <string name="zetime_date_format_2">袛袛/袦袦/袪袪</string>
+    <string name="zetime_date_format_3">袦袦/袛袛/袪袪</string>
+    <string name="mi2_prefs_button_press_broadcast_default_value" translatable="false">nodomain.freeyourgadget.gadgetbridge.ButtonPressed</string>
+    <string name="pref_summary_pebble_forcele">袙懈泻芯褉懈褋褌芯胁褍胁邪褌懈 械泻褋锌械褉懈屑械薪褌邪谢褜薪褍 锌褨写褌褉懈屑泻褍 Pebble LE 写谢褟 胁褋褨褏 Pebble 蟹邪屑褨褋褌褜 泻谢邪褋懈褔薪芯谐芯 BT. 袩芯褌褉械斜褍褦 褋褌胁芯褉械薪薪褟 锌邪褉懈 褋锌芯褔邪褌泻褍 蟹 薪械 LE, 邪 胁卸械 锌芯褌褨屑 蟹 Pebble LE</string>
+    <string name="pref_title_pebble_mtu_limit">袨斜屑械卸械薪薪褟 GATT MTU 写谢褟 Pebble 2/LE</string>
+    <string name="pref_summary_pebble_mtu_limit">携泻褖芯 胁邪褕 Pebble 2/LE 薪械 锌褉邪褑褞褦, 褌邪泻, 褟泻 锌芯褌褉褨斜薪芯, 褋锌褉芯斜褍泄褌械 褑械泄 锌邪褉械屑械褌褉 写谢褟 芯斜屑械卸械薪薪褟 MTU (胁 写褨邪锌邪蟹芯薪褨 20鈥�512)</string>
+    <string name="pref_summary_pebble_enable_applogs">笑械 锌褉懈蟹胁械写械 写芯 褌芯谐芯, 褖芯 卸褍褉薪邪谢懈 胁褨写 蟹邪褋褌芯褋褍薪泻褨胁 谐芯写懈薪薪懈泻邪 斜褍写褍褌褜 褉械褦褋褌褉褍胁邪褌懈褋褟 Gadgetbridge (锌芯褌褉械斜褍褦 锌械褉械\'蟹褦写薪邪薪薪褟)</string>
+    <string name="pref_title_pebble_always_ack_pebblekit">袩械褉械写褔邪褋薪械 锌褨写褌胁械褉写卸械薪薪褟 ACK PebbleKit</string>
+    <string name="pref_summary_pebble_always_ack_pebblekit">笑械 锌褉懈蟹胁械写械 写芯 褌芯谐芯, 褖芯 锌芯胁褨写芯屑谢械薪薪褟, 褟泻褨 薪邪写褋懈谢邪褞褌褜褋褟 写芯 蟹芯胁薪褨褕薪褨褏 褋褌芯褉芯薪薪褨褏 蟹邪褋褌芯褋褍薪泻褨胁, 斜褍写褍褌褜 蟹邪胁卸写懈 锌褨写褌胁械褉写卸褍胁邪褌懈褋褟 蟹邪胁卸写懈 褌邪 薪械谐邪泄薪芯</string>
+    <string name="tap_connected_device_for_app_mananger">孝芯褉泻薪褨褌褜褋褟 锌褨写\'褦写薪邪薪芯谐芯 锌褉懈褋褌褉芯褞 写谢褟 胁懈泻谢懈泻褍 屑械薪械写卸械褉邪 蟹邪褋褌芯褋褍薪泻褨胁</string>
+    <string name="tap_connected_device_for_activity">孝芯褉泻薪褨褌褜褋褟 锌褨写\'褦写薪邪薪芯谐芯 锌褉懈褋褌褉芯褞 写谢褟 锌械褉械谐谢褟写褍 写邪薪懈褏 锌褉芯 写褨褟谢褜薪褨褋褌褜</string>
+    <string name="pairing_creating_bond_with">小褌胁芯褉械薪薪褟 蟹胁\'褟蟹泻褍 蟹 %1$s (%2$s)</string>
+    <string name="pairing_already_bonded">袙卸械 锌芯胁\'褟蟹邪薪懈泄 蟹 %1$s (%2$s), 蟹\'褦写薪邪薪薪褟鈥�</string>
+    <string name="pref_write_logfiles">袟邪锌懈褋褍胁邪褌懈 褎邪泄谢懈 卸褍褉薪邪谢褍</string>
+    <string name="stats_title">袟芯薪懈 褕胁懈写泻芯褋褌褨</string>
+    <string name="miband_prefs_device_time_offset_hours">袟屑褨褖械薪薪褟 褔邪褋褍 胁 谐芯写懈薪邪褏 (写谢褟 褌懈褏, 褏褌芯 锌褉邪褑褞褦 胁薪芯褔褨)</string>
+    <string name="mi2_prefs_goal_notification">小锌芯胁褨褖械薪薪褟 锌褉芯 屑械褌褍</string>
+    <string name="mi2_prefs_display_items">袩芯泻邪蟹褍胁邪薪褨 械谢械屑械薪褌懈</string>
+    <string name="mi2_prefs_activate_display_on_lift">袙屑懈泻邪褌懈 械泻褉邪薪 锌褨写薪械褋械薪薪褟屑 褉褍泻懈</string>
+    <string name="mi2_prefs_do_not_disturb">袧械 褌褍褉斜褍胁邪褌懈</string>
+    <string name="mi2_prefs_inactivity_warnings">袩芯锌械褉械写卸械薪薪褟 锌褉芯 斜械蟹写褨褟谢褜薪褨褋褌褜</string>
+    <string name="mi2_prefs_inactivity_warnings_threshold">袦械卸邪 斜械蟹写褨褟谢褜薪芯褋褌褨 (褍 褏胁懈谢懈薪邪褏)</string>
+    <string name="mi2_prefs_do_not_disturb_start">效邪褋 锌芯褔邪褌泻褍</string>
+    <string name="mi2_prefs_do_not_disturb_end">效邪褋 蟹邪胁械褉褕械薪薪褟</string>
+    <string name="authenticating">袗胁褌芯褉懈蟹邪褑褨褟</string>
+    <string name="authentication_required">袩芯褌褉褨斜薪邪 邪胁褌芯褉懈蟹邪褑褨褟</string>
+    <string name="activity_prefs_sleep_duration">袘邪卸邪薪邪 褌褉懈胁邪谢褨褋褌褜 褋薪褍 褍 谐芯写懈薪邪褏</string>
+    <string name="updatefirmwareoperation_update_in_progress">袙褋褌邪薪芯胁谢械薪薪褟 屑褨泻褉芯锌褉芯谐褉邪屑懈</string>
+    <string name="updatefirmwareoperation_firmware_not_sent">袦褨泻褉芯锌褉芯谐褉邪屑褍 薪械 胁褨写锌褉邪胁谢械薪芯</string>
+    <string name="charts_legend_heartrate">袩褍谢褜褋</string>
+    <string name="live_activity_heart_rate">袩褍谢褜褋</string>
+    <string name="action_db_management">袣械褉褍胁邪薪薪褟 写邪薪懈屑懈</string>
+    <string name="title_activity_db_management">袣械褉褍胁邪薪薪褟 写邪薪懈屑懈</string>
+    <string name="activity_db_management_merge_old_title">袙懈写邪谢械薪薪褟 蟹邪褋褌邪褉褨谢芯褩 斜邪蟹懈 写邪薪懈褏</string>
+    <string name="dbmanagementactivity_exported_to">袝泻褋锌芯褉褌芯胁邪薪芯 写芯: %1$s</string>
+    <string name="dbmanagementactivity_error_exporting_db">袩芯屑懈谢泻邪 械泻褋锌芯褉褌褍胁邪薪薪褟 斜邪蟹懈 写邪薪懈褏: %1$s</string>
+    <string name="dbmanagementactivity_error_exporting_shared">袩芯屑懈谢泻邪 械泻褋锌芯褉褌褍胁邪薪薪褟 薪邪谢邪褕褌褍胁邪薪褜: %1$s</string>
+    <string name="mi2_prefs_button_actions">袛褨褩 泻薪芯锌泻懈</string>
+    <string name="mi2_prefs_button_actions_summary">袨斜械褉褨褌褜 写褨褩 泻薪芯锌芯泻 锌褉懈 薪邪褌懈褋薪械薪薪褨</string>
+    <string name="mi2_prefs_button_press_count">袣褨谢褜泻褨褋褌褜 薪邪褌懈褋泻邪薪褜 薪邪 泻薪芯锌泻褍</string>
+    <string name="mi2_prefs_button_press_broadcast">袩芯胁褨写芯屑谢械薪薪褟 写谢褟 胁褨写锌褉邪胁谢械薪薪褟</string>
+    <string name="mi2_prefs_button_action">校胁褨屑泻薪褍褌懈 写褨褞 泻薪芯锌泻懈</string>
+    <string name="mi2_prefs_button_action_vibrate">校胁褨屑泻薪褍褌懈 胁褨斜褉邪褑褨褞 斜褉邪褋谢械褌邪</string>
+    <string name="pref_summary_pebble_enable_bgjs">袣芯谢懈 褍胁褨屑泻薪械薪芯, 写芯蟹胁芯谢褟褦 褑懈褎械褉斜谢邪褌褍 锌芯泻邪蟹褍胁邪褌懈 锌芯谐芯写褍, 蟹邪褉褟写 斜邪褌邪褉械褩 褌芯褖芯.</string>
+    <string name="automatic">袗胁褌芯屑邪褌懈褔薪芯</string>
+    <string name="simplified_chinese">小锌褉芯褖械薪邪 泻懈褌邪泄褋褜泻邪</string>
+    <string name="traditional_chinese">孝褉邪写懈褑褨泄薪邪 泻懈褌邪泄褋褜泻邪</string>
+    <string name="english">袗薪谐谢褨泄褋褜泻邪</string>
+    <string name="prefs_title_heartrate_measurement_interval">笑褨谢芯写芯斜芯胁械 胁懈屑褨褉褞胁邪薪薪褟 锌褍谢褜褋褍</string>
+    <string name="pref_header_auto_export">袗胁褌芯屑邪褌懈褔薪懈泄 械泻褋锌芯褉褌</string>
+    <string name="pref_title_auto_export_location">袪芯蟹褌邪褕褍胁邪薪薪褟 械泻褋锌芯褉褌褍</string>
+    <string name="pref_title_auto_export_interval">袉薪褌械褉胁邪谢 械泻褋锌芯褉褌褍</string>
+    <string name="pref_summary_auto_export_interval">袝泻褋锌芯褉褌褍胁邪褌懈 泻芯卸薪褨 %d 谐芯写懈薪(懈)</string>
+    <string name="spanish">袉褋锌邪薪褋褜泻邪</string>
+    <string name="pref_title_pebble_gatt_clientonly">袥懈褕械 泻谢褨褦薪褌 GATT</string>
+    <string name="pref_summary_pebble_gatt_clientonly">笑械泄 械泻褋锌械褉懈屑械薪褌邪谢褜薪懈泄 锌邪褉邪屑械褌褉 谢懈褕械 写谢褟 Pebble 2, 褋锌褉芯斜褍泄褌械 褑械, 褟泻褖芯 褍 胁邪褋 胁懈薪懈泻邪褞褌褜 锌褉芯斜谢械屑懈 蟹褨 蟹\'褦写薪邪薪薪褟屑</string>
+    <string name="pref_auto_fetch">袗胁褌芯屑邪褌懈褔薪械 芯褌褉懈屑褍胁邪薪薪褟 写邪薪懈褏 锌褉芯 写褨褟谢褜薪褨褋褌褜</string>
+    <string name="pref_auto_fetch_limit_fetches">袦褨薪褨屑邪谢褜薪懈泄 褔邪褋 屑褨卸 芯褌褉懈屑邪薪薪褟屑懈</string>
+    <string name="pref_auto_fetch_limit_fetches_summary">袨褌褉懈屑褍胁邪褌懈 写邪薪褨 泻芯卸薪褨 %d 褏胁懈谢懈薪(懈)</string>
+    <string name="russian">袪芯褋褨泄褋褜泻邪</string>
+    <string name="german">袧褨屑械褑褜泻邪</string>
+    <string name="italian">袉褌邪谢褨泄褋褜泻邪</string>
+    <string name="french">肖褉邪薪褑褍蟹褜泻邪</string>
+    <string name="polish">袩芯谢褜褋褜泻邪</string>
+    <string name="korean">袣芯褉械泄褋褜泻邪</string>
+    <string name="japanese">携锌芯薪褋褜泻邪</string>
+    <string name="you_slept">%1$s - %2$s</string>
+    <string name="norwegian_bokmal">袧芯褉胁械蟹褜泻邪 (袘褍泻屑芯谢)</string>
+    <string name="mi3_prefs_night_mode">袧褨褔薪懈泄 褉械卸懈屑</string>
+    <string name="activity_prefs_charts">袧邪谢邪褕褌褍胁邪薪薪褟 写褨邪谐褉邪屑懈</string>
+    <string name="activity_prefs_chart_max_heart_rate">袦邪泻褋懈屑邪谢褜薪懈泄 锌褍谢褜褋</string>
+    <string name="activity_prefs_chart_min_heart_rate">袦褨薪褨屑邪谢褜薪懈泄 锌褍谢褜褋</string>
+    <string name="notif_battery_low">袗泻褍屑褍谢褟褌芯褉 %1$s 褉芯蟹褉褟写卸械薪懈泄</string>
+    <string name="notif_battery_low_extended">袗泻褍屑褍谢褟褌芯褉 %1$s 褉芯蟹褉褟写卸械薪懈泄: %2$s</string>
+    <string name="pref_summary_rtl">校胁褨屑泻薪褨褌褜 褑械, 褟泻褖芯 胁邪褕 锌褉懈褋褌褉褨泄 薪械 屑芯卸械 胁褨写芯斜褉邪卸邪褌懈 屑芯胁懈 \"褋锌褉邪胁邪 薪邪谢褨胁芯\"</string>
+    <string name="pref_rtl_max_line_length">袧邪泄斜褨谢褜褕邪 写芯胁卸懈薪邪 褉褟写泻邪 写谢褟 褌械泻褋褌褍 \"褋锌褉邪胁邪 薪邪谢褨胁芯\"</string>
+    <string name="pref_rtl_max_line_length_summary">袟斜褨谢褜褕褍胁邪褌懈 邪斜芯 蟹屑械薪褕褍胁邪褌懈 泻褨谢褜泻褨褋褌褜 褋懈屑胁芯谢褨胁 褍 褉褟写泻邪褏, 薪邪 褟泻褨 褉芯蟹斜懈胁邪褦褌褜褋褟 褌械泻褋褌, 薪邪锌懈褋邪薪懈泄 \"褋锌褉邪胁邪 薪邪谢褨胁芯\"</string>
+    <string name="lack_of_sleep">袧械写芯褋懈锌邪薪薪褟: %1$s</string>
+    <string name="overslept">袩械褉械褋懈锌邪薪薪褟: %1$s</string>
+    <string name="lack_of_step">袧械写芯褋褌邪褌薪褜芯 泻褉芯泻褨胁: %1$d</string>
+    <string name="live_activity_max_heart_rate">袩芯褌芯褔薪懈泄 / 薪邪泄胁懈褖懈泄 锌褍谢褜褋: %1$d / %2$d</string>
+    <string name="debugactivity_really_factoryreset">小泻懈写邪薪薪褟 薪邪谢邪褕褌褍胁邪薪褜 锌褉懈蟹胁械写械 写芯 胁懈写邪谢械薪薪褟 胁褋褨褏 写邪薪懈褏 蟹 锌褨写\'褦写薪邪薪芯谐芯 锌褉懈褋褌褉芯褞 (褟泻褖芯 锌褨写褌褉懈屑褍褦褌褜褋褟). 袩褉懈褋褌褉芯褩 Xiaomi/Huami 褌邪泻芯卸 蟹屑褨薪褟褌褜 褋胁芯褞 Bluetooth MAC-邪写褉械褋褍, 褌芯屑褍 胁芯薪懈 蟹\'褟胁谢褟褌褜褋褟 褟泻 薪芯胁褨 锌褉懈褋褌褉芯褩 写谢褟 Gadgetbridge.</string>
+    <string name="prefs_disconnect_notification">小锌芯胁褨褖械薪薪褟 锌褉芯 胁褨写\'褦写薪邪薪薪褟</string>
+    <string name="zetime_title_heart_rate_alarm">袩芯锌械褉械写卸械薪薪褟 锌褉芯 锌褍谢褜褋</string>
+    <string name="zetime_title_heart_rate_alarm_summary">袚芯写懈薪薪懈泻 锌芯锌械褉械写卸邪褌懈屑械 胁邪褋, 泻芯谢懈 胁邪褕 锌褍谢褜褋 锌械褉械胁懈褖褍胁邪褌懈屑械 写芯锌褍褋褌懈屑褨 蟹薪邪褔械薪薪褟.</string>
+    <string name="zetime_heart_rate_alarm_enable">校胁褨屑泻薪褍褌懈 锌芯锌械褉械写卸械薪薪褟 锌褉芯 锌褍谢褜褋</string>
+    <string name="zetime_analog_mode_handsandsteps">小褌褉褨谢泻懈 褌邪 泻褉芯泻懈</string>
+    <string name="zetime_activity_tracking">袙褨写褋褌械卸褍胁邪薪薪褟 写褨褟谢褜薪芯褋褌褨</string>
+    <string name="zetime_activity_tracking_summary">校胁褨屑泻薪械薪薪褟 胁褨写褋褌械卸褍胁邪薪薪褟 写褨褟谢褜薪芯褋褌褨, 褉邪褏褍胁邪褌懈屑械 泻褉芯泻懈 褌芯褖芯.</string>
+    <string name="zetime_handmove_display">袪褍褏 褉褍泻懈</string>
+    <string name="zetime_handmove_display_summary">袩芯胁械褉薪褨褌褜 蟹邪锌\'褟褋褌褟, 褖芯斜 褍胁褨屑泻薪褍褌懈 褔懈 胁懈屑泻薪褍褌懈 写懈褋锌谢械泄.</string>
+    <string name="zetime_calories_type">孝懈锌 泻邪谢芯褉褨泄</string>
+    <string name="zetime_calories_type_active">孝褨谢褜泻懈 邪泻褌懈胁薪芯 褋锌邪谢械薪褨 泻邪谢芯褉褨褩</string>
+    <string name="zetime_calories_type_all">袗泻褌懈胁薪芯 褌邪 薪械邪泻褌懈胁薪芯 褋锌邪谢械薪褨 泻邪谢芯褉褨褩</string>
+    <string name="zetime_prefs_inactivity_repetitions">袩芯胁褌芯褉械薪薪褟</string>
+    <string name="zetime_prefs_inactivity_mo">袩芯薪械写褨谢芯泻</string>
+    <string name="zetime_prefs_inactivity_tu">袙褨胁褌芯褉芯泻</string>
+    <string name="zetime_prefs_inactivity_we">小械褉械写邪</string>
+    <string name="zetime_prefs_inactivity_th">效械褌胁械褉</string>
+    <string name="zetime_prefs_inactivity_fr">袩\'褟褌薪懈褑褟</string>
+    <string name="zetime_prefs_inactivity_sa">小褍斜芯褌邪</string>
+    <string name="zetime_prefs_inactivity_su">袧械写褨谢褟</string>
+    <string name="zetime_title_alarm_signaling">袧邪谢邪褕褌褍胁邪褌懈 褋懈谐薪邪谢 斜褍写懈谢褜薪懈泻邪</string>
+    <string name="zetime_signaling_none">袘械蟹蟹胁褍褔薪懈泄</string>
+    <string name="zetime_signaling_vibrate">袩芯褋褌褨泄薪邪 胁褨斜褉邪褑褨褟</string>
+    <string name="zetime_signaling_beep">袩芯褋褌褨泄薪懈泄 褋懈谐薪邪谢</string>
+    <string name="zetime_signaling_vibrate_beep">袩芯褋褌褨泄薪邪 胁褨斜褉邪褑褨褟 褌邪 褋懈谐薪邪谢</string>
+    <string name="zetime_signaling_beep_once">小懈谐薪邪谢 芯写懈薪 褉邪蟹</string>
+    <string name="zetime_signaling_beep_twice">小懈谐薪邪谢 写胁褨褔褨</string>
+    <string name="pref_screen_notification_profile_missed_call">小锌芯胁褨褖械薪薪褟 锌褉芯 锌褉芯锌褍褖械薪褨 胁懈泻谢懈泻懈</string>
+    <string name="pref_screen_notification_profile_calendar">小锌芯胁褨褖械薪薪褟 泻邪谢械薪写邪褉褟</string>
+    <string name="pref_screen_notification_profile_inactivity">小锌芯胁褨褖械薪薪褟 锌褉芯 斜械蟹写褨褟谢褜薪褨褋褌褜</string>
+    <string name="pref_screen_notification_profile_low_power">袩芯锌械褉械写卸械薪薪褟 锌褉芯 薪懈蟹褜泻懈泄 蟹邪褉褟写</string>
+    <string name="pref_screen_notification_profile_anti_loss">袩芯锌械褉械写卸械薪薪褟 锌褉芯 胁褌褉邪褌褍</string>
+    <string name="interval_fifteen_minutes">泻芯卸薪褨 15 褏胁懈谢懈薪</string>
+    <string name="interval_forty_five_minutes">泻芯卸薪褨 45 褏胁懈谢懈薪</string>
+    <string name="pref_title_support_voip_calls">校胁褨屑泻薪褍褌懈 VoIP-胁懈泻谢懈泻懈</string>
+    <string name="title_activity_device_specific_settings">小锌械褑懈褎褨褔薪褨 薪邪谢邪褕褌褍胁邪薪薪褟 锌褉懈褋褌褉芯褞</string>
+    <string name="pref_title_authkey">袣谢褞褔 邪胁褌械薪褌懈褎褨泻邪褑褨褩</string>
+    <string name="dutch">袧褨写械褉谢邪薪写褋褜泻邪</string>
+    <string name="turkish">孝褍褉械褑褜泻邪</string>
+    <string name="ukrainian">校泻褉邪褩薪褋褜泻邪</string>
+    <string name="arabic">袗褉邪斜褋褜泻邪</string>
+    <string name="indonesian">袉薪写芯薪械蟹褨泄褋褜泻邪</string>
+    <string name="thai">孝邪泄褋褜泻邪</string>
+    <string name="vietnamese">袙\'褦褌薪邪屑褋褜泻邪</string>
+    <string name="portuguese">袩芯褉褌褍谐邪谢褜褋褜泻邪</string>
+    <string name="prefs_hr_alarm_activity">袩芯锌械褉械写卸械薪薪褟 锌褉芯 锌褍谢褜褋 锌褨写 褔邪褋 蟹邪薪褟褌褜 褋锌芯褉褌芯屑</string>
+    <string name="prefs_hr_alarm_low">袧懈卸薪褟 屑械卸邪</string>
+    <string name="prefs_hr_alarm_high">袙械褉褏薪褟 屑械卸邪</string>
+    <string name="average">袙 褋械褉械写薪褜芯屑褍: %1$s</string>
+    <string name="pref_header_charts">袧邪谢邪褕褌褍胁邪薪薪褟 写褨邪谐褉邪屑</string>
+    <string name="pref_title_charts_range">袛褨邪锌邪蟹芯薪 写褨邪谐褉邪屑</string>
+    <string name="weekstepschart_steps_a_month">袣褉芯泻褨胁 蟹邪 屑褨褋褟褑褜</string>
+    <string name="weeksleepchart_sleep_a_month">小芯薪 蟹邪 屑褨褋褟褑褜</string>
+    <string name="pref_summary_expose_hr">袛芯蟹胁芯谢褟褦 褨薪褕懈屑 蟹邪褋褌芯褋褍薪泻邪屑 芯褌褉懈屑褍胁邪褌懈 写芯褋褌褍锌 写芯 写邪薪懈褏 锌褉芯 锌褍谢褜褋 胁 褉械邪谢褜薪芯屑褍 褔邪褋褨, 锌芯泻懈 Gadgetbridge 锌褨写\'褦写薪邪薪芯</string>
+    <string name="pref_title_expose_hr">袛芯褋褌褍锌 写芯 写邪薪懈褏 锌褉芯 锌褍谢褜褋 褋褌芯褉芯薪薪褨屑 蟹邪褋褌芯褋褍薪泻邪屑</string>
+    <string name="pref_title_use_custom_font">袧械褋褌邪薪写邪褉褌薪懈泄 褕褉懈褎褌</string>
+    <string name="pref_summary_use_custom_font">校胁褨屑泻薪褨褌褜 褑械, 褟泻褖芯 胁邪褕 锌褉懈褋褌褉褨泄 屑邪褦 薪械褋褌邪薪写邪褉褌薪褍 屑褨泻褉芯锌褉芯谐褉邪屑褍 写谢褟 锌褨写褌褉懈屑泻懈 械屑芯写蟹褨</string>
+    <string name="activity_DB_ExportButton">袝泻褋锌芯褉褌褍胁邪褌懈 写邪薪褨</string>
+    <string name="activity_DB_import_button">袉屑锌芯褉褌褍胁邪褌懈 写邪薪褨</string>
+    <string name="pref_display_add_device_fab">袣薪芯锌泻邪 锌褨写\'褦写薪邪薪薪褟 薪芯胁芯谐芯 锌褉懈褋褌褉芯褞</string>
+    <string name="pref_display_add_device_fab_on">袟邪胁卸写懈 胁懈写懈屑邪</string>
+    <string name="pref_display_add_device_fab_off">袙懈写懈屑邪, 褟泻褖芯 薪械 写芯写邪薪芯 卸芯写薪芯谐芯 锌褉懈褋褌褉芯褞</string>
+    <string name="preferences_makibes_hr3_settings">袧邪谢邪褕褌褍胁邪薪薪褟 Makibes HR3</string>
+    <string name="prefs_find_phone">袟薪邪泄褌懈 褌械谢械褎芯薪</string>
+    <string name="pref_chart_heartrate_color_red">效械褉胁芯薪懈泄</string>
+    <string name="pref_chart_heartrate_color_orange">袩芯屑邪褉邪薪褔械胁懈泄</string>
+    <string name="pref_title_chart_heartrate_color">袣芯谢褨褉 锌褍谢褜褋褍</string>
+    <string name="pref_title_chart_sleep_rolling_24_hour">袛褨邪锌邪蟹芯薪 褋薪褍</string>
+    <string name="pref_chart_sleep_rolling_24_on">袨褋褌邪薪薪褨 24 谐芯写懈薪懈</string>
+    <string name="pref_chart_sleep_rolling_24_off">袙褨写 锌芯谢褍写薪褟 写芯 锌芯谢褍写薪褟</string>
+    <string name="pref_disable_new_ble_scanning">袙懈屑泻薪褍褌懈 锌芯褕褍泻 薪芯胁懈褏 BLE</string>
+    <string name="pref_summary_disable_new_ble_scanning">校胁褨屑泻薪褨褌褜 褑械泄 锌邪褉邪屑械褌褉, 褟泻褖芯 胁邪褕 锌褉懈褋褌褉褨泄 薪械 蟹薪邪泄写械薪芯 锌褨写 褔邪褋 锌芯褕褍泻褍</string>
+    <string name="alarm_snooze">袙褨写泻谢邪褋褌懈</string>
+    <string name="discovery_connected_devices_hint">袟褉芯斜褨褌褜 胁邪褕 锌褉懈褋褌褉褨泄 胁懈褟胁薪懈屑. 袧械写邪胁薪芯 锌褨写\'褦写薪邪薪褨 锌褉懈褋褌褉芯褩, 泄屑芯胁褨褉薪芯, 薪械 斜褍写褍褌褜 胁懈褟胁谢械薪褨. 袗泻褌懈胁褍泄褌械 屑褨褋褑械锌械褉械斜褍胁邪薪薪褟 (GPS) 薪邪 Android 6+. 袙懈屑泻薪褨褌褜 蟹邪褏懈褋褌 锌褉懈胁邪褌薪芯褋褌褨 写谢褟 Gadgetbridge, 芯褋泻褨谢褜泻懈 褑械 屑芯卸械 锌褉懈蟹胁械褋褌懈 写芯 蟹斜芯褞 褌邪 锌械褉械蟹邪胁邪薪褌邪卸械薪薪褟 胁邪褕芯谐芯 褌械谢械褎芯薪褍. 携泻褖芯 蟹邪 泻褨谢褜泻邪 褏胁懈谢懈薪 薪械 斜褍写械 胁懈褟胁谢械薪芯 卸芯写薪芯谐芯 锌褉懈褋褌褉芯褞, 锌芯胁褌芯褉褨褌褜 褋锌褉芯斜褍 锌褨褋谢褟 锌械褉械蟹邪胁邪薪褌邪卸械薪薪褟 胁邪褕芯谐芯 屑芯斜褨谢褜薪芯谐芯 锌褉懈褋褌褉芯褞.</string>
+    <string name="pref_title_low_latency_fw_update">袙懈泻芯褉懈褋褌芯胁褍胁邪褌懈 褉械卸懈屑 薪懈蟹褜泻芯褩 蟹邪褌褉懈屑泻懈 写谢褟 胁褋褌邪薪芯胁谢械薪薪褟 屑褨泻褉芯锌褉芯谐褉邪屑懈</string>
+    <string name="mi2_prefs_goal_notification_summary">袘褉邪褋谢械褌 胁褨斜褉褍胁邪褌懈屑械 蟹邪 写芯褋褟谐薪械薪薪褟 褖芯写械薪薪芯褩 屑械褌懈 蟹 泻褨谢褜泻芯褋褌褨 泻褉芯泻褨胁</string>
+    <string name="mi2_prefs_display_items_summary">袨斜械褉褨褌褜 械谢械屑械薪褌懈 写谢褟 锌芯泻邪蟹褍 薪邪 械泻褉邪薪褨 斜褉邪褋谢械褌邪</string>
+    <string name="mi2_prefs_rotate_wrist_to_switch_info">袨斜械褉薪褨褌褜 褉褍泻褍, 褖芯斜 蟹屑褨薪懈褌懈 锌芯写褉芯斜懈褑褨</string>
+    <string name="mi2_prefs_do_not_disturb_summary">携泻褖芯 胁胁褨屑泻薪褍褌懈, 谐芯写懈薪薪懈泻 薪械 芯褌褉懈屑褍胁邪褌懈屑械 褋锌芯胁褨褖械薪薪褟</string>
+    <string name="mi2_prefs_inactivity_warnings_summary">袘褉邪褋谢械褌 胁褨斜褉褍胁邪褌懈屑械, 褟泻褖芯 胁懈 斜褍写械褌械 薪械邪泻褌懈胁薪褨 锌褉芯褌褟谐芯屑 锌械胁薪芯谐芯 褔邪褋褍</string>
+    <string name="mi2_prefs_inactivity_warnings_dnd_summary">袙懈屑泻薪褍褌懈 锌芯锌械褉械写卸械薪薪褟 锌褉芯 斜械蟹写褨褟谢褜薪褨褋褌褜 薪邪 锌械胁薪懈泄 褔邪褋</string>
+    <string name="FetchActivityOperation_about_to_transfer_since">袩械褉械写邪褔邪 写邪薪懈褏 斜褍写械 蟹邪胁械褉褕械薪邪 褔械褉械蟹 %1$s</string>
+    <string name="appwidget_text">Zzz</string>
+    <string name="error_creating_directory_for_logfiles">袩芯屑懈谢泻邪 褋褌胁芯褉械薪薪褟 褌械泻懈 写谢褟 褎邪泄谢褨胁 卸褍褉薪邪谢褍: %1$s</string>
+    <string name="pref_title_pebble_health_store_raw">袟斜械褉褨谐邪褌懈 褋懈褉褨 蟹邪锌懈褋懈 写芯 斜邪蟹懈 写邪薪懈褏</string>
+    <string name="pref_summary_pebble_health_store_raw">袟斜械褉褨谐邪褌懈 写邪薪褨 \"褟泻 褦\", 蟹斜褨谢褜褕褍褞褔懈 胁懈泻芯褉懈褋褌邪薪薪褟 斜邪蟹懈 写邪薪懈褏 写谢褟 锌芯写邪谢褜褕芯褩 褨薪褌械褉锌褉械褌邪褑褨褩.</string>
+    <string name="activity_db_management_import_export_explanation">袨锌械褉邪褑褨褩 械泻褋锌芯褉褌褍/褨屑锌芯褉褌褍 胁懈泻芯褉懈褋褌芯胁褍褞褌褜 褌邪泻懈泄 褕谢褟褏 (写懈胁. 薪懈卸褔械) 写芯 褌械泻懈 薪邪 胁邪褕芯屑褍 锌褉懈褋褌褉芯褩. 笑褟 褌械泻邪 写芯褋褌褍锌薪邪 写谢褟 褨薪褕懈褏 蟹邪褋褌芯褋褍薪泻褨胁 Android 褨 胁邪褕芯谐芯 泻芯屑锌\'褞褌械褉邪. 袟邪褍胁邪卸褌械, 褖芯 褑褟 褌械泻邪 褌邪 胁褋褨 褎邪泄谢懈 褍 薪褨泄 斜褍写褍褌褜 胁懈写邪谢械薪褨 锌褨写 褔邪褋 胁懈写邪谢械薪薪褟 Gadgetbridge. 笑褨 写邪薪褨 胁泻谢褞褔邪褞褌褜:
+\nExport_preference 鈥� 蟹邪谐邪谢褜薪褨 薪邪谢邪褕褌褍胁邪薪薪褟
+\nExport_preference_MAC 鈥� 薪邪谢邪褕褌褍胁邪薪薪褟 泻芯薪泻褉械褌薪芯谐芯 锌褉懈褋褌褉芯褞
+\nGadgetbridge 鈥� 斜邪蟹邪 写邪薪懈褏 锌褉懈褋褌褉芯褩胁 褌邪 写褨褟谢褜薪芯褋褌械泄
+\nGadgetbridge_date 鈥� 斜邪蟹邪 写邪薪懈褏 械泻褋锌芯褉褌芯胁邪薪邪 褍 锌械胁薪褍 写邪褌褍
+\n*.gpx 鈥� 蟹邪锌懈褋懈 GPS
+\n*.log 鈥� 褎邪泄谢懈 卸褍褉薪邪谢褨胁
+\n袝泻褋锌芯褉褌芯胁邪薪褨 褎邪泄谢懈 (邪斜芯 锌芯屑褨褋褌褨褌褜 褋褞写懈 褎邪泄谢懈 写谢褟 褨屑锌芯褉褌褍) 褉芯蟹屑褨褖械薪芯 褌褍褌:</string>
+    <string name="dbmanagementactivvity_cannot_access_export_path">袧械屑芯卸谢懈胁芯 芯褌褉懈屑邪褌懈 写芯褋褌褍锌 写芯 褌械泻懈 写谢褟 械泻褋锌芯褉褌褍. 袘褍写褜 谢邪褋泻邪, 蟹胁械褉薪褨褌褜褋褟 写芯 褉芯蟹褉芯斜薪懈泻褨胁.</string>
+    <string name="dbmanagementactivity_overwrite_database_confirmation">袛褨泄褋薪芯 锌械褉械蟹邪锌懈褋邪褌懈 锌芯褌芯褔薪褨 写邪薪褨? 校褋褨 锌芯褌芯褔薪褨 写邪薪褨 锌褉芯 写褨褟谢褜薪褨褋褌褜 (褟泻褖芯 胁芯薪懈 褦) 锌褉懈褋褌褉芯褩 褌邪 薪邪谢邪褕褌褍胁邪薪薪褟 斜褍写褍褌褜 锌械褉械蟹邪锌懈褋邪薪褨.</string>
+    <string name="dbmanagementactivity_really_delete_entire_db">袛褨泄褋薪芯 胁懈写邪谢懈褌懈 胁褋褞 斜邪蟹褍 写邪薪懈褏? 袙褋褨 胁邪褕褨 写邪薪褨 锌褉芯 写褨褟谢褜薪褨褋褌褜 褌邪 写邪薪褨 锌褉芯 胁邪褕褨 锌褉懈褋褌褉芯褩 斜褍写褍褌褜 胁褌褉邪褔械薪褨.</string>
+    <string name="dbmanagementactivity_delete_old_activity_db">袙懈写邪谢懈褌懈 褋褌邪褉褍 斜邪蟹褍 写邪薪懈褏?</string>
+    <string name="dbmanagementactivity_delete_old_activitydb_confirmation">袛褨泄褋薪芯 胁懈写邪谢懈褌懈 褋褌邪褉褍 斜邪蟹褍 写邪薪懈褏 写褨褟谢褜薪芯褋褌械泄? 袛邪薪褨, 褟泻褨 薪械 斜褍谢懈 褨屑锌芯褉褌芯胁邪薪褨, 斜褍写褍褌褜 胁褌褉邪褔械薪褨.</string>
+    <string name="dbmanagementactivity_old_activity_db_successfully_deleted">小褌邪褉褨 写邪薪褨 锌褉芯 写褨褟谢褜薪褨褋褌褜 胁懈写邪谢械薪芯.</string>
+    <string name="dbmanagementactivity_old_activity_db_deletion_failed">袧械 胁写邪谢芯褋褟 胁懈写邪谢懈褌懈 褋褌邪褉褍 斜邪蟹褍 写邪薪懈褏.</string>
+    <string name="title_activity_pebble_pairing">小褌胁芯褉械薪薪褟 锌邪褉懈 蟹 Pebble</string>
+    <string name="pebble_pairing_hint">袧邪 胁邪褕芯屑褍 Android 锌褉懈褋褌褉芯褩 蟹\'褟胁懈褌褜褋褟 褋锌芯胁褨褖械薪薪褟 锌褉芯 褋褌胁芯褉械薪薪褟 锌邪褉懈. 携泻褖芯 褑褜芯谐芯 薪械 褋褌邪谢芯褋褟, 褌芯 蟹薪邪泄写褨褌褜 泄芯谐芯 薪邪 锌邪薪械谢褨 褋锌芯胁褨褖械薪褜 褌邪 锌褨写褌胁械褉写褜褌械 蟹邪锌懈褌 薪邪 褋褌胁芯褉械薪薪褟 锌邪褉懈. 袩褨褋谢褟 褑褜芯谐芯 锌褨写褌胁械褉写褜褌械 蟹邪锌懈褌 薪邪 褋褌胁芯褉械薪薪褟 锌邪褉懈 蟹薪芯胁褍 薪邪 胁邪褕芯屑褍 Pebble.</string>
+    <string name="weather_notification_label">些芯斜 芯褌褉懈屑褍胁邪褌懈 锌芯谐芯写薪褨 写邪薪褨 薪邪 胁邪褕 Pebble, 锌械褉械泻芯薪邪泄褌械褋褟, 褖芯 锌褍薪泻褌 \"Gadgetbridge\" 胁胁褨屑泻薪械薪芯 胁 蟹邪褋褌芯褋褍薪泻褍 \"Weather notification\".
+\n
+\n袛芯写邪褌泻芯胁褨 薪邪谢邪褕褌褍胁邪薪薪褟 褌褍褌 薪械 锌芯褌褉褨斜薪褨.
+\n
+\n校胁褨屑泻薪褍褌懈 蟹邪褋褌芯褋褍薪芯泻 锌芯谐芯写懈 薪邪 胁邪褕芯屑褍 Pebble 屑芯卸薪邪 胁 \"袦械薪械写卸械褉褨 蟹邪褋褌芯褋褍薪泻褨胁\".
+\n
+\n袩褨写褌褉懈屑褍胁邪薪褨 褑懈褎械褉斜谢邪褌懈 锌芯泻邪卸褍褌褜 锌芯谐芯写褍 邪胁褌芯屑邪褌懈褔薪芯.</string>
+    <string name="pref_title_setup_bt_pairing">校胁褨屑泻薪褨褌褜 褋褌胁芯褉械薪薪褟 锌邪褉懈 褔械褉械蟹 Bluetooth</string>
+    <string name="pref_summary_setup_bt_pairing">袙懈屑泻薪褨褌褜 褑械, 褟泻褖芯 褍 胁邪褋 胁懈薪懈泻邪褞褌褜 锌褉芯斜谢械屑懈 蟹 锌褨写\'褦写薪邪薪薪褟屑</string>
+    <string name="mi2_fw_installhandler_fw53_hint">袩械褉械写 胁褋褌邪薪芯胁谢械薪薪褟屑 褑褨褦褩 屑褨泻褉芯锌褉芯谐褉邪屑懈 褋锌芯褔邪褌泻褍 锌芯褌褉褨斜薪芯 胁褋褌邪薪芯胁懈褌懈 胁械褉褋褨褞 %1$s!</string>
+    <string name="mi2_enable_text_notifications_summary"><![CDATA[袩芯褌褉褨斜薪邪 屑褨泻褉芯锌褉芯谐褉邪屑邪 >= 1.0.1.28 褌邪 胁褋褌邪薪芯胁谢械薪懈泄 Mili_pro.ft*.]]></string>
+    <string name="off">袙懈屑泻薪褍褌懈</string>
+    <string name="mi2_dnd_off">袙懈屑泻薪褍褌懈</string>
+    <string name="discovery_attempting_to_pair">小锌褉芯斜邪 褋褌胁芯褉械薪薪褟 锌邪褉懈 蟹 %1$s</string>
+    <string name="discovery_bonding_failed_immediately">袩芯胁\'褟蟹褍胁邪薪薪褟 蟹 %1$s 胁褨写褉邪蟹褍 薪械 胁写邪谢芯褋褟.</string>
+    <string name="discovery_trying_to_connect_to">小锌褉芯斜邪 蟹\'褦写薪邪薪薪褟 蟹: %1$s</string>
+    <string name="discovery_enable_bluetooth">校胁褨屑泻薪褨褌褜 Bluetooth 写谢褟 胁懈褟胁谢械薪薪褟 锌褉懈褋褌褉芯褩胁.</string>
+    <string name="discovery_successfully_bonded">袩芯胁\'褟蟹邪薪懈泄 蟹 %1$s.</string>
+    <string name="discovery_pair_title">小褌胁芯褉懈褌懈 锌邪褉褍 蟹 %1$s?</string>
+    <string name="discovery_pair_question">袙懈斜械褉褨褌褜 \"小褌胁芯褉懈褌懈 锌邪褉褍\" 写谢褟 褋褌胁芯褉械薪薪褟 锌邪褉懈 蟹 胁邪褕懈屑 锌褉懈褋褌褉芯褦屑. 携泻褖芯 薪械 胁写邪褦褌褜褋褟, 褋锌褉芯斜褍泄褌械 蟹薪芯胁褍 斜械蟹 褋褌胁芯褉械薪薪褟 锌邪褉懈.</string>
+    <string name="discovery_yes_pair">小褌胁芯褉懈褌懈 锌邪褉褍</string>
+    <string name="discovery_dont_pair">袧械 褋褌胁芯褉褞胁邪褌懈 锌邪褉褍</string>
+    <string name="mi2_prefs_button_press_count_summary">袣褨谢褜泻褨褋褌褜 薪邪褌懈褋泻邪薪褜 薪邪 泻薪芯锌泻懈 写谢褟 蟹邪锌褍褋泻褍 \"袩芯写褨褟 1\". 袧邪褋褌褍锌薪械 薪邪褌懈褋泻邪薪薪褟 锌褉懈蟹胁械写械 写芯 褋褌胁芯褉械薪薪褟 \"袩芯写褨褟 2\" 泄 褌邪泻 写邪谢褨.</string>
+    <string name="mi2_prefs_button_press_broadcast_summary">袩芯胁褨写芯屑谢械薪薪褟 薪邪写褋懈谢邪褦褌褜褋褟 褉邪蟹芯屑 蟹 锌芯写褨褦褞. 袩邪褉邪屑械褌褉 `button_id` 写芯写邪褦褌褜褋褟 邪胁褌芯屑邪褌懈褔薪芯 写芯 泻芯卸薪芯谐芯 锌芯胁褨写芯屑谢械薪薪褟.</string>
+    <string name="mi2_prefs_button_action_summary">校胁褨屑泻薪褍褌懈 写褨褞 薪邪 锌械胁薪褍 泻褨谢褜泻褨褋褌褜 薪邪褌懈褋泻邪薪褜 薪邪 泻薪芯锌泻褍</string>
+    <string name="mi2_prefs_button_action_vibrate_summary">袙屑懈泻邪褌懈 胁褨斜褉邪褑褨褞 薪邪 斜褉邪褋谢械褌褨 锌褉懈 褋锌褉邪褑褜芯胁邪薪薪褨 写褨褩 泻薪芯锌泻懈</string>
+    <string name="mi2_prefs_button_press_count_max_delay">袦邪泻褋懈屑邪谢褜薪邪 蟹邪褌褉懈屑泻邪 屑褨卸 薪邪褌懈褋泻邪薪薪褟屑懈</string>
+    <string name="mi2_prefs_button_press_count_max_delay_summary">袦邪泻褋懈屑邪谢褜薪邪 蟹邪褌褉懈屑泻邪 屑褨卸 薪邪褌懈褋泻邪薪薪褟屑 泻薪芯锌芯泻 褍 屑褨谢褨褋械泻褍薪写邪褏</string>
+    <string name="_pebble_watch_open_on_phone">袙褨写泻褉懈褌懈 薪邪 锌褉懈褋褌褉芯褩 Android</string>
+    <string name="_pebble_watch_mute">袘械蟹 蟹胁褍泻褍</string>
+    <string name="activity_web_view">袛褨褟谢褜薪褨褋褌褜 (Web 胁械褉褋褨褟)</string>
+    <string name="kind_gps_cep">袙懈锌褉邪胁谢械薪薪褟 锌芯屑懈谢芯泻 GPS</string>
+    <string name="kind_watchface">笑懈褎械褉斜谢邪褌</string>
+    <string name="on">校胁褨屑泻薪褍褌懈</string>
+    <string name="controlcenter_start_activity_tracks">袉褋褌芯褉褨褟 胁邪褕芯褩 写褨褟谢褜薪芯褋褌褨</string>
+    <string name="activity_type_not_measured">袧械 胁懈屑褨褉褟薪芯</string>
+    <string name="activity_type_activity">袛褨褟谢褜薪褨褋褌褜</string>
+    <string name="activity_type_not_worn">袩褉懈褋褌褉褨泄 薪械 芯写褟谐薪褍褌芯</string>
+    <string name="activity_type_running">袘褨谐</string>
+    <string name="activity_type_walking">啸芯写褜斜邪</string>
+    <string name="reset_index">小泻懈薪褍褌懈 写邪褌褍 芯褌褉懈屑邪薪薪褟</string>
+    <string name="watch9_calibration_hint">袙褋褌邪薪芯胁懈褌懈 锌芯褌芯褔薪懈泄 褔邪褋 胁邪褕芯谐芯 锌褉懈褋褌褉芯褞.</string>
+    <string name="title_activity_watch9_pairing">Watch 9 褋褌胁芯褉械薪薪褟 锌邪褉懈</string>
+    <string name="title_activity_watch9_calibration">Watch 9 泻邪谢褨斜褉褍胁邪薪薪褟</string>
+    <string name="mi3_prefs_band_screen_unlock">袪芯蟹斜谢芯泻褍胁邪薪薪褟 械泻褉邪薪邪 斜褉邪褋谢械褌邪</string>
+    <string name="mi3_prefs_band_screen_unlock_summary">袚芯褉褌邪泄褌械 胁谐芯褉褍, 褖芯斜 褉芯蟹斜谢芯泻褍胁邪褌懈 械泻褉邪薪 斜褉邪褋谢械褌邪</string>
+    <string name="mi3_prefs_night_mode_summary">袗胁褌芯屑邪褌懈褔薪芯 蟹薪懈卸褍胁邪褌懈 褟褋泻褉邪胁褨褋褌褜 械泻褉邪薪邪 斜褉邪褋谢械褌邪 胁薪芯褔褨</string>
+    <string name="share_log_warning">袘褍写褜 谢邪褋泻邪, 锌邪屑\'褟褌邪泄褌械, 褖芯 褎邪泄谢懈 卸褍褉薪邪谢褨胁 Gadgetbridge 屑芯卸褍褌褜 屑褨褋褌懈褌懈 斜邪谐邪褌芯 芯褋芯斜懈褋褌芯褩 褨薪褎芯褉屑邪褑褨褩, 胁泻谢褞褔邪褞褔懈, 邪谢械 薪械 芯斜屑械卸褍褞褔懈褋褜, 写邪薪褨 锌褉芯 蟹写芯褉芯胁\'褟, 褍薪褨泻邪谢褜薪褨 褨写械薪褌懈褎褨泻邪褌芯褉懈 (褟泻-芯褌 MAC 邪写褉械褋邪 锌褉懈褋褌褉芯褞), 屑褍蟹懈褔薪褨 胁锌芯写芯斜邪薪薪褟 褌芯褖芯. 袙褨写褉械写邪谐褍泄褌械 褎邪泄谢 褌邪 胁懈写邪谢褨褌褜 褑褞 褨薪褎芯褉屑邪褑褨褞 锌械褉械写 薪邪写褋懈谢邪薪薪褟屑 褎邪泄谢褍.</string>
+    <string name="pref_title_contextual_arabic">袣芯薪褌械泻褋褌薪邪 邪褉邪斜褋褜泻邪 屑芯胁邪</string>
+    <string name="pref_summary_contextual_arabic">校胁褨屑泻薪褨褌褜 褑械, 褖芯斜 锌褨写褌褉懈屑褍胁邪褌懈 泻芯薪褌械泻褋褌薪褍 邪褉邪斜褋褜泻褍 屑芯胁褍</string>
+    <string name="preferences_rtl_settings">袩褨写褌褉懈屑泻邪 褋锌褉邪胁邪 薪邪谢褨胁芯</string>
+    <string name="overstep">袩械褉械斜褨谢褜褕械薪芯: %1$d</string>
+    <string name="appwidget_setting_alarm">袘褍写懈谢褜薪懈泻 胁褋褌邪薪芯胁谢械薪芯 薪邪 %1$02d:%2$02d</string>
+    <string name="edittext_notification_filter_words_hint">袙胁械写褨褌褜 薪械芯斜褏褨写薪褨 褋谢芯胁邪, 薪芯胁懈泄 褉褟写芯泻 写谢褟 泻芯卸薪芯谐芯 褋谢芯胁邪</string>
+    <string name="toast_notification_filter_saved_successfully">肖褨谢褜褌褉 褋锌芯胁褨褖械薪褜 蟹斜械褉械卸械薪芯</string>
+    <string name="filter_mode_none">袧械 褎褨谢褜褌褉褍胁邪褌懈</string>
+    <string name="filter_mode_whitelist">袩芯泻邪蟹褍胁邪褌懈, 泻芯谢懈 褋谢芯胁邪 胁懈褟胁谢械薪芯</string>
+    <string name="filter_mode_blacklist">袧械 锌芯泻邪蟹褍胁邪褌懈, 泻芯谢懈 褋谢芯胁邪 胁懈褟胁谢械薪芯</string>
+    <string name="filter_submode_at_least_one">袩褉懈薪邪泄屑薪褨 芯写薪械 蟹褨 褋谢褨胁</string>
+    <string name="filter_submode_all">校褋褨 褋谢芯胁邪</string>
+    <string name="mode_configuration">袣芯薪褎褨谐褍褉邪褑褨褟 褉械卸懈屑褍</string>
+    <string name="appwidget_not_connected">袧械 锌褨写\'褦写薪邪薪芯, 斜褍写懈谢褜薪懈泻 薪械 胁褋褌邪薪芯胁谢械薪芯.</string>
+    <string name="zetime_signaling_vibrate_once">袙褨斜褉邪褑褨褟 芯写懈薪 褉邪蟹</string>
+    <string name="zetime_signaling_vibrate_twice">袙褨斜褉邪褑褨褟 写胁褨褔褨</string>
+    <string name="zetime_signaling_vibrate_beep_once">袙褨斜褉邪褑褨褟 褌邪 褋懈谐薪邪谢 芯写懈薪 褉邪蟹</string>
+    <string name="activity_prefs_calories_burnt">些芯写械薪薪邪 屑械褌邪: 褋锌邪谢械薪褨 泻邪谢芯褉褨褩</string>
+    <string name="activity_prefs_distance_meters">些芯写械薪薪邪 屑械褌邪: 胁褨写褋褌邪薪褜 褍 屑械褌褉邪褏</string>
+    <string name="activity_prefs_activetime_minutes">些芯写械薪薪邪 屑械褌邪: 褌褉懈胁邪谢褨褋褌褜 写褨褟谢褜薪芯褋褌褨 褍 褏胁懈谢懈薪邪褏</string>
+    <string name="devicetype_miscale2">Mi Scale 2</string>
+    <string name="pref_summary_authkey">袟屑褨薪褨褌褜 泻谢褞褔 邪胁褌械薪褌懈褎褨泻邪褑褨褩 薪邪 蟹邪谐邪谢褜薪懈泄 泻谢褞褔 薪邪 胁褋褨褏 胁邪褕懈褏 锌褉懈褋褌褉芯褟褏 Android, 蟹 褟泻懈屑懈 锌芯褌褉褨斜薪芯 蟹\'褦写薪邪褌懈褋褟. 袩芯锌械褉械写薪褨泄 褍褋褌邪谢械薪懈泄 泻谢褞褔 写谢褟 胁褋褨褏 锌褉懈褋褌褉芯褩胁 鈥� 0123456789@ABCDE</string>
+    <string name="devicetype_bfh16">BFH-16</string>
+    <string name="fw_upgrade_notice_amazfitcor2">袙懈 蟹斜懈褉邪褦褌械褋褟 胁褋褌邪薪芯胁懈褌懈 屑褨泻褉芯锌褉芯谐褉邪屑褍 %s 薪邪 胁邪褕 Amazfit Cor 2.
+\n
+\n小锌芯褔邪褌泻褍 胁褋褌邪薪芯胁褨褌褜 褎邪泄谢 .fw, 锌芯褌褨屑 .res. 袙邪褕 谐芯写懈薪薪懈泻 锌械褉械蟹邪胁邪薪褌邪卸懈褌褜褋褟 锌褨褋谢褟 胁褋褌邪薪芯胁谢械薪薪褟 褎邪泄谢褍 .fw.
+\n
+\n袩褉懈屑褨褌泻邪: 胁邪屑 薪械 锌芯褌褉褨斜薪芯 胁褋褌邪薪芯胁谢褞胁邪褌懈 褎邪泄谢 .res, 褟泻褖芯 胁褨薪 褌邪泻懈泄 褋邪屑懈泄, 褟泻 褉邪薪褨褕械 胁褋褌邪薪芯胁谢械薪懈泄.
+\n
+\n袩袪袨袛袨袙袞校袡孝袝 袧袗 袙袥袗小袧袠袡 袪袠袟袠袣!
+\n
+\n袩袨袙袧袉小孝挟 袧袝袩袝袪袝袙袉袪袝袧袨, 袡袦袨袙袉袪袧袨, 袙袗袦 袩袨孝袪袉袘袧袨 袙小孝袗袧袨袙袠孝袠 袦袉袣袪袨袩袪袨袚袪袗袦校 BEATS_W, 携袣些袨 袧袗袟袙袗 袙袗楔袨袚袨 袩袪袠小孝袪袨挟 \"Amazfit Band 2\"</string>
+    <string name="devicetype_amazfit_cor2">Amazfit Cor 2</string>
+    <string name="devicetype_miband4">Mi Smart Band 4</string>
+    <string name="fw_upgrade_notice_miband4">袙懈 蟹斜懈褉邪褦褌械褋褟 胁褋褌邪薪芯胁懈褌懈 屑褨泻褉芯锌褉芯谐褉邪屑褍 %s 薪邪 胁邪褕 Mi Band 4.
+\n
+\n小锌芯褔邪褌泻褍 胁褋褌邪薪芯胁褨褌褜 褎邪泄谢 .fw, 锌芯褌褨屑 .res. 袙邪褕 谐芯写懈薪薪懈泻 锌械褉械蟹邪胁邪薪褌邪卸懈褌褜褋褟 锌褨褋谢褟 胁褋褌邪薪芯胁谢械薪薪褟 褎邪泄谢褍 .fw.
+\n
+\n袩褉懈屑褨褌泻邪: 胁邪屑 薪械 锌芯褌褉褨斜薪芯 胁褋褌邪薪芯胁谢褞胁邪褌懈 褎邪泄谢 .res, 褟泻褖芯 胁褨薪 褌邪泻懈泄 褋邪屑懈泄, 褟泻 褉邪薪褨褕械 胁褋褌邪薪芯胁谢械薪懈泄.
+\n
+\n袩袪袨袛袨袙袞校袡孝袝 袧袗 袙袥袗小袧袠袡 袪袠袟袠袣!</string>
+    <string name="pref_title_charts_average">袩芯泻邪蟹褍胁邪褌懈 褋械褉械写薪褦 蟹薪邪褔械薪薪褟 胁 写褨邪谐褉邪屑邪褏</string>
+    <string name="pref_charts_range_on">袛褨邪锌邪蟹芯薪 写褨邪谐褉邪屑 蟹邪 屑褨褋褟褑褜</string>
+    <string name="pref_charts_range_off">袛褨邪锌邪蟹芯薪 写褨邪谐褉邪屑 蟹邪 褌懈卸写械薪褜</string>
+    <string name="devicetype_mijia_lywsd02">Mijia Smart Clock</string>
+    <string name="menuitem_nfc">NFC</string>
+    <string name="activity_db_management_autoexport_explanation">袛谢褟 邪胁褌芯屑邪褌懈褔薪芯谐芯 械泻褋锌芯褉褌褍胁邪薪薪褟 斜邪蟹懈 写邪薪懈褏 斜褍谢芯 锌褉懈蟹薪邪褔械薪芯 褑褞 褌械泻褍:</string>
+    <string name="activity_db_management_autoexport_label">袗胁褌芯械泻褋锌芯褉褌</string>
+    <string name="activity_DB_test_export_button">袟邪锌褍褋褌懈褌懈 邪胁褌芯械泻褋锌芯褉褌 蟹邪褉邪蟹</string>
+    <string name="activity_DB_test_export_message">袝泻褋锌芯褉褌 斜邪蟹懈 写邪薪懈褏鈥�</string>
+    <string name="activity_DB_delete_legacy_button">袙懈写邪谢懈褌懈 褋褌邪褉褍 斜邪蟹褍 写邪薪懈褏</string>
+    <string name="activity_DB_empty_button">袨褔懈褋褌懈褌懈 斜邪蟹褍 写邪薪懈褏</string>
+    <string name="activity_db_management_empty_DB">袨褔懈褋褌懈褌懈 斜邪蟹褍 写邪薪懈褏</string>
+    <string name="activity_db_management_exportimport_label">袝泻褋锌芯褉褌 褌邪 褨屑锌芯褉褌</string>
+    <string name="activity_db_management_empty_db_warning">校胁邪谐邪! 袧邪褌懈褋薪褍胁褕懈 褑褞 泻薪芯锌泻褍, 胁懈 胁懈写邪谢懈褌械 褋胁芯褞 斜邪蟹褍 写邪薪懈褏 褨 锌芯褔薪械褌械 蟹 薪褍谢褟.</string>
+    <string name="appwidget_sleep_alarm_widget_label">袘褍写懈谢褜薪懈泻</string>
+    <string name="widget_listing_label">小褌邪薪 褨 斜褍写懈谢褜薪懈泻懈</string>
+    <string name="widget_set_alarm_after">袙褋褌邪薪芯胁懈褌懈 斜褍写懈谢褜薪懈泻 锌褨褋谢褟:</string>
+    <string name="widget_5_minutes">5 褏胁懈谢懈薪</string>
+    <string name="widget_10_minutes">10 褏胁懈谢懈薪</string>
+    <string name="widget_20_minutes">20 褏胁懈谢懈薪</string>
+    <string name="widget_1_hour">1 谐芯写懈薪褍</string>
+    <plurals name="widget_alarm_target_hours">
+        <item quantity="one">%d 谐芯写懈薪邪</item>
+        <item quantity="few">%d 谐芯写懈薪懈</item>
+        <item quantity="many">%d 谐芯写懈薪</item>
+        <item quantity="other">%d 谐芯写懈薪</item>
+    </plurals>
+    <string name="activity_error_no_app_for_gpx">袛谢褟 锌械褉械谐谢褟写褍 屑邪褉褕褉褍褌褨胁, 蟹邪胁邪薪褌邪卸褌械 蟹邪褋褌芯褋褍薪芯泻, 褟泻懈泄 屑芯卸械 芯斜褉芯斜谢褟褌懈 GPX 褎邪泄谢懈.</string>
+    <string name="devicetype_makibes_hr3">Makibes HR3</string>
+    <string name="devicetype_amazfit_bip_lite">Amazfit Bip Lite</string>
+    <string name="prefs_enable_find_phone">校胁褨屑泻薪褍褌懈 \'袟薪邪泄褌懈 褌械谢械褎芯薪\'</string>
+    <string name="prefs_find_phone_summary">袙懈泻芯褉懈褋褌芯胁褍泄褌械 褋胁褨泄 斜褉邪褋谢械褌, 褖芯斜 胁褨写褌胁芯褉懈褌懈 屑械谢芯写褨褞 胁懈泻谢懈泻褍 薪邪 胁邪褕芯屑褍 褌械谢械褎芯薪褨.</string>
+    <string name="prefs_find_phone_duration">孝褉懈胁邪谢褨褋褌褜 屑械谢芯写褨褩 胁 褋械泻褍薪写邪褏</string>
+    <string name="maximum_duration">孝褉懈胁邪谢褨褋褌褜</string>
+    <string name="discovery_need_to_enter_authkey">笑械泄 锌褉懈褋褌褉褨泄 锌芯褌褉械斜褍褦 褋械泻褉械褌薪懈泄 泻谢褞褔 邪胁褌械薪褌懈褎褨泻邪褑褨褩, 蟹邪褌懈褋薪褨褌褜, 褖芯斜 胁胁械褋褌懈 泄芯谐芯. 袩褉芯褔懈褌邪泄褌械 锌褉芯 褑械 褍 胁褨泻褨.</string>
+    <string name="fw_upgrade_notice_amazfitbip_lite">袙懈 蟹斜懈褉邪褦褌械褋褟 胁褋褌邪薪芯胁懈褌懈 屑褨泻褉芯锌褉芯谐褉邪屑褍 %s 薪邪 胁邪褕 Amazfit Bip Lite.
+\n
+\n小锌芯褔邪褌泻褍 胁褋褌邪薪芯胁褨褌褜 褎邪泄谢 .fw, 锌芯褌褨屑 .res. 袙邪褕 谐芯写懈薪薪懈泻 锌械褉械蟹邪胁邪薪褌邪卸懈褌褜褋褟 锌褨褋谢褟 胁褋褌邪薪芯胁谢械薪薪褟 褎邪泄谢褍 .fw.
+\n
+\n袩褉懈屑褨褌泻邪: 胁邪屑 薪械 锌芯褌褉褨斜薪芯 胁褋褌邪薪芯胁谢褞胁邪褌懈 褎邪泄谢 .res, 褟泻褖芯 胁褨薪 褌邪泻懈泄 褋邪屑懈泄, 褟泻 褉邪薪褨褕械 胁褋褌邪薪芯胁谢械薪懈泄.
+\n
+\n袩袪袨袛袨袙袞校袡孝袝 袧袗 袙袥袗小袧袠袡 袪袠袟袠袣!</string>
+    <string name="devicetype_amazfit_gtr">Amazfit GTR</string>
+    <string name="fw_upgrade_notice_amazfitgtr">袙懈 蟹斜懈褉邪褦褌械褋褟 胁褋褌邪薪芯胁懈褌懈 屑褨泻褉芯锌褉芯谐褉邪屑褍 %s 薪邪 胁邪褕 Amazfit GTR.
+\n
+\n小锌芯褔邪褌泻褍 胁褋褌邪薪芯胁褨褌褜 褎邪泄谢 .fw, 锌芯褌褨屑 .res, 邪 胁 泻褨薪褑褨 .gps. 袙邪褕 谐芯写懈薪薪懈泻 锌械褉械蟹邪胁邪薪褌邪卸懈褌褜褋褟 锌褨褋谢褟 胁褋褌邪薪芯胁谢械薪薪褟 褎邪泄谢褍 .fw.
+\n
+\n袩褉懈屑褨褌泻邪: 胁邪屑 薪械 锌芯褌褉褨斜薪芯 胁褋褌邪薪芯胁谢褞胁邪褌懈 褎邪泄谢懈 .res 褌邪 .gps, 褟泻褖芯 胁芯薪懈 褌邪泻褨 褋邪屑褨, 褖芯 斜褍谢懈 胁褋褌邪薪芯胁谢械薪褨 褉邪薪褨褕械.
+\n
+\n袩袪袨袛袨袙袞校袡孝袝 袧袗 袙袥袗小袧袠袡 袪袠袟袠袣!</string>
+    <string name="devicetype_amazfit_gts">Amazfit GTS</string>
+    <string name="fw_upgrade_notice_amazfitgts">袙懈 蟹斜懈褉邪褦褌械褋褟 胁褋褌邪薪芯胁懈褌懈 屑褨泻褉芯锌褉芯谐褉邪屑褍 %s 薪邪 胁邪褕 Amazfit GTS.
+\n
+\n小锌芯褔邪褌泻褍 胁褋褌邪薪芯胁褨褌褜 褎邪泄谢 .fw, 锌芯褌褨屑 .res, 邪 胁 泻褨薪褑褨 .gps. 袙邪褕 谐芯写懈薪薪懈泻 锌械褉械蟹邪胁邪薪褌邪卸懈褌褜褋褟 锌褨褋谢褟 胁褋褌邪薪芯胁谢械薪薪褟 褎邪泄谢褍 .fw.
+\n
+\n袩褉懈屑褨褌泻邪: 胁邪屑 薪械 锌芯褌褉褨斜薪芯 胁褋褌邪薪芯胁谢褞胁邪褌懈 褎邪泄谢懈 .res 褌邪 .gps, 褟泻褖芯 胁芯薪懈 褌邪泻褨 褋邪屑褨, 褖芯 斜褍谢懈 胁褋褌邪薪芯胁谢械薪褨 褉邪薪褨褕械.
+\n
+\n袩袪袨袛袨袙袞校袡孝袝 袧袗 袙袥袗小袧袠袡 袪袠袟袠袣!</string>
+    <string name="devicetype_qhybrid">Fossil Q Hybrid</string>
+    <string name="preferences_qhybrid_settings">袧邪谢邪褕褌褍胁邪薪薪褟 Q Hybrid</string>
+    <string name="watch_not_connected">袚芯写懈薪薪懈泻 薪械 锌褨写\'褦写薪邪薪芯</string>
+    <string name="qhybrid_vibration_strength">褋懈谢邪 胁褨斜褉邪褑褨褩:</string>
+    <string name="qhybrid_goal_in_steps">袦械褌邪 褍 泻褉芯泻邪褏</string>
+    <string name="qhybrid_time_shift">蟹屑褨褖械薪薪褟 褔邪褋褍</string>
+    <string name="qhybrid_second_timezone_offset_relative_to_utc">蟹屑褨褖械薪薪褟 写褉褍谐芯谐芯 褔邪褋芯胁芯谐芯 锌芯褟褋邪 胁褨写薪芯褋薪芯 UTC</string>
+    <string name="qhybrid_overwrite_buttons">锌械褉械蟹邪锌懈褋 泻薪芯锌芯泻</string>
+    <string name="qhybrid_use_activity_hand_as_notification_counter">胁懈泻芯褉懈褋褌芯胁褍泄褌械 邪泻褌懈胁薪褍 褉褍泻褍 谢褨褔懈谢褜薪懈泻芯屑 褋锌芯胁褨褖械薪褜</string>
+    <string name="qhybrid_prompt_million_steps">袙褋褌邪薪芯胁褨褌褜 泻褨谢褜泻褨褋褌褜 泻褉芯泻褨胁 写芯 屑褨谢褜泄芯薪邪, 褖芯斜 邪泻褌懈胁褍胁邪褌懈 褑械.</string>
+    <string name="qhybrid_buttons_overwrite_success">袣薪芯锌泻懈 锌械褉械蟹邪锌懈褋邪薪芯</string>
+    <string name="qhybrid_buttons_overwrite_error">袩芯屑懈谢泻邪 锌械褉械蟹邪锌懈褋褍 泻薪芯锌芯泻</string>
+    <string name="qhybrid_offset_timezone">蟹屑褨褖械薪薪褟 褔邪褋芯胁芯谐芯 锌芯褟褋邪 薪邪</string>
+    <string name="qhybrid_changes_delay_prompt">蟹屑褨薪邪 屑芯卸械 褌褉懈胁邪褌懈 泻褨谢褜泻邪 褋械泻褍薪写鈥�</string>
+    <string name="qhybrid_offset_time_by">蟹屑褨褖械薪薪褟 褔邪褋褍 薪邪</string>
+    <string name="devicetype_banglejs">Bangle.js</string>
+    <string name="devicetype_y5">Y5</string>
+    <string name="prefs_button_single_press_action_selection_title">袩芯写褨褟 1 写褨褟</string>
+    <string name="prefs_button_double_press_action_selection_title">袩芯写褨褟 2 写褨褟</string>
+    <string name="prefs_button_triple_press_action_selection_title">袩芯写褨褟 3 写褨褟</string>
+    <string name="prefs_button_variable_actions">袛械褌邪谢褜薪褨 薪邪谢邪褕褌褍胁邪薪薪褟 薪邪褌懈褋泻邪薪薪褟 泻薪芯锌泻懈</string>
+    <string name="prefs_button_long_press_action_selection_title">袛褨褟 褌褉懈胁邪谢芯谐芯 蟹邪褌懈褋泻邪薪薪褟 泻薪芯锌泻懈</string>
+    <string name="error_no_location_access">袛谢褟 薪邪谢械卸薪芯褩 褉芯斜芯褌懈 褋泻邪薪褍胁邪薪薪褟 锌芯褌褉褨斜薪芯 褍胁褨屑泻薪褍褌懈 褌邪 薪邪写邪褌懈 写芯褋褌褍锌 写芯 屑褨褋褑械锌械褉械斜褍胁邪薪薪褟</string>
+    <string name="devicetype_itag">iTag</string>
+    <string name="pref_title_allow_high_mtu">袛芯蟹胁芯谢褟褌懈 胁懈褋芯泻懈泄 MTU</string>
+    <string name="pref_summary_allow_high_mtu">袩褨写胁懈褖褍褦 褕胁懈写泻褨褋褌褜 锌械褉械写邪褔褨 写邪薪懈褏, 邪谢械 屑芯卸械 薪械 锌褉邪褑褞胁邪褌懈 薪邪 写械褟泻懈褏 锌褉懈褋褌褉芯褟褏 Android.</string>
+    <string name="hr_widget_steps">袣褉芯泻懈</string>
+    <string name="hr_widget_calories">袣邪谢芯褉褨褩</string>
+    <string name="hr_widget_battery">袗泻褍屑褍谢褟褌芯褉</string>
+    <string name="hr_widget_weather">袩芯谐芯写邪</string>
+    <string name="find_my_phone_notification">袟薪邪泄褌懈 屑褨泄 褌械谢械褎芯薪</string>
+    <string name="power_mode_watch">袥懈褕械 谐芯写懈薪薪懈泻</string>
+    <string name="power_mode_saving">袟邪芯褖邪写卸械薪薪褟 械薪械褉谐褨褩</string>
+    <string name="power_mode_normal">袟胁懈褔邪泄薪懈泄</string>
+    <string name="prefs_sensors_button_bp_calibration">袣邪谢褨斜褉褍胁邪褌懈</string>
+    <string name="pref_summary_notifications_and_calls_title_reject">袙懈屑泻薪械薪芯 鈥� 褨谐薪芯褉褍胁邪薪薪褟, 褍胁褨屑泻薪械薪芯 鈥� 胁褨写褏懈谢械薪薪褟</string>
+    <string name="prefs_notifications_and_calls_reject">袣薪芯锌泻邪 褨谐薪芯褉褍胁邪褌懈/胁褨写褏懈谢懈褌懈 胁懈泻谢懈泻</string>
+    <string name="pref_header_notifications_and_calls_callhandling">校褌褉懈屑邪薪薪褟 胁懈泻谢懈泻褍</string>
+    <string name="pref_title_notifications_and_calls_repeat_on_misscall">袩芯胁褌芯褉褞胁邪褌懈 泻芯卸薪褨 X 褏胁懈谢懈薪(懈)</string>
+    <string name="pref_summary_notifications_and_calls_enable_misscall">袩芯胁褌芯褉褞胁邪褌懈 褖芯褏胁懈谢懈薪懈</string>
+    <string name="pref_notifications_and_calls_enable_misscall">小锌芯胁褨褖械薪薪褟 锌褉芯 锌褉芯锌褍褖械薪褨 胁懈泻谢懈泻懈</string>
+    <string name="prefs_notifications_and_calls_continious_ring">袛芯蟹胁芯谢懈褌懈 褋锌芯胁褨褖械薪薪褟 锌褨写 褔邪褋 胁懈泻谢懈泻褍</string>
+    <string name="pref_title_notifications_and_calls_repeat_on_call">袩芯胁褌芯褉褞胁邪褌懈 褋锌芯胁褨褖械薪薪褟 锌褉芯 胁懈泻谢懈泻懈</string>
+    <string name="pref_header_notifications_and_calls">小锌芯胁褨褖械薪薪褟 褌邪 胁懈泻谢懈泻懈</string>
+    <string name="title_activity_LenovoWatch_calibration">袣邪谢褨斜褉褍胁邪薪薪褟 Watch X Plus</string>
+    <string name="pref_title_vibration_strength">小懈谢邪 胁褨斜褉邪褑褨褩</string>
+    <string name="pref_title_relax_firmware_checks">校胁褨屑泻薪褨褌褜 褑械, 褟泻褖芯 胁懈 褏芯褔械褌械 胁褋褌邪薪芯胁懈褌懈 屑褨泻褉芯锌褉芯谐褉邪屑褍, 褟泻邪 薪械 锌褉懈蟹薪邪褔械薪邪 写谢褟 胁邪褕芯谐芯 锌褉懈褋褌褉芯褞 (薪邪 胁邪褕 胁谢邪褋薪懈泄 褉懈蟹懈泻)</string>
+    <string name="pref_summary_relax_firmware_checks">袟薪懈蟹懈褌懈 胁懈屑芯谐懈 写芯 屑褨泻褉芯锌褉芯谐褉邪屑懈</string>
+    <string name="pref_title_sync_caldendar">小懈薪褏褉芯薪褨蟹褍胁邪褌懈 锌芯写褨褩 泻邪谢械薪写邪褉褟</string>
+    <string name="pref_summary_sync_calendar">袧邪写褋懈谢邪褌懈 褋锌芯胁褨褖械薪薪褟 泻邪谢械薪写邪褉褟, 薪邪胁褨褌褜 泻芯谢懈 锌褉懈褋褌褉褨泄 胁褨写\'褦写薪邪薪芯</string>
+    <string name="pref_summary_custom_deviceicon">袩芯泻邪蟹褍胁邪褌懈 褋锌械褑懈褎褨褔薪褍 锌褨泻褌芯谐褉邪屑褍 褋锌芯胁褨褖械薪褜 胁褨写 Android 蟹邪屑褨褋褌褜 Gadgetbridge 泻芯谢懈 锌褉懈褋褌褉褨泄 锌褨写\'褦写薪邪薪芯</string>
+    <string name="pref_title_custom_deviceicon">袩芯泻邪蟹褍胁邪褌懈 褋锌械褑懈褎褨褔薪褨 锌褨泻褌芯谐褉邪屑懈 褋锌芯胁褨褖械薪褜</string>
+    <string name="pref_title_device_spec_settings_show_raw_graph">袩芯泻邪蟹褍胁邪褌懈 RAW 写邪薪褨 薪邪 谐褉邪褎褨泻褍 邪泻褌懈胁薪芯褋褌褨</string>
+    <string name="pref_summary_device_spec_settings_title_force_time">袩褉懈屑褍褋芯胁邪 邪胁褌芯屑邪褌懈褔薪邪 褋懈薪褏褉芯薪褨蟹邪褑褨褟 褔邪褋褍 锌褉懈 锌械褉械蟹\'褦写薪邪薪薪褨. 小褌褉褨谢泻懈 谐芯写懈薪薪懈泻邪 屑芯卸褍褌褜 锌芯泻邪蟹褍胁邪褌懈 薪械锌褉邪胁懈谢褜薪懈泄 褔邪褋!</string>
+    <string name="pref_summary_notifications_and_calls_title_shake_reject">袩芯胁褌芯褉褞褦 写褨褞 泻薪芯锌泻懈 薪邪 谐芯写懈薪薪懈泻褍</string>
+    <string name="pref_summary_force_white_color_scheme">袣芯褉懈褋薪芯, 褟泻褖芯 薪邪 胁邪褕芯屑褍 谐芯写懈薪薪懈泻褍 褋褌褉褨谢泻懈 褌械屑薪芯谐芯 泻芯谢褜芯褉褍</string>
+    <string name="notification_channel_high_priority_name">袙懈褋芯泻懈泄 锌褉褨芯褉懈褌械褌</string>
+    <string name="pref_qhybrid_title_widget_draw_circles">袩芯泻邪蟹邪褌懈 泻芯谢邪 胁褨写卸械褌褨胁</string>
+    <string name="pref_qhybrid_save_raw_activity_files">袟斜械褉械谐褌懈 褋懈褉褨 写邪薪褨 锌褉芯 写褨褟谢褜薪褨褋褌褜</string>
+    <string name="hr_appname_commute">袩芯褩蟹写泻邪</string>
+    <string name="hr_appname_stopwatch">小械泻褍薪写芯屑褨褉</string>
+    <string name="hr_widget_active_minutes">啸胁懈谢懈薪懈 写褨褟谢褜薪芯褋褌褨</string>
+    <string name="hr_widget_date">袛邪褌邪</string>
+    <string name="hr_widget_heart_rate">袩褍谢褜褋</string>
+    <string name="menuitem_workout">孝褉械薪褍胁邪薪薪褟</string>
+    <string name="menuitem_eventreminder">袧邪谐邪写褍胁邪薪薪褟</string>
+    <string name="menuitem_hr">袩褍谢褜褋</string>
+    <string name="menuitem_pai">PAI</string>
+    <string name="devicetype_watchxplus">Watch X Plus</string>
+    <string name="devicetype_watchx">Watch X</string>
+    <string name="devicetype_amazfit_bips">Amazfit Bip S</string>
+    <string name="find_lost_device_you_found_it">袟薪邪泄写械薪芯!</string>
+    <string name="pref_title_force_white_color_scheme">袩褉懈屑褍褋芯胁芯 胁褋褌邪薪芯胁懈褌懈 褔芯褉薪芯-斜褨谢褍 泻芯谢褨褉薪褍 褋褏械屑褍</string>
+    <string name="pref_header_auto_fetch">袗胁褌芯屑邪褌懈褔薪械 芯褌褉懈屑褍胁邪薪薪褟</string>
+    <string name="power_mode_title">袪械卸懈屑 卸懈胁谢械薪薪褟 谐芯写懈薪薪懈泻邪</string>
+    <string name="prefs_sensors_button_bp_calibration_sum">袧邪褌懈褋薪褨褌褜 褌褍褌, 褖芯斜 锌芯褔邪褌懈 泻邪谢褨斜褉褍胁邪薪薪褟</string>
+    <string name="pref_sensors_bp_calibration_high">袗褉褌械褉褨邪谢褜薪懈泄 褌懈褋泻 小袠小孝袨袥袉效袧袠袡 (胁懈褋芯泻懈泄)</string>
+    <string name="pref_sensors_bp_calibration_low">袗褉褌械褉褨邪谢褜薪懈泄 褌懈褋泻 袛袉袗小孝袨袥袉效袧袠袡 (薪懈蟹褜泻懈泄)</string>
+    <string name="pref_sensors_bp_calibration">袣邪谢褨斜褉褍胁邪薪薪褟 邪褉褌械褉褨邪谢褜薪芯谐芯 褌懈褋泻褍</string>
+    <string name="pref_title_sensors_altitude">袣邪谢褨斜褉褍胁邪薪薪褟 胁懈褋芯褌懈</string>
+    <string name="pref_header_sensors_calibration">袣邪谢褨斜褉褍胁邪薪薪褟 褋械薪褋芯褉邪</string>
+    <string name="pref_title_device_spec_settings_force_time">袩褉懈屑褍褋芯胁邪 褋懈薪褏褉芯薪褨蟹邪褑褨褟 褔邪褋褍</string>
+    <string name="pref_header_device_spec_settings">袧邪谢邪褕褌褍胁邪薪薪褟 锌褉懈褋褌褉芯褞</string>
+    <string name="hr_widget_nothing">袩芯褉芯卸薪褜芯</string>
+    <string name="hr_appname_wellness">小邪屑芯锌芯褔褍褌褌褟</string>
+    <string name="hr_appname_workout">孝褉械薪褍胁邪薪薪褟</string>
+    <string name="prefs_notifications_and_calls_shake_reject">袩芯褌褉褍褋懈褌懈 写谢褟 褨谐薪芯褉褍胁邪薪薪褟/胁褨写褏懈谢械薪薪褟 胁懈泻谢懈泻褍</string>
+    <string name="menuitem_unknown">袧械胁褨写芯屑芯</string>
+    <string name="czesh">效械褋褜泻邪</string>
+    <string name="swedish">楔胁械写褋褜泻邪</string>
+    <string name="hebrew">袉胁褉懈褌</string>
+    <string name="greek">袚褉械褑褜泻邪</string>
+    <string name="hungarian">校谐芯褉褋褜泻邪</string>
+    <string name="romanian">袪褍屑褍薪褋褜泻邪</string>
+    <string name="bip_prefs_shortcuts">携褉谢懈泻懈</string>
+    <string name="pref_theme_system">携泻 褍 褋懈褋褌械屑褨</string>
+    <string name="controlcenter_set_alias">袙褋褌邪薪芯胁懈褌懈 锌褋械胁写芯薪褨屑</string>
+    <string name="devicetype_sg2">Lemfo SG2</string>
+    <string name="devicetype_amazfit_trex">Amazfit T-Rex</string>
+    <string name="devicetype_miband5">Mi Band 5</string>
+    <string name="swolfIndex">SWOLF</string>
+    <string name="swimStyle">小褌懈谢褜 锌谢邪胁邪薪薪褟</string>
+    <string name="pref_title_weather_summary">袙懈泻芯褉懈褋褌芯胁褍褦褌褜褋褟 写谢褟 锌芯褋褌邪褔邪谢褜薪懈泻邪 锌芯谐芯写懈 LineageOS, 褨薪褕褨 胁械褉褋褨褩 Android 屑邪褞褌褜 胁懈泻芯褉懈褋褌芯胁褍胁邪褌懈 蟹邪褋褌芯褋褍薪泻懈 褟泻-芯褌 \"Weather notification\". 袛芯写邪褌泻芯胁褍 褨薪褎芯褉屑邪褑褨褞 屑芯卸薪邪 蟹薪邪泄褌懈 褍 胁褨泻褨 Gadgetbridge.</string>
+    <string name="prefs_events_forwarding_summary">袙懈泻芯褉懈褋褌芯胁褍胁邪褌懈 锌芯写褨褩 锌褉懈褋褌褉芯褞 写谢褟 蟹邪锌褍褋泻褍 写褨泄 褨 褌褉邪薪褋谢褟褑褨泄 Android</string>
+    <string name="prefs_events_forwarding_broadcast_title">孝褉邪薪褋谢褟褑褨泄薪褨 锌芯胁褨写芯屑谢械薪薪褟</string>
+    <string name="medley">袟屑褨褕邪薪懈泄</string>
+    <string name="backstroke">袧邪 褋锌懈薪褨</string>
+    <string name="freestyle">肖褉懈褋褌邪泄谢</string>
+    <string name="breaststroke">袘褉邪褋</string>
+    <string name="activity_filter_to_placeholder">褋褜芯谐芯写薪褨</string>
+    <string name="activity_filter_from_placeholder">胁褨写写邪谢械薪械 屑懈薪褍谢械</string>
+    <string name="activity_summaries_all_devices">袙褋褨 锌褉懈褋褌褉芯褩</string>
+    <string name="sports_activity_quick_filter_select">袩械褉褨芯写 褔邪褋褍</string>
+    <string name="sports_activity_quick_filter_30days">30 写薪褨胁</string>
+    <string name="sports_activity_quick_filter_7days">7 写薪褨胁</string>
+    <string name="sports_activity_quick_filter_last_month">袦懈薪褍谢懈泄 屑褨褋褟褑褜</string>
+    <string name="sports_activity_quick_filter_this_month">袩芯褌芯褔薪懈泄 屑褨褋褟褑褜</string>
+    <string name="sports_activity_quick_filter_last_week">袦懈薪褍谢懈泄 褌懈卸写械薪褜</string>
+    <string name="sports_activity_quick_filter_this_week">袩芯褌芯褔薪懈泄 褌懈卸写械薪褜</string>
+    <string name="activity_summaries_all_activities">校褋褨 邪泻褌懈胁薪芯褋褌褨</string>
+    <string name="activity_summaries_statistics">小褌邪褌懈褋褌懈泻邪</string>
+    <string name="no">袧褨</string>
+    <string name="yes">孝邪泻</string>
+    <string name="activity_filter_individual_items">袉薪写懈胁褨写褍邪谢褜薪芯 蟹邪蟹薪邪褔械薪褨 械谢械屑械薪褌懈</string>
+    <string name="addto_filter">袛芯写邪褌懈 写芯 褎褨谢褜褌褉邪</string>
+    <string name="activity_filter_apply_filter">袟邪褋褌芯褋褍胁邪褌懈 褎褨谢褜褌褉</string>
+    <string name="activity_filter_name_contains">袦褨褌泻邪</string>
+    <string name="activity_filter_filter_title">肖褨谢褜褌褉</string>
+    <string name="activity_filter_reset_filter">小泻懈薪褍褌懈 褎褨谢褜褌褉</string>
+    <string name="activity_filter_date_to">袛芯</string>
+    <string name="activity_filter_date_from">袟</string>
+    <string name="pref_header_statistics">小褌邪褌懈褋褌懈泻邪 褋锌芯褉褌懈胁薪芯褩 写褨褟谢褜薪芯褋褌褨</string>
+    <string name="pref_header_filter">肖褨谢褜褌褉 褋锌芯褉褌懈胁薪芯褩 写褨褟谢褜薪芯褋褌褨</string>
+    <string name="prefs_events_forwarding_action_title">袙懈泻芯薪邪褌懈 写褨褞</string>
+    <string name="prefs_events_forwarding_startnonwear">袣芯谢懈 薪械 芯写褟谐薪械薪芯</string>
+    <string name="prefs_events_forwarding_wokeup">袩褨褋谢褟 锌褉芯斜褍写卸械薪薪褟</string>
+    <string name="prefs_events_forwarding_fellsleep">袩褨褋谢褟 蟹邪褋懈薪邪薪薪褟</string>
+    <string name="prefs_events_forwarding_title">袛褨褩 蟹 锌褉懈褋褌褉芯褦屑</string>
+    <string name="activity_detail_show_gps_label">袩芯泻邪蟹邪褌懈 GPS 褌褉械泻</string>
+    <string name="activity_detail_duration_label">孝褉懈胁邪谢褨褋褌褜</string>
+    <string name="activity_detail_end_label">袣褨薪械褑褜</string>
+    <string name="activity_detail_start_label">袩芯褔邪褌芯泻</string>
+    <string name="Steps">袣褉芯泻懈</string>
+    <string name="Activity">袗泻褌懈胁薪褨褋褌褜</string>
+    <string name="Speed">楔胁懈写泻褨褋褌褜</string>
+    <string name="Elevation">袩褨写胁懈褖械薪薪褟</string>
+    <string name="Distance">袙褨写褋褌邪薪褜</string>
+    <string name="Swimming">袩谢邪胁邪薪薪褟</string>
+    <string name="Strokes">袦邪褏懈</string>
+    <string name="km">泻屑</string>
+    <string name="bpm">褍写/褏胁</string>
+    <string name="minutes_km">褏胁/泻屑</string>
+    <string name="seconds_m">褋械泻/屑</string>
+    <string name="seconds_km">褋械泻/泻屑</string>
+    <string name="calories_unit">泻泻邪谢</string>
+    <string name="laps_unit">泻褨谢</string>
+    <string name="swim_style">褋褌懈谢褜 锌谢邪胁邪薪薪褟</string>
+    <string name="swolf_index">swolf 褨薪写械泻褋</string>
+    <string name="seconds">褋械泻</string>
+    <string name="strokes_second">屑邪褏/褋</string>
+    <string name="strokes_unit">屑邪褏</string>
+    <string name="km_h">泻屑/谐芯写</string>
+    <string name="meters_second">屑/褋</string>
+    <string name="steps_unit">泻褉芯泻懈</string>
+    <string name="cm">褋屑</string>
+    <string name="meters">屑</string>
+    <string name="averageSpeed">小械褉械写薪褟 褕胁懈写泻褨褋褌褜</string>
+    <string name="baseAltitude">袘邪蟹芯胁邪 胁懈褋芯褌邪</string>
+    <string name="flatSeconds">袪褨胁薪芯</string>
+    <string name="ascentSeconds">袟褉芯褋褌邪薪薪褟</string>
+    <string name="laps">袣芯谢邪</string>
+    <string name="averageLapPace">小械褉械写薪褨泄 褌械屑锌 泻芯谢邪</string>
+    <string name="averageStrokesPerSecond">小械褉械写薪褟 泻褨谢褜泻褨褋褌褜 屑邪褏褨胁</string>
+    <string name="averageStrokeDistance">小械褉械写薪褟 胁褨写褋褌邪薪褜 屑邪褏褍</string>
+    <string name="averageStride">小械褉械写薪褨泄 泻褉芯泻</string>
+    <string name="averageKMPaceSeconds">孝械屑锌</string>
+    <string name="averageHR">袩褍谢褜褋</string>
+    <string name="totalStride">袟胁懈褔邪泄薪懈泄 泻褉芯泻</string>
+    <string name="maxPace">袧邪泄褕胁懈写褕懈泄 褌械屑锌</string>
+    <string name="minPace">袧邪泄锌芯胁褨谢褜薪褨褕懈泄 褌械屑锌</string>
+    <string name="maxSpeed">袦邪泻褋懈屑邪谢褜薪邪</string>
+    <string name="caloriesBurnt">袣邪谢芯褉褨褩</string>
+    <string name="activeSeconds">袗泻褌懈胁薪褨</string>
+    <string name="steps">袣褉芯泻懈</string>
+    <string name="minAltitude">袦褨薪褨屑邪谢褜薪邪</string>
+    <string name="maxAltitude">袦邪泻褋懈屑邪谢褜薪邪</string>
+    <string name="descentMeters">小锌褍褋泻</string>
+    <string name="ascentMeters">袩褨写泄芯屑</string>
+    <string name="distanceMeters">袙褨写褋褌邪薪褜</string>
+    <string name="error_location_enabled_mandatory">袛谢褟 褋泻邪薪褍胁邪薪薪褟 锌褉懈褋褌褉芯褩胁 锌芯褌褉褨斜薪械 胁懈蟹薪邪褔械薪薪褟 屑褨褋褑械锌械褉械斜褍胁邪薪薪褟</string>
+    <string name="ignore_bonded_devices_description">校胁褨屑泻薪械薪薪褟 褑褨褦褩 芯锌褑褨褩 褨谐薪芯褉褍褦 锌褉懈褋褌褉芯褩, 褟泻褨 胁卸械 斜褍谢懈 锌芯胁\'褟蟹邪薪褨 锌械褉械写 褋泻邪薪褍胁邪薪薪褟屑</string>
+    <string name="ignore_bonded_devices">袉谐薪芯褉褍胁邪褌懈 锌芯胁\'褟蟹邪薪褨 锌褉懈褋褌褉芯褩</string>
+    <string name="error_retrieving_devices_database">袩芯屑懈谢泻邪 芯褌褉懈屑邪薪薪褟 锌褉懈褋褌褉芯褩胁 蟹 斜邪蟹懈 写邪薪懈褏</string>
+    <string name="error_setting_alias">袩芯屑懈谢泻邪 胁褋褌邪薪芯胁谢械薪薪褟 锌褋械胁写芯薪褨屑邪:</string>
+    <string name="error_exporting_device_preferences">袩芯屑懈谢泻邪 械泻褋锌芯褉褌褍胁邪薪薪褟 褋锌械褑懈褎褨褔薪懈褏 薪邪谢邪褕褌褍胁邪薪褜 写谢褟 锌褉懈褋褌褉芯褞</string>
+    <string name="pref_check_permission_status_summary">袩械褉械胁褨褉褟褌懈 褌邪 蟹邪锌懈褌褍胁邪褌懈 胁褨写褋褍褌薪褨 写芯蟹胁芯谢懈, 薪邪胁褨褌褜 褟泻褖芯 胁芯薪懈 蟹邪褉邪蟹 薪械 锌芯褌褉褨斜薪褨. 袙懈屑泻薪褨褌褜 褑械 谢懈褕械 褟泻褖芯 胁邪褕 锌褉懈褋褌褉褨泄 薪械 锌褨写褌褉懈屑褍褦 卸芯写薪褍 蟹 褑懈褏 褎褍薪泻褑褨泄. 袙褨写褋褍褌薪褨褋褌褜 写芯蟹胁芯谢褍 屑芯卸械 锌褉懈蟹胁械褋褌懈 写芯 锌褉芯斜谢械屑!</string>
+    <string name="pref_check_permission_status">袩械褉械胁褨褉褟褌懈 褋褌邪薪 写芯蟹胁芯谢褨胁</string>
+    <string name="error_background_service_reason">袧械 胁写邪谢芯褋褟 蟹邪锌褍褋褌懈褌懈 褎芯薪芯胁褍 褋谢褍卸斜褍. 袩芯屑懈谢泻邪:</string>
+    <string name="device_requires_key">袩袨孝袪袉袘袝袧 袣袥挟效, 袟袗孝袠小袧袉孝鞋 袛袥携 袙袙袝袛袝袧袧携</string>
+    <string name="device_is_currently_bonded">袙袞袝 袩袨袙\'携袟袗袧袨</string>
+    <string name="error_background_service_reason_truncated">袧械 胁写邪谢芯褋褟 蟹邪锌褍褋褌懈褌懈 褎芯薪芯胁褍 褋谢褍卸斜褍 褌芯屑褍 褖芯鈥�</string>
+    <string name="error_background_service">袧械 胁写邪谢芯褋褜 蟹邪锌褍褋褌懈褌懈 褎芯薪芯胁褍 褋谢褍卸斜褍</string>
+    <string name="companiondevice_pairing_details">袙屑懈泻邪褦 锌褨写褌褉懈屑泻褍 薪芯胁芯谐芯 API CompanionDevice (锌褉邪褑褞褦 薪邪 Android 8 褌邪 薪芯胁褨褕械), 褖芯 屑邪褦 锌褨写胁懈褖懈褌懈 薪邪写褨泄薪褨褋褌褜, 泻芯谢懈 褋谢褍卸斜邪 屑邪褦 斜褍褌懈 锌械褉械蟹邪锌褍褖械薪邪 褍 褎芯薪褨, 锌芯褌褉械斜褍褦 锌芯胁褌芯褉薪芯谐芯 蟹\'褦写薪邪薪薪褟 胁懈泻芯褉懈褋褌芯胁褍褞褔懈 Gadgetbridge</string>
+    <string name="companiondevice_pairing">小褌胁芯褉械薪薪褟 锌邪褉懈 蟹 CompanionDevice</string>
+    <string name="require_location_provider">袙懈蟹薪邪褔械薪薪褟 屑褨褋褑械锌械褉械斜褍胁邪薪薪褟 屑邪褦 斜褍褌懈 褍胁褨屑泻薪械薪芯</string>
+    <string name="error_version_check_extreme_caution">校袙袗袚袗: 袩芯屑懈谢泻邪 锌褉懈 锌械褉械胁褨褉褑褨 褨薪褎芯褉屑邪褑褨褩 锌褉芯 胁械褉褋褨褞! 袙邪屑 薪械 褋谢褨写 锌褉芯写芯胁卸褍胁邪褌懈! 袟薪邪泄写械薪芯 薪邪蟹胁褍 胁械褉褋褨褩 \"%s\"</string>
+    <string name="permission_granting_mandatory">校褋褨 褑褨 写芯蟹胁芯谢懈 锌芯褌褉褨斜薪褨, 邪 褩褏薪褟 胁褨写褋褍褌薪褨褋褌褜 屑芯卸械 锌褉懈蟹胁械褋褌懈 写芯 薪械褋褌邪斜褨谢褜薪芯褩 褉芯斜芯褌懈</string>
+    <string name="about_links">袩芯褋懈谢邪薪薪褟</string>
+    <string name="about_additional_contributions">袙械谢懈泻邪 锌芯写褟泻邪 胁褋褨屑 薪械胁泻邪蟹邪薪懈屑 褍褔邪褋薪懈泻邪屑 蟹邪 胁薪械褋芯泻 褍 泻芯写, 锌械褉械泻谢邪写懈, 锌褨写褌褉懈屑泻褍, 褨写械褩, 屑芯褌懈胁邪褑褨褞, 锌芯胁褨写芯屑谢械薪薪褟 锌褉芯 锌芯屑懈谢泻懈 褌邪 谐褉芯褕褨... 鉁�</string>
+    <string name="about_additional_device_support">袩褨写褌褉懈屑泻邪 写芯写邪褌泻芯胁懈褏 锌褉懈褋褌褉芯褩胁</string>
+    <string name="about_contributors">校褔邪褋薪懈泻懈</string>
+    <string name="about_core_team_title">袨褋薪芯胁薪邪 泻芯屑邪薪写邪 (褍 锌芯褉褟写泻褍 锌械褉褕芯谐芯 胁薪械褋泻褍 褍 泻芯写)</string>
+    <string name="about_description_generic">袧械蟹邪谢械卸薪邪 胁褨写 褏屑邪褉懈, 胁褨谢褜薪邪 蟹 谢褨褑械薪蟹褨褦褞 Copyleft 蟹邪屑褨薪邪 蟹邪泻褉懈褌懈褏 蟹邪褋褌芯褋褍薪泻褨胁 胁褨写 褨薪褕懈褏 胁懈褉芯斜薪懈泻褨胁 写谢褟 锌褉懈褋褌褉芯褩胁 Android.</string>
+    <string name="about_activity_title_generic">袩褉芯 Gadgetbridge</string>
+    <string name="about_title">袩褉芯 蟹邪褋褌芯褋褍薪芯泻</string>
+    <string name="hr_widget_last_notification">袨褋褌邪薪薪褦 褋锌芯胁褨褖械薪薪褟</string>
+    <string name="menuitem_worldclock">小胁褨褌芯胁懈泄 褔邪褋</string>
+    <string name="menuitem_stress">袧邪锌褉褍卸械薪褨褋褌褜</string>
+    <string name="menuitem_cycles">袙褨写褋褌械卸械薪薪褟 褑懈泻谢褍</string>
+    <string name="menuitem_breathing">袛懈褏邪薪薪褟</string>
+    <string name="devicetype_pinetime_jf">PineTime (袦褨泻褉芯锌褉芯谐褉邪屑邪 JF)</string>
+    <string name="devicetype_tlw64">TLW64</string>
+    <string name="activity_summary_yesterday">袙褔芯褉邪</string>
+    <string name="activity_summary_today">小褜芯谐芯写薪褨</string>
+    <string name="activity_summary_edit_name_title">袪械写邪谐褍胁邪褌懈 薪邪蟹胁褍</string>
+    <string name="activity_summary_detail">袩芯写褉芯斜懈褑褨 锌褉芯 写褨褟谢褜薪芯褋褌褜</string>
+    <string name="activity_type_badminton">袘邪写屑褨薪褌芯薪</string>
+    <string name="activity_type_pingpong">袩褨薪谐-锌芯薪谐</string>
+    <string name="activity_type_basketball">袘邪褋泻械褌斜芯谢</string>
+    <string name="activity_type_cricket">袣褉懈泻械褌</string>
+    <string name="activity_type_rowing_machine">袙械褋谢褍胁邪谢褜薪懈泄 褌褉械薪邪卸械褉</string>
+    <string name="activity_type_soccer">肖褍褌斜芯谢</string>
+    <string name="activity_type_yoga">袡芯谐邪</string>
+    <string name="activity_type_jump_roping">小泻邪泻邪谢泻邪</string>
+    <string name="activity_type_elliptical_trainer">袝谢褨锌褌懈褔薪懈泄 褌褉械薪邪卸械褉</string>
+    <string name="activity_type_indoor_cycling">袙械谢芯褌褉械薪邪卸械褉</string>
+    <string name="activity_type_swimming_openwater">袩谢邪胁邪薪薪褟 (褍 胁褨写泻褉懈褌懈褏 胁芯写芯泄屑邪褏)</string>
+    <string name="bip_prefs_shotcuts_summary">袨斜械褉褨褌褜 褟褉谢懈泻懈 写谢褟 械泻褉邪薪邪 斜褉邪褋谢械褌邪</string>
+    <string name="fw_upgrade_notice_miband5">袙懈 蟹斜懈褉邪褦褌械褋褟 胁褋褌邪薪芯胁懈褌懈 屑褨泻褉芯锌褉芯谐褉邪屑褍 %s 薪邪 胁邪褕 Mi Band 5.
+\n
+\n小锌芯褔邪褌泻褍 胁褋褌邪薪芯胁褨褌褜 褎邪泄谢 .fw, 锌芯褌褨屑 .res. 袙邪褕 谐芯写懈薪薪懈泻 锌械褉械蟹邪胁邪薪褌邪卸懈褌褜褋褟 锌褨褋谢褟 胁褋褌邪薪芯胁谢械薪薪褟 褎邪泄谢褍 .fw.
+\n
+\n袩褉懈屑褨褌泻邪: 胁邪屑 薪械 锌芯褌褉褨斜薪芯 胁褋褌邪薪芯胁谢褞胁邪褌懈 褎邪泄谢 .res, 褟泻褖芯 胁褨薪 褌邪泻懈泄 褋邪屑懈泄, 褟泻 褉邪薪褨褕械 胁褋褌邪薪芯胁谢械薪懈泄.
+\n
+\n袩袪袨袛袨袙袞校袡孝袝 袧袗 袙袥袗小袧袠袡 袪袠袟袠袣!</string>
+    <string name="fw_upgrade_notice_amazfit_trex">袙懈 蟹斜懈褉邪褦褌械褋褟 胁褋褌邪薪芯胁懈褌懈 屑褨泻褉芯锌褉芯谐褉邪屑褍 %s 薪邪 胁邪褕 Amazfit T-Rex.
+\n
+\n小锌芯褔邪褌泻褍 胁褋褌邪薪芯胁褨褌褜 褎邪泄谢 .fw, 锌芯褌褨屑 .res, 邪 胁 泻褨薪褑褨 .gps. 袙邪褕 谐芯写懈薪薪懈泻 锌械褉械蟹邪胁邪薪褌邪卸懈褌褜褋褟 锌褨褋谢褟 胁褋褌邪薪芯胁谢械薪薪褟 褎邪泄谢褍 .fw.
+\n
+\n袩褉懈屑褨褌泻邪: 胁邪屑 薪械 锌芯褌褉褨斜薪芯 胁褋褌邪薪芯胁谢褞胁邪褌懈 褎邪泄谢懈 .res 褌邪 .gps, 褟泻褖芯 胁芯薪懈 褌邪泻褨 褋邪屑褨, 褖芯 斜褍谢懈 胁褋褌邪薪芯胁谢械薪褨 褉邪薪褨褕械.
+\n
+\n袩袪袨袛袨袙袞校袡孝袝 袧袗 袙袥袗小袧袠袡 袪袠袟袠袣!</string>
+    <string name="sonyswr12_settings_title">袧邪谢邪褕褌褍胁邪薪薪褟 Sony SWR12</string>
+    <string name="sonyswr12_settings_low_vibration">小谢邪斜泻邪 胁褨斜褉邪褑褨褟 褍胁褨屑泻薪械薪邪</string>
+    <string name="sonyswr12_settings_stamina">袝薪械褉谐芯芯褖邪写薪懈泄 褉械卸懈屑 褍胁褨屑泻薪械薪芯</string>
+    <string name="sonyswr12_settings_alarm_interval">袉薪褌械褉胁邪谢 褉芯蟹褍屑薪芯谐芯 斜褍写懈谢褜薪懈泻邪 褍 褏胁懈谢懈薪邪褏</string>
+    <string name="sonyswr12_settings_low_vibration_summary">校胁褨屑泻薪褍褌懈 薪懈蟹褜泻褍 褨薪褌械薪褋懈胁薪褨褋褌褜 胁褨斜褉邪褑褨褩 薪邪 斜褉邪褋谢械褌褨</string>
+    <string name="sonyswr12_settings_stamina_summary">袝薪械褉谐芯芯褖邪写薪懈泄 褉械卸懈屑 胁懈屑懈泻邪褦 锌械褉褨芯写懈褔薪械 邪胁褌芯胁懈屑褨褉褞胁邪薪薪褟 锌褍谢褜褋褍, 锌芯写芯胁卸褍褞褔懈 褔邪褋 褉芯斜芯褌懈</string>
+    <string name="sonyswr12_settings_alarm_interval_summary">袉薪褌械褉胁邪谢 褉芯蟹褍屑薪芯谐芯 斜褍写懈谢褜薪懈泻邪 鈥� 褑械 褨薪褌械褉胁邪谢 锌械褉械写 胁褋褌邪薪芯胁谢械薪懈屑 斜褍写懈谢褜薪懈泻芯屑. 袩褉芯褌褟谐芯屑 褑褜芯谐芯 褨薪褌械褉胁邪谢褍 锌褉懈褋褌褉褨泄 薪邪屑邪谐邪褦褌褜褋褟 胁懈褟胁懈褌懈 薪邪泄谢械谐褕褍 褎邪蟹褍 褋薪褍 写谢褟 锌褉芯斜褍写卸械薪薪褟 泻芯褉懈褋褌褍胁邪褔邪</string>
+    <string name="prefs_key_vibration">袙褨斜褉邪褑褨褟 泻谢邪胁褨褕</string>
+    <string name="activity_db_management_clean_export_directory_text">袝泻褋锌芯褉褌芯胁邪薪褨 褎邪泄谢懈 胁 褌械褑褨 写谢褟 械泻褋锌芯褉褌褍/褨屑锌芯褉褌褍 写芯褋褌褍锌薪褨 写谢褟 斜褍写褜-褟泻芯谐芯 蟹邪褋褌芯褋褍薪泻褍 薪邪 胁邪褕芯屑褍 锌褉懈褋褌褉芯褩. 袦芯卸谢懈胁芯 胁懈 蟹邪褏芯褔械褌械 胁懈写邪谢懈褌懈 褑褨 褎邪泄谢懈 锌褨褋谢褟 褋懈薪褏褉芯薪褨蟹邪褑褨褩 褔懈 褋褌胁芯褉械薪薪褟 褉械蟹械褉胁薪芯褩 泻芯锌褨褩. 袩械褉械写 胁懈写邪谢械薪薪褟屑 锌械褉械泻芯薪邪泄褌械褋褟, 褖芯 胁懈 褋褌胁芯褉懈谢懈 褉械蟹械褉胁薪褍 泻芯锌褨褞. 肖邪泄谢懈 GPX, 锌褨写褌械泻懈 褌邪 邪胁褌芯械泻褋锌芯褉褌芯胁邪薪懈泄 褎邪泄谢 斜邪蟹懈 写邪薪懈褏 (褟泻褖芯 胁褨薪 褦) 薪械 斜褍写褍褌褜 胁懈写邪谢械薪褨. 楔谢褟褏 写芯 褌械泻懈 写谢褟 械泻褋锌芯褉褌褍/褨屑锌芯褉褌褍:</string>
+    <string name="activity_error_no_app_for_png">些芯斜 锌芯写褨谢懈褌懈褋褜 褑懈屑 蟹薪褨屑泻芯屑 械泻褉邪薪邪, 胁褋褌邪薪芯胁褨褌褜 蟹邪褋褌芯褋褍薪芯泻, 褟泻懈泄 屑芯卸械 芯斜褉芯斜谢褟褌懈 褎邪泄谢懈 蟹芯斜褉邪卸械薪褜.</string>
+    <string name="prefs_fake_ring_duration">袧械褋锌褉邪胁卸薪褨泄 斜械蟹锌械褉械褉胁薪懈泄 写蟹胁褨薪芯泻</string>
+    <string name="prefs_autoremove_message">袗胁褌芯屑邪褌懈褔薪芯 胁懈写邪谢褟褌懈 小袦小-锌芯胁褨写芯屑谢械薪薪褟</string>
+    <string name="fossil_hr_commute_processing">袨斜褉芯斜泻邪 蟹邪锌懈褌褍鈥�</string>
+    <string name="fossil_hr_unavailable_unauthed">袧械写芯褋褌褍锌薪芯 斜械蟹 邪褍褌械薪褌懈褎褨泻邪褑褨褩</string>
+    <string name="fossil_hr_synced_activity_data">小懈薪褏褉芯薪褨蟹芯胁邪薪褨 写邪薪褨 邪泻褌懈胁薪芯褋褌械泄</string>
+    <string name="fossil_hr_auth_failed">袗胁褌械薪褌懈褎褨泻邪褑褨褞 薪械 锌褉芯泄写械薪芯, 褎褍薪泻褑褨芯薪邪谢褜薪褨褋褌褜 芯斜屑械卸械薪芯</string>
+    <string name="qhybrid_title_apps">袟邪褋褌芯褋褍薪泻懈</string>
+    <string name="qhybrid_title_background_image">袟芯斜褉邪卸械薪薪褟 褌谢邪</string>
+    <string name="qhybrid_title_file_management">袣械褉褍胁邪薪薪褟 褎邪泄谢邪屑懈</string>
+    <string name="fossil_hr_warning_firmware_too_new">袛械褟泻褨 褎褍薪泻褑褨褩 胁懈屑泻薪械薪芯, 芯褋泻褨谢褜泻懈 屑褨泻褉芯锌褉芯谐褉邪屑邪 谐芯写懈薪薪懈泻邪 蟹邪薪邪写褌芯 薪芯胁邪</string>
+    <string name="qhybrid_title_apps_management">袣械褉褍胁邪薪薪褟 蟹邪褋褌芯褋褍薪泻邪屑懈</string>
+    <string name="qhybrid_title_calibration">袣邪谢褨斜褉褍胁邪薪薪褟</string>
+    <string name="prefs_operating_sounds">袪芯斜芯褔褨 蟹胁褍泻懈</string>
+    <string name="prefs_autolight">袗胁褌芯屑邪褌懈褔薪械 褋胁褨褌谢芯</string>
+    <string name="qhybrid_calibration_align_hint">袙懈泻芯褉懈褋褌芯胁褍泄褌械 泻薪芯锌泻懈 胁薪懈蟹褍, 褖芯斜 胁懈褉褨胁薪褟褌懈 褋褌褉褨谢泻懈 薪邪 12:00.</string>
+    <string name="devicetype_nut_mini">Nut mini</string>
+    <string name="gps_track">GPS-褌褉械泻</string>
+    <string name="mi">屑懈谢褜</string>
+    <string name="minutes_mi">褏胁/屑懈谢褞</string>
+    <string name="mi_h">屑懈谢褜/谐芯写</string>
+    <string name="ft">褎褍褌懈</string>
+    <string name="descentSeconds">小锌邪写邪薪薪褟</string>
+    <string name="about_version">袙械褉褋褨褟 %s</string>
+    <string name="pref_title_lower_button_function_double">袩芯写胁褨泄薪械 薪邪褌懈褋薪械薪薪褟 薪懈卸薪褜芯褩 泻薪芯锌泻懈</string>
+    <string name="pref_title_middle_button_function_double">袩芯写胁褨泄薪械 薪邪褌懈褋薪械薪薪褟 褋械褉械写薪褜芯褩 泻薪芯锌泻懈</string>
+    <string name="pref_title_upper_button_function_double">袩芯写胁褨泄薪械 薪邪褌懈褋薪械薪薪褟 胁械褉褏薪褜芯褩 泻薪芯锌泻懈</string>
+    <string name="pref_title_lower_button_function_long">孝褉懈胁邪谢械 薪邪褌懈褋薪械薪薪褟 薪懈卸薪褜芯褩 泻薪芯锌泻懈</string>
+    <string name="pref_title_middle_button_function_long">孝褉懈胁邪谢械 薪邪褌懈褋薪械薪薪褟 褋械褉械写薪褜芯褩 泻薪芯锌泻懈</string>
+    <string name="pref_title_upper_button_function_long">孝褉懈胁邪谢械 薪邪褌懈褋薪械薪薪褟 胁械褉褏薪褜芯褩 泻薪芯锌泻懈</string>
+    <string name="pref_title_lower_button_function_short">袣芯褉芯褌泻械 薪邪褌懈褋薪械薪薪褟 薪懈卸薪褜芯褩 泻薪芯锌泻懈</string>
+    <string name="pref_title_middle_button_function_short">袣芯褉芯褌泻械 薪邪褌懈褋薪械薪薪褟 褋械褉械写薪褜芯褩 泻薪芯锌泻懈</string>
+    <string name="pref_title_upper_button_function_short">袣芯褉芯褌泻械 薪邪褌懈褋薪械薪薪褟 胁械褉褏薪褜芯褩 泻薪芯锌泻懈</string>
+    <string name="widget_settings_select_device_title">袨斜械褉褨褌褜 锌褉懈褋褌褉褨泄</string>
+    <string name="menuitem_temperature">孝械屑锌械褉邪褌褍褉邪</string>
+    <string name="menuitem_widgets">袙褨写卸械褌懈</string>
+    <string name="menuitem_events">袩芯写褨褩</string>
+    <string name="menuitem_pomodoro">袙褨写褋褌械卸褍胁邪褔 Pomodoro</string>
+    <string name="menuitem_sleep">小芯薪</string>
+    <string name="menuitem_goal">袦械褌邪 写褨褟谢褜薪芯褋褌褨</string>
+    <string name="menuitem_stopwatch">小械泻褍薪写芯屑褨褉</string>
+    <string name="menuitem_dnd">袧械 褌褍褉斜褍胁邪褌懈</string>
+    <string name="menuitem_alexa">Alexa</string>
+    <string name="menuitem_takephoto">袙褨写写邪谢械薪械 泻械褉褍胁邪薪薪褟 泻邪屑械褉芯褞</string>
+    <string name="menuitem_mutephone">袙懈屑泻薪褍褌懈 蟹胁褍泻 褌械谢械褎芯薪褍</string>
+    <string name="menuitem_findphone">袟薪邪泄褌懈 褌械谢械褎芯薪</string>
+    <string name="menuitem_spo2">SpO2</string>
+    <string name="devicetype_lefun">Lefun</string>
+    <string name="devicetype_amazfit_vergel">Amazfit Verge Lite</string>
+    <string name="devicetype_waspos">Wasp-os</string>
+    <string name="devicetype_casiogbx100">Casio GBX-100</string>
+    <string name="devicetype_um25">UM-25</string>
+    <string name="devicetype_zepp_e">Zepp E</string>
+    <string name="devicetype_amazfit_x">Amazfit X</string>
+    <string name="devicetype_amazfit_gts2e">Amazfit GTS 2e</string>
+    <string name="devicetype_amazfit_gts2_mini">Amazfit GTS 2 Mini</string>
+    <string name="devicetype_amazfit_gts2">Amazfit GTS 2</string>
+    <string name="devicetype_amazfit_gtr2e">Amazfit GTR 2e</string>
+    <string name="devicetype_amazfit_gtr2">Amazfit GTR 2</string>
+    <string name="devicetype_amazfit_bipupro">Amazfit Bip U Pro</string>
+    <string name="devicetype_amazfit_bipu">Amazfit Bip U</string>
+    <string name="devicetype_amazfit_bips_lite">Amazfit Bip S Lite</string>
+    <string name="devicetype_amazfit_neo">Amazfit Neo</string>
+    <string name="devicetype_amazfit_band5">Amazfit Band 5</string>
+    <string name="activity_type_strength_training">小懈谢芯胁械 褌褉械薪褍胁邪薪薪褟</string>
+    <string name="dbmanagementactivity_export_finished">袙懈写邪谢械薪薪褟 蟹邪胁械褉褕械薪芯</string>
+    <string name="activity_DB_clean_export_directory_warning_message">袛褨泄褋薪芯 胁懈写邪谢懈褌懈 褎邪泄谢懈 蟹 褌械泻懈 写谢褟 械泻褋锌芯褉褌褍/褨屑锌芯褉褌褍?</string>
+    <string name="activity_DB_clean_export_directory_warning_title">袙懈写邪谢懈褌懈 褎邪泄谢懈 蟹 褌械泻懈 写谢褟 械泻褋锌芯褉褌褍/褨屑锌芯褉褌褍?</string>
+    <string name="activity_db_management_clean_export_directory_label">袙懈写邪谢懈褌懈 褎邪泄谢懈 蟹 褌械泻懈 写谢褟 械泻褋锌芯褉褌褍/褨屑锌芯褉褌褍</string>
+    <string name="activity_DB_ShowContentButton">袩芯泻邪蟹邪褌懈 胁屑褨褋褌 褌械泻懈 写谢褟 械泻褋锌芯褉褌褍/褨屑锌芯褉褌褍</string>
+    <string name="activity_data_management_directory_content_title">袝泻褋锌芯褉褌/褨屑锌芯褉褌 胁屑褨褋褌褍 褌械泻懈</string>
+    <string name="dbmanagementactivity_export_confirmation">袛褨泄褋薪芯 械泻褋锌芯褉褌褍胁邪褌懈 写邪薪褨? 袩芯锌械褉械写薪褜芯 械泻褋锌芯褉褌芯胁邪薪褨 写邪薪褨 (褟泻褖芯 胁芯薪懈 褦) 褌邪 薪邪谢邪褕褌褍胁邪薪薪褟 斜褍写褍褌褜 锌械褉械蟹邪锌懈褋邪薪褨.</string>
+    <string name="dbmanagementactivity_error_cleaning_export_directory">袩芯屑懈谢泻邪 胁懈写邪谢械薪薪褟 褎邪泄谢褨胁 蟹 褌械泻懈 写谢褟 械泻褋锌芯褉褌褍 %1$s</string>
+    <string name="dbmanagementactivity_export_data_title">袝泻褋锌芯褉褌褍胁邪褌懈 写邪薪褨\?</string>
+    <string name="charts_legend_heartrate_average">小械褉械写薪褨泄 锌褍谢褜褋</string>
+    <string name="activity_prefs_chart_min_session_length">袦褨薪褨屑邪谢褜薪邪 褌褉懈胁邪谢褨褋褌褜 写褨褟谢褜薪芯褋褌褨 (褍 褏胁懈谢懈薪邪褏)</string>
+    <string name="activity_prefs_chart_max_idle_phase_length">孝褉懈胁邪谢褨褋褌褜 锌邪褍蟹懈 写谢褟 胁褨写芯泻褉械屑谢械薪薪褟 写褨褟谢褜薪芯褋褌械泄 (褍 褏胁懈谢懈薪邪褏)</string>
+    <string name="prefs_charts_tabs">袙泻谢邪写泻懈 写褨邪谐褉邪屑</string>
+    <string name="battery_detail_activity_title">袉薪褎芯褉屑邪褑褨褟 锌褉芯 斜邪褌邪褉械褞</string>
+    <string name="activity_prefs_chart_min_steps_per_minute">袦褨薪褨屑邪谢褜薪邪 泻褨谢褜泻褨褋褌褜 泻褉芯泻褨胁 薪邪 褏胁懈谢懈薪褍 写谢褟 胁懈褟胁谢械薪薪褟 写褨褟谢褜薪芯褋褌褨</string>
+    <string name="activity_prefs_chart_min_steps_per_minute_for_run">袦褨薪褨屑邪谢褜薪邪 泻褨谢褜泻褨褋褌褜 泻褉芯泻褨胁 薪邪 褏胁懈谢懈薪褍 写谢褟 胁懈褟胁谢械薪薪褟 斜褨谐褍</string>
+    <string name="activity_prefs_step_length_cm">袛芯胁卸懈薪邪 泻褉芯泻褍 褍 褋邪薪褌懈屑械褌褉邪褏</string>
+    <string name="lefun_prefs_interface_language_title">袦芯胁邪 褨薪褌械褉褎械泄褋褍</string>
+    <string name="lefun_prefs_antilost_summary">袘褉邪褋谢械褌 胁褨斜褉褍胁邪褌懈屑械, 褟泻褖芯 胁邪褕 褌械谢械褎芯薪 胁褨写\'褦写薪邪褦褌褜褋褟 胁褨写 薪褜芯谐芯</string>
+    <string name="lefun_prefs_antilost_title">袩褉芯褌懈胁褌褉邪褌邪</string>
+    <string name="lefun_prefs_hydration_reminder_interval_title">袉薪褌械褉胁邪谢 薪邪谐邪写褍胁邪薪薪褟 锌褉芯 薪械芯斜褏褨写薪褨褋褌褜 胁懈锌懈褌懈 胁芯写懈 (褍 褏胁懈谢懈薪邪褏)</string>
+    <string name="lefun_prefs_hydration_reminder_summary">袘褉邪褋谢械褌 胁褨斜褉褍胁邪褌懈屑械, 褖芯斜 薪邪谐邪写邪褌懈 胁邪屑 锌芯锌懈褌懈 胁芯写懈</string>
+    <string name="lefun_prefs_hydration_reminder_title">袧邪谐邪写褍胁邪薪薪褟 锌褉芯 薪械芯斜褏褨写薪褨褋褌褜 胁懈锌懈褌懈 胁芯写懈</string>
+    <string name="charts_min_max_heartrate_popup">袧邪泄薪懈卸褔懈泄 锌褍谢褜褋: %1$d
+\n袧邪泄胁懈褖懈泄 锌褍谢褜褋: %2$d
+\n袉薪褌械薪褋懈胁薪褨褋褌褜 褉褍褏褍: %3$s</string>
+    <string name="movement_intensity">袉薪褌械薪褋懈胁薪褨褋褌褜 褉褍褏褍</string>
+    <string name="firmware_update_progress">孝褉懈胁邪褦 胁懈胁邪薪褌邪卸械薪薪褟
+\n%1d%% 薪邪 %.2fkbps (褋械褉械写薪褟 %.2fkbps)
+\n效邪褋褌懈薪邪 %1d 蟹 %1d</string>
+    <string name="devicestatus_upload_failed">袧械 胁写邪谢芯褋褟 胁懈胁邪薪褌邪卸懈褌懈</string>
+    <string name="devicestatus_upload_aborted">袙懈胁邪薪褌邪卸褍胁邪薪薪褟 锌械褉械褉胁邪薪芯!</string>
+    <string name="devicestatus_upload_validating">袙懈胁邪薪褌邪卸械薪薪褟 锌械褉械胁褨褉褟褦褌褜褋褟</string>
+    <string name="devicestatus_upload_completed">袙懈胁邪薪褌邪卸械薪薪褟 蟹邪胁械褉褕械薪芯</string>
+    <string name="devicestatus_disconnected">袩褉懈褋褌褉褨泄 胁褨写\'褦写薪邪薪芯!</string>
+    <string name="devicestatus_disconnecting">袩褉懈褋褌褉褨泄 胁褨写\'褦写薪褍褦褌褜褋褟!</string>
+    <string name="devicestatus_upload_started">袙懈胁邪薪褌邪卸械薪薪褟 褉芯蟹锌芯褔邪褌芯</string>
+    <string name="devicestatus_upload_starting">袪芯蟹锌芯褔懈薪邪褦褌褜褋褟 胁懈胁邪薪褌邪卸械薪薪褟</string>
+    <string name="devicestatus_connected">袩褉懈褋褌褉褨泄 锌褨写\'褦写薪邪薪芯</string>
+    <string name="devicestatus_connecting">袩褉懈褋褌褉褨泄 锌褨写\'褦写薪褍褦褌褜褋褟</string>
+    <string name="prefs_sounds_summary">袧邪谢邪褕褌褍泄褌械, 泻芯谢懈 锌褉懈褋褌褉褨泄 锌芯写邪胁邪褌懈屑械 褋懈谐薪邪谢</string>
+    <string name="prefs_sounds">袟胁褍泻懈</string>
+    <string name="show_ongoing_activity">袩芯泻邪蟹邪褌懈 胁褨泻薪芯 蟹 锌芯褌芯褔薪芯褞 写褨褟谢褜薪褨褋褌褞</string>
+    <string name="dialog_hide">小褏芯胁邪褌懈</string>
+    <string name="activity_list_summary_activities">袛褨褟谢褜薪芯褋褌褨</string>
+    <string name="activity_list_summary_intensity">袉薪褌械薪褋懈胁薪褨褋褌褜
+\n袪褍褏褍</string>
+    <string name="activity_list_summary_active_time">孝褉懈胁邪谢褨褋褌褜</string>
+    <string name="activity_list_summary_distance">袙褨写褋褌邪薪褜</string>
+    <string name="activity_list_summary_active_steps">袗泻褌懈胁薪褨 泻褉芯泻懈</string>
+    <string name="charts_activity_list">小锌懈褋芯泻 写褨褟谢褜薪芯褋褌械泄</string>
+    <string name="prefs_charts_tabs_summary">袙褨写芯斜褉邪卸褍胁邪薪薪褨 胁泻谢邪写泻懈 写褨邪谐褉邪屑</string>
+    <string name="chart_get_active_and_synchronize">袙懈泻芯薪邪泄褌械 写械泻褨谢褜泻邪 胁锌褉邪胁 褌邪 褋懈薪褏褉芯薪褨蟹褍泄褌械 锌褉懈褋褌褉褨泄.</string>
+    <string name="chart_no_active_data">袛褨褟谢褜薪芯褋褌褨 薪械 胁懈褟胁谢械薪芯.</string>
+    <string name="discovery_entered_invalid_authkey">袙胁械写械薪懈泄 胁邪屑懈 泻谢褞褔 邪胁褌械薪褌懈褎褨泻邪褑褨褩 薪械写褨泄褋薪懈泄! 袟邪褌懈褋薪褨褌褜, 褖芯斜 褉械写邪谐褍胁邪褌懈.</string>
+    <string name="pref_summary_connected_advertisement">袪芯斜懈褌褜 锌褉懈褋褌褉褨泄 写芯褋褌褍锌薪懈屑 写谢褟 胁懈褟胁谢械薪薪褟 褔械褉械蟹 Bluetooth, 薪邪胁褨褌褜 泻芯谢懈 胁褨薪 锌褨写\'褦写薪邪薪懈泄</string>
+    <string name="pref_title_connected_advertisement">袙懈写懈屑懈泄 泻芯谢懈 锌褨写\'褦写薪邪薪懈泄</string>
+    <string name="fw_upgrade_notice_amazfitneo">袙懈 蟹斜懈褉邪褦褌械褋褟 胁褋褌邪薪芯胁懈褌懈 屑褨泻褉芯锌褉芯谐褉邪屑褍 %s 薪邪 胁邪褕 Amazfit Neo.
+\n
+\n袩褨褋谢褟 胁褋褌邪薪芯胁谢械薪薪褟 褎邪泄谢褍 .fw 胁邪褕 谐芯写懈薪薪懈泻 锌械褉械蟹邪胁邪薪褌邪卸懈褌褜褋褟.
+\n
+\n袩袪袨袛袨袙袞校袡孝袝 袧袗 袙袥袗小袧袠袡 袪袠袟袠袣!</string>
+    <string name="fw_upgrade_notice_amazfitx">袙懈 蟹斜懈褉邪褦褌械褋褟 胁褋褌邪薪芯胁懈褌懈 屑褨泻褉芯锌褉芯谐褉邪屑褍 %s 薪邪 胁邪褕 Amazfit X.
+\n
+\n小锌芯褔邪褌泻褍 胁褋褌邪薪芯胁褨褌褜 褎邪泄谢 .fw, 锌芯褌褨屑 .res. 袙邪褕 谐芯写懈薪薪懈泻 锌械褉械蟹邪胁邪薪褌邪卸懈褌褜褋褟 锌褨褋谢褟 胁褋褌邪薪芯胁谢械薪薪褟 褎邪泄谢褍 .fw.
+\n
+\n袩褉懈屑褨褌泻邪: 胁邪屑 薪械 锌芯褌褉褨斜薪芯 胁褋褌邪薪芯胁谢褞胁邪褌懈 褎邪泄谢 .res, 褟泻褖芯 胁褨薪 褌邪泻懈泄 褋邪屑懈泄, 褟泻 褉邪薪褨褕械 胁褋褌邪薪芯胁谢械薪懈泄.
+\n
+\n袩袪袨袛袨袙袞校袡孝袝 袧袗 袙袥袗小袧袠袡 袪袠袟袠袣!</string>
+    <string name="fw_upgrade_notice_amazfitvergel">袙懈 蟹斜懈褉邪褦褌械褋褟 胁褋褌邪薪芯胁懈褌懈 屑褨泻褉芯锌褉芯谐褉邪屑褍 %s 薪邪 胁邪褕 Amazfit Verge Lite.
+\n
+\n小锌芯褔邪褌泻褍 褌褉械斜邪 胁褋褌邪薪芯胁懈褌懈 褎邪泄谢 .fw, 锌芯褌褨屑 .res, 邪 胁 泻褨薪褑褨 .gps. 袙邪褕 谐芯写懈薪薪懈泻 锌械褉械蟹邪胁邪薪褌邪卸懈褌褜褋褟 锌褨褋谢褟 胁褋褌邪薪芯胁谢械薪薪褟 褎邪泄谢褍 .fw.
+\n
+\n袩褉懈屑褨褌泻邪: 胁邪屑 薪械 锌芯褌褉褨斜薪芯 胁褋褌邪薪芯胁谢褞胁邪褌懈 褎邪泄谢懈 .res 褌邪 .gps, 褟泻褖芯 胁芯薪懈 褌邪泻褨 褋邪屑褨, 褖芯 斜褍谢懈 胁褋褌邪薪芯胁谢械薪褨 褉邪薪褨褕械.
+\n
+\n袩袪袨袛袨袙袞校袡孝袝 袧袗 袙袥袗小袧袠袡 袪袠袟袠袣!</string>
+    <string name="calendar_year">袪褨泻</string>
+    <string name="calendar_six_months">6 屑褨褋褟褑褨胁</string>
+    <string name="calendar_three_months">3 屑褨褋褟褑褨</string>
+    <string name="calendar_month">袦褨褋褟褑褜</string>
+    <string name="calendar_two_weeks">袛胁邪 褌懈卸薪褨</string>
+    <string name="calendar_week">孝懈卸写械薪褜</string>
+    <string name="calendar_day">袛械薪褜</string>
+    <string name="battery_level">袪褨胁械薪褜 蟹邪褉褟写褍</string>
+    <string name="find_lost_device_message">楔褍泻邪褌懈 %1$s\?</string>
+    <string name="fw_upgrade_notice_miband6">袙懈 蟹斜懈褉邪褦褌械褋褟 胁褋褌邪薪芯胁懈褌懈 屑褨泻褉芯锌褉芯谐褉邪屑褍 %s 薪邪 胁邪褕 Mi Band 6.
+\n
+\n小锌芯褔邪褌泻褍 胁褋褌邪薪芯胁褨褌褜 褎邪泄谢 .fw, 锌芯褌褨屑 .res. 袙邪褕 谐芯写懈薪薪懈泻 锌械褉械蟹邪胁邪薪褌邪卸懈褌褜褋褟 锌褨褋谢褟 胁褋褌邪薪芯胁谢械薪薪褟 褎邪泄谢褍 .fw.
+\n
+\n袩褉懈屑褨褌泻邪: 胁邪屑 薪械 锌芯褌褉褨斜薪芯 胁褋褌邪薪芯胁谢褞胁邪褌懈 褎邪泄谢 .res, 褟泻褖芯 胁褨薪 褌邪泻懈泄 褋邪屑懈泄, 褟泻 褉邪薪褨褕械 胁褋褌邪薪芯胁谢械薪懈泄.
+\n
+\n袩袪袨袛袨袙袞校袡孝袝 袧袗 袙袥袗小袧袠袡 袪袠袟袠袣!</string>
+    <string name="fossil_hr_commute_actions_explanation">袛褨褩 薪邪谢邪褕褌芯胁邪薪褨 褌褍褌 斜褍写械 锌芯泻邪蟹邪薪芯 褍 蟹邪褋褌芯褋褍薪泻褍 Commute 薪邪 谐芯写懈薪薪懈泻褍. 校 胁褨泻褨 胁懈 蟹薪邪泄写械褌械 胁褨写芯屑芯褋褌褨 褟泻 芯斜褉芯斜谢褟褌懈 薪邪屑褨褉懈 褋褌胁芯褉械薪褨 褑懈屑懈 写褨褟屑懈.</string>
+    <string name="qhybrid_watchface_configuration_old_firmware">袝泻褉邪薪 薪邪谢邪褕褌褍胁邪薪薪褟 褑懈褎械褉斜谢邪褌褍 写谢褟 谐芯写懈薪薪懈泻褨胁 蟹 胁械褉褋褨褦褞 屑褨泻褉芯锌褉芯谐褉邪屑懈 DN1.0.2.19r 褌邪 薪懈卸褔械</string>
+    <string name="qhybrid_pref_summary_external_intents">袛芯蟹胁芯谢懈褌懈 褨薪褕懈屑懈 蟹邪褋褌芯褋褍薪泻邪屑 Android 薪邪写褋懈谢邪褌懈/芯褌褉懈屑褍胁邪褌懈 褎邪泄谢懈</string>
+    <string name="devicetype_amazfit_trex_pro">Amazfit T-Rex Pro</string>
+    <string name="qhybrid_pref_title_external_intents">袛芯蟹胁芯谢懈褌懈 薪械斜械蟹锌械褔薪褨 蟹芯胁薪褨褕薪褨 薪邪屑褨褉懈</string>
+    <string name="pref_summary_developer_settings">袧邪谢邪褕褌褍胁邪薪薪褟 褨 褎褍薪泻褑褨褩 褟泻褨 胁懈泻芯褉懈褋褌芯胁褍褞褌褜 褉芯蟹褉芯斜薪懈泻懈</string>
+    <string name="pref_title_developer_settings">袧邪谢邪褕褌褍胁邪薪薪褟 褉芯蟹褉芯斜薪懈泻邪</string>
+    <string name="qhybrid_pref_summary_actions">袛褨褩 写谢褟 蟹邪褋褌芯褋褍薪泻褍 Commute</string>
+    <string name="qhybrid_pref_title_actions">袛褨褩</string>
+    <string name="qhybrid_calibration_100_steps">100 泻褉芯泻褨胁</string>
+    <string name="qhybrid_calibration_10_steps">10 泻褉芯泻褨胁</string>
+    <string name="qhybrid_calibration_1_step">1 泻褉芯泻</string>
+    <string name="qhybrid_calibration_clockwise">袟邪 谐芯写懈薪薪懈泻芯胁芯褞 褋褌褉褨谢泻芯褞</string>
+    <string name="qhybrid_calibration_counterclockwise">袩褉芯褌懈 谐芯写懈薪薪懈泻芯胁芯褩 褋褌褉褨谢泻懈</string>
+    <string name="fossil_hr_new_action">袧芯胁邪 写褨褟</string>
+    <string name="fossil_hr_new_action_cancel">褋泻邪褋褍胁邪褌懈</string>
+    <string name="fossil_hr_edit_action">袪械写邪谐褍胁邪褌懈 写褨褞</string>
+    <string name="fossil_hr_edit_action_delete">胁懈写邪谢懈褌懈</string>
+    <string name="pref_summary_canned_messages_dismisscall">袙褨写褏懈谢褟褌懈 胁懈泻谢懈泻懈 蟹 谐芯写懈薪薪懈泻邪 小袦小-锌芯胁褨写芯屑谢械薪薪褟屑</string>
+    <string name="pref_summary_canned_messages_set">袧邪写褨褋谢邪褌懈 薪邪 锌褉懈褋褌褉褨泄 薪邪谢邪褕褌芯胁邪薪褨 胁薪懈蟹褍 锌芯胁褨写芯屑谢械薪薪褟</string>
+    <string name="gpx_receiver_overwrite_some_files">袛械褟泻褨 褎邪泄谢懈 胁卸械 褨褋薪褍褞褌褜. 袩械褉械蟹邪锌懈褋邪褌懈?</string>
+    <string name="gpx_receiver_files_received">袨褌褉懈屑邪薪芯 褎邪泄谢(懈) GPX:</string>
+    <string name="gpx_receiver_activity_title">袩褉懈泄屑邪褔 GPX Gadgetbridge</string>
+    <string name="menuitem_barometer">袘邪褉芯屑械褌褉</string>
+    <string name="notification_channel_low_battery_name">袗泻褍屑褍谢褟褌芯褉 褉芯蟹褉褟写卸械薪芯</string>
+    <string name="notification_channel_transfer_name">袩械褉械写邪褔邪 写邪薪懈褏</string>
+    <string name="devicetype_miband6">Mi Band 6</string>
+    <string name="kind_agps_bundle">袧邪斜褨褉 AGPS</string>
+    <string name="activity_summary_detail_editing_gpx_track">袪械写邪谐褍胁邪薪薪褟 锌芯胁\'褟蟹邪薪芯谐芯 GPX-褌褉械泻褍</string>
+    <string name="activity_summary_detail_clear_gpx_track">袨褔懈褋褌懈褌懈 GPX-褌褉械泻</string>
+    <string name="activity_summary_detail_select_gpx_track">袙懈斜褉邪褌懈 GPX-褌褉械泻</string>
+    <string name="set">袟屑褨薪懈褌懈</string>
+    <string name="watchface_install_info">袙懈 蟹斜懈褉邪褦褌械褋褟 胁褋褌邪薪芯胁懈褌懈 褑械泄 褑懈褎械褉斜谢邪褌:
+\n
+\n%1$s
+\n袙械褉褋褨褟 %2$s 胁褨写 %3$s
+\n</string>
+    <string name="pref_theme_black_background">袙懈泻芯褉懈褋褌芯胁褍胁邪褌懈 褔芯褉薪械 褌谢芯 写谢褟 褌械屑薪芯褩 褌械屑懈</string>
+    <string name="qhybrid_summary_calibration">袣邪谢褨斜褉褍胁邪薪薪褟 褋褌褉褨谢芯泻 谐芯写懈薪薪懈泻邪</string>
+    <string name="qhybrid_summary_file_management">袙褨写锌褉邪胁泻邪 褌邪 蟹邪胁邪薪褌邪卸械薪薪褟 褎邪泄谢褨胁</string>
+    <string name="pref_summary_physical_buttons">袧邪谢邪褕褌褍泄褌械 褎褍薪泻褑褨褩 褎褨蟹懈褔薪懈褏 泻薪芯锌芯泻 谐芯写懈薪薪懈泻邪</string>
+    <string name="pref_title_physical_buttons">肖褨蟹懈褔薪褨 泻薪芯锌泻懈</string>
+    <string name="qhybrid_title_watchface">袧邪谢邪褕褌褍胁邪薪薪褟 褑懈褎械褉斜谢邪褌邪</string>
+    <string name="watchface_setting_wrist_flick_duration">孝褉懈胁邪谢褨褋褌褜 (褍 屑褋)</string>
+    <string name="watchface_setting_wrist_flick_minute">啸胁懈谢懈薪薪邪 褋褌褉褨谢泻邪 (胁褨写 -360 写芯 360)</string>
+    <string name="watchface_setting_wrist_flick_hour">袚芯写懈薪薪邪 褋褌褉褨谢泻邪 (胁褨写 -360 写芯 360)</string>
+    <string name="watchface_setting_wrist_flick_move_relative">袩械褉械屑褨褖械薪薪褟 褉褍泻 胁褨写薪芯褋薪芯 褔邪褋褍</string>
+    <string name="watchface_setting_desc_wrist_flick">(褖芯斜 锌芯胁薪褨褋褌褞 胁懈屑泻薪褍褌懈, 褍胁褨屑泻薪褨褌褜 胁褨写薪芯褋薪懈泄 褉褍褏 褨 胁褋褌邪薪芯胁褨褌褜 写谢褟 胁褋褨褏 蟹薪邪褔械薪薪褟 0)</string>
+    <string name="watchface_setting_title_wrist_flick">袪褍褏 蟹邪锌\'褟褋褌褟</string>
+    <string name="watchface_setting_display_refresh_partial">效邪褋褌泻芯胁械 芯薪芯胁谢械薪薪褟 (褍 褏胁懈谢懈薪邪褏)</string>
+    <string name="watchface_setting_display_refresh_full">袩芯胁薪械 芯薪芯胁谢械薪薪褟 (褍 褏胁懈谢懈薪邪褏)</string>
+    <string name="watchface_setting_title_display_refresh_timeout">效邪褋 芯薪芯胁谢械薪薪褟 械泻褉邪薪邪</string>
+    <string name="watchface_dialog_title_settings">袧邪谢邪褕褌褍胁邪薪薪褟 褑懈褎械褉斜谢邪褌邪</string>
+    <string name="watchface_dialog_widget_color_black">效芯褉薪懈泄</string>
+    <string name="watchface_dialog_widget_color_white">袘褨谢懈泄</string>
+    <string name="watchface_dialog_widget_color">袣芯谢褨褉</string>
+    <string name="qhybrid_title_watchface_designer">袛懈蟹邪泄薪械褉 褑懈褎械褉斜谢邪褌邪</string>
+    <string name="watchface_dialog_widget_preset_right">袩褉邪胁芯褉褍褔</string>
+    <string name="watchface_dialog_widget_preset_left">袥褨胁芯褉褍褔</string>
+    <string name="button_watchface_preview">袩芯锌械褉械写薪褨泄 锌械褉械谐谢褟写 薪邪 谐芯写懈薪薪懈泻褍</string>
+    <string name="watchface_widget_type_heart_rate">袩褍谢褜褋</string>
+    <string name="watchface_dialog_title_set_name">袙褋褌邪薪芯胁褨褌褜 薪邪蟹胁褍 褑懈褎械褉斜谢邪褌邪</string>
+    <string name="button_watchface_settings">袧邪谢邪褕褌褍胁邪薪薪褟 褑懈褎械褉斜谢邪褌邪</string>
+    <string name="button_watchface_add_widget">袛芯写邪褌懈 胁褨写卸械褌</string>
+    <string name="button_watchface_select_image">袟屑褨薪懈褌懈 蟹芯斜褉邪卸械薪薪褟 褌谢邪</string>
+    <string name="button_watchface_edit_name">袟屑褨薪懈褌懈 薪邪蟹胁褍</string>
+    <string name="watchface_dialog_widget_preset_bottom">袙薪懈蟹褍</string>
+    <string name="watchface_dialog_widget_preset_top">袙谐芯褉褨</string>
+    <string name="watchface_dialog_widget_presets">袩芯锌械褉械写薪褜芯 胁褋褌邪薪芯胁谢械薪褨 褉芯蟹褌邪褕褍胁邪薪薪褟</string>
+    <string name="watchface_dialog_widget_y_coordinate">袣芯芯褉写懈薪邪褌邪 Y (屑邪泻褋. 240)</string>
+    <string name="watchface_dialog_widget_x_coordinate">袣芯芯褉写懈薪邪褌邪 X (屑邪泻褋. 240)</string>
+    <string name="watchface_dialog_widget_type">孝懈锌</string>
+    <string name="appmanager_app_edit">袙褨写褉械写邪谐褍胁邪褌懈</string>
+    <string name="button_watchface_save_apply">袟斜械褉械谐褌懈 褌邪 蟹邪褋褌芯褋褍胁邪褌懈</string>
+    <string name="watchface_widget_type_steps">袣褉芯泻懈</string>
+    <string name="watchface_widget_type_weather">袩芯谐芯写邪</string>
+    <string name="watchface_widget_type_date">袛邪褌邪</string>
+    <string name="watchface_dialog_title_add_widget">袛芯写邪褌懈 胁褨写卸械褌</string>
+    <string name="watchface_toast_settings_incomplete">袧邪谢邪褕褌褍胁邪薪薪褟 薪械蟹邪胁械褉褕械薪褨, 胁褨写卸械褌 薪械 写芯写邪薪芯</string>
+    <string name="devicetype_smaq2oss">SMA-Q2 OSS</string>
+    <string name="watchface_upload_failed">袧械 胁写邪谢芯褋褟 蟹邪胁邪薪褌邪卸懈褌懈 褑懈褎械褉斜谢邪褌. 袩芯胁褌芯褉褨褌褜 褋锌褉芯斜褍.</string>
+    <string name="watchface_cache_confirm_overwrite">笑懈褎械褉斜谢邪褌 褨蟹 褌邪泻芯褞 薪邪蟹胁芯褞 胁卸械 褨褋薪褍褦 胁 泻械褕褨. 袘邪卸邪褦褌械 锌械褉械蟹邪锌懈褋邪褌懈 泄芯谐芯\?</string>
+    <string name="heart_rate_result">袪械蟹褍谢褜褌邪褌懈 胁懈屑褨褉褞胁邪薪褜</string>
+    <string name="getting_heart_rate">袙懈屑褨褉褞胁邪薪薪褟</string>
+    <string name="blood_pressure">袗褉褌械褉褨邪谢褜薪懈泄 褌懈褋泻</string>
+    <string name="watchface_setting_power_saving_hands">袙懈屑泻薪褨褌褜 褉褍褏 褉褍泻, 泻芯谢懈 斜褉邪褋谢械褌 薪械 芯写褟谐薪械薪芯</string>
+    <string name="watchface_setting_power_saving_display">袙懈屑泻薪褍褌懈 芯薪芯胁谢械薪薪褟 械泻褉邪薪邪, 泻芯谢懈 斜褉邪褋谢械褌 薪械 芯写褟谐薪械薪芯</string>
+    <string name="watchface_setting_title_power_saving">袟邪芯褖邪写卸械薪薪褟 械薪械褉谐褨褩</string>
+    <string name="watchface_widget_type_chance_rain">袉屑芯胁褨褉薪褨褋褌褜 写芯褖褍</string>
+    <string name="watchface_widget_type_active_mins">啸胁懈谢懈薪懈 邪泻褌懈胁薪芯褋褌褨</string>
+    <string name="watchface_widget_type_2nd_tz">袛褉褍谐懈泄 褔邪褋芯胁懈泄 锌芯褟褋</string>
+    <string name="watchface_widget_type_calories">袣邪谢芯褉褨褩</string>
+    <string name="watchface_widget_type_battery">袗泻褍屑褍谢褟褌芯褉</string>
+    <string name="pref_summary_huami_force_new_protocol">校胁褨屑泻薪褨褌褜, 褟泻褖芯 胁邪褕 锌褉懈褋褌褉褨泄 斜褨谢褜褕械 薪械 锌褨写\'褦写薪褍褦褌褜褋褟 锌褨褋谢褟 芯薪芯胁谢械薪薪褟 屑褨泻褉芯锌褉芯谐褉邪屑懈</string>
+    <string name="pref_title_huami_force_new_protocol">袧芯胁懈泄 锌褉芯褌芯泻芯谢 邪胁褌械薪褌懈褎褨泻邪褑褨褩</string>
+    <string name="menuitem_flashlight">袥褨褏褌邪褉懈泻</string>
+    <string name="prefs_autoheartrate_interval">效邪褋褌芯褌邪 胁懈屑褨褉褞胁邪薪褜</string>
+    <string name="prefs_autoheartrate_sleep">袙懈屑褨褉褞胁邪褌懈 锌褨写 褔邪褋 褋薪褍</string>
+    <string name="prefs_autoheartrate_measurement">袗胁褌芯胁懈屑褨褉褞胁邪薪薪褟 锌褍谢褜褋褍</string>
+    <string name="prefs_autoheartrate_summary">袩械褉褨芯写懈褔薪褨 胁懈屑褨褉褞胁邪薪薪褟 锌褍谢褜褋褍 胁锌褉芯写芯胁卸 写薪褟, 邪 褌邪泻芯卸 锌褨写 褔邪褋 褋薪褍</string>
+    <string name="prefs_autoheartrate">袗胁褌芯胁懈蟹薪邪褔械薪薪褟 锌褍谢褜褋褍</string>
+    <string name="enable_notifications_summary">小锌芯胁褨褖械薪薪褟 锌褉芯 胁懈泻谢懈泻懈, 锌芯胁褨写芯屑谢械薪薪褟 褌芯褖芯</string>
+    <string name="enable_vibrations_summary">袙褨斜褉邪褑褨褟 写谢褟 胁懈泻谢懈泻褨胁, 锌芯胁褨写芯屑谢械薪褜, 褋锌芯胁褨褖械薪褜 褌芯褖芯</string>
+    <string name="prefs_notifications_enable">校胁褨屑泻薪褍褌懈 褋锌芯胁褨褖械薪薪褟</string>
+    <string name="prefs_vibration_enable">校胁褨屑泻薪褍褌懈 胁褨斜褉邪褑褨褞</string>
+    <string name="prefs_sleep_time_summary">袙懈蟹薪邪褔邪褦 褔邪褋, 泻芯谢懈 褉芯蟹锌褨蟹薪邪薪芯 褋芯薪</string>
+    <string name="prefs_sleep_time_label">袙懈蟹薪邪褔械薪薪褟 谐芯写懈薪 褋薪褍</string>
+    <string name="prefs_sleep_time">效邪褋 褋薪褍</string>
+    <string name="devicetype_fitpro">FitPro</string>
+    <string name="pref_title_ping_tone">袟胁褍泻芯胁懈泄 褋懈谐薪邪谢</string>
+    <string name="devicetype_nothingear1">Nothing Ear (1)</string>
+    <string name="nothing_prefs_inear_title">袙懈褟胁谢械薪薪褟 芯写褟谐邪薪薪褟 薪邪 胁褍褏邪</string>
+    <string name="nothing_prefs_inear_summary">袙褨写褌胁芯褉械薪薪褟/蟹褍锌懈薪泻邪 屑褍蟹懈泻懈 蟹邪谢械卸薪芯 胁褨写 褌芯谐芯, 褔懈 芯写褟谐薪械薪芯 薪邪胁褍褕薪懈泻懈</string>
+    <string name="nothing_prefs_audiomode_title">袗褍写褨芯褉械卸懈屑</string>
+    <string name="check_all_applications">袙懈斜褉邪褌懈 胁褋褨 蟹邪褋褌芯褋褍薪泻懈</string>
+    <string name="uncheck_all_applications">袩褉懈斜褉邪褌懈 胁懈斜褨褉 蟹 褍褋褨褏 蟹邪褋褌芯褋褍薪泻褨胁</string>
+    <string name="pref_title_notification_use_as_allow">袛芯蟹胁芯谢懈褌懈 褋锌芯胁褨褖械薪薪褟 蟹 胁懈斜褉邪薪懈褏 蟹邪褋褌芯褋褍薪泻褨胁</string>
+    <string name="title_activity_notification_management">袧邪谢邪褕褌褍胁邪薪薪褟 褋锌芯胁褨褖械薪褜</string>
+    <string name="pref_header_notification_application_settings">袧邪谢邪褕褌褍胁邪薪薪褟 写谢褟 泻芯卸薪芯谐芯 蟹邪褋褌芯褋褍薪泻褍</string>
+    <string name="pref_title_notification_use_as">袙懈泻芯褉懈褋褌邪褌懈 褋锌懈褋芯泻 蟹邪褋褌芯褋褍薪泻褨胁 写谢褟鈥�</string>
+    <string name="pref_title_notification_use_as_deny">袟邪斜芯褉芯薪懈褌懈 褋锌芯胁褨褖械薪薪褟 蟹 胁懈斜褉邪薪懈褏 蟹邪褋褌芯褋褍薪泻褨胁</string>
+    <string name="pref_title_message_privacy_mode">袪械卸懈屑 锌褉懈胁邪褌薪芯褋褌褨 写谢褟 锌芯胁褨写芯屑谢械薪褜</string>
+    <string name="pref_message_privacy_mode_off">袩芯泻邪蟹褍胁邪褌懈 褍胁械褋褜 胁屑褨褋褌</string>
+    <string name="pref_message_privacy_mode_complete">袩褉懈褏芯胁褍胁邪褌懈 褍胁械褋褜 胁屑褨褋褌</string>
+    <string name="pref_applications_settings">小锌懈褋芯泻 蟹邪褋褌芯褋褍薪泻褨胁</string>
+    <string name="toast_app_must_not_be_selected">袟邪褋褌芯褋褍薪芯泻 薪械 锌芯胁懈薪械薪 斜褍褌懈 胁懈斜懈褉邪薪懈屑 写谢褟 薪邪谢邪褕褌褍胁邪薪薪褟</string>
+    <string name="toast_app_must_be_selected">袟邪褋褌芯褋褍薪芯泻 锌芯胁懈薪械薪 斜褍褌懈 胁懈斜褉邪薪懈屑 写谢褟 薪邪谢邪褕褌褍胁邪薪薪褟</string>
+    <string name="devicetype_galaxybuds">Galaxy Buds</string>
+    <string name="prefs_equalizer_preset">袩芯锌械褉械写薪褨 薪邪谢邪褕褌褍胁邪薪薪褟 械泻胁邪谢邪泄蟹械褉邪</string>
+    <string name="pref_title_equalizer_bass_boost">袩褨写褋懈谢械薪薪褟 斜邪褋褨胁</string>
+    <string name="pref_title_equalizer_soft">袦\'褟泻懈泄</string>
+    <string name="pref_title_equalizer_dynamic">袛懈薪邪屑褨褔薪懈泄</string>
+    <string name="pref_title_equalizer_trebble">袩褨写胁懈褖械薪薪褟 胁懈褋芯泻懈褏 褔邪褋褌芯褌</string>
+    <string name="prefs_dolby_mode">袪械卸懈屑 Dolby</string>
+    <string name="prefs_equalizer">袝泻胁邪谢邪泄蟹械褉</string>
+    <string name="prefs_equalizer_summary">校胁褨屑泻薪褍褌懈 邪斜芯 胁懈屑泻薪褍褌懈 械泻胁邪谢邪泄蟹械褉</string>
+    <string name="prefs_game_mode_summary">袥懈褕械 褟泻褖芯 胁邪褕 褌械谢械褎芯薪 锌褨写褌褉懈屑褍褦 褨谐褉芯胁懈泄 褉械卸懈屑</string>
+    <string name="prefs_game_mode">袉谐褉芯胁懈泄 褉械卸懈屑</string>
+    <string name="prefs_touch_lock">小械薪褋芯褉薪械 斜谢芯泻褍胁邪薪薪褟</string>
+    <string name="prefs_touch_lock_summary">袙懈屑泻薪褍褌懈 锌芯写褨褩 褌芯褉泻邪薪薪褟</string>
+    <string name="prefs_galaxy_buds_experimental">袝泻褋锌械褉懈屑械薪褌邪谢褜薪械</string>
+    <string name="prefs_ambient_volume">袚褍褔薪褨褋褌褜 褋械褉械写芯胁懈褖邪</string>
+    <string name="prefs_ambient_voice_focus">肖芯泻褍褋 谐芯谢芯褋褍</string>
+    <string name="prefs_ambient_voice_summary">袩褨写褋懈谢械薪薪褟 谐芯谢芯褋褍</string>
+    <string name="prefs_left">袥褨胁懈泄</string>
+    <string name="prefs_right">袩褉邪胁懈泄</string>
+    <string name="pref_title_equalizer_clear">效懈褋褌懈泄</string>
+    <string name="prefs_dolby_summary">袧邪谢邪褕褌褍胁邪薪薪褟 Dolby 写谢褟 械泻胁邪谢邪泄蟹械褉邪</string>
+    <string name="prefs_ambient_sound">袧邪胁泻芯谢懈褕薪褨泄 蟹胁褍泻</string>
+    <string name="prefs_ambient_mode">袪械卸懈屑 蟹胁褍泻褍 写芯胁泻褨谢谢褟</string>
+    <string name="prefs_galaxy_touch_options">袩邪褉邪屑械褌褉懈 写芯褌懈泻褍</string>
+    <string name="battery_case">袣芯褉锌褍褋 邪泻褍屑褍谢褟褌芯褉邪</string>
+    <string name="left_earbud">袥褨胁懈泄 薪邪胁褍褕薪懈泻</string>
+    <string name="right_earbud">袩褉邪胁懈泄 薪邪胁褍褕薪懈泻</string>
+    <string name="pref_header_other">袉薪褕械</string>
+    <string name="pref_header_system">小懈褋褌械屑邪</string>
+    <string name="pref_header_equalizer">袝泻胁邪谢邪泄蟹械褉</string>
+    <string name="prefs_active_noise_cancelling_summary">袘谢芯泻褍胁邪褌懈 薪邪胁泻芯谢懈褕薪褨 褕褍屑懈</string>
+    <string name="pref_header_sony_ambient_sound_control">袣械褉褍胁邪薪薪褟 薪邪胁泻芯谢懈褕薪褨屑 蟹胁褍泻芯屑</string>
+    <string name="sony_ambient_sound_wind_noise_reduction">袟薪懈卸械薪薪褟 褕褍屑褍 胁褨褌褉褍</string>
+    <string name="prefs_pressure_relief">袟薪懈卸械薪薪褟 褌懈褋泻褍 褔械褉械蟹 薪邪胁泻芯谢懈褕薪褨泄 褕褍屑</string>
+    <string name="pressure_relief_summary">袟邪锌芯斜褨谐邪薪薪褟 胁褨写褔褍褌褌褞 褌懈褋泻褍 褍 胁褍褏邪褏, 泻芯谢懈 薪械 胁懈泻芯褉懈褋褌芯胁褍褦褌褜褋褟 邪泻褌懈胁薪械 褕褍屑芯锌芯谐谢懈薪邪薪薪褟</string>
+    <string name="sony_ambient_sound_focus_voice">袟芯褋械褉械写懈褌懈褋褟 薪邪 谐芯谢芯褋褨</string>
+    <string name="sony_sound_position_off">袙懈屑泻.</string>
+    <string name="sony_sound_position_front">小锌械褉械写褍</string>
+    <string name="sony_sound_position_rear_left">袟蟹邪写褍 谢褨胁芯褉褍褔</string>
+    <string name="sony_sound_position_rear_right">袟蟹邪写褍 锌褉邪胁芯褉褍褔</string>
+    <string name="sony_surround_mode">袪械卸懈屑 芯斜\'褦屑薪芯谐芯 蟹胁褍褔邪薪薪褟</string>
+    <string name="sony_surround_mode_off">袙懈屑泻.</string>
+    <string name="sony_surround_mode_arena">袗褉械薪邪</string>
+    <string name="sony_surround_mode_club">袣谢褍斜</string>
+    <string name="sony_equalizer_preset_bright">携褋泻褉邪胁懈泄</string>
+    <string name="sony_equalizer_preset_excited">小褏胁懈谢褜芯胁邪薪懈泄</string>
+    <string name="sony_equalizer_preset_mellow">袦\'褟泻懈泄</string>
+    <string name="sony_equalizer_preset_treble_boost">袩褨写胁懈褖械薪薪褟 胁懈褋芯泻懈褏 褔邪褋褌芯褌</string>
+    <string name="sony_equalizer_preset_bass_boost">袩褨写褋懈谢械薪薪褟 斜邪褋褨胁</string>
+    <string name="pref_header_sony_equalizer_preset_custom_1">袙谢邪褋薪褨 薪邪谢邪褕褌褍胁邪薪薪褟 1</string>
+    <string name="sony_equalizer_band_400">400</string>
+    <string name="sony_equalizer_band_2500">2,5k</string>
+    <string name="sony_equalizer_band_6300">6,3k</string>
+    <string name="sony_touch_sensor">小械薪褋芯褉薪械 泻械褉褍胁邪薪薪褟</string>
+    <string name="sony_notification_voice_guide">小锌芯胁褨褖械薪薪褟 泄 谐芯谢芯褋芯胁褨 胁泻邪蟹褨胁泻懈</string>
+    <string name="sony_automatic_power_off">袗胁褌芯胁懈屑泻薪械薪薪褟 卸懈胁谢械薪薪褟</string>
+    <string name="sony_automatic_power_off_off">袧械 胁懈屑懈泻邪褌懈</string>
+    <string name="sony_automatic_power_off_5_min">5 褏胁懈谢懈薪</string>
+    <string name="sony_automatic_power_off_30_min">30 褏胁懈谢懈薪</string>
+    <string name="watchface_widget_type_custom">袙谢邪褋薪懈泄 胁褨写卸械褌</string>
+    <string name="pref_title_equalizer_normal">袟胁懈褔邪泄薪褨</string>
+    <string name="devicetype_galaxybuds_live">Galaxy Buds Live</string>
+    <string name="devicetype_sony_wh_1000xm3">Sony WH-1000XM3</string>
+    <string name="prefs_active_noise_cancelling">袗泻褌懈胁薪械 褕褍屑芯锌芯谐谢懈薪邪薪薪褟</string>
+    <string name="sony_ambient_sound">袪械卸懈屑</string>
+    <string name="sony_ambient_sound_noise_cancelling">楔褍屑芯锌芯谐谢懈薪邪薪薪褟</string>
+    <string name="sony_ambient_sound_ambient_sound">袧邪胁泻芯谢懈褕薪褨泄 蟹胁褍泻</string>
+    <string name="sony_ambient_sound_level">袪褨胁械薪褜 薪邪胁泻芯谢懈褕薪褨褏 蟹胁褍泻褨胁</string>
+    <string name="sony_sound_position_front_right">小锌械褉械写褍 锌褉邪胁芯褉褍褔</string>
+    <string name="sony_surround_mode_outdoor_stage">袙褨写泻褉懈褌邪 褋褑械薪邪</string>
+    <string name="sony_surround_mode_concert_hall">袣芯薪褑械褉褌薪邪 蟹邪谢邪</string>
+    <string name="sony_ambient_sound_off">袙懈屑泻.</string>
+    <string name="sony_sound_position">袩芯谢芯卸械薪薪褟 蟹胁褍泻褍</string>
+    <string name="sony_sound_position_front_left">小锌械褉械写褍 谢褨胁芯褉褍褔</string>
+    <string name="sony_warn_sbc_codec">袩芯锌械褉械写卸械薪薪褟: 袝泻胁邪谢邪泄蟹械褉, 锌芯谢芯卸械薪薪褟 蟹胁褍泻褍 褌邪 锌邪褉邪屑械褌褉懈 芯斜\'褦屑褍 锌褉邪褑褞褞褌褜 谢懈褕械 写谢褟 邪褍写褨芯泻芯写械泻邪 SBC.</string>
+    <string name="sony_equalizer_preset_off">袙懈屑泻.</string>
+    <string name="sony_equalizer">袝泻胁邪谢邪泄蟹械褉</string>
+    <string name="pref_header_sony_equalizer_preset_custom_2">袙谢邪褋薪褨 薪邪谢邪褕褌褍胁邪薪薪褟 2</string>
+    <string name="sony_equalizer_preset_manual">袙褉褍褔薪褍</string>
+    <string name="sony_equalizer_preset_custom_1">袙谢邪褋薪懈泄 1</string>
+    <string name="sony_equalizer_preset_custom_2">袙谢邪褋薪懈泄 2</string>
+    <string name="sony_equalizer_band_1000">1k</string>
+    <string name="sony_automatic_power_off_3_hour">3 谐芯写懈薪懈</string>
+    <string name="sony_equalizer_preset_relaxed">袪芯蟹褋谢邪斜谢械薪懈泄</string>
+    <string name="sony_equalizer_preset_vocal">袙芯泻邪谢</string>
+    <string name="sony_equalizer_preset_speech">袦芯胁谢械薪薪褟</string>
+    <string name="sony_automatic_power_off_1_hour">1 谐芯写懈薪邪</string>
+    <string name="sony_equalizer_clear_bass">效懈褋褌懈泄 斜邪褋</string>
+    <string name="sony_equalizer_band_16000">16k</string>
+    <string name="prefs_activity_in_device_card_title">袩芯泻邪蟹褍胁邪褌懈 锌芯写褉芯斜懈褑褨 锌褉芯 写褨褟谢褜薪褨褋褌褜 薪邪 泻邪褉褌褑褨 锌褉懈褋褌褉芯褞</string>
+    <string name="prefs_activity_in_device_card_sleep_title">小芯薪</string>
+    <string name="prefs_activity_in_device_card_steps_title_summary">袩芯泻邪蟹褍胁邪褌懈 蟹邪谐邪谢褜薪褍 泻褨谢褜泻褨褋褌褜 泻褉芯泻褨胁</string>
+    <string name="device_card_activity_card_title">袩芯泻邪蟹褍胁邪薪褨 写邪薪褨 锌褉芯 写褨褟谢褜薪褨褋褌褜</string>
+    <string name="device_card_activity_card_title_summary">袨斜械褉褨褌褜, 褟泻褨 锌芯写褉芯斜懈褑褨 胁邪褕芯褩 写褨褟谢褜薪芯褋褌褨 锌芯泻邪蟹褍胁邪褌懈屑褍褌褜褋褟 薪邪 泻邪褉褌褑褨 锌褉懈褋褌褉芯褞</string>
+    <string name="prefs_activity_in_device_card_title_summary">袩芯泻邪蟹褍胁邪褌懈 锌芯褌芯褔薪褨 泻褉芯泻懈, 胁褨写褋褌邪薪褜 邪斜芯 褋芯薪 薪邪 泻邪褉褌褑褨 锌褉懈褋褌褉芯褞</string>
+    <string name="prefs_activity_in_device_card_sleep_title_summary">袩芯泻邪蟹褍胁邪褌懈 褌褉懈胁邪谢褨褋褌褜 褋薪褍</string>
+    <string name="prefs_activity_in_device_card_distance_title_summary">袙褨写褋褌邪薪褜 芯斜褔懈褋谢褞褦褌褜褋褟 薪邪 芯褋薪芯胁褨 泻褨谢褜泻芯褋褌褨 泻褉芯泻褨胁 褌邪 写芯胁卸懈薪懈 泻褉芯泻褍 (薪邪谢邪褕褌芯胁褍褦褌褜褋褟 胁 袧邪谢邪褕褌褍胁邪薪薪褟 鈥� 袩褉芯 胁邪褋)</string>
+    <string name="sony_automatic_power_off_when_taken_off">袩褉懈 蟹薪褟褌褌褨</string>
+    <string name="prefs_fm_preset_instructions">校褌褉懈屑褍泄褌械 泻薪芯锌泻褍, 褖芯斜 蟹斜械褉械谐褌懈 薪邪谢邪褕褌褍胁邪薪薪褟</string>
+    <string name="prefs_fm_presets_presets">袩芯锌械褉械写薪褨 薪邪谢邪褕褌褍胁邪薪薪褟</string>
+    <string name="watchface_dialog_widget_update_timeout">效邪褋 芯褔褨泻褍胁邪薪薪褟 芯薪芯胁谢械薪薪褟 褍 褏胁懈谢懈薪邪褏</string>
+    <string name="watchface_dialog_widget_timeout_hide_text">小褏芯胁邪褌懈 褌械泻褋褌 锌褨褋谢褟 芯褔褨泻褍胁邪薪薪褟</string>
+    <string name="watchface_dialog_widget_timeout_show_circle">袩芯泻邪蟹邪褌懈 泻芯谢芯 薪邪 褔邪褋 芯褔褨泻褍胁邪薪薪褟</string>
+    <string name="watchface_dialog_widget_timezone">效邪褋芯胁懈泄 锌芯褟褋</string>
+    <string name="qhybrid_title_on_device_confirmation">校胁褨屑泻薪褍褌懈 锌褨写褌胁械褉写卸械薪薪褟 锌褨写始褦写薪邪薪薪褟 锌褉懈褋褌褉芯褞</string>
+    <string name="qhybrid_summary_on_device_confirmation">袩褨写褌胁械褉写卸械薪薪褟 锌褨写始褦写薪邪薪薪褟 锌褉懈褋褌褉芯褞 屑芯卸褍褌褜 写褉邪褌褍胁邪褌懈. 袙懈屑泻薪械薪薪褟 褩褏 屑芯卸械 蟹屑械薪褕懈褌懈 褎褍薪泻褑褨芯薪邪谢褜薪褨褋褌褜.</string>
+    <string name="reminder_repeat">袩芯胁褌芯褉褞胁邪褌懈</string>
+    <string name="reminder_date">袛邪褌邪</string>
+    <string name="reminder_time">效邪褋</string>
+    <string name="reminder_once">袨写懈薪 褉邪蟹</string>
+    <string name="reminder_every_day">些芯写薪褟</string>
+    <string name="reminder_every_week">些芯褌懈卸薪褟</string>
+    <string name="reminder_every_month">些芯屑褨褋褟褑褟</string>
+    <string name="reminder_every_year">些芯褉芯泻褍</string>
+    <string name="reminder_delete_confirm_description">袙懈 胁锌械胁薪械薪褨, 褖芯 褏芯褔械褌械 胁懈写邪谢懈褌懈 薪邪谐邪写褍胁邪薪薪褟\?</string>
+    <string name="title_activity_reminder_details">袩芯写褉芯斜懈褑褨 薪邪谐邪写褍胁邪薪薪褟</string>
+    <string name="miband_prefs_reserve_reminder_calendar">袧邪谐邪写褍胁邪薪薪褟 写谢褟 褉械蟹械褉胁褍胁邪薪薪褟 屑邪泄斜褍褌薪褨褏 锌芯写褨泄</string>
+    <string name="prefs_reserve_reminder_calendar_summary">袣褨谢褜泻褨褋褌褜 锌芯写褨泄 泻邪谢械薪写邪褉褟, 褟泻褨 斜褍写褍褌褜 褋懈薪褏褉芯薪褨蟹芯胁邪薪褨</string>
+    <string name="reminder_time_every_week">%1$s, 褖芯褌懈卸薪褟</string>
+    <string name="title_activity_set_reminders">袧邪谢邪褕褌褍胁邪褌懈 薪邪谐邪写褍胁邪薪薪褟</string>
+    <string name="controlcenter_start_configure_reminders">袧邪谢邪褕褌褍胁邪褌懈 薪邪谐邪写褍胁邪薪薪褟</string>
+    <string name="reminder_message">袩芯胁褨写芯屑谢械薪薪褟</string>
+    <string name="reminder_time_once">%1$s, 芯写懈薪 褉邪蟹</string>
+    <string name="reminder_time_every_day">%1$s, 褖芯写薪褟</string>
+    <string name="reminder_time_every_month">%1$s, 褖芯屑褨褋褟褑褟</string>
+    <string name="reminder_time_every_year">%1$s, 褖芯褉芯泻褍</string>
+    <string name="reminder_delete_confirm_title">袙懈写邪谢懈褌懈 薪邪谐邪写褍胁邪薪薪褟</string>
+    <string name="reminder_no_free_slots_title">袧械屑邪褦 胁褨谢褜薪懈褏 泻芯屑褨褉芯泻</string>
+    <string name="reminder_no_free_slots_description">袧邪 锌褉懈褋褌褉芯褩 薪械屑邪褦 胁褨谢褜薪懈褏 泻芯屑褨褉芯泻 写谢褟 薪邪谐邪写褍胁邪薪褜 (褍褋褜芯谐芯 泻芯屑褨褉芯泻: %1$s)</string>
+    <string name="mi2_prefs_do_not_disturb_lift_wrist">袙屑懈泻邪褌懈 械泻褉邪薪 锌褨写 褔邪褋 锌褨写薪械褋械薪薪褟 褉褍泻懈 胁 褉械卸懈屑褨 \"袧械 褌褍褉斜褍胁邪褌懈\"</string>
+    <string name="averageAltitude">小械褉械写薪褟</string>
+    <string name="minSpeed">袦褨薪褨屑邪谢褜薪邪</string>
+    <string name="maxHR">袦邪泻褋懈屑邪谢褜薪懈泄 锌褍谢褜褋</string>
+    <string name="spm">泻褉芯泻褨胁/褏胁</string>
+    <string name="averageCadence">小械褉械写薪褨泄 褌械屑锌</string>
+    <string name="maxCadence">袦邪泻褋懈屑邪谢褜薪懈泄 褌械屑锌</string>
+    <string name="minHR">袦褨薪褨屑邪谢褜薪懈泄 锌褍谢褜褋</string>
+    <string name="maxStride">袦邪泻褋懈屑邪谢褜薪懈泄 泻褉芯泻</string>
+    <string name="minStride">袦褨薪褨屑邪谢褜薪懈泄 泻褉芯泻</string>
+    <string name="minCadence">袦褨薪褨屑邪谢褜薪懈泄 褌械屑锌</string>
+    <string name="pref_header_sony_equalizer_bands">袛褨邪锌邪蟹芯薪</string>
+    <string name="sony_audio_upsampling">袩褨写胁懈褖械薪薪褟 写懈褋泻褉械褌懈蟹邪褑褨褩 蟹胁褍泻褍</string>
+    <string name="activity_prefs_discovery_pairing">袩邪褉邪屑械褌褉懈 胁懈褟胁谢械薪薪褟 褌邪 蟹始褦写薪邪薪薪褟</string>
+    <string name="add_test_device">袛芯写邪褌懈 褌械褋褌芯胁懈泄 锌褉懈褋褌褉褨泄</string>
+    <string name="discover_unsupported_devices">袙懈褟胁谢褟褌懈 薪械锌褨写褌褉懈屑褍胁邪薪褨 锌褉懈褋褌褉芯褩</string>
+    <string name="discover_unsupported_devices_description">携泻褖芯 胁胁褨屑泻薪褍褌懈 褑械泄 锌邪褉邪屑械褌褉, 胁褋褨 胁懈褟胁谢械薪褨 锌褨写 褔邪褋 褋泻邪薪褍胁邪薪薪褟 锌褉懈褋褌褉芯褩 Bluetooth 斜褍写械 锌芯泻邪蟹邪薪芯. 袣芯褉芯褌泻械 蟹邪褌懈褋泻邪薪薪褟 褋泻芯锌褨褞褦 薪邪蟹胁褍 锌褉懈褋褌褉芯褞 褌邪 MAC-邪写褉械褋褍 写芯 斜褍褎械褉邪 芯斜屑褨薪褍. 孝褉懈胁邪谢械 蟹邪褌懈褋泻邪薪薪褟 胁褨写泻褉懈褦 褋锌谢懈胁薪械 胁褨泻薪芯 \"袛芯写邪褌懈 褌械褋褌芯胁懈泄 锌褉懈褋褌褉褨泄\". 袦芯卸械 锌褉懈蟹胁械褋褌懈 写芯 锌芯褌械薪褑褨泄薪懈褏 锌褉芯斜谢械屑 褨蟹 蟹邪胁懈褋邪薪薪褟屑 蟹邪褋褌芯褋褍薪泻褍.</string>
+    <string name="sony_button_mode_left">袪械卸懈屑 泻薪芯锌泻懈 (袥褨胁芯褉褍褔)</string>
+    <string name="devicetype_sony_wf_sp800n">Sony WF-SP800N</string>
+    <string name="sony_button_mode_volume_control">袪械谐褍谢褞胁邪薪薪褟 谐褍褔薪芯褋褌褨</string>
+    <string name="controlcenter_power_off_confirm_description">袙懈 胁锌械胁薪械薪褨, 褖芯 褏芯褔械褌械 胁懈屑泻薪褍褌懈 锌褉懈褋褌褉褨泄\?</string>
+    <string name="controlcenter_power_off">袙懈屑泻薪褍褌懈</string>
+    <string name="controlcenter_power_off_confirm_title">袙懈屑泻薪褍褌懈</string>
+    <string name="sony_pause_when_taken_off">袟褍锌懈薪褟褌懈, 泻芯谢懈 蟹薪褨屑邪褞褌褜褋褟 薪邪胁褍褕薪懈泻懈</string>
+    <string name="sony_button_mode_off">袙懈屑泻薪褍褌懈</string>
+    <string name="sony_button_mode_right">袪械卸懈屑 泻薪芯锌泻懈 (袩褉邪胁芯褉褍褔)</string>
+    <string name="sony_button_mode_ambient_sound_control">袣械褉褍胁邪薪薪褟 薪邪胁泻芯谢懈褕薪褨屑 蟹胁褍泻芯屑</string>
+    <string name="sony_button_mode_playback_control">袣械褉褍胁邪薪薪褟 胁褨写褌胁芯褉械薪薪褟屑</string>
+    <string name="devicetype_bose_qc35">Bose QC35</string>
+    <string name="ascentDistance">袙褨写褋褌邪薪褜 锌褨写泄芯屑褍</string>
+    <string name="descentDistance">袙褨写褋褌邪薪褜 褋锌褍褋泻褍</string>
+    <string name="devicetype_vesc">VESC</string>
+    <string name="activity_type_hiking">袩褨褕芯褏褨写薪懈泄 褌褍褉懈蟹屑</string>
+    <string name="activity_type_climbing">袗谢褜锌褨薪褨蟹屑</string>
+    <string name="distance_format_kilometers">###.#泻屑</string>
+    <string name="distance_format_miles">###.#屑懈谢褜</string>
+    <string name="pref_button_action_disabled">袙懈屑泻薪械薪芯</string>
+    <string name="distance_format_meters">###屑</string>
+    <string name="distance_format_feet">###褎褍褌褨胁</string>
+    <string name="pref_media_next">袧邪褋褌褍锌薪邪 写芯褉褨卸泻邪</string>
+    <string name="pref_media_previous">袩芯锌械褉械写薪褟 写芯褉褨卸泻邪</string>
+    <string name="pref_device_action_broadcast">袧邪写褨褋谢邪褌懈 Broadcast</string>
+    <string name="pref_media_playpause">袩械褉械屑懈泻薪褍褌懈 胁褨写褌胁芯褉械薪薪褟</string>
+    <string name="pref_media_volumeup">袚褍褔薪褨褕械</string>
+    <string name="about_hash">袣芯屑褨褌 %s</string>
+    <string name="pref_media_play">袙褨写褌胁芯褉懈褌懈 屑械写褨邪</string>
+    <string name="pref_media_pause">袟褍锌懈薪懈褌懈 屑械写褨邪</string>
+    <string name="pref_media_volumedown">孝懈褏褨褕械</string>
+    <string name="devicetype_domyos_t540">Domyos T540</string>
+    <string name="pref_media_forward">袩褉芯泻褉褍褌懈褌懈 胁锌械褉械写</string>
+    <string name="pref_media_rewind">袩褉芯泻褉褍褌懈褌懈 薪邪蟹邪写</string>
+    <string name="prefs_activity_recognition">袧邪谢邪褕褌褍胁邪薪薪褟 褉芯蟹锌褨蟹薪邪胁邪薪薪褟 邪泻褌懈胁薪芯褋褌褨</string>
+    <string name="pref_activity_recognize_biking">褉芯蟹锌褨蟹薪邪胁邪褌懈 褩蟹写褍 薪邪 胁械谢芯褋懈锌械写褨</string>
+    <string name="pref_activity_recognize_walking">褉芯蟹锌褨蟹薪邪胁邪褌懈 褏芯写褜斜褍</string>
+    <string name="pref_activity_recognize_rowing">褉芯蟹锌褨蟹薪邪胁邪褌懈 胁械褋谢褍胁邪薪薪褟</string>
+    <string name="pref_activity_recognition_mode_ask">蟹邪锌懈褌褍胁邪褌懈</string>
+    <string name="pref_activity_recognition_mode_auto">邪胁褌芯屑邪褌懈褔薪芯</string>
+    <string name="menuitem_menu">袦械薪褞</string>
+    <string name="pref_activity_recognize_running">褉芯蟹锌褨蟹薪邪胁邪褌懈 斜褨谐</string>
+    <string name="pref_activity_recognition_mode_none">薪械 褉芯蟹锌褨蟹薪邪胁邪褌懈</string>
+    <string name="fossil_hr_button_config_info">袛械褟泻褨 泻薪芯锌泻懈 薪械 屑芯卸薪邪 薪邪谢邪褕褌褍胁邪褌懈, 芯褋泻褨谢褜泻懈 褩褏薪褨 褎褍薪泻褑褨褩 蟹邪泻芯写芯胁邪薪褨 胁 屑褨泻褉芯锌褉芯谐褉邪屑褍 谐芯写懈薪薪懈泻邪.
+\n
+\n袩芯锌械褉械写卸械薪薪褟: 褌褉懈胁邪谢械 薪邪褌懈褋泻邪薪薪褟 胁械褉褏薪褜芯褩 泻薪芯锌泻懈, 褟泻褖芯 胁褋褌邪薪芯胁谢械薪芯 谐芯写懈薪薪懈泻 蟹 芯褎褨褑褨泄薪芯谐芯 蟹邪褋褌芯褋褍薪泻褍 Fossil, 褌邪泻芯卸 锌械褉械屑懈泻邪褦褌褜褋褟 屑褨卸 锌芯泻邪蟹芯屑/褏芯胁邪薪薪褟屑 胁褨写卸械褌褨胁.</string>
+    <string name="sony_speak_to_chat_timeout_standard">小褌邪薪写邪褉褌薪邪 (30 褋)</string>
+    <string name="sony_speak_to_chat_timeout_long">袛芯胁谐邪 (1 褏胁)</string>
+    <string name="sony_connect_two_devices">袨写薪芯褔邪褋薪械 锌褨写\'褦写薪邪薪薪褟 写芯 2 锌褉懈褋褌褉芯褩胁</string>
+    <string name="devicetype_sony_wh_1000xm4">Sony WH-1000XM4</string>
+    <string name="sony_speak_to_chat">袚芯谢芯褋芯胁褨 锌芯胁褨写芯屑谢械薪薪褟 褍 褔邪褌</string>
+    <string name="sony_speak_to_chat_sensitivity">效褍褌谢懈胁褨褋褌褜 褉芯蟹锌褨蟹薪邪胁邪薪薪褟 谐芯谢芯褋褍</string>
+    <string name="sony_speak_to_chat_sensitivity_auto">袗胁褌芯屑邪褌懈褔薪邪</string>
+    <string name="sony_speak_to_chat_sensitivity_high">袙懈褋芯泻邪</string>
+    <string name="sony_speak_to_chat_focus_on_voice">袟芯褋械褉械写懈褌懈褋褟 薪邪 谐芯谢芯褋褨</string>
+    <string name="sony_speak_to_chat_timeout">袟邪褌褉懈屑泻邪</string>
+    <string name="sony_speak_to_chat_sensitivity_low">袧懈蟹褜泻邪</string>
+    <string name="sony_speak_to_chat_timeout_off">袙懈屑泻薪械薪芯</string>
+    <string name="sony_speak_to_chat_timeout_short">袣芯褉芯褌泻邪 (15 褋)</string>
+    <string name="watchface_dialog_widget_width">楔懈褉懈薪邪 胁褨写卸械褌邪 (褍 锌褨泻褋械谢褟褏)</string>
+    <string name="discovery_bluetooth_scan">小泻邪薪褍胁邪薪薪褟 Bluetooth:</string>
+    <string name="discovery_bluetooth_le_scan">小泻邪薪褍胁邪薪薪褟 Bluetooth LE:</string>
+    <string name="device_unsupported">袧袝 袩袉袛孝袪袠袦校袆孝鞋小携</string>
+    <string name="devicetype_sonyswr12">Sony SWR12</string>
+    <string name="pref_header_sony_device_info">袙褨写芯屑芯褋褌褨 锌褉芯 锌褉懈褋褌褉褨泄</string>
+    <string name="audio_codec">袗褍写褨芯泻芯写械泻</string>
+    <string name="unknown">袧械胁褨写芯屑芯</string>
+    <string name="start">袪芯蟹锌芯褔邪褌懈</string>
+    <string name="sony_anc_optimize_title">袨锌褌懈屑褨蟹褍胁邪褌懈</string>
+    <string name="pref_header_sony_anc_optimizer">袨锌褌懈屑褨蟹邪褌芯褉 褕褍屑芯锌芯谐谢懈薪邪薪薪褟</string>
+    <string name="sony_anc_optimize_description">袧邪褌懈褋薪褨褌褜, 褖芯斜 蟹邪锌褍褋褌懈褌懈 芯锌褌懈屑褨蟹邪褌芯褉 褕褍屑芯锌芯谐谢懈薪邪薪薪褟.</string>
+    <string name="pref_anc_optimizer_state_pressure">袗褌屑芯褋褎械褉薪懈泄 褌懈褋泻</string>
+    <string name="sony_anc_optimizer_status_starting">袟邪锌褍褋泻鈥�</string>
+    <string name="sony_anc_optimizer_status_not_running">袧械 蟹邪锌褍褖械薪芯</string>
+    <string name="sony_anc_optimizer_status_analyzing">袗薪邪谢褨蟹褍胁邪薪薪褟鈥�</string>
+    <string name="sony_anc_optimize_confirmation_title">袨锌褌懈屑褨蟹邪褌芯褉 褕褍屑芯锌芯谐谢懈薪邪薪薪褟</string>
+    <string name="sony_anc_optimizer_status_wearing_condition">袙懈屑褨褉褞胁邪薪薪褟 褋褌邪薪褍 褋械褉械写芯胁懈褖邪鈥�</string>
+    <string name="sony_anc_optimize_confirmation_description">袙懈泻芯褉懈褋褌芯胁褍泄褌械 薪邪胁褍褕薪懈泻懈, 褟泻 蟹邪蟹胁懈褔邪泄. 携泻褖芯 褋械褉械写芯胁懈褖械 邪斜芯 邪褌屑芯褋褎械褉薪懈泄 褌懈褋泻 蟹屑褨薪褞褞褌褜褋褟, 蟹邪锌褍褋褌褨褌褜 芯锌褌懈屑褨蟹邪褌芯褉 褖械 褉邪蟹.</string>
+    <string name="sony_anc_optimizer_status_atmospheric_pressure">袙懈屑褨褉褞胁邪薪薪褟 邪褌屑芯褋褎械褉薪芯谐芯 褌懈褋泻褍鈥�</string>
+    <string name="sony_anc_optimizer_status_finished">袟邪胁械褉褕械薪薪褟鈥�</string>
+    <string name="devicetype_amazfit_pop">Amazfit Pop</string>
+    <string name="devicetype_amazfit_pop_pro">Amazfit Pop Pro</string>
+    <string name="menuitem_nothing">袩芯褉芯卸薪褜芯</string>
+    <string name="pref_title_opentracks_packagename">袧邪蟹胁邪 锌邪泻褍薪泻邪 OpenTracks</string>
+    <string name="pref_summary_opentracks_packagename">袙懈泻芯褉懈褋褌芯胁褍褦褌褜褋褟 写谢褟 蟹邪锌褍褋泻褍/蟹褍锌懈薪泻懈 蟹邪锌懈褋褍 褌褉械泻褨胁 GPS 褍 蟹芯胁薪褨褕薪褜芯屑褍 褎褨褌薪械褋-蟹邪褋褌芯褋褍薪泻褍.</string>
+    <string name="pref_device_action_fitness_app_control_start">袩芯褔邪褌芯泻 胁褨写褋褌械卸械薪薪褟 蟹邪褋褌芯褋褍薪泻褍 写谢褟 褎褨褌薪械褋褍</string>
+    <string name="pref_device_action_fitness_app_control_stop">袟褍锌懈薪泻邪 胁褨写褋褌械卸械薪薪褟 蟹邪褋褌芯褋褍薪泻褍 写谢褟 褎褨褌薪械褋褍</string>
+    <string name="pref_title_notifications_generic_settings">袧邪谢邪褕褌褍胁邪薪薪褟 褋锌芯胁褨褖械薪褜 Android</string>
+    <string name="activity_db_management_autoexport_scheduled_yes">袗胁褌芯械泻褋锌芯褉褌 (锌芯褔邪褌泻芯胁芯) 斜褍谢芯 蟹邪锌谢邪薪芯胁邪薪芯 薪邪 %1$s</string>
+    <string name="activity_db_management_autoexport_enabled_yes">袗胁褌芯械泻褋锌芯褉褌 褍胁褨屑泻薪械薪芯.</string>
+    <string name="activity_db_management_autoexport_enabled_no">袗胁褌芯械泻褋锌芯褉褌 薪械 胁胁褨屑泻薪械薪芯.</string>
+    <string name="activity_db_management_autoexport_scheduled_no">袗胁褌芯械泻褋锌芯褉褌 薪械 蟹邪锌谢邪薪芯胁邪薪芯.</string>
+    <string name="autoExport_lastTime_label">袨褋褌邪薪薪褨泄 邪胁褌芯械泻褋锌芯褉褌: %1$s</string>
+    <string name="activity_db_management_autoexport_location">袧械 胁写邪谢芯褋褟 胁褋褌邪薪芯胁懈褌懈 屑褨褋褑械锌械褉械斜褍胁邪薪薪褟. 袡屑芯胁褨褉薪芯, 锌褉芯斜谢械屑邪 蟹 薪芯胁褨褕芯褞 褋懈褋褌械屑芯褞 写芯蟹胁芯谢褨胁 Android. 楔胁懈写褕械 蟹邪 胁褋械, 邪胁褌芯械泻褋锌芯褉褌 蟹邪褉邪蟹 薪械 锌褉邪褑褞褦.</string>
+    <string name="watchface_dialog_pre_setting_position">锌芯锌械褉械写薪褦 胁褋褌邪薪芯胁谢械薪薪褟 褉芯蟹褌邪褕褍胁邪薪薪褟 薪邪 %s</string>
+    <string name="watchface_setting_light_up_on_notification">袟邪褋胁褨褌谢褞胁邪褌懈 蟹邪 薪芯胁芯谐芯 褋锌芯胁褨褖械薪薪褟</string>
+    <string name="menuitem_email">袝谢. 锌芯褕褌邪</string>
+    <string name="application_name_banglejs_main">Bangle.js Gadgetbridge</string>
+    <string name="about_description_banglejs_main">Android-蟹邪褋褌芯褋褍薪芯泻 写谢褟 Bangle.js 褋褌胁芯褉械薪懈泄 薪邪 芯褋薪芯胁褨 锌褉芯褦泻褌褍 Gadgetbridge 蟹 写芯写邪褌泻芯胁懈屑 写芯褋褌褍锌芯屑 写芯 褨薪褌械褉薪械褌褍.
+\n
+\n效械褉械蟹 锌芯谢褨褌懈泻褍 Google Play 袦邪褉泻械褌褍, 屑懈 薪械 屑芯卸械屑芯 褉芯蟹屑褨褖褍胁邪褌懈 锌芯褋懈谢邪薪薪褟 薪邪 胁薪械褋泻懈 胁 褋邪屑芯屑褍 蟹邪褋褌芯褋褍薪泻褍, 邪谢械 褟泻褖芯 胁褨薪 胁邪屑 锌芯写芯斜邪褦褌褜褋褟, 褉芯蟹谐谢褟薪褜褌械 屑芯卸谢懈胁褨褋褌褜 写芯锌芯屑芯谐褌懈 薪邪屑 褔械褉械蟹 写芯屑邪褕薪褞 褋褌芯褉褨薪泻褍 Gadgetbridge 薪懈卸褔械.</string>
+    <string name="gadgetbridge_running_banglejs_main">Bangle.js 蟹邪锌褍褖械薪芯</string>
+    <string name="application_name_banglejs_nopebble">Bangle.js Gadgetbridge</string>
+    <string name="title_activity_controlcenter_banglejs_nopebble">Bangle.js Gadgetbridge</string>
+    <string name="gadgetbridge_running_banglejs_nopebble">Bangle.js 锌褉邪褑褞褦</string>
+    <string name="application_name_main_nightly">Gadgetbridge (Nightly)</string>
+    <string name="about_activity_title_main_nightly">袩褉芯 Gadgetbridge Nightly</string>
+    <string name="gadgetbridge_running_main_nightly">Nightly GB 蟹邪锌褍褖械薪芯</string>
+    <string name="application_name_main_nopebble">Gadgetbridge (Nightly, 斜械蟹 锌芯褋褌邪褔邪谢褜薪懈泻邪 Pebble)</string>
+    <string name="title_activity_controlcenter_main_nopebble">Gadgetbridge Nightly 斜械蟹 Pebble</string>
+    <string name="about_activity_title_main_nopebble">袩褉芯 Gadgetbridge Nightly 斜械蟹 Pebble</string>
+    <string name="gadgetbridge_running_main_nopebble">Nightly NoPebble GB 锌褉邪褑褞褦</string>
+    <string name="about_activity_title_banglejs_nopebble">袩褉芯 Bangle.js Gadgetbridge</string>
+    <string name="about_activity_title_banglejs_main">袩褉芯 Bangle.js Gadgetbridge</string>
+    <string name="title_activity_controlcenter_banglejs_main">Bangle.js Gadgetbridge</string>
+    <string name="about_description_banglejs_nopebble">袟邪褋芯褋褍薪芯泻-泻芯屑锌邪薪褜泄芯薪 Android 写谢褟 Bangle.js 锌芯斜褍写芯胁邪薪懈泄 薪邪 芯褋薪芯胁褨 锌褉芯褦泻褌褍 Gadgetbridge, 蟹 写芯写邪胁邪薪薪褟屑 写芯褋褌褍锌褍 写芯 褨薪褌械褉薪械褌褍.
+\n
+\n袟谐褨写薪芯 蟹 锌褉邪胁懈谢邪屑懈 Google Play 袦邪褉泻械褌褍, 薪邪屑 薪械 写芯蟹胁芯谢械薪芯 胁泻邪蟹褍胁邪褌懈 锌芯褋懈谢邪薪薪褟 薪邪 写芯锌芯屑芯谐褍 胁 褋邪屑芯屑褍 蟹邪褋褌芯褋褍薪泻褍, 邪谢械 褟泻褖芯 胁褨薪 胁邪屑 写芯 胁锌芯写芯斜懈, 胁懈 屑芯卸械褌械 锌芯褋锌褉懈褟褌懈 褉芯蟹胁懈褌泻褍 锌褉芯褦泻褌褍 褔械褉械蟹 写芯屑邪褕薪褞 褋褌芯褉褨薪泻褍 Gadgetbridge 薪懈卸褔械.</string>
+    <string name="title_activity_controlcenter_main_nightly">Gadgetbridge Nightly</string>
+    <string name="about_description_main_nightly">袧械蟹邪谢械卸薪邪 胁褨写 褏屑邪褉懈, 胁褨谢褜薪邪 蟹 谢褨褑械薪蟹褨褦褞 Copyleft 蟹邪屑褨薪邪 蟹邪泻褉懈褌懈褏 蟹邪褋褌芯褋褍薪泻褨胁 胁褨写 褨薪褕懈褏 胁懈褉芯斜薪懈泻褨胁 写谢褟 锌褉懈褋褌褉芯褩胁 Android. 袙懈锌褍褋泻懈 Gadgetbridge Nightly. 袡芯谐芯 薪械 屑芯卸薪邪 胁褋褌邪薪芯胁懈褌懈, 褟泻褖芯 褍 胁邪褋 褍卸械 胁褋褌邪薪芯胁谢械薪芯 蟹邪褋褌芯褋褍薪芯泻 Gadgetbridge 邪斜芯 Pebble 褔械褉械蟹 泻芯薪褎谢褨泻褌 褍 锌芯褋褌邪褔邪谢褜薪懈泻褍 Pebble.</string>
+    <string name="about_description_main_nopebble">袘械蟹褏屑邪褉薪邪 胁褨谢褜薪邪 蟹 泻芯锌褨谢械褎褌 谢褨褑械薪蟹褨褦褞 蟹邪屑褨薪邪 胁谢邪褋薪懈褑褜泻懈褏 蟹邪褋褌芯褋褍薪泻褨胁 写谢褟 锌褉懈褋褌褉芯褩胁 Android. 袙懈锌褍褋泻懈 Gadgetbridge Nightly. 校 褑褨泄 胁械褉褋褨褩 锌芯褋褌邪褔邪谢褜薪懈泻 Pebble 锌械褉械泄屑械薪芯胁邪薪懈泄 写谢褟 蟹邪锌芯斜褨谐邪薪薪褟 泻芯薪褎谢褨泻褌褨胁, 褌芯屑褍 写械褟泻褨 褨薪褌械谐褉邪褑褨褩, 锌芯胁\'褟蟹邪薪褨 蟹 Pebble, 薪械 锌褉邪褑褞胁邪褌懈屑褍褌褜, 邪谢械 泄芯谐芯 屑芯卸薪邪 胁褋褌邪薪芯胁懈褌懈 褉邪蟹芯屑 褨蟹 薪邪褟胁薪懈屑 褍褋褌邪薪芯胁谢械薪懈屑 Gadgetbridge.</string>
+    <string name="pref_device_action_fitness_app_control_toggle">袩械褉械屑泻薪褍褌懈 褋褌械卸械薪薪褟 褎褨褌薪械褋-蟹邪褋褌芯褋褍薪泻褍</string>
+    <string name="world_clock_delete_confirm_title">袙懈写邪谢懈褌懈 \'%1$s\'</string>
+    <string name="world_clock_delete_confirm_description">袙懈 胁锌械胁薪械薪褨, 褖芯 褏芯褔械褌械 胁懈写邪谢懈褌懈 褑械泄 谐芯写懈薪薪懈泻?</string>
+    <string name="world_clock_no_free_slots_description">袧邪 锌褉懈褋褌褉芯褩 薪械屑邪褦 胁褨谢褜薪懈褏 泻芯屑褨褉芯泻 写谢褟 褋胁褨褌芯胁懈褏 谐芯写懈薪薪懈泻褨胁 (褍褋褜芯谐芯 泻芯屑褨褉芯泻: %1$s)</string>
+    <string name="prefs_activate_display_on_lift_sensitivity">效褍褌谢懈胁褨褋褌褜</string>
+    <string name="normal">袟胁懈褔邪泄薪芯</string>
+    <string name="sensitive">效褍褌谢懈胁芯</string>
+    <string name="pref_world_clocks_title">小胁褨褌芯胁懈泄 褔邪褋</string>
+    <string name="pref_world_clocks_summary">袧邪谢邪褕褌褍泄褌械 谐芯写懈薪薪懈泻懈 写谢褟 褨薪褕懈褏 褔邪褋芯胁懈褏 锌芯褟褋褨胁</string>
+    <string name="title_activity_world_clock_details">袩芯写褉芯斜懈褑褨 褋胁褨褌芯胁芯谐芯 谐芯写懈薪薪懈泻邪</string>
+    <string name="world_clock_no_free_slots_title">袧械屑邪褦 胁褨谢褜薪懈褏 泻芯屑褨褉芯泻</string>
+    <string name="world_clock_timezone">效邪褋芯胁懈泄 锌芯褟褋</string>
+    <string name="world_clock_label">袧邪蟹胁邪</string>
+    <string name="mi5_prefs_workout_activity_types">孝懈锌懈 褌褉械薪褍胁邪薪褜</string>
+    <string name="activity_type_outdoor_running">袘褨谐 薪邪写胁芯褉褨</string>
+    <string name="mi5_prefs_workout_activity_types_summary">袨斜械褉褨褌褜 褌懈锌懈 写褨褟谢褜薪芯褋褌褨 写谢褟 胁褨写芯斜褉邪卸械薪薪褟 薪邪 械泻褉邪薪褨 褌褉械薪褍胁邪薪褜</string>
+    <string name="activity_type_freestyle">肖褉懈褋褌邪泄谢</string>
+    <string name="activity_type_outdoor_cycling">袙械谢芯褩蟹写邪 薪邪写胁芯褉褨</string>
+    <string name="activity_type_elliptical">袝谢褨锌褌懈褔薪懈泄 褌褉械薪邪卸械褉</string>
+    <string name="pref_screen_notification_idle_alerts">小锌芯胁褨褖械薪薪褟 锌褉芯 斜械蟹写褨褟谢褜薪褨褋褌褜</string>
+    <string name="pref_screen_notification_profile_event_reminder">袧邪谐邪写褍胁邪薪薪褟 锌褉芯 锌芯写褨褩</string>
+    <string name="pref_screen_notification_profile_find_device">袟薪邪泄褌懈 锌褉懈褋褌褉褨泄</string>
+    <string name="pref_screen_vibration_patterns_title">楔邪斜谢芯薪懈 胁褨斜褉邪褑褨褩</string>
+    <string name="pref_screen_vibration_patterns_summary">袧邪谢邪褕褌褍泄褌械 褕邪斜谢芯薪懈 胁褨斜褉邪褑褨褩 写谢褟 褉褨蟹薪懈褏 褋锌芯胁褨褖械薪褜</string>
+    <string name="devicetype_sony_wf_1000xm3">Sony WF-1000XM3</string>
+    <string name="devicetype_galaxybuds_pro">Galaxy Buds Pro</string>
+    <string name="prefs_in_ear_detection_summary">袙褨写褌胁芯褉械薪薪褟 胁懈泻谢懈泻褨胁 褔械褉械蟹 薪邪胁褍褕薪懈泻懈, 泻芯谢懈 胁芯薪懈 褍 胁邪褕懈褏 胁褍褏邪褏</string>
+    <string name="prefs_seamless_connection_switch_summary">袗胁褌芯屑邪褌懈褔薪械 锌械褉械屑懈泻邪褦 薪邪胁褍褕薪懈泻懈 屑褨卸 锌芯胁\'褟蟹邪薪懈屑懈 锌褉懈褋褌褉芯褟屑懈</string>
+    <string name="prefs_ambient_volume_left">袚褍褔薪褨褋褌褜 褋械褉械写芯胁懈褖邪 蟹谢褨胁邪</string>
+    <string name="prefs_customize_ambient_sound_summary">袧邪谢邪褕褌褍胁邪薪薪褟 薪邪胁泻芯谢懈褕薪褜芯谐芯 蟹胁褍泻褍</string>
+    <string name="prefs_ambient_sound_during_call_title">袧邪胁泻芯谢懈褕薪褨泄 蟹胁褍泻 锌褨写 褔邪褋 胁懈泻谢懈泻褍</string>
+    <string name="prefs_active_noise_cancelling_level">袪褨胁械薪褜 邪泻褌懈胁薪芯谐芯 褕褍屑芯锌芯谐谢懈薪邪薪薪褟</string>
+    <string name="prefs_active_noise_cancelling_level_high">袙懈褋芯泻懈泄</string>
+    <string name="prefs_switch_control_left">袩械褉械屑懈泻邪薪薪褟 械谢械屑械薪褌邪 泻械褉褍胁邪薪薪褟 谢褨胁芯褉褍褔</string>
+    <string name="prefs_switch_control_right">袩械褉械屑懈泻邪薪薪褟 械谢械屑械薪褌邪 泻械褉褍胁邪薪薪褟 锌褉邪胁芯褉褍褔</string>
+    <string name="pref_title_touch_anc">袗泻褌懈胁薪械 褕褍屑芯锌芯谐谢懈薪邪薪薪褟</string>
+    <string name="pref_title_touch_volume">袚褍褔薪褨褋褌褜</string>
+    <string name="pref_ambient_sound_tone_summary">袙褨写 屑\'褟泻芯谐芯 写芯 褔懈褋褌芯谐芯</string>
+    <string name="pref_switch_controls_anc_off">楔褍屑芯锌芯谐谢懈薪邪薪薪褟 鈫愨啋 袙懈屑泻薪械薪芯</string>
+    <string name="pref_switch_controls_ambient_off">小械褉械写芯胁懈褖械 鈫愨啋 袙懈屑泻薪械薪芯</string>
+    <string name="prefs_noise_control">袣芯薪褌褉芯谢褜 褕褍屑褍</string>
+    <string name="prefs_voice_detect">袙懈褟胁谢械薪薪褟 谐芯谢芯褋褍</string>
+    <string name="prefs_double_tap_edge">袩芯写胁褨泄薪械 褌芯褉泻邪薪薪褟 泻褉邪褞</string>
+    <string name="prefs_voice_detect_duration">袟邪胁械褉褕械薪薪褟 锌褨褋谢褟 褌懈褕褨 蟹邪:</string>
+    <string name="pref_voice_detect_duration_15">15 褋械泻褍薪写</string>
+    <string name="prefs_ambient_volume_right">袚褍褔薪褨褋褌褜 褋械褉械写芯胁懈褖邪 褋锌褉邪胁邪</string>
+    <string name="prefs_ambient_sound_during_call_summary">效褨褌泻芯 褔褍褌懈 胁谢邪褋薪懈泄 谐芯谢芯褋 锌褨写 褔邪褋 胁懈泻谢懈泻褨胁</string>
+    <string name="prefs_seamless_connection_switch_title">袩谢邪胁薪械 锌械褉械屑懈泻邪薪薪褟 蟹\'褦写薪邪薪薪褟</string>
+    <string name="prefs_ambient_settings_title">袩邪褉邪屑械褌褉懈 薪邪胁泻芯谢懈褕薪褜芯谐芯 蟹胁褍泻褍</string>
+    <string name="pref_title_touch_voice_assistant">袚芯谢芯褋芯胁懈泄 锌芯屑褨褔薪懈泻</string>
+    <string name="prefs_active_noise_cancelling_level_low">袧懈蟹褜泻懈泄</string>
+    <string name="prefs_noise_control_with_one_earbud">袣械褉褍胁邪薪薪褟 褕褍屑芯屑 蟹邪 写芯锌芯屑芯谐芯褞 芯写薪芯谐芯 薪邪胁褍褕薪懈泻邪</string>
+    <string name="pref_title_touch_quick_ambient">楔胁懈写泻懈泄 薪邪胁泻芯谢懈褕薪褨泄 蟹胁褍泻</string>
+    <string name="pref_title_touch_spotify">Spotify</string>
+    <string name="pref_switch_noise_control">袩械褉械屑懈泻邪薪薪褟 泻械褉褍胁邪薪薪褟 褕褍屑芯屑</string>
+    <string name="pref_title_touch_ambient">袧邪胁泻芯谢懈褕薪褨泄 蟹胁褍泻</string>
+    <string name="prefs_noise_control_with_one_earbud_summary">袛芯蟹胁芯谢懈褌懈 泻芯薪褌褉芯谢褜 褕褍屑褍 锌褨写 褔邪褋 胁懈泻芯褉懈褋褌邪薪薪褟 谢懈褕械 芯写薪芯谐芯 薪邪胁褍褕薪懈泻邪</string>
+    <string name="pref_balance">袘邪谢邪薪褋</string>
+    <string name="pref_ambient_sound_tone">孝芯薪 薪邪胁泻芯谢懈褕薪褜芯谐芯 蟹胁褍泻褍</string>
+    <string name="pref_switch_controls_anc_ambient">楔褍屑芯锌芯谐谢懈薪邪薪薪褟 鈫愨啋 小械褉械写芯胁懈褖械</string>
+    <string name="prefs_voice_detect_summary">校胁褨屑泻薪褍褌懈 薪邪胁泻芯谢懈褕薪褨泄 蟹胁褍泻 褨 邪胁褌芯屑邪褌懈褔薪械 褋褌懈褕械薪薪褟 胁褨写褌胁芯褉械薪薪褟 锌褨褋谢褟 胁懈褟胁谢械薪薪褟 谐芯谢芯褋褍</string>
+    <string name="prefs_double_tap_edge_summary">袙懈褟胁谢械薪薪褟 锌芯写胁褨泄薪芯谐芯 褌芯褉泻邪薪薪褟, 薪邪胁褨褌褜 褟泻褖芯 薪械 褌芯褉泻邪褌懈褋褟 褌邪褔锌邪写邪</string>
+    <string name="pref_voice_detect_duration_10">10 褋械泻褍薪写</string>
+    <string name="pref_voice_detect_duration_5">5 褋械泻褍薪写</string>
+    <string name="heartrate_bpm_100">100 褍写/褏胁</string>
+    <string name="prefs_heartrate_alert_experimental_title">袩芯锌械褉械写卸械薪薪褟 锌褉芯 锌褍谢褜褋 (械泻褋锌械褉懈屑械薪褌邪谢褜薪芯)</string>
+    <string name="prefs_heartrate_alert_threshold">袦械卸邪 锌芯锌械褉械写卸械薪薪褟 锌褉芯 锌褍谢褜褋</string>
+    <string name="heartrate_bpm_110">110 褍写/褏胁</string>
+    <string name="heartrate_bpm_140">140 褍写/褏胁</string>
+    <string name="heartrate_bpm_150">150 褍写/褏胁</string>
+    <string name="heartrate_bpm_105">105 褍写/褏胁</string>
+    <string name="heartrate_bpm_120">120 褍写/褏胁</string>
+    <string name="heartrate_bpm_112">112 褍写邪褉褨胁 薪邪 褏胁懈谢懈薪褍</string>
+    <string name="heartrate_bpm_130">130 褍写/褏胁</string>
+    <string name="heartrate_bpm_145">145 褍写/褏胁</string>
+    <string name="heartrate_bpm_125">125 褍写/褏胁</string>
+    <string name="heartrate_bpm_135">135 褍写/褏胁</string>
+    <string name="prefs_stress_monitoring_description">小锌芯褋褌械褉褨谐邪褌懈 蟹邪 褉褨胁械薪械屑 薪邪锌褉褍卸械薪芯褋褌褨 锌褨写 褔邪褋 胁褨写锌芯褔懈薪泻褍</string>
+    <string name="prefs_heartrate_alert_experimental_description">袘褉邪褋谢械褌 胁褨斜褉褍胁邪褌懈屑械, 泻芯谢懈 锌褍谢褜褋 锌械褉械胁懈褖懈褌褜 芯斜屑械卸械薪薪褟, 斜械蟹 褟胁薪芯褩 褎褨蟹懈褔薪芯褩 邪泻褌懈胁薪芯褋褌褨 锌褉芯褌褟谐芯屑 芯褋褌邪薪薪褨褏 10 褏胁懈谢懈薪. 笑褟 褎褍薪泻褑褨褟 械泻褋锌械褉懈屑械薪褌邪谢褜薪邪 褌邪 薪械 斜褍谢邪 写芯褋褌邪褌薪褜芯 胁懈锌褉芯斜褍胁邪薪邪.</string>
+    <string name="prefs_stress_monitoring_title">小锌芯褋褌械褉械卸械薪薪褟 蟹邪 薪邪锌褉褍卸械薪褨褋褌褞</string>
+    <string name="prefs_activity_monitoring_title">小锌芯褋褌械褉械卸械薪薪褟 蟹邪 写褨褟谢褜薪褨褋褌褞</string>
+    <string name="prefs_activity_monitoring_description">袗胁褌芯屑邪褌懈褔薪芯 蟹斜褨谢褜褕褍胁邪褌懈 褔邪褋褌芯褌褍 锌械褉械胁褨褉泻懈 锌褍谢褜褋褍, 泻芯谢懈 谐芯写懈薪薪懈泻 胁懈褟胁谢褟褦 褎褨蟹懈褔薪褨 胁锌褉邪胁懈, 褖芯斜 锌褨写胁懈褖懈褌懈 褌芯褔薪褨褋褌褜 芯褏芯锌谢械薪薪褟 锌褍谢褜褋褍.</string>
+    <string name="mi2_prefs_heart_rate_monitoring_alerts_summary">袧邪谢邪褕褌褍泄褌械 褋锌芯褋褌械褉械卸械薪薪褟 褌邪 屑械卸褨 锌芯锌械褉械写卸械薪褜 锌褉芯 锌褍谢褜褋</string>
+    <string name="mi2_prefs_heart_rate_monitoring">小锌芯褋褌械褉械卸械薪薪褟 蟹邪 锌褍谢褜褋芯屑</string>
+    <string name="mi2_prefs_heart_rate_monitoring_summary">袧邪谢邪褕褌褍泄褌械 褋锌芯褋褌械褉械卸械薪薪褟 蟹邪 锌褍谢褜褋芯屑</string>
+    <string name="pref_title_device_internet_access">袛芯蟹胁芯谢懈褌懈 写芯褋褌褍锌 写芯 褨薪褌械褉薪械褌褍</string>
+    <string name="pref_summary_device_internet_access">袛芯蟹胁芯谢懈褌懈 蟹邪褋褌芯褋褍薪泻邪屑 薪邪 褑褜芯屑褍 锌褉懈褋褌褉芯褩 写芯褋褌褍锌 写芯 褨薪褌械褉薪械褌褍</string>
+    <string name="pref_summary_device_intents">袛芯蟹胁芯谢懈褌懈 蟹邪褋褌芯褋褍薪泻邪屑 写谢褟 谐芯写懈薪薪懈泻邪 Bangle.js 薪邪写褋懈谢邪褌懈 Android Intents 褌邪 褨薪褕懈屑 蟹邪褋褌芯褋褍薪泻邪屑 薪邪 Android (薪邪锌褉懈泻谢邪写, Tasker) 薪邪写褋懈谢邪褌懈 写邪薪褨 写芯 Bangle.js 蟹 com.banglejs.uart.tx Intent. 袛谢褟 褉芯斜芯褌懈 褍 褎芯薪芯胁芯屑褍 褉械卸懈屑褨 锌芯褌褉褨斜械薪 写芯蟹胁褨谢 薪邪 锌芯泻邪蟹 锌芯胁械褉褏 褨薪褕懈褏 蟹邪褋褌芯褋褍薪泻褨胁.</string>
+    <string name="pref_title_device_intents">袛芯蟹胁芯谢懈褌懈 Intents</string>
+    <string name="pref_title_banglejs_text_bitmap">孝械泻褋褌 褍 胁懈谐谢褟写褨 褉邪褋褌褉芯胁懈褏 蟹芯斜褉邪卸械薪褜</string>
+    <string name="pref_summary_banglejs_text_bitmap">携泻褖芯 褋谢芯胁芯 薪械 胁褨写褌胁芯褉褞褦褌褜褋褟 褕褉懈褎褌芯屑 谐芯写懈薪薪懈泻邪, 锌械褉械褌胁芯褉褞胁邪褌懈 泄芯谐芯 薪邪 褉邪褋褌褉芯胁械 蟹芯斜褉邪卸械薪薪褟 胁 Gadgetbridge 褌邪 锌芯泻邪蟹褍胁邪褌懈 褉邪褋褌褉芯胁械 蟹芯斜褉邪卸械薪薪褟 薪邪 谐芯写懈薪薪懈泻褍</string>
+    <string name="pref_workout_start_on_phone_title">袙褨写褋褌械卸械薪薪褟 褎褨褌薪械褋-蟹邪褋褌芯褋褍薪泻褍</string>
+    <string name="pref_workout_start_on_phone_summary">袩芯褔懈薪邪褌懈/锌褉懈锌懈薪褟褌懈 胁褨写褋褌械卸械薪薪褟 褎褨褌薪械褋-蟹邪褋褌芯褋褍薪泻褍 薪邪 褌械谢械褎芯薪褨, 泻芯谢懈 GPS-褌褉械薪褍胁邪薪薪褟 褉芯蟹锌芯褔邪褌芯 薪邪 斜褉邪褋谢械褌褨</string>
+    <string name="pref_workout_send_gps_title">袧邪写褋懈谢邪褌懈 写邪薪褨 GPS 锌褨写 褔邪褋 褌褉械薪褍胁邪薪薪褟</string>
+    <string name="pref_workout_send_gps_summary">袧邪写褋懈谢邪褌懈 锌芯褌芯褔薪械 屑褨褋褑械锌械褉械斜褍胁邪薪薪褟 GPS 薪邪 斜褉邪褋谢械褌 锌褨写 褔邪褋 褌褉械薪褍胁邪薪薪褟</string>
+    <string name="notification_channel_gps">GPS-褋褌械卸械薪薪褟</string>
+    <string name="notification_gps_title">Gadgetbridge GPS</string>
+    <string name="notification_gps_text">袧邪写褋懈谢邪薪薪褟 屑褨褋褑械锌械褉械斜褍胁邪薪薪褟 GPS 薪邪 锌褉懈褋褌褉褨泄 %1$d</string>
+    <string name="permission_notification_listener">%1$s 锌芯褌褉械斜褍褦 写芯褋褌褍锌 写芯 褋锌芯胁褨褖械薪褜, 褖芯斜 锌芯泻邪蟹褍胁邪褌懈 褩褏 薪邪 胁邪褕芯屑褍 谐芯写懈薪薪懈泻褍, 泻芯谢懈 械泻褉邪薪 褌械谢械褎芯薪褍 胁懈屑泻薪械薪芯.
+\n
+\n孝芯褉泻薪褨褌褜褋褟 芦%2$s禄, 锌芯褌褨屑 芦%1$s禄 褌邪 胁胁褨屑泻薪褨褌褜 芦袛芯蟹胁芯谢懈褌懈 写芯褋褌褍锌 写芯 褋锌芯胁褨褖械薪褜禄, 锌芯褌褨屑 褌芯褉泻薪褨褌褜褋褟 芦袧邪蟹邪写禄, 褖芯斜 锌芯胁械褉薪褍褌懈褋褟 写芯 %1$s.</string>
+    <string name="permission_notification_policy_access">%1$s 锌芯褌褉械斜褍褦 写芯褋褌褍锌 写芯 薪邪谢邪褕褌褍胁邪薪褜 芦袧械 褌褍褉斜褍胁邪褌懈禄, 褖芯斜 写芯褌褉懈屑褍胁邪褌懈褋褟 褩褏 薪邪 胁邪褕芯屑褍 谐芯写懈薪薪懈泻褍, 泻芯谢懈 械泻褉邪薪 褌械谢械褎芯薪褍 胁懈屑泻薪械薪芯.
+\n
+\n孝芯褉泻薪褨褌褜褋褟 芦%2$s禄, 锌芯褌褨屑 芦%1$s禄 褌邪 胁胁褨屑泻薪褨褌褜 芦袛芯蟹胁芯谢懈褌懈 薪械 褌褍褉斜褍胁邪褌懈禄, 锌芯褌褨屑 薪邪褌懈褋薪褨褌褜 芦袧邪蟹邪写禄, 褖芯斜 锌芯胁械褉薪褍褌懈褋褟 写芯 %1$s.</string>
+    <string name="pref_device_action_phone_gps_location_listener_stop">袟褍锌懈薪泻邪 胁褨写褋褌械卸褍胁邪褔邪 GPS-褉芯蟹褌邪褕褍胁邪薪薪褟</string>
+    <string name="discovery_scanning_intensity_warning">携泻褖芯 胁懈 斜邪褔懈褌械 蟹邪屑芯褉芯卸褍胁邪薪薪褟 邪斜芯 薪械褋锌褉懈泄薪褟褌褌褟, 褋锌褉芯斜褍泄褌械 胁褋褌邪薪芯胁懈褌懈 薪懈卸褔懈泄 褉褨胁械薪褜 褨薪褌械薪褋懈胁薪芯褋褌褨 褋泻邪薪褍胁邪薪薪褟. 携泻褖芯 锌褉懈褋褌褉褨泄 薪械 胁懈褟胁谢械薪芯, 褋锌褉芯斜褍泄褌械 胁褋褌邪薪芯胁懈褌懈 褨薪褌械薪褋懈胁薪褨褋褌褜 褋泻邪薪褍胁邪薪薪褟 薪邪 胁懈褖懈泄 褉褨胁械薪褜.</string>
+    <string name="discovery_scanning_intensity">袉薪褌械薪褋懈胁薪褨褋褌褜 褋泻邪薪褍胁邪薪薪褟</string>
+    <string name="portuguese_br">袩芯褉褌褍谐邪谢褜褋褜泻邪 (袘褉邪蟹懈谢褨褟)</string>
+    <string name="quick_alarm">楔胁懈写泻懈泄 斜褍写懈谢褜薪懈泻</string>
+    <string name="portuguese_pt">袩芯褉褌褍谐邪谢褜褋褜泻邪 (袩芯褉褌褍谐邪谢褨褟)</string>
+    <string name="quick_alarm_description">袘褍写懈谢褜薪懈泻 蟹 胁褨写卸械褌邪</string>
+    <string name="pref_title_banglejs_webview_url">URL-邪写褉械褋邪 蟹邪胁邪薪褌邪卸褍胁邪褔邪 蟹邪褋褌芯褋褍薪泻褍</string>
+    <string name="pref_summary_banglejs_webview_url">携泻褖芯 胁懈 褏芯褔械褌械 胁懈泻芯褉懈褋褌芯胁褍胁邪褌懈 胁谢邪褋薪懈泄 蟹邪胁邪薪褌邪卸褍胁邪褔 蟹邪褋褌芯褋褍薪泻褨胁, 胁泻邪卸褨褌褜 胁邪褕褍 URL-邪写褉械褋褍 https://鈥�/android.html 褌褍褌. 袙 褨薪褕芯屑褍 胁懈锌邪写泻褍 蟹邪谢懈褕褌械 锌芯褉芯卸薪褨屑 写谢褟 https://banglejs.com/apps</string>
+    <string name="permission_location">%1$s 锌芯褌褉褨斜褍褦 写芯褋褌褍锌 写芯 胁邪褕芯谐芯 屑褨褋褑械锌械褉械斜褍胁邪薪薪褟 褍 褎芯薪芯胁芯屑褍 褉械卸懈屑褨, 褖芯斜 屑邪褌懈 屑芯卸谢懈胁褨褋褌褜 蟹邪谢懈褕邪褌懈褋褟 蟹\'褦写薪邪薪懈屑 蟹 谐芯写懈薪薪懈泻芯屑, 薪邪胁褨褌褜 泻芯谢懈 械泻褉邪薪 胁懈屑泻薪械薪芯. \n \n袧邪褌懈褋薪褨褌褜 芦%2$s禄, 褖芯斜 锌芯谐芯写懈褌懈褋褟.</string>
+    <string name="info_no_devices_connected">袧械屑邪褦 锌褨写\'褦写薪邪薪懈褏 锌褉懈褋褌褉芯褩胁</string>
+    <string name="info_connected_count">袩褨写\'褦写薪邪薪芯 锌褉懈褋褌褉芯褩胁: %d</string>
+    <string name="bengali">袘械薪谐邪谢褜褋褜泻邪</string>
+    <string name="persian">袩械褉褋褜泻邪</string>
+    <string name="ukranian">校泻褉邪褩薪褋褜泻邪</string>
+    <string name="czech">效械褋褜泻邪</string>
+    <string name="lithuanian">袥懈褌芯胁褋褜泻邪</string>
+    <string name="icelandic">袉褋谢邪薪写褋褜泻邪</string>
+    <string name="estonian">袝褋褌芯薪褋褜泻邪</string>
+    <string name="extended_ascii">袪芯蟹褕懈褉械薪懈泄 ASCII</string>
+    <string name="scandinavian">小泻邪薪写懈薪邪胁褋褜泻邪</string>
+    <string name="controlcenter_set_preferences">校褋褌邪薪芯胁懈褌懈 锌邪褉邪屑械褌褉懈</string>
+    <string name="controlcenter_toggle_details">袩械褉械屑泻薪褍褌懈 锌芯写褉芯斜懈褑褨</string>
+    <string name="pref_explanation_authkey">袛械褟泻褨 锌褉懈褋褌褉芯褩 锌芯褌褉械斜褍褞褌褜 褋锌械褑褨邪谢褜薪懈泄 泻谢褞褔 褋褌胁芯褉械薪薪褟 锌邪褉懈 写谢褟 锌械褉褕芯褩 褨薪褨褑褨邪谢褨蟹邪褑褨褩 锌褉懈褋褌褉芯褞. 孝芯褉泻薪褨褌褜褋褟 褌褍褌, 褖芯斜 锌械褉械谐谢褟薪褍褌懈 锌芯写褉芯斜懈褑褨 褍 胁褨泻褨.</string>
+    <string name="pref_explanation_authkey_new_protocol">携泻褖芯 胁懈 芯褌褉懈屑邪谢懈 锌芯胁褨写芯屑谢械薪薪褟 \"袨薪芯胁褨褌褜 蟹邪褋褌芯褋褍薪芯泻 写芯 薪邪泄薪芯胁褨褕芯褩 胁械褉褋褨褩\" 薪邪 斜褉邪褋谢械褌褨, 锌械褉械泻芯薪邪泄褌械褋褟, 褖芯 锌械褉械胁褨褉懈谢懈 \"袧芯胁懈泄 锌褉芯褌芯泻芯谢 邪胁褌械薪褌懈褎褨泻邪褑褨褩\" 胁懈褖械. 孝芯褉泻薪褨褌褜褋褟 褌褍褌, 褖芯斜 锌械褉械谐谢褟薪褍褌懈 锌芯写褉芯斜懈褑褨 褍 胁褨泻褨.</string>
+    <string name="controlcenter_connected_fraction">袩褨写\'褦写薪邪薪芯: %d/%d</string>
+    <string name="connection_over_bt_classic">袟\'褦写薪邪薪薪褟 褔械褉械蟹 泻谢邪褋懈褔薪懈泄 Bluetooth</string>
+    <string name="controlcenter_add_new_folder">袛芯写邪褌懈 薪芯胁褍 褌械泻褍</string>
+    <string name="error_setting_parent_folder">袩芯屑懈谢泻邪 薪邪谢邪褕褌褍胁邪薪薪褟 斜邪褌褜泻褨胁褋褜泻芯褩 褌械泻懈: %s</string>
+    <string name="autoconnect_from_device_summary">校褋褌邪薪芯胁谢褞胁邪褌懈 蟹\'褦写薪邪薪薪褟, 泻芯谢懈 泄芯谐芯 褨薪褨褑褨褞褦 锌褉懈褋褌褉褨泄, 薪邪锌褉懈泻谢邪写 薪邪胁褍褕薪懈泻懈</string>
+    <string name="pref_title_notification_delay_calls">袟邪褌褉懈屑泻邪 褋锌芯胁褨褖械薪薪褟 锌褉芯 胁懈泻谢懈泻</string>
+    <string name="pref_summary_notification_delay_calls">袟邪褌褉懈屑泻邪 写芯 薪邪写褋懈谢邪薪薪褟 褋锌芯胁褨褖械薪褜 锌褉芯 胁褏褨写薪懈泄 胁懈泻谢懈泻 薪邪 锌褉懈褋褌褉褨泄, 褍 褋械泻褍薪写邪褏.</string>
+    <string name="prefs_password">袩邪褉芯谢褜</string>
+    <string name="prefs_password_summary">袘谢芯泻褍胁邪褌懈 斜褉邪褋谢械褌 蟹邪 写芯锌芯屑芯谐芯褞 锌邪褉芯谢褟 锌褨褋谢褟 蟹薪褟褌褌褟 蟹 蟹邪锌\'褟褋褌褟</string>
+    <string name="prefs_password_enabled">袩邪褉芯谢褜 褍胁褨屑泻薪械薪芯</string>
+    <string name="prefs_password_6_digits_0_to_9_summary">袩邪褉芯谢褜 锌芯胁懈薪械薪 褋泻谢邪写邪褌懈褋褟 蟹 6 褑懈褎褉 褌邪 屑褨褋褌懈褌懈 谢懈褕械 褑懈褎褉懈</string>
+    <string name="prefs_password_4_digits_1_to_4_summary">袩邪褉芯谢褜 锌芯胁懈薪械薪 褋泻谢邪写邪褌懈褋褟 蟹 4 褑懈褎褉 褌邪 屑褨褋褌懈褌懈 褑懈褎褉懈 胁褨写 1 写芯 4</string>
+    <string name="pref_blacklist_calendars_summary">袣邪谢械薪写邪褉褨 蟹 褔芯褉薪芯谐芯 褋锌懈褋泻褍 薪械 斜褍写褍褌褜 褋懈薪褏褉芯薪褨蟹褍胁邪褌懈褋褟 蟹 锌褉懈褋褌褉芯褦屑</string>
+    <string name="controlcenter_set_parent_folder">校褋褌邪薪芯胁懈褌懈 斜邪褌褜泻褨胁褋褜泻褍 褌械泻褍</string>
+    <string name="error_deleting_device">袩芯屑懈谢泻邪 胁懈写邪谢械薪薪褟 锌褉懈褋褌褉芯褞: %s</string>
+    <string name="controlcenter_folder_name">袧邪蟹胁邪 褌械泻懈:</string>
+    <string name="controlcenter_unset_folder">小泻邪褋褍胁邪褌懈 薪邪谢邪褕褌褍胁邪薪薪褟 褌械泻懈</string>
+    <string name="controlcenter_set_folder_title">校褋褌邪薪芯胁懈褌懈 邪斜芯 褋褌胁芯褉懈褌懈 薪芯胁褍 褌械泻褍</string>
+    <string name="auto_reconnect_ble_title">袩芯胁褌芯褉薪械 邪胁褌芯锌褨写\'褦写薪邪薪薪褟 写芯 锌褉懈褋褌褉芯褞</string>
+    <string name="auto_reconnect_ble_summary">袩褉芯邪泻褌懈胁薪芯 锌械褉褨芯写懈褔薪芯 薪邪屑邪谐邪褌懈褋褟 锌褨写\'褦写薪褍胁邪褌懈褋褟 写芯 锌褉懈褋褌褉芯褞</string>
+    <string name="connection_over_ble">袟\'褦写薪邪薪薪褟 褔械褉械蟹 BLE</string>
+    <string name="autoconnect_from_device_title">袩褨写\'褦写薪褍胁邪褌懈褋褟 锌褨褋谢褟 蟹\'褦写薪邪薪薪褟 蟹 锌褉懈褋褌褉芯褞</string>
+    <string name="watchface_dialog_widget_timezone_duration">孝褉懈胁邪谢褨褋褌褜 锌芯泻邪蟹褍 谐芯写懈薪薪懈泻邪 (胁 褋械泻褍薪写邪褏)</string>
+    <string name="open_fw_installer_warning_title">袩芯锌械褉械写卸械薪薪褟</string>
+    <string name="open_fw_installer_pick_file">袙懈斜褉邪褌懈 褎邪泄谢</string>
+    <string name="open_fw_installer_connect_minimum_one_device">袩褨写\'褦写薪邪泄褌械 袩袪袠袧袗袡袦袧袉 袨袛袠袧 锌褉懈褋褌褉褨泄, 薪邪 褟泻懈泄 胁懈 褏芯褔械褌械 薪邪写褨褋谢邪褌懈 褎邪泄谢.</string>
+    <string name="open_fw_installer_info_text">袙褋褌邪薪芯胁谢褞胁邪褔 屑褨泻褉芯锌褉芯谐褉邪屑/褑懈褎械褉斜谢邪褌褨胁/蟹邪褋褌芯褋褍薪泻褨胁/褎邪泄谢褨胁 写邪褦 蟹屑芯谐褍 蟹邪胁邪薪褌邪卸褍胁邪褌懈/胁褋褌邪薪芯胁谢褞胁邪褌懈 锌褨写褌褉懈屑褍胁邪薪褨 褎邪泄谢懈 (屑褨泻褉芯锌褉芯谐褉邪屑懈, 褑懈褎械褉斜谢邪褌懈, 蟹邪褋褌芯褋褍薪泻懈, GPS, 褉械褋褍褉褋懈 褌邪 褕褉懈褎褌懈...) 薪邪 锌褉懈褋褌褉褨泄. 袛芯写邪褌泻芯胁褍 褨薪褎芯褉屑邪褑褨褞 写懈胁褨褌褜褋褟 褍 胁褨泻褨: https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Firmware-Update</string>
+    <string name="open_fw_installer_warning_text">笑褟 褎褍薪泻褑褨褟 屑芯卸械 蟹谢邪屑邪褌懈 胁邪褕 锌褉懈褋褌褉褨泄. 袨写薪邪泻 褑褜芯谐芯 薪褨泻芯谢懈 薪械 褌褉邪锌谢褟谢芯褋褟 褍 卸芯写薪芯谐芯 蟹 褉芯蟹褉芯斜薪懈泻褨胁 锌褨写 褔邪褋 胁褋褌邪薪芯胁谢械薪薪褟, 邪谢械 锌邪屑\'褟褌邪泄褌械, 褖芯 胁懈 褉芯斜懈褌械 褑械 薪邪 胁谢邪褋薪懈泄 褉懈蟹懈泻.</string>
+    <string name="open_fw_installer_getting_files_title">袨褌褉懈屑邪薪薪褟 褎邪泄谢褍 屑褨泻褉芯锌褉芯谐褉邪屑懈/蟹邪褋褌芯褋褍薪泻褍</string>
+    <string name="open_fw_installer_getting_files_text">袨褋泻褨谢褜泻懈 屑懈 屑芯卸械屑芯 薪械 锌芯褕懈褉褞胁邪褌懈 褎邪泄谢懈 屑褨泻褉芯锌褉芯谐褉邪屑, 胁邪屑 写芯胁械写械褌褜褋褟 褋邪屑芯褋褌褨泄薪芯 蟹薪邪泄褌懈 褑褨 褎邪泄谢懈. 笑械 芯蟹薪邪褔邪褦, 褖芯 胁邪屑 锌芯褌褉褨斜薪芯 斜褍写械 褕褍泻邪褌懈 褎邪泄谢懈 胁 apk-褎邪泄谢邪褏, 胁 屑械褉械卸褨, 薪邪 褎芯褉褍屑邪褏, 薪邪 Amazfitwatchfaces (写谢褟 锌褉懈褋褌褉芯褩胁 Miband/Amazfit) 褌芯褖芯.</string>
+    <string name="open_fw_installer_info_text_title">袙褋褌邪薪芯胁谢褞胁邪褔 褎邪泄谢褨胁</string>
+    <string name="open_fw_installer_select_file">袙懈斜械褉褨褌褜 褎邪泄谢, 褟泻懈泄 胁懈 褏芯褔械褌械 胁懈胁邪薪褌邪卸懈褌懈 薪邪 锌褉懈褋褌褉褨泄: %s</string>
+    <string name="open_fw_installer_connect_maximum_one_device">袩褨写\'褦写薪邪泄褌械 袥袠楔袝 袨袛袠袧 锌褉懈褋褌褉褨泄, 薪邪 褟泻懈泄 胁懈 褏芯褔械褌械 薪邪写褨褋谢邪褌懈 褎邪泄谢.</string>
+    <string name="open_fw_installer_ensure_device_connected">袩械褉械泻芯薪邪泄褌械褋褟, 褖芯 锌褉懈褋褌褉褨泄 %s 锌褨写\'褦写薪邪薪芯</string>
+    <string name="appmanager_item_outdated">(蟹邪褋褌邪褉褨谢懈泄)</string>
+    <string name="appmanager_app_share">袩芯写褨谢懈褌懈褋褟</string>
+    <string name="watchface_dialog_widget_cat_position">袩芯蟹懈褑褨褟</string>
+    <string name="pref_title_banglejs_txt_bitmap_size">袪芯蟹屑褨褉 褉邪褋褌褉芯胁芯谐芯 蟹芯斜褉邪卸械薪薪褟 褌械泻褋褌褍</string>
+    <string name="hybridhr_widget_bg_thin_circle">孝芯薪泻械 泻芯谢芯</string>
+    <string name="pref_summary_banglejs_txt_bitmap_size">袪芯蟹屑褨褉 写谢褟 胁褨写褌胁芯褉械薪薪褟 褉邪褋褌褉芯胁芯谐芯 褌械泻褋褌褍</string>
+    <string name="watchface_dialog_widget_cat_custom_widget">袣芯褉懈褋褌褍胁邪谢褜薪懈褑褜泻懈泄 胁褨写卸械褌</string>
+    <string name="watchface_dialog_widget_cat_2nd_tz_widget">袙褨写卸械褌 2-谐芯 褔邪褋芯胁芯谐芯 锌芯褟褋褍</string>
+    <string name="hybridhr_widget_bg_dashed_circle">袩褍薪泻褌懈褉薪械 泻芯谢芯</string>
+    <string name="watchface_dialog_widget_background">孝谢芯</string>
+    <string name="hybridhr_widget_bg_double_circle">袩芯写胁褨泄薪械 泻芯谢芯</string>
+    <string name="watchface_dialog_widget_cat_generic">袟邪谐邪谢褜薪懈泄</string>
+    <string name="watchface_dialog_title_widget">袧邪谢邪褕褌褍胁邪薪薪褟 胁褨写卸械褌邪</string>
+    <string name="watchface_setting_title_button_toggle_widgets">袩械褉械屑泻薪褍褌懈 胁褨写卸械褌懈</string>
+    <string name="watchface_setting_button_toggle_widgets">袩械褉械屑泻薪褍褌懈 胁褨写卸械褌懈</string>
+    <string name="note">袩褉懈屑褨褌泻邪</string>
+    <string name="share_log_not_enabled_message">小锌芯褔邪褌泻褍 锌芯褌褉褨斜薪芯 褍胁褨屑泻薪褍褌懈 胁械写械薪薪褟 卸褍褉薪邪谢褍 胁 薪邪谢邪褕褌褍胁邪薪薪褟褏 (袧邪谢邪褕褌褍胁邪薪薪褟 - 袟邪锌懈褋褍胁邪褌懈 褎邪泄谢懈 卸褍褉薪邪谢褍)</string>
+    <string name="steps_streaks">校褋锌褨褕薪懈褏 写薪褨胁 锌褨写褉褟写</string>
+    <string name="step_streak_total">校褋褜芯谐芯</string>
+    <string name="steps_streaks_total_steps">校褋褜芯谐芯
+\n泻褉芯泻褨胁</string>
+    <string name="steps_streaks_streak_days">袛薪褨胁
+\n袩芯褋锌褨谢褜</string>
+    <string name="steps_streaks_average_steps">校 褋械褉械写薪褜芯屑褍
+\n泻褉芯泻褨胁</string>
+    <string name="steps_streaks_since_date">袟 %s</string>
+    <string name="step_streak_average_steps_hint">小械褉械写薪褟 泻褨谢褜泻褨褋褌褜 泻褉芯泻褨胁 蟹邪 写械薪褜 褋械褉褨褩</string>
+    <string name="steps_streaks_total_steps_hint_totals">袟邪谐邪谢褜薪邪 泻褨谢褜泻褨褋褌褜 蟹邪锌懈褋邪薪懈褏 泻褉芯泻褨胁 蟹邪 胁械褋褜 褔邪褋</string>
+    <string name="steps_streaks_total_days_hint_totals">袙褨写褋芯褌芯泻 写薪褨胁 蟹 写芯褋褟谐薪褍褌芯褞 屑械褌芯褞 胁褨写薪芯褋薪芯 写芯 胁褋褨褏 写薪褨胁 蟹 泻褉芯泻邪屑懈</string>
+    <string name="steps_streaks_achievement_rate">袨褑褨薪泻邪
+\n写芯褋褟谐薪械薪薪褟</string>
+    <string name="steps_streaks_hint">小械褉褨褟 写薪褨胁 锌芯褋锌褨谢褜 蟹 写芯褋褟谐薪械薪薪褟屑 屑械褌懈 泻褨谢褜泻芯褋褌褨 泻褉芯泻褨胁</string>
+    <string name="steps_streaks_total_steps_hint">袟邪谐邪谢褜薪邪 泻褨谢褜泻褨褋褌褜 泻褉芯泻褨胁 褍 胁褋褨泄 褋械褉褨褩</string>
+    <string name="step_streak_ongoing">袩芯褌芯褔薪邪</string>
+    <string name="step_streak_longest">袧邪泄褌褉懈胁邪谢褨褕邪</string>
+    <string name="step_streak_days_hint">袣褨谢褜泻褨褋褌褜 写薪褨胁 锌芯褋锌褨谢褜 蟹 写芯褋褟谐薪械薪薪褟屑 屑械褌懈 泻褨谢褜泻芯褋褌褨 泻褉芯泻褨胁</string>
+    <string name="steps_streaks_total_steps_average_hint">袟邪谐邪谢褜薪懈泄 褋械褉械写薪褨泄 锌芯泻邪蟹薪懈泻 %d 泻褉芯泻褨胁 薪邪 写械薪褜</string>
+    <string name="watchface_setting_title_custom_events">袣芯褉懈褋褌褍胁邪褑褜泻褨 锌芯写褨褩</string>
+    <string name="watchface_setting_button_toggle_backlight">校胁褨屑泻薪褍褌懈 锌褨写褋胁褨褌泻褍</string>
+    <string name="controlcenter_get_heartrate_measurement">袙懈屑褨褉褟褌懈 锌褍谢褜褋</string>
+    <string name="pref_write_logfiles_not_available">袧械 胁写邪谢芯褋褟 褨薪褨褑褨邪谢褨蟹褍胁邪褌懈 褎邪泄谢 卸褍褉薪邪谢褍, 蟹邪锌懈褋 褎邪泄谢褨胁 卸褍褉薪邪谢褍 薪邪褉邪蟹褨 薪械写芯褋褌褍锌薪懈泄. 袩械褉械蟹邪锌褍褋褌褨褌褜 蟹邪褋褌芯褋褍薪芯泻, 褖芯斜 蟹薪芯胁褍 褋锌褉芯斜褍胁邪褌懈 褨薪褨褑褨邪谢褨蟹褍胁邪褌懈 褎邪泄谢懈 卸褍褉薪邪谢褍.</string>
+    <string name="devicetype_binary_sensor">袘褨薪邪褉薪懈泄 写邪褌褔懈泻</string>
+    <string name="gadgetbridge_running_banglejs_nightly">Nightly Bangle.js 蟹邪锌褍褖械薪芯</string>
+    <string name="application_name_banglejs_nightly">Bangle.js Gadgetbridge (Nightly)</string>
+    <string name="title_activity_controlcenter_banglejs_nightly">Bangle.js Gadgetbridge (Nightly)</string>
+    <string name="about_activity_title_banglejs_nightly">袩褉芯 Bangle.js Gadgetbridge (Nightly)</string>
+    <string name="about_description_banglejs_nightly">Android-蟹邪褋褌芯褋褍薪芯泻 写谢褟 Bangle.js 褋褌胁芯褉械薪懈泄 薪邪 芯褋薪芯胁褨 锌褉芯褦泻褌褍 Gadgetbridge 蟹 写芯写邪褌泻芯胁懈屑 写芯褋褌褍锌芯屑 写芯 褨薪褌械褉薪械褌褍.
+\n
+\n效械褉械蟹 锌芯谢褨褌懈泻褍 Google Play 袦邪褉泻械褌褍, 屑懈 薪械 屑芯卸械屑芯 褉芯蟹屑褨褖褍胁邪褌懈 锌芯褋懈谢邪薪薪褟 薪邪 胁薪械褋泻懈 胁 褋邪屑芯屑褍 蟹邪褋褌芯褋褍薪泻褍, 邪谢械 褟泻褖芯 胁褨薪 胁邪屑 锌芯写芯斜邪褦褌褜褋褟, 褉芯蟹谐谢褟薪褜褌械 屑芯卸谢懈胁褨褋褌褜 锌褨写褌褉懈屑邪褌懈 薪邪褋 褔械褉械蟹 写芯屑邪褕薪褞 褋褌芯褉褨薪泻褍 Gadgetbridge 薪懈卸褔械.</string>
+    <string name="prefs_hourly_chime">袛蟹胁褨薪 褖芯谐芯写懈薪懈</string>
+    <string name="prefs_hourly_chime_summary">袚芯写懈薪薪懈泻 锌芯写邪胁邪褌懈屑械 蟹胁褍泻芯胁懈泄 褋懈谐薪邪谢 褉邪蟹 薪邪 谐芯写懈薪褍</string>
+    <string name="step_streaks_achievements_sharing_title">袛芯褋褟谐薪械薪薪褟 泻褨谢褜泻芯褋褌褨 泻褉芯泻褨胁</string>
+    <string name="step_streaks_achievements_sharing_message">袦芯褩 褖芯写械薪薪褨 写芯褋褟谐薪械薪薪褟 泻褨谢褜泻芯褋褌褨 泻褉芯泻褨胁!</string>
+    <string name="permission_display_over_other_apps">%1$s 锌芯褌褉械斜褍褦 写芯褋褌褍锌 薪邪 锌芯泻邪蟹 锌芯胁械褉褏 褨薪褕懈褏 蟹邪褋褌芯褋褍薪泻褨胁, 褖芯斜 写芯蟹胁芯谢懈褌懈 谐芯写懈薪薪懈泻邪屑 Bangle.js 锌芯褔懈薪邪褌懈 写褨褟谢褜薪褨褋褌褜 褔械褉械蟹 Intents, 泻芯谢懈 %1$s 锌械褉械斜褍胁邪褦 褍 褎芯薪芯胁芯屑褍 褉械卸懈屑褨.
+\n
+\n笑械 屑芯卸械 斜褍褌懈 胁懈泻芯褉懈褋褌邪薪芯, 褖芯斜 蟹邪锌褍褋褌懈褌懈 蟹邪褋褌芯褋褍薪芯泻 写谢褟 锌褉芯褋谢褍褏芯胁褍胁邪薪薪褟 屑褍蟹懈泻懈 褌邪 胁褨写褌胁芯褉械薪薪褟 锌褨褋薪褨, 邪 褌邪泻芯卸 写谢褟 斜邪谐邪褌褜芯褏 褨薪褕懈褏 褉械褔械泄.
+\n
+\n孝芯褉泻薪褨褌褜褋褟 芦%2$s禄, 锌芯褌褨屑 芦%1$s禄 褌邪 胁胁褨屑泻薪褨褌褜 芦袛芯蟹胁芯谢懈褌懈 胁褨写芯斜褉邪卸械薪薪褟 锌芯胁械褉褏 褨薪褕懈褏 蟹邪褋褌芯褋褍薪泻褨胁禄, 锌芯褌褨屑 薪邪褌懈褋薪褨褌褜 芦袧邪蟹邪写禄, 褖芯斜 锌芯胁械褉薪褍褌懈褋褟 写芯 %1$s.
+\n
+\n些芯斜 %1$s 薪械 蟹邪锌懈褌褍胁邪胁 写芯蟹胁芯谢褨胁, 锌械褉械泄写褨褌褜 写芯 芦袧邪谢邪褕褌褍胁邪薪薪褟禄 褌邪 锌褉懈斜械褉褨褌褜 锌芯蟹薪邪褔泻褍 蟹 芦袩械褉械胁褨褉褟褌懈 褋褌邪薪 写芯蟹胁芯谢褨胁禄.
+\n
+\n袩械褉械泻芯薪邪泄褌械褋褟, 褖芯 胁懈 薪邪写邪谢懈 写芯蟹胁芯谢懈 写谢褟 %1$s, 薪械芯斜褏褨写薪褨 写谢褟 薪邪谢械卸薪芯褩 褉芯斜芯褌懈.</string>
+    <string name="pref_summary_canned_replies">袙褨写锌芯胁褨写邪泄褌械 蟹 谐芯写懈薪薪懈泻邪 蟹邪 写芯锌芯屑芯谐芯褞 蟹邪蟹写邪谢械谐褨写褜 胁褋褌邪薪芯胁谢械薪懈褏 锌芯胁褨写芯屑谢械薪褜</string>
+    <string name="pref_title_screen_on_on_notifications">袙屑懈泻邪褌懈 械泻褉邪薪 锌褉懈 芯褌褉懈屑邪薪薪褨 褋锌芯胁褨褖械薪褜</string>
+    <string name="pref_summary_screen_on_on_notifications">袙屑懈泻邪褌懈 械泻褉邪薪 斜褉邪褋谢械褌邪, 泻芯谢懈 薪邪写褏芯写懈褌褜 褋锌芯胁褨褖械薪薪褟</string>
+    <string name="heartrate_bpm_40">40 褍写/褏胁</string>
+    <string name="heartrate_bpm_50">50 褍写/褏胁</string>
+    <string name="heartrate_bpm_115">115 褍写/褏胁</string>
+    <string name="spo2_perc_80">80%</string>
+    <string name="spo2_perc_85">85%</string>
+    <string name="spo2_perc_90">90%</string>
+    <string name="spo2_off">袙懈屑泻薪械薪芯</string>
+    <string name="prefs_relaxation_reminder_title">袧邪谐邪写褍胁邪薪薪褟 锌褉芯 胁褨写锌芯褔懈薪芯泻</string>
+    <string name="prefs_spo2_monitoring_title">小锌芯褋褌械褉械卸械薪薪褟 蟹邪 褉褨胁薪械屑 泻懈褋薪褞 褍 泻褉芯胁褨</string>
+    <string name="prefs_spo2_monitoring_description">袗胁褌芯褋锌芯褋褌械褉械卸械薪薪褟 蟹邪 褉褨胁薪械屑 泻懈褋薪褞 胁 泻褉芯胁褨 锌褉芯褌褟谐芯屑 写薪褟</string>
+    <string name="seconds_6">6 褋械泻褍薪写</string>
+    <string name="seconds_7">7 褋械泻褍薪写</string>
+    <string name="seconds_8">8 褋械泻褍薪写</string>
+    <string name="seconds_9">9 褋械泻褍薪写</string>
+    <string name="seconds_11">11 褋械泻褍薪写</string>
+    <string name="seconds_12">12 褋械泻褍薪写</string>
+    <string name="prefs_screen_timeout">效邪褋 芯褔褨泻褍胁邪薪薪褟 械泻褉邪薪邪</string>
+    <string name="devicetype_miband7">Xiaomi Smart Band 7</string>
+    <string name="pref_header_heartrate_sleep">小芯薪</string>
+    <string name="pref_header_heartrate_allday">笑褨谢芯写芯斜芯胁械 褋锌芯褋褌械褉械卸械薪薪褟</string>
+    <string name="pref_header_stress">袧邪锌褉褍卸械薪褨褋褌褜</string>
+    <string name="pref_header_spo2">袣懈褋械薪褜 褍 泻褉芯胁褨</string>
+    <string name="pref_screen_brightness">携褋泻褉邪胁褨褋褌褜 械泻褉邪薪邪</string>
+    <string name="pref_header_heartrate_alerts">袩芯锌械褉械写卸械薪薪褟 锌褉芯 锌褍谢褜褋</string>
+    <string name="fw_upgrade_notice_miband7">袙懈 蟹斜懈褉邪褦褌械褋褟 胁褋褌邪薪芯胁懈褌懈 屑褨泻褉芯锌褉芯谐褉邪屑褍 %s 薪邪 胁邪褕 Xiaomi Smart Band 7.
+\n
+\n袩褨褋谢褟 胁褋褌邪薪芯胁谢械薪薪褟 褎邪泄谢褍 .zip 胁邪褕 谐芯写懈薪薪懈泻 锌械褉械蟹邪胁邪薪褌邪卸懈褌褜褋褟.
+\n
+\n袩袪袨袛袨袙袞校袡孝袝 袧袗 袙袥袗小袧袠袡 袪袠袟袠袣!</string>
+    <string name="pref_header_connection">袟\'褦写薪邪薪薪褟</string>
+    <string name="pref_header_calendar">袣邪谢械薪写邪褉</string>
+    <string name="pref_header_health">袟写芯褉芯胁\'褟</string>
+    <string name="pref_header_time">效邪褋</string>
+    <string name="pref_header_workout">孝褉械薪褍胁邪薪薪褟</string>
+    <string name="pref_header_display">袛懈褋锌谢械泄</string>
+    <string name="pref_cache_weather_summary">袩芯谐芯写薪褨 写邪薪褨 泻械褕褍胁邪褌懈屑褍褌褜褋褟 蟹邪 泻芯卸薪芯谐芯 锌械褉械蟹邪锌褍褋泻褍 蟹邪褋褌芯褋褍薪泻褍.</string>
+    <string name="mi2_dnd_always">袟邪胁卸写懈</string>
+    <string name="pref_cache_weather">袣械褕褍胁邪褌懈 锌芯谐芯写薪褨 写邪薪褨</string>
+    <string name="heartrate_bpm_45">45 褍写/褏胁</string>
+    <string name="prefs_relaxation_reminder_description">袘褉邪褋谢械褌 胁褨斜褉褍胁邪褌懈屑械, 褖芯斜 锌芯胁褨写芯屑懈褌懈 胁邪褋, 褟泻褖芯 蟹薪邪褔械薪薪褟 薪邪锌褉褍卸械薪芯褋褌褨 锌芯薪邪写 80</string>
+    <string name="prefs_spo2_alert_threshold">袦械卸邪 锌芯锌械褉械写卸械薪薪褟 锌褉芯 SPO2</string>
+    <string name="seconds_14">14 褋械泻褍薪写</string>
+    <string name="seconds_15">15 褋械泻褍薪写</string>
+    <string name="pref_sleep_breathing_quality_monitoring">小锌芯褋褌械褉械卸械薪薪褟 蟹邪 褟泻褨褋褌褞 写懈褏邪薪薪褟 褍胁褨 褋薪褨</string>
+    <string name="seconds_13">13 褋械泻褍薪写</string>
+    <string name="prefs_always_on_display">袟邪胁卸写懈 薪邪 械泻褉邪薪褨</string>
+    <string name="menuitem_female_health">袞褨薪芯褔械 蟹写芯褉芯胁\'褟</string>
+    <string name="prefs_always_on_display_summary">袟邪谢懈褕邪褌懈 写懈褋锌谢械泄 斜褉邪褋谢械褌邪 蟹邪胁卸写懈 褍胁褨屑泻薪械薪懈屑</string>
+    <string name="smart">袪芯蟹褍屑薪芯</string>
+    <string name="prefs_heartrate_alert_high_threshold">袦械卸邪 锌芯锌械褉械写卸械薪薪褟 锌褉芯 胁懈褋芯泻懈泄 锌褍谢褜褋</string>
+    <string name="prefs_heartrate_alert_low_threshold">袦械卸邪 锌芯锌械褉械写卸械薪薪褟 锌褉芯 薪懈蟹褜泻懈泄 锌褍谢褜褋</string>
+    <string name="menuitem_countdown">孝邪泄屑械褉</string>
+    <string name="menuitem_workout_history">袉褋褌芯褉褨褟 褌褉械薪褍胁邪薪褜</string>
+    <string name="menuitem_workout_status">小褌邪薪 褌褉械薪褍胁邪薪薪褟</string>
+    <string name="menuitem_personal_activity_intelligence">袩械褉褋芯薪邪谢褜薪懈泄 褨薪写械泻褋 邪泻褌懈胁薪芯褋褌褨 (PAI)</string>
+    <string name="pref_title_notifications_ignore_low_priority">袧械褏褌褍胁邪褌懈 褋锌芯胁褨褖械薪薪褟屑懈 蟹 薪懈蟹褜泻懈屑 锌褉褨芯褉懈褌械褌芯屑</string>
+    <string name="pref_summary_notifications_ignore_low_priority">袧械 薪邪写褋懈谢邪褌懈 薪邪 谐芯写懈薪薪懈泻 褋锌芯胁褨褖械薪薪褟 蟹 薪懈蟹褜泻懈屑 褨 薪邪泄薪懈卸褔懈屑 锌褉褨芯褉懈褌械褌芯屑</string>
+    <string name="activity_prefs_allow_bluetooth_intent_api">Bluetooth Intent API</string>
+    <string name="devicetype_flipper_zero">Flipper zero</string>
+    <string name="activity_prefs_summary_allow_bluetooth_intent_api">袛芯蟹胁芯谢懈褌懈 泻械褉褍胁邪薪薪褟 蟹\'褦写薪邪薪薪褟屑 Bluetooth 褔械褉械蟹 Intent API</string>
+    <string name="fw_upgrade_notice_amazfit_gts3">袙懈 蟹斜懈褉邪褦褌械褋褟 胁褋褌邪薪芯胁懈褌懈 屑褨泻褉芯锌褉芯谐褉邪屑褍 %s 薪邪 胁邪褕 Amazfit GTS 3.
+\n
+\n袩褨褋谢褟 胁褋褌邪薪芯胁谢械薪薪褟 褎邪泄谢褍 .zip 胁邪褕 谐芯写懈薪薪懈泻 锌械褉械蟹邪胁邪薪褌邪卸懈褌褜褋褟.
+\n
+\n袩袪袨袛袨袙袞校袡孝袝 袧袗 袙袥袗小袧袠袡 袪袠袟袠袣!</string>
+    <string name="devicetype_amazfit_gts3">Amazfit GTS 3</string>
+    <string name="pref_title_notification_prefer_long_text">袧邪写邪胁邪褌懈 锌械褉械胁邪谐褍 写芯胁谐芯屑褍 褌械泻褋褌褍 褋锌芯胁褨褖械薪褜</string>
+    <string name="pref_summary_notification_prefer_long_text">携泻褖芯 屑芯卸谢懈胁芯, 薪邪写褋懈谢邪褌懈 写芯胁谐懈泄 褌械泻褋褌 褋锌芯胁褨褖械薪薪褟 薪邪 锌褉懈褋褌褉褨泄</string>
+    <string name="menuitem_unknown_app">袧械胁褨写芯屑芯 (%s)</string>
+    <string name="kind_app">袟邪褋褌芯褋褍薪芯泻</string>
+    <string name="pref_title_overwrite_settings_on_connection">袩械褉械蟹邪锌懈褋褍胁邪褌懈 薪邪谢邪褕褌褍胁邪薪薪褟 锌褨写 褔邪褋 蟹\'褦写薪邪薪薪褟</string>
+    <string name="pref_summary_overwrite_settings_on_connection">袩褨写 褔邪褋 锌褨写\'褦写薪邪薪薪褟 写芯 谐芯写懈薪薪懈泻邪 锌械褉械蟹邪锌懈褋褍胁邪褌懈 胁褋褨 薪邪谢邪褕褌褍胁邪薪薪褟 薪邪 薪褜芯屑褍.</string>
+    <string name="dismiss">袙褨写褏懈谢懈褌懈</string>
+    <string name="devicetype_amazfit_gtr3">Amazfit GTR 3</string>
+    <string name="fw_upgrade_notice_amazfit_gtr3">袙懈 蟹斜懈褉邪褦褌械褋褟 胁褋褌邪薪芯胁懈褌懈 屑褨泻褉芯锌褉芯谐褉邪屑褍 %s 薪邪 胁邪褕 Amazfit GTR 3.
+\n
+\n袩褨褋谢褟 胁褋褌邪薪芯胁谢械薪薪褟 褎邪泄谢褍 .zip 胁邪褕 谐芯写懈薪薪懈泻 锌械褉械蟹邪胁邪薪褌邪卸懈褌褜褋褟.
+\n
+\n袩袪袨袛袨袙袞校袡孝袝 袧袗 袙袥袗小袧袠袡 袪袠袟袠袣!</string>
+    <string name="pref_title_third_party_app_device_settings">袛芯蟹胁芯谢懈褌懈 褋褌芯褉芯薪薪褨屑 蟹邪褋褌芯褋褍薪泻邪屑 蟹屑褨薪褞胁邪褌懈 薪邪谢邪褕褌褍胁邪薪薪褟</string>
+    <string name="pref_summary_third_party_app_device_settings">袛芯蟹胁芯谢褟褌懈 褨薪褕懈屑 胁褋褌邪薪芯胁谢械薪懈屑 褋褌芯褉芯薪薪褨屑 蟹邪褋褌芯褋褍薪泻邪屑 胁褋褌邪薪芯胁谢褞胁邪褌懈 薪邪谢邪褕褌褍胁邪薪薪褟 锌褉懈褋褌褉芯褞 褔械褉械蟹 Intents.</string>
+    <string name="activity_type_dance">孝邪薪械褑褜</string>
+    <string name="activity_type_indoor_fitness">肖褨褌薪械褋 褍 锌褉懈屑褨褖械薪薪褨</string>
+    <string name="activity_type_stepper">小褌械锌械褉</string>
+    <string name="activity_type_pilates">袩褨谢邪褌械褋</string>
+    <string name="activity_type_volleyball">袙芯谢械泄斜芯谢</string>
+    <string name="activity_type_table_tennis">袧邪褋褌褨谢褜薪懈泄 褌械薪褨褋</string>
+    <string name="activity_type_bowling">袘芯褍谢褨薪谐</string>
+    <string name="activity_type_street_dance">袙褍谢懈褔薪褨 褌邪薪褑褨</string>
+    <string name="activity_type_zumba">袟褍屑斜邪</string>
+    <string name="activity_type_indoor_ice_skating">袣邪褌邪薪薪褟 薪邪 泻芯胁蟹邪薪邪褏 褍 锌褉懈屑褨褖械薪薪褨</string>
+    <string name="activity_type_gymnastics">袚褨屑薪邪褋褌懈泻邪</string>
+    <string name="activity_type_hiit">袙懈褋芯泻芯褨薪褌械薪褋懈胁薪械 褨薪褌械褉胁邪谢褜薪械 褌褉械薪褍胁邪薪薪褟</string>
+    <string name="activity_type_stretching">袪芯蟹褌褟谐褍胁邪薪薪褟</string>
+    <string name="activity_type_core_training">袨褋薪芯胁薪械 褌褉械薪褍胁邪薪薪褟</string>
+    <string name="activity_type_boxing">袘芯泻褋</string>
+    <string name="activity_type_kickboxing">袣褨泻斜芯泻褋懈薪谐</string>
+    <string name="devicetype_super_cars">Shell Racing</string>
+    <string name="devicetype_amazfit_gtr_lite">Amazfit GTR Lite</string>
+    <string name="watchface_setting_button_move_hands">袪褍褏 褉褍泻</string>
+    <string name="supercars_lights_label">小胁褨褌谢芯</string>
+    <string name="supercars_turbo_speed_label">Turbo Speed</string>
+    <string name="pref_header_authentication">袗胁褌械薪褌懈褎褨泻邪褑褨褟</string>
+    <string name="abstract_chart_fragment_kind_rem_sleep">楔胁懈写泻懈泄 褋芯薪</string>
+    <string name="sports_activity_confirm_delete_title">袙懈写邪谢懈褌懈 写褨褟谢褜薪褨褋褌褜 %d</string>
+    <string name="sports_activity_confirm_delete_description">袙懈 胁锌械胁薪械薪褨, 褖芯 褏芯褔械褌械 胁懈写邪谢懈褌懈 写褨褩 %d\?</string>
+    <string name="supercars_lights_blinking_label">袘谢懈屑邪薪薪褟</string>
+    <string name="hrZoneNa">袧/袛</string>
+    <string name="hrZoneFatBurn">小锌邪谢褞胁邪薪薪褟 卸懈褉褍</string>
+    <string name="hrZoneWarmUp">袪芯蟹屑懈薪泻邪</string>
+    <string name="hrZoneExtreme">袝泻褋褌褉械屑邪谢褜薪邪</string>
+    <string name="hrZoneAnaerobic">袗薪邪械褉芯斜薪邪</string>
+    <string name="TrainingEffect">孝褉械薪褍胁邪谢褜薪懈泄 械褎械泻褌</string>
+    <string name="aerobicTrainingEffect">袗械褉芯斜薪懈泄 械褎械泻褌</string>
+    <string name="anaerobicTrainingEffect">袗薪邪械褉芯斜薪懈泄 械褎械泻褌</string>
+    <string name="maximumOxygenUptake">袦邪泻褋懈屑邪谢褜薪械 褋锌芯卸懈胁邪薪薪褟 泻懈褋薪褞</string>
+    <string name="HeartRateZones">袟芯薪懈 锌褍谢褜褋褍</string>
+    <string name="currentWorkoutLoad">孝褉械薪褍胁邪谢褜薪械 薪邪胁邪薪褌邪卸械薪薪褟</string>
+    <string name="hrZoneAerobic">袗械褉芯斜薪邪</string>
+    <string name="pref_chart_sleep_lines_limit">小泻褨谢褜泻懈 褉褟写泻褨胁 褋械邪薪褋褍 褋薪褍 锌芯泻邪蟹褍胁邪褌懈 锌械褉械写 谐芯褉褌邪薪薪褟屑</string>
+    <string name="elevationGain">袟斜褨谢褜褕械薪薪褟 胁懈褋芯褌懈</string>
+    <string name="elevationLoss">袟薪懈卸械薪薪褟 胁懈褋芯褌懈</string>
+    <string name="devicetype_sony_wh_1000xm2">Sony WH-1000XM2</string>
+    <string name="devicetype_sony_wf_1000xm4">Sony WF-1000XM4</string>
+    <string name="activity_prefs_goal_standing_time_minutes">些芯写械薪薪邪 屑械褌邪: 褌褉懈胁邪谢褨褋褌褜 褋褌芯褟薪薪褟 褍 褏胁懈谢懈薪邪褏</string>
+    <string name="pref_header_offline_voice">袨褎谢邪泄薪 谐芯谢芯褋</string>
+    <string name="pref_cover_to_mute">袟邪泻褉懈褌懈, 褖芯斜 胁懈屑泻薪褍褌懈 蟹胁褍泻</string>
+    <string name="pref_gps_mode_preset">袪械卸懈屑 GPS</string>
+    <string name="pref_text_to_speech">孝械泻褋褌 褍 屑芯胁谢械薪薪褟</string>
+    <string name="pref_workout_detection_alert_summary">小锌芯胁褨褖邪褌懈 锌褉芯 胁懈褟胁谢械薪薪褟 褌褉械薪褍胁邪薪薪褟</string>
+    <string name="pref_sleep_mode_smart_enable_title">袪芯蟹褍屑薪械 胁胁褨屑泻薪械薪薪褟</string>
+    <string name="prefs_always_on_display_follow_watchface">小褌懈谢褜 胁褨写锌芯胁褨写邪褦 褑懈褎械褉斜谢邪褌褍</string>
+    <string name="menuitem_one_tap_measuring">袙懈屑褨褉褞胁邪薪薪褟 芯写薪懈屑 写芯褌懈泻芯屑</string>
+    <string name="pref_workout_detection_title">袙懈褟胁谢械薪薪褟 褌褉械薪褍胁邪薪薪褟</string>
+    <string name="pref_workout_detection_summary">袗胁褌芯屑邪褌懈褔薪械 胁懈褟胁谢械薪薪褟 褌褉械薪褍胁邪薪薪褟</string>
+    <string name="pref_workout_detection_categories_title">袣邪褌械谐芯褉褨褩 褌褉械薪褍胁邪薪褜</string>
+    <string name="pref_sleep_mode_title">袪械卸懈屑 褋薪褍</string>
+    <string name="pref_sleep_mode_sleep_screen_title">袝泻褉邪薪 褋薪褍</string>
+    <string name="seconds_25">25 褋械泻褍薪写</string>
+    <string name="pref_sleep_mode_smart_enable_summary">袗胁褌芯屑邪褌懈褔薪芯 胁屑懈泻邪褌懈 褉械卸懈屑 褋薪褍, 褟泻褖芯 谐芯写懈薪薪懈泻 芯写褟谐薪械薪芯 锌褨写 褔邪褋 褋薪褍</string>
+    <string name="menuitem_theater_mode">袪械卸懈屑 褌械邪褌褉褍</string>
+    <string name="menuitem_brightness">携褋泻褉邪胁褨褋褌褜</string>
+    <string name="pref_enable_unsupported_settings_title">校胁褨屑泻薪褍褌懈 薪械锌褨写褌褉懈屑褍胁邪薪褨 薪邪谢邪褕褌褍胁邪薪薪褟</string>
+    <string name="pref_enable_unsupported_settings_summary">笑械 写邪褋褌褜 蟹屑芯谐褍 芯褌褉懈屑邪褌懈 写芯褋褌褍锌 写芯 胁褋褨褏 薪邪褟胁薪懈褏 薪邪谢邪褕褌褍胁邪薪褜, 薪邪胁褨褌褜 褟泻褖芯 胁芯薪懈 薪械 锌褨写褌褉懈屑褍褞褌褜褋褟 锌褉懈褋褌褉芯褦屑. 笑械 屑芯卸械 锌褉懈蟹胁械褋褌懈 写芯 薪械褋褌邪斜褨谢褜薪芯褩 褉芯斜芯褌懈 褌邪 蟹斜芯褩胁 褍 褉芯斜芯褌褨 锌褉懈褋褌褉芯褞.</string>
+    <string name="prefs_always_on_display_style">小褌懈谢褜</string>
+    <string name="prefs_control_center">笑械薪褌褉 泻械褉褍胁邪薪薪褟</string>
+    <string name="prefs_control_center_summary">袙懈斜械褉褨褌褜 械谢械屑械薪褌懈 褍 褋锌邪写薪芯屑褍 褋锌懈褋泻褍 褑械薪褌褉褍 泻械褉褍胁邪薪薪褟</string>
+    <string name="activity_type_indoor_walking">啸芯写褜斜邪 褍 锌褉懈屑褨褖械薪薪褨</string>
+    <string name="devicetype_amazfit_gtr4">Amazfit GTR 4</string>
+    <string name="menuitem_volume">袚褍褔薪褨褋褌褜</string>
+    <string name="pref_screen_auto_brightness_title">袗胁褌芯褟褋泻褉邪胁褨褋褌褜</string>
+    <string name="menuitem_screen_always_lit">袝泻褉邪薪 蟹邪胁卸写懈 褋胁褨褌懈褌褜褋褟</string>
+    <string name="pref_title_upper_button_long_press_action">袛褨褟 锌褉懈 写芯胁谐芯屑褍 蟹邪褌懈褋泻邪薪薪褨 胁械褉褏薪褜芯褩 泻薪芯锌泻懈</string>
+    <string name="fw_upgrade_notice_amazfit_gtr4">袙懈 蟹斜懈褉邪褦褌械褋褟 胁褋褌邪薪芯胁懈褌懈 屑褨泻褉芯锌褉芯谐褉邪屑褍 %s 薪邪 胁邪褕 Amazfit GTR 4.
+\n
+\n袩褨褋谢褟 胁褋褌邪薪芯胁谢械薪薪褟 褎邪泄谢褍 .zip 胁邪褕 谐芯写懈薪薪懈泻 锌械褉械蟹邪胁邪薪褌邪卸懈褌褜褋褟.
+\n
+\n袩袪袨袛袨袙袞校袡孝袝 袧袗 袙袥袗小袧袠袡 袪袠袟袠袣!</string>
+    <string name="pref_gps_satellite_search">袩芯褕褍泻 褋褍锌褍褌薪懈泻褨胁</string>
+    <string name="pref_agps_header">AGPS</string>
+    <string name="pref_workout_detection_sensitivity">效褍褌谢懈胁褨褋褌褜</string>
+    <string name="accuracy">孝芯褔薪褨褋褌褜</string>
+    <string name="balanced">袟斜邪谢邪薪褋芯胁邪薪懈泄</string>
+    <string name="power_saving">袝薪械褉谐芯芯褖邪写卸械薪薪褟</string>
+    <string name="custom">袙谢邪褋薪懈泄</string>
+    <string name="gps">GPS</string>
+    <string name="gps_bds">GPS + BDS</string>
+    <string name="gps_gnolass">GPS + GNOLASS</string>
+    <string name="low_power_gps">袝薪械褉谐芯芯褖邪写薪懈泄 GPS</string>
+    <string name="single_band">袨写薪芯褋屑褍谐芯胁懈泄</string>
+    <string name="dual_band">袛胁芯褋屑褍谐芯胁懈泄</string>
+    <string name="gps_galileo">GPS + GALILEO</string>
+    <string name="all_satellites">校褋褨 褋褍锌褍褌薪懈泻懈</string>
+    <string name="speed_first">袩褉褨芯褉懈褌械褌 褕胁懈写泻芯褋褌褨</string>
+    <string name="accuracy_first">袩褉褨芯褉懈褌械褌 褌芯褔薪芯褋褌褨</string>
+    <string name="pref_header_sound_vibration">袟胁褍泻 褌邪 胁褨斜褉邪褑褨褟</string>
+    <string name="pref_gps_header">GPS</string>
+    <string name="pref_gps_band">袛褨邪锌邪蟹芯薪 GPS</string>
+    <string name="pref_gps_combination">袣芯屑斜褨薪芯胁邪薪懈泄 GPS</string>
+    <string name="pref_alert_tone">小懈谐薪邪谢 褋锌芯胁褨褖械薪薪褟</string>
+    <string name="pref_vibrate_for_alert">袙褨斜褉邪褑褨褟 锌褉懈 褋锌芯胁褨褖械薪薪褨</string>
+    <string name="pref_crown_vibration">袙褨斜褉邪褑褨褟 泻芯褉芯薪泻懈</string>
+    <string name="offline_voice_respond_turn_wrist">袪械邪谐褍胁邪褌懈 薪邪 锌芯胁芯褉芯褌 蟹邪锌\'褟褋褌褟</string>
+    <string name="offline_voice_respond_screen_on">袪械邪谐褍胁邪褌懈, 泻芯谢懈 械泻褉邪薪 褍胁褨屑泻薪械薪芯</string>
+    <string name="offline_voice_response_during_screen_lighting">袪械邪谐褍胁邪褌懈, 泻芯谢懈 械泻褉邪薪 褋胁褨褌懈褌褜褋褟</string>
+    <string name="pref_agps_expiry_reminder_enabled">袧邪谐邪写褍胁邪薪薪褟 锌褉芯 蟹邪泻褨薪褔械薪薪褟 褌械褉屑褨薪褍 写褨褩 AGPS</string>
+    <string name="pref_workout_detection_categories_summary">袣邪褌械谐芯褉褨褩 褌褉械薪褍胁邪薪褜 写谢褟 邪胁褌芯屑邪褌懈褔薪芯谐芯 胁懈褟胁谢械薪薪褟</string>
+    <string name="pref_workout_detection_alert_title">小锌芯胁褨褖械薪薪褟</string>
+    <string name="pref_sleep_mode_sleep_screen_summary">袩芯泻邪蟹褍胁邪褌懈 械泻褉邪薪 褋薪褍 锌褨写 褔邪褋 锌褉芯斜褍写卸械薪薪褟 械泻褉邪薪邪 蟹 褉械卸懈屑褍 褋薪褍, 褖芯斜 蟹屑械薪褕懈褌懈 胁褨写胁芯谢褨泻邪薪薪褟</string>
+    <string name="buttons_on_left">袣薪芯锌泻懈 谢褨胁芯褉褍褔</string>
+    <string name="buttons_on_right">袣薪芯锌泻懈 锌褉邪胁芯褉褍褔</string>
+    <string name="prefs_weardirection">袧邪锌褉褟屑芯泻 薪芯褋褨薪薪褟</string>
+    <string name="vibration_profile_default">孝懈锌芯胁芯</string>
+    <string name="pref_screen_notification_profile_schedule">袪芯蟹泻谢邪写</string>
+    <string name="pref_screen_notification_profile_todo_list">小锌懈褋芯泻 褋锌褉邪胁</string>
+    <string name="activity_prefs_target_weight_kg">袦械褌邪 胁邪谐懈 褍 泻褨谢芯谐褉邪屑邪褏</string>
+    <string name="activity_prefs_goal_fat_burn_time_minutes">些芯写械薪薪邪 屑械褌邪: 褌褉懈胁邪谢褨褋褌褜 褋锌邪谢褞胁邪薪薪褟 卸懈褉褍 褍 褏胁懈谢懈薪邪褏</string>
+    <string name="activity_type_pool_swimming">袩谢邪胁邪薪薪褟 胁 斜邪褋械泄薪褨</string>
+    <string name="menuitem_calendar">袣邪谢械薪写邪褉</string>
+    <string name="menuitem_todo">小锌懈褋芯泻 褋锌褉邪胁</string>
+    <string name="menuitem_voice_memos">袚芯谢芯褋芯胁褨 薪芯褌邪褌泻懈</string>
+    <string name="menuitem_sun_moon">小芯薪褑械 褌邪 屑褨褋褟褑褜</string>
+    <string name="menuitem_offline_voice">袨褎谢邪泄薪 谐芯谢芯褋</string>
+    <string name="menuitem_membership_cards">效谢械薪褋褜泻褨 泻邪褉褌泻懈</string>
+    <string name="menuitem_phone">孝械谢械褎芯薪</string>
+    <string name="menuitem_bluetooth">Bluetooth</string>
+    <string name="menuitem_wifi">Wi-Fi</string>
+    <string name="menuitem_lockscreen">袝泻褉邪薪 斜谢芯泻褍胁邪薪薪褟</string>
+    <string name="menuitem_unsupported">[袧袝 袩袉袛孝袪袠袦校袆孝鞋小携] %s</string>
+    <string name="pref_title_lower_button_short_press_action">袛褨褟 锌褉懈 泻芯褉芯褌泻芯屑褍 蟹邪褌懈褋泻邪薪薪褨 薪懈卸薪褜芯褩 泻薪芯锌泻懈</string>
+    <string name="pref_screen_auto_brightness_summary">袪械谐褍谢褞胁邪薪薪褟 褟褋泻褉邪胁芯褋褌褨 械泻褉邪薪邪 蟹邪谢械卸薪芯 胁褨写 薪邪胁泻芯谢懈褕薪褜芯谐芯 芯褋胁褨褌谢械薪薪褟</string>
+    <string name="sony_speak_to_chat_sensitivity_standard">小褌邪薪写邪褉褌薪邪</string>
+    <string name="pref_agps_expiry_reminder_time">效邪褋 薪邪谐邪写褍胁邪薪薪褟 锌褉芯 蟹邪泻褨薪褔械薪薪褟 褌械褉屑褨薪褍 写褨褩 AGPS</string>
+    <string name="menuitem_eject_water">袙懈写邪谢械薪薪褟 胁芯写懈</string>
+    <string name="pref_huami_truncate_fetch_operation_timestamps_title">小泻芯褉芯褌懈褌懈 屑褨褌泻懈 褔邪褋褍 芯褌褉懈屑邪薪薪褟</string>
+    <string name="pref_huami_truncate_fetch_operation_timestamps_summary">小泻芯褉芯褔褍胁邪褌懈 屑褨褌泻懈 褔邪褋褍 芯褌褉懈屑邪薪薪褟 写芯 褏胁懈谢懈薪. 袙懈屑泻薪褨褌褜 褑械泄 锌邪褉邪屑械褌褉, 褖芯斜 蟹邪谢懈褕懈褌懈 屑褨褌泻懈 褔邪褋褍 胁 褋械泻褍薪写邪褏, 褟泻褖芯 褍 胁邪褋 胁懈薪懈泻薪褍褌褜 锌褉芯斜谢械屑懈 锌褨写 褔邪褋 芯褌褉懈屑邪薪薪褟 写褍卸械 泻芯褉芯褌泻懈褏 褌褉械薪褍胁邪薪褜.</string>
+    <string name="pref_summary_notification_media_ignores_application_list">袨斜褉芯斜谢褟褌懈 屑械写褨邪褋锌芯胁褨褖械薪薪褟 锌械褉械写 褋锌懈褋泻芯屑 蟹邪褋褌芯褋褍薪泻褨胁. 携泻褖芯 褑械泄 锌邪褉邪屑械褌褉 薪械 芯斜褉邪薪芯, 屑械写褨邪蟹邪褋褌芯褋褍薪泻懈 锌芯胁懈薪薪褨 斜褍褌懈 写芯蟹胁芯谢械薪褨 褍 褋锌懈褋泻褍 蟹邪褋褌芯褋褍薪泻褨胁, 褖芯斜 械谢械屑械薪褌懈 泻械褉褍胁邪薪薪褟 屑械写褨邪 锌褉邪褑褞胁邪谢懈 薪邪 锌褉懈褋褌褉芯褩.</string>
+    <string name="pref_title_notification_media_ignores_application_list">袦械写褨邪褋锌芯胁褨褖械薪薪褟 褨谐薪芯褉褍褞褌褜 褋锌懈褋芯泻 蟹邪褋褌芯褋褍薪泻褨胁</string>
+    <string name="debugactivity_confirm_remove_device_preferences_title">袙懈谢褍褔懈褌懈 薪邪谢邪褕褌褍胁邪薪薪褟 锌褉懈褋褌褉芯褞\?</string>
+    <string name="debugactivity_confirm_remove_device_preferences">笑械 锌褉懈蟹胁械写械 写芯 褋泻懈写邪薪薪褟 薪邪谢邪褕褌褍胁邪薪褜 锌褉懈褋褌褉芯褞 写谢褟 胁褋褨褏 锌褨写\'褦写薪邪薪懈褏 锌褉懈褋褌褉芯褩胁. 袙懈 胁锌械胁薪械薪褨\?</string>
+    <string name="devicetype_amazfit_band7">Amazfit Band 7</string>
+    <string name="fw_upgrade_notice_amazfit_band7">袙懈 蟹斜懈褉邪褦褌械褋褟 胁褋褌邪薪芯胁懈褌懈 屑褨泻褉芯锌褉芯谐褉邪屑褍 %s 薪邪 胁邪褕 Amazfit Band 7.
+\n
+\n袩褨褋谢褟 胁褋褌邪薪芯胁谢械薪薪褟 褎邪泄谢褍 .zip 胁邪褕 谐芯写懈薪薪懈泻 锌械褉械蟹邪胁邪薪褌邪卸懈褌褜褋褟.
+\n
+\n袩袪袨袛袨袙袞校袡孝袝 袧袗 袙袥袗小袧袠袡 袪袠袟袠袣!</string>
+    <string name="function_enabled">校胁褨屑泻薪械薪芯</string>
+    <string name="world_clock_code">袣芯写</string>
+    <string name="pref_title_touch_spotify_official_app">Spotify (谢懈褕械 芯褎褨褑褨泄薪懈泄 蟹邪褋褌芯褋褍薪芯泻)</string>
+    <string name="pref_title_touch_spotify_galaxy_app">Spotify (谢懈褕械 蟹邪褋褌芯褋褍薪芯泻 Galaxy Wearable)</string>
+    <string name="prefs_touch_lock_buds2">小械薪褋芯褉薪械 泻械褉褍胁邪薪薪褟</string>
+    <string name="devicetype_galaxybuds_2">Galaxy Buds2</string>
+    <string name="devicetype_amazfit_gts4">Amazfit GTS 4</string>
+    <string name="fw_upgrade_notice_amazfit_gts4">袙懈 蟹斜懈褉邪褦褌械褋褟 胁褋褌邪薪芯胁懈褌懈 屑褨泻褉芯锌褉芯谐褉邪屑褍 %s 薪邪 胁邪褕 Amazfit GTS 4.
+\n
+\n袩褨褋谢褟 胁褋褌邪薪芯胁谢械薪薪褟 褎邪泄谢褍 .zip 胁邪褕 谐芯写懈薪薪懈泻 锌械褉械蟹邪胁邪薪褌邪卸懈褌褜褋褟.
+\n
+\n袩袪袨袛袨袙袞校袡孝袝 袧袗 袙袥袗小袧袠袡 袪袠袟袠袣!</string>
+    <string name="fw_upgrade_notice_amazfit_gts4_mini">袙懈 蟹斜懈褉邪褦褌械褋褟 胁褋褌邪薪芯胁懈褌懈 屑褨泻褉芯锌褉芯谐褉邪屑褍 %s 薪邪 胁邪褕 Amazfit GTS 4 Mini.
+\n
+\n袩褨褋谢褟 胁褋褌邪薪芯胁谢械薪薪褟 褎邪泄谢褍 .zip 胁邪褕 谐芯写懈薪薪懈泻 锌械褉械蟹邪胁邪薪褌邪卸懈褌褜褋褟.
+\n
+\n袩袪袨袛袨袙袞校袡孝袝 袧袗 袙袥袗小袧袠袡 袪袠袟袠袣!</string>
+    <string name="devicetype_amazfit_gts4_mini">Amazfit GTS 4 Mini</string>
+    <string name="devicetype_sony_linkbuds_s">Sony LinkBuds S</string>
+    <string name="pref_header_intent_api">Intent API</string>
+    <string name="intent_api_allow_trigger_export_title">袛芯蟹胁芯谢懈褌懈 械泻褋锌芯褉褌 斜邪蟹懈 写邪薪懈褏</string>
+    <string name="intent_api_broadcast_export_title">孝褉邪薪褋谢褟褑褨褟 械泻褋锌芯褉褌褍 斜邪蟹懈 写邪薪懈褏</string>
+    <string name="intent_api_broadcast_export_summary">孝褉邪薪褋谢褟褑褨褟 薪邪屑褨褉褍 锌褨褋谢褟 蟹邪胁械褉褕械薪薪褟 械泻褋锌芯褉褌褍 斜邪蟹懈 写邪薪懈褏</string>
+    <string name="intent_api_allow_activity_sync_title">袛芯蟹胁芯谢懈褌懈 褨薪褨褑褨邪褑褨褞 褋懈薪褏褉芯薪褨蟹邪褑褨褩 写褨泄</string>
+    <string name="intent_api_allow_activity_sync_summary">袛芯蟹胁芯谢懈褌懈 褨薪褨褑褨褞胁邪薪薪褟 褋懈薪褏褉芯薪褨蟹邪褑褨褩 邪泻褌懈胁薪芯褋褌褨 褔械褉械蟹 Intent API</string>
+    <string name="intent_api_allow_trigger_export_summary">袛芯蟹胁芯谢懈褌懈 褨薪褨褑褨褞胁邪褌懈 械泻褋锌芯褉褌 斜邪蟹懈 写邪薪懈褏 褔械褉械蟹 Intent API</string>
+    <string name="sony_ambient_sound_control_button_mode_nc_as_off">楔褍屑芯锌芯谐谢懈薪邪薪薪褟, 袧邪胁泻芯谢懈褕薪褨泄 蟹胁褍泻, 袙懈屑泻</string>
+    <string name="sony_ambient_sound_control_button_mode_nc_as">楔褍屑芯锌芯谐谢懈薪邪薪薪褟, 薪邪胁泻芯谢懈褕薪褨泄 蟹胁褍泻</string>
+    <string name="sony_ambient_sound_control_button_mode_as_off">袧邪胁泻芯谢懈褕薪褨泄 蟹胁褍泻, 袙懈屑泻</string>
+    <string name="sony_quick_access_double_tap">楔胁懈写泻懈泄 写芯褋褌褍锌 (锌芯写胁褨泄薪械 褌芯褉泻邪薪薪褟)</string>
+    <string name="sony_quick_access_triple_tap">楔胁懈写泻懈泄 写芯褋褌褍锌 (锌芯褌褉褨泄薪械 褌芯褉泻邪薪薪褟)</string>
+    <string name="sony_ambient_sound_control_button_modes">袪械卸懈屑懈 泻薪芯锌泻懈 泻械褉褍胁邪薪薪褟 薪邪胁泻芯谢懈褕薪褨屑 蟹胁褍泻芯屑</string>
+    <string name="sony_ambient_sound_control_button_mode_nc_off">楔褍屑芯锌芯谐谢懈薪邪薪薪褟, 袙懈屑泻</string>
+    <string name="menuitem_wechat_pay">WeChat Pay</string>
+    <string name="pref_agps_update_time">效邪褋 芯薪芯胁谢械薪薪褟 AGPS</string>
+    <string name="pref_agps_expire_time">孝械褉屑褨薪 写褨褩 AGPS</string>
+    <string name="appmanager_watchface_activate">校胁褨屑泻薪褍褌懈</string>
+    <string name="appmanager_app_start">袪芯蟹锌芯褔邪褌懈</string>
+    <string name="appmanager_app_download">袟邪胁邪薪褌邪卸懈褌懈 胁 泻械褕</string>
+    <string name="appmanager_download_started">袟邪胁邪薪褌邪卸械薪薪褟 蟹邪褋褌芯褋褍薪泻褍 褉芯蟹锌芯褔邪谢芯褋褟</string>
+    <string name="appmanager_downloaded_to_cache">%s 蟹邪胁邪薪褌邪卸械薪芯 写芯 泻械褕褍</string>
+    <string name="appmanager_download_app_error">袩芯屑懈谢泻邪 锌褨写 褔邪褋 蟹邪胁邪薪褌邪卸械薪薪褟 蟹邪褋褌芯褋褍薪泻褍</string>
+    <string name="devicetype_asteroidos">AsteroidOS</string>
+    <string name="devicetype_amazfit_trex_2">Amazfit T-Rex 2</string>
+    <string name="fw_upgrade_notice_amazfit_trex2">袙懈 蟹斜懈褉邪褦褌械褋褟 胁褋褌邪薪芯胁懈褌懈 屑褨泻褉芯锌褉芯谐褉邪屑褍 %s 薪邪 胁邪褕 Amazfit T-Rex 2.
+\n
+\n袩褨褋谢褟 胁褋褌邪薪芯胁谢械薪薪褟 褎邪泄谢褍 .zip 胁邪褕 谐芯写懈薪薪懈泻 锌械褉械蟹邪胁邪薪褌邪卸懈褌褜褋褟.
+\n
+\n袩袪袨袛袨袙袞校袡孝袝 袧袗 袙袥袗小袧袠袡 袪袠袟袠袣!</string>
+    <string name="activity_type_rowing">袙械褋谢褍胁邪薪薪褟</string>
+    <string name="pref_workout_detection_enabled_summary">校胁褨屑泻薪褍褌懈 邪胁褌芯屑邪褌懈褔薪械 胁懈褟胁谢械薪薪褟 褑褜芯谐芯 褌褉械薪褍胁邪薪薪褟</string>
+    <string name="pref_workout_detection_ask_first">小锌芯褔邪褌泻褍 蟹邪锌懈褌褍胁邪褌懈 屑械薪械</string>
+    <string name="pref_workout_detection_time_summary">袣褨谢褜泻褨褋褌褜 褏胁懈谢懈薪, 锌褉芯褌褟谐芯屑 褟泻懈褏 屑邪褦 褌褉懈胁邪褌懈 褌褉械薪褍胁邪薪薪褟, 锌械褉褕 薪褨卸 泄芯谐芯 斜褍写械 胁懈褟胁谢械薪芯</string>
+    <string name="pref_workout_detection_ask_first_summary">袙懈屑邪谐邪褌懈 锌褨写褌胁械褉写卸械薪薪褟 薪邪 谐芯写懈薪薪懈泻褍, 褟泻褖芯 胁懈褟胁谢械薪芯 褑械 褌褉械薪褍胁邪薪薪褟</string>
+    <string name="pref_workout_detection_enabled">袙懈褟胁谢械薪薪褟 褍胁褨屑泻薪械薪芯</string>
+    <string name="pref_workout_detection_time">啸胁懈谢懈薪 邪泻褌懈胁薪芯褋褌褨 写芯 胁懈蟹薪邪褔械薪薪褟</string>
+    <string name="georgian">袚褉褍蟹懈薪褋褜泻邪</string>
+    <string name="devicetype_soflow_s06">SoFlow SO6</string>
+    <string name="pref_title_lock_unlock">袟邪斜谢芯泻褍胁邪褌懈</string>
+    <string name="devicetype_galaxybuds_2_pro">Galaxy Buds2 Pro</string>
+    <string name="pref_camera_remote_title">袙褨写写邪谢械薪械 泻械褉褍胁邪薪薪褟 泻邪屑械褉芯褞</string>
+    <string name="pref_camera_remote_summary">袛芯蟹胁芯谢褟褦 谐芯写懈薪薪懈泻褍 泻械褉褍胁邪褌懈 泻邪屑械褉芯褞 褌械谢械褎芯薪褍</string>
+    <string name="pref_workout_keep_screen_on_title">袥懈褕邪褌懈 械泻褉邪薪 褍胁褨屑泻薪械薪懈屑 锌褨写 褔邪褋 褌褉械薪褍胁邪薪薪褟</string>
+    <string name="pref_workout_keep_screen_on_summary">袝泻褉邪薪 蟹邪谢懈褕邪褌懈屑械褌褜褋褟 褍胁褨屑泻薪械薪懈屑 锌褨写 褔邪褋 褌褉械薪褍胁邪薪薪褟, 邪 褟褋泻褉邪胁褨褋褌褜 斜褍写械 褉械谐褍谢褞胁邪褌懈褋褟 写谢褟 斜械蟹锌械褉械褉胁薪芯谐芯 锌芯泻邪蟹褍 写邪薪懈褏 锌褉芯 褌褉械薪褍胁邪薪薪褟 胁 褉械卸懈屑褨 褉械邪谢褜薪芯谐芯 褔邪褋褍</string>
+    <string name="status">小褌邪薪</string>
+    <string name="stop">袟褍锌懈薪懈褌懈</string>
+    <string name="wifi_hotspot_configuration">袧邪谢邪褕褌褍胁邪薪薪褟 褌芯褔泻懈 写芯褋褌褍锌褍 Wi-Fi</string>
+    <string name="wifi_hotspot_start_summary">袟邪锌褍褋褌懈褌懈 褌芯褔泻褍 写芯褋褌褍锌褍 Wi-Fi 蟹 谐芯写懈薪薪懈泻邪</string>
+    <string name="ftp_server">FTP-褋械褉胁械褉</string>
+    <string name="ftp_server_configuration">袧邪谢邪褕褌褍胁邪薪薪褟 FTP-褋械褉胁械褉邪</string>
+    <string name="ftp_server_root_dir">袣芯褉械薪械胁懈泄 泻邪褌邪谢芯谐</string>
+    <string name="address">袗写褉械褋邪</string>
+    <string name="username">袉屑鈥櫻� 泻芯褉懈褋褌褍胁邪褔邪</string>
+    <string name="wifi_hotspot">孝芯褔泻邪 写芯褋褌褍锌褍 Wi-Fi</string>
+    <string name="wifi_hotspot_summary">袣械褉褍胁邪薪薪褟 褌芯褔泻芯褞 写芯褋褌褍锌褍 Wi-Fi 蟹 谐芯写懈薪薪懈泻邪</string>
+    <string name="wifi_hotspot_status">小褌邪薪 褌芯褔泻懈 写芯褋褌褍锌褍 Wi-Fi</string>
+    <string name="wifi_ssid">SSID</string>
+    <string name="wifi_hotspot_stop_summary">袟褍锌懈薪懈褌懈 褌芯褔泻褍 写芯褋褌褍锌褍 Wi-Fi 蟹 谐芯写懈薪薪懈泻邪</string>
+    <string name="ftp_server_start_summary">袟邪锌褍褋褌懈褌懈 FTP-褋械褉胁械褉 蟹 谐芯写懈薪薪懈泻邪</string>
+    <string name="ftp_server_summary">袣械褉褍胁邪薪薪褟 FTP-褋械褉胁械褉芯屑 蟹 谐芯写懈薪薪懈泻邪</string>
+    <string name="ftp_server_stop_summary">袟褍锌懈薪懈褌懈 FTP-褋械褉胁械褉 蟹 谐芯写懈薪薪懈泻邪</string>
+    <string name="ftp_server_status">小褌邪薪 FTP-褋械褉胁械褉邪</string>
+    <string name="croatian">啸芯褉胁邪褌褋褜泻邪</string>
+    <string name="sony_speak_to_chat_summary">袗胁褌芯屑邪褌懈褔薪芯 胁懈屑懈泻邪褌懈 褕褍屑芯锌芯谐谢懈薪邪薪薪褟, 泻芯谢懈 胁懈 锌芯褔懈薪邪褦褌械 谐芯胁芯褉懈褌懈.</string>
+    <string name="pref_morning_updates_title">袪邪薪泻芯胁褨 芯薪芯胁谢械薪薪褟</string>
+    <string name="pref_morning_updates_summary">袨薪芯胁谢械薪薪褟 锌芯泻邪蟹褍胁邪褌懈屑褍褌褜褋褟 褖芯褉邪薪泻褍</string>
+    <string name="pref_morning_updates_categories_title">袣邪褌械谐芯褉褨褩 褉邪薪泻芯胁懈褏 芯薪芯胁谢械薪褜</string>
+    <string name="pref_morning_updates_categories_summary">小锌懈褋芯泻 泻邪褌械谐芯褉褨泄 写谢褟 锌芯泻邪蟹褍 褖芯褉邪薪泻褍</string>
+    <string name="yesterdays_activity">校褔芯褉邪褕薪褟 写褨褟谢褜薪褨褋褌褜</string>
+    <string name="bluetooth_calls">袙懈泻谢懈泻懈 褔械褉械蟹 Bluetooth</string>
+    <string name="bluetooth_calls_pairing">小锌芯谢褍褔械薪薪褟 胁懈泻谢懈泻褨胁 Bluetooth</string>
+    <string name="bluetooth_calls_settings">袧邪谢邪褕褌褍胁邪薪薪褟 胁懈泻谢懈泻褨胁 Bluetooth</string>
+    <string name="pref_display_caller_summary">袩芯泻邪蟹褍胁邪褌懈 薪芯屑械褉 褌械谢械褎芯薪褍 邪斜芯 褨屑\'褟 写谢褟 胁褏褨写薪懈褏 胁懈泻谢懈泻褨胁</string>
+    <string name="pref_pair_bluetooth_calls_title">袩邪褉邪 写谢褟 胁懈泻谢懈泻褨胁 褔械褉械蟹 Bluetooth</string>
+    <string name="pref_pair_bluetooth_calls_help_title">携泻 芯褌褉懈屑褍胁邪褌懈 胁懈泻谢懈泻懈 褔械褉械蟹 Bluetooth</string>
+    <string name="pref_pair_bluetooth_calls_help_1">1. 孝芯褉泻薪褨褌褜褋褟 泻薪芯锌泻懈 薪懈卸褔械, 褖芯斜 褉芯蟹锌芯褔邪褌懈 锌褉芯褑械褋 褋褌胁芯褉械薪薪褟 锌邪褉懈.</string>
+    <string name="pref_summary_receive_calls_watch">袟写褨泄褋薪褞胁邪褌懈 褌邪 锌褉懈泄屑邪褌懈 胁懈泻谢懈泻懈 斜械蟹锌芯褋械褉械写薪褜芯 薪邪 谐芯写懈薪薪懈泻褍</string>
+    <string name="pref_display_caller_title">袩芯泻邪蟹邪褌懈 泻芯薪褌邪泻褌薪褍 褨薪褎芯褉屑邪褑褨褞</string>
+    <string name="pref_pair_bluetooth_calls_help_summary">些芯斜 芯褌褉懈屑褍胁邪褌懈 胁懈泻谢懈泻懈 褔械褉械蟹 Bluetooth 胁邪屑 锌芯褌褉褨斜薪芯 蟹\'褦写薪邪褌懈 褋胁褨泄 褌械谢械褎芯薪 蟹 写褉褍谐懈屑 械泻蟹械屑锌谢褟褉芯屑 谐芯写懈薪薪懈泻邪.</string>
+    <string name="pref_pair_bluetooth_calls_summary">袧邪褌懈褋薪褨褌褜 褌褍褌, 褖芯斜 褉芯蟹锌芯褔邪褌懈 锌褉芯褑械褋 褋褌胁芯褉械薪薪褟 锌邪褉懈</string>
+    <string name="pref_pair_bluetooth_calls_help_2">2. 袩械褉械泄写褨褌褜 写芯 薪邪谢邪褕褌褍胁邪薪褜 Bluetooth 薪邪 褋胁芯褦屑褍 褌械谢械褎芯薪褨 褌邪 褋褌胁芯褉褨褌褜 锌邪褉褍 蟹 薪芯胁懈屑 锌褉懈褋褌褉芯褦屑, 褟泻懈泄 蟹鈥櫻徯残秆傃屟佈� (薪邪蟹胁褍 褋褏芯卸褍 薪邪 薪邪蟹胁褍 胁邪褕芯谐芯 锌芯褌芯褔薪芯谐芯 谐芯写懈薪薪懈泻邪, 邪谢械 蟹 褋褍褎褨泻褋芯屑, 薪邪锌褉懈泻谢邪写, \"Amazfit GTR 4 - AFC8\").</string>
+    <string name="pref_pair_bluetooth_calls_help_warning">校袙袗袚袗: 携泻褖芯 胁懈 褍胁褨屑泻薪械褌械 胁懈泻谢懈泻懈 褔械褉械蟹 Bluetooth 斜械蟹 褋褌胁芯褉械薪薪褟 锌邪褉懈 蟹 写褉褍谐懈屑 械泻蟹械屑锌谢褟褉芯屑, 褋锌芯胁褨褖械薪薪褟 锌褉芯 胁懈泻谢懈泻懈 屑芯卸褍褌褜 薪械 锌褉邪褑褞胁邪褌懈 褌邪泻, 褟泻 锌芯褌褉褨斜薪芯.</string>
+    <string name="pref_pair_bluetooth_calls_help_3">3. 校胁褨屑泻薪褨褌褜 薪邪谢邪褕褌褍胁邪薪薪褟 胁懈泻谢懈泻褨胁 褔械褉械蟹 Bluetooth 薪懈卸褔械.</string>
+    <string name="pref_title_notification_cache_while_disconnected">袣械褕 锌芯蟹邪 蟹芯薪芯褞 写芯褋褟卸薪芯褋褌褨</string>
+    <string name="pref_summary_notification_cache_while_disconnected">袧邪写褋懈谢邪褌懈 锌褉芯锌褍褖械薪褨 褋锌芯胁褨褖械薪薪褟, 泻芯谢懈 锌褉懈褋褌褉褨泄 锌褨写\'褦写薪褍褦褌褜褋褟 锌褨褋谢褟 锌械褉械斜褍胁邪薪薪褟 锌芯蟹邪 蟹芯薪芯褞 写芯褋褟卸薪芯褋褌褨</string>
+    <string name="sony_button_mode_help_summary">袨锌懈褋 褉械卸懈屑褍 泻芯卸薪芯褩 泻薪芯锌泻懈</string>
+    <string name="single_tap">袨写懈薪 写芯褌懈泻</string>
+    <string name="double_tap">袩芯写胁褨泄薪械 褌芯褉泻邪薪薪褟</string>
+    <string name="triple_tap">袩芯褌褉褨泄薪懈泄 写芯褌懈泻</string>
+    <string name="continue_pressing">袩褉芯写芯胁卸懈褌懈 薪邪褌懈褋泻邪薪薪褟</string>
+    <string name="quick_attention">楔胁懈写泻邪 褍胁邪谐邪</string>
+    <string name="long_press">袟邪褌懈褋泻邪薪薪褟</string>
+    <string name="pref_switch_controls_anc_ambient_off">楔褍屑芯锌芯谐谢懈薪邪薪薪褟 鈫愨啋 小械褉械写芯胁懈褖械 鈫愨啋 袙懈屑泻薪械薪芯</string>
+    <string name="sony_button_mode_help_title">袪械卸懈屑懈 泻薪芯锌芯泻 - 袛芯胁褨写泻邪</string>
+    <string name="menuitem_mi_ai">MI AI</string>
+    <string name="menuitem_cards">袣邪褉褌泻懈</string>
+    <string name="prefs_shortcut_cards">袣邪褉褌泻懈 褟褉谢懈泻褨胁</string>
+    <string name="prefs_shortcut_cards_summary">袣邪褉褌泻懈 褟褉谢懈泻褨胁, 锌芯泻邪蟹褍胁邪薪褨 锌褨写 褔邪褋 谐芯褉褌邪薪薪褟 锌褉邪胁芯褉褍褔 锌芯 褑懈褎械褉斜谢邪褌褍. 袩褨写 褔邪褋 褉芯斜芯褌懈 蟹邪褋褌芯褋褍薪泻褍 褑械 薪邪谢邪褕褌褍胁邪薪薪褟 薪械 胁锌谢懈胁邪褦 薪邪 邪胁褌芯屑邪褌懈褔薪芯 褋褌胁芯褉械薪褨 泻邪褉褌泻懈.</string>
+    <string name="menuitem_last_workout">袨褋褌邪薪薪褦 褌褉械薪褍胁邪薪薪褟</string>
+    <string name="menuitem_forecast">袩芯谐芯写邪</string>
+    <string name="menuitem_total_workout">袟邪谐邪谢芯屑 褌褉械薪褍胁邪薪褜</string>
+    <string name="menuitem_vo2_max">VO鈧� Max</string>
+    <string name="menuitem_recommendation">袪械泻芯屑械薪写邪褑褨褩</string>
+    <string name="zepp_os_watchface_rush">袣胁邪锌谢懈胁褨褋褌褜</string>
+    <string name="zepp_os_watchface_minimalist">袦褨薪褨屑邪谢褨褋褌懈褔薪懈泄</string>
+    <string name="zepp_os_watchface_vibrant">携褋泻褉邪胁懈泄</string>
+    <string name="zepp_os_watchface_rotating_earth">袨斜械褉褌邪薪薪褟 袟械屑谢褨</string>
+    <string name="zepp_os_watchface_superposition">薪邪泻谢邪写邪薪薪褟</string>
+    <string name="zepp_os_watchface_simplicity_data">袩褉芯褋褌芯褌邪 写邪薪懈褏</string>
+    <string name="zepp_os_watchface_multiple_data">袘邪谐邪褌芯 写邪薪懈褏</string>
+    <string name="zepp_os_watchface_red_fantasy">效械褉胁芯薪邪 褎邪薪褌邪蟹褨褟</string>
+    <string name="zepp_os_watchface_business_style">袛褨谢芯胁懈泄 褋褌懈谢褜</string>
+    <string name="zepp_os_watchface_emerald_moonlight">小屑邪褉邪谐写芯胁械 屑褨褋褟褔薪械 褋褟泄胁芯</string>
+    <string name="menuitem_aqi">袉薪写械泻褋 褟泻芯褋褌褨 锌芯胁褨褌褉褟</string>
+    <string name="devicetype_amazfit_gtr3_pro">Amazfit GTR 3 Pro</string>
+    <string name="fw_upgrade_notice_amazfit_gtr3_pro">袙懈 蟹斜懈褉邪褦褌械褋褟 胁褋褌邪薪芯胁懈褌懈 屑褨泻褉芯锌褉芯谐褉邪屑褍 %s 薪邪 胁邪褕 Amazfit GTR 3 Pro.
+\n
+\n袩褨褋谢褟 胁褋褌邪薪芯胁谢械薪薪褟 褎邪泄谢褍 .zip 胁邪褕 谐芯写懈薪薪懈泻 锌械褉械蟹邪胁邪薪褌邪卸懈褌褜褋褟.
+\n
+\n袩袪袨袛袨袙袞校袡孝袝 袧袗 袙袥袗小袧袠袡 袪袠袟袠袣!</string>
+    <string name="devicetype_sony_wh_1000xm5">Sony WH-1000XM5</string>
+    <string name="fossil_hr_pairing_successful">校褋锌褨褕薪械 褋锌芯谢褍褔械薪薪褟 蟹 谐芯写懈薪薪懈泻芯屑</string>
+    <string name="fossil_hr_pairing_failed">袧械 胁写邪谢芯褋褟 褋褌胁芯褉懈褌懈 锌邪褉褍 蟹 谐芯写懈薪薪懈泻芯屑</string>
+    <string name="fossil_hr_connection_not_confirmed">袟\'褦写薪邪薪薪褟 薪械 锌褨写褌胁械褉写卸械薪芯 薪邪 谐芯写懈薪薪懈泻褍, 胁懈泻芯褉懈褋褌芯胁褍褦褌褜褋褟 薪械邪胁褌械薪褌懈褎褨泻芯胁邪薪懈泄 褉械卸懈屑</string>
+    <string name="fossil_hr_confirm_connection">袩褨写褌胁械褉写褜褌械 薪邪 谐芯写懈薪薪懈泻褍</string>
+    <string name="fossil_hr_confirmation_skipped">袩褉芯锌褍褋泻 锌褨写褌胁械褉写卸械薪薪褟 薪邪 锌褉懈褋褌褉芯褩</string>
+    <string name="fossil_hr_confirmation_timeout">效邪褋 芯褔褨泻褍胁邪薪薪褟 锌褨写褌胁械褉写卸械薪薪褟, 锌褉芯写芯胁卸褍褦褌褜褋褟</string>
+    <string name="debug_companion_pair_current">袩芯胁\'褟蟹邪褌懈 锌芯褌芯褔薪懈泄 锌褉懈褋褌褉褨泄 蟹 泻芯屑锌邪薪褜泄芯薪芯屑</string>
+    <string name="debug_companion_show_associated">袩芯泻邪蟹邪褌懈 锌芯胁\'褟蟹邪薪褨 褋褍锌褍褌薪褨 锌褉懈褋褌褉芯褩</string>
+    <string name="kind_gpx_route">GPX 屑邪褉褕褉褍褌</string>
+    <string name="activity_detail_share_raw_summary">袩芯写褨谢褨褌褜褋褟 薪械芯斜褉芯斜谢械薪懈屑 锌褨写褋褍屑泻芯屑</string>
+    <string name="dev_tools">袉薪褋褌褉褍屑械薪褌懈 褉芯蟹褉芯斜薪懈泻邪</string>
+    <string name="activity_error_share_failed">袧械 胁写邪谢芯褋褟 锌芯写褨谢懈褌懈褋褟 褎邪泄谢芯屑.</string>
+    <string name="activity_detail_share_gps_label">袩芯写褨谢懈褌懈褋褟 GPS-褌褉械泻芯屑</string>
+    <string name="activity_detail_share_raw_details">袩芯写褨谢褨褌褜褋褟 薪械芯斜褉芯斜谢械薪懈屑懈 锌芯写褉芯斜懈褑褟屑懈</string>
+    <string name="gpx_route_upload_failed">袧械 胁写邪谢芯褋褟 胁懈胁邪薪褌邪卸懈褌懈 GPX 屑邪褉褕褉褍褌</string>
+    <string name="gpx_route_upload_complete">袙懈胁邪薪褌邪卸械薪薪褟 GPX 屑邪褉褕褉褍褌褍 蟹邪胁械褉褕械薪芯</string>
+    <string name="gpx_route_upload_in_progress">袙懈胁邪薪褌邪卸械薪薪褟 GPX 屑邪褉褕褉褍褌褍</string>
+    <string name="pref_contacts_summary">袧邪谢邪褕褌褍泄褌械 泻芯薪褌邪泻褌懈 薪邪 谐芯写懈薪薪懈泻褍</string>
+    <string name="contact_missing_name">袉屑\'褟 泻芯薪褌邪泻褌褍 锌芯褉芯卸薪褦</string>
+    <string name="contact_phone_number">袧芯屑械褉 褌械谢械褎芯薪褍</string>
+    <string name="title_activity_set_contacts">袧邪谢邪褕褌褍胁邪褌懈 泻芯薪褌邪泻褌懈</string>
+    <string name="pref_contacts_title">袣芯薪褌邪泻褌懈</string>
+    <string name="contact_delete_confirm_title">袙懈写邪谢懈褌懈 泻芯薪褌邪泻褌</string>
+    <string name="contact_no_free_slots_description">袧邪 锌褉懈褋褌褉芯褩 薪械屑邪褦 胁褨谢褜薪懈褏 泻芯屑褨褉芯泻 写谢褟 泻芯薪褌邪泻褌褨胁 (褍褋褜芯谐芯 泻芯屑褨褉芯泻: %1$s)</string>
+    <string name="contact_name">袉屑\'褟</string>
+    <string name="title_activity_contact_details">袩芯写褉芯斜懈褑褨 泻芯薪褌邪泻褌褍</string>
+    <string name="contact_delete_confirm_description">袙懈 胁锌械胁薪械薪褨, 褖芯 褏芯褔械褌械 胁懈写邪谢懈褌懈 \'%1$s\'?</string>
+    <string name="contact_missing_number">袣芯薪褌邪泻褌薪懈泄 薪芯屑械褉 锌芯褉芯卸薪褨泄</string>
+    <string name="pref_header_navigation">袧邪胁褨谐邪褑褨褟</string>
+    <string name="pref_title_navigation_forward">袧邪写褋懈谢邪褌懈 薪邪胁褨谐邪褑褨褞 薪邪 谐芯写懈薪薪懈泻</string>
+    <string name="pref_summary_navigation_forward">袩械褉械褋懈谢邪褌懈 褨薪褋褌褉褍泻褑褨褩 蟹 薪邪胁褨谐邪褑褨泄薪懈褏 蟹邪褋褌芯褋褍薪泻褨胁 薪邪 谐芯写懈薪薪懈泻</string>
+    <string name="intent_api_allow_debug_commands_title">袛芯蟹胁芯谢懈褌懈 泻芯屑邪薪写懈 薪邪谢邪谐芯写卸械薪薪褟</string>
+    <string name="intent_api_allow_debug_commands_summary">袛芯蟹胁芯谢懈褌懈 蟹邪锌褍褋泻 泻芯屑邪薪写 屑械薪褞 薪邪谢邪谐芯写卸械薪薪褟 褔械褉械蟹 Intent API</string>
+    <string name="voice_service_class_title">袣谢邪褋 谐芯谢芯褋芯胁懈褏 锌芯褋谢褍谐</string>
+    <string name="voice_service_class_summary">袩芯胁薪懈泄 褕谢褟褏 写芯 褋谢褍卸斜懈 芯斜褉芯斜泻懈 谐芯谢芯褋芯胁懈褏 泻芯屑邪薪写</string>
+    <string name="voice_service_package_title">袩邪泻械褌 谐芯谢芯褋芯胁懈褏 锌芯褋谢褍谐</string>
+    <string name="voice_service_package_summary">袟邪褋褌芯褋褍薪芯泻, 褖芯 屑褨褋褌懈褌褜 褋谢褍卸斜褍 芯斜褉芯斜泻懈 谐芯谢芯褋芯胁懈褏 泻芯屑邪薪写</string>
+    <string name="voice_service">袚芯谢芯褋芯胁邪 褋谢褍卸斜邪</string>
+    <string name="pref_app_logs_summary">校胁褨屑泻薪褍褌懈 卸褍褉薪邪谢懈 褨蟹 蟹邪褋褌芯褋褍薪泻褨胁 谐芯写懈薪薪懈泻邪</string>
+    <string name="pref_app_logs_start_summary">袪芯蟹锌芯褔邪褌懈 卸褍褉薪邪谢褞胁邪薪薪褟 褨蟹 蟹邪褋褌芯褋褍薪泻褨胁 谐芯写懈薪薪懈泻邪</string>
+    <string name="pref_app_logs_stop_summary">袩褉懈锌懈薪懈褌懈 蟹邪锌懈褋 卸褍褉薪邪谢褍 褨蟹 蟹邪褋褌芯褋褍薪泻褨胁 谐芯写懈薪薪懈泻邪</string>
+    <string name="pref_app_logs_title">袞褍褉薪邪谢懈 蟹邪褋褌芯褋褍薪泻褨胁</string>
+    <string name="fwapp_install_device_not_supported">袧械屑芯卸谢懈胁芯 胁褋褌邪薪芯胁懈褌懈 褎邪泄谢, 锌褉懈褋褌褉褨泄 薪械 锌褨写褌褉懈屑褍褦褌褜褋褟.</string>
+    <string name="screenshot_taken">袟薪褨屑芯泻 械泻褉邪薪邪 蟹褉芯斜谢械薪芯</string>
+    <string name="share_screenshot">袩芯写褨谢懈褌懈褋褟 蟹薪褨屑泻芯屑 械泻褉邪薪邪</string>
+    <string name="stress_mild">袥械谐泻懈泄</string>
+    <string name="stress_moderate">袩芯屑褨褉薪懈泄</string>
+    <string name="charts_legend_stress_average">小械褉械写薪褨泄 褉褨胁械薪褜 薪邪锌褉褍卸械薪芯褋褌褨</string>
+    <string name="stress_relaxed">袪芯蟹褋谢邪斜谢械薪懈泄</string>
+    <string name="stress_high">袙懈褋芯泻懈泄</string>
+    <string name="watchface_widget_type_uv_index">校肖-褨薪写械泻褋</string>
+    <string name="pref_summary_banglejs_phone_gps_enbale">袙懈泻芯褉懈褋褌芯胁褍胁邪褌懈 写邪薪褨 GPS 褌械谢械褎芯薪褍, 褖芯斜 锌械褉械蟹邪锌懈褋邪褌懈 写邪薪褨 GPS 斜褉邪褋谢械褌邪</string>
+    <string name="action_changelog">袞褍褉薪邪谢 蟹屑褨薪</string>
+    <string name="pref_title_banglejs_phone_gps_enbale">袙懈泻芯褉懈褋褌芯胁褍胁邪褌懈 写邪薪褨 GPS 褌械谢械褎芯薪褍</string>
+    <string name="pref_title_banglejs_phone_gps_network_only">袙懈泻芯褉懈褋褌芯胁褍胁邪褌懈 谢懈褕械 屑械褉械卸褍 写谢褟 胁懈蟹薪邪褔械薪薪褟 屑褨褋褑褟 锌械褉械斜褍胁邪薪薪褟</string>
+    <string name="title">袧邪蟹胁邪</string>
+    <string name="pref_summary_banglejs_phone_gps_update_interval">袉薪褌械褉胁邪谢, 蟹 褟泻芯褞 褔邪褋褌芯褌芯褞 锌芯蟹懈褑褨褟 GPS 芯薪芯胁谢褞褦褌褜褋褟, 褍 屑褨谢褨褋械泻褍薪写邪褏</string>
+    <string name="pref_title_banglejs_phone_gps_update_interval">袉薪褌械褉胁邪谢 芯薪芯胁谢械薪薪褟 写邪薪懈褏 GPS</string>
+    <string name="pref_summary_banglejs_phone_gps_network_only">袙懈蟹薪邪褔邪褌懈 屑褨褋褑械 锌械褉械斜褍胁邪薪薪褟 谢懈褕械 蟹邪 写芯锌芯屑芯谐芯褞 屑械褉械卸械胁芯谐芯 锌芯褋褌邪褔邪谢褜薪懈泻邪. 笑械 蟹薪懈卸褍褦 褋锌芯卸懈胁邪薪薪褟 械薪械褉谐褨褩 蟹邪 褉邪褏褍薪芯泻 褌芯褔薪芯褋褌褨. 袩芯褌褉褨斜薪械 锌芯胁褌芯褉薪械 锌褨写\'褦写薪邪薪薪褟.</string>
+    <string name="description">袨锌懈褋</string>
+    <string name="preview_image">袩芯锌械褉械写薪褨泄 锌械褉械谐谢褟写 蟹芯斜褉邪卸械薪薪褟</string>
+    <string name="status_icon">袩褨泻褌芯谐褉邪屑邪 褋褌邪薪褍</string>
+    <string name="changelog_full_title">袞褍褉薪邪谢 蟹屑褨薪</string>
+    <string name="changelog_show_full">袘褨谢褜褕械鈥�</string>
+    <string name="changelog_ok_button">袚邪褉邪蟹写</string>
+    <string name="changelog_title">些芯 薪芯胁芯谐芯</string>
+    <string name="pai_chart_per_week">PAI 蟹邪 褌懈卸写械薪褜</string>
+    <string name="pai_chart_per_month">PAI 蟹邪 屑褨褋褟褑褜</string>
+    <string name="pai_plus_num">+%d</string>
+    <string name="num_min">%d 褏胁</string>
+    <string name="pai_day">袛械薪薪懈泄 锌褉懈褉褨褋褌</string>
+    <string name="pai_total">校褋褜芯谐芯</string>
+    <string name="loyalty_cards_catima_package">袧邪蟹胁邪 锌邪泻褍薪泻邪 Catima</string>
+    <string name="loyalty_cards_install">校褋褌邪薪芯胁懈褌懈 Catima</string>
+    <string name="loyalty_cards_sync_groups_only">小懈薪褏褉芯薪褨蟹褍胁邪褌懈 谢懈褕械 锌械胁薪褨 谐褉褍锌懈</string>
+    <string name="loyalty_cards_sync_groups">袚褉褍锌懈 写谢褟 褋懈薪褏褉芯薪褨蟹邪褑褨褩</string>
+    <string name="loyalty_cards_install_catima_fail">袧械 胁写邪谢芯褋褟 胁褨写泻褉懈褌懈 屑邪谐邪蟹懈薪 蟹邪褋褌芯褋褍薪泻褨胁 写谢褟 胁褋褌邪薪芯胁谢械薪薪褟 Catima</string>
+    <string name="loyalty_cards_sync_title">小懈薪褏褉芯薪褨蟹褍胁邪褌懈 泻邪褉褌泻懈 谢芯褟谢褜薪芯褋褌褨</string>
+    <string name="loyalty_cards_sync_options">袩邪褉邪屑械褌褉懈 褋懈薪褏褉芯薪褨蟹邪褑褨褩</string>
+    <string name="loyalty_cards_catima">Catima</string>
+    <string name="loyalty_cards">袣邪褉褌泻懈 谢芯褟谢褜薪芯褋褌褨</string>
+    <string name="loyalty_cards_syncing">小懈薪褏褉芯薪褨蟹邪褑褨褟 泻邪褉褌芯泻 谢芯褟谢褜薪芯褋褌褨 %d 蟹 锌褉懈褋褌褉芯褦屑</string>
+    <string name="loyalty_cards_sync_archived">小懈薪褏褉芯薪褨蟹褍胁邪褌懈 邪褉褏褨胁薪褨 泻邪褉褌泻懈</string>
+    <string name="loyalty_cards_sync_starred">小懈薪褏褉芯薪褨蟹褍胁邪褌懈 谢懈褕械 泻邪褉褌泻懈 蟹 蟹褨褉泻邪屑懈</string>
+    <string name="loyalty_cards_catima_not_installed">Catima 锌芯褌褉褨斜薪邪 写谢褟 泻械褉褍胁邪薪薪褟 泻邪褉褌泻邪屑懈 谢芯褟谢褜薪芯褋褌褨</string>
+    <string name="loyalty_cards_sync_summary">孝芯褉泻薪褨褌褜褋褟, 褖芯斜 褋懈薪褏褉芯薪褨蟹褍胁邪褌懈 泻邪褉褌泻懈 蟹 谐芯写懈薪薪懈泻芯屑</string>
+    <string name="loyalty_cards_catima_not_compatible">袙褋褌邪薪芯胁谢械薪邪 胁械褉褋褨褟 Catima 薪械 褋褍屑褨褋薪邪 蟹 Gadgetbridge. 袨薪芯胁褨褌褜 Catima 褌邪 Gadgetbridge 写芯 芯褋褌邪薪薪褨褏 胁械褉褋褨泄.</string>
+    <string name="loyalty_cards_sync">小懈薪褏褉芯薪褨蟹邪褑褨褟</string>
+    <string name="loyalty_cards_catima_permissions_title">袙褨写褋褍褌薪褨 写芯蟹胁芯谢懈</string>
+    <string name="loyalty_cards_open_catima">袙褨写泻褉懈褌懈 Catima</string>
+    <string name="loyalty_cards_catima_permissions_summary">袛谢褟 褋懈薪褏褉芯薪褨蟹邪褑褨褩 Gadgetbridge 锌芯褌褉褨斜薪褨 写芯蟹胁芯谢懈 薪邪 褔懈褌邪薪薪褟 泻邪褉褌芯泻 Catima. 袧邪褌懈褋薪褨褌褜 褑褞 泻薪芯锌泻褍, 褖芯斜 薪邪写邪褌懈 褩褏.</string>
+    <string name="pref_title_mb_intents">袘械蟹锌芯褋械褉械写薪褜芯 褌褉邪薪褋谢褞胁邪褌懈 写褨褩 屑械写褨邪泻薪芯锌芯泻</string>
+    <string name="pref_summary_mb_intents">校胁褨屑泻薪褨褌褜, 褟泻褖芯 泻械褉褍胁邪薪薪褟 屑械写褨邪 薪械 锌褉邪褑褞褦 褍 锌械胁薪懈褏 蟹邪褋褌芯褋褍薪泻邪褏</string>
+    <string name="devicetype_bohemic_smart_bracelet">Bohemic Smart Bracelet</string>
+    <string name="devicetype_vivomove_hr">Garmin Vivomove HR</string>
+    <string name="devicetype_casiogwb5600">Casio GW-B5600</string>
+    <string name="devicetype_casiogmwb5000">Casio GMW-B5000</string>
+    <string name="busy_task_fetch_hr_data">袨褌褉懈屑邪薪薪褟 写邪薪懈褏 锌褉芯 锌褍谢褜褋</string>
+    <string name="busy_task_fetch_sleep_respiratory_rate_data">袨褌褉懈屑邪薪薪褟 写邪薪懈褏 锌褉芯 褔邪褋褌芯褌褍 写懈褏邪薪薪褟 褍胁褨 褋薪褨</string>
+    <string name="busy_task_fetch_debug_logs">袨褌褉懈屑邪薪薪褟 卸褍褉薪邪谢褨胁 薪邪谢邪谐芯写卸械薪薪褟</string>
+    <string name="busy_task_fetch_sports_summaries">袨褌褉懈屑邪薪薪褟 褋锌芯褉褌懈胁薪懈褏 锌褨写褋褍屑泻褨胁</string>
+    <string name="busy_task_fetch_stress_data">袨褌褉懈屑邪薪薪褟 写邪薪懈褏 锌褉芯 薪邪锌褉褍卸械薪薪褟</string>
+    <string name="busy_task_fetch_spo2_data">袨褌褉懈屑邪薪薪褟 写邪薪懈褏 SpO2</string>
+    <string name="busy_task_fetch_sports_details">袨褌褉懈屑邪薪薪褟 褋锌芯褉褌懈胁薪懈褏 锌芯写褉芯斜懈褑褜</string>
+    <string name="busy_task_fetch_pai_data">袨褌褉懈屑邪薪薪褟 写邪薪懈褏 锌褉芯 PAI</string>
+    <string name="activity_prefs_goals">笑褨谢褨</string>
+    <string name="error_no_bluetooth_scan">袛谢褟 薪邪谢械卸薪芯褩 褉芯斜芯褌懈 褋泻邪薪褍胁邪薪薪褟 锌芯褌褉褨斜薪芯 褍胁褨屑泻薪褍褌懈 褌邪 薪邪写邪褌懈 写芯褋褌褍锌 薪邪 胁懈褟胁谢械薪薪褟 褔械褉械蟹 Bluetooth</string>
+    <string name="error_no_bluetooth_connect">袛谢褟 薪邪谢械卸薪芯褩 褉芯斜芯褌懈 褋泻邪薪褍胁邪薪薪褟 锌芯褌褉褨斜薪芯 褍胁褨屑泻薪褍褌懈 褌邪 薪邪写邪褌懈 写芯褋褌褍锌 薪邪 蟹\'褦写薪邪薪薪褟 褔械褉械蟹 Bluetooth</string>
+    <string name="permission_request">%1$s 写芯蟹胁芯谢褟褦 薪邪写褋懈谢邪褌懈 锌芯胁褨写芯屑谢械薪薪褟 褌邪 褨薪褕褨 写邪薪褨 蟹 Android 薪邪 胁邪褕 锌褉懈褋褌褉褨泄. 袛谢褟 褑褜芯谐芯 锌芯褌褉褨斜械薪 写芯蟹胁褨谢 薪邪 写芯褋褌褍锌 写芯 褑懈褏 写邪薪懈褏, 斜械蟹 褟泻芯谐芯 蟹邪褋褌芯褋褍薪芯泻 薪械 蟹屑芯卸械 锌褉邪褑褞胁邪褌懈 薪邪谢械卸薪懈屑 褔懈薪芯屑.
+\n
+\n袟邪褉邪蟹 胁邪屑 斜褍写械 锌芯泻邪蟹邪薪芯 泻褨谢褜泻邪 褋锌谢懈胁薪懈褏 胁褨泻芯薪 Android, 褟泻褨 蟹邪锌懈褌褍褞褌褜 褑褨 写芯蟹胁芯谢懈.
+\n
+\n孝芯褉泻薪褨褌褜褋褟 \'%2$s\', 褖芯斜 锌褉芯写芯胁卸懈褌懈.</string>
+    <string name="withings_bt_calibration_next">袧邪褋褌褍锌薪械</string>
+    <string name="activity_type_surfing">小械褉褎褨薪谐</string>
+    <string name="activity_type_squash">小泻胁芯褕</string>
+    <string name="activity_type_weightlifting">袙邪卸泻邪 邪褌谢械褌懈泻邪</string>
+    <string name="withings_calibration_text_hours">袙懈泻芯褉懈褋褌芯胁褍泄褌械 褑懈褎械褉斜谢邪褌 薪懈卸褔械, 褖芯斜 胁懈褉褨胁薪褟褌懈 谐芯写懈薪薪懈泻芯胁褍 褋褌褉褨谢泻褍 薪邪 12.</string>
+    <string name="withings_calibration_text_minutes">孝械锌械褉 蟹邪 写芯锌芯屑芯谐芯褞 褑懈褎械褉斜谢邪褌邪 胁懈褉褨胁薪褟泄褌械 褏胁懈谢懈薪薪褍 褋褌褉褨谢泻褍 薪邪 12.</string>
+    <string name="withings_steel_hr">Withings Steel HR</string>
+    <string name="withings_calibration_text_activity_target">袧邪褉械褕褌褨 胁懈褉褨胁薪褟泄褌械 褋褌褉褨谢泻褍 写褨褟谢褜薪芯褋褌褨 薪邪 100%. 袟胁械褉薪褨褌褜 褍胁邪谐褍, 褖芯 褑褟 褋褌褉褨谢泻邪 褉褍褏邪褦褌褜褋褟 谢懈褕械 蟹邪 谐芯写懈薪薪懈泻芯胁芯褞 褋褌褉褨谢泻芯褞.</string>
+    <string name="withings_bt_calibration_previous">袩芯锌械褉械写薪褦</string>
+    <string name="activity_type_windsurfing">袙褨薪写褋械褉褎褨薪谐</string>
+    <string name="activity_type_kitesurfing">袣邪泄褌褋械褉褎褨薪谐</string>
+    <string name="activity_type_football">肖褍褌斜芯谢</string>
+    <string name="activity_type_rugby">袪械谐斜褨</string>
+    <string name="activity_type_baseball">袘械泄褋斜芯谢</string>
+    <string name="activity_type_handball">袚邪薪写斜芯谢</string>
+    <string name="activity_type_tennis">孝械薪褨褋</string>
+    <string name="activity_type_dancing">孝邪薪褑褨</string>
+    <string name="activity_type_skiing">袣邪褌邪薪薪褟 薪邪 谢懈卸邪褏</string>
+    <string name="activity_type_snowboarding">小薪芯褍斜芯褉写懈薪谐</string>
+    <string name="activity_type_riding">袙械褉褏芯胁邪 褩蟹写邪</string>
+    <string name="activity_type_hockey">啸芯泻械泄</string>
+    <string name="activity_type_icehockey">啸芯泻械泄 薪邪 谢褜芯写褍</string>
+    <string name="activity_type_iceskating">袣邪褌邪薪薪褟 薪邪 泻芯胁蟹邪薪邪褏</string>
+    <string name="activity_type_golf">袚芯谢褜褎</string>
+    <string name="activity_type_other">袉薪褕械</string>
+    <string name="pref_theme_dynamic">袛懈薪邪屑褨褔薪褨 泻芯谢褜芯褉懈</string>
+    <string name="find_my_phone_found_it">袟袧袗袡袛袝袧袨</string>
+    <string name="pref_canned_message">袩芯胁褨写芯屑谢械薪薪褟</string>
+    <string name="preferences_miband_1_2_settings" tools:ignore="TypographyFractions">袧邪谢邪褕褌褍胁邪薪薪褟 Mi Band 1/2</string>
+    <string name="preferences_miband_1_2_warning">袩芯锌械褉械写卸械薪薪褟: 褑褨 薪邪谢邪褕褌褍胁邪薪薪褟 蟹邪褋褌芯褋芯胁褍褞褌褜褋褟 谢懈褕械 写芯 Mi Band 1 褌邪 2.</string>
+    <string name="drag_handle">褉褍褔泻邪 锌械褉械褌褟谐褍胁邪薪薪褟</string>
+    <string name="pref_activity_full_sync_trigger_summary">袉薪褨褑褨褞胁邪褌懈 锌芯胁薪褍 褋懈薪褏褉芯薪褨蟹邪褑褨褞 胁褋褨褏 写邪薪懈褏 邪泻褌懈胁薪芯褋褌褨</string>
+    <string name="pref_show_changelog">袩芯泻邪蟹褍胁邪褌懈 卸褍褉薪邪谢 蟹屑褨薪 锌褨写 褔邪褋 蟹邪锌褍褋泻褍</string>
+    <string name="pref_show_changelog_summary">袩芯泻邪蟹褍胁邪褌懈 卸褍褉薪邪谢 蟹屑褨薪 薪邪泄薪芯胁褨褕芯褩 胁械褉褋褨褩 锌褨褋谢褟 芯薪芯胁谢械薪薪褟 Gadgetbridge</string>
+    <string name="pref_activity_full_sync_trigger_title">袩芯胁薪邪 褋懈薪褏褉芯薪褨蟹邪褑褨褟</string>
+    <string name="pref_activity_full_sync_trigger_warning">笑械 褨薪褨褑褨褞褦 锌芯胁薪褍 褋懈薪褏褉芯薪褨蟹邪褑褨褞 胁褋褨褏 写邪薪懈褏 邪泻褌懈胁薪芯褋褌褨 蟹 锌褉懈褋褌褉芯褞. 袙懈泻芯薪邪薪薪褟 褌褉懈胁邪褌懈屑械 泻褨谢褜泻邪 褏胁懈谢懈薪.</string>
+    <string name="pref_theme_dynamic_colors_not_available_warning">袛懈薪邪屑褨褔薪褨 泻芯谢褜芯褉懈 薪械写芯褋褌褍锌薪褨 薪邪 胁邪褕芯屑褍 锌褉懈褋褌褉芯褩, 谢懈褕械 Android 12+ 锌褨写褌褉懈屑褍褦 褑褞 褎褍薪泻褑褨褞. Gadgetbridge 胁懈泻芯褉懈褋褌芯胁褍胁邪褌懈屑械 褋褌邪薪写邪褉褌薪褨 泻芯谢褜芯褉懈 Material 3.</string>
+    <string name="menuitem_zepp_coach">Zepp Coach</string>
+    <string name="pref_theme_dynamic_colors_explanation">袩褉懈屑褨褌泻邪: 写谢褟 胁懈泻芯褉懈褋褌邪薪薪褟 褌械屑懈 写懈薪邪屑褨褔薪懈褏 泻芯谢褜芯褉褨胁 胁邪屑 锌芯褌褉褨斜薪芯 褍胁褨屑泻薪褍褌懈 \"袣芯谢褜芯褉懈 褕锌邪谢械褉\" 邪斜芯 \"袩邪谢褨褌褉邪 泻芯谢褜芯褉褨胁\" 褍 薪邪谢邪褕褌褍胁邪薪薪褟褏 蟹芯胁薪褨褕薪褜芯谐芯 胁懈谐谢褟写褍 Android 12+. 携泻褖芯 胁懈 褑褜芯谐芯 薪械 蟹褉芯斜懈褌械, Gadgetbridge 胁懈泻芯褉懈褋褌芯胁褍胁邪褌懈屑械 褋褌邪薪写邪褉褌薪褨 泻芯谢褜芯褉懈 Material 3.</string>
+    <string name="devicetype_amazfit_bip3_pro">Amazfit Bip 3 Pro</string>
+    <string name="fw_upgrade_notice_amazfitbip3pro">袙懈 蟹斜懈褉邪褦褌械褋褟 胁褋褌邪薪芯胁懈褌懈 屑褨泻褉芯锌褉芯谐褉邪屑褍 %s 薪邪 胁邪褕 Amazfit Bip 3 Pro.
+\n
+\n小锌芯褔邪褌泻褍 胁褋褌邪薪芯胁褨褌褜 褎邪泄谢 .fw, 锌芯褌褨屑 .res. 袙邪褕 谐芯写懈薪薪懈泻 锌械褉械蟹邪胁邪薪褌邪卸懈褌褜褋褟 锌褨褋谢褟 胁褋褌邪薪芯胁谢械薪薪褟 褎邪泄谢褍 .fw.
+\n
+\n袩褉懈屑褨褌泻邪: 胁邪屑 薪械 锌芯褌褉褨斜薪芯 胁褋褌邪薪芯胁谢褞胁邪褌懈 褎邪泄谢 .res, 褟泻褖芯 胁褨薪 褌邪泻懈泄 褋邪屑懈泄, 褟泻 褉邪薪褨褕械 胁褋褌邪薪芯胁谢械薪懈泄.
+\n
+\n袩袪袨袛袨袙袞校袡孝袝 袧袗 袙袥袗小袧袠袡 袪袠袟袠袣!</string>
+    <string name="devicetype_amazfit_cheetah_pro">Amazfit Cheetah Pro</string>
+    <string name="fw_upgrade_notice_amazfit_cheetah_pro">袙懈 蟹斜懈褉邪褦褌械褋褟 胁褋褌邪薪芯胁懈褌懈 屑褨泻褉芯锌褉芯谐褉邪屑褍 %s 薪邪 胁邪褕 Amazfit Cheetah Pro.
+\n
+\n袩褨褋谢褟 胁褋褌邪薪芯胁谢械薪薪褟 褎邪泄谢褍 .zip 胁邪褕 谐芯写懈薪薪懈泻 锌械褉械蟹邪胁邪薪褌邪卸懈褌褜褋褟.
+\n
+\n袩袪袨袛袨袙袞校袡孝袝 袧袗 袙袥袗小袧袠袡 袪袠袟袠袣!</string>
+    <string name="fw_upgrade_notice_zepp_os">袙懈 蟹斜懈褉邪褦褌械褋褟 胁褋褌邪薪芯胁懈褌懈 屑褨泻褉芯锌褉芯谐褉邪屑褍 %s 薪邪 胁邪褕 %s.
+\n
+\n袩褨褋谢褟 胁褋褌邪薪芯胁谢械薪薪褟 褎邪泄谢褍 .zip 胁邪褕 谐芯写懈薪薪懈泻 锌械褉械蟹邪胁邪薪褌邪卸懈褌褜褋褟.
+\n
+\n袩袪袨袛袨袙袞校袡孝袝 袧袗 袙袥袗小袧袠袡 袪袠袟袠袣!</string>
+    <string name="devicetype_amazfit_cheetah_square">Amazfit Cheetah (Square)</string>
+    <string name="devicetype_amazfit_cheetah_round">Amazfit Cheetah (Round)</string>
+    <string name="devicetype_amazfit_bip5">Amazfit Bip 5</string>
+    <string name="devicetype_amazfit_trex_ultra">Amazfit T-Rex Ultra</string>
+    <string name="devicetype_amazfit_gtr_mini">Amazfit GTR Mini</string>
+    <string name="devicetype_amazfit_falcon">Amazfit Falcon</string>
+    <string name="device_experimental">袝袣小袩袝袪袠袦袝袧孝袗袥鞋袧袨</string>
+    <string name="info_menu_structure_set">小褌褉褍泻褌褍褉邪 屑械薪褞 JSON 胁 袚袘</string>
+    <string name="french_ca">肖褉邪薪褑褍蟹褜泻邪 (袣邪薪邪写邪)</string>
+    <string name="french_fr">肖褉邪薪褑褍蟹褜泻邪 (肖褉邪薪褑褨褟)</string>
+    <string name="error_invalid_menu_structure">袧械锌褉邪胁懈谢褜薪邪 褋褌褉褍泻褌褍褉邪 屑械薪褞 JSON</string>
+    <string name="button_open_menu_companion">袙褨写泻褉懈泄褌械 蟹邪褋褌芯褋褍薪芯泻-泻芯屑锌邪薪褜泄芯薪 屑械薪褞</string>
+    <string name="button_reset_menu_structure">小泻懈薪褍褌懈 褋褌褉褍泻褌褍褉褍 屑械薪褞</string>
+    <string name="info_menu_structure_removed">小褌褉褍泻褌褍褉褍 屑械薪褞 胁懈谢褍褔械薪芯</string>
+    <string name="info_menu_structure_contents">小褌褉褍泻褌褍褉邪 屑械薪褞: %s</string>
+    <string name="error_menu_companion_not_installed">\"HR Menu Companion\", 泄屑芯胁褨褉薪芯, 薪械 胁褋褌邪薪芯胁谢械薪芯</string>
+    <string name="info_fossil_rebuild_watchface_custom_menu">袩械褉械褉芯斜褨褌褜 褑懈褎械褉斜谢邪褌 谐芯写懈薪薪懈泻邪 写谢褟 泻芯褉懈褋褌褍胁邪褑褜泻芯谐芯 屑械薪褞</string>
+    <string name="english_au">袗薪谐谢褨泄褋褜泻邪 (袗胁褋褌褉邪谢褨褟)</string>
+    <string name="english_ca">袗薪谐谢褨泄褋褜泻邪 (袣邪薪邪写邪)</string>
+    <string name="english_gb">袗薪谐谢褨泄褋褜泻邪 (袙械谢懈泻邪 袘褉懈褌邪薪褨褟)</string>
+    <string name="english_in">袗薪谐谢褨泄褋褜泻邪 (袉薪写褨褟)</string>
+    <string name="english_us">袗薪谐谢褨泄褋褜泻邪 (小楔袗)</string>
+    <string name="spanish_es">袉褋锌邪薪褋褜泻邪 (袉褋锌邪薪褨褟)</string>
+    <string name="spanish_mx">袉褋锌邪薪褋褜泻邪 (袦械泻褋懈泻邪)</string>
+    <string name="spanish_us">袉褋锌邪薪褋褜泻邪 (小楔袗)</string>
+    <string name="updatefirmwareoperation_updateproblem_free_space">袧邪 锌褉懈褋褌褉芯褩 薪械写芯褋褌邪褌薪褜芯 胁褨谢褜薪芯谐芯 锌褉芯褋褌芯褉褍</string>
+    <string name="text_receiver_activity_title">袧邪写褨褋谢邪褌懈 褌械泻褋褌 薪邪 锌褉懈褋褌褉褨泄</string>
+    <string name="updatefirmwareoperation_failed_low_mtu">袩芯褌芯褔薪械 蟹薪邪褔械薪薪褟 MTU %1$d 蟹邪薪懈蟹褜泻械. 校胁褨屑泻薪褨褌褜 胁懈褋芯泻械 蟹薪邪褔械薪薪褟 MTU 褍 薪邪谢邪褕褌褍胁邪薪薪褟褏 锌褉懈褋褌褉芯褞 褌邪 胁褨写\'褦写薪邪泄褌械/锌褨写\'褦写薪邪泄褌械 锌褉懈褋褌褉褨泄.</string>
+    <string name="devicetype_miband2_hrx">Mi Band HRX</string>
+    <string name="new_discover_activity_description">校胁褨屑泻薪褍褌懈 胁懈褟胁谢械薪薪褟 薪芯胁芯褩 邪泻褌懈胁薪芯褋褌褨, 褖芯 屑邪褦 褍褋褍薪褍褌懈 锌褉芯斜谢械屑懈 蟹 胁懈褟胁谢械薪薪褟屑 锌褉懈褋褌褉芯褩胁. 袙懈屑泻薪褨褌褜 褑械泄 锌邪褉邪屑械褌褉, 褟泻褖芯 褍 胁邪褋 胁懈薪懈泻谢懈 锌褉芯斜谢械屑懈 锌褨写 褔邪褋 锌芯褕褍泻褍 邪斜芯 褋锌芯谢褍褔械薪薪褟 蟹 锌褉懈褋褌褉芯褦屑.</string>
+    <string name="new_discover_activity_title">校胁褨屑泻薪褍褌懈 胁懈褟胁谢械薪薪褟 薪芯胁芯褩 邪泻褌懈胁薪芯褋褌褨</string>
+    <string name="prefs_home_icon_left_item">袥褨胁芯褉褍褔</string>
+    <string name="prefs_wena3_title_activity">袧邪谢邪褕褌褍胁邪薪薪褟 写褨褟谢褜薪芯褋褌褨</string>
+    <string name="white">袘褨谢懈泄</string>
+    <string name="prefs_wena3_item_background_sync">小懈薪褏褉芯薪褨蟹邪褑褨褟 写邪薪懈褏 锌褉芯 褎芯薪芯胁褍 写褨褟谢褜薪褨褋褌褜</string>
+    <string name="prefs_home_icon_right_item">袩褉邪胁芯褉褍褔</string>
+    <string name="prefs_wena3_title_screen">袧邪谢邪褕褌褍胁邪薪薪褟 械泻褉邪薪邪</string>
+    <string name="green">袟械谢械薪懈泄</string>
+    <string name="yellow">袞芯胁褌懈泄</string>
+    <string name="devicetype_sony_wena3">Sony Wena 3</string>
+    <string name="red">效械褉胁芯薪懈泄</string>
+    <string name="cyan">袟械谢械薪芯-斜谢邪泻懈褌薪懈泄</string>
+    <string name="prefs_home_icon_center_item">袩芯 褋械褉械写懈薪褨</string>
+    <string name="purple">肖褨芯谢械褌芯胁懈泄</string>
+    <string name="blue">小懈薪褨泄</string>
+    <string name="prefs_wena3_receive_calls_title">袧邪谢邪褕褌褍胁邪薪薪褟 胁褏褨写薪懈褏 胁懈泻谢懈泻褨胁</string>
+    <string name="prefs_wena3_button_action_name_start_timer">袟邪锌褍褋泻 邪泻褌懈胁薪芯谐芯 褌邪泄屑械褉邪</string>
+    <string name="prefs_wena3_menu_icon_hint">袩褨泻褌芯谐褉邪屑邪 袧邪谢邪褕褌褍胁邪薪褜 蟹邪胁卸写懈 锌芯泻邪蟹褍褦褌褜褋褟 胁 泻褨薪褑褨</string>
+    <string name="prefs_wena3_notification_vibration_repetition_3">3 褉邪蟹懈</string>
+    <string name="prefs_wena3_button_action_name_qrio_lock">袟邪斜谢芯泻褍胁邪褌懈 Qrio</string>
+    <string name="prefs_wena3_auto_power_off_turn_off_time_item">效邪褋 胁懈屑泻薪械薪薪褟</string>
+    <string name="prefs_wena3_vibration_short">袣芯褉芯褌泻械</string>
+    <string name="prefs_wena3_home_icon_name_calories">袣邪谢芯褉褨褩</string>
+    <string name="prefs_wena3_led_none">袘械蟹 褋胁褨褌谢芯写褨芯写邪</string>
+    <string name="prefs_wena3_vibration_step_down">袩芯褋谢邪斜谢褞胁邪薪邪</string>
+    <string name="prefs_wena3_vibration_smart_item">袉薪褌械谢械泻褌褍邪谢褜薪邪 胁褨斜褉邪褑褨褟</string>
+    <string name="prefs_wena3_home_icon_name_riiiver">Riiiver</string>
+    <string name="prefs_wena3_hint_background_sync">袛芯蟹胁芯谢懈褌懈 Wena 锌械褉褨芯写懈褔薪芯 蟹邪锌懈褌褍胁邪褌懈 Gadgetbridge 锌褉芯 蟹邪胁邪薪褌邪卸械薪薪褟 蟹 锌褉懈褋褌褉芯褞 写邪薪懈褏 锌褉芯 写褨褟谢褜薪褨褋褌褜</string>
+    <string name="prefs_wena3_home_icon_name_suica">袘邪谢邪薪褋 Suica</string>
+    <string name="prefs_wena3_vibration_strength_item_medium">小械褉械写薪褟</string>
+    <string name="prefs_wena3_vibration_continuous">袘械蟹锌械褉械褉胁薪邪</string>
+    <string name="prefs_wena3_home_icon_name_pedometer">袥褨褔懈谢褜薪懈泻 泻褉芯泻褨胁</string>
+    <string name="prefs_wena3_home_icon_name_qrio">Qrio</string>
+    <string name="prefs_wena3_home_icon_name_clock">袩芯褌芯褔薪懈泄 褔邪褋</string>
+    <string name="prefs_wena3_vibration_strength_item_weak">小谢邪斜泻邪</string>
+    <string name="prefs_wena3_home_icon_title">袩褨泻褌芯谐褉邪屑懈 写芯屑邪褕薪褜芯谐芯 械泻褉邪薪邪</string>
+    <string name="prefs_wena3_item_weather_statusbar">袩芯谐芯写邪 褍 锌邪薪械谢褨 褋褌邪薪褍</string>
+    <string name="temperature_scale_celsius">袟邪 笑械谢褜褋褨褦屑</string>
+    <string name="prefs_wena3_vibration_step_up">袩芯褋懈谢褞胁邪薪邪</string>
+    <string name="prefs_wena3_vibration_basic">袘邪蟹芯胁邪</string>
+    <string name="prefs_wena3_item_large_font">袘褨谢褜褕懈泄 褉芯蟹屑褨褉 褕褉懈褎褌褍</string>
+    <string name="prefs_wena3_home_icon_name_energy">袝薪械褉谐褨褟 芯褉谐邪薪褨蟹屑褍</string>
+    <string name="prefs_wena3_notification_vibration_repetition_2">袛胁褨褔褨</string>
+    <string name="prefs_wena3_auto_power_off_hint">袩褉懈褋褌褉褨泄 邪胁褌芯屑邪褌懈褔薪芯 胁懈屑懈泻邪褌懈屑械褌褜褋褟 褌邪 胁屑懈泻邪褌懈屑械褌褜褋褟 蟹邪 胁泻邪蟹邪薪懈屑 褉芯蟹泻谢邪写芯屑</string>
+    <string name="prefs_wena3_hint_lift_wrist">袙屑懈泻邪褌懈 械泻褉邪薪, 泻芯谢懈 胁懈 写懈胁懈褌械褋褟 薪邪 褋胁芯褦 蟹邪锌\'褟褋褌褟</string>
+    <string name="prefs_wena3_hint_large_font">袟斜褨谢褜褕懈褌懈 褉芯蟹屑褨褉 褕褉懈褎褌褍 胁 泻邪谢械薪写邪褉褨, 褋锌芯胁褨褖械薪薪褟褏 褌芯褖芯.</string>
+    <string name="prefs_wena3_button_action_item_double">袩芯写胁褨泄薪械 薪邪褌懈褋泻邪薪薪褟</string>
+    <string name="prefs_wena3_vibration_rapid">楔胁懈写泻邪</string>
+    <string name="prefs_wena3_button_action_name_qrio_unlock">袪芯蟹斜谢芯泻褍胁邪褌懈 Qrio</string>
+    <string name="temperature_scale_cf">楔泻邪谢邪 褌械屑锌械褉邪褌褍褉懈</string>
+    <string name="prefs_wena3_notification_vibration_repetition_1">袨写懈薪 褉邪蟹</string>
+    <string name="prefs_wena3_receive_calls_item">袨褌褉懈屑褍胁邪褌懈 褋锌芯胁褨褖械薪薪褟 锌褉芯 胁懈泻谢懈泻懈</string>
+    <string name="prefs_wena3_hint_weather_statusbar">袩芯泻邪蟹褍胁邪褌懈 蟹薪邪褔芯泻 锌芯褌芯褔薪懈褏 褍屑芯胁 褍 胁械褉褏薪褜芯屑褍 谢褨胁芯屑褍 泻褍褌泻褍 谐芯谢芯胁薪芯谐芯 械泻褉邪薪邪</string>
+    <string name="prefs_wena3_home_icon_name_wena_pay">Wena Pay</string>
+    <string name="prefs_wena3_button_action_item">袣薪芯锌泻邪 写褨褩</string>
+    <string name="prefs_wena3_auto_power_off_turn_on_time_item">效邪褋 胁褨写薪芯胁谢械薪薪褟</string>
+    <string name="prefs_wena3_notification_default_call_vibration">袙褨斜褉邪褑褨褟 胁褏褨写薪芯谐芯 胁懈泻谢懈泻褍</string>
+    <string name="prefs_wena3_notification_default_call_led">袣芯谢褨褉 褨薪写懈泻邪褌芯褉邪 胁褏褨写薪芯谐芯 胁懈泻谢懈泻褍</string>
+    <string name="prefs_wena3_vibration_siren">小懈褉械薪邪</string>
+    <string name="temperature_scale_cf_summary">袙懈斜械褉褨褌褜, 褔懈 胁懈泻芯褉懈褋褌芯胁褍褦 锌褉懈褋褌褉褨泄 褕泻邪谢褍 蟹邪 笑械谢褜褋褨褦屑, 褔懈 蟹邪 肖邪褉械薪谐械泄褌芯屑.</string>
+    <string name="prefs_wena3_menu_icon_title">袩褨泻褌芯谐褉邪屑懈 屑械薪褞</string>
+    <string name="prefs_wena3_notification_default_call_vibration_repetition">袩芯胁褌芯褉械薪薪褟 胁褨斜褉邪褑褨褩 胁褏褨写薪芯谐芯 胁懈泻谢懈泻褍</string>
+    <string name="prefs_wena3_notification_default_vibration">袙褨斜褉邪褑褨褟 褋锌芯胁褨褖械薪褜</string>
+    <string name="prefs_wena3_notification_vibration_repetition_0">袘械蟹锌械褉械褉胁薪芯</string>
+    <string name="prefs_wena3_notification_vibration_repetition_4">4 褉邪蟹懈</string>
+    <string name="temperature_scale_fahrenheit">袟邪 肖邪褉械薪谐械泄褌芯屑</string>
+    <string name="prefs_wena3_vibration_strength_item">小懈谢邪 胁褨斜褉邪褑褨褩</string>
+    <string name="prefs_wena3_receive_calls_hint">携泻褖芯 胁懈屑泻薪械薪芯, 胁懈 薪械 芯褌褉懈屑褍胁邪褌懈屑械褌械 褋锌芯胁褨褖械薪薪褟 锌褉芯 胁褏褨写薪褨 胁懈泻谢懈泻懈 薪邪 Wena</string>
+    <string name="prefs_wena3_button_action_item_long">袟邪褌懈褋泻邪薪薪褟</string>
+    <string name="prefs_wena3_menu_icon_name_payment">袩谢邪褌褨卸</string>
+    <string name="prefs_wena3_status_page_title">小褌芯褉褨薪泻邪 褋褌邪薪褍 蟹邪屑芯胁谢械薪薪褟</string>
+    <string name="prefs_wena3_title_alarm">袧邪谢邪褕褌褍胁邪薪薪褟 斜褍写懈谢褜薪懈泻邪</string>
+    <string name="prefs_wena3_notification_default_vibration_repetition">袩芯胁褌芯褉械薪薪褟 胁褨斜褉邪褑褨褩 褋锌芯胁褨褖械薪褜</string>
+    <string name="prefs_wena3_hint_rich_design">袛芯写邪褦 蟹邪芯泻褉褍谐谢械薪褨 锌褉褟屑芯泻褍褌薪懈泻懈 薪邪胁泻芯谢芯 锌褨泻褌芯谐褉邪屑 写芯屑邪褕薪褜芯谐芯 械泻褉邪薪邪</string>
+    <string name="prefs_wena3_notification_default_led">袣芯谢褨褉 褋胁褨褌谢芯写褨芯写邪 褋锌芯胁褨褖械薪薪褟</string>
+    <string name="prefs_wena3_auto_power_off_item">袗胁褌芯胁懈屑泻薪械薪薪褟</string>
+    <string name="prefs_wena3_home_icon_name_edy">袘邪谢邪薪褋 Edy</string>
+    <string name="prefs_wena3_vibration_warning">袩芯锌械褉械写卸械薪薪褟</string>
+    <string name="prefs_wena3_smart_alarm_margin_item">袉薪褌械谢械泻褌褍邪谢褜薪懈泄 写褨邪锌邪蟹芯薪 斜褍写懈谢褜薪懈泻邪</string>
+    <string name="prefs_wena3_item_rich_design">袙懈泻芯褉懈褋褌芯胁褍胁邪褌懈 胁懈褉邪蟹薪懈泄 写懈蟹邪泄薪</string>
+    <string name="prefs_wena3_day_start_hour_item">袛械薪褜 锌芯褔懈薪邪褦褌褜褋褟 芯</string>
+    <string name="prefs_wena3_notification_per_app_settings_title">袧邪谢邪褕褌褍胁邪薪薪褟 褋锌芯胁褨褖械薪褜 写谢褟 泻芯卸薪芯谐芯 蟹邪褋褌芯褋褍薪泻褍</string>
+    <string name="prefs_wena3_vibration_none">袘械蟹 胁褨斜褉邪褑褨褩</string>
+    <string name="prefs_wena3_vibration_strength_item_strong">小懈谢褜薪邪</string>
+    <string name="prefs_wena3_vibration_triple">袩芯褌褉褨泄薪邪</string>
+    <string name="prefs_wena3_button_action_name_activity_screen">袝泻褉邪薪 写褨褟谢褜薪芯褋褌褨</string>
+    <string name="prefs_wena3_notification_settings_title">袧邪谢邪褕褌褍胁邪薪薪褟 褋锌芯胁褨褖械薪褜</string>
+    <string name="devicetype_sony_wf_1000xm5">Sony WF-1000XM5</string>
+    <string name="pref_message_privacy_mode_bodyonly">袩褉懈褏芯胁褍胁邪褌懈 谢懈褕械 褌械泻褋褌 锌芯胁褨写芯屑谢械薪薪褟</string>
+    <string name="latvian">袥邪褌懈褋褜泻邪</string>
+    <string name="common_symbols">袟邪谐邪谢褜薪褨 褋懈屑胁芯谢懈</string>
+    <string name="pref_title_notifications_ignore_work_profile">袉谐薪芯褉褍胁邪褌懈 褋锌芯胁褨褖械薪薪褟 褉芯斜芯褔芯谐芯 锌褉芯褎褨谢褞</string>
+    <string name="pref_summary_notifications_ignore_work_profile">袧械 薪邪写褋懈谢邪褌懈 褋锌芯胁褨褖械薪薪褟 褨蟹 蟹邪褋褌芯褋褍薪泻褨胁 蟹 褉芯斜芯褔芯谐芯 锌褉芯褎褨谢褞 薪邪 谐芯写懈薪薪懈泻</string>
+    <string name="zepp_os_watchface_pure_white">效懈褋褌懈泄 斜褨谢懈泄</string>
+    <string name="call_rejection_method_ignore">袉谐薪芯褉褍胁邪褌懈 (褌懈褕邪)</string>
+    <string name="avgStrokeRate">小械褉械写薪褟 褔邪褋褌芯褌邪 屑邪褏褨胁</string>
+    <string name="zepp_os_watchface_the_ultima">袪褨褕褍褔褨褋褌褜</string>
+    <string name="zepp_os_watchface_city_of_speed">袦褨褋褌芯 褕胁懈写泻芯褋褌褨</string>
+    <string name="menuitem_headphone">袧邪胁褍褕薪懈泻懈</string>
+    <string name="femometer_measurement_mode_normal">袟胁懈褔邪泄薪懈泄 (60-90 褋)</string>
+    <string name="call_rejection_method_reject">袙褨写褏懈谢懈褌懈</string>
+    <string name="zepp_os_watchface_free_combination">袙褨谢褜薪械 锌芯褦写薪邪薪薪褟</string>
+    <string name="menuitem_body_composition">小泻谢邪写 褌褨谢邪</string>
+    <string name="menuitem_zepp_pay">Zepp Pay</string>
+    <string name="pref_call_rejection_method_title">小锌芯褋褨斜 胁褨写褏懈谢械薪薪褟 胁懈泻谢懈泻褍</string>
+    <string name="busy_task_fetch_statistics">袨褌褉懈屑邪薪薪褟 褋褌邪褌懈褋褌懈泻懈</string>
+    <string name="femometer_measurement_mode_quick">楔胁懈写泻懈泄 (30 褋)</string>
+    <string name="zepp_os_watchface_starry_sky">袟芯褉褟薪械 薪械斜芯</string>
+    <string name="devicetype_amazfit_balance">Amazfit Balance</string>
+    <string name="fossil_hr_nav_app_not_installed_notify_text">袧邪胁褨谐邪褑褨褞 蟹邪锌褍褖械薪芯, 邪谢械 蟹邪褋褌芯褋褍薪芯泻 薪邪胁褨谐邪褑褨褩 薪械 胁褋褌邪薪芯胁谢械薪芯 薪邪 谐芯写懈薪薪懈泻褍. 校褋褌邪薪芯胁褨褌褜 泄芯谐芯 蟹 袦械薪械写卸械褉邪 蟹邪褋褌芯褋褍薪泻褨胁.</string>
+    <string name="yard">褟褉写懈</string>
+    <string name="strokes_minute">屑邪褏/褏胁</string>
+    <string name="femometer_measurement_mode_title">袪械卸懈屑 胁懈屑褨褉褞胁邪薪薪褟</string>
+    <string name="pref_call_rejection_method_summary">携泻邪 写褨褟 胁懈泻芯薪褍褦褌褜褋褟, 泻芯谢懈 胁褏褨写薪懈泄 胁懈泻谢懈泻 胁褨写褏懈谢褟褦褌褜褋褟 蟹 谐芯写懈薪薪懈泻邪</string>
+    <string name="devicetype_amazfit_active_edge">Amazfit Active Edge</string>
+    <string name="busy_task_fetch_temperature">袨褌褉懈屑邪薪薪褟 写邪薪懈褏 锌褉芯 褌械屑锌械褉邪褌褍褉褍</string>
+    <string name="femometer_measurement_mode_precise">孝芯褔薪懈泄 (3 褏胁)</string>
+    <string name="zepp_os_watchface_vast_sky">袘械蟹泻褉邪褦 薪械斜芯</string>
+    <string name="menuitem_workout_shortcuts">携褉谢懈泻懈 褌褉械薪褍胁邪薪褜</string>
+    <string name="maxStrokeRate">袦邪泻褋懈屑邪谢褜薪邪 褔邪褋褌芯褌邪 屑邪褏褨胁</string>
+    <string name="zepp_os_watchface_lightning_flash">小锌邪谢邪褏 斜谢懈褋泻邪胁泻懈</string>
+    <string name="laneLength">袛芯胁卸懈薪邪 写芯褉褨卸泻懈</string>
+    <string name="charts_legend_spo2_average">小械褉械写薪褨泄 褉褨胁械薪褜 泻懈褋薪褞 胁 泻褉芯胁褨</string>
+    <string name="strokes">校褋褜芯谐芯 屑邪褏褨胁</string>
+    <string name="menuitem_apps_shortcuts">携褉谢懈泻懈 蟹邪褋褌芯褋褍薪泻褨胁</string>
+    <string name="zepp_os_watchface_guider">袩褉芯胁褨写薪懈泻</string>
+    <string name="menuitem_readiness">袚芯褌芯胁薪褨褋褌褜</string>
+    <string name="devicetype_amazfit_active">Amazfit Active</string>
+    <string name="menuitem_thermometer">孝械褉屑芯屑械褌褉</string>
+    <string name="devicetype_femometer_vinca2">Femometer Vinca II</string>
+    <string name="fossil_hr_nav_app_not_installed_notify_title">袧邪 谐芯写懈薪薪懈泻褍 薪械 胁褋褌邪薪芯胁谢械薪芯 薪邪胁褨谐邪褑褨泄薪懈泄 蟹邪褋褌芯褋褍薪芯泻</string>
+    <string name="pref_title_casio_alert_other">袉薪褕褨 褋锌芯胁褨褖械薪薪褟</string>
+    <string name="pref_title_casio_alert_calendar">小锌芯胁褨褖械薪薪褟 泻邪谢械薪写邪褉褟</string>
+    <string name="pref_summary_casio_alert_call">小锌芯胁褨褖械薪薪褟 锌褉芯 胁褏褨写薪褨 胁懈泻谢懈泻懈 (胁褨斜褉邪褑褨褟/褋懈谐薪邪谢)</string>
+    <string name="pref_device_action_dnd_on">袧械 褌褍褉斜褍胁邪褌懈 - 校胁褨屑泻薪械薪芯</string>
+    <string name="prefs_phone_silent_mode">袘械蟹蟹胁褍褔薪懈泄 褉械卸懈屑 薪邪 褌械谢械褎芯薪褨</string>
+    <string name="prefs_wearmode">袪械卸懈屑 薪芯褋褨薪薪褟</string>
+    <string name="wearmode_pebble">Pebble (蟹邪褋褌褨斜泻邪 薪邪 胁蟹褍褌褌褨)</string>
+    <string name="devicetype_miband8">Xiaomi Smart Band 8</string>
+    <string name="pref_title_casio_alert_call">小锌芯胁褨褖械薪薪褟 锌褉芯 胁褏褨写薪褨 胁懈泻谢懈泻懈</string>
+    <string name="silent_mode_normal_vibrate">袟胁懈褔邪泄薪懈泄 / 袙褨斜褉邪褑褨褟</string>
+    <string name="pref_title_goal_secondary">袛芯写邪褌泻芯胁邪 屑械褌邪</string>
+    <string name="pref_title_casio_alert_email">小锌芯胁褨褖械薪薪褟 械-锌芯褕褌懈</string>
+    <string name="activity_type_trekking">袩芯褏褨写 褍 谐芯褉懈</string>
+    <string name="pref_summary_casio_alert_email">小锌芯胁褨褖械薪薪褟 械-锌芯褕褌懈 (胁褨斜褉邪褑褨褟/褋懈谐薪邪谢)</string>
+    <string name="pref_device_action_dnd_alarms">袧械 褌褍褉斜褍胁邪褌懈 - 袥懈褕械 斜褍写懈谢褜薪懈泻</string>
+    <string name="pref_summary_casio_alert_sms">小锌芯胁褨褖械薪薪褟 锌褉芯 SMS (胁褨斜褉邪褑褨褟/褋懈谐薪邪谢)</string>
+    <string name="devicetype_colacao23">ColaCao 2023</string>
+    <string name="pref_title_preview_message_in_title">袩芯泻邪蟹褍胁邪褌懈 锌芯锌械褉械写薪褨泄 锌械褉械谐谢褟写 锌芯胁褨写芯屑谢械薪薪褟 胁 蟹邪谐芯谢芯胁泻褍</string>
+    <string name="devicetype_redmiwatch3active">Redmi Watch 3 Active</string>
+    <string name="silent_mode_vibrate_silent">袙褨斜褉邪褑褨褟 / 袘械蟹蟹胁褍褔薪懈泄</string>
+    <string name="devicetype_redmi_smart_band_2">Redmi Smart Band 2</string>
+    <string name="pref_summary_casio_alert_calendar">小锌芯胁褨褖械薪薪褟 泻邪谢械薪写邪褉褟 (胁褨斜褉邪褑褨褟/褋懈谐薪邪谢)</string>
+    <string name="pref_summary_send_app_notifications">袧邪写褋懈谢邪褌懈 褋锌芯胁褨褖械薪薪褟 蟹邪褋褌芯褋褍薪泻褨胁 薪邪 锌褉懈褋褌褉褨泄</string>
+    <string name="pref_device_action_dnd_priority">袧械 褌褍褉斜褍胁邪褌懈 - 袥懈褕械 胁邪卸谢懈胁褨</string>
+    <string name="danish">袛邪薪褋褜泻邪</string>
+    <string name="devicetype_miband7pro">Xiaomi Smart Band 7 Pro</string>
+    <string name="pref_summary_casio_alert_other">袉薪褕褨 褋锌芯胁褨褖械薪薪褟 (胁褨斜褉邪褑褨褟/褋懈谐薪邪谢)</string>
+    <string name="standing_time">孝褉懈胁邪谢褨褋褌褜 褋褌芯褟薪薪褟</string>
+    <string name="active_time">孝褉懈胁邪谢褨褋褌褜 写褨褟谢褜薪芯褋褌褨</string>
+    <string name="pref_title_prefix_notification_with_app">袧邪蟹胁邪 蟹邪褋褌芯褋褍薪泻褍 胁 褋锌芯胁褨褖械薪薪褨</string>
+    <string name="wearmode_band">袘褉邪褋谢械褌 (薪邪 蟹邪锌\'褟褋褌褨)</string>
+    <string name="devicetype_colacao21">ColaCao 2021</string>
+    <string name="wearmode_necklace">袧邪屑懈褋褌芯 (褉械屑褨薪械褑褜 薪邪 褕懈褞)</string>
+    <string name="devicetype_xiaomi_watch_lite">Xiaomi Watch Lite</string>
+    <string name="pref_summary_preview_message_in_title">袩芯泻邪蟹褍胁邪褌懈 锌芯锌械褉械写薪褨泄 锌械褉械谐谢褟写 锌芯胁褨写芯屑谢械薪薪褟 胁 蟹邪谐芯谢芯胁泻褍 褋锌芯胁褨褖械薪薪褟, 褟泻 褑械 写芯蟹胁芯谢械薪芯 锌褉懈褋褌褉芯褦屑</string>
+    <string name="pref_title_send_app_notifications">袧邪写褋懈谢邪褌懈 褋锌芯胁褨褖械薪薪褟</string>
+    <string name="pref_navigation_app_osmand">OsmAnd(+)</string>
+    <string name="pref_title_casio_alert_sms">小锌芯胁褨褖械薪薪褟 锌褉芯 SMS</string>
+    <string name="pref_device_action_dnd_off">袧械 褌褍褉斜褍胁邪褌懈 - 袙懈屑泻薪械薪芯</string>
+    <string name="silent_mode_normal_silent">袟胁懈褔邪泄薪懈泄 / 袘械蟹蟹胁褍褔薪懈泄</string>
+    <string name="activity_type_trail_run">孝褉械泄谢褉邪薪褨薪谐</string>
+    <string name="devicetype_mi_watch_color_sport">Mi Watch Color Sport</string>
+    <string name="pref_title_navigation_apps">袧邪胁褨谐邪褑褨泄薪褨 蟹邪褋褌芯褋褍薪泻懈</string>
+    <string name="menuitem_running">袘褨谐</string>
+    <string name="pref_sleep_mode_schedule_title">袚褉邪褎褨泻 褉械卸懈屑褍 褋薪褍</string>
+    <string name="clap_hands_to_wakeup_device">袩芯锌谢械褋泻邪泄褌械 胁 写芯谢芯薪褨, 褖芯斜 褍胁褨屑泻薪褍褌懈 械泻褉邪薪</string>"
+    <string name="bedtime">袩芯褔邪褌芯泻 褋薪褍</string>
+    <string name="pref_vitality_score_7_day_summary">袨褌褉懈屑褍泄褌械 褋锌芯胁褨褖械薪薪褟, 泻芯谢懈 胁邪褕 锌芯泻邪蟹薪懈泻 卸懈褌褌褦胁芯褩 械薪械褉谐褨褩 写芯褋褟谐薪械 30, 60 邪斜芯 100 蟹邪 芯褋褌邪薪薪褨 7 写薪褨胁</string>
+    <string name="devicetype_pixoo">Pixoo</string>
+    <string name="pref_vitality_score_title">袨褑褨薪泻邪 卸懈褌褌褦胁芯褩 械薪械褉谐褨褩</string>
+    <string name="pref_title_navigation_prefs">袧邪谢邪褕褌褍胁邪薪薪褟 薪邪胁褨谐邪褑褨褩</string>
+    <string name="menuitem_focus">袟芯褋械褉械写卸械薪薪褟</string>
+    <string name="pref_navigation_app_gmaps">袣邪褉褌懈 Google</string>
+    <string name="clap_hands_to_wakeup_device_summary">袩芯胁褌芯褉薪械 锌谢械褋泻邪薪薪褟 胁懈屑泻薪械 械泻褉邪薪</string>"
+    <string name="devicetype_mijia_lywsd03">Mijia Temperature 褌邪 Humidity Sensor 2</string>
+    <string name="pref_summary_prefix_notification_with_app">袩褉械褎褨泻褋 薪邪蟹胁懈 褋锌芯胁褨褖械薪薪褟 蟹 薪邪蟹胁芯褞 蟹邪褋褌芯褋褍薪泻褍-写卸械褉械谢邪</string>
+    <string name="serial_number">小械褉褨泄薪懈泄 薪芯屑械褉</string>
+    <string name="pref_summary_osmand_packagename">袙懈泻芯褉懈褋褌芯胁褍褦褌褜褋褟 写谢褟 胁懈斜芯褉褍 胁械褉褋褨褩 OsmAnd 写谢褟 锌褨写\'褦写薪邪薪薪褟</string>
+    <string name="wake_up_time">袩褉芯斜褍写卸械薪薪褟</string>
+    <string name="menuitem_stats">小褌邪褌懈褋褌懈泻邪</string>
+    <string name="not_set">袧械 薪邪谢邪褕褌芯胁邪薪芯</string>
+    <string name="pref_title_osmand_packagename">袧邪蟹胁邪 锌邪泻褍薪泻邪 OsmAnd</string>
+    <string name="pixoo_power_saving_summary">袝泻褉邪薪 胁懈屑懈泻邪褌懈屑械褌褜褋褟 锌褨褋谢褟 褌芯谐芯, 褟泻 屑褨泻褉芯褎芯薪 胁懈褟胁懈褌褜 褌懈褕褍 锌褉芯褌褟谐芯屑 锌械胁薪芯谐芯 褔邪褋褍</string>
+    <string name="pref_sleep_mode_schedule_summary">袧邪写褋懈谢邪褌懈 薪邪谐邪写褍胁邪薪薪褟 褌邪 锌械褉械褏芯写懈褌懈 胁 褉械卸懈屑 褋薪褍 锌械褉械写 褋薪芯屑. 校 蟹邪锌谢邪薪芯胁邪薪懈泄 褔邪褋 锌褉芯斜褍写卸械薪薪褟 锌褉芯谢褍薪邪褦 斜褍写懈谢褜薪懈泻.</string>
+    <string name="pref_vitality_score_7_day_title">袩褉芯谐褉械褋 蟹邪 7 写薪褨胁</string>
+    <string name="menuitem_alerts">袨锌芯胁褨褖械薪薪褟</string>
+    <string name="activity_type_wrestling">袘芯褉芯褌褜斜邪</string>
+    <string name="devicetype_xiaomi_watch_s1_active">Xiaomi Watch S1 Active</string>
+    <string name="widget_screen_no_free_slots_description">校 锌褉懈褋褌褉芯褩 薪械屑邪褦 胁褨谢褜薪懈褏 褋谢芯褌褨胁 写谢褟 械泻褉邪薪褨胁 胁褨写卸械褌褨胁 (褍褋褜芯谐芯 褋谢芯褌褨胁: %1$s)</string>
+    <string name="widget_screen_delete_confirm_description">袙懈 写褨泄褋薪芯 褏芯褔械褌械 胁懈写邪谢懈褌懈 \'%1$s\'?</string>
+    <string name="widget_screen_delete_confirm_title">袙懈写邪谢懈褌懈 械泻褉邪薪 胁褨写卸械褌邪</string>
+    <string name="widget">袙褨写卸械褌</string>
+    <string name="pref_vitality_score_daily_summary">袨褌褉懈屑褍胁邪褌懈 褋锌芯胁褨褖械薪薪褟, 泻芯谢懈 胁懈 写芯褋褟谐谢懈 屑邪泻褋懈屑邪谢褜薪芯褩 泻褨谢褜泻芯褋褌褨 斜邪谢褨胁 卸懈褌褌褦胁芯褩 械薪械褉谐褨褩 蟹邪 写械薪褜</string>
+    <string name="widget_screen">袝泻褉邪薪 胁褨写卸械褌邪</string>
+    <string name="pref_vitality_score_daily_title">些芯写械薪薪懈泄 锌褉芯谐褉械褋</string>
+    <string name="widget_layout">袦邪泻械褌 胁褨写卸械褌邪</string>
+    <string name="widget_unknown_workout">袧械胁褨写芯屑懈泄 褌懈锌 褌褉械薪褍胁邪薪薪褟 - %s</string>
+    <string name="widget_move_up">袩械褉械屑褨褋褌懈褌懈 胁懈褖械</string>
+    <string name="pref_title_fossil_hr_navigation_instructions">袉薪褋褌褉褍屑械薪褌懈 薪邪胁褨谐邪褑褨褩</string>
+    <string name="pref_summary_fossil_hr_navigation_instructions">袧邪谢邪褕褌褍胁邪褌懈 锌芯胁械写褨薪泻褍 薪邪胁褨谐邪褑褨泄薪芯谐芯 蟹邪褋褌芯褋褍薪泻褍 薪邪 谐芯写懈薪薪懈泻褍</string>
+    <string name="pref_summary_fossil_hr_nav_foreground">效懈 锌芯胁懈薪械薪 薪邪胁褨谐邪褑褨泄薪懈泄 蟹邪褋褌芯褋褍薪芯泻 邪胁褌芯屑邪褌懈褔薪芯 胁褨写泻褉懈胁邪褌懈褋褟 锌芯胁械褉褏 褨薪褕懈褏 胁褨泻芯薪, 泻芯谢懈 胁褨薪 芯褌褉懈屑褍褦 薪邪胁褨谐邪褑褨泄薪褨 胁泻邪蟹褨胁泻懈</string>
+    <string name="widget_layout_two">2 胁褨写卸械褌懈</string>
+    <string name="widget_layout_top_1_bot_2">1 胁谐芯褉褨, 2 胁薪懈蟹褍</string>
+    <string name="widget_layout_top_2_bot_2">2 胁谐芯褉褨, 2 胁薪懈蟹褍</string>
+    <string name="widget_screen_min_screens">袦邪褦 斜褍褌懈 锌褉懈薪邪泄屑薪褨 %1$s 械泻褉邪薪褨胁</string>
+    <string name="pref_title_fossil_hr_nav_vibrate">袙褨斜褉褍胁邪褌懈 芯褌褉懈屑邪胁褕懈 薪芯胁褍 胁泻邪蟹褨胁泻褍</string>
+    <string name="widget_move_down">袩械褉械屑褨褋褌懈褌懈 胁薪懈蟹</string>
+    <string name="widget_layout_single">1 胁褨写卸械褌</string>
+    <string name="pref_title_fossil_hr_nav_foreground">袩械褉械泄褌懈 锌芯胁械褉褏 褍褋褨褏 胁褨泻芯薪</string>
+    <string name="widget_subtype">袩褨写褌懈锌 胁褨写卸械褌邪</string>
+    <string name="widget_screen_x">袝泻褉邪薪 %s</string>
+    <string name="widget_missing_parts">袙懈斜械褉褨褌褜 褍褋褨 胁褨写卸械褌懈</string>
+    <string name="widget_layout_top_2_bot_1">2 胁谐芯褉褨, 1 胁薪懈蟹褍</string>
+    <string name="pref_summary_fossil_hr_nav_vibrate">效懈 锌芯胁懈薪械薪 谐芯写懈薪薪懈泻 胁褨斜褉褍胁邪褌懈 蟹邪 泻芯卸薪芯褞 薪芯胁芯褞 邪斜芯 蟹屑褨薪械薪芯褞 薪邪胁褨谐邪褑褨泄薪芯褞 胁泻邪蟹褨胁泻芯褞 (褌褨谢褜泻懈 泻芯谢懈 蟹邪褋褌芯褋褍薪芯泻 锌芯泻邪蟹邪薪芯 锌芯胁械褉褏 褨薪褕懈褏 胁褨泻芯薪)</string>
+    <string name="prefs_device_name">袧邪蟹胁邪 锌褉懈褋褌褉芯褞</string>
+    <string name="devicetype_redmi_watch_2_lite">Redmi Watch 2 Lite</string>
+    <string name="devicetype_redmi_smart_band_pro">Redmi Smart Band Pro</string>
+    <string name="devicetype_nothingear2">Nothing Ear (2)</string>
+    <string name="prefs_active_noise_cancelling_light">袥械谐泻械 邪泻褌懈胁薪械 褕褍屑芯锌芯谐谢懈薪邪薪薪褟</string>
+    <string name="notification_channel_connection_status_name">小褌邪薪 蟹\'褦写薪邪薪薪褟</string>
+    <string name="devicetype_nothingearstick">Nothing Ear (Stick)</string>
+    <string name="prefs_active_noise_cancelling_transparency">袩褉芯蟹芯褉褨褋褌褜</string>
+    <string name="updatefirmwareoperation_updateproblem_low_battery">袗泻褍屑褍谢褟褌芯褉 锌褉懈褋褌褉芯褞 蟹邪薪邪写褌芯 褉芯蟹褉褟写卸械薪懈泄</string>
+    <string name="do_not_disturb_lift_wrist_summary">袥懈褕械 褟泻褖芯 胁胁褨屑泻薪械薪芯 褎褍薪泻褑褨褞 \"袙屑懈泻邪褌懈 械泻褉邪薪 锌褨写薪械褋械薪薪褟屑 褉褍泻懈\"</string>
+    <string name="prefs_password_4_digits_0_to_9_summary">袩邪褉芯谢褜 锌芯胁懈薪械薪 褋泻谢邪写邪褌懈褋褟 蟹 4 褑懈褎褉 褌邪 屑褨褋褌懈褌懈 谢懈褕械 褑懈褎褉懈</string>
+    <string name="pref_summary_general_reconnectonlytoconnected">袩械褉械锌褨写\'褦写薪褍胁邪褌懈褋褟 谢懈褕械 蟹 锌褉懈胁\'褟蟹邪薪懈屑懈 锌褉懈褋褌褉芯褟屑懈, 蟹邪屑褨褋褌褜 锌械褉械锌褨写\'褦写薪邪薪薪褟 写芯 胁褋褨褏 锌褉懈褋褌褉芯褩胁</string>
+    <string name="prefs_disconnect_notification_summary">小锌芯胁褨褖械薪薪褟 薪邪 锌褉懈褋褌褉芯褩 蟹邪 胁褨写\'褦写薪邪薪薪褟 胁褨写 BT.</string>
+    <string name="heartrate_bpm_155">155 褍写/褏胁</string>
+    <string name="heartrate_bpm_165">165 褍写/褏胁</string>
+    <string name="heartrate_bpm_175">175 褍写/褏胁</string>
+    <string name="heartrate_bpm_185">185 褍写/褏胁</string>
+    <string name="heartrate_bpm_195">195 褍写/褏胁</string>
+    <string name="heartrate_bpm_205">205 褍写/褏胁</string>
+    <string name="pref_force_connection_type_bt_classic">Bluetooth Classic</string>
+    <string name="pref_title_general_reconnectonlytoconnected">袩芯胁褌芯褉薪芯 锌褨写\'褦写薪褍胁邪褌懈褋褜 谢懈褕械 写芯 锌芯胁\'褟蟹邪薪懈褏 锌褉懈褋褌褉芯褩胁</string>
+    <string name="pref_force_connection_type_ble">Bluetooth LE</string>
+    <string name="activity_type_indoor_running">袘褨谐 褍 锌褉懈屑褨褖械薪薪褨</string>
+    <string name="activity_type_crossfit">袣褉芯褋褎褨褌</string>
+    <string name="activity_type_functional_training">肖褍薪泻褑褨芯薪邪谢褜薪械 褌褉械薪褍胁邪薪薪褟</string>
+    <string name="activity_type_physical_training">肖褨蟹懈褔薪邪 锌褨写谐芯褌芯胁泻邪</string>
+    <string name="activity_type_taekwondo">孝褏械泻胁芯薪写芯</string>
+    <string name="activity_type_karate">袣邪褉邪褌械</string>
+    <string name="activity_type_ballet">袘邪谢械褌</string>
+    <string name="activity_type_other_dance">袉薪褕懈泄 褌邪薪械褑褜</string>
+    <string name="activity_type_stairs">袙锌褉邪胁懈 薪邪 褋褏芯写邪褏</string>
+    <string name="activity_type_karting">袣邪褉褌懈薪谐</string>
+    <string name="activity_type_billiards">袘褨谢褜褟褉写</string>
+    <string name="dnd_all_day">袙械褋褜 写械薪褜</string>
+    <string name="pref_header_audio">袗褍写褨芯</string>
+    <string name="pref_header_generic">袟邪谐邪谢褜薪褨</string>
+    <string name="pref_summary_notification_wake_on_open">袙屑懈泻邪褌懈 褌邪 褉芯蟹斜谢芯泻芯胁褍胁邪褌懈 Android 锌褉懈褋褌褉褨泄, 泻芯谢懈 谐邪写卸械褌 薪邪写褋懈谢邪褦 胁褨写锌芯胁褨写褜 \"袙袉袛袣袪袠孝袨\". 袦邪褦 斜褍褌懈 胁 写芯胁褨褉械薪芯屑褍 褋褌邪薪褨.</string>
+    <string name="proprietary_app_warning">笑褟 褎褍薪泻褑褨褟 锌芯褌褉械斜褍褦 胁褋褌邪薪芯胁谢械薪薪褟 锌褉芯锌褉褨褦褌邪褉薪芯谐芯 蟹邪褋褌芯褋褍薪泻褍</string>
+    <string name="pref_title_notification_wake_on_open">袗胁褌芯屑邪褌懈褔薪械 褍胁褨屑泻薪械薪薪褟 褌邪 褉芯蟹斜谢芯泻褍胁邪薪薪褟</string>
+    <string name="fw_upgrade_notice_amazfitbip3">袙懈 蟹斜懈褉邪褦褌械褋褟 胁褋褌邪薪芯胁懈褌懈 屑褨泻褉芯锌褉芯谐褉邪屑褍 %s 薪邪 胁邪褕 Amazfit Bip 3.
+\n
+\n小锌芯褔邪褌泻褍 胁褋褌邪薪芯胁褨褌褜 褎邪泄谢 .fw, 锌芯褌褨屑 .res. 袙邪褕 谐芯写懈薪薪懈泻 锌械褉械蟹邪胁邪薪褌邪卸懈褌褜褋褟 锌褨褋谢褟 胁褋褌邪薪芯胁谢械薪薪褟 褎邪泄谢褍 .fw.
+\n
+\n袩褉懈屑褨褌泻邪: 胁邪屑 薪械 锌芯褌褉褨斜薪芯 胁褋褌邪薪芯胁谢褞胁邪褌懈 褎邪泄谢 .res, 褟泻褖芯 胁褨薪 褌邪泻懈泄 褋邪屑懈泄, 褟泻 褉邪薪褨褕械 胁褋褌邪薪芯胁谢械薪懈泄.
+\n
+\n袩袪袨袛袨袙袞校袡孝袝 袧袗 袙袥袗小袧袠袡 袪袠袟袠袣!</string>
+    <string name="busy_task_fetch_sports_details_interrupted">袨褌褉懈屑邪薪薪褟 褋锌芯褉褌懈胁薪懈褏 锌芯写褉芯斜懈褑褜 斜褍谢芯 锌械褉械褉胁邪薪芯</string>
+    <string name="prefs_heartrate_alert_active_high_threshold">袩芯褉褨谐 锌芯锌械褉械写卸械薪薪褟 锌褉芯 胁懈褋芯泻懈泄 锌褍谢褜褋</string>
+    <string name="notif_battery_full_title">袗泻褍屑褍谢褟褌芯褉 锌褉懈褋褌褉芯褞 锌芯胁薪褨褋褌褞 蟹邪褉褟写卸械薪懈泄!</string>
+    <string name="notif_battery_full">袗泻褍屑褍谢褟褌芯褉 %1$s 锌芯胁薪褨褋褌褞 蟹邪褉褟写卸械薪懈泄</string>
+    <string name="pref_touch_tone">小懈谐薪邪谢 锌褉懈 写芯褌懈泻褍</string>
+    <string name="pref_touch_tone_summary">袙褨写褌胁芯褉褞胁邪褌懈 褋懈谐薪邪谢, 泻芯谢懈 胁懈 褌芯褉泻薪褍谢懈褋褟 薪邪胁褍褕薪懈泻邪</string>
+    <string name="pref_wearing_tone">小懈谐薪邪谢 锌褉懈 胁写褟谐邪薪薪褨</string>
+    <string name="pref_wearing_tone_summary">袙褨写褌胁芯褉褞胁邪褌懈 褋懈谐薪邪谢, 泻芯谢懈 胁懈 薪邪写褟谐薪褍谢懈 薪邪胁褍褕薪懈泻</string>
+    <string name="alarm_smart_wakeup_interval">袉薪褌械褉胁邪谢 褉芯蟹褍屑薪芯谐芯 锌褉芯斜褍写卸械薪薪褟:</string>
+    <string name="pref_agps_status">小褌邪薪 AGPS</string>
+    <string name="agps_status_missing">袙褨写褋褍褌薪褨泄</string>
+    <string name="agps_status_pending">袨褔褨泻褍褦褌褜褋褟</string>
+    <string name="agps_status_current">袩芯褌芯褔薪懈泄</string>
+    <string name="agps_status_error">袩芯屑懈谢泻邪</string>
+    <string name="alarm_smart_wakeup_interval_default">5 褏胁懈谢懈薪</string>
+    <string name="serbian">小械褉斜褋褜泻邪</string>
+    <string name="armenian">袙褨褉屑械薪褋褜泻邪</string>
+    <string name="activity_prefs_height_inches">袟褉褨褋褌 褍 写褞泄屑邪褏</string>
+    <string name="weekstepschart_steps_a_week_or_month">袣褉芯泻褨胁 蟹邪 褌懈卸写械薪褜/屑褨褋褟褑褜</string>
+    <string name="weeksleepchart_sleep_a_week_or_month">小芯薪 蟹邪 褌懈卸写械薪褜/屑褨褋褟褑褜</string>
+    <string name="authentication_failed_check_key">袧械 胁写邪谢芯褋褟 邪胁褌械薪褌懈褎褨泻褍胁邪褌懈, 锌械褉械胁褨褉褌械 泻谢褞褔 邪胁褌械薪褌懈褎褨泻邪褑褨褩</string>
+    <string name="hydration_dnd_summary">袙懈屑泻薪褍褌懈 锌芯锌械褉械写卸械薪薪褟 锌褉芯 薪械芯斜褏褨写薪褨褋褌褜 胁懈锌懈褌懈 胁芯写懈 薪邪 锌械胁薪懈泄 褔邪褋</string>
+    <string name="pref_do_not_disturb_not_wear">袧械 褌褍褉斜褍胁邪褌懈, 泻芯谢懈 胁懈 薪械 薪芯褋懈褌械 锌褉懈褋褌褉褨泄</string>
+    <string name="manual">袙褉褍褔薪褍</string>
+    <string name="devicetype_garmin_epix_pro">Garmin Epix Pro</string>
+    <string name="activity_type_battle_rope">袘芯泄芯胁懈泄 泻邪薪邪褌</string>
+    <string name="activity_type_archery">小褌褉褨谢褜斜邪 蟹 谢褍泻邪</string>
+    <string name="activity_type_mountain_hike">袩芯褏褨写 褍 谐芯褉懈</string>
+    <string name="devicetype_garmin_fenix_5">Garmin Fenix 5</string>
+    <string name="devicetype_garmin_fenix_6">Garmin Fenix 6</string>
+    <string name="devicetype_garmin_instinct_2s_solar">Garmin Instinct 2S Solar</string>
+    <string name="devicetype_garmin_instinct_2x_solar">Garmin Instinct 2X Solar</string>
+    <string name="activity_type_fencing">肖械褏褌褍胁邪薪薪褟</string>
+    <string name="activity_type_aerobic_exercise">袗械褉芯斜薪械 褌褉械薪褍胁邪薪薪褟</string>
+    <string name="activity_type_plank">袩谢邪薪泻邪</string>
+    <string name="activity_type_javelin">袦械褌邪薪薪褟 褋锌懈褋褍</string>
+    <string name="activity_type_long_jump">小褌褉懈斜泻懈 褍 写芯胁卸懈薪褍</string>
+    <string name="activity_type_trampoline">袙锌褉邪胁懈 薪邪 斜邪褌褍褌褨</string>
+    <string name="activity_type_darts">袛邪褉褌褋</string>
+    <string name="activity_type_kite_flying">袟邪锌褍褋泻邪薪薪褟 锌芯胁褨褌褉褟薪芯谐芯 蟹屑褨褟</string>
+    <string name="activity_type_hand_cycling">袙锌褉邪胁懈 蟹 褏械薪写斜邪泄泻芯屑</string>
+    <string name="activity_type_softball">小芯褎褌斜芯谢</string>
+    <string name="activity_type_dodgeball">袙懈斜懈胁邪谢懈</string>
+    <string name="activity_type_sailing">袙褨褌褉懈谢褜薪懈泻</string>
+    <string name="activity_type_outdoor_walking">啸芯写褜斜邪 薪邪写胁芯褉褨</string>
+    <string name="devicetype_amazfit_bip5_unity">Amazfit Bip 5 Unity</string>
+    <string name="activity_type_rower">袙械褋谢褍胁邪薪薪褟</string>
+    <string name="activity_type_skating">袣邪褌邪薪薪褟 薪邪 泻芯胁蟹邪薪邪褏</string>
+    <string name="activity_type_parallel_bar">袙锌褉邪胁懈 薪邪 锌邪褉邪谢械谢褜薪懈褏 斜褉褍褋邪褏</string>
+    <string name="activity_type_cooldown">袪芯蟹屑懈薪泻邪</string>
+    <string name="devicetype_garmin_venu_3s">Garmin Venu 3S</string>
+    <string name="devicetype_garmin_fenix_6_sapphire">Garmin Fenix 6 Sapphire</string>
+    <string name="devicetype_garmin_instinct">Garmin Instinct</string>
+    <string name="activity_type_free_training">袙褨谢褜薪械 褌褉械薪褍胁邪薪薪褟</string>
+    <string name="activity_type_dynamic_cycle">袙械谢芯褋锌芯褉褌</string>
+    <string name="activity_type_kendo">袣械薪写芯</string>
+    <string name="activity_type_sit_ups">袙锌褉邪胁懈 薪邪 锌褉械褋</string>
+    <string name="activity_type_flexibility">袙锌褉邪胁懈 薪邪 谐薪褍褔泻褨褋褌褜</string>
+    <string name="activity_type_track_and_field">袥械谐泻邪 邪褌谢械褌懈泻邪</string>
+    <string name="activity_type_push_ups">袙褨写褌懈褋泻邪薪薪褟</string>
+    <string name="activity_type_belly_dance">孝邪薪械褑褜 卸懈胁芯褌邪</string>
+    <string name="activity_type_jazz_dance">袛卸邪蟹芯胁懈泄 褌邪薪械褑褜</string>
+    <string name="activity_type_latin_dance">袥邪褌懈薪芯邪屑械褉懈泻邪薪褋褜泻懈泄 褌邪薪械褑褜</string>
+    <string name="activity_type_shuttlecock">袘邪写屑褨薪褌芯薪</string>
+    <string name="activity_type_australian_football">袗胁褋褌褉邪谢褨泄褋褜泻懈泄 褎褍褌斜芯谢</string>
+    <string name="activity_type_curling">袣械褉谢褨薪谐</string>
+    <string name="activity_type_skateboarding">小泻械泄褌斜芯褉写懈薪谐</string>
+    <string name="activity_type_rock_climbing">小泻械谢械谢邪蟹褨薪薪褟</string>
+    <string name="activity_type_hunting">袩芯谢褞胁邪薪薪褟</string>
+    <string name="devicetype_miband8active">Xiaomi Smart Band 8 Active</string>
+    <string name="devicetype_amazfit_bip3">Amazfit Bip 3</string>
+    <string name="devicetype_garmin_vivomove_style">Garmin V铆vomove Style</string>
+    <string name="devicetype_garmin_vivomove_trend">Garmin V铆vomove Trend</string>
+    <string name="devicetype_garmin_venu_2">Garmin Venu 2</string>
+    <string name="devicetype_garmin_instinct_solar">Garmin Instinct Solar</string>
+    <string name="activity_type_smith_machine">孝褉械薪邪卸械褉 小屑褨褌邪</string>
+    <string name="activity_type_pull_ups">袩褨写褌褟谐褍胁邪薪薪褟</string>
+    <string name="activity_type_dumbbell">袙锌褉邪胁懈 蟹 谐邪薪褌械谢褟屑懈</string>
+    <string name="activity_type_shot">楔褌芯胁褏邪薪薪褟 褟写褉邪</string>
+    <string name="activity_type_mind_and_body">袪芯蟹褍屑 褨 褌褨谢芯</string>
+    <string name="activity_type_kabaddi">袣邪斜邪写写褨</string>
+    <string name="activity_type_pickleball">袩褨泻谢斜芯谢</string>
+    <string name="activity_type_jet_skiing">袩谢邪胁邪薪薪褟 薪邪 谐褨写褉芯褑懈泻谢褨</string>
+    <string name="activity_type_ice_hockey">啸芯泻械泄 薪邪 谢褜芯写褍</string>
+    <string name="devicetype_miband8pro">Xiaomi Smart Band 8 Pro</string>
+    <string name="devicetype_garmin_vivomove_hr">Garmin V铆vomove HR</string>
+    <string name="devicetype_garmin_venu_3">Garmin Venu 3</string>
+    <string name="devicetype_garmin_fenix_7_pro">Garmin Fenix 7 Pro</string>
+    <string name="activity_type_cross_trainer">袝谢褨锌褌懈褔薪懈泄 褌褉械薪邪卸械褉</string>
+    <string name="activity_type_stair_stepper">孝褉械薪邪卸械褉 褋褏芯写懈薪泻懈</string>
+    <string name="activity_type_fitness_exercises">肖褨褌薪械褋 胁锌褉邪胁懈</string>
+    <string name="activity_type_cross_training">袣芯屑锌谢械泻褋薪械 褌褉械薪褍胁邪薪薪褟</string>
+    <string name="activity_type_fitness_gaming">肖褨褌薪械褋-褨谐褉懈</string>
+    <string name="activity_type_rolling">袣芯褔械薪薪褟</string>
+    <string name="activity_type_cross_country_running">袣褉芯褋</string>
+    <string name="activity_type_horizontal_bar">袙锌褉邪胁懈 薪邪 锌械褉械泻谢邪写懈薪褨</string>
+    <string name="activity_type_hula_hooping">袙锌褉邪胁懈 蟹 芯斜褉褍褔械屑</string>
+    <string name="activity_type_disc_sports">袙锌褉邪胁懈 蟹 褎褉懈蟹斜褨</string>
+    <string name="activity_type_tai_chi">孝邪泄褑蟹褨褑褞邪薪褜</string>
+    <string name="activity_type_horse_riding">袙械褉褏芯胁邪 褩蟹写邪</string>
+    <string name="devicetype_garmin_fenix_7s">Garmin Fenix 7S</string>
+    <string name="activity_type_roller_skating">袣邪褌邪薪薪褟 薪邪 褉芯谢懈泻邪褏</string>
+    <string name="activity_type_martial_arts">袘芯泄芯胁褨 屑懈褋褌械褑褌胁邪</string>
+    <string name="activity_type_swing">袙锌褉邪胁懈 蟹 谐芯泄写邪谢泻芯褞</string>
+    <string name="activity_type_fishing">袪懈斜邪谢褜褋褌胁芯</string>
+    <string name="activity_type_lacross">袥邪泻褉芯褋</string>
+    <string name="activity_type_snow_sports">袟懈屑芯胁褨 胁懈写懈 褋锌芯褉褌褍</string>
+    <string name="devicetype_garmin_forerunner_255">Garmin Forerunner 255</string>
+    <string name="activity_type_high_jump">小褌褉懈斜泻懈 褍 胁懈褋芯褌褍</string>
+    <string name="devicetype_garmin_instinct_2_solar">Garmin Instinct 2 Solar</string>
+    <string name="devicetype_garmin_forerunner_245">Garmin Forerunner 245</string>
+    <string name="devicetype_garmin_instinct_2_soltac">Garmin Instinct 2 SolTac</string>
+    <string name="devicetype_garmin_instinct_crossover">Garmin Instinct Crossover</string>
+    <string name="devicetype_garmin_venu_2_plus">Garmin Venu 2 Plus</string>
+    <string name="devicetype_garmin_instinct_2s">Garmin Instinct 2S</string>
+    <string name="swolfMax">袦邪泻褋懈屑邪谢褜薪懈泄 SWOLF</string>
+    <string name="devicetype_honor_band7">Honor Band 7</string>
+    <string name="notification_channel_full_battery_name">袗泻褍屑褍谢褟褌芯褉 蟹邪褉褟写卸械薪芯</string>
+    <string name="devicetype_garmin_vivoactive_3">Garmin V铆voactive 3</string>
+    <string name="devicetype_garmin_vivoactive_4s">Garmin V铆voactive 4S</string>
+    <string name="devicetype_sony_linkbuds">Sony LinkBuds</string>
+    <string name="devicetype_honor_band5">Honor Band 5</string>
+    <string name="devicetype_huawei_band_aw70">Huawei Band (AW70)</string>
+    <string name="devicetype_huawei_band6">Huawei Band 6</string>
+    <string name="devicetype_huawei_watch_gt">Huawei Watch GT</string>
+    <string name="devicetype_garmin_forerunner_265">Garmin Forerunner 265</string>
+    <string name="devicetype_redmi_watch_2">Redmi Watch 2</string>
+    <string name="devicetype_redmi_watch_4">Redmi Watch 4</string>
+    <string name="devicetype_huawei_band7">Huawei Band 7</string>
+    <string name="devicetype_hama_fit6900">Hama Fit6900</string>
+    <string name="swolfAvg">小械褉械写薪褨泄 SWOLF</string>
+    <string name="swolfMin">袦褨薪褨屑邪谢褜薪懈泄 SWOLF</string>
+    <string name="devicetype_garmin_vivosmart_5">Garmin V铆vosmart 5</string>
+    <string name="devicetype_huawei_band8">Huawei Band 8</string>
+    <string name="devicetype_huawei_band4pro">Huawei Band 4 (Pro)</string>
+    <string name="devicetype_huawei_watchfit2">Huawei Watch Fit 2</string>
+    <string name="devicetype_garmin_vivoactive_4">Garmin V铆voactive 4</string>
+    <string name="devicetype_garmin_forerunner_255s">Garmin Forerunner 255S</string>
+    <string name="devicetype_garmin_vivoactive_5">Garmin V铆voactive 5</string>
+    <string name="devicetype_moondrop_space_travel">Moondrop Space Travel</string>
+    <string name="devicetype_soundcore_liberty3_pro">Soundcore Liberty 3 Pro</string>
+    <string name="devicetype_huawei_talk_band_b6">Huawei Talk Band B6</string>
+    <string name="devicetype_huawei_watchgt3">Huawei Watch GT 3 (Pro)</string>
+    <string name="devicetype_huawei_watchgt4">Huawei Watch GT 4</string>
+    <string name="devicetype_redmiwatch3">Redmi Watch 3</string>
+    <string name="menuitem_buzzer_intensity">袉薪褌械薪褋懈胁薪褨褋褌褜 褋懈谐薪邪谢褍</string>
+    <string name="flatDistance">袙褨写褋褌邪薪褜 锌芯 褉褨胁薪懈薪褨</string>
+    <string name="devicetype_honor_band6">Honor Band 6</string>
+    <string name="preferences_qhybrid_settings_summary">袟邪褋褌邪褉褨谢褨 薪邪谢邪褕褌褍胁邪薪薪褟 写谢褟 谐芯写懈薪薪懈泻褨胁 Q Hybrid</string>
+    <string name="devicetype_garmin_vivosport">Garmin V铆vosport</string>
+    <string name="devicetype_honor_band3">Honor Band 3</string>
+    <string name="devicetype_huawei_watchgt2e">Huawei Watch GT 2e</string>
+    <string name="devicetype_garmin_swim_2">Garmin Swim 2</string>
+    <string name="devicetype_sony_wi_sp600n">Sony WI-SP600N</string>
+    <string name="devicetype_honor_band4">Honor Band 4</string>
+    <string name="devicetype_huawei_watchfit">Huawei Watch Fit</string>
+    <string name="devicetype_huawei_watchultimate">Huawei Watch Ultimate</string>
+    <string name="devicetype_honor_magicwatch2">Honor MagicWatch 2</string>
+    <string name="devicetype_huawei_watch4pro">Huawei Watch 4 (Pro)</string>
+    <string name="devicetype_huawei_band9">Huawei Band 9</string>
+    <string name="devicetype_huawei_watchgt2">Huawei Watch GT 2 (Pro)</string>
+    <string name="devicetype_mijia_mho_c303">Mijia MHO-C303</string>
+    <string name="devicetype_huawei_watchfit3">Huawei Watch Fit 3</string>
+    <plurals name="amount_of_days">
+        <item quantity="one">%d 写械薪褜</item>
+        <item quantity="few">%d 写薪褨</item>
+        <item quantity="many">%d 写薪褨胁</item>
+        <item quantity="other">%d 写薪褨胁</item>
+    </plurals>
+    <string name="devicetype_nothing_cmf_watch_pro_2">CMF Watch Pro 2</string>
+    <string name="swingAngleAvg">小械褉械写薪褨泄 泻褍褌 薪邪褏懈谢褍</string>
+    <string name="impactAvg">小械褉械写薪褨泄 胁锌谢懈胁</string>
+    <string name="impactMax">袦邪泻褋懈屑邪谢褜薪懈泄 胁锌谢懈胁</string>
+    <string name="backFootLandings">袩褉懈蟹械屑谢械薪薪褟 薪邪 蟹邪写薪褞 褔邪褋褌懈薪褍 褋褌芯锌懈</string>
+    <string name="eversionAngleAvg">小械褉械写薪褨泄 泻褍褌 胁懈胁械褉褌邪薪薪褟</string>
+    <string name="stepLengthAvg">小械褉械写薪褟 写芯胁卸懈薪邪 泻褉芯泻褍</string>
+    <string name="groundContactTimeAvg">小械褉械写薪褨泄 褔邪褋 泻芯薪褌邪泻褌褍 蟹 锌芯胁械褉褏薪械褞</string>
+    <string name="foreFootLandings">袩褉懈蟹械屑谢械薪薪褟 薪邪 锌械褉械写薪褞 褔邪褋褌懈薪褍 褋褌芯锌懈</string>
+    <string name="stepRateAvg">小械褉械写薪褟 褔邪褋褌芯褌邪 泻褉芯泻褨胁</string>
+    <string name="midFootLandings">袩褉懈蟹械屑谢械薪薪褟 薪邪 褋械褉械写薪褞 褔邪褋褌懈薪褍 褋褌芯锌懈</string>
+    <string name="stepRateSum">小褍屑邪 褔邪褋褌芯褌懈 泻褉芯泻褨胁</string>
+    <string name="abstract_chart_fragment_kind_awake_sleep">袩褉芯斜褍写卸械薪薪褟</string>
+    <string name="sleep_colored_stats_deep">袚谢懈斜芯泻懈泄</string>
+    <string name="sleep_colored_stats_light">袧械谐谢懈斜芯泻懈泄</string>
+    <string name="stats_empty_value">-</string>
+    <string name="stats_lowest_hr">袧邪泄薪懈卸褔懈泄 锌褍谢褜褋</string>
+    <string name="stats_highest_hr">袧邪泄胁懈褖懈泄 锌褍谢褜褋</string>
+    <string name="pref_dashboard_all_devices_title">校褋褨 锌褉懈褋褌褉芯褩</string>
+    <string name="pref_title_notification_times_enabled">效邪褋 褋锌芯胁褨褖械薪褜</string>
+    <string name="garmin_agps_local_file">袥芯泻邪谢褜薪懈泄 褎邪泄谢</string>
+    <string name="no_folder_selected">孝械泻褍 薪械 胁懈斜褉邪薪芯</string>
+    <string name="minutes_20">20 褏胁懈谢懈薪</string>
+    <string name="pref_sleepasandroid_feat_oximetry">袨泻褋懈屑械褌褉褨褟</string>
+    <string name="pref_dashboard_widget_today_hr_interval_title">袉薪褌械褉胁邪谢 胁懈屑褨褉褞胁邪薪薪褟 锌褍谢褜褋褍</string>
+    <string name="minutes_60">60 褏胁懈谢懈薪</string>
+    <string name="folder">孝械泻邪</string>
+    <string name="pref_summary_notification_times_enabled">袧邪写褋懈谢邪褌懈 褋锌芯胁褨褖械薪薪褟 谢懈褕械 褍 胁泻邪蟹邪薪懈泄 锌褉芯屑褨卸芯泻 褔邪褋褍</string>
+    <string name="pref_title_wheel_diameter">袛褨邪屑械褌褉 泻芯谢械褋邪</string>
+    <string name="pref_title_bottom_navigation_bar">袧懈卸薪褟 锌邪薪械谢褜 薪邪胁褨谐邪褑褨褩</string>
+    <string name="pref_sleepasandroid_feat_heartrate">袩褍谢褜褋</string>
+    <string name="bottom_nav_devices">袩褉懈褋褌褉芯褩</string>
+    <string name="pref_dashboard_select_devices_title">袙懈斜褉邪褌懈 锌褉懈褋褌褉芯褩...</string>
+    <string name="chart_cycling_point_label_distance">小褜芯谐芯写薪褨: %.1f 泻屑
+\n袙褋褜芯谐芯: %.1f 泻屑</string>
+    <string name="pref_sleepasandroid_feat_spo2">SPO2</string>
+    <string name="pref_sleepasandroid_feat_movement">袗泻褋械谢械褉芯屑械褌褉</string>
+    <string name="folder_is_empty">孝械泻邪 锌芯褉芯卸薪褟</string>
+    <string name="chart_cycling_point_label_speed">%.1f 泻屑/谐芯写</string>
+    <string name="loading">袟邪胁邪薪褌邪卸械薪薪褟鈥�</string>
+    <string name="show_in_notification">袩芯泻邪蟹邪褌懈 褋锌芯胁褨褖械薪薪褟</string>
+    <string name="unsupported">袧械 锌褨写褌褉懈屑褍褦褌褜褋褟</string>
+    <string name="copied_to_clipboard">小泻芯锌褨泄芯胁邪薪芯 写芯 斜褍褎械褉邪 芯斜屑褨薪褍</string>
+    <string name="min_val">袦褨薪褨屑褍屑: %d</string>
+    <string name="max_val">袦邪泻褋懈屑褍屑: %d</string>
+    <string name="battery_full_notify_enabled">小锌芯胁褨褖邪褌懈 锌褉芯 蟹邪胁械褉褕械薪薪褟 蟹邪褉褟写卸邪薪薪褟</string>
+    <string name="battery_percentage_str">%1$s%%</string>
+    <string name="pref_sleepasandroid_feat_alarms">袘褍写懈谢褜薪懈泻懈</string>
+    <string name="battery_low_notify_enabled">小锌芯胁褨褖邪褌懈 锌褉芯 薪懈蟹褜泻懈泄 蟹邪褉褟写</string>
+    <string name="battery_low_threshold">袩芯褉褨谐 薪懈蟹褜泻芯谐芯 蟹邪褉褟写褍</string>
+    <string name="battery_full_threshold">袩芯褉褨谐 蟹邪胁械褉褕械薪薪褟 蟹邪褉褟写卸邪薪薪褟</string>
+    <string name="default_percentage">校褋褌邪谢械薪芯 (%1$d%%)</string>
+    <string name="pref_header_calls_and_notifications">袙懈泻谢懈泻懈 褌邪 褋锌芯胁褨褖械薪薪褟</string>
+    <string name="pref_sleepasandroid_feat_notifications">小锌芯胁褨褖械薪薪褟</string>
+    <string name="url">URL</string>
+    <string name="hrv_status">小褌邪薪 HRV</string>
+    <string name="gps_glonass">GPS + GLONASS</string>
+    <string name="activity_type_beach_soccer">袩谢褟卸薪懈泄 褎褍褌斜芯谢</string>
+    <string name="activity_type_beach_volleyball">袩谢褟卸薪懈泄 胁芯谢械泄斜芯谢</string>
+    <string name="activity_type_parkour">袩邪褉泻褍褉</string>
+    <string name="activity_type_aerobics">袗械褉芯斜褨泻邪</string>
+    <string name="activity_type_chess">楔邪褏懈</string>
+    <string name="activity_type_folk_dance">袧邪褉芯写薪懈泄 褌邪薪械褑褜</string>
+    <string name="hrv">HRV</string>
+    <string name="activity_type_biathlon">袘褨邪褌谢芯薪</string>
+    <string name="pref_time_sync">袗胁褌芯褋懈薪褏褉芯薪褨蟹邪褑褨褟 褔邪褋褍</string>
+    <string name="user_feedback_set_settings_ok">袧邪谢邪褕褌褍胁邪薪薪褟 薪邪写褨褋谢邪薪芯 薪邪 锌褉懈褋褌褉褨泄.</string>
+    <string name="minutes_45">45 褏胁懈谢懈薪</string>
+    <string name="minutes_75">75 褏胁懈谢懈薪</string>
+    <string name="minutes_7">7 褏胁懈谢懈薪</string>
+    <string name="minutes_8">8 褏胁懈谢懈薪</string>
+    <string name="minutes_9">9 褏胁懈谢懈薪</string>
+    <string name="minutes_15">15 褏胁懈谢懈薪</string>
+    <string name="dateformat_day_month">袛械薪褜, 屑褨褋褟褑褜</string>
+    <string name="dateformat_month_day">袦褨褋褟褑褜, 写械薪褜</string>
+    <string name="busy_task_fetch_sleep_data">袨褌褉懈屑邪薪薪褟 写邪薪懈褏 锌褉芯 褋芯薪</string>
+    <string name="minutes_255">255 褏胁懈谢懈薪</string>
+    <string name="stress_average">小械褉械写薪褨泄</string>
+    <string name="pref_header_sound">袟胁褍泻</string>
+    <string name="choose_device">袨斜褉邪褌懈 锌褉懈褋褌褉褨泄</string>
+    <string name="no_supported_devices_found">袧械 蟹薪邪泄写械薪芯 锌褨写褌褉懈屑褍胁邪薪懈褏 锌褉懈褋褌褉芯褩胁</string>
+    <string name="search">袩芯褕褍泻</string>
+    <string name="minutes_3">3 褏胁懈谢懈薪懈</string>
+    <string name="minutes_6">6 褏胁懈谢懈薪</string>
+    <string name="minutes_90">90 褏胁懈谢懈薪</string>
+    <string name="minutes_120">120 褏胁懈谢懈薪</string>
+    <string name="minutes_150">150 褏胁懈谢懈薪</string>
+    <string name="minutes_210">210 褏胁懈谢懈薪</string>
+    <string name="minutes_4">4 褏胁懈谢懈薪懈</string>
+    <string name="minutes_2">2 褏胁懈谢懈薪懈</string>
+    <string name="minutes_180">180 褏胁懈谢懈薪</string>
+    <string name="minutes_240">240 褏胁懈谢懈薪</string>
+    <string name="activity_type_rem_sleep">楔胁懈写泻懈泄 褋芯薪</string>
+    <string name="title_activity_musicmanager">袦褍蟹懈泻邪谢褜薪懈泄 屑械薪械写卸械褉</string>
+    <string name="pref_description_user_interface">孝械屑邪, 谐芯谢芯胁薪懈泄 械泻褉邪薪</string>
+    <string name="pref_description_dashboard">袙褨写卸械褌懈, 锌褉懈褋褌褉芯褩 写谢褟 写芯写邪胁邪薪薪褟</string>
+    <string name="pref_description_automations">袗胁褌芯械泻褋锌芯褉褌, 邪胁褌芯芯褌褉懈屑邪薪薪褟</string>
+    <string name="pref_description_developer_options">袞褍褉薪邪谢懈, Intent API</string>
+    <string name="pref_description_deprecated_functionalities">袧邪谢邪褕褌褍胁邪薪薪褟, 褟泻褨 斜褍写褍褌褜 胁懈写邪谢械薪褨 胁 薪邪褋褌褍锌薪褨泄 胁械褉褋褨褩</string>
+    <string name="pref_title_user_interface">袉薪褌械褉褎械泄褋 泻芯褉懈褋褌褍胁邪褔邪</string>
+    <string name="pref_title_nagivation_apps">袟邪褋褌芯褋褍薪泻懈 写谢褟 薪邪胁褨谐邪褑褨褩</string>
+    <string name="pref_header_external_integrations">袟芯胁薪褨褕薪褨 褨薪褌械谐褉邪褑褨褩</string>
+    <string name="pref_header_automations">袗胁褌芯屑邪褌懈蟹邪褑褨褟</string>
+    <string name="pref_description_general">袟邪锌褍褋泻, 屑芯胁邪, 褉械谐褨芯薪, 褉芯蟹褌邪褕褍胁邪薪薪褟</string>
+    <string name="pref_description_about_you">袛邪褌邪 薪邪褉芯写卸械薪薪褟, 褋褌邪褌褜, 蟹褉褨褋褌, 胁邪谐邪, 褑褨谢褨</string>
+    <string name="pref_description_notifications">小锌芯胁褨褖械薪薪褟 褍 蟹邪褋褌芯褋褍薪泻褍, 斜褨谢懈泄/褔芯褉薪懈泄 褋锌懈褋芯泻</string>
+    <string name="pref_header_main_screen">袚芯谢芯胁薪懈泄 械泻褉邪薪</string>
+</resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-v14/dimens.xml b/app/src/main/res/values-v14/dimens.xml
new file mode 100644
index 0000000..4db8c59
--- /dev/null
+++ b/app/src/main/res/values-v14/dimens.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+    <!--
+Refer to App Widget Documentation for margin information
+http://developer.android.com/guide/topics/appwidgets/index.html#CreatingLayout
+    -->
+    <dimen name="widget_margin">0dp</dimen>
+
+</resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml
new file mode 100644
index 0000000..29856da
--- /dev/null
+++ b/app/src/main/res/values-vi/strings.xml
@@ -0,0 +1,403 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <string name="application_name_generic">Gadgetbridge</string>
+    <string name="title_activity_controlcenter_generic">Gadgetbridge</string>
+    <string name="action_settings">C脿i 膽岷穞</string>
+    <string name="action_debug">D貌 l峄梚</string>
+    <string name="action_quit">Tho谩t</string>
+    <string name="controlcenter_fetch_activity_data">膼峄搉g b峄�</string>
+    <string name="controlcenter_find_device">T矛m thi岷縯 b峄� th岷 l岷鈥�</string>
+    <string name="controlcenter_take_screenshot">Ch峄 岷h m脿n h矛nh</string>
+    <string name="controlcenter_disconnect">Hu峄� k岷縯 n峄慽</string>
+    <string name="title_activity_debug">D貌 l峄梚</string>
+    <!--Strings related to AppManager-->
+    <string name="title_activity_appmanager">Tr矛nh qu岷 l媒 峄﹏g d峄g</string>
+    <string name="appmananger_app_delete">Xo谩</string>
+    <!--Strings related to FwAppInstaller-->
+    <string name="title_activity_fw_app_insaller">Tr矛nh c脿i 膽岷穞 FW/峄﹏g d峄g</string>
+    <string name="fw_upgrade_notice">B岷 s岷痯 c脿i 膽岷穞 %s.</string>
+    <!--Strings related to Settings-->
+    <string name="title_activity_settings">C脿i 膽岷穞</string>
+    <string name="pref_header_general">C脿i 膽岷穞 chung</string>
+    <string name="pref_default">M岷穋 膽峄媙h</string>
+    <string name="pref_header_datetime">Ng脿y gi峄�</string>
+    <string name="pref_title_datetime_syctimeonconnect">膼峄搉g b峄� gi峄�</string>
+    <string name="pref_summary_datetime_syctimeonconnect">膼峄搉g b峄� th峄漣 gian 膽岷縩 thi岷縯 b峄� Gadgetbridge khi k岷縯 n峄慽 v脿 khi th峄漣 gian ho岷穋 m煤i gi峄� thay 膽峄昳 tr锚n thi岷縯 b峄� Android</string>
+    <string name="pref_header_notifications">Th么ng b谩o</string>
+    <string name="pref_title_notifications_repetitions">L岷穚 l岷</string>
+    <string name="pref_title_notifications_call">C谩c cu峄檆 g峄峣</string>
+    <string name="pref_title_notifications_sms">SMS</string>
+    <string name="pref_title_notifications_pebblemsg">Tin nh岷痭 Pebble</string>
+    <string name="always">Lu么n lu么n</string>
+    <string name="when_screen_off">Khi m脿n h矛nh t岷痶</string>
+    <string name="never">Kh么ng bao gi峄�</string>
+    <string name="pref_header_development">Tu峄� ch峄峮 cho nh脿 ph谩t tri峄僴</string>
+    <string name="pref_title_development_miaddr">膼峄媋 ch峄� Mi Band</string>
+    <string name="pref_title_pebble_settings">C脿i 膽岷穞 Pebble</string>
+    <string name="pref_title_enable_pebblekit">Cho ph茅p 峄﹏g d峄g Android b锚n th峄� ba quy峄乶 truy c岷璸</string>
+    <string name="not_connected">Ch瓢a k岷縯 n峄慽</string>
+    <string name="connecting">膼ang k岷縯 n峄慽</string>
+    <string name="connected">膼茫 k岷縯 n峄慽</string>
+    <string name="unknown_state">Tr岷g th谩i kh么ng x谩c 膽峄媙h</string>
+    <string name="_unknown_">(kh么ng r玫)</string>
+    <string name="test">Ki峄僲 tra</string>
+    <string name="test_notification">Ki峄僲 tra th么ng b谩o</string>
+    <string name="this_is_a_test_notification_from_gadgetbridge">膼芒y l脿 m峄檛 th么ng b谩o th峄� nghi峄噈 t峄� Gadgetbridge</string>
+    <string name="bluetooth_is_not_supported_">Kh么ng h峄� tr峄� Bluetooth.</string>
+    <string name="bluetooth_is_disabled_">膼茫 t岷痶 Bluetooth.</string>
+    <string name="cannot_connect_bt_address_invalid_">Kh么ng th峄� k岷縯 n峄慽. 膼峄媋 ch峄� Bluetooth kh么ng h峄 l峄嘰?</string>
+    <string name="gadgetbridge_running_generic">Gadgetbridge 膽ang ch岷</string>
+    <string name="installing_binary_d_d">膼ang c脿i t峄噋 nh峄� ph芒n %1$d/%2$d</string>
+    <string name="installation_failed_">C脿i 膽岷穞 th岷 b岷</string>
+    <string name="installation_successful">膼茫 c脿i 膽岷穞</string>
+    <string name="n_a">N/A</string>
+    <string name="initialized">膽茫 kh峄焛 ch岷</string>
+    <string name="appversion_by_creator">%1$s b峄焛 %2$s</string>
+    <string name="title_activity_discovery">Kh谩m ph谩 thi岷縯 b峄�</string>
+    <string name="discovery_stop_scanning">D峄玭g qu茅t</string>
+    <string name="discovery_start_scanning">B岷痶 膽岷 kh谩m ph谩</string>
+    <string name="action_discover">K岷縯 n峄慽 thi岷縯 b峄� m峄沬</string>
+    <string name="device_with_rssi">%1$s (%2$s)</string>
+    <string name="title_activity_android_pairing">Gh茅p 膽么i thi岷縯 b峄�</string>
+    <string name="title_activity_mi_band_pairing">Gh茅p 膽么i Mi Band</string>
+    <string name="pairing">膼ang gh茅p 膽么i v峄沬 %s鈥�</string>
+    <string name="message_cannot_pair_no_mac">Kh么ng c贸 膽峄媋 ch峄� MAC 膽瓢峄 cho, kh么ng th峄� gh茅p 膽么i.</string>
+    <string name="preferences_category_device_specific_settings">C脿i 膽岷穞 cho thi岷縯 b峄� c峄� th峄�</string>
+    <string name="preferences_miband_settings">C脿i 膽岷穞 Mi Band / Amazfit</string>
+    <string name="male">Nam</string>
+    <string name="female">N峄�</string>
+    <string name="other">Kh谩c</string>
+    <string name="left">Tr谩i</string>
+    <string name="right">Ph岷</string>
+    <string name="appinstaller_install">C脿i 膽岷穞</string>
+    <string name="discovery_note">Ghi ch煤:</string>
+    <string name="candidate_item_device_image">H矛nh 岷h thi岷縯 b峄�</string>
+    <string name="miband_prefs_alias">T锚n/B铆 danh</string>
+    <string name="title_activity_sleepmonitor">Gi谩m s谩t gi岷 ng峄�</string>
+    <string name="initializing">膼ang kh峄焛 ch岷</string>
+    <string name="sleep_activity_date_range">T峄� %1$s 膽岷縩 %2$s</string>
+    <string name="prefs_wearside">膼eo b锚n tr谩i hay ph岷?</string>
+    <string name="vibration_profile_short">Ng岷痭</string>
+    <string name="vibration_profile_medium">Trung b矛nh</string>
+    <string name="vibration_profile_long">D脿i</string>
+    <string name="pref_screen_notification_profile_sms">Th么ng b谩o SMS</string>
+    <string name="pref_header_vibration_settings">C脿i 膽岷穞 rung</string>
+    <string name="pref_screen_notification_profile_email">Th么ng b谩o email</string>
+    <string name="pref_screen_notification_profile_incoming_call">Th么ng b谩o cu峄檆 g峄峣 膽岷縩</string>
+    <string name="control_center_find_lost_device">T矛m thi岷縯 b峄� th岷 l岷</string>
+    <string name="control_center_cancel_to_stop_vibration">Hu峄� 膽峄� ng峄玭g rung.</string>
+    <string name="title_activity_charts">Ho岷 膽峄檔g v脿 Ng峄�</string>
+    <string name="title_activity_set_alarm">Thi岷縯 l岷璸 b谩o th峄ヽ</string>
+    <string name="controlcenter_start_configure_alarms">C岷 h矛nh b谩o th峄ヽ</string>
+    <string name="title_activity_alarm_details">Chi ti岷縯 b谩o th峄ヽ</string>
+    <string name="alarm_sun_short">CN</string>
+    <string name="alarm_mon_short">T2</string>
+    <string name="alarm_tue_short">T3</string>
+    <string name="alarm_wed_short">T4</string>
+    <string name="alarm_thu_short">T5</string>
+    <string name="alarm_fri_short">T6</string>
+    <string name="alarm_sat_short">T7</string>
+    <string name="pbwinstallhandler_app_item">%1$s (%2$s)</string>
+    <string name="chart_steps">S峄� b瓢峄沜</string>
+    <string name="live_activity_steps_history">L峄媍h s峄� b瓢峄沜 ch芒n</string>
+    <string name="live_activity_current_steps_per_minute">S峄� b瓢峄沜/ph煤t hi峄噉 th峄漣</string>
+    <string name="live_activity_total_steps">T峄昻g s峄� b瓢峄沜</string>
+    <string name="live_activity_steps_per_minute_history">L峄媍h s峄� s峄� b瓢峄沜 tr锚n ph煤t</string>
+    <string name="live_activity_start_your_activity">B岷痶 膽岷 ho岷 膽峄檔g</string>
+    <string name="abstract_chart_fragment_kind_activity">Ho岷 膽峄檔g</string>
+    <string name="abstract_chart_fragment_kind_light_sleep">Gi岷 ng峄� n么ng</string>
+    <string name="abstract_chart_fragment_kind_deep_sleep">Gi岷 ng峄� s芒u</string>
+    <string name="abstract_chart_fragment_kind_not_worn">Kh么ng 膽瓢峄 膽eo</string>
+    <string name="device_not_connected">Kh么ng k岷縯 n峄慽.</string>
+    <string name="user_feedback_all_alarms_disabled">膼茫 t岷痶 h岷縯 b谩o th峄ヽ</string>
+    <string name="pref_title_keep_data_on_device">Gi峄� d峄� li峄噓 ho岷 膽峄檔g tr锚n thi岷縯 b峄�</string>
+    <string name="miband_fwinstaller_incompatible_version">Ph岷 c峄﹏g kh么ng t瓢啤ng th铆ch</string>
+    <string name="fwinstaller_firmware_not_compatible_to_device">Ph岷 c峄﹏g n脿y kh么ng t瓢啤ng th铆ch v峄沬 thi岷縯 b峄�</string>
+    <string name="activity_prefs_about_you">Th么ng tin v峄� b岷</string>
+    <string name="activity_prefs_year_birth">N膬m sinh</string>
+    <string name="activity_prefs_gender">Gi峄沬 t铆nh</string>
+    <string name="activity_prefs_height_cm">Chi峄乽 cao b岷眓g cm</string>
+    <string name="activity_prefs_weight_kg">Tr峄峮g l瓢峄g b岷眓g kg</string>
+    <string name="action_donate">峄g h峄�</string>
+    <string name="controlcenter_change_led_color">膼峄昳 m脿u LED</string>
+    <string name="controlcenter_change_fm_frequency">膼峄昳 t岷 s峄� FM</string>
+    <string name="controlcenter_connect">K岷縯 n峄慽鈥�</string>
+    <string name="controlcenter_delete_device">X贸a thi岷縯 b峄�</string>
+    <string name="controlcenter_delete_device_name">X贸a %1$s</string>
+    <string name="controlcenter_delete_device_dialogmessage">S岷� x贸a thi岷縯 b峄� v脿 to脿n b峄� d峄� li峄噓 li锚n quan!</string>
+    <string name="controlcenter_snackbar_need_longpress">岷 gi峄� th岷� 膽峄� ng岷痶 k岷縯 n峄慽</string>
+    <string name="controlcenter_snackbar_disconnecting">膼ang ng岷痶 k岷縯 n峄慽</string>
+    <string name="controlcenter_snackbar_connecting">膼ang k岷縯 n峄慽鈥�</string>
+    <string name="controlcenter_snackbar_requested_screenshot">Ch峄 岷h m脿n h矛nh c峄 thi岷縯 b峄�</string>
+    <string name="controlcenter_calibrate_device">Hi峄噓 ch峄塶h thi岷縯 b峄�</string>
+    <string name="appmanager_cached_watchapps_watchfaces">峄╪g d峄g trong b峄� nh峄� t岷</string>
+    <string name="appmanager_installed_watchapps">峄╪g d峄g 膽茫 c脿i 膽岷穞</string>
+    <string name="appmanager_installed_watchfaces">M岷穞 膽峄搉g h峄� 膽茫 c脿i 膽岷穞</string>
+    <string name="appmananger_app_delete_cache">X贸a v脿 h峄 kh峄廼 b峄� nh峄� t岷</string>
+    <string name="appmananger_app_reinstall">C脿i 膽岷穞 l岷</string>
+    <string name="appmanager_app_openinstore">T矛m ki岷縨 tr锚n Pebble appstore</string>
+    <string name="appmanager_health_activate">K铆ch ho岷</string>
+    <string name="appmanager_health_deactivate">V么 hi峄噓 h贸a</string>
+    <string name="app_configure">T霉y ch峄塶h</string>
+    <string name="app_move_to_top">Di chuy峄僴 l锚n 膽岷</string>
+    <string name="pref_title_general_autoconnectonbluetooth">K岷縯 n峄慽 t峄沬 thi岷縯 b峄� Gadgetbridge khi m峄� Bluetooth</string>
+    <string name="pref_title_general_autostartonboot">T峄� 膽峄檔g kh峄焛 膽峄檔g</string>
+    <string name="pref_title_general_autoreconnect">T峄� 膽峄檔g k岷縯 n峄慽 l岷</string>
+    <string name="pref_title_audio_player">漂u ti锚n tr矛nh ph谩t nh岷</string>
+    <string name="pref_title_charts_swipe">B岷璽 qu岷箃 tr谩i/ph岷 cho bi峄僽 膽峄� ho岷 膽峄檔g</string>
+    <string name="pref_title_theme">Ch峄� 膽峄�</string>
+    <string name="pref_theme_light">S谩ng</string>
+    <string name="pref_theme_dark">T峄慽</string>
+    <string name="pref_title_language">Ng么n ng峄�</string>
+    <string name="pref_title_minimize_priority">岷╪ th么ng b谩o Gadgetbridge</string>
+    <string name="pref_summary_minimize_priority_off">Bi峄僽 t瓢峄g tr锚n thanh tr岷g th谩i v脿 th么ng b谩o tr锚n m脿n h矛nh kh贸a 膽瓢峄 hi峄僴 th峄�</string>
+    <string name="pref_summary_minimize_priority_on">Bi峄僽 t瓢峄g tr锚n thanh tr岷g th谩i v脿 th么ng b谩o tr锚n m脿n h矛nh kh贸a b峄� 岷﹏</string>
+    <string name="pref_title_notifications_timeout">Th峄漣 gian t峄慽 thi峄僽 gi峄痑 c谩c th么ng b谩o</string>
+    <string name="pref_summary_notifications_pebblemsg">H峄� tr峄� cho nh峄痭g 峄﹏g d峄g g峄璱 th么ng b谩o 膽岷縩 Pebble th么ng qua PebbleKit.</string>
+    <string name="pref_title_notifications_generic">H峄� tr峄� th么ng b谩o chung</string>
+    <string name="pref_title_whenscreenon">鈥农ng nh瓢 khi m脿n h矛nh 膽ang m峄�</string>
+    <string name="pref_title_notification_filter">Kh么ng L脿m Phi峄乶</string>
+    <string name="pref_summary_notification_filter">Nh峄痭g th么ng b谩o kh么ng mong mu峄憂 s岷� b峄� d峄玭g 峄� ch岷� 膽峄� n脿y</string>
+    <string name="pref_title_transliteration">Chuy峄僴 ng峄�</string>
+    <string name="pref_summary_transliteration">K铆ch ho岷 n岷縰 thi岷縯 b峄� c峄 b岷 kh么ng h峄� tr峄� ph么ng ch峄� c峄 ng么n ng峄� c峄 b岷</string>
+    <string name="pref_title_rtl">Ph岷-sang-Tr谩i</string>
+    <string name="pref_summary_rtl">K铆ch ho岷 n岷縰 thi岷縯 b峄� c峄 b岷 kh么ng th峄� hi峄僴 th峄� ng么n ng峄� d岷g ph岷-sang-tr谩i</string>
+    <string name="pref_rtl_max_line_length">Ph岷-Sang-Tr谩i t峄慽 膽a chi峄乽 d脿i c峄 h脿ng</string>
+    <string name="pref_header_privacy">Ri锚ng t瓢</string>
+    <string name="pref_title_call_privacy_mode">Ch岷� 膽峄� 膽脿m tho岷 ri锚ng t瓢</string>
+    <string name="pref_call_privacy_mode_off">Hi峄噉 t锚n v脿 s峄� 膽i峄噉 tho岷</string>
+    <string name="pref_call_privacy_mode_name">岷╪ t锚n nh瓢ng hi峄噉 s峄� 膽i峄噉 tho岷</string>
+    <string name="pref_call_privacy_mode_number">岷╪ s峄� 膽i峄噉 tho岷 nh瓢ng hi峄噉 t锚n</string>
+    <string name="pref_call_privacy_mode_complete">岷╪ t锚n v脿 s峄� 膽i峄噉 tho岷</string>
+    <string name="pref_title_weather">Th峄漣 ti岷縯</string>
+    <string name="pref_title_weather_location">V峄� tr铆 th峄漣 ti岷縯 (cho nh脿 cung c岷 th峄漣 ti岷縯 c峄 LineageOS)</string>
+    <string name="pref_title_canned_messages_set">C岷璸 nh岷璽 tr锚n thi岷縯 b峄�</string>
+    <string name="pref_title_pebble_sync_health">膼峄搉g b峄� Pebble Health</string>
+    <string name="pref_title_pebble_sync_misfit">膼峄搉g b峄� Misfit</string>
+    <string name="pref_title_pebble_sync_morpheuz">膼峄搉g b峄� Morpheuz</string>
+    <string name="pref_summary_sunrise_sunset">G峄璱 th峄漣 gian m岷穞 tr峄漣 m峄峜 v脿 m岷穞 tr峄漣 l岷穘 d峄盿 tr锚n v峄� tr铆 膽岷縩 d貌ng th峄漣 gian c峄 Pebble</string>
+    <string name="pref_title_enable_calendar_sync">膼峄搉g b峄� l峄媍h</string>
+    <string name="pref_summary_enable_calendar_sync">G峄璱 s峄� ki峄噉 tr锚n l峄媍h 膽岷縩 d貌ng th峄漣 gian</string>
+    <string name="pref_title_pebble_privacy_mode">Ch岷� 膽峄� ri锚ng t瓢</string>
+    <string name="pref_pebble_privacy_mode_off">Th么ng b谩o b矛nh th瓢峄漬g</string>
+    <string name="pref_header_location">膼峄媋 膽i峄僲</string>
+    <string name="pref_title_location_latitude">V末 膽峄�</string>
+    <string name="pref_title_location_longitude">Kinh 膽峄�</string>
+    <string name="pref_title_location_keep_uptodate">Gi峄� 膽峄媋 膽i峄僲 lu么n 膽瓢峄 c岷璸 nh岷璽</string>
+    <string name="dateformat_time">Gi峄�</string>
+    <string name="dateformat_date_time">Gi峄� v脿 ng脿y</string>
+    <string name="mi2_prefs_button_press_broadcast_default_value" translatable="false">nodomain.freeyourgadget.gadgetbridge.ButtonPressed</string>
+    <string name="sonyswr12_settings_title">C脿i 膽岷穞 Sony SWR12</string>
+    <string name="sonyswr12_settings_stamina">Ch岷� 膽峄� ti岷縯 ki峄噈 pin 膽ang b岷璽</string>
+    <string name="sonyswr12_settings_alarm_interval">Kho岷g th峄漣 gian b谩o th峄ヽ th么ng minh trong v脿i ph煤t</string>
+    <string name="sonyswr12_settings_low_vibration_summary">B岷璽 c瓢峄漬g 膽峄� rung th岷 tr锚n thi岷縯 b峄� 膽eo tay</string>
+    <string name="sonyswr12_settings_stamina_summary">Ch岷� 膽峄� ti岷縯 ki峄噈 n膬ng l瓢峄g t岷痶 t铆nh n膬ng 膽o nh峄媝 tim t峄� 膽峄檔g 膽峄媙h k峄� do 膽贸 t膬ng th峄漣 gian l脿m vi峄嘽</string>
+    <string name="sonyswr12_settings_alarm_interval_summary">Kho岷g th峄漣 gian b谩o 膽峄檔g th么ng minh l脿 kho岷g th峄漣 gian tr瓢峄沜 khi b谩o 膽峄檔g 膽瓢峄 c脿i 膽岷穞. Trong kho岷g th峄漣 gian n脿y, thi岷縯 b峄� 膽ang c峄� g岷痭g ph谩t hi峄噉 giai 膽o岷 ng峄� nh岷� nh岷 膽峄� ng瓢峄漣 d霉ng t峄塶h t谩o</string>
+    <string name="sonyswr12_settings_low_vibration">膼峄� rung th岷 膽瓢峄 k铆ch ho岷</string>
+    <string name="appmanager_weather_install_provider">C脿i 膽岷穞 峄﹏g d峄g Th么ng b谩o th峄漣 ti岷縯</string>
+    <string name="appmanager_weather_deactivate">V么 hi峄噓 ho谩 峄﹏g d峄g Th峄漣 ti岷縯 h峄� th峄憂g</string>
+    <string name="appmanager_weather_activate">K铆ch ho岷 峄﹏g d峄g Th峄漣 ti岷縯 h峄� th峄憂g</string>
+    <string name="debugactivity_really_factoryreset">Vi峄嘽 kh么i ph峄 c脿i 膽岷穞 g峄慶 s岷� xo谩 t岷 c岷� d峄� li峄噓 kh峄廼 thi岷縯 b峄� 膽茫 k岷縯 n峄慽 (n岷縰 膽瓢峄 h峄� tr峄�). C谩c thi岷縯 b峄� Xiaomi/Huami c农ng thay 膽峄昳 膽峄媋 ch峄� MAC c峄 Bluetooth, n锚n ch煤ng hi峄噉 ra l脿 thi岷縯 b峄� m峄沬 膽峄慽 v峄沬 Gadgetbridge.</string>
+    <string name="debugactivity_really_factoryreset_title">Th峄眂 s峄� kh么i ph峄 c脿i 膽岷穞 g峄慶\?</string>
+    <string name="calendar_year">N膬m</string>
+    <string name="calendar_six_months">6 th谩ng</string>
+    <string name="calendar_three_months">3 th谩ng</string>
+    <string name="calendar_month">Th谩ng</string>
+    <string name="calendar_two_weeks">Hai tu岷</string>
+    <string name="calendar_week">Tu岷</string>
+    <string name="calendar_day">Ng脿y</string>
+    <string name="battery_level">M峄ヽ pin</string>
+    <string name="battery_detail_activity_title">Th么ng tin pin</string>
+    <string name="controlcenter_navigation_drawer_close">膼贸ng ng膬n 膽i峄乽 h瓢峄沶g</string>
+    <string name="controlcenter_navigation_drawer_open">M峄� ng膬n 膽i峄乽 h瓢峄沶g</string>
+    <string name="find_lost_device_message">T矛m ki岷縨 %1$s\?</string>
+    <string name="pref_theme_system">H峄� th峄憂g</string>
+    <string name="pref_title_canned_replies">Tr岷� l峄漣</string>
+    <string name="pref_title_pebble_activitytracker">Tr矛nh theo d玫i ho岷 膽峄檔g 膽瓢峄 瓢u ti锚n</string>
+    <string name="pref_header_activitytrackers">Tr矛nh theo d玫i ho岷 膽峄檔g</string>
+    <string name="pref_title_sunrise_sunset">M岷穞 tr峄漣 m峄峜 v脿 m岷穞 tr峄漣 l岷穘</string>
+    <string name="pref_header_pebble_timeline">D貌ng th峄漣 gian c峄 Pebble</string>
+    <string name="appmanager_hrm_deactivate">V么 hi峄噓 ho谩 HRM</string>
+    <string name="appmanager_hrm_activate">K铆ch ho岷 HRM</string>
+    <string name="controlcenter_set_alias">膼岷穞 b铆 danh</string>
+    <string name="miband_firmware_suggest_whitelist">N岷縰 b岷 v岷玭 mu峄憂 ti岷縫 t峄 v脿 sau 膽贸 m峄峣 th峄� ti岷縫 t峄 v岷璶 h脿nh 膽煤ng, th矛 h茫y b岷 c谩c nh脿 ph谩t tri峄僴 c峄 Gadgetbridge cho phi锚n b岷 ph岷 m峄乵 %s v脿o danh s谩ch tr岷痭g.</string>
+    <string name="miband_firmware_unknown_warning">Ph岷 m峄乵 h峄� th峄憂g n脿y ch瓢a 膽瓢峄 th峄� nghi峄噈 v脿 c贸 th峄� kh么ng t瓢啤ng th铆ch v峄沬 Gadgetbridge.
+\n
+\nB岷 KH脭NG 膼漂峄 KHUY岷綨 KH脥CH c脿i 膽岷穞 n贸!</string>
+    <string name="miband_firmware_known">Ph岷 m峄乵 h峄� th峄憂g n脿y 膽茫 膽瓢峄 th峄� nghi峄噈 v脿 膽茫 膽瓢峄 bi岷縯 l脿 t瓢啤ng th铆ch v峄沬 Gadgetbridge.</string>
+    <string name="fw_multi_upgrade_notice">B岷 s岷痯 c脿i 膽岷穞 ph岷 m峄乵 h峄� th峄憂g %1$s v脿 %2$s, thay v矛 ph岷 m峄乵 hi峄噉 t岷 膽ang tr锚n Mi Band c峄 b岷.</string>
+    <string name="fw_upgrade_notice_amazfitneo">B岷 s岷痯 c脿i 膽岷穞 ph岷 m峄乵 h峄� th峄憂g %s l锚n Amazfit Neo c峄 b岷.
+\n
+\nB膬ng 膽eo tay c峄 b岷 s岷� kh峄焛 膽峄檔g l岷 sau khi c脿i 膽岷穞 t峄噋 .fw.
+\n
+\nB岷燦 T峄� CH峄奤 R峄 RO KHI TI岷綪 T峄!</string>
+    <string name="fw_upgrade_notice_amazfitx">B岷 s岷痯 c脿i 膽岷穞 ph岷 m峄乵 h峄� th峄憂g %s l锚n Amazfit X c峄 b岷.
+\n
+\nH茫y ch岷痗 ch岷痭 b岷 c脿i 膽岷穞 t峄噋 .fw, v脿 sau 膽贸 l脿 t峄噋 .res. B膬ng 膽eo tay c峄 b岷 s岷� kh峄焛 膽峄檔g l岷 sau khi c脿i 膽岷穞 t峄噋 .fw.
+\n
+\nL瓢u 媒: B岷 kh么ng c岷 ph岷 c脿i 膽岷穞 .res n岷縰 t峄噋 膽贸 gi峄憂g h峄噒 v峄沬 t峄噋 膽茫 c脿i 膽岷穞 tr瓢峄沜 膽贸.
+\n
+\nB岷燦 T峄� CH峄奤 R峄 RO KHI TI岷綪 T峄!</string>
+    <string name="fw_upgrade_notice_miband5">B岷 s岷痯 c脿i 膽岷穞 ph岷 m峄乵 h峄� th峄憂g %s l锚n Mi Band 5 c峄 b岷.
+\n
+\nH茫y ch岷痗 ch岷痭 b岷 c脿i 膽岷穞 t峄噋 .fw, v脿 sau 膽贸 l脿 t峄噋 .res. B膬ng 膽eo tay c峄 b岷 s岷� kh峄焛 膽峄檔g l岷 sau khi c脿i 膽岷穞 t峄噋 .fw.
+\n
+\nL瓢u 媒: B岷 kh么ng c岷 ph岷 c脿i 膽岷穞 .res n岷縰 t峄噋 膽贸 gi峄憂g h峄噒 v峄沬 t峄噋 膽茫 c脿i 膽岷穞 tr瓢峄沜 膽贸.
+\n
+\nB岷燦 T峄� CH峄奤 R峄 RO KHI TI岷綪 T峄!</string>
+    <string name="fw_upgrade_notice_miband4">B岷 s岷痯 c脿i 膽岷穞 ph岷 m峄乵 h峄� th峄憂g %s l锚n Mi Band 4 c峄 b岷.
+\n
+\nH茫y ch岷痗 ch岷痭 b岷 c脿i 膽岷穞 t峄噋 .fw, v脿 sau 膽贸 l脿 t峄噋 .res. B膬ng 膽eo tay c峄 b岷 s岷� kh峄焛 膽峄檔g l岷 sau khi c脿i 膽岷穞 t峄噋 .fw.
+\n
+\nL瓢u 媒: B岷 kh么ng c岷 ph岷 c脿i 膽岷穞 .res n岷縰 t峄噋 膽贸 gi峄憂g h峄噒 v峄沬 t峄噋 膽茫 c脿i 膽岷穞 tr瓢峄沜 膽贸.
+\n
+\nB岷燦 T峄� CH峄奤 R峄 RO KHI TI岷綪 T峄!</string>
+    <string name="fw_upgrade_notice_miband3">B岷 s岷痯 c脿i 膽岷穞 ph岷 m峄乵 h峄� th峄憂g %s l锚n Mi Band 3 c峄 b岷.
+\n
+\nH茫y ch岷痗 ch岷痭 b岷 c脿i 膽岷穞 t峄噋 .fw, v脿 sau 膽贸 l脿 t峄噋 .res. B膬ng 膽eo tay c峄 b岷 s岷� kh峄焛 膽峄檔g l岷 sau khi c脿i 膽岷穞 t峄噋 .fw.
+\n
+\nL瓢u 媒: B岷 kh么ng c岷 ph岷 c脿i 膽岷穞 .res n岷縰 t峄噋 膽贸 gi峄憂g h峄噒 v峄沬 t峄噋 膽茫 c脿i 膽岷穞 tr瓢峄沜 膽贸.
+\n
+\nB岷燦 T峄� CH峄奤 R峄 RO KHI TI岷綪 T峄!</string>
+    <string name="fw_upgrade_notice_amazfitvergel">B岷 s岷痯 c脿i 膽岷穞 ph岷 m峄乵 h峄� th峄憂g %s l锚n Amazfit Verge Lite c峄 b岷.
+\n
+\nH茫y ch岷痗 ch岷痭 b岷 c脿i 膽岷穞 t峄噋 .fw, sau 膽贸 l脿 t峄噋 .res, v脿 cu峄慽 c霉ng l脿 t峄噋 .gps. 膼峄搉g h峄� c峄 b岷 s岷� kh峄焛 膽峄檔g l岷 sau khi c脿i 膽岷穞 t峄噋 .fw.
+\n
+\nL瓢u 媒: B岷 kh么ng c岷 ph岷 c脿i 膽岷穞 .res v脿 .gps n岷縰 c谩c t峄噋 膽贸 gi峄憂g h峄噒 v峄沬 c谩c t峄噋 膽茫 c脿i 膽岷穞 tr瓢峄沜 膽贸.
+\n
+\nB岷燦 T峄� CH峄奤 R峄 RO KHI TI岷綪 T峄!</string>
+    <string name="fw_upgrade_notice_amazfit_trex">B岷 s岷痯 c脿i 膽岷穞 ph岷 m峄乵 h峄� th峄憂g %s l锚n Amazfit T-Rex c峄 b岷.
+\n
+\nH茫y ch岷痗 ch岷痭 b岷 c脿i 膽岷穞 t峄噋 .fw, sau 膽贸 l脿 t峄噋 .res, v脿 cu峄慽 c霉ng l脿 t峄噋 .gps. 膼峄搉g h峄� c峄 b岷 s岷� kh峄焛 膽峄檔g l岷 sau khi c脿i 膽岷穞 t峄噋 .fw.
+\n
+\nL瓢u 媒: B岷 kh么ng c岷 ph岷 c脿i 膽岷穞 .res v脿 .gps n岷縰 c谩c t峄噋 膽贸 gi峄憂g h峄噒 v峄沬 c谩c t峄噋 膽茫 c脿i 膽岷穞 tr瓢峄沜 膽贸.
+\n
+\nB岷燦 T峄� CH峄奤 R峄 RO KHI TI岷綪 T峄!</string>
+    <string name="fw_upgrade_notice_amazfitgts">B岷 s岷痯 c脿i 膽岷穞 ph岷 m峄乵 h峄� th峄憂g %s l锚n Amazfit GTS c峄 b岷.
+\n
+\nH茫y ch岷痗 ch岷痭 b岷 c脿i 膽岷穞 t峄噋 .fw, sau 膽贸 l脿 t峄噋 .res, v脿 cu峄慽 c霉ng l脿 t峄噋 .gps. 膼峄搉g h峄� c峄 b岷 s岷� kh峄焛 膽峄檔g l岷 sau khi c脿i 膽岷穞 t峄噋 .fw.
+\n
+\nL瓢u 媒: B岷 kh么ng c岷 ph岷 c脿i 膽岷穞 .res v脿 .gps n岷縰 c谩c t峄噋 膽贸 gi峄憂g h峄噒 v峄沬 c谩c t峄噋 膽茫 c脿i 膽岷穞 tr瓢峄沜 膽贸.
+\n
+\nB岷燦 T峄� CH峄奤 R峄 RO KHI TI岷綪 T峄!</string>
+    <string name="fw_upgrade_notice_amazfitgtr">B岷 s岷痯 c脿i 膽岷穞 ph岷 m峄乵 h峄� th峄憂g %s l锚n Amazfit GTR c峄 b岷.
+\n
+\nH茫y ch岷痗 ch岷痭 b岷 c脿i 膽岷穞 t峄噋 .fw, sau 膽贸 l脿 t峄噋 .res, v脿 cu峄慽 c霉ng l脿 t峄噋 .gps. 膼峄搉g h峄� c峄 b岷 s岷� kh峄焛 膽峄檔g l岷 sau khi c脿i 膽岷穞 t峄噋 .fw.
+\n
+\nL瓢u 媒: B岷 kh么ng c岷 ph岷 c脿i 膽岷穞 .res v脿 .gps n岷縰 c谩c t峄噋 膽贸 gi峄憂g h峄噒 v峄沬 c谩c t峄噋 膽茫 c脿i 膽岷穞 tr瓢峄沜 膽贸.
+\n
+\nB岷燦 T峄� CH峄奤 R峄 RO KHI TI岷綪 T峄!</string>
+    <string name="fw_upgrade_notice_amazfitcor2">B岷 s岷痯 c脿i 膽岷穞 ph岷 m峄乵 h峄� th峄憂g %s l锚n Amazfit Cor 2 c峄 b岷.
+\n
+\nH茫y ch岷痗 ch岷痭 b岷 c脿i 膽岷穞 t峄噋 .fw, v脿 sau 膽贸 l脿 t峄噋 .res. B膬ng 膽eo tay c峄 b岷 s岷� kh峄焛 膽峄檔g l岷 sau khi c脿i 膽岷穞 t峄噋 .fw.
+\n
+\nL瓢u 媒: B岷 kh么ng c岷 ph岷 c脿i 膽岷穞 .res n岷縰 t峄噋 膽贸 gi峄憂g h峄噒 v峄沬 t峄噋 膽茫 c脿i 膽岷穞 tr瓢峄沜 膽贸.
+\n
+\nB岷燦 T峄� CH峄奤 R峄 RO KHI TI岷綪 T峄!
+\n
+\nHO脌N TO脌N CH漂A 膼漂峄 TH峄� NGHI峄哅, C脫 TH峄� B岷燦 C岷 C脌I 膼岷禩 PH岷 M峄�M BEATS_W N岷綰 T脢N THI岷綯 B峄� C峄 B岷燦 L脌 \"Amazfit Band 2\"</string>
+    <string name="fw_upgrade_notice_amazfitcor">B岷 s岷痯 c脿i 膽岷穞 ph岷 m峄乵 h峄� th峄憂g %s l锚n Amazfit Cor c峄 b岷.
+\n
+\nH茫y ch岷痗 ch岷痭 b岷 c脿i 膽岷穞 t峄噋 .fw, v脿 sau 膽贸 l脿 t峄噋 .res. B膬ng 膽eo tay c峄 b岷 s岷� kh峄焛 膽峄檔g l岷 sau khi c脿i 膽岷穞 t峄噋 .fw.
+\n
+\nL瓢u 媒: B岷 kh么ng c岷 ph岷 c脿i 膽岷穞 .res n岷縰 t峄噋 膽贸 gi峄憂g h峄噒 v峄沬 t峄噋 膽茫 c脿i 膽岷穞 tr瓢峄沜 膽贸.
+\n
+\nB岷燦 T峄� CH峄奤 R峄 RO KHI TI岷綪 T峄!</string>
+    <string name="fw_upgrade_notice_amazfitbip_lite">B岷 s岷痯 c脿i 膽岷穞 ph岷 m峄乵 h峄� th峄憂g %s l锚n Amazfit Bip Lite c峄 b岷.
+\n
+\nH茫y ch岷痗 ch岷痭 b岷 c脿i 膽岷穞 t峄噋 .fw, v脿 sau 膽贸 l脿 t峄噋 .res. 膼峄搉g h峄� c峄 b岷 s岷� kh峄焛 膽峄檔g l岷 sau khi c脿i 膽岷穞 t峄噋 .fw.
+\n
+\nL瓢u 媒: B岷 kh么ng c岷 ph岷 c脿i 膽岷穞 .res n岷縰 t峄噋 膽贸 gi峄憂g h峄噒 v峄沬 t峄噋 膽茫 c脿i 膽岷穞 tr瓢峄沜 膽贸.
+\n
+\nB岷燦 T峄� CH峄奤 R峄 RO KHI TI岷綪 T峄!</string>
+    <string name="fw_upgrade_notice_amazfitbip">B岷 s岷痯 c脿i 膽岷穞 ph岷 m峄乵 h峄� th峄憂g %s l锚n Amazfit Bip c峄 b岷.
+\n
+\nH茫y ch岷痗 ch岷痭 b岷 c脿i 膽岷穞 t峄噋 .fw, sau 膽贸 l脿 t峄噋 .res, v脿 cu峄慽 c霉ng l脿 t峄噋 .gps. 膼峄搉g h峄� c峄 b岷 s岷� kh峄焛 膽峄檔g l岷 sau khi c脿i 膽岷穞 t峄噋 .fw.
+\n
+\nL瓢u 媒: B岷 kh么ng c岷 ph岷 c脿i 膽岷穞 .res v脿 .gps n岷縰 c谩c t峄噋 膽贸 gi峄憂g h峄噒 v峄沬 c谩c t峄噋 膽茫 c脿i 膽岷穞 tr瓢峄沜 膽贸.
+\n
+\nB岷燦 T峄� CH峄奤 R峄 RO KHI TI岷綪 T峄!</string>
+    <string name="title_activity_calblacklist">C谩c l峄媍h 膽茫 th锚m v脿o danh s谩ch 膽en</string>
+    <string name="pref_title_location_aquire">L岷 v峄� tr铆</string>
+    <string name="pref_title_allow_high_mtu">Cho ph茅p MTU cao</string>
+    <string name="pref_summary_use_custom_font">B岷璽 tu峄� ch峄峮 n脿y n岷縰 thi岷縯 b峄� c峄 b岷 c贸 ph岷 m峄乵 ph么ng ch峄� tu峄� ch峄塶h 膽峄� h峄� tr峄� emoji</string>
+    <string name="pref_title_use_custom_font">S峄� d峄g ph么ng ch峄� tu峄� ch峄塶h</string>
+    <string name="pref_summary_pebble_forceuntested">B岷璽 c谩c t铆nh n膬ng ch瓢a 膽瓢峄 th峄� nghi峄噈. H脙Y BI岷綯 B岷燦 膼ANG L脌M G脤!</string>
+    <string name="pref_title_pebble_forceuntested">B岷璽 c谩c t铆nh n膬ng ch瓢a 膽瓢峄 th峄� nghi峄噈</string>
+    <string name="pref_pebble_privacy_mode_content">D峄媍h chuy峄僴 v膬n b岷 th么ng b谩o ra ngo脿i m脿n h矛nh</string>
+    <string name="pref_title_pebble_forcele">Lu么n 瓢u ti锚n BLE</string>
+    <string name="pref_summary_location_keep_uptodate">C峄� g岷痭g l岷 v峄� tr铆 hi峄噉 t岷 khi ch岷, s峄� d峄g v峄� tr铆 膽茫 l瓢u tr峄� l脿m v峄� tr铆 d峄� ph貌ng</string>
+    <string name="pref_pebble_privacy_mode_complete">Ch峄� hi峄噉 bi峄僽 t瓢峄g th么ng b谩o</string>
+    <string name="pref_header_notifications_and_calls">Th么ng b谩o v脿 Cu峄檆 g峄峣</string>
+    <string name="preferences_hplus_settings">C脿i 膽岷穞 HPlus/Makibes</string>
+    <string name="prefs_title_all_day_heart_rate">膼o nh峄媝 tim c岷� ng脿y</string>
+    <string name="pref_title_screentime">Th峄漣 gian b岷璽 m脿n h矛nh</string>
+    <string name="pref_title_timeformat">膼峄媙h d岷g th峄漣 gian</string>
+    <string name="pref_title_unit_system">膼啤n v峄�</string>
+    <string name="pref_display_add_device_fab_off">Ch峄� nh矛n th岷 膽瓢峄 n岷縰 kh么ng c贸 thi岷縯 b峄� n脿o 膽瓢峄 th锚m</string>
+    <string name="pref_display_add_device_fab_on">Lu么n nh矛n th岷 膽瓢峄</string>
+    <string name="pref_display_add_device_fab">N煤t k岷縯 n峄慽 thi岷縯 b峄� m峄沬</string>
+    <string name="pref_title_vibration_strength">C瓢峄漬g 膽峄� rung</string>
+    <string name="pref_title_relax_firmware_checks">H茫y b岷璽 tu峄� ch峄峮 n脿y n岷縰 b岷 mu峄憂 c脿i 膽岷穞 m峄檛 firmware kh么ng d脿nh cho thi岷縯 b峄� c峄 b岷 (b岷 t峄� ch峄媢 r峄 ro)</string>
+    <string name="pref_summary_relax_firmware_checks">Ki峄僲 tra firmware kh么ng ch岷穞</string>
+    <string name="pref_title_sync_caldendar">膼峄搉g b峄� c谩c s峄� ki峄噉 trong l峄媍h</string>
+    <string name="pref_summary_sync_calendar">B岷璽 c谩c c岷h b谩o l峄媍h, k峄� c岷� khi 膽茫 ng岷痶 k岷縯 n峄慽</string>
+    <string name="pref_summary_allow_high_mtu">T膬ng t峄慶 膽峄� truy峄乶, nh瓢ng c贸 th峄� s岷� kh么ng ho岷 膽峄檔g tr锚n m峄檛 s峄� thi岷縯 b峄� Android.</string>
+    <string name="pref_summary_pebble_enable_applogs">S岷� l脿m nh岷璽 k媒 t峄� c谩c 峄﹏g d峄g 膽峄搉g h峄� 膽瓢峄 Gadgetbridge ghi nh岷璽 k媒 (y锚u c岷 k岷縯 n峄慽 l岷)</string>
+    <string name="pref_summary_pebble_gatt_clientonly">Tu峄� ch峄峮 n脿y ch峄� d脿nh cho Pebble 2 v脿 c贸 t铆nh th峄� nghi峄噈, h茫y th峄� tu峄� ch峄峮 n脿y n岷縰 b岷 c贸 v岷 膽峄� v峄� k岷縯 n峄慽</string>
+    <string name="pref_title_pebble_gatt_clientonly">Ch峄� c贸 kh谩ch GATT</string>
+    <string name="pref_title_pebble_enable_applogs">B岷璽 ghi nh岷璽 k媒 峄﹏g d峄g 膽峄搉g h峄�</string>
+    <string name="pref_summary_pebble_mtu_limit">N岷縰 Pebble 2/Pebble LE c峄 b岷 kh么ng ho岷 膽峄檔g nh瓢 mong 膽峄, h茫y th峄� c脿i 膽岷穞 n脿y 膽峄� gi峄沬 h岷 MTU (ph岷 vi h峄 l峄� 20-512)</string>
+    <string name="pref_title_pebble_mtu_limit">Gi峄沬 h岷 GATT MTU c峄 Pebble 2/LE</string>
+    <string name="pref_summary_pebble_forcele">S峄� d峄g s峄� h峄� tr峄� Pebble LE c贸 t铆nh th峄� nghi峄噈 cho t岷 c岷� thi岷縯 b峄� Pebble, thay v矛 BT c峄� 膽i峄僴. Vi峄嘽 n脿y y锚u c岷 gh茅p 膽么i v峄沬 thi岷縯 b峄� kh么ng ph岷 LE tr瓢峄沜, v脿 sau 膽贸 膽岷縩 Pebble LE</string>
+    <string name="pref_summary_pebble_forceprotocol">Tu峄� ch峄峮 n脿y bu峄檆 s峄� d峄g giao th峄ヽ th么ng b谩o m峄沬 nh岷 tu峄� v脿o phi锚n b岷 firmware. H脙Y BI岷綯 B岷燦 膼ANG L脌M G脤!</string>
+    <string name="pref_title_pebble_forceprotocol">Bu峄檆 giao th峄ヽ th么ng b谩o</string>
+    <string name="toast_aqurired_networklocation">膽茫 nh岷璶 膽瓢峄 v峄� tr铆</string>
+    <string name="toast_enable_networklocationprovider">Vui l貌ng b岷璽 v峄� tr铆 m岷g</string>
+    <string name="pref_summary_autoremove_notifications">C谩c th么ng b谩o s岷� t峄� 膽峄檔g 膽瓢峄 xo谩 kh峄廼 thi岷縯 b峄� khi ch煤ng b峄� b峄� qua t峄� 膽i峄噉 tho岷</string>
+    <string name="pref_title_autoremove_notifications">T峄� 膽峄檔g xo谩 c谩c th么ng b谩o 膽茫 b峄� b峄� qua</string>
+    <string name="pref_summary_custom_deviceicon">Hi峄噉 bi峄僽 t瓢峄g th么ng b谩o Android cho ri锚ng t峄玭g thi岷縯 b峄� thay v矛 bi峄僽 t瓢峄g Gadgetbridge khi 膽瓢峄 k岷縯 n峄慽</string>
+    <string name="pref_title_custom_deviceicon">Hi峄噉 bi峄僽 t瓢峄g th么ng b谩o cho ri锚ng t峄玭g thi岷縯 b峄�</string>
+    <string name="pref_summary_enable_pebblekit">B岷璽 s峄� h峄� tr峄� c贸 t铆nh th峄� nghi峄噈 cho c谩c 峄﹏g d峄g Android b岷眓g PebbleKit</string>
+    <string name="pref_summary_enable_outgoing_call">Vi峄嘽 t岷痶 tu峄� ch峄峮 n脿y c农ng s岷� d峄玭g vi峄嘽 Pebble 2/LE rung khi c贸 cu峄檆 g峄峣 膽i</string>
+    <string name="pref_title_enable_outgoing_call">H峄� tr峄� c谩c cu峄檆 g峄峣 膽i</string>
+    <string name="pref_title_canned_messages_dismisscall">T峄� ch峄慽 cu峄檆 g峄峣</string>
+    <string name="pref_title_canned_reply_suffix">H岷璾 t峄� th瓢峄漬g g岷穚</string>
+    <string name="pref_blacklist_calendars">Cho c谩c l峄媍h v脿o danh s谩ch 膽en</string>
+    <string name="pref_title_weather_summary">膼瓢峄 s峄� d峄g cho nh脿 cung c岷 th峄漣 ti岷縯 c峄 LineageOS, c谩c phi锚n b岷 Android kh谩c c岷 s峄� d峄g m峄檛 峄﹏g d峄g nh瓢 \"Weather notification\". H茫y t矛m th锚m th么ng tin trong wiki c峄 Gadgetbridge.</string>
+    <string name="pref_rtl_max_line_length_summary">K茅o d脿i ho岷穋 r煤t ng岷痭 c谩c d貌ng m脿 v膬n b岷 Ph岷-Qua-Tr谩i 膽瓢峄 t谩ch th脿nh</string>
+    <string name="pref_title_support_voip_calls">B岷璽 c谩c cu峄檆 g峄峣 峄﹏g d峄g VoIP</string>
+    <string name="fw_upgrade_notice_miband6">B岷 s岷痯 c脿i 膽岷穞 ph岷 m峄乵 h峄� th峄憂g %s l锚n Mi Band 6 c峄 b岷.
+\n
+\nH茫y ch岷痗 ch岷痭 b岷 c脿i 膽岷穞 t峄噋 .fw, v脿 sau 膽贸 l脿 t峄噋 .res. B膬ng 膽eo tay c峄 b岷 s岷� kh峄焛 膽峄檔g l岷 sau khi c脿i 膽岷穞 t峄噋 .fw.
+\n
+\nL瓢u 媒: B岷 kh么ng c岷 ph岷 c脿i 膽岷穞 .res n岷縰 t峄噋 膽贸 gi峄憂g h峄噒 v峄沬 t峄噋 膽茫 c脿i 膽岷穞 tr瓢峄沜 膽贸.
+\n
+\nB岷燦 T峄� CH峄奤 R峄 RO KHI TI岷綪 T峄!</string>
+    <string name="whitelist_all_for_notifications">Cho t岷 c岷� v脿o danh s谩ch tr岷痭g 膽峄慽 v峄沬 c谩c th么ng b谩o</string>
+    <string name="blacklist_all_for_notifications">Cho t岷 c岷� v脿o danh s谩ch 膽en 膽峄慽 v峄沬 c谩c th么ng b谩o</string>
+    <string name="pref_title_notification_use_as_deny">T峄� ch峄慽 th么ng b谩o t峄� c谩c 峄﹏g d峄g 膽茫 ch峄峮</string>
+    <string name="title_activity_notification_management">C脿i 膽岷穞 th么ng b谩o</string>
+    <string name="pref_title_message_privacy_mode">Ch岷� 膽峄� b岷 m岷璽 tin nh岷痭</string>
+    <string name="pref_title_pebble_enable_bgjs">B岷璽 n峄乶 JS</string>
+    <string name="pref_summary_pebble_enable_bgjs">Khi 膽瓢峄 b岷璽, cho ph茅p hi峄僴 th峄� th峄漣 ti岷縯, pin鈥� tr锚n m岷穞 膽峄搉g h峄�.</string>
+    <string name="pref_summary_expose_hr">Cho ph茅p c谩c 峄﹏g d峄g kh谩c truy c岷璸 d峄� li峄噓 HR trong th峄漣 gian th峄眂 khi Gadgetbridge 膽瓢峄 k岷縯 n峄慽</string>
+    <string name="pref_summary_connected_advertisement">Cho ph茅p thi岷縯 b峄� 膽瓢峄 ph谩t hi峄噉 khi qu茅t b岷眓g Bluetooth k峄� c岷� khi 膽茫 k岷縯 n峄慽</string>
+    <string name="prefs_notifications_and_calls_shake_reject">L岷痗 c峄� tay 膽峄� b峄� qua/t峄� ch峄慽 cu峄檆 g峄峣</string>
+    <string name="pref_header_device_spec_settings">C脿i 膽岷穞 thi岷縯 b峄�</string>
+    <string name="pref_theme_black_background">S峄� d峄g n峄乶 膽en trong ch峄� 膽峄� T峄慽</string>
+    <string name="pref_title_notification_use_as_allow">Cho ph茅p th么ng b谩o t峄� c谩c 峄﹏g d峄g 膽茫 ch峄峮</string>
+    <string name="pref_title_notification_use_as">S峄� d峄g danh s谩ch 峄﹏g d峄g 膽峄冣��</string>
+    <string name="pref_message_privacy_mode_off">Hi峄噉 th峄� n峄檌 dung</string>
+    <string name="pref_message_privacy_mode_complete">岷╪ n峄檌 dung</string>
+    <string name="pref_applications_settings">Danh s谩ch 峄﹏g d峄g</string>
+    <string name="pref_title_expose_hr">Chia s岷� HR v峄沬 app th峄� ba theo th峄漣 gian th峄眂</string>
+    <string name="pref_title_connected_advertisement">Hi峄噉 khi 膽茫 k岷縯 n峄慽</string>
+    <string name="pref_title_notifications_and_calls_repeat_on_call">L岷穚 l岷 th么ng b谩o cu峄檆 g峄峣</string>
+    <string name="prefs_notifications_and_calls_continious_ring">Th么ng b谩o khi 膽i峄噉 tho岷 膽峄� chu么ng</string>
+    <string name="pref_notifications_and_calls_enable_misscall">Th么ng b谩o cu峄檆 g峄峣 nh峄�</string>
+    <string name="pref_summary_notifications_and_calls_enable_misscall">L岷穚 l岷 m峄梚 ph煤t</string>
+    <string name="pref_title_notifications_and_calls_repeat_on_misscall">L岷穚 l岷 trong X ph煤t</string>
+    <string name="prefs_notifications_and_calls_reject">N煤t b峄� qua/t峄� ch峄慽 cu峄檆 g峄峣</string>
+    <string name="pref_summary_notifications_and_calls_title_reject">T岷痶 - b峄� qua, B岷璽 - t峄� ch峄慽</string>
+</resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-w820dp/dimens.xml b/app/src/main/res/values-w820dp/dimens.xml
new file mode 100644
index 0000000..63fc816
--- /dev/null
+++ b/app/src/main/res/values-w820dp/dimens.xml
@@ -0,0 +1,6 @@
+<resources>
+    <!-- Example customization of dimensions originally defined in res/values/dimens.xml
+         (such as screen margins) for screens with more than 820dp of available width. This
+         would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively). -->
+    <dimen name="activity_horizontal_margin">64dp</dimen>
+</resources>
diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml
new file mode 100644
index 0000000..225b3b7
--- /dev/null
+++ b/app/src/main/res/values-zh-rCN/strings.xml
@@ -0,0 +1,3701 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources xmlns:tools="http://schemas.android.com/tools">
+    <string name="application_name_generic">浜敵绉戞妧</string>
+    <string name="title_activity_controlcenter_generic">浜敵绉戞妧</string>
+    <string name="action_settings">璁剧疆</string>
+    <string name="womp_workbench">鍒嗘嫞宸ヤ綔鍙�</string>
+    <string name="womp_scan">鎵爜</string>
+    <string name="bottom_nav_band">鍋ュ悍鎵嬬幆</string>
+    <string name="bottom_nav_workbench">宸ヤ綔鍙�</string>
+    <string name="bottom_nav_health">鍋ュ悍</string>
+    <string name="womp_press_again_to_exit">鍐嶆寜涓�娆¢��鍑哄簲鐢�</string>
+    <string name="action_debug">璋冭瘯</string>
+    <string name="action_quit">閫�鍑�</string>
+    <string name="action_donate">鎹愯禒</string>
+    <string name="controlcenter_fetch_activity_data">鍚屾</string>
+    <string name="controlcenter_find_device">鏌ユ壘涓㈠け鐨勮澶�</string>
+    <string name="controlcenter_take_screenshot">鎴浘</string>
+    <string name="controlcenter_connect">杩炴帴鈥�</string>
+    <string name="controlcenter_disconnect">鏂紑</string>
+    <string name="controlcenter_delete_device">鍒犻櫎璁惧</string>
+    <string name="controlcenter_delete_device_name">鍒犻櫎 %1$s</string>
+    <string name="controlcenter_delete_device_dialogmessage">姝ゆ搷浣滃皢鍒犻櫎璇ヨ澶囧拰鎵�鏈夌浉鍏崇殑鏁版嵁锛�</string>
+    <string name="controlcenter_navigation_drawer_open">鎵撳紑瀵艰埅鑿滃崟</string>
+    <string name="controlcenter_navigation_drawer_close">鍏抽棴瀵艰埅鑿滃崟</string>
+    <string name="controlcenter_snackbar_need_longpress">闀挎寜姝ゅ崱鐗囦互鏂紑杩炴帴</string>
+    <string name="controlcenter_snackbar_disconnecting">姝e湪鏂紑</string>
+    <string name="controlcenter_snackbar_connecting">姝e湪杩炴帴鈥�</string>
+    <string name="controlcenter_snackbar_requested_screenshot">鑾峰彇璁惧灞忓箷鎴浘</string>
+    <string name="title_activity_debug">璋冭瘯</string>
+    <string name="title_activity_appmanager">搴旂敤绠$悊鍣�</string>
+    <string name="appmanager_installed_watchapps">宸插畨瑁呯殑搴旂敤</string>
+    <string name="appmanager_installed_watchfaces">宸插畨瑁呯殑琛ㄧ洏</string>
+    <string name="appmananger_app_delete">鍒犻櫎</string>
+    <string name="appmananger_app_delete_cache">鍒犻櫎骞舵竻绌虹紦瀛�</string>
+    <string name="appmananger_app_reinstall">閲嶆柊瀹夎</string>
+    <string name="appmanager_app_openinstore">鍦� Pebble 搴旂敤鍟嗗簵鎼滅储</string>
+    <string name="appmanager_health_activate">鍚敤</string>
+    <string name="appmanager_health_deactivate">鍋滅敤</string>
+    <string name="appmanager_hrm_activate">鎵撳紑蹇冪巼妫�娴�</string>
+    <string name="appmanager_hrm_deactivate">鍏抽棴蹇冪巼妫�娴�</string>
+    <string name="appmanager_weather_activate">鍚敤绯荤粺澶╂皵搴旂敤</string>
+    <string name="appmanager_weather_deactivate">绂佺敤绯荤粺澶╂皵搴旂敤</string>
+    <string name="appmanager_weather_install_provider">瀹夎澶╂皵閫氱煡搴旂敤</string>
+    <string name="app_configure">閰嶇疆</string>
+    <string name="app_move_to_top">绉昏嚦椤堕儴</string>
+    <string name="title_activity_calblacklist">闃绘璁板綍</string>
+    <string name="title_activity_fw_app_insaller">鍥轰欢/搴旂敤瀹夎鍣�</string>
+    <string name="fw_upgrade_notice">鎮ㄥ嵆灏嗗畨瑁� %s 銆�</string>
+    <string name="fw_upgrade_notice_amazfitbip">鍗冲皢鍦ㄦ偍鐨勮穬鎴戞墜琛ㄩ潚鏄ョ増(Amazfit Bip)涓婂畨瑁呭浐浠� %s 銆�
+\n
+\n璇风‘淇濆畨瑁�.fw鏂囦欢锛岀劧鍚庡畨瑁�.res鏂囦欢锛屾渶鍚庡畨瑁�.gps鏂囦欢銆� 瀹夎.fw鏂囦欢鍚庯紝鎵嬭〃灏嗛噸鏂板惎鍔ㄣ��
+\n
+\n娉ㄦ剰锛氬鏋滆繖浜涙枃浠朵笌涔嬪墠瀹夎鐨勫畬鍏ㄤ竴鏍凤紝鍒欎笉蹇呭畨瑁�.res鍜�.gps鏂囦欢銆�
+\n
+\n***缁х画闇�瑕佹偍鑷媴椋庨櫓锛�</string>
+    <string name="fw_upgrade_notice_amazfitcor">鎮ㄥ嵆灏嗗湪鎮ㄧ殑璺冩垜鎵嬬幆(Amazfit Cor)涓婂畨瑁呭浐浠� %s 銆�
+\n
+\n璇风‘淇濆畨瑁呬簡 .fw 鏂囦欢锛岀劧鍚庡畨瑁� .rec 鏂囦欢銆� 瀹夎 .fw 鏂囦欢鍚庯紝鎵嬭〃灏嗛噸鏂板惎鍔ㄣ��
+\n
+\n娉ㄦ剰锛氬鏋滆繖浜涙枃浠朵笌涔嬪墠瀹夎鐨勫畬鍏ㄤ竴鏍凤紝鍒欎笉蹇呭畨瑁� .res 銆�
+\n
+\n鎮ㄩ渶瑕佽嚜琛屾壙鎷呴闄╋紒</string>
+    <string name="fw_multi_upgrade_notice">鎮ㄥ嵆灏嗗畨瑁呭浐浠� %1$s 鍜� %2$s锛屼唬鏇垮綋鍓嶅湪灏忕背鎵嬬幆涓婄殑鍥轰欢銆�</string>
+    <string name="miband_firmware_known">璇ュ浐浠跺凡缁忚繃娴嬭瘯锛屽苟涓斿凡鐭ヤ笌 Gadgetbridge 鍏煎銆�</string>
+    <string name="miband_firmware_unknown_warning">姝ゅ浐浠舵湭缁忔祴璇曪紝鍙兘涓� Gadgetbridge 涓嶅吋瀹广�� \n \n鎴戜滑涓嶅缓璁偍鍒峰叆瀹冿紒</string>
+    <string name="miband_firmware_suggest_whitelist">濡傛灉鎮ㄤ粛鎯崇户缁苟涓旀鍥轰欢鍦ㄥ畨瑁呬箣鍚庤兘澶熸甯稿伐浣滐紝璇峰憡鐭� Gadgetbridge 寮�鍙戣�呭皢鍥轰欢 %s 鍒楀叆鐧藉悕鍗曘��</string>
+    <string name="title_activity_settings">璁剧疆</string>
+    <string name="pref_header_general">甯歌璁剧疆</string>
+    <string name="pref_title_general_autoconnectonbluetooth">褰撹摑鐗欐墦寮�鏃惰繛鎺ュ埌 Gadgetbridge 璁惧</string>
+    <string name="pref_title_general_autostartonboot">鑷姩鍚姩</string>
+    <string name="pref_title_general_autoreconnect">鑷姩閲嶆柊杩炴帴</string>
+    <string name="pref_title_audio_player">棣栭�夐煶棰戞挱鏀惧櫒</string>
+    <string name="pref_default">榛樿</string>
+    <string name="pref_title_charts_swipe">鍚敤宸﹀彸婊戝姩鍒囨崲鍥捐〃</string>
+    <string name="pref_header_datetime">鏃ユ湡鍜屾椂闂�</string>
+    <string name="pref_title_datetime_syctimeonconnect">鍚屾鏃堕棿</string>
+    <string name="pref_summary_datetime_syctimeonconnect">杩炴帴鏃躲�丄ndroid 璁惧涓婄殑鏃堕棿鎴栨椂鍖烘洿鏀瑰皢瀹氭湡鍚屾鍒� Gadgetbridge 璁惧</string>
+    <string name="pref_title_theme">涓婚</string>
+    <string name="pref_theme_light">浜壊</string>
+    <string name="pref_theme_dark">鏆楄壊</string>
+    <string name="pref_title_language">璇█</string>
+    <string name="pref_title_minimize_priority">闅愯棌 Gadgetbridge 閫氱煡</string>
+    <string name="pref_summary_minimize_priority_off">鐘舵�佹爮涓殑鍥炬爣鍜岄攣瀹氬睆骞曚腑鐨勯�氱煡宸茶寮�鍚�</string>
+    <string name="pref_summary_minimize_priority_on">鐘舵�佹爮涓殑鍥炬爣鍜岄攣瀹氬睆骞曚腑鐨勯�氱煡宸茶闅愯棌</string>
+    <string name="pref_header_notifications">閫氱煡</string>
+    <string name="pref_title_notifications_repetitions">閲嶅</string>
+    <string name="pref_title_notifications_call">鏉ョ數</string>
+    <string name="pref_title_notifications_sms">鐭俊</string>
+    <string name="pref_title_notifications_pebblemsg">Pebble 娑堟伅</string>
+    <string name="pref_summary_notifications_pebblemsg">鏀寔缁忕敱 PebbleKit 鍚� Pebble 搴旂敤鍙戦�侀�氱煡銆�</string>
+    <string name="pref_title_notifications_generic">閫氱煡浣跨敤鏉�</string>
+    <string name="pref_title_whenscreenon">鍗充娇灞忓箷寮�鍚椂涔熼�氱煡</string>
+    <string name="pref_title_notification_filter">鍕挎壈妯″紡</string>
+    <string name="pref_summary_notification_filter">褰撳湪鎵嬫満涓婂惎鐢ㄥ嬁鎵版ā寮忔椂灞忚斀鎵�鏈夐�氱煡</string>
+    <string name="pref_title_transliteration">闊宠瘧</string>
+    <string name="pref_summary_transliteration">濡傛灉鎮ㄧ殑璁惧涓嶆敮鎸佹偍璇█鐨勫瓧浣�, 璇峰惎鐢ㄦ鍔熻兘</string>
+    <string name="always">鎬绘槸</string>
+    <string name="when_screen_off">褰撳睆骞曞叧闂椂</string>
+    <string name="never">浠庝笉</string>
+    <string name="pref_header_privacy">闅愮</string>
+    <string name="pref_title_call_privacy_mode">鏉ョ數闅愮妯″紡</string>
+    <string name="pref_call_privacy_mode_off">鏄剧ず濮撳悕鍜屽彿鐮�</string>
+    <string name="pref_call_privacy_mode_name">闅愯棌濮撳悕浣嗘樉绀哄彿鐮�</string>
+    <string name="pref_call_privacy_mode_number">闅愯棌鍙风爜浣嗘樉绀哄鍚�</string>
+    <string name="pref_call_privacy_mode_complete">闅愯棌濮撳悕鍜屽彿鐮�</string>
+    <string name="pref_title_weather_location">澶╂皵浣嶇疆 (鐢盠ineageOS澶╂皵鏈嶅姟鎻愪緵)</string>
+    <string name="pref_blacklist_calendars">榛戝悕鍗曞垪琛�</string>
+    <string name="pref_header_cannned_messages">淇濆瓨鐨勬秷鎭�</string>
+    <string name="pref_title_canned_replies">鍥炲</string>
+    <string name="pref_title_canned_messages_dismisscall">鏈帴鏉ョ數</string>
+    <string name="pref_header_development">寮�鍙戣�呴�夐」</string>
+    <string name="pref_title_development_miaddr">灏忕背鎵嬬幆鍦板潃</string>
+    <string name="pref_title_pebble_settings">Pebble 璁剧疆</string>
+    <string name="pref_header_activitytrackers">娲诲姩璺熻釜鍣�</string>
+    <string name="pref_title_pebble_activitytracker">棣栭�夋椿鍔ㄨ窡韪櫒</string>
+    <string name="pref_title_pebble_sync_health">鍚屾 Pebble 鍋ュ悍</string>
+    <string name="pref_title_pebble_sync_misfit">鍚屾鍒癕isfit</string>
+    <string name="pref_title_pebble_sync_morpheuz">鍚屾鍒癕orpheuz</string>
+    <string name="pref_title_enable_outgoing_call">鏀寔鍛煎嚭</string>
+    <string name="pref_summary_enable_outgoing_call">绂佺敤姝ゅ姛鑳戒篃灏嗗仠姝� Pebble 2 / LE 鍦ㄦ嫧鍑虹數璇濇椂鎸姩</string>
+    <string name="pref_title_enable_pebblekit">鍏佽绗笁鏂� Android 搴旂敤绋嬪簭璁块棶</string>
+    <string name="pref_summary_enable_pebblekit">浣跨敤 PebbleKit 涓� Android 搴旂敤绋嬪簭鍚敤瀹為獙鎬ф敮鎸�</string>
+    <string name="pref_header_pebble_timeline">Pebble 鏃堕棿绾�</string>
+    <string name="pref_title_sunrise_sunset">鏃ュ嚭鍜屾棩钀�</string>
+    <string name="pref_summary_sunrise_sunset">鍩轰簬 Pebble 鏃堕棿琛ㄧ殑浣嶇疆鍙戦�佹棩鍑哄拰鏃ヨ惤鏃堕棿</string>
+    <string name="pref_title_enable_calendar_sync">鍚屾璁板綍</string>
+    <string name="pref_summary_enable_calendar_sync">灏嗚褰曞彂閫佸埌鏃堕棿琛�</string>
+    <string name="pref_title_autoremove_notifications">鑷姩鍒犻櫎宸查敊杩囩殑閫氱煡</string>
+    <string name="pref_summary_autoremove_notifications">褰撴偍浠� Android 璁惧涓婂垹闄ゆ椂锛岄�氱煡灏嗚嚜鍔ㄤ粠璁惧涓垹闄�</string>
+    <string name="pref_title_pebble_privacy_mode">闅愮妯″紡</string>
+    <string name="pref_pebble_privacy_mode_off">姝e父閫氱煡</string>
+    <string name="pref_pebble_privacy_mode_content">灏嗛�氱煡鏂囨湰绉诲嚭灞忓箷</string>
+    <string name="pref_pebble_privacy_mode_complete">鍙樉绀洪�氱煡鍥炬爣</string>
+    <string name="pref_header_location">浣嶇疆</string>
+    <string name="pref_title_location_aquire">鑾峰彇浣嶇疆</string>
+    <string name="pref_title_location_latitude">绾害</string>
+    <string name="pref_title_location_longitude">缁忓害</string>
+    <string name="pref_title_location_keep_uptodate">淇濇寔浣嶇疆鏇存柊</string>
+    <string name="pref_summary_location_keep_uptodate">灏濊瘯鍦ㄨ繍琛屾椂鑾峰彇褰撳墠浣嶇疆, 浣跨敤瀛樺偍鐨勪綅缃綔涓哄悗澶�</string>
+    <string name="toast_enable_networklocationprovider">璇峰惎鐢ㄧ綉缁滀綅缃�</string>
+    <string name="toast_aqurired_networklocation">浣嶇疆宸茬‘瀹�</string>
+    <string name="pref_title_pebble_forceprotocol">寮哄埗閫氱煡鍗忚</string>
+    <string name="pref_summary_pebble_forceprotocol">姝ら�夐」寮哄埗浣跨敤鏈�鏂扮殑閫氱煡鍗忚, 鍏蜂綋鍙栧喅浜庡浐浠剁増鏈�傝纭繚鐭ラ亾鎮ㄨ嚜宸辩煡閬撴偍鍦ㄥ仛浠�涔堬紒</string>
+    <string name="pref_title_pebble_forceuntested">鍚敤鏈粡娴嬭瘯鐨勫姛鑳�</string>
+    <string name="pref_summary_pebble_forceuntested">鍚敤鏈粡娴嬭瘯鐨勫姛鑳姐�備粎褰撲綘鐭ラ亾浣犲湪鍋氫粈涔堟椂鍚敤!</string>
+    <string name="pref_title_pebble_enable_applogs">鍚敤鎵嬭〃搴旂敤璋冭瘯鏃ュ織</string>
+    <string name="pref_summary_pebble_enable_applogs">搴旂敤绋嬪簭鏃ュ織灏嗕繚瀛樺湪 Gadgetbridge 涓紙闇�瑕侀噸鏂拌繛鎺ワ級</string>
+    <string name="pref_summary_pebble_always_ack_pebblekit">杩欏皢瀵艰嚧鍙戦�佸埌澶栭儴绗笁鏂瑰簲鐢ㄧ▼搴忕殑娑堟伅濮嬬粓绔嬪嵆寰楀埌纭</string>
+    <string name="pref_title_pebble_enable_bgjs">鍚敤鍚庡彴JS</string>
+    <string name="pref_summary_pebble_enable_bgjs">鍚敤鍚庯紝灏嗗厑璁歌〃鐩樻樉绀哄ぉ姘旓紝鐢垫睜绛変俊鎭��</string>
+    <string name="pref_title_pebble_reconnect_attempts">灏濊瘯閲嶆柊杩炴帴</string>
+    <string name="pref_title_unit_system">鍗曚綅</string>
+    <string name="pref_title_timeformat">鏃堕棿鏍煎紡</string>
+    <string name="pref_title_screentime">浜睆鏃堕棿</string>
+    <string name="prefs_title_all_day_heart_rate">鍏ㄥぉ蹇冪巼娴嬮噺</string>
+    <string name="preferences_hplus_settings">HPlus/Makibes璁剧疆</string>
+    <string name="not_connected">鏈繛鎺�</string>
+    <string name="connecting">杩炴帴涓�</string>
+    <string name="connected">宸茶繛鎺�</string>
+    <string name="unknown_state">鏈煡鐘舵��</string>
+    <string name="_unknown_">(鏈煡)</string>
+    <string name="test">娴嬭瘯</string>
+    <string name="test_notification">娴嬭瘯閫氱煡</string>
+    <string name="this_is_a_test_notification_from_gadgetbridge">杩欐槸鏉ヨ嚜 Gadgetbridge 鐨勬祴璇曢�氱煡</string>
+    <string name="bluetooth_is_not_supported_">涓嶆敮鎸佽摑鐗欍��</string>
+    <string name="bluetooth_is_disabled_">钃濈墮宸茬鐢ㄣ��</string>
+    <string name="tap_connected_device_for_activity">鐐规寜浠ユ縺娲诲凡杩炴帴鐨勮澶�</string>
+    <string name="tap_connected_device_for_vibration">鐐规寜浠ラ渿鍔ㄥ凡杩炴帴鐨勮澶�</string>
+    <string name="tap_a_device_to_connect">鐐规寜涓�涓澶囦互杩炴帴</string>
+    <string name="cannot_connect_bt_address_invalid_">鏃犳硶杩炴帴銆� 钃濈墮鍦板潃鏃犳晥锛�</string>
+    <string name="gadgetbridge_running_generic">浜敵绉戞妧姝e湪杩愯</string>
+    <string name="installing_binary_d_d">姝e湪瀹夎浜岃繘鍒舵枃浠� %1$d/%2$d</string>
+    <string name="installation_failed_">瀹夎澶辫触</string>
+    <string name="installation_successful">瀹夎鎴愬姛</string>
+    <string name="firmware_install_warning">鎮ㄦ鍦ㄥ皾璇曞畨瑁呭浐浠讹紝鐢辨偍鑷鎵挎媴椋庨櫓銆�
+\n
+\n
+\n璇ュ浐浠剁敤浜庣‖浠�(HW)鐗堟湰锛�%s</string>
+    <string name="app_install_info">鎮ㄥ嵆灏嗗畨瑁呬互涓嬪簲鐢ㄧ▼搴�:
+\n
+\n%1$s
+\n鐗堟湰 %2$s 鐢� %3$s
+\n</string>
+    <string name="n_a">N/A</string>
+    <string name="initialized">宸插垵濮嬪寲</string>
+    <string name="appversion_by_creator">%1$s by %2$s</string>
+    <string name="title_activity_discovery">鎼滅储璁惧</string>
+    <string name="discovery_stop_scanning">鍋滄鎵弿</string>
+    <string name="discovery_start_scanning">寮�濮嬫悳绱�</string>
+    <string name="action_discover">杩炴帴鏂拌澶�</string>
+    <string name="device_with_rssi">%1$s (%2$s)</string>
+    <string name="title_activity_android_pairing">閰嶅璁惧</string>
+    <string name="android_pairing_hint">浣跨敤 Android 钃濈墮閰嶅瀵硅瘽妗嗘潵閰嶅璁惧銆�</string>
+    <string name="title_activity_mi_band_pairing">閰嶅鎮ㄧ殑灏忕背鎵嬬幆</string>
+    <string name="pairing">涓� %s 閰嶅涓��</string>
+    <string name="pairing_creating_bond_with">涓� %1$s (%2$s) 閰嶅</string>
+    <string name="pairing_unable_to_pair_with">鏃犳硶涓� %1$s (%2$s) 閰嶅</string>
+    <string name="pairing_in_progress">姝e湪杩涜缁戝畾锛�%1$s (%2$s)</string>
+    <string name="pairing_already_bonded">宸蹭笌 %1$s (%2$s) 缁戝畾锛屾鍦ㄨ繛鎺モ��</string>
+    <string name="message_cannot_pair_no_mac">娌℃湁 MAC 鍦板潃閫氳繃, 涓嶈兘閰嶅銆�</string>
+    <string name="preferences_category_device_specific_settings">璁惧鐗瑰畾璁剧疆</string>
+    <string name="preferences_miband_settings">灏忕背鎵嬬幆/璺冩垜鎵嬬幆璁剧疆</string>
+    <string name="male">鐢�</string>
+    <string name="female">濂�</string>
+    <string name="other">鍏跺畠</string>
+    <string name="left">宸�</string>
+    <string name="right">鍙�</string>
+    <string name="miband_pairing_using_dummy_userdata">娌℃湁鎻愪緵鏈夋晥鐨勭敤鎴锋暟鎹�, 鐜板湪浣跨敤铏氭嫙鐢ㄦ埛鏁版嵁銆�</string>
+    <string name="miband_pairing_tap_hint">褰撴偍鐨勫皬绫虫墜鐜尟鍔ㄥ苟闂儊鏃讹紝璇疯繛缁偣鍑诲嚑涓嬨��</string>
+    <string name="appinstaller_install">瀹夎</string>
+    <string name="discovery_connected_devices_hint">璇疯缃苟浣挎偍鐨勮澶囧彲琚彂鐜般�� 褰撳墠杩炴帴鐨勮澶囧彲鑳戒笉浼氳鍙戠幇銆� 瀵逛簬 Android 6 鍙婃洿楂樼増鏈紝鎮ㄩ渶瑕佸惎鐢ㄤ綅缃俊鎭紙濡� GPS 锛夈�� 鑻ョ鐢ㄦ潈闄愶紝 Gadgetbridge 鍙兘浼氬穿婧冨苟閲嶆柊鍚姩銆� 濡傛灉鍑犲垎閽熷悗娌℃湁鎵惧埌璁惧锛岃閲嶆柊鍚姩鎮ㄧ殑绉诲姩璁惧鍚庡啀璇曘��</string>
+    <string name="discovery_note">娉ㄦ剰:</string>
+    <string name="candidate_item_device_image">璁惧鍥惧儚</string>
+    <string name="miband_prefs_alias">鍚嶇О/鍒悕</string>
+    <string name="pref_header_vibration_count">鎸姩璁℃暟</string>
+    <string name="title_activity_sleepmonitor">鐫$湢妫�娴�</string>
+    <string name="pref_write_logfiles">鍐欏叆鏃ュ織鏂囦欢</string>
+    <string name="initializing">鍒濆鍖栦腑</string>
+    <string name="busy_task_fetch_activity_data">鑾峰彇娲诲姩鏁版嵁</string>
+    <string name="sleep_activity_date_range">浠� %1$s 鍒� %2$s</string>
+    <string name="prefs_wearside">浣╂埓鍦ㄥ乏鎵嬭繕鏄彸鎵嬶紵</string>
+    <string name="vibration_profile_short">鐭�</string>
+    <string name="vibration_profile_medium">涓�</string>
+    <string name="vibration_profile_long">闀�</string>
+    <string name="vibration_profile_alarm_clock">闂归挓</string>
+    <string name="miband_prefs_vibration">鎸姩</string>
+    <string name="vibration_try">娴嬭瘯</string>
+    <string name="pref_screen_notification_profile_sms">鐭俊閫氱煡</string>
+    <string name="pref_header_vibration_settings">鎸姩璁剧疆</string>
+    <string name="pref_screen_notification_profile_generic">涓�鑸�氱煡</string>
+    <string name="pref_screen_notification_profile_email">閭欢閫氱煡</string>
+    <string name="pref_screen_notification_profile_incoming_call">鏉ョ數閫氱煡</string>
+    <string name="pref_screen_notification_profile_generic_chat">鑱婂ぉ</string>
+    <string name="pref_screen_notification_profile_generic_navigation">瀵艰埅</string>
+    <string name="pref_screen_notification_profile_generic_social">绀句氦缃戠粶</string>
+    <string name="prefs_title_heartrate_measurement_interval">鍏ㄥぉ蹇冪巼妫�娴�</string>
+    <string name="interval_one_minute">姣忓垎閽熶竴娆�</string>
+    <string name="interval_five_minutes">姣�5鍒嗛挓涓�娆�</string>
+    <string name="interval_ten_minutes">姣�10鍒嗛挓涓�娆�</string>
+    <string name="interval_thirty_minutes">姣�30鍒嗛挓涓�娆�</string>
+    <string name="interval_one_hour">姣忓皬鏃朵竴娆�</string>
+    <string name="stats_title">閫熷害</string>
+    <string name="stats_x_axis_label">鎬诲垎閽�</string>
+    <string name="stats_y_axis_label">姣忓垎閽熸鏁�</string>
+    <string name="control_center_find_lost_device">鏌ユ壘涓㈠け鐨勮澶�</string>
+    <string name="control_center_cancel_to_stop_vibration">鍙栨秷浠ュ仠姝㈡尟鍔ㄣ��</string>
+    <string name="title_activity_charts">娲诲姩涓庣潯鐪�</string>
+    <string name="title_activity_set_alarm">璁剧疆闂归挓</string>
+    <string name="controlcenter_start_configure_alarms">璁剧疆闂归挓</string>
+    <string name="title_activity_alarm_details">闂归挓鍒楄〃</string>
+    <string name="alarm_sun_short">鍛ㄦ棩</string>
+    <string name="alarm_mon_short">鍛ㄤ竴</string>
+    <string name="alarm_tue_short">鍛ㄤ簩</string>
+    <string name="alarm_wed_short">鍛ㄤ笁</string>
+    <string name="alarm_thu_short">鍛ㄥ洓</string>
+    <string name="alarm_fri_short">鍛ㄤ簲</string>
+    <string name="alarm_sat_short">鍛ㄥ叚</string>
+    <string name="alarm_smart_wakeup">鏅鸿兘鍞ら啋</string>
+    <string name="user_feedback_miband_set_alarms_failed">璁剧疆闂归挓鏃跺嚭鐜伴敊璇�, 璇烽噸璇曘��</string>
+    <string name="user_feedback_miband_set_alarms_ok">闂归挓宸插彂閫佸埌璁惧銆�</string>
+    <string name="chart_no_data_synchronize">娌℃湁鏁版嵁銆傜珛鍗冲悓姝ワ紵</string>
+    <string name="user_feedback_miband_activity_data_transfer">鍗冲皢浠� %2$s 寮�濮嬩紶杈� %1$s 鏁版嵁</string>
+    <string name="miband_prefs_fitness_goal">姣忓ぉ姝ユ暟鐩爣</string>
+    <string name="dbaccess_error_executing">鎵ц \'%1$s\' 鏃跺嚭閿�</string>
+    <string name="controlcenter_start_activitymonitor">鎮ㄧ殑娲诲姩</string>
+    <string name="cannot_connect">鏃犳硶杩炴帴: %1$s</string>
+    <string name="installer_activity_unable_to_find_handler">鏃犳硶鎵惧埌瀹夎姝ゆ枃浠剁殑澶勭悊绋嬪簭銆�</string>
+    <string name="pbw_install_handler_unable_to_install">鏃犳硶瀹夎鎸囧畾鐨勬枃浠�: %1$s</string>
+    <string name="pbw_install_handler_hw_revision_mismatch">鏃犳硶瀹夎鎸囧畾鐨勫浐浠�: 瀹冧笌鎮ㄧ殑 Pebble 纭欢鐗堟湰涓嶇銆�</string>
+    <string name="installer_activity_wait_while_determining_status">姝e湪纭畾瀹夎鐘舵��, 璇风◢鍊欌��</string>
+    <string name="notif_battery_low_title">鐢甸噺涓嶈冻锛�</string>
+    <string name="notif_battery_low_percent">%1$s 鍓╀綑鐢甸噺锛�%2$s%%</string>
+    <string name="notif_battery_low_bigtext_last_charge_time">涓婃鍏呯數锛�%s 
+\n</string>
+    <string name="notif_battery_low_bigtext_number_of_charges">鍏呯數娆℃暟锛�%s</string>
+    <string name="sleepchart_your_sleep">鐫$湢</string>
+    <string name="weeksleepchart_sleep_a_week">涓�鍛ㄧ潯鐪�</string>
+    <string name="weeksleepchart_today_sleep_description">浠婂ぉ鐫$湢锛岀洰鏍囷細%1$s</string>
+    <string name="weekstepschart_steps_a_week">涓�鍛ㄦ鏁�</string>
+    <string name="activity_sleepchart_activity_and_sleep">娲诲姩</string>
+    <string name="updating_firmware">鍥轰欢鏇存柊涓��</string>
+    <string name="fwapp_install_device_not_ready">鏃犳硶瀹夎鏂囦欢, 璁惧鏈氨缁��</string>
+    <string name="installhandler_firmware_name">%1$s: %2$s %3$s</string>
+    <string name="miband_fwinstaller_compatible_version">鍏煎鐗堟湰</string>
+    <string name="miband_fwinstaller_untested_version">鏈粡娴嬭瘯鐨勭増鏈�!</string>
+    <string name="fwappinstaller_connection_state">宸茶繛鎺ヨ澶囷細%1$s</string>
+    <string name="pbw_installhandler_pebble_firmware">Pebble 鍥轰欢 %1$s</string>
+    <string name="pbwinstallhandler_correct_hw_revision">姝g‘鐨勭‖浠朵慨璁�</string>
+    <string name="pbwinstallhandler_incorrect_hw_revision">纭欢鐗堟湰涓嶅尮閰嶏紒</string>
+    <string name="pbwinstallhandler_app_item">%1$s (%2$s)</string>
+    <string name="updatefirmwareoperation_updateproblem_do_not_reboot">鍥轰欢浼犺緭鍑虹幇闂銆傝涓嶈閲嶆柊鍚姩鎮ㄧ殑灏忕背鎵嬬幆锛�</string>
+    <string name="updatefirmwareoperation_metadata_updateproblem">鍥轰欢鍏冩暟鎹紶杈撳嚭鐜伴棶棰�</string>
+    <string name="updatefirmwareoperation_update_complete">鍥轰欢瀹夎瀹屾垚</string>
+    <string name="updatefirmwareoperation_update_complete_rebooting">鍥轰欢瀹夎瀹屾垚, 閲嶆柊鍚姩璁惧鈥�</string>
+    <string name="updatefirmwareoperation_write_failed">鍥轰欢鍐欏叆澶辫触</string>
+    <string name="chart_steps">姝ユ暟</string>
+    <string name="calories">鍗¤矾閲�</string>
+    <string name="distance">璺濈</string>
+    <string name="clock">鏃堕挓</string>
+    <string name="heart_rate">蹇冪巼</string>
+    <string name="battery">鐢甸噺</string>
+    <string name="liveactivity_live_activity">瀹炴椂娲诲姩鏁版嵁</string>
+    <string name="weeksteps_today_steps_description">浠婂ぉ鐨勬鏁�, 鐩爣锛�%1$s</string>
+    <string name="pref_title_dont_ack_transfer">涓嶅彂閫佹椿鍔ㄦ暟鎹�</string>
+    <string name="pref_summary_dont_ack_transfers">濡傛灉娲诲姩鏁版嵁鏈紶杈撳埌璁惧锛屽垯涓嶄細琚竻闄ゃ�� Gadgetbridge 涓庡叾浠栧簲鐢ㄧ▼搴忎竴璧蜂娇鐢ㄦ椂寰堟湁鐢ㄣ��</string>
+    <string name="pref_summary_keep_data_on_device">鍗充娇鍦ㄥ悓姝ュ悗涔熶細灏嗘椿鍔ㄦ暟鎹繚鐣欏湪璁惧涓娿�� 濡傛灉 GB 涓庡叾浠栧簲鐢ㄧ▼搴忎竴璧蜂娇鐢紝鍒欏緢鏈夌敤銆� 杩欏彲鑳戒細瀵艰嚧鎵嬭〃绌洪棿涓嶈冻鍜�/鎴栧仠姝㈡甯稿悓姝ャ��</string>
+    <string name="pref_title_low_latency_fw_update">浣跨敤浣庡欢杩熸ā寮忚繘琛屽浐浠舵洿鏂�</string>
+    <string name="pref_summary_low_latency_fw_update">杩欏彲鑳藉鍥轰欢澶辫触鐨勮澶囨湁甯姪銆�</string>
+    <string name="live_activity_steps_history">姝ユ暟鍘嗗彶</string>
+    <string name="live_activity_current_steps_per_minute">褰撳墠姣忓垎閽熸鏁�</string>
+    <string name="live_activity_total_steps">绱姝ユ暟</string>
+    <string name="live_activity_steps_per_minute_history">姣忓垎閽熸鏁板巻鍙�</string>
+    <string name="live_activity_start_your_activity">璇峰紑濮嬫偍鐨勮繍鍔�</string>
+    <string name="abstract_chart_fragment_kind_activity">娲诲姩</string>
+    <string name="abstract_chart_fragment_kind_light_sleep">娴呯潯</string>
+    <string name="abstract_chart_fragment_kind_deep_sleep">娣辩潯</string>
+    <string name="abstract_chart_fragment_kind_not_worn">鏈僵鎴�</string>
+    <string name="device_not_connected">鏈繛鎺ャ��</string>
+    <string name="user_feedback_all_alarms_disabled">鎵�鏈夐椆閽熷凡鍏抽棴</string>
+    <string name="pref_title_keep_data_on_device">灏嗘椿鍔ㄦ暟鎹繚鐣欏湪璁惧涓�</string>
+    <string name="miband_fwinstaller_incompatible_version">鍥轰欢涓嶅吋瀹�</string>
+    <string name="fwinstaller_firmware_not_compatible_to_device">姝ゅ浐浠朵笌璁惧涓嶅吋瀹�</string>
+    <string name="miband_prefs_hr_sleep_detection">浣跨敤蹇冪巼妫�娴嬩互鎻愰珮鐫$湢妫�娴嬬簿搴�</string>
+    <string name="miband_prefs_device_time_offset_hours">璁惧鏃堕棿鍋忕Щ (鐢ㄤ簬妫�娴嬪�掔彮宸ヤ綔浜哄憳鐨勭潯鐪�)</string>
+    <string name="miband2_prefs_dateformat">鏃ユ湡鏍煎紡</string>
+    <string name="dateformat_time">浠呮椂闂�</string>
+    <string name="dateformat_date_time"><![CDATA[鏃堕棿鍜屾棩鏈焆]></string>
+    <string name="mi2_prefs_button_actions">鎸夐挳鎿嶄綔</string>
+    <string name="mi2_prefs_button_actions_summary">鎸囧畾鎸夐挳鎸変笅鏃剁殑鎿嶄綔</string>
+    <string name="mi2_prefs_button_press_count">鎸夐挳鎸変笅璁℃暟</string>
+    <string name="mi2_prefs_button_press_count_summary">瑙﹀彂浜嬩欢鐨勬寜閽寜涓嬫寜鏁� 1.鍚庣画鐩稿悓鏁伴噺鐨勬寜涓嬪垱寤轰簨浠� 2.浠ユ绫绘帹銆�</string>
+    <string name="mi2_prefs_button_action">鍚敤鎸夐挳鎿嶄綔</string>
+    <string name="mi2_prefs_button_action_summary">瀵规寜涓嬫寚瀹氭鏁扮殑鎸夐挳鍚敤鐩稿簲鐨勬搷浣�</string>
+    <string name="mi2_prefs_button_action_vibrate">鍚敤鎵嬬幆闇囧姩</string>
+    <string name="mi2_prefs_button_action_vibrate_summary">鍦ㄥ凡瑙﹀彂鐨勬寜閽姩浣滀笂鍚敤鎵嬬幆鎸姩</string>
+    <string name="mi2_prefs_button_press_count_max_delay">鎸夐挳鏈�澶у欢杩�</string>
+    <string name="mi2_prefs_button_press_count_max_delay_summary">鎸変笅鎸夐挳涔嬮棿鐨勬渶澶у欢杩燂紝浠ユ绉掍负鍗曚綅</string>
+    <string name="mi2_prefs_goal_notification">杈炬爣鎻愰啋</string>
+    <string name="mi2_prefs_goal_notification_summary">杈炬垚杩愬姩鐩爣鍚�, 鎵嬬幆/鎵嬭〃灏嗕細鎸姩鎻愰啋</string>
+    <string name="mi2_prefs_display_items">鏄剧ず鍐呭</string>
+    <string name="mi2_prefs_display_items_summary">閫夋嫨鍦ㄦ墜鐜睆骞曚笂鏄剧ず鐨勫唴瀹�</string>
+    <string name="mi2_prefs_activate_display_on_lift">鎶厱浜睆</string>
+    <string name="mi2_prefs_rotate_wrist_to_switch_info">鏃嬭浆鎵嬭厱浠ュ垏鎹俊鎭�</string>
+    <string name="mi2_prefs_do_not_disturb">鍕挎壈妯″紡</string>
+    <string name="mi2_prefs_do_not_disturb_summary">鎵嬬幆灏嗕笉浼氭敹鍒伴�氱煡</string>
+    <string name="mi2_prefs_inactivity_warnings">涔呭潗鎻愰啋</string>
+    <string name="mi2_prefs_inactivity_warnings_summary">鎸佺画涓�娈垫椂闂存湭娲诲姩, 鎵嬬幆灏嗘尟鍔ㄦ彁閱�</string>
+    <string name="mi2_prefs_inactivity_warnings_threshold">涔呭潗鏃堕棿闃堝�硷紙鍒嗛挓锛�</string>
+    <string name="mi2_prefs_inactivity_warnings_dnd_summary">鍦ㄤ竴娈垫椂闂村唴绂佺敤涔呭潗鎻愰啋</string>
+    <string name="mi2_prefs_do_not_disturb_start">寮�濮嬫椂闂�</string>
+    <string name="mi2_prefs_do_not_disturb_end">缁撴潫鏃堕棿</string>
+    <string name="automatic">鑷姩</string>
+    <string name="simplified_chinese">绠�浣撲腑鏂�</string>
+    <string name="traditional_chinese">绻佷綋涓枃</string>
+    <string name="english">鑻辫</string>
+    <string name="FetchActivityOperation_about_to_transfer_since">鍑嗗浠� %1$s 寮�濮嬩紶杈撴暟鎹�</string>
+    <string name="waiting_for_reconnect">姝e湪绛夊緟閲嶆柊杩炴帴</string>
+    <string name="activity_prefs_about_you">鍏充簬浣�</string>
+    <string name="activity_prefs_year_birth">鍑虹敓骞翠唤</string>
+    <string name="activity_prefs_gender">鎬у埆</string>
+    <string name="activity_prefs_height_cm">韬珮锛堝帢绫筹級</string>
+    <string name="activity_prefs_weight_kg">浣撻噸锛堝叕鏂わ級</string>
+    <string name="authenticating">璁よ瘉涓�</string>
+    <string name="authentication_required">闇�瑕佽韩浠介獙璇�</string>
+    <string name="appwidget_text">Zzz</string>
+    <string name="add_widget">娣诲姞灏忛儴浠�</string>
+    <string name="activity_prefs_sleep_duration">鐫$湢鏃堕棿鐩爣</string>
+    <string name="appwidget_setting_alarm">宸茶缃竴涓椆閽熶负 %1$02d:%2$02d</string>
+    <string name="device_hw">纭欢鐗堟湰:%1$s</string>
+    <string name="device_fw">鍥轰欢鐗堟湰:%1$s</string>
+    <string name="error_creating_directory_for_logfiles">涓烘棩蹇楁枃浠跺垱寤虹洰褰曟椂鍑洪敊:%1$s</string>
+    <string name="updatefirmwareoperation_update_in_progress">姝e湪杩涜鍥轰欢鏇存柊</string>
+    <string name="updatefirmwareoperation_firmware_not_sent">鍥轰欢鏈彂閫�</string>
+    <string name="charts_legend_heartrate">蹇冪巼</string>
+    <string name="live_activity_heart_rate">蹇冪巼</string>
+    <string name="pref_title_pebble_health_store_raw">鍦ㄦ暟鎹簱涓瓨鍌ㄥ師濮嬭褰�</string>
+    <string name="pref_summary_pebble_health_store_raw">浠ュ師鏍蜂繚瀛樻暟鎹紝澧炲姞鏁版嵁搴撲娇鐢ㄩ噺浠ュ厑璁哥◢鍚庣炕璇戙��</string>
+    <string name="action_db_management">鏁版嵁绠$悊</string>
+    <string name="title_activity_db_management">鏁版嵁绠$悊</string>
+    <string name="activity_db_management_import_export_explanation">瀵煎叆/瀵煎嚭鎿嶄綔灏嗕細浣跨敤鎮ㄨ澶囩殑浠ヤ笅璺緞锛堣涓嬶級銆傝鐩綍鍙互琚偍鐨勫叾浠栧簲鐢ㄥ拰鎮ㄧ殑璁$畻鏈鸿闂�傝娉ㄦ剰锛氳嫢鎮ㄥ嵏杞紾adgetbridge锛岃鐩綍涓殑鎵�鏈夊唴瀹逛篃浼氳鍒犻櫎銆傚叾涓寘鎷殑鏁版嵁鏈夛細
+\n Export_preference - 鍏ㄥ眬璁剧疆
+\n Export_preference_MAC - 鐗瑰畾璁惧璁剧疆
+\n Gadgetbridge - 璁惧鍜屾椿鍔ㄦ暟鎹簱
+\n Gadgetbridge_date - 鏁版嵁搴撳鍑虹殑鏃ユ湡
+\n *.gpx - GPS璁板綍
+\n *.log - 鏃ュ織鏂囦欢
+\n瀵煎嚭鍚庤鍓嶅線姝ゅ鎵惧埌杩欎簺鏂囦欢锛堟垨鎮ㄥ笇鏈涘鍏ユ枃浠剁殑璺緞锛夛細</string>
+    <string name="activity_db_management_merge_old_title">鍒犻櫎鏃х増鏁版嵁搴�</string>
+    <string name="dbmanagementactivvity_cannot_access_export_path">鏃犳硶璁块棶瀵煎嚭璺緞銆傝涓庡紑鍙戣�呰仈绯汇��</string>
+    <string name="dbmanagementactivity_exported_to">瀵煎嚭鍒�:%1$s</string>
+    <string name="dbmanagementactivity_error_exporting_db">瀵煎嚭鏁版嵁搴撴椂鍑洪敊:%1$s</string>
+    <string name="dbmanagementactivity_error_exporting_shared">瀵煎嚭棣栭�夐」鏃跺嚭閿�:%1$s</string>
+    <string name="dbmanagementactivity_import_data_title">鏄惁瀵煎叆鏁版嵁锛�</string>
+    <string name="dbmanagementactivity_overwrite_database_confirmation">鐪熺殑瑕嗙洊褰撳墠鏁版嵁鍚楋紵鎮ㄥ綋鍓嶇殑鎵�鏈夋椿鍔ㄦ暟鎹紙濡傛灉鏈夛級銆佽澶囧拰棣栭�夐」閮藉皢琚鐩栥��</string>
+    <string name="dbmanagementactivity_import_successful">瀵煎叆鎴愬姛銆�</string>
+    <string name="dbmanagementactivity_error_importing_db">瀵煎叆鏁版嵁搴撴椂鍑洪敊:%1$s</string>
+    <string name="dbmanagementactivity_error_importing_shared">瀵煎叆棣栭�夐」鏃跺嚭閿�:%1$s</string>
+    <string name="dbmanagementactivity_delete_activity_data_title">鍒犻櫎娲诲姩鏁版嵁锛�</string>
+    <string name="dbmanagementactivity_really_delete_entire_db">纭畾鍒犻櫎鏁翠釜鏁版嵁搴擄紵鎮ㄧ殑鎵�鏈夋椿鍔ㄦ暟鎹拰鏈夊叧璁惧鐨勪俊鎭兘灏嗕涪澶便��</string>
+    <string name="dbmanagementactivity_database_successfully_deleted">鏁版嵁宸插垹闄ゃ��</string>
+    <string name="dbmanagementactivity_db_deletion_failed">鏁版嵁搴撳垹闄ゅけ璐ャ��</string>
+    <string name="dbmanagementactivity_delete_old_activity_db">鍒犻櫎鏃х殑娲诲姩鏁版嵁搴擄紵</string>
+    <string name="dbmanagementactivity_delete_old_activitydb_confirmation">鏄惁鐪熺殑鍒犻櫎鏃х殑娲诲姩鏁版嵁搴擄紵鏈鍏ョ殑娲诲姩鏁版嵁灏嗕涪澶便��</string>
+    <string name="dbmanagementactivity_old_activity_db_successfully_deleted">宸插垹闄ゆ棫鐨勬椿鍔ㄦ暟鎹��</string>
+    <string name="dbmanagementactivity_old_activity_db_deletion_failed">鏃х殑娲诲姩鏁版嵁搴撳垹闄ゅけ璐ャ��</string>
+    <string name="dbmanagementactivity_overwrite">瑕嗙洊</string>
+    <string name="Cancel">鍙栨秷</string>
+    <string name="Delete">鍒犻櫎</string>
+    <string name="title_activity_vibration">鎸姩</string>
+    <string name="title_activity_pebble_pairing">Pebble 閰嶅涓�</string>
+    <string name="pebble_pairing_hint">鎮ㄧ殑 Android 璁惧涓婂皢浼氬脊鍑轰竴涓厤瀵瑰璇濇銆傚鏋滄病鏈夊嚭鐜�, 璇锋煡鐪嬮�氱煡鏍忓苟鎺ュ彈閰嶅璇锋眰銆備箣鍚�, 鎺ュ彈浣犵殑 Pebble 閰嶅瑕佹眰銆�</string>
+    <string name="weather_notification_label">璇风‘淇濆湪澶╂皵閫氱煡搴旂敤绋嬪簭涓惎鐢ㄦ澶栬锛屼互鑾峰彇鎮ㄧ殑 Pebble 涓婄殑澶╂皵淇℃伅銆� 
+\n 
+\n姝ゅ涓嶉渶瑕侀厤缃�� 
+\n 
+\n鍚敤 Pebble 涓婄殑澶╂皵搴旂敤绋嬪簭鍙互浠庘�滃簲鐢ㄧ▼搴忕鐞嗏�濅腑鑾峰緱銆� 
+\n 
+\n鏀寔鐨勮〃鐩樺皢浼氳嚜鍔ㄦ樉绀哄ぉ姘斻��</string>
+    <string name="pref_title_setup_bt_pairing">鍚敤钃濈墮閰嶅</string>
+    <string name="pref_summary_setup_bt_pairing">濡傛灉杩炴帴鏈夐棶棰橈紝璇峰仠鐢�</string>
+    <string name="unit_metric">鍏埗</string>
+    <string name="unit_imperial">鑻卞埗</string>
+    <string name="timeformat_24h">24灏忔椂鍒�</string>
+    <string name="pref_screen_notification_profile_alarm_clock">闂归挓</string>
+    <string name="activity_web_view">Web瑙嗗浘Activity</string>
+    <string name="StringUtils_sender">(%1$s)</string>
+    <string name="find_device_you_found_it">鎵惧埌浜�!</string>
+    <string name="miband2_prefs_timeformat">灏忕背鎵嬬幆2锛氭椂闂存牸寮�</string>
+    <string name="mi2_fw_installhandler_fw53_hint">鎮ㄩ渶瑕佸畨瑁呯増鏈彿涓�%1$s鐨勫浐浠跺湪瀹夎姝ゅ浐浠跺墠锛�</string>
+    <string name="mi2_enable_text_notifications">鏂囨湰閫氱煡</string>
+    <string name="mi2_enable_text_notifications_summary"><![CDATA[ 闇�瑕佸凡瀹夎鍥轰欢鐗堝ぇ浜庢垨绛変簬 1.0.1.28 鍜� Mili_pro. ft * 銆俔]></string>
+    <string name="off">鍏�</string>
+    <string name="mi2_dnd_off">鍏�</string>
+    <string name="mi2_dnd_automatic">鑷姩 (鐫$湢妫�娴�)</string>
+    <string name="mi2_dnd_scheduled">璁″垝 (鏃堕棿闂撮殧)</string>
+    <string name="discovery_attempting_to_pair">灏濊瘯涓� %1$s 閰嶅</string>
+    <string name="discovery_bonding_failed_immediately">涓� %1$s 鐨勮繛鎺ュけ璐ャ��</string>
+    <string name="discovery_trying_to_connect_to">灏濊瘯杩炴帴鍒帮細%1$s</string>
+    <string name="discovery_enable_bluetooth">鍚敤钃濈墮浠ュ彂鐜拌澶囥��</string>
+    <string name="discovery_successfully_bonded">宸叉垚鍔熶笌 %1$s 缁戝畾銆�</string>
+    <string name="discovery_pair_title">涓� %1$s 閰嶅锛�</string>
+    <string name="discovery_pair_question">閫夋嫨璁惧浠ヨ繘琛岄厤瀵广�傚鏋滃け璐ワ紝璇烽噸璇曟湭鑳介厤瀵圭殑璁惧銆�</string>
+    <string name="discovery_yes_pair">閰嶅</string>
+    <string name="discovery_dont_pair">涓嶉厤瀵�</string>
+    <string name="_pebble_watch_open_on_phone">鍦� Android 璁惧涓婃墦寮�</string>
+    <string name="_pebble_watch_mute">闈欓煶</string>
+    <string name="_pebble_watch_reply">鍥炲</string>
+    <string name="kind_firmware">鍥轰欢</string>
+    <string name="kind_invalid">鏃犳晥鏁版嵁</string>
+    <string name="kind_font">瀛椾綋</string>
+    <string name="kind_gps">GPS 鍥轰欢</string>
+    <string name="kind_gps_cep">GPS 璇樊淇</string>
+    <string name="kind_resources">璧勬簮</string>
+    <string name="devicetype_unknown">鏈煡璁惧</string>
+    <string name="devicetype_miband">灏忕背鎵嬬幆</string>
+    <string name="devicetype_miband2">灏忕背鎵嬬幆2</string>
+    <string name="devicetype_amazfit_bip">璺冩垜鎵嬭〃闈掓槬鐗�</string>
+    <string name="devicetype_amazfit_cor">璺冩垜鎵嬬幆</string>
+    <string name="pref_title_weather">澶╂皵</string>
+    <string name="vibration_profile_waterdrop">姘存淮</string>
+    <string name="devicetype_test">娴嬭瘯璁惧</string>
+    <string name="pref_title_pebble_forcele">鎬绘槸浼樺厛浣跨敤浣庡姛鑰楄摑鐗�</string>
+    <string name="kind_watchface">琛ㄧ洏</string>
+    <string name="appmanager_cached_watchapps_watchfaces">缂撳瓨涓簲鐢�</string>
+    <string name="pref_title_canned_messages_set">鍦ㄨ澶囦笂鍗囩骇</string>
+    <string name="pref_screen_vibration_profile">鎸姩閰嶇疆</string>
+    <string name="vibration_profile_ring">鍝嶉搩</string>
+    <string name="mi2_prefs_button_press_broadcast">瑕佸彂閫佺殑骞挎挱娑堟伅</string>
+    <string name="devicetype_pebble">Pebble</string>
+    <string name="pref_summary_pebble_forcele">浣跨敤瀹為獙鎬� Pebble LE 浠ユ敮鎸佹墍鏈� Pebble 锛屼唬鏇� BT 缁忓吀杩炴帴銆� 杩欓鍏堥渶瑕佷互闈� LE 閰嶅锛岀劧鍚庝互 Pebble LE 杩炴帴</string>
+    <string name="pref_summary_pebble_mtu_limit">濡傛灉鎮ㄧ殑 Pebble 2 / Pebble LE 鏃犳硶姝e父宸ヤ綔锛岃灏濊瘯浣跨敤姝よ缃潵闄愬埗 MTU 锛堟湁鏁堣寖鍥� 20鈥�512 锛�</string>
+    <string name="pref_header_auto_export">鑷姩瀵煎嚭</string>
+    <string name="pref_title_auto_export_enabled">鍚敤鑷姩瀵煎嚭</string>
+    <string name="pref_title_auto_export_location">瀵煎嚭浣嶇疆</string>
+    <string name="pref_title_auto_export_interval">瀵煎嚭闂撮殧</string>
+    <string name="pref_summary_auto_export_interval">姣�%d灏忔椂瀵煎嚭涓�娆�</string>
+    <string name="tap_connected_device_for_app_mananger">鐐规寜涓哄凡杩炴帴鐨勮澶囩鐞嗗簲鐢�</string>
+    <string name="notif_export_failed_title">鏁版嵁搴撳鍑哄け璐ワ紒 璇锋鏌ユ偍鐨勮缃��</string>
+    <string name="miband_prefs_reserve_alarm_calendar">棰勮鏈潵浜嬩欢</string>
+    <string name="mi2_prefs_button_press_broadcast_summary">浜嬩欢涓庡箍鎾秷鎭竴璧峰彂閫併�傚弬鏁� `button_id` 灏嗕細鑷姩娣诲姞鍒版瘡鏉℃秷鎭腑銆�</string>
+    <string name="DEVINFO_HR_VER">"蹇冪巼锛� "</string>
+    <string name="timeformat_am_pm">AM/PM</string>
+    <string name="devicetype_vibratissimo">Vibratissimo</string>
+    <string name="devicetype_hplus">HPlus</string>
+    <string name="choose_auto_export_location">閫夋嫨瀵煎嚭浣嶇疆</string>
+    <string name="notification_channel_name">涓�鑸�</string>
+    <string name="controlcenter_change_led_color">鍒囨崲 LED 鐏鑹�</string>
+    <string name="controlcenter_change_fm_frequency">鏇存敼 FM 棰戠巼</string>
+    <string name="controlcenter_calibrate_device">鏍″噯璁惧</string>
+    <string name="blacklist_all_for_notifications">娣诲姞鎵�鏈夐�氱煡鍒伴粦鍚嶅崟</string>
+    <string name="whitelist_all_for_notifications">娣诲姞鎵�鏈夐�氱煡鍒扮櫧鍚嶅崟</string>
+    <string name="fw_upgrade_notice_miband3">鎮ㄥ嵆灏嗗畨瑁� 灏忕背鎵嬬幆3 鐨勫浐浠� %s 銆�
+\n
+\n璇风‘淇濆厛瀹夎 .fw 鏂囦欢锛屽啀瀹夎 .res 鏂囦欢銆傛偍鐨勬墜鐜皢浼氬湪瀹夎 .fw 鏂囦欢鍚庨噸鍚��
+\n
+\n娉ㄦ剰锛氬鏋滄偍宸茬粡瀹夎鐩稿悓鐗堟湰鐨� .res 鏂囦欢浜嗭紝鍒欐棤闇�瀹夎銆�
+\n
+\n椋庨櫓鑷媴锛�</string>
+    <string name="pref_title_notifications_timeout">閫氱煡闂撮殧鏈�鐭椂闂�</string>
+    <string name="pref_title_rtl">浠庡彸鍒板乏</string>
+    <string name="pref_summary_rtl">濡傛灉鎮ㄧ殑璁惧鏃犳硶鏄剧ず浠庡彸鍒板乏鐨勮瑷�锛岃閫変腑姝ら�夐」</string>
+    <string name="pref_rtl_max_line_length">浠庡彸鍒板乏鏈�闀垮瓧鏁�</string>
+    <string name="pref_rtl_max_line_length_summary">寤堕暱鎴栫缉鐭浠庡彸鍒板乏鏂囨湰鍒嗗壊</string>
+    <string name="pref_title_canned_reply_suffix">甯歌鍚庣紑</string>
+    <string name="pref_title_pebble_mtu_limit">Pebble 2/LE GATT MTU 闄愬埗</string>
+    <string name="pref_title_pebble_gatt_clientonly">浠呴檺 GATT 瀹㈡埛绔�</string>
+    <string name="pref_summary_pebble_gatt_clientonly">杩欐槸浠呬负 Pebble 2 鐨勯�夐」锛屼笖涓鸿瘯楠屾�ц川锛屽鏋滄偍閬囧埌杩炴帴鎬ч棶棰橈紝璇峰皾璇�</string>
+    <string name="pref_title_pebble_always_ack_pebblekit">鎻愬墠 ACK PebbleKit</string>
+    <string name="prefs_screen_orientation">灞忓箷鏈濆悜</string>
+    <string name="pref_auto_fetch">鑷姩鑾峰彇娲诲姩鏁版嵁</string>
+    <string name="pref_auto_fetch_summary">灞忓箷瑙i攣鏃朵細鑷姩鑾峰彇銆備粎鍦ㄨ缃簡閿佸畾鏈哄埗鎵嶄細鏈夋晥锛�</string>
+    <string name="pref_auto_fetch_limit_fetches">鍒锋柊鏈�灏忛棿闅�</string>
+    <string name="pref_auto_fetch_limit_fetches_summary">姣忛殧 %d 鍒嗛挓鍒锋柊</string>
+    <string name="horizontal">妯悜</string>
+    <string name="vertical">绾靛悜</string>
+    <string name="watch9_pairing_tap_hint">褰撴偍鐨勬墜鐜渿鍔ㄦ椂锛屾檭鍔ㄨ澶囨垨鑰呮寜涓嬫寜閽��</string>
+    <string name="vibration_profile_staccato">闂存柇</string>
+    <string name="notif_battery_low">%1$s 鐢甸噺浣�</string>
+    <string name="notif_battery_low_extended">%1$s 鐢甸噺浣庯細%2$s</string>
+    <string name="lack_of_sleep">鐫$湢涓嶈冻锛�%1$s</string>
+    <string name="overslept">鐫¤繃澶达細%1$s</string>
+    <string name="no_limit">娌℃湁闄愬埗</string>
+    <string name="seconds_5">5 绉�</string>
+    <string name="seconds_10">10 绉�</string>
+    <string name="seconds_20">20 绉�</string>
+    <string name="seconds_30">30 绉�</string>
+    <string name="minutes_1">1 鍒嗛挓</string>
+    <string name="minutes_5">5 鍒嗛挓</string>
+    <string name="minutes_10">10 鍒嗛挓</string>
+    <string name="minutes_30">30 鍒嗛挓</string>
+    <string name="lack_of_step">缂哄皯姝ユ暟锛�%1$d</string>
+    <string name="overstep">瓒呰繃姝ユ暟锛�%1$d</string>
+    <string name="live_activity_max_heart_rate">褰撳墠锛忔渶澶у績鐜囷細%1$d / %2$d</string>
+    <string name="you_slept">%1$s - %2$s</string>
+    <string name="you_did_not_sleep">鎮ㄦ病鏈夌潯瑙�</string>
+    <string name="mi3_prefs_band_screen_unlock">鎵嬬幆灞忓箷瑙i攣\"</string>
+    <string name="mi3_prefs_band_screen_unlock_summary">婊戝姩浠ヨВ閿佹墜鐜睆骞�</string>
+    <string name="mi3_prefs_night_mode">澶滄櫄妯″紡</string>
+    <string name="mi3_prefs_night_mode_summary">鍒板鏅氭椂鑷姩闄嶄綆鎵嬬幆灞忓箷浜害</string>
+    <string name="norwegian_bokmal">鎸▉璇�</string>
+    <string name="spanish">瑗跨彮鐗欒</string>
+    <string name="russian">淇勮</string>
+    <string name="german">寰疯</string>
+    <string name="italian">鎰忓ぇ鍒╄</string>
+    <string name="french">娉曡</string>
+    <string name="polish">娉㈠叞璇�</string>
+    <string name="korean">闊╄</string>
+    <string name="japanese">鏃ヨ</string>
+    <string name="activity_prefs_charts">鍥捐〃璁剧疆</string>
+    <string name="activity_prefs_chart_max_heart_rate">鏈�楂樺績鐜�</string>
+    <string name="activity_prefs_chart_min_heart_rate">鏈�浣庡績鐜�</string>
+    <string name="ok">濂�</string>
+    <string name="on">寮�</string>
+    <string name="mi3_night_mode_sunset">鍦ㄦ棩钀芥椂</string>
+    <string name="controlcenter_start_activity_tracks">鎮ㄧ殑娲诲姩璺熻釜</string>
+    <string name="activity_type_not_measured">娌℃湁娴嬮噺</string>
+    <string name="activity_type_activity">娲诲姩</string>
+    <string name="activity_type_light_sleep">娴呯潯鐪�</string>
+    <string name="activity_type_deep_sleep">娣辩潯鐪�</string>
+    <string name="activity_type_not_worn">璁惧娌℃湁浣╂埓</string>
+    <string name="activity_type_running">璺戞</string>
+    <string name="activity_type_walking">璧拌矾</string>
+    <string name="activity_type_swimming">娓告吵</string>
+    <string name="activity_type_unknown">鏈煡娲诲姩</string>
+    <string name="activity_summaries">杩愬姩娲诲姩</string>
+    <string name="activity_type_biking">鑷杞�</string>
+    <string name="activity_type_treadmill">璺戞鏈�</string>
+    <string name="select_all">閫夋嫨鍏ㄩ儴</string>
+    <string name="share">鍒嗕韩</string>
+    <string name="reset_index">閲嶈鍒锋柊鏃ユ湡</string>
+    <string name="kind_gps_almanac">GPS 鍘嗗彶</string>
+    <string name="devicetype_miband3">灏忕背鎵嬬幆3</string>
+    <string name="devicetype_liveview">瀹炴椂鏌ョ湅</string>
+    <string name="devicetype_makibes_f68">Makibes F68</string>
+    <string name="devicetype_exrizu_k8">Exrizu K8</string>
+    <string name="devicetype_q8">Q8</string>
+    <string name="devicetype_no1_f1">No.1 F1</string>
+    <string name="devicetype_teclast_h30">Teclast H30</string>
+    <string name="devicetype_xwatch">XWatch</string>
+    <string name="devicetype_mykronoz_zetime">MyKronoz ZeTime</string>
+    <string name="devicetype_id115">ID115</string>
+    <string name="devicetype_watch9">Watch 9</string>
+    <string name="devicetype_roidmi">鐫跨背</string>
+    <string name="devicetype_roidmi3">鐫跨背 3</string>
+    <string name="menuitem_status">鐘舵��</string>
+    <string name="menuitem_notifications">閫氱煡</string>
+    <string name="menuitem_activity">閿荤偧鍘嗗彶</string>
+    <string name="menuitem_weather">澶╂皵</string>
+    <string name="menuitem_alarm">闂归挓</string>
+    <string name="menuitem_timer">鍊掕鏃�</string>
+    <string name="menuitem_compass">鎸囧崡閽�</string>
+    <string name="menuitem_settings">璁剧疆</string>
+    <string name="menuitem_alipay">鏀粯瀹�</string>
+    <string name="menuitem_music">闊充箰</string>
+    <string name="menuitem_more">鏇村</string>
+    <string name="watch9_time_minutes">鍒嗭細</string>
+    <string name="watch9_time_hours">鏃讹細</string>
+    <string name="watch9_time_seconds">绉掞細</string>
+    <string name="watch9_calibration_hint">璁剧疆璁惧鐜板湪鍚戞偍鏄剧ず鐨勬椂闂淬��</string>
+    <string name="watch9_calibration_button">鏍″噯</string>
+    <string name="title_activity_watch9_pairing">Watch 9 閰嶅</string>
+    <string name="title_activity_watch9_calibration">Watch 9 鏍″噯</string>
+    <string name="pref_title_contextual_arabic">闃挎媺浼璇</string>
+    <string name="pref_summary_contextual_arabic">鍚敤姝ら」浠ユ敮鎸侀樋鎷変集璇澧�</string>
+    <string name="preferences_rtl_settings">浠庡彸鍒板乏鏀寔</string>
+    <string name="share_log">鍒嗕韩鏃ュ織</string>
+    <string name="share_log_warning">璇疯浣忥紝Gadgetbridge 鐨勬棩蹇楁枃浠跺寘鍚澶氫釜浜轰俊鎭紝鍖呮嫭浣嗕笉闄愪簬鍋ュ悍淇℃伅銆佺嫭鏈夎韩浠斤紙鍖呮嫭璁惧鐨� MAC 鍦板潃锛夛紝闊充箰棣栭�夐」绛夌瓑銆傝鍦ㄥ皢鏃ュ織鍙戦�佸埌鍏叡鍒楄〃鍓嶄粩缁嗕慨鏀瑰拰鍒犻櫎杩欎簺淇℃伅銆�</string>
+    <string name="warning">璀﹀憡锛�</string>
+    <string name="no_data">娌℃湁鏁版嵁</string>
+    <string name="preferences_led_color">LED 棰滆壊</string>
+    <string name="preferences_fm_frequency">FM 棰戠巼</string>
+    <string name="pref_invalid_frequency_title">涓嶆纭殑棰戠巼</string>
+    <string name="pref_invalid_frequency_message">璇疯緭鍏ヤ竴涓湪 87.5 鑷� 108.0 涔嬮棿鐨勯鐜�</string>
+    <string name="language_and_region_prefs">璇█鍜屽尯鍩熻缃�</string>
+    <string name="debugactivity_really_factoryreset_title">纭畾瑕佹仮澶嶅嚭鍘傝缃紵</string>
+    <string name="debugactivity_really_factoryreset">鎭㈠鍑哄巶璁剧疆灏嗕細鍒犻櫎宸茶繛鎺ヨ澶囦笂鐨勬墍鏈夋暟鎹紙濡傛灉鏀寔锛夈�傚皬绫�/鍗庣背璁惧灏嗕細鍚屾椂鏀瑰彉钃濈墮 MAC 鍦板潃銆傚 Gadgetbridge 鑰岃█锛岃繖灏嗘槸涓�涓柊璁惧銆�</string>
+    <string name="devicetype_casiogb6900">鍗¤タ娆� GB-6900</string>
+    <string name="title_activity_notification_filter">閫氱煡杩囨护鍣�</string>
+    <string name="edittext_notification_filter_words_hint">杈撳叆鏈熸湜鐨勫瓧璇嶏紝涓�琛屼竴涓�</string>
+    <string name="toast_notification_filter_saved_successfully">宸蹭繚瀛橀�氱煡杩囨护鍣�</string>
+    <string name="filter_mode_none">涓嶈杩囨护</string>
+    <string name="filter_mode_whitelist">褰撳寘鍚椂锛岃繘琛屾樉绀�</string>
+    <string name="filter_mode_blacklist">褰撳寘鍚椂锛屽睆钄芥樉绀�</string>
+    <string name="filter_submode_at_least_one">鑷冲皯涓�涓瓧</string>
+    <string name="filter_submode_all">鎵�鏈夊瓧璇�</string>
+    <string name="toast_notification_filter_words_empty_hint">璇疯緭鍏ヨ嚦灏戜竴涓瓧</string>
+    <string name="filter_mode">杩囨护妯″紡</string>
+    <string name="mode_configuration">妯″紡閰嶇疆</string>
+    <string name="save_configuration">淇濆瓨閰嶇疆</string>
+    <string name="appwidget_not_connected">鏈繛鎺ワ紝闂归挓鏈瀹氥��</string>
+    <string name="activity_type_exercise">閿荤偧</string>
+    <string name="prefs_disconnect_notification">鏈繛鎺ユ彁閱�</string>
+    <string name="zetime_title_settings">ZeTime璁剧疆</string>
+    <string name="zetime_title_heartrate">蹇冪巼璁剧疆</string>
+    <string name="zetime_title_screentime">浜睆鏃堕暱锛堢锛�</string>
+    <string name="zetime_title_heart_rate_alarm">蹇冪巼璀︾ず</string>
+    <string name="zetime_title_heart_rate_alarm_summary">蹇冪巼瓒呰繃闄愬�兼椂锛屾墜琛ㄥ皢鎻愰啋鎮ㄣ��</string>
+    <string name="zetime_heart_rate_alarm_enable">鍚敤蹇冪巼璀︾ず</string>
+    <string name="activity_prefs_alarm_max_heart_rate">鏈�楂樺績鐜�</string>
+    <string name="activity_prefs_alarm_min_heart_rate">鏈�浣庡績鐜�</string>
+    <string name="zetime_analog_mode">妯℃嫙妯″紡</string>
+    <string name="zetime_analog_mode_hands">浠呮寚閽�</string>
+    <string name="zetime_analog_mode_handsandsteps">鎸囬拡鍜岃蛋鍔�</string>
+    <string name="zetime_activity_tracking">婵�娲昏窡韪�</string>
+    <string name="zetime_activity_tracking_summary">鎵撳紑杩愬姩璺熻釜锛屽皢璁$畻鎮ㄧ殑姝ユ暟绛夈��</string>
+    <string name="zetime_handmove_display">鎸囬拡璧板姩</string>
+    <string name="zetime_handmove_display_summary">杞姩鎵嬭厱鍙寒灞忔垨鐔勫睆銆�</string>
+    <string name="zetime_calories_type">鍗¤矾閲岀被鍨�</string>
+    <string name="zetime_calories_type_active">浠呰繍鍔ㄦ秷鑰楃殑鐑噺</string>
+    <string name="zetime_calories_type_all">杩愯鍜岄潪杩愬姩娑堣�楃殑鐑噺</string>
+    <string name="zetime_date_format">鏃ユ湡鏍煎紡</string>
+    <string name="zetime_date_format_1">YY/MM/DD</string>
+    <string name="zetime_date_format_2">DD/MM/YY</string>
+    <string name="zetime_date_format_3">MM/DD/YY</string>
+    <string name="zetime_prefs_inactivity_repetitions">閲嶅娆℃暟</string>
+    <string name="zetime_prefs_inactivity_mo">鍛ㄤ竴</string>
+    <string name="zetime_prefs_inactivity_tu">鍛ㄤ簩</string>
+    <string name="zetime_prefs_inactivity_we">鍛ㄤ笁</string>
+    <string name="zetime_prefs_inactivity_th">鍛ㄥ洓</string>
+    <string name="zetime_prefs_inactivity_fr">鍛ㄤ簲</string>
+    <string name="zetime_prefs_inactivity_sa">鍛ㄥ叚</string>
+    <string name="zetime_prefs_inactivity_su">鍛ㄦ棩</string>
+    <string name="zetime_title_alarm_signaling">璁剧疆璀︾ず淇″彿绫诲瀷</string>
+    <string name="zetime_signaling_none">闈欓粯</string>
+    <string name="zetime_signaling_vibrate">杩炵画鎸姩</string>
+    <string name="zetime_signaling_beep">杩炵画铚傞福</string>
+    <string name="zetime_signaling_vibrate_beep">杩炵画鎸姩鍜岃渹楦�</string>
+    <string name="zetime_signaling_vibrate_once">闇囧姩涓�娆�</string>
+    <string name="zetime_signaling_vibrate_twice">闇囧姩浜屾</string>
+    <string name="zetime_signaling_beep_once">铚傞福涓�娆�</string>
+    <string name="zetime_signaling_beep_twice">铚傞福浜屾</string>
+    <string name="zetime_signaling_vibrate_beep_once">闇囧姩銆佽渹楦e悇涓�娆�</string>
+    <string name="pref_screen_notification_profile_missed_call">鏈帴鏉ョ數鎻愰啋</string>
+    <string name="pref_screen_notification_profile_calendar">鏃ュ巻鎻愰啋</string>
+    <string name="pref_screen_notification_profile_inactivity">鏈繍鍔ㄦ彁閱�</string>
+    <string name="pref_screen_notification_profile_low_power">浣庣數閲忔彁閱�</string>
+    <string name="pref_screen_notification_profile_anti_loss">闃蹭涪澶辨彁閱�</string>
+    <string name="interval_fifteen_minutes">姣�15鍒嗛挓</string>
+    <string name="interval_forty_five_minutes">姣�45鍒嗛挓</string>
+    <string name="activity_prefs_calories_burnt">姣忔棩鐩爣锛氱儹閲忔秷鑰�</string>
+    <string name="activity_prefs_distance_meters">姣忔棩鐩爣锛氳窛绂伙紙绫筹級</string>
+    <string name="activity_prefs_activetime_minutes">姣忔棩鐩爣锛氳繍鍔ㄦ椂闀匡紙鍒嗛挓锛�</string>
+    <string name="devicetype_miscale2">灏忕背鎵嬬幆2</string>
+    <string name="pref_title_support_voip_calls">鍚敤 VoIP 搴旂敤鍛煎彨</string>
+    <string name="title_activity_device_specific_settings">璁惧鐗规畩璁剧疆</string>
+    <string name="pref_title_authkey">璁よ瘉瀵嗛挜</string>
+    <string name="pref_summary_authkey">灏嗘偍鎯宠杩炴帴鐨勬墍鏈� Android 璁惧涓婄殑璁よ瘉瀵嗛挜鏇存敼涓洪�氱敤鐨勫瘑閽ャ�傞粯璁ゆ墍鏈夎澶囩殑瀵嗛挜鏄� 0123456789@ABCDE</string>
+    <string name="devicetype_bfh16">BFH-16</string>
+    <string name="fw_upgrade_notice_amazfitcor2">鎮ㄥ嵆灏嗗湪璺冩垜鎵嬬幆2(Amazfit Cor 2)涓婂畨瑁� %s 鐗堟湰鐨勫浐浠� \n \n璇风‘淇濆厛瀹夎 .fw 鏂囦欢锛屽啀瀹夎 .res 鏂囦欢銆傛偍鐨勬墜鐜皢浼氬湪瀹夎浜� .fw 鏂囦欢鍚庨噸鍚� \n \n澶囨敞锛氬鏋滄偍瀹夎鐨� .res 鏂囦欢鍜屼箣鍓嶇浉鍚岋紝鍒欐棤闇�瀹夎 .res 鏂囦欢 \n \n椋庨櫓鑷媴锛� \n \n姝ゅ姛鑳芥湭缁忔祴璇曪紝濡傛灉鎮ㄧ殑璁惧鍚嶄负 Amazfit Band 2锛屾偍鍙兘闇�瑕佸埛鍏� BEATS_W 鍥轰欢\\</string>
+    <string name="dutch">鑽峰叞璇�</string>
+    <string name="turkish">鍦熻�冲叾璇�</string>
+    <string name="ukrainian">涔屽厠鍏拌</string>
+    <string name="arabic">闃挎媺浼</string>
+    <string name="indonesian">鍗板凹璇�</string>
+    <string name="thai">娉拌</string>
+    <string name="vietnamese">瓒婂崡璇�</string>
+    <string name="portuguese">钁¤悇鐗欒</string>
+    <string name="devicetype_amazfit_cor2">璺冩垜鎵嬬幆2</string>
+    <string name="devicetype_miband4">灏忕背鎵嬬幆4</string>
+    <string name="fw_upgrade_notice_miband4">鎮ㄥ嵆灏嗗湪鎮ㄧ殑鐨勫皬绫虫墜鐜�4涓婂畨瑁� %s 鍥轰欢銆�
+\n
+\n璇风‘淇濇偍鍏堝畨瑁� .fw 鏂囦欢锛屽啀瀹夎 .res 鏂囦欢銆傛偍鐨勬墜鐜湪瀹夎 .fw 鏂囦欢鍚庡皢浼氶噸鍚��
+\n
+\n澶囨敞锛氳嫢鎮ㄧ殑 .res 鏂囦欢鐗堟湰鍜屼箣鍓嶅畨瑁呯殑鐩稿悓锛屽垯娌℃湁蹇呰瀹夎 .res 鏂囦欢銆�
+\n
+\n椋庨櫓鑷媴锛�</string>
+    <string name="prefs_hr_alarm_activity">杩愬姩鏈熼棿鐨勫績鐜囪鎶�</string>
+    <string name="prefs_hr_alarm_low">涓嬮檺</string>
+    <string name="prefs_hr_alarm_high">涓婇檺</string>
+    <string name="average">骞冲潎锛�%1$s</string>
+    <string name="pref_header_charts">鍥捐〃璁剧疆</string>
+    <string name="pref_title_charts_average">鍦ㄥ浘琛ㄤ腑鏄剧ず骞冲潎鍊�</string>
+    <string name="pref_title_charts_range">鍥捐〃鑼冨洿</string>
+    <string name="pref_charts_range_on">鍥捐〃鑼冨洿宸茶缃负鎸夋湀</string>
+    <string name="pref_charts_range_off">鍥捐〃鑼冨洿宸茶缃负鎸夊懆</string>
+    <string name="weekstepschart_steps_a_month">姣忔湀姝ユ暟</string>
+    <string name="weeksleepchart_sleep_a_month">姣忔湀鐫$湢</string>
+    <string name="devicetype_mijia_lywsd02">绫冲鏅鸿兘闂归挓</string>
+    <string name="menuitem_nfc">NFC</string>
+    <string name="pref_summary_expose_hr">褰� Gadgetbridge 宸茶繛鎺ユ椂锛屽厑璁稿叾浠栧簲鐢ㄥ疄鏃惰闂績鐜囨暟鎹�</string>
+    <string name="pref_title_expose_hr">绗笁鏂瑰疄鏃跺績鐜囨暟鎹闂�</string>
+    <string name="pref_title_use_custom_font">浣跨敤鑷畾涔夊瓧浣�</string>
+    <string name="pref_summary_use_custom_font">濡傛灉鎮ㄧ殑璁惧浣跨敤鑷畾涔夌殑瀛椾綋鍥轰欢鎴栬〃鎯呯鍙锋敮鎸侊紝璇峰惎鐢ㄦ閫夐」</string>
+    <string name="activity_db_management_autoexport_explanation">鏁版嵁搴撹嚜鍔ㄥ鍑鸿矾寰勫凡缁忚璁剧疆涓猴細</string>
+    <string name="activity_db_management_autoexport_label">鑷姩瀵煎嚭</string>
+    <string name="activity_DB_ExportButton">瀵煎嚭鏁版嵁</string>
+    <string name="activity_DB_import_button">瀵煎叆鏁版嵁</string>
+    <string name="activity_DB_test_export_button">绔嬪埢杩愯鑷姩瀵煎嚭</string>
+    <string name="activity_DB_test_export_message">姝e湪瀵煎嚭鏁版嵁搴撯��</string>
+    <string name="activity_DB_delete_legacy_button">鍒犻櫎鏃ф暟鎹簱</string>
+    <string name="activity_DB_empty_button">娓呯┖鏁版嵁</string>
+    <string name="activity_db_management_empty_DB">娓呯┖鏁版嵁搴�</string>
+    <string name="activity_db_management_exportimport_label">瀵煎叆涓庡鍑�</string>
+    <string name="activity_db_management_empty_db_warning">璀﹀憡锛佸綋鎸変笅姝ゆ寜閽悗锛屽皢浼氭竻绌烘偍鐨勬暟鎹簱骞朵粠闆跺紑濮嬨��</string>
+    <string name="appwidget_sleep_alarm_widget_label">鐫$湢闂归挓</string>
+    <string name="widget_listing_label">鐘舵�佷笌闂归挓</string>
+    <string name="widget_set_alarm_after">澶氫箙鍚庨椆閾冿細</string>
+    <string name="widget_5_minutes">5 鍒嗛挓</string>
+    <string name="widget_10_minutes">10 鍒嗛挓</string>
+    <string name="widget_20_minutes">20 鍒嗛挓</string>
+    <string name="widget_1_hour">1 灏忔椂</string>
+    <string name="pref_display_add_device_fab">杩炴帴鏂拌澶囨寜閽�</string>
+    <string name="pref_display_add_device_fab_on">鎬绘槸鍙</string>
+    <string name="pref_display_add_device_fab_off">浠呭綋娌℃湁璁惧娣诲姞鏃跺彲瑙�</string>
+    <plurals name="widget_alarm_target_hours">
+        <item quantity="other">%d 灏忔椂</item>
+    </plurals>
+    <string name="activity_error_no_app_for_gpx">鑻ラ渶瑕佹煡鐪嬫椿鍔ㄨ建杩癸紝璇峰畨瑁呬竴涓兘鏌ョ湅 GPX 鏂囦欢鐨勫簲鐢ㄣ��</string>
+    <string name="preferences_makibes_hr3_settings">Makibes HR3 璁剧疆</string>
+    <string name="devicetype_makibes_hr3">Makibes HR3</string>
+    <string name="devicetype_amazfit_bip_lite">璺冩垜鎵嬭〃闈掓槬鐗� Lite</string>
+    <string name="prefs_find_phone">鏌ユ壘鎵嬫満</string>
+    <string name="prefs_enable_find_phone">鍚敤鈥滄煡鎵炬墜鏈衡��</string>
+    <string name="prefs_find_phone_summary">浣跨敤鎮ㄧ殑鎵嬬幆浠ュ湪鎵嬫満涓婃挱鏀鹃搩澹般��</string>
+    <string name="prefs_find_phone_duration">閾冨0灏嗘寔缁暟绉�</string>
+    <string name="maximum_duration">鎸佺画</string>
+    <string name="discovery_need_to_enter_authkey">姝よ澶囬渶瑕佽璇佸瘑閽ワ紝璇峰湪璁惧涓婇暱鎸変互杈撳叆瀵嗛挜銆傚叿浣撹闃呰 Wiki 銆�</string>
+    <string name="fw_upgrade_notice_amazfitbip_lite">鎮ㄥ嵆灏嗗湪璺冩垜鎵嬭〃闈掓槬鐗� Lite(Amazfit Bip Lite)涓婂畨瑁� %s 鐗堟湰鐨勫浐浠�
+\n
+\n璇风‘淇濆厛瀹夎 .fw 鏂囦欢锛屽啀瀹夎 .res 鏂囦欢銆傛偍鐨勬墜鐜皢浼氬湪瀹夎浜� .fw 鏂囦欢鍚庨噸鍚�
+\n
+\n澶囨敞锛氬鏋滄偍瀹夎鐨� .res 鏂囦欢鍜屼箣鍓嶇浉鍚岋紝鍒欐棤闇�瀹夎 .res 鏂囦欢
+\n
+\n椋庨櫓鑷媴锛�</string>
+    <string name="devicetype_amazfit_gtr">璺冩垜 GTR</string>
+    <string name="fw_upgrade_notice_amazfitgtr">鍗冲皢鍦ㄦ偍鐨勮穬鎴� GTR (Amazfit GTR) 涓婂畨瑁呭浐浠� %s 銆�
+\n
+\n璇风‘淇濆厛瀹夎 .fw 鏂囦欢锛岀劧鍚庡啀瀹夎 .res 鏂囦欢锛屾渶鍚庡啀瀹夎 .gps 鏂囦欢銆傛偍鐨勬墜琛ㄥ皢浼氬畨瑁呬簡 .fw 鏂囦欢鍚庨噸鍚��
+\n
+\n娉ㄦ剰锛氬鏋� .res 鍜� .gps 鏂囦欢鍜屼笂涓�涓増鏈浉鍚岋紝鎮ㄥ垯鏃犻渶瀹夎銆�
+\n
+\n椋庨櫓鑷媴锛�</string>
+    <string name="pref_chart_heartrate_color_red">绾�</string>
+    <string name="pref_chart_heartrate_color_orange">姗�</string>
+    <string name="pref_title_chart_heartrate_color">蹇冪巼棰滆壊</string>
+    <string name="pref_title_chart_sleep_rolling_24_hour">鐫$湢鑼冨洿</string>
+    <string name="pref_chart_sleep_rolling_24_on">杩囧幓 24 灏忔椂</string>
+    <string name="pref_chart_sleep_rolling_24_off">涓崍鍒颁腑鍗�</string>
+    <string name="devicetype_amazfit_gts">璺冩垜 GTS</string>
+    <string name="fw_upgrade_notice_amazfitgts">鎮ㄥ嵆灏嗗湪璺冩垜鎵嬭〃 GTS(Amazfit GTS)涓婂畨瑁� %s 鐗堟湰鐨勫浐浠�
+\n
+\n璇风‘淇濆厛瀹夎 .fw 鏂囦欢锛屽啀瀹夎 .res 鏂囦欢銆傛偍鐨勬墜鐜皢浼氬湪瀹夎浜� .fw 鏂囦欢鍚庨噸鍚��
+\n
+\n澶囨敞锛氬鏋滄偍瀹夎鐨� .res 鏂囦欢鍜屼箣鍓嶇浉鍚岋紝鍒欐棤闇�瀹夎 .res 鏂囦欢銆�
+\n
+\n椋庨櫓鑷媴锛�</string>
+    <string name="mi2_prefs_button_press_broadcast_default_value" translatable="false">nodomain.freeyourgadget.gadgetbridge.ButtonPressed</string>
+    <string name="devicetype_qhybrid">Fossil Q Hybrid</string>
+    <string name="preferences_qhybrid_settings">Q Hybrid 璁剧疆</string>
+    <string name="watch_not_connected">鎵嬭〃鏈繛鎺�</string>
+    <string name="qhybrid_vibration_strength">鎸姩寮哄害锛�</string>
+    <string name="qhybrid_goal_in_steps">鐩爣姝ユ暟</string>
+    <string name="qhybrid_time_shift">鏃剁Щ</string>
+    <string name="qhybrid_second_timezone_offset_relative_to_utc">鐩稿浜庡崗璋冧笘鐣屾椂鐨勭浜屼釜鏃跺尯鍋忕Щ</string>
+    <string name="qhybrid_overwrite_buttons">瑕嗙洊鎸夐挳</string>
+    <string name="qhybrid_use_activity_hand_as_notification_counter">浣跨敤娲诲姩鎵嬩綔涓洪�氱煡璁℃暟</string>
+    <string name="qhybrid_prompt_million_steps">璇峰皢姝ユ暟璁剧疆涓�100涓囨潵婵�娲诲畠銆�</string>
+    <string name="qhybrid_buttons_overwrite_success">鎸夐挳瑕嗙洊</string>
+    <string name="qhybrid_buttons_overwrite_error">瑕嗙洊鎸夐挳鏃跺嚭閿�</string>
+    <string name="qhybrid_offset_timezone">鍩轰簬鏃跺尯鍋忕Щ</string>
+    <string name="qhybrid_changes_delay_prompt">淇敼鍙兘闇�瑕佸嚑绉掆��</string>
+    <string name="qhybrid_offset_time_by">缁忕敱鏃堕棿鍋忕Щ</string>
+    <string name="pref_disable_new_ble_scanning">绂佺敤鏂颁綆鍔熺巼钃濈墮鎵弿</string>
+    <string name="pref_summary_disable_new_ble_scanning">鑻ユ偍鐨勮澶囨棤娉曞湪鎵弿杩囩▼涓彂鐜帮紝璇烽�変腑姝ら�夐」</string>
+    <string name="devicetype_banglejs">Bangle.js</string>
+    <string name="devicetype_y5">Y5</string>
+    <string name="prefs_button_single_press_action_selection_title">浜嬩欢 1 鎿嶄綔</string>
+    <string name="prefs_button_double_press_action_selection_title">浜嬩欢 2 鎿嶄綔</string>
+    <string name="prefs_button_triple_press_action_selection_title">浜嬩欢 3 鎿嶄綔</string>
+    <string name="prefs_button_variable_actions">鎸変笅鎸夐挳鏃剁殑璇︾粏璁剧疆</string>
+    <string name="prefs_button_long_press_action_selection_title">闀挎寜鎸夐挳鐨勫姩浣�</string>
+    <string name="alarm_snooze">鎵撶浌</string>
+    <string name="error_no_location_access">涓虹‘淇濆伐浣滄甯稿繀椤绘巿鏉冨湴鐞嗕綅缃闂拰鎵弿</string>
+    <string name="devicetype_itag">iTag</string>
+    <string name="pref_title_allow_high_mtu">鍏佽楂� MTU</string>
+    <string name="pref_summary_allow_high_mtu">澧炲姞浼犺緭閫熷害锛屼絾鏄湪鏌愪簺 Android 璁惧涓婂彲鑳戒笉浼氬伐浣溿��</string>
+    <string name="pref_summary_sync_calendar">鍗充娇宸茬粡鏂紑杩炴帴锛屼篃鍚敤鏃ュ巻鎻愰啋</string>
+    <string name="pref_title_sync_caldendar">鍚屾鏃ュ巻浜嬩欢</string>
+    <string name="hr_widget_heart_rate">蹇冪巼</string>
+    <string name="hr_widget_steps">姝ユ暟</string>
+    <string name="hr_widget_date">鏃ユ湡</string>
+    <string name="hr_widget_active_minutes">娲诲姩鍒嗛挓</string>
+    <string name="hr_widget_calories">鍗¤矾閲�</string>
+    <string name="hr_widget_battery">鐢垫睜</string>
+    <string name="hr_widget_weather">澶╂皵</string>
+    <string name="hr_widget_nothing">鏃�</string>
+    <string name="find_lost_device_you_found_it">鎵惧埌瀹冧簡锛�</string>
+    <string name="pref_title_force_white_color_scheme">褰撳浜庣櫧鑹叉柟妗堟椂濮嬬粓浣跨敤榛戣壊</string>
+    <string name="pref_summary_force_white_color_scheme">濡傛灉鎮ㄥ弻鎵嬮鑹茶緝娣憋紝灏嗕細鏈夊府鍔�</string>
+    <string name="find_my_phone_notification">鏌ユ壘鎴戠殑鎵嬫満</string>
+    <string name="notification_channel_high_priority_name">楂樹紭鍏堢骇</string>
+    <string name="pref_title_custom_deviceicon">鏄剧ず璁惧鐗瑰畾鐨勯�氱煡鍥炬爣</string>
+    <string name="pref_summary_custom_deviceicon">鏄剧ず璁惧鎸囧畾鐨� Android 閫氱煡鍥炬爣浠ユ浛浠e凡杩炴帴鏃剁殑 Gadgetbridge 鍥炬爣</string>
+    <string name="hr_appname_wellness">鍋ュ悍</string>
+    <string name="hr_appname_workout">鍋ヨ韩</string>
+    <string name="hr_appname_stopwatch">绉掕〃</string>
+    <string name="hr_appname_commute">閫氬嫟</string>
+    <string name="pref_title_vibration_strength">鎸姩寮哄害</string>
+    <string name="devicetype_amazfit_bips">璺冩垜鎵嬭〃闈掓槬鐗� S</string>
+    <string name="pref_title_relax_firmware_checks">鑻ユ偍甯屾湜鍒峰叆骞堕潪涓烘偍璁惧璁捐鐨勫浐浠讹紝璇烽�変腑姝ら」锛堥闄╄嚜鎷咃級</string>
+    <string name="pref_summary_relax_firmware_checks">鏀惧鍥轰欢妫�鏌�</string>
+    <string name="pref_header_auto_fetch">鑷姩鑾峰彇</string>
+    <string name="pref_qhybrid_title_widget_draw_circles">缁樺埗灏忛儴浠跺渾鍦�</string>
+    <string name="pref_qhybrid_save_raw_activity_files">淇濆瓨鍘熷(RAW)娲诲姩鏂囦欢</string>
+    <string name="menuitem_workout">閿荤偧</string>
+    <string name="menuitem_eventreminder">浜嬩欢鎻愰啋</string>
+    <string name="menuitem_hr">蹇冪巼</string>
+    <string name="menuitem_pai">PAI</string>
+    <string name="pref_title_device_spec_settings_force_time">寮哄埗鏃堕棿鍚屾</string>
+    <string name="pref_header_device_spec_settings">璁惧璁剧疆</string>
+    <string name="pref_summary_notifications_and_calls_title_shake_reject">閲嶅鎵嬭〃鎸夐挳鎿嶄綔</string>
+    <string name="prefs_notifications_and_calls_shake_reject">鏅冨姩鎵嬭厱浠ュ拷鐣�/鎷掓帴鏉ョ數</string>
+    <string name="pref_summary_notifications_and_calls_title_reject">鍏� - 蹇界暐锛屽紑 - 鎷掓帴</string>
+    <string name="prefs_notifications_and_calls_reject">鎸夐挳蹇界暐鎴栨嫆鎺ユ潵鐢�</string>
+    <string name="pref_header_notifications_and_calls_callhandling">鏉ョ數澶勭悊</string>
+    <string name="pref_title_notifications_and_calls_repeat_on_misscall">X 鍒嗛挓閲嶅涓�娆�</string>
+    <string name="pref_summary_notifications_and_calls_enable_misscall">姣忓垎閽熼噸澶�</string>
+    <string name="pref_notifications_and_calls_enable_misscall">閫氱煡鏈帴鏉ョ數</string>
+    <string name="prefs_notifications_and_calls_continious_ring">鍝嶉搩鏃堕�氱煡</string>
+    <string name="pref_title_notifications_and_calls_repeat_on_call">閲嶅鏉ョ數閫氱煡</string>
+    <string name="pref_header_notifications_and_calls">閫氱煡鍜屾潵鐢�</string>
+    <string name="title_activity_LenovoWatch_calibration">WatchXPlus 棣栭�夐」</string>
+    <string name="devicetype_watchxplus">Watch X Plus</string>
+    <string name="devicetype_watchx">Watch X</string>
+    <string name="power_mode_watch">浠呮墜琛�</string>
+    <string name="power_mode_saving">鑺傝兘</string>
+    <string name="power_mode_normal">鏅��</string>
+    <string name="power_mode_title">鎵嬭〃鐢垫簮妯″紡</string>
+    <string name="prefs_sensors_button_bp_calibration_sum">鐐瑰嚮姝ゅ浠ュ紑濮嬫牎姝�</string>
+    <string name="prefs_sensors_button_bp_calibration">鏍℃</string>
+    <string name="pref_sensors_bp_calibration_high">鏀剁缉鍘嬶紙楂樺帇锛�</string>
+    <string name="pref_sensors_bp_calibration_low">鑸掑紶鍘嬶紙浣庡帇锛�</string>
+    <string name="pref_sensors_bp_calibration">琛�鍘嬫牎姝�</string>
+    <string name="pref_title_sensors_altitude">楂樺害鏍℃</string>
+    <string name="pref_header_sensors_calibration">浼犳劅鍣ㄦ牎姝�</string>
+    <string name="pref_title_device_spec_settings_show_raw_graph">鍦ㄦ椿鍔ㄥ浘琛ㄤ腑鏄剧ず鍘熷(RAW)鏁版嵁</string>
+    <string name="pref_summary_device_spec_settings_title_force_time">褰撻噸鏂拌繛鎺ユ椂寮哄埗鑷姩鍚屾鏃堕棿銆傛寚閽堣〃鐩樺彲鑳戒細鏄剧ず閿欒鐨勬椂闂达紒</string>
+    <string name="czesh">鎹峰厠璇�</string>
+    <string name="swedish">鐟炲吀璇�</string>
+    <string name="hebrew">甯屼集鏉ヨ</string>
+    <string name="greek">甯岃厞璇�</string>
+    <string name="hungarian">鍖堢墮鍒╄</string>
+    <string name="romanian">缃楅┈灏间簹璇�</string>
+    <string name="pref_theme_system">绯荤粺</string>
+    <string name="menuitem_unknown">鏈煡</string>
+    <string name="bip_prefs_shotcuts_summary">閫夋嫨鎵嬬幆涓婃樉绀虹殑蹇嵎鏂瑰紡</string>
+    <string name="bip_prefs_shortcuts">蹇嵎鏂瑰紡</string>
+    <string name="hr_widget_last_notification">鏈�杩戦�氱煡</string>
+    <string name="controlcenter_set_alias">璁剧疆鍒О</string>
+    <string name="fw_upgrade_notice_amazfit_trex">鎮ㄥ嵆灏嗗啀鎮ㄧ殑璺冩垜 T-Rex 涓婂畨瑁� %s 鍥轰欢銆�
+\n
+\n璇风‘淇濆厛瀹夎 .fw 鏂囦欢锛屽啀瀹夎 .res 鏂囦欢锛屾渶鍚庡畨瑁� .gps 鏂囦欢銆傛偍鐨勬墜琛ㄥ皢浼氬啀瀹夎浜� .fw 鏂囦欢鍚庨噸鍚��
+\n
+\n澶囨敞锛氬鏋� .res 鍜� .gps 鐗堟湰涓庝箣鍓嶇殑鐗堟湰鐩稿悓锛屾偍灏辨棤闇�瀹夎杩欎袱涓枃浠躲��
+\n
+\n椋庨櫓鑷媴锛�</string>
+    <string name="devicetype_amazfit_trex">璺冩垜 T-Rex</string>
+    <string name="about_additional_contributions">鎰熻阿鎵�鏈夋湭鍒楀嚭鐨勮础鐚�咃紝浠栦滑璐$尞浜嗕唬鐮併�佺炕璇戙�佹敮鎸併�佹兂娉曘�佸姩鍔涖�侀敊璇姤鍛婂拰閲戦挶鈥� 鉁�</string>
+    <string name="about_additional_device_support">棰濆璁惧鏀寔</string>
+    <string name="about_contributors">璐$尞鑰�</string>
+    <string name="about_core_team_title">鏍稿績鎴愬憳 锛堜互绗竴娆℃彁浜や唬鐮佹帓搴忥級</string>
+    <string name="about_description_generic">鐢ㄤ簬鏇夸唬渚涘簲鍟嗛棴婧愮┛鎴磋澶� Android 搴旂敤绋嬪簭鐨勮劚鏈恒�佹棤鐗堟潈銆佽嚜鐢辩殑鏂规銆�</string>
+    <string name="about_activity_title_generic">鍏充簬 Gadgetbridge</string>
+    <string name="about_title">鍏充簬</string>
+    <string name="pref_title_weather_summary">鐢盠ineageOS澶╂皵鏈嶅姟鎻愪緵锛屽叾浠栫増鏈殑Android绯荤粺闇�瑕佷娇鐢ㄨ濡� Weather notification 銆傛洿澶氫俊鎭璁块棶 Gadgetbridge 鐨刉iki銆�</string>
+    <string name="menuitem_worldclock">涓栫晫鏃堕挓</string>
+    <string name="permission_granting_mandatory">鎵�鏈夋潈闄愰兘鏄繀椤荤殑锛岃嫢鏈巿浜堣繖浜涙潈闄愶紝鍙兘浼氬鑷翠笉绋冲畾</string>
+    <string name="error_background_service_reason">鐢变簬鎰忓閿欒锛屽惎鍔ㄥ悗鍙版湇鍔″け璐ャ�� - 鐐瑰嚮姝ゅ鏌ョ湅鏇村璇︽儏銆俓n\n閿欒锛�</string>
+    <string name="error_background_service_reason_truncated">鏃犳硶鍚姩鍚庡彴鏈嶅姟鏄敱浜庘��</string>
+    <string name="error_background_service">鍚姩鍚庡彴鏈嶅姟澶辫触</string>
+    <string name="error_version_check_extreme_caution">娉ㄦ剰锛氭鏌ョ増鏈俊鎭椂鍙戠敓閿欒锛佹偍涓嶅簲璇ョ户缁笅鍘伙紝宸茬煡鐗堟湰鍚� \\\"%1$s\\\"</string>
+    <string name="menuitem_stress">鍘嬪姏</string>
+    <string name="menuitem_cycles">鍛ㄦ湡璺熻釜</string>
+    <string name="menuitem_breathing">鍛煎惛</string>
+    <string name="devicetype_miband5">灏忕背鎵嬬幆5</string>
+    <string name="fw_upgrade_notice_miband5">鎮ㄥ嵆灏嗗湪鎮ㄧ殑 灏忕背鎵嬬幆5 涓婂畨瑁� %s 鍥轰欢銆�
+\n
+\n璇风‘淇濆厛瀹夎 .fw 鏂囦欢锛屽啀瀹夎 .res 鏂囦欢銆傛偍鐨勬墜鐜皢浼氬啀瀹夎浜� .fw 鏂囦欢鍚庤嚜鍔ㄩ噸鍚��
+\n
+\n娉ㄦ剰锛氳嫢 .res 鏂囦欢娌℃湁鏇存柊锛屽垯鏃犻渶瀹夎銆�
+\n
+\n椋庨櫓鑷媴锛�</string>
+    <string name="devicetype_tlw64">TLW64</string>
+    <string name="companiondevice_pairing_details">鍚敤鏂扮殑 CompanionDevice API 鏀寔锛堜粎闄� Android 8 鎴栦互涓婏紒锛夛紝杩欏皢浼氬湪鐞嗚涓婃彁鍗囧彲闈犳�э紝鑻ラ渶瑕侊紝鍚庡彴鏈嶅姟鍣ㄥ皢浼氳嚜鍔ㄩ噸鍚紝涓旈渶瑕侀噸鏂伴厤瀵规墠鑳界敓鏁�</string>
+    <string name="error_location_enabled_mandatory">蹇呴』鎵撳紑瀹氫綅鎵嶈兘鎵弿璁惧</string>
+    <string name="ignore_bonded_devices_description">鍚敤姝ら�夐」灏嗕細鍦ㄦ壂鎻忔椂蹇界暐宸茬粦瀹氭垨宸查厤瀵圭殑璁惧</string>
+    <string name="ignore_bonded_devices">蹇界暐宸茬粦瀹氱殑璁惧</string>
+    <string name="error_retrieving_devices_database">浠庢暟鎹簱涓幏鍙栬澶囧嚭閿�</string>
+    <string name="error_setting_alias">閿欒璁剧疆鍒悕锛�</string>
+    <string name="error_exporting_device_preferences">瀵煎嚭璁惧鐗瑰畾棣栭�夐」鏃堕亣鍒伴敊璇�</string>
+    <string name="pref_check_permission_status_summary">妫�鏌ュ苟瑕佹眰灏氭湭鑾峰緱鐨勬潈闄愬嵆浣垮苟闈炵珛鍒婚渶瑕併�傝嫢鎮ㄧ殑璁惧鐨勭‘涓嶆敮鎸佹绫诲姛鑳斤紝涔熷彲绂佺敤銆備笉杩涜鎺堟潈鍙兘浼氬鑷撮棶棰橈紒</string>
+    <string name="pref_check_permission_status">妫�鏌ユ潈闄愮姸鎬�</string>
+    <string name="device_requires_key">闇�瑕佸瘑閽ワ紝闀挎寜鍙繘鍏�</string>
+    <string name="device_is_currently_bonded">宸茬粡缁戝畾杩囦簡</string>
+    <string name="companiondevice_pairing">CompanionDevice 閰嶅</string>
+    <string name="require_location_provider">蹇呴』鍚姩瀹氫綅</string>
+    <string name="devicetype_pinetime_jf">PineTime 锛圝F 鍥轰欢锛�</string>
+    <string name="calories_unit">鍗冨崱</string>
+    <string name="ascentSeconds">涓婂崌</string>
+    <string name="descentSeconds">涓嬮檷</string>
+    <string name="activity_detail_show_gps_label">鏄剧ず GPS 璺熻釜</string>
+    <string name="activity_detail_duration_label">鏃堕暱</string>
+    <string name="activity_detail_end_label">缁撴潫</string>
+    <string name="activity_detail_start_label">寮�濮�</string>
+    <string name="Steps">姝ユ暟</string>
+    <string name="Activity">娲诲姩</string>
+    <string name="Speed">閫熷害</string>
+    <string name="Elevation">娴锋嫈</string>
+    <string name="Distance">璺濈</string>
+    <string name="Swimming">娓告吵</string>
+    <string name="Strokes">琛岀▼</string>
+    <string name="bpm">bpm</string>
+    <string name="minutes_km">鍒嗛挓/鍗冪背</string>
+    <string name="seconds_km">绉�/鍗冪背</string>
+    <string name="laps_unit">鍦�</string>
+    <string name="swim_style">娉冲紡</string>
+    <string name="swolf_index">SWOLF 鎸囨暟</string>
+    <string name="seconds">绉�</string>
+    <string name="strokes_unit">STR</string>
+    <string name="strokes_second">STR/绉�</string>
+    <string name="km_h">鍗冪背/灏忔椂</string>
+    <string name="meters_second">绫�/绉�</string>
+    <string name="steps_unit">姝�</string>
+    <string name="cm">鍘樼背</string>
+    <string name="meters">绫�</string>
+    <string name="flatSeconds">骞�</string>
+    <string name="laps">鍦�</string>
+    <string name="swimStyle">娉冲紡</string>
+    <string name="swolfIndex">SWOLF</string>
+    <string name="averageLapPace">骞冲潎姣忓湀姝ヤ紣</string>
+    <string name="averageStrokesPerSecond">骞冲潎姝ヤ紣</string>
+    <string name="averageStrokeDistance">骞冲潎琛岀▼璺濈</string>
+    <string name="averageStride">骞冲潎姝ュ箙</string>
+    <string name="averageKMPaceSeconds">姝ヤ紣</string>
+    <string name="averageHR">蹇冪巼</string>
+    <string name="totalStride">鎬绘浼�</string>
+    <string name="maxPace">鏈�蹇浼�</string>
+    <string name="minPace">鏈�鎱㈡浼�</string>
+    <string name="maxSpeed">鏈�澶�</string>
+    <string name="caloriesBurnt">鐑噺</string>
+    <string name="activeSeconds">娲诲姩</string>
+    <string name="steps">姝ユ暟</string>
+    <string name="minAltitude">鏈�灏�</string>
+    <string name="maxAltitude">鏈�澶�</string>
+    <string name="descentMeters">涓嬪潯</string>
+    <string name="ascentMeters">鐧婚珮</string>
+    <string name="distanceMeters">璺濈</string>
+    <string name="activity_summary_detail">杩愬姩娲诲姩璇︽儏</string>
+    <string name="seconds_m">绉�/绫�</string>
+    <string name="prefs_events_forwarding_action_title">杩愯鍔ㄤ綔</string>
+    <string name="prefs_events_forwarding_broadcast_title">骞挎挱淇℃伅</string>
+    <string name="prefs_events_forwarding_startnonwear">鏈┛鎴存椂</string>
+    <string name="prefs_events_forwarding_wokeup">閱掓潵鏃�</string>
+    <string name="prefs_events_forwarding_fellsleep">鐫¤鏃�</string>
+    <string name="prefs_events_forwarding_title">璁惧鎿嶄綔</string>
+    <string name="prefs_events_forwarding_summary">浣跨敤璁惧浜嬩欢鏉ヨЕ鍙戞搷浣滃拰Android骞挎挱</string>
+    <string name="activity_type_yoga">鐟滀冀</string>
+    <string name="activity_type_jump_roping">璺崇怀</string>
+    <string name="activity_type_elliptical_trainer">妞渾鏈�</string>
+    <string name="activity_type_indoor_cycling">瀹ゅ唴鐜父</string>
+    <string name="activity_type_swimming_openwater">娓告吵锛堝紑鏀炬按鍩燂級</string>
+    <string name="activity_filter_apply_filter">搴旂敤杩囨护鍣�</string>
+    <string name="activity_filter_filter_title">杩囨护鍣�</string>
+    <string name="activity_filter_reset_filter">閲嶇疆杩囨护鍣�</string>
+    <string name="activity_filter_date_to">姝�</string>
+    <string name="activity_filter_date_from">鑷�</string>
+    <string name="pref_header_filter">杩愬姩娲诲姩杩囨护鍣�</string>
+    <string name="activity_summaries_statistics">缁熻</string>
+    <string name="km">鍗冪背</string>
+    <string name="medley">娣峰悎娉�</string>
+    <string name="backstroke">浠版吵</string>
+    <string name="freestyle">鑷敱娉�</string>
+    <string name="breaststroke">铔欐吵</string>
+    <string name="pref_header_statistics">浣撹偛娲诲姩缁熻</string>
+    <string name="activity_filter_name_contains">鏍囩</string>
+    <string name="activity_summary_edit_name_title">缂栬緫鏍囩</string>
+    <string name="activity_summaries_all_activities">鎵�鏈夋椿鍔�</string>
+    <string name="devicetype_sg2">Lemfo SG2</string>
+    <string name="activity_summary_yesterday">鏄ㄥぉ</string>
+    <string name="activity_summary_today">浠婂ぉ</string>
+    <string name="activity_summaries_all_devices">鎵�鏈夎澶�</string>
+    <string name="sports_activity_quick_filter_select">鏃舵湡</string>
+    <string name="sports_activity_quick_filter_7days">7澶�</string>
+    <string name="sports_activity_quick_filter_30days">30澶�</string>
+    <string name="sports_activity_quick_filter_last_month">涓婃湀</string>
+    <string name="sports_activity_quick_filter_this_month">鏈湀</string>
+    <string name="sports_activity_quick_filter_last_week">涓婂懆</string>
+    <string name="sports_activity_quick_filter_this_week">鏈懆</string>
+    <string name="no">鍚�</string>
+    <string name="yes">鏄�</string>
+    <string name="activity_filter_individual_items">鍒嗗埆閫変腑椤�</string>
+    <string name="addto_filter">娣诲姞鍒拌繃婊ゅ櫒</string>
+    <string name="averageSpeed">骞冲潎閫熷害</string>
+    <string name="baseAltitude">娴锋嫈鍩虹嚎</string>
+    <string name="activity_type_badminton">缇芥瘺鐞�</string>
+    <string name="activity_type_pingpong">涔掍箵</string>
+    <string name="activity_type_basketball">绡悆</string>
+    <string name="activity_type_cricket">鏉跨悆</string>
+    <string name="activity_type_rowing_machine">鍒掕埞鏈�</string>
+    <string name="activity_type_soccer">瓒崇悆</string>
+    <string name="activity_filter_to_placeholder">浠婂ぉ</string>
+    <string name="activity_filter_from_placeholder">閬ヨ繙鐨勮繃鍘�</string>
+    <string name="about_links">杩炴帴</string>
+    <string name="activity_prefs_chart_min_session_length">鏈�灏忔椿鍔ㄩ暱搴︼紙鍒嗛挓锛�</string>
+    <string name="activity_prefs_chart_max_idle_phase_length">浠ユ殏鍋滈暱搴︽潵鍒嗗壊娲诲姩锛堝垎閽燂級</string>
+    <string name="activity_prefs_chart_min_steps_per_minute">姣忓垎閽熸娴嬪埌鐨勬渶灏忔椿鍔ㄦ鏁�</string>
+    <string name="activity_prefs_chart_min_steps_per_minute_for_run">姣忓垎閽熸娴嬭窇姝ョ殑鏈�灏忔鏁�</string>
+    <string name="charts_activity_list">娲诲姩鍒楄〃</string>
+    <string name="chart_get_active_and_synchronize">鍋氫竴浜涜繍鍔ㄦ潵鍚屾璁惧銆�</string>
+    <string name="chart_no_active_data">鏈娴嬪埌娲诲姩銆�</string>
+    <string name="devicetype_lefun">Lefun</string>
+    <string name="lefun_prefs_interface_language_title">鐣岄潰璇█</string>
+    <string name="lefun_prefs_antilost_summary">濡傛灉鎮ㄧ殑鎵嬫満鏂紑涓庢墜鐜殑杩炴帴锛屽皢浼氶渿鍔�</string>
+    <string name="lefun_prefs_antilost_title">闃蹭涪澶�</string>
+    <string name="lefun_prefs_hydration_reminder_interval_title">姘村悎鎻愰啋闂撮殧锛堝垎閽燂級</string>
+    <string name="lefun_prefs_hydration_reminder_summary">褰撻渶瑕佹彁閱掑枬姘存椂锛屾墜鐜細闇囧姩</string>
+    <string name="lefun_prefs_hydration_reminder_title">姘村悎鎻愰啋</string>
+    <string name="devicetype_sonyswr12">Sony SWR12</string>
+    <string name="sonyswr12_settings_title">Sony SWR12 璁剧疆</string>
+    <string name="sonyswr12_settings_low_vibration">浣庢尟鍔ㄥ惎鐢�</string>
+    <string name="sonyswr12_settings_stamina">鐪佺數妯″紡宸插紑鍚�</string>
+    <string name="sonyswr12_settings_alarm_interval">鏅鸿兘璀︽姤闂撮殧锛堜互鍒嗛挓涓哄崟浣嶏級</string>
+    <string name="sonyswr12_settings_low_vibration_summary">闄嶄綆棰戝甫涓婄殑鎸姩寮哄害</string>
+    <string name="sonyswr12_settings_stamina_summary">鐪佺數妯″紡浼氬叧闂畾鏈熻嚜鍔ㄦ祴閲忓績鐜囷紝浠庤�屽鍔犲伐浣滄椂闂�</string>
+    <string name="sonyswr12_settings_alarm_interval_summary">鏅鸿兘璀︽姤闂撮殧鏄畨瑁呰鎶ヤ箣鍓嶇殑闂撮殧銆� 鍦ㄦ闂撮殧鍐咃紝璁惧灏濊瘯妫�娴嬫渶杞荤殑鐫$湢闃舵浠ュ敜閱掔敤鎴�</string>
+    <string name="devicetype_nut_mini">Nut mini</string>
+    <string name="activity_error_no_app_for_png">鑻ラ渶瑕佸垎浜鎴浘锛岃瀹夎涓�涓浘鐗囨煡鐪嬭蒋浠躲��</string>
+    <string name="pref_title_lower_button_function_double">搴曢儴鎸夐挳鍙屽嚮</string>
+    <string name="pref_title_middle_button_function_double">涓棿鎸夐挳鍙屽嚮</string>
+    <string name="pref_title_upper_button_function_double">涓婇儴鎸夐挳鍙屽嚮</string>
+    <string name="pref_title_lower_button_function_long">搴曢儴鎸夐挳闀挎寜</string>
+    <string name="pref_title_middle_button_function_long">涓棿鎸夐挳闀挎寜</string>
+    <string name="pref_title_upper_button_function_long">涓婇儴鎸夐挳闀挎寜</string>
+    <string name="pref_title_lower_button_function_short">鐭寜搴曢儴鎸夐挳</string>
+    <string name="pref_title_middle_button_function_short">鐭寜涓棿鎸夐挳</string>
+    <string name="pref_title_upper_button_function_short">鐭寜涓婃寜閽�</string>
+    <string name="firmware_update_progress">涓婁紶姝e湪澶勭悊涓��
+\n%1d%% 鐨� %.2fkbps (骞冲潎 %.2fkbps)
+\n鍒嗘 %1d 鐨� %1d</string>
+    <string name="devicestatus_upload_failed">涓婁紶宸插け璐�</string>
+    <string name="devicestatus_upload_aborted">涓婁紶宸叉斁寮冿紒</string>
+    <string name="devicestatus_upload_validating">涓婁紶姝e湪楠岃瘉涓�</string>
+    <string name="devicestatus_upload_completed">涓婁紶宸插畬鎴�</string>
+    <string name="devicestatus_disconnected">璁惧宸叉柇寮�杩炴帴锛�</string>
+    <string name="devicestatus_disconnecting">璁惧宸茬粡鏂紑杩炴帴锛�</string>
+    <string name="devicestatus_upload_started">涓婁紶宸插惎鍔�</string>
+    <string name="devicestatus_upload_starting">涓婁紶鍚姩涓�</string>
+    <string name="devicestatus_connected">璁惧宸茶繛鎺�</string>
+    <string name="devicestatus_connecting">璁惧姝e湪杩炴帴涓�</string>
+    <string name="activity_prefs_step_length_cm">姝ヤ紣鍘樼背闀垮害</string>
+    <string name="devicetype_amazfit_band5">璺冩垜鎵嬬幆 5</string>
+    <string name="menuitem_spo2">琛�姘чケ鍜屽害</string>
+    <string name="qhybrid_calibration_align_hint">浣跨敤涓嬮潰鐨勬寜閽紝灏嗘墜琛ㄦ寚閽堝鍑�12鐐广��</string>
+    <string name="gps_track">GPS 璺熻釜</string>
+    <string name="about_version">鐗堟湰 %s</string>
+    <string name="devicetype_amazfit_bips_lite">璺冩垜 Bip S Lite</string>
+    <string name="charts_legend_heartrate_average">骞冲潎蹇冪巼</string>
+    <string name="charts_min_max_heartrate_popup">鏈�浣庡績鐜囷細%1$d
+\n鏈�楂樺績鐜囷細%2$d
+\n绉诲姩寮哄害锛�%3$s</string>
+    <string name="find_lost_device_message">鎼滅储 %1$s 锛�</string>
+    <string name="devicetype_amazfit_gtr2">璺冩垜 GTR 2</string>
+    <string name="menuitem_stopwatch">绉掕〃</string>
+    <string name="menuitem_dnd">鍕挎壈</string>
+    <string name="menuitem_alexa">Alexa</string>
+    <string name="menuitem_takephoto">鐩告満閬ユ帶</string>
+    <string name="menuitem_mutephone">闈欓煶鎵嬫満</string>
+    <string name="menuitem_findphone">鏌ユ壘鎵嬫満</string>
+    <string name="movement_intensity">杩愬姩寮哄害</string>
+    <string name="activity_list_summary_activities">娲诲姩</string>
+    <string name="activity_list_summary_intensity">杩愬姩
+\n寮哄害</string>
+    <string name="activity_list_summary_active_time">娲昏穬鏃堕棿</string>
+    <string name="activity_list_summary_distance">璺濈</string>
+    <string name="activity_list_summary_active_steps">娲昏穬姝ユ暟</string>
+    <string name="devicetype_amazfit_gts2">璺冩垜 GTS 2</string>
+    <string name="devicetype_casiogbx100">鍗¤タ娆� GBX-100</string>
+    <string name="prefs_charts_tabs">鍥捐〃鏍囩</string>
+    <string name="prefs_charts_tabs_summary">鍙鐨勫浘琛ㄦ爣绛�</string>
+    <string name="menuitem_pomodoro">Pomodoro 璺熻釜</string>
+    <string name="menuitem_sleep">鐫$湢</string>
+    <string name="menuitem_goal">娲诲姩鐩爣</string>
+    <string name="devicetype_amazfit_bipu">璺冩垜 Bip U</string>
+    <string name="pref_summary_connected_advertisement">鍗充娇璁惧宸茶繛鎺ワ紝涔熷皢璁惧钃濈墮璁剧疆涓哄彲鍙戠幇</string>
+    <string name="pref_title_connected_advertisement">宸茶繛鎺ユ椂鍙</string>
+    <string name="prefs_autoremove_message">鑷姩绉婚櫎鐭俊閫氱煡</string>
+    <string name="prefs_fake_ring_duration">鍋囪繛缁搷閾�</string>
+    <string name="prefs_operating_sounds">鎿嶄綔鎻愮ず闊�</string>
+    <string name="prefs_key_vibration">鎸夐敭闇囧姩</string>
+    <string name="prefs_autolight">鑷姩浜害</string>
+    <string name="discovery_entered_invalid_authkey">杈撳叆鐨勮璇佸瘑閽ユ棤鏁堬紒璇峰湪璁惧涓婇暱鎸変互杩涜缂栬緫銆�</string>
+    <string name="devicetype_amazfit_vergel">璺冩垜 Verge Lite</string>
+    <string name="fw_upgrade_notice_amazfitvergel">鎮ㄥ嵆灏嗗湪鎮ㄧ殑璺冩垜 Verge Lite 涓婂畨瑁� %s 鍥轰欢銆�
+\n
+\n璇风‘淇濆厛瀹夎 .fw 鏂囦欢锛屽啀瀹夎 .res 鏂囦欢鏈�鍚庢槸 .gps 鏂囦欢銆傛偍鐨勬墜鏈鸿〃鍐嶅畨瑁呬簡 .fw 鏂囦欢鍚庝細鑷姩閲嶅惎銆�
+\n
+\n娉ㄦ剰锛氳嫢鎮ㄧ殑 .res 鍜� .gps 鏂囦欢鍜屼箣鍓嶇増鏈浉鍚岋紝鍒欐棤闇�瀹夎銆�
+\n
+\n椋庨櫓鑷媴锛�</string>
+    <string name="activity_DB_ShowContentButton">鏄剧ず瀵煎叆/瀵煎嚭鐩綍涓殑鍐呭</string>
+    <string name="activity_data_management_directory_content_title">瀵煎叆/瀵煎嚭鐩綍鍐呭</string>
+    <string name="widget_settings_select_device_title">閫夋嫨璁惧</string>
+    <string name="dbmanagementactivity_export_confirmation">纭畾闇�瑕佸鍑烘暟鎹悧锛熶箣鍓嶅鍑虹殑鏁版嵁锛堣嫢鏈夛級灏嗕細琚鐩栥��</string>
+    <string name="dbmanagementactivity_export_data_title">瀵煎嚭鏁版嵁锛�</string>
+    <string name="devicetype_amazfit_bipupro">璺冩垜 Bip U Pro</string>
+    <string name="dbmanagementactivity_export_finished">宸插垹闄�</string>
+    <string name="activity_db_management_clean_export_directory_text">浣嶄簬瀵煎叆/瀵煎嚭鐩綍涓殑宸插鍑烘枃浠跺彲浠ヨ鎮ㄨ澶囦笂鐨勪换鎰忓簲鐢ㄦ墍璁块棶銆傛偍鍦ㄥ浠芥垨鍚屾杩欎簺鏂囦欢鍚庯紝寤鸿鍒犻櫎杩欐垙鏂囦欢銆傝纭繚鍦ㄨ繘琛屽浠藉悗鍐嶈繘琛屽垹闄ゃ�侴PX鏂囦欢銆佸瓙鐩綍鍜岃嚜鍔ㄥ鍑虹殑鏁版嵁搴撴枃浠讹紙鑻ユ湁锛夊皢涓嶄細琚垹闄ゃ�傚鍏�/瀵煎嚭鐩綍鐨勮矾寰勬槸锛�</string>
+    <string name="activity_DB_clean_export_directory_warning_title">鍒犻櫎瀵煎叆/瀵煎嚭鐩綍涓殑鏂囦欢锛�</string>
+    <string name="activity_DB_clean_export_directory_warning_message">纭疄闇�瑕佸垹闄ゅ鍏�/瀵煎嚭鐩綍涓殑鏂囦欢锛�</string>
+    <string name="activity_db_management_clean_export_directory_label">鍒犻櫎瀵煎叆/瀵煎嚭鐩綍涓殑鏂囦欢</string>
+    <string name="dbmanagementactivity_error_cleaning_export_directory">浠庡鍑虹洰褰� %1$s 涓垹闄ゆ枃浠舵椂閬囧埌閿欒</string>
+    <string name="devicetype_amazfit_neo">璺冩垜 Neo</string>
+    <string name="activity_type_strength_training">鍔涢噺璁粌</string>
+    <string name="mi">鑻遍噷</string>
+    <string name="minutes_mi">鍒嗛挓/鑻遍噷</string>
+    <string name="mi_h">杩�</string>
+    <string name="ft">鑻卞昂</string>
+    <string name="devicetype_amazfit_gts2_mini">璺冩垜 GTS 2 Mini</string>
+    <string name="devicetype_zepp_e">Zepp E</string>
+    <string name="devicetype_amazfit_gtr2e">璺冩垜 GTR 2e</string>
+    <string name="devicetype_waspos">Wasp os</string>
+    <string name="battery_detail_activity_title">鐢垫睜淇℃伅</string>
+    <string name="devicetype_amazfit_gts2e">璺冩垜 GTS 2e</string>
+    <string name="devicetype_amazfit_x">璺冩垜 X</string>
+    <string name="fw_upgrade_notice_amazfitx">鎮ㄥ嵆灏嗗湪鎮ㄧ殑 璺冩垜 X 涓婂畨瑁� %s 鍥轰欢銆�
+\n
+\n璇风‘淇濋鍏堝畨瑁� .fw 鏂囦欢锛屽啀瀹夎 .res 鏂囦欢銆傛偍鐨勬墜鐜皢浼氬湪瀹夎浜� .fw 鏂囦欢鍚庤嚜鍔ㄩ噸鍚��
+\n
+\n娉ㄦ剰锛氳嫢 .res 鏂囦欢鐗堟湰鍜屼箣鍓嶅畨瑁呯殑鐩稿悓锛屽垯鏃犻渶鍐嶆瀹夎 .res 鏂囦欢銆�
+\n
+\n椋庨櫓鑷媴锛�</string>
+    <string name="calendar_year">骞�</string>
+    <string name="calendar_six_months">鍗婂勾</string>
+    <string name="calendar_month">鏈�</string>
+    <string name="calendar_two_weeks">涓ゅ懆</string>
+    <string name="calendar_week">鍛�</string>
+    <string name="calendar_day">鏃�</string>
+    <string name="battery_level">鐢垫睜鐢甸噺</string>
+    <string name="show_ongoing_activity">鏄剧ず姝e湪杩涜娲诲姩鐨勫脊鍑虹獥鍙�</string>
+    <string name="dialog_hide">闅愯棌</string>
+    <string name="calendar_three_months">3涓湀</string>
+    <string name="fossil_hr_auth_failed">鐢变簬璁よ瘉澶辫触锛屽姛鑳藉彈鍒伴檺鍒�</string>
+    <string name="fossil_hr_unavailable_unauthed">鏈璇佹ā寮忎笅涓嶅彲鐢�</string>
+    <string name="fossil_hr_synced_activity_data">宸插悓姝ョ殑娲诲姩鏁版嵁</string>
+    <string name="fossil_hr_commute_processing">姝e湪澶勭悊璇锋眰鈥�</string>
+    <string name="qhybrid_title_calibration">鏍″噯</string>
+    <string name="qhybrid_title_apps_management">搴旂敤绠$悊</string>
+    <string name="qhybrid_title_file_management">鏂囦欢绠$悊</string>
+    <string name="qhybrid_title_background_image">鑳屾櫙鍥剧墖</string>
+    <string name="qhybrid_title_apps">搴旂敤</string>
+    <string name="devicetype_um25">UM-25</string>
+    <string name="fossil_hr_warning_firmware_too_new">鐢变簬鎵嬭〃鍥轰欢杩囨柊锛屾煇浜涘姛鑳借绂佺敤</string>
+    <string name="prefs_sounds_summary">杩涜閰嶇疆鏃惰澶囧皢浼氬彂鍑哄0鍝�</string>
+    <string name="prefs_sounds">澹伴煶</string>
+    <string name="fw_upgrade_notice_amazfitneo">鎮ㄥ嵆灏嗗湪鎮ㄧ殑璺冩垜 Neo 涓婂畨瑁� %s 鍥轰欢銆�
+\n
+\n鎮ㄧ殑鎵嬬幆灏嗗湪瀹夎浜� .fw 鏂囦欢鍚庨噸鍚��
+\n
+\n椋庨櫓鑷媴锛�</string>
+    <string name="menuitem_temperature">娓╁害</string>
+    <string name="menuitem_widgets">绐楀彛灏忛儴浠�</string>
+    <string name="menuitem_events">浜嬩欢</string>
+    <string name="devicetype_miband6">灏忕背鎵嬬幆6</string>
+    <string name="fw_upgrade_notice_miband6">鎮ㄥ嵆灏嗗湪鎮ㄧ殑灏忕背鎵嬬幆6涓婂畨瑁� %s 鍥轰欢銆�
+\n
+\n璇风‘淇濆厛瀹夎 .fw 鏂囦欢锛岀劧鍚庡啀瀹夎 .res 鏂囦欢銆傛偍鐨勬墜鐜皢浼氬湪瀹夎浜� .fw 鏂囦欢鍚庨噸鏂板惎鍔ㄣ��
+\n
+\n娉ㄦ剰锛氳嫢鐩稿悓鐗堟湰鐨� .res 鏂囦欢宸茬粡瀹夎锛屽垯鏃犻渶鍐嶆瀹夎 .res 鏂囦欢銆�
+\n
+\n椋庨櫓鑷媴锛�</string>
+    <string name="pref_summary_canned_messages_dismisscall">浠ョ煭淇℃柟寮忎粠鎵嬭〃涓婃嫆缁濇潵鐢�</string>
+    <string name="pref_summary_canned_messages_set">灏嗕笅闈㈤厤缃殑淇℃伅鍙戦�佸埌鎮ㄧ殑璁惧涓�</string>
+    <string name="qhybrid_summary_calibration">鏍″噯鎵嬭〃鎸囬拡</string>
+    <string name="qhybrid_summary_file_management">涓婅浇鍜屼笅杞芥枃浠�</string>
+    <string name="pref_summary_physical_buttons">閰嶇疆鎵嬭〃涓婄殑鐗╃悊鎸夐敭鎵�瀵瑰簲鐨勫姛鑳�</string>
+    <string name="pref_title_physical_buttons">鐗╃悊鎸夐敭</string>
+    <string name="kind_agps_bundle">AGPS 鍖�</string>
+    <string name="qhybrid_calibration_100_steps">绗竴鐧炬</string>
+    <string name="qhybrid_calibration_10_steps">绗崄姝�</string>
+    <string name="qhybrid_calibration_1_step">绗竴姝�</string>
+    <string name="qhybrid_calibration_clockwise">椤烘椂閽�</string>
+    <string name="qhybrid_calibration_counterclockwise">閫嗘椂閽�</string>
+    <string name="fossil_hr_new_action">鏂板姩浣�</string>
+    <string name="fossil_hr_new_action_cancel">鍙栨秷</string>
+    <string name="fossil_hr_edit_action">缂栬緫鍔ㄤ綔</string>
+    <string name="fossil_hr_edit_action_delete">鍒犻櫎</string>
+    <string name="fossil_hr_commute_actions_explanation">杩欓噷閰嶇疆鐨勫姩浣滃皢鍑虹幇鍦ㄤ綘鐨勬墜琛ㄤ笂鐨勯�氬嫟搴旂敤涓�傞槄璇籛iki浠ヤ簡瑙e浣曞鐞嗚繖浜涘姩浣滄墍浜х敓鐨勬剰鍥剧殑淇℃伅銆�</string>
+    <string name="watchface_install_info">鎮ㄥ嵆灏嗗畨瑁呬笅鍒楄〃鐩橈細
+\n
+\n%1$s
+\n鐗堟湰 %2$s 鐢� %3$s
+\n</string>
+    <string name="qhybrid_watchface_configuration_old_firmware">涓哄浐浠剁増鏈� DN1.0.2.19r 鍜屾洿浣庣増鏈殑琛ㄧ洏閰嶇疆灞忓箷</string>
+    <string name="qhybrid_title_watchface">琛ㄧ洏閰嶇疆</string>
+    <string name="notification_channel_low_battery_name">浣庣數閲�</string>
+    <string name="notification_channel_transfer_name">鏁版嵁浼犺緭</string>
+    <string name="qhybrid_pref_summary_external_intents">鍏佽鍏朵粬 Android 搴旂敤涓婁紶/瑕嗙洊鏂囦欢</string>
+    <string name="qhybrid_pref_title_external_intents">鍏佽鍗遍櫓鐨勫閮ㄦ剰鍥�</string>
+    <string name="pref_summary_developer_settings">寮�鍙戣�呬娇鐢ㄧ殑璁剧疆鍜屽姛鑳�</string>
+    <string name="pref_title_developer_settings">寮�鍙戣�呰缃�</string>
+    <string name="qhybrid_pref_summary_actions">涓洪�氬嫟搴旂敤鐨勫姩浣�</string>
+    <string name="qhybrid_pref_title_actions">鍔ㄤ綔</string>
+    <string name="devicetype_amazfit_trex_pro">璺冩垜 T-Rex Pro</string>
+    <string name="menuitem_barometer">姘斿帇璁�</string>
+    <string name="pref_theme_black_background">鍦ㄦ繁鑹蹭富棰樹腑浣跨敤榛戣壊鑳屾櫙</string>
+    <string name="gpx_receiver_overwrite_some_files">鏌愪簺鏂囦欢宸茬粡瀛樺湪锛岃鐩栧悧锛�</string>
+    <string name="gpx_receiver_files_received">宸叉敹鍒扮殑 GPX 鏂囦欢锛�</string>
+    <string name="gpx_receiver_activity_title">Gadgetbridge GPX 鎺ユ敹鍣�</string>
+    <string name="activity_summary_detail_editing_gpx_track">缂栬緫宸茶繛鎺ョ殑 GPX 璺熻釜</string>
+    <string name="activity_summary_detail_clear_gpx_track">娓呴櫎 GPX 璺熻釜</string>
+    <string name="activity_summary_detail_select_gpx_track">閫夋嫨 GPX 璺熻釜</string>
+    <string name="watchface_setting_wrist_flick_duration">鏃堕棿闀垮害 锛堟绉掞級</string>
+    <string name="watchface_setting_wrist_flick_minute">鍒嗛拡 锛�-360 鍒� 360锛�</string>
+    <string name="watchface_setting_wrist_flick_move_relative">鐩稿浜庢椂闂寸Щ鍔ㄦ寚閽�</string>
+    <string name="watchface_setting_wrist_flick_hour">鏃堕拡锛�-360 鍒� 360锛�</string>
+    <string name="watchface_setting_desc_wrist_flick">锛堣瀹屽叏绂佺敤锛岃鍚敤鐩稿杩愬姩锛屽苟灏嗘墍鏈夋暟鍊艰涓�0锛�</string>
+    <string name="watchface_setting_title_wrist_flick">杞诲脊鎵嬭厱</string>
+    <string name="watchface_setting_display_refresh_partial">閮ㄥ垎鍒锋柊锛堝垎閽燂級</string>
+    <string name="watchface_setting_display_refresh_full">瀹屾暣鍒锋柊锛堝垎閽燂級</string>
+    <string name="watchface_setting_title_display_refresh_timeout">鏄剧ず鍒锋柊瓒呮椂</string>
+    <string name="watchface_dialog_title_settings">琛ㄧ洏璁剧疆</string>
+    <string name="watchface_dialog_widget_color_black">榛�</string>
+    <string name="watchface_dialog_widget_color_white">鐧�</string>
+    <string name="watchface_dialog_widget_color">棰滆壊</string>
+    <string name="qhybrid_title_watchface_designer">琛ㄧ洏璁捐鍣�</string>
+    <string name="watchface_dialog_widget_preset_right">鍙�</string>
+    <string name="watchface_dialog_widget_preset_left">宸�</string>
+    <string name="watchface_dialog_widget_preset_bottom">搴曢儴</string>
+    <string name="watchface_dialog_widget_preset_top">椤堕儴</string>
+    <string name="watchface_dialog_widget_presets">瀹氫綅棰勮</string>
+    <string name="watchface_dialog_widget_y_coordinate">Y 鍧愭爣 锛堟渶澶�240锛�</string>
+    <string name="watchface_dialog_widget_x_coordinate">X 鍧愭爣锛堟渶澶�240锛�</string>
+    <string name="watchface_dialog_widget_type">绫诲瀷</string>
+    <string name="appmanager_app_edit">缂栬緫</string>
+    <string name="button_watchface_save_apply">淇濆瓨骞跺簲鐢�</string>
+    <string name="button_watchface_preview">鍦ㄦ墜琛ㄤ笂棰勮</string>
+    <string name="watchface_widget_type_heart_rate">蹇冪巼</string>
+    <string name="watchface_widget_type_steps">姝ユ暟</string>
+    <string name="watchface_widget_type_weather">澶╂皵</string>
+    <string name="watchface_widget_type_date">鏃ユ湡</string>
+    <string name="watchface_dialog_title_add_widget">娣诲姞绐楀彛灏忛儴浠�</string>
+    <string name="watchface_toast_settings_incomplete">璁剧疆鏈畬鎴愶紝绐楀彛灏忛儴浠舵湭鑳芥坊鍔犮��</string>
+    <string name="watchface_dialog_title_set_name">璁剧疆琛ㄧ洏鍚嶇О</string>
+    <string name="button_watchface_settings">琛ㄧ洏璁剧疆</string>
+    <string name="button_watchface_add_widget">娣诲姞绐楀彛灏忛儴浠�</string>
+    <string name="button_watchface_select_image">淇敼鑳屾櫙鍥剧墖</string>
+    <string name="button_watchface_edit_name">缂栬緫鍚嶇О</string>
+    <string name="set">璁剧疆</string>
+    <string name="devicetype_smaq2oss">SMA-Q2 OSS</string>
+    <string name="watchface_upload_failed">涓婁紶琛ㄧ洏澶辫触锛岃鍐嶈瘯涓�娆°��</string>
+    <string name="watchface_cache_confirm_overwrite">鍦ㄧ紦瀛樹腑鍙戠幇鍚屽悕鐨勮〃鐩橈紝鏄惁瑕佽鐩栵紵</string>
+    <string name="heart_rate_result">娴嬮噺缁撴灉</string>
+    <string name="getting_heart_rate">娴嬮噺涓�</string>
+    <string name="blood_pressure">琛�鍘�</string>
+    <string name="watchface_setting_power_saving_hands">绂诲紑鎵嬭厱鏃剁鐢ㄦ墜閮ㄨ繍鍔�</string>
+    <string name="watchface_setting_power_saving_display">绂诲紑鎵嬭厱鏃剁鐢ㄦ樉绀烘洿鏂�</string>
+    <string name="watchface_setting_title_power_saving">鑺傝兘</string>
+    <string name="watchface_widget_type_chance_rain">闄嶆按姒傜巼</string>
+    <string name="watchface_widget_type_active_mins">娲诲姩鍒嗛挓</string>
+    <string name="watchface_widget_type_2nd_tz">绗簩鏃跺尯</string>
+    <string name="watchface_widget_type_calories">鍗¤矾閲�</string>
+    <string name="watchface_widget_type_battery">鐢垫睜</string>
+    <string name="pref_summary_huami_force_new_protocol">鑻ユ偍鐨勮澶囧崌绾у浐浠跺悗鏃犳硶杩炴帴锛岃鍚敤璇ラ�夐」</string>
+    <string name="pref_title_huami_force_new_protocol">鏂拌璇佸崗璁�</string>
+    <string name="menuitem_flashlight">鎵嬬數绛�</string>
+    <string name="prefs_autoheartrate_interval">娴嬮噺棰戠巼</string>
+    <string name="prefs_autoheartrate_sleep">鍦ㄧ潯鐪犱腑杩涜娴嬮噺</string>
+    <string name="prefs_autoheartrate_measurement">鑷姩娴嬮噺蹇冪巼</string>
+    <string name="prefs_autoheartrate_summary">鍦ㄧ櫧澶╁拰鐫$湢鏃跺畾鏈熸祴閲忓績鐜�</string>
+    <string name="prefs_autoheartrate">鑷姩蹇冪巼</string>
+    <string name="enable_notifications_summary">涓烘潵鐢点�佹秷鎭拰鍏朵粬鍚敤閫氱煡</string>
+    <string name="enable_vibrations_summary">涓烘潵鐢点�佷俊鎭�侀�氱煡浠ュ強鍏朵粬杩涜闇囧姩</string>
+    <string name="prefs_notifications_enable">鍚敤閫氱煡</string>
+    <string name="prefs_vibration_enable">鍚敤闇囧姩</string>
+    <string name="prefs_sleep_time_summary">宸茶褰曠潯鐪犳椂鎸囧畾鏃堕棿</string>
+    <string name="prefs_sleep_time_label">瀹氫箟鐫$湢灏忔椂</string>
+    <string name="prefs_sleep_time">鐫$湢鏃堕棿</string>
+    <string name="devicetype_fitpro">FitPro</string>
+    <string name="pref_title_ping_tone">Ping 閾冨0</string>
+    <string name="devicetype_nothingear1">Nothing Ear (1)</string>
+    <string name="nothing_prefs_inear_summary">褰撲僵鎴存垨鍙栦笅鑰冲寮忚�虫満鏃惰嚜鍔ㄦ挱鏀炬垨鏆傚仠闊充箰</string>
+    <string name="nothing_prefs_inear_title">鍏ヨ�虫娴�</string>
+    <string name="nothing_prefs_audiomode_title">闊抽妯″紡</string>
+    <string name="check_all_applications">閫変腑鎵�鏈夌殑搴旂敤</string>
+    <string name="uncheck_all_applications">鍙栨秷閫夋嫨鎵�鏈夌殑搴旂敤</string>
+    <string name="pref_title_notification_use_as">浣跨敤搴旂敤鍒楄〃鑷斥��</string>
+    <string name="pref_title_message_privacy_mode">娑堟伅闅愮妯″紡</string>
+    <string name="pref_message_privacy_mode_off">鏄剧ず鍏ㄩ儴鍐呭</string>
+    <string name="pref_applications_settings">搴旂敤鍒楄〃</string>
+    <string name="toast_app_must_be_selected">搴旂敤蹇呴』琚�変腑鎵嶈兘琚厤缃�</string>
+    <string name="toast_app_must_not_be_selected">搴旂敤蹇呴』鏈閫変腑鎵嶈兘琚厤缃�</string>
+    <string name="title_activity_notification_management">閫氱煡璁惧畾</string>
+    <string name="pref_header_notification_application_settings">鍗曚釜搴旂敤璁惧畾</string>
+    <string name="pref_title_notification_use_as_deny">鎷掔粷閫変腑搴旂敤鐨勯�氱煡</string>
+    <string name="pref_title_notification_use_as_allow">鍏佽閫変腑搴旂敤鐨勯�氱煡</string>
+    <string name="pref_message_privacy_mode_complete">闅愯棌鍏ㄩ儴鍐呭</string>
+    <string name="devicetype_galaxybuds">Galaxy Buds</string>
+    <string name="pref_title_equalizer_bass_boost">浣庨煶澧炲己</string>
+    <string name="pref_title_equalizer_soft">杞�</string>
+    <string name="pref_title_equalizer_dynamic">鍔ㄦ��</string>
+    <string name="prefs_dolby_mode">鏉滄瘮妯″紡</string>
+    <string name="prefs_equalizer">鍧囪 鍣�</string>
+    <string name="prefs_equalizer_summary">鍚敤鎴栫鐢ㄥ潎琛″櫒</string>
+    <string name="prefs_dolby_summary">鍧囪 鍣ㄧ殑鏉滄瘮棰勮</string>
+    <string name="prefs_game_mode">娓告垙妯″紡</string>
+    <string name="prefs_touch_lock">瑙︽懜閿�</string>
+    <string name="prefs_galaxy_buds_experimental">璇曢獙鎬�</string>
+    <string name="prefs_ambient_volume">鐜闊抽噺</string>
+    <string name="prefs_ambient_voice_focus">璇煶鐒︾偣</string>
+    <string name="prefs_ambient_voice_summary">浣胯闊崇獊鍑�</string>
+    <string name="prefs_ambient_sound">鐜闊�</string>
+    <string name="prefs_ambient_mode">鐜闊虫ā寮�</string>
+    <string name="prefs_left">宸�</string>
+    <string name="prefs_right">鍙�</string>
+    <string name="prefs_galaxy_touch_options">瑙︽懜閫夐」</string>
+    <string name="prefs_equalizer_preset">鍧囪 鍣ㄩ璁�</string>
+    <string name="prefs_touch_lock_summary">绂佺敤瑙︽懜浜嬩欢</string>
+    <string name="pref_title_equalizer_trebble">楂橀煶澧炲己</string>
+    <string name="pref_title_equalizer_clear">娓呮櫚</string>
+    <string name="prefs_game_mode_summary">浠呭湪鎮ㄧ殑鎵嬫満鏀寔娓告垙妯″紡鏃�</string>
+    <string name="left_earbud">宸﹁�宠�冲</string>
+    <string name="right_earbud">鍙宠�宠�冲</string>
+    <string name="battery_case">鍏呯數鐩�</string>
+    <string name="devicetype_sony_wh_1000xm3">Sony WH-1000XM3</string>
+    <string name="pref_title_equalizer_normal">鏅��</string>
+    <string name="prefs_active_noise_cancelling">涓诲姩闄嶅櫔</string>
+    <string name="prefs_active_noise_cancelling_summary">闃绘鍛ㄥ洿鐨勭幆澧冨櫔闊�</string>
+    <string name="prefs_pressure_relief">甯︾幆澧冮煶鐨勯噴鍘�</string>
+    <string name="pressure_relief_summary">褰撲笉浣跨敤涓诲姩闄嶅櫔鏃讹紝闃叉鑰虫湹鎰熷埌鍘嬪姏銆�</string>
+    <string name="pref_header_sony_ambient_sound_control">鐜闊虫帶鍒�</string>
+    <string name="sony_ambient_sound_off">鍏�</string>
+    <string name="sony_ambient_sound_noise_cancelling">闄嶅櫔</string>
+    <string name="sony_ambient_sound_wind_noise_reduction">椋庡0鎶戝埗</string>
+    <string name="sony_ambient_sound_ambient_sound">鐜闊�</string>
+    <string name="sony_ambient_sound_focus_voice">鑱氱劍璇煶</string>
+    <string name="sony_ambient_sound_level">鐜闊崇骇鍒�</string>
+    <string name="sony_sound_position">澹伴煶浣嶇疆</string>
+    <string name="sony_sound_position_off">鍏�</string>
+    <string name="sony_sound_position_front">鍓�</string>
+    <string name="sony_sound_position_front_left">宸﹀墠</string>
+    <string name="sony_sound_position_front_right">鍙冲墠</string>
+    <string name="sony_sound_position_rear_left">宸﹀悗</string>
+    <string name="sony_sound_position_rear_right">鍙冲悗</string>
+    <string name="sony_surround_mode">鐜粫澹版ā寮�</string>
+    <string name="sony_surround_mode_off">鍏�</string>
+    <string name="sony_surround_mode_concert_hall">闊充箰鍘�</string>
+    <string name="sony_equalizer_preset_bright">鏄庝寒</string>
+    <string name="sony_equalizer_preset_excited">婵�鍔�</string>
+    <string name="sony_equalizer_preset_mellow">鏌斿拰</string>
+    <string name="sony_equalizer_preset_relaxed">鏀炬澗</string>
+    <string name="sony_equalizer_preset_vocal">浜哄0</string>
+    <string name="sony_equalizer_preset_treble_boost">楂橀煶澧炲己</string>
+    <string name="sony_equalizer_preset_bass_boost">浣庨煶澧炲己</string>
+    <string name="sony_equalizer_preset_custom_1">瀹氬埗1</string>
+    <string name="sony_equalizer_preset_custom_2">瀹氬埗2</string>
+    <string name="pref_header_sony_equalizer_preset_custom_1">鑷畾涔夐缃�1</string>
+    <string name="pref_header_sony_equalizer_preset_custom_2">鑷畾涔夐缃�2</string>
+    <string name="sony_equalizer_band_400">400</string>
+    <string name="sony_equalizer_band_1000">1K</string>
+    <string name="sony_equalizer_band_6300">6.3K</string>
+    <string name="sony_equalizer_band_16000">16K</string>
+    <string name="sony_equalizer_clear_bass">娓呮櫚閲嶄綆闊�</string>
+    <string name="sony_touch_sensor">瑙︽懜鎰熷簲鍣ㄦ帶鍒�</string>
+    <string name="sony_notification_voice_guide">閫氱煡涓庤闊冲紩瀵�</string>
+    <string name="sony_automatic_power_off">鑷姩鍏虫満</string>
+    <string name="sony_automatic_power_off_off">涓嶈鍏虫満</string>
+    <string name="sony_automatic_power_off_5_min">5 鍒嗛挓</string>
+    <string name="sony_automatic_power_off_30_min">30 鍒嗛挓</string>
+    <string name="sony_automatic_power_off_1_hour">1 灏忔椂</string>
+    <string name="sony_automatic_power_off_3_hour">3 灏忔椂</string>
+    <string name="watchface_widget_type_custom">鑷畾涔夊皬閮ㄤ欢</string>
+    <string name="devicetype_galaxybuds_live">Galaxy Buds Live</string>
+    <string name="sony_ambient_sound">妯″紡</string>
+    <string name="pref_header_equalizer">鍧囪 鍣�</string>
+    <string name="pref_header_other">鍏朵粬</string>
+    <string name="pref_header_system">绯荤粺</string>
+    <string name="sony_surround_mode_arena">鑸炲彴</string>
+    <string name="sony_surround_mode_outdoor_stage">鎴峰鑸炲彴</string>
+    <string name="sony_equalizer">鍧囪 鍣�</string>
+    <string name="sony_surround_mode_club">淇变箰閮�</string>
+    <string name="sony_equalizer_preset_off">鍏�</string>
+    <string name="sony_equalizer_preset_manual">鎵嬪姩</string>
+    <string name="sony_warn_sbc_codec">璀﹀憡锛氳繖浜涜缃粎鍦⊿BC闊抽缂栫爜涓嬪伐浣溿��</string>
+    <string name="sony_equalizer_preset_speech">婕旇</string>
+    <string name="sony_equalizer_band_2500">2.5K</string>
+    <string name="prefs_activity_in_device_card_title_summary">鍦ㄨ澶囧崱鐗囦笂鏄剧ず褰撳墠鐨勬鏁般�佽璧拌窛绂绘垨鐫$湢鏃堕棿</string>
+    <string name="prefs_activity_in_device_card_sleep_title">鐫$湢</string>
+    <string name="prefs_activity_in_device_card_distance_title_summary">璺濈鐢辨鏁板拰姝ラ暱璁$畻锛堝湪鈥滆缃�-鍏充簬鎮ㄢ�濅腑鍙皟鏁达級</string>
+    <string name="prefs_activity_in_device_card_steps_title_summary">鏄剧ず鎬昏姝ユ暟</string>
+    <string name="device_card_activity_card_title">璁惧鍗$墖涓婄殑娲诲姩淇℃伅</string>
+    <string name="device_card_activity_card_title_summary">閫夋嫨鍦ㄨ澶囧崱鐗囦笂鏄剧ず鐨勬椿鍔ㄨ缁嗕俊鎭�</string>
+    <string name="prefs_activity_in_device_card_title">鍦ㄨ澶囧崱鐗囦笂鏄剧ず娲诲姩淇℃伅</string>
+    <string name="prefs_activity_in_device_card_sleep_title_summary">鏄剧ず鐫$湢鏃堕棿</string>
+    <string name="sony_automatic_power_off_when_taken_off">鎽樹笅鏃�</string>
+    <string name="prefs_fm_preset_instructions">闀挎寜鎸夐挳浠ュ瓨鍌ㄩ璁�</string>
+    <string name="prefs_fm_presets_presets">棰勮</string>
+    <string name="watchface_dialog_widget_timezone">鏃跺尯</string>
+    <string name="watchface_dialog_widget_update_timeout">鏇存柊瓒呮椂鏃堕棿锛堝垎閽燂級</string>
+    <string name="watchface_dialog_widget_timeout_hide_text">闅愯棌鏂囧瓧瓒呮椂</string>
+    <string name="watchface_dialog_widget_timeout_show_circle">鏄剧ず鍦嗗湀瓒呮椂</string>
+    <string name="qhybrid_title_on_device_confirmation">鍚敤璁惧渚х殑閰嶅纭</string>
+    <string name="qhybrid_summary_on_device_confirmation">璁惧渚ч厤瀵圭‘璁ゅ彲鑳戒細鏈変簺鎭间汉銆傜鐢ㄥ畠鍙兘浼氬け鍘婚儴鍒嗗姛鑳姐��</string>
+    <string name="title_activity_set_reminders">閰嶇疆鎻愰啋</string>
+    <string name="reminder_date">鏃ユ湡</string>
+    <string name="reminder_time">鏃堕棿</string>
+    <string name="reminder_message">淇℃伅</string>
+    <string name="reminder_time_once">%1$s锛屼竴娆�</string>
+    <string name="reminder_time_every_day">%1$s锛屾瘡澶�</string>
+    <string name="reminder_time_every_week">%1$s锛屾瘡鍛�</string>
+    <string name="reminder_time_every_month">%1$s锛屾瘡鏈�</string>
+    <string name="reminder_once">鍗曟</string>
+    <string name="reminder_every_day">姣忓ぉ</string>
+    <string name="reminder_every_week">姣忓懆</string>
+    <string name="reminder_every_month">姣忔湀</string>
+    <string name="reminder_every_year">姣忓勾</string>
+    <string name="reminder_delete_confirm_title">鍒犻櫎鎻愰啋</string>
+    <string name="reminder_delete_confirm_description">鎮ㄧ‘璁よ鍒犻櫎姝ゆ彁閱掞紵</string>
+    <string name="title_activity_reminder_details">鎻愰啋璇︽儏</string>
+    <string name="miband_prefs_reserve_reminder_calendar">涓哄嵆灏嗗埌鏉ョ殑浜嬩欢淇濈暀鎻愰啋</string>
+    <string name="reminder_time_every_year">%1$s锛屾瘡骞�</string>
+    <string name="controlcenter_start_configure_reminders">閰嶇疆鎻愰啋</string>
+    <string name="prefs_reserve_reminder_calendar_summary">鍗冲皢鍚屾鐨勪簨浠舵暟</string>
+    <string name="reminder_repeat">閲嶅</string>
+    <string name="reminder_no_free_slots_title">娌℃湁鍙敤绌轰綅</string>
+    <string name="reminder_no_free_slots_description">璇ヨ澶囧凡缁忔病鏈夊彲鐢ㄤ簬鎻愰啋鐨勫彲鐢ㄧ殑绌轰綅锛堟�昏绌轰綅锛�%1$s锛�</string>
+    <string name="mi2_prefs_do_not_disturb_lift_wrist">鍦ㄨ鍕挎墦鎵版湡闂村湪鎶捣鏃舵縺娲绘樉绀�</string>
+    <string name="averageAltitude">骞冲潎</string>
+    <string name="minSpeed">鏈�灏�</string>
+    <string name="maxStride">鏈�澶ф骞�</string>
+    <string name="averageCadence">骞冲潎鑺傚</string>
+    <string name="maxCadence">鏈�澶ц妭濂�</string>
+    <string name="minCadence">鏈�灏忚妭濂�</string>
+    <string name="maxHR">鏈�澶у績鐜�</string>
+    <string name="minHR">鏈�灏忓績鐜�</string>
+    <string name="minStride">鏈�灏忔骞�</string>
+    <string name="spm">姝ユ暟/鍒嗛挓</string>
+    <string name="pref_header_sony_equalizer_bands">涔愰槦</string>
+    <string name="sony_audio_upsampling">闊抽鎻愬崌閲囨牱</string>
+    <string name="controlcenter_power_off_confirm_title">鍏虫満</string>
+    <string name="controlcenter_power_off">鍏虫満</string>
+    <string name="controlcenter_power_off_confirm_description">鎮ㄧ‘瀹氭兂瑕佸叧闂璁惧鐨勭數婧愬悧锛�</string>
+    <string name="devicetype_sony_wf_sp800n">绱㈠凹 WF-SP800N</string>
+    <string name="activity_prefs_discovery_pairing">鍙戠幇鍜岄厤瀵归�夐」</string>
+    <string name="add_test_device">娣诲姞娴嬭瘯璁惧</string>
+    <string name="discover_unsupported_devices">鍙戠幇鏈敮鎸佺殑璁惧</string>
+    <string name="discover_unsupported_devices_description">鍚敤璇ラ�夐」灏嗕細鏄剧ず鎵弿鏃舵墍鏈夊凡鍙戠幇鐨勮澶囥�傜煭鎸夊皢浼氬鍒惰澶囧悕绉板拰MAC鍦板潃鍒板壀鍒囨澘銆傞暱鎸夊皢浼氭樉绀衡�滄坊鍔犳祴璇曡澶団�濆璇濇銆傚彲鑳藉鑷存綔鍦ㄧ殑搴旂敤绋嬪簭鍐荤粨闂銆�</string>
+    <string name="sony_button_mode_off">鍏�</string>
+    <string name="sony_pause_when_taken_off">褰撴憳涓嬭�虫満鏃舵殏鍋�</string>
+    <string name="sony_button_mode_left">鎸夐挳妯″紡锛堝乏锛�</string>
+    <string name="sony_button_mode_right">鎸夐挳妯″紡锛堝彸锛�</string>
+    <string name="sony_button_mode_ambient_sound_control">鐜闊虫帶鍒�</string>
+    <string name="sony_button_mode_playback_control">鍥炴斁鎺у埗</string>
+    <string name="sony_button_mode_volume_control">闊抽噺鎺у埗</string>
+    <string name="devicetype_vesc">VESC</string>
+    <string name="ascentDistance">涓婂潯璺濈</string>
+    <string name="descentDistance">涓嬪潯璺濈</string>
+    <string name="devicetype_bose_qc35">Bose QC35</string>
+    <string name="activity_type_hiking">寰掓</string>
+    <string name="activity_type_climbing">鏀�鐧�</string>
+    <string name="devicetype_domyos_t540">Domyos T540</string>
+    <string name="pref_media_next">涓嬩竴棣�</string>
+    <string name="pref_button_action_disabled">宸插仠鐢�</string>
+    <string name="pref_media_play">濯掍綋鎾斁</string>
+    <string name="pref_media_pause">濯掍綋鏆傚仠</string>
+    <string name="pref_media_playpause">鍒囨崲鎾斁</string>
+    <string name="pref_media_previous">涓婁竴棣�</string>
+    <string name="pref_media_volumeup">闊抽噺鍔�</string>
+    <string name="about_hash">鎻愪氦 %s</string>
+    <string name="pref_media_forward">蹇繘</string>
+    <string name="pref_media_rewind">鍊掑甫</string>
+    <string name="distance_format_meters">###绫�</string>
+    <string name="distance_format_kilometers">###.#鍗冪背</string>
+    <string name="distance_format_miles">###.#鑻遍噷</string>
+    <string name="distance_format_feet">###鑻卞昂</string>
+    <string name="pref_media_volumedown">闊抽噺鍑�</string>
+    <string name="pref_device_action_broadcast">鍙戦�佸箍鎾�</string>
+    <string name="pref_activity_recognition_mode_auto">鑷姩</string>
+    <string name="menuitem_menu">鑿滃崟</string>
+    <string name="fossil_hr_button_config_info">鍥犱负涓�浜涙寜閽殑鍔熻兘鏄纭紪鐮佸湪鎵嬭〃鐨勫浐浠朵腑鐨勶紝鎵�浠ヨ繖浜涙寜閽棤娉曡閰嶇疆銆�
+\n
+\n璀﹀憡锛氬畨瑁呭畼鏂� Fossil 搴旂敤绋嬪簭鐨勮〃鐩樻椂闀挎寜涓婃柟鎸夐挳涔熶細鍦ㄦ樉绀�/闅愯棌灏忛儴浠朵箣闂村垏鎹€��</string>
+    <string name="pref_activity_recognize_rowing">璇嗗埆鍒板垝妗�</string>
+    <string name="pref_activity_recognition_mode_none">鏃�</string>
+    <string name="pref_activity_recognition_mode_ask">璇㈤棶</string>
+    <string name="prefs_activity_recognition">娲诲姩璇嗗埆璁剧疆</string>
+    <string name="pref_activity_recognize_running">姝e湪杩愯璇嗗埆</string>
+    <string name="pref_activity_recognize_walking">璇嗗埆鍒版琛�</string>
+    <string name="pref_activity_recognize_biking">璇嗗埆鍒伴獞鑷杞�</string>
+    <string name="devicetype_sony_wh_1000xm4">Sony WH-1000XM4</string>
+    <string name="sony_connect_two_devices">鍚屾椂杩炴帴涓ゅ彴璁惧</string>
+    <string name="sony_speak_to_chat_sensitivity">璇煶妫�娴嬬伒鏁忓害</string>
+    <string name="sony_speak_to_chat_sensitivity_auto">鑷姩</string>
+    <string name="sony_speak_to_chat_focus_on_voice">鑱氱劍璇煶</string>
+    <string name="sony_speak_to_chat_timeout">瓒呮椂</string>
+    <string name="sony_speak_to_chat_timeout_standard">鏍囧噯锛�30绉掞級</string>
+    <string name="sony_speak_to_chat_timeout_long">闀匡紙1鍒嗛挓锛�</string>
+    <string name="sony_speak_to_chat">瀵硅</string>
+    <string name="sony_speak_to_chat_sensitivity_high">楂�</string>
+    <string name="sony_speak_to_chat_sensitivity_low">浣�</string>
+    <string name="sony_speak_to_chat_timeout_off">鍏�</string>
+    <string name="sony_speak_to_chat_timeout_short">鐭紙15绉掞級</string>
+    <string name="watchface_dialog_widget_width">灏忛儴浠跺搴︼紙浠ュ儚绱犱负鍗曚綅锛�</string>
+    <string name="discovery_bluetooth_scan">钃濈墮鎵弿锛�</string>
+    <string name="discovery_bluetooth_le_scan">浣庡姛鑰楄摑鐗欐壂鎻忥細</string>
+    <string name="device_unsupported">涓嶆敮鎸�</string>
+    <string name="audio_codec">闊抽缂栬В鐮佸櫒</string>
+    <string name="pref_header_sony_device_info">璁惧淇℃伅</string>
+    <string name="start">寮�濮�</string>
+    <string name="sony_anc_optimize_confirmation_title">闄嶅櫔浼樺寲</string>
+    <string name="sony_anc_optimize_confirmation_description">璇锋甯镐娇鐢ㄨ�虫満锛屽鏋滄劅瑙夊埌鑰抽亾姘斿帇鍙樺寲锛岃鍐嶆杩愯浼樺寲銆�</string>
+    <string name="sony_anc_optimizer_status_wearing_condition">姝e湪娴嬮噺浣╂埓鐜鈥�</string>
+    <string name="sony_anc_optimizer_status_atmospheric_pressure">姝e湪娴嬮噺澶ф皵鍘嬪姏鈥�</string>
+    <string name="sony_anc_optimizer_status_analyzing">鍒嗘瀽涓��</string>
+    <string name="pref_anc_optimizer_state_pressure">澶ф皵鍘嬪姏</string>
+    <string name="pref_header_sony_anc_optimizer">闄嶅櫔浼樺寲</string>
+    <string name="sony_anc_optimize_description">鐐瑰嚮寮�濮嬩互杩涜闄嶅櫔浼樺寲銆�</string>
+    <string name="unknown">鏈煡</string>
+    <string name="sony_anc_optimize_title">浼樺寲</string>
+    <string name="sony_anc_optimizer_status_not_running">娌℃湁杩愯</string>
+    <string name="sony_anc_optimizer_status_finished">鍗冲皢瀹屾垚鈥�</string>
+    <string name="sony_anc_optimizer_status_starting">姝e湪寮�濮嬧��</string>
+    <string name="devicetype_amazfit_pop_pro">璺冩垜 Pop Pro</string>
+    <string name="devicetype_amazfit_pop">璺冩垜 Pop</string>
+    <string name="menuitem_nothing">鏃�</string>
+    <string name="pref_title_opentracks_packagename">OpenTracks 鍖呭悕</string>
+    <string name="pref_summary_opentracks_packagename">鐢ㄤ簬鍚姩/鍋滄澶栭儴鍋ヨ韩搴旂敤鐨� GPS 杩借釜璁板綍銆�</string>
+    <string name="pref_device_action_fitness_app_control_start">Fitness 搴旂敤杩借釜寮�濮�</string>
+    <string name="pref_device_action_fitness_app_control_stop">Fitness 搴旂敤杩借釜缁撴潫</string>
+    <string name="pref_title_notifications_generic_settings">Android 閫氱煡璁剧疆</string>
+    <string name="activity_db_management_autoexport_scheduled_yes">涓� %1$s 鑷姩瀵煎嚭锛堝師濮嬬殑锛夊凡缁忓惎鐢ㄨ鍒�</string>
+    <string name="activity_db_management_autoexport_scheduled_no">灏氭湭璁″垝鑷姩瀵煎嚭銆�</string>
+    <string name="activity_db_management_autoexport_location">浣嶇疆鏃犳硶鐞嗚В銆� 鍙兘鏄緝鏂扮殑 Android 鏉冮檺绯荤粺鐨勯棶棰樸�� 鏈�鏈夊彲鑳界殑鏄紝鑷姩瀵煎嚭鐜板湪涓嶈捣浣滅敤銆�</string>
+    <string name="autoExport_lastTime_label">鏈�鍚庝竴娆¤嚜鍔ㄥ鍑猴細%1$s</string>
+    <string name="activity_db_management_autoexport_enabled_no">鑷姩瀵煎嚭鏈紑鍚��</string>
+    <string name="activity_db_management_autoexport_enabled_yes">鑷姩瀵煎嚭宸插紑鍚��</string>
+    <string name="watchface_dialog_pre_setting_position">棰勮浣嶇疆涓� %s</string>
+    <string name="watchface_setting_light_up_on_notification">鏈夋柊閫氱煡鏃朵寒璧�</string>
+    <string name="menuitem_email">鐢靛瓙閭欢</string>
+    <string name="about_description_banglejs_main">Bangle.js 鐨� Android 閰嶅搴旂敤绋嬪簭鏋勫缓鍦� Gadgetbridge 椤圭洰涔嬩笂锛屽鍔犱簡 Internet 璁块棶銆� \n \n鐢变簬 Google Play 鍟嗗簵鏀跨瓥锛屾垜浠笉鍏佽鍦ㄥ簲鐢ㄧ▼搴忔湰韬腑鎻愪緵鎹愯禒閾炬帴锛屼絾濡傛灉鎮ㄥ枩娆㈡搴旂敤绋嬪簭锛岃鑰冭檻閫氳繃涓嬮潰鐨� Gadgetbridge 涓婚〉杩涜鎹愯禒銆�</string>
+    <string name="gadgetbridge_running_banglejs_main">Bangle.js 姝e湪杩愯</string>
+    <string name="application_name_banglejs_nopebble">Bangle.js Gadgetbridge</string>
+    <string name="title_activity_controlcenter_banglejs_nopebble">Bangle.js Gadgetbridge</string>
+    <string name="about_activity_title_banglejs_nopebble">鍏充簬 Bangle.js Gadgetbridge</string>
+    <string name="about_description_banglejs_nopebble">Bangle.js 鐨� Android 閰嶅搴旂敤绋嬪簭鏋勫缓鍦� Gadgetbridge 椤圭洰涔嬩笂锛屽鍔犱簡 Internet 璁块棶銆�
+\n
+\n鐢变簬 Google Play 鍟嗗簵鐨勬斂绛栵紝鎴戜滑涓嶅厑璁稿湪搴旂敤绋嬪簭鏈韩鏈夋崘璧犻摼鎺ワ紝浣嗗鏋滀綘鍠滄杩欎釜搴旂敤绋嬪簭锛岃鑰冭檻閫氳繃涓嬮潰鐨凣adgetbridge涓婚〉杩涜鎹愯禒銆�</string>
+    <string name="gadgetbridge_running_banglejs_nopebble">Bangle.js 姝e湪杩愯</string>
+    <string name="application_name_main_nightly">Gadgetbridge (姣忓缂栬瘧鐗堟湰)</string>
+    <string name="title_activity_controlcenter_main_nightly">Gadgetbridge 姣忓缂栬瘧鐗堟湰</string>
+    <string name="about_activity_title_main_nightly">鍏充簬 Gadgetbridge 姣忓缂栬瘧鐗堟湰</string>
+    <string name="gadgetbridge_running_main_nightly">姣忓缂栬瘧鐗堟湰鐨� GB 姝e湪杩愯</string>
+    <string name="title_activity_controlcenter_main_nopebble">Gadgetbridge 姣忓缂栬瘧鐗堟湰锛屾棤 Pebble</string>
+    <string name="gadgetbridge_running_main_nopebble">鏃� Pebble 鐨勬瘡澶滅紪璇戠増 GB 姝e湪杩愯</string>
+    <string name="about_activity_title_main_nopebble">鍏充簬 Gadgetbridge 姣忓缂栬瘧鐗堟湰锛屾棤 Pebble</string>
+    <string name="about_description_main_nopebble">鐢ㄤ簬鏇夸唬渚涘簲鍟嗛棴婧愮┛鎴磋澶� Android 搴旂敤绋嬪簭鐨勮劚鏈恒�佹棤鐗堟潈銆佽嚜鐢辩殑鏂规銆� 璇ョ▼搴忎负 Gadgetbridge 鐨勬瘡澶滅紪璇戠増鏈�傛鐗堟湰閲嶅懡鍚嶄簡 Pebble 鎻愪緵绋嬪簭浠ラ槻姝㈠啿绐侊紝鍥犳鏌愪簺涓� Pebble 鐩稿叧鐨勯泦鎴愬皢涓嶈捣浣滅敤锛屼絾瀹冨彲浠ヤ笌鐜版湁鐨� Gadgetbridge 瀹夎涓�璧峰畨瑁呫��</string>
+    <string name="application_name_main_nopebble">Gadgetbridge 锛堟瘡澶滅紪璇戠増鏈紝鏃� Pebble 鎻愪緵绋嬪簭锛�</string>
+    <string name="application_name_banglejs_main">Bangle.js Gadgetbridge</string>
+    <string name="about_activity_title_banglejs_main">鍏充簬 Bangle.js Gadgetbridge</string>
+    <string name="title_activity_controlcenter_banglejs_main">Bangle.js Gadgetbridge</string>
+    <string name="about_description_main_nightly">鐢ㄤ簬鏇夸唬渚涘簲鍟嗛棴婧愮┛鎴磋澶� Android 搴旂敤绋嬪簭鐨勮劚鏈恒�佹棤鐗堟潈銆佽嚜鐢辩殑鏂规銆� 璇ョ▼搴忎负 Gadgetbridge 鐨勬瘡澶滅紪璇戠増鏈�� 濡傛灉鎮ㄥ凡缁忓畨瑁呬簡 Gadgetbridge 鎴� Pebble 搴旂敤绋嬪簭锛屽垯鏃犳硶瀹夎瀹冿紝鍥犱负 Pebble 鎻愪緵绋嬪簭瀛樺湪鍐茬獊銆�</string>
+    <string name="prefs_activate_display_on_lift_sensitivity">鏁忔劅搴�</string>
+    <string name="normal">姝e父</string>
+    <string name="sensitive">鏁忔劅</string>
+    <string name="pref_device_action_fitness_app_control_toggle">鍒囨崲鍋ヨ韩搴旂敤璺熻釜</string>
+    <string name="world_clock_delete_confirm_description">鎮ㄧ‘瀹氭兂瑕佸垹闄ゆ涓栫晫鏃堕挓锛�</string>
+    <string name="world_clock_no_free_slots_title">娌℃湁鍙敤鐨勪綅缃�</string>
+    <string name="world_clock_no_free_slots_description">姝よ澶囧凡缁忔病鏈夊彲鐢ㄧ殑浣嶇疆鏀剧疆涓栫晫鏃堕挓浜嗭紙鎬诲彲鐢ㄤ綅缃細%1$s锛�</string>
+    <string name="world_clock_timezone">鏃跺尯</string>
+    <string name="world_clock_label">鏍囩</string>
+    <string name="title_activity_world_clock_details">涓栫晫鏃堕挓璇︽儏</string>
+    <string name="pref_world_clocks_summary">涓哄叾浠栨椂鍖洪厤缃椂閽�</string>
+    <string name="world_clock_delete_confirm_title">鍒犻櫎\'%1$s\'</string>
+    <string name="pref_world_clocks_title">涓栫晫鏃堕挓</string>
+    <string name="mi5_prefs_workout_activity_types">閿荤偧娲诲姩绫诲瀷</string>
+    <string name="mi5_prefs_workout_activity_types_summary">閫夋嫨瑕佸湪閿荤偧灞忓箷涓婃樉绀虹殑娲诲姩绫诲瀷</string>
+    <string name="activity_type_elliptical">妞渾鏈�</string>
+    <string name="activity_type_freestyle">鑷敱寮�</string>
+    <string name="activity_type_outdoor_running">鎴峰璺戞</string>
+    <string name="activity_type_outdoor_cycling">鎴峰鍗曡溅</string>
+    <string name="pref_screen_notification_profile_find_device">鏌ユ壘璁惧</string>
+    <string name="pref_screen_vibration_patterns_summary">涓轰笉鍚岀殑閫氱煡閰嶇疆鎸姩妯″紡</string>
+    <string name="pref_screen_notification_profile_event_reminder">浜嬩欢鎻愰啋</string>
+    <string name="pref_screen_notification_idle_alerts">绌洪棽璀︽姤</string>
+    <string name="pref_screen_vibration_patterns_title">鎸姩妯″紡</string>
+    <string name="devicetype_sony_wf_1000xm3">Sony WF-1000XM3</string>
+    <string name="prefs_ambient_settings_title">鐜澹伴煶閫夐」</string>
+    <string name="prefs_voice_detect_duration">瀹夐潤澶氫箙鍚庣粨鏉燂細</string>
+    <string name="pref_voice_detect_duration_5">5绉�</string>
+    <string name="pref_voice_detect_duration_10">10绉�</string>
+    <string name="pref_voice_detect_duration_15">15绉�</string>
+    <string name="devicetype_galaxybuds_pro">Galaxy Buds Pro</string>
+    <string name="prefs_ambient_volume_right">鍙充晶鐜闊抽噺</string>
+    <string name="prefs_customize_ambient_sound_summary">鑷畾涔夌幆澧冮煶</string>
+    <string name="pref_ambient_sound_tone">鐜闊冲0璋�</string>
+    <string name="prefs_seamless_connection_switch_title">鏃犵紳杩炴帴寮�鍏�</string>
+    <string name="prefs_in_ear_detection_summary">褰撹�冲鍦ㄤ綘鐨勮�虫湹閲屾椂锛岄�氳繃鑰冲鎺ュ惉鐢佃瘽</string>
+    <string name="prefs_seamless_connection_switch_summary">鍦ㄩ厤瀵硅澶囦箣闂磋嚜鍔ㄥ垏鎹㈣�虫満</string>
+    <string name="prefs_ambient_volume_left">宸︿晶鐜闊抽噺</string>
+    <string name="prefs_ambient_sound_during_call_title">閫氳瘽鏈熼棿鐨勭幆澧冮煶</string>
+    <string name="prefs_active_noise_cancelling_level">涓诲姩闄嶅櫔绾у埆</string>
+    <string name="prefs_active_noise_cancelling_level_high">楂�</string>
+    <string name="prefs_active_noise_cancelling_level_low">浣�</string>
+    <string name="prefs_switch_control_left">宸︿晶寮�鍏虫帶鍒�</string>
+    <string name="prefs_ambient_sound_during_call_summary">鍦ㄩ�氳瘽涓竻妤氬湴鍚埌鑷繁鐨勫0闊�</string>
+    <string name="prefs_switch_control_right">鍙充晶寮�鍏虫帶鍒�</string>
+    <string name="pref_title_touch_anc">涓诲姩闄嶅櫔</string>
+    <string name="pref_title_touch_volume">闊抽噺</string>
+    <string name="pref_title_touch_ambient">鐜闊�</string>
+    <string name="pref_title_touch_spotify">Spotify</string>
+    <string name="pref_switch_noise_control">鍒囨崲鍣0鎺у埗</string>
+    <string name="prefs_noise_control_with_one_earbud">鍗曚釜鑰冲鐨勫櫔闊虫帶鍒�</string>
+    <string name="prefs_noise_control_with_one_earbud_summary">鍙娇鐢ㄤ竴涓�冲鐨勬儏鍐典笅鍏佽浣跨敤鍣煶鎺у埗</string>
+    <string name="pref_title_touch_voice_assistant">璇煶鍔╃悊</string>
+    <string name="pref_title_touch_quick_ambient">蹇�熺幆澧冮煶鏁�</string>
+    <string name="pref_ambient_sound_tone_summary">浠庢煍杞埌娓呮櫚</string>
+    <string name="pref_switch_controls_ambient_off">鐜闊� 鈫愨啋 鍏抽棴</string>
+    <string name="prefs_voice_detect_summary">鍚敤鐜闊筹紝骞跺湪妫�娴嬪埌璇煶鍚庤嚜鍔ㄩ檷浣庢挱鏀鹃煶閲�</string>
+    <string name="prefs_double_tap_edge">鍙屽嚮杈圭紭</string>
+    <string name="prefs_double_tap_edge_summary">鍗充娇娌℃湁瑙︽懜鏉夸笂鎸変笅涔熻繘琛屽弻鍑绘娴�</string>
+    <string name="pref_balance">骞宠 </string>
+    <string name="pref_switch_controls_anc_ambient">闄嶅櫔 鈫愨啋 鐜</string>
+    <string name="pref_switch_controls_anc_off">闄嶅櫔 鈫愨啋 鍏抽棴</string>
+    <string name="prefs_noise_control">鍣煶鎺у埗</string>
+    <string name="prefs_voice_detect">璇煶妫�娴�</string>
+    <string name="heartrate_bpm_100">100 娆�/鍒嗛挓</string>
+    <string name="heartrate_bpm_105">105 娆�/鍒嗛挓</string>
+    <string name="heartrate_bpm_110">110 娆�/鍒嗛挓</string>
+    <string name="heartrate_bpm_130">130 娆�/鍒嗛挓</string>
+    <string name="heartrate_bpm_135">135 娆�/鍒嗛挓</string>
+    <string name="heartrate_bpm_140">140 娆�/鍒嗛挓</string>
+    <string name="heartrate_bpm_150">150 娆�/鍒嗛挓</string>
+    <string name="prefs_heartrate_alert_experimental_title">蹇冪巼璀︽姤锛堝疄楠屾�э級</string>
+    <string name="prefs_heartrate_alert_experimental_description">鍦ㄨ繃鍘� 10 鍒嗛挓鍐呮病鏈変换浣曟槑鏄剧殑浣撳姏娲诲姩鐨勬儏鍐典笅锛屽綋蹇冪巼瓒呰繃闃堝�兼椂鎸姩鑵曞甫銆傛鍔熻兘鏄疄楠屾�х殑锛屾湭缁忓箍娉涙祴璇曘��</string>
+    <string name="prefs_activity_monitoring_title">娲诲姩鐩戞祴</string>
+    <string name="heartrate_bpm_145">145 娆�/鍒嗛挓</string>
+    <string name="heartrate_bpm_112">112 娆�/鍒嗛挓</string>
+    <string name="heartrate_bpm_120">120 娆�/鍒嗛挓</string>
+    <string name="heartrate_bpm_125">125 娆�/鍒嗛挓</string>
+    <string name="prefs_heartrate_alert_threshold">蹇冪巼璀︽姤闃堝��</string>
+    <string name="prefs_stress_monitoring_title">鍘嬪姏鐩戞祴</string>
+    <string name="prefs_activity_monitoring_description">褰撴墜鐜娴嬪埌韬綋杩愬姩鏃讹紝鑷姩鎻愰珮蹇冪巼妫�娴嬮鐜囷紝浠ユ彁楂樺績鐜囨崟鎹夌殑鍑嗙‘鎬с��</string>
+    <string name="prefs_stress_monitoring_description">浼戞伅鏃剁洃娴嬪帇鍔涙按骞�</string>
+    <string name="mi2_prefs_heart_rate_monitoring">蹇冪巼鐩戞祴</string>
+    <string name="mi2_prefs_heart_rate_monitoring_summary">閰嶇疆蹇冪巼鐩戞祴</string>
+    <string name="mi2_prefs_heart_rate_monitoring_alerts_summary">閰嶇疆蹇冪巼鐩戞祴鍜岃鎶ラ槇鍊�</string>
+    <string name="pref_title_banglejs_text_bitmap">浣滀负浣嶅浘鐨勬枃鏈�</string>
+    <string name="pref_summary_banglejs_text_bitmap">濡傛灉鏃犳硶浣跨敤鎵嬭〃鐨勫瓧浣撳憟鐜板崟璇嶏紝璇峰皢鍏跺憟鐜颁负 Gadgetbridge 涓殑浣嶅浘锛屽苟鍦ㄦ墜琛ㄤ笂鏄剧ず浣嶅浘</string>
+    <string name="pref_summary_device_intents">鍏佽 Bangle.js 鎵嬭〃搴旂敤鍙戦�� Android 鎰忓浘锛屽苟鍏佽 Android 涓婄殑鍏朵粬搴旂敤锛堝 Tasker锛変娇鐢� com.banglejs.uart.tx 鎰忓浘鍚� Bangle.js 鍙戦�佹暟鎹�傞渶瑕佲�滆兘鍦ㄥ叾浠栧簲鐢ㄥ墠鏄剧ず鈥濇潈闄愩��</string>
+    <string name="pref_title_device_intents">鍏佽鎰忓浘</string>
+    <string name="pref_title_device_internet_access">鍏佽浜掕仈缃戣闂�</string>
+    <string name="pref_summary_device_internet_access">鍏佽姝よ澶囦笂鐨勫簲鐢ㄨ闂簰鑱旂綉</string>
+    <string name="pref_workout_start_on_phone_title">鍋ヨ韩搴旂敤杩借釜</string>
+    <string name="pref_workout_start_on_phone_summary">鍦ㄦ墜鐜笂寮�濮� GPS 閿荤偧鏃讹紝鍦ㄦ墜鏈轰笂寮�濮�/鍋滄鍋ヨ韩搴旂敤璺熻釜</string>
+    <string name="notification_channel_gps">GPS璺熻釜</string>
+    <string name="permission_notification_listener">%1$s闇�瑕佽闂�氱煡锛屼互渚垮綋鎮ㄧ殑鎵嬫満灞忓箷鎭睆鏃惰兘鍦ㄦ偍鐨勬墜琛ㄤ笂鏄剧ず杩欎簺閫氱煡銆�
+\n
+\n璇风偣鍑籠'%2$s\'鐒跺悗\'%1$s\'骞跺惎鐢╘'鍏佽閫氱煡璁块棶\'锛岀劧鍚庣偣鍑籠'杩斿洖\'浠ヨ繑鍥炲埌%1$s銆�</string>
+    <string name="permission_notification_policy_access">%1$s 闇�瑕佽闂�滆鍕挎墦鎵扳�濊缃互渚垮湪鎮ㄧ殑鎵嬫満鎭睆鏃舵墠鑳藉湪鎵嬭〃涓婁娇鐢ㄥ畠浠��
+\n
+\n璇风偣鍑烩��%2$s鈥濈劧鍚庣偣鍑烩��%1$s鈥濆苟鍚敤鈥滃厑璁歌鍕挎墦鎵扳�濓紝鐒跺悗鐐瑰嚮鈥滆繑鍥炩�濊繑鍥炲埌%1$s銆�</string>
+    <string name="pref_workout_send_gps_title">鍦ㄩ敾鐐兼湡闂村彂閫� GPS</string>
+    <string name="pref_workout_send_gps_summary">閿荤偧鏃跺皢褰撳墠鐨凣PS浣嶇疆鍙戦�佸埌鎵嬬幆涓�</string>
+    <string name="notification_gps_title">Gadgetbridge GPS</string>
+    <string name="notification_gps_text">灏� GPS 浣嶇疆鍙戦�佸埌 %1$d 璁惧</string>
+    <string name="pref_device_action_phone_gps_location_listener_stop">GPS 浣嶇疆鐩戝惉鍣ㄥ仠姝�</string>
+    <string name="discovery_scanning_intensity">鎵弿寮哄害</string>
+    <string name="discovery_scanning_intensity_warning">濡傛灉鎮ㄩ亣鍒板崱浣忔垨鏃犲弽搴旂殑鎯呭喌锛屽皾璇曞皢鎵弿寮哄害璁剧疆涓鸿緝浣庢按骞炽�傚鏋滀綘鐨勮澶囨病鏈夎鍙戠幇锛屽皾璇曞皢鎵弿寮哄害璁剧疆鍒版洿楂樼殑姘村钩銆�</string>
+    <string name="portuguese_br">钁¤悇鐗欒(宸磋タ)</string>
+    <string name="quick_alarm">蹇�熼椆閽�</string>
+    <string name="portuguese_pt">钁¤悇鐗欒 (钁¤悇鐗�)</string>
+    <string name="quick_alarm_description">浠庡皬宸ュ叿鍙戝嚭鐨勯椆閽�</string>
+    <string name="info_no_devices_connected">鏈繛鎺ヤ换浣曡澶�</string>
+    <string name="info_connected_count">%d 璁惧宸茶繛鎺�</string>
+    <string name="pref_title_banglejs_webview_url">搴旂敤鍔犺浇绋嬪簭缃戝潃</string>
+    <string name="pref_summary_banglejs_webview_url">濡傛灉鎮ㄦ兂瑕佷竴涓嚜瀹氫箟鐨勫簲鐢ㄧ▼搴忓姞杞藉櫒锛岃灏嗘偍鐨� https://鈥�/android.html 缃戝潃鏀惧湪杩欓噷銆傜暀绌虹殑璇濆垯涓猴細 https://banglejs.com/apps</string>
+    <string name="permission_location">%1$s 闇�瑕佸湪鍚庡彴璁块棶鎮ㄧ殑浣嶇疆锛屼互浣垮叾鍗充娇鍦ㄥ睆骞曞叧闂椂涔熻兘涓庢偍鐨勬墜琛ㄤ繚鎸佽繛鎺ャ�� \n \n璇峰湪绋嶅悗鍑虹幇鐨勯〉闈腑閫夋嫨 \'%2$s\' 锛屽苟鐐瑰嚮杩斿洖鑷� %1$s 銆�</string>
+    <string name="lithuanian">绔嬮櫠瀹涜</string>
+    <string name="persian">娉㈡柉璇�</string>
+    <string name="scandinavian">鏂牚鐨勭撼缁翠簹璇�</string>
+    <string name="czech">鎹峰厠璇�</string>
+    <string name="estonian">鐖辨矙灏间簹璇�</string>
+    <string name="extended_ascii">鎵╁睍鐨� ASCII</string>
+    <string name="ukranian">涔屽厠鍏拌</string>
+    <string name="bengali">瀛熷姞鎷夎</string>
+    <string name="icelandic">鍐板矝璇�</string>
+    <string name="pref_explanation_authkey">鏈変簺璁惧鍦ㄧ涓�娆″垵濮嬪寲鏃堕渶瑕佷竴涓壒娈婄殑閰嶅瀵嗛挜銆傜偣鍑昏繖閲岃闂甒iki浠ヤ簡瑙f洿澶氱粏鑺傘��</string>
+    <string name="pref_explanation_authkey_new_protocol">濡傛灉鎮ㄧ湅鍒扳�滆鏇存柊搴旂敤绋嬪簭鍒版渶鏂扮増鏈�濈殑淇℃伅锛岃纭繚閫変腑涓婇潰鐨刓"鏂拌璇佸崗璁甛"銆傜偣鍑昏繖閲屼互鍦╓iki涓婅幏寰楁洿澶氫俊鎭��</string>
+    <string name="prefs_password">瀵嗙爜</string>
+    <string name="prefs_password_summary">浠庢墜鑵曚笂鍙栦笅鏃讹紝鐢ㄥ瘑鐮侀攣瀹氭墜鐜�</string>
+    <string name="prefs_password_4_digits_1_to_4_summary">瀵嗙爜蹇呴』鏈�4浣嶆暟锛屼娇鐢ㄦ暟瀛�1鑷�4</string>
+    <string name="prefs_password_6_digits_0_to_9_summary">瀵嗙爜蹇呴』鏈�6浣嶆暟锛屽彧浣跨敤鏁板瓧</string>
+    <string name="controlcenter_set_parent_folder">璁剧疆鐖舵枃浠跺す</string>
+    <string name="controlcenter_set_preferences">璁剧疆棣栭�夐」</string>
+    <string name="controlcenter_toggle_details">鍒囨崲璇︾粏淇℃伅</string>
+    <string name="controlcenter_connected_fraction">宸茶繛鎺ワ細 %d/%d</string>
+    <string name="controlcenter_add_new_folder">娣诲姞鏂版枃浠跺す</string>
+    <string name="autoconnect_from_device_title">浠庤澶囪繛鎺ユ椂杩炴帴</string>
+    <string name="pref_title_notification_delay_calls">鏉ョ數閫氱煡寤惰繜</string>
+    <string name="pref_summary_notification_delay_calls">鍚戣澶囧彂閫佹潵鐢甸�氱煡涔嬪墠鐨勫欢杩燂紝浠ョ涓哄崟浣嶃��</string>
+    <string name="pref_blacklist_calendars_summary">琚垪鍏ラ粦鍚嶅崟鐨勬棩鍘嗗皢涓嶄細琚悓姝ュ埌璁惧涓�</string>
+    <string name="controlcenter_unset_folder">鍙栨秷璁剧疆鏂囦欢澶�</string>
+    <string name="controlcenter_set_folder_title">璁剧疆鎴栧垱寤烘柊鐨勬枃浠跺す</string>
+    <string name="prefs_password_enabled">瀵嗙爜宸插惎鐢�</string>
+    <string name="error_setting_parent_folder">璁剧疆鐖舵枃浠跺す鏃跺嚭閿欙細 %s</string>
+    <string name="error_deleting_device">鍒犻櫎璁惧鏃跺嚭閿欙細 %s</string>
+    <string name="controlcenter_folder_name">鏂囦欢澶瑰悕绉帮細</string>
+    <string name="auto_reconnect_ble_title">鑷姩閲嶆柊杩炴帴鍒拌澶�</string>
+    <string name="auto_reconnect_ble_summary">涓诲姩灏濊瘯瀹氭湡涓庤澶囪繛鎺�</string>
+    <string name="connection_over_ble">閫氳繃 BLE 杩涜杩炴帴</string>
+    <string name="connection_over_bt_classic">閫氳繃缁忓吀钃濈墮杩炴帴</string>
+    <string name="autoconnect_from_device_summary">褰撹繛鎺ョ敱璁惧鍙戣捣鏃讹紝寤虹珛涓�涓繛鎺ワ紝濡傝�虫満</string>
+    <string name="watchface_dialog_widget_timezone_duration">鏃堕挓鍙鎬ф寔缁椂闂达紙浠ョ涓哄崟浣嶏級</string>
+    <string name="open_fw_installer_warning_title">璀﹀憡</string>
+    <string name="open_fw_installer_pick_file">閫夋嫨鏂囦欢</string>
+    <string name="open_fw_installer_info_text_title">鏂囦欢瀹夎绋嬪簭</string>
+    <string name="open_fw_installer_select_file">閫夋嫨鎮ㄦ兂涓婁紶鍒拌澶囩殑鏂囦欢锛� %s</string>
+    <string name="open_fw_installer_info_text">鍥轰欢/琛ㄧ洏/搴旂敤绋嬪簭/鏂囦欢瀹夎绋嬪簭鍏佽鎮ㄥ皢鏀寔鐨勬枃浠讹紙鍥轰欢锛岃〃鐩橈紝搴旂敤绋嬪簭锛孏PS锛岃祫婧愶紝瀛椾綋绛夛級涓婁紶/瀹夎鍒拌澶囥�傚湪 Wiki 涓煡鐪嬫洿澶氫俊鎭細 https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Firmware-Update</string>
+    <string name="open_fw_installer_warning_text">姝ゅ姛鑳芥湁鍙兘浣挎偍鐨勮澶囧彉鐮栥�傝櫧鐒跺姝よ繖鑸紝浣嗘槸杩欎粠鏈彂鐢熷湪浠讳綍寮�鍙戜汉鍛樿韩涓婏紝浣嗚璁颁綇锛岃繖鏍峰仛闇�瑕佹偍鑷媴椋庨櫓銆�</string>
+    <string name="open_fw_installer_getting_files_title">鑾峰彇鍥轰欢/搴旂敤绋嬪簭鏂囦欢</string>
+    <string name="open_fw_installer_getting_files_text">鐢变簬鎴戜滑鍙兘涓嶄細鎻愪緵鍥轰欢鏂囦欢锛屽洜姝ゆ偍蹇呴』鑷繁鑾峰彇鏂囦欢銆傝繖鎰忓懗鐫�鎮ㄥ皢闇�瑕佸湪 apk 鏂囦欢涓彁鍙栧浐浠讹紝浣跨敤鎼滅储寮曟搸鏌ユ壘銆佹垨鑰呭湪璁哄潧鎼滅储锛堝 Amazfitwatchfaces锛岄�傜敤浜庡皬绫虫墜鐜�/ 璺冩垜璁惧锛夊搴旂殑鍥轰欢绛夈��</string>
+    <string name="open_fw_installer_ensure_device_connected">璇风‘淇濊澶� %s 宸茬粡杩炴帴</string>
+    <string name="open_fw_installer_connect_minimum_one_device">璇疯嚦灏戣繛鎺ヤ竴涓澶囷紝浠ヤ究灏嗘枃浠惰繘琛屽彂閫佸埌璁惧涓娿��</string>
+    <string name="open_fw_installer_connect_maximum_one_device">璇峰彧杩炴帴涓�涓澶囷紝浠ヤ究灏嗘枃浠跺彂閫佸埌璇ヨ澶囦笂銆�</string>
+    <string name="appmanager_item_outdated">锛堝凡杩囨椂锛�</string>
+    <string name="appmanager_app_share">鍒嗕韩</string>
+    <string name="pref_summary_banglejs_txt_bitmap_size">鐢ㄤ簬浣嶅浘鏂囨湰娓叉煋鐨勫ぇ灏�</string>
+    <string name="watchface_dialog_widget_cat_generic">閫氱敤</string>
+    <string name="watchface_dialog_widget_cat_position">浣嶇疆</string>
+    <string name="watchface_dialog_widget_cat_2nd_tz_widget">绗簩鏃跺尯灏忛儴浠�</string>
+    <string name="watchface_dialog_widget_cat_custom_widget">鑷畾涔夊皬閮ㄤ欢</string>
+    <string name="watchface_dialog_title_widget">灏忛儴浠惰缃�</string>
+    <string name="watchface_dialog_widget_background">鑳屾櫙</string>
+    <string name="hybridhr_widget_bg_thin_circle">缁嗗渾</string>
+    <string name="hybridhr_widget_bg_double_circle">鍙屽渾</string>
+    <string name="pref_title_banglejs_txt_bitmap_size">鏂囨湰浣嶅浘澶у皬</string>
+    <string name="hybridhr_widget_bg_dashed_circle">铏氱嚎鍦�</string>
+    <string name="watchface_setting_title_button_toggle_widgets">鍒囨崲灏忛儴浠�</string>
+    <string name="watchface_setting_button_toggle_widgets">鍒囨崲灏忛儴浠�</string>
+    <string name="steps_streaks_since_date">鑷� %s 璧�</string>
+    <string name="step_streak_average_steps_hint">杩炵画姣忓ぉ鐨勫钩鍧囨鏁�</string>
+    <string name="steps_streaks_total_steps_hint_totals">璁板綍鐨勬�绘鏁�</string>
+    <string name="steps_streaks_total_days_hint_totals">宸插疄鐜扮洰鏍囩殑澶╂暟涓庡叿鏈夋鏁扮殑鎵�鏈夊ぉ鏁扮殑鐧惧垎姣�</string>
+    <string name="steps_streaks_total_steps_average_hint">姣忓ぉ骞冲潎鎬绘鏁� %d</string>
+    <string name="steps_streaks">姝ユ暟鏉$汗</string>
+    <string name="steps_streaks_hint">杩炵画鍑犲ぉ涓嶉棿鏂湴杈惧埌姝ユ暟鐩爣</string>
+    <string name="step_streak_ongoing">杩涜涓�</string>
+    <string name="step_streak_longest">鏈�闀�</string>
+    <string name="steps_streaks_streak_days">杩炵画
+\n澶╂暟</string>
+    <string name="steps_streaks_average_steps">骞冲潎
+\n姝ユ暟</string>
+    <string name="step_streak_total">鍏辫</string>
+    <string name="steps_streaks_total_steps">鎬�
+\n姝ユ暟</string>
+    <string name="step_streak_days_hint">杈惧埌姝ユ暟鐩爣鐨勮繛缁ぉ鏁�</string>
+    <string name="steps_streaks_total_steps_hint">鏁翠釜杩炶儨涓殑鎬绘鏁�</string>
+    <string name="steps_streaks_achievement_rate">鎴愬氨
+\n鐜�</string>
+    <string name="note">娉ㄦ剰</string>
+    <string name="share_log_not_enabled_message">蹇呴』棣栧厛鍦ㄢ�滆缃� - 鍐欏叆鏃ュ織鏂囦欢鈥濅腑鍚敤鏃ュ織璁板綍</string>
+    <string name="watchface_setting_button_toggle_backlight">寮�鍚儗鍏夌伅</string>
+    <string name="watchface_setting_title_custom_events">鑷畾涔変簨浠�</string>
+    <string name="controlcenter_get_heartrate_measurement">鑾峰彇蹇冪巼娴嬮噺鍊�</string>
+    <string name="pref_write_logfiles_not_available">鏂囦欢鏃ュ織鍒濆鍖栧け璐ワ紝鍐欐棩蹇楁枃浠剁洰鍓嶄笉鍙敤銆傝灏濊瘯閲嶆柊鍚姩搴旂敤绋嬪簭锛岀劧璇哄啀娆″垵濮嬪寲鏃ュ織鏂囦欢銆�</string>
+    <string name="devicetype_binary_sensor">浜岃繘鍒朵紶鎰熷櫒</string>
+    <string name="application_name_banglejs_nightly">Bangle.js Gadgetbridge (姣忓缂栬瘧鐗�)</string>
+    <string name="title_activity_controlcenter_banglejs_nightly">Bangle.js Gadgetbridge (姣忓缂栬瘧鐗�)</string>
+    <string name="about_activity_title_banglejs_nightly">鍏充簬Bangle.js Gadgetbridge (姣忓缂栬瘧鐗�)</string>
+    <string name="gadgetbridge_running_banglejs_nightly">Bangle.js 姣忓缂栬瘧鐗堟鍦ㄨ繍琛屼腑</string>
+    <string name="about_description_banglejs_nightly">Bangle.js 鐨� Android 閰嶅搴旂敤绋嬪簭鏋勫缓鍦� Gadgetbridge 椤圭洰涔嬩笂锛屽鍔犱簡 Internet 璁块棶銆�
+\n
+\n鐢变簬 Google Play 鍟嗗簵鏀跨瓥锛屾垜浠笉鍏佽鍦ㄥ簲鐢ㄧ▼搴忔湰韬腑鎻愪緵鎹愯禒閾炬帴锛屼絾濡傛灉鎮ㄥ枩娆㈡搴旂敤绋嬪簭锛岃鑰冭檻閫氳繃涓嬮潰鐨� Gadgetbridge 涓婚〉杩涜鎹愯禒銆�</string>
+    <string name="prefs_hourly_chime">鏁寸偣鎶ユ椂</string>
+    <string name="prefs_hourly_chime_summary">鎵嬭〃姣忓皬鏃跺皢鍙戝嚭涓�娆¤渹楦i煶</string>
+    <string name="step_streaks_achievements_sharing_message">鎴戞瘡澶╃殑姝ユ暟鎴愬氨!</string>
+    <string name="step_streaks_achievements_sharing_title">姝ユ暟鎴愬氨</string>
+    <string name="permission_display_over_other_apps">褰� %1$s 鍦ㄥ悗鍙版椂锛�%1$s 闇�瑕佹樉绀哄湪鍏朵粬搴旂敤绋嬪簭涓婄殑鏉冮檺锛屼互渚胯 Bangle.js 鎵嬭〃閫氳繃鎰忓浘鍚姩娲诲姩銆�
+\n
+\n杩欏彲鐢ㄤ簬鍚姩闊充箰搴旂敤绋嬪簭骞舵挱鏀炬瓕鏇蹭互鍙婅澶氬叾浠栦簨鎯呫��
+\n
+\n璇风偣鍑烩��%2$s鈥濓紝鐒跺悗鐐瑰嚮鈥�%1$s鈥濆苟鍚敤鈥滃厑璁告樉绀哄湪鍏朵粬搴旂敤绋嬪簭涓娾�濓紝鐒跺悗鐐瑰嚮鈥滆繑鍥炩�濊繑鍥炲埌%1$s銆�
+\n
+\n鑻ラ渶瑕佸仠姝� %1$s 璇㈤棶鏉冮檺锛岃杩涘叆鈥滆缃�濓紝骞跺彇娑堚�滄鏌ユ潈闄愮姸鎬佲�濄��
+\n
+\n璇风‘淇� %1$s 宸茬粡缁欎簣浜嗗繀瑕佺殑鏉冮檺銆�</string>
+    <string name="pref_header_display">鏄剧ず</string>
+    <string name="pref_header_health">鍋ュ悍</string>
+    <string name="pref_header_time">鏃堕棿</string>
+    <string name="pref_header_workout">閿荤偧</string>
+    <string name="pref_summary_canned_replies">鍦ㄦ墜琛ㄤ笂浣跨敤棰勮鐨勬秷鎭繘琛屽洖澶�</string>
+    <string name="pref_title_screen_on_on_notifications">鏉ラ�氱煡鏃朵寒灞�</string>
+    <string name="pref_summary_screen_on_on_notifications">鏀跺埌閫氱煡鏃朵寒璧锋墜鐜睆骞�</string>
+    <string name="pref_cache_weather">缂撳瓨澶╂皵淇℃伅</string>
+    <string name="heartrate_bpm_40">40 娆�/鍒嗛挓</string>
+    <string name="heartrate_bpm_45">45 娆�/鍒嗛挓</string>
+    <string name="heartrate_bpm_50">50 娆�/鍒嗛挓</string>
+    <string name="heartrate_bpm_115">115 娆�/鍒嗛挓</string>
+    <string name="prefs_heartrate_alert_high_threshold">楂樺績鐜囪鎶ラ槇鍊�</string>
+    <string name="prefs_relaxation_reminder_description">濡傛灉鍘嬪姏鍊奸珮浜�80锛屽垯鎸姩鎵嬬幆浠ラ�氱煡鎮�</string>
+    <string name="prefs_spo2_monitoring_title">琛�姘х洃娴�</string>
+    <string name="prefs_spo2_monitoring_description">鑷姩鐩戞祴鍏ㄥぉ鐨勮姘ф按骞�</string>
+    <string name="prefs_spo2_alert_threshold">琛�姘ц鎶ラ槇鍊�</string>
+    <string name="seconds_6">6 绉�</string>
+    <string name="seconds_7">7 绉�</string>
+    <string name="seconds_8">8 绉�</string>
+    <string name="seconds_9">9 绉�</string>
+    <string name="seconds_11">11 绉�</string>
+    <string name="seconds_12">12 绉�</string>
+    <string name="seconds_13">13 绉�</string>
+    <string name="seconds_14">14 绉�</string>
+    <string name="seconds_15">15 绉�</string>
+    <string name="smart">鏅鸿兘</string>
+    <string name="mi2_dnd_always">鎬绘槸</string>
+    <string name="pref_header_heartrate_sleep">鐫$湢</string>
+    <string name="pref_header_heartrate_allday">鍏ㄥぉ鐩戞祴</string>
+    <string name="pref_header_heartrate_alerts">蹇冪巼璀︽姤</string>
+    <string name="pref_header_stress">鍘嬪姏</string>
+    <string name="pref_screen_brightness">灞忓箷浜害</string>
+    <string name="prefs_screen_timeout">灞忓箷瓒呮椂</string>
+    <string name="pref_header_calendar">鏃ュ巻</string>
+    <string name="fw_upgrade_notice_miband7">鎮ㄥ嵆灏嗗湪鎮ㄧ殑灏忕背鏅鸿兘鎵嬬幆7涓婂畨瑁� %s 鍥轰欢銆�
+\n
+\n鎮ㄧ殑鎵嬬幆灏嗕細鍦ㄥ畨瑁呬簡 .zip 鏂囦欢鍚庤嚜鍔ㄩ噸鍚��
+\n
+\n椋庨櫓鑷礋锛�</string>
+    <string name="spo2_perc_85">85%</string>
+    <string name="prefs_heartrate_alert_low_threshold">浣庡績鐜囪鎶ラ槇鍊�</string>
+    <string name="pref_cache_weather_summary">澶╂皵淇℃伅灏嗗湪搴旂敤绋嬪簭閲嶆柊鍚姩鏃剁紦瀛樸��</string>
+    <string name="pref_header_connection">杩炴帴</string>
+    <string name="prefs_always_on_display">濮嬬粓鏄剧ず</string>
+    <string name="prefs_always_on_display_summary">淇濇寔鎵嬬幆鐨勬樉绀哄睆濮嬬粓寮�鍚�</string>
+    <string name="menuitem_countdown">鍊掕鏃�</string>
+    <string name="menuitem_workout_status">閿荤偧鐘舵��</string>
+    <string name="pref_header_spo2">琛�姘�</string>
+    <string name="spo2_perc_90">90%</string>
+    <string name="spo2_off">鍏抽棴</string>
+    <string name="spo2_perc_80">80%</string>
+    <string name="devicetype_miband7">灏忕背鏅鸿兘鎵嬬幆7</string>
+    <string name="prefs_relaxation_reminder_title">鏀炬澗鎻愰啋</string>
+    <string name="menuitem_workout_history">閿荤偧鍘嗗彶</string>
+    <string name="pref_sleep_breathing_quality_monitoring">鐫$湢鍛煎惛璐ㄩ噺鐩戞祴</string>
+    <string name="menuitem_personal_activity_intelligence">涓汉娲诲姩鎯呮姤</string>
+    <string name="menuitem_female_health">濂虫�у仴搴�</string>
+    <string name="pref_title_notifications_ignore_low_priority">蹇界暐浣庝紭鍏堢骇閫氱煡</string>
+    <string name="pref_summary_notifications_ignore_low_priority">涓嶈鍙戦�佷綆浼樺厛绾у拰鏈�灏忓寲閫氱煡鍒版墜琛ㄤ笂</string>
+    <string name="devicetype_flipper_zero">Flipper zero</string>
+    <string name="activity_prefs_allow_bluetooth_intent_api">钃濈墮 Intent API</string>
+    <string name="activity_prefs_summary_allow_bluetooth_intent_api">鍏佽閫氳繃 Intent API 鎺у埗钃濈墮杩炴帴</string>
+    <string name="fw_upgrade_notice_amazfit_gts3">鎮ㄥ嵆灏嗗湪鎮ㄧ殑 璺冩垜 GTS 3 涓婂畨瑁� %s 鍥轰欢銆�
+\n
+\n鎮ㄧ殑鎵嬬幆灏嗕細鍦ㄥ畨瑁呬簡 .zip 鏂囦欢鍚庨噸鍚��
+\n
+\n椋庨櫓鑷媴锛�</string>
+    <string name="pref_title_notification_prefer_long_text">棣栭�夐暱閫氱煡鏂囨湰</string>
+    <string name="pref_summary_notification_prefer_long_text">濡傛灉鍙敤锛屽皢闀块�氱煡鏂囨湰鍙戦�佸埌璁惧</string>
+    <string name="devicetype_amazfit_gts3">璺冩垜 GTS 3</string>
+    <string name="kind_app">搴旂敤</string>
+    <string name="menuitem_unknown_app">鏈煡 (%s)</string>
+    <string name="pref_summary_overwrite_settings_on_connection">杩炴帴鍒版墜鐜椂锛岃鐩栧叾涓婄殑鎵�鏈夎缃��</string>
+    <string name="pref_title_overwrite_settings_on_connection">杩炴帴鏃惰鐩栬缃�</string>
+    <string name="dismiss">瑙i櫎</string>
+    <string name="devicetype_amazfit_gtr3">璺冩垜 GTR 3</string>
+    <string name="fw_upgrade_notice_amazfit_gtr3">鎮ㄥ嵆灏嗗畨瑁� %s 鍥轰欢鍦ㄦ偍鐨� 璺冩垜 GTR 3 涓娿��
+\n
+\n鎮ㄧ殑鎵嬭〃灏嗗湪瀹夎浜� .zip 鏂囦欢鍚庨噸鍚��
+\n
+\n椋庨櫓鑷媴锛�</string>
+    <string name="pref_summary_third_party_app_device_settings">鍏佽鍏朵粬宸插畨瑁呯殑搴旂敤閫氳繃鎰忓浘淇敼璁惧璁剧疆銆�</string>
+    <string name="pref_title_third_party_app_device_settings">鍏佽绗笁鏂瑰簲鐢ㄤ慨鏀硅缃�</string>
+    <string name="activity_type_indoor_fitness">瀹ゅ唴鍋ヨ韩</string>
+    <string name="devicetype_super_cars">澹崇墝璧涜溅</string>
+    <string name="activity_type_dance">璺宠垶</string>
+    <string name="activity_type_gymnastics">浣撴搷</string>
+    <string name="activity_type_kickboxing">璺嗘嫵閬�</string>
+    <string name="activity_type_pilates">鏅媺鎻愯繍鍔�</string>
+    <string name="activity_type_volleyball">鎺掔悆</string>
+    <string name="activity_type_street_dance">琛楄垶</string>
+    <string name="activity_type_stepper">鐖ゼ姊�</string>
+    <string name="activity_type_zumba">灏婂反鑸�</string>
+    <string name="activity_type_hiit">楂樺己搴﹂棿姝囪缁�</string>
+    <string name="activity_type_core_training">鏍稿績璁粌</string>
+    <string name="activity_type_stretching">鎷変几</string>
+    <string name="activity_type_table_tennis">涔掍箵鐞�</string>
+    <string name="activity_type_bowling">淇濋緞鐞�</string>
+    <string name="activity_type_boxing">鎷冲嚮</string>
+    <string name="activity_type_indoor_ice_skating">瀹ゅ唴婊戝啺</string>
+    <string name="watchface_setting_button_move_hands">绉诲姩鎵�</string>
+    <string name="devicetype_amazfit_gtr_lite">璺冩垜 GTR 闈掓槬鐗�</string>
+    <string name="supercars_turbo_speed_label">娑¤疆澧為��</string>
+    <string name="supercars_lights_label">鐏厜</string>
+    <string name="abstract_chart_fragment_kind_rem_sleep">蹇�熺溂鍔ㄧ潯鐪�</string>
+    <string name="pref_header_authentication">璁よ瘉</string>
+    <string name="sports_activity_confirm_delete_title">鍒犻櫎 %d 娲诲姩</string>
+    <string name="sports_activity_confirm_delete_description">纭畾瑕佸垹闄� %d 娲诲姩鍚楋紵</string>
+    <string name="supercars_lights_blinking_label">闂儊</string>
+    <string name="HeartRateZones">蹇冪巼鍖�</string>
+    <string name="hrZoneWarmUp">鐑韩杩愬姩</string>
+    <string name="hrZoneAerobic">鏈夋哀杩愬姩</string>
+    <string name="hrZoneAnaerobic">鍘屾哀</string>
+    <string name="hrZoneExtreme">鏋佺</string>
+    <string name="anaerobicTrainingEffect">鍘屾哀鏁堝簲</string>
+    <string name="currentWorkoutLoad">閿荤偧璐熻嵎</string>
+    <string name="maximumOxygenUptake">鏈�澶ф憚姘ч噺</string>
+    <string name="hrZoneNa">涓嶉�傜敤</string>
+    <string name="hrZoneFatBurn">鑴傝偑鐕冪儳</string>
+    <string name="TrainingEffect">鍩硅鏁堟灉</string>
+    <string name="aerobicTrainingEffect">鏈夋哀鏁堝簲</string>
+    <string name="pref_chart_sleep_lines_limit">鍦ㄦ粴鍔ㄥ墠鏄剧ず澶氬皯琛岀潯鐪犱俊鎭�</string>
+    <string name="elevationLoss">娴锋嫈鍑忓��</string>
+    <string name="elevationGain">娴锋嫈澧炵泭</string>
+    <string name="devicetype_sony_wh_1000xm2">绱㈠凹 WH-1000XM2</string>
+    <string name="devicetype_sony_wf_1000xm4">绱㈠凹 WF-1000XM4</string>
+    <string name="accuracy">鍑嗙‘搴�</string>
+    <string name="balanced">骞宠 </string>
+    <string name="power_saving">鐪佺數</string>
+    <string name="custom">鑷畾涔�</string>
+    <string name="single_band">鍗曢娈�</string>
+    <string name="dual_band">鍙岄娈�</string>
+    <string name="low_power_gps">浣庡姛鑰� GPS</string>
+    <string name="gps">鍏ㄧ悆瀹氫綅绯荤粺</string>
+    <string name="gps_bds">鍏ㄧ悆瀹氫綅绯荤粺 + 鍖楁枟瀹氫綅绯荤粺</string>
+    <string name="gps_gnolass">鍏ㄧ悆瀹氫綅绯荤粺 + 鏍艰鎷夋柉</string>
+    <string name="speed_first">閫熷害浼樺厛</string>
+    <string name="accuracy_first">绮惧害浼樺厛</string>
+    <string name="fw_upgrade_notice_amazfit_gtr4">鎮ㄥ嵆灏嗗畨瑁� %s 鍥轰欢鍦ㄦ偍鐨� 璺冩垜 GTR 4 涓娿��
+\n
+\n鎮ㄧ殑鎵嬭〃灏嗗湪瀹夎浜� .zip 鏂囦欢鍚庨噸鍚��
+\n
+\n椋庨櫓鑷媴锛�</string>
+    <string name="pref_gps_header">鍏ㄧ悆瀹氫綅绯荤粺</string>
+    <string name="pref_gps_mode_preset">GPS妯″紡</string>
+    <string name="pref_gps_band">GPS 棰戞</string>
+    <string name="pref_gps_combination">GPS缁勫悎</string>
+    <string name="pref_gps_satellite_search">鍗槦鎼滅储</string>
+    <string name="pref_crown_vibration">鍐犳尟</string>
+    <string name="pref_alert_tone">鎻愮ず闊�</string>
+    <string name="pref_cover_to_mute">瑕嗙洊浠ラ潤闊�</string>
+    <string name="pref_vibrate_for_alert">闇囧姩鎻愮ず</string>
+    <string name="pref_text_to_speech">鏂囧瓧杞闊�</string>
+    <string name="offline_voice_respond_turn_wrist">杞姩鎵嬭厱鏃剁殑鍙嶅簲</string>
+    <string name="pref_agps_header">AGPS</string>
+    <string name="pref_agps_expiry_reminder_enabled">AGPS杩囨湡鎻愰啋</string>
+    <string name="pref_agps_expiry_reminder_time">AGPS杩囨湡鎻愰啋鏃堕棿</string>
+    <string name="pref_workout_detection_title">閿荤偧妫�娴�</string>
+    <string name="pref_workout_detection_summary">鑷姩妫�娴嬮敾鐐兼儏鍐�</string>
+    <string name="pref_workout_detection_categories_title">閿荤偧绫诲埆</string>
+    <string name="pref_workout_detection_categories_summary">鑷姩妫�娴嬮敾鐐肩被鍒�</string>
+    <string name="pref_workout_detection_alert_title">鎻愰啋</string>
+    <string name="pref_workout_detection_alert_summary">妫�娴嬪埌閿荤偧鏃堕�氱煡</string>
+    <string name="pref_workout_detection_sensitivity">鏁忔劅搴�</string>
+    <string name="pref_sleep_mode_smart_enable_title">鏅鸿兘鍚敤</string>
+    <string name="pref_sleep_mode_smart_enable_summary">鐫$湢鏈熼棿浣╂埓鎵嬬幆鏃讹紝鑷姩鍚敤鐫$湢妯″紡</string>
+    <string name="buttons_on_left">宸﹁竟鐨勬寜閽�</string>
+    <string name="buttons_on_right">鍙宠竟鐨勬寜閽�</string>
+    <string name="prefs_weardirection">绌跨潃鏂瑰悜</string>
+    <string name="vibration_profile_default">榛樿</string>
+    <string name="pref_screen_notification_profile_schedule">鏃ョ▼</string>
+    <string name="pref_screen_notification_profile_todo_list">寰呭姙浜嬮」鍒楄〃</string>
+    <string name="prefs_always_on_display_follow_watchface">椋庢牸閬靛惊鎵嬭〃琛ㄧ洏</string>
+    <string name="activity_prefs_goal_standing_time_minutes">姣忔棩鐩爣锛氱珯绔嬫椂闂达紙鍒嗛挓锛�</string>
+    <string name="activity_prefs_goal_fat_burn_time_minutes">姣忔棩鐩爣锛氳剛鑲噧鐑ф椂闂达紙鍒嗛挓锛�</string>
+    <string name="devicetype_amazfit_gtr4">璺冩垜 GTR 4</string>
+    <string name="menuitem_bluetooth">钃濈墮</string>
+    <string name="menuitem_wifi">Wi-Fi</string>
+    <string name="pref_title_upper_button_long_press_action">涓婃柟鎸夐挳闀挎寜鍔ㄤ綔</string>
+    <string name="pref_title_lower_button_short_press_action">涓嬫柟鎸夐挳鐭寜鍔ㄤ綔</string>
+    <string name="offline_voice_response_during_screen_lighting">灞忓箷浜捣鏃跺仛鍑虹殑鍙嶅簲</string>
+    <string name="gps_galileo">鍏ㄧ悆瀹氫綅绯荤粺 + 浼藉埄鐣�</string>
+    <string name="offline_voice_respond_screen_on">褰撳睆骞曟墦寮�鏃跺仛鍑哄弽搴�</string>
+    <string name="all_satellites">鎵�鏈夊崼鏄�</string>
+    <string name="pref_header_sound_vibration">澹伴煶鍜屾尟鍔�</string>
+    <string name="seconds_25">25绉�</string>
+    <string name="activity_type_pool_swimming">娉虫睜娓告吵</string>
+    <string name="menuitem_todo">寰呭姙浜嬮」</string>
+    <string name="menuitem_theater_mode">褰遍櫌妯″紡</string>
+    <string name="menuitem_volume">闊抽噺</string>
+    <string name="menuitem_eject_water">鍠锋按</string>
+    <string name="pref_screen_auto_brightness_summary">鏍规嵁鐜鍏夎皟鑺傚睆骞曚寒搴�</string>
+    <string name="pref_header_offline_voice">绂荤嚎璇煶</string>
+    <string name="pref_sleep_mode_title">鐫$湢妯″紡</string>
+    <string name="pref_sleep_mode_sleep_screen_summary">鍦ㄧ潯鐪犳ā寮忎笅鍞ら啋灞忓箷鏃舵樉绀虹潯鐪犲睆骞曪紝浠ュ噺灏戞墦鎵�</string>
+    <string name="pref_enable_unsupported_settings_title">鍚敤涓嶆敮鎸佺殑璁剧疆</string>
+    <string name="prefs_control_center">鎺у埗涓績</string>
+    <string name="prefs_control_center_summary">閫夋嫨鎺у埗涓績涓嬫媺鍒楄〃涓殑椤圭洰</string>
+    <string name="activity_prefs_target_weight_kg">鐩爣閲嶉噺锛堝崈鍏嬶級</string>
+    <string name="menuitem_voice_memos">璇煶澶囧繕褰�</string>
+    <string name="menuitem_one_tap_measuring">涓�閿祴閲�</string>
+    <string name="menuitem_screen_always_lit">灞忓箷甯镐寒</string>
+    <string name="menuitem_lockscreen">閿佸睆</string>
+    <string name="pref_screen_auto_brightness_title">鑷姩浜害</string>
+    <string name="pref_sleep_mode_sleep_screen_title">鐫$湢灞忓箷</string>
+    <string name="prefs_always_on_display_style">椋庢牸</string>
+    <string name="menuitem_brightness">浜害</string>
+    <string name="menuitem_unsupported">[涓嶆敮鎸乚 %s</string>
+    <string name="sony_speak_to_chat_sensitivity_standard">鏍囧噯</string>
+    <string name="pref_enable_unsupported_settings_summary">杩欏皢鍏佽璁块棶鎵�鏈夊彲鐢ㄨ缃紝鍗充娇璁惧涓嶆敮鎸佷篃鏄姝ゃ�傝繖鍙兘浼氬鑷磋澶囦笉绋冲畾鍜屽穿婧冦��</string>
+    <string name="activity_type_indoor_walking">瀹ゅ唴姝ヨ</string>
+    <string name="menuitem_calendar">鏃ュ巻</string>
+    <string name="menuitem_sun_moon">澶槼 / 鏈堜寒</string>
+    <string name="menuitem_phone">鐢佃瘽</string>
+    <string name="menuitem_offline_voice">绂荤嚎璇煶</string>
+    <string name="menuitem_membership_cards">浼氬憳鍗�</string>
+    <string name="pref_huami_truncate_fetch_operation_timestamps_title">鎴柇鎻愬彇鎿嶄綔鐨勬椂闂存埑</string>
+    <string name="pref_huami_truncate_fetch_operation_timestamps_summary">灏嗚幏鍙栨搷浣滅殑鏃堕棿鎴虫埅鏂负鍒嗛挓銆傚鏋滀綘鍦ㄨ幏鍙栧緢鐭殑閿荤偧鏃堕潰涓撮棶棰橈紝鍙鐢ㄦ璁剧疆锛屼互淇濇寔鏃堕棿鎴充负绉掋��</string>
+    <string name="pref_summary_notification_media_ignores_application_list">鍦ㄥ簲鐢ㄧ▼搴忓垪琛ㄤ箣鍓嶅鐞嗗獟浣撻�氱煡銆傚鏋滆繖涓亸濂芥病鏈夎閫変腑锛屽獟浣撳簲鐢ㄩ渶瑕佸湪搴旂敤鍒楄〃涓鍏佽锛屽獟浣撴帶鍒舵墠鑳藉湪璁惧涓婂伐浣溿��</string>
+    <string name="pref_title_notification_media_ignores_application_list">濯掍綋閫氱煡蹇界暐搴旂敤鍒楄〃</string>
+    <string name="debugactivity_confirm_remove_device_preferences_title">绉婚櫎璁惧棣栭�夐」锛�</string>
+    <string name="debugactivity_confirm_remove_device_preferences">杩欏皢閲嶇疆鎵�鏈夊凡杩炴帴璁惧鐨勮澶囬閫夐」銆傛槸鍚︾‘瀹氾紵</string>
+    <string name="devicetype_amazfit_band7">璺冩垜鎵嬬幆7</string>
+    <string name="fw_upgrade_notice_amazfit_band7">鎮ㄥ嵆灏嗗畨瑁� %s 鍥轰欢鍦ㄦ偍鐨� 璺冩垜鎵嬬幆7 涓娿��
+\n
+\n鎮ㄧ殑鎵嬭〃灏嗗湪瀹夎浜� .zip 鏂囦欢鍚庨噸鍚��
+\n
+\n椋庨櫓鑷媴锛�</string>
+    <string name="world_clock_code">浠g爜</string>
+    <string name="function_enabled">鍚敤</string>
+    <string name="pref_title_touch_spotify_official_app">Spotify锛堜粎闄愬畼鏂瑰簲鐢ㄧ▼搴忥級</string>
+    <string name="devicetype_galaxybuds_2">Galaxy Buds2</string>
+    <string name="prefs_touch_lock_buds2">瑙︽懜鎺у埗</string>
+    <string name="pref_title_touch_spotify_galaxy_app">Spotify锛堜粎闄� Galaxy Wearable 搴旂敤绋嬪簭锛�</string>
+    <string name="fw_upgrade_notice_amazfit_gts4">鎮ㄥ嵆灏嗗湪 璺冩垜 GTS 4涓婂畨瑁� %s 鍥轰欢銆�
+\n
+\n瀹夎瀹� .zip鏂囦欢鍚庯紝鎮ㄧ殑鎵嬬幆灏嗛噸鏂板惎鍔ㄣ��
+\n
+\n椋庨櫓鑷媴锛�</string>
+    <string name="devicetype_amazfit_gts4">璺冩垜 GTS 4</string>
+    <string name="fw_upgrade_notice_amazfit_gts4_mini">鎮ㄥ嵆灏嗗湪鎮ㄧ殑 璺冩垜 GTS 4 Mini 涓婂畨瑁� %s 鍥轰欢銆�
+\n
+\n鎮ㄧ殑鎵嬬幆灏嗕細鍦ㄥ畨瑁呬簡 .zip 鏂囦欢鍚庤嚜鍔ㄩ噸鍚��
+\n
+\n椋庨櫓鑷媴锛�</string>
+    <string name="devicetype_amazfit_gts4_mini">璺冩垜 GTS 4 Mini</string>
+    <string name="devicetype_sony_linkbuds_s">绱㈠凹 LinkBuds S</string>
+    <string name="pref_header_intent_api">鎰忓浘鎺ュ彛</string>
+    <string name="intent_api_allow_activity_sync_title">鍏佽娲诲姩鍚屾瑙﹀彂鍣�</string>
+    <string name="intent_api_allow_activity_sync_summary">鍏佽閫氳繃鎰忓浘 API 瑙﹀彂娲诲姩鍚屾</string>
+    <string name="intent_api_allow_trigger_export_summary">鍏佽閫氳繃鎰忓浘 API 瑙﹀彂鏁版嵁搴撳鍑�</string>
+    <string name="intent_api_broadcast_export_title">鏁版嵁搴撳鍑烘椂骞挎挱</string>
+    <string name="intent_api_allow_trigger_export_title">鍏佽鏁版嵁搴撳鍑�</string>
+    <string name="intent_api_broadcast_export_summary">鏁版嵁搴撳鍑哄畬鎴愬悗骞挎挱鎰忓浘</string>
+    <string name="sony_ambient_sound_control_button_modes">鐜澹版帶鍒舵寜閽ā寮�</string>
+    <string name="sony_ambient_sound_control_button_mode_nc_as_off">闄嶅櫔锛岀幆澧冮煶锛屽叧闂�</string>
+    <string name="sony_ambient_sound_control_button_mode_nc_off">闄嶅櫔锛屽叧闂�</string>
+    <string name="sony_ambient_sound_control_button_mode_as_off">鐜闊筹紝鍏抽棴</string>
+    <string name="sony_quick_access_double_tap">蹇�熻闂紙鍙屽嚮锛�</string>
+    <string name="sony_quick_access_triple_tap">蹇�熻闂紙涓夎繛鍑伙級</string>
+    <string name="sony_ambient_sound_control_button_mode_nc_as">闄嶅櫔锛岀幆澧冮煶</string>
+    <string name="menuitem_wechat_pay">寰俊鏀粯</string>
+    <string name="pref_agps_expire_time">AGPS 杩囨湡鏃堕棿</string>
+    <string name="pref_agps_update_time">AGPS 鏇存柊鏃堕棿</string>
+    <string name="appmanager_app_download">涓嬭浇鍒扮紦瀛�</string>
+    <string name="appmanager_download_started">寮�濮嬩笅杞藉簲鐢ㄧ▼搴�</string>
+    <string name="appmanager_downloaded_to_cache">宸蹭笅杞� %s 鍒扮紦瀛�</string>
+    <string name="appmanager_download_app_error">涓嬭浇搴旂敤绋嬪簭鏃跺嚭閿�</string>
+    <string name="appmanager_watchface_activate">鍚敤</string>
+    <string name="appmanager_app_start">寮�濮�</string>
+    <string name="devicetype_asteroidos">AsteroidOS</string>
+    <string name="fw_upgrade_notice_amazfit_trex2">鎮ㄥ嵆灏嗗湪鎮ㄧ殑 璺冩垜 T-Rex 2 涓婂畨瑁� %s 鍥轰欢銆�
+\n
+\n鎮ㄧ殑鎵嬬幆灏嗕細鍦ㄥ畨瑁呬簡 .zip 鏂囦欢鍚庨噸鍚��
+\n
+\n椋庨櫓鑷媴锛�</string>
+    <string name="devicetype_amazfit_trex_2">璺冩垜 T-Rex 2</string>
+    <string name="pref_workout_detection_enabled_summary">瀵规閿荤偧鍚敤鑷姩妫�娴�</string>
+    <string name="pref_workout_detection_enabled">妫�娴嬪凡寮�鍚�</string>
+    <string name="pref_workout_detection_ask_first">鍏堣闂垜</string>
+    <string name="pref_workout_detection_ask_first_summary">妫�娴嬪埌璇ラ敾鐐兼椂锛岄渶瑕佸湪鎵嬭〃涓婅繘琛岀‘璁�</string>
+    <string name="pref_workout_detection_time">妫�娴嬪墠鍑犲垎閽熺殑娲诲姩</string>
+    <string name="pref_workout_detection_time_summary">閿荤偧蹇呴』鎸佺画澶氬皯鍒嗛挓鎵嶈兘妫�娴嬪埌瀹�</string>
+    <string name="activity_type_rowing">璧涜墖</string>
+    <string name="georgian">涔旀不浜�</string>
+    <string name="pref_title_lock_unlock">閿佸畾</string>
+    <string name="devicetype_soflow_s06">SoFlow SO6</string>
+    <string name="devicetype_galaxybuds_2_pro">Galaxy Buds 2 Pro</string>
+    <string name="pref_workout_keep_screen_on_title">鍦ㄩ敾鐐兼椂璁╁睆骞曚繚鎸佸紑鍚�</string>
+    <string name="pref_camera_remote_title">鐩告満閬ユ帶</string>
+    <string name="pref_camera_remote_summary">鍏佽鎵嬭〃浣跨敤鎵嬫満鐩告満閬ユ帶鎷嶇収</string>
+    <string name="pref_workout_keep_screen_on_summary">灞忓箷鍦ㄩ敾鐐兼湡闂村皢淇濇寔寮�鍚紝骞朵笖浜害灏嗚璋冩暣浠ユ寔缁樉绀哄疄鏃剁殑閿荤偧鏁版嵁</string>
+    <string name="wifi_ssid">SSID</string>
+    <string name="stop">鍋滄</string>
+    <string name="status">鐘舵��</string>
+    <string name="wifi_hotspot_stop_summary">鍦ㄦ墜琛ㄤ笂鍋滄 Wi-Fi 鐑偣</string>
+    <string name="wifi_hotspot_summary">鍦ㄦ墜琛ㄤ笂鎺у埗 Wi-Fi 鐑偣</string>
+    <string name="ftp_server">FTP 鏈嶅姟鍣�</string>
+    <string name="ftp_server_summary">鍦ㄦ墜琛ㄤ笂鎺у埗 FTP 鏈嶅姟鍣�</string>
+    <string name="ftp_server_start_summary">鍦ㄦ墜琛ㄤ笂鍚姩 FTP 鏈嶅姟鍣�</string>
+    <string name="ftp_server_stop_summary">鍦ㄦ墜琛ㄤ笂鍋滄 FTP 鏈嶅姟鍣�</string>
+    <string name="wifi_hotspot_configuration">Wi-Fi 鐑偣閰嶇疆</string>
+    <string name="wifi_hotspot">Wi-Fi 鐑偣</string>
+    <string name="wifi_hotspot_status">Wi-Fi 鐑偣鐘舵��</string>
+    <string name="wifi_hotspot_start_summary">鍦ㄦ墜琛ㄤ笂鍚姩 Wi-Fi 鐑偣</string>
+    <string name="address">鍦板潃</string>
+    <string name="ftp_server_root_dir">鏍圭洰褰�</string>
+    <string name="username">鐢ㄦ埛鍚�</string>
+    <string name="ftp_server_configuration">FTP 鏈嶅姟鍣ㄩ厤缃�</string>
+    <string name="ftp_server_status">FTP 鏈嶅姟鍣ㄧ姸鎬�</string>
+    <string name="croatian">鍏嬬綏鍦颁簹璇�</string>
+    <string name="sony_speak_to_chat_summary">寮�濮嬭璇濇椂鑷姩鍏抽棴闄嶅櫔銆�</string>
+    <string name="yesterdays_activity">鏄ㄥぉ鐨勬椿鍔�</string>
+    <string name="pref_morning_updates_summary">姣忓ぉ鏃╀笂鏄剧ず鏇存柊</string>
+    <string name="pref_morning_updates_categories_summary">姣忓ぉ鏃╀笂瑕佹樉绀虹殑鍒嗙被鍒楄〃</string>
+    <string name="pref_morning_updates_title">鏃╀笂鏇存柊</string>
+    <string name="pref_morning_updates_categories_title">鏃╀笂鏇存柊鍒嗙被</string>
+    <string name="bluetooth_calls">钃濈墮閫氳瘽</string>
+    <string name="bluetooth_calls_pairing">钃濈墮閫氳瘽閰嶅</string>
+    <string name="bluetooth_calls_settings">钃濈墮閫氳瘽璁剧疆</string>
+    <string name="pref_display_caller_title">鏄剧ず鑱旂郴浜轰俊鎭�</string>
+    <string name="pref_display_caller_summary">鏄剧ず鏉ョ數鑰呯殑鐢佃瘽鍙风爜鎴栧鍚�</string>
+    <string name="pref_pair_bluetooth_calls_title">閰嶅浠ヨ繘琛岃摑鐗欓�氳瘽</string>
+    <string name="pref_pair_bluetooth_calls_help_title">濡備綍鎺ュ惉钃濈墮鐢佃瘽</string>
+    <string name="pref_pair_bluetooth_calls_help_1">1. 鐐瑰嚮涓嬮潰鐨勬寜閽紑濮嬮厤瀵硅繃绋嬨��</string>
+    <string name="pref_pair_bluetooth_calls_help_2">2. 杞埌鎵嬫満鐨勮摑鐗欒缃紝骞朵笌灏嗘樉绀虹殑鏂拌澶囬厤瀵癸紙涓庢偍褰撳墠鎵嬭〃鐨勫悕绉扮浉浼硷紝浣嗗甫鏈夊悗缂�锛屼緥濡� Amazfit GTR 4 - AFC8 锛�</string>
+    <string name="pref_pair_bluetooth_calls_help_3">3. 鍚敤涓嬫柟鐨勮摑鐗欓�氳瘽璁剧疆銆�</string>
+    <string name="pref_summary_receive_calls_watch">鐩存帴鍦ㄦ墜琛ㄤ笂鎺ユ墦鐢佃瘽</string>
+    <string name="pref_pair_bluetooth_calls_help_warning">璀﹀憡锛氬鏋滄偍鍦ㄦ湭涓庣浜屼釜瀹炰緥閰嶅鐨勬儏鍐典笅鍚敤钃濈墮鍛煎彨锛屾潵鐢甸�氱煡鍙兘鏃犳硶鎸夐鏈熷伐浣溿��</string>
+    <string name="pref_pair_bluetooth_calls_summary">鍗曞嚮姝ゅ寮�濮嬮厤瀵硅繃绋�</string>
+    <string name="pref_pair_bluetooth_calls_help_summary">涓轰簡鎺ュ惉钃濈墮鐢佃瘽锛屾偍闇�瑕佸皢鎵嬫満涓庢墜琛ㄧ殑绗簩涓疄渚嬮厤瀵广��</string>
+    <string name="pref_switch_controls_anc_ambient_off">闄嶅櫔 鈫愨啋 鐜闊� 鈫愨啋 鍏抽棴</string>
+    <string name="sony_button_mode_help_title">鎸夐挳妯″紡 - 甯姪</string>
+    <string name="long_press">闀挎寜</string>
+    <string name="pref_summary_notification_cache_while_disconnected">褰撹澶囪秴鍑鸿寖鍥村悗閲嶆柊杩炴帴鏃跺彂閫侀敊杩囩殑閫氱煡</string>
+    <string name="single_tap">鍗曞嚮</string>
+    <string name="sony_button_mode_help_summary">姣忎釜鎸夐挳妯″紡鐨勮鏄�</string>
+    <string name="triple_tap">涓夎繛鍑�</string>
+    <string name="double_tap">鍙屽嚮</string>
+    <string name="continue_pressing">鎸佺画鎸変笅</string>
+    <string name="quick_attention">蹇�熷叧娉�</string>
+    <string name="pref_title_notification_cache_while_disconnected">瓒呭嚭鑼冨洿澶栨椂鐨勭紦瀛�</string>
+    <string name="menuitem_cards">鍗″寘</string>
+    <string name="menuitem_mi_ai">灏忕埍鍚屽</string>
+    <string name="prefs_shortcut_cards">蹇嵎鏂瑰紡鍗$墖</string>
+    <string name="prefs_shortcut_cards_summary">鍦ㄨ〃鐩樹笂鍚戝彸婊戝姩鏃剁湅鍒扮殑蹇嵎鏂瑰紡鍗$墖銆傚綋搴旂敤绋嬪簭杩愯鏃讹紝鑷姩鐢熸垚鐨勫崱鐗囦笉鍙楁璁剧疆鐨勫奖鍝嶃��</string>
+    <string name="menuitem_forecast">棰勬姤</string>
+    <string name="menuitem_last_workout">鏈�鍚庝竴娆¢敾鐐�</string>
+    <string name="menuitem_total_workout">鎬婚敾鐐�</string>
+    <string name="menuitem_vo2_max">鏈�澶ф憚姘ч噺</string>
+    <string name="menuitem_recommendation">鎺ㄨ崘</string>
+    <string name="zepp_os_watchface_rotating_earth">鏃嬭浆鍦扮悆</string>
+    <string name="zepp_os_watchface_superposition">鍙犲姞</string>
+    <string name="zepp_os_watchface_simplicity_data">绠�鍖栨暟鎹�</string>
+    <string name="zepp_os_watchface_red_fantasy">绾㈣壊骞绘兂</string>
+    <string name="zepp_os_watchface_multiple_data">澶氫釜鏁版嵁</string>
+    <string name="zepp_os_watchface_rush">蹇啿</string>
+    <string name="zepp_os_watchface_minimalist">鏋佺畝涓讳箟</string>
+    <string name="zepp_os_watchface_vibrant">鍏呮弧娲诲姏</string>
+    <string name="zepp_os_watchface_business_style">鍟嗗姟椋庢牸</string>
+    <string name="zepp_os_watchface_emerald_moonlight">缈$繝鏈堝厜</string>
+    <string name="menuitem_aqi">绌烘皵璐ㄩ噺鎸囨暟</string>
+    <string name="fw_upgrade_notice_amazfit_gtr3_pro">鎮ㄥ嵆灏嗗湪璺冩垜 GTR 3 Pro (Amazfit GTR 3 Pro)涓婂畨瑁� %s 鐗堟湰鐨勫浐浠�
+\n
+\n鎮ㄧ殑鎵嬬幆灏嗕細鍦ㄥ畨瑁呬簡 .fw 鏂囦欢鍚庨噸鍚�
+\n
+\n椋庨櫓鑷媴锛�</string>
+    <string name="devicetype_amazfit_gtr3_pro">璺冩垜 GTR 3 Pro</string>
+    <string name="devicetype_sony_wh_1000xm5">Sony WH-1000XM5</string>
+    <string name="fossil_hr_confirm_connection">璇峰湪鎮ㄧ殑鎵嬭〃涓婄‘璁�</string>
+    <string name="fossil_hr_connection_not_confirmed">鏈湪鎵嬭〃涓婄‘璁よ繛鎺ワ紝浣跨敤鏈獙璇佹ā寮�</string>
+    <string name="fossil_hr_pairing_successful">涓庢墜琛ㄩ厤瀵规垚鍔�</string>
+    <string name="fossil_hr_pairing_failed">涓庢墜琛ㄩ厤瀵瑰け璐�</string>
+    <string name="fossil_hr_confirmation_skipped">璺宠繃璁惧涓婄殑纭</string>
+    <string name="fossil_hr_confirmation_timeout">纭瓒呮椂锛屾鍦ㄧ户缁�</string>
+    <string name="debug_companion_pair_current">灏嗗綋鍓嶈澶囬厤瀵逛负閰嶅璁惧</string>
+    <string name="debug_companion_show_associated">鏄剧ず鍏宠仈鐨勯厤濂楄澶�</string>
+    <string name="gpx_route_upload_complete">GPX璺緞涓婁紶鎴愬姛</string>
+    <string name="gpx_route_upload_in_progress">姝e湪涓婁紶GPX璺緞</string>
+    <string name="gpx_route_upload_failed">GPX 璺緞涓婁紶澶辫触</string>
+    <string name="kind_gpx_route">GPX璺緞</string>
+    <string name="activity_error_share_failed">鍏变韩鏂囦欢澶辫触銆�</string>
+    <string name="activity_detail_share_raw_summary">鍒嗕韩鍘熷鎽樿</string>
+    <string name="activity_detail_share_raw_details">鍒嗕韩鍘熷璇︽儏</string>
+    <string name="dev_tools">寮�鍙戝伐鍏�</string>
+    <string name="contact_phone_number">鐢佃瘽鍙风爜</string>
+    <string name="contact_missing_number">鑱旂郴浜虹數璇濆彿鐮佷负绌�</string>
+    <string name="pref_contacts_title">鑱旂郴浜�</string>
+    <string name="title_activity_set_contacts">閰嶇疆鑱旂郴浜�</string>
+    <string name="contact_delete_confirm_title">鍒犻櫎鑱旂郴浜�</string>
+    <string name="contact_delete_confirm_description">鎮ㄧ‘瀹氳鍒犻櫎 鈥�%1$s鈥� 鍚楋紵</string>
+    <string name="title_activity_contact_details">鑱旂郴浜鸿鎯�</string>
+    <string name="pref_contacts_summary">鍦ㄦ墜琛ㄤ笂閰嶇疆鑱旂郴浜�</string>
+    <string name="activity_detail_share_gps_label">鍒嗕韩 GPS 杞ㄨ抗</string>
+    <string name="contact_no_free_slots_description">姝よ澶囨病鏈夌┖闂茬殑鑱旂郴浜轰綅缃簡锛堟�讳綅缃暟锛�%1$s锛�</string>
+    <string name="contact_name">鍚嶇О</string>
+    <string name="contact_missing_name">鑱旂郴浜哄鍚嶄负绌�</string>
+    <string name="pref_header_navigation">瀵艰埅</string>
+    <string name="pref_summary_navigation_forward">灏嗗鑸簲鐢ㄧ▼搴忕殑鎸囦护杞彂鍒版墜琛�</string>
+    <string name="pref_title_navigation_forward">鍙戦�佸鑸埌鎵嬭〃</string>
+    <string name="intent_api_allow_debug_commands_summary">鍏佽閫氳繃鎰忓浘 API 瑙﹀彂璋冭瘯鑿滃崟鍛戒护</string>
+    <string name="intent_api_allow_debug_commands_title">鍏佽璋冭瘯鍛戒护</string>
+    <string name="voice_service">璇煶鏈嶅姟</string>
+    <string name="voice_service_package_title">璇煶鏈嶅姟鍖�</string>
+    <string name="voice_service_package_summary">鍖呭惈澶勭悊璇煶鍛戒护鏈嶅姟鐨勫簲鐢ㄧ▼搴�</string>
+    <string name="voice_service_class_title">璇煶鏈嶅姟绫诲埆</string>
+    <string name="voice_service_class_summary">瀹屾暣鐨勬湇鍔¤矾寰勫鐞嗚闊冲懡浠�</string>
+    <string name="pref_app_logs_title">搴旂敤鏃ュ織</string>
+    <string name="pref_app_logs_summary">鍦ㄦ墜琛ㄥ簲鐢ㄧ▼搴忛噷鍚敤鏃ュ織</string>
+    <string name="pref_app_logs_start_summary">鍦ㄦ墜琛ㄥ簲鐢ㄩ噷寮�濮嬫棩蹇�</string>
+    <string name="pref_app_logs_stop_summary">鍦ㄦ墜琛ㄥ簲鐢ㄩ噷鍋滄鏃ュ織</string>
+    <string name="fwapp_install_device_not_supported">鏃犳硶瀹夎鏂囦欢锛岃澶囦笉琚敮鎸併��</string>
+    <string name="screenshot_taken">宸叉埅鍥�</string>
+    <string name="share_screenshot">鍒嗕韩灞忓箷鎴浘</string>
+    <string name="stress_high">楂�</string>
+    <string name="charts_legend_stress_average">鍘嬪姏骞冲潎鍊�</string>
+    <string name="stress_mild">娓╁拰</string>
+    <string name="stress_moderate">缂撳拰</string>
+    <string name="stress_relaxed">鏀炬澗</string>
+    <string name="watchface_widget_type_uv_index">绱绾挎寚鏁�</string>
+    <string name="pref_summary_banglejs_phone_gps_update_interval">GPS 浣嶇疆鏇存柊棰戠巼鐨勯棿闅旓紙浠ユ绉掍负鍗曚綅锛�</string>
+    <string name="pref_title_banglejs_phone_gps_update_interval">GPS 鏁版嵁鏇存柊闂撮殧</string>
+    <string name="title">鏍囬</string>
+    <string name="status_icon">鐘舵�佸浘鏍�</string>
+    <string name="preview_image">鍥剧墖棰勮</string>
+    <string name="description">鎻忚堪</string>
+    <string name="changelog_full_title">鍙樻洿鏃ュ織</string>
+    <string name="changelog_show_full">鏇村鈥�</string>
+    <string name="changelog_ok_button">濂�</string>
+    <string name="changelog_title">鏂板姛鑳�</string>
+    <string name="pref_summary_banglejs_phone_gps_network_only">浠呬娇鐢ㄧ綉缁滄彁渚涘晢鏉ョ‘瀹氫綅缃�傝繖闄嶄綆浜嗗姛鑰楋紝浣嗙壓鐗蹭簡绮惧害銆傞渶瑕侀噸鏂拌繛鎺ャ��</string>
+    <string name="pref_title_banglejs_phone_gps_enbale">浣跨敤鎵嬫満鐨凣PS鏁版嵁</string>
+    <string name="pref_summary_banglejs_phone_gps_enbale">浣跨敤鎵嬫満鐨凣PS鏁版嵁瑕嗙洊鎵嬬幆璁惧鐨凣PS鏁版嵁</string>
+    <string name="action_changelog">鏇存敼鏃ュ織</string>
+    <string name="pref_title_banglejs_phone_gps_network_only">浠呬娇鐢ㄧ綉缁滄潵纭畾浣嶇疆</string>
+    <string name="pai_chart_per_month">姣忔湀 PAI</string>
+    <string name="num_min">%d 鍒嗛挓</string>
+    <string name="pai_chart_per_week">姣忓懆 PAI</string>
+    <string name="pai_plus_num">+%d</string>
+    <string name="pai_total">鎬昏</string>
+    <string name="pai_day">姣忔棩澧為暱</string>
+    <string name="loyalty_cards_catima_not_installed">闇�瑕� Catima 鏉ョ鐞嗕細鍛樺崱</string>
+    <string name="loyalty_cards_catima_package">Catima 鍖呭悕</string>
+    <string name="loyalty_cards_install">瀹夎 Catima</string>
+    <string name="loyalty_cards_sync_groups_only">浠呭悓姝ョ壒瀹氱殑鍒嗙粍</string>
+    <string name="loyalty_cards_sync_groups">瑕佸悓姝ョ殑缇ょ粍</string>
+    <string name="loyalty_cards_sync_archived">鍚屾宸插瓨妗g殑鍗$墖</string>
+    <string name="loyalty_cards_install_catima_fail">鏃犳硶鎵撳紑搴旂敤绋嬪簭鍟嗗簵鏉ュ畨瑁� Catima</string>
+    <string name="loyalty_cards_sync_starred">浠呭悓姝ュ姞鏄熸爣鐨勫崱鐗�</string>
+    <string name="loyalty_cards_sync_title">鍚屾浼氬憳鍗�</string>
+    <string name="loyalty_cards_sync_summary">鐐瑰嚮鍗冲彲灏嗗崱鐗囧悓姝ュ埌鎵嬭〃</string>
+    <string name="loyalty_cards_catima_not_compatible">瀹夎鐨� Catima 鐗堟湰涓� Gadgetbridge 涓嶅吋瀹广�傝灏� Catima 鍜� Gadgetbridge 鏇存柊鍒版渶鏂扮増鏈��</string>
+    <string name="loyalty_cards_sync_options">鍚屾閫夐」</string>
+    <string name="loyalty_cards_sync">鍚屾</string>
+    <string name="loyalty_cards_catima">Catima</string>
+    <string name="loyalty_cards_catima_permissions_title">缂哄皯鏉冮檺</string>
+    <string name="loyalty_cards_open_catima">鎵撳紑 Catima</string>
+    <string name="loyalty_cards_catima_permissions_summary">Gadgetbridge 闇�瑕� Catima 鍗″寘鐨勮鍙栨潈闄愭墠鑳藉悓姝ュ畠浠�傜偣鍑绘鎸夐挳鍗冲彲杩涜鎺堟潈銆�</string>
+    <string name="loyalty_cards_syncing">姝e湪灏� %d 寮犱細鍛樺崱鍚屾鍒拌澶�</string>
+    <string name="loyalty_cards">浼氬憳鍗�</string>
+    <string name="pref_title_mb_intents">骞挎挱濯掍綋鎸夐挳鐩存帴骞挎挱鎰忓浘</string>
+    <string name="pref_summary_mb_intents">濡傛灉璁惧濯掍綋鎺у埗瀵规煇浜涘簲鐢ㄧ▼搴忎笉璧蜂綔鐢紝鍒欏彲浠ュ惎鐢�</string>
+    <string name="devicetype_bohemic_smart_bracelet">Bohemic 鏅鸿兘鎵嬬幆</string>
+    <string name="devicetype_vivomove_hr">浣虫槑 Vivomove HR</string>
+    <string name="devicetype_casiogwb5600">鍗¤タ娆� GW-B5600</string>
+    <string name="devicetype_casiogmwb5000">鍗¤タ娆� GMW-B5000</string>
+    <string name="busy_task_fetch_sports_summaries">姝e湪鑾峰彇杩愬姩鎽樿</string>
+    <string name="busy_task_fetch_sports_details">姝e湪鑾峰彇杩愬姩璇︽儏</string>
+    <string name="busy_task_fetch_debug_logs">姝e湪鑾峰彇璋冭瘯鏃ュ織</string>
+    <string name="busy_task_fetch_stress_data">姝e湪鑾峰彇鍘嬪姏鏁版嵁</string>
+    <string name="busy_task_fetch_pai_data">姝e湪鑾峰彇 PAI 鏁版嵁</string>
+    <string name="busy_task_fetch_spo2_data">姝e湪鑾峰彇 SpO2 鏁版嵁</string>
+    <string name="busy_task_fetch_hr_data">姝e湪鑾峰彇蹇冪巼鏁版嵁</string>
+    <string name="busy_task_fetch_sleep_respiratory_rate_data">姝e湪鑾峰彇鐫$湢鍛煎惛棰戠巼鏁版嵁</string>
+    <string name="activity_prefs_goals">鐩爣</string>
+    <string name="error_no_bluetooth_scan">蹇呴』鎺堜簣骞跺惎鐢ㄨ摑鐗欐壂鎻忚闂潈闄愭墠鑳戒娇鎵弿姝e父鐨勫伐浣�</string>
+    <string name="permission_request">%1$s 鍏佽鎮ㄥ皢娑堟伅鍜屽叾浠栨暟鎹粠 Android 鍙戦�佸埌鎮ㄧ殑璁惧銆� 涓烘锛屽畠闇�瑕佽闂鏁版嵁鐨勬潈闄愶紝濡傛灉娌℃湁璇ユ潈闄愶紝瀹冨彲鑳芥棤娉曟甯歌繍琛屻��
+\n
+\n鐜板湪鎮ㄥ皢鐪嬪埌涓�浜涜姹傝繖浜涙潈闄愮殑 Android 瀵硅瘽妗嗐��
+\n
+\n璇风偣鍑烩��%2$s鈥濈户缁��</string>
+    <string name="error_no_bluetooth_connect">蹇呴』鎺堜簣骞跺惎鐢ㄨ摑鐗欒繛鎺ヨ闂潈闄愭墠鑳戒娇鎵弿姝e父鐨勫伐浣�</string>
+    <string name="activity_type_surfing">鍐叉氮</string>
+    <string name="activity_type_kitesurfing">椋庣瓭鍐叉氮</string>
+    <string name="activity_type_windsurfing">椋庡竼鍐叉氮</string>
+    <string name="activity_type_football">瓒崇悆</string>
+    <string name="activity_type_dancing">璺宠垶</string>
+    <string name="activity_type_skiing">婊戦洩</string>
+    <string name="activity_type_snowboarding">鍗曟澘婊戦洩</string>
+    <string name="withings_steel_hr">Withings Steel HR</string>
+    <string name="activity_type_icehockey">鍐扮悆</string>
+    <string name="activity_type_other">鍏朵粬</string>
+    <string name="withings_calibration_text_activity_target">鏈�鍚庡皢娲诲姩琛ㄧ洏瀵归綈鍒�100锛呫�傝娉ㄦ剰锛岃繖鍙椿鍔ㄨ〃鐩樺彧鑳介『鏃堕拡鏃嬭浆銆�</string>
+    <string name="withings_bt_calibration_next">涔嬪悗</string>
+    <string name="activity_type_rugby">姗勬鐞�</string>
+    <string name="activity_type_baseball">妫掔悆</string>
+    <string name="activity_type_handball">鎵嬬悆</string>
+    <string name="activity_type_tennis">缃戠悆</string>
+    <string name="activity_type_squash">澹佺悆</string>
+    <string name="activity_type_weightlifting">涓鹃噸</string>
+    <string name="activity_type_riding">楠戦┈</string>
+    <string name="activity_type_hockey">鏇叉鐞�</string>
+    <string name="activity_type_iceskating">婧滃啺</string>
+    <string name="activity_type_golf">鎵撻珮灏斿か鐞�</string>
+    <string name="withings_calibration_text_hours">璇蜂娇鐢ㄤ笅闈㈢殑琛ㄧ洏灏嗘椂閽堝鍑� 12銆�</string>
+    <string name="withings_calibration_text_minutes">鐜板湪浣跨敤琛ㄧ洏灏嗗垎閽堜笌 12 瀵归綈銆�</string>
+    <string name="withings_bt_calibration_previous">涔嬪墠</string>
+    <string name="pref_theme_dynamic">鍔ㄦ�佽壊褰�</string>
+    <string name="pref_canned_message">淇℃伅</string>
+    <string name="drag_handle">鎷栧姩鎶婃墜</string>
+    <string name="find_my_phone_found_it">宸叉壘鍒�</string>
+    <string name="preferences_miband_1_2_settings" tools:ignore="TypographyFractions">灏忕背鎵嬬幆 1銆�2 璁剧疆</string>
+    <string name="preferences_miband_1_2_warning">璀﹀憡锛氳繖浜涘亸濂借缃粎閫傜敤浜庡皬绫虫墜鐜� 1 鍜� 2銆�</string>
+    <string name="pref_activity_full_sync_trigger_title">瀹屾暣鍚屾</string>
+    <string name="pref_activity_full_sync_trigger_warning">杩欏皢瑙﹀彂璁惧涓墍鏈夋椿鍔ㄦ暟鎹殑瀹屾暣鍚屾銆� 鍙兘闇�瑕佸嚑鍒嗛挓鎵嶈兘瀹屾垚銆�</string>
+    <string name="pref_activity_full_sync_trigger_summary">瑙﹀彂鎵�鏈夋椿鍔ㄦ暟鎹殑瀹屾暣鍚屾</string>
+    <string name="pref_show_changelog_summary">Gadgetbridge 鏇存柊鍚庢樉绀鸿嚜涓婁竴鐗堟湰浠ユ潵鐨勬洿鏂版棩蹇�</string>
+    <string name="pref_show_changelog">鍚姩鏃舵樉绀烘洿鏂版棩蹇�</string>
+    <string name="pref_theme_dynamic_colors_not_available_warning">鎮ㄧ殑璁惧涓嶆敮鎸佸姩鎬佽壊褰╋紝浠� Android 12+ 鏀寔姝ゅ姛鑳姐�侴adgetbridge 灏嗕娇鐢ㄩ粯璁ょ殑 Material 3 鑹插僵銆�</string>
+    <string name="pref_theme_dynamic_colors_explanation">娉ㄦ剰锛氬浜庡姩鎬佽壊褰╀富棰橈紝鎮ㄩ渶瑕佸湪 Android 12+ 澶栬璁剧疆涓惎鐢ㄥ绾搁鑹叉垨璋冭壊鏉裤�� 濡傛灉涓嶈繖鏍峰仛锛孏adgetbridge 灏嗕娇鐢ㄩ粯璁ょ殑 Material 3 鑹插僵銆�</string>
+    <string name="menuitem_zepp_coach">Zepp Coach</string>
+    <string name="fw_upgrade_notice_amazfitbip3pro">鎮ㄥ嵆灏嗗湪鎮ㄧ殑璺冩垜 Bip 3 Pro 涓婂畨瑁呭畨瑁� %s 鍥轰欢銆�
+\n
+\n璇风‘淇濆厛瀹夎 .fw 鏂囦欢锛屽啀瀹夎 .res 鏂囦欢銆傛偍鐨勬墜琛ㄥ皢浼氬湪瀹夎瀹� .fw 鏂囦欢鍚庤嚜鍔ㄩ噸鍚��
+\n
+\n娉ㄦ剰锛氬鏋� .res 鏂囦欢鍜屼箣鍓嶇殑鐗堟湰鐩稿悓锛屾偍鏃犻渶鍐嶅畨瑁呬竴娆°��
+\n
+\n椋庨櫓鑷媴锛�</string>
+    <string name="devicetype_amazfit_bip3_pro">璺冩垜 Bip 3 Pro</string>
+    <string name="devicetype_amazfit_cheetah_pro">璺冩垜 Cheetah Pro</string>
+    <string name="fw_upgrade_notice_amazfit_cheetah_pro">鎮ㄥ嵆灏嗗湪鎮ㄧ殑 璺冩垜 Cheetah Pro 涓婂畨瑁� %s 鍥轰欢銆�
+\n
+\n鎮ㄧ殑鎵嬬幆灏嗗湪瀹夎瀹� .zip 鏂囦欢鍚庤嚜鍔ㄩ噸鍚��
+\n
+\n椋庨櫓鑷媴锛�</string>
+    <string name="info_menu_structure_set">Gadgetbridge 涓殑鑿滃崟 JSON 缁撴瀯璁剧疆</string>
+    <string name="error_invalid_menu_structure">鏃犳晥鐨� JSON 鑿滃崟缁撴瀯</string>
+    <string name="fw_upgrade_notice_zepp_os">鎮ㄥ嵆灏嗗湪鎮ㄧ殑 %s 涓婂畨瑁� %s 鐨勫浐浠躲��
+\n
+\n鎮ㄧ殑鎵嬭〃灏嗕細鍦ㄥ畨瑁呬簡 .zip 鏂囦欢鍚庨噸鍚��
+\n
+\n椋庨櫓鑷媴锛�</string>
+    <string name="devicetype_amazfit_cheetah_square">璺冩垜 Cheetah (鏂瑰舰)</string>
+    <string name="devicetype_amazfit_trex_ultra">璺冩垜 T-Rex Ultra</string>
+    <string name="devicetype_amazfit_gtr_mini">璺冩垜 GTR Mini</string>
+    <string name="button_open_menu_companion">鎵撳紑閰嶅搴旂敤鐨勮彍鍗�</string>
+    <string name="devicetype_amazfit_cheetah_round">璺冩垜 Cheetah (鍦嗗舰)</string>
+    <string name="devicetype_amazfit_bip5">璺冩垜 Bip 5</string>
+    <string name="devicetype_amazfit_falcon">璺冩垜 Falcon</string>
+    <string name="button_reset_menu_structure">閲嶇疆鑿滃崟缁撴瀯</string>
+    <string name="info_fossil_rebuild_watchface_custom_menu">璇烽噸寤烘偍鐨勮〃鐩樹互杩涜鑷畾涔夎彍鍗�</string>
+    <string name="device_experimental">璇曢獙鎬�</string>
+    <string name="info_menu_structure_removed">鑿滃崟缁撴瀯宸插垹闄�</string>
+    <string name="error_menu_companion_not_installed">鈥滃績鐜囪彍鍗曚即渚b�濆彲鑳芥湭瀹夎</string>
+    <string name="info_menu_structure_contents">鑿滃崟缁撴瀯锛�%s</string>
+    <string name="english_au">鑻辨枃 锛堟境澶у埄浜氾級</string>
+    <string name="english_ca">鑻辨枃 锛堝姞鎷垮ぇ锛�</string>
+    <string name="english_gb">鑻辨枃 锛堣嫳鍥斤級</string>
+    <string name="english_in">鑻辨枃 锛堝嵃搴︼級</string>
+    <string name="english_us">鑻辨枃 锛堢編鍥斤級</string>
+    <string name="spanish_es">瑗跨彮鐗欒 锛堣タ鐝墮锛�</string>
+    <string name="spanish_mx">瑗跨彮鐗欒 锛堝ⅷ瑗垮摜锛�</string>
+    <string name="spanish_us">瑗跨彮鐗欒 锛堢編鍥斤級</string>
+    <string name="french_ca">娉曡 锛堝姞鎷垮ぇ锛�</string>
+    <string name="french_fr">娉曡 锛堟硶鍥斤級</string>
+    <string name="updatefirmwareoperation_updateproblem_free_space">璁惧娌℃湁瓒冲鐨勫彲鐢ㄧ┖闂�</string>
+    <string name="text_receiver_activity_title">鍙戦�佹枃鏈埌璁惧</string>
+    <string name="updatefirmwareoperation_failed_low_mtu">%1$d 鐨勫綋鍓� MTU 澶綆锛岃鍦ㄨ澶囪缃腑鍚敤楂� MTU 骞舵柇寮�/閲嶆柊杩炴帴璁惧銆�</string>
+    <string name="devicetype_miband2_hrx">灏忕背鎵嬬幆 HRX</string>
+    <string name="new_discover_activity_description">鍚敤鏂扮殑鍙戠幇娲诲姩锛岃繖搴旇鍙互瑙e喅璁惧鍙戠幇鐨勯棶棰樸�傚鏋滄偍鍦ㄦ悳绱㈣澶囨垨涓庤澶囬厤瀵规椂閬囧埌浠讳綍闂锛岃绂佺敤姝ゅ姛鑳姐��</string>
+    <string name="new_discover_activity_title">鍚敤鏂扮殑鍙戠幇娲诲姩</string>
+    <string name="prefs_wena3_receive_calls_title">鏉ョ數璁剧疆</string>
+    <string name="prefs_wena3_button_action_name_start_timer">鍚姩娲诲姩璁℃椂鍣�</string>
+    <string name="prefs_wena3_menu_icon_hint">璁剧疆鍥炬爣濮嬬粓鏄剧ず鍦ㄦ湯灏�</string>
+    <string name="prefs_wena3_notification_vibration_repetition_3">涓夋</string>
+    <string name="prefs_wena3_button_action_name_qrio_lock">Qrio 宸查攣瀹�</string>
+    <string name="prefs_wena3_auto_power_off_turn_off_time_item">鍏虫満鏃堕棿</string>
+    <string name="prefs_wena3_vibration_short">鐭�</string>
+    <string name="prefs_wena3_home_icon_name_calories">鍗¤矾閲�</string>
+    <string name="prefs_home_icon_left_item">宸�</string>
+    <string name="prefs_wena3_led_none">鏃� LED</string>
+    <string name="prefs_wena3_vibration_step_down">鍑忛��</string>
+    <string name="prefs_wena3_vibration_smart_item">鏅鸿兘鎸姩</string>
+    <string name="prefs_wena3_home_icon_name_riiiver">Riiiver</string>
+    <string name="prefs_wena3_hint_background_sync">鍏佽 Wena 瀹氭湡瑕佹眰 Gadgetbridge 浠庤澶囦笅杞芥椿鍔ㄦ暟鎹�</string>
+    <string name="prefs_wena3_home_icon_name_suica">瑗跨摐鍗′綑棰�</string>
+    <string name="prefs_wena3_vibration_strength_item_medium">涓�</string>
+    <string name="prefs_wena3_title_activity">娲诲姩璁剧疆</string>
+    <string name="prefs_wena3_vibration_continuous">杩炵画</string>
+    <string name="prefs_wena3_home_icon_name_pedometer">璁℃鍣�</string>
+    <string name="prefs_wena3_home_icon_name_qrio">Qrio</string>
+    <string name="white">鐧�</string>
+    <string name="prefs_wena3_home_icon_name_clock">褰撳墠鏃堕棿</string>
+    <string name="prefs_wena3_item_background_sync">鍚庡彴娲诲姩鏁版嵁鍚屾</string>
+    <string name="prefs_wena3_vibration_strength_item_weak">寮�</string>
+    <string name="prefs_wena3_home_icon_title">涓诲睆骞曞浘鏍�</string>
+    <string name="prefs_wena3_item_weather_statusbar">鐘舵�佹爮涓殑澶╂皵</string>
+    <string name="prefs_wena3_vibration_step_up">鍔犵揣</string>
+    <string name="prefs_home_icon_right_item">鍙�</string>
+    <string name="prefs_wena3_vibration_basic">鍩烘湰</string>
+    <string name="prefs_wena3_title_screen">鏄剧ず璁剧疆</string>
+    <string name="prefs_wena3_item_large_font">鏇村ぇ鐨勫瓧浣�</string>
+    <string name="prefs_wena3_home_icon_name_energy">韬綋鐑噺</string>
+    <string name="prefs_wena3_notification_vibration_repetition_2">涓ゆ</string>
+    <string name="prefs_wena3_auto_power_off_hint">璁惧灏嗘寜鐓ф寚瀹氱殑鏃堕棿琛ㄨ嚜鍔ㄥ叧鏈哄拰寮�鏈�</string>
+    <string name="prefs_wena3_hint_lift_wrist">褰撴偍鐪嬪悜鎵嬭厱鏃舵墦寮�鏄剧ず灞�</string>
+    <string name="prefs_wena3_hint_large_font">澧炲姞鏃ュ巻銆侀�氱煡绛変腑鐨勫瓧浣撳ぇ灏忋��</string>
+    <string name="prefs_wena3_button_action_item_double">鍙屽嚮</string>
+    <string name="prefs_wena3_vibration_rapid">蹇��</string>
+    <string name="prefs_wena3_button_action_name_qrio_unlock">Qrio 宸茶В閿�</string>
+    <string name="prefs_wena3_notification_vibration_repetition_1">涓�娆�</string>
+    <string name="prefs_wena3_receive_calls_item">鎺ユ敹鏉ョ數閫氱煡</string>
+    <string name="prefs_wena3_hint_weather_statusbar">鍦ㄤ富灞忓箷宸︿笂瑙掓樉绀哄綋鍓嶇姸鍐靛浘鏍�</string>
+    <string name="prefs_wena3_home_icon_name_wena_pay">Wena Pay</string>
+    <string name="prefs_wena3_button_action_item">鎸夐挳鍔ㄤ綔</string>
+    <string name="prefs_wena3_auto_power_off_turn_on_time_item">鎭㈠鏃堕棿</string>
+    <string name="prefs_wena3_notification_default_call_vibration">鏉ョ數鎸姩</string>
+    <string name="green">缁�</string>
+    <string name="yellow">榛�</string>
+    <string name="devicetype_sony_wena3">绱㈠凹 Wena 3</string>
+    <string name="prefs_wena3_notification_default_call_led">鏉ョ數 LED 棰滆壊</string>
+    <string name="red">绾�</string>
+    <string name="cyan">闈�</string>
+    <string name="prefs_wena3_vibration_siren">璀︾瑳</string>
+    <string name="prefs_wena3_menu_icon_title">鑿滃崟鍥炬爣</string>
+    <string name="prefs_wena3_notification_default_call_vibration_repetition">閲嶅鏉ョ數鎸姩</string>
+    <string name="prefs_wena3_notification_default_vibration">閫氱煡鎸姩</string>
+    <string name="prefs_wena3_notification_vibration_repetition_0">鏃犻檺鏈�</string>
+    <string name="prefs_wena3_notification_vibration_repetition_4">鍥涙</string>
+    <string name="prefs_wena3_vibration_strength_item">鎸姩寮哄害</string>
+    <string name="prefs_wena3_receive_calls_hint">濡傛灉鍏抽棴锛屾偍灏嗕笉浼氬湪 Wena 涓婃敹鍒版潵鐢甸�氱煡</string>
+    <string name="prefs_wena3_button_action_item_long">闀挎寜</string>
+    <string name="prefs_wena3_menu_icon_name_payment">浠樻</string>
+    <string name="prefs_wena3_status_page_title">鐘舵�侀〉闈㈡帓搴�</string>
+    <string name="prefs_wena3_title_alarm">闂归挓璁剧疆</string>
+    <string name="prefs_wena3_notification_default_vibration_repetition">閲嶅閫氱煡鎸姩</string>
+    <string name="prefs_home_icon_center_item">涓�</string>
+    <string name="prefs_wena3_hint_rich_design">鍦ㄤ富灞忓箷鍥炬爣鍛ㄥ洿娣诲姞鍦嗚鐭╁舰</string>
+    <string name="prefs_wena3_notification_default_led">閫氱煡 LED 棰滆壊</string>
+    <string name="prefs_wena3_auto_power_off_item">鑷姩鍏虫満</string>
+    <string name="prefs_wena3_home_icon_name_edy">Edy 浣欓</string>
+    <string name="prefs_wena3_vibration_warning">璀﹀憡</string>
+    <string name="prefs_wena3_smart_alarm_margin_item">鏅鸿兘闂归挓瑁曞害</string>
+    <string name="prefs_wena3_item_rich_design">浣跨敤涓板瘜鐨勮璁�</string>
+    <string name="prefs_wena3_day_start_hour_item">涓�澶╁紑濮嬩簬</string>
+    <string name="prefs_wena3_notification_per_app_settings_title">姣忎釜搴旂敤绋嬪簭鐨勯�氱煡璁剧疆</string>
+    <string name="prefs_wena3_vibration_none">鏃犳尟鍔�</string>
+    <string name="purple">绱�</string>
+    <string name="prefs_wena3_vibration_strength_item_strong">寮�</string>
+    <string name="prefs_wena3_vibration_triple">涓夊��</string>
+    <string name="blue">钃�</string>
+    <string name="prefs_wena3_button_action_name_activity_screen">娲诲姩灞忓箷</string>
+    <string name="prefs_wena3_notification_settings_title">閫氱煡璁剧疆</string>
+    <string name="temperature_scale_celsius">鎽勬皬搴�</string>
+    <string name="temperature_scale_cf">娓╁害鏍囪瘑</string>
+    <string name="temperature_scale_cf_summary">閫夋嫨璁惧鏄娇鐢ㄦ憚姘忓害杩樻槸鍗庢皬搴︺��</string>
+    <string name="temperature_scale_fahrenheit">鍗庢皬</string>
+    <string name="devicetype_sony_wf_1000xm5">Sony WF-1000XM5</string>
+    <string name="pref_message_privacy_mode_bodyonly">浠呴殣钘忎富浣�</string>
+    <string name="latvian">鎷夎劚缁翠簹璇�</string>
+    <string name="common_symbols">甯哥敤绗﹀彿</string>
+    <string name="pref_title_notifications_ignore_work_profile">蹇界暐宸ヤ綔璧勬枡閫氱煡</string>
+    <string name="pref_summary_notifications_ignore_work_profile">涓嶈灏嗗伐浣滈厤缃枃浠朵腑鐨勫簲鐢ㄧ▼搴忕殑閫氱煡鍙戦�佸埌鎵嬭〃</string>
+    <string name="femometer_measurement_mode_normal">鏅�氭ā寮忥紙60鑷�90绉掞級</string>
+    <string name="femometer_measurement_mode_quick">蹇�熸ā寮忥紙30绉掞級</string>
+    <string name="devicetype_amazfit_balance">璺冩垜 Balance</string>
+    <string name="femometer_measurement_mode_title">娴嬮噺妯″紡</string>
+    <string name="femometer_measurement_mode_precise">绮剧‘妯″紡锛�3鍒嗛挓锛�</string>
+    <string name="devicetype_femometer_vinca2">Femometer Vinca II</string>
+    <string name="zepp_os_watchface_pure_white">绾櫧</string>
+    <string name="zepp_os_watchface_the_ultima">鏈�鍚庨�氱墥</string>
+    <string name="zepp_os_watchface_city_of_speed">閫熷害涔嬪煄</string>
+    <string name="menuitem_headphone">鑰虫満</string>
+    <string name="zepp_os_watchface_free_combination">鑷敱缁勫悎</string>
+    <string name="menuitem_body_composition">韬綋鏋勬垚</string>
+    <string name="menuitem_zepp_pay">Zepp 鏀粯</string>
+    <string name="zepp_os_watchface_starry_sky">鏄熺┖</string>
+    <string name="zepp_os_watchface_vast_sky">娴╃�氱殑澶╃┖</string>
+    <string name="menuitem_workout_shortcuts">閿荤偧蹇嵎鏂瑰紡</string>
+    <string name="zepp_os_watchface_lightning_flash">闂數</string>
+    <string name="menuitem_apps_shortcuts">搴旂敤蹇嵎鏂瑰紡</string>
+    <string name="zepp_os_watchface_guider">鎸囧</string>
+    <string name="menuitem_readiness">鍑嗗鐘舵��</string>
+    <string name="menuitem_thermometer">娓╁害璁�</string>
+    <string name="avgStrokeRate">骞冲潎鍑荤悆璺濈</string>
+    <string name="yard">鐮�</string>
+    <string name="strokes_minute">鐩寸嚎/鍒嗛挓</string>
+    <string name="maxStrokeRate">鏈�澶у嚮鐞冭窛绂�</string>
+    <string name="laneLength">閬撹矾闀垮害</string>
+    <string name="strokes">鎬昏鍑荤悆</string>
+    <string name="fossil_hr_nav_app_not_installed_notify_text">瀵艰埅宸插惎鍔紝浣嗘墜琛ㄤ笂鏈畨瑁呭鑸簲鐢ㄧ▼搴忋�傝浠庡簲鐢ㄧ▼搴忕鐞嗗櫒瀹夎瀹冦��</string>
+    <string name="fossil_hr_nav_app_not_installed_notify_title">鎵嬭〃涓婃湭瀹夎瀵艰埅搴旂敤</string>
+    <string name="call_rejection_method_ignore">蹇界暐 锛堥潤闊筹級</string>
+    <string name="call_rejection_method_reject">鎷掔粷</string>
+    <string name="pref_call_rejection_method_title">鏉ョ數鎷掓帴鏂规硶</string>
+    <string name="busy_task_fetch_statistics">姝e湪鑾峰彇缁熻鏁版嵁</string>
+    <string name="pref_call_rejection_method_summary">鎷掓帴鎵嬭〃鏉ョ數鏃堕噰鍙栦粈涔堟搷浣�</string>
+    <string name="busy_task_fetch_temperature">姝e湪鑾峰彇娓╁害鏁版嵁</string>
+    <string name="devicetype_amazfit_active_edge">璺冩垜 Active Edge</string>
+    <string name="charts_legend_spo2_average">琛�姘у钩鍧囧��</string>
+    <string name="devicetype_amazfit_active">璺冩垜 Active</string>
+    <string name="pref_title_navigation_apps">瀵艰埅搴旂敤</string>
+    <string name="pref_title_navigation_prefs">瀵艰埅棣栭�夐」</string>
+    <string name="pref_navigation_app_gmaps">璋锋瓕鍦板浘</string>
+    <string name="pref_summary_prefix_notification_with_app">鍦ㄩ�氱煡鏍囬鍓嶆坊鍔犳潵鑷簲鐢ㄥ悕绉扮殑鍓嶇紑</string>
+    <string name="pref_summary_osmand_packagename">閫夋嫨鐢ㄤ簬瑕佽繛鎺ョ殑 OsmAnd 鐗堟湰</string>
+    <string name="pref_title_prefix_notification_with_app">閫氱煡涓殑搴旂敤鍚�</string>
+    <string name="pref_title_osmand_packagename">OsmAnd 鍖呭悕</string>
+    <string name="pref_navigation_app_osmand">OsmAnd(+)</string>
+    <string name="pref_title_casio_alert_other">鍏朵粬閫氱煡鎻愰啋</string>
+    <string name="pref_title_casio_alert_calendar">鏃ュ巻閫氱煡鎻愰啋</string>
+    <string name="pref_summary_casio_alert_call">鏉ョ數鎻愰啋锛堟尟鍔�/楦e彨锛�</string>
+    <string name="devicetype_miband8">灏忕背鎵嬬幆8</string>
+    <string name="menuitem_running">璺戞</string>
+    <string name="pref_sleep_mode_schedule_title">鐫$湢妯″紡璁″垝</string>
+    <string name="pref_title_casio_alert_call">鏉ョ數鎻愰啋</string>
+    <string name="bedtime">鐫$湢</string>
+    <string name="pref_title_casio_alert_email">鐢靛瓙閭欢閫氱煡鎻愰啋</string>
+    <string name="pref_summary_casio_alert_email">鐢靛瓙閭欢閫氱煡鎻愰啋锛堟尟鍔�/楦e彨锛�</string>
+    <string name="menuitem_focus">涓撴敞</string>
+    <string name="pref_summary_casio_alert_sms">SMS锛堢煭淇★級閫氱煡鐨勬彁閱掞紙鎸姩/楦e彨锛�</string>
+    <string name="pref_title_preview_message_in_title">鍦ㄦ爣棰樹腑鏄剧ず娑堟伅鐨勯瑙�</string>
+    <string name="devicetype_redmiwatch3active">绾㈢背鎵嬭〃3 杩愬姩鐗�</string>
+    <string name="pref_summary_casio_alert_calendar">鏃ュ巻閫氱煡鎻愰啋锛堟尟鍔�/楦e彨锛�</string>
+    <string name="serial_number">搴忓垪鍙�</string>
+    <string name="danish">涓归害璇�</string>
+    <string name="wake_up_time">璧峰簥</string>
+    <string name="menuitem_stats">缁熻</string>
+    <string name="pref_summary_casio_alert_other">閽堝鍏朵粬绫诲埆鐨勯�氱煡鍙戝嚭鎻愰啋锛堟尟鍔�/楦e彨锛�</string>
+    <string name="devicetype_xiaomi_watch_lite">灏忕背鎵嬭〃闈掓槬鐗�</string>
+    <string name="pref_summary_preview_message_in_title">鍦ㄨ澶囧厑璁哥殑鎯呭喌涓嬶紝鍦ㄩ�氱煡鏍囬涓樉绀烘秷鎭瑙�</string>
+    <string name="pref_sleep_mode_schedule_summary">鍙戝嚭涓�涓彁閱掑苟鍦ㄥ氨瀵濇椂闂磋繘鍏ョ潯鐪犳ā寮忋�� 鍦ㄩ瀹氱殑璧峰簥鏃堕棿锛岃捣搴婇椆閽熷皢浼氬搷璧枫��</string>
+    <string name="pref_title_casio_alert_sms">鐭俊閫氱煡鎻愰啋</string>
+    <string name="menuitem_alerts">鎻愰啋</string>
+    <string name="devicetype_miband7pro">灏忕背鏅鸿兘鎵嬬幆 7 Pro</string>
+    <string name="devicetype_xiaomi_watch_s1_active">灏忕背鎵嬭〃 S1 杩愬姩鐗�</string>
+    <string name="devicetype_mi_watch_color_sport">灏忕背鎵嬭〃褰╄壊杩愬姩鐗�</string>
+    <string name="devicetype_pixoo">Pixoo</string>
+    <string name="pref_summary_send_app_notifications">鍚戣澶囧彂閫佸簲鐢ㄩ�氱煡</string>
+    <string name="pref_title_send_app_notifications">鍙戦�侀�氱煡</string>
+    <string name="pref_device_action_dnd_on">璇峰嬁鎵撴壈 - 宸插紑鍚�</string>
+    <string name="pref_device_action_dnd_alarms">璇峰嬁鎵撴壈 - 浠呴檺闂归挓</string>
+    <string name="pref_device_action_dnd_priority">璇峰嬁鎵撴壈 - 浠呴檺浼樺厛</string>
+    <string name="not_set">鏈缃�</string>
+    <string name="pref_device_action_dnd_off">璇峰嬁鎵撴壈 - 鍏抽棴</string>
+    <string name="prefs_wearmode">浣╂埓鏂瑰紡</string>
+    <string name="wearmode_pebble">Pebble锛堥瀷鎵o級</string>
+    <string name="pref_title_goal_secondary">娆¤鐩爣</string>
+    <string name="pref_vitality_score_7_day_summary">褰撴偍鐨勬椿鍔涘�煎湪杩囧幓 7 澶╁唴杈惧埌 30銆�60 鎴� 100 鏃讹紝鎮ㄤ細鏀跺埌閫氱煡</string>
+    <string name="pref_vitality_score_title">娲诲姏鍊�</string>
+    <string name="pref_vitality_score_daily_summary">褰撴偍杈惧埌褰撳ぉ鐨勬渶澶ф椿鍔涘�兼椂鏀跺埌閫氱煡</string>
+    <string name="standing_time">绔欑珛鏃堕棿</string>
+    <string name="active_time">娲诲姩鏃堕棿</string>
+    <string name="wearmode_band">鎵嬬幆锛堣厱甯︼級</string>
+    <string name="wearmode_necklace">椤归摼锛堥甯︼級</string>
+    <string name="pref_vitality_score_daily_title">姣忔棩杩涘害</string>
+    <string name="pref_vitality_score_7_day_title">7澶╄繘灞�</string>
+    <string name="prefs_phone_silent_mode">鎵嬫満闈欓煶妯″紡</string>
+    <string name="silent_mode_normal_vibrate">鏅�� / 鎸姩</string>
+    <string name="silent_mode_vibrate_silent">闇囧姩 / 闈欓煶</string>
+    <string name="devicetype_redmi_smart_band_2">绾㈢背鏅鸿兘鎵嬬幆2</string>
+    <string name="silent_mode_normal_silent">鏅�� / 闈欓煶</string>
+    <string name="activity_type_trail_run">瓒婇噹璺�</string>
+    <string name="widget_layout">灏忛儴浠跺竷灞�</string>
+    <string name="widget_screen_no_free_slots_description">璇ヨ澶囨病鏈夌敤浜庡皬閮ㄤ欢灞忓箷鐨勭┖闂蹭綅缃紙浣嶇疆鎬绘暟锛�%1$s锛�</string>
+    <string name="widget_unknown_workout">鏈煡閿荤偧 - %s</string>
+    <string name="widget_move_up">涓婄Щ</string>
+    <string name="widget_screen_delete_confirm_description">鎮ㄧ‘瀹氳鍒犻櫎鈥�%1$s鈥濆悧锛�</string>
+    <string name="activity_type_trekking">寰掓</string>
+    <string name="widget_layout_two">2 涓皬閮ㄤ欢</string>
+    <string name="widget_screen_delete_confirm_title">鍒犻櫎灏忛儴浠跺睆骞�</string>
+    <string name="widget_layout_top_1_bot_2">1 涓《閮紝2 涓簳閮�</string>
+    <string name="widget_layout_top_2_bot_2">2 涓《閮紝2 涓簳閮�</string>
+    <string name="widget_screen_min_screens">蹇呴』鑷冲皯鏈� %1$s 涓睆骞�</string>
+    <string name="widget">灏忛儴浠�</string>
+    <string name="widget_screen">灏忛儴浠跺睆骞�</string>
+    <string name="widget_move_down">涓嬬Щ</string>
+    <string name="widget_layout_single">1 涓皬閮ㄤ欢</string>
+    <string name="widget_subtype">灏忛儴浠跺瓙绫诲瀷</string>
+    <string name="widget_screen_x">灞忓箷 %s</string>
+    <string name="widget_missing_parts">璇烽�夋嫨鎵�鏈夊皬閮ㄤ欢</string>
+    <string name="widget_layout_top_2_bot_1">2 涓《閮紝1 涓簳閮�</string>
+    <string name="activity_type_wrestling">鎽旇</string>
+    <string name="devicetype_colacao23">楂樹箰楂� 2023</string>
+    <string name="devicetype_colacao21">楂樹箰楂� 2021</string>
+    <string name="clap_hands_to_wakeup_device">鎷嶆墜浜捣灞忓箷</string>"
+    <string name="clap_hands_to_wakeup_device_summary">鍐嶆鎷嶆墜鍏抽棴灞忓箷</string>"
+    <string name="pixoo_power_saving_summary">楹﹀厠椋庢娴嬪埌闈欓煶涓�娈垫椂闂村悗灏嗗睆骞曞叧闂�</string>
+    <string name="devicetype_mijia_lywsd03">绫冲娓╂箍搴︿紶鎰熷櫒2</string>
+    <string name="pref_title_fossil_hr_navigation_instructions">瀵艰埅璇存槑</string>
+    <string name="pref_summary_fossil_hr_navigation_instructions">閰嶇疆鎵嬭〃涓婂鑸簲鐢ㄧ殑琛屼负</string>
+    <string name="pref_summary_fossil_hr_nav_foreground">瀵艰埅搴旂敤鏀跺埌瀵艰埅鎸囦护鏃舵槸鍚﹁嚜鍔ㄨ繘鍏ュ墠鍙�</string>
+    <string name="pref_title_fossil_hr_nav_vibrate">鏍规嵁鏂版寚浠ゆ尟鍔�</string>
+    <string name="pref_title_fossil_hr_nav_foreground">鏉ュ埌鍓嶅彴</string>
+    <string name="prefs_device_name">璁惧鍚嶇О</string>
+    <string name="pref_summary_fossil_hr_nav_vibrate">鎵嬭〃鏄惁搴旇鍦ㄦ瘡涓柊鐨勬垨鏇存敼鐨勫鑸寚浠ゆ椂鎸姩锛堜粎褰撳簲鐢ㄧ▼搴忎綅浜庡墠鍙版椂锛�</string>
+    <string name="devicetype_redmi_watch_2_lite">绾㈢背鎵嬭〃2闈掓槬鐗�</string>
+    <string name="devicetype_redmi_smart_band_pro">绾㈢背鏅鸿兘鎵嬬幆Pro</string>
+    <string name="notification_channel_connection_status_name">杩炴帴鐘舵��</string>
+    <string name="devicetype_nothingear2">Nothing Ear (2)</string>
+    <string name="devicetype_nothingearstick">Nothing Ear (Stick)</string>
+    <string name="prefs_active_noise_cancelling_light">杞诲害涓诲姩闄嶅櫔</string>
+    <string name="prefs_active_noise_cancelling_transparency">閫氶�忓害</string>
+    <string name="devicetype_honor_band4">鑽h��鎵嬬幆4</string>
+    <string name="devicetype_honor_band5">鑽h��鎵嬬幆5</string>
+    <string name="devicetype_huawei_talk_band_b6">鍗庝负璇煶鎵嬬幆B6</string>
+    <string name="huawei_alarm_smart_description">涓嶈鍙栨秷閫変腑鏅鸿兘鍞ら啋澶嶉�夋銆�</string>
+    <string name="huawei_trusleep_summary">瀹炴椂鐩戞帶鎮ㄧ殑鐫$湢璐ㄩ噺鍜屽懠鍚告ā寮忋��
+\n鍒嗘瀽鎮ㄧ殑鐫$湢妯″紡骞跺噯纭瘖鏂� 6 绉嶇潯鐪犻棶棰樸��</string>
+    <string name="devicetype_huawei_watchgt3">鍗庝负鎵嬭〃GT 3 锛圥ro锛�</string>
+    <string name="pref_enable_call_accept">鍚敤鎺ュ惉鐢佃瘽</string>
+    <string name="pref_enable_call_accept_summary">鍚敤浠庤澶囨帴鍙楃數璇�</string>
+    <string name="pref_heartrate_automatic_enable">鍚敤鑷姩蹇冪巼娴嬮噺</string>
+    <string name="pref_force_options_summary">鏌愪簺璁惧閿欒鍦板0绉颁笉鏀寔鏌愪簺閫夐」銆傛棤璁哄浣曪紝姝よ缃彲鐢ㄤ簬鍚敤瀹冧滑銆�
+\n浣跨敤椋庨櫓鐢辨偍鑷鎵挎媴銆�
+\n闃呰缁村熀</string>
+    <string name="pref_force_smart_alarm_summary">寮哄埗鏅鸿兘鎶ヨ鏀寔銆�
+\n椋庨櫓鑷媴</string>
+    <string name="pref_force_dnd_support">寮哄埗璇峰嬁鎵撴壈鏀寔</string>
+    <string name="huawei_reparse_workout_data">閲嶆柊瑙f瀽閿荤偧鏁版嵁</string>
+    <string name="prefs_disconnect_notification_summary">褰撹摑鐗欐柇寮�鏃跺湪璁惧涓婅繘琛岄�氱煡銆�</string>
+    <string name="do_not_disturb_lift_wrist_summary">浠呭綋鎶厱鍚敤鏃舵縺娲绘樉绀�</string>
+    <string name="pref_do_not_disturb_not_wear">涓嶄僵鎴存椂鍚敤璇峰嬁鎵撴壈</string>
+    <string name="manual">鎵嬪姩</string>
+    <string name="dnd_all_day">鍏ㄥぉ</string>
+    <string name="devicetype_honor_band3">鑽h��鎵嬬幆3</string>
+    <string name="devicetype_honor_band6">鑽h��鎵嬬幆6</string>
+    <string name="devicetype_honor_band7">鑽h��鎵嬬幆7</string>
+    <string name="devicetype_huawei_band_aw70">鍗庝负鎵嬬幆锛圓W70锛�</string>
+    <string name="devicetype_huawei_band6">鑽h��鎵嬬幆6</string>
+    <string name="devicetype_huawei_band7">鑽h��鎵嬬幆7</string>
+    <string name="devicetype_huawei_band8">鑽h��鎵嬬幆8</string>
+    <string name="devicetype_huawei_watch_gt">鍗庝负鎵嬭〃GT</string>
+    <string name="devicetype_huawei_band4pro">鍗庝负鎵嬬幆4锛圥ro锛�</string>
+    <string name="devicetype_huawei_watchgt2">鍗庝负鎵嬭〃GT 2锛圥ro锛�</string>
+    <string name="devicetype_huawei_watchgt2e">鍗庝负鎵嬭〃GT 2e</string>
+    <string name="prefs_workmode">宸ヤ綔妯″紡</string>
+    <string name="huawei_alarm_event_description">涓嶈閫変腑鏅鸿兘鍞ら啋澶嶉�夋銆�</string>
+    <string name="huawei_trusleep_title">鍗庝负 TruSleep鈩�</string>
+    <string name="huawei_trusleep_summary_light">鏀瑰杽鐫$湢鐩戞祴</string>
+    <string name="pref_enable_call_reject">鍚敤鎷掔粷鏉ョ數</string>
+    <string name="pref_enable_call_reject_summary">鍚敤鎷掔粷鏉ヨ嚜璁惧鐨勫懠鍙�</string>
+    <string name="pref_disable_find_phone_with_dnd">褰撹鍕挎墦鎵板浜庢椿鍔ㄧ姸鎬佹椂绂佺敤鏌ユ壘鎴戠殑鎵嬫満</string>
+    <string name="pref_spo_automatic_enable">鍚敤鑷姩琛�姘ф祴閲�</string>
+    <string name="pref_force_options">寮哄埗閫夐」</string>
+    <string name="pref_force_smart_alarm">寮哄埗鏅鸿兘闂归挓</string>
+    <string name="pref_force_wear_location">寮哄埗浣╂埓浣嶇疆</string>
+    <string name="pref_force_wear_location_summary">寮哄埗浣╂埓浣嶇疆鏀寔銆�
+\n椋庨櫓鑷媴</string>
+    <string name="pref_force_dnd_support_summary">寮哄埗璇峰嬁鎵撴壈鏀寔銆�
+\n椋庨櫓鑷媴</string>
+    <string name="huawei_ignore_wakeup_status_start">蹇界暐鍞ら啋鍚姩鐘舵��</string>
+    <string name="huawei_ignore_wakeup_status_start_description">鍙兘鏈夊姪浜庢纭殑鐫$湢妫�娴嬨�� 鍦ㄦ棩甯告椿鍔ㄨ鍥句腑绔嬪嵆鍙銆�</string>
+    <string name="huawei_ignore_wakeup_status_end">蹇界暐鍞ら啋缁撴潫鐘舵��</string>
+    <string name="huawei_ignore_wakeup_status_end_description">鍙兘鏈夊姪浜庢纭殑鐫$湢妫�娴嬨�� 鍦ㄦ棩甯告椿鍔ㄨ鍥句腑绔嬪嵆鍙銆�</string>
+    <string name="huawei_reparse_workout_data_description">杩欏彧浼氬湪鏌愪簺鏇存柊鍚庢墽琛屾煇浜涙搷浣�</string>
+    <string name="pref_summary_debug">鍚戝崕涓鸿澶囧彂閫佽皟璇曡姹�</string>
+    <string name="pref_title_debug">璋冭瘯璇锋眰</string>
+    <string name="pref_title_general_reconnectonlytoconnected">浠呴噸鏂拌繛鎺ュ埌宸茶繛鎺ョ殑璁惧</string>
+    <string name="pref_summary_general_reconnectonlytoconnected">浠呴噸鏂拌繛鎺ュ埌宸茶繛鎺ョ殑璁惧锛岃�屼笉鏄噸鏂拌繛鎺ュ埌鎵�鏈夎澶�</string>
+    <string name="updatefirmwareoperation_updateproblem_low_battery">璁惧鐢垫睜鐢甸噺杩囦綆</string>
+    <string name="menuitem_buzzer_intensity">铚傞福鍣ㄥ己搴�</string>
+    <string name="prefs_password_4_digits_0_to_9_summary">瀵嗙爜蹇呴』涓� 4 浣嶆暟瀛楋紝涓斿彧鑳戒娇鐢ㄦ暟瀛�</string>
+    <string name="devicetype_mijia_mho_c303">绫冲 MHO-C303</string>
+    <string name="sony_protocol_v1">鐗堟湰 1</string>
+    <string name="devicetype_miband8pro">灏忕背鏅鸿兘鎵嬬幆 8 Pro</string>
+    <string name="flatDistance">骞抽潰璺濈</string>
+    <string name="protocol_version">鍗忚鐗堟湰</string>
+    <string name="devicetype_xiaomi_watch_s3">灏忕背鎵嬭〃 S3</string>
+    <string name="sony_protocol_v2">鐗堟湰 2</string>
+    <string name="devicetype_xiaomi_watch_s1">灏忕背鎵嬭〃 S1</string>
+    <string name="sony_protocol_v3">鐗堟湰 3</string>
+    <string name="devicetype_xiaomi_watch_s1_pro">灏忕背鎵嬭〃 S1 Pro</string>
+    <string name="activity_info">娲诲姩淇℃伅</string>
+    <string name="pref_force_connection_type_description">濡傛灉鎮ㄧ殑璁惧涓嶅搷搴� Gadgetbridge锛屾偍鍙互灏濊瘯寮哄埗杩炴帴绫诲瀷</string>
+    <string name="uploading_watchface">姝e湪涓婁紶琛ㄧ洏鈥︹��</string>
+    <string name="uploadwatchfaceoperation_in_progress">姝e湪涓婁紶琛ㄧ洏</string>
+    <string name="uploadwatchfaceoperation_complete">琛ㄧ洏瀹夎宸插畬鎴�</string>
+    <string name="uploadwatchfaceoperation_failed">琛ㄧ洏瀹夎澶辫触</string>
+    <string name="pref_force_connection_type_title">寮哄埗杩炴帴绫诲瀷</string>
+    <string name="pref_force_connection_type_auto">鑷姩</string>
+    <string name="pref_force_connection_type_ble">浣庡姛鑰楄摑鐗�</string>
+    <string name="pref_force_connection_type_bt_classic">缁忓吀钃濈墮</string>
+    <string name="devicetype_nothing_cmf_watch_pro">CMF Watch Pro</string>
+    <string name="heartrate_bpm_155">155 bpm</string>
+    <string name="heartrate_bpm_165">165 bpm</string>
+    <string name="heartrate_bpm_175">175 bpm</string>
+    <string name="heartrate_bpm_185">185 bpm</string>
+    <string name="heartrate_bpm_195">195 bpm</string>
+    <string name="heartrate_bpm_205">205 bpm</string>
+    <string name="prefs_heartrate_alert_active_high_threshold">寮哄姏杩愬姩蹇冪巼璀︽姤闃堝��</string>
+    <string name="activity_type_indoor_running">瀹ゅ唴璺戞</string>
+    <string name="activity_type_mountain_hike">鐧诲北</string>
+    <string name="activity_type_cross_trainer">浜ゅ弶璁粌鏈�</string>
+    <string name="activity_type_free_training">鑷敱璁粌</string>
+    <string name="activity_type_rower">璧涜墖</string>
+    <string name="activity_type_dynamic_cycle">鍔ㄦ劅鍗曡溅</string>
+    <string name="activity_type_stair_stepper">妤兼韪忔鏈�</string>
+    <string name="activity_type_fitness_exercises">鍋ヨ韩杩愬姩</string>
+    <string name="activity_type_crossfit">缁煎悎鍋ヨ韩</string>
+    <string name="activity_type_functional_training">鍔熻兘璁粌</string>
+    <string name="activity_type_physical_training">浣撳姏璁粌</string>
+    <string name="activity_type_taekwondo">璺嗘嫵閬�</string>
+    <string name="activity_type_cross_country_running">瓒婇噹璺�</string>
+    <string name="activity_type_karate">绌烘墜閬�</string>
+    <string name="activity_type_fencing">鍑诲墤</string>
+    <string name="activity_type_kendo">鍓戦亾</string>
+    <string name="activity_type_horizontal_bar">鍗曟潬</string>
+    <string name="activity_type_parallel_bar">鍙屾潬</string>
+    <string name="activity_type_cooldown">鍐峰嵈</string>
+    <string name="activity_type_cross_training">浜ゅ弶璁粌</string>
+    <string name="activity_type_sit_ups">浠板崸璧峰潗</string>
+    <string name="activity_type_fitness_gaming">鍋ヨ韩娓告垙</string>
+    <string name="activity_type_aerobic_exercise">鏈夋哀杩愬姩</string>
+    <string name="activity_type_rolling">婊氬姩</string>
+    <string name="activity_type_push_ups">淇崸鎾�</string>
+    <string name="activity_type_battle_rope">鎴樼怀</string>
+    <string name="activity_type_pull_ups">寮曚綋鍚戜笂</string>
+    <string name="activity_type_plank">鏈ㄦ澘</string>
+    <string name="activity_type_javelin">鏍囨灙</string>
+    <string name="activity_type_long_jump">闀胯烦</string>
+    <string name="activity_type_dumbbell">鍝戦搩</string>
+    <string name="activity_type_belly_dance">鑲氱毊鑸�</string>
+    <string name="activity_type_jazz_dance">鐖靛+鑸�</string>
+    <string name="activity_type_latin_dance">鎷変竵鑸�</string>
+    <string name="activity_type_ballet">鑺暰鑸�</string>
+    <string name="activity_type_darts">椋為晼</string>
+    <string name="activity_type_archery">灏勭</string>
+    <string name="activity_type_horse_riding">楠戦┈</string>
+    <string name="activity_type_kite_flying">鏀鹃绛�</string>
+    <string name="activity_type_swing">鎽囨憜</string>
+    <string name="activity_type_stairs">妤兼</string>
+    <string name="activity_type_fishing">閽撻奔</string>
+    <string name="activity_type_hand_cycling">鎵嬮獞鑷杞�</string>
+    <string name="activity_type_mind_and_body">蹇冪伒鍜岃韩浣�</string>
+    <string name="activity_type_kabaddi">鍗″反杩�</string>
+    <string name="activity_type_karting">鍗′竵杞�</string>
+    <string name="activity_type_billiards">鍙扮悆</string>
+    <string name="activity_type_shuttlecock">缇芥瘺鐞�</string>
+    <string name="activity_type_dodgeball">韬查伩鐞�</string>
+    <string name="activity_type_australian_football">婢冲ぇ鍒╀簹瓒崇悆</string>
+    <string name="activity_type_pickleball">鍖瑰厠鐞�</string>
+    <string name="activity_type_lacross">鏇叉鐞�</string>
+    <string name="activity_type_shot">灏勫嚮</string>
+    <string name="activity_type_sailing">甯嗚埞杩愬姩</string>
+    <string name="activity_type_jet_skiing">婊戞按</string>
+    <string name="devicetype_sony_wi_sp600n">绱㈠凹 WI-SP600N</string>
+    <string name="activity_type_martial_arts">姝︽湳</string>
+    <string name="activity_type_smith_machine">鍙插瘑鏂満</string>
+    <string name="activity_type_flexibility">鐏垫椿鎬�</string>
+    <string name="activity_type_track_and_field">鐢板緞</string>
+    <string name="hydration_dnd_summary">鍦ㄤ竴娈垫椂闂村唴绂佺敤琛ュ厖姘村垎璀﹀憡</string>
+    <string name="activity_type_high_jump">璺抽珮</string>
+    <string name="activity_type_other_dance">鍏朵粬鑸炶箞</string>
+    <string name="activity_type_tai_chi">澶瀬</string>
+    <string name="activity_type_disc_sports">椋炵洏杩愬姩</string>
+    <string name="activity_type_trampoline">韫﹀簥</string>
+    <string name="activity_type_ice_hockey">鍐扮悆</string>
+    <string name="activity_type_roller_skating">杞粦</string>
+    <string name="activity_type_hula_hooping">鍛煎暒鍦�</string>
+    <string name="activity_type_softball">鍨掔悆</string>
+    <string name="activity_type_skating">婧滃啺</string>
+    <string name="activity_type_curling">鍐板6</string>
+    <string name="activity_type_snow_sports">闆笂杩愬姩</string>
+    <string name="activity_type_hunting">鎵撶寧</string>
+    <string name="activity_type_outdoor_walking">鎴峰鏁f</string>
+    <string name="activity_type_cross_country_skiing">瓒婇噹婊戦洩</string>
+    <string name="activity_type_skateboarding">婊戞澘杩愬姩</string>
+    <string name="activity_type_rock_climbing">鏀�宀�</string>
+    <string name="devicetype_honor_magicwatch2">鑽h�� MagicWatch 2</string>
+    <string name="devicetype_redmi_watch_2">绾㈢背鎵嬭〃2</string>
+    <string name="warning_missing_notification_permission">鐢变簬缂哄皯鏉冮檺锛屾棤娉曞彂甯冩寔缁�氱煡</string>
+    <string name="devicetype_huawei_watchfit">鍗庝负杩愬姩鎵嬭〃</string>
+    <string name="pref_test_features_title">鍔熻兘</string>
+    <string name="pref_test_features_summary">璇ユ祴璇曡澶囧惎鐢ㄧ殑鍔熻兘</string>
+    <string name="devicetype_redmiwatch3">绾㈢背鎵嬭〃3</string>
+    <string name="alarm_smart_wakeup_interval">鏅鸿兘鍞ら啋闂撮殧锛�</string>
+    <string name="alarm_smart_wakeup_interval_default">5 鍒嗛挓</string>
+    <string name="auto_reconnect_ble_scan_title">閫氳繃浣庡姛鑰楄摑鐗欐壂鎻忛噸鏂拌繛鎺�</string>
+    <string name="device_state_waiting_scan">姝e湪绛夊緟璁惧鎵弿</string>
+    <string name="auto_reconnect_ble_scan_summary">绛夊緟璁惧鎵弿鑰屼笉鏄洸鐩繛鎺ュ皾璇�</string>
+    <string name="prompt_restart_gadgetbridge">璇烽噸鏂板惎鍔� Gadgetbridge 鎵嶈兘鐢熸晥銆�</string>
+    <string name="waiting_for_bluetooth">绛夊緟钃濈墮鈥�</string>
+    <string name="error_scan_failed">鎵弿澶辫触锛� %d</string>
+    <string name="scan_not_scanning">涓嶆壂鎻�</string>
+    <string name="scan_scanning_all_devices">鎵弿鎵�鏈夎澶�</string>
+    <string name="scan_scanning_single_device">姝e湪鎵弿涓�涓澶�</string>
+    <string name="scan_scanning_multiple_devices">姝e湪鎵弿 %d 涓澶�</string>
+    <string name="notification_channel_scan_service_name">鎵弿鏈嶅姟</string>
+    <string name="devicetype_miband8active">灏忕背鏅鸿兘鎵嬬幆8 杩愬姩鐗�</string>
+    <string name="temperature">娓╁害</string>
+    <string name="pref_wide_area_tap_summary">璇嗗埆鑴搁鍜岃�虫湹涔嬮棿鐨勮交鎷�</string>
+    <string name="pref_wide_area_tap_title">澶ц寖鍥磋Е鍑�</string>
+    <string name="pref_header_sony_sound_control">澹伴煶鎺у埗</string>
+    <string name="pref_adaptive_volume_control_summary">鐜澹伴煶鍙樺ぇ鏃惰嚜鍔ㄥ澶ч煶閲�</string>
+    <string name="pref_adaptive_volume_control_title">鑷�傚簲闊抽噺鎺у埗</string>
+    <string name="devicetype_sony_linkbuds">绱㈠凹 LinkBuds</string>
+    <string name="devicetype_amazfit_bip3">鍗庣背 Bip 3</string>
+    <string name="fw_upgrade_notice_amazfitbip3">鎮ㄥ嵆灏嗗湪鎮ㄧ殑鍗庣背 Bip 3 涓婂畨瑁� %s 鍥轰欢銆�
+\n
+\n璇风‘淇濆厛瀹夎 .fw 鏂囦欢锛屽啀瀹夎 .res 鏂囦欢銆傛偍鐨勬墜琛ㄥ皢浼氬湪瀹夎浜� .fw 鏂囦欢鍚庤嚜鍔ㄩ噸鍚��
+\n
+\n澶囨敞锛氬鏋滃嵆灏嗗畨瑁呯殑 .res 鏂囦欢鐨勭増鏈笌褰撳墠宸插畨瑁呯浉鍚岋紝鎮ㄦ棤闇�鍐嶆瀹夎銆�
+\n
+\n椋庨櫓鑷媴锛�</string>
+    <string name="weekstepschart_steps_a_week_or_month">姣忓懆/姣忔湀姝ユ暟</string>
+    <string name="weeksleepchart_sleep_a_week_or_month">姣忓懆/姣忔湀鐫$湢</string>
+    <string name="devicetype_huawei_watchgt4">鍗庝负鎵嬭〃 GT 4</string>
+    <string name="busy_task_fetch_sports_details_interrupted">鑾峰彇杩愬姩璇︽儏鏃惰涓柇</string>
+    <string name="devicetype_huawei_watchultimate">鍗庝负鎵嬭〃 Ultimate</string>
+    <string name="unbind_before_pair_title">宸茬粦瀹�</string>
+    <string name="companion_pairing_request_title">闄即璁惧</string>
+    <string name="companion_pairing_request_description">灏嗘璁惧閰嶅涓洪櫔浼磋澶囷紵
+\n
+\n寤鸿灏嗘鍔熻兘鐢ㄤ簬鏌愪簺鍔熻兘锛堜緥濡傛煡鎵捐澶囷級锛屽苟鎻愪緵鏇村ソ鐨勮繛鎺ャ��</string>
+    <string name="unbind_before_pair_message">璇ヨ澶囧凡鍦� Android 璁剧疆涓粦瀹氾紝杩欏彲鑳戒細瀵艰嚧鏌愪簺璁惧閰嶅澶辫触銆�
+\n
+\n濡傛灉娣诲姞璁惧澶辫触锛岃鍦� Android 璁剧疆涓皢鍏跺垹闄わ紝鐒跺悗閲嶈瘯銆�</string>
+    <string name="devicetype_scannable">鍙壂鎻忕殑璁惧</string>
+    <string name="state_scanned">宸叉壂鎻�</string>
+    <string name="devicesetting_scannable_rssi">鏈�灏� RSSI 闃堝��</string>
+    <string name="devicesetting_scannable_rssi_summary">妫�娴嬬殑鏈�灏� RSSI 闃堝��</string>
+    <string name="devicesetting_scannable_debounce">鍙壂鎻忕殑鍘绘姈鍔ㄨ秴鏃讹紙绉掞級</string>
+    <string name="devicesetting_scannable_minimum_unseen">鏈�鐭湭鍙戠幇鏃堕棿锛堢锛�</string>
+    <string name="devicesetting_scannable_debounce_summary">鎵弿鍚庯紝璁惧灏嗕繚鎸佹壂鎻忕姸鎬佸苟鍦ㄦ寚瀹氭椂闂村唴琚拷鐣�</string>
+    <string name="devicesetting_scannable_minimum_unseen_summary">鎵弿鍚庯紝璁惧蹇呴』鍦ㄨ繖娈垫椂闂村唴涓嶅彲瑙侊紝鐒跺悗鎵嶈兘鍐嶆娉ㄥ唽</string>
+    <string name="swolfAvg">骞冲潎 Swolf</string>
+    <string name="swolfMax">鏈�澶� Swolf</string>
+    <string name="swolfMin">鏈�灏� Swolf</string>
+    <string name="stepRateSum">姝ラ�熸�诲拰</string>
+    <string name="stepRateAvg">骞冲潎姝ラ��</string>
+    <string name="stepLengthAvg">骞冲潎姝ラ暱</string>
+    <string name="groundContactTimeAvg">骞冲潎瑙﹀湴鏃堕棿</string>
+    <string name="impactAvg">骞冲潎褰卞搷</string>
+    <string name="impactMax">鏈�澶у奖鍝�</string>
+    <string name="swingAngleAvg">骞冲潎鎽嗚</string>
+    <string name="foreFootLandings">鍓嶈剼钀藉湴</string>
+    <string name="midFootLandings">涓冻钀藉湴</string>
+    <string name="backFootLandings">鍚庤剼钀藉湴</string>
+    <string name="eversionAngleAvg">骞冲潎澶栫炕瑙掑害</string>
+    <string name="eversionAngleMax">鏈�澶у缈昏搴�</string>
+    <string name="fmtPaceDistance">姝ラ�� %d 璺濈</string>
+    <string name="fmtPaceType">姝ラ�� %d 绫诲瀷</string>
+    <string name="fmtPacePace">姝ラ�� %d 姝ラ��</string>
+    <string name="degrees">搴�</string>
+    <string name="fmtPaceCorrection">姝ラ�� %d 淇</string>
+    <string name="fmtPaceTypeAverage">閰嶉�熺被鍨� %d 骞冲潎鍊�</string>
+    <string name="unknownDataEncountered">閬囧埌鏈煡鏁版嵁</string>
+    <string name="milliseconds">姣</string>
+    <string name="Pace">姝ヤ紣</string>
+    <string name="RunningForm">璺戞褰㈠紡</string>
+    <string name="pref_force_enable_heartrate_support">寮哄埗鏀寔蹇冪巼</string>
+    <string name="pref_force_enable_spo2_support">寮哄埗鏀寔琛�姘�</string>
+    <string name="pref_force_enable_heartrate_support_summary">寮哄埗鍚敤蹇冪巼鏀寔銆�
+\n椋庨櫓鑷媴</string>
+    <string name="pref_force_enable_spo2_support_summary">寮哄埗鍚敤琛�姘ф敮鎸併��
+\n椋庨櫓鑷媴</string>
+    <string name="pref_dashboard_devices_to_include">瑕佸寘鎷殑璁惧</string>
+    <string name="pref_dashboard_all_devices_title">鎵�鏈夎澶�</string>
+    <string name="pref_dashboard_select_devices_title">閫夋嫨璁惧...</string>
+    <string name="pref_dashboard_widgets_order_summary">閫夋嫨鍚敤鍝簺灏忛儴浠朵互鍙婂畠浠湪浠〃鏉夸笂鐨勬樉绀洪『搴�</string>
+    <string name="pref_dashboard_widget_today_24h_summary">浠ュ崟涓� 24 灏忔椂鍦嗗湀鑰屼笉鏄弻 12 灏忔椂鍦嗗湀鏄剧ず娲诲姩</string>
+    <string name="pref_dashboard_widget_show_legend_summary">鍦ㄥ皬閮ㄤ欢涓嬫柟鏄剧ず璇存槑棰滆壊鐨勫浘渚�</string>
+    <string name="pref_dashboard_select_devices_summary">灏嗘潵鑷壒瀹氳澶囩殑娲诲姩鏁版嵁鍚堝苟鍒颁华琛ㄦ澘涓婄殑鎬昏涓�</string>
+    <string name="activity_type_worn">浣╂埓</string>
+    <string name="bottom_nav_dashboard">鍋ュ悍</string>
+    <string name="error_showing_changelog">鏄剧ず鍙樻洿鏃ュ織鏃跺嚭閿�</string>
+    <string name="dashboard_settings">浠〃鏉胯缃�</string>
+    <string name="bottom_nav_devices">璁惧</string>
+    <string name="pref_dashboard_first_title">棣栧厛鏄剧ず浠〃鏉�</string>
+    <string name="pref_dashboard_first_summary">Gadgetbridge 鍚姩鏃舵樉绀轰华琛ㄦ澘锛岃�屼笉鏄澶囧睆骞�</string>
+    <string name="pref_dashboard_cards_title">鍦ㄥ崱鐗囦笂鏄剧ず灏忛儴浠�</string>
+    <string name="pref_dashboard_cards_summary">鍦ㄤ华琛ㄦ澘涓婄殑灏忛儴浠跺懆鍥寸粯鍒跺崱鐗�</string>
+    <string name="pref_dashboard_widget_settings">灏忛儴浠惰缃�</string>
+    <string name="pref_dashboard_widget_today_title">娲诲姩鍥捐〃</string>
+    <string name="pref_dashboard_widget_today_24h_title">24灏忔椂妯″紡</string>
+    <string name="pref_dashboard_widget_double_size_title">鍙屽�嶅昂瀵�</string>
+    <string name="pref_dashboard_widget_goals_chart_title">鐩爣鍥捐〃</string>
+    <string name="pref_dashboard_widget_show_legend_title">鏄剧ず鍥句緥</string>
+    <string name="pref_dashboard_widget_double_size_summary">鍏佽灏忛儴浠跺崰鎹华琛ㄦ澘涓婄殑涓ゅ垪</string>
+    <string name="pref_dashboard_all_devices_summary">灏嗘墍鏈夋坊鍔犺澶囩殑娲诲姩鏁版嵁鍚堝苟鍒颁华琛ㄦ澘涓婄殑鎬昏涓�</string>
+    <string name="pref_dashboard_widget_today_hr_interval_title">蹇冪巼鍖洪棿</string>
+    <string name="pref_dashboard_widget_today_hr_interval_summary">姣忔鎴愬姛娴嬮噺蹇冪巼鍚庡浘琛ㄦ樉绀衡�滀僵鎴粹�濈殑鍒嗛挓鏁�</string>
+    <string name="pref_auto_reply_calls_summary">鎵嬫満灏嗚嚜鍔ㄦ帴鍚潵鐢�</string>
+    <string name="pref_auto_reply_calls_title">鑷姩鎺ュ惉鐢佃瘽</string>
+    <string name="pref_auto_reply_calls_delay_summary">鑷姩鎺ュ惉鐢佃瘽涔嬪墠鐨勭鏁�</string>
+    <string name="pref_auto_reply_calls_delay_title">鑷姩搴旂瓟寤惰繜</string>
+    <string name="pref_header_audio">闊抽</string>
+    <string name="pref_header_generic">鏅��</string>
+    <string name="pref_title_notification_wake_on_open">鑷姩鍞ら啋骞惰В閿�</string>
+    <string name="pref_summary_notification_wake_on_open">褰� gadget 鍙戝洖 OPEN 鍝嶅簲鏃讹紝鍞ら啋骞惰В閿� Android 璁惧銆傞渶瑕佸浜庡彲淇$姸鎬併��</string>
+    <string name="widget_layout_top_wide_bot_large">涓婇潰瀹斤紝涓嬮潰澶�</string>
+    <string name="widget_layout_top_large_bot_wide">涓婇潰澶э紝涓嬮潰瀹�</string>
+    <string name="widget_name_colored_tile">%1$s 锛堝僵鑹茬璐达級</string>
+    <string name="devicetype_redmi_watch_4">绾㈢背鎵嬭〃 4</string>
+    <string name="pref_speak_notifications_aloud_summary">閫氱煡灏嗛�氳繃鑰虫満澶у0鏈楄</string>
+    <string name="pref_speak_notifications_aloud_title">澶у0璇村嚭閫氱煡</string>
+    <string name="widget_name_untitled">鏃犳爣棰樺皬閮ㄤ欢锛�%1$s锛�</string>
+    <string name="pref_header_calls_and_notifications">鏉ョ數鍜岄�氱煡</string>
+    <string name="armenian">浜氱編灏间簹璇�</string>
+    <string name="pref_title_bottom_navigation_bar">搴曢儴瀵艰埅鏍�</string>
+    <string name="pref_summary_bottom_navigation_bar_off">浠呬娇鐢ㄦ按骞虫粦鍔ㄥ湪涓诲睆骞曚箣闂村垏鎹�</string>
+    <string name="pref_summary_bottom_navigation_bar_on">浣跨敤瀵艰埅鏍忔垨妯悜婊戝姩鍦ㄤ富灞忓箷涔嬮棿鍒囨崲</string>
+    <string name="pref_dashboard_widget_today_upside_down_summary">鍦� 24 灏忔椂妯″紡涓嬶紝鍦ㄥ浘琛ㄥ簳閮ㄧ粯鍒跺崍澶滐紝鍦ㄥ浘琛ㄩ《閮ㄧ粯鍒朵腑鍗�</string>
+    <string name="pref_dashboard_widget_today_upside_down_title">鍗堝鍦ㄥ簳閮�</string>
+    <string name="pref_sleepasandroid_device_summary">閫夋嫨 Sleep As Android 浣滀负璁惧鏁版嵁鎻愪緵鑰�</string>
+    <string name="pref_sleepasandroid_device_title">璁惧鎻愪緵鑰�</string>
+    <string name="pref_sleepasandroid_features_title">鐗圭偣</string>
+    <string name="pref_sleepasandroid_features_summary">鏀寔鍥犺澶囪�屽紓</string>
+    <string name="pref_sleepasandroid_feat_movement">鍔犻�熻</string>
+    <string name="pref_sleepasandroid_feat_heartrate">蹇冪巼</string>
+    <string name="pref_sleepasandroid_feat_oximetry">琛�姘чケ鍜屽害</string>
+    <string name="pref_sleepasandroid_feat_spo2">SPO2</string>
+    <string name="pref_sleepasandroid_feat_alarms">闂归挓</string>
+    <string name="pref_sleepasandroid_slot_summary">璁剧疆闂归挓鏃朵娇鐢ㄥ摢涓椆閽熸Ы</string>
+    <string name="pref_sleepasandroid_slot_title">闂归挓妲�</string>
+    <string name="alarm_slot_reset">闂归挓妲藉凡璁剧疆涓洪粯璁ゅ��</string>
+    <string name="pref_sleepasandroid_feat_notifications">閫氱煡</string>
+    <string name="sleepasandroid_settings">Sleep As Android</string>
+    <string name="pref_summary_huawei_account">閰嶅杩囩▼涓娇鐢ㄧ殑鍗庝负甯愬彿銆� 璁剧疆瀹冧互鍏佽鍦ㄤ笉鎭㈠鍑哄巶璁剧疆鐨勬儏鍐典笅杩涜閰嶅銆�</string>
+    <string name="pref_title_huawei_account">鍗庝负璐﹀彿</string>
+    <string name="serbian">濉炲皵缁翠簹璇�</string>
+    <string name="pref_sleepasandroid_enable_summary">鍚敤 Sleep As Android 闆嗘垚</string>
+    <string name="watchface_resolution_doesnt_match">琛ㄧ洏鍒嗚鲸鐜囦笌璁惧灞忓箷涓嶅尮閰嶃�傝〃鐩樹负 %1$s 璁惧灞忓箷涓� %2$s</string>
+    <string name="cyclingPowerMin">鏈�灏忛獞琛屽姛鐜�</string>
+    <string name="cyclingPowerAverage">骞冲潎楠戣鍔熺巼</string>
+    <string name="cyclingPowerMax">鏈�澶ч獞琛屽姛鐜�</string>
+    <string name="devicetype_huawei_watchfit2">鍗庝负 Watch Fit 2</string>
+    <string name="huawei_trusleep_warning">璀﹀憡锛氬惎鐢ㄦ鍔熻兘灏嗕娇鎵�鏈夌潯鐪犲湪 Gadgetbridge 涓樉绀轰负娴呯潯鐪狅紒濡傛灉鎮ㄦ帴鍙楋紝璇风偣鍑绘澶勩��</string>
+    <string name="pref_touch_tone">鎸夐敭闊�</string>
+    <string name="pref_wearing_tone">绌挎埓鎻愮ず闊�</string>
+    <string name="pref_wearing_tone_summary">鎻掑叆鑰虫満鏃跺彂鍑烘彁绀洪煶</string>
+    <string name="pref_adaptive_noise_cancelling_title">鑷�傚簲涓诲姩闄嶅櫔</string>
+    <string name="pref_touch_tone_summary">瑙︽懜鑰虫満鏃朵細鍙戝嚭鎻愮ず闊�</string>
+    <string name="devicetype_soundcore_liberty3_pro">Soundcore Liberty 3 Pro</string>
+    <string name="pref_adaptive_noise_cancelling_summary">鏍规嵁鐜澹扮骇鑷姩璁剧疆鑷姩闄嶅櫔寮哄害</string>
+    <string name="devicetype_huawei_watchfit3">鍗庝负鍋ヨ韩鎵嬭〃 3</string>
+    <string name="devicetype_huawei_band9">鍗庝负鎵嬬幆 9</string>
+    <string name="devicetype_huawei_watch4pro">鍗庝负 Watch 4 (Pro)</string>
+    <string name="preferences_qhybrid_settings_summary">Q Hybrid 鎵嬭〃鐨勬棫璁剧疆</string>
+    <string name="device_name_cycling_sensor">楠戣浼犳劅鍣�</string>
+    <string name="devicetype_cycling_sensor">楠戣閫熷害浼犳劅鍣�</string>
+    <string name="title_cycling">楠戣嚜琛岃溅</string>
+    <string name="pref_title_wheel_diameter">杞緞</string>
+    <string name="pref_summary_cycling_persistence_interval">褰撳墠楠戣鏁版嵁搴斿啓鍏ユ暟鎹簱鐨勬椂闂撮棿闅旓紙浠ョ涓哄崟浣嶏級</string>
+    <string name="chart_cycling_point_label_distance">浠婂ぉ锛�%.1f 鍏噷
+\n鎬昏锛�%.1f 鍏噷</string>
+    <string name="chart_cycling_point_label_speed">%.1f 鍏噷/灏忔椂</string>
+    <string name="device_name_bicycle_sensor">鑷杞︿紶鎰熷櫒</string>
+    <string name="pref_summary_wheel_diameter">杞﹁疆鐩村緞锛堣嫳瀵革級銆� 閫氬父涓� 29銆�27.5 鎴� 26銆�</string>
+    <string name="pref_title_cycling_persistence_interval">鎸佺画鏃堕棿闂撮殧</string>
+    <string name="pref_speak_notifications_focus_exclusive_summary_on">璇诲嚭閫氱煡鍐呭鏃讹紝鏆傚仠鍏朵粬搴旂敤绋嬪簭鐨勬挱鏀�</string>
+    <string name="pref_speak_notifications_focus_exclusive_summary_off">鎾斁閫氱煡鍐呭鏃堕檷浣庡叾浠栧簲鐢ㄧ▼搴忕殑鎾斁闊抽噺</string>
+    <string name="open_camera">鎵撳紑鐩告満</string>
+    <string name="toast_camera_permission_required">姝ゅ姛鑳介渶瑕佺浉鏈烘潈闄愩��</string>
+    <string name="toast_camera_support_required">姝ゅ姛鑳介渶瑕佺浉鏈烘敮鎸併��</string>
+    <string name="toast_camera_photo_taken">鐓х墖宸叉媿鎽勫苟淇濆瓨鍦細%s</string>
+    <string name="pref_speak_notifications_focus_exclusive_title">鏆傚仠鍏朵粬搴旂敤绋嬪簭鐨勯煶棰�</string>
+    <string name="toast_setting_requires_reconnect">姝よ缃皢鍦ㄩ噸鏂拌繛鎺ュ悗鐢熸晥</string>
+    <string name="devicetype_amazfit_bip5_unity">璺冩垜 Bip 5 Unity</string>
+    <string name="pref_agps_status">AGPS 鐘舵��</string>
+    <string name="agps_status_missing">涓㈠け</string>
+    <string name="agps_status_pending">绛夊緟涓�</string>
+    <string name="agps_status_current">褰撳墠</string>
+    <string name="agps_status_error">閿欒</string>
+    <string name="devicetype_garmin_venu_3">浣虫槑 Venu 3</string>
+    <string name="devicetype_garmin_instinct_solar">浣虫槑 Instinct Solar</string>
+    <string name="devicetype_garmin_instinct_2s">浣虫槑 Instinct 2S</string>
+    <string name="devicetype_garmin_instinct_crossover">浣虫槑 Instinct Crossover</string>
+    <string name="devicetype_garmin_forerunner_245">浣虫槑 Forerunner 245</string>
+    <string name="devicetype_garmin_swim_2">浣虫槑 Swim 2</string>
+    <string name="devicetype_garmin_vivoactive_4">浣虫槑 V铆voactive 4</string>
+    <string name="devicetype_garmin_vivoactive_4s">浣虫槑 V铆voactive 4S</string>
+    <string name="copied_to_clipboard">宸插鍒跺埌鍓创鏉�</string>
+    <string name="devicetype_garmin_vivomove_hr">浣虫槑 V铆vomove HR</string>
+    <string name="devicetype_garmin_vivomove_style">浣虫槑 V铆vomove Style</string>
+    <string name="devicetype_garmin_instinct_2_solar">浣虫槑 Instinct 2 Solar</string>
+    <string name="devicetype_garmin_instinct_2_soltac">浣虫槑 Instinct 2 SolTac</string>
+    <string name="pref_title_garmin_default_reply_suffix">浣跨敤棰勫畾涔夌殑鍥炲鍚庣紑</string>
+    <string name="devicetype_garmin_vivoactive_5">浣虫槑 V铆voactive 5</string>
+    <string name="pref_garmin_agps_help">涓嬮潰鐨勫垪琛ㄥ寘鍚墜琛ㄨ姹傜殑 AGPS 鏇存柊鐨勬墍鏈夌綉鍧�銆� 鎮ㄥ彲浠ヤ粠鎵嬫満瀛樺偍涓�夋嫨涓�涓枃浠讹紝褰撴墜琛ㄨ姹傛洿鏂版椂锛岃鏂囦欢灏嗗彂閫佸埌鎵嬭〃銆�</string>
+    <string name="pref_summary_garmin_default_reply_suffix">娣诲姞鍒� Gadgetbridge 涓缃殑鍚庣紑涔嬪</string>
+    <string name="no_folder_selected">鏈�夋嫨鏂囦欢澶�</string>
+    <string name="none">鏃�</string>
+    <string name="garmin_agps_url_i">AGPS %1$d 缃戝潃</string>
+    <string name="folder_is_empty">鏂囦欢澶规槸绌虹殑</string>
+    <string name="folder">鏂囦欢澶�</string>
+    <string name="url">缃戝潃</string>
+    <string name="garmin_agps_local_file">鏈湴鏂囦欢</string>
+    <string name="devicetype_garmin_venu_2_plus">浣虫槑 Venu 2 Plus</string>
+    <string name="devicetype_garmin_instinct_2s_solar">浣虫槑 Instinct 2S Solar</string>
+    <string name="devicetype_garmin_vivosmart_5">浣虫槑 V铆vosmart 5</string>
+    <string name="devicetype_garmin_epix_pro">浣虫槑 Epix Pro</string>
+    <string name="devicetype_garmin_instinct_2x_solar">浣虫槑 Instinct 2X Solar</string>
+    <string name="intent_api_broadcast_activity_sync_title">娲诲姩鍚屾瀹屾垚鏃跺箍鎾�</string>
+    <string name="intent_api_broadcast_activity_sync_summary">褰撲换浣曡澶囩殑娲诲姩鍚屾瀹屾垚鏃跺箍鎾剰鍥�</string>
+    <string name="devicetype_garmin_fenix_6_sapphire">浣虫槑 Fenix 6 Sapphire</string>
+    <string name="loading">鍔犺浇涓��</string>
+    <string name="activity_prefs_height_inches">浠ヨ嫳瀵镐负鍗曚綅鐨勯珮搴�</string>
+    <string name="toggle_debug_mode">鍒囨崲璋冭瘯妯″紡</string>
+    <string name="share_debug_info">鍒嗕韩璋冭瘯淇℃伅</string>
+    <string name="realtime_settings">瀹炴椂璁剧疆</string>
+    <string name="unsupported">涓嶆敮鎸�</string>
+    <string name="max_val">鏈� 澶у�硷細 %d</string>
+    <string name="min_val">鏈�灏忓�硷細 %d</string>
+    <string name="devicetype_garmin_venu_3s">浣虫槑 Venu 3S</string>
+    <string name="devicetype_garmin_fenix_7_pro">浣虫槑 Fenix 7 Pro</string>
+    <string name="notif_battery_full_title">Gadget 鐢垫睜宸插厖婊★紒</string>
+    <string name="notif_battery_full">%1$s 鐢垫睜宸叉弧</string>
+    <string name="authentication_failed_check_key">韬唤楠岃瘉澶辫触锛岃妫�鏌ヨ韩浠介獙璇佸瘑閽�</string>
+    <string name="notification_channel_full_battery_name">婊$數</string>
+    <string name="battery_i">鐢甸噺 %d</string>
+    <string name="show_in_notification">鍦ㄩ�氱煡涓樉绀�</string>
+    <string name="battery_low_notify_enabled">鐢垫睜鐢甸噺浣庢椂閫氱煡</string>
+    <string name="battery_low_threshold">浣庣數閲忛槇鍊�</string>
+    <string name="battery_full_notify_enabled">鐢垫睜鍏呮弧鏃堕�氱煡</string>
+    <string name="battery_full_threshold">婊$數闃堝��</string>
+    <string name="default_percentage">榛樿锛�%1$d%%锛�</string>
+    <string name="devicetype_garmin_fenix_7s">浣虫槑 Fenix 7S</string>
+    <string name="battery_percentage_str">%1$s%%</string>
+    <string name="devicetype_garmin_forerunner_265">浣虫槑 Forerunner 265</string>
+    <string name="devicetype_garmin_forerunner_255">浣虫槑 Forerunner 255</string>
+    <string name="devicetype_hama_fit6900">Hama Fit6900</string>
+    <string name="pref_fetch_unknown_files_title">鑾峰彇鏈煡鏂囦欢</string>
+    <string name="dashboard_calendar_month_goals_reached_title">% 姣忔棩杈炬爣姝ユ暟</string>
+    <string name="pref_fetch_unknown_files_summary">浠庢墜琛ㄤ腑鑾峰彇鏈煡鐨勬椿鍔ㄦ枃浠躲�� 瀹冧滑涓嶄細琚鐞嗭紝浣嗕細淇濆瓨鍦ㄦ墜鏈轰腑銆�</string>
+    <plurals name="amount_of_days">
+        <item quantity="other">%d 澶�</item>
+    </plurals>
+    <string name="devicetype_garmin_venu_2">浣虫槑 Venu 2</string>
+    <string name="devicetype_garmin_forerunner_255s">浣虫槑 Forerunner 255S</string>
+    <string name="devicetype_garmin_instinct">浣虫槑 Instinct</string>
+    <string name="moondrop_touch_trigger">瑙﹀彂鍣�</string>
+    <string name="moondrop_touch_action_play_pause">鎾斁/鏆傚仠</string>
+    <string name="moondrop_touch_action_call_pick_hang">鎺ュ惉/鎸傛柇鐢佃瘽</string>
+    <string name="moondrop_touch_action_call_start">寮�濮嬮�氳瘽</string>
+    <string name="moondrop_touch_action_assistant">瑙﹀彂璇煶鍔╂墜</string>
+    <string name="moondrop_touch_action_anc_mode">鍒囨崲涓诲姩闄嶅櫔妯″紡</string>
+    <string name="moondrop_touch_trigger_long_press_1s">闀挎寜锛�1绉掞級</string>
+    <string name="moondrop_touch_trigger_long_press_3s">闀挎寜锛�3绉掞級</string>
+    <string name="devicetype_moondrop_space_travel">鏈堣惤澶┖鏃呰</string>
+    <string name="moondrop_equalizer_preset_reference">鍙傝��</string>
+    <string name="moondrop_equalizer_preset_monitor">鏄剧ず鍣�</string>
+    <string name="moondrop_touch_earbud">鑰冲</string>
+    <string name="moondrop_touch_earbud_both">涓よ��</string>
+    <string name="moondrop_equalizer_preset_basshead">浣庨煶鐐�</string>
+    <string name="devicetype_garmin_fenix_6">浣虫槑 Fenix 6</string>
+    <string name="devicetype_garmin_fenix_5">浣虫槑 Fenix 5</string>
+    <string name="devicetype_garmin_vivoactive_3">浣虫槑 V铆voactive 3</string>
+    <string name="pref_battery_polling_interval_format">姣� %1$s 鍒嗛挓</string>
+    <string name="pref_battery_polling_interval">鐢垫睜杞闂撮殧</string>
+    <string name="proprietary_app_warning">姝ゅ姛鑳介渶瑕佸畨瑁呬笓鏈夊簲鐢ㄧ▼搴�</string>
+    <string name="devicetype_garmin_vivomove_trend">浣虫槑 V铆vomove Trend</string>
+    <string name="pref_battery_polling_configuration">鐢垫睜杞閰嶇疆</string>
+    <string name="pref_battery_polling_summary">杩欐槸灏藉姏鑰屼负锛屽彲鑳戒細鍥犲绉嶅師鍥犺�屽欢杩�</string>
+    <string name="pref_battery_polling_enable">鍚敤鐢垫睜杞</string>
+    <string name="battery_polling_failed_start">鏃犳硶鍚姩鐢垫睜杞</string>
+    <string name="devicetype_garmin_vivosport">浣虫槑 V铆vosport</string>
+    <string name="devicetype_nothing_cmf_watch_pro_2">CMF Watch Pro 2</string>
+    <string name="activity_type_beach_soccer">娌欐哗瓒崇悆</string>
+    <string name="activity_type_auto_racing">璧涜溅</string>
+    <string name="activity_type_parkour">璺戦叿</string>
+    <string name="activity_type_tae_bo">璺嗘悘</string>
+    <string name="activity_type_gateball">闂ㄧ悆</string>
+    <string name="activity_type_beach_volleyball">娌欐哗鎺掔悆</string>
+    <string name="activity_type_sepak_takraw">韪㈣棨鐞�</string>
+    <string name="activity_type_parachuting">璺充紴</string>
+    <string name="activity_type_luge">闆﹪</string>
+    <string name="pref_title_notification_times_enabled">閫氱煡鏃堕棿</string>
+    <string name="pref_summary_notification_times_enabled">浠呭湪鐗瑰畾鏃堕棿涔嬮棿鍙戦�侀�氱煡</string>
+    <string name="devicetype_huawei_watch3">鍗庝负鎵嬭〃3 Pro</string>
+    <string name="devicetype_vivitar_hr_bp_monitor_activity_tracker">Vivitar 蹇冪巼鍜岃鍘嬬洃娴嬫椿鍔ㄨ拷韪櫒</string>
+    <string name="devicetype_nothing_cmf_buds_pro_2">CMF Buds Pro 2</string>
+    <string name="devicetype_garmin_forerunner_255s_music">浣虫槑 Forerunner 255S Music</string>
+    <string name="devicetype_garmin_forerunner_255_music">浣虫槑 Forerunner 255 Music</string>
+    <string name="devicetype_huawei_watchgtrunner">鍗庝负鎵嬭〃GT鏄熷璺戣��</string>
+    <string name="find_my_phone_companion_warning">鏌ユ壘鎵嬫満闇�瑕佸悓浼撮厤瀵广�� 鐐瑰嚮杩欓噷鏌ョ湅鏇村淇℃伅銆�</string>
+    <string name="abstract_chart_fragment_kind_awake_sleep">娓呴啋</string>
+    <string name="sleep_colored_stats_light">娴�</string>
+    <string name="sleep_colored_stats_rem">蹇�熺溂鍔ㄧ潯鐪�</string>
+    <string name="sleep_colored_stats_deep_avg">骞冲潎娣卞害</string>
+    <string name="sleep_colored_stats_light_avg">骞冲潎娴呭害</string>
+    <string name="sleep_colored_stats_rem_avg">骞冲潎蹇�熺溂鍔ㄧ潯鐪�</string>
+    <string name="stats_empty_value">-</string>
+    <string name="stats_lowest_hr">鏈�浣庡績鐜�</string>
+    <string name="stats_highest_hr">鏈�楂樺績鐜�</string>
+    <string name="hrv_status_day_avg">鏃ュ钩鍧�</string>
+    <string name="hrv_status_seven_days_avg">7 澶╁钩鍧�</string>
+    <string name="hrv_status_seven_days_avg_status">鐘舵��</string>
+    <string name="hrv_status_balanced">骞宠 </string>
+    <string name="hrv_status_low">浣�</string>
+    <string name="hrv_status_poor">缂轰箯</string>
+    <string name="hrv_status_last_night">鏈�鍚庝竴鏅�</string>
+    <string name="hrv_status_last_night_highest_5">鏈�鍚庝竴鏅� 5 鍒嗛挓骞虫渶楂樺潎鍊�</string>
+    <string name="hrv_status_seven_days_avg_long">7澶╁钩鍧�</string>
+    <string name="hrv_status_unit">%1$d 姣</string>
+    <string name="hrv_status_baseline">%1$d-%2$d 姣</string>
+    <string name="hrv_status_baseline_label">鍩虹嚎</string>
+    <string name="pref_header_hrv_status">蹇冪巼鍙樺紓鐘跺喌</string>
+    <string name="hrv_status">蹇冪巼鍙樺紓鐘跺喌</string>
+    <string name="sleep_colored_stats_deep">娣�</string>
+    <string name="hrv_status_day_avg_legend">鏃ュ钩鍧囷紙姣锛�</string>
+    <string name="hrv_status_unbalanced">澶辫 </string>
+    <string name="devicetype_garmin_forerunner_965">浣虫槑 Forerunner 965</string>
+    <string name="gps_glonass">GPS + 鏍兼礇绾虫柉</string>
+    <string name="devicetype_garmin_fenix_5_plus">浣虫槑 Fenix 5 Plus</string>
+    <string name="minutes_20">20 鍒嗛挓</string>
+    <string name="minutes_60">60 鍒嗛挓</string>
+    <string name="soundcore_button_brightness_low">浣�</string>
+    <string name="soundcore_ldac_mode_summary">鍚敤 LDAC 灏嗙缉鐭數姹犲鍛藉苟鍙兘瀵艰嚧杩炴帴涓嶇ǔ瀹�</string>
+    <string name="soundcore_adaptive_direction_title">鑷�傚簲鏂瑰悜</string>
+    <string name="soundcore_equalizer_preset_signature">澹伴様绛惧悕</string>
+    <string name="soundcore_equalizer_custom_summary">閰嶇疆鍙傛暟鍧囪 鍣ㄨ缃�</string>
+    <string name="soundcore_equalizer_direction">璁惧鏂瑰悜</string>
+    <string name="soundcore_equalizer_direction_lying">韬轰笅</string>
+    <string name="soundcore_equalizer_direction_hanging">澶存埓</string>
+    <string name="soundcore_equalizer_reset_title">鎭㈠榛樿</string>
+    <string name="soundcore_equalizer_reset_summary">灏嗘墍鏈夊潎琛″櫒棰戞鎭㈠涓洪粯璁よ缃�</string>
+    <string name="soundcore_equalizer_band1">棰戞 1</string>
+    <string name="soundcore_equalizer_band2">棰戞 2</string>
+    <string name="soundcore_equalizer_band3">棰戞 3</string>
+    <string name="soundcore_equalizer_band4">棰戞 4</string>
+    <string name="soundcore_equalizer_band5">棰戞 5</string>
+    <string name="soundcore_equalizer_band6">棰戞 6</string>
+    <string name="soundcore_equalizer_band8">棰戞 8</string>
+    <string name="soundcore_equalizer_band9">棰戞 9</string>
+    <string name="devicetype_soundcore_motion300">澹伴様 Motion 300</string>
+    <string name="soundcore_voice_prompts">璇煶鎻愮ず</string>
+    <string name="soundcore_button_brightness">鎸夐挳浜害</string>
+    <string name="soundcore_button_brightness_medium">涓�</string>
+    <string name="soundcore_equalizer_preset">棰勮</string>
+    <string name="soundcore_equalizer_preset_xtra_bass">瓒呬綆闊�</string>
+    <string name="soundcore_ldac_mode_title">LDAC 妯″紡</string>
+    <string name="soundcore_button_brightness_high">楂�</string>
+    <string name="soundcore_adaptive_direction_summary">鏍规嵁璁惧鏂瑰悜鑷姩璋冩暣鍧囪 鍣ㄩ璁�</string>
+    <string name="soundcore_equalizer_preset_balanced">鍧囪 </string>
+    <string name="soundcore_equalizer_preset_voice">璇煶</string>
+    <string name="soundcore_equalizer_custom_title">瀹氬埗鈥�</string>
+    <string name="soundcore_equalizer_direction_standing">绔欑珛</string>
+    <string name="soundcore_equalizer_band7">棰戞 7</string>
+    <string name="soundcore_equalizer_value">鍊�</string>
+    <string name="soundcore_equalizer_frequency">棰戠巼</string>
+    <string name="body_energy_gained">宸茶幏寰�</string>
+    <string name="body_energy_legend_level">韬綋鑳介噺姘村钩</string>
+    <string name="body_energy">韬綋鑳介噺</string>
+    <string name="body_energy_lost">宸叉祦澶�</string>
+    <string name="transition">杩囨浮</string>
+    <string name="activity_type_handcycling">鎵嬫憞涓夎疆杞�</string>
+    <string name="activity_type_bike_commute">鑷杞﹂�氬嫟</string>
+    <string name="activity_type_transition">杩囨浮</string>
+    <string name="activity_type_fitness_equipment">鍋ヨ韩鍣ㄦ潗</string>
+    <string name="activity_type_breathwork">鍛煎惛娉�</string>
+    <string name="activity_type_flying">椋炶</string>
+    <string name="activity_type_boating">鍒掕埞</string>
+    <string name="activity_type_driving">椹鹃┒</string>
+    <string name="activity_type_bouldering">鎶辩煶</string>
+    <string name="activity_type_sail_race">甯嗚埞姣旇禌</string>
+    <string name="activity_type_sail_expedition">鑸鎺㈤櫓</string>
+    <string name="activity_type_ice_skating">婧滃啺</string>
+    <string name="activity_type_snowmobiling">闆湴鎽╂墭</string>
+    <string name="activity_type_stand_up_paddleboarding">绔欑珛寮忔〃鏉垮啿娴�</string>
+    <string name="activity_type_wakeboarding">灏炬尝婊戞按</string>
+    <string name="activity_type_kayaking">鐨垝鑹�</string>
+    <string name="activity_type_rafting">婕傛祦</string>
+    <string name="activity_type_tactical">鎴樻湳璋嬪垝</string>
+    <string name="activity_type_jumpmaster">璺宠穬澶у笀</string>
+    <string name="activity_type_floor_climbing">鐖ゼ</string>
+    <string name="activity_type_softball_slow_pitch">鍨掔悆鎱㈡姇</string>
+    <string name="activity_type_grinding">婊戝潯</string>
+    <string name="activity_type_racket">鐞冩媿</string>
+    <string name="activity_type_padel">璺屽��</string>
+    <string name="activity_type_para_sport">涓轰簡杩愬姩</string>
+    <string name="activity_type_disc_golf">椋炵洏楂樺皵澶�</string>
+    <string name="activity_type_ultimate_disc">鏃犻檺鍒堕鐩�</string>
+    <string name="activity_type_lacrosse">闀挎洸妫嶇悆</string>
+    <string name="activity_type_water_tubing">姘寸</string>
+    <string name="activity_type_aerobic_combo">鏈夋哀缁勫悎</string>
+    <string name="activity_type_air_walker">绌轰腑婕鑰�</string>
+    <string name="activity_type_bmx">灏忚疆杞�</string>
+    <string name="activity_type_bridge">妗�</string>
+    <string name="activity_type_cardio_combat">鏈夋哀杩愬姩</string>
+    <string name="activity_type_checkers">璺虫</string>
+    <string name="activity_type_chess">妫�</string>
+    <string name="activity_type_hacky_sack">鍝堝熀楹昏</string>
+    <string name="activity_type_hip_hop">鍢诲搱</string>
+    <string name="activity_type_judo">鏌旈亾</string>
+    <string name="activity_type_mass_gymnastics">缇や紬浣撴搷</string>
+    <string name="activity_type_modern_dance">鐜颁唬鑸�</string>
+    <string name="activity_type_parallel_bars">鍙屾潬</string>
+    <string name="activity_type_shuffleboard">娌欑嫄鐞�</string>
+    <string name="activity_type_tug_of_war">鎷夐敮鎴�</string>
+    <string name="activity_type_weiqi">鍥存</string>
+    <string name="activity_type_somatosensory_game">浣撴劅娓告垙</string>
+    <string name="activity_type_navigate">瀵艰埅</string>
+    <string name="activity_type_indoor_track">瀹ゅ唴璺戦亾</string>
+    <string name="activity_type_cardio">鏈夋哀杩愬姩</string>
+    <string name="activity_type_e_bike">鐢靛崟杞�</string>
+    <string name="activity_type_american_football">缇庡紡瓒崇悆</string>
+    <string name="activity_type_mountaineering">鐧诲北</string>
+    <string name="activity_type_paddling">鍒掓〃</string>
+    <string name="activity_type_handcycling_indoor">瀹ゅ唴鎵嬫憞涓夎疆杞�</string>
+    <string name="activity_type_platform_tennis">绗煎紡缃戠悆</string>
+    <string name="activity_type_training">璁粌</string>
+    <string name="activity_type_xc_classic_ski">XC 缁忓吀婊戦洩</string>
+    <string name="activity_type_multisport">澶氶」杩愬姩</string>
+    <string name="activity_type_motorcycling">鎽╂墭杞�</string>
+    <string name="activity_type_hang_gliding">婊戠繑缈�</string>
+    <string name="activity_type_inline_skating">鐩存帓杞粦</string>
+    <string name="activity_type_climb_indoor">瀹ゅ唴鏀�鐖�</string>
+    <string name="activity_type_sky_diving">璺充紴</string>
+    <string name="activity_type_snowshoe">闆瀷</string>
+    <string name="activity_type_racquetball">澹佺悆</string>
+    <string name="activity_type_health_snapshot">鍋ュ悍蹇収</string>
+    <string name="activity_type_marine">娴锋磱</string>
+    <string name="activity_type_indoor_push_walk_speed">瀹ゅ唴鎺� - 姝ヨ閫熷害</string>
+    <string name="activity_type_futsal">浜斾汉鍒惰冻鐞�</string>
+    <string name="activity_type_push_walk_speed">鎺� - 琛岃蛋閫熷害</string>
+    <string name="activity_type_push_run_speed">鎺� - 濂旇窇閫熷害</string>
+    <string name="activity_type_spinning">鍔ㄦ劅鍗曡溅</string>
+    <string name="activity_type_mixed_martial_arts">缁煎悎姝︽湳</string>
+    <string name="activity_type_aerobics">鏈夋哀杩愬姩</string>
+    <string name="activity_type_frisbee">椋炵洏</string>
+    <string name="activity_type_indoor_push_run_speed">瀹ゅ唴鎺ㄥ姏 - 濂旇窇閫熷害</string>
+    <string name="activity_type_meditation">鍐ユ兂</string>
+    <string name="activity_type_team_sport">鍥㈤槦杩愬姩</string>
+    <string name="activity_type_wakesurfing">婊戞按鍐叉氮</string>
+    <string name="activity_type_ballroom_dance">浜よ皧鑸�</string>
+    <string name="activity_type_artistic_swimming">鑺辨牱娓告吵</string>
+    <string name="activity_type_bocce">鍦版幏鐞�</string>
+    <string name="activity_type_breaking">琛楄垶</string>
+    <string name="activity_type_dragon_boat">榫欒垷</string>
+    <string name="activity_type_esports">鐢靛瓙绔炴妧</string>
+    <string name="activity_type_folk_dance">姘戦棿鑸炶箞</string>
+    <string name="activity_type_board_game">妫嬬洏娓告垙</string>
+    <string name="activity_type_finswimming">鑴氳辜娓告吵</string>
+    <string name="activity_type_flowriding">楠戞祦</string>
+    <string name="activity_type_muay_thai">娉版嫵</string>
+    <string name="activity_type_snorkeling">娴綔</string>
+    <string name="activity_type_hula_hoop">鍛煎暒鍦�</string>
+    <string name="activity_type_jai_alai">鍥炲姏</string>
+    <string name="activity_type_jujitsu">鏌旀湳</string>
+    <string name="activity_type_pole_dance">閽㈢鑸�</string>
+    <string name="activity_type_square_dance">骞垮満鑸�</string>
+    <string name="activity_type_race_walking">绔炶蛋</string>
+    <string name="activity_type_stair_climber">鐖ゼ姊満</string>
+    <string name="activity_type_table_football">妗屼笂瓒崇悆</string>
+    <string name="activity_type_wall_ball">澧欑悆</string>
+    <string name="activity_type_water_polo">姘寸悆</string>
+    <string name="activity_type_water_skiing">婊戞按</string>
+    <string name="activity_type_shooting">灏勫嚮</string>
+    <string name="activity_type_video_gaming">鎵撴父鎴�</string>
+    <string name="activity_type_winter_sport">鍐杩愬姩</string>
+    <string name="insufficient_space_for_upload">绌洪棿涓嶈冻锛屾棤娉曚笂浼�</string>
+    <string name="cannot_upload_watchface_too_many_watchfaces_installed">鏃犳硶涓婁紶琛ㄧ洏锛屽畨瑁呯殑琛ㄧ洏杩囧</string>
+    <string name="activity_type_free_sparring">鑷敱闄粌</string>
+    <string name="activity_type_body_combat">韬綋鏍兼枟</string>
+    <string name="activity_type_plaza_dancing">骞垮満鑸�</string>
+    <string name="activity_type_laser_tag">婵�鍏夋爣绛�</string>
+    <string name="activity_type_obstacle_race">闅滅璧�</string>
+    <string name="activity_type_billiard_pool">姘存睜</string>
+    <string name="activity_type_canoeing">鍒掔嫭鏈ㄨ垷</string>
+    <string name="activity_type_water_scooter">姘翠笂婊戞澘杞�</string>
+    <string name="activity_type_bobsleigh">鏈夎埖闆﹪</string>
+    <string name="activity_type_sledding">闆﹪</string>
+    <string name="activity_type_biathlon">鍐涓ら」</string>
+    <string name="activity_type_bungee_jumping">韫︽瀬</string>
+    <string name="activity_type_orienteering">瀹氬悜杩愬姩</string>
+    <string name="devicetype_garmin_venu">浣虫槑 Venu</string>
+    <string name="authentication_failed_negotiation">璁よ瘉瀵嗛挜鍗忓晢澶辫触</string>
+    <string name="devicetype_garmin_venu_2s">浣虫槑 Venu 2S</string>
+    <string name="estimatedSweatLoss">浼拌鍑烘睏閲�</string>
+    <string name="hrv">HRV</string>
+    <string name="devicetype_mismartscale">灏忕背鏅鸿兘浣撻噸绉� 2</string>
+    <string name="devicetype_micompositionscale">灏忕背浣撹剛绉� 2</string>
+    <string name="miscale_weight_unit_imperial">鑻卞埗锛堢锛�</string>
+    <string name="miscale_weight_unit_chinese">涓崕锛堟枻锛�</string>
+    <string name="miscale_small_objects_summary">瀛樺偍閲嶉噺灏忎簬10鍏枻鐨勭墿浣�</string>
+    <string name="miscale_weight_unit_title">閲嶉噺鍗曚綅</string>
+    <string name="miscale_weight_unit_summary">璁剧疆鏄剧ず娴嬮噺鍊肩殑閲嶉噺鍗曚綅</string>
+    <string name="miscale_weight_unit_metric">鍏埗锛堝叕鏂わ級</string>
+    <string name="miscale_small_objects_title">灏忕墿浣�</string>
+    <string name="steps_avg">骞冲潎姝ユ暟</string>
+    <string name="steps_total">鎬绘鏁�</string>
+    <string name="distance_avg">骞冲潎璺濈</string>
+    <string name="steps_distance_unit">%1$,.2f 鍗冪背</string>
+    <string name="distance_total">鎬昏窛绂�</string>
+    <string name="minutes_2">2 鍒嗛挓</string>
+    <string name="minutes_3">3 鍒嗛挓</string>
+    <string name="minutes_6">6 鍒嗛挓</string>
+    <string name="minutes_7">7 鍒嗛挓</string>
+    <string name="minutes_75">75 鍒嗛挓</string>
+    <string name="minutes_120">120 鍒嗛挓</string>
+    <string name="minutes_150">150 鍒嗛挓</string>
+    <string name="minutes_180">180 鍒嗛挓</string>
+    <string name="minutes_210">210 鍒嗛挓</string>
+    <string name="pref_app_connection_duration">搴旂敤绋嬪簭杩炴帴鏃堕暱</string>
+    <string name="minutes_4">4 鍒嗛挓</string>
+    <string name="minutes_45">45 鍒嗛挓</string>
+    <string name="pref_time_sync">鑷姩鏃堕棿鍚屾</string>
+    <string name="minutes_9">9 鍒嗛挓</string>
+    <string name="minutes_90">90 鍒嗛挓</string>
+    <string name="minutes_255">255 鍒嗛挓</string>
+    <string name="user_feedback_set_settings_ok">鍙戦�佽缃埌璁惧銆�</string>
+    <string name="minutes_15">15 鍒嗛挓</string>
+    <string name="minutes_8">8 鍒嗛挓</string>
+    <string name="dateformat_day_month">鏃ワ紝鏈�</string>
+    <string name="minutes_240">240 鍒嗛挓</string>
+    <string name="dateformat_month_day">鏈堬紝鏃�</string>
+    <string name="prefs_light_duration_longer">鍏夌収鏃堕棿鏇撮暱</string>
+    <string name="devicetype_colmi_r06">Colmi R06</string>
+    <string name="smart_ring_measurement_error_unknown">浠庢垝鎸囨敹鍒版湭鐭ユ祴閲忛敊璇� %d</string>
+    <string name="busy_task_fetch_sleep_data">姝e湪鑾峰彇鐫$湢鏁版嵁</string>
+    <string name="devicetype_colmi_r02">Colmi R02</string>
+    <string name="devicetype_colmi_r03">Colmi R03</string>
+    <string name="smart_ring_measurement_error_worn_incorrectly">娴嬮噺瀛樺湪璇樊銆傛垝鎸囩殑浼犳劅鍣ㄦ柟鍚戞槸鍚︽纭紵</string>
+    <string name="stress_average">骞冲潎</string>
+    <string name="menuitem_weight">閲嶉噺</string>
+    <string name="lactateThresholdHeartRate">涔抽吀闃堝績鐜�</string>
+    <string name="devicetype_mijia_xmwsdj04">绫冲娓╂箍搴︿紶鎰熷櫒2锛堢數瀛愬ⅷ姘村睆锛�</string>
+    <string name="devicetype_honor_watchgs3">鑽h��鎵嬭〃GS 3</string>
+    <string name="sleep_colored_stats_awake_avg">骞冲潎娓呴啋</string>
+    <string name="activity_type_rem_sleep">蹇�熺溂鍔ㄧ潯鐪�</string>
+    <string name="recoveryTime">鎭㈠鏃堕棿</string>
+    <string name="weight_kg">%1$.2f 鍗冨厠</string>
+    <string name="weight_lbs">%1$.2f 纾�</string>
+    <string name="target">鐩爣</string>
+    <string name="devicetype_garmin_fenix_5x_plus">浣虫槑 Fenix 5X Plus</string>
+    <string name="pref_header_sound">澹伴煶</string>
+    <string name="workout_set_repetitions_weight_kg">%1d x %2$.2f 鍏枻</string>
+    <string name="workout_set_repetitions_weight_lbs">%1d x %2$.2f 纾�</string>
+    <string name="workoutSets">璁剧疆</string>
+    <string name="workout_set_i">璁剧疆 %1d</string>
+    <string name="workout_set_repetitions">%1d x</string>
+    <string name="menuitem_stress_breakdown">鍘嬪姏锛堝垎瑙o級</string>
+    <string name="choose_device">閫夋嫨璁惧</string>
+    <string name="no_supported_devices_found">鏈壘鍒版敮鎸佺殑璁惧</string>
+    <string name="menuitem_stress_simple">鍘嬪姏锛堢畝鍗曪級</string>
+    <string name="menuitem_stress_segmented">鍘嬪姏锛堝垎娈碉級</string>
+    <string name="search">鎼滅储</string>
+    <string name="devicetype_soundcore_liberty4_nc">澹伴様 Liberty 4 NC</string>
+    <string name="pref_header_deprecated_functionalities">宸插純鐢ㄧ殑鍔熻兘</string>
+    <string name="pref_deprecated_media_control_title">宸插純鐢ㄧ殑濯掍綋鎺у埗</string>
+    <string name="pref_deprecated_media_control_summary">灏嗗獟浣撴帶鍒跺懡浠や綔涓烘寜閿簨浠惰�屼笉鏄獟浣撴帶鍒跺櫒鍙戦�併��</string>
+    <string name="pref_header_deprecated_functionalities_warning">浠ヤ笅鍔熻兘宸茶寮冪敤锛屽苟灏嗗緢蹇粠杞欢涓垹闄ゃ��
+\n濡傛灉鎮ㄩ渶瑕佸惎鐢ㄤ互涓嬭缃箣涓�锛岃鍔″繀涓庨」鐩洟闃熻仈绯汇��</string>
+    <string name="pref_title_nagivation_apps">瀵艰埅搴旂敤</string>
+    <string name="pref_header_external_integrations">澶栭儴闆嗘垚</string>
+    <string name="pref_header_automations">鑷姩鍖�</string>
+    <string name="pref_description_general">鍚姩銆佽瑷�銆佸湴鍖恒�佷綅缃�</string>
+    <string name="pref_description_about_you">鐢熸棩銆佹�у埆銆佽韩楂樸�佷綋閲嶃�佺洰鏍�</string>
+    <string name="pref_description_notifications">搴旂敤閫氱煡銆佺櫧鍚嶅崟/榛戝悕鍗�</string>
+    <string name="pref_description_user_interface">涓婚銆佷富灞忓箷</string>
+    <string name="pref_description_dashboard">灏忛儴浠讹紝瑕佸寘鍚殑璁惧</string>
+    <string name="pref_description_automations">鑷姩瀵煎嚭銆佽嚜鍔ㄨ幏鍙�</string>
+    <string name="pref_description_developer_options">鏃ュ織銆佹剰鍥� API</string>
+    <string name="pref_header_main_screen">涓诲睆骞�</string>
+    <string name="pref_title_user_interface">鐢ㄦ埛鐣岄潰</string>
+    <string name="pref_description_deprecated_functionalities">灏嗗湪鏈潵鐗堟湰涓垹闄ょ殑璁剧疆</string>
+    <string name="activity_prefs_date_birth">鐢熸棩</string>
+    <string name="devicetype_garmin_forerunner_165">浣虫槑 Forerunner 165</string>
+    <string name="prefs_summary_gatt_client_notification_intents">閫氳繃浣庡姛鑰楄摑鐗欐帴鏀舵剰鍥剧壒鎬у彉鍖�</string>
+    <string name="prefs_title_gatt_client_allow_gatt_interactions">鍏佽閫氳繃浣庡姛鑰楄摑鐗欐剰鍥� API 杩涜 GATT 浜や簰</string>
+    <string name="prefs_summary_gatt_client_allow_gatt_interactions">鍏佽鍙戦�佷綆鍔熻�楄摑鐗欑壒鎬ц/鍐欏拰杩炴帴鍛戒护</string>
+    <string name="prefs_summary_gatt_client_device_state_updates">閫氳繃浣庡姛鑰楄摑鐗欐帴鏀舵剰鍥捐繛鎺ョ姸鎬佸彉鍖�</string>
+    <string name="prefs_summary_gatt_client_api_package">闄愬埗浣庡姛鑰楄摑鐗欐剰鍥� API 閫氫俊鑷虫鍖�</string>
+    <string name="pref_continuous_skin_temperature_measurement_title">杩炵画鐨偆娓╁害娴嬮噺</string>
+    <string name="pref_continuous_skin_temperature_measurement_description">鐩墠涓嶆敮鎸佹俯搴︽绱€�傛璁剧疆浠呭惎鐢ㄨ澶囦笂鐨勮繛缁祴閲�</string>
+    <string name="devicetype_ble_gatt_client">閫氱敤浣庡姛鑰楄摑鐗� GATT 瀹㈡埛绔�</string>
+    <string name="prefs_title_gatt_client_notification_intents">閫氳繃浣庡姛鑰楄摑鐗欏唴閮� API 骞挎挱 GATT 閫氱煡鎰忓浘</string>
+    <string name="prefs_title_gatt_client_api_package">浣庡姛鑰楄摑鐗� API 鍖�</string>
+    <string name="prefs_title_ble_intent_api">浣庡姛鑰楄摑鐗欐剰鍥続PI</string>
+    <string name="activity_db_management_backup_restore_label">澶囦唤涓庢仮澶�</string>
+    <string name="activity_db_management_backup_restore_explanation">瀵煎叆/瀵煎嚭鎿嶄綔鍏佽浣犲鍑烘垨澶囦唤鎵�鏈� Gadgetbridge 璁剧疆銆佽澶囧拰鏁版嵁鍒� zip 鏂囦欢锛堟垨浠� zip 鏂囦欢鎭㈠锛夈��
+\n
+\n瀵煎叆涓�涓枃浠朵細绉婚櫎鎵�鏈夌幇鏈夋暟鎹�佽澶囧拰棣栭�夐」锛屽畬鍏ㄦ浛鎹㈠畠浠负澶囦唤鐨勬暟鎹��</string>
+    <string name="backup_restore_exporting">姝e湪瀵煎嚭涓� zip 鈥�</string>
+    <string name="backup_restore_exporting_preferences">姝e湪瀵煎嚭棣栭�夐」鈥�</string>
+    <string name="backup_restore_exporting_database">姝e湪瀵煎嚭鏁版嵁搴撯��</string>
+    <string name="backup_restore_exporting_files">姝e湪瀵煎嚭鏂囦欢鈥�</string>
+    <string name="backup_restore_exporting_files_i_of_n">姝e湪瀵煎嚭鏂囦欢鈥� %1d 鐨� %2d</string>
+    <string name="backup_restore_exporting_finishing">姝e湪瀹屾垚瀵煎嚭鈥�</string>
+    <string name="backup_restore_importing">姝e湪瀵煎叆 zip 鈥�</string>
+    <string name="backup_restore_do_not_exit">%s 璇风‘淇濆睆骞曚竴鐩村紑鍚洿鑷虫搷浣滅粨鏉熴��</string>
+    <string name="backup_restore_importing_loading">璇诲彇鏂囦欢鈥�</string>
+    <string name="backup_restore_warning_files">%1d 涓枃浠舵仮澶嶅け璐ワ細
+\n%2s</string>
+    <string name="backup_restore_import_complete">瀵煎叆瀹屾垚</string>
+    <string name="backup_restore_error_import">瀵煎叆 zip 澶辫触</string>
+    <string name="backup_restore_abort_title">鏀惧純</string>
+    <string name="backup_restore_abort_export_confirmation">鏀惧純瀵煎嚭锛熸湭瀹屾垚鐨� zip 鏂囦欢灏嗚鍒犻櫎銆�</string>
+    <string name="backup_restore_restart_title">閲嶅惎</string>
+    <string name="backup_restore_restart_summary">%1s 灏嗕細閲嶅惎銆�</string>
+    <string name="activity_db_management_export_to_zip">瀵煎嚭 zip</string>
+    <string name="activity_db_management_import_from_zip">瀵煎叆 zip</string>
+    <string name="backup_restore_importing_preferences">姝e湪瀵煎叆棣栭�夐」鈥�</string>
+    <string name="backup_restore_importing_validating">楠岃瘉鏂囦欢鈥�</string>
+    <string name="backup_restore_importing_files_i_of_n">瀵煎叆鏂囦欢鈥� %1d 鐨� %2d</string>
+    <string name="backup_restore_importing_database">姝e湪瀵煎叆鏁版嵁搴撯��</string>
+    <string name="backup_restore_error_export">瀵煎嚭鍒� zip 澶辫触</string>
+    <string name="backup_restore_export_complete">瀵煎嚭瀹屾垚</string>
+    <string name="backup_restore_abort_import_confirmation">鏀惧純瀵煎叆锛熻繖鍙兘浼氬鑷存崯鍧忔垨涓嶅畬鏁寸殑鏁版嵁搴撱��</string>
+    <string name="devicetype_honor_watchgspro">鑽h�� Watch GS Pro</string>
+    <string name="devicetype_amazfit_trex_3">鍗庣背 T-Rex 3</string>
+    <string name="menuitem_heart_rate_push">蹇冪巼鎺ㄩ��</string>
+    <string name="menuitem_map">鍦板浘</string>
+    <string name="menuitem_night_display">澶滈棿鏄剧ず</string>
+    <string name="label_distance_trip_mph">鏃呴�旓細 %.1f 灏�</string>
+    <string name="label_distance_total_mph">鎬昏锛�%.1f 灏�</string>
+    <string name="error_no_cycling_sensor_found">鏈壘鍒伴獞琛屼紶鎰熷櫒</string>
+    <string name="label_distance_trip">鏃呴�旓細%.1f 鍗冪背</string>
+    <string name="label_distance_total">鎬昏锛�%.1f 鍗冪背</string>
+    <string name="devicetype_garmin_forerunner_245_music">浣虫槑 Forerunner 245 Music</string>
+    <string name="hr_resting">浼戞伅涓�</string>
+    <string name="hr_maximum">鏈�澶�</string>
+    <string name="hr_minimum">鏈�灏�</string>
+    <string name="hr_average">骞冲潎</string>
+    <string name="devicetype_garmin_forerunner_955">浣虫槑 Forerunner 955</string>
+    <string name="devicetype_sony_wf_c500">绱㈠凹 WF-C500</string>
+    <string name="bpm_value_unit">%1$d 娆�/鍒�</string>
+    <string name="devicetype_garmin_enduro_3">浣虫槑 Enduro 3</string>
+    <string name="vo2max_cycling">楠戣鏈�澶ф憚姘ч噺</string>
+    <string name="devicetype_garmin_forerunner_265s">浣虫槑 Forerunner 265S</string>
+    <string name="vo2max_running">璺戞鏈�澶ф憚姘ч噺</string>
+    <string name="thirty_days_timeline">30 澶╂椂闂寸嚎</string>
+    <string name="devicetype_miband9">灏忕背鏅鸿兘鎵嬬幆 9</string>
+    <string name="devicetype_redmi_watch_5_active">绾㈢背鎵嬭〃 5 Active</string>
+    <string name="devicetype_huawei_watchgt5">鍗庝负 Watch GT 5 (Pro)</string>
+    <string name="pref_title_sync_birthdays">鍚屾鐢熸棩</string>
+    <string name="birthdays">鐢熸棩</string>
+    <string name="pref_summary_sync_birthdays">灏嗚仈绯讳汉鐢熸棩涓庢棩鍘嗕簨浠跺悓姝�</string>
+    <string name="contact_birthday">%1s 鐨勭敓鏃�</string>
+    <string name="devicetype_sony_wf_c700n">绱㈠凹 WF-C700N</string>
+    <string name="pref_crash_notification_title">宕╂簝鏃堕�氱煡</string>
+    <string name="pref_crash_notification_summary">褰撳簲鐢ㄧ▼搴忓穿婧冩椂锛屾樉绀哄寘鍚敊璇殑閫氱煡</string>
+    <string name="app_crash_notification_title">%1s 宸茬粡宕╂簝</string>
+    <string name="app_crash_share_stacktrace">鍒嗕韩閿欒</string>
+    <string name="pref_dashboard_widget_today_yesterday_data_title">鏄ㄥぉ鐨勬暟鎹�</string>
+    <string name="pref_dashboard_widget_today_yesterday_data_summary">鏄剧ず鏄ㄥぉ鐨勬暟鎹湪褰撳墠鏃堕棿鍜屽崍澶滀箣闂村彉鏆�</string>
+    <string name="pref_dashboard_widget_today_time_indicator_summary">鏄剧ず褰撳墠鏃堕棿鐨勬寚鏍囷紝浠ョ洿瑙傚湴鍖哄垎鏄ㄥぉ鍜屼粖澶╃殑鏁版嵁</string>
+    <string name="pref_dashboard_widget_today_time_indicator_title">褰撳墠鏃堕棿鎸囩ず鍣�</string>
+    <string name="pref_title_calendar_lookahead">鎻愬墠澶╂暟</string>
+    <string name="pref_summary_calendar_lookahead">鍚屾鏈�澶� %1s 澶╃殑鏃ュ巻浜嬩欢</string>
+    <string name="workout_set_reps">閲嶅娆℃暟</string>
+    <string name="hrZoneEasy">瀹规槗</string>
+    <string name="hrZoneThreshold">涓寸晫鐐�</string>
+    <string name="hrZoneMaximum">鏈�澶�</string>
+    <string name="devicetype_huawei_watchgtcyber">鍗庝负 Watch GT Cyber</string>
+    <string name="paceCorrection">淇</string>
+    <string name="number_selected_items">宸查�夋嫨 %1d</string>
+    <string name="activity_detail_share_json_details">鍒嗕韩 JSON 璇︽儏</string>
+    <string name="devicetype_garmin_fenix_8">浣虫槑 Fenix 8</string>
+    <string name="devicetype_garmin_venu_sq">浣虫槑 Venu Sq</string>
+    <string name="mijia_lywsd_comfort_upper">涓婇檺</string>
+    <string name="date_placeholders__date__time">%1s锛�%1s</string>
+    <string name="date_placeholders__start_time__end_time">%1s - %2s</string>
+    <string name="mijia_lywsd_comfort_level_title">鑸掗�傚害</string>
+    <string name="mijia_lywsd_comfort_level_summary">閰嶇疆鏄剧ず琛ㄦ儏绗﹀彿鐨勬俯搴﹀拰婀垮害闄愬埗</string>
+    <string name="mijia_lywsd_comfort_temperature_title">鎽勬皬搴�</string>
+    <string name="mijia_lywsd_comfort_temperature_summary">鎺ㄨ崘鑼冨洿锛�19 - 27</string>
+    <string name="mijia_lywsd_comfort_humidity_title">婀垮害</string>
+    <string name="mijia_lywsd_comfort_humidity_summary">鎺ㄨ崘鑼冨洿锛� 20 - 85</string>
+    <string name="mijia_lywsd_comfort_lower">涓嬮檺</string>
+    <string name="devicetype_garmin_venu_sq_2">浣虫槑 Venu Sq 2</string>
+    <string name="devicetype_garmin_fenix_6s_sapphire">浣虫槑 Fenix 6S Sapphire</string>
+    <string name="busy_task_fetch_hrv_data">鑾峰彇蹇冪巼鍙樺紓鎬ф暟鎹�</string>
+    <string name="hrv_sdrr">蹇冭烦闂撮殧鏃堕棿鐨勬爣鍑嗗亸宸�</string>
+    <string name="hrv_rmssd">HRV鍏稿瀷鏃跺煙鎸囨爣</string>
+    <string name="inactivity_warnings_minimum_steps_title">鏈�灏戞鏁�</string>
+    <string name="average_respiration_rate">鍛煎惛棰戠巼</string>
+    <string name="max_respiration_rate">鏈�澶у懠鍚哥巼</string>
+    <string name="min_respiration_rate">鏈�浣庡懠鍚搁鐜�</string>
+    <string name="fmtPaceAverage">骞冲潎閰嶉��</string>
+    <string name="pref_dnd_follow_phone_title">閬靛惊鐢佃瘽鍏嶆墦鎵拌缃�</string>
+    <string name="pref_dnd_follow_phone_summary">褰撴墜鏈轰笂鍚敤鎴栫鐢ㄥ厤鎵撴壈鏃讹紝涔熶細鍦ㄨ澶囦笂鑷姩鍒囨崲</string>
+    <string name="breaths_per_min">鍛煎惛/鍒嗛挓</string>
+    <string name="milliseconds_ms">姣</string>
+    <string name="inactivity_warnings_minimum_steps_summary">鍦ㄩ槇鍊煎垎閽熷唴闇�瑕侀噰鍙栫殑鏈�灏戞鏁�</string>
+    <string name="prefs_hrv_monitoring_title">蹇冪巼鐩戞帶</string>
+    <string name="prefs_hrv_monitoring_description">鑷姩鐩戞祴鍏ㄥぉ蹇冪巼鍙樺紓鎬�</string>
+    <string name="devicetype_huawei_watchd2">鍗庝负 Watch D2</string>
+    <string name="idasen_control_button_sit">鍧�</string>
+    <string name="idasen_control_button_mid">涓�</string>
+    <string name="idasen_control_button_stand">绔�</string>
+    <string name="idasen_pref_mid_height">涓棿浣嶇疆楂樺害锛堝帢绫筹級</string>
+    <string name="idasen_pref_sit_height">鍧愬Э楂樺害锛堝帢绫筹級</string>
+    <string name="idasen_pref_stand_height">绔欑珛楂樺害锛堝帢绫筹級</string>
+    <string name="idasen_pref_value_warning">璇ュ�煎繀椤诲湪 62 - 126 鍘樼背涔嬮棿</string>
+    <string name="devicetype_idasen">瀹滃浼婃湹妫功妗�</string>
+    <string name="devicetype_colmi_r10">Colmi R10</string>
+    <string name="file_already_exists">鏂囦欢宸插瓨鍦�</string>
+    <string name="music_upload_info">鎮ㄥ皢瑕佷笂浼犱互涓嬮煶涔愭枃浠讹細\n\n%1$s\n闊充箰鏍囬锛�%2$s\n涓撹緫锛�%3$s\n</string>
+    <string name="devicetype_garmin_fenix_7">浣虫槑 Fenix 7</string>
+    <string name="devicetype_garmin_forerunner_235">浣虫槑 Forerunner 235</string>
+    <string name="devicetype_sony_wi_c100">绱㈠凹 WI-C100</string>
+    <string name="devicetype_bandw_pseries">Bowers and Wilkins P 绯诲垪</string>
+    <string name="devicetype_garmin_fenix_6s_pro">浣虫槑 Fenix 6S Pro</string>
+    <string name="devicetype_huawei_band3pro">鍗庝负鎵嬬幆3 Pro</string>
+    <string name="pref_voice_passthrough_enabled">閫氶��</string>
+    <string name="pref_voice_passthrough_enabled_summary">鍏佽澶栭儴澹伴煶浼犲叆鎮ㄧ殑鑰虫湹</string>
+    <string name="pref_voice_passthrough_level">閫氶�忕瓑绾�</string>
+    <string name="devicetype_garmin_forerunner_55">浣虫槑 Forerunner 55</string>
+    <string name="devicetype_oppo_enco_air">Oppo Enco Air</string>
+    <string name="devicetype_garmin_forerunner_620">浣虫槑 Forerunner 620</string>
+    <string name="pref_wear_sensor_summary">妫�娴嬭澶囦綍鏃舵湭浣╂埓</string>
+    <string name="pref_wear_sensor_title">浣╂埓浼犳劅鍣�</string>
+    <string name="pref_developer_add_test_activities_title">娣诲姞娴嬭瘯娲诲姩</string>
+    <string name="pref_developer_add_test_activities_summary">浣跨敤铏氭嫙娴嬭瘯娲诲姩濉厖鏁版嵁搴�</string>
+    <string name="pref_music_management_title">绠$悊闊充箰</string>
+    <string name="music_delete_confirm_description">鎮ㄧ‘璁よ鍒犻櫎鈥�%1$s鈥欏悧锛�</string>
+    <string name="music_delete_from_playlist">浠庢挱鏀惧垪琛ㄤ腑鍒犻櫎</string>
+    <string name="music_all_songs">鎵�鏈夋瓕鏇�</string>
+    <string name="music_new_playlist">鏂版挱鏀惧垪琛�</string>
+    <string name="music_rename_playlist">閲嶅懡鍚嶆挱鏀惧垪琛�</string>
+    <string name="music_error">鍙戠敓閿欒</string>
+    <string name="music_huawei_device_info">鏀寔鐨勬牸寮忥細%1$s\n鎵嬭〃绌洪棿锛�%2$d MB</string>
+    <string name="title_activity_musicmanager">闊充箰绠$悊鍣�</string>
+    <string name="pref_music_management_summary">绠$悊鎵嬭〃涓婄殑闊充箰</string>
+    <string name="music_add_to_playlist">娣诲姞鍒版挱鏀惧垪琛�</string>
+    <string name="music_delete">鍒犻櫎姝屾洸</string>
+    <string name="devicetype_realme_buds_t110">鐪熸垜 Buds T110</string>
+    <string name="devicetype_garmin_instinct_2">浣虫槑 Instinct 2</string>
+    <string name="activity_prefs_goal_active_calories_burnt">姣忔棩鐩爣锛氭秷鑰楁椿璺冨崱璺噷</string>
+    <string name="first_start_welcome_title">娆㈣繋</string>
+    <string name="first_start_intro_welcome_to">娆㈣繋鏉ュ埌</string>
+    <string name="first_start_intro_tag_line">鎽嗚劚绌挎埓璁惧渚涘簲鍟嗙殑涓撴湁搴旂敤绋嬪簭鍜屼簯鏈嶅姟銆�</string>
+    <string name="first_start_overview_title">鎬昏</string>
+    <string name="first_start_overview_devices">璁惧瑙嗗浘鏄剧ず鎮ㄥ凡閰嶇疆鐨勬墍鏈夎澶囧強鍏剁姸鎬侊紝骞跺厑璁歌闂澶囩壒瀹氬姛鑳斤紝渚嬪璇︾粏鍥捐〃銆佽缃�佸簲鐢ㄧ▼搴忓拰闂归挓銆�</string>
+    <string name="first_start_open_source_title">寮�婧�</string>
+    <string name="first_start_permissions_request_all_button">璇锋眰鎵�鏈夋潈闄�</string>
+    <string name="first_start_permissions_request_button">璇锋眰</string>
+    <string name="permission_manage_dnd_summary">鏇存敼鍏嶆墦鎵伴�氱煡鏀跨瓥</string>
+    <string name="permission_background_location_title">鍚庡彴浣嶇疆璁块棶</string>
+    <string name="permission_bluetooth_summary">杩炴帴鑷宠摑鐗欒澶�</string>
+    <string name="permission_bluetooth_scan_summary">鎵弿鏂扮殑钃濈墮璁惧</string>
+    <string name="permission_bluetooth_connect_title">钃濈墮杩炴帴</string>
+    <string name="permission_contacts_title">鑱旂郴浜�</string>
+    <string name="permission_receive_sms_summary">灏� SMS 娑堟伅杞彂鍒扮┛鎴磋澶�</string>
+    <string name="permission_send_sms_summary">浠庣┛鎴磋澶囧彂閫佺煭淇★紙棰勮鍥炲锛�</string>
+    <string name="permission_read_call_log_title">璇诲彇閫氳瘽璁板綍</string>
+    <string name="permission_read_call_log_summary">灏嗛�氳瘽璁板綍杞彂鍒扮┛鎴磋澶�</string>
+    <string name="permission_call_phone_summary">浠庣┛鎴磋澶囧彂璧风數璇濆懠鍙�</string>
+    <string name="permission_query_all_packages_title">鏌ヨ鎵�鏈夊寘</string>
+    <string name="permission_query_all_packages_summary">璇诲彇鎵�鏈夊凡瀹夎搴旂敤绋嬪簭鐨勫悕绉板拰鍥炬爣</string>
+    <string name="active_calories">娲诲姩鍗¤矾閲�</string>
+    <string name="menuitem_calories_segmented">鍗¤矾閲岋紙鍒嗘锛�</string>
+    <string name="first_start_permissions_title">鏉冮檺</string>
+    <string name="active_calories_short">娲诲姩</string>
+    <string name="active_calories_goal">娲诲姩鐩爣</string>
+    <string name="total_calories_burnt">鎬昏鐕冪儳</string>
+    <string name="menuitem_calories_active_goal">鍗¤矾閲岀洰鏍囷紙涓诲姩锛�</string>
+    <string name="first_start_get_started_add_first_device_button">娣诲姞绗竴鍙拌澶�</string>
+    <string name="permission_manage_dnd_title">绠$悊璇峰嬁鎵撴壈</string>
+    <string name="first_start_overview_desc">Gadgetbridge 鏈変袱涓富瑕佽鍥撅紝姣忎釜瑙嗗浘閮芥湁鑷繁鐨勪綔鐢ㄣ��</string>
+    <string name="first_start_overview_dashboard">浠〃鏉垮彲璁╂偍蹇�熶簡瑙f偍浠婂ぉ鐨勮〃鐜般�傛棩鍘嗚鍥炬樉绀烘偍鏁翠釜鏈堢殑鐩爣杈炬垚鐘舵�併��</string>
+    <string name="first_start_permissions_desc">Gadgetbridge 闇�瑕佸ぇ閲忔潈闄愭墠鑳芥墽琛屽叾鎵�鏈夊姛鑳姐�傚湪涓嬫柟鏌ョ湅鎵�闇�鐨勬潈闄愬強鍏剁敤閫斻��</string>
+    <string name="first_start_get_started_restore_button">鎭㈠澶囦唤</string>
+    <string name="first_start_open_source_text">Gadgetbridge 鏄竴涓紑婧愬簲鐢ㄧ▼搴忋�傚畠鏄敱绀惧尯寮�鍙戙�佷负绀惧尯鏈嶅姟鐨勩�俓n\n娆㈣繋浠讳綍浜洪�氳繃浠g爜銆佹枃妗c�佹祴璇曞拰鎹愯禒鍋氬嚭璐$尞銆俓n\nGadgetbridge 涓嶅寘鍚箍鍛婏紝涔熸病鏈夎窡韪櫒銆傚畠灏嗘偍鐨勬暟鎹湪鎮ㄧ殑 Android 璁惧涓婃湰鍦颁繚瀛橈紝鍥犳 100% 闅愮鍙嬪ソ銆俓n\n璇疯闂垜浠殑缃戠珯浠ヨ幏鍙栨洿澶氫俊鎭�佹枃妗d互鍙婃垜浠殑娌熼�氶閬撻摼鎺ャ��</string>
+    <string name="permission_displayover_title">鏄剧ず鍦ㄥ叾浠栧簲鐢ㄤ箣涓�</string>
+    <string name="permission_displayover_summary">鐢� Bangle.js 鐢ㄤ簬鍚姩鎵嬫満涓婄殑搴旂敤绋嬪簭鍜屽叾浠栧姛鑳�</string>
+    <string name="permission_fine_location_title">绮剧‘浣嶇疆</string>
+    <string name="permission_bluetooth_title">钃濈墮</string>
+    <string name="first_start_get_started_title">寮�濮嬩娇鐢�</string>
+    <string name="first_start_get_started_go_to_app_button">鍓嶅線搴旂敤</string>
+    <string name="first_start_get_started_desc">涓轰簡浣跨敤搴旂敤锛岃浠庢灞忓箷娣诲姞鎮ㄧ殑绗竴涓澶囥�佹仮澶嶅浠芥垨鏄粠闆跺紑濮嬨��</string>
+    <string name="permission_notifications_summary">灏嗛�氱煡杞彂鍒板凡杩炴帴鐨勭┛鎴磋澶�</string>
+    <string name="permission_fine_location_summary">鎵弿钃濈墮璁惧</string>
+    <string name="permission_background_location_summary">鍦ㄥ悗鍙版壂鎻忚摑鐗欒澶囧苟灏嗕綅缃彂閫佸埌鐗瑰畾璁惧</string>
+    <string name="permission_bluetooth_admin_title">钃濈墮绠$悊鍛�</string>
+    <string name="permission_bluetooth_scan_title">钃濈墮鎵弿</string>
+    <string name="permission_bluetooth_admin_summary">鍙戠幇骞堕厤瀵硅摑鐗欒澶�</string>
+    <string name="permission_answer_phone_calls_title">鎺ュ惉鐢佃瘽</string>
+    <string name="permission_post_notification_title">鍙戝竷閫氱煡</string>
+    <string name="permission_post_notification_summary">鍙戝竷鎸佺画閫氱煡浠ヤ繚鎸佹湇鍔¤繍琛�</string>
+    <string name="permission_internet_access_title">浜掕仈缃戣闂�</string>
+    <string name="permission_internet_access_summary">涓庡湪绾胯祫婧愬悓姝�</string>
+    <string name="permission_bluetooth_connect_summary">杩炴帴鍒板凡閰嶅鐨勮摑鐗欒澶�</string>
+    <string name="permission_calendar_title">鏃ュ巻</string>
+    <string name="permission_calendar_summary">灏嗘棩鍘嗗彂閫佸埌绌挎埓璁惧</string>
+    <string name="permission_read_phone_state_title">璇诲彇鎵嬫満鐘舵��</string>
+    <string name="permission_call_phone_title">鎷ㄦ墦鐢佃瘽</string>
+    <string name="permission_process_outgoing_calls_title">澶勭悊鎷ㄥ嚭鐢佃瘽</string>
+    <string name="permission_contacts_summary">灏嗚仈绯讳汉鍙戦�佸埌绌挎埓璁惧</string>
+    <string name="permission_send_sms_title">鍙戦�佺煭淇�</string>
+    <string name="permission_external_storage_title">澶栭儴瀛樺偍</string>
+    <string name="permission_external_storage_summary">浣跨敤鍥惧儚銆侀搩澹般�佸簲鐢ㄧ▼搴忔枃浠剁瓑</string>
+    <string name="permission_receive_sms_title">鎺ユ敹鐭俊</string>
+    <string name="permission_read_phone_state_summary">璇诲彇姝e湪杩涜鐨勯�氳瘽鐨勭姸鎬�</string>
+    <string name="permission_process_outgoing_calls_summary">璇诲彇鎷ㄥ嚭鐢佃瘽鐨勫彿鐮佸苟灏嗗叾鏄剧ず鍦ㄧ┛鎴磋澶囦笂</string>
+    <string name="permission_answer_phone_calls_summary">閫氳繃绌挎埓璁惧鎺ュ惉鐢佃瘽</string>
+    <string name="devicetype_oppo_enco_air2">Oppo Enco Air2</string>
+    <string name="redmi_buds_5_pro_combo_all">闄嶅櫔 / 閫氶�� / 鍏�</string>
+    <string name="redmi_buds_5_pro_double_connection">鍙岃繛鎺�</string>
+    <string name="redmi_buds_5_pro_adaptive_sound">鑷�傚簲澹�</string>
+    <string name="redmi_buds_5_pro_equalizer_preset_standard">鏍囧噯</string>
+    <string name="redmi_buds_5_pro_equalizer_preset_treble">楂橀煶澧炲己</string>
+    <string name="redmi_buds_5_pro_equalizer_preset_voice">浜哄0澧炲己</string>
+    <string name="redmi_buds_5_pro_equalizer_band_250">250 Hz</string>
+    <string name="redmi_buds_5_pro_equalizer_band_2k">2 kHz</string>
+    <string name="redmi_buds_5_pro_equalizer_band_4k">4 kHz</string>
+    <string name="redmi_buds_5_pro_equalizer_band_8k">8 kHz</string>
+    <string name="redmi_buds_5_pro_equalizer_band_12k">12 kHz</string>
+    <string name="redmi_buds_5_pro_equalizer_band_16k">16 kHz</string>
+    <string name="redmi_buds_5_pro_equalizer_neg6">-6 dB</string>
+    <string name="redmi_buds_5_pro_equalizer_neg5">-5 dB</string>
+    <string name="redmi_buds_5_pro_equalizer_neg3">-3 dB</string>
+    <string name="devicetype_redmi_buds_5_pro">绾㈢背 Buds 5 Pro</string>
+    <string name="redmi_buds_5_pro_anc_balanced">鍧囪 </string>
+    <string name="redmi_buds_5_pro_transparency_regular">甯歌</string>
+    <string name="redmi_buds_5_pro_anc_light">灏戦噺</string>
+    <string name="redmi_buds_5_pro_anc_deep">娣辨矇</string>
+    <string name="redmi_buds_5_pro_transparency_strength">閫氶�忓己搴�</string>
+    <string name="redmi_buds_5_pro_transparency_voice">澧炲己璇煶</string>
+    <string name="redmi_buds_5_pro_combo_transparency_off">閫氶�� / 鍏�</string>
+    <string name="redmi_buds_5_pro_transparency_ambient">澧炲己鐜闊�</string>
+    <string name="redmi_buds_5_pro_combo_anc_off">闄嶅櫔 / 鍏�</string>
+    <string name="redmi_buds_5_pro_combo_anc_transparency">闄嶅櫔 / 閫氶��</string>
+    <string name="redmi_buds_5_pro_double_connection_description">鍏佽鑰虫満鍚屾椂杩炴帴鍒颁袱涓澶�</string>
+    <string name="redmi_buds_5_pro_adaptive_sound_description">鏍规嵁鑰虫湹褰㈢姸鍜岀幆澧冭皟鑺傚0闊�</string>
+    <string name="redmi_buds_5_pro_equalizer_preset_bass">浣庨煶澧炲己</string>
+    <string name="redmi_buds_5_pro_equalizer_preset_custom">鑷畾涔�</string>
+    <string name="redmi_buds_5_pro_equalizer_band_62">62 Hz</string>
+    <string name="redmi_buds_5_pro_equalizer_band_125">125 Hz</string>
+    <string name="redmi_buds_5_pro_equalizer_3">3 dB</string>
+    <string name="redmi_buds_5_pro_equalizer_band_500">500 Hz</string>
+    <string name="redmi_buds_5_pro_equalizer_band_1k">1 kHz</string>
+    <string name="redmi_buds_5_pro_equalizer_neg4">-4 dB</string>
+    <string name="redmi_buds_5_pro_equalizer_neg1">-1 dB</string>
+    <string name="redmi_buds_5_pro_equalizer_zero">0 dB</string>
+    <string name="redmi_buds_5_pro_equalizer_neg2">-2 dB</string>
+    <string name="redmi_buds_5_pro_equalizer_1">1 dB</string>
+    <string name="redmi_buds_5_pro_equalizer_2">2 dB</string>
+    <string name="redmi_buds_5_pro_equalizer_5">5 dB</string>
+    <string name="redmi_buds_5_pro_equalizer_6">6 dB</string>
+    <string name="redmi_buds_5_pro_equalizer_4">4 dB</string>
+    <string name="devicetype_miband9pro">灏忕背鎵嬬幆 9 Pro</string>
+    <string name="music_multiselect_limit">宸茶揪鍒伴�夋嫨澶氫簬涓�椤圭殑闄愬埗</string>
+    <string name="sleep_avg">骞冲潎鐫$湢</string>
+    <string name="respiratoryrate">鍛煎惛棰戠巼</string>
+    <string name="lowest">鏈�浣�</string>
+    <string name="highest">鏈�楂�</string>
+    <string name="music_delete_multiple_confirm_description">鎮ㄧ‘瀹氭兂瑕佸垹闄ゆ瓕鏇� %1$d 锛�</string>
+    <string name="sleep_score">鐫$湢璇勫垎</string>
+    <string name="sleep_score_value">璇勫垎锛�%1d</string>
+    <string name="devicetype_colmi_r09">Colmi R09</string>
+    <string name="watchface_widget_type_sp02">SpO2</string>
+    <string name="devicetype_huawei_band2pro">鍗庝负鎵嬬幆 2 Pro</string>
+    <string name="devicetype_huawei_freebuds_5i">鍗庝负 FreeBuds 5i</string>
+    <string name="avgJumpRate">骞冲潎璺宠繙璺濈</string>
+    <string name="totalJumps">鎬昏璺宠繙娆℃暟</string>
+    <string name="jumps_unit">璺宠繙</string>
+    <string name="maxJumpRate">鏈�澶ц烦杩滆窛绂�</string>
+    <string name="jumps_minute">璺宠繙/鍒嗛挓</string>
+    <string name="Jumps">璺宠繙</string>
+    <string name="devicetype_redmi_watch_5_lite">绾㈢背鎵嬭〃5 闈掓槬鐗�</string>
+    <string name="devicetype_garmin_fenix_7x">浣虫槑 Fenix 7X</string>
+    <string name="stepRateMax">鏈�澶ф鏁拌寖鍥�</string>
+    <string name="weekly_total">姣忓懆鎬昏</string>
+    <string name="garmin_intensity_minutes">姣忓垎閽熷己搴�</string>
+    <string name="about_build_details_copied_to_clipboard">鏋勫缓璇︽儏宸插鍒跺埌鍓创鏉�</string>
+    <string name="discharge_interval_3">鏀剧數闂撮殧 3</string>
+    <string name="discharge_interval_4">鏀剧數闂撮殧 4</string>
+    <string name="power_w">浠璁$畻鍔熺巼</string>
+    <string name="summary_battery_discharge_intervals_set">灏嗕互涓嬮厤缃彂閫佸埌璁惧</string>
+    <string name="battery_allow_pass_through">鍏佽鐢垫睜璐��</string>
+    <string name="manual_discharge">鎵嬪姩鏀剧數闂撮殧</string>
+    <string name="battery_allow_pass_though_summary">鍚敤鍚庯紝鐢垫睜鍙互杈瑰厖鐢佃竟鏀剧數</string>
+    <string name="manual_discharge_summary">绂佺敤鏃讹紝鍋囧畾鐢卞閮ㄥ姛鐜囪鎺у埗鏅鸿兘鏀剧數锛圙adgetbridge 涓嶆敮鎸侊級</string>
+    <string name="discharge_interval_2">鏀剧數闂撮殧 2</string>
+    <string name="battery_discharge_intervals">鐢垫睜鏀剧數闂撮殧</string>
+    <string name="discharge_interval_1">鏀剧數闂撮殧 1</string>
+    <string name="devicetype_casioecbs100">鍗¤タ娆� ECB-S100</string>
+    <string name="devicetype_marstek_b2500">鍌ㄦ瘮鐗� B2500</string>
+    <string name="discharge_interval_5">鏀剧數闂撮殧 5</string>
+    <string name="battery_minimum_charge">鏈�浣庣數姹犵數閲忥紙%锛�</string>
+</resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml
new file mode 100644
index 0000000..1c40900
--- /dev/null
+++ b/app/src/main/res/values-zh-rTW/strings.xml
@@ -0,0 +1,3411 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources xmlns:tools="http://schemas.android.com/tools">
+    <string name="action_settings">瑷畾</string>
+    <string name="action_debug">闄ら尟</string>
+    <string name="action_quit">闆㈤枊</string>
+    <string name="action_donate">璐婂姪</string>
+    <string name="controlcenter_fetch_activity_data">鍚屾</string>
+    <string name="controlcenter_find_device">灏嬫壘閬哄け鐨勮缃�</string>
+    <string name="controlcenter_take_screenshot">鎿峰彇鐣潰</string>
+    <string name="controlcenter_change_led_color">鏇存敼LED椤忚壊</string>
+    <string name="controlcenter_change_fm_frequency">鏇存敼 FM 闋荤巼</string>
+    <string name="controlcenter_connect">閫g窔鈥�</string>
+    <string name="controlcenter_disconnect">涓柗閫g窔</string>
+    <string name="controlcenter_delete_device">鍒櫎瑁濈疆</string>
+    <string name="controlcenter_delete_device_name">鍒櫎 %1$s</string>
+    <string name="controlcenter_delete_device_dialogmessage">閫欏皣鍒櫎瑁濈疆鍜屾墍鏈夌浉闂滆硣鏂欙紒</string>
+    <string name="controlcenter_navigation_drawer_open">鎵撻枊灏庤埅鍒�</string>
+    <string name="controlcenter_navigation_drawer_close">闂滈枆灏庤埅鍒�</string>
+    <string name="controlcenter_snackbar_need_longpress">闀锋寜姝ゅ崱鐗囦互涓柗閫g窔</string>
+    <string name="controlcenter_snackbar_disconnecting">姝e湪涓柗閫f帴</string>
+    <string name="title_activity_debug">闄ら尟</string>
+    <string name="debugactivity_really_factoryreset_title">鐪熺殑瑕侀噸缃紵</string>
+    <string name="debugactivity_really_factoryreset">鍩疯閲嶇疆灏囧埅闄ら�f帴鐨勮ō鍌欎腑鎵�鏈夎硣鏂欙紙濡傛灉鍙楁敮鎸侊級銆傚皬绫�/鑿背瑷倷涔熸渻鏇存敼钘嶇墮 MAC 浣嶅潃锛屽洜姝ゅ畠鍊戦’绀虹偤 Gadgetbridge 鐨勬柊瑷倷銆�</string>
+    <string name="appmanager_installed_watchapps">宸插畨瑁濈殑绋嬪紡</string>
+    <string name="appmananger_app_delete">鍒櫎</string>
+    <string name="appmananger_app_delete_cache">鍒櫎涓︽竻闄ょ珐瀛�</string>
+    <string name="appmananger_app_reinstall">閲嶆柊瀹夎</string>
+    <string name="mi2_prefs_button_press_broadcast_default_value" translatable="false">nodomain.freeyourgadget.gadgetbridge.ButtonPressed</string>
+    <string name="application_name_generic">Gadgetbridge</string>
+    <string name="title_activity_controlcenter_generic">Gadgetbridge</string>
+    <string name="controlcenter_snackbar_connecting">閫g窔涓��</string>
+    <string name="controlcenter_snackbar_requested_screenshot">鎴彇瑁濈疆鐨勮灑骞曟埅鍦�</string>
+    <string name="controlcenter_calibrate_device">鏍℃簴瑁濈疆</string>
+    <string name="title_activity_appmanager">鎳夌敤绠$悊鍣�</string>
+    <string name="appmanager_cached_watchapps_watchfaces">绶╁瓨鐨勬噳鐢ㄧ▼寮�</string>
+    <string name="appmanager_installed_watchfaces">宸插畨瑁濈殑閷剁洡</string>
+    <string name="appmanager_app_openinstore">鍦≒ebble鎳夌敤鍟嗗簵涓悳绱�</string>
+    <string name="appmanager_health_activate">鍟熺敤</string>
+    <string name="appmanager_health_deactivate">鍋滅敤</string>
+    <string name="appmanager_hrm_activate">婵�娲籋RM</string>
+    <string name="appmanager_hrm_deactivate">鍋滅敤HRM</string>
+    <string name="appmanager_weather_activate">鍟熷嫊绯荤当澶╂埃鎳夌敤</string>
+    <string name="appmanager_weather_deactivate">鍋滅敤绯荤粺澶╂皵搴旂敤</string>
+    <string name="fw_upgrade_notice_miband3">鎮ㄥ嵆灏囧湪鎮ㄧ殑灏忕背鎵嬬挵3涓婂畨瑁濋煂楂� %s
+\n
+\n璜嬬⒑瀹氬厛瀹夎 .fw 妾旓紝鐒跺緦鍐嶅畨瑁� .res 妾斻�傛偍鐨勬墜鐠板皣鍦ㄥ畨瑁濆畬 .fw 妾斿緦閲嶆柊鍟熷嫊銆�
+\n
+\n娉ㄦ剰锛氬鏋滄偍瀹夎鐨勬槸鑸囧厛鍓嶅悓妯g殑鐗堟湰锛屾偍灏变笉蹇呭啀瀹夎 .res 妾斻��
+\n
+\n閬庣▼涓殑棰ㄩ毆璜嬭嚜璨狅紒</string>
+    <string name="miband_firmware_known">姝ら煂楂斿凡缍撻亷娓│涓旂⒑瑾嶈垏 Gadgetbridge 鐩稿銆�</string>
+    <string name="title_activity_settings">瑷畾</string>
+    <string name="pref_header_general">涓�鑸ō瀹�</string>
+    <string name="pref_title_general_autoconnectonbluetooth">鍦ㄨ棈鑺介枊鍟熸檪閫g窔鍒� Gadgetbridge 瑁濈疆</string>
+    <string name="pref_title_general_autostartonboot">鑷嫊鍟熷嫊</string>
+    <string name="pref_title_general_autoreconnect">鑷嫊閲嶆柊閫g窔</string>
+    <string name="pref_title_audio_player">闋愯ō闊虫▊鎾斁鍣�</string>
+    <string name="pref_default">闋愯ō</string>
+    <string name="pref_header_datetime">鏃ユ湡鑸囨檪闁�</string>
+    <string name="pref_title_datetime_syctimeonconnect">鏅傞枔鍚屾</string>
+    <string name="pref_summary_datetime_syctimeonconnect">鐣舵偍 Android 瑁濈疆涓婄殑鏅傚崁鏀硅畩鎴栭�f帴鍒� Gadgetbridge 瑁濈疆鏅傚悓姝ユ檪闁�</string>
+    <string name="pref_title_theme">涓婚</string>
+    <string name="pref_theme_light">浜壊</string>
+    <string name="pref_theme_dark">鏆楄壊</string>
+    <string name="pref_title_language">瑾炶█</string>
+    <string name="pref_title_minimize_priority">闅辫棌 Gadgetbridge 閫氱煡</string>
+    <string name="pref_header_notifications">閫氱煡</string>
+    <string name="pref_title_notifications_sms">绨¤▕閫氱煡</string>
+    <string name="pref_title_notifications_generic">涓�鑸�氱煡鏀彺</string>
+    <string name="pref_title_notification_filter">璜嬪嬁鎵撴摼</string>
+    <string name="always">绺芥槸鍟熺敤</string>
+    <string name="when_screen_off">鐣惰灑骞曢棞闁夋檪</string>
+    <string name="never">鍋滅敤</string>
+    <string name="pref_header_privacy">闅辩</string>
+    <string name="pref_title_call_privacy_mode">渚嗛浕闅辩妯″紡</string>
+    <string name="pref_call_privacy_mode_off">椤ず鍚嶇ū鍙婅櫉纰�</string>
+    <string name="pref_call_privacy_mode_name">闅辫棌鍚嶇ū浣嗛’绀鸿櫉纰�</string>
+    <string name="pref_call_privacy_mode_number">闅辫棌铏熺⒓浣嗛’绀哄悕绋�</string>
+    <string name="pref_call_privacy_mode_complete">闅辫棌鍚嶇ū鍙婅櫉纰�</string>
+    <string name="pref_title_weather">澶╂埃</string>
+    <string name="pref_title_weather_location">澶╂埃鍋垫脯浣嶇疆(閬╃敤鏂� LineageOS 澶╂埃鎻愪緵鑰�)</string>
+    <string name="zetime_date_format">鏃ユ湡鏍煎紡</string>
+    <string name="zetime_date_format_1">骞�/鏈�/鏃�</string>
+    <string name="zetime_date_format_2">鏃�/鏈�/骞�</string>
+    <string name="zetime_date_format_3">鏈�/鏃�/骞�</string>
+    <string name="title_activity_device_specific_settings">瑁濈疆椤嶅瑷畾</string>
+    <string name="pref_header_auto_export">鑷嫊鍖嚭</string>
+    <string name="pref_title_auto_export_enabled">鍟熺敤鑷嫊鍖嚭</string>
+    <string name="pref_title_auto_export_interval">鍖嚭闁撻殧</string>
+    <string name="pref_summary_auto_export_interval">姣� %d 灏忔檪鍖嚭涓�娆�</string>
+    <string name="not_connected">鏈�g窔</string>
+    <string name="connected">宸查�g窔</string>
+    <string name="action_discover">閫f帴鏂拌缃�</string>
+    <string name="preferences_category_device_specific_settings">瑁濈疆椤嶅瑷畾</string>
+    <string name="sleep_activity_date_range">寰� %1$s 鍒� %2$s</string>
+    <string name="title_activity_charts">娲诲嫊鑸囩潯鐪�</string>
+    <string name="sleepchart_your_sleep">鐫$湢</string>
+    <string name="weeksleepchart_sleep_a_week">姣忛�辩潯鐪�</string>
+    <string name="weeksleepchart_today_sleep_description">浠婃棩鐫$湢锛岀洰妯欙細%1$s</string>
+    <string name="activity_sleepchart_activity_and_sleep">娲诲嫊</string>
+    <string name="lack_of_sleep">鐫$湢涓嶈冻锛�%1$s</string>
+    <string name="abstract_chart_fragment_kind_light_sleep">娣哄堡鐫$湢</string>
+    <string name="abstract_chart_fragment_kind_deep_sleep">娣卞堡鐫$湢</string>
+    <string name="you_did_not_sleep">浣犻倓娌掓湁鐫¤</string>
+    <string name="device_not_connected">鏈�g窔銆�</string>
+    <string name="miband_prefs_hr_sleep_detection">浣跨敤蹇冨緥鎰熸脯鍣ㄤ締鏀瑰杽鐫$湢鍋垫脯</string>
+    <string name="miband2_prefs_dateformat">鏃ユ湡鏍煎紡</string>
+    <string name="dateformat_date_time"><![CDATA[鏅傞枔鑸囨棩鏈焆]></string>
+    <string name="mi2_prefs_display_items">椤ず闋呯洰</string>
+    <string name="mi2_prefs_display_items_summary">閬告搰瑕佸湪鎵嬬挵铻㈠箷涓婇’绀虹殑闋呯洰</string>
+    <string name="mi3_prefs_band_screen_unlock">鎵嬬挵铻㈠箷瑙i帠</string>
+    <string name="mi3_prefs_band_screen_unlock_summary">涓婃粦浠ヨВ閹栨墜鐠拌灑骞�</string>
+    <string name="activity_prefs_about_you">闂滄柤浣�</string>
+    <string name="weeksleepchart_sleep_a_month">姣忔湀鐫$湢</string>
+    <string name="pref_chart_sleep_rolling_24_on">閬庡幓 24 灏忔檪</string>
+    <string name="menuitem_weather">澶╂埃</string>
+    <string name="menuitem_settings">瑷畾</string>
+    <string name="language_and_region_prefs">瑾炶█鑸囧湴鍗�瑷畾</string>
+    <plurals name="widget_alarm_target_hours">
+        <item quantity="other">%d 灏忔檪</item>
+    </plurals>
+    <string name="app_configure">瑷畾</string>
+    <string name="blacklist_all_for_notifications">灏囨墍鏈夐�氱煡鍒楀叆榛戝悕鍠�</string>
+    <string name="whitelist_all_for_notifications">灏囨墍鏈夐�氱煡鍒楀叆鐧藉悕鍠�</string>
+    <string name="title_activity_fw_app_insaller">FW/App 瀹夎绋嬪紡</string>
+    <string name="fw_upgrade_notice_miband4">鎮ㄥ嵆灏囧湪鎮ㄧ殑灏忕背鎵嬬挵4涓婂畨瑁濋煂楂� %s 
+\n 
+\n璜嬬⒑瀹氬厛瀹夎 .fw 妾旓紝鐒跺緦鍐嶅畨瑁� .res 妾斻�傛偍鐨勬墜鐠板皣鍦ㄥ畨瑁濆畬 .fw 妾斿緦閲嶆柊鍟熷嫊銆� 
+\n 
+\n娉ㄦ剰锛氬鏋滄偍瀹夎鐨勬槸鑸囧厛鍓嶅悓妯g殑鐗堟湰锛屾偍灏变笉蹇呭啀瀹夎 .res 妾斻�� 
+\n 
+\n閬庣▼涓殑棰ㄩ毆璜嬭嚜璨狅紒</string>
+    <string name="miband_firmware_unknown_warning">姝ら煂楂旀湭缍撴脯瑭︼紝鍙兘鑸� Gadgetbridge 涓嶇浉瀹广��
+\n
+\n涓嶅缓璀版偍瀹夎瀹冿紒</string>
+    <string name="miband_firmware_suggest_whitelist">濡傛灉鎮ㄤ粛瑕佺辜绾岋紝涓︿笖寰岀簩浠嶈兘姝e父浣跨敤锛岃珛鍛婅ù Gadgetbridge 闁嬬櫦浜哄摗灏囬煂楂旂増鏈� %s 鍔犲叆鐧藉悕鍠��</string>
+    <string name="pref_summary_minimize_priority_off">椤ず鐙�鎱嬪垪涓殑鍦栫ず鍜岄帠瀹氳灑骞曚腑鐨勯�氱煡</string>
+    <string name="pref_summary_minimize_priority_on">闅辫棌鐙�鎱嬪垪涓殑鍦栫ず鍜岄帠瀹氳灑骞曚腑鐨勯�氱煡</string>
+    <string name="pref_title_notifications_call">闆昏┍閫氱煡</string>
+    <string name="pref_title_support_voip_calls">鍟熺敤 VoIP 鎳夌敤绋嬪紡闆昏┍閫氱煡</string>
+    <string name="pref_title_notifications_timeout">鏈�浣庨�氱煡闁撻殧鏅傞枔</string>
+    <string name="pref_title_whenscreenon">鍦ㄨ灑骞曢枊鍟熸檪浠嶈閫氱煡</string>
+    <string name="pref_summary_notification_filter">鐣舵墜姗熷暉鐢ㄨ珛鍕挎墦鎿炬檪闃绘鎵�鏈夐�氱煡</string>
+    <string name="pref_header_development">闁嬬櫦浜哄摗閬搁爡</string>
+    <string name="pref_title_expose_hr">绗笁鏂瑰嵆鏅傚績鐜囪硣鏂欏瓨鍙�</string>
+    <string name="pref_display_add_device_fab">閫f帴鏂拌缃寜閳�</string>
+    <string name="pref_display_add_device_fab_on">绺芥槸椤ず</string>
+    <string name="pref_display_add_device_fab_off">鍙湪鏈柊澧炶缃檪椤ず</string>
+    <string name="pref_title_timeformat">鏅傞枔鏍煎紡</string>
+    <string name="prefs_title_all_day_heart_rate">鍏ㄥぉ蹇冪巼娓噺</string>
+    <string name="zetime_prefs_inactivity_mo">鏄熸湡涓�</string>
+    <string name="zetime_prefs_inactivity_tu">鏄熸湡浜�</string>
+    <string name="zetime_prefs_inactivity_we">鏄熸湡涓�</string>
+    <string name="zetime_prefs_inactivity_th">鏄熸湡鍥�</string>
+    <string name="zetime_prefs_inactivity_fr">鏄熸湡浜�</string>
+    <string name="zetime_prefs_inactivity_sa">鏄熸湡鍏�</string>
+    <string name="zetime_prefs_inactivity_su">鏄熸湡鏃�</string>
+    <string name="pref_title_authkey">瀛樺彇閲戦懓</string>
+    <string name="pref_title_auto_export_location">鍖嚭浣嶇疆</string>
+    <string name="pref_auto_fetch">鑷嫊鎻愬彇娲诲嫊璩囨枡</string>
+    <string name="pref_auto_fetch_summary">鍦ㄨ灑骞曡В閹栨檪鎻愬彇銆傚儏鍦ㄥ凡瑷畾閹栧畾姗熷埗鏅傛墠鏈夋晥锛�</string>
+    <string name="pref_auto_fetch_limit_fetches">鏈�浣庢彁鍙栭枔闅�</string>
+    <string name="pref_auto_fetch_limit_fetches_summary">姣� %d 鍒嗛悩鎻愬彇涓�娆�</string>
+    <string name="connecting">閫g窔涓�</string>
+    <string name="unknown_state">鏈煡鐙�鎱�</string>
+    <string name="bluetooth_is_disabled_">钘嶈娊宸查棞闁夈��</string>
+    <string name="installation_failed_">瀹夎澶辨晽</string>
+    <string name="installation_successful">瀹夎鎴愬姛</string>
+    <string name="title_activity_discovery">瑁濈疆鎺㈢储</string>
+    <string name="discovery_stop_scanning">鍋滄鎺冩弿</string>
+    <string name="discovery_start_scanning">闁嬪鎺㈢储</string>
+    <string name="male">鐢锋��</string>
+    <string name="female">濂虫��</string>
+    <string name="other">鍏朵粬</string>
+    <string name="left">宸﹂倞</string>
+    <string name="right">鍙抽倞</string>
+    <string name="miband_prefs_alias">濮撳悕/鍒ュ悕</string>
+    <string name="pref_write_logfiles">瀵叆鏃ヨ獙妾�</string>
+    <string name="busy_task_fetch_activity_data">姝e湪鎻愬彇娲诲嫊璩囨枡</string>
+    <string name="prefs_wearside">閰嶆埓鍦ㄥ乏閭婇倓鏄彸閭婏紵</string>
+    <string name="prefs_title_heartrate_measurement_interval">鍏ㄥぉ蹇冪巼娓噺</string>
+    <string name="interval_one_minute">姣� 1 鍒嗛悩</string>
+    <string name="interval_five_minutes">姣� 5 鍒嗛悩</string>
+    <string name="interval_ten_minutes">姣� 10 鍒嗛悩</string>
+    <string name="interval_fifteen_minutes">姣� 15 鍒嗛悩</string>
+    <string name="interval_thirty_minutes">姣� 30 鍒嗛悩</string>
+    <string name="interval_forty_five_minutes">姣� 45 鍒嗛悩</string>
+    <string name="interval_one_hour">姣� 1 灏忔檪</string>
+    <string name="control_center_find_lost_device">灏嬫壘閬哄け鐨勮缃�</string>
+    <string name="title_activity_set_alarm">楝ч悩瑷畾</string>
+    <string name="controlcenter_start_configure_alarms">楝ч悩瑷畾</string>
+    <string name="title_activity_alarm_details">楝ч悩瑭崇窗璩囪▕</string>
+    <string name="alarm_sun_short">閫辨棩</string>
+    <string name="alarm_mon_short">閫变竴</string>
+    <string name="alarm_tue_short">閫变簩</string>
+    <string name="alarm_wed_short">閫变笁</string>
+    <string name="alarm_thu_short">閫卞洓</string>
+    <string name="alarm_fri_short">閫变簲</string>
+    <string name="alarm_sat_short">閫卞叚</string>
+    <string name="user_feedback_miband_set_alarms_failed">鍦ㄨō瀹氶閻樻檪鐧肩敓閷锛岃珛鍐嶈│涓�娆°��</string>
+    <string name="user_feedback_miband_set_alarms_ok">楝ч悩宸插偝閫佽嚦瑁濈疆銆�</string>
+    <string name="miband_prefs_fitness_goal">姣忔棩姝ヨ鐩</string>
+    <string name="weekstepschart_steps_a_week">姣忛�辨鏁�</string>
+    <string name="overslept">鐫$湢閬庡害锛�%1$s</string>
+    <string name="updating_firmware">瀹夎闊岄珨涓��</string>
+    <string name="miband_fwinstaller_compatible_version">鐩稿鐗堟湰</string>
+    <string name="miband_fwinstaller_untested_version">鏈脯瑭︾殑鐗堟湰锛�</string>
+    <string name="battery">闆婚噺</string>
+    <string name="no_limit">涓嶉檺鍒�</string>
+    <string name="seconds_5">5 绉�</string>
+    <string name="seconds_10">10 绉�</string>
+    <string name="seconds_20">20 绉�</string>
+    <string name="seconds_30">30 绉�</string>
+    <string name="minutes_1">1 鍒嗛悩</string>
+    <string name="minutes_5">5 鍒嗛悩</string>
+    <string name="minutes_10">10 鍒嗛悩</string>
+    <string name="minutes_30">30 鍒嗛悩</string>
+    <string name="weeksteps_today_steps_description">浠婃棩姝ユ暩锛岀洰妯欙細%1$s</string>
+    <string name="lack_of_step">姝ユ暩涓嶈冻锛�%1$d</string>
+    <string name="overstep">姝ユ暩閬庡墿锛�%1$d</string>
+    <string name="average">骞冲潎锛�%1$s</string>
+    <string name="abstract_chart_fragment_kind_activity">娲诲嫊</string>
+    <string name="abstract_chart_fragment_kind_not_worn">鏈僵鎴�</string>
+    <string name="you_slept">%1$s - %2$s</string>
+    <string name="user_feedback_all_alarms_disabled">鎵�鏈夐閻樼殕宸查棞闁�</string>
+    <string name="miband_prefs_reserve_alarm_calendar">鐐哄嵆灏囩櫦鐢熺殑浜嬩欢淇濈暀楝ч悩</string>
+    <string name="dateformat_time">鏅傞枔</string>
+    <string name="mi2_prefs_goal_notification">鐩閬旀垚閫氱煡</string>
+    <string name="mi2_prefs_goal_notification_summary">鎵嬬挵灏囧湪閬旀垚姣忔棩鐩鏅傞�氱煡</string>
+    <string name="mi2_prefs_activate_display_on_lift">鍟熺敤鎶厱椤ず</string>
+    <string name="mi2_prefs_do_not_disturb">璜嬪嬁鎵撴摼</string>
+    <string name="mi2_prefs_inactivity_warnings">涔呭潗鎻愰啋</string>
+    <string name="mi2_prefs_do_not_disturb_start">闁嬪鏅傞枔</string>
+    <string name="mi2_prefs_do_not_disturb_end">绲愭潫鏅傞枔</string>
+    <string name="mi3_prefs_night_mode">澶滈枔妯″紡</string>
+    <string name="mi3_prefs_night_mode_summary">鍦ㄥ鏅氭檪鑷嫊闄嶄綆铻㈠箷浜害</string>
+    <string name="automatic">鑷嫊</string>
+    <string name="simplified_chinese">绨¢珨涓枃</string>
+    <string name="traditional_chinese">绻侀珨涓枃</string>
+    <string name="english">鑻辫獮</string>
+    <string name="norwegian_bokmal">鎸▉瑾�</string>
+    <string name="spanish">瑗跨彮鐗欐枃</string>
+    <string name="russian">淇勮獮</string>
+    <string name="german">寰疯獮</string>
+    <string name="italian">缇╁ぇ鍒╂枃</string>
+    <string name="french">娉曡獮</string>
+    <string name="polish">娉㈣槶瑾�</string>
+    <string name="korean">闊撹獮</string>
+    <string name="japanese">鏃ユ湰瑾�</string>
+    <string name="dutch">鑽疯槶瑾�</string>
+    <string name="turkish">鍦熻�冲叾瑾�</string>
+    <string name="ukrainian">鐑忓厠铇獮</string>
+    <string name="arabic">闃挎媺浼獮</string>
+    <string name="indonesian">鍗板害灏艰タ浜炶獮</string>
+    <string name="thai">娉拌獮</string>
+    <string name="vietnamese">瓒婂崡瑾�</string>
+    <string name="portuguese">钁¤悇鐗欒獮</string>
+    <string name="FetchActivityOperation_about_to_transfer_since">鍗冲皣鍌宠几鑷� %1$s 浠ヤ締鐨勮硣鏂�</string>
+    <string name="waiting_for_reconnect">姝e湪绛夊緟閲嶆柊閫g窔</string>
+    <string name="activity_prefs_year_birth">鍑虹敓骞�</string>
+    <string name="activity_prefs_gender">鎬у垾</string>
+    <string name="activity_prefs_height_cm">韬珮(鍏垎)</string>
+    <string name="activity_prefs_weight_kg">楂旈噸(鍏枻)</string>
+    <string name="pref_header_charts">鍦栬〃瑷畾</string>
+    <string name="pref_title_charts_swipe">鍟熺敤宸�/鍙虫粦鍕曞垏鎻涘湒琛�</string>
+    <string name="pref_title_charts_average">鍦ㄥ湒琛ㄤ腑椤ず骞冲潎鍊�</string>
+    <string name="activity_prefs_charts">鍦栬〃瑷畾</string>
+    <string name="activity_prefs_chart_max_heart_rate">鏈�楂樺績鐜�</string>
+    <string name="activity_prefs_chart_min_heart_rate">鏈�浣庡績鐜�</string>
+    <string name="pref_title_charts_range">鍦栬〃绡勫湇</string>
+    <string name="pref_charts_range_on">鍦栬〃绡勫湇瑷畾鐐轰竴鍊嬫湀</string>
+    <string name="pref_charts_range_off">鍦栬〃绡勫湇瑷畾鐐轰竴閫�</string>
+    <string name="pref_chart_heartrate_color_red">绱呰壊</string>
+    <string name="pref_chart_heartrate_color_orange">姗樿壊</string>
+    <string name="pref_title_chart_heartrate_color">蹇冪巼椤忚壊</string>
+    <string name="weekstepschart_steps_a_month">姣忔湀姝ユ暩</string>
+    <string name="pref_title_chart_sleep_rolling_24_hour">鐫$湢绡勫湇</string>
+    <string name="pref_chart_sleep_rolling_24_off">鍓嶅ぉ涓崍鑷崇暥澶╀腑鍗�</string>
+    <string name="action_db_management">璩囨枡绠$悊</string>
+    <string name="title_activity_db_management">璩囨枡绠$悊</string>
+    <string name="activity_DB_empty_button">娓呯┖璩囨枡搴�</string>
+    <string name="activity_db_management_empty_DB">娓呯┖璩囨枡搴�</string>
+    <string name="activity_db_management_empty_db_warning">璀﹀憡锛佺暥鎮ㄦ寜涓嬫鎸夐垥鏅傦紝鎮ㄥ皣娓呴櫎璩囨枡搴甫寰為牠闁嬪銆�</string>
+    <string name="timeformat_24h">24灏忔檪鍒�</string>
+    <string name="timeformat_am_pm">AM/PM</string>
+    <string name="find_device_you_found_it">鎵惧埌浜�!</string>
+    <string name="activity_type_light_sleep">娣哄堡鐫$湢</string>
+    <string name="activity_type_deep_sleep">娣卞堡鐫$湢</string>
+    <string name="select_all">鍏ㄩ伕</string>
+    <string name="devicetype_miband">灏忕背鎵嬬挵</string>
+    <string name="devicetype_miband2">灏忕背鎵嬬挵 2</string>
+    <string name="devicetype_miband3">灏忕背鎵嬬挵 3</string>
+    <string name="devicetype_miband4">灏忕背鎵嬬挵 4</string>
+    <string name="menuitem_status">鐙�鎱�</string>
+    <string name="menuitem_notifications">閫氱煡</string>
+    <string name="menuitem_activity">閸涚厜姝峰彶</string>
+    <string name="menuitem_timer">鍊掓暩</string>
+    <string name="menuitem_more">鏇村</string>
+    <string name="menuitem_nfc">NFC</string>
+    <string name="widget_listing_label">鐙�鎱嬭垏楝ч悩</string>
+    <string name="pref_disable_new_ble_scanning">鍋滅敤鏂板瀷钘嶈娊浣庡姛鑰楁巸鎻�</string>
+    <string name="pref_summary_disable_new_ble_scanning">濡傛灉鍦ㄦ巸鎻忛亷绋嬩腑鎵句笉鍒版偍鐨勮缃紝璜嬪嬀閬告閬搁爡</string>
+    <string name="pref_title_transliteration">杞夋彌鏂囧瓧</string>
+    <string name="pref_summary_transliteration">濡傛灉鎮ㄧ殑瑁濈疆涓嶆敮鎻存偍鎵�浣跨敤鐨勮獮瑷�鐨勫瓧楂旓紝璜嬪暉鐢ㄦ鍔熻兘</string>
+    <string name="pref_title_rtl">鐢卞彸鑷冲乏椤ず</string>
+    <string name="pref_summary_rtl">濡傛灉鎮ㄧ殑瑁濈疆鐒℃硶椤ず鐢卞彸鑷冲乏鐨勮獮瑷�锛岃珛鍟熺敤姝ら伕闋�</string>
+    <string name="pref_rtl_max_line_length">鐢卞彸鑷冲乏椤ず鏈�澶у瓧鏁�</string>
+    <string name="pref_rtl_max_line_length_summary">澧為暦鎴栫府鐭敱鍙宠嚦宸︽枃瀛楃殑鍒嗛殧</string>
+    <string name="pref_title_development_miaddr">灏忕背鎵嬬挵钘嶈娊 MAC 浣嶅潃</string>
+    <string name="pref_title_unit_system">鍠綅</string>
+    <string name="pref_summary_authkey">灏囨偍鎯宠閫f帴鐨勬墍鏈� Android 瑁濈疆涓婄殑瀛樺彇閲戦懓璁婃洿鐐洪�氱敤閲戦懓銆傚師鍏堟墍鏈夎缃殑闋愯ō閲戦懓鏄� 0123456789@ABCDE</string>
+    <string name="vibration_profile_short">鐭�</string>
+    <string name="vibration_profile_medium">涓�</string>
+    <string name="vibration_profile_long">闀�</string>
+    <string name="vibration_profile_waterdrop">姘存淮</string>
+    <string name="miband_prefs_vibration">鎸嫊</string>
+    <string name="vibration_try">娓│</string>
+    <string name="pref_screen_notification_profile_sms">绨¤▕閫氱煡</string>
+    <string name="pref_header_vibration_settings">鎸嫊瑷畾</string>
+    <string name="pref_screen_notification_profile_generic">涓�鑸�氱煡</string>
+    <string name="pref_screen_notification_profile_email">闆诲瓙閮典欢閫氱煡</string>
+    <string name="pref_screen_notification_profile_incoming_call">渚嗛浕閫氱煡</string>
+    <string name="pref_screen_notification_profile_missed_call">鏈帴渚嗛浕閫氱煡</string>
+    <string name="pref_screen_notification_profile_generic_chat">鑱婂ぉ</string>
+    <string name="pref_screen_notification_profile_generic_navigation">灏庤埅</string>
+    <string name="pref_screen_notification_profile_generic_social">绀句氦缍茬珯</string>
+    <string name="pref_summary_keep_data_on_device">鍦ㄥ悓姝ュ緦浠嶄繚鐣欐墜閷朵笂鐨勬椿鍕曡硣鏂欍�傚鏋滄偍灏� GB 鑸囧叾浠栨噳鐢ㄧ▼寮忎竴璧蜂娇鐢ㄦ檪锛岄�欓潪甯告湁鐢ㄣ�傚彲鑳藉皫鑷存墜閷跺劜瀛樼┖闁撹�楃洝鍜�/鎴栧仠姝㈡甯稿悓姝ャ��</string>
+    <string name="pref_title_keep_data_on_device">灏囨椿鍕曡硣鏂欎繚鐣欏湪瑁濈疆涓�</string>
+    <string name="mi2_prefs_inactivity_warnings_summary">鎵嬬挵灏囧湪鎮ㄤ竴娈垫檪闁撴湭娲诲嫊鏅傞渿鍕�</string>
+    <string name="mi2_prefs_inactivity_warnings_threshold">鏈椿鍕曢柧鍊硷紙鍒嗛悩锛�</string>
+    <string name="mi2_prefs_inactivity_warnings_dnd_summary">鍦ㄦ檪闁撳収鍋滅敤涔呭潗鎻愰啋</string>
+    <string name="activity_db_management_import_export_explanation">鍖嚭/鍖叆鍕曚綔灏囦娇鐢ㄩ�欎簺(瑕嬩笅鏂�)鍦ㄨ缃笂鐨勮矾寰戙�傛璩囨枡澶惧彲渚涘叾浠� Android 鎳夌敤绋嬪紡鍙婃偍鐨勯浕鑵﹀瓨鍙栥�傝珛娉ㄦ剰锛屽鏋滄偍绉婚櫎 Gadgetbridge锛屾鐩寗鍜屾墍鏈夊収鍚獢妗堥兘鏈冭鍒櫎銆傞�欎簺璩囨枡鍖呭惈锛�
+\n Export_preference - 鍏ㄥ煙瑷畾
+\n Export_preference_MAC - 瑁濈疆椤嶅瑷畾
+\n Gadgetbridge - 瑁濈疆鍙婃椿鍕曠殑璩囨枡搴�
+\n Gadgetbridge_date - 鍦ㄦ煇鍊嬫棩鏈熷尟鍑虹殑璩囨枡搴�
+\n *.gpx - GPS 绱�閷�
+\n *.log - 瑷橀寗妾�
+\n鎮ㄥ彲浠ュ湪閭h鎵惧埌宸插尟鍑虹殑妾旀锛堟垨鏀惧叆瑕佸尟鍏ョ殑妾旀锛夛細</string>
+    <string name="activity_DB_ExportButton">鍖嚭璩囨枡</string>
+    <string name="activity_DB_import_button">鍖叆璩囨枡</string>
+    <string name="activity_db_management_exportimport_label">鍖嚭鍜屽尟鍏�</string>
+    <string name="pref_title_setup_bt_pairing">鍟熺敤钘嶈娊閰嶅皪</string>
+    <string name="pref_summary_setup_bt_pairing">濡傛灉鎮ㄥ湪閫f帴鏅傞亣鍒板晱椤岋紝璜嬪仠鐢ㄦ閬搁爡</string>
+    <string name="unit_metric">鍏埗</string>
+    <string name="unit_imperial">鑻卞埗</string>
+    <string name="on">闁嬪暉</string>
+    <string name="off">闂滈枆</string>
+    <string name="mi2_dnd_off">闂滈枆</string>
+    <string name="mi2_dnd_automatic">鑷嫊锛堢潯鐪犲伒娓級</string>
+    <string name="mi2_dnd_scheduled">瀹夋帓锛堟檪闁撳崁闁擄級</string>
+    <string name="discovery_enable_bluetooth">闁嬪暉钘嶈娊浠ュ皨鎵捐缃��</string>
+    <string name="activity_type_not_measured">鏈脯閲�</string>
+    <string name="activity_type_activity">娲诲嫊</string>
+    <string name="activity_type_not_worn">鏈僵鎴磋缃�</string>
+    <string name="share">鍒嗕韩</string>
+    <string name="kind_firmware">闊岄珨</string>
+    <string name="kind_font">瀛楅珨</string>
+    <string name="devicetype_miscale2">灏忕背楂旈噸瑷� 2</string>
+    <string name="choose_auto_export_location">閬告搰鍖嚭浣嶇疆</string>
+    <string name="menuitem_music">闊虫▊</string>
+    <string name="share_log">鍒嗕韩鏃ヨ獙妾�</string>
+    <string name="share_log_warning">璜嬫敞鎰忥紝Gadgetbridge 鍙兘鏈冭閷勫寘鍚ぇ閲忕殑鍊嬩汉璩囪▕锛屽寘鎷絾涓嶉檺鏂煎仴搴疯硣鏂欍�佸敮涓�璀樺垾纰硷紙濡傝缃殑 MAC 浣嶅潃锛夈�侀煶妯傚亸濂借ō瀹氱瓑銆傚湪灏囨獢妗堝偝閫佸埌鍏枊鐨勫晱椤屽洖鍫卞钩鍙颁箣鍓嶏紝璜嬭�冩叜绶ㄨ集瑭叉獢妗堜甫鍒櫎閫欎簺璩囪▕銆�</string>
+    <string name="warning">璀﹀憡锛�</string>
+    <string name="appwidget_not_connected">鏈�g窔锛岄閻樻湭瑷畾銆�</string>
+    <string name="widget_set_alarm_after">瑷畾澶氫箙寰岀殑楝ч悩锛�</string>
+    <string name="widget_5_minutes">5鍒嗛悩</string>
+    <string name="widget_10_minutes">10鍒嗛悩</string>
+    <string name="widget_20_minutes">20鍒嗛悩</string>
+    <string name="widget_1_hour">1 灏忔檪</string>
+    <string name="watch_not_connected">鎵嬮尪鏈�g窔</string>
+    <string name="appmanager_weather_install_provider">瀹夎澶╂埃閫氱煡鎳夌敤绋嬪紡</string>
+    <string name="app_move_to_top">绉诲埌鏈�涓婃柟</string>
+    <string name="pref_title_notifications_repetitions">閲嶈娆℃暩</string>
+    <string name="pref_summary_expose_hr">鍏佽ū鍏朵粬鎳夌敤绋嬪紡鍦ㄨ缃凡閫g窔鏅傚瓨鍙栧嵆鏅傚績鐜囪硣鏂�</string>
+    <string name="activity_prefs_alarm_max_heart_rate">鏈�楂樺績鐜�</string>
+    <string name="zetime_prefs_inactivity_repetitions">閲嶈娆℃暩</string>
+    <string name="appinstaller_install">瀹夎</string>
+    <string name="vibration_profile_alarm_clock">楝ч悩</string>
+    <string name="stats_title">閫熷害绡勫湇</string>
+    <string name="stats_x_axis_label">绺藉垎閻樻暩</string>
+    <string name="stats_y_axis_label">姣忓垎閻樻鏁�</string>
+    <string name="fwappinstaller_connection_state">閫g窔鍒拌缃細%1$s</string>
+    <string name="heart_rate">蹇冪巼</string>
+    <string name="liveactivity_live_activity">鍗虫檪娲诲嫊</string>
+    <string name="live_activity_current_steps_per_minute">鐩墠姣忓垎閻樻鏁�</string>
+    <string name="live_activity_total_steps">绺芥鏁�</string>
+    <string name="live_activity_steps_per_minute_history">姣忓垎閻樻鏁告鍙�</string>
+    <string name="live_activity_max_heart_rate">鐩墠 / 鏈�楂樺績鐜囷細%1$d / %2$d</string>
+    <string name="mi2_prefs_do_not_disturb_summary">鎵嬬挵灏囦笉鏈冨湪鍟熺敤鏅傛敹鍒伴�氱煡</string>
+    <string name="activity_prefs_sleep_duration">鍋忓ソ鐫$湢鏅傞枔锛堝皬鏅傦級</string>
+    <string name="charts_legend_heartrate">蹇冪巼</string>
+    <string name="live_activity_heart_rate">蹇冪巼</string>
+    <string name="activity_prefs_calories_burnt">姣忔棩鐩锛氬崱璺噷娑堣��</string>
+    <string name="activity_prefs_distance_meters">姣忔棩鐩锛氳窛闆紙鍏昂锛�</string>
+    <string name="activity_prefs_activetime_minutes">姣忔棩鐩锛氭椿鍕曟檪闁擄紙鍒嗛悩锛�</string>
+    <string name="pref_screen_notification_profile_alarm_clock">楝ч悩</string>
+    <string name="pref_title_contextual_arabic">闃挎媺浼獮鐨勬枃鍙�</string>
+    <string name="pref_summary_contextual_arabic">鍟熺敤姝ら伕闋呬互鏀彺闃挎媺浼獮鐨勬枃鍙�</string>
+    <string name="preferences_rtl_settings">鐢卞彸鑷冲乏椤ず鏀彺</string>
+    <string name="fw_upgrade_notice">鎮ㄥ嵆灏囧畨瑁� %s銆�</string>
+    <string name="notif_battery_low_title">瑁濈疆闆婚噺涓嶈冻锛�</string>
+    <string name="notif_battery_low_percent">%1$s 闆婚噺鍓╅锛�%2$s%%</string>
+    <string name="pref_title_location_longitude">缍撳害</string>
+    <string name="pref_title_screentime">铻㈠箷閫炬檪</string>
+    <string name="zetime_title_screentime">铻㈠箷閫炬檪锛堢锛�</string>
+    <string name="activity_prefs_alarm_min_heart_rate">鏈�浣庡績鐜�</string>
+    <string name="test">娓│</string>
+    <string name="cannot_connect_bt_address_invalid_">鐒℃硶閫g窔銆傝棈鑺戒綅鍧�鐒℃晥锛�</string>
+    <string name="n_a">涓嶉仼鐢�</string>
+    <string name="pairing">姝e湪鑸� %s 閰嶅皪鈥�</string>
+    <string name="message_cannot_pair_no_mac">鏈几鍏� MAC 浣嶅潃锛岀劇娉曢厤灏嶃��</string>
+    <string name="miband_pairing_using_dummy_userdata">鏈彁渚涙湁鏁堢殑浣跨敤鑰呰硣鏂欙紝鐩墠浣跨敤鍋囧畾璩囨枡銆�</string>
+    <string name="discovery_connected_devices_hint">璜嬬⒑瑾嶆偍鐨勮缃彲浠ヨ鍋垫脯鍒般�傚凡閫f帴鐨勮缃皣鐒℃硶琚伒娓�傝珛鍦� Android 6 浠ヤ笂鐨勮缃暉鐢ㄤ綅缃瑠闄愶紙濡傦細GPS锛夈�傝珛灏� Gadgetbridge 鍋滅敤闅辩瀹堣锛屽洜鐐洪�欏彲鑳芥渻灏庤嚧鎮ㄧ殑鎵嬫宕╂桨涓﹂噸鏂板暉鍕曘�傚鏋滃湪鏁稿垎閻樺緦浠嶆湭鎵惧埌浠讳綍瑁濈疆锛岃珛閲嶆柊鍟熷嫊鎮ㄧ殑鎵嬫涓﹀啀瑭︿竴娆°��</string>
+    <string name="discovery_note">鍌欒ɑ锛�</string>
+    <string name="initializing">姝e湪鍒濆鍖�</string>
+    <string name="pref_screen_notification_profile_low_power">浣庨浕閲忚鍛�</string>
+    <string name="control_center_cancel_to_stop_vibration">鍙栨秷浠ュ仠姝㈡尟鍕曘��</string>
+    <string name="notif_battery_low_bigtext_last_charge_time">涓婃鍏呴浕锛�%s 
+\n</string>
+    <string name="notif_export_failed_title">鍖嚭璩囨枡搴け鏁楋紒璜嬫鏌ユ偍鐨勮ō瀹氬�笺��</string>
+    <string name="updatefirmwareoperation_update_complete">闊岄珨瀹夎瀹屾垚</string>
+    <string name="updatefirmwareoperation_update_complete_rebooting">闊岄珨瀹夎瀹屾垚锛屾鍦ㄩ噸鍟熻缃��</string>
+    <string name="chart_steps">姝ユ暩</string>
+    <string name="calories">鍗¤矾閲�</string>
+    <string name="distance">璺濋洟</string>
+    <string name="prefs_find_phone">灏嬫壘鎵嬫</string>
+    <string name="updatefirmwareoperation_update_in_progress">姝e湪瀹夎闊岄珨</string>
+    <string name="dbmanagementactivity_import_data_title">鎮ㄧ⒑瀹氳鍖叆璩囨枡锛�</string>
+    <string name="dbmanagementactivity_error_importing_db">鍖叆璩囨枡搴檪鐧肩敓閷锛�%1$s</string>
+    <string name="dbmanagementactivity_database_successfully_deleted">璩囨枡宸插埅闄ゃ��</string>
+    <string name="activity_db_management_autoexport_explanation">璩囨枡搴殑鑷嫊鍖嚭浣嶇疆宸茶ō瀹氱偤锛�</string>
+    <string name="activity_db_management_autoexport_label">鑷嫊鍖嚭</string>
+    <string name="activity_DB_test_export_button">绔嬪嵆鍩疯鑷嫊鍖嚭</string>
+    <string name="activity_DB_test_export_message">姝e湪鍖嚭璩囨枡搴��</string>
+    <string name="Cancel">鍙栨秷</string>
+    <string name="Delete">鍒櫎</string>
+    <string name="discovery_yes_pair">閰嶅皪</string>
+    <string name="activity_type_exercise">閬嬪嫊</string>
+    <string name="kind_gps_cep">GPS 瑾ゅ樊鏍℃</string>
+    <string name="kind_resources">璩囨簮鍖�</string>
+    <string name="devicetype_unknown">鏈煡鐨勮缃�</string>
+    <string name="devicetype_test">娓│鐢ㄨ缃�</string>
+    <string name="menuitem_alarm">楝ч悩</string>
+    <string name="menuitem_alipay">鏀粯瀵�</string>
+    <string name="title_activity_notification_filter">閫氱煡閬庢烤</string>
+    <string name="edittext_notification_filter_words_hint">杓稿叆瑕侀�茶姣斿皪鐨勫瓧锛屼娇鐢ㄦ彌琛屼綔鐐哄垎闅�</string>
+    <string name="toast_notification_filter_saved_successfully">閫氱煡閬庢烤宸插劜瀛�</string>
+    <string name="filter_mode_none">涓嶉亷婵�</string>
+    <string name="filter_mode_whitelist">鍦ㄥ寘鍚�欎簺瀛楁檪椤ず</string>
+    <string name="filter_mode_blacklist">鍦ㄥ寘鍚�欎簺瀛楁檪闅辫棌</string>
+    <string name="filter_submode_at_least_one">閮ㄤ唤绗﹀悎</string>
+    <string name="filter_submode_all">瀹屽叏绗﹀悎</string>
+    <string name="toast_notification_filter_words_empty_hint">璜嬭嚦灏戣几鍏ヤ竴鍊嬪瓧</string>
+    <string name="filter_mode">閬庢烤妯″紡</string>
+    <string name="mode_configuration">妯″紡瑷畾</string>
+    <string name="add_widget">鏂板灏忓伐鍏�</string>
+    <string name="alarm_snooze">璨潯</string>
+    <string name="pref_title_allow_high_mtu">鍏佽ū杓冮珮鐨� MTU 鍊�</string>
+    <string name="pref_summary_allow_high_mtu">鎻愰珮鍌抽�侀�熷害锛屼絾鍙兘鐒℃硶鍦ㄦ煇浜� Android 瑁濈疆涓婁娇鐢ㄣ��</string>
+    <string name="pref_summary_sync_calendar">鍟熺敤鏃ユ泦浜嬩欢閫氱煡锛屽嵆渚垮湪鏈�g窔鏅�</string>
+    <string name="pref_title_sync_caldendar">鍚屾鏃ユ泦浜嬩欢</string>
+    <string name="hr_widget_heart_rate">蹇冪巼</string>
+    <string name="hr_widget_steps">姝ユ暩</string>
+    <string name="hr_widget_date">鏃ユ湡</string>
+    <string name="hr_widget_battery">闆婚噺</string>
+    <string name="hr_widget_weather">澶╂埃</string>
+    <string name="find_lost_device_you_found_it">鎵惧埌浜嗭紒</string>
+    <string name="find_my_phone_notification">灏嬫壘鎴戠殑鎵嬫</string>
+    <string name="devicetype_amazfit_bips">鑿背 Bip S</string>
+    <string name="hr_widget_nothing">娌掓湁</string>
+    <string name="hr_widget_calories">鍗¤矾閲�</string>
+    <string name="error_no_location_access">浣嶇疆瀛樺彇娆婇檺蹇呴爤琚巿浜堜甫鍟熺敤鎵嶈兘閫茶鎺冩弿</string>
+    <string name="devicetype_y5">Y5</string>
+    <string name="devicetype_banglejs">Bangle.js</string>
+    <string name="qhybrid_goal_in_steps">鐩姝ユ暩</string>
+    <string name="qhybrid_vibration_strength">鎸嫊寮峰害锛�</string>
+    <string name="save_configuration">鍎插瓨瑷畾</string>
+    <string name="preferences_fm_frequency">FM 闋荤巼</string>
+    <string name="preferences_led_color">LED 椤忚壊</string>
+    <string name="no_data">娌掓湁璩囨枡</string>
+    <string name="watch9_time_seconds">绉掞細</string>
+    <string name="watch9_time_hours">灏忔檪锛�</string>
+    <string name="watch9_time_minutes">鍒嗛悩锛�</string>
+    <string name="menuitem_compass">鎸囧崡閲�</string>
+    <string name="notification_channel_name">涓�鑸�</string>
+    <string name="devicetype_roidmi3">鐫跨背3</string>
+    <string name="devicetype_roidmi">鐫跨背</string>
+    <string name="devicetype_watch9">Watch 9</string>
+    <string name="devicetype_mykronoz_zetime">MyKronoz ZeTime</string>
+    <string name="devicetype_no1_f1">No.1 F1</string>
+    <string name="devicetype_amazfit_bip">鑿背 Bip</string>
+    <string name="kind_invalid">鐒℃晥鐨勮硣鏂�</string>
+    <string name="activity_type_walking">姝ヨ</string>
+    <string name="activity_type_running">璺戞</string>
+    <string name="discovery_dont_pair">涓嶉厤灏�</string>
+    <string name="discovery_pair_title">鑸�%1$s閫茶閰嶅皪\?</string>
+    <string name="discovery_attempting_to_pair">姝e湪鍢楄│鑸�%1$s閫茶閰嶅皪</string>
+    <string name="mi2_enable_text_notifications">鏂囧瓧閫氱煡</string>
+    <string name="ok">濂�</string>
+    <string name="activity_DB_delete_legacy_button">鍒櫎鑸婄殑璩囨枡搴�</string>
+    <string name="dbmanagementactivity_import_successful">宸插尟鍏ャ��</string>
+    <string name="dbmanagementactivity_exported_to">宸插尟鍑哄埌锛�%1$s</string>
+    <string name="updatefirmwareoperation_firmware_not_sent">闊岄珨鏈偝閫�</string>
+    <string name="device_fw">闊岄珨鐗堟湰锛�%1$s</string>
+    <string name="authenticating">椹楄瓑涓�</string>
+    <string name="fwinstaller_firmware_not_compatible_to_device">姝ら煂楂旇垏瑭茶缃笉鐩稿</string>
+    <string name="miband_fwinstaller_incompatible_version">涓嶇浉瀹圭殑鐗堟湰</string>
+    <string name="updatefirmwareoperation_write_failed">闊岄珨瀹夎澶辨晽</string>
+    <string name="updatefirmwareoperation_updateproblem_do_not_reboot">鍦ㄥ偝杓搁煂楂旀檪鐧肩敓鍟忛銆傝珛鍕块噸鍟熸偍鐨勫皬绫虫墜鐠帮紒</string>
+    <string name="fwapp_install_device_not_ready">妾旀鐒℃硶琚畨瑁濓紝瑁濈疆鏈氨绶掋��</string>
+    <string name="chart_no_data_synchronize">灏氱劇璩囨枡銆傚悓姝ヨ缃紵</string>
+    <string name="pref_screen_notification_profile_calendar">鏃ユ泦閫氱煡</string>
+    <string name="discovery_need_to_enter_authkey">姝よ缃渶瑕佷竴绲勯璀夊瘑閼帮紝鍦ㄨ缃笂闀锋寜浠ラ�插叆瑷畾銆傝┏绱拌硣瑷婅珛闁辫畝灏堟 wiki銆�</string>
+    <string name="miband_pairing_tap_hint">鐣舵偍鐨勫皬绫虫墜鐠版尟鍕曚甫闁冪垗鏅傦紝璜嬮�g簩榛炴搳鏁告銆�</string>
+    <string name="pairing_already_bonded">宸茶垏瑁濈疆 %1$s (%2$s) 瀹屾垚缍佸畾锛岄�g窔涓��</string>
+    <string name="title_activity_mi_band_pairing">閰嶅皪鎮ㄧ殑灏忕背鎵嬬挵</string>
+    <string name="title_activity_android_pairing">閰嶅皪瑁濈疆</string>
+    <string name="initialized">鍒濆鍖栧畬鎴�</string>
+    <string name="gadgetbridge_running_generic">Gadgetbridge 姝e湪鍩疯</string>
+    <string name="tap_a_device_to_connect">榛炴搳涓�鍊嬭缃互閫茶閫g窔</string>
+    <string name="bluetooth_is_not_supported_">钘嶈娊涓嶆敮鎻淬��</string>
+    <string name="zetime_calories_type_all">娑佃搵娲诲嫊鑸囨湭娲诲嫊鏅傛秷鑰楃殑鍗¤矾閲�</string>
+    <string name="zetime_calories_type_active">鍍呮椿鍕曟檪娑堣�楃殑鍗¤矾閲�</string>
+    <string name="zetime_calories_type">鍗¤矾閲岀ó椤�</string>
+    <string name="zetime_activity_tracking_summary">闁嬪暉娲诲嫊杩借工灏囩磤閷勬偍鐨勬鏁哥瓑璩囨枡銆�</string>
+    <string name="zetime_activity_tracking">娲诲嫊杩借工</string>
+    <string name="zetime_heart_rate_alarm_enable">鍟熺敤蹇冪巼璀﹀憡</string>
+    <string name="zetime_title_heart_rate_alarm_summary">鎵嬮尪灏囨渻鍦ㄦ偍鐨勫績鐜囪秴閬庨檺鍒舵檪璀﹀憡鎮ㄣ��</string>
+    <string name="zetime_title_heart_rate_alarm">蹇冪巼璀﹀憡</string>
+    <string name="pref_summary_use_custom_font">鑻ユ偍鐨勮缃娇鐢ㄤ簡鑷▊鐨勫瓧楂斾互鏀彺琛ㄦ儏绗﹁櫉锛岃珛闁嬪暉姝ゅ姛鑳�</string>
+    <string name="pref_title_use_custom_font">浣跨敤鑷▊瀛楅珨</string>
+    <string name="pref_title_pebble_reconnect_attempts">閲嶆柊閫g窔鍢楄│娆℃暩</string>
+    <string name="pref_title_pebble_forceuntested">鍟熺敤灏氭湭娓│鐨勫姛鑳�</string>
+    <string name="pref_title_location_keep_uptodate">淇濇寔浣嶇疆鎸佺簩鏇存柊</string>
+    <string name="pref_title_location_latitude">绶害</string>
+    <string name="pref_title_location_aquire">鍙栧緱浣嶇疆</string>
+    <string name="pref_header_location">浣嶇疆</string>
+    <string name="pref_title_enable_pebblekit">鍏佽ū绗笁鏂� Android 鎳夌敤绋嬪紡瀛樺彇</string>
+    <string name="pref_title_enable_outgoing_call">鏀彺鎾ュ嚭闆昏┍</string>
+    <string name="title_activity_calblacklist">鍒楀叆榛戝悕鍠殑鏃ユ泦</string>
+    <string name="no">鍚�</string>
+    <string name="yes">鏄�</string>
+    <string name="activity_summary_yesterday">鏄ㄥぉ</string>
+    <string name="activity_summary_today">浠婂ぉ</string>
+    <string name="km">鍏噷</string>
+    <string name="activity_type_yoga">鐟滀冀</string>
+    <string name="activity_type_swimming_openwater">娓告吵锛堥枊鏀惧紡姘村煙锛�</string>
+    <string name="prefs_events_forwarding_action_title">鍩疯鍕曚綔</string>
+    <string name="prefs_events_forwarding_broadcast_title">寤f挱瑷婃伅</string>
+    <string name="prefs_events_forwarding_startnonwear">鍦ㄦ湭閰嶆埓鏅�</string>
+    <string name="prefs_events_forwarding_wokeup">鍦ㄩ啋渚嗘檪</string>
+    <string name="prefs_events_forwarding_fellsleep">鍦ㄧ啛鐫℃檪</string>
+    <string name="prefs_events_forwarding_title">瑁濈疆琛岀偤</string>
+    <string name="prefs_events_forwarding_summary">浣跨敤瑁濈疆鐨勪簨浠朵締瑙哥櫦鍕曚綔鍜� Android 寤f挱</string>
+    <string name="activity_detail_duration_label">鎸佺簩鏅傞枔</string>
+    <string name="activity_detail_end_label">绲愭潫鏅傞枔</string>
+    <string name="activity_detail_start_label">闁嬪鏅傞枔</string>
+    <string name="Steps">姝ユ暩</string>
+    <string name="Speed">閫熷害</string>
+    <string name="Swimming">娓告吵</string>
+    <string name="steps_unit">姝�</string>
+    <string name="cm">鍏垎</string>
+    <string name="meters">鍏昂</string>
+    <string name="activity_summary_detail">閬嬪嫊娲诲嫊瑭崇窗璩囪▕</string>
+    <string name="companiondevice_pairing_details">鍟熺敤鏂扮殑CompanionDevice API鏀彺锛堝儏鍦ˋndroid 8鎴栨洿楂樼増鏈笂鏈夋晥锛夛紝閫欏皣鎻愰珮鐣舵湇鍕欓渶瑕佸湪鑳屾櫙閲嶆柊鍟熷嫊鏅傜殑鍙潬鎬э紝闇�瑕佷娇鐢� Gadgetbridge 閫茶閲嶆柊閰嶅皪鎵嶈兘鐢熸晥</string>
+    <string name="companiondevice_pairing">浣跨敤 CompanionDevice 閫茶閰嶅皪</string>
+    <string name="ignore_bonded_devices_description">鍟熺敤姝ら伕闋呭皣鍦ㄦ巸鎻忔檪蹇界暐宸茬秮瀹�/宸查厤灏嶇殑瑁濈疆</string>
+    <string name="ignore_bonded_devices">蹇界暐宸茬秮瀹氱殑瑁濈疆</string>
+    <string name="pref_check_permission_status">妾㈡煡娆婇檺鐙�鎱�</string>
+    <string name="device_requires_key">闇�瑕侀噾閼帮紝闀锋寜鍙�插叆</string>
+    <string name="device_is_currently_bonded">宸茬秮瀹�</string>
+    <string name="devicetype_miband5">灏忕背鎵嬬挵5</string>
+    <string name="pref_title_weather_summary">閬╃敤鏂� LineageOS 澶╂埃鎻愪緵鑰咃紝鍏朵粬 Android 鐗堟湰闇�瑕佷娇鐢ㄣ�學eather notification銆嶄箣椤炵殑鎳夌敤绋嬪紡銆� 璜嬪湪 Gadgetbridge Wiki 涓皨鎵炬洿澶氳硣瑷娿��</string>
+    <string name="about_additional_contributions">闈炲父鎰熻瑵鎵�鏈夋湭鍒楀嚭鐨勮并鐛昏�呰并鐛讳簡绋嬪紡纰笺�佺炕璀�佹敮鎸併�佹兂娉曘�佸嫊鍔涖�侀尟瑾ゅ牨鍛娿�侀噾閷⑩�︹�︹湂</string>
+    <string name="about_additional_device_support">椤嶅瑁濈疆鏀彺</string>
+    <string name="about_contributors">璨㈢嵒鑰�</string>
+    <string name="about_core_team_title">鏍稿績鍦橀殜锛堟寜绗竴娆$▼寮忕⒓璨㈢嵒闋嗗簭鎺掑垪锛�</string>
+    <string name="about_description_generic">鐢ㄤ締鍙栦唬寤犲晢鎻愪緵鐨勯枆婧� Android 灏忚缃噳鐢ㄧ▼寮忕殑闆㈢窔涓旂増娆婅嚜鐢辩殑鏇夸唬鍝併��</string>
+    <string name="about_activity_title_generic">闂滄柤 Gadgetbridge</string>
+    <string name="about_title">闂滄柤</string>
+    <string name="activity_summaries">閬嬪嫊娲诲嫊</string>
+    <string name="activity_type_swimming">娓告吵</string>
+    <string name="this_is_a_test_notification_from_gadgetbridge">閫欐槸涓�鍊嬩締鑷� Gadgetbridge 鐨勬脯瑭﹂�氱煡</string>
+    <string name="test_notification">娓│閫氱煡</string>
+    <string name="sonyswr12_settings_title">Sony SWR12 璁剧疆</string>
+    <string name="sonyswr12_settings_low_vibration">浣庢尟鍔ㄥ惎鐢�</string>
+    <string name="sonyswr12_settings_low_vibration_summary">闄嶄綆棰戝甫涓婄殑鎸姩寮哄害</string>
+    <string name="sonyswr12_settings_stamina">鐪佺數妯″紡宸插紑鍚�</string>
+    <string name="sonyswr12_settings_stamina_summary">鐪佺數妯″紡浼氬叧闂畾鏈熻嚜鍔ㄦ祴閲忓績鐜囷紝浠庤�屽鍔犲伐浣滄椂闂�</string>
+    <string name="sonyswr12_settings_alarm_interval">鏅鸿兘璀︽姤闂撮殧锛堜互鍒嗛挓涓哄崟浣嶏級</string>
+    <string name="sonyswr12_settings_alarm_interval_summary">鏅鸿兘璀︽姤闂撮殧鏄畨瑁呰鎶ヤ箣鍓嶇殑闂撮殧銆� 鍦ㄦ闂撮殧鍐咃紝璁惧灏濊瘯妫�娴嬫渶杞荤殑鐫$湢闃舵浠ュ敜閱掔敤鎴�</string>
+    <string name="activity_filter_to_placeholder">浠婂ぉ</string>
+    <string name="activity_filter_from_placeholder">閬欓仩鐨勯亷鍘�</string>
+    <string name="activity_summaries_all_devices">鎵�鏈夎缃�</string>
+    <string name="sports_activity_quick_filter_select">鏅傞枔閫辨湡</string>
+    <string name="sports_activity_quick_filter_30days">30澶�</string>
+    <string name="sports_activity_quick_filter_7days">7澶�</string>
+    <string name="sports_activity_quick_filter_last_month">涓婂�嬫湀</string>
+    <string name="sports_activity_quick_filter_this_month">閫欏�嬫湀</string>
+    <string name="sports_activity_quick_filter_last_week">涓婇��</string>
+    <string name="sports_activity_quick_filter_this_week">閫欓��</string>
+    <string name="activity_summaries_all_activities">鎵�鏈夋椿鍕�</string>
+    <string name="activity_filter_apply_filter">濂楃敤绡╅伕鍣�</string>
+    <string name="activity_filter_name_contains">妯欑堡</string>
+    <string name="activity_filter_filter_title">绡╅伕鍣�</string>
+    <string name="activity_filter_reset_filter">閲嶈ō绡╅伕鍣�</string>
+    <string name="activity_filter_date_to">鑷�</string>
+    <string name="activity_filter_date_from">寰�</string>
+    <string name="about_links">閫g祼</string>
+    <string name="about_version">鐗堟湰 %s</string>
+    <string name="widget_settings_select_device_title">閬告搰瑁濈疆</string>
+    <string name="dbmanagementactivity_export_finished">鍒櫎瀹屾垚</string>
+    <string name="activity_db_management_clean_export_directory_text">鍦ㄦ偍瑁濈疆涓婄殑浠讳綍鎳夌敤绋嬪紡鐨嗗彲瀛樺彇鍦ㄥ尟鍑�/鍖叆璩囨枡澶句腑宸插尟鍑虹殑妾旀銆傛偍鍙兘甯屾湜鍦ㄥ倷浠芥垨鍚屾寰岀Щ闄ら�欎簺妾旀銆傚埅闄や箣鍓嶏紝璜嬬⒑瑾嶇暀鏈変竴浠藉倷浠姐�侴PX妾旀銆佸瓙璩囨枡澶惧強鑷嫊鍖嚭鐨勮硣鏂欏韩妾旀锛堝鏋滄湁鐨勮┍锛夊皣涓嶆渻琚埅闄ゃ�傚尟鍑�/鍖叆璩囨枡澶剧殑璺緫鏄細</string>
+    <string name="activity_DB_clean_export_directory_warning_message">纰哄畾瑕佸埅闄ゅ湪鍖嚭/鍖叆璩囨枡澶剧殑妾旀锛�</string>
+    <string name="activity_DB_clean_export_directory_warning_title">鍒櫎鍦ㄥ尟鍑�/鍖叆璩囨枡澶剧殑妾旀锛�</string>
+    <string name="activity_db_management_clean_export_directory_label">鍒櫎鍦ㄥ尟鍑�/鍖叆璩囨枡澶剧殑妾旀</string>
+    <string name="activity_DB_ShowContentButton">椤ず鍦ㄥ尟鍑�/鍖叆璩囨枡澶剧殑鍏у</string>
+    <string name="activity_data_management_directory_content_title">鍖嚭/鍖叆璩囨枡澶剧殑鍏у</string>
+    <string name="dbmanagementactivity_overwrite">瑕嗗</string>
+    <string name="activity_prefs_step_length_cm">姝ヤ紣闀峰害锛堝叕鍒嗭級</string>
+    <string name="bip_prefs_shotcuts_summary">閬告搰鍦ㄦ墜鐠拌灑骞曚笂鐨勬嵎寰�</string>
+    <string name="bip_prefs_shortcuts">鎹峰緫</string>
+    <string name="devicestatus_connected">瑁濈疆宸查�g窔</string>
+    <string name="devicestatus_connecting">瑁濈疆閫g窔涓�</string>
+    <string name="dialog_hide">闅辫棌</string>
+    <string name="activity_list_summary_activities">娲诲嫊娆℃暩</string>
+    <string name="activity_list_summary_intensity">绉诲嫊
+\n寮峰害</string>
+    <string name="activity_list_summary_active_time">娲诲嫊鏅傞枔</string>
+    <string name="activity_list_summary_distance">璺濋洟</string>
+    <string name="activity_list_summary_active_steps">娲诲嫊姝ユ暩</string>
+    <string name="charts_activity_list">娲诲嫊娓呭柈</string>
+    <string name="prefs_charts_tabs_summary">椤ず鐨勫湒琛ㄦ绫�</string>
+    <string name="prefs_charts_tabs">鍦栬〃妯欑堡</string>
+    <string name="pref_summary_connected_advertisement">璁撹缃湪閫g窔寰屼粛鍙�忛亷钘嶈娊鐧肩従</string>
+    <string name="pref_title_connected_advertisement">鍦ㄩ�g窔寰屼繚鎸佸彲瑕�</string>
+    <string name="calendar_year">涓�骞�</string>
+    <string name="calendar_six_months">6鍊嬫湀</string>
+    <string name="calendar_three_months">3鍊嬫湀</string>
+    <string name="calendar_month">涓�鍊嬫湀</string>
+    <string name="calendar_two_weeks">鍏╅��</string>
+    <string name="calendar_week">涓�閫�</string>
+    <string name="calendar_day">涓�澶�</string>
+    <string name="battery_level">闆绘睜闆婚噺</string>
+    <string name="battery_detail_activity_title">闆绘睜璩囪▕</string>
+    <string name="controlcenter_set_alias">瑷畾鍒ュ悕</string>
+    <string name="find_lost_device_message">灏嬫壘 %1$s\?</string>
+    <string name="controlcenter_power_off_confirm_title">闂滄</string>
+    <string name="controlcenter_power_off_confirm_description">鎮ㄧ⒑瀹氳闂滈枆瑁濈疆闆绘簮鍡庯紵</string>
+    <string name="check_all_applications">鍕鹃伕鎵�鏈夋噳鐢ㄧ▼寮�</string>
+    <string name="uncheck_all_applications">鍙栨秷鍕鹃伕鎵�鏈夋噳鐢ㄧ▼寮�</string>
+    <string name="pref_title_notification_use_as_deny">鎷掔禃渚嗚嚜鎵�閬告噳鐢ㄧ▼寮忕殑閫氱煡</string>
+    <string name="pref_header_other">鍏朵粬</string>
+    <string name="pref_header_system">绯荤当</string>
+    <string name="pref_title_message_privacy_mode">瑷婃伅闅辩妯″紡</string>
+    <string name="pref_message_privacy_mode_off">椤ず鍏ㄩ儴鍏у</string>
+    <string name="pref_message_privacy_mode_complete">闅辫棌鍏ㄩ儴鍏у</string>
+    <string name="controlcenter_power_off">闂滄</string>
+    <string name="mi2_prefs_do_not_disturb_lift_wrist">鍦ㄨ珛鍕挎墦鎿炬湡闁撳暉鐢ㄦ姮鑵曢’绀�</string>
+    <string name="prefs_activity_in_device_card_title_summary">鍦ㄨ缃崱涓婇’绀虹洰鍓嶆鏁搞�佽窛闆㈡垨鐫$湢</string>
+    <string name="title_activity_notification_management">閫氱煡瑷畾</string>
+    <string name="pref_title_notification_use_as">浣跨敤鎳夌敤绋嬪紡娓呭柈渚嗏��</string>
+    <string name="pref_applications_settings">鎳夌敤绋嬪紡娓呭柈</string>
+    <string name="pref_title_notification_use_as_allow">鍏佽ū渚嗚嚜鎵�閬告噳鐢ㄧ▼寮忕殑閫氱煡</string>
+    <string name="pref_header_notification_application_settings">鍊嬪垾鎳夌敤绋嬪紡瑷畾</string>
+    <string name="discovery_trying_to_connect_to">姝e湪鍢楄│閫g窔鍒帮細 %1$s</string>
+    <string name="activity_prefs_discovery_pairing">鎺㈢储鍜岄厤灏嶉伕闋�</string>
+    <string name="pref_summary_location_keep_uptodate">鍢楄│鍦ㄥ煼琛屾檪鐛插彇鐩墠浣嶇疆锛屼娇鐢ㄥ劜瀛樼殑浣嶇疆浣滅偤寰屽倷</string>
+    <string name="pref_theme_black_background">鍦ㄦ繁鑹蹭富椤屼腑浣跨敤榛戣壊鑳屾櫙</string>
+    <string name="pref_header_auto_fetch">鑷嫊鎻愬彇</string>
+    <string name="device_card_activity_card_title_summary">閬告搰鍦ㄨ缃崱涓婅椤ず鍝簺娲诲嫊瑭崇窗璩囪▕</string>
+    <string name="device_card_activity_card_title">瑁濈疆鍗′笂鐨勬椿鍕曠媭鎱�</string>
+    <string name="prefs_activity_in_device_card_title">鍦ㄨ缃崱涓婇’绀烘椿鍕曡硣瑷�</string>
+    <string name="prefs_activity_in_device_card_sleep_title">鐫$湢</string>
+    <string name="prefs_activity_in_device_card_sleep_title_summary">椤ず鐫$湢鏅傞枔</string>
+    <string name="prefs_activity_in_device_card_distance_title_summary">璺濋洟鏄牴鎿氭鏁稿拰姝ラ暦瑷堢畻鍑虹殑锛堝彲浠ュ湪瑷畾 - 闂滄柤浣犱腑瑾挎暣锛�</string>
+    <string name="prefs_activity_in_device_card_steps_title_summary">椤ず绺芥鏁�</string>
+    <string name="discover_unsupported_devices_description">鍟熺敤姝ら伕闋呭皣鍦ㄦ巸鎻忔檪椤ず鎵�鏈夌櫦鐝剧殑钘嶈娊瑁濈疆銆傜煭鎸夋渻灏囪ō鍌欏悕绋卞拰 mac 浣嶅潃瑜囪=鍒板壀璨肩翱銆傞暦鎸夊皣闁嬪暉銆屾柊澧炴脯瑭﹁缃�嶅皪瑭辨銆傛湁鍙兘灏庤嚧鎳夌敤绋嬪紡鍑嶇祼銆�</string>
+    <string name="discover_unsupported_devices">鎺㈢储涓嶆敮鎻寸殑瑁濈疆</string>
+    <string name="pref_theme_system">鏍规摎绯荤当</string>
+    <string name="discovery_bluetooth_scan">钘嶈娊鎺冩弿锛�</string>
+    <string name="discovery_bluetooth_le_scan">浣庡姛鑰楄棈鑺芥巸鎻忥細</string>
+    <string name="fw_upgrade_notice_miband6">鎮ㄥ嵆灏囧湪鎮ㄧ殑灏忕背鎵嬬挵6涓婂畨瑁濋煂楂� %s 
+\n 
+\n璜嬬⒑瀹氬厛瀹夎 .fw 妾旓紝鐒跺緦鍐嶅畨瑁� .res 妾斻�傛偍鐨勬墜鐠板皣鍦ㄥ畨瑁濆畬 .fw 妾斿緦閲嶆柊鍟熷嫊銆� 
+\n 
+\n娉ㄦ剰锛氬鏋滄偍瀹夎鐨勬槸鑸囧厛鍓嶅悓妯g殑鐗堟湰锛屾偍灏变笉蹇呭啀瀹夎 .res 妾斻�� 
+\n 
+\n閬庣▼涓殑棰ㄩ毆璜嬭嚜璨狅紒</string>
+    <string name="watchface_setting_light_up_on_notification">鏂伴�氱煡鏃朵寒璧�</string>
+    <string name="fw_upgrade_notice_amazfitneo">鎮ㄥ皣瑕佸湪Amazfit Neo涓婂畨瑁�%s鍥轰欢銆�
+\n
+\n瀹夎 .fw 妾斿緦锛屾偍鐨勬墜鐠板皣閲嶆柊鍟熷嫊銆�
+\n
+\n閬庣▼涓殑棰ㄩ毆璜嬭嚜璨狅紒</string>
+    <string name="fw_upgrade_notice_amazfitx">鎮ㄥ皣瑕佸湪Amazfit X涓婂畨瑁�%s鍥轰欢銆�
+\n
+\n璜嬬⒑瀹氬厛瀹夎 .fw 妾旓紝鐒跺緦鍐嶅畨瑁� .res 妾斻�傛偍鐨勬墜鐠板皣鍦ㄥ畨瑁濆畬 .fw 妾斿緦閲嶆柊鍟熷嫊銆�
+\n
+\n娉ㄦ剰锛氬鏋滄偍瀹夎鐨勬槸鑸囧厛鍓嶅悓妯g殑鐗堟湰锛屾偍灏变笉蹇呭啀瀹夎 .res 妾斻��
+\n
+\n閬庣▼涓殑棰ㄩ毆璜嬭嚜璨狅紒</string>
+    <string name="about_activity_title_banglejs_main">闂滄柤Bangle.js Gadgetbridge</string>
+    <string name="gadgetbridge_running_banglejs_main">Bangle.js姝e湪鍩疯</string>
+    <string name="about_activity_title_banglejs_nightly">闂滄柤Bangle.js Gadgetbridge锛堝厛琛岀増锛�</string>
+    <string name="supercars_lights_blinking_label">闁冪垗</string>
+    <string name="mi2_prefs_button_actions">鎸夐垥鍕曚綔</string>
+    <string name="prefs_disconnect_notification_summary">鐣惰垏钘嶈娊鏂风窔鏅傦紝瑁濈疆涓婃渻鐧煎嚭閫氱煡銆�</string>
+    <string name="mi2_prefs_button_actions_summary">鎸囧畾鎸変笅鎸夐嵉鐨勫嫊浣�</string>
+    <string name="accuracy">婧栫⒑</string>
+    <string name="title_activity_controlcenter_main_nightly">Gadgetbridge 鍏堣鐗�</string>
+    <string name="application_name_main_nightly">Gadgetbridge (鍏堣鐗�)</string>
+    <string name="about_activity_title_main_nightly">闂滄柤 Gadgetbridge 鍏堣鐗�</string>
+    <string name="controlcenter_get_heartrate_measurement">鍙栧緱蹇冪巼娓噺</string>
+    <string name="action_changelog">鏇存柊鏃ヨ獙</string>
+    <string name="search">鎼滃皨</string>
+    <string name="balanced">骞宠 </string>
+    <string name="power_saving">鐪侀浕</string>
+    <string name="custom">鑷▊</string>
+    <string name="speed_first">閫熷害鍎厛</string>
+    <string name="accuracy_first">婧栫⒑鍎厛</string>
+    <string name="prefs_disconnect_notification">涓柗閫氱煡</string>
+    <string name="open_fw_installer_warning_title">璀﹀憡</string>
+    <string name="pref_header_calendar">鏃ユ泦</string>
+    <string name="pref_header_workout">閬嬪嫊</string>
+    <string name="bluetooth_calls_pairing">钘嶇墮閫氳┍閰嶅皪</string>
+    <string name="pref_title_banglejs_webview_url">鎳夌敤绋嬪紡鍔犺級鍣ㄧ恫鍧�</string>
+    <string name="world_clock_no_free_slots_description">瑭茶ō鍌欐矑鏈夊彲鐢ㄧ殑涓栫晫鏅傞悩鎻掓Ы锛堢附鎻掓Ы鏁革細%1$s锛�</string>
+    <string name="title_activity_contact_details">鑱怠璩囨枡</string>
+    <string name="appmanager_watchface_activate">鍟熷嫊</string>
+    <string name="activity_type_indoor_ice_skating">瀹ゅ収婊戝啺</string>
+    <string name="menuitem_hr">蹇冪巼</string>
+    <string name="menuitem_events">浜嬩欢</string>
+    <string name="pref_invalid_frequency_message">璜嬭几鍏ヤ竴鍊嬩粙鏂�87.5鍜�108.0涔嬮枔鐨勯牷鐜�</string>
+    <string name="pref_title_equalizer_bass_boost">閲嶄綆闊冲寮�</string>
+    <string name="sony_equalizer_band_6300">6.3鍗�</string>
+    <string name="long_press">闀锋寜</string>
+    <string name="continue_pressing">绻肩簩鎸夊</string>
+    <string name="step_streak_total">绺借▓</string>
+    <string name="steps_streaks_average_steps">骞冲潎
+\n姝ユ暩</string>
+    <string name="prefs_wena3_item_background_sync">鑳屾櫙娲诲嫊鏁告摎鍚屾</string>
+    <string name="pref_header_vibration_count">鎸嫊瑷堟暩</string>
+    <string name="watch9_pairing_tap_hint">鐣朵綘鐨勬墜閷堕渿鍕曟檪锛屾悥鍕曡ō鍌欐垨鎸変笅鍏舵寜閳曘��</string>
+    <string name="pref_speak_notifications_aloud_title">澶ц伈鏈楄畝閫氱煡</string>
+    <string name="pref_title_notifications_ignore_low_priority">蹇界暐浣庡劒鍏堢礆閫氱煡</string>
+    <string name="prefs_spo2_monitoring_title">琛�姘х洠娓�</string>
+    <string name="pref_title_pebble_always_ack_pebblekit">鎻愬墠 ACK PebbleKit</string>
+    <string name="devicetype_amazfit_gts2">Amazfit GTS 2</string>
+    <string name="devicetype_amazfit_gts2_mini">Amazfit GTS 2 Mini</string>
+    <string name="devicetype_amazfit_gts2e">Amazfit GTS 2e</string>
+    <string name="devicetype_garmin_vivomove_style">Garmin V铆vomove 棰ㄥ皻</string>
+    <string name="devicetype_garmin_vivomove_trend">Garmin V铆vomove Trend</string>
+    <string name="devicetype_garmin_venu_3s">Garmin Venu 3S</string>
+    <string name="devicetype_garmin_fenix_5">Garmin Fenix 5</string>
+    <string name="devicetype_q8">Q8</string>
+    <string name="menuitem_nothing">娌掓湁</string>
+    <string name="menuitem_volume">闊抽噺</string>
+    <string name="menuitem_eject_water">鎺掑嚭姘�</string>
+    <string name="menuitem_headphone">鑰虫</string>
+    <string name="menuitem_buzzer_intensity">铚傞炒鍣ㄥ挤搴�</string>
+    <string name="zepp_os_watchface_red_fantasy">绱呰壊骞绘兂</string>
+    <string name="toast_app_must_be_selected">蹇呴爤閬告搰鎳夌敤绋嬪紡鎵嶈兘閫茶閰嶇疆</string>
+    <string name="appwidget_sleep_alarm_widget_label">鐫$湢楝ч悩</string>
+    <string name="qhybrid_prompt_million_steps">璜嬪皣姝ユ暩瑷疆鐐轰竴鐧捐惉浠ュ暉鐢ㄨ┎鍔熻兘銆�</string>
+    <string name="qhybrid_buttons_overwrite_success">鎸夐垥琚钃�</string>
+    <string name="qhybrid_buttons_overwrite_error">瑕嗗鎸夐垥鏅傚嚭閷�</string>
+    <string name="qhybrid_offset_timezone">鍋忕Щ鏅傚崁</string>
+    <string name="pref_title_lower_button_short_press_action">涓嬫柟鎸夐垥鐭寜鍕曚綔</string>
+    <string name="pref_title_upper_button_function_short">涓婃寜閳曠煭</string>
+    <string name="prefs_button_single_press_action_selection_title">浜嬩欢 1 琛屽嫊</string>
+    <string name="prefs_button_double_press_action_selection_title">浜嬩欢 2 琛屽嫊</string>
+    <string name="prefs_button_variable_actions">瑭崇窗鐨勬寜閳曟寜澹撹ō瀹�</string>
+    <string name="pref_check_permission_status_summary">鍗充娇閫欎簺娆婇檺鍙兘涓嶆渻绔嬪嵆闇�瑕侊紝涔熻妾㈡煡涓﹁珛姹傜己灏戠殑娆婇檺銆傚彧鏈夊湪鎮ㄧ殑瑷倷纰哄涓嶆敮鎸侀�欎簺鍔熻兘鏅傦紝鎵嶇鐢ㄦ閬搁爡銆備笉鎺堜簣娆婇檺鍙兘鏈冨皫鑷村晱椤岋紒</string>
+    <string name="descentDistance">涓嬪潯璺濋洟</string>
+    <string name="flatDistance">骞冲湴璺濋洟</string>
+    <string name="maxAltitude">鏈�澶�</string>
+    <string name="minAltitude">鏈�浣�</string>
+    <string name="averageAltitude">骞冲潎</string>
+    <string name="steps">姝ラ</string>
+    <string name="caloriesBurnt">鍗¤矾閲�</string>
+    <string name="maxSpeed">鏈�澶�</string>
+    <string name="minSpeed">鏈�浣�</string>
+    <string name="maxPace">鏈�蹇殑姝ヤ紣</string>
+    <string name="totalStride">绺芥骞�</string>
+    <string name="maxHR">鏈�澶у績鐜�</string>
+    <string name="minHR">鏈�灏忓績鐜�</string>
+    <string name="averageKMPaceSeconds">姝ヤ紣</string>
+    <string name="hrZoneNa">涓嶉仼鐢�</string>
+    <string name="hrZoneWarmUp">鐔辫韩</string>
+    <string name="swolfIndex">SWOLF</string>
+    <string name="swolfAvg">骞冲潎SWOLF</string>
+    <string name="swolfMax">鏈�澶wolf</string>
+    <string name="ascentSeconds">涓婂崌</string>
+    <string name="groundContactTimeAvg">骞冲潎鎺ュ湴鏅傞枔</string>
+    <string name="impactAvg">骞冲潎褰遍熆</string>
+    <string name="impactMax">鏈�澶у奖闊�</string>
+    <string name="swingAngleAvg">骞冲潎鎻】瑙掑害</string>
+    <string name="foreFootLandings">鍓嶈叧钁楀湴</string>
+    <string name="midFootLandings">涓冻钁楀湴</string>
+    <string name="backFootLandings">寰岃叧钁楅櫢</string>
+    <string name="calories_unit">鍗冨崱</string>
+    <string name="seconds_km">绉�/鍏噷</string>
+    <string name="seconds_m">绉�/绫�</string>
+    <string name="minutes_km">鍒嗛悩/鍏噷</string>
+    <string name="bpm">姣忓垎閻樼瘈鎷�</string>
+    <string name="degrees">搴�</string>
+    <string name="activity_detail_share_gps_label">鍒嗕韩 GPS 璺緫</string>
+    <string name="activity_detail_share_raw_details">鍒嗕韩鍘熷绱扮瘈</string>
+    <string name="gps_track">GPS 璺緫</string>
+    <string name="dev_tools">闁嬬櫦宸ュ叿</string>
+    <string name="addto_filter">娣诲姞鍒扮閬稿櫒</string>
+    <string name="activity_summaries_statistics">绲辫▓</string>
+    <string name="sports_activity_confirm_delete_title">鍒櫎 %d 鍊嬫椿鍕�</string>
+    <string name="sports_activity_confirm_delete_description">鎮ㄧ⒑瀹氳鍒櫎 %d 闋呮椿鍕曞棊锛�</string>
+    <string name="backstroke">浠版吵</string>
+    <string name="medley">娣峰悎鏇�</string>
+    <string name="devicetype_nut_mini">鍫呮灉杩蜂綘</string>
+    <string name="qhybrid_calibration_align_hint">浣跨敤涓嬮潰鐨勬寜閳曞皣閻橀尪鎸囬嚌灏嶉綂鍒�12:00銆�</string>
+    <string name="prefs_autolight">鑷嫊鐕堝厜</string>
+    <string name="prefs_key_vibration">闂滈嵉鎸嫊</string>
+    <string name="watchface_setting_title_wrist_flick">鎵嬭厱杓曞綀</string>
+    <string name="watchface_setting_desc_wrist_flick">(瀹屽叏绂佺敤锛屽暉鐢ㄧ浉灏嶉亱鍕曚甫灏囨墍鏈夊�艰ō缃偤0)</string>
+    <string name="watchface_setting_wrist_flick_move_relative">鐩稿皪鏂兼檪闁撶Щ鍕曟墜</string>
+    <string name="watchface_setting_wrist_flick_hour">鏅傞嚌 (-360 鍒� 360)</string>
+    <string name="devicetype_galaxybuds_pro">Galaxy Buds Pro</string>
+    <string name="prefs_ambient_sound_during_call_title">閫氳┍鏈熼枔鐨勭挵澧冭伈闊�</string>
+    <string name="prefs_ambient_sound_during_call_summary">鍦ㄩ�氳┍涓竻妤氬湴鑱藉埌鑷繁鐨勮伈闊�</string>
+    <string name="prefs_ambient_mode">鐠板妯″紡</string>
+    <string name="prefs_ambient_settings_title">鐠板鑱查煶閬搁爡</string>
+    <string name="prefs_active_noise_cancelling">涓诲嫊鍣煶娑堥櫎</string>
+    <string name="prefs_active_noise_cancelling_light">杓曢噺涓诲嫊鍣煶鍙栨秷</string>
+    <string name="sony_anc_optimizer_status_starting">闁嬪鈥�</string>
+    <string name="sony_anc_optimizer_status_not_running">鏈亱琛�</string>
+    <string name="sony_anc_optimizer_status_atmospheric_pressure">娓噺澶ф埃澹撳姏鈥�</string>
+    <string name="sony_anc_optimizer_status_analyzing">鍒嗘瀽涓��</string>
+    <string name="sony_touch_sensor">瑙告帶鎰熸噳鍣ㄦ帶鍒�</string>
+    <string name="pref_wide_area_tap_summary">杈ㄨ瓨闈㈤牥鑸囪�虫湹涔嬮枔鐨勮紩鎷�</string>
+    <string name="pref_wide_area_tap_title">寤e煙鎺ヨЦ</string>
+    <string name="pref_adaptive_volume_control_summary">鐣剁挵澧冭伈闊冲ぇ鏅傝嚜鍕曞寮烽煶閲�</string>
+    <string name="pref_adaptive_noise_cancelling_title">鑷仼鎳変富鍕曞櫔闊虫帶鍒�</string>
+    <string name="pref_adaptive_noise_cancelling_summary">鏍规摎鐠板闊抽噺鑷嫊瑾挎暣 ANC 鐨勫挤搴�</string>
+    <string name="sony_button_mode_right">鎸夐垥妯″紡锛堝彸锛�</string>
+    <string name="soundcore_button_brightness_high">楂�</string>
+    <string name="soundcore_ldac_mode_title">LDAC 妯″紡</string>
+    <string name="soundcore_ldac_mode_summary">鍟熺敤 LDAC 灏囨笡灏戦浕姹犲=鍛戒甫鍙兘灏庤嚧閫f帴涓嶇┅瀹�</string>
+    <string name="soundcore_adaptive_direction_title">鑷仼鎳夋柟鍚�</string>
+    <string name="soundcore_equalizer_preset_signature">soundcore 缃插悕</string>
+    <string name="soundcore_equalizer_band1">绗竴绲�</string>
+    <string name="pref_media_previous">涓婁竴鏇�</string>
+    <string name="pref_media_volumeup">闊抽噺澧炲姞</string>
+    <string name="pref_media_forward">璺抽亷</string>
+    <string name="huawei_alarm_event_description">璜嬪嬁鍕鹃伕鏅鸿兘鍠氶啋閬搁爡</string>
+    <string name="huawei_trusleep_title">HUAWEI TruSleep 鈩�</string>
+    <string name="huawei_trusleep_summary_light">鏀瑰杽鐨勭潯鐪犵洠娓�</string>
+    <string name="huawei_trusleep_warning">璀﹀憡锛氬暉鐢ㄦ閬搁爡灏囦娇鎵�鏈夌潯鐪犻’绀虹偤杓曞害鐫$湢鍦� Gadgetbridge 涓紒濡傛灉鎮ㄦ帴鍙楁姊濅欢锛岃珛榛炴搳閫欒!銆�</string>
+    <string name="prefs_activity_recognition">娲诲嫊璀樺垾瑷疆</string>
+    <string name="pref_activity_recognize_walking">璀樺垾琛岃蛋</string>
+    <string name="pref_activity_recognition_mode_none">鐒�</string>
+    <string name="pref_activity_recognition_mode_auto">鑷嫊</string>
+    <string name="pref_continuous_skin_temperature_measurement_title">鎸佺簩鐨啔婧害娓噺</string>
+    <string name="menuitem_menu">鑿滃柈</string>
+    <string name="pref_title_debug">闄ら尟璜嬫眰</string>
+    <string name="devicetype_asteroidos">灏忚鏄熶綔妤郴绲�</string>
+    <string name="devicetype_soflow_s06">SoFlow SO6</string>
+    <string name="pref_title_lock_unlock">閹�</string>
+    <string name="wifi_hotspot">Wi-Fi 鐔遍粸</string>
+    <string name="wifi_hotspot_configuration">Wi-Fi 鐔遍粸閰嶇疆</string>
+    <string name="wifi_hotspot_status">Wi-Fi 鐔遍粸鐙�鎱�</string>
+    <string name="ftp_server_status">FTP 浼烘湇鍣ㄧ媭鎱�</string>
+    <string name="loyalty_cards_sync_groups">鍚屾鐨勭兢绲�</string>
+    <string name="loyalty_cards">蹇犺獱鍗�</string>
+    <string name="loyalty_cards_syncing">姝e湪灏� %d 寮靛繝瑾犲崱鍚屾鍒拌ō鍌�</string>
+    <string name="withings_calibration_text_hours">璜嬩娇鐢ㄤ笅闈㈢殑鍒诲害鐩ゅ皣鏅傞嚌灏嶆簴12榛炪��</string>
+    <string name="purple">绱壊</string>
+    <string name="white">鐧借壊</string>
+    <string name="prefs_wena3_title_activity">娲诲嫊瑷畾</string>
+    <string name="prefs_wena3_hint_background_sync">鍏佽ū Wena 瀹氭湡璜嬫眰 Gadgetbridge 寰炶ō鍌欎笅杓夋椿鍕曟暩鎿�</string>
+    <string name="prefs_wena3_item_large_font">杓冨ぇ瀛楀瀷</string>
+    <string name="prefs_wena3_hint_large_font">澧炲姞鏃ユ泦銆侀�氱煡绛夌殑瀛楅珨澶у皬銆�</string>
+    <string name="prefs_wena3_hint_rich_design">鍦ㄤ富灞忓箷鍦栨鍛ㄥ湇娣诲姞鍦撹鐭╁舰</string>
+    <string name="prefs_wena3_title_alarm">璀﹀牨瑷畾</string>
+    <string name="prefs_wena3_vibration_strength_item_strong">寮风儓</string>
+    <string name="prefs_wena3_auto_power_off_hint">瑭茶ō鍌欏皣鏍规摎鎸囧畾鐨勬檪闁撹〃鑷嫊闁嬮棞姗�</string>
+    <string name="pref_vitality_score_title">娲诲姏鍒嗘暩</string>
+    <string name="pref_vitality_score_daily_title">姣忔棩閫插睍</string>
+    <string name="pref_vitality_score_daily_summary">鐣舵偍閬斿埌鐣跺ぉ鏈�澶х敓鍛藉�奸粸鏁告檪锛岀嵅寰楅�氱煡</string>
+    <string name="device_state_waiting_scan">绛夊緟瑷倷鎺冩弿</string>
+    <string name="auto_reconnect_ble_scan_title">閫氶亷 BLE 鎺冩弿閲嶆柊閫f帴</string>
+    <string name="devicetype_scannable">鍙巸鎻忚ō鍌�</string>
+    <string name="devicesetting_scannable_minimum_unseen">鏈�灏忔湭瑕嬫檪闁擄紙绉掞級</string>
+    <string name="devicesetting_scannable_rssi_summary">妾㈡脯鐨勬渶灏廟SSI闁惧��</string>
+    <string name="pref_dashboard_cards_title">鍦ㄥ崱鐗囦笂椤ず灏忓伐鍏�</string>
+    <string name="pref_dashboard_widget_double_size_title">闆欏�嶅ぇ灏�</string>
+    <string name="alarm_slot_reset">璀﹀牨鏅傛宸茶ō缃偤榛樿獚</string>
+    <string name="label_distance_trip_mph">琛岀▼: %.1f 鑻遍噷</string>
+    <string name="devicetype_garmin_venu_2">Garmin Venu 2</string>
+    <string name="preferences_miband_1_2_settings" tools:ignore="TypographyFractions">Mi Band 1/2 瑷畾</string>
+    <string name="reminder_message">瑷婃伅</string>
+    <string name="reminder_delete_confirm_description">鎮ㄧ⒑瀹氳鍒櫎閫欏�嬫彁閱掑棊锛�</string>
+    <string name="pref_screen_vibration_profile">鎸嫊杓粨</string>
+    <string name="title_activity_watch9_pairing">瑙�鐪�9灏�</string>
+    <string name="error_no_bluetooth_connect">蹇呴爤鎺堜簣涓﹀暉鐢ㄨ棈鐗欓�f帴瑷晱锛屼互渚挎巸鎻忔甯搁亱浣�</string>
+    <string name="devicetype_amazfit_trex_pro">Amazfit T-Rex Pro</string>
+    <string name="wifi_hotspot_summary">鎺у埗鎵嬮尪涓婄殑 Wi-Fi 鐔遍粸</string>
+    <string name="prefs_wena3_home_icon_name_wena_pay">Wena Pay</string>
+    <string name="pref_vitality_score_7_day_summary">鐣舵偍鐨勬椿鍔涘垎鏁稿湪閬庡幓7澶╁収閬斿埌30銆�60鎴�100鏅傦紝鐛插彇閫氱煡</string>
+    <string name="open_fw_installer_connect_maximum_one_device">璜嬪儏閫f帴涓�鍊嬫偍鎯宠鐧奸�佹獢妗堢殑瑷倷銆�</string>
+    <string name="pairing_unable_to_pair_with">鐒℃硶鑸� %1$s (%2$s) 閰嶅皪</string>
+    <string name="pairing_in_progress">姝e湪閫茶缍佸畾锛�%1$s (%2$s)</string>
+    <string name="horizontal">姘村钩</string>
+    <string name="vertical">鍨傜洿</string>
+    <string name="busy_task_fetch_stress_data">鐛插彇澹撳姏鏁告摎</string>
+    <string name="prefs_wearmode">绌挎埓妯″紡</string>
+    <string name="vibration_profile_ring">鎴掓寚</string>
+    <string name="pref_screen_notification_profile_anti_loss">闃蹭笩澶辫鍛�</string>
+    <string name="pref_screen_notification_profile_todo_list">寰呰睛浜嬮爡娓呭柈</string>
+    <string name="pref_screen_notification_profile_find_device">灏嬫壘瑷倷</string>
+    <string name="pref_screen_vibration_patterns_title">鎸嫊妯″紡</string>
+    <string name="heartrate_bpm_145">145 鎷嶆瘡鍒嗛悩</string>
+    <string name="heartrate_bpm_195">195 鎷嶆瘡鍒嗛悩</string>
+    <string name="heartrate_bpm_205">205 鎷嶆瘡鍒嗛悩</string>
+    <string name="spo2_perc_80">0.8</string>
+    <string name="reminder_every_month">姣忓�嬫湀</string>
+    <string name="contact_delete_confirm_title">鍒櫎鑱怠浜�</string>
+    <string name="title_activity_world_clock_details">涓栫晫鏅傞悩瑭虫儏</string>
+    <string name="controlcenter_start_activitymonitor">鎮ㄧ殑娲诲嫊</string>
+    <string name="devicestatus_disconnected">瑷倷宸叉柗闁嬮�f帴锛�</string>
+    <string name="updatefirmwareoperation_updateproblem_free_space">瑁濈疆娌掓湁瓒冲鐨勭┖闁�</string>
+    <string name="pref_summary_third_party_app_device_settings">鍏佽ū鍏朵粬宸插畨瑁濈殑绗笁鏂规噳鐢ㄩ�氶亷鎰忓湒瑷疆瑷倷瑷疆銆�</string>
+    <string name="mi2_prefs_button_press_count">鎸夐垥鎸夊娆℃暩</string>
+    <string name="prefs_device_name">瑷倷鍚嶇ū</string>
+    <string name="mi2_prefs_heart_rate_monitoring_alerts_summary">閰嶇疆蹇冪巼鐩f脯鍜岃鍫遍柧鍊�</string>
+    <string name="prefs_shortcut_cards">蹇嵎鍗$墖</string>
+    <string name="lefun_prefs_interface_language_title">浠嬮潰瑾炶█</string>
+    <string name="english_in">鑻辫獮锛堝嵃搴︼級</string>
+    <string name="english_us">鑻辨枃锛堢編鍦嬶級</string>
+    <string name="spanish_mx">瑗跨彮鐗欒獮锛堝ⅷ瑗垮摜锛�</string>
+    <string name="georgian">鍠不浜炶獮</string>
+    <string name="scandinavian">鏂牚鍦伴偅缍簽</string>
+    <string name="french_ca">娉曡獮锛堝姞鎷垮ぇ锛�</string>
+    <string name="activity_prefs_target_weight_kg">鐩楂旈噸锛堝叕鏂わ級</string>
+    <string name="dnd_all_day">鏁村ぉ</string>
+    <string name="discovery_bonding_failed_immediately">鑸� %1$s 鐨勯�f帴绔嬪嵆澶辨晽銆�</string>
+    <string name="controlcenter_start_activity_tracks">鎮ㄧ殑娲诲嫊杩借工</string>
+    <string name="activity_type_tae_bo">娉版嫵鎼忔搳</string>
+    <string name="activity_type_auto_racing">姹借粖璩借粖</string>
+    <string name="activity_type_table_tennis">涔掍箵鐞�</string>
+    <string name="activity_type_biking">楱庤嚜琛岃粖</string>
+    <string name="kind_gpx_route">GPX 璺窔</string>
+    <string name="devicetype_amazfit_gtr2e">Amazfit GTR 2e</string>
+    <string name="menuitem_flashlight">鎵嬮浕绛�</string>
+    <string name="qhybrid_overwrite_buttons">瑕嗗鎸夐垥</string>
+    <string name="sony_equalizer_band_400">400</string>
+    <string name="prefs_wena3_item_weather_statusbar">鐙�鎱嬫瑒涓殑澶╂埃</string>
+    <string name="prefs_wena3_button_action_item_long">闀锋寜</string>
+    <string name="pref_title_notification_times_enabled">閫氱煡鏅傞枔</string>
+    <string name="pref_summary_notification_times_enabled">鍍呭湪鐗瑰畾鏅傞枔鍏х櫦閫侀�氱煡</string>
+    <string name="pref_title_notification_media_ignores_application_list">濯掗珨閫氱煡蹇界暐鎳夌敤绋嬪紡鍒楄〃</string>
+    <string name="pref_title_banglejs_text_bitmap">鏂囧瓧浣滅偤浣嶅湒</string>
+    <string name="pref_summary_banglejs_text_bitmap">濡傛灉涓�鍊嬭鐒℃硶浣跨敤鎵嬮尪鐨勫瓧楂旈’绀猴紝鍓囧湪Gadgetbridge涓皣鍏舵覆鏌撶偤浣嶅湒涓﹀湪鎵嬮尪涓婇’绀鸿┎浣嶅湒</string>
+    <string name="pref_summary_banglejs_txt_bitmap_size">鐢ㄦ柤浣嶅湒鏂囧瓧娓叉煋鐨勫ぇ灏�</string>
+    <string name="pref_title_banglejs_phone_gps_enbale">浣跨敤鎵嬫 GPS 鏁告摎</string>
+    <string name="pref_summary_banglejs_phone_gps_network_only">鍍呬娇鐢ㄧ恫璺彁渚涜�呬締纰哄畾浣嶇疆銆傞�欐ǎ鍙互娓涘皯鍔熻�楋紝浣嗘渻浠ユ簴纰烘�х偤浠e児銆傞渶瑕侀噸鏂伴�f帴銆�</string>
+    <string name="pref_title_pebble_sync_health">鍚屾 Pebble 鍋ュ悍</string>
+    <string name="pref_title_casio_alert_sms">鐭俊閫氱煡鎻愰啋</string>
+    <string name="pref_title_casio_alert_other">鍏朵粬閫氱煡鐨勮鍫�</string>
+    <string name="pref_summary_casio_alert_other">灏嶆柤鍏朵粬椤炲垾鐨勯�氱煡閫茶璀﹀牨锛堟尟鍕�/铚傞炒锛�</string>
+    <string name="pref_header_navigation">灏庤埅</string>
+    <string name="toast_enable_networklocationprovider">璜嬪暉鐢ㄧ恫绲′綅缃�</string>
+    <string name="toast_aqurired_networklocation">浣嶇疆宸茬嵅寰�</string>
+    <string name="pref_title_pebble_gatt_clientonly">鍍呴檺 GATT 瀹㈡埗绔�</string>
+    <string name="pref_title_device_internet_access">鍏佽ū缍茬怠瑷晱</string>
+    <string name="title_activity_LenovoWatch_calibration">瑙�鐪� X Plus 鏍℃簴</string>
+    <string name="pref_sensors_bp_calibration_low">琛�澹撹垝寮靛锛堜綆锛�</string>
+    <string name="prefs_sensors_button_bp_calibration">鏍℃簴</string>
+    <string name="power_mode_normal">姝e父</string>
+    <string name="zetime_analog_mode_handsandsteps">鎵嬪拰姝�</string>
+    <string name="zetime_title_alarm_signaling">瑷疆璀﹀牨鐨勪俊铏熼鍨�</string>
+    <string name="zetime_signaling_none">闈滈粯</string>
+    <string name="zetime_signaling_vibrate">鎸佺簩鎸嫊</string>
+    <string name="clap_hands_to_wakeup_device_summary">鍐嶆鎷嶆墜灏囬棞闁夎灑骞�</string>
+    <string name="pixoo_power_saving_summary">鍦ㄩ亥鍏嬮ⅷ妾㈡脯鍒伴潨闊充竴娈垫檪闁撳緦锛岃灑骞曞皣闂滈枆</string>
+    <string name="prefs_hr_alarm_activity">閬嬪嫊閬庣▼涓殑蹇冪巼璀﹀牨</string>
+    <string name="pref_gps_mode_preset">GPS 妯″紡</string>
+    <string name="pref_gps_band">GPS 闋诲付</string>
+    <string name="pref_gps_combination">GPS 绲勫悎</string>
+    <string name="pref_vibrate_for_alert">鎸嫊鎻愰啋</string>
+    <string name="pref_agps_update_time">AGPS 鏇存柊鏅傞枔</string>
+    <string name="agps_status_pending">寰呰檿鐞�</string>
+    <string name="agps_status_current">鐣跺墠</string>
+    <string name="pref_camera_remote_summary">鍏佽ū鎵嬮尪瑙哥櫦鎵嬫鐨勭浉姗�</string>
+    <string name="pref_morning_updates_title">鏃╂櫒鏇存柊</string>
+    <string name="pref_morning_updates_summary">姣忓ぉ鏃╀笂椤ず鏇存柊</string>
+    <string name="pref_workout_send_gps_title">閬嬪嫊鏅傜櫦閫丟PS</string>
+    <string name="pref_workout_detection_title">閬嬪嫊妾㈡脯</string>
+    <string name="pref_workout_detection_summary">鑷嫊鍋垫脯閬嬪嫊</string>
+    <string name="pref_workout_detection_categories_summary">鑷嫊妾㈡脯鐨勯亱鍕曢鍒�</string>
+    <string name="pref_workout_detection_alert_title">璀﹀牨</string>
+    <string name="pref_workout_detection_ask_first">鍏堝晱鎴�</string>
+    <string name="pref_workout_detection_ask_first_summary">鐣舵娓埌姝ら崨閸婃檪锛岄渶瑕佸湪鐩f帶涓⒑瑾�</string>
+    <string name="pref_workout_detection_time">妾㈡脯鍓嶇殑娲昏簫鍒嗛悩鏁�</string>
+    <string name="pref_sleep_mode_title">鐫$湢妯″紡</string>
+    <string name="pref_sleep_mode_sleep_screen_summary">鍦ㄧ潯鐪犳ā寮忎笅鍠氶啋铻㈠箷鏅傞’绀虹潯鐪犵暙闈紝浠ユ笡灏戝共鎿�</string>
+    <string name="_unknown_">(鏈煡)</string>
+    <string name="menuitem_mutephone">闈滈煶闆昏┍</string>
+    <string name="appwidget_text">鍡″棥鑱�</string>
+    <string name="error_retrieving_devices_database">寰炶硣鏂欏韩妾㈢储瑷倷鏅傚嚭閷�</string>
+    <string name="qhybrid_calibration_counterclockwise">閫嗘檪閲�</string>
+    <string name="prefs_sleep_time_label">瀹氱京鐫$湢鏅傞枔</string>
+    <string name="prefs_double_tap_edge">闆欐搳閭婄罚</string>
+    <string name="sony_sound_position_rear_right">寰屽彸</string>
+    <string name="intent_api_allow_debug_commands_title">鍏佽ū瑾胯│鍛戒护</string>
+    <string name="loyalty_cards_sync_starred">鍍呭悓姝ュ凡鍔犳槦妯欑殑鍗$墖</string>
+    <string name="devicesetting_scannable_rssi">鏈�灏廟SSI闁惧��</string>
+    <string name="prefs_password">瀵嗙⒓</string>
+    <string name="portuguese_br">钁¤悇鐗欒獮锛堝反瑗匡級</string>
+    <string name="candidate_item_device_image">瑷倷鍦栧儚</string>
+    <string name="pref_title_ping_tone">鍡惰伈</string>
+    <string name="audio_codec">闊抽牷绶ㄧ⒓</string>
+    <string name="qhybrid_title_background_image">鑳屾櫙鍦栫墖</string>
+    <string name="sony_ambient_sound_focus_voice">灏堟敞鏂艰伈闊�</string>
+    <string name="about_description_banglejs_main">鍩烘柤 Gadgetbridge 灏堟鐨� Bangle.js Android 浼翠径鎳夌敤绋嬪紡锛屼甫澧炲姞浜嗙恫闅涚恫璺瓨鍙栥��
+\n
+\n鐢辨柤 Google Play 鍟嗗簵鐨勬斂绛栵紝鎴戝�戠劇娉曞湪鎳夌敤绋嬪紡涓彁渚涙崘璐堥�g祼锛屼絾濡傛灉鎮ㄥ枩姝¢�欏�嬫噳鐢ㄧ▼寮忥紝璜嬭�冩叜閫氶亷涓嬮潰鐨� Gadgetbridge 棣栭爜閫茶鎹愯磮銆�</string>
+    <string name="about_description_banglejs_nightly">鍩烘柤 Gadgetbridge 闋呯洰鐨� Bangle.js Android 浼翠径鎳夌敤绋嬪紡锛屼甫鏂板浜嗕簰鑱恫瑷晱鍔熻兘銆�
+\n
+\n鐢辨柤 Google Play 鍟嗗簵鐨勬斂绛栵紝鎴戝�戜笉鑳藉湪鎳夌敤绋嬪紡涓斁缃崘娆鹃�g祼锛屼絾濡傛灉鎮ㄥ枩姝¢�欏�嬫噳鐢ㄧ▼寮忥紝璜嬭�冩叜閫氶亷涓嬮潰鐨� Gadgetbridge 涓婚爜閫茶鎹愭銆�</string>
+    <string name="gadgetbridge_running_banglejs_nightly">鍏堣鐗� Bangle.js 鍩疯涓�</string>
+    <string name="about_activity_title_main_nopebble">闂滄柤 Gadgetbridge Nightly 鐒� Pebble</string>
+    <string name="gadgetbridge_running_main_nopebble">鍏堣鐗� NoPebble GB 鍩疯涓�</string>
+    <string name="appmanager_app_start">闁嬪</string>
+    <string name="appmanager_app_download">涓嬭級鍒板揩鍙�</string>
+    <string name="appmanager_app_share">鍒嗕韩</string>
+    <string name="appmanager_item_outdated">(閬庢檪鐨�)</string>
+    <string name="appmanager_download_started">鎳夌敤绋嬪紡涓嬭級宸查枊濮�</string>
+    <string name="appmanager_downloaded_to_cache">宸蹭笅杓� %s 鍒板揩鍙�</string>
+    <string name="fw_upgrade_notice_amazfitbip">鎮ㄥ嵆灏囧湪鎮ㄧ殑 Amazfit Bip 涓婂畨瑁� %s 闊岄珨銆�
+\n
+\n璜嬬⒑淇濆厛瀹夎 .fw 妾旀锛岀劧寰屾槸 .res 妾旀锛屾渶寰屾槸 .gps 妾旀銆傚畨瑁� .fw 妾旀寰岋紝鎮ㄧ殑鎵嬮尪灏囬噸鏂板暉鍕曘��
+\n
+\n娉ㄦ剰锛氬鏋滈�欎簺妾旀鑸囦箣鍓嶅畨瑁濈殑妾旀瀹屽叏鐩稿悓锛屽墖涓嶉渶瑕佸畨瑁� .res 鍜� .gps銆�
+\n
+\n璜嬭嚜琛屾壙鎿旈ⅷ闅�茶鎿嶄綔锛�</string>
+    <string name="fw_upgrade_notice_amazfitbip_lite">鎮ㄥ嵆灏囧湪鎮ㄧ殑 Amazfit Bip Lite 涓婂畨瑁� %s 闊岄珨銆�
+\n
+\n璜嬬⒑淇濆畨瑁� .fw 妾旀锛岀劧寰屽啀瀹夎 .res 妾旀銆傚畨瑁� .fw 妾旀寰岋紝鎮ㄧ殑鎵嬮尪灏囬噸鏂板暉鍕曘��
+\n
+\n娉ㄦ剰锛氬鏋� .res 妾旀鑸囦箣鍓嶅畨瑁濈殑瀹屽叏鐩稿悓锛屽墖涓嶅繀鍐嶆瀹夎銆�
+\n
+\n璜嬭嚜琛屾壙鎿旈ⅷ闅紒</string>
+    <string name="fw_upgrade_notice_amazfitbip3pro">鎮ㄥ嵆灏囧湪鎮ㄧ殑 Amazfit Bip 3 Pro 涓婂畨瑁� %s 闊岄珨銆�
+\n
+\n璜嬬⒑淇濆厛瀹夎 .fw 妾旀锛岀劧寰屽啀瀹夎 .res 妾旀銆傚畨瑁� .fw 妾旀寰岋紝鎮ㄧ殑鎵嬮尪灏囨渻閲嶆柊鍟熷嫊銆�
+\n
+\n娉ㄦ剰锛氬鏋� .res 妾旀鑸囦箣鍓嶅畨瑁濈殑瀹屽叏鐩稿悓锛屽墖鐒¢渶瀹夎 .res銆�
+\n
+\n璜嬭嚜琛屾壙鎿旈ⅷ闅�茶鎿嶄綔锛�</string>
+    <string name="fw_upgrade_notice_amazfitcor2">鎮ㄥ嵆灏囧湪鎮ㄧ殑 Amazfit Cor 2 涓婂畨瑁� %s 闊岄珨銆�
+\n
+\n璜嬬⒑淇濆厛瀹夎 .fw 妾旀锛岀劧寰屽啀瀹夎 .res 妾旀銆傚畨瑁� .fw 妾旀寰岋紝鎮ㄧ殑鎵嬬挵灏囨渻閲嶅暉銆�
+\n
+\n娉ㄦ剰锛氬鏋� .res 妾旀鑸囦箣鍓嶅畨瑁濈殑瀹屽叏鐩稿悓锛屽墖涓嶉渶瑕佸啀娆″畨瑁濄��
+\n
+\n璜嬭嚜琛屾壙鎿旈ⅷ闅紒
+\n
+\n瀹屽叏鏈稉娓│锛岃嫢鎮ㄧ殑瑷倷鍚嶇ū鐐� \"Amazfit Band 2\"锛屽彲鑳介渶瑕侀杻瀛� BEATS_W 闊岄珨銆�</string>
+    <string name="fw_upgrade_notice_amazfitcor">鎮ㄥ嵆灏囧湪鎮ㄧ殑 Amazfit Cor 涓婂畨瑁� %s 闊岄珨銆�
+\n
+\n璜嬬⒑淇濆厛瀹夎 .fw 妾旀锛岀劧寰屽啀瀹夎 .res 妾旀銆傚畨瑁� .fw 妾旀寰岋紝鎮ㄧ殑鎵嬬挵灏囨渻閲嶅暉銆�
+\n
+\n娉ㄦ剰锛氬鏋� .res 妾旀鑸囦箣鍓嶅畨瑁濈殑瀹屽叏鐩稿悓锛屽墖鐒¢渶瀹夎 .res銆�
+\n
+\n璜嬭嚜璨犻ⅷ闅�茶锛�</string>
+    <string name="fw_upgrade_notice_amazfitgtr">鎮ㄥ嵆灏囧湪鎮ㄧ殑 Amazfit GTR 涓婂畨瑁� %s 闊岄珨銆�
+\n
+\n璜嬬⒑淇濆厛瀹夎 .fw 妾旀锛岀劧寰屾槸 .res 妾旀锛屾渶寰屾槸 .gps 妾旀銆傚畨瑁� .fw 妾旀寰岋紝鎮ㄧ殑鎵嬮尪灏囬噸鏂板暉鍕曘��
+\n
+\n娉ㄦ剰锛氬鏋滈�欎簺妾旀鑸囦箣鍓嶅畨瑁濈殑妾旀瀹屽叏鐩稿悓锛屽墖涓嶅繀瀹夎 .res 鍜� .gps 妾旀銆�
+\n
+\n璜嬭嚜琛屾壙鎿旈ⅷ闅�茶鎿嶄綔锛�</string>
+    <string name="fw_upgrade_notice_amazfitgts">鎮ㄥ嵆灏囧湪鎮ㄧ殑 Amazfit GTS 涓婂畨瑁� %s 鍥轰欢銆�
+\n
+\n璜嬬⒑淇濆厛瀹夎 .fw 鏂囦欢锛岀劧寰屾槸 .res 鏂囦欢锛屾渶寰屾槸 .gps 鏂囦欢銆傚畨瑁� .fw 鏂囦欢寰岋紝鎮ㄧ殑鎵嬮尪灏囨渻閲嶅暉銆�
+\n
+\n娉ㄦ剰锛氬鏋� .res 鍜� .gps 鏂囦欢鑸囦箣鍓嶅畨瑁濈殑鏂囦欢瀹屽叏鐩稿悓锛屽墖涓嶅繀鍐嶆瀹夎銆�
+\n
+\n璜嬭嚜琛屾壙鎿旈ⅷ闅紒</string>
+    <string name="fw_upgrade_notice_amazfit_trex">鎮ㄥ嵆灏囧湪鎮ㄧ殑 Amazfit T-Rex 涓婂畨瑁� %s 闊岄珨銆�
+\n
+\n璜嬬⒑淇濆厛瀹夎 .fw 妾旀锛岀劧寰屾槸 .res 妾旀锛屾渶寰屾槸 .gps 妾旀銆傚畨瑁� .fw 妾旀寰岋紝鎮ㄧ殑鎵嬮尪灏囨渻閲嶆柊鍟熷嫊銆�
+\n
+\n娉ㄦ剰锛氬鏋� .res 鍜� .gps 妾旀鑸囦箣鍓嶅畨瑁濈殑妾旀瀹屽叏鐩稿悓锛屽墖鐒¢渶瀹夎閫欎簺妾旀銆�
+\n
+\n璜嬭嚜琛屾壙鎿旈ⅷ闅紒</string>
+    <string name="fw_upgrade_notice_amazfitvergel">鎮ㄥ嵆灏囧湪鎮ㄧ殑 Amazfit Verge Lite 涓婂畨瑁� %s 闊岄珨銆�
+\n
+\n璜嬬⒑淇濆厛瀹夎 .fw 妾旀锛岀劧寰屾槸 .res 妾旀锛屾渶寰屾槸 .gps 妾旀銆傚畨瑁� .fw 妾旀寰岋紝鎮ㄧ殑鎵嬮尪灏囬噸鏂板暉鍕曘��
+\n
+\n娉ㄦ剰锛氬鏋滈�欎簺妾旀鑸囦箣鍓嶅畨瑁濈殑妾旀瀹屽叏鐩稿悓锛屽墖涓嶅繀瀹夎 .res 鍜� .gps銆�
+\n
+\n璜嬭嚜琛屾壙鎿旈ⅷ闅�茶鎿嶄綔锛�</string>
+    <string name="fw_upgrade_notice_miband5">鎮ㄥ嵆灏囧湪鎮ㄧ殑 Mi Band 5 涓婂畨瑁� %s 闊岄珨銆�
+\n
+\n璜嬬⒑淇濆厛瀹夎 .fw 妾旀锛岀劧寰屽啀瀹夎 .res 妾旀銆傚畨瑁� .fw 妾旀寰岋紝鎮ㄧ殑鎵嬬挵灏囬噸鏂板暉鍕曘��
+\n
+\n娉ㄦ剰锛氬鏋� .res 妾旀鑸囦箣鍓嶅畨瑁濈殑瀹屽叏鐩稿悓锛屽墖涓嶉渶瑕佸畨瑁� .res銆�
+\n
+\n璜嬭嚜琛屾壙鎿旈ⅷ闅�茶鎿嶄綔锛�</string>
+    <string name="fw_upgrade_notice_amazfit_gts4_mini">鎮ㄥ嵆灏囧湪鎮ㄧ殑 Amazfit GTS 4 Mini 涓婂畨瑁� %s 闊岄珨銆�
+\n
+\n瀹夎 .zip 妾旀寰岋紝鎮ㄧ殑鎵嬬挵灏囬噸鏂板暉鍕曘��
+\n
+\n璜嬭嚜琛屾壙鎿旈ⅷ闅�茶锛�</string>
+    <string name="fw_upgrade_notice_amazfit_gtr3">鎮ㄥ嵆灏囧湪鎮ㄧ殑 Amazfit GTR 3 涓婂畨瑁� %s 闊岄珨銆�
+\n
+\n瀹夎 .zip 妾旀寰岋紝鎮ㄧ殑鎵嬬挵灏囬噸鏂板暉鍕曘��
+\n
+\n璜嬭嚜琛屾壙鎿旈ⅷ闅�茶鎿嶄綔锛�</string>
+    <string name="fw_upgrade_notice_miband7">鎮ㄥ嵆灏囧湪鎮ㄧ殑灏忕背鏅鸿兘鎵嬬挵 7 涓婂畨瑁� %s 闊岄珨銆�
+\n
+\n瀹夎 .zip 妾旀寰岋紝鎮ㄧ殑鎵嬬挵灏囨渻閲嶅暉銆�
+\n
+\n璜嬭嚜琛屾壙鎿旈ⅷ闅紒</string>
+    <string name="fw_upgrade_notice_amazfit_gts3">鎮ㄥ嵆灏囧湪鎮ㄧ殑 Amazfit GTS 3 涓婂畨瑁� %s 闊岄珨銆�
+\n
+\n瀹夎 .zip 妾旀寰岋紝鎮ㄧ殑鎵嬬挵灏囬噸鏂板暉鍕曘��
+\n
+\n璜嬭嚜琛屾壙鎿旈ⅷ闅辜绾岋紒</string>
+    <string name="fw_upgrade_notice_amazfit_gts4">鎮ㄥ嵆灏囧湪鎮ㄧ殑 Amazfit GTS 4 涓婂畨瑁� %s 闊岄珨銆�
+\n
+\n瀹夎 .zip 妾旀寰岋紝鎮ㄧ殑鎵嬬挵灏囬噸鏂板暉鍕曘��
+\n
+\n璜嬭嚜琛屾壙鎿旈ⅷ闅�茶鎿嶄綔锛�</string>
+    <string name="fw_upgrade_notice_amazfit_gtr3_pro">鎮ㄥ嵆灏囧湪鎮ㄧ殑 Amazfit GTR 3 Pro 涓婂畨瑁� %s 闊岄珨銆�
+\n
+\n瀹夎 .zip 妾旀寰岋紝鎮ㄧ殑瑷倷灏囨渻閲嶆柊鍟熷嫊銆�
+\n
+\n璜嬭嚜琛屾壙鎿旈ⅷ闅�茶鎿嶄綔锛�</string>
+    <string name="fw_upgrade_notice_amazfit_band7">鎮ㄥ嵆灏囧湪鎮ㄧ殑 Amazfit Band 7 涓婂畨瑁� %s 闊岄珨銆�
+\n
+\n瀹夎 .zip 妾旀寰岋紝鎮ㄧ殑鎵嬬挵灏囨渻閲嶆柊鍟熷嫊銆�
+\n
+\n璜嬭嚜琛屾壙鎿旈ⅷ闅�茶鎿嶄綔锛�</string>
+    <string name="open_fw_installer_info_text">鍥轰欢/琛ㄩ潰/鎳夌敤绋嬪紡/妾旀瀹夎绋嬪紡鍏佽ū鎮ㄥ皣鏀彺鐨勬獢妗堬紙鍥轰欢銆佽〃闈€�佹噳鐢ㄧ▼寮忋�丟PS銆佽硣婧愩�佸瓧鍨嬬瓑锛変笂鍌�/瀹夎鍒拌ō鍌欎笂銆傛洿澶氳硣瑷婅珛鍙冭缍熀锛歨ttps://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Firmware-Update</string>
+    <string name="open_fw_installer_warning_text">姝ゅ姛鑳芥湁鍙兘浣挎偍鐨勮ō鍌欒畩纾氥�備笉閬庯紝閫氶亷鍒锋锛岄�欏皪浠讳綍闁嬬櫦鑰呬締瑾緸鏈櫦鐢熼亷锛屼絾璜嬭浣忥紝鎮ㄦ槸鍦ㄨ嚜鎿旈ⅷ闅殑鎯呮硜涓嬮�茶閫欓爡鎿嶄綔銆�</string>
+    <string name="open_fw_installer_getting_files_title">鐛插彇鍥轰欢/鎳夌敤绋嬪紡妾旀</string>
+    <string name="open_fw_installer_getting_files_text">鐢辨柤鎴戝�戠劇娉曞垎鐧奸煂楂旀獢妗堬紝鎮ㄩ渶瑕佽嚜琛岀嵅鍙栭�欎簺妾旀銆傞�欐剰鍛宠憲鎮ㄩ渶瑕佸湪apk妾旀銆佺恫璺�佽珫澹囥�丄mazfitwatchfaces锛堥嚌灏峂iband/Amazfit瑷倷锛夌瓑鍦版柟鎼滃皨閫欎簺妾旀銆�</string>
+    <string name="open_fw_installer_ensure_device_connected">璜嬬⒑淇濊ō鍌� %s 宸查�f帴</string>
+    <string name="pref_header_connection">閫f帴</string>
+    <string name="pref_summary_mb_intents">濡傛灉鏌愪簺鎳夌敤绋嬪紡鐨勮ō鍌欏獟楂旀帶鍒剁劇娉曟甯搁亱浣滐紝璜嬪暉鐢ㄦ閬搁爡</string>
+    <string name="pref_summary_receive_calls_watch">鐩存帴鍦ㄦ墜閷朵笂鎾ユ墦鍜屾帴鑱介浕瑭�</string>
+    <string name="bluetooth_calls">钘嶇墮閫氳┍</string>
+    <string name="bluetooth_calls_settings">钘嶇墮閫氳┍瑷畾</string>
+    <string name="pref_pair_bluetooth_calls_help_title">濡備綍鎺ヨ伣钘嶇墮闆昏┍</string>
+    <string name="pref_pair_bluetooth_calls_help_1">1. 榛炴搳涓嬮潰鐨勬寜閳曢枊濮嬮厤灏嶉亷绋嬨��</string>
+    <string name="pref_pair_bluetooth_calls_help_3">3. 鍟熺敤涓嬮潰鐨勮棈鐗欓�氳┍瑷畾銆�</string>
+    <string name="pref_pair_bluetooth_calls_help_warning">璀﹀憡锛氬鏋滄偍鍟熺敤钘嶇墮閫氳┍鑰屾湭鑸囩浜屽�嬪渚嬮厤灏嶏紝閫氳┍閫氱煡鍙兘鐒℃硶姝e父宸ヤ綔銆�</string>
+    <string name="pref_summary_notifications_pebblemsg">鏀寔閫氶亷 PebbleKit 鍚� Pebble 鐧奸�侀�氱煡鐨勬噳鐢ㄧ▼寮忋��</string>
+    <string name="pref_title_notifications_generic_settings">Android 閫氱煡瑷畾</string>
+    <string name="pref_summary_notifications_ignore_low_priority">涓嶈灏囦綆鍎厛绱氬拰鏈�浣庡劒鍏堢礆鐨勯�氱煡鐧奸�佸埌鎵嬮尪</string>
+    <string name="pref_summary_notifications_ignore_work_profile">涓嶈灏囧伐浣滈厤缃枃浠朵腑鐨勬噳鐢ㄧ▼搴忛�氱煡鐧奸�佸埌鎵嬮尪</string>
+    <string name="pref_title_notification_prefer_long_text">鍋忓ソ闀烽�氱煡鏂囧瓧</string>
+    <string name="pref_summary_notification_prefer_long_text">濡傛灉鏈夌殑瑭憋紝灏囬暦閫氱煡鏂囨湰鐧奸�佸埌瑷倷</string>
+    <string name="pref_title_notification_cache_while_disconnected">瓒呭嚭绡勫湇鏅傚揩鍙�</string>
+    <string name="pref_summary_notification_cache_while_disconnected">鐣惰ō鍌欓噸鏂伴�f帴寰岋紝鐧奸�侀尟閬庣殑閫氱煡锛屽洜鐐哄畠鏇剧稉瓒呭嚭绡勫湇</string>
+    <string name="pref_summary_banglejs_phone_gps_enbale">浣跨敤鎵嬫鐨凣PS鏁告摎渚嗚钃嬫墜鐠拌ō鍌欑殑GPS鏁告摎</string>
+    <string name="pref_title_banglejs_txt_bitmap_size">鏂囨湰浣嶅湒澶у皬</string>
+    <string name="pref_title_banglejs_phone_gps_network_only">鍍呬娇鐢ㄧ恫绲′締纰哄畾浣嶇疆</string>
+    <string name="pref_title_banglejs_phone_gps_update_interval">GPS鏁告摎鏇存柊闁撻殧</string>
+    <string name="pref_blacklist_calendars">榛戝悕鍠棩鏇�</string>
+    <string name="pref_summary_banglejs_phone_gps_update_interval">GPS 浣嶇疆鏇存柊鐨勯枔闅旀檪闁擄紝浠ユ绉掔偤鍠綅</string>
+    <string name="pref_summary_banglejs_webview_url">濡傛灉鎮ㄦ兂瑕佽嚜瑷傛噳鐢ㄧ▼寮忚級鍏ュ櫒锛岃珛鍦ㄦ铏曡几鍏ユ偍鐨� https://鈥�/android.html URL銆傚惁鍓囷紝璜嬬暀绌轰互浣跨敤 https://banglejs.com/apps</string>
+    <string name="pref_canned_message">瑷婃伅</string>
+    <string name="pref_summary_canned_replies">浣跨敤闋愯ō娑堟伅寰炴墜閷跺洖瑕�</string>
+    <string name="pref_title_canned_reply_suffix">甯歌寰岀洞</string>
+    <string name="pref_title_canned_messages_dismisscall">閫氳┍鎷掓帴</string>
+    <string name="pref_title_canned_messages_set">瑷倷鏇存柊</string>
+    <string name="pref_header_intent_api">鎰忓湒 API</string>
+    <string name="pref_header_authentication">韬唤椹楄瓑</string>
+    <string name="pref_title_pebble_sync_misfit">鍚屾涓嶅悎</string>
+    <string name="pref_summary_enable_calendar_sync">灏囨棩鏇嗕簨浠剁櫦閫佸埌鏅傞枔绶�</string>
+    <string name="pref_title_custom_deviceicon">椤ず瑷倷鐗瑰畾鐨勯�氱煡鍦栨</string>
+    <string name="pref_summary_custom_deviceicon">鐣堕�f帴鏅傞’绀鸿ō鍌欑壒瀹氱殑 Android 閫氱煡鍦栨锛岃�屼笉鏄� Gadgetbridge 鍦栨</string>
+    <string name="pref_title_preview_message_in_title">鍦ㄦ椤屼腑椤ず娑堟伅鐨勯爯瑕�</string>
+    <string name="pref_summary_casio_alert_call">渚嗛浕鏅傛彁閱掞紙闇囧嫊/闊块埓锛�</string>
+    <string name="pref_title_casio_alert_email">闆诲瓙閮典欢閫氱煡璀﹀牨</string>
+    <string name="pref_summary_casio_alert_email">闆诲瓙閮典欢閫氱煡鏅傝鍫憋紙闇囧嫊/闊块埓锛�</string>
+    <string name="pref_title_autoremove_notifications">鑷嫊绉婚櫎宸查棞闁夌殑閫氱煡</string>
+    <string name="pref_summary_autoremove_notifications">鐣跺緸鎵嬫涓婅В闄ら�氱煡鏅傦紝閫氱煡鏈冭嚜鍕曞緸瑷倷涓Щ闄�</string>
+    <string name="pref_title_pebble_forceprotocol">寮峰埗閫氱煡鍗旇</string>
+    <string name="pref_summary_screen_on_on_notifications">鐣堕�氱煡鍒伴仈鏅傛墦闁嬫▊闅婄殑铻㈠箷</string>
+    <string name="pref_title_pebble_privacy_mode">闅辩妯″紡</string>
+    <string name="pref_summary_pebble_forceuntested">鍟熺敤鏈稉娓│鐨勫姛鑳姐�傜煡閬撲綘鍦ㄥ仛浠�楹硷紒</string>
+    <string name="pref_title_pebble_forcele">姘搁仩鍎厛閬告搰BLE</string>
+    <string name="pref_pebble_privacy_mode_off">姝e父閫氱煡</string>
+    <string name="pref_pebble_privacy_mode_content">灏囬�氱煡鏂囨湰绉昏嚦铻㈠箷澶�</string>
+    <string name="pref_pebble_privacy_mode_complete">鍍呴’绀洪�氱煡鍦栫ず</string>
+    <string name="pref_summary_pebble_forcele">灏嶆墍鏈� Pebble 浣跨敤瀵﹂鎬х殑 Pebble LE 鏀彺锛岃�屼笉鏄� BT 鍌崇当銆傞�欓渶瑕佸厛閰嶅皪鍒伴潪 LE锛岀劧寰屽啀閰嶅皪 Pebble LE銆�</string>
+    <string name="pref_summary_pebble_mtu_limit">濡傛灉鎮ㄧ殑 Pebble 2/Pebble LE 娌掓湁濡傞爯鏈熻埇閬嬩綔锛岃珛鍢楄│閫欏�嬭ō缃締闄愬埗 MTU锛堟湁鏁堢瘎鍦� 20鈥�512锛�</string>
+    <string name="pref_summary_pebble_gatt_clientonly">閫欏儏閬╃敤鏂� Pebble 2 涓︿笖鏄椹楁�х殑锛屽鏋滄偍閬囧埌閫f帴鍟忛锛岃珛鍢楄│閫欏��</string>
+    <string name="pref_title_pebble_mtu_limit">Pebble 2/LE GATT MTU 闄愬埗</string>
+    <string name="pref_summary_pebble_enable_applogs">灏囧皫鑷翠締鑷墜閷舵噳鐢ㄧ▼寮忕殑鏃ヨ獙琚獹adgetbridge瑷橀寗锛堥渶瑕侀噸鏂伴�f帴锛�</string>
+    <string name="pref_summary_pebble_always_ack_pebblekit">灏囦娇鐧奸�佸埌澶栭儴绗笁鏂规噳鐢ㄧ殑娑堟伅濮嬬祩绔嬪嵆寰楀埌纰鸿獚</string>
+    <string name="pref_title_overwrite_settings_on_connection">鍦ㄩ�f帴涓婅钃嬭ō缃�</string>
+    <string name="pref_summary_pebble_enable_bgjs">鍟熺敤寰岋紝鍏佽ū琛ㄧ洡椤ず澶╂埃銆侀浕姹犱俊鎭瓑銆�</string>
+    <string name="pref_title_device_intents">鍏佽ū鎰忓湒</string>
+    <string name="preferences_hplus_settings">HPlus/Makibes 瑷畾</string>
+    <string name="pref_summary_device_intents">鍏佽ū Bangle.js 鎵嬮尪鎳夌敤绋嬪紡鐧奸�� Android Intent锛屼甫鍏佽ū鍏朵粬 Android 鎳夌敤绋嬪紡锛堝 Tasker锛夐�氶亷 com.banglejs.uart.tx Intent 鍚� Bangle.js 鐧奸�佹暩鎿氥�傞渶瑕侀’绀哄湪鍏朵粬鎳夌敤绋嬪紡涔嬩笂浠ヤ究鍦ㄨ儗鏅腑閬嬩綔鐨勬瑠闄愩��</string>
+    <string name="pref_title_relax_firmware_checks">濡傛灉鎮ㄦ兂瑕侀杻瀛樹笉閬╃敤鏂兼偍瑷倷鐨勫浐浠讹紙棰ㄩ毆鑷矤锛夛紝璜嬪暉鐢ㄦ閬搁爡</string>
+    <string name="pref_summary_huami_force_new_protocol">濡傛灉鎮ㄧ殑瑷倷鍦ㄥ浐浠跺崌绱氬緦涓嶅啀閫f帴锛岃珛鍟熺敤</string>
+    <string name="pref_header_notifications_and_calls">閫氱煡鍜岄浕瑭�</string>
+    <string name="pref_summary_notifications_and_calls_enable_misscall">姣忓垎閻橀噸瑜囦竴娆�</string>
+    <string name="pref_title_notifications_and_calls_repeat_on_misscall">閲嶈 X 鍒嗛悩</string>
+    <string name="pref_header_notifications_and_calls_callhandling">鍛煎彨铏曠悊</string>
+    <string name="prefs_notifications_and_calls_reject">鎸夐垥蹇界暐/鎷掔禃渚嗛浕</string>
+    <string name="pref_summary_notifications_and_calls_title_reject">闂滈枆 - 蹇界暐锛岄枊鍟� - 鎷掔禃</string>
+    <string name="pref_summary_notifications_and_calls_title_shake_reject">閲嶈鐩g湅鎸夐垥鍕曚綔</string>
+    <string name="pref_summary_device_spec_settings_title_force_time">寮峰埗鍦ㄩ噸鏂伴�f帴鏅傝嚜鍕曞悓姝ユ檪闁撱�傛寚閲濆彲鑳介’绀轰笉姝g⒑鐨勬檪闁擄紒</string>
+    <string name="pref_title_device_spec_settings_show_raw_graph">鍦ㄦ椿鍕曞湒涓婇’绀哄師濮嬫暩鎿�</string>
+    <string name="pref_header_sensors_calibration">鍌虫劅鍣ㄦ牎婧�</string>
+    <string name="pref_sensors_bp_calibration">琛�澹撴牎婧�</string>
+    <string name="prefs_sensors_button_bp_calibration_sum">鎸夋闁嬪鏍℃簴</string>
+    <string name="power_mode_title">瑙�鐪嬮浕婧愭ā寮�</string>
+    <string name="power_mode_saving">鐪侀浕</string>
+    <string name="power_mode_watch">鍙湅</string>
+    <string name="femometer_measurement_mode_title">娓噺妯″紡</string>
+    <string name="femometer_measurement_mode_quick">蹇�熸ā寮� (30绉�)</string>
+    <string name="femometer_measurement_mode_normal">姝e父妯″紡 (60绉�-90绉�)</string>
+    <string name="femometer_measurement_mode_precise">绮剧⒑妯″紡 (3鍒嗛悩)</string>
+    <string name="preferences_makibes_hr3_settings">Makibes HR3 瑷畾</string>
+    <string name="zetime_title_heartrate">蹇冪巼瑷畾</string>
+    <string name="zetime_analog_mode">椤炴瘮妯″紡</string>
+    <string name="zetime_analog_mode_hands">鍙湁鎵�</string>
+    <string name="zetime_handmove_display">鎵嬮儴鍕曚綔</string>
+    <string name="zetime_handmove_display_summary">鏃嬭綁鎵嬭厱浠ュ暉鍕曟垨鍋滅敤椤ず鍣ㄣ��</string>
+    <string name="zetime_signaling_beep">鎸佺簩鐨勫椂鍡惰伈</string>
+    <string name="zetime_signaling_vibrate_beep">鎸佺簩鎸嫊鍜岃渹槌�</string>
+    <string name="zetime_signaling_vibrate_twice">闇囧嫊鍏╂</string>
+    <string name="zetime_signaling_beep_twice">鍡跺椂鍏╂</string>
+    <string name="zetime_signaling_vibrate_beep_once">闇囧嫊涓︾櫦鍑轰竴娆¤渹槌�</string>
+    <string name="clap_hands_to_wakeup_device">鎷嶆墜浠ユ彁楂樺睆骞�</string>
+    <string name="pref_explanation_authkey_new_protocol">濡傛灉鎮ㄥ湪鎵嬬挵涓婄湅鍒般�屽皣鎳夌敤绋嬪紡鏇存柊鑷虫渶鏂扮増鏈�嶇殑瑷婃伅锛岃珛纰轰繚妾㈡煡涓婇潰鐨勩�屾柊椹楄瓑鍗旇銆嶃�傞粸鎿婇�欒!浠ョ嵅鍙栫董鍩轰腑鐨勬洿澶氳┏绱拌硣瑷娿��</string>
+    <string name="prefs_hr_alarm_high">楂橀檺</string>
+    <string name="pref_gps_satellite_search">琛涙槦鎼滃皨</string>
+    <string name="pref_crown_vibration">鍐犳尟</string>
+    <string name="pref_alert_tone">璀﹀牨闊�</string>
+    <string name="pref_touch_tone_summary">鐣惰�虫琚Ц纰版檪鐧煎嚭闊宠</string>
+    <string name="pref_wearing_tone">绌胯憲瑾炴埃</string>
+    <string name="pref_wearing_tone_summary">鐣惰�冲鎻掑叆鏅傜櫦鍑洪煶瑾�</string>
+    <string name="pref_text_to_speech">鏂囧瓧杞夎獮闊�</string>
+    <string name="offline_voice_respond_turn_wrist">杞夊嫊鎵嬭厱鏅傚洖鎳�</string>
+    <string name="offline_voice_response_during_screen_lighting">鍦ㄨ灑骞曚寒璧锋檪鍥炴噳</string>
+    <string name="pref_agps_header">AGPS</string>
+    <string name="pref_agps_expiry_reminder_enabled">AGPS 鍒版湡鎻愰啋</string>
+    <string name="pref_agps_expiry_reminder_time">AGPS 鍒版湡鎻愰啋鏅傞枔</string>
+    <string name="pref_morning_updates_categories_title">鏃╂櫒鏇存柊椤炲垾</string>
+    <string name="pref_workout_start_on_phone_title">鍋ヨ韩鎳夌敤绋嬪紡杩借工</string>
+    <string name="pref_workout_start_on_phone_summary">鐣舵墜鐠伴枊濮婫PS閬嬪嫊鏅傦紝鍦ㄦ墜姗熶笂鍟熷嫊/鍋滄鍋ヨ韩鎳夌敤绋嬪紡杩借工</string>
+    <string name="pref_workout_send_gps_summary">鍦ㄩ崨鐓夋湡闁撳皣鐣跺墠鐨� GPS 浣嶇疆鐧奸�佺郸妯傞殜</string>
+    <string name="pref_workout_keep_screen_on_title">鍦ㄩ亱鍕曟湡闁撲繚鎸佽灑骞曢枊鍟�</string>
+    <string name="pref_workout_keep_screen_on_summary">鍦ㄩ亱鍕曢亷绋嬩腑锛岃灑骞曞皣淇濇寔闁嬪暉锛屼寒搴﹀皣鑷嫊瑾挎暣浠ユ寔绾岄’绀哄鏅傞亱鍕曟暩鎿氥��</string>
+    <string name="pref_workout_detection_alert_summary">鐣舵娓埌閬嬪嫊鏅傞�氱煡</string>
+    <string name="pref_workout_detection_enabled">妾㈡脯宸插暉鐢�</string>
+    <string name="pref_workout_detection_enabled_summary">鍟熺敤鑷嫊妾㈡脯姝ら亱鍕�</string>
+    <string name="pref_workout_detection_time_summary">閬嬪嫊蹇呴爤鎸佺簩鐨勫垎閻樻暩鎵嶈兘琚娓埌</string>
+    <string name="pref_sleep_mode_sleep_screen_title">鐫$湢铻㈠箷</string>
+    <string name="find_my_phone_companion_warning">闇�瑕侀厤灏嶄即渚舵墠鑳藉皨鎵炬墜姗熴�傞粸鎿婇�欒!浠ョ嵅鍙栨洿澶氳硣瑷娿��</string>
+    <string name="tap_connected_device_for_app_mananger">榛炴搳閫f帴鐨勮ō鍌欎互绠$悊鎳夌敤绋嬪紡</string>
+    <string name="tap_connected_device_for_activity">杓曡Ц閫f帴鐨勮ō鍌欎互閫茶娲诲嫊</string>
+    <string name="installing_binary_d_d">瀹夎浜岄�蹭綅妾� %1$d/%2$d</string>
+    <string name="firmware_install_warning">鎮ㄦ鍦ㄥ槜瑭﹀畨瑁濆浐浠讹紝璜嬭嚜琛屾壙鎿旈ⅷ闅��
+\n
+\n
+\n 姝ゅ浐浠堕仼鐢ㄦ柤纭珨鐗堟湰锛�%s</string>
+    <string name="watchface_install_info">鎮ㄥ嵆灏囧畨瑁濅互涓嬮尪闈細
+\n
+\n%1$s
+\n鐗堟湰 %2$s 鐢� %3$s 鎻愪緵
+\n</string>
+    <string name="choose_device">閬告搰涓�鍊嬭ō鍌�</string>
+    <string name="no_supported_devices_found">鏈壘鍒版敮鎻寸殑瑁濈疆</string>
+    <string name="wearmode_band">鎵嬬挵</string>
+    <string name="preferences_miband_1_2_warning">璀﹀憡锛氶�欎簺鍋忓ソ鍍呴仼鐢ㄦ柤 Mi Bands 1 鍜� 2銆�</string>
+    <string name="buttons_on_right">鍙冲伌鐨勬寜閳�</string>
+    <string name="discovery_entered_invalid_authkey">鎮ㄨ几鍏ョ殑绉樺瘑鎺堟瑠閲戦懓鐒℃晥锛侀暦鎸夎ō鍌欎互閫茶绶ㄨ集銆�</string>
+    <string name="pref_cache_weather">蹇彇澶╂埃璩囪▕</string>
+    <string name="pref_cache_weather_summary">#ERROR!</string>
+    <string name="pref_write_logfiles_not_available">妾旀鏃ヨ獙鍒濆鍖栧け鏁楋紝鐣跺墠鐒℃硶瀵叆鏃ヨ獙妾旀銆傝珛閲嶆柊鍟熷嫊鎳夌敤绋嬪紡浠ュ啀娆″槜瑭﹀垵濮嬪寲鏃ヨ獙妾旀銆�</string>
+    <string name="busy_task_fetch_sports_summaries">鐛插彇楂旇偛鎽樿</string>
+    <string name="busy_task_fetch_sports_details">鐛插彇閬嬪嫊瑭虫儏</string>
+    <string name="busy_task_fetch_sports_details_interrupted">鐛插彇楂旇偛瑭虫儏鐨勯亷绋嬭涓柗</string>
+    <string name="pref_screen_notification_profile_inactivity">涓嶆椿鍕曢�氱煡</string>
+    <string name="pref_screen_notification_idle_alerts">闁掔疆璀﹀牨</string>
+    <string name="heartrate_bpm_125">125 鎷嶆瘡鍒嗛悩</string>
+    <string name="heartrate_bpm_150">150 鎷嶆瘡鍒嗛悩</string>
+    <string name="heartrate_bpm_155">155 姣忓垎閻�</string>
+    <string name="heartrate_bpm_165">165 鎷嶆瘡鍒嗛悩</string>
+    <string name="heartrate_bpm_185">185 鎷嶆瘡鍒嗛悩</string>
+    <string name="reminder_once">涓�娆�</string>
+    <string name="reminder_every_week">姣忛��</string>
+    <string name="reminder_no_free_slots_description">瑭茶ō鍌欐矑鏈夊彲鐢ㄧ殑鎻愰啋鎻掓Ы锛堢附鎻掓Ы锛�%1$s锛�</string>
+    <string name="world_clock_code">浠g⒓</string>
+    <string name="alarm_smart_wakeup_interval">鏅烘収鍠氶啋闁撻殧锛�</string>
+    <string name="user_feedback_set_settings_ok">瑷畾宸茬櫦閫佽嚦瑷倷銆�</string>
+    <string name="chart_no_active_data">鏈伒娓埌浠讳綍娲诲嫊銆�</string>
+    <string name="user_feedback_miband_activity_data_transfer">鍗冲皣寰� %2$s 闁嬪鍌宠几 %1$s 鐨勬暩鎿�</string>
+    <string name="prefs_heartrate_alert_experimental_title">蹇冪巼璀﹀牨锛堝椹楁�э級</string>
+    <string name="prefs_heartrate_alert_active_high_threshold">楂樻椿鍕曞績鐜囪鍫遍柧鍊�</string>
+    <string name="prefs_heartrate_alert_low_threshold">浣庡績鐜囪鍫遍柧鍊�</string>
+    <string name="prefs_spo2_monitoring_description">鑷嫊鐩f脯鍏ㄥぉ鐨勮姘ф按骞�</string>
+    <string name="devicestatus_upload_aborted">涓婂偝宸茶涓锛�</string>
+    <string name="devicestatus_upload_failed">涓婂偝澶辨晽</string>
+    <string name="fwapp_install_device_not_supported">鐒℃硶瀹夎妾旀锛岃ō鍌欎笉鏀彺銆�</string>
+    <string name="hr_average">骞冲潎</string>
+    <string name="blood_pressure">琛�澹�</string>
+    <string name="heart_rate_result">娓噺绲愭灉</string>
+    <string name="movement_intensity">閬嬪嫊寮峰害</string>
+    <string name="seconds_8">8绉�</string>
+    <string name="seconds_9">9绉�</string>
+    <string name="seconds_11">11绉�</string>
+    <string name="seconds_13">13绉�</string>
+    <string name="seconds_15">15绉�</string>
+    <string name="seconds_25">25绉�</string>
+    <string name="minutes_2">2鍒嗛悩</string>
+    <string name="minutes_3">3鍒嗛悩</string>
+    <string name="minutes_4">4鍒嗛悩</string>
+    <string name="minutes_6">6鍒嗛悩</string>
+    <string name="minutes_7">7鍒嗛悩</string>
+    <string name="minutes_8">8鍒嗛悩</string>
+    <string name="minutes_9">9鍒嗛悩</string>
+    <string name="minutes_15">15鍒嗛悩</string>
+    <string name="minutes_90">90鍒嗛悩</string>
+    <string name="minutes_120">120鍒嗛悩</string>
+    <string name="minutes_150">150鍒嗛悩</string>
+    <string name="minutes_180">180鍒嗛悩</string>
+    <string name="minutes_210">210鍒嗛悩</string>
+    <string name="minutes_255">255鍒嗛悩</string>
+    <string name="pref_huami_truncate_fetch_operation_timestamps_title">鎴柗鐛插彇鎿嶄綔鏅傞枔鎴�</string>
+    <string name="pref_title_dont_ack_transfer">涓嶈纰鸿獚娲诲嫊鏁告摎鍌宠几</string>
+    <string name="pref_summary_dont_ack_transfers">濡傛灉鏈皪闋诲付閫茶纰鸿獚锛屽墖娲诲嫊鏁告摎涓嶆渻琚竻闄ゃ�傞�欏湪鑸囧叾浠栨噳鐢ㄧ▼寮忎竴璧蜂娇鐢℅B鏅傞潪甯告湁鐢ㄣ��</string>
+    <string name="pref_enable_unsupported_settings_title">鍟熺敤涓嶆敮鎻寸殑瑷畾</string>
+    <string name="pref_enable_unsupported_settings_summary">閫欏皣浣挎墍鏈夊彲鐢ㄧ殑瑷疆閮借兘瑷晱锛屽嵆浣胯ō鍌欎笉鏀寔銆傞�欏彲鑳芥渻灏庤嚧瑷倷鐨勪笉绌╁畾鍜屽穿娼般��</string>
+    <string name="pref_title_low_latency_fw_update">浣跨敤浣庡欢閬叉ā寮忛�茶闊岄珨鐕掗寗</string>
+    <string name="pref_summary_low_latency_fw_update">閫欏彲鑳藉皪鍥轰欢鐕掗寗澶辨晽鐨勮ō鍌欐湁鎵�骞姪銆�</string>
+    <string name="pref_title_third_party_app_device_settings">鍏佽ū绗笁鏂规噳鐢ㄧ▼寮忔洿鏀硅ō瀹�</string>
+    <string name="live_activity_steps_history">姝ラ姝峰彶</string>
+    <string name="live_activity_start_your_activity">闁嬪鎮ㄧ殑娲诲嫊</string>
+    <string name="abstract_chart_fragment_kind_awake_sleep">閱掕憲</string>
+    <string name="sleep_colored_stats_deep">娣�</string>
+    <string name="sleep_colored_stats_light">娣�</string>
+    <string name="sleep_colored_stats_rem">REM</string>
+    <string name="sleep_colored_stats_deep_avg">娣辩潯骞冲潎</string>
+    <string name="prefs_reserve_reminder_calendar_summary">灏囪鍚屾鐨勬棩鏇嗕簨浠舵暩閲�</string>
+    <string name="sleep_colored_stats_light_avg">娣虹潯骞冲潎</string>
+    <string name="sleep_colored_stats_rem_avg">REM骞冲潎</string>
+    <string name="sleep_colored_stats_awake_avg">閱掍締骞冲潎</string>
+    <string name="stats_lowest_hr">鏈�浣庡績鐜�</string>
+    <string name="stats_highest_hr">鏈�楂樹汉鍔涜硣婧�</string>
+    <string name="charts_min_max_heartrate_popup">鏈�浣庡績鐜�: %1$d
+\n鏈�楂樺績鐜�: %2$d
+\n閬嬪嫊寮峰害: %3$s</string>
+    <string name="prefs_find_phone_summary">浣跨敤浣犵殑妯傞殜渚嗘挱鏀句綘鎵嬫鐨勯埓鑱层��</string>
+    <string name="prefs_find_phone_duration">閳磋伈鎸佺簩鏅傞枔锛堢锛�</string>
+    <string name="mi2_prefs_button_press_broadcast">鐧奸�佸唬鎾秷鎭�</string>
+    <string name="mi2_prefs_button_action_summary">鍦ㄦ寚瀹氱殑鎸夐垥鎸変笅娆℃暩涓婂暉鐢ㄥ嫊浣�</string>
+    <string name="mi2_prefs_button_action_vibrate_summary">鍟熺敤鎸夐垥鍕曚綔瑙哥櫦鐨勫付鎸嫊</string>
+    <string name="mi2_prefs_button_press_count_max_delay">鎸夊涔嬮枔鐨勬渶澶у欢閬�</string>
+    <string name="mi2_prefs_button_press_count_max_delay_summary">鎸夐垥鎸夊涔嬮枔鐨勬渶澶у欢閬诧紙姣锛�</string>
+    <string name="mi2_prefs_rotate_wrist_to_switch_info">鏃嬭綁鎵嬭厱浠ュ垏鎻涜硣瑷�</string>
+    <string name="mi2_prefs_heart_rate_monitoring">蹇冪巼鐩f脯</string>
+    <string name="prefs_phone_silent_mode">闆昏┍闈滈煶妯″紡</string>
+    <string name="silent_mode_normal_silent">姝e父 / 闈滈煶</string>
+    <string name="silent_mode_vibrate_silent">闇囧嫊 / 闈滈煶</string>
+    <string name="prefs_password_4_digits_1_to_4_summary">瀵嗙⒓蹇呴爤鏈�4浣嶆暩瀛楋紝浣跨敤1鍒�4鐨勬暩瀛�</string>
+    <string name="prefs_always_on_display_style">棰ㄦ牸</string>
+    <string name="prefs_shortcut_cards_summary">鍦ㄦ墜閷惰〃闈㈠悜鍙虫粦鍕曟檪鐪嬪埌鐨勫揩鎹峰崱鐗囥�傜暥鎳夌敤绋嬪紡姝e湪閬嬭鏅傦紝鑷嫊鐢熸垚鐨勫崱鐗囦笉鍙楁瑷疆褰遍熆銆�</string>
+    <string name="prefs_control_center_summary">閬告搰鎺у埗涓績涓嬫媺鑿滃柈涓殑闋呯洰</string>
+    <string name="prefs_screen_timeout">铻㈠箷瓒呮檪</string>
+    <string name="lefun_prefs_hydration_reminder_title">瑁滄按鎻愰啋</string>
+    <string name="lefun_prefs_hydration_reminder_summary">妯傞殜灏囨渻闇囧嫊浠ユ彁閱掍綘鍠濇按</string>
+    <string name="lefun_prefs_antilost_summary">濡傛灉鎮ㄧ殑鎵嬫鑸囨墜鐠版柗闁嬮�f帴锛屾墜鐠板皣鏈冮渿鍕�</string>
+    <string name="bengali">瀛熷姞鎷夎獮</string>
+    <string name="spanish_us">缇庡湅瑗跨彮鐗欒獮</string>
+    <string name="common_symbols">甯歌绗﹁櫉</string>
+    <string name="croatian">鍏嬬緟鍦颁簽瑾�</string>
+    <string name="czech">鎹峰厠瑾�</string>
+    <string name="icelandic">鍐板扯瑾�</string>
+    <string name="latvian">鎷夎劔缍簽瑾�</string>
+    <string name="lithuanian">绔嬮櫠瀹涜獮</string>
+    <string name="persian">娉㈡柉瑾�</string>
+    <string name="armenian">浜炵編灏间簽瑾�</string>
+    <string name="french_fr">娉曡獮锛堟硶鍦嬶級</string>
+    <string name="hungarian">鍖堢墮鍒╄獮</string>
+    <string name="greek">甯岃嚇瑾�</string>
+    <string name="swedish">鐟炲吀瑾�</string>
+    <string name="czesh">鎹峰厠瑾�</string>
+    <string name="danish">涓归亥瑾�</string>
+    <string name="activity_prefs_goals">鐩</string>
+    <string name="weekstepschart_steps_a_week_or_month">姣忛��/姣忔湀姝ユ暩</string>
+    <string name="weeksleepchart_sleep_a_week_or_month">姣忛��/姣忔湀鐫$湢</string>
+    <string name="activity_prefs_chart_min_steps_per_minute">姣忓垎閻樻渶灏戞鏁镐互妾㈡脯娲诲嫊</string>
+    <string name="charts_legend_heartrate_average">蹇冪巼骞冲潎</string>
+    <string name="charts_legend_stress_average">澹撳姏骞冲潎</string>
+    <string name="charts_legend_spo2_average">琛�姘у钩鍧囧��</string>
+    <string name="activity_prefs_goal_standing_time_minutes">姣忔棩鐩锛氱珯绔嬫檪闁擄紙鍒嗛悩锛�</string>
+    <string name="pref_summary_pebble_health_store_raw">灏囨暩鎿氥�屽師妯c�嶅劜瀛橈紝澧炲姞鏁告摎搴殑浣跨敤閲忥紝浠ヤ究寰岀簩瑙i噵銆�</string>
+    <string name="activity_db_management_merge_old_title">鍒櫎鑸婃湁璩囨枡搴�</string>
+    <string name="dbmanagementactivvity_cannot_access_export_path">鐒℃硶瑷晱灏庡嚭璺緫銆傝珛鑱公闁嬬櫦鑰呫��</string>
+    <string name="dbmanagementactivity_error_exporting_db">灏庡嚭璩囨枡搴檪鍑洪尟锛�%1$s</string>
+    <string name="dbmanagementactivity_overwrite_database_confirmation">鐪熺殑瑕佽钃嬬暥鍓嶆暩鎿氬棊锛熸偍鎵�鏈夌暥鍓嶇殑娲诲嫊鏁告摎锛堝鏋滄湁锛夈�佽ō鍌欏拰鍋忓ソ瑷疆灏囪瑕嗚搵銆�</string>
+    <string name="dbmanagementactivity_error_importing_shared">灏庡叆鍋忓ソ瑷畾鏅傚嚭閷�: %1$s</string>
+    <string name="dbmanagementactivity_really_delete_entire_db">鐪熺殑瑕佸埅闄ゆ暣鍊嬫暩鎿氬韩鍡庯紵鎮ㄦ墍鏈夌殑娲诲嫊鏁告摎鍜屾湁闂滄偍瑷倷鐨勪俊鎭皣鏈冧笩澶便��</string>
+    <string name="dbmanagementactivity_db_deletion_failed">璩囨枡搴埅闄ゅけ鏁椼��</string>
+    <string name="dbmanagementactivity_old_activity_db_successfully_deleted">鑸婄殑娲诲嫊鏁告摎宸插埅闄ゃ��</string>
+    <string name="activity_db_management_autoexport_enabled_no">鑷嫊鍖嚭鏈暉鐢ㄣ��</string>
+    <string name="activity_db_management_autoexport_location">浣嶇疆鐒℃硶鐞嗚В銆傚彲鑳芥槸杓冩柊鐨� Android 娆婇檺绯荤当鐨勫晱椤屻�傛渶鏈夊彲鑳界殑鏄紝鑷嫊鍖嚭鐩墠鐒℃硶姝e父閬嬩綔銆�</string>
+    <string name="dismiss">瑙i泧</string>
+    <string name="status">鐙�鎱�</string>
+    <string name="set">瑷疆</string>
+    <string name="pebble_pairing_hint">鎮ㄧ殑 Android 瑷倷涓婂皣鏈冨綀鍑洪厤灏嶅皪瑭辨銆傚鏋滄矑鏈夛紝璜嬪湪閫氱煡鎶藉睖涓煡鐪嬩甫鎺ュ彈閰嶅皪璜嬫眰銆傜劧寰屽湪鎮ㄧ殑 Pebble 涓婁篃鎺ュ彈瀹冦��</string>
+    <string name="weather_notification_label">璜嬬⒑淇濆湪澶╂埃閫氱煡鎳夌敤绋嬪紡涓暉鐢ㄦ涓婚锛屼互渚垮湪鎮ㄧ殑 Pebble 涓婄嵅鍙栧ぉ姘h硣瑷娿��
+\n
+\n閫欒!涓嶉渶瑕佷换浣曢厤缃��
+\n
+\n鎮ㄥ彲浠ュ緸鎳夌敤绠$悊涓暉鐢� Pebble 鐨勭郴绲卞ぉ姘f噳鐢ㄧ▼寮忋��
+\n
+\n鏀彺鐨勮〃鐩ゅ皣鑷嫊椤ず澶╂埃璩囪▕銆�</string>
+    <string name="dateformat_day_month">鏃ワ紝鏈�</string>
+    <string name="dateformat_month_day">鏈堜唤锛屾棩鏈�</string>
+    <string name="StringUtils_sender">(%1$s)</string>
+    <string name="sensitive">鏁忔劅</string>
+    <string name="mi2_dnd_always">姘搁仩</string>
+    <string name="mi3_night_mode_sunset">鍦ㄦ棩钀芥檪</string>
+    <string name="discovery_successfully_bonded">缍佸畾鍒� %1$s銆�</string>
+    <string name="discovery_pair_question">閬告搰閰嶅皪浠ラ�f帴鎮ㄧ殑瑷倷銆傚鏋滃け鏁楋紝璜嬪槜瑭︿笉閫茶閰嶅皪鍐嶈│涓�娆°��</string>
+    <string name="_pebble_watch_mute">闈滈煶</string>
+    <string name="activity_type_rem_sleep">蹇�熺溂鍕曠潯鐪�</string>
+    <string name="activity_type_indoor_running">瀹ゅ収璺戞</string>
+    <string name="activity_type_mountain_hike">灞卞湴寰掓</string>
+    <string name="activity_type_cross_trainer">浜ゅ弶瑷撶反鍣�</string>
+    <string name="activity_type_stair_stepper">韪忔姗�</string>
+    <string name="activity_type_crossfit">璺‵it</string>
+    <string name="activity_type_functional_training">鍔熻兘瑷撶反</string>
+    <string name="activity_type_taekwondo">璺嗘嫵閬�</string>
+    <string name="activity_type_beach_soccer">娌欑仒瓒崇悆</string>
+    <string name="activity_type_beach_volleyball">娌欑仒鎺掔悆</string>
+    <string name="activity_type_gateball">闁�鐞�</string>
+    <string name="activity_type_sepak_takraw">Sepak Takraw</string>
+    <string name="activity_type_parachuting">璺冲倶</string>
+    <string name="activity_type_parkour">璺戦叿</string>
+    <string name="activity_type_parallel_bar">闆欐</string>
+    <string name="activity_type_sit_ups">浠拌嚗璧峰潗</string>
+    <string name="activity_type_fitness_gaming">鍋ヨ韩閬婃埐</string>
+    <string name="activity_type_aerobic_exercise">鏈夋哀閬嬪嫊</string>
+    <string name="activity_type_rolling">婊惧嫊</string>
+    <string name="activity_type_pull_ups">寮曢珨鍚戜笂</string>
+    <string name="activity_type_plank">骞虫澘</string>
+    <string name="activity_type_javelin">妯欐</string>
+    <string name="activity_type_high_jump">璺抽珮</string>
+    <string name="activity_type_latin_dance">鎷変竵鑸�</string>
+    <string name="activity_type_disc_sports">椋涚洡閬嬪嫊</string>
+    <string name="activity_type_archery">灏勭</string>
+    <string name="activity_type_horse_riding">楱庨Μ</string>
+    <string name="activity_type_swing">绉嬪崈</string>
+    <string name="activity_type_stairs">妯撴</string>
+    <string name="activity_type_fishing">閲i瓪</string>
+    <string name="activity_type_mind_and_body">蹇冮潏鑸囪韩楂�</string>
+    <string name="activity_type_kabaddi">鍗″反杩�</string>
+    <string name="activity_type_karting">鍗′竵杌�</string>
+    <string name="activity_type_billiards">鎾炵悆</string>
+    <string name="activity_type_shuttlecock">缇芥瘺鐞�</string>
+    <string name="activity_type_softball">澹樼悆</string>
+    <string name="activity_type_australian_football">婢虫床瓒崇悆</string>
+    <string name="activity_type_lacross">鎷夊厠缇呮柉</string>
+    <string name="activity_type_sailing">鑸</string>
+    <string name="activity_type_skating">婊戝啺</string>
+    <string name="activity_type_curling">鍐板:</string>
+    <string name="activity_type_snow_sports">闆笂閬嬪嫊</string>
+    <string name="activity_type_skateboarding">婊戞澘</string>
+    <string name="activity_type_outdoor_walking">鎴跺鏁f</string>
+    <string name="activity_type_indoor_walking">瀹ゅ収姝ヨ</string>
+    <string name="activity_type_surfing">琛濇氮</string>
+    <string name="activity_type_windsurfing">棰ㄧ畯琛濇氮</string>
+    <string name="activity_type_kitesurfing">棰ㄧ畯琛濇氮</string>
+    <string name="activity_type_freestyle">鑷敱寮�</string>
+    <string name="activity_type_football">瓒崇悆</string>
+    <string name="activity_type_rugby">姗勬瑬鐞�</string>
+    <string name="activity_type_cricket">鏉跨悆</string>
+    <string name="activity_type_badminton">缇芥瘺鐞�</string>
+    <string name="activity_type_gymnastics">楂旀搷</string>
+    <string name="activity_type_core_training">鏍稿績瑷撶反</string>
+    <string name="activity_type_stretching">浼稿睍</string>
+    <string name="activity_type_stepper">姝ラ�插櫒</string>
+    <string name="activity_type_pilates">鏅媺鎻�</string>
+    <string name="activity_type_volleyball">鎺掔悆</string>
+    <string name="activity_type_bowling">淇濋健鐞�</string>
+    <string name="activity_type_kickboxing">韪㈡嫵</string>
+    <string name="activity_type_street_dance">琛楄垶</string>
+    <string name="activity_type_dancing">璺宠垶</string>
+    <string name="activity_type_skiing">婊戦洩</string>
+    <string name="activity_type_snowboarding">婊戦洩鏉�</string>
+    <string name="activity_type_hockey">鏇叉鐞�</string>
+    <string name="activity_type_icehockey">鍐扮悆</string>
+    <string name="activity_type_other">鍏朵粬</string>
+    <string name="activity_type_iceskating">婊戝啺</string>
+    <string name="activity_type_trekking">寰掓鏃呰</string>
+    <string name="activity_type_navigate">灏庤埅</string>
+    <string name="activity_type_handcycling">鎵嬪嫊鑷杌�</string>
+    <string name="activity_type_e_bike">闆诲嫊鑷杌�</string>
+    <string name="activity_type_bike_commute">鑷杌婇�氬嫟</string>
+    <string name="activity_type_handcycling_indoor">鎵嬪嫊鍠粖瀹ゅ収</string>
+    <string name="activity_type_transition">閬庢浮</string>
+    <string name="activity_type_fitness_equipment">鍋ヨ韩鍣ㄦ潗</string>
+    <string name="activity_type_platform_tennis">骞冲彴缍茬悆</string>
+    <string name="activity_type_american_football">缇庡紡瓒崇悆</string>
+    <string name="activity_type_cardio">蹇冭偤閬嬪嫊</string>
+    <string name="activity_type_training">瑷撶反</string>
+    <string name="activity_type_breathwork">鍛煎惛绶寸繏</string>
+    <string name="activity_type_multisport">澶氶爡閬嬪嫊</string>
+    <string name="activity_type_paddling">鍒掕埞</string>
+    <string name="activity_type_flying">椋涜</string>
+    <string name="activity_type_motorcycling">鎽╂墭杌婇◣琛�</string>
+    <string name="activity_type_sail_race">甯嗚埞姣旇辰</string>
+    <string name="activity_type_sail_expedition">鑸捣鎺㈤毆</string>
+    <string name="activity_type_floor_climbing">妯撴鏀�鐖�</string>
+    <string name="activity_type_shooting">灏勬搳</string>
+    <string name="activity_type_winter_sport">鍐閬嬪嫊</string>
+    <string name="activity_type_racket">鐞冩媿</string>
+    <string name="activity_type_grinding">纾ㄧ</string>
+    <string name="activity_type_padel">甯曞痉鐖�</string>
+    <string name="activity_type_racquetball">澹佺悆</string>
+    <string name="activity_type_push_walk_speed">鎺� - 姝ヨ閫熷害</string>
+    <string name="activity_type_indoor_push_walk_speed">瀹ゅ収鎺� - 姝ヨ閫熷害</string>
+    <string name="activity_type_push_run_speed">鎺� - 璺戞閫熷害</string>
+    <string name="activity_type_meditation">鍐ユ兂</string>
+    <string name="activity_type_para_sport">娈樺ェ閬嬪嫊</string>
+    <string name="activity_type_disc_golf">椋涚楂樼埦澶�</string>
+    <string name="activity_type_ultimate_disc">绲傛サ椋涚洡</string>
+    <string name="activity_type_team_sport">鍦橀殜閬嬪嫊</string>
+    <string name="activity_type_lacrosse">鏇叉鐞�</string>
+    <string name="activity_type_mixed_martial_arts">缍滃悎鏍奸</string>
+    <string name="activity_type_artistic_swimming">钘濊娓告吵</string>
+    <string name="activity_type_ballroom_dance">绀句氦鑸�</string>
+    <string name="activity_type_breaking">绐佺櫦</string>
+    <string name="activity_type_bridge">姗�</string>
+    <string name="activity_type_cardio_combat">蹇冭偤鎼忛</string>
+    <string name="activity_type_chess">璞℃</string>
+    <string name="activity_type_dragon_boat">榫嶈垷</string>
+    <string name="activity_type_esports">闆诲瓙绔舵妧</string>
+    <string name="activity_type_flowriding">娴佹氮琛濇氮</string>
+    <string name="activity_type_judo">鏌旈亾</string>
+    <string name="activity_type_jujitsu">鏌旇</string>
+    <string name="activity_type_pole_dance">閶肩鑸�</string>
+    <string name="activity_type_race_walking">绔惰蛋</string>
+    <string name="activity_type_shuffleboard">婊戞澘鐞�</string>
+    <string name="activity_type_snorkeling">娴經</string>
+    <string name="activity_type_somatosensory_game">楂旀劅閬婃埐</string>
+    <string name="activity_type_stair_climber">鐖〒姗�</string>
+    <string name="activity_type_free_sparring">鑷敱 sparring</string>
+    <string name="activity_type_obstacle_race">闅滅璩�</string>
+    <string name="activity_type_laser_tag">婵�鍏夋</string>
+    <string name="activity_type_billiard_pool">娓告吵姹�</string>
+    <string name="activity_type_canoeing">鐛ㄦ湪鑸�</string>
+    <string name="activity_type_water_scooter">姘翠笂鎽╂墭</string>
+    <string name="activity_type_bobsleigh">闆﹪</string>
+    <string name="activity_type_sledding">婊戦洩</string>
+    <string name="activity_type_biathlon">鍐鍏╅爡</string>
+    <string name="activity_summary_detail_editing_gpx_track">绶ㄨ集閫g祼鐨� GPX 璺緫</string>
+    <string name="hrv_status_balanced">骞宠 鐨�</string>
+    <string name="hrv_status_unbalanced">涓嶅钩琛�</string>
+    <string name="hrv_status_low">浣�</string>
+    <string name="hrv_status_poor">璨х</string>
+    <string name="activity_type_treadmill">璺戞姗�</string>
+    <string name="activity_error_no_app_for_gpx">瑕佹煡鐪嬫椿鍕曡拷韫わ紝璜嬪畨瑁濆彲浠ヨ檿鐞� GPX 妾旀鐨勬噳鐢ㄧ▼寮忋��</string>
+    <string name="share_screenshot">鍒嗕韩鎴湒</string>
+    <string name="screenshot_taken">鎴湒宸叉媿鏀�</string>
+    <string name="reset_index">閲嶇疆鐛插彇鏃ユ湡</string>
+    <string name="kind_gps">GPS 闊岄珨</string>
+    <string name="kind_gps_almanac">GPS 骞撮憫</string>
+    <string name="kind_agps_bundle">AGPS 濂椾欢</string>
+    <string name="devicetype_miband9">灏忕背鏅鸿兘鎵嬬挵9</string>
+    <string name="devicetype_amazfit_balance">Amazfit 骞宠 </string>
+    <string name="devicetype_amazfit_active">Amazfit Active</string>
+    <string name="devicetype_amazfit_active_edge">Amazfit Active Edge</string>
+    <string name="devicetype_amazfit_cheetah_square">Amazfit 鐛佃惫 (鏂瑰舰)</string>
+    <string name="devicetype_amazfit_cheetah_round">Amazfit 鐛佃惫 (鍦撳舰)</string>
+    <string name="devicetype_amazfit_cheetah_pro">Amazfit 鐛佃惫 Pro</string>
+    <string name="devicetype_amazfit_gts4">Amazfit GTS 4</string>
+    <string name="devicetype_amazfit_gts4_mini">Amazfit GTS 4 Mini</string>
+    <string name="devicetype_amazfit_gtr3">Amazfit GTR 3</string>
+    <string name="devicetype_amazfit_bip3">Amazfit Bip 3</string>
+    <string name="devicetype_amazfit_bip3_pro">Amazfit Bip 3 Pro</string>
+    <string name="devicetype_amazfit_bip5">Amazfit Bip 5</string>
+    <string name="devicetype_amazfit_gtr4">Amazfit GTR 4</string>
+    <string name="devicetype_amazfit_trex_3">Amazfit T-Rex 3</string>
+    <string name="devicetype_amazfit_band7">Amazfit Band 7</string>
+    <string name="devicetype_amazfit_neo">Amazfit Neo</string>
+    <string name="devicetype_amazfit_bip_lite">Amazfit Bip Lite</string>
+    <string name="devicetype_amazfit_cor2">Amazfit Cor 2</string>
+    <string name="devicetype_amazfit_gtr">Amazfit GTR</string>
+    <string name="devicetype_amazfit_gtr_lite">Amazfit GTR Lite</string>
+    <string name="devicetype_amazfit_falcon">Amazfit 榉�</string>
+    <string name="devicetype_amazfit_bips_lite">Amazfit Bip S Lite</string>
+    <string name="devicetype_amazfit_bipu">Amazfit Bip U</string>
+    <string name="devicetype_amazfit_pop_pro">Amazfit Pop Pro</string>
+    <string name="devicetype_amazfit_gtr2">Amazfit GTR 2</string>
+    <string name="devicetype_garmin_venu">Garmin Venu</string>
+    <string name="devicetype_garmin_venu_2_plus">Garmin Venu 2 Plus</string>
+    <string name="devicetype_garmin_venu_3">Garmin Venu 3</string>
+    <string name="devicetype_garmin_enduro_3">Garmin Enduro 3</string>
+    <string name="devicetype_garmin_epix_pro">Garmin Epix Pro</string>
+    <string name="devicetype_garmin_fenix_5x_plus">Garmin Fenix 5X Plus</string>
+    <string name="devicetype_garmin_fenix_6">Garmin Fenix 6</string>
+    <string name="devicetype_garmin_fenix_7s">Garmin Fenix 7S</string>
+    <string name="devicetype_garmin_forerunner_265s">Garmin Forerunner 265S</string>
+    <string name="devicetype_garmin_swim_2">Garmin Swim 2</string>
+    <string name="devicetype_garmin_vivoactive_3">Garmin V铆voactive 3</string>
+    <string name="devicetype_garmin_vivoactive_4">Garmin V铆voactive 4</string>
+    <string name="devicetype_garmin_vivoactive_5">Garmin V铆voactive 5</string>
+    <string name="devicetype_garmin_vivosmart_5">Garmin V铆vosmart 5</string>
+    <string name="devicetype_makibes_f68">Makibes F68</string>
+    <string name="devicetype_teclast_h30">Teclast H30</string>
+    <string name="devicetype_xwatch">XWatch</string>
+    <string name="devicetype_qhybrid">鍖栫煶Q娣峰悎</string>
+    <string name="devicetype_id115">ID115</string>
+    <string name="devicetype_watchx">瑙�鐪� X</string>
+    <string name="devicetype_casiogb6900">鍗¤タ姝� GB-6900</string>
+    <string name="devicetype_casiogbx100">鍗¤タ姝� GBX-100</string>
+    <string name="devicetype_casiogwb5600">鍗¤タ姝� GW-B5600</string>
+    <string name="devicetype_casiogmwb5000">鍗¤タ姝� GMW-B5000</string>
+    <string name="devicetype_mismartscale">绫冲鏅鸿兘楂旈噸瑷�2</string>
+    <string name="devicetype_micompositionscale">灏忕背楂旇剛绉� 2</string>
+    <string name="devicetype_itag">iTag</string>
+    <string name="devicetype_bfh16">BFH-16</string>
+    <string name="devicetype_mijia_xmwsdj04">绫冲婧繒搴﹀偝鎰熷櫒 2 (闆诲瓙澧ㄦ按)</string>
+    <string name="devicetype_makibes_hr3">Makibes HR3</string>
+    <string name="devicetype_sony_wh_1000xm2">Sony WH-1000XM2</string>
+    <string name="devicetype_sony_wh_1000xm3">绱㈠凹 WH-1000XM3</string>
+    <string name="devicetype_sony_wh_1000xm5">Sony WH-1000XM5</string>
+    <string name="devicetype_sony_wf_sp800n">绱㈠凹 WF-SP800N</string>
+    <string name="devicetype_sony_wf_1000xm3">绱㈠凹 WF-1000XM3</string>
+    <string name="devicetype_sony_wf_1000xm4">绱㈠凹 WF-1000XM4</string>
+    <string name="devicetype_sony_linkbuds">绱㈠凹 LinkBuds</string>
+    <string name="devicetype_sony_linkbuds_s">Sony LinkBuds S</string>
+    <string name="devicetype_soundcore_liberty3_pro">Soundcore Liberty 3 Pro</string>
+    <string name="devicetype_soundcore_liberty4_nc">Soundcore Liberty 4 NC</string>
+    <string name="devicetype_soundcore_motion300">Soundcore Motion 300</string>
+    <string name="devicetype_moondrop_space_travel">鏈堢悆鏃呰</string>
+    <string name="devicetype_binary_sensor">浜岄�蹭綅鎰熸脯鍣�</string>
+    <string name="devicetype_honor_band3">姒��鎵嬬挵 3</string>
+    <string name="devicetype_honor_band6">姒��鎵嬬挵6</string>
+    <string name="devicetype_honor_band7">姒��鎵嬬挵7</string>
+    <string name="devicetype_honor_magicwatch2">姒��榄旀硶鎵嬮尪2</string>
+    <string name="devicetype_honor_watchgs3">姒��鎵嬮尪 GS 3</string>
+    <string name="devicetype_huawei_band6">鑿偤鎵嬬挵6</string>
+    <string name="devicetype_huawei_band7">鑿偤鎵嬬挵7</string>
+    <string name="devicetype_huawei_band8">鑿偤鎵嬬挵 8</string>
+    <string name="devicetype_huawei_band9">鑿偤鎵嬬挵9</string>
+    <string name="devicetype_huawei_talk_band_b6">鑿偤璜囪┍甯� B6</string>
+    <string name="devicetype_huawei_watchgt3">鑿偤鎵嬮尪 GT 3 (Pro)</string>
+    <string name="devicetype_huawei_watchgt4">鑿偤鎵嬮尪 GT 4</string>
+    <string name="devicetype_huawei_watchgtrunner">鑿偤 Watch GT Runner</string>
+    <string name="devicetype_huawei_watchfit">鑿偤鎵嬮尪 Fit</string>
+    <string name="devicetype_huawei_watch4pro">鑿偤鎵嬮尪 4 Pro</string>
+    <string name="devicetype_femometer_vinca2">Femometer Vinca II</string>
+    <string name="devicetype_xiaomi_watch_lite">灏忕背鎵嬮尪 Lite</string>
+    <string name="devicetype_redmiwatch3active">Redmi 鎵嬮尪 3 Active</string>
+    <string name="devicetype_redmiwatch3">Redmi 鎵嬮尪 3</string>
+    <string name="devicetype_redmi_watch_2_lite">Redmi 鎵嬮尪 2 Lite</string>
+    <string name="devicetype_redmi_smart_band_pro">Redmi 鏅鸿兘鎵嬬挵 Pro</string>
+    <string name="devicetype_redmi_watch_4">Redmi 鎵嬮尪 4</string>
+    <string name="devicetype_redmi_watch_5_active">Redmi Watch 5 Active</string>
+    <string name="devicetype_colmi_r03">Colmi R03</string>
+    <string name="devicetype_colmi_r06">Colmi R06</string>
+    <string name="notification_channel_low_battery_name">浣庨浕閲�</string>
+    <string name="notification_channel_high_priority_name">楂樺劒鍏堢礆</string>
+    <string name="notification_channel_transfer_name">璩囨枡鍌宠几</string>
+    <string name="notification_channel_gps">GPS 杩借工</string>
+    <string name="notification_gps_title">Gadgetbridge GPS</string>
+    <string name="devicetype_sg2">Lemfo SG2</string>
+    <string name="devicetype_hama_fit6900">鍝堢應 Fit6900</string>
+    <string name="menuitem_last_workout">鏈�寰屼竴娆¢崨閸�</string>
+    <string name="menuitem_total_workout">绺介崨閸�</string>
+    <string name="menuitem_vo2_max">鏈�澶ф敐姘ч噺</string>
+    <string name="menuitem_recommendation">寤鸿</string>
+    <string name="menuitem_zepp_coach">Zepp 鏁欑反</string>
+    <string name="menuitem_zepp_pay">Zepp Pay</string>
+    <string name="menuitem_map">鍦板湒</string>
+    <string name="menuitem_heart_rate_push">蹇冪巼鎺ㄩ��</string>
+    <string name="menuitem_wechat_pay">寰俊鏀粯</string>
+    <string name="menuitem_stress">澹撳姏</string>
+    <string name="menuitem_eventreminder">娲诲嫊鎻愰啋</string>
+    <string name="menuitem_worldclock">涓栫晫鏅傞悩</string>
+    <string name="menuitem_findphone">灏嬫壘闆昏┍</string>
+    <string name="menuitem_takephoto">鐩告閬欐帶鍣�</string>
+    <string name="menuitem_alexa">Alexa</string>
+    <string name="menuitem_stopwatch">瑷堟檪鍣�</string>
+    <string name="menuitem_sleep">鐫¤</string>
+    <string name="menuitem_pomodoro">鐣寗閻樿拷韫ゅ櫒</string>
+    <string name="menuitem_temperature">婧害</string>
+    <string name="menuitem_weight">閲嶉噺</string>
+    <string name="menuitem_barometer">姘e瑷�</string>
+    <string name="menuitem_countdown">鍊掓暩</string>
+    <string name="menuitem_personal_activity_intelligence">鍊嬩汉娲诲嫊鏅鸿兘</string>
+    <string name="menuitem_one_tap_measuring">涓�閸垫脯閲�</string>
+    <string name="menuitem_phone">闆昏┍</string>
+    <string name="menuitem_theater_mode">鍔囬櫌妯″紡</string>
+    <string name="menuitem_unsupported">[涓嶆敮鎻碷 %s</string>
+    <string name="zepp_os_watchface_multiple_data">澶氶噸鏁告摎</string>
+    <string name="zepp_os_watchface_rush">鎬ラ��</string>
+    <string name="zepp_os_watchface_minimalist">妤电啊涓荤京</string>
+    <string name="zepp_os_watchface_simplicity_data">绨″柈鏁告摎</string>
+    <string name="zepp_os_watchface_business_style">鍟嗘キ棰ㄦ牸</string>
+    <string name="zepp_os_watchface_free_combination">鑷敱绲勫悎</string>
+    <string name="share_log_not_enabled_message">鎮ㄥ繀闋堥鍏堝湪瑷畾涓暉鐢ㄦ棩瑾岃閷� - 瀵叆鏃ヨ獙鏂囦欢</string>
+    <string name="note">娉ㄦ剰</string>
+    <string name="prefs_fm_preset_instructions">闀锋寜鎸夐垥浠ュ瓨鍎查爯瑷�</string>
+    <string name="toast_app_must_not_be_selected">鎳夌敤绋嬪紡涓嶅緱琚伕鎿囬�茶閰嶇疆</string>
+    <string name="hr_widget_active_minutes">娲昏簫鍒嗛悩</string>
+    <string name="hr_appname_commute">閫氬嫟</string>
+    <string name="pref_title_upper_button_long_press_action">涓婃柟鎸夐垥闀锋寜鎿嶄綔</string>
+    <string name="pref_title_lower_button_function_short">鐭笅閳�</string>
+    <string name="pref_title_middle_button_function_long">涓枔鎸夐垥闀�</string>
+    <string name="pref_title_lower_button_function_double">涓嬫柟鎸夐垥闆欓噸</string>
+    <string name="pref_title_upper_button_function_double">涓婃寜閳曢洐閲�</string>
+    <string name="prefs_button_triple_press_action_selection_title">浜嬩欢 3 琛屽嫊</string>
+    <string name="prefs_button_long_press_action_selection_title">闀锋寜鎸夐垥鎿嶄綔</string>
+    <string name="pref_qhybrid_save_raw_activity_files">淇濆瓨鍘熷娲诲嫊鏂囦欢</string>
+    <string name="hr_widget_last_notification">鏈�寰岄�氱煡</string>
+    <string name="gpx_receiver_activity_title">GPX 鎺ユ敹鍣� Gadgetbridge</string>
+    <string name="text_receiver_activity_title">灏囨枃瀛楃櫦閫佸埌瑷倷</string>
+    <string name="gpx_receiver_files_received">鏀跺埌 GPX 妾旀锛�</string>
+    <string name="gpx_receiver_overwrite_some_files">鏌愪簺妾旀宸茬稉瀛樺湪銆傝瑕嗚搵鍡庯紵</string>
+    <string name="error_background_service">鐒℃硶鍟熷嫊鑳屾櫙鏈嶅嫏</string>
+    <string name="error_background_service_reason_truncated">鍟熷嫊鑳屾櫙鏈嶅嫏澶辨晽锛屽洜鐐衡��</string>
+    <string name="device_unsupported">涓嶆敮鎻�</string>
+    <string name="pref_show_changelog_summary">鍦℅adgetbridge鏇存柊寰岄’绀鸿嚜涓婂�嬬増鏈互渚嗙殑璁婃洿瑷橀寗</string>
+    <string name="error_exporting_device_preferences">灏庡嚭瑷倷鐗瑰畾鍋忓ソ瑷疆鏅傚嚭閷�</string>
+    <string name="steps_avg">姝ユ暩骞冲潎</string>
+    <string name="steps_total">姝ユ暩绺借▓</string>
+    <string name="distance_avg">璺濋洟骞冲潎</string>
+    <string name="distance_total">璺濋洟绺借▓</string>
+    <string name="activeSeconds">娲昏簫</string>
+    <string name="hrZoneFatBurn">鐕冭剛</string>
+    <string name="hrZoneAerobic">鏈夋哀</string>
+    <string name="hrZoneAnaerobic">鍘哀</string>
+    <string name="hrZoneExtreme">妤电</string>
+    <string name="TrainingEffect">瑷撶反鏁堟灉</string>
+    <string name="estimatedSweatLoss">浼拌▓姹楁按鎼嶅け</string>
+    <string name="lactateThresholdHeartRate">涔抽吀闁惧�煎績鐜�</string>
+    <string name="recoveryTime">鎭㈠京鏅傞枔</string>
+    <string name="maxStride">鏈�澶ф骞�</string>
+    <string name="minStride">鎴戠殑姝ヤ紣</string>
+    <string name="averageCadence">骞冲潎韪忛牷</string>
+    <string name="maxCadence">鏈�澶ц笍闋�</string>
+    <string name="minCadence">鏈�灏忚笍闋�</string>
+    <string name="averageStrokeDistance">骞冲潎鎿婄悆璺濋洟</string>
+    <string name="averageStrokesPerSecond">骞冲潎鎿婄悆鏁�</string>
+    <string name="averageLapPace">骞冲潎鍦堥��</string>
+    <string name="swolfMin">鏈�浣巗wolf</string>
+    <string name="laneLength">杌婇亾闀峰害</string>
+    <string name="laps">鍦�</string>
+    <string name="descentSeconds">涓嬮檷</string>
+    <string name="baseAltitude">鍩哄簳楂樺害</string>
+    <string name="averageSpeed">骞冲潎閫熷害</string>
+    <string name="stepRateSum">姝ラ牷绺藉拰</string>
+    <string name="stepRateAvg">骞冲潎姝ラ牷</string>
+    <string name="stepLengthAvg">骞冲潎姝ラ暦</string>
+    <string name="eversionAngleMax">鏈�澶у缈昏</string>
+    <string name="fmtPaceDistance">閰嶉�� %d 璺濋洟</string>
+    <string name="fmtPaceType">姝ヤ紣 %d 椤炲瀷</string>
+    <string name="fmtPacePace">閰嶉�� %d 閰嶉��</string>
+    <string name="fmtPaceCorrection">姝ヤ紣 %d 淇</string>
+    <string name="fmtPaceTypeAverage">姝ラ牷椤炲瀷 %d 骞冲潎</string>
+    <string name="unknownDataEncountered">閬囧埌鏈煡鏁告摎</string>
+    <string name="cyclingPowerAverage">骞冲潎楱庝箻鍔熺巼</string>
+    <string name="cyclingPowerMin">鏈�灏忛◣琛屽姛鐜�</string>
+    <string name="yard">闄㈠瓙</string>
+    <string name="cyclingPowerMax">鏈�澶ч◣琛屽姛鐜�</string>
+    <string name="workout_set_i">瑷疆 %1d</string>
+    <string name="meters_second">绫�/绉�</string>
+    <string name="km_h">鍏噷姣忓皬鏅�</string>
+    <string name="mi_h">绫�/灏忔檪</string>
+    <string name="swim_style">娓告吵棰ㄦ牸</string>
+    <string name="laps_unit">鍦�</string>
+    <string name="Pace">姝ヤ紣</string>
+    <string name="Strokes">涓ⅷ</string>
+    <string name="Activity">娲诲嫊</string>
+    <string name="activity_detail_show_gps_label">椤ず GPS 璺緫</string>
+    <string name="activity_filter_individual_items">鍊嬪垾鎸戦伕鐨勯爡鐩�</string>
+    <string name="prefs_operating_sounds">鎿嶄綔鑱查煶</string>
+    <string name="prefs_fake_ring_duration">鍋囨寔绾岄熆閳�</string>
+    <string name="prefs_autoremove_message">鑷嫊绉婚櫎 SMS 閫氱煡</string>
+    <string name="fossil_hr_commute_processing">铏曠悊璜嬫眰涓��</string>
+    <string name="fossil_hr_synced_activity_data">鍚屾娲诲嫊鏁告摎</string>
+    <string name="fossil_hr_unavailable_unauthed">鍦ㄦ湭缍撹韩浠介璀夌殑妯″紡涓嬩笉鍙敤</string>
+    <string name="fossil_hr_warning_firmware_too_new">鏌愪簺鍔熻兘宸茶绂佺敤锛屽洜鐐烘墜閷剁殑闊岄珨澶柊</string>
+    <string name="qhybrid_summary_file_management">涓婂偝鍜屼笅杓夋獢妗�</string>
+    <string name="pref_title_physical_buttons">瀵﹂珨鎸夐垥</string>
+    <string name="qhybrid_summary_calibration">鏍℃簴鎵嬮尪鎸囬嚌</string>
+    <string name="pref_summary_canned_messages_dismisscall">鐢ㄧ煭淇′俊鎭嫆绲曚締鑷墜閷剁殑渚嗛浕</string>
+    <string name="fossil_hr_edit_action">绶ㄨ集鍕曚綔</string>
+    <string name="fossil_hr_new_action_cancel">鍙栨秷</string>
+    <string name="qhybrid_calibration_clockwise">闋嗘檪閲�</string>
+    <string name="qhybrid_calibration_1_step">涓�姝�</string>
+    <string name="pref_summary_developer_settings">闁嬬櫦浜哄摗浣跨敤鐨勮ō缃拰鍔熻兘</string>
+    <string name="qhybrid_pref_title_external_intents">鍏佽ū鍗遍毆鐨勫閮ㄦ剰鍦�</string>
+    <string name="qhybrid_pref_summary_external_intents">鍏佽ū鍏朵粬Android鎳夌敤绋嬪簭涓婂偝/瑕嗚搵鏂囦欢</string>
+    <string name="appmanager_app_edit">绶ㄨ集</string>
+    <string name="watchface_dialog_widget_cat_generic">閫氱敤</string>
+    <string name="watchface_dialog_widget_cat_position">鑱蜂綅</string>
+    <string name="watchface_dialog_widget_cat_2nd_tz_widget">绗簩鏅傚崁灏忓伐鍏�</string>
+    <string name="watchface_dialog_widget_cat_custom_widget">鑷▊灏忓伐鍏�</string>
+    <string name="watchface_dialog_widget_type">椤炲瀷</string>
+    <string name="watchface_dialog_widget_y_coordinate">Y搴ф锛堟渶澶�240锛�</string>
+    <string name="watchface_dialog_widget_preset_top">闋傞儴</string>
+    <string name="watchface_dialog_widget_preset_bottom">搴曢儴</string>
+    <string name="watchface_dialog_widget_preset_left">宸�</string>
+    <string name="watchface_dialog_widget_preset_right">鍙�</string>
+    <string name="watchface_dialog_widget_color">椤忚壊</string>
+    <string name="watchface_dialog_title_widget">灏忓伐鍏疯ō瀹�</string>
+    <string name="watchface_dialog_widget_width">灏忓伐鍏峰搴︼紙浠ュ儚绱犵偤鍠綅锛�</string>
+    <string name="watchface_setting_title_display_refresh_timeout">椤ず鍒锋柊瓒呮檪</string>
+    <string name="watchface_setting_display_refresh_full">瀹屽叏鍒锋柊锛堜互鍒嗛悩瑷堬級</string>
+    <string name="watchface_setting_display_refresh_partial">閮ㄥ垎鍒锋柊锛堜互鍒嗛悩瑷堬級</string>
+    <string name="watchface_setting_wrist_flick_minute">鍒嗛悩閲� (-360 鍒� 360)</string>
+    <string name="watchface_setting_button_toggle_widgets">鍒囨彌灏忓伐鍏�</string>
+    <string name="watchface_setting_button_toggle_backlight">鎵撻枊鑳屽厜</string>
+    <string name="watchface_setting_button_move_hands">绉诲嫊鎵�</string>
+    <string name="watchface_cache_confirm_overwrite">宸茬稉瀛樺湪涓�鍊嬪悕鐐烘鐨勯尪闈㈠湪蹇彇涓�傛偍鎯宠瑕嗚搵瀹冨棊锛�</string>
+    <string name="watchface_setting_title_power_saving">鐪侀浕</string>
+    <string name="watchface_upload_failed">涓婂偝閷堕潰澶辨晽銆傝珛鍐嶈│涓�娆°��</string>
+    <string name="watchface_widget_type_2nd_tz">绗簩鏅傚崁</string>
+    <string name="watchface_widget_type_chance_rain">闄嶉洦姗熺巼</string>
+    <string name="watchface_dialog_widget_background">鑳屾櫙</string>
+    <string name="hybridhr_widget_bg_thin_circle">绱板湏</string>
+    <string name="hybridhr_widget_bg_double_circle">闆欏湏</string>
+    <string name="hybridhr_widget_bg_dashed_circle">铏涚窔鍦撳湀</string>
+    <string name="prefs_sleep_time_summary">鎸囧畾瑷诲唺鐫$湢鐨勬檪闁�</string>
+    <string name="prefs_notifications_enable">鍟熺敤閫氱煡</string>
+    <string name="enable_vibrations_summary">渚嗛浕銆佽▕鎭�侀�氱煡绛夌殑闇囧嫊</string>
+    <string name="prefs_autoheartrate_interval">娓噺闋荤巼</string>
+    <string name="enable_notifications_summary">渚嗛浕銆佽▕鎭強鏇村閫氱煡</string>
+    <string name="prefs_autoheartrate">鑷嫊蹇冪巼</string>
+    <string name="prefs_autoheartrate_summary">鐧藉ぉ鍙婄潯鐪犳湡闁撶殑瀹氭湡蹇冪巼娓噺</string>
+    <string name="prefs_autoheartrate_measurement">鑷嫊蹇冪巼娓噺</string>
+    <string name="prefs_autoheartrate_sleep">鍦ㄧ潯鐪犳湡闁撻�茶娓噺</string>
+    <string name="devicetype_nothingear1">Nothing Ear (1)</string>
+    <string name="devicetype_nothing_cmf_watch_pro_2">CMF 鎵嬮尪灏堟キ鐗� 2</string>
+    <string name="devicetype_galaxybuds_live">Galaxy Buds Live</string>
+    <string name="prefs_in_ear_detection_summary">鐣惰�虫鍦ㄤ綘鑰虫湹瑁℃檪锛岄�氶亷鑰虫鎾斁閫氳┍</string>
+    <string name="nothing_prefs_audiomode_title">闊宠▕妯″紡</string>
+    <string name="prefs_equalizer_preset">鍧囪 鍣ㄩ爯瑷�</string>
+    <string name="pref_title_equalizer_dynamic">鍕曟厠</string>
+    <string name="pref_title_equalizer_trebble">楂橀煶澧炲挤</string>
+    <string name="prefs_dolby_mode">鏉滄瘮妯″紡</string>
+    <string name="prefs_equalizer_summary">鍟熺敤鎴栫鐢ㄥ潎琛″櫒</string>
+    <string name="prefs_dolby_summary">鏉滄瘮鍧囪 鍣ㄩ爯瑷�</string>
+    <string name="prefs_touch_lock">瑙告帶閹�</string>
+    <string name="prefs_touch_lock_buds2">瑙告帶鎺у埗</string>
+    <string name="prefs_galaxy_buds_experimental">瀵﹂鐨�</string>
+    <string name="prefs_seamless_connection_switch_title">鐒$斧閫f帴鍒囨彌</string>
+    <string name="prefs_seamless_connection_switch_summary">鑷嫊鍦ㄩ厤灏嶈ō鍌欎箣闁撳垏鎻涜�冲</string>
+    <string name="prefs_ambient_voice_summary">璁撹伈闊宠劔绌庤�屽嚭</string>
+    <string name="prefs_ambient_sound">鐠板鑱查煶</string>
+    <string name="prefs_customize_ambient_sound_summary">鑷▊鐠板鑱查煶</string>
+    <string name="prefs_active_noise_cancelling_transparency">閫忔槑搴�</string>
+    <string name="prefs_active_noise_cancelling_level_high">楂�</string>
+    <string name="prefs_active_noise_cancelling_level_low">浣�</string>
+    <string name="pref_switch_noise_control">鍒囨彌鍣煶鎺у埗</string>
+    <string name="prefs_noise_control_with_one_earbud">鍠�宠�虫鐨勫櫔闊虫帶鍒�</string>
+    <string name="prefs_noise_control_with_one_earbud_summary">鐣跺彧浣跨敤涓�鍊嬭�虫鏅傚厑瑷卞櫔闊虫帶鍒�</string>
+    <string name="pref_ambient_sound_tone">鐠板鑱查煶闊宠</string>
+    <string name="pref_balance">骞宠 </string>
+    <string name="pref_switch_controls_anc_ambient">闄嶅櫔 鈫愨啋 鐠板</string>
+    <string name="pref_switch_controls_anc_off">鍣煶娑堥櫎 鈫愨啋 闂滈枆</string>
+    <string name="pref_switch_controls_ambient_off">鐠板 鈫愨啋 闂�</string>
+    <string name="pref_switch_controls_anc_ambient_off">闄嶅櫔 鈫愨啋 鐠扮篂闊� 鈫愨啋 闂�</string>
+    <string name="prefs_voice_detect">瑾為煶妾㈡脯</string>
+    <string name="prefs_voice_detect_summary">鍟熺敤鐠板鑱查煶锛屼甫鍦ㄦ娓埌鑱查煶寰岃嚜鍕曢檷浣庢挱鏀鹃煶閲�</string>
+    <string name="prefs_double_tap_edge_summary">鍗充娇鏈湪瑙告帶鏉夸笂榛炴搳锛屼篃鑳芥娓洐鎿�</string>
+    <string name="pref_header_heartrate_sleep">鐫¤</string>
+    <string name="pref_header_heartrate_alerts">蹇冪巼璀﹀牨</string>
+    <string name="pref_header_stress">澹撳姏</string>
+    <string name="pref_header_sony_sound_control">闊抽噺鎺у埗</string>
+    <string name="pref_header_spo2">琛�姘�</string>
+    <string name="pref_header_hrv_status">HRV 鐙�鎱�</string>
+    <string name="body_energy">韬珨鑳介噺</string>
+    <string name="vo2max_running">璺戞鐨� VO鈧� 鏈�澶у��</string>
+    <string name="vo2max_cycling">楱庤嚜琛岃粖鐨勬渶澶ф敐姘ч噺</string>
+    <string name="pref_header_sony_ambient_sound_control">鐠板鑱查煶鎺у埗</string>
+    <string name="stress_relaxed">鏀鹃瑔</string>
+    <string name="stress_mild">婧拰</string>
+    <string name="stress_moderate">閬╁害</string>
+    <string name="stress_high">楂�</string>
+    <string name="pai_total">绺借▓</string>
+    <string name="pai_day">鏃ュ</string>
+    <string name="target">鐩</string>
+    <string name="sony_ambient_sound">妯″紡</string>
+    <string name="sony_ambient_sound_off">闂�</string>
+    <string name="sony_ambient_sound_noise_cancelling">鍣煶鍙栨秷</string>
+    <string name="sony_ambient_sound_wind_noise_reduction">棰ㄥ櫔闊虫笡灏�</string>
+    <string name="sony_ambient_sound_ambient_sound">鐠板鑱查煶</string>
+    <string name="sony_sound_position">鑱查煶浣嶇疆</string>
+    <string name="sony_sound_position_off">闂�</string>
+    <string name="sony_sound_position_front">鍓嶉潰</string>
+    <string name="sony_sound_position_front_right">鍓嶅彸</string>
+    <string name="sony_sound_position_rear_left">宸﹀緦</string>
+    <string name="sony_surround_mode_arena">绔舵妧鍫�</string>
+    <string name="sony_surround_mode_club">淇辨▊閮�</string>
+    <string name="sony_surround_mode_outdoor_stage">鎴跺鑸炲彴</string>
+    <string name="sony_surround_mode_concert_hall">闊虫▊寤�</string>
+    <string name="sony_warn_sbc_codec">璀﹀憡锛氬潎琛″櫒銆侀煶闋讳綅缃拰鐠扮篂瑷疆鍍呴仼鐢ㄦ柤SBC闊抽牷绶ㄨВ纰煎櫒銆�</string>
+    <string name="sony_equalizer">鍧囪 鍣�</string>
+    <string name="sony_anc_optimizer_status_finished">瀹屾垚涓��</string>
+    <string name="sony_equalizer_preset_mellow">鏌斿拰</string>
+    <string name="sony_equalizer_preset_relaxed">鏀鹃瑔</string>
+    <string name="sony_equalizer_preset_vocal">鑱查煶</string>
+    <string name="sony_equalizer_preset_custom_1">鑷▊ 1</string>
+    <string name="sony_equalizer_preset_custom_2">鑷▊ 2</string>
+    <string name="pref_header_sony_equalizer_bands">妯傞殜</string>
+    <string name="pref_header_sony_equalizer_preset_custom_2">鑷▊闋愯ō 2</string>
+    <string name="sony_equalizer_band_2500">2.5鍗�</string>
+    <string name="sony_equalizer_band_16000">16k</string>
+    <string name="sony_equalizer_clear_bass">娓呮櫚浣庨煶</string>
+    <string name="sony_audio_upsampling">闊抽牷鍗囬牷</string>
+    <string name="sony_speak_to_chat_summary">鐣朵綘闁嬪璎涜┍鏅傝嚜鍕曢棞闁夊櫔闊虫秷闄ゅ姛鑳姐��</string>
+    <string name="sony_speak_to_chat_sensitivity">瑾為煶妾㈡脯闈堟晱搴�</string>
+    <string name="sony_speak_to_chat_sensitivity_auto">鑷嫊</string>
+    <string name="sony_speak_to_chat_sensitivity_standard">妯欐簴</string>
+    <string name="sony_speak_to_chat_focus_on_voice">灏堟敞鏂艰伈闊�</string>
+    <string name="sony_speak_to_chat_timeout">瓒呮檪</string>
+    <string name="sony_speak_to_chat_timeout_off">闂�</string>
+    <string name="sony_speak_to_chat_timeout_long">闀� (1绫�)</string>
+    <string name="sony_connect_two_devices">鍚屾檪閫f帴鍏╁�嬭ō鍌�</string>
+    <string name="sony_notification_voice_guide">閫氱煡鑸囪獮闊虫寚鍗�</string>
+    <string name="sony_automatic_power_off">鑷嫊闂滄</string>
+    <string name="sony_automatic_power_off_5_min">5鍒嗛悩</string>
+    <string name="sony_automatic_power_off_1_hour">1 灏忔檪</string>
+    <string name="sony_automatic_power_off_3_hour">3灏忔檪</string>
+    <string name="sony_button_mode_left">鎸夐垥妯″紡锛堝乏锛�</string>
+    <string name="sony_button_mode_playback_control">鎾斁鎺у埗</string>
+    <string name="sony_automatic_power_off_when_taken_off">鐣惰鍙栦笅鏅�</string>
+    <string name="sony_pause_when_taken_off">鎽樹笅鑰虫鏅傛毇鍋�</string>
+    <string name="sony_button_mode_volume_control">闊抽噺鎺у埗</string>
+    <string name="sony_ambient_sound_control_button_modes">鐠板闊虫晥鎺у埗鎸夐垥妯″紡</string>
+    <string name="sony_ambient_sound_control_button_mode_nc_as">涓诲嫊鍣煶鍙栨秷锛岀挵澧冭伈闊�</string>
+    <string name="sony_protocol_v1">鐗堟湰 1</string>
+    <string name="sony_ambient_sound_control_button_mode_nc_off">闄嶅櫔锛岄棞闁�</string>
+    <string name="sony_ambient_sound_control_button_mode_as_off">鐠板鑱查煶锛岄棞闁�</string>
+    <string name="sony_quick_access_triple_tap">蹇�熻í鍟忥紙涓夋杓曡Ц锛�</string>
+    <string name="sony_protocol_v2">鐗堟湰 2</string>
+    <string name="sony_protocol_v3">鐗堟湰 3</string>
+    <string name="moondrop_equalizer_preset_reference">鍙冭��</string>
+    <string name="moondrop_equalizer_preset_basshead">浣庨煶杩�</string>
+    <string name="moondrop_equalizer_preset_monitor">鐩h鍣�</string>
+    <string name="moondrop_touch_trigger">瑙哥櫦</string>
+    <string name="moondrop_touch_earbud_both">鍏╄��</string>
+    <string name="moondrop_touch_action_play_pause">鎾斁/鏆仠</string>
+    <string name="moondrop_touch_action_call_pick_hang">閬告搰/鎺涙柗闆昏┍</string>
+    <string name="moondrop_touch_action_call_start">闁嬪閫氳┍</string>
+    <string name="moondrop_touch_action_assistant">瑙哥櫦瑾為煶鍔╂墜</string>
+    <string name="moondrop_touch_action_anc_mode">鍒囨彌涓诲嫊鍣煶鍙栨秷妯″紡</string>
+    <string name="moondrop_touch_trigger_long_press_1s">闀锋寜 (1绉�)</string>
+    <string name="soundcore_button_brightness">鎸夐垥浜害</string>
+    <string name="soundcore_button_brightness_medium">涓瓑</string>
+    <string name="soundcore_equalizer_preset_xtra_bass">瓒呴噸浣庨煶</string>
+    <string name="soundcore_equalizer_preset_balanced">骞宠 鐨�</string>
+    <string name="soundcore_equalizer_custom_title">鑷▊鈥�</string>
+    <string name="soundcore_equalizer_custom_summary">閰嶇疆鍙冩暩鍧囪 鍣ㄨō缃�</string>
+    <string name="soundcore_equalizer_direction_lying">鎾掕瑠</string>
+    <string name="soundcore_equalizer_direction_hanging">鎳告帥</string>
+    <string name="soundcore_equalizer_band2">绗簩绲�</string>
+    <string name="soundcore_equalizer_band3">绗笁绱�</string>
+    <string name="soundcore_equalizer_band5">绗簲绱�</string>
+    <string name="soundcore_equalizer_band9">绗節绱�</string>
+    <string name="soundcore_equalizer_frequency">闋荤巼</string>
+    <string name="soundcore_equalizer_value">鍍瑰��</string>
+    <string name="miscale_weight_unit_title">閲嶉噺鍠綅</string>
+    <string name="miscale_weight_unit_metric">鍏埗 (鍏枻)</string>
+    <string name="miscale_weight_unit_imperial">鑻辩 (lbs)</string>
+    <string name="miscale_weight_unit_chinese">绻侀珨涓枃</string>
+    <string name="miscale_small_objects_title">灏忕墿浠�</string>
+    <string name="miscale_small_objects_summary">鍎插瓨閲嶉噺浣庢柤10鍏枻鐨勭墿楂�</string>
+    <string name="protocol_version">鍗旇鐗堟湰</string>
+    <string name="pref_screen_auto_brightness_title">鑷嫊浜害</string>
+    <string name="pref_screen_auto_brightness_summary">鏍规摎鐠板鍏夎鏁磋灑骞曚寒搴�</string>
+    <string name="pref_screen_brightness">铻㈠箷浜害</string>
+    <string name="quick_attention">蹇�熸敞鎰�</string>
+    <string name="watchface_widget_type_custom">鑷▊灏忓伐鍏�</string>
+    <string name="watchface_dialog_widget_timezone">鏅傚崁</string>
+    <string name="watchface_dialog_widget_timezone_duration">鏅傞悩鍙鎸佺簩鏅傞枔锛堜互绉掔偤鍠綅锛�</string>
+    <string name="watchface_dialog_widget_timeout_hide_text">鍦ㄨ秴鏅傛檪闅辫棌鏂囨湰</string>
+    <string name="qhybrid_title_on_device_confirmation">鍟熺敤瑷倷涓婄殑閰嶅皪纰鸿獚</string>
+    <string name="watchface_dialog_widget_timeout_show_circle">瓒呮檪椤ず鍦撳湀</string>
+    <string name="qhybrid_summary_on_device_confirmation">瑷倷涓婄殑閰嶅皪纰鸿獚鍙兘鏈冭畵浜烘劅鍒扮叐鎯便�傜鐢ㄥ畠鍊戝彲鑳芥渻澶卞幓鏌愪簺鍔熻兘銆�</string>
+    <string name="devicetype_bose_qc35">Bose QC35</string>
+    <string name="withings_steel_hr">Withings Steel HR</string>
+    <string name="pref_button_action_disabled">娈樼柧</string>
+    <string name="pref_media_play">濯掗珨鎾斁</string>
+    <string name="pref_media_pause">濯掗珨鏆仠</string>
+    <string name="pref_media_playpause">鍒囨彌鎾斁</string>
+    <string name="pref_media_next">涓嬩竴棣�</string>
+    <string name="pref_device_action_broadcast">鐧奸�佸唬鎾�</string>
+    <string name="pref_device_action_fitness_app_control_start">鍋ヨ韩鎳夌敤绋嬪紡杩借工闁嬪</string>
+    <string name="pref_device_action_fitness_app_control_stop">鍋ヨ韩鎳夌敤绋嬪紡杩借工鍋滄</string>
+    <string name="pref_device_action_fitness_app_control_toggle">鍒囨彌鍋ヨ韩鎳夌敤绋嬪紡杩借工</string>
+    <string name="distance_format_kilometers">###.#鍏噷</string>
+    <string name="distance_format_miles">###.#绫�</string>
+    <string name="distance_format_feet">###ft</string>
+    <string name="huawei_alarm_smart_description">璜嬪嬁鍙栨秷鍕鹃伕鏅鸿兘鍠氶啋閬搁爡銆�</string>
+    <string name="fossil_hr_button_config_info">鏌愪簺鎸夐垥鐒℃硶閰嶇疆锛屽洜鐐哄畠鍊戠殑鍔熻兘鏄‖绶ㄧ⒓鍦ㄦ墜閷剁殑鍥轰欢涓��
+\n
+\n璀﹀憡锛氱暥瀹夎浜嗗畼鏂� Fossil 鎳夌敤绋嬪簭鐨勯尪闈㈡檪锛岄暦鎸変笂鏂规寜閳曚篃鏈冨湪椤ず/闅辫棌灏忛儴浠朵箣闁撳垏鎻涖��</string>
+    <string name="pref_title_navigation_forward">鐧奸�佸皫鑸埌鎵嬮尪</string>
+    <string name="pref_enable_call_accept_summary">鍟熺敤寰炶ō鍌欐帴鑱介浕瑭�</string>
+    <string name="pref_enable_call_reject">鍟熺敤鎷掓帴渚嗛浕</string>
+    <string name="pref_enable_call_reject_summary">鍟熺敤鎷掔禃渚嗛浕鐨勮ō鍌�</string>
+    <string name="pref_disable_find_phone_with_dnd">鐣跺嬁鎿炬ā寮忓暉鐢ㄦ檪绂佺敤灏嬫壘鎴戠殑鎵嬫</string>
+    <string name="pref_spo_automatic_enable">鍟熺敤鑷嫊SpO2娓噺</string>
+    <string name="pref_force_options">寮峰埗閬搁爡</string>
+    <string name="pref_force_options_summary">鏌愪簺瑷倷閷鍦拌伈绋变笉鏀寔鏌愪簺閬搁爡銆傞�欎簺瑷疆鍙互鐢ㄤ締寮疯鍟熺敤瀹冨�戙��
+\n鑷摂棰ㄩ毆浣跨敤
+\n璜嬮柋璁�缍熀</string>
+    <string name="pref_force_smart_alarm">寮峰埗鏅鸿兘璀﹀牨</string>
+    <string name="pref_force_smart_alarm_summary">寮峰埗鏅鸿兘璀﹀牨鏀彺銆�
+\n浣跨敤棰ㄩ毆鑷矤</string>
+    <string name="pref_force_wear_location">寮峰埗浣╂埓浣嶇疆</string>
+    <string name="pref_force_wear_location_summary">寮峰埗绌挎埓浣嶇疆鏀彺銆�
+\n浣跨敤棰ㄩ毆鑷矤</string>
+    <string name="pref_force_dnd_support">寮峰埗鍕挎摼鏀彺</string>
+    <string name="pref_force_enable_heartrate_support">寮峰寲蹇冪巼鏀寔</string>
+    <string name="pref_force_enable_heartrate_support_summary">寮峰埗鍟熺敤蹇冪巼鏀寔銆�
+\n浣跨敤棰ㄩ毆鑷矤</string>
+    <string name="huawei_ignore_wakeup_status_start_description">鍙兘鏈夊姪鏂兼纰虹殑鐫$湢妾㈡脯銆傚湪姣忔棩娲诲嫊瑕栧湒涓珛鍗冲彲瑕嬨��</string>
+    <string name="pref_force_dnd_support_summary">寮峰埗鍕挎摼鏀寔銆�
+\n浣跨敤棰ㄩ毆鑷矤</string>
+    <string name="huawei_ignore_wakeup_status_end_description">鍙兘鏈夊姪鏂兼纰虹殑鐫$湢妾㈡脯銆傚湪姣忔棩娲诲嫊瑕栧湒涓珛鍗冲彲瑕嬨��</string>
+    <string name="huawei_reparse_workout_data">閲嶆柊瑙f瀽閬嬪嫊鏁告摎</string>
+    <string name="huawei_reparse_workout_data_description">閫欏彧鏈夊湪鏌愪簺鏇存柊寰屾墠鏈冩湁浣滅敤</string>
+    <string name="info_no_devices_connected">娌掓湁瑷倷閫f帴</string>
+    <string name="controlcenter_set_parent_folder">瑷疆鐖舵枃浠跺ぞ</string>
+    <string name="controlcenter_set_preferences">瑷疆鍋忓ソ</string>
+    <string name="controlcenter_connected_fraction">宸查�f帴: %d/%d</string>
+    <string name="error_setting_parent_folder">瑷疆鐖舵枃浠跺ぞ鏅傚嚭閷�: %s</string>
+    <string name="error_deleting_device">鍒櫎瑷倷鏅傚嚭閷�: %s</string>
+    <string name="controlcenter_folder_name">璩囨枡澶惧悕绋憋細</string>
+    <string name="controlcenter_set_folder_title">瑷疆鎴栧壍寤烘柊鏂囦欢澶�</string>
+    <string name="auto_reconnect_ble_summary">涓诲嫊瀹氭湡鍢楄│閫f帴瑷倷</string>
+    <string name="connection_over_ble">閫氶亷BLE閫f帴</string>
+    <string name="connection_over_bt_classic">閫氶亷钘嶇墮缍撳吀閫f帴</string>
+    <string name="autoconnect_from_device_title">寰炶ō鍌欓�f帴鐨勯�f帴</string>
+    <string name="autoconnect_from_device_summary">鐣惰ō鍌欙紙濡傝�虫锛夌櫦璧烽�f帴鏅傦紝寤虹珛閫f帴</string>
+    <string name="steps_streaks">姝ラ姊濈磱</string>
+    <string name="steps_streaks_hint">閫g簩骞惧ぉ涓嶉枔鏂峰湴閬旀垚姝ユ暩鐩</string>
+    <string name="step_streak_longest">鏈�闀�</string>
+    <string name="steps_streaks_achievement_rate">鎴愬氨鐜�</string>
+    <string name="steps_streaks_since_date">鑷� %s</string>
+    <string name="step_streak_average_steps_hint">姣忔棩骞冲潎姝ユ暩</string>
+    <string name="prefs_hourly_chime">姣忓皬鏅傚牨鏅�</string>
+    <string name="prefs_hourly_chime_summary">鎵嬮尪姣忓皬鏅傛渻鐧煎嚭涓�娆″椂鑱�</string>
+    <string name="activity_prefs_allow_bluetooth_intent_api">钘嶇墮鎰忓湒API</string>
+    <string name="activity_prefs_summary_allow_bluetooth_intent_api">鍏佽ū閫氶亷 Intent API 鎺у埗钘嶇墮閫f帴</string>
+    <string name="intent_api_allow_activity_sync_title">鍏佽ū娲诲嫊鍚屾瑙哥櫦</string>
+    <string name="intent_api_allow_activity_sync_summary">鍏佽ū閫氶亷 Intent API 瑙哥櫦娲诲嫊鍚屾</string>
+    <string name="intent_api_allow_trigger_export_title">鍏佽ū鏁告摎搴皫鍑�</string>
+    <string name="wifi_hotspot_start_summary">鍦ㄦ墜閷朵笂鍟熷嫊 Wi-Fi 鐔遍粸</string>
+    <string name="wifi_hotspot_stop_summary">鍋滄鎵嬮尪涓婄殑 Wi-Fi 鐔遍粸</string>
+    <string name="ftp_server_summary">鎺у埗鎵嬮尪涓婄殑FTP浼烘湇鍣�</string>
+    <string name="ftp_server_start_summary">鍦ㄦ墜閷朵笂鍟熷嫊FTP浼烘湇鍣�</string>
+    <string name="ftp_server_stop_summary">鍋滄鎵嬮尪涓婄殑FTP浼烘湇鍣�</string>
+    <string name="ftp_server_configuration">FTP 浼烘湇鍣ㄩ厤缃�</string>
+    <string name="ftp_server_root_dir">鏍圭洰閷�</string>
+    <string name="fossil_hr_confirm_connection">璜嬪湪鎮ㄧ殑鎵嬮尪涓婄⒑瑾�</string>
+    <string name="fossil_hr_connection_not_confirmed">鎵嬮尪涓婃湭纰鸿獚閫f帴锛屼娇鐢ㄦ湭缍撹韩浠介璀夋ā寮�</string>
+    <string name="fossil_hr_pairing_failed">閰嶅皪鎵嬮尪澶辨晽</string>
+    <string name="fossil_hr_confirmation_skipped">璺抽亷瑷倷涓婄殑纰鸿獚</string>
+    <string name="fossil_hr_confirmation_timeout">纰鸿獚瓒呮檪锛岀辜绾�</string>
+    <string name="debug_companion_show_associated">椤ず鐩搁棞鐨勪即闅ㄨō鍌�</string>
+    <string name="contact_missing_number">鑱怠闆昏┍鐐虹┖</string>
+    <string name="pref_app_logs_title">鎳夌敤绋嬪簭鏃ヨ獙</string>
+    <string name="changelog_show_full">鏇村鈥�</string>
+    <string name="changelog_ok_button">濂�</string>
+    <string name="loyalty_cards_catima_package">Catima 濂椾欢鍚嶇ū</string>
+    <string name="loyalty_cards_install">瀹夎 Catima</string>
+    <string name="loyalty_cards_sync_groups_only">鍍呭悓姝ョ壒瀹氱兢绲�</string>
+    <string name="loyalty_cards_sync_archived">鍚屾宸插瓨妾旂殑鍗$墖</string>
+    <string name="loyalty_cards_install_catima_fail">鐒℃硶鎵撻枊鎳夌敤鍟嗗簵浠ュ畨瑁� Catima</string>
+    <string name="loyalty_cards_sync_summary">榛炴搳浠ュ悓姝ュ崱鐗囪嚦鎵嬮尪</string>
+    <string name="loyalty_cards_catima_not_installed">Catima 闇�瑕佺敤渚嗙鐞嗗繝瑾犲崱</string>
+    <string name="loyalty_cards_catima_permissions_summary">Gadgetbridge 闇�瑕佸皪 Catima 鍗$墖鐨勮畝鍙栨瑠闄愭墠鑳藉悓姝ュ畠鍊戙�傞粸鎿婃鎸夐垥浠ユ巿浜堟瑠闄愩��</string>
+    <string name="pref_theme_dynamic_colors_explanation">娉ㄦ剰锛氬皪鏂煎嫊鎱嬮鑹蹭富椤岋紝鎮ㄩ渶瑕佸湪 Android 12 浠ヤ笂鐨勫瑙�瑷疆涓暉鐢ㄥ绱欓鑹叉垨椤忚壊瑾胯壊鏉裤�傚鏋滀笉鍟熺敤锛孏adgetbridge 灏囦娇鐢ㄩ粯瑾嶇殑 Material 3 椤忚壊銆�</string>
+    <string name="prefs_home_icon_left_item">宸�</string>
+    <string name="prefs_wena3_vibration_smart_item">鏅烘収闇囧嫊</string>
+    <string name="prefs_wena3_hint_weather_statusbar">鍦ㄤ富灞忓箷鐨勫乏涓婅椤ず鐣跺墠姊濅欢鍦栨</string>
+    <string name="prefs_wena3_vibration_strength_item">鎸嫊寮峰害</string>
+    <string name="prefs_wena3_vibration_strength_item_weak">寮�</string>
+    <string name="prefs_wena3_vibration_strength_item_medium">涓瓑</string>
+    <string name="prefs_wena3_home_icon_title">涓荤暙闈㈠湒绀�</string>
+    <string name="prefs_wena3_home_icon_name_qrio">Qrio</string>
+    <string name="prefs_wena3_home_icon_name_clock">鐣跺墠鏅傞枔</string>
+    <string name="prefs_wena3_status_page_title">鐙�鎱嬮爜闈㈣▊璩�</string>
+    <string name="prefs_wena3_day_start_hour_item">鏃ラ枊濮嬫柤</string>
+    <string name="prefs_wena3_auto_power_off_item">鑷嫊闂滄</string>
+    <string name="prefs_wena3_auto_power_off_turn_on_time_item">鎭㈠京鏅傞枔</string>
+    <string name="prefs_wena3_button_action_item">鎸夐垥鍕曚綔</string>
+    <string name="prefs_wena3_button_action_item_double">闆欐搳</string>
+    <string name="prefs_wena3_button_action_name_qrio_unlock">Qrio 瑙i帠</string>
+    <string name="prefs_wena3_button_action_name_activity_screen">娲诲嫊铻㈠箷</string>
+    <string name="prefs_wena3_receive_calls_item">鎺ユ敹渚嗛浕閫氱煡</string>
+    <string name="prefs_wena3_receive_calls_hint">濡傛灉闂滈枆锛屾偍灏囦笉鏈冩敹鍒� Wena 涓婄殑渚嗛浕閫氱煡</string>
+    <string name="prefs_wena3_notification_default_vibration">閫氱煡闇囧嫊</string>
+    <string name="prefs_wena3_notification_default_vibration_repetition">閫氱煡闇囧嫊閲嶈</string>
+    <string name="prefs_wena3_notification_vibration_repetition_0">鐒¢檺鏈�</string>
+    <string name="prefs_wena3_notification_vibration_repetition_1">涓�娆�</string>
+    <string name="prefs_wena3_notification_vibration_repetition_2">鍏╂</string>
+    <string name="prefs_wena3_notification_vibration_repetition_3">涓夋</string>
+    <string name="prefs_wena3_notification_vibration_repetition_4">鍥涙</string>
+    <string name="prefs_wena3_notification_per_app_settings_title">姣忓�嬫噳鐢ㄧ▼寮忕殑閫氱煡瑷畾</string>
+    <string name="prefs_wena3_notification_default_call_vibration_repetition">渚嗛浕闇囧嫊閲嶈</string>
+    <string name="prefs_wena3_notification_default_call_led">渚嗛浕 LED 椤忚壊</string>
+    <string name="prefs_wena3_vibration_none">鐒¢渿鍕�</string>
+    <string name="prefs_wena3_vibration_continuous">鎸佺簩鐨�</string>
+    <string name="prefs_wena3_vibration_basic">鍩烘湰</string>
+    <string name="prefs_wena3_vibration_rapid">蹇��</string>
+    <string name="prefs_wena3_vibration_triple">涓夐噸</string>
+    <string name="pref_call_rejection_method_summary">鐣朵締闆诲緸鎵嬮尪琚嫆绲曟檪锛屾渻鎺″彇浠�楹艰鍕曪紵</string>
+    <string name="devicetype_mi_watch_color_sport">绫冲閬嬪嫊鎵嬮尪</string>
+    <string name="devicetype_pixoo">Pixoo</string>
+    <string name="not_set">鏈ō瀹�</string>
+    <string name="widget_screen">灏忓伐鍏疯灑骞�</string>
+    <string name="widget_screen_delete_confirm_title">鍒櫎灏忓伐鍏疯灑骞�</string>
+    <string name="widget_screen_no_free_slots_description">瑭茶ō鍌欐矑鏈夊彲鐢ㄧ殑绐楀彛灞忓箷鎻掓Ы锛堢附鎻掓Ы鏁革細%1$s锛�</string>
+    <string name="widget_screen_min_screens">蹇呴爤鑷冲皯鏈� %1$s 鍊嬭灑骞�</string>
+    <string name="widget_layout_top_1_bot_2">1浠朵笂琛o紝2姊濊げ瀛�</string>
+    <string name="widget_layout_top_2_bot_1">2 浠朵笂琛o紝1 浠朵笅琛�</string>
+    <string name="widget_layout_top_2_bot_2">2 涓�, 2 涓�</string>
+    <string name="widget_layout_single">1 鍊嬪皬宸ュ叿</string>
+    <string name="pref_summary_fossil_hr_navigation_instructions">閰嶇疆鎵嬮尪涓婄殑灏庤埅鎳夌敤绋嬪紡琛岀偤</string>
+    <string name="uploading_watchface">涓婂偝閷堕潰鈥�</string>
+    <string name="uploadwatchfaceoperation_in_progress">涓婂偝閷堕潰</string>
+    <string name="uploadwatchfaceoperation_complete">閷堕潰瀹夎瀹屾垚</string>
+    <string name="uploadwatchfaceoperation_failed">琛ㄩ潰瀹夎澶辨晽</string>
+    <string name="pref_force_connection_type_title">寮峰埗閫f帴椤炲瀷</string>
+    <string name="pref_force_connection_type_description">濡傛灉鎮ㄧ殑瑷倷灏� Gadgetbridge 娌掓湁鍙嶆噳锛屾偍鍙互鍢楄│寮峰埗瑷疆閫f帴椤炲瀷銆�</string>
+    <string name="auto_reconnect_ble_scan_summary">绛夊緟瑁濈疆鎺冩弿锛岃�屼笉鏄洸鐩殑閫f帴鍢楄│</string>
+    <string name="prompt_restart_gadgetbridge">璜嬮噸鏂板暉鍕旼B浠ヤ娇鍏剁敓鏁堛��</string>
+    <string name="waiting_for_bluetooth">绛夊緟钘嶇墮鈥�</string>
+    <string name="error_scan_failed">鎺冩弿澶辨晽锛�%d</string>
+    <string name="scan_not_scanning">鏈巸鎻�</string>
+    <string name="scan_scanning_single_device">鎺冩弿 1 鍊嬭ō鍌�</string>
+    <string name="scan_scanning_multiple_devices">鎺冩弿 %d 鍙拌ō鍌�</string>
+    <string name="notification_channel_scan_service_name">鎺冩弿鏈嶅嫏</string>
+    <string name="state_scanned">鎺冩弿</string>
+    <string name="devicesetting_scannable_debounce">鍙巸鎻忕殑鍘诲綀璺宠秴鏅傦紙绉掞級</string>
+    <string name="devicesetting_scannable_debounce_summary">鍦ㄦ巸鎻忓緦锛岃┎瑷倷灏囧湪鎸囧畾鐨勬檪闁撳収淇濇寔鐐烘巸鎻忕媭鎱嬩甫琚拷鐣�</string>
+    <string name="devicesetting_scannable_minimum_unseen_summary">鍦ㄨ鎺冩弿寰岋紝瑭茶ō鍌欏繀闋堝湪閫欐鏅傞枔鍏т繚鎸佷笉鍙锛岀劧寰屾墠鑳藉啀娆¤ɑ鍐娿��</string>
+    <string name="error_showing_changelog">椤ず璁婃洿绱�閷勬檪鍑洪尟</string>
+    <string name="activity_type_worn">浣╂埓</string>
+    <string name="dashboard_settings">鍎�琛ㄦ澘瑷畾</string>
+    <string name="bottom_nav_dashboard">鍎�琛ㄦ澘</string>
+    <string name="pref_dashboard_cards_summary">鍦ㄥ剙琛ㄦ澘涓婄躬瑁藉皬宸ュ叿鍛ㄥ湇鐨勫崱鐗�</string>
+    <string name="pref_dashboard_devices_to_include">瑕佸寘鎷殑瑷倷</string>
+    <string name="pref_dashboard_widget_show_legend_title">椤ず鍦栦緥</string>
+    <string name="pref_dashboard_widget_goals_chart_title">鐩鍦栬〃</string>
+    <string name="pref_dashboard_all_devices_title">鎵�鏈夎ō鍌�</string>
+    <string name="pref_dashboard_widgets_order_summary">閬告搰鍟熺敤鍝簺灏忓伐鍏蜂互鍙婂畠鍊戝湪鍎�琛ㄦ澘涓婄殑椤ず闋嗗簭</string>
+    <string name="pref_auto_reply_calls_title">鑷嫊鎺ヨ伣闆昏┍</string>
+    <string name="pref_auto_reply_calls_delay_title">鑷嫊鍥炵瓟寤堕伈</string>
+    <string name="pref_speak_notifications_aloud_summary">閫氱煡灏囬�氶亷鑰虫鏈楄畝</string>
+    <string name="pref_speak_notifications_focus_exclusive_summary_on">鍦ㄩ�氱煡琚湕璁�鏅傦紝鍏朵粬鎳夌敤绋嬪紡鐨勬挱鏀惧皣鏈冩毇鍋�</string>
+    <string name="pref_header_calls_and_notifications">閫氳┍鍜岄�氱煡</string>
+    <string name="pref_summary_bottom_navigation_bar_off">鍍呬娇鐢ㄦ┇鍚戞粦鍕曞湪涓诲睆骞曚箣闁撳垏鎻�</string>
+    <string name="pref_summary_garmin_default_reply_suffix">闄勫姞鍦℅adgetbridge涓ō缃殑寰岀洞涔嬪</string>
+    <string name="pref_title_garmin_default_reply_suffix">浣跨敤闋愬畾缇╃殑鍥炶寰岀洞</string>
+    <string name="pref_dashboard_widget_today_upside_down_title">鍗堝鍦ㄥ簳閮�</string>
+    <string name="pref_dashboard_widget_today_upside_down_summary">鍦�24灏忔檪妯″紡涓嬶紝灏囧崍澶滅暙鍦ㄥ湒琛ㄧ殑搴曢儴锛屾鍗堢暙鍦ㄥ湒琛ㄧ殑闋傞儴</string>
+    <string name="sleepasandroid_settings">鐫$湢浣滅偤瀹夊崜</string>
+    <string name="pref_sleepasandroid_enable_summary">鍟熺敤 Sleep As Android 鏁村悎</string>
+    <string name="pref_sleepasandroid_device_summary">閬告搰瑷倷浣滅偤 Sleep As Android 鏁告摎鎻愪緵鑰�</string>
+    <string name="pref_sleepasandroid_slot_summary">瑷疆楝ч悩鏅備娇鐢ㄥ摢鍊嬮閻樻彃妲�</string>
+    <string name="pref_sleepasandroid_feat_movement">鍔犻�熷害瑷�</string>
+    <string name="pref_sleepasandroid_feat_heartrate">蹇冪巼</string>
+    <string name="pref_sleepasandroid_feat_oximetry">琛�姘ф脯瀹氭硶</string>
+    <string name="pref_sleepasandroid_feat_spo2">琛�姘ч=鍜屽害</string>
+    <string name="pref_summary_huawei_account">鍦ㄩ厤灏嶉亷绋嬩腑浣跨敤鐨勮彲鐐哄赋鎴躲�傝ō缃畠鍙互鍦ㄤ笉鎭㈠京鍑哄粻瑷疆鐨勬儏娉佷笅閫茶閰嶅皪銆�</string>
+    <string name="watchface_resolution_doesnt_match">閷堕潰瑙f瀽搴﹁垏瑷倷铻㈠箷涓嶅尮閰嶃�傞尪闈㈢偤 %1$s锛岃ō鍌欒灑骞曠偤 %2$s</string>
+    <string name="device_name_bicycle_sensor">鑷杌婃劅娓櫒</string>
+    <string name="device_name_cycling_sensor">鑷杌婃劅鎳夊櫒</string>
+    <string name="devicetype_cycling_sensor">鑷杌婇�熷害鎰熸噳鍣�</string>
+    <string name="pref_summary_wheel_diameter">杓緫浠ヨ嫳瀵歌▓銆傞�氬父鐐�29銆�27.5鎴�26銆�</string>
+    <string name="pref_title_wheel_diameter">杓緫</string>
+    <string name="pref_summary_cycling_persistence_interval">鐣跺墠寰挵鏁告摎鎳夊鍏ユ暩鎿氬韩鐨勬檪闁撻枔闅旓紙浠ョ鐐哄柈浣嶏級</string>
+    <string name="pref_title_cycling_persistence_interval">鎸佺簩鏅傞枔</string>
+    <string name="chart_cycling_point_label_distance">浠婂ぉ: %.1f 鍏噷
+\n绺借▓: %.1f 鍏噷</string>
+    <string name="toast_setting_requires_reconnect">姝よō瀹氬皣鍦ㄩ噸鏂伴�f帴寰岀敓鏁�</string>
+    <string name="open_camera">鎵撻枊鐩告</string>
+    <string name="toast_camera_permission_required">姝ゅ姛鑳介渶瑕佺浉姗熸瑠闄愩��</string>
+    <string name="toast_camera_support_required">姝ゅ姛鑳介渶瑕佺浉姗熸敮鎻淬��</string>
+    <string name="pref_battery_polling_configuration">闆绘睜杓閰嶇疆</string>
+    <string name="garmin_agps_url_i">AGPS %1$d 缍插潃</string>
+    <string name="no_folder_selected">鏈伕鎿囦换浣曡硣鏂欏ぞ</string>
+    <string name="folder_is_empty">璩囨枡澶炬槸绌虹殑</string>
+    <string name="folder">璩囨枡澶�</string>
+    <string name="url">缍插潃</string>
+    <string name="garmin_agps_local_file">鏈湴鏂囦欢</string>
+    <string name="pref_garmin_agps_help">浠ヤ笅鍒楄〃鍖呭惈鎵嬮尪璜嬫眰鐨勬墍鏈� AGPS 鏇存柊鐨� URL銆傛偍鍙互寰炴墜姗熺殑瀛樺劜涓伕鎿囦竴鍊嬫枃浠讹紝鐣舵墜閷惰珛姹傛洿鏂版檪灏囧叾鐧奸�佸埌鎵嬮尪銆�</string>
+    <string name="toggle_debug_mode">鍒囨彌闄ら尟妯″紡</string>
+    <string name="battery_low_notify_enabled">浣庨浕閲忛�氱煡</string>
+    <string name="battery_full_notify_enabled">鍏呴浕瀹屾垚鏅傞�氱煡</string>
+    <string name="battery_full_threshold">婊块浕闁惧��</string>
+    <string name="default_percentage">闋愯ō (%1$d%%)</string>
+    <string name="pref_fetch_unknown_files_title">鐛插彇鏈煡妾旀</string>
+    <string name="pref_fetch_unknown_files_summary">寰炴墜閷舵彁鍙栨湭鐭ユ椿鍕曟獢妗堛�傞�欎簺妾旀涓嶆渻琚檿鐞嗭紝浣嗘渻淇濆瓨鍦ㄦ墜姗熶腑銆�</string>
+    <string name="smart_ring_measurement_error_worn_incorrectly">娓噺瑾ゅ樊銆傛垝鎸囩殑鎰熸脯鍣ㄦ柟鍚戞纰哄棊锛�</string>
+    <string name="pref_header_deprecated_functionalities">宸蹭笉鍐嶆敮鎸佺殑鍔熻兘</string>
+    <string name="pref_header_deprecated_functionalities_warning">浠ヤ笅鍔熻兘宸茶娣樻卑锛屽皣寰堝揩寰炶粺楂斾腑绉婚櫎銆�
+\n濡傛灉鎮ㄩ渶瑕佸暉鐢ㄤ互涓嬫煇鍊嬭ō缃紝璜嬪嫏蹇呰垏灏堟鍦橀殜鑱公銆�</string>
+    <string name="pref_deprecated_media_control_title">宸查亷鏅傜殑濯掗珨鎺у埗</string>
+    <string name="pref_deprecated_media_control_summary">灏囧獟楂旀帶鍒跺懡浠や綔鐐烘寜閸典簨浠剁櫦閫侊紝鑰屼笉鏄獟楂旀帶鍒跺櫒銆�</string>
+    <string name="devicetype_ble_gatt_client">閫氱敤 BLE GATT 瀹㈡埗绔�</string>
+    <string name="prefs_title_gatt_client_notification_intents">閫氶亷 BLE Intent API 寤f挱 GATT 閫氱煡鎰忓湒</string>
+    <string name="prefs_summary_gatt_client_notification_intents">閫忛亷 Intent 鎺ユ敹 BLE 鐗瑰镜璁婃洿</string>
+    <string name="prefs_summary_gatt_client_allow_gatt_interactions">鍏佽ū鐧奸�� BLE 鐗瑰镜鐨勮畝鍙�/瀵叆鍜岄�f帴鍛戒护</string>
+    <string name="prefs_summary_gatt_client_device_state_updates">閫氶亷鎰忓湒鎺ユ敹 BLE 閫f帴鐙�鎱嬭畩鍖�</string>
+    <string name="prefs_title_gatt_client_api_package">BLE API 濂椾欢</string>
+    <string name="prefs_title_ble_intent_api">BLE 鎰忓湒 API</string>
+    <string name="activity_db_management_backup_restore_label">鍌欎唤鍜岄倓鍘�</string>
+    <string name="activity_db_management_export_to_zip">鍖嚭澹撶府妾�</string>
+    <string name="activity_db_management_import_from_zip">灏庡叆澹撶府妾�</string>
+    <string name="activity_db_management_backup_restore_explanation">閫插彛/鍑哄彛鎿嶄綔鍏佽ū鎮ㄥ皣鎵�鏈塆adgetbridge瑷疆銆佽ō鍌欏拰鏁告摎寰瀦ip鏂囦欢涓伔绉绘垨鍌欎唤銆�
+\n
+\n灏庡叆鏂囦欢灏囧埅闄ゆ墍鏈夌従鏈夋暩鎿氥�佽ō鍌欏拰鍋忓ソ瑷疆锛屼甫瀹屽叏鐢ㄥ倷浠芥浛鎻涘畠鍊戙��</string>
+    <string name="backup_restore_importing_files_i_of_n">姝e湪鍖叆妾旀鈥� %1d / %2d</string>
+    <string name="backup_restore_export_complete">鍖嚭瀹屾垚</string>
+    <string name="backup_restore_import_complete">灏庡叆瀹屾垚</string>
+    <string name="backup_restore_error_import">寰炲绺獢鍖叆澶辨晽</string>
+    <string name="backup_restore_abort_export_confirmation">瑕佷腑姝㈠皫鍑哄棊锛熼儴鍒嗗绺獢妗堝皣琚埅闄ゃ��</string>
+    <string name="backup_restore_abort_import_confirmation">瑕佷腑姝㈠皫鍏ュ棊锛熼�欏彲鑳藉皫鑷存暩鎿氬韩鎼嶅鎴栦笉涓�鑷淬��</string>
+    <string name="backup_restore_restart_title">閲嶅暉</string>
+    <string name="label_distance_trip">琛岀▼锛�%.1f 鍏噷</string>
+    <string name="label_distance_total">绺借▓: %.1f 鍏噷</string>
+    <string name="title_activity_controlcenter_banglejs_nightly">Bangle.js Gadgetbridge (鍏堣鐗�)</string>
+    <string name="about_description_main_nopebble">Cloudless copyleft 鑷敱鏇夸唬鍝侊紝鐢ㄦ柤渚涙噳鍟嗙殑闁夋簮 Android 灏忓伐鍏锋噳鐢ㄧ▼搴忋�侴adgetbridge 鐨勫厛琛岀増鏈櫦甯冦�傛鐗堟湰宸插皣 Pebble 鎻愪緵鑰呴噸鏂板懡鍚嶄互闃叉琛濈獊锛屽洜姝ゆ煇浜涜垏 Pebble 鐩搁棞鐨勬暣鍚堝皣鐒℃硶姝e父閬嬩綔锛屼絾瀹冨彲浠ヨ垏鐝炬湁鐨� Gadgetbridge 瀹夎涓�璧峰畨瑁濄��</string>
+    <string name="about_description_main_nightly">Cloudless copyleft 鑷敱鏇夸唬鍝侊紝鐢ㄦ柤渚涙噳鍟嗙殑灏侀枆婧� Android 灏忓伐鍏锋噳鐢ㄧ▼寮忋�侴adgetbridge 鐨勫闁撶増鏈�傚鏋滄偍宸插畨瑁� Gadgetbridge 鎴� Pebble 鎳夌敤绋嬪紡锛屽墖鐒℃硶瀹夎锛屽洜鐐� Pebble 鎻愪緵鑰呬箣闁撳瓨鍦ㄨ绐併��</string>
+    <string name="pref_title_pebble_settings">Pebble 瑷畾</string>
+    <string name="pref_title_notifications_pebblemsg">Pebble 瑷婃伅</string>
+    <string name="pref_gps_header">GPS</string>
+    <string name="minutes_20">20鍒嗛悩</string>
+    <string name="minutes_60">60鍒嗛悩</string>
+    <string name="mi2_prefs_button_press_count_summary">瑙哥櫦浜嬩欢鎵�闇�鐨勬寜閳曟寜澹撴鏁� 1銆傞毃寰岀浉鍚屾暩閲忕殑鎸夊鏈冨壍寤轰簨浠� 2锛屼緷姝ら鎺ㄣ��</string>
+    <string name="prefs_control_center">鎺у埗涓績</string>
+    <string name="english_gb">鑻辫獮锛堣嫳鍦嬶級</string>
+    <string name="spanish_es">瑗跨彮鐗欒獮锛堣タ鐝墮锛�</string>
+    <string name="activity_type_push_ups">浼忓湴鎸鸿韩</string>
+    <string name="activity_type_tennis">缍茬悆</string>
+    <string name="pref_header_health">鍋ュ悍</string>
+    <string name="pref_sleep_mode_smart_enable_title">鏅烘収鍟熺敤</string>
+    <string name="pref_header_display">椤ず</string>
+    <string name="pref_header_audio">闊抽牷</string>
+    <string name="pref_header_generic">閫氱敤</string>
+    <string name="pref_header_sound_vibration">鑱查煶鑸囨尟鍕�</string>
+    <string name="pref_header_offline_voice">闆㈢窔瑾為煶</string>
+    <string name="pref_header_time">鏅傞枔</string>
+    <string name="pref_title_general_reconnectonlytoconnected">鍍呴噸鏂伴�f帴宸查�f帴鐨勮ō鍌�</string>
+    <string name="pref_summary_general_reconnectonlytoconnected">鍍呴噸鏂伴�f帴宸查�f帴鐨勮ō鍌欙紝鑰屼笉鏄噸鏂伴�f帴鎵�鏈夎ō鍌�</string>
+    <string name="pref_theme_dynamic">鍕曟厠椤忚壊</string>
+    <string name="pref_title_notification_delay_calls">渚嗛浕閫氱煡寤堕伈</string>
+    <string name="pref_summary_notification_delay_calls">鍦ㄧ櫦閫佷締闆婚�氱煡鍒拌ō鍌欎箣鍓嶇殑寤堕伈鏅傞枔锛堜互绉掔偤鍠綅锛夈��</string>
+    <string name="pref_pair_bluetooth_calls_help_summary">鐐轰簡鎺ヨ伣钘嶇墮闆昏┍锛屾偍闇�瑕佸皣鎵嬫鑸囨墜閷剁殑绗簩鍊嬪渚嬮厤灏嶃��</string>
+    <string name="pref_title_huami_force_new_protocol">鏂拌獚璀夊崝璀�</string>
+    <string name="pref_workout_detection_categories_title">閬嬪嫊椤炲垾</string>
+    <string name="pairing_creating_bond_with">寤虹珛鑸� %1$s (%2$s) 鐨勮伅绻�</string>
+    <string name="buttons_on_left">宸﹀伌鐨勬寜閳�</string>
+    <string name="pref_screen_notification_profile_schedule">鏅傞枔琛�</string>
+    <string name="pref_contacts_summary">鍦ㄦ墜閷朵笂閰嶇疆鑱怠浜�</string>
+    <string name="controlcenter_start_configure_reminders">瑷畾鎻愰啋</string>
+    <string name="reminder_repeat">閲嶈</string>
+    <string name="reminder_delete_confirm_title">鍒櫎鎻愰啋</string>
+    <string name="firmware_update_progress">涓婂偝姝e湪閫茶涓�
+\n%1d%% 閫熷害 %.2fkbps (骞冲潎 %.2fkbps)
+\n绗� %1d 閮ㄥ垎锛屽叡 %1d 閮ㄥ垎</string>
+    <string name="devicetype_amazfit_gts">Amazfit GTS</string>
+    <string name="pref_summary_overwrite_settings_on_connection">閫f帴鍒伴煶闊挎檪锛岃钃嬪叾涓婄殑鎵�鏈夎ō缃��</string>
+    <string name="application_name_banglejs_main">Please initialize the addon first by opening it from the Extensions Menu &gt; GPT for Docs, Sheets and Slides &gt; Start</string>
+    <string name="title_activity_controlcenter_banglejs_main">Bangle.js Gadgetbridge</string>
+    <string name="gadgetbridge_running_main_nightly">鍏堣鐗� GB 鍩疯涓�</string>
+    <string name="application_name_main_nopebble">Gadgetbridge锛堝厛琛岀増 锛岀劇 Pebble 鎻愪緵鑰咃級</string>
+    <string name="title_activity_controlcenter_main_nopebble">Gadgetbridge 鍏堣鐗� 鐒� Pebble</string>
+    <string name="bedtime">鐫¤鏅傞枔</string>
+    <string name="widget_screen_delete_confirm_description">鎮ㄧ⒑瀹氳鍒櫎銆�%1$s銆嶅棊锛�</string>
+    <string name="heartrate_bpm_115">115 鎷嶆瘡鍒嗛悩</string>
+    <string name="world_clock_no_free_slots_title">娌掓湁绌轰綅</string>
+    <string name="world_clock_label">妯欑堡</string>
+    <string name="prefs_heartrate_alert_threshold">蹇冪巼璀﹀牨闁惧��</string>
+    <string name="seconds_6">6绉�</string>
+    <string name="prefs_password_enabled">瀵嗙⒓宸插暉鐢�</string>
+    <string name="english_ca">鑻辫獮锛堝姞鎷垮ぇ锛�</string>
+    <string name="extended_ascii">鎿村睍ASCII</string>
+    <string name="authentication_required">闇�瑕佽韩浠介璀�</string>
+    <string name="active_time">娲诲嫊鏅傞枔</string>
+    <string name="standing_time">绔欑珛鏅傞枔</string>
+    <string name="activity_type_outdoor_cycling">鎴跺楱庤嚜琛岃粖</string>
+    <string name="activity_type_baseball">妫掔悆</string>
+    <string name="activity_type_pingpong">涔掍箵</string>
+    <string name="activity_type_zumba">灏婂反</string>
+    <string name="activity_type_riding">楱庨Μ</string>
+    <string name="heartrate_bpm_120">120 姣忓垎閻�</string>
+    <string name="devicetype_amazfit_bipupro">Amazfit Bip U Pro</string>
+    <string name="menuitem_widgets">灏忓伐鍏�</string>
+    <string name="ascentMeters">涓婂潯</string>
+    <string name="watchface_widget_type_steps">姝ラ</string>
+    <string name="scan_scanning_all_devices">鎺冩弿鎵�鏈夎ō鍌�</string>
+    <string name="sony_speak_to_chat_sensitivity_low">浣�</string>
+    <string name="transition">閬庢浮</string>
+    <string name="silent_mode_normal_vibrate">姝e父 / 闇囧嫊</string>
+    <string name="prefs_always_on_display_summary">淇濇寔妯傞殜鐨勯’绀哄绲傞枊鍟�</string>
+    <string name="prefs_password_summary">鐣舵墜鐠拌鍙栦笅鏅傦紝璜嬭ō缃瘑纰奸帠瀹�</string>
+    <string name="prefs_password_4_digits_0_to_9_summary">瀵嗙⒓蹇呴爤鏈�4浣嶆暩瀛楋紝鍙兘浣跨敤鏁稿瓧</string>
+    <string name="prefs_password_6_digits_0_to_9_summary">瀵嗙⒓蹇呴爤鏈�6浣嶆暩锛屽彧鑳戒娇鐢ㄦ暩瀛�</string>
+    <string name="prefs_activate_display_on_lift_sensitivity">鏁忔劅鎬�</string>
+    <string name="mi5_prefs_workout_activity_types">閬嬪嫊娲诲嫊椤炲瀷</string>
+    <string name="mi5_prefs_workout_activity_types_summary">閬告搰瑕佸湪閬嬪嫊铻㈠箷涓婇’绀虹殑娲诲嫊椤炲瀷</string>
+    <string name="pref_title_force_white_color_scheme">寮峰埗榛戝簳鐧藉瓧閰嶈壊鏂规</string>
+    <string name="pref_summary_force_white_color_scheme">濡傛灉浣犵殑鎵嬮尪鏈夐粦鑹茬殑鎸囬嚌锛岄�欐渻寰堟湁鐢�</string>
+    <string name="lefun_prefs_hydration_reminder_interval_title">瑁滄按鎻愰啋闁撻殧锛堜互鍒嗛悩瑷堬級</string>
+    <string name="lefun_prefs_antilost_title">闃蹭笩澶�</string>
+    <string name="serbian">濉炵埦缍簽瑾�</string>
+    <string name="ukranian">鐑忓厠铇獮</string>
+    <string name="pref_chart_sleep_lines_limit">鍦ㄦ痪鍕曚箣鍓嶉’绀哄灏戝�嬬潯鐪犳渻瑭辫</string>
+    <string name="activity_prefs_chart_min_session_length">鏈�灏忔椿鍕曢暦搴︼紙鍒嗛悩锛�</string>
+    <string name="pref_title_pebble_health_store_raw">灏囧師濮嬭閷勫瓨鍎插湪鏁告摎搴腑</string>
+    <string name="soundcore_equalizer_direction_standing">绔欑珛</string>
+    <string name="averageHR">蹇冪巼</string>
+    <string name="busy_task_fetch_debug_logs">鐛插彇闄ら尟鏃ヨ獙</string>
+    <string name="busy_task_fetch_hr_data">鐛插彇蹇冪巼鏁告摎</string>
+    <string name="busy_task_fetch_temperature">鐛插彇婧害鏁告摎</string>
+    <string name="world_clock_timezone">鏅傚崁</string>
+    <string name="estonian">鎰涙矙灏间簽瑾�</string>
+    <string name="dbmanagementactivity_export_data_title">鍖嚭鏁告摎锛�</string>
+    <string name="widget_layout_two">2 鍊嬪皬宸ュ叿</string>
+    <string name="activity_type_bouldering">鎶辩煶</string>
+    <string name="activity_type_inline_skating">鐩存帓杓粦</string>
+    <string name="activity_type_climb_indoor">瀹ゅ収鏀�宀�</string>
+    <string name="activity_type_sky_diving">璺冲倶</string>
+    <string name="activity_type_snowshoe">闆瀷</string>
+    <string name="activity_type_snowmobiling">闆湴鎽╂墭</string>
+    <string name="activity_type_wakeboarding">婊戞按鏉块亱鍕�</string>
+    <string name="activity_type_water_skiing">姘翠笂婊戦洩</string>
+    <string name="activity_type_softball_slow_pitch">鎱㈤�熷鐞�</string>
+    <string name="activity_type_marine">娴锋磱</string>
+    <string name="activity_type_video_gaming">瑕栭牷閬婃埐</string>
+    <string name="activity_type_aerobic_combo">鏈夋哀绲勫悎</string>
+    <string name="activity_type_aerobics">鏈夋哀閬嬪嫊</string>
+    <string name="activity_type_air_walker">绌轰腑婕鑰�</string>
+    <string name="activity_type_bmx">BMX</string>
+    <string name="activity_type_board_game">妗岄亰</string>
+    <string name="activity_type_bocce">纰楃悆</string>
+    <string name="activity_type_folk_dance">姘戦枔鑸炶箞</string>
+    <string name="activity_type_frisbee">椋涚洡</string>
+    <string name="activity_type_futsal">浜斾汉鍒惰冻鐞�</string>
+    <string name="activity_type_hacky_sack">韪㈣购韫�</string>
+    <string name="activity_type_hip_hop">鍢诲搱</string>
+    <string name="activity_type_hula_hoop">鍛兼媺鍦�</string>
+    <string name="activity_type_jai_alai">璩界悆</string>
+    <string name="activity_type_muay_thai">娉版嫵</string>
+    <string name="activity_type_parallel_bars">闆欐</string>
+    <string name="activity_type_spinning">鏃嬭綁</string>
+    <string name="activity_type_square_dance">鏂瑰鑸�</string>
+    <string name="activity_type_water_polo">姘寸悆</string>
+    <string name="activity_type_weiqi">鍦嶆</string>
+    <string name="devicetype_miband8active">灏忕背鏅鸿兘鎵嬬挵 8 Active</string>
+    <string name="soundcore_adaptive_direction_summary">鏍规摎瑷倷鏂瑰悜鑷嫊瑾挎暣鍧囪 鍣ㄩ爯瑷�</string>
+    <string name="soundcore_equalizer_preset">闋愯ō</string>
+    <string name="soundcore_equalizer_reset_summary">灏囨墍鏈夊潎琛″櫒闋绘鎭㈠京鐐洪粯瑾嶈ō缃�</string>
+    <string name="strokes_unit">瀛椾覆</string>
+    <string name="Elevation">娴锋嫈</string>
+    <string name="loyalty_cards_open_catima">闁嬫斁 Catima</string>
+    <string name="body_energy_gained">鐛插緱</string>
+    <string name="body_energy_lost">杩峰け</string>
+    <string name="body_energy_legend_level">韬珨鑳介噺姘村钩</string>
+    <string name="devicetype_miband8pro">灏忕背鏅鸿兘鎵嬬挵 8 Pro</string>
+    <string name="soundcore_button_brightness_low">浣�</string>
+    <string name="soundcore_equalizer_direction">瑷倷鏂瑰悜</string>
+    <string name="soundcore_equalizer_reset_title">閲嶇疆鐐洪爯瑷�</string>
+    <string name="menuitem_readiness">婧栧倷鐙�鎱�</string>
+    <string name="menuitem_mi_ai">鎴戠殑 AI</string>
+    <string name="qhybrid_calibration_10_steps">10 姝ラ</string>
+    <string name="prefs_vibration_enable">鍟熺敤闇囧嫊</string>
+    <string name="sony_speak_to_chat">瑾為煶灏嶈┍</string>
+    <string name="open_fw_installer_pick_file">閬告搰妾旀</string>
+    <string name="open_fw_installer_connect_minimum_one_device">璜嬭嚦灏戦�f帴涓�鍊嬫偍鎯宠鐧奸�佹獢妗堢殑瑷倷銆�</string>
+    <string name="pref_blacklist_calendars_summary">琚垪鍏ラ粦鍚嶅柈鐨勬棩鏇嗗皣涓嶆渻鍚屾鍒拌ō鍌�</string>
+    <string name="pref_summary_pebble_forceprotocol">姝ら伕闋呭挤鍒舵牴鎿氶煂楂旂増鏈娇鐢ㄦ渶鏂扮殑閫氱煡鍗旇銆傜煡閬撲綘鍦ㄥ仛浠�楹硷紒</string>
+    <string name="application_name_banglejs_nightly">Bangle.js Gadgetbridge (鍏堣鐗�)</string>
+    <string name="fw_multi_upgrade_notice">鎮ㄥ嵆灏囧畨瑁� %1$s 鍜� %2$s 鍥轰欢锛岃�屼笉鏄洰鍓嶅湪鎮ㄧ殑 Mi Band 涓婄殑鍥轰欢銆�</string>
+    <string name="unbind_before_pair_message">姝よō鍌欏凡鍦� Android 瑷畾涓秮瀹氾紝閫欏彲鑳芥渻灏庤嚧鏌愪簺瑷倷閰嶅皪澶辨晽銆�
+\n
+\n濡傛灉娣诲姞瑷倷澶辨晽锛岃珛鍦� Android 瑷畾涓皣鍏剁Щ闄や甫鍐嶆鍢楄│銆�</string>
+    <string name="sony_speak_to_chat_sensitivity_high">楂�</string>
+    <string name="pref_summary_sunrise_sunset">鏍规摎浣嶇疆鐧奸�佹棩鍑哄拰鏃ヨ惤鏅傞枔鍒癙ebble鏅傞枔绶�</string>
+    <string name="strokes">绺芥】鏁�</string>
+    <string name="RunningForm">璺戞濮垮嫝</string>
+    <string name="nothing_prefs_inear_title">鍏ヨ�虫娓�</string>
+    <string name="pref_title_equalizer_normal">姝e父</string>
+    <string name="qhybrid_pref_summary_actions">閫氬嫟鎳夌敤绋嬪紡鐨勬搷浣�</string>
+    <string name="prefs_wena3_notification_default_call_vibration">渚嗛浕闇囧嫊</string>
+    <string name="prefs_wena3_notification_default_led">閫氱煡 LED 椤忚壊</string>
+    <string name="pref_dashboard_widget_today_24h_title">24灏忔檪妯″紡</string>
+    <string name="pref_title_notification_wake_on_open">鑷嫊鍠氶啋鍜岃В閹�</string>
+    <string name="pref_summary_notification_wake_on_open">鐣惰ō鍌欑櫦閫� OPEN 鍥炴噳鏅傦紝鍠氶啋涓﹁В閹� Android 瑷倷銆傞渶瑕佽檿鏂煎彈淇′换鐨勭媭鎱嬨��</string>
+    <string name="pref_title_device_spec_settings_force_time">寮峰埗鍚屾鏅傞枔</string>
+    <string name="pref_header_device_spec_settings">瑷倷瑷畾</string>
+    <string name="pref_title_sensors_altitude">楂樺害鏍℃簴</string>
+    <string name="tap_connected_device_for_vibration">杓曡Ц閫f帴鐨勮ō鍌欎互闇囧嫊</string>
+    <string name="busy_task_fetch_pai_data">鐛插彇PAI鏁告摎</string>
+    <string name="busy_task_fetch_spo2_data">鐛插彇 SpO2 鏁告摎</string>
+    <string name="busy_task_fetch_sleep_respiratory_rate_data">鐛插彇鐫$湢鍛煎惛閫熺巼鏁告摎</string>
+    <string name="busy_task_fetch_statistics">鐛插彇绲辫▓鏁告摎</string>
+    <string name="pref_screen_vibration_patterns_summary">鐐轰笉鍚岀殑閫氱煡閰嶇疆闇囧嫊妯″紡</string>
+    <string name="heartrate_bpm_40">40 娆℃瘡鍒嗛悩</string>
+    <string name="heartrate_bpm_50">50 娆℃瘡鍒嗛悩</string>
+    <string name="title_activity_set_reminders">瑷畾鎻愰啋</string>
+    <string name="pref_world_clocks_title">涓栫晫鏅傞悩</string>
+    <string name="reminder_date">鏃ユ湡</string>
+    <string name="reminder_time">鏅傞枔</string>
+    <string name="world_clock_delete_confirm_description">鎮ㄧ⒑瀹氳鍒櫎涓栫晫鏅傞悩鍡庯紵</string>
+    <string name="installer_activity_unable_to_find_handler">鐒℃硶鎵惧埌铏曠悊绋嬪簭渚嗗畨瑁濇鏂囦欢銆�</string>
+    <string name="updatefirmwareoperation_metadata_updateproblem">闊岄珨鍏冭硣鏂欏偝杓稿晱椤�</string>
+    <string name="updatefirmwareoperation_failed_low_mtu">鐣跺墠鐨� MTU 鍊� %1$d 澶綆锛岃珛鍦ㄨō鍌欒ō瀹氫腑鍟熺敤楂� MTU锛岀劧寰屾柗闁�/閲嶆柊閫f帴瑷倷銆�</string>
+    <string name="dbmanagementactivity_export_confirmation">鐪熺殑瑕佸皫鍑烘暩鎿氬棊锛熶箣鍓嶅皫鍑虹殑娲诲嫊鏁告摎锛堝鏋滄湁锛夊拰鍋忓ソ瑷疆灏囪瑕嗚搵銆�</string>
+    <string name="activity_db_management_autoexport_enabled_yes">鑷嫊鍖嚭宸插暉鐢ㄣ��</string>
+    <string name="autoExport_lastTime_label">鏈�寰岃嚜鍕曞尟鍑�: %1$s</string>
+    <string name="stop">鍋滄</string>
+    <string name="activity_type_physical_training">楂旇兘瑷撶反</string>
+    <string name="activity_type_cross_country_running">瓒婇噹璺�</string>
+    <string name="activity_type_karate">绌烘墜閬�</string>
+    <string name="activity_type_fencing">鎿婂妽</string>
+    <string name="activity_type_cross_training">浜ゅ弶瑷撶反</string>
+    <string name="activity_type_roller_skating">婧滃啺</string>
+    <string name="activity_type_martial_arts">姝﹁</string>
+    <string name="activity_type_darts">椋涢彚</string>
+    <string name="activity_type_hunting">鐙╃嵉</string>
+    <string name="activity_type_elliptical">姗㈠湏褰�</string>
+    <string name="activity_type_weightlifting">鑸夐噸</string>
+    <string name="activity_type_dance">鑸炶箞</string>
+    <string name="activity_type_strength_training">鍔涢噺瑷撶反</string>
+    <string name="activity_type_boxing">鎷虫搳</string>
+    <string name="devicetype_garmin_vivoactive_4s">Garmin V铆voactive 4S</string>
+    <string name="pref_message_privacy_mode_bodyonly">鍍呴毐钘忚韩楂�</string>
+    <string name="pref_title_sunrise_sunset">鏃ュ嚭鑸囨棩钀�</string>
+    <string name="pref_title_pebble_enable_applogs">鍟熺敤鎵嬮尪鎳夌敤绋嬪紡鏃ヨ獙</string>
+    <string name="pref_explanation_authkey">鏌愪簺瑷倷鍦ㄩ娆″垵濮嬪寲鏅傞渶瑕佷竴鍊嬬壒娈婄殑閰嶅皪瀵嗛懓銆傞粸鎿婇�欒!浠ョ嵅鍙栫董鍩轰腑鐨勬洿澶氳┏绱拌硣瑷娿��</string>
+    <string name="pref_workout_detection_sensitivity">鏁忔劅鎬�</string>
+    <string name="pref_title_touch_spotify">Spotify</string>
+    <string name="sony_equalizer_preset_speech">婕旇瑳</string>
+    <string name="pref_media_rewind">璺冲洖</string>
+    <string name="english_au">鑻辫獮锛堟境澶у埄浜烇級</string>
+    <string name="mi2_enable_text_notifications_summary"><![CDATA[闇�瑕佸浐浠� >= 1.0.1.28 涓﹀畨瑁� Mili_pro.ft*銆俔]></string>
+    <string name="maximum_duration">鎸佺簩鏅傞枔</string>
+    <string name="activity_type_free_training">鍏嶈不瑷撶反</string>
+    <string name="activity_type_rower">鍒掕埞鑰�</string>
+    <string name="activity_type_dynamic_cycle">鍕曟厠寰挵</string>
+    <string name="activity_type_climbing">鏀�鐧�</string>
+    <string name="activity_type_jump_roping">璺崇供</string>
+    <string name="devicetype_redmi_watch_2">Redmi 鎵嬮尪 2</string>
+    <string name="cannot_upload_watchface_too_many_watchfaces_installed">鐒℃硶涓婂偝琛ㄩ潰锛屽凡瀹夎鐨勮〃闈㈠お澶�</string>
+    <string name="insufficient_space_for_upload">涓婂偝绌洪枔涓嶈冻</string>
+    <string name="debugactivity_confirm_remove_device_preferences_title">绉婚櫎瑁濈疆鍋忓ソ瑷畾锛�</string>
+    <string name="debugactivity_confirm_remove_device_preferences">閫欏皣閲嶇疆鎵�鏈夐�f帴瑷倷鐨勮ō鍌欏亸濂借ō瀹氥�傛偍纰哄畾鍡庯紵</string>
+    <string name="alarm_smart_wakeup">鏅烘収閱掍締</string>
+    <string name="pbw_install_handler_unable_to_install">鐒℃硶瀹夎绲﹀畾鐨勬獢妗堬細%1$s</string>
+    <string name="devicetype_amazfit_pop">Amazfit Pop</string>
+    <string name="menuitem_workout_status">閬嬪嫊鐙�鎱�</string>
+    <string name="zepp_os_watchface_vibrant">鐢熷嫊鐨�</string>
+    <string name="watch9_calibration_button">鏍℃簴</string>
+    <string name="devicetype_flipper_zero">Flipper zero</string>
+    <string name="loyalty_cards_catima">鍗℃彁鐟�</string>
+    <string name="title_activity_set_contacts">閰嶇疆鑱怠浜�</string>
+    <string name="ftp_server">FTP 浼烘湇鍣�</string>
+    <string name="prefs_always_on_display">濮嬬祩椤ず</string>
+    <string name="start">闁嬪</string>
+    <string name="activity_type_rowing">鍒掕埞</string>
+    <string name="devicetype_amazfit_trex">Amazfit T-Rex</string>
+    <string name="watchface_widget_type_calories">鍗¤矾閲�</string>
+    <string name="heartrate_bpm_45">姣忓垎閻�45娆�</string>
+    <string name="heartrate_bpm_105">105 鎷嶆瘡鍒嗛悩</string>
+    <string name="backup_restore_error_export">灏庡嚭鐐� zip 澶辨晽</string>
+    <string name="open_fw_installer_select_file">閬告搰鎮ㄦ兂涓婂偝鍒拌ō鍌欑殑妾旀锛�%s</string>
+    <string name="hrv_status_baseline_label">鍩烘簴</string>
+    <string name="devicetype_amazfit_x">Amazfit X</string>
+    <string name="fw_upgrade_notice_amazfitbip3">鎮ㄥ嵆灏囧湪鎮ㄧ殑 Amazfit Bip 3 涓婂畨瑁� %s 闊岄珨銆�
+\n
+\n璜嬬⒑淇濆厛瀹夎 .fw 妾旀锛岀劧寰屽啀瀹夎 .res 妾旀銆傚畨瑁� .fw 妾旀寰岋紝鎮ㄧ殑鎵嬮尪灏囬噸鏂板暉鍕曘��
+\n
+\n娉ㄦ剰锛氬鏋� .res 妾旀鑸囦箣鍓嶅畨瑁濈殑瀹屽叏鐩稿悓锛屽墖鎮ㄤ笉蹇呭畨瑁� .res 妾旀銆�
+\n
+\n璜嬭嚜琛屾壙鎿旈ⅷ闅�茶鎿嶄綔锛�</string>
+    <string name="pref_agps_expire_time">AGPS 鍒版湡鏅傞枔</string>
+    <string name="pref_title_canned_replies">鍥炶</string>
+    <string name="pref_header_activitytrackers">娲诲嫊杩借工鍣�</string>
+    <string name="pref_title_pebble_activitytracker">棣栭伕娲诲嫊杩借工鍣�</string>
+    <string name="pref_summary_enable_pebblekit">鍟熺敤灏嶄娇鐢� PebbleKit 鐨� Android 鎳夌敤绋嬪紡鐨勫椹楁�ф敮鎻�</string>
+    <string name="pref_title_enable_calendar_sync">鍚屾鏃ユ泦</string>
+    <string name="pref_summary_preview_message_in_title">椤ず瑷倷鍏佽ū鐨勯�氱煡妯欓涓殑娑堟伅闋愯</string>
+    <string name="pref_summary_casio_alert_calendar">鏃ユ泦閫氱煡鐨勬彁閱掞紙闇囧嫊/闊块埓锛�</string>
+    <string name="pref_title_casio_alert_call">渚嗛浕鎻愰啋</string>
+    <string name="pref_summary_casio_alert_sms">灏嶆柤 SMS (绨¤▕) 閫氱煡鐨勮鍫� (闇囧嫊/闊块埓)</string>
+    <string name="pref_title_screen_on_on_notifications">閫氱煡鏅傝灑骞曢枊鍟�</string>
+    <string name="pref_title_pebble_enable_bgjs">鍟熺敤鑳屾櫙 JavaScript</string>
+    <string name="pref_title_vibration_strength">鎸嫊寮峰害</string>
+    <string name="pref_notifications_and_calls_enable_misscall">鏈帴渚嗛浕閫氱煡</string>
+    <string name="pref_sensors_bp_calibration_high">琛�澹撴敹绺锛堥珮锛�</string>
+    <string name="prefs_screen_orientation">铻㈠箷鏂瑰悜</string>
+    <string name="zetime_title_settings">ZeTime 瑷畾</string>
+    <string name="zetime_signaling_beep_once">鍡朵竴涓�</string>
+    <string name="pref_sleep_mode_smart_enable_summary">鍦ㄧ潯鐪犳檪浣╂埓鎵嬬挵鏅傝嚜鍕曞暉鐢ㄧ潯鐪犳ā寮�</string>
+    <string name="app_install_info">鎮ㄥ嵆灏囧畨瑁濅互涓嬫噳鐢ㄧ▼寮忥細
+\n
+\n%1$s
+\n鐗堟湰 %2$s 鐢� %3$s 鎻愪緵
+\n</string>
+    <string name="android_pairing_hint">浣跨敤 Android 钘嶇墮閰嶅皪灏嶈┍妗嗕締閰嶅皪瑷倷銆�</string>
+    <string name="world_clock_delete_confirm_title">鍒櫎 \'%1$s\'</string>
+    <string name="contact_delete_confirm_description">鎮ㄧ⒑瀹氳鍒櫎銆�%1$s銆嶅棊锛�</string>
+    <string name="contact_no_free_slots_description">瑭茶ō鍌欐矑鏈夊彲鐢ㄧ殑鑱怠浜烘彃妲斤紙绺芥彃妲芥暩锛�%1$s锛�</string>
+    <string name="title_activity_reminder_details">鎻愰啋绱扮瘈</string>
+    <string name="prefs_heartrate_alert_experimental_description">鐣跺績鐜囪秴閬庨柧鍊间笖鍦ㄩ亷鍘�10鍒嗛悩鍏ф矑鏈夋槑椤殑韬珨娲诲嫊鏅傦紝璁撴墜鐠伴渿鍕曘�傛鍔熻兘鐐哄椹楁�э紝灏氭湭閫茶寤f硾娓│銆�</string>
+    <string name="prefs_heartrate_alert_high_threshold">楂樺績鐜囪鍫遍柧鍊�</string>
+    <string name="prefs_activity_monitoring_title">娲诲嫊鐩f帶</string>
+    <string name="dbaccess_error_executing">鍩疯 \'%1$s\' 鏅傜櫦鐢熼尟瑾�</string>
+    <string name="cannot_connect">鐒℃硶閫f帴: %1$s</string>
+    <string name="pref_header_cannned_messages">闋愯ō瑷婃伅</string>
+    <string name="activity_type_dodgeball">韬查伩鐞�</string>
+    <string name="devicetype_amazfit_gtr3_pro">Amazfit GTR 3 Pro</string>
+    <string name="devicetype_zepp_e">Zepp E</string>
+    <string name="devicetype_garmin_vivomove_hr">Garmin V铆vomove HR</string>
+    <string name="gpx_route_upload_failed">Gpx 璺緫涓婂偝澶辨晽</string>
+    <string name="authentication_failed_negotiation">韬唤椹楄瓑瀵嗛懓鍗斿晢澶辨晽</string>
+    <string name="activity_type_health_snapshot">鍋ュ悍蹇収</string>
+    <string name="activity_type_orienteering">瀹氬悜瓒婇噹</string>
+    <string name="devicetype_amazfit_gtr_mini">Amazfit GTR Mini</string>
+    <string name="eversionAngleAvg">骞冲潎澶栫炕瑙�</string>
+    <string name="strokes_minute">瀛椾覆/鍒嗛悩</string>
+    <string name="soundcore_equalizer_band7">涓冪礆</string>
+    <string name="pref_title_mb_intents">鐩存帴寤f挱濯掗珨鎸夐垥鎰忓湒</string>
+    <string name="prefs_notifications_and_calls_continious_ring">闆昏┍閳磋伈鏈熼枔鐨勯�氱煡</string>
+    <string name="zetime_signaling_vibrate_once">闇囧嫊涓�娆�</string>
+    <string name="offline_voice_respond_screen_on">铻㈠箷闁嬪暉鏅傚洖鎳�</string>
+    <string name="vibration_profile_staccato">鏂烽煶</string>
+    <string name="qhybrid_title_watchface_designer">閷堕潰瑷▓甯�</string>
+    <string name="devicetype_garmin_forerunner_265">Garmin Forerunner 265</string>
+    <string name="reminder_every_year">姣忓勾</string>
+    <string name="reminder_no_free_slots_title">娌掓湁绌轰綅</string>
+    <string name="installer_activity_wait_while_determining_status">璜嬬◢鍊欙紝姝e湪纰哄畾瀹夎鐙�鎱嬧��</string>
+    <string name="pai_chart_per_week">姣忛�� PAI</string>
+    <string name="pai_chart_per_month">姣忔湀PAI</string>
+    <string name="show_ongoing_activity">椤ず姝e湪閫茶鐨勬椿鍕曞綀鍑虹獥鍙�</string>
+    <string name="abstract_chart_fragment_kind_rem_sleep">蹇�熺溂鍕曠潯鐪�</string>
+    <string name="romanian">缇呴Μ灏间簽瑾�</string>
+    <string name="pref_title_lower_button_function_long">闀蜂笅鎸夐垥</string>
+    <string name="devicetype_galaxybuds">Galaxy Buds</string>
+    <string name="pref_continuous_skin_temperature_measurement_description">鐩墠涓嶆敮鎻存韩搴︽绱€�傛瑷疆鍍呭暉鐢ㄨō鍌欎笂鐨勯�g簩娓噺銆�</string>
+    <string name="watchface_dialog_pre_setting_position">闋愯ō浣嶇疆鐐� %s</string>
+    <string name="pref_summary_canned_messages_set">灏囦互涓嬮厤缃殑娑堟伅鐧奸�佸埌鎮ㄧ殑瑷倷</string>
+    <string name="heartrate_bpm_140">140 姣忓垎閻�</string>
+    <string name="spo2_perc_85">0.85</string>
+    <string name="prefs_active_noise_cancelling_level">涓诲嫊鍣煶鍙栨秷绛夌礆</string>
+    <string name="pref_ambient_sound_tone_summary">寰炴煍鍜屽埌娓呮櫚</string>
+    <string name="pref_header_sony_equalizer_preset_custom_1">鑷▊闋愯ō 1</string>
+    <string name="pref_adaptive_volume_control_title">鑷仼鎳夐煶閲忔帶鍒�</string>
+    <string name="sony_speak_to_chat_timeout_short">鐭� (15绉�)</string>
+    <string name="sony_speak_to_chat_timeout_standard">妯欐簴锛�30绉掞級</string>
+    <string name="sony_button_mode_off">闂�</string>
+    <string name="pref_title_opentracks_packagename">OpenTracks 濂椾欢鍚嶇ū</string>
+    <string name="pref_force_enable_spo2_support">寮峰埗 SpO2 鏀彺</string>
+    <string name="huawei_ignore_wakeup_status_start">蹇界暐鍠氶啋闁嬪鐙�鎱�</string>
+    <string name="auto_reconnect_ble_title">鑷嫊閲嶆柊閫f帴鍒拌ō鍌�</string>
+    <string name="prefs_wena3_home_icon_name_suica">Suica椁橀</string>
+    <string name="prefs_wena3_auto_power_off_turn_off_time_item">闂滄鏅傞枔</string>
+    <string name="pref_dashboard_widget_today_title">娲诲嫊鍦�</string>
+    <string name="pref_sleepasandroid_feat_alarms">璀﹀牨</string>
+    <string name="pref_sleepasandroid_feat_notifications">閫氱煡</string>
+    <string name="activity_prefs_chart_max_idle_phase_length">鍒嗛殧娲诲嫊鐨勬毇鍋滈暦搴︼紙鍒嗛悩锛�</string>
+    <string name="activity_prefs_chart_min_steps_per_minute_for_run">姣忓垎閻樻渶灏戞鏁镐互妾㈡脯璺戞</string>
+    <string name="activity_db_management_autoexport_scheduled_yes">AutoExport 鍘熷畾鏂� %1$s</string>
+    <string name="miband2_prefs_timeformat">Mi2: 鏅傞枔鏍煎紡</string>
+    <string name="activity_type_cooldown">鍐峰嵒</string>
+    <string name="activity_type_smith_machine">鍙插瘑鏂</string>
+    <string name="activity_type_basketball">绫冪悆</string>
+    <string name="activity_type_handball">鎵嬬悆</string>
+    <string name="activity_type_squash">澹佺悆</string>
+    <string name="activity_type_indoor_fitness">瀹ゅ収鍋ヨ韩</string>
+    <string name="activity_type_hiit">楂樺挤搴﹂枔姝囪〒绶�</string>
+    <string name="activity_summary_detail_clear_gpx_track">娓呴櫎 GPX 璺緫</string>
+    <string name="activity_error_share_failed">鍏变韩鏂囦欢澶辨晽銆�</string>
+    <string name="add_test_device">娣诲姞娓│瑷倷</string>
+    <string name="devicetype_miband7">灏忕背鏅鸿兘鎵嬬挵 7</string>
+    <string name="devicetype_amazfit_band5">Amazfit Band 5</string>
+    <string name="devicetype_amazfit_cor">Amazfit Cor</string>
+    <string name="devicetype_tlw64">TLW64</string>
+    <string name="error_version_check_extreme_caution">璀﹀憡锛氭鏌ョ増鏈硣瑷婃檪鍑洪尟锛佹偍涓嶆噳绻肩簩锛佺湅鍒扮増鏈悕绋� \"%s\"</string>
+    <string name="prefs_game_mode">閬婃埐妯″紡</string>
+    <string name="double_tap">闆欐搳</string>
+    <string name="triple_tap">涓夋杓曡Ц</string>
+    <string name="devicetype_vesc">VESC</string>
+    <string name="prefs_wena3_led_none">娌掓湁LED</string>
+    <string name="pref_summary_fossil_hr_nav_vibrate">鎵嬮尪鏄惁鎳夎┎鍦ㄦ瘡娆℃柊鐨勬垨鏇存敼鐨勫皫鑸寚浠ゆ檪鎸嫊锛堝儏鍦ㄦ噳鐢ㄧ▼寮忓湪鍓嶆櫙鏅傦級</string>
+    <string name="fw_upgrade_notice_amazfit_cheetah_pro">鎮ㄥ嵆灏囧湪鎮ㄧ殑 Amazfit Cheetah Pro 涓婂畨瑁� %s 闊岄珨銆�
+\n
+\n瀹夎 .zip 鏂囦欢寰岋紝鎮ㄧ殑鎵嬬挵灏囬噸鏂板暉鍕曘��
+\n
+\n璜嬭嚜琛屾壙鎿旈ⅷ闅紒</string>
+    <string name="fw_upgrade_notice_zepp_os">鎮ㄥ嵆灏囧湪鎮ㄧ殑%s涓婂畨瑁�%s鍥轰欢銆�
+\n
+\n瀹夎瀹�.zip鏂囦欢寰岋紝鎮ㄧ殑鎵嬮尪灏囬噸鍟熴��
+\n
+\n璜嬭嚜琛屾壙鎿旈ⅷ闅紒</string>
+    <string name="prefs_equalizer">鍧囪 鍣�</string>
+    <string name="copied_to_clipboard">宸茶瑁藉埌鍓布绨�</string>
+    <string name="minPace">鏈�鎱㈢殑姝ヤ紣</string>
+    <string name="proprietary_app_warning">姝ゅ姛鑳介渶瑕佸畨瑁濆皥鏈夋噳鐢ㄧ▼寮�</string>
+    <string name="pref_title_notifications_and_calls_repeat_on_call">閲嶈渚嗛浕閫氱煡</string>
+    <string name="pbw_install_handler_hw_revision_mismatch">鐒℃硶瀹夎绲﹀畾鐨勫浐浠讹細瀹冭垏鎮ㄧ殑 Pebble 纭珨鐗堟湰涓嶅尮閰嶃��</string>
+    <string name="notif_battery_low_bigtext_number_of_charges">鍏呴浕娆℃暩锛�%s</string>
+    <string name="prefs_stress_monitoring_description">浼戞伅鏅傜洠娓鍔涙按骞�</string>
+    <string name="device_hw">纭珨淇▊锛�%1$s</string>
+    <string name="error_creating_directory_for_logfiles">鍓靛缓鏃ヨ獙鏂囦欢鐩寗鏅傚嚭閷細%1$s</string>
+    <string name="authentication_failed_check_key">椹楄瓑澶辨晽锛岃珛妾㈡煡鎺堟瑠閲戦懓</string>
+    <string name="dbmanagementactivity_error_cleaning_export_directory">寰炲皫鍑虹洰閷� %1$s 鍒櫎鏂囦欢鏅傚嚭閷�</string>
+    <string name="smart">鑱版槑</string>
+    <string name="activity_type_rock_climbing">鏀�宀�</string>
+    <string name="activity_type_elliptical_trainer">姗㈠湏姗�</string>
+    <string name="activity_type_rowing_machine">鍒掕埞姗�</string>
+    <string name="devicetype_garmin_instinct_2_solar">Garmin Instinct 2 Solar</string>
+    <string name="devicetype_garmin_venu_2s">Garmin Venu 2S</string>
+    <string name="devicetype_garmin_fenix_5_plus">Garmin Fenix 5 Plus</string>
+    <string name="devicetype_garmin_fenix_6_sapphire">Garmin Fenix 6 钘嶅鐭�</string>
+    <string name="devicetype_garmin_instinct">Garmin Instinct</string>
+    <string name="devicetype_garmin_forerunner_965">Garmin Forerunner 965</string>
+    <string name="pref_title_touch_voice_assistant">瑾為煶鍔╂墜</string>
+    <string name="pref_title_touch_anc">涓诲嫊鍣煶娑堥櫎</string>
+    <string name="pref_title_touch_volume">闊抽噺</string>
+    <string name="pref_title_touch_ambient">鐠板鑱查煶</string>
+    <string name="sony_ambient_sound_control_button_mode_nc_as_off">闄嶅櫔銆佺挵澧冭伈銆侀棞闁�</string>
+    <string name="wearmode_necklace">闋呴崐锛堥牳甯讹級</string>
+    <string name="wearmode_pebble">Pebble 锛堥瀷鎵o級</string>
+    <string name="prefs_relaxation_reminder_description">鐣跺鍔涘�奸珮鏂�80鏅傦紝鎸嫊鎵嬬挵浠ラ�氱煡鎮�</string>
+    <string name="watchface_widget_type_battery">闆绘睜</string>
+    <string name="watchface_widget_type_active_mins">娲昏簫鍒嗛悩</string>
+    <string name="watchface_widget_type_uv_index">绱绶氭寚鏁�</string>
+    <string name="watchface_setting_power_saving_display">鍦ㄦ墜鑵曞闂滈枆椤ず鏇存柊</string>
+    <string name="prefs_home_icon_center_item">涓績</string>
+    <string name="prefs_wena3_title_screen">椤ず瑷畾</string>
+    <string name="prefs_wena3_hint_lift_wrist">鐣朵綘鐪嬫墜鑵曟檪锛岄枊鍟熼’绀哄睆</string>
+    <string name="prefs_wena3_smart_alarm_margin_item">鏅鸿兘楝ч悩閭婇殯</string>
+    <string name="pref_speak_notifications_focus_exclusive_title">鏆仠鍏朵粬鎳夌敤绋嬪紡鐨勯煶闋�</string>
+    <string name="hrv">HRV</string>
+    <string name="getting_heart_rate">娓噺</string>
+    <string name="seconds_7">7绉�</string>
+    <string name="seconds_14">14绉�</string>
+    <string name="activity_db_management_autoexport_scheduled_no">AutoExport 灏氭湭琚帓绋嬨��</string>
+    <string name="activity_type_fitness_exercises">鍋ヨ韩閬嬪嫊</string>
+    <string name="activity_type_luge">闆﹪</string>
+    <string name="dbmanagementactivity_delete_old_activitydb_confirmation">鐪熺殑瑕佸埅闄よ垔鐨勬椿鍕曡硣鏂欏韩鍡庯紵鏈灏庡叆鐨勬椿鍕曟暩鎿氬皣鏈冧笩澶便��</string>
+    <string name="dbmanagementactivity_old_activity_db_deletion_failed">鑸婃椿鍕曟暩鎿氬韩鍒櫎澶辨晽銆�</string>
+    <string name="activity_type_unknown">鏈煡娲诲嫊</string>
+    <string name="activity_type_indoor_track">瀹ゅ収鐢板緫</string>
+    <string name="activity_type_golf">楂樼埦澶悆</string>
+    <string name="activity_type_trail_run">瓒婇噹璺�</string>
+    <string name="activity_type_wrestling">鎽旇筏</string>
+    <string name="activity_type_driving">椐曢</string>
+    <string name="activity_type_hang_gliding">婊戠繑鍌�</string>
+    <string name="activity_type_ice_skating">婊戝啺</string>
+    <string name="activity_type_stand_up_paddleboarding">绔嬪紡鍒掓С</string>
+    <string name="activity_type_kayaking">鐨垝鑹�</string>
+    <string name="activity_type_rafting">婕傛祦</string>
+    <string name="activity_type_tactical">鎴拌</string>
+    <string name="activity_type_jumpmaster">璺冲倶鎸囨彯瀹�</string>
+    <string name="activity_type_water_tubing">姘翠笂婊戠储</string>
+    <string name="activity_type_wakesurfing">灏炬尝琛濇氮</string>
+    <string name="activity_type_checkers">璺虫</string>
+    <string name="activity_type_finswimming">娼涙吵</string>
+    <string name="notif_battery_full_title">瑁濈疆闆绘睜宸插厖婊匡紒</string>
+    <string name="updatefirmwareoperation_updateproblem_low_battery">瑷倷闆绘睜闆婚噺閬庝綆</string>
+    <string name="gpx_route_upload_complete">Gpx 璺窔涓婂偝瀹屾垚</string>
+    <string name="gpx_route_upload_in_progress">涓婂偝 GPX 璺窔</string>
+    <string name="clock">閻�</string>
+    <string name="pref_sleep_breathing_quality_monitoring">鐫$湢鍛煎惛璩噺鐩f脯</string>
+    <string name="hydration_dnd_summary">鍦ㄤ竴娈垫檪闁撳収绂佺敤姘村悎璀﹀憡</string>
+    <string name="prefs_always_on_display_follow_watchface">棰ㄦ牸璺熼毃閷堕潰</string>
+    <string name="_pebble_watch_open_on_phone">鍦� Android 瑁濈疆涓婃墦闁�</string>
+    <string name="withings_calibration_text_activity_target">鏈�寰屽皣娲诲嫊鎵嬪皪榻婂埌100%銆傝珛娉ㄦ剰锛岄�欏�嬫墜鍙渻闋嗘檪閲濈Щ鍕曘��</string>
+    <string name="pref_title_notifications_ignore_work_profile">蹇界暐宸ヤ綔妾旀閫氱煡</string>
+    <string name="menuitem_todo">寰呰睛浜嬮爡</string>
+    <string name="activity_type_tai_chi">澶サ</string>
+    <string name="activity_type_hula_hooping">鍛兼媺鍦�</string>
+    <string name="activity_type_kite_flying">鏀鹃ⅷ绠�</string>
+    <string name="activity_type_hand_cycling">鎵嬪嫊鑷杌�</string>
+    <string name="activity_type_pickleball">娉¤彍鐞�</string>
+    <string name="activity_type_shot">灏勬搳</string>
+    <string name="activity_type_jet_skiing">婊戞按鎽╂墭</string>
+    <string name="pref_touch_tone">瑙告帶闊�</string>
+    <string name="vibration_profile_default">闋愯ō</string>
+    <string name="pref_screen_notification_profile_event_reminder">娲诲嫊鎻愰啋</string>
+    <string name="heartrate_bpm_130">130 鎷嶆瘡鍒嗛悩</string>
+    <string name="heartrate_bpm_135">135 鎷嶆瘡鍒嗛悩</string>
+    <string name="manual">鎵嬪唺</string>
+    <string name="activity_web_view">缍查爜瑕栧湒娲诲嫊</string>
+    <string name="mi2_fw_installhandler_fw53_hint">鎮ㄩ渶瑕佸湪瀹夎姝ゅ浐浠朵箣鍓嶅畨瑁濈増鏈� %1$s锛�</string>
+    <string name="activity_type_ice_hockey">鍐扮悆</string>
+    <string name="devicetype_garmin_instinct_2s_solar">Garmin Instinct 2S 澶櫧鑳�</string>
+    <string name="pref_title_send_app_notifications">鐧奸�侀�氱煡</string>
+    <string name="pref_summary_send_app_notifications">灏囨噳鐢ㄧ▼搴忛�氱煡鐧奸�佸埌瑷倷</string>
+    <string name="pref_summary_device_internet_access">鍏佽ū姝よō鍌欎笂鐨勬噳鐢ㄧ▼寮忚í鍟忎簰鑱恫</string>
+    <string name="pref_summary_relax_firmware_checks">鏀鹃瑔鍥轰欢妾㈡煡</string>
+    <string name="battery_i">闆绘睜 %d</string>
+    <string name="pref_time_sync">鑷嫊鏅傞枔鍚屾</string>
+    <string name="prefs_notifications_and_calls_shake_reject">鎼栨墜鑵曞拷鐣�/鎷掔禃渚嗛浕</string>
+    <string name="pref_morning_updates_categories_summary">姣忓ぉ鏃╀笂椤ず鐨勯鍒ュ垪琛�</string>
+    <string name="unknown">鏈煡</string>
+    <string name="title_activity_sleepmonitor">鐫$湢鐩f脯鍣�</string>
+    <string name="spm">姝�/鍒嗛悩</string>
+    <string name="button_reset_menu_structure">閲嶇疆鑿滃柈绲愭</string>
+    <string name="heartrate_bpm_110">110 姣忓垎閻�</string>
+    <string name="busy_task_fetch_sleep_data">鐛插彇鐫$湢鏁告摎</string>
+    <string name="heartrate_bpm_100">100 姣忓垎閻樺績璺虫鏁�</string>
+    <string name="reminder_every_day">姣忓ぉ</string>
+    <string name="prefs_spo2_alert_threshold">SPO2 璀﹀牨闁惧��</string>
+    <string name="activity_type_soccer">瓒崇悆</string>
+    <string name="activity_type_mountaineering">鐧诲北</string>
+    <string name="activity_type_body_combat">鎼忔搳鎿�</string>
+    <string name="activity_type_plaza_dancing">寤e牬鑸�</string>
+    <string name="hrv_status_day_avg">鏃ュ潎</string>
+    <string name="devicetype_watchxplus">瑙�鐪� X Plus</string>
+    <string name="devicetype_colmi_r02">Colmi R02</string>
+    <string name="activity_type_bungee_jumping">褰堣烦璺冲倶</string>
+    <string name="activity_summary_edit_name_title">绶ㄨ集妯欑堡</string>
+    <string name="activity_summary_detail_select_gpx_track">閬告搰 GPX 璺緫</string>
+    <string name="pref_title_casio_alert_calendar">鏃ユ泦閫氱煡鎻愰啋</string>
+    <string name="stress_average">骞冲潎</string>
+    <string name="activity_type_table_football">妗屼笂瓒崇悆</string>
+    <string name="activity_type_tug_of_war">鎷旀渤</string>
+    <string name="activity_type_wall_ball">鐗嗙悆</string>
+    <string name="pref_activity_recognize_biking">瑾嶅彲楱庤嚜琛岃粖</string>
+    <string name="pref_activity_recognize_rowing">瑾嶈瓨鍒掕埞</string>
+    <string name="controlcenter_add_new_folder">鏂板璩囨枡澶�</string>
+    <string name="devicetype_garmin_vivosport">Garmin V铆vosport</string>
+    <string name="devicetype_vibratissimo">妤垫尟鍕�</string>
+    <string name="devicetype_um25">UM-25</string>
+    <string name="devicetype_liveview">鍗虫檪瑕栧湒</string>
+    <string name="devicetype_exrizu_k8">Exrizu K8</string>
+    <string name="devicetype_mijia_lywsd03">绫冲婧害鑸囨繒搴﹀偝鎰熷櫒 2</string>
+    <string name="devicetype_mijia_mho_c303">绫冲 MHO-C303</string>
+    <string name="devicetype_pinetime_jf">PineTime (JF 闊岄珨)</string>
+    <string name="devicetype_sonyswr12">绱㈠凹 SWR12</string>
+    <string name="devicetype_waspos">榛冭渹瑾�</string>
+    <string name="devicetype_smaq2oss">SMA-Q2 OSS</string>
+    <string name="devicetype_fitpro">FitPro</string>
+    <string name="devicetype_colacao21">ColaCao 2021</string>
+    <string name="devicetype_colacao23">鍙▊鍙彲 2023</string>
+    <string name="devicetype_domyos_t540">Domyos T540</string>
+    <string name="devicetype_sony_wh_1000xm4">绱㈠凹 WH-1000XM4</string>
+    <string name="devicetype_sony_wf_1000xm5">绱㈠凹 WF-1000XM5</string>
+    <string name="devicetype_sony_wf_c500">Sony WF-C500</string>
+    <string name="devicetype_honor_band4">姒��鎵嬬挵4</string>
+    <string name="devicetype_honor_band5">姒��鎵嬬挵 5</string>
+    <string name="devicetype_honor_watchgspro">姒��鎵嬮尪 GS Pro</string>
+    <string name="devicetype_huawei_band_aw70">鑿偤鎵嬬挵 (AW70)</string>
+    <string name="devicetype_huawei_watchgt2e">鑿偤鎵嬮尪 GT 2e</string>
+    <string name="devicetype_huawei_watch3">鑿偤鎵嬮尪 3 (灏堟キ鐗�)</string>
+    <string name="devicetype_huawei_watchfit2">鑿偤鎵嬮尪 Fit 2</string>
+    <string name="devicetype_huawei_watchfit3">鑿偤鎵嬮尪 Fit 3</string>
+    <string name="devicetype_huawei_watchultimate">鑿偤鎵嬮尪绲傛サ鐗�</string>
+    <string name="devicetype_redmi_smart_band_2">Redmi 鏅鸿兘鎵嬬挵 2</string>
+    <string name="notification_channel_full_battery_name">婊块浕</string>
+    <string name="notification_gps_text">姝e湪灏� GPS 浣嶇疆鐧奸�佽嚦 %1$d 鍙拌ō鍌�</string>
+    <string name="devicetype_amazfit_vergel">Amazfit Verge Lite</string>
+    <string name="menuitem_aqi">绌烘埃鍝佽唱鎸囨暩</string>
+    <string name="menuitem_forecast">闋愭脯</string>
+    <string name="menuitem_thermometer">婧害瑷�</string>
+    <string name="menuitem_body_composition">韬珨绲勬垚</string>
+    <string name="menuitem_apps_shortcuts">鎳夌敤绋嬪紡蹇嵎鏂瑰紡</string>
+    <string name="menuitem_breathing">鍛煎惛</string>
+    <string name="menuitem_cycles">閫辨湡杩借工</string>
+    <string name="menuitem_cards">鍗$墖</string>
+    <string name="preferences_qhybrid_settings">Q 娣峰悎瑷疆</string>
+    <string name="preferences_qhybrid_settings_summary">Q Hybrid 鎵嬮尪鐨勯伜鐣欒ō缃�</string>
+    <string name="menuitem_workout">閬嬪嫊</string>
+    <string name="menuitem_unknown">鏈煡</string>
+    <string name="menuitem_goal">娲诲嫊鐩</string>
+    <string name="menuitem_female_health">濂虫�у仴搴�</string>
+    <string name="menuitem_calendar">鏃ユ泦</string>
+    <string name="menuitem_voice_memos">瑾為煶鍌欏繕閷�</string>
+    <string name="menuitem_sun_moon">鏃ユ湀</string>
+    <string name="menuitem_offline_voice">闆㈢窔瑾為煶</string>
+    <string name="menuitem_membership_cards">鏈冨摗鍗�</string>
+    <string name="menuitem_brightness">浜害</string>
+    <string name="menuitem_bluetooth">钘嶇墮</string>
+    <string name="menuitem_wifi">鐒$窔缍茶矾</string>
+    <string name="menuitem_lockscreen">閹栧睆</string>
+    <string name="menuitem_night_display">澶滈枔椤ず</string>
+    <string name="menuitem_unknown_app">鏈煡 (%s)</string>
+    <string name="zepp_os_watchface_emerald_moonlight">缈$繝鏈堝厜</string>
+    <string name="zepp_os_watchface_rotating_earth">鏃嬭綁鐨勫湴鐞�</string>
+    <string name="zepp_os_watchface_superposition">瓒呬綅缃�</string>
+    <string name="zepp_os_watchface_vast_sky">寤i棅鐨勫ぉ绌�</string>
+    <string name="zepp_os_watchface_lightning_flash">闁冮浕</string>
+    <string name="zepp_os_watchface_pure_white">绱旂櫧</string>
+    <string name="zepp_os_watchface_guider">寮曞皫鑰�</string>
+    <string name="zepp_os_watchface_city_of_speed">閫熷害涔嬪煄</string>
+    <string name="zepp_os_watchface_starry_sky">鏄熺┖</string>
+    <string name="zepp_os_watchface_the_ultima">绲傛サ</string>
+    <string name="yesterdays_activity">鏄ㄥぉ鐨勬椿鍕�</string>
+    <string name="watch9_calibration_hint">瑷疆鎮ㄧ殑瑷倷鐝惧湪椤ず鐨勬檪闁撱��</string>
+    <string name="title_activity_watch9_calibration">瑙�鐪� 9 鏍℃簴</string>
+    <string name="prefs_fm_presets_presets">闋愯ō</string>
+    <string name="qhybrid_second_timezone_offset_relative_to_utc">鐩稿皪鏂糢TC鐨勭浜屾檪鍗�鍋忕Щ</string>
+    <string name="qhybrid_use_activity_hand_as_notification_counter">浣跨敤娲诲嫊鎵嬩綔鐐洪�氱煡瑷堟暩鍣�</string>
+    <string name="qhybrid_changes_delay_prompt">璁婃洿鍙兘闇�瑕佸咕绉掗悩鈥�</string>
+    <string name="qhybrid_offset_time_by">鍋忕Щ鏅傞枔</string>
+    <string name="hr_appname_wellness">鍋ュ悍</string>
+    <string name="hr_appname_workout">閬嬪嫊</string>
+    <string name="hr_appname_stopwatch">瑷堟檪鍣�</string>
+    <string name="pref_title_middle_button_function_short">涓枔鎸夐垥鐭�</string>
+    <string name="error_no_bluetooth_scan">蹇呴爤鎺堜簣涓﹀暉鐢ㄨ棈鐗欐巸鎻忚í鍟忔瑠闄愶紝鎵嶈兘浣挎巸鎻忔甯搁亱浣�</string>
+    <string name="pref_qhybrid_title_widget_draw_circles">绻=灏忓伐鍏峰湏鍦�</string>
+    <string name="device_experimental">瀵﹂鐨�</string>
+    <string name="error_background_service_reason">鍟熷嫊鑳屾櫙鏈嶅嫏澶辨晽锛屽洜鐐虹櫦鐢熶簡鐣板父銆傛寜姝よ檿浠ュ彇寰楁洿澶氳┏鎯呫�俓n\n閷锛�</string>
+    <string name="pref_show_changelog">鍟熷嫊鏅傞’绀鸿畩鏇存棩瑾�</string>
+    <string name="error_setting_alias">瑷畾鍒ュ悕鏅傚嚭閷細</string>
+    <string name="distanceMeters">璺濋洟</string>
+    <string name="elevationGain">楂樺害澧炵泭</string>
+    <string name="elevationLoss">楂樺害鎼嶅け</string>
+    <string name="HeartRateZones">蹇冪巼鍗�闁�</string>
+    <string name="aerobicTrainingEffect">鏈夋哀鏁堟灉</string>
+    <string name="anaerobicTrainingEffect">鍘哀鏁堟噳</string>
+    <string name="currentWorkoutLoad">瑷撶反璨犺嵎</string>
+    <string name="maximumOxygenUptake">鏈�澶ф哀姘f敐鍙栭噺</string>
+    <string name="averageStride">骞冲潎姝ュ箙</string>
+    <string name="avgStrokeRate">骞冲潎涓ⅷ鐜�</string>
+    <string name="maxStrokeRate">鏈�澶у績鐜�</string>
+    <string name="flatSeconds">骞冲潶</string>
+    <string name="Distance">璺濋洟</string>
+    <string name="activity_detail_share_raw_summary">鍒嗕韩鍘熷鎽樿</string>
+    <string name="pref_header_filter">閬嬪嫊娲诲嫊绡╅伕</string>
+    <string name="pref_header_statistics">閬嬪嫊娲诲嫊绲辫▓</string>
+    <string name="breaststroke">铔欐吵</string>
+    <string name="freestyle">鑷敱寮�</string>
+    <string name="qhybrid_title_watchface">琛ㄩ潰閰嶇疆</string>
+    <string name="qhybrid_title_apps">鎳夌敤绋嬪紡</string>
+    <string name="qhybrid_title_file_management">妾旀绠$悊</string>
+    <string name="qhybrid_title_apps_management">鎳夌敤绋嬪紡绠$悊</string>
+    <string name="qhybrid_title_calibration">鏍℃簴</string>
+    <string name="fossil_hr_commute_actions_explanation">閫欒!閰嶇疆鐨勬搷浣滃皣鍑虹従鍦ㄦ偍鎵嬮尪涓婄殑閫氬嫟鎳夌敤绋嬪紡涓�傝珛闁辫畝缍熀浠ョ嵅鍙栨湁闂滃浣曡檿鐞嗛�欎簺鎿嶄綔鐢㈢敓鐨勬剰鍦栫殑淇℃伅銆�</string>
+    <string name="fossil_hr_edit_action_delete">鍒櫎</string>
+    <string name="fossil_hr_new_action">鏂拌鍕�</string>
+    <string name="qhybrid_calibration_100_steps">100 姝�</string>
+    <string name="qhybrid_watchface_configuration_old_firmware">閬╃敤鏂煎浐浠剁増鏈� DN1.0.2.19r 鍙婁互涓嬬殑鎵嬮尪鐨勮〃闈㈤厤缃灑骞�</string>
+    <string name="qhybrid_pref_title_actions">琛屽嫊</string>
+    <string name="pref_title_developer_settings">闁嬬櫦鑰呰ō瀹�</string>
+    <string name="button_watchface_select_image">鏇存敼鑳屾櫙鍦栫墖</string>
+    <string name="button_watchface_add_widget">鏂板灏忓伐鍏�</string>
+    <string name="watchface_dialog_title_set_name">瑷畾琛ㄩ潰鍚嶇ū</string>
+    <string name="watchface_widget_type_date">鏃ユ湡</string>
+    <string name="watchface_widget_type_weather">澶╂埃</string>
+    <string name="watchface_widget_type_heart_rate">蹇冪巼</string>
+    <string name="button_watchface_preview">鍦ㄦ墜閷朵笂闋愯</string>
+    <string name="button_watchface_save_apply">淇濆瓨涓︽噳鐢�</string>
+    <string name="watchface_dialog_widget_presets">浣嶇疆闋愯ō</string>
+    <string name="watchface_dialog_widget_color_white">鐧借壊</string>
+    <string name="watchface_dialog_widget_color_black">榛戣壊</string>
+    <string name="watchface_dialog_title_settings">琛ㄩ潰瑷疆</string>
+    <string name="watchface_setting_wrist_flick_duration">鎸佺簩鏅傞枔锛堟绉掞級</string>
+    <string name="watchface_setting_title_custom_events">鑷▊浜嬩欢</string>
+    <string name="prefs_sleep_time">鐫$湢鏅傞枔</string>
+    <string name="devicetype_nothingear2">Nothing Ear (2)</string>
+    <string name="devicetype_nothingearstick">Nothing 鑰虫 (Stick)</string>
+    <string name="devicetype_nothing_cmf_buds_pro_2">CMF Buds Pro 2</string>
+    <string name="devicetype_nothing_cmf_watch_pro">CMF Watch Pro</string>
+    <string name="devicetype_galaxybuds_2_pro">Galaxy Buds2 Pro</string>
+    <string name="devicetype_galaxybuds_2">Galaxy Buds2</string>
+    <string name="nothing_prefs_inear_summary">鏍规摎浣犳槸鍚︿僵鎴磋�虫渚嗘挱鏀�/鏆仠闊虫▊</string>
+    <string name="pref_title_equalizer_clear">娓呮櫚</string>
+    <string name="prefs_touch_lock_summary">绂佺敤瑙告帶浜嬩欢</string>
+    <string name="prefs_ambient_volume">鐠板闊抽噺</string>
+    <string name="prefs_ambient_volume_left">鐠板闊抽噺宸�</string>
+    <string name="prefs_ambient_volume_right">鐠板闊抽噺鍙�</string>
+    <string name="prefs_ambient_voice_focus">瑾為煶鑱氱劍</string>
+    <string name="prefs_noise_control">鍣煶鎺у埗</string>
+    <string name="prefs_voice_detect_duration">瀹夐潨寰岀祼鏉熸柤锛�</string>
+    <string name="pref_voice_detect_duration_5">5 绉�</string>
+    <string name="pref_voice_detect_duration_10">10绉�</string>
+    <string name="pref_voice_detect_duration_15">15绉�</string>
+    <string name="thirty_days_timeline">30澶╂檪闁撹〃</string>
+    <string name="sony_surround_mode_off">闂�</string>
+    <string name="sony_anc_optimizer_status_wearing_condition">娓噺绌胯憲鐙�娉佲��</string>
+    <string name="sony_equalizer_preset_off">闂�</string>
+    <string name="sony_equalizer_preset_bright">鏄庝寒</string>
+    <string name="sony_equalizer_preset_excited">鑸堝ギ</string>
+    <string name="sony_equalizer_preset_treble_boost">楂橀煶澧炲挤</string>
+    <string name="sony_equalizer_preset_bass_boost">浣庨煶澧炲挤</string>
+    <string name="sony_equalizer_preset_manual">鎵嬪唺</string>
+    <string name="sony_button_mode_help_title">鎸夐垥妯″紡 - 骞姪</string>
+    <string name="sony_button_mode_help_summary">姣忓�嬫寜閳曟ā寮忕殑鎻忚堪</string>
+    <string name="sony_button_mode_ambient_sound_control">鐠板鑱查煶鎺у埗</string>
+    <string name="sony_quick_access_double_tap">蹇�熻í鍟忥紙闆欐搳锛�</string>
+    <string name="moondrop_touch_trigger_long_press_3s">闀锋寜 (3绉�)</string>
+    <string name="soundcore_equalizer_preset_voice">鑱查煶</string>
+    <string name="soundcore_equalizer_band4">绗洓绱�</string>
+    <string name="soundcore_equalizer_band6">绗叚绱�</string>
+    <string name="soundcore_equalizer_band8">绗叓绱�</string>
+    <string name="miscale_weight_unit_summary">瑷疆椤ず娓噺鐨勯噸閲忓柈浣�</string>
+    <string name="pref_media_volumedown">闊抽噺娓涘皯</string>
+    <string name="pref_device_action_phone_gps_location_listener_stop">GPS 浣嶇疆鐩h伣鍣ㄥ仠姝�</string>
+    <string name="pref_device_action_dnd_off">璜嬪嬁鎵撴摼 - 闂滈枆</string>
+    <string name="pref_device_action_dnd_priority">璜嬪嬁鎵撴摼 - 鍍呴檺鍎厛</string>
+    <string name="pref_device_action_dnd_alarms">璜嬪嬁鎵撴摼 - 鍍呴檺璀﹀牨</string>
+    <string name="distance_format_meters">###m</string>
+    <string name="huawei_trusleep_summary">瀵︽檪鐩f脯鎮ㄧ殑鐫$湢璩噺鍜屽懠鍚告ā寮忋��
+\n鍒嗘瀽鎮ㄧ殑鐫$湢妯″紡锛屾簴纰鸿ê鏂�6绋潯鐪犲晱椤屻��</string>
+    <string name="pref_activity_recognition_mode_ask">鍟�</string>
+    <string name="pref_summary_opentracks_packagename">鐢ㄦ柤鍦ㄥ閮ㄥ仴韬噳鐢ㄧ▼寮忎腑鍟熷嫊/鍋滄 GPS 璺緫閷勮=銆�</string>
+    <string name="pref_enable_call_accept">鍟熺敤鎺ヨ伣闆昏┍</string>
+    <string name="pref_heartrate_automatic_enable">鍟熺敤鑷嫊蹇冪巼娓噺</string>
+    <string name="controlcenter_toggle_details">鍒囨彌瑭虫儏</string>
+    <string name="controlcenter_unset_folder">鏈ō瀹氱殑璩囨枡澶�</string>
+    <string name="steps_streaks_total_steps">绺芥鏁�</string>
+    <string name="steps_streaks_streak_days">閫g簩澶╂暩</string>
+    <string name="pref_summary_debug">鍚戣彲鐐鸿ō鍌欑櫦閫佽瑭﹁珛姹�</string>
+    <string name="wifi_ssid">SSID</string>
+    <string name="username">鐢ㄦ埗鍚�</string>
+    <string name="fossil_hr_pairing_successful">鑸囨墜閷堕厤灏嶆垚鍔�</string>
+    <string name="debug_companion_pair_current">灏囩暥鍓嶈ō鍌欓厤灏嶇偤浼翠径</string>
+    <string name="contact_name">鍚嶇ū</string>
+    <string name="contact_phone_number">闆昏┍铏熺⒓</string>
+    <string name="loyalty_cards_sync_title">鍚屾蹇犺獱鍗�</string>
+    <string name="devicetype_miband2_hrx">绫冲付 HRX</string>
+    <string name="cyan">闈掕壊</string>
+    <string name="blue">钘嶈壊</string>
+    <string name="prefs_wena3_receive_calls_title">渚嗛浕瑷畾</string>
+    <string name="pref_call_rejection_method_title">渚嗛浕鎷掓帴鏂规硶</string>
+    <string name="widget">灏忓伐鍏�</string>
+    <string name="pref_summary_fossil_hr_nav_foreground">鐣跺皫鑸噳鐢ㄦ帴鏀跺埌灏庤埅鎸囦护鏅傦紝鏄惁鎳夎嚜鍕曢’绀哄湪鍓嶆櫙</string>
+    <string name="pref_title_fossil_hr_nav_vibrate">鏍规摎鏂版寚绀烘尟鍕�</string>
+    <string name="notification_channel_connection_status_name">閫f帴鐙�鎱�</string>
+    <string name="pref_force_connection_type_auto">鑷嫊</string>
+    <string name="pref_force_connection_type_ble">钘嶇墮浣庤兘鑰�</string>
+    <string name="pref_force_connection_type_bt_classic">钘嶇墮缍撳吀</string>
+    <string name="unbind_before_pair_title">宸茬稉缍佸畾</string>
+    <string name="companion_pairing_request_title">浼撮毃瑷倷</string>
+    <string name="bottom_nav_devices">瑷倷</string>
+    <string name="pref_dashboard_first_title">棣栧厛椤ず鍎�琛ㄦ澘</string>
+    <string name="pref_dashboard_first_summary">鐣� Gadgetbridge 鍟熷嫊鏅傞’绀哄剙琛ㄦ澘锛岃�屼笉鏄ō鍌欒灑骞�</string>
+    <string name="pref_dashboard_widget_settings">灏忓伐鍏疯ō瀹�</string>
+    <string name="pref_dashboard_select_devices_title">閬告搰瑷倷...</string>
+    <string name="pref_speak_notifications_focus_exclusive_summary_off">鍦ㄩ�氱煡琚湕璁�鏅傦紝鍏朵粬鎳夌敤绋嬪紡鐨勬挱鏀鹃煶閲忓皣鏈冮檷浣�</string>
+    <string name="pref_title_bottom_navigation_bar">搴曢儴灏庤埅娆�</string>
+    <string name="pref_summary_bottom_navigation_bar_on">浣跨敤灏庤埅娆勬垨姗悜婊戝嫊鍦ㄤ富鐣潰涔嬮枔鍒囨彌</string>
+    <string name="pref_sleepasandroid_features_title">鐗瑰镜</string>
+    <string name="pref_title_huawei_account">鑿偤甯虫埗</string>
+    <string name="toast_camera_photo_taken">鐓х墖宸叉媿鏀濅甫淇濆瓨鏂硷細%s</string>
+    <string name="none">鐒�</string>
+    <string name="loading">杓夊叆涓��</string>
+    <string name="share_debug_info">鍒嗕韩闄ら尟璩囪▕</string>
+    <string name="realtime_settings">鍗虫檪瑷疆</string>
+    <string name="unsupported">涓嶆敮鎻�</string>
+    <string name="max_val">鏈�澶у��: %d</string>
+    <string name="battery_low_threshold">浣庨浕閲忛柧鍊�</string>
+    <string name="smart_ring_measurement_error_unknown">寰炵挵鎺ユ敹鍒版湭鐭ョ殑娓噺閷 %d</string>
+    <string name="prefs_title_gatt_client_allow_gatt_interactions">鍏佽ū閫忛亷 BLE Intent API 閫茶 GATT 浜掑嫊</string>
+    <string name="prefs_summary_gatt_client_api_package">闄愬埗 BLE Intent API 閫氫俊鑷虫濂椾欢</string>
+    <string name="backup_restore_exporting">姝e湪鍖嚭鍒� zip鈥�</string>
+    <string name="backup_restore_exporting_preferences">灏庡嚭鍋忓ソ瑷畾涓��</string>
+    <string name="backup_restore_exporting_database">姝e湪鍖嚭璩囨枡搴��</string>
+    <string name="backup_restore_exporting_files">姝e湪鍖嚭妾旀鈥�</string>
+    <string name="backup_restore_exporting_files_i_of_n">姝e湪灏庡嚭妾旀鈥� %1d 鐨� %2d</string>
+    <string name="backup_restore_exporting_finishing">姝e湪瀹屾垚鍖嚭鈥�</string>
+    <string name="backup_restore_importing">姝e湪寰炲绺獢鍖叆鈥�</string>
+    <string name="backup_restore_importing_loading">姝e湪鍔犺級妾旀鈥�</string>
+    <string name="backup_restore_importing_validating">椹楄瓑妾旀涓��</string>
+    <string name="backup_restore_abort_title">涓</string>
+    <string name="label_distance_total_mph">绺借▓: %.1f 鑻遍噷</string>
+    <string name="activity_type_mass_gymnastics">澶х溇楂旀搷</string>
+    <string name="button_watchface_edit_name">绶ㄨ集鍚嶇ū</string>
+    <string name="button_watchface_settings">琛ㄩ潰瑷疆</string>
+    <string name="watchface_dialog_widget_x_coordinate">X搴ф锛堟渶澶�240锛�</string>
+    <string name="prefs_wena3_item_rich_design">浣跨敤璞愬瘜瑷▓</string>
+    <string name="heartrate_bpm_175">175 姣忓垎閻�</string>
+    <string name="pbwinstallhandler_incorrect_hw_revision">纭珨鐗堟湰涓嶅尮閰嶏紒</string>
+    <string name="activity_type_kendo">鍔嶉亾</string>
+    <string name="activity_type_track_and_field">鐢板緫</string>
+    <string name="activity_type_battle_rope">鎴扮供</string>
+    <string name="watchface_setting_power_saving_hands">鍦ㄦ墜鑵曡劔闆㈡檪绂佺敤鎵嬮儴閬嬪嫊</string>
+    <string name="sony_automatic_power_off_off">璜嬪嬁闂滈枆</string>
+    <string name="sony_automatic_power_off_30_min">30鍒嗛悩</string>
+    <string name="pref_summary_navigation_forward">灏囧皫鑸噳鐢ㄧ▼寮忕殑鎸囩ず杞夌櫦鑷虫墜閷�</string>
+    <string name="pref_force_enable_spo2_support_summary">寮峰埗鍟熺敤 SpO2 鏀彺銆�
+\n鑷摂棰ㄩ毆浣跨敤</string>
+    <string name="contact_missing_name">鑱怠浜哄悕绋辨槸绌虹殑</string>
+    <string name="devicetype_garmin_forerunner_255">Garmin Forerunner 255</string>
+    <string name="menuitem_email">闆诲瓙閮典欢</string>
+    <string name="pref_title_goal_secondary">娆¤鐩</string>
+    <string name="changelog_title">鏈�鏂版秷鎭�</string>
+    <string name="right_earbud">鍙宠�冲</string>
+    <string name="prefs_wena3_vibration_warning">璀﹀憡</string>
+    <string name="prefs_stress_monitoring_title">澹撳姏鐩f脯</string>
+    <string name="prefs_relaxation_reminder_title">鏀鹃瑔鎻愰啋</string>
+    <string name="prefs_activity_monitoring_description">鐣舵墜鐠版娓埌韬珨閬嬪嫊鏅傦紝鑷嫊鎻愰珮蹇冪巼妾㈡脯闋荤巼锛屼互鎻愰珮蹇冪巼鎹曟崏鐨勬簴纰烘�с��</string>
+    <string name="prefs_sounds_summary">閰嶇疆瑷倷浣曟檪鐧煎嚭铚傞炒鑱�</string>
+    <string name="devicestatus_upload_starting">涓婂偝闁嬪</string>
+    <string name="devicestatus_upload_started">涓婂偝宸查枊濮�</string>
+    <string name="devicestatus_disconnecting">瑷倷姝e湪鏂烽枊閫f帴锛�</string>
+    <string name="devicestatus_upload_completed">涓婂偝宸插畬鎴�</string>
+    <string name="devicestatus_upload_validating">涓婂偝姝e湪椹楄瓑涓�</string>
+    <string name="devicetype_amazfit_gts3">Amazfit GTS 3</string>
+    <string name="pref_title_upper_button_function_long">涓婃寜閳曢暦</string>
+    <string name="pref_title_middle_button_function_double">涓枔鎸夐垥闆欐搳</string>
+    <string name="min_val">鏈�灏忓��: %d</string>
+    <string name="show_in_notification">椤ず鍦ㄩ�氱煡涓�</string>
+    <string name="pref_header_sony_device_info">瑷倷璩囪▕</string>
+    <string name="pref_title_touch_spotify_official_app">Spotify锛堝畼鏂规噳鐢ㄧ▼寮忓皥鐢級</string>
+    <string name="pref_huami_truncate_fetch_operation_timestamps_summary">灏囧彇鍥炴搷浣滅殑鏅傞枔鎴虫埅鏂峰埌鍒嗛悩銆傚鏋滃湪鍙栧洖闈炲父鐭殑閸涚厜鏅傞亣鍒板晱椤岋紝璜嬬鐢ㄦ瑷疆浠ヤ繚鎸佹檪闁撴埑鐐虹銆�</string>
+    <string name="mi2_prefs_button_press_broadcast_summary">闅ㄤ簨浠剁櫦閫佺殑寤f挱娑堟伅銆傚弮鏁� `button_id` 鑷嫊娣诲姞鍒版瘡姊濇秷鎭腑銆�</string>
+    <string name="mi2_prefs_button_action">鍟熺敤鎸夐垥鍕曚綔</string>
+    <string name="mi2_prefs_button_action_vibrate">鍟熺敤甯舵尟鍕�</string>
+    <string name="portuguese_pt">钁¤悇鐗欒獮锛堣憽钀勭墮锛�</string>
+    <string name="dbmanagementactivity_delete_activity_data_title">鍒櫎娲诲嫊鏁告摎锛�</string>
+    <string name="title_activity_vibration">鎸嫊</string>
+    <string name="_pebble_watch_reply">鍥炶</string>
+    <string name="activity_type_outdoor_running">鎴跺璺戞</string>
+    <string name="hrv_status_day_avg_legend">姣忔棩骞冲潎 (姣)</string>
+    <string name="hrv_status_seven_days_avg">7澶╁钩鍧�</string>
+    <string name="hrv_status_last_night_highest_5">鏄ㄦ櫄 5 鍒嗛悩鏈�澶у钩鍧�</string>
+    <string name="hrv_status_seven_days_avg_long">7澶╁钩鍧�</string>
+    <string name="devicetype_sony_wi_sp600n">绱㈠凹 WI-SP600N</string>
+    <string name="about_hash">鎻愪氦 %s</string>
+    <string name="pref_activity_recognize_running">瑾嶅彲閬嬭</string>
+    <string name="single_tap">鍠搳</string>
+    <string name="voice_service_package_title">瑾為煶鏈嶅嫏濂楅</string>
+    <string name="pref_display_caller_title">椤ず鑱怠璩囪▕</string>
+    <string name="pref_header_sound">鑱查煶</string>
+    <string name="pref_header_equalizer">鍧囪 鍣�</string>
+    <string name="pref_display_caller_summary">椤ず渚嗛浕鐨勯浕瑭辫櫉纰兼垨濮撳悕</string>
+    <string name="pref_pair_bluetooth_calls_title">钘嶇墮閫氳┍閰嶅皪</string>
+    <string name="pref_pair_bluetooth_calls_summary">榛炴搳閫欒!闁嬪閰嶅皪閬庣▼</string>
+    <string name="pref_summary_notification_media_ignores_application_list">鍦ㄦ噳鐢ㄧ▼寮忓垪琛ㄤ箣鍓嶈檿鐞嗗獟楂旈�氱煡銆傚鏋滄湭鍕鹃伕姝ら伕闋咃紝鍓囬渶瑕佸湪鎳夌敤绋嬪紡鍒楄〃涓厑瑷卞獟楂旀噳鐢ㄧ▼寮忥紝浠ヤ究濯掗珨鎺у埗鑳藉湪瑷倷涓婃甯搁亱浣溿��</string>
+    <string name="pref_title_pebble_sync_morpheuz">鍚屾鑾彶鏂�</string>
+    <string name="pref_summary_enable_outgoing_call">绂佺敤姝ゅ姛鑳戒篃灏囧仠姝� Pebble 2/LE 鍦ㄦ挜鍑洪浕瑭辨檪闇囧嫊</string>
+    <string name="pref_header_pebble_timeline">Pebble 鏅傞枔绶�</string>
+    <string name="pref_camera_remote_title">鐩告閬欐帶鍣�</string>
+    <string name="pref_cover_to_mute">闈滈煶钃�</string>
+    <string name="agps_status_error">閷</string>
+    <string name="prefs_weardirection">绌胯憲鏂瑰悜</string>
+    <string name="miband_prefs_device_time_offset_hours">瑷倷鏅傞枔鍋忕Щ锛堝皬鏅傦級锛堢敤鏂兼娓吉鐝伐浣滆�呯殑鐫$湢锛�</string>
+    <string name="mi2_prefs_heart_rate_monitoring_summary">閰嶇疆蹇冪巼鐩f脯</string>
+    <string name="activity_prefs_height_inches">韬珮锛堣嫳瀵革級</string>
+    <string name="dbmanagementactivity_delete_old_activity_db">鍒櫎鑸婄殑娲诲嫊璩囨枡搴紵</string>
+    <string name="activity_type_horizontal_bar">姗】</string>
+    <string name="description">鎻忚堪</string>
+    <string name="workoutSets">濂椾欢</string>
+    <string name="pref_agps_status">AGPS 鐙�鎱�</string>
+    <string name="alarm_smart_wakeup_interval_default">5鍒嗛悩</string>
+    <string name="chart_get_active_and_synchronize">閫茶涓�浜涙椿鍕曚甫鍚屾瑷倷銆�</string>
+    <string name="prefs_sounds">鑱查煶</string>
+    <string name="do_not_disturb_lift_wrist_summary">鍍呭湪鎻愬崌鍟熺敤鏅傛墠婵�娲婚’绀�</string>
+    <string name="pref_do_not_disturb_not_wear">涓嶇┛钁楁檪璜嬪嬁鎵撴摼</string>
+    <string name="dbmanagementactivity_error_exporting_shared">灏庡嚭鍋忓ソ瑷疆鏅傚嚭閷細%1$s</string>
+    <string name="title_activity_pebble_pairing">Pebble 閰嶅皪</string>
+    <string name="activity_type_cross_country_skiing">瓒婇噹婊戦洩</string>
+    <string name="activity_type_indoor_cycling">瀹ゅ収鑷杌�</string>
+    <string name="activity_error_no_app_for_png">瑕佸垎浜铻㈠箷鎴湒锛岃珛瀹夎鑳借檿鐞嗗奖鍍忔獢妗堢殑鎳夌敤绋嬪紡銆�</string>
+    <string name="devicetype_amazfit_bip5_unity">Amazfit Bip 5 鑱悎鐗�</string>
+    <string name="devicetype_amazfit_trex_2">Amazfit T-Rex 2</string>
+    <string name="devicetype_amazfit_trex_ultra">Amazfit T-Rex Ultra</string>
+    <string name="activity_type_xc_classic_ski">XC缍撳吀婊戦洩</string>
+    <string name="activity_type_boating">鍒掕埞</string>
+    <string name="activity_type_indoor_push_run_speed">瀹ゅ収鎺� - 璺戞閫熷害</string>
+    <string name="gps">GPS</string>
+    <string name="single_band">鍠牷</string>
+    <string name="dual_band">闆欓牷</string>
+    <string name="low_power_gps">浣庡姛鑰桮PS</string>
+    <string name="gps_bds">GPS + BDS</string>
+    <string name="gps_galileo">GPS + GALILEO</string>
+    <string name="all_satellites">鎵�鏈夎鏄�</string>
+    <string name="appmanager_download_app_error">涓嬭級鎳夌敤绋嬪紡鏅傚嚭閷�</string>
+    <string name="fw_upgrade_notice_amazfit_gtr4">鎮ㄥ嵆灏囧湪鎮ㄧ殑 Amazfit GTR 4 涓婂畨瑁� %s 闊岄珨銆�
+\n
+\n瀹夎 .zip 妾旀寰岋紝鎮ㄧ殑鎵嬬挵灏囬噸鏂板暉鍕曘��
+\n
+\n璜嬭嚜琛屾壙鎿旈ⅷ闅�茶鎿嶄綔锛�</string>
+    <string name="fw_upgrade_notice_amazfit_trex2">鎮ㄥ嵆灏囧湪鎮ㄧ殑 Amazfit T-Rex 2 涓婂畨瑁� %s 闊岄珨銆�
+\n
+\n瀹夎 .zip 妾旀寰岋紝鎮ㄧ殑鎵嬬挵灏囬噸鏂板暉鍕曘��
+\n
+\n璜嬭嚜琛屾壙鎿旈ⅷ闅紒</string>
+    <string name="open_fw_installer_info_text_title">妾旀瀹夎绋嬪紡</string>
+    <string name="spo2_perc_90">0.9</string>
+    <string name="spo2_off">闂�</string>
+    <string name="pref_world_clocks_summary">鐐哄叾浠栨檪鍗�閰嶇疆鏅傞悩</string>
+    <string name="pref_contacts_title">鑱怠浜�</string>
+    <string name="function_enabled">鍟熺敤</string>
+    <string name="activity_prefs_goal_fat_burn_time_minutes">姣忔棩鐩锛氱噧鐕掕剛鑲檪闁擄紙鍒嗛悩锛�</string>
+    <string name="activity_type_flexibility">闈堟椿鎬�</string>
+    <string name="activity_type_trampoline">韫﹀簥</string>
+    <string name="activity_type_dumbbell">鍟為埓</string>
+    <string name="activity_type_belly_dance">鑲氱毊鑸�</string>
+    <string name="activity_type_jazz_dance">鐖靛+鑸�</string>
+    <string name="activity_type_ballet">鑺暰</string>
+    <string name="activity_type_other_dance">鍏朵粬鑸炶箞</string>
+    <string name="hr_resting">浼戞伅</string>
+    <string name="hr_maximum">鏈�澶�</string>
+    <string name="hr_minimum">鏈�浣�</string>
+    <string name="miband_prefs_reserve_reminder_calendar">鎻愰啋闋愯▊鍗冲皣鑸夎鐨勬椿鍕�</string>
+    <string name="prefs_enable_find_phone">鍟熺敤銆屽皨鎵炬墜姗熴��</string>
+    <string name="normal">姝e父</string>
+    <string name="kind_watchface">閷堕潰</string>
+    <string name="kind_app">鎳夌敤绋嬪紡</string>
+    <string name="devicetype_miband6">绫冲鎵嬬挵 6</string>
+    <string name="devicetype_miband7pro">灏忕背鏅鸿兘鎵嬬挵 7 Pro</string>
+    <string name="devicetype_miband8">灏忕背鏅鸿兘鎵嬬挵8</string>
+    <string name="devicetype_pebble">Pebble</string>
+    <string name="devicetype_garmin_fenix_7_pro">Garmin Fenix 7 Pro</string>
+    <string name="devicetype_garmin_instinct_2x_solar">Garmin Instinct 2X Solar</string>
+    <string name="devicetype_garmin_instinct_2_soltac">Garmin Instinct 2 SolTac</string>
+    <string name="devicetype_garmin_instinct_crossover">Garmin Instinct Crossover</string>
+    <string name="devicetype_garmin_forerunner_165">Garmin Forerunner 165</string>
+    <string name="devicetype_garmin_forerunner_245">Garmin Forerunner 245</string>
+    <string name="devicetype_garmin_forerunner_245_music">Garmin Forerunner 245 闊虫▊鐗�</string>
+    <string name="devicetype_garmin_forerunner_255_music">Garmin Forerunner 255 闊虫▊鐗�</string>
+    <string name="devicetype_garmin_forerunner_255s">Garmin Forerunner 255S</string>
+    <string name="devicetype_garmin_forerunner_255s_music">Garmin Forerunner 255S 闊虫▊</string>
+    <string name="devicetype_hplus">HPlus</string>
+    <string name="menuitem_dnd">DND</string>
+    <string name="ft">ft</string>
+    <string name="mi">鎴�</string>
+    <string name="activity_info">娲诲嫊璩囪▕</string>
+    <string name="devicetype_garmin_forerunner_955">Garmin Forerunner 955</string>
+    <string name="devicetype_huawei_watch_gt">鑿偤鎵嬮尪 GT</string>
+    <string name="devicetype_huawei_band4pro">鑿偤鎵嬬挵 4 (灏堟キ鐗�)</string>
+    <string name="devicetype_huawei_watchgt2">鑿偤鎵嬮尪 GT 2 (灏堟キ鐗�)</string>
+    <string name="devicetype_lefun">妯傝叮</string>
+    <string name="devicetype_bohemic_smart_bracelet">娉㈠笇绫充簽鏅鸿兘鎵嬬挵</string>
+    <string name="devicetype_vivitar_hr_bp_monitor_activity_tracker">Vivitar HR 鑸� BP 鐩f脯娲诲嫊杩借工鍣�</string>
+    <string name="menuitem_stress_segmented">澹撳姏锛堝垎娈碉級</string>
+    <string name="menuitem_stress_simple">澹撳姏锛堢啊鍠級</string>
+    <string name="menuitem_stress_breakdown">澹撳姏锛堝穿娼帮級</string>
+    <string name="menuitem_pai">PAI</string>
+    <string name="menuitem_spo2">琛�姘ч=鍜屽害</string>
+    <string name="appwidget_setting_alarm">瑷畾楝ч悩鐐� %1$02d:%2$02d</string>
+    <string name="quick_alarm">蹇�熻鍫�</string>
+    <string name="quick_alarm_description">灏忓伐鍏风殑璀﹀牨</string>
+    <string name="qhybrid_time_shift">鏅傞枔杞夌Щ</string>
+    <string name="permission_granting_mandatory">鎵�鏈夐�欎簺娆婇檺閮芥槸蹇呴渶鐨勶紝濡傛灉涓嶆巿浜堝彲鑳芥渻鐧肩敓涓嶇┅瀹氥��</string>
+    <string name="require_location_provider">蹇呴爤鍟熺敤浣嶇疆</string>
+    <string name="error_location_enabled_mandatory">蹇呴爤闁嬪暉浣嶇疆鎵嶈兘鎺冩弿瑷倷</string>
+    <string name="descentMeters">涓嬪潯</string>
+    <string name="minutes_mi">鍒嗛悩/鑻遍噷</string>
+    <string name="strokes_second">瀛椾覆/瀛椾覆</string>
+    <string name="seconds">绉�</string>
+    <string name="milliseconds">姣</string>
+    <string name="swolf_index">swolf 鎸囨暩</string>
+    <string name="pref_title_equalizer_soft">鏌旇粺</string>
+    <string name="prefs_active_noise_cancelling_summary">闃婚殧鍛ㄥ湇鐨勫櫔闊�</string>
+    <string name="prefs_pressure_relief">鐠板鑱查煶鐨勫鍔涢噵鏀�</string>
+    <string name="pressure_relief_summary">闃叉鍦ㄤ笉浣跨敤涓诲嫊鍣煶鍙栨秷鏅傛劅瑕鸿�虫湹鏈夊鍔�</string>
+    <string name="prefs_left">宸�</string>
+    <string name="prefs_right">鍙�</string>
+    <string name="prefs_switch_control_left">鍒囨彌鎺у埗宸�</string>
+    <string name="prefs_switch_control_right">鍒囨彌鎺у埗鍙�</string>
+    <string name="prefs_galaxy_touch_options">瑙告帶閬搁爡</string>
+    <string name="battery_case">闆绘睜娈�</string>
+    <string name="left_earbud">宸﹁�冲</string>
+    <string name="pref_title_touch_quick_ambient">蹇�熺挵澧冭伈闊�</string>
+    <string name="pref_title_touch_spotify_galaxy_app">Spotify锛堝儏闄� Galaxy Wearable 鎳夌敤绋嬪紡锛�</string>
+    <string name="soundcore_voice_prompts">瑾為煶鎻愮ず</string>
+    <string name="steps_streaks_total_days_hint_totals">閬旀垚鐩鐨勫ぉ鏁哥櫨鍒嗘瘮鐩稿皪鏂兼墍鏈夋湁姝ユ暩鐨勫ぉ鏁�</string>
+    <string name="step_streak_days_hint">閫g簩閬旀垚姝ユ暩鐩鐨勫ぉ鏁�</string>
+    <string name="steps_streaks_total_steps_hint">鏁村�嬮�g簩鏈熼枔鐨勭附姝ユ暩</string>
+    <string name="steps_streaks_total_steps_average_hint">姣忔棩骞冲潎%d姝�</string>
+    <string name="step_streaks_achievements_sharing_message">鎴戞瘡澶╃殑姝ユ暩鎴愬氨锛�</string>
+    <string name="step_streaks_achievements_sharing_title">姝ラ鎴愬氨</string>
+    <string name="steps_streaks_total_steps_hint_totals">姝峰彶涓婅閷勭殑绺芥鏁�</string>
+    <string name="voice_service_package_summary">鍖呭惈铏曠悊瑾為煶鍛戒护鐨勬湇鍕欑殑鎳夌敤绋嬪紡</string>
+    <string name="voice_service_class_title">瑾為煶鏈嶅嫏椤炲垾</string>
+    <string name="voice_service_class_summary">鍏ㄦ柟浣嶆湇鍕欒矾寰戣檿鐞嗚獮闊虫寚浠�</string>
+    <string name="voice_service">瑾為煶鏈嶅嫏</string>
+    <string name="pref_app_logs_summary">鍟熺敤鎵嬮尪鎳夌敤绋嬪紡鐨勬棩瑾�</string>
+    <string name="pref_app_logs_start_summary">寰炴墜閷舵噳鐢ㄧ▼寮忛枊濮嬭閷�</string>
+    <string name="pref_app_logs_stop_summary">鍋滄寰炴墜閷舵噳鐢ㄧ▼寮忚閷�</string>
+    <string name="pref_app_connection_duration">鎳夌敤绋嬪紡閫f帴鎸佺簩鏅傞枔</string>
+    <string name="title">妯欓</string>
+    <string name="preview_image">闋愯鍦栫墖</string>
+    <string name="status_icon">鐙�鎱嬪湒绀�</string>
+    <string name="changelog_full_title">璁婃洿鏃ヨ獙</string>
+    <string name="loyalty_cards_catima_not_compatible">瀹夎鐨� Catima 鐗堟湰鑸� Gadgetbridge 涓嶇浉瀹广�傝珛灏� Catima 鍜� Gadgetbridge 鏇存柊鍒版渶鏂扮増鏈��</string>
+    <string name="loyalty_cards_sync_options">鍚屾閬搁爡</string>
+    <string name="loyalty_cards_sync">鍚屾</string>
+    <string name="loyalty_cards_catima_permissions_title">缂哄皯娆婇檺</string>
+    <string name="pref_title_osmand_packagename">OsmAnd 濂椾欢鍚嶇ū</string>
+    <string name="pref_summary_osmand_packagename">鐢ㄦ柤閬告搰瑕侀�f帴鐨凮smAnd鐗堟湰</string>
+    <string name="pref_auto_reply_calls_delay_summary">鑷嫊鎺ヨ伣闆昏┍鐨勭鏁�</string>
+    <string name="title_cycling">楱庤嚜琛岃粖</string>
+    <string name="pref_battery_polling_summary">閫欐槸鏈�浣冲姫鍔涳紝鍙兘鍥犵偤澶氱ó鍘熷洜鑰屽欢閬�</string>
+    <string name="pref_battery_polling_enable">鍟熺敤闆绘睜杓</string>
+    <string name="pref_battery_polling_interval">闆绘睜杓闁撻殧</string>
+    <string name="pref_battery_polling_interval_format">姣� %1$s 鍒嗛悩</string>
+    <string name="battery_polling_failed_start">鐒℃硶鍟熷嫊闆绘睜杓</string>
+    <string name="backup_restore_importing_database">姝e湪鍖叆璩囨枡搴��</string>
+    <string name="backup_restore_importing_preferences">姝e湪鍖叆鍋忓ソ瑷畾鈥�</string>
+    <string name="hrv_status_seven_days_avg_status">鐙�鎱�</string>
+    <string name="hrv_status_last_night">鏄ㄦ櫄</string>
+    <string name="pref_title_nagivation_apps">灏庤埅鎳夌敤绋嬪紡</string>
+    <string name="pref_header_external_integrations">澶栭儴鏁村悎</string>
+    <string name="pref_header_automations">鑷嫊鍖�</string>
+    <string name="pref_description_general">鍓垫キ, 瑾炶█, 鍦板崁, 浣嶇疆</string>
+    <string name="pref_description_about_you">鍑虹敓鏃ユ湡銆佹�у垾銆佽韩楂樸�侀珨閲嶃�佺洰妯�</string>
+    <string name="pref_description_notifications">鎳夌敤绋嬪簭閫氱煡锛岀櫧鍚嶅柈/榛戝悕鍠�</string>
+    <string name="pref_description_user_interface">涓婚锛屼富鐣潰</string>
+    <string name="pref_description_dashboard">灏忓伐鍏凤紝鍖呭惈瑷倷</string>
+    <string name="pref_description_automations">鑷嫊鍖嚭锛岃嚜鍕曠嵅鍙�</string>
+    <string name="pref_description_developer_options">鏃ヨ獙锛屾剰鍦� API</string>
+    <string name="pref_description_deprecated_functionalities">灏囧湪鏈締鐗堟湰涓Щ闄ょ殑瑷畾</string>
+    <string name="pref_header_main_screen">涓荤暙闈�</string>
+    <string name="pref_title_user_interface">浣跨敤鑰呬粙闈�</string>
+    <string name="pref_pair_bluetooth_calls_help_2">2. 鍓嶅線鎮ㄦ墜姗熺殑钘嶇墮瑷畾锛屼甫鑸囧皣椤ず鐨勬柊瑷倷閰嶅皪锛堝悕绋遍浼兼柤鎮ㄧ暥鍓嶇殑鎵嬮尪锛屼絾甯舵湁寰岀洞锛屼緥濡傘�孉mazfit GTR 4 - AFC8銆嶃��</string>
+    <string name="prefs_hr_alarm_low">浣庨檺鍒�</string>
+    <string name="agps_status_missing">缂哄け</string>
+    <string name="hrv_status">HRV 鐙�鎱�</string>
+    <string name="pbw_installhandler_pebble_firmware">Pebble 闊岄珨 %1$s</string>
+    <string name="pbwinstallhandler_correct_hw_revision">姝g⒑鐨勭‖楂斾慨瑷�</string>
+    <string name="seconds_12">12绉�</string>
+    <string name="minutes_45">45鍒嗛悩</string>
+    <string name="minutes_75">75鍒嗛悩</string>
+    <string name="minutes_240">240鍒嗛悩</string>
+    <string name="pref_sleepasandroid_device_title">鎻愪緵鑰呰ō鍌�</string>
+    <string name="pref_sleepasandroid_features_summary">鏀彺鍥犺ō鍌欒�岀暟</string>
+    <string name="pref_sleepasandroid_slot_title">璀﹀牨鎻掓Ы</string>
+    <string name="activity_prefs_date_birth">鍑虹敓鏃ユ湡</string>
+    <string name="activity_type_hiking">鍋ヨ</string>
+    <string name="activity_type_pool_swimming">娓告吵姹犳父娉�</string>
+    <string name="devicetype_garmin_instinct_solar">Garmin Instinct Solar</string>
+    <string name="devicetype_garmin_instinct_2s">Garmin Instinct 2S</string>
+    <string name="gps_glonass">GPS + GLONASS</string>
+    <string name="sony_ambient_sound_level">鐠板鑱查煶姘村钩</string>
+    <string name="pref_header_sony_anc_optimizer">鍣煶娑堥櫎鍎寲鍣�</string>
+    <string name="sony_anc_optimize_title">鍎寲</string>
+    <string name="sony_anc_optimize_description">榛炴搳浠ラ枊濮嬪櫔闊虫秷闄ゅ劒鍖栧櫒銆�</string>
+    <string name="sony_anc_optimize_confirmation_title">鍣煶娑堥櫎鍎寲鍣�</string>
+    <string name="sony_anc_optimize_confirmation_description">鍍忓钩甯镐竴妯d娇鐢ㄨ�虫銆傚鏋滀僵鎴存浠舵垨姘e鏀硅畩锛岃珛鍐嶆閬嬭鍎寲鍣ㄣ��</string>
+    <string name="pref_anc_optimizer_state_pressure">澶ф埃澹�</string>
+    <string name="moondrop_touch_earbud">鑰虫</string>
+    <string name="pref_device_action_dnd_on">璜嬪嬁鎵撴摼 - 闁嬪暉</string>
+    <string name="prefs_workmode">宸ヤ綔妯″紡</string>
+    <string name="huawei_ignore_wakeup_status_end">蹇界暐鍠氶啋绲愭潫鐙�鎱�</string>
+    <string name="step_streak_ongoing">鎸佺簩閫茶涓�</string>
+    <string name="intent_api_broadcast_export_summary">鍦ㄨ硣鏂欏韩鍖嚭瀹屾垚鏅傚唬鎾竴鍊嬫剰鍦�</string>
+    <string name="intent_api_broadcast_activity_sync_title">娲诲嫊鍚屾瀹屾垚鏅傚唬鎾�</string>
+    <string name="intent_api_broadcast_activity_sync_summary">鐣舵椿鍕曞悓姝ュ畬鎴愭檪锛屽悜浠讳綍瑷倷寤f挱涓�鍊嬫剰鍦�</string>
+    <string name="intent_api_allow_debug_commands_summary">鍏佽ū閫氶亷 Intent API 瑙哥櫦瑾胯│鑿滃柈鍛戒护</string>
+    <string name="supercars_turbo_speed_label">娓﹁吉閫熷害</string>
+    <string name="supercars_lights_label">鐕堝厜</string>
+    <string name="intent_api_allow_trigger_export_summary">鍏佽ū閫氶亷 Intent API 瑙哥櫦鏁告摎搴皫鍑�</string>
+    <string name="intent_api_broadcast_export_title">鏁告摎搴皫鍑哄唬鎾�</string>
+    <string name="address">鍦板潃</string>
+    <string name="withings_calibration_text_minutes">鐝惧湪浣跨敤鏃嬮垥灏囧垎閻橀嚌灏嶆簴12榛炪��</string>
+    <string name="withings_bt_calibration_previous">涓婁竴闋�</string>
+    <string name="withings_bt_calibration_next">涓嬩竴姝�</string>
+    <string name="drag_handle">鎷栨洺鎵嬫焺</string>
+    <string name="find_my_phone_found_it">鎵惧埌瀹�</string>
+    <string name="pref_activity_full_sync_trigger_summary">瑙哥櫦鎵�鏈夋椿鍕曟暩鎿氱殑瀹屾暣鍚屾</string>
+    <string name="pref_activity_full_sync_trigger_title">瀹屽叏鍚屾</string>
+    <string name="pref_activity_full_sync_trigger_warning">閫欏皣瑙哥櫦瑷倷涓婃墍鏈夋椿鍕曟暩鎿氱殑瀹屽叏鍚屾銆傞�欏彲鑳介渶瑕佸咕鍒嗛悩鎵嶈兘瀹屾垚銆�</string>
+    <string name="pref_theme_dynamic_colors_not_available_warning">鍕曟厠椤忚壊鍦ㄦ偍鐨勮ō鍌欎笂涓嶅彲鐢紝鍙湁 Android 12 浠ヤ笂鐨勭増鏈敮鎸佹鍔熻兘銆侴adgetbridge 灏囦娇鐢ㄩ粯瑾嶇殑 Material 3 椤忚壊銆�</string>
+    <string name="info_menu_structure_set">鍦ㄨ嫳鍦嬭ō缃殑鑿滃柈绲愭 JSON</string>
+    <string name="error_invalid_menu_structure">鐒℃晥鐨勮彍鍠祼妲� JSON</string>
+    <string name="button_open_menu_companion">鎵撻枊鑿滃柈浼翠径鎳夌敤绋嬪紡</string>
+    <string name="info_menu_structure_removed">鑿滃柈绲愭宸茬Щ闄�</string>
+    <string name="info_fossil_rebuild_watchface_custom_menu">璜嬮噸鏂板缓妲嬫偍鐨勮〃闈互渚胯嚜瑷傝彍鍠�</string>
+    <string name="prefs_home_icon_right_item">鍙�</string>
+    <string name="red">绱呰壊</string>
+    <string name="yellow">榛冭壊</string>
+    <string name="green">缍犺壊</string>
+    <string name="prefs_wena3_home_icon_name_edy">Edy椁橀</string>
+    <string name="prefs_wena3_home_icon_name_pedometer">姝ユ暩</string>
+    <string name="prefs_wena3_home_icon_name_energy">韬珨鑳介噺</string>
+    <string name="prefs_wena3_home_icon_name_calories">鍗¤矾閲�</string>
+    <string name="prefs_wena3_home_icon_name_riiiver">Riiiver</string>
+    <string name="prefs_wena3_menu_icon_name_payment">浠樻</string>
+    <string name="prefs_wena3_menu_icon_title">鑿滃柈鍦栨</string>
+    <string name="prefs_wena3_menu_icon_hint">瑷疆鍦栨绺芥槸椤ず鍦ㄦ渶寰�</string>
+    <string name="prefs_wena3_button_action_name_qrio_lock">Qrio 閹�</string>
+    <string name="prefs_wena3_button_action_name_start_timer">闁嬪瑷堟檪</string>
+    <string name="prefs_wena3_vibration_step_up">鑸炲嚭鎴戜汉鐢�</string>
+    <string name="prefs_wena3_vibration_step_down">閫�浣�</string>
+    <string name="prefs_wena3_vibration_siren">娴峰</string>
+    <string name="prefs_wena3_vibration_short">鐭�</string>
+    <string name="temperature_scale_cf">婧害鍒诲害</string>
+    <string name="temperature_scale_cf_summary">閬告搰瑷倷浣跨敤鏀濇皬鎴栬彲姘忓埢搴︺��</string>
+    <string name="temperature_scale_celsius">鏀濇皬</string>
+    <string name="temperature_scale_fahrenheit">鑿皬</string>
+    <string name="fossil_hr_nav_app_not_installed_notify_title">鎵嬮尪涓婃湭瀹夎灏庤埅鎳夌敤绋嬪紡</string>
+    <string name="fossil_hr_nav_app_not_installed_notify_text">灏庤埅宸查枊濮嬶紝浣嗘墜閷朵笂鏈畨瑁濆皫鑸噳鐢ㄧ▼寮忋�傝珛寰炴噳鐢ㄧ▼寮忕鐞嗗櫒瀹夎瀹冦��</string>
+    <string name="call_rejection_method_reject">鎷掔禃</string>
+    <string name="call_rejection_method_ignore">蹇界暐锛堥潨闊筹級</string>
+    <string name="pref_title_navigation_prefs">灏庤埅鍋忓ソ</string>
+    <string name="pref_title_navigation_apps">灏庤埅鎳夌敤绋嬪紡</string>
+    <string name="pref_title_prefix_notification_with_app">閫氱煡涓殑鎳夌敤鍚嶇ū</string>
+    <string name="pref_summary_prefix_notification_with_app">鍦ㄩ�氱煡妯欓鍓嶅姞涓婁締婧愭噳鐢ㄧ▼寮忕殑鍚嶇ū</string>
+    <string name="pref_navigation_app_osmand">OsmAnd(+)</string>
+    <string name="pref_navigation_app_gmaps">Google 鍦板湒</string>
+    <string name="serial_number">搴忚櫉</string>
+    <string name="menuitem_stats">绲辫▓</string>
+    <string name="menuitem_running">璺戞</string>
+    <string name="menuitem_focus">鐒﹂粸</string>
+    <string name="wake_up_time">閱掍締</string>
+    <string name="pref_sleep_mode_schedule_title">鐫$湢妯″紡鎺掔▼</string>
+    <string name="pref_sleep_mode_schedule_summary">鍦ㄧ潯瑕烘檪闁撶櫦閫佹彁閱掍甫閫插叆鐫$湢妯″紡銆傚湪闋愬畾鐨勯啋渚嗘檪闁擄紝楝ч悩鏈冮熆璧枫��</string>
+    <string name="devicetype_xiaomi_watch_s1">灏忕背鎵嬮尪 S1</string>
+    <string name="devicetype_xiaomi_watch_s3">灏忕背鎵嬮尪 S3</string>
+    <string name="devicetype_xiaomi_watch_s1_active">灏忕背鎵嬮尪 S1 Active</string>
+    <string name="devicetype_xiaomi_watch_s1_pro">灏忕背鎵嬮尪 S1 Pro</string>
+    <string name="widget_layout_top_wide_bot_large">涓婂涓嬪ぇ</string>
+    <string name="widget_layout_top_large_bot_wide">涓婂ぇ涓嬪</string>
+    <string name="widget_layout">灏忓伐鍏蜂綀灞�</string>
+    <string name="widget_subtype">灏忓伐鍏峰瓙椤炲瀷</string>
+    <string name="widget_screen_x">铻㈠箷 %s</string>
+    <string name="widget_move_up">鍚戜笂绉诲嫊</string>
+    <string name="widget_move_down">鍚戜笅绉诲嫊</string>
+    <string name="widget_missing_parts">璜嬮伕鎿囨墍鏈夊皬宸ュ叿</string>
+    <string name="widget_unknown_workout">鏈煡鐨勯亱鍕� - %s</string>
+    <string name="widget_name_untitled">鏈懡鍚嶅皬宸ュ叿 (%1$s)</string>
+    <string name="pref_title_fossil_hr_navigation_instructions">灏庤埅鎸囩ず</string>
+    <string name="pref_title_fossil_hr_nav_foreground">渚嗗埌鍓嶆櫙</string>
+    <string name="warning_missing_notification_permission">鐒℃硶鐧奸�佹寔绾岄�氱煡锛屽洜鐐虹己灏戞瑠闄�</string>
+    <string name="pref_test_features_title">鐗瑰镜</string>
+    <string name="pref_test_features_summary">鐐烘娓│瑷倷鍟熺敤鐨勫姛鑳�</string>
+    <string name="pref_dashboard_widget_today_24h_summary">灏囨椿鍕曢’绀哄湪鍠竴鐨�24灏忔檪鍦撳湀涓紝鑰屼笉鏄洐閲嶇殑12灏忔檪鍦撳湀</string>
+    <string name="pref_dashboard_widget_double_size_summary">鍏佽ū灏忓伐鍏峰湪鍎�琛ㄦ澘涓婁綌鐢ㄥ叐鍊嬫瑒浣�</string>
+    <string name="pref_dashboard_widget_show_legend_summary">鍦ㄥ皬宸ュ叿涓嬫柟椤ず涓�鍊嬪湒渚嬶紝瑙i噵椤忚壊</string>
+    <string name="pref_dashboard_all_devices_summary">灏囨墍鏈夋柊澧炶ō鍌欑殑娲诲嫊鏁告摎鍚堜降锛屼互渚垮湪鍎�琛ㄦ澘涓婇’绀虹附鏁�</string>
+    <string name="pref_dashboard_select_devices_summary">灏囩壒瀹氳ō鍌欑殑娲诲嫊鏁告摎鍚堜降浠ラ’绀哄剙琛ㄦ澘涓婄殑绺借▓</string>
+    <string name="pref_dashboard_widget_today_hr_interval_title">蹇冪巼闁撻殧</string>
+    <string name="pref_dashboard_widget_today_hr_interval_summary">鍦栬〃椤ず姣忔鎴愬姛蹇冪巼娓噺寰岀殑銆屼僵鎴淬�嶆檪闁擄紙鍒嗛悩锛�</string>
+    <string name="pref_auto_reply_calls_summary">闆昏┍灏囪嚜鍕曟帴鑱戒締闆�</string>
+    <string name="error_no_cycling_sensor_found">鏈壘鍒拌嚜琛岃粖鎰熸噳鍣�</string>
+    <string name="hebrew">甯屼集渚嗚獮</string>
+    <string name="activity_type_modern_dance">鐝句唬鑸�</string>
+    <string name="active_calories">娲诲嫊鍗¤矾閲�</string>
+    <string name="active_calories_short">娲诲嫊</string>
+    <string name="activity_prefs_goal_active_calories_burnt">姣忔棩鐩锛氭椿鍕曟秷鑰楃殑鍗¤矾閲�</string>
+    <string name="workout_set_repetitions_weight_kg">%1d x %2$.2f 鍏枻</string>
+    <string name="pref_title_calendar_lookahead">鎻愬墠澶╂暩</string>
+    <string name="reminder_time_every_year">%1$s锛屾瘡骞�</string>
+    <string name="fmtPaceAverage">骞冲潎閰嶉��</string>
+    <string name="breaths_per_min">鍛煎惛/鍒嗛悩</string>
+    <string name="reminder_time_every_month">%1$s锛屾瘡鏈�</string>
+    <string name="ascentDistance">涓婂潯璺濋洟</string>
+    <string name="paceCorrection">鏇存</string>
+    <string name="milliseconds_ms">姣</string>
+    <string name="activity_type_long_jump">璺抽仩</string>
+    <string name="reminder_time_once">%1$s锛屽柈娆�</string>
+    <string name="active_calories_goal">娲诲嫊鐩</string>
+    <string name="total_calories_burnt">绺藉叡娑堣��</string>
+    <string name="workout_set_repetitions_weight_lbs">%1d x %2$.2f 纾�</string>
+    <string name="pref_header_heartrate_allday">鍏ㄥぉ鐩f脯</string>
+    <string name="hrZoneThreshold">闁惧��</string>
+    <string name="pref_voice_passthrough_enabled"></string>
+    <string name="music_upload_info">鎮ㄥ皣瑕佷笂鍌充互涓嬮煶妯傛獢妗圽n\n%1$s\n姝屾洸妯欓锛�%2$s\n灏堣集锛�%3$s\n</string>
+    <string name="hrv_status_unit">%1$d 姣</string>
+    <string name="min_respiration_rate">鏈�灏忓懠鍚搁牷鐜�</string>
+    <string name="hrZoneEasy">瀹规槗</string>
+    <string name="average_respiration_rate">鍛煎惛闋荤巼</string>
+    <string name="max_respiration_rate">鏈�澶у懠鍚搁牷鐜�</string>
+    <string name="pref_summary_sync_birthdays">鍦ㄥ悓姝ユ棩鏇嗕簨浠舵檪鍚屾鑱公浜虹敓鏃�</string>
+    <string name="pref_title_sync_birthdays">鍚屾鐢熸棩</string>
+    <string name="notif_battery_low">%1$s 闆婚噺浣�</string>
+    <string name="busy_task_fetch_hrv_data">鍙栧緱蹇冪巼璁婄暟搴﹁硣鏂�</string>
+    <string name="first_start_welcome_title">姝¤繋</string>
+    <string name="permission_receive_sms_title">鎺ユ敹绨¤▕</string>
+    <string name="permission_receive_sms_summary">灏囩啊瑷婅綁鐧艰嚦瑁濈疆</string>
+    <string name="permission_send_sms_title">鐧奸�佺啊瑷�</string>
+    <string name="permission_send_sms_summary">寰炶缃笂鐧奸�佺啊瑷婏紙闋愯ō鍥炶锛�</string>
+    <string name="permission_read_call_log_title">璁�鍙栭�氳┍瑷橀寗</string>
+    <string name="permission_read_call_log_summary">杞夌櫦閫氳┍瑷橀寗鑷宠缃�</string>
+    <string name="permission_read_phone_state_title">璁�鍙栨墜姗熺媭鎱�</string>
+    <string name="permission_call_phone_title">鎾ユ墦闆昏┍</string>
+    <string name="DEVINFO_HR_VER">"蹇冪巼锛� "</string>
+    <string name="reminder_time_every_day">%1$s锛屾瘡鏃�</string>
+    <string name="pref_summary_physical_buttons">瑾挎暣鎵嬮尪涓婂楂旀寜閸电殑鍔熻兘</string>
+    <string name="notif_battery_low_extended">%1$s 闆婚噺浣庯細%2$s</string>
+    <string name="steps_distance_unit">%1$,.2f 鍏噷</string>
+    <string name="num_min">%d 鍒嗛悩</string>
+    <string name="title_activity_musicmanager">闊虫▊绠$悊鍣�</string>
+    <string name="pref_summary_calendar_lookahead">鍚屾鏈�澶� %1s 澶╃殑鏃ユ泦浜嬩欢</string>
+    <string name="reminder_time_every_week">%1$s锛屾瘡閫�</string>
+    <string name="notif_battery_full">%1$s 闆绘睜宸插厖椋�</string>
+    <string name="hrv_status_baseline">%1$d-%2$d 姣</string>
+    <string name="music_add_to_playlist">鍔犲叆鎾斁娓呭柈</string>
+    <string name="music_delete_from_playlist">寰炴挱鏀炬竻鍠埅闄�</string>
+    <string name="music_delete">鍒櫎姝屾洸</string>
+    <string name="music_all_songs">鎵�鏈夋瓕鏇�</string>
+    <string name="music_new_playlist">鏂板鎾斁娓呭柈</string>
+    <string name="music_rename_playlist">閲嶆柊鍛藉悕鎾斁娓呭柈</string>
+    <string name="hrZoneMaximum">鏈�澶�</string>
+    <string name="workout_set_reps">閲嶈娆℃暩</string>
+    <string name="fossil_hr_auth_failed">椹楄瓑澶辨晽锛屽姛鑳藉彈闄�</string>
+</resources>
\ No newline at end of file
diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml
new file mode 100644
index 0000000..5205831
--- /dev/null
+++ b/app/src/main/res/values/arrays.xml
@@ -0,0 +1,4490 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <string-array name="empty_array">
+        <!-- empty array -->
+    </string-array>
+
+    <string-array name="pref_theme_options">
+        <item>@string/pref_theme_system</item>
+        <item>@string/pref_theme_light</item>
+        <item>@string/pref_theme_dark</item>
+        <item>@string/pref_theme_dynamic</item>
+    </string-array>
+    <string-array name="pref_theme_values">
+        <item>@string/pref_theme_value_system</item>
+        <item>@string/pref_theme_value_light</item>
+        <item>@string/pref_theme_value_dark</item>
+        <item>@string/pref_theme_value_dynamic</item>
+    </string-array>
+
+    <!-- https://meta.wikimedia.org/wiki/Template:List_of_language_names_ordered_by_code -->
+    <string-array name="pref_language_options">
+        <item name="default">System Default</item>
+        <item name="id">Bahasa Indonesia</item>
+        <item name="ca">Catal脿</item>
+        <item name="cs">膶e拧tina</item>
+        <item name="de">Deutsch</item>
+        <item name="et">Eesti</item>
+        <item name="en">English</item>
+        <item name="en_GB">English (United Kingdom)</item>
+        <item name="es">Espa帽ol</item>
+        <item name="eu">Euskara</item>
+        <item name="fr">Fran莽ais</item>
+        <item name="fr_CA">Fran莽ais (Canadien)</item>
+        <item name="gl">Galego</item>
+        <item name="hr">Hrvatski</item>
+        <item name="ia">Interlingua</item>
+        <item name="it">Italiano</item>
+        <item name="lv">Latvie拧u</item>
+        <item name="lt">Lietuvi懦</item>
+        <item name="hu">Magyar</item>
+        <item name="my">Myanmasa</item>
+        <item name="nl">Nederlands</item>
+        <item name="nb_NO">Norsk (bokm氓l)</item>
+        <item name="pl">Polski</item>
+        <item name="pt">Portugu锚s</item>
+        <item name="pt_BR">Portugu锚s (Brasil)</item>
+        <item name="ro">Rom芒n膬</item>
+        <item name="sk">Sloven膷ina</item>
+        <item name="fi">Suomi</item>
+        <item name="sv">Svenska</item>
+        <item name="vi">Ti岷縩g Vi峄噒</item>
+        <item name="tr">T眉rk莽e</item>
+        <item name="el">螘位位畏谓喂魏维</item>
+        <item name="bg">袘褗谢谐邪褉褋泻懈</item>
+        <item name="ru">袪褍褋褋泻懈泄</item>
+        <item name="sr">小褉锌褋泻懈</item>
+        <item name="uk">校泻褉邪褩薪褋褜泻邪</item>
+        <item name="ka">醿メ儛醿犪儣醿a儦醿�</item>
+        <item name="he">注讘专讬转</item>
+        <item name="ar">丕賱毓乇亘賷丞</item>
+        <item name="fa">賮丕乇爻蹖</item>
+        <item name="hi">啶灌た啶ㄠ啶︵</item>
+        <item name="ta">喈む喈苦喁�</item>
+        <item name="ml">啻床啻淳啻赤磦</item>
+        <item name="th">喙勦笚喔� / Phasa Thai</item>
+        <item name="ko">頃滉淡鞏�</item>
+        <item name="ja">鏃ユ湰瑾�</item>
+        <item name="zh_CN">绠�浣撲腑鏂�</item>
+        <item name="zh_TW">绻侀珨涓枃</item>
+    </string-array>
+
+    <string-array name="pref_language_values">
+        <item>default</item>
+        <item>id</item>
+        <item>ca</item>
+        <item>cs</item>
+        <item>de</item>
+        <item>et</item>
+        <item>en</item>
+        <item>en_GB</item>
+        <item>es</item>
+        <item>eu</item>
+        <item>fr</item>
+        <item>fr_CA</item>
+        <item>gl</item>
+        <item>hr</item>
+        <item>ia</item>
+        <item>it</item>
+        <item>lv</item>
+        <item>lt</item>
+        <item>hu</item>
+        <item>my</item>
+        <item>nl</item>
+        <item>nb_NO</item>
+        <item>pl</item>
+        <item>pt</item>
+        <item>pt_BR</item>
+        <item>ro</item>
+        <item>sk</item>
+        <item>fi</item>
+        <item>sv</item>
+        <item>vi</item>
+        <item>tr</item>
+        <item>el</item>
+        <item>bg</item>
+        <item>ru</item>
+        <item>sr</item>
+        <item>uk</item>
+        <item>ka</item>
+        <item>he</item>
+        <item>ar</item>
+        <item>fa</item>
+        <item>hi</item>
+        <item>ta</item>
+        <item>ml</item>
+        <item>th</item>
+        <item>ko</item>
+        <item>ja</item>
+        <item>zh_CN</item>
+        <item>zh_TW</item>
+    </string-array>
+
+    <string-array name="pref_fitpro_language_options">
+        <item>@string/automatic</item>
+        <item>@string/simplified_chinese</item>
+        <item>@string/english</item>
+        <item>@string/turkish</item>
+        <item>@string/polish</item>
+        <item>@string/portuguese</item>
+        <item>@string/russian</item>
+        <item>@string/spanish</item>
+        <item>@string/german</item>
+        <item>@string/french</item>
+        <item>@string/czesh</item>
+    </string-array>
+
+    <string-array name="pref_fitpro_language_values">
+        <item>auto</item>
+        <item>zh_CN</item>
+        <item>en_US</item>
+        <item>tr_TR</item>
+        <item>pl_PL</item>
+        <item>pt_BR</item>
+        <item>ru_RU</item>
+        <item>es_ES</item>
+        <item>de_DE</item>
+        <item>fr_FR</item>
+        <item>cs_CZ</item>
+    </string-array>
+
+    <string-array name="notification_mode">
+        <item>@string/always</item>
+        <item>@string/when_screen_off</item>
+        <item>@string/never</item>
+    </string-array>
+    <string-array name="notification_mode_values">
+        <item>always</item>
+        <item>when_screen_off</item>
+        <item>never</item>
+    </string-array>
+
+    <string-array name="notification_mode_toggle">
+        <item>@string/always</item>
+        <item>@string/never</item>
+    </string-array>
+    <string-array name="notification_mode_values_toggle">
+        <item>always</item>
+        <item>never</item>
+    </string-array>
+
+    <string-array name="language_only_EN_CH">
+        <item name="0">@string/simplified_chinese</item>
+        <item name="1">@string/english</item>
+    </string-array>
+    <string-array name="language_only_EN_CH_values">
+        <item>0</item>
+        <item>1</item>
+    </string-array>
+    <string-array name="sensors_bp_cal">
+        <item name="0">@string/prefs_sensors_button_bp_calibration</item>
+        <item name="1">@string/Cancel</item>
+    </string-array>
+    <string-array name="sensors_bp_cal_values">
+        <item>0</item>
+        <item>1</item>
+    </string-array>
+
+    <string-array name="power_mode">
+        <item name="0">@string/power_mode_normal</item>
+        <item name="1">@string/power_mode_saving</item>
+        <item name="2">@string/power_mode_watch</item>
+    </string-array>
+    <string-array name="power_mode_values">
+        <item>0</item>
+        <item>1</item>
+        <item>2</item>
+    </string-array>
+
+    <string-array name="gender">
+        <item name="1">@string/male</item>
+        <item name="0">@string/female</item>
+        <item name="2">@string/other</item>
+    </string-array>
+    <string-array name="gender_values">
+        <item>1</item>
+        <item>0</item>
+        <item>2</item>
+    </string-array>
+    <string-array name="wearside">
+        <item>@string/left</item>
+        <item>@string/right</item>
+    </string-array>
+    <string-array name="wearside_values">
+        <item>left</item>
+        <item>right</item>
+    </string-array>
+
+    <string-array name="weardirection">
+        <item>@string/buttons_on_left</item>
+        <item>@string/buttons_on_right</item>
+    </string-array>
+    <string-array name="weardirection_values">
+        <item>buttons_on_left</item>
+        <item>buttons_on_right</item>
+    </string-array>
+
+    <string-array name="wearmode">
+        <item>@string/wearmode_band</item>
+        <item>@string/wearmode_pebble</item>
+        <item>@string/wearmode_necklace</item>
+    </string-array>
+    <string-array name="wearmode_values">
+        <item>band</item>
+        <item>pebble</item>
+        <item>necklace</item>
+    </string-array>
+
+    <string-array name="orientation">
+        <item>@string/horizontal</item>
+        <item>@string/vertical</item>
+    </string-array>
+    <string-array name="orientation_values">
+        <item>horizontal</item>
+        <item>vertical</item>
+    </string-array>
+
+    <string-array name="vibration_profile">
+        <item>@string/vibration_profile_default</item>
+        <item>@string/vibration_profile_staccato</item>
+        <item>@string/vibration_profile_short</item>
+        <item>@string/vibration_profile_medium</item>
+        <item>@string/vibration_profile_long</item>
+        <item>@string/vibration_profile_waterdrop</item>
+        <item>@string/vibration_profile_ring</item>
+        <item>@string/vibration_profile_alarm_clock</item>
+    </string-array>
+    <string-array name="vibration_profile_values">
+        <!-- special case - default -->
+        <item>@string/p_default</item>
+        <!-- Note: corresponds with IDs in VibrationProfile -->
+        <item>@string/p_staccato</item>
+        <item>@string/p_short</item>
+        <item>@string/p_medium</item>
+        <item>@string/p_long</item>
+        <item>@string/p_waterdrop</item>
+        <item>@string/p_ring</item>
+        <item>@string/p_alarm_clock</item>
+    </string-array>
+
+    <string-array name="phone_silent_mode">
+        <item>@string/silent_mode_normal_vibrate</item>
+        <item>@string/silent_mode_normal_silent</item>
+        <item>@string/silent_mode_vibrate_silent</item>
+    </string-array>
+    <string-array name="phone_silent_mode_values">
+        <item>normal_vibrate</item>
+        <item>normal_silent</item>
+        <item>vibrate_silent</item>
+    </string-array>
+
+    <string-array name="pebble_activitytracker">
+        <item name="4">Pebble Health</item>
+        <item name="3">Misfit</item>
+        <item name="1">Morpheuz</item>
+    </string-array>
+
+    <string-array name="pebble_activitytracker_values">
+        <item>4</item>
+        <item>3</item>
+        <item>1</item>
+    </string-array>
+
+    <string-array name="pebble_privacymode">
+        <item name="off">@string/pref_pebble_privacy_mode_off</item>
+        <item name="content">@string/pref_pebble_privacy_mode_content</item>
+        <item name="complete">@string/pref_pebble_privacy_mode_complete</item>
+    </string-array>
+
+    <string-array name="pebble_privacymode_values">
+        <item>@string/p_pebble_privacy_mode_off</item>
+        <item>@string/p_pebble_privacy_mode_content</item>
+        <item>@string/p_pebble_privacy_mode_complete</item>
+    </string-array>
+
+    <string-array name="prefs_find_phone">
+        <item>@string/off</item>
+        <item>@string/on</item>
+        <item>@string/maximum_duration</item>
+    </string-array>
+
+    <string-array name="prefs_find_phone_values">
+        <item>@string/p_off</item>
+        <item>@string/p_on</item>
+        <item>@string/p_scheduled</item>
+    </string-array>
+
+    <string-array name="mi2_dateformats">
+        <item>@string/dateformat_time</item>
+        <item>@string/dateformat_date_time</item>
+    </string-array>
+    <string-array name="mi2_dateformats_values">
+        <item>@string/p_dateformat_time</item>
+        <item>@string/p_dateformat_datetime</item>
+    </string-array>
+
+    <string-array name="dateformats">
+        <item>dd.MM.yyyy</item>
+        <item>dd/MM/yyyy</item>
+        <item>MM/dd/yyyy</item>
+    </string-array>
+
+    <string-array name="dateformats_values">
+        <item>dd.MM.yyyy</item>
+        <item>dd/MM/yyyy</item>
+        <item>MM/dd/yyyy</item>
+    </string-array>
+
+    <string-array name="dateformats_2">
+        <item>YYYY/MM/DD</item>
+        <item>DD/MM/YYYY</item>
+        <item>MM/DD/YYYY</item>
+    </string-array>
+    <string-array name="dateformats_2_values">
+        <item>YYYY/MM/DD</item>
+        <item>DD/MM/YYYY</item>
+        <item>MM/DD/YYYY</item>
+    </string-array>
+
+    <string-array name="dateformat_day_month_order_entries">
+        <item>@string/automatic</item>
+        <item>@string/dateformat_day_month</item>
+        <item>@string/dateformat_month_day</item>
+    </string-array>
+    <string-array name="dateformat_day_month_order_values">
+        <item>@string/p_dateformat_auto</item>
+        <item>@string/p_dateformat_day_month</item>
+        <item>@string/p_dateformat_month_day</item>
+    </string-array>
+
+    <string-array name="do_not_disturb_no_auto">
+        <item>@string/mi2_dnd_off</item>
+        <item>@string/mi2_dnd_scheduled</item>
+    </string-array>
+    <string-array name="do_not_disturb_no_auto_values">
+        <item>@string/p_off</item>
+        <item>@string/p_scheduled</item>
+    </string-array>
+
+    <string-array name="mi2_do_not_disturb">
+        <item>@string/mi2_dnd_off</item>
+        <item>@string/mi2_dnd_automatic</item>
+        <item>@string/mi2_dnd_scheduled</item>
+    </string-array>
+    <string-array name="do_not_disturb_allday">
+        <item>@string/mi2_dnd_off</item>
+        <item>@string/dnd_all_day</item>
+        <item>@string/mi2_dnd_scheduled</item>
+    </string-array>
+    <string-array name="mi2_do_not_disturb_values">
+        <item>@string/p_off</item>
+        <item>@string/p_automatic</item>
+        <item>@string/p_scheduled</item>
+    </string-array>
+
+    <string-array name="do_not_disturb_with_always">
+        <item>@string/mi2_dnd_off</item>
+        <item>@string/mi2_dnd_always</item>
+        <item>@string/mi2_dnd_automatic</item>
+        <item>@string/mi2_dnd_scheduled</item>
+    </string-array>
+    <string-array name="do_not_disturb_with_always_values">
+        <item>@string/p_off</item>
+        <item>@string/p_always</item>
+        <item>@string/p_automatic</item>
+        <item>@string/p_scheduled</item>
+    </string-array>
+
+    <string-array name="zetime_do_not_disturb">
+        <item>@string/mi2_dnd_off</item>
+        <item>@string/mi2_dnd_scheduled</item>
+    </string-array>
+    <string-array name="zetime_do_not_disturb_values">
+        <item>@string/p_off</item>
+        <item>@string/p_scheduled</item>
+    </string-array>
+
+    <string-array name="always_on_display">
+        <item>@string/off</item>
+        <item>@string/always</item>
+        <item>@string/mi2_dnd_automatic</item>
+        <item>@string/mi2_dnd_scheduled</item>
+    </string-array>
+    <string-array name="always_on_display_values">
+        <item>@string/p_off</item>
+        <item>@string/p_always</item>
+        <item>@string/p_automatic</item>
+        <item>@string/p_scheduled</item>
+    </string-array>
+
+    <string-array name="activate_display_on_lift">
+        <item>@string/off</item>
+        <item>@string/on</item>
+        <item>@string/mi2_dnd_scheduled</item>
+    </string-array>
+    <string-array name="activate_display_on_lift_values">
+        <item>@string/p_off</item>
+        <item>@string/p_on</item>
+        <item>@string/p_scheduled</item>
+    </string-array>
+
+    <string-array name="activate_display_on_lift_with_smart">
+        <item>@string/off</item>
+        <item>@string/on</item>
+        <item>@string/mi2_dnd_scheduled</item>
+        <item>@string/smart</item>
+    </string-array>
+    <string-array name="activate_display_on_lift_with_smart_values">
+        <item>@string/p_off</item>
+        <item>@string/p_on</item>
+        <item>@string/p_scheduled</item>
+        <item>@string/p_smart</item>
+    </string-array>
+
+    <string-array name="activate_display_on_lift_sensitivity">
+        <item>@string/normal</item>
+        <item>@string/sensitive</item>
+    </string-array>
+    <string-array name="activate_display_on_lift_sensitivity_values">
+        <item>@string/p_normal</item>
+        <item>@string/p_sensitive</item>
+    </string-array>
+
+    <string-array name="mi3_night_mode">
+        <item>@string/off</item>
+        <!--
+        <item>@string/mi3_night_mode_sunset</item>
+        -->
+        <item>@string/mi2_dnd_scheduled</item>
+    </string-array>
+    <string-array name="mi3_night_mode_values">
+        <item>@string/p_off</item>
+        <!--
+        <item>@string/p_sunset</item>
+        -->
+        <item>@string/p_scheduled</item>
+    </string-array>
+
+    <string-array name="pref_mi2_display_items">
+        <item>@string/chart_steps</item>
+        <item>@string/distance</item>
+        <item>@string/calories</item>
+        <item>@string/heart_rate</item>
+        <item>@string/battery</item>
+    </string-array>
+    <string-array name="pref_mi2_display_items_values">
+        <item>@string/p_steps</item>
+        <item>@string/p_distance</item>
+        <item>@string/p_calories</item>
+        <item>@string/p_heart_rate</item>
+        <item>@string/p_battery</item>
+    </string-array>
+    <string-array name="pref_mi2_display_items_default">
+        <item>@string/p_steps</item>
+        <item>@string/p_distance</item>
+        <item>@string/p_calories</item>
+        <item>@string/p_heart_rate</item>
+        <item>@string/p_battery</item>
+    </string-array>
+
+    <string-array name="pref_miband3_display_items">
+        <item>@string/menuitem_notifications</item>
+        <item>@string/menuitem_weather</item>
+        <item>@string/menuitem_activity</item>
+        <item>@string/menuitem_more</item>
+        <item>@string/menuitem_status</item>
+        <item>@string/heart_rate</item>
+        <item>@string/menuitem_timer</item>
+        <item>@string/menuitem_nfc</item>
+    </string-array>
+
+    <string-array name="pref_miband3_display_items_values">
+        <item>@string/p_menuitem_notifications</item>
+        <item>@string/p_menuitem_weather</item>
+        <item>@string/p_menuitem_activity</item>
+        <item>@string/p_menuitem_more</item>
+        <item>@string/p_menuitem_status</item>
+        <item>@string/p_heart_rate</item>
+        <item>@string/p_menuitem_timer</item>
+        <item>@string/p_menuitem_nfc</item>
+    </string-array>
+
+    <string-array name="pref_miband3_display_items_default">
+        <item>@string/p_menuitem_notifications</item>
+        <item>@string/p_menuitem_weather</item>
+        <item>@string/p_menuitem_activity</item>
+        <item>@string/p_menuitem_more</item>
+        <item>@string/p_menuitem_status</item>
+        <item>@string/p_heart_rate</item>
+    </string-array>
+
+    <string-array name="pref_miband5_display_items">
+        <item>@string/menuitem_status</item>
+        <item>@string/menuitem_pai</item>
+        <item>@string/menuitem_hr</item>
+        <item>@string/menuitem_notifications</item>
+        <item>@string/menuitem_breathing</item>
+        <item>@string/menuitem_eventreminder</item>
+        <item>@string/menuitem_weather</item>
+        <item>@string/menuitem_workout</item>
+        <item>@string/menuitem_more</item>
+        <item>@string/menuitem_stress</item>
+        <item>@string/menuitem_cycles</item>
+        <item>@string/menuitem_nfc</item>
+    </string-array>
+
+    <string-array name="pref_miband5_display_items_values">
+        <item>@string/p_menuitem_status</item>
+        <item>@string/p_menuitem_pai</item>
+        <item>@string/p_menuitem_hr</item>
+        <item>@string/p_menuitem_notifications</item>
+        <item>@string/p_menuitem_breathing</item>
+        <item>@string/p_menuitem_eventreminder</item>
+        <item>@string/p_menuitem_weather</item>
+        <item>@string/p_menuitem_workout</item>
+        <item>@string/p_menuitem_more</item>
+        <item>@string/p_menuitem_stress</item>
+        <item>@string/p_menuitem_cycles</item>
+        <item>@string/p_menuitem_nfc</item>
+    </string-array>
+
+    <string-array name="pref_miband5_display_items_default">
+        <item>@string/p_menuitem_status</item>
+        <item>@string/p_menuitem_pai</item>
+        <item>@string/p_menuitem_hr</item>
+        <item>@string/p_menuitem_notifications</item>
+        <item>@string/p_menuitem_breathing</item>
+        <item>@string/p_menuitem_eventreminder</item>
+        <item>@string/p_menuitem_weather</item>
+        <item>@string/p_menuitem_workout</item>
+        <item>@string/p_menuitem_more</item>
+        <item>@string/p_menuitem_stress</item>
+        <item>@string/p_menuitem_cycles</item>
+        <item>@string/p_menuitem_nfc</item>
+    </string-array>
+
+    <string-array name="pref_miband5_shortcuts">
+        <item>@string/menuitem_notifications</item>
+        <item>@string/menuitem_weather</item>
+        <item>@string/menuitem_music</item>
+        <item>@string/menuitem_timer</item>
+        <item>@string/menuitem_alarm</item>
+        <item>@string/menuitem_findphone</item>
+        <item>@string/menuitem_worldclock</item>
+        <item>@string/menuitem_status</item>
+        <item>@string/menuitem_pai</item>
+        <item>@string/menuitem_hr</item>
+        <item>@string/menuitem_breathing</item>
+        <item>@string/menuitem_stress</item>
+        <item>@string/menuitem_eventreminder</item>
+        <item>@string/menuitem_dnd</item>
+        <item>@string/menuitem_stopwatch</item>
+        <item>@string/menuitem_workout</item>
+        <item>@string/menuitem_mutephone</item>
+        <item>@string/menuitem_cycles</item>
+        <item>@string/menuitem_takephoto</item>
+        <item>@string/menuitem_nfc</item>
+    </string-array>
+
+    <string-array name="pref_miband5_shortcuts_values">
+        <item>@string/p_menuitem_notifications</item>
+        <item>@string/p_menuitem_weather</item>
+        <item>@string/p_menuitem_music</item>
+        <item>@string/p_menuitem_timer</item>
+        <item>@string/p_menuitem_alarm</item>
+        <item>@string/p_menuitem_findphone</item>
+        <item>@string/p_menuitem_worldclock</item>
+        <item>@string/p_menuitem_status</item>
+        <item>@string/p_menuitem_pai</item>
+        <item>@string/p_menuitem_hr</item>
+        <item>@string/p_menuitem_breathing</item>
+        <item>@string/p_menuitem_stress</item>
+        <item>@string/p_menuitem_eventreminder</item>
+        <item>@string/p_menuitem_dnd</item>
+        <item>@string/p_menuitem_stopwatch</item>
+        <item>@string/p_menuitem_workout</item>
+        <item>@string/p_menuitem_mutephone</item>
+        <item>@string/p_menuitem_cycles</item>
+        <item>@string/p_menuitem_takephoto</item>
+        <item>@string/p_menuitem_nfc</item>
+    </string-array>
+
+    <string-array name="pref_miband5_shortcuts_default">
+        <item>@string/p_menuitem_notifications</item>
+        <item>@string/p_menuitem_weather</item>
+        <item>@string/p_menuitem_music</item>
+    </string-array>
+
+    <string-array name="pref_miband6_display_items">
+        <item>@string/menuitem_status</item>
+        <item>@string/menuitem_hr</item>
+        <item>@string/menuitem_workout</item>
+        <item>@string/menuitem_weather</item>
+        <item>@string/menuitem_notifications</item>
+        <item>@string/menuitem_alarm</item>
+        <item>@string/menuitem_takephoto</item>
+        <item>@string/menuitem_pomodoro</item>
+        <item>@string/menuitem_music</item>
+        <item>@string/menuitem_stopwatch</item>
+        <item>@string/menuitem_timer</item>
+        <item>@string/menuitem_findphone</item>
+        <item>@string/menuitem_mutephone</item>
+        <item>@string/menuitem_settings</item>
+        <item>@string/menuitem_activity</item>
+        <item>@string/menuitem_eventreminder</item>
+        <item>@string/menuitem_pai</item>
+        <item>@string/menuitem_sleep</item>
+        <item>@string/menuitem_worldclock</item>
+        <item>@string/menuitem_stress</item>
+        <item>@string/menuitem_cycles</item>
+        <item>@string/menuitem_spo2</item>
+        <item>@string/menuitem_breathing</item>
+        <item>@string/menuitem_flashlight</item>
+        <item>@string/menuitem_alipay</item>
+        <item>@string/menuitem_nfc</item>
+    </string-array>
+
+    <string-array name="pref_miband6_display_items_values">
+        <item>@string/p_menuitem_status</item>
+        <item>@string/p_menuitem_hr</item>
+        <item>@string/p_menuitem_workout</item>
+        <item>@string/p_menuitem_weather</item>
+        <item>@string/p_menuitem_notifications</item>
+        <item>@string/p_menuitem_alarm</item>
+        <item>@string/p_menuitem_takephoto</item>
+        <item>@string/p_menuitem_pomodoro</item>
+        <item>@string/p_menuitem_music</item>
+        <item>@string/p_menuitem_stopwatch</item>
+        <item>@string/p_menuitem_timer</item>
+        <item>@string/p_menuitem_findphone</item>
+        <item>@string/p_menuitem_mutephone</item>
+        <item>@string/p_menuitem_settings</item>
+        <item>@string/p_menuitem_activity</item>
+        <item>@string/p_menuitem_eventreminder</item>
+        <item>@string/p_menuitem_pai</item>
+        <item>@string/p_menuitem_sleep</item>
+        <item>@string/p_menuitem_worldclock</item>
+        <item>@string/p_menuitem_stress</item>
+        <item>@string/p_menuitem_cycles</item>
+        <item>@string/p_menuitem_spo2</item>
+        <item>@string/p_menuitem_breathing</item>
+        <item>@string/p_menuitem_flashlight</item>
+        <item>@string/p_menuitem_alipay</item>
+        <item>@string/p_menuitem_nfc</item>
+    </string-array>
+
+    <string-array name="pref_miband6_display_items_default">
+        <item>@string/p_menuitem_status</item>
+        <item>@string/p_menuitem_hr</item>
+        <item>@string/p_menuitem_workout</item>
+        <item>@string/p_menuitem_weather</item>
+        <item>@string/p_menuitem_notifications</item>
+        <item>@string/p_menuitem_alarm</item>
+        <item>@string/p_menuitem_takephoto</item>
+        <item>@string/p_menuitem_pomodoro</item>
+        <item>@string/p_menuitem_music</item>
+        <item>@string/p_menuitem_stopwatch</item>
+        <item>@string/p_menuitem_timer</item>
+        <item>@string/p_menuitem_findphone</item>
+        <item>@string/p_menuitem_mutephone</item>
+        <item>@string/p_menuitem_settings</item>
+        <item>@string/p_menuitem_activity</item>
+        <item>@string/p_menuitem_eventreminder</item>
+        <item>@string/p_menuitem_pai</item>
+        <item>@string/p_menuitem_sleep</item>
+        <item>@string/p_menuitem_worldclock</item>
+        <item>@string/p_menuitem_stress</item>
+        <item>@string/p_menuitem_cycles</item>
+        <item>@string/p_menuitem_spo2</item>
+        <item>@string/p_menuitem_breathing</item>
+        <item>@string/p_menuitem_flashlight</item>
+    </string-array>
+
+    <string-array name="pref_miband6_shortcuts">
+        <item>@string/menuitem_status</item>
+        <item>@string/menuitem_hr</item>
+        <item>@string/menuitem_workout</item>
+        <item>@string/menuitem_weather</item>
+        <item>@string/menuitem_notifications</item>
+        <item>@string/menuitem_dnd</item>
+        <item>@string/menuitem_alarm</item>
+        <item>@string/menuitem_takephoto</item>
+        <item>@string/menuitem_music</item>
+        <item>@string/menuitem_stopwatch</item>
+        <item>@string/menuitem_timer</item>
+        <item>@string/menuitem_findphone</item>
+        <item>@string/menuitem_mutephone</item>
+        <item>@string/menuitem_eventreminder</item>
+        <item>@string/menuitem_pai</item>
+        <item>@string/menuitem_sleep</item>
+        <item>@string/menuitem_worldclock</item>
+        <item>@string/menuitem_stress</item>
+        <item>@string/menuitem_cycles</item>
+        <item>@string/menuitem_spo2</item>
+        <item>@string/menuitem_breathing</item>
+        <item>@string/menuitem_flashlight</item>
+        <item>@string/menuitem_alipay</item>
+        <item>@string/menuitem_nfc</item>
+    </string-array>
+
+    <string-array name="pref_miband6_shortcuts_values">
+        <item>@string/p_menuitem_status</item>
+        <item>@string/p_menuitem_hr</item>
+        <item>@string/p_menuitem_workout</item>
+        <item>@string/p_menuitem_weather</item>
+        <item>@string/p_menuitem_notifications</item>
+        <item>@string/p_menuitem_dnd</item>
+        <item>@string/p_menuitem_alarm</item>
+        <item>@string/p_menuitem_takephoto</item>
+        <item>@string/p_menuitem_music</item>
+        <item>@string/p_menuitem_stopwatch</item>
+        <item>@string/p_menuitem_timer</item>
+        <item>@string/p_menuitem_findphone</item>
+        <item>@string/p_menuitem_mutephone</item>
+        <item>@string/p_menuitem_eventreminder</item>
+        <item>@string/p_menuitem_pai</item>
+        <item>@string/p_menuitem_sleep</item>
+        <item>@string/p_menuitem_worldclock</item>
+        <item>@string/p_menuitem_stress</item>
+        <item>@string/p_menuitem_cycles</item>
+        <item>@string/p_menuitem_spo2</item>
+        <item>@string/p_menuitem_breathing</item>
+        <item>@string/p_menuitem_flashlight</item>
+        <item>@string/p_menuitem_alipay</item>
+        <item>@string/p_menuitem_nfc</item>
+    </string-array>
+
+    <string-array name="pref_miband6_shortcuts_default">
+        <item>@string/p_menuitem_notifications</item>
+        <item>@string/p_menuitem_weather</item>
+        <item>@string/p_menuitem_music</item>
+    </string-array>
+
+    <string-array name="pref_huami2021_empty_array">
+        <!-- empty array, will be requested from band -->
+    </string-array>
+
+    <string-array name="pref_workout_detection_categories">
+        <item>@string/activity_type_walking</item>
+        <item>@string/activity_type_indoor_walking</item>
+        <item>@string/activity_type_outdoor_running</item>
+        <item>@string/activity_type_treadmill</item>
+        <item>@string/activity_type_outdoor_cycling</item>
+        <item>@string/activity_type_pool_swimming</item>
+        <item>@string/activity_type_elliptical</item>
+        <item>@string/activity_type_rowing_machine</item>
+    </string-array>
+    <string-array name="pref_workout_detection_categories_values">
+        <item>walking</item>
+        <item>indoor_walking</item>
+        <item>outdoor_running</item>
+        <item>treadmill</item>
+        <item>outdoor_cycling</item>
+        <item>pool_swimming</item>
+        <item>elliptical</item>
+        <item>rowing_machine</item>
+    </string-array>
+
+    <string-array name="pref_morning_updates_categories">
+        <item>@string/menuitem_weather</item>
+        <item>@string/battery</item>
+        <item>@string/menuitem_sleep</item>
+        <item>@string/menuitem_events</item>
+        <item>@string/menuitem_pai</item>
+        <item>@string/yesterdays_activity</item>
+        <item>@string/menuitem_zepp_coach</item>
+        <item>@string/menuitem_cycles</item>
+    </string-array>
+    <string-array name="pref_morning_updates_categories_values">
+        <item>weather</item>
+        <item>battery</item>
+        <item>sleep</item>
+        <item>event</item>
+        <item>pai</item>
+        <item>yesterdays_activity</item>
+        <item>zepp_coach</item>
+        <item>cycle_tracking</item>
+    </string-array>
+
+    <string-array name="pref_gps_mode_preset">
+        <item>@string/accuracy</item>
+        <item>@string/balanced</item>
+        <item>@string/power_saving</item>
+        <item>@string/custom</item>
+    </string-array>
+    <string-array name="pref_gps_mode_preset_values">
+        <item>accuracy</item>
+        <item>balanced</item>
+        <item>power_saving</item>
+        <item>custom</item>
+    </string-array>
+
+    <string-array name="pref_gps_band">
+        <item>@string/single_band</item>
+        <item>@string/dual_band</item>
+    </string-array>
+    <string-array name="pref_gps_band_values">
+        <item>single_band</item>
+        <item>dual_band</item>
+    </string-array>
+
+    <string-array name="pref_gps_combination">
+        <item>@string/low_power_gps</item>
+        <item>@string/gps</item>
+        <item>@string/gps_bds</item>
+        <item>@string/gps_glonass</item>
+        <item>@string/gps_galileo</item>
+        <item>@string/all_satellites</item>
+    </string-array>
+    <string-array name="pref_gps_combination_values">
+        <item>low_power_gps</item>
+        <item>gps</item>
+        <item>gps_bds</item>
+        <item>gps_glonass</item>
+        <item>gps_galileo</item>
+        <item>all_satellites</item>
+    </string-array>
+
+    <string-array name="pref_gps_satellite_search">
+        <item>@string/speed_first</item>
+        <item>@string/accuracy_first</item>
+    </string-array>
+    <string-array name="pref_gps_satellite_search_values">
+        <item>speed_first</item>
+        <item>accuracy_first</item>
+    </string-array>
+
+    <string-array name="workout_detection_sensitivity">
+        <item>@string/sony_speak_to_chat_sensitivity_high</item>
+        <item>@string/sony_speak_to_chat_sensitivity_standard</item>
+        <item>@string/sony_speak_to_chat_sensitivity_low</item>
+    </string-array>
+    <string-array name="workout_detection_sensitivity_values">
+        <item>high</item>
+        <item>standard</item>
+        <item>low</item>
+    </string-array>
+
+    <string-array name="pref_zepp_os_watchfaces">
+        <!-- GTR 4 -->
+        <item>@string/zepp_os_watchface_red_fantasy</item>
+        <item>@string/zepp_os_watchface_multiple_data</item>
+        <item>@string/zepp_os_watchface_rush</item>
+        <item>@string/zepp_os_watchface_minimalist</item>
+        <item>@string/zepp_os_watchface_simplicity_data</item>
+        <item>@string/zepp_os_watchface_vibrant</item>
+        <item>@string/zepp_os_watchface_business_style</item>
+        <item>@string/zepp_os_watchface_emerald_moonlight</item>
+        <item>@string/zepp_os_watchface_rotating_earth</item>
+        <item>@string/zepp_os_watchface_superposition</item>
+        <!-- Balance -->
+        <item>@string/zepp_os_watchface_vast_sky</item>
+        <item>@string/zepp_os_watchface_lightning_flash</item>
+        <item>@string/zepp_os_watchface_free_combination</item>
+        <item>@string/zepp_os_watchface_pure_white</item>
+        <item>@string/zepp_os_watchface_guider</item>
+        <item>@string/zepp_os_watchface_city_of_speed</item>
+        <item>@string/zepp_os_watchface_starry_sky</item>
+        <item>@string/zepp_os_watchface_the_ultima</item>
+
+    </string-array>
+
+    <string-array name="pref_zepp_os_watchfaces_values">
+        <!-- GTR 4 -->
+        <item>red_fantasy</item>
+        <item>multiple_data</item>
+        <item>rush</item>
+        <item>minimalist</item>
+        <item>simplicity_data</item>
+        <item>vibrant</item>
+        <item>business_style</item>
+        <item>emerald_moonlight</item>
+        <item>rotating_earth</item>
+        <item>superposition</item>
+        <item>vast_sky</item>
+        <item>lightning_flash</item>
+        <item>free_combination</item>
+        <item>pure_white</item>
+        <item>guider</item>
+        <item>city_of_speed</item>
+        <item>starry_sky</item>
+        <item>the_ultima</item>
+    </string-array>
+
+    <string-array name="pref_zepp_os_apps">
+        <item>@string/menuitem_activity</item>
+        <item>@string/menuitem_alarm</item>
+        <item>@string/menuitem_alexa</item>
+        <item>@string/menuitem_alexa</item>
+        <item>@string/menuitem_barometer</item>
+        <item>@string/menuitem_breathing</item>
+        <item>@string/menuitem_calendar</item>
+        <item>@string/menuitem_compass</item>
+        <item>@string/menuitem_countdown</item>
+        <item>@string/menuitem_eventreminder</item>
+        <item>@string/menuitem_events</item>
+        <item>@string/menuitem_female_health</item>
+        <item>@string/menuitem_findphone</item>
+        <item>@string/menuitem_flashlight</item>
+        <item>@string/menuitem_hr</item>
+        <item>@string/menuitem_membership_cards</item>
+        <item>@string/menuitem_music</item>
+        <item>@string/menuitem_mutephone</item>
+        <item>@string/menuitem_offline_voice</item>
+        <item>@string/menuitem_one_tap_measuring</item>
+        <item>@string/menuitem_pai</item>
+        <item>@string/menuitem_personal_activity_intelligence</item>
+        <item>@string/menuitem_phone</item>
+        <item>@string/menuitem_pomodoro</item>
+        <item>@string/menuitem_settings</item>
+        <item>@string/menuitem_sleep</item>
+        <item>@string/menuitem_spo2</item>
+        <item>@string/menuitem_stopwatch</item>
+        <item>@string/menuitem_stress</item>
+        <item>@string/menuitem_sun_moon</item>
+        <item>@string/menuitem_takephoto</item>
+        <item>@string/menuitem_todo</item>
+        <item>@string/menuitem_voice_memos</item>
+        <item>@string/menuitem_weather</item>
+        <item>@string/menuitem_workout_history</item>
+        <item>@string/menuitem_workout</item>
+        <item>@string/menuitem_workout_status</item>
+        <item>@string/menuitem_worldclock</item>
+        <item>@string/menuitem_alipay</item>
+        <item>@string/menuitem_wechat_pay</item>
+        <item>@string/menuitem_cards</item>
+        <item>@string/menuitem_mi_ai</item>
+        <item>@string/menuitem_zepp_coach</item>
+        <item>@string/menuitem_zepp_pay</item>
+        <item>@string/menuitem_readiness</item>
+        <item>@string/menuitem_body_composition</item>
+        <item>@string/menuitem_thermometer</item>
+        <item>@string/menuitem_workout_shortcuts</item>
+        <item>@string/menuitem_apps_shortcuts</item>
+        <item>@string/hrv</item>
+        <item>@string/menuitem_map</item>
+        <item>@string/menuitem_heart_rate_push</item>
+        <!-- dummy item -->
+        <item>@string/menuitem_more</item>
+    </string-array>
+
+    <string-array name="pref_zepp_os_apps_values">
+        <item>activity</item>
+        <item>alarm</item>
+        <item>alexa</item>
+        <item>alexa2</item>
+        <item>barometer</item>
+        <item>breathing</item>
+        <item>calendar</item>
+        <item>compass</item>
+        <item>countdown</item>
+        <item>eventreminder</item>
+        <item>events</item>
+        <item>female_health</item>
+        <item>findphone</item>
+        <item>flashlight</item>
+        <item>hr</item>
+        <item>membership_cards</item>
+        <item>music</item>
+        <item>mutephone</item>
+        <item>offline_voice</item>
+        <item>one_tap_measuring</item>
+        <item>pai</item>
+        <item>personal_activity_intelligence</item>
+        <item>phone</item>
+        <item>pomodoro</item>
+        <item>settings</item>
+        <item>sleep</item>
+        <item>spo2</item>
+        <item>stopwatch</item>
+        <item>stress</item>
+        <item>sun_moon</item>
+        <item>takephoto</item>
+        <item>todo</item>
+        <item>voice_memos</item>
+        <item>weather</item>
+        <item>workout_history</item>
+        <item>workout</item>
+        <item>workout_status</item>
+        <item>worldclock</item>
+        <item>alipay</item>
+        <item>wechat_pay</item>
+        <item>cards</item>
+        <item>mi_ai</item>
+        <item>zepp_coach</item>
+        <item>zepp_pay</item>
+        <item>readiness</item>
+        <item>body_composition</item>
+        <item>thermometer</item>
+        <item>workout_shortcuts</item>
+        <item>apps_shortcuts</item>
+        <item>hrv</item>
+        <item>map</item>
+        <item>heart_rate_push</item>
+        <!-- dummy item -->
+        <item>more</item>
+    </string-array>
+
+    <string-array name="pref_zepp_os_shortcut_cards">
+        <item>@string/menuitem_weather</item>
+        <item>@string/menuitem_aqi</item>
+        <item>@string/menuitem_forecast</item>
+        <item>@string/menuitem_pai</item>
+        <item>@string/menuitem_alarm</item>
+        <item>@string/menuitem_sleep</item>
+        <item>@string/menuitem_hr</item>
+        <item>@string/menuitem_activity</item>
+        <item>@string/calories</item>
+        <item>@string/menuitem_spo2</item>
+        <item>@string/menuitem_phone</item>
+        <item>@string/menuitem_events</item>
+        <item>@string/menuitem_stress</item>
+        <item>@string/menuitem_worldclock</item>
+        <item>@string/menuitem_todo</item>
+        <item>@string/menuitem_countdown</item>
+        <item>@string/menuitem_last_workout</item>
+        <item>@string/menuitem_total_workout</item>
+        <item>@string/menuitem_workout_status</item>
+        <item>@string/menuitem_vo2_max</item>
+        <item>@string/menuitem_music</item>
+        <item>@string/menuitem_cycles</item>
+        <item>@string/menuitem_one_tap_measuring</item>
+        <item>@string/menuitem_breathing</item>
+        <item>@string/menuitem_stopwatch</item>
+        <item>@string/menuitem_recommendation</item>
+        <item>@string/menuitem_zepp_coach</item>
+        <item>@string/menuitem_zepp_pay</item>
+        <item>@string/hrv</item>
+        <item>@string/menuitem_readiness</item>
+        <item>@string/menuitem_body_composition</item>
+        <item>@string/menuitem_thermometer</item>
+        <item>@string/menuitem_workout_shortcuts</item>
+        <item>@string/menuitem_apps_shortcuts</item>
+        <item>@string/menuitem_alexa</item>
+    </string-array>
+
+    <string-array name="pref_zepp_os_shortcut_cards_values">
+        <item>weather</item>
+        <item>aqi</item>
+        <item>forecast</item>
+        <item>pai</item>
+        <item>alarm</item>
+        <item>sleep</item>
+        <item>heartrate</item>
+        <item>activity</item>
+        <item>calories</item>
+        <item>spo2</item>
+        <item>phone</item>
+        <item>events</item>
+        <item>stress</item>
+        <item>worldclock</item>
+        <item>todo</item>
+        <item>countdown</item>
+        <item>last_workout</item>
+        <item>total_workout</item>
+        <item>workout_status</item>
+        <item>vo2_max</item>
+        <item>music</item>
+        <item>cycle_tracking</item>
+        <item>one_tap_measuring</item>
+        <item>breathing</item>
+        <item>stopwatch</item>
+        <item>recommendation</item>
+        <item>zepp_coach</item>
+        <item>zepp_pay</item>
+        <item>hrv</item>
+        <item>readiness</item>
+        <item>body_composition</item>
+        <item>thermometer</item>
+        <item>workout_shortcuts</item>
+        <item>apps_shortcuts</item>
+        <item>alexa</item>
+    </string-array>
+
+    <string-array name="pref_huami2021_control_center">
+        <item>@string/battery</item>
+        <item>@string/menuitem_dnd</item>
+        <item>@string/menuitem_sleep</item>
+        <item>@string/menuitem_theater_mode</item>
+        <item>@string/menuitem_calendar</item>
+        <item>@string/menuitem_volume</item>
+        <item>@string/menuitem_screen_always_lit</item>
+        <item>@string/menuitem_brightness</item>
+        <item>@string/menuitem_settings</item>
+        <item>@string/menuitem_flashlight</item>
+        <item>@string/menuitem_bluetooth</item>
+        <item>@string/menuitem_wifi</item>
+        <item>@string/menuitem_lockscreen</item>
+        <item>@string/menuitem_findphone</item>
+        <item>@string/menuitem_eject_water</item>
+        <item>@string/menuitem_headphone</item>
+        <item>@string/menuitem_buzzer_intensity</item>
+        <item>@string/menuitem_night_display</item>
+    </string-array>
+
+    <string-array name="pref_huami2021_control_center_values">
+        <item>battery</item>
+        <item>dnd</item>
+        <item>sleep</item>
+        <item>theater_mode</item>
+        <item>calendar</item>
+        <item>volume</item>
+        <item>screen_always_lit</item>
+        <item>brightness</item>
+        <item>settings</item>
+        <item>flashlight</item>
+        <item>bluetooth</item>
+        <item>wifi</item>
+        <item>lockscreen</item>
+        <item>findphone</item>
+        <item>eject_water</item>
+        <item>headphone</item>
+        <item>buzzer_intensity</item>
+        <item>night_display</item>
+    </string-array>
+
+    <string-array name="pref_miband5_workout_activity_types">
+        <item>@string/activity_type_outdoor_running</item>
+        <item>@string/activity_type_walking</item>
+        <item>@string/activity_type_treadmill</item>
+        <item>@string/activity_type_outdoor_cycling</item>
+        <item>@string/activity_type_indoor_cycling</item>
+        <item>@string/activity_type_elliptical</item>
+        <item>@string/activity_type_swimming</item>
+        <item>@string/activity_type_freestyle</item>
+        <item>@string/activity_type_jump_roping</item>
+        <item>@string/activity_type_rowing_machine</item>
+        <item>@string/activity_type_yoga</item>
+    </string-array>
+
+    <string-array name="pref_miband5_workout_activity_types_values">
+        <item>outdoor_running</item>
+        <item>walking</item>
+        <item>treadmill</item>
+        <item>outdoor_cycling</item>
+        <item>indoor_cycling</item>
+        <item>elliptical</item>
+        <item>pool_swimming</item>
+        <item>freestyle</item>
+        <item>jump_rope</item>
+        <item>rowing_machine</item>
+        <item>yoga</item>
+    </string-array>
+
+    <string-array name="pref_miband5_workout_activity_types_default">
+        <item>outdoor_running</item>
+        <item>walking</item>
+        <item>treadmill</item>
+        <item>outdoor_cycling</item>
+        <item>indoor_cycling</item>
+        <item>elliptical</item>
+        <item>pool_swimming</item>
+        <item>freestyle</item>
+        <item>jump_rope</item>
+        <item>rowing_machine</item>
+        <item>yoga</item>
+    </string-array>
+
+    <string-array name="pref_miband6_workout_activity_types_values">
+        <item>outdoor_running</item>
+        <item>walking</item>
+        <item>treadmill</item>
+        <item>outdoor_cycling</item>
+        <item>indoor_cycling</item>
+        <item>elliptical</item>
+        <item>pool_swimming</item>
+        <item>freestyle</item>
+        <item>jump_rope</item>
+        <item>rowing_machine</item>
+        <item>yoga</item>
+        <item>dance</item>
+        <item>indoor_fitness</item>
+        <item>gymnastics</item>
+        <item>hiit</item>
+        <item>core_training</item>
+        <item>stretching</item>
+        <item>stepper</item>
+        <item>pilates</item>
+        <item>basketball</item>
+        <item>volleyball</item>
+        <item>table_tennis</item>
+        <item>badminton</item>
+        <item>cricket</item>
+        <item>bowling</item>
+        <item>boxing</item>
+        <!-- TODO: Not working <item>kickboxing</item> -->
+        <item>street_dance</item>
+        <item>zumba</item>
+        <item>indoor_ice_skating</item>
+        <!-- more is not an actual workout activity type, only a placeholder to separate types between the main and the more section -->
+        <item>more</item>
+    </string-array>
+
+    <string-array name="pref_miband6_workout_activity_types">
+        <item>@string/activity_type_outdoor_running</item>
+        <item>@string/activity_type_walking</item>
+        <item>@string/activity_type_treadmill</item>
+        <item>@string/activity_type_outdoor_cycling</item>
+        <item>@string/activity_type_indoor_cycling</item>
+        <item>@string/activity_type_elliptical</item>
+        <item>@string/activity_type_swimming</item>
+        <item>@string/activity_type_freestyle</item>
+        <item>@string/activity_type_jump_roping</item>
+        <item>@string/activity_type_rowing_machine</item>
+        <item>@string/activity_type_yoga</item>
+        <item>@string/activity_type_dance</item>
+        <item>@string/activity_type_indoor_fitness</item>
+        <item>@string/activity_type_gymnastics</item>
+        <item>@string/activity_type_hiit</item>
+        <item>@string/activity_type_core_training</item>
+        <item>@string/activity_type_stretching</item>
+        <item>@string/activity_type_stepper</item>
+        <item>@string/activity_type_pilates</item>
+        <item>@string/activity_type_basketball</item>
+        <item>@string/activity_type_volleyball</item>
+        <item>@string/activity_type_table_tennis</item>
+        <item>@string/activity_type_badminton</item>
+        <item>@string/activity_type_cricket</item>
+        <item>@string/activity_type_bowling</item>
+        <item>@string/activity_type_boxing</item>
+        <!-- TODO: Not working <item>@string/activity_type_kickboxing</item> -->
+        <item>@string/activity_type_street_dance</item>
+        <item>@string/activity_type_zumba</item>
+        <item>@string/activity_type_indoor_ice_skating</item>
+        <!-- more is not an actual workout activity type, only a placeholder to separate types between the main and the more section -->
+        <item>@string/menuitem_more</item>
+    </string-array>
+
+    <string-array name="pref_miband6_workout_activity_types_default">
+        <item>outdoor_running</item>
+        <item>walking</item>
+        <item>treadmill</item>
+        <item>outdoor_cycling</item>
+        <item>indoor_cycling</item>
+        <item>elliptical</item>
+        <item>pool_swimming</item>
+        <item>freestyle</item>
+        <item>jump_rope</item>
+        <item>rowing_machine</item>
+        <item>yoga</item>
+        <item>more</item>
+        <item>dance</item>
+        <item>indoor_fitness</item>
+        <item>gymnastics</item>
+        <item>hiit</item>
+        <item>core_training</item>
+        <item>stretching</item>
+        <item>stepper</item>
+        <item>pilates</item>
+        <item>basketball</item>
+        <item>volleyball</item>
+        <item>table_tennis</item>
+        <item>badminton</item>
+        <item>cricket</item>
+        <item>bowling</item>
+        <item>boxing</item>
+        <!-- TODO: Not working <item>kickboxing</item> -->
+        <item>street_dance</item>
+        <item>zumba</item>
+        <item>indoor_ice_skating</item>
+    </string-array>
+
+    <string-array name="pref_workout_activity_types">
+        <item>@string/activity_type_indoor_running</item>
+        <item>@string/activity_type_outdoor_running</item>
+        <item>@string/activity_type_outdoor_walking</item>
+        <item>@string/activity_type_indoor_walking</item>
+        <item>@string/activity_type_outdoor_cycling</item>
+        <item>@string/activity_type_indoor_cycling</item>
+        <item>@string/activity_type_mountain_hike</item>
+        <item>@string/activity_type_hiking</item>
+        <item>@string/activity_type_cross_trainer</item>
+        <item>@string/activity_type_free_training</item>
+        <item>@string/activity_type_strength_training</item>
+        <item>@string/activity_type_yoga</item>
+        <item>@string/activity_type_boxing</item>
+        <item>@string/activity_type_rower</item>
+        <item>@string/activity_type_dynamic_cycle</item>
+        <item>@string/activity_type_stair_stepper</item>
+        <item>@string/activity_type_treadmill</item>
+        <item>@string/activity_type_hiit</item>
+        <item>@string/activity_type_fitness_exercises</item>
+        <item>@string/activity_type_jump_roping</item>
+        <item>@string/activity_type_pilates</item>
+        <item>@string/activity_type_crossfit</item>
+        <item>@string/activity_type_functional_training</item>
+        <item>@string/activity_type_physical_training</item>
+        <item>@string/activity_type_taekwondo</item>
+        <item>@string/activity_type_cross_country_running</item>
+        <item>@string/activity_type_karate</item>
+        <item>@string/activity_type_fencing</item>
+        <item>@string/activity_type_core_training</item>
+        <item>@string/activity_type_kendo</item>
+        <item>@string/activity_type_horizontal_bar</item>
+        <item>@string/activity_type_parallel_bar</item>
+        <item>@string/activity_type_cooldown</item>
+        <item>@string/activity_type_cross_training</item>
+        <item>@string/activity_type_sit_ups</item>
+        <item>@string/activity_type_fitness_gaming</item>
+        <item>@string/activity_type_aerobic_exercise</item>
+        <item>@string/activity_type_rolling</item>
+        <item>@string/activity_type_flexibility</item>
+        <item>@string/activity_type_gymnastics</item>
+        <item>@string/activity_type_track_and_field</item>
+        <item>@string/activity_type_push_ups</item>
+        <item>@string/activity_type_battle_rope</item>
+        <item>@string/activity_type_smith_machine</item>
+        <item>@string/activity_type_pull_ups</item>
+        <item>@string/activity_type_plank</item>
+        <item>@string/activity_type_javelin</item>
+        <item>@string/activity_type_long_jump</item>
+        <item>@string/activity_type_high_jump</item>
+        <item>@string/activity_type_trampoline</item>
+        <item>@string/activity_type_dumbbell</item>
+        <item>@string/activity_type_belly_dance</item>
+        <item>@string/activity_type_jazz_dance</item>
+        <item>@string/activity_type_latin_dance</item>
+        <item>@string/activity_type_ballet</item>
+        <item>@string/activity_type_street_dance</item>
+        <item>@string/activity_type_zumba</item>
+        <item>@string/activity_type_other_dance</item>
+        <item>@string/activity_type_roller_skating</item>
+        <item>@string/activity_type_martial_arts</item>
+        <item>@string/activity_type_tai_chi</item>
+        <item>@string/activity_type_hula_hooping</item>
+        <item>@string/activity_type_disc_sports</item>
+        <item>@string/activity_type_darts</item>
+        <item>@string/activity_type_archery</item>
+        <item>@string/activity_type_horse_riding</item>
+        <item>@string/activity_type_kite_flying</item>
+        <item>@string/activity_type_swing</item>
+        <item>@string/activity_type_stairs</item>
+        <item>@string/activity_type_fishing</item>
+        <item>@string/activity_type_hand_cycling</item>
+        <item>@string/activity_type_mind_and_body</item>
+        <item>@string/activity_type_wrestling</item>
+        <item>@string/activity_type_kabaddi</item>
+        <item>@string/activity_type_karting</item>
+        <item>@string/activity_type_badminton</item>
+        <item>@string/activity_type_table_tennis</item>
+        <item>@string/activity_type_tennis</item>
+        <item>@string/activity_type_billiards</item>
+        <item>@string/activity_type_bowling</item>
+        <item>@string/activity_type_volleyball</item>
+        <item>@string/activity_type_shuttlecock</item>
+        <item>@string/activity_type_handball</item>
+        <item>@string/activity_type_baseball</item>
+        <item>@string/activity_type_softball</item>
+        <item>@string/activity_type_cricket</item>
+        <item>@string/activity_type_rugby</item>
+        <item>@string/activity_type_hockey</item>
+        <item>@string/activity_type_squash</item>
+        <item>@string/activity_type_dodgeball</item>
+        <item>@string/activity_type_soccer</item>
+        <item>@string/activity_type_basketball</item>
+        <item>@string/activity_type_australian_football</item>
+        <item>@string/activity_type_golf</item>
+        <item>@string/activity_type_pickleball</item>
+        <item>@string/activity_type_lacross</item>
+        <item>@string/activity_type_shot</item>
+        <item>@string/activity_type_sailing</item>
+        <item>@string/activity_type_surfing</item>
+        <item>@string/activity_type_jet_skiing</item>
+        <item>@string/activity_type_skating</item>
+        <item>@string/activity_type_ice_hockey</item>
+        <item>@string/activity_type_curling</item>
+        <item>@string/activity_type_snowboarding</item>
+        <item>@string/activity_type_cross_country_skiing</item>
+        <item>@string/activity_type_snow_sports</item>
+        <item>@string/activity_type_skiing</item>
+        <item>@string/activity_type_skateboarding</item>
+        <item>@string/activity_type_rock_climbing</item>
+        <item>@string/activity_type_hunting</item>
+        <item>@string/activity_type_kickboxing</item>
+        <item>@string/activity_type_tae_bo</item>
+        <item>@string/activity_type_beach_soccer</item>
+        <item>@string/activity_type_beach_volleyball</item>
+        <item>@string/activity_type_gateball</item>
+        <item>@string/activity_type_sepak_takraw</item>
+        <item>@string/activity_type_luge</item>
+        <item>@string/activity_type_parachuting</item>
+        <item>@string/activity_type_auto_racing</item>
+        <item>@string/activity_type_parkour</item>
+    </string-array>
+
+    <string-array name="pref_workout_activity_types_values">
+        <item>indoor_running</item>
+        <item>outdoor_running</item>
+        <item>outdoor_walking</item>
+        <item>indoor_walking</item>
+        <item>outdoor_cycling</item>
+        <item>indoor_cycling</item>
+        <item>mountain_hike</item>
+        <item>hiking</item>
+        <item>cross_trainer</item>
+        <item>free_training</item>
+        <item>strength_training</item>
+        <item>yoga</item>
+        <item>boxing</item>
+        <item>rower</item>
+        <item>dynamic_cycle</item>
+        <item>stair_stepper</item>
+        <item>treadmill</item>
+        <item>hiit</item>
+        <item>fitness_exercises</item>
+        <item>jump_roping</item>
+        <item>pilates</item>
+        <item>crossfit</item>
+        <item>functional_training</item>
+        <item>physical_training</item>
+        <item>taekwondo</item>
+        <item>cross_country_running</item>
+        <item>karate</item>
+        <item>fencing</item>
+        <item>core_training</item>
+        <item>kendo</item>
+        <item>horizontal_bar</item>
+        <item>parallel_bar</item>
+        <item>cooldown</item>
+        <item>cross_training</item>
+        <item>sit_ups</item>
+        <item>fitness_gaming</item>
+        <item>aerobic_exercise</item>
+        <item>rolling</item>
+        <item>flexibility</item>
+        <item>gymnastics</item>
+        <item>track_and_field</item>
+        <item>push_ups</item>
+        <item>battle_rope</item>
+        <item>smith_machine</item>
+        <item>pull_ups</item>
+        <item>plank</item>
+        <item>javelin</item>
+        <item>long_jump</item>
+        <item>high_jump</item>
+        <item>trampoline</item>
+        <item>dumbbell</item>
+        <item>belly_dance</item>
+        <item>jazz_dance</item>
+        <item>latin_dance</item>
+        <item>ballet</item>
+        <item>street_dance</item>
+        <item>zumba</item>
+        <item>other_dance</item>
+        <item>roller_skating</item>
+        <item>martial_arts</item>
+        <item>tai_chi</item>
+        <item>hula_hooping</item>
+        <item>disc_sports</item>
+        <item>darts</item>
+        <item>archery</item>
+        <item>horse_riding</item>
+        <item>kite_flying</item>
+        <item>swing</item>
+        <item>stairs</item>
+        <item>fishing</item>
+        <item>hand_cycling</item>
+        <item>mind_and_body</item>
+        <item>wrestling</item>
+        <item>kabaddi</item>
+        <item>karting</item>
+        <item>badminton</item>
+        <item>table_tennis</item>
+        <item>tennis</item>
+        <item>billiards</item>
+        <item>bowling</item>
+        <item>volleyball</item>
+        <item>shuttlecock</item>
+        <item>handball</item>
+        <item>baseball</item>
+        <item>softball</item>
+        <item>cricket</item>
+        <item>rugby</item>
+        <item>hockey</item>
+        <item>squash</item>
+        <item>dodgeball</item>
+        <item>soccer</item>
+        <item>basketball</item>
+        <item>australian_football</item>
+        <item>golf</item>
+        <item>pickleball</item>
+        <item>lacross</item>
+        <item>shot</item>
+        <item>sailing</item>
+        <item>surfing</item>
+        <item>jet_skiing</item>
+        <item>skating</item>
+        <item>ice_hockey</item>
+        <item>curling</item>
+        <item>snowboarding</item>
+        <item>cross_country_skiing</item>
+        <item>snow_sports</item>
+        <item>skiing</item>
+        <item>skateboarding</item>
+        <item>rock_climbing</item>
+        <item>hunting</item>
+        <item>kickboxing</item>
+        <item>tae_bo</item>
+        <item>beach_soccer</item>
+        <item>beach_volleyball</item>
+        <item>gateball</item>
+        <item>sepak_takraw</item>
+        <item>luge</item>
+        <item>parachuting</item>
+        <item>auto_racing</item>
+        <item>parkour</item>
+    </string-array>
+
+    <string-array name="pref_workout_activity_types_default">
+        <item>indoor_run</item>
+        <item>outdoor_run</item>
+    </string-array>
+
+    <string-array name="pref_withings_steel_activity_types">
+        <item>@string/activity_type_outdoor_running</item>
+        <item>@string/activity_type_hiking</item>
+        <item>@string/activity_type_biking</item>
+        <item>@string/activity_type_swimming</item>
+        <item>@string/activity_type_surfing</item>
+        <item>@string/activity_type_windsurfing</item>
+        <item>@string/activity_type_kitesurfing</item>
+        <item>@string/activity_type_tennis</item>
+        <item>@string/activity_type_pingpong</item>
+        <item>@string/activity_type_squash</item>
+        <item>@string/activity_type_badminton</item>
+        <item>@string/activity_type_basketball</item>
+        <item>@string/activity_type_soccer</item>
+        <item>@string/activity_type_football</item>
+        <item>@string/activity_type_rugby</item>
+        <item>@string/activity_type_volleyball</item>
+        <item>@string/activity_type_baseball</item>
+        <item>@string/activity_type_handball</item>
+        <item>@string/activity_type_golf</item>
+        <item>@string/activity_type_gymnastics</item>
+        <item>@string/activity_type_elliptical</item>
+        <item>@string/activity_type_pilates</item>
+        <item>@string/activity_type_yoga</item>
+        <item>@string/activity_type_dancing</item>
+        <item>@string/activity_type_zumba</item>
+        <item>@string/activity_type_weightlifting</item>
+        <item>@string/activity_type_boxing</item>
+        <item>@string/activity_type_skiing</item>
+        <item>@string/activity_type_snowboarding</item>
+        <item>@string/activity_type_rowing_machine</item>
+        <item>@string/activity_type_hockey</item>
+        <item>@string/activity_type_icehockey</item>
+        <item>@string/activity_type_climbing</item>
+        <item>@string/activity_type_iceskating</item>
+        <item>@string/activity_type_riding</item>
+        <item>@string/activity_type_other</item>
+    </string-array>
+
+    <string-array name="pref_withings_steel_activity_types_values">
+        <item>running</item>
+        <item>hiking</item>
+        <item>biking</item>
+        <item>swimming</item>
+        <item>surfing</item>
+        <item>windsurfing</item>
+        <item>kitesurfing</item>
+        <item>tennis</item>
+        <item>pingpong</item>
+        <item>squash</item>
+        <item>badminton</item>
+        <item>basketball</item>
+        <item>soccer</item>
+        <item>football</item>
+        <item>rugby</item>
+        <item>volleyball</item>
+        <item>baseball</item>
+        <item>handball</item>
+        <item>golf</item>
+        <item>gymnastics</item>
+        <item>elliptical</item>
+        <item>pilates</item>
+        <item>yoga</item>
+        <item>dancing</item>
+        <item>zumba</item>
+        <item>weightlifting</item>
+        <item>boxing</item>
+        <item>skiing</item>
+        <item>snowboarding</item>
+        <item>rowing_machine</item>
+        <item>hockey</item>
+        <item>icehockey</item>
+        <item>climbing</item>
+        <item>iceskating</item>
+        <item>riding</item>
+        <item>other</item>
+    </string-array>
+
+    <string-array name="pref_withings_steel_activity_types_default">
+        <item>running</item>
+        <item>hiking</item>
+        <item>biking</item>
+        <item>swimming</item>
+        <item>climbing</item>
+        <item>other</item>
+    </string-array>
+
+    <string-array name="pref_neo_display_items">
+        <item>@string/menuitem_pai</item>
+        <item>@string/menuitem_dnd</item>
+        <item>@string/menuitem_hr</item>
+        <item>@string/menuitem_weather</item>
+        <item>@string/menuitem_stopwatch</item>
+        <item>@string/menuitem_alarm</item>
+        <item>@string/menuitem_worldclock</item>
+        <item>@string/chart_steps</item>
+        <item>@string/distance</item>
+        <item>@string/calories</item>
+        <item>@string/battery</item>
+    </string-array>
+
+    <string-array name="pref_neo_display_items_values">
+        <item>@string/p_menuitem_pai</item>
+        <item>@string/p_menuitem_dnd</item>
+        <item>@string/p_menuitem_hr</item>
+        <item>@string/p_menuitem_weather</item>
+        <item>@string/p_menuitem_stopwatch</item>
+        <item>@string/p_menuitem_alarm</item>
+        <item>@string/p_menuitem_worldclock</item>
+        <item>@string/p_steps</item>
+        <item>@string/p_distance</item>
+        <item>@string/p_calories</item>
+        <item>@string/p_battery</item>
+    </string-array>
+
+    <string-array name="pref_neo_display_items_default">
+        <item>@string/p_menuitem_pai</item>
+        <item>@string/p_menuitem_dnd</item>
+        <item>@string/p_menuitem_hr</item>
+        <item>@string/p_menuitem_weather</item>
+        <item>@string/p_menuitem_stopwatch</item>
+        <item>@string/p_menuitem_alarm</item>
+        <item>@string/p_menuitem_worldclock</item>
+        <item>@string/p_steps</item>
+        <item>@string/p_distance</item>
+        <item>@string/p_calories</item>
+        <item>@string/p_battery</item>
+    </string-array>
+
+    <string-array name="pref_amazfitband5_display_items">
+        <item>@string/menuitem_status</item>
+        <item>@string/menuitem_pai</item>
+        <item>@string/menuitem_hr</item>
+        <item>@string/menuitem_spo2</item>
+        <item>@string/menuitem_notifications</item>
+        <item>@string/menuitem_breathing</item>
+        <item>@string/menuitem_eventreminder</item>
+        <item>@string/menuitem_weather</item>
+        <item>@string/menuitem_workout</item>
+        <item>@string/menuitem_more</item>
+        <item>@string/menuitem_stress</item>
+        <item>@string/menuitem_cycles</item>
+    </string-array>
+
+    <string-array name="pref_amazfitband5_display_items_values">
+        <item>@string/p_menuitem_status</item>
+        <item>@string/p_menuitem_pai</item>
+        <item>@string/p_menuitem_hr</item>
+        <item>@string/p_menuitem_spo2</item>
+        <item>@string/p_menuitem_notifications</item>
+        <item>@string/p_menuitem_breathing</item>
+        <item>@string/p_menuitem_eventreminder</item>
+        <item>@string/p_menuitem_weather</item>
+        <item>@string/p_menuitem_workout</item>
+        <item>@string/p_menuitem_more</item>
+        <item>@string/p_menuitem_stress</item>
+        <item>@string/p_menuitem_cycles</item>
+    </string-array>
+
+    <string-array name="pref_amazfitband5_display_items_default">
+        <item>@string/p_menuitem_status</item>
+        <item>@string/p_menuitem_pai</item>
+        <item>@string/p_menuitem_hr</item>
+        <item>@string/p_menuitem_spo2</item>
+        <item>@string/p_menuitem_notifications</item>
+        <item>@string/p_menuitem_breathing</item>
+        <item>@string/p_menuitem_eventreminder</item>
+        <item>@string/p_menuitem_weather</item>
+        <item>@string/p_menuitem_workout</item>
+        <item>@string/p_menuitem_more</item>
+        <item>@string/p_menuitem_stress</item>
+        <item>@string/p_menuitem_cycles</item>
+    </string-array>
+
+    <string-array name="pref_amazfitband5_shortcuts">
+        <item>@string/menuitem_notifications</item>
+        <item>@string/menuitem_weather</item>
+        <item>@string/menuitem_music</item>
+        <item>@string/menuitem_timer</item>
+        <item>@string/menuitem_alarm</item>
+        <item>@string/menuitem_findphone</item>
+        <item>@string/menuitem_worldclock</item>
+        <item>@string/menuitem_status</item>
+        <item>@string/menuitem_pai</item>
+        <item>@string/menuitem_hr</item>
+        <item>@string/menuitem_spo2</item>
+        <item>@string/menuitem_breathing</item>
+        <item>@string/menuitem_stress</item>
+        <item>@string/menuitem_eventreminder</item>
+        <item>@string/menuitem_dnd</item>
+        <item>@string/menuitem_stopwatch</item>
+        <item>@string/menuitem_workout</item>
+        <item>@string/menuitem_mutephone</item>
+        <item>@string/menuitem_cycles</item>
+        <item>@string/menuitem_takephoto</item>
+        <item>@string/menuitem_alexa</item>
+    </string-array>
+
+    <string-array name="pref_amazfitband5_shortcuts_values">
+        <item>@string/p_menuitem_notifications</item>
+        <item>@string/p_menuitem_weather</item>
+        <item>@string/p_menuitem_music</item>
+        <item>@string/p_menuitem_timer</item>
+        <item>@string/p_menuitem_alarm</item>
+        <item>@string/p_menuitem_findphone</item>
+        <item>@string/p_menuitem_worldclock</item>
+        <item>@string/p_menuitem_status</item>
+        <item>@string/p_menuitem_pai</item>
+        <item>@string/p_menuitem_hr</item>
+        <item>@string/p_menuitem_spo2</item>
+        <item>@string/p_menuitem_breathing</item>
+        <item>@string/p_menuitem_stress</item>
+        <item>@string/p_menuitem_eventreminder</item>
+        <item>@string/p_menuitem_dnd</item>
+        <item>@string/p_menuitem_stopwatch</item>
+        <item>@string/p_menuitem_workout</item>
+        <item>@string/p_menuitem_mutephone</item>
+        <item>@string/p_menuitem_cycles</item>
+        <item>@string/p_menuitem_takephoto</item>
+        <item>@string/p_menuitem_alexa</item>
+    </string-array>
+
+    <string-array name="pref_amazfitband5_shortcuts_default">
+        <item>@string/p_menuitem_notifications</item>
+        <item>@string/p_menuitem_weather</item>
+        <item>@string/p_menuitem_music</item>
+    </string-array>
+
+    <string-array name="pref_bip_display_items">
+        <item>@string/menuitem_status</item>
+        <item>@string/menuitem_activity</item>
+        <item>@string/menuitem_weather</item>
+        <item>@string/menuitem_alarm</item>
+        <item>@string/menuitem_timer</item>
+        <item>@string/menuitem_compass</item>
+        <item>@string/menuitem_settings</item>
+        <item>@string/menuitem_alipay</item>
+    </string-array>
+
+    <string-array name="pref_bip_display_items_values">
+        <item>@string/p_menuitem_status</item>
+        <item>@string/p_menuitem_activity</item>
+        <item>@string/p_menuitem_weather</item>
+        <item>@string/p_menuitem_alarm</item>
+        <item>@string/p_menuitem_timer</item>
+        <item>@string/p_menuitem_compass</item>
+        <item>@string/p_menuitem_settings</item>
+        <item>@string/p_menuitem_alipay</item>
+    </string-array>
+
+    <string-array name="pref_bip_display_items_default">
+        <item>@string/p_menuitem_status</item>
+        <item>@string/p_menuitem_activity</item>
+        <item>@string/p_menuitem_weather</item>
+        <item>@string/p_menuitem_alarm</item>
+        <item>@string/p_menuitem_timer</item>
+        <item>@string/p_menuitem_compass</item>
+        <item>@string/p_menuitem_settings</item>
+    </string-array>
+
+    <string-array name="pref_bip_shortcuts">
+        <item>@string/menuitem_alipay</item>
+        <item>@string/menuitem_weather</item>
+    </string-array>
+
+    <string-array name="pref_bip_shortcuts_values">
+        <item>@string/p_menuitem_alipay</item>
+        <item>@string/p_menuitem_weather</item>
+    </string-array>
+
+    <string-array name="pref_bip_shortcuts_default">
+        <item>@string/p_menuitem_weather</item>
+    </string-array>
+
+    <string-array name="pref_cor_display_items">
+        <item>@string/menuitem_status</item>
+        <item>@string/menuitem_notifications</item>
+        <item>@string/menuitem_activity</item>
+        <item>@string/menuitem_weather</item>
+        <item>@string/menuitem_alarm</item>
+        <item>@string/menuitem_timer</item>
+        <item>@string/menuitem_settings</item>
+        <item>@string/menuitem_alipay</item>
+        <item>@string/menuitem_music</item>
+    </string-array>
+
+    <string-array name="pref_cor_display_items_values">
+        <item>@string/p_menuitem_status</item>
+        <item>@string/p_menuitem_notifications</item>
+        <item>@string/p_menuitem_activity</item>
+        <item>@string/p_menuitem_weather</item>
+        <item>@string/p_menuitem_alarm</item>
+        <item>@string/p_menuitem_timer</item>
+        <item>@string/p_menuitem_settings</item>
+        <item>@string/p_menuitem_alipay</item>
+        <item>@string/p_menuitem_music</item>
+    </string-array>
+
+    <string-array name="pref_cor_display_items_default">
+        <item>@string/p_menuitem_status</item>
+        <item>@string/p_menuitem_notifications</item>
+        <item>@string/p_menuitem_activity</item>
+        <item>@string/p_menuitem_weather</item>
+        <item>@string/p_menuitem_alarm</item>
+        <item>@string/p_menuitem_timer</item>
+        <item>@string/p_menuitem_settings</item>
+        <item>@string/p_menuitem_music</item>
+    </string-array>
+
+    <string-array name="pref_gts_display_items">
+        <item>@string/menuitem_status</item>
+        <item>@string/menuitem_pai</item>
+        <item>@string/menuitem_hr</item>
+        <item>@string/menuitem_workout</item>
+        <item>@string/menuitem_activity</item>
+        <item>@string/menuitem_weather</item>
+        <item>@string/menuitem_music</item>
+        <item>@string/menuitem_notifications</item>
+        <item>@string/menuitem_alarm</item>
+        <item>@string/menuitem_eventreminder</item>
+        <item>@string/menuitem_more</item>
+        <item>@string/menuitem_settings</item>
+    </string-array>
+
+    <string-array name="pref_gts_display_items_values">
+        <item>@string/p_menuitem_status</item>
+        <item>@string/p_menuitem_pai</item>
+        <item>@string/p_menuitem_hr</item>
+        <item>@string/p_menuitem_workout</item>
+        <item>@string/p_menuitem_activity</item>
+        <item>@string/p_menuitem_weather</item>
+        <item>@string/p_menuitem_music</item>
+        <item>@string/p_menuitem_notifications</item>
+        <item>@string/p_menuitem_alarm</item>
+        <item>@string/p_menuitem_eventreminder</item>
+        <item>@string/p_menuitem_more</item>
+        <item>@string/p_menuitem_settings</item>
+    </string-array>
+
+    <string-array name="pref_gts_display_items_default">
+        <item>@string/p_menuitem_status</item>
+        <item>@string/p_menuitem_pai</item>
+        <item>@string/p_menuitem_hr</item>
+        <item>@string/p_menuitem_workout</item>
+        <item>@string/p_menuitem_activity</item>
+        <item>@string/p_menuitem_weather</item>
+        <item>@string/p_menuitem_music</item>
+        <item>@string/p_menuitem_notifications</item>
+        <item>@string/p_menuitem_alarm</item>
+        <item>@string/p_menuitem_eventreminder</item>
+        <item>@string/p_menuitem_more</item>
+        <item>@string/p_menuitem_settings</item>
+    </string-array>
+
+    <string-array name="pref_gtsgtr2_display_items">
+        <item>@string/menuitem_status</item>
+        <item>@string/menuitem_goal</item>
+        <item>@string/menuitem_hr</item>
+        <item>@string/menuitem_pai</item>
+        <item>@string/menuitem_workout</item>
+        <item>@string/menuitem_activity</item>
+        <item>@string/menuitem_sleep</item>
+        <item>@string/menuitem_weather</item>
+        <item>@string/menuitem_alarm</item>
+        <item>@string/menuitem_events</item>
+        <item>@string/menuitem_stopwatch</item>
+        <item>@string/menuitem_timer</item>
+        <item>@string/menuitem_breathing</item>
+        <item>@string/menuitem_stress</item>
+        <item>@string/menuitem_spo2</item>
+        <item>@string/menuitem_worldclock</item>
+        <item>@string/menuitem_pomodoro</item>
+        <item>@string/menuitem_music</item>
+        <item>@string/menuitem_cycles</item>
+        <item>@string/menuitem_compass</item>
+        <item>@string/menuitem_temperature</item>
+        <item>@string/menuitem_widgets</item>
+        <item>@string/menuitem_settings</item>
+        <item>@string/menuitem_more</item>
+    </string-array>
+
+    <string-array name="pref_gtsgtr2_display_items_values">
+        <item>@string/p_menuitem_status</item>
+        <item>@string/p_menuitem_goal</item>
+        <item>@string/p_menuitem_hr</item>
+        <item>@string/p_menuitem_pai</item>
+        <item>@string/p_menuitem_workout</item>
+        <item>@string/p_menuitem_activity</item>
+        <item>@string/p_menuitem_sleep</item>
+        <item>@string/p_menuitem_weather</item>
+        <item>@string/p_menuitem_alarm</item>
+        <item>@string/p_menuitem_events</item>
+        <item>@string/p_menuitem_stopwatch</item>
+        <item>@string/p_menuitem_timer</item>
+        <item>@string/p_menuitem_breathing</item>
+        <item>@string/p_menuitem_stress</item>
+        <item>@string/p_menuitem_spo2</item>
+        <item>@string/p_menuitem_worldclock</item>
+        <item>@string/p_menuitem_pomodoro</item>
+        <item>@string/p_menuitem_music</item>
+        <item>@string/p_menuitem_cycles</item>
+        <item>@string/p_menuitem_compass</item>
+        <item>@string/p_menuitem_temperature</item>
+        <item>@string/p_menuitem_widgets</item>
+        <item>@string/p_menuitem_settings</item>
+        <item>@string/p_menuitem_more</item>
+    </string-array>
+
+    <string-array name="pref_gtsgtr2_display_items_default">
+        <item>@string/p_menuitem_status</item>
+        <item>@string/p_menuitem_goal</item>
+        <item>@string/p_menuitem_hr</item>
+        <item>@string/p_menuitem_pai</item>
+        <item>@string/p_menuitem_workout</item>
+        <item>@string/p_menuitem_activity</item>
+        <item>@string/p_menuitem_sleep</item>
+        <item>@string/p_menuitem_weather</item>
+        <item>@string/p_menuitem_alarm</item>
+        <item>@string/p_menuitem_events</item>
+        <item>@string/p_menuitem_stopwatch</item>
+        <item>@string/p_menuitem_timer</item>
+        <item>@string/p_menuitem_breathing</item>
+        <item>@string/p_menuitem_stress</item>
+        <item>@string/p_menuitem_spo2</item>
+        <item>@string/p_menuitem_worldclock</item>
+        <item>@string/p_menuitem_pomodoro</item>
+        <item>@string/p_menuitem_music</item>
+        <item>@string/p_menuitem_cycles</item>
+        <item>@string/p_menuitem_compass</item>
+        <item>@string/p_menuitem_temperature</item>
+        <item>@string/p_menuitem_widgets</item>
+        <item>@string/p_menuitem_settings</item>
+        <item>@string/p_menuitem_more</item>
+    </string-array>
+
+    <string-array name="pref_bip3pro_display_items">
+        <item>@string/menuitem_goal</item>
+        <item>@string/menuitem_hr</item>
+        <item>@string/menuitem_pai</item>
+        <item>@string/menuitem_workout</item>
+        <item>@string/menuitem_activity</item>
+        <item>@string/menuitem_sleep</item>
+        <item>@string/menuitem_weather</item>
+        <item>@string/menuitem_alarm</item>
+        <item>@string/menuitem_stopwatch</item>
+        <item>@string/menuitem_timer</item>
+        <item>@string/menuitem_breathing</item>
+        <item>@string/menuitem_stress</item>
+        <item>@string/menuitem_spo2</item>
+        <item>@string/menuitem_worldclock</item>
+        <item>@string/menuitem_music</item>
+        <item>@string/menuitem_cycles</item>
+        <item>@string/menuitem_settings</item>
+        <item>@string/menuitem_more</item>
+    </string-array>
+
+    <string-array name="pref_bip3pro_display_items_values">
+        <item>@string/p_menuitem_goal</item>
+        <item>@string/p_menuitem_hr</item>
+        <item>@string/p_menuitem_pai</item>
+        <item>@string/p_menuitem_workout</item>
+        <item>@string/p_menuitem_activity</item>
+        <item>@string/p_menuitem_sleep</item>
+        <item>@string/p_menuitem_weather</item>
+        <item>@string/p_menuitem_alarm</item>
+        <item>@string/p_menuitem_stopwatch</item>
+        <item>@string/p_menuitem_timer</item>
+        <item>@string/p_menuitem_breathing</item>
+        <item>@string/p_menuitem_stress</item>
+        <item>@string/p_menuitem_spo2</item>
+        <item>@string/p_menuitem_worldclock</item>
+        <item>@string/p_menuitem_music</item>
+        <item>@string/p_menuitem_cycles</item>
+        <item>@string/p_menuitem_settings</item>
+        <item>@string/p_menuitem_more</item>
+    </string-array>
+
+    <string-array name="pref_bip3pro_display_items_default">
+        <item>@string/p_menuitem_goal</item>
+        <item>@string/p_menuitem_hr</item>
+        <item>@string/p_menuitem_pai</item>
+        <item>@string/p_menuitem_workout</item>
+        <item>@string/p_menuitem_activity</item>
+        <item>@string/p_menuitem_sleep</item>
+        <item>@string/p_menuitem_weather</item>
+        <item>@string/p_menuitem_alarm</item>
+        <item>@string/p_menuitem_stopwatch</item>
+        <item>@string/p_menuitem_timer</item>
+        <item>@string/p_menuitem_breathing</item>
+        <item>@string/p_menuitem_stress</item>
+        <item>@string/p_menuitem_spo2</item>
+        <item>@string/p_menuitem_worldclock</item>
+        <item>@string/p_menuitem_music</item>
+        <item>@string/p_menuitem_cycles</item>
+        <item>@string/p_menuitem_settings</item>
+        <item>@string/p_menuitem_more</item>
+    </string-array>
+
+    <string-array name="pref_gts2mini_display_items">
+        <item>@string/menuitem_goal</item>
+        <item>@string/menuitem_hr</item>
+        <item>@string/menuitem_pai</item>
+        <item>@string/menuitem_workout</item>
+        <item>@string/menuitem_activity</item>
+        <item>@string/menuitem_sleep</item>
+        <item>@string/menuitem_weather</item>
+        <item>@string/menuitem_alarm</item>
+        <item>@string/menuitem_eventreminder</item>
+        <item>@string/menuitem_stopwatch</item>
+        <item>@string/menuitem_timer</item>
+        <item>@string/menuitem_breathing</item>
+        <item>@string/menuitem_stress</item>
+        <item>@string/menuitem_spo2</item>
+        <item>@string/menuitem_worldclock</item>
+        <item>@string/menuitem_pomodoro</item>
+        <item>@string/menuitem_music</item>
+        <item>@string/menuitem_cycles</item>
+        <item>@string/menuitem_compass</item>
+        <item>@string/menuitem_findphone</item>
+        <item>@string/menuitem_takephoto</item>
+        <item>@string/menuitem_alexa</item>
+        <item>@string/menuitem_settings</item>
+        <item>@string/menuitem_more</item>
+    </string-array>
+
+    <string-array name="pref_gts2mini_display_items_values">
+        <item>@string/p_menuitem_goal</item>
+        <item>@string/p_menuitem_hr</item>
+        <item>@string/p_menuitem_pai</item>
+        <item>@string/p_menuitem_workout</item>
+        <item>@string/p_menuitem_activity</item>
+        <item>@string/p_menuitem_sleep</item>
+        <item>@string/p_menuitem_weather</item>
+        <item>@string/p_menuitem_alarm</item>
+        <item>@string/p_menuitem_eventreminder</item>
+        <item>@string/p_menuitem_stopwatch</item>
+        <item>@string/p_menuitem_timer</item>
+        <item>@string/p_menuitem_breathing</item>
+        <item>@string/p_menuitem_stress</item>
+        <item>@string/p_menuitem_spo2</item>
+        <item>@string/p_menuitem_worldclock</item>
+        <item>@string/p_menuitem_pomodoro</item>
+        <item>@string/p_menuitem_music</item>
+        <item>@string/p_menuitem_cycles</item>
+        <item>@string/p_menuitem_compass</item>
+        <item>@string/p_menuitem_findphone</item>
+        <item>@string/p_menuitem_takephoto</item>
+        <item>@string/p_menuitem_alexa</item>
+        <item>@string/p_menuitem_settings</item>
+        <item>@string/p_menuitem_more</item>
+    </string-array>
+
+    <string-array name="pref_gts2mini_display_items_default">
+        <item>@string/p_menuitem_goal</item>
+        <item>@string/p_menuitem_hr</item>
+        <item>@string/p_menuitem_pai</item>
+        <item>@string/p_menuitem_workout</item>
+        <item>@string/p_menuitem_activity</item>
+        <item>@string/p_menuitem_sleep</item>
+        <item>@string/p_menuitem_weather</item>
+        <item>@string/p_menuitem_alarm</item>
+        <item>@string/p_menuitem_eventreminder</item>
+        <item>@string/p_menuitem_stopwatch</item>
+        <item>@string/p_menuitem_timer</item>
+        <item>@string/p_menuitem_breathing</item>
+        <item>@string/p_menuitem_stress</item>
+        <item>@string/p_menuitem_spo2</item>
+        <item>@string/p_menuitem_worldclock</item>
+        <item>@string/p_menuitem_pomodoro</item>
+        <item>@string/p_menuitem_music</item>
+        <item>@string/p_menuitem_cycles</item>
+        <item>@string/p_menuitem_compass</item>
+        <item>@string/p_menuitem_findphone</item>
+        <item>@string/p_menuitem_takephoto</item>
+        <item>@string/p_menuitem_settings</item>
+        <item>@string/p_menuitem_alexa</item>
+        <item>@string/p_menuitem_more</item>
+    </string-array>
+
+    <string-array name="pref_trex_display_items">
+        <item>@string/menuitem_status</item>
+        <item>@string/menuitem_pai</item>
+        <item>@string/menuitem_hr</item>
+        <item>@string/menuitem_workout</item>
+        <item>@string/menuitem_activity</item>
+        <item>@string/menuitem_weather</item>
+        <item>@string/menuitem_music</item>
+        <item>@string/menuitem_alarm</item>
+        <item>@string/menuitem_eventreminder</item>
+        <item>@string/menuitem_stopwatch</item>
+        <item>@string/menuitem_timer</item>
+        <item>@string/menuitem_findphone</item>
+        <item>@string/menuitem_compass</item>
+        <item>@string/menuitem_settings</item>
+    </string-array>
+
+    <string-array name="pref_trex_display_items_values">
+        <item>@string/p_menuitem_status</item>
+        <item>@string/p_menuitem_pai</item>
+        <item>@string/p_menuitem_hr</item>
+        <item>@string/p_menuitem_workout</item>
+        <item>@string/p_menuitem_activity</item>
+        <item>@string/p_menuitem_weather</item>
+        <item>@string/p_menuitem_music</item>
+        <item>@string/p_menuitem_alarm</item>
+        <item>@string/p_menuitem_eventreminder</item>
+        <item>@string/p_menuitem_stopwatch</item>
+        <item>@string/p_menuitem_timer</item>
+        <item>@string/p_menuitem_findphone</item>
+        <item>@string/p_menuitem_compass</item>
+        <item>@string/p_menuitem_settings</item>
+    </string-array>
+
+    <string-array name="pref_trex_display_items_default">
+        <item>@string/p_menuitem_status</item>
+        <item>@string/p_menuitem_pai</item>
+        <item>@string/p_menuitem_hr</item>
+        <item>@string/p_menuitem_workout</item>
+        <item>@string/p_menuitem_activity</item>
+        <item>@string/p_menuitem_weather</item>
+        <item>@string/p_menuitem_music</item>
+        <item>@string/p_menuitem_alarm</item>
+        <item>@string/p_menuitem_eventreminder</item>
+        <item>@string/p_menuitem_stopwatch</item>
+        <item>@string/p_menuitem_timer</item>
+        <item>@string/p_menuitem_findphone</item>
+        <item>@string/p_menuitem_compass</item>
+        <item>@string/p_menuitem_settings</item>
+    </string-array>
+
+
+    <string-array name="pref_trex_pro_display_items">
+        <item>@string/menuitem_status</item>
+        <item>@string/menuitem_pai</item>
+        <item>@string/menuitem_hr</item>
+        <item>@string/menuitem_workout</item>
+        <item>@string/menuitem_activity</item>
+        <item>@string/menuitem_weather</item>
+        <item>@string/menuitem_music</item>
+        <item>@string/menuitem_alarm</item>
+        <item>@string/menuitem_notifications</item>
+        <item>@string/menuitem_stopwatch</item>
+        <item>@string/menuitem_timer</item>
+        <item>@string/menuitem_findphone</item>
+        <item>@string/menuitem_compass</item>
+        <item>@string/menuitem_barometer</item>
+        <item>@string/menuitem_stress</item>
+        <item>@string/menuitem_spo2</item>
+        <item>@string/menuitem_goal</item>
+        <item>@string/menuitem_events</item>
+        <item>@string/menuitem_settings</item>
+    </string-array>
+
+    <string-array name="pref_trex_pro_display_items_values">
+        <item>@string/p_menuitem_status</item>
+        <item>@string/p_menuitem_pai</item>
+        <item>@string/p_menuitem_hr</item>
+        <item>@string/p_menuitem_workout</item>
+        <item>@string/p_menuitem_activity</item>
+        <item>@string/p_menuitem_weather</item>
+        <item>@string/p_menuitem_music</item>
+        <item>@string/p_menuitem_alarm</item>
+        <item>@string/p_menuitem_notifications</item>
+        <item>@string/p_menuitem_stopwatch</item>
+        <item>@string/p_menuitem_timer</item>
+        <item>@string/p_menuitem_findphone</item>
+        <item>@string/p_menuitem_compass</item>
+        <item>@string/p_menuitem_barometer</item>
+        <item>@string/p_menuitem_stress</item>
+        <item>@string/p_menuitem_spo2</item>
+        <item>@string/p_menuitem_goal</item>
+        <item>@string/p_menuitem_events</item>
+        <item>@string/p_menuitem_settings</item>
+    </string-array>
+
+    <string-array name="pref_trex_pro_display_items_default">
+        <item>@string/p_menuitem_status</item>
+        <item>@string/p_menuitem_pai</item>
+        <item>@string/p_menuitem_hr</item>
+        <item>@string/p_menuitem_workout</item>
+        <item>@string/p_menuitem_activity</item>
+        <item>@string/p_menuitem_weather</item>
+        <item>@string/p_menuitem_music</item>
+        <item>@string/p_menuitem_alarm</item>
+        <item>@string/p_menuitem_notifications</item>
+        <item>@string/p_menuitem_stopwatch</item>
+        <item>@string/p_menuitem_timer</item>
+        <item>@string/p_menuitem_findphone</item>
+        <item>@string/p_menuitem_compass</item>
+        <item>@string/p_menuitem_barometer</item>
+        <item>@string/p_menuitem_stress</item>
+        <item>@string/p_menuitem_spo2</item>
+        <item>@string/p_menuitem_goal</item>
+        <item>@string/p_menuitem_events</item>
+        <item>@string/p_menuitem_settings</item>
+    </string-array>
+
+    <string-array name="pref_bips_display_items">
+        <item>@string/menuitem_status</item>
+        <item>@string/menuitem_hr</item>
+        <item>@string/menuitem_pai</item>
+        <item>@string/menuitem_workout</item>
+        <item>@string/menuitem_alipay</item>
+        <item>@string/menuitem_nfc</item>
+        <item>@string/menuitem_weather</item>
+        <item>@string/menuitem_alarm</item>
+        <item>@string/menuitem_timer</item>
+        <item>@string/menuitem_compass</item>
+        <item>@string/menuitem_worldclock</item>
+        <item>@string/menuitem_music</item>
+        <item>@string/menuitem_settings</item>
+    </string-array>
+
+    <string-array name="pref_bips_display_items_values">
+        <item>@string/p_menuitem_status</item>
+        <item>@string/p_menuitem_hr</item>
+        <item>@string/p_menuitem_pai</item>
+        <item>@string/p_menuitem_workout</item>
+        <item>@string/p_menuitem_alipay</item>
+        <item>@string/p_menuitem_nfc</item>
+        <item>@string/p_menuitem_weather</item>
+        <item>@string/p_menuitem_alarm</item>
+        <item>@string/p_menuitem_timer_stopwatch</item>
+        <item>@string/p_menuitem_compass</item>
+        <item>@string/p_menuitem_worldclock</item>
+        <item>@string/p_menuitem_music</item>
+        <item>@string/p_menuitem_settings</item>
+    </string-array>
+
+    <string-array name="pref_bips_display_items_default">
+        <item>@string/p_menuitem_status</item>
+        <item>@string/p_menuitem_hr</item>
+        <item>@string/p_menuitem_pai</item>
+        <item>@string/p_menuitem_workout</item>
+        <item>@string/p_menuitem_alipay</item>
+        <item>@string/p_menuitem_nfc</item>
+        <item>@string/p_menuitem_weather</item>
+        <item>@string/p_menuitem_alarm</item>
+        <item>@string/p_menuitem_timer</item>
+        <item>@string/p_menuitem_compass</item>
+        <item>@string/p_menuitem_worldclock</item>
+        <item>@string/p_menuitem_music</item>
+        <item>@string/p_menuitem_settings</item>
+    </string-array>
+
+    <string-array name="pref_bipu_display_items">
+        <item>@string/menuitem_hr</item>
+        <item>@string/menuitem_pai</item>
+        <item>@string/menuitem_goal</item>
+        <item>@string/menuitem_workout</item>
+        <item>@string/menuitem_activity</item>
+        <item>@string/menuitem_sleep</item>
+        <item>@string/menuitem_weather</item>
+        <item>@string/menuitem_alarm</item>
+        <item>@string/menuitem_eventreminder</item>
+        <item>@string/menuitem_stress</item>
+        <item>@string/menuitem_spo2</item>
+        <item>@string/menuitem_breathing</item>
+        <item>@string/menuitem_stopwatch</item>
+        <item>@string/menuitem_timer</item>
+        <item>@string/menuitem_worldclock</item>
+        <item>@string/menuitem_pomodoro</item>
+        <item>@string/menuitem_music</item>
+        <item>@string/menuitem_cycles</item>
+        <item>@string/menuitem_settings</item>
+        <item>@string/menuitem_more</item>
+        <item>@string/menuitem_findphone</item>
+        <item>@string/menuitem_takephoto</item>
+    </string-array>
+
+    <string-array name="pref_bipu_display_items_values">
+        <item>@string/p_menuitem_hr</item>
+        <item>@string/p_menuitem_pai</item>
+        <item>@string/p_menuitem_goal</item>
+        <item>@string/p_menuitem_workout</item>
+        <item>@string/p_menuitem_activity</item>
+        <item>@string/p_menuitem_sleep</item>
+        <item>@string/p_menuitem_weather</item>
+        <item>@string/p_menuitem_alarm</item>
+        <item>@string/p_menuitem_eventreminder</item>
+        <item>@string/p_menuitem_stress</item>
+        <item>@string/p_menuitem_spo2</item>
+        <item>@string/p_menuitem_breathing</item>
+        <item>@string/p_menuitem_stopwatch</item>
+        <item>@string/p_menuitem_timer</item>
+        <item>@string/p_menuitem_worldclock</item>
+        <item>@string/p_menuitem_pomodoro</item>
+        <item>@string/p_menuitem_music</item>
+        <item>@string/p_menuitem_cycles</item>
+        <item>@string/p_menuitem_settings</item>
+        <item>@string/p_menuitem_more</item>
+        <item>@string/p_menuitem_findphone</item>
+        <item>@string/p_menuitem_takephoto</item>
+    </string-array>
+
+    <string-array name="pref_bipu_display_items_default">
+        <item>@string/p_menuitem_hr</item>
+        <item>@string/p_menuitem_pai</item>
+        <item>@string/p_menuitem_workout</item>
+        <item>@string/p_menuitem_weather</item>
+        <item>@string/p_menuitem_alarm</item>
+        <item>@string/p_menuitem_timer</item>
+        <item>@string/p_menuitem_music</item>
+        <item>@string/p_menuitem_settings</item>
+        <item>@string/p_menuitem_more</item>
+        <item>@string/p_menuitem_worldclock</item>
+    </string-array>
+
+    <string-array name="pref_bipupro_display_items">
+        <item>@string/menuitem_hr</item>
+        <item>@string/menuitem_pai</item>
+        <item>@string/menuitem_goal</item>
+        <item>@string/menuitem_workout</item>
+        <item>@string/menuitem_activity</item>
+        <item>@string/menuitem_sleep</item>
+        <item>@string/menuitem_weather</item>
+        <item>@string/menuitem_alarm</item>
+        <item>@string/menuitem_eventreminder</item>
+        <item>@string/menuitem_stress</item>
+        <item>@string/menuitem_spo2</item>
+        <item>@string/menuitem_breathing</item>
+        <item>@string/menuitem_stopwatch</item>
+        <item>@string/menuitem_timer</item>
+        <item>@string/menuitem_worldclock</item>
+        <item>@string/menuitem_pomodoro</item>
+        <item>@string/menuitem_music</item>
+        <item>@string/menuitem_cycles</item>
+        <item>@string/menuitem_settings</item>
+        <item>@string/menuitem_more</item>
+        <item>@string/menuitem_compass</item>
+        <item>@string/menuitem_findphone</item>
+        <item>@string/menuitem_takephoto</item>
+        <item>@string/menuitem_alexa</item>
+    </string-array>
+
+    <string-array name="pref_bipupro_display_items_values">
+        <item>@string/p_menuitem_hr</item>
+        <item>@string/p_menuitem_pai</item>
+        <item>@string/p_menuitem_goal</item>
+        <item>@string/p_menuitem_workout</item>
+        <item>@string/p_menuitem_activity</item>
+        <item>@string/p_menuitem_sleep</item>
+        <item>@string/p_menuitem_weather</item>
+        <item>@string/p_menuitem_alarm</item>
+        <item>@string/p_menuitem_eventreminder</item>
+        <item>@string/p_menuitem_stress</item>
+        <item>@string/p_menuitem_spo2</item>
+        <item>@string/p_menuitem_breathing</item>
+        <item>@string/p_menuitem_stopwatch</item>
+        <item>@string/p_menuitem_timer</item>
+        <item>@string/p_menuitem_worldclock</item>
+        <item>@string/p_menuitem_pomodoro</item>
+        <item>@string/p_menuitem_music</item>
+        <item>@string/p_menuitem_cycles</item>
+        <item>@string/p_menuitem_settings</item>
+        <item>@string/p_menuitem_more</item>
+        <item>@string/p_menuitem_compass</item>
+        <item>@string/p_menuitem_findphone</item>
+        <item>@string/p_menuitem_takephoto</item>
+        <item>@string/p_menuitem_alexa</item>
+
+    </string-array>
+
+    <string-array name="pref_bipupro_display_items_default">
+        <item>@string/p_menuitem_hr</item>
+        <item>@string/p_menuitem_pai</item>
+        <item>@string/p_menuitem_workout</item>
+        <item>@string/p_menuitem_weather</item>
+        <item>@string/p_menuitem_alarm</item>
+        <item>@string/p_menuitem_timer</item>
+        <item>@string/p_menuitem_music</item>
+        <item>@string/p_menuitem_settings</item>
+        <item>@string/p_menuitem_more</item>
+        <item>@string/p_menuitem_worldclock</item>
+    </string-array>
+
+    <string-array name="pref_bips_shortcuts">
+        <item>@string/menuitem_status</item>
+        <item>@string/menuitem_alipay</item>
+        <item>@string/menuitem_nfc</item>
+        <item>@string/menuitem_pai</item>
+        <item>@string/menuitem_hr</item>
+        <item>@string/menuitem_music</item>
+        <item>@string/menuitem_weather</item>
+    </string-array>
+
+    <string-array name="pref_bips_shortcuts_values">
+        <item>@string/p_menuitem_status</item>
+        <item>@string/p_menuitem_alipay</item>
+        <item>@string/p_menuitem_nfc</item>
+        <item>@string/p_menuitem_pai</item>
+        <item>@string/p_menuitem_hr</item>
+        <item>@string/p_menuitem_music</item>
+        <item>@string/p_menuitem_weather</item>
+    </string-array>
+
+    <string-array name="pref_bips_shortcuts_default">
+        <item>@string/p_menuitem_music</item>
+        <item>@string/p_menuitem_weather</item>
+    </string-array>
+
+    <string-array name="pref_entries_unit_system">
+        <item>@string/unit_metric</item>
+        <item>@string/unit_imperial</item>
+    </string-array>
+
+    <string-array name="pref_values_unit_system">
+        <item>@string/p_unit_metric</item>
+        <item>@string/p_unit_imperial</item>
+    </string-array>
+
+    <string-array name="pref_timeformat_entries">
+        <item>@string/automatic</item>
+        <item>@string/timeformat_24h</item>
+        <item>@string/timeformat_am_pm</item>
+    </string-array>
+
+    <string-array name="goal_fitness_secondary_goal_values">
+        <item>active_time</item>
+        <item>standing_time</item>
+    </string-array>
+
+    <string-array name="goal_fitness_secondary_goal_entries">
+        <item>@string/active_time</item>
+        <item>@string/standing_time</item>
+    </string-array>
+
+    <string-array name="pref_timeformat_values">
+        <item>@string/p_timeformat_auto</item>
+        <item>@string/p_timeformat_24h</item>
+        <item>@string/p_timeformat_am_pm</item>
+    </string-array>
+
+    <string-array name="pref_call_privacy_mode">
+        <item name="off">@string/pref_call_privacy_mode_off</item>
+        <item name="name">@string/pref_call_privacy_mode_name</item>
+        <item name="number">@string/pref_call_privacy_mode_number</item>
+        <item name="complete">@string/pref_call_privacy_mode_complete</item>
+    </string-array>
+
+    <string-array name="pref_call_privacy_mode_values">
+        <item>@string/p_call_privacy_mode_off</item>
+        <item>@string/p_call_privacy_mode_name</item>
+        <item>@string/p_call_privacy_mode_number</item>
+        <item>@string/p_call_privacy_mode_complete</item>
+    </string-array>
+
+    <string-array name="pref_message_privacy_mode">
+        <item name="off">@string/pref_message_privacy_mode_off</item>
+        <item name="complete">@string/pref_message_privacy_mode_complete</item>
+        <item name="bodyonly">@string/pref_message_privacy_mode_bodyonly</item>
+    </string-array>
+
+    <string-array name="pref_message_privacy_mode_values">
+        <item>@string/p_message_privacy_mode_off</item>
+        <item>@string/p_message_privacy_mode_complete</item>
+        <item>@string/p_message_privacy_mode_bodyonly</item>
+    </string-array>
+
+
+    <string-array name="pref_language_all">
+        <item name="auto">@string/automatic</item>
+        <item name="zh_CN">@string/simplified_chinese</item>
+        <item name="zh_TW">@string/traditional_chinese</item>
+        <item name="en_AU">@string/english_au</item>
+        <item name="en_CA">@string/english_ca</item>
+        <item name="en_GB">@string/english_gb</item>
+        <item name="en_IN">@string/english_in</item>
+        <item name="en_US">@string/english_us</item>
+        <item name="es_ES">@string/spanish_es</item>
+        <item name="es_MX">@string/spanish_mx</item>
+        <item name="es_US">@string/spanish_us</item>
+        <item name="de_DE">@string/german</item>
+        <item name="it_IT">@string/italian</item>
+        <item name="fr_CA">@string/french_ca</item>
+        <item name="fr_FR">@string/french_fr</item>
+        <item name="pt_BR">@string/portuguese_br</item>
+        <item name="pt_PT">@string/portuguese_pt</item>
+        <item name="nl_NL">@string/dutch</item>
+        <item name="pl_PL">@string/polish</item>
+        <item name="cs_CZ">@string/czesh</item>
+        <item name="tr_TR">@string/turkish</item>
+        <item name="el_GR">@string/greek</item>
+        <item name="ru_RU">@string/russian</item>
+        <item name="uk_UA">@string/ukrainian</item>
+        <item name="ar_SA">@string/arabic</item>
+        <item name="id_ID">@string/indonesian</item>
+        <item name="th_TH">@string/thai</item>
+        <item name="vi_VN">@string/vietnamese</item>
+        <item name="ja_JP">@string/japanese</item>
+        <item name="ko_KO">@string/korean</item>
+        <item name="he_IL">@string/hebrew</item>
+        <item name="da_DK">@string/danish</item>
+        <item name="nb_NO">@string/norwegian_bokmal</item>
+        <item name="ro_RO">@string/romanian</item>
+        <item name="sv_SE">@string/swedish</item>
+    </string-array>
+
+    <string-array name="pref_language_all_values">
+        <item>auto</item>
+        <item>zh_CN</item>
+        <item>zh_TW</item>
+        <item>en_AU</item>
+        <item>en_CA</item>
+        <item>en_GB</item>
+        <item>en_IN</item>
+        <item>en_US</item>
+        <item>es_ES</item>
+        <item>es_MX</item>
+        <item>es_US</item>
+        <item>de_DE</item>
+        <item>it_IT</item>
+        <item>fr_CA</item>
+        <item>fr_FR</item>
+        <item>pt_BR</item>
+        <item>pt_PT</item>
+        <item>nl_NL</item>
+        <item>pl_PL</item>
+        <item>cs_CZ</item>
+        <item>tr_TR</item>
+        <item>el_GR</item>
+        <item>ru_RU</item>
+        <item>uk_UA</item>
+        <item>ar_SA</item>
+        <item>id_ID</item>
+        <item>th_TH</item>
+        <item>vi_VN</item>
+        <item>ja_JP</item>
+        <item>ko_KO</item>
+        <item>he_IL</item>
+        <item>da_DK</item>
+        <item>nb_NO</item>
+        <item>ro_RO</item>
+        <item>sv_SE</item>
+    </string-array>
+
+    <string-array name="pref_amazfitbip_language">
+        <item name="auto">@string/automatic</item>
+        <item name="zh_CN">@string/simplified_chinese</item>
+        <item name="zh_TW">@string/traditional_chinese</item>
+        <item name="en_US">@string/english</item>
+        <item name="es_ES">@string/spanish</item>
+        <item name="de_DE">@string/german</item>
+        <item name="it_IT">@string/italian</item>
+        <item name="fr_FR">@string/french</item>
+        <item name="pt_BR">@string/portuguese</item>
+        <item name="tr_TR">@string/turkish</item>
+        <item name="cs_CZ">@string/czesh</item>
+        <item name="ru_RU">@string/russian</item>
+    </string-array>
+
+    <string-array name="pref_amazfitbip_language_values">
+        <item>auto</item>
+        <item>zh_CN</item>
+        <item>zh_TW</item>
+        <item>en_US</item>
+        <item>es_ES</item>
+        <item>de_DE</item>
+        <item>it_IT</item>
+        <item>fr_FR</item>
+        <item>pt_BR</item>
+        <item>tr_TR</item>
+        <item>cs_CZ</item>
+        <item>ru_RU</item>
+    </string-array>
+
+    <string-array name="pref_amazfitcor_language">
+        <item name="auto">@string/automatic</item>
+        <item name="zh_CN">@string/simplified_chinese</item>
+        <item name="zh_TW">@string/traditional_chinese</item>
+        <item name="en_US">@string/english</item>
+        <item name="es_ES">@string/spanish</item>
+        <item name="ru_RU">@string/russian</item>
+    </string-array>
+
+    <string-array name="pref_amazfitcor_language_values">
+        <item>auto</item>
+        <item>zh_CN</item>
+        <item>zh_TW</item>
+        <item>en_US</item>
+        <item>es_ES</item>
+        <item>ru_RU</item>
+    </string-array>
+
+    <string-array name="prefs_heartrate_measurement_interval">
+        <!-- This will be filled dynamically by HeartRateCapability -->
+        <item name="0">@string/off</item>
+    </string-array>
+
+    <string-array name="prefs_heartrate_measurement_interval_values">
+        <!-- This will be filled dynamically by HeartRateCapability -->
+        <item>0</item>
+    </string-array>
+
+    <string-array name="prefs_miband_heartrate_alert_threshold">
+        <item name="100">@string/heartrate_bpm_100</item>
+        <item name="105">@string/heartrate_bpm_105</item>
+        <item name="110">@string/heartrate_bpm_110</item>
+        <item name="115">@string/heartrate_bpm_115</item>
+        <item name="120">@string/heartrate_bpm_120</item>
+        <item name="125">@string/heartrate_bpm_125</item>
+        <item name="130">@string/heartrate_bpm_130</item>
+        <item name="135">@string/heartrate_bpm_135</item>
+        <item name="140">@string/heartrate_bpm_140</item>
+        <item name="145">@string/heartrate_bpm_145</item>
+        <item name="150">@string/heartrate_bpm_150</item>
+    </string-array>
+
+    <string-array name="prefs_miband_heartrate_alert_threshold_values">
+        <item>100</item>
+        <item>105</item>
+        <item>110</item>
+        <item>115</item>
+        <item>120</item>
+        <item>125</item>
+        <item>130</item>
+        <item>135</item>
+        <item>140</item>
+        <item>145</item>
+        <item>150</item>
+    </string-array>
+
+    <string-array name="prefs_miband_heartrate_high_alert_threshold_with_off">
+        <item name="0">@string/off</item>
+        <item name="100">@string/heartrate_bpm_100</item>
+        <item name="110">@string/heartrate_bpm_110</item>
+        <item name="120">@string/heartrate_bpm_120</item>
+        <item name="130">@string/heartrate_bpm_130</item>
+        <item name="140">@string/heartrate_bpm_140</item>
+        <item name="150">@string/heartrate_bpm_150</item>
+    </string-array>
+
+    <string-array name="prefs_miband_heartrate_high_alert_threshold_with_off_values">
+        <item>0</item>
+        <item>100</item>
+        <item>110</item>
+        <item>120</item>
+        <item>130</item>
+        <item>140</item>
+        <item>150</item>
+    </string-array>
+
+    <string-array name="prefs_miband_heartrate_high_active_alert_threshold_with_off">
+        <item name="0">@string/off</item>
+        <item name="155">@string/heartrate_bpm_155</item>
+        <item name="165">@string/heartrate_bpm_165</item>
+        <item name="175">@string/heartrate_bpm_175</item>
+        <item name="185">@string/heartrate_bpm_185</item>
+        <item name="195">@string/heartrate_bpm_195</item>
+        <item name="205">@string/heartrate_bpm_205</item>
+    </string-array>
+
+    <string-array name="prefs_miband_heartrate_high_active_alert_threshold_with_off_values">
+        <item>0</item>
+        <item>155</item>
+        <item>165</item>
+        <item>175</item>
+        <item>185</item>
+        <item>195</item>
+        <item>205</item>
+    </string-array>
+
+    <string-array name="prefs_miband_heartrate_low_alert_threshold">
+        <item name="0">@string/off</item>
+        <item name="40">@string/heartrate_bpm_40</item>
+        <item name="45">@string/heartrate_bpm_45</item>
+        <item name="50">@string/heartrate_bpm_50</item>
+    </string-array>
+
+    <string-array name="prefs_miband_heartrate_low_alert_threshold_values">
+        <item>0</item>
+        <item>40</item>
+        <item>45</item>
+        <item>50</item>
+    </string-array>
+
+    <string-array name="prefs_spo2_alert_threshold">
+        <item name="80">@string/spo2_perc_80</item>
+        <item name="85">@string/spo2_perc_85</item>
+        <item name="90">@string/spo2_perc_90</item>
+        <item name="off">@string/spo2_off</item>
+    </string-array>
+
+    <string-array name="prefs_spo2_alert_threshold_values">
+        <item>80</item>
+        <item>85</item>
+        <item>90</item>
+        <item>0</item>
+    </string-array>
+
+    <string-array name="prefs_zetime_heartrate_measurement_interval">
+        <item name="0">@string/off</item>
+        <item name="300">@string/interval_five_minutes</item>
+        <item name="600">@string/interval_ten_minutes</item>
+        <item name="900">@string/interval_fifteen_minutes</item>
+        <item name="1800">@string/interval_thirty_minutes</item>
+        <item name="2700">@string/interval_forty_five_minutes</item>
+        <item name="3600">@string/interval_one_hour</item>
+    </string-array>
+
+    <string-array name="prefs_zetime_heartrate_measurement_interval_values">
+        <item>0</item>
+        <item>300</item>
+        <item>600</item>
+        <item>900</item>
+        <item>1800</item>
+        <item>2700</item>
+        <item>3600</item>
+    </string-array>
+
+    <string-array name="pref_casio_connection_duration">
+        <item name="1">@string/minutes_1</item>
+        <item name="2">@string/minutes_2</item>
+        <item name="3">@string/minutes_3</item>
+        <item name="4">@string/minutes_4</item>
+        <item name="5">@string/minutes_5</item>
+        <item name="6">@string/minutes_6</item>
+        <item name="7">@string/minutes_7</item>
+        <item name="8">@string/minutes_8</item>
+        <item name="9">@string/minutes_9</item>
+        <item name="10">@string/minutes_10</item>
+        <item name="15">@string/minutes_15</item>
+        <item name="20">@string/minutes_20</item>
+        <item name="30">@string/minutes_30</item>
+        <item name="45">@string/minutes_45</item>
+        <item name="60">@string/minutes_60</item>
+        <item name="75">@string/minutes_75</item>
+        <item name="90">@string/minutes_90</item>
+        <item name="120">@string/minutes_120</item>
+        <item name="150">@string/minutes_150</item>
+        <item name="180">@string/minutes_180</item>
+        <item name="210">@string/minutes_210</item>
+        <item name="240">@string/minutes_240</item>
+        <item name="255">@string/minutes_255</item>
+    </string-array>
+
+    <string-array name="pref_casio_connection_duration_values">
+        <item>1</item>
+        <item>2</item>
+        <item>3</item>
+        <item>4</item>
+        <item>5</item>
+        <item>6</item>
+        <item>7</item>
+        <item>8</item>
+        <item>9</item>
+        <item>10</item>
+        <item>15</item>
+        <item>20</item>
+        <item>30</item>
+        <item>45</item>
+        <item>60</item>
+        <item>75</item>
+        <item>90</item>
+        <item>120</item>
+        <item>150</item>
+        <item>180</item>
+        <item>210</item>
+        <item>240</item>
+        <item>255</item>
+    </string-array>
+
+    <string-array name="notifications_timeout">
+        <item>@string/no_limit</item>
+        <item>@string/seconds_5</item>
+        <item>@string/seconds_10</item>
+        <item>@string/seconds_20</item>
+        <item>@string/seconds_30</item>
+        <item>@string/minutes_1</item>
+        <item>@string/minutes_5</item>
+        <item>@string/minutes_10</item>
+        <item>@string/minutes_30</item>
+    </string-array>
+    <string-array name="notifications_timeout_values">
+        <item>0</item>
+        <item>5</item>
+        <item>10</item>
+        <item>20</item>
+        <item>30</item>
+        <item>60</item>
+        <item>300</item>
+        <item>600</item>
+        <item>1800</item>
+    </string-array>
+
+    <string-array name="screen_timeout">
+        <item>@string/seconds_5</item>
+        <item>@string/seconds_6</item>
+        <item>@string/seconds_7</item>
+        <item>@string/seconds_8</item>
+        <item>@string/seconds_9</item>
+        <item>@string/seconds_10</item>
+        <item>@string/seconds_11</item>
+        <item>@string/seconds_12</item>
+        <item>@string/seconds_13</item>
+        <item>@string/seconds_14</item>
+        <item>@string/seconds_15</item>
+        <item>@string/seconds_20</item>
+        <item>@string/seconds_25</item>
+        <item>@string/seconds_30</item>
+    </string-array>
+    <string-array name="screen_timeout_values">
+        <item>5</item>
+        <item>6</item>
+        <item>7</item>
+        <item>8</item>
+        <item>9</item>
+        <item>10</item>
+        <item>11</item>
+        <item>12</item>
+        <item>13</item>
+        <item>14</item>
+        <item>15</item>
+        <item>20</item>
+        <item>25</item>
+        <item>30</item>
+    </string-array>
+
+    <string-array name="reminder_repeat">
+        <item>@string/reminder_once</item>
+        <item>@string/reminder_every_day</item>
+        <item>@string/reminder_every_week</item>
+        <item>@string/reminder_every_month</item>
+        <item>@string/reminder_every_year</item>
+    </string-array>
+
+    <string-array name="notification_filter_modes_entries">
+        <item>@string/filter_mode_none</item>
+        <item>@string/filter_mode_whitelist</item>
+        <item>@string/filter_mode_blacklist</item>
+    </string-array>
+
+    <string-array name="notification_filter_submodes_entries">
+        <item>@string/filter_submode_at_least_one</item>
+        <item>@string/filter_submode_all</item>
+    </string-array>
+
+    <!-- Note: ZeTime preferences -->
+    <string-array name="zetime_analog_mode">
+        <item name="0">@string/zetime_analog_mode_hands</item>
+        <item name="1">@string/zetime_analog_mode_handsandsteps</item>
+    </string-array>
+    <string-array name="zetime_analog_mode_values">
+        <item>0</item>
+        <item>1</item>
+    </string-array>
+    <string-array name="zetime_calories_type">
+        <item name="0">@string/zetime_calories_type_active</item>
+        <item name="1">@string/zetime_calories_type_all</item>
+    </string-array>
+    <string-array name="zetime_calories_type_values">
+        <item>0</item>
+        <item>1</item>
+    </string-array>
+    <string-array name="zetime_date_format">
+        <item name="1">@string/zetime_date_format_1</item>
+        <item name="2">@string/zetime_date_format_2</item>
+        <item name="3">@string/zetime_date_format_3</item>
+    </string-array>
+    <string-array name="zetime_date_format_values">
+        <item>1</item>
+        <item>2</item>
+        <item>3</item>
+    </string-array>
+    <string-array name="zetime_signaling_types">
+        <item name="2">@string/zetime_signaling_vibrate_once</item>
+        <item name="4">@string/zetime_signaling_vibrate_twice</item>
+        <item name="7">@string/zetime_signaling_vibrate</item>
+        <item name="9">@string/zetime_signaling_beep_once</item>
+        <item name="10">@string/zetime_signaling_beep_twice</item>
+        <item name="11">@string/zetime_signaling_beep</item>
+        <item name="12">@string/zetime_signaling_vibrate_beep_once</item>
+        <item name="13">@string/zetime_signaling_vibrate_beep</item>
+        <item name="14">@string/zetime_signaling_none</item>
+    </string-array>
+    <string-array name="zetime_signaling_types_values">
+        <item>2</item>
+        <item>4</item>
+        <item>7</item>
+        <item>9</item>
+        <item>10</item>
+        <item>11</item>
+        <item>12</item>
+        <item>13</item>
+        <item>14</item>
+    </string-array>
+
+    <string-array name="button_action_options">
+        <item>@string/pref_button_action_disabled</item>
+        <item>@string/pref_media_play</item>
+        <item>@string/pref_media_pause</item>
+        <item>@string/pref_media_playpause</item>
+        <item>@string/pref_media_next</item>
+        <item>@string/pref_media_previous</item>
+        <item>@string/pref_media_volumeup</item>
+        <item>@string/pref_media_volumedown</item>
+        <item>@string/pref_media_forward</item>
+        <item>@string/pref_media_rewind</item>
+        <item>@string/pref_device_action_broadcast</item>
+        <item>@string/pref_device_action_fitness_app_control_start</item>
+        <item>@string/pref_device_action_fitness_app_control_stop</item>
+        <item>@string/pref_device_action_fitness_app_control_toggle</item>
+    </string-array>
+
+    <string-array name="button_action_values">
+        <item>@string/pref_button_action_disabled_value</item>
+        <item>@string/pref_media_play_value</item>
+        <item>@string/pref_media_pause_value</item>
+        <item>@string/pref_media_playpause_value</item>
+        <item>@string/pref_media_next_value</item>
+        <item>@string/pref_media_previous_value</item>
+        <item>@string/pref_media_volumeup_value</item>
+        <item>@string/pref_media_volumedown_value</item>
+        <item>@string/pref_media_forward_value</item>
+        <item>@string/pref_media_rewind_value</item>
+        <item>@string/pref_device_action_broadcast_value</item>
+        <item>@string/pref_device_action_fitness_app_control_start_value</item>
+        <item>@string/pref_device_action_fitness_app_control_stop_value</item>
+        <item>@string/pref_device_action_fitness_app_control_toggle_value</item>
+    </string-array>
+
+    <string-array name="device_action_options">
+        <item>@string/pref_media_play</item>
+        <item>@string/pref_media_pause</item>
+        <item>@string/pref_media_playpause</item>
+        <item>@string/pref_device_action_broadcast</item>
+        <item>@string/pref_device_action_fitness_app_control_start</item>
+        <item>@string/pref_device_action_fitness_app_control_stop</item>
+        <item>@string/pref_device_action_fitness_app_control_toggle</item>
+        <item>@string/pref_device_action_dnd_off</item>
+        <item>@string/pref_device_action_dnd_priority</item>
+        <item>@string/pref_device_action_dnd_alarms</item>
+        <item>@string/pref_device_action_dnd_on</item>
+    </string-array>
+
+    <string-array name="device_action_values">
+        <item>@string/pref_media_play_value</item>
+        <item>@string/pref_media_pause_value</item>
+        <item>@string/pref_media_playpause_value</item>
+        <item>@string/pref_device_action_broadcast_value</item>
+        <item>@string/pref_device_action_fitness_app_control_start_value</item>
+        <item>@string/pref_device_action_fitness_app_control_stop_value</item>
+        <item>@string/pref_device_action_fitness_app_control_toggle_value</item>
+        <item>@string/pref_device_action_dnd_off_value</item>
+        <item>@string/pref_device_action_dnd_priority_value</item>
+        <item>@string/pref_device_action_dnd_alarms_value</item>
+        <item>@string/pref_device_action_dnd_on_value</item>
+    </string-array>
+
+    <string-array name="pref_hybridhr_buttonfunctions">
+        <item name="">@string/menuitem_nothing</item>
+        <item name="notificationsPanelApp">@string/menuitem_notifications</item>
+        <item name="weatherApp">@string/menuitem_weather</item>
+        <item name="musicApp">@string/menuitem_music</item>
+        <item name="stopwatchApp">@string/hr_appname_stopwatch</item>
+        <item name="timerApp">@string/menuitem_timer</item>
+        <item name="commuteApp">@string/hr_appname_commute</item>
+        <item name="wellnessApp">@string/hr_appname_wellness</item>
+        <item name="workoutApp">@string/hr_appname_workout</item>
+        <item name="launcherApp">@string/menuitem_menu</item>
+        <item name="settingApp">@string/menuitem_settings</item>
+        <item name="ringPhoneApp">@string/menuitem_findphone</item>
+    </string-array>
+    <string-array name="pref_hybridhr_buttonfunctions_values">
+        <item></item>
+        <item>notificationsPanelApp</item>
+        <item>weatherApp</item>
+        <item>musicApp</item>
+        <item>stopwatchApp</item>
+        <item>timerApp</item>
+        <item>commuteApp"</item>
+        <item>wellnessApp</item>
+        <item>workoutApp</item>
+        <item>launcherApp</item>
+        <item>settingApp</item>
+        <item>ringPhoneApp</item>
+    </string-array>
+
+    <string-array name="pref_hybridhr_widgetbackgrounds_items">
+        <item name="">@string/menuitem_nothing</item>
+        <item name="widget_bg_thin_circle">@string/hybridhr_widget_bg_thin_circle</item>
+        <item name="widget_bg_double_circle">@string/hybridhr_widget_bg_double_circle</item>
+        <item name="widget_bg_dashed_circle">@string/hybridhr_widget_bg_dashed_circle</item>
+    </string-array>
+    <string-array name="pref_hybridhr_widgetbackgrounds_values">
+        <item></item>
+        <item>widget_bg_thin_circle</item>
+        <item>widget_bg_double_circle</item>
+        <item>widget_bg_dashed_circle</item>
+    </string-array>
+
+    <string-array name="pref_hybridhr_watchface_events_items">
+        <item name="">@string/menuitem_nothing</item>
+        <item name="top_short_press_release">@string/pref_title_upper_button_function_short</item>
+        <item name="top_hold">@string/pref_title_upper_button_function_long</item>
+        <item name="middle_short_press_release">@string/pref_title_middle_button_function_short</item>
+        <item name="bottom_short_press_release">@string/pref_title_lower_button_function_short</item>
+        <item name="bottom_hold">@string/pref_title_lower_button_function_long</item>
+        <item name="flick_away">@string/watchface_setting_title_wrist_flick</item>
+    </string-array>
+    <string-array name="pref_hybridhr_watchface_events_values">
+        <item></item>
+        <item>top_short_press_release</item>
+        <item>top_hold</item>
+        <item>middle_short_press_release</item>
+        <item>bottom_short_press_release</item>
+        <item>bottom_hold</item>
+        <item>flick_away</item>
+    </string-array>
+
+    <string-array name="activity_filter_quick_filter_period_items">
+        <item>@string/sports_activity_quick_filter_select</item>
+        <item>@string/sports_activity_quick_filter_this_week</item>
+        <item>@string/sports_activity_quick_filter_this_month</item>
+        <item>@string/sports_activity_quick_filter_last_week</item>
+        <item>@string/sports_activity_quick_filter_last_month</item>
+        <item>@string/sports_activity_quick_filter_7days</item>
+        <item>@string/sports_activity_quick_filter_30days</item>
+    </string-array>
+    <string-array name="activity_filter_quick_filter_period_values">
+        <item>noselection</item>
+        <item>thisweek</item>
+        <item>thismonth</item>
+        <item>lastweek</item>
+        <item>lastmonth</item>
+        <item>7days</item>
+        <item>30days</item>
+    </string-array>
+
+    <string-array name="lefun_interface_language_names">
+        <item>@string/english</item>
+        <item>@string/simplified_chinese</item>
+    </string-array>
+
+    <string-array name="lefun_interface_language_values">
+        <item>0</item>
+        <item>1</item>
+    </string-array>
+    <string-array name="sonyswr12_smart_alarm_intervals">
+        <item>0 minutes</item>
+        <item>10 minutes</item>
+        <item>20 minutes</item>
+        <item>30 minutes</item>
+        <item>40 minutes</item>
+        <item>50 minutes</item>
+        <item>60 minutes</item>
+    </string-array>
+    <string-array name="sonyswr12_smart_alarm_interval_values">
+        <item>0</item>
+        <item>10</item>
+        <item>20</item>
+        <item>30</item>
+        <item>40</item>
+        <item>50</item>
+        <item>60</item>
+    </string-array>
+
+    <string-array name="pref_charts_tabs_items">
+        <item>@string/activity_sleepchart_activity_and_sleep</item>
+        <item>@string/charts_activity_list</item>
+        <item>@string/sleepchart_your_sleep</item>
+        <item>@string/weekstepschart_steps_a_week_or_month</item>
+        <item>@string/pref_header_hrv_status</item>
+        <item>@string/body_energy</item>
+        <item>@string/menuitem_vo2_max</item>
+        <item>@string/menuitem_hr</item>
+        <item>@string/menuitem_stress</item>
+        <item>@string/menuitem_pai</item>
+        <item>@string/stats_title</item>
+        <item>@string/liveactivity_live_activity</item>
+        <item>@string/pref_header_spo2</item>
+        <item>@string/menuitem_temperature</item>
+        <item>@string/menuitem_weight</item>
+        <item>@string/watchface_widget_type_calories</item>
+        <item>@string/respiratoryrate</item>
+    </string-array>
+
+    <string-array name="pref_charts_tabs_values">
+        <item>@string/p_activity</item>
+        <item>@string/p_activity_list</item>
+        <item>@string/p_sleep</item>
+        <item>@string/p_steps_week</item>
+        <item>@string/p_hrv_status</item>
+        <item>@string/p_body_energy</item>
+        <item>@string/p_vo2max</item>
+        <item>@string/p_heartrate</item>
+        <item>@string/p_stress</item>
+        <item>@string/p_pai</item>
+        <item>@string/p_speed_zones</item>
+        <item>@string/p_live_stats</item>
+        <item>@string/p_spo2</item>
+        <item>@string/p_temperature</item>
+        <item>@string/p_weight</item>
+        <item>@string/p_calories</item>
+        <item>@string/p_respiratory_rate</item>
+    </string-array>
+
+    <string-array name="pref_charts_tabs_items_default">
+        <item>@string/p_cycling</item>
+        <item>@string/p_activity</item>
+        <item>@string/p_activity_list</item>
+        <item>@string/p_sleep</item>
+        <item>@string/p_hrv_status</item>
+        <item>@string/p_body_energy</item>
+        <item>@string/p_vo2max</item>
+        <item>@string/p_heartrate</item>
+        <item>@string/p_steps_week</item>
+        <item>@string/p_stress</item>
+        <item>@string/p_pai</item>
+        <item>@string/p_speed_zones</item>
+        <item>@string/p_live_stats</item>
+        <item>@string/p_spo2</item>
+        <item>@string/p_temperature</item>
+        <item>@string/p_weight</item>
+        <item>@string/p_calories</item>
+        <item>@string/p_respiratory_rate</item>
+    </string-array>
+
+
+    <string-array name="pref_amazfitneo_sounds">
+        <item>@string/prefs_operating_sounds</item>
+        <item>@string/pref_title_notifications_call</item>
+        <item>@string/menuitem_alarm</item>
+        <item>@string/menuitem_notifications</item>
+        <item>@string/mi2_prefs_inactivity_warnings</item>
+        <item>@string/pref_title_notifications_sms</item>
+        <item>@string/menuitem_email</item>
+        <item>@string/menuitem_goal</item>
+    </string-array>
+
+    <string-array name="pref_amazfitneo_sounds_values">
+        <item>@string/p_menuitem_button</item>
+        <item>@string/p_menuitem_calls</item>
+        <item>@string/p_menuitem_alarm</item>
+        <item>@string/p_menuitem_notifications</item>
+        <item>@string/p_menuitem_inactivity_warning</item>
+        <item>@string/p_menuitem_sms</item>
+        <item>@string/p_menuitem_email</item>
+        <item>@string/p_menuitem_goal</item>
+    </string-array>
+
+    <string-array name="pref_amazfitneo_sounds_default">
+        <item>@string/p_menuitem_button</item>
+        <item>@string/p_menuitem_calls</item>
+        <item>@string/p_menuitem_alarm</item>
+        <item>@string/p_menuitem_notifications</item>
+        <item>@string/p_menuitem_inactivity_warning</item>
+        <item>@string/p_menuitem_sms</item>
+        <item>@string/p_menuitem_goal</item>
+    </string-array>
+
+    <string-array name="inactivity_minutes">
+        <item>45</item>
+        <item>60</item>
+        <item>75</item>
+        <item>90</item>
+        <item>105</item>
+        <item>120</item>
+    </string-array>
+
+    <string-array name="inactivity_minutes_values">
+        <item>3</item>
+        <item>4</item>
+        <item>5</item>
+        <item>6</item>
+        <item>7</item>
+        <item>8</item>
+    </string-array>
+
+    <string-array name="autoheartrate_interval_hours">
+        <item>1</item>
+        <item>2</item>
+        <item>3</item>
+    </string-array>
+
+    <string-array name="nothing_ear1_audio_mode_names">
+        <item>@string/prefs_active_noise_cancelling</item>
+        <item>@string/prefs_active_noise_cancelling_light</item>
+        <item>@string/prefs_active_noise_cancelling_transparency</item>
+        <item>@string/off</item>
+    </string-array>
+
+    <string-array name="nothing_ear1_audio_mode_values">
+        <item>anc</item>
+        <item>anc-light</item>
+        <item>transparency</item>
+        <item>off</item>
+    </string-array>
+
+    <string-array name="notification_list_is_blacklist_names">
+        <item>@string/pref_title_notification_use_as_deny</item>
+        <item>@string/pref_title_notification_use_as_allow</item>
+    </string-array>
+
+    <string-array name="notification_list_is_blacklist_values">
+        <item>true</item>
+        <item>false</item>
+    </string-array>
+
+    <string-array name="galaxy_eqalizer_modes">
+        <item>@string/pref_title_equalizer_bass_boost</item>
+        <item>@string/pref_title_equalizer_soft</item>
+        <item>@string/pref_title_equalizer_dynamic</item>
+        <item>@string/pref_title_equalizer_clear</item>
+        <item>@string/pref_title_equalizer_trebble</item>
+    </string-array>
+
+    <string-array name="galaxy_eqalizer_values">
+        <item>0</item>
+        <item>1</item>
+        <item>2</item>
+        <item>3</item>
+        <item>4</item>
+    </string-array>
+
+    <string-array name="galaxy_buds_live_equalizer_modes">
+        <item>@string/pref_title_equalizer_normal</item>
+        <item>@string/pref_title_equalizer_bass_boost</item>
+        <item>@string/pref_title_equalizer_soft</item>
+        <item>@string/pref_title_equalizer_dynamic</item>
+        <item>@string/pref_title_equalizer_clear</item>
+        <item>@string/pref_title_equalizer_trebble</item>
+    </string-array>
+
+    <string-array name="galaxy_buds_live_equalizer_values">
+        <item>0</item>
+        <item>1</item>
+        <item>2</item>
+        <item>3</item>
+        <item>4</item>
+        <item>5</item>
+    </string-array>
+
+    <string-array name="galaxy_pro_noise_controls_options_values">
+        <item>0</item>
+        <item>1</item>
+        <item>2</item>
+    </string-array>
+
+    <string-array name="galaxy_pro_noise_controls_options">
+        <item>@string/off</item>
+        <item>@string/prefs_active_noise_cancelling</item>
+        <item>@string/prefs_ambient_sound</item>
+    </string-array>
+
+    <string-array name="galaxy_pro_anc_level_values">
+        <item>0</item>
+        <item>1</item>
+    </string-array>
+
+    <string-array name="galaxy_pro_anc_level_options">
+        <item>@string/prefs_active_noise_cancelling_level_low</item>
+        <item>@string/prefs_active_noise_cancelling_level_high</item>
+    </string-array>
+
+
+    <string-array name="galaxy_pro_touch_options_left">
+        <item>@string/pref_switch_noise_control</item>
+        <item>@string/pref_title_touch_voice_assistant</item>
+        <item>@string/pref_media_volumedown</item>
+        <item>@string/pref_title_touch_spotify_official_app</item>
+    </string-array>
+
+    <string-array name="galaxy_pro_touch_options_right">
+        <item>@string/pref_switch_noise_control</item>
+        <item>@string/pref_title_touch_voice_assistant</item>
+        <item>@string/pref_media_volumeup</item>
+        <item>@string/pref_title_touch_spotify_official_app</item>
+    </string-array>
+
+    <string-array name="galaxy_pro_touch_options_values">
+        <item>2</item>
+        <item>1</item>
+        <item>3</item>
+        <item>4</item>
+    </string-array>
+
+    <string-array name="galaxy_pro_touch_switch_controls_options">
+        <item>@string/pref_switch_controls_anc_ambient</item>
+        <item>@string/pref_switch_controls_anc_off</item>
+        <item>@string/pref_switch_controls_ambient_off</item>
+    </string-array>
+
+    <string-array name="galaxy_pro_touch_switch_controls_values">
+        <item>0</item>
+        <item>1</item>
+        <item>2</item>
+    </string-array>
+
+    <string-array name="galaxy_pro_voice_detect_duration_options">
+        <item>@string/pref_voice_detect_duration_5</item>
+        <item>@string/pref_voice_detect_duration_10</item>
+        <item>@string/pref_voice_detect_duration_15</item>
+    </string-array>
+
+    <string-array name="galaxy_pro_voice_detect_duration_values">
+        <item>0</item>
+        <item>1</item>
+        <item>2</item>
+    </string-array>
+
+    <string-array name="galaxy_touch_options_left">
+        <item>@string/pref_title_touch_voice_assistant</item>
+        <item>@string/pref_title_touch_quick_ambient</item>
+        <item>@string/pref_media_volumedown</item>
+        <item>@string/pref_title_touch_ambient</item>
+    </string-array>
+
+    <string-array name="galaxy_touch_options_right">
+        <item>@string/pref_title_touch_voice_assistant</item>
+        <item>@string/pref_title_touch_quick_ambient</item>
+        <item>@string/pref_media_volumeup</item>
+        <item>@string/pref_title_touch_ambient</item>
+    </string-array>
+
+    <string-array name="galaxy_touch_options_values">
+        <item>0</item>
+        <item>1</item>
+        <item>2</item>
+        <item>3</item>
+    </string-array>
+
+    <string-array name="galaxy_buds_live_touch_options_left">
+        <item>@string/pref_title_touch_voice_assistant</item>
+        <item>@string/pref_title_touch_anc</item>
+        <item>@string/pref_media_volumedown</item>
+    </string-array>
+
+    <string-array name="galaxy_buds_live_touch_options_right">
+        <item>@string/pref_title_touch_voice_assistant</item>
+        <item>@string/pref_title_touch_anc</item>
+        <item>@string/pref_media_volumeup</item>
+    </string-array>
+
+    <string-array name="galaxy_buds_live_touch_options_values">
+        <item>1</item>
+        <item>2</item>
+        <item>3</item>
+    </string-array>
+
+    <string-array name="redmi_buds_5_pro_ambient_sound_control_names">
+        <item>@string/sony_ambient_sound_off</item>
+        <item>@string/sony_ambient_sound_noise_cancelling</item>
+        <item>@string/prefs_active_noise_cancelling_transparency</item>
+    </string-array>
+
+    <string-array name="redmi_buds_5_pro_ambient_sound_control_values">
+        <item>0</item>
+        <item>1</item>
+        <item>2</item>
+    </string-array>
+
+    <string-array name="redmi_buds_5_pro_noise_cancelling_strength_names">
+        <item>@string/redmi_buds_5_pro_anc_balanced</item>
+        <item>@string/redmi_buds_5_pro_anc_light</item>
+        <item>@string/redmi_buds_5_pro_anc_deep</item>
+    </string-array>
+
+    <string-array name="redmi_buds_5_pro_noise_cancelling_strength_values">
+        <item>0</item>
+        <item>1</item>
+        <item>2</item>
+    </string-array>
+
+    <string-array name="redmi_buds_5_pro_transparency_strength_names">
+        <item>@string/redmi_buds_5_pro_transparency_regular</item>
+        <item>@string/redmi_buds_5_pro_transparency_voice</item>
+        <item>@string/redmi_buds_5_pro_transparency_ambient</item>
+    </string-array>
+
+    <string-array name="redmi_buds_5_pro_transparency_strength_values">
+        <item>0</item>
+        <item>1</item>
+        <item>2</item>
+    </string-array>
+
+    <string-array name="redmi_buds_5_pro_single_button_function_names">
+        <item>@string/none</item>
+        <item>@string/pref_media_playpause</item>
+        <item>@string/pref_media_previous</item>
+        <item>@string/pref_media_next</item>
+        <item>@string/pref_media_volumeup</item>
+        <item>@string/pref_media_volumedown</item>
+    </string-array>
+
+    <string-array name="redmi_buds_5_pro_single_button_function_values">
+        <item>8</item>
+        <item>1</item>
+        <item>2</item>
+        <item>3</item>
+        <item>4</item>
+        <item>5</item>
+    </string-array>
+
+    <string-array name="redmi_buds_5_pro_button_function_names">
+        <item>@string/pref_media_playpause</item>
+        <item>@string/pref_media_previous</item>
+        <item>@string/pref_media_next</item>
+        <item>@string/pref_media_volumeup</item>
+        <item>@string/pref_media_volumedown</item>
+    </string-array>
+
+    <string-array name="redmi_buds_5_pro_button_function_values">
+        <item>1</item>
+        <item>2</item>
+        <item>3</item>
+        <item>4</item>
+        <item>5</item>
+    </string-array>
+
+    <string-array name="redmi_buds_5_pro_long_button_mode_names">
+        <item>@string/pref_title_touch_voice_assistant</item>
+        <item>@string/pref_header_sony_ambient_sound_control</item>
+    </string-array>
+
+    <string-array name="redmi_buds_5_pro_long_button_mode_values">
+        <item>0</item>
+        <item>6</item>
+    </string-array>
+
+    <string-array name="redmi_buds_5_pro_long_button_settings_names">
+        <item>@string/redmi_buds_5_pro_combo_anc_off</item>
+        <item>@string/redmi_buds_5_pro_combo_transparency_off</item>
+        <item>@string/redmi_buds_5_pro_combo_anc_transparency</item>
+        <item>@string/redmi_buds_5_pro_combo_all</item>
+    </string-array>
+
+    <string-array name="redmi_buds_5_pro_long_button_settings_values">
+        <item>3</item>
+        <item>5</item>
+        <item>6</item>
+        <item>7</item>
+    </string-array>
+
+<!--    <string-array name="redmi_buds_5_pro_surround_sound_mode_names">-->
+<!--        <item>Standard</item>-->
+<!--        <item>Music</item>-->
+<!--        <item>Video</item>-->
+<!--    </string-array>-->
+
+<!--    <string-array name="redmi_buds_5_pro_surround_sound_mode_values">-->
+<!--        <item>1</item>-->
+<!--        <item>2</item>-->
+<!--        <item>3</item>-->
+<!--    </string-array>-->
+
+    <string-array name="redmi_buds_5_pro_equalizer_presets_names">
+        <item>@string/redmi_buds_5_pro_equalizer_preset_standard</item>
+        <item>@string/redmi_buds_5_pro_equalizer_preset_treble</item>
+        <item>@string/redmi_buds_5_pro_equalizer_preset_bass</item>
+        <item>@string/redmi_buds_5_pro_equalizer_preset_voice</item>
+        <item>@string/redmi_buds_5_pro_equalizer_preset_custom</item>
+    </string-array>
+
+    <string-array name="redmi_buds_5_pro_equalizer_presets_values">
+        <item>0</item>
+        <item>6</item>
+        <item>5</item>
+        <item>1</item>
+        <item>10</item>
+    </string-array>
+
+    <string-array name="redmi_buds_5_pro_equalizer_offsets_names">
+        <item>@string/redmi_buds_5_pro_equalizer_neg6</item>
+        <item>@string/redmi_buds_5_pro_equalizer_neg5</item>
+        <item>@string/redmi_buds_5_pro_equalizer_neg4</item>
+        <item>@string/redmi_buds_5_pro_equalizer_neg3</item>
+        <item>@string/redmi_buds_5_pro_equalizer_neg2</item>
+        <item>@string/redmi_buds_5_pro_equalizer_neg1</item>
+        <item>@string/redmi_buds_5_pro_equalizer_zero</item>
+        <item>@string/redmi_buds_5_pro_equalizer_1</item>
+        <item>@string/redmi_buds_5_pro_equalizer_2</item>
+        <item>@string/redmi_buds_5_pro_equalizer_3</item>
+        <item>@string/redmi_buds_5_pro_equalizer_4</item>
+        <item>@string/redmi_buds_5_pro_equalizer_5</item>
+        <item>@string/redmi_buds_5_pro_equalizer_6</item>
+    </string-array>
+
+    <string-array name="redmi_buds_5_pro_equalizer_offsets_values">
+        <item>134</item>
+        <item>133</item>
+        <item>132</item>
+        <item>131</item>
+        <item>130</item>
+        <item>129</item>
+        <item>0</item>
+        <item>1</item>
+        <item>2</item>
+        <item>3</item>
+        <item>4</item>
+        <item>5</item>
+        <item>6</item>
+    </string-array>
+
+    <string-array name="sony_ambient_sound_control_wind_noise_reduction_names">
+        <item>@string/sony_ambient_sound_off</item>
+        <item>@string/sony_ambient_sound_noise_cancelling</item>
+        <item>@string/sony_ambient_sound_wind_noise_reduction</item>
+        <item>@string/sony_ambient_sound_ambient_sound</item>
+    </string-array>
+
+    <string-array name="sony_ambient_sound_control_wind_noise_reduction_values">
+        <item>off</item>
+        <item>noise_cancelling</item>
+        <item>wind_noise_reduction</item>
+        <item>ambient_sound</item>
+    </string-array>
+
+    <string-array name="sony_ambient_sound_control_names">
+        <item>@string/sony_ambient_sound_off</item>
+        <item>@string/sony_ambient_sound_noise_cancelling</item>
+        <item>@string/sony_ambient_sound_ambient_sound</item>
+    </string-array>
+
+    <string-array name="sony_ambient_sound_control_values">
+        <item>off</item>
+        <item>noise_cancelling</item>
+        <item>ambient_sound</item>
+    </string-array>
+
+    <string-array name="sony_protocol_version_names">
+        <item>@string/automatic</item>
+        <item>@string/sony_protocol_v1</item>
+        <item>@string/sony_protocol_v2</item>
+    </string-array>
+
+    <string-array name="sony_protocol_version_values">
+        <item>auto</item>
+        <item>v1</item>
+        <item>v2</item>
+    </string-array>
+
+    <string-array name="sony_sound_position_names">
+        <item>@string/sony_sound_position_off</item>
+        <item>@string/sony_sound_position_front</item>
+        <item>@string/sony_sound_position_front_left</item>
+        <item>@string/sony_sound_position_front_right</item>
+        <item>@string/sony_sound_position_rear_left</item>
+        <item>@string/sony_sound_position_rear_right</item>
+    </string-array>
+
+    <string-array name="sony_sound_position_values">
+        <item>off</item>
+        <item>front</item>
+        <item>front_left</item>
+        <item>front_right</item>
+        <item>rear_left</item>
+        <item>rear_right</item>
+    </string-array>
+
+    <string-array name="sony_surround_mode_names">
+        <item>@string/sony_surround_mode_off</item>
+        <item>@string/sony_surround_mode_arena</item>
+        <item>@string/sony_surround_mode_club</item>
+        <item>@string/sony_surround_mode_outdoor_stage</item>
+        <item>@string/sony_surround_mode_concert_hall</item>
+    </string-array>
+
+    <string-array name="sony_surround_mode_values">
+        <item>off</item>
+        <item>arena</item>
+        <item>club</item>
+        <item>outdoor_stage</item>
+        <item>concert_hall</item>
+    </string-array>
+
+    <string-array name="sony_equalizer_mode_names">
+        <item>@string/sony_equalizer_preset_off</item>
+        <item>@string/sony_equalizer_preset_bright</item>
+        <item>@string/sony_equalizer_preset_excited</item>
+        <item>@string/sony_equalizer_preset_mellow</item>
+        <item>@string/sony_equalizer_preset_relaxed</item>
+        <item>@string/sony_equalizer_preset_vocal</item>
+        <item>@string/sony_equalizer_preset_treble_boost</item>
+        <item>@string/sony_equalizer_preset_bass_boost</item>
+        <item>@string/sony_equalizer_preset_speech</item>
+        <item>@string/sony_equalizer_preset_manual</item>
+        <item>@string/sony_equalizer_preset_custom_1</item>
+        <item>@string/sony_equalizer_preset_custom_2</item>
+    </string-array>
+
+    <string-array name="sony_equalizer_mode_values">
+        <item>off</item>
+        <item>bright</item>
+        <item>excited</item>
+        <item>mellow</item>
+        <item>relaxed</item>
+        <item>vocal</item>
+        <item>treble_boost</item>
+        <item>bass_boost</item>
+        <item>speech</item>
+        <item>manual</item>
+        <item>custom_1</item>
+        <item>custom_2</item>
+    </string-array>
+
+    <string-array name="sony_automatic_power_off_names">
+        <item>@string/sony_automatic_power_off_off</item>
+        <item>@string/sony_automatic_power_off_5_min</item>
+        <item>@string/sony_automatic_power_off_30_min</item>
+        <item>@string/sony_automatic_power_off_1_hour</item>
+        <item>@string/sony_automatic_power_off_3_hour</item>
+    </string-array>
+
+    <string-array name="sony_automatic_power_off_values">
+        <item>off</item>
+        <item>after_5_min</item>
+        <item>after_30_min</item>
+        <item>after_1_hour</item>
+        <item>after_3_hour</item>
+    </string-array>
+
+    <string-array name="sony_automatic_power_off_when_taken_off_names">
+        <item>@string/sony_automatic_power_off_off</item>
+        <item>@string/sony_automatic_power_off_when_taken_off</item>
+    </string-array>
+
+    <string-array name="sony_automatic_power_off_when_taken_off_values">
+        <item>off</item>
+        <item>when_taken_off</item>
+    </string-array>
+
+    <string-array name="sony_speak_to_chat_voice_sensitivity_names">
+        <item>@string/sony_speak_to_chat_sensitivity_auto</item>
+        <item>@string/sony_speak_to_chat_sensitivity_high</item>
+        <item>@string/sony_speak_to_chat_sensitivity_low</item>
+    </string-array>
+
+    <string-array name="sony_speak_to_chat_voice_sensitivity_values">
+        <item>auto</item>
+        <item>high</item>
+        <item>low</item>
+    </string-array>
+
+    <string-array name="sony_speak_to_chat_voice_timeout_names">
+        <item>@string/sony_speak_to_chat_timeout_off</item>
+        <item>@string/sony_speak_to_chat_timeout_short</item>
+        <item>@string/sony_speak_to_chat_timeout_standard</item>
+        <item>@string/sony_speak_to_chat_timeout_long</item>
+    </string-array>
+
+    <string-array name="sony_speak_to_chat_voice_timeout_values">
+        <item>off</item>
+        <item>short</item>
+        <item>standard</item>
+        <item>long</item>
+    </string-array>
+
+    <string-array name="sony_wfsp800n_button_mode_names">
+        <item>@string/sony_button_mode_off</item>
+        <item>@string/sony_button_mode_ambient_sound_control</item>
+        <item>@string/sony_button_mode_playback_control</item>
+        <item>@string/sony_button_mode_volume_control</item>
+    </string-array>
+
+    <string-array name="sony_wfsp800n_button_mode_values">
+        <item>off</item>
+        <item>ambient_sound_control</item>
+        <item>playback_control</item>
+        <item>volume_control</item>
+    </string-array>
+
+    <string-array name="sony_quick_access_names">
+        <item>@string/off</item>
+        <item>@string/pref_title_touch_spotify</item>
+    </string-array>
+
+    <string-array name="sony_quick_access_values">
+        <item>off</item>
+        <item>spotify</item>
+    </string-array>
+
+    <string-array name="sony_ambient_sound_control_button_mode_names">
+        <item>@string/sony_ambient_sound_control_button_mode_nc_as_off</item>
+        <item>@string/sony_ambient_sound_control_button_mode_nc_as</item>
+        <item>@string/sony_ambient_sound_control_button_mode_nc_off</item>
+        <item>@string/sony_ambient_sound_control_button_mode_as_off</item>
+    </string-array>
+
+    <string-array name="sony_ambient_sound_control_button_mode_values">
+        <item>nc_as_off</item>
+        <item>nc_as</item>
+        <item>nc_off</item>
+        <item>as_off</item>
+    </string-array>
+
+    <string-array name="oppo_touch_tap_names">
+        <!-- superset of values, unsupported are hidden by the coordinator -->
+        <item>@string/sony_button_mode_off</item>
+        <item>@string/moondrop_touch_action_play_pause</item>
+        <item>@string/pref_media_previous</item>
+        <item>@string/pref_media_next</item>
+        <item>@string/pref_media_volumeup</item>
+        <item>@string/pref_media_volumedown</item>
+        <item>@string/pref_title_touch_voice_assistant</item>
+        <item>@string/pref_title_touch_voice_assistant</item>
+        <item>@string/prefs_game_mode</item>
+    </string-array>
+
+    <string-array name="oppo_touch_tap_values">
+        <!-- superset of values, unsupported are hidden by the coordinator -->
+        <item>off</item>
+        <item>play_pause</item>
+        <item>previous</item>
+        <item>next</item>
+        <item>volume_up</item>
+        <item>volume_down</item>
+        <item>voice_assistant</item>
+        <item>voice_assistant_realme</item>
+        <item>game_mode</item>
+    </string-array>
+
+    <string-array name="soundcore_button_function_names">
+        <item>@string/pref_media_volumedown</item>
+        <item>@string/pref_media_volumeup</item>
+        <item>@string/pref_media_next</item>
+        <item>@string/pref_media_previous</item>
+        <item>@string/pref_media_playpause</item>
+        <item>@string/pref_title_touch_voice_assistant</item>
+        <item>@string/sony_button_mode_ambient_sound_control</item>
+    </string-array>
+
+    <string-array name="soundcore_button_function_values">
+        <item>VOLUME_DOWN</item>
+        <item>VOLUME_UP</item>
+        <item>MEDIA_NEXT</item>
+        <item>MEDIA_PREV</item>
+        <item>PLAYPAUSE</item>
+        <item>VOICE_ASSISTANT</item>
+        <item>AMBIENT_SOUND_CONTROL</item>
+    </string-array>
+
+    <string-array name="soundcore_button_brightness_names">
+        <item>@string/off</item>
+        <item>@string/soundcore_button_brightness_low</item>
+        <item>@string/soundcore_button_brightness_medium</item>
+        <item>@string/soundcore_button_brightness_high</item>
+    </string-array>
+
+    <string-array name="soundcore_button_brightness_values">
+        <item>0</item>
+        <item>20</item>
+        <item>70</item>
+        <item>100</item>
+    </string-array>
+
+    <string-array name="soundcore_auto_power_off_names">
+        <item>@string/pref_button_action_disabled</item>
+        <item>@string/minutes_5</item>
+        <item>@string/minutes_10</item>
+        <item>@string/minutes_20</item>
+        <item>@string/minutes_60</item>
+    </string-array>
+
+    <string-array name="soundcore_auto_power_off_values">
+        <item>0</item>
+        <item>1</item>
+        <item>2</item>
+        <item>3</item>
+        <item>4</item>
+    </string-array>
+
+    <string-array name="soundcore_equalizer_preset_names">
+        <item>@string/soundcore_equalizer_preset_signature</item>
+        <item>@string/soundcore_equalizer_preset_xtra_bass</item>
+        <item>@string/soundcore_equalizer_preset_voice</item>
+        <item>@string/soundcore_equalizer_preset_balanced</item>
+        <item>@string/custom</item>
+    </string-array>
+
+    <string-array name="soundcore_equalizer_preset_values">
+        <item>0</item>
+        <item>1</item>
+        <item>2</item>
+        <item>3</item>
+        <item>254</item>
+    </string-array>
+
+    <string-array name="soundcore_equalizer_direction_names">
+        <item>@string/soundcore_equalizer_direction_standing</item>
+        <item>@string/soundcore_equalizer_direction_lying</item>
+        <item>@string/soundcore_equalizer_direction_hanging</item>
+    </string-array>
+
+    <string-array name="soundcore_equalizer_direction_values">
+        <item>0</item>
+        <item>1</item>
+        <item>2</item>
+    </string-array>
+
+    <string-array name="soundcore_equalizer_band1_freq_names">
+        <item>48 Hz</item>
+        <item>51 Hz</item>
+        <item>55 Hz</item>
+        <item>59 Hz</item>
+        <item>63 Hz</item>
+        <item>68 Hz</item>
+        <item>72 Hz</item>
+        <item>80 Hz</item>
+        <item>83 Hz</item>
+        <item>89 Hz</item>
+    </string-array>
+
+    <string-array name="soundcore_equalizer_band2_freq_names">
+        <item>95 Hz</item>
+        <item>102 Hz</item>
+        <item>109 Hz</item>
+        <item>117 Hz</item>
+        <item>125 Hz</item>
+        <item>134 Hz</item>
+        <item>144 Hz</item>
+        <item>150 Hz</item>
+        <item>165 Hz</item>
+        <item>177 Hz</item>
+    </string-array>
+
+    <string-array name="soundcore_equalizer_band3_freq_names">
+        <item>190 Hz</item>
+        <item>203 Hz</item>
+        <item>218 Hz</item>
+        <item>233 Hz</item>
+        <item>250 Hz</item>
+        <item>268 Hz</item>
+        <item>287 Hz</item>
+        <item>300 Hz</item>
+        <item>330 Hz</item>
+        <item>353 Hz</item>
+    </string-array>
+
+    <string-array name="soundcore_equalizer_band4_freq_names">
+        <item>379 Hz</item>
+        <item>406 Hz</item>
+        <item>435 Hz</item>
+        <item>467 Hz</item>
+        <item>500 Hz</item>
+        <item>536 Hz</item>
+        <item>574 Hz</item>
+        <item>600 Hz</item>
+        <item>659 Hz</item>
+        <item>706 Hz</item>
+    </string-array>
+
+    <string-array name="soundcore_equalizer_band5_freq_names">
+        <item>759 Hz</item>
+        <item>813 Hz</item>
+        <item>871 Hz</item>
+        <item>933 Hz</item>
+        <item>1.0 kHz</item>
+        <item>1.07 kHz</item>
+        <item>1.1 kHz</item>
+        <item>1.2 kHz</item>
+        <item>1.3 kHz</item>
+        <item>1.4 kHz</item>
+    </string-array>
+
+    <string-array name="soundcore_equalizer_band6_freq_names">
+        <item>1.5 kHz</item>
+        <item>1.6 kHz</item>
+        <item>1.75 kHz</item>
+        <item>1.86 kHz</item>
+        <item>2.0 kHz</item>
+        <item>2.15 kHz</item>
+        <item>2.3 kHz</item>
+        <item>2.5 kHz</item>
+        <item>2.6 kHz</item>
+        <item>2.8 kHz</item>
+    </string-array>
+
+    <string-array name="soundcore_equalizer_band7_freq_names">
+        <item>3.0 kHz</item>
+        <item>3.2 kHz</item>
+        <item>3.5 kHz</item>
+        <item>3.7 kHz</item>
+        <item>4.0 kHz</item>
+        <item>4.3 kHz</item>
+        <item>4.6 kHz</item>
+        <item>5.0 kHz</item>
+        <item>5.3 kHz</item>
+        <item>5.6 kHz</item>
+    </string-array>
+
+    <string-array name="soundcore_equalizer_band8_freq_names">
+        <item>6.0 kHz</item>
+        <item>6.5 kHz</item>
+        <item>7.0 kHz</item>
+        <item>7.5 kHz</item>
+        <item>8.0 kHz</item>
+        <item>8.5 kHz</item>
+        <item>9.0 kHz</item>
+        <item>9.8 kHz</item>
+        <item>10.5 kHz</item>
+        <item>11.3 kHz</item>
+    </string-array>
+
+    <string-array name="soundcore_equalizer_band9_freq_names">
+        <item>12.1 kHz</item>
+        <item>13.0 kHz</item>
+        <item>14.0 kHz</item>
+        <item>15.0 kHz</item>
+        <item>16.0 kHz</item>
+        <item>17.1 kHz</item>
+        <item>18.3 kHz</item>
+        <item>20.0 kHz</item>
+    </string-array>
+
+    <string-array name="soundcore_equalizer_freq_values">
+        <item>0</item>
+        <item>1</item>
+        <item>2</item>
+        <item>3</item>
+        <item>4</item>
+        <item>5</item>
+        <item>6</item>
+        <item>7</item>
+        <item>8</item>
+        <item>9</item>
+    </string-array>
+
+    <string-array name="moondrop_equalizer_preset_names">
+        <item>@string/moondrop_equalizer_preset_reference</item>
+        <item>@string/moondrop_equalizer_preset_basshead</item>
+        <item>@string/moondrop_equalizer_preset_monitor</item>
+    </string-array>
+
+    <string-array name="moondrop_equalizer_preset_values">
+        <item>0</item>
+        <item>1</item>
+        <item>2</item>
+    </string-array>
+
+    <string-array name="moondrop_touch_earbud_names">
+        <item>@string/left</item>
+        <item>@string/right</item>
+        <item>@string/moondrop_touch_earbud_both</item>
+    </string-array>
+
+    <string-array name="moondrop_touch_earbud_values">
+        <item>1</item>
+        <item>2</item>
+        <item>3</item>
+    </string-array>
+
+    <string-array name="moondrop_touch_trigger_names">
+        <item>@string/pref_button_action_disabled</item>
+        <item>@string/single_tap</item>
+        <item>@string/double_tap</item>
+        <item>@string/triple_tap</item>
+        <item>@string/moondrop_touch_trigger_long_press_1s</item>
+        <item>@string/moondrop_touch_trigger_long_press_3s</item>
+    </string-array>
+
+    <string-array name="moondrop_touch_trigger_values">
+        <item>0</item>
+        <item>1</item>
+        <item>2</item>
+        <item>3</item>
+        <item>4</item>
+        <item>5</item>
+    </string-array>
+
+    <string-array name="miscale_weight_unit_names">
+        <item>@string/miscale_weight_unit_metric</item>
+        <item>@string/miscale_weight_unit_imperial</item>
+        <item>@string/miscale_weight_unit_chinese</item>
+    </string-array>
+
+    <string-array name="miscale_weight_unit_values">
+        <item>0</item>
+        <item>1</item>
+        <item>2</item>
+    </string-array>
+
+    <string-array name="fitness_tracking_apps_package_names">
+        <item>de.dennisguse.opentracks</item>
+        <item>de.dennisguse.opentracks.playStore</item>
+        <item>de.dennisguse.opentracks.debug</item>
+        <item>de.dennisguse.opentracks.nightly</item>
+    </string-array>
+
+    <string-array name="osmand_package_names">
+        <item>@string/automatic</item>
+        <item>net.osmand.plus</item>
+        <item>net.osmand</item>
+        <item>net.osmand.dev</item>
+    </string-array>
+    <string-array name="osmand_package_names_values">
+        <item>autodetect</item>
+        <item>net.osmand.plus</item>
+        <item>net.osmand</item>
+        <item>net.osmand.dev</item>
+    </string-array>
+
+    <string-array name="workmode">
+        <item>@string/automatic</item>
+        <item>@string/manual</item>
+    </string-array>
+
+    <string-array name="workmode_values">
+        <item>auto</item>
+        <item>manual</item>
+    </string-array>
+
+    <string-array name="pref_transliteration_languages">
+        <item>@string/arabic</item>
+        <item>@string/bengali</item>
+        <item>@string/common_symbols</item>
+        <item>@string/croatian</item>
+        <item>@string/czech</item>
+        <item>@string/estonian</item>
+        <item>@string/extended_ascii</item>
+        <item>@string/french</item>
+        <item>@string/georgian</item>
+        <item>@string/german</item>
+        <item>@string/greek</item>
+        <item>@string/hebrew</item>
+        <item>@string/icelandic</item>
+        <item>@string/korean</item>
+        <item>@string/latvian</item>
+        <item>@string/lithuanian</item>
+        <item>@string/persian</item>
+        <item>@string/polish</item>
+        <item>@string/russian</item>
+        <item>@string/scandinavian</item>
+        <item>@string/serbian</item>
+        <item>@string/turkish</item>
+        <item>@string/ukranian</item>
+        <item>@string/hungarian</item>
+        <item>@string/armenian</item>
+    </string-array>
+
+    <string-array name="pref_transliteration_languages_values">
+        <item>arabic</item>
+        <item>bengali</item>
+        <item>common_symbols</item>
+        <item>croatian</item>
+        <item>czech</item>
+        <item>estonian</item>
+        <item>extended_ascii</item>
+        <item>french</item>
+        <item>georgian</item>
+        <item>german</item>
+        <item>greek</item>
+        <item>hebrew</item>
+        <item>icelandic</item>
+        <item>korean</item>
+        <item>latvian</item>
+        <item>lithuanian</item>
+        <item>persian</item>
+        <item>polish</item>
+        <item>russian</item>
+        <item>scandinavian</item>
+        <item>serbian</item>
+        <item>turkish</item>
+        <item>ukranian</item>
+        <item>hungarian</item>
+        <item>armenian</item>
+    </string-array>
+
+    <string-array name="pref_transliteration_languages_default">
+        <!-- transliteration disabled by default -->
+    </string-array>
+
+    <string-array name="prefs_wena3_vibration_strength_names_list">
+        <item>@string/prefs_wena3_vibration_strength_item_weak</item>
+        <item>@string/prefs_wena3_vibration_strength_item_medium</item>
+        <item>@string/prefs_wena3_vibration_strength_item_strong</item>
+    </string-array>
+    <string-array name="prefs_wena3_vibration_strength_values_list">
+        <item>weak</item>
+        <item>normal</item>
+        <item>strong</item>
+    </string-array>
+
+    <string-array name="prefs_wena3_home_icons_names_list">
+        <item>@string/menuitem_timer</item>
+        <item>@string/menuitem_alarm</item>
+        <item>@string/prefs_wena3_home_icon_name_clock</item>
+        <!-- <item>@string/menuitem_alexa</item> -->
+        <!-- <item>@string/prefs_wena3_home_icon_name_wena_pay</item> -->
+        <!-- <item>@string/prefs_wena3_home_icon_name_qrio</item> -->
+        <item>@string/prefs_wena3_home_icon_name_edy</item>
+        <item>@string/menuitem_notifications</item>
+        <item>@string/menuitem_events</item>
+        <item>@string/prefs_wena3_home_icon_name_pedometer</item>
+        <item>@string/menuitem_sleep</item>
+        <item>@string/menuitem_hr</item>
+        <item>@string/menuitem_vo2_max</item>
+        <item>@string/menuitem_stress</item>
+        <item>@string/prefs_wena3_home_icon_name_energy</item>
+        <item>@string/prefs_wena3_home_icon_name_suica</item>
+        <item>@string/prefs_wena3_home_icon_name_calories</item>
+        <!-- <item>@string/prefs_wena3_home_icon_name_riiiver</item> -->
+        <item>@string/menuitem_music</item>
+        <item>@string/menuitem_takephoto</item>
+    </string-array>
+    <string-array name="prefs_wena3_home_icons_values_list">
+        <item>timer</item>
+        <item>alarm</item>
+        <item>clock</item>
+        <!-- <item>alexa</item> -->
+        <!-- <item>wena_pay</item> -->
+        <!-- <item>qrio_lock</item> -->
+        <item>edy</item>
+        <item>notification_count</item>
+        <item>schedule</item>
+        <item>pedometer</item>
+        <item>sleep</item>
+        <item>heart_rate</item>
+        <item>vo2max</item>
+        <item>stress</item>
+        <item>energy</item>
+        <item>suica</item>
+        <item>calories</item>
+        <!-- <item>riiiver</item> -->
+        <item>music</item>
+        <item>camera</item>
+    </string-array>
+
+    <string-array name="prefs_wena3_menu_icons_names_list">
+        <item>@string/menuitem_timer</item>
+        <item>@string/menuitem_alarm</item>
+        <item>@string/menuitem_findphone</item>
+        <!-- <item>@string/menuitem_alexa</item> -->
+        <item>@string/prefs_wena3_menu_icon_name_payment</item>
+        <!-- <item>@string/prefs_wena3_home_icon_name_qrio</item> -->
+        <item>@string/menuitem_weather</item>
+        <item>@string/menuitem_music</item>
+        <item>@string/menuitem_takephoto</item>
+    </string-array>
+    <string-array name="prefs_wena3_menu_icons_values_list">
+        <item>timer</item>
+        <item>alarm</item>
+        <item>find_phone</item>
+        <!-- <item>alexa</item> -->
+        <item>payment</item>
+        <!-- <item>qrio</item> -->
+        <item>weather</item>
+        <item>music</item>
+        <item>camera</item>
+    </string-array>
+    <string-array name="prefs_wena3_menu_icons_default_list">
+        <item>alarm</item>
+        <item>find_phone</item>
+        <item>weather</item>
+        <item>camera</item>
+        <item>music</item>
+        <item>payment</item>
+    </string-array>
+
+    <string-array name="prefs_wena3_status_pages_list">
+        <item>@string/prefs_wena3_home_icon_name_pedometer</item>
+        <item>@string/menuitem_sleep</item>
+        <item>@string/menuitem_hr</item>
+        <item>@string/menuitem_vo2_max</item>
+        <item>@string/menuitem_stress</item>
+        <item>@string/prefs_wena3_home_icon_name_energy</item>
+        <item>@string/prefs_wena3_home_icon_name_calories</item>
+    </string-array>
+    <string-array name="prefs_wena3_status_page_values_list">
+        <item>pedometer</item>
+        <item>sleep</item>
+        <item>heart_rate</item>
+        <item>vo2max</item>
+        <item>stress</item>
+        <item>energy</item>
+        <item>calories</item>
+    </string-array>
+    <string-array name="prefs_wena3_status_page_default_list">
+        <item>pedometer</item>
+        <item>sleep</item>
+        <item>heart_rate</item>
+        <item>vo2max</item>
+        <item>stress</item>
+        <item>energy</item>
+        <item>calories</item>
+    </string-array>
+
+    <string-array name="prefs_wena3_day_start_list">
+        <item>00:00</item>
+        <item>01:00</item>
+        <item>02:00</item>
+        <item>03:00</item>
+        <item>04:00</item>
+        <item>05:00</item>
+        <item>06:00</item>
+        <item>07:00</item>
+        <item>08:00</item>
+        <item>09:00</item>
+        <item>10:00</item>
+        <item>11:00</item>
+        <item>12:00</item>
+        <item>13:00</item>
+        <item>14:00</item>
+        <item>15:00</item>
+        <item>16:00</item>
+        <item>17:00</item>
+        <item>18:00</item>
+        <item>19:00</item>
+        <item>20:00</item>
+        <item>21:00</item>
+        <item>22:00</item>
+        <item>23:00</item>
+    </string-array>
+    <string-array name="prefs_wena3_day_start_values_list">
+        <item>0</item>
+        <item>1</item>
+        <item>2</item>
+        <item>3</item>
+        <item>4</item>
+        <item>5</item>
+        <item>6</item>
+        <item>7</item>
+        <item>8</item>
+        <item>9</item>
+        <item>10</item>
+        <item>11</item>
+        <item>12</item>
+        <item>13</item>
+        <item>14</item>
+        <item>15</item>
+        <item>16</item>
+        <item>17</item>
+        <item>18</item>
+        <item>19</item>
+        <item>20</item>
+        <item>21</item>
+        <item>22</item>
+        <item>23</item>
+    </string-array>
+
+    <string-array name="prefs_wena3_button_action_list">
+        <item>@string/menuitem_nothing</item>
+        <!-- <item>@string/menuitem_alexa</item> -->
+        <!-- <item>@string/prefs_wena3_home_icon_name_qrio</item> -->
+        <!-- <item>@string/prefs_wena3_button_action_name_qrio_unlock</item> -->
+        <!-- <item>@string/prefs_wena3_button_action_name_qrio_lock</item> -->
+        <item>@string/menuitem_findphone</item>
+        <item>@string/menuitem_nfc</item>
+        <item>@string/menuitem_dnd</item>
+        <item>@string/prefs_wena3_button_action_name_start_timer</item>
+        <item>@string/menuitem_timer</item>
+        <item>@string/menuitem_weather</item>
+        <item>@string/menuitem_events</item>
+        <item>@string/prefs_wena3_button_action_name_activity_screen</item>
+        <item>@string/menuitem_notifications</item>
+        <item>@string/prefs_wena3_home_icon_name_suica</item>
+        <item>@string/prefs_wena3_home_icon_name_edy</item>
+        <item>@string/menuitem_alarm</item>
+        <!-- <item>@string/prefs_wena3_home_icon_name_riiiver</item> -->
+        <item>@string/menuitem_music</item>
+        <item>@string/pref_media_playpause</item>
+        <item>@string/pref_media_next</item>
+        <item>@string/pref_media_previous</item>
+        <!-- <item>@string/prefs_wena3_home_icon_name_wena_pay</item> -->
+    </string-array>
+    <string-array name="prefs_wena3_button_action_values_list">
+        <item>none</item>
+        <!-- <item>alexa</item> -->
+        <!-- <item>qrio</item> -->
+        <!-- <item>qrio_unlock</item> -->
+        <!-- <item>qrio_lock</item> -->
+        <item>find_phone</item>
+        <item>toggle_nfc</item>
+        <item>toggle_silent</item>
+        <item>start_timer</item>
+        <item>timer</item>
+        <item>weather</item>
+        <item>schedule</item>
+        <item>activity_screen</item>
+        <item>notification_screen</item>
+        <item>suica</item>
+        <item>edy</item>
+        <item>alarm</item>
+        <!-- <item>riiiver</item> -->
+        <item>music</item>
+        <item>toggle_music</item>
+        <item>music_next</item>
+        <item>music_prev</item>
+        <!-- <item>wena_pay</item> -->
+    </string-array>
+
+    <string-array name="prefs_wena3_vibration_times">
+        <item>@string/prefs_wena3_notification_vibration_repetition_0</item>
+        <item>@string/prefs_wena3_notification_vibration_repetition_1</item>
+        <item>@string/prefs_wena3_notification_vibration_repetition_2</item>
+        <item>@string/prefs_wena3_notification_vibration_repetition_3</item>
+        <item>@string/prefs_wena3_notification_vibration_repetition_4</item>
+    </string-array>
+    <string-array name="prefs_wena3_vibration_times_values">
+        <item>0</item>
+        <item>1</item>
+        <item>2</item>
+        <item>3</item>
+        <item>4</item>
+    </string-array>
+
+    <string-array name="prefs_wena3_vibration_patterns">
+        <item>@string/prefs_wena3_vibration_none</item>
+        <item>@string/prefs_wena3_vibration_continuous</item>
+        <item>@string/prefs_wena3_vibration_basic</item>
+        <item>@string/prefs_wena3_vibration_rapid</item>
+        <item>@string/prefs_wena3_vibration_triple</item>
+        <item>@string/prefs_wena3_vibration_step_up</item>
+        <item>@string/prefs_wena3_vibration_step_down</item>
+        <item>@string/prefs_wena3_vibration_warning</item>
+        <item>@string/prefs_wena3_vibration_siren</item>
+        <item>@string/prefs_wena3_vibration_short</item>
+    </string-array>
+    <string-array name="prefs_wena3_vibration_pattern_values">
+        <item>none</item>
+        <item>continuous</item>
+        <item>basic</item>
+        <item>rapid</item>
+        <item>triple</item>
+        <item>step_up</item>
+        <item>step_down</item>
+        <item>warning</item>
+        <item>siren</item>
+        <item>short</item>
+    </string-array>
+
+    <string-array name="prefs_wena3_led_patterns">
+        <item>@string/prefs_wena3_led_none</item>
+        <item>@string/red</item>
+        <item>@string/yellow</item>
+        <item>@string/green</item>
+        <item>@string/cyan</item>
+        <item>@string/blue</item>
+        <item>@string/purple</item>
+        <item>@string/white</item>
+    </string-array>
+    <string-array name="prefs_wena3_led_pattern_values">
+        <item>none</item>
+        <item>red</item>
+        <item>yellow</item>
+        <item>green</item>
+        <item>cyan</item>
+        <item>blue</item>
+        <item>purple</item>
+        <item>white</item>
+    </string-array>
+
+    <string-array name="temperature_scales_cf">
+        <item>@string/temperature_scale_celsius</item>
+        <item>@string/temperature_scale_fahrenheit</item>
+    </string-array>
+    <string-array name="temperature_scales_cf_values">
+        <item>c</item>
+        <item>f</item>
+    </string-array>
+
+    <string-array name="femometer_measurement_mode">
+        <item>@string/femometer_measurement_mode_quick</item>
+        <item>@string/femometer_measurement_mode_normal</item>
+        <item>@string/femometer_measurement_mode_precise</item>
+    </string-array>
+    <string-array name="femometer_measurement_mode_values">
+        <item>quick</item>
+        <item>normal</item>
+        <item>precise</item>
+    </string-array>
+    <string-array name="call_rejection_methods">
+        <item>@string/call_rejection_method_reject</item>
+        <item>@string/call_rejection_method_ignore</item>
+    </string-array>
+    <string-array name="call_rejection_methods_values">
+        <item>reject</item>
+        <item>ignore</item>
+    </string-array>
+
+    <string-array name="pref_force_connection_type">
+        <item>@string/pref_force_connection_type_auto</item>
+        <item>@string/pref_force_connection_type_ble</item>
+        <item>@string/pref_force_connection_type_bt_classic</item>
+    </string-array>
+    <string-array name="pref_force_connection_type_values">
+        <item>@string/pref_force_connection_type_auto_value</item>
+        <item>@string/pref_force_connection_type_ble_value</item>
+        <item>@string/pref_force_connection_type_bt_classic_value</item>
+    </string-array>
+
+    <string-array name="pref_dashboard_widgets_order">
+        <item>@string/pref_dashboard_widget_today_title</item>
+        <item>@string/pref_dashboard_widget_goals_chart_title</item>
+        <item>@string/steps</item>
+        <item>@string/distance</item>
+        <item>@string/active_time</item>
+        <item>@string/menuitem_sleep</item>
+        <item>@string/sleep_score</item>
+        <item>@string/body_energy</item>
+        <item>@string/menuitem_stress_simple</item>
+        <item>@string/menuitem_stress_segmented</item>
+        <item>@string/menuitem_stress_breakdown</item>
+        <item>@string/hrv</item>
+        <item>@string/menuitem_vo2_max</item>
+        <item>@string/vo2max_running</item>
+        <item>@string/vo2max_cycling</item>
+        <item>@string/menuitem_calories_active_goal</item>
+        <item>@string/menuitem_calories_segmented</item>
+    </string-array>
+
+    <string-array name="pref_dashboard_widgets_order_values">
+        <item>today</item>
+        <item>goals</item>
+        <item>steps</item>
+        <item>distance</item>
+        <item>activetime</item>
+        <item>sleep</item>
+        <item>sleepscore</item>
+        <item>bodyenergy</item>
+        <item>stress_simple</item>
+        <item>stress_segmented</item>
+        <item>stress_breakdown</item>
+        <item>hrv</item>
+        <item>vo2max</item>
+        <item>vo2max_running</item>
+        <item>vo2max_cycling</item>
+        <item>calories_active</item>
+        <item>calories_segmented</item>
+    </string-array>
+
+    <string-array name="pref_dashboard_widgets_order_default">
+        <item>today</item>
+        <item>goals</item>
+        <item>steps</item>
+        <item>distance</item>
+        <item>activetime</item>
+        <item>sleep</item>
+        <item>bodyenergy</item>
+        <item>stress_segmented</item>
+        <item>hrv</item>
+        <item>vo2max</item>
+        <item>calories_active</item>
+        <item>calories_segmented</item>
+    </string-array>
+
+    <string-array name="huawei_freebuds_audio_mode_names">
+        <item>@string/prefs_active_noise_cancelling</item>
+        <item>@string/prefs_active_noise_cancelling_transparency</item>
+        <item>@string/off</item>
+    </string-array>
+
+    <string-array name="huawei_freebuds_audio_mode_values">
+        <item>anc</item>
+        <item>transparency</item>
+        <item>off</item>
+    </string-array>
+</resources>
diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml
new file mode 100644
index 0000000..4b2f671
--- /dev/null
+++ b/app/src/main/res/values/attrs.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <attr name="textColorPrimary" format="color" />
+    <attr name="textColorSecondary" format="color" />
+    <attr name="textColorTertiary" format="color" />
+
+    <attr name="drawer_header_color" format="color" />
+    <attr name="drawer_title_color" format="color" />
+    <attr name="cardview_background_color" format="color" />
+    <attr name="actionmenu_icon_color" format="color" />
+
+    <attr name="chart_deep_sleep" format="color" />
+    <attr name="chart_light_sleep" format="color" />
+    <attr name="chart_rem_sleep" format="color" />
+    <attr name="chart_awake_sleep" format="color" />
+    <attr name="chart_activity" format="color" />
+    <attr name="chart_not_worn" format="color" />
+    <attr name="alternate_row_background" format="color" />
+    <attr name="sports_activity_summary_background" format="color" />
+
+    <attr name="deviceIconPrimary" format="color" />
+    <attr name="deviceIconOnPrimary" format="color" />
+    <attr name="deviceIconLight" format="color" />
+    <attr name="deviceIconDark" format="color" />
+</resources>
\ No newline at end of file
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000..9d63081
--- /dev/null
+++ b/app/src/main/res/values/colors.xml
@@ -0,0 +1,90 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <color name="primary_light" type="color">#ff3d00</color>
+    <color name="primarydark_light" type="color">#dd2c00</color>
+
+    <color name="primary_dark" type="color">#ff3d00</color>
+    <color name="primarydark_dark" type="color">#dd2c00</color>
+
+    <color name="accent" type="color">#0091ea</color>
+
+    <color name="primarytext_light" type="color">#000000</color>
+    <color name="primarytext_dark" type="color">#ffffff</color>
+
+    <color name="secondarytext" type="color">#ff808080</color>
+    <color name="tertiarytext_light" type="color">#ffd0d0d0</color>
+    <color name="tertiarytext_dark" type="color">#ff606060</color>
+    <color name="tertiarytext_black" type="color">#000000</color>
+
+    <color name="divider" type="color">#1f000000</color>
+
+    <color name="chart_heartrate" type="color">#ffab40</color>
+    <color name="chart_heartrate_alternative" type="color">#8B0000</color>
+    <color name="chart_heartrate_fill" type="color">#fadab1</color>
+
+    <color name="chart_deep_sleep_light" type="color">#0054a3</color>
+    <color name="chart_deep_sleep_dark" type="color">#0054a3</color>
+
+    <color name="chart_light_sleep_light" type="color">#079ef3</color>
+    <color name="chart_light_sleep_dark" type="color">#079ef3</color>
+
+    <color name="chart_rem_sleep_light" type="color">#e427c7</color>
+    <color name="chart_rem_sleep_dark" type="color">#e427c7</color>
+
+    <color name="chart_awake_sleep_light" type="color">#ff866e</color>
+    <color name="chart_awake_sleep_dark" type="color">#ff866e</color>
+
+    <color name="chart_activity_light" type="color">#60bd6d</color>
+    <color name="chart_activity_dark" type="color">#59b22c</color>
+
+    <color name="chart_pai_weekly" type="color">#007BFF</color>
+    <color name="chart_pai_today" type="color">#2ECC71</color>
+
+    <color name="chart_not_worn_light" type="color">#545254</color>
+    <color name="chart_not_worn_dark" type="color">#d8d9d8</color>
+
+    <color name="chart_stress_unknown" type="color">#545254</color>
+    <color name="chart_stress_relaxed" type="color">#46acea</color>
+    <color name="chart_stress_mild" type="color">#59b22c</color>
+    <color name="chart_stress_moderate" type="color">#ffab40</color>
+    <color name="chart_stress_high" type="color">#8B0000</color>
+
+    <color name="hrv_status_balanced" type="color">#0c8a03</color>
+    <color name="hrv_status_unbalanced" type="color">#fc9d03</color>
+    <color name="hrv_status_low" type="color">#fc5203</color>
+    <color name="hrv_status_poor" type="color">#be03fc</color>
+    <color name="hrv_status_char_line_color" type="color">#d12a2a</color>
+    <color name="vo2max_running_char_line_color" type="color">#46acea</color>
+    <color name="vo2max_cycling_char_line_color" type="color">#59b22c</color>
+    <color name="vo2max_value_poor_color" type="color">#d93832</color>
+    <color name="vo2max_value_fair_color" type="color">#ffa703</color>
+    <color name="vo2max_value_good_color" type="color">#04c79c</color>
+    <color name="vo2max_value_excellent_color" type="color">#02a8e6</color>
+    <color name="vo2max_value_superior_color" type="color">#824be3</color>
+    <color name="body_energy_level_color" type="color">#5ac234</color>
+    <color name="body_energy_lost_color" type="color">#ff6c43</color>
+    <color name="steps_color" type="color">#00c9bf</color>
+    <color name="calories_color" type="color">#fa4502</color>
+    <color name="calories_resting_color" type="color">#1f49f2</color>
+    <color name="respiratory_rate_color" type="color">#163ede</color>
+
+    <color name="value_line_color" type="color">#858585</color>
+    <color name="row_separator_light" type="color">#ffe2e2e5</color>
+    <color name="row_separator_dark" type="color">#19808080</color>
+    <color name="gauge_line_color" type="color">#19808080</color>
+
+    <color name="hr_zone_warm_up_color" type="color">#92a4ad</color>
+    <color name="hr_zone_easy_color" type="color">#80D8FF</color>
+    <color name="hr_zone_aerobic_color" type="color">#66BB6A</color>
+    <color name="hr_zone_threshold_color" type="color">#FF7043</color>
+    <color name="hr_zone_maximum_color" type="color">#F44336</color>
+
+    <attr name="row_separator" format="color" />
+    <color name="alternate_row_background_light">#FFEDEDED</color>
+    <color name="alternate_row_background_dark">#545254</color>
+
+    <color name="transparent">#00000000</color>
+
+    <drawable name="selected">@color/accent</drawable>
+
+</resources>
\ No newline at end of file
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..ed6845a
--- /dev/null
+++ b/app/src/main/res/values/dimens.xml
@@ -0,0 +1,19 @@
+<resources>
+    <!-- Default screen margins, per the Android Design guidelines. -->
+    <dimen name="activity_horizontal_margin">16dp</dimen>
+    <dimen name="activity_vertical_margin">16dp</dimen>
+
+    <!--
+Refer to App Widget Documentation for margin information
+http://developer.android.com/guide/topics/appwidgets/index.html#CreatingLayout
+    -->
+    <dimen name="about_margin">4dp</dimen>
+    <dimen name="widget_margin">8dp</dimen>
+    <!-- Default screen margins, per the Android Design guidelines. -->
+    <dimen name="nav_header_vertical_spacing">16dp</dimen>
+    <dimen name="nav_header_height">160dp</dimen>
+    <dimen name="fab_margin">16dp</dimen>
+    <dimen name="dialog_margin">20dp</dimen>
+    <dimen name="um25_value_text_size">60dp</dimen>
+    <dimen name="um25_value_text_size_small">30dp</dimen>
+</resources>
diff --git a/app/src/main/res/values/dslv_attrs.xml b/app/src/main/res/values/dslv_attrs.xml
new file mode 100644
index 0000000..b04e60f
--- /dev/null
+++ b/app/src/main/res/values/dslv_attrs.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<resources>
+    <declare-styleable name="DragSortListView">
+        <attr name="collapsed_height" format="dimension" />
+
+        <!-- Float view -->
+        <attr name="float_background_color" format="color" />
+        <attr name="float_alpha" format="float" />
+
+        <!-- Animations -->
+        <attr name="remove_animation_duration" format="integer" />
+        <attr name="drop_animation_duration" format="integer" />
+        <attr name="slide_shuffle_speed" format="float" />
+
+        <!-- Remove properties -->
+        <attr name="remove_enabled" format="boolean" />
+        <attr name="remove_mode">
+            <enum name="clickRemove" value="0" />
+            <enum name="flingRemove" value="1" />
+        </attr>
+        <attr name="fling_handle_id" format="integer" />
+        <attr name="click_remove_id" format="integer" />
+
+        <!-- Drag properties -->
+        <attr name="drag_enabled" format="boolean" />
+        <attr name="drag_start_mode">
+            <enum name="onDown" value="0" />
+            <enum name="onMove" value="1" />
+            <enum name="onLongPress" value="2" />
+        </attr>
+        <attr name="drag_handle_id" format="integer" />
+        <attr name="drag_scroll_start" format="float" />
+        <attr name="max_drag_scroll_speed" format="float" />
+
+        <attr name="track_drag_sort" format="boolean" />
+        <attr name="use_default_controller" format="boolean" />
+        <attr name="sort_enabled" format="boolean" />
+    </declare-styleable>
+</resources>
diff --git a/app/src/main/res/values/ic_launcher_background.xml b/app/src/main/res/values/ic_launcher_background.xml
new file mode 100644
index 0000000..bc4c1e7
--- /dev/null
+++ b/app/src/main/res/values/ic_launcher_background.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <color name="ic_launcher_background">#0091EA</color>
+</resources>
\ No newline at end of file
diff --git a/app/src/main/res/values/rc_attrs.xml b/app/src/main/res/values/rc_attrs.xml
new file mode 100644
index 0000000..d98b25e
--- /dev/null
+++ b/app/src/main/res/values/rc_attrs.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <declare-styleable name="RotaryControl">
+        <attr name="start_angle" format="float" />
+        <attr name="angle" format="float" />
+        <attr name="line_thickness" format="dimension" />
+        <attr name="line_color" format="color" />
+        <attr name="controlpoint_size" format="dimension" />
+        <attr name="controlpoint_color" format="color" />
+    </declare-styleable>
+</resources>
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..88cf58a
--- /dev/null
+++ b/app/src/main/res/values/strings.xml
@@ -0,0 +1,3580 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation">
+    <string name="application_name_generic">浜敵绉戞妧</string>
+    <string name="title_activity_controlcenter_generic">浜敵绉戞妧</string>
+    <string name="about_activity_title_generic">About Gadgetbridge</string>
+    <string name="about_description_generic">Cloudless copylefted libre replacement for closed source Android gadget apps from vendors.</string>
+    <string name="gadgetbridge_running_generic">浜敵绉戞妧姝e湪杩愯</string>
+
+    <string name="application_name_banglejs_main">Bangle.js Gadgetbridge</string>
+    <string name="title_activity_controlcenter_banglejs_main">Bangle.js Gadgetbridge</string>
+    <string name="about_activity_title_banglejs_main">About Bangle.js Gadgetbridge</string>
+    <string name="about_description_banglejs_main">Android companion app for Bangle.js built on top of the Gadgetbridge project, with added Internet Access.\n\nDue to Google Play Store policies, we are not allowed a donation link in the app itself, but if you like this app please consider donating via the Gadgetbridge homepage below.</string>
+    <string name="gadgetbridge_running_banglejs_main">Bangle.js running</string>
+
+    <string name="application_name_banglejs_nightly">Bangle.js Gadgetbridge (Nightly)</string>
+    <string name="title_activity_controlcenter_banglejs_nightly">Bangle.js Gadgetbridge (Nightly)</string>
+    <string name="about_activity_title_banglejs_nightly">About Bangle.js Gadgetbridge (Nightly)</string>
+    <string name="about_description_banglejs_nightly">Android companion app for Bangle.js built on top of the Gadgetbridge project, with added Internet Access.\n\nDue to Google Play Store policies, we are not allowed a donation link in the app itself, but if you like this app please consider donating via the Gadgetbridge homepage below.</string>
+    <string name="gadgetbridge_running_banglejs_nightly">Nightly Bangle.js running</string>
+
+    <string name="application_name_main_nightly">Gadgetbridge (Nightly)</string>
+    <string name="title_activity_controlcenter_main_nightly">Gadgetbridge Nightly</string>
+    <string name="about_activity_title_main_nightly">About Gadgetbridge Nightly</string>
+    <string name="about_description_main_nightly">Cloudless copylefted libre replacement for closed source Android gadget apps from vendors. Nightly releases of Gadgetbridge. It cannot be installed if you already have either the Gadgetbridge or the Pebble app installed, due to a conflict in the Pebble provider.</string>
+    <string name="gadgetbridge_running_main_nightly">Nightly GB running</string>
+
+    <string name="application_name_main_nopebble">Gadgetbridge (Nightly, No Pebble provider)</string>
+    <string name="title_activity_controlcenter_main_nopebble">Gadgetbridge Nightly No Pebble </string>
+    <string name="about_activity_title_main_nopebble">About Gadgetbridge Nightly No Pebble</string>
+    <string name="about_description_main_nopebble">Cloudless copylefted libre replacement for closed source Android gadget apps from vendors. Nightly releases of Gadgetbridge. This version has the Pebble provider renamed to prevent conflicts, so some Pebble related integrations will not work, but it can be installed alongside existing Gadgetbridge installation.</string>
+    <string name="gadgetbridge_running_main_nopebble">Nightly NoPebble GB running</string>
+
+    <string name="action_settings">Settings</string>
+    <string name="womp_workbench">Workbench</string>
+    <string name="womp_scan">Scan</string>
+    <string name="bottom_nav_band">Health Band</string>
+    <string name="bottom_nav_workbench">Workbench</string>
+    <string name="bottom_nav_health">Health</string>
+    <string name="womp_press_again_to_exit">Press back again to exit</string>
+    <string name="action_debug">Debug</string>
+    <string name="action_quit">Quit</string>
+    <string name="action_donate">Donate</string>
+    <string name="action_changelog">Changelog</string>
+    <string name="controlcenter_fetch_activity_data">Synchronize</string>
+    <string name="controlcenter_find_device">Find lost device</string>
+    <string name="search">Search</string>
+    <string name="find_lost_device_message">Search for %1$s?</string>
+    <string name="controlcenter_take_screenshot">Take Screenshot</string>
+    <string name="controlcenter_power_off">Power Off</string>
+    <string name="controlcenter_power_off_confirm_title">Power Off</string>
+    <string name="controlcenter_power_off_confirm_description">Are you sure you want to power off the device?</string>
+    <string name="controlcenter_change_led_color">Change LED Color</string>
+    <string name="controlcenter_change_fm_frequency">Change FM Frequency</string>
+    <string name="controlcenter_connect">Connect鈥�</string>
+    <string name="controlcenter_disconnect">Disconnect</string>
+    <string name="controlcenter_delete_device">Delete Device</string>
+    <string name="controlcenter_delete_device_name">Delete %1$s</string>
+    <string name="controlcenter_delete_device_dialogmessage">This will delete the device and all associated data!</string>
+    <string name="controlcenter_set_alias">Set Alias</string>
+    <string name="controlcenter_navigation_drawer_open">Open navigation drawer</string>
+    <string name="controlcenter_navigation_drawer_close">Close navigation drawer</string>
+    <string name="controlcenter_snackbar_need_longpress">Long press the card to disconnect</string>
+    <string name="controlcenter_snackbar_disconnecting">Disconnecting</string>
+    <string name="controlcenter_snackbar_connecting">Connecting鈥�</string>
+    <string name="controlcenter_snackbar_requested_screenshot">Taking a screenshot of the device</string>
+    <string name="controlcenter_calibrate_device">Calibrate Device</string>
+    <string name="controlcenter_get_heartrate_measurement">Get heart rate measurement</string>
+
+    <string name="accuracy">Accuracy</string>
+    <string name="balanced">Balanced</string>
+    <string name="power_saving">Power Saving</string>
+    <string name="custom">Custom</string>
+    <string name="single_band">Single Band</string>
+    <string name="dual_band">Dual Band</string>
+    <string name="low_power_gps">Low Power GPS</string>
+    <string name="gps">GPS</string>
+    <string name="gps_bds">GPS + BDS</string>
+    <string name="gps_glonass">GPS + GLONASS</string>
+    <string name="gps_galileo">GPS + GALILEO</string>
+    <string name="all_satellites">All Satellites</string>
+    <string name="speed_first">Speed first</string>
+    <string name="accuracy_first">Accuracy First</string>
+
+    <string name="device_card_activity_card_title">Activity info on device card</string>
+    <string name="device_card_activity_card_title_summary">Choose what activity details are displayed on device card</string>
+    <string name="prefs_activity_in_device_card_title">Show Activity info on device card</string>
+    <string name="prefs_activity_in_device_card_title_summary">Show current steps, distance or sleep on device card</string>
+    <string name="prefs_activity_in_device_card_sleep_title">Sleep</string>
+    <string name="prefs_activity_in_device_card_sleep_title_summary">Show sleep duration</string>
+    <string name="prefs_activity_in_device_card_distance_title_summary">Distance is calculated from steps and step length (adjustable in Settings - About you)</string>
+    <string name="prefs_activity_in_device_card_steps_title_summary">Show total steps</string>
+    <!-- Strings related to battery Info Activity -->
+    <string name="battery_detail_activity_title">Battery info</string>
+    <string name="battery_level">Battery level</string>
+    <string name="calendar_day">Day</string>
+    <string name="calendar_week">Week</string>
+    <string name="calendar_two_weeks">Two weeks</string>
+    <string name="calendar_month">Month</string>
+    <string name="calendar_three_months">3 months</string>
+    <string name="calendar_six_months">6 months</string>
+    <string name="calendar_year">Year</string>
+    <!-- Strings related to Debug Activity -->
+    <string name="title_activity_debug">Debug</string>
+    <string name="debugactivity_really_factoryreset_title">Really factory reset?</string>
+    <string name="debugactivity_really_factoryreset">Doing a factory reset will delete all data from the connected device (if supported). Xiaomi/Huami devices also change Bluetooth MAC address, so they appear as a new devices to Gadgetbridge.</string>
+    <string name="debugactivity_confirm_remove_device_preferences_title">Remove device preferences?</string>
+    <string name="debugactivity_confirm_remove_device_preferences">This will reset the device preferences for all connected devices. Are you sure?</string>
+    <!-- Strings related to AppManager -->
+    <string name="title_activity_appmanager">App Manager</string>
+    <string name="appmanager_cached_watchapps_watchfaces">Apps in cache</string>
+    <string name="appmanager_installed_watchapps">Installed apps</string>
+    <string name="appmanager_installed_watchfaces">Installed watchfaces</string>
+    <string name="appmanager_watchface_activate">Activate</string>
+    <string name="appmanager_app_start">Start</string>
+    <string name="appmanager_app_download">Download to cache</string>
+    <string name="appmananger_app_delete">Delete</string>
+    <string name="appmananger_app_delete_cache">Delete and remove from cache</string>
+    <string name="appmananger_app_reinstall">Reinstall</string>
+    <string name="appmanager_app_share">Share</string>
+    <string name="appmanager_app_openinstore">Search in Pebble appstore</string>
+    <string name="appmanager_health_activate">Activate</string>
+    <string name="appmanager_health_deactivate">Deactivate</string>
+    <string name="appmanager_hrm_activate">Activate HRM</string>
+    <string name="appmanager_hrm_deactivate">Deactivate HRM</string>
+    <string name="appmanager_weather_activate">Activate System Weather app</string>
+    <string name="appmanager_weather_deactivate">Deactivate System Weather app</string>
+    <string name="appmanager_weather_install_provider">Install the Weather Notification app</string>
+    <string name="app_configure">Configure</string>
+    <string name="app_move_to_top">Move to top</string>
+    <string name="appmanager_item_outdated">(outdated)</string>
+    <string name="appmanager_download_started">App download started</string>
+    <string name="appmanager_downloaded_to_cache">Downloaded %s to cache</string>
+    <string name="appmanager_download_app_error">Error downloading app</string>
+    <!-- Strings related to AppBlacklist -->
+    <string name="title_activity_notification_management">Notification settings</string>
+    <string name="blacklist_all_for_notifications">Blacklist all for notifications</string>
+    <string name="whitelist_all_for_notifications">Whitelist all for notifications</string>
+    <string name="check_all_applications">Check all applications</string>
+    <string name="uncheck_all_applications">Uncheck all applications</string>
+    <!-- Strings related to CalBlacklist -->
+    <string name="title_activity_calblacklist">Blacklisted Calendars</string>
+    <!-- Strings related to FwAppInstaller -->
+    <string name="title_activity_fw_app_insaller">FW/App installer</string>
+    <string name="fw_upgrade_notice">You are about to install the %s.</string>
+    <string name="fw_upgrade_notice_amazfitbip">You are about to install the %s firmware on your Amazfit Bip.\n\nPlease make sure to install the .fw file, then the .res file, and finally the .gps file. Your watch will reboot after installing the .fw file.\n\nNote: You do not have to install .res and .gps if these files are exactly the same as the ones previously installed.\n\nPROCEED AT YOUR OWN RISK!</string>
+    <string name="fw_upgrade_notice_amazfitbip_lite">You are about to install the %s firmware on your Amazfit Bip Lite.\n\nPlease make sure to install the .fw file, and after that the .res file. Your watch will reboot after installing the .fw file.\n\nNote: You do not have to install .res if it is exactly the same as the one previously installed.\n\nPROCEED AT YOUR OWN RISK!</string>
+    <string name="fw_upgrade_notice_amazfitbip3">You are about to install the %s firmware on your Amazfit Bip 3.\n\nPlease make sure to install the .fw file, and after that the .res file. Your watch will reboot after installing the .fw file.\n\nNote: You do not have to install .res if it is exactly the same as the one previously installed.\n\nPROCEED AT YOUR OWN RISK!</string>
+    <string name="fw_upgrade_notice_amazfitbip3pro">You are about to install the %s firmware on your Amazfit Bip 3 Pro.\n\nPlease make sure to install the .fw file, and after that the .res file. Your watch will reboot after installing the .fw file.\n\nNote: You do not have to install .res if it is exactly the same as the one previously installed.\n\nPROCEED AT YOUR OWN RISK!</string>
+    <string name="fw_upgrade_notice_amazfitcor">You are about to install the %s firmware on your Amazfit Cor.\n\nPlease make sure to install the .fw file, and after that the .res file. Your band will reboot after installing the .fw file.\n\nNote: You do not have to install .res if it is exactly the same as the one previously installed.\n\nPROCEED AT YOUR OWN RISK!</string>
+    <string name="fw_upgrade_notice_amazfitcor2">You are about to install the %s firmware on your Amazfit Cor 2.
+\n
+\nPlease make sure to install the .fw file, and after that the .res file. Your band will reboot after installing the .fw file.
+\n
+\nNote: You do not have to install .res if it is exactly the same as the one previously installed.
+\n
+\nPROCEED AT YOUR OWN RISK!
+\n
+\nCOMPLETELY UNTESTED, PROBABLY YOU NEED TO FLASH A BEATS_W FIRMWARE IF YOUR DEVICE NAME IS \"Amazfit Band 2\"</string>
+    <string name="fw_upgrade_notice_amazfitgtr">You are about to install the %s firmware on your Amazfit GTR.\n\nPlease make sure to install the .fw file, then the .res file, and finally the .gps file. Your watch will reboot after installing the .fw file.\n\nNote: You do not have to install .res and .gps if these files are exactly the same as the ones previously installed.\n\nPROCEED AT YOUR OWN RISK!</string>
+    <string name="fw_upgrade_notice_amazfitgts">You are about to install the %s firmware on your Amazfit GTS.\n\nPlease make sure to install the .fw file, then the .res file, and finally the .gps file. Your watch will reboot after installing the .fw file.\n\nNote: You do not have to install .res and .gps if these files are exactly the same as the ones previously installed.\n\nPROCEED AT YOUR OWN RISK!</string>
+    <string name="fw_upgrade_notice_amazfit_trex">You are about to install the %s firmware on your Amazfit T-Rex.\n\nPlease make sure to install the .fw file, then the .res file, and finally the .gps file. Your watch will reboot after installing the .fw file.\n\nNote: You do not have to install .res and .gps if these files are exactly the same as the ones previously installed.\n\nPROCEED AT YOUR OWN RISK!</string>
+    <string name="fw_upgrade_notice_amazfitvergel">You are about to install the %s firmware on your Amazfit Verge Lite.\n\nPlease make sure to install the .fw file, then the .res file, and finally the .gps file. Your watch will reboot after installing the .fw file.\n\nNote: You do not have to install .res and .gps if these files are exactly the same as the ones previously installed.\n\nPROCEED AT YOUR OWN RISK!</string>
+    <string name="fw_upgrade_notice_miband3">You are about to install the %s firmware on your Mi Band 3.\n\nPlease make sure to install the .fw file, and after that the .res file. Your band will reboot after installing the .fw file.\n\nNote: You do not have to install .res if it is exactly the same as the one previously installed.\n\nPROCEED AT YOUR OWN RISK!</string>
+    <string name="fw_upgrade_notice_miband4">You are about to install the %s firmware on your Mi Band 4.\n\nPlease make sure to install the .fw file, and after that the .res file. Your band will reboot after installing the .fw file.\n\nNote: You do not have to install .res if it is exactly the same as the one previously installed.\n\nPROCEED AT YOUR OWN RISK!</string>
+    <string name="fw_upgrade_notice_miband5">You are about to install the %s firmware on your Mi Band 5.\n\nPlease make sure to install the .fw file, and after that the .res file. Your band will reboot after installing the .fw file.\n\nNote: You do not have to install .res if it is exactly the same as the one previously installed.\n\nPROCEED AT YOUR OWN RISK!</string>
+    <string name="fw_upgrade_notice_miband6">You are about to install the %s firmware on your Mi Band 6.\n\nPlease make sure to install the .fw file, and after that the .res file. Your band will reboot after installing the .fw file.\n\nNote: You do not have to install .res if it is exactly the same as the one previously installed.\n\nPROCEED AT YOUR OWN RISK!</string>
+    <string name="fw_upgrade_notice_miband7">You are about to install the %s firmware on your Xiaomi Smart Band 7.\n\nYour band will reboot after installing the .zip file.\n\nPROCEED AT YOUR OWN RISK!</string>
+    <string name="fw_upgrade_notice_amazfit_gts3">You are about to install the %s firmware on your Amazfit GTS 3.\n\nYour band will reboot after installing the .zip file.\n\nPROCEED AT YOUR OWN RISK!</string>
+    <string name="fw_upgrade_notice_amazfit_gts4">You are about to install the %s firmware on your Amazfit GTS 4.\n\nYour band will reboot after installing the .zip file.\n\nPROCEED AT YOUR OWN RISK!</string>
+    <string name="fw_upgrade_notice_amazfit_gts4_mini">You are about to install the %s firmware on your Amazfit GTS 4 Mini.\n\nYour band will reboot after installing the .zip file.\n\nPROCEED AT YOUR OWN RISK!</string>
+    <string name="fw_upgrade_notice_amazfit_gtr3">You are about to install the %s firmware on your Amazfit GTR 3.\n\nYour band will reboot after installing the .zip file.\n\nPROCEED AT YOUR OWN RISK!</string>
+    <string name="fw_upgrade_notice_amazfit_gtr3_pro">You are about to install the %s firmware on your Amazfit GTR 3 Pro.\n\nYour band will reboot after installing the .zip file.\n\nPROCEED AT YOUR OWN RISK!</string>
+    <string name="fw_upgrade_notice_amazfit_gtr4">You are about to install the %s firmware on your Amazfit GTR 4.\n\nYour band will reboot after installing the .zip file.\n\nPROCEED AT YOUR OWN RISK!</string>
+    <string name="fw_upgrade_notice_amazfit_trex2">You are about to install the %s firmware on your Amazfit T-Rex 2.\n\nYour band will reboot after installing the .zip file.\n\nPROCEED AT YOUR OWN RISK!</string>
+    <string name="fw_upgrade_notice_amazfit_band7">You are about to install the %s firmware on your Amazfit Band 7.\n\nYour band will reboot after installing the .zip file.\n\nPROCEED AT YOUR OWN RISK!</string>
+    <string name="fw_upgrade_notice_amazfit_cheetah_pro">You are about to install the %s firmware on your Amazfit Cheetah Pro.\n\nYour band will reboot after installing the .zip file.\n\nPROCEED AT YOUR OWN RISK!</string>
+    <string name="fw_upgrade_notice_zepp_os">You are about to install the %s firmware on your %s.\n\nYour watch will reboot after installing the .zip file.\n\nPROCEED AT YOUR OWN RISK!</string>
+    <string name="fw_upgrade_notice_amazfitx">You are about to install the %s firmware on your Amazfit X.\n\nPlease make sure to install the .fw file, and after that the .res file. Your band will reboot after installing the .fw file.\n\nNote: You do not have to install .res if it is exactly the same as the one previously installed.\n\nPROCEED AT YOUR OWN RISK!</string>
+    <string name="fw_upgrade_notice_amazfitneo">You are about to install the %s firmware on your Amazfit Neo.
+\n
+\nYour band will reboot after installing the .fw file.
+\n
+\nPROCEED AT YOUR OWN RISK!</string>
+    <string name="fw_multi_upgrade_notice">You are about to install the %1$s and %2$s firmware, instead of the ones currently on your Mi Band.</string>
+    <string name="miband_firmware_known">This firmware has been tested and is known to be compatible with Gadgetbridge.</string>
+    <string name="miband_firmware_unknown_warning">"This firmware is untested and may not be compatible with Gadgetbridge.\n\nYou are DISCOURAGED from flashing it!"</string>
+    <string name="miband_firmware_suggest_whitelist">If you still want to proceed and things continue to work properly afterwards, please tell the Gadgetbridge developers to whitelist the %s firmware version.</string>
+    <!-- Strings related to the FwAppInstaller opener activity -->
+    <string name="open_fw_installer_info_text">The Firmware/Watchface/App/File Installer allows you to upload/install supported files (firmware, watchfaces, applications, GPS, resources, fonts...) to the device. See some more information in the wiki: https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Firmware-Update</string>
+    <string name="open_fw_installer_warning_title">Warning</string>
+    <string name="open_fw_installer_warning_text">This feature has the potential to brick your device. That said, this has never happened to any of the developers through flashing, but remember that you are doing this at your own risk.</string>
+    <string name="open_fw_installer_getting_files_title">Getting the firmware/app file</string>
+    <string name="open_fw_installer_getting_files_text">Since we may not distribute the firmware files, you will have to get the files yourself. This means that you will need to search for the files in apk files, online, in forums, on Amazfitwatchfaces (for Miband/Amazfit devices) and so on.</string>
+    <string name="open_fw_installer_pick_file">Select file</string>
+    <string name="open_fw_installer_info_text_title">File Installer</string>
+    <string name="open_fw_installer_select_file">Select a file you want to upload to device: %s</string>
+    <string name="open_fw_installer_connect_minimum_one_device">Please connect AT LEAST ONE device you want to send the file to.</string>
+    <string name="open_fw_installer_connect_maximum_one_device">Please connect ONLY ONE device you want to send the file to.</string>
+    <string name="open_fw_installer_ensure_device_connected">Make sure that the device %s is connected</string>
+    <!-- Strings related to MusicManager -->
+    <string name="title_activity_musicmanager">Music Manager</string>
+    <!-- Strings related to Settings -->
+    <string name="title_activity_settings">Settings</string>
+    <string name="proprietary_app_warning">This feature requires the installation of a proprietary app</string>
+    <string name="pref_header_general">General settings</string>
+    <string name="pref_header_other">Other</string>
+    <string name="pref_header_system">System</string>
+    <string name="pref_header_audio">Audio</string>
+    <string name="pref_header_calendar">Calendar</string>
+    <string name="pref_header_connection">Connection</string>
+    <string name="pref_header_display">Display</string>
+    <string name="pref_header_generic">Generic</string>
+    <string name="pref_header_health">Health</string>
+    <string name="pref_header_sound_vibration">Sound &amp; Vibration</string>
+    <string name="pref_header_offline_voice">Offline Voice</string>
+    <string name="pref_header_sound">Sound</string>
+    <string name="pref_header_time">Time</string>
+    <string name="pref_header_workout">Workout</string>
+    <string name="pref_header_equalizer">Equalizer</string>
+    <string name="pref_title_general_autoconnectonbluetooth">Connect to Gadgetbridge device(s) when Bluetooth is turned on</string>
+    <string name="pref_title_general_reconnectonlytoconnected">Reconnect only to connected devices</string>
+    <string name="pref_summary_general_reconnectonlytoconnected">Reconnect only to connected devices, instead of reconnecting to all devices</string>
+    <string name="pref_title_general_autostartonboot">Start automatically</string>
+    <string name="pref_title_general_autoreconnect">Reconnect automatically</string>
+    <string name="pref_title_mb_intents">Broadcast Media Button Intents Directly</string>
+    <string name="pref_summary_mb_intents">Enable if device media control is not working for certain applications</string>
+    <string name="pref_title_audio_player">Preferred Audioplayer</string>
+    <string name="pref_title_nagivation_apps">Navigation apps</string>
+    <string name="pref_header_external_integrations">External Integrations</string>
+    <string name="pref_header_automations">Automations</string>
+    <string name="pref_description_general">Startup, language, region, location</string>
+    <string name="pref_description_about_you">Date of birth, gender, height, weight, goals</string>
+    <string name="pref_description_notifications">App notifications, whitelist/blacklist</string>
+    <string name="pref_description_user_interface">Theme, main screen</string>
+    <string name="pref_description_dashboard">Widgets, devices to include</string>
+    <string name="pref_description_automations">Auto export, auto fetch</string>
+    <string name="pref_description_developer_options">Logs, Intent API</string>
+    <string name="pref_description_deprecated_functionalities">Settings that will be removed in a future version</string>
+    <string name="pref_default">Default</string>
+    <string name="pref_header_datetime">Date and Time</string>
+    <string name="pref_title_datetime_syctimeonconnect">Sync time</string>
+    <string name="pref_summary_datetime_syctimeonconnect">Sync time to Gadgetbridge device(s) when connecting, when time or time zone changes on Android device, and periodically</string>
+    <string name="pref_header_main_screen">Main screen</string>
+    <string name="pref_title_theme">Theme</string>
+    <string name="pref_theme_light">Light</string>
+    <string name="pref_theme_dark">Dark</string>
+    <string name="pref_theme_system">System</string>
+    <string name="pref_theme_dynamic">Dynamic colors</string>
+    <string name="pref_theme_black_background">Use black background in Dark Theme</string>
+    <string name="pref_title_language">Language</string>
+    <string name="pref_title_minimize_priority">Hide the Gadgetbridge notification</string>
+    <string name="pref_summary_minimize_priority_off">The icon in the status bar and the notification in the lockscreen are shown</string>
+    <string name="pref_summary_minimize_priority_on">The icon in the status bar and the notification in the lockscreen are hidden</string>
+    <string name="pref_header_notifications">Notifications</string>
+    <string name="pref_title_user_interface">User interface</string>
+    <string name="pref_title_notifications_repetitions">Repetitions</string>
+    <string name="pref_title_notifications_call">Phone Calls</string>
+    <string name="pref_title_notification_delay_calls">Call notification delay</string>
+    <string name="pref_summary_notification_delay_calls">Delay before sending incoming call notifications to the device, in seconds.</string>
+    <string name="pref_title_notification_wake_on_open">Auto wake and unlock</string>
+    <string name="pref_summary_notification_wake_on_open">Wake and unlock the Android device when the gadget sends a OPEN response back. Needs to be in a trusted state.</string>
+    <string name="pref_summary_receive_calls_watch">Perform and receive calls directly on the watch</string>
+    <string name="bluetooth_calls">Bluetooth calls</string>
+    <string name="bluetooth_calls_pairing">Bluetooth calls pairing</string>
+    <string name="bluetooth_calls_settings">Bluetooth calls settings</string>
+    <string name="pref_display_caller_title">Show contact information</string>
+    <string name="pref_display_caller_summary">Display phone number or name for incoming calls</string>
+    <string name="pref_pair_bluetooth_calls_title">Pair for bluetooth calls</string>
+    <string name="pref_pair_bluetooth_calls_summary">Click here to start the pairing process</string>
+    <string name="pref_pair_bluetooth_calls_help_title">How to receive bluetooth calls</string>
+    <string name="pref_pair_bluetooth_calls_help_summary">In order to receive bluetooth calls, you need to pair your phone with a second instance of the watch.</string>
+    <string name="pref_pair_bluetooth_calls_help_1">1. Tap the button below to start the pairing process.</string>
+    <string name="pref_pair_bluetooth_calls_help_2">2. Go to your phone\'s bluetooth settings, and pair with the new device that will show up (similar name to your current watch, but with a suffix, eg. \"Amazfit GTR 4 - AFC8\".</string>
+    <string name="pref_pair_bluetooth_calls_help_3">3. Enable the "Bluetooth calls" setting below.</string>
+    <string name="pref_pair_bluetooth_calls_help_warning">WARNING: If you enable bluetooth calls without pairing with the second instance, call notifications might not work as expected.</string>
+    <string name="pref_title_support_voip_calls">Enable VoIP app calls</string>
+    <string name="pref_title_ping_tone">Ping tone</string>
+    <string name="pref_title_notifications_sms">SMS</string>
+    <string name="pref_title_notifications_timeout">Minimum time between notifications</string>
+    <string name="pref_title_notifications_pebblemsg">Pebble Messages</string>
+    <string name="pref_summary_notifications_pebblemsg">Support for apps that send notifications to the Pebble via PebbleKit.</string>
+    <string name="pref_title_notifications_generic">Generic notification support</string>
+    <string name="pref_title_notifications_generic_settings">Android notification settings</string>
+    <string name="pref_title_whenscreenon">鈥lso when screen is on</string>
+    <string name="pref_title_notifications_ignore_low_priority">Ignore low priority notifications</string>
+    <string name="pref_summary_notifications_ignore_low_priority">Do not send low and minimum priority notifications to the watch</string>
+    <string name="pref_title_notifications_ignore_work_profile">Ignore work profile notifications</string>
+    <string name="pref_summary_notifications_ignore_work_profile">Do not send notifications from apps in the work profile to the watch</string>
+    <string name="pref_title_notification_prefer_long_text">Prefer long notification text</string>
+    <string name="pref_summary_notification_prefer_long_text">If available, send the long notification text to the device</string>
+    <string name="pref_title_notification_cache_while_disconnected">Cache while out of range</string>
+    <string name="pref_summary_notification_cache_while_disconnected">Send missed notifications when a device reconnects after being out of range</string>
+    <string name="pref_title_notification_filter">Do Not Disturb</string>
+    <string name="pref_summary_notification_filter">Block all notifications when Do Not Disturb is enabled on the phone</string>
+    <string name="pref_title_notification_times_enabled">Notification times</string>
+    <string name="pref_summary_notification_times_enabled">Only send notifications between specific times</string>
+    <string name="pref_title_notification_media_ignores_application_list">Media notifications ignore app list</string>
+    <string name="pref_summary_notification_media_ignores_application_list">Process media notifications before the app list. If this preference is unchecked, media applications need to be allowed in the application list for media controls to work on the device.</string>
+    <string name="pref_header_notification_application_settings">Per application settings</string>
+    <string name="pref_title_notification_use_as">Use the Applications list to鈥�</string>
+    <string name="pref_title_notification_use_as_deny">Deny notifications from selected apps</string>
+    <string name="pref_title_notification_use_as_allow">Allow notifications from selected apps</string>
+    <string name="pref_title_transliteration">Transliteration</string>
+    <string name="pref_summary_transliteration">Enable this if your device has no support for your language\'s font</string>
+    <string name="pref_title_banglejs_text_bitmap">Text as Bitmaps</string>
+    <string name="pref_summary_banglejs_text_bitmap">If a word cannot be rendered with the watch\'s font, render it to a bitmap in Gadgetbridge and display the bitmap on the watch</string>
+    <string name="pref_title_banglejs_txt_bitmap_size">Text Bitmaps Size</string>
+    <string name="pref_summary_banglejs_txt_bitmap_size">Size to use for bitmap text rendering</string>
+    <string name="pref_title_banglejs_phone_gps_enbale">Use phone gps data</string>
+    <string name="pref_summary_banglejs_phone_gps_enbale">Use the gps data of the phone to overwrite the gps data of the bangle device</string>
+    <string name="pref_title_banglejs_phone_gps_network_only">Only use network to determine location</string>
+    <string name="pref_summary_banglejs_phone_gps_network_only">Use only the network provider to determine the location. This reduces the power consumption at the cost of accuracy. A reconnection is needed.</string>
+    <string name="pref_title_banglejs_phone_gps_update_interval">GPS data update interval</string>
+    <string name="pref_summary_banglejs_phone_gps_update_interval">The interval for how often the gps position is being updated, in ms</string>
+    <string name="pref_title_banglejs_webview_url">App loader URL</string>
+    <string name="pref_summary_banglejs_webview_url">If you want a custom app loader put your https://鈥�/android.html URL here. Otherwise leave blank for https://banglejs.com/apps</string>
+    <string name="pref_title_rtl">Right-To-Left</string>
+    <string name="pref_summary_rtl">Enable this if your device can not show right-to-left languages</string>
+    <string name="pref_rtl_max_line_length">Right-To-Left Max Line Length</string>
+    <string name="pref_rtl_max_line_length_summary">Lengthens or shortens the lines Right-To-Left text is separated into</string>
+    <string name="always">Always</string>
+    <string name="when_screen_off">When screen is off</string>
+    <string name="never">Never</string>
+    <string name="pref_header_privacy">Privacy</string>
+    <string name="pref_title_call_privacy_mode">Call privacy mode</string>
+    <string name="pref_call_privacy_mode_off">Display name and number</string>
+    <string name="pref_call_privacy_mode_name">Hide name but display number</string>
+    <string name="pref_call_privacy_mode_number">Hide number but display name</string>
+    <string name="pref_call_privacy_mode_complete">Hide name and number</string>
+    <string name="pref_title_message_privacy_mode">Message privacy mode</string>
+    <string name="pref_message_privacy_mode_off">Display all content</string>
+    <string name="pref_message_privacy_mode_complete">Hide all content</string>
+    <string name="pref_message_privacy_mode_bodyonly">Hide only body</string>
+    <string name="pref_title_weather">Weather</string>
+    <string name="pref_title_weather_location">Weather location (for LineageOS weather provider)</string>
+    <string name="pref_title_weather_summary">Used for the LineageOS weather provider, other Android versions need to use an app like \"Weather notification\". Find more information in the Gadgetbridge wiki.</string>
+    <string name="pref_applications_settings">Applications list</string>
+    <string name="pref_blacklist_calendars">Blacklist Calendars</string>
+    <string name="pref_blacklist_calendars_summary">Blacklisted calendars will not be synced to the device</string>
+    <string name="pref_header_cannned_messages">Canned messages</string>
+    <string name="pref_title_canned_replies">Replies</string>
+    <string name="pref_canned_message">Message</string>
+    <string name="pref_summary_canned_replies">Reply from the watch using preset messages</string>
+    <string name="pref_title_canned_reply_suffix">Common suffix</string>
+    <string name="pref_title_canned_messages_dismisscall">Call Dismissal</string>
+    <string name="pref_title_canned_messages_set">Update on device</string>
+    <string name="pref_header_development">Developer options</string>
+    <string name="pref_header_intent_api">Intent API</string>
+    <string name="pref_header_authentication">Authentication</string>
+    <string name="pref_title_development_miaddr">Mi Band address</string>
+    <string name="pref_title_pebble_settings">Pebble settings</string>
+    <string name="pref_header_activitytrackers">Activity trackers</string>
+    <string name="pref_title_pebble_activitytracker">Preferred activity tracker</string>
+    <string name="pref_title_pebble_sync_health">Sync Pebble Health</string>
+    <string name="pref_title_pebble_sync_misfit">Sync Misfit</string>
+    <string name="pref_title_pebble_sync_morpheuz">Sync Morpheuz</string>
+    <string name="pref_title_enable_outgoing_call">Support outgoing calls</string>
+    <string name="pref_summary_enable_outgoing_call">Disabling this will also stop the Pebble 2/LE to vibrate on outgoing calls</string>
+    <string name="pref_title_enable_pebblekit">Allow 3rd party Android App access</string>
+    <string name="pref_summary_enable_pebblekit">Enable experimental support for Android apps using PebbleKit</string>
+    <string name="pref_header_pebble_timeline">Pebble timeline</string>
+    <string name="pref_title_sunrise_sunset">Sunrise and sunset</string>
+    <string name="pref_summary_sunrise_sunset">Send sunrise and sunset times based on the location to the Pebble timeline</string>
+    <string name="pref_title_enable_calendar_sync">Sync calendar</string>
+    <string name="pref_summary_enable_calendar_sync">Send calendar events to the timeline</string>
+    <string name="pref_time_sync">Automatic time sync</string>
+    <string name="pref_title_custom_deviceicon">Show device specific notification icon</string>
+    <string name="pref_summary_custom_deviceicon">Show a device specific Android notification icon instead the Gadgetbridge icon when connected</string>
+    <string name="pref_title_preview_message_in_title">Show a preview of the message in the title</string>
+    <string name="pref_summary_preview_message_in_title">Shows a preview of the message in the title of a notification as allowed by the device</string>
+    <string name="pref_title_casio_alert_calendar">Alert for calendar notifications</string>
+    <string name="pref_summary_casio_alert_calendar">Alert (vibrate/beep) for calendar notifications</string>
+    <string name="pref_title_casio_alert_call">Alert for incoming calls</string>
+    <string name="pref_summary_casio_alert_call">Alert (vibrate/beep) for incoming calls</string>
+    <string name="pref_title_casio_alert_email">Alert for email notifications</string>
+    <string name="pref_summary_casio_alert_email">Alert (vibrate/beep) for email notifications</string>
+    <string name="pref_title_casio_alert_sms">Alert for SMS notifications</string>
+    <string name="pref_summary_casio_alert_sms">Alert (vibrate/beep) for SMS (text message) notifications</string>
+    <string name="pref_title_casio_alert_other">Alert for "other" notifications</string>
+    <string name="pref_summary_casio_alert_other">Alert (vibrate/beep) for notifications in the "other" category</string>
+    <string name="pref_title_autoremove_notifications">Autoremove dismissed notifications</string>
+    <string name="pref_summary_autoremove_notifications">Notifications are automatically removed from the device when dismissed from the phone</string>
+    <string name="pref_title_screen_on_on_notifications">Screen On on Notifications</string>
+    <string name="pref_summary_screen_on_on_notifications">Turn on the band\'s screen when a notification arrives</string>
+    <string name="pref_title_send_app_notifications">Send notifications</string>
+    <string name="pref_summary_send_app_notifications">Send app notifications to the device</string>
+    <string name="pref_title_pebble_privacy_mode">Privacy mode</string>
+    <string name="pref_pebble_privacy_mode_off">Normal notifications</string>
+    <string name="pref_pebble_privacy_mode_content">Shift the notification text off-screen</string>
+    <string name="pref_pebble_privacy_mode_complete">Show only the notification icon</string>
+    <string name="pref_header_location">Location</string>
+    <string name="pref_header_navigation">Navigation</string>
+    <string name="pref_title_location_aquire">Acquire location</string>
+    <string name="pref_title_location_latitude">Latitude</string>
+    <string name="pref_title_location_longitude">Longitude</string>
+    <string name="pref_title_location_keep_uptodate">Keep location updated</string>
+    <string name="pref_summary_location_keep_uptodate">Try to get the current location at runtime, use the stored location as fallback</string>
+    <string name="toast_enable_networklocationprovider">Please enable network location</string>
+    <string name="toast_aqurired_networklocation">location acquired</string>
+    <string name="pref_title_pebble_forceprotocol">Force notification protocol</string>
+    <string name="pref_summary_pebble_forceprotocol">This option forces using the latest notification protocol depending on the firmware version. KNOW WHAT YOU ARE DOING!</string>
+    <string name="pref_title_pebble_forceuntested">Enable untested features</string>
+    <string name="pref_summary_pebble_forceuntested">Enable untested features. KNOW WHAT YOU ARE DOING!</string>
+    <string name="pref_title_pebble_forcele">Always prefer BLE</string>
+    <string name="pref_summary_pebble_forcele">Use experimental Pebble LE support for all Pebbles, instead of BT classic. This requires pairing to non LE first, and then Pebble LE</string>
+    <string name="pref_title_pebble_mtu_limit">Pebble 2/LE GATT MTU limit</string>
+    <string name="pref_summary_pebble_mtu_limit">If your Pebble 2/Pebble LE does not work as expected, try this setting to limit the MTU (valid range 20鈥�512)</string>
+    <string name="pref_title_pebble_enable_applogs">Enable watch app logging</string>
+    <string name="pref_title_pebble_gatt_clientonly">GATT client only</string>
+    <string name="pref_summary_pebble_gatt_clientonly">This is for Pebble 2 only and experimental, try this if you have connectivity problems</string>
+    <string name="pref_summary_pebble_enable_applogs">Will cause logs from watch apps to be logged by Gadgetbridge (requires reconnect)</string>
+    <string name="pref_title_pebble_always_ack_pebblekit">Prematurely ACK PebbleKit</string>
+    <string name="pref_summary_pebble_always_ack_pebblekit">Will cause messages that are sent to external 3rd party apps to be acknowledged always and immediately</string>
+    <string name="pref_title_pebble_enable_bgjs">Enable background JS</string>
+    <string name="pref_summary_pebble_enable_bgjs">When enabled, allows watchfaces to show weather, battery info etc.</string>
+    <string name="pref_title_pebble_reconnect_attempts">Reconnection attempts</string>
+    <string name="pref_summary_expose_hr">Allows other apps to access HR data in realtime while Gadgetbridge is connected</string>
+    <string name="pref_title_expose_hr">3rd party realtime HR access</string>
+    <string name="pref_title_connected_advertisement">Visible while connected</string>
+    <string name="pref_summary_connected_advertisement">Makes the device discoverable via Bluetooth even when connected</string>
+    <string name="pref_title_use_custom_font">Use custom font</string>
+    <string name="pref_summary_use_custom_font">Enable this if your device has a custom font firmware for emoji support</string>
+    <string name="pref_title_allow_high_mtu">Allow high MTU</string>
+    <string name="pref_summary_allow_high_mtu">Increases transfer speed, but might not work on some Android devices.</string>
+    <string name="pref_title_calendar_lookahead">Number of days ahead</string>
+    <string name="pref_summary_calendar_lookahead">Sync up to %1s days of calendar events</string>
+    <string name="pref_title_overwrite_settings_on_connection">Overwrite settings on connection</string>
+    <string name="pref_summary_overwrite_settings_on_connection">When connecting to the band, overwrite all the settings on it.</string>
+    <string name="pref_title_device_internet_access">Allow Internet Access</string>
+    <string name="pref_summary_device_internet_access">Allow apps on this device to access the internet</string>
+    <string name="pref_title_device_intents">Allow Intents</string>
+    <string name="pref_summary_device_intents">Allow Bangle.js watch apps to send Android Intents, and allow other apps on Android (like Tasker) to send data to Bangle.js with the com.banglejs.uart.tx Intent. Needs permission to display over other apps to work in the background.</string>
+    <string name="pref_summary_sync_calendar">Enables calendar alerts, even when disconnected</string>
+    <string name="pref_title_sync_caldendar">Sync calendar events</string>
+    <string name="pref_summary_sync_birthdays">Sync contact birthdays alongside calendar events</string>
+    <string name="pref_title_sync_birthdays">Sync birthdays</string>
+    <string name="pref_summary_relax_firmware_checks">Relax firmware checks</string>
+    <string name="pref_title_relax_firmware_checks">Enable this if you want to flash a firmware not intended for your device (at your own risk)</string>
+    <string name="pref_title_vibration_strength">Vibration strength</string>
+    <string name="pref_display_add_device_fab">Connect new device button</string>
+    <string name="pref_display_add_device_fab_on">Always visible</string>
+    <string name="pref_display_add_device_fab_off">Visible only if no device is added</string>
+    <string name="pref_title_huami_force_new_protocol">New Auth Protocol</string>
+    <string name="pref_summary_huami_force_new_protocol">Enable if your device no longer connects after a firmware upgrade</string>
+    <!-- HPlus Preferences -->
+    <string name="pref_title_unit_system">Units</string>
+    <string name="pref_title_timeformat">Time format</string>
+    <string name="pref_title_screentime">Screen on duration</string>
+    <string name="pref_title_goal_secondary">Secondary goal</string>
+    <string name="prefs_title_all_day_heart_rate">All day heart rate measurement</string>
+    <string name="preferences_hplus_settings">HPlus/Makibes settings</string>
+    <!-- WatchXPlus Preferences -->
+    <string name="title_activity_LenovoWatch_calibration">Watch X Plus calibration</string>
+    <!-- Device Settings - Notifications and Calls - Used in devicesettings_watchxplus.xml -->
+    <string name="pref_header_notifications_and_calls">Notifications and Calls</string>
+    <string name="pref_title_notifications_and_calls_repeat_on_call">Repeat call notification</string>
+    <string name="prefs_notifications_and_calls_continious_ring">Notification during phone ring</string>
+    <string name="pref_notifications_and_calls_enable_misscall">Notify for missed call</string>
+    <string name="pref_summary_notifications_and_calls_enable_misscall">Repeats on every minute</string>
+    <string name="pref_title_notifications_and_calls_repeat_on_misscall">Repeat for X minutes</string>
+    <string name="pref_header_notifications_and_calls_callhandling">Call Handling</string>
+    <string name="prefs_notifications_and_calls_reject">Button ignore/reject call</string>
+    <string name="pref_summary_notifications_and_calls_title_reject">Off - ignore, On - reject</string>
+    <string name="prefs_notifications_and_calls_shake_reject">Shake wrist ignore/reject call</string>
+    <string name="pref_summary_notifications_and_calls_title_shake_reject">Duplicates watch button action</string>
+    <!-- Device Settings - Device Settings - Used in devicesettings_watchxplus.xml -->
+    <string name="pref_header_device_spec_settings">Device settings</string>
+    <string name="pref_title_device_spec_settings_force_time">Force synchronize time</string>
+    <string name="pref_summary_device_spec_settings_title_force_time">Force auto synchronize time on reconnect. Analog hands may show incorrect time!</string>
+    <string name="pref_title_device_spec_settings_show_raw_graph">Show RAW data on activity graph</string>
+    <!-- Device Settings - Calibration - Used in devicesettings_watchxplus.xml -->
+    <string name="pref_header_sensors_calibration">Sensor Calibration</string>
+    <string name="pref_title_sensors_altitude">Altitude calibration</string>
+    <string name="pref_sensors_bp_calibration">Blood Pressure calibration</string>
+    <string name="pref_sensors_bp_calibration_low">Blood Pressure DIASTOLIC (low)</string>
+    <string name="pref_sensors_bp_calibration_high">Blood Pressure SYSTOLIC (high)</string>
+    <string name="prefs_sensors_button_bp_calibration">Calibration</string>
+    <string name="prefs_sensors_button_bp_calibration_sum">Press here to begin calibration</string>
+    <!-- Device Settings - Power Mode -->
+    <string name="power_mode_title">Watch power mode</string>
+    <string name="power_mode_normal">Normal</string>
+    <string name="power_mode_saving">Power saving</string>
+    <string name="power_mode_watch">Only watch</string>
+    <!-- Femometer Preferences -->
+    <string name="femometer_measurement_mode_title">Measurement mode</string>
+    <string name="femometer_measurement_mode_quick">Quick Mode (30s)</string>
+    <string name="femometer_measurement_mode_normal">Normal Mode (60s-90s)</string>
+    <string name="femometer_measurement_mode_precise">Precise Mode (3min)</string>
+    <!-- Makibes HR3 Preferences -->
+    <string name="preferences_makibes_hr3_settings">Makibes HR3 settings</string>
+    <!-- ID115 Preferences -->
+    <string name="prefs_screen_orientation">Screen orientation</string>
+    <!-- ZeTime Preferences -->
+    <string name="zetime_title_settings">ZeTime settings</string>
+    <string name="zetime_title_heartrate">Heart rate settings</string>
+    <string name="zetime_title_screentime">Screen on duration in seconds</string>
+    <string name="zetime_title_heart_rate_alarm">Heart rate alarm</string>
+    <string name="zetime_title_heart_rate_alarm_summary">The watch will warn you when your heart rate exceeds the limits.</string>
+    <string name="zetime_heart_rate_alarm_enable">Enable the heart rate alarm</string>
+    <string name="activity_prefs_alarm_max_heart_rate">Max heart rate</string>
+    <string name="activity_prefs_alarm_min_heart_rate">Min heart rate</string>
+    <string name="zetime_analog_mode">Analog mode</string>
+    <string name="zetime_analog_mode_hands">Only hands</string>
+    <string name="zetime_analog_mode_handsandsteps">Hands and steps</string>
+    <string name="zetime_activity_tracking">Activity tracking</string>
+    <string name="zetime_activity_tracking_summary">Switching the activity tracking on, will count your steps and so on.</string>
+    <string name="zetime_handmove_display">Hand movement</string>
+    <string name="zetime_handmove_display_summary">Rotate your wrist to activate or deactivate the display.</string>
+    <string name="zetime_calories_type">Calories type</string>
+    <string name="zetime_calories_type_active">Only active burnt calories</string>
+    <string name="zetime_calories_type_all">Active and inactive burnt calories</string>
+    <string name="zetime_date_format">Date format</string>
+    <string name="zetime_date_format_1">YY/MM/DD</string>
+    <string name="zetime_date_format_2">DD/MM/YY</string>
+    <string name="zetime_date_format_3">MM/DD/YY</string>
+    <string name="zetime_prefs_inactivity_repetitions">Repetitions</string>
+    <string name="zetime_prefs_inactivity_mo">Monday</string>
+    <string name="zetime_prefs_inactivity_tu">Tuesday</string>
+    <string name="zetime_prefs_inactivity_we">Wednesday</string>
+    <string name="zetime_prefs_inactivity_th">Thursday</string>
+    <string name="zetime_prefs_inactivity_fr">Friday</string>
+    <string name="zetime_prefs_inactivity_sa">Saturday</string>
+    <string name="zetime_prefs_inactivity_su">Sunday</string>
+    <string name="zetime_title_alarm_signaling">Set type of signaling for the alarm</string>
+    <string name="zetime_signaling_none">Silent</string>
+    <string name="zetime_signaling_vibrate">Continuous vibration</string>
+    <string name="zetime_signaling_beep">Continuous beeping</string>
+    <string name="zetime_signaling_vibrate_beep">Continuous vibration and beeping</string>
+    <string name="zetime_signaling_vibrate_once">Vibrate once</string>
+    <string name="zetime_signaling_vibrate_twice">Vibrate twice</string>
+    <string name="zetime_signaling_beep_once">Beep once</string>
+    <string name="zetime_signaling_beep_twice">Beep twice</string>
+    <string name="zetime_signaling_vibrate_beep_once">Vibrate and beep once</string>
+    <!-- pixoo specific settings -->
+    <string name="clap_hands_to_wakeup_device">Clap hands to turn up screen</string>
+    <string name="clap_hands_to_wakeup_device_summary">Clapping again will turn off the screen</string>
+    <string name="pixoo_power_saving_summary">The screen will turn off after the microphone has detected silence for a while</string>
+    <!-- Device specific settings -->
+    <string name="title_activity_device_specific_settings">Device specific settings</string>
+    <string name="pref_title_authkey">Auth Key</string>
+    <string name="pref_summary_authkey">Change the auth key to a common key on all your Android devices from which you would like to connect from. The previous default key for all devices is 0123456789@ABCDE</string>
+    <string name="pref_explanation_authkey">Some devices require a special pairing key for the very first initialization of the device. Tap here for more details in the wiki.</string>
+    <string name="pref_explanation_authkey_new_protocol">If you get \"Update the app to latest version\" message on the band, make sure to check the \"New Auth Protocol\" above. Tap here for more details in the wiki.</string>
+    <string name="prefs_hr_alarm_activity">Heart rate alarm during sports activity</string>
+    <string name="prefs_hr_alarm_low">Low limit</string>
+    <string name="prefs_hr_alarm_high">High limit</string>
+    <string name="pref_gps_header">GPS</string>
+    <string name="pref_gps_mode_preset">GPS Mode</string>
+    <string name="pref_gps_band">GPS Band</string>
+    <string name="pref_gps_combination">GPS Combination</string>
+    <string name="pref_gps_satellite_search">Satellite Search</string>
+    <string name="pref_crown_vibration">Crown Vibration</string>
+    <string name="pref_alert_tone">Alert Tone</string>
+    <string name="pref_touch_tone">Touch Tone</string>
+    <string name="pref_touch_tone_summary">Plays a tone when the earbud is touched</string>
+    <string name="pref_wearing_tone">Wearing Tone</string>
+    <string name="pref_wearing_tone_summary">Plays a tone when the earbud is inserted</string>
+    <string name="pref_cover_to_mute">Cover to Mute</string>
+    <string name="pref_vibrate_for_alert">Vibrate for Alert</string>
+    <string name="pref_text_to_speech">Text to Speech</string>
+    <string name="offline_voice_respond_turn_wrist">Respond when turning the wrist</string>
+    <string name="offline_voice_respond_screen_on">Respond when screen on</string>
+    <string name="offline_voice_response_during_screen_lighting">Respond during screen lighting</string>
+    <string name="pref_agps_header">AGPS</string>
+    <string name="pref_agps_expiry_reminder_enabled">AGPS Expiry Reminder</string>
+    <string name="pref_agps_expiry_reminder_time">AGPS Expiry Reminder Time</string>
+    <string name="pref_agps_update_time">AGPS Update Time</string>
+    <string name="pref_agps_expire_time">AGPS Expire Time</string>
+    <string name="pref_agps_status">AGPS Status</string>
+    <string name="agps_status_missing">Missing</string>
+    <string name="agps_status_pending">Pending</string>
+    <string name="agps_status_current">Current</string>
+    <string name="agps_status_error">Error</string>
+    <string name="pref_camera_remote_title">Camera Remote</string>
+    <string name="pref_camera_remote_summary">Allows the watch to trigger the phone\'s camera</string>
+    <string name="pref_morning_updates_title">Morning Updates</string>
+    <string name="pref_morning_updates_summary">Display updates every morning</string>
+    <string name="pref_morning_updates_categories_title">Morning Updates Categories</string>
+    <string name="pref_morning_updates_categories_summary">List of categories to display every morning</string>
+    <string name="pref_workout_start_on_phone_title">Fitness app tracking</string>
+    <string name="pref_workout_start_on_phone_summary">Start/stop fitness app tracking on phone when a GPS workout is started on the band</string>
+    <string name="pref_workout_send_gps_title">Send GPS during workout</string>
+    <string name="pref_workout_send_gps_summary">Send the current GPS location to the band during a workout</string>
+    <string name="pref_workout_keep_screen_on_title">Keep screen on during a workout</string>
+    <string name="pref_workout_keep_screen_on_summary">The screen will stay on during a workout, and brightness will be adjusted to continuously display real-time workout data</string>
+    <string name="pref_workout_detection_title">Workout Detection</string>
+    <string name="pref_workout_detection_summary">Detect workout automatically</string>
+    <string name="pref_workout_detection_categories_title">Workout Categories</string>
+    <string name="pref_workout_detection_categories_summary">Workouts categories to detect automatically</string>
+    <string name="pref_workout_detection_alert_title">Alert</string>
+    <string name="pref_workout_detection_alert_summary">Notify when a workout is detected</string>
+    <string name="pref_workout_detection_sensitivity">Sensitivity</string>
+    <string name="pref_workout_detection_enabled">Detection enabled</string>
+    <string name="pref_workout_detection_enabled_summary">Enable automatic detection of this workout</string>
+    <string name="pref_workout_detection_ask_first">Ask me first</string>
+    <string name="pref_workout_detection_ask_first_summary">Require on-watch confirmation when detecting this workout</string>
+    <string name="pref_workout_detection_time">Active minutes before detection</string>
+    <string name="pref_workout_detection_time_summary">The amount of minutes the workout must be ongoing before detecting it</string>
+    <string name="pref_sleep_mode_title">Sleep Mode</string>
+    <string name="pref_sleep_mode_sleep_screen_title">Sleep Screen</string>
+    <string name="pref_sleep_mode_sleep_screen_summary">Show the Sleep Screen when waking the screen during sleep mode, to reduce distractions</string>
+    <string name="pref_sleep_mode_smart_enable_title">Smart Enable</string>
+    <string name="pref_sleep_mode_smart_enable_summary">Enable the sleep mode automatically when wearing the band during sleep</string>
+    <!-- Auto export preferences -->
+    <string name="pref_header_auto_export">Auto export</string>
+    <string name="pref_title_auto_export_enabled">Auto export enabled</string>
+    <string name="pref_title_auto_export_location">Export location</string>
+    <string name="pref_title_auto_export_interval">Export interval</string>
+    <string name="pref_summary_auto_export_interval">Export every %d hour</string>
+    <!-- Auto fetch activity preferences -->
+    <string name="pref_header_auto_fetch">Auto fetch</string>
+    <string name="pref_auto_fetch">Auto fetch activity data</string>
+    <string name="pref_auto_fetch_summary">Fetch happens upon screen unlock. Only works if a lock mechanism is set!</string>
+    <string name="pref_auto_fetch_limit_fetches">Minimum time between fetches</string>
+    <string name="pref_auto_fetch_limit_fetches_summary">Fetches every %d minutes</string>
+    <!-- developer/debug preferences-->
+    <string name="pref_disable_new_ble_scanning">Disable new BLE scanning</string>
+    <string name="pref_summary_disable_new_ble_scanning">Check this option if your device cannot be found during discovery</string>
+    <string name="not_connected">Not connected</string>
+    <string name="connecting">Connecting</string>
+    <string name="connected">Connected</string>
+    <string name="unknown_state">Unknown state</string>
+    <string name="_unknown_">(unknown)</string>
+    <string name="unknown">Unknown</string>
+    <string name="test">Test</string>
+    <string name="test_notification">Test notification</string>
+    <string name="this_is_a_test_notification_from_gadgetbridge">This is a test notification from Gadgetbridge</string>
+    <string name="find_my_phone_notification">Find my phone</string>
+    <string name="find_my_phone_companion_warning">Companion pairing is required for find phone. Click here for more information.</string>
+    <string name="bluetooth_is_not_supported_">Bluetooth is not supported.</string>
+    <string name="bluetooth_is_disabled_">Bluetooth is disabled.</string>
+    <string name="tap_connected_device_for_app_mananger">Tap connected device for App manager</string>
+    <string name="tap_connected_device_for_activity">Tap connected device for activity</string>
+    <string name="tap_connected_device_for_vibration">Tap connected device for vibration</string>
+    <string name="tap_a_device_to_connect">Tap a device to connect</string>
+    <string name="cannot_connect_bt_address_invalid_">Cannot connect. Bluetooth address invalid?</string>
+    <string name="installing_binary_d_d">Installing binary %1$d/%2$d</string>
+    <string name="installation_failed_">Installation failed</string>
+    <string name="installation_successful">Installed</string>
+    <string name="firmware_install_warning">YOU ARE TRYING TO INSTALL A FIRMWARE, PROCEED AT YOUR OWN RISK.\n\n\n This firmware is for HW Revision: %s</string>
+    <string name="app_install_info">You are about to install the following app:\n\n%1$s\nVersion %2$s by %3$s\n</string>
+    <string name="watchface_install_info">You are about to install the following watchface:\n\n%1$s\nVersion %2$s by %3$s\n</string>
+    <string name="music_upload_info">You are about to upload the following music file:\n\n%1$s\nSong title: %2$s\nAlbum: %3$s\n</string>
+    <string name="n_a">N/A</string>
+    <string name="initialized">initialized</string>
+    <string name="appversion_by_creator">%1$s by %2$s</string>
+    <string name="title_activity_discovery">Device discovery</string>
+    <string name="discovery_stop_scanning">Stop scanning</string>
+    <string name="discovery_start_scanning">Start discovery</string>
+    <string name="discovery_bluetooth_scan">Bluetooth scan:</string>
+    <string name="discovery_bluetooth_le_scan">Bluetooth LE scan:</string>
+    <string name="action_discover">Connect new device</string>
+    <string name="device_with_rssi">%1$s (%2$s)</string>
+    <string name="title_activity_android_pairing">Pair device</string>
+    <string name="android_pairing_hint">Use the Android Bluetooth pairing dialog to pair the device.</string>
+    <string name="title_activity_mi_band_pairing">Pair your Mi Band</string>
+    <string name="pairing">Pairing with %s鈥�</string>
+    <string name="choose_device">Choose a device</string>
+    <string name="no_supported_devices_found">No supported devices found</string>
+    <string name="pairing_creating_bond_with">"Creating bond with %1$s (%2$s)"</string>
+    <string name="pairing_unable_to_pair_with">"Unable to pair with %1$s (%2$s)"</string>
+    <string name="pairing_in_progress">Bonding in progress: %1$s (%2$s)</string>
+    <string name="pairing_already_bonded">"Already bonded with %1$s (%2$s), connecting鈥�"</string>
+    <string name="message_cannot_pair_no_mac">No MAC address passed, cannot pair.</string>
+    <string name="preferences_category_device_specific_settings">Device specific settings</string>
+    <string name="preferences_miband_1_2_settings" tools:ignore="TypographyFractions">Mi Band 1/2 settings</string>
+    <string name="preferences_miband_1_2_warning">Warning: These preferences only apply to the Mi Bands 1 and 2.</string>
+    <string name="male">Male</string>
+    <string name="female">Female</string>
+    <string name="other">Other</string>
+    <string name="left">Left</string>
+    <string name="right">Right</string>
+    <string name="horizontal">Horizontal</string>
+    <string name="vertical">Vertical</string>
+    <string name="buttons_on_left">Buttons on left</string>
+    <string name="buttons_on_right">Buttons on right</string>
+    <string name="wearmode_band">Band (wristband)</string>
+    <string name="wearmode_pebble">Pebble (shoe buckle)</string>
+    <string name="wearmode_necklace">Necklace (neck strap)</string>
+    <string name="miband_pairing_using_dummy_userdata">No valid user data given, using dummy user data for now.</string>
+    <string name="miband_pairing_tap_hint">When your Mi Band vibrates and blinks, tap it a few times in a row.</string>
+    <string name="appinstaller_install">Install</string>
+    <string name="discovery_connected_devices_hint">Make your device discoverable. Currently connected devices will likely not be discovered. Activate location (e.g. GPS) on Android 6+. Disable Privacy Guard for Gadgetbridge, because it may crash and reboot your phone. If no device is found after a few minutes, try again after rebooting your mobile device.</string>
+    <string name="discovery_need_to_enter_authkey">This device needs a secret auth key, long press on the device to enter it. Read the wiki.</string>
+    <string name="discovery_entered_invalid_authkey">The secret auth key you entered is invalid! Long press on the device to edit.</string>
+    <string name="discovery_note">Note:</string>
+    <string name="candidate_item_device_image">Device image</string>
+    <string name="miband_prefs_alias">Name/Alias</string>
+    <string name="pref_header_vibration_count">Vibration count</string>
+    <string name="watch9_pairing_tap_hint">When your watch vibrates, shake the device or press its button.</string>
+    <string name="title_activity_sleepmonitor">Sleep monitor</string>
+    <string name="pref_write_logfiles">Write log files</string>
+    <string name="pref_cache_weather">Cache weather information</string>
+    <string name="pref_cache_weather_summary">Weather information will be cached across application restarts.</string>
+    <string name="pref_write_logfiles_not_available">File logging initialization failed, writing log files is currently not available. Restart the application to attempt to initialize the log files again.</string>
+    <string name="initializing">Initializing</string>
+    <string name="busy_task_fetch_activity_data">Fetching activity data</string>
+    <string name="busy_task_fetch_sports_summaries">Fetching sports summaries</string>
+    <string name="busy_task_fetch_sports_details">Fetching sports details</string>
+    <string name="busy_task_fetch_sports_details_interrupted">Fetching sports details was interrupted</string>
+    <string name="busy_task_fetch_debug_logs">Fetching debug logs</string>
+    <string name="busy_task_fetch_stress_data">Fetching stress data</string>
+    <string name="busy_task_fetch_pai_data">Fetching PAI data</string>
+    <string name="busy_task_fetch_spo2_data">Fetching SpO2 data</string>
+    <string name="busy_task_fetch_hrv_data">Fetching HRV data</string>
+    <string name="busy_task_fetch_hr_data">Fetching heart rate data</string>
+    <string name="busy_task_fetch_sleep_data">Fetching sleep data</string>
+    <string name="busy_task_fetch_sleep_respiratory_rate_data">Fetching sleep respiratory rate data</string>
+    <string name="busy_task_fetch_temperature">Fetching temperature data</string>
+    <string name="busy_task_fetch_statistics">Fetching statistics</string>
+    <string name="sleep_activity_date_range">From %1$s to %2$s</string>
+    <string name="prefs_wearside">Wearing left or right?</string>
+    <string name="prefs_weardirection">Wearing direction</string>
+    <string name="prefs_wearmode">Wearing mode</string>
+    <string name="pref_screen_vibration_profile">Vibration profile</string>
+    <string name="vibration_profile_default">Default</string>
+    <string name="vibration_profile_staccato">Staccato</string>
+    <string name="vibration_profile_short">Short</string>
+    <string name="vibration_profile_medium">Medium</string>
+    <string name="vibration_profile_long">Long</string>
+    <string name="vibration_profile_waterdrop">Waterdrop</string>
+    <string name="vibration_profile_ring">Ring</string>
+    <string name="vibration_profile_alarm_clock">Alarm clock</string>
+    <string name="miband_prefs_vibration">Vibration</string>
+    <string name="vibration_try">Try</string>
+    <string name="pref_screen_notification_profile_sms">SMS notification</string>
+    <string name="pref_header_vibration_settings">Vibration settings</string>
+    <string name="pref_screen_notification_profile_generic">Generic notification</string>
+    <string name="pref_screen_notification_profile_email">E-mail notification</string>
+    <string name="pref_screen_notification_profile_incoming_call">Incoming call notification</string>
+    <string name="pref_screen_notification_profile_missed_call">Missed call notification</string>
+    <string name="pref_screen_notification_profile_generic_chat">Chat</string>
+    <string name="pref_screen_notification_profile_generic_navigation">Navigation</string>
+    <string name="pref_screen_notification_profile_generic_social">Social network</string>
+    <string name="pref_screen_notification_profile_calendar">Calendar notification</string>
+    <string name="pref_screen_notification_profile_inactivity">Inactivity notification</string>
+    <string name="pref_screen_notification_profile_low_power">Low power warning</string>
+    <string name="pref_screen_notification_profile_anti_loss">Anti-loss warning</string>
+    <string name="pref_screen_notification_profile_schedule">Schedule</string>
+    <string name="pref_screen_notification_profile_todo_list">To-Do List</string>
+    <string name="prefs_title_heartrate_measurement_interval">Whole day HR measurement</string>
+    <string name="pref_screen_notification_profile_event_reminder">Event reminder</string>
+    <string name="pref_screen_notification_profile_find_device">Find device</string>
+    <string name="pref_screen_notification_idle_alerts">Idle Alerts</string>
+    <string name="pref_screen_vibration_patterns_title">Vibration Patterns</string>
+    <string name="pref_screen_vibration_patterns_summary">Configure the vibration patterns for different notifications</string>
+    <string name="interval_one_minute">once a minute</string>
+    <string name="interval_five_minutes">every 5 minutes</string>
+    <string name="interval_ten_minutes">every 10 minutes</string>
+    <string name="interval_fifteen_minutes">every 15 minutes</string>
+    <string name="interval_thirty_minutes">every 30 minutes</string>
+    <string name="interval_forty_five_minutes">every 45 minutes</string>
+    <string name="heartrate_bpm_40">40 bpm</string>
+    <string name="heartrate_bpm_45">45 bpm</string>
+    <string name="heartrate_bpm_50">50 bpm</string>
+    <string name="heartrate_bpm_100">100 bpm</string>
+    <string name="heartrate_bpm_105">105 bpm</string>
+    <string name="heartrate_bpm_110">110 bpm</string>
+    <string name="heartrate_bpm_115">115 bpm</string>
+    <string name="heartrate_bpm_120">120 bpm</string>
+    <string name="heartrate_bpm_125">125 bpm</string>
+    <string name="heartrate_bpm_130">130 bpm</string>
+    <string name="heartrate_bpm_135">135 bpm</string>
+    <string name="heartrate_bpm_140">140 bpm</string>
+    <string name="heartrate_bpm_145">145 bpm</string>
+    <string name="heartrate_bpm_150">150 bpm</string>
+    <string name="heartrate_bpm_155">155 bpm</string>
+    <string name="heartrate_bpm_165">165 bpm</string>
+    <string name="heartrate_bpm_175">175 bpm</string>
+    <string name="heartrate_bpm_185">185 bpm</string>
+    <string name="heartrate_bpm_195">195 bpm</string>
+    <string name="heartrate_bpm_205">205 bpm</string>
+    <string name="spo2_perc_80">80%</string>
+    <string name="spo2_perc_85">85%</string>
+    <string name="spo2_perc_90">90%</string>
+    <string name="spo2_off">Off</string>
+    <string name="interval_one_hour">once an hour</string>
+    <string name="stats_title">Speed zones</string>
+    <string name="stats_x_axis_label">Total minutes</string>
+    <string name="stats_y_axis_label">Steps per minute</string>
+    <string name="control_center_find_lost_device">Find lost device</string>
+    <string name="control_center_cancel_to_stop_vibration">Cancel to stop vibration.</string>
+    <string name="title_activity_charts">Activity and Sleep</string>
+    <string name="title_activity_set_alarm">Configure alarms</string>
+    <string name="title_activity_set_reminders">Configure reminders</string>
+    <string name="title_activity_set_contacts">Configure contacts</string>
+    <string name="pref_world_clocks_title">World Clocks</string>
+    <string name="pref_world_clocks_summary">Configure clocks for other timezones</string>
+    <string name="pref_contacts_title">Contacts</string>
+    <string name="pref_contacts_summary">Configure contacts on the watch</string>
+    <string name="controlcenter_start_configure_alarms">Configure alarms</string>
+    <string name="controlcenter_start_configure_reminders">Configure reminders</string>
+    <string name="reminder_repeat">Repeat</string>
+    <string name="reminder_date">Date</string>
+    <string name="reminder_time">Time</string>
+    <string name="reminder_message">Message</string>
+    <string name="reminder_time_once">%1$s, once</string>
+    <string name="reminder_time_every_day">%1$s, every day</string>
+    <string name="reminder_time_every_week">%1$s, every week</string>
+    <string name="reminder_time_every_month">%1$s, every month</string>
+    <string name="reminder_time_every_year">%1$s, every year</string>
+    <string name="reminder_once">Once</string>
+    <string name="reminder_every_day">Every day</string>
+    <string name="reminder_every_week">Every week</string>
+    <string name="reminder_every_month">Every month</string>
+    <string name="reminder_every_year">Every year</string>
+    <string name="reminder_delete_confirm_title">Delete reminder</string>
+    <string name="reminder_delete_confirm_description">Are you sure you want to delete the reminder?</string>
+    <string name="reminder_no_free_slots_title">No free slots</string>
+    <string name="reminder_no_free_slots_description">The device has no free slots for reminders (total slots: %1$s)</string>
+    <string name="contact_delete_confirm_title">Delete contact</string>
+    <string name="contact_delete_confirm_description">Are you sure you want to delete \'%1$s\'?</string>
+    <string name="contact_no_free_slots_description">The device has no free slots for contacts (total slots: %1$s)</string>
+    <string name="world_clock_delete_confirm_title">Delete \'%1$s\'</string>
+    <string name="world_clock_delete_confirm_description">Are you sure you want to delete the world clock?</string>
+    <string name="world_clock_no_free_slots_title">No free slots</string>
+    <string name="world_clock_no_free_slots_description">The device has no free slots for world clocks (total slots: %1$s)</string>
+    <string name="function_enabled">Enabled</string>
+    <string name="world_clock_timezone">Time Zone</string>
+    <string name="world_clock_label">Label</string>
+    <string name="world_clock_code">Code</string>
+    <string name="title_activity_alarm_details">Alarm details</string>
+    <string name="title_activity_reminder_details">Reminder details</string>
+    <string name="title_activity_contact_details">Contact details</string>
+    <string name="title_activity_world_clock_details">World Clock details</string>
+    <string name="alarm_sun_short">Sun</string>
+    <string name="alarm_mon_short">Mon</string>
+    <string name="alarm_tue_short">Tue</string>
+    <string name="alarm_wed_short">Wed</string>
+    <string name="alarm_thu_short">Thu</string>
+    <string name="alarm_fri_short">Fri</string>
+    <string name="alarm_sat_short">Sat</string>
+    <string name="alarm_smart_wakeup">Smart wakeup</string>
+    <string name="alarm_smart_wakeup_interval">Smart wakeup interval:</string>
+    <string name="alarm_smart_wakeup_interval_default">5 minutes</string>
+    <string name="alarm_snooze">Snooze</string>
+    <string name="user_feedback_miband_set_alarms_failed">There was an error setting the alarms, please try again.</string>
+    <string name="user_feedback_miband_set_alarms_ok">Alarms sent to device.</string>
+    <string name="user_feedback_set_settings_ok">Settings sent to device.</string>
+    <string name="chart_no_data_synchronize">No data. Synchronize device?</string>
+    <string name="chart_no_active_data">No activities detected.</string>
+    <string name="chart_get_active_and_synchronize">Do some activity and synchronize device.</string>
+    <string name="user_feedback_miband_activity_data_transfer">About to transfer %1$s of data starting from %2$s</string>
+    <string name="miband_prefs_fitness_goal">Daily step target</string>
+    <string name="prefs_heartrate_alert_experimental_title">Heart rate alert (experimental)</string>
+    <string name="prefs_heartrate_alert_experimental_description">Vibrate the band when the heart rate is over a threshold, without any obvious physical activity in the last 10 minutes. This feature is experimental, and was not extensively tested.</string>
+    <string name="prefs_heartrate_alert_threshold">Heart rate alert threshold</string>
+    <string name="prefs_heartrate_alert_high_threshold">High heart rate alert threshold</string>
+    <string name="prefs_heartrate_alert_active_high_threshold">High activity heart rate alert threshold</string>
+    <string name="prefs_heartrate_alert_low_threshold">Low heart rate alert threshold</string>
+    <string name="prefs_stress_monitoring_title">Stress monitoring</string>
+    <string name="prefs_stress_monitoring_description">Monitor stress level while resting</string>
+    <string name="prefs_relaxation_reminder_title">Relaxation reminder</string>
+    <string name="prefs_relaxation_reminder_description">Vibrate the band to notify you if the stress value is higher than 80</string>
+    <string name="prefs_spo2_monitoring_title">Blood Oxygen Monitoring</string>
+    <string name="prefs_spo2_monitoring_description">Automatically monitor the blood oxygen levels throughout the day</string>
+    <string name="prefs_spo2_alert_threshold">SPO2 alert threshold</string>
+    <string name="prefs_activity_monitoring_title">Activity monitoring</string>
+    <string name="prefs_activity_monitoring_description">Automatically increase the heart rate detection frequency when the band detects physical exercise, to increase heart rate capture accuracy.</string>
+    <string name="dbaccess_error_executing">Error executing \'%1$s\'</string>
+    <string name="controlcenter_start_activitymonitor">Your activity</string>
+    <string name="cannot_connect">Cannot connect: %1$s</string>
+    <string name="installer_activity_unable_to_find_handler">Unable to find a handler to install this file.</string>
+    <string name="pbw_install_handler_unable_to_install">Unable to install the given file: %1$s</string>
+    <string name="pbw_install_handler_hw_revision_mismatch">Unable to install the given firmware: It doesn\'t match your Pebble\'s hardware revision.</string>
+    <string name="installer_activity_wait_while_determining_status">Please wait while determining the installation status鈥�</string>
+    <string name="notif_battery_low_title">Gadget battery Low!</string>
+    <string name="notif_battery_full_title">Gadget battery Full!</string>
+    <string name="notif_battery_low_percent">%1$s battery left: %2$s%%</string>
+    <string name="notif_battery_low_bigtext_last_charge_time">Last charge: %s \n</string>
+    <string name="notif_battery_low_bigtext_number_of_charges">Number of charges: %s</string>
+    <string name="notif_battery_low">%1$s battery low</string>
+    <string name="notif_battery_full">%1$s battery full</string>
+    <string name="notif_battery_low_extended">%1$s battery low: %2$s</string>
+    <string name="notif_export_failed_title">Export database failed! Please check your settings.</string>
+    <string name="prefs_charts_tabs">Charts tabs</string>
+    <string name="prefs_charts_tabs_summary">Visible chart tabs</string>
+    <string name="sleepchart_your_sleep">Sleep</string>
+    <string name="hrv_status">HRV Status</string>
+    <string name="hrv">HRV</string>
+    <string name="weeksleepchart_sleep_a_week">Sleep per week</string>
+    <string name="weeksleepchart_today_sleep_description">Sleep today, target: %1$s</string>
+    <string name="weekstepschart_steps_a_week">Steps per week</string>
+    <string name="pai_chart_per_week">PAI per week</string>
+    <string name="pai_chart_per_month">PAI per month</string>
+    <string name="pai_plus_num">+%d</string>
+    <string name="num_min">%d min</string>
+    <string name="activity_sleepchart_activity_and_sleep">Activity</string>
+    <string name="charts_activity_list">Activity list</string>
+    <string name="activity_list_summary_active_steps">Active steps</string>
+    <string name="activity_list_summary_distance">Distance</string>
+    <string name="activity_list_summary_active_time">Active time</string>
+    <string name="activity_list_summary_intensity">Movement\nIntensity</string>
+    <string name="activity_list_summary_activities">Activities</string>
+    <string name="dialog_hide">Hide</string>
+    <string name="show_ongoing_activity">Show ongoing activity popup</string>
+    <string name="lack_of_sleep">Lack of sleep: %1$s</string>
+    <string name="overslept">Overslept: %1$s</string>
+    <string name="prefs_sounds">Sounds</string>
+    <string name="prefs_sounds_summary">Configure when the device will beep</string>
+    <!-- Firmware updating -->
+    <string name="devicestatus_connecting">Device is connecting</string>
+    <string name="devicestatus_connected">Device is connected</string>
+    <string name="devicestatus_upload_starting">Upload is starting</string>
+    <string name="devicestatus_upload_started">Upload has started</string>
+    <string name="devicestatus_disconnecting">Device is disconnecting!</string>
+    <string name="devicestatus_disconnected">Device has disconnected!</string>
+    <string name="devicestatus_upload_completed">Upload has completed</string>
+    <string name="devicestatus_upload_validating">Upload is being validated</string>
+    <string name="devicestatus_upload_aborted">Upload has been aborted!</string>
+    <string name="devicestatus_upload_failed">Upload has failed</string>
+    <string name="firmware_update_progress">Upload is in progress\n%1d%% at %.2fkbps (average %.2fkbps)\nPart %1d of %1d</string>
+    <string name="updating_firmware">Flashing firmware鈥�</string>
+    <string name="fwapp_install_device_not_supported">File cannot be installed, device not supported.</string>
+    <string name="fwapp_install_device_not_ready">File cannot be installed, device not ready.</string>
+    <string name="installhandler_firmware_name">%1$s: %2$s %3$s</string>
+    <string name="miband_fwinstaller_compatible_version">Compatible version</string>
+    <string name="miband_fwinstaller_untested_version">Untested version!</string>
+    <string name="fwappinstaller_connection_state">Connection to device: %1$s</string>
+    <string name="pbw_installhandler_pebble_firmware">Pebble Firmware %1$s</string>
+    <string name="pbwinstallhandler_correct_hw_revision">Correct hardware revision</string>
+    <string name="pbwinstallhandler_incorrect_hw_revision">Hardware revision mismatch!</string>
+    <string name="pbwinstallhandler_app_item">%1$s (%2$s)</string>
+    <string name="updatefirmwareoperation_updateproblem_do_not_reboot">Problem with the firmware transfer. DO NOT REBOOT your Mi Band!</string>
+    <string name="updatefirmwareoperation_metadata_updateproblem">Problem with the firmware metadata transfer</string>
+    <string name="updatefirmwareoperation_updateproblem_free_space">The device does not have enough free space</string>
+    <string name="updatefirmwareoperation_updateproblem_low_battery">The device battery is too low</string>
+    <string name="updatefirmwareoperation_update_complete">Firmware installation complete</string>
+    <string name="updatefirmwareoperation_update_complete_rebooting">Firmware installation complete, rebooting device鈥�</string>
+    <string name="updatefirmwareoperation_write_failed">Firmware flashing failed</string>
+    <string name="gpx_route_upload_failed">Gpx route upload failed</string>
+    <string name="gpx_route_upload_complete">Gpx route upload complete</string>
+    <string name="gpx_route_upload_in_progress">Uploading gpx route</string>
+    <string name="updatefirmwareoperation_failed_low_mtu">Current MTU of %1$d is too low, please enable high MTU in the device settings and disconnect/re-connect the device.</string>
+    <string name="chart_steps">Steps</string>
+    <string name="calories">Calories</string>
+    <string name="respiratoryrate">Respiratory Rate</string>
+    <string name="active_calories">Active calories</string>
+    <string name="distance">Distance</string>
+    <string name="clock">Clock</string>
+    <string name="heart_rate">Heart rate</string>
+    <string name="hr_resting">Resting</string>
+    <string name="hr_maximum">Maximum</string>
+    <string name="hr_minimum">Minimum</string>
+    <string name="hr_average">Average</string>
+    <string name="active_calories_short">Active</string>
+    <string name="active_calories_goal">Active goal</string>
+    <string name="total_calories_burnt">Total burnt</string>
+    <string name="blood_pressure">Blood pressure</string>
+    <string name="getting_heart_rate">Measuring</string>
+    <string name="heart_rate_result">Measurement results</string>
+    <string name="movement_intensity">Movement intensity</string>
+    <string name="battery">Battery</string>
+    <string name="no_limit">No limit</string>
+    <string name="seconds_5">5 seconds</string>
+    <string name="seconds_6">6 seconds</string>
+    <string name="seconds_7">7 seconds</string>
+    <string name="seconds_8">8 seconds</string>
+    <string name="seconds_9">9 seconds</string>
+    <string name="seconds_10">10 seconds</string>
+    <string name="seconds_11">11 seconds</string>
+    <string name="seconds_12">12 seconds</string>
+    <string name="seconds_13">13 seconds</string>
+    <string name="seconds_14">14 seconds</string>
+    <string name="seconds_15">15 seconds</string>
+    <string name="seconds_20">20 seconds</string>
+    <string name="seconds_25">25 seconds</string>
+    <string name="seconds_30">30 seconds</string>
+    <string name="minutes_1">1 minute</string>
+    <string name="minutes_2">2 minutes</string>
+    <string name="minutes_3">3 minutes</string>
+    <string name="minutes_4">4 minutes</string>
+    <string name="minutes_5">5 minutes</string>
+    <string name="minutes_6">6 minutes</string>
+    <string name="minutes_7">7 minutes</string>
+    <string name="minutes_8">8 minutes</string>
+    <string name="minutes_9">9 minutes</string>
+    <string name="minutes_10">10 minutes</string>
+    <string name="minutes_15">15 minutes</string>
+    <string name="minutes_20">20 minutes</string>
+    <string name="minutes_30">30 minutes</string>
+    <string name="minutes_45">45 minutes</string>
+    <string name="minutes_60">60 minutes</string>
+    <string name="minutes_75">75 minutes</string>
+    <string name="minutes_90">90 minutes</string>
+    <string name="minutes_120">120 minutes</string>
+    <string name="minutes_150">150 minutes</string>
+    <string name="minutes_180">180 minutes</string>
+    <string name="minutes_210">210 minutes</string>
+    <string name="minutes_240">240 minutes</string>
+    <string name="minutes_255">255 minutes</string>
+    <string name="liveactivity_live_activity">Live activity</string>
+    <string name="weeksteps_today_steps_description">Steps today, target: %1$s</string>
+    <string name="lack_of_step">Lack of steps: %1$d</string>
+    <string name="overstep">Overstep: %1$d</string>
+    <string name="average">Average: %1$s</string>
+    <string name="stress_average">Average</string>
+    <string name="pref_huami_truncate_fetch_operation_timestamps_title">Truncate fetch operation timestamps</string>
+    <string name="pref_huami_truncate_fetch_operation_timestamps_summary">Truncate the fetch operation timestamps to minutes. Disable this setting to keep the timestamps in seconds, if you face issues while fetching very short workouts.</string>
+    <string name="pref_title_dont_ack_transfer">Do not ACK activity data transfer</string>
+    <string name="pref_summary_dont_ack_transfers">If not ACKed to the band, activity data is not cleared. Useful if GB is used together with other apps.</string>
+    <string name="pref_summary_keep_data_on_device">Will keep activity data on the device even after synchronization. Useful if GB is used together with other apps. This may cause the watch to run out of space and/or stop syncing properly.</string>
+    <string name="pref_enable_unsupported_settings_title">Enable unsupported settings</string>
+    <string name="pref_enable_unsupported_settings_summary">This will enable access to all available settings, even if unsupported by the device. This can cause instability and crashes on the device.</string>
+    <string name="pref_title_low_latency_fw_update">Use low-latency mode for firmware flashing</string>
+    <string name="pref_summary_low_latency_fw_update">This might help on devices where firmware flashing fails.</string>
+    <string name="pref_title_third_party_app_device_settings">Allow 3rd party apps to change settings</string>
+    <string name="pref_summary_third_party_app_device_settings">Allow other installed 3rd party apps to set device settings through intents.</string>
+    <string name="live_activity_steps_history">Steps history</string>
+    <string name="live_activity_current_steps_per_minute">Current steps/min</string>
+    <string name="live_activity_total_steps">Total steps</string>
+    <string name="live_activity_steps_per_minute_history">Steps per minute history</string>
+    <string name="live_activity_start_your_activity">Start your activity</string>
+    <string name="live_activity_max_heart_rate">Current / Max heart rate: %1$d / %2$d</string>
+    <string name="abstract_chart_fragment_kind_activity">Activity</string>
+    <string name="abstract_chart_fragment_kind_light_sleep">Light sleep</string>
+    <string name="abstract_chart_fragment_kind_deep_sleep">Deep sleep</string>
+    <string name="abstract_chart_fragment_kind_rem_sleep">REM sleep</string>
+    <string name="abstract_chart_fragment_kind_awake_sleep">Awake</string>
+    <string name="abstract_chart_fragment_kind_not_worn">Not worn</string>
+    <string name="sleep_colored_stats_deep">Deep</string>
+    <string name="sleep_colored_stats_light">Light</string>
+    <string name="sleep_colored_stats_rem">REM</string>
+    <string name="sleep_colored_stats_deep_avg">Deep AVG</string>
+    <string name="sleep_colored_stats_light_avg">Light AVG</string>
+    <string name="sleep_colored_stats_rem_avg">REM AVG</string>
+    <string name="sleep_colored_stats_awake_avg">Awake AVG</string>
+    <string name="sleep_avg">Sleep AVG</string>
+    <string name="lowest">Lowest</string>
+    <string name="highest">Highest</string>
+    <string name="sleep_score">Sleep score</string>
+    <string name="sleep_score_value">Score: %1d</string>
+    <string name="stats_empty_value">-</string>
+    <string name="time_empty_value" translatable="false">--:--</string>
+    <string name="date_placeholders__date__time">%1s, %1s</string>
+    <string name="date_placeholders__start_time__end_time">%1s - %2s</string>
+    <string name="stats_lowest_hr">Lowest HR</string>
+    <string name="stats_highest_hr">Highest HR</string>
+    <string name="transition">Transition</string>
+    <string name="you_slept">%1$s - %2$s</string>
+    <string name="you_did_not_sleep">You did not sleep</string>
+    <string name="charts_min_max_heartrate_popup">Lowest heart rate: %1$d \nHighest heart rate: %2$d \nMovement intensity: %3$s</string>
+    <string name="device_not_connected">Not connected.</string>
+    <string name="user_feedback_all_alarms_disabled">All alarms disabled</string>
+    <string name="pref_title_keep_data_on_device">Keep activity data on device</string>
+    <string name="miband_fwinstaller_incompatible_version">Incompatible firmware</string>
+    <string name="fwinstaller_firmware_not_compatible_to_device">This firmware is not compatible with the device</string>
+    <string name="miband_prefs_reserve_alarm_calendar">Alarms to reserve for upcoming events</string>
+    <string name="miband_prefs_reserve_reminder_calendar">Reminders to reserve for upcoming events</string>
+    <string name="prefs_reserve_reminder_calendar_summary">Number of calendar events that will be synchronized</string>
+    <string name="miband_prefs_hr_sleep_detection">Use heart rate sensor to improve sleep detection</string>
+    <string name="pref_sleep_breathing_quality_monitoring">Sleep breathing quality monitoring</string>
+    <string name="miband_prefs_device_time_offset_hours">Device time offset in hours (for detecting sleep of shift workers)</string>
+    <string name="prefs_find_phone">Find phone</string>
+    <string name="prefs_enable_find_phone">Turn on \'Find phone\'</string>
+    <string name="prefs_find_phone_summary">Use your band to play your phone\'s ringtone.</string>
+    <string name="prefs_find_phone_duration">Ring duration in seconds</string>
+    <string name="miband2_prefs_dateformat">Date format</string>
+    <string name="dateformat_time">Time</string>
+    <string name="dateformat_date_time"><![CDATA[Time & date]]></string>
+    <string name="prefs_disconnect_notification">Disconnect notification</string>
+    <string name="prefs_disconnect_notification_summary">Notification on device when disconnected from BT.</string>
+    <string name="mi2_prefs_button_actions">Button actions</string>
+    <string name="mi2_prefs_button_actions_summary">Specify button press actions</string>
+    <string name="mi2_prefs_button_press_count">Button press count</string>
+    <string name="mi2_prefs_button_press_count_summary">Number of button presses to trigger an Event 1. Subsequent same amount of presses create Event 2 and so on.</string>
+    <string name="mi2_prefs_button_press_broadcast">Broadcast message to send</string>
+    <string name="mi2_prefs_button_press_broadcast_summary">Broadcast message sent with the event. Parameter `button_id` is added automatically to each message.</string>
+    <string name="mi2_prefs_button_press_broadcast_default_value" translatable="false">nodomain.freeyourgadget.gadgetbridge.ButtonPressed</string>
+    <string name="mi2_prefs_button_action">Enable button action</string>
+    <string name="mi2_prefs_button_action_summary">Enable action on specified number of button presses</string>
+    <string name="mi2_prefs_button_action_vibrate">Enable band vibration</string>
+    <string name="mi2_prefs_button_action_vibrate_summary">Enable band vibration on button action triggered</string>
+    <string name="mi2_prefs_button_press_count_max_delay">Maximum delay between presses</string>
+    <string name="mi2_prefs_button_press_count_max_delay_summary">Maximum delay between button presses in milliseconds</string>
+    <string name="mi2_prefs_goal_notification">Goal notification</string>
+    <string name="mi2_prefs_goal_notification_summary">The band will vibrate when the daily steps goal is reached</string>
+    <string name="mi2_prefs_display_items">Display items</string>
+    <string name="mi2_prefs_display_items_summary">Choose the items displayed on the band screen</string>
+    <string name="mi2_prefs_activate_display_on_lift">Activate display upon lift</string>
+    <string name="mi2_prefs_rotate_wrist_to_switch_info">Rotate wrist to switch info</string>
+    <string name="mi2_prefs_do_not_disturb">Do Not Disturb</string>
+    <string name="mi2_prefs_do_not_disturb_summary">The band won\'t receive notifications while active</string>
+    <string name="mi2_prefs_inactivity_warnings">Inactivity warnings</string>
+    <string name="mi2_prefs_inactivity_warnings_summary">The band will vibrate when you have been inactive for a while</string>
+    <string name="mi2_prefs_inactivity_warnings_threshold">Inactivity threshold (in minutes)</string>
+    <string name="mi2_prefs_inactivity_warnings_dnd_summary">Disable inactivity warnings for a time interval</string>
+    <string name="hydration_dnd_summary">Disable hydration warnings for a time interval</string>
+    <string name="mi2_prefs_heart_rate_monitoring">Heart Rate Monitoring</string>
+    <string name="mi2_prefs_heart_rate_monitoring_summary">Configure heart rate monitoring</string>
+    <string name="prefs_phone_silent_mode">Phone Silent Mode</string>
+    <string name="silent_mode_normal_vibrate">Normal / Vibrate</string>
+    <string name="silent_mode_normal_silent">Normal / Silent</string>
+    <string name="silent_mode_vibrate_silent">Vibrate / Silent</string>
+    <string name="prefs_always_on_display">Always On Display</string>
+    <string name="prefs_always_on_display_follow_watchface">Style follows Watchface</string>
+    <string name="prefs_always_on_display_style">Style</string>
+    <string name="prefs_always_on_display_summary">Keep the band\'s display always on</string>
+    <string name="prefs_device_name">Device name</string>
+    <string name="prefs_password">Password</string>
+    <string name="prefs_password_summary">Lock the band with a password when removed from the wrist</string>
+    <string name="prefs_password_enabled">Password Enabled</string>
+    <string name="prefs_password_4_digits_1_to_4_summary">The password must have 4 digits, using numbers 1 to 4</string>
+    <string name="prefs_password_4_digits_0_to_9_summary">The password must have 4 digits, using only numbers</string>
+    <string name="prefs_password_6_digits_0_to_9_summary">The password must have 6 digits, using only numbers</string>
+    <string name="mi2_prefs_heart_rate_monitoring_alerts_summary">Configure heart rate monitoring and alert thresholds</string>
+    <string name="mi2_prefs_do_not_disturb_start">Start time</string>
+    <string name="mi2_prefs_do_not_disturb_end">End time</string>
+    <string name="mi2_prefs_do_not_disturb_lift_wrist">Activate display upon lift during Do Not Disturb</string>
+    <string name="do_not_disturb_lift_wrist_summary">Only if activate display upon lift enabled</string>
+    <string name="pref_do_not_disturb_not_wear">Do not disturb when not wearing</string>
+    <string name="mi3_prefs_band_screen_unlock">Band screen unlock"</string>
+    <string name="mi3_prefs_band_screen_unlock_summary">Swipe up to unlock the band\'s screen</string>
+    <string name="mi3_prefs_night_mode">Night mode</string>
+    <string name="mi3_prefs_night_mode_summary">Lower band screen brightness automatically at night</string>
+    <string name="bip_prefs_shortcuts">Shortcuts</string>
+    <string name="bip_prefs_shotcuts_summary">Choose the shortcuts on the band screen</string>
+    <string name="prefs_shortcut_cards">Shortcut Cards</string>
+    <string name="prefs_shortcut_cards_summary">Shortcut cards seen when swiping right on the watchface. When an app is running, auto-generated cards are not affected by this setting.</string>
+    <string name="prefs_control_center">Control Center</string>
+    <string name="prefs_control_center_summary">Choose the items on the control center dropdown</string>
+    <string name="prefs_activate_display_on_lift_sensitivity">Sensitivity</string>
+    <string name="prefs_screen_timeout">Screen Timeout</string>
+    <string name="mi5_prefs_workout_activity_types">Workout Activity Types</string>
+    <string name="mi5_prefs_workout_activity_types_summary">Choose the activity types to display on the workouts screen</string>
+    <string name="pref_title_force_white_color_scheme">Force black on white color scheme</string>
+    <string name="pref_summary_force_white_color_scheme">Useful if you your watch has dark hands</string>
+    <string name="lefun_prefs_hydration_reminder_title">Hydration reminder</string>
+    <string name="lefun_prefs_hydration_reminder_summary">The band will vibrate to remind you to drink water</string>
+    <string name="lefun_prefs_hydration_reminder_interval_title">Hydration reminder interval (in minutes)</string>
+    <string name="lefun_prefs_antilost_title">Anti-loss</string>
+    <string name="lefun_prefs_antilost_summary">The band will vibrate if your phone disconnects from the band</string>
+    <string name="lefun_prefs_interface_language_title">Interface language</string>
+    <string name="automatic">Automatic</string>
+    <string name="manual">Manual</string>
+    <string name="simplified_chinese">Simplified Chinese</string>
+    <string name="traditional_chinese">Traditional Chinese</string>
+    <string name="english">English</string>
+    <string name="english_au">English (Australia)</string>
+    <string name="english_ca">English (Canada)</string>
+    <string name="english_gb">English (United Kingdom)</string>
+    <string name="english_in">English (India)</string>
+    <string name="english_us">English (United States)</string>
+    <string name="norwegian_bokmal">Norwegian Bokm氓l</string>
+    <string name="spanish">Spanish</string>
+    <string name="spanish_es">Spanish (Spain)</string>
+    <string name="spanish_mx">Spanish (Mexico)</string>
+    <string name="spanish_us">Spanish (United States)</string>
+    <string name="russian">Russian</string>
+    <string name="georgian">Georgian</string>
+    <string name="german">German</string>
+    <string name="bengali">Bengali</string>
+    <string name="common_symbols">Common Symbols</string>
+    <string name="croatian">Croatian</string>
+    <string name="czech">Czech</string>
+    <string name="estonian">Estonian</string>
+    <string name="extended_ascii">Extended ASCII</string>
+    <string name="icelandic">Icelandic</string>
+    <string name="latvian">Latvian</string>
+    <string name="lithuanian">Lithuanian</string>
+    <string name="persian">Persian</string>
+    <string name="scandinavian">Scandinavian</string>
+    <string name="serbian">Serbian</string>
+    <string name="ukranian">Ukranian</string>
+    <string name="armenian">Armenian</string>
+    <string name="italian">Italian</string>
+    <string name="french">French</string>
+    <string name="french_ca">French (Canada)</string>
+    <string name="french_fr">French (France)</string>
+    <string name="polish">Polish</string>
+    <string name="korean">Korean</string>
+    <string name="japanese">Japanese</string>
+    <string name="dutch">Dutch</string>
+    <string name="turkish">Turkish</string>
+    <string name="ukrainian">Ukrainian</string>
+    <string name="arabic">Arabic</string>
+    <string name="indonesian">Indonesian</string>
+    <string name="thai">Thai</string>
+    <string name="vietnamese">Vietnamese</string>
+    <string name="portuguese">Portuguese</string>
+    <string name="portuguese_br">Portuguese (Brazil)</string>
+    <string name="portuguese_pt">Portuguese (Portugal)</string>
+    <string name="romanian">Romanian</string>
+    <string name="hungarian">Hungarian</string>
+    <string name="greek">Greek</string>
+    <string name="hebrew">Hebrew</string>
+    <string name="swedish">Swedish</string>
+    <string name="czesh">Czech</string>
+    <string name="danish">Danish</string>
+    <string name="FetchActivityOperation_about_to_transfer_since">About to transfer data since %1$s</string>
+    <string name="waiting_for_reconnect">Waiting for reconnect</string>
+    <string name="activity_prefs_about_you">About you</string>
+    <string name="activity_prefs_goals">Goals</string>
+    <string name="activity_prefs_date_birth">Date of birth</string>
+    <string name="activity_prefs_year_birth">Year of birth</string>
+    <string name="activity_prefs_gender">Gender</string>
+    <string name="activity_prefs_height_cm">Height in cm</string>
+    <string name="activity_prefs_height_inches">Height in inches</string>
+    <string name="activity_prefs_weight_kg">Weight in kg</string>
+    <string name="activity_prefs_target_weight_kg">Target weight in kg</string>
+    <string name="activity_prefs_step_length_cm">Step length in cm</string>
+    <!-- Settings - Charts Preferences -->
+    <string name="pref_header_charts">Charts Settings</string>
+    <string name="pref_title_charts_swipe">Enable left/right swipe in the charts activity</string>
+    <string name="pref_title_charts_average">Show averages in the charts</string>
+    <string name="activity_prefs_charts">Chart settings</string>
+    <string name="activity_prefs_discovery_pairing">Discovery and Pairing options</string>
+    <string name="activity_prefs_chart_max_heart_rate">Max heart rate</string>
+    <string name="activity_prefs_chart_min_heart_rate">Min heart rate</string>
+    <string name="pref_title_charts_range">Charts Range</string>
+    <string name="pref_chart_sleep_lines_limit">How many sleep session lines to show before scrolling them</string>
+    <string name="pref_charts_range_on">Charts range is set to a Month</string>
+    <string name="pref_charts_range_off">Charts range is set to a Week</string>
+    <string name="pref_chart_heartrate_color_red">Red</string>
+    <string name="pref_chart_heartrate_color_orange">Orange</string>
+    <string name="pref_title_chart_heartrate_color">Heart rate color</string>
+    <string name="weekstepschart_steps_a_month">Steps per month</string>
+    <string name="weeksleepchart_sleep_a_month">Sleep per month</string>
+    <string name="weekstepschart_steps_a_week_or_month">Steps per week/month</string>
+    <string name="weeksleepchart_sleep_a_week_or_month">Sleep per week/month</string>
+    <string name="pref_title_chart_sleep_rolling_24_hour">Sleep range</string>
+    <string name="pref_chart_sleep_rolling_24_on">Past 24 hours</string>
+    <string name="pref_chart_sleep_rolling_24_off">Noon to noon</string>
+    <string name="activity_prefs_chart_min_steps_per_minute_for_run">Minimal steps per minute to detect run</string>
+    <string name="activity_prefs_chart_min_steps_per_minute">Minimal steps per minute to detect activity</string>
+    <string name="activity_prefs_chart_max_idle_phase_length">Pause length to separate activities (minutes)</string>
+    <string name="activity_prefs_chart_min_session_length">Minimal activity length (minutes)</string>
+    <string name="authenticating">Authenticating</string>
+    <string name="authentication_required">Authentication required</string>
+    <string name="authentication_failed_check_key">Authentication failed, please check auth key</string>
+    <string name="authentication_failed_negotiation">Authentication key negotiation failed</string>
+    <string name="activity_prefs_sleep_duration">Preferred sleep duration in hours</string>
+    <string name="device_hw">Hardware revision: %1$s</string>
+    <string name="device_fw">Firmware version: %1$s</string>
+    <string name="error_creating_directory_for_logfiles">Error creating directory for log files: %1$s</string>
+    <string name="DEVINFO_HR_VER">"HR: "</string>
+    <string name="updatefirmwareoperation_update_in_progress">Flashing firmware</string>
+    <string name="updatefirmwareoperation_firmware_not_sent">Firmware not sent</string>
+    <string name="charts_legend_heartrate">Heart rate</string>
+    <string name="live_activity_heart_rate">Heart rate</string>
+    <string name="charts_legend_heartrate_average">Heart rate average</string>
+    <string name="charts_legend_stress_average">Stress average</string>
+    <string name="charts_legend_spo2_average">Blood oxygen average</string>
+    <string name="activity_prefs_calories_burnt">Daily target: calories burnt</string>
+    <string name="activity_prefs_distance_meters">Daily target: distance in meters</string>
+    <string name="activity_prefs_activetime_minutes">Daily target: active time in minutes</string>
+    <string name="activity_prefs_goal_standing_time_minutes">Daily target: standing time in minutes</string>
+    <string name="activity_prefs_goal_fat_burn_time_minutes">Daily target: fat burn time in minutes</string>
+    <string name="activity_prefs_goal_active_calories_burnt">Daily target: active calories burnt</string>
+    <string name="active_time">Active time</string>
+    <string name="standing_time">Standing time</string>
+    <string name="pref_title_pebble_health_store_raw">Store raw record in the database</string>
+    <string name="pref_summary_pebble_health_store_raw">Stores the data \"as is\", increasing the database usage to allow for later interpretation.</string>
+    <string name="action_db_management">Data management</string>
+    <string name="title_activity_db_management">Data management</string>
+    <string name="activity_db_management_import_export_explanation">The export/import operations use the following path (see below) to a directory on your device. This directory is accessible to other Android apps and your computer. Do note, that this directory and all containing files are deleted if you uninstall Gadgetbridge. The data includes:\n Export_preference - global settings\n Export_preference_MAC - device specific settings\n Gadgetbridge - device and activity database\n Gadgetbridge_date - database exported on a date\n *.gpx - GPS recordings\n *.log - log files\nExpect to find your exported files (or place the files you want to import) there:</string>
+    <string name="activity_db_management_merge_old_title">Legacy database delete</string>
+    <string name="dbmanagementactivvity_cannot_access_export_path">Cannot access export path. Please contact the developers.</string>
+    <string name="dbmanagementactivity_exported_to">Exported to: %1$s</string>
+    <string name="dbmanagementactivity_error_exporting_db">"Error exporting DB: %1$s"</string>
+    <string name="dbmanagementactivity_error_cleaning_export_directory">"Error erasing files from export directory %1$s"</string>
+    <string name="dbmanagementactivity_error_exporting_shared">"Error exporting preference: %1$s"</string>
+    <string name="dbmanagementactivity_import_data_title">Import Data?</string>
+    <string name="dbmanagementactivity_export_data_title">Export Data?</string>
+    <string name="dbmanagementactivity_overwrite_database_confirmation">Really overwrite the current data? All your current activity data (if any), devices, and preferences will be overwritten.</string>
+    <string name="dbmanagementactivity_export_confirmation">Really export data? Previously exported activity data (if any) and preferences will be overwritten.</string>
+    <string name="dbmanagementactivity_import_successful">Imported.</string>
+    <string name="dbmanagementactivity_error_importing_db">"Error importing DB: %1$s"</string>
+    <string name="dbmanagementactivity_error_importing_shared">"Error importing preference: %1$s"</string>
+    <string name="dbmanagementactivity_delete_activity_data_title">Delete Activity Data?</string>
+    <string name="dbmanagementactivity_really_delete_entire_db">Really delete the entire database? All your activity data and information about your devices will be lost.</string>
+    <string name="dbmanagementactivity_database_successfully_deleted">Data deleted.</string>
+    <string name="dbmanagementactivity_db_deletion_failed">Database deletion failed.</string>
+    <string name="dbmanagementactivity_delete_old_activity_db">Delete old Activity Database?</string>
+    <string name="dbmanagementactivity_delete_old_activitydb_confirmation">Really delete the old activity database? Activity data that was not imported will be lost.</string>
+    <string name="dbmanagementactivity_old_activity_db_successfully_deleted">Old activity data deleted.</string>
+    <string name="dbmanagementactivity_old_activity_db_deletion_failed">Old Activity database deletion failed.</string>
+    <string name="dbmanagementactivity_overwrite">Overwrite</string>
+    <string name="activity_db_management_autoexport_explanation">Database autoexport location has been set to:</string>
+    <string name="autoExport_lastTime_label">Last AutoExport: %1$s</string>
+    <string name="activity_db_management_autoexport_enabled_yes">AutoExport is enabled.</string>
+    <string name="activity_db_management_autoexport_enabled_no">AutoExport is not enabled.</string>
+    <string name="activity_db_management_autoexport_scheduled_yes">AutoExport has (originally) been scheduled for %1$s</string>
+    <string name="activity_db_management_autoexport_scheduled_no">AutoExport has not been not scheduled.</string>
+    <string name="activity_db_management_autoexport_label">AutoExport</string>
+    <string name="activity_db_management_autoexport_location">Location could not be understood. Likely an issue of newer Android permission system. Most likely, autoexport is not working now.</string>
+    <string name="activity_DB_ExportButton">Export Data</string>
+    <string name="activity_DB_import_button">Import Data</string>
+    <string name="activity_DB_test_export_button">Run AutoExport Now</string>
+    <string name="activity_DB_test_export_message">Exporting database鈥�</string>
+    <string name="activity_DB_delete_legacy_button">Delete old DB</string>
+    <string name="activity_DB_empty_button">Empty Database</string>
+    <string name="activity_db_management_empty_DB">Empty Database</string>
+    <string name="activity_db_management_exportimport_label">Export and Import</string>
+    <string name="activity_db_management_empty_db_warning">Warning! By pushing this button you will wipe your database and start from scratch.</string>
+    <string name="Cancel">Cancel</string>
+    <string name="Delete">Delete</string>
+    <string name="ok">OK</string>
+    <string name="dismiss">Dismiss</string>
+    <string name="start">Start</string>
+    <string name="stop">Stop</string>
+    <string name="status">Status</string>
+    <string name="set">Set</string>
+    <string name="activity_data_management_directory_content_title">Export/Import directory content</string>
+    <string name="activity_DB_ShowContentButton">Show Export/Import directory content</string>
+    <string name="activity_db_management_clean_export_directory_label">Delete files in Export/Import directory</string>
+    <string name="activity_DB_clean_export_directory_warning_title">Delete files in the Export/Import directory?</string>
+    <string name="activity_DB_clean_export_directory_warning_message">Really delete files in the Export/Import directory?</string>
+    <string name="activity_db_management_clean_export_directory_text">Exported files in the Export/Import directory are accessible by any app on your device. You might like to remove these files after synchronisation or backup. Make sure to have a backup before deleting them. GPX files, sub-directories and auto-exported database file (if exist) will not be deleted. The path to the Export/Import directory is:</string>
+    <string name="dbmanagementactivity_export_finished">Deletion finished</string>
+    <!-- Strings related to Vibration Activity -->
+    <string name="title_activity_vibration">Vibration</string>
+    <!-- Strings related to Pebble Pairing Activity-->
+    <string name="title_activity_pebble_pairing">Pebble pairing</string>
+    <string name="pebble_pairing_hint">A pairing dialog will pop up on your Android device. If not, look in the notification drawer and accept the pairing request. Also accept it on your Pebble afterwards.</string>
+    <string name="weather_notification_label">Make sure that this skin is enabled in the Weather Notification app to get weather information on your Pebble.\n\nNo configuration is needed here.\n\nYou can enable the system weather app of your Pebble from the app management.\n\nSupported watchfaces will show the weather automatically.</string>
+    <string name="pref_title_setup_bt_pairing">Enable Bluetooth pairing</string>
+    <string name="pref_summary_setup_bt_pairing">Deactivate this if you have trouble connecting</string>
+    <string name="unit_metric">Metric</string>
+    <string name="unit_imperial">Imperial</string>
+    <string name="timeformat_24h">24H</string>
+    <string name="timeformat_am_pm">AM/PM</string>
+    <string name="dateformat_day_month">Day, Month</string>
+    <string name="dateformat_month_day">Month, Day</string>
+    <string name="pref_screen_notification_profile_alarm_clock">Alarm clock</string>
+    <string name="activity_web_view">Web View Activity</string>
+    <string name="StringUtils_sender"> (%1$s)</string>
+    <string name="find_device_you_found_it">You found it!</string>
+    <string name="find_lost_device_you_found_it">Found it!</string>
+    <string name="miband2_prefs_timeformat">Mi2: Time format</string>
+    <string name="mi2_fw_installhandler_fw53_hint">You need to install version %1$s before installing this firmware!</string>
+    <string name="mi2_enable_text_notifications">Text notifications</string>
+    <string name="mi2_enable_text_notifications_summary"><![CDATA[Needs firmware >= 1.0.1.28 and Mili_pro.ft* installed.]]></string>
+    <string name="on">On</string>
+    <string name="smart">Smart</string>
+    <string name="off">Off</string>
+    <string name="normal">Normal</string>
+    <string name="sensitive">Sensitive</string>
+    <string name="mi2_dnd_off">Off</string>
+    <string name="mi2_dnd_always">Always</string>
+    <string name="mi3_night_mode_sunset">At sunset</string>
+    <string name="mi2_dnd_automatic">Automatic (sleep detection)</string>
+    <string name="mi2_dnd_scheduled">Scheduled (time interval)</string>
+    <string name="dnd_all_day">All day</string>
+    <string name="maximum_duration">Duration</string>
+    <string name="discovery_attempting_to_pair">Attempting to pair with %1$s</string>
+    <string name="discovery_bonding_failed_immediately">Bonding with %1$s failed immediately.</string>
+    <string name="discovery_trying_to_connect_to">Trying to connect to: %1$s</string>
+    <string name="discovery_enable_bluetooth">Enable Bluetooth to discover devices.</string>
+    <string name="discovery_successfully_bonded">Bound to %1$s.</string>
+    <string name="discovery_pair_title">Pair with %1$s?</string>
+    <string name="discovery_pair_question">Select Pair to pair your devices. If this fails, try again without pairing.</string>
+    <string name="discovery_yes_pair">Pair</string>
+    <string name="discovery_dont_pair">Don\'t Pair</string>
+    <!-- strings sent to pebble watches for quick actions -->
+    <string name="_pebble_watch_open_on_phone">Open on Android device</string>
+    <string name="_pebble_watch_mute">Mute</string>
+    <string name="_pebble_watch_reply">Reply</string>
+    <string name="controlcenter_start_activity_tracks">Your activity tracks</string>
+    <string name="activity_type_not_measured">Not measured</string>
+    <string name="activity_type_activity">Activity</string>
+    <string name="activity_type_light_sleep">Light sleep</string>
+    <string name="activity_type_rem_sleep">REM sleep</string>
+    <string name="activity_type_deep_sleep">Deep sleep</string>
+    <string name="activity_type_not_worn">Device not worn</string>
+    <string name="activity_type_running">Running</string>
+    <string name="activity_type_outdoor_running">Outdoor Running</string>
+    <string name="activity_type_indoor_running">Indoor Running</string>
+    <string name="activity_type_mountain_hike">Mountain Hike</string>
+    <string name="activity_type_cross_trainer">Cross trainer</string>
+    <string name="activity_type_free_training">Free training</string>
+    <string name="activity_type_rower">Rower</string>
+    <string name="activity_type_dynamic_cycle">Dynamic cycle</string>
+    <string name="activity_type_stair_stepper">Stair stepper</string>
+    <string name="activity_type_fitness_exercises">Fitness exercises</string>
+    <string name="activity_type_crossfit">Crossfit</string>
+    <string name="activity_type_functional_training">Functional training</string>
+    <string name="activity_type_physical_training">Physical training</string>
+    <string name="activity_type_taekwondo">Taekwondo</string>
+    <string name="activity_type_tae_bo">Tae Bo</string>
+    <string name="activity_type_beach_soccer">Beach Soccer</string>
+    <string name="activity_type_beach_volleyball">Beach Volleyball</string>
+    <string name="activity_type_gateball">Gateball</string>
+    <string name="activity_type_sepak_takraw">Sepak Takraw</string>
+    <string name="activity_type_luge">Luge</string>
+    <string name="activity_type_parachuting">Parachuting</string>
+    <string name="activity_type_auto_racing">Auto racing</string>
+    <string name="activity_type_parkour">Parkour</string>
+    <string name="activity_type_cross_country_running">Cross country running</string>
+    <string name="activity_type_karate">Karate</string>
+    <string name="activity_type_fencing">Fencing</string>
+    <string name="activity_type_kendo">Kendo</string>
+    <string name="activity_type_horizontal_bar">Horizontal bar</string>
+    <string name="activity_type_parallel_bar">Parallel bar</string>
+    <string name="activity_type_cooldown">Cooldown</string>
+    <string name="activity_type_cross_training">Cross training</string>
+    <string name="activity_type_sit_ups">Sit ups</string>
+    <string name="activity_type_fitness_gaming">Fitness gaming</string>
+    <string name="activity_type_aerobic_exercise">Aerobic exercise</string>
+    <string name="activity_type_rolling">Rolling</string>
+    <string name="activity_type_flexibility">Flexibility</string>
+    <string name="activity_type_track_and_field">Track and field</string>
+    <string name="activity_type_push_ups">Push ups</string>
+    <string name="activity_type_battle_rope">Battle rope</string>
+    <string name="activity_type_smith_machine">Smith machine</string>
+    <string name="activity_type_pull_ups">Pull ups</string>
+    <string name="activity_type_plank">Plank</string>
+    <string name="activity_type_javelin">Javelin</string>
+    <string name="activity_type_long_jump">Long jump</string>
+    <string name="activity_type_high_jump">High jump</string>
+    <string name="activity_type_trampoline">Trampoline</string>
+    <string name="activity_type_dumbbell">Dumbbell</string>
+    <string name="activity_type_belly_dance">Belly dance</string>
+    <string name="activity_type_jazz_dance">Jazz dance</string>
+    <string name="activity_type_latin_dance">Latin dance</string>
+    <string name="activity_type_ballet">Ballet</string>
+    <string name="activity_type_other_dance">Other dance</string>
+    <string name="activity_type_roller_skating">Roller skating</string>
+    <string name="activity_type_martial_arts">Martial arts</string>
+    <string name="activity_type_tai_chi">Tai chi</string>
+    <string name="activity_type_hula_hooping">Hula hooping</string>
+    <string name="activity_type_disc_sports">Disc sports</string>
+    <string name="activity_type_darts">Darts</string>
+    <string name="activity_type_archery">Archery</string>
+    <string name="activity_type_horse_riding">Horse riding</string>
+    <string name="activity_type_kite_flying">Kite Flying</string>
+    <string name="activity_type_swing">Swing</string>
+    <string name="activity_type_stairs">Stairs</string>
+    <string name="activity_type_fishing">Fishing</string>
+    <string name="activity_type_hand_cycling">Hand cycling</string>
+    <string name="activity_type_mind_and_body">Mind and body</string>
+    <string name="activity_type_kabaddi">Kabaddi</string>
+    <string name="activity_type_karting">Karting</string>
+    <string name="activity_type_billiards">Billiards</string>
+    <string name="activity_type_shuttlecock">Shuttlecock</string>
+    <string name="activity_type_softball">Softball</string>
+    <string name="activity_type_dodgeball">Dodgeball</string>
+    <string name="activity_type_australian_football">Australian football</string>
+    <string name="activity_type_pickleball">Pickleball</string>
+    <string name="activity_type_lacross">Lacross</string>
+    <string name="activity_type_shot">Shot</string>
+    <string name="activity_type_sailing">Sailing</string>
+    <string name="activity_type_jet_skiing">Jet skiing</string>
+    <string name="activity_type_skating">Skating</string>
+    <string name="activity_type_ice_hockey">Ice hockey</string>
+    <string name="activity_type_curling">Curling</string>
+    <string name="activity_type_cross_country_skiing">Cross country skiing</string>
+    <string name="activity_type_snow_sports">Snow sports</string>
+    <string name="activity_type_skateboarding">Skateboarding</string>
+    <string name="activity_type_rock_climbing">Rock climbing</string>
+    <string name="activity_type_hunting">Hunting</string>
+    <string name="activity_type_walking">Walking</string>
+    <string name="activity_type_outdoor_walking">Outdoor Walking</string>
+    <string name="activity_type_indoor_walking">Indoor Walking</string>
+    <string name="activity_type_surfing">Surfing</string>
+    <string name="activity_type_windsurfing">Windsurfing</string>
+    <string name="activity_type_kitesurfing">Kitesurfing</string>
+    <string name="activity_type_freestyle">Freestyle</string>
+    <string name="activity_type_hiking">Hiking</string>
+    <string name="activity_type_climbing">Climbing</string>
+    <string name="activity_type_swimming">Swimming</string>
+    <string name="activity_type_pool_swimming">Pool Swimming</string>
+    <string name="activity_type_swimming_openwater">Swimming (Open water)</string>
+    <string name="activity_type_indoor_cycling">Indoor Cycling</string>
+    <string name="activity_type_outdoor_cycling">Outdoor Cycling</string>
+    <string name="activity_type_elliptical_trainer">Elliptical Trainer</string>
+    <string name="activity_type_elliptical">Elliptical</string>
+    <string name="activity_type_jump_roping">Jumping Rope</string>
+    <string name="activity_type_yoga">Yoga</string>
+    <string name="activity_type_soccer">Soccer</string>
+    <string name="activity_type_football">Football</string>
+    <string name="activity_type_rugby">Rugby</string>
+    <string name="activity_type_rowing_machine">Rowing Machine</string>
+    <string name="activity_type_rowing">Rowing</string>
+    <string name="activity_type_cricket">Cricket</string>
+    <string name="activity_type_baseball">Baseball</string>
+    <string name="activity_type_basketball">Basketball</string>
+    <string name="activity_type_handball">Handball</string>
+    <string name="activity_type_tennis">Tennis</string>
+    <string name="activity_type_pingpong">Ping Pong</string>
+    <string name="activity_type_squash">Squash</string>
+    <string name="activity_type_badminton">Badminton</string>
+    <string name="activity_type_weightlifting">Weightlifting</string>
+    <string name="activity_type_strength_training">Strength Training</string>
+    <string name="activity_type_dance">Dance</string>
+    <string name="activity_type_indoor_fitness">Indoor Fitness</string>
+    <string name="activity_type_gymnastics">Gymnastics</string>
+    <string name="activity_type_hiit">High-intensity Interval Training</string>
+    <string name="activity_type_core_training">Core Training</string>
+    <string name="activity_type_stretching">Stretching</string>
+    <string name="activity_type_stepper">Stepper</string>
+    <string name="activity_type_pilates">Pilates</string>
+    <string name="activity_type_volleyball">Volleyball</string>
+    <string name="activity_type_table_tennis">Table Tennis</string>
+    <string name="activity_type_bowling">Bowling</string>
+    <string name="activity_type_boxing">Boxing</string>
+    <string name="activity_type_kickboxing">Kickboxing</string>
+    <string name="activity_type_street_dance">Street Dance</string>
+    <string name="activity_type_zumba">Zumba</string>
+    <string name="activity_type_indoor_ice_skating">Indoor Ice Skating</string>
+    <string name="activity_type_dancing">Dancing</string>
+    <string name="activity_type_skiing">Skiing</string>
+    <string name="activity_type_snowboarding">Snowboarding</string>
+    <string name="activity_type_riding">Horseback Riding</string>
+    <string name="activity_type_hockey">Hockey</string>
+    <string name="activity_type_icehockey">Icehockey</string>
+    <string name="activity_type_iceskating">Ice Skating</string>
+    <string name="activity_type_golf">Golfing</string>
+    <string name="activity_type_other">Other</string>
+    <string name="activity_type_trekking">Trekking</string>
+    <string name="activity_type_trail_run">Trail run</string>
+    <string name="activity_type_wrestling">Wrestling</string>
+    <string name="activity_type_unknown">Unknown activity</string>
+    <string name="activity_type_navigate">Navigate</string>
+    <string name="activity_type_indoor_track">Indoor Track</string>
+    <string name="activity_type_handcycling">Handcycling</string>
+    <string name="activity_type_e_bike">E-Bike</string>
+    <string name="activity_type_bike_commute">Bike Commute</string>
+    <string name="activity_type_handcycling_indoor">Handcycling Indoor</string>
+    <string name="activity_type_transition">Transition</string>
+    <string name="activity_type_fitness_equipment">Fitness Equipment</string>
+    <string name="activity_type_platform_tennis">Platform Tennis</string>
+    <string name="activity_type_american_football">American Football</string>
+    <string name="activity_type_training">Training</string>
+    <string name="activity_type_cardio">Cardio</string>
+    <string name="activity_type_breathwork">Breathwork</string>
+    <string name="activity_type_xc_classic_ski">XC Classic Ski</string>
+    <string name="activity_type_mountaineering">Mountaineering</string>
+    <string name="activity_type_multisport">Multisport</string>
+    <string name="activity_type_paddling">Paddling</string>
+    <string name="activity_type_flying">Flying</string>
+    <string name="activity_type_motorcycling">Motorcycling</string>
+    <string name="activity_type_boating">Boating</string>
+    <string name="activity_type_driving">Driving</string>
+    <string name="activity_type_hang_gliding">Hang Gliding</string>
+    <string name="activity_type_inline_skating">Inline Skating</string>
+    <string name="activity_type_climb_indoor">Climb Indoor</string>
+    <string name="activity_type_bouldering">Bouldering</string>
+    <string name="activity_type_sail_race">Sail Race</string>
+    <string name="activity_type_sail_expedition">Sail Expedition</string>
+    <string name="activity_type_ice_skating">Ice Skating</string>
+    <string name="activity_type_sky_diving">Sky Diving</string>
+    <string name="activity_type_snowshoe">Snowshoe</string>
+    <string name="activity_type_snowmobiling">Snowmobiling</string>
+    <string name="activity_type_stand_up_paddleboarding">Standup Paddleboarding</string>
+    <string name="activity_type_wakeboarding">Wakeboarding</string>
+    <string name="activity_type_water_skiing">Water Skiing</string>
+    <string name="activity_type_kayaking">Kayaking</string>
+    <string name="activity_type_rafting">Rafting</string>
+    <string name="activity_type_tactical">Tactical</string>
+    <string name="activity_type_jumpmaster">Jumpmaster</string>
+    <string name="activity_type_floor_climbing">Floor Climbing</string>
+    <string name="activity_type_softball_slow_pitch">Softball Slow Pitch</string>
+    <string name="activity_type_shooting">Shooting</string>
+    <string name="activity_type_winter_sport">Winter Sport</string>
+    <string name="activity_type_grinding">Grinding</string>
+    <string name="activity_type_health_snapshot">Health Snapshot</string>
+    <string name="activity_type_marine">Marine</string>
+    <string name="activity_type_video_gaming">Video Gaming</string>
+    <string name="activity_type_racket">Racket</string>
+    <string name="activity_type_padel">Padel</string>
+    <string name="activity_type_racquetball">Racquetball</string>
+    <string name="activity_type_push_walk_speed">Push - Walk Speed</string>
+    <string name="activity_type_indoor_push_walk_speed">Indoor Push - Walk Speed</string>
+    <string name="activity_type_push_run_speed">Push - Run Speed</string>
+    <string name="activity_type_indoor_push_run_speed">Indoor Push - Run Speed</string>
+    <string name="activity_type_meditation">Meditation</string>
+    <string name="activity_type_para_sport">Para Sport</string>
+    <string name="activity_type_disc_golf">Disc Golf</string>
+    <string name="activity_type_ultimate_disc">Ultimate Disc</string>
+    <string name="activity_type_team_sport">Team Sport</string>
+    <string name="activity_type_lacrosse">Lacrosse</string>
+    <string name="activity_type_water_tubing">Water Tubing</string>
+    <string name="activity_type_wakesurfing">Wakesurfing</string>
+    <string name="activity_type_mixed_martial_arts">Mixed Martial Arts</string>
+    <string name="activity_type_aerobic_combo">Aerobic combo</string>
+    <string name="activity_type_aerobics">Aerobics</string>
+    <string name="activity_type_air_walker">Air walker</string>
+    <string name="activity_type_artistic_swimming">Artistic swimming</string>
+    <string name="activity_type_ballroom_dance">Ballroom dance</string>
+    <string name="activity_type_bmx">BMX</string>
+    <string name="activity_type_board_game">Board game</string>
+    <string name="activity_type_bocce">Bocce</string>
+    <string name="activity_type_breaking">Breaking</string>
+    <string name="activity_type_bridge">Bridge</string>
+    <string name="activity_type_cardio_combat">Cardio combat</string>
+    <string name="activity_type_checkers">Checkers</string>
+    <string name="activity_type_chess">Chess</string>
+    <string name="activity_type_dragon_boat">Dragon boat</string>
+    <string name="activity_type_esports">Esports</string>
+    <string name="activity_type_finswimming">Finswimming</string>
+    <string name="activity_type_flowriding">Flowriding</string>
+    <string name="activity_type_folk_dance">Folk dance</string>
+    <string name="activity_type_frisbee">Frisbee</string>
+    <string name="activity_type_futsal">Futsal</string>
+    <string name="activity_type_hacky_sack">Hacky sack</string>
+    <string name="activity_type_hip_hop">Hip-hop</string>
+    <string name="activity_type_hula_hoop">Hula hoop</string>
+    <string name="activity_type_jai_alai">Jai alai</string>
+    <string name="activity_type_judo">Judo</string>
+    <string name="activity_type_jujitsu">Jujitsu</string>
+    <string name="activity_type_mass_gymnastics">Mass gymnastics</string>
+    <string name="activity_type_modern_dance">Modern dance</string>
+    <string name="activity_type_muay_thai">Muay thai</string>
+    <string name="activity_type_parallel_bars">Parallel bars</string>
+    <string name="activity_type_pole_dance">Pole dance</string>
+    <string name="activity_type_race_walking">Race walking</string>
+    <string name="activity_type_shuffleboard">Shuffleboard</string>
+    <string name="activity_type_snorkeling">Snorkeling</string>
+    <string name="activity_type_somatosensory_game">Somatosensory game</string>
+    <string name="activity_type_spinning">Spinning</string>
+    <string name="activity_type_square_dance">Square dance</string>
+    <string name="activity_type_stair_climber">Stair climber</string>
+    <string name="activity_type_table_football">Table football</string>
+    <string name="activity_type_tug_of_war">Tug of war</string>
+    <string name="activity_type_wall_ball">Wall ball</string>
+    <string name="activity_type_water_polo">Water polo</string>
+    <string name="activity_type_weiqi">Weiqi</string>
+    <string name="activity_type_free_sparring">Free sparring</string>
+    <string name="activity_type_body_combat">Body combat</string>
+    <string name="activity_type_plaza_dancing">Plaza dancing</string>
+    <string name="activity_type_laser_tag">Laser tag</string>
+    <string name="activity_type_obstacle_race">Obstacle race</string>
+    <string name="activity_type_billiard_pool">Pool</string>
+    <string name="activity_type_canoeing">Canoeing</string>
+    <string name="activity_type_water_scooter">Water scooter</string>
+    <string name="activity_type_bobsleigh">Bobsleigh</string>
+    <string name="activity_type_sledding">Sledding</string>
+    <string name="activity_type_biathlon">Biathlon</string>
+    <string name="activity_type_bungee_jumping">Bungee jumping</string>
+    <string name="activity_type_orienteering">Orienteering</string>
+    <string name="activity_summaries">Sport Activities</string>
+    <string name="activity_summary_detail">Sport Activity Detail</string>
+    <string name="activity_summary_edit_name_title">Edit label</string>
+    <string name="activity_summary_detail_select_gpx_track">Select GPX track</string>
+    <string name="activity_summary_detail_clear_gpx_track">Clear GPX track</string>
+    <string name="activity_summary_detail_editing_gpx_track">Editing linked GPX track</string>
+    <string name="activity_summary_today">Today</string>
+    <string name="activity_summary_yesterday">Yesterday</string>
+    <string name="hrv_status_day_avg">Day average</string>
+    <string name="hrv_status_day_avg_legend">Daily average (ms)</string>
+    <string name="hrv_status_seven_days_avg">7-day average</string>
+    <string name="hrv_status_seven_days_avg_status">Status</string>
+    <string name="hrv_status_balanced">Balanced</string>
+    <string name="hrv_status_unbalanced">Unbalanced</string>
+    <string name="hrv_status_low">Low</string>
+    <string name="hrv_status_poor">Poor</string>
+    <string name="hrv_status_last_night">Last night</string>
+    <string name="hrv_status_last_night_highest_5">Last night 5-min max avg</string>
+    <string name="hrv_status_seven_days_avg_long">7-day average</string>
+    <string name="hrv_status_unit">%1$d ms</string>
+    <string name="hrv_status_baseline">%1$d-%2$d ms</string>
+    <string name="hrv_status_baseline_label">Baseline</string>
+    <string name="bpm_value_unit">%1$d bpm</string>
+    <string name="steps_distance_unit">%1$,.2f km</string>
+    <string name="body_energy_gained">Gained</string>
+    <string name="body_energy_lost">Lost</string>
+    <string name="body_energy_legend_level">Body Energy Level</string>
+    <string name="activity_type_biking">Biking</string>
+    <string name="activity_type_treadmill">Treadmill</string>
+    <string name="activity_type_exercise">Exercise</string>
+    <string name="activity_error_no_app_for_gpx">To view activity trace, install app which can handle GPX files.</string>
+    <string name="activity_error_no_app_for_png">To share this screenshot, install an app which can handle image files.</string>
+    <string name="activity_error_share_failed">Sharing file failed.</string>
+    <string name="select_all">Select all</string>
+    <string name="share">Share</string>
+    <string name="share_screenshot">Share screenshot</string>
+    <string name="screenshot_taken">Screenshot taken</string>
+    <string name="reset_index">Reset fetch date</string>
+    <string name="kind_firmware">Firmware</string>
+    <string name="kind_invalid">Invalid data</string>
+    <string name="kind_font">Font</string>
+    <string name="kind_gps">GPS Firmware</string>
+    <string name="kind_gps_almanac">GPS Almanac</string>
+    <string name="kind_gps_cep">GPS Error Correction</string>
+    <string name="kind_agps_bundle">AGPS Bundle</string>
+    <string name="kind_gpx_route">GPX Route</string>
+    <string name="kind_resources">Resources</string>
+    <string name="kind_watchface">Watchface</string>
+    <string name="kind_app">App</string>
+    <string name="devicetype_unknown">Unknown Device</string>
+    <string name="devicetype_test">Test Device</string>
+    <string name="add_test_device">Add test device</string>
+    <string name="devicetype_pebble">Pebble</string>
+    <string name="devicetype_miband">Mi Band</string>
+    <string name="devicetype_miband2">Mi Band 2</string>
+    <string name="devicetype_miband3">Mi Band 3</string>
+    <string name="devicetype_miband4">Mi Band 4</string>
+    <string name="devicetype_miband5">Mi Band 5</string>
+    <string name="devicetype_miband6">Mi Band 6</string>
+    <string name="devicetype_miband7">Xiaomi Smart Band 7</string>
+    <string name="devicetype_miband7pro">Xiaomi Smart Band 7 Pro</string>
+    <string name="devicetype_miband8">Xiaomi Smart Band 8</string>
+    <string name="devicetype_miband8active">Xiaomi Smart Band 8 Active</string>
+    <string name="devicetype_miband8pro">Xiaomi Smart Band 8 Pro</string>
+    <string name="devicetype_miband9">Xiaomi Smart Band 9</string>
+    <string name="devicetype_miband9pro">Xiaomi Smart Band 9 Pro</string>
+    <string name="devicetype_amazfit_balance">Amazfit Balance</string>
+    <string name="devicetype_amazfit_active">Amazfit Active</string>
+    <string name="devicetype_amazfit_active_edge">Amazfit Active Edge</string>
+    <string name="devicetype_amazfit_cheetah_square">Amazfit Cheetah (Square)</string>
+    <string name="devicetype_amazfit_cheetah_round">Amazfit Cheetah (Round)</string>
+    <string name="devicetype_amazfit_cheetah_pro">Amazfit Cheetah Pro</string>
+    <string name="devicetype_amazfit_gts3">Amazfit GTS 3</string>
+    <string name="devicetype_amazfit_gts4">Amazfit GTS 4</string>
+    <string name="devicetype_amazfit_gts4_mini">Amazfit GTS 4 Mini</string>
+    <string name="devicetype_amazfit_gtr3">Amazfit GTR 3</string>
+    <string name="devicetype_amazfit_gtr3_pro">Amazfit GTR 3 Pro</string>
+    <string name="devicetype_amazfit_bip3">Amazfit Bip 3</string>
+    <string name="devicetype_amazfit_bip3_pro">Amazfit Bip 3 Pro</string>
+    <string name="devicetype_amazfit_bip5">Amazfit Bip 5</string>
+    <string name="devicetype_amazfit_bip5_unity">Amazfit Bip 5 Unity</string>
+    <string name="devicetype_amazfit_gtr4">Amazfit GTR 4</string>
+    <string name="devicetype_amazfit_trex_2">Amazfit T-Rex 2</string>
+    <string name="devicetype_amazfit_trex_3">Amazfit T-Rex 3</string>
+    <string name="devicetype_amazfit_trex_ultra">Amazfit T-Rex Ultra</string>
+    <string name="devicetype_amazfit_band5">Amazfit Band 5</string>
+    <string name="devicetype_amazfit_band7">Amazfit Band 7</string>
+    <string name="devicetype_amazfit_neo">Amazfit Neo</string>
+    <string name="devicetype_amazfit_bip">Amazfit Bip</string>
+    <string name="devicetype_amazfit_bip_lite">Amazfit Bip Lite</string>
+    <string name="devicetype_amazfit_cor">Amazfit Cor</string>
+    <string name="devicetype_amazfit_cor2">Amazfit Cor 2</string>
+    <string name="devicetype_amazfit_gtr">Amazfit GTR</string>
+    <string name="devicetype_amazfit_gtr_lite">Amazfit GTR Lite</string>
+    <string name="devicetype_amazfit_gtr_mini">Amazfit GTR Mini</string>
+    <string name="devicetype_amazfit_falcon">Amazfit Falcon</string>
+    <string name="devicetype_amazfit_trex">Amazfit T-Rex</string>
+    <string name="devicetype_amazfit_bips">Amazfit Bip S</string>
+    <string name="devicetype_amazfit_bips_lite">Amazfit Bip S Lite</string>
+    <string name="devicetype_amazfit_bipu">Amazfit Bip U</string>
+    <string name="devicetype_amazfit_bipupro">Amazfit Bip U Pro</string>
+    <string name="devicetype_amazfit_pop">Amazfit Pop</string>
+    <string name="devicetype_amazfit_pop_pro">Amazfit Pop Pro</string>
+    <string name="devicetype_amazfit_gtr2">Amazfit GTR 2</string>
+    <string name="devicetype_amazfit_gtr2e">Amazfit GTR 2e</string>
+    <string name="devicetype_amazfit_gts2">Amazfit GTS 2</string>
+    <string name="devicetype_amazfit_gts2_mini">Amazfit GTS 2 Mini</string>
+    <string name="devicetype_amazfit_gts2e">Amazfit GTS 2e</string>
+    <string name="devicetype_amazfit_x">Amazfit X</string>
+    <string name="devicetype_zepp_e">Zepp E</string>
+    <string name="devicetype_garmin_vivomove_hr">Garmin V铆vomove HR</string>
+    <string name="devicetype_garmin_vivomove_style">Garmin V铆vomove Style</string>
+    <string name="devicetype_garmin_vivomove_trend">Garmin V铆vomove Trend</string>
+    <string name="devicetype_garmin_venu">Garmin Venu</string>
+    <string name="devicetype_garmin_venu_sq">Garmin Venu Sq</string>
+    <string name="devicetype_garmin_venu_sq_2">Garmin Venu Sq 2</string>
+    <string name="devicetype_garmin_venu_2">Garmin Venu 2</string>
+    <string name="devicetype_garmin_venu_2_plus">Garmin Venu 2 Plus</string>
+    <string name="devicetype_garmin_venu_2s">Garmin Venu 2S</string>
+    <string name="devicetype_garmin_venu_3">Garmin Venu 3</string>
+    <string name="devicetype_garmin_venu_3s">Garmin Venu 3S</string>
+    <string name="devicetype_garmin_enduro_3">Garmin Enduro 3</string>
+    <string name="devicetype_garmin_epix_pro">Garmin Epix Pro</string>
+    <string name="devicetype_garmin_fenix_5">Garmin Fenix 5</string>
+    <string name="devicetype_garmin_fenix_5_plus">Garmin Fenix 5 Plus</string>
+    <string name="devicetype_garmin_fenix_5x_plus">Garmin Fenix 5X Plus</string>
+    <string name="devicetype_garmin_fenix_6">Garmin Fenix 6</string>
+    <string name="devicetype_garmin_fenix_6_sapphire">Garmin Fenix 6 Sapphire</string>
+    <string name="devicetype_garmin_fenix_6s_pro">Garmin Fenix 6S Pro</string>
+    <string name="devicetype_garmin_fenix_6s_sapphire">Garmin Fenix 6S Sapphire</string>
+    <string name="devicetype_garmin_fenix_7">Garmin Fenix 7</string>
+    <string name="devicetype_garmin_fenix_7s">Garmin Fenix 7S</string>
+    <string name="devicetype_garmin_fenix_7x">Garmin Fenix 7X</string>
+    <string name="devicetype_garmin_fenix_7_pro">Garmin Fenix 7 Pro</string>
+    <string name="devicetype_garmin_fenix_8">Garmin Fenix 8</string>
+    <string name="devicetype_garmin_instinct">Garmin Instinct</string>
+    <string name="devicetype_garmin_instinct_solar">Garmin Instinct Solar</string>
+    <string name="devicetype_garmin_instinct_2">Garmin Instinct 2</string>
+    <string name="devicetype_garmin_instinct_2s">Garmin Instinct 2S</string>
+    <string name="devicetype_garmin_instinct_2s_solar">Garmin Instinct 2S Solar</string>
+    <string name="devicetype_garmin_instinct_2x_solar">Garmin Instinct 2X Solar</string>
+    <string name="devicetype_garmin_instinct_2_solar">Garmin Instinct 2 Solar</string>
+    <string name="devicetype_garmin_instinct_2_soltac">Garmin Instinct 2 SolTac</string>
+    <string name="devicetype_garmin_instinct_crossover">Garmin Instinct Crossover</string>
+    <string name="devicetype_garmin_forerunner_55">Garmin Forerunner 55</string>
+    <string name="devicetype_garmin_forerunner_165">Garmin Forerunner 165</string>
+    <string name="devicetype_garmin_forerunner_235">Garmin Forerunner 235</string>
+    <string name="devicetype_garmin_forerunner_245">Garmin Forerunner 245</string>
+    <string name="devicetype_garmin_forerunner_245_music">Garmin Forerunner 245 Music</string>
+    <string name="devicetype_garmin_forerunner_255">Garmin Forerunner 255</string>
+    <string name="devicetype_garmin_forerunner_255_music">Garmin Forerunner 255 Music</string>
+    <string name="devicetype_garmin_forerunner_255s">Garmin Forerunner 255S</string>
+    <string name="devicetype_garmin_forerunner_255s_music">Garmin Forerunner 255S Music</string>
+    <string name="devicetype_garmin_forerunner_265">Garmin Forerunner 265</string>
+    <string name="devicetype_garmin_forerunner_265s">Garmin Forerunner 265S</string>
+    <string name="devicetype_garmin_forerunner_620">Garmin Forerunner 620</string>
+    <string name="devicetype_garmin_forerunner_955">Garmin Forerunner 955</string>
+    <string name="devicetype_garmin_forerunner_965">Garmin Forerunner 965</string>
+    <string name="devicetype_garmin_swim_2">Garmin Swim 2</string>
+    <string name="devicetype_garmin_vivoactive_3">Garmin V铆voactive 3</string>
+    <string name="devicetype_garmin_vivoactive_4">Garmin V铆voactive 4</string>
+    <string name="devicetype_garmin_vivoactive_4s">Garmin V铆voactive 4S</string>
+    <string name="devicetype_garmin_vivoactive_5">Garmin V铆voactive 5</string>
+    <string name="devicetype_garmin_vivosmart_5">Garmin V铆vosmart 5</string>
+    <string name="devicetype_garmin_vivosport">Garmin V铆vosport</string>
+    <string name="devicetype_vibratissimo">Vibratissimo</string>
+    <string name="devicetype_um25">UM-25</string>
+    <string name="devicetype_liveview">LiveView</string>
+    <string name="devicetype_hplus">HPlus</string>
+    <string name="devicetype_makibes_f68">Makibes F68</string>
+    <string name="devicetype_exrizu_k8">Exrizu K8</string>
+    <string name="devicetype_q8">Q8</string>
+    <string name="devicetype_no1_f1">No.1 F1</string>
+    <string name="devicetype_teclast_h30">Teclast H30</string>
+    <string name="devicetype_xwatch">XWatch</string>
+    <string name="devicetype_qhybrid">Fossil Q Hybrid</string>
+    <string name="devicetype_mykronoz_zetime">MyKronoz ZeTime</string>
+    <string name="devicetype_id115">ID115</string>
+    <string name="devicetype_watch9">Watch 9</string>
+    <string name="devicetype_watchx">Watch X</string>
+    <string name="devicetype_watchxplus">Watch X Plus</string>
+    <string name="devicetype_roidmi">Roidmi</string>
+    <string name="devicetype_roidmi3">Roidmi 3</string>
+    <string name="devicetype_y5">Y5</string>
+    <string name="devicetype_casioecbs100">Casio ECB-S100</string>
+    <string name="devicetype_casiogb6900">Casio GB-6900</string>
+    <string name="devicetype_casiogbx100">Casio GBX-100</string>
+    <string name="devicetype_casiogwb5600">Casio GW-B5600</string>
+    <string name="devicetype_casiogmwb5000">Casio GMW-B5000</string>
+    <string name="devicetype_mismartscale">Mi Smart Scale 2</string>
+    <string name="devicetype_micompositionscale">Mi Body Composition Scale 2</string>
+    <string name="devicetype_itag">iTag</string>
+    <string name="devicetype_idasen">IKEA Idasen Desk</string>
+    <string name="devicetype_bfh16">BFH-16</string>
+    <string name="devicetype_mijia_lywsd02">Mijia Smart Clock</string>
+    <string name="devicetype_mijia_lywsd03">Mijia Temperature and Humidity Sensor 2</string>
+    <string name="devicetype_mijia_xmwsdj04">Mijia Temperature and Humidity Sensor 2 (E-ink)</string>
+    <string name="devicetype_mijia_mho_c303">Mijia MHO-C303</string>
+    <string name="devicetype_makibes_hr3">Makibes HR3</string>
+    <string name="devicetype_banglejs">Bangle.js</string>
+    <string name="devicetype_tlw64">TLW64</string>
+    <string name="devicetype_pinetime_jf">PineTime (JF Firmware)</string>
+    <string name="devicetype_sonyswr12">Sony SWR12</string>
+    <string name="devicetype_waspos">Wasp-os</string>
+    <string name="devicetype_smaq2oss">SMA-Q2 OSS</string>
+    <string name="devicetype_fitpro">FitPro</string>
+    <string name="devicetype_colacao21">ColaCao 2021</string>
+    <string name="devicetype_colacao23">ColaCao 2023</string>
+    <string name="devicetype_domyos_t540">Domyos T540</string>
+    <string name="devicetype_sony_wh_1000xm2">Sony WH-1000XM2</string>
+    <string name="devicetype_sony_wh_1000xm3">Sony WH-1000XM3</string>
+    <string name="devicetype_sony_wh_1000xm4">Sony WH-1000XM4</string>
+    <string name="devicetype_sony_wh_1000xm5">Sony WH-1000XM5</string>
+    <string name="devicetype_sony_wf_sp800n">Sony WF-SP800N</string>
+    <string name="devicetype_sony_wf_1000xm3">Sony WF-1000XM3</string>
+    <string name="devicetype_sony_wf_1000xm4">Sony WF-1000XM4</string>
+    <string name="devicetype_sony_wf_1000xm5">Sony WF-1000XM5</string>
+    <string name="devicetype_sony_wf_c500">Sony WF-C500</string>
+    <string name="devicetype_sony_wf_c700n">Sony WF-C700N</string>
+    <string name="devicetype_sony_wi_c100">Sony WI-C100</string>
+    <string name="devicetype_sony_wi_sp600n">Sony WI-SP600N</string>
+    <string name="devicetype_sony_linkbuds">Sony LinkBuds</string>
+    <string name="devicetype_sony_linkbuds_s">Sony LinkBuds S</string>
+    <string name="devicetype_soundcore_liberty3_pro">Soundcore Liberty 3 Pro</string>
+    <string name="devicetype_soundcore_liberty4_nc">Soundcore Liberty 4 NC</string>
+    <string name="devicetype_soundcore_motion300">Soundcore Motion 300</string>
+    <string name="devicetype_moondrop_space_travel">Moondrop Space Travel</string>
+    <string name="devicetype_binary_sensor">Binary sensor</string>
+    <string name="devicetype_honor_band3">Honor Band 3</string>
+    <string name="devicetype_honor_band4">Honor Band 4</string>
+    <string name="devicetype_honor_band5">Honor Band 5</string>
+    <string name="devicetype_honor_band6">Honor Band 6</string>
+    <string name="devicetype_honor_band7">Honor Band 7</string>
+    <string name="devicetype_honor_magicwatch2">Honor MagicWatch 2</string>
+    <string name="devicetype_honor_watchgs3">Honor Watch GS 3</string>
+    <string name="devicetype_honor_watchgspro">Honor Watch GS Pro</string>
+    <string name="devicetype_huawei_band_aw70">Huawei Band (AW70)</string>
+    <string name="devicetype_huawei_band6">Huawei Band 6</string>
+    <string name="devicetype_huawei_band7">Huawei Band 7</string>
+    <string name="devicetype_huawei_band8">Huawei Band 8</string>
+    <string name="devicetype_huawei_band9">Huawei Band 9</string>
+    <string name="devicetype_huawei_watch_gt">Huawei Watch GT</string>
+    <string name="devicetype_huawei_band2pro">Huawei Band 2 (Pro)</string>
+    <string name="devicetype_huawei_band3pro">Huawei Band 3 (Pro)</string>
+    <string name="devicetype_huawei_band4pro">Huawei Band 4 (Pro)</string>
+    <string name="devicetype_huawei_watchgt2">Huawei Watch GT 2 (Pro)</string>
+    <string name="devicetype_huawei_watchgt2e">Huawei Watch GT 2e</string>
+    <string name="devicetype_huawei_talk_band_b6">Huawei Talk Band B6</string>
+    <string name="devicetype_huawei_watchd2">Huawei Watch D2</string>
+    <string name="devicetype_huawei_watchgt3">Huawei Watch GT 3 (Pro)</string>
+    <string name="devicetype_huawei_watchgt4">Huawei Watch GT 4</string>
+    <string name="devicetype_huawei_watchgt5">Huawei Watch GT 5 (Pro)</string>
+    <string name="devicetype_huawei_watchgtrunner">Huawei Watch GT Runner</string>
+    <string name="devicetype_huawei_watchgtcyber">Huawei Watch GT Cyber</string>
+    <string name="devicetype_huawei_watchfit">Huawei Watch Fit</string>
+    <string name="devicetype_huawei_watchfit2">Huawei Watch Fit 2</string>
+    <string name="devicetype_huawei_watchfit3">Huawei Watch Fit 3</string>
+    <string name="devicetype_huawei_watchultimate">Huawei Watch Ultimate</string>
+    <string name="devicetype_huawei_watch3">Huawei Watch 3 (Pro)</string>
+    <string name="devicetype_huawei_watch4pro">Huawei Watch 4 (Pro)</string>
+    <string name="devicetype_huawei_freebuds_5i">Huawei FreeBuds 5i</string>
+    <string name="devicetype_femometer_vinca2">Femometer Vinca II</string>
+    <string name="devicetype_xiaomi_watch_lite">Xiaomi Watch Lite</string>
+    <string name="devicetype_redmiwatch3active">Redmi Watch 3 Active</string>
+    <string name="devicetype_redmiwatch3">Redmi Watch 3</string>
+    <string name="devicetype_redmi_buds_5_pro">Redmi Buds 5 Pro</string>
+    <string name="devicetype_redmi_smart_band_2">Redmi Smart Band 2</string>
+    <string name="devicetype_redmi_watch_2">Redmi Watch 2</string>
+    <string name="devicetype_redmi_watch_2_lite">Redmi Watch 2 Lite</string>
+    <string name="devicetype_redmi_smart_band_pro">Redmi Smart Band Pro</string>
+    <string name="devicetype_redmi_watch_4">Redmi Watch 4</string>
+    <string name="devicetype_redmi_watch_5_active">Redmi Watch 5 Active</string>
+    <string name="devicetype_redmi_watch_5_lite">Redmi Watch 5 Lite</string>
+    <string name="devicetype_colmi_r02">Colmi R02</string>
+    <string name="devicetype_colmi_r03">Colmi R03</string>
+    <string name="devicetype_colmi_r06">Colmi R06</string>
+    <string name="devicetype_colmi_r09">Colmi R09</string>
+    <string name="devicetype_colmi_r10">Colmi R10</string>
+    <string name="devicetype_bandw_pseries">Bowers and Wilkins P series</string>
+    <string name="choose_auto_export_location">Choose export location</string>
+    <string name="notification_channel_name">General</string>
+    <string name="notification_channel_high_priority_name">High-priority</string>
+    <string name="notification_channel_transfer_name">Data transfer</string>
+    <string name="notification_channel_low_battery_name">Low battery</string>
+    <string name="notification_channel_full_battery_name">Full battery</string>
+    <string name="notification_channel_gps">GPS tracking</string>
+    <string name="notification_gps_title">Gadgetbridge GPS</string>
+    <string name="notification_gps_text">Sending GPS location to %1$d devices</string>
+    <string name="devicetype_amazfit_gts">Amazfit GTS</string>
+    <string name="devicetype_amazfit_vergel">Amazfit Verge Lite</string>
+    <string name="devicetype_sg2">Lemfo SG2</string>
+    <string name="devicetype_lefun">Lefun</string>
+    <string name="devicetype_bohemic_smart_bracelet">Bohemic Smart Bracelet</string>
+    <string name="devicetype_vivitar_hr_bp_monitor_activity_tracker">Vivitar HR &amp; BP Monitor Activity Tracker</string>
+    <string name="devicetype_hama_fit6900">Hama Fit6900</string>
+    <!-- Menus on the smart device -->
+    <string name="menuitem_nothing">Nothing</string>
+    <string name="menuitem_status">Status</string>
+    <string name="menuitem_notifications">Notifications</string>
+    <string name="menuitem_activity">Workout History</string>
+    <string name="menuitem_weather">Weather</string>
+    <string name="menuitem_aqi">Air Quality Index</string>
+    <string name="menuitem_forecast">Forecast</string>
+    <string name="menuitem_last_workout">Last workout</string>
+    <string name="menuitem_total_workout">Total workout</string>
+    <string name="menuitem_vo2_max">VO鈧� Max</string>
+    <string name="menuitem_recommendation">Recommendation</string>
+    <string name="menuitem_zepp_coach">Zepp Coach</string>
+    <string name="menuitem_zepp_pay">Zepp Pay</string>
+    <string name="menuitem_thermometer">Thermometer</string>
+    <string name="menuitem_readiness">Readiness</string>
+    <string name="menuitem_body_composition">Body composition</string>
+    <string name="menuitem_workout_shortcuts">Workout shortcuts</string>
+    <string name="menuitem_apps_shortcuts">Apps shortcuts</string>
+    <string name="menuitem_map">Map</string>
+    <string name="menuitem_heart_rate_push">Heart Rate Push</string>
+    <string name="menuitem_breathing">Breathing</string>
+    <string name="menuitem_cycles">Cycle Tracking</string>
+    <string name="menuitem_alarm">Alarm</string>
+    <string name="menuitem_timer">Countdown</string>
+    <string name="menuitem_compass">Compass</string>
+    <string name="menuitem_settings">Settings</string>
+    <string name="menuitem_alipay">Alipay</string>
+    <string name="menuitem_wechat_pay">WeChat Pay</string>
+    <string name="menuitem_cards">Cards</string>
+    <string name="menuitem_mi_ai">MI AI</string>
+    <string name="preferences_qhybrid_settings">Q Hybrid Settings</string>
+    <string name="preferences_qhybrid_settings_summary">Legacy settings for Q Hybrid watches</string>
+    <string name="menuitem_music">Music</string>
+    <string name="menuitem_more">More</string>
+    <string name="menuitem_nfc">NFC</string>
+    <string name="menuitem_stress">Stress</string>
+    <string name="menuitem_stress_simple">Stress (simple)</string>
+    <string name="menuitem_stress_segmented">Stress (segmented)</string>
+    <string name="menuitem_stress_breakdown">Stress (breakdown)</string>
+    <string name="menuitem_calories_segmented">Calories (segmented)</string>
+    <string name="menuitem_calories_active_goal">Calories goal (active)</string>
+    <string name="menuitem_pai">PAI</string>
+    <string name="menuitem_hr">Heart Rate</string>
+    <string name="menuitem_spo2">SpO2</string>
+    <string name="menuitem_eventreminder">Event Reminder</string>
+    <string name="menuitem_workout">Workout</string>
+    <string name="menuitem_unknown">Unknown</string>
+    <string name="menuitem_worldclock">World Clock</string>
+    <string name="menuitem_findphone">Find Phone</string>
+    <string name="menuitem_mutephone">Mute Phone</string>
+    <string name="menuitem_takephoto">Camera Remote</string>
+    <string name="menuitem_alexa">Alexa</string>
+    <string name="menuitem_dnd">DND</string>
+    <string name="menuitem_stopwatch">Stopwatch</string>
+    <string name="menuitem_goal">Activity Goal</string>
+    <string name="menuitem_sleep">Sleep</string>
+    <string name="menuitem_pomodoro">Pomodoro Tracker</string>
+    <string name="menuitem_events">Events</string>
+    <string name="menuitem_widgets">Widgets</string>
+    <string name="menuitem_temperature">Temperature</string>
+    <string name="menuitem_weight">Weight</string>
+    <string name="menuitem_barometer">Barometer</string>
+    <string name="menuitem_flashlight">Flashlight</string>
+    <string name='menuitem_email'>E-mail</string>
+    <string name='menuitem_countdown'>Countdown</string>
+    <string name='menuitem_personal_activity_intelligence'>Personal Activity Intelligence</string>
+    <string name='menuitem_workout_history'>Workout History</string>
+    <string name='menuitem_female_health'>Female Health</string>
+    <string name='menuitem_workout_status'>Workout Status</string>
+    <string name='menuitem_calendar'>Calendar</string>
+    <string name='menuitem_todo'>To-Do</string>
+    <string name='menuitem_voice_memos'>Voice Memos</string>
+    <string name='menuitem_sun_moon'>Sun &amp; Moon</string>
+    <string name='menuitem_one_tap_measuring'>One-tap Measuring</string>
+    <string name='menuitem_offline_voice'>Offline Voice</string>
+    <string name='menuitem_membership_cards'>Membership Cards</string>
+    <string name='menuitem_phone'>Phone</string>
+    <string name='menuitem_theater_mode'>Theater Mode</string>
+    <string name='menuitem_volume'>Volume</string>
+    <string name='menuitem_screen_always_lit'>Screen Always Lit</string>
+    <string name='menuitem_brightness'>Brightness</string>
+    <string name='menuitem_bluetooth'>Bluetooth</string>
+    <string name='menuitem_wifi'>Wi-Fi</string>
+    <string name='menuitem_lockscreen'>Lockscreen</string>
+    <string name='menuitem_eject_water'>Eject Water</string>
+    <string name='menuitem_headphone'>Headphone</string>
+    <string name='menuitem_buzzer_intensity'>Buzzer Intensity</string>
+    <string name='menuitem_night_display'>Night Display</string>
+    <string name="menuitem_unknown_app">Unknown (%s)</string>
+    <string name="menuitem_unsupported">[UNSUPPORTED] %s</string>
+    <string name="zepp_os_watchface_red_fantasy">Red Fantasy</string>
+    <string name="zepp_os_watchface_multiple_data">Multiple Data</string>
+    <string name="zepp_os_watchface_rush">Rush</string>
+    <string name="zepp_os_watchface_minimalist">Minimalist</string>
+    <string name="zepp_os_watchface_simplicity_data">Simplicity Data</string>
+    <string name="zepp_os_watchface_vibrant">Vibrant</string>
+    <string name="zepp_os_watchface_business_style">Business Style</string>
+    <string name="zepp_os_watchface_emerald_moonlight">Emerald Moonlight</string>
+    <string name="zepp_os_watchface_rotating_earth">Rotating Earth</string>
+    <string name="zepp_os_watchface_superposition">superposition</string>
+    <string name="zepp_os_watchface_vast_sky">Vast Sky</string>
+    <string name="zepp_os_watchface_lightning_flash">Lightning flash</string>
+    <string name="zepp_os_watchface_free_combination">Free combination</string>
+    <string name="zepp_os_watchface_pure_white">Pure white</string>
+    <string name="zepp_os_watchface_guider">Guider</string>
+    <string name="zepp_os_watchface_city_of_speed">City of speed</string>
+    <string name="zepp_os_watchface_starry_sky">Starry sky</string>
+    <string name="zepp_os_watchface_the_ultima">The ultima</string>
+    <string name="yesterdays_activity">Yesterday\'s Activity</string>
+    <string name="watch9_time_minutes">Minutes:</string>
+    <string name="watch9_time_hours">Hours:</string>
+    <string name="watch9_time_seconds">Seconds:</string>
+    <string name="watch9_calibration_hint">Set the time your device is showing to you right now.</string>
+    <string name="watch9_calibration_button">Calibrate</string>
+    <string name="title_activity_watch9_pairing">Watch 9 pairing</string>
+    <string name="title_activity_watch9_calibration">Watch 9 calibration</string>
+    <string name="pref_title_contextual_arabic">Contextual Arabic</string>
+    <string name="pref_summary_contextual_arabic">Enable this to support contextual Arabic</string>
+    <string name="preferences_rtl_settings">Right To Left Support</string>
+    <string name="share_log">Share log</string>
+    <string name="share_log_warning">Please keep in mind Gadgetbridge logs files that may contain lots of personal info, including but not limited to health data, unique identifiers (such as a device\'s MAC address), music preferences, etc. Consider editing the file and removing this info before sending the file to a public issue report.</string>
+    <string name="share_log_not_enabled_message">You must first enable logging in the Settings - Write log files</string>
+    <string name="warning">Warning!</string>
+    <string name="note">Note</string>
+    <string name="no_data">No data</string>
+    <!-- LED Color -->
+    <string name="preferences_led_color">LED Color</string>
+    <!-- FM transmitters -->
+    <string name="preferences_fm_frequency">FM Frequency</string>
+    <string name="pref_invalid_frequency_title">Invalid frequency</string>
+    <string name="pref_invalid_frequency_message">Please enter a frequency between 87.5 and 108.0</string>
+    <string name="language_and_region_prefs">Language and region settings</string>
+    <string name="prefs_fm_preset_instructions">Long press the button to store preset</string>
+    <string name="prefs_fm_presets_presets">Presets</string>
+    <!--Notification Filter Black-/ Whitelist-->
+    <string name="title_activity_notification_filter">Notification Filter</string>
+    <string name="toast_app_must_not_be_selected">App must not be selected to be configured</string>
+    <string name="toast_app_must_be_selected">App must be selected to be configured</string>
+    <string name="edittext_notification_filter_words_hint">Enter desired words, new line for each</string>
+    <string name="toast_notification_filter_saved_successfully">Notification filter saved</string>
+    <string name="filter_mode_none">Do not filter</string>
+    <string name="filter_mode_whitelist">Show when words are contained</string>
+    <string name="filter_mode_blacklist">Block when words are contained</string>
+    <string name="filter_submode_at_least_one">At least one of the words</string>
+    <string name="filter_submode_all">All of the words</string>
+    <string name="toast_notification_filter_words_empty_hint">Please enter at least one word</string>
+    <string name="filter_mode">Filter Mode</string>
+    <string name="mode_configuration">Mode Configuration</string>
+    <string name="save_configuration">Save Configuration</string>
+    <!-- App Widgets-->
+    <string name="appwidget_not_connected">Not connected, alarm not set.</string>
+    <string name="widget_settings_select_device_title">Select device</string>
+    <string name="appwidget_text">Zzz</string>
+    <string name="add_widget">Add widget</string>
+    <string name="appwidget_setting_alarm">Setting alarm for %1$02d:%2$02d</string>
+    <string name="appwidget_sleep_alarm_widget_label">Sleep Alarm</string>
+    <string name="widget_listing_label">Status and Alarms</string>
+    <string name="widget_set_alarm_after">Set alarm after:</string>
+    <string name="widget_5_minutes">5 minutes</string>
+    <string name="widget_10_minutes">10 minutes</string>
+    <string name="widget_20_minutes">20 minutes</string>
+    <string name="widget_1_hour">1 hour</string>
+    <string name="quick_alarm">Quick alarm</string>
+    <string name="quick_alarm_description">Alarm from widget</string>
+    <string name="icon_placeholder" translatable="false">Icon</string>
+    <string name="watch_not_connected">Watch not connected</string>
+    <string name="qhybrid_vibration_strength">vibration strength:</string>
+    <string name="qhybrid_goal_in_steps">Goal in steps</string>
+    <string name="qhybrid_time_shift">time shift</string>
+    <string name="qhybrid_second_timezone_offset_relative_to_utc">second timezone offset relative to UTC</string>
+    <string name="qhybrid_overwrite_buttons">overwrite buttons</string>
+    <string name="qhybrid_use_activity_hand_as_notification_counter">use activity hand as notification counter</string>
+    <string name="qhybrid_prompt_million_steps">Please set the step count to a million to activate that.</string>
+    <string name="qhybrid_buttons_overwrite_success">Buttons overwritten</string>
+    <string name="qhybrid_buttons_overwrite_error">Error overwriting buttons</string>
+    <string name="qhybrid_offset_timezone">offset timezone by</string>
+    <string name="qhybrid_changes_delay_prompt">change might take some seconds鈥�</string>
+    <string name="qhybrid_offset_time_by">offset time by</string>
+    <string name="hr_widget_heart_rate">Heart rate</string>
+    <string name="hr_widget_steps">Steps</string>
+    <string name="hr_widget_date">Date</string>
+    <string name="hr_widget_active_minutes">Active minutes</string>
+    <string name="hr_widget_calories">Calories</string>
+    <string name="hr_widget_battery">Battery</string>
+    <string name="hr_widget_weather">Weather</string>
+    <string name="hr_widget_nothing">Nothing</string>
+    <string name="hr_appname_wellness">Wellness</string>
+    <string name="hr_appname_workout">Workout</string>
+    <string name="hr_appname_stopwatch">Stopwatch</string>
+    <string name="hr_appname_commute">Commute</string>
+    <string name="pref_title_upper_button_long_press_action">Upper Button long press action</string>
+    <string name="pref_title_lower_button_short_press_action">Lower Button short press action</string>
+    <string name="pref_title_upper_button_function_short">Upper Button short</string>
+    <string name="pref_title_middle_button_function_short">Middle Button short</string>
+    <string name="pref_title_lower_button_function_short">Lower Button short</string>
+    <string name="pref_title_upper_button_function_long">Upper Button long</string>
+    <string name="pref_title_middle_button_function_long">Middle Button long</string>
+    <string name="pref_title_lower_button_function_long">Lower Button long</string>
+    <string name="pref_title_upper_button_function_double">Upper Button double</string>
+    <string name="pref_title_middle_button_function_double">Middle Button double</string>
+    <string name="pref_title_lower_button_function_double">Lower Button double</string>
+    <string name="prefs_button_single_press_action_selection_title">Event 1 action</string>
+    <string name="prefs_button_double_press_action_selection_title">Event 2 action</string>
+    <string name="prefs_button_triple_press_action_selection_title">Event 3 action</string>
+    <string name="prefs_button_variable_actions">Detailed button press settings</string>
+    <string name="prefs_button_long_press_action_selection_title">Long press button action</string>
+    <string name="error_no_location_access">Location access must be granted and enabled for scanning to work properly</string>
+    <string name="error_no_bluetooth_scan">Bluetooth scan access must be granted and enabled for scanning to work properly</string>
+    <string name="error_no_bluetooth_connect">Bluetooth connection access must be granted and enabled for scanning to work properly</string>
+    <string name="pref_qhybrid_title_widget_draw_circles">Draw widget circles</string>
+    <string name="pref_qhybrid_save_raw_activity_files">Save raw activity files</string>
+    <string name="hr_widget_last_notification">Last notification</string>
+    <string name="homepage_url" translatable="false">Homepage: <a href="https://gadgetbridge.org/">https://gadgetbridge.org/</a></string>
+    <string name="codeberg_url" translatable="false">Code: <a href="https://codeberg.org/Freeyourgadget/Gadgetbridge">https://codeberg.org/Freeyourgadget/Gadgetbridge</a></string>
+    <string name="fdroid_url" translatable="false">F-Droid: <a href="https://f-droid.org/packages/nodomain.freeyourgadget.gadgetbridge/">https://f-droid.org/packages/nodomain.freeyourgadget.gadgetbridge/</a></string>
+    <string name="about_title">About</string>
+    <string name="about_version">Version %s</string>
+    <string name="about_hash">Commit %s</string>
+    <string name="gpx_receiver_activity_title">GPX Receiver Gadgetbridge</string>
+    <string name="text_receiver_activity_title">Send text to device</string>
+    <string name="gpx_receiver_files_received">GPX file(s) received:</string>
+    <string name="gpx_receiver_overwrite_some_files">Some file(s) already exist. Overwrite?</string>
+    <string name="about_core_team_title">Core Team (in order of first code contribution)</string>
+    <string name="about_contributors">Contributors</string>
+    <string name="about_core_team_members" translatable="false">Andreas Shimokawa\nCarsten Pfeiffer\nDaniele Gobbetti</string>
+    <string name="about_additional_device_support">Additional device support</string>
+    <string name="about_additional_contributors" translatable="false">Joa虄o Paulo Barraca (HPlus)\nVitaly Svyastyn (NO.1 F1)\nSami Alaoui (Teclast H30)\n鈥渓adbsoft鈥� (XWatch)\nSebastian Kranz (ZeTime)\nVadim Kaushan (ID115)\n鈥渕axirnilian鈥� (Lenovo Watch 9)\n鈥渒siwczynski鈥�, 鈥渕kusnierz鈥�, 鈥渕amutcho鈥� (Lenovo Watch X Plus)\nAndreas B枚hler (Casio GB-6900B, Casio GB-5600B, Casio GBX-100)\nJean-Fran莽ois Greffier (Mi Scale 2)\nJohannes Schmitt (BFH-16)\nLukas Schwichtenberg (Makibes HR3)\nDaniel Dakhno (Fossil Q Hybrid, Fossil Hybrid HR)\nGordon Williams (Bangle.js)\nPavel Elagin (JYou Y5)\nTaavi Eom盲e (iTag)\nJohannes Krude(Casio GW-B5600)</string>
+    <string name="about_additional_contributions">Many thanks to all unlisted contributors for contributing code, translations, support, ideas, motivation, bug reports, money鈥� 鉁�</string>
+    <string name="about_links">Links</string>
+    <string name="permission_request">%1$s allows you to send messages and other data from Android to your device. To do this it requires permission to access that data and without it, it might not function properly.\n\nYou\'ll now be presented with a few Android dialogs requesting those permissions.\n\nPlease tap \'%2$s\' to continue.</string>
+    <string name="permission_granting_mandatory">All these permissions are required and instability might occur if not granted</string>
+    <string name="permission_notification_listener">%1$s needs access to Notifications in order to display them on your watch when your phone\'s screen is off.\n\nPlease tap \'%2$s\' then \'%1$s\' and enable \'Allow Notification Access\', then tap \'Back\' to return to %1$s.</string>
+    <string name="permission_notification_policy_access">%1$s needs access to Do Not Disturb settings in order to honour them on your watch when your phone\'s screen is off.\n\nPlease tap \'%2$s\' then \'%1$s\' and enable \'Allow Do Not Disturb\', then tap \'Back\' to return to %1$s.</string>
+    <string name="permission_location">%1$s needs access to your location in the background to allow it to stay connected to your watch even when your screen is off.\n\nPlease choose \'%2$s\' in the following screen, then tap \'Back\' to return to %1$s.</string>
+    <string name="permission_display_over_other_apps">%1$s needs permission to display over other apps in order to let Bangle.js watches start activities via intents when %1$s is in the background.\n\nThis can be used to start a music app and play a song, and many other things.\n\nPlease tap \'%2$s\' then \'%1$s\' and enable \'Allow display over other apps\', then tap \'Back\' to return to %1$s.\n\nTo stop %1$s asking for permissions go to \'Settings\' and uncheck \'Check permission status\'.\n\nMake sure to grant %1$s the permissions needed to function as you expect.</string>
+    <string name="error_version_check_extreme_caution">CAUTION: Error when checking version information! You should not continue! Saw version name \"%s\"</string>
+    <string name="require_location_provider">Location must be enabled</string>
+    <string name="companiondevice_pairing">CompanionDevice Pairing</string>
+
+    <string name="error_background_service">Failed to start background service</string>
+    <string name="error_background_service_reason_truncated">Starting the background service failed because鈥�</string>
+    <string name="pref_crash_notification_title">Notify on crash</string>
+    <string name="pref_crash_notification_summary">When the app crashes, display a notification with the error</string>
+    <string name="app_crash_notification_title">%1s has crashed</string>
+    <string name="app_crash_share_stacktrace">Share error</string>
+    <string name="device_is_currently_bonded">ALREADY BONDED</string>
+    <string name="device_requires_key">KEY REQUIRED, LONG PRESS TO ENTER</string>
+    <string name="device_unsupported">UNSUPPORTED</string>
+    <string name="device_experimental">EXPERIMENTAL</string>
+    <string name="error_background_service_reason">Starting the background service failed because of an exception - click here for more information.\n\nError:</string>
+    <string name="pref_check_permission_status">Check permission status</string>
+    <string name="pref_check_permission_status_summary">Check and ask for missing permissions even when they might not be instantly needed. Disable this only if your devices actually doesn\'t support any of these features. Not granting a permission might cause issues!</string>
+    <string name="pref_show_changelog">Show changelog on startup</string>
+    <string name="pref_show_changelog_summary">Display the changelog since last version after Gadgetbridge is updated</string>
+    <string name="error_exporting_device_preferences">Error exporting device specific preferences</string>
+    <string name="error_setting_alias">Error setting alias: </string>
+    <string name="error_retrieving_devices_database">Error retrieving devices from database</string>
+    <string name="discover_unsupported_devices">Discover unsupported devices</string>
+    <string name="discover_unsupported_devices_description">Enabling this option will display all discovered bluetooth devices when scanning. Short tap will copy device name and mac address to clipboard. Long press will launch `Add test device` dialog. Can cause potential app freezing issues.</string>
+    <string name="error_location_enabled_mandatory">Location must be turned on to scan for devices</string>
+    <string name="sonyswr12_settings_title">Sony SWR12 Settings</string>
+    <string name="sonyswr12_settings_low_vibration">Low vibration enabled</string>
+    <string name="sonyswr12_settings_low_vibration_summary">Enable low intensity of vibration on wristband</string>
+    <string name="sonyswr12_settings_stamina">Power saving mode on</string>
+    <string name="sonyswr12_settings_stamina_summary">Power saving mode turns off periodic auto measuring of heart rate thus increases working time</string>
+    <string name="sonyswr12_settings_alarm_interval">Smart alarm interval in minutes</string>
+    <string name="sonyswr12_settings_alarm_interval_summary">Smart alarm interval is interval before of installed alarm. In this interval device is trying to detect lightest phase of sleep to awake user</string>
+    <!-- activity summary labels-->
+    <string name="distanceMeters">Distance</string>
+    <string name="ascentMeters">Uphill</string>
+    <string name="descentMeters">Downhill</string>
+    <string name="ascentDistance">Uphill distance</string>
+    <string name="descentDistance">Downhill distance</string>
+    <string name="flatDistance">Flat distance</string>
+    <string name="elevationGain">Elevation gain</string>
+    <string name="elevationLoss">Elevation loss</string>
+    <string name="maxAltitude">Maximum</string>
+    <string name="minAltitude">Minimum</string>
+    <string name="averageAltitude">Average</string>
+    <string name="steps">Steps</string>
+    <string name="steps_avg">Steps AVG</string>
+    <string name="steps_total">Steps Total</string>
+    <string name="distance_avg">Distance AVG</string>
+    <string name="distance_total">Distance Total</string>
+    <string name="activeSeconds">Active</string>
+    <string name="caloriesBurnt">Calories</string>
+    <string name="maxSpeed">Maximum</string>
+    <string name="minSpeed">Minimum</string>
+    <string name="minPace">Slowest Pace</string>
+    <string name="maxPace">Fastest Pace</string>
+    <string name="totalStride">Total stride</string>
+    <string name="averageHR">Heartrate</string>
+    <string name="maxHR">Max Heartrate</string>
+    <string name="minHR">Min Heartrate</string>
+    <string name="averageKMPaceSeconds">Pace</string>
+    <string name="HeartRateZones">Heart Rate Zones</string>
+    <string name="hrZoneNa">N/A</string>
+    <string name="hrZoneWarmUp">Warm-Up</string>
+    <string name="hrZoneFatBurn">Fat Burn</string>
+    <string name="hrZoneEasy">Easy</string>
+    <string name="hrZoneAerobic">Aerobic</string>
+    <string name="hrZoneAnaerobic">Anaerobic</string>
+    <string name="hrZoneThreshold">Threshold</string>
+    <string name="hrZoneExtreme">Extreme</string>
+    <string name="hrZoneMaximum">Maximum</string>
+    <string name="average_respiration_rate">Respiration Rate</string>
+    <string name="max_respiration_rate">Max Respiration Rate</string>
+    <string name="min_respiration_rate">Min Respiration Rate</string>
+    <string name="hrv_sdrr">HRV SDRR</string>
+    <string name="hrv_rmssd">HRV RMSSD</string>
+    <string name="breaths_per_min">breaths/min</string>
+    <string name="TrainingEffect">Training Effect</string>
+    <string name="aerobicTrainingEffect">Aerobic Effect</string>
+    <string name="anaerobicTrainingEffect">Anaerobic Effect</string>
+    <string name="currentWorkoutLoad">Workout Load</string>
+    <string name="maximumOxygenUptake">Maximum Oxygen Uptake</string>
+    <string name="estimatedSweatLoss">Estimated Sweat Loss</string>
+    <string name="lactateThresholdHeartRate">Lactate Threshold Heart Rate</string>
+    <string name="recoveryTime">Recovery Time</string>
+    <string name="averageStride">Average Stride</string>
+    <string name="maxStride">Max Stride</string>
+    <string name="minStride">Min Stride</string>
+    <string name="averageCadence">Average Cadence</string>
+    <string name="maxCadence">Max Cadence</string>
+    <string name="minCadence">Min Cadence</string>
+    <string name="averageStrokeDistance">Average Stroke Distance</string>
+    <string name="averageStrokesPerSecond">Average Strokes</string>
+    <string name="avgStrokeRate">Average Stroke Rate</string>
+    <string name="maxStrokeRate">Max Stroke Rate</string>
+    <string name="strokes">Total Strokes</string>
+    <string name="avgJumpRate">Average Jump Rate</string>
+    <string name="maxJumpRate">Max Jump Rate</string>
+    <string name="totalJumps">Total Jumps</string>
+    <string name="averageLapPace">Average Lap Pace</string>
+    <string name="swolfIndex">SWOLF</string>
+    <string name="swolfAvg">Average swolf</string>
+    <string name="swolfMax">Maximum swolf</string>
+    <string name="swolfMin">Minimum swolf</string>
+    <string name="swimStyle">Swim Style</string>
+    <string name="laneLength">Lane Length</string>
+    <string name="laps">Laps</string>
+    <string name="ascentSeconds">Ascending</string>
+    <string name="descentSeconds">Descending</string>
+    <string name="flatSeconds">Flat</string>
+    <string name="baseAltitude">Base Elevation</string>
+    <string name="averageSpeed">Average Speed</string>
+    <string name="stepRateSum">Sum of step rate</string>
+    <string name="stepRateAvg">Average step rate</string>
+    <string name="stepRateMax">Max step rate</string>
+    <string name="stepLengthAvg">Average Step Length</string>
+    <string name="groundContactTimeAvg">Average ground contact time</string>
+    <string name="impactAvg">Average impact</string>
+    <string name="impactMax">Maximum impact</string>
+    <string name="swingAngleAvg">Average swing angle</string>
+    <string name="foreFootLandings">Fore foot landings</string>
+    <string name="midFootLandings">Mid foot landings</string>
+    <string name="backFootLandings">Back foot landings</string>
+    <string name="eversionAngleAvg">Average eversion angle</string>
+    <string name="eversionAngleMax">Max eversion angle</string>
+    <string name="fmtPaceDistance">Pace %d distance</string>
+    <string name="fmtPaceType">Pace %d type</string>
+    <string name="fmtPacePace">Pace %d pace</string>
+    <string name="fmtPaceCorrection">Pace %d correction</string>
+    <string name="paceCorrection">Correction</string>
+    <string name="fmtPaceTypeAverage">Pace Type %d average</string>
+    <string name="fmtPaceAverage">Pace average</string>
+    <string name="unknownDataEncountered">Unknown data encountered</string>
+    <string name="cyclingPowerAverage">Average cycling power</string>
+    <string name="cyclingPowerMin">Min cycling power</string>
+    <string name="cyclingPowerMax">Max cycling power</string>
+    <string name="workoutSets">Sets</string>
+    <string name="workout_set_i">Set %1d</string>
+    <string name="workout_set_reps">Repetitions</string>
+    <string name="workout_set_repetitions">%1d x</string>
+    <string name="workout_set_repetitions_weight_kg">%1d x %2$.2f kg</string>
+    <string name="workout_set_repetitions_weight_lbs">%1d x %2$.2f lbs</string>
+    <!-- activity summary units-->
+    <string name="meters">m</string>
+    <string name="cm">cm</string>
+    <string name="yard">yard</string>
+    <string name="ft">ft</string>
+    <string name="steps_unit">steps</string>
+    <string name="meters_second">m/s</string>
+    <string name="km_h">km/h</string>
+    <string name="mi_h">mi/h</string>
+    <string name="minutes_mi">min/mi</string>
+    <string name="strokes_second">str/s</string>
+    <string name="strokes_minute">str/min</string>
+    <string name="strokes_unit">str</string>
+    <string name="jumps_minute">jumps/min</string>
+    <string name="jumps_unit">jumps</string>
+    <string name="seconds">sec</string>
+    <string name="milliseconds">milliseconds</string>
+    <string name="milliseconds_ms">ms</string>
+    <string name="swolf_index">swolf index</string>
+    <string name="swim_style">swim style</string>
+    <string name="laps_unit">laps</string>
+    <string name="calories_unit">kcal</string>
+    <string name="seconds_km">sec/km</string>
+    <string name="seconds_m">sec/m</string>
+    <string name="minutes_km">min/km</string>
+    <string name="spm">steps/min</string>
+    <string name="bpm">bpm</string>
+    <string name="km">km</string>
+    <string name="mi">mi</string>
+    <string name="degrees">degrees</string>
+    <string name="Pace">Pace</string>
+    <!-- activity summary groups-->
+    <string name="Strokes">Strokes</string>
+    <string name="Jumps">Jumps</string>
+    <string name="Swimming">Swimming</string>
+    <string name="Distance">Distance</string>
+    <string name="Elevation">Elevation</string>
+    <string name="Speed">Speed</string>
+    <string name="Activity">Activity</string>
+    <string name="Steps">Steps</string>
+    <string name="RunningForm">Running Form</string>
+    <!-- Sports Activity Detail -->
+    <string name="activity_detail_start_label">Start</string>
+    <string name="activity_detail_end_label">End</string>
+    <string name="activity_detail_duration_label">Duration</string>
+    <string name="activity_detail_show_gps_label">Show GPS Track</string>
+    <string name="activity_detail_share_gps_label">Share GPS Track</string>
+    <string name="activity_detail_share_raw_summary">Share Raw Summary</string>
+    <string name="activity_detail_share_raw_details">Share Raw Details</string>
+    <string name="activity_detail_share_json_details">Share JSON Details</string>
+    <string name="gps_track">GPS track</string>
+    <string name="dev_tools">Dev Tools</string>
+    <!-- Device Actions Preferences -->
+    <string name="prefs_events_forwarding_summary">Use device events to trigger actions and Android broadcasts</string>
+    <string name="prefs_events_forwarding_title">Device actions</string>
+    <string name="prefs_events_forwarding_fellsleep">On Fall Asleep</string>
+    <string name="prefs_events_forwarding_fellsleep_broadcast_default_value" translatable="false">nodomain.freeyourgadget.gadgetbridge.FellAsleep</string>
+    <string name="prefs_events_forwarding_wokeup">On Wake Up</string>
+    <string name="prefs_events_forwarding_wokeup_broadcast_default_value" translatable="false">nodomain.freeyourgadget.gadgetbridge.WokeUp</string>
+    <string name="prefs_events_forwarding_startnonwear">On Not Wearing</string>
+    <string name="prefs_events_forwarding_startnonwear_broadcast_default_value" translatable="false">nodomain.freeyourgadget.gadgetbridge.StartNonWear</string>
+    <string name="prefs_events_forwarding_broadcast_title">Broadcast message</string>
+    <string name="prefs_events_forwarding_action_title">Run action</string>
+    <string name="pref_header_filter">Sports Activities Filter</string>
+    <string name="pref_header_statistics">Sports Activities Statistics</string>
+    <string name="activity_filter_date_from">From</string>
+    <string name="activity_filter_date_to">To</string>
+    <string name="activity_filter_reset_filter">Reset Filter</string>
+    <string name="activity_filter_filter_title">Filter</string>
+    <string name="activity_filter_name_contains">Label</string>
+    <string name="activity_filter_apply_filter">Apply Filter</string>
+    <string name="addto_filter">Add to filter</string>
+    <string name="activity_filter_individual_items">Individually selected items</string>
+    <string name="yes">Yes</string>
+    <string name="no">No</string>
+    <string name="activity_summaries_statistics">Statistics</string>
+    <string name="activity_summaries_all_activities">All Activities</string>
+    <string name="sports_activity_quick_filter_this_week">This week</string>
+    <string name="sports_activity_quick_filter_last_week">Previous week</string>
+    <string name="sports_activity_quick_filter_this_month">This month</string>
+    <string name="sports_activity_quick_filter_last_month">Previous month</string>
+    <string name="sports_activity_quick_filter_7days">7 days</string>
+    <string name="sports_activity_quick_filter_30days">30 days</string>
+    <string name="sports_activity_quick_filter_select">Time period</string>
+    <string name="sports_activity_confirm_delete_title">Delete %d activities</string>
+    <string name="sports_activity_confirm_delete_description">Are you sure you want to delete %d activities?</string>
+    <string name="activity_summaries_all_devices">All devices</string>
+    <string name="activity_filter_from_placeholder">distant past</string>
+    <string name="activity_filter_to_placeholder">today</string>
+    <!-- swim styles -->
+    <string name="breaststroke">Breaststroke</string>
+    <string name="freestyle">Freestyle</string>
+    <string name="backstroke">Backstroke</string>
+    <string name="medley">Medley</string>
+    <string name="devicetype_nut_mini">Nut mini</string>
+    <string name="qhybrid_calibration_align_hint">Use the buttons below to align the watch hands to 12:00.</string>
+    <string name="lorem_ipsum" translatable="false">Lorem Ipsum</string>
+    <plurals name="widget_alarm_target_hours">
+        <item quantity="one">%d hour</item>
+        <item quantity="two">%d hours</item>
+        <item quantity="few">%d hours</item>
+        <item quantity="many">%d hours</item>
+        <item quantity="other">%d hours</item>
+    </plurals>
+    <string name="prefs_autolight">Automatic light</string>
+    <string name="prefs_light_duration_longer">Longer light duration</string>
+    <string name="prefs_key_vibration">Key Vibration</string>
+    <string name="prefs_operating_sounds">Operating Sounds</string>
+    <string name="prefs_fake_ring_duration">Fake continuous ringing</string>
+    <string name="prefs_autoremove_message">Automatically remove SMS notifications</string>
+    <string name="fossil_hr_commute_processing">Processing request鈥�</string>
+    <string name="fossil_hr_synced_activity_data">Synchronised activity data</string>
+    <string name="fossil_hr_unavailable_unauthed">Not available in unauthenticated mode</string>
+    <string name="fossil_hr_auth_failed">Authentication failed, limited functionality</string>
+    <string name="qhybrid_title_watchface">Watchface configuration</string>
+    <string name="qhybrid_title_apps">Apps</string>
+    <string name="qhybrid_title_background_image">Background image</string>
+    <string name="qhybrid_title_file_management">File management</string>
+    <string name="qhybrid_title_apps_management">Apps management</string>
+    <string name="qhybrid_title_calibration">Calibration</string>
+    <string name="fossil_hr_warning_firmware_too_new">Some functions are disabled because the firmware of the watch is too new</string>
+    <string name="pref_title_physical_buttons">Physical buttons</string>
+    <string name="pref_summary_physical_buttons">Configure the functionality of the physical buttons on the watch</string>
+    <string name="qhybrid_summary_file_management">Upload and download files</string>
+    <string name="qhybrid_summary_calibration">Calibrate the watch hands</string>
+    <string name="pref_summary_canned_messages_set">Send the messages configured below to your device</string>
+    <string name="pref_summary_canned_messages_dismisscall">Dismiss calls from the watch with an SMS message</string>
+    <string name="fossil_hr_commute_actions_explanation">The actions configured here will appear in the Commute app on your watch. Read the wiki for information on how to handle the intents produced by these actions.</string>
+    <string name="fossil_hr_edit_action_delete">delete</string>
+    <string name="fossil_hr_edit_action">Edit action</string>
+    <string name="fossil_hr_new_action_cancel">cancel</string>
+    <string name="fossil_hr_new_action">New action</string>
+    <string name="qhybrid_calibration_counterclockwise">Counterclockwise</string>
+    <string name="qhybrid_calibration_clockwise">Clockwise</string>
+    <string name="qhybrid_calibration_1_step">1 step</string>
+    <string name="qhybrid_calibration_10_steps">10 steps</string>
+    <string name="qhybrid_calibration_100_steps">100 steps</string>
+    <string name="qhybrid_watchface_configuration_old_firmware">Watchface configuration screen for watches with firmware version DN1.0.2.19r and lower</string>
+    <string name="qhybrid_pref_title_actions">Actions</string>
+    <string name="qhybrid_pref_summary_actions">Actions for the Commute app</string>
+    <string name="pref_title_developer_settings">Developer settings</string>
+    <string name="pref_summary_developer_settings">Settings and functionality used by developers</string>
+    <string name="qhybrid_pref_title_external_intents">Allow dangerous external intents</string>
+    <string name="qhybrid_pref_summary_external_intents">Allows other Android apps to upload/overwrite files</string>
+    <string name="devicetype_amazfit_trex_pro">Amazfit T-Rex Pro</string>
+    <string name="button_watchface_edit_name">Edit name</string>
+    <string name="button_watchface_select_image">Change background image</string>
+    <string name="button_watchface_add_widget">Add widget</string>
+    <string name="button_watchface_settings">Watchface settings</string>
+    <string name="watchface_dialog_title_set_name">Set watchface name</string>
+    <string name="watchface_widget_type_date">Date</string>
+    <string name="watchface_widget_type_weather">Weather</string>
+    <string name="watchface_widget_type_steps">Steps</string>
+    <string name="watchface_widget_type_heart_rate">Heart rate</string>
+    <string name="button_watchface_preview">Preview on watch</string>
+    <string name="button_watchface_save_apply">Save and apply</string>
+    <string name="appmanager_app_edit">Edit</string>
+    <string name="watchface_dialog_widget_cat_generic">Generic</string>
+    <string name="watchface_dialog_widget_cat_position">Position</string>
+    <string name="watchface_dialog_widget_cat_2nd_tz_widget">2nd timezone widget</string>
+    <string name="watchface_dialog_widget_cat_custom_widget">Custom widget</string>
+    <string name="watchface_dialog_widget_type">Type</string>
+    <string name="watchface_dialog_widget_x_coordinate">X coordinate (max 240)</string>
+    <string name="watchface_dialog_widget_y_coordinate">Y coordinate (max 240)</string>
+    <string name="watchface_dialog_widget_presets">Position presets</string>
+    <string name="watchface_dialog_widget_preset_top">Top</string>
+    <string name="watchface_dialog_widget_preset_bottom">Bottom</string>
+    <string name="watchface_dialog_widget_preset_left">Left</string>
+    <string name="watchface_dialog_widget_preset_right">Right</string>
+    <string name="qhybrid_title_watchface_designer">Watchface designer</string>
+    <string name="watchface_dialog_widget_color">Color</string>
+    <string name="watchface_dialog_widget_color_white">White</string>
+    <string name="watchface_dialog_widget_color_black">Black</string>
+    <string name="watchface_dialog_title_settings">Watchface settings</string>
+    <string name="watchface_dialog_title_widget">Widget settings</string>
+    <string name="watchface_dialog_widget_width">Widget width (in pixels)</string>
+    <string name="watchface_setting_title_display_refresh_timeout">Display refresh timeout</string>
+    <string name="watchface_setting_display_refresh_full">Full refresh (in minutes)</string>
+    <string name="watchface_setting_display_refresh_partial">Partial refresh (in minutes)</string>
+    <string name="watchface_setting_title_wrist_flick">Wrist flick</string>
+    <string name="watchface_setting_desc_wrist_flick">(to disable completely, enable relative movement and set all values to 0)</string>
+    <string name="watchface_setting_wrist_flick_move_relative">Move hands relative to time</string>
+    <string name="watchface_setting_wrist_flick_hour">Hour hand (-360 to 360)</string>
+    <string name="watchface_setting_wrist_flick_minute">Minute hand (-360 to 360)</string>
+    <string name="watchface_setting_wrist_flick_duration">Duration (in ms)</string>
+    <string name="watchface_setting_title_custom_events">Custom events</string>
+    <string name="watchface_setting_button_toggle_widgets">Toggle widgets</string>
+    <string name="watchface_setting_button_toggle_backlight">Turn backlight on</string>
+    <string name="watchface_setting_button_move_hands">Move hands</string>
+    <string name="watchface_cache_confirm_overwrite">A watchface with this name already exists in the cache. Do you want to overwrite it?</string>
+    <string name="watchface_upload_failed">Upload of the watchface failed. Please try again.</string>
+    <string name="watchface_widget_type_battery">Battery</string>
+    <string name="watchface_widget_type_calories">Calories</string>
+    <string name="watchface_widget_type_2nd_tz">2nd time zone</string>
+    <string name="watchface_widget_type_active_mins">Active minutes</string>
+    <string name="watchface_widget_type_chance_rain">Chance of rain</string>
+    <string name="watchface_widget_type_uv_index">UV index</string>
+    <string name="watchface_widget_type_sp02">SpO2</string>
+    <string name="watchface_setting_title_power_saving">Power saving</string>
+    <string name="watchface_setting_power_saving_display">Disable display updates while off wrist</string>
+    <string name="watchface_setting_power_saving_hands">Disable hands movement while off wrist</string>
+    <string name="watchface_dialog_widget_background">Background</string>
+    <string name="hybridhr_widget_bg_thin_circle">Thin circle</string>
+    <string name="hybridhr_widget_bg_double_circle">Double circle</string>
+    <string name="hybridhr_widget_bg_dashed_circle">Dashed circle</string>
+    <string name="prefs_sleep_time">Sleep times</string>
+    <string name="prefs_sleep_time_label">Define sleep hours</string>
+    <string name="prefs_sleep_time_summary">Specifies times when sleep is registered</string>
+    <string name="prefs_vibration_enable">Enable vibrations</string>
+    <string name="prefs_notifications_enable">Enable notifications</string>
+    <string name="enable_vibrations_summary">Vibrations for calls, messages, notifications and more</string>
+    <string name="enable_notifications_summary">Notifications for calls, messages and more</string>
+    <string name="prefs_autoheartrate">Automatic Heart Rate</string>
+    <string name="prefs_autoheartrate_summary">Periodical Heart Rate measurements during the day and also while asleep</string>
+    <string name="prefs_autoheartrate_measurement">Automatic Heart Rate measurements</string>
+    <string name="prefs_autoheartrate_sleep">Take measurements during sleep</string>
+    <string name="prefs_autoheartrate_interval">Frequency of measurements</string>
+    <string name="devicetype_nothingear1">Nothing Ear (1)</string>
+    <string name="devicetype_nothingear2">Nothing Ear (2)</string>
+    <string name="devicetype_nothingearstick">Nothing Ear (Stick)</string>
+    <string name="devicetype_nothing_cmf_buds_pro_2">CMF Buds Pro 2</string>
+    <string name="devicetype_nothing_cmf_watch_pro">CMF Watch Pro</string>
+    <string name="devicetype_nothing_cmf_watch_pro_2">CMF Watch Pro 2</string>
+    <string name="devicetype_oppo_enco_air">Oppo Enco Air</string>
+    <string name="devicetype_oppo_enco_air2">Oppo Enco Air2</string>
+    <string name="devicetype_realme_buds_t110">Realme Buds T110</string>
+    <string name="devicetype_galaxybuds">Galaxy Buds</string>
+    <string name="devicetype_galaxybuds_live">Galaxy Buds Live</string>
+    <string name="devicetype_galaxybuds_pro">Galaxy Buds Pro</string>
+    <string name="devicetype_galaxybuds_2">Galaxy Buds2</string>
+    <string name="devicetype_galaxybuds_2_pro">Galaxy Buds2 Pro</string>
+    <string name="nothing_prefs_inear_summary">Play/pause the music depending if you wear the earbuds</string>
+    <string name="nothing_prefs_inear_title">In-Ear detection</string>
+    <string name="prefs_in_ear_detection_summary">Play calls through your earbuds when they are in your ears</string>
+    <string name="nothing_prefs_audiomode_title">Audio mode</string>
+    <string name="prefs_equalizer_preset">Equalizer Preset</string>
+    <string name="pref_title_equalizer_normal">Normal</string>
+    <string name="pref_title_equalizer_bass_boost">Bass boost</string>
+    <string name="pref_title_equalizer_soft">Soft</string>
+    <string name="pref_title_equalizer_dynamic">Dynamic</string>
+    <string name="pref_title_equalizer_clear">Clear</string>
+    <string name="pref_title_equalizer_trebble">Treble boost</string>
+    <string name="prefs_dolby_mode">Dolby Mode</string>
+    <string name="prefs_equalizer">Equalizer</string>
+    <string name="prefs_equalizer_summary">Enable or disable equalizer</string>
+    <string name="prefs_dolby_summary">Dolby preset for equalizer</string>
+    <string name="prefs_game_mode">Game mode</string>
+    <string name="prefs_game_mode_summary">Only if your phone supports game mode</string>
+    <string name="prefs_touch_lock">Touch Lock</string>
+    <string name="prefs_touch_lock_buds2">Touch controls</string>
+    <string name="prefs_touch_lock_summary">Disable touch events</string>
+    <string name="prefs_galaxy_buds_experimental">Experimental</string>
+    <string name="prefs_seamless_connection_switch_title">Seamless connection switch</string>
+    <string name="prefs_seamless_connection_switch_summary">Switches the buds between paired devices automatically</string>
+    <string name="prefs_ambient_volume">Ambient volume</string>
+    <string name="prefs_ambient_volume_left">Ambient Volume Left</string>
+    <string name="prefs_ambient_volume_right">Ambient Volume Right</string>
+    <string name="prefs_ambient_voice_focus">Voice Focus</string>
+    <string name="prefs_ambient_voice_summary">Make voice stand out</string>
+    <string name="prefs_ambient_sound">Ambient Sound</string>
+    <string name="prefs_customize_ambient_sound_summary">Customize Ambient Sound</string>
+    <string name="prefs_ambient_sound_during_call_title">Ambient Sound during call</string>
+    <string name="prefs_ambient_sound_during_call_summary">Hear own voice clearly during calls</string>
+    <string name="prefs_ambient_mode">Ambient Mode</string>
+    <string name="prefs_ambient_settings_title">Ambient Sound Options</string>
+    <string name="prefs_active_noise_cancelling">Active Noise Cancelling</string>
+    <string name="prefs_active_noise_cancelling_light">Light Active Noise Cancelling</string>
+    <string name="prefs_active_noise_cancelling_transparency">Transparency</string>
+    <string name="prefs_active_noise_cancelling_level">Active Noise Cancelling Level</string>
+    <string name="prefs_active_noise_cancelling_level_high">High</string>
+    <string name="prefs_active_noise_cancelling_level_low">Low</string>
+    <string name="prefs_active_noise_cancelling_summary">Block noises of the surroundings</string>
+    <string name="prefs_pressure_relief">Pressure relief with ambient sound</string>
+    <string name="pressure_relief_summary">Prevent feeling of pressure in ears when not using Active Noise Cancelling</string>
+    <string name="prefs_left">Left</string>
+    <string name="prefs_right">Right</string>
+    <string name="prefs_switch_control_left">Switch control left</string>
+    <string name="prefs_switch_control_right">Switch control right</string>
+    <string name="prefs_galaxy_touch_options">Touch Options</string>
+    <string name="battery_i">Battery %d</string>
+    <string name="battery_case">Battery case</string>
+    <string name="left_earbud">Left earbud</string>
+    <string name="right_earbud">Right earbud</string>
+    <string name="audio_codec">Audio Codec</string>
+    <string name="pref_title_touch_voice_assistant">Voice Assistant</string>
+    <string name="pref_title_touch_anc">Active Noise Cancelling</string>
+    <string name="pref_title_touch_quick_ambient">Quick Ambient Sound</string>
+    <string name="pref_title_touch_volume">Volume</string>
+    <string name="pref_title_touch_ambient">Ambient Sound</string>
+    <string name="pref_title_touch_spotify">Spotify</string>
+    <string name="pref_title_touch_spotify_official_app">Spotify (official app only)</string>
+    <string name="pref_title_touch_spotify_galaxy_app">Spotify (Galaxy Wearable app only)</string>
+    <string name="pref_switch_noise_control">Switch Noise Control</string>
+    <string name="prefs_noise_control_with_one_earbud">Noise Control with one earbud</string>
+    <string name="prefs_noise_control_with_one_earbud_summary">Allow noise control when using one earbud only</string>
+    <string name="pref_ambient_sound_tone">Ambient Sound Tone</string>
+    <string name="pref_ambient_sound_tone_summary">From Soft to Clear</string>
+    <string name="pref_balance">Balance</string>
+    <string name="pref_switch_controls_anc_ambient">Noise cancelling 鈫愨啋 Ambient</string>
+    <string name="pref_switch_controls_anc_off">Noise cancelling 鈫愨啋 Off</string>
+    <string name="pref_switch_controls_ambient_off">Ambient 鈫愨啋 Off</string>
+    <string name="pref_switch_controls_anc_ambient_off">Noise cancelling 鈫愨啋 Ambient 鈫愨啋 Off</string>
+    <string name="prefs_noise_control">Noise control</string>
+    <string name="prefs_voice_detect">Voice detect</string>
+    <string name="prefs_voice_detect_summary">Enable Ambient sound and lower playback automatically after voice has been detected</string>
+    <string name="prefs_double_tap_edge">Double tap edge</string>
+    <string name="prefs_double_tap_edge_summary">Detect double tap even when not tapped on touch pad</string>
+    <string name="prefs_voice_detect_duration">End after quiet for:</string>
+    <string name="pref_voice_detect_duration_5">5 seconds</string>
+    <string name="pref_voice_detect_duration_10">10 seconds</string>
+    <string name="pref_voice_detect_duration_15">15 seconds</string>
+    <string name="pref_voice_passthrough_enabled">Passthrough</string>
+    <string name="pref_voice_passthrough_enabled_summary">Allow external sounds to pass through to your ears</string>
+    <string name="pref_voice_passthrough_level">Passthrough level</string>
+    <string name="pref_header_heartrate_sleep">Sleep</string>
+    <string name="pref_header_heartrate_allday">All-day monitoring</string>
+    <string name="pref_header_heartrate_alerts">Heart rate alerts</string>
+    <string name="pref_header_stress">Stress</string>
+    <string name="pref_header_spo2">Blood Oxygen</string>
+    <string name="pref_header_hrv_status">HRV Status</string>
+    <string name="pref_wear_sensor_summary">Detect when the device is not being worn</string>
+    <string name="pref_wear_sensor_title">Wear Sensor</string>
+    <string name="body_energy">Body Energy</string>
+    <string name="vo2max_running">Running VO鈧� Max</string>
+    <string name="vo2max_cycling">Cycling VO鈧� Max</string>
+    <string name="thirty_days_timeline">30 Days Timeline</string>
+    <string name="pref_header_sony_ambient_sound_control">Ambient Sound Control</string>
+    <string name="pref_header_sony_sound_control">Sound Control</string>
+    <string name="pref_header_sony_device_info">Device Information</string>
+    <string name="stress_relaxed">Relaxed</string>
+    <string name="stress_mild">Mild</string>
+    <string name="stress_moderate">Moderate</string>
+    <string name="stress_high">High</string>
+    <string name="pai_total">Total</string>
+    <string name="pai_day">Day increase</string>
+    <string name="weight_kg">%1$.2f kg</string>
+    <string name="weight_lbs">%1$.2f lbs</string>
+    <string name="target">Target</string>
+    <string name="redmi_buds_5_pro_anc_balanced">Balanced</string>
+    <string name="redmi_buds_5_pro_anc_light">Light</string>
+    <string name="redmi_buds_5_pro_anc_deep">Deep</string>
+    <string name="redmi_buds_5_pro_transparency_strength">Transparency Strength</string>
+    <string name="redmi_buds_5_pro_transparency_regular">Regular</string>
+    <string name="redmi_buds_5_pro_transparency_voice">Enhance Voices</string>
+    <string name="redmi_buds_5_pro_transparency_ambient">Enhance Ambient Sounds</string>
+    <string name="redmi_buds_5_pro_combo_anc_off">ANC / Off</string>
+    <string name="redmi_buds_5_pro_combo_transparency_off">Transparency / Off</string>
+    <string name="redmi_buds_5_pro_combo_anc_transparency">ANC / Transparency</string>
+    <string name="redmi_buds_5_pro_combo_all">ANC / Transparency / Off</string>
+    <string name="redmi_buds_5_pro_double_connection">Double Connection</string>
+    <string name="redmi_buds_5_pro_double_connection_description">Allow the earbuds to connect to two devices at the same time</string>
+    <!--    <string name="redmi_buds_5_pro_spatial_audio">Spatial Audio</string>-->
+    <!--    <string name="redmi_buds_5_pro_spatial_audio_mode">Spatial Audio Mode</string>-->
+    <string name="redmi_buds_5_pro_adaptive_sound">Adaptive Sound</string>
+    <string name="redmi_buds_5_pro_adaptive_sound_description">Adjusts the sound according to the ear shape and the environment</string>
+    <string name="redmi_buds_5_pro_equalizer_preset_standard">Standard</string>
+    <string name="redmi_buds_5_pro_equalizer_preset_treble">Enhance Treble</string>
+    <string name="redmi_buds_5_pro_equalizer_preset_bass">Enhance Bass</string>
+    <string name="redmi_buds_5_pro_equalizer_preset_voice">Enhance Voice</string>
+    <string name="redmi_buds_5_pro_equalizer_preset_custom">Custom</string>
+    <string name="redmi_buds_5_pro_equalizer_band_62">62 Hz</string>
+    <string name="redmi_buds_5_pro_equalizer_band_125">125 Hz</string>
+    <string name="redmi_buds_5_pro_equalizer_band_250">250 Hz</string>
+    <string name="redmi_buds_5_pro_equalizer_band_500">500 Hz</string>
+    <string name="redmi_buds_5_pro_equalizer_band_1k">1 kHz</string>
+    <string name="redmi_buds_5_pro_equalizer_band_2k">2 kHz</string>
+    <string name="redmi_buds_5_pro_equalizer_band_4k">4 kHz</string>
+    <string name="redmi_buds_5_pro_equalizer_band_8k">8 kHz</string>
+    <string name="redmi_buds_5_pro_equalizer_band_12k">12 kHz</string>
+    <string name="redmi_buds_5_pro_equalizer_band_16k">16 kHz</string>
+    <string name="redmi_buds_5_pro_equalizer_neg6">-6 dB</string>
+    <string name="redmi_buds_5_pro_equalizer_neg5">-5 dB</string>
+    <string name="redmi_buds_5_pro_equalizer_neg4">-4 dB</string>
+    <string name="redmi_buds_5_pro_equalizer_neg3">-3 dB</string>
+    <string name="redmi_buds_5_pro_equalizer_neg2">-2 dB</string>
+    <string name="redmi_buds_5_pro_equalizer_neg1">-1 dB</string>
+    <string name="redmi_buds_5_pro_equalizer_zero">0 dB</string>
+    <string name="redmi_buds_5_pro_equalizer_1">1 dB</string>
+    <string name="redmi_buds_5_pro_equalizer_2">2 dB</string>
+    <string name="redmi_buds_5_pro_equalizer_3">3 dB</string>
+    <string name="redmi_buds_5_pro_equalizer_4">4 dB</string>
+    <string name="redmi_buds_5_pro_equalizer_5">5 dB</string>
+    <string name="redmi_buds_5_pro_equalizer_6">6 dB</string>
+    <string name="sony_ambient_sound">Mode</string>
+    <string name="sony_ambient_sound_off">Off</string>
+    <string name="sony_ambient_sound_noise_cancelling">Noise Cancelling</string>
+    <string name="sony_ambient_sound_wind_noise_reduction">Wind Noise Reduction</string>
+    <string name="sony_ambient_sound_ambient_sound">Ambient Sound</string>
+    <string name="sony_ambient_sound_focus_voice">Focus on Voice</string>
+    <string name="sony_ambient_sound_level">Ambient Sound Level</string>
+    <string name="pref_header_sony_anc_optimizer">Noise Cancelling Optimizer</string>
+    <string name="sony_anc_optimize_title">Optimize</string>
+    <string name="sony_anc_optimize_description">Click to start the noise cancelling optimizer.</string>
+    <string name="sony_anc_optimize_confirmation_title">Noise Cancelling Optimizer</string>
+    <string name="sony_anc_optimize_confirmation_description">Use the headphones as you normally would. If the wearing condition or atmospheric pressure change, run the optimizer again.</string>
+    <string name="pref_anc_optimizer_state_pressure">Atmospheric pressure</string>
+    <string name="sony_sound_position">Sound Position</string>
+    <string name="sony_sound_position_off">Off</string>
+    <string name="sony_sound_position_front">Front</string>
+    <string name="sony_sound_position_front_left">Front Left</string>
+    <string name="sony_sound_position_front_right">Front Right</string>
+    <string name="sony_sound_position_rear_left">Rear Left</string>
+    <string name="sony_sound_position_rear_right">Rear Right</string>
+    <string name="sony_surround_mode">Surround Mode</string>
+    <string name="sony_surround_mode_off">Off</string>
+    <string name="sony_surround_mode_arena">Arena</string>
+    <string name="sony_surround_mode_club">Club</string>
+    <string name="sony_surround_mode_outdoor_stage">Outdoor Stage</string>
+    <string name="sony_surround_mode_concert_hall">Concert Hall</string>
+    <string name="sony_warn_sbc_codec">Warning: The equalizer, audio position and surround settings only work for the SBC audio codec.</string>
+    <string name="sony_equalizer">Equalizer</string>
+    <string name="sony_anc_optimizer_status_starting">Starting鈥�</string>
+    <string name="sony_anc_optimizer_status_not_running">Not Running</string>
+    <string name="sony_anc_optimizer_status_wearing_condition">Measuring wearing condition鈥�</string>
+    <string name="sony_anc_optimizer_status_atmospheric_pressure">Measuring atmospheric pressure鈥�</string>
+    <string name="sony_anc_optimizer_status_analyzing">Analyzing鈥�</string>
+    <string name="sony_anc_optimizer_status_finished">Finishing鈥�</string>
+    <string name="sony_equalizer_preset_off">Off</string>
+    <string name="sony_equalizer_preset_bright">Bright</string>
+    <string name="sony_equalizer_preset_excited">Excited</string>
+    <string name="sony_equalizer_preset_mellow">Mellow</string>
+    <string name="sony_equalizer_preset_relaxed">Relaxed</string>
+    <string name="sony_equalizer_preset_vocal">Vocal</string>
+    <string name="sony_equalizer_preset_treble_boost">Treble Boost</string>
+    <string name="sony_equalizer_preset_bass_boost">Bass Boost</string>
+    <string name="sony_equalizer_preset_speech">Speech</string>
+    <string name="sony_equalizer_preset_manual">Manual</string>
+    <string name="sony_equalizer_preset_custom_1">Custom 1</string>
+    <string name="sony_equalizer_preset_custom_2">Custom 2</string>
+    <string name="pref_header_sony_equalizer_bands">Bands</string>
+    <string name="pref_header_sony_equalizer_preset_custom_1">Custom Preset 1</string>
+    <string name="pref_header_sony_equalizer_preset_custom_2">Custom Preset 2</string>
+    <string name="sony_equalizer_band_400">400</string>
+    <string name="sony_equalizer_band_1000">1k</string>
+    <string name="sony_equalizer_band_2500">2.5k</string>
+    <string name="sony_equalizer_band_6300">6.3k</string>
+    <string name="sony_equalizer_band_16000">16k</string>
+    <string name="sony_equalizer_clear_bass">Clear Bass</string>
+    <string name="sony_audio_upsampling">Audio Upsampling</string>
+    <string name="sony_touch_sensor">Touch sensor control</string>
+    <string name="pref_wide_area_tap_summary">Recognize taps between cheeks and ears</string>
+    <string name="pref_wide_area_tap_title">Wide area tap</string>
+    <string name="pref_adaptive_volume_control_summary">Increase volume automatically when ambient sound is loud</string>
+    <string name="pref_adaptive_volume_control_title">Adaptive volume control</string>
+    <string name="pref_adaptive_noise_cancelling_title">Adaptive ANC</string>
+    <string name="pref_adaptive_noise_cancelling_summary">Set the strength of the ANC automatically depending on the ambient sound level</string>
+    <!--    <string name="pref_personalized_noise_cancelling_title">Personalized ANC</string>-->
+    <!--    <string name="pref_personalized_noise_cancelling_summary">Set whether to use personalized active noise cancelling</string>-->
+    <string name="sony_speak_to_chat">Speak-to-chat</string>
+    <string name="sony_speak_to_chat_summary">Turn off noise cancelling automatically when you start talking.</string>
+    <string name="sony_speak_to_chat_sensitivity">Voice Detection Sensitivity</string>
+    <string name="sony_speak_to_chat_sensitivity_auto">Automatic</string>
+    <string name="sony_speak_to_chat_sensitivity_high">High</string>
+    <string name="sony_speak_to_chat_sensitivity_low">Low</string>
+    <string name="sony_speak_to_chat_sensitivity_standard">Standard</string>
+    <string name="sony_speak_to_chat_focus_on_voice">Focus on Voice</string>
+    <string name="sony_speak_to_chat_timeout">Timeout</string>
+    <string name="sony_speak_to_chat_timeout_off">Off</string>
+    <string name="sony_speak_to_chat_timeout_short">Short (15s)</string>
+    <string name="sony_speak_to_chat_timeout_standard">Standard (30s)</string>
+    <string name="sony_speak_to_chat_timeout_long">Long (1m)</string>
+    <string name="sony_connect_two_devices">Connect to 2 devices simultaneously</string>
+    <string name="sony_notification_voice_guide">Notifications &amp; Voice Guide</string>
+    <string name="sony_automatic_power_off">Automatic Power Off</string>
+    <string name="sony_automatic_power_off_off">Do not turn off</string>
+    <string name="sony_automatic_power_off_5_min">5 minutes</string>
+    <string name="sony_automatic_power_off_30_min">30 minutes</string>
+    <string name="sony_automatic_power_off_1_hour">1 hour</string>
+    <string name="sony_automatic_power_off_3_hour">3 hours</string>
+    <string name="sony_automatic_power_off_when_taken_off">When taken off</string>
+    <string name="sony_pause_when_taken_off">Pause when headphones are taken off</string>
+    <string name="sony_button_mode_left">Button Mode (Left)</string>
+    <string name="sony_button_mode_right">Button Mode (Right)</string>
+    <string name="sony_button_mode_off">Off</string>
+    <string name="sony_button_mode_help_title">Button Modes - Help</string>
+    <string name="sony_button_mode_help_summary">A description of each button mode</string>
+    <string name="sony_button_mode_ambient_sound_control">Ambient Sound Control</string>
+    <string name="sony_button_mode_playback_control">Playback Control</string>
+    <string name="sony_button_mode_volume_control">Volume Control</string>
+    <string name="sony_ambient_sound_control_button_modes">Ambient Sound Control Button Modes</string>
+    <string name="sony_ambient_sound_control_button_mode_nc_as_off">Noise Cancelling, Ambient Sound, Off</string>
+    <string name="sony_ambient_sound_control_button_mode_nc_as">Noise Cancelling, Ambient Sound</string>
+    <string name="sony_ambient_sound_control_button_mode_nc_off">Noise Cancelling, Off</string>
+    <string name="sony_ambient_sound_control_button_mode_as_off">Ambient Sound, Off</string>
+    <string name="sony_quick_access_double_tap">Quick Access (Double Tap)</string>
+    <string name="sony_quick_access_triple_tap">Quick Access (Triple Tap)</string>
+    <string name="sony_protocol_v1">Version 1</string>
+    <string name="sony_protocol_v2">Version 2</string>
+    <string name="sony_protocol_v3">Version 3</string>
+    <string name="moondrop_equalizer_preset_reference">Reference</string>
+    <string name="moondrop_equalizer_preset_basshead">Basshead</string>
+    <string name="moondrop_equalizer_preset_monitor">Monitor</string>
+    <string name="moondrop_touch_earbud">Earbud</string>
+    <string name="moondrop_touch_earbud_both">Both</string>
+    <string name="moondrop_touch_trigger">Trigger</string>
+    <string name="moondrop_touch_action_play_pause">Play/Pause</string>
+    <string name="moondrop_touch_action_call_pick_hang">Pick/Hang Call</string>
+    <string name="moondrop_touch_action_call_start">Start Call</string>
+    <string name="moondrop_touch_action_assistant">Trigger Voice Assistant</string>
+    <string name="moondrop_touch_action_anc_mode">Switch Active Noise Cancelling Mode</string>
+    <string name="moondrop_touch_trigger_long_press_1s">Long Press (1s)</string>
+    <string name="moondrop_touch_trigger_long_press_3s">Long Press (3s)</string>
+    <string name="soundcore_voice_prompts">Voice Prompts</string>
+    <string name="soundcore_button_brightness">Button Brightness</string>
+    <string name="soundcore_button_brightness_low">Low</string>
+    <string name="soundcore_button_brightness_medium">Medium</string>
+    <string name="soundcore_button_brightness_high">High</string>
+    <string name="soundcore_ldac_mode_title">LDAC Mode</string>
+    <string name="soundcore_ldac_mode_summary">Enabling LDAC will decrease the battery life and might lead to connection instability</string>
+    <string name="soundcore_adaptive_direction_title">Adaptive Direction</string>
+    <string name="soundcore_adaptive_direction_summary">Adjust equalizer preset automatically based on device direction</string>
+    <string name="soundcore_equalizer_preset">Preset</string>
+    <string name="soundcore_equalizer_preset_signature">soundcore Signature</string>
+    <string name="soundcore_equalizer_preset_xtra_bass">Xtra Bass</string>
+    <string name="soundcore_equalizer_preset_voice">Voice</string>
+    <string name="soundcore_equalizer_preset_balanced">Balanced</string>
+    <string name="soundcore_equalizer_custom_title">Customize鈥�</string>
+    <string name="soundcore_equalizer_custom_summary">Configure parametric equalizer settings</string>
+    <string name="soundcore_equalizer_direction">Device Direction</string>
+    <string name="soundcore_equalizer_direction_standing">Standing</string>
+    <string name="soundcore_equalizer_direction_lying">Lying</string>
+    <string name="soundcore_equalizer_direction_hanging">Hanging</string>
+    <string name="soundcore_equalizer_reset_title">Reset to default</string>
+    <string name="soundcore_equalizer_reset_summary">Set all equalizer bands back to default settings</string>
+    <string name="soundcore_equalizer_band1">Band 1</string>
+    <string name="soundcore_equalizer_band2">Band 2</string>
+    <string name="soundcore_equalizer_band3">Band 3</string>
+    <string name="soundcore_equalizer_band4">Band 4</string>
+    <string name="soundcore_equalizer_band5">Band 5</string>
+    <string name="soundcore_equalizer_band6">Band 6</string>
+    <string name="soundcore_equalizer_band7">Band 7</string>
+    <string name="soundcore_equalizer_band8">Band 8</string>
+    <string name="soundcore_equalizer_band9">Band 9</string>
+    <string name="soundcore_equalizer_frequency">Frequency</string>
+    <string name="soundcore_equalizer_value">Value</string>
+    <string name="miscale_weight_unit_title">Weight Unit</string>
+    <string name="miscale_weight_unit_summary">Set unit of weight for displayed measurements</string>
+    <string name="miscale_weight_unit_metric">Metric (kg)</string>
+    <string name="miscale_weight_unit_imperial">Imperial (lbs)</string>
+    <string name="miscale_weight_unit_chinese">Chinese (jin)</string>
+    <string name="miscale_small_objects_title">Small Objects</string>
+    <string name="miscale_small_objects_summary">Store weight of objects lighter than 10 kg</string>
+    <string name="mijia_lywsd_comfort_level_title">Comfort Level</string>
+    <string name="mijia_lywsd_comfort_level_summary">Configure the temperature and humidity limits for the displayed emoji</string>
+    <string name="mijia_lywsd_comfort_temperature_title">Temperature (掳C)</string>
+    <string name="mijia_lywsd_comfort_temperature_summary">Recommended range: 19 - 27</string>
+    <string name="mijia_lywsd_comfort_humidity_title">Humidity (%)</string>
+    <string name="mijia_lywsd_comfort_humidity_summary">Recommended range: 20 - 85</string>
+    <string name="mijia_lywsd_comfort_lower">Lower Limit</string>
+    <string name="mijia_lywsd_comfort_upper">Upper Limit</string>
+    <string name="protocol_version">Protocol Version</string>
+    <string name="pref_screen_auto_brightness_title">Auto Brightness</string>
+    <string name="pref_screen_auto_brightness_summary">Adjust screen brightness according to ambient light</string>
+    <string name="pref_screen_brightness">Screen Brightness</string>
+    <string name="single_tap">Single Tap</string>
+    <string name="double_tap">Double Tap</string>
+    <string name="triple_tap">Triple Tap</string>
+    <string name="long_press">Long Press</string>
+    <string name="continue_pressing">Continue Pressing</string>
+    <string name="quick_attention">Quick Attention</string>
+    <string name="watchface_widget_type_custom">Custom widget</string>
+    <string name="watchface_dialog_widget_timezone">Time zone</string>
+    <string name="watchface_dialog_widget_timezone_duration">Clock visibility duration (in seconds)</string>
+    <string name="watchface_dialog_widget_update_timeout">Update timeout in minutes</string>
+    <string name="watchface_dialog_widget_timeout_hide_text">Hide text on timeout</string>
+    <string name="watchface_dialog_widget_timeout_show_circle">Show circle on timeout</string>
+    <string name="qhybrid_title_on_device_confirmation">Enable on-device pairing confirmation</string>
+    <string name="qhybrid_summary_on_device_confirmation">On-device pairing confirmations can get annoying. Disabling them might lose you functionality.</string>
+    <string name="devicetype_vesc">VESC</string>
+    <string name="devicetype_bose_qc35">Bose QC35</string>
+    <string name="devicetype_sony_wena3">Sony Wena 3</string>
+    <string name="withings_steel_hr">Withings Steel HR</string>
+    <string name="pref_button_action_disabled">Disabled</string>
+    <string name="pref_media_play">Media Play</string>
+    <string name="pref_media_pause">Media Pause</string>
+    <string name="pref_media_playpause">Toggle playback</string>
+    <string name="pref_media_next">Next Track</string>
+    <string name="pref_media_previous">Previous Track</string>
+    <string name="pref_media_volumeup">Volume Up</string>
+    <string name="pref_media_volumedown">Volume Down</string>
+    <string name="pref_media_forward">Skip forward</string>
+    <string name="pref_media_rewind">Skip back</string>
+    <string name="pref_device_action_broadcast">Send Broadcast</string>
+    <string name="pref_device_action_fitness_app_control_start">Fitness App Tracking Start</string>
+    <string name="pref_device_action_fitness_app_control_stop">Fitness App Tracking Stop</string>
+    <string name="pref_device_action_fitness_app_control_toggle">Toggle Fitness App Tracking</string>
+    <string name="pref_device_action_phone_gps_location_listener_stop">GPS Location Listener Stop</string>
+    <string name="pref_device_action_dnd_off">Do not disturb - Off</string>
+    <string name="pref_device_action_dnd_priority">Do not disturb - Priority only</string>
+    <string name="pref_device_action_dnd_alarms">Do not disturb - Alarms only</string>
+    <string name="pref_device_action_dnd_on">Do not disturb - On</string>
+    <!-- Translators: the ### indicate number of digits, keep intact -->
+    <string name="distance_format_meters">###m</string>
+    <!-- Translators: the ### indicate number of digits, keep intact -->
+    <string name="distance_format_kilometers">###.#km</string>
+    <!-- Translators: the ### indicate number of digits, keep intact -->
+    <string name="distance_format_miles">###.#mi</string>
+    <!-- Translators: the ### indicate number of digits, keep intact -->
+    <string name="distance_format_feet">###ft</string>
+
+    <string name="idasen_control_button_mid">MID</string>
+    <string name="idasen_control_button_sit">SIT</string>
+    <string name="idasen_control_button_stand">STAND</string>
+    <string name="idasen_pref_mid_height">Middle position height (in centimeters)</string>
+    <string name="idasen_pref_sit_height">Sit position height (in centimeters)</string>
+    <string name="idasen_pref_stand_height">Stand position height (in centimeters)</string>
+    <string name="idasen_pref_value_warning">The value must be between 62 - 126 cm</string>
+    <string name="prefs_workmode">Work Mode</string>
+    <string name="huawei_alarm_smart_description">Do not uncheck smart wakeup checkbox.</string>
+    <string name="huawei_alarm_event_description">Do not check smart wakeup checkbox.</string>
+    <string name="huawei_trusleep_title">HUAWEI TruSleep &#8482;</string>
+    <string name="huawei_trusleep_summary">Monitor your sleep quality and breathing pattern in real time.\nAnalyze your sleep patterns and accurately diagnose 6 types of sleeping problems.</string>
+    <string name="huawei_trusleep_summary_light">Improved sleep monitoring</string>
+    <string name="huawei_trusleep_warning">Warning: enabling this will make all sleep show up as light sleep in Gadgetbridge! Click here if you accept this.</string>
+    <string name="prefs_activity_recognition">Activity recognition settings</string>
+    <string name="pref_activity_recognize_running">recognize running</string>
+    <string name="pref_activity_recognize_biking">recognize biking</string>
+    <string name="pref_activity_recognize_walking">recognize walking</string>
+    <string name="pref_activity_recognize_rowing">recognize rowing</string>
+    <string name="pref_activity_recognition_mode_none">none</string>
+    <string name="pref_activity_recognition_mode_ask">ask</string>
+    <string name="pref_activity_recognition_mode_auto">auto</string>
+    <string name="pref_continuous_skin_temperature_measurement_title">Continuous skin temperature measurement</string>
+
+    <string name="menuitem_menu">Menu</string>
+    <string name="fossil_hr_button_config_info">Some buttons cannot be configured because their functions are hard-coded in the watch firmware.\n\nWarning: long-pressing the upper button when a watchface from the official Fossil app is installed will also toggle between showing/hiding widgets.</string>
+    <string name="pref_title_opentracks_packagename">OpenTracks package name</string>
+    <string name="pref_summary_opentracks_packagename">Used for starting/stopping GPS track recording in external fitness app.</string>
+    <string name="pref_summary_navigation_forward">Forward instructions from navigation apps to the watch</string>
+    <string name="pref_title_navigation_forward">Send navigation to watch</string>
+    <string name="watchface_dialog_pre_setting_position">pre-setting position to %s</string>
+    <string name="watchface_setting_light_up_on_notification">Light up on new notification</string>
+
+    <string name="pref_enable_call_accept">Enable accepting calls</string>
+    <string name="pref_enable_call_accept_summary">Enable accepting calls from the device</string>
+    <string name="pref_enable_call_reject">Enable rejecting calls</string>
+    <string name="pref_enable_call_reject_summary">Enable rejecting calls from the device</string>
+
+    <string name="pref_disable_find_phone_with_dnd">Disable find my phone when do not disturb is active</string>
+
+    <string name="pref_heartrate_automatic_enable">Enable automatic heartrate measuring</string>
+    <string name="pref_spo_automatic_enable">Enable automatic SpO2 measuring</string>
+
+    <string name="pref_force_options">Force options</string>
+    <string name="pref_force_options_summary">Some devices falsely claim not to have support for some options. This settings can be used to enable them anyway.\nUSE AT YOUR OWN RISK\nRead the wiki</string>
+    <string name="pref_force_smart_alarm">Force smart alarm</string>
+    <string name="pref_force_smart_alarm_summary">Force smart alarms support.\nUSE AT YOUR OWN RISK</string>
+    <string name="pref_force_wear_location">Force wear location</string>
+    <string name="pref_force_wear_location_summary">Force wear location support.\nUSE AT YOUR OWN RISK</string>
+    <string name="pref_force_dnd_support">Force Do Not Disturb support</string>
+    <string name="pref_force_dnd_support_summary">Force Do Not Disturb support.\nUSE AT YOUR OWN RISK</string>
+    <string name="pref_force_enable_heartrate_support">Force heart rate support</string>
+    <string name="pref_force_enable_heartrate_support_summary">Force enable heart rate support.\nUSE AT YOUR OWN RISK</string>
+    <string name="pref_force_enable_spo2_support">Force SpO2 support</string>
+    <string name="pref_force_enable_spo2_support_summary">Force enable SpO2 support.\nUSE AT YOUR OWN RISK</string>
+    <string name="huawei_ignore_wakeup_status_start">Ignore wakeup start status</string>
+    <string name="huawei_ignore_wakeup_status_start_description">May help with proper sleep detection. Visible immediately in the daily activities view.</string>
+    <string name="huawei_ignore_wakeup_status_end">Ignore wakeup end status</string>
+    <string name="huawei_ignore_wakeup_status_end_description">May help with proper sleep detection. Visible immediately in the daily activities view.</string>
+    <string name="huawei_reparse_workout_data">"Reparse workout data"</string>
+    <string name="huawei_reparse_workout_data_description">"This will only do something after certain updates"</string>
+
+    <string name="info_no_devices_connected">No devices connected</string>
+    <string name="info_connected_count">%d devices connected</string>
+    <string name="controlcenter_set_parent_folder">Set parent folder</string>
+    <string name="controlcenter_set_preferences">Set preferences</string>
+    <string name="controlcenter_toggle_details">Toggle details</string>
+    <string name="controlcenter_connected_fraction">Connected: %d/%d</string>
+    <string name="error_setting_parent_folder">Error setting parent folder: %s</string>
+    <string name="error_deleting_device">Error deleting device: %s</string>
+    <string name="controlcenter_folder_name">Folder name:</string>
+    <string name="controlcenter_add_new_folder">Add new folder</string>
+    <string name="controlcenter_unset_folder">Unset folder</string>
+    <string name="controlcenter_set_folder_title">Set or create new folder</string>
+    <string name="auto_reconnect_ble_title">Auto reconnect to device</string>
+    <string name="auto_reconnect_ble_summary">Proactively try to connect to device periodically</string>
+    <string name="connection_over_ble">Connection over BLE</string>
+    <string name="connection_over_bt_classic">Connection over Bluetooth classic</string>
+    <string name="autoconnect_from_device_title">Connect on connection from device</string>
+    <string name="autoconnect_from_device_summary">Establish a connection when connection is initiated by device, like headphones</string>
+    <!-- related to steps streak view -->
+    <string name="steps_streaks">Steps streaks</string>
+    <string name="steps_streaks_hint">Series of consecutive days without interruption with steps goal being reached</string>
+    <string name="step_streak_ongoing">Ongoing</string>
+    <string name="step_streak_longest">Longest</string>
+    <string name="step_streak_total">Total</string>
+    <string name="steps_streaks_total_steps">Total\nsteps</string>
+    <string name="steps_streaks_streak_days">Streak\nDays</string>
+    <string name="steps_streaks_average_steps">Average\nsteps</string>
+    <string name="steps_streaks_achievement_rate">Achievement\nrate</string>
+    <string name="steps_streaks_since_date">Since %s</string>
+    <string name="step_streak_average_steps_hint">Average steps per day of the streak</string>
+    <string name="steps_streaks_total_steps_hint_totals">Total number of steps ever recorded</string>
+    <string name="steps_streaks_total_days_hint_totals">Percentage of days with achieved goal in relation to all days with steps</string>
+    <string name="step_streak_days_hint">Number of consecutive days with steps goal being reached</string>
+    <string name="steps_streaks_total_steps_hint">Total number of steps in the whole streak</string>
+    <string name="steps_streaks_total_steps_average_hint">Total average %d steps per day</string>
+    <string name="step_streaks_achievements_sharing_message">My daily step achievements!</string>
+    <string name="step_streaks_achievements_sharing_title">Steps Achievements</string>
+    <string name="weekly_total">Weekly total</string>
+    <string name="prefs_hourly_chime">Hourly chime</string>
+    <string name="prefs_hourly_chime_summary">The watch will beep once an hour</string>
+    <string name="devicetype_flipper_zero">Flipper zero</string>
+    <string name="activity_prefs_allow_bluetooth_intent_api">Bluetooth Intent API</string>
+    <string name="activity_prefs_summary_allow_bluetooth_intent_api">Allow controlling Bluetooth connection via Intent API</string>
+    <string name="intent_api_allow_activity_sync_title">Allow activity sync trigger</string>
+    <string name="intent_api_allow_activity_sync_summary">Allow triggering activity sync via Intent API</string>
+    <string name="intent_api_allow_trigger_export_title">Allow database export</string>
+    <string name="intent_api_allow_trigger_export_summary">Allow triggering database export via Intent API</string>
+    <string name="intent_api_broadcast_export_title">Broadcast on database export</string>
+    <string name="intent_api_broadcast_export_summary">Broadcast an intent when database export finishes</string>
+    <string name="intent_api_broadcast_activity_sync_title">Broadcast on activity sync finish</string>
+    <string name="intent_api_broadcast_activity_sync_summary">Broadcast an intent when activity sync finishes for any device</string>
+    <string name="intent_api_allow_debug_commands_title">Allow Debug Commands</string>
+    <string name="intent_api_allow_debug_commands_summary">Allow triggering debug menu commands via Intent API</string>
+    <string name="devicetype_super_cars">Shell Racing</string>
+    <string name="supercars_turbo_speed_label">Turbo Speed</string>
+    <string name="supercars_lights_label">Lights</string>
+    <string name="supercars_lights_blinking_label">Blinking</string>
+    <string name="pref_summary_debug">Send a debug request to Huawei device</string>
+    <string name="pref_title_debug">Debug request</string>
+    <string name="devicetype_asteroidos">AsteroidOS</string>
+    <string name="devicetype_soflow_s06">SoFlow SO6</string>
+    <string name="pref_title_lock_unlock">Lock</string>
+    <string name="wifi_hotspot">Wi-Fi Hotspot</string>
+    <string name="wifi_hotspot_summary">Control the Wi-Fi hotspot on the watch</string>
+    <string name="wifi_hotspot_configuration">Wi-Fi Hotspot Configuration</string>
+    <string name="wifi_ssid">SSID</string>
+    <string name="wifi_hotspot_status">Wi-Fi Hotspot Status</string>
+    <string name="wifi_hotspot_start_summary">Start the Wi-Fi hotspot on the watch</string>
+    <string name="wifi_hotspot_stop_summary">Stop the Wi-Fi hotspot on the watch</string>
+    <string name="ftp_server">FTP Server</string>
+    <string name="ftp_server_summary">Control the FTP server on the watch</string>
+    <string name="ftp_server_start_summary">Start the FTP server on the watch</string>
+    <string name="ftp_server_stop_summary">Stop the FTP server on the watch</string>
+    <string name="ftp_server_status">FTP Server Status</string>
+    <string name="ftp_server_configuration">FTP Server Configuration</string>
+    <string name="ftp_server_root_dir">Root Directory</string>
+    <string name="address">Address</string>
+    <string name="username">Username</string>
+    <string name="fossil_hr_confirm_connection">Please confirm on your watch</string>
+    <string name="fossil_hr_connection_not_confirmed">Connection not confirmed on watch, using unauthenticated mode</string>
+    <string name="fossil_hr_pairing_successful">Pairing with watch successful</string>
+    <string name="fossil_hr_pairing_failed">Pairing with watch failed</string>
+    <string name="fossil_hr_confirmation_skipped">Skipping on-device confirmation</string>
+    <string name="fossil_hr_confirmation_timeout">Confirmation timeout, continuing</string>
+    <string name="debug_companion_show_associated">Show associated companion devices</string>
+    <string name="debug_companion_pair_current">Pair current device as companion</string>
+    <string name="contact_name">Name</string>
+    <string name="contact_phone_number">Phone number</string>
+    <string name="contact_missing_name">Contact name is empty</string>
+    <string name="contact_missing_number">Contact number is empty</string>
+    <string name="voice_service_package_title">Voice service package</string>
+    <string name="voice_service_package_summary">Application that contains the service handling voice commands</string>
+    <string name="voice_service_class_title">Voice service class</string>
+    <string name="voice_service_class_summary">Full service path handling voice commands</string>
+    <string name="voice_service">Voice Service</string>
+    <string name="pref_app_logs_title">App logs</string>
+    <string name="pref_app_logs_summary">Enable logs from watch apps</string>
+    <string name="pref_app_logs_start_summary">Start logging from watch apps</string>
+    <string name="pref_app_logs_stop_summary">Stop logging from watch apps</string>
+    <string name="pref_app_connection_duration">App connection duration</string>
+    <string name="title">Title</string>
+    <string name="description">Description</string>
+    <string name="preview_image">Preview image</string>
+    <string name="status_icon">Status icon</string>
+    <string name="changelog_full_title">Changelog</string>
+    <string name="changelog_show_full">More鈥�</string>
+    <string name="changelog_ok_button">OK</string>
+    <string name="changelog_title">What\'s New</string>
+    <string name="loyalty_cards_catima_package">Catima package name</string>
+    <string name="loyalty_cards_install">Install Catima</string>
+    <string name="loyalty_cards_sync_groups_only">Sync only specific groups</string>
+    <string name="loyalty_cards_sync_groups">Groups to Sync</string>
+    <string name="loyalty_cards_sync_archived">Sync archived cards</string>
+    <string name="loyalty_cards_install_catima_fail">Failed to open app store to install Catima</string>
+    <string name="loyalty_cards_sync_starred">Sync only starred cards</string>
+    <string name="loyalty_cards_sync_title">Sync Loyalty Cards</string>
+    <string name="loyalty_cards_sync_summary">Tap to sync the cards to the watch</string>
+    <string name="loyalty_cards_catima_not_installed">Catima is needed to manage the loyalty cards</string>
+    <string name="loyalty_cards_catima_not_compatible">The installed Catima version is not compatible with Gadgetbridge. Please update Catima and Gadgetbridge to the latest versions.</string>
+    <string name="loyalty_cards_sync_options">Sync Options</string>
+    <string name="loyalty_cards_sync">Sync</string>
+    <string name="loyalty_cards_catima">Catima</string>
+    <string name="loyalty_cards_open_catima">Open Catima</string>
+    <string name="loyalty_cards_catima_permissions_title">Missing permissions</string>
+    <string name="loyalty_cards_catima_permissions_summary">Gadgetbridge needs read permissions on Catima cards to sync them. Tap this button to grant them.</string>
+    <string name="loyalty_cards">Loyalty Cards</string>
+    <string name="loyalty_cards_syncing">Syncing %d loyalty cards to device</string>
+    <string name="withings_calibration_text_hours">Please use the dial below to align the hour hand to the 12.</string>
+    <string name="withings_calibration_text_minutes">Now use the dial to align the minute hand to the 12.</string>
+    <string name="withings_calibration_text_activity_target">Finally align the activity hand to 100%. Please be aware that this hand only moves clockwise.</string>
+    <string name="withings_bt_calibration_previous">Previous</string>
+    <string name="withings_bt_calibration_next">Next</string>
+    <string name="drag_handle">drag handle</string>
+    <string name="find_my_phone_found_it">FOUND IT</string>
+    <string name="pref_activity_full_sync_trigger_summary">Trigger a full sync of all activity data</string>
+    <string name="pref_activity_full_sync_trigger_title">Full sync</string>
+    <string name="pref_activity_full_sync_trigger_warning">This will trigger a full sync of all activity data from the device. It may take a few minutes to complete.</string>
+    <string name="pref_theme_dynamic_colors_not_available_warning">Dynamic colors are not available on your device, only Android 12+ supports this functionality. Gadgetbridge will use the default Material 3 colors.</string>
+    <string name="pref_theme_dynamic_colors_explanation">Note: for the dynamic colors theme you need to enable Wallpaper Colors or Color Palette in your Android 12+ appearance settings. If you don\'t, Gadgetbridge will use the default Material 3 colors.</string>
+    <string name="info_menu_structure_set">Menu structure JSON set in GB</string>
+    <string name="error_invalid_menu_structure">Invalid menu structure JSON</string>
+    <string name="button_open_menu_companion">Open menu companion app</string>
+    <string name="button_reset_menu_structure">Reset menu structure</string>
+    <string name="info_menu_structure_removed">Menu structure removed</string>
+    <string name="error_menu_companion_not_installed">\'HR Menu Companion\' probably not installed</string>
+    <string name="info_menu_structure_contents">Menu structure: %s</string>
+    <string name="info_fossil_rebuild_watchface_custom_menu">Please rebuild your watchface for custom menu</string>
+    <string name="devicetype_miband2_hrx">Mi Band HRX</string>
+
+    <string name="prefs_home_icon_left_item">Left</string>
+    <string name="prefs_home_icon_center_item">Center</string>
+    <string name="prefs_home_icon_right_item">Right</string>
+
+    <string name="red">Red</string>
+    <string name="yellow">Yellow</string>
+    <string name="green">Green</string>
+    <string name="cyan">Cyan</string>
+    <string name="blue">Blue</string>
+    <string name="purple">Purple</string>
+    <string name="white">White</string>
+
+    <string name="prefs_wena3_title_screen">Display Settings</string>
+    <string name="prefs_wena3_title_activity">Activity Settings</string>
+    <string name="prefs_wena3_item_background_sync">Background Activity Data Sync</string>
+    <string name="prefs_wena3_hint_background_sync">Allow the Wena to periodically ask Gadgetbridge to download activity data from the device</string>
+    <string name="prefs_wena3_hint_lift_wrist">Turn on the display when you look at your wrist</string>
+    <string name="prefs_wena3_item_large_font">Larger Font Size</string>
+    <string name="prefs_wena3_hint_large_font">Increase the font size in calendar, notifications, etc.</string>
+    <string name="prefs_wena3_item_rich_design">Use Rich Design</string>
+    <string name="prefs_wena3_hint_rich_design">Adds rounded rectangles around home screen icons</string>
+    <string name="prefs_wena3_item_weather_statusbar">Weather In Status Bar</string>
+    <string name="prefs_wena3_hint_weather_statusbar">Show current conditions icon in the top left corner of the home screen</string>
+    <string name="prefs_wena3_title_alarm">Alarm Settings</string>
+    <string name="prefs_wena3_smart_alarm_margin_item">Smart Alarm Margin</string>
+    <string name="prefs_wena3_vibration_strength_item">Vibration Strength</string>
+    <string name="prefs_wena3_vibration_smart_item">Smart Vibration</string>
+    <string name="prefs_wena3_vibration_strength_item_weak">Weak</string>
+    <string name="prefs_wena3_vibration_strength_item_medium">Medium</string>
+    <string name="prefs_wena3_vibration_strength_item_strong">Strong</string>
+    <string name="prefs_wena3_home_icon_title">Home Screen Icons</string>
+    <string name="prefs_wena3_home_icon_name_clock">Current Time</string>
+    <string name="prefs_wena3_home_icon_name_wena_pay">Wena Pay</string>
+    <string name="prefs_wena3_home_icon_name_qrio">Qrio</string>
+    <string name="prefs_wena3_home_icon_name_edy">Edy balance</string>
+    <string name="prefs_wena3_home_icon_name_pedometer">Step Count</string>
+    <string name="prefs_wena3_home_icon_name_energy">Body Energy</string>
+    <string name="prefs_wena3_home_icon_name_suica">Suica Balance</string>
+    <string name="prefs_wena3_home_icon_name_calories">Calories</string>
+    <string name="prefs_wena3_home_icon_name_riiiver">Riiiver</string>
+    <string name="prefs_wena3_menu_icon_name_payment">Payment</string>
+    <string name="prefs_wena3_menu_icon_title">Menu Icons</string>
+    <string name="prefs_wena3_menu_icon_hint">The Settings icon is always shown at the end</string>
+    <string name="prefs_wena3_status_page_title">Status Page Ordering</string>
+    <string name="prefs_wena3_day_start_hour_item">Day Starts At</string>
+    <string name="prefs_wena3_auto_power_off_item">Auto Power Off</string>
+    <string name="prefs_wena3_auto_power_off_hint">The device will turn off and on automatically at the specified schedule</string>
+    <string name="prefs_wena3_auto_power_off_turn_off_time_item">Shutdown time</string>
+    <string name="prefs_wena3_auto_power_off_turn_on_time_item">Resume time</string>
+    <string name="prefs_wena3_button_action_item">Button Action</string>
+    <string name="prefs_wena3_button_action_item_double">Double Press</string>
+    <string name="prefs_wena3_button_action_item_long">Long Press</string>
+    <string name="prefs_wena3_button_action_name_qrio_unlock">Qrio Unlock</string>
+    <string name="prefs_wena3_button_action_name_qrio_lock">Qrio Lock</string>
+    <string name="prefs_wena3_button_action_name_start_timer">Start active timer</string>
+    <string name="prefs_wena3_button_action_name_activity_screen">Activity Screen</string>
+    <string name="prefs_wena3_notification_settings_title">Notification Settings</string>
+    <string name="prefs_wena3_receive_calls_title">Incoming Call Settings</string>
+    <string name="prefs_wena3_receive_calls_item">Receive Call Notifications</string>
+    <string name="prefs_wena3_receive_calls_hint">If turned off, you will not get notified of incoming calls on the Wena</string>
+    <string name="prefs_wena3_notification_default_vibration">Notification Vibration</string>
+    <string name="prefs_wena3_notification_default_vibration_repetition">Notification Vibration Repeat</string>
+    <string name="prefs_wena3_notification_vibration_repetition_0">Indefinitely</string>
+    <string name="prefs_wena3_notification_vibration_repetition_1">Once</string>
+    <string name="prefs_wena3_notification_vibration_repetition_2">Twice</string>
+    <string name="prefs_wena3_notification_vibration_repetition_3">3 times</string>
+    <string name="prefs_wena3_notification_vibration_repetition_4">4 times</string>
+    <string name="prefs_wena3_notification_per_app_settings_title">Per-app Notification Settings</string>
+    <string name="prefs_wena3_notification_default_led">Notification LED Color</string>
+    <string name="prefs_wena3_notification_default_call_vibration">Incoming Call Vibration</string>
+    <string name="prefs_wena3_notification_default_call_vibration_repetition">Incoming Call Vibration Repeat</string>
+    <string name="prefs_wena3_notification_default_call_led">Incoming Call LED Color</string>
+    <string name="prefs_wena3_vibration_none">No Vibration</string>
+    <string name="prefs_wena3_vibration_continuous">Continuous</string>
+    <string name="prefs_wena3_vibration_basic">Basic</string>
+    <string name="prefs_wena3_vibration_rapid">Rapid</string>
+    <string name="prefs_wena3_vibration_triple">Triple</string>
+    <string name="prefs_wena3_vibration_step_up">Step Up</string>
+    <string name="prefs_wena3_vibration_step_down">Step Down</string>
+    <string name="prefs_wena3_vibration_warning">Warning</string>
+    <string name="prefs_wena3_vibration_siren">Siren</string>
+    <string name="prefs_wena3_vibration_short">Short</string>
+    <string name="prefs_wena3_led_none">No LED</string>
+    <string name="temperature_scale_cf">Temperature scale</string>
+    <string name="temperature_scale_cf_summary">Select whether device uses Celsius or Fahrenheit scale.</string>
+    <string name="temperature_scale_celsius">Celsius</string>
+    <string name="temperature_scale_fahrenheit">Fahrenheit</string>
+    <string name="fossil_hr_nav_app_not_installed_notify_title">Navigation app not installed on watch</string>
+    <string name="fossil_hr_nav_app_not_installed_notify_text">Navigation started but navigationApp not installed on watch. Please install it from the App Manager.</string>
+    <string name="call_rejection_method_reject">Reject</string>
+    <string name="call_rejection_method_ignore">Ignore (silence)</string>
+    <string name="pref_call_rejection_method_title">Call rejection method</string>
+    <string name="pref_call_rejection_method_summary">Which action is taken when an incoming call is rejected from the watch</string>
+    <string name="pref_title_prefix_notification_with_app">App name in notification</string>
+    <string name="pref_summary_prefix_notification_with_app">Prefix notification title with name of source application</string>
+    <string name="pref_title_osmand_packagename">OsmAnd package name</string>
+    <string name="pref_summary_osmand_packagename">Used for selecting the version of OsmAnd to connect to</string>
+    <string name="pref_title_navigation_prefs">Navigation preferences</string>
+    <string name="pref_title_navigation_apps">Navigation apps</string>
+    <string name="pref_navigation_app_osmand">OsmAnd(+)</string>
+    <string name="pref_navigation_app_gmaps">Google Maps</string>
+    <string name="serial_number">Serial Number</string>
+    <string name="menuitem_stats">Stats</string>
+    <string name="menuitem_running">Running</string>
+    <string name="menuitem_alerts">Alerts</string>
+    <string name="menuitem_focus">Focus</string>
+    <string name="bedtime">Bedtime</string>
+    <string name="wake_up_time">Wake Up</string>
+    <string name="pref_sleep_mode_schedule_title">Sleep Mode Schedule</string>
+    <string name="pref_sleep_mode_schedule_summary">Send a reminder and enter sleep mode at bedtime. At the scheduled wake-up time, the wake-up alarm will sound.</string>
+    <string name="devicetype_xiaomi_watch_s1">Xiaomi Watch S1</string>
+    <string name="devicetype_xiaomi_watch_s3">Xiaomi Watch S3</string>
+    <string name="devicetype_xiaomi_watch_s1_active">Xiaomi Watch S1 Active</string>
+    <string name="devicetype_xiaomi_watch_s1_pro">Xiaomi Watch S1 Pro</string>
+    <string name="devicetype_mi_watch_color_sport">Mi Watch Color Sport</string>
+    <string name="devicetype_pixoo">Pixoo</string>
+    <string name="not_set">Not set</string>
+    <string name="garmin_intensity_minutes">Intensity Minutes</string>
+    <string name="pref_vitality_score_title">Vitality Score</string>
+    <string name="pref_vitality_score_7_day_title">7-day progress</string>
+    <string name="pref_vitality_score_7_day_summary">Get a notification when your vitality score reaches 30, 60 or 100 in the past 7 days</string>
+    <string name="pref_vitality_score_daily_title">Daily progress</string>
+    <string name="pref_vitality_score_daily_summary">Get a notification when you reached the maximum number of vitality points for the day</string>
+    <string name="widget">Widget</string>
+    <string name="widget_screen">Widget Screen</string>
+    <string name="widget_screen_delete_confirm_title">Delete widget screen</string>
+    <string name="widget_screen_delete_confirm_description">Are you sure you want to delete \'%1$s\'?</string>
+    <string name="widget_screen_no_free_slots_description">The device has no free slots for widget screens (total slots: %1$s)</string>
+    <string name="widget_screen_min_screens">There must be a minimum of %1$s screens</string>
+    <string name="widget_layout_top_1_bot_2">1 top, 2 bottom</string>
+    <string name="widget_layout_top_2_bot_1">2 top, 1 bottom</string>
+    <string name="widget_layout_top_2_bot_2">2 top, 2 bottom</string>
+    <string name="widget_layout_single">1 widget</string>
+    <string name="widget_layout_two">2 widgets</string>
+    <string name="widget_layout_top_wide_bot_large">Wide above, large below</string>
+    <string name="widget_layout_top_large_bot_wide">Large above, wide below</string>
+    <string name="widget_layout">Widget layout</string>
+    <string name="widget_subtype">Widget Subtype</string>
+    <string name="widget_screen_x">Screen %s</string>
+    <string name="widget_move_up">Move up</string>
+    <string name="widget_move_down">Move down</string>
+    <string name="widget_missing_parts">Please select all widgets</string>
+    <string name="widget_unknown_workout">Unknown workout - %s</string>
+    <string name="widget_name_colored_tile">%1$s (colored tile)</string>
+    <string name="widget_name_untitled">Untitled widget (%1$s)</string>
+    <string name="pref_title_fossil_hr_navigation_instructions">Navigation instructions</string>
+    <string name="pref_summary_fossil_hr_navigation_instructions">Configure on-watch navigation app behavior</string>
+    <string name="pref_title_fossil_hr_nav_foreground">Come to foreground</string>
+    <string name="pref_summary_fossil_hr_nav_foreground">Whether the navigation app should automatically come to the foreground when it receives a navigation instruction</string>
+    <string name="pref_title_fossil_hr_nav_vibrate">Vibrate on new instruction</string>
+    <string name="pref_summary_fossil_hr_nav_vibrate">Whether the watch should vibrate on every new or changed navigation instruction (only when the app is in the foreground)</string>
+    <string name="notification_channel_connection_status_name">Connection Status</string>
+    <string name="uploading_watchface">Uploading watchface鈥�</string>
+    <string name="uploadwatchfaceoperation_in_progress">Uploading watchface</string>
+    <string name="uploadwatchfaceoperation_complete">Watchface installation completed</string>
+    <string name="uploadwatchfaceoperation_failed">Watchface installation failed</string>
+    <string name="pref_force_connection_type_title">Force connection type</string>
+    <string name="pref_force_connection_type_description">You may try forcing the connection type in case your device does not respond to Gadgetbridge</string>
+    <string name="pref_force_connection_type_auto">Automatic</string>
+    <string name="pref_force_connection_type_ble">Bluetooth LE</string>
+    <string name="pref_force_connection_type_bt_classic">Bluetooth Classic</string>
+    <string name="pref_force_connection_type_auto_value" translatable="false">BOTH</string>
+    <string name="pref_force_connection_type_ble_value" translatable="false">BLE</string>
+    <string name="pref_force_connection_type_bt_classic_value" translatable="false">BT_CLASSIC</string>
+    <string name="activity_info">Activity info</string>
+    <string name="warning_missing_notification_permission">Could not post ongoing notification due to missing permission</string>
+    <string name="pref_test_features_title">Features</string>
+    <string name="pref_test_features_summary">Enabled features for this test device</string>
+    <string name="pref_developer_add_test_activities_title">Add test activities</string>
+    <string name="pref_developer_add_test_activities_summary">Populate the database with dummy test activities</string>
+    <string name="device_state_waiting_scan">Waiting for device scan</string>
+    <string name="auto_reconnect_ble_scan_title">Reconnect by BLE scan</string>
+    <string name="auto_reconnect_ble_scan_summary">Wait for device scan instead of blind connection attempts</string>
+    <string name="prompt_restart_gadgetbridge">Please restart GB in order to take effect.</string>
+    <string name="waiting_for_bluetooth">Waiting for bluetooth鈥�</string>
+    <string name="error_scan_failed">"Scan failed: %d"</string>
+    <string name="scan_not_scanning">Not scanning</string>
+    <string name="scan_scanning_all_devices">Scanning all devices</string>
+    <string name="scan_scanning_single_device">Scanning 1 device</string>
+    <string name="scan_scanning_multiple_devices">Scanning %d devices</string>
+    <string name="notification_channel_scan_service_name">Scan service</string>
+    <string name="unbind_before_pair_title">Already bound</string>
+    <string name="unbind_before_pair_message">This device is already bound in Android settings, which can make pairing fail for some devices.\n\nIf adding the device fails, please remove it in Android settings and try again.</string>
+    <string name="companion_pairing_request_title">Companion device</string>
+    <string name="companion_pairing_request_description">Pair this device as companion?.\n\nThis is recommended for some functions such as find device, and provides a better connection.</string>
+    <string name="devicetype_scannable">Scannable device</string>
+    <string name="state_scanned">Scanned</string>
+    <string name="devicesetting_scannable_debounce">Scannable debounce timeout (seconds)</string>
+    <string name="devicesetting_scannable_minimum_unseen">Minimum unseen time (seconds)</string>
+    <string name="devicesetting_scannable_rssi">Minimal RSSI threshold</string>
+    <string name="devicesetting_scannable_debounce_summary">After being scanned, the device will stick as scanned and ignored for the specified amount of time</string>
+    <string name="devicesetting_scannable_minimum_unseen_summary">After being scanned, the device has to be unseen for this amount of time before being registered again</string>
+    <string name="devicesetting_scannable_rssi_summary">The minimum RSSI threshold for detection</string>
+    <string name="error_showing_changelog">Error showing Changelog</string>
+    <string name="activity_type_worn">Worn</string>
+    <string name="dashboard_settings">Dashboard settings</string>
+    <string name="bottom_nav_dashboard">Dashboard</string>
+    <string name="bottom_nav_devices">Devices</string>
+    <string name="pref_dashboard_first_title">Show dashboard first</string>
+    <string name="pref_dashboard_first_summary">Show the dashboard when Gadgetbridge starts, instead of the devices screen</string>
+    <string name="pref_dashboard_cards_title">Show widgets on cards</string>
+    <string name="pref_dashboard_cards_summary">Draw cards around the widgets on the dashboard</string>
+    <string name="pref_dashboard_widget_settings">Widget settings</string>
+    <string name="pref_dashboard_widget_today_title">Activity chart</string>
+    <string name="pref_dashboard_widget_today_24h_title">24h mode</string>
+    <string name="pref_dashboard_widget_double_size_title">Double size</string>
+    <string name="pref_dashboard_widget_show_legend_title">Show legend</string>
+    <string name="pref_dashboard_widget_goals_chart_title">Goals chart</string>
+    <string name="pref_dashboard_devices_to_include">Devices to include</string>
+    <string name="pref_dashboard_all_devices_title">All devices</string>
+    <string name="pref_dashboard_select_devices_title">Select devices...</string>
+    <string name="pref_dashboard_widgets_order_summary">Select which widgets are enabled and in what order they are displayed on the dashboard</string>
+    <string name="pref_dashboard_widget_today_24h_summary">Show the activity in a single 24h circle instead of a double 12h circle</string>
+    <string name="pref_dashboard_widget_double_size_summary">Allow the widget to take up two columns on the dashboard</string>
+    <string name="pref_dashboard_widget_show_legend_summary">Show a legend below the widget explaining the colors</string>
+    <string name="pref_dashboard_all_devices_summary">Combine activity data from all added devices for the totals on the dashboard</string>
+    <string name="pref_dashboard_select_devices_summary">Combine activity data from specific devices for the totals on the dashboard</string>
+    <string name="pref_dashboard_widget_today_hr_interval_title">Heart rate interval</string>
+    <string name="pref_dashboard_widget_today_hr_interval_summary">The amount of minutes the chart shows \'worn\' after each successful heart rate measurement</string>
+    <plurals name="amount_of_days">
+        <item quantity="one">%d day</item>
+        <item quantity="other">%d days</item>
+    </plurals>
+    <string name="dashboard_calendar_month_goals_reached_title">% of steps goal reached</string>
+    <string name="pref_auto_reply_calls_summary">The phone will automatically pick-up incoming phonecalls</string>
+    <string name="pref_auto_reply_calls_title">Automatically answer phone calls</string>
+    <string name="pref_auto_reply_calls_delay_summary">Number of seconds after which the call is automatically picked up</string>
+    <string name="pref_auto_reply_calls_delay_title">Automatic Answer Delay</string>
+    <string name="pref_speak_notifications_aloud_summary">Notifications will be read aloud through the headphones</string>
+    <string name="pref_speak_notifications_aloud_title">Speak Notifications Aloud</string>
+    <string name="pref_speak_notifications_focus_exclusive_title">Pause audio of other applications</string>
+    <string name="pref_speak_notifications_focus_exclusive_summary_on">Playback from other applications will be paused while the notification is spoken</string>
+    <string name="pref_speak_notifications_focus_exclusive_summary_off">Playback volume of other applications will be lowered while the notification is spoken</string>
+    <string name="pref_header_calls_and_notifications">Calls and notifications</string>
+    <string name="pref_title_bottom_navigation_bar">Bottom navigation bar</string>
+    <string name="pref_summary_bottom_navigation_bar_on">Switch between main screens using the navigation bar or horizontal swiping</string>
+    <string name="pref_summary_bottom_navigation_bar_off">Switch between main screens only using horizontal swiping</string>
+    <string name="pref_summary_garmin_default_reply_suffix">Appended in addition to the suffix set in Gadgetbridge</string>
+    <string name="pref_title_garmin_default_reply_suffix">Use predefined reply suffix</string>
+    <string name="pref_dashboard_widget_today_upside_down_title">Midnight at bottom</string>
+    <string name="pref_dashboard_widget_today_upside_down_summary">In 24h mode, draw midnight at the bottom, midday at the top of the chart</string>
+
+    <string name="sleepasandroid_settings">Sleep As Android</string>
+    <string name="pref_sleepasandroid_enable_summary">Enable Sleep As Android integration</string>
+    <string name="pref_sleepasandroid_device_title">Provider device</string>
+    <string name="pref_sleepasandroid_device_summary">Select device as Sleep As Android data provider</string>
+    <string name="pref_sleepasandroid_features_title">Features</string>
+    <string name="pref_sleepasandroid_features_summary">Support differs from device to device</string>
+    <string name="pref_sleepasandroid_feat_alarms">Alarms</string>
+    <string name="pref_sleepasandroid_slot_title">Alarms slot</string>
+    <string name="pref_sleepasandroid_slot_summary">Which alarm slot to use when setting alarms</string>
+    <string name="alarm_slot_reset">Alarm slot has been set to default</string>
+    <string name="pref_sleepasandroid_feat_notifications">Notifications</string>
+    <string name="pref_sleepasandroid_feat_movement">Accelerometer</string>
+    <string name="pref_sleepasandroid_feat_heartrate">Heart rate</string>
+    <string name="pref_sleepasandroid_feat_oximetry">Oximetry</string>
+    <string name="pref_sleepasandroid_feat_spo2">SPO2</string>
+    <string name="pref_title_huawei_account">Huawei Account</string>
+    <string name="pref_summary_huawei_account">Huawei account used in pairing process. Setting it allows to pair without factory reset.</string>
+    <string name="watchface_resolution_doesnt_match">Watchface resolution doesnt match device screen. Watchface is %1$s device screen is %2$s</string>
+    <string name="device_name_bicycle_sensor">Bicycle sensor</string>
+    <string name="device_name_cycling_sensor">Cycling sensor</string>
+    <string name="devicetype_cycling_sensor">Cycling speed sensor</string>
+    <string name="title_cycling">Cycling</string>
+    <string name="pref_summary_wheel_diameter">Wheel diameter in inches. Typically 29, 27,5 or 26.</string>
+    <string name="pref_title_wheel_diameter">Wheel diameter</string>
+    <string name="pref_summary_cycling_persistence_interval">Interval in seconds when the current cycling data should be written to the database</string>
+    <string name="pref_title_cycling_persistence_interval">Persistence interval</string>
+    <string name="chart_cycling_point_label_distance">Today: %.1f km\nTotal: %.1f km</string>
+    <string name="chart_cycling_point_label_speed">%.1f km/h</string>
+    <string name="toast_setting_requires_reconnect">This setting will take effect after a reconnect</string>
+
+    <!-- Camera strings -->
+    <string name="open_camera">Open Camera</string>
+    <string name="toast_camera_permission_required">Camera permission is required for this function.</string>
+    <string name="toast_camera_support_required">Camera support is required for this function.</string>
+    <string name="toast_camera_photo_taken">Photo has been taken and saved at: %s</string>
+
+    <!-- Battery polling preference strings -->
+    <string name="pref_battery_polling_configuration">Battery polling configuration</string>
+    <string name="pref_battery_polling_summary">This is best-effort, and might be delayed for several reasons</string>
+    <string name="pref_battery_polling_enable">Enable battery polling</string>
+    <string name="pref_battery_polling_interval">Battery polling interval</string>
+    <string name="pref_battery_polling_interval_format">every %1$s minutes</string>
+    <string name="battery_polling_failed_start">Failed to start the battery polling</string>
+
+    <string name="none">None</string>
+    <string name="garmin_agps_url_i">AGPS %1$d URL</string>
+    <string name="no_folder_selected">No folder selected</string>
+    <string name="folder_is_empty">Folder is empty</string>
+    <string name="folder">Folder</string>
+    <string name="url">URL</string>
+    <string name="number_selected_items">%1d selected</string>
+    <string name="garmin_agps_local_file">Local file</string>
+    <string name="pref_garmin_agps_help">The list below contains all URLs requested by the watch for AGPS updates. You can select a file from the phone\'s storage that will be sent to the watch when it requests an update.</string>
+    <string name="copied_to_clipboard">Copied to clipboard</string>
+    <string name="loading">Loading鈥�</string>
+    <string name="toggle_debug_mode">Toggle debug mode</string>
+    <string name="share_debug_info">Share debug info</string>
+    <string name="realtime_settings">Realtime settings</string>
+    <string name="unsupported">Unsupported</string>
+    <string name="min_val">Minimum: %d</string>
+    <string name="max_val">Maximum: %d</string>
+    <string name="show_in_notification">Show in notification</string>
+    <string name="battery_low_notify_enabled">Notify on low battery</string>
+    <string name="battery_low_threshold">Low battery threshold</string>
+    <string name="battery_full_notify_enabled">Notify on full battery</string>
+    <string name="battery_full_threshold">Full battery threshold</string>
+    <string name="default_percentage">Default (%1$d%%)</string>
+    <string name="battery_percentage_str">%1$s%%</string>
+    <string name="pref_fetch_unknown_files_title">Fetch unknown files</string>
+    <string name="pref_fetch_unknown_files_summary">Fetch unknown activity files from the watch. They will not be processed, but will be saved in the phone.</string>
+    <string name="cannot_upload_watchface_too_many_watchfaces_installed">"Cannot upload watchface, too many watchfaces installed"</string>
+    <string name="insufficient_space_for_upload">"Insufficient space for upload"</string>
+    <string name="file_already_exists">"File already exists"</string>
+    <string name="smart_ring_measurement_error_worn_incorrectly">Measurement error. Are the ring\'s sensors oriented correctly?</string>
+    <string name="smart_ring_measurement_error_unknown">Unknown measurement error %d received from ring</string>
+    <string name="pref_header_deprecated_functionalities">Deprecated functionalities</string>
+    <string name="pref_header_deprecated_functionalities_warning">The following functionalities have been deprecated and will be removed soon from the software.\nIf you need to enable one of the following settings be sure to get in touch with the project team.</string>
+    <string name="pref_deprecated_media_control_title">Deprecated media control</string>
+    <string name="pref_deprecated_media_control_summary">Send media control commands as key events instead of the media controller.</string>
+    <string name="devicetype_ble_gatt_client">Generic BLE GATT Client</string>
+    <string name="prefs_title_gatt_client_notification_intents">Broadcast GATT notification Intents through BLE Intent API</string>
+    <string name="prefs_summary_gatt_client_notification_intents">Receive BLE characteristic changes through Intents</string>
+    <string name="prefs_title_gatt_client_allow_gatt_interactions">Allow GATT interaction through BLE Intent API</string>
+    <string name="prefs_summary_gatt_client_allow_gatt_interactions">Allow to send BLE characteristic read/write and connect commands</string>
+    <string name="prefs_summary_gatt_client_device_state_updates">Receive BLE connection state changes via Intents</string>
+    <string name="prefs_title_gatt_client_api_package">BLE API package</string>
+    <string name="prefs_summary_gatt_client_api_package">Restrict BLE Intent API communication to this package</string>
+    <string name="prefs_title_ble_intent_api">BLE Intent API</string>
+    <string name="activity_db_management_backup_restore_label">Backup and Restore</string>
+    <string name="activity_db_management_export_to_zip">Export zip</string>
+    <string name="activity_db_management_import_from_zip">Import zip</string>
+    <string name="activity_db_management_backup_restore_explanation">The import/export operations allows you to migrate or backup all Gadgetbridge settings, devices and data to and from a zip file.\n\nImporting a file will remove all existing data, devices, and preferences, completely replacing them with the backup.</string>
+    <string name="backup_restore_exporting">Exporting to zip鈥�</string>
+    <string name="backup_restore_exporting_preferences">Exporting preferences鈥�</string>
+    <string name="backup_restore_exporting_database">Exporting database鈥�</string>
+    <string name="backup_restore_exporting_files">Exporting files鈥�</string>
+    <string name="backup_restore_exporting_files_i_of_n">Exporting files鈥� %1d of %2d</string>
+    <string name="backup_restore_exporting_finishing">Finishing export鈥�</string>
+    <string name="backup_restore_importing">Importing from zip鈥�</string>
+    <string name="backup_restore_do_not_exit">%s Please keep this screen open until the operation finishes.</string>
+    <string name="backup_restore_importing_loading">Loading file鈥�</string>
+    <string name="backup_restore_importing_validating">Validating file鈥�</string>
+    <string name="backup_restore_importing_files_i_of_n">Importing files鈥� %1d of %2d</string>
+    <string name="backup_restore_importing_database">Importing database鈥�</string>
+    <string name="backup_restore_importing_preferences">Importing preferences鈥�</string>
+    <string name="backup_restore_warning_files">%1d files failed to be restored: \n%2s</string>
+    <string name="backup_restore_export_complete">Export complete</string>
+    <string name="backup_restore_import_complete">Import complete</string>
+    <string name="backup_restore_error_export">Export to zip failed</string>
+    <string name="backup_restore_error_import">Import from zip failed</string>
+    <string name="backup_restore_abort_title">Abort</string>
+    <string name="backup_restore_abort_export_confirmation">Abort the export? The partial zip file will be deleted.</string>
+    <string name="backup_restore_abort_import_confirmation">Abort the import? This may lead to a corrupted or inconsistent database.</string>
+    <string name="backup_restore_restart_title">Restart</string>
+    <string name="backup_restore_restart_summary">%1s will now restart.</string>
+    <string name="label_distance_trip">Trip: %.1f km</string>
+    <string name="label_distance_total">Total: %.1f km</string>
+    <string name="label_distance_trip_mph">Trip: %.1f mi</string>
+    <string name="label_distance_total_mph">Total: %.1f mi</string>
+    <string name="error_no_cycling_sensor_found">no cycling sensor found</string>
+    <string name="contact_birthday">%1s\'s birthday</string>
+    <string name="birthdays">Birthdays</string>
+    <string name="pref_dashboard_widget_today_yesterday_data_title">Data from yesterday</string>
+    <string name="pref_dashboard_widget_today_yesterday_data_summary">Show data from yesterday dimmed between the current time and midnight</string>
+    <string name="pref_dashboard_widget_today_time_indicator_title">Current time indicator</string>
+    <string name="pref_dashboard_widget_today_time_indicator_summary">Show an indicator at the current time, to visually separate data from yesterday and today</string>
+    <string name="pref_dnd_follow_phone_title">Follow phone DND setting</string>
+    <string name="pref_dnd_follow_phone_summary">When DND is enabled or disabled on the phone, automatically toggle it on the device too</string>
+    <string name="inactivity_warnings_minimum_steps_title">Minimum amount of steps</string>
+    <string name="inactivity_warnings_minimum_steps_summary">Minimum amount of steps that need to be taken during the threshold minutes</string>
+    <string name="prefs_hrv_monitoring_title">HRV monitoring</string>
+    <string name="prefs_hrv_monitoring_description">Automatically monitor heart rate variability throughout the day</string>
+    <string name="pref_music_management_title">Manage Music</string>
+    <string name="pref_music_management_summary">Manage music on the watch</string>
+    <string name="music_delete_confirm_description">Are you sure you want to delete \'%1$s\'?</string>
+    <string name="music_delete_multiple_confirm_description">Are you sure you want to delete \'%1$d\' songs?</string>
+    <string name="music_add_to_playlist">Add to playlist</string>
+    <string name="music_delete_from_playlist">Delete from playlist</string>
+    <string name="music_delete">Delete song</string>
+    <string name="music_all_songs">All songs</string>
+    <string name="music_new_playlist">New playlist</string>
+    <string name="music_rename_playlist">Rename playlist</string>
+    <string name="music_error">Error occurred</string>
+    <string name="music_huawei_device_info">Supported formats: %1$s\nWatch storage: %2$d MB</string>
+    <string name="music_multiselect_limit">Limit for selecting more than one item reached</string>
+
+    <!-- Welcome screens strings -->
+    <string name="first_start_welcome_title">Welcome</string>
+    <string name="first_start_intro_welcome_to">Welcome to</string>
+    <string name="first_start_intro_tag_line">Break free from the proprietary apps and cloud services of gadget vendors.</string>
+    <string name="first_start_overview_title">Overview</string>
+    <string name="first_start_overview_desc">Gadgetbridge has two main views, each with their own purpose.</string>
+    <string name="first_start_overview_dashboard">The dashboard allows you to get a quick idea of how you\'re doing today. The calendar view shows the status of your goals over a whole month.</string>
+    <string name="first_start_overview_devices">The devices view shows all devices you have configured and their status, and gives access to device specific functions such as detailed charts, settings, apps and alarms.</string>
+    <string name="first_start_open_source_title">Open Source</string>
+    <string name="first_start_open_source_text">Gadgetbridge is an open source app. It is developed by the community, for the community.\n\nAnyone is welcome to contribute via code, documentation, testing and donations.\n\nGadgetbridge contains no ads and no tracking. It keeps your data locally on your Android device, so it is 100% privacy friendly.\n\nVisit our website for more information, documentation and links to our communication channels.</string>
+    <string name="first_start_permissions_title">Permissions</string>
+    <string name="first_start_permissions_desc">Gadgetbridge needs a lot of permissions to perform all its functions. Review the permissions and their purposes below.</string>
+    <string name="first_start_permissions_request_all_button">Request all permissions</string>
+    <string name="first_start_permissions_request_button">Request</string>
+    <string name="first_start_get_started_title">Get started</string>
+    <string name="first_start_get_started_desc">To get started, add your first device directly from this screen, restore a backup or start with a clean database.</string>
+    <string name="first_start_get_started_add_first_device_button">Add first device</string>
+    <string name="first_start_get_started_restore_button">Restore backup</string>
+    <string name="first_start_get_started_go_to_app_button">Go to the app</string>
+    <string name="permission_notifications_summary">Forwarding notifications to connected gadgets</string>
+    <string name="permission_manage_dnd_title">Manage Do Not Disturb</string>
+    <string name="permission_manage_dnd_summary">Changing DND notification policy</string>
+    <string name="permission_displayover_title">Display over other apps</string>
+    <string name="permission_displayover_summary">Used by Bangle.js for starting apps and other functionality on your phone</string>
+    <string name="permission_fine_location_title">Fine location</string>
+    <string name="permission_fine_location_summary">Scanning for Bluetooth devices</string>
+    <string name="permission_background_location_title">Background location</string>
+    <string name="permission_background_location_summary">Scanning for Bluetooth devices in the background and sending the location to certain gadgets</string>
+    <string name="permission_bluetooth_title">Bluetooth</string>
+    <string name="permission_bluetooth_summary">Connecting to Bluetooth devices</string>
+    <string name="permission_bluetooth_admin_title">Bluetooth admin</string>
+    <string name="permission_bluetooth_admin_summary">Discovering and pairing Bluetooth devices</string>
+    <string name="permission_bluetooth_scan_title">Bluetooth scan</string>
+    <string name="permission_bluetooth_scan_summary">Scanning for new Bluetooth devices</string>
+    <string name="permission_bluetooth_connect_title">Bluetooth connect</string>
+    <string name="permission_bluetooth_connect_summary">Connecting to already-paired Bluetooth devices</string>
+    <string name="permission_post_notification_title">Post notifications</string>
+    <string name="permission_post_notification_summary">Posting ongoing notification which keeps the service running</string>
+    <string name="permission_internet_access_title">Internet access</string>
+    <string name="permission_internet_access_summary">Synchronization with online resources</string>
+    <string name="permission_contacts_title">Contacts</string>
+    <string name="permission_contacts_summary">Sending contacts to gadgets</string>
+    <string name="permission_calendar_title">Calendar</string>
+    <string name="permission_calendar_summary">Sending calendar to gadgets</string>
+    <string name="permission_receive_sms_title">Receive SMS</string>
+    <string name="permission_receive_sms_summary">Forwarding SMS messages to gadgets</string>
+    <string name="permission_send_sms_title">Send SMS</string>
+    <string name="permission_send_sms_summary">Sending SMS (canned response) from gadgets</string>
+    <string name="permission_read_call_log_title">Read call log</string>
+    <string name="permission_read_call_log_summary">Forwarding call log to gadgets</string>
+    <string name="permission_read_phone_state_title">Read phone state</string>
+    <string name="permission_read_phone_state_summary">Reading status of ongoing calls</string>
+    <string name="permission_call_phone_title">Call phone</string>
+    <string name="permission_call_phone_summary">Initiating phone calls from gadgets</string>
+    <string name="permission_process_outgoing_calls_title">Process outgoing calls</string>
+    <string name="permission_process_outgoing_calls_summary">Reading the number of an outgoing call to display it on a gadget</string>
+    <string name="permission_answer_phone_calls_title">Answer phone calls</string>
+    <string name="permission_answer_phone_calls_summary">Answering phone calls from gadgets</string>
+    <string name="permission_external_storage_title">External storage</string>
+    <string name="permission_external_storage_summary">Using images, ringtones, app files and more</string>
+    <string name="permission_query_all_packages_title">Query all packages</string>
+    <string name="permission_query_all_packages_summary">Reading names and icons of all installed apps</string>
+    <string name="about_build_details_copied_to_clipboard">Build details copied to clipboard</string>
+    <string name="devicetype_marstek_b2500">Marstek B2500</string>
+    <string name="battery_discharge_intervals">Battery Discharge Intervals</string>
+    <string name="discharge_interval_1">Discharge interval 1</string>
+    <string name="discharge_interval_2">Discharge interval 2</string>
+    <string name="discharge_interval_3">Discharge interval 3</string>
+    <string name="discharge_interval_4">Discharge interval 4</string>
+    <string name="discharge_interval_5">Discharge interval 5</string>
+    <string name="power_w">Power in W</string>
+    <string name="manual_discharge_summary">when disabled, this assumes intelligent discharge controlled by an external power meter (not supported by Gadgetbridge)</string>
+    <string name="manual_discharge">Manual discharge intervals</string>
+    <string name="summary_battery_discharge_intervals_set">Send configuration below to device</string>
+    <string name="battery_minimum_charge">Minimum battery charge in %</string>
+    <string name="battery_allow_pass_though_summary">When enabled, the battery can be charged while discharging</string>
+    <string name="battery_allow_pass_through">Allow battery pass-through</string>
+</resources>
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000..78d703e
--- /dev/null
+++ b/app/src/main/res/values/styles.xml
@@ -0,0 +1,224 @@
+<resources xmlns:tools="http://schemas.android.com/tools">
+
+    <!-- light theme -->
+    <style name="GadgetbridgeTheme" parent="Theme.Material3.Light">
+        <item name="textColorPrimary">@color/primarytext_light</item>
+        <item name="textColorSecondary">@color/secondarytext</item>
+        <item name="textColorTertiary">@color/tertiarytext_light</item>
+
+        <item name="colorPrimary">@color/primarydark_light</item>
+        <item name="colorPrimaryDark">@color/primarydark_light</item>
+
+        <item name="colorPrimaryContainer">@color/accent</item>
+        <item name="colorOnPrimaryContainer">@android:color/white</item>
+
+        <item name="elevationOverlayEnabled">false</item>
+
+        <item name="actionBarStyle">@style/GadgetbridgeTheme.RedActionBar</item>
+        <item name="drawerArrowStyle">@style/GadgetbridgeTheme.DrawerButtonStyle</item>
+        <item name="toolbarNavigationButtonStyle">@style/GadgetbridgeTheme.NavigationButtonStyle</item>
+        <item name="actionOverflowButtonStyle">@style/GadgetbridgeTheme.OverflowMenuButton</item>
+        <item name="preferenceTheme">@style/AppPreferenceThemeOverlay</item>
+
+        <item name="drawer_header_color">?attr/colorPrimaryDark</item>
+        <item name="drawer_title_color">@android:color/white</item>
+        <item name="cardview_background_color">@color/cardview_light_background</item>
+        <item name="actionmenu_icon_color">@android:color/white</item>
+        <item name="chart_deep_sleep">@color/chart_deep_sleep_light</item>
+        <item name="chart_light_sleep">@color/chart_light_sleep_light</item>
+        <item name="chart_rem_sleep">@color/chart_rem_sleep_light</item>
+        <item name="chart_awake_sleep">@color/chart_awake_sleep_light</item>
+        <item name="chart_activity">@color/chart_activity_light</item>
+        <item name="chart_not_worn">@color/chart_not_worn_light</item>
+        <item name="alternate_row_background">@color/alternate_row_background_light</item>
+        <item name="row_separator">@color/row_separator_light</item>
+        <item name="sports_activity_summary_background">@color/chart_heartrate_fill</item>
+
+        <item name="deviceIconPrimary">#2196f3</item>
+        <item name="deviceIconOnPrimary">#ffffff</item>
+        <item name="deviceIconLight">#1f7fdb</item>
+        <item name="deviceIconDark">#4dabf5</item>
+
+        <item name="android:navigationBarColor">?attr/colorSurface</item>
+        <!-- FIXME: SDK 21 to 27 has light navbar on light background -->
+        <item name="android:windowLightNavigationBar" tools:targetApi="27">true</item>
+    </style>
+
+    <!-- dark theme -->
+    <style name="GadgetbridgeThemeDark" parent="Theme.Material3.Dark">
+        <item name="textColorPrimary">@color/primarytext_dark</item>
+        <item name="textColorSecondary">@color/secondarytext</item>
+        <item name="textColorTertiary">@color/tertiarytext_dark</item>
+
+        <item name="colorPrimary">@color/primarydark_dark</item>
+        <item name="colorPrimaryDark">@color/primarydark_dark</item>
+
+        <item name="colorOnPrimary">@android:color/white</item>
+        <item name="colorPrimaryContainer">@color/accent</item>
+        <item name="colorOnPrimaryContainer">@android:color/white</item>
+
+        <item name="elevationOverlayEnabled">false</item>
+
+        <item name="actionBarStyle">@style/GadgetbridgeTheme.RedActionBar</item>
+        <item name="drawerArrowStyle">@style/GadgetbridgeTheme.DrawerButtonStyle</item>
+        <item name="toolbarNavigationButtonStyle">@style/GadgetbridgeTheme.NavigationButtonStyle</item>
+        <item name="actionOverflowButtonStyle">@style/GadgetbridgeTheme.OverflowMenuButton</item>
+        <item name="preferenceTheme">@style/AppPreferenceThemeOverlay</item>
+
+        <item name="drawer_header_color">?attr/colorPrimaryDark</item>
+        <item name="drawer_title_color">@android:color/white</item>
+        <item name="cardview_background_color">@color/cardview_dark_background</item>
+        <item name="actionmenu_icon_color">@android:color/white</item>
+        <item name="chart_deep_sleep">@color/chart_deep_sleep_dark</item>
+        <item name="chart_light_sleep">@color/chart_light_sleep_dark</item>
+        <item name="chart_rem_sleep">@color/chart_rem_sleep_dark</item>
+        <item name="chart_awake_sleep">@color/chart_awake_sleep_dark</item>
+        <item name="chart_activity">@color/chart_activity_dark</item>
+        <item name="chart_not_worn">@color/chart_not_worn_dark</item>
+        <item name="alternate_row_background">@color/alternate_row_background_dark</item>
+        <item name="row_separator">@color/row_separator_dark</item>
+        <item name="sports_activity_summary_background">@color/alternate_row_background_dark</item>
+
+        <item name="deviceIconPrimary">#2196f3</item>
+        <item name="deviceIconOnPrimary">#ffffff</item>
+        <item name="deviceIconLight">#1f7fdb</item>
+        <item name="deviceIconDark">#4dabf5</item>
+
+        <item name="android:navigationBarColor">?attr/colorSurface</item>
+        <item name="android:windowLightNavigationBar" tools:targetApi="27">false</item>
+    </style>
+
+    <!-- black theme -->
+    <style name="GadgetbridgeThemeBlack" parent="GadgetbridgeThemeDark">
+        <item name="android:windowBackground">@android:color/black</item>
+        <item name="android:navigationBarColor">@android:color/black</item>
+        <item name="android:windowLightNavigationBar" tools:targetApi="27">false</item>
+    </style>
+
+    <!-- Material 3 dynamic color (Material You) themes -->
+    <style name="GadgetbridgeThemeDynamicLight" parent="Theme.Material3.DynamicColors.Light">
+        <item name="actionBarStyle">@style/GadgetbridgeThemeDynamic.ActionBar</item>
+        <item name="actionOverflowMenuStyle">@style/GadgetbridgeTheme.OverflowMenuStyle</item>
+        <item name="android:statusBarColor">?attr/colorSurface</item>
+        <item name="android:windowLightStatusBar" tools:targetApi="23">true</item>
+        <item name="android:colorControlHighlight">?attr/colorPrimary</item>
+        <item name="drawer_header_color">?attr/colorSurface</item>
+        <item name="drawer_title_color">?attr/colorOnSurface</item>
+        <item name="cardview_background_color">?attr/colorSurface</item>
+        <item name="actionmenu_icon_color">?attr/colorOnSurface</item>
+        <item name="alternate_row_background">?attr/colorSecondaryContainer</item>
+        <item name="row_separator">@color/row_separator_light</item>
+        <item name="sports_activity_summary_background">?attr/colorSurface</item>
+        <item name="preferenceTheme">@style/AppPreferenceThemeOverlay</item>
+        <item name="android:navigationBarColor">?attr/colorSurface</item>
+        <!-- FIXME: SDK 21 to 27 has light navbar on light background -->
+        <item name="android:windowLightNavigationBar" tools:targetApi="27">true</item>
+        <item name="deviceIconPrimary">?attr/colorPrimary</item>
+        <item name="deviceIconOnPrimary">?attr/colorOnPrimary</item>
+        <item name="deviceIconLight">?attr/colorPrimaryVariant</item>
+        <item name="deviceIconDark">?attr/colorPrimaryDark</item>
+    </style>
+    <style name="GadgetbridgeThemeDynamicDark" parent="Theme.Material3.DynamicColors.Dark">
+        <item name="actionBarStyle">@style/GadgetbridgeThemeDynamic.ActionBar</item>
+        <item name="actionOverflowMenuStyle">@style/GadgetbridgeTheme.OverflowMenuStyle</item>
+        <item name="android:statusBarColor">?attr/colorSurface</item>
+        <item name="android:windowLightStatusBar" tools:targetApi="23">false</item>
+        <item name="android:colorControlHighlight">?attr/colorPrimary</item>
+        <item name="drawer_header_color">?attr/colorSurface</item>
+        <item name="drawer_title_color">?attr/colorOnSurface</item>
+        <item name="cardview_background_color">?attr/colorSurface</item>
+        <item name="actionmenu_icon_color">?attr/colorOnSurface</item>
+        <item name="alternate_row_background">?attr/colorSecondaryContainer</item>
+        <item name="row_separator">@color/row_separator_dark</item>
+        <item name="sports_activity_summary_background">?attr/colorSurface</item>
+        <item name="preferenceTheme">@style/AppPreferenceThemeOverlay</item>
+        <item name="android:navigationBarColor">?attr/colorSurface</item>
+        <item name="android:windowLightNavigationBar" tools:targetApi="27">false</item>
+        <item name="deviceIconPrimary">?attr/colorPrimary</item>
+        <item name="deviceIconOnPrimary">?attr/colorOnPrimary</item>
+        <item name="deviceIconLight">?attr/colorPrimaryVariant</item>
+        <item name="deviceIconDark">?attr/colorPrimaryDark</item>
+    </style>
+    <style name="GadgetbridgeThemeDynamicDarkAmoled" parent="GadgetbridgeThemeDynamicDark">
+        <item name="android:windowBackground">@android:color/black</item>
+    </style>
+
+    <!-- theme variants without action bar -->
+    <style name="GadgetbridgeTheme.NoActionBar">
+        <item name="windowActionBar">false</item>
+        <item name="windowNoTitle">true</item>
+    </style>
+    <style name="GadgetbridgeThemeDark.NoActionBar">
+        <item name="windowActionBar">false</item>
+        <item name="windowNoTitle">true</item>
+    </style>
+    <style name="GadgetbridgeThemeBlack.NoActionBar">
+        <item name="windowActionBar">false</item>
+        <item name="windowNoTitle">true</item>
+    </style>
+    <style name="GadgetbridgeThemeDynamicLight.NoActionBar">
+        <item name="windowActionBar">false</item>
+        <item name="windowNoTitle">true</item>
+    </style>
+    <style name="GadgetbridgeThemeDynamicDark.NoActionBar">
+        <item name="windowActionBar">false</item>
+        <item name="windowNoTitle">true</item>
+    </style>
+    <style name="GadgetbridgeThemeDynamicDarkAmoled.NoActionBar">
+        <item name="windowActionBar">false</item>
+        <item name="windowNoTitle">true</item>
+    </style>
+
+    <!-- action bar styles -->
+    <style name="GadgetbridgeTheme.RedActionBar" parent="Widget.Material3.ActionBar.Solid">
+        <item name="titleTextStyle">@style/GadgetbridgeTheme.ActionBarTitleStyle</item>
+        <item name="background">@color/primarydark_dark</item>
+    </style>
+    <style name="GadgetbridgeThemeDynamic.ActionBar" parent="Widget.Material3.ActionBar.Solid">
+        <item name="background">?attr/colorSurface</item>
+        <item name="elevation">0dp</item>
+    </style>
+    <style name="GadgetbridgeTheme.DrawerButtonStyle" parent="@style/Widget.AppCompat.DrawerArrowToggle">
+        <item name="spinBars">true</item>
+        <item name="color">@android:color/white</item>
+    </style>
+    <style name="GadgetbridgeTheme.NavigationButtonStyle" parent="Widget.AppCompat.Toolbar.Button.Navigation">
+        <item name="tint">@android:color/white</item>
+    </style>
+    <style name="GadgetbridgeTheme.OverflowMenuButton" parent="Widget.AppCompat.ActionButton.Overflow">
+        <item name="tint">@android:color/white</item>
+    </style>
+    <style name="GadgetbridgeTheme.ActionBarTitleStyle" parent="TextAppearance.Material3.ActionBar.Title">
+        <item name="android:textColor">@color/primarytext_dark</item>
+    </style>
+    <style name="GadgetbridgeTheme.OverflowMenuStyle" parent="Widget.Material3.PopupMenu.Overflow">
+        <item name="android:popupBackground">?attr/colorSecondaryContainer</item>
+        <item name="android:textColorPrimary">?attr/colorOnSecondaryContainer</item>
+    </style>
+
+    <!-- splash screen -->
+    <style name="SplashTheme" parent="GadgetbridgeTheme.NoActionBar">
+        <item name="android:statusBarColor">@color/primarydark_dark</item>
+        <item name="android:navigationBarColor">@color/primarydark_dark</item>
+        <item name="android:windowLightNavigationBar" tools:targetApi="27">false</item>
+        <item name="android:windowBackground">@drawable/splash</item>
+    </style>
+
+    <!-- preferences screens -->
+    <style name="AppPreferenceThemeOverlay" parent="@style/PreferenceThemeOverlay">
+        <item name="switchPreferenceCompatStyle">@style/AppSwitchPreference</item>
+    </style>
+    <style name="AppSwitchPreference" parent="@style/Preference.SwitchPreferenceCompat.Material">
+        <item name="widgetLayout">@layout/preference_material_switch</item>
+    </style>
+    <style name="GridTile">
+        <item name="android:background">?android:attr/windowBackground</item>
+        <item name="android:padding">15dp</item>
+        <item name="android:gravity">center</item>
+        <item name="android:orientation">vertical</item>
+        <item name="android:layout_columnWeight">1</item>
+        <item name="android:layout_marginBottom">2dp</item>
+        <item name="android:layout_width">0dp</item>
+        <item name="android:layout_rowWeight">1</item>
+    </style>
+</resources>
diff --git a/app/src/main/res/values/values.xml b/app/src/main/res/values/values.xml
new file mode 100644
index 0000000..6e0e578
--- /dev/null
+++ b/app/src/main/res/values/values.xml
@@ -0,0 +1,148 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <!-- Note: these are not translated! They are used as preference keys -->
+    <item name="p_default" type="string">default</item>
+    <item name="p_staccato" type="string">staccato</item>
+    <item name="p_short" type="string">short</item>
+    <item name="p_medium" type="string">medium</item>
+    <item name="p_long" type="string">long</item>
+    <item name="p_waterdrop" type="string">waterdrop</item>
+    <item name="p_ring" type="string">ring</item>
+    <item name="p_alarm_clock" type="string">alarm_clock</item>
+
+    <item name="p_dateformat_auto" type="string">auto</item>
+    <item name="p_dateformat_time" type="string">dateformat_time</item>
+    <item name="p_dateformat_datetime" type="string">dateformat_datetime</item>
+    <item name="p_dateformat_day_month" type="string">day_month</item>
+    <item name="p_dateformat_month_day" type="string">month_day</item>
+
+    <item name="p_clock" type="string">clock</item>
+    <item name="p_steps" type="string">steps</item>
+    <item name="p_distance" type="string">distance</item>
+    <item name="p_calories" type="string">calories</item>
+    <item name="p_heart_rate" type="string">heart_rate</item>
+    <item name="p_battery" type="string">battery</item>
+
+    <item name="p_menuitem_status" type="string">status</item>
+    <item name="p_menuitem_activity" type="string">activity</item>
+    <item name="p_menuitem_weather" type="string">weather</item>
+    <item name="p_menuitem_alarm" type="string">alarm</item>
+    <item name="p_menuitem_timer" type="string">timer</item>
+    <item name="p_menuitem_compass" type="string">compass</item>
+    <item name="p_menuitem_settings" type="string">settings</item>
+    <item name="p_menuitem_alipay" type="string">alipay</item>
+    <item name="p_menuitem_notifications" type="string">notifications</item>
+    <item name="p_menuitem_breathing" type="string">breathing</item>
+    <item name="p_menuitem_music" type="string">music</item>
+    <item name="p_menuitem_more" type="string">more</item>
+    <item name="p_menuitem_nfc" type="string">nfc</item>
+    <item name="p_menuitem_hr" type="string">hr</item>
+    <item name="p_menuitem_spo2" type="string">spo2</item>
+    <item name="p_menuitem_pai" type="string">pai</item>
+    <item name="p_menuitem_stress" type="string">stress</item>
+    <item name="p_menuitem_cycles" type="string">period</item>
+    <item name="p_menuitem_eventreminder" type="string">eventreminder</item>
+    <item name="p_menuitem_workout" type="string">workout</item>
+    <item name="p_menuitem_worldclock" type="string">worldclock</item>
+    <item name="p_menuitem_findphone" type="string">findphone</item>
+    <item name="p_menuitem_dnd" type="string">dnd</item>
+    <item name="p_menuitem_stopwatch" type="string">stopwatch</item>
+    <item name="p_menuitem_mutephone" type="string">mutephone</item>
+    <item name="p_menuitem_takephoto" type="string">takephoto</item>
+    <item name="p_menuitem_alexa" type="string">alexa</item>
+    <item name="p_menuitem_timer_stopwatch" type="string">timer_stopwatch</item>
+    <item name="p_menuitem_goal" type="string">goal</item>
+    <item name="p_menuitem_sleep" type="string">sleep</item>
+    <item name="p_menuitem_pomodoro" type="string">pomodoro</item>
+    <item name="p_menuitem_events" type="string">events</item>
+    <item name="p_menuitem_widgets" type="string">widgets</item>
+    <item name="p_menuitem_temperature" type="string">temperature</item>
+    <item name="p_menuitem_barometer" type="string">barometer</item>
+    <item name="p_menuitem_flashlight" type="string">flashlight</item>
+    <item name="p_menuitem_countdown" type="string">countdown</item>
+    <item name="p_menuitem_personal_activity_intelligence" type="string">personal_activity_intelligence</item>
+    <item name="p_menuitem_workout_history" type="string">workout_history</item>
+    <item name="p_menuitem_female_health" type="string">female_health</item>
+    <item name="p_menuitem_workout_status" type="string">workout_status</item>
+
+    <!-- currently used for sounds -->
+    <item name="p_menuitem_button" type="string">button</item>
+    <item name="p_menuitem_calls" type="string">calls</item>
+    <item name="p_menuitem_inactivity_warning" type="string">inactivity_warning</item>
+    <item name="p_menuitem_sms" type="string">sms</item>
+    <item name="p_menuitem_email" type="string">email</item>
+
+    <item name="p_off" type="string">off</item>
+    <item name="p_on" type="string">on</item>
+    <item name="p_sunset" type="string">sunset</item>
+    <item name="p_always" type="string">always</item>
+    <item name="p_automatic" type="string">automatic</item>
+    <item name="p_scheduled" type="string">scheduled</item>
+    <item name="p_smart" type="string">smart</item>
+
+    <item name="p_normal" type="string">normal</item>
+    <item name="p_sensitive" type="string">sensitive</item>
+
+    <item name="p_unit_metric" type="string">metric</item>
+    <item name="p_unit_imperial" type="string">imperial</item>
+
+    <item name="p_timeformat_auto" type="string">auto</item>
+    <item name="p_timeformat_24h" type="string">24h</item>
+    <item name="p_timeformat_am_pm" type="string">am/pm</item>
+
+    <item name="p_pebble_privacy_mode_off" type="string">off</item>
+    <item name="p_pebble_privacy_mode_content" type="string">content</item>
+    <item name="p_pebble_privacy_mode_complete" type="string">complete</item>
+
+    <item name="p_call_privacy_mode_off" type="string">off</item>
+    <item name="p_call_privacy_mode_name" type="string">name</item>
+    <item name="p_call_privacy_mode_number" type="string">number</item>
+    <item name="p_call_privacy_mode_complete" type="string">complete</item>
+
+    <item name="p_cycling" type="string">cycling</item>
+    <item name="p_activity" type="string">activity</item>
+    <item name="p_activity_list" type="string">activitylist</item>
+    <item name="p_sleep" type="string">sleep</item>
+    <item name="p_steps_week" type="string">stepsweek</item>
+    <item name="p_stress" type="string">stress</item>
+    <item name="p_pai" type="string">pai</item>
+    <item name="p_speed_zones" type="string">speedzones</item>
+    <item name="p_hrv_status" type="string">hrvstatus</item>
+    <item name="p_body_energy" type="string">bodyenergy</item>
+    <item name="p_vo2max" type="string">vo2max</item>
+    <item name="p_heartrate" type="string">heartrate</item>
+    <item name="p_live_stats" type="string">livestats</item>
+    <item name="p_spo2" type="string">spo2</item>
+    <item name="p_respiratory_rate" type="string">respiratoryrate</item>
+    <item name="p_temperature" type="string">temperature</item>
+    <item name="p_weight" type="string">weight</item>
+
+    <item name="p_message_privacy_mode_off" type="string">off</item>
+    <item name="p_message_privacy_mode_complete" type="string">complete</item>
+    <item name="p_message_privacy_mode_bodyonly" type="string">bodyonly</item>
+
+    <item name="pref_button_action_disabled_value" translatable="false" type="string">UNKNOWN</item>
+    <item name="pref_media_play_value" translatable="false" type="string">PLAY</item>
+    <item name="pref_media_pause_value" translatable="false" type="string">PAUSE</item>
+    <item name="pref_media_playpause_value" translatable="false" type="string">PLAYPAUSE</item>
+    <item name="pref_media_next_value" translatable="false" type="string">NEXT</item>
+    <item name="pref_media_previous_value" translatable="false" type="string">PREVIOUS</item>
+    <item name="pref_media_volumeup_value" translatable="false" type="string">VOLUMEUP</item>
+    <item name="pref_media_volumedown_value" translatable="false" type="string">VOLUMEDOWN</item>
+    <item name="pref_media_forward_value" translatable="false" type="string">FORWARD</item>
+    <item name="pref_media_rewind_value" translatable="false" type="string">REWIND</item>
+    <item name="pref_device_action_broadcast_value" translatable="false" type="string">BROADCAST</item>
+    <item name="pref_device_action_fitness_app_control_start_value" translatable="false" type="string">FITNESS_CONTROL_START</item>
+    <item name="pref_device_action_fitness_app_control_stop_value" translatable="false" type="string">FITNESS_CONTROL_STOP</item>
+    <item name="pref_device_action_fitness_app_control_toggle_value" translatable="false" type="string">FITNESS_CONTROL_TOGGLE</item>
+    <item name="pref_device_action_dnd_off_value" translatable="false" type="string">DO_NOT_DISTURB_OFF</item>
+    <item name="pref_device_action_dnd_priority_value" translatable="false" type="string">DO_NOT_DISTURB_PRIORITY</item>
+    <item name="pref_device_action_dnd_alarms_value" translatable="false" type="string">DO_NOT_DISTURB_ALARMS</item>
+    <item name="pref_device_action_dnd_on_value" translatable="false" type="string">DO_NOT_DISTURB_ON</item>
+
+    <item name="pref_theme_value_system" translatable="false" type="string">system</item>
+    <item name="pref_theme_value_light" translatable="false" type="string">light</item>
+    <item name="pref_theme_value_dark" translatable="false" type="string">dark</item>
+    <item name="pref_theme_value_dynamic" translatable="false" type="string">dynamic</item>
+
+</resources>
diff --git a/app/src/main/res/values/wpi_attrs.xml b/app/src/main/res/values/wpi_attrs.xml
new file mode 100644
index 0000000..f431f31
--- /dev/null
+++ b/app/src/main/res/values/wpi_attrs.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<resources>
+    <declare-styleable name="WelcomePageIndicator">
+        <attr name="page_indicator_color" format="color" />
+    </declare-styleable>
+</resources>
diff --git a/app/src/main/res/xml/about_user.xml b/app/src/main/res/xml/about_user.xml
new file mode 100644
index 0000000..18c6d05
--- /dev/null
+++ b/app/src/main/res/xml/about_user.xml
@@ -0,0 +1,132 @@
+<?xml version="1.0" encoding="utf-8"?>
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+    <PreferenceCategory
+        app:iconSpaceReserved="false"
+        android:key="pref_category_activity_personal"
+        android:title="@string/activity_prefs_about_you">
+
+        <EditTextPreference
+            android:key="mi_user_alias"
+            android:maxLength="17"
+            android:title="@string/miband_prefs_alias"
+            app:iconSpaceReserved="false"
+            app:useSimpleSummaryProvider="true" />
+
+        <nodomain.freeyourgadget.gadgetbridge.util.XDatePreference
+            app:iconSpaceReserved="false"
+            android:key="activity_user_date_of_birth"
+            android:title="@string/activity_prefs_date_birth" />
+
+        <ListPreference
+            app:iconSpaceReserved="false"
+            android:defaultValue="2"
+            android:entries="@array/gender"
+            android:entryValues="@array/gender_values"
+            android:key="activity_user_gender"
+            android:summary="%s"
+            android:title="@string/activity_prefs_gender" />
+
+        <!--TODO: support localized heights and weights -->
+        <EditTextPreference
+            app:iconSpaceReserved="false"
+            android:inputType="number"
+            android:key="activity_user_height_cm"
+            android:maxLength="3"
+            android:title="@string/activity_prefs_height_cm"
+            app:useSimpleSummaryProvider="true" />
+
+        <EditTextPreference
+            app:iconSpaceReserved="false"
+            android:inputType="number"
+            android:key="activity_user_weight_kg"
+            android:maxLength="3"
+            android:title="@string/activity_prefs_weight_kg"
+            app:useSimpleSummaryProvider="true" />
+
+        <EditTextPreference
+            app:iconSpaceReserved="false"
+            android:inputType="number"
+            android:key="activity_user_step_length_cm"
+            android:maxLength="3"
+            android:title="@string/activity_prefs_step_length_cm"
+            app:useSimpleSummaryProvider="true" />
+
+    </PreferenceCategory>
+
+    <PreferenceCategory
+        app:iconSpaceReserved="false"
+        android:key="pref_category_targets"
+        android:title="@string/activity_prefs_goals">
+
+        <EditTextPreference
+            app:iconSpaceReserved="false"
+            android:inputType="number"
+            android:key="activity_user_goal_weight_kg"
+            android:maxLength="3"
+            android:title="@string/activity_prefs_target_weight_kg"
+            app:useSimpleSummaryProvider="true" />
+
+        <EditTextPreference
+            app:iconSpaceReserved="false"
+            android:defaultValue="10000"
+            android:inputType="number"
+            android:key="fitness_goal"
+            android:maxLength="5"
+            android:title="@string/miband_prefs_fitness_goal"
+            app:useSimpleSummaryProvider="true" />
+
+        <EditTextPreference
+            app:iconSpaceReserved="false"
+            android:inputType="number"
+            android:key="activity_user_sleep_duration"
+            android:maxLength="2"
+            android:title="@string/activity_prefs_sleep_duration"
+            app:useSimpleSummaryProvider="true" />
+
+        <EditTextPreference
+            app:iconSpaceReserved="false"
+            android:defaultValue="350"
+            android:inputType="number"
+            android:key="activity_user_calories_burnt"
+            android:maxLength="4"
+            android:title="@string/activity_prefs_goal_active_calories_burnt"
+            app:useSimpleSummaryProvider="true" />
+
+        <EditTextPreference
+            app:iconSpaceReserved="false"
+            android:defaultValue="5000"
+            android:inputType="number"
+            android:key="activity_user_distance_meters"
+            android:maxLength="5"
+            android:title="@string/activity_prefs_distance_meters"
+            app:useSimpleSummaryProvider="true" />
+
+        <EditTextPreference
+            app:iconSpaceReserved="false"
+            android:defaultValue="60"
+            android:inputType="number"
+            android:key="activity_user_activetime_minutes"
+            android:maxLength="3"
+            android:title="@string/activity_prefs_activetime_minutes"
+            app:useSimpleSummaryProvider="true" />
+
+        <EditTextPreference
+            app:iconSpaceReserved="false"
+            android:defaultValue="12"
+            android:inputType="number"
+            android:key="activity_user_goal_standing_time_minutes"
+            android:maxLength="2"
+            android:title="@string/activity_prefs_goal_standing_time_minutes"
+            app:useSimpleSummaryProvider="true" />
+
+        <EditTextPreference
+            app:iconSpaceReserved="false"
+            android:defaultValue="30"
+            android:inputType="number"
+            android:key="activity_user_goal_fat_burn_time_minutes"
+            android:maxLength="3"
+            android:title="@string/activity_prefs_goal_fat_burn_time_minutes"
+            app:useSimpleSummaryProvider="true" />
+    </PreferenceCategory>
+</PreferenceScreen>
diff --git a/app/src/main/res/xml/changelog_master.xml b/app/src/main/res/xml/changelog_master.xml
new file mode 100644
index 0000000..a6a70b4
--- /dev/null
+++ b/app/src/main/res/xml/changelog_master.xml
@@ -0,0 +1,738 @@
+<?xml version="1.0" encoding="utf-8"?>
+<changelog>
+    <release version="0.83.0" versioncode="235">
+        <change>Add first start onboarding screen</change>
+        <change>Initial support for Bowers and Wilkins P Series</change>
+        <change>Initial support for Casio ECB-S100</change>
+        <change>Initial support for Colmi R09</change>
+        <change>Initial support for Freebuds 5i</change>
+        <change>Initial support for Garmin Fenix 6S Pro / 7X, Forerunner 55 / 235 / 620, Instinct 2</change>
+        <change>Initial support for Huawei Band 2 / 2 Pro / 3 Pro</change>
+        <change>Initial support for Oppo Enco Air / Air2</change>
+        <change>Initial support for Realme Buds T110</change>
+        <change>Initial support for Redmi Buds 5 Pro</change>
+        <change>Initial support for Xiaomi Smart Band 9 Pro</change>
+        <change>Initial support for Marstek B2500</change>
+        <change>Add calories charts and widgets</change>
+        <change>Add more workout icons</change>
+        <change>About screen: Copy build details on tap</change>
+        <change>Amazfit Bip 3 Pro: Fix title and sender on some notifications</change>
+        <change>Bangle.js: Fix calendar sync</change>
+        <change>Bangle.js: Fix call notification in Turkish locale</change>
+        <change>Bangle.js: Support sending activity type from Bangle</change>
+        <change>Casio GBX-100: Fix notification title</change>
+        <change>Charts: Display HR measurement gaps</change>
+        <change>Colmi R0x: Fix occasional crash on disconnection</change>
+        <change>Fix crash in some chart pages</change>
+        <change>Fix heart rate charts average and maximum value</change>
+        <change>Fix imperial unit on steps charts</change>
+        <change>Fix notifications after a notification is received with a time in the future</change>
+        <change>Fossil/Skagen Hybrids: Add SpO2 support</change>
+        <change>Fossil/Skagen Hybrids: Fix erroneous watchface downgrade</change>
+        <change>Garmin: Add intensity minutes, respiratory rate, sleep score</change>
+        <change>Garmin: Display AGPS age</change>
+        <change>Garmin: Fix weather temperature conversion to celsius</change>
+        <change>Garmin: Persist sleep score and metabolic rate</change>
+        <change>Garmin: Send notification pictures</change>
+        <change>Huawei Band 3 pro: Fix notifications</change>
+        <change>Huawei: Add HR zones configuration for non-P2P devices</change>
+        <change>Huawei: Add temperature chart</change>
+        <change>Huawei: Allow more languages to be set on the watch</change>
+        <change>Huawei: Display active calories</change>
+        <change>Huawei: Display high-resolution heart rate</change>
+        <change>Huawei: Fix watchface upload for some watches</change>
+        <change>Huawei: Improve activity parsing</change>
+        <change>Huawei: Music management</change>
+        <change>Huawei: Send weather error if there is no data</change>
+        <change>Huawei: Sync blood pressure if supported</change>
+        <change>Huawei: Sync skin temperature</change>
+        <change>Restore sleep balance on weekly and monthly charts</change>
+        <change>Test device: Add dummy activities</change>
+        <change>Workout page: Add colors to HR zones</change>
+        <change>Xiaomi Smart Band 8 Active: Fix freestyle and walking workout parsing </change>
+        <change>Xiaomi Smart Band 9: Fix outdoor cycling parsing</change>
+        <change>Xiaomi SPPv2: Fix message processing getting stuck after exception</change>
+        <change>Xiaomi-protobuf: Add resting heart rate</change>
+        <change>Xiaomi-protobuf: Fix activity sync stuck on duplicated or invalid files</change>
+        <change>Xiaomi-protobuf: Improve workout parsing</change>
+        <change>Xiaomi-protobuf: Persist RR intervals during sleep</change>
+        <change>Zepp OS: Add sleep respiratory rate chart</change>
+        <change>Zepp OS: Send notification pictures</change>
+    </release>
+    <release version="0.82.1" versioncode="234">
+        <change>Huawei: Improve activity parsing</change>
+        <change>Huawei Watch GT: Fix connection failure</change>
+        <change>Withings: Fix crash on connection</change>
+        <change>Improve Armenian transliterator for mixed-case words</change>
+    </release>
+    <release version="0.82.0" versioncode="233">
+        <change>Initial support for Anker Soundcore Liberty 4 NC</change>
+        <change>Initial support for CMF Buds Pro 2 / Watch Pro 2</change>
+        <change>Initial support for Colmi R02/R03/R06/R10 smart rings</change>
+        <change>Initial support for Garmin Enduro 3, Fenix 5/5 Plus/5X Plus/6/6S Sapphire/7/8, Forerunner 165/255/255S Music/245 Music/265S/955/965, Venu/Venu Sq/Venu Sq 2/Venu 2S, Vivoactive 3, Vivomove Trend, Vivosport</change>
+        <change>Initial support for Huawei Watch 3 / 3 Pro / 4 Pro / D2 / GT 3 SE / GT 5 / GT 5 Pro / GT Cyber / GT Runner</change>
+        <change>Initial support for Honor Watch GS 3 / Watch GS Pro</change>
+        <change>Initial support for IKEA desk controller</change>
+        <change>Initial support for Moondrop Space Travel</change>
+        <change>Initial support for Mijia XMWSDJ04MMC</change>
+        <change>Initial support for Mi Smart Scale 2</change>
+        <change>Initial support for Sony WF-C500 / WF-C700N / WI-C100</change>
+        <change>Initial support for Soundcore Motion 300</change>
+        <change>Initial support for Vivitar HR &amp; BP Monitor Activity Tracker</change>
+        <change>Experimental support for Amazfit T-Rex 3</change>
+        <change>Experimental support for Redmi Watch 5 Active</change>
+        <change>Experimental support for Xiaomi Smart Band 9</change>
+        <change>Experimental support for Xiaomi Watch S3</change>
+        <change>Add all languages supported in weblate</change>
+        <change>Add BLE intent API</change>
+        <change>Add configuration for calendar lookahead</change>
+        <change>Add month and day to date of birth</change>
+        <change>Add more activity types (CMF, Garmin, Huawei, Zepp OS)</change>
+        <change>Allow configuration of notification times</change>
+        <change>Allow syncing birthdays with calendar events</change>
+        <change>Amazfit GTR 2: Enable PAI support</change>
+        <change>AsteroidOS: Fix missing weather day and set-time on connection</change>
+        <change>Bangle.js: Add canned responses</change>
+        <change>Bangle.js: Fix calendar sync setting</change>
+        <change>Bangle.js: Fix distance in activity details</change>
+        <change>Bluetooth Intent API: Add disconnect action</change>
+        <change>Casio GW-B5600: Alarms, find phone, reminders, watch settings</change>
+        <change>Casio: Fix notifications on long messages</change>
+        <change>Change device icons to use theme colors</change>
+        <change>Charts: Add button to pick date</change>
+        <change>Charts: Add charts for HRV, body energy, heart rate, steps, VO2 max, weight</change>
+        <change>Charts: Fix heart rate charts when min is set to 0</change>
+        <change>Charts: Re-design sleep, stress, PAI, workout details</change>
+        <change>Charts: Use HR from workout track file if available</change>
+        <change>CMF Watch Pro 2: Negotiate authentication key</change>
+        <change>CMF Watch Pro: Fix activity transfer</change>
+        <change>Cycling sensor: added live data view</change>
+        <change>Cycling sensor: Improve cycling data display</change>
+        <change>Dashboard: Add new widgets for stress, HRV, body energy</change>
+        <change>Dashboard: Add option to show yesterday's data in Today widget</change>
+        <change>Dashboard: Improve widget gauge resolution</change>
+        <change>Data Management: Add file manager</change>
+        <change>Data Management: Allow browse folders, open and share files</change>
+        <change>Data Management: Allow full backup/restore from a zip file</change>
+        <change>Data Management: Fix import of some preference from a backup</change>
+        <change>Fix activity charts generation for devices that do not report intensity</change>
+        <change>Fix crash when companion pairing</change>
+        <change>Fix discovery of connected devices</change>
+        <change>Fix emoji when some connected device does not support them</change>
+        <change>Fix language not being respected in some situations</change>
+        <change>Fix media controls not working for some apps</change>
+        <change>Fix notification text not being sent for some apps</change>
+        <change>Fix reconnection when device connects back during BLE scan</change>
+        <change>Fossil HR: Fix crash on disconnection</change>
+        <change>Fossil HR: Minor watchface fixes</change>
+        <change>Garmin: Display awake time during sleep</change>
+        <change>Garmin: Display HRV and body energy</change>
+        <change>Garmin: Display resting heart rate</change>
+        <change>Garmin: Fetch SKIP_TEMP files</change>
+        <change>Garmin: Fix agps upload for some URLs</change>
+        <change>Garmin: Fix all-day events</change>
+        <change>Garmin: Fix auto-activity fetch on some devices</change>
+        <change>Garmin: Fix canned replies reset to defaults</change>
+        <change>Garmin: Fix crash on call with privacy mode on</change>
+        <change>Garmin: Fix crash on timezones without DST</change>
+        <change>Garmin: Fix daily weather missing current day</change>
+        <change>Garmin: Fix weather temperature and speed units</change>
+        <change>Garmin: Improve activity, sleep and workout parsing</change>
+        <change>Garmin: Infer sleep time for devices that do not send sleep stages</change>
+        <change>Garmin: Manual HR measurements and live activity</change>
+        <change>Garmin: Map some unknown realtime settings</change>
+        <change>Garmin: Parse workout physiological metrics, strength training workout sets</change>
+        <change>Garmin: Re-parse workout summary when opening details page</change>
+        <change>Garmin: Upload gpx and workout fit files to watch</change>
+        <change>Garmin: Use distance and calories provided by the watch</change>
+        <change>Garmin: View and share gpx files</change>
+        <change>Huami: Fetch workouts during normal sync</change>
+        <change>Huami: Migrate all device settings to sub-screens</change>
+        <change>Huawei Band 9: Improved support</change>
+        <change>Huawei: Add battery polling</change>
+        <change>Huawei: Basic support for the installation of the applications</change>
+        <change>Huawei: Calendar sync support</change>
+        <change>Huawei: Contacts uploading support</change>
+        <change>Huawei: Continuous skin temperature measurement switch</change>
+        <change>Huawei: Enable emoji for HarmonyOS watches</change>
+        <change>Huawei: Fix crash when notification has no text</change>
+        <change>Huawei: Fix initialization issues on some watches</change>
+        <change>Huawei: Fix notifications for Huawei Band 4e</change>
+        <change>Huawei: Fix some reconnection issues</change>
+        <change>Huawei: Fix watchface upload, activity sync, event alarms, weather for some devices</change>
+        <change>Huawei: Fix workout altitude, pace, workout re-parsing</change>
+        <change>Huawei: Improve device initialization</change>
+        <change>Huawei: Improve watchface install support</change>
+        <change>Huawei: Improve weather support</change>
+        <change>Huawei: Initial ephemeris update support</change>
+        <change>Huawei: Map more workout types</change>
+        <change>Huawei: Music upload support</change>
+        <change>Huawei: Provide an activity sample every minute</change>
+        <change>Huawei: Re-parse workout details when opening details</change>
+        <change>Huawei: Send default HR zones</change>
+        <change>Huawei: Workout GPS synchronization</change>
+        <change>Huawei: Simple TruSleep support</change>
+        <change>Huawei: Use distance and calories provided by the watch</change>
+        <change>Improve calendar change detection</change>
+        <change>Mi Band: Migrate global preferences to device-specific</change>
+        <change>Mi Composition Scale: Add alternative bluetooth name</change>
+        <change>Mi Composition Scale: Persist and display weight samples</change>
+        <change>Mijia LYWSD/XMWSDJ: Add comfort level preference</change>
+        <change>Pebble: Migrate global preferences to device-specific</change>
+        <change>Redmi Smart Band Pro: Fix crash on connection and activity sync issues</change>
+        <change>Sony Headphones: Enable read aloud incoming notifications and auto call pickup</change>
+        <change>UI: Add new activity icons</change>
+        <change>UI: Fix changelog on device rotation</change>
+        <change>UI: Fix HR samples displayed on wrong device</change>
+        <change>UI: Fix light navbar buttons on light themes for Android 8+</change>
+        <change>UI: Fix pull-down to refresh for some devices</change>
+        <change>UI: Improvements for large screen resolutions, font sizes, landscape</change>
+        <change>UI: Reduce stutters on device changes / data fetch / scrolling</change>
+        <change>UI: Refactor preferences screen</change>
+        <change>UM25C: Fix some disconnection issues</change>
+        <change>Use default system TTS language</change>
+        <change>Xiaomi Protobuf: Allow re-parse activity from storage</change>
+        <change>Xiaomi Protobuf: Enable watchface upload for all devices</change>
+        <change>Xiaomi Protobuf: Show watchface preview</change>
+        <change>Xiaomi Protobuf: Fix watchface install on some watches</change>
+        <change>Xiaomi Protobuf: Fix deleting first widget screen</change>
+        <change>Xiaomi Protobuf: Fix naps</change>
+        <change>Xiaomi Protobuf: Improve workout parsing for some devices</change>
+        <change>Zepp OS 3: Fix file transfer (notification icons, gpx upload, agps updates)</change>
+        <change>Zepp OS 3.5 / 4: Fix shortcuts, shortcut cards, menu items</change>
+        <change>Zepp OS: Add VO2 Max support</change>
+        <change>Zepp OS: Display resting heart rate</change>
+        <change>Zepp OS: Fix reminder creation in some cases</change>
+        <change>ZeTime: Migrate global preferences to device-specific</change>
+    </release>
+    <release version="0.81.0" versioncode="232">
+        <change>Experimental support for Amazfit Bip 5 Unity</change>
+        <change>Experimental support for Redmi Watch 4</change>
+        <change>Initial support for cycling sensor</change>
+        <change>Initial support for more Garmin watches</change>
+        <change>Initial support for Hama Fit6900</change>
+        <change>Initial support for Huawei Watch Fit 2, Watch Fit 3, Watch 4 Pro</change>
+        <change>Initial support for Soundcore Liberty 3 Pro</change>
+        <change>Introduce new Dashboard view</change>
+        <change>AsteroidOS: Added icons to the notifications</change>
+        <change>Bangle.js: Add Sleep as Android support</change>
+        <change>Bangle.js: Add screenshot support</change>
+        <change>Bangle.js: Add setting to disable notifications</change>
+        <change>Bangle.js: Allow wake phone when opening notification response from watch</change>
+        <change>Bangle.js: Fix activity intensity normalization</change>
+        <change>Bangle.js: Fix message reply</change>
+        <change>Bangle.js: Improve text rendering</change>
+        <change>Fossil/Skagen Hybrids: Update device settings to new structure</change>
+        <change>Galaxy Buds Live: Update device settings to new structure</change>
+        <change>Galaxy Buds 2: Fix recognition of some versions</change>
+        <change>HPlus: Migrate global preferences to device-specific</change>
+        <change>Huami: Fix reminder message encoding</change>
+        <change>Huawei: Add cycling workout type</change>
+        <change>Huawei: Add enable HeartRate and SpO2 force option</change>
+        <change>Huawei: Add huawei account support (pair without resetting watch)</change>
+        <change>Huawei: Add support for workout calories and cycling power</change>
+        <change>Huawei: Add remote camera shutter</change>
+        <change>Huawei: Ask pincode only on first connection</change>
+        <change>Huawei: Enable sleep detection</change>
+        <change>Huawei: File upload and watchface management</change>
+        <change>Huawei: Fix force DND support</change>
+        <change>Huawei: Fix long notification</change>
+        <change>Huawei: Fix TimeZone offset calculation</change>
+        <change>Huawei: Improve connection and reconnection</change>
+        <change>Huawei: Improve music controls</change>
+        <change>Huawei: Improve notification icons</change>
+        <change>Huawei: Improve weather, HR and SpO2 support</change>
+        <change>Huawei: Improve workout parsing</change>
+        <change>Huawei: Rework settings menu with sub-screens</change>
+        <change>Huawei: Send user info to device</change>
+        <change>Huawei: Support sending GPS to band</change>
+        <change>Huawei Watch GT4: Add HR and SpO support</change>
+        <change>Huawei Watch Ultimate: Add HR and SpO support</change>
+        <change>Intent API: Add broadcast on activity sync finish</change>
+        <change>Intent API: Added debug end call</change>
+        <change>Mi Band 6: Add menu items for NFC shortcuts</change>
+        <change>Nothing CMF Watch Pro: Add weather support</change>
+        <change>Nothing Earbuds: Add adjustable delay for auto-pick-up of calls</change>
+        <change>Nothing Earbuds: Add option to auto-reply to incoming phone calls</change>
+        <change>Nothing Earbuds: Add option to read aloud incoming notifications</change>
+        <change>Sony LinkBuds S: Enable some missing features</change>
+        <change>Xiaomi Smart Band 8 Active: Fix discovery</change>
+        <change>Xiaomi: Add swimming workout type</change>
+        <change>Xiaomi: Allow transliteration</change>
+        <change>Xiaomi: Fix barometer</change>
+        <change>Xiaomi: Fix notification for apps in work profile</change>
+        <change>Xiaomi: Fix some crashes</change>
+        <change>Xiaomi: Improve reconnection</change>
+        <change>Xiaomi: Improve sleep and activity parsing</change>
+        <change>Xiaomi: Improve weather support, add multiple locations</change>
+        <change>Xiaomi: Sync calendar event reminders</change>
+        <change>Zepp OS: Add support for Sleep as Android</change>
+        <change>Zepp OS: Sync calendar event reminders</change>
+        <change>Add Armenian and Serbian transliterators</change>
+        <change>Add GENERIC_PHONE and GENERIC_CALENDAR NotificationType handling</change>
+        <change>Add support for scannable-only devices</change>
+        <change>Fix crash when connecting on some phones</change>
+        <change>Fix crash when enabling bluetooth</change>
+        <change>Fix receiving shared gpx files</change>
+        <change>Fix text cutoff on all checkbox preferences</change>
+        <change>Format pace as mm:ss</change>
+        <change>Make battery threshold notifications configurable</change>
+        <change>Prevent some bluetooth events from starting Gadgetbridge</change>
+        <change>Recognize "Delta Chat" as generic chat</change>
+        <change>Remove deprecated general auto-reconnect preference</change>
+        <change>Refactor location service</change>
+        <change>Set navbar color to match theme</change>
+        <change>Simplify pairing of bonded and companion devices</change>
+    </release>
+    <release version="0.80.0" versioncode="231">
+        <change>Initial support for Amazfit Bip 3</change>
+        <change>Initial support for Huawei Band 8</change>
+        <change>Initial support for Huawei Watch GT 4</change>
+        <change>Initial support for Huawei Watch Ultimate</change>
+        <change>Initial support for Sony LinkBuds</change>
+        <change>Initial support for Xiaomi Smart Band 8 Active</change>
+        <change>Bangle.js: Allow saving files on phone from watch</change>
+        <change>Bangle.js: Fix crash when file save is cancelled</change>
+        <change>Bangle.js: Set filename on save file dialogs</change>
+        <change>Bangle.js: Improve communication stability</change>
+        <change>Bangle.js: Sync activity tracks</change>
+        <change>Bangle.js: remove unwanted charaters from calendar events</change>
+        <change>Femometer Vinca II: Add temperature charts</change>
+        <change>Fossil/Skagen Hybrids: Remove activity fetching toasts and add finished signal</change>
+        <change>Fossil/Skagen Hybrids: Use steps instead of calories for activity intensity</change>
+        <change>Fossil/Skagen Hybrids: Mark device busy and show transfer notification while syncing</change>
+        <change>Huami/Zepp OS: Fix activity sync getting stuck sometimes</change>
+        <change>Mi Band 1/2: Fix vibration settings preference screens</change>
+        <change>Huawei: Add cycling workout type </change>
+        <change>Huawei: Add smart wakeup interval</change>
+        <change>Pebble: Fix pairing with LE counterpart</change>
+        <change>Xiaomi Watch S1 Pro: Add temperature charts</change>
+        <change>Xiaomi: Fix sleep sync failing when sleep stages are not found</change>
+        <change>Xiaomi: Improve activity sync</change>
+        <change>Nothing CMF Watch Pro: Fix music playback status</change>
+        <change>Allow for device settings sub-screens</change>
+        <change>Device connection: Add support for scan before BLE connection</change>
+        <change>Misc UI improvements (alarms, chart settings)</change>
+    </release>
+    <release version="0.79.1" versioncode="230">
+        <change>Initial support for Huawei Watch Fit</change>
+        <change>Initial support for Xiaomi Redmi Watch 3</change>
+        <change>Fossil/Skagen Hybrids: Fix crash on multi-byte unicode characters in menu</change>
+        <change>Huawei: Add weather support</change>
+	<change>Bangle.js: Support higher MTU</change>
+        <change>Test Device: Add fake features and data</change>
+        <change>Periodically (around every 2 days) synchronize time on connected devices</change>
+        <change>Set alarm as used and enabled if time has changed</change>
+    </release>
+    <release version="0.79.0" versioncode="229">
+        <change>Initial support for Honor Magic Watch 2</change>
+        <change>Initial support for Mijia MHO-C303</change>
+        <change>Initial support for Nothing CMF Watch Pro</change>
+        <change>Initial support for Sony WI-SP600N</change>
+        <change>Experimental support for Redmi Watch 2</change>
+        <change>Experimental support for Xiaomi Smart Band 8 Pro</change>
+        <change>Experimental support for Xiaomi Watch S1 Pro</change>
+        <change>Experimental support for Xiaomi Watch S1</change>
+        <change>Experimental support for Xiaomi Watch S3</change>
+        <change>Galaxy Buds2 Pro: Fix recognition of some versions</change>
+        <change>Huawei Watch GT 2: Fix pairing</change>
+        <change>Redmi Smart Band Pro: Fix password digits</change>
+        <change>Pebble: Fix app configuration page</change>
+        <change>Pebble 2: Fix pairing issue</change>
+        <change>PineTime: Fix weather forecast on InfiniTime's new simple weather</change>
+        <change>Xiaomi: Fix sleep sometimes extending past the wakeup time</change>
+        <change>Xiaomi: Request battery level and charging state periodically</change>
+        <change>Xiaomi: Fix sleep stage parsing for some devices</change>
+        <change>Zepp OS: Improve device discovery</change>
+        <change>Zepp OS: Fix weather not working on some devices</change>
+        <change>Zepp OS: Prevent crash when installing large firmware updates</change>
+        <change>Fix sport activity summary group order</change>
+        <change>Fix reconnection to devices failing occasionally</change>
+    </release>
+    <release version="0.78.0" versioncode="228">
+        <change>Initial support for Honor Band 3,4,5,6</change>
+        <change>Initial support for Huawei Band 4, 4 Pro, 6, 7, 3e, 4e</change>
+        <change>Initial support for Huawei Talk Band B6</change>
+        <change>Initial support for Huawei Watch GT, GT 2</change>
+        <change>Initial support for Mijia LYWSD03MMC</change>
+        <change>Initial support for Nothing Ear (2)</change>
+        <change>Initial support for Nothing Ear (Stick)</change>
+        <change>Experimental support for Honor Band 7</change>
+        <change>Experimental support for Redmi Watch 2 Lite</change>
+        <change>Experimental support for Redmi Smart Band Pro</change>
+        <change>Casio GBX100: Add support for snooze alarm</change>
+        <change>Fossil/Skagen Hybrids: Update navigationApp to 1.1</change>
+        <change>Huami: Fetch SpO2 on devices that support it</change>
+        <change>Pebble: Attempt to fix app configuration webview</change>
+        <change>PineTime: Add support for InfiniTime's new simple weather</change>
+        <change>PineTime: Fix freeze and reboot when upgrading firmware</change>
+        <change>Pixoo: Enable sending images (non-persistent)</change>
+        <change>Pixoo: Get and send alarms</change>
+        <change>Pixoo: Set custom device name</change>
+        <change>Pixoo: support "clap hands to turn off screen" and "sleep after silence" settings</change>
+        <change>Xiaomi: Improve activity and workout parsing</change>
+        <change>Xiaomi: Improve stability and fix some crashes</change>
+        <change>Xiaomi: Improve weather</change>
+        <change>Xiaomi: Parse sleep stages</change>
+        <change>Add a notifications channel for connection status notifications</change>
+        <change>Improve automatic connection to all or previous devices</change>
+        <change>Fix devices sometimes staying stuck in a "Connecting" state</change>
+        <change>Map some missing Google Maps navigation actions</change>
+    </release>
+    <release version="0.77.0" versioncode="227">
+        <change>Initial support for Amazfit Balance</change>
+        <change>Initial support for Amazfit Active</change>
+        <change>Initial support for ColaCao 2021</change>
+        <change>Initial support for ColaCao 2023</change>
+        <change>Initial support for Femometer Vinca II</change>
+        <change>Initial support for Mijia LYWSD02MMC variant</change>
+        <change>Initial support for Sony Wena 3</change>
+        <change>Experimental support for Divoom Pixoo</change>
+        <change>Experimental support for Sony WF-1000XM5</change>
+        <change>Experimental support for Amazfit Active Edge</change>
+        <change>Experimental support for Mi Band 7 Pro (Xiaomi Smart Band 7 Pro)</change>
+        <change>Experimental support for Mi Band 8 (Xiaomi Smart Band 8)</change>
+        <change>Experimental support for Mi Watch Lite</change>
+        <change>Experimental support for Mi Watch Color Sport</change>
+        <change>Experimental support for Redmi Smart Band 2</change>
+        <change>Experimental support for Redmi Watch 3 Active</change>
+        <change>Experimental support for Xiaomi Watch S1 Active</change>
+        <change>Amazfit Band 7: Add alexa menu entries</change>
+        <change>Amazfit GTR 3 Pro: Fix firmware and watchface upload</change>
+        <change>Amazfit T-Rex: Fix activity summary parsing</change>
+        <change>Amazfit T-Rex Pro: Add activate display on lift sensitivity</change>
+        <change>AsteroidOS: Add more supported watch models</change>
+        <change>AsteroidOS: Fix media info</change>
+        <change>AsteroidOS: Fix notification dismissal</change>
+        <change>Bangle.js: Add loyalty cards integration with Catima</change>
+        <change>Bangle.js: Ensure SMS messages have src field set to "SMS Message"</change>
+        <change>Bangle.js: Fix GPS speed</change>
+        <change>Bangle.js: Improve handling of chinese characters</change>
+        <change>Bangle.js: Lower threshold for low battery warning</change>
+        <change>Bangle.js: Recover from device initialization failure</change>
+        <change>Casio GBX100/GBD-200: Fix first connect</change>
+        <change>Casio GB5600/6900/STB-1000: Fix pairing</change>
+        <change>Casio GDB-200: Fix notification timestamp</change>
+        <change>Casio GDB-200: Fixed notification categories and default category</change>
+        <change>Casio GDB-200: Allow preview of notification message alongside title</change>
+        <change>Casio GDB-200: Fixed find my phone feature</change>
+        <change>Intent API: Add debug action for test new function</change>
+        <change>Fossil/Skagen Hybrids: Add new navigation app</change>
+        <change>Fossil/Skagen Hybrids: Allow configuring call rejection method</change>
+        <change>Fossil/Skagen Hybrids: Fix some preference crashes on the nightly</change>
+        <change>Fossil/Skagen Hybrids: Reduce toasts on release builds</change>
+        <change>Fossil/Skagen Hybrids: Show device specific settings in more logical order</change>
+        <change>Huami: Toggle phone silent mode from band</change>
+        <change>Message privacy: Add mode Hide only body</change>
+        <change>Mijia LYWSD02: Add battery</change>
+        <change>Mijia LYWSD02: Add low battery notification</change>
+        <change>Mijia LYWSD02: Set temperature unit</change>
+        <change>Mijia LYWSD02: Fix battery drain while connected</change>
+        <change>PineTime: Display app name for VoIP app calls</change>
+        <change>PineTime: Honor Sync time setting on connect</change>
+        <change>PineTime: Improve notification handling</change>
+        <change>PineTime: Reduce weather memory usage</change>
+        <change>Withings Steel HR: Fix crash when calibrating hands on the nightly</change>
+        <change>Zepp OS: Add blood oxygen graph</change>
+        <change>Zepp OS: Add workout codes for hiking and outdoor swimming</change>
+        <change>Zepp OS: Allow disabling app notifications per device</change>
+        <change>Zepp OS: Attempt to fix activity fetch operation getting stuck</change>
+        <change>Zepp OS: Display swimming activity data</change>
+        <change>Zepp OS: Fix health settings on older Zepp OS versions</change>
+        <change>Zepp OS: Fix setting of unknown button press apps</change>
+        <change>Zepp OS: Fix sunrise and moon dates being off by local time + UTC offset</change>
+        <change>Zepp OS: Map hiking, outdoor swimming, climbing and table tennis activity types</change>
+        <change>Zepp OS: Toggle phone silent mode from band</change>
+        <change>Add transliteration for Latvian, Hungarian, Common Symbols</change>
+        <change>Allow multiple device actions to be triggered for the same event</change>
+        <change>Allow toggling DND through device actions</change>
+        <change>Autodetect OsmAnd package name and make it configurable</change>
+        <change>Improve ASCII transliterator</change>
+        <change>Make GMaps navigation handler follow the "navigation forwarding" setting</change>
+        <change>Support selecting enabled navigation apps</change>
+        <change>Allow ignore notifications from work profile apps</change>
+        <change>Display alias in low battery notification</change>
+        <change>Fix crash when pairing current device as companion</change>
+        <change>Fix emoji when a transliterator is enabled</change>
+        <change>Fix UV Index and rain probability for some weather apps</change>
+        <change>Improve device discovery stability and fix freezes</change>
+        <change>Improve Telegram and COL Reminder notifications</change>
+        <change>Replace old-style preference switch with Material 3 switch</change>
+    </release>
+    <release version="0.76.1" versioncode="226">
+        <change>Amazfit GTR Mini: Mark as not experimental</change>
+        <change>Bangle.js: Improve file downloads</change>
+        <change>Bangle.js: Fix app interfaces</change>
+        <change>Allow text to be shared to devices</change>
+        <change>Fix connection to some Amazfit devices </change>
+    </release>
+    <release version="0.76.0" versioncode="225">
+        <change>Upgrade UI to Material 3 and add dynamic colors theme</change>
+        <change>Initial support for Amazfit Bip 3 Pro</change>
+        <change>Initial support for Amazfit Cheetah Pro</change>
+        <change>Initial support for Bohemic Smart Bracelet</change>
+        <change>Initial support for Casio GW-B5600</change>
+        <change>Initial support for Garmin Vivomove HR</change>
+        <change>Initial support for Withings Steel HR</change>
+        <change>Experimental support for Amazfit Bip 5</change>
+        <change>Experimental support for Amazfit Falcon</change>
+        <change>Experimental support for Amazfit GTR Mini</change>
+        <change>Experimental support for Amazfit Cheetah (Round/Square)</change>
+        <change>Experimental support for Amazfit T-Rex Ultra</change>
+        <change>Amazfit GTS 2e: Add activate display on lift sensitivity</change>
+        <change>Amazfit GTR 3 / GTS 3: Enable AGPS Updates</change>
+        <change>Amazfit Neo: Enable PAI support</change>
+        <change>Bangle.js: Allow enable/disable of alarms</change>
+        <change>Bangle.js: Fetch activity data</change>
+        <change>Bangle.js: Fix GB integration when watch is not programmable</change>
+        <change>Bangle.js: Put JSON keys in quotes</change>
+        <change>Bangle.js: Reorganize device settings</change>
+        <change>Fossil/Skagen Hybrids: Embed custom menu in watchface, fixes lost menu on reset</change>
+        <change>Fossil/Skagen Hybrids: Fix unused alarms being pushed to the device</change>
+        <change>Fossil/Skagen Hybrids: Strip unicode characters that the watch can't display</change>
+        <change>Fossil/Skagen Hybrids: Sunrise/sunset follows weather location</change>
+        <change>Huami/Zepp OS: Add PAI charts</change>
+        <change>Huami/Zepp OS: Improve music info stability</change>
+        <change>Huami/Zepp OS: Improve reconnection and device initialization</change>
+        <change>Huami: Persist workout raw details even if gpx has no points</change>
+        <change>InfiniTime: Add heart rate measurement support</change>
+        <change>Mi Band 5: Fix activity fetch error toast when stress monitoring is enabled</change>
+        <change>Mi Band 6: Enable PAI and stress support</change>
+        <change>LeFun: Fix heart rate popup when measurement is triggered from phone</change>
+        <change>Sony WH-1000XM3/WF-SP800N: Add volume setting</change>
+        <change>Sony WH-1000XM5: Fix speak-to-chat enable/disable</change>
+        <change>Zepp OS: Add loyalty cards integration with Catima</change>
+        <change>Zepp OS: Enable AGPS updates for all devices</change>
+        <change>Zepp OS: Fix calendar sync on Zepp OS 2, send event location</change>
+        <change>Zepp OS: Fix reminder creation</change>
+        <change>Zepp OS: Fix shortcut cards setting on Zepp OS 2.1</change>
+        <change>Zepp OS: Fix weather, add hourly information</change>
+        <change>Zepp OS: Map barcode types for ITF, PDF_417 and DATA_MATRIX</change>
+        <change>Add preference to display changelog on startup</change>
+        <change>Add Termux RUN_COMMAND permission</change>
+        <change>Allow filtering notifications from work profile apps</change>
+        <change>Fix daylight saving time not being transmitted to the watch</change>
+        <change>Fix media button control for some applications</change>
+        <change>Fix notification filters by title if notification does not contain a body</change>
+        <change>Fix opening screenshots from notification on external apps</change>
+        <change>Fix reconnect delay reset after all devices are initialized</change>
+        <change>Fix some security error crashes when permissions are not granted</change>
+        <change>Fix transliteration of emoji</change>
+        <change>Fix transliteration of non-ASCII accented characters</change>
+        <change>Force gps speed calculation on some phones that do not report it correctly</change>
+        <change>Make application list sorting case-insensitive</change>
+        <change>Introduce native app shortcuts for android > 30</change>
+        <change>Update device settings action bar title depending on current screen</change>
+    </release>
+    <release version="0.75.1" versioncode="224">
+        <change>Fix Weather Notification integration</change>
+    </release>
+    <release version="0.75.0" versioncode="223">
+        <change>Bangle.js: Add message size limitation to Calendar and Messages</change>
+        <change>Bangle.js: Add switch to control if the GPS chip should be used to locate the location</change>
+        <change>Bangle.js: Send more weather data to watch</change>
+        <change>Bangle.js: Allow an activity sample to have a timestamp</change>
+        <change>Bangle.js: Send last received activity timestamp on connect (to allow sync of activity samples)</change>
+        <change>Bangle.js: Allow connecting HW keyboard without closing app loader</change>
+        <change>Bangle.js: Bump flavor target SDK version to 31</change>
+        <change>Bangle.js: Fix convertion of emoji/unicode to bitmap without width/height</change>
+        <change>Bangle.js: Fix location listener not being cleaned up when waiting for reconnect</change>
+        <change>Bangle.js: Fix memory leak from HTTP requests</change>
+        <change>Bangle.js: Fix orientation changes closing app loader</change>
+        <change>Bangle.js: Fix return to applications management activity after having opened another window</change>
+        <change>Bangle.js: Set default value for GPS event interval to 1 second</change>
+        <change>Bangle.js: Support navigation instructions</change>
+        <change>Bangle.js: Escape characters that fall in the Unicode codepoint area (for Espruino ~2v18.20 and later)</change>
+        <change>Bangle.js: HTTP request XPath can now return Arrays</change>
+        <change>Fossil/Skagen Hybrids: Add support for ultraviolet index and rain probability</change>
+        <change>Fossil/Skagen Hybrids: Add UV index and chance of rain widgets</change>
+        <change>Fossil/Skagen Hybrids: Allow launching the calibration activity on any Gadgetbridge variant</change>
+        <change>Fossil/Skagen Hybrids: Increase accuracy of workout distance calculation</change>
+        <change>Fossil/Skagen Hybrids: Fix weather icons day/night status</change>
+        <change>InfiniTime: Fix weather expiry time</change>
+        <change>InfiniTime: Support navigation instructions</change>
+        <change>Mi Band 6: Allow making device discoverable via Bluetooth when connected</change>
+        <change>Mi Band 7: Add preference to display call contact information</change>
+        <change>Zepp OS: Add gpx route file upload</change>
+        <change>Zepp OS: Add screenshot support</change>
+        <change>Zepp OS: Add stress charts</change>
+        <change>Zepp OS: Add watch app logs developer option</change>
+        <change>Zepp OS: Display watchface and app preview on install</change>
+        <change>Zepp OS: Fix update operations on Zepp OS 2.1+</change>
+        <change>Zepp OS: Manage contacts on watch</change>
+        <change>Zepp OS: Start new GPX segments on pause/resume</change>
+        <change>Zepp OS: Support flashing zab files</change>
+        <change>App Manager: Fix cached apps sorting</change>
+        <change>App Manager: Hide drag handle if app reorder is not supported</change>
+        <change>App Manager: Add confirmation before deleting app</change>
+        <change>Add menus to share GPX, raw summary, raw details</change>
+        <change>Debug Activity: Allow pairing current device as companion</change>
+        <change>Fix some null pointer exception crashes</change>
+        <change>Intent API: Add command to set device mac address</change>
+        <change>Intent API: Add dataTypes parameter for activity sync</change>
+        <change>Intent API: Add debug actions for notifications and incoming calls</change>
+        <change>OsmAnd: Add support for navigation instructions</change>
+        <change>Scrape navigation instructions from Google Maps notifications</change>
+        <change>Fix lag when a folder has a lot of devices</change>
+        <change>Fix transliteration returning non-ASCII characters</change>
+        <change>Enable "allow high MTU" setting by default</change>
+        <change>Make some hardcoded english strings translatable</change>
+    </release>
+    <release version="0.74.0" versioncode="222">
+        <change>Initial support for Amazfit GTR 3 Pro</change>
+        <change>Initial support for Sony WH-1000XM5</change>
+        <change>Amazfit Bip U: Remove alarm snooze option</change>
+        <change>Amazfit GTR 4 / GTS 4: Add watch Wi-Fi Hotspot and FTP Server</change>
+        <change>Amazfit GTR 4 / GTS 4: Perform and receive phone calls on watch</change>
+        <change>Amazfit GTS 2 Mini: Add missing alexa menu item</change>
+        <change>Bangle.js: Fix updating timezone in settings.json if the timezone is zero</change>
+        <change>Fossil/Skagen Hybrids: Pair watch to phone, fixes repeating confirmation request</change>
+        <change>Huami: Implement repeated activity fetching</change>
+        <change>Sony WH-1000XM4: Add speak-to-chat</change>
+        <change>Sony Headphones: Add button modes help</change>
+        <change>Zepp OS: Add shortcut cards preference</change>
+        <change>Zepp OS: Add support for morning updates</change>
+        <change>Zepp OS: Add preference to keep screen on during workout</change>
+        <change>Zepp OS: Add preference for camera remote</change>
+        <change>Zepp OS: Fix activate display upon lift wrist smart mode</change>
+        <change>Zepp OS: Fix Cards and MI AI display item and shortcuts</change>
+        <change>Zepp OS: Fix setting of control center</change>
+        <change>Zepp OS: Fix setting of unknown configuration values</change>
+        <change>Zepp OS: Set watchface from phone</change>
+        <change>Add Croatian transliterator</change>
+        <change>Fix restoring app notification/pebble blacklist preferences on import</change>
+        <change>Cache notifications while devices are out of range (opt-in)</change>
+    </release>
+    <release version="0.73.0" versioncode="221">
+        <change>Initial support for Amazfit T-Rex 2</change>
+        <change>Initial support for AsteroidOS watches</change>
+        <change>Initial support for Sony LinkBuds S</change>
+        <change>Initial support for Galaxy Buds2 Pro</change>
+        <change>Initial support for SoFlow S06(just for lock and unlock, needs key)</change>
+        <change>Fossil/Skagen Hybrids: Fix truncation of notifications</change>
+        <change>Fossil/Skagen Hybrids: Fix washed out colors in imported watchfaces</change>
+        <change>Fossil/Skagen Hybrids: Allow launching watch apps from app manager</change>
+        <change>Fossil/Skagen Hybrids: Fix activity parser</change>
+        <change>Fossil/Skagen Hybrids: Add app/watchface downloading from watch to app manager</change>
+        <change>Fossil/Skagen Hybrids: Fix crash on empty or multi-byte unicode alarm texts</change>
+        <change>Fossil/Skagen Hybrids: Implement inactivity warnings</change>
+        <change>Fossil/Skagen Hybrids: Remove obsolete debug message</change>
+        <change>Mi Band 6: Add NFC display item</change>
+        <change>Zepp OS: Fix Alipay and WeChat Pay display item and shortcuts</change>
+        <change>Amazfit GTR 4/GTS 4: Support for AGPS Updates</change>
+        <change>Bangle.js: Stop sending bitmaps for common characters that already have good enough equivalents on the watch</change>
+        <change>Bangle.js: Stop toast warning message appearing when starting the app loader</change>
+        <change>Bangle.js: Increase default realtime HRM/step interval to 10 seconds</change>
+        <change>Bangle.js: Support additional values for GPS event</change>
+        <change>Sony WF-1000XM4: Fix battery updates while connected</change>
+        <change>Sony WF-1000XM4: Fix audio codec</change>
+        <change>Add Georgian Transliteration</change>
+    </release>
+    <release version="0.72.0" versioncode="220">
+        <change>Initial support for Amazfit GTR 4/GTS 4/GTS 4 Mini</change>
+        <change>Initial support for Amazfit Band 7</change>
+        <change>Initial support for Galaxy Buds 2</change>
+        <change>Initial Support for Sony WH-1000XM2/WF-1000XM4</change>
+        <change>Sony headphones: Fix pause when taken off</change>
+        <change>Sony Headphones: Fix setting surround mode</change>
+        <change>Zepp OS: Map strength training, basketball and cricket activity types</change>
+        <change>Zepp OS: Add World Clocks</change>
+        <change>Zepp OS: Fix notification icons larger than 56x56px</change>
+        <change>Zepp OS: Fix notification icons for work profile apps</change>
+        <change>Zepp OS: Fix notification icon for SMS</change>
+        <change>Zepp OS: Fix app and watchface install</change>
+        <change>Zepp OS: Fix NPE when no weather data is available</change>
+        <change>Amazfit GTR 2: Fix activate display upon lift only working when scheduled</change>
+        <change>Bangle.js: Fix calendar sync</change>
+        <change>InfiniTime: Fix weather</change>
+        <change>InfiniTime: Add support for local time service</change>
+        <change>InfiniTime: Add world clock support</change>
+        <change>Skagen Gen 6 Hybrid HR: Add support for 38mm watches</change>
+        <change>Complete rewrite of new device discovery</change>
+        <change>Add Intent API to trigger activity sync and DB export</change>
+        <change>Allow media notifications to bypass app list</change>
+        <change>Debug Activity: Add confirmation dialog before removing device preferences</change>
+    </release>
+    <release version="0.71.3" versioncode="219">
+        <change>Fossil/Skagen Hybrids: Update known watch app versions</change>
+        <change>Skagen Hybrids: Allow firmware installation</change>
+        <change>Fossil Hybrid HR: Request menu config upon app connection</change>
+        <change>Amazfit GTS3: Fix crash when fetching workouts</change>
+        <change>Zepp OS: Fix fetching workouts shorter than 1 minute</change>
+        <change>Zepp OS: Decode workout elevation and altitude</change>
+        <change>Huami: Do not crash when failing to parse activity summary</change>
+        <change>Re-connect after update, especially useful for users of nightly releases</change>
+        <change>Make number of not scrollable sleep sessions lines configurable</change>
+    </release>
+    <release version="0.71.2" versioncode="218">
+        <change>Zepp OS: Display HR zones and Training Effect in Activity Details</change>
+        <change>Remove shortcut feature due to non-free dependencies (#2918)</change>
+    </release>
+    <release version="0.71.1" versioncode="217">
+        <change>Try to exclude non-free stuff from shortcuts library (#2918)</change>
+        <change>SuperCars: fix periodicDataSender, add tricks</change>
+        <change>Zepp OS: Fix crash when user attempts to disable Settings display item</change>
+        <change>Fix crash when opening Gadgetbridge from the notification</change>
+    </release>
+    <release version="0.71.0" versioncode="216">
+        <change>Remove KitKat support, Gadgetbridge now requires Android 5.0</change>
+        <change>Initial support for Amazfit GTR 3</change>
+        <change>Initial support for SuperCars (Shell Racing Cars)</change>
+        <change>Huami: Add preference to overwrite band settings on connection</change>
+        <change>Huami: Fix crash when selecting automatic Always On Display</change>
+        <change>Huami: Set OpenTracks track category and icon</change>
+        <change>Huami: Implement proper find device</change>
+        <change>Huami: Change default find band vibration pattern</change>
+        <change>Flipper Zero: added duration to Intent API</change>
+        <change>Flipper Zero: fixed crash due to unregistered boradcast receiver</change>
+        <change>Flipper Zero: fetch firmware version from flipper</change>
+        <change>Fossil Hybrid HR: Correctly initialize watchface after reset or crash</change>
+        <change>Fossil Hybrid HR: Set OpenTracks track category and icon to workout type selected on watch</change>
+        <change>Fossil Hybrid HR: Allow flick_away as custom event and add move_hands event </change>
+        <change>InfiniTime: Add weather support</change>
+        <change>Amazfit Neo: Fix world clock</change>
+        <change>Amazfit Neo: Fix long caller name display</change>
+        <change>Amazfit Neo: Remove activity tracks (unsupported)</change>
+        <change>Amazfit GTS 3: Fix battery drain due to unanswered weather requests</change>
+        <change>Mi Band 7: Fix Weather</change>
+        <change>Mi Band 6: Add support for workout activity types</change>
+        <change>Mi Band 6: Enable adding workout activity types to the "more" section</change>
+        <change>Amazfit GTR: Enable button actions</change>
+        <change>Zepp OS: Implement activity, sleep and workout fetching</change>
+        <change>Zepp OS: Improve firmware upgrades</change>
+        <change>Bangle.js: Add PATCH HTTP request type, and fix for VolleyError UnsupportedOperationException when supplying custom headers.</change>
+        <change>Bangle.js: Add ability to start services on the Android device via intents.</change>
+        <change>Bangle.js: Flags and multiple categories can now be specified for intents initiated on the watch.</change>
+        <change>Bangle.js: Add ability to wake and unlock the Android device via a special intent.</change>
+        <change>Allow 3rd party apps to set device settings</change>
+        <change>Re-implement C code in Java and remove Android NDK dependency entirely</change>
+        <change>Fix crashes on older Android versions when using some devices</change>
+        <change>Add support for REM sleep</change>
+        <change>App shortcuts support (long press on the launcher icon for directly connecting a device)</change>
+    </release>
+    <release version="0.70.0" versioncode="215">
+	<change>Initial support for Amazfit GTS 3</change>
+        <change>Initial support for Fossil Hybrid Gen6</change>
+        <change>Initial support for Flipper Zero</change>
+        <change>Huami: fix default vibration pattern</change>
+        <change>Huami: Enable vibration patterns for all compatible devices</change>
+        <change>Huami: Improve large firmware zip file handling</change>
+        <change>Bangle.js: Fix null pointer issue if headers not supplied for HTTP request</change>
+        <change>Bangle.js: Support calendar color and name</change>
+        <change>Mi Band 7: Fix crash if reminder, calendar or canned messages contain non-ascii characters</change>
+        <change>Mi Band 7: Fix NPE when acknowledging that icon was sent</change>
+        <change>Mi Band 7: Fix and enable firmware upgrades</change>
+        <change>Mi Band 7: Support for watchapps</change>
+        <change>Amazfit Neo: remove 1 hour heartrate interval (not supported)</change>
+        <change>Fossil Hybrid HR: Fix watchface redraw after powersave and after wrist flick</change>
+        <change>Fossil Hybrid HR: Enable configuring middle long press on FW 3.0 and newer</change>
+        <change>InfiniTime: Fix firmware update</change>
+        <change>Make heart rate measurement intervals configurable per-device</change>
+        <change>Add option to ignore low priority notifications</change>
+        <change>Fix Skype notifications</change>
+        <change>Prefer long notification text by default</change>
+        <change>Prefer big text when scraping gmail notifications</change>
+        <change>Do not remove newline and whitespace characters from notification content</change>
+        <change>Debug: Add companion device list to debug activity</change>
+    </release>
+</changelog>
diff --git a/app/src/main/res/xml/charts_preferences.xml b/app/src/main/res/xml/charts_preferences.xml
new file mode 100644
index 0000000..d5edce7
--- /dev/null
+++ b/app/src/main/res/xml/charts_preferences.xml
@@ -0,0 +1,151 @@
+<?xml version="1.0" encoding="utf-8"?>
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+
+    <!-- this only shows up when a device is set
+    <com.mobeta.android.dslv.DragSortListPreference
+        android:icon="@drawable/ic_tabs"
+        android:defaultValue="@array/pref_charts_tabs_items_default"
+        android:dialogTitle="@string/prefs_charts_tabs"
+        android:entries="@array/pref_charts_tabs_items"
+        android:entryValues="@array/pref_charts_tabs_values"
+        android:key="charts_tabs"
+        android:persistent="false"
+        android:summary="@string/prefs_charts_tabs_summary"
+        android:title="@string/prefs_charts_tabs" />-->
+
+    <PreferenceCategory
+        android:key="pref_charts"
+        android:title="@string/activity_prefs_charts"
+        app:iconSpaceReserved="false">
+
+        <EditTextPreference
+            android:defaultValue="250"
+            android:inputType="number"
+            android:key="chart_max_heart_rate"
+            android:maxLength="3"
+            android:title="@string/activity_prefs_chart_max_heart_rate"
+            app:iconSpaceReserved="false"
+            app:useSimpleSummaryProvider="true" />
+
+        <EditTextPreference
+            android:defaultValue="10"
+            android:inputType="number"
+            android:key="chart_min_heart_rate"
+            android:maxLength="3"
+            android:title="@string/activity_prefs_chart_min_heart_rate"
+            app:iconSpaceReserved="false"
+            app:useSimpleSummaryProvider="true" />
+
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:key="chart_heartrate_color"
+            android:layout="@layout/preference_checkbox"
+            android:summaryOff="@string/pref_chart_heartrate_color_orange"
+            android:summaryOn="@string/pref_chart_heartrate_color_red"
+            android:title="@string/pref_title_chart_heartrate_color"
+            app:iconSpaceReserved="false" />
+
+        <SwitchPreferenceCompat
+            android:defaultValue="true"
+            android:key="charts_allow_swipe"
+            android:layout="@layout/preference_checkbox"
+            android:title="@string/pref_title_charts_swipe"
+            app:iconSpaceReserved="false" />
+
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:key="chart_sleep_range_24h"
+            android:layout="@layout/preference_checkbox"
+            android:summaryOff="@string/pref_chart_sleep_rolling_24_off"
+            android:summaryOn="@string/pref_chart_sleep_rolling_24_on"
+            android:title="@string/pref_title_chart_sleep_rolling_24_hour"
+            app:iconSpaceReserved="false" />
+
+        <SwitchPreferenceCompat
+            android:defaultValue="true"
+            android:key="charts_show_average"
+            android:layout="@layout/preference_checkbox"
+            android:title="@string/pref_title_charts_average"
+            app:iconSpaceReserved="false" />
+
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:key="charts_range"
+            android:layout="@layout/preference_checkbox"
+            android:summaryOff="@string/pref_charts_range_off"
+            android:summaryOn="@string/pref_charts_range_on"
+            android:title="@string/pref_title_charts_range"
+            app:iconSpaceReserved="false" />
+
+        <SwitchPreferenceCompat
+            android:defaultValue="true"
+            android:key="charts_show_ongoing_activity"
+            android:layout="@layout/preference_checkbox"
+            android:title="@string/show_ongoing_activity"
+            app:iconSpaceReserved="false" />
+
+        <EditTextPreference
+            android:defaultValue="6"
+            android:inputType="number"
+            android:key="chart_sleep_lines_limit"
+            android:maxLength="2"
+            android:title="@string/pref_chart_sleep_lines_limit"
+            app:iconSpaceReserved="false"
+            app:useSimpleSummaryProvider="true" />
+
+    </PreferenceCategory>
+
+    <PreferenceCategory
+        android:key="pref_charts_activity_list"
+        android:title="@string/charts_activity_list"
+        app:iconSpaceReserved="false">
+
+        <EditTextPreference
+            android:defaultValue="5"
+            android:inputType="number"
+            android:key="chart_list_min_session_length"
+            android:maxLength="2"
+            android:title="@string/activity_prefs_chart_min_session_length"
+            app:iconSpaceReserved="false"
+            app:useSimpleSummaryProvider="true" />
+
+        <EditTextPreference
+            android:defaultValue="5"
+            android:inputType="number"
+            android:key="chart_list_max_idle_phase_length"
+            android:maxLength="2"
+            android:title="@string/activity_prefs_chart_max_idle_phase_length"
+            app:iconSpaceReserved="false"
+            app:useSimpleSummaryProvider="true" />
+
+        <EditTextPreference
+            android:defaultValue="20"
+            android:inputType="number"
+            android:key="chart_list_min_steps_per_minute"
+            android:maxLength="3"
+            android:title="@string/activity_prefs_chart_min_steps_per_minute"
+            app:iconSpaceReserved="false"
+            app:useSimpleSummaryProvider="true" />
+
+        <EditTextPreference
+            android:defaultValue="120"
+            android:inputType="number"
+            android:key="chart_list_min_steps_per_minute_for_run"
+            android:maxLength="3"
+            android:title="@string/activity_prefs_chart_min_steps_per_minute_for_run"
+            app:iconSpaceReserved="false"
+            app:useSimpleSummaryProvider="true" />
+    </PreferenceCategory>
+
+    <PreferenceCategory
+        android:key="pref_category_activity_personal_title"
+        android:title="@string/activity_prefs_about_you"
+        app:iconSpaceReserved="false">
+
+        <Preference
+            android:key="pref_category_activity_personal"
+            android:title="@string/activity_prefs_about_you"
+            app:iconSpaceReserved="false" />
+    </PreferenceCategory>
+</PreferenceScreen>
diff --git a/app/src/main/res/xml/dashboard_preferences.xml b/app/src/main/res/xml/dashboard_preferences.xml
new file mode 100644
index 0000000..4b61f19
--- /dev/null
+++ b/app/src/main/res/xml/dashboard_preferences.xml
@@ -0,0 +1,137 @@
+<?xml version="1.0" encoding="utf-8"?>
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+    <PreferenceCategory
+        android:key="pref_key_dashboard_general"
+        android:title="@string/pref_header_general"
+        app:iconSpaceReserved="false">
+        <SwitchPreferenceCompat
+            android:defaultValue="true"
+            android:key="dashboard_as_default_view"
+            android:layout="@layout/preference_checkbox"
+            android:title="@string/pref_dashboard_first_title"
+            android:summary="@string/pref_dashboard_first_summary"
+            app:iconSpaceReserved="false" />
+        <SwitchPreferenceCompat
+            android:defaultValue="true"
+            android:key="dashboard_cards_enabled"
+            android:layout="@layout/preference_checkbox"
+            android:title="@string/pref_dashboard_cards_title"
+            android:summary="@string/pref_dashboard_cards_summary"
+            app:iconSpaceReserved="false" />
+        <com.mobeta.android.dslv.DragSortListPreference
+            android:defaultValue="@array/pref_dashboard_widgets_order_default"
+            android:dialogTitle="@string/menuitem_widgets"
+            android:entries="@array/pref_dashboard_widgets_order"
+            android:entryValues="@array/pref_dashboard_widgets_order_values"
+            android:key="pref_dashboard_widgets_order"
+            android:persistent="true"
+            android:summary="@string/pref_dashboard_widgets_order_summary"
+            android:title="@string/menuitem_widgets"
+            app:iconSpaceReserved="false" />
+    </PreferenceCategory>
+
+    <PreferenceCategory
+        android:key="pref_key_dashboard_widgets"
+        android:title="@string/pref_dashboard_widget_settings"
+        app:iconSpaceReserved="false">
+        <PreferenceScreen
+            android:key="pref_key_dashboard_today"
+            android:title="@string/pref_dashboard_widget_today_title"
+            app:iconSpaceReserved="false">
+            <SwitchPreferenceCompat
+                android:defaultValue="false"
+                android:key="dashboard_widget_today_24h"
+                android:layout="@layout/preference_checkbox"
+                android:title="@string/pref_dashboard_widget_today_24h_title"
+                android:summary="@string/pref_dashboard_widget_today_24h_summary"
+                app:iconSpaceReserved="false" />
+            <SwitchPreferenceCompat
+                android:defaultValue="false"
+                android:key="dashboard_widget_today_24h_upside_down"
+                android:dependency="dashboard_widget_today_24h"
+                android:layout="@layout/preference_checkbox"
+                android:title="@string/pref_dashboard_widget_today_upside_down_title"
+                android:summary="@string/pref_dashboard_widget_today_upside_down_summary"
+                app:iconSpaceReserved="false" />
+            <SwitchPreferenceCompat
+                android:defaultValue="false"
+                android:key="dashboard_widget_today_show_yesterday"
+                android:layout="@layout/preference_checkbox"
+                android:title="@string/pref_dashboard_widget_today_yesterday_data_title"
+                android:summary="@string/pref_dashboard_widget_today_yesterday_data_summary"
+                app:iconSpaceReserved="false" />
+            <SwitchPreferenceCompat
+                android:defaultValue="false"
+                android:key="dashboard_widget_today_time_indicator"
+                android:layout="@layout/preference_checkbox"
+                android:title="@string/pref_dashboard_widget_today_time_indicator_title"
+                android:summary="@string/pref_dashboard_widget_today_time_indicator_summary"
+                app:iconSpaceReserved="false" />
+            <SwitchPreferenceCompat
+                android:defaultValue="true"
+                android:key="dashboard_widget_today_2columns"
+                android:layout="@layout/preference_checkbox"
+                android:title="@string/pref_dashboard_widget_double_size_title"
+                android:summary="@string/pref_dashboard_widget_double_size_summary"
+                app:iconSpaceReserved="false" />
+            <SwitchPreferenceCompat
+                android:defaultValue="true"
+                android:key="dashboard_widget_today_legend"
+                android:layout="@layout/preference_checkbox"
+                android:title="@string/pref_dashboard_widget_show_legend_title"
+                android:summary="@string/pref_dashboard_widget_show_legend_summary"
+                app:iconSpaceReserved="false" />
+            <EditTextPreference
+                android:defaultValue="1"
+                android:inputType="number"
+                android:key="dashboard_widget_today_hr_interval"
+                android:maxLength="4"
+                android:summary="@string/pref_dashboard_widget_today_hr_interval_summary"
+                android:title="@string/pref_dashboard_widget_today_hr_interval_title"
+                app:iconSpaceReserved="false" />
+        </PreferenceScreen>
+        <PreferenceScreen
+            android:key="pref_key_dashboard_goals"
+            android:title="@string/pref_dashboard_widget_goals_chart_title"
+            app:iconSpaceReserved="false">
+            <SwitchPreferenceCompat
+                android:defaultValue="true"
+                android:key="dashboard_widget_goals_2columns"
+                android:layout="@layout/preference_checkbox"
+                android:title="@string/pref_dashboard_widget_double_size_title"
+                android:summary="@string/pref_dashboard_widget_double_size_summary"
+                app:iconSpaceReserved="false" />
+            <SwitchPreferenceCompat
+                android:defaultValue="true"
+                android:key="dashboard_widget_goals_legend"
+                android:layout="@layout/preference_checkbox"
+                android:title="@string/pref_dashboard_widget_show_legend_title"
+                android:summary="@string/pref_dashboard_widget_show_legend_summary"
+                app:iconSpaceReserved="false" />
+        </PreferenceScreen>
+    </PreferenceCategory>
+
+    <PreferenceCategory
+        android:key="pref_key_dashboard_devices"
+        android:title="@string/pref_dashboard_devices_to_include"
+        app:iconSpaceReserved="false">
+        <SwitchPreferenceCompat
+            android:defaultValue="true"
+            android:key="dashboard_devices_all"
+            android:layout="@layout/preference_checkbox"
+            android:title="@string/pref_dashboard_all_devices_title"
+            android:summary="@string/pref_dashboard_all_devices_summary"
+            android:disableDependentsState="true"
+            app:iconSpaceReserved="false" />
+        <MultiSelectListPreference
+            android:dependency="dashboard_devices_all"
+            android:dialogTitle="@string/pref_dashboard_select_devices_title"
+            android:entries="@array/empty_array"
+            android:entryValues="@array/empty_array"
+            android:key="dashboard_devices_multiselect"
+            android:title="@string/pref_dashboard_select_devices_title"
+            android:summary="@string/pref_dashboard_select_devices_summary"
+            app:iconSpaceReserved="false" />
+    </PreferenceCategory>
+</PreferenceScreen>
\ No newline at end of file
diff --git a/app/src/main/res/xml/devicesettings_active_noise_cancelling_toggle.xml b/app/src/main/res/xml/devicesettings_active_noise_cancelling_toggle.xml
new file mode 100644
index 0000000..426cb3e
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_active_noise_cancelling_toggle.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <SwitchPreferenceCompat
+        android:defaultValue="false"
+        android:icon="@drawable/ic_surround"
+        android:key="active_noise_cancelling_toggle"
+        android:layout="@layout/preference_checkbox"
+        android:summary="@string/prefs_active_noise_cancelling_summary"
+        android:title="@string/prefs_active_noise_cancelling" />
+</androidx.preference.PreferenceScreen>
\ No newline at end of file
diff --git a/app/src/main/res/xml/devicesettings_activity_info_header.xml b/app/src/main/res/xml/devicesettings_activity_info_header.xml
new file mode 100644
index 0000000..5b28103
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_activity_info_header.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceCategory
+        android:title="@string/activity_info" />
+</androidx.preference.PreferenceScreen>
\ No newline at end of file
diff --git a/app/src/main/res/xml/devicesettings_allow_accept_reject_calls.xml b/app/src/main/res/xml/devicesettings_allow_accept_reject_calls.xml
new file mode 100644
index 0000000..59b8894
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_allow_accept_reject_calls.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <SwitchPreferenceCompat
+        android:defaultValue="true"
+        android:icon="@drawable/ic_notifications"
+        android:key="enable_call_accept"
+        android:layout="@layout/preference_checkbox"
+        android:title="@string/pref_enable_call_accept"
+        android:summary="@string/pref_enable_call_accept_summary" />
+    <SwitchPreferenceCompat
+        android:defaultValue="true"
+        android:icon="@drawable/ic_notifications"
+        android:key="enable_call_reject"
+        android:layout="@layout/preference_checkbox"
+        android:title="@string/pref_enable_call_reject"
+        android:summary="@string/pref_enable_call_reject_summary" />
+</PreferenceScreen>
\ No newline at end of file
diff --git a/app/src/main/res/xml/devicesettings_always_on_display.xml b/app/src/main/res/xml/devicesettings_always_on_display.xml
new file mode 100644
index 0000000..8c4574c
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_always_on_display.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceScreen
+        android:icon="@drawable/ic_always_on_display"
+        android:key="pref_screen_always_on_display"
+        android:persistent="false"
+        android:summary="@string/prefs_always_on_display_summary"
+        android:title="@string/prefs_always_on_display">
+
+        <!-- workaround for missing toolbar -->
+        <PreferenceCategory android:title="@string/prefs_always_on_display" />
+
+        <ListPreference
+            android:defaultValue="@string/p_off"
+            android:entries="@array/always_on_display"
+            android:entryValues="@array/always_on_display_values"
+            android:key="always_on_display_mode"
+            android:summary="%s"
+            android:title="@string/prefs_always_on_display" />
+
+        <nodomain.freeyourgadget.gadgetbridge.util.XTimePreference
+            android:defaultValue="00:00"
+            android:key="always_on_display_start"
+            android:title="@string/mi2_prefs_do_not_disturb_start" />
+
+        <nodomain.freeyourgadget.gadgetbridge.util.XTimePreference
+            android:defaultValue="00:00"
+            android:key="always_on_display_end"
+            android:title="@string/mi2_prefs_do_not_disturb_end" />
+
+        <SwitchPreferenceCompat
+            android:defaultValue="true"
+            android:key="always_on_display_follow_watchface"
+            android:layout="@layout/preference_checkbox"
+            android:title="@string/prefs_always_on_display_follow_watchface" />
+
+        <ListPreference
+            android:defaultValue=""
+            android:entries="@array/pref_huami2021_empty_array"
+            android:entryValues="@array/pref_huami2021_empty_array"
+            android:key="always_on_display_style"
+            android:summary="%s"
+            android:title="@string/prefs_always_on_display_style" />
+    </PreferenceScreen>
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_amazfitband5.xml b/app/src/main/res/xml/devicesettings_amazfitband5.xml
new file mode 100644
index 0000000..3ee60e7
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_amazfitband5.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <com.mobeta.android.dslv.DragSortListPreference
+        android:icon="@drawable/ic_widgets"
+        android:defaultValue="@array/pref_amazfitband5_display_items_default"
+        android:dialogTitle="@string/mi2_prefs_display_items"
+        android:entries="@array/pref_amazfitband5_display_items"
+        android:entryValues="@array/pref_amazfitband5_display_items_values"
+        android:key="display_items_sortable"
+        android:persistent="true"
+        android:summary="@string/mi2_prefs_display_items_summary"
+        android:title="@string/mi2_prefs_display_items" />
+    <com.mobeta.android.dslv.DragSortListPreference
+        android:icon="@drawable/ic_shortcut"
+        android:defaultValue="@array/pref_amazfitband5_shortcuts_default"
+        android:dialogTitle="@string/bip_prefs_shortcuts"
+        android:entries="@array/pref_amazfitband5_shortcuts"
+        android:entryValues="@array/pref_amazfitband5_shortcuts_values"
+        android:key="shortcuts_sortable"
+        android:persistent="true"
+        android:summary="@string/bip_prefs_shotcuts_summary"
+        android:title="@string/bip_prefs_shortcuts" />
+    <com.mobeta.android.dslv.DragSortListPreference
+        android:icon="@drawable/ic_activity_unknown_small"
+        android:defaultValue="@array/pref_miband5_workout_activity_types_default"
+        android:dialogTitle="@string/mi5_prefs_workout_activity_types"
+        android:entries="@array/pref_miband5_workout_activity_types"
+        android:entryValues="@array/pref_miband5_workout_activity_types_values"
+        android:key="workout_activity_types_sortable"
+        android:persistent="true"
+        android:summary="@string/mi5_prefs_workout_activity_types_summary"
+        android:title="@string/mi5_prefs_workout_activity_types" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_amazfitbip.xml b/app/src/main/res/xml/devicesettings_amazfitbip.xml
new file mode 100644
index 0000000..03d0b93
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_amazfitbip.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <com.mobeta.android.dslv.DragSortListPreference
+        android:icon="@drawable/ic_widgets"
+        android:defaultValue="@array/pref_bip_display_items_default"
+        android:dialogTitle="@string/mi2_prefs_display_items"
+        android:entries="@array/pref_bip_display_items"
+        android:entryValues="@array/pref_bip_display_items_values"
+        android:key="display_items_sortable"
+        android:persistent="true"
+        android:summary="@string/mi2_prefs_display_items_summary"
+        android:title="@string/mi2_prefs_display_items" />
+    <com.mobeta.android.dslv.DragSortListPreference
+        android:icon="@drawable/ic_shortcut"
+        android:defaultValue="@array/pref_bip_shortcuts_default"
+        android:dialogTitle="@string/bip_prefs_shortcuts"
+        android:entries="@array/pref_bip_shortcuts"
+        android:entryValues="@array/pref_bip_shortcuts_values"
+        android:key="shortcuts_sortable"
+        android:persistent="true"
+        android:summary="@string/bip_prefs_shotcuts_summary"
+        android:title="@string/bip_prefs_shortcuts" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_amazfitbip3pro.xml b/app/src/main/res/xml/devicesettings_amazfitbip3pro.xml
new file mode 100644
index 0000000..ab67f24
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_amazfitbip3pro.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <com.mobeta.android.dslv.DragSortListPreference
+        android:icon="@drawable/ic_widgets"
+        android:defaultValue="@array/pref_bip3pro_display_items_default"
+        android:dialogTitle="@string/mi2_prefs_display_items"
+        android:entries="@array/pref_bip3pro_display_items"
+        android:entryValues="@array/pref_bip3pro_display_items_values"
+        android:key="display_items_sortable"
+        android:persistent="true"
+        android:summary="@string/mi2_prefs_display_items_summary"
+        android:title="@string/mi2_prefs_display_items" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_amazfitbips.xml b/app/src/main/res/xml/devicesettings_amazfitbips.xml
new file mode 100644
index 0000000..7863b52
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_amazfitbips.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <com.mobeta.android.dslv.DragSortListPreference
+        android:icon="@drawable/ic_widgets"
+        android:defaultValue="@array/pref_bips_display_items_default"
+        android:dialogTitle="@string/mi2_prefs_display_items"
+        android:entries="@array/pref_bips_display_items"
+        android:entryValues="@array/pref_bips_display_items_values"
+        android:key="display_items_sortable"
+        android:persistent="true"
+        android:summary="@string/mi2_prefs_display_items_summary"
+        android:title="@string/mi2_prefs_display_items" />
+    <com.mobeta.android.dslv.DragSortListPreference
+        android:icon="@drawable/ic_shortcut"
+        android:defaultValue="@array/pref_bips_shortcuts_default"
+        android:dialogTitle="@string/bip_prefs_shortcuts"
+        android:entries="@array/pref_bips_shortcuts"
+        android:entryValues="@array/pref_bips_shortcuts_values"
+        android:key="shortcuts_sortable"
+        android:persistent="true"
+        android:summary="@string/bip_prefs_shotcuts_summary"
+        android:title="@string/bip_prefs_shortcuts" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_amazfitbipu.xml b/app/src/main/res/xml/devicesettings_amazfitbipu.xml
new file mode 100644
index 0000000..fe91aa5
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_amazfitbipu.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <com.mobeta.android.dslv.DragSortListPreference
+        android:icon="@drawable/ic_widgets"
+        android:defaultValue="@array/pref_bipu_display_items_default"
+        android:dialogTitle="@string/mi2_prefs_display_items"
+        android:entries="@array/pref_bipu_display_items"
+        android:entryValues="@array/pref_bipu_display_items_values"
+        android:key="display_items_sortable"
+        android:persistent="true"
+        android:summary="@string/mi2_prefs_display_items_summary"
+        android:title="@string/mi2_prefs_display_items" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_amazfitbipupro.xml b/app/src/main/res/xml/devicesettings_amazfitbipupro.xml
new file mode 100644
index 0000000..94e2042
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_amazfitbipupro.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <com.mobeta.android.dslv.DragSortListPreference
+        android:icon="@drawable/ic_widgets"
+        android:defaultValue="@array/pref_bipupro_display_items_default"
+        android:dialogTitle="@string/mi2_prefs_display_items"
+        android:entries="@array/pref_bipupro_display_items"
+        android:entryValues="@array/pref_bipupro_display_items_values"
+        android:key="display_items_sortable"
+        android:persistent="true"
+        android:summary="@string/mi2_prefs_display_items_summary"
+        android:title="@string/mi2_prefs_display_items" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_amazfitcor.xml b/app/src/main/res/xml/devicesettings_amazfitcor.xml
new file mode 100644
index 0000000..57f4451
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_amazfitcor.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <com.mobeta.android.dslv.DragSortListPreference
+        android:icon="@drawable/ic_widgets"
+        android:defaultValue="@array/pref_cor_display_items_default"
+        android:dialogTitle="@string/mi2_prefs_display_items"
+        android:entries="@array/pref_cor_display_items"
+        android:entryValues="@array/pref_cor_display_items_values"
+        android:key="display_items_sortable"
+        android:persistent="true"
+        android:summary="@string/mi2_prefs_display_items_summary"
+        android:title="@string/mi2_prefs_display_items" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_amazfitgts2mini.xml b/app/src/main/res/xml/devicesettings_amazfitgts2mini.xml
new file mode 100644
index 0000000..ae4aab2
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_amazfitgts2mini.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <com.mobeta.android.dslv.DragSortListPreference
+        android:icon="@drawable/ic_widgets"
+        android:defaultValue="@array/pref_gts2mini_display_items_default"
+        android:dialogTitle="@string/mi2_prefs_display_items"
+        android:entries="@array/pref_gts2mini_display_items"
+        android:entryValues="@array/pref_gts2mini_display_items_values"
+        android:key="display_items_sortable"
+        android:persistent="true"
+        android:summary="@string/mi2_prefs_display_items_summary"
+        android:title="@string/mi2_prefs_display_items" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_amazfitgtsgtr.xml b/app/src/main/res/xml/devicesettings_amazfitgtsgtr.xml
new file mode 100644
index 0000000..2d11c00
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_amazfitgtsgtr.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <com.mobeta.android.dslv.DragSortListPreference
+        android:icon="@drawable/ic_widgets"
+        android:defaultValue="@array/pref_gts_display_items_default"
+        android:dialogTitle="@string/mi2_prefs_display_items"
+        android:entries="@array/pref_gts_display_items"
+        android:entryValues="@array/pref_gts_display_items_values"
+        android:key="display_items_sortable"
+        android:persistent="true"
+        android:summary="@string/mi2_prefs_display_items_summary"
+        android:title="@string/mi2_prefs_display_items" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_amazfitgtsgtr2.xml b/app/src/main/res/xml/devicesettings_amazfitgtsgtr2.xml
new file mode 100644
index 0000000..2858ce1
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_amazfitgtsgtr2.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <com.mobeta.android.dslv.DragSortListPreference
+        android:icon="@drawable/ic_widgets"
+        android:defaultValue="@array/pref_gtsgtr2_display_items_default"
+        android:dialogTitle="@string/mi2_prefs_display_items"
+        android:entries="@array/pref_gtsgtr2_display_items"
+        android:entryValues="@array/pref_gtsgtr2_display_items_values"
+        android:key="display_items_sortable"
+        android:persistent="true"
+        android:summary="@string/mi2_prefs_display_items_summary"
+        android:title="@string/mi2_prefs_display_items" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_amazfitneo_display.xml b/app/src/main/res/xml/devicesettings_amazfitneo_display.xml
new file mode 100644
index 0000000..3608759
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_amazfitneo_display.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <com.mobeta.android.dslv.DragSortListPreference
+        android:icon="@drawable/ic_widgets"
+        android:defaultValue="@array/pref_neo_display_items_default"
+        android:dialogTitle="@string/mi2_prefs_display_items"
+        android:entries="@array/pref_neo_display_items"
+        android:entryValues="@array/pref_neo_display_items_values"
+        android:key="display_items_sortable"
+        android:persistent="true"
+        android:summary="@string/mi2_prefs_display_items_summary"
+        android:title="@string/mi2_prefs_display_items" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_amazfitneo_sound.xml b/app/src/main/res/xml/devicesettings_amazfitneo_sound.xml
new file mode 100644
index 0000000..2ef0034
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_amazfitneo_sound.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <MultiSelectListPreference
+        android:icon="@drawable/ic_beep"
+        android:defaultValue="@array/pref_amazfitneo_sounds_default"
+        android:dialogTitle="@string/prefs_sounds"
+        android:entries="@array/pref_amazfitneo_sounds"
+        android:entryValues="@array/pref_amazfitneo_sounds_values"
+        android:key="sounds"
+        android:persistent="true"
+        android:summary="@string/prefs_sounds_summary"
+        android:title="@string/prefs_sounds" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_amazfittrex.xml b/app/src/main/res/xml/devicesettings_amazfittrex.xml
new file mode 100644
index 0000000..0e7ab7f
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_amazfittrex.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <com.mobeta.android.dslv.DragSortListPreference
+        android:icon="@drawable/ic_widgets"
+        android:defaultValue="@array/pref_trex_display_items_default"
+        android:dialogTitle="@string/mi2_prefs_display_items"
+        android:entries="@array/pref_trex_display_items"
+        android:entryValues="@array/pref_trex_display_items_values"
+        android:key="display_items_sortable"
+        android:persistent="true"
+        android:summary="@string/mi2_prefs_display_items_summary"
+        android:title="@string/mi2_prefs_display_items" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_amazfittrexpro.xml b/app/src/main/res/xml/devicesettings_amazfittrexpro.xml
new file mode 100644
index 0000000..b7690dd
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_amazfittrexpro.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <com.mobeta.android.dslv.DragSortListPreference
+        android:icon="@drawable/ic_widgets"
+        android:defaultValue="@array/pref_trex_pro_display_items_default"
+        android:dialogTitle="@string/mi2_prefs_display_items"
+        android:entries="@array/pref_trex_pro_display_items"
+        android:entryValues="@array/pref_trex_pro_display_items_values"
+        android:key="display_items_sortable"
+        android:persistent="true"
+        android:summary="@string/mi2_prefs_display_items_summary"
+        android:title="@string/mi2_prefs_display_items" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_amazfitvergel.xml b/app/src/main/res/xml/devicesettings_amazfitvergel.xml
new file mode 100644
index 0000000..14eecb1
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_amazfitvergel.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <com.mobeta.android.dslv.DragSortListPreference
+        android:defaultValue="@array/pref_gts_display_items_values"
+        android:dialogTitle="@string/mi2_prefs_display_items"
+        android:entries="@array/pref_gts_display_items"
+        android:entryValues="@array/pref_gts_display_items_values"
+        android:icon="@drawable/ic_widgets"
+        android:key="display_items_sortable"
+        android:persistent="true"
+        android:summary="@string/mi2_prefs_display_items_summary"
+        android:title="@string/mi2_prefs_display_items" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_amazfitx.xml b/app/src/main/res/xml/devicesettings_amazfitx.xml
new file mode 100644
index 0000000..0ef11be
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_amazfitx.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <com.mobeta.android.dslv.DragSortListPreference
+        android:icon="@drawable/ic_widgets"
+        android:defaultValue="@array/pref_amazfitband5_display_items_default"
+        android:dialogTitle="@string/mi2_prefs_display_items"
+        android:entries="@array/pref_amazfitband5_display_items"
+        android:entryValues="@array/pref_amazfitband5_display_items_values"
+        android:key="display_items_sortable"
+        android:persistent="true"
+        android:summary="@string/mi2_prefs_display_items_summary"
+        android:title="@string/mi2_prefs_display_items" />
+    <com.mobeta.android.dslv.DragSortListPreference
+        android:icon="@drawable/ic_shortcut"
+        android:defaultValue="@array/pref_amazfitband5_shortcuts_default"
+        android:dialogTitle="@string/bip_prefs_shortcuts"
+        android:entries="@array/pref_amazfitband5_shortcuts"
+        android:entryValues="@array/pref_amazfitband5_shortcuts_values"
+        android:key="shortcuts_sortable"
+        android:persistent="true"
+        android:summary="@string/bip_prefs_shotcuts_summary"
+        android:title="@string/bip_prefs_shortcuts" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_antilost.xml b/app/src/main/res/xml/devicesettings_antilost.xml
new file mode 100644
index 0000000..289c3c0
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_antilost.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <SwitchPreferenceCompat
+        android:defaultValue="true"
+        android:icon="@drawable/ic_vibration"
+        android:key="pref_antilost_enabled"
+        android:layout="@layout/preference_checkbox"
+        android:summary="@string/lefun_prefs_antilost_summary"
+        android:title="@string/lefun_prefs_antilost_title" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_app_logs_start_stop.xml b/app/src/main/res/xml/devicesettings_app_logs_start_stop.xml
new file mode 100644
index 0000000..f6a2096
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_app_logs_start_stop.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceScreen
+        android:icon="@drawable/ic_developer_mode"
+        android:key="pref_screen_app_logs_start_stop"
+        android:summary="@string/pref_app_logs_summary"
+        android:title="@string/pref_app_logs_title">
+
+        <PreferenceCategory
+            android:key="pref_header_app_logs_start_stop"
+            android:title="@string/pref_app_logs_title">
+
+            <Preference
+                android:icon="@drawable/ic_play"
+                android:key="pref_app_logs_start"
+                android:summary="@string/pref_app_logs_start_summary"
+                android:title="@string/start" />
+
+            <Preference
+                android:icon="@drawable/ic_stop"
+                android:key="pref_app_logs_stop"
+                android:summary="@string/pref_app_logs_stop_summary"
+                android:title="@string/stop" />
+        </PreferenceCategory>
+    </PreferenceScreen>
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_autoheartrate.xml b/app/src/main/res/xml/devicesettings_autoheartrate.xml
new file mode 100644
index 0000000..96bbc60
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_autoheartrate.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceScreen
+        android:icon="@drawable/ic_heartrate"
+        android:key="screen_autoheartrate"
+        android:persistent="false"
+        android:summary="@string/prefs_autoheartrate_summary"
+        android:title="@string/prefs_autoheartrate">
+
+        <!-- workaround for missing toolbar -->
+        <PreferenceCategory android:title="@string/prefs_autoheartrate_summary" />
+
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:key="pref_autoheartrate_switch"
+            android:layout="@layout/preference_checkbox"
+            android:title="@string/prefs_autoheartrate_measurement" />
+
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:dependency="pref_autoheartrate_switch"
+            android:key="pref_autoheartrate_sleep"
+            android:layout="@layout/preference_checkbox"
+            android:title="@string/prefs_autoheartrate_sleep" />
+
+        <ListPreference
+            android:defaultValue="2"
+            android:dependency="pref_autoheartrate_switch"
+            android:entries="@array/autoheartrate_interval_hours"
+            android:entryValues="@array/autoheartrate_interval_hours"
+            android:key="pref_autoheartrate_interval"
+            android:summary="%s"
+            android:title="@string/prefs_autoheartrate_interval" />
+
+        <nodomain.freeyourgadget.gadgetbridge.util.XTimePreference
+            android:defaultValue="06:00"
+            android:key="pref_autoheartrate_start"
+            android:title="@string/mi2_prefs_do_not_disturb_start" />
+
+        <nodomain.freeyourgadget.gadgetbridge.util.XTimePreference
+            android:defaultValue="23:00"
+            android:key="pref_autoheartrate_end"
+            android:title="@string/mi2_prefs_do_not_disturb_end" />
+    </PreferenceScreen>
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_autolight.xml b/app/src/main/res/xml/devicesettings_autolight.xml
new file mode 100644
index 0000000..940383b
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_autolight.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <SwitchPreferenceCompat
+        android:defaultValue="false"
+        android:icon="@drawable/ic_wb_sunny"
+        android:key="autolight"
+        android:layout="@layout/preference_checkbox"
+        android:title="@string/prefs_autolight" />
+</androidx.preference.PreferenceScreen>
\ No newline at end of file
diff --git a/app/src/main/res/xml/devicesettings_automatic_power_off_by_time.xml b/app/src/main/res/xml/devicesettings_automatic_power_off_by_time.xml
new file mode 100644
index 0000000..6b11d54
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_automatic_power_off_by_time.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <ListPreference
+        android:defaultValue="off"
+        android:entries="@array/sony_automatic_power_off_names"
+        android:entryValues="@array/sony_automatic_power_off_values"
+        android:icon="@drawable/ic_power_settings_new"
+        android:key="pref_sony_automatic_power_off"
+        android:summary="%s"
+        android:title="@string/sony_automatic_power_off" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_automatic_power_off_when_taken_off.xml b/app/src/main/res/xml/devicesettings_automatic_power_off_when_taken_off.xml
new file mode 100644
index 0000000..f7f98ec
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_automatic_power_off_when_taken_off.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <ListPreference
+        android:defaultValue="off"
+        android:entries="@array/sony_automatic_power_off_when_taken_off_names"
+        android:entryValues="@array/sony_automatic_power_off_when_taken_off_values"
+        android:icon="@drawable/ic_power_settings_new"
+        android:key="pref_sony_automatic_power_off"
+        android:summary="%s"
+        android:title="@string/sony_automatic_power_off" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_autoremove_message.xml b/app/src/main/res/xml/devicesettings_autoremove_message.xml
new file mode 100644
index 0000000..8ef1b80
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_autoremove_message.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <SwitchPreferenceCompat
+        android:defaultValue="false"
+        android:icon="@drawable/ic_block"
+        android:key="autoremove_message"
+        android:layout="@layout/preference_checkbox"
+        android:title="@string/prefs_autoremove_message" />
+</androidx.preference.PreferenceScreen>
\ No newline at end of file
diff --git a/app/src/main/res/xml/devicesettings_autoremove_notifications.xml b/app/src/main/res/xml/devicesettings_autoremove_notifications.xml
new file mode 100644
index 0000000..23e19d5
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_autoremove_notifications.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <SwitchPreferenceCompat
+        android:icon="@drawable/ic_done_all"
+        android:defaultValue="true"
+        android:key="autoremove_notifications"
+        android:layout="@layout/preference_checkbox"
+        android:summary="@string/pref_summary_autoremove_notifications"
+        android:title="@string/pref_title_autoremove_notifications" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_bandw_pseries.xml b/app/src/main/res/xml/devicesettings_bandw_pseries.xml
new file mode 100644
index 0000000..96fb3c0
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_bandw_pseries.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+        <SeekBarPreference
+            style="@style/Widget.AppCompat.SeekBar.Discrete"
+            android:icon="@drawable/ic_hearing"
+            android:key="bandw_pseries_gui_vpt_level"
+            android:max="2"
+            android:summary="@string/pref_voice_passthrough_enabled_summary"
+            android:title="@string/pref_voice_passthrough_level" />
+</androidx.preference.PreferenceScreen>
\ No newline at end of file
diff --git a/app/src/main/res/xml/devicesettings_banglejs_activity.xml b/app/src/main/res/xml/devicesettings_banglejs_activity.xml
new file mode 100644
index 0000000..e9b277b
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_banglejs_activity.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+    <PreferenceCategory
+        android:key="pref_key_header_banglejs_activity"
+        android:title="@string/Activity">
+
+        <Preference
+            android:icon="@drawable/ic_refresh"
+            android:key="pref_banglejs_activity_full_sync_trigger"
+            android:summary="@string/pref_activity_full_sync_trigger_summary"
+            android:title="@string/pref_activity_full_sync_trigger_title" />
+
+        <!-- dummy preference, just to trigger customizer notifications -->
+        <EditTextPreference
+            android:defaultValue="NOT_RUNNING"
+            android:enabled="false"
+            android:key="pref_banglejs_activity_full_sync_status"
+            android:shouldDisableView="false"
+            android:title="pref_banglejs_activity_full_sync_status"
+            app:isPreferenceVisible="false" />
+    </PreferenceCategory>
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_banglejs_apploader.xml b/app/src/main/res/xml/devicesettings_banglejs_apploader.xml
new file mode 100644
index 0000000..d8a3320
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_banglejs_apploader.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <EditTextPreference
+        android:defaultValue=""
+        android:icon="@drawable/ic_link"
+        android:key="banglejs_webview_url"
+        android:summary="@string/pref_summary_banglejs_webview_url"
+        android:title="@string/pref_title_banglejs_webview_url" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_banglejs_location.xml b/app/src/main/res/xml/devicesettings_banglejs_location.xml
new file mode 100644
index 0000000..25c6cb4
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_banglejs_location.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceCategory
+        android:key="pref_header_location"
+        android:title="@string/pref_header_location">
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:icon="@drawable/ic_gps_location"
+            android:key="banglejs_gps_update"
+            android:layout="@layout/preference_checkbox"
+            android:summary="@string/pref_summary_banglejs_phone_gps_enbale"
+            android:title="@string/pref_title_banglejs_phone_gps_enbale" />
+        <EditTextPreference
+            android:defaultValue="1000"
+            android:dependency="banglejs_gps_update"
+            android:icon="@drawable/ic_access_time"
+            android:inputType="number"
+            android:key="banglejs_gps_update_interval"
+            android:summary="@string/pref_summary_banglejs_phone_gps_update_interval"
+            android:title="@string/pref_title_banglejs_phone_gps_update_interval" />
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:dependency="banglejs_gps_update"
+            android:icon="@drawable/ic_gps_location"
+            android:key="banglejs_gps_use_network_only"
+            android:layout="@layout/preference_checkbox"
+            android:summary="@string/pref_summary_banglejs_phone_gps_network_only"
+            android:title="@string/pref_title_banglejs_phone_gps_network_only" />
+    </PreferenceCategory>
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_battery_allow_pass_through.xml b/app/src/main/res/xml/devicesettings_battery_allow_pass_through.xml
new file mode 100644
index 0000000..791d3f8
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_battery_allow_pass_through.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <SwitchPreferenceCompat
+        android:defaultValue="false"
+        android:key="battery_charge_before_discharge"
+        android:layout="@layout/preference_checkbox"
+        android:summary="@string/battery_allow_pass_though_summary"
+        android:title="@string/battery_allow_pass_through" />
+</androidx.preference.PreferenceScreen>
\ No newline at end of file
diff --git a/app/src/main/res/xml/devicesettings_battery_discharge_5.xml b/app/src/main/res/xml/devicesettings_battery_discharge_5.xml
new file mode 100644
index 0000000..92a43f1
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_battery_discharge_5.xml
@@ -0,0 +1,169 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+    <PreferenceScreen
+        android:icon="@drawable/ic_access_time"
+        android:key="screen_battery_discharge settings"
+        android:persistent="false"
+        android:title="@string/battery_discharge_intervals">
+
+        <Preference
+            android:icon="@drawable/ic_refresh"
+            android:key="battery_discharge_intervals_set"
+            android:summary="@string/summary_battery_discharge_intervals_set"
+            android:title="@string/pref_title_canned_messages_set" />
+
+        <SwitchPreferenceCompat
+            android:defaultValue="true"
+            android:key="battery_discharge_manual"
+            android:layout="@layout/preference_checkbox"
+            android:summary="@string/manual_discharge_summary"
+            android:title="@string/manual_discharge" />
+
+        <PreferenceCategory
+            android:dependency="battery_discharge_manual"
+            android:title="@string/discharge_interval_1">
+            <SwitchPreferenceCompat
+                android:defaultValue="false"
+                android:key="battery_discharge_interval1_enabled"
+                android:layout="@layout/preference_checkbox"
+                android:title="@string/function_enabled" />
+
+            <nodomain.freeyourgadget.gadgetbridge.util.XTimePreference
+                android:defaultValue="00:00"
+                android:icon="@drawable/ic_access_time"
+                android:key="battery_discharge_interval1_start"
+                android:title="@string/mi2_prefs_do_not_disturb_start" />
+
+            <nodomain.freeyourgadget.gadgetbridge.util.XTimePreference
+                android:defaultValue="00:00"
+                android:icon="@drawable/ic_access_time"
+                android:key="battery_discharge_interval1_end"
+                android:title="@string/mi2_prefs_do_not_disturb_end" />
+
+            <EditTextPreference
+                android:defaultValue="80"
+                android:inputType="number"
+                android:key="battery_discharge_interval1_watt"
+                android:title="@string/power_w"
+                app:useSimpleSummaryProvider="true" />
+        </PreferenceCategory>
+
+        <PreferenceCategory
+            android:dependency="battery_discharge_manual"
+            android:title="@string/discharge_interval_2">
+            <SwitchPreferenceCompat
+                android:defaultValue="false"
+                android:key="battery_discharge_interval2_enabled"
+                android:layout="@layout/preference_checkbox"
+                android:title="@string/function_enabled" />
+
+            <nodomain.freeyourgadget.gadgetbridge.util.XTimePreference
+                android:defaultValue="00:00"
+                android:icon="@drawable/ic_access_time"
+                android:key="battery_discharge_interval2_start"
+                android:title="@string/mi2_prefs_do_not_disturb_start" />
+
+            <nodomain.freeyourgadget.gadgetbridge.util.XTimePreference
+                android:defaultValue="00:00"
+                android:icon="@drawable/ic_access_time"
+                android:key="battery_discharge_interval2_end"
+                android:title="@string/mi2_prefs_do_not_disturb_end" />
+
+            <EditTextPreference
+                android:defaultValue="80"
+                android:inputType="number"
+                android:key="battery_discharge_interval2_watt"
+                android:title="@string/power_w"
+                app:useSimpleSummaryProvider="true" />
+        </PreferenceCategory>
+
+        <PreferenceCategory
+            android:dependency="battery_discharge_manual"
+            android:title="@string/discharge_interval_3">
+            <SwitchPreferenceCompat
+                android:defaultValue="false"
+                android:key="battery_discharge_interval3_enabled"
+                android:layout="@layout/preference_checkbox"
+                android:title="@string/function_enabled" />
+
+            <nodomain.freeyourgadget.gadgetbridge.util.XTimePreference
+                android:defaultValue="00:00"
+                android:icon="@drawable/ic_access_time"
+                android:key="battery_discharge_interval3_start"
+                android:title="@string/mi2_prefs_do_not_disturb_start" />
+
+            <nodomain.freeyourgadget.gadgetbridge.util.XTimePreference
+                android:defaultValue="00:00"
+                android:icon="@drawable/ic_access_time"
+                android:key="battery_discharge_interval3_end"
+                android:title="@string/mi2_prefs_do_not_disturb_end" />
+
+            <EditTextPreference
+                android:defaultValue="80"
+                android:inputType="number"
+                android:key="battery_discharge_interval3_watt"
+                android:title="@string/power_w"
+                app:useSimpleSummaryProvider="true" />
+        </PreferenceCategory>
+
+        <PreferenceCategory
+            android:dependency="battery_discharge_manual"
+            android:title="@string/discharge_interval_4">
+            <SwitchPreferenceCompat
+                android:defaultValue="false"
+                android:key="battery_discharge_interval4_enabled"
+                android:layout="@layout/preference_checkbox"
+                android:title="@string/function_enabled" />
+
+            <nodomain.freeyourgadget.gadgetbridge.util.XTimePreference
+                android:defaultValue="00:00"
+                android:icon="@drawable/ic_access_time"
+                android:key="battery_discharge_interval4_start"
+                android:title="@string/mi2_prefs_do_not_disturb_start" />
+
+            <nodomain.freeyourgadget.gadgetbridge.util.XTimePreference
+                android:defaultValue="00:00"
+                android:icon="@drawable/ic_access_time"
+                android:key="battery_discharge_interval4_end"
+                android:title="@string/mi2_prefs_do_not_disturb_end" />
+
+            <EditTextPreference
+                android:defaultValue="80"
+                android:inputType="number"
+                android:key="battery_discharge_interval4_watt"
+                android:title="@string/power_w"
+                app:useSimpleSummaryProvider="true" />
+        </PreferenceCategory>
+
+        <PreferenceCategory
+            android:dependency="battery_discharge_manual"
+            android:title="@string/discharge_interval_5">
+            <SwitchPreferenceCompat
+                android:defaultValue="false"
+                android:key="battery_discharge_interval5_enabled"
+                android:layout="@layout/preference_checkbox"
+                android:title="@string/function_enabled" />
+
+            <nodomain.freeyourgadget.gadgetbridge.util.XTimePreference
+                android:defaultValue="00:00"
+                android:icon="@drawable/ic_access_time"
+                android:key="battery_discharge_interval5_start"
+                android:title="@string/mi2_prefs_do_not_disturb_start" />
+
+            <nodomain.freeyourgadget.gadgetbridge.util.XTimePreference
+                android:defaultValue="00:00"
+                android:icon="@drawable/ic_access_time"
+                android:key="battery_discharge_interval5_end"
+                android:title="@string/mi2_prefs_do_not_disturb_end" />
+
+            <EditTextPreference
+                android:defaultValue="80"
+                android:inputType="number"
+                android:key="battery_discharge_interval5_watt"
+                android:title="@string/power_w"
+                app:useSimpleSummaryProvider="true" />
+        </PreferenceCategory>
+
+    </PreferenceScreen>
+</androidx.preference.PreferenceScreen>
\ No newline at end of file
diff --git a/app/src/main/res/xml/devicesettings_battery_minimum_charge.xml b/app/src/main/res/xml/devicesettings_battery_minimum_charge.xml
new file mode 100644
index 0000000..8fad45e
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_battery_minimum_charge.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+    <EditTextPreference
+        android:defaultValue="10"
+        android:inputType="number"
+        android:key="battery_minimum_charge"
+        android:title="@string/battery_minimum_charge"
+        app:useSimpleSummaryProvider="true" />
+</androidx.preference.PreferenceScreen>
\ No newline at end of file
diff --git a/app/src/main/res/xml/devicesettings_ble_api.xml b/app/src/main/res/xml/devicesettings_ble_api.xml
new file mode 100644
index 0000000..e9ab0ce
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_ble_api.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceCategory
+        android:title="@string/prefs_title_ble_intent_api" />
+
+    <SwitchPreferenceCompat
+        android:icon="@drawable/ic_bluetooth"
+        android:key="prefs_device_ble_api_characteristic_read_write"
+        android:layout="@layout/preference_checkbox"
+        android:title="@string/prefs_title_gatt_client_allow_gatt_interactions"
+        android:summary="@string/prefs_summary_gatt_client_allow_gatt_interactions"
+        android:defaultValue="false" />
+
+    <SwitchPreferenceCompat
+        android:icon="@drawable/ic_bluetooth"
+        android:key="prefs_device_ble_api_characteristic_notify"
+        android:layout="@layout/preference_checkbox"
+        android:title="@string/prefs_title_gatt_client_notification_intents"
+        android:summary="@string/prefs_summary_gatt_client_notification_intents"
+        android:defaultValue="false" />
+
+    <EditTextPreference
+        android:key="prefs_device_ble_api_package"
+        android:title="@string/prefs_title_gatt_client_api_package"
+        android:summary="@string/prefs_summary_gatt_client_api_package" />
+
+</PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_bt_connected_advertisement.xml b/app/src/main/res/xml/devicesettings_bt_connected_advertisement.xml
new file mode 100644
index 0000000..93d1584
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_bt_connected_advertisement.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <SwitchPreferenceCompat
+        android:defaultValue="false"
+        android:icon="@drawable/ic_settings_remote"
+        android:key="bt_connected_advertisement"
+        android:layout="@layout/preference_checkbox"
+        android:summary="@string/pref_summary_connected_advertisement"
+        android:title="@string/pref_title_connected_advertisement"/>
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_buttonactions.xml b/app/src/main/res/xml/devicesettings_buttonactions.xml
new file mode 100644
index 0000000..7eb826a
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_buttonactions.xml
@@ -0,0 +1,93 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceScreen
+        android:key="screen_button_actions"
+        android:icon="@drawable/ic_smart_button"
+        android:persistent="false"
+        android:summary="@string/mi2_prefs_button_actions_summary"
+        android:title="@string/mi2_prefs_button_actions">
+
+        <!-- workaround for missing toolbar -->
+        <PreferenceCategory android:title="@string/mi2_prefs_button_action" />
+
+        public static final String PREF_BUTTON_ACTION_PRESS_DELAY = "button_action_press_count_delay";
+
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:icon="@drawable/ic_smart_button"
+            android:key="button_action_enable"
+            android:layout="@layout/preference_checkbox"
+            android:summary="@string/mi2_prefs_button_action_summary"
+            android:title="@string/mi2_prefs_button_action" />
+
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:icon="@drawable/ic_vibration"
+            android:dependency="button_action_enable"
+            android:key="button_action_vibrate"
+            android:layout="@layout/preference_checkbox"
+            android:summary="@string/mi2_prefs_button_action_vibrate_summary"
+            android:title="@string/mi2_prefs_button_action_vibrate" />
+
+        <ListPreference
+            android:icon="@drawable/ic_filter_1"
+            android:dependency="button_action_enable"
+            android:entries="@array/button_action_options"
+            android:entryValues="@array/button_action_values"
+            android:key="button_single_press_action_selection"
+            android:defaultValue="@string/pref_button_action_disabled_value"
+            android:summary="%s"
+            android:title="@string/prefs_button_single_press_action_selection_title" />
+
+        <ListPreference
+            android:icon="@drawable/ic_filter_2"
+            android:dependency="button_action_enable"
+            android:entries="@array/button_action_options"
+            android:entryValues="@array/button_action_values"
+            android:key="button_double_press_action_selection"
+            android:summary="%s"
+            android:defaultValue="@string/pref_button_action_disabled_value"
+            android:title="@string/prefs_button_double_press_action_selection_title" />
+
+        <ListPreference
+            android:icon="@drawable/ic_filter_3"
+            android:dependency="button_action_enable"
+            android:entries="@array/button_action_options"
+            android:entryValues="@array/button_action_values"
+            android:key="button_triple_press_action_selection"
+            android:summary="%s"
+            android:defaultValue="@string/pref_button_action_disabled_value"
+            android:title="@string/prefs_button_triple_press_action_selection_title" />
+
+        <PreferenceCategory android:title="@string/prefs_button_variable_actions">
+
+            <EditTextPreference
+                android:icon="@drawable/ic_list_numbered"
+                android:defaultValue="6"
+                android:dependency="button_action_enable"
+                android:inputType="number"
+                android:key="button_action_press_count"
+                android:summary="@string/mi2_prefs_button_press_count_summary"
+                android:title="@string/mi2_prefs_button_press_count" />
+
+            <EditTextPreference
+                android:defaultValue="@string/mi2_prefs_button_press_broadcast_default_value"
+                android:dependency="button_action_enable"
+                android:icon="@drawable/ic_message_outline"
+                android:key="button_action_broadcast"
+                android:summary="@string/mi2_prefs_button_press_broadcast_summary"
+                android:title="@string/mi2_prefs_button_press_broadcast" />
+
+            <EditTextPreference
+                android:icon="@drawable/ic_timer"
+                android:defaultValue="2000"
+                android:dependency="button_action_enable"
+                android:inputType="number"
+                android:key="button_action_press_max_interval"
+                android:summary="@string/mi2_prefs_button_press_count_max_delay_summary"
+                android:title="@string/mi2_prefs_button_press_count_max_delay" />
+
+        </PreferenceCategory>
+
+    </PreferenceScreen>
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_buttonactions_lower_short.xml b/app/src/main/res/xml/devicesettings_buttonactions_lower_short.xml
new file mode 100644
index 0000000..56f28bd
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_buttonactions_lower_short.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <ListPreference
+        android:defaultValue=""
+        android:entries="@array/pref_zepp_os_apps"
+        android:entryValues="@array/pref_zepp_os_apps_values"
+        android:icon="@drawable/ic_smart_button"
+        android:key="pref_button_action_lower_short"
+        android:summary="%s"
+        android:title="@string/pref_title_lower_button_short_press_action" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_buttonactions_upper_long.xml b/app/src/main/res/xml/devicesettings_buttonactions_upper_long.xml
new file mode 100644
index 0000000..9287a5b
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_buttonactions_upper_long.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <ListPreference
+        android:defaultValue=""
+        android:entries="@array/pref_zepp_os_apps"
+        android:entryValues="@array/pref_zepp_os_apps_values"
+        android:icon="@drawable/ic_smart_button"
+        android:key="pref_button_action_upper_long"
+        android:summary="%s"
+        android:title="@string/pref_title_upper_button_long_press_action" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_buttonactions_with_longpress.xml b/app/src/main/res/xml/devicesettings_buttonactions_with_longpress.xml
new file mode 100644
index 0000000..74edea9
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_buttonactions_with_longpress.xml
@@ -0,0 +1,102 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceScreen
+        android:key="screen_button_actions"
+        android:icon="@drawable/ic_smart_button"
+        android:persistent="false"
+        android:summary="@string/mi2_prefs_button_actions_summary"
+        android:title="@string/mi2_prefs_button_actions">
+
+        <!-- workaround for missing toolbar -->
+        <PreferenceCategory android:title="@string/mi2_prefs_button_action" />
+
+        public static final String PREF_BUTTON_ACTION_PRESS_DELAY = "button_action_press_count_delay";
+
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:icon="@drawable/ic_smart_button"
+            android:key="button_action_enable"
+            android:layout="@layout/preference_checkbox"
+            android:summary="@string/mi2_prefs_button_action_summary"
+            android:title="@string/mi2_prefs_button_action" />
+
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:icon="@drawable/ic_vibration"
+            android:dependency="button_action_enable"
+            android:key="button_action_vibrate"
+            android:layout="@layout/preference_checkbox"
+            android:summary="@string/mi2_prefs_button_action_vibrate_summary"
+            android:title="@string/mi2_prefs_button_action_vibrate" />
+
+        <ListPreference
+            android:icon="@drawable/ic_filter_1"
+            android:dependency="button_action_enable"
+            android:entries="@array/button_action_options"
+            android:entryValues="@array/button_action_values"
+            android:key="button_single_press_action_selection"
+            android:defaultValue="@string/pref_button_action_disabled_value"
+            android:summary="%s"
+            android:title="@string/prefs_button_single_press_action_selection_title" />
+
+        <ListPreference
+            android:icon="@drawable/ic_filter_2"
+            android:dependency="button_action_enable"
+            android:entries="@array/button_action_options"
+            android:entryValues="@array/button_action_values"
+            android:key="button_double_press_action_selection"
+            android:summary="%s"
+            android:defaultValue="@string/pref_button_action_disabled_value"
+            android:title="@string/prefs_button_double_press_action_selection_title" />
+
+        <ListPreference
+            android:icon="@drawable/ic_filter_3"
+            android:dependency="button_action_enable"
+            android:entries="@array/button_action_options"
+            android:entryValues="@array/button_action_values"
+            android:key="button_triple_press_action_selection"
+            android:summary="%s"
+            android:defaultValue="@string/pref_button_action_disabled_value"
+            android:title="@string/prefs_button_triple_press_action_selection_title" />
+
+        <ListPreference
+            android:icon="@drawable/ic_smart_button"
+            android:dependency="button_action_enable"
+            android:entries="@array/button_action_options"
+            android:entryValues="@array/button_action_values"
+            android:key="button_long_press_action_selection"
+            android:defaultValue="@string/pref_button_action_disabled_value"
+            android:summary="%s"
+            android:title="@string/prefs_button_long_press_action_selection_title" />
+        <PreferenceCategory android:title="@string/prefs_button_variable_actions">
+
+            <EditTextPreference
+                android:icon="@drawable/ic_list_numbered"
+                android:defaultValue="6"
+                android:dependency="button_action_enable"
+                android:inputType="number"
+                android:key="button_action_press_count"
+                android:summary="@string/mi2_prefs_button_press_count_summary"
+                android:title="@string/mi2_prefs_button_press_count" />
+
+            <EditTextPreference
+                android:icon="@drawable/ic_message_outline"
+                android:defaultValue="@string/mi2_prefs_button_press_broadcast_default_value"
+                android:dependency="button_action_enable"
+                android:key="button_action_broadcast"
+                android:summary="@string/mi2_prefs_button_press_broadcast_summary"
+                android:title="@string/mi2_prefs_button_press_broadcast" />
+
+            <EditTextPreference
+                android:icon="@drawable/ic_timer"
+                android:defaultValue="2000"
+                android:dependency="button_action_enable"
+                android:inputType="number"
+                android:key="button_action_press_max_interval"
+                android:summary="@string/mi2_prefs_button_press_count_max_delay_summary"
+                android:title="@string/mi2_prefs_button_press_count_max_delay" />
+
+        </PreferenceCategory>
+
+    </PreferenceScreen>
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_camera_remote.xml b/app/src/main/res/xml/devicesettings_camera_remote.xml
new file mode 100644
index 0000000..8c022b6
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_camera_remote.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <SwitchPreferenceCompat
+        android:defaultValue="false"
+        android:icon="@drawable/ic_camera_remote"
+        android:key="camera_remote"
+        android:layout="@layout/preference_checkbox"
+        android:summary="@string/pref_camera_remote_summary"
+        android:title="@string/pref_camera_remote_title" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_canned_dismisscall_16.xml b/app/src/main/res/xml/devicesettings_canned_dismisscall_16.xml
new file mode 100644
index 0000000..5436114
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_canned_dismisscall_16.xml
@@ -0,0 +1,122 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+    <PreferenceScreen
+        android:icon="@drawable/ic_phone_msg"
+        android:key="screen_canned_messages_dismisscall"
+        android:persistent="false"
+        android:summary="@string/pref_summary_canned_messages_dismisscall"
+        android:title="@string/pref_title_canned_messages_dismisscall">
+        <Preference
+            android:icon="@drawable/ic_refresh"
+            android:key="canned_messages_dismisscall_send"
+            android:summary="@string/pref_summary_canned_messages_set"
+            android:title="@string/pref_title_canned_messages_set" />
+
+        <!--
+            WARNING: Make sure to override getCannedRepliesSlotCount in the device coordinator
+            in order for the options below to show up.
+        -->
+
+        <PreferenceCategory
+            android:key="pref_key_canned_reply_messages"
+            android:title="@string/pref_header_cannned_messages">
+            <EditTextPreference
+                android:dialogTitle="@string/pref_canned_message"
+                android:icon="@drawable/ic_message_outline"
+                android:key="canned_message_dismisscall_1"
+                android:maxLength="64"
+                app:useSimpleSummaryProvider="true" />
+            <EditTextPreference
+                android:dialogTitle="@string/pref_canned_message"
+                android:icon="@drawable/ic_message_outline"
+                android:key="canned_message_dismisscall_2"
+                android:maxLength="64"
+                app:useSimpleSummaryProvider="true" />
+            <EditTextPreference
+                android:dialogTitle="@string/pref_canned_message"
+                android:icon="@drawable/ic_message_outline"
+                android:key="canned_message_dismisscall_3"
+                android:maxLength="64"
+                app:useSimpleSummaryProvider="true" />
+            <EditTextPreference
+                android:dialogTitle="@string/pref_canned_message"
+                android:icon="@drawable/ic_message_outline"
+                android:key="canned_message_dismisscall_4"
+                android:maxLength="64"
+                app:useSimpleSummaryProvider="true" />
+            <EditTextPreference
+                android:dialogTitle="@string/pref_canned_message"
+                android:icon="@drawable/ic_message_outline"
+                android:key="canned_message_dismisscall_5"
+                android:maxLength="64"
+                app:useSimpleSummaryProvider="true" />
+            <EditTextPreference
+                android:dialogTitle="@string/pref_canned_message"
+                android:icon="@drawable/ic_message_outline"
+                android:key="canned_message_dismisscall_6"
+                android:maxLength="64"
+                app:useSimpleSummaryProvider="true" />
+            <EditTextPreference
+                android:dialogTitle="@string/pref_canned_message"
+                android:icon="@drawable/ic_message_outline"
+                android:key="canned_message_dismisscall_7"
+                android:maxLength="64"
+                app:useSimpleSummaryProvider="true" />
+            <EditTextPreference
+                android:dialogTitle="@string/pref_canned_message"
+                android:icon="@drawable/ic_message_outline"
+                android:key="canned_message_dismisscall_8"
+                android:maxLength="64"
+                app:useSimpleSummaryProvider="true" />
+            <EditTextPreference
+                android:dialogTitle="@string/pref_canned_message"
+                android:icon="@drawable/ic_message_outline"
+                android:key="canned_message_dismisscall_9"
+                android:maxLength="64"
+                app:useSimpleSummaryProvider="true" />
+            <EditTextPreference
+                android:dialogTitle="@string/pref_canned_message"
+                android:icon="@drawable/ic_message_outline"
+                android:key="canned_message_dismisscall_10"
+                android:maxLength="64"
+                app:useSimpleSummaryProvider="true" />
+            <EditTextPreference
+                android:dialogTitle="@string/pref_canned_message"
+                android:icon="@drawable/ic_message_outline"
+                android:key="canned_message_dismisscall_11"
+                android:maxLength="64"
+                app:useSimpleSummaryProvider="true" />
+            <EditTextPreference
+                android:dialogTitle="@string/pref_canned_message"
+                android:icon="@drawable/ic_message_outline"
+                android:key="canned_message_dismisscall_12"
+                android:maxLength="64"
+                app:useSimpleSummaryProvider="true" />
+            <EditTextPreference
+                android:dialogTitle="@string/pref_canned_message"
+                android:icon="@drawable/ic_message_outline"
+                android:key="canned_message_dismisscall_13"
+                android:maxLength="64"
+                app:useSimpleSummaryProvider="true" />
+            <EditTextPreference
+                android:dialogTitle="@string/pref_canned_message"
+                android:icon="@drawable/ic_message_outline"
+                android:key="canned_message_dismisscall_14"
+                android:maxLength="64"
+                app:useSimpleSummaryProvider="true" />
+            <EditTextPreference
+                android:dialogTitle="@string/pref_canned_message"
+                android:icon="@drawable/ic_message_outline"
+                android:key="canned_message_dismisscall_15"
+                android:maxLength="64"
+                app:useSimpleSummaryProvider="true" />
+            <EditTextPreference
+                android:dialogTitle="@string/pref_canned_message"
+                android:icon="@drawable/ic_message_outline"
+                android:key="canned_message_dismisscall_16"
+                android:maxLength="64"
+                app:useSimpleSummaryProvider="true" />
+        </PreferenceCategory>
+    </PreferenceScreen>
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_canned_reply_16.xml b/app/src/main/res/xml/devicesettings_canned_reply_16.xml
new file mode 100644
index 0000000..5d1790f
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_canned_reply_16.xml
@@ -0,0 +1,131 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+    <PreferenceScreen
+        android:icon="@drawable/ic_reply"
+        android:key="screen_canned_messages_reply"
+        android:persistent="false"
+        android:summary="@string/pref_summary_canned_replies"
+        android:title="@string/pref_title_canned_replies">
+        <Preference
+            android:icon="@drawable/ic_refresh"
+            android:key="canned_messages_generic_send"
+            android:summary="@string/pref_summary_canned_messages_set"
+            android:title="@string/pref_title_canned_messages_set" />
+
+        <EditTextPreference
+            android:defaultValue=" (canned reply)"
+            android:key="canned_reply_suffix"
+            android:maxLength="64"
+            android:title="@string/pref_title_canned_reply_suffix"
+            app:useSimpleSummaryProvider="true" />
+
+        <!--
+            WARNING: Make sure to override getCannedRepliesSlotCount in the device coordinator
+            in order for the options below to show up.
+        -->
+
+        <PreferenceCategory
+            android:key="pref_key_canned_reply_messages"
+            android:title="@string/pref_header_cannned_messages">
+            <EditTextPreference
+                android:dialogTitle="@string/pref_canned_message"
+                android:icon="@drawable/ic_message_outline"
+                android:key="canned_reply_1"
+                android:maxLength="64"
+                app:useSimpleSummaryProvider="true" />
+            <EditTextPreference
+                android:dialogTitle="@string/pref_canned_message"
+                android:icon="@drawable/ic_message_outline"
+                android:key="canned_reply_2"
+                android:maxLength="64"
+                app:useSimpleSummaryProvider="true" />
+            <EditTextPreference
+                android:dialogTitle="@string/pref_canned_message"
+                android:icon="@drawable/ic_message_outline"
+                android:key="canned_reply_3"
+                android:maxLength="64"
+                app:useSimpleSummaryProvider="true" />
+            <EditTextPreference
+                android:dialogTitle="@string/pref_canned_message"
+                android:icon="@drawable/ic_message_outline"
+                android:key="canned_reply_4"
+                android:maxLength="64"
+                app:useSimpleSummaryProvider="true" />
+            <EditTextPreference
+                android:dialogTitle="@string/pref_canned_message"
+                android:icon="@drawable/ic_message_outline"
+                android:key="canned_reply_5"
+                android:maxLength="64"
+                app:useSimpleSummaryProvider="true" />
+            <EditTextPreference
+                android:dialogTitle="@string/pref_canned_message"
+                android:icon="@drawable/ic_message_outline"
+                android:key="canned_reply_6"
+                android:maxLength="64"
+                app:useSimpleSummaryProvider="true" />
+            <EditTextPreference
+                android:dialogTitle="@string/pref_canned_message"
+                android:icon="@drawable/ic_message_outline"
+                android:key="canned_reply_7"
+                android:maxLength="64"
+                app:useSimpleSummaryProvider="true" />
+            <EditTextPreference
+                android:dialogTitle="@string/pref_canned_message"
+                android:icon="@drawable/ic_message_outline"
+                android:key="canned_reply_8"
+                android:maxLength="64"
+                app:useSimpleSummaryProvider="true" />
+            <EditTextPreference
+                android:dialogTitle="@string/pref_canned_message"
+                android:icon="@drawable/ic_message_outline"
+                android:key="canned_reply_9"
+                android:maxLength="64"
+                app:useSimpleSummaryProvider="true" />
+            <EditTextPreference
+                android:dialogTitle="@string/pref_canned_message"
+                android:icon="@drawable/ic_message_outline"
+                android:key="canned_reply_10"
+                android:maxLength="64"
+                app:useSimpleSummaryProvider="true" />
+            <EditTextPreference
+                android:dialogTitle="@string/pref_canned_message"
+                android:icon="@drawable/ic_message_outline"
+                android:key="canned_reply_11"
+                android:maxLength="64"
+                app:useSimpleSummaryProvider="true" />
+            <EditTextPreference
+                android:dialogTitle="@string/pref_canned_message"
+                android:icon="@drawable/ic_message_outline"
+                android:key="canned_reply_12"
+                android:maxLength="64"
+                app:useSimpleSummaryProvider="true" />
+            <EditTextPreference
+                android:dialogTitle="@string/pref_canned_message"
+                android:icon="@drawable/ic_message_outline"
+                android:key="canned_reply_13"
+                android:maxLength="64"
+                app:useSimpleSummaryProvider="true" />
+            <EditTextPreference
+                android:dialogTitle="@string/pref_canned_message"
+                android:icon="@drawable/ic_message_outline"
+                android:key="canned_reply_14"
+                android:maxLength="64"
+                app:useSimpleSummaryProvider="true" />
+            <EditTextPreference
+                android:dialogTitle="@string/pref_canned_message"
+                android:icon="@drawable/ic_message_outline"
+                android:key="canned_reply_15"
+                android:maxLength="64"
+                app:useSimpleSummaryProvider="true" />
+            <EditTextPreference
+                android:dialogTitle="@string/pref_canned_message"
+                android:icon="@drawable/ic_message_outline"
+                android:key="canned_reply_16"
+                android:maxLength="64"
+                app:useSimpleSummaryProvider="true" />
+        </PreferenceCategory>
+    </PreferenceScreen>
+</androidx.preference.PreferenceScreen>
+
+
diff --git a/app/src/main/res/xml/devicesettings_casio_alert.xml b/app/src/main/res/xml/devicesettings_casio_alert.xml
new file mode 100644
index 0000000..20213a4
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_casio_alert.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <SwitchPreferenceCompat
+        android:defaultValue="true"
+        android:icon="@drawable/ic_email"
+        android:key="casio_alert_email"
+        android:layout="@layout/preference_checkbox"
+        android:summary="@string/pref_summary_casio_alert_email"
+        android:title="@string/pref_title_casio_alert_email" />
+    <SwitchPreferenceCompat
+        android:defaultValue="true"
+        android:icon="@drawable/ic_message_outline"
+        android:key="casio_alert_sms"
+        android:layout="@layout/preference_checkbox"
+        android:summary="@string/pref_summary_casio_alert_sms"
+        android:title="@string/pref_title_casio_alert_sms" />
+    <SwitchPreferenceCompat
+        android:defaultValue="true"
+        android:icon="@android:drawable/ic_menu_call"
+        android:key="casio_alert_call"
+        android:layout="@layout/preference_checkbox"
+        android:summary="@string/pref_summary_casio_alert_call"
+        android:title="@string/pref_title_casio_alert_call" />
+    <SwitchPreferenceCompat
+        android:defaultValue="true"
+        android:icon="@android:drawable/ic_menu_my_calendar"
+        android:key="casio_alert_calendar"
+        android:layout="@layout/preference_checkbox"
+        android:summary="@string/pref_summary_casio_alert_calendar"
+        android:title="@string/pref_title_casio_alert_calendar" />
+    <SwitchPreferenceCompat
+        android:defaultValue="true"
+        android:icon="@android:drawable/ic_menu_help"
+        android:key="casio_alert_other"
+        android:layout="@layout/preference_checkbox"
+        android:summary="@string/pref_summary_casio_alert_other"
+        android:title="@string/pref_title_casio_alert_other" />
+</androidx.preference.PreferenceScreen>
\ No newline at end of file
diff --git a/app/src/main/res/xml/devicesettings_casio_connection_duration.xml b/app/src/main/res/xml/devicesettings_casio_connection_duration.xml
new file mode 100644
index 0000000..f72ff89
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_casio_connection_duration.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <ListPreference
+        android:icon="@drawable/ic_sync"
+        android:defaultValue="2"
+        android:entries="@array/pref_casio_connection_duration"
+        android:entryValues="@array/pref_casio_connection_duration_values"
+        android:key="connection_duration"
+        android:summary="%s"
+        android:title="@string/pref_app_connection_duration" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_chartstabs.xml b/app/src/main/res/xml/devicesettings_chartstabs.xml
new file mode 100644
index 0000000..b5da769
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_chartstabs.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <com.mobeta.android.dslv.DragSortListPreference
+        android:icon="@drawable/ic_tabs"
+        android:defaultValue="@array/pref_charts_tabs_items_default"
+        android:dialogTitle="@string/mi2_prefs_display_items"
+        android:entries="@array/pref_charts_tabs_items"
+        android:entryValues="@array/pref_charts_tabs_values"
+        android:key="charts_tabs"
+        android:persistent="true"
+        android:summary="@string/prefs_charts_tabs_summary"
+        android:title="@string/prefs_charts_tabs" />
+</androidx.preference.PreferenceScreen>
+
diff --git a/app/src/main/res/xml/devicesettings_colmi_r0x.xml b/app/src/main/res/xml/devicesettings_colmi_r0x.xml
new file mode 100644
index 0000000..1bb8fa2
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_colmi_r0x.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+        <ListPreference
+            android:defaultValue="0"
+            android:entries="@array/prefs_heartrate_measurement_interval"
+            android:entryValues="@array/prefs_heartrate_measurement_interval_values"
+            android:icon="@drawable/ic_heartrate"
+            android:key="heartrate_measurement_interval"
+            android:summary="%s"
+            android:title="@string/prefs_title_heartrate_measurement_interval" />
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:icon="@drawable/ic_mood_bad"
+            android:key="heartrate_stress_monitoring"
+            android:layout="@layout/preference_checkbox"
+            android:summary="@string/prefs_stress_monitoring_description"
+            android:title="@string/prefs_stress_monitoring_title" />
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:icon="@drawable/baseline_bloodtype_24"
+            android:key="spo2_all_day_monitoring_enabled"
+            android:layout="@layout/preference_checkbox"
+            android:summary="@string/prefs_spo2_monitoring_description"
+            android:title="@string/prefs_spo2_monitoring_title" />
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:icon="@drawable/ic_show_chart"
+            android:key="hrv_all_day_monitoring_enabled"
+            android:layout="@layout/preference_checkbox"
+            android:summary="@string/prefs_hrv_monitoring_description"
+            android:title="@string/prefs_hrv_monitoring_title" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_connect_two_devices.xml b/app/src/main/res/xml/devicesettings_connect_two_devices.xml
new file mode 100644
index 0000000..b72f6d0
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_connect_two_devices.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <SwitchPreferenceCompat
+        android:defaultValue="false"
+        android:icon="@drawable/ic_devices_other"
+        android:key="pref_sony_connect_two_devices"
+        android:layout="@layout/preference_checkbox"
+        android:title="@string/sony_connect_two_devices" />
+
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_contacts.xml b/app/src/main/res/xml/devicesettings_contacts.xml
new file mode 100644
index 0000000..f1688c7
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_contacts.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <Preference
+        android:icon="@drawable/ic_person"
+        android:key="pref_contacts"
+        android:summary="@string/pref_contacts_summary"
+        android:title="@string/pref_contacts_title" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_custom_deviceicon.xml b/app/src/main/res/xml/devicesettings_custom_deviceicon.xml
new file mode 100644
index 0000000..4f39a5a
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_custom_deviceicon.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <SwitchPreferenceCompat
+        android:icon="@drawable/ic_devices_other"
+        android:defaultValue="true"
+        android:key="use_custom_deviceicon"
+        android:layout="@layout/preference_checkbox"
+        android:summary="@string/pref_summary_custom_deviceicon"
+        android:title="@string/pref_title_custom_deviceicon" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_custom_emoji_font.xml b/app/src/main/res/xml/devicesettings_custom_emoji_font.xml
new file mode 100644
index 0000000..66a047c
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_custom_emoji_font.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <SwitchPreferenceCompat
+        android:defaultValue="false"
+        android:icon="@drawable/ic_font_download"
+        android:key="use_custom_font"
+        android:layout="@layout/preference_checkbox"
+        android:summary="@string/pref_summary_use_custom_font"
+        android:title="@string/pref_title_use_custom_font" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_cycling_sensor.xml b/app/src/main/res/xml/devicesettings_cycling_sensor.xml
new file mode 100644
index 0000000..21f7b3e
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_cycling_sensor.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen
+    xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <EditTextPreference
+        android:defaultValue="29"
+        android:inputType="number"
+        android:title="@string/pref_title_wheel_diameter"
+        android:summary="@string/pref_summary_wheel_diameter"
+        android:icon="@drawable/ic_activity_biking"
+        android:key="pref_cycling_wheel_diameter" />
+
+    <EditTextPreference
+        android:defaultValue="60"
+        android:inputType="number"
+        android:title="@string/pref_title_cycling_persistence_interval"
+        android:summary="@string/pref_summary_cycling_persistence_interval"
+        android:icon="@drawable/ic_activity_biking"
+        android:key="pref_cycling_persistence_interval" />
+
+</androidx.preference.PreferenceScreen>
\ No newline at end of file
diff --git a/app/src/main/res/xml/devicesettings_dateformat.xml b/app/src/main/res/xml/devicesettings_dateformat.xml
new file mode 100644
index 0000000..ce29ed0
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_dateformat.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <ListPreference
+        android:defaultValue="MM/dd/yyyy"
+        android:icon="@drawable/ic_access_time"
+        android:entries="@array/dateformats"
+        android:entryValues="@array/dateformats_values"
+        android:key="dateformat"
+        android:summary="%s"
+        android:title="@string/miband2_prefs_dateformat" />
+
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_dateformat_2.xml b/app/src/main/res/xml/devicesettings_dateformat_2.xml
new file mode 100644
index 0000000..148547a
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_dateformat_2.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <ListPreference
+        android:defaultValue="YYYY/MM/DD"
+        android:entries="@array/dateformats_2"
+        android:entryValues="@array/dateformats_2_values"
+        android:icon="@drawable/ic_access_time"
+        android:key="dateformat"
+        android:summary="%s"
+        android:title="@string/miband2_prefs_dateformat" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_dateformat_day_month_order.xml b/app/src/main/res/xml/devicesettings_dateformat_day_month_order.xml
new file mode 100644
index 0000000..4b10ca3
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_dateformat_day_month_order.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <ListPreference
+        android:defaultValue="auto"
+        android:icon="@drawable/ic_access_time"
+        android:entries="@array/dateformat_day_month_order_entries"
+        android:entryValues="@array/dateformat_day_month_order_values"
+        android:key="dateformat"
+        android:summary="%s"
+        android:title="@string/miband2_prefs_dateformat" />
+
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_developer_add_test_activities.xml b/app/src/main/res/xml/devicesettings_developer_add_test_activities.xml
new file mode 100644
index 0000000..f53f4ab
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_developer_add_test_activities.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <Preference
+        android:icon="@drawable/ic_auto_awesome"
+        android:key="pref_developer_add_test_activities"
+        android:summary="@string/pref_developer_add_test_activities_summary"
+        android:title="@string/pref_developer_add_test_activities_title" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_device_actions.xml b/app/src/main/res/xml/devicesettings_device_actions.xml
new file mode 100644
index 0000000..db353cf
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_device_actions.xml
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+    <PreferenceScreen
+        android:key="screen_events_forwarding"
+        android:icon="@drawable/ic_extension"
+        android:persistent="false"
+        android:summary="@string/prefs_events_forwarding_summary"
+        android:title="@string/prefs_events_forwarding_title">
+
+        <PreferenceCategory
+            android:title="@string/prefs_events_forwarding_fellsleep"
+            android:icon="@drawable/ic_nights_stay">
+
+            <MultiSelectListPreference
+                android:entries="@array/device_action_options"
+                android:entryValues="@array/device_action_values"
+                android:key="events_forwarding_fellsleep_action_selections"
+                android:defaultValue="@array/empty_array"
+                android:title="@string/prefs_events_forwarding_action_title" />
+            <EditTextPreference
+                android:defaultValue="@string/prefs_events_forwarding_fellsleep_broadcast_default_value"
+                android:key="prefs_events_forwarding_fellsleep_broadcast"
+                android:title="@string/prefs_events_forwarding_broadcast_title"
+                app:useSimpleSummaryProvider="true" />
+        </PreferenceCategory>
+
+        <PreferenceCategory
+            android:title="@string/prefs_events_forwarding_wokeup"
+            android:icon="@drawable/ic_wb_sunny">
+
+            <MultiSelectListPreference
+                android:entries="@array/device_action_options"
+                android:entryValues="@array/device_action_values"
+                android:key="events_forwarding_wokeup_action_selections"
+                android:defaultValue="@array/empty_array"
+                android:title="@string/prefs_events_forwarding_action_title" />
+            <EditTextPreference
+                android:defaultValue="@string/prefs_events_forwarding_wokeup_broadcast_default_value"
+                android:key="prefs_events_forwarding_wokeup_broadcast"
+                android:title="@string/prefs_events_forwarding_broadcast_title"
+                app:useSimpleSummaryProvider="true" />
+        </PreferenceCategory>
+
+        <PreferenceCategory
+            android:title="@string/prefs_events_forwarding_startnonwear"
+            android:icon="@drawable/ic_no_watch">
+
+            <MultiSelectListPreference
+                android:entries="@array/device_action_options"
+                android:entryValues="@array/device_action_values"
+                android:key="events_forwarding_startnonwear_action_selections"
+                android:summary="%s"
+                android:defaultValue="@array/empty_array"
+                android:title="@string/prefs_events_forwarding_action_title" />
+            <EditTextPreference
+                android:defaultValue="@string/prefs_events_forwarding_startnonwear_broadcast_default_value"
+                android:key="prefs_events_forwarding_startnonwear_broadcast"
+                android:title="@string/prefs_events_forwarding_broadcast_title"
+                app:useSimpleSummaryProvider="true" />
+        </PreferenceCategory>
+
+
+    </PreferenceScreen>
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_device_actions_without_not_wear.xml b/app/src/main/res/xml/devicesettings_device_actions_without_not_wear.xml
new file mode 100644
index 0000000..18e37e8
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_device_actions_without_not_wear.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+    <PreferenceScreen
+        android:icon="@drawable/ic_extension"
+        android:key="screen_events_forwarding"
+        android:persistent="false"
+        android:summary="@string/prefs_events_forwarding_summary"
+        android:title="@string/prefs_events_forwarding_title">
+
+        <PreferenceCategory
+            android:icon="@drawable/ic_nights_stay"
+            android:title="@string/prefs_events_forwarding_fellsleep">
+
+            <MultiSelectListPreference
+                android:defaultValue="@array/empty_array"
+                android:entries="@array/device_action_options"
+                android:entryValues="@array/device_action_values"
+                android:key="events_forwarding_fellsleep_action_selections"
+                android:summary="%s"
+                android:title="@string/prefs_events_forwarding_action_title" />
+            <EditTextPreference
+                android:defaultValue="@string/prefs_events_forwarding_fellsleep_broadcast_default_value"
+                android:key="prefs_events_forwarding_fellsleep_broadcast"
+                android:title="@string/prefs_events_forwarding_broadcast_title"
+                app:useSimpleSummaryProvider="true" />
+        </PreferenceCategory>
+
+        <PreferenceCategory
+            android:icon="@drawable/ic_wb_sunny"
+            android:title="@string/prefs_events_forwarding_wokeup">
+
+            <MultiSelectListPreference
+                android:defaultValue="@array/empty_array"
+                android:entries="@array/device_action_options"
+                android:entryValues="@array/device_action_values"
+                android:key="events_forwarding_wokeup_action_selections"
+                android:summary="%s"
+                android:title="@string/prefs_events_forwarding_action_title" />
+            <EditTextPreference
+                android:defaultValue="@string/prefs_events_forwarding_wokeup_broadcast_default_value"
+                android:key="prefs_events_forwarding_wokeup_broadcast"
+                android:title="@string/prefs_events_forwarding_broadcast_title"
+                app:useSimpleSummaryProvider="true" />
+        </PreferenceCategory>
+    </PreferenceScreen>
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_device_card_activity_card_preferences.xml b/app/src/main/res/xml/devicesettings_device_card_activity_card_preferences.xml
new file mode 100644
index 0000000..65e24b9
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_device_card_activity_card_preferences.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceScreen
+        android:icon="@drawable/ic_settings_applications"
+        android:key="device_card_activity_card"
+        android:persistent="false"
+        android:summary="@string/device_card_activity_card_title_summary"
+        android:title="@string/device_card_activity_card_title">
+
+        <SwitchPreferenceCompat
+            android:defaultValue="true"
+            android:icon="@drawable/ic_activity_unknown_small"
+            android:key="prefs_activity_in_device_card"
+            android:layout="@layout/preference_checkbox"
+            android:persistent="true"
+            android:summary="@string/prefs_activity_in_device_card_title_summary"
+            android:title="@string/prefs_activity_in_device_card_title" />
+
+        <SwitchPreferenceCompat
+            android:defaultValue="true"
+            android:dependency="prefs_activity_in_device_card"
+            android:icon="@drawable/ic_shoe_prints_many"
+            android:key="prefs_activity_in_device_card_steps"
+            android:layout="@layout/preference_checkbox"
+            android:persistent="true"
+            android:summary="@string/prefs_activity_in_device_card_steps_title_summary"
+            android:title="@string/Steps" />
+
+        <SwitchPreferenceCompat
+            android:defaultValue="true"
+            android:dependency="prefs_activity_in_device_card"
+            android:icon="@drawable/ic_distance_total"
+            android:key="prefs_activity_in_device_card_distance"
+            android:layout="@layout/preference_checkbox"
+            android:persistent="true"
+            android:summary="@string/prefs_activity_in_device_card_distance_title_summary"
+            android:title="@string/Distance" />
+
+        <SwitchPreferenceCompat
+            android:defaultValue="true"
+            android:dependency="prefs_activity_in_device_card"
+            android:icon="@drawable/ic_activity_sleep"
+            android:key="prefs_activity_in_device_card_sleep"
+            android:layout="@layout/preference_checkbox"
+            android:persistent="true"
+            android:summary="@string/prefs_activity_in_device_card_sleep_title_summary"
+            android:title="@string/prefs_activity_in_device_card_sleep_title" />
+
+    </PreferenceScreen>
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_device_intents.xml b/app/src/main/res/xml/devicesettings_device_intents.xml
new file mode 100644
index 0000000..c6ee8e7
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_device_intents.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <SwitchPreferenceCompat
+        android:defaultValue="false"
+        android:icon="@drawable/ic_language"
+        android:key="device_intents"
+        android:layout="@layout/preference_checkbox"
+        android:summary="@string/pref_summary_device_intents"
+        android:title="@string/pref_title_device_intents" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_device_internet_access.xml b/app/src/main/res/xml/devicesettings_device_internet_access.xml
new file mode 100644
index 0000000..27474a1
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_device_internet_access.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <SwitchPreferenceCompat
+        android:defaultValue="false"
+        android:icon="@drawable/ic_language"
+        android:key="device_internet_access"
+        android:layout="@layout/preference_checkbox"
+        android:summary="@string/pref_summary_device_internet_access"
+        android:title="@string/pref_title_device_internet_access" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_device_name.xml b/app/src/main/res/xml/devicesettings_device_name.xml
new file mode 100644
index 0000000..c37d906
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_device_name.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+    <EditTextPreference
+        android:icon="@drawable/ic_bluetooth"
+        android:key="pref_device_name"
+        android:title="@string/prefs_device_name"
+        app:useSimpleSummaryProvider="true" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_disable_find_phone_with_dnd.xml b/app/src/main/res/xml/devicesettings_disable_find_phone_with_dnd.xml
new file mode 100644
index 0000000..02fc537
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_disable_find_phone_with_dnd.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <SwitchPreferenceCompat
+        android:defaultValue="false"
+        android:icon="@drawable/ic_notifications"
+        android:key="disable_find_phone_with_dnd"
+        android:layout="@layout/preference_checkbox"
+        android:title="@string/pref_disable_find_phone_with_dnd" />
+</PreferenceScreen>
\ No newline at end of file
diff --git a/app/src/main/res/xml/devicesettings_disconnectnotification.xml b/app/src/main/res/xml/devicesettings_disconnectnotification.xml
new file mode 100644
index 0000000..8ec99b2
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_disconnectnotification.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <PreferenceScreen
+        android:key="screen_disconnect_nofifiction"
+        android:icon="@drawable/ic_signal_cellular_connected_no_internet_4_bar"
+        android:persistent="false"
+        android:title="@string/prefs_disconnect_notification">
+
+        <!-- workaround for missing toolbar -->
+        <PreferenceCategory android:title="@string/prefs_disconnect_notification" />
+
+        <ListPreference
+            android:defaultValue="@string/p_off"
+            android:entries="@array/activate_display_on_lift"
+            android:entryValues="@array/activate_display_on_lift_values"
+            android:key="disconnect_notification"
+            android:summary="%s"
+            android:title="@string/prefs_disconnect_notification" />
+
+        <nodomain.freeyourgadget.gadgetbridge.util.XTimePreference
+            android:defaultValue="00:00"
+            android:key="disconnect_notification_start"
+            android:title="@string/mi2_prefs_do_not_disturb_start" />
+        <nodomain.freeyourgadget.gadgetbridge.util.XTimePreference
+            android:defaultValue="00:00"
+            android:key="disconnect_notification_end"
+            android:title="@string/mi2_prefs_do_not_disturb_end" />
+
+    </PreferenceScreen>
+
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_disconnectnotification_noshed.xml b/app/src/main/res/xml/devicesettings_disconnectnotification_noshed.xml
new file mode 100644
index 0000000..2304d70
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_disconnectnotification_noshed.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <SwitchPreferenceCompat
+        android:icon="@drawable/ic_signal_cellular_connected_no_internet_4_bar"
+        android:defaultValue="false"
+        android:key="disconnect_notification_noshed"
+        android:layout="@layout/preference_checkbox"
+        android:title="@string/prefs_disconnect_notification"
+        android:summary="@string/prefs_disconnect_notification_summary"/>
+</androidx.preference.PreferenceScreen>
\ No newline at end of file
diff --git a/app/src/main/res/xml/devicesettings_display_caller.xml b/app/src/main/res/xml/devicesettings_display_caller.xml
new file mode 100644
index 0000000..c993dc3
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_display_caller.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <SwitchPreferenceCompat
+        android:defaultValue="true"
+        android:icon="@drawable/ic_person"
+        android:key="display_caller"
+        android:layout="@layout/preference_checkbox"
+        android:summary="@string/pref_display_caller_summary"
+        android:title="@string/pref_display_caller_title" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_donotdisturb_allday_liftwirst.xml b/app/src/main/res/xml/devicesettings_donotdisturb_allday_liftwirst.xml
new file mode 100644
index 0000000..81c2299
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_donotdisturb_allday_liftwirst.xml
@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceScreen
+        android:icon="@drawable/ic_block"
+        android:key="screen_do_not_disturb"
+        android:persistent="false"
+        android:summary="@string/mi2_prefs_do_not_disturb_summary"
+        android:title="@string/mi2_prefs_do_not_disturb">
+
+        <!-- workaround for missing toolbar -->
+        <PreferenceCategory
+            android:title="@string/mi2_prefs_do_not_disturb"
+            android:summary="@string/mi2_prefs_do_not_disturb_summary"/>
+
+        <ListPreference
+            android:defaultValue="@string/p_off"
+            android:entries="@array/do_not_disturb_allday"
+            android:entryValues="@array/mi2_do_not_disturb_values"
+            android:key="do_not_disturb"
+            android:summary="%s"
+            android:title="@string/mi2_prefs_do_not_disturb" />
+
+        <nodomain.freeyourgadget.gadgetbridge.util.XTimePreference
+            android:defaultValue="00:00"
+            android:key="do_not_disturb_start"
+            android:title="@string/mi2_prefs_do_not_disturb_start" />
+
+        <nodomain.freeyourgadget.gadgetbridge.util.XTimePreference
+            android:defaultValue="23:59"
+            android:key="do_not_disturb_end"
+            android:title="@string/mi2_prefs_do_not_disturb_end" />
+
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:icon="@drawable/ic_arrow_upward"
+            android:key="do_not_disturb_lift_wrist"
+            android:layout="@layout/preference_checkbox"
+            android:title="@string/mi2_prefs_do_not_disturb_lift_wrist"
+            android:summary="@string/do_not_disturb_lift_wrist_summary"/>
+
+        <!-- workaround for missing toolbar -->
+        <PreferenceCategory android:title="@string/zetime_prefs_inactivity_repetitions">
+            <CheckBoxPreference
+                android:defaultValue="true"
+                android:key="pref_do_not_disturb_mo"
+                android:title="@string/zetime_prefs_inactivity_mo" />
+            <CheckBoxPreference
+                android:defaultValue="true"
+                android:key="pref_do_not_disturb_tu"
+                android:title="@string/zetime_prefs_inactivity_tu" />
+            <CheckBoxPreference
+                android:defaultValue="true"
+                android:key="pref_do_not_disturb_we"
+                android:title="@string/zetime_prefs_inactivity_we" />
+            <CheckBoxPreference
+                android:defaultValue="true"
+                android:key="pref_do_not_disturb_th"
+                android:title="@string/zetime_prefs_inactivity_th" />
+            <CheckBoxPreference
+                android:defaultValue="true"
+                android:key="pref_do_not_disturb_fr"
+                android:title="@string/zetime_prefs_inactivity_fr" />
+            <CheckBoxPreference
+                android:defaultValue="true"
+                android:key="pref_do_not_disturb_sa"
+                android:title="@string/zetime_prefs_inactivity_sa" />
+            <CheckBoxPreference
+                android:defaultValue="true"
+                android:key="pref_do_not_disturb_su"
+                android:title="@string/zetime_prefs_inactivity_su" />
+        </PreferenceCategory>
+
+    </PreferenceScreen>
+
+
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_donotdisturb_allday_liftwirst_notwear.xml b/app/src/main/res/xml/devicesettings_donotdisturb_allday_liftwirst_notwear.xml
new file mode 100644
index 0000000..910eaa0
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_donotdisturb_allday_liftwirst_notwear.xml
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceScreen
+        android:icon="@drawable/ic_block"
+        android:key="screen_do_not_disturb"
+        android:persistent="false"
+        android:summary="@string/mi2_prefs_do_not_disturb_summary"
+        android:title="@string/mi2_prefs_do_not_disturb">
+
+        <ListPreference
+            android:defaultValue="@string/p_off"
+            android:entries="@array/do_not_disturb_allday"
+            android:entryValues="@array/mi2_do_not_disturb_values"
+            android:key="do_not_disturb"
+            android:summary="%s"
+            android:title="@string/mi2_prefs_do_not_disturb" />
+
+        <nodomain.freeyourgadget.gadgetbridge.util.XTimePreference
+            android:defaultValue="00:00"
+            android:key="do_not_disturb_start"
+            android:title="@string/mi2_prefs_do_not_disturb_start" />
+
+        <nodomain.freeyourgadget.gadgetbridge.util.XTimePreference
+            android:defaultValue="23:59"
+            android:key="do_not_disturb_end"
+            android:title="@string/mi2_prefs_do_not_disturb_end" />
+
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:icon="@drawable/ic_arrow_upward"
+            android:key="do_not_disturb_lift_wrist"
+            android:layout="@layout/preference_checkbox"
+            android:title="@string/mi2_prefs_do_not_disturb_lift_wrist"
+            android:summary="@string/do_not_disturb_lift_wrist_summary"/>
+
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:icon="@drawable/ic_notification_disconnected"
+            android:key="do_not_disturb_not_wear"
+            android:layout="@layout/preference_checkbox"
+            android:title="@string/pref_do_not_disturb_not_wear" />
+
+        <!-- workaround for missing toolbar -->
+        <PreferenceCategory android:title="@string/zetime_prefs_inactivity_repetitions">
+            <CheckBoxPreference
+                android:defaultValue="true"
+                android:key="pref_do_not_disturb_mo"
+                android:title="@string/zetime_prefs_inactivity_mo" />
+            <CheckBoxPreference
+                android:defaultValue="true"
+                android:key="pref_do_not_disturb_tu"
+                android:title="@string/zetime_prefs_inactivity_tu" />
+            <CheckBoxPreference
+                android:defaultValue="true"
+                android:key="pref_do_not_disturb_we"
+                android:title="@string/zetime_prefs_inactivity_we" />
+            <CheckBoxPreference
+                android:defaultValue="true"
+                android:key="pref_do_not_disturb_th"
+                android:title="@string/zetime_prefs_inactivity_th" />
+            <CheckBoxPreference
+                android:defaultValue="true"
+                android:key="pref_do_not_disturb_fr"
+                android:title="@string/zetime_prefs_inactivity_fr" />
+            <CheckBoxPreference
+                android:defaultValue="true"
+                android:key="pref_do_not_disturb_sa"
+                android:title="@string/zetime_prefs_inactivity_sa" />
+            <CheckBoxPreference
+                android:defaultValue="true"
+                android:key="pref_do_not_disturb_su"
+                android:title="@string/zetime_prefs_inactivity_su" />
+        </PreferenceCategory>
+
+    </PreferenceScreen>
+
+
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_donotdisturb_lift_wrist.xml b/app/src/main/res/xml/devicesettings_donotdisturb_lift_wrist.xml
new file mode 100644
index 0000000..a3ce885
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_donotdisturb_lift_wrist.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <SwitchPreferenceCompat
+        android:defaultValue="false"
+        android:icon="@drawable/ic_arrow_upward"
+        android:key="do_not_disturb_lift_wrist"
+        android:layout="@layout/preference_checkbox"
+        android:title="@string/mi2_prefs_do_not_disturb_lift_wrist" />
+
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_donotdisturb_no_auto.xml b/app/src/main/res/xml/devicesettings_donotdisturb_no_auto.xml
new file mode 100644
index 0000000..a9ae834
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_donotdisturb_no_auto.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceScreen
+        android:icon="@drawable/ic_block"
+        android:key="screen_do_not_disturb_no_auto"
+        android:persistent="false"
+        android:summary="@string/mi2_prefs_do_not_disturb_summary"
+        android:title="@string/mi2_prefs_do_not_disturb">
+
+        <ListPreference
+            android:defaultValue="@string/p_off"
+            android:entries="@array/do_not_disturb_no_auto"
+            android:entryValues="@array/do_not_disturb_no_auto_values"
+            android:key="do_not_disturb_no_auto"
+            android:summary="%s"
+            android:title="@string/mi2_prefs_do_not_disturb" />
+
+        <nodomain.freeyourgadget.gadgetbridge.util.XTimePreference
+            android:defaultValue="22:00"
+            android:key="do_not_disturb_no_auto_start"
+            android:title="@string/mi2_prefs_do_not_disturb_start" />
+
+        <nodomain.freeyourgadget.gadgetbridge.util.XTimePreference
+            android:defaultValue="06:00"
+            android:key="do_not_disturb_no_auto_end"
+            android:title="@string/mi2_prefs_do_not_disturb_end" />
+
+    </PreferenceScreen>
+
+
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_donotdisturb_withauto.xml b/app/src/main/res/xml/devicesettings_donotdisturb_withauto.xml
new file mode 100644
index 0000000..f83e5ee
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_donotdisturb_withauto.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceScreen
+        android:icon="@drawable/ic_block"
+        android:key="screen_do_not_disturb"
+        android:persistent="false"
+        android:summary="@string/mi2_prefs_do_not_disturb_summary"
+        android:title="@string/mi2_prefs_do_not_disturb">
+
+        <!-- workaround for missing toolbar -->
+        <PreferenceCategory android:title="@string/mi2_prefs_do_not_disturb" />
+
+        <ListPreference
+            android:defaultValue="@string/p_off"
+            android:entries="@array/mi2_do_not_disturb"
+            android:entryValues="@array/mi2_do_not_disturb_values"
+            android:key="do_not_disturb"
+            android:summary="%s"
+            android:title="@string/mi2_prefs_do_not_disturb" />
+
+        <nodomain.freeyourgadget.gadgetbridge.util.XTimePreference
+            android:defaultValue="01:00"
+            android:key="do_not_disturb_start"
+            android:title="@string/mi2_prefs_do_not_disturb_start" />
+
+        <nodomain.freeyourgadget.gadgetbridge.util.XTimePreference
+            android:defaultValue="06:00"
+            android:key="do_not_disturb_end"
+            android:title="@string/mi2_prefs_do_not_disturb_end" />
+
+    </PreferenceScreen>
+
+
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_donotdisturb_withauto_and_always.xml b/app/src/main/res/xml/devicesettings_donotdisturb_withauto_and_always.xml
new file mode 100644
index 0000000..d27210c
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_donotdisturb_withauto_and_always.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceScreen
+        android:icon="@drawable/ic_block"
+        android:key="pref_screen_do_not_disturb"
+        android:persistent="false"
+        android:summary="@string/mi2_prefs_do_not_disturb_summary"
+        android:title="@string/mi2_prefs_do_not_disturb">
+
+        <!-- workaround for missing toolbar -->
+        <PreferenceCategory android:title="@string/mi2_prefs_do_not_disturb" />
+
+        <ListPreference
+            android:defaultValue="@string/p_off"
+            android:entries="@array/do_not_disturb_with_always"
+            android:entryValues="@array/do_not_disturb_with_always_values"
+            android:key="do_not_disturb"
+            android:summary="%s"
+            android:title="@string/mi2_prefs_do_not_disturb" />
+
+        <nodomain.freeyourgadget.gadgetbridge.util.XTimePreference
+            android:defaultValue="01:00"
+            android:key="do_not_disturb_start"
+            android:title="@string/mi2_prefs_do_not_disturb_start" />
+
+        <nodomain.freeyourgadget.gadgetbridge.util.XTimePreference
+            android:defaultValue="06:00"
+            android:key="do_not_disturb_end"
+            android:title="@string/mi2_prefs_do_not_disturb_end" />
+    </PreferenceScreen>
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_expose_hr_thirdparty.xml b/app/src/main/res/xml/devicesettings_expose_hr_thirdparty.xml
new file mode 100644
index 0000000..a3386a9
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_expose_hr_thirdparty.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <SwitchPreferenceCompat
+        android:defaultValue="false"
+        android:icon="@drawable/ic_settings_remote"
+        android:key="expose_hr_thirdparty"
+        android:layout="@layout/preference_checkbox"
+        android:summary="@string/pref_summary_expose_hr"
+        android:title="@string/pref_title_expose_hr" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_fake_ring_duration.xml b/app/src/main/res/xml/devicesettings_fake_ring_duration.xml
new file mode 100644
index 0000000..88d4042
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_fake_ring_duration.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <SwitchPreferenceCompat
+        android:defaultValue="false"
+        android:icon="@drawable/ic_rotate_left"
+        android:key="fake_ring_duration"
+        android:layout="@layout/preference_checkbox"
+        android:title="@string/prefs_fake_ring_duration" />
+</androidx.preference.PreferenceScreen>
\ No newline at end of file
diff --git a/app/src/main/res/xml/devicesettings_fake_timeoffset.xml b/app/src/main/res/xml/devicesettings_fake_timeoffset.xml
new file mode 100644
index 0000000..7db798c
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_fake_timeoffset.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <EditTextPreference
+        android:icon="@drawable/ic_access_time"
+        android:defaultValue="0"
+        android:inputType="number"
+        android:key="device_time_offset_hours"
+        android:maxLength="2"
+        android:title="@string/miband_prefs_device_time_offset_hours" />
+</androidx.preference.PreferenceScreen>
\ No newline at end of file
diff --git a/app/src/main/res/xml/devicesettings_femometer.xml b/app/src/main/res/xml/devicesettings_femometer.xml
new file mode 100644
index 0000000..1e41c46
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_femometer.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <ListPreference
+        android:entries="@array/femometer_measurement_mode"
+        android:entryValues="@array/femometer_measurement_mode_values"
+        android:defaultValue="normal"
+        android:icon="@drawable/ic_checklist"
+        android:key="femometer_measurement_mode"
+        android:summary="%s"
+        android:title="@string/femometer_measurement_mode_title" />
+</androidx.preference.PreferenceScreen>
\ No newline at end of file
diff --git a/app/src/main/res/xml/devicesettings_fetch_unknown_files.xml b/app/src/main/res/xml/devicesettings_fetch_unknown_files.xml
new file mode 100644
index 0000000..443c1af
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_fetch_unknown_files.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <SwitchPreferenceCompat
+        android:defaultValue="false"
+        android:icon="@drawable/ic_activity_unknown_small"
+        android:key="fetch_unknown_files"
+        android:layout="@layout/preference_checkbox"
+        android:summary="@string/pref_fetch_unknown_files_summary"
+        android:title="@string/pref_fetch_unknown_files_title" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_find_phone.xml b/app/src/main/res/xml/devicesettings_find_phone.xml
new file mode 100644
index 0000000..0e440e1
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_find_phone.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceScreen
+        android:icon="@drawable/ic_vibration"
+        android:key="screen_prefs_find_phone"
+        android:persistent="false"
+        android:summary="@string/prefs_find_phone_summary"
+        android:title="@string/prefs_find_phone">
+
+        <ListPreference
+            android:defaultValue="@string/p_off"
+            android:entries="@array/prefs_find_phone"
+            android:entryValues="@array/prefs_find_phone_values"
+            android:key="prefs_find_phone"
+            android:title="@string/prefs_enable_find_phone" />
+
+        <EditTextPreference
+            android:defaultValue="60"
+            android:inputType="number"
+            android:key="prefs_find_phone_duration"
+            android:title="@string/prefs_find_phone_duration" />
+
+    </PreferenceScreen>
+</androidx.preference.PreferenceScreen>
\ No newline at end of file
diff --git a/app/src/main/res/xml/devicesettings_fitpro.xml b/app/src/main/res/xml/devicesettings_fitpro.xml
new file mode 100644
index 0000000..739f281
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_fitpro.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <ListPreference
+        android:defaultValue="auto"
+        android:entries="@array/pref_fitpro_language_options"
+        android:entryValues="@array/pref_fitpro_language_values"
+        android:icon="@drawable/ic_language"
+        android:key="language"
+        android:summary="%s"
+        android:title="@string/pref_title_language" />
+    
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_force_connection_type.xml b/app/src/main/res/xml/devicesettings_force_connection_type.xml
new file mode 100644
index 0000000..3b584e0
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_force_connection_type.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <ListPreference
+        android:key="pref_force_connection_type"
+        android:icon="@drawable/ic_bluetooth"
+        android:title="@string/pref_force_connection_type_title"
+        android:summary="@string/pref_force_connection_type_description"
+        android:entries="@array/pref_force_connection_type"
+        android:entryValues="@array/pref_force_connection_type_values"
+        android:defaultValue="@string/pref_force_connection_type_auto_value"
+        />
+</PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_fossilhybridhr_calibration.xml b/app/src/main/res/xml/devicesettings_fossilhybridhr_calibration.xml
new file mode 100644
index 0000000..2a5f8d7
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_fossilhybridhr_calibration.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <Preference
+        android:title="@string/qhybrid_title_calibration"
+        android:icon="@drawable/ic_sensor_calibration"
+        android:summary="@string/qhybrid_summary_calibration">
+        <intent
+            android:targetPackage="@string/applicationId"
+            android:targetClass="nodomain.freeyourgadget.gadgetbridge.devices.qhybrid.CalibrationActivity" />
+    </Preference>
+
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_fossilhybridhr_dev.xml b/app/src/main/res/xml/devicesettings_fossilhybridhr_dev.xml
new file mode 100644
index 0000000..ffeb137
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_fossilhybridhr_dev.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+
+    <SwitchPreferenceCompat
+        android:defaultValue="false"
+        android:key="save_raw_activity_files"
+        android:layout="@layout/preference_checkbox"
+        android:title="@string/pref_qhybrid_save_raw_activity_files"
+        android:icon="@drawable/ic_date_range" />
+
+    <SwitchPreferenceCompat
+        android:defaultValue="false"
+        android:key="dangerous_external_intents"
+        android:layout="@layout/preference_checkbox"
+        android:title="@string/qhybrid_pref_title_external_intents"
+        android:summary="@string/qhybrid_pref_summary_external_intents"
+        android:icon="@drawable/ic_warning_gray"/>
+
+    <Preference
+        android:title="@string/qhybrid_title_file_management"
+        android:summary="@string/qhybrid_summary_file_management"
+        android:icon="@drawable/ic_file_upload">
+        <intent
+            android:targetPackage="@string/applicationId"
+            android:targetClass="nodomain.freeyourgadget.gadgetbridge.devices.qhybrid.FileManagementActivity" />
+    </Preference>
+
+    <SwitchPreferenceCompat
+        android:defaultValue="true"
+        android:key="enable_on_device_confirmation"
+        android:layout="@layout/preference_checkbox"
+        android:title="@string/qhybrid_title_on_device_confirmation"
+        android:summary="@string/qhybrid_summary_on_device_confirmation"
+        android:icon="@drawable/ic_vpn_key"/>
+
+    <EditTextPreference
+        android:key="voice_service_package"
+        android:title="@string/voice_service_package_title"
+        android:summary="@string/voice_service_package_summary"
+        android:icon="@drawable/ic_voice"
+        app:useSimpleSummaryProvider="true" />
+
+    <EditTextPreference
+        android:key="voice_service_class"
+        android:title="@string/voice_service_class_title"
+        android:summary="@string/voice_service_class_summary"
+        android:icon="@drawable/ic_voice"
+        app:useSimpleSummaryProvider="true" />
+
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_fossilhybridhr_navigation.xml b/app/src/main/res/xml/devicesettings_fossilhybridhr_navigation.xml
new file mode 100644
index 0000000..6d11b3f
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_fossilhybridhr_navigation.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <PreferenceScreen
+        android:icon="@drawable/baseline_merge_24"
+        android:title="@string/pref_title_fossil_hr_navigation_instructions"
+        android:summary="@string/pref_summary_fossil_hr_navigation_instructions"
+        android:key="navigation_app_config">
+
+        <SwitchPreferenceCompat
+            android:defaultValue="true"
+            android:key="fossil_hr_nav_auto_foreground"
+            android:layout="@layout/preference_checkbox"
+            android:title="@string/pref_title_fossil_hr_nav_foreground"
+            android:summary="@string/pref_summary_fossil_hr_nav_foreground"
+            android:icon="@drawable/ic_info"/>
+        <SwitchPreferenceCompat
+            android:defaultValue="true"
+            android:key="fossil_hr_nav_vibrate"
+            android:layout="@layout/preference_checkbox"
+            android:title="@string/pref_title_fossil_hr_nav_vibrate"
+            android:summary="@string/pref_summary_fossil_hr_nav_vibrate"
+            android:icon="@drawable/ic_action_find_lost_device"/>
+
+    </PreferenceScreen>
+
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_fossilhybridhr_post_fw300.xml b/app/src/main/res/xml/devicesettings_fossilhybridhr_post_fw300.xml
new file mode 100644
index 0000000..d5156ec
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_fossilhybridhr_post_fw300.xml
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+	
+	<PreferenceScreen
+        android:key="button_configuration"
+        android:icon="@drawable/ic_smart_button"
+        android:title="@string/pref_title_physical_buttons"
+        android:summary="@string/pref_summary_physical_buttons">
+        <ListPreference
+            android:defaultValue="weatherApp"
+            android:entries="@array/pref_hybridhr_buttonfunctions"
+            android:entryValues="@array/pref_hybridhr_buttonfunctions_values"
+            android:key="button_1_function_short"
+            android:summary="%s"
+            android:title="@string/pref_title_upper_button_function_short" />
+        <ListPreference
+            android:defaultValue="weatherApp"
+            android:entries="@array/pref_hybridhr_buttonfunctions"
+            android:entryValues="@array/pref_hybridhr_buttonfunctions_values"
+            android:key="button_1_function_long"
+            android:summary="%s"
+            android:title="@string/pref_title_upper_button_function_long" />
+        <ListPreference
+            android:defaultValue="launcherApp"
+            android:entries="@array/pref_hybridhr_buttonfunctions"
+            android:entryValues="@array/pref_hybridhr_buttonfunctions_values"
+            android:key="button_2_function_short"
+            android:summary="%s"
+            android:title="@string/pref_title_middle_button_function_short" />
+        <ListPreference
+            android:defaultValue="launcherApp"
+            android:entries="@array/pref_hybridhr_buttonfunctions"
+            android:entryValues="@array/pref_hybridhr_buttonfunctions_values"
+            android:key="button_2_function_long"
+            android:summary="%s"
+            android:title="@string/pref_title_middle_button_function_long" />
+        <ListPreference
+            android:defaultValue="musicApp"
+            android:entries="@array/pref_hybridhr_buttonfunctions"
+            android:entryValues="@array/pref_hybridhr_buttonfunctions_values"
+            android:key="button_3_function_short"
+            android:summary="%s"
+            android:title="@string/pref_title_lower_button_function_short" />
+        <ListPreference
+            android:defaultValue="musicApp"
+            android:entries="@array/pref_hybridhr_buttonfunctions"
+            android:entryValues="@array/pref_hybridhr_buttonfunctions_values"
+            android:key="button_3_function_long"
+            android:summary="%s"
+            android:title="@string/pref_title_lower_button_function_long" />
+        <Preference
+            android:selectable="false"
+            android:persistent="false"
+            android:key="button_1_function_long_warning"
+            android:summary="@string/fossil_hr_button_config_info" />
+    </PreferenceScreen>
+	
+</androidx.preference.PreferenceScreen>
\ No newline at end of file
diff --git a/app/src/main/res/xml/devicesettings_fossilhybridhr_pre_fw220.xml b/app/src/main/res/xml/devicesettings_fossilhybridhr_pre_fw220.xml
new file mode 100644
index 0000000..f425a09
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_fossilhybridhr_pre_fw220.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+
+    <Preference
+        android:title="@string/qhybrid_title_watchface"
+        android:icon="@drawable/ic_watch"
+        android:summary="@string/qhybrid_watchface_configuration_old_firmware">
+        <intent
+            android:targetPackage="@string/applicationId"
+            android:targetClass="nodomain.freeyourgadget.gadgetbridge.devices.qhybrid.HRConfigActivity" />
+    </Preference>
+
+    <SwitchPreferenceCompat
+        android:defaultValue="false"
+        android:icon="@drawable/ic_filter_b_and_w"
+        android:key="force_white_color_scheme"
+        android:layout="@layout/preference_checkbox"
+        android:summary="@string/pref_summary_force_white_color_scheme"
+        android:title="@string/pref_title_force_white_color_scheme" />
+    <SwitchPreferenceCompat
+        android:defaultValue="false"
+        android:icon="@drawable/ic_circle"
+        android:key="widget_draw_circles"
+        android:layout="@layout/preference_checkbox"
+        android:title="@string/pref_qhybrid_title_widget_draw_circles" />
+
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_fossilhybridhr_pre_fw300.xml b/app/src/main/res/xml/devicesettings_fossilhybridhr_pre_fw300.xml
new file mode 100644
index 0000000..9c60150
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_fossilhybridhr_pre_fw300.xml
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+	
+	<PreferenceScreen
+        android:key="button_configuration"
+        android:icon="@drawable/ic_smart_button"
+        android:title="@string/pref_title_physical_buttons"
+        android:summary="@string/pref_summary_physical_buttons">
+        <ListPreference
+            android:defaultValue="weatherApp"
+            android:entries="@array/pref_hybridhr_buttonfunctions"
+            android:entryValues="@array/pref_hybridhr_buttonfunctions_values"
+            android:key="button_1_function_short"
+            android:summary="%s"
+            android:title="@string/pref_title_upper_button_function_short" />
+        <ListPreference
+            android:defaultValue="weatherApp"
+            android:entries="@array/pref_hybridhr_buttonfunctions"
+            android:entryValues="@array/pref_hybridhr_buttonfunctions_values"
+            android:key="button_1_function_long"
+            android:summary="%s"
+            android:title="@string/pref_title_upper_button_function_long" />
+        <ListPreference
+            android:defaultValue="commuteApp"
+            android:entries="@array/pref_hybridhr_buttonfunctions"
+            android:entryValues="@array/pref_hybridhr_buttonfunctions_values"
+            android:key="button_2_function_short"
+            android:summary="%s"
+            android:title="@string/pref_title_middle_button_function_short" />
+        <Preference
+            android:selectable="false"
+            android:persistent="false"
+            android:key="button_2_function_long"
+            android:summary="@string/menuitem_menu"
+            android:title="@string/pref_title_middle_button_function_long" />
+        <ListPreference
+            android:defaultValue="musicApp"
+            android:entries="@array/pref_hybridhr_buttonfunctions"
+            android:entryValues="@array/pref_hybridhr_buttonfunctions_values"
+            android:key="button_3_function_short"
+            android:summary="%s"
+            android:title="@string/pref_title_lower_button_function_short" />
+        <ListPreference
+            android:defaultValue="musicApp"
+            android:entries="@array/pref_hybridhr_buttonfunctions"
+            android:entryValues="@array/pref_hybridhr_buttonfunctions_values"
+            android:key="button_3_function_long"
+            android:summary="%s"
+            android:title="@string/pref_title_lower_button_function_long" />
+        <Preference
+            android:selectable="false"
+            android:persistent="false"
+            android:key="button_1_function_long_warning"
+            android:summary="@string/fossil_hr_button_config_info" />
+    </PreferenceScreen>
+
+    <Preference
+            android:title="@string/qhybrid_pref_title_actions"
+            android:icon="@drawable/ic_pending_actions"
+            android:summary="@string/qhybrid_pref_summary_actions">
+                <intent
+                    android:targetPackage="@string/applicationId"
+                    android:targetClass="nodomain.freeyourgadget.gadgetbridge.devices.qhybrid.CommuteActionsActivity" />
+    </Preference>
+
+</androidx.preference.PreferenceScreen>
\ No newline at end of file
diff --git a/app/src/main/res/xml/devicesettings_fossilhybridhr_vibration.xml b/app/src/main/res/xml/devicesettings_fossilhybridhr_vibration.xml
new file mode 100644
index 0000000..3ad0f98
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_fossilhybridhr_vibration.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+
+    <SeekBarPreference
+        android:defaultValue="2"
+        android:icon="@drawable/ic_vibration"
+        android:key="vibration_strength"
+        android:max="3"
+        android:title="@string/pref_title_vibration_strength"
+        app:showSeekBarValue="true" />
+
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_fossilhybridhr_workout_detection.xml b/app/src/main/res/xml/devicesettings_fossilhybridhr_workout_detection.xml
new file mode 100644
index 0000000..0e61776
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_fossilhybridhr_workout_detection.xml
@@ -0,0 +1,125 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+
+    <PreferenceScreen
+        android:icon="@drawable/ic_activity_unknown_small"
+        android:title="@string/pref_workout_detection_title"
+        android:summary="@string/pref_workout_detection_summary"
+        android:key="workout_detection_settings">
+
+        <PreferenceCategory
+            android:key="pref_workout_detection_running"
+            android:title="@string/activity_type_running">
+            <SwitchPreferenceCompat
+                android:defaultValue="false"
+                android:key="activity_recognize_running_enabled"
+                android:layout="@layout/preference_checkbox"
+                android:title="@string/pref_workout_detection_enabled"
+                android:summary="@string/pref_workout_detection_summary"
+                android:icon="@drawable/ic_activity_running"/>
+            <SwitchPreferenceCompat
+                android:defaultValue="false"
+                android:key="activity_recognize_running_ask_first"
+                android:layout="@layout/preference_checkbox"
+                android:title="@string/pref_workout_detection_ask_first"
+                android:summary="@string/pref_workout_detection_ask_first_summary"
+                android:icon="@drawable/ic_warning_gray"
+                android:dependency="activity_recognize_running_enabled"/>
+            <EditTextPreference
+                android:defaultValue="3"
+                android:inputType="number"
+                android:icon="@drawable/ic_timer"
+                android:key="activity_recognize_running_minutes"
+                android:title="@string/pref_workout_detection_time"
+                android:summary="@string/pref_workout_detection_time_summary"
+                android:dependency="activity_recognize_running_enabled"/>
+        </PreferenceCategory>
+
+        <PreferenceCategory
+            android:key="pref_workout_detection_biking"
+            android:title="@string/activity_type_biking">
+            <SwitchPreferenceCompat
+                android:defaultValue="false"
+                android:key="activity_recognize_biking_enabled"
+                android:layout="@layout/preference_checkbox"
+                android:title="@string/pref_workout_detection_enabled"
+                android:summary="@string/pref_workout_detection_summary"
+                android:icon="@drawable/ic_activity_biking"/>
+            <SwitchPreferenceCompat
+                android:defaultValue="false"
+                android:key="activity_recognize_biking_ask_first"
+                android:layout="@layout/preference_checkbox"
+                android:title="@string/pref_workout_detection_ask_first"
+                android:summary="@string/pref_workout_detection_ask_first_summary"
+                android:icon="@drawable/ic_warning_gray"
+                android:dependency="activity_recognize_biking_enabled"/>
+            <EditTextPreference
+                android:defaultValue="5"
+                android:inputType="number"
+                android:icon="@drawable/ic_timer"
+                android:key="activity_recognize_biking_minutes"
+                android:title="@string/pref_workout_detection_time"
+                android:summary="@string/pref_workout_detection_time_summary"
+                android:dependency="activity_recognize_biking_enabled"/>
+        </PreferenceCategory>
+
+        <PreferenceCategory
+            android:key="pref_workout_detection_walking"
+            android:title="@string/activity_type_walking">
+            <SwitchPreferenceCompat
+                android:defaultValue="false"
+                android:key="activity_recognize_walking_enabled"
+                android:layout="@layout/preference_checkbox"
+                android:title="@string/pref_workout_detection_enabled"
+                android:summary="@string/pref_workout_detection_summary"
+                android:icon="@drawable/ic_activity_walking"/>
+            <SwitchPreferenceCompat
+                android:defaultValue="false"
+                android:key="activity_recognize_walking_ask_first"
+                android:layout="@layout/preference_checkbox"
+                android:title="@string/pref_workout_detection_ask_first"
+                android:summary="@string/pref_workout_detection_ask_first_summary"
+                android:icon="@drawable/ic_warning_gray"
+                android:dependency="activity_recognize_walking_enabled"/>
+            <EditTextPreference
+                android:defaultValue="10"
+                android:inputType="number"
+                android:icon="@drawable/ic_timer"
+                android:key="activity_recognize_walking_minutes"
+                android:title="@string/pref_workout_detection_time"
+                android:summary="@string/pref_workout_detection_time_summary"
+                android:dependency="activity_recognize_walking_enabled"/>
+        </PreferenceCategory>
+
+        <PreferenceCategory
+            android:key="pref_workout_detection_rowing"
+            android:title="@string/activity_type_rowing">
+            <SwitchPreferenceCompat
+                android:defaultValue="false"
+                android:key="activity_recognize_rowing_enabled"
+                android:layout="@layout/preference_checkbox"
+                android:title="@string/pref_workout_detection_enabled"
+                android:summary="@string/pref_workout_detection_summary"
+                android:icon="@drawable/ic_activity_rowing"/>
+            <SwitchPreferenceCompat
+                android:defaultValue="false"
+                android:key="activity_recognize_rowing_ask_first"
+                android:layout="@layout/preference_checkbox"
+                android:title="@string/pref_workout_detection_ask_first"
+                android:summary="@string/pref_workout_detection_ask_first_summary"
+                android:icon="@drawable/ic_warning_gray"
+                android:dependency="activity_recognize_rowing_enabled"/>
+            <EditTextPreference
+                android:defaultValue="3"
+                android:inputType="number"
+                android:icon="@drawable/ic_timer"
+                android:key="activity_recognize_rowing_minutes"
+                android:title="@string/pref_workout_detection_time"
+                android:summary="@string/pref_workout_detection_time_summary"
+                android:dependency="activity_recognize_rowing_enabled"/>
+        </PreferenceCategory>
+
+    </PreferenceScreen>
+
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_fossilqhybrid_legacy.xml b/app/src/main/res/xml/devicesettings_fossilqhybrid_legacy.xml
new file mode 100644
index 0000000..aeed1ab
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_fossilqhybrid_legacy.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <Preference
+        android:icon="@drawable/ic_settings"
+        android:key="pref_key_qhybrid_legacy"
+        android:summary="@string/preferences_qhybrid_settings_summary"
+        android:title="@string/preferences_qhybrid_settings" />
+
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_ftp_server.xml b/app/src/main/res/xml/devicesettings_ftp_server.xml
new file mode 100644
index 0000000..54d8e68
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_ftp_server.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceScreen
+        android:icon="@drawable/ic_file_upload"
+        android:key="pref_screen_ftp_server"
+        android:summary="@string/ftp_server_summary"
+        android:title="@string/ftp_server">
+
+        <PreferenceCategory
+            android:key="pref_header_ftp_server_configuration"
+            android:title="@string/ftp_server_configuration">
+
+            <EditTextPreference
+                android:icon="@drawable/ic_folder"
+                android:defaultValue=""
+                android:key="ftp_server_root_dir"
+                android:title="@string/ftp_server_root_dir" />
+
+            <Preference
+                android:icon="@drawable/ic_link"
+                android:key="ftp_server_address"
+                android:summary=""
+                android:title="@string/address" />
+
+            <Preference
+                android:icon="@drawable/ic_person"
+                android:key="ftp_server_username"
+                android:summary=""
+                android:title="@string/username" />
+        </PreferenceCategory>
+
+        <PreferenceCategory
+            android:key="pref_header_ftp_status"
+            android:title="@string/ftp_server_status">
+
+            <Preference
+                android:icon="@drawable/ic_play"
+                android:key="ftp_server_start"
+                android:summary="@string/ftp_server_start_summary"
+                android:title="@string/start" />
+
+            <Preference
+                android:icon="@drawable/ic_stop"
+                android:key="ftp_server_stop"
+                android:summary="@string/ftp_server_stop_summary"
+                android:title="@string/stop" />
+
+            <Preference
+                android:icon="@drawable/ic_mtu"
+                android:key="ftp_server_status"
+                android:summary=""
+                android:title="@string/status" />
+        </PreferenceCategory>
+    </PreferenceScreen>
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_galaxy_buds.xml b/app/src/main/res/xml/devicesettings_galaxy_buds.xml
new file mode 100644
index 0000000..24e2e52
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_galaxy_buds.xml
@@ -0,0 +1,112 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+    <PreferenceScreen
+        android:icon="@drawable/ic_hearing"
+        android:key="prefs_ambient_mode"
+        android:persistent="false"
+        android:title="@string/prefs_ambient_mode">
+
+        <PreferenceCategory android:title="@string/prefs_ambient_mode">
+            <SwitchPreferenceCompat
+                android:defaultValue="false"
+                android:icon="@drawable/ic_hearing"
+                android:key="pref_galaxy_buds_ambient_mode"
+                android:layout="@layout/preference_checkbox"
+                android:summary="Hear surrounding sounds"
+                android:title="@string/prefs_ambient_sound" />
+            <SwitchPreferenceCompat
+                android:defaultValue="false"
+                android:dependency="pref_galaxy_buds_ambient_mode"
+                android:icon="@drawable/ic_voice"
+                android:key="pref_galaxy_buds_ambient_voice_focus"
+                android:layout="@layout/preference_checkbox"
+                android:summary="@string/prefs_ambient_voice_summary"
+                android:title="@string/prefs_ambient_voice_focus" />
+
+            <SeekBarPreference
+                style="@style/Widget.AppCompat.SeekBar.Discrete"
+                android:icon="@drawable/ic_volume_up"
+                android:key="pref_galaxy_buds_ambient_volume"
+                android:max="5"
+                android:title="@string/prefs_ambient_volume"
+                app:min="1"
+                app:showSeekBarValue="true" />
+        </PreferenceCategory>
+    </PreferenceScreen>
+
+    <PreferenceScreen
+        android:icon="@drawable/ic_graphic_eq"
+        android:key="prefs_equalizer"
+        android:persistent="false"
+        android:title="@string/prefs_equalizer">
+        <PreferenceCategory android:title="@string/prefs_equalizer">
+            <SwitchPreferenceCompat
+                android:defaultValue="false"
+                android:icon="@drawable/ic_graphic_eq"
+                android:key="pref_galaxy_buds_equalizer"
+                android:layout="@layout/preference_checkbox"
+                android:summary="@string/prefs_equalizer_summary"
+                android:title="@string/prefs_equalizer" />
+            <SwitchPreferenceCompat
+                android:defaultValue="false"
+                android:dependency="pref_galaxy_buds_equalizer"
+                android:icon="@drawable/ic_music_note"
+                android:key="pref_galaxy_buds_equalizer_dolby"
+                android:layout="@layout/preference_checkbox"
+                android:summary="@string/prefs_dolby_summary"
+                android:title="@string/prefs_dolby_mode" />
+            <ListPreference
+                android:dependency="pref_galaxy_buds_equalizer"
+                android:entries="@array/galaxy_eqalizer_modes"
+                android:entryValues="@array/galaxy_eqalizer_values"
+                android:icon="@drawable/ic_equalizer"
+                android:key="pref_galaxy_buds_equalizer_mode"
+                android:summary="%s"
+                android:title="@string/prefs_equalizer_preset" />
+        </PreferenceCategory>
+    </PreferenceScreen>
+
+    <PreferenceScreen
+        android:icon="@drawable/ic_touch"
+        android:key="prefs_galaxy_touch_options"
+        android:persistent="false"
+        android:title="@string/prefs_galaxy_touch_options">
+        <PreferenceCategory android:title="@string/prefs_galaxy_touch_options">
+            <SwitchPreferenceCompat
+                android:defaultValue="false"
+                android:disableDependentsState="true"
+                android:icon="@drawable/ic_lock_open"
+                android:key="pref_galaxy_buds_lock_touch"
+                android:layout="@layout/preference_checkbox"
+                android:summary="@string/prefs_touch_lock_summary"
+                android:title="@string/prefs_touch_lock" />
+            <ListPreference
+                android:dependency="pref_galaxy_buds_lock_touch"
+                android:entries="@array/galaxy_touch_options_left"
+                android:entryValues="@array/galaxy_touch_options_values"
+                android:icon="@drawable/ic_touch"
+                android:key="pref_galaxy_buds_touch_left"
+                android:summary="%s"
+                android:title="@string/prefs_left" />
+            <ListPreference
+                android:dependency="pref_galaxy_buds_lock_touch"
+                android:entries="@array/galaxy_touch_options_right"
+                android:entryValues="@array/galaxy_touch_options_values"
+                android:icon="@drawable/ic_touch"
+                android:key="pref_galaxy_buds_touch_right"
+                android:summary="%s"
+                android:title="@string/prefs_right" />
+        </PreferenceCategory>
+    </PreferenceScreen>
+    <PreferenceCategory android:title="@string/prefs_galaxy_buds_experimental">
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:icon="@drawable/ic_videogame"
+            android:key="pref_galaxy_buds_game_mode"
+            android:layout="@layout/preference_checkbox"
+            android:summary="@string/prefs_game_mode_summary"
+            android:title="@string/prefs_game_mode" />
+    </PreferenceCategory>
+
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_galaxy_buds_2.xml b/app/src/main/res/xml/devicesettings_galaxy_buds_2.xml
new file mode 100644
index 0000000..540e11f
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_galaxy_buds_2.xml
@@ -0,0 +1,212 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+
+    <PreferenceScreen
+        android:icon="@drawable/ic_surround"
+        android:key="pref_galaxy_buds_pro_noise_control_preference"
+        android:persistent="false"
+        android:title="@string/prefs_noise_control">
+        <PreferenceCategory android:title="@string/prefs_noise_control">
+
+            <ListPreference
+                android:defaultValue="0"
+                android:entries="@array/galaxy_pro_noise_controls_options"
+                android:entryValues="@array/galaxy_pro_noise_controls_options_values"
+                android:icon="@drawable/ic_surround"
+                android:key="pref_galaxy_buds_2_noise_control"
+                android:summary="%s"
+                android:title="@string/prefs_noise_control" />
+
+            <SeekBarPreference
+                android:icon="@drawable/ic_volume_up"
+                android:key="pref_galaxy_buds_ambient_volume"
+                android:max="2"
+                android:min="0"
+                android:title="@string/prefs_ambient_volume"
+                app:showSeekBarValue="false" />
+
+        </PreferenceCategory>
+    </PreferenceScreen>
+
+    <PreferenceScreen
+        android:icon="@drawable/ic_hearing"
+        android:key="prefs_accessibility"
+        android:persistent="false"
+        android:title="@string/prefs_ambient_settings_title">
+
+        <PreferenceCategory android:title="@string/prefs_ambient_sound">
+            <SwitchPreferenceCompat
+                android:defaultValue="false"
+                android:icon="@drawable/ic_phone"
+                android:key="pref_galaxy_buds_ambient_mode_during_call"
+                android:layout="@layout/preference_checkbox"
+                android:summary="@string/prefs_ambient_sound_during_call_summary"
+                android:title="@string/prefs_ambient_sound_during_call_title" />
+
+            <SwitchPreferenceCompat
+                android:defaultValue="false"
+                android:icon="@drawable/ic_hearing"
+                android:key="pref_galaxy_buds_ambient_sound"
+                android:layout="@layout/preference_checkbox"
+                android:title="@string/prefs_customize_ambient_sound_summary" />
+
+            <SeekBarPreference
+                android:defaultValue="1"
+                android:dependency="pref_galaxy_buds_ambient_sound"
+                android:icon="@drawable/ic_volume_up"
+                android:key="pref_galaxy_buds_pro_ambient_volume_right"
+                android:max="2"
+                android:min="0"
+                android:title="@string/prefs_ambient_volume_right"
+                app:showSeekBarValue="false" />
+
+            <SeekBarPreference
+                android:defaultValue="1"
+                android:dependency="pref_galaxy_buds_ambient_sound"
+                android:icon="@drawable/ic_volume_up"
+                android:key="pref_galaxy_buds_pro_ambient_volume_left"
+                android:max="2"
+                android:min="0"
+                android:title="@string/prefs_ambient_volume_left"
+                app:showSeekBarValue="false" />
+
+            <SeekBarPreference
+                android:defaultValue="1"
+                android:dependency="pref_galaxy_buds_ambient_sound"
+                android:icon="@drawable/ic_surround"
+                android:key="pref_galaxy_buds_pro_ambient_sound_tone"
+                android:max="4"
+                android:min="0"
+                android:summary="@string/pref_ambient_sound_tone_summary"
+                android:title="@string/pref_ambient_sound_tone"
+                app:showSeekBarValue="false" />
+        </PreferenceCategory>
+    </PreferenceScreen>
+
+    <ListPreference
+        android:defaultValue="0"
+        android:entries="@array/galaxy_buds_live_equalizer_modes"
+        android:entryValues="@array/galaxy_buds_live_equalizer_values"
+        android:icon="@drawable/ic_equalizer"
+        android:key="pref_galaxy_buds_equalizer_mode"
+        android:summary="%s"
+        android:title="@string/prefs_equalizer_preset" />
+
+    <PreferenceScreen
+        android:icon="@drawable/ic_touch"
+        android:key="prefs_galaxy_touch_options"
+        android:persistent="false"
+        android:title="@string/prefs_galaxy_touch_options">
+        <PreferenceCategory android:title="@string/prefs_galaxy_touch_options">
+            <SwitchPreferenceCompat
+                android:defaultValue="true"
+                android:icon="@drawable/ic_touch"
+                android:key="pref_galaxy_buds_lock_touch"
+                android:layout="@layout/preference_checkbox"
+                android:title="@string/prefs_touch_lock_buds2" />
+            <ListPreference
+                android:defaultValue="0"
+                android:dependency="pref_galaxy_buds_lock_touch"
+                android:entries="@array/galaxy_pro_touch_options_left"
+                android:entryValues="@array/galaxy_pro_touch_options_values"
+                android:icon="@drawable/ic_switch_left"
+                android:key="pref_galaxy_buds_touch_left"
+                android:summary="%s"
+                android:title="@string/prefs_left" />
+
+            <ListPreference
+                android:defaultValue="0"
+                android:dependency="pref_galaxy_buds_lock_touch"
+                android:entries="@array/galaxy_pro_touch_switch_controls_options"
+                android:entryValues="@array/galaxy_pro_touch_switch_controls_values"
+                android:key="pref_galaxy_buds_touch_left_switch"
+                android:summary="%s"
+                android:title="@string/prefs_switch_control_left" />
+
+            <ListPreference
+                android:defaultValue="0"
+                android:dependency="pref_galaxy_buds_lock_touch"
+                android:entries="@array/galaxy_pro_touch_options_right"
+                android:entryValues="@array/galaxy_pro_touch_options_values"
+                android:icon="@drawable/ic_switch_right"
+                android:key="pref_galaxy_buds_touch_right"
+                android:summary="%s"
+                android:title="@string/prefs_right" />
+            <ListPreference
+                android:defaultValue="0"
+                android:dependency="pref_galaxy_buds_lock_touch"
+                android:entries="@array/galaxy_pro_touch_switch_controls_options"
+                android:entryValues="@array/galaxy_pro_touch_switch_controls_values"
+                android:key="pref_galaxy_buds_touch_right_switch"
+                android:summary="%s"
+                android:title="@string/prefs_switch_control_right" />
+
+            <SwitchPreferenceCompat
+                android:defaultValue="false"
+                android:dependency="pref_galaxy_buds_lock_touch"
+                android:icon="@drawable/ic_touch"
+                android:key="pref_galaxy_pro_double_tap_edge"
+                android:layout="@layout/preference_checkbox"
+                android:summary="@string/prefs_double_tap_edge_summary"
+                android:title="@string/prefs_double_tap_edge" />
+        </PreferenceCategory>
+    </PreferenceScreen>
+
+    <PreferenceScreen
+        android:icon="@drawable/ic_settings"
+        android:key="prefs_settings"
+        android:persistent="false"
+        android:title="@string/title_activity_settings">
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:icon="@drawable/ic_hearing"
+            android:key="pref_galaxy_buds_pro_in_ear_detection"
+            android:layout="@layout/preference_checkbox"
+            android:summary="@string/prefs_in_ear_detection_summary"
+            android:title="@string/nothing_prefs_inear_title" />
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:icon="@drawable/ic_auto_awesome"
+            android:key="prefs_galaxy_buds_seamless_connection"
+            android:layout="@layout/preference_checkbox"
+            android:summary="@string/prefs_seamless_connection_switch_summary"
+            android:title="@string/prefs_seamless_connection_switch_title" />
+
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:icon="@drawable/ic_hearing"
+            android:key="pref_galaxy_buds_noise_controls_with_one_earbud"
+            android:layout="@layout/preference_checkbox"
+            android:summary="@string/prefs_noise_control_with_one_earbud_summary"
+            android:title="@string/prefs_noise_control_with_one_earbud" />
+
+        <SeekBarPreference
+            android:defaultValue="50"
+            android:icon="@drawable/ic_volume_up"
+            android:key="pref_galaxy_buds_pro_balance"
+            android:max="32"
+            android:min="0"
+            android:title="@string/pref_balance"
+            app:defaultValue="16"
+            app:showSeekBarValue="true" />
+
+        <PreferenceCategory android:title="@string/prefs_galaxy_buds_experimental">
+            <SwitchPreferenceCompat
+                android:defaultValue="false"
+                android:icon="@drawable/ic_videogame"
+                android:key="pref_galaxy_buds_game_mode"
+                android:layout="@layout/preference_checkbox"
+                android:summary="@string/prefs_game_mode_summary"
+                android:title="@string/prefs_game_mode" />
+        </PreferenceCategory>
+
+        <!-- <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:icon="@drawable/ic_graphic_eq"
+            android:key="pref_galaxy_buds_pro_read_notifications_outloud"
+            android:title="@string/prefs_read_notification_outloud" />
+            -->
+
+    </PreferenceScreen>
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_galaxy_buds_2_pro.xml b/app/src/main/res/xml/devicesettings_galaxy_buds_2_pro.xml
new file mode 100644
index 0000000..3a933dd
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_galaxy_buds_2_pro.xml
@@ -0,0 +1,214 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+    <ListPreference
+        android:defaultValue="0"
+        android:entries="@array/galaxy_pro_noise_controls_options"
+        android:entryValues="@array/galaxy_pro_noise_controls_options_values"
+        android:icon="@drawable/ic_surround"
+        android:key="pref_galaxy_buds_pro_noise_control"
+        android:summary="%s"
+        android:title="@string/prefs_noise_control" />
+    <PreferenceScreen
+        android:icon="@drawable/ic_hearing"
+        android:key="prefs_accessibility"
+        android:persistent="false"
+        android:title="@string/prefs_ambient_settings_title">
+
+        <PreferenceCategory android:title="@string/prefs_voice_detect">
+            <SwitchPreferenceCompat
+                android:defaultValue="false"
+                android:icon="@drawable/ic_voice"
+                android:key="pref_galaxy_buds_pro_voice_detect"
+                android:layout="@layout/preference_checkbox"
+                android:summary="@string/prefs_voice_detect_summary"
+                android:title="@string/prefs_voice_detect" />
+            <ListPreference
+                android:defaultValue="5"
+                android:dependency="pref_galaxy_buds_pro_voice_detect"
+                android:entries="@array/galaxy_pro_voice_detect_duration_options"
+                android:entryValues="@array/galaxy_pro_voice_detect_duration_values"
+                android:icon="@drawable/ic_timer"
+                android:key="pref_galaxy_buds_pro_voice_detect_duration"
+                android:summary="%s"
+                android:title="@string/prefs_voice_detect_duration" />
+
+        </PreferenceCategory>
+        <PreferenceCategory android:title="@string/prefs_ambient_sound">
+
+
+            <SwitchPreferenceCompat
+                android:defaultValue="false"
+                android:icon="@drawable/ic_phone"
+                android:key="pref_galaxy_buds_ambient_mode_during_call"
+                android:layout="@layout/preference_checkbox"
+                android:summary="@string/prefs_ambient_sound_during_call_summary"
+                android:title="@string/prefs_ambient_sound_during_call_title" />
+
+            <SwitchPreferenceCompat
+                android:defaultValue="false"
+                android:icon="@drawable/ic_hearing"
+                android:key="pref_galaxy_buds_ambient_sound"
+                android:layout="@layout/preference_checkbox"
+                android:title="@string/prefs_customize_ambient_sound_summary" />
+
+            <SeekBarPreference
+                android:defaultValue="1"
+                android:dependency="pref_galaxy_buds_ambient_sound"
+                android:icon="@drawable/ic_volume_up"
+                android:key="pref_galaxy_buds_pro_ambient_volume_right"
+                android:max="4"
+                android:min="0"
+                android:title="@string/prefs_ambient_volume_right"
+                app:showSeekBarValue="false" />
+
+            <SeekBarPreference
+                android:defaultValue="1"
+                android:dependency="pref_galaxy_buds_ambient_sound"
+                android:icon="@drawable/ic_volume_up"
+                android:key="pref_galaxy_buds_pro_ambient_volume_left"
+                android:max="4"
+                android:min="0"
+                android:title="@string/prefs_ambient_volume_left"
+                app:showSeekBarValue="false" />
+
+            <SeekBarPreference
+                android:defaultValue="1"
+                android:dependency="pref_galaxy_buds_ambient_sound"
+                android:icon="@drawable/ic_surround"
+                android:key="pref_galaxy_buds_pro_ambient_sound_tone"
+                android:max="4"
+                android:min="0"
+                android:summary="@string/pref_ambient_sound_tone_summary"
+                android:title="@string/pref_ambient_sound_tone"
+                app:showSeekBarValue="false" />
+        </PreferenceCategory>
+    </PreferenceScreen>
+    <ListPreference
+        android:defaultValue="0"
+        android:entries="@array/galaxy_buds_live_equalizer_modes"
+        android:entryValues="@array/galaxy_buds_live_equalizer_values"
+        android:icon="@drawable/ic_equalizer"
+        android:key="pref_galaxy_buds_equalizer_mode"
+        android:summary="%s"
+        android:title="@string/prefs_equalizer_preset" />
+
+    <PreferenceScreen
+        android:icon="@drawable/ic_touch"
+        android:key="prefs_galaxy_touch_options"
+        android:persistent="false"
+        android:title="@string/prefs_galaxy_touch_options">
+        <PreferenceCategory android:title="@string/prefs_galaxy_touch_options">
+            <SwitchPreferenceCompat
+                android:defaultValue="true"
+                android:icon="@drawable/ic_touch"
+                android:key="pref_galaxy_buds_lock_touch"
+                android:layout="@layout/preference_checkbox"
+                android:title="@string/prefs_touch_lock_buds2" />
+            <ListPreference
+                android:defaultValue="0"
+                android:dependency="pref_galaxy_buds_lock_touch"
+                android:entries="@array/galaxy_pro_touch_options_left"
+                android:entryValues="@array/galaxy_pro_touch_options_values"
+                android:icon="@drawable/ic_switch_left"
+                android:key="pref_galaxy_buds_touch_left"
+                android:summary="%s"
+                android:title="@string/prefs_left" />
+
+            <ListPreference
+                android:defaultValue="0"
+                android:dependency="pref_galaxy_buds_lock_touch"
+                android:entries="@array/galaxy_pro_touch_switch_controls_options"
+                android:entryValues="@array/galaxy_pro_touch_switch_controls_values"
+                android:key="pref_galaxy_buds_touch_left_switch"
+                android:summary="%s"
+                android:title="@string/prefs_switch_control_left" />
+
+            <ListPreference
+                android:defaultValue="0"
+                android:dependency="pref_galaxy_buds_lock_touch"
+                android:entries="@array/galaxy_pro_touch_options_right"
+                android:entryValues="@array/galaxy_pro_touch_options_values"
+                android:icon="@drawable/ic_switch_right"
+                android:key="pref_galaxy_buds_touch_right"
+                android:summary="%s"
+                android:title="@string/prefs_right" />
+            <ListPreference
+                android:defaultValue="0"
+                android:dependency="pref_galaxy_buds_lock_touch"
+                android:entries="@array/galaxy_pro_touch_switch_controls_options"
+                android:entryValues="@array/galaxy_pro_touch_switch_controls_values"
+                android:key="pref_galaxy_buds_touch_right_switch"
+                android:summary="%s"
+                android:title="@string/prefs_switch_control_right" />
+
+            <SwitchPreferenceCompat
+                android:defaultValue="false"
+                android:dependency="pref_galaxy_buds_lock_touch"
+                android:icon="@drawable/ic_touch"
+                android:key="pref_galaxy_pro_double_tap_edge"
+                android:layout="@layout/preference_checkbox"
+                android:summary="@string/prefs_double_tap_edge_summary"
+                android:title="@string/prefs_double_tap_edge" />
+        </PreferenceCategory>
+    </PreferenceScreen>
+
+    <PreferenceScreen
+        android:icon="@drawable/ic_settings"
+        android:key="prefs_settings"
+        android:persistent="false"
+        android:title="@string/title_activity_settings">
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:icon="@drawable/ic_hearing"
+            android:key="pref_galaxy_buds_pro_in_ear_detection"
+            android:layout="@layout/preference_checkbox"
+            android:summary="@string/prefs_in_ear_detection_summary"
+            android:title="@string/nothing_prefs_inear_title" />
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:icon="@drawable/ic_auto_awesome"
+            android:key="prefs_galaxy_buds_seamless_connection"
+            android:layout="@layout/preference_checkbox"
+            android:summary="@string/prefs_seamless_connection_switch_summary"
+            android:title="@string/prefs_seamless_connection_switch_title" />
+
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:icon="@drawable/ic_hearing"
+            android:key="pref_galaxy_buds_noise_controls_with_one_earbud"
+            android:layout="@layout/preference_checkbox"
+            android:summary="@string/prefs_noise_control_with_one_earbud_summary"
+            android:title="@string/prefs_noise_control_with_one_earbud" />
+
+        <SeekBarPreference
+            android:defaultValue="50"
+            android:icon="@drawable/ic_volume_up"
+            android:key="pref_galaxy_buds_pro_balance"
+            android:max="32"
+            android:min="0"
+            android:title="@string/pref_balance"
+            app:defaultValue="16"
+            app:showSeekBarValue="true" />
+
+        <PreferenceCategory android:title="@string/prefs_galaxy_buds_experimental">
+            <SwitchPreferenceCompat
+                android:defaultValue="false"
+                android:icon="@drawable/ic_videogame"
+                android:key="pref_galaxy_buds_game_mode"
+                android:layout="@layout/preference_checkbox"
+                android:summary="@string/prefs_game_mode_summary"
+                android:title="@string/prefs_game_mode" />
+        </PreferenceCategory>
+
+        <!-- <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:icon="@drawable/ic_graphic_eq"
+            android:key="pref_galaxy_buds_pro_read_notifications_outloud"
+            android:layout="@layout/preference_checkbox"
+            android:title="@string/prefs_read_notification_outloud" />
+            -->
+
+    </PreferenceScreen>
+
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_galaxy_buds_live.xml b/app/src/main/res/xml/devicesettings_galaxy_buds_live.xml
new file mode 100644
index 0000000..08e56d1
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_galaxy_buds_live.xml
@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <SwitchPreferenceCompat
+        android:defaultValue="false"
+        android:icon="@drawable/ic_hearing"
+        android:key="pref_galaxy_buds_live_anc"
+        android:layout="@layout/preference_checkbox"
+        android:summary="@string/prefs_active_noise_cancelling_summary"
+        android:title="@string/prefs_active_noise_cancelling" />
+
+
+    <ListPreference
+        android:entries="@array/galaxy_buds_live_equalizer_modes"
+        android:entryValues="@array/galaxy_buds_live_equalizer_values"
+        android:icon="@drawable/ic_equalizer"
+        android:key="pref_galaxy_buds_equalizer_mode"
+        android:summary="%s"
+        android:title="@string/prefs_equalizer_preset" />
+
+    <PreferenceScreen
+        android:icon="@drawable/ic_touch"
+        android:key="prefs_galaxy_touch_options"
+        android:persistent="false"
+        android:title="@string/prefs_galaxy_touch_options">
+        <PreferenceCategory android:title="@string/prefs_galaxy_touch_options">
+            <SwitchPreferenceCompat
+                android:defaultValue="false"
+                android:disableDependentsState="true"
+                android:icon="@drawable/ic_lock_open"
+                android:key="pref_galaxy_buds_lock_touch"
+                android:layout="@layout/preference_checkbox"
+                android:summary="@string/prefs_touch_lock_summary"
+                android:title="@string/prefs_touch_lock" />
+            <ListPreference
+                android:dependency="pref_galaxy_buds_lock_touch"
+                android:entries="@array/galaxy_buds_live_touch_options_left"
+                android:entryValues="@array/galaxy_buds_live_touch_options_values"
+                android:icon="@drawable/ic_touch"
+                android:key="pref_galaxy_buds_touch_left"
+                android:summary="%s"
+                android:title="@string/prefs_left" />
+            <ListPreference
+                android:dependency="pref_galaxy_buds_lock_touch"
+                android:entries="@array/galaxy_buds_live_touch_options_right"
+                android:entryValues="@array/galaxy_buds_live_touch_options_values"
+                android:icon="@drawable/ic_touch"
+                android:key="pref_galaxy_buds_touch_right"
+                android:summary="%s"
+                android:title="@string/prefs_right" />
+        </PreferenceCategory>
+    </PreferenceScreen>
+    <PreferenceCategory android:title="@string/prefs_galaxy_buds_experimental">
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:icon="@drawable/ic_videogame"
+            android:key="pref_galaxy_buds_game_mode"
+            android:layout="@layout/preference_checkbox"
+            android:summary="@string/prefs_game_mode_summary"
+            android:title="@string/prefs_game_mode" />
+
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:icon="@drawable/ic_engineering"
+            android:key="pref_galaxy_buds_live_pressure_relief"
+            android:layout="@layout/preference_checkbox"
+            android:summary="@string/pressure_relief_summary"
+            android:title="@string/prefs_pressure_relief" />
+    </PreferenceCategory>
+
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_galaxy_buds_pro.xml b/app/src/main/res/xml/devicesettings_galaxy_buds_pro.xml
new file mode 100644
index 0000000..8b564a2
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_galaxy_buds_pro.xml
@@ -0,0 +1,247 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+    <PreferenceScreen
+        android:icon="@drawable/ic_surround"
+        android:key="pref_galaxy_buds_pro_noise_control_preference"
+        android:persistent="false"
+        android:title="@string/prefs_noise_control">
+        <PreferenceCategory android:title="@string/prefs_noise_control">
+
+            <ListPreference
+                android:defaultValue="0"
+                android:entries="@array/galaxy_pro_noise_controls_options"
+                android:entryValues="@array/galaxy_pro_noise_controls_options_values"
+                android:icon="@drawable/ic_surround"
+                android:key="pref_galaxy_buds_pro_noise_control"
+                android:summary="%s"
+                android:title="@string/prefs_noise_control" />
+
+            <ListPreference
+                android:defaultValue="0"
+                android:dependency="pref_galaxy_buds_pro_noise_control"
+                android:entries="@array/galaxy_pro_anc_level_options"
+                android:entryValues="@array/galaxy_pro_anc_level_values"
+                android:icon="@drawable/ic_surround"
+                android:key="pref_galaxy_buds_pro_anc_level"
+                android:summary="%s"
+                android:title="@string/prefs_active_noise_cancelling_level" />
+
+
+            <SeekBarPreference
+                android:icon="@drawable/ic_volume_up"
+                android:key="pref_galaxy_buds_ambient_volume"
+                android:max="3"
+                android:min="0"
+                android:title="@string/prefs_ambient_volume"
+                app:showSeekBarValue="false" />
+
+        </PreferenceCategory>
+    </PreferenceScreen>
+
+    <PreferenceScreen
+        android:icon="@drawable/ic_hearing"
+        android:key="prefs_accessibility"
+        android:persistent="false"
+        android:title="@string/prefs_ambient_settings_title">
+
+        <PreferenceCategory android:title="@string/prefs_voice_detect">
+            <SwitchPreferenceCompat
+                android:defaultValue="false"
+                android:icon="@drawable/ic_voice"
+                android:key="pref_galaxy_buds_pro_voice_detect"
+                android:layout="@layout/preference_checkbox"
+                android:summary="@string/prefs_voice_detect_summary"
+                android:title="@string/prefs_voice_detect" />
+            <ListPreference
+                android:defaultValue="5"
+                android:dependency="pref_galaxy_buds_pro_voice_detect"
+                android:entries="@array/galaxy_pro_voice_detect_duration_options"
+                android:entryValues="@array/galaxy_pro_voice_detect_duration_values"
+                android:icon="@drawable/ic_timer"
+                android:key="pref_galaxy_buds_pro_voice_detect_duration"
+                android:summary="%s"
+                android:title="@string/prefs_voice_detect_duration" />
+
+        </PreferenceCategory>
+        <PreferenceCategory android:title="@string/prefs_ambient_sound">
+
+
+            <SwitchPreferenceCompat
+                android:defaultValue="false"
+                android:icon="@drawable/ic_phone"
+                android:key="pref_galaxy_buds_ambient_mode_during_call"
+                android:layout="@layout/preference_checkbox"
+                android:summary="@string/prefs_ambient_sound_during_call_summary"
+                android:title="@string/prefs_ambient_sound_during_call_title" />
+
+            <SwitchPreferenceCompat
+                android:defaultValue="false"
+                android:icon="@drawable/ic_hearing"
+                android:key="pref_galaxy_buds_ambient_sound"
+                android:layout="@layout/preference_checkbox"
+                android:title="@string/prefs_customize_ambient_sound_summary" />
+
+            <SeekBarPreference
+                android:defaultValue="1"
+                android:dependency="pref_galaxy_buds_ambient_sound"
+                android:icon="@drawable/ic_volume_up"
+                android:key="pref_galaxy_buds_pro_ambient_volume_right"
+                android:max="4"
+                android:min="0"
+                android:title="@string/prefs_ambient_volume_right"
+                app:showSeekBarValue="false" />
+
+            <SeekBarPreference
+                android:defaultValue="1"
+                android:dependency="pref_galaxy_buds_ambient_sound"
+                android:icon="@drawable/ic_volume_up"
+                android:key="pref_galaxy_buds_pro_ambient_volume_left"
+                android:max="4"
+                android:min="0"
+                android:title="@string/prefs_ambient_volume_left"
+                app:showSeekBarValue="false" />
+
+            <SeekBarPreference
+                android:defaultValue="1"
+                android:dependency="pref_galaxy_buds_ambient_sound"
+                android:icon="@drawable/ic_surround"
+                android:key="pref_galaxy_buds_pro_ambient_sound_tone"
+                android:max="4"
+                android:min="0"
+                android:summary="@string/pref_ambient_sound_tone_summary"
+                android:title="@string/pref_ambient_sound_tone"
+                app:showSeekBarValue="false" />
+        </PreferenceCategory>
+    </PreferenceScreen>
+
+    <ListPreference
+        android:defaultValue="0"
+        android:entries="@array/galaxy_buds_live_equalizer_modes"
+        android:entryValues="@array/galaxy_buds_live_equalizer_values"
+        android:icon="@drawable/ic_equalizer"
+        android:key="pref_galaxy_buds_equalizer_mode"
+        android:summary="%s"
+        android:title="@string/prefs_equalizer_preset" />
+
+    <PreferenceScreen
+        android:icon="@drawable/ic_touch"
+        android:key="prefs_galaxy_touch_options"
+        android:persistent="false"
+        android:title="@string/prefs_galaxy_touch_options">
+        <PreferenceCategory android:title="@string/prefs_galaxy_touch_options">
+            <SwitchPreferenceCompat
+                android:defaultValue="false"
+                android:disableDependentsState="true"
+                android:icon="@drawable/ic_lock_open"
+                android:key="pref_galaxy_buds_lock_touch"
+                android:layout="@layout/preference_checkbox"
+                android:summary="@string/prefs_touch_lock_summary"
+                android:title="@string/prefs_touch_lock" />
+            <ListPreference
+                android:defaultValue="0"
+                android:dependency="pref_galaxy_buds_lock_touch"
+                android:entries="@array/galaxy_pro_touch_options_left"
+                android:entryValues="@array/galaxy_pro_touch_options_values"
+                android:icon="@drawable/ic_touch"
+                android:key="pref_galaxy_buds_touch_left"
+                android:summary="%s"
+                android:title="@string/prefs_left" />
+
+            <ListPreference
+                android:defaultValue="0"
+                android:dependency="pref_galaxy_buds_lock_touch"
+                android:entries="@array/galaxy_pro_touch_switch_controls_options"
+                android:entryValues="@array/galaxy_pro_touch_switch_controls_values"
+                android:icon="@drawable/ic_switch_left"
+                android:key="pref_galaxy_buds_touch_left_switch"
+                android:summary="%s"
+                android:title="@string/prefs_switch_control_left" />
+
+            <ListPreference
+                android:defaultValue="0"
+                android:dependency="pref_galaxy_buds_lock_touch"
+                android:entries="@array/galaxy_pro_touch_options_right"
+                android:entryValues="@array/galaxy_pro_touch_options_values"
+                android:icon="@drawable/ic_touch"
+                android:key="pref_galaxy_buds_touch_right"
+                android:summary="%s"
+                android:title="@string/prefs_right" />
+            <ListPreference
+                android:defaultValue="0"
+                android:dependency="pref_galaxy_buds_lock_touch"
+                android:entries="@array/galaxy_pro_touch_switch_controls_options"
+                android:entryValues="@array/galaxy_pro_touch_switch_controls_values"
+                android:icon="@drawable/ic_switch_right"
+                android:key="pref_galaxy_buds_touch_right_switch"
+                android:summary="%s"
+                android:title="@string/prefs_switch_control_right" />
+
+            <SwitchPreferenceCompat
+                android:defaultValue="false"
+                android:dependency="pref_galaxy_buds_lock_touch"
+                android:icon="@drawable/ic_touch"
+                android:key="pref_galaxy_pro_double_tap_edge"
+                android:layout="@layout/preference_checkbox"
+                android:summary="@string/prefs_double_tap_edge_summary"
+                android:title="@string/prefs_double_tap_edge" />
+        </PreferenceCategory>
+    </PreferenceScreen>
+
+    <PreferenceScreen
+        android:icon="@drawable/ic_settings"
+        android:key="prefs_settings"
+        android:persistent="false"
+        android:title="@string/title_activity_settings">
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:icon="@drawable/ic_hearing"
+            android:key="pref_galaxy_buds_pro_in_ear_detection"
+            android:layout="@layout/preference_checkbox"
+            android:summary="@string/prefs_in_ear_detection_summary"
+            android:title="@string/nothing_prefs_inear_title" />
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:icon="@drawable/ic_auto_awesome"
+            android:key="pref_galaxy_buds_pro_seamless_connection"
+            android:layout="@layout/preference_checkbox"
+            android:summary="@string/prefs_seamless_connection_switch_summary"
+            android:title="@string/prefs_seamless_connection_switch_title" />
+
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:icon="@drawable/ic_hearing"
+            android:key="pref_galaxy_buds_noise_controls_with_one_earbud"
+            android:layout="@layout/preference_checkbox"
+            android:summary="@string/prefs_noise_control_with_one_earbud_summary"
+            android:title="@string/prefs_noise_control_with_one_earbud" />
+
+        <SeekBarPreference
+            android:defaultValue="50"
+            android:icon="@drawable/ic_volume_up"
+            android:key="pref_galaxy_buds_pro_balance"
+            android:max="32"
+            android:min="0"
+            android:title="@string/pref_balance"
+            app:defaultValue="16"
+            app:showSeekBarValue="true" />
+
+        <PreferenceCategory android:title="@string/prefs_galaxy_buds_experimental">
+            <SwitchPreferenceCompat
+                android:defaultValue="false"
+                android:icon="@drawable/ic_videogame"
+                android:key="pref_galaxy_buds_game_mode"
+                android:layout="@layout/preference_checkbox"
+                android:summary="@string/prefs_game_mode_summary"
+                android:title="@string/prefs_game_mode" />
+        </PreferenceCategory>
+
+        <!-- <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:icon="@drawable/ic_graphic_eq"
+            android:key="pref_galaxy_buds_pro_read_notifications_outloud"
+            android:title="@string/prefs_read_notification_outloud" />
+            -->
+
+    </PreferenceScreen>
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_garmin_agps.xml b/app/src/main/res/xml/devicesettings_garmin_agps.xml
new file mode 100644
index 0000000..2c93e06
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_garmin_agps.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+    <PreferenceCategory
+        android:icon="@drawable/ic_gps_edit"
+        android:key="pref_header_agps"
+        android:title="@string/pref_agps_header">
+
+        <Preference
+            android:key="pref_garmin_agps_help"
+            android:summary="@string/pref_garmin_agps_help"
+            app:iconSpaceReserved="false" />
+
+        <Preference
+            android:icon="@drawable/ic_folder"
+            android:key="garmin_agps_folder"
+            android:title="@string/folder" />
+
+    </PreferenceCategory>
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_garmin_realtime_settings.xml b/app/src/main/res/xml/devicesettings_garmin_realtime_settings.xml
new file mode 100644
index 0000000..89c718c
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_garmin_realtime_settings.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <Preference
+        android:icon="@drawable/ic_settings"
+        android:key="garmin_realtime_settings"
+        android:title="@string/realtime_settings" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_goal_notification.xml b/app/src/main/res/xml/devicesettings_goal_notification.xml
new file mode 100644
index 0000000..7a40a1e
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_goal_notification.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <SwitchPreferenceCompat
+        android:defaultValue="false"
+        android:icon="@drawable/ic_star_gray"
+        android:key="fitness_goal_notification"
+        android:layout="@layout/preference_checkbox"
+        android:summary="@string/mi2_prefs_goal_notification_summary"
+        android:title="@string/mi2_prefs_goal_notification" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_goal_secondary.xml b/app/src/main/res/xml/devicesettings_goal_secondary.xml
new file mode 100644
index 0000000..bfb1c47
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_goal_secondary.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+    <ListPreference
+        android:defaultValue="standing_time"
+        android:entries="@array/goal_fitness_secondary_goal_entries"
+        android:entryValues="@array/goal_fitness_secondary_goal_values"
+        android:icon="@drawable/ic_star_gray"
+        android:key="fitness_goal_secondary"
+        android:title="@string/pref_title_goal_secondary"
+        app:useSimpleSummaryProvider="true" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_gps_agps.xml b/app/src/main/res/xml/devicesettings_gps_agps.xml
new file mode 100644
index 0000000..2af9a55
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_gps_agps.xml
@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceScreen
+        android:icon="@drawable/ic_gps_location"
+        android:key="pref_header_gps"
+        android:persistent="false"
+        android:title="@string/pref_gps_header">
+
+        <PreferenceCategory
+            android:key="pref_gps_header_category"
+            android:title="@string/pref_gps_header">
+
+            <ListPreference
+                android:defaultValue=""
+                android:entries="@array/pref_gps_mode_preset"
+                android:entryValues="@array/pref_gps_mode_preset_values"
+                android:key="pref_gps_mode_preset"
+                android:summary="%s"
+                android:title="@string/pref_gps_mode_preset" />
+
+            <ListPreference
+                android:defaultValue=""
+                android:entries="@array/pref_gps_band"
+                android:entryValues="@array/pref_gps_band_values"
+                android:key="pref_gps_band"
+                android:summary="%s"
+                android:title="@string/pref_gps_band" />
+
+            <ListPreference
+                android:defaultValue=""
+                android:entries="@array/pref_gps_combination"
+                android:entryValues="@array/pref_gps_combination_values"
+                android:key="pref_gps_combination"
+                android:summary="%s"
+                android:title="@string/pref_gps_combination" />
+
+            <ListPreference
+                android:defaultValue=""
+                android:entries="@array/pref_gps_satellite_search"
+                android:entryValues="@array/pref_gps_satellite_search_values"
+                android:key="pref_gps_satellite_search"
+                android:summary="%s"
+                android:title="@string/pref_gps_satellite_search" />
+        </PreferenceCategory>
+
+        <PreferenceCategory
+            android:key="pref_header_agps"
+            android:title="@string/pref_agps_header">
+
+            <SwitchPreferenceCompat
+                android:defaultValue="false"
+                android:key="pref_agps_expiry_reminder_enabled"
+                android:layout="@layout/preference_checkbox"
+                android:title="@string/pref_agps_expiry_reminder_enabled" />
+
+            <nodomain.freeyourgadget.gadgetbridge.util.XTimePreference
+                android:defaultValue="20:00"
+                android:dependency="pref_agps_expiry_reminder_enabled"
+                android:key="pref_agps_expiry_reminder_time"
+                android:title="@string/pref_agps_expiry_reminder_time" />
+
+            <Preference
+                android:key="pref_agps_update_time"
+                android:title="@string/pref_agps_update_time" />
+
+            <Preference
+                android:key="pref_agps_expire_time"
+                android:title="@string/pref_agps_expire_time" />
+        </PreferenceCategory>
+    </PreferenceScreen>
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_header_apps.xml b/app/src/main/res/xml/devicesettings_header_apps.xml
new file mode 100644
index 0000000..c93fe84
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_header_apps.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceCategory
+        android:key="pref_header_apps"
+        android:title="@string/qhybrid_title_apps" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_header_authentication.xml b/app/src/main/res/xml/devicesettings_header_authentication.xml
new file mode 100644
index 0000000..9050be3
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_header_authentication.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceCategory
+        android:key="pref_header_authentication"
+        android:title="@string/pref_header_authentication" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_header_calendar.xml b/app/src/main/res/xml/devicesettings_header_calendar.xml
new file mode 100644
index 0000000..643e04d
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_header_calendar.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceCategory
+        android:key="pref_header_calendar"
+        android:title="@string/pref_header_calendar" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_header_connection.xml b/app/src/main/res/xml/devicesettings_header_connection.xml
new file mode 100644
index 0000000..280626d
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_header_connection.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceCategory
+        android:key="pref_header_connection"
+        android:title="@string/pref_header_connection" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_header_developer.xml b/app/src/main/res/xml/devicesettings_header_developer.xml
new file mode 100644
index 0000000..961e987
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_header_developer.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceCategory
+        android:key="pref_header_development"
+        android:title="@string/pref_header_development" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_header_display.xml b/app/src/main/res/xml/devicesettings_header_display.xml
new file mode 100644
index 0000000..e9a3de6
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_header_display.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceCategory
+        android:key="pref_header_display"
+        android:title="@string/pref_header_display" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_header_health.xml b/app/src/main/res/xml/devicesettings_header_health.xml
new file mode 100644
index 0000000..b82579b
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_header_health.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceCategory
+        android:key="pref_header_health"
+        android:title="@string/pref_header_health" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_header_notifications.xml b/app/src/main/res/xml/devicesettings_header_notifications.xml
new file mode 100644
index 0000000..3dee611
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_header_notifications.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceCategory
+        android:key="pref_header_notifications"
+        android:title="@string/pref_header_notifications" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_header_other.xml b/app/src/main/res/xml/devicesettings_header_other.xml
new file mode 100644
index 0000000..637c27b
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_header_other.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceCategory
+        android:key="pref_header_other"
+        android:title="@string/pref_header_other" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_header_sound_vibration.xml b/app/src/main/res/xml/devicesettings_header_sound_vibration.xml
new file mode 100644
index 0000000..9042ce7
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_header_sound_vibration.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceCategory
+        android:key="pref_header_sound_vibration"
+        android:title="@string/pref_header_sound_vibration" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_header_system.xml b/app/src/main/res/xml/devicesettings_header_system.xml
new file mode 100644
index 0000000..363230c
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_header_system.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceCategory
+        android:key="pref_header_system"
+        android:title="@string/pref_header_system" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_header_time.xml b/app/src/main/res/xml/devicesettings_header_time.xml
new file mode 100644
index 0000000..59eff04
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_header_time.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceCategory
+        android:key="pref_header_time"
+        android:title="@string/pref_header_time" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_header_workout.xml b/app/src/main/res/xml/devicesettings_header_workout.xml
new file mode 100644
index 0000000..102063e
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_header_workout.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceCategory
+        android:key="pref_header_workout"
+        android:title="@string/pref_header_workout" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_headphones.xml b/app/src/main/res/xml/devicesettings_headphones.xml
new file mode 100644
index 0000000..d8f33cd
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_headphones.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+    <SwitchPreferenceCompat
+        android:defaultValue="false"
+        android:icon="@drawable/ic_phone"
+        android:key="pref_auto_reply_phonecall"
+        android:summary="@string/pref_auto_reply_calls_summary"
+        android:title="@string/pref_auto_reply_calls_title"/>
+    <EditTextPreference
+        android:icon="@drawable/ic_timer"
+        android:digits="123"
+        android:dependency="pref_auto_reply_phonecall"
+        android:inputType="numberDecimal"
+        android:key="pref_auto_reply_phonecall_delay"
+        android:summary="@string/pref_auto_reply_calls_delay_summary"
+        android:title="@string/pref_auto_reply_calls_delay_title"
+        app:defaultValue="15" />
+    <SwitchPreferenceCompat
+        android:defaultValue="false"
+        android:icon="@drawable/ic_voice"
+        android:key="pref_speak_notifications_aloud"
+        android:summary="@string/pref_speak_notifications_aloud_summary"
+        android:title="@string/pref_speak_notifications_aloud_title" />
+    <SwitchPreferenceCompat
+        android:defaultValue="false"
+        android:icon="@drawable/ic_voice"
+        android:key="pref_speak_notifications_focus_exclusive"
+        android:summaryOff="@string/pref_speak_notifications_focus_exclusive_summary_off"
+        android:summaryOn="@string/pref_speak_notifications_focus_exclusive_summary_on"
+        android:title="@string/pref_speak_notifications_focus_exclusive_title" />
+</androidx.preference.PreferenceScreen>
\ No newline at end of file
diff --git a/app/src/main/res/xml/devicesettings_heartrate_automatic_enable.xml b/app/src/main/res/xml/devicesettings_heartrate_automatic_enable.xml
new file mode 100644
index 0000000..9f67a72
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_heartrate_automatic_enable.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <SwitchPreferenceCompat
+        android:defaultValue="false"
+        android:icon="@drawable/ic_heartrate"
+        android:key="heartrate_automatic_enable"
+        android:layout="@layout/preference_checkbox"
+        android:title="@string/pref_heartrate_automatic_enable" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_heartrate_sleep.xml b/app/src/main/res/xml/devicesettings_heartrate_sleep.xml
new file mode 100644
index 0000000..1df7f21
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_heartrate_sleep.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceScreen
+        android:icon="@drawable/ic_heartrate"
+        android:key="heartrate_monitoring_key"
+        android:persistent="false"
+        android:summary="@string/mi2_prefs_heart_rate_monitoring_summary"
+        android:title="@string/mi2_prefs_heart_rate_monitoring">
+
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:icon="@drawable/ic_activity_sleep"
+            android:key="heartrate_sleep_detection"
+            android:layout="@layout/preference_checkbox"
+            android:title="@string/miband_prefs_hr_sleep_detection" />
+
+        <ListPreference
+            android:defaultValue="0"
+            android:entries="@array/prefs_heartrate_measurement_interval"
+            android:entryValues="@array/prefs_heartrate_measurement_interval_values"
+            android:icon="@drawable/ic_heartrate"
+            android:key="heartrate_measurement_interval"
+            android:summary="%s"
+            android:title="@string/prefs_title_heartrate_measurement_interval" />
+    </PreferenceScreen>
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_heartrate_sleep_activity.xml b/app/src/main/res/xml/devicesettings_heartrate_sleep_activity.xml
new file mode 100644
index 0000000..14c92fb
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_heartrate_sleep_activity.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceScreen
+        android:icon="@drawable/ic_heartrate"
+        android:key="heartrate_monitoring_key"
+        android:persistent="false"
+        android:summary="@string/mi2_prefs_heart_rate_monitoring_summary"
+        android:title="@string/mi2_prefs_heart_rate_monitoring">
+
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:icon="@drawable/ic_activity_sleep"
+            android:key="heartrate_sleep_detection"
+            android:layout="@layout/preference_checkbox"
+            android:title="@string/miband_prefs_hr_sleep_detection" />
+
+        <ListPreference
+            android:defaultValue="0"
+            android:entries="@array/prefs_heartrate_measurement_interval"
+            android:entryValues="@array/prefs_heartrate_measurement_interval_values"
+            android:icon="@drawable/ic_heartrate"
+            android:key="heartrate_measurement_interval"
+            android:summary="%s"
+            android:title="@string/prefs_title_heartrate_measurement_interval" />
+
+        <!-- Activity Monitoring -->
+
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:icon="@drawable/ic_activity_unknown_small"
+            android:key="heartrate_activity_monitoring"
+            android:layout="@layout/preference_checkbox"
+            android:summary="@string/prefs_activity_monitoring_description"
+            android:title="@string/prefs_activity_monitoring_title" />
+
+    </PreferenceScreen>
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_heartrate_sleep_alert_activity_stress.xml b/app/src/main/res/xml/devicesettings_heartrate_sleep_alert_activity_stress.xml
new file mode 100644
index 0000000..ae2048f
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_heartrate_sleep_alert_activity_stress.xml
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceScreen
+        android:icon="@drawable/ic_heartrate"
+        android:key="heartrate_monitoring_key"
+        android:persistent="false"
+        android:summary="@string/mi2_prefs_heart_rate_monitoring_alerts_summary"
+        android:title="@string/mi2_prefs_heart_rate_monitoring">
+
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:icon="@drawable/ic_activity_sleep"
+            android:key="heartrate_sleep_detection"
+            android:layout="@layout/preference_checkbox"
+            android:title="@string/miband_prefs_hr_sleep_detection" />
+
+        <ListPreference
+            android:defaultValue="0"
+            android:entries="@array/prefs_heartrate_measurement_interval"
+            android:entryValues="@array/prefs_heartrate_measurement_interval_values"
+            android:icon="@drawable/ic_heartrate"
+            android:key="heartrate_measurement_interval"
+            android:summary="%s"
+            android:title="@string/prefs_title_heartrate_measurement_interval" />
+
+        <!-- Activity Monitoring -->
+
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:icon="@drawable/ic_activity_unknown_small"
+            android:key="heartrate_activity_monitoring"
+            android:layout="@layout/preference_checkbox"
+            android:summary="@string/prefs_activity_monitoring_description"
+            android:title="@string/prefs_activity_monitoring_title" />
+
+        <!-- Heart Rate Alerts -->
+
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:icon="@drawable/ic_warning_gray"
+            android:key="heartrate_alert_enabled"
+            android:layout="@layout/preference_checkbox"
+            android:summary="@string/prefs_heartrate_alert_experimental_description"
+            android:title="@string/prefs_heartrate_alert_experimental_title" />
+
+        <ListPreference
+            android:defaultValue="0"
+            android:dependency="heartrate_alert_enabled"
+            android:entries="@array/prefs_miband_heartrate_alert_threshold"
+            android:entryValues="@array/prefs_miband_heartrate_alert_threshold_values"
+            android:icon="@drawable/ic_heartrate"
+            android:key="heartrate_alert_threshold"
+            android:summary="%s"
+            android:title="@string/prefs_heartrate_alert_threshold" />
+
+        <!-- Stress Monitoring -->
+
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:icon="@drawable/ic_mood_bad"
+            android:key="heartrate_stress_monitoring"
+            android:layout="@layout/preference_checkbox"
+            android:summary="@string/prefs_stress_monitoring_description"
+            android:title="@string/prefs_stress_monitoring_title" />
+    </PreferenceScreen>
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_heartrate_sleep_alert_activity_stress_spo2.xml b/app/src/main/res/xml/devicesettings_heartrate_sleep_alert_activity_stress_spo2.xml
new file mode 100644
index 0000000..d9faee3
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_heartrate_sleep_alert_activity_stress_spo2.xml
@@ -0,0 +1,132 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceScreen
+        android:icon="@drawable/ic_heartrate"
+        android:key="pref_screen_heartrate_monitoring"
+        android:persistent="false"
+        android:summary="@string/mi2_prefs_heart_rate_monitoring_alerts_summary"
+        android:title="@string/mi2_prefs_heart_rate_monitoring">
+
+        <PreferenceCategory
+            android:key="pref_key_header_heartrate_sleep"
+            android:title="@string/pref_header_heartrate_sleep">
+
+            <SwitchPreferenceCompat
+                android:defaultValue="false"
+                android:icon="@drawable/ic_activity_sleep"
+                android:key="heartrate_sleep_detection"
+                android:layout="@layout/preference_checkbox"
+                android:title="@string/miband_prefs_hr_sleep_detection" />
+
+            <SwitchPreferenceCompat
+                android:defaultValue="false"
+                android:icon="@drawable/ic_activity_sleep"
+                android:key="heartrate_sleep_breathing_quality_monitoring"
+                android:layout="@layout/preference_checkbox"
+                android:title="@string/pref_sleep_breathing_quality_monitoring" />
+        </PreferenceCategory>
+
+        <PreferenceCategory
+            android:key="pref_key_header_heartrate_allday"
+            android:title="@string/pref_header_heartrate_allday">
+
+            <ListPreference
+                android:defaultValue="0"
+                android:entries="@array/prefs_heartrate_measurement_interval"
+                android:entryValues="@array/prefs_heartrate_measurement_interval_values"
+                android:icon="@drawable/ic_heartrate"
+                android:key="heartrate_measurement_interval"
+                android:summary="%s"
+                android:title="@string/prefs_title_heartrate_measurement_interval" />
+        </PreferenceCategory>
+
+        <!-- Activity Monitoring -->
+
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:icon="@drawable/ic_activity_unknown_small"
+            android:key="heartrate_activity_monitoring"
+            android:layout="@layout/preference_checkbox"
+            android:summary="@string/prefs_activity_monitoring_description"
+            android:title="@string/prefs_activity_monitoring_title" />
+
+        <!-- Heart Rate Alerts -->
+
+        <PreferenceCategory
+            android:key="pref_key_header_heartrate_alerts"
+            android:title="@string/pref_header_heartrate_alerts">
+
+            <ListPreference
+                android:defaultValue="0"
+                android:entries="@array/prefs_miband_heartrate_high_alert_threshold_with_off"
+                android:entryValues="@array/prefs_miband_heartrate_high_alert_threshold_with_off_values"
+                android:icon="@drawable/ic_heartrate"
+                android:key="heartrate_alert_threshold"
+                android:summary="%s"
+                android:title="@string/prefs_heartrate_alert_high_threshold" />
+
+            <ListPreference
+                android:defaultValue="0"
+                android:entries="@array/prefs_miband_heartrate_low_alert_threshold"
+                android:entryValues="@array/prefs_miband_heartrate_low_alert_threshold_values"
+                android:icon="@drawable/ic_heartrate"
+                android:key="heartrate_alert_low_threshold"
+                android:summary="%s"
+                android:title="@string/prefs_heartrate_alert_low_threshold" />
+
+            <ListPreference
+                android:defaultValue="0"
+                android:entries="@array/prefs_miband_heartrate_high_active_alert_threshold_with_off"
+                android:entryValues="@array/prefs_miband_heartrate_high_active_alert_threshold_with_off_values"
+                android:icon="@drawable/ic_heartrate"
+                android:key="heartrate_alert_active_high_threshold"
+                android:summary="%s"
+                android:title="@string/prefs_heartrate_alert_active_high_threshold" />
+        </PreferenceCategory>
+
+        <!-- Stress Monitoring -->
+
+        <PreferenceCategory
+            android:key="pref_key_header_stress"
+            android:title="@string/pref_header_stress">
+
+            <SwitchPreferenceCompat
+                android:defaultValue="false"
+                android:icon="@drawable/ic_mood_bad"
+                android:key="heartrate_stress_monitoring"
+                android:layout="@layout/preference_checkbox"
+                android:summary="@string/prefs_stress_monitoring_description"
+                android:title="@string/prefs_stress_monitoring_title" />
+
+            <SwitchPreferenceCompat
+                android:defaultValue="false"
+                android:dependency="heartrate_stress_monitoring"
+                android:key="heartrate_stress_relaxation_reminder"
+                android:layout="@layout/preference_checkbox"
+                android:summary="@string/prefs_relaxation_reminder_description"
+                android:title="@string/prefs_relaxation_reminder_title" />
+        </PreferenceCategory>
+
+        <!-- SPO2 -->
+        <PreferenceCategory
+            android:key="pref_key_header_spo2"
+            android:title="@string/pref_header_spo2">
+
+            <SwitchPreferenceCompat
+                android:defaultValue="false"
+                android:key="spo2_all_day_monitoring_enabled"
+                android:layout="@layout/preference_checkbox"
+                android:summary="@string/prefs_spo2_monitoring_description"
+                android:title="@string/prefs_spo2_monitoring_title" />
+
+            <ListPreference
+                android:defaultValue="0"
+                android:dependency="spo2_all_day_monitoring_enabled"
+                android:entries="@array/prefs_spo2_alert_threshold"
+                android:entryValues="@array/prefs_spo2_alert_threshold_values"
+                android:key="spo2_low_alert_threshold"
+                android:summary="%s"
+                android:title="@string/prefs_spo2_alert_threshold" />
+        </PreferenceCategory>
+    </PreferenceScreen>
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_high_mtu.xml b/app/src/main/res/xml/devicesettings_high_mtu.xml
new file mode 100644
index 0000000..83551a7
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_high_mtu.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <SwitchPreferenceCompat
+        android:icon="@drawable/ic_mtu"
+        android:defaultValue="true"
+        android:key="allow_high_mtu"
+        android:layout="@layout/preference_checkbox"
+        android:summary="@string/pref_summary_allow_high_mtu"
+        android:title="@string/pref_title_allow_high_mtu" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_hourly_chime.xml b/app/src/main/res/xml/devicesettings_hourly_chime.xml
new file mode 100644
index 0000000..b7b9dd3
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_hourly_chime.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceScreen
+        android:icon="@drawable/ic_action_find_lost_device"
+        android:key="screen_hourly_chime"
+        android:persistent="false"
+        android:summary="@string/prefs_hourly_chime_summary"
+        android:title="@string/prefs_hourly_chime">
+
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:key="hourly_chime_enable"
+            android:layout="@layout/preference_checkbox"
+            android:title="@string/prefs_hourly_chime"
+            android:summary="@string/prefs_hourly_chime_summary" />
+
+        <nodomain.freeyourgadget.gadgetbridge.util.XTimePreference
+            android:defaultValue="09:00"
+            android:dependency="hourly_chime_enable"
+            android:key="hourly_chime_start"
+            android:title="@string/mi2_prefs_do_not_disturb_start" />
+
+        <nodomain.freeyourgadget.gadgetbridge.util.XTimePreference
+            android:defaultValue="22:00"
+            android:dependency="hourly_chime_enable"
+            android:key="hourly_chime_end"
+            android:title="@string/mi2_prefs_do_not_disturb_end" />
+
+    </PreferenceScreen>
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_hourly_chime_enable.xml b/app/src/main/res/xml/devicesettings_hourly_chime_enable.xml
new file mode 100644
index 0000000..3a9cbc0
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_hourly_chime_enable.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <SwitchPreferenceCompat
+        android:defaultValue="false"
+        android:icon="@drawable/ic_beep"
+        android:key="hourly_chime_enable"
+        android:layout="@layout/preference_checkbox"
+        android:title="@string/prefs_hourly_chime" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_hplus.xml b/app/src/main/res/xml/devicesettings_hplus.xml
new file mode 100644
index 0000000..3a4a24c
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_hplus.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+
+    <EditTextPreference
+        android:defaultValue="5"
+        android:icon="@drawable/ic_always_on_display"
+        android:key="hplus_screentime"
+        android:title="@string/pref_title_screentime"
+        app:useSimpleSummaryProvider="true" />
+
+    <SwitchPreferenceCompat
+        android:defaultValue="true"
+        android:icon="@drawable/ic_heart"
+        android:key="hplus_alldayhr"
+        android:layout="@layout/preference_checkbox"
+        android:title="@string/prefs_title_all_day_heart_rate" />
+</PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_huami2021_alexa.xml b/app/src/main/res/xml/devicesettings_huami2021_alexa.xml
new file mode 100644
index 0000000..ce18936
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_huami2021_alexa.xml
@@ -0,0 +1,83 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+
+    <PreferenceScreen
+        android:icon="@drawable/ic_voice"
+        android:key="pref_screen_alexa"
+        android:title="@string/menuitem_alexa">
+
+        <ListPreference
+            android:defaultValue="auto"
+            android:entries="@array/pref_language_all"
+            android:entryValues="@array/pref_language_all_values"
+            android:icon="@drawable/ic_language"
+            android:key="voice_service_language"
+            android:summary="%s"
+            android:title="@string/pref_title_language" />
+
+        <PreferenceCategory
+            android:key="pref_header_voice_service"
+            android:title="@string/voice_service">
+
+            <EditTextPreference
+                android:key="voice_service_package"
+                android:summary="@string/voice_service_package_summary"
+                android:title="@string/voice_service_package_title"
+                app:useSimpleSummaryProvider="true" />
+
+            <EditTextPreference
+                android:key="voice_service_class"
+                android:summary="@string/voice_service_class_summary"
+                android:title="@string/voice_service_class_title"
+                app:useSimpleSummaryProvider="true" />
+        </PreferenceCategory>
+
+        <!-- Keeping these without a translation intentionally, they should be temporary -->
+        <PreferenceCategory
+            android:key="pref_header_voice_service_debug"
+            android:title="@string/action_debug">
+
+            <EditTextPreference
+                android:defaultValue=""
+                android:key="zepp_os_alexa_reply_title"
+                android:title="Reply Title"
+                app:useSimpleSummaryProvider="true" />
+
+            <EditTextPreference
+                android:defaultValue=""
+                android:key="zepp_os_alexa_reply_subtitle"
+                android:title="Reply Subtitle"
+                app:useSimpleSummaryProvider="true" />
+
+            <EditTextPreference
+                android:defaultValue=""
+                android:key="zepp_os_alexa_reply_text"
+                android:title="Reply Text"
+                app:useSimpleSummaryProvider="true" />
+
+            <SwitchPreferenceCompat
+                android:defaultValue="false"
+                android:key="zepp_os_alexa_ask_more_input"
+                android:layout="@layout/preference_checkbox"
+                android:summary="Continue listening after sending the reply"
+                android:title="Ask for more input" />
+
+            <Preference
+                android:key="zepp_os_alexa_btn_trigger"
+                android:summary="Send a command trigger to the watch (stops sending voice data)"
+                android:title="Send command trigger" />
+
+            <Preference
+                android:key="zepp_os_alexa_btn_send_simple"
+                android:summary="Send a simple reply to the watch (just text)"
+                android:title="Send simple reply" />
+
+            <Preference
+                android:key="zepp_os_alexa_btn_send_complex"
+                android:summary="Send a complex reply to the watch (title, subtitle, text)"
+                android:title="Send complex reply" />
+        </PreferenceCategory>
+
+    </PreferenceScreen>
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_huami2021_control_center.xml b/app/src/main/res/xml/devicesettings_huami2021_control_center.xml
new file mode 100644
index 0000000..fb685c5
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_huami2021_control_center.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <com.mobeta.android.dslv.DragSortListPreference
+        android:defaultValue="@array/pref_huami2021_empty_array"
+        android:dialogTitle="@string/bip_prefs_shortcuts"
+        android:entries="@array/pref_huami2021_control_center"
+        android:entryValues="@array/pref_huami2021_control_center_values"
+        android:icon="@drawable/ic_menu"
+        android:key="control_center_sortable"
+        android:persistent="true"
+        android:summary="@string/prefs_control_center_summary"
+        android:title="@string/prefs_control_center" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_huami2021_displayitems.xml b/app/src/main/res/xml/devicesettings_huami2021_displayitems.xml
new file mode 100644
index 0000000..46a4c4f
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_huami2021_displayitems.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <com.mobeta.android.dslv.DragSortListPreference
+        android:defaultValue="@array/pref_huami2021_empty_array"
+        android:dialogTitle="@string/mi2_prefs_display_items"
+        android:entries="@array/pref_zepp_os_apps"
+        android:entryValues="@array/pref_zepp_os_apps_values"
+        android:icon="@drawable/ic_widgets"
+        android:key="display_items_sortable"
+        android:persistent="true"
+        android:summary="@string/mi2_prefs_display_items_summary"
+        android:title="@string/mi2_prefs_display_items" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_huami2021_fetch_operation_time_unit.xml b/app/src/main/res/xml/devicesettings_huami2021_fetch_operation_time_unit.xml
new file mode 100644
index 0000000..57b6b6e
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_huami2021_fetch_operation_time_unit.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <SwitchPreferenceCompat
+        android:defaultValue="true"
+        android:icon="@drawable/ic_timer"
+        android:key="huami_truncate_fetch_operation_timestamps"
+        android:layout="@layout/preference_checkbox"
+        android:summary="@string/pref_huami_truncate_fetch_operation_timestamps_summary"
+        android:title="@string/pref_huami_truncate_fetch_operation_timestamps_title" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_huami2021_shortcut_cards.xml b/app/src/main/res/xml/devicesettings_huami2021_shortcut_cards.xml
new file mode 100644
index 0000000..86b792f
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_huami2021_shortcut_cards.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <com.mobeta.android.dslv.DragSortListPreference
+        android:defaultValue="@array/pref_huami2021_empty_array"
+        android:dialogTitle="@string/prefs_shortcut_cards"
+        android:entries="@array/pref_zepp_os_shortcut_cards"
+        android:entryValues="@array/pref_zepp_os_shortcut_cards_values"
+        android:icon="@drawable/ic_shortcut"
+        android:key="shortcut_cards_sortable"
+        android:persistent="true"
+        android:summary="@string/prefs_shortcut_cards_summary"
+        android:title="@string/prefs_shortcut_cards" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_huami2021_shortcuts.xml b/app/src/main/res/xml/devicesettings_huami2021_shortcuts.xml
new file mode 100644
index 0000000..8147cea
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_huami2021_shortcuts.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <com.mobeta.android.dslv.DragSortListPreference
+        android:defaultValue="@array/pref_huami2021_empty_array"
+        android:dialogTitle="@string/bip_prefs_shortcuts"
+        android:entries="@array/pref_zepp_os_apps"
+        android:entryValues="@array/pref_zepp_os_apps_values"
+        android:icon="@drawable/ic_shortcut"
+        android:key="shortcuts_sortable"
+        android:persistent="true"
+        android:summary="@string/bip_prefs_shotcuts_summary"
+        android:title="@string/bip_prefs_shortcuts" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_huami2021_watchface.xml b/app/src/main/res/xml/devicesettings_huami2021_watchface.xml
new file mode 100644
index 0000000..e3da5fb
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_huami2021_watchface.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <ListPreference
+        android:defaultValue=""
+        android:dialogTitle="@string/kind_watchface"
+        android:entries="@array/pref_zepp_os_watchfaces"
+        android:entryValues="@array/pref_zepp_os_watchfaces_values"
+        android:icon="@drawable/ic_watch"
+        android:key="watchface"
+        android:title="@string/kind_watchface" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_huawei_account.xml b/app/src/main/res/xml/devicesettings_huawei_account.xml
new file mode 100644
index 0000000..6797b04
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_huawei_account.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <EditTextPreference
+        android:icon="@drawable/ic_vpn_key"
+        android:key="huawei_account"
+        android:maxLength="17"
+        android:summary="@string/pref_summary_huawei_account"
+        android:title="@string/pref_title_huawei_account" />
+</androidx.preference.PreferenceScreen>
\ No newline at end of file
diff --git a/app/src/main/res/xml/devicesettings_huawei_debug.xml b/app/src/main/res/xml/devicesettings_huawei_debug.xml
new file mode 100644
index 0000000..4aab406
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_huawei_debug.xml
@@ -0,0 +1,83 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <EditTextPreference
+        android:icon="@drawable/ic_warning"
+        android:key="debug_huawei_request"
+        android:maxLength="16"
+        android:summary="@string/pref_summary_debug"
+        android:title="@string/pref_title_debug" />
+
+    <SwitchPreferenceCompat
+        android:defaultValue="false"
+        android:key="huawei_reparse_workout_data"
+        android:layout="@layout/preference_checkbox"
+        android:title="Reparse workout data"
+        android:summary="This will only do something after certain updates" />
+
+    <PreferenceCategory
+        android:key="pref_force_options"
+        android:title="@string/pref_force_options">
+
+        <Preference
+            android:icon="@drawable/ic_settings"
+            android:summary="@string/pref_force_options_summary"/>
+
+        <SwitchPreferenceCompat
+            android:icon="@drawable/ic_access_time"
+            android:defaultValue="false"
+            android:key="pref_force_enable_smart_alarm"
+            android:layout="@layout/preference_checkbox"
+            android:title="@string/pref_force_smart_alarm"
+            android:summary="@string/pref_force_smart_alarm_summary"/>
+
+        <SwitchPreferenceCompat
+            android:icon="@drawable/ic_switch_left"
+            android:defaultValue="false"
+            android:key="pref_force_enable_wear_location"
+            android:layout="@layout/preference_checkbox"
+            android:title="@string/pref_force_wear_location"
+            android:summary="@string/pref_force_wear_location_summary" />
+
+        <SwitchPreferenceCompat
+            android:icon="@drawable/ic_block"
+            android:defaultValue="false"
+            android:key="pref_force_dnd_support"
+            android:layout="@layout/preference_checkbox"
+            android:title="@string/pref_force_dnd_support"
+            android:summary="@string/pref_force_dnd_support_summary" />
+
+        <SwitchPreferenceCompat
+            android:icon="@drawable/ic_heartrate"
+            android:defaultValue="false"
+            android:key="pref_force_enable_heartrate_support"
+            android:layout="@layout/preference_checkbox"
+            android:title="@string/pref_force_enable_heartrate_support"
+            android:summary="@string/pref_force_enable_heartrate_support_summary" />
+
+        <SwitchPreferenceCompat
+            android:icon="@drawable/ic_heart"
+            android:defaultValue="false"
+            android:key="pref_force_enable_spo2_support"
+            android:layout="@layout/preference_checkbox"
+            android:title="@string/pref_force_enable_spo2_support"
+            android:summary="@string/pref_force_enable_spo2_support_summary" />
+
+        <SwitchPreferenceCompat
+            android:icon="@drawable/ic_activity_sleep"
+            android:defaultValue="false"
+            android:key="pref_force_ignore_wakeup_status_start"
+            android:layout="@layout/preference_checkbox"
+            android:title="@string/huawei_ignore_wakeup_status_start"
+            android:summary="@string/huawei_ignore_wakeup_status_start_description"/>
+
+        <SwitchPreferenceCompat
+            android:icon="@drawable/ic_activity_sleep"
+            android:defaultValue="false"
+            android:key="pref_force_ignore_wakeup_status_end"
+            android:layout="@layout/preference_checkbox"
+            android:title="@string/huawei_ignore_wakeup_status_end"
+            android:summary="@string/huawei_ignore_wakeup_status_end_description"/>
+    </PreferenceCategory>
+
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_huawei_freebuds.xml b/app/src/main/res/xml/devicesettings_huawei_freebuds.xml
new file mode 100644
index 0000000..67c9b32
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_huawei_freebuds.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+    <SwitchPreferenceCompat
+        android:defaultValue="true"
+        android:icon="@drawable/ic_extension"
+        android:key="pref_freebuds_inear_detection"
+        android:layout="@layout/preference_checkbox"
+        android:summary="@string/nothing_prefs_inear_summary"
+        android:title="@string/nothing_prefs_inear_title" />
+    <ListPreference
+        android:entries="@array/huawei_freebuds_audio_mode_names"
+        android:entryValues="@array/huawei_freebuds_audio_mode_values"
+        android:icon="@drawable/ic_extension"
+        android:key="pref_freebuds_audiomode"
+        android:summary="%s"
+        android:title="@string/nothing_prefs_audiomode_title" />
+</androidx.preference.PreferenceScreen>
\ No newline at end of file
diff --git a/app/src/main/res/xml/devicesettings_hydration_reminder.xml b/app/src/main/res/xml/devicesettings_hydration_reminder.xml
new file mode 100644
index 0000000..d0783cb
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_hydration_reminder.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceScreen
+        android:icon="@drawable/ic_drink"
+        android:key="screen_hydration_reminder"
+        android:persistent="false"
+        android:summary="@string/lefun_prefs_hydration_reminder_summary"
+        android:title="@string/lefun_prefs_hydration_reminder_title">
+
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:key="pref_hydration_switch"
+            android:layout="@layout/preference_checkbox"
+            android:title="@string/lefun_prefs_hydration_reminder_title" />
+        <EditTextPreference
+            android:defaultValue="60"
+            android:key="pref_hydration_period"
+            android:summary="@string/lefun_prefs_hydration_reminder_summary"
+            android:title="@string/lefun_prefs_hydration_reminder_interval_title"/>
+    </PreferenceScreen>
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_hydration_reminder_dnd.xml b/app/src/main/res/xml/devicesettings_hydration_reminder_dnd.xml
new file mode 100644
index 0000000..26be8b6
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_hydration_reminder_dnd.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceScreen
+        android:icon="@drawable/ic_drink"
+        android:key="screen_hydration_reminder"
+        android:persistent="false"
+        android:summary="@string/lefun_prefs_hydration_reminder_summary"
+        android:title="@string/lefun_prefs_hydration_reminder_title">
+
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:key="pref_hydration_switch"
+            android:layout="@layout/preference_checkbox"
+            android:title="@string/lefun_prefs_hydration_reminder_title" />
+        <EditTextPreference
+            android:defaultValue="60"
+            android:key="pref_hydration_period"
+            android:summary="@string/lefun_prefs_hydration_reminder_summary"
+            android:title="@string/lefun_prefs_hydration_reminder_interval_title"/>
+
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:dependency="screen_hydration_reminder"
+            android:key="pref_hydration_dnd"
+            android:layout="@layout/preference_checkbox"
+            android:summary="@string/hydration_dnd_summary"
+            android:title="@string/mi2_prefs_do_not_disturb" />
+
+        <nodomain.freeyourgadget.gadgetbridge.util.XTimePreference
+            android:defaultValue="12:00"
+            android:dependency="pref_hydration_dnd"
+            android:key="pref_hydration_dnd_start"
+            android:title="@string/mi2_prefs_do_not_disturb_start" />
+
+        <nodomain.freeyourgadget.gadgetbridge.util.XTimePreference
+            android:defaultValue="14:00"
+            android:dependency="pref_hydration_dnd"
+            android:key="pref_hydration_dnd_end"
+            android:title="@string/mi2_prefs_do_not_disturb_end" />
+    </PreferenceScreen>
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_idasen.xml b/app/src/main/res/xml/devicesettings_idasen.xml
new file mode 100644
index 0000000..4998911
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_idasen.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<PreferenceScreen
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+
+    <EditTextPreference
+        android:title="@string/idasen_pref_sit_height"
+        android:key="idasen_sit_height"
+        android:inputType="number"
+        android:icon="@drawable/ic_activity_yoga"
+        app:useSimpleSummaryProvider="true" />
+
+    <EditTextPreference
+        android:title="@string/idasen_pref_mid_height"
+        android:key="idasen_mid_height"
+        android:inputType="number"
+        android:icon="@drawable/ic_activity_bike_trainer"
+        app:useSimpleSummaryProvider="true" />
+
+    <EditTextPreference
+        android:title="@string/idasen_pref_stand_height"
+        android:key="idasen_stand_height"
+        android:inputType="number"
+        android:icon="@drawable/ic_activity_exercise"
+        app:useSimpleSummaryProvider="true" />
+
+</PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_inactivity.xml b/app/src/main/res/xml/devicesettings_inactivity.xml
new file mode 100644
index 0000000..ffbfa5d
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_inactivity.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceScreen
+        android:icon="@drawable/ic_chair"
+        android:key="screen_inactivity"
+        android:persistent="false"
+        android:summary="@string/mi2_prefs_inactivity_warnings_summary"
+        android:title="@string/mi2_prefs_inactivity_warnings">
+
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:key="inactivity_warnings_enable"
+            android:layout="@layout/preference_checkbox"
+            android:title="@string/mi2_prefs_inactivity_warnings"
+            android:summary="@string/mi2_prefs_inactivity_warnings_summary" />
+        <EditTextPreference
+            android:defaultValue="60"
+            android:dependency="inactivity_warnings_enable"
+            android:key="inactivity_warnings_threshold"
+            android:summary="@string/mi2_prefs_inactivity_warnings_summary"
+            android:title="@string/mi2_prefs_inactivity_warnings_threshold"/>
+        <nodomain.freeyourgadget.gadgetbridge.util.XTimePreference
+            android:defaultValue="06:00"
+            android:dependency="inactivity_warnings_enable"
+            android:key="inactivity_warnings_start"
+            android:title="@string/mi2_prefs_do_not_disturb_start" />
+
+        <nodomain.freeyourgadget.gadgetbridge.util.XTimePreference
+            android:defaultValue="23:00"
+            android:dependency="inactivity_warnings_enable"
+            android:key="inactivity_warnings_end"
+            android:title="@string/mi2_prefs_do_not_disturb_end" />
+
+    </PreferenceScreen>
+
+
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_inactivity_dnd.xml b/app/src/main/res/xml/devicesettings_inactivity_dnd.xml
new file mode 100644
index 0000000..f57789a
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_inactivity_dnd.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceScreen
+        android:icon="@drawable/ic_chair"
+        android:key="screen_inactivity"
+        android:persistent="false"
+        android:summary="@string/mi2_prefs_inactivity_warnings_summary"
+        android:title="@string/mi2_prefs_inactivity_warnings">
+
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:key="inactivity_warnings_enable"
+            android:layout="@layout/preference_checkbox"
+            android:title="@string/mi2_prefs_inactivity_warnings"
+            android:summary="@string/mi2_prefs_inactivity_warnings_summary" />
+
+        <EditTextPreference
+            android:defaultValue="60"
+            android:dependency="inactivity_warnings_enable"
+            android:inputType="number"
+            android:key="inactivity_warnings_threshold"
+            android:maxLength="2"
+            android:title="@string/mi2_prefs_inactivity_warnings_threshold" />
+
+        <nodomain.freeyourgadget.gadgetbridge.util.XTimePreference
+            android:defaultValue="06:00"
+            android:dependency="inactivity_warnings_enable"
+            android:key="inactivity_warnings_start"
+            android:title="@string/mi2_prefs_do_not_disturb_start" />
+
+        <nodomain.freeyourgadget.gadgetbridge.util.XTimePreference
+            android:defaultValue="22:00"
+            android:dependency="inactivity_warnings_enable"
+            android:key="inactivity_warnings_end"
+            android:title="@string/mi2_prefs_do_not_disturb_end" />
+
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:dependency="inactivity_warnings_enable"
+            android:key="inactivity_warnings_dnd"
+            android:layout="@layout/preference_checkbox"
+            android:summary="@string/mi2_prefs_inactivity_warnings_dnd_summary"
+            android:title="@string/mi2_prefs_do_not_disturb" />
+
+        <nodomain.freeyourgadget.gadgetbridge.util.XTimePreference
+            android:defaultValue="12:00"
+            android:dependency="inactivity_warnings_dnd"
+            android:key="inactivity_warnings_dnd_start"
+            android:title="@string/mi2_prefs_do_not_disturb_start" />
+
+        <nodomain.freeyourgadget.gadgetbridge.util.XTimePreference
+            android:defaultValue="14:00"
+            android:dependency="inactivity_warnings_dnd"
+            android:key="inactivity_warnings_dnd_end"
+            android:title="@string/mi2_prefs_do_not_disturb_end" />
+
+    </PreferenceScreen>
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_inactivity_dnd_no_threshold.xml b/app/src/main/res/xml/devicesettings_inactivity_dnd_no_threshold.xml
new file mode 100644
index 0000000..d0c154e
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_inactivity_dnd_no_threshold.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceScreen
+        android:icon="@drawable/ic_chair"
+        android:key="pref_screen_inactivity_extended"
+        android:persistent="false"
+        android:summary="@string/mi2_prefs_inactivity_warnings_summary"
+        android:title="@string/mi2_prefs_inactivity_warnings">
+
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:key="inactivity_warnings_enable"
+            android:layout="@layout/preference_checkbox"
+            android:summary="@string/mi2_prefs_inactivity_warnings_summary"
+            android:title="@string/mi2_prefs_inactivity_warnings" />
+
+        <nodomain.freeyourgadget.gadgetbridge.util.XTimePreference
+            android:defaultValue="06:00"
+            android:dependency="inactivity_warnings_enable"
+            android:key="inactivity_warnings_start"
+            android:title="@string/mi2_prefs_do_not_disturb_start" />
+
+        <nodomain.freeyourgadget.gadgetbridge.util.XTimePreference
+            android:defaultValue="22:00"
+            android:dependency="inactivity_warnings_enable"
+            android:key="inactivity_warnings_end"
+            android:title="@string/mi2_prefs_do_not_disturb_end" />
+
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:dependency="inactivity_warnings_enable"
+            android:key="inactivity_warnings_dnd"
+            android:layout="@layout/preference_checkbox"
+            android:summary="@string/mi2_prefs_inactivity_warnings_dnd_summary"
+            android:title="@string/mi2_prefs_do_not_disturb" />
+
+        <nodomain.freeyourgadget.gadgetbridge.util.XTimePreference
+            android:defaultValue="12:00"
+            android:dependency="inactivity_warnings_dnd"
+            android:key="inactivity_warnings_dnd_start"
+            android:title="@string/mi2_prefs_do_not_disturb_start" />
+
+        <nodomain.freeyourgadget.gadgetbridge.util.XTimePreference
+            android:defaultValue="14:00"
+            android:dependency="inactivity_warnings_dnd"
+            android:key="inactivity_warnings_dnd_end"
+            android:title="@string/mi2_prefs_do_not_disturb_end" />
+
+    </PreferenceScreen>
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_inactivity_extended.xml b/app/src/main/res/xml/devicesettings_inactivity_extended.xml
new file mode 100644
index 0000000..ebe2b60
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_inactivity_extended.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceScreen
+        android:icon="@drawable/ic_chair"
+        android:key="pref_screen_inactivity_extended"
+        android:persistent="false"
+        android:summary="@string/mi2_prefs_inactivity_warnings_summary"
+        android:title="@string/mi2_prefs_inactivity_warnings">
+
+        <!-- workaround for missing toolbar -->
+        <PreferenceCategory android:title="@string/mi2_prefs_inactivity_warnings_summary" />
+
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:key="inactivity_warnings_enable"
+            android:layout="@layout/preference_checkbox"
+            android:summary="@string/mi2_prefs_inactivity_warnings_summary"
+            android:title="@string/mi2_prefs_inactivity_warnings" />
+
+        <ListPreference
+            android:defaultValue="4"
+            android:dependency="inactivity_warnings_enable"
+            android:entries="@array/inactivity_minutes"
+            android:entryValues="@array/inactivity_minutes_values"
+            android:key="inactivity_warnings_threshold_extended"
+            android:summary="@string/mi2_prefs_inactivity_warnings_summary"
+            android:title="@string/mi2_prefs_inactivity_warnings_threshold" />
+
+        <nodomain.freeyourgadget.gadgetbridge.util.XTimePreference
+            android:defaultValue="08:00"
+            android:dependency="inactivity_warnings_enable"
+            android:key="inactivity_warnings_start"
+            android:title="@string/mi2_prefs_do_not_disturb_start" />
+
+        <nodomain.freeyourgadget.gadgetbridge.util.XTimePreference
+            android:defaultValue="16:00"
+            android:dependency="inactivity_warnings_enable"
+            android:key="inactivity_warnings_end"
+            android:title="@string/mi2_prefs_do_not_disturb_end" />
+    </PreferenceScreen>
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_inactivity_noshed.xml b/app/src/main/res/xml/devicesettings_inactivity_noshed.xml
new file mode 100644
index 0000000..c72a0ea
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_inactivity_noshed.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <SwitchPreferenceCompat
+        android:icon="@drawable/ic_chair"
+        android:defaultValue="false"
+        android:key="inactivity_warnings_enable_noshed"
+        android:layout="@layout/preference_checkbox"
+        android:title="@string/mi2_prefs_inactivity_warnings"
+        android:summary="@string/mi2_prefs_inactivity_warnings_summary"/>
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_inactivity_sheduled.xml b/app/src/main/res/xml/devicesettings_inactivity_sheduled.xml
new file mode 100644
index 0000000..b04489b
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_inactivity_sheduled.xml
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceScreen
+        android:icon="@drawable/ic_chair"
+        android:key="screen_inactivity"
+        android:persistent="false"
+        android:summary="@string/mi2_prefs_inactivity_warnings_summary"
+        android:title="@string/mi2_prefs_inactivity_warnings">
+
+        <!-- workaround for missing toolbar -->
+        <PreferenceCategory android:title="@string/mi2_prefs_inactivity_warnings" />
+
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:key="inactivity_warnings_enable"
+            android:layout="@layout/preference_checkbox"
+            android:title="@string/mi2_prefs_inactivity_warnings"
+            android:summary="@string/mi2_prefs_inactivity_warnings_summary" />
+
+        <EditTextPreference
+            android:defaultValue="60"
+            android:dependency="inactivity_warnings_enable"
+            android:key="inactivity_warnings_threshold"
+            android:inputType="numberSigned"
+            android:maxLength="3"
+            android:title="@string/mi2_prefs_inactivity_warnings_threshold"/>
+
+        <nodomain.freeyourgadget.gadgetbridge.util.XTimePreference
+            android:defaultValue="06:00"
+            android:dependency="inactivity_warnings_enable"
+            android:key="inactivity_warnings_start"
+            android:title="@string/mi2_prefs_do_not_disturb_start" />
+
+        <nodomain.freeyourgadget.gadgetbridge.util.XTimePreference
+            android:defaultValue="23:00"
+            android:dependency="inactivity_warnings_enable"
+            android:key="inactivity_warnings_end"
+            android:title="@string/mi2_prefs_do_not_disturb_end" />
+
+        <!-- workaround for missing toolbar -->
+        <PreferenceCategory android:title="@string/zetime_prefs_inactivity_repetitions"
+            android:dependency="inactivity_warnings_enable">
+            <CheckBoxPreference
+                android:defaultValue="false"
+                android:key="inactivity_warnings_mo"
+                android:title="@string/zetime_prefs_inactivity_mo" />
+            <CheckBoxPreference
+                android:defaultValue="false"
+                android:key="inactivity_warnings_tu"
+                android:title="@string/zetime_prefs_inactivity_tu" />
+            <CheckBoxPreference
+                android:defaultValue="false"
+                android:key="inactivity_warnings_we"
+                android:title="@string/zetime_prefs_inactivity_we" />
+            <CheckBoxPreference
+                android:defaultValue="false"
+                android:key="inactivity_warnings_th"
+                android:title="@string/zetime_prefs_inactivity_th" />
+            <CheckBoxPreference
+                android:defaultValue="false"
+                android:key="inactivity_warnings_fr"
+                android:title="@string/zetime_prefs_inactivity_fr" />
+            <CheckBoxPreference
+                android:defaultValue="false"
+                android:key="inactivity_warnings_sa"
+                android:title="@string/zetime_prefs_inactivity_sa" />
+            <CheckBoxPreference
+                android:defaultValue="false"
+                android:key="inactivity_warnings_su"
+                android:title="@string/zetime_prefs_inactivity_su" />
+        </PreferenceCategory>
+    </PreferenceScreen>
+
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_keep_activity_data_on_device.xml b/app/src/main/res/xml/devicesettings_keep_activity_data_on_device.xml
new file mode 100644
index 0000000..7afb693
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_keep_activity_data_on_device.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <SwitchPreferenceCompat
+        android:icon="@drawable/ic_activity_unknown_small"
+        android:defaultValue="false"
+        android:key="keep_activity_data_on_device"
+        android:layout="@layout/preference_checkbox"
+        android:summary="@string/pref_summary_keep_data_on_device"
+        android:title="@string/pref_title_keep_data_on_device" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_key_vibration.xml b/app/src/main/res/xml/devicesettings_key_vibration.xml
new file mode 100644
index 0000000..56631cd
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_key_vibration.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <SwitchPreferenceCompat
+        android:defaultValue="false"
+        android:icon="@drawable/ic_vibration"
+        android:key="key_vibration"
+        android:layout="@layout/preference_checkbox"
+        android:title="@string/prefs_key_vibration" />
+</androidx.preference.PreferenceScreen>
\ No newline at end of file
diff --git a/app/src/main/res/xml/devicesettings_language_generic.xml b/app/src/main/res/xml/devicesettings_language_generic.xml
new file mode 100644
index 0000000..371bf31
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_language_generic.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <ListPreference
+        android:icon="@drawable/ic_language"
+        android:defaultValue="auto"
+        android:entries="@array/pref_language_all"
+        android:entryValues="@array/pref_language_all_values"
+        android:key="language"
+        android:summary="%s"
+        android:title="@string/pref_title_language" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_lefun_interface_language.xml b/app/src/main/res/xml/devicesettings_lefun_interface_language.xml
new file mode 100644
index 0000000..6c821ba
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_lefun_interface_language.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <ListPreference
+        android:defaultValue="0"
+        android:entries="@array/lefun_interface_language_names"
+        android:entryValues="@array/lefun_interface_language_values"
+        android:icon="@drawable/ic_language"
+        android:key="language"
+        android:summary="%s"
+        android:title="@string/lefun_prefs_interface_language_title" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_liftwrist_display.xml b/app/src/main/res/xml/devicesettings_liftwrist_display.xml
new file mode 100644
index 0000000..c703c8e
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_liftwrist_display.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceScreen
+        android:icon="@drawable/ic_arrow_upward"
+        android:key="screen_liftwrist_display"
+        android:persistent="false"
+        android:title="@string/mi2_prefs_activate_display_on_lift">
+
+        <!-- workaround for missing toolbar -->
+        <PreferenceCategory android:title="@string/mi2_prefs_activate_display_on_lift" />
+
+        <ListPreference
+            android:defaultValue="@string/p_off"
+            android:entries="@array/activate_display_on_lift"
+            android:entryValues="@array/activate_display_on_lift_values"
+            android:key="activate_display_on_lift_wrist"
+            android:summary="%s"
+            android:title="@string/mi2_prefs_activate_display_on_lift" />
+
+        <nodomain.freeyourgadget.gadgetbridge.util.XTimePreference
+            android:defaultValue="00:00"
+            android:key="display_on_lift_start"
+            android:title="@string/mi2_prefs_do_not_disturb_start" />
+
+        <nodomain.freeyourgadget.gadgetbridge.util.XTimePreference
+            android:defaultValue="00:00"
+            android:key="display_on_lift_end"
+            android:title="@string/mi2_prefs_do_not_disturb_end" />
+
+    </PreferenceScreen>
+</androidx.preference.PreferenceScreen>
\ No newline at end of file
diff --git a/app/src/main/res/xml/devicesettings_liftwrist_display_no_on.xml b/app/src/main/res/xml/devicesettings_liftwrist_display_no_on.xml
new file mode 100644
index 0000000..c29a103
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_liftwrist_display_no_on.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceScreen
+        android:icon="@drawable/ic_arrow_upward"
+        android:key="screen_liftwrist_display"
+        android:persistent="false"
+        android:title="@string/mi2_prefs_activate_display_on_lift">
+
+        <!-- workaround for missing toolbar -->
+        <PreferenceCategory android:title="@string/mi2_prefs_activate_display_on_lift" />
+
+        <ListPreference
+            android:defaultValue="@string/p_off"
+            android:enabled="true"
+            android:entries="@array/do_not_disturb_no_auto"
+            android:entryValues="@array/do_not_disturb_no_auto_values"
+            android:key="activate_display_on_lift_wrist"
+            android:summary="%s"
+            android:title="@string/mi2_prefs_activate_display_on_lift" />
+
+        <nodomain.freeyourgadget.gadgetbridge.util.XTimePreference
+            android:defaultValue="06:00"
+            android:key="display_on_lift_start"
+            android:title="@string/mi2_prefs_do_not_disturb_start" />
+
+        <nodomain.freeyourgadget.gadgetbridge.util.XTimePreference
+            android:defaultValue="23:00"
+            android:key="display_on_lift_end"
+            android:title="@string/mi2_prefs_do_not_disturb_end" />
+    </PreferenceScreen>
+</androidx.preference.PreferenceScreen>
\ No newline at end of file
diff --git a/app/src/main/res/xml/devicesettings_liftwrist_display_noshed.xml b/app/src/main/res/xml/devicesettings_liftwrist_display_noshed.xml
new file mode 100644
index 0000000..943b7b0
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_liftwrist_display_noshed.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <SwitchPreferenceCompat
+        android:icon="@drawable/ic_arrow_upward"
+        android:defaultValue="false"
+        android:key="activate_display_on_lift_wrist_noshed"
+        android:layout="@layout/preference_checkbox"
+        android:title="@string/mi2_prefs_activate_display_on_lift" />
+</androidx.preference.PreferenceScreen>
\ No newline at end of file
diff --git a/app/src/main/res/xml/devicesettings_liftwrist_display_sensitivity.xml b/app/src/main/res/xml/devicesettings_liftwrist_display_sensitivity.xml
new file mode 100644
index 0000000..fe2e64a
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_liftwrist_display_sensitivity.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceScreen
+        android:icon="@drawable/ic_arrow_upward"
+        android:key="pref_screen_lift_wrist"
+        android:persistent="false"
+        android:title="@string/mi2_prefs_activate_display_on_lift">
+
+        <!-- workaround for missing toolbar -->
+        <PreferenceCategory android:title="@string/mi2_prefs_activate_display_on_lift" />
+
+        <ListPreference
+            android:defaultValue="@string/p_off"
+            android:entries="@array/activate_display_on_lift"
+            android:entryValues="@array/activate_display_on_lift_values"
+            android:key="activate_display_on_lift_wrist"
+            android:summary="%s"
+            android:title="@string/mi2_prefs_activate_display_on_lift" />
+
+        <ListPreference
+            android:defaultValue="@string/p_normal"
+            android:entries="@array/activate_display_on_lift_sensitivity"
+            android:entryValues="@array/activate_display_on_lift_sensitivity_values"
+            android:key="display_on_lift_sensitivity"
+            android:summary="%s"
+            android:title="@string/prefs_activate_display_on_lift_sensitivity" />
+
+        <nodomain.freeyourgadget.gadgetbridge.util.XTimePreference
+            android:defaultValue="00:00"
+            android:key="display_on_lift_start"
+            android:title="@string/mi2_prefs_do_not_disturb_start" />
+
+        <nodomain.freeyourgadget.gadgetbridge.util.XTimePreference
+            android:defaultValue="00:00"
+            android:key="display_on_lift_end"
+            android:title="@string/mi2_prefs_do_not_disturb_end" />
+
+    </PreferenceScreen>
+</androidx.preference.PreferenceScreen>
\ No newline at end of file
diff --git a/app/src/main/res/xml/devicesettings_liftwrist_display_sensitivity_with_smart.xml b/app/src/main/res/xml/devicesettings_liftwrist_display_sensitivity_with_smart.xml
new file mode 100644
index 0000000..8b0002f
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_liftwrist_display_sensitivity_with_smart.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceScreen
+        android:icon="@drawable/ic_arrow_upward"
+        android:key="pref_screen_lift_wrist"
+        android:persistent="false"
+        android:title="@string/mi2_prefs_activate_display_on_lift">
+
+        <!-- workaround for missing toolbar -->
+        <PreferenceCategory android:title="@string/mi2_prefs_activate_display_on_lift" />
+
+        <ListPreference
+            android:defaultValue="@string/p_off"
+            android:entries="@array/activate_display_on_lift_with_smart"
+            android:entryValues="@array/activate_display_on_lift_with_smart_values"
+            android:key="activate_display_on_lift_wrist"
+            android:summary="%s"
+            android:title="@string/mi2_prefs_activate_display_on_lift" />
+
+        <ListPreference
+            android:defaultValue="@string/p_normal"
+            android:entries="@array/activate_display_on_lift_sensitivity"
+            android:entryValues="@array/activate_display_on_lift_sensitivity_values"
+            android:key="display_on_lift_sensitivity"
+            android:summary="%s"
+            android:title="@string/prefs_activate_display_on_lift_sensitivity" />
+
+        <nodomain.freeyourgadget.gadgetbridge.util.XTimePreference
+            android:defaultValue="00:00"
+            android:key="display_on_lift_start"
+            android:title="@string/mi2_prefs_do_not_disturb_start" />
+
+        <nodomain.freeyourgadget.gadgetbridge.util.XTimePreference
+            android:defaultValue="00:00"
+            android:key="display_on_lift_end"
+            android:title="@string/mi2_prefs_do_not_disturb_end" />
+
+    </PreferenceScreen>
+</androidx.preference.PreferenceScreen>
\ No newline at end of file
diff --git a/app/src/main/res/xml/devicesettings_light_duration_longer.xml b/app/src/main/res/xml/devicesettings_light_duration_longer.xml
new file mode 100644
index 0000000..7183de4
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_light_duration_longer.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <SwitchPreferenceCompat
+        android:defaultValue="false"
+        android:icon="@drawable/ic_wb_sunny"
+        android:key="light_duration_longer"
+        android:layout="@layout/preference_checkbox"
+        android:title="@string/prefs_light_duration_longer" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_lock_unlock.xml b/app/src/main/res/xml/devicesettings_lock_unlock.xml
new file mode 100644
index 0000000..39db02c
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_lock_unlock.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <SwitchPreferenceCompat
+        android:icon="@drawable/ic_lock_open"
+        android:defaultValue="false"
+        android:key="lock"
+        android:layout="@layout/preference_checkbox"
+        android:title="@string/pref_title_lock_unlock" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_lowlatency_fwupdate.xml b/app/src/main/res/xml/devicesettings_lowlatency_fwupdate.xml
new file mode 100644
index 0000000..f4f6b84
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_lowlatency_fwupdate.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <SwitchPreferenceCompat
+        android:icon="@drawable/ic_mtu"
+        android:defaultValue="true"
+        android:key="low_latency_fw_update"
+        android:layout="@layout/preference_checkbox"
+        android:summary="@string/pref_summary_low_latency_fw_update"
+        android:title="@string/pref_title_low_latency_fw_update" />
+</androidx.preference.PreferenceScreen>
\ No newline at end of file
diff --git a/app/src/main/res/xml/devicesettings_loyalty_cards.xml b/app/src/main/res/xml/devicesettings_loyalty_cards.xml
new file mode 100644
index 0000000..e391ddd
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_loyalty_cards.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <Preference
+        android:icon="@drawable/ic_loyalty"
+        android:key="pref_key_loyalty_cards"
+        android:title="@string/loyalty_cards" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_miband2_dateformat.xml b/app/src/main/res/xml/devicesettings_miband2_dateformat.xml
new file mode 100644
index 0000000..9d55f94
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_miband2_dateformat.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <ListPreference
+        android:defaultValue="@string/p_dateformat_time"
+        android:icon="@drawable/ic_access_time"
+        android:entries="@array/mi2_dateformats"
+        android:entryValues="@array/mi2_dateformats_values"
+        android:key="mi2_dateformat"
+        android:summary="%s"
+        android:title="@string/miband2_prefs_dateformat" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_miband2_display.xml b/app/src/main/res/xml/devicesettings_miband2_display.xml
new file mode 100644
index 0000000..d6e820f
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_miband2_display.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <MultiSelectListPreference
+        android:defaultValue="@array/pref_mi2_display_items_default"
+        android:dialogTitle="@string/mi2_prefs_display_items"
+        android:entries="@array/pref_mi2_display_items"
+        android:entryValues="@array/pref_mi2_display_items_values"
+        android:icon="@drawable/ic_widgets"
+        android:key="display_items"
+        android:summary="@string/mi2_prefs_display_items_summary"
+        android:title="@string/mi2_prefs_display_items" />
+
+    <SwitchPreferenceCompat
+        android:defaultValue="true"
+        android:icon="@drawable/ic_font_download"
+        android:key="mi2_enable_text_notifications"
+        android:layout="@layout/preference_checkbox"
+        android:summary="@string/mi2_enable_text_notifications_summary"
+        android:title="@string/mi2_enable_text_notifications" />
+
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_miband3.xml b/app/src/main/res/xml/devicesettings_miband3.xml
new file mode 100644
index 0000000..150f5ef
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_miband3.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <com.mobeta.android.dslv.DragSortListPreference
+        android:icon="@drawable/ic_widgets"
+        android:defaultValue="@array/pref_miband3_display_items_default"
+        android:dialogTitle="@string/mi2_prefs_display_items"
+        android:entries="@array/pref_miband3_display_items"
+        android:entryValues="@array/pref_miband3_display_items_values"
+        android:key="display_items_sortable"
+        android:persistent="true"
+        android:summary="@string/mi2_prefs_display_items_summary"
+        android:title="@string/mi2_prefs_display_items" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_miband5.xml b/app/src/main/res/xml/devicesettings_miband5.xml
new file mode 100644
index 0000000..3269121
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_miband5.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <com.mobeta.android.dslv.DragSortListPreference
+        android:icon="@drawable/ic_widgets"
+        android:defaultValue="@array/pref_miband5_display_items_default"
+        android:dialogTitle="@string/mi2_prefs_display_items"
+        android:entries="@array/pref_miband5_display_items"
+        android:entryValues="@array/pref_miband5_display_items_values"
+        android:key="display_items_sortable"
+        android:persistent="true"
+        android:summary="@string/mi2_prefs_display_items_summary"
+        android:title="@string/mi2_prefs_display_items" />
+    <com.mobeta.android.dslv.DragSortListPreference
+        android:icon="@drawable/ic_shortcut"
+        android:defaultValue="@array/pref_miband5_shortcuts_default"
+        android:dialogTitle="@string/bip_prefs_shortcuts"
+        android:entries="@array/pref_miband5_shortcuts"
+        android:entryValues="@array/pref_miband5_shortcuts_values"
+        android:key="shortcuts_sortable"
+        android:persistent="true"
+        android:summary="@string/bip_prefs_shotcuts_summary"
+        android:title="@string/bip_prefs_shortcuts" />
+    <com.mobeta.android.dslv.DragSortListPreference
+        android:icon="@drawable/ic_activity_unknown_small"
+        android:defaultValue="@array/pref_miband5_workout_activity_types_default"
+        android:dialogTitle="@string/mi5_prefs_workout_activity_types"
+        android:entries="@array/pref_miband5_workout_activity_types"
+        android:entryValues="@array/pref_miband5_workout_activity_types_values"
+        android:key="workout_activity_types_sortable"
+        android:persistent="true"
+        android:summary="@string/mi5_prefs_workout_activity_types_summary"
+        android:title="@string/mi5_prefs_workout_activity_types" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_miband6.xml b/app/src/main/res/xml/devicesettings_miband6.xml
new file mode 100644
index 0000000..6deef2a
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_miband6.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <com.mobeta.android.dslv.DragSortListPreference
+        android:defaultValue="@array/pref_miband6_display_items_default"
+        android:dialogTitle="@string/mi2_prefs_display_items"
+        android:entries="@array/pref_miband6_display_items"
+        android:entryValues="@array/pref_miband6_display_items_values"
+        android:icon="@drawable/ic_widgets"
+        android:key="display_items_sortable"
+        android:persistent="true"
+        android:summary="@string/mi2_prefs_display_items_summary"
+        android:title="@string/mi2_prefs_display_items" />
+    <com.mobeta.android.dslv.DragSortListPreference
+        android:icon="@drawable/ic_shortcut"
+        android:defaultValue="@array/pref_miband6_shortcuts_default"
+        android:dialogTitle="@string/bip_prefs_shortcuts"
+        android:entries="@array/pref_miband6_shortcuts"
+        android:entryValues="@array/pref_miband6_shortcuts_values"
+        android:key="shortcuts_sortable"
+        android:persistent="true"
+        android:summary="@string/bip_prefs_shotcuts_summary"
+        android:title="@string/bip_prefs_shortcuts" />
+    <com.mobeta.android.dslv.DragSortListPreference
+        android:icon="@drawable/ic_activity_unknown_small"
+        android:defaultValue="@array/pref_miband6_workout_activity_types_default"
+        android:dialogTitle="@string/mi5_prefs_workout_activity_types"
+        android:entries="@array/pref_miband6_workout_activity_types"
+        android:entryValues="@array/pref_miband6_workout_activity_types_values"
+        android:key="workout_activity_types_sortable"
+        android:persistent="true"
+        android:summary="@string/mi5_prefs_workout_activity_types_summary"
+        android:title="@string/mi5_prefs_workout_activity_types" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_miband6_new_auth_protocol_explanation.xml b/app/src/main/res/xml/devicesettings_miband6_new_auth_protocol_explanation.xml
new file mode 100644
index 0000000..4a5c001
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_miband6_new_auth_protocol_explanation.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <Preference
+        android:icon="@drawable/ic_open_in_browser"
+        android:persistent="false"
+        android:key="authkey_explanation_new_auth_protocol"
+        android:summary="@string/pref_explanation_authkey_new_protocol" >
+        <intent
+            android:action="android.intent.action.VIEW"
+            android:data="https://gadgetbridge.org/gadgets/wearables/xiaomi/#device__xiaomi_mi_band_6"
+            />
+    </Preference>
+
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_miband6_new_protocol.xml b/app/src/main/res/xml/devicesettings_miband6_new_protocol.xml
new file mode 100644
index 0000000..35c7624
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_miband6_new_protocol.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <SwitchPreferenceCompat
+        android:defaultValue="false"
+        android:icon="@drawable/ic_mtu"
+        android:key="force_new_protocol"
+        android:layout="@layout/preference_checkbox"
+        android:summary="@string/pref_summary_huami_force_new_protocol"
+        android:title="@string/pref_title_huami_force_new_protocol" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_miband_vibrationpatterns.xml b/app/src/main/res/xml/devicesettings_miband_vibrationpatterns.xml
new file mode 100644
index 0000000..b2ab8dc
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_miband_vibrationpatterns.xml
@@ -0,0 +1,248 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+    <PreferenceScreen
+        android:icon="@drawable/ic_vibration"
+        android:key="vibration_patterns_key"
+        android:persistent="false"
+        android:summary="@string/pref_screen_vibration_patterns_summary"
+        android:title="@string/pref_screen_vibration_patterns_title">
+
+        <PreferenceScreen
+            android:icon="@drawable/ic_message_outline"
+            android:key="vibration_profile_key_generic_sms"
+            android:persistent="false"
+            android:title="@string/pref_screen_notification_profile_sms">
+
+            <ListPreference
+                android:defaultValue="@string/p_staccato"
+                android:entries="@array/vibration_profile"
+                android:entryValues="@array/vibration_profile_values"
+                android:key="mi_vibration_profile_generic_sms"
+                android:title="@string/miband_prefs_vibration"
+                app:iconSpaceReserved="false"
+                app:useSimpleSummaryProvider="true" />
+
+            <EditTextPreference
+                android:defaultValue="3"
+                android:inputType="number"
+                android:key="mi_vibration_count_generic_sms"
+                android:maxLength="1"
+                android:title="@string/pref_title_notifications_repetitions"
+                app:iconSpaceReserved="false"
+                app:useSimpleSummaryProvider="true" />
+
+            <Preference
+                android:key="mi_try_generic_sms"
+                android:persistent="false"
+                android:title="@string/vibration_try"
+                app:iconSpaceReserved="false" />
+        </PreferenceScreen>
+
+        <PreferenceScreen
+            android:icon="@drawable/ic_phone"
+            android:key="vibration_profile_key_incoming_call"
+            android:persistent="false"
+            android:title="@string/pref_screen_notification_profile_incoming_call">
+
+            <ListPreference
+                android:defaultValue="@string/p_ring"
+                android:entries="@array/vibration_profile"
+                android:entryValues="@array/vibration_profile_values"
+                android:key="mi_vibration_profile_incoming_call"
+                android:title="@string/miband_prefs_vibration"
+                app:iconSpaceReserved="false"
+                app:useSimpleSummaryProvider="true" />
+
+            <EditTextPreference
+                android:defaultValue="60"
+                android:inputType="number"
+                android:key="mi_vibration_count_incoming_call"
+                android:maxLength="2"
+                android:title="@string/pref_title_notifications_repetitions"
+                app:iconSpaceReserved="false"
+                app:useSimpleSummaryProvider="true" />
+        </PreferenceScreen>
+
+        <PreferenceScreen
+            android:icon="@drawable/ic_email"
+            android:key="vibration_profile_key_generic_email"
+            android:persistent="false"
+            android:title="@string/pref_screen_notification_profile_email">
+
+            <ListPreference
+                android:defaultValue="@string/p_medium"
+                android:entries="@array/vibration_profile"
+                android:entryValues="@array/vibration_profile_values"
+                android:key="mi_vibration_profile_generic_email"
+                android:title="@string/miband_prefs_vibration"
+                app:iconSpaceReserved="false"
+                app:useSimpleSummaryProvider="true" />
+
+            <EditTextPreference
+                android:defaultValue="2"
+                android:inputType="number"
+                android:key="mi_vibration_count_generic_email"
+                android:maxLength="1"
+                android:title="@string/pref_title_notifications_repetitions"
+                app:iconSpaceReserved="false"
+                app:useSimpleSummaryProvider="true" />
+            <Preference
+                android:key="mi_try_generic_email"
+                android:persistent="false"
+                android:title="@string/vibration_try"
+                app:iconSpaceReserved="false" />
+        </PreferenceScreen>
+
+        <PreferenceScreen
+            android:icon="@drawable/ic_message_outline"
+            android:key="vibration_profile_key_generic_chat"
+            android:persistent="false"
+            android:title="@string/pref_screen_notification_profile_generic_chat">
+
+            <ListPreference
+                android:defaultValue="@string/p_waterdrop"
+                android:entries="@array/vibration_profile"
+                android:entryValues="@array/vibration_profile_values"
+                android:key="mi_vibration_profile_generic_chat"
+                android:title="@string/miband_prefs_vibration"
+                app:iconSpaceReserved="false"
+                app:useSimpleSummaryProvider="true" />
+
+            <EditTextPreference
+                android:defaultValue="3"
+                android:inputType="number"
+                android:key="mi_vibration_count_generic_chat"
+                android:maxLength="1"
+                android:title="@string/pref_title_notifications_repetitions"
+                app:iconSpaceReserved="false"
+                app:useSimpleSummaryProvider="true" />
+            <Preference
+                android:key="mi_try_generic_chat"
+                android:persistent="false"
+                android:title="@string/vibration_try"
+                app:iconSpaceReserved="false" />
+        </PreferenceScreen>
+
+        <PreferenceScreen
+            android:icon="@drawable/ic_person"
+            android:key="vibration_profile_key_generic_social"
+            android:persistent="false"
+            android:title="@string/pref_screen_notification_profile_generic_social">
+
+            <ListPreference
+                android:defaultValue="@string/p_waterdrop"
+                android:entries="@array/vibration_profile"
+                android:entryValues="@array/vibration_profile_values"
+                android:key="mi_vibration_profile_generic_social"
+                android:title="@string/miband_prefs_vibration"
+                app:iconSpaceReserved="false"
+                app:useSimpleSummaryProvider="true" />
+
+            <EditTextPreference
+                android:defaultValue="3"
+                android:inputType="number"
+                android:key="mi_vibration_count_generic_social"
+                android:maxLength="1"
+                android:title="@string/pref_title_notifications_repetitions"
+                app:iconSpaceReserved="false"
+                app:useSimpleSummaryProvider="true" />
+            <Preference
+                android:key="mi_try_generic_social"
+                android:persistent="false"
+                android:title="@string/vibration_try"
+                app:iconSpaceReserved="false" />
+        </PreferenceScreen>
+
+        <PreferenceScreen
+            android:icon="@drawable/ic_access_alarms"
+            android:key="vibration_profile_key_alarm_clock"
+            android:persistent="false"
+            android:title="@string/pref_screen_notification_profile_alarm_clock">
+
+            <ListPreference
+                android:defaultValue="@string/p_alarm_clock"
+                android:entries="@array/vibration_profile"
+                android:entryValues="@array/vibration_profile_values"
+                android:key="mi_vibration_profile_alarm_clock"
+                android:title="@string/miband_prefs_vibration"
+                app:iconSpaceReserved="false"
+                app:useSimpleSummaryProvider="true" />
+
+            <EditTextPreference
+                android:defaultValue="3"
+                android:inputType="number"
+                android:key="mi_vibration_count_alarm_clock"
+                android:maxLength="2"
+                android:title="@string/pref_title_notifications_repetitions"
+                app:iconSpaceReserved="false"
+                app:useSimpleSummaryProvider="true" />
+            <Preference
+                android:key="mi_try_generic_alarm_clock"
+                android:persistent="false"
+                android:title="@string/vibration_try"
+                app:iconSpaceReserved="false" />
+        </PreferenceScreen>
+
+        <PreferenceScreen
+            android:icon="@drawable/ic_map"
+            android:key="vibration_profile_key_generic_navigation"
+            android:persistent="false"
+            android:title="@string/pref_screen_notification_profile_generic_navigation">
+
+            <ListPreference
+                android:defaultValue="@string/p_waterdrop"
+                android:entries="@array/vibration_profile"
+                android:entryValues="@array/vibration_profile_values"
+                android:key="mi_vibration_profile_generic_navigation"
+                android:title="@string/miband_prefs_vibration"
+                app:iconSpaceReserved="false"
+                app:useSimpleSummaryProvider="true" />
+
+            <EditTextPreference
+                android:defaultValue="3"
+                android:inputType="number"
+                android:key="mi_vibration_count_generic_navigation"
+                android:maxLength="1"
+                android:title="@string/pref_title_notifications_repetitions"
+                app:iconSpaceReserved="false"
+                app:useSimpleSummaryProvider="true" />
+            <Preference
+                android:key="mi_try_generic_navigation"
+                android:persistent="false"
+                android:title="@string/vibration_try"
+                app:iconSpaceReserved="false" />
+        </PreferenceScreen>
+
+        <PreferenceScreen
+            android:icon="@drawable/ic_notifications"
+            android:key="vibration_profile_key_generic"
+            android:persistent="false"
+            android:title="@string/pref_screen_notification_profile_generic">
+
+            <ListPreference
+                android:defaultValue="@string/p_waterdrop"
+                android:entries="@array/vibration_profile"
+                android:entryValues="@array/vibration_profile_values"
+                android:key="mi_vibration_profile_generic"
+                android:title="@string/miband_prefs_vibration"
+                app:iconSpaceReserved="false"
+                app:useSimpleSummaryProvider="true" />
+
+            <EditTextPreference
+                android:defaultValue="3"
+                android:inputType="number"
+                android:key="mi_vibration_count_generic"
+                android:maxLength="1"
+                android:title="@string/pref_title_notifications_repetitions"
+                app:iconSpaceReserved="false"
+                app:useSimpleSummaryProvider="true" />
+            <Preference
+                android:key="mi_try_generic"
+                android:persistent="false"
+                android:title="@string/vibration_try"
+                app:iconSpaceReserved="false" />
+        </PreferenceScreen>
+
+    </PreferenceScreen>
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_mijia_lywsd.xml b/app/src/main/res/xml/devicesettings_mijia_lywsd.xml
new file mode 100644
index 0000000..8f89436
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_mijia_lywsd.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+    <PreferenceScreen
+        android:persistent="false"
+        android:icon="@drawable/ic_chair"
+        android:key="pref_mijia_lywsd_comfort_level"
+        android:title="@string/mijia_lywsd_comfort_level_title"
+        android:summary="@string/mijia_lywsd_comfort_level_summary">
+        <PreferenceCategory
+            android:icon="@drawable/ic_thermometer"
+            android:title="@string/mijia_lywsd_comfort_temperature_title"
+            android:summary="@string/mijia_lywsd_comfort_temperature_summary">
+            <SeekBarPreference
+                android:defaultValue="19"
+                android:max="60"
+                android:key="pref_mijia_lywsd_comfort_temperature_lower"
+                android:title="@string/mijia_lywsd_comfort_lower"
+                app:showSeekBarValue="true" />
+            <SeekBarPreference
+                android:defaultValue="27"
+                android:max="60"
+                android:key="pref_mijia_lywsd_comfort_temperature_upper"
+                android:title="@string/mijia_lywsd_comfort_upper"
+                app:showSeekBarValue="true" />
+        </PreferenceCategory>
+        <PreferenceCategory
+            android:icon="@drawable/ic_humidity_mid"
+            android:title="@string/mijia_lywsd_comfort_humidity_title"
+            android:summary="@string/mijia_lywsd_comfort_humidity_summary">
+            <SeekBarPreference
+                android:defaultValue="20"
+                android:max="100"
+                android:key="pref_mijia_lywsd_comfort_humidity_lower"
+                android:title="@string/mijia_lywsd_comfort_lower"
+                app:showSeekBarValue="true" />
+            <SeekBarPreference
+                android:defaultValue="85"
+                android:max="100"
+                android:key="pref_mijia_lywsd_comfort_humidity_upper"
+                android:title="@string/mijia_lywsd_comfort_upper"
+                app:showSeekBarValue="true" />
+        </PreferenceCategory>
+    </PreferenceScreen>
+</PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_mismartscale.xml b/app/src/main/res/xml/devicesettings_mismartscale.xml
new file mode 100644
index 0000000..767b533
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_mismartscale.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <ListPreference
+        android:defaultValue="0"
+        android:entries="@array/miscale_weight_unit_names"
+        android:entryValues="@array/miscale_weight_unit_values"
+        android:icon="@drawable/ic_calculate"
+        android:key="pref_miscale_weight_unit"
+        android:title="@string/miscale_weight_unit_title"
+        android:summary="@string/miscale_weight_unit_summary" />
+    <SwitchPreferenceCompat
+        android:defaultValue="false"
+        android:icon="@drawable/ic_pressure"
+        android:key="pref_miscale_small_objects"
+        android:layout="@layout/preference_checkbox"
+        android:title="@string/miscale_small_objects_title"
+        android:summary="@string/miscale_small_objects_summary" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_moondrop_space_travel_equalizer.xml b/app/src/main/res/xml/devicesettings_moondrop_space_travel_equalizer.xml
new file mode 100644
index 0000000..34e04bb
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_moondrop_space_travel_equalizer.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <ListPreference
+        android:entries="@array/moondrop_equalizer_preset_names"
+        android:entryValues="@array/moondrop_equalizer_preset_values"
+        android:icon="@drawable/ic_graphic_eq"
+        android:key="pref_moondrop_equalizer_preset"
+        android:summary="%s"
+        android:title="@string/prefs_equalizer_preset" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_moondrop_space_travel_touch.xml b/app/src/main/res/xml/devicesettings_moondrop_space_travel_touch.xml
new file mode 100644
index 0000000..cceaab5
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_moondrop_space_travel_touch.xml
@@ -0,0 +1,115 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceCategory android:title="@string/moondrop_touch_action_play_pause">
+        <ListPreference
+            android:entries="@array/moondrop_touch_earbud_names"
+            android:entryValues="@array/moondrop_touch_earbud_values"
+            android:icon="@drawable/ic_hearing"
+            android:key="pref_moondrop_touch_play_pause_earbud"
+            android:summary="%s"
+            android:title="@string/moondrop_touch_earbud" />
+        <ListPreference
+            android:entries="@array/moondrop_touch_trigger_names"
+            android:entryValues="@array/moondrop_touch_trigger_values"
+            android:icon="@drawable/ic_touch"
+            android:key="pref_moondrop_touch_play_pause_trigger"
+            android:summary="%s"
+            android:title="@string/moondrop_touch_trigger" />
+    </PreferenceCategory>
+    <PreferenceCategory android:title="@string/pref_media_previous">
+        <ListPreference
+            android:entries="@array/moondrop_touch_earbud_names"
+            android:entryValues="@array/moondrop_touch_earbud_values"
+            android:icon="@drawable/ic_hearing"
+            android:key="pref_moondrop_touch_media_prev_earbud"
+            android:summary="%s"
+            android:title="@string/moondrop_touch_earbud" />
+        <ListPreference
+            android:entries="@array/moondrop_touch_trigger_names"
+            android:entryValues="@array/moondrop_touch_trigger_values"
+            android:icon="@drawable/ic_touch"
+            android:key="pref_moondrop_touch_media_prev_trigger"
+            android:summary="%s"
+            android:title="@string/moondrop_touch_trigger" />
+    </PreferenceCategory>
+    <PreferenceCategory android:title="@string/pref_media_next">
+        <ListPreference
+            android:entries="@array/moondrop_touch_earbud_names"
+            android:entryValues="@array/moondrop_touch_earbud_values"
+            android:icon="@drawable/ic_hearing"
+            android:key="pref_moondrop_touch_media_next_earbud"
+            android:summary="%s"
+            android:title="@string/moondrop_touch_earbud" />
+        <ListPreference
+            android:entries="@array/moondrop_touch_trigger_names"
+            android:entryValues="@array/moondrop_touch_trigger_values"
+            android:icon="@drawable/ic_touch"
+            android:key="pref_moondrop_touch_media_next_trigger"
+            android:summary="%s"
+            android:title="@string/moondrop_touch_trigger" />
+    </PreferenceCategory>
+    <PreferenceCategory android:title="@string/moondrop_touch_action_call_pick_hang">
+        <ListPreference
+            android:entries="@array/moondrop_touch_earbud_names"
+            android:entryValues="@array/moondrop_touch_earbud_values"
+            android:icon="@drawable/ic_hearing"
+            android:key="pref_moondrop_touch_call_pick_hang_earbud"
+            android:summary="%s"
+            android:title="@string/moondrop_touch_earbud" />
+        <ListPreference
+            android:entries="@array/moondrop_touch_trigger_names"
+            android:entryValues="@array/moondrop_touch_trigger_values"
+            android:icon="@drawable/ic_touch"
+            android:key="pref_moondrop_touch_call_pick_hang_trigger"
+            android:summary="%s"
+            android:title="@string/moondrop_touch_trigger" />
+    </PreferenceCategory>
+    <PreferenceCategory android:title="@string/moondrop_touch_action_call_start">
+        <ListPreference
+            android:entries="@array/moondrop_touch_earbud_names"
+            android:entryValues="@array/moondrop_touch_earbud_values"
+            android:icon="@drawable/ic_hearing"
+            android:key="pref_moondrop_touch_call_start_earbud"
+            android:summary="%s"
+            android:title="@string/moondrop_touch_earbud" />
+        <ListPreference
+            android:entries="@array/moondrop_touch_trigger_names"
+            android:entryValues="@array/moondrop_touch_trigger_values"
+            android:icon="@drawable/ic_touch"
+            android:key="pref_moondrop_touch_call_start_trigger"
+            android:summary="%s"
+            android:title="@string/moondrop_touch_trigger" />
+    </PreferenceCategory>
+    <PreferenceCategory android:title="@string/moondrop_touch_action_assistant">
+        <ListPreference
+            android:entries="@array/moondrop_touch_earbud_names"
+            android:entryValues="@array/moondrop_touch_earbud_values"
+            android:icon="@drawable/ic_hearing"
+            android:key="pref_moondrop_touch_assistant_earbud"
+            android:summary="%s"
+            android:title="@string/moondrop_touch_earbud" />
+        <ListPreference
+            android:entries="@array/moondrop_touch_trigger_names"
+            android:entryValues="@array/moondrop_touch_trigger_values"
+            android:icon="@drawable/ic_touch"
+            android:key="pref_moondrop_touch_assistant_trigger"
+            android:summary="%s"
+            android:title="@string/moondrop_touch_trigger" />
+    </PreferenceCategory>
+    <PreferenceCategory android:title="@string/moondrop_touch_action_anc_mode">
+        <ListPreference
+            android:entries="@array/moondrop_touch_earbud_names"
+            android:entryValues="@array/moondrop_touch_earbud_values"
+            android:icon="@drawable/ic_hearing"
+            android:key="pref_moondrop_touch_anc_mode_earbud"
+            android:summary="%s"
+            android:title="@string/moondrop_touch_earbud" />
+        <ListPreference
+            android:entries="@array/moondrop_touch_trigger_names"
+            android:entryValues="@array/moondrop_touch_trigger_values"
+            android:icon="@drawable/ic_touch"
+            android:key="pref_moondrop_touch_anc_mode_trigger"
+            android:summary="%s"
+            android:title="@string/moondrop_touch_trigger" />
+    </PreferenceCategory>
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_morning_updates.xml b/app/src/main/res/xml/devicesettings_morning_updates.xml
new file mode 100644
index 0000000..fb5d52b
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_morning_updates.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceScreen
+        android:icon="@drawable/ic_wb_sunny"
+        android:key="pref_morning_updates"
+        android:persistent="false"
+        android:summary="@string/pref_morning_updates_summary"
+        android:title="@string/pref_morning_updates_title">
+
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:icon="@drawable/ic_wb_sunny"
+            android:key="morning_updates_enabled"
+            android:layout="@layout/preference_checkbox"
+            android:summary="@string/pref_morning_updates_summary"
+            android:title="@string/pref_morning_updates_title" />
+
+        <com.mobeta.android.dslv.DragSortListPreference
+            android:defaultValue="@array/pref_huami2021_empty_array"
+            android:dialogTitle="@string/pref_morning_updates_categories_title"
+            android:entries="@array/pref_morning_updates_categories"
+            android:entryValues="@array/pref_morning_updates_categories_values"
+            android:icon="@drawable/ic_list_numbered"
+            android:key="morning_updates_categories"
+            android:persistent="true"
+            android:summary="@string/pref_morning_updates_categories_summary"
+            android:title="@string/pref_morning_updates_categories_title" />
+    </PreferenceScreen>
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_musicmanagement.xml b/app/src/main/res/xml/devicesettings_musicmanagement.xml
new file mode 100644
index 0000000..ad0d7d2
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_musicmanagement.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <Preference
+        android:icon="@drawable/ic_music_note"
+        android:key="pref_music_management"
+        android:summary="@string/pref_music_management_summary"
+        android:title="@string/pref_music_management_title" />
+</androidx.preference.PreferenceScreen>
\ No newline at end of file
diff --git a/app/src/main/res/xml/devicesettings_nightmode.xml b/app/src/main/res/xml/devicesettings_nightmode.xml
new file mode 100644
index 0000000..01b0099
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_nightmode.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <PreferenceScreen
+        android:icon="@drawable/ic_brightness_2"
+        android:key="pref_screen_night_mode"
+        android:persistent="false"
+        android:summary="@string/mi3_prefs_night_mode_summary"
+        android:title="@string/mi3_prefs_night_mode">
+
+        <!-- workaround for missing toolbar -->
+        <PreferenceCategory android:title="@string/mi3_prefs_night_mode" />
+
+        <ListPreference
+            android:defaultValue="@string/p_off"
+            android:entries="@array/mi3_night_mode"
+            android:entryValues="@array/mi3_night_mode_values"
+            android:key="night_mode"
+            android:summary="%s"
+            android:title="@string/mi3_prefs_night_mode" />
+
+        <nodomain.freeyourgadget.gadgetbridge.util.XTimePreference
+            android:defaultValue="16:00"
+            android:key="night_mode_start"
+            android:title="@string/mi2_prefs_do_not_disturb_start" />
+
+        <nodomain.freeyourgadget.gadgetbridge.util.XTimePreference
+            android:defaultValue="7:00"
+            android:key="night_mode_end"
+            android:title="@string/mi2_prefs_do_not_disturb_end" />
+
+    </PreferenceScreen>
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_nothing_ear1.xml b/app/src/main/res/xml/devicesettings_nothing_ear1.xml
new file mode 100644
index 0000000..3616079
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_nothing_ear1.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+    <SwitchPreferenceCompat
+        android:defaultValue="true"
+        android:icon="@drawable/ic_extension"
+        android:key="pref_nothing_inear_detection"
+        android:layout="@layout/preference_checkbox"
+        android:summary="@string/nothing_prefs_inear_summary"
+        android:title="@string/nothing_prefs_inear_title" />
+    <ListPreference
+        android:icon="@drawable/ic_extension"
+        android:entryValues="@array/nothing_ear1_audio_mode_values"
+        android:entries="@array/nothing_ear1_audio_mode_names"
+        android:key="pref_nothing_audiomode"
+        android:summary="%s"
+        android:title="@string/nothing_prefs_audiomode_title" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_notification_wake_on_open.xml b/app/src/main/res/xml/devicesettings_notification_wake_on_open.xml
new file mode 100644
index 0000000..f5c6714
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_notification_wake_on_open.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <SwitchPreferenceCompat
+        android:defaultValue="false"
+        android:icon="@drawable/ic_notifications"
+        android:key="notification_wake_on_open"
+        android:layout="@layout/preference_checkbox"
+        android:summary="@string/pref_summary_notification_wake_on_open"
+        android:title="@string/pref_title_notification_wake_on_open" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_notifications_enable.xml b/app/src/main/res/xml/devicesettings_notifications_enable.xml
new file mode 100644
index 0000000..baf58f2
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_notifications_enable.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <SwitchPreferenceCompat
+        android:defaultValue="false"
+        android:icon="@drawable/ic_notifications"
+        android:key="notification_enable"
+        android:layout="@layout/preference_checkbox"
+        android:summary="@string/enable_notifications_summary"
+        android:title="@string/prefs_notifications_enable" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_nutmini.xml b/app/src/main/res/xml/devicesettings_nutmini.xml
new file mode 100644
index 0000000..323dfcd
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_nutmini.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <EditTextPreference
+        android:dialogTitle="Packet key"
+        android:icon="@drawable/ic_lock_open"
+        android:key="nut_packet_challenge"
+        android:title="Challenge" />
+    <EditTextPreference
+        android:dialogTitle="Challenge key"
+        android:icon="@drawable/ic_lock_open"
+        android:key="nut_packet_response"
+        android:title="Response" />
+    <EditTextPreference
+        android:dialogTitle="Challenge key"
+        android:icon="@drawable/ic_lock_open"
+        android:key="nut_packet_key_1"
+        android:title="Known key 1 (overrides)" />
+    <EditTextPreference
+        android:dialogTitle="Challenge key"
+        android:icon="@drawable/ic_lock_open"
+        android:key="nut_packet_key_2"
+        android:title="Known key 2 (overrides)" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_offline_voice.xml b/app/src/main/res/xml/devicesettings_offline_voice.xml
new file mode 100644
index 0000000..0cf86a8
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_offline_voice.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceScreen
+        android:icon="@drawable/ic_voice"
+        android:key="pref_screen_offline_voice"
+        android:persistent="false"
+        android:title="@string/pref_header_offline_voice">
+
+        <ListPreference
+            android:defaultValue="en_US"
+            android:entries="@array/pref_language_all"
+            android:entryValues="@array/pref_language_all_values"
+            android:icon="@drawable/ic_language"
+            android:key="offline_voice_language"
+            android:summary="%s"
+            android:title="@string/pref_title_language" />
+
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:icon="@drawable/ic_rotate_left"
+            android:key="offline_voice_respond_turn_wrist"
+            android:layout="@layout/preference_checkbox"
+            android:title="@string/offline_voice_respond_turn_wrist" />
+
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:icon="@drawable/ic_always_on_display"
+            android:key="offline_voice_respond_screen_on"
+            android:layout="@layout/preference_checkbox"
+            android:title="@string/offline_voice_respond_screen_on" />
+
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:icon="@drawable/ic_always_on_display"
+            android:key="offline_voice_response_during_screen_lighting"
+            android:layout="@layout/preference_checkbox"
+            android:title="@string/offline_voice_response_during_screen_lighting" />
+    </PreferenceScreen>
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_operating_sounds.xml b/app/src/main/res/xml/devicesettings_operating_sounds.xml
new file mode 100644
index 0000000..cd6bfea
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_operating_sounds.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <SwitchPreferenceCompat
+        android:defaultValue="false"
+        android:icon="@drawable/ic_beep"
+        android:key="operating_sounds"
+        android:layout="@layout/preference_checkbox"
+        android:title="@string/prefs_operating_sounds" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_oppo_headphones_touch_options.xml b/app/src/main/res/xml/devicesettings_oppo_headphones_touch_options.xml
new file mode 100644
index 0000000..bae22d4
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_oppo_headphones_touch_options.xml
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+
+    <PreferenceCategory
+        android:key="oppo_touch_header_left"
+        android:title="@string/left_earbud"
+        app:iconSpaceReserved="false">
+        <ListPreference
+            android:defaultValue="off"
+            android:entries="@array/oppo_touch_tap_names"
+            android:entryValues="@array/oppo_touch_tap_values"
+            android:icon="@drawable/ic_filter_2"
+            android:key="oppo_touch__left__tap_2"
+            android:summary="%s"
+            android:title="@string/double_tap" />
+        <ListPreference
+            android:defaultValue="off"
+            android:entries="@array/oppo_touch_tap_names"
+            android:entryValues="@array/oppo_touch_tap_values"
+            android:icon="@drawable/ic_filter_3"
+            android:key="oppo_touch__left__tap_3"
+            android:summary="%s"
+            android:title="@string/triple_tap" />
+        <ListPreference
+            android:defaultValue="off"
+            android:entries="@array/oppo_touch_tap_names"
+            android:entryValues="@array/oppo_touch_tap_values"
+            android:icon="@drawable/ic_horizontal_rule"
+            android:key="oppo_touch__left__hold"
+            android:summary="%s"
+            android:title="@string/long_press" />
+    </PreferenceCategory>
+
+    <PreferenceCategory
+        android:key="oppo_touch_header_right"
+        android:title="@string/right_earbud"
+        app:iconSpaceReserved="false">
+        <ListPreference
+            android:defaultValue="off"
+            android:entries="@array/oppo_touch_tap_names"
+            android:entryValues="@array/oppo_touch_tap_values"
+            android:icon="@drawable/ic_filter_2"
+            android:key="oppo_touch__right__tap_2"
+            android:summary="%s"
+            android:title="@string/double_tap" />
+        <ListPreference
+            android:defaultValue="off"
+            android:entries="@array/oppo_touch_tap_names"
+            android:entryValues="@array/oppo_touch_tap_values"
+            android:icon="@drawable/ic_filter_3"
+            android:key="oppo_touch__right__tap_3"
+            android:summary="%s"
+            android:title="@string/triple_tap" />
+        <ListPreference
+            android:defaultValue="off"
+            android:entries="@array/oppo_touch_tap_names"
+            android:entryValues="@array/oppo_touch_tap_values"
+            android:icon="@drawable/ic_horizontal_rule"
+            android:key="oppo_touch__right__hold"
+            android:summary="%s"
+            android:title="@string/long_press" />
+    </PreferenceCategory>
+
+    <PreferenceCategory
+        android:key="oppo_touch_header_both"
+        android:title="@string/moondrop_touch_earbud_both"
+        app:iconSpaceReserved="false">
+        <ListPreference
+            android:defaultValue="off"
+            android:entries="@array/oppo_touch_tap_names"
+            android:entryValues="@array/oppo_touch_tap_values"
+            android:icon="@drawable/ic_horizontal_rule"
+            android:key="oppo_touch__both__hold"
+            android:summary="%s"
+            android:title="@string/long_press" />
+    </PreferenceCategory>
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_overwrite_settings_on_connection.xml b/app/src/main/res/xml/devicesettings_overwrite_settings_on_connection.xml
new file mode 100644
index 0000000..9dc6721
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_overwrite_settings_on_connection.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <SwitchPreferenceCompat
+        android:defaultValue="true"
+        android:icon="@drawable/ic_settings_applications"
+        android:key="overwrite_settings_on_connection"
+        android:layout="@layout/preference_checkbox"
+        android:summary="@string/pref_summary_overwrite_settings_on_connection"
+        android:title="@string/pref_title_overwrite_settings_on_connection" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_pairingkey.xml b/app/src/main/res/xml/devicesettings_pairingkey.xml
new file mode 100644
index 0000000..422a5c6
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_pairingkey.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <EditTextPreference
+        android:icon="@drawable/ic_vpn_key"
+        android:key="authkey"
+        android:maxLength="16"
+        android:summary="@string/pref_summary_authkey"
+        android:title="@string/pref_title_authkey" />
+
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_pairingkey_explanation.xml b/app/src/main/res/xml/devicesettings_pairingkey_explanation.xml
new file mode 100644
index 0000000..539020f
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_pairingkey_explanation.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <Preference
+        android:icon="@drawable/ic_open_in_browser"
+        android:persistent="false"
+        android:key="authkey_explanation"
+        android:summary="@string/pref_explanation_authkey" >
+        <intent
+            android:action="android.intent.action.VIEW"
+            android:data="https://gadgetbridge.org/basics/pairing/"
+            />
+    </Preference>
+
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_password.xml b/app/src/main/res/xml/devicesettings_password.xml
new file mode 100644
index 0000000..bab7e1a
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_password.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceScreen
+        android:icon="@drawable/ic_password"
+        android:key="pref_screen_password"
+        android:persistent="false"
+        android:summary="@string/prefs_password_summary"
+        android:title="@string/prefs_password">
+
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:icon="@drawable/ic_password"
+            android:key="pref_password_enabled"
+            android:layout="@layout/preference_checkbox"
+            android:summary="@string/prefs_password_summary"
+            android:title="@string/prefs_password_enabled" />
+
+        <EditTextPreference
+            android:dependency="pref_password_enabled"
+            android:icon="@drawable/ic_password"
+            android:inputType="numberPassword"
+            android:key="pref_password"
+            android:title="@string/prefs_password" />
+    </PreferenceScreen>
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_pebble_activity.xml b/app/src/main/res/xml/devicesettings_pebble_activity.xml
new file mode 100644
index 0000000..555d25e
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_pebble_activity.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+
+    <PreferenceCategory
+        android:title="@string/pref_header_activitytrackers"
+        app:iconSpaceReserved="false">
+        <ListPreference
+            android:defaultValue="4"
+            android:entries="@array/pebble_activitytracker"
+            android:entryValues="@array/pebble_activitytracker_values"
+            android:key="pebble_activitytracker"
+            android:summary="%s"
+            android:title="@string/pref_title_pebble_activitytracker"
+            app:iconSpaceReserved="false" />
+        <SwitchPreferenceCompat
+            android:defaultValue="true"
+            android:key="pebble_sync_health"
+            android:layout="@layout/preference_checkbox"
+            android:title="@string/pref_title_pebble_sync_health"
+            app:iconSpaceReserved="false" />
+        <SwitchPreferenceCompat
+            android:defaultValue="true"
+            android:dependency="pebble_sync_health"
+            android:key="pebble_health_store_raw"
+            android:layout="@layout/preference_checkbox"
+            android:summary="@string/pref_summary_pebble_health_store_raw"
+            android:title="@string/pref_title_pebble_health_store_raw"
+            app:iconSpaceReserved="false" />
+        <SwitchPreferenceCompat
+            android:defaultValue="true"
+            android:key="pebble_sync_misfit"
+            android:layout="@layout/preference_checkbox"
+            android:title="@string/pref_title_pebble_sync_misfit"
+            app:iconSpaceReserved="false" />>
+        <SwitchPreferenceCompat
+            android:defaultValue="true"
+            android:key="pebble_sync_morpheuz"
+            android:layout="@layout/preference_checkbox"
+            android:title="@string/pref_title_pebble_sync_morpheuz"
+            app:iconSpaceReserved="false" />
+    </PreferenceCategory>
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_pebble_calendar.xml b/app/src/main/res/xml/devicesettings_pebble_calendar.xml
new file mode 100644
index 0000000..4fbfb2b
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_pebble_calendar.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+
+<PreferenceCategory
+        android:title="@string/pref_header_pebble_timeline"
+        app:iconSpaceReserved="false">
+        <SwitchPreferenceCompat
+            android:key="send_sunrise_sunset"
+            android:layout="@layout/preference_checkbox"
+            android:summary="@string/pref_summary_sunrise_sunset"
+            android:title="@string/pref_title_sunrise_sunset"
+            app:iconSpaceReserved="false" />
+    </PreferenceCategory>
+
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_pebble_calls_notifications.xml b/app/src/main/res/xml/devicesettings_pebble_calls_notifications.xml
new file mode 100644
index 0000000..7a21183
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_pebble_calls_notifications.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+    <ListPreference
+        android:defaultValue="@string/p_pebble_privacy_mode_off"
+        android:entries="@array/pebble_privacymode"
+        android:entryValues="@array/pebble_privacymode_values"
+        android:key="pebble_pref_privacy_mode"
+        android:summary="%s"
+        android:title="@string/pref_title_pebble_privacy_mode"
+        app:iconSpaceReserved="false" />
+    <SwitchPreferenceCompat
+        android:defaultValue="true"
+        android:key="pebble_enable_outgoing_call"
+        android:layout="@layout/preference_checkbox"
+        android:summary="@string/pref_summary_enable_outgoing_call"
+        android:title="@string/pref_title_enable_outgoing_call"
+        app:iconSpaceReserved="false" />
+</androidx.preference.PreferenceScreen>
\ No newline at end of file
diff --git a/app/src/main/res/xml/devicesettings_pebble_developer.xml b/app/src/main/res/xml/devicesettings_pebble_developer.xml
new file mode 100644
index 0000000..0f40e12
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_pebble_developer.xml
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+
+    <PreferenceCategory
+        android:title="@string/pref_header_development"
+        app:iconSpaceReserved="false">
+        <EditTextPreference
+            android:defaultValue="512"
+            android:inputType="number"
+            android:key="pebble_mtu_limit"
+            android:maxLength="3"
+            android:summary="@string/pref_summary_pebble_mtu_limit"
+            android:title="@string/pref_title_pebble_mtu_limit"
+            app:iconSpaceReserved="false" />
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:key="pebble_force_untested"
+            android:layout="@layout/preference_checkbox"
+            android:summary="@string/pref_summary_pebble_forceuntested"
+            android:title="@string/pref_title_pebble_forceuntested"
+            app:iconSpaceReserved="false" />
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:key="pebble_force_protocol"
+            android:layout="@layout/preference_checkbox"
+            android:summary="@string/pref_summary_pebble_forceprotocol"
+            android:title="@string/pref_title_pebble_forceprotocol"
+            app:iconSpaceReserved="false" />
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:key="pebble_gatt_clientonly"
+            android:layout="@layout/preference_checkbox"
+            android:summary="@string/pref_summary_pebble_gatt_clientonly"
+            android:title="@string/pref_title_pebble_gatt_clientonly"
+            app:iconSpaceReserved="false" />
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:key="pebble_enable_applogs"
+            android:layout="@layout/preference_checkbox"
+            android:summary="@string/pref_summary_pebble_enable_applogs"
+            android:title="@string/pref_title_pebble_enable_applogs"
+            app:iconSpaceReserved="false" />
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:key="pebble_always_ack_pebblekit"
+            android:layout="@layout/preference_checkbox"
+            android:summary="@string/pref_summary_pebble_always_ack_pebblekit"
+            android:title="@string/pref_title_pebble_always_ack_pebblekit"
+            app:iconSpaceReserved="false" />
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:dependency="pebble_force_untested"
+            android:key="pebble_enable_background_javascript"
+            android:layout="@layout/preference_checkbox"
+            android:summary="@string/pref_summary_pebble_enable_bgjs"
+            android:title="@string/pref_title_pebble_enable_bgjs"
+            app:iconSpaceReserved="false" />
+    </PreferenceCategory>
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_phone_calls_watch_pair.xml b/app/src/main/res/xml/devicesettings_phone_calls_watch_pair.xml
new file mode 100644
index 0000000..1717c9e
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_phone_calls_watch_pair.xml
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <PreferenceScreen
+        android:icon="@drawable/ic_phone"
+        android:key="pref_phone_calls_header"
+        android:persistent="false"
+        android:summary="@string/pref_summary_receive_calls_watch"
+        android:title="@string/pref_title_notifications_call">
+
+        <PreferenceCategory
+            android:key="pref_bluetooth_calls_pairing_header"
+            android:title="@string/bluetooth_calls_pairing">
+
+            <Preference
+                android:icon="@drawable/ic_info"
+                android:key="phone_calls_pair_info"
+                android:summary="@string/pref_pair_bluetooth_calls_help_summary" />
+
+            <Preference
+                android:key="phone_calls_pair_info_1"
+                android:summary="@string/pref_pair_bluetooth_calls_help_1" />
+
+            <Preference
+                android:key="phone_calls_pair_info_2"
+                android:summary="@string/pref_pair_bluetooth_calls_help_2" />
+
+            <Preference
+                android:key="phone_calls_pair_info_3"
+                android:summary="@string/pref_pair_bluetooth_calls_help_3" />
+
+            <Preference
+                android:icon="@drawable/ic_warning_gray"
+                android:key="phone_calls_pair_info_warning"
+                android:summary="@string/pref_pair_bluetooth_calls_help_warning" />
+
+            <Preference
+                android:icon="@drawable/ic_link"
+                android:key="bluetooth_calls_pair"
+                android:summary="@string/pref_pair_bluetooth_calls_summary"
+                android:title="@string/pref_pair_bluetooth_calls_title" />
+        </PreferenceCategory>
+
+        <PreferenceCategory
+            android:key="pref_bluetooth_calls_settings_header"
+            android:title="@string/bluetooth_calls_settings">
+
+            <SwitchPreferenceCompat
+                android:defaultValue="false"
+                android:icon="@drawable/ic_voice"
+                android:key="bluetooth_calls_enabled"
+                android:layout="@layout/preference_checkbox"
+                android:summary="@string/pref_summary_receive_calls_watch"
+                android:title="@string/bluetooth_calls" />
+
+            <SwitchPreferenceCompat
+                android:defaultValue="true"
+                android:icon="@drawable/ic_person"
+                android:key="display_caller"
+                android:layout="@layout/preference_checkbox"
+                android:summary="@string/pref_display_caller_summary"
+                android:title="@string/pref_display_caller_title" />
+
+            <EditTextPreference
+                android:defaultValue="0"
+                android:digits="0123456789"
+                android:icon="@drawable/ic_access_time"
+                android:inputType="number"
+                android:key="notification_delay_calls"
+                android:maxLength="2"
+                android:summary="@string/pref_summary_notification_delay_calls"
+                android:title="@string/pref_title_notification_delay_calls" />
+        </PreferenceCategory>
+
+    </PreferenceScreen>
+
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_phone_silent_mode.xml b/app/src/main/res/xml/devicesettings_phone_silent_mode.xml
new file mode 100644
index 0000000..1866d10
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_phone_silent_mode.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+    <ListPreference
+        android:defaultValue="normal_silent"
+        android:entries="@array/phone_silent_mode"
+        android:entryValues="@array/phone_silent_mode_values"
+        android:icon="@drawable/ic_volume_off"
+        android:key="phone_silent_mode"
+        android:title="@string/prefs_phone_silent_mode"
+        app:useSimpleSummaryProvider="true" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_pixoo.xml b/app/src/main/res/xml/devicesettings_pixoo.xml
new file mode 100644
index 0000000..635aad0
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_pixoo.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <SwitchPreferenceCompat
+        android:icon="@drawable/ic_power_settings_new"
+        android:key="pref_key_clap_hands_to_wakeup_device"
+        android:layout="@layout/preference_checkbox"
+        android:title="@string/clap_hands_to_wakeup_device"
+        android:summary="@string/clap_hands_to_wakeup_device_summary"
+        android:defaultValue="false" />
+    <SwitchPreferenceCompat
+        android:icon="@drawable/ic_music_note"
+        android:key="pref_key_power_saving"
+        android:layout="@layout/preference_checkbox"
+        android:title="@string/power_saving"
+        android:summary="@string/pixoo_power_saving_summary"
+        android:defaultValue="false" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_power_mode.xml b/app/src/main/res/xml/devicesettings_power_mode.xml
new file mode 100644
index 0000000..038cc30
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_power_mode.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <ListPreference
+        android:icon="@drawable/ic_power_settings_new"
+        android:defaultValue="0"
+        android:entries="@array/power_mode"
+        android:entryValues="@array/power_mode_values"
+        android:key="power_mode"
+        android:summary="%s"
+        android:title="@string/power_mode_title" />
+</androidx.preference.PreferenceScreen>
\ No newline at end of file
diff --git a/app/src/main/res/xml/devicesettings_power_saving.xml b/app/src/main/res/xml/devicesettings_power_saving.xml
new file mode 100644
index 0000000..3ca804e
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_power_saving.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <SwitchPreferenceCompat
+        android:defaultValue="true"
+        android:icon="@drawable/ic_power_settings_new"
+        android:key="pref_key_power_saving"
+        android:layout="@layout/preference_checkbox"
+        android:title="@string/power_saving" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_prefix_notification_with_app.xml b/app/src/main/res/xml/devicesettings_prefix_notification_with_app.xml
new file mode 100644
index 0000000..842f96f
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_prefix_notification_with_app.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <SwitchPreferenceCompat
+        android:defaultValue="true"
+        android:icon="@drawable/ic_notifications"
+        android:key="pref_prefix_notification_with_app"
+        android:layout="@layout/preference_checkbox"
+        android:title="@string/pref_title_prefix_notification_with_app"
+        android:summary="@string/pref_summary_prefix_notification_with_app" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_preview_message_in_title.xml b/app/src/main/res/xml/devicesettings_preview_message_in_title.xml
new file mode 100644
index 0000000..c74d068
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_preview_message_in_title.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <SwitchPreferenceCompat
+        android:defaultValue="true"
+        android:icon="@android:drawable/ic_menu_view"
+        android:key="preview_message_in_title"
+        android:layout="@layout/preference_checkbox"
+        android:summary="@string/pref_summary_preview_message_in_title"
+        android:title="@string/pref_title_preview_message_in_title" />
+</androidx.preference.PreferenceScreen>
\ No newline at end of file
diff --git a/app/src/main/res/xml/devicesettings_qc35.xml b/app/src/main/res/xml/devicesettings_qc35.xml
new file mode 100644
index 0000000..e220130
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_qc35.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <SeekBarPreference
+        android:title="Noise cancelling level"
+        android:max="2"
+        android:key="qc35_noise_cancelling_level" />
+
+</PreferenceScreen>
\ No newline at end of file
diff --git a/app/src/main/res/xml/devicesettings_reconnect_bl_classic.xml b/app/src/main/res/xml/devicesettings_reconnect_bl_classic.xml
new file mode 100644
index 0000000..139fd4c
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_reconnect_bl_classic.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceCategory
+        android:title="@string/connection_over_bt_classic" />
+
+    <SwitchPreferenceCompat
+        android:icon="@drawable/ic_bluetooth"
+        android:key="prefs_key_device_reconnect_on_acl"
+        android:layout="@layout/preference_checkbox"
+        android:title="@string/autoconnect_from_device_title"
+        android:summary="@string/autoconnect_from_device_summary" />
+</PreferenceScreen>
\ No newline at end of file
diff --git a/app/src/main/res/xml/devicesettings_reconnect_ble.xml b/app/src/main/res/xml/devicesettings_reconnect_ble.xml
new file mode 100644
index 0000000..1914ed3
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_reconnect_ble.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceCategory
+        android:title="@string/connection_over_ble" />
+
+    <SwitchPreferenceCompat
+        android:icon="@drawable/ic_bluetooth"
+        android:key="prefs_key_device_auto_reconnect"
+        android:layout="@layout/preference_checkbox"
+        android:title="@string/auto_reconnect_ble_title"
+        android:summary="@string/auto_reconnect_ble_summary"
+        android:defaultValue="true" />
+</PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_redmibuds5pro_gestures.xml b/app/src/main/res/xml/devicesettings_redmibuds5pro_gestures.xml
new file mode 100644
index 0000000..fe413b1
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_redmibuds5pro_gestures.xml
@@ -0,0 +1,123 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceCategory
+        android:key="pref_key_header_redmibuds5pro_system"
+        android:title="@string/pref_header_system">
+
+        <PreferenceScreen
+            android:icon="@drawable/ic_touch"
+            android:key="pref_screen_redmibuds5pro_touch_options"
+            android:persistent="false"
+            android:title="@string/prefs_galaxy_touch_options">
+
+            <PreferenceCategory android:title="@string/single_tap">
+                <ListPreference
+                    android:entries="@array/redmi_buds_5_pro_single_button_function_names"
+                    android:entryValues="@array/redmi_buds_5_pro_single_button_function_values"
+                    android:icon="@drawable/ic_filter_1"
+                    android:defaultValue="8"
+                    android:key="pref_redmi_buds_5_pro_control_single_tap_left"
+                    android:summary="%s"
+                    android:title="@string/prefs_left" />
+                <ListPreference
+                    android:entries="@array/redmi_buds_5_pro_single_button_function_names"
+                    android:entryValues="@array/redmi_buds_5_pro_single_button_function_values"
+                    android:defaultValue="8"
+                    android:key="pref_redmi_buds_5_pro_control_single_tap_right"
+                    android:summary="%s"
+                    android:title="@string/prefs_right" />
+            </PreferenceCategory>
+
+            <PreferenceCategory android:title="@string/double_tap">
+                <ListPreference
+                    android:entries="@array/redmi_buds_5_pro_button_function_names"
+                    android:entryValues="@array/redmi_buds_5_pro_button_function_values"
+                    android:icon="@drawable/ic_filter_2"
+                    android:defaultValue="1"
+                    android:key="pref_redmi_buds_5_pro_control_double_tap_left"
+                    android:summary="%s"
+                    android:title="@string/prefs_left" />
+                <ListPreference
+                    android:entries="@array/redmi_buds_5_pro_button_function_names"
+                    android:entryValues="@array/redmi_buds_5_pro_button_function_values"
+                    android:defaultValue="1"
+                    android:key="pref_redmi_buds_5_pro_control_double_tap_right"
+                    android:summary="%s"
+                    android:title="@string/prefs_right" />
+            </PreferenceCategory>
+
+            <PreferenceCategory android:title="@string/triple_tap">
+                <ListPreference
+                    android:entries="@array/redmi_buds_5_pro_button_function_names"
+                    android:entryValues="@array/redmi_buds_5_pro_button_function_values"
+                    android:icon="@drawable/ic_filter_3"
+                    android:defaultValue="2"
+                    android:key="pref_redmi_buds_5_pro_control_triple_tap_left"
+                    android:summary="%s"
+                    android:title="@string/prefs_left" />
+                <ListPreference
+                    android:entries="@array/redmi_buds_5_pro_button_function_names"
+                    android:entryValues="@array/redmi_buds_5_pro_button_function_values"
+                    android:defaultValue="3"
+                    android:key="pref_redmi_buds_5_pro_control_triple_tap_right"
+                    android:summary="%s"
+                    android:title="@string/prefs_right" />
+            </PreferenceCategory>
+
+            <PreferenceCategory android:title="@string/long_press">
+                <ListPreference
+                    android:entries="@array/redmi_buds_5_pro_long_button_mode_names"
+                    android:entryValues="@array/redmi_buds_5_pro_long_button_mode_values"
+                    android:icon="@drawable/ic_touch"
+                    android:defaultValue="6"
+                    android:key="pref_redmi_buds_5_pro_control_long_tap_mode_left"
+                    android:summary="%s"
+                    android:title="@string/sony_button_mode_left" />
+                <ListPreference
+                    android:entries="@array/redmi_buds_5_pro_long_button_settings_names"
+                    android:entryValues="@array/redmi_buds_5_pro_long_button_settings_values"
+                    android:defaultValue="7"
+                    android:key="pref_redmi_buds_5_pro_control_long_tap_settings_left"
+                    android:summary="%s"
+                    android:title="@string/sony_ambient_sound_control_button_modes" />
+                <ListPreference
+                    android:entries="@array/redmi_buds_5_pro_long_button_mode_names"
+                    android:entryValues="@array/redmi_buds_5_pro_long_button_mode_values"
+                    android:icon="@drawable/ic_touch"
+                    android:defaultValue="6"
+                    android:key="pref_redmi_buds_5_pro_control_long_tap_mode_right"
+                    android:summary="%s"
+                    android:title="@string/sony_button_mode_right" />
+                <ListPreference
+                    android:entries="@array/redmi_buds_5_pro_long_button_settings_names"
+                    android:entryValues="@array/redmi_buds_5_pro_long_button_settings_values"
+                    android:defaultValue="7"
+                    android:key="pref_redmi_buds_5_pro_control_long_tap_settings_right"
+                    android:summary="%s"
+                    android:title="@string/sony_ambient_sound_control_button_modes" />
+            </PreferenceCategory>
+        </PreferenceScreen>
+
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:key="pref_redmi_buds_5_pro_wearing_detection"
+            android:layout="@layout/preference_checkbox"
+            android:icon="@drawable/ic_pause"
+            android:summary="@string/nothing_prefs_inear_summary"
+            android:title="@string/nothing_prefs_inear_title"/>
+
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:icon="@drawable/ic_phone"
+            android:key="pref_redmi_buds_5_pro_auto_reply_phonecall"
+            android:summary="@string/pref_auto_reply_calls_summary"
+            android:title="@string/pref_auto_reply_calls_title"/>
+
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:key="pref_redmi_buds_5_pro_double_connection"
+            android:summary="@string/redmi_buds_5_pro_double_connection_description"
+            android:title="@string/redmi_buds_5_pro_double_connection"/>
+
+    </PreferenceCategory>
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_redmibuds5pro_headphones.xml b/app/src/main/res/xml/devicesettings_redmibuds5pro_headphones.xml
new file mode 100644
index 0000000..c621abc
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_redmibuds5pro_headphones.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceCategory
+        android:key="pref_key_header_redmibuds5pro_ambient_sound_control"
+        android:title="@string/pref_header_sony_ambient_sound_control">
+
+        <ListPreference
+            android:defaultValue="0"
+            android:entries="@array/redmi_buds_5_pro_ambient_sound_control_names"
+            android:entryValues="@array/redmi_buds_5_pro_ambient_sound_control_values"
+            android:icon="@drawable/ic_hearing"
+            android:key="pref_redmi_buds_5_pro_ambient_sound_control"
+            android:summary="%s"
+            android:title="@string/sony_ambient_sound" />
+
+        <ListPreference
+            android:defaultValue="0"
+            android:dependency="pref_redmi_buds_5_pro_adaptive_noise_cancelling"
+            android:entries="@array/redmi_buds_5_pro_noise_cancelling_strength_names"
+            android:entryValues="@array/redmi_buds_5_pro_noise_cancelling_strength_values"
+            android:key="pref_redmi_buds_5_pro_noise_cancelling_strength"
+            android:summary="%s"
+            android:title="@string/prefs_active_noise_cancelling_level" />
+
+        <ListPreference
+            android:defaultValue="0"
+            android:entries="@array/redmi_buds_5_pro_transparency_strength_names"
+            android:entryValues="@array/redmi_buds_5_pro_transparency_strength_values"
+            android:key="pref_redmi_buds_5_pro_transparency_strength"
+            android:summary="%s"
+            android:title="@string/redmi_buds_5_pro_transparency_strength" />
+
+        <SwitchPreferenceCompat
+            android:defaultValue="true"
+            android:disableDependentsState="true"
+            android:key="pref_redmi_buds_5_pro_adaptive_noise_cancelling"
+            android:layout="@layout/preference_checkbox"
+            android:summary="@string/pref_adaptive_noise_cancelling_summary"
+            android:title="@string/pref_adaptive_noise_cancelling_title" />
+
+<!--        <SwitchPreferenceCompat-->
+<!--            android:defaultValue="true"-->
+<!--            android:key="pref_redmi_buds_5_pro_personalized_noise_cancelling"-->
+<!--            android:layout="@layout/preference_checkbox"-->
+<!--            android:summary="@string/pref_personalized_noise_cancelling_summary"-->
+<!--            android:title="@string/pref_personalized_noise_cancelling_title" />-->
+
+    </PreferenceCategory>
+
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_redmibuds5pro_sound.xml b/app/src/main/res/xml/devicesettings_redmibuds5pro_sound.xml
new file mode 100644
index 0000000..163026f
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_redmibuds5pro_sound.xml
@@ -0,0 +1,144 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+    <PreferenceCategory
+        android:key="pref_key_header_redmibuds5pro_sound"
+        android:title="@string/pref_header_sound">
+
+        <PreferenceScreen
+            android:icon="@drawable/ic_graphic_eq"
+            android:key="pref_sony_equalizer"
+            android:persistent="false"
+            android:title="@string/pref_header_equalizer">
+
+            <ListPreference
+                android:entries="@array/redmi_buds_5_pro_equalizer_presets_names"
+                android:entryValues="@array/redmi_buds_5_pro_equalizer_presets_values"
+                android:icon="@drawable/ic_graphic_eq"
+                android:defaultValue="0"
+                android:key="pref_redmi_buds_5_pro_equalizer_preset"
+                android:summary="%s"
+                android:title="@string/prefs_equalizer_preset" />
+
+            <PreferenceCategory
+                android:key="pref_key_header_redmibuds5pro_custom_equalizer"
+                android:title="@string/soundcore_equalizer_custom_title">
+
+                <ListPreference
+                    android:entries="@array/redmi_buds_5_pro_equalizer_offsets_names"
+                    android:entryValues="@array/redmi_buds_5_pro_equalizer_offsets_values"
+                    android:icon="@drawable/ic_graphic_eq"
+                    android:defaultValue="0"
+                    android:key="pref_redmi_buds_5_pro_equalizer_band_62"
+                    android:summary="%s"
+                    android:title="@string/redmi_buds_5_pro_equalizer_band_62" />
+
+                <ListPreference
+                    android:entries="@array/redmi_buds_5_pro_equalizer_offsets_names"
+                    android:entryValues="@array/redmi_buds_5_pro_equalizer_offsets_values"
+                    android:icon="@drawable/ic_graphic_eq"
+                    android:defaultValue="0"
+                    android:key="pref_redmi_buds_5_pro_equalizer_band_125"
+                    android:summary="%s"
+                    android:title="@string/redmi_buds_5_pro_equalizer_band_125" />
+
+                <ListPreference
+                    android:entries="@array/redmi_buds_5_pro_equalizer_offsets_names"
+                    android:entryValues="@array/redmi_buds_5_pro_equalizer_offsets_values"
+                    android:icon="@drawable/ic_graphic_eq"
+                    android:defaultValue="0"
+                    android:key="pref_redmi_buds_5_pro_equalizer_band_250"
+                    android:summary="%s"
+                    android:title="@string/redmi_buds_5_pro_equalizer_band_250" />
+
+                <ListPreference
+                    android:entries="@array/redmi_buds_5_pro_equalizer_offsets_names"
+                    android:entryValues="@array/redmi_buds_5_pro_equalizer_offsets_values"
+                    android:icon="@drawable/ic_graphic_eq"
+                    android:defaultValue="0"
+                    android:key="pref_redmi_buds_5_pro_equalizer_band_500"
+                    android:summary="%s"
+                    android:title="@string/redmi_buds_5_pro_equalizer_band_500" />
+
+                <ListPreference
+                    android:entries="@array/redmi_buds_5_pro_equalizer_offsets_names"
+                    android:entryValues="@array/redmi_buds_5_pro_equalizer_offsets_values"
+                    android:icon="@drawable/ic_graphic_eq"
+                    android:defaultValue="0"
+                    android:key="pref_redmi_buds_5_pro_equalizer_band_1k"
+                    android:summary="%s"
+                    android:title="@string/redmi_buds_5_pro_equalizer_band_1k" />
+
+                <ListPreference
+                    android:entries="@array/redmi_buds_5_pro_equalizer_offsets_names"
+                    android:entryValues="@array/redmi_buds_5_pro_equalizer_offsets_values"
+                    android:icon="@drawable/ic_graphic_eq"
+                    android:defaultValue="0"
+                    android:key="pref_redmi_buds_5_pro_equalizer_band_2k"
+                    android:summary="%s"
+                    android:title="@string/redmi_buds_5_pro_equalizer_band_2k" />
+
+                <ListPreference
+                    android:entries="@array/redmi_buds_5_pro_equalizer_offsets_names"
+                    android:entryValues="@array/redmi_buds_5_pro_equalizer_offsets_values"
+                    android:icon="@drawable/ic_graphic_eq"
+                    android:defaultValue="0"
+                    android:key="pref_redmi_buds_5_pro_equalizer_band_4k"
+                    android:summary="%s"
+                    android:title="@string/redmi_buds_5_pro_equalizer_band_4k" />
+
+                <ListPreference
+                    android:entries="@array/redmi_buds_5_pro_equalizer_offsets_names"
+                    android:entryValues="@array/redmi_buds_5_pro_equalizer_offsets_values"
+                    android:icon="@drawable/ic_graphic_eq"
+                    android:defaultValue="0"
+                    android:key="pref_redmi_buds_5_pro_equalizer_band_8k"
+                    android:summary="%s"
+                    android:title="@string/redmi_buds_5_pro_equalizer_band_8k" />
+
+                <ListPreference
+                    android:entries="@array/redmi_buds_5_pro_equalizer_offsets_names"
+                    android:entryValues="@array/redmi_buds_5_pro_equalizer_offsets_values"
+                    android:icon="@drawable/ic_graphic_eq"
+                    android:defaultValue="0"
+                    android:key="pref_redmi_buds_5_pro_equalizer_band_12k"
+                    android:summary="%s"
+                    android:title="@string/redmi_buds_5_pro_equalizer_band_12k" />
+
+                <ListPreference
+                    android:entries="@array/redmi_buds_5_pro_equalizer_offsets_names"
+                    android:entryValues="@array/redmi_buds_5_pro_equalizer_offsets_values"
+                    android:icon="@drawable/ic_graphic_eq"
+                    android:defaultValue="0"
+                    android:key="pref_redmi_buds_5_pro_equalizer_band_16k"
+                    android:summary="%s"
+                    android:title="@string/redmi_buds_5_pro_equalizer_band_16k" />
+
+            </PreferenceCategory>
+
+        </PreferenceScreen>
+
+<!--        <SwitchPreferenceCompat-->
+<!--            android:defaultValue="false"-->
+<!--            android:key="pref_redmi_buds_5_pro_surround_sound"-->
+<!--            android:icon="@drawable/ic_surround"-->
+<!--            android:layout="@layout/preference_checkbox"-->
+<!--            android:title="@string/redmi_buds_5_pro_spatial_audio" />-->
+
+<!--        <ListPreference-->
+<!--            android:entries="@array/redmi_buds_5_pro_surround_sound_mode_names"-->
+<!--            android:entryValues="@array/redmi_buds_5_pro_surround_sound_mode_values"-->
+<!--            android:key="pref_redmi_buds_5_pro_surround_sound_mode"-->
+<!--            android:summary="%s"-->
+<!--            android:title="@string/redmi_buds_5_pro_spatial_audio_mode" />-->
+
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:key="pref_redmi_buds_5_pro_adaptive_sound"
+            android:layout="@layout/preference_checkbox"
+            android:summary="@string/redmi_buds_5_pro_adaptive_sound_description"
+            android:title="@string/redmi_buds_5_pro_adaptive_sound" />
+
+    </PreferenceCategory>
+
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_reject_call_method.xml b/app/src/main/res/xml/devicesettings_reject_call_method.xml
new file mode 100644
index 0000000..c9b9d8a
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_reject_call_method.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <ListPreference
+        android:defaultValue="reject"
+        android:key="call_reject_method"
+        android:title="@string/pref_call_rejection_method_title"
+        android:summary="@string/pref_call_rejection_method_summary"
+        android:icon="@drawable/ic_phone_missed_outline"
+        android:entries="@array/call_rejection_methods"
+        android:entryValues="@array/call_rejection_methods_values"/>
+
+</PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_relax_firmware_checks.xml b/app/src/main/res/xml/devicesettings_relax_firmware_checks.xml
new file mode 100644
index 0000000..78a345f
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_relax_firmware_checks.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <SwitchPreferenceCompat
+        android:icon="@drawable/ic_verified"
+        android:defaultValue="false"
+        android:key="relax_firmware_checks"
+        android:layout="@layout/preference_checkbox"
+        android:summary="@string/pref_summary_relax_firmware_checks"
+        android:title="@string/pref_title_relax_firmware_checks" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_reserve_alarms_calendar.xml b/app/src/main/res/xml/devicesettings_reserve_alarms_calendar.xml
new file mode 100644
index 0000000..0785e45
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_reserve_alarms_calendar.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <EditTextPreference
+        android:icon="@drawable/ic_access_alarms"
+        android:defaultValue="0"
+        android:digits="0123"
+        android:inputType="number"
+        android:key="reserve_alarms_calendar"
+        android:maxLength="1"
+        android:title="@string/miband_prefs_reserve_alarm_calendar" />
+
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_reserve_reminders_calendar.xml b/app/src/main/res/xml/devicesettings_reserve_reminders_calendar.xml
new file mode 100644
index 0000000..dc6bb6f
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_reserve_reminders_calendar.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <EditTextPreference
+        android:icon="@drawable/ic_access_alarms"
+        android:defaultValue="9"
+        android:dependency="sync_calendar"
+        android:digits="0123"
+        android:inputType="number"
+        android:key="reserve_reminders_calendar"
+        android:maxLength="1"
+        android:title="@string/miband_prefs_reserve_reminder_calendar"
+        android:summary="@string/prefs_reserve_reminder_calendar_summary" />
+
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_root_activity_info.xml b/app/src/main/res/xml/devicesettings_root_activity_info.xml
new file mode 100644
index 0000000..fe5d7c3
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_root_activity_info.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceScreen
+        android:icon="@drawable/ic_activity_unknown_small"
+        android:key="pref_screen_activity_info"
+        android:persistent="false"
+        android:title="@string/activity_info">
+    </PreferenceScreen>
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_root_audio.xml b/app/src/main/res/xml/devicesettings_root_audio.xml
new file mode 100644
index 0000000..9300163
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_root_audio.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceScreen
+        android:icon="@drawable/ic_music_note"
+        android:key="pref_screen_audio"
+        android:persistent="false"
+        android:title="@string/pref_header_audio">
+    </PreferenceScreen>
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_root_authentication.xml b/app/src/main/res/xml/devicesettings_root_authentication.xml
new file mode 100644
index 0000000..8e56128
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_root_authentication.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceScreen
+        android:icon="@drawable/ic_vpn_key"
+        android:key="pref_screen_authentication"
+        android:persistent="false"
+        android:title="@string/pref_header_authentication">
+    </PreferenceScreen>
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_root_battery.xml b/app/src/main/res/xml/devicesettings_root_battery.xml
new file mode 100644
index 0000000..4d167ef
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_root_battery.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceScreen
+        android:icon="@drawable/ic_battery"
+        android:key="pref_screen_battery"
+        android:persistent="false"
+        android:title="@string/battery">
+    </PreferenceScreen>
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_root_calendar.xml b/app/src/main/res/xml/devicesettings_root_calendar.xml
new file mode 100644
index 0000000..aaca622
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_root_calendar.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceScreen
+        android:icon="@drawable/ic_calendar_today"
+        android:key="pref_screen_calendar"
+        android:persistent="false"
+        android:title="@string/pref_header_calendar">
+    </PreferenceScreen>
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_root_calls_and_notifications.xml b/app/src/main/res/xml/devicesettings_root_calls_and_notifications.xml
new file mode 100644
index 0000000..ed0aed4
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_root_calls_and_notifications.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceScreen
+        android:icon="@drawable/ic_notifications"
+        android:key="pref_screen_calls_and_notifications"
+        android:persistent="false"
+        android:title="@string/pref_header_calls_and_notifications">
+    </PreferenceScreen>
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_root_connection.xml b/app/src/main/res/xml/devicesettings_root_connection.xml
new file mode 100644
index 0000000..a588083
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_root_connection.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceScreen
+        android:icon="@drawable/ic_mtu"
+        android:key="pref_screen_connection"
+        android:persistent="false"
+        android:title="@string/pref_header_connection">
+    </PreferenceScreen>
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_root_date_time.xml b/app/src/main/res/xml/devicesettings_root_date_time.xml
new file mode 100644
index 0000000..7a9f532
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_root_date_time.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceScreen
+        android:icon="@drawable/ic_timer"
+        android:key="pref_screen_date_time"
+        android:persistent="false"
+        android:title="@string/dateformat_date_time">
+    </PreferenceScreen>
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_root_developer.xml b/app/src/main/res/xml/devicesettings_root_developer.xml
new file mode 100644
index 0000000..307e157
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_root_developer.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceScreen
+        android:icon="@drawable/ic_developer_mode"
+        android:key="pref_screen_developer"
+        android:persistent="false"
+        android:title="@string/pref_title_developer_settings">
+    </PreferenceScreen>
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_root_display.xml b/app/src/main/res/xml/devicesettings_root_display.xml
new file mode 100644
index 0000000..0246a6c
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_root_display.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceScreen
+        android:icon="@drawable/ic_watch"
+        android:key="pref_screen_display"
+        android:persistent="false"
+        android:title="@string/pref_header_display">
+    </PreferenceScreen>
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_root_generic.xml b/app/src/main/res/xml/devicesettings_root_generic.xml
new file mode 100644
index 0000000..a3c9451
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_root_generic.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceScreen
+        android:icon="@drawable/ic_settings"
+        android:key="pref_screen_generic"
+        android:persistent="false"
+        android:title="@string/pref_header_generic">
+    </PreferenceScreen>
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_root_health.xml b/app/src/main/res/xml/devicesettings_root_health.xml
new file mode 100644
index 0000000..0baa635
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_root_health.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceScreen
+        android:icon="@drawable/ic_health"
+        android:key="pref_screen_health"
+        android:persistent="false"
+        android:title="@string/pref_header_health">
+    </PreferenceScreen>
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_root_location.xml b/app/src/main/res/xml/devicesettings_root_location.xml
new file mode 100644
index 0000000..2a936ed
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_root_location.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceScreen
+        android:icon="@drawable/ic_gps_location"
+        android:key="pref_screen_location"
+        android:persistent="false"
+        android:title="@string/pref_header_location">
+    </PreferenceScreen>
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_root_notifications.xml b/app/src/main/res/xml/devicesettings_root_notifications.xml
new file mode 100644
index 0000000..e68a77e
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_root_notifications.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceScreen
+        android:icon="@drawable/ic_notifications"
+        android:key="pref_screen_notifications"
+        android:persistent="false"
+        android:title="@string/pref_header_notifications">
+    </PreferenceScreen>
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_root_sound.xml b/app/src/main/res/xml/devicesettings_root_sound.xml
new file mode 100644
index 0000000..dc128b5
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_root_sound.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceScreen
+        android:icon="@drawable/ic_volume_up"
+        android:key="pref_screen_sound"
+        android:persistent="false"
+        android:title="@string/pref_header_sound">
+    </PreferenceScreen>
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_root_touch_options.xml b/app/src/main/res/xml/devicesettings_root_touch_options.xml
new file mode 100644
index 0000000..de5a204
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_root_touch_options.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceScreen
+        android:icon="@drawable/ic_touch"
+        android:key="pref_screen_touch_options"
+        android:persistent="false"
+        android:title="@string/prefs_galaxy_touch_options">
+    </PreferenceScreen>
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_root_workout.xml b/app/src/main/res/xml/devicesettings_root_workout.xml
new file mode 100644
index 0000000..457c240
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_root_workout.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceScreen
+        android:icon="@drawable/ic_activity_unknown_small"
+        android:key="pref_screen_workout"
+        android:persistent="false"
+        android:title="@string/menuitem_workout">
+    </PreferenceScreen>
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_rotatewrist_cycleinfo.xml b/app/src/main/res/xml/devicesettings_rotatewrist_cycleinfo.xml
new file mode 100644
index 0000000..74c617c
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_rotatewrist_cycleinfo.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <SwitchPreferenceCompat
+        android:defaultValue="false"
+        android:icon="@drawable/ic_rotate_left"
+        android:key="rotate_wrist_to_cycle_info"
+        android:layout="@layout/preference_checkbox"
+        android:title="@string/mi2_prefs_rotate_wrist_to_switch_info" />
+</androidx.preference.PreferenceScreen>
\ No newline at end of file
diff --git a/app/src/main/res/xml/devicesettings_scannable.xml b/app/src/main/res/xml/devicesettings_scannable.xml
new file mode 100644
index 0000000..40793c6
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_scannable.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<PreferenceScreen
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+
+    <EditTextPreference
+        android:title="@string/devicesetting_scannable_debounce"
+        android:summary="@string/devicesetting_scannable_debounce_summary"
+        android:key="devicesetting_scannable_debounce"
+        android:defaultValue="60"
+        android:inputType="number" />
+
+    <EditTextPreference
+        android:title="@string/devicesetting_scannable_minimum_unseen"
+        android:summary="@string/devicesetting_scannable_minimum_unseen_summary"
+        android:key="devicesetting_scannable_unseen"
+        android:defaultValue="0"
+        android:inputType="number" />
+
+    <EditTextPreference
+        android:title="@string/devicesetting_scannable_rssi"
+        android:summary="@string/devicesetting_scannable_rssi_summary"
+        android:key="devicesetting_scannable_rssi"
+        android:defaultValue="-100"
+        android:min="-100"
+        android:max="0"
+        android:inputType="number" />
+
+</PreferenceScreen>
\ No newline at end of file
diff --git a/app/src/main/res/xml/devicesettings_screen_brightness.xml b/app/src/main/res/xml/devicesettings_screen_brightness.xml
new file mode 100644
index 0000000..3eb9dab
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_screen_brightness.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <SeekBarPreference
+        android:defaultValue="50"
+        android:icon="@drawable/ic_wb_sunny"
+        android:key="screen_brightness"
+        android:max="100"
+        android:title="@string/pref_screen_brightness" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_screen_brightness_withauto.xml b/app/src/main/res/xml/devicesettings_screen_brightness_withauto.xml
new file mode 100644
index 0000000..7c454ea
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_screen_brightness_withauto.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceScreen
+        android:icon="@drawable/ic_wb_sunny"
+        android:key="pref_screen_auto_brightness"
+        android:persistent="false"
+        android:title="@string/pref_screen_brightness">
+
+        <SwitchPreferenceCompat
+            android:defaultValue="true"
+            android:icon="@drawable/ic_wb_sunny"
+            android:key="screen_auto_brightness"
+            android:layout="@layout/preference_checkbox"
+            android:summary="@string/pref_screen_auto_brightness_summary"
+            android:title="@string/pref_screen_auto_brightness_title" />
+
+        <SeekBarPreference
+            android:defaultValue="50"
+            android:icon="@drawable/ic_wb_sunny"
+            android:key="screen_brightness"
+            android:max="100"
+            android:title="@string/pref_screen_brightness" />
+    </PreferenceScreen>
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_screen_on_on_notifications.xml b/app/src/main/res/xml/devicesettings_screen_on_on_notifications.xml
new file mode 100644
index 0000000..339bc45
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_screen_on_on_notifications.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <SwitchPreferenceCompat
+        android:defaultValue="true"
+        android:icon="@drawable/ic_notifications"
+        android:key="screen_on_on_notifications"
+        android:layout="@layout/preference_checkbox"
+        android:summary="@string/pref_summary_screen_on_on_notifications"
+        android:title="@string/pref_title_screen_on_on_notifications" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_screen_timeout.xml b/app/src/main/res/xml/devicesettings_screen_timeout.xml
new file mode 100644
index 0000000..bb7f874
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_screen_timeout.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <ListPreference
+        android:defaultValue="5"
+        android:entries="@array/screen_timeout"
+        android:entryValues="@array/screen_timeout_values"
+        android:key="screen_timeout"
+        android:icon="@drawable/ic_hourglass_empty"
+        android:summary="%s"
+        android:title="@string/prefs_screen_timeout" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_screenorientation.xml b/app/src/main/res/xml/devicesettings_screenorientation.xml
new file mode 100644
index 0000000..7cc6965
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_screenorientation.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <ListPreference
+        android:icon="@drawable/ic_screen_rotation"
+        android:defaultValue="horizontal"
+        android:entries="@array/orientation"
+        android:entryValues="@array/orientation_values"
+        android:key="screen_orientation"
+        android:summary="%s"
+        android:title="@string/prefs_screen_orientation" />
+
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_send_app_notifications.xml b/app/src/main/res/xml/devicesettings_send_app_notifications.xml
new file mode 100644
index 0000000..cf02b16
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_send_app_notifications.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <SwitchPreferenceCompat
+        android:defaultValue="true"
+        android:icon="@drawable/ic_notifications"
+        android:key="send_app_notifications"
+        android:layout="@layout/preference_checkbox"
+        android:summary="@string/pref_summary_send_app_notifications"
+        android:title="@string/pref_title_send_app_notifications" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_settings_third_party_apps.xml b/app/src/main/res/xml/devicesettings_settings_third_party_apps.xml
new file mode 100644
index 0000000..11bb311
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_settings_third_party_apps.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <SwitchPreferenceCompat
+        android:icon="@drawable/ic_developer_mode"
+        android:defaultValue="false"
+        android:key="third_party_apps_set_settings"
+        android:layout="@layout/preference_checkbox"
+        android:summary="@string/pref_summary_third_party_app_device_settings"
+        android:title="@string/pref_title_third_party_app_device_settings" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_sleep_mode.xml b/app/src/main/res/xml/devicesettings_sleep_mode.xml
new file mode 100644
index 0000000..6a4da89
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_sleep_mode.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceScreen
+        android:icon="@drawable/ic_activity_sleep"
+        android:key="pref_screen_sleep_mode"
+        android:persistent="false"
+        android:title="@string/pref_sleep_mode_title">
+
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:icon="@drawable/ic_activity_sleep"
+            android:key="pref_sleep_mode_sleep_screen"
+            android:layout="@layout/preference_checkbox"
+            android:summary="@string/pref_sleep_mode_sleep_screen_summary"
+            android:title="@string/pref_sleep_mode_sleep_screen_title" />
+
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:icon="@drawable/ic_auto_awesome"
+            android:key="pref_sleep_mode_smart_enable"
+            android:layout="@layout/preference_checkbox"
+            android:summary="@string/pref_sleep_mode_smart_enable_summary"
+            android:title="@string/pref_sleep_mode_smart_enable_title" />
+    </PreferenceScreen>
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_sleep_mode_schedule.xml b/app/src/main/res/xml/devicesettings_sleep_mode_schedule.xml
new file mode 100644
index 0000000..953502f
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_sleep_mode_schedule.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceScreen
+        android:icon="@drawable/ic_activity_sleep"
+        android:key="prefs_sleep_mode_schedule"
+        android:persistent="false"
+        android:title="@string/pref_sleep_mode_title">
+
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:icon="@drawable/ic_activity_sleep"
+            android:key="sleep_mode_schedule_enabled"
+            android:layout="@layout/preference_checkbox"
+            android:summary="@string/pref_sleep_mode_schedule_summary"
+            android:title="@string/pref_sleep_mode_schedule_title" />
+
+        <nodomain.freeyourgadget.gadgetbridge.util.XTimePreference
+            android:defaultValue="22:00"
+            android:key="sleep_mode_schedule_start"
+            android:title="@string/bedtime" />
+
+        <nodomain.freeyourgadget.gadgetbridge.util.XTimePreference
+            android:defaultValue="06:00"
+            android:key="sleep_mode_schedule_end"
+            android:title="@string/wake_up_time" />
+    </PreferenceScreen>
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_sleep_time.xml b/app/src/main/res/xml/devicesettings_sleep_time.xml
new file mode 100644
index 0000000..bacfc66
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_sleep_time.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceScreen
+        android:icon="@drawable/ic_access_time"
+        android:key="prefs_sleep_time"
+        android:persistent="false"
+        android:summary="@string/prefs_sleep_time_summary"
+        android:title="@string/prefs_sleep_time">
+
+        <!-- workaround for missing toolbar -->
+        <PreferenceCategory android:title="@string/prefs_sleep_time_label" />
+
+        <ListPreference
+            android:defaultValue="@string/p_off"
+            android:enabled="true"
+            android:entries="@array/do_not_disturb_no_auto"
+            android:entryValues="@array/do_not_disturb_no_auto_values"
+            android:key="prefs_enable_sleep_time"
+            android:summary="%s"
+            android:title="@string/prefs_sleep_time" />
+
+        <nodomain.freeyourgadget.gadgetbridge.util.XTimePreference
+            android:defaultValue="22:00"
+            android:key="prefs_sleep_time_start"
+            android:title="@string/mi2_prefs_do_not_disturb_start" />
+
+        <nodomain.freeyourgadget.gadgetbridge.util.XTimePreference
+            android:defaultValue="06:00"
+            android:key="prefs_sleep_time_end"
+            android:title="@string/mi2_prefs_do_not_disturb_end" />
+    </PreferenceScreen>
+</androidx.preference.PreferenceScreen>
\ No newline at end of file
diff --git a/app/src/main/res/xml/devicesettings_sony_headphones_adaptive_volume_control.xml b/app/src/main/res/xml/devicesettings_sony_headphones_adaptive_volume_control.xml
new file mode 100644
index 0000000..159657e
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_sony_headphones_adaptive_volume_control.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceCategory
+        android:key="pref_key_header_sony_ambient_sound_control"
+        android:title="@string/pref_header_sony_sound_control">
+
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:icon="@drawable/ic_hearing"
+            android:key="pref_adaptive_volume_control"
+            android:layout="@layout/preference_checkbox"
+            android:summary="@string/pref_adaptive_volume_control_summary"
+            android:title="@string/pref_adaptive_volume_control_title" />
+    </PreferenceCategory>
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_sony_headphones_ambient_sound_control.xml b/app/src/main/res/xml/devicesettings_sony_headphones_ambient_sound_control.xml
new file mode 100644
index 0000000..2e3625b
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_sony_headphones_ambient_sound_control.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceCategory
+        android:key="pref_key_header_sony_ambient_sound_control"
+        android:title="@string/pref_header_sony_ambient_sound_control">
+
+        <ListPreference
+            android:defaultValue="noise_cancelling"
+            android:entries="@array/sony_ambient_sound_control_names"
+            android:entryValues="@array/sony_ambient_sound_control_values"
+            android:icon="@drawable/ic_hearing"
+            android:key="pref_sony_ambient_sound_control"
+            android:summary="%s"
+            android:title="@string/sony_ambient_sound" />
+
+        <!-- [0, 19], which maps to [1, 20] on the device, as we can't configure the min on the current API level -->
+        <SeekBarPreference
+            android:defaultValue="0"
+            android:icon="@drawable/ic_volume_up"
+            android:key="pref_sony_ambient_sound_level"
+            android:max="19"
+            android:title="@string/sony_ambient_sound_level" />
+
+        <SwitchPreferenceCompat
+            android:defaultValue="true"
+            android:icon="@drawable/ic_voice"
+            android:key="pref_sony_focus_voice"
+            android:layout="@layout/preference_checkbox"
+            android:title="@string/sony_ambient_sound_focus_voice" />
+
+    </PreferenceCategory>
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_sony_headphones_ambient_sound_control_button_modes.xml b/app/src/main/res/xml/devicesettings_sony_headphones_ambient_sound_control_button_modes.xml
new file mode 100644
index 0000000..a62c7d7
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_sony_headphones_ambient_sound_control_button_modes.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <ListPreference
+        android:defaultValue="nc_as_off"
+        android:entries="@array/sony_ambient_sound_control_button_mode_names"
+        android:entryValues="@array/sony_ambient_sound_control_button_mode_values"
+        android:icon="@drawable/ic_touch"
+        android:key="pref_sony_ambient_sound_control_button_mode"
+        android:summary="%s"
+        android:title="@string/sony_ambient_sound_control_button_modes" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_sony_headphones_ambient_sound_control_wind_noise_reduction.xml b/app/src/main/res/xml/devicesettings_sony_headphones_ambient_sound_control_wind_noise_reduction.xml
new file mode 100644
index 0000000..f532d99
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_sony_headphones_ambient_sound_control_wind_noise_reduction.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceCategory
+        android:key="pref_key_header_sony_ambient_sound_control"
+        android:title="@string/pref_header_sony_ambient_sound_control">
+
+        <ListPreference
+            android:defaultValue="noise_cancelling"
+            android:entries="@array/sony_ambient_sound_control_wind_noise_reduction_names"
+            android:entryValues="@array/sony_ambient_sound_control_wind_noise_reduction_values"
+            android:icon="@drawable/ic_hearing"
+            android:key="pref_sony_ambient_sound_control"
+            android:summary="%s"
+            android:title="@string/sony_ambient_sound" />
+
+        <!-- [0, 19], which maps to [1, 20] on the device, as we can't configure the min on the current API level -->
+        <SeekBarPreference
+            android:defaultValue="0"
+            android:icon="@drawable/ic_volume_up"
+            android:key="pref_sony_ambient_sound_level"
+            android:max="19"
+            android:title="@string/sony_ambient_sound_level" />
+
+        <SwitchPreferenceCompat
+            android:defaultValue="true"
+            android:icon="@drawable/ic_voice"
+            android:key="pref_sony_focus_voice"
+            android:layout="@layout/preference_checkbox"
+            android:title="@string/sony_ambient_sound_focus_voice" />
+
+    </PreferenceCategory>
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_sony_headphones_anc_optimizer.xml b/app/src/main/res/xml/devicesettings_sony_headphones_anc_optimizer.xml
new file mode 100644
index 0000000..860e193
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_sony_headphones_anc_optimizer.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+    <PreferenceCategory
+        android:key="pref_key_header_sony_anc_optimizer"
+        android:title="@string/pref_header_sony_anc_optimizer">
+
+        <EditTextPreference
+            android:defaultValue="@string/unknown"
+            android:enabled="false"
+            android:icon="@drawable/ic_pressure"
+            android:key="pref_sony_noise_optimizer_state_pressure"
+            android:shouldDisableView="false"
+            android:title="@string/pref_anc_optimizer_state_pressure"
+            app:useSimpleSummaryProvider="true" />
+
+        <Preference
+            android:icon="@drawable/ic_auto_awesome"
+            android:key="pref_sony_anc_optimizer"
+            android:summary="@string/sony_anc_optimize_description"
+            android:title="@string/sony_anc_optimize_title" />
+
+        <EditTextPreference
+            android:defaultValue="NOT_RUNNING"
+            android:enabled="false"
+            android:key="pref_sony_noise_optimizer_status"
+            android:shouldDisableView="false"
+            android:title="pref_sony_noise_optimizer_status"
+            app:isPreferenceVisible="false" />
+    </PreferenceCategory>
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_sony_headphones_audio_upsampling.xml b/app/src/main/res/xml/devicesettings_sony_headphones_audio_upsampling.xml
new file mode 100644
index 0000000..1b56e1e
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_sony_headphones_audio_upsampling.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <SwitchPreferenceCompat
+        android:defaultValue="false"
+        android:icon="@drawable/ic_extension"
+        android:key="pref_sony_audio_upsampling"
+        android:layout="@layout/preference_checkbox"
+        android:title="@string/sony_audio_upsampling" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_sony_headphones_button_modes_left_right.xml b/app/src/main/res/xml/devicesettings_sony_headphones_button_modes_left_right.xml
new file mode 100644
index 0000000..83b7aa3
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_sony_headphones_button_modes_left_right.xml
@@ -0,0 +1,87 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceScreen
+        android:icon="@drawable/ic_help_outline"
+        android:key="pref_screen_sony_button_mode_help"
+        android:persistent="false"
+        android:summary="@string/sony_button_mode_help_summary"
+        android:title="@string/sony_button_mode_help_title">
+
+        <PreferenceCategory android:title="@string/sony_button_mode_ambient_sound_control">
+            <Preference
+                android:icon="@drawable/ic_filter_1"
+                android:key="pref_button_mode_help_ambient_sound_single_tap"
+                android:summary="@string/pref_switch_controls_anc_ambient_off"
+                android:title="@string/single_tap" />
+
+            <Preference
+                android:icon="@drawable/ic_horizontal_rule"
+                android:key="pref_button_mode_help_ambient_sound_continue_pressing"
+                android:summary="@string/quick_attention"
+                android:title="@string/continue_pressing" />
+        </PreferenceCategory>
+
+        <PreferenceCategory android:title="@string/sony_button_mode_playback_control">
+            <Preference
+                android:icon="@drawable/ic_filter_1"
+                android:key="pref_button_mode_help_playback_control_single_tap"
+                android:summary="@string/pref_media_playpause"
+                android:title="@string/single_tap" />
+            <Preference
+                android:icon="@drawable/ic_filter_2"
+                android:key="pref_button_mode_help_playback_control_double_tap"
+                android:summary="@string/pref_media_next"
+                android:title="@string/double_tap" />
+            <Preference
+                android:icon="@drawable/ic_filter_3"
+                android:key="pref_button_mode_help_playback_control_triple_tap"
+                android:summary="@string/pref_media_previous"
+                android:title="@string/triple_tap" />
+            <Preference
+                android:icon="@drawable/ic_horizontal_rule"
+                android:key="pref_button_mode_help_playback_control_long_press"
+                android:summary="@string/pref_title_touch_voice_assistant"
+                android:title="@string/long_press" />
+        </PreferenceCategory>
+
+        <PreferenceCategory android:title="@string/sony_button_mode_volume_control">
+            <Preference
+                android:icon="@drawable/ic_filter_1"
+                android:key="pref_button_mode_help_volume_control_single_tap"
+                android:summary="@string/pref_media_volumeup"
+                android:title="@string/single_tap" />
+            <Preference
+                android:icon="@drawable/ic_horizontal_rule"
+                android:key="pref_button_mode_help_volume_control_continue_pressing"
+                android:summary="@string/pref_media_volumedown"
+                android:title="@string/continue_pressing" />
+        </PreferenceCategory>
+
+        <!--
+            Google Assistant:
+                - Tap: Cancel Audio Input
+                - Double Tap: Voice Notification
+                - Continue Pressing: Audio Input
+            Amazon Alexa
+                - Tap: Audio Input / Cancel
+        -->
+    </PreferenceScreen>
+
+    <ListPreference
+        android:defaultValue="off"
+        android:entries="@array/sony_wfsp800n_button_mode_names"
+        android:entryValues="@array/sony_wfsp800n_button_mode_values"
+        android:icon="@drawable/ic_touch"
+        android:key="pref_sony_button_mode_left"
+        android:summary="%s"
+        android:title="@string/sony_button_mode_left" />
+
+    <ListPreference
+        android:defaultValue="off"
+        android:entries="@array/sony_wfsp800n_button_mode_names"
+        android:entryValues="@array/sony_wfsp800n_button_mode_values"
+        android:icon="@drawable/ic_touch"
+        android:key="pref_sony_button_mode_right"
+        android:summary="%s"
+        android:title="@string/sony_button_mode_right" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_sony_headphones_device_info.xml b/app/src/main/res/xml/devicesettings_sony_headphones_device_info.xml
new file mode 100644
index 0000000..6bfc4c2
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_sony_headphones_device_info.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+    <PreferenceCategory
+        android:key="pref_key_header_sony_device_info"
+        android:title="@string/pref_header_sony_device_info">
+
+        <EditTextPreference
+            android:defaultValue="\?"
+            android:enabled="false"
+            android:icon="@drawable/ic_music_note"
+            android:key="pref_sony_audio_codec"
+            android:shouldDisableView="false"
+            android:title="@string/audio_codec"
+            app:useSimpleSummaryProvider="true" />
+
+        <EditTextPreference
+            android:defaultValue="\?"
+            android:enabled="false"
+            android:icon="@drawable/ic_developer_mode"
+            android:key="pref_actual_protocol_version"
+            android:shouldDisableView="false"
+            android:title="@string/protocol_version"
+            app:useSimpleSummaryProvider="true" />
+
+    </PreferenceCategory>
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_sony_headphones_equalizer.xml b/app/src/main/res/xml/devicesettings_sony_headphones_equalizer.xml
new file mode 100644
index 0000000..bf9e6b8
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_sony_headphones_equalizer.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <ListPreference
+        android:defaultValue="off"
+        android:entries="@array/sony_equalizer_mode_names"
+        android:entryValues="@array/sony_equalizer_mode_values"
+        android:icon="@drawable/ic_graphic_eq"
+        android:key="pref_sony_equalizer_mode"
+        android:summary="%s"
+        android:title="@string/sony_equalizer" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_sony_headphones_equalizer_with_custom_bands.xml b/app/src/main/res/xml/devicesettings_sony_headphones_equalizer_with_custom_bands.xml
new file mode 100644
index 0000000..f223aeb
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_sony_headphones_equalizer_with_custom_bands.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <PreferenceScreen
+        android:icon="@drawable/ic_graphic_eq"
+        android:key="pref_sony_equalizer"
+        android:persistent="false"
+        android:title="@string/pref_header_equalizer">
+
+        <ListPreference
+            android:defaultValue="off"
+            android:entries="@array/sony_equalizer_mode_names"
+            android:entryValues="@array/sony_equalizer_mode_values"
+            android:icon="@drawable/ic_graphic_eq"
+            android:key="pref_sony_equalizer_mode"
+            android:summary="%s"
+            android:title="@string/sony_equalizer" />
+
+        <SeekBarPreference
+            android:defaultValue="10"
+            android:icon="@drawable/ic_graphic_eq"
+            android:key="pref_sony_equalizer_band_400"
+            android:max="20"
+            android:title="@string/sony_equalizer_band_400" />
+        <SeekBarPreference
+            android:defaultValue="10"
+            android:icon="@drawable/ic_graphic_eq"
+            android:key="pref_sony_equalizer_band_1000"
+            android:max="20"
+            android:title="@string/sony_equalizer_band_1000" />
+        <SeekBarPreference
+            android:defaultValue="10"
+            android:icon="@drawable/ic_graphic_eq"
+            android:key="pref_sony_equalizer_band_2500"
+            android:max="20"
+            android:title="@string/sony_equalizer_band_2500" />
+        <SeekBarPreference
+            android:defaultValue="10"
+            android:icon="@drawable/ic_graphic_eq"
+            android:key="pref_sony_equalizer_band_6300"
+            android:max="20"
+            android:title="@string/sony_equalizer_band_6300" />
+        <SeekBarPreference
+            android:defaultValue="10"
+            android:icon="@drawable/ic_graphic_eq"
+            android:key="pref_sony_equalizer_band_16000"
+            android:max="20"
+            android:title="@string/sony_equalizer_band_16000" />
+
+        <SeekBarPreference
+            android:defaultValue="10"
+            android:icon="@drawable/ic_speaker"
+            android:key="pref_sony_equalizer_bass"
+            android:max="20"
+            android:title="@string/sony_equalizer_clear_bass" />
+    </PreferenceScreen>
+
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_sony_headphones_notifications_voice_guide.xml b/app/src/main/res/xml/devicesettings_sony_headphones_notifications_voice_guide.xml
new file mode 100644
index 0000000..cd6d6a8
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_sony_headphones_notifications_voice_guide.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <SwitchPreferenceCompat
+        android:defaultValue="true"
+        android:icon="@drawable/ic_notifications"
+        android:key="pref_sony_notification_voice_guide"
+        android:layout="@layout/preference_checkbox"
+        android:title="@string/sony_notification_voice_guide" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_sony_headphones_pause_when_taken_off.xml b/app/src/main/res/xml/devicesettings_sony_headphones_pause_when_taken_off.xml
new file mode 100644
index 0000000..8a961d7
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_sony_headphones_pause_when_taken_off.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <SwitchPreferenceCompat
+        android:defaultValue="false"
+        android:icon="@drawable/ic_pause"
+        android:key="pref_sony_pause_when_taken_off"
+        android:layout="@layout/preference_checkbox"
+        android:title="@string/sony_pause_when_taken_off" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_sony_headphones_protocol_version.xml b/app/src/main/res/xml/devicesettings_sony_headphones_protocol_version.xml
new file mode 100644
index 0000000..c9e1d73
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_sony_headphones_protocol_version.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+
+    <ListPreference
+        android:defaultValue="auto"
+        android:entries="@array/sony_protocol_version_names"
+        android:entryValues="@array/sony_protocol_version_values"
+        android:icon="@drawable/ic_developer_mode"
+        android:key="pref_protocol_version"
+        android:summary="%s"
+        android:title="@string/protocol_version"
+        app:useSimpleSummaryProvider="true" />
+
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_sony_headphones_quick_access.xml b/app/src/main/res/xml/devicesettings_sony_headphones_quick_access.xml
new file mode 100644
index 0000000..ed543e6
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_sony_headphones_quick_access.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <ListPreference
+        android:defaultValue="off"
+        android:entries="@array/sony_quick_access_names"
+        android:entryValues="@array/sony_quick_access_values"
+        android:icon="@drawable/ic_filter_2"
+        android:key="pref_sony_quick_access_double_tap"
+        android:summary="%s"
+        android:title="@string/sony_quick_access_double_tap" />
+
+    <ListPreference
+        android:defaultValue="off"
+        android:entries="@array/sony_quick_access_names"
+        android:entryValues="@array/sony_quick_access_values"
+        android:icon="@drawable/ic_filter_3"
+        android:key="pref_sony_quick_access_triple_tap"
+        android:summary="%s"
+        android:title="@string/sony_quick_access_triple_tap" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_sony_headphones_sound_position.xml b/app/src/main/res/xml/devicesettings_sony_headphones_sound_position.xml
new file mode 100644
index 0000000..5e3bb41
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_sony_headphones_sound_position.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <ListPreference
+        android:defaultValue="off"
+        android:entries="@array/sony_sound_position_names"
+        android:entryValues="@array/sony_sound_position_values"
+        android:icon="@drawable/ic_switch_left"
+        android:key="pref_sony_sound_position"
+        android:summary="%s"
+        android:title="@string/sony_sound_position" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_sony_headphones_speak_to_chat_simple.xml b/app/src/main/res/xml/devicesettings_sony_headphones_speak_to_chat_simple.xml
new file mode 100644
index 0000000..b49ca41
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_sony_headphones_speak_to_chat_simple.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <SwitchPreferenceCompat
+        android:defaultValue="false"
+        android:icon="@drawable/ic_voice"
+        android:key="pref_sony_speak_to_chat"
+        android:layout="@layout/preference_checkbox"
+        android:summary="@string/sony_speak_to_chat_summary"
+        android:title="@string/sony_speak_to_chat" />
+
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_sony_headphones_speak_to_chat_with_settings.xml b/app/src/main/res/xml/devicesettings_sony_headphones_speak_to_chat_with_settings.xml
new file mode 100644
index 0000000..b13a60a
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_sony_headphones_speak_to_chat_with_settings.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <PreferenceScreen
+        android:icon="@drawable/ic_voice"
+        android:key="pref_sony_speak_to_chat_header"
+        android:persistent="false"
+        android:summary="@string/sony_speak_to_chat_summary"
+        android:title="@string/sony_speak_to_chat">
+
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:icon="@drawable/ic_voice"
+            android:key="pref_sony_speak_to_chat"
+            android:layout="@layout/preference_checkbox"
+            android:summary="@string/sony_speak_to_chat_summary"
+            android:title="@string/sony_speak_to_chat" />
+
+        <PreferenceCategory
+            android:key="pref_key_sony_speak_to_chat_settings_header"
+            android:title="@string/sony_speak_to_chat">
+
+            <ListPreference
+                android:defaultValue="auto"
+                android:dependency="pref_sony_speak_to_chat"
+                android:entries="@array/sony_speak_to_chat_voice_sensitivity_names"
+                android:entryValues="@array/sony_speak_to_chat_voice_sensitivity_values"
+                android:key="pref_sony_speak_to_chat_sensitivity"
+                android:summary="%s"
+                android:title="@string/sony_speak_to_chat_sensitivity" />
+
+            <SwitchPreferenceCompat
+                android:defaultValue="false"
+                android:dependency="pref_sony_speak_to_chat"
+                android:icon="@drawable/ic_voice"
+                android:layout="@layout/preference_checkbox"
+                android:key="pref_sony_speak_to_chat_focus_on_voice"
+                android:title="@string/sony_speak_to_chat_focus_on_voice" />
+
+            <ListPreference
+                android:defaultValue="standard"
+                android:dependency="pref_sony_speak_to_chat"
+                android:entries="@array/sony_speak_to_chat_voice_timeout_names"
+                android:entryValues="@array/sony_speak_to_chat_voice_timeout_values"
+                android:icon="@drawable/ic_timer"
+                android:key="pref_sony_speak_to_chat_timeout"
+                android:summary="%s"
+                android:title="@string/sony_speak_to_chat_timeout" />
+        </PreferenceCategory>
+    </PreferenceScreen>
+
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_sony_headphones_surround_mode.xml b/app/src/main/res/xml/devicesettings_sony_headphones_surround_mode.xml
new file mode 100644
index 0000000..0b8244c
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_sony_headphones_surround_mode.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <ListPreference
+        android:defaultValue="off"
+        android:entries="@array/sony_surround_mode_names"
+        android:entryValues="@array/sony_surround_mode_values"
+        android:icon="@drawable/ic_surround"
+        android:key="pref_sony_surround_mode"
+        android:summary="%s"
+        android:title="@string/sony_surround_mode" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_sony_headphones_touch_sensor_single.xml b/app/src/main/res/xml/devicesettings_sony_headphones_touch_sensor_single.xml
new file mode 100644
index 0000000..b986e5a
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_sony_headphones_touch_sensor_single.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <SwitchPreferenceCompat
+        android:defaultValue="true"
+        android:icon="@drawable/ic_touch"
+        android:key="pref_sony_touch_sensor"
+        android:layout="@layout/preference_checkbox"
+        android:title="@string/sony_touch_sensor" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_sony_headphones_wide_area_tap.xml b/app/src/main/res/xml/devicesettings_sony_headphones_wide_area_tap.xml
new file mode 100644
index 0000000..0ff8d24
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_sony_headphones_wide_area_tap.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <SwitchPreferenceCompat
+        android:defaultValue="false"
+        android:icon="@drawable/ic_touch"
+        android:key="pref_wide_area_tap"
+        android:layout="@layout/preference_checkbox"
+        android:summary="@string/pref_wide_area_tap_summary"
+        android:title="@string/pref_wide_area_tap_title" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_sony_warning_wh1000xm3.xml b/app/src/main/res/xml/devicesettings_sony_warning_wh1000xm3.xml
new file mode 100644
index 0000000..7590f80
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_sony_warning_wh1000xm3.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <Preference
+        android:icon="@drawable/ic_warning"
+        android:key="pref_sony_warning_sbc_codec_1"
+        android:summary="@string/sony_warn_sbc_codec" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_sonyswr12.xml b/app/src/main/res/xml/devicesettings_sonyswr12.xml
new file mode 100644
index 0000000..7b28701
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_sonyswr12.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <SwitchPreferenceCompat
+        android:icon="@drawable/ic_bolt"
+        android:key="stamina_preference"
+        android:layout="@layout/preference_checkbox"
+        android:title="@string/sonyswr12_settings_stamina"
+        android:defaultValue="false"
+        android:summary="@string/sonyswr12_settings_stamina_summary" />
+    <SwitchPreferenceCompat
+        android:icon="@drawable/ic_vibration"
+        android:key="vibration_preference"
+        android:layout="@layout/preference_checkbox"
+        android:title="@string/sonyswr12_settings_low_vibration"
+        android:defaultValue="false"
+        android:summary="@string/sonyswr12_settings_low_vibration_summary" />
+    <ListPreference
+        android:key="smart_alarm_interval_preference"
+        android:title="@string/sonyswr12_settings_alarm_interval"
+        android:icon="@drawable/ic_access_alarms"
+        android:defaultValue="10"
+        android:entries="@array/sonyswr12_smart_alarm_intervals"
+        android:entryValues="@array/sonyswr12_smart_alarm_interval_values"
+        android:summary="@string/sonyswr12_settings_alarm_interval_summary" />
+</PreferenceScreen>
\ No newline at end of file
diff --git a/app/src/main/res/xml/devicesettings_sound_and_vibration.xml b/app/src/main/res/xml/devicesettings_sound_and_vibration.xml
new file mode 100644
index 0000000..327c0ad
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_sound_and_vibration.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceScreen
+        android:icon="@drawable/ic_volume_up"
+        android:key="pref_screen_sound_and_vibration"
+        android:persistent="false"
+        android:title="@string/pref_header_sound_vibration">
+
+        <SeekBarPreference
+            android:defaultValue="50"
+            android:icon="@drawable/ic_volume_up"
+            android:key="volume"
+            android:max="100"
+            android:title="@string/menuitem_volume" />
+
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:icon="@drawable/ic_rotate_left"
+            android:key="crown_vibration"
+            android:layout="@layout/preference_checkbox"
+            android:title="@string/pref_crown_vibration" />
+
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:icon="@drawable/ic_warning_gray"
+            android:key="alert_tone"
+            android:layout="@layout/preference_checkbox"
+            android:title="@string/pref_alert_tone" />
+
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:icon="@drawable/ic_volume_off"
+            android:key="cover_to_mute"
+            android:layout="@layout/preference_checkbox"
+            android:title="@string/pref_cover_to_mute" />
+
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:icon="@drawable/ic_vibration"
+            android:key="vibrate_for_alert"
+            android:layout="@layout/preference_checkbox"
+            android:title="@string/pref_vibrate_for_alert" />
+
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:icon="@drawable/ic_voice"
+            android:key="text_to_speech"
+            android:layout="@layout/preference_checkbox"
+            android:title="@string/pref_text_to_speech" />
+    </PreferenceScreen>
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_soundcore_headphones.xml b/app/src/main/res/xml/devicesettings_soundcore_headphones.xml
new file mode 100644
index 0000000..6a280e9
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_soundcore_headphones.xml
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceCategory
+        android:key="pref_key_header_soundcore_ambient_sound_control"
+        android:title="@string/pref_header_sony_ambient_sound_control">
+
+        <ListPreference
+            android:defaultValue="noise_cancelling"
+            android:entries="@array/sony_ambient_sound_control_names"
+            android:entryValues="@array/sony_ambient_sound_control_values"
+            android:icon="@drawable/ic_hearing"
+            android:key="pref_soundcore_ambient_sound_control"
+            android:summary="%s"
+            android:title="@string/sony_ambient_sound" />
+
+        <SwitchPreferenceCompat
+            android:defaultValue="true"
+            android:disableDependentsState="true"
+            android:icon="@drawable/ic_hearing"
+            android:key="pref_adaptive_noise_cancelling"
+            android:layout="@layout/preference_checkbox"
+            android:summary="@string/pref_adaptive_noise_cancelling_summary"
+            android:title="@string/pref_adaptive_noise_cancelling_title" />
+
+        <!-- [0, 2], low moderate and high -->
+        <SeekBarPreference
+            android:dependency="pref_adaptive_noise_cancelling"
+            android:defaultValue="0"
+            android:icon="@drawable/ic_hearing"
+            android:key="pref_sony_ambient_sound_level"
+            android:max="2"
+            android:title="@string/prefs_active_noise_cancelling_level" />
+
+
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:icon="@drawable/ic_block"
+            android:key="pref_soundcore_wind_noise_reduction"
+            android:layout="@layout/preference_checkbox"
+            android:title="@string/sony_ambient_sound_wind_noise_reduction" />
+
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:icon="@drawable/ic_voice"
+            android:key="pref_soundcore_transparency_vocal_mode"
+            android:layout="@layout/preference_checkbox"
+            android:title="@string/sony_ambient_sound_focus_voice" />
+
+    </PreferenceCategory>
+
+
+
+    <PreferenceCategory
+        android:key="pref_key_header_soundcore_other"
+        android:title="@string/pref_header_other">
+
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:key="pref_soundcore_wearing_detection"
+            android:layout="@layout/preference_checkbox"
+            android:summary="@string/nothing_prefs_inear_summary"
+            android:title="@string/nothing_prefs_inear_title"/>
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:key="pref_soundcore_wearing_tone"
+            android:layout="@layout/preference_checkbox"
+            android:summary="@string/pref_wearing_tone_summary"
+            android:title="@string/pref_wearing_tone"/>
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:key="pref_soundcore_touch_tone"
+            android:layout="@layout/preference_checkbox"
+            android:summary="@string/pref_touch_tone_summary"
+            android:title="@string/pref_touch_tone"/>
+    </PreferenceCategory>
+
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_soundcore_motion300.xml b/app/src/main/res/xml/devicesettings_soundcore_motion300.xml
new file mode 100644
index 0000000..2cae644
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_soundcore_motion300.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <SwitchPreferenceCompat
+        android:defaultValue="true"
+        android:icon="@drawable/ic_voice"
+        android:key="pref_soundcore_voice_prompts"
+        android:layout="@layout/preference_checkbox"
+        android:title="@string/soundcore_voice_prompts" />
+    <ListPreference
+        android:defaultValue="70"
+        android:entries="@array/soundcore_button_brightness_names"
+        android:entryValues="@array/soundcore_button_brightness_values"
+        android:icon="@drawable/ic_wb_sunny"
+        android:key="pref_soundcore_button_brightness"
+        android:summary="%s"
+        android:title="@string/soundcore_button_brightness" />
+    <ListPreference
+        android:defaultValue="3"
+        android:entries="@array/soundcore_auto_power_off_names"
+        android:entryValues="@array/soundcore_auto_power_off_values"
+        android:icon="@drawable/ic_power_settings_new"
+        android:key="pref_soundcore_auto_power_off"
+        android:summary="%s"
+        android:title="@string/prefs_wena3_auto_power_off_item" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_soundcore_motion300_audio.xml b/app/src/main/res/xml/devicesettings_soundcore_motion300_audio.xml
new file mode 100644
index 0000000..5f89b04
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_soundcore_motion300_audio.xml
@@ -0,0 +1,202 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+    <SwitchPreferenceCompat
+        android:defaultValue="false"
+        android:icon="@drawable/ic_music_note"
+        android:key="pref_soundcore_ldac_mode"
+        android:layout="@layout/preference_checkbox"
+        android:title="@string/soundcore_ldac_mode_title"
+        android:summary="@string/soundcore_ldac_mode_summary" />
+    <PreferenceCategory android:title="@string/pref_header_equalizer">
+        <SwitchPreferenceCompat
+            android:defaultValue="true"
+            android:icon="@drawable/ic_switch_left"
+            android:key="pref_soundcore_adaptive_direction"
+            android:layout="@layout/preference_checkbox"
+            android:title="@string/soundcore_adaptive_direction_title"
+            android:summary="@string/soundcore_adaptive_direction_summary" />
+        <ListPreference
+            android:defaultValue="0"
+            android:entries="@array/soundcore_equalizer_preset_names"
+            android:entryValues="@array/soundcore_equalizer_preset_values"
+            android:icon="@drawable/ic_equalizer"
+            android:key="pref_soundcore_equalizer_preset"
+            android:summary="%s"
+            android:title="@string/soundcore_equalizer_preset" />
+        <PreferenceScreen
+            android:persistent="false"
+            android:icon="@drawable/ic_graphic_eq"
+            android:key="pref_soundcore_equalizer_custom"
+            android:title="@string/soundcore_equalizer_custom_title"
+            android:summary="@string/soundcore_equalizer_custom_summary">
+            <ListPreference
+                android:defaultValue="0"
+                android:entries="@array/soundcore_equalizer_direction_names"
+                android:entryValues="@array/soundcore_equalizer_direction_values"
+                android:icon="@drawable/ic_switch_left"
+                android:key="pref_soundcore_equalizer_direction"
+                android:summary="%s"
+                android:title="@string/soundcore_equalizer_direction" />
+            <Preference
+                android:persistent="false"
+                android:icon="@drawable/ic_history"
+                android:key="pref_soundcore_equalizer_reset"
+                android:summary="@string/soundcore_equalizer_reset_summary"
+                android:title="@string/soundcore_equalizer_reset_title" />
+            <PreferenceCategory android:title="@string/soundcore_equalizer_band1">
+                <ListPreference
+                    android:defaultValue="7"
+                    android:entries="@array/soundcore_equalizer_band1_freq_names"
+                    android:entryValues="@array/soundcore_equalizer_freq_values"
+                    android:icon="@drawable/ic_hearing"
+                    android:key="pref_soundcore_equalizer_band1_freq"
+                    android:summary="%s"
+                    android:title="@string/soundcore_equalizer_frequency" />
+                <SeekBarPreference
+                    android:defaultValue="60"
+                    android:icon="@drawable/ic_graphic_eq"
+                    android:key="pref_soundcore_equalizer_band1_value"
+                    android:max="120"
+                    android:title="@string/soundcore_equalizer_value"
+                    app:showSeekBarValue="true" />
+            </PreferenceCategory>
+            <PreferenceCategory android:title="@string/soundcore_equalizer_band2">
+                <ListPreference
+                    android:defaultValue="7"
+                    android:entries="@array/soundcore_equalizer_band2_freq_names"
+                    android:entryValues="@array/soundcore_equalizer_freq_values"
+                    android:icon="@drawable/ic_hearing"
+                    android:key="pref_soundcore_equalizer_band2_freq"
+                    android:summary="%s"
+                    android:title="@string/soundcore_equalizer_frequency" />
+                <SeekBarPreference
+                    android:defaultValue="60"
+                    android:icon="@drawable/ic_graphic_eq"
+                    android:key="pref_soundcore_equalizer_band2_value"
+                    android:max="120"
+                    android:title="@string/soundcore_equalizer_value"
+                    app:showSeekBarValue="true" />
+            </PreferenceCategory>
+            <PreferenceCategory android:title="@string/soundcore_equalizer_band3">
+                <ListPreference
+                    android:defaultValue="7"
+                    android:entries="@array/soundcore_equalizer_band3_freq_names"
+                    android:entryValues="@array/soundcore_equalizer_freq_values"
+                    android:icon="@drawable/ic_hearing"
+                    android:key="pref_soundcore_equalizer_band3_freq"
+                    android:summary="%s"
+                    android:title="@string/soundcore_equalizer_frequency" />
+                <SeekBarPreference
+                    android:defaultValue="60"
+                    android:icon="@drawable/ic_graphic_eq"
+                    android:key="pref_soundcore_equalizer_band3_value"
+                    android:max="120"
+                    android:title="@string/soundcore_equalizer_value"
+                    app:showSeekBarValue="true" />
+            </PreferenceCategory>
+            <PreferenceCategory android:title="@string/soundcore_equalizer_band4">
+                <ListPreference
+                    android:defaultValue="7"
+                    android:entries="@array/soundcore_equalizer_band4_freq_names"
+                    android:entryValues="@array/soundcore_equalizer_freq_values"
+                    android:icon="@drawable/ic_hearing"
+                    android:key="pref_soundcore_equalizer_band4_freq"
+                    android:summary="%s"
+                    android:title="@string/soundcore_equalizer_frequency" />
+                <SeekBarPreference
+                    android:defaultValue="60"
+                    android:icon="@drawable/ic_graphic_eq"
+                    android:key="pref_soundcore_equalizer_band4_value"
+                    android:max="120"
+                    android:title="@string/soundcore_equalizer_value"
+                    app:showSeekBarValue="true" />
+            </PreferenceCategory>
+            <PreferenceCategory android:title="@string/soundcore_equalizer_band5">
+                <ListPreference
+                    android:defaultValue="7"
+                    android:entries="@array/soundcore_equalizer_band5_freq_names"
+                    android:entryValues="@array/soundcore_equalizer_freq_values"
+                    android:icon="@drawable/ic_hearing"
+                    android:key="pref_soundcore_equalizer_band5_freq"
+                    android:summary="%s"
+                    android:title="@string/soundcore_equalizer_frequency" />
+                <SeekBarPreference
+                    android:defaultValue="60"
+                    android:icon="@drawable/ic_graphic_eq"
+                    android:key="pref_soundcore_equalizer_band5_value"
+                    android:max="120"
+                    android:title="@string/soundcore_equalizer_value"
+                    app:showSeekBarValue="true" />
+            </PreferenceCategory>
+            <PreferenceCategory android:title="@string/soundcore_equalizer_band6">
+                <ListPreference
+                    android:defaultValue="7"
+                    android:entries="@array/soundcore_equalizer_band6_freq_names"
+                    android:entryValues="@array/soundcore_equalizer_freq_values"
+                    android:icon="@drawable/ic_hearing"
+                    android:key="pref_soundcore_equalizer_band6_freq"
+                    android:summary="%s"
+                    android:title="@string/soundcore_equalizer_frequency" />
+                <SeekBarPreference
+                    android:defaultValue="60"
+                    android:icon="@drawable/ic_graphic_eq"
+                    android:key="pref_soundcore_equalizer_band6_value"
+                    android:max="120"
+                    android:title="@string/soundcore_equalizer_value"
+                    app:showSeekBarValue="true" />
+            </PreferenceCategory>
+            <PreferenceCategory android:title="@string/soundcore_equalizer_band7">
+                <ListPreference
+                    android:defaultValue="7"
+                    android:entries="@array/soundcore_equalizer_band7_freq_names"
+                    android:entryValues="@array/soundcore_equalizer_freq_values"
+                    android:icon="@drawable/ic_hearing"
+                    android:key="pref_soundcore_equalizer_band7_freq"
+                    android:summary="%s"
+                    android:title="@string/soundcore_equalizer_frequency" />
+                <SeekBarPreference
+                    android:defaultValue="60"
+                    android:icon="@drawable/ic_graphic_eq"
+                    android:key="pref_soundcore_equalizer_band7_value"
+                    android:max="120"
+                    android:title="@string/soundcore_equalizer_value"
+                    app:showSeekBarValue="true" />
+            </PreferenceCategory>
+            <PreferenceCategory android:title="@string/soundcore_equalizer_band8">
+                <ListPreference
+                    android:defaultValue="6"
+                    android:entries="@array/soundcore_equalizer_band8_freq_names"
+                    android:entryValues="@array/soundcore_equalizer_freq_values"
+                    android:icon="@drawable/ic_hearing"
+                    android:key="pref_soundcore_equalizer_band8_freq"
+                    android:summary="%s"
+                    android:title="@string/soundcore_equalizer_frequency" />
+                <SeekBarPreference
+                    android:defaultValue="60"
+                    android:icon="@drawable/ic_graphic_eq"
+                    android:key="pref_soundcore_equalizer_band8_value"
+                    android:max="120"
+                    android:title="@string/soundcore_equalizer_value"
+                    app:showSeekBarValue="true" />
+            </PreferenceCategory>
+            <PreferenceCategory android:title="@string/soundcore_equalizer_band9">
+                <ListPreference
+                    android:defaultValue="1"
+                    android:entries="@array/soundcore_equalizer_band9_freq_names"
+                    android:entryValues="@array/soundcore_equalizer_freq_values"
+                    android:icon="@drawable/ic_hearing"
+                    android:key="pref_soundcore_equalizer_band9_freq"
+                    android:summary="%s"
+                    android:title="@string/soundcore_equalizer_frequency" />
+                <SeekBarPreference
+                    android:defaultValue="60"
+                    android:icon="@drawable/ic_graphic_eq"
+                    android:key="pref_soundcore_equalizer_band9_value"
+                    android:max="120"
+                    android:title="@string/soundcore_equalizer_value"
+                    app:showSeekBarValue="true" />
+            </PreferenceCategory>
+        </PreferenceScreen>
+    </PreferenceCategory>
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_soundcore_touch_options.xml b/app/src/main/res/xml/devicesettings_soundcore_touch_options.xml
new file mode 100644
index 0000000..5b8cb96
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_soundcore_touch_options.xml
@@ -0,0 +1,107 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceCategory android:title="@string/single_tap">
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:disableDependentsState="true"
+            android:icon="@drawable/ic_lock_open"
+            android:key="pref_soundcore_control_single_tap_disabled"
+            android:layout="@layout/preference_checkbox"
+            android:summary="@string/prefs_touch_lock_summary"
+            android:title="@string/prefs_touch_lock" />
+            <ListPreference
+                android:dependency="pref_soundcore_control_single_tap_disabled"
+                android:entries="@array/soundcore_button_function_names"
+                android:entryValues="@array/soundcore_button_function_values"
+                android:icon="@drawable/ic_touch"
+                android:key="pref_soundcore_control_single_tap_action_left"
+                android:summary="%s"
+                android:title="@string/prefs_left" />
+            <ListPreference
+                android:dependency="pref_soundcore_control_single_tap_disabled"
+                android:entries="@array/soundcore_button_function_names"
+                android:entryValues="@array/soundcore_button_function_values"
+                android:icon="@drawable/ic_touch"
+                android:key="pref_soundcore_control_single_tap_action_right"
+                android:summary="%s"
+                android:title="@string/prefs_right" />
+    </PreferenceCategory>
+    <PreferenceCategory android:title="@string/double_tap">
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:disableDependentsState="true"
+            android:icon="@drawable/ic_lock_open"
+            android:key="pref_soundcore_control_double_tap_disabled"
+            android:layout="@layout/preference_checkbox"
+            android:summary="@string/prefs_touch_lock_summary"
+            android:title="@string/prefs_touch_lock" />
+        <ListPreference
+            android:dependency="pref_soundcore_control_double_tap_disabled"
+            android:entries="@array/soundcore_button_function_names"
+            android:entryValues="@array/soundcore_button_function_values"
+            android:icon="@drawable/ic_touch"
+            android:key="pref_soundcore_control_double_tap_action_left"
+            android:summary="%s"
+            android:title="@string/prefs_left" />
+        <ListPreference
+            android:dependency="pref_soundcore_control_double_tap_disabled"
+            android:entries="@array/soundcore_button_function_names"
+            android:entryValues="@array/soundcore_button_function_values"
+            android:icon="@drawable/ic_touch"
+            android:key="pref_soundcore_control_double_tap_action_right"
+            android:summary="%s"
+            android:title="@string/prefs_right" />
+    </PreferenceCategory>
+    <PreferenceCategory android:title="@string/triple_tap">
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:disableDependentsState="true"
+            android:icon="@drawable/ic_lock_open"
+            android:key="pref_soundcore_control_triple_tap_disabled"
+            android:layout="@layout/preference_checkbox"
+            android:summary="@string/prefs_touch_lock_summary"
+            android:title="@string/prefs_touch_lock" />
+        <ListPreference
+            android:dependency="pref_soundcore_control_triple_tap_disabled"
+            android:entries="@array/soundcore_button_function_names"
+            android:entryValues="@array/soundcore_button_function_values"
+            android:icon="@drawable/ic_touch"
+            android:key="pref_soundcore_control_triple_tap_action_left"
+            android:summary="%s"
+            android:title="@string/prefs_left" />
+        <ListPreference
+            android:dependency="pref_soundcore_control_triple_tap_disabled"
+            android:entries="@array/soundcore_button_function_names"
+            android:entryValues="@array/soundcore_button_function_values"
+            android:icon="@drawable/ic_touch"
+            android:key="pref_soundcore_control_triple_tap_action_right"
+            android:summary="%s"
+            android:title="@string/prefs_right" />
+    </PreferenceCategory>
+    <PreferenceCategory android:title="@string/long_press">
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:disableDependentsState="true"
+            android:icon="@drawable/ic_lock_open"
+            android:key="pref_soundcore_control_long_press_disabled"
+            android:layout="@layout/preference_checkbox"
+            android:summary="@string/prefs_touch_lock_summary"
+            android:title="@string/prefs_touch_lock" />
+        <ListPreference
+            android:dependency="pref_soundcore_control_long_press_disabled"
+            android:entries="@array/soundcore_button_function_names"
+            android:entryValues="@array/soundcore_button_function_values"
+            android:icon="@drawable/ic_touch"
+            android:key="pref_soundcore_control_long_press_action_left"
+            android:summary="%s"
+            android:title="@string/prefs_left" />
+        <ListPreference
+            android:dependency="pref_soundcore_control_long_press_disabled"
+            android:entries="@array/soundcore_button_function_names"
+            android:entryValues="@array/soundcore_button_function_values"
+            android:icon="@drawable/ic_touch"
+            android:key="pref_soundcore_control_long_press_action_right"
+            android:summary="%s"
+            android:title="@string/prefs_right" />
+    </PreferenceCategory>
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_spo_automatic_enable.xml b/app/src/main/res/xml/devicesettings_spo_automatic_enable.xml
new file mode 100644
index 0000000..7cd6e26
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_spo_automatic_enable.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <SwitchPreferenceCompat
+        android:defaultValue="false"
+        android:icon="@drawable/ic_heart"
+        android:key="spo_automatic_enable"
+        android:layout="@layout/preference_checkbox"
+        android:title="@string/pref_spo_automatic_enable" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_swipeunlock.xml b/app/src/main/res/xml/devicesettings_swipeunlock.xml
new file mode 100644
index 0000000..fb2b11a
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_swipeunlock.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <SwitchPreferenceCompat
+        android:defaultValue="false"
+        android:icon="@drawable/ic_lock_open"
+        android:key="swipe_unlock"
+        android:layout="@layout/preference_checkbox"
+        android:summary="@string/mi3_prefs_band_screen_unlock_summary"
+        android:title="@string/mi3_prefs_band_screen_unlock" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_sync_calendar.xml b/app/src/main/res/xml/devicesettings_sync_calendar.xml
new file mode 100644
index 0000000..d8183ed
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_sync_calendar.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+    <SwitchPreferenceCompat
+        android:defaultValue="false"
+        android:icon="@drawable/ic_calendar_sync"
+        android:key="sync_calendar"
+        android:layout="@layout/preference_checkbox"
+        android:summary="@string/pref_summary_sync_calendar"
+        android:title="@string/pref_title_sync_caldendar" />
+    <EditTextPreference
+        android:defaultValue="7"
+        android:dependency="sync_calendar"
+        android:icon="@drawable/ic_calendar_to"
+        android:inputType="number"
+        android:key="calendar_lookahead_days"
+        android:maxLength="3"
+        android:title="@string/pref_title_calendar_lookahead"
+        app:useSimpleSummaryProvider="true" />
+    <SwitchPreferenceCompat
+        android:defaultValue="false"
+        android:dependency="sync_calendar"
+        android:icon="@drawable/ic_person"
+        android:key="sync_birthdays"
+        android:layout="@layout/preference_checkbox"
+        android:summary="@string/pref_summary_sync_birthdays"
+        android:title="@string/pref_title_sync_birthdays" />
+    <Preference
+        android:dependency="sync_calendar"
+        android:icon="@drawable/ic_block"
+        android:key="blacklist_calendars"
+        android:summary="@string/pref_blacklist_calendars_summary"
+        android:title="@string/pref_blacklist_calendars" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_temperature_automatic_enable.xml b/app/src/main/res/xml/devicesettings_temperature_automatic_enable.xml
new file mode 100644
index 0000000..5bf81f7
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_temperature_automatic_enable.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <SwitchPreferenceCompat
+        android:icon="@drawable/ic_temperature"
+        android:defaultValue="false"
+        android:key="continuous_skin_temperature_measurement"
+        android:layout="@layout/preference_checkbox"
+        android:title="@string/pref_continuous_skin_temperature_measurement_title" />
+</androidx.preference.PreferenceScreen>
\ No newline at end of file
diff --git a/app/src/main/res/xml/devicesettings_temperature_scale_cf.xml b/app/src/main/res/xml/devicesettings_temperature_scale_cf.xml
new file mode 100644
index 0000000..16e5f13
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_temperature_scale_cf.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <ListPreference
+        android:icon="@drawable/ic_thermometer"
+        android:key="temperature_scale_cf"
+        android:title="@string/temperature_scale_cf"
+        android:entries="@array/temperature_scales_cf"
+        android:entryValues="@array/temperature_scales_cf_values"
+        android:summary="@string/temperature_scale_cf_summary" />
+</PreferenceScreen>
\ No newline at end of file
diff --git a/app/src/main/res/xml/devicesettings_test_features.xml b/app/src/main/res/xml/devicesettings_test_features.xml
new file mode 100644
index 0000000..4a62220
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_test_features.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <!-- the values will be populated by the TestDeviceSpecificSettingsCustomizer -->
+    <MultiSelectListPreference
+        android:defaultValue="@array/empty_array"
+        android:entries="@array/empty_array"
+        android:entryValues="@array/empty_array"
+        android:icon="@drawable/ic_developer_mode"
+        android:key="test_features"
+        android:summary="@string/pref_test_features_summary"
+        android:title="@string/pref_test_features_title" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_text_bitmaps.xml b/app/src/main/res/xml/devicesettings_text_bitmaps.xml
new file mode 100644
index 0000000..918bd44
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_text_bitmaps.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <SwitchPreferenceCompat
+        android:defaultValue="false"
+        android:icon="@drawable/ic_translate"
+        android:key="banglejs_text_bitmap"
+        android:layout="@layout/preference_checkbox"
+        android:summary="@string/pref_summary_banglejs_text_bitmap"
+        android:title="@string/pref_title_banglejs_text_bitmap" />
+    <EditTextPreference
+        android:dependency="banglejs_text_bitmap"
+        android:defaultValue="18"
+        android:inputType="number"
+        android:icon="@drawable/ic_font_size"
+        android:key="banglejs_txt_bitmap_size"
+        android:summary="@string/pref_summary_banglejs_txt_bitmap_size"
+        android:title="@string/pref_title_banglejs_txt_bitmap_size" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_time_sync.xml b/app/src/main/res/xml/devicesettings_time_sync.xml
new file mode 100644
index 0000000..4c10090
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_time_sync.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <SwitchPreferenceCompat
+        android:defaultValue="true"
+        android:icon="@drawable/ic_sync"
+        android:key="time_sync"
+        android:layout="@layout/preference_checkbox"
+        android:title="@string/pref_time_sync" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_timeformat.xml b/app/src/main/res/xml/devicesettings_timeformat.xml
new file mode 100644
index 0000000..b733bf0
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_timeformat.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <ListPreference
+        android:defaultValue="@string/p_timeformat_auto"
+        android:entries="@array/pref_timeformat_entries"
+        android:entryValues="@array/pref_timeformat_values"
+        android:icon="@drawable/ic_access_time"
+        android:key="timeformat"
+        android:summary="%s"
+        android:title="@string/pref_title_timeformat" />
+
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_transliteration.xml b/app/src/main/res/xml/devicesettings_transliteration.xml
new file mode 100644
index 0000000..59e0c44
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_transliteration.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <com.mobeta.android.dslv.DragSortListPreference
+        android:defaultValue="@array/pref_transliteration_languages_default"
+        android:dialogTitle="@string/pref_title_transliteration"
+        android:entries="@array/pref_transliteration_languages"
+        android:entryValues="@array/pref_transliteration_languages_values"
+        android:icon="@drawable/ic_translate"
+        android:key="pref_transliteration_languages"
+        android:persistent="true"
+        android:summary="@string/pref_summary_transliteration"
+        android:title="@string/pref_title_transliteration" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_trusleep.xml b/app/src/main/res/xml/devicesettings_trusleep.xml
new file mode 100644
index 0000000..912e0e1
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_trusleep.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceScreen
+        android:icon="@drawable/ic_activity_sleep"
+        android:key="screen_trusleep"
+        android:persistent="false"
+        android:title="@string/huawei_trusleep_title"
+        android:summary="@string/huawei_trusleep_summary_light">
+
+        <PreferenceScreen
+            android:icon="@drawable/ic_activity_sleep"
+            android:key="screen_trusleep_warning"
+            android:title="@string/huawei_trusleep_title"
+            android:summary="@string/huawei_trusleep_warning">
+
+            <SwitchPreferenceCompat
+                android:icon="@drawable/ic_activity_sleep"
+                android:defaultValue="false"
+                android:key="trusleep"
+                android:layout="@layout/preference_checkbox"
+                android:title="@string/huawei_trusleep_title"
+                android:summary="@string/huawei_trusleep_summary"/>
+
+        </PreferenceScreen>
+    </PreferenceScreen>
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_um25.xml b/app/src/main/res/xml/devicesettings_um25.xml
new file mode 100644
index 0000000..b005f48
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_um25.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<PreferenceScreen
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+    <CheckBoxPreference
+        android:title="Notify if below current threshold"
+        android:key="um25_current_threshold_notify" />
+    <EditTextPreference
+        android:title="Current threshold im mA"
+        android:key="um25_current_threshold"
+        android:inputType="number"
+        android:defaultValue="100"
+        app:useSimpleSummaryProvider="true" />/>
+</PreferenceScreen>
\ No newline at end of file
diff --git a/app/src/main/res/xml/devicesettings_vesc.xml b/app/src/main/res/xml/devicesettings_vesc.xml
new file mode 100644
index 0000000..d4906ee
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_vesc.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<PreferenceScreen
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+
+    <EditTextPreference
+        android:title="Minimum battery voltage"
+        android:key="vesc_minimum_battery_voltage"
+        android:inputType="number"
+        app:useSimpleSummaryProvider="true" />
+
+    <EditTextPreference
+        android:title="Maximum battery voltage"
+        android:key="vesc_maximum_battery_voltage"
+        android:inputType="number"
+        app:useSimpleSummaryProvider="true" />
+
+</PreferenceScreen>
\ No newline at end of file
diff --git a/app/src/main/res/xml/devicesettings_vibrationpatterns.xml b/app/src/main/res/xml/devicesettings_vibrationpatterns.xml
new file mode 100644
index 0000000..ec0acaa
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_vibrationpatterns.xml
@@ -0,0 +1,323 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+    <PreferenceScreen
+        android:icon="@drawable/ic_vibration"
+        android:key="vibration_patterns_key"
+        android:persistent="false"
+        android:summary="@string/pref_screen_vibration_patterns_summary"
+        android:title="@string/pref_screen_vibration_patterns_title">
+
+        <PreferenceCategory android:title="@string/pref_screen_vibration_patterns_title" />
+
+        <PreferenceScreen
+            android:icon="@drawable/ic_settings_applications"
+            android:key="vibration_profile_key_app_alerts"
+            android:persistent="false"
+            android:title="@string/pref_screen_notification_profile_generic">
+
+            <!-- workaround for missing toolbar -->
+            <PreferenceCategory android:title="@string/pref_screen_notification_profile_generic" />
+
+            <ListPreference
+                android:defaultValue="@string/p_default"
+                android:entries="@array/vibration_profile"
+                android:entryValues="@array/vibration_profile_values"
+                android:key="huami_vibration_profile_app_alerts"
+                android:summary="%s"
+                android:title="@string/miband_prefs_vibration" />
+
+            <EditTextPreference
+                android:defaultValue="2"
+                android:inputType="number"
+                android:key="huami_vibration_count_app_alerts"
+                android:maxLength="1"
+                android:title="@string/pref_title_notifications_repetitions"
+                app:useSimpleSummaryProvider="true" />
+
+            <Preference
+                android:key="huami_vibration_try_app_alerts"
+                android:persistent="false"
+                android:title="@string/vibration_try" />
+        </PreferenceScreen>
+
+        <PreferenceScreen
+            android:icon="@drawable/ic_phone"
+            android:key="vibration_profile_key_incoming_call"
+            android:persistent="false"
+            android:title="@string/pref_screen_notification_profile_incoming_call">
+
+            <!-- workaround for missing toolbar -->
+            <PreferenceCategory android:title="@string/pref_screen_notification_profile_incoming_call" />
+
+            <ListPreference
+                android:defaultValue="@string/p_default"
+                android:entries="@array/vibration_profile"
+                android:entryValues="@array/vibration_profile_values"
+                android:key="huami_vibration_profile_incoming_call"
+                android:summary="%s"
+                android:title="@string/miband_prefs_vibration" />
+
+            <EditTextPreference
+                android:defaultValue="1"
+                android:inputType="number"
+                android:key="huami_vibration_count_incoming_call"
+                android:maxLength="1"
+                android:title="@string/pref_title_notifications_repetitions"
+                app:useSimpleSummaryProvider="true" />
+
+            <Preference
+                android:key="huami_vibration_try_incoming_call"
+                android:persistent="false"
+                android:title="@string/vibration_try" />
+        </PreferenceScreen>
+
+        <PreferenceScreen
+            android:icon="@drawable/ic_message_outline"
+            android:key="vibration_profile_key_incoming_sms"
+            android:persistent="false"
+            android:title="@string/pref_screen_notification_profile_sms">
+
+            <!-- workaround for missing toolbar -->
+            <PreferenceCategory android:title="@string/pref_screen_notification_profile_sms" />
+
+            <ListPreference
+                android:defaultValue="@string/p_default"
+                android:entries="@array/vibration_profile"
+                android:entryValues="@array/vibration_profile_values"
+                android:key="huami_vibration_profile_incoming_sms"
+                android:summary="%s"
+                android:title="@string/miband_prefs_vibration" />
+
+            <EditTextPreference
+                android:defaultValue="2"
+                android:inputType="number"
+                android:key="huami_vibration_count_incoming_sms"
+                android:maxLength="1"
+                android:title="@string/pref_title_notifications_repetitions"
+                app:useSimpleSummaryProvider="true" />
+
+            <Preference
+                android:key="huami_vibration_try_incoming_sms"
+                android:persistent="false"
+                android:title="@string/vibration_try" />
+        </PreferenceScreen>
+
+        <PreferenceScreen
+            android:icon="@drawable/ic_star_gray"
+            android:key="vibration_profile_key_goal_notification"
+            android:persistent="false"
+            android:title="@string/mi2_prefs_goal_notification">
+
+            <!-- workaround for missing toolbar -->
+            <PreferenceCategory android:title="@string/mi2_prefs_goal_notification" />
+
+            <ListPreference
+                android:defaultValue="@string/p_default"
+                android:entries="@array/vibration_profile"
+                android:entryValues="@array/vibration_profile_values"
+                android:key="huami_vibration_profile_goal_notification"
+                android:summary="%s"
+                android:title="@string/miband_prefs_vibration" />
+
+            <EditTextPreference
+                android:defaultValue="1"
+                android:inputType="number"
+                android:key="huami_vibration_count_goal_notification"
+                android:maxLength="1"
+                android:title="@string/pref_title_notifications_repetitions"
+                app:useSimpleSummaryProvider="true" />
+
+            <Preference
+                android:key="huami_vibration_try_goal_notification"
+                android:persistent="false"
+                android:title="@string/vibration_try" />
+        </PreferenceScreen>
+
+        <PreferenceScreen
+            android:icon="@drawable/ic_access_alarms"
+            android:key="vibration_profile_key_alarm"
+            android:persistent="false"
+            android:title="@string/vibration_profile_alarm_clock">
+
+            <!-- workaround for missing toolbar -->
+            <PreferenceCategory android:title="@string/vibration_profile_alarm_clock" />
+
+            <ListPreference
+                android:defaultValue="@string/p_default"
+                android:entries="@array/vibration_profile"
+                android:entryValues="@array/vibration_profile_values"
+                android:key="huami_vibration_profile_alarm"
+                android:summary="%s"
+                android:title="@string/miband_prefs_vibration" />
+
+            <EditTextPreference
+                android:defaultValue="7"
+                android:inputType="number"
+                android:key="huami_vibration_count_alarm"
+                android:maxLength="1"
+                android:title="@string/pref_title_notifications_repetitions"
+                app:useSimpleSummaryProvider="true" />
+
+            <Preference
+                android:key="huami_vibration_try_alarm"
+                android:persistent="false"
+                android:title="@string/vibration_try" />
+        </PreferenceScreen>
+
+        <PreferenceScreen
+            android:icon="@drawable/ic_chair"
+            android:key="vibration_profile_key_idle_alerts"
+            android:persistent="false"
+            android:title="@string/pref_screen_notification_idle_alerts">
+
+            <!-- workaround for missing toolbar -->
+            <PreferenceCategory android:title="@string/pref_screen_notification_idle_alerts" />
+
+            <ListPreference
+                android:defaultValue="@string/p_default"
+                android:entries="@array/vibration_profile"
+                android:entryValues="@array/vibration_profile_values"
+                android:key="huami_vibration_profile_idle_alerts"
+                android:summary="%s"
+                android:title="@string/miband_prefs_vibration" />
+
+            <EditTextPreference
+                android:defaultValue="2"
+                android:inputType="number"
+                android:key="huami_vibration_count_idle_alerts"
+                android:maxLength="1"
+                android:title="@string/pref_title_notifications_repetitions"
+                app:useSimpleSummaryProvider="true" />
+
+            <Preference
+                android:key="huami_vibration_try_idle_alerts"
+                android:persistent="false"
+                android:title="@string/vibration_try" />
+        </PreferenceScreen>
+
+        <PreferenceScreen
+            android:icon="@drawable/ic_device_set_reminders"
+            android:key="vibration_profile_key_event_reminder"
+            android:persistent="false"
+            android:title="@string/pref_screen_notification_profile_event_reminder">
+
+            <!-- workaround for missing toolbar -->
+            <PreferenceCategory android:title="@string/pref_screen_notification_profile_event_reminder" />
+
+            <ListPreference
+                android:defaultValue="@string/p_default"
+                android:entries="@array/vibration_profile"
+                android:entryValues="@array/vibration_profile_values"
+                android:key="huami_vibration_profile_event_reminder"
+                android:summary="%s"
+                android:title="@string/miband_prefs_vibration" />
+
+            <EditTextPreference
+                android:defaultValue="1"
+                android:inputType="number"
+                android:key="huami_vibration_count_event_reminder"
+                android:maxLength="1"
+                android:title="@string/pref_title_notifications_repetitions"
+                app:useSimpleSummaryProvider="true" />
+
+            <Preference
+                android:key="huami_vibration_try_event_reminder"
+                android:persistent="false"
+                android:title="@string/vibration_try" />
+        </PreferenceScreen>
+
+        <PreferenceScreen
+            android:icon="@drawable/ic_action_find_lost_device"
+            android:key="vibration_profile_key_find_band"
+            android:persistent="false"
+            android:title="@string/pref_screen_notification_profile_find_device">
+
+            <!-- workaround for missing toolbar -->
+            <PreferenceCategory android:title="@string/pref_screen_notification_profile_find_device" />
+
+            <ListPreference
+                android:defaultValue="@string/p_default"
+                android:entries="@array/vibration_profile"
+                android:entryValues="@array/vibration_profile_values"
+                android:key="huami_vibration_profile_find_band"
+                android:summary="%s"
+                android:title="@string/miband_prefs_vibration" />
+
+            <EditTextPreference
+                android:defaultValue="1"
+                android:inputType="number"
+                android:key="huami_vibration_count_find_band"
+                android:maxLength="1"
+                android:title="@string/pref_title_notifications_repetitions"
+                app:useSimpleSummaryProvider="true" />
+
+            <Preference
+                android:key="huami_vibration_try_find_band"
+                android:persistent="false"
+                android:title="@string/vibration_try" />
+        </PreferenceScreen>
+
+        <PreferenceScreen
+            android:icon="@drawable/ic_checklist"
+            android:key="vibration_profile_key_todo_list"
+            android:persistent="false"
+            android:title="@string/pref_screen_notification_profile_todo_list">
+
+            <!-- workaround for missing toolbar -->
+            <PreferenceCategory android:title="@string/pref_screen_notification_profile_todo_list" />
+
+            <ListPreference
+                android:defaultValue="@string/p_default"
+                android:entries="@array/vibration_profile"
+                android:entryValues="@array/vibration_profile_values"
+                android:key="huami_vibration_profile_todo_list"
+                android:summary="%s"
+                android:title="@string/miband_prefs_vibration" />
+
+            <EditTextPreference
+                android:defaultValue="2"
+                android:inputType="number"
+                android:key="huami_vibration_count_todo_list"
+                android:maxLength="1"
+                android:title="@string/pref_title_notifications_repetitions"
+                app:useSimpleSummaryProvider="true" />
+
+            <Preference
+                android:key="huami_vibration_try_todo_list"
+                android:persistent="false"
+                android:title="@string/vibration_try" />
+        </PreferenceScreen>
+
+        <PreferenceScreen
+            android:icon="@drawable/ic_calendar_to"
+            android:key="vibration_profile_key_schedule"
+            android:persistent="false"
+            android:title="@string/pref_screen_notification_profile_schedule">
+
+            <!-- workaround for missing toolbar -->
+            <PreferenceCategory android:title="@string/pref_screen_notification_profile_schedule" />
+
+            <ListPreference
+                android:defaultValue="@string/p_default"
+                android:entries="@array/vibration_profile"
+                android:entryValues="@array/vibration_profile_values"
+                android:key="huami_vibration_profile_schedule"
+                android:summary="%s"
+                android:title="@string/miband_prefs_vibration" />
+
+            <EditTextPreference
+                android:defaultValue="2"
+                android:inputType="number"
+                android:key="huami_vibration_count_schedule"
+                android:maxLength="1"
+                android:title="@string/pref_title_notifications_repetitions"
+                app:useSimpleSummaryProvider="true" />
+
+            <Preference
+                android:key="huami_vibration_try_schedule"
+                android:persistent="false"
+                android:title="@string/vibration_try" />
+        </PreferenceScreen>
+    </PreferenceScreen>
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_vibrations_enable.xml b/app/src/main/res/xml/devicesettings_vibrations_enable.xml
new file mode 100644
index 0000000..617b8dc
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_vibrations_enable.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <SwitchPreferenceCompat
+        android:defaultValue="false"
+        android:icon="@drawable/ic_vibration"
+        android:key="vibration_enable"
+        android:layout="@layout/preference_checkbox"
+        android:summary="@string/enable_vibrations_summary"
+        android:title="@string/prefs_vibration_enable" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_vitality_score.xml b/app/src/main/res/xml/devicesettings_vitality_score.xml
new file mode 100644
index 0000000..72d93f6
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_vitality_score.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceScreen
+        android:icon="@drawable/ic_health"
+        android:key="pref_screen_vitality_score"
+        android:persistent="false"
+        android:title="@string/pref_vitality_score_title">
+
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:icon="@drawable/ic_notifications"
+            android:key="pref_vitality_score_7_day"
+            android:layout="@layout/preference_checkbox"
+            android:summary="@string/pref_vitality_score_7_day_summary"
+            android:title="@string/pref_vitality_score_7_day_title" />
+
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:icon="@drawable/ic_notifications"
+            android:key="pref_vitality_score_daily"
+            android:layout="@layout/preference_checkbox"
+            android:summary="@string/pref_vitality_score_daily_summary"
+            android:title="@string/pref_vitality_score_daily_title" />
+    </PreferenceScreen>
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_volume.xml b/app/src/main/res/xml/devicesettings_volume.xml
new file mode 100644
index 0000000..47b4a0c
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_volume.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+        <SeekBarPreference
+            android:defaultValue="15"
+            android:icon="@drawable/ic_volume_up"
+            android:key="volume"
+            android:max="30"
+            android:title="@string/menuitem_volume" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_watchxplus.xml b/app/src/main/res/xml/devicesettings_watchxplus.xml
new file mode 100644
index 0000000..6aef21e
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_watchxplus.xml
@@ -0,0 +1,112 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+    <PreferenceScreen
+        android:icon="@drawable/ic_vibration"
+        android:key="pref_category_notifications_and_calls"
+        android:title="@string/pref_header_notifications_and_calls">
+            <PreferenceCategory
+                android:title="@string/pref_screen_notification_profile_incoming_call"/>
+                <SeekBarPreference
+                    android:icon="@drawable/ic_rotate_left"
+                    android:defaultValue="0"
+                    android:key="notification_repeat_ring"
+                    android:max="10"
+                    android:title="@string/pref_title_notifications_and_calls_repeat_on_call"
+                    app:showSeekBarValue="true" />
+                <SwitchPreferenceCompat
+                    android:defaultValue="false"
+                    android:key="notification_enable_continious_ring"
+                    android:layout="@layout/preference_checkbox"
+                    android:title="@string/prefs_notifications_and_calls_continious_ring" />
+            <PreferenceCategory
+                android:title="@string/pref_screen_notification_profile_missed_call"/>
+                <SwitchPreferenceCompat
+                    android:defaultValue="false"
+                    android:key="notification_enable_missed_call"
+                    android:layout="@layout/preference_checkbox"
+                    android:title="@string/pref_notifications_and_calls_enable_misscall"
+                    android:summary="@string/pref_summary_notifications_and_calls_enable_misscall"/>
+                <SeekBarPreference
+                    android:icon="@drawable/ic_rotate_left"
+                    android:defaultValue="0"
+                    android:dependency="notification_enable_missed_call"
+                    android:key="notification_repeat_missed_call"
+                    android:max="10"
+                    android:title="@string/pref_title_notifications_and_calls_repeat_on_misscall"
+                    app:showSeekBarValue="true" />
+            <PreferenceCategory
+                android:title="@string/pref_header_notifications_and_calls_callhandling"/>
+                <SwitchPreferenceCompat
+                    android:defaultValue="false"
+                    android:key="notification_button_reject"
+                    android:layout="@layout/preference_checkbox"
+                    android:summary="@string/pref_summary_notifications_and_calls_title_reject"
+                    android:title="@string/prefs_notifications_and_calls_reject" />
+                <SwitchPreferenceCompat
+                    android:defaultValue="false"
+                    android:key="notification_shake_reject"
+                    android:layout="@layout/preference_checkbox"
+                    android:summary="@string/pref_summary_notifications_and_calls_title_shake_reject"
+                    android:title="@string/prefs_notifications_and_calls_shake_reject" />
+    </PreferenceScreen>
+
+    <PreferenceScreen
+        android:icon="@drawable/ic_settings"
+        android:key="pref_category_device_spec_settings"
+        android:title="@string/pref_header_device_spec_settings">
+        <ListPreference
+            android:icon="@drawable/ic_language"
+            android:defaultValue="1"
+            android:title="@string/pref_title_language"
+            android:entries="@array/language_only_EN_CH"
+            android:entryValues="@array/language_only_EN_CH_values"
+            android:key="language"
+            android:summary="%s" />
+        <SwitchPreferenceCompat
+            android:icon="@drawable/ic_access_time"
+            android:defaultValue="false"
+            android:key="pref_device_spec_settings_force_time"
+            android:layout="@layout/preference_checkbox"
+            android:summary="@string/pref_summary_device_spec_settings_title_force_time"
+            android:title="@string/pref_title_device_spec_settings_force_time" />
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:key="show_raw_graph"
+            android:layout="@layout/preference_checkbox"
+            android:title="@string/pref_title_device_spec_settings_show_raw_graph" />
+    </PreferenceScreen>
+    <PreferenceScreen
+        android:icon="@drawable/ic_sensor_calibration"
+        android:key="pref_sensors_calibration"
+        android:title="@string/pref_header_sensors_calibration">
+        <EditTextPreference
+            android:icon="@drawable/ic_arrow_upward"
+            android:defaultValue="200"
+            android:key="pref_sensors_altitude"
+            android:title="@string/pref_title_sensors_altitude"/>
+        <PreferenceScreen
+            android:key="pref_sensors_bp_calibration"
+            android:title="@string/pref_sensors_bp_calibration">
+            <PreferenceCategory
+                android:title="@string/pref_sensors_bp_calibration"/>
+            <EditTextPreference
+                android:inputType="number"
+                android:key="pref_sensors_bp_calibration_high"
+                android:defaultValue="130"
+                android:title="@string/pref_sensors_bp_calibration_high" />
+            <EditTextPreference
+                android:inputType="number"
+                android:key="pref_sensors_bp_calibration_low"
+                android:defaultValue="80"
+                android:title="@string/pref_sensors_bp_calibration_low" />
+            <ListPreference
+                android:defaultValue="1"
+                android:title="@string/prefs_sensors_button_bp_calibration"
+                android:entries="@array/sensors_bp_cal"
+                android:entryValues="@array/sensors_bp_cal_values"
+                android:key="prefs_sensors_button_bp_calibration"
+                android:summary="@string/prefs_sensors_button_bp_calibration_sum" />
+        </PreferenceScreen>
+    </PreferenceScreen>
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_wear_sensor_toggle.xml b/app/src/main/res/xml/devicesettings_wear_sensor_toggle.xml
new file mode 100644
index 0000000..61ed78f
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_wear_sensor_toggle.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <SwitchPreferenceCompat
+        android:defaultValue="false"
+        android:icon="@drawable/ic_hearing"
+        android:key="wear_sensor_toggle"
+        android:layout="@layout/preference_checkbox"
+        android:summary="@string/pref_wear_sensor_summary"
+        android:title="@string/pref_wear_sensor_title" />
+</androidx.preference.PreferenceScreen>
\ No newline at end of file
diff --git a/app/src/main/res/xml/devicesettings_weardirection.xml b/app/src/main/res/xml/devicesettings_weardirection.xml
new file mode 100644
index 0000000..dbfe705
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_weardirection.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <ListPreference
+        android:defaultValue="buttons_on_right"
+        android:entries="@array/weardirection"
+        android:entryValues="@array/weardirection_values"
+        android:icon="@drawable/ic_switch_left"
+        android:key="weardirection"
+        android:summary="%s"
+        android:title="@string/prefs_weardirection" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_wearlocation.xml b/app/src/main/res/xml/devicesettings_wearlocation.xml
new file mode 100644
index 0000000..8452ffc
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_wearlocation.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <ListPreference
+        android:defaultValue="left"
+        android:icon="@drawable/ic_switch_left"
+        android:entries="@array/wearside"
+        android:entryValues="@array/wearside_values"
+        android:key="wearlocation"
+        android:summary="%s"
+        android:title="@string/prefs_wearside" />
+
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_wearmode.xml b/app/src/main/res/xml/devicesettings_wearmode.xml
new file mode 100644
index 0000000..7584dd9
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_wearmode.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+    <ListPreference
+        android:defaultValue="band"
+        android:entries="@array/wearmode"
+        android:entryValues="@array/wearmode_values"
+        android:icon="@drawable/ic_switch_left"
+        android:key="wearmode"
+        android:title="@string/prefs_wearmode"
+        app:useSimpleSummaryProvider="true" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_wena3.xml b/app/src/main/res/xml/devicesettings_wena3.xml
new file mode 100644
index 0000000..3bdf624
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_wena3.xml
@@ -0,0 +1,222 @@
+<?xml version="1.0" encoding="utf-8"?>
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <!-- Not including PREF_LANGUAGE here since GadgetBridge seems to add it for us -->
+    <PreferenceCategory
+        android:title="@string/prefs_wena3_title_activity">
+        <SwitchPreferenceCompat
+            android:defaultValue="true"
+            android:key="pref_wena3_background_sync"
+            android:layout="@layout/preference_checkbox"
+            android:icon="@drawable/ic_access_time"
+            android:title="@string/prefs_wena3_item_background_sync"
+            android:summary="@string/prefs_wena3_hint_background_sync" />
+        <ListPreference
+            android:defaultValue="6"
+            android:entries="@array/prefs_wena3_day_start_list"
+            android:entryValues="@array/prefs_wena3_day_start_values_list"
+            android:key="pref_wena3_day_start_hour"
+            android:icon="@drawable/ic_wb_twilight"
+            android:summary="%s"
+            android:title="@string/prefs_wena3_day_start_hour_item" />
+    </PreferenceCategory>
+    <PreferenceCategory
+        android:title="@string/prefs_wena3_notification_settings_title">
+        <ListPreference
+            android:defaultValue="basic"
+            android:entries="@array/prefs_wena3_vibration_patterns"
+            android:entryValues="@array/prefs_wena3_vibration_pattern_values"
+            android:key="pref_wena3_default_vibration"
+            android:icon="@drawable/ic_vibration"
+            android:title="@string/prefs_wena3_notification_default_vibration"
+            android:summary="%s" />
+        <ListPreference
+            android:defaultValue="1"
+            android:entries="@array/prefs_wena3_vibration_times"
+            android:entryValues="@array/prefs_wena3_vibration_times_values"
+            android:key="pref_wena3_default_vibration_repetition"
+            android:icon="@drawable/ic_refresh"
+            android:title="@string/prefs_wena3_notification_default_vibration_repetition"
+            android:summary="%s" />
+        <ListPreference
+            android:defaultValue="blue"
+            android:entries="@array/prefs_wena3_led_patterns"
+            android:entryValues="@array/prefs_wena3_led_pattern_values"
+            android:key="pref_wena3_default_led_color"
+            android:icon="@drawable/ic_circle"
+            android:title="@string/prefs_wena3_notification_default_led"
+            android:summary="%s" />
+        <Preference
+            android:title="@string/prefs_wena3_notification_per_app_settings_title"
+            android:icon="@drawable/ic_widgets"
+            android:key="pref_per_app_notification_settings">
+            <intent android:action=".SonyWena3PerAppNotificationSettingsActivity"/>
+        </Preference>
+    </PreferenceCategory>
+    <PreferenceCategory
+        android:title="@string/prefs_wena3_receive_calls_title">
+        <SwitchPreferenceCompat
+            android:title="@string/prefs_wena3_receive_calls_item"
+            android:key="pref_wena3_receive_calls"
+            android:layout="@layout/preference_checkbox"
+            android:icon="@drawable/ic_phone"
+            android:summary="@string/prefs_wena3_receive_calls_hint"
+            android:defaultValue="true"
+            />
+        <ListPreference
+            android:defaultValue="continuous"
+            android:entries="@array/prefs_wena3_vibration_patterns"
+            android:entryValues="@array/prefs_wena3_vibration_pattern_values"
+            android:key="pref_wena3_call_default_vibration"
+            android:icon="@drawable/ic_vibration"
+            android:title="@string/prefs_wena3_notification_default_call_vibration"
+            android:summary="%s" />
+        <ListPreference
+            android:defaultValue="0"
+            android:entries="@array/prefs_wena3_vibration_times"
+            android:entryValues="@array/prefs_wena3_vibration_times_values"
+            android:key="pref_wena3_default_call_vibration_repetition"
+            android:icon="@drawable/ic_refresh"
+            android:title="@string/prefs_wena3_notification_default_call_vibration_repetition"
+            android:summary="%s" />
+        <ListPreference
+            android:defaultValue="white"
+            android:entries="@array/prefs_wena3_led_patterns"
+            android:entryValues="@array/prefs_wena3_led_pattern_values"
+            android:key="pref_wena3_default_call_led_color"
+            android:icon="@drawable/ic_circle"
+            android:title="@string/prefs_wena3_notification_default_call_led"
+            android:summary="%s" />
+    </PreferenceCategory>
+
+    <PreferenceCategory
+        android:title="@string/prefs_wena3_title_screen">
+        <ListPreference
+            android:defaultValue="5"
+            android:entries="@array/screen_timeout"
+            android:entryValues="@array/screen_timeout_values"
+            android:key="screen_timeout"
+            android:icon="@drawable/ic_hourglass_empty"
+            android:summary="%s"
+            android:title="@string/prefs_screen_timeout" />
+        <SwitchPreferenceCompat
+            android:title="@string/mi2_prefs_activate_display_on_lift"
+            android:key="pref_screen_lift_wrist"
+            android:layout="@layout/preference_checkbox"
+            android:icon="@drawable/ic_watch"
+            android:summary="@string/prefs_wena3_hint_lift_wrist"
+            android:defaultValue="false"
+            />
+        <SwitchPreferenceCompat
+            android:title="@string/prefs_wena3_item_weather_statusbar"
+            android:icon="@drawable/ic_wb_sunny"
+            android:key="pref_wena3_weather_in_statusbar"
+            android:layout="@layout/preference_checkbox"
+            android:summary="@string/prefs_wena3_hint_weather_statusbar"
+            android:defaultValue="true" />
+        <SwitchPreferenceCompat
+            android:title="@string/prefs_wena3_item_rich_design"
+            android:icon="@drawable/ic_auto_awesome"
+            android:key="pref_wena3_rich_design_mode"
+            android:layout="@layout/preference_checkbox"
+            android:summary="@string/prefs_wena3_hint_rich_design"
+            android:defaultValue="false" />
+        <SwitchPreferenceCompat
+            android:title="@string/prefs_wena3_item_large_font"
+            android:icon="@drawable/ic_font_size"
+            android:key="pref_wena3_large_font_size"
+            android:layout="@layout/preference_checkbox"
+            android:summary="@string/prefs_wena3_hint_large_font"
+            android:defaultValue="false"/>
+        <com.mobeta.android.dslv.DragSortListPreference
+            android:defaultValue="@array/prefs_wena3_menu_icons_default_list"
+            android:dialogTitle="@string/prefs_wena3_menu_icon_title"
+            android:entries="@array/prefs_wena3_menu_icons_names_list"
+            android:entryValues="@array/prefs_wena3_menu_icons_values_list"
+            android:icon="@drawable/ic_widgets"
+            android:key="pref_wena3_menu_icon_csv"
+            android:persistent="true"
+            android:summary="@string/prefs_wena3_menu_icon_hint"
+            android:title="@string/prefs_wena3_menu_icon_title" />
+        <com.mobeta.android.dslv.DragSortListPreference
+            android:defaultValue="@array/prefs_wena3_status_page_default_list"
+            android:dialogTitle="@string/prefs_wena3_status_page_title"
+            android:entries="@array/prefs_wena3_status_pages_list"
+            android:entryValues="@array/prefs_wena3_status_page_values_list"
+            android:icon="@drawable/ic_list_numbered"
+            android:key="pref_wena3_status_pages_csv"
+            android:persistent="true"
+            android:summary="%s"
+            android:title="@string/prefs_wena3_status_page_title" />
+    </PreferenceCategory>
+    <PreferenceCategory
+        android:title="@string/prefs_wena3_home_icon_title">
+        <ListPreference
+            android:key="pref_wena3_home_icon_left"
+            android:title="@string/prefs_home_icon_left_item"
+            android:defaultValue="music"
+            android:entries="@array/prefs_wena3_home_icons_names_list"
+            android:entryValues="@array/prefs_wena3_home_icons_values_list"
+            android:summary="%s"
+            android:icon="@drawable/ic_wena_icon_left" />
+        <ListPreference
+            android:key="pref_wena3_home_icon_center"
+            android:title="@string/prefs_home_icon_center_item"
+            android:defaultValue="pedometer"
+            android:entries="@array/prefs_wena3_home_icons_names_list"
+            android:entryValues="@array/prefs_wena3_home_icons_values_list"
+            android:summary="%s"
+            android:icon="@drawable/ic_wena_icon_center" />
+        <ListPreference
+            android:key="pref_wena3_home_icon_right"
+            android:title="@string/prefs_home_icon_right_item"
+            android:defaultValue="calories"
+            android:entries="@array/prefs_wena3_home_icons_names_list"
+            android:entryValues="@array/prefs_wena3_home_icons_values_list"
+            android:summary="%s"
+            android:icon="@drawable/ic_wena_icon_right" />
+    </PreferenceCategory>
+    <PreferenceCategory
+        android:title="@string/prefs_wena3_title_alarm">
+        <ListPreference
+            android:key="smart_alarm_interval_preference"
+            android:title="@string/prefs_wena3_smart_alarm_margin_item"
+            android:icon="@drawable/ic_access_alarms"
+            android:defaultValue="10"
+            android:entries="@array/sonyswr12_smart_alarm_intervals"
+            android:entryValues="@array/sonyswr12_smart_alarm_interval_values"
+            android:summary="%s" />
+    </PreferenceCategory>
+    <PreferenceCategory
+        android:title="@string/pref_header_vibration_settings">
+        <ListPreference
+            android:key="pref_wena3_vibration_strength"
+            android:title="@string/prefs_wena3_vibration_strength_item"
+            android:icon="@drawable/ic_vibration"
+            android:defaultValue="normal"
+            android:entries="@array/prefs_wena3_vibration_strength_names_list"
+            android:entryValues="@array/prefs_wena3_vibration_strength_values_list"
+            android:summary="%s" />
+        <SwitchPreferenceCompat
+            android:title="@string/prefs_wena3_vibration_smart_item"
+            android:key="pref_wena3_vibration_smart"
+            android:layout="@layout/preference_checkbox"
+            android:defaultValue="true" />
+    </PreferenceCategory>
+    <PreferenceCategory
+        android:title="@string/prefs_wena3_button_action_item">
+        <ListPreference
+            android:key="pref_wena3_button_double_action"
+            android:title="@string/prefs_wena3_button_action_item_double"
+            android:defaultValue="none"
+            android:entries="@array/prefs_wena3_button_action_list"
+            android:entryValues="@array/prefs_wena3_button_action_values_list"
+            android:summary="%s"/>
+        <ListPreference
+            android:key="pref_wena3_button_long_action"
+            android:title="@string/prefs_wena3_button_action_item_long"
+            android:defaultValue="none"
+            android:entries="@array/prefs_wena3_button_action_list"
+            android:entryValues="@array/prefs_wena3_button_action_values_list"
+            android:summary="%s"/>
+    </PreferenceCategory>
+</PreferenceScreen>
\ No newline at end of file
diff --git a/app/src/main/res/xml/devicesettings_wena3_auto_power_off.xml b/app/src/main/res/xml/devicesettings_wena3_auto_power_off.xml
new file mode 100644
index 0000000..a966fdf
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_wena3_auto_power_off.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceScreen
+        android:icon="@drawable/ic_power_settings_new"
+        android:key="screen_wena3_auto_power_off"
+        android:persistent="false"
+        android:summary="@string/prefs_wena3_auto_power_off_hint"
+        android:title="@string/prefs_wena3_auto_power_off_item">
+
+        <!-- workaround for missing toolbar -->
+        <PreferenceCategory android:title="@string/prefs_wena3_auto_power_off_item" />
+
+        <ListPreference
+            android:defaultValue="@string/p_off"
+            android:entries="@array/do_not_disturb_no_auto"
+            android:entryValues="@array/do_not_disturb_no_auto_values"
+            android:key="pref_wena3_power_schedule_kind"
+            android:summary="%s"
+            android:title="@string/prefs_wena3_auto_power_off_item" />
+
+        <nodomain.freeyourgadget.gadgetbridge.util.XTimePreference
+            android:defaultValue="22:00"
+            android:key="pref_wena3_power_schedule_start"
+            android:title="@string/prefs_wena3_auto_power_off_turn_off_time_item" />
+
+        <nodomain.freeyourgadget.gadgetbridge.util.XTimePreference
+            android:defaultValue="06:00"
+            android:key="pref_wena3_power_schedule_end"
+            android:title="@string/prefs_wena3_auto_power_off_turn_on_time_item" />
+
+    </PreferenceScreen>
+
+
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_widgets.xml b/app/src/main/res/xml/devicesettings_widgets.xml
new file mode 100644
index 0000000..5c137f9
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_widgets.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <Preference
+        android:icon="@drawable/ic_widgets"
+        android:key="pref_widgets"
+        android:title="@string/menuitem_widgets" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_wifi_hotspot.xml b/app/src/main/res/xml/devicesettings_wifi_hotspot.xml
new file mode 100644
index 0000000..94c2f92
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_wifi_hotspot.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceScreen
+        android:icon="@drawable/ic_wifi_tethering"
+        android:key="pref_screen_wifi_hotspot"
+        android:summary="@string/wifi_hotspot_summary"
+        android:title="@string/wifi_hotspot">
+
+        <PreferenceCategory
+            android:key="pref_header_wifi_hotspot_configuration"
+            android:title="@string/wifi_hotspot_configuration">
+
+            <EditTextPreference
+                android:defaultValue=""
+                android:key="wifi_hotspot_ssid"
+                android:title="@string/wifi_ssid" />
+
+            <EditTextPreference
+                android:defaultValue=""
+                android:key="wifi_hotspot_password"
+                android:title="@string/prefs_password" />
+        </PreferenceCategory>
+
+        <PreferenceCategory
+            android:key="pref_header_wifi_hotspot_status"
+            android:title="@string/wifi_hotspot_status">
+
+            <Preference
+                android:icon="@drawable/ic_play"
+                android:key="wifi_hotspot_start"
+                android:summary="@string/wifi_hotspot_start_summary"
+                android:title="@string/start" />
+
+            <Preference
+                android:icon="@drawable/ic_stop"
+                android:key="wifi_hotspot_stop"
+                android:summary="@string/wifi_hotspot_stop_summary"
+                android:title="@string/stop" />
+
+            <Preference
+                android:icon="@drawable/ic_mtu"
+                android:key="wifi_hotspot_status"
+                android:summary=""
+                android:title="@string/status" />
+        </PreferenceCategory>
+    </PreferenceScreen>
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_withingssteelhr.xml b/app/src/main/res/xml/devicesettings_withingssteelhr.xml
new file mode 100644
index 0000000..5177131
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_withingssteelhr.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <com.mobeta.android.dslv.DragSortListPreference
+        android:icon="@drawable/ic_activity_unknown_small"
+        android:defaultValue="@array/pref_withings_steel_activity_types_default"
+        android:dialogTitle="@string/mi5_prefs_workout_activity_types"
+        android:entries="@array/pref_withings_steel_activity_types"
+        android:entryValues="@array/pref_withings_steel_activity_types_values"
+        android:key="workout_activity_types_sortable"
+        android:persistent="true"
+        android:summary="@string/mi5_prefs_workout_activity_types_summary"
+        android:title="@string/mi5_prefs_workout_activity_types" />
+    <Preference
+        android:title="@string/qhybrid_title_calibration"
+        android:icon="@drawable/ic_sensor_calibration"
+        android:summary="@string/qhybrid_summary_calibration"
+        android:key="hands_calibration">
+        <intent
+            android:targetPackage="@string/applicationId"
+            android:targetClass="nodomain.freeyourgadget.gadgetbridge.devices.withingssteelhr.WithingsCalibrationActivity" />
+    </Preference>
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_workmode.xml b/app/src/main/res/xml/devicesettings_workmode.xml
new file mode 100644
index 0000000..51edc5d
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_workmode.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <ListPreference
+        android:defaultValue="auto"
+        android:icon="@drawable/ic_switch_left"
+        android:entries="@array/workmode"
+        android:entryValues="@array/workmode_values"
+        android:key="workmode"
+        android:summary="%s"
+        android:title="@string/prefs_workmode" />
+
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_workout_activity_types.xml b/app/src/main/res/xml/devicesettings_workout_activity_types.xml
new file mode 100644
index 0000000..5213d34
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_workout_activity_types.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <com.mobeta.android.dslv.DragSortListPreference
+        android:icon="@drawable/ic_activity_unknown_small"
+        android:defaultValue="@array/pref_workout_activity_types_default"
+        android:dialogTitle="@string/mi5_prefs_workout_activity_types"
+        android:entries="@array/pref_workout_activity_types"
+        android:entryValues="@array/pref_workout_activity_types_values"
+        android:key="workout_activity_types_sortable"
+        android:persistent="true"
+        android:summary="@string/mi5_prefs_workout_activity_types_summary"
+        android:title="@string/mi5_prefs_workout_activity_types" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_workout_detection.xml b/app/src/main/res/xml/devicesettings_workout_detection.xml
new file mode 100644
index 0000000..bb5aa1a
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_workout_detection.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceScreen
+        android:icon="@drawable/ic_activity_unknown_small"
+        android:key="pref_header_workout_detection"
+        android:persistent="false"
+        android:summary="@string/pref_workout_detection_summary"
+        android:title="@string/pref_workout_detection_title">
+
+        <MultiSelectListPreference
+            android:defaultValue="@array/pref_huami2021_empty_array"
+            android:dialogTitle="@string/pref_workout_detection_categories_title"
+            android:entries="@array/pref_workout_detection_categories"
+            android:entryValues="@array/pref_workout_detection_categories_values"
+            android:icon="@drawable/ic_activity_unknown_small"
+            android:key="workout_detection_categories"
+            android:summary="@string/pref_workout_detection_categories_summary"
+            android:title="@string/pref_workout_detection_categories_title" />
+
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:icon="@drawable/ic_notifications"
+            android:key="workout_detection_alert"
+            android:layout="@layout/preference_checkbox"
+            android:summary="@string/pref_workout_detection_alert_summary"
+            android:title="@string/pref_workout_detection_alert_title" />
+
+        <ListPreference
+            android:defaultValue=""
+            android:entries="@array/workout_detection_sensitivity"
+            android:entryValues="@array/workout_detection_sensitivity_values"
+            android:key="workout_detection_sensitivity"
+            android:summary="%s"
+            android:title="@string/pref_workout_detection_sensitivity" />
+    </PreferenceScreen>
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_workout_keep_screen_on.xml b/app/src/main/res/xml/devicesettings_workout_keep_screen_on.xml
new file mode 100644
index 0000000..4d6d35f
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_workout_keep_screen_on.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <SwitchPreferenceCompat
+        android:defaultValue="false"
+        android:icon="@drawable/ic_always_on_display"
+        android:key="workout_keep_screen_on"
+        android:layout="@layout/preference_checkbox"
+        android:summary="@string/pref_workout_keep_screen_on_summary"
+        android:title="@string/pref_workout_keep_screen_on_title" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_workout_send_gps_to_band.xml b/app/src/main/res/xml/devicesettings_workout_send_gps_to_band.xml
new file mode 100644
index 0000000..2de99fb
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_workout_send_gps_to_band.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <SwitchPreferenceCompat
+        android:defaultValue="false"
+        android:icon="@drawable/ic_gps_location"
+        android:key="workout_send_gps_to_band"
+        android:layout="@layout/preference_checkbox"
+        android:summary="@string/pref_workout_send_gps_summary"
+        android:title="@string/pref_workout_send_gps_title" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_workout_start_on_phone.xml b/app/src/main/res/xml/devicesettings_workout_start_on_phone.xml
new file mode 100644
index 0000000..3512358
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_workout_start_on_phone.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <SwitchPreferenceCompat
+        android:defaultValue="false"
+        android:icon="@drawable/ic_activity_unknown_small"
+        android:key="workout_start_on_phone"
+        android:layout="@layout/preference_checkbox"
+        android:summary="@string/pref_workout_start_on_phone_summary"
+        android:title="@string/pref_workout_start_on_phone_title" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_world_clocks.xml b/app/src/main/res/xml/devicesettings_world_clocks.xml
new file mode 100644
index 0000000..46d2247
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_world_clocks.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <Preference
+        android:icon="@drawable/ic_access_time"
+        android:key="pref_world_clocks"
+        android:summary="@string/pref_world_clocks_summary"
+        android:title="@string/pref_world_clocks_title" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_xiaomi_displayitems.xml b/app/src/main/res/xml/devicesettings_xiaomi_displayitems.xml
new file mode 100644
index 0000000..1ca0247
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_xiaomi_displayitems.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <com.mobeta.android.dslv.DragSortListPreference
+        android:defaultValue="@array/pref_huami2021_empty_array"
+        android:dialogTitle="@string/mi2_prefs_display_items"
+        android:entries="@array/pref_huami2021_empty_array"
+        android:entryValues="@array/pref_huami2021_empty_array"
+        android:icon="@drawable/ic_widgets"
+        android:key="display_items_sortable"
+        android:persistent="true"
+        android:summary="@string/mi2_prefs_display_items_summary"
+        android:title="@string/mi2_prefs_display_items" />
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_zetime_activity_info.xml b/app/src/main/res/xml/devicesettings_zetime_activity_info.xml
new file mode 100644
index 0000000..e7420a1
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_zetime_activity_info.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+
+    <SwitchPreferenceCompat
+        android:defaultValue="false"
+        android:key="zetime_activity_tracking"
+        android:layout="@layout/preference_checkbox"
+        android:summary="@string/zetime_activity_tracking_summary"
+        android:title="@string/zetime_activity_tracking"
+        app:iconSpaceReserved="false" />
+
+    <ListPreference
+        android:defaultValue="0"
+        android:entries="@array/zetime_calories_type"
+        android:entryValues="@array/zetime_calories_type_values"
+        android:key="zetime_calories_type"
+        android:title="@string/zetime_calories_type"
+        app:iconSpaceReserved="false"
+        app:useSimpleSummaryProvider="true" />
+
+</PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_zetime_date_format.xml b/app/src/main/res/xml/devicesettings_zetime_date_format.xml
new file mode 100644
index 0000000..473f957
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_zetime_date_format.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+
+    <ListPreference
+        android:defaultValue="2"
+        android:entries="@array/zetime_date_format"
+        android:entryValues="@array/zetime_date_format_values"
+        android:icon="@drawable/ic_access_time"
+        android:key="zetime_date_format"
+        android:title="@string/zetime_date_format"
+        app:iconSpaceReserved="false"
+        app:useSimpleSummaryProvider="true" />
+
+</PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_zetime_display.xml b/app/src/main/res/xml/devicesettings_zetime_display.xml
new file mode 100644
index 0000000..fb96394
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_zetime_display.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+
+    <EditTextPreference
+        android:defaultValue="30"
+        android:inputType="number"
+        android:key="zetime_screentime"
+        android:maxLength="5"
+        android:title="@string/zetime_title_screentime"
+        app:iconSpaceReserved="false"
+        app:useSimpleSummaryProvider="true" />
+
+    <SwitchPreferenceCompat
+        android:defaultValue="false"
+        android:key="zetime_handmove_display"
+        android:layout="@layout/preference_checkbox"
+        android:summary="@string/zetime_handmove_display_summary"
+        android:title="@string/zetime_handmove_display"
+        app:iconSpaceReserved="false" />
+
+    <ListPreference
+        android:defaultValue="0"
+        android:entries="@array/zetime_analog_mode"
+        android:entryValues="@array/zetime_analog_mode_values"
+        android:key="zetime_analog_mode"
+        android:title="@string/zetime_analog_mode"
+        app:iconSpaceReserved="false"
+        app:useSimpleSummaryProvider="true" />
+
+</PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_zetime_health.xml b/app/src/main/res/xml/devicesettings_zetime_health.xml
new file mode 100644
index 0000000..ab304a3
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_zetime_health.xml
@@ -0,0 +1,139 @@
+<?xml version="1.0" encoding="utf-8"?>
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+
+    <ListPreference
+        android:defaultValue="0"
+        android:entries="@array/prefs_zetime_heartrate_measurement_interval"
+        android:entryValues="@array/prefs_zetime_heartrate_measurement_interval_values"
+        android:icon="@drawable/ic_heart"
+        android:key="heartrate_measurement_interval"
+        android:title="@string/prefs_title_heartrate_measurement_interval"
+        app:iconSpaceReserved="false"
+        app:useSimpleSummaryProvider="true" />
+
+    <PreferenceScreen
+        android:icon="@drawable/ic_heartrate"
+        android:key="zetime_heartrate_alarm_key"
+        android:persistent="false"
+        android:summary="@string/zetime_title_heart_rate_alarm_summary"
+        android:title="@string/zetime_title_heart_rate_alarm"
+        app:iconSpaceReserved="false">
+
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:key="zetime_heartrate_alarm_enable"
+            android:layout="@layout/preference_checkbox"
+            android:summary="@string/zetime_title_heart_rate_alarm_summary"
+            android:title="@string/zetime_title_heart_rate_alarm"
+            app:iconSpaceReserved="false" />
+
+        <EditTextPreference
+            android:defaultValue="180"
+            android:dependency="zetime_heartrate_alarm_enable"
+            android:inputType="number"
+            android:key="alarm_max_heart_rate"
+            android:maxLength="3"
+            android:title="@string/activity_prefs_alarm_max_heart_rate"
+            app:iconSpaceReserved="false"
+            app:useSimpleSummaryProvider="true" />
+
+        <EditTextPreference
+            android:defaultValue="60"
+            android:dependency="zetime_heartrate_alarm_enable"
+            android:inputType="number"
+            android:key="alarm_min_heart_rate"
+            android:maxLength="3"
+            android:title="@string/activity_prefs_alarm_min_heart_rate"
+            app:iconSpaceReserved="false"
+            app:useSimpleSummaryProvider="true" />
+    </PreferenceScreen>
+
+    <PreferenceScreen
+        android:icon="@drawable/ic_chair"
+        android:key="inactivity_warnings_key"
+        android:summary="@string/mi2_prefs_inactivity_warnings_summary"
+        android:title="@string/mi2_prefs_inactivity_warnings"
+        app:iconSpaceReserved="false">
+
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:key="inactivity_warnings_enable"
+            android:layout="@layout/preference_checkbox"
+            android:summary="@string/mi2_prefs_inactivity_warnings_summary"
+            android:title="@string/mi2_prefs_inactivity_warnings"
+            app:iconSpaceReserved="false" />
+
+        <EditTextPreference
+            android:defaultValue="60"
+            android:dependency="inactivity_warnings_enable"
+            android:inputType="numberSigned"
+            android:key="inactivity_warnings_threshold"
+            android:maxLength="3"
+            android:title="@string/mi2_prefs_inactivity_warnings_threshold"
+            app:iconSpaceReserved="false" />
+
+        <nodomain.freeyourgadget.gadgetbridge.util.XTimePreference
+            android:defaultValue="06:00"
+            android:dependency="inactivity_warnings_enable"
+            android:key="inactivity_warnings_start"
+            android:title="@string/mi2_prefs_do_not_disturb_start"
+            app:iconSpaceReserved="false" />
+
+        <nodomain.freeyourgadget.gadgetbridge.util.XTimePreference
+            android:defaultValue="22:00"
+            android:dependency="inactivity_warnings_enable"
+            android:key="inactivity_warnings_end"
+            android:title="@string/mi2_prefs_do_not_disturb_end"
+            app:iconSpaceReserved="false" />
+
+        <PreferenceCategory
+            android:dependency="inactivity_warnings_enable"
+            android:title="@string/zetime_prefs_inactivity_repetitions"
+            app:iconSpaceReserved="false">
+
+            <SwitchPreferenceCompat
+                android:defaultValue="false"
+                android:key="inactivity_warnings_mo"
+                android:layout="@layout/preference_checkbox"
+                android:title="@string/zetime_prefs_inactivity_mo"
+                app:iconSpaceReserved="false" />
+            <SwitchPreferenceCompat
+                android:defaultValue="false"
+                android:key="inactivity_warnings_tu"
+                android:layout="@layout/preference_checkbox"
+                android:title="@string/zetime_prefs_inactivity_tu"
+                app:iconSpaceReserved="false" />
+            <SwitchPreferenceCompat
+                android:defaultValue="false"
+                android:key="inactivity_warnings_we"
+                android:layout="@layout/preference_checkbox"
+                android:title="@string/zetime_prefs_inactivity_we"
+                app:iconSpaceReserved="false" />
+            <SwitchPreferenceCompat
+                android:defaultValue="false"
+                android:key="inactivity_warnings_th"
+                android:layout="@layout/preference_checkbox"
+                android:title="@string/zetime_prefs_inactivity_th"
+                app:iconSpaceReserved="false" />
+            <SwitchPreferenceCompat
+                android:defaultValue="false"
+                android:key="inactivity_warnings_fr"
+                android:layout="@layout/preference_checkbox"
+                android:title="@string/zetime_prefs_inactivity_fr"
+                app:iconSpaceReserved="false" />
+            <SwitchPreferenceCompat
+                android:defaultValue="false"
+                android:key="inactivity_warnings_sa"
+                android:layout="@layout/preference_checkbox"
+                android:title="@string/zetime_prefs_inactivity_sa"
+                app:iconSpaceReserved="false" />
+            <SwitchPreferenceCompat
+                android:defaultValue="false"
+                android:key="inactivity_warnings_su"
+                android:layout="@layout/preference_checkbox"
+                android:title="@string/zetime_prefs_inactivity_su"
+                app:iconSpaceReserved="false" />
+        </PreferenceCategory>
+    </PreferenceScreen>
+</PreferenceScreen>
diff --git a/app/src/main/res/xml/devicesettings_zetime_vibrationpatterns.xml b/app/src/main/res/xml/devicesettings_zetime_vibrationpatterns.xml
new file mode 100644
index 0000000..fd4ddef
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_zetime_vibrationpatterns.xml
@@ -0,0 +1,112 @@
+<?xml version="1.0" encoding="utf-8"?>
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+
+    <PreferenceScreen
+        android:icon="@drawable/ic_vibration"
+        android:key="vibration_patterns_key"
+        android:persistent="false"
+        android:summary="@string/pref_screen_vibration_patterns_summary"
+        android:title="@string/pref_screen_vibration_patterns_title">
+
+        <ListPreference
+            android:defaultValue="2"
+            android:entries="@array/zetime_signaling_types"
+            android:entryValues="@array/zetime_signaling_types_values"
+            android:icon="@drawable/ic_message_outline"
+            android:key="zetime_vibration_profile_sms"
+            android:title="@string/pref_screen_notification_profile_sms"
+            app:iconSpaceReserved="false"
+            app:useSimpleSummaryProvider="true" />
+
+        <ListPreference
+            android:defaultValue="13"
+            android:entries="@array/zetime_signaling_types"
+            android:entryValues="@array/zetime_signaling_types_values"
+            android:icon="@drawable/ic_phone"
+            android:key="zetime_vibration_profile_incoming_call"
+            android:title="@string/pref_screen_notification_profile_incoming_call"
+            app:iconSpaceReserved="false"
+            app:useSimpleSummaryProvider="true" />
+
+        <ListPreference
+            android:defaultValue="12"
+            android:entries="@array/zetime_signaling_types"
+            android:entryValues="@array/zetime_signaling_types_values"
+            android:icon="@drawable/ic_phone_missed_outline"
+            android:key="zetime_vibration_profile_missed_call"
+            android:title="@string/pref_screen_notification_profile_missed_call"
+            app:iconSpaceReserved="false"
+            app:useSimpleSummaryProvider="true" />
+
+        <ListPreference
+            android:defaultValue="12"
+            android:entries="@array/zetime_signaling_types"
+            android:entryValues="@array/zetime_signaling_types_values"
+            android:icon="@drawable/ic_email"
+            android:key="zetime_vibration_profile_generic_email"
+            android:title="@string/pref_screen_notification_profile_email"
+            app:iconSpaceReserved="false"
+            app:useSimpleSummaryProvider="true" />
+
+        <ListPreference
+            android:icon="@drawable/ic_person"
+            android:defaultValue="12"
+            android:entries="@array/zetime_signaling_types"
+            android:entryValues="@array/zetime_signaling_types_values"
+            android:key="zetime_vibration_profile_generic_social"
+            android:title="@string/pref_screen_notification_profile_generic_social"
+            app:iconSpaceReserved="false"
+            app:useSimpleSummaryProvider="true" />
+
+        <ListPreference
+            android:icon="@drawable/ic_access_alarms"
+            android:defaultValue="11"
+            android:entries="@array/zetime_signaling_types"
+            android:entryValues="@array/zetime_signaling_types_values"
+            android:key="zetime_alarm_signaling"
+            android:title="@string/pref_screen_notification_profile_alarm_clock"
+            app:iconSpaceReserved="false"
+            app:useSimpleSummaryProvider="true" />
+
+        <ListPreference
+            android:icon="@drawable/ic_calendar_to"
+            android:defaultValue="12"
+            android:entries="@array/zetime_signaling_types"
+            android:entryValues="@array/zetime_signaling_types_values"
+            android:key="zetime_vibration_profile_calendar"
+            android:title="@string/pref_screen_notification_profile_calendar"
+            app:iconSpaceReserved="false"
+            app:useSimpleSummaryProvider="true" />
+
+        <ListPreference
+            android:icon="@drawable/ic_chair"
+            android:defaultValue="12"
+            android:entries="@array/zetime_signaling_types"
+            android:entryValues="@array/zetime_signaling_types_values"
+            android:key="zetime_vibration_profile_inactivity"
+            android:title="@string/pref_screen_notification_profile_inactivity"
+            app:iconSpaceReserved="false"
+            app:useSimpleSummaryProvider="true" />
+
+        <ListPreference
+            android:icon="@drawable/ic_battery"
+            android:defaultValue="4"
+            android:entries="@array/zetime_signaling_types"
+            android:entryValues="@array/zetime_signaling_types_values"
+            android:key="zetime_vibration_profile_lowpower"
+            android:title="@string/pref_screen_notification_profile_low_power"
+            app:iconSpaceReserved="false"
+            app:useSimpleSummaryProvider="true" />
+
+        <ListPreference
+            android:icon="@drawable/ic_signal_cellular_connected_no_internet_4_bar"
+            android:defaultValue="13"
+            android:entries="@array/zetime_signaling_types"
+            android:entryValues="@array/zetime_signaling_types_values"
+            android:key="zetime_vibration_profile_antiloss"
+            android:title="@string/pref_screen_notification_profile_anti_loss"
+            app:iconSpaceReserved="false"
+            app:useSimpleSummaryProvider="true" />
+    </PreferenceScreen>
+</PreferenceScreen>
diff --git a/app/src/main/res/xml/discovery_pairing_preferences.xml b/app/src/main/res/xml/discovery_pairing_preferences.xml
new file mode 100644
index 0000000..b58fdbe
--- /dev/null
+++ b/app/src/main/res/xml/discovery_pairing_preferences.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+
+    <SwitchPreferenceCompat
+        android:defaultValue="false"
+        android:key="discover_unsupported_devices"
+        android:layout="@layout/preference_checkbox"
+        android:summary="@string/discover_unsupported_devices_description"
+        android:title="@string/discover_unsupported_devices"
+        app:iconSpaceReserved="false" />
+    <SwitchPreferenceCompat
+        android:defaultValue="false"
+        android:key="prefs_general_key_auto_reconnect_scan"
+        android:title="@string/auto_reconnect_ble_scan_title"
+        android:summary="@string/auto_reconnect_ble_scan_summary"
+        android:layout="@layout/preference_checkbox"
+        app:iconSpaceReserved="false" />
+    <PreferenceCategory
+        android:title="@string/pref_title_pebble_settings"
+        app:iconSpaceReserved="false">
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:key="pebble_force_le"
+            android:layout="@layout/preference_checkbox"
+            android:summary="@string/pref_summary_pebble_forcele"
+            android:title="@string/pref_title_pebble_forcele"
+            app:iconSpaceReserved="false" />
+    </PreferenceCategory>
+</PreferenceScreen>
diff --git a/app/src/main/res/xml/fossil_hr_watchface_settings.xml b/app/src/main/res/xml/fossil_hr_watchface_settings.xml
new file mode 100644
index 0000000..3a365b5
--- /dev/null
+++ b/app/src/main/res/xml/fossil_hr_watchface_settings.xml
@@ -0,0 +1,114 @@
+<?xml version="1.0" encoding="utf-8"?>
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+
+    <PreferenceCategory
+        android:title="@string/watchface_setting_title_display_refresh_timeout"
+        app:iconSpaceReserved="false">
+
+        <EditTextPreference
+            android:key="pref_hybridhr_watchface_refresh_full"
+            android:persistent="false"
+            android:title="@string/watchface_setting_display_refresh_full"
+            app:iconSpaceReserved="false" />
+        <EditTextPreference
+            android:key="pref_hybridhr_watchface_refresh_partial"
+            android:persistent="false"
+            android:title="@string/watchface_setting_display_refresh_partial"
+            app:iconSpaceReserved="false" />
+
+    </PreferenceCategory>
+
+    <PreferenceCategory
+        android:title="@string/watchface_setting_title_wrist_flick"
+        app:iconSpaceReserved="false">
+
+        <Preference
+            android:enabled="false"
+            android:selectable="false"
+            android:singleLineTitle="false"
+            android:title="@string/watchface_setting_desc_wrist_flick"
+            app:iconSpaceReserved="false" />
+        <SwitchPreferenceCompat
+            android:key="pref_hybridhr_watchface_wrist_flick_relative"
+            android:layout="@layout/preference_checkbox"
+            android:persistent="false"
+            android:title="@string/watchface_setting_wrist_flick_move_relative"
+            app:iconSpaceReserved="false" />
+        <EditTextPreference
+            android:key="pref_hybridhr_watchface_wrist_flick_hour_hand"
+            android:persistent="false"
+            android:title="@string/watchface_setting_wrist_flick_hour"
+            app:iconSpaceReserved="false" />
+        <EditTextPreference
+            android:key="pref_hybridhr_watchface_wrist_flick_minute_hand"
+            android:persistent="false"
+            android:title="@string/watchface_setting_wrist_flick_minute"
+            app:iconSpaceReserved="false" />
+        <EditTextPreference
+            android:key="pref_hybridhr_watchface_wrist_flick_duration"
+            android:persistent="false"
+            android:title="@string/watchface_setting_wrist_flick_duration"
+            app:iconSpaceReserved="false" />
+
+    </PreferenceCategory>
+
+    <PreferenceCategory
+        android:title="@string/watchface_setting_title_custom_events"
+        app:iconSpaceReserved="false">
+
+        <ListPreference
+            android:dialogTitle="@string/watchface_setting_button_toggle_widgets"
+            android:entries="@array/pref_hybridhr_watchface_events_items"
+            android:entryValues="@array/pref_hybridhr_watchface_events_values"
+            android:key="pref_hybridhr_watchface_toggle_widgets"
+            android:negativeButtonText="@string/Cancel"
+            android:persistent="false"
+            android:title="@string/watchface_setting_button_toggle_widgets"
+            app:iconSpaceReserved="false" />
+        <ListPreference
+            android:dialogTitle="@string/watchface_setting_button_toggle_backlight"
+            android:entries="@array/pref_hybridhr_watchface_events_items"
+            android:entryValues="@array/pref_hybridhr_watchface_events_values"
+            android:key="pref_hybridhr_watchface_toggle_backlight"
+            android:negativeButtonText="@string/Cancel"
+            android:persistent="false"
+            android:title="@string/watchface_setting_button_toggle_backlight"
+            app:iconSpaceReserved="false" />
+        <ListPreference
+            android:dialogTitle="@string/watchface_setting_button_move_hands"
+            android:entries="@array/pref_hybridhr_watchface_events_items"
+            android:entryValues="@array/pref_hybridhr_watchface_events_values"
+            android:key="pref_hybridhr_watchface_move_hands"
+            android:negativeButtonText="@string/Cancel"
+            android:persistent="false"
+            android:title="@string/watchface_setting_button_move_hands"
+            app:iconSpaceReserved="false" />
+
+    </PreferenceCategory>
+
+    <PreferenceCategory
+        android:title="@string/watchface_setting_title_power_saving"
+        app:iconSpaceReserved="false">
+
+        <SwitchPreferenceCompat
+            android:key="pref_hybridhr_watchface_power_saving_display"
+            android:layout="@layout/preference_checkbox"
+            android:persistent="false"
+            android:title="@string/watchface_setting_power_saving_display"
+            app:iconSpaceReserved="false" />
+        <SwitchPreferenceCompat
+            android:key="pref_hybridhr_watchface_power_saving_hands"
+            android:layout="@layout/preference_checkbox"
+            android:persistent="false"
+            android:title="@string/watchface_setting_power_saving_hands"
+            app:iconSpaceReserved="false" />
+        <SwitchPreferenceCompat
+            android:key="pref_hybridhr_watchface_light_up_on_notification"
+            android:layout="@layout/preference_checkbox"
+            android:persistent="false"
+            android:title="@string/watchface_setting_light_up_on_notification"
+            app:iconSpaceReserved="false" />
+
+    </PreferenceCategory>
+</PreferenceScreen>
\ No newline at end of file
diff --git a/app/src/main/res/xml/fossil_hr_widget_settings.xml b/app/src/main/res/xml/fossil_hr_widget_settings.xml
new file mode 100644
index 0000000..ec50623
--- /dev/null
+++ b/app/src/main/res/xml/fossil_hr_widget_settings.xml
@@ -0,0 +1,109 @@
+<?xml version="1.0" encoding="utf-8"?>
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+
+    <PreferenceCategory
+        android:key="widget_pref_category_generic"
+        android:title="@string/watchface_dialog_widget_cat_generic"
+        app:iconSpaceReserved="false">
+        <ListPreference
+            android:dialogTitle="@string/watchface_dialog_widget_type"
+            android:key="pref_hybridhr_widget_type"
+            android:negativeButtonText="@string/Cancel"
+            android:persistent="false"
+            android:title="@string/watchface_dialog_widget_type"
+            app:iconSpaceReserved="false" />
+        <ListPreference
+            android:dialogTitle="@string/watchface_dialog_widget_color"
+            android:key="pref_hybridhr_widget_color"
+            android:negativeButtonText="@string/Cancel"
+            android:persistent="false"
+            android:title="@string/watchface_dialog_widget_color"
+            app:iconSpaceReserved="false" />
+        <ListPreference
+            android:dialogTitle="@string/watchface_dialog_widget_background"
+            android:entries="@array/pref_hybridhr_widgetbackgrounds_items"
+            android:entryValues="@array/pref_hybridhr_widgetbackgrounds_values"
+            android:key="pref_hybridhr_widget_background"
+            android:negativeButtonText="@string/Cancel"
+            android:persistent="false"
+            android:title="@string/watchface_dialog_widget_background"
+            app:iconSpaceReserved="false" />
+    </PreferenceCategory>
+
+    <PreferenceCategory
+        android:key="widget_pref_category_position"
+        android:title="@string/watchface_dialog_widget_cat_position"
+        app:iconSpaceReserved="false">
+        <EditTextPreference
+            android:inputType="numberDecimal"
+            android:key="pref_hybridhr_widget_pos_x"
+            android:persistent="false"
+            android:title="@string/watchface_dialog_widget_x_coordinate"
+            app:iconSpaceReserved="false" />
+        <EditTextPreference
+            android:inputType="numberDecimal"
+            android:key="pref_hybridhr_widget_pos_y"
+            android:persistent="false"
+            android:title="@string/watchface_dialog_widget_y_coordinate"
+            app:iconSpaceReserved="false" />
+        <ListPreference
+            android:dialogTitle="@string/watchface_dialog_widget_presets"
+            android:key="pref_hybridhr_widget_pos_preset"
+            android:negativeButtonText="@string/Cancel"
+            android:persistent="false"
+            android:title="@string/watchface_dialog_widget_presets"
+            app:iconSpaceReserved="false"
+            app:useSimpleSummaryProvider="true" />
+    </PreferenceCategory>
+
+    <PreferenceCategory
+        android:key="widget_pref_category_2nd_tz_widget"
+        android:title="@string/watchface_dialog_widget_cat_2nd_tz_widget"
+        app:iconSpaceReserved="false">
+        <ListPreference
+            android:dialogTitle="@string/watchface_dialog_widget_timezone"
+            android:key="pref_hybridhr_widget_timezone"
+            android:negativeButtonText="@string/Cancel"
+            android:persistent="false"
+            android:title="@string/watchface_dialog_widget_timezone"
+            app:iconSpaceReserved="false" />
+        <EditTextPreference
+            android:inputType="numberDecimal"
+            android:key="pref_hybridhr_widget_timezone_timeout"
+            android:persistent="false"
+            android:title="@string/watchface_dialog_widget_timezone_duration"
+            app:iconSpaceReserved="false" />
+    </PreferenceCategory>
+
+    <PreferenceCategory
+        android:key="widget_pref_category_custom_widget"
+        android:title="@string/watchface_dialog_widget_cat_custom_widget"
+        app:iconSpaceReserved="false">
+        <EditTextPreference
+            android:inputType="numberDecimal"
+            android:key="pref_hybridhr_widget_width"
+            android:persistent="false"
+            android:title="@string/watchface_dialog_widget_width"
+            app:iconSpaceReserved="false" />
+        <EditTextPreference
+            android:inputType="numberDecimal"
+            android:key="pref_hybridhr_widget_custom_timeout"
+            android:persistent="false"
+            android:title="@string/watchface_dialog_widget_update_timeout"
+            app:iconSpaceReserved="false" />
+        <SwitchPreferenceCompat
+            android:key="pref_hybridhr_widget_custom_hide_text"
+            android:layout="@layout/preference_checkbox"
+            android:persistent="false"
+            android:title="@string/watchface_dialog_widget_timeout_hide_text"
+            app:iconSpaceReserved="false" />
+        <SwitchPreferenceCompat
+            android:key="pref_hybridhr_widget_custom_show_circle"
+            android:layout="@layout/preference_checkbox"
+            android:persistent="false"
+            android:title="@string/watchface_dialog_widget_timeout_show_circle"
+            app:iconSpaceReserved="false" />
+    </PreferenceCategory>
+
+</PreferenceScreen>
\ No newline at end of file
diff --git a/app/src/main/res/xml/garmin_realtime_settings.xml b/app/src/main/res/xml/garmin_realtime_settings.xml
new file mode 100644
index 0000000..f94b2ef
--- /dev/null
+++ b/app/src/main/res/xml/garmin_realtime_settings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+    android:key="garmin_realtime_settings">
+
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/loyalty_cards.xml b/app/src/main/res/xml/loyalty_cards.xml
new file mode 100644
index 0000000..715553b
--- /dev/null
+++ b/app/src/main/res/xml/loyalty_cards.xml
@@ -0,0 +1,89 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceCategory
+        android:key="pref_key_header_loyalty_cards_catima"
+        android:title="@string/loyalty_cards_catima">
+
+        <ListPreference
+            android:defaultValue="me.hackerchick.catima"
+            android:entries="@array/pref_huami2021_empty_array"
+            android:entryValues="@array/pref_huami2021_empty_array"
+            android:key="loyalty_cards_catima_package"
+            android:summary="%s"
+            android:title="@string/loyalty_cards_catima_package" />
+
+        <Preference
+            android:icon="@drawable/ic_loyalty"
+            android:key="loyalty_cards_open_catima"
+            android:title="@string/loyalty_cards_open_catima" />
+
+        <Preference
+            android:icon="@drawable/ic_warning_gray"
+            android:key="loyalty_cards_catima_not_installed"
+            android:summary="@string/loyalty_cards_catima_not_installed" />
+
+        <Preference
+            android:icon="@drawable/ic_warning_gray"
+            android:key="loyalty_cards_catima_not_compatible"
+            android:summary="@string/loyalty_cards_catima_not_compatible" />
+
+        <Preference
+            android:icon="@drawable/ic_loyalty"
+            android:key="loyalty_cards_install_catima"
+            android:title="@string/loyalty_cards_install" />
+
+        <Preference
+            android:icon="@drawable/ic_warning_gray"
+            android:key="loyalty_cards_catima_permissions"
+            android:summary="@string/loyalty_cards_catima_permissions_summary"
+            android:title="@string/loyalty_cards_catima_permissions_title" />
+    </PreferenceCategory>
+
+    <PreferenceCategory
+        android:key="pref_key_header_loyalty_cards_sync"
+        android:title="@string/loyalty_cards_sync">
+
+        <Preference
+            android:icon="@drawable/ic_refresh"
+            android:key="loyalty_cards_sync"
+            android:summary="@string/loyalty_cards_sync_summary"
+            android:title="@string/loyalty_cards_sync_title" />
+    </PreferenceCategory>
+
+    <PreferenceCategory
+        android:key="pref_key_header_loyalty_cards_sync_options"
+        android:title="@string/loyalty_cards_sync_options">
+
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:icon="@drawable/ic_list_numbered"
+            android:key="loyalty_cards_sync_groups_only"
+            android:layout="@layout/preference_checkbox"
+            android:title="@string/loyalty_cards_sync_groups_only" />
+
+        <MultiSelectListPreference
+            android:defaultValue="@array/pref_huami2021_empty_array"
+            android:dependency="loyalty_cards_sync_groups_only"
+            android:dialogTitle="@string/loyalty_cards_sync_groups"
+            android:entries="@array/pref_huami2021_empty_array"
+            android:entryValues="@array/pref_huami2021_empty_array"
+            android:icon="@drawable/ic_checklist"
+            android:key="loyalty_cards_sync_groups"
+            android:summary=""
+            android:title="@string/loyalty_cards_sync_groups" />
+
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:icon="@drawable/ic_archive"
+            android:key="loyalty_cards_sync_archived"
+            android:layout="@layout/preference_checkbox"
+            android:title="@string/loyalty_cards_sync_archived" />
+
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:icon="@drawable/ic_star_gray"
+            android:key="loyalty_cards_sync_starred"
+            android:layout="@layout/preference_checkbox"
+            android:title="@string/loyalty_cards_sync_starred" />
+    </PreferenceCategory>
+</androidx.preference.PreferenceScreen>
diff --git a/app/src/main/res/xml/network_security_config.xml b/app/src/main/res/xml/network_security_config.xml
new file mode 100644
index 0000000..2439f15
--- /dev/null
+++ b/app/src/main/res/xml/network_security_config.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<network-security-config>
+    <base-config cleartextTrafficPermitted="true" />
+</network-security-config>
diff --git a/app/src/main/res/xml/notifications_preferences.xml b/app/src/main/res/xml/notifications_preferences.xml
new file mode 100644
index 0000000..2c288c2
--- /dev/null
+++ b/app/src/main/res/xml/notifications_preferences.xml
@@ -0,0 +1,213 @@
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+    <PreferenceCategory xmlns:app="http://schemas.android.com/apk/res-auto"
+        android:key="pref_key_notifications"
+        android:title="@string/pref_header_notifications"
+        app:iconSpaceReserved="false">
+
+        <Preference
+            android:key="notifications_settings"
+            android:title="@string/pref_title_notifications_generic_settings"
+            app:iconSpaceReserved="false" />
+
+        <Preference
+            android:key="notifications_generic"
+            android:title="@string/pref_title_notifications_generic"
+            app:iconSpaceReserved="false" />
+
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:key="notifications_generic_whenscreenon"
+            android:layout="@layout/preference_checkbox"
+            android:title="@string/pref_title_whenscreenon"
+            app:iconSpaceReserved="false" />
+
+        <SwitchPreferenceCompat
+            android:defaultValue="true"
+            android:key="notifications_ignore_low_priority"
+            android:layout="@layout/preference_checkbox"
+            android:summary="@string/pref_summary_notifications_ignore_low_priority"
+            android:title="@string/pref_title_notifications_ignore_low_priority"
+            app:iconSpaceReserved="false" />
+
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:key="notifications_ignore_work_profile"
+            android:layout="@layout/preference_checkbox"
+            android:summary="@string/pref_summary_notifications_ignore_work_profile"
+            android:title="@string/pref_title_notifications_ignore_work_profile"
+            app:iconSpaceReserved="false" />
+
+        <SwitchPreferenceCompat
+            android:defaultValue="true"
+            android:key="notification_prefer_long_text"
+            android:layout="@layout/preference_checkbox"
+            android:summary="@string/pref_summary_notification_prefer_long_text"
+            android:title="@string/pref_title_notification_prefer_long_text"
+            app:iconSpaceReserved="false" />
+
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:key="notification_cache_while_disconnected"
+            android:layout="@layout/preference_checkbox"
+            android:summary="@string/pref_summary_notification_cache_while_disconnected"
+            android:title="@string/pref_title_notification_cache_while_disconnected"
+            app:iconSpaceReserved="false" />
+
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:key="minimize_priority"
+            android:layout="@layout/preference_checkbox"
+            android:summaryOff="@string/pref_summary_minimize_priority_off"
+            android:summaryOn="@string/pref_summary_minimize_priority_on"
+            android:title="@string/pref_title_minimize_priority"
+            app:iconSpaceReserved="false" />
+
+        <Preference
+            android:id="@+id/preference"
+            android:key="ping_tone"
+            android:title="@string/pref_title_ping_tone"
+            app:iconSpaceReserved="false"
+            app:summary="%s" />
+
+        <ListPreference
+            android:defaultValue="always"
+            android:entries="@array/notification_mode_toggle"
+            android:entryValues="@array/notification_mode_values_toggle"
+            android:key="notification_mode_calls"
+            android:summary="%s"
+            android:title="@string/pref_title_notifications_call"
+            app:iconSpaceReserved="false" />
+
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:key="notification_support_voip_calls"
+            android:layout="@layout/preference_checkbox"
+            android:title="@string/pref_title_support_voip_calls"
+            app:iconSpaceReserved="false" />
+
+        <EditTextPreference
+            android:defaultValue="0"
+            android:digits="0123456789"
+            android:inputType="number"
+            android:key="notification_delay_calls"
+            android:maxLength="2"
+            android:summary="@string/pref_summary_notification_delay_calls"
+            android:title="@string/pref_title_notification_delay_calls"
+            app:iconSpaceReserved="false" />
+
+        <ListPreference
+            android:defaultValue="when_screen_off"
+            android:entries="@array/notification_mode"
+            android:entryValues="@array/notification_mode_values"
+            android:key="notification_mode_sms"
+            android:summary="%s"
+            android:title="@string/pref_title_notifications_sms"
+            app:iconSpaceReserved="false" />
+
+        <ListPreference
+            android:defaultValue="when_screen_off"
+            android:entries="@array/notification_mode"
+            android:entryValues="@array/notification_mode_values"
+            android:key="notification_mode_pebblemsg"
+            android:summary="@string/pref_summary_notifications_pebblemsg"
+            android:title="@string/pref_title_notifications_pebblemsg"
+            app:iconSpaceReserved="false" />
+
+        <ListPreference
+            android:defaultValue="0"
+            android:entries="@array/notifications_timeout"
+            android:entryValues="@array/notifications_timeout_values"
+            android:key="notifications_timeout"
+            android:summary="%s"
+            android:title="@string/pref_title_notifications_timeout"
+            app:iconSpaceReserved="false" />
+
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:key="notification_filter"
+            android:layout="@layout/preference_checkbox"
+            android:summary="@string/pref_summary_notification_filter"
+            android:title="@string/pref_title_notification_filter"
+            app:iconSpaceReserved="false" />
+
+    </PreferenceCategory>
+
+    <PreferenceCategory
+        android:title="@string/pref_title_notification_times_enabled"
+        app:iconSpaceReserved="false">
+
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:key="notification_times_enabled"
+            android:layout="@layout/preference_checkbox"
+            android:summary="@string/pref_summary_notification_times_enabled"
+            android:title="@string/pref_title_notification_times_enabled"
+            app:iconSpaceReserved="false" />
+
+        <nodomain.freeyourgadget.gadgetbridge.util.XTimePreference
+            android:defaultValue="08:00"
+            android:dependency="notification_times_enabled"
+            android:key="notification_times_start"
+            android:title="@string/mi2_prefs_do_not_disturb_start"
+            app:iconSpaceReserved="false"/>
+
+        <nodomain.freeyourgadget.gadgetbridge.util.XTimePreference
+            android:defaultValue="22:00"
+            android:dependency="notification_times_enabled"
+            android:key="notification_times_end"
+            android:title="@string/mi2_prefs_do_not_disturb_end"
+            app:iconSpaceReserved="false"/>
+    </PreferenceCategory>
+
+    <PreferenceCategory
+        android:title="@string/pref_header_notification_application_settings"
+        app:iconSpaceReserved="false">
+
+        <ListPreference
+            android:defaultValue="true"
+            android:entries="@array/notification_list_is_blacklist_names"
+            android:entryValues="@array/notification_list_is_blacklist_values"
+            android:key="notification_list_is_blacklist"
+            android:summary="%s"
+            android:title="@string/pref_title_notification_use_as"
+            app:iconSpaceReserved="false" />
+
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:key="notification_media_ignores_application_list"
+            android:layout="@layout/preference_checkbox"
+            android:summary="@string/pref_summary_notification_media_ignores_application_list"
+            android:title="@string/pref_title_notification_media_ignores_application_list"
+            app:iconSpaceReserved="false" />
+
+        <Preference
+            android:key="pref_key_blacklist"
+            android:title="@string/pref_applications_settings"
+            app:iconSpaceReserved="false" />
+    </PreferenceCategory>
+
+    <PreferenceCategory
+        android:title="@string/pref_header_privacy"
+        app:iconSpaceReserved="false">
+
+        <ListPreference
+            android:defaultValue="@string/p_call_privacy_mode_off"
+            android:entries="@array/pref_call_privacy_mode"
+            android:entryValues="@array/pref_call_privacy_mode_values"
+            android:key="pref_call_privacy_mode"
+            android:summary="%s"
+            android:title="@string/pref_title_call_privacy_mode"
+            app:iconSpaceReserved="false" />
+
+        <ListPreference
+            android:defaultValue="off"
+            android:entries="@array/pref_message_privacy_mode"
+            android:entryValues="@array/pref_message_privacy_mode_values"
+            android:key="pref_message_privacy_mode"
+            android:summary="%s"
+            android:title="@string/pref_title_message_privacy_mode"
+            app:iconSpaceReserved="false" />
+    </PreferenceCategory>
+
+</PreferenceScreen>
\ No newline at end of file
diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml
new file mode 100644
index 0000000..b5729f5
--- /dev/null
+++ b/app/src/main/res/xml/preferences.xml
@@ -0,0 +1,483 @@
+<?xml version="1.0" encoding="utf-8"?>
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    app:iconSpaceReserved="false">
+
+    <PreferenceScreen
+        android:icon="@drawable/ic_settings"
+        android:key="pref_screen_general"
+        android:summary="@string/pref_description_general"
+        android:title="@string/pref_header_general">
+
+        <SwitchPreferenceCompat
+            android:defaultValue="true"
+            android:key="general_autostartonboot"
+            android:layout="@layout/preference_checkbox"
+            android:title="@string/pref_title_general_autostartonboot"
+            app:iconSpaceReserved="false" />
+
+        <SwitchPreferenceCompat
+            android:defaultValue="true"
+            android:key="show_changelog"
+            android:layout="@layout/preference_checkbox"
+            android:summary="@string/pref_show_changelog_summary"
+            android:title="@string/pref_show_changelog"
+            app:iconSpaceReserved="false" />
+
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:key="general_autoconnectonbluetooth"
+            android:layout="@layout/preference_checkbox"
+            android:title="@string/pref_title_general_autoconnectonbluetooth"
+            app:iconSpaceReserved="false" />
+
+        <SwitchPreferenceCompat
+            android:defaultValue="true"
+            android:key="general_reconnectonlytoconnected"
+            android:layout="@layout/preference_checkbox"
+            android:summary="@string/pref_summary_general_reconnectonlytoconnected"
+            android:title="@string/pref_title_general_reconnectonlytoconnected"
+            app:iconSpaceReserved="false" />
+
+        <PreferenceCategory
+            android:key="language_category"
+            android:title="@string/language_and_region_prefs"
+            app:iconSpaceReserved="false">
+            <ListPreference
+                android:defaultValue="default"
+                android:entries="@array/pref_language_options"
+                android:entryValues="@array/pref_language_values"
+                android:key="language"
+                android:summary="%s"
+                android:title="@string/pref_title_language"
+                app:iconSpaceReserved="false" />
+            <ListPreference
+                android:defaultValue="metric"
+                android:entries="@array/pref_entries_unit_system"
+                android:entryValues="@array/pref_values_unit_system"
+                android:key="measurement_system"
+                android:summary="%s"
+                android:title="@string/pref_title_unit_system"
+                app:iconSpaceReserved="false" />
+            <PreferenceScreen
+                android:key="pref_screen_rtl"
+                android:title="@string/preferences_rtl_settings"
+                app:iconSpaceReserved="false">
+                <SwitchPreferenceCompat
+                    android:defaultValue="false"
+                    android:key="rtl"
+                    android:layout="@layout/preference_checkbox"
+                    android:summary="@string/pref_summary_rtl"
+                    android:title="@string/pref_title_rtl"
+                    app:iconSpaceReserved="false" />
+                <SwitchPreferenceCompat
+                    android:defaultValue="false"
+                    android:key="contextualArabic"
+                    android:layout="@layout/preference_checkbox"
+                    android:summary="@string/pref_summary_contextual_arabic"
+                    android:title="@string/pref_title_contextual_arabic"
+                    app:iconSpaceReserved="false" />
+                <EditTextPreference
+                    android:defaultValue="20"
+                    android:inputType="number"
+                    android:key="rtl_max_line_length"
+                    android:maxLength="159"
+                    android:summary="@string/pref_rtl_max_line_length_summary"
+                    android:title="@string/pref_rtl_max_line_length"
+                    app:iconSpaceReserved="false" />
+            </PreferenceScreen>
+        </PreferenceCategory>
+
+        <PreferenceCategory
+            android:key="pref_key_datetime"
+            android:title="@string/pref_header_datetime"
+            app:iconSpaceReserved="false">
+            <SwitchPreferenceCompat
+                android:defaultValue="true"
+                android:key="datetime_synconconnect"
+                android:layout="@layout/preference_checkbox"
+                android:summary="@string/pref_summary_datetime_syctimeonconnect"
+                android:title="@string/pref_title_datetime_syctimeonconnect"
+                app:iconSpaceReserved="false" />
+        </PreferenceCategory>
+
+        <PreferenceCategory
+            android:title="@string/pref_header_location"
+            app:iconSpaceReserved="false">
+            <Preference
+                android:key="location_aquire"
+                android:title="@string/pref_title_location_aquire"
+                app:iconSpaceReserved="false" />
+            <EditTextPreference
+                android:defaultValue="0"
+                android:inputType="numberDecimal|numberSigned"
+                android:key="location_latitude"
+                android:maxLength="7"
+                android:title="@string/pref_title_location_latitude"
+                app:iconSpaceReserved="false"
+                app:useSimpleSummaryProvider="true" />
+            <EditTextPreference
+                android:defaultValue="0"
+                android:inputType="numberDecimal|numberSigned"
+                android:key="location_longitude"
+                android:maxLength="7"
+                android:title="@string/pref_title_location_longitude"
+                app:iconSpaceReserved="false"
+                app:useSimpleSummaryProvider="true" />
+            <SwitchPreferenceCompat
+                android:defaultValue="true"
+                android:dependency="location_aquire"
+                android:key="use_updated_location_if_available"
+                android:layout="@layout/preference_checkbox"
+                android:summary="@string/pref_summary_location_keep_uptodate"
+                android:title="@string/pref_title_location_keep_uptodate"
+                app:iconSpaceReserved="false" />
+        </PreferenceCategory>
+    </PreferenceScreen>
+
+    <Preference
+        android:icon="@drawable/ic_person"
+        android:key="pref_category_activity_personal"
+        android:summary="@string/pref_description_about_you"
+        android:title="@string/activity_prefs_about_you"
+        app:iconSpaceReserved="false" />
+
+    <Preference
+        android:icon="@drawable/ic_notifications"
+        android:key="pref_category_notifications"
+        android:summary="@string/pref_description_notifications"
+        android:title="@string/pref_header_notifications"
+        app:iconSpaceReserved="false" />
+
+    <PreferenceScreen
+        android:icon="@drawable/ic_paint"
+        android:key="pref_screen_user_interface"
+        android:summary="@string/pref_description_user_interface"
+        android:title="@string/pref_title_user_interface">
+
+        <PreferenceCategory
+            android:key="pref_theme_dynamic_colors_explanation"
+            android:title="@string/pref_theme_dynamic_colors_explanation"
+            app:iconSpaceReserved="false"
+            app:singleLineTitle="false" />
+
+        <ListPreference
+            android:defaultValue="@string/pref_theme_value_light"
+            android:entries="@array/pref_theme_options"
+            android:entryValues="@array/pref_theme_values"
+            android:key="pref_key_theme"
+            android:summary="%s"
+            android:title="@string/pref_title_theme"
+            app:iconSpaceReserved="false" />
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:key="pref_key_theme_amoled_black"
+            android:layout="@layout/preference_checkbox"
+            android:title="@string/pref_theme_black_background"
+            app:iconSpaceReserved="false" />
+
+        <PreferenceCategory
+            android:key="pref_screen_theme"
+            android:title="@string/pref_header_main_screen"
+            app:iconSpaceReserved="false">
+
+            <SwitchPreferenceCompat
+                android:defaultValue="true"
+                android:key="display_add_device_fab"
+                android:layout="@layout/preference_checkbox"
+                android:summaryOff="@string/pref_display_add_device_fab_off"
+                android:summaryOn="@string/pref_display_add_device_fab_on"
+                android:title="@string/pref_display_add_device_fab"
+                app:iconSpaceReserved="false" />
+
+            <SwitchPreferenceCompat
+                android:defaultValue="true"
+                android:key="display_bottom_navigation_bar"
+                android:layout="@layout/preference_checkbox"
+                android:summaryOff="@string/pref_summary_bottom_navigation_bar_off"
+                android:summaryOn="@string/pref_summary_bottom_navigation_bar_on"
+                android:title="@string/pref_title_bottom_navigation_bar"
+                app:iconSpaceReserved="false" />
+        </PreferenceCategory>
+    </PreferenceScreen>
+
+    <Preference
+        android:icon="@drawable/ic_dashboard"
+        android:key="pref_category_dashboard"
+        android:summary="@string/pref_description_dashboard"
+        android:title="@string/bottom_nav_dashboard"
+        app:iconSpaceReserved="false" />
+
+    <Preference
+        android:icon="@drawable/ic_show_chart"
+        android:key="pref_charts"
+        android:title="@string/activity_prefs_charts"
+        app:iconSpaceReserved="false" />
+
+    <PreferenceScreen
+        android:icon="@drawable/ic_auto_awesome"
+        android:key="pref_screen_automations"
+        android:summary="@string/pref_description_automations"
+        android:title="@string/pref_header_automations">
+
+        <PreferenceCategory
+            android:title="@string/pref_header_auto_export"
+            app:iconSpaceReserved="false">
+            <Preference
+                android:key="auto_export_location"
+                android:summary="%s"
+                android:title="@string/pref_title_auto_export_location"
+                app:iconSpaceReserved="false" />
+            <SwitchPreferenceCompat
+                android:defaultValue="false"
+                android:key="auto_export_enabled"
+                android:layout="@layout/preference_checkbox"
+                android:title="@string/pref_title_auto_export_enabled"
+                app:iconSpaceReserved="false" />
+            <EditTextPreference
+                android:defaultValue="3"
+                android:dependency="auto_export_enabled"
+                android:inputType="number"
+                android:key="auto_export_interval"
+                android:maxLength="3"
+                android:summary="@string/pref_summary_auto_export_interval"
+                android:title="@string/pref_title_auto_export_interval"
+                app:iconSpaceReserved="false" />
+        </PreferenceCategory>
+
+        <PreferenceCategory
+            android:title="@string/pref_header_auto_fetch"
+            app:iconSpaceReserved="false">
+            <SwitchPreferenceCompat
+                android:defaultValue="false"
+                android:key="auto_fetch_enabled"
+                android:layout="@layout/preference_checkbox"
+                android:summary="@string/pref_auto_fetch_summary"
+                android:title="@string/pref_auto_fetch"
+                app:iconSpaceReserved="false" />
+            <EditTextPreference
+                android:defaultValue="0"
+                android:dependency="auto_fetch_enabled"
+                android:inputType="number"
+                android:key="auto_fetch_interval_limit"
+                android:maxLength="3"
+                android:summary="@string/pref_auto_fetch_limit_fetches_summary"
+                android:title="@string/pref_auto_fetch_limit_fetches"
+                app:iconSpaceReserved="false" />
+        </PreferenceCategory>
+    </PreferenceScreen>
+
+    <PreferenceScreen
+        android:icon="@drawable/ic_extension"
+        android:key="pref_screen_external_integrations"
+        android:title="@string/pref_header_external_integrations">
+
+        <PreferenceScreen
+            android:icon="@drawable/ic_navigation"
+            android:key="navigation_settings"
+            android:summary="@string/pref_title_navigation_forward"
+            android:title="@string/pref_title_nagivation_apps"
+            app:iconSpaceReserved="false">
+
+            <SwitchPreferenceCompat
+                android:defaultValue="true"
+                android:key="navigation_forward"
+                android:layout="@layout/preference_checkbox"
+                android:summary="@string/pref_summary_navigation_forward"
+                android:title="@string/pref_title_navigation_forward"
+                app:iconSpaceReserved="false" />
+            <SwitchPreferenceCompat
+                android:defaultValue="true"
+                android:dependency="navigation_forward"
+                android:key="nagivation_screen_on"
+                android:layout="@layout/preference_checkbox"
+                android:title="@string/pref_title_whenscreenon"
+                app:iconSpaceReserved="false" />
+            <ListPreference
+                android:defaultValue="autodetect"
+                android:entries="@array/osmand_package_names"
+                android:entryValues="@array/osmand_package_names_values"
+                android:key="pref_key_osmand_packagename"
+                android:summary="@string/pref_summary_osmand_packagename"
+                android:title="@string/pref_title_osmand_packagename"
+                app:iconSpaceReserved="false" />
+            <PreferenceCategory
+                android:key="navigation_apps_selection"
+                android:title="@string/pref_title_navigation_apps"
+                app:iconSpaceReserved="false">
+                <SwitchPreferenceCompat
+                    android:defaultValue="true"
+                    android:key="navigation_app_osmand"
+                    android:layout="@layout/preference_checkbox"
+                    android:title="@string/pref_navigation_app_osmand"
+                    app:iconSpaceReserved="false" />
+                <SwitchPreferenceCompat
+                    android:defaultValue="true"
+                    android:key="navigation_app_gmaps"
+                    android:layout="@layout/preference_checkbox"
+                    android:title="@string/pref_navigation_app_gmaps"
+                    app:iconSpaceReserved="false" />
+            </PreferenceCategory>
+        </PreferenceScreen>
+
+        <Preference
+            android:icon="@drawable/ic_navigation"
+            android:key="pref_key_opentracks_packagename"
+            android:summary="@string/pref_summary_opentracks_packagename"
+            android:title="@string/pref_title_opentracks_packagename" />
+
+        <Preference
+            android:icon="@drawable/ic_activity_sleep"
+            android:key="pref_category_sleepasandroid"
+            android:title="@string/sleepasandroid_settings" />
+
+        <PreferenceScreen
+            android:icon="@drawable/ic_wb_sunny"
+            android:key="pref_screen_weather"
+            android:summary="@string/pref_title_weather_summary"
+            android:title="@string/pref_title_weather">
+
+            <EditTextPreference
+                android:inputType="text"
+                android:key="weather_city"
+                android:title="@string/pref_title_weather_location"
+                app:iconSpaceReserved="false"
+                app:useSimpleSummaryProvider="true" />
+        </PreferenceScreen>
+    </PreferenceScreen>
+
+    <Preference
+        android:icon="@drawable/ic_bluetooth_searching"
+        android:key="pref_discovery_pairing"
+        android:title="@string/activity_prefs_discovery_pairing"
+        app:iconSpaceReserved="false" />
+
+    <PreferenceScreen
+        android:icon="@drawable/ic_developer_mode"
+        android:key="pref_key_development"
+        android:summary="@string/pref_description_developer_options"
+        android:title="@string/pref_header_development">
+
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:key="log_to_file"
+            android:layout="@layout/preference_checkbox"
+            android:title="@string/pref_write_logfiles"
+            app:iconSpaceReserved="false" />
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:key="crash_notification"
+            android:layout="@layout/preference_checkbox"
+            android:summary="@string/pref_crash_notification_summary"
+            android:title="@string/pref_crash_notification_title"
+            app:iconSpaceReserved="false" />
+        <SwitchPreferenceCompat
+            android:defaultValue="true"
+            android:key="permission_pestering"
+            android:layout="@layout/preference_checkbox"
+            android:summary="@string/pref_check_permission_status_summary"
+            android:title="@string/pref_check_permission_status"
+            app:iconSpaceReserved="false" />
+        <SwitchPreferenceCompat
+            android:defaultValue="true"
+            android:key="cache_weather"
+            android:layout="@layout/preference_checkbox"
+            android:summary="@string/pref_cache_weather_summary"
+            android:title="@string/pref_cache_weather"
+            app:iconSpaceReserved="false" />
+
+        <PreferenceCategory
+            android:key="pref_screen_intent_api"
+            android:title="@string/pref_header_intent_api"
+            app:iconSpaceReserved="false">
+
+            <SwitchPreferenceCompat
+                android:key="prefs_key_allow_bluetooth_intent_api"
+                android:layout="@layout/preference_checkbox"
+                android:summary="@string/activity_prefs_summary_allow_bluetooth_intent_api"
+                android:title="@string/activity_prefs_allow_bluetooth_intent_api"
+                app:iconSpaceReserved="false" />
+
+            <SwitchPreferenceCompat
+                android:key="intent_api_allow_activity_sync"
+                android:layout="@layout/preference_checkbox"
+                android:summary="@string/intent_api_allow_activity_sync_summary"
+                android:title="@string/intent_api_allow_activity_sync_title"
+                app:iconSpaceReserved="false" />
+
+            <SwitchPreferenceCompat
+                android:key="intent_api_broadcast_activity_sync"
+                android:layout="@layout/preference_checkbox"
+                android:summary="@string/intent_api_broadcast_activity_sync_summary"
+                android:title="@string/intent_api_broadcast_activity_sync_title"
+                app:iconSpaceReserved="false" />
+
+            <SwitchPreferenceCompat
+                android:key="intent_api_allow_trigger_export"
+                android:layout="@layout/preference_checkbox"
+                android:summary="@string/intent_api_allow_trigger_export_summary"
+                android:title="@string/intent_api_allow_trigger_export_title"
+                app:iconSpaceReserved="false" />
+
+            <SwitchPreferenceCompat
+                android:key="intent_api_broadcast_export"
+                android:layout="@layout/preference_checkbox"
+                android:summary="@string/intent_api_broadcast_export_summary"
+                android:title="@string/intent_api_broadcast_export_title"
+                app:iconSpaceReserved="false" />
+
+            <SwitchPreferenceCompat
+                android:key="intent_api_allow_debug_commands"
+                android:layout="@layout/preference_checkbox"
+                android:summary="@string/intent_api_allow_debug_commands_summary"
+                android:title="@string/intent_api_allow_debug_commands_title"
+                app:iconSpaceReserved="false" />
+        </PreferenceCategory>
+    </PreferenceScreen>
+
+    <PreferenceScreen
+        android:icon="@drawable/ic_report"
+        android:key="pref_screen_deprecated_functionalities"
+        android:summary="@string/pref_description_deprecated_functionalities"
+        android:title="@string/pref_header_deprecated_functionalities"
+        app:iconSpaceReserved="false">
+
+        <PreferenceCategory
+            android:key="pref_key_deprecated_functionalities"
+            android:title="@string/pref_header_deprecated_functionalities_warning"
+            app:iconSpaceReserved="false"
+            app:singleLineTitle="false" />
+
+        <PreferenceCategory
+            android:key="pref_header_media_control"
+            android:title="@string/pref_deprecated_media_control_title"
+            app:iconSpaceReserved="false"
+            app:singleLineTitle="false">
+
+            <SwitchPreferenceCompat
+                android:defaultValue="false"
+                android:key="pref_deprecated_media_control"
+                android:layout="@layout/preference_checkbox"
+                android:summary="@string/pref_deprecated_media_control_summary"
+                android:title="@string/pref_deprecated_media_control_title"
+                app:iconSpaceReserved="false" />
+
+            <SwitchPreferenceCompat
+                android:defaultValue="false"
+                android:dependency="pref_deprecated_media_control"
+                android:key="mb_intents"
+                android:layout="@layout/preference_checkbox"
+                android:summary="@string/pref_summary_mb_intents"
+                android:title="@string/pref_title_mb_intents"
+                app:iconSpaceReserved="false" />
+
+            <ListPreference
+                android:defaultValue="default"
+                android:dependency="mb_intents"
+                android:key="audio_player"
+                android:summary="%s"
+                android:title="@string/pref_title_audio_player"
+                app:iconSpaceReserved="false" />
+        </PreferenceCategory>
+    </PreferenceScreen>
+</PreferenceScreen>
diff --git a/app/src/main/res/xml/shared_paths.xml b/app/src/main/res/xml/shared_paths.xml
new file mode 100644
index 0000000..4cd3168
--- /dev/null
+++ b/app/src/main/res/xml/shared_paths.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- defines directories shared to other applications -->
+<paths xmlns:android="http://schemas.android.com/apk/res/android">
+    <external-path name="external_files" path="."/>
+    <files-path name="gpx" path="./" />
+    <cache-path name="raw" path="raw/" />
+    <cache-path name="gpx" path="gpx/" />
+</paths>
diff --git a/app/src/main/res/xml/sleep_alarm_widget_info.xml b/app/src/main/res/xml/sleep_alarm_widget_info.xml
new file mode 100644
index 0000000..dcf5c6a
--- /dev/null
+++ b/app/src/main/res/xml/sleep_alarm_widget_info.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
+    android:initialKeyguardLayout="@layout/sleep_alarm_widget"
+    android:initialLayout="@layout/sleep_alarm_widget"
+    android:minHeight="40dp"
+    android:minWidth="40dp"
+    android:previewImage="@drawable/ic_launcher"
+    android:updatePeriodMillis="86400000"
+    android:configure="nodomain.freeyourgadget.gadgetbridge.activities.SleepAlarmWidgetConfigurationActivity"
+    android:widgetCategory="home_screen">
+</appwidget-provider>
\ No newline at end of file
diff --git a/app/src/main/res/xml/sleepasandroid_preferences.xml b/app/src/main/res/xml/sleepasandroid_preferences.xml
new file mode 100644
index 0000000..238192a
--- /dev/null
+++ b/app/src/main/res/xml/sleepasandroid_preferences.xml
@@ -0,0 +1,83 @@
+<?xml version="1.0" encoding="utf-8"?>
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+    <Preference
+        android:icon="@drawable/ic_warning"
+        android:key="pref_proprietary_app_warning"
+        android:summary="@string/proprietary_app_warning" />
+
+    <PreferenceCategory
+        android:key="pref_key_sleepasandroid_general"
+        android:title="@string/pref_header_general"
+        app:iconSpaceReserved="false">
+        <SwitchPreferenceCompat
+            android:defaultValue="false"
+            android:key="pref_key_sleepasandroid_enable"
+            android:layout="@layout/preference_checkbox"
+            android:summary="@string/pref_sleepasandroid_enable_summary"
+            android:title="@string/function_enabled"
+            app:iconSpaceReserved="false" />
+        <ListPreference
+            android:key="sleepasandroid_device"
+            android:entries="@array/empty_array"
+            android:entryValues="@array/empty_array"
+            android:title="@string/pref_sleepasandroid_device_title"
+            android:summary="@string/pref_sleepasandroid_device_summary"
+            app:iconSpaceReserved="false" />
+        <ListPreference
+            android:key="sleepasandroid_alarm_slot"
+            android:entries="@array/empty_array"
+            android:entryValues="@array/empty_array"
+            android:title="@string/pref_sleepasandroid_slot_title"
+            android:summary="@string/pref_sleepasandroid_slot_summary"
+            android:enabled="false"
+            app:iconSpaceReserved="false" />
+    </PreferenceCategory>
+
+    <PreferenceCategory
+        android:key="pref_key_sleepasandroid_features"
+        android:title="@string/pref_sleepasandroid_features_title"
+        android:summary="@string/pref_sleepasandroid_features_summary"
+        app:iconSpaceReserved="false">
+        <SwitchPreferenceCompat
+            android:key="pref_key_sleepasandroid_feat_alarms"
+            android:title="@string/pref_sleepasandroid_feat_alarms"
+            app:iconSpaceReserved="false"
+            android:defaultValue="true">
+        </SwitchPreferenceCompat>
+        <SwitchPreferenceCompat
+            android:key="pref_key_sleepasandroid_feat_notifications"
+            android:title="@string/pref_sleepasandroid_feat_notifications"
+            app:iconSpaceReserved="false"
+            android:defaultValue="true">
+        </SwitchPreferenceCompat>
+        <SwitchPreferenceCompat
+            android:key="pref_key_sleepasandroid_feat_movement"
+            android:title="@string/pref_sleepasandroid_feat_movement"
+            app:iconSpaceReserved="false"
+            android:defaultValue="true"
+            android:enabled="false">
+        </SwitchPreferenceCompat>
+        <SwitchPreferenceCompat
+            android:key="pref_key_sleepasandroid_feat_hr"
+            android:title="@string/pref_sleepasandroid_feat_heartrate"
+            app:iconSpaceReserved="false"
+            android:defaultValue="true"
+            android:enabled="false">
+        </SwitchPreferenceCompat>
+        <SwitchPreferenceCompat
+            android:key="pref_key_sleepasandroid_feat_oximetry"
+            android:title="@string/pref_sleepasandroid_feat_oximetry"
+            app:iconSpaceReserved="false"
+            android:defaultValue="true"
+            android:enabled="false">
+        </SwitchPreferenceCompat>
+        <SwitchPreferenceCompat
+            android:key="pref_key_sleepasandroid_feat_spo2"
+            android:title="@string/pref_sleepasandroid_feat_spo2"
+            app:iconSpaceReserved="false"
+            android:defaultValue="true"
+            android:enabled="false">
+        </SwitchPreferenceCompat>
+    </PreferenceCategory>
+</PreferenceScreen>
\ No newline at end of file
diff --git a/app/src/main/res/xml/widget_info.xml b/app/src/main/res/xml/widget_info.xml
new file mode 100644
index 0000000..eb00deb
--- /dev/null
+++ b/app/src/main/res/xml/widget_info.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
+    android:initialKeyguardLayout="@layout/widget"
+    android:initialLayout="@layout/widget"
+    android:minHeight="1dp"
+    android:minWidth="1dp"
+    minResizeHeight="1dp"
+    minResizeWidth="1dp"
+    android:resizeMode="horizontal|vertical"
+    android:previewImage="@drawable/widget_preview"
+    android:updatePeriodMillis="86400000"
+    android:configure="nodomain.freeyourgadget.gadgetbridge.activities.WidgetConfigurationActivity"
+    android:widgetCategory="home_screen|keyguard|searchbox">
+</appwidget-provider>
+
diff --git a/app/src/mainline/res/values/strings.xml b/app/src/mainline/res/values/strings.xml
new file mode 100644
index 0000000..82d920d
--- /dev/null
+++ b/app/src/mainline/res/values/strings.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <string translatable="false" name="pebble_content_provider">com.getpebble.android.provider</string>
+    <string translatable="false" name="app_name">@string/application_name_generic</string>
+    <string translatable="false" name="title_activity_controlcenter">@string/title_activity_controlcenter_generic</string>
+    <string translatable="false" name="about_activity_title">@string/about_activity_title_generic</string>
+    <string translatable="false" name="about_description">@string/about_description_generic</string>
+    <string translatable="false" name="gadgetbridge_running">@string/gadgetbridge_running_generic</string>
+</resources>
diff --git a/app/src/nightly/res/drawable-hdpi/ic_launcher.png b/app/src/nightly/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..f22cc65
--- /dev/null
+++ b/app/src/nightly/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/app/src/nightly/res/drawable-mdpi/ic_launcher.png b/app/src/nightly/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..0c05143
--- /dev/null
+++ b/app/src/nightly/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/app/src/nightly/res/drawable-xhdpi/ic_launcher.png b/app/src/nightly/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..d7e4db4
--- /dev/null
+++ b/app/src/nightly/res/drawable-xhdpi/ic_launcher.png
Binary files differ
diff --git a/app/src/nightly/res/drawable-xxhdpi/ic_launcher.png b/app/src/nightly/res/drawable-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..e193977
--- /dev/null
+++ b/app/src/nightly/res/drawable-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/app/src/nightly/res/drawable-xxxhdpi/ic_launcher.png b/app/src/nightly/res/drawable-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..2ef6996
--- /dev/null
+++ b/app/src/nightly/res/drawable-xxxhdpi/ic_launcher.png
Binary files differ
diff --git a/app/src/nightly/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/nightly/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..036d09b
--- /dev/null
+++ b/app/src/nightly/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@color/ic_launcher_background"/>
+    <foreground android:drawable="@mipmap/ic_launcher_foreground"/>
+</adaptive-icon>
\ No newline at end of file
diff --git a/app/src/nightly/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/nightly/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 0000000..036d09b
--- /dev/null
+++ b/app/src/nightly/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@color/ic_launcher_background"/>
+    <foreground android:drawable="@mipmap/ic_launcher_foreground"/>
+</adaptive-icon>
\ No newline at end of file
diff --git a/app/src/nightly/res/mipmap-hdpi/ic_launcher.png b/app/src/nightly/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..f22cc65
--- /dev/null
+++ b/app/src/nightly/res/mipmap-hdpi/ic_launcher.png
Binary files differ
diff --git a/app/src/nightly/res/mipmap-hdpi/ic_launcher_foreground.png b/app/src/nightly/res/mipmap-hdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..9398dc4
--- /dev/null
+++ b/app/src/nightly/res/mipmap-hdpi/ic_launcher_foreground.png
Binary files differ
diff --git a/app/src/nightly/res/mipmap-hdpi/ic_launcher_round.png b/app/src/nightly/res/mipmap-hdpi/ic_launcher_round.png
new file mode 100644
index 0000000..17dd589
--- /dev/null
+++ b/app/src/nightly/res/mipmap-hdpi/ic_launcher_round.png
Binary files differ
diff --git a/app/src/nightly/res/mipmap-mdpi/ic_launcher.png b/app/src/nightly/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..0c05143
--- /dev/null
+++ b/app/src/nightly/res/mipmap-mdpi/ic_launcher.png
Binary files differ
diff --git a/app/src/nightly/res/mipmap-mdpi/ic_launcher_foreground.png b/app/src/nightly/res/mipmap-mdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..9d392e4
--- /dev/null
+++ b/app/src/nightly/res/mipmap-mdpi/ic_launcher_foreground.png
Binary files differ
diff --git a/app/src/nightly/res/mipmap-mdpi/ic_launcher_round.png b/app/src/nightly/res/mipmap-mdpi/ic_launcher_round.png
new file mode 100644
index 0000000..85eb2d5
--- /dev/null
+++ b/app/src/nightly/res/mipmap-mdpi/ic_launcher_round.png
Binary files differ
diff --git a/app/src/nightly/res/mipmap-xhdpi/ic_launcher.png b/app/src/nightly/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..d7e4db4
--- /dev/null
+++ b/app/src/nightly/res/mipmap-xhdpi/ic_launcher.png
Binary files differ
diff --git a/app/src/nightly/res/mipmap-xhdpi/ic_launcher_foreground.png b/app/src/nightly/res/mipmap-xhdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..d02420b
--- /dev/null
+++ b/app/src/nightly/res/mipmap-xhdpi/ic_launcher_foreground.png
Binary files differ
diff --git a/app/src/nightly/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/nightly/res/mipmap-xhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..cdbcaa8
--- /dev/null
+++ b/app/src/nightly/res/mipmap-xhdpi/ic_launcher_round.png
Binary files differ
diff --git a/app/src/nightly/res/mipmap-xxhdpi/ic_launcher.png b/app/src/nightly/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..e193977
--- /dev/null
+++ b/app/src/nightly/res/mipmap-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/app/src/nightly/res/mipmap-xxhdpi/ic_launcher_foreground.png b/app/src/nightly/res/mipmap-xxhdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..be3261d
--- /dev/null
+++ b/app/src/nightly/res/mipmap-xxhdpi/ic_launcher_foreground.png
Binary files differ
diff --git a/app/src/nightly/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/nightly/res/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..5a1a15c
--- /dev/null
+++ b/app/src/nightly/res/mipmap-xxhdpi/ic_launcher_round.png
Binary files differ
diff --git a/app/src/nightly/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/nightly/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..2ef6996
--- /dev/null
+++ b/app/src/nightly/res/mipmap-xxxhdpi/ic_launcher.png
Binary files differ
diff --git a/app/src/nightly/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/app/src/nightly/res/mipmap-xxxhdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..2e864e6
--- /dev/null
+++ b/app/src/nightly/res/mipmap-xxxhdpi/ic_launcher_foreground.png
Binary files differ
diff --git a/app/src/nightly/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/nightly/res/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..2d33cbc
--- /dev/null
+++ b/app/src/nightly/res/mipmap-xxxhdpi/ic_launcher_round.png
Binary files differ
diff --git a/app/src/nightly/res/values/ic_launcher_background.xml b/app/src/nightly/res/values/ic_launcher_background.xml
new file mode 100644
index 0000000..beab31f
--- /dev/null
+++ b/app/src/nightly/res/values/ic_launcher_background.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <color name="ic_launcher_background">#000000</color>
+</resources>
\ No newline at end of file
diff --git a/app/src/nightly/res/values/strings.xml b/app/src/nightly/res/values/strings.xml
new file mode 100644
index 0000000..ccc5ea3
--- /dev/null
+++ b/app/src/nightly/res/values/strings.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<resources>
+    <string translatable="false" name="app_name">@string/application_name_main_nightly</string>
+    <string translatable="false" name="title_activity_controlcenter">@string/title_activity_controlcenter_main_nightly</string>
+    <string translatable="false" name="about_activity_title">@string/about_activity_title_main_nightly</string>
+    <string translatable="false" name="about_description">@string/about_description_main_nightly</string>
+    <string translatable="false" name="gadgetbridge_running">@string/gadgetbridge_running_main_nightly</string>
+</resources>
diff --git a/app/src/nopebble/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/nopebble/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..036d09b
--- /dev/null
+++ b/app/src/nopebble/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@color/ic_launcher_background"/>
+    <foreground android:drawable="@mipmap/ic_launcher_foreground"/>
+</adaptive-icon>
\ No newline at end of file
diff --git a/app/src/nopebble/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/nopebble/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 0000000..036d09b
--- /dev/null
+++ b/app/src/nopebble/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@color/ic_launcher_background"/>
+    <foreground android:drawable="@mipmap/ic_launcher_foreground"/>
+</adaptive-icon>
\ No newline at end of file
diff --git a/app/src/nopebble/res/mipmap-hdpi/ic_launcher.png b/app/src/nopebble/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..f22cc65
--- /dev/null
+++ b/app/src/nopebble/res/mipmap-hdpi/ic_launcher.png
Binary files differ
diff --git a/app/src/nopebble/res/mipmap-hdpi/ic_launcher_foreground.png b/app/src/nopebble/res/mipmap-hdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..9398dc4
--- /dev/null
+++ b/app/src/nopebble/res/mipmap-hdpi/ic_launcher_foreground.png
Binary files differ
diff --git a/app/src/nopebble/res/mipmap-hdpi/ic_launcher_round.png b/app/src/nopebble/res/mipmap-hdpi/ic_launcher_round.png
new file mode 100644
index 0000000..17dd589
--- /dev/null
+++ b/app/src/nopebble/res/mipmap-hdpi/ic_launcher_round.png
Binary files differ
diff --git a/app/src/nopebble/res/mipmap-mdpi/ic_launcher.png b/app/src/nopebble/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..0c05143
--- /dev/null
+++ b/app/src/nopebble/res/mipmap-mdpi/ic_launcher.png
Binary files differ
diff --git a/app/src/nopebble/res/mipmap-mdpi/ic_launcher_foreground.png b/app/src/nopebble/res/mipmap-mdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..9d392e4
--- /dev/null
+++ b/app/src/nopebble/res/mipmap-mdpi/ic_launcher_foreground.png
Binary files differ
diff --git a/app/src/nopebble/res/mipmap-mdpi/ic_launcher_round.png b/app/src/nopebble/res/mipmap-mdpi/ic_launcher_round.png
new file mode 100644
index 0000000..85eb2d5
--- /dev/null
+++ b/app/src/nopebble/res/mipmap-mdpi/ic_launcher_round.png
Binary files differ
diff --git a/app/src/nopebble/res/mipmap-xhdpi/ic_launcher.png b/app/src/nopebble/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..d7e4db4
--- /dev/null
+++ b/app/src/nopebble/res/mipmap-xhdpi/ic_launcher.png
Binary files differ
diff --git a/app/src/nopebble/res/mipmap-xhdpi/ic_launcher_foreground.png b/app/src/nopebble/res/mipmap-xhdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..d02420b
--- /dev/null
+++ b/app/src/nopebble/res/mipmap-xhdpi/ic_launcher_foreground.png
Binary files differ
diff --git a/app/src/nopebble/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/nopebble/res/mipmap-xhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..cdbcaa8
--- /dev/null
+++ b/app/src/nopebble/res/mipmap-xhdpi/ic_launcher_round.png
Binary files differ
diff --git a/app/src/nopebble/res/mipmap-xxhdpi/ic_launcher.png b/app/src/nopebble/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..e193977
--- /dev/null
+++ b/app/src/nopebble/res/mipmap-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/app/src/nopebble/res/mipmap-xxhdpi/ic_launcher_foreground.png b/app/src/nopebble/res/mipmap-xxhdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..be3261d
--- /dev/null
+++ b/app/src/nopebble/res/mipmap-xxhdpi/ic_launcher_foreground.png
Binary files differ
diff --git a/app/src/nopebble/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/nopebble/res/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..5a1a15c
--- /dev/null
+++ b/app/src/nopebble/res/mipmap-xxhdpi/ic_launcher_round.png
Binary files differ
diff --git a/app/src/nopebble/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/nopebble/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..2ef6996
--- /dev/null
+++ b/app/src/nopebble/res/mipmap-xxxhdpi/ic_launcher.png
Binary files differ
diff --git a/app/src/nopebble/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/app/src/nopebble/res/mipmap-xxxhdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..2e864e6
--- /dev/null
+++ b/app/src/nopebble/res/mipmap-xxxhdpi/ic_launcher_foreground.png
Binary files differ
diff --git a/app/src/nopebble/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/nopebble/res/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..2d33cbc
--- /dev/null
+++ b/app/src/nopebble/res/mipmap-xxxhdpi/ic_launcher_round.png
Binary files differ
diff --git a/app/src/nopebble/res/values/ic_launcher_background.xml b/app/src/nopebble/res/values/ic_launcher_background.xml
new file mode 100644
index 0000000..beab31f
--- /dev/null
+++ b/app/src/nopebble/res/values/ic_launcher_background.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <color name="ic_launcher_background">#000000</color>
+</resources>
\ No newline at end of file
diff --git a/app/src/nopebble/res/values/strings.xml b/app/src/nopebble/res/values/strings.xml
new file mode 100644
index 0000000..60f6e86
--- /dev/null
+++ b/app/src/nopebble/res/values/strings.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <string translatable="false" name="pebble_content_provider">com.getpebble.android.nopebble.provider</string>
+    <string translatable="false" name="app_name">@string/application_name_main_nopebble</string>
+    <string translatable="false" name="title_activity_controlcenter">@string/title_activity_controlcenter_main_nopebble</string>
+    <string translatable="false" name="about_activity_title">@string/about_activity_title_main_nopebble</string>
+    <string translatable="false" name="about_description">@string/about_description_main_nopebble</string>
+    <string translatable="false" name="gadgetbridge_running">@string/gadgetbridge_running_main_nopebble</string>
+</resources>
diff --git a/app/src/test/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactoryTest.java b/app/src/test/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactoryTest.java
new file mode 100644
index 0000000..5441806
--- /dev/null
+++ b/app/src/test/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactoryTest.java
@@ -0,0 +1,70 @@
+package com.google.gson.typeadapters;
+
+import static org.junit.Assert.*;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+
+import org.junit.Test;
+
+/**
+ * Test our changes to the RuntimeTypeAdapterFactory, which allow for serialization and deserialization
+ * of a null type label.
+ */
+public class RuntimeTypeAdapterFactoryTest {
+    private static final Gson GSON = new GsonBuilder()
+            .registerTypeAdapterFactory(RuntimeTypeAdapterFactory
+                    .of(Parent.class, "type")
+                    .registerSubtype(Child1.class, null)
+                    .registerSubtype(Child2.class, "child2")
+            )
+            .create();
+
+    @Test
+    public void testNullLabelSerialize() {
+        final Child1 child1 = new Child1("hello1");
+        final Child2 child2 = new Child2("hello2");
+
+        assertEquals("{\"field1\":\"hello1\",\"fieldParent\":\"fromChild1\"}", GSON.toJson(child1, Parent.class));
+        assertEquals("{\"type\":\"child2\",\"field2\":\"hello2\",\"fieldParent\":\"fromChild2\"}", GSON.toJson(child2, Parent.class));
+    }
+
+    @Test
+    public void testNullLabelDeserialize() {
+        final Parent child1 = GSON.fromJson("{\"field1\":\"hello1\",\"fieldParent\":\"fromChild1\"}", Parent.class);
+        assertTrue(child1 instanceof Child1);
+        assertEquals("fromChild1", child1.fieldParent);
+        assertEquals("hello1", ((Child1) child1).field1);
+
+        final Parent child2 = GSON.fromJson("{\"type\":\"child2\",\"field2\":\"hello2\",\"fieldParent\":\"fromChild2\"}", Parent.class);
+        assertTrue(child2 instanceof Child2);
+        assertEquals("fromChild2", child2.fieldParent);
+        assertEquals("hello2", ((Child2) child2).field2);
+    }
+
+    private static class Parent {
+        private final String fieldParent;
+
+        private Parent(final String fieldParent) {
+            this.fieldParent = fieldParent;
+        }
+    }
+
+    private static class Child1 extends Parent {
+        private final String field1;
+
+        private Child1(final String field1) {
+            super("fromChild1");
+            this.field1 = field1;
+        }
+    }
+
+    private static class Child2 extends Parent {
+        private final String field2;
+
+        private Child2(final String field2) {
+            super("fromChild2");
+            this.field2 = field2;
+        }
+    }
+}
diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/database/EntitiesTest.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/database/EntitiesTest.java
new file mode 100644
index 0000000..16f6d34
--- /dev/null
+++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/database/EntitiesTest.java
@@ -0,0 +1,205 @@
+package nodomain.freeyourgadget.gadgetbridge.database;
+
+import org.junit.Test;
+
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.entities.ActivityDescription;
+import nodomain.freeyourgadget.gadgetbridge.entities.ActivityDescriptionDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.entities.DeviceAttributes;
+import nodomain.freeyourgadget.gadgetbridge.entities.Tag;
+import nodomain.freeyourgadget.gadgetbridge.entities.User;
+import nodomain.freeyourgadget.gadgetbridge.entities.UserAttributes;
+import nodomain.freeyourgadget.gadgetbridge.entities.UserAttributesDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.UserDao;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.test.TestBase;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+public class EntitiesTest extends TestBase {
+
+    private UserDao userDao;
+    private UserAttributesDao userAttributesDao;
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        userDao = daoSession.getUserDao();
+        userAttributesDao = daoSession.getUserAttributesDao();
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+
+    @Test
+    public void testUser() {
+        User user = new User();
+        user.setName("Peter");
+        user.setGender(ActivityUser.GENDER_FEMALE);
+        Calendar cal = GregorianCalendar.getInstance();
+        cal.add(Calendar.YEAR, -20);
+        user.setBirthday(cal.getTime());
+        UserAttributes attributes = new UserAttributes();
+        attributes.setWeightKG(55);
+        attributes.setHeightCM(170);
+        attributes.setSleepGoalHPD(8);
+        attributes.setStepsGoalSPD(10000);
+
+        daoSession.getUserDao().insert(user);
+        assertNotNull(user.getId());
+
+        attributes.setUserId(user.getId());
+        daoSession.getUserAttributesDao().insert(attributes);
+        user.getUserAttributesList().add(attributes);
+
+        assertNotNull(userDao.load(user.getId()));
+        assertEquals(1, userDao.count());
+        assertEquals(1, daoSession.loadAll(User.class).size());
+
+        assertNotNull(userAttributesDao.load(attributes.getId()));
+        assertEquals(1, userAttributesDao.count());
+        assertEquals(1, daoSession.loadAll(UserAttributes.class).size());
+
+        daoSession.getUserDao().update(user);
+        daoSession.delete(user);
+        daoSession.delete(attributes);
+        daoSession.delete(attributes);
+        assertNull(userDao.load(user.getId()));
+    }
+
+    @Test
+    public void testDBHelper() {
+        GBDevice dummyGBDevice = createDummyGDevice("00:00:00:00:01");
+        dummyGBDevice.setState(GBDevice.State.INITIALIZED);
+        Device device = DBHelper.getDevice(dummyGBDevice, daoSession);
+        assertNotNull(device);
+        assertEquals("00:00:00:00:01", device.getIdentifier());
+        assertEquals("Testie", device.getName());
+        assertEquals("4.0", device.getModel());
+        assertEquals(DeviceType.TEST.name(), device.getTypeName());
+        DeviceAttributes attributes = device.getDeviceAttributesList().get(0);
+        assertNotNull(attributes);
+        assertEquals("1.2.3", attributes.getFirmwareVersion1());
+    }
+
+    @Test
+    public void testActivityDescription() {
+        User user = DBHelper.getUser(daoSession);
+        assertNotNull(user);
+
+        ActivityDescriptionDao descDao = daoSession.getActivityDescriptionDao();
+        assertEquals(0, descDao.count());
+
+        List<ActivityDescription> list = DBHelper.findActivityDecriptions(user, 10, 100, daoSession);
+        assertTrue(list.isEmpty());
+
+        ActivityDescription desc = DBHelper.createActivityDescription(user, 10, 100, daoSession);
+        assertNotNull(desc);
+        assertEquals(user, desc.getUser());
+        assertEquals(10, desc.getTimestampFrom());
+        assertEquals(100, desc.getTimestampTo());
+        List<Tag> tagList = desc.getTagList();
+        assertEquals(0, tagList.size());
+
+        Tag t1 = DBHelper.getTag(user, "Table Tennis", daoSession);
+        assertNotNull(t1);
+        assertEquals("Table Tennis", t1.getName());
+        t1.setDescription("Table tennis training for Olympia");
+        tagList.add(t1);
+
+        list = DBHelper.findActivityDecriptions(user, 10, 100, daoSession);
+        assertEquals(1, list.size());
+        ActivityDescription desc1 = list.get(0);
+        assertEquals(desc, desc1);
+        assertEquals(1, desc1.getTagList().size());
+
+        // check for partial range overlaps
+        list = DBHelper.findActivityDecriptions(user, 20, 80, daoSession);
+        assertEquals(1, list.size());
+
+        list = DBHelper.findActivityDecriptions(user, 5, 120, daoSession);
+        assertEquals(1, list.size());
+
+        list = DBHelper.findActivityDecriptions(user, 20, 120, daoSession);
+        assertEquals(1, list.size());
+
+        list = DBHelper.findActivityDecriptions(user, 5, 80, daoSession);
+        assertEquals(1, list.size());
+
+        // Now with a second, adjacent ActivityDescription
+        ActivityDescription desc2 = DBHelper.createActivityDescription(user, 101, 200, daoSession);
+
+        list = DBHelper.findActivityDecriptions(user, 10, 100, daoSession);
+        assertEquals(1, list.size());
+
+        list = DBHelper.findActivityDecriptions(user, 20, 80, daoSession);
+        assertEquals(1, list.size());
+
+        list = DBHelper.findActivityDecriptions(user, 5, 120, daoSession);
+        assertEquals(2, list.size());
+
+        list = DBHelper.findActivityDecriptions(user, 20, 120, daoSession);
+        assertEquals(2, list.size());
+
+        list = DBHelper.findActivityDecriptions(user, 5, 80, daoSession);
+        assertEquals(1, list.size());
+
+        // Now with a third, partially overlapping ActivityDescription
+        ActivityDescription desc3 = DBHelper.createActivityDescription(user, 5, 15, daoSession);
+
+        list = DBHelper.findActivityDecriptions(user, 10, 100, daoSession);
+        assertEquals(2, list.size());
+
+        list = DBHelper.findActivityDecriptions(user, 20, 80, daoSession);
+        assertEquals(1, list.size());
+
+        list = DBHelper.findActivityDecriptions(user, 5, 120, daoSession);
+        assertEquals(3, list.size());
+
+        list = DBHelper.findActivityDecriptions(user, 20, 120, daoSession);
+        assertEquals(2, list.size());
+
+        list = DBHelper.findActivityDecriptions(user, 5, 80, daoSession);
+        assertEquals(2, list.size());
+    }
+
+    @Test
+    public void testDeviceAttributes() throws Exception {
+        GBDevice dummyGBDevice = createDummyGDevice("00:00:00:00:02");
+        dummyGBDevice.setFirmwareVersion("1.0");
+        dummyGBDevice.setState(GBDevice.State.INITIALIZED);
+
+        Device deviceOld = DBHelper.getDevice(dummyGBDevice, daoSession);
+        assertNotNull(deviceOld);
+
+        List<DeviceAttributes> attrListOld = deviceOld.getDeviceAttributesList();
+        assertEquals(1, attrListOld.size());
+        assertEquals("1.0", attrListOld.get(0).getFirmwareVersion1());
+        assertEquals("1.0", DBHelper.getDeviceAttributes(deviceOld).getFirmwareVersion1());
+
+        // some time passes, firmware update occurs
+        Thread.sleep(2 * 1000);
+        dummyGBDevice.setFirmwareVersion("2.0");
+
+        Device deviceNew = DBHelper.getDevice(dummyGBDevice, daoSession);
+        assertNotNull(deviceNew);
+        List<DeviceAttributes> attrListNew = deviceNew.getDeviceAttributesList();
+        assertEquals(2, attrListNew.size());
+        assertEquals("2.0", attrListNew.get(0).getFirmwareVersion1());
+        assertEquals("1.0", attrListNew.get(1).getFirmwareVersion1());
+
+        assertEquals("2.0", DBHelper.getDeviceAttributes(deviceNew).getFirmwareVersion1());
+    }
+}
diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/database/SampleProviderTest.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/database/SampleProviderTest.java
new file mode 100644
index 0000000..d3cc0fd
--- /dev/null
+++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/database/SampleProviderTest.java
@@ -0,0 +1,216 @@
+package nodomain.freeyourgadget.gadgetbridge.database;
+
+import org.junit.Test;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.AbstractActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.entities.MiBandActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.entities.User;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+import nodomain.freeyourgadget.gadgetbridge.test.TestBase;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+public class SampleProviderTest extends TestBase {
+
+    private GBDevice dummyGBDevice;
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        dummyGBDevice = createDummyGDevice("00:00:00:00:10");
+    }
+
+    @Test
+    public void testBasics() {
+        MiBandSampleProvider sampleProvider = new MiBandSampleProvider(dummyGBDevice, daoSession);
+        assertNotNull(sampleProvider.getDevice());
+        assertSame(dummyGBDevice, sampleProvider.getDevice());
+        assertNotNull(sampleProvider.getSampleDao());
+        assertNotNull(sampleProvider.createActivitySample());
+        float intensity = sampleProvider.normalizeIntensity(50);
+        assertTrue(intensity > 0);
+        assertTrue(intensity < 1);
+    }
+
+    @Test
+    public void testActivityKind() {
+        MiBandSampleProvider sampleProvider = new MiBandSampleProvider(dummyGBDevice, daoSession);
+        ActivityKind type = sampleProvider.normalizeType(MiBandSampleProvider.TYPE_ACTIVITY);
+        assertEquals(ActivityKind.ACTIVITY, type);
+
+        type = sampleProvider.normalizeType(MiBandSampleProvider.TYPE_DEEP_SLEEP);
+        assertEquals(ActivityKind.DEEP_SLEEP, type);
+
+        type = sampleProvider.normalizeType(MiBandSampleProvider.TYPE_LIGHT_SLEEP);
+        assertEquals(ActivityKind.LIGHT_SLEEP, type);
+
+        type = sampleProvider.normalizeType(MiBandSampleProvider.TYPE_NONWEAR);
+        assertEquals(ActivityKind.NOT_WORN, type);
+    }
+
+    @Test
+    public void testNoSamples() {
+        MiBandSampleProvider sampleProvider = new MiBandSampleProvider(dummyGBDevice, daoSession);
+        List<MiBandActivitySample> samples = sampleProvider.getAllActivitySamples(0, 0);
+        assertEquals(0, samples.size());
+
+        samples = sampleProvider.getAllActivitySamples(-1, 1);
+        assertEquals(0, samples.size());
+
+        samples = sampleProvider.getAllActivitySamples(1, -1);
+        assertEquals(0, samples.size());
+
+        // now specific activity kinds
+        samples = sampleProvider.getActivitySamples(0, 0);
+        assertEquals(0, samples.size());
+
+        samples = sampleProvider.getActivitySamples(-1, 1);
+        assertEquals(0, samples.size());
+
+        samples = sampleProvider.getActivitySamples(1, -1);
+        assertEquals(0, samples.size());
+
+        // Now high res data
+        samples = sampleProvider.getAllActivitySamplesHighRes(0, 0);
+        assertTrue(samples.isEmpty());
+
+        samples = sampleProvider.getAllActivitySamplesHighRes(-1, 1);
+        assertTrue(samples.isEmpty());
+
+        samples = sampleProvider.getAllActivitySamplesHighRes(1, -1);
+        assertTrue(samples.isEmpty());
+    }
+
+    private <T extends AbstractActivitySample> T createSample(SampleProvider<T> sampleProvider, int rawKind, int timestamp, int rawIntensity, int heartRate, int steps, User user, Device device) {
+        T sample = sampleProvider.createActivitySample();
+        sample.setProvider(sampleProvider);
+        sample.setRawKind(rawKind);
+        sample.setTimestamp(timestamp);
+        sample.setRawIntensity(rawIntensity);
+        sample.setHeartRate(heartRate);
+        sample.setSteps(steps);
+        sample.setUserId(user.getId());
+        sample.setDeviceId(device.getId());
+
+        return sample;
+    }
+
+    @Test
+    public void testSamples() {
+        MiBandSampleProvider sampleProvider = new MiBandSampleProvider(dummyGBDevice, daoSession);
+        User user = DBHelper.getUser(daoSession);
+        assertNotNull(user);
+        assertNotNull(user.getId());
+        Device device = DBHelper.getDevice(dummyGBDevice, daoSession);
+        assertNotNull(device);
+
+        MiBandActivitySample s1 = createSample(sampleProvider, MiBandSampleProvider.TYPE_ACTIVITY, 100, 10, 70, 1000, user, device);
+        sampleProvider.addGBActivitySample(s1);
+        sampleProvider.addGBActivitySample(s1); // add again, should not throw or fail
+
+        MiBandActivitySample s2 = createSample(sampleProvider, MiBandSampleProvider.TYPE_ACTIVITY, 200, 20, 80, 1030, user, device);
+        sampleProvider.addGBActivitySample(s2);
+
+        MiBandActivitySample s3 = createSample(sampleProvider, MiBandSampleProvider.TYPE_DEEP_SLEEP, 1200, 10, 62, 4030, user, device);
+        MiBandActivitySample s4 = createSample(sampleProvider, MiBandSampleProvider.TYPE_LIGHT_SLEEP, 2000, 10, 60, 4030, user, device);
+        sampleProvider.addGBActivitySamples(new MiBandActivitySample[] { s3, s4 });
+
+        // first checks for irrelevant timestamps => no samples
+        List<MiBandActivitySample> samples = sampleProvider.getAllActivitySamples(0, 0);
+        assertEquals(0, samples.size());
+
+        samples = sampleProvider.getAllActivitySamples(-1, 1);
+        assertEquals(0, samples.size());
+
+        samples = sampleProvider.getAllActivitySamples(1, -1);
+        assertEquals(0, samples.size());
+
+        // now specific activity kinds
+        samples = sampleProvider.getActivitySamples(0, 0);
+        assertEquals(0, samples.size());
+
+        samples = sampleProvider.getActivitySamples(-1, 1);
+        assertEquals(0, samples.size());
+
+        samples = sampleProvider.getActivitySamples(1, -1);
+        assertEquals(0, samples.size());
+
+        // Now high res data
+        samples = sampleProvider.getAllActivitySamplesHighRes(0, 0);
+        assertTrue(samples.isEmpty());
+
+        samples = sampleProvider.getAllActivitySamplesHighRes(-1, 1);
+        assertTrue(samples.isEmpty());
+
+        samples = sampleProvider.getAllActivitySamplesHighRes(1, -1);
+        assertTrue(samples.isEmpty());
+
+        // finally checks for existing timestamps
+        List<MiBandActivitySample> allSamples = sampleProvider.getAllActivitySamples(0, 10000);
+        assertEquals(4, allSamples.size());
+        // FIXME List<MiBandActivitySample> activitySamples = sampleProvider.getActivitySamples(0, 10000);
+        // FIXME assertEquals(2, activitySamples.size());
+
+        // now with more strict time ranges
+        allSamples = sampleProvider.getAllActivitySamples(0, 1300);
+        assertEquals(3, allSamples.size());
+        // FIXME activitySamples = sampleProvider.getActivitySamples(10, 150);
+        // FIXME assertEquals(1, activitySamples.size());
+    }
+
+    @Test
+    public void testHighResSamples() {
+        // Mi Band sample provider does not support this at the moment, so we use the Huawei sample provider
+        HuaweiSampleProvider sampleProvider = new HuaweiSampleProvider(dummyGBDevice, daoSession);
+        User user = DBHelper.getUser(daoSession);
+        assertNotNull(user);
+        assertNotNull(user.getId());
+        Device device = DBHelper.getDevice(dummyGBDevice, daoSession);
+        assertNotNull(device);
+
+        HuaweiActivitySample s1 = createSample(sampleProvider, MiBandSampleProvider.TYPE_ACTIVITY, 100, 10, 70, 1000, user, device);
+        s1.setOtherTimestamp(110); // Necessary for Huawei samples
+        sampleProvider.addGBActivitySample(s1);
+        sampleProvider.addGBActivitySample(s1); // add again, should not throw or fail
+
+        HuaweiActivitySample s2 = createSample(sampleProvider, MiBandSampleProvider.TYPE_ACTIVITY, 110, 20, 80, 1030, user, device);
+        s2.setOtherTimestamp(120);
+        sampleProvider.addGBActivitySample(s2);
+
+        HuaweiActivitySample s3 = createSample(sampleProvider, MiBandSampleProvider.TYPE_DEEP_SLEEP, 120, 10, 62, 4030, user, device);
+        s3.setOtherTimestamp(200);
+        HuaweiActivitySample s4 = createSample(sampleProvider, MiBandSampleProvider.TYPE_LIGHT_SLEEP, 200, 10, 60, 4030, user, device);
+        s4.setOtherTimestamp(220);
+        sampleProvider.addGBActivitySamples(new HuaweiActivitySample[] { s3, s4 });
+
+        List<HuaweiActivitySample> samples = sampleProvider.getAllActivitySamples(0, 1);
+        assertEquals(1, samples.size()); // It generates a sample for every 60 seconds that is requested
+
+        samples = sampleProvider.getAllActivitySamplesHighRes(0, 1);
+        assertTrue(samples.isEmpty());
+
+        samples = sampleProvider.getAllActivitySamples(100, 150);
+        assertEquals(1, samples.size());
+        assertEquals(100, samples.get(0).getTimestamp());
+
+        samples = sampleProvider.getAllActivitySamplesHighRes(100, 115);
+        assertEquals(2, samples.size());
+
+        samples = sampleProvider.getAllActivitySamples(100, 200);
+        assertEquals(2, samples.size()); // First three are combined
+
+        samples = sampleProvider.getAllActivitySamplesHighRes(100, 200);
+        assertEquals(4, samples.size()); // No combining takes place for the high res
+    }
+}
diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/devices/banglejs/BangleJSActivityPointTest.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/devices/banglejs/BangleJSActivityPointTest.java
new file mode 100644
index 0000000..17162b3
--- /dev/null
+++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/devices/banglejs/BangleJSActivityPointTest.java
@@ -0,0 +1,143 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.banglejs;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.model.GPSCoordinate;
+
+public class BangleJSActivityPointTest {
+    @Test
+    public void testParseCsvLine5s() {
+        testTemplate(
+                "Time,Latitude,Longitude,Altitude,Heartrate,Confidence,Source,Steps,Battery Percentage,Battery Voltage,Charging",
+                "1710610740,,,,92,50,,0,96,3.30644531249,false",
+                new BangleJSActivityPoint(
+                        1710610740_000L,
+                        null,
+                        92,
+                        50,
+                        "",
+                        0,
+                        96,
+                        3.30644531249,
+                        false,
+                        0,
+                        0,
+                        GPSCoordinate.UNKNOWN_ALTITUDE
+                )
+        );
+    }
+
+    @Test
+    public void testParseCsvLine5sWithLocation() {
+        testTemplate(
+                "Time,Latitude,Longitude,Altitude,Heartrate,Confidence,Source,Steps,Battery Percentage,Battery Voltage,Charging",
+                "1710610740,-65.999000,10.12300,,92,50,,0,96,3.30644531249,false",
+                new BangleJSActivityPoint(
+                        1710610740_000L,
+                        new GPSCoordinate(10.123d, -65.999d),
+                        92,
+                        50,
+                        "",
+                        0,
+                        96,
+                        3.30644531249,
+                        false,
+                        0,
+                        0,
+                        GPSCoordinate.UNKNOWN_ALTITUDE
+                )
+        );
+    }
+
+    @Test
+    public void testParseCsvLine5sWithLocationAndAltitude() {
+        testTemplate(
+                "Time,Latitude,Longitude,Altitude,Heartrate,Confidence,Source,Steps,Battery Percentage,Battery Voltage,Charging",
+                "1710610740,-65.999000,10.12300,55,92,50,,0,96,3.30644531249,false",
+                new BangleJSActivityPoint(
+                        1710610740_000L,
+                        new GPSCoordinate(10.123d, -65.999d, 55d),
+                        92,
+                        50,
+                        "",
+                        0,
+                        96,
+                        3.30644531249,
+                        false,
+                        0,
+                        0,
+                        GPSCoordinate.UNKNOWN_ALTITUDE
+                )
+        );
+    }
+
+    @Test
+    public void testParseCsvLine1s() {
+        testTemplate(
+                "Time,Battery Percentage,Battery Voltage,Charging,Steps,Barometer Temperature,Barometer Pressure,Barometer Altitude,Heartrate,Confidence,Source,Latitude,Longitude,Altitude",
+                "1700265185.2,78,3.31787109374,false,0,33.39859771728,1012.66780596669,4.84829130165,95.7,0,,,,",
+                new BangleJSActivityPoint(
+                        1700265185_200L,
+                        null,
+                        96,
+                        0,
+                        "",
+                        0,
+                        78,
+                        3.31787109374,
+                        false,
+                        33.39859771728,
+                        1012.66780596669,
+                        4.84829130165
+                )
+        );
+    }
+
+    @Test
+    public void testParseCsvLineBthrm() {
+        testTemplate(
+                "Time,Heartrate,Confidence,Source,Latitude,Longitude,Altitude,Int Heartrate,Int Confidence,BT Heartrate,BT Battery,Energy expended,Contact,RR,Barometer Temperature,Barometer Pressure,Barometer Altitude,Steps,Battery Percentage,Battery Voltage,Charging",
+                "1727544008.4,61,100,bthrm,,,,0,32,61,,,,1069,31.20888417561,994.92400814020,153.70596141680,0,88,3.32226562499,false",
+                new BangleJSActivityPoint(
+                        1727544008_400L,
+                        null,
+                        61,
+                        100,
+                        "bthrm",
+                        0,
+                        88,
+                        3.32226562499,
+                        false,
+                        31.20888417561,
+                        994.92400814020,
+                        153.70596141680
+                )
+        );
+    }
+
+    private void testTemplate(final String headerStr, final String csvLine, final BangleJSActivityPoint expected) {
+        final List<String> header = Arrays.asList(headerStr.split(","));
+        final BangleJSActivityPoint point = BangleJSActivityPoint.fromCsvLine(header, csvLine);
+        assertPointEquals(expected, point);
+    }
+
+    private void assertPointEquals(final BangleJSActivityPoint expected, final BangleJSActivityPoint actual) {
+        assertEquals("Mismatch on Time", expected.getTime(), actual.getTime());
+        assertEquals("Mismatch on Location", expected.getLocation(), actual.getLocation());
+        assertEquals("Mismatch on HeartRate", expected.getHeartRate(), actual.getHeartRate());
+        assertEquals("Mismatch on HrConfidence", expected.getHrConfidence(), actual.getHrConfidence());
+        assertEquals("Mismatch on HrSource", expected.getHrSource(), actual.getHrSource());
+        assertEquals("Mismatch on Steps", expected.getSteps(), actual.getSteps());
+        assertEquals("Mismatch on BatteryPercentage", expected.getBatteryPercentage(), actual.getBatteryPercentage());
+        assertEquals("Mismatch on BatteryVoltage", expected.getBatteryVoltage(), actual.getBatteryVoltage(), 0.000001d);
+        assertEquals("Mismatch on Charging", expected.isCharging(), actual.isCharging());
+        assertEquals("Mismatch on BarometerTemperature", expected.getBarometerTemperature(), actual.getBarometerTemperature(), 0.000001d);
+        assertEquals("Mismatch on BarometerPressure", expected.getBarometerPressure(), actual.getBarometerPressure(), 0.000001d);
+        assertEquals("Mismatch on BarometerAltitude", expected.getBarometerAltitude(), actual.getBarometerAltitude(), 0.000001d);
+    }
+}
diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/devices/banglejs/BangleJSWorkoutParserTest.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/devices/banglejs/BangleJSWorkoutParserTest.java
new file mode 100644
index 0000000..f4b68f4
--- /dev/null
+++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/devices/banglejs/BangleJSWorkoutParserTest.java
@@ -0,0 +1,23 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.banglejs;
+
+import static org.junit.Assert.*;
+
+import org.junit.Ignore;
+import org.junit.Test;
+
+import java.io.File;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryData;
+import nodomain.freeyourgadget.gadgetbridge.test.TestBase;
+
+public class BangleJSWorkoutParserTest extends TestBase {
+    @Test
+    @Ignore("helper test for development, remove this while debugging")
+    public void testLocal() {
+        final File file = new File("/storage/downloads/recorder.log20240317a.csv");
+        final List<BangleJSActivityPoint> pointsFromCsv = BangleJSActivityPoint.fromCsv(file);
+        assert pointsFromCsv != null;
+        final ActivitySummaryData summaryData = BangleJSWorkoutParser.dataFromPoints(pointsFromCsv);
+    }
+}
diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/TestHuaweiCrypto.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/TestHuaweiCrypto.java
new file mode 100644
index 0000000..622ed93
--- /dev/null
+++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/TestHuaweiCrypto.java
@@ -0,0 +1,77 @@
+/*  Copyright (C) 2022 Martin.JM
+
+    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 <http://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huawei;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.util.Arrays;
+
+public class TestHuaweiCrypto {
+
+    private void printByteArrayAsHex(String name, byte[] array) {
+        System.out.print(name + ": [");
+        for (int i = 0; i < array.length - 1; i++) {
+            System.out.print(Integer.toHexString(array[i] & 0xFF) + ", ");
+        }
+        System.out.println(Integer.toHexString(array[array.length - 1] & 0xFF) + "]");
+    }
+
+    private void printIntArray(String name, int[] array) {
+        System.out.print(name + ": [");
+        for (int i = 0; i < array.length - 1; i++) {
+            System.out.print(array[i] + ", ");
+        }
+        System.out.println(array[array.length - 1] + "]");
+    }
+
+    @Test
+    public void testGenerateNonce() {
+        // The function output contains randomness, so we test multiple times
+
+        // We also check how often each byte is present, and that the difference isn't too high
+        // Note that this may fail due to the randomness
+        int[] bytePresent = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+        Assert.assertEquals(256, bytePresent.length);
+
+        for (int i = 0; i < 1000; i++) {
+            byte[] output = HuaweiCrypto.generateNonce();
+
+            printByteArrayAsHex("Output", output);
+
+            Assert.assertEquals(16, output.length);
+
+            for (byte b : output) {
+                bytePresent[b & 0xFF] += 1;
+            }
+        }
+
+        printIntArray("Bytes present", bytePresent);
+
+        int minCount = Integer.MAX_VALUE;
+        int maxCount = Integer.MIN_VALUE;
+        for (int c : bytePresent) {
+            minCount = Math.min(c, minCount);
+            maxCount = Math.max(c, maxCount);
+        }
+
+        // The limit here is quite arbitrary, erring on the high side
+        if (maxCount - minCount > 60) {
+            Assert.fail("Difference in byte counts is suspiciously high, check the randomness of the nonce.");
+        }
+    }
+}
diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/TestHuaweiPacket.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/TestHuaweiPacket.java
new file mode 100644
index 0000000..a6b7612
--- /dev/null
+++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/TestHuaweiPacket.java
@@ -0,0 +1,269 @@
+/*  Copyright (C) 2023 Martin.JM
+
+    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 <http://www.gnu.org/licenses/>. */
+
+package nodomain.freeyourgadget.gadgetbridge.devices.huawei;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FindPhone;
+
+public class TestHuaweiPacket {
+
+    HuaweiPacket.ParamsProvider paramsProvider = new HuaweiPacket.ParamsProvider() {
+
+        @Override
+        public byte getDeviceSupportType() {
+            return 0;
+        }
+
+        @Override
+        public byte[] getSecretKey() {
+            return new byte[] {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+        }
+
+        @Override
+        public byte[] getIv() {
+            return new byte[] {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+        }
+
+        @Override
+        public boolean areTransactionsCrypted() {
+            return false;
+        }
+
+        @Override
+        public int getMtu() {
+            return 0;
+        }
+
+        @Override
+        public int getSliceSize() {
+            return 0xF4;
+        }
+    };
+
+    HuaweiPacket.ParamsProvider paramsProviderEncrypt = new HuaweiPacket.ParamsProvider() {
+
+        @Override
+        public byte getDeviceSupportType() {
+            return 0;
+        }
+
+        @Override
+        public byte[] getSecretKey() {
+            return new byte[] {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+        }
+
+        @Override
+        public byte[] getIv() {
+            return new byte[] {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+        }
+
+        @Override
+        public boolean areTransactionsCrypted() {
+            return true;
+        }
+
+        @Override
+        public int getMtu() {
+            return 0;
+        }
+
+        @Override
+        public int getSliceSize() {
+            return 0xF4;
+        }
+    };
+
+    HuaweiPacket.ParamsProvider paramsProviderSmallSlice = new HuaweiPacket.ParamsProvider() {
+
+        @Override
+        public byte getDeviceSupportType() {
+            return 0;
+        }
+
+        @Override
+        public byte[] getSecretKey() {
+            return new byte[] {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+        }
+
+        @Override
+        public byte[] getIv() {
+            return new byte[] {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+        }
+
+        @Override
+        public boolean areTransactionsCrypted() {
+            return false;
+        }
+
+        @Override
+        public int getMtu() {
+            return 0;
+        }
+
+        @Override
+        public int getSliceSize() {
+            return 0x10;
+        }
+    };
+
+    @Test
+    public void testEmptyPacketParse() {
+        byte[] input = {};
+
+        HuaweiPacket packet = new HuaweiPacket(paramsProvider);
+
+        try {
+            packet.parse(input);
+        } catch (HuaweiPacket.LengthMismatchException e) {
+            // Pass
+        } catch (HuaweiPacket.ParseException e) {
+            Assert.fail();
+        }
+    }
+
+    @Test
+    public void testUnknownUnencryptedPacketParse() throws HuaweiPacket.ParseException {
+        byte[] input = {0x5a, 0x00, 0x07, 0x00, 0x7f, 0x7f, 0x01, 0x02, 0x03, 0x04, 0x40, (byte) 0xb6};
+
+        HuaweiTLV expectedTlv = new HuaweiTLV()
+                .put(0x01, (short) 0x0304);
+
+        HuaweiPacket packet = new HuaweiPacket(paramsProvider);
+        packet = packet.parse(input);
+
+        Assert.assertEquals(HuaweiPacket.class, packet.getClass());
+        Assert.assertEquals(0x7f, packet.serviceId);
+        Assert.assertEquals(0x7f, packet.commandId);
+        Assert.assertFalse(packet.isEncrypted);
+        Assert.assertTrue(packet.complete);
+        Assert.assertEquals(expectedTlv, packet.getTlv());
+    }
+
+    @Test
+    public void testUnknownEncryptedPacketParse() throws HuaweiPacket.ParseException {
+        byte[] input = {(byte) 0x5a, (byte) 0x00, (byte) 0x2a, (byte) 0x00, (byte) 0x7f, (byte) 0x7f, (byte) 0x7c, (byte) 0x01, (byte) 0x01, (byte) 0x7d, (byte) 0x10, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x7e, (byte) 0x10, (byte) 0x9e, (byte) 0x40, (byte) 0xe1, (byte) 0xea, (byte) 0x15, (byte) 0xf6, (byte) 0x50, (byte) 0x80, (byte) 0x8c, (byte) 0x45, (byte) 0x19, (byte) 0xd5, (byte) 0x2a, (byte) 0xbb, (byte) 0x29, (byte) 0xb8, (byte) 0xD5, (byte) 0x24};
+
+        HuaweiTLV expectedTlv = new HuaweiTLV()
+                .put(0x01, (short) 0x0304);
+
+        HuaweiPacket packet = new HuaweiPacket(paramsProvider);
+        packet = packet.parse(input);
+
+        Assert.assertEquals(HuaweiPacket.class, packet.getClass());
+        Assert.assertEquals(0x7f, packet.serviceId);
+        Assert.assertEquals(0x7f, packet.commandId);
+        Assert.assertTrue(packet.isEncrypted);
+        Assert.assertTrue(packet.complete);
+        Assert.assertEquals(expectedTlv, packet.getTlv());
+    }
+
+    @Test
+    public void testKnownEncryptedPacketParse() throws HuaweiPacket.ParseException {
+        byte[] input = {(byte) 0x5a, (byte) 0x00, (byte) 0x2a, (byte) 0x00, (byte) 0x0b, (byte) 0x01, (byte) 0x7c, (byte) 0x01, (byte) 0x01, (byte) 0x7d, (byte) 0x10, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x7e, (byte) 0x10, (byte) 0x28, (byte) 0x00, (byte) 0x99, (byte) 0x6f, (byte) 0x2a, (byte) 0xcb, (byte) 0x62, (byte) 0x3a, (byte) 0xe6, (byte) 0x54, (byte) 0x28, (byte) 0x54, (byte) 0xf8, (byte) 0xab, (byte) 0x54, (byte) 0x83, (byte) 0xf4, (byte) 0xf4};
+
+        HuaweiTLV expectedTlv = new HuaweiTLV()
+                .put(0x01, false);
+
+        HuaweiPacket packet = new HuaweiPacket(paramsProvider);
+        packet = packet.parse(input);
+
+        Assert.assertEquals(FindPhone.Response.class, packet.getClass());
+        Assert.assertEquals(0x0b, packet.serviceId);
+        Assert.assertEquals(0x01, packet.commandId);
+        Assert.assertTrue(packet.isEncrypted);
+        Assert.assertTrue(packet.complete);
+        Assert.assertEquals(expectedTlv, packet.getTlv());
+    }
+
+    @Test
+    public void testUnencryptedUnslicedSerialize() throws HuaweiPacket.CryptoException {
+        byte serviceId = 0x01;
+        byte commandId = 0x02;
+        HuaweiTLV tlv = new HuaweiTLV()
+                .put(0x03, 0x05060708);
+
+        byte[] expected = {(byte) 0x5a, (byte) 0x00, (byte) 0x09, (byte) 0x00, (byte) 0x01, (byte) 0x02, (byte) 0x03, (byte) 0x04, (byte) 0x05, (byte) 0x06, (byte) 0x07, (byte) 0x08, (byte) 0xA4, (byte) 0xF0};
+
+        HuaweiPacket packet = new HuaweiPacket(paramsProvider);
+        packet.isSliced = false;
+        packet.isEncrypted = false;
+        packet.serviceId = serviceId;
+        packet.commandId = commandId;
+        packet.setTlv(tlv);
+
+        List<byte[]> output = packet.serialize();
+
+        Assert.assertEquals(1, output.size());
+        Assert.assertArrayEquals(expected, output.get(0));
+    }
+
+    @Test
+    public void testEncryptedUnslicedSerialize() throws HuaweiPacket.CryptoException {
+        byte serviceId = 0x01;
+        byte commandId = 0x02;
+        HuaweiTLV tlv = new HuaweiTLV()
+                .put(0x03, 0x05060708);
+
+        byte[] expected = {(byte) 0x5a, (byte) 0x00, (byte) 0x2a, (byte) 0x00, (byte) 0x01, (byte) 0x02, (byte) 0x7c, (byte) 0x01, (byte) 0x01, (byte) 0x7d, (byte) 0x10, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x7e, (byte) 0x10, (byte) 0x3b, (byte) 0x89, (byte) 0xfc, (byte) 0x79, (byte) 0xd9, (byte) 0x05, (byte) 0x5e, (byte) 0xed, (byte) 0x52, (byte) 0x35, (byte) 0xfe, (byte) 0x16, (byte) 0xa0, (byte) 0x8a, (byte) 0x4d, (byte) 0x53, (byte) 0x93, (byte) 0xc7};
+
+        HuaweiPacket packet = new HuaweiPacket(paramsProviderEncrypt);
+        packet.isSliced = false;
+        packet.isEncrypted = true;
+        packet.serviceId = serviceId;
+        packet.commandId = commandId;
+        packet.setTlv(tlv);
+
+        List<byte[]> output = packet.serialize();
+
+        Assert.assertEquals(1, output.size());
+        Assert.assertArrayEquals(expected, output.get(0));
+    }
+
+    @Test
+    public void testUnencryptedSlicedSerialize() throws HuaweiPacket.CryptoException {
+        byte serviceId = 0x01;
+        byte commandId = 0x02;
+        HuaweiTLV tlv = new HuaweiTLV()
+                .put(0x01, 0x00)
+                .put(0x02, 0x00)
+                .put(0x03, 0x00)
+                .put(0x04, 0x00);
+
+        byte[] expected1 = {(byte) 0x5a, (byte) 0x00, (byte) 0x0b, (byte) 0x01, (byte) 0x00, (byte) 0x01, (byte) 0x02, (byte) 0x01, (byte) 0x04, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x02, (byte) 0xcc, (byte) 0x98};
+        byte[] expected2 = {(byte) 0x5a, (byte) 0x00, (byte) 0x0b, (byte) 0x02, (byte) 0x01, (byte) 0x04, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x03, (byte) 0x04, (byte) 0x00, (byte) 0x00, (byte) 0xfa, (byte) 0xd3};
+        byte[] expected3 = {(byte) 0x5a, (byte) 0x00, (byte) 0x0a, (byte) 0x03, (byte) 0x02, (byte) 0x00, (byte) 0x00, (byte) 0x04, (byte) 0x04, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x37, (byte) 0xca};
+
+        HuaweiPacket packet = new HuaweiPacket(paramsProviderSmallSlice);
+        packet.isSliced = true;
+        packet.isEncrypted = false;
+        packet.serviceId = serviceId;
+        packet.commandId = commandId;
+        packet.setTlv(tlv);
+
+        List<byte[]> output = packet.serialize();
+
+        Assert.assertEquals(3, output.size());
+        Assert.assertArrayEquals(expected1, output.get(0));
+        Assert.assertArrayEquals(expected2, output.get(1));
+        Assert.assertArrayEquals(expected3, output.get(2));
+    }
+}
diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/TestHuaweiTLV.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/TestHuaweiTLV.java
new file mode 100644
index 0000000..02fcdb6
--- /dev/null
+++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/TestHuaweiTLV.java
@@ -0,0 +1,532 @@
+/*  Copyright (C) 2022 Martin.JM
+
+    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 <http://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huawei;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+public class TestHuaweiTLV {
+
+    HuaweiPacket.ParamsProvider secretsProvider = new HuaweiPacket.ParamsProvider() {
+        @Override
+        public byte getDeviceSupportType() {
+            return 0;
+        }
+
+        @Override
+        public byte[] getSecretKey() {
+            return new byte[] {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+        }
+
+        @Override
+        public byte[] getIv() {
+            return new byte[] {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+        }
+
+        @Override
+        public boolean areTransactionsCrypted() {
+            return false;
+        }
+
+        @Override
+        public int getMtu() {
+            return 0;
+        }
+
+        @Override
+        public int getSliceSize() {
+            return 0xF4;
+        }
+    };
+
+    @Test
+    public void testSerializeEmpty() {
+        byte[] expectedOutput = {};
+
+        HuaweiTLV huaweiTLV = new HuaweiTLV();
+
+        Assert.assertArrayEquals(expectedOutput, huaweiTLV.serialize());
+    }
+
+    @Test
+    public void testSerialize() {
+        ArrayList<HuaweiTLV.TLV> input = new ArrayList<>();
+        input.add(new HuaweiTLV.TLV((byte) 0x01, new byte[] {}));
+        input.add(new HuaweiTLV.TLV((byte) 0x01, new byte[] {0x42}));
+        byte[] expectedOutput = {0x01, 0x00, 0x01, 0x01, 0x42};
+
+        HuaweiTLV huaweiTLV = new HuaweiTLV();
+        huaweiTLV.valueMap = input;
+
+        Assert.assertArrayEquals(expectedOutput, huaweiTLV.serialize());
+    }
+
+    @Test
+    public void testPutEmptyTag() {
+        int tag = 0x01;
+        ArrayList<HuaweiTLV.TLV> expectedValueMap = new ArrayList<>();
+        expectedValueMap.add(new HuaweiTLV.TLV((byte) 0x01, new byte[] {}));
+
+        HuaweiTLV huaweiTLV = new HuaweiTLV().put(tag);
+
+        Assert.assertEquals(expectedValueMap, huaweiTLV.valueMap);
+    }
+
+    @Test
+    public void testPutNullByteArray() {
+        int tag = 0x01;
+        byte[] input = null;
+        ArrayList<HuaweiTLV.TLV> expectedValueMap = new ArrayList<>();
+
+        //noinspection ConstantConditions
+        HuaweiTLV huaweiTLV = new HuaweiTLV().put(tag, input);
+
+        Assert.assertEquals(expectedValueMap, huaweiTLV.valueMap);
+    }
+
+    @Test
+    public void testPutByteArray() {
+        int tag = 0x01;
+        byte[] input = {0x01, 0x02, 0x03};
+        ArrayList<HuaweiTLV.TLV> expectedValueMap = new ArrayList<>();
+        expectedValueMap.add(new HuaweiTLV.TLV((byte) 0x01, new byte[] {0x01, 0x02, 0x03}));
+
+        HuaweiTLV huaweiTLV = new HuaweiTLV()
+                .put(tag, input);
+
+        Assert.assertEquals(expectedValueMap, huaweiTLV.valueMap);
+    }
+
+    @Test
+    public void testPutByte() {
+        int tag = 0x01;
+        byte input = 0x13;
+        ArrayList<HuaweiTLV.TLV> expectedValueMap = new ArrayList<>();
+        expectedValueMap.add(new HuaweiTLV.TLV((byte) 0x01, new byte[] {0x13}));
+
+        HuaweiTLV huaweiTLV = new HuaweiTLV()
+                .put(tag, input);
+
+        Assert.assertEquals(expectedValueMap, huaweiTLV.valueMap);
+    }
+
+    @Test
+    public void testPutBooleans() {
+        int tag1 = 0x01;
+        int tag2 = 0x02;
+        ArrayList<HuaweiTLV.TLV> expectedValueMap = new ArrayList<>();
+        expectedValueMap.add(new HuaweiTLV.TLV((byte) 0x01, new byte[] {0x01}));
+        expectedValueMap.add(new HuaweiTLV.TLV((byte) 0x02, new byte[] {0x00}));
+
+        HuaweiTLV huaweiTLV = new HuaweiTLV()
+                .put(tag1, true)
+                .put(tag2, false);
+
+        Assert.assertEquals(expectedValueMap, huaweiTLV.valueMap);
+    }
+
+    @Test
+    public void testPutInt() {
+        int tag = 0x01;
+        int input = 0xDEADBEEF;
+        ArrayList<HuaweiTLV.TLV> expectedValueMap = new ArrayList<>();
+        expectedValueMap.add(new HuaweiTLV.TLV((byte) 0x01, new byte[] {(byte) 0xDE, (byte) 0xAD, (byte) 0xBE, (byte) 0xEF}));
+
+        HuaweiTLV huaweiTLV = new HuaweiTLV()
+                .put(tag, input);
+
+        Assert.assertEquals(expectedValueMap, huaweiTLV.valueMap);
+    }
+
+    @Test
+    public void testPutShort() {
+        int tag = 0x01;
+        short input = (short) 0xCAFE;
+        ArrayList<HuaweiTLV.TLV> expectedValueMap = new ArrayList<>();
+        expectedValueMap.add(new HuaweiTLV.TLV((byte) 0x01, new byte[] {(byte) 0xCA, (byte) 0xFE}));
+
+        HuaweiTLV huaweiTLV = new HuaweiTLV()
+                .put(tag, input);
+
+        Assert.assertEquals(expectedValueMap, huaweiTLV.valueMap);
+    }
+
+    @Test
+    public void testPutString() {
+        int tag = 0x01;
+        String input = "Hello world!";
+        ArrayList<HuaweiTLV.TLV> expectedValueMap = new ArrayList<>();
+        expectedValueMap.add(new HuaweiTLV.TLV(
+                (byte) 0x01,
+                new byte[] {0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, 0x77, 0x6F, 0x72, 0x6C, 0x64, 0x21}
+        ));
+
+        HuaweiTLV huaweiTLV = new HuaweiTLV()
+                .put(tag, input);
+
+        Assert.assertEquals(expectedValueMap, huaweiTLV.valueMap);
+    }
+
+    @Test
+    public void testPutHuaweiTLV() {
+        int tag = 0x01;
+        HuaweiTLV input = new HuaweiTLV().put(0x01, (short) 0x1337);
+        ArrayList<HuaweiTLV.TLV> expectedValueMap = new ArrayList<>();
+        expectedValueMap.add(new HuaweiTLV.TLV((byte) 0x01, new byte[] {0x01, 0x02, 0x13, 0x37}));
+
+        HuaweiTLV huaweiTLV = new HuaweiTLV()
+                .put(tag, input);
+
+        Assert.assertEquals(expectedValueMap, huaweiTLV.valueMap);
+    }
+
+    @Test
+    public void testPutMultipleEqualEmptyTags() {
+        int tag = 0x01;
+        ArrayList<HuaweiTLV.TLV> expectedValueMap = new ArrayList<>();
+        expectedValueMap.add(new HuaweiTLV.TLV((byte) 0x01, new byte[] {}));
+        expectedValueMap.add(new HuaweiTLV.TLV((byte) 0x01, new byte[] {}));
+
+        HuaweiTLV huaweiTLV = new HuaweiTLV()
+                .put(tag)
+                .put(tag);
+
+        Assert.assertEquals(expectedValueMap, huaweiTLV.valueMap);
+    }
+
+    @Test
+    public void testParseEmpty() {
+        byte[] input = {};
+        ArrayList<HuaweiTLV.TLV> expectedValueMap = new ArrayList<>();
+
+        HuaweiTLV huaweiTLV = new HuaweiTLV()
+                .parse(input);
+
+        Assert.assertEquals(expectedValueMap, huaweiTLV.valueMap);
+    }
+
+    @Test
+    public void testParseSingleByte() {
+        byte[] input = {0x01, 0x01, 0x01};
+        ArrayList<HuaweiTLV.TLV> expectedValueMap = new ArrayList<>();
+        expectedValueMap.add(new HuaweiTLV.TLV((byte) 0x01, new byte[] {0x01}));
+
+        HuaweiTLV huaweiTLV = new HuaweiTLV()
+                .parse(input);
+
+        Assert.assertEquals(expectedValueMap, huaweiTLV.valueMap);
+    }
+
+    @Test
+    public void testParseBytes() {
+        byte[] input = {0x01, 0x04, (byte) 0xDE, (byte) 0xAD, (byte) 0xBE, (byte) 0xEF};
+        ArrayList<HuaweiTLV.TLV> expectedValueMap = new ArrayList<>();
+        expectedValueMap.add(new HuaweiTLV.TLV((byte) 0x01, new byte[] {(byte) 0xDE, (byte) 0xAD, (byte) 0xBE, (byte) 0xEF}));
+
+        HuaweiTLV huaweiTLV = new HuaweiTLV()
+                .parse(input);
+
+        Assert.assertEquals(expectedValueMap, huaweiTLV.valueMap);
+    }
+
+    @Test
+    public void testParseZeroOffsetLength() {
+        byte[] input = {};
+        ArrayList<HuaweiTLV.TLV> expectedValueMap = new ArrayList<>();
+
+        HuaweiTLV huaweiTLV = new HuaweiTLV()
+                .parse(input, 0, 0);
+
+        Assert.assertEquals(expectedValueMap, huaweiTLV.valueMap);
+    }
+
+    @Test(expected = ArrayIndexOutOfBoundsException.class)
+    public void testParseMalformed() {
+        byte[] input = {(byte) 0x01, (byte) 0x01};
+        new HuaweiTLV()
+                .parse(input);
+        Assert.fail();
+    }
+
+    @Test
+    public void testParseOffsetLength() {
+        byte[] input = {(byte) 0x90, (byte) 0x90, (byte) 0x90, 0x01, 0x00};
+        int offset = 3;
+        int length = 2;
+        ArrayList<HuaweiTLV.TLV> expectedValueMap = new ArrayList<>();
+        expectedValueMap.add(new HuaweiTLV.TLV((byte) 0x01, new byte[] {}));
+
+        HuaweiTLV huaweiTLV = new HuaweiTLV()
+                .parse(input, offset, length);
+
+        Assert.assertEquals(expectedValueMap, huaweiTLV.valueMap);
+    }
+
+    @Test(expected = ArrayIndexOutOfBoundsException.class)
+    public void testParseWrongOffsetLength() {
+        byte[] input = {};
+        new HuaweiTLV()
+                .parse(input, 1, 1);
+        Assert.fail();
+    }
+
+    @Test
+    public void testGetBytesEmpty() throws HuaweiPacket.MissingTagException {
+        int tag = 0x01;
+        ArrayList<HuaweiTLV.TLV> input = new ArrayList<>();
+        input.add(new HuaweiTLV.TLV((byte) 0x01, new byte[] {}));
+        byte[] expectedOutput = new byte[] {};
+
+        HuaweiTLV huaweiTLV = new HuaweiTLV();
+        huaweiTLV.valueMap = input;
+
+        Assert.assertArrayEquals(expectedOutput, huaweiTLV.getBytes(tag));
+    }
+
+    @Test
+    public void testGetBytes() throws HuaweiPacket.MissingTagException {
+        int tag = 0x01;
+        ArrayList<HuaweiTLV.TLV> input = new ArrayList<>();
+        input.add(new HuaweiTLV.TLV((byte) 0x01, new byte[] {0x01, 0x02, 0x03}));
+        byte[] expectedOutput = new byte[] {0x01, 0x02, 0x03};
+
+        HuaweiTLV huaweiTLV = new HuaweiTLV();
+        huaweiTLV.valueMap = input;
+
+        Assert.assertArrayEquals(expectedOutput, huaweiTLV.getBytes(tag));
+    }
+
+    @Test
+    public void testGetByte() throws HuaweiPacket.MissingTagException {
+        int tag = 0x01;
+        ArrayList<HuaweiTLV.TLV> input = new ArrayList<>();
+        input.add(new HuaweiTLV.TLV((byte) 0x01, new byte[] {0x04}));
+        Byte expectedOutput = 0x04;
+
+        HuaweiTLV huaweiTLV = new HuaweiTLV();
+        huaweiTLV.valueMap = input;
+
+        Assert.assertEquals(expectedOutput, huaweiTLV.getByte(tag));
+    }
+
+    @Test
+    public void testGetBooleans() throws HuaweiPacket.MissingTagException {
+        ArrayList<HuaweiTLV.TLV> input = new ArrayList<>();
+        input.add(new HuaweiTLV.TLV((byte) 0x01, new byte[] {0x01}));
+        input.add(new HuaweiTLV.TLV((byte) 0x02, new byte[] {0x00}));
+
+        HuaweiTLV huaweiTLV = new HuaweiTLV();
+        huaweiTLV.valueMap = input;
+
+        Assert.assertEquals(true, huaweiTLV.getBoolean(0x01));
+        Assert.assertEquals(false, huaweiTLV.getBoolean(0x02));
+    }
+
+    @Test
+    public void testGetInteger() throws HuaweiPacket.MissingTagException {
+        int tag = 0x01;
+        ArrayList<HuaweiTLV.TLV> input = new ArrayList<>();
+        input.add(new HuaweiTLV.TLV((byte) 0x01, new byte[] {(byte) 0xDE, (byte) 0xAD, (byte) 0xBE, (byte) 0xEF}));
+        Integer expectedOutput = 0xDEADBEEF;
+
+        HuaweiTLV huaweiTLV = new HuaweiTLV();
+        huaweiTLV.valueMap = input;
+
+        Assert.assertEquals(expectedOutput, huaweiTLV.getInteger(tag));
+    }
+
+    @Test
+    public void testGetShort() throws HuaweiPacket.MissingTagException {
+        int tag = 0x01;
+        ArrayList<HuaweiTLV.TLV> input = new ArrayList<>();
+        input.add(new HuaweiTLV.TLV((byte) 0x01, new byte[] {(byte) 0xCA, (byte) 0xFE}));
+        Short expectedOutput = (short) 0xCAFE;
+
+        HuaweiTLV huaweiTLV = new HuaweiTLV();
+        huaweiTLV.valueMap = input;
+
+        Assert.assertEquals(expectedOutput, huaweiTLV.getShort(tag));
+    }
+
+    @Test
+    public void testGetString() throws HuaweiPacket.MissingTagException {
+        int tag = 0x01;
+        ArrayList<HuaweiTLV.TLV> input = new ArrayList<>();
+        input.add(new HuaweiTLV.TLV((byte) 0x01, new byte[] {0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, 0x77, 0x6F, 0x72, 0x6C, 0x64, 0x21}));
+        String expectedOutput = "Hello world!";
+
+        HuaweiTLV huaweiTLV = new HuaweiTLV();
+        huaweiTLV.valueMap = input;
+
+        Assert.assertEquals(expectedOutput, huaweiTLV.getString(tag));
+    }
+
+    @Test
+    public void testGetObject() throws HuaweiPacket.MissingTagException {
+        int tag = 0x01;
+        ArrayList<HuaweiTLV.TLV> input = new ArrayList<>();
+        input.add(new HuaweiTLV.TLV((byte) 0x01, new byte[] {0x01, 0x00}));
+        HuaweiTLV expectedOutput = new HuaweiTLV().put(0x01);
+
+        HuaweiTLV huaweiTLV = new HuaweiTLV();
+        huaweiTLV.valueMap = input;
+
+        // assertEquals currently tests if the objects are the same, thus this would fail
+        // Assert.assertEquals(expectedOutput, huaweiTLV.getObject(tag));
+
+        HuaweiTLV result = huaweiTLV.getObject(tag);
+
+        Assert.assertEquals(expectedOutput.valueMap, result.valueMap);
+    }
+
+    @Test
+    public void testContains() {
+        int existingTag = 0x01;
+        int nonExistingTag = 0x02;
+        ArrayList<HuaweiTLV.TLV> input = new ArrayList<>();
+        input.add(new HuaweiTLV.TLV((byte) 0x01, new byte[] {}));
+
+        HuaweiTLV huaweiTLV = new HuaweiTLV();
+        huaweiTLV.valueMap = input;
+
+        Assert.assertTrue(huaweiTLV.contains(existingTag));
+        Assert.assertFalse(huaweiTLV.contains(nonExistingTag));
+    }
+
+    @Test
+    public void testRemoveExisting() {
+        int tag = 0x01;
+        ArrayList<HuaweiTLV.TLV> input = new ArrayList<>();
+        input.add(new HuaweiTLV.TLV((byte) 0x01, new byte[] {0x13, 0x37}));
+        input.add(new HuaweiTLV.TLV((byte) 0x02, new byte[] {}));
+        byte[] expectedOutput = {0x13, 0x37};
+        ArrayList<HuaweiTLV.TLV> expectedValueMap = new ArrayList<>();
+        expectedValueMap.add(new HuaweiTLV.TLV((byte) 0x02, new byte[] {}));
+
+        HuaweiTLV huaweiTLV = new HuaweiTLV();
+        huaweiTLV.valueMap = input;
+
+        Assert.assertArrayEquals(expectedOutput, huaweiTLV.remove(tag));
+        Assert.assertEquals(expectedValueMap, huaweiTLV.valueMap);
+    }
+
+    @Test
+    public void testRemoveNonExisting() {
+        int tag = 0x02;
+        ArrayList<HuaweiTLV.TLV> input = new ArrayList<>();
+        input.add(new HuaweiTLV.TLV((byte) 0x01, new byte[] {0x13, 0x37}));
+
+        HuaweiTLV huaweiTLV = new HuaweiTLV();
+        huaweiTLV.valueMap = input;
+
+        Assert.assertNull(huaweiTLV.remove(tag));
+        Assert.assertEquals(input, huaweiTLV.valueMap);
+    }
+
+    @Test
+    public void testRemoveDouble() {
+        int tag = 0x01;
+        ArrayList<HuaweiTLV.TLV> input = new ArrayList<>();
+        input.add(new HuaweiTLV.TLV((byte) 0x01, new byte[] {(byte) 0xCA}));
+        input.add(new HuaweiTLV.TLV((byte) 0x01, new byte[] {(byte) 0xFE}));
+        byte[] expectedOutput1 = {(byte) 0xFE};
+        byte[] expectedOutput2 = {(byte) 0xCA};
+        ArrayList<HuaweiTLV.TLV> expectedValueMap1 = new ArrayList<>();
+        expectedValueMap1.add(new HuaweiTLV.TLV((byte) 0x01, new byte[] {(byte) 0xCA}));
+        ArrayList<HuaweiTLV.TLV> expectedValueMap2 = new ArrayList<>();
+
+        HuaweiTLV huaweiTLV = new HuaweiTLV();
+        huaweiTLV.valueMap = input;
+
+        Assert.assertArrayEquals(expectedOutput1, huaweiTLV.remove(tag));
+        Assert.assertEquals(expectedValueMap1, huaweiTLV.valueMap);
+        Assert.assertArrayEquals(expectedOutput2, huaweiTLV.remove(tag));
+        Assert.assertEquals(expectedValueMap2, huaweiTLV.valueMap);
+    }
+
+    @Test
+    public void testToStringEmpty() {
+        ArrayList<HuaweiTLV.TLV> input = new ArrayList<>();
+        String expectedOutput = "Empty";
+
+        HuaweiTLV huaweiTLV = new HuaweiTLV();
+        huaweiTLV.valueMap = input;
+
+        Assert.assertEquals(expectedOutput, huaweiTLV.toString());
+    }
+
+    @Test
+    public void testToString() {
+        ArrayList<HuaweiTLV.TLV> input = new ArrayList<>();
+        input.add(new HuaweiTLV.TLV((byte) 0x01, new byte[] {0x01, 0x02}));
+        input.add(new HuaweiTLV.TLV((byte) 0x02, new byte[] {0x03, 0x04}));
+        String expectedOutput = "{tag: 1 - Value: 0102} - {tag: 2 - Value: 0304}";
+
+        HuaweiTLV huaweiTLV = new HuaweiTLV();
+        huaweiTLV.valueMap = input;
+
+        Assert.assertEquals(expectedOutput, huaweiTLV.toString());
+    }
+
+    /**
+     * Following test also depends on the HuaweiCrypto class functioning correctly
+     */
+    @Test
+    public void testEncrypt() throws HuaweiCrypto.CryptoException {
+        ArrayList<HuaweiTLV.TLV> input = new ArrayList<>();
+        input.add(new HuaweiTLV.TLV((byte) 0x01, new byte[] {(byte) 0xCA, (byte) 0xFE}));
+
+        byte[] expectedCiphertext = {(byte) 0x0E, (byte) 0xA0, (byte) 0x01, (byte) 0xBB, (byte) 0x1E, (byte) 0xDA, (byte) 0xCB, (byte) 0x09, (byte) 0x83, (byte) 0x20, (byte) 0x40, (byte) 0x7D, (byte) 0x97, (byte) 0x1B, (byte) 0xF6, (byte) 0xD0};
+        ArrayList<HuaweiTLV.TLV> expectedValueMap = new ArrayList<>();
+        expectedValueMap.add(new HuaweiTLV.TLV((byte) 0x7C, new byte[] {0x01}));
+        expectedValueMap.add(new HuaweiTLV.TLV((byte) 0x7D, secretsProvider.getIv()));
+        expectedValueMap.add(new HuaweiTLV.TLV((byte) 0x7E, expectedCiphertext));
+
+        HuaweiTLV huaweiTLV = new HuaweiTLV();
+        huaweiTLV.valueMap = input;
+
+        HuaweiTLV encryptedTlv = huaweiTLV.encrypt(secretsProvider);
+
+        Assert.assertEquals(input, huaweiTLV.valueMap);
+        Assert.assertEquals(expectedValueMap, encryptedTlv.valueMap);
+    }
+
+    /**
+     * Following test also depends on the HuaweiCrypto class functioning correctly
+     */
+    @Test
+    public void testDecrypt() throws HuaweiCrypto.CryptoException, HuaweiPacket.MissingTagException {
+        byte[] ciphertext = {(byte) 0x0E, (byte) 0xA0, (byte) 0x01, (byte) 0xBB, (byte) 0x1E, (byte) 0xDA, (byte) 0xCB, (byte) 0x09, (byte) 0x83, (byte) 0x20, (byte) 0x40, (byte) 0x7D, (byte) 0x97, (byte) 0x1B, (byte) 0xF6, (byte) 0xD0};
+        ArrayList<HuaweiTLV.TLV> input = new ArrayList<>();
+        input.add(new HuaweiTLV.TLV((byte) 0x7C, new byte[] {0x01}));
+        input.add(new HuaweiTLV.TLV((byte) 0x7D, secretsProvider.getIv()));
+        input.add(new HuaweiTLV.TLV((byte) 0x7E, ciphertext));
+
+        ArrayList<HuaweiTLV.TLV> expectedValueMap = new ArrayList<>();
+        expectedValueMap.add(new HuaweiTLV.TLV((byte) 0x01, new byte[] {(byte) 0xCA, (byte) 0xFE}));
+
+        HuaweiTLV huaweiTLV = new HuaweiTLV();
+        huaweiTLV.valueMap = input;
+
+        huaweiTLV.decrypt(secretsProvider);
+
+        Assert.assertEquals(expectedValueMap, huaweiTLV.valueMap);
+    }
+}
diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/TestHuaweiTruSleepParser.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/TestHuaweiTruSleepParser.java
new file mode 100644
index 0000000..f724420
--- /dev/null
+++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/TestHuaweiTruSleepParser.java
@@ -0,0 +1,229 @@
+/*  Copyright (C) 2024 Martin.JM
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huawei;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.util.ArrayList;
+
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class TestHuaweiTruSleepParser {
+
+    @Test
+    public void testParseState() {
+        byte[] test = GB.hexStringToByteArray("0100000002000000000000000000000003000000050000000000000000000000");
+
+        HuaweiTruSleepParser.TruSleepStatus[] expected = new HuaweiTruSleepParser.TruSleepStatus[] {
+                new HuaweiTruSleepParser.TruSleepStatus(1, 2),
+                new HuaweiTruSleepParser.TruSleepStatus(3, 5)
+        };
+
+        HuaweiTruSleepParser.TruSleepStatus[] result = HuaweiTruSleepParser.parseState(test);
+
+        Assert.assertArrayEquals(expected, result);
+    }
+    @Test
+    public void testParseDataPpg() {
+        byte[] test1 = GB.hexStringToByteArray("02B000403B0B672AAADA13C109A915E707BE130C06E3112104F80F84182F0888109900390805121004850E4317EC07C911D4037610C701580E2A02310F6B022B100E0448119B075B154F099916390A07191A0E9B00D10E2D02A7105405AAE8FDE8FDBDDA92B767943B71104EE52AB907EE06AC08BD06BE067C07DB06CC05E2079B06C206E906920639063807E0056406D905E2067807870660074D03720A8406AA0624064005CA07DF062D088706D2084B0A00");
+        byte[] test2 = GB.hexStringToByteArray("027500F61E0B6724BB0C016968685756585753545050535354565B595B5B5B5F5C5A5E5B61635D5E5B5756585656AADD401138452F7826FC1D61221F27DD2BFC1A7B248B11260A7A221E1C9128FE185025C827D41C1B206223E31B73126210EC22B620D212B232DC0A93115A322E279B116717F30E2E1500");
+        byte[] test3 = GB.hexStringToByteArray("025A00B2180B671BBB410174706A726F6D6D747B757B776E6E6D6E6F777D77777670757570AA543D0E52276956406837B63C103EB22C6929C7265326EA2FCC2F743CC9380239673B9739EA2FC92CC526BB2E652D6433DD3506397E2F00");
+
+        // TODO: Unlike PPGs with 0xBB compression the timestamps are not linear
+        // The data also looks different, hmmm....
+        ArrayList<HuaweiTruSleepParser.TruSleepDataPpg> expected = new ArrayList<>();
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728789362820L, (short)0xfde8));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728789336970L, (short)0xfde8));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728789367450L, (short)0xdabd));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728789332230L, (short)0xb792));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728789362540L, (short)0x9467));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728789327480L, (short)0x713b));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728789357790L, (short)0x4e10));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728789322570L, (short)0x2ae5));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728789352880L, (short)0x07b9));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728789374760L, (short)0x06ee));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728789332950L, (short)0x08ac));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728789354320L, (short)0x06bd));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728789313530L, (short)0x06be));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728789333050L, (short)0x077c));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728789358130L, (short)0x06db));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728789322400L, (short)0x05cc));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728789349170L, (short)0x07e2));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728789371550L, (short)0x069b));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728789332280L, (short)0x06c2));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728789357530L, (short)0x06e9));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728789321800L, (short)0x0692));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728789354140L, (short)0x0639));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728789316550L, (short)0x0738));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728789348720L, (short)0x05e0));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728789317540L, (short)0x0664));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728789350890L, (short)0x05d9));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728789318190L, (short)0x06e2));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728789353390L, (short)0x0778));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728789322380L, (short)0x0687));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728789356240L, (short)0x0760));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728789331470L, (short)0x034d));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728789366670L, (short)0x0a72));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728789335830L, (short)0x0684));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728789369850L, (short)0x06aa));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728789338170L, (short)0x0624));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728789376070L, (short)0x0540));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728789348100L, (short)0x07ca));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728789313550L, (short)0x06df));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728789349930L, (short)0x082d));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728789317570L, (short)0x0687));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728789354630L, (short)0x08d2));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728789325640L, (short)0x0a4b));
+
+        HuaweiTruSleepParser.TruSleepData result = HuaweiTruSleepParser.parseData(test1);
+        Assert.assertEquals(expected, result.dataPPGs);
+
+        expected = new ArrayList<>();
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728782072680L, (short)0x40dd));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728782073730L, (short)0x3811));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728782074770L, (short)0x2f45));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728782075810L, (short)0x2678));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728782076680L, (short)0x1dfc));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728782077540L, (short)0x2261));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728782078420L, (short)0x271f));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728782079290L, (short)0x2bdd));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728782080120L, (short)0x1afc));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728782080960L, (short)0x247b));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728782081760L, (short)0x118b));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728782082560L, (short)0x0a26));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728782083390L, (short)0x227a));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728782084220L, (short)0x1c1e));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728782085060L, (short)0x2891));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728782085920L, (short)0x18fe));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728782086830L, (short)0x2550));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728782087720L, (short)0x27c8));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728782088630L, (short)0x1cd4));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728782089540L, (short)0x201b));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728782090450L, (short)0x2362));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728782091400L, (short)0x1be3));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728782092320L, (short)0x1273));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728782093220L, (short)0x1062));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728782094160L, (short)0x22ec));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728782095070L, (short)0x20b6));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728782096040L, (short)0x12d2));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728782097030L, (short)0x32b2));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728782097960L, (short)0x0adc));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728782098900L, (short)0x1193));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728782099810L, (short)0x325a));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728782100680L, (short)0x272e));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728782101540L, (short)0x119b));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728782102420L, (short)0x1767));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728782103280L, (short)0x0ef3));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728782104140L, (short)0x152e));
+
+        result = HuaweiTruSleepParser.parseData(test2);
+        Assert.assertEquals(expected, result.dataPPGs);
+
+        expected = new ArrayList<>();
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728780469210L, (short)0x3d54));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728780470370L, (short)0x520e));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728780471490L, (short)0x6927));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728780472550L, (short)0x4056));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728780473690L, (short)0x3768));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728780474800L, (short)0x3cb6));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728780475890L, (short)0x3e10));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728780476980L, (short)0x2cb2));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728780478140L, (short)0x2969));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728780479370L, (short)0x26c7));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728780480540L, (short)0x2653));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728780481770L, (short)0x2fea));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728780482960L, (short)0x2fcc));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728780484060L, (short)0x3c74));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728780485160L, (short)0x38c9));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728780486250L, (short)0x3902));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728780487350L, (short)0x3b67));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728780488460L, (short)0x3997));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728780489650L, (short)0x2fea));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728780490900L, (short)0x2cc9));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728780492090L, (short)0x26c5));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728780493280L, (short)0x2ebb));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728780494460L, (short)0x2d65));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728780495580L, (short)0x3364));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728780496750L, (short)0x35dd));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728780497920L, (short)0x3906));
+        expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728780499040L, (short)0x2f7e));
+
+        result = HuaweiTruSleepParser.parseData(test3);
+        Assert.assertEquals(expected, result.dataPPGs);
+    }
+
+    @Test
+    public void testParseDataAcc() {
+        byte[] test = GB.hexStringToByteArray("0107F02A0B670000B6");
+
+        ArrayList<HuaweiTruSleepParser.TruSleepDataAcc> expected = new ArrayList<>();
+        expected.add(new HuaweiTruSleepParser.TruSleepDataAcc (0x670b2af0, (short)0x0000));
+
+        HuaweiTruSleepParser.TruSleepData result = HuaweiTruSleepParser.parseData(test);
+        Assert.assertEquals(0, result.dataPPGs.size());
+        Assert.assertEquals(expected, result.dataACCs);
+    }
+
+    @Test
+    public void testParsePadding() {
+        byte[] test1 = GB.hexStringToByteArray("000107F02A0B670000B6");
+        byte[] test2 = GB.hexStringToByteArray("00000000000107F02A0B670000B6");
+
+        ArrayList<HuaweiTruSleepParser.TruSleepDataAcc> expected = new ArrayList<>();
+        expected.add(new HuaweiTruSleepParser.TruSleepDataAcc (0x670b2af0, (short)0x0000));
+
+        HuaweiTruSleepParser.TruSleepData result = HuaweiTruSleepParser.parseData(test1);
+
+        Assert.assertEquals(0, result.dataPPGs.size());
+        Assert.assertEquals(expected, result.dataACCs);
+
+        result = HuaweiTruSleepParser.parseData(test2);
+
+        Assert.assertEquals(0, result.dataPPGs.size());
+        Assert.assertEquals(expected, result.dataACCs);
+    }
+
+    @Test
+    public void testParseFailure() {
+        ArrayList<byte[]> tests = new ArrayList<byte[]>();
+        // Length shorter than expected
+        tests.add(GB.hexStringToByteArray("0105F02A0B670000B6"));
+        // Zero length
+        tests.add(GB.hexStringToByteArray("0100F02A0B670000B6"));
+        // Length bigger than input data
+        tests.add(GB.hexStringToByteArray("0108F02A0B670000B6"));
+
+        // Length bigger than input data
+        tests.add(GB.hexStringToByteArray("027600F61E0B6724BB0C016968685756585753545050535354565B595B5B5B5F5C5A5E5B61635D5E5B5756585656AADD401138452F7826FC1D61221F27DD2BFC1A7B248B11260A7A221E1C9128FE185025C827D41C1B206223E31B73126210EC22B620D212B232DC0A93115A322E279B116717F30E2E1500"));
+        // Number of UINT16 less than provided
+        tests.add(GB.hexStringToByteArray("027500F61E0B6723BB0C016968685756585753545050535354565B595B5B5B5F5C5A5E5B61635D5E5B5756585656AADD401138452F7826FC1D61221F27DD2BFC1A7B248B11260A7A221E1C9128FE185025C827D41C1B206223E31B73126210EC22B620D212B232DC0A93115A322E279B116717F30E2E1500"));
+        // Wrong compression tag
+        tests.add(GB.hexStringToByteArray("025A00B2180B671BCC410174706A726F6D6D747B757B776E6E6D6E6F777D77777670757570AA543D0E52276956406837B63C103EB22C6929C7265326EA2FCC2F743CC9380239673B9739EA2FC92CC526BB2E652D6433DD3506397E2F00"));
+        // Wrong compression tag 2
+        tests.add(GB.hexStringToByteArray("025A00B2180B671BBB410174706A726F6D6D747B757B776E6E6D6E6F777D77777670757570DD543D0E52276956406837B63C103EB22C6929C7265326EA2FCC2F743CC9380239673B9739EA2FC92CC526BB2E652D6433DD3506397E2F00"));
+        // Wrong restart tags in compressed data
+        tests.add(GB.hexStringToByteArray("025A00B2180B671BBB410FFFFFFA726F6D6D747B757B776E6E6D6E6F777D77777670757570AA543D0E52276956406837B63C103EB22C6929C7265326EA2FCC2F743CC9380239673B9739EA2FC92CC526BB2E652D6433DD3506397E2F00"));
+
+        for (byte[] test: tests) {
+            HuaweiTruSleepParser.TruSleepData result = HuaweiTruSleepParser.parseData(test);
+
+            Assert.assertEquals("Test input data " + GB.hexdump(test), 0, result.dataPPGs.size());
+            Assert.assertEquals("Test input data " + GB.hexdump(test),0, result.dataACCs.size());
+        }
+    }
+}
diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/TestVarInt.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/TestVarInt.java
new file mode 100644
index 0000000..fde67f5
--- /dev/null
+++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/TestVarInt.java
@@ -0,0 +1,72 @@
+/*  Copyright (C) 2022 Martin.JM
+
+    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 <http://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huawei;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class TestVarInt {
+
+    private void testValue(int intValue, byte[] bytesValue, int size) {
+        Assert.assertEquals(size, VarInt.getVarIntSize(intValue));
+        Assert.assertEquals(intValue, VarInt.getVarIntValue(bytesValue, 0));
+        Assert.assertArrayEquals(bytesValue, VarInt.putVarIntValue(intValue));
+
+        VarInt varInt = new VarInt(bytesValue, 0);
+        Assert.assertEquals(size, varInt.size);
+        Assert.assertEquals(intValue, varInt.dValue);
+        Assert.assertArrayEquals(bytesValue, varInt.eValue);
+    }
+
+    @Test
+    public void testZero() {
+        testValue(0, new byte[]{0}, 1);
+    }
+
+    @Test
+    public void testSingleValue() {
+        testValue(17, new byte[]{17}, 1);
+    }
+
+    @Test
+    public void test0x80() {
+        // This is 1 << 8, the first 'overflowing' value
+        testValue(0x80, new byte[]{(byte) 0x81, 0x00}, 2);
+    }
+
+    @Test
+    public void testDoubleValue() {
+        testValue(460, new byte[]{(byte) 0x83, 0x4C}, 2);
+    }
+
+    @Test
+    public void testOffset() {
+        int intValue = 460;
+        byte[] bytesValue = {(byte) 0x83, 0x4C};
+        byte[] bytesTest = {0x00, (byte) 0x83, 0x4C};
+        int size = 2;
+
+        Assert.assertEquals(size, VarInt.getVarIntSize(intValue));
+        Assert.assertEquals(intValue, VarInt.getVarIntValue(bytesTest, 1));
+        Assert.assertArrayEquals(bytesValue, VarInt.putVarIntValue(intValue));
+
+        VarInt varInt = new VarInt(bytesTest, 1);
+        Assert.assertEquals(size, varInt.size);
+        Assert.assertEquals(intValue, varInt.dValue);
+        Assert.assertArrayEquals(bytesValue, varInt.eValue);
+    }
+}
diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/TestAlarms.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/TestAlarms.java
new file mode 100644
index 0000000..0712dd2
--- /dev/null
+++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/TestAlarms.java
@@ -0,0 +1,167 @@
+/*  Copyright (C) 2022-2023 Martin.JM
+
+    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 <http://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiTLV;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.lang.reflect.Field;
+import java.util.List;
+
+public class TestAlarms {
+
+    HuaweiPacket.ParamsProvider paramsProvider = new HuaweiPacket.ParamsProvider() {
+        @Override
+        public byte getDeviceSupportType() {
+            return 0;
+        }
+
+        @Override
+        public byte[] getSecretKey() {
+            return new byte[] {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+        }
+
+        @Override
+        public byte[] getIv() {
+            return new byte[] {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+        }
+
+        @Override
+        public boolean areTransactionsCrypted() {
+            return true;
+        }
+
+        @Override
+        public int getMtu() {
+            return 0;
+        }
+
+        @Override
+        public int getSliceSize() {
+            return 0xF4;
+        }
+    };
+
+    @Test
+    public void testEventAlarmsRequest() throws NoSuchFieldException, IllegalAccessException, HuaweiPacket.CryptoException {
+        Field tlvField = HuaweiPacket.class.getDeclaredField("tlv");
+        tlvField.setAccessible(true);
+
+        Field alarmField = Alarms.EventAlarmsRequest.class.getDeclaredField("alarms");
+        alarmField.setAccessible(true);
+
+        HuaweiTLV expectedAlarmsTlv = new HuaweiTLV();
+
+        Alarms.EventAlarmsRequest request = new Alarms.EventAlarmsRequest(paramsProvider);
+
+        expectedAlarmsTlv.put(0x82, new HuaweiTLV()
+                .put(0x03, (byte) 1)
+                .put(0x04, true)
+                .put(0x05, (short) 0x1337)
+                .put(0x06, (byte) 0)
+                .put(0x07, "Alarm1")
+        );
+
+        request.addEventAlarm(
+                new Alarms.EventAlarm(
+                        (byte) 1,
+                        true,
+                        (byte) 0x13,
+                        (byte) 0x37,
+                        (byte) 0,
+                        "Alarm1"
+                )
+        );
+
+        Assert.assertEquals(0x08, request.serviceId);
+        Assert.assertEquals(0x01, request.commandId);
+        // TODO: check count in request
+        Assert.assertEquals(expectedAlarmsTlv, alarmField.get(request));
+
+        // A serialize will change the tlv, so we cannot test it here
+
+        expectedAlarmsTlv.put(0x82,new HuaweiTLV()
+                .put(0x03, (byte) 2)
+                .put(0x04, false)
+                .put(0x05, (short) 0xCAFE)
+                .put(0x06, (byte) 1)
+                .put(0x07, "Alarm2")
+        );
+
+        request.addEventAlarm(
+                new Alarms.EventAlarm(
+                        (byte) 2,
+                        false,
+                        (byte) 0xCA,
+                        (byte) 0xFE,
+                        (byte) 1,
+                        "Alarm2"
+                )
+        );
+
+        Assert.assertEquals(expectedAlarmsTlv, alarmField.get(request));
+
+        HuaweiTLV expectedTlv = new HuaweiTLV().put(0x81, expectedAlarmsTlv);
+
+        // Different order for better assertion messages in case of failure
+        List<byte[]> listOut = request.serialize();
+        Assert.assertEquals(1, listOut.size());
+        Assert.assertEquals(expectedAlarmsTlv, alarmField.get(request));
+        Assert.assertEquals(expectedTlv, tlvField.get(request));
+    }
+
+    @Test
+    public void testSmartAlarmRequest() throws NoSuchFieldException, IllegalAccessException, HuaweiPacket.CryptoException {
+        Field tlvField = HuaweiPacket.class.getDeclaredField("tlv");
+        tlvField.setAccessible(true);
+
+        byte[] expectedOutput = new byte[] {(byte) 0x5a, (byte) 0x00, (byte) 0x3a, (byte) 0x00, (byte) 0x08, (byte) 0x02, (byte) 0x7c, (byte) 0x01, (byte) 0x01, (byte) 0x7d, (byte) 0x10, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x7e, (byte) 0x20, (byte) 0xcd, (byte) 0x7f, (byte) 0x80, (byte) 0x67, (byte) 0x02, (byte) 0x8d, (byte) 0x46, (byte) 0xfb, (byte) 0xc1, (byte) 0x0b, (byte) 0xed, (byte) 0x6c, (byte) 0x46, (byte) 0xb7, (byte) 0x59, (byte) 0xba, (byte) 0x08, (byte) 0xfd, (byte) 0xde, (byte) 0x3b, (byte) 0xee, (byte) 0x54, (byte) 0xbd, (byte) 0x4f, (byte) 0x27, (byte) 0xf6, (byte) 0x52, (byte) 0x9a, (byte) 0xae, (byte) 0xbf, (byte) 0x55, (byte) 0xd9, (byte) 0xe0, (byte) 0xa6};
+
+        HuaweiTLV expectedTlv = new HuaweiTLV()
+                .put(0x81, new HuaweiTLV()
+                        .put(0x82, new HuaweiTLV()
+                                .put(0x03, (byte) 0x01)
+                                .put(0x04, true)
+                                .put(0x05, (short) 0x1337)
+                                .put(0x06, (byte) 1)
+                                .put(0x07, (byte) 2)
+                        )
+                );
+
+        Alarms.SmartAlarmRequest request = new Alarms.SmartAlarmRequest(
+                paramsProvider,
+                new Alarms.SmartAlarm(
+                        true,
+                        (byte) 0x13,
+                        (byte) 0x37,
+                        (byte) 1,
+                        (byte) 2
+                )
+        );
+
+        Assert.assertEquals(0x08, request.serviceId);
+        Assert.assertEquals(0x02, request.commandId);
+        Assert.assertTrue(request.complete);
+        Assert.assertEquals(expectedTlv, tlvField.get(request));
+        List<byte[]> out = request.serialize();
+        Assert.assertEquals(1, out.size());
+        Assert.assertArrayEquals(expectedOutput, out.get(0));
+    }
+}
diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/TestCalls.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/TestCalls.java
new file mode 100644
index 0000000..366a19d
--- /dev/null
+++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/TestCalls.java
@@ -0,0 +1,68 @@
+/*  Copyright (C) 2022 Martin.JM
+
+    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 <http://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.lang.reflect.Field;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiTLV;
+
+public class TestCalls {
+
+    @Test
+    public void testAnswerCallResponseAccept() throws NoSuchFieldException, IllegalAccessException, HuaweiPacket.ParseException {
+        byte[] raw = new byte[] {(byte) 0x5A, (byte) 0x00, (byte) 0x06, (byte) 0x00, (byte) 0x04, (byte) 0x01, (byte) 0x01, (byte) 0x01, (byte) 0x02, (byte) 0x99, (byte) 0x6B};
+
+        Field tlvField = HuaweiPacket.class.getDeclaredField("tlv");
+        tlvField.setAccessible(true);
+
+        HuaweiTLV expectedTlv = new HuaweiTLV().put(0x01, (byte) 0x02);
+
+        HuaweiPacket packet = new HuaweiPacket(null).parse(raw);
+        packet.parseTlv();
+
+        Assert.assertEquals(0x04, packet.serviceId);
+        Assert.assertEquals(0x01, packet.commandId);
+        Assert.assertEquals(expectedTlv, tlvField.get(packet));
+        Assert.assertTrue(packet.complete);
+        Assert.assertTrue(packet instanceof Calls.AnswerCallResponse);
+        Assert.assertEquals(Calls.AnswerCallResponse.Action.CALL_ACCEPT, ((Calls.AnswerCallResponse) packet).action);
+    }
+
+    @Test
+    public void testAnswerCallResponseReject() throws NoSuchFieldException, IllegalAccessException, HuaweiPacket.ParseException {
+        byte[] raw = new byte[] {(byte) 0x5A, (byte) 0x00, (byte) 0x06, (byte) 0x00, (byte) 0x04, (byte) 0x01, (byte) 0x01, (byte) 0x01, (byte) 0x01, (byte) 0xA9, (byte) 0x08};
+
+        Field tlvField = HuaweiPacket.class.getDeclaredField("tlv");
+        tlvField.setAccessible(true);
+
+        HuaweiTLV expectedTlv = new HuaweiTLV().put(0x01, (byte) 0x01);
+
+        HuaweiPacket packet = new HuaweiPacket(null).parse(raw);
+        packet.parseTlv();
+
+        Assert.assertEquals(0x04, packet.serviceId);
+        Assert.assertEquals(0x01, packet.commandId);
+        Assert.assertEquals(expectedTlv, tlvField.get(packet));
+        Assert.assertTrue(packet.complete);
+        Assert.assertTrue(packet instanceof Calls.AnswerCallResponse);
+        Assert.assertEquals(Calls.AnswerCallResponse.Action.CALL_REJECT, ((Calls.AnswerCallResponse) packet).action);
+    }
+}
diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/TestDeviceConfig.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/TestDeviceConfig.java
new file mode 100644
index 0000000..d8d48c1
--- /dev/null
+++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/TestDeviceConfig.java
@@ -0,0 +1,505 @@
+/*  Copyright (C) 2022 Gaignon Damien, Martin.JM
+    Copyright (C) 2022-2023 MartinJM
+
+    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 <http://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiCoordinatorSupplier;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiCrypto;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiTLV;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.lang.reflect.Field;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.List;
+import java.util.TimeZone;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.NoSuchPaddingException;
+
+public class TestDeviceConfig {
+
+    HuaweiPacket.ParamsProvider secretsProvider = new HuaweiPacket.ParamsProvider() {
+        @Override
+        public byte getDeviceSupportType() {
+            return 0;
+        }
+
+        @Override
+        public byte[] getSecretKey() {
+            return new byte[] {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+        }
+
+        @Override
+        public byte[] getIv() {
+            return new byte[] {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+        }
+
+        @Override
+        public boolean areTransactionsCrypted() {
+            return true;
+        }
+
+        @Override
+        public int getMtu() {
+            return 0;
+        }
+
+        @Override
+        public int getSliceSize() {
+            return 0xF4;
+        }
+    };
+
+    @Test
+    public void testLinkParamsRequest() throws NoSuchFieldException, IllegalAccessException, HuaweiPacket.CryptoException {
+        Field tlvField = HuaweiPacket.class.getDeclaredField("tlv");
+        tlvField.setAccessible(true);
+
+        HuaweiTLV expectedTlv = new HuaweiTLV()
+                .put(0x01)
+                .put(0x02)
+                .put(0x03)
+                .put(0x04);
+
+        byte[] serialized = new byte[] {(byte) 0x5a, (byte) 0x00, (byte) 0x0b, (byte) 0x00, (byte) 0x01, (byte) 0x01, (byte) 0x01, (byte) 0x00, (byte) 0x02, (byte) 0x00, (byte) 0x03, (byte) 0x00, (byte) 0x04, (byte) 0x00, (byte) 0xf1, (byte) 0x3b};
+        DeviceConfig.LinkParams.Request request = new DeviceConfig.LinkParams.Request(
+                secretsProvider, HuaweiCoordinatorSupplier.HuaweiDeviceType.BLE
+        );
+
+        Assert.assertEquals(0x01, request.serviceId);
+        Assert.assertEquals(0x01, request.commandId);
+        Assert.assertEquals(expectedTlv, tlvField.get(request));
+        Assert.assertTrue(request.complete);
+        List<byte[]> out = request.serialize();
+        Assert.assertEquals(1, out.size());
+        Assert.assertArrayEquals(serialized, out.get(0));
+    }
+
+    @Test
+    public void testLinkParamsResponse() throws NoSuchFieldException, IllegalAccessException, HuaweiPacket.ParseException {
+        byte[] rawLinkParams = new byte[] {(byte) 0x5a, (byte) 0x00, (byte) 0x1b, (byte) 0x00, (byte) 0x01, (byte) 0x01, (byte) 0x03, (byte) 0x02, (byte) 0x00, (byte) 0x14, (byte) 0x05, (byte) 0x12, (byte) 0x00, (byte) 0x01, (byte) 0x6a, (byte) 0xa2, (byte) 0x96, (byte) 0xe3, (byte) 0x76, (byte) 0x41, (byte) 0xb1, (byte) 0x0c, (byte) 0xf8, (byte) 0xaa, (byte) 0xf7, (byte) 0x47, (byte) 0x05, (byte) 0x5d, (byte) 0x0a, (byte) 0xa3, (byte) 0xe8, (byte) 0x9f};
+
+        byte[] expectedServerNonceWithAuth = new byte[] {(byte) 0x00, (byte) 0x01, (byte) 0x6A, (byte) 0xA2, (byte) 0x96, (byte) 0xE3, (byte) 0x76, (byte) 0x41, (byte) 0xB1, (byte) 0x0C, (byte) 0xF8, (byte) 0xAA, (byte) 0xF7, (byte) 0x47, (byte) 0x05, (byte) 0x5D, (byte) 0x0A, (byte) 0xA3};
+        byte[] expectedServerNonce = new byte[expectedServerNonceWithAuth.length - 2];
+        System.arraycopy(expectedServerNonceWithAuth, 2, expectedServerNonce, 0, expectedServerNonceWithAuth.length - 2);
+
+        Field tlvField = HuaweiPacket.class.getDeclaredField("tlv");
+        tlvField.setAccessible(true);
+
+        HuaweiTLV expectedTlv = new HuaweiTLV()
+                .put(0x03, new byte[] {0x00, 0x14})
+                .put(0x05, expectedServerNonceWithAuth);
+
+        HuaweiPacket packetLinkParams = new HuaweiPacket(secretsProvider).parse(rawLinkParams);
+        packetLinkParams.parseTlv();
+
+        Assert.assertEquals(0x01, packetLinkParams.serviceId);
+        Assert.assertEquals(0x01, packetLinkParams.commandId);
+        Assert.assertEquals(expectedTlv, tlvField.get(packetLinkParams));
+        Assert.assertTrue(packetLinkParams.complete);
+        Assert.assertTrue(packetLinkParams instanceof DeviceConfig.LinkParams.Response);
+        Assert.assertEquals(0x01, ((DeviceConfig.LinkParams.Response) packetLinkParams).authVersion);
+        Assert.assertArrayEquals(expectedServerNonce, ((DeviceConfig.LinkParams.Response) packetLinkParams).serverNonce);
+    }
+
+    @Test(expected=HuaweiPacket.ParseException.class)
+    public void testLinkParamsResponseException() throws NoSuchFieldException, IllegalAccessException, HuaweiPacket.ParseException {
+        byte[] rawLinkParams = new byte[] {(byte) 0x5a, (byte) 0x00, (byte) 0x17, (byte) 0x00, (byte) 0x01, (byte) 0x01, (byte) 0x01, (byte) 0x12, (byte) 0x00, (byte) 0x01, (byte) 0x6a, (byte) 0xa2, (byte) 0x96, (byte) 0xe3, (byte) 0x76, (byte) 0x41, (byte) 0xb1, (byte) 0x0c, (byte) 0xf8, (byte) 0xaa, (byte) 0xf7, (byte) 0x47, (byte) 0x05, (byte) 0x5d, (byte) 0x0a, (byte) 0xa3, (byte) 0xdd, (byte) 0x41};
+
+        HuaweiPacket packetLinkParams = new HuaweiPacket(secretsProvider).parse(rawLinkParams);
+        packetLinkParams.parseTlv();
+    }
+
+    @Test
+    public void testSupportedServicesRequest() throws NoSuchFieldException, IllegalAccessException, HuaweiPacket.CryptoException {
+        byte[] allSupportedServices = new byte[] {(byte) 0x01, (byte) 0x02, (byte) 0x03, (byte) 0x04, (byte) 0x05, (byte) 0x06, (byte) 0x07, (byte) 0x08, (byte) 0x09, (byte) 0x0a, (byte) 0x0b, (byte) 0x0c, (byte) 0x0d, (byte) 0x0e, (byte) 0x0f, (byte) 0x10, (byte) 0x11, (byte) 0x12, (byte) 0x13, (byte) 0x14, (byte) 0x15, (byte) 0x16, (byte) 0x17, (byte) 0x18, (byte) 0x19, (byte) 0x1a, (byte) 0x1b, (byte) 0x1c, (byte) 0x1d, (byte) 0x1e, (byte) 0x1f, (byte) 0x20, (byte) 0x21, (byte) 0x22, (byte) 0x23, (byte) 0x24, (byte) 0x25, (byte) 0x26, (byte) 0x27, (byte) 0x28, (byte) 0x29, (byte) 0x2a, (byte) 0x2b, (byte) 0x2c, (byte) 0x2d, (byte) 0x2e, (byte) 0x2f, (byte) 0x30, (byte) 0x31, (byte) 0x32, (byte) 0x33, (byte) 0x34, (byte) 0x35};
+
+        Field tlvField = HuaweiPacket.class.getDeclaredField("tlv");
+        tlvField.setAccessible(true);
+
+        HuaweiTLV expectedTlv = new HuaweiTLV()
+                .put(0x01, allSupportedServices);
+
+        byte[] serialized = new byte[] {(byte) 0x5a, (byte) 0x00, (byte) 0x5a, (byte) 0x00, (byte) 0x01, (byte) 0x02, (byte) 0x7c, (byte) 0x01, (byte) 0x01, (byte) 0x7d, (byte) 0x10, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x7e, (byte) 0x40, (byte) 0x14, (byte) 0xb1, (byte) 0x75, (byte) 0x7d, (byte) 0xc0, (byte) 0xa5, (byte) 0x32, (byte) 0xeb, (byte) 0xc1, (byte) 0x20, (byte) 0x7b, (byte) 0xb8, (byte) 0x59, (byte) 0xdb, (byte) 0xdb, (byte) 0xfe, (byte) 0x5b, (byte) 0x01, (byte) 0x0a, (byte) 0x7d, (byte) 0xb7, (byte) 0x76, (byte) 0xfc, (byte) 0xcc, (byte) 0x5f, (byte) 0x22, (byte) 0xff, (byte) 0x13, (byte) 0xcb, (byte) 0xbb, (byte) 0x4f, (byte) 0xe2, (byte) 0xcd, (byte) 0x6e, (byte) 0x4b, (byte) 0xd7, (byte) 0x7c, (byte) 0x05, (byte) 0x24, (byte) 0x85, (byte) 0x65, (byte) 0x5f, (byte) 0x95, (byte) 0x32, (byte) 0xb4, (byte) 0x5e, (byte) 0x16, (byte) 0xef, (byte) 0xad, (byte) 0x62, (byte) 0x38, (byte) 0xd5, (byte) 0x88, (byte) 0x63, (byte) 0xa4, (byte) 0xb0, (byte) 0x29, (byte) 0xbb, (byte) 0x90, (byte) 0x66, (byte) 0x8c, (byte) 0x3f, (byte) 0x58, (byte) 0x69, (byte) 0x40, (byte) 0x22};
+        DeviceConfig.SupportedServices.Request request = new DeviceConfig.SupportedServices.Request(
+                secretsProvider,
+                allSupportedServices
+        );
+
+        Assert.assertEquals(0x01, request.serviceId);
+        Assert.assertEquals(0x02, request.commandId);
+        Assert.assertEquals(expectedTlv, tlvField.get(request));
+        Assert.assertTrue(request.complete);
+        List<byte[]> out = request.serialize();
+        Assert.assertEquals(1, out.size());
+        Assert.assertArrayEquals(serialized, out.get(0));
+    }
+
+    @Test
+    public void testSupportedServicesResponse() throws NoSuchFieldException, IllegalAccessException, HuaweiPacket.ParseException {
+        byte[] rawSupportedServices = new byte[] {(byte) 0x5a, (byte) 0x00, (byte) 0x5a, (byte) 0x00, (byte) 0x01, (byte) 0x02, (byte) 0x7c, (byte) 0x01, (byte) 0x01, (byte) 0x7d, (byte) 0x10, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x7e, (byte) 0x40, (byte) 0xC8, (byte) 0x9F, (byte) 0x1E, (byte) 0x2F, (byte) 0xE8, (byte) 0x31, (byte) 0xC8, (byte) 0x1E, (byte) 0x92, (byte) 0xB0, (byte) 0xE8, (byte) 0x9E, (byte) 0xC7, (byte) 0x2E, (byte) 0x76, (byte) 0xD7, (byte) 0x6C, (byte) 0x64, (byte) 0x22, (byte) 0x5A, (byte) 0x6C, (byte) 0xF9, (byte) 0xAF, (byte) 0xFB, (byte) 0x8E, (byte) 0x98, (byte) 0x74, (byte) 0xB6, (byte) 0xF9, (byte) 0x84, (byte) 0x3C, (byte) 0x1E, (byte) 0x3D, (byte) 0xCB, (byte) 0x7C, (byte) 0x23, (byte) 0x4F, (byte) 0x7B, (byte) 0x34, (byte) 0x0C, (byte) 0x49, (byte) 0xBD, (byte) 0x80, (byte) 0x94, (byte) 0x67, (byte) 0x1B, (byte) 0x5C, (byte) 0x64, (byte) 0x6B, (byte) 0xA4, (byte) 0xB9, (byte) 0xEC, (byte) 0xA7, (byte) 0x97, (byte) 0x95, (byte) 0x6F, (byte) 0x44, (byte) 0x13, (byte) 0x66, (byte) 0x7C, (byte) 0xF5, (byte) 0x9F, (byte) 0x05, (byte) 0x72, (byte) 0xc9, (byte) 0xe9};
+        byte[] expectedSupportedServices = new byte[] {(byte) 0x01, (byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x01, (byte) 0x01, (byte) 0x01, (byte) 0x01, (byte) 0x01, (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01, (byte) 0x01, (byte) 0x01, (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01, (byte) 0x01, (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00};
+
+        Field tlvField = HuaweiPacket.class.getDeclaredField("tlv");
+        tlvField.setAccessible(true);
+
+        HuaweiTLV expectedTlv = new HuaweiTLV()
+                .put(0x02, expectedSupportedServices);
+
+        HuaweiPacket packetSupportedServices = new HuaweiPacket(secretsProvider).parse(rawSupportedServices);
+        packetSupportedServices.parseTlv();
+
+        Assert.assertEquals(0x01, packetSupportedServices.serviceId);
+        Assert.assertEquals(0x02, packetSupportedServices.commandId);
+        Assert.assertEquals(expectedTlv, tlvField.get(packetSupportedServices));
+        Assert.assertTrue(packetSupportedServices.complete);
+        Assert.assertTrue(packetSupportedServices instanceof DeviceConfig.SupportedServices.Response);
+        Assert.assertArrayEquals(expectedSupportedServices, ((DeviceConfig.SupportedServices.Response) packetSupportedServices).supportedServices);
+    }
+
+    @Test(expected=HuaweiPacket.ParseException.class)
+    public void testSupportedServicesResponseException() throws NoSuchFieldException, IllegalAccessException, HuaweiPacket.ParseException {
+        byte[] rawSupportedServices = new byte[] {(byte) 0x5A, (byte) 0x00, (byte) 0x5A, (byte) 0x00, (byte) 0x01, (byte) 0x02, (byte) 0x7C, (byte) 0x01, (byte) 0x01, (byte) 0x7D, (byte) 0x10, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x7E, (byte) 0x40, (byte) 0x28, (byte) 0xB0, (byte) 0x4D, (byte) 0x00, (byte) 0xCF, (byte) 0xE1, (byte) 0xD1, (byte) 0x7A, (byte) 0x5D, (byte) 0xE7, (byte) 0x61, (byte) 0x0E, (byte) 0xE1, (byte) 0xA5, (byte) 0xE8, (byte) 0xF9, (byte) 0x6D, (byte) 0x2D, (byte) 0x32, (byte) 0xB3, (byte) 0xC3, (byte) 0x7C, (byte) 0x07, (byte) 0xBC, (byte) 0x11, (byte) 0x03, (byte) 0x8A, (byte) 0x66, (byte) 0x8C, (byte) 0x47, (byte) 0x94, (byte) 0x86, (byte) 0x8C, (byte) 0x0D, (byte) 0xC6, (byte) 0xBC, (byte) 0xDF, (byte) 0xB3, (byte) 0x00, (byte) 0xFB, (byte) 0x68, (byte) 0x11, (byte) 0xC1, (byte) 0xB3, (byte) 0x66, (byte) 0x6D, (byte) 0x85, (byte) 0x6F, (byte) 0xF0, (byte) 0xA9, (byte) 0xD0, (byte) 0x49, (byte) 0xDF, (byte) 0xF5, (byte) 0x82, (byte) 0x01, (byte) 0x9F, (byte) 0xE4, (byte) 0x60, (byte) 0x36, (byte) 0x81, (byte) 0xAA, (byte) 0x31, (byte) 0xA1, (byte) 0x39, (byte) 0xD6};
+
+        HuaweiPacket packetSupportedServices = new HuaweiPacket(secretsProvider).parse(rawSupportedServices);
+        packetSupportedServices.parseTlv();
+    }
+
+    @Test
+    public void testSupportedCommandsRequest() throws NoSuchFieldException, IllegalAccessException, HuaweiPacket.CryptoException {
+        byte service1 = (byte) 0x01;
+        byte[] commands1 = new byte[]{(byte) 0x04, (byte) 0x07, (byte) 0x08, (byte) 0x09, (byte) 0x0A, (byte) 0x0D, (byte) 0x0E, (byte) 0x10, (byte) 0x11, (byte) 0x12, (byte) 0x13, (byte) 0x14, (byte) 0x1B, (byte) 0x1A, (byte) 0x1D, (byte) 0x21, (byte) 0x22, (byte) 0x23, (byte) 0x24, (byte) 0x29, (byte) 0x2A, (byte) 0x2B, (byte) 0x32, (byte) 0x2E, (byte) 0x31, (byte) 0x30, (byte) 0x35, (byte) 0x36, (byte) 0x37, (byte) 0x2F};
+        byte service2 = (byte) 0x02;
+        byte[] commands2 = new byte[]{(byte) 0x01, (byte) 0x04, (byte) 0x05, (byte) 0x06, (byte) 0x07, (byte) 0x08};
+        byte service3 = (byte) 0x04;
+        byte[] commands3 = new byte[]{(byte) 0x01};
+
+        Field tlvField = HuaweiPacket.class.getDeclaredField("tlv");
+        tlvField.setAccessible(true);
+
+        HuaweiTLV expectedTlv = new HuaweiTLV()
+                .put(0x81, new HuaweiTLV()
+                    .put(0x02, service1)
+                    .put(0x03, commands1)
+                    .put(0x02, service2)
+                    .put(0x03, commands2)
+                    .put(0x02, service3)
+                    .put(0x03, commands3)
+                );
+
+        byte[] expectedSerialized = new byte[] {(byte) 0x5A, (byte) 0x00, (byte) 0x5A, (byte) 0x00, (byte) 0x01, (byte) 0x03, (byte) 0x7C, (byte) 0x01, (byte) 0x01, (byte) 0x7D, (byte) 0x10, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x7E, (byte) 0x40, (byte) 0x81, (byte) 0xED, (byte) 0x07, (byte) 0xF6, (byte) 0x51, (byte) 0xA3, (byte) 0x19, (byte) 0xBD, (byte) 0xCE, (byte) 0x35, (byte) 0x13, (byte) 0x23, (byte) 0x1E, (byte) 0xFC, (byte) 0x1A, (byte) 0x51, (byte) 0x92, (byte) 0xBB, (byte) 0x43, (byte) 0xC5, (byte) 0xF5, (byte) 0xD9, (byte) 0x4E, (byte) 0xCC, (byte) 0x2F, (byte) 0xE0, (byte) 0xDB, (byte) 0xB1, (byte) 0x5E, (byte) 0x78, (byte) 0x66, (byte) 0x69, (byte) 0x61, (byte) 0x85, (byte) 0x46, (byte) 0xB2, (byte) 0x50, (byte) 0xEC, (byte) 0xB5, (byte) 0x3F, (byte) 0x74, (byte) 0x68, (byte) 0x47, (byte) 0x03, (byte) 0x87, (byte) 0xC1, (byte) 0xB3, (byte) 0x53, (byte) 0x7B, (byte) 0x53, (byte) 0xDB, (byte) 0xE8, (byte) 0x5E, (byte) 0x82, (byte) 0x56, (byte) 0xFD, (byte) 0x16, (byte) 0x66, (byte) 0x03, (byte) 0xB2, (byte) 0x56, (byte) 0xA3, (byte) 0x14, (byte) 0x70, (byte) 0x38, (byte) 0x3E};
+        DeviceConfig.SupportedCommands.Request request = new DeviceConfig.SupportedCommands.Request(
+                secretsProvider
+        );
+        request.addCommandsForService(service1, commands1);
+        request.addCommandsForService(service2, commands2);
+        request.addCommandsForService(service3, commands3);
+
+        List<byte[]> out = request.serialize();
+
+        Assert.assertEquals(0x01, request.serviceId);
+        Assert.assertEquals(0x03, request.commandId);
+        Assert.assertEquals(expectedTlv, tlvField.get(request));
+        Assert.assertTrue(request.complete);
+        Assert.assertEquals(1, out.size());
+        Assert.assertArrayEquals(expectedSerialized, out.get(0));
+    }
+
+    @Test
+    public void testSupportedCommandsResponse() throws NoSuchFieldException, IllegalAccessException, HuaweiPacket.ParseException {
+        byte[] rawSupportedCommands = new byte[] {(byte) 0x5A, (byte) 0x00, (byte) 0x5A, (byte) 0x00, (byte) 0x01, (byte) 0x03, (byte) 0x7C, (byte) 0x01, (byte) 0x01, (byte) 0x7D, (byte) 0x10, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x7E, (byte) 0x40, (byte) 0x8B, (byte) 0x86, (byte) 0x29, (byte) 0xE3, (byte) 0x90, (byte) 0x25, (byte) 0x4E, (byte) 0x14, (byte) 0xE1, (byte) 0xDD, (byte) 0x96, (byte) 0x63, (byte) 0x66, (byte) 0xB8, (byte) 0x1E, (byte) 0x4A, (byte) 0xC1, (byte) 0xA7, (byte) 0x49, (byte) 0xB0, (byte) 0x9F, (byte) 0x21, (byte) 0x7C, (byte) 0xE8, (byte) 0x2C, (byte) 0x72, (byte) 0x93, (byte) 0x9F, (byte) 0xAC, (byte) 0x37, (byte) 0x3B, (byte) 0x4D, (byte) 0x1A, (byte) 0xCB, (byte) 0xC2, (byte) 0xFF, (byte) 0x64, (byte) 0xE5, (byte) 0xF0, (byte) 0x3E, (byte) 0x5B, (byte) 0xFF, (byte) 0xB1, (byte) 0x9C, (byte) 0x59, (byte) 0xB2, (byte) 0xF1, (byte) 0xD6, (byte) 0x4B, (byte) 0x2B, (byte) 0x99, (byte) 0xFB, (byte) 0xEA, (byte) 0x29, (byte) 0x66, (byte) 0xD3, (byte) 0x90, (byte) 0x0B, (byte) 0xC9, (byte) 0xF0, (byte) 0xB4, (byte) 0x9B, (byte) 0x3B, (byte) 0x3E, (byte) 0x50, (byte) 0xFA};
+
+        List<DeviceConfig.SupportedCommands.Response.CommandsList> expectedSupportedCommandsList = new ArrayList<>();
+        DeviceConfig.SupportedCommands.Response.CommandsList commandsList = new DeviceConfig.SupportedCommands.Response.CommandsList();
+        commandsList.service = 1;
+        commandsList.commands = new byte[] {(byte) 0x01, (byte) 0x02, (byte) 0x03, (byte) 0x04, (byte) 0x05, (byte) 0x06, (byte) 0x07, (byte) 0x09, (byte) 0x0A, (byte) 0x0B, (byte) 0x0D, (byte) 0x0F, (byte) 0x10, (byte) 0x11};
+        expectedSupportedCommandsList.add(commandsList);
+        commandsList.service = 2;
+        commandsList.commands = new byte[] {(byte) 0x01, (byte) 0x02, (byte) 0x03, (byte) 0x04, (byte) 0x06};
+        expectedSupportedCommandsList.add(commandsList);
+        commandsList.service = 4;
+        commandsList.commands = new byte[] {(byte) 0x01};
+        expectedSupportedCommandsList.add(commandsList);
+
+        byte[] expectedSupportedCommands = new byte[] {(byte) 0x02, (byte) 0x01, (byte) 0x01, (byte) 0x04, (byte) 0x1E, (byte) 0x01, (byte) 0x01, (byte) 0x01, (byte) 0x01, (byte) 0x01, (byte) 0x01, (byte) 0x01, (byte) 0x00, (byte) 0x01, (byte) 0x01, (byte) 0x01, (byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0x01, (byte) 0x01, (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x02, (byte) 0x01, (byte) 0x02, (byte) 0x04, (byte) 0x06, (byte) 0x01, (byte) 0x01, (byte) 0x01, (byte) 0x01, (byte) 0x00, (byte) 0x01, (byte) 0x02, (byte) 0x01, (byte) 0x04, (byte) 0x04, (byte) 0x01, (byte) 0x01};
+
+        Field tlvField = HuaweiPacket.class.getDeclaredField("tlv");
+        tlvField.setAccessible(true);
+
+        HuaweiTLV expectedTlv = new HuaweiTLV()
+                .put(0x81, expectedSupportedCommands);
+
+        HuaweiPacket packetSupportedCommands = new HuaweiPacket(secretsProvider).parse(rawSupportedCommands);
+        packetSupportedCommands.parseTlv();
+
+        Assert.assertEquals(0x01, packetSupportedCommands.serviceId);
+        Assert.assertEquals(0x03, packetSupportedCommands.commandId);
+        Assert.assertEquals(expectedTlv, tlvField.get(packetSupportedCommands));
+        Assert.assertTrue(packetSupportedCommands.complete);
+        Assert.assertTrue(packetSupportedCommands instanceof DeviceConfig.SupportedCommands.Response);
+        Assert.assertEquals(expectedSupportedCommandsList.size(), ((DeviceConfig.SupportedCommands.Response) packetSupportedCommands).commandsLists.size());
+    }
+
+    @Test(expected=HuaweiPacket.ParseException.class)
+    public void testSupportedCommandsResponseException02() throws NoSuchFieldException, IllegalAccessException, HuaweiPacket.ParseException {
+        byte[] rawSupportedCommands = new byte[] {(byte) 0x5A, (byte) 0x00, (byte) 0x4A, (byte) 0x00, (byte) 0x01, (byte) 0x03, (byte) 0x7C, (byte) 0x01, (byte) 0x01, (byte) 0x7D, (byte) 0x10, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x7E, (byte) 0x30, (byte) 0x57, (byte) 0xC7, (byte) 0xF7, (byte) 0x47, (byte) 0x0E, (byte) 0xA9, (byte) 0xA2, (byte) 0x9E, (byte) 0xF3, (byte) 0xB0, (byte) 0xBD, (byte) 0x02, (byte) 0xE0, (byte) 0x79, (byte) 0x3C, (byte) 0x12, (byte) 0xE6, (byte) 0x58, (byte) 0xDA, (byte) 0xF7, (byte) 0x0B, (byte) 0xC3, (byte) 0x93, (byte) 0x8D, (byte) 0x37, (byte) 0x2E, (byte) 0xA9, (byte) 0xB8, (byte) 0xF8, (byte) 0xF7, (byte) 0x97, (byte) 0xF3, (byte) 0x22, (byte) 0x08, (byte) 0xDF, (byte) 0xAD, (byte) 0x2B, (byte) 0x62, (byte) 0x33, (byte) 0x11, (byte) 0x93, (byte) 0x66, (byte) 0xD1, (byte) 0xAE, (byte) 0xF3, (byte) 0x02, (byte) 0x18, (byte) 0x49, (byte) 0xD0, (byte) 0x40};
+
+        HuaweiPacket packetSupportedCommands = new HuaweiPacket(secretsProvider).parse(rawSupportedCommands);
+        packetSupportedCommands.parseTlv();
+    }
+
+    @Test(expected=HuaweiPacket.ParseException.class)
+    public void testSupportedCommandsResponseException04() throws NoSuchFieldException, IllegalAccessException, HuaweiPacket.ParseException {
+        byte[] rawSupportedCommands = new byte[] {(byte) 0x5A, (byte) 0x00, (byte) 0x2A, (byte) 0x00, (byte) 0x01, (byte) 0x03, (byte) 0x7C, (byte) 0x01, (byte) 0x01, (byte) 0x7D, (byte) 0x10, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x7E, (byte) 0x10, (byte) 0x25, (byte) 0x26, (byte) 0xE0, (byte) 0x79, (byte) 0x1D, (byte) 0x19, (byte) 0x82, (byte) 0xDB, (byte) 0x0A, (byte) 0x3A, (byte) 0x21, (byte) 0x6E, (byte) 0x70, (byte) 0x52, (byte) 0xAB, (byte) 0xF3, (byte) 0x14, (byte) 0xB4};
+
+        HuaweiPacket packetSupportedCommands = new HuaweiPacket(secretsProvider).parse(rawSupportedCommands);
+        packetSupportedCommands.parseTlv();
+    }
+
+    @Test
+    public void testDateFormatRequest() throws NoSuchFieldException, IllegalAccessException, HuaweiPacket.CryptoException {
+        byte dateformat = (byte) 0x02;
+        byte timeFormat = (byte) 0x02;
+
+        Field tlvField = HuaweiPacket.class.getDeclaredField("tlv");
+        tlvField.setAccessible(true);
+
+        HuaweiTLV expectedTlv = new HuaweiTLV()
+                .put(0x81, new HuaweiTLV()
+                    .put(0x02, dateformat)
+                    .put(0x03, timeFormat)
+                );
+
+        byte[] serialized = new byte[] {(byte) 0x5a, (byte) 0x00, (byte) 0x2a, (byte) 0x00, (byte) 0x01, (byte) 0x04, (byte) 0x7c, (byte) 0x01, (byte) 0x01, (byte) 0x7d, (byte) 0x10, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x7e, (byte) 0x10, (byte) 0x63, (byte) 0xe0, (byte) 0xf1, (byte) 0xbf, (byte) 0x40, (byte) 0xab, (byte) 0x09, (byte) 0x63, (byte) 0x51, (byte) 0x7c, (byte) 0xa7, (byte) 0x8c, (byte) 0x2e, (byte) 0xd9, (byte) 0x6a, (byte) 0x6c, (byte) 0xdc, (byte) 0xe9};
+        DeviceConfig.DateFormat.Request request = new DeviceConfig.DateFormat.Request (
+                secretsProvider,
+                dateformat,
+                timeFormat
+        );
+
+        Assert.assertEquals(0x01, request.serviceId);
+        Assert.assertEquals(0x04, request.commandId);
+        Assert.assertEquals(expectedTlv, tlvField.get(request));
+        Assert.assertTrue(request.complete);
+        List<byte[]> out = request.serialize();
+        Assert.assertEquals(1, out.size());
+        Assert.assertArrayEquals(serialized, out.get(0));
+    }
+
+    @Test
+    public void testTimeRequest() throws NoSuchFieldException, IllegalAccessException, HuaweiPacket.CryptoException {
+        int timestamp = 1633987331;
+        short zoneOffset = (short) 512;
+
+        Calendar calendar = Calendar.getInstance();
+        calendar.setTimeInMillis(timestamp * 1000L);
+        calendar.setTimeZone(TimeZone.getTimeZone("GMT+2"));
+
+        Field tlvField = HuaweiPacket.class.getDeclaredField("tlv");
+        tlvField.setAccessible(true);
+
+        HuaweiTLV expectedTlv = new HuaweiTLV()
+                .put(0x01, timestamp)
+                .put(0x02, zoneOffset);
+
+        byte[] serialized = new byte[] {(byte) 0x5A, (byte) 0x00, (byte) 0x2A, (byte) 0x00, (byte) 0x01, (byte) 0x05, (byte) 0x7C, (byte) 0x01, (byte) 0x01, (byte) 0x7D, (byte) 0x10, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x7E, (byte) 0x10, (byte) 0xED, (byte) 0x67, (byte) 0x61, (byte) 0x8A, (byte) 0x8E, (byte) 0x44, (byte) 0x67, (byte) 0xB1, (byte) 0x2A, (byte) 0xB4, (byte) 0xFA, (byte) 0x86, (byte) 0x76, (byte) 0x17, (byte) 0x8C, (byte) 0x61, (byte) 0xFC, (byte) 0x99};
+        DeviceConfig.TimeRequest request = new DeviceConfig.TimeRequest(secretsProvider, calendar);
+
+        Assert.assertEquals(0x01, request.serviceId);
+        Assert.assertEquals(0x05, request.commandId);
+        Assert.assertEquals(expectedTlv, tlvField.get(request));
+        Assert.assertTrue(request.complete);
+        List<byte[]> out = request.serialize();
+        Assert.assertEquals(1, out.size());
+        Assert.assertArrayEquals(serialized, out.get(0));
+    }
+
+    @Test
+    public void testProductInformationRequest() throws NoSuchFieldException, IllegalAccessException, HuaweiPacket.CryptoException {
+        Field tlvField = HuaweiPacket.class.getDeclaredField("tlv");
+        tlvField.setAccessible(true);
+
+        HuaweiTLV expectedTlv = new HuaweiTLV();
+        byte[] expectedTags = {0x01, 0x02, 0x07, 0x09, 0x0A, 0x11, 0x12, 0x16, 0x1A, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23};
+        for (byte tag : expectedTags) {
+            expectedTlv.put(tag);
+        }
+
+        // Outdated
+        //byte[] serialized = new byte[] {(byte) 0x5A, (byte) 0x00, (byte) 0x3A, (byte) 0x00, (byte) 0x01, (byte) 0x07, (byte) 0x7C, (byte) 0x01, (byte) 0x01, (byte) 0x7D, (byte) 0x10, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x7E, (byte) 0x20, (byte) 0x10, (byte) 0x9B, (byte) 0x27, (byte) 0x5D, (byte) 0xB1, (byte) 0x3C, (byte) 0xFD, (byte) 0x40, (byte) 0x4B, (byte) 0xA8, (byte) 0xAC, (byte) 0xAF, (byte) 0x8A, (byte) 0xB6, (byte) 0xA5, (byte) 0x3D, (byte) 0x40, (byte) 0x30, (byte) 0x2C, (byte) 0x79, (byte) 0x98, (byte) 0x6D, (byte) 0xEC, (byte) 0xD1, (byte) 0x39, (byte) 0xE6, (byte) 0xFE, (byte) 0x5C, (byte) 0xE8, (byte) 0xB2, (byte) 0xF3, (byte) 0x9E, (byte) 0x3E, (byte) 0x1B};
+        DeviceConfig.ProductInfo.Request request = new DeviceConfig.ProductInfo.Request (
+                secretsProvider, HuaweiCoordinatorSupplier.HuaweiDeviceType.BLE
+        );
+
+        Assert.assertEquals(0x01, request.serviceId);
+        Assert.assertEquals(0x07, request.commandId);
+        Assert.assertEquals(expectedTlv, tlvField.get(request));
+        Assert.assertTrue(request.complete);
+        List<byte[]> out = request.serialize();
+        Assert.assertEquals(1, out.size());
+        // Assert.assertArrayEquals(serialized, out.get(0));
+    }
+
+    @Test
+    public void testProductInformationResponse() throws NoSuchFieldException, IllegalAccessException, HuaweiPacket.ParseException {
+        byte[] rawProductInformation = new byte[] {(byte) 0x5A, (byte) 0x00, (byte) 0x4A, (byte) 0x00, (byte) 0x01, (byte) 0x07, (byte) 0x7C, (byte) 0x01, (byte) 0x01, (byte) 0x7D, (byte) 0x10, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x7E, (byte) 0x30, (byte) 0x50, (byte) 0x75, (byte) 0xA5, (byte) 0x4F, (byte) 0x26, (byte) 0xF7, (byte) 0x74, (byte) 0x0B, (byte) 0xB2, (byte) 0xD8, (byte) 0x01, (byte) 0xBA, (byte) 0xDC, (byte) 0x7E, (byte) 0x40, (byte) 0x36, (byte) 0xD5, (byte) 0x6D, (byte) 0x4B, (byte) 0x7B, (byte) 0x8F, (byte) 0xC6, (byte) 0xFB, (byte) 0x48, (byte) 0xFC, (byte) 0x89, (byte) 0x54, (byte) 0xF8, (byte) 0xBB, (byte) 0xC0, (byte) 0x48, (byte) 0x9E, (byte) 0x34, (byte) 0x0E, (byte) 0xB1, (byte) 0x24, (byte) 0xD8, (byte) 0x89, (byte) 0x02, (byte) 0x7E, (byte) 0x6C, (byte) 0x3E, (byte) 0x81, (byte) 0x7D, (byte) 0x38, (byte) 0x0F, (byte) 0xD9, (byte) 0x2A, (byte) 0x98, (byte) 0xE3};
+        String softwareVersion = "1.0.10.78";
+        String productModel = "Crius";
+
+        Field tlvField = HuaweiPacket.class.getDeclaredField("tlv");
+        tlvField.setAccessible(true);
+
+        HuaweiTLV expectedTlv = new HuaweiTLV()
+                .put(0x03, new byte[] {(byte) 0x4E, (byte) 0x41})
+                .put(0x07, new byte[] {(byte) 0x31, (byte) 0x2E, (byte) 0x30, (byte) 0x2E, (byte) 0x31, (byte) 0x30, (byte) 0x2E, (byte) 0x37, (byte) 0x38})
+                .put(0x0A, new byte[] {(byte) 0x43, (byte) 0x72, (byte) 0x69, (byte) 0x75, (byte) 0x73, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00});
+
+        HuaweiPacket packetProductInformation = new HuaweiPacket(secretsProvider).parse(rawProductInformation);
+        packetProductInformation.parseTlv();
+
+        Assert.assertEquals(0x01, packetProductInformation.serviceId);
+        Assert.assertEquals(0x07, packetProductInformation.commandId);
+        Assert.assertEquals(expectedTlv, tlvField.get(packetProductInformation));
+        Assert.assertTrue(packetProductInformation.complete);
+        Assert.assertTrue(packetProductInformation instanceof DeviceConfig.ProductInfo.Response);
+        Assert.assertTrue(softwareVersion.equals(((DeviceConfig.ProductInfo.Response) packetProductInformation).softwareVersion));
+        System.out.println(((DeviceConfig.ProductInfo.Response) packetProductInformation).productModel);
+        System.out.println(((DeviceConfig.ProductInfo.Response) packetProductInformation).productModel.length());
+        Assert.assertTrue(productModel.equals(((DeviceConfig.ProductInfo.Response) packetProductInformation).productModel));
+    }
+
+    @Test(expected=HuaweiPacket.ParseException.class)
+    public void testProductInformationResponseException07() throws NoSuchFieldException, IllegalAccessException, HuaweiPacket.ParseException {
+        byte[] rawProductInformation = new byte[] {(byte) 0x5A, (byte) 0x00, (byte) 0x3A, (byte) 0x00, (byte) 0x01, (byte) 0x07, (byte) 0x7C, (byte) 0x01, (byte) 0x01, (byte) 0x7D, (byte) 0x10, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x7E, (byte) 0x20, (byte) 0xFE, (byte) 0x24, (byte) 0x4C, (byte) 0x68, (byte) 0xFC, (byte) 0x1D, (byte) 0xAD, (byte) 0x64, (byte) 0x77, (byte) 0xC9, (byte) 0xE9, (byte) 0x26, (byte) 0x8D, (byte) 0x3C, (byte) 0x3C, (byte) 0x8C, (byte) 0xB6, (byte) 0xA6, (byte) 0xF1, (byte) 0xBF, (byte) 0xAC, (byte) 0xB6, (byte) 0x7A, (byte) 0x75, (byte) 0xA3, (byte) 0xA9, (byte) 0x07, (byte) 0x5F, (byte) 0x39, (byte) 0x0F, (byte) 0x28, (byte) 0x61, (byte) 0x50, (byte) 0x61};
+
+        HuaweiPacket packetProductInformation = new HuaweiPacket(secretsProvider).parse(rawProductInformation);
+        packetProductInformation.parseTlv();
+    }
+
+    @Test(expected=HuaweiPacket.ParseException.class)
+    public void testProductInformationResponseException0A() throws NoSuchFieldException, IllegalAccessException, HuaweiPacket.ParseException {
+        byte[] rawProductInformation = new byte[] {(byte) 0x5A, (byte) 0x00, (byte) 0x2A, (byte) 0x00, (byte) 0x01, (byte) 0x07, (byte) 0x7C, (byte) 0x01, (byte) 0x01, (byte) 0x7D, (byte) 0x10, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x7E, (byte) 0x10, (byte) 0xB6, (byte) 0x67, (byte) 0xA5, (byte) 0x6A, (byte) 0x46, (byte) 0x0F, (byte) 0x08, (byte) 0x1E, (byte) 0xAC, (byte) 0x1E, (byte) 0x6B, (byte) 0xF2, (byte) 0x11, (byte) 0x4A, (byte) 0x54, (byte) 0x20, (byte) 0xCF, (byte) 0xB6};
+
+        HuaweiPacket packetProductInformation = new HuaweiPacket(secretsProvider).parse(rawProductInformation);
+        packetProductInformation.parseTlv();
+    }
+
+    @Test
+    public void testBondRequest() throws NoSuchFieldException, IllegalAccessException {
+        byte[] clientSerial = new byte[] {(byte) 0x54, (byte) 0x56, (byte) 0x64, (byte) 0x54, (byte) 0x4D, (byte) 0x44};
+        String mac = "FF:FF:FF:FF:FF:CC";
+        HuaweiCrypto huaweiCrypto = new HuaweiCrypto(0x01);
+        Field tlvField = HuaweiPacket.class.getDeclaredField("tlv");
+        tlvField.setAccessible(true);
+        try {
+            byte[] encryptionKey = huaweiCrypto.createSecretKey(mac);
+            byte[] iv = secretsProvider.getIv();
+            byte[] key = huaweiCrypto.encryptBondingKey(secretsProvider.getEncryptMethod(), secretsProvider.getSecretKey(), encryptionKey, iv);
+            HuaweiTLV expectedTlv = new HuaweiTLV()
+                    .put(0x01)
+                    .put(0x03, (byte) 0x00)
+                    .put(0x05, clientSerial)
+                    .put(0x06, key)
+                    .put(0x07, iv);
+
+            byte[] serialized = new byte[]{(byte) 0x5A, (byte) 0x00, (byte) 0x44, (byte) 0x00, (byte) 0x01, (byte) 0x0E, (byte) 0x01, (byte) 0x00, (byte) 0x03, (byte) 0x01, (byte) 0x00, (byte) 0x05, (byte) 0x06, (byte) 0x54, (byte) 0x56, (byte) 0x64, (byte) 0x54, (byte) 0x4D, (byte) 0x44, (byte) 0x06, (byte) 0x20, (byte) 0x88, (byte) 0x45, (byte) 0xAA, (byte) 0xB5, (byte) 0x9C, (byte) 0x84, (byte) 0x39, (byte) 0xAE, (byte) 0xD8, (byte) 0xE9, (byte) 0x71, (byte) 0x01, (byte) 0x5D, (byte) 0xC8, (byte) 0x34, (byte) 0x05, (byte) 0xC5, (byte) 0x9A, (byte) 0x6B, (byte) 0xDB, (byte) 0x62, (byte) 0x7D, (byte) 0xC8, (byte) 0xC3, (byte) 0xF4, (byte) 0xCC, (byte) 0x30, (byte) 0x74, (byte) 0x21, (byte) 0xD4, (byte) 0x45, (byte) 0x0E, (byte) 0x07, (byte) 0x10, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x72, (byte) 0xFC};
+            DeviceConfig.Bond.Request request = new DeviceConfig.Bond.Request(
+                    secretsProvider,
+                    clientSerial,
+                    key,
+                    iv
+            );
+
+            Assert.assertEquals(0x01, request.serviceId);
+            Assert.assertEquals(0x0E, request.commandId);
+            Assert.assertEquals(expectedTlv, tlvField.get(request));
+            Assert.assertTrue(request.complete);
+            List<byte[]> out = request.serialize();
+            Assert.assertEquals(1, out.size());
+            Assert.assertArrayEquals(serialized, out.get(0));
+        } catch (InvalidAlgorithmParameterException | NoSuchPaddingException | IllegalBlockSizeException | NoSuchAlgorithmException | BadPaddingException | InvalidKeyException | HuaweiPacket.CryptoException e) {
+            e.printStackTrace();
+            Assert.fail();
+        }
+    }
+
+    @Test
+    public void testBondParamsRequest() throws NoSuchFieldException, IllegalAccessException, HuaweiPacket.CryptoException {
+        byte[] clientSerial = new byte[] {(byte) 0x54, (byte) 0x56, (byte) 0x64, (byte) 0x54, (byte) 0x4D, (byte) 0x44};
+        byte[] mac = new byte[] {(byte) 0x46, (byte) 0x46, (byte) 0x3A, (byte) 0x46, (byte) 0x46, (byte) 0x3A, (byte) 0x46, (byte) 0x46, (byte) 0x3A, (byte) 0x46, (byte) 0x46, (byte) 0x3A, (byte) 0x46, (byte) 0x46, (byte) 0x3A, (byte) 0x43, (byte) 0x43};
+        Field tlvField = HuaweiPacket.class.getDeclaredField("tlv");
+        tlvField.setAccessible(true);
+
+        HuaweiTLV expectedTlv = new HuaweiTLV()
+                .put(0x01)
+                .put(0x03, clientSerial)
+                .put(0x04, (byte) 0x02)
+                .put(0x05)
+                .put(0x07, mac)
+                .put(0x09);
+
+        byte[] serialized = new byte[] {(byte) 0x5A, (byte) 0x00, (byte) 0x27, (byte) 0x00, (byte) 0x01, (byte) 0x0F, (byte) 0x01, (byte) 0x00, (byte) 0x03, (byte) 0x06, (byte) 0x54, (byte) 0x56, (byte) 0x64, (byte) 0x54, (byte) 0x4D, (byte) 0x44, (byte) 0x04, (byte) 0x01, (byte) 0x02, (byte) 0x05, (byte) 0x00, (byte) 0x07, (byte) 0x11, (byte) 0x46, (byte) 0x46, (byte) 0x3A, (byte) 0x46, (byte) 0x46, (byte) 0x3A, (byte) 0x46, (byte) 0x46, (byte) 0x3A, (byte) 0x46, (byte) 0x46, (byte) 0x3A, (byte) 0x46, (byte) 0x46, (byte) 0x3A, (byte) 0x43, (byte) 0x43, (byte) 0x09, (byte) 0x00, (byte) 0xE5, (byte) 0xD8};
+        DeviceConfig.BondParams.Request request = new DeviceConfig.BondParams.Request(
+                secretsProvider,
+                clientSerial,
+                mac
+        );
+        Assert.assertEquals(0x01, request.serviceId);
+        Assert.assertEquals(0x0F, request.commandId);
+        Assert.assertEquals(expectedTlv, tlvField.get(request));
+        Assert.assertTrue(request.complete);
+        List<byte[]> out = request.serialize();
+        Assert.assertEquals(1, out.size());
+        Assert.assertArrayEquals(serialized, out.get(0));
+    }
+
+    @Test
+    public void testAuthRequest() throws NoSuchFieldException, IllegalAccessException, HuaweiPacket.CryptoException {
+        byte[] challenge = new byte[] {(byte) 0x9D, (byte) 0xF6, (byte) 0x52, (byte) 0x69, (byte) 0x06, (byte) 0x7B, (byte) 0xEB, (byte) 0x46, (byte) 0x94, (byte) 0xAD, (byte) 0x35, (byte) 0xE2, (byte) 0x88, (byte) 0xC3, (byte) 0x84, (byte) 0x24, (byte) 0xA2, (byte) 0x55, (byte) 0xD8, (byte) 0x0F, (byte) 0xA7, (byte) 0x68, (byte) 0x21, (byte) 0x9B, (byte) 0xA1, (byte) 0xC3, (byte) 0xDC, (byte) 0x09, (byte) 0x24, (byte) 0x81, (byte) 0x51, (byte) 0x61};
+        byte[] nonce = new byte[] {(byte) 0x00, (byte) 0x01, (byte) 0xBF, (byte) 0x1F, (byte) 0xEF, (byte) 0x9F, (byte) 0xF0, (byte) 0xFE, (byte) 0xEF, (byte) 0xEF, (byte) 0x9F, (byte) 0xEF, (byte) 0xF0, (byte) 0xEF, (byte) 0xF8, (byte) 0xFA, (byte) 0xEF, (byte) 0xF0};
+
+        Field tlvField = HuaweiPacket.class.getDeclaredField("tlv");
+        tlvField.setAccessible(true);
+
+        HuaweiTLV expectedTlv = new HuaweiTLV()
+                .put(0x01, challenge)
+                .put(0x02, nonce);
+
+        byte[] serialized = new byte[] {(byte) 0x5a, (byte) 0x00, (byte) 0x39, (byte) 0x00, (byte) 0x01, (byte) 0x13, (byte) 0x01, (byte) 0x20, (byte) 0x9d, (byte) 0xf6, (byte) 0x52, (byte) 0x69, (byte) 0x06, (byte) 0x7b, (byte) 0xeb, (byte) 0x46, (byte) 0x94, (byte) 0xad, (byte) 0x35, (byte) 0xe2, (byte) 0x88, (byte) 0xc3, (byte) 0x84, (byte) 0x24, (byte) 0xa2, (byte) 0x55, (byte) 0xd8, (byte) 0x0f, (byte) 0xa7, (byte) 0x68, (byte) 0x21, (byte) 0x9b, (byte) 0xa1, (byte) 0xc3, (byte) 0xdc, (byte) 0x09, (byte) 0x24, (byte) 0x81, (byte) 0x51, (byte) 0x61, (byte) 0x02, (byte) 0x12, (byte) 0x00, (byte) 0x01, (byte) 0xbf, (byte) 0x1f, (byte) 0xef, (byte) 0x9f, (byte) 0xf0, (byte) 0xfe, (byte) 0xef, (byte) 0xef, (byte) 0x9f, (byte) 0xef, (byte) 0xf0, (byte) 0xef, (byte) 0xf8, (byte) 0xfa, (byte) 0xef, (byte) 0xf0, (byte) 0xdc, (byte) 0x88};
+        DeviceConfig.Auth.Request request = new DeviceConfig.Auth.Request(
+                secretsProvider,
+                challenge,
+                nonce
+        );
+
+        Assert.assertEquals(0x01, request.serviceId);
+        Assert.assertEquals(0x13, request.commandId);
+        Assert.assertEquals(expectedTlv, tlvField.get(request));
+        Assert.assertTrue(request.complete);
+        List<byte[]> out = request.serialize();
+        Assert.assertEquals(1, out.size());
+        Assert.assertArrayEquals(serialized, out.get(0));
+    }
+}
diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/TestDisconnectNotification.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/TestDisconnectNotification.java
new file mode 100644
index 0000000..9db8562
--- /dev/null
+++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/TestDisconnectNotification.java
@@ -0,0 +1,104 @@
+/*  Copyright (C) 2022 Martin.JM
+
+    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 <http://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.lang.reflect.Field;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiTLV;
+
+public class TestDisconnectNotification {
+
+    HuaweiPacket.ParamsProvider secretsProvider = new HuaweiPacket.ParamsProvider() {
+        @Override
+        public byte getDeviceSupportType() {
+            return 0;
+        }
+
+        @Override
+        public byte[] getSecretKey() {
+            return new byte[] {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+        }
+
+        @Override
+        public byte[] getIv() {
+            return new byte[] {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+        }
+
+        @Override
+        public boolean areTransactionsCrypted() {
+            return true;
+        }
+
+        @Override
+        public int getMtu() {
+            return 0;
+        }
+
+        @Override
+        public int getSliceSize() {
+            return 0xF4;
+        }
+    };
+
+    @Test
+    public void testBluetoothDisconnectNotificationSetting() throws NoSuchFieldException, IllegalAccessException, HuaweiPacket.CryptoException {
+        Field tlvField = HuaweiPacket.class.getDeclaredField("tlv");
+        tlvField.setAccessible(true);
+
+        HuaweiTLV expectedTlvEnable = new HuaweiTLV()
+                .put(0x01, true);
+
+        byte[] serializedEnable = new byte[] {(byte) 0x5a, (byte) 0x00, (byte) 0x2a, (byte) 0x00, (byte) 0x0b, (byte) 0x03, (byte) 0x7c, (byte) 0x01, (byte) 0x01, (byte) 0x7d, (byte) 0x10, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x7e, (byte) 0x10, (byte) 0xcd, (byte) 0x97, (byte) 0x7e, (byte) 0x01, (byte) 0x48, (byte) 0x34, (byte) 0x2a, (byte) 0x48, (byte) 0x58, (byte) 0x0d, (byte) 0x30, (byte) 0xc7, (byte) 0xbc, (byte) 0x2e, (byte) 0x40, (byte) 0xd4, (byte) 0x20, (byte) 0xaf};
+
+        DisconnectNotification.DisconnectNotificationSetting.Request requestEnable = new DisconnectNotification.DisconnectNotificationSetting.Request(
+                secretsProvider,
+                true
+        );
+
+        Assert.assertEquals(0x0b, requestEnable.serviceId);
+        Assert.assertEquals(0x03, requestEnable.commandId);
+        Assert.assertEquals(expectedTlvEnable, tlvField.get(requestEnable));
+        Assert.assertTrue(requestEnable.complete);
+        List<byte[]> outEnable = requestEnable.serialize();
+        Assert.assertEquals(1, outEnable.size());
+        Assert.assertArrayEquals(serializedEnable, outEnable.get(0));
+
+
+        HuaweiTLV expectedTlvDisable = new HuaweiTLV()
+                .put(0x01, false);
+
+        byte[] serializedDisable = new byte[] {(byte) 0x5a, (byte) 0x00, (byte) 0x2a, (byte) 0x00, (byte) 0x0b, (byte) 0x03, (byte) 0x7c, (byte) 0x01, (byte) 0x01, (byte) 0x7d, (byte) 0x10, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x7e, (byte) 0x10, (byte) 0x28, (byte) 0x00, (byte) 0x99, (byte) 0x6f, (byte) 0x2a, (byte) 0xcb, (byte) 0x62, (byte) 0x3a, (byte) 0xe6, (byte) 0x54, (byte) 0x28, (byte) 0x54, (byte) 0xf8, (byte) 0xab, (byte) 0x54, (byte) 0x83, (byte) 0x39, (byte) 0x9D};
+
+        DisconnectNotification.DisconnectNotificationSetting.Request requestDisable = new DisconnectNotification.DisconnectNotificationSetting.Request(
+                secretsProvider,
+                false
+        );
+
+        Assert.assertEquals(0x0b, requestDisable.serviceId);
+        Assert.assertEquals(0x03, requestDisable.commandId);
+        Assert.assertEquals(expectedTlvDisable, tlvField.get(requestDisable));
+        Assert.assertTrue(requestDisable.complete);
+        List<byte[]> outDisable = requestDisable.serialize();
+        Assert.assertEquals(1, outDisable.size());
+        Assert.assertArrayEquals(serializedDisable, outDisable.get(0));
+    }
+}
diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/TestFindPhone.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/TestFindPhone.java
new file mode 100644
index 0000000..7204f20
--- /dev/null
+++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/TestFindPhone.java
@@ -0,0 +1,120 @@
+/*  Copyright (C) 2022 Martin.JM
+
+    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 <http://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.lang.reflect.Field;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiTLV;
+
+public class TestFindPhone {
+
+    HuaweiPacket.ParamsProvider secretsProvider = new HuaweiPacket.ParamsProvider() {
+        @Override
+        public byte getDeviceSupportType() {
+            return 0;
+        }
+
+        @Override
+        public byte[] getSecretKey() {
+            return new byte[] {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+        }
+
+        @Override
+        public byte[] getIv() {
+            return new byte[] {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+        }
+
+        @Override
+        public boolean areTransactionsCrypted() {
+            return true;
+        }
+
+        @Override
+        public int getMtu() {
+            return 0;
+        }
+
+        @Override
+        public int getSliceSize() {
+            return 0xF4;
+        }
+    };
+
+    @Test
+    public void testStartFindPhone() throws NoSuchFieldException, IllegalAccessException, HuaweiPacket.ParseException {
+        byte[] raw = new byte[] {(byte) 0x5a, (byte) 0x00, (byte) 0x06, (byte) 0x00, (byte) 0x0b, (byte) 0x01, (byte) 0x01, (byte) 0x01, (byte) 0x01, (byte) 0xcc, (byte) 0xf1};
+
+        Field tlvField = HuaweiPacket.class.getDeclaredField("tlv");
+        tlvField.setAccessible(true);
+
+        HuaweiTLV expectedTlv = new HuaweiTLV().put(0x01, true);
+
+        HuaweiPacket packet = new HuaweiPacket(secretsProvider).parse(raw);
+        packet.parseTlv();
+
+        Assert.assertEquals(0x0b, packet.serviceId);
+        Assert.assertEquals(0x01, packet.commandId);
+        Assert.assertEquals(expectedTlv, tlvField.get(packet));
+        Assert.assertTrue(packet.complete);
+        Assert.assertTrue(packet instanceof FindPhone.Response);
+        Assert.assertTrue(((FindPhone.Response) packet).start);
+    }
+
+    @Test
+    public void testStopFindPhone() throws NoSuchFieldException, IllegalAccessException, HuaweiPacket.ParseException {
+        byte[] raw = new byte[] {(byte) 0x5a, (byte) 0x00, (byte) 0x06, (byte) 0x00, (byte) 0x0b, (byte) 0x01, (byte) 0x01, (byte) 0x01, (byte) 0x00, (byte) 0xdc, (byte) 0xd0};
+
+        Field tlvField = HuaweiPacket.class.getDeclaredField("tlv");
+        tlvField.setAccessible(true);
+
+        HuaweiTLV expectedTlv = new HuaweiTLV().put(0x01, false);
+
+        HuaweiPacket packet = new HuaweiPacket(secretsProvider).parse(raw);
+        packet.parseTlv();
+
+        Assert.assertEquals(0x0b, packet.serviceId);
+        Assert.assertEquals(0x01, packet.commandId);
+        Assert.assertEquals(expectedTlv, tlvField.get(packet));
+        Assert.assertTrue(packet.complete);
+        Assert.assertTrue(packet instanceof FindPhone.Response);
+        Assert.assertFalse(((FindPhone.Response) packet).start);
+    }
+
+    @Test
+    public void testFindPhoneMissingTag() throws NoSuchFieldException, IllegalAccessException, HuaweiPacket.ParseException {
+        byte[] raw = new byte[] {(byte) 0x5a, (byte) 0x00, (byte) 0x03, (byte) 0x00, (byte) 0x0b, (byte) 0x01, (byte) 0xa1, (byte) 0x91};
+
+        Field tlvField = HuaweiPacket.class.getDeclaredField("tlv");
+        tlvField.setAccessible(true);
+
+        HuaweiTLV expectedTlv = new HuaweiTLV();
+
+        HuaweiPacket packet = new HuaweiPacket(secretsProvider).parse(raw);
+        packet.parseTlv();
+
+        Assert.assertEquals(0x0b, packet.serviceId);
+        Assert.assertEquals(0x01, packet.commandId);
+        Assert.assertEquals(expectedTlv, tlvField.get(packet));
+        Assert.assertTrue(packet.complete);
+        Assert.assertTrue(packet instanceof FindPhone.Response);
+        Assert.assertFalse(((FindPhone.Response) packet).start);
+    }
+}
diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/TestFitnessData.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/TestFitnessData.java
new file mode 100644
index 0000000..717944d
--- /dev/null
+++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/TestFitnessData.java
@@ -0,0 +1,502 @@
+/*  Copyright (C) 2022 Martin.JM
+
+    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 <http://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.lang.reflect.Field;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiCrypto;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiTLV;
+
+public class TestFitnessData {
+
+    HuaweiPacket.ParamsProvider secretsProvider = new HuaweiPacket.ParamsProvider() {
+        @Override
+        public byte getDeviceSupportType() {
+            return 0;
+        }
+
+        @Override
+        public byte[] getSecretKey() {
+            return new byte[] {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+        }
+
+        @Override
+        public byte[] getIv() {
+            return new byte[] {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+        }
+
+        @Override
+        public boolean areTransactionsCrypted() {
+            return true;
+        }
+
+        @Override
+        public int getMtu() {
+            return 0;
+        }
+
+        @Override
+        public int getSliceSize() {
+            return 0xF4;
+        }
+    };
+
+    @Test
+    public void testMessageCountRequest() throws NoSuchFieldException, IllegalAccessException, HuaweiPacket.CryptoException {
+        int startSleep = 0x00000000;
+        int endSleep = 0x01020304;
+        int startStep = 0x01020304;
+        int endStep = 0x10203040;
+
+        Field tlvField = HuaweiPacket.class.getDeclaredField("tlv");
+        tlvField.setAccessible(true);
+
+        HuaweiTLV expectedTlvSleep = new HuaweiTLV()
+                .put(0x81)
+                .put(0x03, startSleep)
+                .put(0x04, endSleep);
+        HuaweiTLV expectedTlvStep = new HuaweiTLV()
+                .put(0x81)
+                .put(0x03, startStep)
+                .put(0x04, endStep);
+
+        byte[] sleepSerialized = new byte[] {(byte) 0x5a, (byte) 0x00, (byte) 0x2a, (byte) 0x00, (byte) 0x07, (byte) 0x0c, (byte) 0x7c, (byte) 0x01, (byte) 0x01, (byte) 0x7d, (byte) 0x10, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x7e, (byte) 0x10, (byte) 0x32, (byte) 0x69, (byte) 0x7b, (byte) 0x51, (byte) 0x85, (byte) 0x20, (byte) 0x9b, (byte) 0x16, (byte) 0x6b, (byte) 0x93, (byte) 0x8a, (byte) 0x3d, (byte) 0xd5, (byte) 0x9a, (byte) 0xf9, (byte) 0x29, (byte) 0xdf, (byte) 0x07};
+        byte[] stepSerialized = new byte[] {(byte) 0x5a, (byte) 0x00, (byte) 0x2a, (byte) 0x00, (byte) 0x07, (byte) 0x0a, (byte) 0x7c, (byte) 0x01, (byte) 0x01, (byte) 0x7d, (byte) 0x10, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x7e, (byte) 0x10, (byte) 0x4d, (byte) 0x52, (byte) 0x79, (byte) 0x57, (byte) 0x49, (byte) 0x30, (byte) 0x75, (byte) 0xc6, (byte) 0x28, (byte) 0x5b, (byte) 0x79, (byte) 0xd5, (byte) 0xab, (byte) 0x89, (byte) 0x0d, (byte) 0x1e, (byte) 0xa9, (byte) 0xc9};
+
+        FitnessData.MessageCount.Request sleepRequest = new FitnessData.MessageCount.Request(secretsProvider, FitnessData.MessageCount.sleepId, startSleep, endSleep);
+        FitnessData.MessageCount.Request stepRequest = new FitnessData.MessageCount.Request(secretsProvider, FitnessData.MessageCount.stepId, startStep, endStep);
+
+        Assert.assertEquals(0x07, sleepRequest.serviceId);
+        Assert.assertEquals(0x0c, sleepRequest.commandId);
+        Assert.assertEquals(expectedTlvSleep, tlvField.get(sleepRequest));
+        Assert.assertTrue(sleepRequest.complete);
+        List<byte[]> outSleep = sleepRequest.serialize();
+        Assert.assertEquals(1, outSleep.size());
+        Assert.assertArrayEquals(sleepSerialized, outSleep.get(0));
+
+        Assert.assertEquals(0x07, stepRequest.serviceId);
+        Assert.assertEquals(0x0a, stepRequest.commandId);
+        Assert.assertEquals(expectedTlvStep, tlvField.get(stepRequest));
+        Assert.assertTrue(stepRequest.complete);
+        List<byte[]> outStep = stepRequest.serialize();
+        Assert.assertEquals(1, outStep.size());
+        Assert.assertArrayEquals(stepSerialized, outStep.get(0));
+    }
+
+    @Test
+    public void testMessageCountResponse() throws NoSuchFieldException, IllegalAccessException, HuaweiPacket.ParseException {
+        byte[] rawSleep = new byte[] {(byte) 0x5a, (byte) 0x00, (byte) 0x2a, (byte) 0x00, (byte) 0x07, (byte) 0x0c, (byte) 0x7c, (byte) 0x01, (byte) 0x01, (byte) 0x7d, (byte) 0x10, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x7e, (byte) 0x10, (byte) 0xf6, (byte) 0xfb, (byte) 0xc0, (byte) 0xb6, (byte) 0x4f, (byte) 0x9a, (byte) 0xfa, (byte) 0x77, (byte) 0x53, (byte) 0x28, (byte) 0x7d, (byte) 0x13, (byte) 0xca, (byte) 0x49, (byte) 0xda, (byte) 0xfd, (byte) 0x93, (byte) 0x09};
+        byte[] rawStep = new byte[] {(byte) 0x5a, (byte) 0x00, (byte) 0x2a, (byte) 0x00, (byte) 0x07, (byte) 0x0a, (byte) 0x7c, (byte) 0x01, (byte) 0x01, (byte) 0x7d, (byte) 0x10, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x7e, (byte) 0x10, (byte) 0xf6, (byte) 0xfb, (byte) 0xc0, (byte) 0xb6, (byte) 0x4f, (byte) 0x9a, (byte) 0xfa, (byte) 0x77, (byte) 0x53, (byte) 0x28, (byte) 0x7d, (byte) 0x13, (byte) 0xca, (byte) 0x49, (byte) 0xda, (byte) 0xfd, (byte) 0xd4, (byte) 0x93};
+
+        short expectedCount = 0x1337;
+
+        Field tlvField = HuaweiPacket.class.getDeclaredField("tlv");
+        tlvField.setAccessible(true);
+
+        HuaweiTLV expectedTlv = new HuaweiTLV().put(0x81, new HuaweiTLV().put(0x02, expectedCount));
+
+        HuaweiPacket packetSleep = new HuaweiPacket(secretsProvider).parse(rawSleep);
+        HuaweiPacket packetStep = new HuaweiPacket(secretsProvider).parse(rawStep);
+        packetSleep.parseTlv();
+        packetStep.parseTlv();
+
+        Assert.assertEquals(0x07, packetSleep.serviceId);
+        Assert.assertEquals(0x0c, packetSleep.commandId);
+        Assert.assertEquals(expectedTlv, tlvField.get(packetSleep));
+        Assert.assertTrue(packetSleep.complete);
+        Assert.assertTrue(packetSleep instanceof FitnessData.MessageCount.Response);
+        Assert.assertEquals(expectedCount, ((FitnessData.MessageCount.Response) packetSleep).count);
+
+        Assert.assertEquals(0x07, packetStep.serviceId);
+        Assert.assertEquals(0x0a, packetStep.commandId);
+        Assert.assertEquals(expectedTlv, tlvField.get(packetStep));
+        Assert.assertTrue(packetStep.complete);
+        Assert.assertTrue(packetStep instanceof FitnessData.MessageCount.Response);
+        Assert.assertEquals(expectedCount, ((FitnessData.MessageCount.Response) packetStep).count);
+    }
+
+    @Test
+    public void testMessageDataRequest() throws NoSuchFieldException, IllegalAccessException, HuaweiCrypto.CryptoException, HuaweiPacket.CryptoException {
+        short count = 0x1337;
+
+        Field tlvField = HuaweiPacket.class.getDeclaredField("tlv");
+        tlvField.setAccessible(true);
+
+        HuaweiTLV expectedTlv = new HuaweiTLV().put(0x81, new HuaweiTLV().put(0x02, count));
+        expectedTlv.encrypt(secretsProvider);
+
+        byte[] expectedSleep = new byte[] {(byte) 0x5a, (byte) 0x00, (byte) 0x2a, (byte) 0x00, (byte) 0x07, (byte) 0x0d, (byte) 0x7c, (byte) 0x01, (byte) 0x01, (byte) 0x7d, (byte) 0x10, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x7e, (byte) 0x10, (byte) 0xf6, (byte) 0xfb, (byte) 0xc0, (byte) 0xb6, (byte) 0x4f, (byte) 0x9a, (byte) 0xfa, (byte) 0x77, (byte) 0x53, (byte) 0x28, (byte) 0x7d, (byte) 0x13, (byte) 0xca, (byte) 0x49, (byte) 0xda, (byte) 0xfd, (byte) 0x7d, (byte) 0xad};
+        byte[] expectedStep = new byte[] {(byte) 0x5a, (byte) 0x00, (byte) 0x2a, (byte) 0x00, (byte) 0x07, (byte) 0x0b, (byte) 0x7c, (byte) 0x01, (byte) 0x01, (byte) 0x7d, (byte) 0x10, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x7e, (byte) 0x10, (byte) 0xf6, (byte) 0xfb, (byte) 0xc0, (byte) 0xb6, (byte) 0x4f, (byte) 0x9a, (byte) 0xfa, (byte) 0x77, (byte) 0x53, (byte) 0x28, (byte) 0x7d, (byte) 0x13, (byte) 0xca, (byte) 0x49, (byte) 0xda, (byte) 0xfd, (byte) 0x3a, (byte) 0x37};
+
+        FitnessData.MessageData.Request sleepRequest = new FitnessData.MessageData.Request(secretsProvider, FitnessData.MessageData.sleepId, count);
+        FitnessData.MessageData.Request stepRequest = new FitnessData.MessageData.Request(secretsProvider, FitnessData.MessageData.stepId, count);
+
+        Assert.assertEquals(0x07, sleepRequest.serviceId);
+        Assert.assertEquals(0x0d, sleepRequest.commandId);
+        Assert.assertEquals(expectedTlv, tlvField.get(sleepRequest));
+        Assert.assertTrue(sleepRequest.complete);
+        List<byte[]> outSleep = sleepRequest.serialize();
+        Assert.assertEquals(1, outSleep.size());
+        Assert.assertArrayEquals(expectedSleep, outSleep.get(0));
+
+        Assert.assertEquals(0x07, stepRequest.serviceId);
+        Assert.assertEquals(0x0b, stepRequest.commandId);
+        Assert.assertEquals(expectedTlv, tlvField.get(stepRequest));
+        Assert.assertTrue(stepRequest.complete);
+        List<byte[]> outStep = stepRequest.serialize();
+        Assert.assertEquals(1, outStep.size());
+        Assert.assertArrayEquals(expectedStep, outStep.get(0));
+    }
+
+    @Test
+    public void testMessageDataSleepResponse() throws NoSuchFieldException, IllegalAccessException, HuaweiPacket.ParseException {
+        byte[] raw = new byte[] {(byte) 0x5a, (byte) 0x00, (byte) 0x3a, (byte) 0x00, (byte) 0x07, (byte) 0x0d, (byte) 0x7c, (byte) 0x01, (byte) 0x01, (byte) 0x7d, (byte) 0x10, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x7e, (byte) 0x20, (byte) 0xa4, (byte) 0x9e, (byte) 0xd8, (byte) 0xd3, (byte) 0x7a, (byte) 0x0e, (byte) 0x51, (byte) 0x55, (byte) 0xc5, (byte) 0x48, (byte) 0x07, (byte) 0x99, (byte) 0xf5, (byte) 0x99, (byte) 0x48, (byte) 0x3e, (byte) 0x41, (byte) 0xed, (byte) 0x16, (byte) 0xf1, (byte) 0x52, (byte) 0xd2, (byte) 0x9f, (byte) 0x38, (byte) 0xe8, (byte) 0xb1, (byte) 0x83, (byte) 0xd6, (byte) 0xcb, (byte) 0x52, (byte) 0xb0, (byte) 0x9f, (byte) 0x48, (byte) 0x05};
+
+        Field tlvField = HuaweiPacket.class.getDeclaredField("tlv");
+        tlvField.setAccessible(true);
+
+        HuaweiTLV expectedTlv = new HuaweiTLV().put(0x81, new HuaweiTLV()
+                .put(0x02, (short) 0x1337)
+                .put(0x83, new HuaweiTLV()
+                        .put(0x04, (byte) 0x00)
+                        .put(0x05, new byte[] {})
+                )
+                .put(0x83, new HuaweiTLV()
+                        .put(0x04, (byte) 0x01)
+                        .put(0x05, new byte[] {0x01, 0x02})
+                )
+        );
+
+        HuaweiPacket packet = new HuaweiPacket(secretsProvider).parse(raw);
+        packet.parseTlv();
+
+        Assert.assertEquals(0x07, packet.serviceId);
+        Assert.assertEquals(0x0d, packet.commandId);
+        Assert.assertEquals(expectedTlv, tlvField.get(packet));
+        Assert.assertTrue(packet.complete);
+        Assert.assertTrue(packet instanceof FitnessData.MessageData.SleepResponse);
+        Assert.assertEquals(0x1337, ((FitnessData.MessageData.SleepResponse) packet).number);
+        Assert.assertEquals(2, ((FitnessData.MessageData.SleepResponse) packet).containers.size());
+        Assert.assertEquals(0x00, ((FitnessData.MessageData.SleepResponse) packet).containers.get(0).type);
+        Assert.assertArrayEquals(new byte[] {}, ((FitnessData.MessageData.SleepResponse) packet).containers.get(0).timestamp);
+        Assert.assertEquals(0x01, ((FitnessData.MessageData.SleepResponse) packet).containers.get(1).type);
+        Assert.assertArrayEquals(new byte[] {0x01, 0x02}, ((FitnessData.MessageData.SleepResponse) packet).containers.get(1).timestamp);
+    }
+
+    @Test
+    public void testMessageDataStepResponse() throws NoSuchFieldException, IllegalAccessException, HuaweiPacket.ParseException {
+        byte[] raw = new byte[] {(byte) 0x5a, (byte) 0x00, (byte) 0x5a, (byte) 0x00, (byte) 0x07, (byte) 0x0b, (byte) 0x7c, (byte) 0x01, (byte) 0x01, (byte) 0x7d, (byte) 0x10, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x7e, (byte) 0x40, (byte) 0xdc, (byte) 0xb7, (byte) 0xf6, (byte) 0xaa, (byte) 0xb2, (byte) 0xf1, (byte) 0x03, (byte) 0x53, (byte) 0x25, (byte) 0x39, (byte) 0xe4, (byte) 0x79, (byte) 0xdd, (byte) 0xbf, (byte) 0x18, (byte) 0x7b, (byte) 0x98, (byte) 0x30, (byte) 0xb7, (byte) 0x4c, (byte) 0x33, (byte) 0xd2, (byte) 0x0c, (byte) 0xa5, (byte) 0xee, (byte) 0xfe, (byte) 0x5f, (byte) 0xa5, (byte) 0x12, (byte) 0x20, (byte) 0xec, (byte) 0x79, (byte) 0x38, (byte) 0xec, (byte) 0x9e, (byte) 0x4d, (byte) 0xfc, (byte) 0xc3, (byte) 0x5c, (byte) 0x59, (byte) 0x67, (byte) 0x51, (byte) 0x4b, (byte) 0xef, (byte) 0x50, (byte) 0x48, (byte) 0xb7, (byte) 0xf8, (byte) 0xc7, (byte) 0xe3, (byte) 0xf7, (byte) 0xdf, (byte) 0x82, (byte) 0xb4, (byte) 0x1a, (byte) 0xb8, (byte) 0x94, (byte) 0x78, (byte) 0x0d, (byte) 0xda, (byte) 0x53, (byte) 0xe3, (byte) 0xbe, (byte) 0xbf, (byte) 0x21, (byte) 0xc2};
+
+        Field tlvField = HuaweiPacket.class.getDeclaredField("tlv");
+        tlvField.setAccessible(true);
+
+        // TODO: add HR data
+
+        HuaweiTLV expectedTlv = new HuaweiTLV().put(0x81, new HuaweiTLV()
+                .put(0x02, (short) 0x1337)
+                .put(0x03, 0xCAFEBEEF)
+                .put(0x084, new HuaweiTLV()
+                        .put(0x05, (byte) 0x00)
+                        .put(0x06, new byte[] {})
+                )
+                .put(0x84, new HuaweiTLV()
+                        .put(0x05, (byte) 0x01)
+                        .put(0x06, new byte[] {0x01, 0x02})
+                )
+                .put(0x84, new HuaweiTLV()
+                        .put(0x05, (byte) 0x02)
+                        .put(0x06, new byte[] {0x0e, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03})
+                )
+                .put(0x84, new HuaweiTLV()
+                        .put(0x05, (byte) 0x02)
+                        .put(0x06, new byte[] {0x01, 0x00, 0x01})
+                )
+        );
+
+        HuaweiPacket packet = new HuaweiPacket(secretsProvider).parse(raw);
+        packet.parseTlv();
+
+        Assert.assertEquals(0x07, packet.serviceId);
+        Assert.assertEquals(0x0b, packet.commandId);
+        Assert.assertEquals(expectedTlv, tlvField.get(packet));
+        Assert.assertTrue(packet.complete);
+        Assert.assertTrue(packet instanceof FitnessData.MessageData.StepResponse);
+
+        Assert.assertEquals(0x1337, ((FitnessData.MessageData.StepResponse) packet).number);
+        Assert.assertEquals(0xCAFEBEEF, ((FitnessData.MessageData.StepResponse) packet).timestamp);
+        Assert.assertEquals(4, ((FitnessData.MessageData.StepResponse) packet).containers.size());
+
+        Assert.assertEquals(0x00, ((FitnessData.MessageData.StepResponse) packet).containers.get(0).timestampOffset);
+        Assert.assertArrayEquals(new byte[] {}, ((FitnessData.MessageData.StepResponse) packet).containers.get(0).data);
+        Assert.assertEquals(0xCAFEBEEF, ((FitnessData.MessageData.StepResponse) packet).containers.get(0).timestamp);
+        Assert.assertNull(((FitnessData.MessageData.StepResponse) packet).containers.get(0).parsedData);
+        Assert.assertEquals("Data is missing feature bitmap.", ((FitnessData.MessageData.StepResponse) packet).containers.get(0).parsedDataError);
+        Assert.assertEquals(-1, ((FitnessData.MessageData.StepResponse) packet).containers.get(0).steps);
+        Assert.assertEquals(-1, ((FitnessData.MessageData.StepResponse) packet).containers.get(0).calories);
+        Assert.assertEquals(-1, ((FitnessData.MessageData.StepResponse) packet).containers.get(0).distance);
+        Assert.assertNull(((FitnessData.MessageData.StepResponse) packet).containers.get(0).unknownTVs);
+
+        Assert.assertEquals(0x01, ((FitnessData.MessageData.StepResponse) packet).containers.get(1).timestampOffset);
+        Assert.assertArrayEquals(new byte[] {0x01, 0x02}, ((FitnessData.MessageData.StepResponse) packet).containers.get(1).data);
+        Assert.assertEquals(0xCAFEBF2B, ((FitnessData.MessageData.StepResponse) packet).containers.get(1).timestamp);
+        Assert.assertNull(((FitnessData.MessageData.StepResponse) packet).containers.get(1).parsedData);
+        Assert.assertEquals("Data is too short for selected features.", ((FitnessData.MessageData.StepResponse) packet).containers.get(1).parsedDataError);
+        Assert.assertEquals(-1, ((FitnessData.MessageData.StepResponse) packet).containers.get(1).steps);
+        Assert.assertEquals(-1, ((FitnessData.MessageData.StepResponse) packet).containers.get(1).calories);
+        Assert.assertEquals(-1, ((FitnessData.MessageData.StepResponse) packet).containers.get(1).distance);
+        Assert.assertEquals(0, ((FitnessData.MessageData.StepResponse) packet).containers.get(1).unknownTVs.size());
+
+        Assert.assertEquals(0x02, ((FitnessData.MessageData.StepResponse) packet).containers.get(2).timestampOffset);
+        Assert.assertArrayEquals(new byte[] {0x0e, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03}, ((FitnessData.MessageData.StepResponse) packet).containers.get(2).data);
+        Assert.assertEquals(0xCAFEBF67, ((FitnessData.MessageData.StepResponse) packet).containers.get(2).timestamp);
+        Assert.assertEquals(3, ((FitnessData.MessageData.StepResponse) packet).containers.get(2).parsedData.size());
+        Assert.assertEquals(1, ((FitnessData.MessageData.StepResponse) packet).containers.get(2).parsedData.get(0).bitmap);
+        Assert.assertEquals(0x02, ((FitnessData.MessageData.StepResponse) packet).containers.get(2).parsedData.get(0).tag);
+        Assert.assertEquals(0x01, ((FitnessData.MessageData.StepResponse) packet).containers.get(2).parsedData.get(0).value);
+        Assert.assertEquals(1, ((FitnessData.MessageData.StepResponse) packet).containers.get(2).parsedData.get(1).bitmap);
+        Assert.assertEquals(0x04, ((FitnessData.MessageData.StepResponse) packet).containers.get(2).parsedData.get(1).tag);
+        Assert.assertEquals(0x02, ((FitnessData.MessageData.StepResponse) packet).containers.get(2).parsedData.get(1).value);
+        Assert.assertEquals(1, ((FitnessData.MessageData.StepResponse) packet).containers.get(2).parsedData.get(2).bitmap);
+        Assert.assertEquals(0x08, ((FitnessData.MessageData.StepResponse) packet).containers.get(2).parsedData.get(2).tag);
+        Assert.assertEquals(0x03, ((FitnessData.MessageData.StepResponse) packet).containers.get(2).parsedData.get(2).value);
+        Assert.assertEquals("", ((FitnessData.MessageData.StepResponse) packet).containers.get(2).parsedDataError);
+        Assert.assertEquals(0x01, ((FitnessData.MessageData.StepResponse) packet).containers.get(2).steps);
+        Assert.assertEquals(0x02, ((FitnessData.MessageData.StepResponse) packet).containers.get(2).calories);
+        Assert.assertEquals(0x03, ((FitnessData.MessageData.StepResponse) packet).containers.get(2).distance);
+        Assert.assertEquals(0, ((FitnessData.MessageData.StepResponse) packet).containers.get(2).unknownTVs.size());
+
+        Assert.assertEquals(0x02, ((FitnessData.MessageData.StepResponse) packet).containers.get(3).timestampOffset);
+        Assert.assertArrayEquals(new byte[] {0x01, 0x00, 0x01}, ((FitnessData.MessageData.StepResponse) packet).containers.get(3).data);
+        Assert.assertEquals(0xCAFEBF67, ((FitnessData.MessageData.StepResponse) packet).containers.get(3).timestamp);
+        Assert.assertEquals(1, ((FitnessData.MessageData.StepResponse) packet).containers.get(3).parsedData.size());
+        Assert.assertEquals(1, ((FitnessData.MessageData.StepResponse) packet).containers.get(3).parsedData.get(0).bitmap);
+        Assert.assertEquals(0x01, ((FitnessData.MessageData.StepResponse) packet).containers.get(3).parsedData.get(0).tag);
+        Assert.assertEquals(0x01, ((FitnessData.MessageData.StepResponse) packet).containers.get(3).parsedData.get(0).value);
+        Assert.assertEquals("", ((FitnessData.MessageData.StepResponse) packet).containers.get(3).parsedDataError);
+        Assert.assertEquals(-1, ((FitnessData.MessageData.StepResponse) packet).containers.get(3).steps);
+        Assert.assertEquals(-1, ((FitnessData.MessageData.StepResponse) packet).containers.get(3).calories);
+        Assert.assertEquals(-1, ((FitnessData.MessageData.StepResponse) packet).containers.get(3).distance);
+        Assert.assertEquals(1, ((FitnessData.MessageData.StepResponse) packet).containers.get(3).unknownTVs.size());
+        Assert.assertEquals(1, ((FitnessData.MessageData.StepResponse) packet).containers.get(3).unknownTVs.get(0).bitmap);
+        Assert.assertEquals(0x01, ((FitnessData.MessageData.StepResponse) packet).containers.get(3).unknownTVs.get(0).tag);
+        Assert.assertEquals(0x01, ((FitnessData.MessageData.StepResponse) packet).containers.get(3).unknownTVs.get(0).value);
+    }
+
+    @Test
+    public void testMessageDataStepResponseSingleByte() throws NoSuchFieldException, IllegalAccessException, HuaweiPacket.ParseException {
+        byte[] raw = new byte[] {(byte) 0x5a, (byte) 0x00, (byte) 0x3a, (byte) 0x00, (byte) 0x07, (byte) 0x0b, (byte) 0x7c, (byte) 0x01, (byte) 0x01, (byte) 0x7d, (byte) 0x10, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x7e, (byte) 0x20, (byte) 0xcf, (byte) 0xa7, (byte) 0x76, (byte) 0x30, (byte) 0x69, (byte) 0xa3, (byte) 0x83, (byte) 0x6e, (byte) 0xd2, (byte) 0x84, (byte) 0x70, (byte) 0xc8, (byte) 0xca, (byte) 0x94, (byte) 0x87, (byte) 0xd2, (byte) 0x0d, (byte) 0x1e, (byte) 0xf5, (byte) 0x60, (byte) 0x72, (byte) 0xa4, (byte) 0xd9, (byte) 0x8f, (byte) 0xf6, (byte) 0xdf, (byte) 0x09, (byte) 0x35, (byte) 0x3c, (byte) 0x86, (byte) 0x62, (byte) 0x00, (byte) 0x0a, (byte) 0x3b};
+
+        Field tlvField = HuaweiPacket.class.getDeclaredField("tlv");
+        tlvField.setAccessible(true);
+
+        // TODO: change test as 0x40 is now added as HR
+
+        HuaweiTLV expectedTlv = new HuaweiTLV().put(0x81, new HuaweiTLV()
+                .put(0x02, (short) 0x01)
+                .put(0x03, 0x02)
+                .put(0x84, new HuaweiTLV()
+                        .put(0x05, (byte) 0x00)
+                        .put(0x06, new byte[] {0x20, 0x01})
+                )
+                .put(0x84, new HuaweiTLV()
+                        .put(0x05, (byte) 0x01)
+                        .put(0x06, new byte[] {0x40, 0x02})
+                )
+        );
+
+        HuaweiPacket packet = new HuaweiPacket(secretsProvider).parse(raw);
+        packet.parseTlv();
+
+        Assert.assertEquals(0x07, packet.serviceId);
+        Assert.assertEquals(0x0b, packet.commandId);
+        Assert.assertEquals(expectedTlv, tlvField.get(packet));
+        Assert.assertTrue(packet.complete);
+        Assert.assertTrue(packet instanceof FitnessData.MessageData.StepResponse);
+
+        Assert.assertEquals(0x01, ((FitnessData.MessageData.StepResponse) packet).number);
+        Assert.assertEquals(0x02, ((FitnessData.MessageData.StepResponse) packet).timestamp);
+        Assert.assertEquals(2, ((FitnessData.MessageData.StepResponse) packet).containers.size());
+
+        Assert.assertEquals(0x00, ((FitnessData.MessageData.StepResponse) packet).containers.get(0).timestampOffset);
+        Assert.assertArrayEquals(new byte[] {0x20, 0x01}, ((FitnessData.MessageData.StepResponse) packet).containers.get(0).data);
+        Assert.assertEquals(0x02, ((FitnessData.MessageData.StepResponse) packet).containers.get(0).timestamp);
+        Assert.assertEquals(1, ((FitnessData.MessageData.StepResponse) packet).containers.get(0).parsedData.size());
+        Assert.assertEquals(1, ((FitnessData.MessageData.StepResponse) packet).containers.get(0).parsedData.get(0).bitmap);
+        Assert.assertEquals(0x20, ((FitnessData.MessageData.StepResponse) packet).containers.get(0).parsedData.get(0).tag);
+        Assert.assertEquals(0x01, ((FitnessData.MessageData.StepResponse) packet).containers.get(0).parsedData.get(0).value);
+        Assert.assertEquals("", ((FitnessData.MessageData.StepResponse) packet).containers.get(0).parsedDataError);
+        Assert.assertEquals(-1, ((FitnessData.MessageData.StepResponse) packet).containers.get(0).steps);
+        Assert.assertEquals(-1, ((FitnessData.MessageData.StepResponse) packet).containers.get(0).calories);
+        Assert.assertEquals(-1, ((FitnessData.MessageData.StepResponse) packet).containers.get(0).distance);
+        Assert.assertEquals(1, ((FitnessData.MessageData.StepResponse) packet).containers.get(0).unknownTVs.size());
+        Assert.assertEquals(1, ((FitnessData.MessageData.StepResponse) packet).containers.get(0).unknownTVs.get(0).bitmap);
+        Assert.assertEquals(0x20, ((FitnessData.MessageData.StepResponse) packet).containers.get(0).unknownTVs.get(0).tag);
+        Assert.assertEquals(0x01, ((FitnessData.MessageData.StepResponse) packet).containers.get(0).unknownTVs.get(0).value);
+
+        Assert.assertEquals(0x01, ((FitnessData.MessageData.StepResponse) packet).containers.get(1).timestampOffset);
+        Assert.assertArrayEquals(new byte[] {0x40, 0x02}, ((FitnessData.MessageData.StepResponse) packet).containers.get(1).data);
+        Assert.assertEquals(0x3e, ((FitnessData.MessageData.StepResponse) packet).containers.get(1).timestamp);
+        Assert.assertEquals(1, ((FitnessData.MessageData.StepResponse) packet).containers.get(1).parsedData.size());
+        Assert.assertEquals(1, ((FitnessData.MessageData.StepResponse) packet).containers.get(1).parsedData.get(0).bitmap);
+        Assert.assertEquals(0x40, ((FitnessData.MessageData.StepResponse) packet).containers.get(1).parsedData.get(0).tag);
+        Assert.assertEquals(0x02, ((FitnessData.MessageData.StepResponse) packet).containers.get(1).parsedData.get(0).value);
+        Assert.assertEquals("", ((FitnessData.MessageData.StepResponse) packet).containers.get(1).parsedDataError);
+        Assert.assertEquals(-1, ((FitnessData.MessageData.StepResponse) packet).containers.get(1).steps);
+        Assert.assertEquals(-1, ((FitnessData.MessageData.StepResponse) packet).containers.get(1).calories);
+        Assert.assertEquals(-1, ((FitnessData.MessageData.StepResponse) packet).containers.get(1).distance);
+        Assert.assertEquals(0, ((FitnessData.MessageData.StepResponse) packet).containers.get(1).unknownTVs.size());
+//        Assert.assertEquals(1, ((FitnessData.MessageData.StepResponse) packet).containers.get(1).unknownTVs.get(0).bitmap);
+//        Assert.assertEquals(0x40, ((FitnessData.MessageData.StepResponse) packet).containers.get(1).unknownTVs.get(0).tag);
+//        Assert.assertEquals(0x02, ((FitnessData.MessageData.StepResponse) packet).containers.get(1).unknownTVs.get(0).value);
+    }
+
+    @SuppressWarnings("ConstantConditions")
+    @Test
+    public void testActivityReminderRequest() throws NoSuchFieldException, IllegalAccessException, HuaweiPacket.CryptoException, HuaweiCrypto.CryptoException {
+        boolean longSitSwitch = false;
+        byte longSitInterval = 0x00;
+        byte[] longSitStart = {0x01, 0x02};
+        byte[] longSitEnd = {0x03, 0x04};
+        byte cycle = 0x05;
+
+        Field tlvField = HuaweiPacket.class.getDeclaredField("tlv");
+        tlvField.setAccessible(true);
+
+        HuaweiTLV expectedTlv = new HuaweiTLV()
+                .put(0x81, new HuaweiTLV()
+                        .put(0x02, longSitSwitch)
+                        .put(0x03, longSitInterval)
+                        .put(0x04, longSitStart)
+                        .put(0x05, longSitEnd)
+                        .put(0x06, cycle)
+                );
+        expectedTlv.encrypt(secretsProvider);
+
+        byte[] expected = new byte[] {(byte) 0x5a, (byte) 0x00, (byte) 0x3a, (byte) 0x00, (byte) 0x07, (byte) 0x07, (byte) 0x7c, (byte) 0x01, (byte) 0x01, (byte) 0x7d, (byte) 0x10, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x7e, (byte) 0x20, (byte) 0x5b, (byte) 0x9b, (byte) 0x16, (byte) 0xa8, (byte) 0x65, (byte) 0x81, (byte) 0xc1, (byte) 0x18, (byte) 0x2f, (byte) 0x42, (byte) 0xab, (byte) 0xf3, (byte) 0x43, (byte) 0x1e, (byte) 0x5c, (byte) 0x32, (byte) 0x9a, (byte) 0xa9, (byte) 0xa2, (byte) 0x18, (byte) 0x36, (byte) 0xb3, (byte) 0x60, (byte) 0x39, (byte) 0xeb, (byte) 0xdb, (byte) 0x6b, (byte) 0xe5, (byte) 0xac, (byte) 0x7b, (byte) 0x45, (byte) 0x36, (byte) 0xbc, (byte) 0x0c};
+
+        FitnessData.ActivityReminder.Request request = new FitnessData.ActivityReminder.Request(
+                secretsProvider,
+                longSitSwitch,
+                longSitInterval,
+                longSitStart,
+                longSitEnd,
+                cycle
+        );
+
+        Assert.assertEquals(0x07, request.serviceId);
+        Assert.assertEquals(0x07, request.commandId);
+        Assert.assertEquals(expectedTlv, tlvField.get(request));
+        Assert.assertTrue(request.complete);
+        List<byte[]> out = request.serialize();
+        Assert.assertEquals(1, out.size());
+        Assert.assertArrayEquals(expected, out.get(0));
+    }
+
+    @SuppressWarnings("ConstantConditions")
+    @Test
+    public void testTruSleepRequest() throws NoSuchFieldException, IllegalAccessException, HuaweiCrypto.CryptoException, HuaweiPacket.CryptoException {
+        boolean truSleepSwitch = false;
+
+        Field tlvField = HuaweiPacket.class.getDeclaredField("tlv");
+        tlvField.setAccessible(true);
+
+        HuaweiTLV expectedTlv = new HuaweiTLV()
+                .put(0x01, truSleepSwitch);
+        expectedTlv.encrypt(secretsProvider);
+
+        byte [] expected = new byte[] {(byte) 0x5a, (byte) 0x00, (byte) 0x2a, (byte) 0x00, (byte) 0x07, (byte) 0x16, (byte) 0x7c, (byte) 0x01, (byte) 0x01, (byte) 0x7d, (byte) 0x10, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x7e, (byte) 0x10, (byte) 0x28, (byte) 0x00, (byte) 0x99, (byte) 0x6f, (byte) 0x2a, (byte) 0xcb, (byte) 0x62, (byte) 0x3a, (byte) 0xe6, (byte) 0x54, (byte) 0x28, (byte) 0x54, (byte) 0xf8, (byte) 0xab, (byte) 0x54, (byte) 0x83, (byte) 0x02, (byte) 0x23};
+
+        FitnessData.TruSleep.Request request = new FitnessData.TruSleep.Request(secretsProvider, truSleepSwitch);
+
+        Assert.assertEquals(0x07, request.serviceId);
+        Assert.assertEquals(0x16, request.commandId);
+        Assert.assertEquals(expectedTlv, tlvField.get(request));
+        Assert.assertTrue(request.complete);
+        List<byte[]> out = request.serialize();
+        Assert.assertEquals(1, out.size());
+        Assert.assertArrayEquals(expected, out.get(0));
+    }
+
+    @Test
+    public void testMessageDataStepResponseNoCount() throws NoSuchFieldException, IllegalAccessException {
+        // I've seen this happening because of a bug in the counts, so it's probably best to stop the sync if this happens.
+        byte[] raw = new byte[] {(byte) 0x5a, (byte) 0x00, (byte) 0x2a, (byte) 0x00, (byte) 0x07, (byte) 0x0b, (byte) 0x7c, (byte) 0x01, (byte) 0x01, (byte) 0x7d, (byte) 0x10, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x7e, (byte) 0x10, (byte) 0x4c, (byte) 0xdd, (byte) 0x99, (byte) 0x79, (byte) 0xf6, (byte) 0x3c, (byte) 0x1e, (byte) 0xbb, (byte) 0x0a, (byte) 0x95, (byte) 0x8d, (byte) 0x12, (byte) 0x05, (byte) 0x81, (byte) 0x7f, (byte) 0xff, (byte) 0xeb, (byte) 0x45};
+
+        Field tlvField = HuaweiPacket.class.getDeclaredField("tlv");
+        tlvField.setAccessible(true);
+
+        try {
+            HuaweiPacket packet = new HuaweiPacket(secretsProvider).parse(raw);
+            packet.parseTlv();
+            Assert.fail();
+        } catch (HuaweiPacket.ParseException e) {
+            if (e instanceof HuaweiPacket.MissingTagException) {
+                Assert.assertNotNull(e.getMessage());
+                if (!e.getMessage().equals("Missing tag: 2")) {
+                    Assert.fail();
+                }
+            } else {
+                Assert.fail();
+            }
+        }
+    }
+
+    @Test
+    public void testSpoData() throws NoSuchFieldException, IllegalAccessException, HuaweiPacket.ParseException {
+        byte[] raw = {(byte) 0x5a, (byte) 0x00, (byte) 0x3a, (byte) 0x00, (byte) 0x07, (byte) 0x0b, (byte) 0x7c, (byte) 0x01, (byte) 0x01, (byte) 0x7d, (byte) 0x10, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x7e, (byte) 0x20, (byte) 0x30, (byte) 0xdf, (byte) 0x42, (byte) 0xc9, (byte) 0x79, (byte) 0x91, (byte) 0x36, (byte) 0x3d, (byte) 0x80, (byte) 0x6b, (byte) 0x99, (byte) 0xd3, (byte) 0x3f, (byte) 0xbf, (byte) 0x1f, (byte) 0x1e, (byte) 0xc1, (byte) 0x0b, (byte) 0xbf, (byte) 0xcd, (byte) 0xae, (byte) 0x38, (byte) 0x89, (byte) 0x60, (byte) 0x60, (byte) 0xf7, (byte) 0x93, (byte) 0x84, (byte) 0x3a, (byte) 0x09, (byte) 0xd3, (byte) 0x77, (byte) 0x1e, (byte) 0xb9};
+
+        Field tlvField = HuaweiPacket.class.getDeclaredField("tlv");
+        tlvField.setAccessible(true);
+
+        HuaweiTLV expectedTlv = new HuaweiTLV().put(0x81, new HuaweiTLV()
+                .put(0x02, (short) 0x1337)
+                .put(0x03, 0xCAFEBEEF)
+                .put(0x84, new HuaweiTLV()
+                        .put(0x05, (byte) 0x00)
+                        .put(0x06, new byte[] {(byte) 0x80, 0x01, 0x42})
+                )
+        );
+
+        HuaweiPacket packet = new HuaweiPacket(secretsProvider).parse(raw);
+        packet.parseTlv();
+
+        Assert.assertEquals(0x07, packet.serviceId);
+        Assert.assertEquals(0x0b, packet.commandId);
+        Assert.assertEquals(expectedTlv, tlvField.get(packet));
+        Assert.assertTrue(packet.complete);
+        Assert.assertTrue(packet instanceof FitnessData.MessageData.StepResponse);
+
+        Assert.assertEquals(0x1337, ((FitnessData.MessageData.StepResponse) packet).number);
+        Assert.assertEquals(0xCAFEBEEF, ((FitnessData.MessageData.StepResponse) packet).timestamp);
+        Assert.assertEquals(1, ((FitnessData.MessageData.StepResponse) packet).containers.size());
+
+        Assert.assertEquals(0x00, ((FitnessData.MessageData.StepResponse) packet).containers.get(0).timestampOffset);
+        Assert.assertArrayEquals(new byte[] {(byte) 0x80, 0x01, 0x42}, ((FitnessData.MessageData.StepResponse) packet).containers.get(0).data);
+        Assert.assertEquals(0xCAFEBEEF, ((FitnessData.MessageData.StepResponse) packet).containers.get(0).timestamp);
+        Assert.assertEquals(1, ((FitnessData.MessageData.StepResponse) packet).containers.get(0).parsedData.size());
+        Assert.assertEquals(2, ((FitnessData.MessageData.StepResponse) packet).containers.get(0).parsedData.get(0).bitmap);
+        Assert.assertEquals(0x01, ((FitnessData.MessageData.StepResponse) packet).containers.get(0).parsedData.get(0).tag);
+        Assert.assertEquals(0x42, ((FitnessData.MessageData.StepResponse) packet).containers.get(0).parsedData.get(0).value);
+        Assert.assertEquals("", ((FitnessData.MessageData.StepResponse) packet).containers.get(0).parsedDataError);
+        Assert.assertEquals(0, ((FitnessData.MessageData.StepResponse) packet).containers.get(0).unknownTVs.size());
+    }
+}
diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/TestLocaleConfig.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/TestLocaleConfig.java
new file mode 100644
index 0000000..a8b288c
--- /dev/null
+++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/TestLocaleConfig.java
@@ -0,0 +1,87 @@
+/*  Copyright (C) 2022 Martin.JM
+
+    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 <http://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.lang.reflect.Field;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiTLV;
+
+public class TestLocaleConfig {
+
+    HuaweiPacket.ParamsProvider paramsProvider = new HuaweiPacket.ParamsProvider() {
+        @Override
+        public byte getDeviceSupportType() {
+            return 0;
+        }
+
+        @Override
+        public byte[] getSecretKey() {
+            return new byte[] {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+        }
+
+        @Override
+        public byte[] getIv() {
+            return new byte[] {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+        }
+
+        @Override
+        public boolean areTransactionsCrypted() {
+            return true;
+        }
+
+        @Override
+        public int getMtu() {
+            return 0;
+        }
+
+        @Override
+        public int getSliceSize() {
+            return 0xF4;
+        }
+    };
+
+    @Test
+    public void testSetLocaleRequest() throws NoSuchFieldException, IllegalAccessException, HuaweiPacket.CryptoException {
+        Field tlvField = HuaweiPacket.class.getDeclaredField("tlv");
+        tlvField.setAccessible(true);
+
+        HuaweiTLV expectedTlv = new HuaweiTLV()
+                .put(0x01, new byte[] {0x45, 0x4e, 0x2d, 0x47, 0x42})
+                .put(0x02, (byte) 0x00);
+
+        byte[] serialized = new byte[] {(byte) 0x5a, (byte) 0x00, (byte) 0x2a, (byte) 0x00, (byte) 0x0c, (byte) 0x01, (byte) 0x7c, (byte) 0x01, (byte) 0x01, (byte) 0x7d, (byte) 0x10, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x7e, (byte) 0x10, (byte) 0x4e, (byte) 0xb0, (byte) 0x71, (byte) 0x05, (byte) 0x7b, (byte) 0xf1, (byte) 0x07, (byte) 0x31, (byte) 0xc4, (byte) 0x6c, (byte) 0x5b, (byte) 0x6d, (byte) 0xbf, (byte) 0x07, (byte) 0xf5, (byte) 0x55, (byte) 0x65, (byte) 0x06};
+
+        LocaleConfig.SetLanguageSetting request = new LocaleConfig.SetLanguageSetting(
+                paramsProvider,
+                new byte[] {0x45, 0x4e, 0x2d, 0x47, 0x42},
+                (byte) 0x00
+        );
+
+        Assert.assertEquals(0x0c, request.serviceId);
+        Assert.assertEquals(0x01, request.commandId);
+        Assert.assertEquals(expectedTlv, tlvField.get(request));
+        Assert.assertTrue(request.complete);
+        List<byte[]> out = request.serialize();
+        Assert.assertEquals(1, out.size());
+        Assert.assertArrayEquals(serialized, out.get(0));
+    }
+}
diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/TestMusicControl.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/TestMusicControl.java
new file mode 100644
index 0000000..8e53304
--- /dev/null
+++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/TestMusicControl.java
@@ -0,0 +1,389 @@
+/*  Copyright (C) 2022-2023 Martin.JM
+
+    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 <http://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets;
+
+import static nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.MusicControl.Control.Response.Button;
+
+import org.junit.Assert;
+import org.junit.Ignore;
+import org.junit.Test;
+
+import java.lang.reflect.Field;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiTLV;
+
+public class TestMusicControl {
+
+    HuaweiPacket.ParamsProvider secretsProvider = new HuaweiPacket.ParamsProvider() {
+        @Override
+        public byte getDeviceSupportType() {
+            return 0;
+        }
+
+        @Override
+        public byte[] getSecretKey() {
+            return new byte[] {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+        }
+
+        @Override
+        public byte[] getIv() {
+            return new byte[] {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+        }
+
+        @Override
+        public boolean areTransactionsCrypted() {
+            return true;
+        }
+
+        @Override
+        public int getMtu() {
+            return 0;
+        }
+
+        @Override
+        public int getSliceSize() {
+            return 0xF4;
+        }
+    };
+
+    @Test
+    public void testMusicStatusRequest() throws NoSuchFieldException, IllegalAccessException, HuaweiPacket.CryptoException {
+        int okInput = 0x000186a0;
+        int errInput = 0x00000000;
+
+        byte commandId1 = 0x01;
+        byte commandId2 = 0x02;
+
+        Field tlvField = HuaweiPacket.class.getDeclaredField("tlv");
+        tlvField.setAccessible(true);
+
+        HuaweiTLV okExpectedTlv = new HuaweiTLV()
+                .put(0x7f, okInput);
+        HuaweiTLV errExpectedTlv = new HuaweiTLV()
+                .put(0x7f, errInput);
+
+        MusicControl.MusicStatusRequest okRequest = new MusicControl.MusicStatusRequest(secretsProvider, commandId1, okInput);
+        MusicControl.MusicStatusRequest errRequest = new MusicControl.MusicStatusRequest(secretsProvider, commandId2, errInput);
+
+        Assert.assertEquals(0x25, okRequest.serviceId);
+        Assert.assertEquals(commandId1, okRequest.commandId);
+        Assert.assertEquals(okExpectedTlv, tlvField.get(okRequest));
+        Assert.assertTrue(okRequest.complete);
+
+        // To check it doesn't error
+        okRequest.serialize();
+
+        Assert.assertEquals(0x25, errRequest.serviceId);
+        Assert.assertEquals(commandId2, errRequest.commandId);
+        Assert.assertEquals(errExpectedTlv, tlvField.get(errRequest));
+        Assert.assertTrue(errRequest.complete);
+
+        // To check it doesn't error
+        errRequest.serialize();
+    }
+
+    @Test
+    public void testMusicStatusResponse() throws NoSuchFieldException, IllegalAccessException, HuaweiPacket.ParseException {
+        byte[] raw = new byte[] {(byte) 0x5a, (byte) 0x00, (byte) 0x2a, (byte) 0x00, (byte) 0x25, (byte) 0x01, (byte) 0x7c, (byte) 0x01, (byte) 0x01, (byte) 0x7d, (byte) 0x10, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x7e, (byte) 0x10, (byte) 0x01, (byte) 0x43, (byte) 0xdb, (byte) 0x63, (byte) 0xee, (byte) 0x66, (byte) 0xb0, (byte) 0xcd, (byte) 0xff, (byte) 0x9f, (byte) 0x69, (byte) 0x91, (byte) 0x76, (byte) 0x80, (byte) 0x15, (byte) 0x1e, (byte) 0x52, (byte) 0x46};
+
+        Field tlvField = HuaweiPacket.class.getDeclaredField("tlv");
+        tlvField.setAccessible(true);
+
+        HuaweiTLV expectedTlv = new HuaweiTLV();
+
+        HuaweiPacket packet = new HuaweiPacket(secretsProvider).parse(raw);
+        packet.parseTlv();
+
+        Assert.assertEquals(0x25, packet.serviceId);
+        Assert.assertEquals(0x01, packet.commandId);
+        Assert.assertEquals(expectedTlv, tlvField.get(packet));
+        Assert.assertTrue(packet instanceof MusicControl.MusicStatusResponse);
+
+        // TODO: complete test when more is known about packet contents
+    }
+
+    @Test
+    public void testMusicStatusResponseUnencrypted() throws NoSuchFieldException, IllegalAccessException, HuaweiPacket.ParseException {
+        byte[] rawOk = new byte[] {0x5a, 0x00, 0x09, 0x00, 0x25, 0x01, 0x7f, 0x04, 0x00, 0x01, (byte) 0x86, (byte) 0xa0, 0x63, (byte) 0x96};
+        byte[] rawErr = new byte[] {0x5a, 0x00, 0x09, 0x00, 0x25, 0x01, 0x7f, 0x04, 0x00, 0x01, (byte) 0x86, (byte) 0xaa, (byte) 0xc2, (byte) 0xdc};
+
+        Field tlvField = HuaweiPacket.class.getDeclaredField("tlv");
+        tlvField.setAccessible(true);
+
+        HuaweiTLV expectedTlvOk = new HuaweiTLV().put(0x7F, 0x000186A0);
+        HuaweiTLV expectedTlvErr = new HuaweiTLV().put(0x7F, 0x000186AA);
+
+        HuaweiPacket packetOk = new HuaweiPacket(secretsProvider).parse(rawOk);
+        HuaweiPacket packetErr = new HuaweiPacket(secretsProvider).parse(rawErr);
+        packetOk.parseTlv();
+        packetErr.parseTlv();
+
+        Assert.assertEquals(0x25, packetOk.serviceId);
+        Assert.assertEquals(0x01, packetOk.commandId);
+        Assert.assertEquals(expectedTlvOk, tlvField.get(packetOk));
+        Assert.assertTrue(packetOk instanceof MusicControl.MusicStatusResponse);
+        Assert.assertEquals(0x000186A0, ((MusicControl.MusicStatusResponse) packetOk).status);
+
+        Assert.assertEquals(0x25, packetErr.serviceId);
+        Assert.assertEquals(0x01, packetErr.commandId);
+        Assert.assertEquals(expectedTlvErr, tlvField.get(packetErr));
+        Assert.assertTrue(packetErr instanceof MusicControl.MusicStatusResponse);
+        Assert.assertEquals(0x000186AA, ((MusicControl.MusicStatusResponse) packetErr).status);
+    }
+
+    @Test
+    public void testMusicInfoRequest() throws NoSuchFieldException, IllegalAccessException, HuaweiPacket.CryptoException {
+        String artistName = "Artist";
+        String songName = "Song";
+        byte playState = 0x01;
+        byte maxVolume = 0x03;
+        byte currentVolume = 0x02;
+        HuaweiTLV expectedTlv = new HuaweiTLV()
+                .put(0x01, artistName)
+                .put(0x02, songName)
+                .put(0x03, playState)
+                .put(0x04, maxVolume)
+                .put(0x05, currentVolume);
+        byte[] expectedSerializedPacket = new byte[] {(byte) 0x5a, (byte) 0x00, (byte) 0x3a, (byte) 0x00, (byte) 0x25, (byte) 0x02, (byte) 0x7c, (byte) 0x01, (byte) 0x01, (byte) 0x7d, (byte) 0x10, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x7e, (byte) 0x20, (byte) 0x21, (byte) 0x8b, (byte) 0xe1, (byte) 0x3d, (byte) 0x9f, (byte) 0x85, (byte) 0xd2, (byte) 0x2e, (byte) 0x64, (byte) 0x87, (byte) 0x3f, (byte) 0x1d, (byte) 0xab, (byte) 0x3f, (byte) 0xc7, (byte) 0x39, (byte) 0xb6, (byte) 0x34, (byte) 0x89, (byte) 0x60, (byte) 0xa0, (byte) 0x36, (byte) 0x4a, (byte) 0x08, (byte) 0x7a, (byte) 0x16, (byte) 0xed, (byte) 0xc9, (byte) 0x9e, (byte) 0xf3, (byte) 0xbf, (byte) 0x44, (byte) 0xac, (byte) 0x58};
+
+        Field tlvField = HuaweiPacket.class.getDeclaredField("tlv");
+        tlvField.setAccessible(true);
+
+        MusicControl.MusicInfo.Request musicInfoRequest = new MusicControl.MusicInfo.Request(
+                secretsProvider,
+                artistName,
+                songName,
+                playState,
+                maxVolume,
+                currentVolume
+        );
+
+        Assert.assertEquals(0x25, musicInfoRequest.serviceId);
+        Assert.assertEquals(0x02, musicInfoRequest.commandId);
+        Assert.assertEquals(expectedTlv, tlvField.get(musicInfoRequest));
+        Assert.assertTrue(musicInfoRequest.complete);
+        List<byte[]> out = musicInfoRequest.serialize();
+        Assert.assertEquals(1, out.size());
+        Assert.assertArrayEquals(expectedSerializedPacket, out.get(0));
+    }
+
+    @Test
+    public void testMusicInfoResponse() throws NoSuchFieldException, IllegalAccessException, HuaweiPacket.ParseException {
+        byte[] rawOk = new byte[] {(byte) 0x5a, (byte) 0x00, (byte) 0x09, (byte) 0x00, (byte) 0x25, (byte) 0x02, (byte) 0x7f, (byte) 0x04, (byte) 0x00, (byte) 0x01, (byte) 0x86, (byte) 0xA0, (byte) 0xbb, (byte) 0x14};
+        byte[] rawErr = new byte[] {(byte) 0x5a, (byte) 0x00, (byte) 0x09, (byte) 0x00, (byte) 0x25, (byte) 0x02, (byte) 0x7f, (byte) 0x04, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x88, (byte) 0xf0};
+        byte[] rawMissing = new byte[] {(byte) 0x5a, (byte) 0x00, (byte) 0x03, (byte) 0x00, (byte) 0x25, (byte) 0x02, (byte) 0xb4, (byte) 0x1b};
+
+        Field tlvField = HuaweiPacket.class.getDeclaredField("tlv");
+        tlvField.setAccessible(true);
+
+        HuaweiTLV okExpectedTlv = new HuaweiTLV()
+                .put(0x7f, 0x000186a0);
+        HuaweiTLV errExpectedTlv = new HuaweiTLV()
+                .put(0x7f, 0x00000000);
+        HuaweiTLV missingExpectedTlv = new HuaweiTLV();
+
+        HuaweiPacket packetOk = new HuaweiPacket(secretsProvider).parse(rawOk);
+        HuaweiPacket packetErr = new HuaweiPacket(secretsProvider).parse(rawErr);
+        HuaweiPacket packetMissing = new HuaweiPacket(secretsProvider).parse(rawMissing);
+
+        packetOk.parseTlv();
+        packetErr.parseTlv();
+        packetMissing.parseTlv();
+
+        Assert.assertEquals(0x25, packetOk.serviceId);
+        Assert.assertEquals(0x02, packetOk.commandId);
+        Assert.assertEquals(okExpectedTlv, tlvField.get(packetOk));
+        Assert.assertTrue(packetOk instanceof MusicControl.MusicInfo.Response);
+        Assert.assertTrue(((MusicControl.MusicInfo.Response) packetOk).ok);
+        Assert.assertEquals("", ((MusicControl.MusicInfo.Response) packetOk).error);
+
+        Assert.assertEquals(0x25, packetErr.serviceId);
+        Assert.assertEquals(0x02, packetErr.commandId);
+        Assert.assertEquals(errExpectedTlv, tlvField.get(packetErr));
+        Assert.assertTrue(packetErr instanceof MusicControl.MusicInfo.Response);
+        Assert.assertFalse(((MusicControl.MusicInfo.Response) packetErr).ok);
+        Assert.assertEquals("Music information error code: 0", ((MusicControl.MusicInfo.Response) packetErr).error);
+
+        Assert.assertEquals(0x25, packetMissing.serviceId);
+        Assert.assertEquals(0x02, packetMissing.commandId);
+        Assert.assertEquals(missingExpectedTlv, tlvField.get(packetMissing));
+        Assert.assertTrue(packetMissing instanceof MusicControl.MusicInfo.Response);
+        Assert.assertFalse(((MusicControl.MusicInfo.Response) packetMissing).ok);
+        Assert.assertEquals("Music information response no status tag", ((MusicControl.MusicInfo.Response) packetMissing).error);
+    }
+
+    @Test
+    public void testControlResponse() throws NoSuchFieldException, IllegalAccessException, HuaweiPacket.ParseException {
+        byte[] emptyInput = new byte[] {(byte) 0x5a, (byte) 0x00, (byte) 0x03, (byte) 0x00, (byte) 0x25, (byte) 0x03, (byte) 0xa4, (byte) 0x3a, };
+        byte[] playPauseInput = new byte[] {(byte) 0x5a, (byte) 0x00, (byte) 0x06, (byte) 0x00, (byte) 0x25, (byte) 0x03, (byte) 0x01, (byte) 0x01, (byte) 0x01, (byte) 0xe6, (byte) 0x85};
+        byte[] previousInput = new byte[] {(byte) 0x5a, (byte) 0x00, (byte) 0x06, (byte) 0x00, (byte) 0x25, (byte) 0x03, (byte) 0x01, (byte) 0x01, (byte) 0x03, (byte) 0xc6, (byte) 0xc7};
+        byte[] nextInput = new byte[] {(byte) 0x5a, (byte) 0x00, (byte) 0x06, (byte) 0x00, (byte) 0x25, (byte) 0x03, (byte) 0x01, (byte) 0x01, (byte) 0x04, (byte) 0xb6, (byte) 0x20};
+        byte[] unknownButtonInput = new byte[] {(byte) 0x5a, (byte) 0x00, (byte) 0x06, (byte) 0x00, (byte) 0x25, (byte) 0x03, (byte) 0x01, (byte) 0x01, (byte) 0xFF, (byte) 0xe8, (byte) 0x54};
+        byte[] exitButtonInput = new byte[] {(byte) 0x5a, (byte) 0x00, (byte) 0x06, (byte) 0x00, (byte) 0x25, (byte) 0x03, (byte) 0x01, (byte) 0x01, (byte) 0x64, (byte) 0xDA, (byte) 0x86};
+        byte[] volumeInput = new byte[] {(byte) 0x5a, (byte) 0x00, (byte) 0x06, (byte) 0x00, (byte) 0x25, (byte) 0x03, (byte) 0x02, (byte) 0x01, (byte) 0x42, (byte) 0xc7, (byte) 0x72};
+        byte[] combinedInput = new byte[] {(byte) 0x5a, (byte) 0x00, (byte) 0x09, (byte) 0x00, (byte) 0x25, (byte) 0x03, (byte) 0x01, (byte) 0x01, (byte) 0x01, (byte) 0x02, (byte) 0x01, (byte) 0x42, (byte) 0x95, (byte) 0x9a};
+
+        Field tlvField = HuaweiPacket.class.getDeclaredField("tlv");
+        tlvField.setAccessible(true);
+
+        HuaweiTLV emptyExpectedTlv = new HuaweiTLV();
+        HuaweiTLV playPauseExpectedTlv = new HuaweiTLV()
+                .put(0x01, (byte) 0x01);
+        HuaweiTLV previousExpectedTlv = new HuaweiTLV()
+                .put(0x01,  (byte) 0x03);
+        HuaweiTLV nextExpectedTlv = new HuaweiTLV()
+                .put(0x01, (byte) 0x04);
+        HuaweiTLV unknownButtonExpectedTlv = new HuaweiTLV()
+                .put(0x01, (byte) 0xFF);
+        HuaweiTLV exitButtonExpectedTlv = new HuaweiTLV()
+                .put(0x01, (byte) 0x64);
+        HuaweiTLV volumeExpectedTlv = new HuaweiTLV()
+                .put(0x02, (byte) 0x42);
+        HuaweiTLV combinedExpectedTlv = new HuaweiTLV()
+                .put(0x01, (byte) 0x01)
+                .put(0x02, (byte) 0x42);
+
+        HuaweiPacket emptyResponse = new HuaweiPacket(secretsProvider).parse(emptyInput);
+        HuaweiPacket playPauseResponse = new HuaweiPacket(secretsProvider).parse(playPauseInput);
+        HuaweiPacket previousResponse = new HuaweiPacket(secretsProvider).parse(previousInput);
+        HuaweiPacket nextResponse = new HuaweiPacket(secretsProvider).parse(nextInput);
+        HuaweiPacket unknownButtonResponse = new HuaweiPacket(secretsProvider).parse(unknownButtonInput);
+        HuaweiPacket exitButtonResponse = new HuaweiPacket(secretsProvider).parse(exitButtonInput);
+        HuaweiPacket volumeResponse = new HuaweiPacket(secretsProvider).parse(volumeInput);
+        HuaweiPacket combinedResponse = new HuaweiPacket(secretsProvider).parse(combinedInput);
+
+        emptyResponse.parseTlv();
+        playPauseResponse.parseTlv();
+        previousResponse.parseTlv();
+        nextResponse.parseTlv();
+        unknownButtonResponse.parseTlv();
+        exitButtonResponse.parseTlv();
+        volumeResponse.parseTlv();
+        combinedResponse.parseTlv();
+
+        // TODO: play and pause are now split - test needs to be updated
+
+        Assert.assertEquals(0x25, emptyResponse.serviceId);
+        Assert.assertEquals(0x03, emptyResponse.commandId);
+        Assert.assertEquals(emptyExpectedTlv, tlvField.get(emptyResponse));
+        Assert.assertTrue(emptyResponse instanceof MusicControl.Control.Response);
+        Assert.assertFalse(((MusicControl.Control.Response) emptyResponse).buttonPresent);
+        Assert.assertFalse(((MusicControl.Control.Response) emptyResponse).volumePresent);
+
+        Assert.assertEquals(0x25, playPauseResponse.serviceId);
+        Assert.assertEquals(0x03, playPauseResponse.commandId);
+        Assert.assertEquals(playPauseExpectedTlv, tlvField.get(playPauseResponse));
+        Assert.assertTrue(playPauseResponse instanceof MusicControl.Control.Response);
+        Assert.assertTrue(((MusicControl.Control.Response) playPauseResponse).buttonPresent);
+        Assert.assertEquals(0x01, ((MusicControl.Control.Response) playPauseResponse).rawButton);
+        // Assert.assertEquals(Button.PlayPause, ((MusicControl.Control.Response) playPauseResponse).button);
+        Assert.assertFalse(((MusicControl.Control.Response) playPauseResponse).volumePresent);
+
+        Assert.assertEquals(0x25, previousResponse.serviceId);
+        Assert.assertEquals(0x03, previousResponse.commandId);
+        Assert.assertEquals(previousExpectedTlv, tlvField.get(previousResponse));
+        Assert.assertTrue(previousResponse instanceof MusicControl.Control.Response);
+        Assert.assertTrue(((MusicControl.Control.Response) previousResponse).buttonPresent);
+        Assert.assertEquals(0x03, ((MusicControl.Control.Response) previousResponse).rawButton);
+        Assert.assertEquals(Button.Previous, ((MusicControl.Control.Response) previousResponse).button);
+        Assert.assertFalse(((MusicControl.Control.Response) previousResponse).volumePresent);
+
+        Assert.assertEquals(0x25, nextResponse.serviceId);
+        Assert.assertEquals(0x03, nextResponse.commandId);
+        Assert.assertEquals(nextExpectedTlv, tlvField.get(nextResponse));
+        Assert.assertTrue(nextResponse instanceof MusicControl.Control.Response);
+        Assert.assertTrue(((MusicControl.Control.Response) nextResponse).buttonPresent);
+        Assert.assertEquals(0x04, ((MusicControl.Control.Response) nextResponse).rawButton);
+        Assert.assertEquals(Button.Next, ((MusicControl.Control.Response) nextResponse).button);
+        Assert.assertFalse(((MusicControl.Control.Response) nextResponse).volumePresent);
+
+        Assert.assertEquals(0x25, unknownButtonResponse.serviceId);
+        Assert.assertEquals(0x03, unknownButtonResponse.commandId);
+        Assert.assertEquals(unknownButtonExpectedTlv, tlvField.get(unknownButtonResponse));
+        Assert.assertTrue(unknownButtonResponse instanceof MusicControl.Control.Response);
+        Assert.assertTrue(((MusicControl.Control.Response) unknownButtonResponse).buttonPresent);
+        Assert.assertEquals((byte) 0xFF, ((MusicControl.Control.Response) unknownButtonResponse).rawButton);
+        Assert.assertEquals(Button.Unknown, ((MusicControl.Control.Response) unknownButtonResponse).button);
+        Assert.assertFalse(((MusicControl.Control.Response) unknownButtonResponse).volumePresent);
+
+        Assert.assertEquals(0x25, exitButtonResponse.serviceId);
+        Assert.assertEquals(0x03, exitButtonResponse.commandId);
+        Assert.assertEquals(exitButtonExpectedTlv, tlvField.get(exitButtonResponse));
+        Assert.assertTrue(exitButtonResponse instanceof MusicControl.Control.Response);
+        Assert.assertFalse(((MusicControl.Control.Response) exitButtonResponse).buttonPresent);
+        Assert.assertEquals((byte) 0x64, ((MusicControl.Control.Response) exitButtonResponse).rawButton);
+        Assert.assertEquals(Button.Unknown, ((MusicControl.Control.Response) exitButtonResponse).button);
+        Assert.assertFalse(((MusicControl.Control.Response) exitButtonResponse).volumePresent);
+
+        Assert.assertEquals(0x25, volumeResponse.serviceId);
+        Assert.assertEquals(0x03, volumeResponse.commandId);
+        Assert.assertEquals(volumeExpectedTlv, tlvField.get(volumeResponse));
+        Assert.assertTrue(volumeResponse instanceof MusicControl.Control.Response);
+        Assert.assertFalse(((MusicControl.Control.Response) volumeResponse).buttonPresent);
+        Assert.assertTrue(((MusicControl.Control.Response) volumeResponse).volumePresent);
+        Assert.assertEquals(0x42, ((MusicControl.Control.Response) volumeResponse).volume);
+
+        Assert.assertEquals(0x25, combinedResponse.serviceId);
+        Assert.assertEquals(0x03, combinedResponse.commandId);
+        Assert.assertEquals(combinedExpectedTlv, tlvField.get(combinedResponse));
+        Assert.assertTrue(combinedResponse instanceof MusicControl.Control.Response);
+        Assert.assertTrue(((MusicControl.Control.Response) combinedResponse).buttonPresent);
+        Assert.assertEquals(0x01, ((MusicControl.Control.Response) combinedResponse).rawButton);
+        // Assert.assertEquals(Button.PlayPause, ((MusicControl.Control.Response) combinedResponse).button);
+        Assert.assertTrue(((MusicControl.Control.Response) combinedResponse).volumePresent);
+        Assert.assertEquals(0x42, ((MusicControl.Control.Response) combinedResponse).volume);
+    }
+
+    @Test
+    public void testIntValues() throws NoSuchFieldException, HuaweiPacket.ParseException, IllegalAccessException {
+        byte[] intButtonInput = new byte[] {(byte) 0x5a, (byte) 0x00, (byte) 0x09, (byte) 0x00, (byte) 0x25, (byte) 0x03, (byte) 0x01, (byte) 0x04, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01, (byte) 0xE5, (byte) 0x8F};
+        byte[] intVolumeInput = new byte[] {(byte) 0x5a, (byte) 0x00, (byte) 0x09, (byte) 0x00, (byte) 0x25, (byte) 0x03, (byte) 0x02, (byte) 0x04, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x28, (byte) 0x9E, (byte) 0x24};
+
+        Field tlvField = HuaweiPacket.class.getDeclaredField("tlv");
+        tlvField.setAccessible(true);
+
+        HuaweiTLV intButtonExpectedTlv = new HuaweiTLV()
+                .put(0x01, 0x01);
+        HuaweiTLV intVolumeExpectedTlv = new HuaweiTLV()
+                .put(0x02, 0x28);
+
+        HuaweiPacket intButtonResponse = new HuaweiPacket(secretsProvider).parse(intButtonInput);
+        HuaweiPacket intVolumeResponse = new HuaweiPacket(secretsProvider).parse(intVolumeInput);
+
+        intButtonResponse.parseTlv();
+        intVolumeResponse.parseTlv();
+
+        Assert.assertEquals(0x25, intButtonResponse.serviceId);
+        Assert.assertEquals(0x03, intButtonResponse.commandId);
+        Assert.assertEquals(intButtonExpectedTlv, tlvField.get(intButtonResponse));
+        Assert.assertTrue(intButtonResponse instanceof MusicControl.Control.Response);
+        Assert.assertTrue(((MusicControl.Control.Response) intButtonResponse).buttonPresent);
+        Assert.assertEquals((byte) 0x01, ((MusicControl.Control.Response) intButtonResponse).rawButton);
+        Assert.assertEquals(Button.Play, ((MusicControl.Control.Response) intButtonResponse).button);
+        Assert.assertFalse(((MusicControl.Control.Response) intButtonResponse).volumePresent);
+
+        Assert.assertEquals(0x25, intVolumeResponse.serviceId);
+        Assert.assertEquals(0x03, intVolumeResponse.commandId);
+        Assert.assertEquals(intVolumeExpectedTlv, tlvField.get(intVolumeResponse));
+        Assert.assertTrue(intVolumeResponse instanceof MusicControl.Control.Response);
+        Assert.assertFalse(((MusicControl.Control.Response) intVolumeResponse).buttonPresent);
+        Assert.assertTrue(((MusicControl.Control.Response) intVolumeResponse).volumePresent);
+        Assert.assertEquals(0x28, ((MusicControl.Control.Response) intVolumeResponse).volume);
+    }
+}
diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/TestNotifications.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/TestNotifications.java
new file mode 100644
index 0000000..29793da
--- /dev/null
+++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/TestNotifications.java
@@ -0,0 +1,195 @@
+/*  Copyright (C) 2022-2023 Martin.JM
+
+    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 <http://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets;
+
+import org.junit.Assert;
+import org.junit.Ignore;
+import org.junit.Test;
+
+import java.lang.reflect.Field;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiTLV;
+
+public class TestNotifications {
+
+    HuaweiPacket.ParamsProvider secretsProvider = new HuaweiPacket.ParamsProvider() {
+        @Override
+        public byte getDeviceSupportType() {
+            return 0;
+        }
+
+        @Override
+        public byte[] getSecretKey() {
+            return new byte[] {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+        }
+
+        @Override
+        public byte[] getIv() {
+            return new byte[] {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+        }
+
+        @Override
+        public boolean areTransactionsCrypted() {
+            return true;
+        }
+
+        @Override
+        public int getMtu() {
+            return 0;
+        }
+
+        @Override
+        public int getSliceSize() {
+            return 0xF4;
+        }
+    };
+
+    @Test
+    public void testNotificationActionRequest() throws NoSuchFieldException, IllegalAccessException, HuaweiPacket.CryptoException {
+        short notificationId = 0x01;
+        byte notificationType = 0x02;
+        byte encoding = 0x02;
+        String titleContent = "Title";
+        String senderContent = "Sender";
+        String bodyContent = "Body";
+        String sourceAppId = "SourceApp";
+
+        Field tlvField = HuaweiPacket.class.getDeclaredField("tlv");
+        tlvField.setAccessible(true);
+
+        HuaweiTLV expectedTlv = new HuaweiTLV()
+                .put(0x01, notificationId)
+                .put(0x02, notificationType)
+                .put(0x03, true)
+                .put(0x84, new HuaweiTLV()
+                        .put(0x8C, new HuaweiTLV()
+                                .put(0x8D, new HuaweiTLV()
+                                        .put(0x0E, (byte) 0x03)
+                                        .put(0x0F, encoding)
+                                        .put(0x10, titleContent)
+                                )
+                                .put(0x8D, new HuaweiTLV()
+                                        .put(0x0E, (byte) 0x02)
+                                        .put(0x0F, encoding)
+                                        .put(0x10, senderContent)
+                                )
+                                .put(0x8D, new HuaweiTLV()
+                                        .put(0x0E, (byte) 0x01)
+                                        .put(0x0F, encoding)
+                                        .put(0x10, bodyContent)
+                                )
+                        )
+                )
+                .put(0x11, sourceAppId);
+
+        Notifications.NotificationActionRequest request = new Notifications.NotificationActionRequest(
+                secretsProvider,
+                notificationId,
+                notificationType,
+                encoding,
+                titleContent,
+                senderContent,
+                bodyContent,
+                sourceAppId
+        );
+
+        Assert.assertEquals(0x02, request.serviceId);
+        Assert.assertEquals(0x01, request.commandId);
+        Assert.assertTrue(request.complete);
+        Assert.assertEquals(expectedTlv, tlvField.get(request));
+
+        // Only check that this doesn't error
+        request.serialize();
+    }
+
+    @Test
+    public void testSetNotificationRequest() throws NoSuchFieldException, IllegalAccessException, HuaweiPacket.CryptoException {
+        Field tlvField = HuaweiPacket.class.getDeclaredField("tlv");
+        tlvField.setAccessible(true);
+
+        HuaweiTLV expectedTlvTrue = new HuaweiTLV()
+                .put(0x81, new HuaweiTLV()
+                        .put(0x02, true)
+                        .put(0x03, true)
+                );
+
+        HuaweiTLV expectedTlvFalse = new HuaweiTLV()
+                .put(0x81, new HuaweiTLV()
+                        .put(0x02, false)
+                        .put(0x03, false)
+                );
+
+        byte[] expectedOutputTrue = new byte[] {(byte) 0x5a, (byte) 0x00, (byte) 0x2a, (byte) 0x00, (byte) 0x02, (byte) 0x04, (byte) 0x7c, (byte) 0x01, (byte) 0x01, (byte) 0x7d, (byte) 0x10, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x7e, (byte) 0x10, (byte) 0xd9, (byte) 0xc4, (byte) 0xaa, (byte) 0x7d, (byte) 0xa3, (byte) 0x5c, (byte) 0x42, (byte) 0xab, (byte) 0x2d, (byte) 0xc2, (byte) 0xe7, (byte) 0x73, (byte) 0xc0, (byte) 0x4c, (byte) 0x97, (byte) 0x5a, (byte) 0x41, (byte) 0x23};
+        byte[] expectedOutputFalse = new byte[] {(byte) 0x5a, (byte) 0x00, (byte) 0x2a, (byte) 0x00, (byte) 0x02, (byte) 0x04, (byte) 0x7c, (byte) 0x01, (byte) 0x01, (byte) 0x7d, (byte) 0x10, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x7e, (byte) 0x10, (byte) 0xeb, (byte) 0x1f, (byte) 0x20, (byte) 0x0a, (byte) 0x7d, (byte) 0xe2, (byte) 0x25, (byte) 0x45, (byte) 0x01, (byte) 0x5b, (byte) 0xe8, (byte) 0x24, (byte) 0xe3, (byte) 0x7e, (byte) 0x1d, (byte) 0x9c, (byte) 0x47, (byte) 0x31};
+
+        Notifications.NotificationStateRequest requestTrue = new Notifications.NotificationStateRequest(secretsProvider, true);
+        Notifications.NotificationStateRequest requestFalse = new Notifications.NotificationStateRequest(secretsProvider, false);
+
+        Assert.assertEquals(0x02, requestTrue.serviceId);
+        Assert.assertEquals(0x04, requestTrue.commandId);
+        Assert.assertTrue(requestTrue.complete);
+        Assert.assertEquals(expectedTlvTrue, tlvField.get(requestTrue));
+        List<byte[]> outTrue = requestTrue.serialize();
+        Assert.assertEquals(1, outTrue.size());
+        Assert.assertArrayEquals(expectedOutputTrue, outTrue.get(0));
+
+        Assert.assertEquals(0x02, requestFalse.serviceId);
+        Assert.assertEquals(0x04, requestFalse.commandId);
+        Assert.assertTrue(requestFalse.complete);
+        Assert.assertEquals(expectedTlvFalse, tlvField.get(requestFalse));
+        List<byte[]> outFalse = requestFalse.serialize();
+        Assert.assertEquals(1, outFalse.size());
+        Assert.assertArrayEquals(expectedOutputFalse, outFalse.get(0));
+    }
+
+    @Test
+    @Ignore("Broken since https://codeberg.org/psolyca/Gadgetbridge/commit/5b0736b7518aa5c998ac13207fff66286393965b")
+    public void testSetWearMessagePushRequest() throws NoSuchFieldException, IllegalAccessException, HuaweiPacket.CryptoException {
+        Field tlvField = HuaweiPacket.class.getDeclaredField("tlv");
+        tlvField.setAccessible(true);
+
+        HuaweiTLV expectedTlvTrue = new HuaweiTLV()
+                .put(0x01, true);
+
+        HuaweiTLV expectedTlvFalse = new HuaweiTLV()
+                .put(0x01, false);
+
+        byte[] expectedOutputTrue = new byte[] {(byte) 0x5a, (byte) 0x00, (byte) 0x2a, (byte) 0x00, (byte) 0x02, (byte) 0x08, (byte) 0x7c, (byte) 0x01, (byte) 0x01, (byte) 0x7d, (byte) 0x10, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x7e, (byte) 0x10, (byte) 0xcd, (byte) 0x97, (byte) 0x7e, (byte) 0x01, (byte) 0x48, (byte) 0x34, (byte) 0x2a, (byte) 0x48, (byte) 0x58, (byte) 0x0d, (byte) 0x30, (byte) 0xc7, (byte) 0xbc, (byte) 0x2e, (byte) 0x40, (byte) 0xd4, (byte) 0x29, (byte) 0xe0};
+        byte[] expectedOutputFalse = new byte[] {(byte) 0x5a, (byte) 0x00, (byte) 0x2a, (byte) 0x00, (byte) 0x02, (byte) 0x08, (byte) 0x7c, (byte) 0x01, (byte) 0x01, (byte) 0x7d, (byte) 0x10, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x7e, (byte) 0x10, (byte) 0x28, (byte) 0x00, (byte) 0x99, (byte) 0x6f, (byte) 0x2a, (byte) 0xcb, (byte) 0x62, (byte) 0x3a, (byte) 0xe6, (byte) 0x54, (byte) 0x28, (byte) 0x54, (byte) 0xf8, (byte) 0xab, (byte) 0x54, (byte) 0x83, (byte) 0x30, (byte) 0xd2};
+
+        Notifications.WearMessagePushRequest requestTrue = new Notifications.WearMessagePushRequest(secretsProvider, true);
+        Notifications.WearMessagePushRequest requestFalse = new Notifications.WearMessagePushRequest(secretsProvider, false);
+
+        Assert.assertEquals(0x02, requestTrue.serviceId);
+        Assert.assertEquals(0x08, requestTrue.commandId);
+        Assert.assertTrue(requestTrue.complete);
+        Assert.assertEquals(expectedTlvTrue, tlvField.get(requestTrue));
+        List<byte[]> outTrue = requestTrue.serialize();
+        Assert.assertEquals(1, outTrue.size());
+        Assert.assertArrayEquals(expectedOutputTrue, outTrue.get(0));
+
+        Assert.assertEquals(0x02, requestFalse.serviceId);
+        Assert.assertEquals(0x08, requestFalse.commandId);
+        Assert.assertTrue(requestFalse.complete);
+        Assert.assertEquals(expectedTlvFalse, tlvField.get(requestFalse));
+        List<byte[]> outFalse = requestFalse.serialize();
+        Assert.assertEquals(1, outFalse.size());
+        Assert.assertArrayEquals(expectedOutputFalse, outFalse.get(0));
+    }
+}
diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/TestWorkMode.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/TestWorkMode.java
new file mode 100644
index 0000000..2be8d6c
--- /dev/null
+++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/TestWorkMode.java
@@ -0,0 +1,94 @@
+/*  Copyright (C) 2022 Martin.JM
+
+    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 <http://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.lang.reflect.Field;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiTLV;
+
+public class TestWorkMode {
+
+    HuaweiPacket.ParamsProvider secretsProvider = new HuaweiPacket.ParamsProvider() {
+        @Override
+        public byte getDeviceSupportType() {
+            return 0;
+        }
+
+        @Override
+        public byte[] getSecretKey() {
+            return new byte[] {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+        }
+
+        @Override
+        public byte[] getIv() {
+            return new byte[] {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+        }
+
+        @Override
+        public boolean areTransactionsCrypted() {
+            return true;
+        }
+
+        @Override
+        public int getMtu() {
+            return 0;
+        }
+
+        @Override
+        public int getSliceSize() {
+            return 0xF4;
+        }
+    };
+
+    @Test
+    public void testSwitchStatusRequest() throws NoSuchFieldException, IllegalAccessException, HuaweiPacket.CryptoException {
+        Field tlvField = HuaweiPacket.class.getDeclaredField("tlv");
+        tlvField.setAccessible(true);
+
+        HuaweiTLV expectedTlvTrue = new HuaweiTLV()
+                .put(0x01, true);
+        HuaweiTLV expectedTlvFalse = new HuaweiTLV()
+                .put(0x01, false);
+
+        byte[] serializedTrue = new byte[] {(byte) 0x5a, (byte) 0x00, (byte) 0x2a, (byte) 0x00, (byte) 0x26, (byte) 0x02, (byte) 0x7c, (byte) 0x01, (byte) 0x01, (byte) 0x7d, (byte) 0x10, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x7e, (byte) 0x10, (byte) 0xcd, (byte) 0x97, (byte) 0x7e, (byte) 0x01, (byte) 0x48, (byte) 0x34, (byte) 0x2a, (byte) 0x48, (byte) 0x58, (byte) 0x0d, (byte) 0x30, (byte) 0xc7, (byte) 0xbc, (byte) 0x2e, (byte) 0x40, (byte) 0xd4, (byte) 0x5c, (byte) 0x5a};
+        byte[] serializedFalse = new byte[] {(byte) 0x5a, (byte) 0x00, (byte) 0x2a, (byte) 0x00, (byte) 0x26, (byte) 0x02, (byte) 0x7c, (byte) 0x01, (byte) 0x01, (byte) 0x7d, (byte) 0x10, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x7e, (byte) 0x10, (byte) 0x28, (byte) 0x00, (byte) 0x99, (byte) 0x6f, (byte) 0x2a, (byte) 0xcb, (byte) 0x62, (byte) 0x3a, (byte) 0xe6, (byte) 0x54, (byte) 0x28, (byte) 0x54, (byte) 0xf8, (byte) 0xab, (byte) 0x54, (byte) 0x83, (byte) 0x45, (byte) 0x68};
+
+        WorkMode.SwitchStatusRequest requestTrue = new WorkMode.SwitchStatusRequest(secretsProvider, true);
+        WorkMode.SwitchStatusRequest requestFalse = new WorkMode.SwitchStatusRequest(secretsProvider, false);
+
+        Assert.assertEquals(0x26, requestTrue.serviceId);
+        Assert.assertEquals(0x02, requestTrue.commandId);
+        Assert.assertEquals(expectedTlvTrue, tlvField.get(requestTrue));
+        Assert.assertTrue(requestTrue.complete);
+        List<byte[]> outTrue = requestTrue.serialize();
+        Assert.assertEquals(1, outTrue.size());
+        Assert.assertArrayEquals(serializedTrue, outTrue.get(0));
+
+        Assert.assertEquals(0x26, requestFalse.serviceId);
+        Assert.assertEquals(0x02, requestFalse.commandId);
+        Assert.assertEquals(expectedTlvFalse, tlvField.get(requestFalse));
+        Assert.assertTrue(requestFalse.complete);
+        List<byte[]> outFalse = requestFalse.serialize();
+        Assert.assertEquals(1, outFalse.size());
+        Assert.assertArrayEquals(serializedFalse, outFalse.get(0));
+    }
+}
diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/TestWorkout.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/TestWorkout.java
new file mode 100644
index 0000000..b65934e
--- /dev/null
+++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/TestWorkout.java
@@ -0,0 +1,445 @@
+/*  Copyright (C) 2022 Martin.JM
+
+    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 <http://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.lang.reflect.Field;
+import java.nio.ByteBuffer;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiTLV;
+
+public class TestWorkout {
+
+    HuaweiPacket.ParamsProvider secretsProvider = new HuaweiPacket.ParamsProvider() {
+        @Override
+        public byte getDeviceSupportType() {
+            return 0;
+        }
+
+        @Override
+        public byte[] getSecretKey() {
+            return new byte[] {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+        }
+
+        @Override
+        public byte[] getIv() {
+            return new byte[] {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+        }
+
+        @Override
+        public boolean areTransactionsCrypted() {
+            return true;
+        }
+
+        @Override
+        public int getMtu() {
+            return 0;
+        }
+
+        @Override
+        public int getSliceSize() {
+            return 0xF4;
+        }
+    };
+
+    @Test
+    public void testWorkoutCountRequest() throws NoSuchFieldException, IllegalAccessException, HuaweiPacket.CryptoException {
+        int start = 0x00000000;
+        int end = 0x01020304;
+
+        Field tlvField = HuaweiPacket.class.getDeclaredField("tlv");
+        tlvField.setAccessible(true);
+
+        HuaweiTLV expectedTlv = new HuaweiTLV().put(0x81, new HuaweiTLV()
+                .put(0x03, start)
+                .put(0x04, end)
+        );
+
+        byte[] expected = new byte[] {(byte) 0x5a, (byte) 0x00, (byte) 0x2a, (byte) 0x00, (byte) 0x17, (byte) 0x07, (byte) 0x7c, (byte) 0x01, (byte) 0x01, (byte) 0x7d, (byte) 0x10, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x7e, (byte) 0x10, (byte) 0xf7, (byte) 0x48, (byte) 0xf7, (byte) 0x49, (byte) 0x4a, (byte) 0xa5, (byte) 0xb2, (byte) 0xc9, (byte) 0x41, (byte) 0xf5, (byte) 0x7f, (byte) 0xb4, (byte) 0xe9, (byte) 0x17, (byte) 0xac, (byte) 0xb5, (byte) 0x5f, (byte) 0x8e};
+
+        Workout.WorkoutCount.Request request = new Workout.WorkoutCount.Request(secretsProvider, start, end);
+
+        Assert.assertEquals(0x17, request.serviceId);
+        Assert.assertEquals(0x07, request.commandId);
+        Assert.assertEquals(expectedTlv, tlvField.get(request));
+        Assert.assertTrue(request.complete);
+        List<byte[]> out = request.serialize();
+        Assert.assertEquals(1, out.size());
+        Assert.assertArrayEquals(expected, out.get(0));
+    }
+
+    @Test
+    public void testWorkoutCountResponse() throws NoSuchFieldException, IllegalAccessException, HuaweiPacket.ParseException {
+        byte[] raw = new byte[] {(byte) 0x5a, (byte) 0x00, (byte) 0x4a, (byte) 0x00, (byte) 0x17, (byte) 0x07, (byte) 0x7c, (byte) 0x01, (byte) 0x01, (byte) 0x7d, (byte) 0x10, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x7e, (byte) 0x30, (byte) 0xee, (byte) 0xdd, (byte) 0xa9, (byte) 0x23, (byte) 0x2c, (byte) 0xe4, (byte) 0x9f, (byte) 0x41, (byte) 0x0b, (byte) 0x9f, (byte) 0x7a, (byte) 0xc2, (byte) 0xe0, (byte) 0x72, (byte) 0x6d, (byte) 0xe1, (byte) 0x8f, (byte) 0xd0, (byte) 0xe7, (byte) 0x41, (byte) 0x59, (byte) 0x38, (byte) 0xac, (byte) 0x17, (byte) 0x66, (byte) 0xc8, (byte) 0x60, (byte) 0xd7, (byte) 0xd2, (byte) 0x32, (byte) 0x8b, (byte) 0xa5, (byte) 0x91, (byte) 0xc7, (byte) 0xc5, (byte) 0xe5, (byte) 0x7d, (byte) 0x8d, (byte) 0xa1, (byte) 0xd0, (byte) 0x6f, (byte) 0xe2, (byte) 0xe2, (byte) 0x24, (byte) 0x7d, (byte) 0xef, (byte) 0x02, (byte) 0x03, (byte) 0x59, (byte) 0x3e};
+
+        Field tlvField = HuaweiPacket.class.getDeclaredField("tlv");
+        tlvField.setAccessible(true);
+
+        HuaweiTLV expectedTlv = new HuaweiTLV().put(0x81, new HuaweiTLV()
+                .put(0x02, (short) 0x1337)
+                .put(0x85, new HuaweiTLV()
+                        .put(0x06, (short) 0x0001)
+                        .put(0x07, (short) 0x0002)
+                        .put(0x08, (short) 0x0003)
+                        .put(0x0a, (short) 0x0004)
+                )
+                .put(0x85, new HuaweiTLV()
+                        .put(0x06, (short) 0x0005)
+                        .put(0x07, (short) 0x0006)
+                        .put(0x08, (short) 0x0007)
+                        .put(0x0a, (short) 0x0008)
+                )
+        );
+
+        HuaweiPacket packet = new HuaweiPacket(secretsProvider).parse(raw);
+        packet.parseTlv();
+
+        Assert.assertEquals(0x17, packet.serviceId);
+        Assert.assertEquals(0x07, packet.commandId);
+        Assert.assertEquals(expectedTlv, tlvField.get(packet));
+        Assert.assertTrue(packet.complete);
+        Assert.assertTrue(packet instanceof Workout.WorkoutCount.Response);
+        Assert.assertEquals(0x1337, ((Workout.WorkoutCount.Response) packet).count);
+        Assert.assertEquals(2, ((Workout.WorkoutCount.Response) packet).workoutNumbers.size());
+
+        Assert.assertArrayEquals(new byte[] {0x06, 0x02, 0x00, 0x01, 0x07, 0x02, 0x00, 0x02, 0x08, 0x02, 0x00, 0x03, 0x0a, 0x02, 0x00, 0x04}, ((Workout.WorkoutCount.Response) packet).workoutNumbers.get(0).rawData);
+        Assert.assertEquals(0x01, ((Workout.WorkoutCount.Response) packet).workoutNumbers.get(0).workoutNumber);
+        Assert.assertEquals(0x02, ((Workout.WorkoutCount.Response) packet).workoutNumbers.get(0).dataCount);
+        Assert.assertEquals(0x03, ((Workout.WorkoutCount.Response) packet).workoutNumbers.get(0).paceCount);
+
+        Assert.assertArrayEquals(new byte[] {0x06, 0x02, 0x00, 0x05, 0x07, 0x02, 0x00, 0x06, 0x08, 0x02, 0x00, 0x07, 0x0a, 0x02, 0x00, 0x08}, ((Workout.WorkoutCount.Response) packet).workoutNumbers.get(1).rawData);
+        Assert.assertEquals(0x05, ((Workout.WorkoutCount.Response) packet).workoutNumbers.get(1).workoutNumber);
+        Assert.assertEquals(0x06, ((Workout.WorkoutCount.Response) packet).workoutNumbers.get(1).dataCount);
+        Assert.assertEquals(0x07, ((Workout.WorkoutCount.Response) packet).workoutNumbers.get(1).paceCount);
+    }
+
+    @Test
+    public void testWorkoutTotalsRequest() throws NoSuchFieldException, IllegalAccessException, HuaweiPacket.CryptoException {
+        short number = 0x1337;
+
+        Field tlvField = HuaweiPacket.class.getDeclaredField("tlv");
+        tlvField.setAccessible(true);
+
+        HuaweiTLV expectedTlv = new HuaweiTLV().put(0x81, new HuaweiTLV()
+                .put(0x02, number)
+        );
+
+        byte[] expected = new byte[] {(byte) 0x5a, (byte) 0x00, (byte) 0x2a, (byte) 0x00, (byte) 0x17, (byte) 0x08, (byte) 0x7c, (byte) 0x01, (byte) 0x01, (byte) 0x7d, (byte) 0x10, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x7e, (byte) 0x10, (byte) 0xf6, (byte) 0xfb, (byte) 0xc0, (byte) 0xb6, (byte) 0x4f, (byte) 0x9a, (byte) 0xfa, (byte) 0x77, (byte) 0x53, (byte) 0x28, (byte) 0x7d, (byte) 0x13, (byte) 0xca, (byte) 0x49, (byte) 0xda, (byte) 0xfd, (byte) 0x26, (byte) 0x91};
+
+        Workout.WorkoutTotals.Request request = new Workout.WorkoutTotals.Request(secretsProvider, number);
+
+        Assert.assertEquals(0x17, request.serviceId);
+        Assert.assertEquals(0x08, request.commandId);
+        Assert.assertEquals(expectedTlv, tlvField.get(request));
+        Assert.assertTrue(request.complete);
+        List<byte[]> out = request.serialize();
+        Assert.assertEquals(1, out.size());
+        Assert.assertArrayEquals(expected, out.get(0));
+    }
+
+    @Test
+    public void testWorkoutTotalsResponse() throws NoSuchFieldException, IllegalAccessException, HuaweiPacket.ParseException {
+        byte[] raw = new byte[] {(byte) 0x5a, (byte) 0x00, (byte) 0x5a, (byte) 0x00, (byte) 0x17, (byte) 0x08, (byte) 0x7c, (byte) 0x01, (byte) 0x01, (byte) 0x7d, (byte) 0x10, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x7e, (byte) 0x40, (byte) 0x0f, (byte) 0xa0, (byte) 0x3a, (byte) 0x90, (byte) 0xae, (byte) 0x8c, (byte) 0xcf, (byte) 0x03, (byte) 0xce, (byte) 0x5a, (byte) 0x68, (byte) 0x87, (byte) 0x05, (byte) 0x51, (byte) 0xf7, (byte) 0x2f, (byte) 0x78, (byte) 0xbd, (byte) 0x84, (byte) 0xf1, (byte) 0x4f, (byte) 0xb8, (byte) 0x51, (byte) 0x28, (byte) 0xec, (byte) 0xfd, (byte) 0x8b, (byte) 0x2e, (byte) 0x99, (byte) 0xd3, (byte) 0x42, (byte) 0xd7, (byte) 0x65, (byte) 0xb2, (byte) 0x82, (byte) 0x02, (byte) 0x28, (byte) 0x00, (byte) 0x34, (byte) 0xbc, (byte) 0x39, (byte) 0x59, (byte) 0x8f, (byte) 0x0b, (byte) 0xa7, (byte) 0x3a, (byte) 0x5c, (byte) 0xfb, (byte) 0xf1, (byte) 0xd4, (byte) 0x8f, (byte) 0xf6, (byte) 0x6d, (byte) 0x98, (byte) 0xd6, (byte) 0x5a, (byte) 0x51, (byte) 0x0a, (byte) 0x4a, (byte) 0x1c, (byte) 0x42, (byte) 0xc8, (byte) 0x9d, (byte) 0xee, (byte) 0x55, (byte) 0x44};
+
+        Field tlvField = HuaweiPacket.class.getDeclaredField("tlv");
+        tlvField.setAccessible(true);
+
+        HuaweiTLV expectedTlv = new HuaweiTLV().put(0x81, new HuaweiTLV()
+                .put(0x02, (short) 0x1337)
+                .put(0x03, (byte) 0x01)
+                .put(0x04, 0x01020304)
+                .put(0x05, 0x05060708)
+                .put(0x06, 0x090a0b0c)
+                .put(0x07, 0x0d0e0f10)
+                .put(0x08, 0x11121314)
+                .put(0x09, 0x15161718)
+                .put(0x12, 0x191a1b1c)
+                .put(0x14, (byte) 0x1d)
+        );
+
+        HuaweiPacket packet = new HuaweiPacket(secretsProvider).parse(raw);
+        packet.parseTlv();
+
+        // TODO: find out what the status and type can be
+
+        Assert.assertEquals(0x17, packet.serviceId);
+        Assert.assertEquals(0x08, packet.commandId);
+        Assert.assertEquals(expectedTlv, tlvField.get(packet));
+        Assert.assertTrue(packet.complete);
+        Assert.assertTrue(packet instanceof Workout.WorkoutTotals.Response);
+        Assert.assertArrayEquals(new byte[] {0x02, 0x02, 0x13, 0x37, 0x03, 0x01, 0x01, 0x04, 0x04, 0x01, 0x02, 0x03, 0x04, 0x05, 0x04, 0x05, 0x06, 0x07, 0x08, 0x06, 0x04, 0x09, 0x0a, 0x0b, 0x0c, 0x07, 0x04, 0x0d, 0x0e, 0x0f, 0x10, 0x08, 0x04, 0x11, 0x12, 0x13, 0x14, 0x09, 0x04, 0x15, 0x16, 0x17, 0x18, 0x12, 0x04, 0x19, 0x1a, 0x1b, 0x1c, 0x14, 0x01, 0x1d}, ((Workout.WorkoutTotals.Response) packet).rawData);
+        Assert.assertEquals(0x1337, ((Workout.WorkoutTotals.Response) packet).number);
+        Assert.assertEquals(0x01, ((Workout.WorkoutTotals.Response) packet).status);
+        Assert.assertEquals(0x01020304, ((Workout.WorkoutTotals.Response) packet).startTime);
+        Assert.assertEquals(0x05060708, ((Workout.WorkoutTotals.Response) packet).endTime);
+        Assert.assertEquals(0x090a0b0c, ((Workout.WorkoutTotals.Response) packet).calories);
+        Assert.assertEquals(0x0d0e0f10, ((Workout.WorkoutTotals.Response) packet).distance);
+        Assert.assertEquals(0x11121314, ((Workout.WorkoutTotals.Response) packet).stepCount);
+        Assert.assertEquals(0x15161718, ((Workout.WorkoutTotals.Response) packet).totalTime);
+        Assert.assertEquals(0x191a1b1c, ((Workout.WorkoutTotals.Response) packet).duration);
+        Assert.assertEquals(0x1d, ((Workout.WorkoutTotals.Response) packet).type);
+    }
+
+    @Test
+    public void testWorkoutDataRequest() throws NoSuchFieldException, IllegalAccessException, HuaweiPacket.CryptoException {
+        short workoutNumber = 0x0102;
+        short dataNumber = 0x0304;
+
+        Field tlvField = HuaweiPacket.class.getDeclaredField("tlv");
+        tlvField.setAccessible(true);
+
+        HuaweiTLV expectedTlv = new HuaweiTLV().put(0x81, new HuaweiTLV()
+                .put(0x02, workoutNumber)
+                .put(0x03, dataNumber)
+        );
+
+        byte[] expected = {(byte) 0x5a, (byte) 0x00, (byte) 0x2a, (byte) 0x00, (byte) 0x17, (byte) 0x0a, (byte) 0x7c, (byte) 0x01, (byte) 0x01, (byte) 0x7d, (byte) 0x10, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x7e, (byte) 0x10, (byte) 0xd2, (byte) 0xd7, (byte) 0x55, (byte) 0x23, (byte) 0xeb, (byte) 0x51, (byte) 0x4f, (byte) 0xe0, (byte) 0x35, (byte) 0x6c, (byte) 0x60, (byte) 0xc5, (byte) 0xbf, (byte) 0x61, (byte) 0x68, (byte) 0xd1, (byte) 0x03, (byte) 0x83};
+
+        Workout.WorkoutData.Request request = new Workout.WorkoutData.Request(secretsProvider, workoutNumber, dataNumber);
+
+        Assert.assertEquals(0x17, request.serviceId);
+        Assert.assertEquals(0x0a, request.commandId);
+        Assert.assertEquals(expectedTlv, tlvField.get(request));
+        Assert.assertTrue(request.complete);
+        List<byte[]> out = request.serialize();
+        Assert.assertEquals(1, out.size());
+        Assert.assertArrayEquals(expected, out.get(0));
+    }
+
+    @Test
+    public void testWorkoutDataResponse() throws NoSuchFieldException, IllegalAccessException, HuaweiPacket.ParseException {
+        byte[] raw = {(byte) 0x5a, (byte) 0x00, (byte) 0x5a, (byte) 0x00, (byte) 0x17, (byte) 0x0a, (byte) 0x7c, (byte) 0x01, (byte) 0x01, (byte) 0x7d, (byte) 0x10, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x7e, (byte) 0x40, (byte) 0x03, (byte) 0x66, (byte) 0xf5, (byte) 0x16, (byte) 0xc9, (byte) 0x60, (byte) 0xb9, (byte) 0xf2, (byte) 0xe3, (byte) 0x88, (byte) 0x99, (byte) 0xab, (byte) 0x50, (byte) 0x22, (byte) 0xcb, (byte) 0x83, (byte) 0x53, (byte) 0xd0, (byte) 0xb2, (byte) 0xc3, (byte) 0x66, (byte) 0xa9, (byte) 0x16, (byte) 0x23, (byte) 0xa5, (byte) 0x8e, (byte) 0x81, (byte) 0x68, (byte) 0x85, (byte) 0x38, (byte) 0x3e, (byte) 0xd5, (byte) 0x8e, (byte) 0x21, (byte) 0xc8, (byte) 0xa1, (byte) 0x80, (byte) 0x98, (byte) 0x2d, (byte) 0x78, (byte) 0x75, (byte) 0x80, (byte) 0xa1, (byte) 0x39, (byte) 0x61, (byte) 0xa6, (byte) 0x3e, (byte) 0x61, (byte) 0x2c, (byte) 0x5e, (byte) 0xe2, (byte) 0x6f, (byte) 0xef, (byte) 0xdf, (byte) 0xdb, (byte) 0x39, (byte) 0x8f, (byte) 0xab, (byte) 0x21, (byte) 0xde, (byte) 0xba, (byte) 0xdb, (byte) 0x2c, (byte) 0xff, (byte) 0x97, (byte) 0x94};
+
+        short workoutNumber = 0x0102;
+        short dataNumber = 0x0304;
+
+        int timestamp = 0x05060708;
+        byte interval = 0x09;
+        short dataCount = 0x0002;
+        byte dataLength = 0x0F; // Data length must match
+        short bitmap = 0x0042; // Inner data and speed
+
+        short speed1 = 0x0a0b;
+        short cadence1 = 0x0c0d;
+        short stepLength1 = 0x0e0f;
+        short groundContactTime1 = 0x1011;
+        byte groundImpact1 = 0x12;
+        short swingAngle1 = 0x1314;
+        byte foreFootLanding1 = 0x15;
+        byte midFootLanding1 = 0x16;
+        byte backFootLanding1 = 0x17;
+        byte eversionAngle1 = 0x18;
+
+        short speed2 = 0x191a;
+        short cadence2 = 0x1b1c;
+        short stepLength2 = 0x1d1e;
+        short groundContactTime2 = 0x1f20;
+        byte groundImpact2 = 0x21;
+        short swingAngle2 = 0x2223;
+        byte foreFootLanding2 = 0x24;
+        byte midFootLanding2 = 0x25;
+        byte backFootLanding2 = 0x26;
+        byte eversionAngle2 = 0x27;
+
+        // TODO: Add:
+        //  - swolf
+        //  - stoke rate
+        //  - calories
+        //  - cycling power
+        //  - frequency
+        //  - altitude
+
+        ByteBuffer headerBuf = ByteBuffer.allocate(14);
+        headerBuf.putShort(workoutNumber);
+        headerBuf.putShort(dataNumber);
+        headerBuf.putInt(timestamp);
+        headerBuf.put(interval);
+        headerBuf.putShort(dataCount);
+        headerBuf.put(dataLength);
+        headerBuf.putShort(bitmap);
+
+        ByteBuffer dataBuf = ByteBuffer.allocate(30);
+
+        dataBuf.putShort(speed1);
+        dataBuf.putShort(cadence1);
+        dataBuf.putShort(stepLength1);
+        dataBuf.putShort(groundContactTime1);
+        dataBuf.put(groundImpact1);
+        dataBuf.putShort(swingAngle1);
+        dataBuf.put(foreFootLanding1);
+        dataBuf.put(midFootLanding1);
+        dataBuf.put(backFootLanding1);
+        dataBuf.put(eversionAngle1);
+
+        dataBuf.putShort(speed2);
+        dataBuf.putShort(cadence2);
+        dataBuf.putShort(stepLength2);
+        dataBuf.putShort(groundContactTime2);
+        dataBuf.put(groundImpact2);
+        dataBuf.putShort(swingAngle2);
+        dataBuf.put(foreFootLanding2);
+        dataBuf.put(midFootLanding2);
+        dataBuf.put(backFootLanding2);
+        dataBuf.put(eversionAngle2);
+
+        Field tlvField = HuaweiPacket.class.getDeclaredField("tlv");
+        tlvField.setAccessible(true);
+
+        HuaweiTLV expectedTlv = new HuaweiTLV().put(0x81, new HuaweiTLV()
+                .put(0x02, workoutNumber)
+                .put(0x03, dataNumber)
+                .put(0x04, headerBuf.array())
+                .put(0x05, dataBuf.array())
+        );
+
+        HuaweiPacket packet = new HuaweiPacket(secretsProvider).parse(raw);
+        packet.parseTlv();
+
+        Assert.assertEquals(0x17, packet.serviceId);
+        Assert.assertEquals(0x0a, packet.commandId);
+        Assert.assertEquals(expectedTlv, tlvField.get(packet));
+        Assert.assertTrue(packet.complete);
+        Assert.assertTrue(packet instanceof Workout.WorkoutData.Response);
+
+        Assert.assertEquals(0x0102, ((Workout.WorkoutData.Response) packet).workoutNumber);
+        Assert.assertEquals(0x0304, ((Workout.WorkoutData.Response) packet).dataNumber);
+        Assert.assertArrayEquals(headerBuf.array(), ((Workout.WorkoutData.Response) packet).rawHeader);
+        Assert.assertArrayEquals(dataBuf.array(), ((Workout.WorkoutData.Response) packet).rawData);
+
+        Assert.assertEquals(0x0102, ((Workout.WorkoutData.Response) packet).header.workoutNumber);
+        Assert.assertEquals(0x0304, ((Workout.WorkoutData.Response) packet).header.dataNumber);
+        Assert.assertEquals(0x05060708, ((Workout.WorkoutData.Response) packet).header.timestamp);
+        Assert.assertEquals(0x09, ((Workout.WorkoutData.Response) packet).header.interval);
+        Assert.assertEquals(0x0002, ((Workout.WorkoutData.Response) packet).header.dataCount);
+        Assert.assertEquals(0x0f, ((Workout.WorkoutData.Response) packet).header.dataLength);
+        Assert.assertEquals(0x0042, ((Workout.WorkoutData.Response) packet).header.bitmap);
+
+        Assert.assertEquals(2, ((Workout.WorkoutData.Response) packet).dataList.size());
+
+        Assert.assertNull(((Workout.WorkoutData.Response) packet).dataList.get(0).unknownData);
+        Assert.assertEquals(0x0a0b, ((Workout.WorkoutData.Response) packet).dataList.get(0).speed);
+        Assert.assertEquals(0x0c0d, ((Workout.WorkoutData.Response) packet).dataList.get(0).cadence);
+        Assert.assertEquals(0x0e0f, ((Workout.WorkoutData.Response) packet).dataList.get(0).stepLength);
+        Assert.assertEquals(0x1011, ((Workout.WorkoutData.Response) packet).dataList.get(0).groundContactTime);
+        Assert.assertEquals(0x12, ((Workout.WorkoutData.Response) packet).dataList.get(0).impact);
+        Assert.assertEquals(0x1314, ((Workout.WorkoutData.Response) packet).dataList.get(0).swingAngle);
+        Assert.assertEquals(0x15, ((Workout.WorkoutData.Response) packet).dataList.get(0).foreFootLanding);
+        Assert.assertEquals(0x16, ((Workout.WorkoutData.Response) packet).dataList.get(0).midFootLanding);
+        Assert.assertEquals(0x17, ((Workout.WorkoutData.Response) packet).dataList.get(0).backFootLanding);
+        Assert.assertEquals(0x18, ((Workout.WorkoutData.Response) packet).dataList.get(0).eversionAngle);
+
+        Assert.assertNull(((Workout.WorkoutData.Response) packet).dataList.get(1).unknownData);
+        Assert.assertEquals(0x191a, ((Workout.WorkoutData.Response) packet).dataList.get(1).speed);
+        Assert.assertEquals(0x1b1c, ((Workout.WorkoutData.Response) packet).dataList.get(1).cadence);
+        Assert.assertEquals(0x1d1e, ((Workout.WorkoutData.Response) packet).dataList.get(1).stepLength);
+        Assert.assertEquals(0x1f20, ((Workout.WorkoutData.Response) packet).dataList.get(1).groundContactTime);
+        Assert.assertEquals(0x21, ((Workout.WorkoutData.Response) packet).dataList.get(1).impact);
+        Assert.assertEquals(0x2223, ((Workout.WorkoutData.Response) packet).dataList.get(1).swingAngle);
+        Assert.assertEquals(0x24, ((Workout.WorkoutData.Response) packet).dataList.get(1).foreFootLanding);
+        Assert.assertEquals(0x25, ((Workout.WorkoutData.Response) packet).dataList.get(1).midFootLanding);
+        Assert.assertEquals(0x26, ((Workout.WorkoutData.Response) packet).dataList.get(1).backFootLanding);
+        Assert.assertEquals(0x27, ((Workout.WorkoutData.Response) packet).dataList.get(1).eversionAngle);
+    }
+
+    @Test
+    public void testWorkoutPaceRequest() throws NoSuchFieldException, IllegalAccessException, HuaweiPacket.CryptoException {
+        short workoutNumber = 0x0102;
+        short paceNumber = 0x0304;
+
+        Field tlvField = HuaweiPacket.class.getDeclaredField("tlv");
+        tlvField.setAccessible(true);
+
+        HuaweiTLV expectedTlv = new HuaweiTLV().put(0x81, new HuaweiTLV()
+                .put(0x02, workoutNumber)
+                .put(0x08, paceNumber)
+        );
+
+        byte[] expected = {(byte) 0x5a, (byte) 0x00, (byte) 0x2a, (byte) 0x00, (byte) 0x17, (byte) 0x0c, (byte) 0x7c, (byte) 0x01, (byte) 0x01, (byte) 0x7d, (byte) 0x10, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x7e, (byte) 0x10, (byte) 0x0c, (byte) 0x18, (byte) 0x24, (byte) 0x67, (byte) 0x5e, (byte) 0xe9, (byte) 0x8d, (byte) 0x36, (byte) 0x5f, (byte) 0xde, (byte) 0x1c, (byte) 0x9e, (byte) 0xa0, (byte) 0xd7, (byte) 0x0a, (byte) 0x01, (byte) 0xd3, (byte) 0xce};
+
+        Workout.WorkoutPace.Request request = new Workout.WorkoutPace.Request(secretsProvider, workoutNumber, paceNumber);
+
+        Assert.assertEquals(0x17, request.serviceId);
+        Assert.assertEquals(0x0c, request.commandId);
+        Assert.assertEquals(expectedTlv, tlvField.get(request));
+        Assert.assertTrue(request.complete);
+        List<byte[]> out = request.serialize();
+        Assert.assertEquals(1, out.size());
+        Assert.assertArrayEquals(expected, out.get(0));
+    }
+
+    @Test
+    public void testWorkoutPaceResponse() throws NoSuchFieldException, IllegalAccessException, HuaweiPacket.ParseException {
+        byte[] raw = {(byte) 0x5a, (byte) 0x00, (byte) 0x4a, (byte) 0x00, (byte) 0x17, (byte) 0x0c, (byte) 0x7c, (byte) 0x01, (byte) 0x01, (byte) 0x7d, (byte) 0x10, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x7e, (byte) 0x30, (byte) 0xe8, (byte) 0xfe, (byte) 0xb9, (byte) 0x27, (byte) 0xa6, (byte) 0xc5, (byte) 0x81, (byte) 0x65, (byte) 0x51, (byte) 0xb8, (byte) 0x24, (byte) 0xfe, (byte) 0x2a, (byte) 0xdc, (byte) 0x3d, (byte) 0x22, (byte) 0xd7, (byte) 0x34, (byte) 0x62, (byte) 0xaf, (byte) 0x06, (byte) 0x5f, (byte) 0xfe, (byte) 0x9c, (byte) 0xe8, (byte) 0xa6, (byte) 0x87, (byte) 0x23, (byte) 0xd6, (byte) 0xc7, (byte) 0x7a, (byte) 0xeb, (byte) 0x07, (byte) 0x06, (byte) 0x5c, (byte) 0x35, (byte) 0xe8, (byte) 0x99, (byte) 0xd3, (byte) 0x96, (byte) 0x0b, (byte) 0x99, (byte) 0x38, (byte) 0x65, (byte) 0x48, (byte) 0xcf, (byte) 0x0f, (byte) 0x99, (byte) 0xe2, (byte) 0x23};
+
+        short workoutNumber = 0x0102;
+        short paceNumber = 0x0304;
+
+        short distance1 = 0x0506;
+        byte type1 = 0x07;
+        int pace1 = 0x08090a0b;
+
+        short distance2 = 0x0c0d;
+        byte type2 = 0x0e;
+        int pace2 = 0x0f101112;
+        short correction = 0x1314;
+
+        Field tlvField = HuaweiPacket.class.getDeclaredField("tlv");
+        tlvField.setAccessible(true);
+
+        HuaweiTLV expectedTlv = new HuaweiTLV().put(0x81, new HuaweiTLV()
+                .put(0x02, workoutNumber)
+                .put(0x08, paceNumber)
+                .put(0x83, new HuaweiTLV()
+                        .put(0x04, distance1)
+                        .put(0x05, type1)
+                        .put(0x06, pace1)
+                )
+                .put(0x83, new HuaweiTLV()
+                        .put(0x04, distance2)
+                        .put(0x05, type2)
+                        .put(0x06, pace2)
+                        .put(0x09, correction)
+                )
+        );
+
+        HuaweiPacket packet = new HuaweiPacket(secretsProvider).parse(raw);
+        packet.parseTlv();
+
+        Assert.assertEquals(0x17, packet.serviceId);
+        Assert.assertEquals(0x0c, packet.commandId);
+        Assert.assertEquals(expectedTlv, tlvField.get(packet));
+        Assert.assertTrue(packet.complete);
+        Assert.assertTrue(packet instanceof Workout.WorkoutPace.Response);
+        Assert.assertEquals(0x0102, ((Workout.WorkoutPace.Response) packet).workoutNumber);
+        Assert.assertEquals(0x0304, ((Workout.WorkoutPace.Response) packet).paceNumber);
+        Assert.assertEquals(2, ((Workout.WorkoutPace.Response) packet).blocks.size());
+
+        Assert.assertEquals(0x0506, ((Workout.WorkoutPace.Response) packet).blocks.get(0).distance);
+        Assert.assertEquals(0x07, ((Workout.WorkoutPace.Response) packet).blocks.get(0).type);
+        Assert.assertEquals(0x08090a0b, ((Workout.WorkoutPace.Response) packet).blocks.get(0).pace);
+        Assert.assertEquals(0, ((Workout.WorkoutPace.Response) packet).blocks.get(0).correction);
+
+        Assert.assertEquals(0x0c0d, ((Workout.WorkoutPace.Response) packet).blocks.get(1).distance);
+        Assert.assertEquals(0x0e, ((Workout.WorkoutPace.Response) packet).blocks.get(1).type);
+        Assert.assertEquals(0x0f101112, ((Workout.WorkoutPace.Response) packet).blocks.get(1).pace);
+        Assert.assertEquals(0x1314, ((Workout.WorkoutPace.Response) packet).blocks.get(1).correction);
+    }
+}
diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/export/GPXExporterTest.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/export/GPXExporterTest.java
new file mode 100644
index 0000000..7c61c23
--- /dev/null
+++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/export/GPXExporterTest.java
@@ -0,0 +1,124 @@
+package nodomain.freeyourgadget.gadgetbridge.export;
+
+import com.google.gson.internal.bind.util.ISO8601Utils;
+
+import org.junit.Test;
+import org.xml.sax.SAXException;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.text.ParseException;
+import java.text.ParsePosition;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import javax.xml.XMLConstants;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.Source;
+import javax.xml.transform.stream.StreamSource;
+import javax.xml.validation.Schema;
+import javax.xml.validation.SchemaFactory;
+import javax.xml.validation.Validator;
+
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.entities.User;
+import nodomain.freeyourgadget.gadgetbridge.export.ActivityTrackExporter.GPXTrackEmptyException;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityPoint;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityTrack;
+import nodomain.freeyourgadget.gadgetbridge.model.GPSCoordinate;
+import nodomain.freeyourgadget.gadgetbridge.test.TestBase;
+
+public class GPXExporterTest extends TestBase {
+    @Test
+    public void shouldCreateValidGpxFromSimulatedData() throws IOException, ParseException, GPXTrackEmptyException, SAXException {
+        final List<ActivityPoint> points = readActivityPoints("/GPXExporterTest-SampleTracks.csv");
+
+        final GPXExporter gpxExporter = new GPXExporter();
+        gpxExporter.setCreator("Gadgetbridge Test");
+        final ActivityTrack track = createTestTrack(points);
+
+        final File tempFile = File.createTempFile("gpx-exporter-test-track", ".gpx");
+        tempFile.deleteOnExit();
+
+        gpxExporter.performExport(track, tempFile);
+        validateGpxFile(tempFile);
+    }
+
+    @Test
+    public void shouldCreateValidGpxFromSimulatedDataWithHeartrate() throws IOException, ParseException, GPXTrackEmptyException, ParserConfigurationException, SAXException {
+        final List<ActivityPoint> points = readActivityPoints("/GPXExporterTest-SampleTracksHR.csv");
+
+        final GPXExporter gpxExporter = new GPXExporter();
+        gpxExporter.setCreator("Gadgetbridge Test");
+        final ActivityTrack track = createTestTrack(points);
+
+        final File tempFile = File.createTempFile("gpx-exporter-test-track", ".gpx");
+        tempFile.deleteOnExit();
+
+        gpxExporter.performExport(track, tempFile);
+        validateGpxFile(tempFile);
+    }
+
+    private ActivityTrack createTestTrack(List<ActivityPoint> points) {
+        final User user = new User();
+        user.setName("Test User");
+
+        Device device = new Device();
+        device.setName("Test Device");
+
+        final ActivityTrack track = new ActivityTrack();
+        track.setName("Test Track");
+        track.setBaseTime(new Date());
+        track.setUser(user);
+        track.setDevice(device);
+
+        for (final ActivityPoint point : points) {
+            track.addTrackPoint(point);
+        }
+        return track;
+    }
+
+    private List<ActivityPoint> readActivityPoints(String resourcePath) throws IOException, ParseException {
+        final List<ActivityPoint> points = new ArrayList<>();
+        try (final InputStream inputStream = getClass().getResourceAsStream(resourcePath)) {
+            try (final BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
+                String nextLine = reader.readLine();
+                while (nextLine != null) {
+                    final String[] pieces = nextLine.split("\\s+");
+                    final ActivityPoint point = new ActivityPoint();
+                    point.setLocation(new GPSCoordinate(
+                            Double.parseDouble(pieces[0]),
+                            Double.parseDouble(pieces[1]),
+                            Double.parseDouble(pieces[2]))
+                    );
+
+                    final int dateIndex;
+                    if (pieces.length == 5) {
+                        point.setHeartRate(Integer.parseInt(pieces[3]));
+                        dateIndex = 4;
+                    } else {
+                        dateIndex = 3;
+                    }
+                    // Not sure about this parser but seemed safe to use
+                    point.setTime(ISO8601Utils.parse(pieces[dateIndex], new ParsePosition(0)));
+
+                    points.add(point);
+                    nextLine = reader.readLine();
+                }
+            }
+        }
+        return points;
+    }
+
+    private void validateGpxFile(File tempFile) throws SAXException, IOException {
+        final Source xmlFile = new StreamSource(tempFile);
+        final SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
+        final Schema schema = schemaFactory.newSchema(new StreamSource(getClass().getResourceAsStream("/gpx.xsd")));
+        final Validator validator = schema.newValidator();
+        validator.validate(xmlFile);
+    }
+}
\ No newline at end of file
diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/externalevents/NotificationListenerTest.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/externalevents/NotificationListenerTest.java
new file mode 100644
index 0000000..69ebd37
--- /dev/null
+++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/externalevents/NotificationListenerTest.java
@@ -0,0 +1,111 @@
+package nodomain.freeyourgadget.gadgetbridge.externalevents;
+
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.activities.NotificationFilterActivity;
+import nodomain.freeyourgadget.gadgetbridge.entities.NotificationFilter;
+import nodomain.freeyourgadget.gadgetbridge.test.TestBase;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+public class NotificationListenerTest extends TestBase {
+
+    private NotificationListener mNotificationListener;
+    private List<String> wordList = Arrays.asList("Hello", "world", "test");
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        mNotificationListener = new NotificationListener();
+    }
+
+    @Test
+    public void shouldContinueAfterFilter_TestBlacklistFindAnyWord_WordFound_MustReturnFalse() {
+        String body = "Hello world this is a test";
+        NotificationFilter filter = new NotificationFilter();
+        filter.setNotificationFilterMode(NotificationFilterActivity.NOTIFICATION_FILTER_MODE_BLACKLIST);
+        filter.setNotificationFilterSubMode(NotificationFilterActivity.NOTIFICATION_FILTER_SUBMODE_ANY);
+        assertFalse(mNotificationListener.shouldContinueAfterFilter(body, wordList, filter));
+
+    }
+
+    @Test
+    public void shouldContinueAfterFilter_TestWhitelistFindAnyWord_WordFound_MustReturnTrue() {
+        String body = "Hello world this is a test";
+        NotificationFilter filter = new NotificationFilter();
+        filter.setNotificationFilterMode(NotificationFilterActivity.NOTIFICATION_FILTER_MODE_WHITELIST);
+        filter.setNotificationFilterSubMode(NotificationFilterActivity.NOTIFICATION_FILTER_SUBMODE_ANY);
+        assertTrue(mNotificationListener.shouldContinueAfterFilter(body, wordList, filter));
+
+    }
+
+    @Test
+    public void shouldContinueAfterFilter_TestBlacklistFindAllWords_WordsFound_MustReturnFalse() {
+        String body = "Hello world this is a test";
+        NotificationFilter filter = new NotificationFilter();
+        filter.setNotificationFilterMode(NotificationFilterActivity.NOTIFICATION_FILTER_MODE_BLACKLIST);
+        filter.setNotificationFilterSubMode(NotificationFilterActivity.NOTIFICATION_FILTER_SUBMODE_ALL);
+        assertFalse(mNotificationListener.shouldContinueAfterFilter(body, wordList, filter));
+
+    }
+
+    @Test
+    public void shouldContinueAfterFilter_TestWhitelistFindAllWords_WordsFound_MustReturnTrue() {
+        String body = "Hello world this is a test";
+        NotificationFilter filter = new NotificationFilter();
+        filter.setNotificationFilterMode(NotificationFilterActivity.NOTIFICATION_FILTER_MODE_WHITELIST);
+        filter.setNotificationFilterSubMode(NotificationFilterActivity.NOTIFICATION_FILTER_SUBMODE_ALL);
+        assertTrue(mNotificationListener.shouldContinueAfterFilter(body, wordList, filter));
+    }
+
+    @Test
+    public void shouldContinueAfterFilter_TestBlacklistFindAnyWord_WordNotFound_MustReturnTrue() {
+        String body = "Hallo Welt das ist ein Versuch";
+        NotificationFilter filter = new NotificationFilter();
+        filter.setNotificationFilterMode(NotificationFilterActivity.NOTIFICATION_FILTER_MODE_BLACKLIST);
+        filter.setNotificationFilterSubMode(NotificationFilterActivity.NOTIFICATION_FILTER_SUBMODE_ANY);
+        assertTrue(mNotificationListener.shouldContinueAfterFilter(body, wordList, filter));
+
+    }
+
+    @Test
+    public void shouldContinueAfterFilter_TestWhitelistFindAnyWord_WordNotFound_MustReturnFalse() {
+        String body = "Hallo Welt das ist ein Versuch";
+        NotificationFilter filter = new NotificationFilter();
+        filter.setNotificationFilterMode(NotificationFilterActivity.NOTIFICATION_FILTER_MODE_WHITELIST);
+        filter.setNotificationFilterSubMode(NotificationFilterActivity.NOTIFICATION_FILTER_SUBMODE_ANY);
+        assertFalse(mNotificationListener.shouldContinueAfterFilter(body, wordList, filter));
+
+    }
+
+    @Test
+    public void shouldContinueAfterFilter_TestBlacklistFindAllWords_WordNotFound_MustReturnTrue() {
+        String body = "Hallo Welt das ist ein Versuch";
+        NotificationFilter filter = new NotificationFilter();
+        filter.setNotificationFilterMode(NotificationFilterActivity.NOTIFICATION_FILTER_MODE_BLACKLIST);
+        filter.setNotificationFilterSubMode(NotificationFilterActivity.NOTIFICATION_FILTER_SUBMODE_ALL);
+        assertTrue(mNotificationListener.shouldContinueAfterFilter(body, wordList, filter));
+
+    }
+
+    @Test
+    public void shouldContinueAfterFilter_TestWhitelistFindAllWords_WordNotFound_MustReturnFalse() {
+        String body = "Hallo Welt das ist ein Versuch";
+        NotificationFilter filter = new NotificationFilter();
+        filter.setNotificationFilterMode(NotificationFilterActivity.NOTIFICATION_FILTER_MODE_WHITELIST);
+        filter.setNotificationFilterSubMode(NotificationFilterActivity.NOTIFICATION_FILTER_SUBMODE_ALL);
+        assertFalse(mNotificationListener.shouldContinueAfterFilter(body, wordList, filter));
+    }
+
+    @Test
+    public void shouldContinueAfterFilter_TestFilterNone_MustReturnTrue() {
+        String body = "A text without a meaning";
+        NotificationFilter filter = new NotificationFilter();
+        filter.setNotificationFilterMode(NotificationFilterActivity.NOTIFICATION_FILTER_MODE_NONE);
+        assertTrue(mNotificationListener.shouldContinueAfterFilter(body, wordList, filter));
+    }
+}
\ No newline at end of file
diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/model/ActivityKindTest.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/model/ActivityKindTest.java
new file mode 100644
index 0000000..269a7df
--- /dev/null
+++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/model/ActivityKindTest.java
@@ -0,0 +1,20 @@
+package nodomain.freeyourgadget.gadgetbridge.model;
+
+import static org.junit.Assert.assertNull;
+
+import org.junit.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class ActivityKindTest {
+    @Test
+    public void enumCheckNoOverlap() {
+        // Ensure that no 2 activity kind overlap in codes
+        final Map<Integer, Boolean> knownCodes = new HashMap<>();
+        for (final ActivityKind kind : ActivityKind.values()) {
+            final Boolean existingCode = knownCodes.put(kind.getCode(), true);
+            assertNull("ActivityKind with overlapping codes: " + kind, existingCode);
+        }
+    }
+}
diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/model/ActivitySummaryDataTest.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/model/ActivitySummaryDataTest.java
new file mode 100644
index 0000000..0cf4d42
--- /dev/null
+++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/model/ActivitySummaryDataTest.java
@@ -0,0 +1,122 @@
+package nodomain.freeyourgadget.gadgetbridge.model;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import nodomain.freeyourgadget.gadgetbridge.activities.workouts.entries.ActivitySummaryEntry;
+import nodomain.freeyourgadget.gadgetbridge.activities.workouts.entries.ActivitySummaryProgressEntry;
+import nodomain.freeyourgadget.gadgetbridge.activities.workouts.entries.ActivitySummarySimpleEntry;
+
+public class ActivitySummaryDataTest {
+    /**
+     * Ensure that we can still deserialize old workouts that consisted of manual json and had
+     * no explicit type.
+     */
+    @Test
+    public void deserializeOld() {
+        final String json = "{\n" +
+                "    \"activeSeconds\": {\n" +
+                "        \"group\": \"Some Group\",\n" +
+                "        \"value\": 3828,\n" +
+                "        \"unit\": \"seconds\"\n" +
+                "    },\n" +
+                "    \"averageHR\": {\n" +
+                "        \"value\": 81,\n" +
+                "        \"unit\": \"bpm\"\n" +
+                "    },\n" +
+                "    \"maxHR\": {\n" +
+                "        \"value\": 115,\n" +
+                "        \"unit\": \"bpm\"\n" +
+                "    },\n" +
+                "    \"minHR\": {\n" +
+                "        \"value\": 61,\n" +
+                "        \"unit\": \"bpm\"\n" +
+                "    },\n" +
+                "    \"caloriesBurnt\": {\n" +
+                "        \"value\": 228,\n" +
+                "        \"unit\": \"calories_unit\"\n" +
+                "    },\n" +
+                "    \"hrZoneNa\": {\n" +
+                "        \"value\": 3365,\n" +
+                "        \"unit\": \"seconds\"\n" +
+                "    },\n" +
+                "    \"hrZoneWarmUp\": {\n" +
+                "        \"value\": 447,\n" +
+                "        \"unit\": \"seconds\"\n" +
+                "    },\n" +
+                "    \"aerobicTrainingEffect\": {\n" +
+                "        \"value\": 0.20000000298023224,\n" +
+                "        \"unit\": \"\"\n" +
+                "    },\n" +
+                "    \"currentWorkoutLoad\": {\n" +
+                "        \"value\": 1,\n" +
+                "        \"unit\": \"\"\n" +
+                "    }\n" +
+                "}";
+
+        final ActivitySummaryData summaryData = ActivitySummaryData.fromJson(json);
+
+        assertNotNull(summaryData);
+
+        final Map<String, ActivitySummaryEntry> expected = new LinkedHashMap<String, ActivitySummaryEntry>() {{
+            put("activeSeconds", new ActivitySummarySimpleEntry("Some Group", 3828, "seconds"));
+            put("averageHR", new ActivitySummarySimpleEntry(81, "bpm"));
+            put("maxHR", new ActivitySummarySimpleEntry(115, "bpm"));
+            put("minHR", new ActivitySummarySimpleEntry(61, "bpm"));
+            put("caloriesBurnt", new ActivitySummarySimpleEntry(228, "calories_unit"));
+            put("hrZoneNa", new ActivitySummarySimpleEntry(3365, "seconds"));
+            put("hrZoneWarmUp", new ActivitySummarySimpleEntry(447, "seconds"));
+            put("aerobicTrainingEffect", new ActivitySummarySimpleEntry(0.20000000298023224, ""));
+            put("currentWorkoutLoad", new ActivitySummarySimpleEntry(1, ""));
+        }};
+
+        final List<String> keys = new ArrayList<>(summaryData.getKeys());
+        assertEquals(new ArrayList<>(expected.keySet()), keys);
+
+        for (final Map.Entry<String, ActivitySummaryEntry> e : expected.entrySet()) {
+            final ActivitySummaryEntry jsonEntry = summaryData.get(e.getKey());
+            assertTrue(jsonEntry instanceof ActivitySummarySimpleEntry);
+            assertEquals(e.getValue().getGroup(), jsonEntry.getGroup());
+
+            Number expectedValue = (Number) ((ActivitySummarySimpleEntry) e.getValue()).getValue();
+            Number actualValue = (Number) ((ActivitySummarySimpleEntry) jsonEntry).getValue();
+
+            assertEquals(expectedValue.doubleValue(), actualValue.doubleValue(), 0.000000001d);
+            assertEquals(((ActivitySummarySimpleEntry) e.getValue()).getUnit(), ((ActivitySummarySimpleEntry) jsonEntry).getUnit());
+        }
+    }
+
+    @Test
+    public void deserializeSerializeNew() {
+        final String json = "{" +
+                "\"test_progress\":{" +
+                "\"type\":\"progress\"," +
+                "\"progress\":51," +
+                "\"color\":0," +
+                "\"value\":3828.0," +
+                "\"unit\":\"seconds\"" +
+                "}" +
+                "}";
+
+        final ActivitySummaryData summaryData = ActivitySummaryData.fromJson(json);
+
+        assertNotNull(summaryData);
+
+        ActivitySummaryEntry activitySummaryEntry = summaryData.get("test_progress");
+        assertTrue(activitySummaryEntry instanceof ActivitySummaryProgressEntry);
+
+        ActivitySummaryProgressEntry testProgress = (ActivitySummaryProgressEntry) activitySummaryEntry;
+
+        assertEquals(3828, ((Number) testProgress.getValue()).doubleValue(), 0.000000001d);
+        assertEquals("seconds", testProgress.getUnit());
+        assertEquals(51, testProgress.getProgress());
+
+        assertEquals(json, summaryData.toJson());
+    }
+}
diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceTypeTest.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceTypeTest.java
new file mode 100644
index 0000000..04aa252
--- /dev/null
+++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceTypeTest.java
@@ -0,0 +1,37 @@
+/*  Copyright (C) 2023 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.model;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.test.TestBase;
+
+public class DeviceTypeTest extends TestBase {
+    @Test
+    public void ensureNoMissingDeviceInfo() {
+        // Check that all coordinators for all device types declare valid device names, icons and manufacturer
+        for (final DeviceType deviceType : DeviceType.values()) {
+            final DeviceCoordinator coordinator = deviceType.getDeviceCoordinator();
+            Assert.assertNotEquals("Device name for " + deviceType + " is 0", 0, coordinator.getDeviceNameResource());
+            Assert.assertNotEquals("Device icon for " + deviceType + " is 0", 0, coordinator.getDefaultIconResource());
+            Assert.assertNotEquals("Disabled device icon for " + deviceType + " is 0", 0, coordinator.getDisabledIconResource());
+            Assert.assertNotEquals("Manufacturer for " + deviceType + " is null", null, coordinator.getManufacturer());
+        }
+    }
+}
diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationServiceTestCase.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationServiceTestCase.java
new file mode 100644
index 0000000..b066b54
--- /dev/null
+++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationServiceTestCase.java
@@ -0,0 +1,135 @@
+package nodomain.freeyourgadget.gadgetbridge.service;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_NOTIFICATION_BODY;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+
+import org.junit.Ignore;
+import org.junit.Test;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.GBException;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.test.TestBase;
+import nodomain.freeyourgadget.gadgetbridge.util.GBPrefs;
+
+public class DeviceCommunicationServiceTestCase extends TestBase {
+    private static final java.lang.String TEST_DEVICE_ADDRESS = TestDeviceSupport.class.getName();
+
+    /**
+     * Factory that always returns the mockSupport instance
+     */
+    private class TestDeviceSupportFactory extends DeviceSupportFactory {
+        TestDeviceSupportFactory(Context context) {
+            super(context);
+        }
+
+        @Override
+        public synchronized DeviceSupport createDeviceSupport(GBDevice device) throws GBException {
+            return mockSupport;
+        }
+    }
+
+    private TestDeviceService mDeviceService;
+    @Mock
+    private TestDeviceSupport realSupport;
+    private TestDeviceSupport mockSupport;
+
+    public DeviceCommunicationServiceTestCase() {
+        super();
+    }
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        mockSupport = null;
+        realSupport = new TestDeviceSupport();
+        realSupport.setContext(new GBDevice(TEST_DEVICE_ADDRESS, "Test Device", "Test Device Alias", "Test Folder", DeviceType.TEST), null, getContext());
+        mockSupport = Mockito.spy(realSupport);
+        DeviceCommunicationService.setDeviceSupportFactory(new TestDeviceSupportFactory(getContext()));
+
+        mDeviceService = new TestDeviceService(getContext());
+    }
+
+    private GBDevice getDevice() {
+        return realSupport.getDevice();
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        mDeviceService.stopService(mDeviceService.createIntent());
+        super.tearDown();
+    }
+
+    @Test
+    public void testNotConnected() {
+        GBDevice device = getDevice();
+        assertEquals(GBDevice.State.NOT_CONNECTED, device.getState());
+
+        // verify that the events like onFindDevice do not reach the DeviceSupport instance,
+        // because not connected
+        InOrder inOrder = Mockito.inOrder(mockSupport);
+        mDeviceService.onFindDevice(true);
+        inOrder.verify(mockSupport, Mockito.times(0)).onFindDevice(true);
+        inOrder.verifyNoMoreInteractions();
+    }
+
+    @Test
+    public void ensureConnected() {
+        // connection goes synchronously here
+        mDeviceService.forDevice(getDevice()).connect();
+        Mockito.verify(mockSupport, Mockito.times(1)).connect();
+        assertTrue(getDevice().isInitialized());
+    }
+
+    @Ignore //FIXME, probably broken after adding multi-device support
+    @Test
+    public void testFindDevice() {
+        ensureConnected();
+
+        InOrder inOrder = Mockito.inOrder(mockSupport);
+        mDeviceService.onFindDevice(true);
+        mDeviceService.onFindDevice(false);
+        inOrder.verify(mockSupport, Mockito.times(1)).onFindDevice(true);
+        inOrder.verify(mockSupport, Mockito.times(1)).onFindDevice(false);
+        inOrder.verifyNoMoreInteractions();
+    }
+
+    @Ignore //FIXME, probably broken after adding multi-device support
+    @Test
+    public void testTransliterationSupport() {
+        SharedPreferences settings = GBApplication.getPrefs().getPreferences();
+        SharedPreferences.Editor editor = settings.edit();
+        editor.putBoolean("transliteration", true);
+        editor.commit();
+
+        Intent intent = mDeviceService.createIntent().putExtra(EXTRA_NOTIFICATION_BODY, "袩褉玫褋褌芯 褌械泻膷褌");
+        mDeviceService.invokeService(intent);
+        String result = intent.getStringExtra(EXTRA_NOTIFICATION_BODY);
+
+        assertEquals("Transliteration support fail!", "Prosto tekct", result);
+    }
+
+    @Test
+    public void testRtlSupport() {
+        SharedPreferences settings = GBApplication.getPrefs().getPreferences();
+        SharedPreferences.Editor editor = settings.edit();
+        editor.putBoolean("transliteration", false);
+        editor.putBoolean(GBPrefs.RTL_SUPPORT, true);
+        editor.commit();
+
+        Intent intent = mDeviceService.createIntent().putExtra(EXTRA_NOTIFICATION_BODY, "English and 注讘专讬转");
+        mDeviceService.invokeService(intent);
+        String result = intent.getStringExtra(EXTRA_NOTIFICATION_BODY);
+
+        assertEquals("Rtl support fail!", "转讬专讘注 English and", result);
+    }
+}
diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/TestBtBRQueue.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/TestBtBRQueue.java
new file mode 100644
index 0000000..5984566
--- /dev/null
+++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/TestBtBRQueue.java
@@ -0,0 +1,68 @@
+/*  Copyright (C) 2022-2023 Martin.JM
+
+    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 <http://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.when;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mockito;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.btbr.BtBRQueue;
+
+@RunWith(MockitoJUnitRunner.class)
+public class TestBtBRQueue {
+
+    @Test
+    public void connect() {
+        GBDevice device = Mockito.mock(GBDevice.class);
+        when(device.isConnected()).thenReturn(false);
+
+        BluetoothDevice btDevice = Mockito.mock(BluetoothDevice.class);
+
+        BluetoothAdapter btAdapter = Mockito.mock(BluetoothAdapter.class);
+        when(btAdapter.getRemoteDevice((String) any())).thenReturn(btDevice);
+
+        BtBRQueue queue = new BtBRQueue(btAdapter, device, null, null, null, 512);
+        Assert.assertTrue(queue.connect());
+    }
+
+    @Test
+    public void reconnect() {
+        GBDevice device = Mockito.mock(GBDevice.class);
+        when(device.isConnected()).thenReturn(false);
+
+        BluetoothDevice btDevice = Mockito.mock(BluetoothDevice.class);
+
+        BluetoothAdapter btAdapter = Mockito.mock(BluetoothAdapter.class);
+        when(btAdapter.getRemoteDevice((String) any())).thenReturn(btDevice);
+
+        BtBRQueue queue = new BtBRQueue(btAdapter, device, null, null, null, 512);
+        Assert.assertTrue(queue.connect());
+
+        queue.disconnect();
+
+        Assert.assertTrue(queue.connect());
+    }
+}
diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/TestDeviceService.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/TestDeviceService.java
new file mode 100644
index 0000000..bd4818f
--- /dev/null
+++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/TestDeviceService.java
@@ -0,0 +1,60 @@
+package nodomain.freeyourgadget.gadgetbridge.service;
+
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+
+import org.robolectric.Robolectric;
+import org.robolectric.android.controller.ServiceController;
+
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceService;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
+
+/**
+ * Extends GBDeviceServer so that communication with the service works
+ * with Robolectric.
+ */
+class TestDeviceService extends GBDeviceService {
+    private final ServiceController<DeviceCommunicationService> serviceController;
+    private final DeviceCommunicationService service;
+
+    TestDeviceService(Context context) {
+        this(context, null);
+    }
+
+    TestDeviceService(final Context context, final GBDevice device) {
+        super(context, device);
+
+        serviceController = Robolectric.buildService(DeviceCommunicationService.class, createIntent());
+        service = serviceController.create().get();
+    }
+
+    @Override
+    public DeviceService forDevice(final GBDevice device) {
+        return new TestDeviceService(mContext, device);
+    }
+
+    @Override
+    protected void invokeService(Intent intent) {
+        if (getDevice() != null) {
+            intent.putExtra(GBDevice.EXTRA_DEVICE, getDevice());
+        }
+
+        // calling though to the service natively does not work with robolectric,
+        // we have to use the ServiceController to do that
+        service.onStartCommand(intent, Service.START_FLAG_REDELIVERY, (int) (Math.random() * 10000));
+        super.invokeService(intent);
+    }
+
+    @Override
+    protected void stopService(Intent intent) {
+        super.stopService(intent);
+        serviceController.destroy();
+    }
+
+    @Override
+    public Intent createIntent() {
+        return super.createIntent();
+    }
+}
diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/TestDeviceSupport.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/TestDeviceSupport.java
new file mode 100644
index 0000000..3e160db
--- /dev/null
+++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/TestDeviceSupport.java
@@ -0,0 +1,49 @@
+package nodomain.freeyourgadget.gadgetbridge.service;
+
+import android.bluetooth.BluetoothAdapter;
+import android.content.Context;
+import android.location.Location;
+import android.net.Uri;
+
+import java.util.ArrayList;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
+import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.Reminder;
+import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.WorldClock;
+
+class TestDeviceSupport extends AbstractDeviceSupport {
+
+    TestDeviceSupport() {
+    }
+
+    @Override
+    public void setContext(GBDevice gbDevice, BluetoothAdapter btAdapter, Context context) {
+        super.setContext(gbDevice, btAdapter, context);
+    }
+
+    @Override
+    public boolean connect() {
+        gbDevice.setState(GBDevice.State.INITIALIZED);
+        gbDevice.sendDeviceUpdateIntent(getContext());
+        return true;
+    }
+
+    @Override
+    public void dispose() {
+
+    }
+
+    @Override
+    public boolean useAutoConnect() {
+        return false;
+    }
+}
diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BtLEQueueTest.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BtLEQueueTest.java
new file mode 100644
index 0000000..327728a
--- /dev/null
+++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BtLEQueueTest.java
@@ -0,0 +1,23 @@
+package nodomain.freeyourgadget.gadgetbridge.service.btle;
+
+import org.junit.Test;
+
+import nodomain.freeyourgadget.gadgetbridge.test.TestBase;
+
+public class BtLEQueueTest extends TestBase {
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    @Test
+    public void testIsConnected() throws Exception {
+        // TODO
+    }
+}
\ No newline at end of file
diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/cmfwatchpro/CmfCommandTest.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/cmfwatchpro/CmfCommandTest.java
new file mode 100644
index 0000000..678be85
--- /dev/null
+++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/cmfwatchpro/CmfCommandTest.java
@@ -0,0 +1,39 @@
+/*  Copyright (C) 2024 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.cmfwatchpro;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class CmfCommandTest {
+    @Test
+    public void commandEnumCheckNoOverlap() {
+        // Ensure that no 2 commands overlap in codes
+        final Map<String, Boolean> knownCodes = new HashMap<>();
+        for (final CmfCommand cmd : CmfCommand.values()) {
+            final Boolean existingCode = knownCodes.put(
+                    String.format("cmd1=0x%04x cmd2=0x%04x", cmd.getCmd1(), cmd.getCmd2()),
+                    true
+            );
+            assertNull("Commands with overlapping codes", existingCode);
+        }
+    }
+}
diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/fitpro/FitProDeviceSupportTest.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/fitpro/FitProDeviceSupportTest.java
new file mode 100644
index 0000000..7ea5ea9
--- /dev/null
+++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/fitpro/FitProDeviceSupportTest.java
@@ -0,0 +1,69 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.fitpro;
+
+import static junit.framework.TestCase.assertEquals;
+import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.CMD_FIND_BAND;
+import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.CMD_GET_HW_INFO;
+import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.CMD_GROUP_GENERAL;
+import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.CMD_GROUP_REQUEST_DATA;
+import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.CMD_SET_DEVICE_VIBRATIONS;
+import static nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProConstants.VALUE_ON;
+import static nodomain.freeyourgadget.gadgetbridge.util.ArrayUtils.arrayToString;
+
+import org.junit.Test;
+
+// invoke individual test this way:
+//./gradlew :app:testMainDebugUnitTest --tests nodomain.freeyourgadget.gadgetbridge.service.devices.fitpro.FitProDeviceSupportTest
+
+public class FitProDeviceSupportTest {
+
+    @Test
+    public void testCraftDataFromCommandAndSingleValue() {
+        byte[] data = FitProDeviceSupport.craftData(CMD_GROUP_REQUEST_DATA, CMD_GET_HW_INFO);
+        String result = arrayToString(data);
+        String expected = arrayToString(new byte[]{(byte) 0xCD, 0x00, 0x05, 0x1A, 0x01, 0x10, 0x00, 0x00});
+        System.out.println("fitpro test, data 1: " + result);
+        assertEquals(expected, result);
+    }
+
+    @Test
+    public void testCraftDataFromCommandSingleValueAndParameter() {
+        byte[] data = FitProDeviceSupport.craftData(CMD_GROUP_GENERAL, CMD_FIND_BAND, VALUE_ON);
+        String result = arrayToString(data);
+        String expected = arrayToString(new byte[]{(byte) 0xCD, 0x00, 0x06, 0x12, 0x01, 0x0B, 0x00, 0x01, 0x01});
+        System.out.println("fitpro test, data 2: " + result);
+        assertEquals(expected, result);
+    }
+
+    @Test
+    public void testCraftDataFromCommandAndByteArray() {
+        byte[] data = FitProDeviceSupport.craftData(CMD_GROUP_GENERAL, CMD_SET_DEVICE_VIBRATIONS, new byte[]{0, 0, 0, 0});
+        String result = arrayToString(data);
+        String expected = arrayToString(new byte[]{(byte) 0xCD, 0x00, 0x09, 0x12, 0x01, 0x08, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00});
+        System.out.println("fitpro test, data 3: " + result);
+        assertEquals(expected, result);
+    }
+
+    public byte[] stringToByteArray(String s) {
+        byte[] byteArray = new byte[s.length() / 2];
+        String[] strBytes = new String[s.length() / 2];
+        int k = 0;
+        for (int i = 0; i < s.length(); i = i + 2) {
+            int j = i + 2;
+            strBytes[k] = s.substring(i, j);
+            byteArray[k] = (byte) Integer.parseInt(strBytes[k], 16);
+            k++;
+        }
+        return byteArray;
+    }
+
+    public static byte[] stringWith0xToByteArray(String s) {
+        String[] split = s.split(" ");
+        int k = 0;
+        byte[] byteArray = new byte[split.length];
+        for (String ch : split) {
+            byteArray[k] = (byte) Integer.parseInt(ch.split("x")[1], 16);
+            k++;
+        }
+        return byteArray;
+    }
+}
\ No newline at end of file
diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/GarminSupportTest.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/GarminSupportTest.java
new file mode 100644
index 0000000..7b09dce
--- /dev/null
+++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/GarminSupportTest.java
@@ -0,0 +1,374 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.math.BigInteger;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.FieldDefinition;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.FitFile;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.GlobalFITMessage;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordData;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordDefinition;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordHeader;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.baseTypes.BaseType;
+import nodomain.freeyourgadget.gadgetbridge.test.TestBase;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class GarminSupportTest extends TestBase {
+    @Test
+    public void testBaseFields() {
+
+        RecordDefinition recordDefinition = new RecordDefinition(new RecordHeader((byte) 6), ByteOrder.LITTLE_ENDIAN, GlobalFITMessage.WEATHER, null, null); //just some random data
+        List<FieldDefinition> fieldDefinitionList = new ArrayList<>();
+        for (BaseType baseType :
+                BaseType.values()) {
+            fieldDefinitionList.add(new FieldDefinition(baseType.getIdentifier(), baseType.getSize(), baseType, baseType.name()));
+
+        }
+        recordDefinition.setFieldDefinitions(fieldDefinitionList);
+
+        RecordData test = new RecordData(recordDefinition, recordDefinition.getRecordHeader());
+
+        for (BaseType baseType :
+                BaseType.values()) {
+            System.out.println(baseType.getIdentifier());
+            Object startVal, endVal;
+
+            switch (baseType.name()) {
+                case "ENUM":
+                case "UINT8":
+                case "BASE_TYPE_BYTE":
+                    startVal = 0;
+                    test.setFieldByName(baseType.name(), startVal);
+                    endVal = test.getFieldByName(baseType.name());
+                    Assert.assertEquals(startVal, endVal);
+                    startVal = (int) 0xff - 1;
+                    test.setFieldByName(baseType.name(), startVal);
+                    endVal = test.getFieldByName(baseType.name());
+                    Assert.assertEquals(startVal, endVal);
+                    startVal = -1;
+                    test.setFieldByName(baseType.name(), startVal);
+                    endVal = test.getFieldByName(baseType.name());
+                    Assert.assertNull(endVal);
+                    break;
+                case "SINT8":
+                    startVal = (int) Byte.MIN_VALUE;
+                    test.setFieldByName(baseType.name(), startVal);
+                    endVal = test.getFieldByName(baseType.name());
+                    Assert.assertEquals(startVal, endVal);
+                    startVal = (int) Byte.MAX_VALUE - 1;
+                    test.setFieldByName(baseType.name(), startVal);
+                    endVal = test.getFieldByName(baseType.name());
+                    Assert.assertEquals(startVal, endVal);
+                    startVal = (int) Byte.MIN_VALUE - 1;
+                    test.setFieldByName(baseType.name(), startVal);
+                    endVal = test.getFieldByName(baseType.name());
+                    Assert.assertNull(endVal);
+                    break;
+                case "SINT16":
+                    startVal = (int) Short.MIN_VALUE;
+                    test.setFieldByName(baseType.name(), startVal);
+                    endVal = test.getFieldByName(baseType.name());
+                    Assert.assertEquals(startVal, endVal);
+                    startVal = (int) Short.MAX_VALUE - 1;
+                    test.setFieldByName(baseType.name(), startVal);
+                    endVal = test.getFieldByName(baseType.name());
+                    Assert.assertEquals(startVal, endVal);
+                    startVal = (int) Short.MIN_VALUE - 1;
+                    test.setFieldByName(baseType.name(), startVal);
+                    endVal = test.getFieldByName(baseType.name());
+                    Assert.assertNull(endVal);
+                    break;
+                case "UINT16":
+                    startVal = 0;
+                    test.setFieldByName(baseType.name(), startVal);
+                    endVal = test.getFieldByName(baseType.name());
+                    Assert.assertEquals(startVal, endVal);
+                    startVal = (int) 0xffff - 1;
+                    test.setFieldByName(baseType.name(), startVal);
+                    endVal = test.getFieldByName(baseType.name());
+                    Assert.assertEquals(startVal, endVal);
+                    startVal = -1;
+                    test.setFieldByName(baseType.name(), startVal);
+                    endVal = test.getFieldByName(baseType.name());
+                    Assert.assertNull(endVal);
+                    break;
+                case "SINT32":
+                    startVal = (long) Integer.MIN_VALUE;
+                    test.setFieldByName(baseType.name(), startVal);
+                    endVal = test.getFieldByName(baseType.name());
+                    Assert.assertEquals(startVal, endVal);
+                    startVal = (long) Integer.MAX_VALUE - 1;
+                    test.setFieldByName(baseType.name(), startVal);
+                    endVal = test.getFieldByName(baseType.name());
+                    Assert.assertEquals(startVal, endVal);
+                    startVal = (long) Integer.MIN_VALUE - 1;
+                    test.setFieldByName(baseType.name(), startVal);
+                    endVal = test.getFieldByName(baseType.name());
+                    Assert.assertNull(endVal);
+                    break;
+                case "UINT32":
+                    startVal = 0L;
+                    test.setFieldByName(baseType.name(), startVal);
+                    endVal = test.getFieldByName(baseType.name());
+                    Assert.assertEquals(startVal, endVal);
+                    startVal = (long) 0xffffffffL - 1;
+                    test.setFieldByName(baseType.name(), startVal);
+                    endVal = test.getFieldByName(baseType.name());
+                    Assert.assertEquals(startVal, (long) ((long) endVal & 0xffffffffL));
+                    startVal = 0xffffffff;
+                    test.setFieldByName(baseType.name(), startVal);
+                    endVal = test.getFieldByName(baseType.name());
+                    Assert.assertNull(endVal);
+                    break;
+                case "FLOAT32":
+                    startVal = 0.0f;
+                    test.setFieldByName(baseType.name(), startVal);
+                    endVal = test.getFieldByName(baseType.name());
+                    Assert.assertEquals(startVal, endVal);
+                    startVal = -Float.MAX_VALUE;
+                    test.setFieldByName(baseType.name(), startVal);
+                    endVal = test.getFieldByName(baseType.name());
+                    Assert.assertEquals(startVal, endVal);
+                    startVal = Float.MAX_VALUE;
+                    test.setFieldByName(baseType.name(), startVal);
+                    endVal = test.getFieldByName(baseType.name());
+                    Assert.assertEquals(startVal, endVal);
+                    startVal = (double) -Float.MAX_VALUE * 2;
+                    test.setFieldByName(baseType.name(), startVal);
+                    endVal = test.getFieldByName(baseType.name());
+                    Assert.assertNull(endVal);
+                    break;
+                case "FLOAT64":
+                    startVal = 0.0d;
+                    test.setFieldByName(baseType.name(), startVal);
+                    endVal = test.getFieldByName(baseType.name());
+                    Assert.assertEquals(startVal, endVal);
+                    startVal = Double.MIN_VALUE;
+                    test.setFieldByName(baseType.name(), startVal);
+                    endVal = test.getFieldByName(baseType.name());
+                    Assert.assertEquals(startVal, endVal);
+                    startVal = Double.MAX_VALUE;
+                    test.setFieldByName(baseType.name(), startVal);
+                    endVal = test.getFieldByName(baseType.name());
+                    Assert.assertEquals(startVal, endVal);
+                    startVal = (double) -Double.MAX_VALUE * 2;
+                    test.setFieldByName(baseType.name(), startVal);
+                    endVal = test.getFieldByName(baseType.name());
+                    Assert.assertNull(endVal);
+                    break;
+                case "UINT8Z":
+                    startVal = 1;
+                    test.setFieldByName(baseType.name(), startVal);
+                    endVal = test.getFieldByName(baseType.name());
+                    Assert.assertEquals(startVal, endVal);
+                    startVal = (int) 0xff;
+                    test.setFieldByName(baseType.name(), startVal);
+                    endVal = test.getFieldByName(baseType.name());
+                    Assert.assertEquals(startVal, endVal);
+                    startVal = 0;
+                    test.setFieldByName(baseType.name(), startVal);
+                    endVal = test.getFieldByName(baseType.name());
+                    Assert.assertNull(endVal);
+                    startVal = -1;
+                    test.setFieldByName(baseType.name(), startVal);
+                    endVal = test.getFieldByName(baseType.name());
+                    Assert.assertNull(endVal);
+                    break;
+                case "UINT16Z":
+                    startVal = 1;
+                    test.setFieldByName(baseType.name(), startVal);
+                    endVal = test.getFieldByName(baseType.name());
+                    Assert.assertEquals(startVal, endVal);
+                    startVal = (int) 0xffff;
+                    test.setFieldByName(baseType.name(), startVal);
+                    endVal = test.getFieldByName(baseType.name());
+                    Assert.assertEquals(startVal, endVal);
+                    startVal = -1;
+                    test.setFieldByName(baseType.name(), startVal);
+                    endVal = test.getFieldByName(baseType.name());
+                    Assert.assertNull(endVal);
+                    startVal = 0;
+                    test.setFieldByName(baseType.name(), startVal);
+                    endVal = test.getFieldByName(baseType.name());
+                    Assert.assertNull(endVal);
+                    break;
+                case "UINT32Z":
+                    startVal = 1L;
+                    test.setFieldByName(baseType.name(), startVal);
+                    endVal = test.getFieldByName(baseType.name());
+                    Assert.assertEquals(startVal, endVal);
+                    startVal = (long) 0xffffffffL;
+                    test.setFieldByName(baseType.name(), startVal);
+                    endVal = test.getFieldByName(baseType.name());
+                    Assert.assertEquals(startVal, (long) ((long) endVal & 0xffffffffL));
+                    startVal = -1;
+                    test.setFieldByName(baseType.name(), startVal);
+                    endVal = test.getFieldByName(baseType.name());
+                    Assert.assertNull(endVal);
+                    startVal = 0;
+                    test.setFieldByName(baseType.name(), startVal);
+                    endVal = test.getFieldByName(baseType.name());
+                    Assert.assertNull(endVal);
+                    break;
+                case "SINT64":
+                    startVal = BigInteger.valueOf(Long.MIN_VALUE);
+                    test.setFieldByName(baseType.name(), startVal);
+                    endVal = test.getFieldByName(baseType.name());
+                    Assert.assertEquals(((BigInteger) startVal).longValue(), endVal);
+                    startVal = BigInteger.valueOf(Long.MAX_VALUE - 1);
+                    test.setFieldByName(baseType.name(), startVal);
+                    endVal = test.getFieldByName(baseType.name());
+                    Assert.assertEquals(((BigInteger) startVal).longValue(), endVal);
+                    startVal = BigInteger.valueOf(Long.MAX_VALUE);
+                    test.setFieldByName(baseType.name(), startVal);
+                    endVal = test.getFieldByName(baseType.name());
+                    Assert.assertNull(endVal);
+                    break;
+                case "UINT64":
+                    startVal = 0L;
+                    test.setFieldByName(baseType.name(), startVal);
+                    endVal = test.getFieldByName(baseType.name());
+                    Assert.assertEquals(startVal, endVal);
+                    startVal = BigInteger.valueOf(0xFFFFFFFFFFFFFFFFL - 1);
+                    test.setFieldByName(baseType.name(), startVal);
+                    endVal = test.getFieldByName(baseType.name());
+                    Assert.assertEquals(((BigInteger) startVal).longValue() & 0xFFFFFFFFFFFFFFFFL, endVal);
+                    startVal = BigInteger.valueOf(0xFFFFFFFFFFFFFFFFL);
+                    test.setFieldByName(baseType.name(), startVal);
+                    endVal = test.getFieldByName(baseType.name());
+                    Assert.assertNull(endVal);
+                    break;
+                case "UINT64Z":
+                    startVal = 1L;
+                    test.setFieldByName(baseType.name(), startVal);
+                    endVal = test.getFieldByName(baseType.name());
+                    Assert.assertEquals(startVal, endVal);
+                    startVal = BigInteger.valueOf(0xFFFFFFFFFFFFFFFFL);
+                    test.setFieldByName(baseType.name(), startVal);
+                    endVal = test.getFieldByName(baseType.name());
+                    Assert.assertEquals(((BigInteger) startVal).longValue() & 0xFFFFFFFFFFFFFFFFL, endVal);
+                    startVal = 0L;
+                    test.setFieldByName(baseType.name(), startVal);
+                    endVal = test.getFieldByName(baseType.name());
+                    Assert.assertNull(endVal);
+                    break;
+                case "STRING":
+                    //TODO
+                    break;
+                default:
+                    System.out.println(baseType.name());
+                    Assert.assertFalse(true); //we should not end up here, if it happen we forgot a case in the switch
+            }
+
+        }
+
+    }
+
+    @Test
+    public void TestFitFileSettings2() {
+
+        byte[] fileContents = GB.hexStringToByteArray("0e101405b90600002e464954b18b40000000000603048c04048601028402" +
+                "028405028400010000ed2adce7ffffffff01001906ffff02410000310002" +
+                "000284010102015401ff4200000200160104860204860001020301000401" +
+                "000501010a01000b01000c01000d01020e01020f01021001001101001201" +
+                "001501001601001a01001b01001d01003401003501000200000000000000" +
+                "0000000000030002000032ffffff0100fe00000001430000030013000807" +
+                "0402840101000201020301020501000601000701000801020a01020b0102" +
+                "0c01000d01000e0100100100110100120100150100180102036564676535" +
+                "3130000c030129b70000003cb9b901000001a80200ff440000040004fe02" +
+                "8401028b00010203010a04000058c3010145000006002400040703048627" +
+                "040a290c0afe028404028b05028b06028b07028b0802840902840a02840b" +
+                "02842a028b0101000201000c01020d01020e01020f010210010211010212" +
+                "010213010214010215010a16010a17010a18010a23030224010025010226" +
+                "010a28010a2b010a2c01000545564f00849eb90227350000171513121110" +
+                "0f0e0d0c0b00000000000000000001ba300800005000f4010000ffff0101" +
+                "0000000001fe01000000050032ff04ff020b000046000006002400050703" +
+                "048627040a290c0afe028404028b05028b06028b07028b0802840902840a" +
+                "02840b02842a028b0101000201000c01020d01020e01020f010210010211" +
+                "010212010213010214010215010a16010a17010a18010a23030224010025" +
+                "010226010a28010a2b010a2c0100065032534c0000000000273500001715" +
+                "131211100f0e0d0c0b000100000000000000316e300800005a00f4010000" +
+                "ffff01010000000001fe01000000050076be04ff020b0000470000060024" +
+                "00090703048627040a290c0afe028404028b05028b06028b07028b080284" +
+                "0902840a02840b02842a028b0101000201000c01020d01020e01020f0102" +
+                "10010211010212010213010214010215010a16010a17010a18010a230302" +
+                "24010025010226010a28010a2b010a2c0100074c414e47535445520013cc" +
+                "1200273500001715131211100f0e0d0c0b000200000000000000632a3008" +
+                "00005f00f4010000ffff010100000000010001000000050032ff04ff020b" +
+                "000048000006002400020703048627040a290c0afe028404028b05028b06" +
+                "028b07028b0802840902840a02840b02842a028b0101000201000c01020d" +
+                "01020e01020f010210010211010212010213010214010215010a16010a17" +
+                "010a18010a23030224010025010226010a28010a2b010a2c0100084d0000" +
+                "000000352700001715131211100f0e0d0c0b000300000000000000697a30" +
+                "0800005f00f4010000ffff010100000000010001000000050032ff04ff02" +
+                "0b000049000006002400070703048627040a290c0afe028404028b05028b" +
+                "06028b07028b0802840902840a02840b02842a028b0101000201000c0102" +
+                "0d01020e01020f010210010211010212010213010214010215010a16010a" +
+                "17010a18010a23030224010025010226010a28010a2b010a2c0100094269" +
+                "6b6520350000000000273500001715131211100f0e0d0c0b000400000000" +
+                "0000000000300800005f00f4010000ffff01010000000000fe0000000000" +
+                "0032ff04ff020b00000942696b6520360000000000273500001715131211" +
+                "100f0e0d0c0b0005000000000000000000300800005f00f4010000ffff01" +
+                "010000000000fe00000000000032ff04ff020b00000942696b6520370000" +
+                "000000273500001715131211100f0e0d0c0b000600000000000000000030" +
+                "0800005f00f4010000ffff01010000000000fe00000000000032ff04ff02" +
+                "0b00000942696b6520380000000000273500001715131211100f0e0d0c0b" +
+                "0007000000000000000000300800005f00f4010000ffff01010000000000" +
+                "fe00000000000032ff04ff020b00000942696b6520390000000000273500" +
+                "001715131211100f0e0d0c0b0008000000000000000000300800005f00f4" +
+                "010000ffff01010000000000fe00000000000032ff04ff020b00004a0000" +
+                "06002400080703048627040a290c0afe028404028b05028b06028b07028b" +
+                "0802840902840a02840b02842a028b0101000201000c01020d01020e0102" +
+                "0f010210010211010212010213010214010215010a16010a17010a18010a" +
+                "23030224010025010226010a28010a2b010a2c01000a42696b6520313000" +
+                "00000000273500001715131211100f0e0d0c0b0009000000000000000000" +
+                "300800005f00f4010000ffff01010000000000fe00000000000032ff04ff" +
+                "020b00004b00007f00090309070001000401000501000601000701000801" +
+                "000901000a01000b45646765203531300000ffffffffffffff09ef");//https://github.com/polyvertex/fitdecode/blob/48b6554d8a3baf33f8b5b9b2fd079fcbe9ac8ce2/tests/files/Settings2.fit
+
+        String expectedOutput = "[" +
+                "FitFileId{serial_number=3889965805, manufacturer=1, product=1561, type=SETTINGS}, " +
+                "FitFileCreator{software_version=340}, " +
+                "FitDeviceSettings{utc_offset=0, time_offset=0, active_time_zone=0, unknown_3(ENUM/1)=0, time_mode=0, time_zone_offset=0, unknown_10(ENUM/1)=3, unknown_11(ENUM/1)=0, backlight_mode=2, unknown_13(UINT8/1)=0, unknown_14(UINT8/1)=0, unknown_15(UINT8/1)=50, unknown_21(ENUM/1)=1, unknown_22(ENUM/1)=0, unknown_26(ENUM/1)=254, unknown_27(ENUM/1)=0, unknown_29(ENUM/1)=0, unknown_52(ENUM/1)=0, unknown_53(ENUM/1)=1}, " +
+                "FitUserProfile{friendly_name=edge510, weight=78.0, gender=1, age=41, height=183, language=english, elev_setting=metric, weight_setting=metric, resting_heart_rate=60, default_max_biking_heart_rate=185, default_max_heart_rate=185, hr_setting=1, speed_setting=metric, dist_setting=metric, power_setting=1, activity_class=168, position_setting=2, temperature_setting=metric}, " +
+                "RecordData{UNK_4, unknown_254(UINT16/2)=0, unknown_1(UINT16Z/2)=50008, unknown_0(UINT8/1)=1, unknown_3(UINT8Z/1)=1}, " +
+                "RecordData{UNK_6, unknown_0(STRING/4)=EVO, unknown_3(UINT32/4)=45719172, unknown_39(UINT8Z/4)=[39,53,,], unknown_41(UINT8Z/12)=[23,21,19,18,17,16,15,14,13,12,11,], unknown_254(UINT16/2)=0, unknown_7(UINT16Z/2)=47617, unknown_8(UINT16/2)=2096, unknown_9(UINT16/2)=0, unknown_10(UINT16/2)=80, unknown_11(UINT16/2)=500, unknown_12(UINT8/1)=1, unknown_13(UINT8/1)=1, unknown_14(UINT8/1)=0, unknown_15(UINT8/1)=0, unknown_16(UINT8/1)=0, unknown_17(UINT8/1)=0, unknown_18(UINT8/1)=1, unknown_19(UINT8/1)=254, unknown_20(UINT8/1)=1, unknown_24(UINT8Z/1)=5, unknown_35(UINT8/3)=[0,50,], unknown_36(ENUM/1)=4, unknown_38(UINT8Z/1)=2, unknown_40(UINT8Z/1)=11, unknown_44(ENUM/1)=0}, " +
+                "RecordData{UNK_6, unknown_0(STRING/5)=P2SL, unknown_3(UINT32/4)=0, unknown_39(UINT8Z/4)=[39,53,,], unknown_41(UINT8Z/12)=[23,21,19,18,17,16,15,14,13,12,11,], unknown_254(UINT16/2)=1, unknown_7(UINT16Z/2)=28209, unknown_8(UINT16/2)=2096, unknown_9(UINT16/2)=0, unknown_10(UINT16/2)=90, unknown_11(UINT16/2)=500, unknown_12(UINT8/1)=1, unknown_13(UINT8/1)=1, unknown_14(UINT8/1)=0, unknown_15(UINT8/1)=0, unknown_16(UINT8/1)=0, unknown_17(UINT8/1)=0, unknown_18(UINT8/1)=1, unknown_19(UINT8/1)=254, unknown_20(UINT8/1)=1, unknown_24(UINT8Z/1)=5, unknown_35(UINT8/3)=[0,118,190], unknown_36(ENUM/1)=4, unknown_38(UINT8Z/1)=2, unknown_40(UINT8Z/1)=11, unknown_44(ENUM/1)=0}, " +
+                "RecordData{UNK_6, unknown_0(STRING/9)=LANGSTER, unknown_3(UINT32/4)=1231891, unknown_39(UINT8Z/4)=[39,53,,], unknown_41(UINT8Z/12)=[23,21,19,18,17,16,15,14,13,12,11,], unknown_254(UINT16/2)=2, unknown_7(UINT16Z/2)=10851, unknown_8(UINT16/2)=2096, unknown_9(UINT16/2)=0, unknown_10(UINT16/2)=95, unknown_11(UINT16/2)=500, unknown_12(UINT8/1)=1, unknown_13(UINT8/1)=1, unknown_14(UINT8/1)=0, unknown_15(UINT8/1)=0, unknown_16(UINT8/1)=0, unknown_17(UINT8/1)=0, unknown_18(UINT8/1)=1, unknown_19(UINT8/1)=0, unknown_20(UINT8/1)=1, unknown_24(UINT8Z/1)=5, unknown_35(UINT8/3)=[0,50,], unknown_36(ENUM/1)=4, unknown_38(UINT8Z/1)=2, unknown_40(UINT8Z/1)=11, unknown_44(ENUM/1)=0}, " +
+                "RecordData{UNK_6, unknown_0(STRING/2)=M, unknown_3(UINT32/4)=0, unknown_39(UINT8Z/4)=[53,39,,], unknown_41(UINT8Z/12)=[23,21,19,18,17,16,15,14,13,12,11,], unknown_254(UINT16/2)=3, unknown_7(UINT16Z/2)=31337, unknown_8(UINT16/2)=2096, unknown_9(UINT16/2)=0, unknown_10(UINT16/2)=95, unknown_11(UINT16/2)=500, unknown_12(UINT8/1)=1, unknown_13(UINT8/1)=1, unknown_14(UINT8/1)=0, unknown_15(UINT8/1)=0, unknown_16(UINT8/1)=0, unknown_17(UINT8/1)=0, unknown_18(UINT8/1)=1, unknown_19(UINT8/1)=0, unknown_20(UINT8/1)=1, unknown_24(UINT8Z/1)=5, unknown_35(UINT8/3)=[0,50,], unknown_36(ENUM/1)=4, unknown_38(UINT8Z/1)=2, unknown_40(UINT8Z/1)=11, unknown_44(ENUM/1)=0}, " +
+                "RecordData{UNK_6, unknown_0(STRING/7)=Bike 5, unknown_3(UINT32/4)=0, unknown_39(UINT8Z/4)=[39,53,,], unknown_41(UINT8Z/12)=[23,21,19,18,17,16,15,14,13,12,11,], unknown_254(UINT16/2)=4, unknown_8(UINT16/2)=2096, unknown_9(UINT16/2)=0, unknown_10(UINT16/2)=95, unknown_11(UINT16/2)=500, unknown_12(UINT8/1)=1, unknown_13(UINT8/1)=1, unknown_14(UINT8/1)=0, unknown_15(UINT8/1)=0, unknown_16(UINT8/1)=0, unknown_17(UINT8/1)=0, unknown_18(UINT8/1)=0, unknown_19(UINT8/1)=254, unknown_20(UINT8/1)=0, unknown_35(UINT8/3)=[0,50,], unknown_36(ENUM/1)=4, unknown_38(UINT8Z/1)=2, unknown_40(UINT8Z/1)=11, unknown_44(ENUM/1)=0}, " +
+                "RecordData{UNK_6, unknown_0(STRING/7)=Bike 6, unknown_3(UINT32/4)=0, unknown_39(UINT8Z/4)=[39,53,,], unknown_41(UINT8Z/12)=[23,21,19,18,17,16,15,14,13,12,11,], unknown_254(UINT16/2)=5, unknown_8(UINT16/2)=2096, unknown_9(UINT16/2)=0, unknown_10(UINT16/2)=95, unknown_11(UINT16/2)=500, unknown_12(UINT8/1)=1, unknown_13(UINT8/1)=1, unknown_14(UINT8/1)=0, unknown_15(UINT8/1)=0, unknown_16(UINT8/1)=0, unknown_17(UINT8/1)=0, unknown_18(UINT8/1)=0, unknown_19(UINT8/1)=254, unknown_20(UINT8/1)=0, unknown_35(UINT8/3)=[0,50,], unknown_36(ENUM/1)=4, unknown_38(UINT8Z/1)=2, unknown_40(UINT8Z/1)=11, unknown_44(ENUM/1)=0}, " +
+                "RecordData{UNK_6, unknown_0(STRING/7)=Bike 7, unknown_3(UINT32/4)=0, unknown_39(UINT8Z/4)=[39,53,,], unknown_41(UINT8Z/12)=[23,21,19,18,17,16,15,14,13,12,11,], unknown_254(UINT16/2)=6, unknown_8(UINT16/2)=2096, unknown_9(UINT16/2)=0, unknown_10(UINT16/2)=95, unknown_11(UINT16/2)=500, unknown_12(UINT8/1)=1, unknown_13(UINT8/1)=1, unknown_14(UINT8/1)=0, unknown_15(UINT8/1)=0, unknown_16(UINT8/1)=0, unknown_17(UINT8/1)=0, unknown_18(UINT8/1)=0, unknown_19(UINT8/1)=254, unknown_20(UINT8/1)=0, unknown_35(UINT8/3)=[0,50,], unknown_36(ENUM/1)=4, unknown_38(UINT8Z/1)=2, unknown_40(UINT8Z/1)=11, unknown_44(ENUM/1)=0}, " +
+                "RecordData{UNK_6, unknown_0(STRING/7)=Bike 8, unknown_3(UINT32/4)=0, unknown_39(UINT8Z/4)=[39,53,,], unknown_41(UINT8Z/12)=[23,21,19,18,17,16,15,14,13,12,11,], unknown_254(UINT16/2)=7, unknown_8(UINT16/2)=2096, unknown_9(UINT16/2)=0, unknown_10(UINT16/2)=95, unknown_11(UINT16/2)=500, unknown_12(UINT8/1)=1, unknown_13(UINT8/1)=1, unknown_14(UINT8/1)=0, unknown_15(UINT8/1)=0, unknown_16(UINT8/1)=0, unknown_17(UINT8/1)=0, unknown_18(UINT8/1)=0, unknown_19(UINT8/1)=254, unknown_20(UINT8/1)=0, unknown_35(UINT8/3)=[0,50,], unknown_36(ENUM/1)=4, unknown_38(UINT8Z/1)=2, unknown_40(UINT8Z/1)=11, unknown_44(ENUM/1)=0}, " +
+                "RecordData{UNK_6, unknown_0(STRING/7)=Bike 9, unknown_3(UINT32/4)=0, unknown_39(UINT8Z/4)=[39,53,,], unknown_41(UINT8Z/12)=[23,21,19,18,17,16,15,14,13,12,11,], unknown_254(UINT16/2)=8, unknown_8(UINT16/2)=2096, unknown_9(UINT16/2)=0, unknown_10(UINT16/2)=95, unknown_11(UINT16/2)=500, unknown_12(UINT8/1)=1, unknown_13(UINT8/1)=1, unknown_14(UINT8/1)=0, unknown_15(UINT8/1)=0, unknown_16(UINT8/1)=0, unknown_17(UINT8/1)=0, unknown_18(UINT8/1)=0, unknown_19(UINT8/1)=254, unknown_20(UINT8/1)=0, unknown_35(UINT8/3)=[0,50,], unknown_36(ENUM/1)=4, unknown_38(UINT8Z/1)=2, unknown_40(UINT8Z/1)=11, unknown_44(ENUM/1)=0}, " +
+                "RecordData{UNK_6, unknown_0(STRING/8)=Bike 10, unknown_3(UINT32/4)=0, unknown_39(UINT8Z/4)=[39,53,,], unknown_41(UINT8Z/12)=[23,21,19,18,17,16,15,14,13,12,11,], unknown_254(UINT16/2)=9, unknown_8(UINT16/2)=2096, unknown_9(UINT16/2)=0, unknown_10(UINT16/2)=95, unknown_11(UINT16/2)=500, unknown_12(UINT8/1)=1, unknown_13(UINT8/1)=1, unknown_14(UINT8/1)=0, unknown_15(UINT8/1)=0, unknown_16(UINT8/1)=0, unknown_17(UINT8/1)=0, unknown_18(UINT8/1)=0, unknown_19(UINT8/1)=254, unknown_20(UINT8/1)=0, unknown_35(UINT8/3)=[0,50,], unknown_36(ENUM/1)=4, unknown_38(UINT8Z/1)=2, unknown_40(UINT8Z/1)=11, unknown_44(ENUM/1)=0}, " +
+                "FitConnectivity{name=Edge 510, bluetooth_enabled=0}" +
+                "]";
+
+        FitFile fitFile = FitFile.parseIncoming(fileContents);
+        Assert.assertEquals(expectedOutput, fitFile.toString());
+
+    }
+
+    @Test
+    public void TestFitFileDevelopersField() {
+        byte[] fileContents = GB.hexStringToByteArray("0e206806a20000002e464954bed040000100000401028400010002028403048c00000f042329000006a540000100cf0201100d030102000101020305080d1522375990e97962db0040000100ce05000102010102020102031107080a0700000001646f7567686e7574735f6561726e656400646f7567686e7574730060000100140403010204010205048606028401000100008c580000c738b98001008f5a00032c808e400200905c0005a9388a1003d39e");//https://github.com/polyvertex/fitdecode/blob/48b6554d8a3baf33f8b5b9b2fd079fcbe9ac8ce2/tests/files/DeveloperData.fit
+
+        String expectedOutput = "[" +
+                "FitFileId{manufacturer=15, type=ACTIVITY, product=9001, serial_number=1701}, " +
+                "FitDeveloperData{application_id=[1,1,2,3,5,8,13,21,34,55,89,144,233,121,98,219], developer_data_index=0}, " +
+                "FitFieldDescription{developer_data_index=0, field_definition_number=0, fit_base_type_id=1, field_name=doughnuts_earned, units=doughnuts}, " +
+                "FitRecord{heart_rate=140, cadence=88, distance=510.0, speed=47.488, doughnuts_earned=1}, " +
+                "FitRecord{heart_rate=143, cadence=90, distance=2080.0, speed=36.416, doughnuts_earned=2}, " +
+                "FitRecord{heart_rate=144, cadence=92, distance=3710.0, speed=35.344, doughnuts_earned=3}" +
+                "]";
+
+        FitFile fitFile = FitFile.parseIncoming(fileContents);
+        Assert.assertEquals(expectedOutput, fitFile.toString());
+    }
+}
diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/communicator/CobsCoDecTest.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/communicator/CobsCoDecTest.java
new file mode 100644
index 0000000..c8f9e00
--- /dev/null
+++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/communicator/CobsCoDecTest.java
@@ -0,0 +1,113 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.communicator;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class CobsCoDecTest {
+    final CobsCoDec cobsCoDec = new CobsCoDec();
+
+    @Test
+    public void testCobsDecoder() {
+        cobsCoDec.receivedBytes(GB.hexStringToByteArray("00022C04A0139623310F684C1BCA840508020B"));
+        Assert.assertNull(cobsCoDec.retrieveMessage());
+        cobsCoDec.receivedBytes(GB.hexStringToByteArray("496E7374696E637420325308496E7374696E63"));
+        Assert.assertNull(cobsCoDec.retrieveMessage());
+        cobsCoDec.receivedBytes(GB.hexStringToByteArray("74023253010304B800"));
+        Assert.assertArrayEquals(
+                GB.hexStringToByteArray("2C00A0139600310F684C1BCA840508020B496E7374696E637420325308496E7374696E6374023253000004B8"),
+                cobsCoDec.retrieveMessage()
+        );
+    }
+
+    @Test
+    public void testCobsDecoder2() {
+        cobsCoDec.receivedBytes(GB.hexStringToByteArray("00022b058813a013029623ffffffffffffa71fffff046c61726a07756e6b6e6f776e0758512d4343373201f9cf00"));
+        Assert.assertArrayEquals(
+                new byte[]{0x2b, 0x00, (byte) 0x88, 0x13, (byte) 0xa0, 0x13, 0x00, (byte) 0x96, 0x00, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xa7, 0x1f, (byte) 0xff, (byte) 0xff, 0x04, 0x6c, 0x61, 0x72, 0x6a, 0x07, 0x75, 0x6e, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x07, 0x58, 0x51, 0x2d, 0x43, 0x43, 0x37, 0x32, 0x01, (byte) 0xf9, (byte) 0xcf},
+                cobsCoDec.retrieveMessage()
+        );
+    }
+
+    @Test
+    public void testCobsEncoder2() {
+        byte[] result = cobsCoDec.encode(GB.hexStringToByteArray("022b058813a013029623ffffffffffffa71fffff046c61726a07756e6b6e6f776e0758512d4343373201f9cf00"));
+        Assert.assertArrayEquals(
+                new byte[]{0x00, 0x2d, (byte) 0x02, (byte) 0x2b, (byte) 0x05, (byte) 0x88, (byte) 0x13, (byte) 0xa0, (byte) 0x13, (byte) 0x02, (byte) 0x96, (byte) 0x23, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xa7, (byte) 0x1f, (byte) 0xff, (byte) 0xff, (byte) 0x04, (byte) 0x6c, (byte) 0x61, (byte) 0x72, (byte) 0x6a, (byte) 0x07, (byte) 0x75, (byte) 0x6e, (byte) 0x6b, (byte) 0x6e, (byte) 0x6f, (byte) 0x77, (byte) 0x6e, (byte) 0x07, (byte) 0x58, (byte) 0x51, (byte) 0x2d, (byte) 0x43, (byte) 0x43, (byte) 0x37, (byte) 0x32, (byte) 0x01, (byte) 0xf9, (byte) 0xcf, (byte) 0x01, (byte) 0x00},
+                result
+        );
+    }
+
+    @Test
+    public void testCobsDecoderSingleByteAtStart() {
+        cobsCoDec.receivedBytes(GB.hexStringToByteArray("00"));
+        Assert.assertNull(cobsCoDec.retrieveMessage());
+        cobsCoDec.receivedBytes(GB.hexStringToByteArray("022C04A0139623310F684C1BCA840508020B"));
+        Assert.assertNull(cobsCoDec.retrieveMessage());
+        cobsCoDec.receivedBytes(GB.hexStringToByteArray("496E7374696E637420325308496E7374696E63"));
+        Assert.assertNull(cobsCoDec.retrieveMessage());
+        cobsCoDec.receivedBytes(GB.hexStringToByteArray("74023253010304B800"));
+        Assert.assertArrayEquals(
+                GB.hexStringToByteArray("2C00A0139600310F684C1BCA840508020B496E7374696E637420325308496E7374696E6374023253000004B8"),
+                cobsCoDec.retrieveMessage()
+        );
+    }
+
+    @Test
+    public void testCobsDecoderSingleByteAtEnd() {
+        cobsCoDec.receivedBytes(GB.hexStringToByteArray("00022C04A0139623310F684C1BCA840508020B"));
+        Assert.assertNull(cobsCoDec.retrieveMessage());
+        cobsCoDec.receivedBytes(GB.hexStringToByteArray("496E7374696E637420325308496E7374696E63"));
+        Assert.assertNull(cobsCoDec.retrieveMessage());
+        cobsCoDec.receivedBytes(GB.hexStringToByteArray("74023253010304B8"));
+        Assert.assertNull(cobsCoDec.retrieveMessage());
+        cobsCoDec.receivedBytes(GB.hexStringToByteArray("00"));
+        Assert.assertArrayEquals(
+                GB.hexStringToByteArray("2C00A0139600310F684C1BCA840508020B496E7374696E637420325308496E7374696E6374023253000004B8"),
+                cobsCoDec.retrieveMessage()
+        );
+    }
+
+    @Test
+    public void testCobsEncoder() {
+        Assert.assertArrayEquals(
+                GB.hexStringToByteArray("00022C04A0139623310F684C1BCA840508020B496E7374696E637420325308496E7374696E6374023253010304B800"),
+                cobsCoDec.encode(GB.hexStringToByteArray("2C00A0139600310F684C1BCA840508020B496E7374696E637420325308496E7374696E6374023253000004B8"))
+        );
+    }
+
+    @Test
+    public void testLongPayload() {
+        //test strings from https://github.com/themarpe/cobs-java/blob/master/tests-java/Tests.java
+        final byte[] test_string_0 = new byte[]{0, 0, 0, 0};
+        final byte[] test_string_1 = new byte[]{0, '1', '2', '3', '4', '5'};
+        final byte[] test_string_2 = new byte[]{0, '1', '2', '3', '4', '5', 0};
+        final byte[] test_string_3 = new byte[]{'1', '2', '3', '4', '5', 0, '6', '7', '8', '9'};
+        final byte[] test_string_4 = new byte[]{0, '1', '2', '3', '4', '5', 0, '6', '7', '8', '9', 0};
+        final byte[] test_string_5 = new byte[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, -128, -127, -126, -125, -124, -123, -122, -121, -120, -119, -118, -117, -116, -115, -114, -113, -112, -111, -110, -109, -108, -107, -106, -105, -104, -103, -102, -101, -100, -99, -98, -97, -96, -95, -94, -93, -92, -91, -90, -89, -88, -87, -86, -85, -84, -83, -82, -81, -80, -79, -78, -77, -76, -75, -74, -73, -72, -71, -70, -69, -68, -67, -66, -65, -64, -63, -62, -61, -60, -59, -58, -57, -56, -55, -54, -53, -52, -51, -50, -49, -48, -47, -46, -45, -44, -43, -42, -41, -40, -39, -38, -37, -36, -35, -34, -33, -32, -31, -30, -29, -28, -27, -26, -25, -24, -23, -22, -21, -20, -19, -18, -17, -16, -15, -14, -13, -12, -11, -10, -9, -8, -7, -6, -5, -4, -3, -2, -1, 0};
+        final byte[] test_string_6 = new byte[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, -128, -127, -126, -125, -124, -123, -122, -121, -120, -119, -118, -117, -116, -115, -114, -113, -112, -111, -110, -109, -108, -107, -106, -105, -104, -103, -102, -101, -100, -99, -98, -97, -96, -95, -94, -93, -92, -91, -90, -89, -88, -87, -86, -85, -84, -83, -82, -81, -80, -79, -78, -77, -76, -75, -74, -73, -72, -71, -70, -69, -68, -67, -66, -65, -64, -63, -62, -61, -60, -59, -58, -57, -56, -55, -54, -53, -52, -51, -50, -49, -48, -47, -46, -45, -44, -43, -42, -41, -40, -39, -38, -37, -36, -35, -34, -33, -32, -31, -30, -29, -28, -27, -26, -25, -24, -23, -22, -21, -20, -19, -18, -17, -16, -15, -14, -13, -12, -11, -10, -9, -8, -7, -6, -5, -4, -3, -2, -1, 0};
+        final byte[] test_string_7 = new byte[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, -128, -127, -126, -125, -124, -123, -122, -121, -120, -119, -118, -117, -116, -115, -114, -113, -112, -111, -110, -109, -108, -107, -106, -105, -104, -103, -102, -101, -100, -99, -98, -97, -96, -95, -94, -93, -92, -91, -90, -89, -88, -87, -86, -85, -84, -83, -82, -81, -80, -79, -78, -77, -76, -75, -74, -73, -72, -71, -70, -69, -68, -67, -66, -65, -64, -63, -62, -61, -60, -59, -58, -57, -56, -55, -54, -53, -52, -51, -50, -49, -48, -47, -46, -45, -44, -43, -42, -41, -40, -39, -38, -37, -36, -35, -34, -33, -32, -31, -30, -29, -28, -27, -26, -25, -24, -23, -22, -21, -20, -19, -18, -17, -16, -15, -14, -13, -12, -11, -10, -9, -8, -7, -6, -5, -4, -3, -2, -1};
+        final byte[] test_string_8 = new byte[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, -128, -127, -126, -125, -124, -123, -122, -121, -120, -119, -118, -117, -116, -115, -114, -113, -112, -111, -110, -109, -108, -107, -106, -105, -104, -103, -102, -101, -100, -99, -98, -97, -96, -95, -94, -93, -92, -91, -90, -89, -88, -87, -86, -85, -84, -83, -82, -81, -80, -79, -78, -77, -76, -75, -74, -73, -72, -71, -70, -69, -68, -67, -66, -65, -64, -63, -62, -61, -60, -59, -58, -57, -56, -55, -54, -53, -52, -51, -50, -49, -48, -47, -46, -45, -44, -43, -42, -41, -40, -39, -38, -37, -36, -35, -34, -33, -32, -31, -30, -29, -28, -27, -26, -25, -24, -23, -22, -21, -20, -19, -18, -17, -16, -15, -14, -13, -12, -11, -10, -9, -8, -7, -6, -5, -4, -3, -2, -1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, -128, -127, -126, -125, -124, -123, -122, -121, -120, -119, -118, -117, -116, -115, -114, -113, -112, -111, -110, -109, -108, -107, -106, -105, -104, -103, -102, -101, -100, -99, -98, -97, -96, -95, -94, -93, -92, -91, -90, -89, -88, -87, -86, -85, -84, -83, -82, -81, -80, -79, -78, -77, -76, -75, -74, -73, -72, -71, -70, -69, -68, -67, -66, -65, -64, -63, -62, -61, -60, -59, -58, -57, -56, -55, -54, -53, -52, -51, -50, -49, -48, -47, -46, -45, -44, -43, -42, -41, -40, -39, -38, -37, -36, -35, -34, -33, -32, -31, -30, -29, -28, -27, -26, -25, -24, -23, -22, -21, -20, -19, -18, -17, -16, -15, -14, -13, -12, -11, -10, -9, -8, -7, -6, 0, -5, -4, -3, -2, -1};
+        final byte[] test_string_long_nonzero = GB.hexStringToByteArray("49535a73394d73483159456f515352443069546e466c4b32394d3479336f396a6936543263544954794e576b456f4e365769704734444a6b4c6439657a6256386b30676f4e544e587954326e596e617567334f546449376c535471506e724e4444326664774d7a3142725653596c4838794b524b50327a5a3438704961545457384b483571744a6c726948704552364b7a466c54315776337a373954524942467442784c3062486f6c386b786a48377750726f5277766546757a596876533731726e6972344e644f70475a6c6a4c65753371554545396a4f556974703655774b426b34575970754e4f484b6349364f425468334c753532324b66abababab000B");
+        final byte[][] allTests = new byte[][]{
+                test_string_1,
+                test_string_2,
+                test_string_3,
+                test_string_4,
+                test_string_5,
+                test_string_6,
+                test_string_7,
+                test_string_8,
+                test_string_long_nonzero,
+        };
+
+        for (byte[] payload : allTests) {
+            byte[] encodedData = cobsCoDec.encode(payload);
+            cobsCoDec.receivedBytes(encodedData);
+            byte[] decodedData = cobsCoDec.retrieveMessage();
+
+            Assert.assertArrayEquals(payload, decodedData);
+        }
+    }
+}
diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/FitImporterTest.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/FitImporterTest.java
new file mode 100644
index 0000000..9d68184
--- /dev/null
+++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/FitImporterTest.java
@@ -0,0 +1,16 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit;
+
+import org.junit.Ignore;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.IOException;
+
+public class FitImporterTest {
+    @Test
+    @Ignore("helper test for development, remove this while debugging")
+    public void localTest() throws IOException {
+        final FitImporter fitImporter = new FitImporter(  null, null);
+        fitImporter.importFile(new File("/storage/SKIN_TEMP.fit"));
+    }
+}
diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/enums/GarminSportTest.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/enums/GarminSportTest.java
new file mode 100644
index 0000000..c74f69a
--- /dev/null
+++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/enums/GarminSportTest.java
@@ -0,0 +1,31 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.enums;
+
+import static org.junit.Assert.assertTrue;
+
+import android.util.Pair;
+
+import org.junit.Test;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import nodomain.freeyourgadget.gadgetbridge.test.TestBase;
+
+public class GarminSportTest extends TestBase {
+    @Test
+    public void testNoDuplicates() {
+        // Ensure there are no duplicated sports with the same type and subtype
+        final Set<GarminSport> duplicates = new HashSet<>();
+        final Set<Pair<Integer, Integer>> seen = new HashSet<>();
+
+        for (final GarminSport sport : GarminSport.values()) {
+            final Pair<Integer, Integer> codePair = Pair.create(sport.getType(), sport.getSubtype());
+            if (seen.contains(codePair)) {
+                duplicates.add(sport);
+            }
+            seen.add(codePair);
+        }
+
+        assertTrue("Duplicated sport codes: " + duplicates, duplicates.isEmpty());
+    }
+}
diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiSupportTest.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiSupportTest.java
new file mode 100644
index 0000000..1ac81f1
--- /dev/null
+++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiSupportTest.java
@@ -0,0 +1,71 @@
+/*  Copyright (C) 2022 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami;
+
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.content.Context;
+import android.net.Uri;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.WriteAction;
+
+public class HuamiSupportTest {
+    @Test
+    public void testSetCurrentTimeWithService() {
+        final TransactionBuilder testTransactionBuilder = new TransactionBuilder("test");
+        final HuamiSupport huamiSupport = createSupport();
+
+        huamiSupport.setCurrentTimeWithService(testTransactionBuilder);
+        final WriteAction action = (WriteAction) testTransactionBuilder.getTransaction().getActions().get(0);
+
+        Assert.assertArrayEquals(new byte[]{-26, 7, 12, 15, 20, 38, 53, 4, 0, 0, 4}, action.getValue());
+    }
+
+    private HuamiSupport createSupport() {
+        return new HuamiSupport() {
+            @Override
+            public BluetoothGattCharacteristic getCharacteristic(final UUID uuid) {
+                return new BluetoothGattCharacteristic(null, 0, 0);
+            }
+
+            @Override
+            public HuamiFWHelper createFWHelper(final Uri uri, final Context context) throws IOException {
+                return null;
+            }
+
+            @Override
+            public Calendar createCalendar() {
+                // 2022-12-15 20:38:53 GMT+1
+                final Instant instant = Instant.ofEpochMilli(1671133133000L);
+                final ZonedDateTime zdt = ZonedDateTime.ofInstant(instant, ZoneId.of("Europe/Paris"));
+                return GregorianCalendar.from(zdt);
+            }
+        };
+    }
+}
diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/ZeppOsSupportTest.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/ZeppOsSupportTest.java
new file mode 100644
index 0000000..ae64b19
--- /dev/null
+++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/ZeppOsSupportTest.java
@@ -0,0 +1,71 @@
+/*  Copyright (C) 2022 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos;
+
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.content.Context;
+import android.net.Uri;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.WriteAction;
+
+public class ZeppOsSupportTest {
+    @Test
+    public void testSetCurrentTimeWithService() {
+        final ZeppOsSupport support = createSupport();
+
+        final TransactionBuilder testTransactionBuilder = new TransactionBuilder("test");
+        support.setCurrentTimeWithService(testTransactionBuilder);
+        final WriteAction action = (WriteAction) testTransactionBuilder.getTransaction().getActions().get(0);
+
+        Assert.assertArrayEquals(new byte[]{-26, 7, 12, 15, 20, 38, 53, 4, 0, 8, 4}, action.getValue());
+    }
+
+    private ZeppOsSupport createSupport() {
+        return new ZeppOsSupport() {
+            @Override
+            public BluetoothGattCharacteristic getCharacteristic(final UUID uuid) {
+                return new BluetoothGattCharacteristic(null, 0, 0);
+            }
+
+            @Override
+            public HuamiFWHelper createFWHelper(final Uri uri, final Context context) throws IOException {
+                return null;
+            }
+
+            @Override
+            public Calendar createCalendar() {
+                // 2022-12-15 20:38:53 GMT+1
+                final Instant instant = Instant.ofEpochMilli(1671133133000L);
+                final ZonedDateTime zdt = ZonedDateTime.ofInstant(instant, ZoneId.of("Europe/Paris"));
+                return GregorianCalendar.from(zdt);
+            }
+        };
+    }
+}
diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/TestDebugRequestParser.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/TestDebugRequestParser.java
new file mode 100644
index 0000000..f04edbd
--- /dev/null
+++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/TestDebugRequestParser.java
@@ -0,0 +1,401 @@
+/*  Copyright (C) 2023 Martin.JM
+
+    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 <http://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei;
+
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.content.Context;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiTLV;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Workout;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.btbr.Transaction;
+import nodomain.freeyourgadget.gadgetbridge.service.btbr.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.DebugRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.Request;
+
+public class TestDebugRequestParser {
+
+    HuaweiSupportProvider supportProvider = new HuaweiSupportProvider(new HuaweiLESupport()) {
+
+        @Override
+        public boolean isBLE() {
+            return true;
+        }
+
+        @Override
+        public Context getContext() {
+            return null;
+        }
+
+        @Override
+        public GBDevice getDevice() {
+            return null;
+        }
+
+        @Override
+        public byte[] getSerial() {
+            return new byte[0];
+        }
+
+        @Override
+        public String getDeviceMac() {
+            return null;
+        }
+
+        @Override
+        public byte[] getMacAddress() {
+            return new byte[0];
+        }
+
+        @Override
+        public byte[] getAndroidId() {
+            return new byte[0];
+        }
+
+        @Override
+        public short getNotificationId() {
+            return 0;
+        }
+
+        @Override
+        public TransactionBuilder createBrTransactionBuilder(String taskName) {
+            return null;
+        }
+
+        @Override
+        public nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder createLeTransactionBuilder(String taskName) {
+            return null;
+        }
+
+        @Override
+        public void performConnected(Transaction transaction) throws IOException {
+
+        }
+
+        @Override
+        public void performConnected(nodomain.freeyourgadget.gadgetbridge.service.btle.Transaction transaction) throws IOException {
+
+        }
+
+        @Override
+        public void evaluateGBDeviceEvent(GBDeviceEvent deviceEvent) {
+
+        }
+
+        @Override
+        public BluetoothGattCharacteristic getLeCharacteristic(UUID uuid) {
+            return null;
+        }
+
+        @Override
+        public HuaweiPacket.ParamsProvider getParamsProvider() {
+            return null;
+        }
+
+        @Override
+        public void addInProgressRequest(Request request) {
+
+        }
+
+        @Override
+        public void removeInProgressRequests(Request request) {
+
+        }
+
+        @Override
+        public void setSecretKey(byte[] authKey) {
+
+        }
+
+        @Override
+        public byte[] getSecretKey() {
+            return new byte[0];
+        }
+
+        @Override
+        public void addTotalFitnessData(int steps, int calories, int distance) {
+
+        }
+
+        @Override
+        public void addStepData(int timestamp, short steps, short calories, short distance, byte spo, byte heartrate) {
+
+        }
+
+        @Override
+        public Long addWorkoutTotalsData(Workout.WorkoutTotals.Response packet) {
+            return null;
+        }
+
+        @Override
+        public void addWorkoutSampleData(Long workoutId, List<Workout.WorkoutData.Response.Data> dataList) {
+
+        }
+
+        @Override
+        public void sendSetMusic() {
+
+        }
+    };
+
+    @Test
+    public void emptyPacket() throws Request.RequestCreationException {
+        DebugRequest debugRequest = new DebugRequest(supportProvider);
+
+        HuaweiPacket expected = new HuaweiPacket(supportProvider.getParamsProvider());
+        expected.serviceId = 1;
+        expected.commandId = 1;
+        expected.setEncryption(false);
+        expected.complete = true;
+
+        HuaweiPacket packet = debugRequest.parseDebugString("1,1,false,false");
+
+        Assert.assertEquals(expected, packet);
+    }
+
+    @Test
+    public void emptyPacketShortBooleans() throws Request.RequestCreationException {
+        DebugRequest debugRequest = new DebugRequest(supportProvider);
+
+        HuaweiPacket expected = new HuaweiPacket(supportProvider.getParamsProvider());
+        expected.serviceId = 1;
+        expected.commandId = 1;
+        expected.setEncryption(false);
+        expected.complete = true;
+
+        HuaweiPacket packet = debugRequest.parseDebugString("1,1,f,f");
+
+        Assert.assertEquals(expected, packet);
+    }
+
+    @Test
+    public void emptyTag() throws Request.RequestCreationException {
+        DebugRequest debugRequest = new DebugRequest(supportProvider);
+
+        HuaweiPacket expected = new HuaweiPacket(supportProvider.getParamsProvider());
+        expected.serviceId = 1;
+        expected.commandId = 1;
+        expected.setEncryption(false);
+        expected.setTlv(new HuaweiTLV().put(1));
+        expected.complete = true;
+
+        HuaweiPacket packet = debugRequest.parseDebugString("1,1,false,false,(1,/)");
+
+        Assert.assertEquals(expected, packet);
+    }
+
+    @Test
+    public void byteTag() throws Request.RequestCreationException {
+        DebugRequest debugRequest = new DebugRequest(supportProvider);
+
+        HuaweiPacket expected = new HuaweiPacket(supportProvider.getParamsProvider());
+        expected.serviceId = 1;
+        expected.commandId = 1;
+        expected.setEncryption(false);
+        expected.setTlv(new HuaweiTLV().put(1, (byte) 1));
+        expected.complete = true;
+
+        HuaweiPacket packet = debugRequest.parseDebugString("1,1,false,false,(1,B1)");
+
+        Assert.assertEquals(expected, packet);
+    }
+
+    @Test
+    public void shortTag() throws Request.RequestCreationException {
+        DebugRequest debugRequest = new DebugRequest(supportProvider);
+
+        HuaweiPacket expected = new HuaweiPacket(supportProvider.getParamsProvider());
+        expected.serviceId = 1;
+        expected.commandId = 1;
+        expected.setEncryption(false);
+        expected.setTlv(new HuaweiTLV().put(1, (short) 1));
+        expected.complete = true;
+
+        HuaweiPacket packet = debugRequest.parseDebugString("1,1,false,false,(1,S1)");
+
+        Assert.assertEquals(expected, packet);
+    }
+
+    @Test
+    public void integerTag() throws Request.RequestCreationException {
+        DebugRequest debugRequest = new DebugRequest(supportProvider);
+
+        HuaweiPacket expected = new HuaweiPacket(supportProvider.getParamsProvider());
+        expected.serviceId = 1;
+        expected.commandId = 1;
+        expected.setEncryption(false);
+        expected.setTlv(new HuaweiTLV().put(1, (int) 1));
+        expected.complete = true;
+
+        HuaweiPacket packet = debugRequest.parseDebugString("1,1,false,false,(1,I1)");
+
+        Assert.assertEquals(expected, packet);
+    }
+
+    @Test
+    public void booleanTag() throws Request.RequestCreationException {
+        DebugRequest debugRequest = new DebugRequest(supportProvider);
+
+        HuaweiPacket expected = new HuaweiPacket(supportProvider.getParamsProvider());
+        expected.serviceId = 1;
+        expected.commandId = 1;
+        expected.setEncryption(false);
+        expected.setTlv(new HuaweiTLV().put(1, true));
+        expected.complete = true;
+
+        HuaweiPacket packet = debugRequest.parseDebugString("1,1,false,false,(1,b1)");
+
+        Assert.assertEquals(expected, packet);
+    }
+
+    @Test
+    public void arrayTag() throws Request.RequestCreationException {
+        DebugRequest debugRequest = new DebugRequest(supportProvider);
+
+        HuaweiPacket expected = new HuaweiPacket(supportProvider.getParamsProvider());
+        expected.serviceId = 1;
+        expected.commandId = 1;
+        expected.setEncryption(false);
+        expected.setTlv(new HuaweiTLV().put(1, new byte[] {(byte) 0xCA, (byte) 0xFE}));
+        expected.complete = true;
+
+        HuaweiPacket packet = debugRequest.parseDebugString("1,1,false,false,(1,aCAFE)");
+
+        Assert.assertEquals(expected, packet);
+    }
+
+    @Test
+    public void stringTag() throws Request.RequestCreationException {
+        DebugRequest debugRequest = new DebugRequest(supportProvider);
+
+        HuaweiPacket expected = new HuaweiPacket(supportProvider.getParamsProvider());
+        expected.serviceId = 1;
+        expected.commandId = 1;
+        expected.setEncryption(false);
+        expected.setTlv(new HuaweiTLV().put(1, new byte[] {0x79, 0x65, 0x73}));
+        expected.complete = true;
+
+        HuaweiPacket packet = debugRequest.parseDebugString("1,1,false,false,(1,-yes)");
+
+        Assert.assertEquals(expected, packet);
+    }
+
+    @Test
+    public void hexValues() throws Request.RequestCreationException {
+        DebugRequest debugRequest = new DebugRequest(supportProvider);
+
+        HuaweiPacket expected = new HuaweiPacket(supportProvider.getParamsProvider());
+        expected.serviceId = 1;
+        expected.commandId = 1;
+        expected.setEncryption(false);
+        expected.setTlv(new HuaweiTLV().put(1, new byte[] {0x79, 0x65, 0x73}));
+        expected.complete = true;
+
+        HuaweiPacket packet = debugRequest.parseDebugString("0x01,0x1,false,false,(0x01,-yes)");
+
+        Assert.assertEquals(expected, packet);
+    }
+
+    @Test
+    public void largeServiceCommand() throws Request.RequestCreationException {
+        DebugRequest debugRequest = new DebugRequest(supportProvider);
+
+        HuaweiPacket expected = new HuaweiPacket(supportProvider.getParamsProvider());
+        expected.serviceId = (byte) 0xff;
+        expected.commandId = (byte) 255;
+        expected.setEncryption(false);
+        expected.complete = true;
+
+        HuaweiPacket packet = debugRequest.parseDebugString("0xff,255,false,false");
+
+        Assert.assertEquals(expected, packet);
+    }
+
+    @Test
+    public void subTlv() throws Request.RequestCreationException {
+        DebugRequest debugRequest = new DebugRequest(supportProvider);
+
+        HuaweiPacket expected = new HuaweiPacket(supportProvider.getParamsProvider());
+        expected.serviceId = 1;
+        expected.commandId = 1;
+        expected.setEncryption(false);
+        expected.setTlv(new HuaweiTLV()
+                .put(129, new HuaweiTLV()
+                        .put(1)
+                        .put(2)
+                ));
+        expected.complete = true;
+
+        HuaweiPacket packet = debugRequest.parseDebugString("1,1,false,false,(129,(1,/),(2,/))");
+
+        Assert.assertEquals(expected, packet);
+    }
+
+    @Test
+    public void subSubSubTlv() throws Request.RequestCreationException {
+        DebugRequest debugRequest = new DebugRequest(supportProvider);
+
+        HuaweiPacket expected = new HuaweiPacket(supportProvider.getParamsProvider());
+        expected.serviceId = 1;
+        expected.commandId = 1;
+        expected.setEncryption(false);
+        expected.setTlv(new HuaweiTLV()
+                .put(129, new HuaweiTLV()
+                        .put(129, new HuaweiTLV()
+                                .put(129, new HuaweiTLV()
+                                        .put(1)
+                                )
+                        )
+                ));
+        expected.complete = true;
+
+        HuaweiPacket packet = debugRequest.parseDebugString("1,1,false,false,(129,(129,(129,(1,/))))");
+
+        Assert.assertEquals(expected, packet);
+    }
+
+    @Test
+    public void subTlvVCombined() throws Request.RequestCreationException {
+        DebugRequest debugRequest = new DebugRequest(supportProvider);
+
+        HuaweiPacket expected = new HuaweiPacket(supportProvider.getParamsProvider());
+        expected.serviceId = 1;
+        expected.commandId = 1;
+        expected.setEncryption(false);
+        expected.setTlv(new HuaweiTLV()
+                .put(129, new HuaweiTLV()
+                        .put(1)
+                        .put(2, true)
+                )
+                .put(1, true)
+        );
+        expected.complete = true;
+
+        HuaweiPacket packet = debugRequest.parseDebugString("1,1,false,false,(129,(1,/),(2,b1)),(1,b1)");
+
+        Assert.assertEquals(expected, packet);
+    }
+}
diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/TestHuaweiSupportProvider.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/TestHuaweiSupportProvider.java
new file mode 100644
index 0000000..7544201
--- /dev/null
+++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/TestHuaweiSupportProvider.java
@@ -0,0 +1,71 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei;
+
+import org.junit.Test;
+import org.mockito.Mockito;
+
+public class TestHuaweiSupportProvider {
+
+    @Test
+    public void testOnSocketReadExactPacket() {
+        byte[] data1 = {(byte) 0x5A, (byte) 0x00, (byte) 0x06, (byte) 0x00, (byte) 0x04, (byte) 0x01, (byte) 0x01, (byte) 0x01, (byte) 0x02, (byte) 0x99, (byte) 0x6B};
+
+        HuaweiBRSupport support = new HuaweiBRSupport();
+
+        HuaweiSupportProvider supportProvider = new HuaweiSupportProvider(support);
+        supportProvider.responseManager = Mockito.mock(ResponseManager.class);
+
+        supportProvider.onSocketRead(data1);
+
+        Mockito.verify(supportProvider.responseManager, Mockito.times(1)).handleData(data1);
+    }
+
+//    @Test
+//    public void testOnSocketReadMultiplePacket() {
+//        byte[] expected = {(byte) 0x5A, (byte) 0x00, (byte) 0x06, (byte) 0x00, (byte) 0x04, (byte) 0x01, (byte) 0x01, (byte) 0x01, (byte) 0x02, (byte) 0x99, (byte) 0x6B};
+//        byte[] data1 = {(byte) 0x5A, (byte) 0x00, (byte) 0x06, (byte) 0x00, (byte) 0x04, (byte) 0x01, (byte) 0x01, (byte) 0x01, (byte) 0x02, (byte) 0x99, (byte) 0x6B, (byte) 0x5A, (byte) 0x00, (byte) 0x06, (byte) 0x00, (byte) 0x04, (byte) 0x01, (byte) 0x01, (byte) 0x01, (byte) 0x02, (byte) 0x99, (byte) 0x6B};
+//
+//        HuaweiBRSupport support = new HuaweiBRSupport();
+//
+//        HuaweiSupportProvider supportProvider = new HuaweiSupportProvider(support);
+//        supportProvider.responseManager = Mockito.mock(ResponseManager.class);
+//
+//        supportProvider.onSocketRead(data1);
+//
+//        Mockito.verify(supportProvider.responseManager, Mockito.times(2)).handleData(expected);
+//    }
+
+    @Test
+    public void testOnSocketReadPartialPacket() {
+        byte[] data1 = {(byte) 0x5A, (byte) 0x00, (byte) 0x06, (byte) 0x00, (byte) 0x04, (byte) 0x01};
+        byte[] data2 = {(byte) 0x01, (byte) 0x01, (byte) 0x02, (byte) 0x99, (byte) 0x6B};
+
+        HuaweiBRSupport support = new HuaweiBRSupport();
+
+        HuaweiSupportProvider supportProvider = new HuaweiSupportProvider(support);
+        supportProvider.responseManager = Mockito.mock(ResponseManager.class);
+
+        supportProvider.onSocketRead(data1);
+        supportProvider.onSocketRead(data2);
+
+        Mockito.verify(supportProvider.responseManager, Mockito.times(1)).handleData(data1);
+        Mockito.verify(supportProvider.responseManager, Mockito.times(1)).handleData(data2);
+    }
+
+    @Test
+    public void testOnSocketReadPartialPacket5a() {
+        byte[] data1 = {(byte) 0x5A, (byte) 0x00, (byte) 0x06, (byte) 0x00, (byte) 0x04, (byte) 0x01};
+        byte[] data2 = {(byte) 0x5A, (byte) 0x91, (byte) 0x92, (byte) 0x99, (byte) 0x6B};
+
+        HuaweiBRSupport support = new HuaweiBRSupport();
+
+        HuaweiSupportProvider supportProvider = new HuaweiSupportProvider(support);
+        supportProvider.responseManager = Mockito.mock(ResponseManager.class);
+
+        supportProvider.onSocketRead(data1);
+        supportProvider.onSocketRead(data2);
+
+        Mockito.verify(supportProvider.responseManager, Mockito.times(1)).handleData(data1);
+        Mockito.verify(supportProvider.responseManager, Mockito.times(1)).handleData(data2);
+    }
+
+}
diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/TestResponseManager.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/TestResponseManager.java
new file mode 100644
index 0000000..8e3d65e
--- /dev/null
+++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/TestResponseManager.java
@@ -0,0 +1,485 @@
+/*  Copyright (C) 2022-2023 Martin.JM
+
+    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 <http://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.content.Context;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mockito;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Workout;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.btbr.Transaction;
+import nodomain.freeyourgadget.gadgetbridge.service.btbr.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetEventAlarmList;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.Request;
+
+@RunWith(MockitoJUnitRunner.class)
+public class TestResponseManager {
+
+    HuaweiSupportProvider supportProvider = new HuaweiSupportProvider(new HuaweiLESupport()) {
+
+        @Override
+        public boolean isBLE() {
+            return true;
+        }
+
+        @Override
+        public Context getContext() {
+            return null;
+        }
+
+        @Override
+        public GBDevice getDevice() {
+            return null;
+        }
+
+        @Override
+        public byte[] getSerial() {
+            return new byte[0];
+        }
+
+        @Override
+        public String getDeviceMac() {
+            return null;
+        }
+
+        @Override
+        public byte[] getMacAddress() {
+            return new byte[0];
+        }
+
+        @Override
+        public byte[] getAndroidId() {
+            return new byte[0];
+        }
+
+        @Override
+        public short getNotificationId() {
+            return 0;
+        }
+
+        @Override
+        public TransactionBuilder createBrTransactionBuilder(String taskName) {
+            return null;
+        }
+
+        @Override
+        public nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder createLeTransactionBuilder(String taskName) {
+            return null;
+        }
+
+        @Override
+        public void performConnected(Transaction transaction) throws IOException {
+
+        }
+
+        @Override
+        public void performConnected(nodomain.freeyourgadget.gadgetbridge.service.btle.Transaction transaction) throws IOException {
+
+        }
+
+        @Override
+        public void evaluateGBDeviceEvent(GBDeviceEvent deviceEvent) {
+
+        }
+
+        @Override
+        public BluetoothGattCharacteristic getLeCharacteristic(UUID uuid) {
+            return null;
+        }
+
+        @Override
+        public HuaweiPacket.ParamsProvider getParamsProvider() {
+            return null;
+        }
+
+        @Override
+        public void addInProgressRequest(Request request) {
+
+        }
+
+        @Override
+        public void removeInProgressRequests(Request request) {
+
+        }
+
+        @Override
+        public void setSecretKey(byte[] authKey) {
+
+        }
+
+        @Override
+        public byte[] getSecretKey() {
+            return new byte[0];
+        }
+
+        @Override
+        public void addTotalFitnessData(int steps, int calories, int distance) {
+
+        }
+
+        @Override
+        public void addStepData(int timestamp, short steps, short calories, short distance, byte spo, byte heartrate) {
+
+        }
+
+        @Override
+        public Long addWorkoutTotalsData(Workout.WorkoutTotals.Response packet) {
+            return null;
+        }
+
+        @Override
+        public void addWorkoutSampleData(Long workoutId, List<Workout.WorkoutData.Response.Data> dataList) {
+
+        }
+
+        @Override
+        public void sendSetMusic() {
+
+        }
+    };
+
+    Field handlersField;
+    Field receivedPacketField;
+    Field asynchronousResponseField;
+
+    @Before
+    public void beforeClass() throws NoSuchFieldException {
+        handlersField = ResponseManager.class.getDeclaredField("handlers");
+        handlersField.setAccessible(true);
+
+        asynchronousResponseField = ResponseManager.class.getDeclaredField("asynchronousResponse");
+        asynchronousResponseField.setAccessible(true);
+
+        receivedPacketField = ResponseManager.class.getDeclaredField("receivedPacket");
+        receivedPacketField.setAccessible(true);
+    }
+
+    @Test
+    public void testAddHandler() throws IllegalAccessException {
+        Request input = new Request(supportProvider);
+
+        List<Request> expectedHandlers = Collections.synchronizedList(new ArrayList<Request>());
+        expectedHandlers.add(input);
+
+        ResponseManager responseManager = new ResponseManager(supportProvider);
+        responseManager.addHandler(input);
+
+        Assert.assertEquals(expectedHandlers, handlersField.get(responseManager));
+    }
+
+    @Test
+    public void testRemoveHandler() throws IllegalAccessException {
+        Request input = new Request(supportProvider);
+        Request extra = new Request(supportProvider);
+
+        List<Request> inputHandlers = Collections.synchronizedList(new ArrayList<Request>());
+        inputHandlers.add(extra);
+        inputHandlers.add(input);
+        inputHandlers.add(extra);
+
+        List<Request> expectedHandlers = Collections.synchronizedList(new ArrayList<Request>());
+        expectedHandlers.add(extra);
+        expectedHandlers.add(extra);
+
+        ResponseManager responseManager = new ResponseManager(supportProvider);
+        handlersField.set(responseManager, inputHandlers);
+
+        responseManager.removeHandler(input);
+
+        Assert.assertEquals(expectedHandlers, handlersField.get(responseManager));
+    }
+
+    @Test
+    public void testRemoveHandlerClass() throws IllegalAccessException {
+        Request input1 = new GetEventAlarmList(supportProvider);
+        Request input2 = new GetEventAlarmList(supportProvider);
+        Request extra = new Request(supportProvider);
+
+        List<Request> inputHandlers = Collections.synchronizedList(new ArrayList<Request>());
+        inputHandlers.add(extra);
+        inputHandlers.add(input1);
+        inputHandlers.add(extra);
+        inputHandlers.add(input2);
+
+        List<Request> expectedHandlers = Collections.synchronizedList(new ArrayList<Request>());
+        expectedHandlers.add(extra);
+        expectedHandlers.add(extra);
+
+        ResponseManager responseManager = new ResponseManager(supportProvider);
+        handlersField.set(responseManager, inputHandlers);
+
+        responseManager.removeHandler(GetEventAlarmList.class);
+
+        Assert.assertEquals(expectedHandlers, handlersField.get(responseManager));
+    }
+
+    @Test
+    public void testHandleDataCompletePacketSynchronous() throws Exception {
+        // Note that this is not a proper packet, but that doesn't matter as we're not testing
+        // the packet parsing.
+        byte[] input = {0x01, 0x02, 0x03, 0x04};
+
+        AsynchronousResponse mockAsynchronousResponse = Mockito.mock(AsynchronousResponse.class);
+
+        HuaweiPacket mockHuaweiPacket = Mockito.mock(HuaweiPacket.class);
+        mockHuaweiPacket.complete = true;
+        when(mockHuaweiPacket.parse((byte[]) any()))
+                .thenReturn(mockHuaweiPacket);
+
+        Request request1 = Mockito.mock(Request.class);
+        when(request1.handleResponse((HuaweiPacket) any()))
+                .thenReturn(true);
+        when(request1.autoRemoveFromResponseHandler())
+                .thenReturn(true);
+        Request request2 = Mockito.mock(Request.class);
+
+        List<Request> inputHandlers = Collections.synchronizedList(new ArrayList<Request>());
+        inputHandlers.add(request1);
+        inputHandlers.add(request2);
+
+        List<Request> expectedHandlers = Collections.synchronizedList(new ArrayList<Request>());
+        expectedHandlers.add(request2);
+
+        ResponseManager responseManager = new ResponseManager(supportProvider);
+        handlersField.set(responseManager, inputHandlers);
+        receivedPacketField.set(responseManager, mockHuaweiPacket);
+        asynchronousResponseField.set(responseManager, mockAsynchronousResponse);
+
+        responseManager.handleData(input);
+
+        Assert.assertEquals(expectedHandlers, handlersField.get(responseManager));
+        Assert.assertNull(receivedPacketField.get(responseManager));
+
+        verify(mockHuaweiPacket, times(1)).parse(input);
+        verify(mockAsynchronousResponse, times(0)).handleResponse((HuaweiPacket) any());
+        verify(request1, times(1)).handleResponse(mockHuaweiPacket);
+        verify(request1, times(1)).handleResponse();
+        verify(request2, times(0)).handleResponse((HuaweiPacket) any());
+        verify(request2, times(0)).handleResponse();
+    }
+
+    @Test
+    public void testHandleDataCompletePacketAsynchronous() throws Exception {
+        // Note that this is not a proper packet, but that doesn't matter as we're not testing
+        // the packet parsing.
+        byte[] input = {0x01, 0x02, 0x03, 0x04};
+
+        AsynchronousResponse mockAsynchronousResponse = Mockito.mock(AsynchronousResponse.class);
+
+        HuaweiPacket mockHuaweiPacket = Mockito.mock(HuaweiPacket.class);
+        mockHuaweiPacket.complete = true;
+        when(mockHuaweiPacket.parse((byte[]) any()))
+                .thenReturn(mockHuaweiPacket);
+
+        Request request1 = Mockito.mock(Request.class);
+        when(request1.handleResponse((HuaweiPacket) any()))
+                .thenReturn(false);
+        Request request2 = Mockito.mock(Request.class);
+        when(request2.handleResponse((HuaweiPacket) any()))
+                .thenReturn(false);
+
+        List<Request> inputHandlers = Collections.synchronizedList(new ArrayList<Request>());
+        inputHandlers.add(request1);
+        inputHandlers.add(request2);
+
+        List<Request> expectedHandlers = Collections.synchronizedList(new ArrayList<Request>());
+        expectedHandlers.add(request1);
+        expectedHandlers.add(request2);
+
+        ResponseManager responseManager = new ResponseManager(supportProvider);
+        handlersField.set(responseManager, inputHandlers);
+        receivedPacketField.set(responseManager, mockHuaweiPacket);
+        asynchronousResponseField.set(responseManager, mockAsynchronousResponse);
+
+        responseManager.handleData(input);
+
+        Assert.assertEquals(expectedHandlers, handlersField.get(responseManager));
+        Assert.assertNull(receivedPacketField.get(responseManager));
+
+        verify(mockHuaweiPacket, times(1)).parse(input);
+        verify(mockAsynchronousResponse, times(1)).handleResponse(mockHuaweiPacket);
+        verify(request1, times(1)).handleResponse(mockHuaweiPacket);
+        verify(request1, times(0)).handleResponse();
+        verify(request2, times(1)).handleResponse(mockHuaweiPacket);
+        verify(request2, times(0)).handleResponse();
+    }
+
+    @Test
+    public void testHandleDataTwoPartialPacketsSynchronous() throws Exception {
+        // Note that this is not a proper packet, but that doesn't matter as we're not testing
+        // the packet parsing.
+        byte[] input1 = {0x01, 0x02, 0x03, 0x04};
+        byte[] input2 = {0x05, 0x06, 0x07, 0x08};
+
+        AsynchronousResponse mockAsynchronousResponse = Mockito.mock(AsynchronousResponse.class);
+
+        HuaweiPacket mockHuaweiPacket = Mockito.mock(HuaweiPacket.class);
+        mockHuaweiPacket.complete = false;
+        when(mockHuaweiPacket.parse((byte[]) any()))
+                .thenReturn(mockHuaweiPacket);
+
+        Request request1 = Mockito.mock(Request.class);
+        when(request1.handleResponse((HuaweiPacket) any()))
+                .thenReturn(true);
+        when(request1.autoRemoveFromResponseHandler())
+                .thenReturn(true);
+        Request request2 = Mockito.mock(Request.class);
+
+        List<Request> inputHandlers = Collections.synchronizedList(new ArrayList<Request>());
+        inputHandlers.add(request1);
+        inputHandlers.add(request2);
+
+        List<Request> expectedHandlers1 = Collections.synchronizedList(new ArrayList<Request>());
+        expectedHandlers1.add(request1);
+        expectedHandlers1.add(request2);
+
+        List<Request> expectedHandlers2 = Collections.synchronizedList(new ArrayList<Request>());
+        expectedHandlers2.add(request2);
+
+        ResponseManager responseManager = new ResponseManager(supportProvider);
+        handlersField.set(responseManager, inputHandlers);
+        receivedPacketField.set(responseManager, mockHuaweiPacket);
+        asynchronousResponseField.set(responseManager, mockAsynchronousResponse);
+
+        responseManager.handleData(input1);
+
+        Assert.assertEquals(expectedHandlers1, handlersField.get(responseManager));
+        Assert.assertEquals(mockHuaweiPacket, receivedPacketField.get(responseManager));
+
+        verify(mockHuaweiPacket, times(1)).parse(input1);
+        verify(mockAsynchronousResponse, times(0)).handleResponse((HuaweiPacket) any());
+        verify(request1, times(0)).handleResponse(mockHuaweiPacket);
+        verify(request1, times(0)).handleResponse();
+        verify(request2, times(0)).handleResponse((HuaweiPacket) any());
+        verify(request2, times(0)).handleResponse();
+
+        mockHuaweiPacket.complete = true;
+        responseManager.handleData(input2);
+
+        Assert.assertEquals(expectedHandlers2, handlersField.get(responseManager));
+        Assert.assertNull(receivedPacketField.get(responseManager));
+
+        verify(mockHuaweiPacket, times(1)).parse(input2);
+        verify(mockAsynchronousResponse, times(0)).handleResponse((HuaweiPacket) any());
+        verify(request1, times(1)).handleResponse(mockHuaweiPacket);
+        verify(request1, times(1)).handleResponse();
+        verify(request2, times(0)).handleResponse((HuaweiPacket) any());
+        verify(request2, times(0)).handleResponse();
+    }
+
+    @Test
+    public void testHandleDataTwoPartialPacketsAsynchronous() throws Exception {
+        // Note that this is not a proper packet, but that doesn't matter as we're not testing
+        // the packet parsing.
+        byte[] input1 = {0x01, 0x02, 0x03, 0x04};
+        byte[] input2 = {0x05, 0x06, 0x07, 0x08};
+
+        AsynchronousResponse mockAsynchronousResponse = Mockito.mock(AsynchronousResponse.class);
+
+        HuaweiPacket mockHuaweiPacket = Mockito.mock(HuaweiPacket.class);
+        mockHuaweiPacket.complete = false;
+        when(mockHuaweiPacket.parse((byte[]) any()))
+                .thenReturn(mockHuaweiPacket);
+
+        Request request1 = Mockito.mock(Request.class);
+        when(request1.handleResponse((HuaweiPacket) any()))
+                .thenReturn(false);
+        Request request2 = Mockito.mock(Request.class);
+        when(request2.handleResponse((HuaweiPacket) any()))
+                .thenReturn(false);
+
+        List<Request> inputHandlers = Collections.synchronizedList(new ArrayList<Request>());
+        inputHandlers.add(request1);
+        inputHandlers.add(request2);
+
+        List<Request> expectedHandlers = Collections.synchronizedList(new ArrayList<Request>());
+        expectedHandlers.add(request1);
+        expectedHandlers.add(request2);
+
+        ResponseManager responseManager = new ResponseManager(supportProvider);
+        handlersField.set(responseManager, inputHandlers);
+        receivedPacketField.set(responseManager, mockHuaweiPacket);
+        asynchronousResponseField.set(responseManager, mockAsynchronousResponse);
+
+        responseManager.handleData(input1);
+
+        Assert.assertEquals(expectedHandlers, handlersField.get(responseManager));
+        Assert.assertEquals(mockHuaweiPacket, receivedPacketField.get(responseManager));
+
+        verify(mockHuaweiPacket, times(1)).parse(input1);
+        verify(mockAsynchronousResponse, times(0)).handleResponse((HuaweiPacket) any());
+        verify(request1, times(0)).handleResponse(mockHuaweiPacket);
+        verify(request1, times(0)).handleResponse();
+        verify(request2, times(0)).handleResponse((HuaweiPacket) any());
+        verify(request2, times(0)).handleResponse();
+
+        mockHuaweiPacket.complete = true;
+        responseManager.handleData(input2);
+
+        Assert.assertEquals(expectedHandlers, handlersField.get(responseManager));
+        Assert.assertNull(receivedPacketField.get(responseManager));
+
+        verify(mockHuaweiPacket, times(1)).parse(input2);
+        verify(mockAsynchronousResponse, times(1)).handleResponse((HuaweiPacket) any());
+        verify(request1, times(1)).handleResponse(mockHuaweiPacket);
+        verify(request1, times(0)).handleResponse();
+        verify(request2, times(1)).handleResponse((HuaweiPacket) any());
+        verify(request2, times(0)).handleResponse();
+    }
+
+    @Test
+    public void testOnSocketReadMultiplePacketSplit() throws IllegalAccessException, HuaweiPacket.ParseException {
+        byte[] expected = {(byte) 0x5A, (byte) 0x00, (byte) 0x06, (byte) 0x00, (byte) 0x04, (byte) 0x01, (byte) 0x01, (byte) 0x01, (byte) 0x02, (byte) 0x99, (byte) 0x6B};
+
+        byte[] data1 = {(byte) 0x5A, (byte) 0x00, (byte) 0x06, (byte) 0x00, (byte) 0x04, (byte) 0x01, (byte) 0x01};
+        byte[] data2 = {(byte) 0x01, (byte) 0x02, (byte) 0x99, (byte) 0x6B, (byte) 0x5A, (byte) 0x00, (byte) 0x06, (byte) 0x00, (byte) 0x04, (byte) 0x01, (byte) 0x01, (byte) 0x01, (byte) 0x02, (byte) 0x99, (byte) 0x6B};
+
+        HuaweiPacket expectedPacket = new HuaweiPacket(supportProvider.getParamsProvider()).parse(expected);
+
+        AsynchronousResponse mockAsynchronousResponse = Mockito.mock(AsynchronousResponse.class);
+
+        ResponseManager responseManager = new ResponseManager(supportProvider);
+        asynchronousResponseField.set(responseManager, mockAsynchronousResponse);
+
+        responseManager.handleData(data1);
+        responseManager.handleData(data2);
+
+        verify(mockAsynchronousResponse, times(2)).handleResponse(expectedPacket);
+    }
+}
diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/FirmwareTest.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/FirmwareTest.java
new file mode 100644
index 0000000..9a79ab0
--- /dev/null
+++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/FirmwareTest.java
@@ -0,0 +1,190 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.miband;
+
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Ignore;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.Arrays;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
+
+// while read i; do ./gradlew --daemon -a --offline  -DMiFirmwareDir=$i  test; if test $? != 0; then echo "Failure in $i" && break; fi; done < fw.dirs
+@Ignore("Disabled for travis -- needs vm parameter -DMiFirmwareDir=/path/to/firmware/directory/")
+public class FirmwareTest {
+
+    private static final long MAX_FILE_SIZE_BYTES = 1024 * 1536; // 1.5MB
+    private static final int MI_FW_VERSION = 0; // FIXME
+    private static final int MI1A_FW_VERSION = 0; // FIXME
+    private static final int MI1S_FW1_VERSION = 0;
+    private static final int MI1S_FW2_VERSION = 0;
+
+    private static final int SINGLE = 1;
+    private static final int DOUBLE = 2;
+
+    @BeforeClass
+    public static void setupSuite() {
+        getFirmwareDir(); // throws if firmware directory not available
+    }
+
+    @Test
+    public void testFirmwareMi1() throws Exception {
+        byte[] wholeFw = getFirmwareMi();
+        Assert.assertNotNull(wholeFw);
+
+        AbstractMiFirmwareInfo info = getFirmwareInfo(wholeFw, SINGLE);
+        info.checkValid();
+
+        int calculatedVersion = info.getFirmwareVersion();
+        String version = MiBandFWHelper.formatFirmwareVersion(calculatedVersion);
+        Assert.assertTrue(version.startsWith("1."));
+        Assert.assertArrayEquals(wholeFw, info.getFirmwareBytes());
+//        Assert.assertEquals("Unexpected firmware version: " + calculatedVersion, MI_FW_VERSION, calculatedVersion);
+    }
+
+    @Test
+    public void testFirmwareMi1A() throws Exception {
+        byte[] wholeFw = getFirmwareMi1A();
+        Assert.assertNotNull(wholeFw);
+
+        AbstractMiFirmwareInfo info = getFirmwareInfo(wholeFw, SINGLE);
+        info.checkValid();
+
+        int calculatedVersion = info.getFirmwareVersion();
+        String version = MiBandFWHelper.formatFirmwareVersion(calculatedVersion);
+        Assert.assertTrue(version.startsWith("5."));
+        Assert.assertArrayEquals(wholeFw, info.getFirmwareBytes());
+//        Assert.assertEquals("Unexpected firmware version: " + calculatedVersion, MI1A_FW_VERSION, calculatedVersion);
+    }
+
+    @Test
+    public void testFirmwareMi1S() throws Exception {
+        byte[] wholeFw = getFirmwareMi1S();
+        Assert.assertNotNull(wholeFw);
+
+        AbstractMiFirmwareInfo info = getFirmwareInfo(wholeFw, DOUBLE);
+        info.checkValid();
+
+        // Mi Band version
+        int calculatedVersionFw1 = info.getFirst().getFirmwareVersion();
+//        Assert.assertEquals("Unexpected firmware 1 version: " + calculatedVersionFw1, MI1S_FW1_VERSION, calculatedVersionFw1);
+        String version1 = MiBandFWHelper.formatFirmwareVersion(calculatedVersionFw1);
+        Assert.assertTrue(version1.startsWith("4."));
+
+        // HR version
+        int calculatedVersionFw2 = info.getSecond().getFirmwareVersion();
+//        Assert.assertEquals("Unexpected firmware 2 version: " + calculatedVersionFw2, MI1S_FW2_VERSION, calculatedVersionFw2);
+        String version2 = MiBandFWHelper.formatFirmwareVersion(calculatedVersionFw2);
+        Assert.assertTrue(version2.startsWith("1."));
+
+        try {
+            info.getFirmwareVersion();
+            Assert.fail("should not get fw version from AbstractMi1SFirmwareInfo");
+        } catch (UnsupportedOperationException expected) {
+        }
+
+        Assert.assertFalse(Arrays.equals(info.getFirst().getFirmwareBytes(), info.getSecond().getFirmwareBytes()));
+    }
+
+    @Test
+    public void testDoubleFirmwareMi1A() throws Exception {
+        byte[] wholeFw = getFirmwareMi1A();
+        Assert.assertNotNull(wholeFw);
+
+        AbstractMiFirmwareInfo info = TestMi1AFirmwareInfo.getInstance(wholeFw);
+        Assert.assertNotNull(info);
+        info.checkValid();
+
+        // Mi Band version
+        int calculatedVersionFw1 = info.getFirst().getFirmwareVersion();
+//        Assert.assertEquals("Unexpected firmware 1 version: " + calculatedVersionFw1, MI1S_FW1_VERSION, calculatedVersionFw1);
+        String version1 = MiBandFWHelper.formatFirmwareVersion(calculatedVersionFw1);
+        Assert.assertTrue(version1.startsWith("5."));
+
+        // Same Mi Band version
+        int calculatedVersionFw2 = info.getSecond().getFirmwareVersion();
+//        Assert.assertEquals("Unexpected firmware 2 version: " + calculatedVersionFw2, MI1S_FW2_VERSION, calculatedVersionFw2);
+        String version2 = MiBandFWHelper.formatFirmwareVersion(calculatedVersionFw2);
+        Assert.assertTrue(version2.startsWith("5."));
+
+        try {
+            info.getFirmwareVersion();
+            Assert.fail("should not get fw version from AbstractMi1SFirmwareInfo");
+        } catch (UnsupportedOperationException expected) {
+        }
+
+        // these are actually the same with this test info!
+        Assert.assertEquals(info.getFirst().getFirmwareOffset(), info.getSecond().getFirmwareOffset());
+        Assert.assertTrue(Arrays.equals(info.getFirst().getFirmwareBytes(), info.getSecond().getFirmwareBytes()));
+    }
+
+    private AbstractMiFirmwareInfo getFirmwareInfo(byte[] wholeFw, int numFirmwares) {
+        AbstractMiFirmwareInfo info = AbstractMiFirmwareInfo.determineFirmwareInfoFor(wholeFw);
+        assertFirmwareInfo(info, wholeFw, numFirmwares);
+        return info;
+    }
+
+    private AbstractMiFirmwareInfo assertFirmwareInfo(AbstractMiFirmwareInfo info, byte[] wholeFw, int numFirmwares) {
+        switch (numFirmwares) {
+            case SINGLE: {
+                Assert.assertTrue("should be single miband firmware", info.isSingleMiBandFirmware());
+                Assert.assertSame(info, info.getFirst());
+                try {
+                    info.getSecond();
+                    Assert.fail("should throw UnsuportedOperationException");
+                } catch (UnsupportedOperationException expected) {
+                }
+                int calculatedLength = info.getFirmwareLength();
+                Assert.assertTrue("Unexpected firmware length: " + wholeFw.length, calculatedLength <= wholeFw.length);
+                break;
+            }
+            case DOUBLE: {
+                Assert.assertFalse("should not be single miband firmware", info.isSingleMiBandFirmware());
+                Assert.assertNotSame(info, info.getFirst());
+                Assert.assertNotSame(info, info.getSecond());
+                Assert.assertNotSame(info.getFirst(), info.getSecond());
+                int calculatedLength = info.getFirst().getFirmwareLength();
+                Assert.assertTrue("Unexpected firmware length: " + wholeFw.length, calculatedLength <= wholeFw.length);
+                calculatedLength = info.getSecond().getFirmwareLength();
+                Assert.assertTrue("Unexpected firmware length: " + wholeFw.length, calculatedLength <= wholeFw.length);
+                break;
+            }
+            default:
+                Assert.fail("unexpected numFirmwares: " + numFirmwares);
+        }
+        Assert.assertTrue(info.isGenerallySupportedFirmware());
+        return info;
+    }
+
+    private static File getFirmwareDir() {
+        String path = System.getProperty("MiFirmwareDir");
+        Assert.assertNotNull("You must run this test with -DMiFirmwareDir=/path/to/directory/with/miband/firmwarefiles/", path);
+        File dir = new File(path);
+        Assert.assertTrue("System property MiFirmwareDir should point to a directory containing the Mi Band firmware files", dir.isDirectory());
+        return dir;
+    }
+
+    private byte[] getFirmwareMi() throws IOException {
+        return getFirmware(new File(getFirmwareDir(), "Mili.fw"));
+    }
+
+    private byte[] getFirmwareMi1A() throws IOException {
+        return getFirmware(new File(getFirmwareDir(), "Mili_1a.fw"));
+    }
+
+    private byte[] getFirmwareMi1S() throws IOException {
+        return getFirmware(new File(getFirmwareDir(), "Mili_hr.fw"));
+    }
+
+    private byte[] getFirmware(File aFile) throws IOException {
+        Assert.assertNotNull(aFile);
+        try (FileInputStream stream = new FileInputStream(aFile)) {
+            return FileUtils.readAll(stream, MAX_FILE_SIZE_BYTES);
+        }
+    }
+
+}
diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/nut/NutUtilsTest.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/nut/NutUtilsTest.java
new file mode 100644
index 0000000..0df0211
--- /dev/null
+++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/nut/NutUtilsTest.java
@@ -0,0 +1,247 @@
+/*  Copyright (C) 2020 Taavi Eom盲e
+
+    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 <http://www.gnu.org/licenses/>.
+    */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.nut;
+
+import org.junit.Test;
+
+import java.math.BigInteger;
+import java.util.AbstractMap;
+import java.util.Map;
+
+import static junit.framework.TestCase.assertEquals;
+import static junit.framework.TestCase.assertNotNull;
+import static junit.framework.TestCase.fail;
+import static nodomain.freeyourgadget.gadgetbridge.devices.nut.NutKey.byteArraysConcatReverseWithPad;
+import static nodomain.freeyourgadget.gadgetbridge.devices.nut.NutKey.byteArraysDeConcatReverseWithPad;
+import static nodomain.freeyourgadget.gadgetbridge.devices.nut.NutKey.bytesToHex2;
+import static nodomain.freeyourgadget.gadgetbridge.devices.nut.NutKey.hexStringToByteArrayNut;
+import static nodomain.freeyourgadget.gadgetbridge.devices.nut.NutKey.macAsByteArray;
+import static nodomain.freeyourgadget.gadgetbridge.devices.nut.NutKey.passwordGeneration;
+import static nodomain.freeyourgadget.gadgetbridge.devices.nut.NutKey.reversePasswordGeneration;
+
+public class NutUtilsTest {
+    @Test
+    public void testPasswordGen() {
+        String result = bytesToHex2(passwordGeneration(
+                "00:00:00:00:00:00",
+                new byte[]{1, 0, 0, 0, 0, 0, 0},
+                BigInteger.ZERO,
+                BigInteger.ZERO)
+        );
+        String expected = bytesToHex2(new byte[]{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
+        assertEquals(expected, result);
+    }
+
+    @Test
+    public void testPasswordGen2() {
+        String result = bytesToHex2(passwordGeneration(
+                "00:00:00:00:00:00",
+                new byte[]{1, 0, 0, 0, 0, 0, 0},
+                BigInteger.ZERO,
+                BigInteger.ZERO)
+        );
+        String expected = bytesToHex2(new byte[]{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
+        assertEquals(expected, result);
+    }
+
+    @Test
+    public void testPasswordGen3() {
+        String result = bytesToHex2(passwordGeneration(
+                "00:00:00:00:00:00",
+                new byte[]{1, 1, 0, 0, 0, 0, 0},
+                BigInteger.ZERO,
+                BigInteger.ZERO)
+        );
+        String expected = bytesToHex2(new byte[]{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
+        assertEquals(expected, result);
+    }
+
+    @Test
+    public void testPasswordGen4() {
+        String result = bytesToHex2(passwordGeneration(
+                "00:00:00:00:00:00",
+                new byte[]{1, 0, 0, 0, 0, 0, 0},
+                new BigInteger(1, new byte[]{1, 0, 0, 0, 0, 0}),
+                BigInteger.ZERO)
+        );
+        String expected = bytesToHex2(new byte[]{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00});
+        assertEquals(expected, result);
+    }
+
+    @Test
+    public void testPasswordGen5() {
+        String result = bytesToHex2(passwordGeneration(
+                "00:00:00:00:00:00",
+                new byte[]{1, 0, 0, 0, 0, 0, 0, 0, 0},
+                BigInteger.ZERO,
+                new BigInteger(1, new byte[]{1, 1, 1, 1, 1, 1, 0, 0}))
+        );
+        String expected = bytesToHex2(new byte[]{0x00, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
+        assertEquals(expected, result);
+    }
+
+    @Test
+    public void testPasswordGen6() {
+        String result = bytesToHex2(passwordGeneration(
+                "01:02:03:04:05:06",
+                new byte[]{1, 1, 2, 3, 4, 5, 6, 7, 8},
+                BigInteger.ZERO,
+                BigInteger.ZERO)
+        );
+        String expected = bytesToHex2(new byte[]{0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
+        assertEquals(expected, result);
+    }
+
+    @Test
+    public void testPasswordGen7() {
+        String result = bytesToHex2(passwordGeneration(
+                "01:02:03:04:05:06",
+                new byte[]{1, 1, 2, 3, 4, 5, 6, 7, 8},
+                new BigInteger(1, new byte[]{1, 2, 3, 4, 5, 6, 7, 8}),
+                new BigInteger(1, new byte[]{1, 2, 3, 4, 5, 6, 7, 8}))
+        );
+        String expected = bytesToHex2(new byte[]{0x17, 0x15, 0x13, 0x11, 0x0f, 0x0d, 0x0b, 0x0a, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01});
+        assertEquals(expected, result);
+    }
+
+    @Test
+    public void testReversePassword() {
+        String challenge = "01e8f0340d000000000000000000000000";
+        String response = "029bbd0fa25aed0000dcfd0c0000000000";
+        String device_mac = "ED:5A:94:CB:98:E4";
+
+        Map.Entry<BigInteger, BigInteger> key = reversePasswordGeneration(
+                hexStringToByteArrayNut(challenge),
+                hexStringToByteArrayNut(response),
+                device_mac
+        );
+
+        assertNotNull(key);
+        assertEquals(new AbstractMap.SimpleEntry<>(new BigInteger("851420"), new BigInteger("996303")), key);
+    }
+
+    @Test
+    public void testPassword() {
+        String challenge = "011a9b826c000000000000000000000000";
+        String response = "02cd675d015bed0000dcfd0c0000000000";
+        String device_mac = "ED:5A:94:CB:98:E4";
+
+        Map.Entry<BigInteger, BigInteger> key = new AbstractMap.SimpleEntry<>(new BigInteger("851420"), new BigInteger("996303"));
+
+        byte[] result = new byte[17];
+        result[0] = 0x02;
+        System.arraycopy(passwordGeneration(device_mac, hexStringToByteArrayNut(challenge), key.getKey(), key.getValue()), 0, result, 1, 16);
+
+        assertEquals(bytesToHex2(hexStringToByteArrayNut(response)), bytesToHex2(result));
+    }
+
+    @Test
+    public void testInvalidResponse() {
+        String challenge = "00";
+        String response = "00";
+        String device_mac = "0:00:00:00:00:00";
+        try {
+            reversePasswordGeneration(
+                    hexStringToByteArrayNut(challenge),
+                    hexStringToByteArrayNut(response),
+                    device_mac
+            );
+        } catch (IllegalArgumentException e) {
+            // This is intended behaviour
+            assertNotNull(e);
+            return;
+        }
+        fail();
+    }
+
+    @Test
+    public void testHexToByteArray() {
+        byte[] arr = hexStringToByteArrayNut("0x0000000000");
+        assertEquals(bytesToHex2(new byte[]{0x00, 0x00, 0x00, 0x00, 0x00}), bytesToHex2(arr));
+    }
+
+    @Test
+    public void testHexToByteArray2() {
+        byte[] arr = hexStringToByteArrayNut("cafebabe");
+        assertEquals(bytesToHex2(new byte[]{(byte) 0xca, (byte) 0xfe, (byte) 0xba, (byte) 0xbe}), bytesToHex2(arr));
+    }
+
+    @Test
+    public void testMACToByteArray() {
+        byte[] arr = macAsByteArray("AA:BB:CC:DD:EE:FF");
+        assertEquals(bytesToHex2(
+                new byte[]{
+                        (byte) 0xaa,
+                        (byte) 0xbb,
+                        (byte) 0xcc,
+                        (byte) 0xdd,
+                        (byte) 0xee,
+                        (byte) 0xff
+                }
+        ), bytesToHex2(arr));
+    }
+
+    @Test
+    public void testConcat() {
+        byte[] src1 = new byte[]{1, 2, 3, 4, 5, 6, 7, 8};
+        byte[] src2 = new byte[]{9, 10, 11, 12, 13, 14, 15, 16};
+        assertEquals(
+                bytesToHex2(new byte[]{0x10, 0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01}),
+                bytesToHex2(byteArraysConcatReverseWithPad(
+                        src1,
+                        src2)
+                )
+        );
+    }
+
+    @Test
+    public void testConcatDeConcat() {
+        byte[] src1 = new byte[]{0, 0, 3, 4, 5, 6, 0, 0};
+        byte[] src2 = new byte[]{0, 0, 7, 8, 9, 1, 0, 0};
+        byte[] dst1 = new byte[8];
+        byte[] dst2 = new byte[8];
+        byteArraysDeConcatReverseWithPad(
+                byteArraysConcatReverseWithPad(
+                        src1,
+                        src2
+                ),
+                dst1,
+                dst2
+        );
+        assertEquals(bytesToHex2(src1), bytesToHex2(dst1));
+        assertEquals(bytesToHex2(src2), bytesToHex2(dst2));
+    }
+
+    @Test
+    public void testConcatDeConcat2() {
+        byte[] src1 = new byte[]{1, 2, 3, 4, 5, 6, 7, 8};
+        byte[] src2 = new byte[]{9, 10, 11, 12, 13, 14, 15, 16};
+        byte[] dst1 = new byte[8];
+        byte[] dst2 = new byte[8];
+        byteArraysDeConcatReverseWithPad(
+                byteArraysConcatReverseWithPad(
+                        src1,
+                        src2
+                ),
+                dst1,
+                dst2
+        );
+        assertEquals(bytesToHex2(src1), bytesToHex2(dst1));
+        assertEquals(bytesToHex2(src2), bytesToHex2(dst2));
+    }
+}
diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/oppo/OppoHeadphonesProtocolTest.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/oppo/OppoHeadphonesProtocolTest.java
new file mode 100644
index 0000000..cebbab5
--- /dev/null
+++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/oppo/OppoHeadphonesProtocolTest.java
@@ -0,0 +1,44 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.oppo;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdatePreferences;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo;
+import nodomain.freeyourgadget.gadgetbridge.test.TestBase;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class OppoHeadphonesProtocolTest extends TestBase {
+    @Test
+    public void testHandleFirmware() {
+        final OppoHeadphonesProtocol protocol = new OppoHeadphonesProtocol(null);
+
+        GBDeviceEvent[] oppoEvents = protocol.decodeResponse(GB.hexStringToByteArray("aa4f00000581f34800000a312c312c312c312c322c3136302c312c332c3838382c312c342c302c322c312c312c322c322c3136302c322c332c3838382c322c342c302c332c312c312c332c322c38323700"));
+        Assert.assertEquals(1, oppoEvents.length);
+        GBDeviceEventVersionInfo oppoEvent = (GBDeviceEventVersionInfo) oppoEvents[0];
+        Assert.assertEquals("160.160.827", oppoEvent.fwVersion);
+
+        GBDeviceEvent[] air2Events = protocol.decodeResponse(GB.hexStringToByteArray("AA4500000581003E000009312C312C302C312C322C3134322C312C342C302C322C312C382C322C322C3134322C322C342C302C332C312C33332C332C322C3132392C332C342C30"));
+        Assert.assertEquals(1, air2Events.length);
+        GBDeviceEventVersionInfo air2event = (GBDeviceEventVersionInfo) air2Events[0];
+        Assert.assertEquals("142.142.129", air2event.fwVersion);
+
+        GBDeviceEvent[] realme = protocol.decodeResponse(GB.hexStringToByteArray("aa2a040005810e23000003312c322c312e312e302e37352c322c322c312e312e302e37352c332c322c303031"));
+        Assert.assertEquals(1, realme.length);
+        GBDeviceEventVersionInfo realmeEvent = (GBDeviceEventVersionInfo) realme[0];
+        Assert.assertEquals("1.1.0.75", realmeEvent.fwVersion);
+    }
+
+    @Test
+    public void testMultipleResponses() {
+        final OppoHeadphonesProtocol protocol = new OppoHeadphonesProtocol(null);
+        GBDeviceEvent[] events = protocol.decodeResponse(GB.hexStringToByteArray("AA4100000881013A00000E010100000101010101010205010103000101040C0101050001010600020100000201010102010206020103000201040B0201050002010600AA0F000006810208000003016402640346"));
+        Assert.assertEquals(4, events.length);
+        Assert.assertTrue(events[0] instanceof GBDeviceEventUpdatePreferences);
+        Assert.assertTrue(events[1] instanceof GBDeviceEventBatteryInfo);
+        Assert.assertTrue(events[2] instanceof GBDeviceEventBatteryInfo);
+        Assert.assertTrue(events[3] instanceof GBDeviceEventBatteryInfo);
+    }
+}
diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/headphones/protocol/impl/MockSonyCoordinator.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/headphones/protocol/impl/MockSonyCoordinator.java
new file mode 100644
index 0000000..4ef0799
--- /dev/null
+++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/headphones/protocol/impl/MockSonyCoordinator.java
@@ -0,0 +1,53 @@
+/*  Copyright (C) 2023 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.impl;
+
+import androidx.annotation.NonNull;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.SonyHeadphonesCapabilities;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.SonyHeadphonesCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+
+public class MockSonyCoordinator extends SonyHeadphonesCoordinator {
+    private final DeviceType deviceType = DeviceType.SONY_WH_1000XM3;
+
+    private final List<SonyHeadphonesCapabilities> capabilities = new ArrayList<>();
+
+    @NonNull
+    @Override
+    public boolean supports(final GBDeviceCandidate candidate) {
+        return true;
+    }
+
+    @Override
+    public int getDeviceNameResource() {
+        return R.string.devicetype_sony_wh_1000xm5;
+    }
+
+    public void addCapability(final SonyHeadphonesCapabilities capability) {
+        capabilities.add(capability);
+    }
+
+    public List<SonyHeadphonesCapabilities> getCapabilities() {
+        return capabilities;
+    }
+}
diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/headphones/protocol/impl/SonyTestUtils.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/headphones/protocol/impl/SonyTestUtils.java
new file mode 100644
index 0000000..b274aa1
--- /dev/null
+++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/headphones/protocol/impl/SonyTestUtils.java
@@ -0,0 +1,121 @@
+/*  Copyright (C) 2022 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.impl;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.function.Function;
+
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdatePreferences;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.QuickAccess;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.Message;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.MessageType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.Request;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class SonyTestUtils {
+    public static void assertRequest(final Request request, final String messageHex) {
+        final Message message = Message.fromBytes(GB.hexStringToByteArray(messageHex.replace(":", "")));
+        assertNotNull(message);
+        assertRequest(request, message.getType(), message.getPayload());
+    }
+
+    public static void assertRequest(final Request request, final int messageType, final String payloadHex) {
+        if (payloadHex == null) {
+            assertNull(request);
+            return;
+        }
+
+        assertRequest(
+                request,
+                MessageType.fromCode((byte) messageType),
+                GB.hexStringToByteArray(payloadHex.replace(":", ""))
+        );
+    }
+
+    public static void assertRequest(final Request request, final MessageType messageType, final byte[] payload) {
+        assertEquals("Message types should be the same", messageType, request.messageType());
+        assertEquals("Payloads should be the same", hexdump(payload), hexdump(request.payload()));
+    }
+
+    public static <A> void assertRequests(Function<A, Request> requestFunction, final int messageType, final Map<A, String> expectedRequests) {
+        for (Map.Entry<A, String> entry : expectedRequests.entrySet()) {
+            final Request request = requestFunction.apply(entry.getKey());
+            assertRequest(request, messageType, entry.getValue());
+        }
+    }
+
+    public static <A> void assertRequests(Function<A, Request> requestFunction, final Map<A, String> expectedRequests) {
+        assertRequests(requestFunction, 0x0c, expectedRequests);
+    }
+
+    public static void assertPrefs(final List<? extends GBDeviceEvent> events, final Map<String, Object> expectedPrefs) {
+        assertEquals("Expect 1 events", 1, events.size());
+        final GBDeviceEventUpdatePreferences event = (GBDeviceEventUpdatePreferences) events.get(0);
+        assertEquals("Expect number of prefs", expectedPrefs.size(), event.preferences.size());
+
+        for (Map.Entry<String, Object> pref : expectedPrefs.entrySet()) {
+            assertEquals("Expect " + pref.getKey() + " value", pref.getValue(), event.preferences.get(pref.getKey()));
+        }
+    }
+    
+    public static <A> void printRequests(Function<A, Request> requestFunction, final Map<A, String> expectedRequests) {
+        for (Map.Entry<A, String> entry : expectedRequests.entrySet()) {
+            final Request request = requestFunction.apply(entry.getKey());
+            printRequest(request);
+        }
+    }
+
+    public static List<? extends GBDeviceEvent> handleMessage(final AbstractSonyProtocolImpl protocol, final String messageHex) {
+        final Message message = Message.fromBytes(GB.hexStringToByteArray(messageHex.replace(":", "")));
+        assertNotNull("Failed to deserialize message from bytes", message);
+        return protocol.handlePayload(message.getType(), message.getPayload());
+    }
+
+    public static void printRequest(final Request request) {
+        System.out.printf(Locale.ROOT, "0x%02x - %s%n", request.messageType().getCode(), hexdump(request.payload()));
+    }
+
+    public static String hexdump(byte[] buffer, int offset, int length) {
+        if (length == -1) {
+            length = buffer.length - offset;
+        }
+
+        char[] hexChars = new char[length * 2 + length - 1];
+        for (int i = 0; i < length; i++) {
+            int v = buffer[i + offset] & 0xFF;
+            hexChars[i * 3] = GB.HEX_CHARS[v >>> 4];
+            hexChars[i * 3 + 1] = GB.HEX_CHARS[v & 0x0F];
+            if (i + 1 < length) {
+                hexChars[i * 3 + 2] = ':';
+            }
+        }
+        return new String(hexChars).toLowerCase(Locale.ROOT);
+    }
+
+    public static String hexdump(byte[] buffer) {
+        return hexdump(buffer, 0, buffer.length);
+    }
+}
diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/headphones/protocol/impl/v1/SonyProtocolImplV1Test.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/headphones/protocol/impl/v1/SonyProtocolImplV1Test.java
new file mode 100644
index 0000000..5c3ce9a
--- /dev/null
+++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/headphones/protocol/impl/v1/SonyProtocolImplV1Test.java
@@ -0,0 +1,538 @@
+/*  Copyright (C) 2023 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.impl.v1;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.impl.SonyTestUtils.assertRequest;
+import static nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.impl.SonyTestUtils.assertRequests;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.SonyHeadphonesCapabilities;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.SonyHeadphonesCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.AmbientSoundControl;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.AmbientSoundControlButtonMode;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.AudioUpsampling;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.AutomaticPowerOff;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.ButtonModes;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.EqualizerCustomBands;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.EqualizerPreset;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.PauseWhenTakenOff;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.QuickAccess;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.SoundPosition;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.SpeakToChatConfig;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.SpeakToChatEnabled;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.SurroundMode;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.TouchSensor;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.VoiceNotifications;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.MessageType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.Request;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.impl.MockSonyCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.impl.v1.params.BatteryType;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class SonyProtocolImplV1Test {
+    private final MockSonyCoordinator coordinator = new MockSonyCoordinator();
+    private final SonyProtocolImplV1 protocol = new SonyProtocolImplV1(null) {
+        @Override
+        protected SonyHeadphonesCoordinator getCoordinator() {
+            return coordinator;
+        }
+    };
+
+    @Before
+    public void before() {
+        coordinator.getCapabilities().clear();
+    }
+
+    @Test
+    public void getAmbientSoundControl() {
+        final Request request = protocol.getAmbientSoundControl();
+        assertRequest(request, 0x0c, "66:02");
+    }
+
+    @Test
+    public void setAmbientSoundControl() {
+        final Map<AmbientSoundControl, String> commands = new LinkedHashMap<AmbientSoundControl, String>() {{
+            put(new AmbientSoundControl(AmbientSoundControl.Mode.OFF, false, 0), "68:02:00:00:00:01:00:00");
+            put(new AmbientSoundControl(AmbientSoundControl.Mode.OFF, false, 5), "68:02:00:00:00:01:00:05");
+            put(new AmbientSoundControl(AmbientSoundControl.Mode.OFF, true, 5), "68:02:00:00:00:01:01:05");
+            put(new AmbientSoundControl(AmbientSoundControl.Mode.OFF, true, 10), "68:02:00:00:00:01:01:0A");
+            put(new AmbientSoundControl(AmbientSoundControl.Mode.AMBIENT_SOUND, false, 0), "68:02:11:00:00:01:00:00");
+            put(new AmbientSoundControl(AmbientSoundControl.Mode.AMBIENT_SOUND, false, 10), "68:02:11:00:00:01:00:0A");
+            put(new AmbientSoundControl(AmbientSoundControl.Mode.AMBIENT_SOUND, true, 10), "68:02:11:00:00:01:01:0A");
+            put(new AmbientSoundControl(AmbientSoundControl.Mode.AMBIENT_SOUND, false, 15), "68:02:11:00:00:01:00:0F");
+            put(new AmbientSoundControl(AmbientSoundControl.Mode.AMBIENT_SOUND, true, 15), "68:02:11:00:00:01:01:0F");
+            put(new AmbientSoundControl(AmbientSoundControl.Mode.NOISE_CANCELLING, false, 0), "68:02:11:00:01:01:00:00");
+            put(new AmbientSoundControl(AmbientSoundControl.Mode.NOISE_CANCELLING, true, 15), "68:02:11:00:01:01:01:00");
+            put(new AmbientSoundControl(AmbientSoundControl.Mode.NOISE_CANCELLING, false, 15), "68:02:11:00:01:01:00:00");
+        }};
+
+        for (Map.Entry<AmbientSoundControl, String> entry : commands.entrySet()) {
+            final Request request = protocol.setAmbientSoundControl(entry.getKey());
+            assertRequest(request, 0x0c, entry.getValue());
+        }
+
+        coordinator.addCapability(SonyHeadphonesCapabilities.WindNoiseReduction);
+        final Map<AmbientSoundControl, String> commandsWindNoiseReduction = new LinkedHashMap<AmbientSoundControl, String>() {{
+            put(new AmbientSoundControl(AmbientSoundControl.Mode.OFF, false, 0), "68:02:00:02:00:01:00:00");
+            put(new AmbientSoundControl(AmbientSoundControl.Mode.OFF, false, 5), "68:02:00:02:00:01:00:05");
+            put(new AmbientSoundControl(AmbientSoundControl.Mode.OFF, true, 5), "68:02:00:02:00:01:01:05");
+            put(new AmbientSoundControl(AmbientSoundControl.Mode.OFF, true, 10), "68:02:00:02:00:01:01:0A");
+            put(new AmbientSoundControl(AmbientSoundControl.Mode.AMBIENT_SOUND, false, 0), "68:02:11:02:00:01:00:00");
+            put(new AmbientSoundControl(AmbientSoundControl.Mode.AMBIENT_SOUND, false, 10), "68:02:11:02:00:01:00:0A");
+            put(new AmbientSoundControl(AmbientSoundControl.Mode.AMBIENT_SOUND, true, 10), "68:02:11:02:00:01:01:0A");
+            put(new AmbientSoundControl(AmbientSoundControl.Mode.AMBIENT_SOUND, false, 15), "68:02:11:02:00:01:00:0F");
+            put(new AmbientSoundControl(AmbientSoundControl.Mode.AMBIENT_SOUND, true, 15), "68:02:11:02:00:01:01:0F");
+            put(new AmbientSoundControl(AmbientSoundControl.Mode.NOISE_CANCELLING, false, 0), "68:02:11:02:02:01:00:00");
+            put(new AmbientSoundControl(AmbientSoundControl.Mode.NOISE_CANCELLING, true, 15), "68:02:11:02:02:01:01:00");
+            put(new AmbientSoundControl(AmbientSoundControl.Mode.NOISE_CANCELLING, false, 15), "68:02:11:02:02:01:00:00");
+            put(new AmbientSoundControl(AmbientSoundControl.Mode.WIND_NOISE_REDUCTION, false, 5), "68:02:11:02:01:01:00:00");
+            put(new AmbientSoundControl(AmbientSoundControl.Mode.WIND_NOISE_REDUCTION, false, 15), "68:02:11:02:01:01:00:00");
+            put(new AmbientSoundControl(AmbientSoundControl.Mode.WIND_NOISE_REDUCTION, true, 15), "68:02:11:02:01:01:01:00");
+        }};
+
+        for (Map.Entry<AmbientSoundControl, String> entry : commandsWindNoiseReduction.entrySet()) {
+            final Request request = protocol.setAmbientSoundControl(entry.getKey());
+            assertRequest(request, 0x0c, entry.getValue());
+        }
+    }
+
+    @Test
+    public void setSpeakToChatEnabled() {
+        assertRequests(protocol::setSpeakToChatEnabled, new LinkedHashMap<SpeakToChatEnabled, String>() {{
+            put(new SpeakToChatEnabled(false), "f8:05:01:00");
+            put(new SpeakToChatEnabled(true), "f8:05:01:01");
+        }});
+    }
+
+    @Test
+    public void getSpeakToChatEnabled() {
+        final Request request = protocol.getSpeakToChatEnabled();
+        assertRequest(request, 0x0c, "f6:05");
+    }
+
+    @Test
+    public void setSpeakToChatConfig() {
+        assertRequests(protocol::setSpeakToChatConfig, new LinkedHashMap<SpeakToChatConfig, String>() {{
+            put(new SpeakToChatConfig(false, SpeakToChatConfig.Sensitivity.AUTO, SpeakToChatConfig.Timeout.SHORT), "fc:05:00:00:00:00");
+            put(new SpeakToChatConfig(false, SpeakToChatConfig.Sensitivity.AUTO, SpeakToChatConfig.Timeout.STANDARD), "fc:05:00:00:00:01");
+            put(new SpeakToChatConfig(false, SpeakToChatConfig.Sensitivity.AUTO, SpeakToChatConfig.Timeout.LONG), "fc:05:00:00:00:02");
+            put(new SpeakToChatConfig(false, SpeakToChatConfig.Sensitivity.AUTO, SpeakToChatConfig.Timeout.OFF), "fc:05:00:00:00:03");
+
+            put(new SpeakToChatConfig(false, SpeakToChatConfig.Sensitivity.HIGH, SpeakToChatConfig.Timeout.SHORT), "fc:05:00:01:00:00");
+            put(new SpeakToChatConfig(false, SpeakToChatConfig.Sensitivity.HIGH, SpeakToChatConfig.Timeout.STANDARD), "fc:05:00:01:00:01");
+            put(new SpeakToChatConfig(false, SpeakToChatConfig.Sensitivity.HIGH, SpeakToChatConfig.Timeout.LONG), "fc:05:00:01:00:02");
+            put(new SpeakToChatConfig(false, SpeakToChatConfig.Sensitivity.HIGH, SpeakToChatConfig.Timeout.OFF), "fc:05:00:01:00:03");
+
+            put(new SpeakToChatConfig(false, SpeakToChatConfig.Sensitivity.LOW, SpeakToChatConfig.Timeout.SHORT), "fc:05:00:02:00:00");
+            put(new SpeakToChatConfig(false, SpeakToChatConfig.Sensitivity.LOW, SpeakToChatConfig.Timeout.STANDARD), "fc:05:00:02:00:01");
+            put(new SpeakToChatConfig(false, SpeakToChatConfig.Sensitivity.LOW, SpeakToChatConfig.Timeout.LONG), "fc:05:00:02:00:02");
+            put(new SpeakToChatConfig(false, SpeakToChatConfig.Sensitivity.LOW, SpeakToChatConfig.Timeout.OFF), "fc:05:00:02:00:03");
+
+            put(new SpeakToChatConfig(true, SpeakToChatConfig.Sensitivity.AUTO, SpeakToChatConfig.Timeout.SHORT), "fc:05:00:00:01:00");
+            put(new SpeakToChatConfig(true, SpeakToChatConfig.Sensitivity.AUTO, SpeakToChatConfig.Timeout.STANDARD), "fc:05:00:00:01:01");
+            put(new SpeakToChatConfig(true, SpeakToChatConfig.Sensitivity.AUTO, SpeakToChatConfig.Timeout.LONG), "fc:05:00:00:01:02");
+            put(new SpeakToChatConfig(true, SpeakToChatConfig.Sensitivity.AUTO, SpeakToChatConfig.Timeout.OFF), "fc:05:00:00:01:03");
+
+            put(new SpeakToChatConfig(true, SpeakToChatConfig.Sensitivity.HIGH, SpeakToChatConfig.Timeout.SHORT), "fc:05:00:01:01:00");
+            put(new SpeakToChatConfig(true, SpeakToChatConfig.Sensitivity.HIGH, SpeakToChatConfig.Timeout.STANDARD), "fc:05:00:01:01:01");
+            put(new SpeakToChatConfig(true, SpeakToChatConfig.Sensitivity.HIGH, SpeakToChatConfig.Timeout.LONG), "fc:05:00:01:01:02");
+            put(new SpeakToChatConfig(true, SpeakToChatConfig.Sensitivity.HIGH, SpeakToChatConfig.Timeout.OFF), "fc:05:00:01:01:03");
+
+            put(new SpeakToChatConfig(true, SpeakToChatConfig.Sensitivity.LOW, SpeakToChatConfig.Timeout.SHORT), "fc:05:00:02:01:00");
+            put(new SpeakToChatConfig(true, SpeakToChatConfig.Sensitivity.LOW, SpeakToChatConfig.Timeout.STANDARD), "fc:05:00:02:01:01");
+            put(new SpeakToChatConfig(true, SpeakToChatConfig.Sensitivity.LOW, SpeakToChatConfig.Timeout.LONG), "fc:05:00:02:01:02");
+            put(new SpeakToChatConfig(true, SpeakToChatConfig.Sensitivity.LOW, SpeakToChatConfig.Timeout.OFF), "fc:05:00:02:01:03");
+        }});
+    }
+
+    @Test
+    public void getSpeakToChatConfig() {
+        final Request request = protocol.getSpeakToChatConfig();
+        assertRequest(request, 0x0c, "fa:05");
+    }
+
+    @Test
+    public void getNoiseCancellingOptimizerState() {
+        final Request request = protocol.getNoiseCancellingOptimizerState();
+        assertRequest(request, 0x0c, "86:01");
+    }
+
+    @Test
+    public void getAudioCodec() {
+        final Request request = protocol.getAudioCodec();
+        assertRequest(request, 0x0c, "18:00");
+    }
+
+    @Test
+    public void getBattery() {
+        final Map<BatteryType, String> commands = new LinkedHashMap<BatteryType, String>() {{
+            put(BatteryType.SINGLE, "10:00");
+            put(BatteryType.DUAL, "10:01");
+            put(BatteryType.CASE, "10:02");
+        }};
+
+        for (Map.Entry<BatteryType, String> entry : commands.entrySet()) {
+            final Request request = protocol.getBattery(entry.getKey());
+            assertRequest(request, 0x0c, entry.getValue());
+        }
+    }
+
+    @Test
+    public void getFirmwareVersion() {
+        final Request request = protocol.getFirmwareVersion();
+        assertRequest(request, 0x0c, "04:02");
+    }
+
+    @Test
+    public void getAudioUpsampling() {
+        final Request request = protocol.getAudioUpsampling();
+        assertRequest(request, 0x0c, "e6:02");
+    }
+
+    @Test
+    public void setAudioUpsampling() {
+        assertRequests(protocol::setAudioUpsampling, new LinkedHashMap<AudioUpsampling, String>() {{
+            put(new AudioUpsampling(false), "e8:02:00:00");
+            put(new AudioUpsampling(true), "e8:02:00:01");
+        }});
+    }
+
+    @Test
+    public void getAutomaticPowerOff() {
+        final Request request = protocol.getAutomaticPowerOff();
+        assertRequest(request, 0x0c, "f6:04");
+    }
+
+    @Test
+    public void setAutomaticPowerOff() {
+        assertRequests(protocol::setAutomaticPowerOff, new LinkedHashMap<AutomaticPowerOff, String>() {{
+            put(AutomaticPowerOff.OFF, "f8:04:01:11:00");
+            put(AutomaticPowerOff.AFTER_5_MIN, "f8:04:01:00:00");
+            put(AutomaticPowerOff.AFTER_30_MIN, "f8:04:01:01:01");
+            put(AutomaticPowerOff.AFTER_1_HOUR, "f8:04:01:02:02");
+            put(AutomaticPowerOff.AFTER_3_HOUR, "f8:04:01:03:03");
+            put(AutomaticPowerOff.WHEN_TAKEN_OFF, "f8:04:01:10:00");
+        }});
+    }
+
+    @Test
+    public void getButtonModes() {
+        final Request request = protocol.getButtonModes();
+        assertRequest(request, 0x0c, "f6:06");
+    }
+
+    @Test
+    public void setButtonModes() {
+        assertRequests(protocol::setButtonModes, new LinkedHashMap<ButtonModes, String>() {{
+            put(new ButtonModes(ButtonModes.Mode.OFF, ButtonModes.Mode.OFF), "f8:06:02:ff:ff");
+            put(new ButtonModes(ButtonModes.Mode.OFF, ButtonModes.Mode.AMBIENT_SOUND_CONTROL), "f8:06:02:ff:00");
+            put(new ButtonModes(ButtonModes.Mode.OFF, ButtonModes.Mode.PLAYBACK_CONTROL), "f8:06:02:ff:20");
+            put(new ButtonModes(ButtonModes.Mode.OFF, ButtonModes.Mode.VOLUME_CONTROL), "f8:06:02:ff:10");
+            put(new ButtonModes(ButtonModes.Mode.OFF, ButtonModes.Mode.OFF), "f8:06:02:ff:ff");
+            put(new ButtonModes(ButtonModes.Mode.AMBIENT_SOUND_CONTROL, ButtonModes.Mode.OFF), "f8:06:02:00:ff");
+            put(new ButtonModes(ButtonModes.Mode.PLAYBACK_CONTROL, ButtonModes.Mode.OFF), "f8:06:02:20:ff");
+            put(new ButtonModes(ButtonModes.Mode.VOLUME_CONTROL, ButtonModes.Mode.OFF), "f8:06:02:10:ff");
+        }});
+    }
+
+    @Test
+    public void getQuickAccess() {
+        final Request request = protocol.getQuickAccess();
+        assertNull(request);
+    }
+
+    @Test
+    public void setQuickAccess() {
+        assertRequests(protocol::setQuickAccess, new LinkedHashMap<QuickAccess, String>() {{
+            put(new QuickAccess(QuickAccess.Mode.OFF, QuickAccess.Mode.OFF), null);
+            put(new QuickAccess(QuickAccess.Mode.OFF, QuickAccess.Mode.SPOTIFY), null);
+            put(new QuickAccess(QuickAccess.Mode.SPOTIFY, QuickAccess.Mode.OFF), null);
+            put(new QuickAccess(QuickAccess.Mode.SPOTIFY, QuickAccess.Mode.SPOTIFY), null);
+        }});
+    }
+
+    @Test
+    public void getAmbientSoundControlButtonMode() {
+        final Request request = protocol.getAmbientSoundControlButtonMode();
+        assertNull(request);
+    }
+
+    @Test
+    public void setAmbientSoundControlButtonMode() {
+        assertRequests(protocol::setAmbientSoundControlButtonMode, new LinkedHashMap<AmbientSoundControlButtonMode, String>() {{
+            put(AmbientSoundControlButtonMode.NC_AS_OFF, null);
+            put(AmbientSoundControlButtonMode.NC_AS, null);
+            put(AmbientSoundControlButtonMode.NC_OFF, null);
+            put(AmbientSoundControlButtonMode.AS_OFF, null);
+        }});
+    }
+
+    @Test
+    public void getPauseWhenTakenOff() {
+        final Request request = protocol.getPauseWhenTakenOff();
+        assertRequest(request, 0x0c, "f6:03");
+    }
+
+    @Test
+    public void setPauseWhenTakenOff() {
+        assertRequests(protocol::setPauseWhenTakenOff, new LinkedHashMap<PauseWhenTakenOff, String>() {{
+            put(new PauseWhenTakenOff(false), "f8:03:00:00");
+            put(new PauseWhenTakenOff(true), "f8:03:00:01");
+        }});
+    }
+
+    @Test
+    public void getEqualizer() {
+        final Request request = protocol.getEqualizer();
+        assertRequest(request, 0x0c, "56:01");
+    }
+
+    @Test
+    public void setEqualizerPreset() {
+        assertRequests(protocol::setEqualizerPreset, new LinkedHashMap<EqualizerPreset, String>() {{
+            put(EqualizerPreset.OFF, "58:01:00:00");
+            put(EqualizerPreset.BRIGHT, "58:01:10:00");
+            put(EqualizerPreset.EXCITED, "58:01:11:00");
+            put(EqualizerPreset.MELLOW, "58:01:12:00");
+            put(EqualizerPreset.RELAXED, "58:01:13:00");
+            put(EqualizerPreset.VOCAL, "58:01:14:00");
+            put(EqualizerPreset.TREBLE_BOOST, "58:01:15:00");
+            put(EqualizerPreset.BASS_BOOST, "58:01:16:00");
+            put(EqualizerPreset.SPEECH, "58:01:17:00");
+            put(EqualizerPreset.MANUAL, "58:01:a0:00");
+            put(EqualizerPreset.CUSTOM_1, "58:01:a1:00");
+        }});
+    }
+
+    @Test
+    public void setEqualizerCustomBands() {
+        assertRequests(protocol::setEqualizerCustomBands, new LinkedHashMap<EqualizerCustomBands, String>() {{
+            put(new EqualizerCustomBands(Arrays.asList(0, 0, 0, 0, 0), 0), "58:01:ff:06:0a:0a:0a:0a:0a:0a");
+            put(new EqualizerCustomBands(Arrays.asList(5, 1, 0, 4, 0), 0), "58:01:ff:06:0a:0f:0b:0a:0e:0a");
+            put(new EqualizerCustomBands(Arrays.asList(10, 0, 3, 0, 5), -2), "58:01:ff:06:08:14:0a:0d:0a:0f");
+            put(new EqualizerCustomBands(Arrays.asList(-3, -7, 0, 2, 9), 0), "58:01:ff:06:0a:07:03:0a:0c:13");
+            put(new EqualizerCustomBands(Arrays.asList(-3, -7, 0, 2, 9), 7), "58:01:ff:06:11:07:03:0a:0c:13");
+        }});
+    }
+
+    @Test
+    public void getSoundPosition() {
+        final Request request = protocol.getSoundPosition();
+        assertRequest(request, 0x0c, "46:02");
+    }
+
+    @Test
+    public void setSoundPosition() {
+        assertRequests(protocol::setSoundPosition, new LinkedHashMap<SoundPosition, String>() {{
+            put(SoundPosition.OFF, "48:02:00");
+            put(SoundPosition.FRONT, "48:02:03");
+            put(SoundPosition.FRONT_LEFT, "48:02:01");
+            put(SoundPosition.FRONT_RIGHT, "48:02:02");
+            put(SoundPosition.REAR_LEFT, "48:02:11");
+            put(SoundPosition.REAR_RIGHT, "48:02:12");
+        }});
+    }
+
+    @Test
+    public void getSurroundMode() {
+        final Request request = protocol.getSurroundMode();
+        assertRequest(request, 0x0c, "46:01");
+    }
+
+    @Test
+    public void setSurroundMode() {
+        assertRequests(protocol::setSurroundMode, new LinkedHashMap<SurroundMode, String>() {{
+            put(SurroundMode.OFF, "48:01:00");
+            put(SurroundMode.ARENA, "48:01:02");
+            put(SurroundMode.CLUB, "48:01:04");
+            put(SurroundMode.OUTDOOR_STAGE, "48:01:01");
+            put(SurroundMode.CONCERT_HALL, "48:01:03");
+        }});
+    }
+
+    @Test
+    public void getTouchSensor() {
+        final Request request = protocol.getTouchSensor();
+        assertRequest(request, 0x0c, "d6:d2");
+    }
+
+    @Test
+    public void setTouchSensor() {
+        assertRequests(protocol::setTouchSensor, new LinkedHashMap<TouchSensor, String>() {{
+            put(new TouchSensor(false), "d8:d2:01:00");
+            put(new TouchSensor(true), "d8:d2:01:01");
+        }});
+    }
+
+    @Test
+    public void getVoiceNotifications() {
+        final Request request = protocol.getVoiceNotifications();
+        assertRequest(request, 0x0e, "46:01:01");
+    }
+
+    @Test
+    public void setVoiceNotifications() {
+        assertRequests(protocol::setVoiceNotifications, 0x0e, new LinkedHashMap<VoiceNotifications, String>() {{
+            put(new VoiceNotifications(false), "48:01:01:00");
+            put(new VoiceNotifications(true), "48:01:01:01");
+        }});
+    }
+
+    @Test
+    public void startNoiseCancellingOptimizer() {
+        assertRequests(protocol::startNoiseCancellingOptimizer, new LinkedHashMap<Boolean, String>() {{
+            put(Boolean.TRUE, "84:01:00:01");
+            put(Boolean.FALSE, "84:01:00:00");
+        }});
+    }
+
+    @Test
+    public void powerOff() {
+        final Request request = protocol.powerOff();
+        assertRequest(request, 0x0c, "22:00:01");
+    }
+
+    @Test
+    public void handlePayload() {
+        // TODO
+    }
+
+    @Test
+    public void validInitPayload() {
+        // TODO
+    }
+
+    @Test
+    public void handleInitResponse() {
+        // TODO
+    }
+
+    @Test
+    public void handleAmbientSoundControl() {
+        // TODO
+    }
+
+    @Test
+    public void handleNoiseCancellingOptimizerStatus() {
+        // TODO
+    }
+
+    @Test
+    public void handleNoiseCancellingOptimizerState() {
+        // TODO
+    }
+
+    @Test
+    public void handleAudioUpsampling() {
+        // TODO
+    }
+
+    @Test
+    public void handleAutomaticPowerOff() {
+        // TODO
+    }
+
+    @Test
+    public void handleSpeakToChatEnabled() {
+        // TODO
+    }
+
+    @Test
+    public void handleButtonModes() {
+        // TODO
+    }
+
+    @Test
+    public void handlePauseWhenTakenOff() {
+        // TODO
+    }
+
+    @Test
+    public void handleBattery() {
+        // TODO
+    }
+
+    @Test
+    public void handleAudioCodec() {
+        final List<? extends GBDeviceEvent> event = protocol.handlePayload(
+                MessageType.fromCode((byte) 0x0c),
+                GB.hexStringToByteArray("1b:00:01".replace(":", ""))
+        );
+
+        assertEquals("Expect 2 events", 2, event.size());
+    }
+
+    @Test
+    public void handleEqualizer() {
+        // TODO
+    }
+
+    @Test
+    public void handleFirmwareVersion() {
+        // TODO
+    }
+
+    @Test
+    public void handleJson() {
+        // TODO
+    }
+
+    @Test
+    public void handleAutomaticPowerOffButtonMode() {
+        // TODO
+    }
+
+    @Test
+    public void handleVirtualSound() {
+        // TODO
+    }
+
+    @Test
+    public void handleSoundPosition() {
+        // TODO
+    }
+
+    @Test
+    public void handleSurroundMode() {
+        // TODO
+    }
+
+    @Test
+    public void handleTouchSensor() {
+        // TODO
+    }
+
+    @Test
+    public void handleSpeakToChatConfig() {
+        // TODO
+    }
+
+    @Test
+    public void handleVoiceNotifications() {
+        // TODO
+    }
+}
diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/headphones/protocol/impl/v2/SonyProtocolImplV2Test.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/headphones/protocol/impl/v2/SonyProtocolImplV2Test.java
new file mode 100644
index 0000000..45ebab8
--- /dev/null
+++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/headphones/protocol/impl/v2/SonyProtocolImplV2Test.java
@@ -0,0 +1,525 @@
+/*  Copyright (C) 2022 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.impl.v2;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import static nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.impl.SonyTestUtils.assertPrefs;
+import static nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.impl.SonyTestUtils.assertRequest;
+import static nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.impl.SonyTestUtils.assertRequests;
+import static nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.impl.SonyTestUtils.handleMessage;
+
+import org.junit.Ignore;
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdatePreferences;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.SonyHeadphonesCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.coordinators.SonyWF1000XM4Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.AmbientSoundControlButtonMode;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.AudioUpsampling;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.AutomaticPowerOff;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.EqualizerCustomBands;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.EqualizerPreset;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.PauseWhenTakenOff;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.QuickAccess;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.SpeakToChatConfig;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.SpeakToChatEnabled;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.VoiceNotifications;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.Request;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.impl.v1.params.BatteryType;
+
+public class SonyProtocolImplV2Test {
+    private final SonyProtocolImplV2 protocol = new SonyProtocolImplV2(null) {
+        @Override
+        protected SonyHeadphonesCoordinator getCoordinator() {
+            return new SonyWF1000XM4Coordinator();
+        }
+    };
+
+    @Test
+    public void getAmbientSoundControl() {
+        // TODO
+    }
+
+    @Test
+    public void setAmbientSoundControl() {
+        // TODO
+        //final Request request = protocol.setAmbientSoundControl(new AmbientSoundControl(
+        //        AmbientSoundControl.Mode.WIND_NOISE_REDUCTION,
+        //        true,
+        //        15
+        //));
+        //assertRequest(request, "3e:0c:00:00:00:00:08:68:15:01:01:00:03:01:0f:a6:3c");
+
+        //final Map<AmbientSoundControl, String> commands = new LinkedHashMap<AmbientSoundControl, String>() {{
+        //    put(new AmbientSoundControl(AmbientSoundControl.Mode.AMBIENT_SOUND, false, 20), "68:17:01:01:01:00:14");
+        //    put(new AmbientSoundControl(AmbientSoundControl.Mode.OFF, false, 20), "68:17:01:00:00:00:14");
+        //    put(new AmbientSoundControl(AmbientSoundControl.Mode.AMBIENT_SOUND, false, 10), "68:17:01:01:01:00:0a");
+        //    put(new AmbientSoundControl(AmbientSoundControl.Mode.AMBIENT_SOUND, true, 20), "68:17:01:01:01:01:14");
+        //    put(new AmbientSoundControl(AmbientSoundControl.Mode.NOISE_CANCELLING, false, 20), "68:17:01:01:00:00:14");
+        //}};
+//
+        //for (Map.Entry<AmbientSoundControl, String> entry : commands.entrySet()) {
+        //    final Request request2 = protocol.setAmbientSoundControl(entry.getKey());
+        //    assertRequest(request2, 0x0c, entry.getValue());
+        //}
+    }
+
+    @Test
+    public void getNoiseCancellingOptimizerState() {
+        // TODO
+    }
+
+    @Test
+    public void getAudioCodec() {
+        final Request requestEnabled = protocol.getAudioCodec();
+        assertRequest(requestEnabled, "3e:0c:01:00:00:00:02:12:02:23:3c");
+    }
+
+    @Test
+    public void getBattery() {
+        final Map<BatteryType, String> commands = new LinkedHashMap<BatteryType, String>() {{
+            put(BatteryType.SINGLE, "22:00");
+            put(BatteryType.DUAL, "22:09");
+            put(BatteryType.DUAL2, "22:01");
+            put(BatteryType.CASE, "22:0a");
+        }};
+
+        for (Map.Entry<BatteryType, String> entry : commands.entrySet()) {
+            final Request request = protocol.getBattery(entry.getKey());
+            assertRequest(request, 0x0c, entry.getValue());
+        }
+    }
+
+    @Test
+    public void getFirmwareVersion() {
+        // TODO
+    }
+
+    @Test
+    public void setSpeakToChatEnabled() {
+        assertRequests(protocol::setSpeakToChatEnabled, new LinkedHashMap<SpeakToChatEnabled, String>() {{
+            put(new SpeakToChatEnabled(false), "f8:0c:01:01");
+            put(new SpeakToChatEnabled(true), "f8:0c:00:01");
+        }});
+    }
+
+    @Test
+    public void setSpeakToChatConfig() {
+        assertRequests(protocol::setSpeakToChatConfig, new LinkedHashMap<SpeakToChatConfig, String>() {{
+            put(new SpeakToChatConfig(false, SpeakToChatConfig.Sensitivity.HIGH, SpeakToChatConfig.Timeout.STANDARD), "fc:0c:01:01");
+            put(new SpeakToChatConfig(false, SpeakToChatConfig.Sensitivity.LOW, SpeakToChatConfig.Timeout.STANDARD), "fc:0c:02:01");
+            put(new SpeakToChatConfig(false, SpeakToChatConfig.Sensitivity.AUTO, SpeakToChatConfig.Timeout.STANDARD), "fc:0c:00:01");
+            put(new SpeakToChatConfig(false, SpeakToChatConfig.Sensitivity.AUTO, SpeakToChatConfig.Timeout.SHORT), "fc:0c:00:00");
+            put(new SpeakToChatConfig(false, SpeakToChatConfig.Sensitivity.AUTO, SpeakToChatConfig.Timeout.LONG), "fc:0c:00:02");
+            put(new SpeakToChatConfig(false, SpeakToChatConfig.Sensitivity.AUTO, SpeakToChatConfig.Timeout.OFF), "fc:0c:00:03");
+            put(new SpeakToChatConfig(false, SpeakToChatConfig.Sensitivity.AUTO, SpeakToChatConfig.Timeout.STANDARD), "fc:0c:00:01");
+        }});
+    }
+
+    @Test
+    public void getAudioUpsampling() {
+        // TODO
+    }
+
+    @Test
+    public void setAudioUpsampling() {
+        final Request requestEnabled = protocol.setAudioUpsampling(new AudioUpsampling(true));
+        assertRequest(requestEnabled, "3e:0c:00:00:00:00:03:e8:01:01:f9:3c");
+
+        final Request requestDisabled = protocol.setAudioUpsampling(new AudioUpsampling(false));
+        assertRequest(requestDisabled, "3e:0c:01:00:00:00:03:e8:01:00:f9:3c");
+    }
+
+    @Test
+    public void getAutomaticPowerOff() {
+        // TODO
+    }
+
+    @Test
+    public void setAutomaticPowerOff() {
+        assertRequests(protocol::setAutomaticPowerOff, new LinkedHashMap<AutomaticPowerOff, String>() {{
+            put(AutomaticPowerOff.OFF, "28:05:11:00");
+            put(AutomaticPowerOff.WHEN_TAKEN_OFF, "28:05:10:00");
+        }});
+    }
+
+    @Test
+    public void getButtonModes() {
+        // TODO
+    }
+
+    @Test
+    public void setButtonModes() {
+        // TODO
+    }
+
+    @Test
+    public void getQuickAccess() {
+        final Request request = protocol.getQuickAccess();
+        assertRequest(request, "3e:0c:00:00:00:00:02:f6:0d:11:3c");
+    }
+
+    @Test
+    public void setQuickAccess() {
+        final Map<QuickAccess, String> commands = new LinkedHashMap<QuickAccess, String>() {{
+            put(new QuickAccess(QuickAccess.Mode.OFF, QuickAccess.Mode.OFF), "3e:0c:01:00:00:00:05:f8:0d:02:00:00:19:3c");
+            put(new QuickAccess(QuickAccess.Mode.OFF, QuickAccess.Mode.SPOTIFY), "3e:0c:00:00:00:00:05:f8:0d:02:00:01:19:3c");
+            put(new QuickAccess(QuickAccess.Mode.SPOTIFY, QuickAccess.Mode.OFF), "3e:0c:00:00:00:00:05:f8:0d:02:01:00:19:3c");
+        }};
+
+        for (Map.Entry<QuickAccess, String> entry : commands.entrySet()) {
+            final Request request = protocol.setQuickAccess(entry.getKey());
+            assertRequest(request, entry.getValue());
+        }
+    }
+
+    @Test
+    public void getAmbientSoundControlButtonMode() {
+        final Request request = protocol.getAmbientSoundControlButtonMode();
+        assertRequest(request, "3e:0c:00:00:00:00:02:fa:03:0b:3c");
+    }
+
+    @Test
+    public void setAmbientSoundControlButtonMode() {
+        final Map<AmbientSoundControlButtonMode, String> commands = new LinkedHashMap<AmbientSoundControlButtonMode, String>() {{
+            put(AmbientSoundControlButtonMode.NC_AS_OFF, "3e:0c:00:00:00:00:07:fc:03:01:35:01:00:01:4a:3c");
+            put(AmbientSoundControlButtonMode.NC_AS, "3e:0c:01:00:00:00:07:fc:03:01:35:01:00:02:4c:3c");
+            put(AmbientSoundControlButtonMode.NC_OFF, "3e:0c:01:00:00:00:07:fc:03:01:35:01:00:03:4d:3c");
+            put(AmbientSoundControlButtonMode.AS_OFF, "3e:0c:01:00:00:00:07:fc:03:01:35:01:00:04:4e:3c");
+        }};
+
+        for (Map.Entry<AmbientSoundControlButtonMode, String> entry : commands.entrySet()) {
+            final Request request = protocol.setAmbientSoundControlButtonMode(entry.getKey());
+            assertRequest(request, entry.getValue());
+        }
+    }
+
+    @Test
+    public void getPauseWhenTakenOff() {
+        // TODO
+    }
+
+    @Test
+    public void setPauseWhenTakenOff() {
+        assertRequests(protocol::setPauseWhenTakenOff, new LinkedHashMap<PauseWhenTakenOff, String>() {{
+            put(new PauseWhenTakenOff(false), "f8:01:01");
+            put(new PauseWhenTakenOff(true), "f8:01:00");
+        }});
+    }
+
+    @Test
+    public void getEqualizer() {
+        final Request requestDisabled = protocol.getEqualizer();
+        assertRequest(requestDisabled, "3e:0c:00:00:00:00:02:56:00:64:3c");
+    }
+
+    @Test
+    public void setEqualizerPreset() {
+        final Map<EqualizerPreset, String> commands = new LinkedHashMap<EqualizerPreset, String>() {{
+            put(EqualizerPreset.OFF, "3e:0c:01:00:00:00:04:58:00:00:00:69:3c");
+            put(EqualizerPreset.BRIGHT, "3e:0c:01:00:00:00:04:58:00:10:00:79:3c");
+            put(EqualizerPreset.EXCITED, "3e:0c:01:00:00:00:04:58:00:11:00:7a:3c");
+            put(EqualizerPreset.MELLOW, "3e:0c:01:00:00:00:04:58:00:12:00:7b:3c");
+            put(EqualizerPreset.RELAXED, "3e:0c:01:00:00:00:04:58:00:13:00:7c:3c");
+            put(EqualizerPreset.VOCAL, "3e:0c:01:00:00:00:04:58:00:14:00:7d:3c");
+            put(EqualizerPreset.TREBLE_BOOST, "3e:0c:01:00:00:00:04:58:00:15:00:7e:3c");
+            put(EqualizerPreset.BASS_BOOST, "3e:0c:01:00:00:00:04:58:00:16:00:7f:3c");
+            put(EqualizerPreset.SPEECH, "3e:0c:01:00:00:00:04:58:00:17:00:80:3c");
+            put(EqualizerPreset.MANUAL, "3e:0c:01:00:00:00:04:58:00:a0:00:09:3c");
+            put(EqualizerPreset.CUSTOM_1, "3e:0c:01:00:00:00:04:58:00:a1:00:0a:3c");
+            put(EqualizerPreset.CUSTOM_2, "3e:0c:01:00:00:00:04:58:00:a2:00:0b:3c");
+        }};
+
+        for (Map.Entry<EqualizerPreset, String> entry : commands.entrySet()) {
+            final Request request = protocol.setEqualizerPreset(entry.getKey());
+            assertRequest(request, entry.getValue());
+        }
+    }
+
+    @Test
+    public void setEqualizerCustomBands() {
+        assertRequests(protocol::setEqualizerCustomBands, new LinkedHashMap<EqualizerCustomBands, String>() {{
+            put(new EqualizerCustomBands(Arrays.asList(0, 1, 2, 3, 1), 0), "58:00:a0:06:0a:0a:0b:0c:0d:0b");
+            put(new EqualizerCustomBands(Arrays.asList(0, 1, 2, 3, 5), 0), "58:00:a0:06:0a:0a:0b:0c:0d:0f");
+            put(new EqualizerCustomBands(Arrays.asList(0, 1, 2, 4, 5), 0), "58:00:a0:06:0a:0a:0b:0c:0e:0f");
+            put(new EqualizerCustomBands(Arrays.asList(5, 1, 2, 3, 5), 0), "58:00:a0:06:0a:0f:0b:0c:0d:0f");
+            put(new EqualizerCustomBands(Arrays.asList(0, 1, 2, 3, 5), -6), "58:00:a0:06:04:0a:0b:0c:0d:0f");
+            put(new EqualizerCustomBands(Arrays.asList(0, 1, 2, 3, 5), 10), "58:00:a0:06:14:0a:0b:0c:0d:0f");
+        }});
+    }
+
+    @Test
+    @Ignore("Not implemented on V2")
+    public void getSoundPosition() {
+    }
+
+    @Test
+    @Ignore("Not implemented on V2")
+    public void setSoundPosition() {
+    }
+
+    @Test
+    @Ignore("Not implemented on V2")
+    public void getSurroundMode() {
+    }
+
+    @Test
+    @Ignore("Not implemented on V2")
+    public void setSurroundMode() {
+    }
+
+    @Test
+    @Ignore("Not implemented on V2")
+    public void getTouchSensor() {
+    }
+
+    @Test
+    @Ignore("Not implemented on V2")
+    public void setTouchSensor() {
+    }
+
+    @Test
+    @Ignore("Not implemented on V2")
+    public void getVoiceNotifications() {
+    }
+
+    @Test
+    public void setVoiceNotifications() {
+        assertRequests(protocol::setVoiceNotifications, 0x0e, new LinkedHashMap<VoiceNotifications, String>() {{
+            put(new VoiceNotifications(false), "48:01:01");
+            put(new VoiceNotifications(true), "48:01:00");
+        }});
+    }
+
+    @Test
+    @Ignore("Not implemented on V2")
+    public void startNoiseCancellingOptimizer() {
+    }
+
+    @Test
+    public void powerOff() {
+        final Request request = protocol.powerOff();
+        assertRequest(request, 0x0c, "24:03:01");
+    }
+
+    @Test
+    public void handlePayload() {
+        // TODO
+    }
+
+    @Test
+    public void validInitPayload() {
+        // TODO
+    }
+
+    @Test
+    public void handleInitResponse() {
+        // TODO
+    }
+
+    @Test
+    public void handleAmbientSoundControl() {
+        // TODO
+    }
+
+    @Test
+    @Ignore("Not implemented on V2")
+    public void handleNoiseCancellingOptimizerStatus() {
+    }
+
+    @Test
+    @Ignore("Not implemented on V2")
+    public void handleNoiseCancellingOptimizerState() {
+    }
+
+    @Test
+    public void handleAudioUpsampling() {
+        final Map<AudioUpsampling, String> commands = new LinkedHashMap<AudioUpsampling, String>() {{
+            put(new AudioUpsampling(false), "3e:0c:00:00:00:00:03:e9:01:00:f9:3c");
+            put(new AudioUpsampling(true), "3e:0c:01:00:00:00:03:e9:01:01:fb:3c");
+        }};
+
+        for (Map.Entry<AudioUpsampling, String> entry : commands.entrySet()) {
+            final List<? extends GBDeviceEvent> events = handleMessage(protocol, entry.getValue());
+            assertEquals("Expect 1 events", 1, events.size());
+            final GBDeviceEventUpdatePreferences event = (GBDeviceEventUpdatePreferences) events.get(0);
+            final Object modePrefValue = entry.getKey()
+                    .toPreferences()
+                    .get(DeviceSettingsPreferenceConst.PREF_SONY_AUDIO_UPSAMPLING);
+            assertEquals(modePrefValue, event.preferences.get(DeviceSettingsPreferenceConst.PREF_SONY_AUDIO_UPSAMPLING));
+        }
+    }
+
+    @Test
+    public void handleAutomaticPowerOff() {
+        // TODO
+    }
+
+    @Test
+    public void handleButtonModes() {
+        // TODO
+    }
+
+    @Test
+    public void handlePauseWhenTakenOff() {
+        final Map<PauseWhenTakenOff, String> commands = new LinkedHashMap<PauseWhenTakenOff, String>() {{
+            put(new PauseWhenTakenOff(false), "3e:0c:00:00:00:00:03:f9:01:01:0a:3c");
+            put(new PauseWhenTakenOff(true), "3e:0c:01:00:00:00:03:f9:01:00:0a:3c");
+        }};
+
+        for (Map.Entry<PauseWhenTakenOff, String> entry : commands.entrySet()) {
+            final List<? extends GBDeviceEvent> events = handleMessage(protocol, entry.getValue());
+            assertEquals("Expect 1 events", 1, events.size());
+            final GBDeviceEventUpdatePreferences event = (GBDeviceEventUpdatePreferences) events.get(0);
+            final Object modePrefValue = entry.getKey()
+                    .toPreferences()
+                    .get(DeviceSettingsPreferenceConst.PREF_SONY_PAUSE_WHEN_TAKEN_OFF);
+            assertEquals(modePrefValue, event.preferences.get(DeviceSettingsPreferenceConst.PREF_SONY_PAUSE_WHEN_TAKEN_OFF));
+        }
+    }
+
+    @Test
+    public void handleBattery() {
+        // TODO
+    }
+
+    @Test
+    public void handleAudioCodec() {
+        // TODO
+    }
+
+    @Test
+    public void handleEqualizer() {
+        final Map<EqualizerPreset, String> commands = new LinkedHashMap<EqualizerPreset, String>() {{
+            put(EqualizerPreset.OFF, "3e:0c:01:00:00:00:0a:59:00:00:06:0a:0a:0a:0a:0a:0a:b2:3c");
+            put(EqualizerPreset.BRIGHT, "3e:0c:01:00:00:00:0a:59:00:10:06:09:0a:0f:11:11:13:dd:3c");
+            put(EqualizerPreset.EXCITED, "3e:0c:01:00:00:00:0a:59:00:11:06:12:09:0b:0a:0d:0f:d3:3c");
+            put(EqualizerPreset.MELLOW, "3e:0c:01:00:00:00:0a:59:00:12:06:07:09:08:07:06:04:b1:3c");
+            put(EqualizerPreset.RELAXED, "3e:0c:01:00:00:00:0a:59:00:13:06:01:07:09:07:05:02:a8:3c");
+            put(EqualizerPreset.VOCAL, "3e:0c:01:00:00:00:0a:59:00:14:06:0a:10:0e:0c:0d:09:d4:3c");
+            put(EqualizerPreset.TREBLE_BOOST, "3e:0c:01:00:00:00:0a:59:00:15:06:0a:0a:0a:0c:10:14:d9:3c");
+            put(EqualizerPreset.BASS_BOOST, "3e:0c:01:00:00:00:0a:59:00:16:06:11:0a:0a:0a:0a:0a:cf:3c");
+            put(EqualizerPreset.SPEECH, "3e:0c:01:00:00:00:0a:59:00:17:06:00:0e:0d:0b:0c:00:bf:3c");
+            put(EqualizerPreset.MANUAL, "3e:0c:01:00:00:00:0a:59:00:a0:06:0a:0a:0a:0a:0a:0a:52:3c");
+            put(EqualizerPreset.CUSTOM_1, "3e:0c:01:00:00:00:0a:59:00:a1:06:0a:0a:0a:0a:0a:0a:53:3c");
+            put(EqualizerPreset.CUSTOM_2, "3e:0c:01:00:00:00:0a:59:00:a2:06:0a:0a:0a:0a:0a:0a:54:3c");
+        }};
+
+        for (Map.Entry<EqualizerPreset, String> entry : commands.entrySet()) {
+            final List<? extends GBDeviceEvent> events = handleMessage(protocol, entry.getValue());
+            assertEquals("Expect 1 events", 1, events.size());
+            final GBDeviceEventUpdatePreferences event = (GBDeviceEventUpdatePreferences) events.get(0);
+            final Object modePrefValue = entry.getKey()
+                    .toPreferences()
+                    .get(DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_MODE);
+            assertNotNull(modePrefValue);
+            assertEquals(modePrefValue, event.preferences.get(DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_MODE));
+        }
+    }
+
+    @Test
+    public void handleFirmwareVersion() {
+        // TODO
+    }
+
+    @Test
+    @Ignore("Not implemented on V2")
+    public void handleJson() {
+    }
+
+    @Test
+    public void handleAutomaticPowerOffButtonMode() {
+        // TODO
+    }
+
+    @Test
+    @Ignore("Not implemented on V2")
+    public void handleVirtualSound() {
+    }
+
+    @Test
+    @Ignore("Not implemented on V2")
+    public void handleSoundPosition() {
+    }
+
+    @Test
+    @Ignore("Not implemented on V2")
+    public void handleSurroundMode() {
+    }
+
+    @Test
+    @Ignore("Not implemented on V2")
+    public void handleTouchSensor() {
+    }
+
+    @Test
+    @Ignore("Not implemented on V2")
+    public void handleVoiceNotifications() {
+    }
+
+    @Test
+    public void handleQuickAccess() {
+        final Map<String, QuickAccess> commands = new LinkedHashMap<String, QuickAccess>() {{
+            // Ret
+            put("3e:0c:00:00:00:00:05:f7:0d:02:00:00:17:3c", new QuickAccess(QuickAccess.Mode.OFF, QuickAccess.Mode.OFF));
+            put("3e:0c:01:00:00:00:05:f7:0d:02:00:01:19:3c", new QuickAccess(QuickAccess.Mode.OFF, QuickAccess.Mode.SPOTIFY));
+            put("3e:0c:01:00:00:00:05:f7:0d:02:01:00:19:3c", new QuickAccess(QuickAccess.Mode.SPOTIFY, QuickAccess.Mode.OFF));
+
+            // Notify
+            put("3e:0c:00:00:00:00:05:f9:0d:02:00:00:19:3c", new QuickAccess(QuickAccess.Mode.OFF, QuickAccess.Mode.OFF));
+            put("3e:0c:01:00:00:00:05:f9:0d:02:00:01:1b:3c", new QuickAccess(QuickAccess.Mode.OFF, QuickAccess.Mode.SPOTIFY));
+            put("3e:0c:01:00:00:00:05:f9:0d:02:01:00:1b:3c", new QuickAccess(QuickAccess.Mode.SPOTIFY, QuickAccess.Mode.OFF));
+        }};
+
+        for (Map.Entry<String, QuickAccess> entry : commands.entrySet()) {
+            final List<? extends GBDeviceEvent> events = handleMessage(protocol, entry.getKey());
+            assertPrefs(events, entry.getValue().toPreferences());
+        }
+    }
+
+    @Test
+    public void handleAmbientSoundControlButtonMode() {
+        final Map<AmbientSoundControlButtonMode, String> commands = new LinkedHashMap<AmbientSoundControlButtonMode, String>() {{
+            // Notify
+            put(AmbientSoundControlButtonMode.NC_AS_OFF, "3e:0c:01:00:00:00:07:fd:03:01:35:01:00:01:4c:3c");
+            put(AmbientSoundControlButtonMode.NC_AS, "3e:0c:00:00:00:00:07:fd:03:01:35:01:00:02:4c:3c");
+            put(AmbientSoundControlButtonMode.NC_OFF, "3e:0c:00:00:00:00:07:fd:03:01:35:01:00:03:4d:3c");
+            put(AmbientSoundControlButtonMode.AS_OFF, "3e:0c:01:00:00:00:07:fd:03:01:35:01:00:04:4f:3c");
+        }};
+
+        for (Map.Entry<AmbientSoundControlButtonMode, String> entry : commands.entrySet()) {
+            final List<? extends GBDeviceEvent> events = handleMessage(protocol, entry.getValue());
+            assertEquals("Expect 1 events", 1, events.size());
+            final GBDeviceEventUpdatePreferences event = (GBDeviceEventUpdatePreferences) events.get(0);
+            final Map<String, Object> expectedPrefs = entry.getKey().toPreferences();
+            assertEquals("Expect 1 prefs", 1, expectedPrefs.size());
+            final Object modePrefValue = expectedPrefs
+                    .get(DeviceSettingsPreferenceConst.PREF_SONY_AMBIENT_SOUND_CONTROL_BUTTON_MODE);
+            assertNotNull(modePrefValue);
+            assertEquals(modePrefValue, event.preferences.get(DeviceSettingsPreferenceConst.PREF_SONY_AMBIENT_SOUND_CONTROL_BUTTON_MODE));
+        }
+    }
+}
diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/MessageBuilderTest.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/MessageBuilderTest.java
new file mode 100644
index 0000000..a3bf285
--- /dev/null
+++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/MessageBuilderTest.java
@@ -0,0 +1,151 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verifyNoInteractions;
+import static org.mockito.Mockito.when;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.WithingsSteelHRDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.BatteryValues;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.Message;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.MessageBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.MessageFactory;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.WithingsMessage;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class MessageBuilderTest {
+
+    @Mock
+    private MessageFactory messageFactoryMock;
+
+    @Mock
+    private WithingsSteelHRDeviceSupport supportMock;
+
+    private MessageBuilder messageBuilder2Test;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        messageBuilder2Test = new MessageBuilder(supportMock, messageFactoryMock);
+    }
+
+    @Test
+    public void testUnresolveableMessage() {
+        // arrange
+        byte[] data = GB.hexStringToByteArray("143fbcce");
+        when(messageFactoryMock.createMessageFromRawData(data)).thenReturn(null);
+
+        // act
+        boolean result = messageBuilder2Test.buildMessage(data);
+
+        // assert;
+        assertFalse(result);
+        verifyNoInteractions(supportMock);
+    }
+
+    @Test
+    public void testUnknownMessageType() {
+        // arrange
+        byte[] data = GB.hexStringToByteArray("0103e7000456abcdef");
+        when(messageFactoryMock.createMessageFromRawData(data)).thenReturn(new WithingsMessage((short)999));
+
+        // act
+        boolean result = messageBuilder2Test.buildMessage(data);
+
+        // assert;
+        assertTrue(result);
+        verify(messageFactoryMock, times(1)).createMessageFromRawData(data);
+        verifyNoInteractions(supportMock);
+    }
+
+    @Test
+    public void testIncompleteMessage() {
+        // arrange
+        byte[] data = GB.hexStringToByteArray("0103e7000856abcdef");
+        when(messageFactoryMock.createMessageFromRawData(data)).thenReturn(new WithingsMessage((short)1286));
+
+        // act
+        boolean result = messageBuilder2Test.buildMessage(data);
+
+        // assert;
+        assertFalse(result);
+        verify(messageFactoryMock, never()).createMessageFromRawData(data);
+        verifyNoInteractions(supportMock);
+    }
+
+    @Test
+    public void testUnknownMessageInTwoChunks() {
+        // arrange
+        byte[] data1 = GB.hexStringToByteArray("0103e7000856abcdef");
+        byte[] data2 = GB.hexStringToByteArray("56abcdef");
+        byte[] dataComplete= GB.hexStringToByteArray("0103e7000856abcdef56abcdef");
+        when(messageFactoryMock.createMessageFromRawData(dataComplete)).thenReturn(new WithingsMessage((short)1286));
+
+        // act
+        boolean result1 = messageBuilder2Test.buildMessage(data1);
+        boolean result2 = messageBuilder2Test.buildMessage(data2);
+
+        // assert;
+        assertFalse(result1);
+        assertTrue(result2);
+        verify(messageFactoryMock, never()).createMessageFromRawData(data1);
+        verify(messageFactoryMock, never()).createMessageFromRawData(data2);
+        verify(messageFactoryMock, times(1)).createMessageFromRawData(dataComplete);
+        verifyNoInteractions(supportMock);
+    }
+
+    @Test
+    public void testKnownMessageInTwoChunks() {
+        // arrange
+        byte[] data1 = GB.hexStringToByteArray("010504000856abcdef");
+        byte[] data2 = GB.hexStringToByteArray("56abcdef");
+        byte[] dataComplete= GB.hexStringToByteArray("010504000856abcdef56abcdef");
+        when(messageFactoryMock.createMessageFromRawData(dataComplete)).thenReturn(new WithingsMessage((short)1284));
+
+        // act
+        boolean result1 = messageBuilder2Test.buildMessage(data1);
+        boolean result2 = messageBuilder2Test.buildMessage(data2);
+
+        // assert;
+        assertFalse(result1);
+        assertTrue(result2);
+        verify(messageFactoryMock, never()).createMessageFromRawData(data1);
+        verify(messageFactoryMock, never()).createMessageFromRawData(data2);
+        verify(messageFactoryMock, times(1)).createMessageFromRawData(dataComplete);
+        verifyNoInteractions(supportMock);
+    }
+
+    @Test
+    public void testKnownMessageWithValidDataInTwoChunks() {
+        // arrange
+        byte[] data1 = GB.hexStringToByteArray("010504000e0504000a4702");
+        byte[] data2 = GB.hexStringToByteArray("00000f0100000000");
+        byte[] dataComplete= GB.hexStringToByteArray("010504000e0504000a470200000f0100000000");
+        Message message = new WithingsMessage((short)1284);
+        BatteryValues batteryValues = new BatteryValues();
+        message.addDataStructure(batteryValues);
+        when(messageFactoryMock.createMessageFromRawData(dataComplete)).thenReturn(message);
+
+        // act
+        boolean result1 = messageBuilder2Test.buildMessage(data1);
+        boolean result2 = messageBuilder2Test.buildMessage(data2);
+
+        // assert;
+        assertFalse(result1);
+        assertTrue(result2);
+        verify(messageFactoryMock, never()).createMessageFromRawData(data1);
+        verify(messageFactoryMock, never()).createMessageFromRawData(data2);
+        verify(messageFactoryMock, times(1)).createMessageFromRawData(dataComplete);
+        verifyNoMoreInteractions(supportMock);
+    }
+
+}
\ No newline at end of file
diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/ChallengeTest.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/ChallengeTest.java
new file mode 100644
index 0000000..023a1b6
--- /dev/null
+++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/ChallengeTest.java
@@ -0,0 +1,67 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class ChallengeTest {
+
+    @Test
+    public void testFillFromRawData() {
+        // arrange
+        byte[] rawData = GB.hexStringToByteArray("1130303a32343a65343a36653a34633a38611082f3d9e121f16a5a3cf0ba94261e8ff6");
+        byte[] expectedChallengeBytes = GB.hexStringToByteArray("82f3d9e121f16a5a3cf0ba94261e8ff6");
+        Challenge challenge2Test = new Challenge();
+
+        // act
+        challenge2Test.fillFromRawData(rawData);
+
+        // assert
+        assertEquals("00:24:e4:6e:4c:8a", challenge2Test.getMacAddress());
+        assertArrayEquals(expectedChallengeBytes, challenge2Test.getChallenge());
+    }
+
+    @Test
+    public void testToRawData() {
+        // arrange
+        Challenge challenge2Test = new Challenge();
+        challenge2Test.setMacAddress("00:24:e4:6e:4c:8a");
+        challenge2Test.setChallenge(GB.hexStringToByteArray("82f3d9e121f16a5a3cf0ba94261e8ff6"));
+
+        // act
+        byte[] result = challenge2Test.getRawData();
+
+        // assert
+        assertArrayEquals(GB.hexStringToByteArray("012200231130303a32343a65343a36653a34633a38611082f3d9e121f16a5a3cf0ba94261e8ff6"), result);
+    }
+
+    @Test
+    public void testToRawDataNoMacAddress() {
+        // arrange
+        Challenge challenge2Test = new Challenge();
+        challenge2Test.setChallenge(GB.hexStringToByteArray("82f3d9e121f16a5a3cf0ba94261e8ff6"));
+
+        // act
+        byte[] result = challenge2Test.getRawData();
+
+        // assert
+        assertArrayEquals(GB.hexStringToByteArray("01220012001082f3d9e121f16a5a3cf0ba94261e8ff6"), result);
+    }
+
+    @Test
+    public void testToRawDataNoChallengeBytes() {
+        // arrange
+        Challenge challenge2Test = new Challenge();
+        challenge2Test.setMacAddress("00:24:e4:6e:4c:8a");
+
+        // act
+        byte[] result = challenge2Test.getRawData();
+
+        // assert
+        assertArrayEquals(GB.hexStringToByteArray("012200131130303a32343a65343a36653a34633a386100"), result);
+    }
+
+}
\ No newline at end of file
diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/DataStructureFactoryTest.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/DataStructureFactoryTest.java
new file mode 100644
index 0000000..3efe68b
--- /dev/null
+++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/DataStructureFactoryTest.java
@@ -0,0 +1,133 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures;
+
+import static org.junit.Assert.*;
+
+import org.bouncycastle.util.encoders.Hex;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
+import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
+
+public class DataStructureFactoryTest {
+
+    private DataStructureFactory factory2Test;
+
+    @Before
+    public void setUp() {
+        factory2Test = new DataStructureFactory();
+    }
+
+    @Test
+    public void testEmptyData() {
+        // arrange
+
+        // act
+        List<WithingsStructure> result = factory2Test.createStructuresFromRawData(new byte[0]);
+
+        // assert
+        assertTrue(result.isEmpty());
+    }
+
+
+    @Test
+    public void testNullData() {
+        // arrange
+
+        // act
+        List<WithingsStructure> result = factory2Test.createStructuresFromRawData(null);
+
+        // assert
+        assertTrue(result.isEmpty());
+    }
+
+    @Test
+    public void testOneStructure() {
+        // arrange
+        String dataString = "0504000a470200000f0100000000";
+        byte[] data = Hex.decode(dataString);
+
+        // act
+        List<WithingsStructure> result = factory2Test.createStructuresFromRawData(data);
+
+        // assert
+        assertEquals(1, result.size());
+        BatteryValues batteryValues = (BatteryValues)result.get(0);
+        assertEquals(2, batteryValues.getStatus());
+        assertEquals(71, batteryValues.getPercent());
+        assertEquals(3841, batteryValues.getVolt());
+    }
+
+    @Test
+    public void testTwoStructures() {
+        // arrange
+        String dataString = "0504000a470200000f01000000000504000a350100000e0200000000";
+        byte[] data = Hex.decode(dataString);
+
+        // act
+        List<WithingsStructure> result = factory2Test.createStructuresFromRawData(data);
+
+        // assert
+        assertEquals(2, result.size());
+        BatteryValues batteryValues = (BatteryValues)result.get(0);
+        assertEquals(2, batteryValues.getStatus());
+        assertEquals(71, batteryValues.getPercent());
+        assertEquals(3841, batteryValues.getVolt());
+        batteryValues = (BatteryValues)result.get(1);
+        assertEquals(1, batteryValues.getStatus());
+        assertEquals(53, batteryValues.getPercent());
+        assertEquals(3586, batteryValues.getVolt());
+    }
+
+    @Test
+    public void testTwoStructuresWithAdditionalBytes() {
+        // arrange
+        String dataString = "0504000a470200000f01000000000504000a350100000e0200000000abcdef1234";
+        byte[] data = Hex.decode(dataString);
+
+        // act
+        List<WithingsStructure> result = factory2Test.createStructuresFromRawData(data);
+
+        // assert
+        assertEquals(2, result.size());
+        BatteryValues batteryValues = (BatteryValues)result.get(0);
+        assertEquals(2, batteryValues.getStatus());
+        assertEquals(71, batteryValues.getPercent());
+        assertEquals(3841, batteryValues.getVolt());
+        batteryValues = (BatteryValues)result.get(1);
+        assertEquals(1, batteryValues.getStatus());
+        assertEquals(53, batteryValues.getPercent());
+        assertEquals(3586, batteryValues.getVolt());
+    }
+
+
+
+    @Test
+    public void testMovementData() {
+        // arrange
+        String dataString = "0601000463fbb15806020002003c060a00040008002e0603000e0010000004cf0000000000000000060400020000060b000433971a5a0601000463fbb19406020002003c060a00040006001d0603000e000b000003400000000000000000060400020000060b000435971c5a";
+        byte[] data = Hex.decode(dataString);
+
+        // act
+        List<WithingsStructure> result = factory2Test.createStructuresFromRawData(data);
+
+        // assert
+        assertEquals(12, result.size());
+        assertTrue(result.get(0) instanceof ActivitySampleTime);
+        assertTrue(result.get(1) instanceof ActivitySampleDuration);
+        assertTrue(result.get(2) instanceof ActivitySampleCalories2);
+        assertTrue(result.get(3) instanceof ActivitySampleMovement);
+        assertTrue(result.get(4) instanceof ActivitySampleWalk);
+        assertTrue(result.get(5) instanceof ActivitySampleUnknown);
+        assertTrue(result.get(6) instanceof ActivitySampleTime);
+        assertTrue(result.get(7) instanceof ActivitySampleDuration);
+        assertTrue(result.get(8) instanceof ActivitySampleCalories2);
+        assertTrue(result.get(9) instanceof ActivitySampleMovement);
+        assertTrue(result.get(10) instanceof ActivitySampleWalk);
+        assertTrue(result.get(11) instanceof ActivitySampleUnknown);
+    }
+
+}
\ No newline at end of file
diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/ProbeReplyTest.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/ProbeReplyTest.java
new file mode 100644
index 0000000..ecc6bae
--- /dev/null
+++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/ProbeReplyTest.java
@@ -0,0 +1,32 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class ProbeReplyTest {
+
+    @Test
+    public void testFillFromRawData() {
+        // arrange
+        // this data is a real world example:
+        byte[] rawData = GB.hexStringToByteArray("0000000008537465656c2048521130303a32343a65343a36653a34633a3861103433303765303861643433383531616500ffffff0830303132303132320000001b00001b8100ffffff");
+        ProbeReply probeReply2Test = new ProbeReply();
+
+        // act
+        probeReply2Test.fillFromRawData(rawData);
+
+        // assert
+        assertEquals(0, probeReply2Test.getYetUnknown1());
+        assertEquals("Steel HR", probeReply2Test.getName());
+        assertEquals("00:24:e4:6e:4c:8a", probeReply2Test.getMac());
+        assertEquals("4307e08ad43851ae", probeReply2Test.getSecret());
+        assertEquals(16777215, probeReply2Test.getYetUnknown2());
+        assertEquals("00120122", probeReply2Test.getmId());
+        assertEquals(27, probeReply2Test.getYetUnknown3());
+        assertEquals(7041, probeReply2Test.getFirmwareVersion());
+        assertEquals(16777215, probeReply2Test.getYetUnknown4());
+    }
+}
\ No newline at end of file
diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/TimeTest.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/TimeTest.java
new file mode 100644
index 0000000..5b08a72
--- /dev/null
+++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/TimeTest.java
@@ -0,0 +1,33 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+
+import java.text.SimpleDateFormat;
+import java.time.Instant;
+import java.util.Date;
+
+import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
+
+public class TimeTest {
+
+    @Test
+    public void testGetRawDataWithValues() throws Exception {
+        // arrange
+        Time time = new Time();
+        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss 'GMT'Z");
+        Date date = sdf.parse("2000-04-01 12:00:00 GMT+0200");
+        time.setNow(Instant.ofEpochMilli(date.getTime()));
+        time.setTimeOffsetInSeconds(7200);
+        date = sdf.parse("2000-10-26 02:00:00 GMT+0200");
+        time.setNextDaylightSavingTransition(Instant.ofEpochMilli(date.getTime()));
+        time.setNextDaylightSavingTransitionOffsetInSeconds(2400);
+
+        // act
+        byte[] rawData = time.getRawData();
+
+        // assert
+        assertEquals("0501001038e5c8a000001c2039f7740000000960", StringUtils.bytesToHex(rawData).toLowerCase());
+    }
+}
\ No newline at end of file
diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/WithingsTestStructure.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/WithingsTestStructure.java
new file mode 100644
index 0000000..745ec8b
--- /dev/null
+++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/datastructures/WithingsTestStructure.java
@@ -0,0 +1,30 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures;
+
+import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+
+public class WithingsTestStructure extends WithingsStructure {
+    @Override
+    public short getLength() {
+        return 6;
+    }
+
+    @Override
+    protected void fillinTypeSpecificData(ByteBuffer buffer) {
+
+    }
+
+    @Override
+    public byte[] getRawData() {
+        ByteBuffer rawDataBuffer = ByteBuffer.allocate(getLength());
+        rawDataBuffer.putShort(getType());
+        rawDataBuffer.put("Test".getBytes(StandardCharsets.UTF_8));
+        return rawDataBuffer.array();
+    }
+
+    @Override
+    public short getType() {
+        return 99;
+    }
+}
diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/message/AbstractMessageTest.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/message/AbstractMessageTest.java
new file mode 100644
index 0000000..a4e0aab
--- /dev/null
+++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/message/AbstractMessageTest.java
@@ -0,0 +1,60 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.WithingsTestStructure;
+import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
+
+public class AbstractMessageTest {
+
+    @Test
+    public void testGetRawDataNoData() {
+        // arrange
+        Message testMessage = createTestcommand();
+
+        // act
+        byte[] rawData = testMessage.getRawData();
+
+        // assert
+        assertEquals("0100630000", StringUtils.bytesToHex(rawData));
+    }
+
+    @Test
+    public void testGetRawDataWithData() {
+        // arrange
+        Message testMessage = createTestcommand();
+        testMessage.addDataStructure(new WithingsTestStructure());
+
+        // act
+        byte[] rawData = testMessage.getRawData();
+
+        // assert
+        assertEquals("0100630006006354657374", StringUtils.bytesToHex(rawData));
+    }
+
+    private Message createTestcommand() {
+        return new AbstractMessage(){
+            @Override
+            public short getType() {
+                    return 99;
+            }
+
+            @Override
+            public boolean needsResponse() {
+                return false;
+            }
+
+            @Override
+            public boolean needsEOT() {
+                return false;
+            }
+
+            @Override
+            public boolean isIncomingMessage() {
+                return false;
+            }
+        };
+    }
+}
\ No newline at end of file
diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/notification/NotificationAttributeTest.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/notification/NotificationAttributeTest.java
new file mode 100644
index 0000000..4bd623f
--- /dev/null
+++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/notification/NotificationAttributeTest.java
@@ -0,0 +1,56 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.notification;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+
+import java.nio.charset.StandardCharsets;
+
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class NotificationAttributeTest {
+
+    @Test
+    public void testSerializeDeserialize() {
+        // arrange
+        NotificationAttribute attribute = new NotificationAttribute();
+        attribute.setAttributeID((byte)4);
+        String value = "TestNotificationAttribute";
+        attribute.setValue(value);
+        byte[] rawValue = value.getBytes(StandardCharsets.UTF_8);
+        short expectedLength = (short) rawValue.length;
+
+        // act
+        byte[] result = attribute.serialize();
+
+        // assert
+        NotificationAttribute attribute2 = new NotificationAttribute();
+        attribute2.deserialize(result);
+        assertEquals((byte)4, attribute2.getAttributeID());
+        assertEquals(expectedLength, attribute2.getAttributeLength());
+        assertEquals(value, attribute2.getValue());
+    }
+
+    @Test
+    public void testSerializeDeserializeValueTooLong() {
+        // arrange
+        NotificationAttribute attribute = new NotificationAttribute();
+        attribute.setAttributeID((byte)4);
+        attribute.setAttributeMaxLength((short)4);
+        String value = "TestNotificationAttribute";
+        attribute.setValue(value);
+        byte[] rawValue = value.getBytes(StandardCharsets.UTF_8);
+        short expectedLength = (short) rawValue.length;
+
+        // act
+        byte[] result = attribute.serialize();
+
+        // assert
+        NotificationAttribute attribute2 = new NotificationAttribute();
+        attribute2.deserialize(result);
+        assertEquals((byte)4, attribute2.getAttributeID());
+        assertEquals(4, attribute2.getAttributeLength());
+        assertEquals("Test", attribute2.getValue());
+    }
+
+}
\ No newline at end of file
diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/notification/RequestedNotificationAttributeTest.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/notification/RequestedNotificationAttributeTest.java
new file mode 100644
index 0000000..87468c8
--- /dev/null
+++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/communication/notification/RequestedNotificationAttributeTest.java
@@ -0,0 +1,29 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.notification;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class RequestedNotificationAttributeTest {
+
+    @Test
+    public void testSerializeDeserialize() {
+        // arrange
+        RequestedNotificationAttribute attribute = new RequestedNotificationAttribute();
+        attribute.setAttributeID((byte)4);
+        attribute.setAttributeMaxLength((short)19);
+
+        // act
+        byte[] result = attribute.serialize();
+
+        // assert
+        String test = GB.hexdump(result);
+        RequestedNotificationAttribute attribute2 = new RequestedNotificationAttribute();
+        attribute2.deserialize(result);
+        assertEquals((byte)4, attribute2.getAttributeID());
+        assertEquals(19, attribute2.getAttributeMaxLength());
+    }
+
+}
\ No newline at end of file
diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/activity/XiaomiActivityFileIdTest.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/activity/XiaomiActivityFileIdTest.java
new file mode 100644
index 0000000..9f750fc
--- /dev/null
+++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/activity/XiaomiActivityFileIdTest.java
@@ -0,0 +1,90 @@
+/*  Copyright (C) 2023 Jos茅 Rebelo
+
+    This file is part of Gadgetbridge.
+
+    Gadgetbridge is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Gadgetbridge is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.activity;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class XiaomiActivityFileIdTest {
+    @Test
+    public void testEncode() {
+        final byte[] expectedEncoding = GB.hexStringToByteArray("21F3286504008C");
+        final XiaomiActivityFileId xiaomiActivityFileId = new XiaomiActivityFileId(
+                new Date(1697182497000L),
+                4,
+                3,
+                1,
+                8,
+                0
+        );
+
+        assertArrayEquals(expectedEncoding, xiaomiActivityFileId.toBytes());
+    }
+
+    @Test
+    public void testDecode() {
+        final byte[] bytes = GB.hexStringToByteArray("21F328650403A0");
+        final XiaomiActivityFileId expectedFileId = XiaomiActivityFileId.from(bytes);
+
+        assertEquals(1697182497000L, expectedFileId.getTimestamp().getTime());
+        assertEquals(4, expectedFileId.getTimezone());
+        assertEquals(3, expectedFileId.getVersion());
+        assertEquals(XiaomiActivityFileId.Type.SPORTS, expectedFileId.getType());
+        assertEquals(XiaomiActivityFileId.Subtype.SPORTS_FREESTYLE, expectedFileId.getSubtype());
+        assertEquals(XiaomiActivityFileId.DetailType.DETAILS, expectedFileId.getDetailType());
+    }
+
+    @Test
+    public void testDecodeEncode() {
+        final byte[] bytes = GB.hexStringToByteArray("21F328650403A021F3286504008C");
+
+        final ByteBuffer bufDecode = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN);
+
+        final List<XiaomiActivityFileId> fileIds = new ArrayList<>();
+
+        while (bufDecode.position() < bufDecode.limit()) {
+            final XiaomiActivityFileId fileId = XiaomiActivityFileId.from(bufDecode);
+            fileIds.add(fileId);
+            System.out.println(fileId);
+        }
+
+        final ByteBuffer bufEncode = ByteBuffer.allocate(fileIds.size() * 7).order(ByteOrder.LITTLE_ENDIAN);
+
+        for (final XiaomiActivityFileId fileId : fileIds) {
+            bufEncode.put(fileId.toBytes());
+        }
+
+        assertArrayEquals(bytes, bufEncode.array());
+    }
+
+    @Test
+    public void testZero() {
+        final XiaomiActivityFileId fileId = XiaomiActivityFileId.from(new byte[]{0, 0, 0, 0, 0, 0, 0});
+        assertTrue(fileId.getTimestamp().getTime() == 0 && fileId.getVersion() == 0);
+    }
+}
diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/activity/XiaomiActivityParserTest.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/activity/XiaomiActivityParserTest.java
new file mode 100644
index 0000000..432bcd2
--- /dev/null
+++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/activity/XiaomiActivityParserTest.java
@@ -0,0 +1,22 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.activity;
+
+import org.junit.Ignore;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.Arrays;
+
+public class XiaomiActivityParserTest {
+    @Test
+    @Ignore("helper test for development, remove this while debugging")
+    public void localTest() throws IOException {
+        final byte[] data = Files.readAllBytes(Paths.get("/storage/activity.bin"));
+        final byte[] fileIdBytes = Arrays.copyOfRange(data, 0, 7);
+        final XiaomiActivityFileId fileId = XiaomiActivityFileId.from(fileIdBytes);
+        final XiaomiActivityParser parser = XiaomiActivityParser.create(fileId);
+
+        parser.parse(null, fileId, data);
+    }
+}
diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/test/ArmenianTransliteratorTest.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/test/ArmenianTransliteratorTest.java
new file mode 100644
index 0000000..ca58e5d
--- /dev/null
+++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/test/ArmenianTransliteratorTest.java
@@ -0,0 +1,168 @@
+package nodomain.freeyourgadget.gadgetbridge.test;
+
+import junit.framework.TestCase;
+
+import org.apache.commons.lang3.text.WordUtils;
+import org.junit.Test;
+import org.junit.Assert;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import nodomain.freeyourgadget.gadgetbridge.util.language.impl.ArmenianTransliterator;
+
+public class ArmenianTransliteratorTest extends TestCase {
+    @Test
+    public void testSimpleCases() {
+        Assert.assertEquals("aybuben", new ArmenianTransliterator().transliterate("铡盏闸崭謧闸榨斩"));
+        Assert.assertEquals("vorotan", new ArmenianTransliterator().transliterate("崭謤崭湛铡斩"));
+        Assert.assertEquals("voroshel", new ArmenianTransliterator().transliterate("崭謤崭辗榨宅"));
+        Assert.assertEquals("uzox", new ArmenianTransliterator().transliterate("崭謧咋崭詹"));
+        Assert.assertEquals(
+                "AVO", new ArmenianTransliterator().transliterate("员請")
+        );
+    }
+
+    @Test
+    public void testMultipleWords() {
+        Assert.assertEquals("vory karucum en Viqipedia kayqic ogtvoxnery azat xmbagrman dzevachapov",
+                new ArmenianTransliterator().transliterate("崭謤炸 寨铡占崭謧謥崭謧沾 榨斩 諑斋謩斋蘸榨栅斋铡 寨铡盏謩斋謥 謪眨湛站崭詹斩榨謤炸 铡咋铡湛 窄沾闸铡眨謤沾铡斩 毡謬铡展铡謨崭站"));
+    }
+
+    @Test
+    public void testMixedStrings() {
+        Assert.assertEquals("vor1voshel 12 uzox", new ArmenianTransliterator().transliterate("崭謤1崭辗榨宅 12 崭謧咋崭詹"));
+        Assert.assertEquals("vory jet iridescent karucum en sheen Viqipedia kayqic ogtvoxnery and a distinctive azat xmbagrman dzevachapov",
+                new ArmenianTransliterator().transliterate("崭謤炸 jet iridescent 寨铡占崭謧謥崭謧沾 榨斩 sheen 諑斋謩斋蘸榨栅斋铡 寨铡盏謩斋謥 謪眨湛站崭詹斩榨謤炸 and a distinctive 铡咋铡湛 窄沾闸铡眨謤沾铡斩 毡謬铡展铡謨崭站"));
+    }
+
+    @Test
+    public void testMixedCaseWords() {
+        Assert.assertEquals(
+                "Inchpes", new ArmenianTransliterator().transliterate("曰斩展蘸榨战")
+        );
+        Assert.assertEquals(
+                "VOr諘SHEL", new ArmenianTransliterator().transliterate("請謤請諊缘约")
+        );
+        Assert.assertEquals(
+                "Ushadir", new ArmenianTransliterator().transliterate("請謧辗铡栅斋謤")
+        );
+        Assert.assertEquals(
+                "AU", new ArmenianTransliterator().transliterate("员請謧")
+        );
+    }
+
+    @Test
+    public void testTop100Words() {
+        final Map<String,String> topWords = new LinkedHashMap<String,String>() {{
+            put("斋斩展蘸榨战", "inchpes");
+            put("榨战", "es");
+            put("斩謤铡", "nra");
+            put("崭謤", "vor");
+            put("斩铡", "na");
+            put("乍謤", "er");
+            put("瞻铡沾铡謤", "hamar");
+            put("斋斩", "in");
+            put("瞻榨湛", "het");
+            put("斩謤铡斩謩", "nranq");
+            put("宅斋斩榨宅", "linel");
+            put("沾榨寨", "mek");
+            put("崭謧斩榨斩铡宅", "unenal");
+            put("铡盏战", "ays");
+            put("斋謥", "ic");
+            put("斋", "i");
+            put("湛铡謩", "taq");
+            put("闸铡占", "bar");
+            put("闸铡盏謥", "bayc");
+            put("斋斩展", "inch");
+            put("沾斋", "mi");
+            put("謩铡斩斋", "qani");
+            put("乍", "e");
+            put("铡盏斩", "ayn");
+            put("栅崭謧謩", "duq");
+            put("寨铡沾", "kam");
+            put("榨謧", "ev");
+            put("沾斋斩展榨謧", "minchev");
+            put("斋战寨", "isk");
+            put("铡", "a");
+            put("沾榨斩謩", "menq");
+            put("寨铡謤崭詹", "karox");
+            put("铡盏宅", "ayl");
+            put("榨斩", "en");
+            put("崭謤炸", "vory");
+            put("铡斩榨宅", "anel");
+            put("斋謤榨斩謥", "irenc");
+            put("摘铡沾铡斩铡寨", "jamanak");
+            put("榨诈榨", "ete");
+            put("寨铡沾謩", "kamq");
+            put("盏崭謧謤铡謩铡斩展盏崭謧謤", "yuraqanchyur");
+            put("铡战榨宅", "asel");
+            put("辗铡謤謩", "sharq");
+            put("榨謤榨謩", "ereq");
+            put("崭謧咋崭謧沾", "uzum");
+            put("謪栅斋", "odi");
+            put("宅铡站", "lav");
+            put("斩崭謧盏斩蘸榨战", "nuynpes");
+            put("窄铡詹铡宅", "xaxal");
+            put("謨崭謩謤", "poqr");
+            put("站榨謤栈", "verj");
+            put("寨铡謤栅铡宅", "kardal");
+            put("毡榨占謩", "dzerq");
+            put("斩铡站铡瞻铡斩眨斋战湛", "navahangist");
+            put("湛铡占", "tar");
+            put("铡占", "ar");
+            put("铡站榨宅铡謥斩榨宅", "avelacnel");
+            put("斩崭謧盏斩斋战寨", "nuynisk");
+            put("铡盏战湛榨詹", "aystex");
+            put("蘸榨湛謩", "petq");
+            put("沾榨债", "mec");
+            put("闸铡謤毡謤", "bardzr");
+            put("铡盏栅蘸斋战斋", "aydpisi");
+            put("瞻榨湛榨謧榨宅", "hetevel");
+            put("眨崭謤债", "gorc");
+            put("斋斩展崭謧", "inchu");
+            put("窄斩栅謤榨宅", "xndrel");
+            put("湛詹铡沾铡謤栅斋寨", "txamardik");
+            put("謨崭謨崭窄崭謧诈盏崭謧斩", "popoxutyun");
+            put("眨斩铡謥", "gnac");
+            put("宅崭謧盏战", "luys");
+            put("闸铡謤斋", "bari");
+            put("栅崭謧謤战", "durs");
+            put("铡斩瞻謤铡摘榨辗湛", "anhrajesht");
+            put("湛崭謧斩", "tun");
+            put("斩寨铡謤", "nkar");
+            put("謨崭謤毡榨宅", "pordzel");
+            put("沾榨咋", "mez");
+            put("寨謤寨斋斩", "krkin");
+            put("寨榨斩栅铡斩斋", "kendani");
+            put("寨榨湛", "ket");
+            put("沾铡盏謤", "mayr");
+            put("铡辗窄铡謤瞻", "ashxarh");
+            put("沾崭湛", "mot");
+            put("寨铡占崭謧謥榨宅", "karucel");
+            put("斋斩謩斩崭謧謤崭謧盏斩", "inqnuruyn");
+            put("榨謤寨斋謤", "erkir");
+            put("瞻铡盏謤", "hayr");
+            put("謥铡斩寨铡謥铡债", "cankacac");
+            put("斩崭謤", "nor");
+            put("铡辗窄铡湛铡斩謩", "ashxatanq");
+            put("沾铡战", "mas");
+            put("站榨謤謥斩榨宅", "vercnel");
+            put("战湛铡斩铡宅", "stanal");
+            put("湛榨詹", "tex");
+            put("铡蘸謤榨宅", "aprel");
+            put("崭謤湛榨詹", "vortex");
+            put("榨謤闸", "erb");
+            put("諑榨謤铡栅铡占斩铡宅", "Veradarnal");
+            put("沾斋铡盏斩", "miayn");
+        }};
+
+        for (final Map.Entry<String,String> entry : topWords.entrySet()) {
+            Assert.assertEquals(entry.getValue(), new ArmenianTransliterator().transliterate(entry.getKey()));
+        }
+
+        for (final Map.Entry<String,String> entry : topWords.entrySet()) {
+            Assert.assertEquals(WordUtils.capitalize(entry.getValue()), WordUtils.capitalize(new ArmenianTransliterator().transliterate(entry.getKey())));
+        }
+    }
+}
\ No newline at end of file
diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/test/ArrayUtilsTest.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/test/ArrayUtilsTest.java
new file mode 100644
index 0000000..71064f5
--- /dev/null
+++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/test/ArrayUtilsTest.java
@@ -0,0 +1,157 @@
+package nodomain.freeyourgadget.gadgetbridge.test;
+
+import org.junit.Test;
+
+import nodomain.freeyourgadget.gadgetbridge.util.ArrayUtils;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * Tests ArrayUtils
+ */
+public class ArrayUtilsTest extends TestBase {
+
+    private static final byte[] EMPTY = new byte[0];
+    private static final byte[] DATA_5 = new byte[] { 1, 2, 3, 4, 5};
+
+    public ArrayUtilsTest() throws Exception {
+    }
+
+    @Test
+    public void testEqualsException1() throws Exception {
+        try {
+            ArrayUtils.equals(null, EMPTY, 0);
+            fail("equals should throw on bad argument");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+    }
+
+    @Test
+    public void testEqualsException2() throws Exception {
+        try {
+            ArrayUtils.equals(EMPTY, null, 0);
+            fail("equals should throw on bad argument");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+    }
+
+    @Test
+    public void testEqualsException5() throws Exception {
+        try {
+            ArrayUtils.equals(EMPTY, EMPTY, -1);
+            fail("equals should throw on bad argument");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+    }
+
+    @Test
+    public void testEqualsEmptyData() throws Exception {
+        assertFalse(ArrayUtils.equals(EMPTY, DATA_5, 0));
+    }
+
+    @Test
+    public void testEqualsEmptyTest() throws Exception {
+        // testing for 0 equal bytes may be senseless, but always true
+        assertTrue(ArrayUtils.equals(EMPTY, EMPTY, 0));
+        assertTrue(ArrayUtils.equals(DATA_5, EMPTY, 0));
+    }
+
+    @Test
+    public void testEquals1() throws Exception {
+        assertTrue(ArrayUtils.equals(DATA_5, b(1), 0));
+    }
+
+    @Test
+    public void testEquals2() throws Exception {
+        assertTrue(ArrayUtils.equals(DATA_5, b(2), 1));
+    }
+
+    @Test
+    public void testEquals5() throws Exception {
+        assertTrue(ArrayUtils.equals(DATA_5, b(5), 4));
+    }
+
+    @Test
+    public void testEqualsOutOfRange() throws Exception {
+        assertFalse(ArrayUtils.equals(DATA_5, b(5), 5));
+    }
+
+    @Test
+    public void testEquals123() throws Exception {
+        assertTrue(ArrayUtils.equals(DATA_5, new byte[] {1, 2, 3}, 0));
+    }
+
+    @Test
+    public void testEquals234() throws Exception {
+        assertTrue(ArrayUtils.equals(DATA_5, new byte[] {2, 3, 4}, 1));
+    }
+
+    @Test
+    public void testEquals345() throws Exception {
+        assertTrue(ArrayUtils.equals(DATA_5, new byte[] {3, 4, 5}, 2));
+    }
+
+    @Test
+    public void testEquals12345() throws Exception {
+        assertTrue(ArrayUtils.equals(DATA_5, DATA_5, 0));
+    }
+
+    @Test
+    public void testEqualsWrongStart() throws Exception {
+        assertFalse(ArrayUtils.equals(DATA_5, new byte[] {0, 2, 3}, 0));
+    }
+
+    @Test
+    public void testEqualsWrongEnd() throws Exception {
+        assertFalse(ArrayUtils.equals(DATA_5, new byte[] {3, 4, 6}, 2));
+    }
+
+    @Test
+    public void testStartsWith1() throws Exception {
+        assertTrue(ArrayUtils.startsWith(DATA_5, new byte[] {1}));
+    }
+
+    @Test
+    public void testStartsWith2() throws Exception {
+        assertTrue(ArrayUtils.startsWith(DATA_5, new byte[] {1, 2}));
+    }
+
+    @Test
+    public void testStartsWithAll() throws Exception {
+        assertTrue(ArrayUtils.startsWith(DATA_5, DATA_5.clone()));
+    }
+
+    @Test
+    public void testStartsWithEmpty() throws Exception {
+        assertTrue(ArrayUtils.startsWith(DATA_5, EMPTY));
+    }
+
+    @Test
+    public void testStartsWithFail1() throws Exception {
+        try {
+            ArrayUtils.startsWith(DATA_5, null);
+            fail("should have thrown an exception");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+    }
+
+    @Test
+    public void testStartsWithFail3() throws Exception {
+        assertFalse(ArrayUtils.startsWith(DATA_5, new byte[] {2, 3}));
+    }
+
+    @Test
+    public void testStartsWithFail4() throws Exception {
+        assertFalse(ArrayUtils.startsWith(DATA_5, new byte[] {1, 2, 3, 4, 5, 6}));
+    }
+
+    private byte[] b(int b) {
+        return new byte[] {(byte) b};
+    }
+}
diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/test/BLETypeConversionsTest.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/test/BLETypeConversionsTest.java
new file mode 100644
index 0000000..a8da60a
--- /dev/null
+++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/test/BLETypeConversionsTest.java
@@ -0,0 +1,218 @@
+package nodomain.freeyourgadget.gadgetbridge.test;
+
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.GregorianCalendar;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+public class BLETypeConversionsTest extends TestBase {
+    @Test
+    public void testTimeParsing1() {
+        byte[] requested = new byte[]{
+                (byte) 0xe1, 0x07, 0x0a, 0x1d, 0x00, 0x1c, 0x00, 0x08
+        };
+        byte[] received = new byte[]{
+                (byte) 0xe1, 0x07, 0x0a, 0x1c, 0x17, 0x1c, 0x00, 0x04
+        };
+        GregorianCalendar calRequested = BLETypeConversions.rawBytesToCalendar(requested);
+        GregorianCalendar calReceived = BLETypeConversions.rawBytesToCalendar(received);
+
+        assertEquals(calRequested.getTime(), calReceived.getTime());
+    }
+
+    @Test
+    public void testTimeParsingWithDST() {
+        byte[] requested = new byte[]{
+                (byte) 0xe1, 0x07, 0x0a, 0x09, 0x11, 0x23, 0x00, 0x08
+        };
+        byte[] received = new byte[]{
+                (byte) 0xe1, 0x07, 0x0a, 0x09, 0x10, 0x23, 0x00, 0x04
+        };
+        GregorianCalendar calRequested = BLETypeConversions.rawBytesToCalendar(requested);
+        GregorianCalendar calReceived = BLETypeConversions.rawBytesToCalendar(received);
+
+        assertEquals(calRequested.getTime(), calReceived.getTime());
+    }
+
+    @Test
+    public void testToUnsignedInt() {
+        assertEquals(0L, BLETypeConversions.toUnsigned(0));
+        assertEquals(12345L, BLETypeConversions.toUnsigned(12345));
+        assertEquals(0x7FFFFFFFL, BLETypeConversions.toUnsigned(0x7FFFFFFF));
+        assertEquals(0x80000000L, BLETypeConversions.toUnsigned(0x80000000));
+        assertEquals(0xFFFFFFFFL, BLETypeConversions.toUnsigned(0xFFFFFFFF));
+    }
+
+    @Test
+    public void testToUnsignedShort() {
+        assertEquals(0, BLETypeConversions.toUnsigned((short) 0));
+        assertEquals(12345, BLETypeConversions.toUnsigned((short) 12345));
+        assertEquals(0x7FFF, BLETypeConversions.toUnsigned((short) 0x7FFF));
+        assertEquals(0x8000, BLETypeConversions.toUnsigned((short) 0x8000));
+        assertEquals(0xFFFF, BLETypeConversions.toUnsigned((short) 0xFFFF));
+    }
+
+    @Test
+    public void testToUnsignedByte() {
+        assertEquals(0, BLETypeConversions.toUnsigned((byte) 0));
+        assertEquals(123, BLETypeConversions.toUnsigned((byte) 123));
+        assertEquals(0x7F, BLETypeConversions.toUnsigned((byte) 0x7F));
+        assertEquals(0x80, BLETypeConversions.toUnsigned((byte) 0x80));
+        assertEquals(0xFF, BLETypeConversions.toUnsigned((byte) 0xFF));
+    }
+
+    @Test
+    public void testToUnsignedByteArray() {
+        final byte[] array = new byte[]{(byte) 0x12, (byte) 0x00, (byte) 123, (byte) 0x7F, (byte) 0x80, (byte) 0xFF};
+        assertEquals(0, BLETypeConversions.toUnsigned(array, 1));
+        assertEquals(123, BLETypeConversions.toUnsigned(array, 2));
+        assertEquals(0x7F, BLETypeConversions.toUnsigned(array, 3));
+        assertEquals(0x80, BLETypeConversions.toUnsigned(array, 4));
+        assertEquals(0xFF, BLETypeConversions.toUnsigned(array, 5));
+    }
+
+    @Test
+    public void testToUint16Single() {
+        assertEquals(0, BLETypeConversions.toUint16((byte) 0));
+        assertEquals(123, BLETypeConversions.toUint16((byte) 123));
+        assertEquals(0x7F, BLETypeConversions.toUint16((byte) 0x7F));
+        assertEquals(0x80, BLETypeConversions.toUint16((byte) 0x80));
+        assertEquals(0xFF, BLETypeConversions.toUint16((byte) 0xFF));
+    }
+
+    @Test
+    public void testToUint16Bytes() {
+        assertEquals(0, BLETypeConversions.toUint16((byte) 0, (byte) 0));
+        assertEquals(0x1234, BLETypeConversions.toUint16((byte) 0x34, (byte) 0x12));
+        assertEquals(0x369C, BLETypeConversions.toUint16((byte) 0x9C, (byte) 0x36));
+        assertEquals(0x7FFF, BLETypeConversions.toUint16((byte) 0xFF, (byte) 0x7F));
+        assertEquals(0x8000, BLETypeConversions.toUint16((byte) 0x00, (byte) 0x80));
+        assertEquals(0xFFFF, BLETypeConversions.toUint16((byte) 0xFF, (byte) 0xFF));
+    }
+
+    @Test
+    public void testToUint16Array() {
+        final byte[] array = new byte[]{
+                (byte) 0x12,
+                (byte) 0x00, (byte) 0x00,
+                (byte) 0x34, (byte) 0x12,
+                (byte) 0x9C, (byte) 0x36,
+                (byte) 0xFF, (byte) 0x7F,
+                (byte) 0x00, (byte) 0x80,
+                (byte) 0xFF, (byte) 0xFF,
+        };
+        assertEquals(0, BLETypeConversions.toUint16(array, 1));
+        assertEquals(0x1234, BLETypeConversions.toUint16(array, 3));
+        assertEquals(0x369C, BLETypeConversions.toUint16(array, 5));
+        assertEquals(0x7FFF, BLETypeConversions.toUint16(array, 7));
+        assertEquals(0x8000, BLETypeConversions.toUint16(array, 9));
+        assertEquals(0xFFFF, BLETypeConversions.toUint16(array, 11));
+    }
+
+    @Test
+    public void testToUint32Bytes() {
+        assertEquals(0, BLETypeConversions.toUint32((byte) 0, (byte) 0, (byte) 0, (byte) 0));
+        assertEquals(0x12345678, BLETypeConversions.toUint32((byte) 0x78, (byte) 0x56, (byte) 0x34, (byte) 0x12));
+        assertEquals(0x2468ACE0, BLETypeConversions.toUint32((byte) 0xE0, (byte) 0xAC, (byte) 0x68, (byte) 0x24));
+        assertEquals(0x7FFFFFFF, BLETypeConversions.toUint32((byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0x7F));
+        assertEquals(0x80000000, BLETypeConversions.toUint32((byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80));
+        assertEquals(0xFFFFFFFF, BLETypeConversions.toUint32((byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF));
+    }
+
+    @Test
+    public void testToUint32Array() {
+        final byte[] array = new byte[]{
+                (byte) 0x12,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x78, (byte) 0x56, (byte) 0x34, (byte) 0x12,
+                (byte) 0xE0, (byte) 0xAC, (byte) 0x68, (byte) 0x24,
+                (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0x7F,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80,
+                (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
+        };
+        assertEquals(0, BLETypeConversions.toUint32(array, 1));
+        assertEquals(0x12345678, BLETypeConversions.toUint32(array, 5));
+        assertEquals(0x2468ACE0, BLETypeConversions.toUint32(array, 9));
+        assertEquals(0x7FFFFFFF, BLETypeConversions.toUint32(array, 13));
+        assertEquals(0x80000000, BLETypeConversions.toUint32(array, 17));
+        assertEquals(0xFFFFFFFF, BLETypeConversions.toUint32(array, 21));
+    }
+
+    @Test
+    public void testToUint64Bytes() {
+        assertEquals(0, BLETypeConversions.toUint64((byte) 0, (byte) 0, (byte) 0, (byte) 0, (byte) 0, (byte) 0, (byte) 0, (byte) 0));
+        assertEquals(0x123456789ABCDEF0L, BLETypeConversions.toUint64((byte) 0xF0, (byte) 0xDE, (byte) 0xBC, (byte) 0x9A, (byte) 0x78, (byte) 0x56, (byte) 0x34, (byte) 0x12));
+        assertEquals(0x7FFFFFFFFFFFFFFFL, BLETypeConversions.toUint64((byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0x7F));
+        assertEquals(0x8000000000000000L, BLETypeConversions.toUint64((byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80));
+        assertEquals(0xFFFFFFFFFFFFFFFFL, BLETypeConversions.toUint64((byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF));
+    }
+
+    @Test
+    public void testToUint64Array() {
+        final byte[] array = new byte[]{
+                (byte) 0x12,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0xF0, (byte) 0xDE, (byte) 0xBC, (byte) 0x9A, (byte) 0x78, (byte) 0x56, (byte) 0x34, (byte) 0x12,
+                (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0x7F,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80,
+                (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
+        };
+        assertEquals(0, BLETypeConversions.toUint64(array, 1));
+        assertEquals(0x123456789ABCDEF0L, BLETypeConversions.toUint64(array, 9));
+        assertEquals(0x7FFFFFFFFFFFFFFFL, BLETypeConversions.toUint64(array, 17));
+        assertEquals(0x8000000000000000L, BLETypeConversions.toUint64(array, 25));
+        assertEquals(0xFFFFFFFFFFFFFFFFL, BLETypeConversions.toUint64(array, 33));
+    }
+
+    @Test
+    public void testWriteUint8() {
+        for (final int testedValue : new int[]{
+                0x00, 123, 0x7F, 0x80, 0xFF
+        }) {
+            final byte[] buffer = new byte[2];
+            Arrays.fill(buffer, (byte) 0xFF);
+            BLETypeConversions.writeUint8(buffer, 1, testedValue);
+            assertEquals(testedValue, BLETypeConversions.toUnsigned(buffer, 1));
+        }
+    }
+
+    @Test
+    public void testWriteUint16() {
+        for (final int testedValue : new int[]{
+                0x0000, 12345, 0x7FFF, 0x8000, 0xFFFF
+        }) {
+            final byte[] buffer = new byte[3];
+            Arrays.fill(buffer, (byte) 0xFF);
+            BLETypeConversions.writeUint16(buffer, 1, testedValue);
+            assertEquals(testedValue, BLETypeConversions.toUint16(buffer, 1));
+        }
+    }
+
+    @Test
+    public void testWriteUint32() {
+        for (final int testedValue : new int[]{
+                0, 0x12345678, 0x7FFFFFFF, 0x80000000, 0xFFFFFFFF
+        }) {
+            final byte[] buffer = new byte[5];
+            Arrays.fill(buffer, (byte) 0xFF);
+            BLETypeConversions.writeUint32(buffer, 1, testedValue);
+            assertEquals(testedValue, BLETypeConversions.toUint32(buffer, 1));
+        }
+    }
+
+    @Test
+    public void testWriteUint64() {
+        for (final long testedValue : new long[]{
+                0L, 0x123456789ABCDEF0L, 0x7FFFFFFFFFFFFFFFL, 0x8000000000000000L, 0xFFFFFFFFFFFFFFFFL
+        }) {
+            final byte[] buffer = new byte[9];
+            Arrays.fill(buffer, (byte) 0xFF);
+            BLETypeConversions.writeUint64(buffer, 1, testedValue);
+            assertEquals(testedValue, BLETypeConversions.toUint64(buffer, 1));
+        }
+    }
+}
diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/test/CalendarEventTest.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/test/CalendarEventTest.java
new file mode 100644
index 0000000..5ced1c9
--- /dev/null
+++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/test/CalendarEventTest.java
@@ -0,0 +1,62 @@
+package nodomain.freeyourgadget.gadgetbridge.test;
+
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.entities.CalendarSyncStateDao;
+import nodomain.freeyourgadget.gadgetbridge.externalevents.CalendarReceiver;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.util.calendar.CalendarEvent;
+
+import static junit.framework.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+public class CalendarEventTest extends TestBase {
+    private static final long BEGIN = 1;
+    private static final long END = 2;
+    private static final long ID_1 = 100;
+    private static final long ID_2 = 101;
+    private static final String CALNAME_1 = "cal1";
+    private static final String CALACCOUNTNAME_1 = "account1";
+    private static final int COLOR_1 = 185489;
+
+    @Test
+    public void testHashCode() {
+        CalendarEvent c1 =
+                new CalendarEvent(BEGIN, END, ID_1, "something", null, null, CALNAME_1, CALACCOUNTNAME_1, COLOR_1, false, null, null, null, null);
+        CalendarEvent c2 =
+                new CalendarEvent(BEGIN, END, ID_1, null, "something", null, CALNAME_1, CALACCOUNTNAME_1, COLOR_1, false, null, null, null, null);
+        CalendarEvent c3 =
+                new CalendarEvent(BEGIN, END, ID_1, null, null, "something", CALNAME_1, CALACCOUNTNAME_1, COLOR_1, false, null, null, null, null);
+        CalendarEvent c4 =
+                new CalendarEvent(BEGIN, END, ID_1, null, null, "something", CALNAME_1, CALACCOUNTNAME_1, COLOR_1, false, "some", null, null, null);
+
+        assertEquals(c1.hashCode(), c1.hashCode());
+        assertNotEquals(c1.hashCode(), c2.hashCode());
+        assertNotEquals(c2.hashCode(), c3.hashCode());
+        assertNotEquals(c3.hashCode(), c4.hashCode());
+    }
+
+
+    @Test
+    public void testSync() {
+        List<CalendarEvent> eventList = new ArrayList<>();
+        eventList.add(new CalendarEvent(BEGIN, END, ID_1, null, "something", null, CALNAME_1, CALACCOUNTNAME_1, COLOR_1, false, null, null, null, null));
+
+        GBDevice dummyGBDevice = createDummyGDevice("00:00:01:00:03");
+        dummyGBDevice.setState(GBDevice.State.INITIALIZED);
+//        Device device = DBHelper.getDevice(dummyGBDevice, daoSession);
+        CalendarReceiver testCR = new CalendarReceiver(getContext(), dummyGBDevice);
+
+        testCR.syncCalendar(eventList);
+
+        eventList.add(new CalendarEvent(BEGIN, END, ID_2, null, "something", null, CALNAME_1, CALACCOUNTNAME_1, COLOR_1, false, null, null, null, null));
+        testCR.syncCalendar(eventList);
+
+        CalendarSyncStateDao calendarSyncStateDao = daoSession.getCalendarSyncStateDao();
+        assertEquals(2, calendarSyncStateDao.count());
+    }
+
+}
diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/test/FileUtilsTest.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/test/FileUtilsTest.java
new file mode 100644
index 0000000..e4a4db0
--- /dev/null
+++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/test/FileUtilsTest.java
@@ -0,0 +1,44 @@
+package nodomain.freeyourgadget.gadgetbridge.test;
+
+import org.junit.Test;
+
+import java.io.File;
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+public class FileUtilsTest extends TestBase {
+
+    @Test
+    public void testValidFileName() {
+        String tempName = "foo:bar";
+        assertEquals("foo_bar", FileUtils.makeValidFileName(tempName));
+
+        tempName = "fo\no::bar";
+        assertEquals("fo_o__bar", FileUtils.makeValidFileName(tempName));
+    }
+
+    @Test
+    public void testExternalFilesDir() throws IOException {
+        File dir = FileUtils.getExternalFilesDir();
+        assertTrue(dir.exists());
+    }
+
+    @Test
+    public void testExternalFile() throws IOException {
+        File extDir = FileUtils.getExternalFilesDir();
+        File dir = FileUtils.getExternalFile("qfiles");
+        assertFalse(dir.exists());
+
+        File file = FileUtils.getExternalFile("qfiles/test");
+        assertTrue(file.getParentFile().exists());
+        assertFalse(file.exists());
+
+        File expectedFile = new File(extDir + "/qfiles/test");
+        assertEquals(expectedFile.getPath(), file.getPath());
+    }
+}
diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/test/FitProTests.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/test/FitProTests.java
new file mode 100644
index 0000000..00c9948
--- /dev/null
+++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/test/FitProTests.java
@@ -0,0 +1,26 @@
+package nodomain.freeyourgadget.gadgetbridge.test;
+
+import org.junit.Test;
+
+public class FitProTests extends TestBase {
+    // since unit tests seem to be broken in Android Studio these are not complete
+    // but but at least this provides some help for the future
+    // can be used this way:
+
+    @Test
+    public void testSleepPacket() {
+        String sleepData = "cd002915010300242b1f0008000f0003001e0002002d0001003c0001004b0001005a00010069000200780002";
+
+        //handleSleepData(stringToByteArray(sleepData));
+    }
+
+    @Test
+    public void testStepPacket() {
+        //note different format of data and thus different stringWith0xToByteArray method
+        String stepData = "0xcd 0x00 0x31 0x15 0x01 0x02 0x00 0x2c 0x2b 0x34 0x00 0x05 0x80 0x45 0x07 0x1a 0x09 0xc8 0xbb 0x06 0x79 0x55 0x06 0x26 0x09 0xe8 0xb0 0xe4 0x71 0x15 0x05 0x9b 0x0a 0x08 0xa4 0xdc 0x7b 0x85 0x06 0x67 0x0a 0x28 0xb4 0x16 0x76 0x35 0x06 0x16 0x0a 0x48 0xac 0x57";
+
+        //handleStepData(stringWith0xToByteArray(stepData));
+    }
+
+
+}
diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/test/GBZipFileTest.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/test/GBZipFileTest.java
new file mode 100644
index 0000000..d634643
--- /dev/null
+++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/test/GBZipFileTest.java
@@ -0,0 +1,185 @@
+package nodomain.freeyourgadget.gadgetbridge.test;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+
+import nodomain.freeyourgadget.gadgetbridge.util.GBZipFile;
+import nodomain.freeyourgadget.gadgetbridge.util.ZipFileException;
+
+public class GBZipFileTest extends TestBase {
+    private static final String TEST_FILE_NAME = "manifest.json";
+    private static final String TEST_NESTED_FILE_NAME = "directory/manifest.json";
+    private static final String TEST_FILE_CONTENTS_1 = "{ \"mykey\": \"myvalue\", \"myarr\": [0, 1, 2, 3] }";
+    private static final String TEST_FILE_CONTENTS_2 = "{\n" +
+        "    \"manifest\": {\n" +
+        "        \"application\": {\n" +
+        "            \"bin_file\": \"pinetime-mcuboot-app-image-1.10.0.bin\",\n" +
+        "            \"dat_file\": \"pinetime-mcuboot-app-image-1.10.0.dat\",\n" +
+        "            \"init_packet_data\": {\n" +
+        "                \"application_version\": 4294967295,\n" +
+        "                \"device_revision\": 65535,\n" +
+        "                \"device_type\": 82,\n" +
+        "                \"firmware_crc16\": 21770,\n" +
+        "                \"softdevice_req\": [\n" +
+        "                    65534\n" +
+        "                ]\n" +
+        "            }\n" +
+        "        },\n" +
+        "        \"dfu_version\": 0.5\n" +
+        "    }\n" +
+        "}";
+
+    @Test
+    public void testZipSize1() throws IOException, ZipFileException {
+        final String contents = TEST_FILE_CONTENTS_1;
+
+        byte[] zipArchive = createZipArchive(TEST_FILE_NAME, contents);
+
+        GBZipFile zipFile = new GBZipFile(zipArchive);
+        String readContents = new String(zipFile.getFileFromZip(TEST_FILE_NAME));
+
+        Assert.assertEquals(contents, readContents);
+    }
+
+    @Test
+    public void testZipSize2() throws IOException, ZipFileException {
+        final String contents = TEST_FILE_CONTENTS_2;
+
+        byte[] zipArchive = createZipArchive(TEST_FILE_NAME, contents);
+
+        GBZipFile zipFile = new GBZipFile(zipArchive);
+        String readContents = new String(zipFile.getFileFromZip(TEST_FILE_NAME));
+
+        Assert.assertEquals(contents, readContents);
+    }
+
+    @Test
+    public void testZipSize3() throws IOException, ZipFileException, JSONException {
+        String contents = makeLargeJsonObject(new JSONObject(TEST_FILE_CONTENTS_2), 4).toString(4);
+
+        byte[] zipArchive = createZipArchive(TEST_FILE_NAME, contents);
+
+        GBZipFile zipFile = new GBZipFile(zipArchive);
+        String readContents = new String(zipFile.getFileFromZip(TEST_FILE_NAME));
+
+        Assert.assertEquals(contents, readContents);
+    }
+
+    @Test
+    public void testZipSize4() throws IOException, ZipFileException, JSONException {
+        String contents = makeLargeJsonObject(new JSONObject(TEST_FILE_CONTENTS_2), 32).toString(4);
+
+        byte[] zipArchive = createZipArchive(TEST_FILE_NAME, contents);
+
+        GBZipFile zipFile = new GBZipFile(zipArchive);
+        String readContents = new String(zipFile.getFileFromZip(TEST_FILE_NAME));
+
+        Assert.assertEquals(contents, readContents);
+    }
+
+    @Test
+    public void testZipFileInDir() throws IOException, ZipFileException {
+        String contents = TEST_FILE_CONTENTS_1;
+
+        byte[] zipArchive = createZipArchive(TEST_NESTED_FILE_NAME, contents);
+
+        GBZipFile zipFile = new GBZipFile(zipArchive);
+        String readContents = new String(zipFile.getFileFromZip(TEST_NESTED_FILE_NAME));
+
+        Assert.assertEquals(contents, readContents);
+    }
+
+    @Test
+    public void testZipFilesUnorderedAccess() throws IOException, ZipFileException {
+        String contents1 = TEST_FILE_CONTENTS_1;
+        String contents2 = TEST_FILE_CONTENTS_2;
+        String contents3 = "zbuMyWvIxeKgcWnsSYOd8CTLgjc9x7ti21OlLlGduMJVXlKc835WEUKJ3xR6GDA5d0tHSXnYxZkDlznFQVyueHhwYywsMO9PlkJqjOCA2Mn8uTuTliIKUNPBraFipOodb6rW31HdKLOd7gmniLF5mvdRPHOUKIXSMciqogOsZnvGXylMx6TegesGBWAeHFhTSQ5xXOrOEUsDHK78M3A0yFXzLE0XgwI90Tl87OHyWfE0y0yINv5PjxgGCLUB7mHYFpgPW1C5yyIkb2JA6CePE3hHv369khwmLumW7P9ErZhzdGgeskz6Os0p5HMrTFuySc0PWxsIfru1HldIH9TZTSMCbd91G5jCCikyx2zrzDKaasuQZyBGZcMjr1zcCLpPQiKT7ELSoUBCKhiFODxbFA06MC5bLXh2WvyP8W2kVxT2T4AnDX6pwf1BKs4nbHpAjvMmHrzlhQp7Q6VWBEiniY5M9QW4ExRcMGIBYXvY7vu5p";
+
+        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        final ZipOutputStream zipWriteStream = new ZipOutputStream(baos);
+
+        writeFileToZip(contents1, "file1", zipWriteStream);
+        writeFileToZip(contents2, "file2", zipWriteStream);
+        writeFileToZip(contents3, "file3", zipWriteStream);
+        zipWriteStream.close();
+
+        GBZipFile zipFile = new GBZipFile(baos.toByteArray());
+        String readContents2 = new String(zipFile.getFileFromZip("file2"));
+        String readContents1 = new String(zipFile.getFileFromZip("file1"));
+        String readContents3 = new String(zipFile.getFileFromZip("file3"));
+
+        Assert.assertEquals(contents1, readContents1);
+        Assert.assertEquals(contents2, readContents2);
+        Assert.assertEquals(contents3, readContents3);
+    }
+
+    @Test
+    public void testZipFilesFileExists() throws IOException, ZipFileException {
+        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        final ZipOutputStream zipWriteStream = new ZipOutputStream(baos);
+
+        writeFileToZip(TEST_FILE_CONTENTS_1, "file1", zipWriteStream);
+        writeFileToZip(TEST_FILE_CONTENTS_2, "file2", zipWriteStream);
+        writeFileToZip("Hello, World!", "folder1/file3", zipWriteStream);
+        zipWriteStream.close();
+
+        final GBZipFile zipFile = new GBZipFile(baos.toByteArray());
+        Assert.assertTrue(zipFile.fileExists("file2"));
+        Assert.assertTrue(zipFile.fileExists("file1"));
+        Assert.assertTrue(zipFile.fileExists("folder1/file3"));
+        Assert.assertFalse(zipFile.fileExists("folder1"));
+        Assert.assertFalse(zipFile.fileExists("file4"));
+    }
+
+    /**
+     * Create a ZIP archive with a single text file.
+     * The archive will not be saved to a file, it is kept in memory.
+     *
+     * @return the ZIP archive
+     */
+    private byte[] createZipArchive(String path, String fileContents) throws IOException {
+        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        final ZipOutputStream zipFile = new ZipOutputStream(baos);
+
+        writeFileToZip(fileContents, path, zipFile);
+        zipFile.close();
+
+        return baos.toByteArray();
+    }
+
+    /**
+     * Make a larger JSON object for testing purposes, based on a preexisting JSON object.
+     */
+    private JSONObject makeLargeJsonObject(JSONObject base, int repetitions) throws JSONException {
+        JSONObject manifestObj = base.getJSONObject("manifest");
+        JSONArray array = new JSONArray();
+
+        for (int i = 0; i < repetitions; i++) {
+            array.put(manifestObj);
+        }
+
+        return base.put("array", array);
+    }
+
+    /**
+     * Write given data to file at given path into an already opened ZIP archive.
+     * Allows to create an archive with multiple files.
+     */
+    private void writeFileToZip(String fileContents, String path, ZipOutputStream zipFile) throws IOException {
+        byte[] data = fileContents.getBytes(StandardCharsets.UTF_8);
+
+        ZipEntry zipEntry = new ZipEntry(path);
+        zipFile.putNextEntry(zipEntry);
+        zipFile.write(data, 0, fileContents.length());
+        zipFile.closeEntry();
+    }
+}
diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/test/HexToBinaryInputStream.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/test/HexToBinaryInputStream.java
new file mode 100644
index 0000000..8f75246
--- /dev/null
+++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/test/HexToBinaryInputStream.java
@@ -0,0 +1,52 @@
+package nodomain.freeyourgadget.gadgetbridge.test;
+
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import androidx.annotation.NonNull;
+
+class HexToBinaryInputStream extends FilterInputStream {
+    HexToBinaryInputStream(InputStream in) {
+        super(in);
+    }
+
+    @Override
+    public int read() throws IOException {
+        int value;
+        StringBuilder buffer = new StringBuilder(4);
+
+        loop:
+        while (true) {
+            value = super.read();
+            switch (value) {
+                case -1:
+                case ' ':
+                case '\r':
+                case '\n':
+                    break loop;
+                default:
+                    buffer.append((char) value);
+            }
+        }
+        if (buffer.length() > 0) {
+            return Integer.decode(buffer.toString());
+        }
+        return -1;
+    }
+
+    @Override
+    public int read(@NonNull byte[] b, int off, int len) throws IOException {
+        if (off < 0 || len < 0 || len > b.length - off) {
+            throw new IndexOutOfBoundsException();
+        }
+        for (int i = 0; i < len; i++) {
+            int value = read();
+            if (value == -1) {
+                return i;
+            }
+            b[off + i] = (byte) value;
+        }
+        return len;
+    }
+}
diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/test/HexToBinaryInputStreamTest.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/test/HexToBinaryInputStreamTest.java
new file mode 100644
index 0000000..f879ea3
--- /dev/null
+++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/test/HexToBinaryInputStreamTest.java
@@ -0,0 +1,50 @@
+package nodomain.freeyourgadget.gadgetbridge.test;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+
+import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
+
+import static org.junit.Assert.assertTrue;
+
+public class HexToBinaryInputStreamTest extends TestBase {
+
+    @Test
+    public void testConversion() throws IOException {
+        byte[] hexString;
+        byte[] binString;
+
+        try (InputStream in = HuamiActivityDetailsParserTest.class.getClassLoader().getResourceAsStream("ActivityDetailsDump1.txt")) {
+            hexString = FileUtils.readAll(in, 1024 * 1024);
+            assertTrue(hexString.length > 1);
+            try (InputStream in2 = getContents(HuamiActivityDetailsParserTest.class.getClassLoader().getResource("ActivityDetailsDump1.txt"))) {
+                binString = FileUtils.readAll(in2, 1024 * 1024);
+                assertTrue(binString.length > 1);
+            }
+        }
+        Assert.assertTrue(hexString.length > binString.length);
+        ByteArrayOutputStream binToHexOut = new ByteArrayOutputStream(hexString.length);
+        for (int i = 0; i < binString.length; i++) {
+            String hexed = String.format("0x%x", binString[i]);
+            binToHexOut.write(hexed.getBytes("US-ASCII"));
+            if ((i + 1) % 17 == 0) {
+                binToHexOut.write('\n');
+            } else {
+                binToHexOut.write(' ');
+            }
+        }
+
+        byte[] hexedBytes = binToHexOut.toByteArray();
+        Assert.assertArrayEquals(hexString, hexedBytes);
+    }
+
+    private InputStream getContents(URL hexFile) throws IOException {
+        return new HexToBinaryInputStream(hexFile.openStream());
+    }
+
+}
diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/test/HuamiActivityDetailsParserTest.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/test/HuamiActivityDetailsParserTest.java
new file mode 100644
index 0000000..2abca82
--- /dev/null
+++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/test/HuamiActivityDetailsParserTest.java
@@ -0,0 +1,107 @@
+package nodomain.freeyourgadget.gadgetbridge.test;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.Date;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.amazfitbip.BipActivitySummary;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.entities.User;
+import nodomain.freeyourgadget.gadgetbridge.export.GPXExporter;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityPoint;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityTrack;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiActivityDetailsParser;
+import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+public class HuamiActivityDetailsParserTest extends TestBase {
+    private static final URL DETAILS_1 = HuamiActivityDetailsParserTest.class.getClassLoader().getResource("ActivityDetailsDump1.txt");
+    private static final long MAX_DETAILS = 1024 * 1024;
+    private static Date baseTime;
+
+    @BeforeClass
+    public static void setUpSuite() throws Exception {
+        baseTime = DateTimeUtils.ISO_8601_FORMAT.parse("2017-01-20T14:00:00-00:00"); // yyyy-mm-dd'T'hh:mm:ssZ
+    }
+
+    @Test
+    public void testActivityDetails() throws Exception {
+        BipActivitySummary summary = createSummary();
+
+        HuamiActivityDetailsParser parser = new HuamiActivityDetailsParser(summary);
+        parser.setSkipCounterByte(true);
+        try (InputStream in = getContents(DETAILS_1)) {
+            ActivityTrack track = parser.parse(FileUtils.readAll(in, MAX_DETAILS));
+            assertEquals("SuperBand 2000", track.getDevice().getName());
+            assertEquals("Elvis", track.getUser().getName());
+
+            List<List<ActivityPoint>> segments = track.getSegments();
+            assertEquals(1, segments.size());
+            assertEquals(972, segments.get(0).size());
+        }
+    }
+
+    private BipActivitySummary createSummary() {
+        BipActivitySummary summary = new BipActivitySummary();
+        summary.setBaseLongitude(1);
+        summary.setBaseLatitude(1);
+        summary.setBaseAltitude(1);
+        summary.setStartTime(baseTime);
+        User dummyUser = new User(0L);
+        dummyUser.setName("Elvis");
+        summary.setName("testtrack");
+        summary.setUser(dummyUser);
+        Device device = new Device(0l);
+        device.setName("SuperBand 2000");
+        summary.setDevice(device);
+
+        return summary;
+    }
+
+    @Test
+    public void testGPXExport() throws Exception {
+        BipActivitySummary summary = createSummary();
+
+        int baseLongi = BLETypeConversions.toUint32((byte) 0xd6, (byte) 0xc4,(byte) 0x62,(byte) 0x02);
+        int baseLati = BLETypeConversions.toUint32((byte) 0xff, (byte) 0xa9, (byte) 0x61, (byte) 0x9);
+        int baseAlti = BLETypeConversions.toUint32((byte) 0x30, (byte) 0x0, (byte) 0x0, (byte) 0x0);
+
+        summary.setBaseLongitude(baseLongi);
+        summary.setBaseLatitude(baseLati);
+        summary.setBaseAltitude(baseAlti);
+
+        HuamiActivityDetailsParser parser = new HuamiActivityDetailsParser(summary);
+        parser.setSkipCounterByte(true);
+        try (InputStream in = getContents(DETAILS_1)) {
+            ActivityTrack track = parser.parse(FileUtils.readAll(in, MAX_DETAILS));
+
+            List<List<ActivityPoint>> segments = track.getSegments();
+            assertEquals(1, segments.size());
+            assertEquals(972, segments.get(0).size());
+
+            GPXExporter exporter = new GPXExporter();
+            exporter.setIncludeHeartRate(false);
+            exporter.setCreator(getClass().getName());
+            File targetFile = File.createTempFile("gadgetbridge-track", ".gpx");
+            System.out.println("Writing GPX file: " + targetFile);
+            exporter.performExport(track, targetFile);
+
+            assertTrue(targetFile.length() > 1024);
+        }
+
+    }
+
+    private InputStream getContents(URL hexFile) throws IOException {
+        return new HexToBinaryInputStream(hexFile.openStream());
+    }
+}
diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/test/LoggingTest.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/test/LoggingTest.java
new file mode 100644
index 0000000..fb83bef
--- /dev/null
+++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/test/LoggingTest.java
@@ -0,0 +1,82 @@
+package nodomain.freeyourgadget.gadgetbridge.test;
+
+import org.junit.After;
+import org.junit.Test;
+
+import java.io.File;
+
+import androidx.annotation.NonNull;
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.Logging;
+import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * Tests dynamic enablement and disablement of file appenders.
+ */
+public class LoggingTest extends TestBase {
+
+    public LoggingTest() {
+    }
+
+    private Logging logging = GBApplication.getLogging();
+
+    @Override
+    @After
+    public void tearDown() throws Exception {
+        super.tearDown();
+        assertTrue(FileUtils.deleteRecursively(getLogFilesDir()));
+    }
+
+    @NonNull
+    private File getLogFilesDir() {
+        String dirName = System.getProperty(Logging.PROP_LOGFILES_DIR);
+        if (dirName != null && dirName.length() > 5) {
+            return new File(dirName);
+        }
+        fail("Property " + Logging.PROP_LOGFILES_DIR + " has invalid value: " + dirName);
+        return null; // not reached
+    }
+
+    @Test
+    public void testToggleLogging() {
+        try {
+            getLogFilesDir();
+        } catch (AssertionError ignored) {
+            // expected, as not yet set up
+        }
+
+        try {
+            logging.setupLogging(true);
+            File dir = getLogFilesDir();
+            assertEquals(1, dir.list().length);
+            assertNotNull(logging.getFileLogger());
+            assertTrue(logging.getFileLogger().isStarted());
+
+            logging.setupLogging(false);
+            assertNull(logging.getFileLogger());
+
+            logging.setupLogging(true);
+            assertNotNull(logging.getFileLogger());
+            assertTrue(logging.getFileLogger().isStarted());
+        } catch (AssertionError ex) {
+            logging.debugLoggingConfiguration();
+            System.err.println(System.getProperty("java.class.path"));
+            throw ex;
+        }
+    }
+
+    @Test
+    public void testLogFormat() {
+        String tempOut = Logging.formatBytes(new byte[] {0xa});
+        assertEquals("0x0a", tempOut);
+
+        tempOut = Logging.formatBytes(new byte[] {0xa, 1, (byte) 255});
+        assertEquals("0x0a 0x01 0xff", tempOut);
+    }
+}
diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/test/MiscTest.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/test/MiscTest.java
new file mode 100644
index 0000000..9c49976
--- /dev/null
+++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/test/MiscTest.java
@@ -0,0 +1,14 @@
+package nodomain.freeyourgadget.gadgetbridge.test;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import nodomain.freeyourgadget.gadgetbridge.service.btle.GattCharacteristic;
+
+public class MiscTest extends TestBase {
+    @Test
+    public void testGattCharacteristic() {
+        String desc = GattCharacteristic.lookup(GattCharacteristic.UUID_CHARACTERISTIC_HEART_RATE_CONTROL_POINT, "xxx");
+        Assert.assertEquals("heart rate control point", desc);
+    }
+}
diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/test/PebbleUtilsTest.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/test/PebbleUtilsTest.java
new file mode 100644
index 0000000..6fce5d4
--- /dev/null
+++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/test/PebbleUtilsTest.java
@@ -0,0 +1,54 @@
+package nodomain.freeyourgadget.gadgetbridge.test;
+
+import org.junit.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.pebble.PebbleColor;
+import nodomain.freeyourgadget.gadgetbridge.util.PebbleUtils;
+
+import static org.junit.Assert.assertEquals;
+
+public class PebbleUtilsTest extends TestBase {
+    @Test
+    public void testHexToPebbleColorConversion() {
+        Map<String, Byte> testCases = new HashMap<>();
+
+        testCases.put("#000000", PebbleColor.Black);
+        testCases.put("#ffffff", PebbleColor.White);
+        testCases.put("#00ff00", PebbleColor.Green);
+
+        testCases.put("#452435", PebbleColor.Black);
+        testCases.put("#334afd", PebbleColor.DukeBlue);
+        testCases.put("#ccb75c", PebbleColor.Brass);
+        testCases.put("#1b1c94", PebbleColor.OxfordBlue);
+        testCases.put("#90f892", PebbleColor.MayGreen);
+        testCases.put("#ff7301", PebbleColor.Orange);
+
+        testCases.put("#00aa00", PebbleColor.IslamicGreen);
+
+        for (String colorKey : testCases.keySet()) {
+            byte evaluatedColor = PebbleUtils.getPebbleColor(colorKey);
+            assertEquals("Color " + colorKey + " failed to translate properly!",
+                    testCases.get(colorKey).byteValue(), evaluatedColor);
+        }
+    }
+
+    @Test
+    public void testIntToPebbleColorConversion() {
+        Map<Integer, Byte> testCases = new HashMap<>();
+
+        testCases.put(0x000000, PebbleColor.Black);
+        testCases.put(0xffffff, PebbleColor.White);
+        testCases.put(0x00ff00, PebbleColor.Green);
+
+        testCases.put(0x00aa00, PebbleColor.IslamicGreen);
+
+        for (int colorKey : testCases.keySet()) {
+            byte evaluatedColor = PebbleUtils.getPebbleColor(colorKey);
+            assertEquals("Color " + Integer.toHexString(colorKey) + " failed to translate properly!",
+                    testCases.get(colorKey).byteValue(), evaluatedColor);
+        }
+    }
+}
diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/test/ProtobufTest.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/test/ProtobufTest.java
new file mode 100644
index 0000000..34a37a1
--- /dev/null
+++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/test/ProtobufTest.java
@@ -0,0 +1,129 @@
+package nodomain.freeyourgadget.gadgetbridge.test;
+
+import junit.framework.TestCase;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.protobuf.messagefields.NestedMessageField;
+import nodomain.freeyourgadget.gadgetbridge.util.protobuf.messagefields.RootMessageField;
+import nodomain.freeyourgadget.gadgetbridge.util.protobuf.messagefields.StringMessageField;
+import nodomain.freeyourgadget.gadgetbridge.util.protobuf.messagefields.VarintMessageField;
+import nodomain.freeyourgadget.gadgetbridge.util.protobuf.ProtobufUtils;
+
+public class ProtobufTest extends TestCase {
+    private void assertEncodeVarint(byte[] expected, int valueToEncode){
+        byte[] encoded = ProtobufUtils.encode_varint(valueToEncode);
+        assertEquals(expected.length, encoded.length);
+
+        for(int i = 0; i < expected.length; i++){
+            assertEquals(expected[i], encoded[i]);
+        }
+    }
+
+    public void testEncodeVarint(){
+        assertEncodeVarint(new byte[]{0x00}, 0);
+        assertEncodeVarint(new byte[]{0x01}, 1);
+        assertEncodeVarint(new byte[]{0b1111111}, 0b1111111);
+        assertEncodeVarint(new byte[]{(byte)0b11111111, 0b00000001}, 0b11111111);
+        assertEncodeVarint(new byte[]{(byte)0xeb, (byte)0x86, (byte)0x4e}, 1278827);
+        assertEncodeVarint(new byte[]{(byte)0xf9, (byte) 0xa5 , (byte) 0x3c}, 987897);
+        assertEncodeVarint(new byte[]{(byte) 0xfd, (byte) 0xe5, (byte) 0x90, (byte) 0x01}, 2372349);
+    }
+
+    private void assertEncodeVarintMessageField(byte[] expected, int fieldId, int value) throws IOException {
+        VarintMessageField field = new VarintMessageField(fieldId, value);
+        ByteArrayOutputStream os = new ByteArrayOutputStream();
+        field.encode(os);
+        byte[] result = os.toByteArray();
+
+        assertEquals(expected.length, result.length);
+
+        for(int i = 0; i < expected.length; i++){
+            assertEquals(expected[i], result[i]);
+        }
+    }
+
+    public void testEncodeVarintMessageField() throws IOException {
+        assertEncodeVarintMessageField(new byte[]{0b00001000, 0b00000000}, 1, 0);
+        assertEncodeVarintMessageField(new byte[]{(byte) 0b10000000, 0b00000001, 0b01111111}, 16, 127);
+        assertEncodeVarintMessageField(new byte[]{(byte) 0b10000000, 0b00000001, (byte) 0xfd, (byte) 0xe5, (byte) 0x90, (byte) 0x01}, 16, 2372349);
+    }
+
+    private void assertEncodeStringMessageField(byte[] expected, int fieldId, String value) throws IOException {
+        StringMessageField field = new StringMessageField(fieldId, value);
+        ByteArrayOutputStream os = new ByteArrayOutputStream();
+        field.encode(os);
+        byte[] result = os.toByteArray();
+
+        assertEquals(expected.length, result.length);
+
+        for(int i = 0; i < expected.length; i++){
+            assertEquals(expected[i], result[i]);
+        }
+    }
+
+    private void assertEncodeRootMessageField(byte[] expected, RootMessageField value) throws IOException {
+        byte[] result = value.encodeToBytes();
+        
+        System.out.println(StringUtils.bytesToHex(result));
+
+        assertEquals(expected.length, result.length);
+
+        for(int i = 0; i < expected.length; i++){
+            assertEquals(expected[i], result[i]);
+        }
+    }
+
+    public void testEncodeStringMessageField() throws IOException {
+        assertEncodeStringMessageField(new byte[]{0b00001010, 0b00000000}, 1, "");
+        assertEncodeStringMessageField(new byte[]{0b01010010, 0x04, 0x74, 0x65, 0x73, 0x74}, 10, "test");
+    }
+
+    public void testNestedMessageField() throws IOException {
+        RootMessageField startAppRequest = new RootMessageField(
+                new VarintMessageField(1, 2),
+                new NestedMessageField(16,
+                        new StringMessageField(1, "Infrared"),
+                        new StringMessageField(2, "RPC")
+                )
+        );
+        assertEncodeRootMessageField(
+                new byte[]{(byte) 0x14, (byte) 0x08, (byte) 0x02, (byte) 0x82, (byte) 0x01, (byte) 0x0f, (byte) 0x0a, (byte) 0x08, (byte) 0x49, (byte) 0x6e, (byte) 0x66, (byte) 0x72, (byte) 0x61, (byte) 0x72, (byte) 0x65, (byte) 0x64, (byte) 0x12, (byte) 0x03, (byte) 0x52, (byte) 0x50, (byte) 0x43},
+                startAppRequest
+        );
+
+        RootMessageField loadFileRequest = new RootMessageField(
+                new VarintMessageField(1, 3),
+                new NestedMessageField(48,
+                        new StringMessageField(1, "/any/infrared/Remote.ir")
+                )
+        );
+        assertEncodeRootMessageField(
+                new byte[]{(byte) 0x1e, (byte) 0x08, (byte) 0x03, (byte) 0x82, (byte) 0x03, (byte) 0x19, (byte) 0x0a, (byte) 0x17, (byte) 0x2f, (byte) 0x61, (byte) 0x6e, (byte) 0x79, (byte) 0x2f, (byte) 0x69, (byte) 0x6e, (byte) 0x66, (byte) 0x72, (byte) 0x61, (byte) 0x72, (byte) 0x65, (byte) 0x64, (byte) 0x2f, (byte) 0x52, (byte) 0x65, (byte) 0x6d, (byte) 0x6f, (byte) 0x74, (byte) 0x65, (byte) 0x2e, (byte) 0x69, (byte) 0x72},
+                loadFileRequest
+        );
+
+        RootMessageField pressButtonRequest = new RootMessageField(
+                new VarintMessageField(1, 4),
+                new NestedMessageField(49,
+                        new StringMessageField(1, "Pwr")
+                )
+        );
+        assertEncodeRootMessageField(new byte[]{(byte) 0x0a, (byte) 0x08, (byte) 0x04, (byte) 0x8a, (byte) 0x03, (byte) 0x05, (byte) 0x0a, (byte) 0x03, (byte) 0x50, (byte) 0x77, (byte) 0x72}, pressButtonRequest);
+
+        RootMessageField releaseButtonRequest = new RootMessageField(
+                new VarintMessageField(1, 5),
+                new NestedMessageField(50)
+        );
+        assertEncodeRootMessageField(new byte[]{(byte) 0x05, (byte) 0x08, (byte) 0x05, (byte) 0x92, (byte) 0x03, (byte) 0x00}, releaseButtonRequest);
+
+        RootMessageField exitAppRequest = new RootMessageField(
+                new VarintMessageField(1, 6),
+                new NestedMessageField(47)
+        );
+        assertEncodeRootMessageField(new byte[]{(byte) 0x05, (byte) 0x08, (byte) 0x06, (byte) 0xfa, (byte) 0x02, (byte) 0x00}, exitAppRequest);
+
+    }
+}
diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/test/RtlUtilsTest.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/test/RtlUtilsTest.java
new file mode 100644
index 0000000..910c145
--- /dev/null
+++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/test/RtlUtilsTest.java
@@ -0,0 +1,369 @@
+package nodomain.freeyourgadget.gadgetbridge.test;
+
+import android.content.SharedPreferences;
+
+import org.junit.Test;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.util.GBPrefs;
+import nodomain.freeyourgadget.gadgetbridge.util.RtlUtils;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Tests RtlUtils
+ */
+public class RtlUtilsTest extends TestBase {
+
+//    @Test
+    private Character hebrew1 = '讗';
+    private Character hebrew2 = '诐';
+
+    private Character arabic1 = '丕';
+    private Character arabic2 = '賳';
+
+    private Character english1 = 'a';
+    private Character english2 = 'N';
+
+    private Character punctuation1 = '.';
+    private Character punctuation2 = '-';
+
+    private Character space = ' ';
+
+    private Character endLine1 = '\0';
+    private Character endLine2 = '\n';
+
+    private Character[] contextualIsolated = {'亍', '\uFE80'};
+    private Character[] contextualBeginning = {'爻', '\uFEB3'};
+    private Character[] contextualMiddle = {'囟', '\uFEC0'};
+    private Character[] contextualEndNoMiddle = {'丌', '\uFE82'};
+    private Character[] contextualLam = {(char)('賱' + '兀'), '\uFEF8'};
+
+    private String hebrewWord1 = "砖诇讜诐";
+    private String hebrewWord2 = "讜讘专讻讛";
+    private String hebrewPhrase = hebrewWord1 + " " + hebrewWord2;
+
+    private String arabicWord1 = "爻賱丕賲";
+    private String arabicWord2 = "丕賱亘乇賰丞";
+    private String arabicPhrase = arabicWord1 + " " + arabicWord2;
+
+    private String englishWord1 = "Hello";
+    private String englishWord2 = "Welcome";
+    private String englishPhrase = englishWord1 + " and " + englishWord2;
+
+    @Test
+    public void testGetCharacterType() throws Exception {
+
+        RtlUtils.characterType actual = RtlUtils.getCharacterType(hebrew1);
+        RtlUtils.characterType expected = RtlUtils.characterType.rtl;
+        assertEquals("Get Hebrew Character type failed", expected, actual);
+
+        actual = RtlUtils.getCharacterType(arabic1);
+        expected = RtlUtils.characterType.rtl_arabic;
+        assertEquals("Get Arabic Character type failed", expected, actual);
+
+        actual = RtlUtils.getCharacterType(english1);
+        expected = RtlUtils.characterType.ltr;
+        assertEquals("Get Hebrew Character type failed", expected, actual);
+
+        actual = RtlUtils.getCharacterType(' ');
+        expected = RtlUtils.characterType.space;
+        assertEquals("Get Hebrew Character type failed", expected, actual);
+    }
+
+
+    @Test
+    public void testIsHebrew() throws Exception {
+        boolean result = RtlUtils.isHebrew(hebrew1);
+        assertTrue("Is Hebrew Character failed", result);
+
+        result = RtlUtils.isHebrew(hebrew2);
+        assertTrue("Is Hebrew Character failed", result);
+
+        result = RtlUtils.isHebrew(arabic1);
+        assertFalse("Is Hebrew Character failed", result);
+
+        result = RtlUtils.isHebrew(english1);
+        assertFalse("Is Hebrew Character failed", result);
+    }
+
+    @Test
+    public void testIsArabic() throws Exception {
+        boolean result = RtlUtils.isArabic(arabic1);
+        assertTrue("Is Arabic Character failed", result);
+
+        result = RtlUtils.isArabic(arabic2);
+        assertTrue("Is Arabic Character failed", result);
+
+        result = RtlUtils.isArabic(hebrew1);
+        assertFalse("Is Arabic Character failed", result);
+
+        result = RtlUtils.isArabic(english1);
+        assertFalse("Is Arabic Character failed", result);
+    }
+
+    @Test
+    public void testIsLtr() throws Exception {
+        boolean result = RtlUtils.isLtr(english1);
+        assertTrue("Is Ltr Character failed", result);
+
+        result = RtlUtils.isLtr(english2);
+        assertTrue("Is Ltr Character failed", result);
+
+        result = RtlUtils.isLtr(hebrew1);
+        assertFalse("Is Ltr Character failed", result);
+
+        result = RtlUtils.isLtr(arabic1);
+        assertFalse("Is Ltr Character failed", result);
+    }
+
+    @Test
+    public void testIsRtl() throws Exception {
+        boolean result = RtlUtils.isRtl(hebrew1);
+        assertTrue("Is Rtl Character failed", result);
+
+        result = RtlUtils.isRtl(arabic1);
+        assertTrue("Is Rtl Character failed", result);
+
+        result = RtlUtils.isRtl(english1);
+        assertFalse("Is Rtl Character failed", result);
+
+        result = RtlUtils.isRtl(punctuation1);
+        assertFalse("Is Rtl Character failed", result);
+    }
+
+    @Test
+    public void testIsPunctuation() throws Exception {
+        boolean result = RtlUtils.isPunctuations(punctuation1);
+        assertTrue("Is Punctuation Character failed", result);
+
+        result = RtlUtils.isPunctuations(punctuation2);
+        assertTrue("Is Punctuation Character failed", result);
+
+        result = RtlUtils.isPunctuations(english1);
+        assertFalse("Is Punctuation Character failed", result);
+
+        result = RtlUtils.isPunctuations(space);
+        assertFalse("Is Punctuation Character failed", result);
+    }
+
+    @Test
+    public void testIsSpaceSign() throws Exception {
+        boolean result = RtlUtils.isSpaceSign(space);
+        assertTrue("Is Space Sign Character failed", result);
+
+        result = RtlUtils.isSpaceSign(punctuation1);
+        assertFalse("Is Space Sign Character failed", result);
+
+        result = RtlUtils.isSpaceSign(english1);
+        assertFalse("Is SpaceSign Character failed", result);
+
+        result = RtlUtils.isSpaceSign(hebrew1);
+        assertFalse("Is SpaceSign Character failed", result);
+    }
+
+    @Test
+    public void testIsEndLineSign() throws Exception {
+        boolean result = RtlUtils.isEndLineSign(endLine1);
+        assertTrue("Is End Line Sign Character failed", result);
+
+        result = RtlUtils.isEndLineSign(endLine2);
+        assertTrue("Is End Line Sign Character failed", result);
+
+        result = RtlUtils.isEndLineSign(english1);
+        assertFalse("Is End Line Sign Character failed", result);
+
+        result = RtlUtils.isEndLineSign(space);
+        assertFalse("Is End Line Sign Character failed", result);
+    }
+
+    @Test
+    public void testExceptionAfterLam() throws Exception {
+        boolean result = RtlUtils.exceptionAfterLam('\u0623');
+        assertTrue("Is Exception After Lam failed", result);
+
+        result = RtlUtils.exceptionAfterLam('\u0630');
+        assertFalse("Is Exception After Lam failed", result);
+
+        result = RtlUtils.exceptionAfterLam('\u062A');
+        assertFalse("Is Exception After Lam failed", result);
+    }
+
+
+    @Test
+    public void testGetContextualSymbol() throws Exception {
+
+        Character actual = RtlUtils.getContextualSymbol(contextualIsolated[0], RtlUtils.contextualState.isolate);
+        Character expected = contextualIsolated[1];
+        assertEquals("Get Contextual Symbol failed", expected, actual);
+
+        actual = RtlUtils.getContextualSymbol(contextualBeginning[0], RtlUtils.contextualState.begin);
+        expected = contextualBeginning[1];
+        assertEquals("Get Contextual Symbol failed", expected, actual);
+
+        actual = RtlUtils.getContextualSymbol(contextualMiddle[0], RtlUtils.contextualState.middle);
+        expected = contextualMiddle[1];
+        assertEquals("Get Contextual Symbol failed", expected, actual);
+
+        actual = RtlUtils.getContextualSymbol(contextualEndNoMiddle[0], RtlUtils.contextualState.end);
+        expected = contextualEndNoMiddle[1];
+        assertEquals("Get Contextual Symbol failed", expected, actual);
+
+        actual = RtlUtils.getContextualSymbol(contextualEndNoMiddle[0], RtlUtils.contextualState.middle);
+        expected = contextualEndNoMiddle[0];
+        assertEquals("Get Contextual Symbol failed", expected, actual);
+    }
+
+    @Test
+    public void testGetCharContextualState() throws Exception {
+
+        RtlUtils.contextualState actual = RtlUtils.getCharContextualState(RtlUtils.contextualState.isolate, contextualBeginning[0], contextualEndNoMiddle[0]);
+        RtlUtils.contextualState expected = RtlUtils.contextualState.begin;
+        assertEquals("Get Char Contextual State failed", expected, actual);
+
+        actual = RtlUtils.getCharContextualState(RtlUtils.contextualState.begin, contextualMiddle[0], contextualEndNoMiddle[0]);
+        expected = RtlUtils.contextualState.middle;
+        assertEquals("Get Char Contextual State failed", expected, actual);
+
+        actual = RtlUtils.getCharContextualState(RtlUtils.contextualState.begin, contextualMiddle[0], contextualIsolated[0]);
+        expected = RtlUtils.contextualState.end;
+        assertEquals("Get Char Contextual State failed", expected, actual);
+
+        actual = RtlUtils.getCharContextualState(RtlUtils.contextualState.begin, contextualIsolated[0], contextualEndNoMiddle[0]);
+        expected = RtlUtils.contextualState.isolate;
+        assertEquals("Get Char Contextual State failed", expected, actual);
+    }
+
+    @Test
+    public void testConvertToContextual() throws Exception {
+
+        String actual = RtlUtils.convertToContextual("");
+        String expected = "";
+        assertEquals("Convert To Contextual failed", expected, actual);
+
+        char[] nonContextual = {'噩', '噩', '噩'};
+        char[] contextual = {'\uFE9F', '\uFEA0', '\uFE9E'};
+        actual = RtlUtils.convertToContextual(new StringBuilder().append(nonContextual).toString());
+        expected = new StringBuilder().append(contextual).toString();
+        assertEquals("Convert To Contextual failed", expected, actual);
+
+        char[] nonContextual2 = {'噩', '噩', '賱', '\u0622','噩'};
+        char[] contextual2 = {'\uFE9F', '\uFEA0', '\uFEF6', '\uFE9D'};
+        actual = RtlUtils.convertToContextual(new StringBuilder().append(nonContextual2).toString());
+        expected = new StringBuilder().append(contextual2).toString();
+        assertEquals("Convert To Contextual failed", expected, actual);
+    }
+
+    @Test
+    public void testReverse() throws Exception {
+
+        String actual = RtlUtils.reverse(hebrewWord1);
+        String expected = "诐讜诇砖";
+        assertEquals("Reverse failed", expected, actual);
+
+        actual = RtlUtils.reverse(hebrewPhrase);
+        expected = "讛讻专讘讜 诐讜诇砖";
+        assertEquals("Reverse failed", expected, actual);
+
+        actual = RtlUtils.reverse("讟拽住讟 注诐 (住讜讙专讬讬诐) 讜住讬诪谉 砖讗诇讛?");
+        expected = "?讛诇讗砖 谉诪讬住讜 (诐讬讬专讙讜住) 诐注 讟住拽讟";
+        assertEquals("Reverse failed", expected, actual);
+    }
+
+    @Test
+    public void testFixWhitespace() throws Exception {
+
+        String actual = RtlUtils.fixWhitespace(englishPhrase + " ");
+        String expected = " " + englishPhrase;
+        assertEquals("fix whitespace failed", expected, actual);
+
+        actual = RtlUtils.fixWhitespace(englishPhrase);
+        expected = englishPhrase;
+        assertEquals("fix whitespace failed", expected, actual);
+    }
+
+    @Test
+    public void testFixRtl() throws Exception {
+
+        String actual = RtlUtils.fixRtl("Only english");
+        String expected = "Only english";
+        assertEquals("fix rtl failed", expected, actual);
+
+        actual = RtlUtils.fixRtl("Only very long english with more than 18 characters");
+        expected = " Only very long\n" +
+                " english with more\n" +
+                " than 18\n" +
+                "characters";
+        assertEquals("fix rtl failed", expected, actual);
+
+        actual = RtlUtils.fixRtl("专拽 注讘专讬转");
+        expected = "转讬专讘注 拽专";
+        assertEquals("fix rtl failed", expected, actual);
+
+        actual = RtlUtils.fixRtl("English and 注讘专讬转");
+        expected = "转讬专讘注 English and";
+        assertEquals("fix rtl failed", expected, actual);
+
+        actual = RtlUtils.fixRtl("诪砖驻讟 讗专讜讱 注诐 注讘专讬转 and English 讜讙诐 拽爪转 住讬诪谞讬诐 讻诪讜 ?!$ (讜讙诐 ^.)");
+        expected = " 诐注 讱讜专讗 讟驻砖诪\n" +
+                " and English 转讬专讘注\n" +
+                " 诐讬谞诪讬住 转爪拽 诐讙讜\n" +
+                "(.^ 诐讙讜) $!? 讜诪讻";
+        assertEquals("fix rtl failed", expected, actual);
+
+
+
+        actual = RtlUtils.fixRtl("诪砖驻讟 注诐 讻讜转专转\0讜砖诪讻讬诇 讙诐 讬专讬讚转\n砖讜专讛");
+        expected = "转专转讜讻 诐注 讟驻砖诪\0转讚讬专讬 诐讙 诇讬讻诪砖讜\n" +
+                "讛专讜砖";
+        assertEquals("fix rtl failed", expected, actual);
+    }
+
+    @Test
+    public void testRtlSupport() throws Exception {
+        setDefaultRtl();
+        assertFalse("Rtl option fail! Expected 'Off' by default, but result is 'On'", RtlUtils.rtlSupport());
+
+        enableRtl(true);
+        assertTrue("Rtl option fail! Expected 'On', but result is 'Off'", RtlUtils.rtlSupport());
+    }
+
+    private void setDefaultRtl() {
+        SharedPreferences settings = GBApplication.getPrefs().getPreferences();
+        SharedPreferences.Editor editor = settings.edit();
+        editor.remove(GBPrefs.RTL_SUPPORT);
+        editor.apply();
+    }
+
+    private void enableRtl(boolean enable) {
+        SharedPreferences settings = GBApplication.getPrefs().getPreferences();
+        SharedPreferences.Editor editor = settings.edit();
+        editor.putBoolean(GBPrefs.RTL_SUPPORT, enable);
+        editor.apply();
+    }
+
+    @Test
+    public void testContextualSupport() throws Exception {
+        setDefaultContextual();
+        assertFalse("Contextual option fail! Expected 'Off' by default, but result is 'On'", RtlUtils.contextualSupport());
+
+        enableContextual(true);
+        assertTrue("Contextual option fail! Expected 'On', but result is 'Off'", RtlUtils.contextualSupport());
+    }
+
+    private void setDefaultContextual() {
+        SharedPreferences settings = GBApplication.getPrefs().getPreferences();
+        SharedPreferences.Editor editor = settings.edit();
+        editor.remove(GBPrefs.RTL_CONTEXTUAL_ARABIC);
+        editor.apply();
+    }
+
+    private void enableContextual(boolean enable) {
+        SharedPreferences settings = GBApplication.getPrefs().getPreferences();
+        SharedPreferences.Editor editor = settings.edit();
+        editor.putBoolean(GBPrefs.RTL_CONTEXTUAL_ARABIC, enable);
+        editor.apply();
+    }
+}
diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/test/StringUtilsTest.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/test/StringUtilsTest.java
new file mode 100644
index 0000000..8ce331f
--- /dev/null
+++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/test/StringUtilsTest.java
@@ -0,0 +1,70 @@
+package nodomain.freeyourgadget.gadgetbridge.test;
+
+import org.junit.Test;
+
+import java.nio.charset.StandardCharsets;
+
+import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+public class StringUtilsTest extends TestBase {
+    private static final String SEP = ":";
+    private static final String E1 = "e1";
+    private static final String E2 = "e2";
+    private static final String E3 = "e3";
+
+    @Test
+    public void testJoinNull() {
+        StringBuilder result = StringUtils.join(SEP, (String[]) null);
+        assertEquals("", result.toString());
+    }
+
+    @Test
+    public void testJoinNullElement() {
+        StringBuilder result = StringUtils.join(SEP, (String) null);
+        assertEquals("", result.toString());
+    }
+
+    @Test
+    public void testJoinSingleElement() {
+        StringBuilder result = StringUtils.join(SEP, E1);
+        assertEquals(E1, result.toString());
+    }
+
+    @Test
+    public void testJoinSingleAndNullElement() {
+        StringBuilder result = StringUtils.join(SEP, E1, null);
+        assertEquals(E1, result.toString());
+    }
+
+    @Test
+    public void testJoinTwoElements() {
+        StringBuilder result = StringUtils.join(SEP, E1, E2);
+        assertEquals(E1 + SEP + E2, result.toString());
+    }
+
+    @Test
+    public void testJoinTwoElementsAndNull() {
+        StringBuilder result = StringUtils.join(SEP, E1, null, E2);
+        assertEquals(E1 + SEP + E2, result.toString());
+    }
+
+    @Test
+    public void testJoinThreeElements() {
+        StringBuilder result = StringUtils.join(SEP, E1, E2, E3);
+        assertEquals(E1 + SEP + E2 + SEP + E3, result.toString());
+    }
+
+    @Test
+    public void testUntilNullTerminator() {
+        assertNull(StringUtils.untilNullTerminator("Hello, World!".getBytes(StandardCharsets.UTF_8), 7));
+        assertNull(StringUtils.untilNullTerminator("Hello, World!".getBytes(StandardCharsets.UTF_8), 99));
+        assertEquals("Hello, World!", StringUtils.untilNullTerminator("Hello, World!\0Another String".getBytes(StandardCharsets.UTF_8), 0));
+        assertEquals("World!", StringUtils.untilNullTerminator("Hello, World!\0Another String".getBytes(StandardCharsets.UTF_8), 7));
+        assertEquals("", StringUtils.untilNullTerminator("Hello, World!\0Another String".getBytes(StandardCharsets.UTF_8), 13));
+        assertNull(StringUtils.untilNullTerminator("Hello, World!\0Another String".getBytes(StandardCharsets.UTF_8), 14));
+        assertNull(StringUtils.untilNullTerminator("Hello, World!\0Another String".getBytes(StandardCharsets.UTF_8), 99));
+    }
+}
diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/test/TestBase.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/test/TestBase.java
new file mode 100644
index 0000000..703c018
--- /dev/null
+++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/test/TestBase.java
@@ -0,0 +1,95 @@
+package nodomain.freeyourgadget.gadgetbridge.test;
+
+import android.content.Context;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+import java.io.File;
+
+import ch.qos.logback.classic.util.ContextInitializer;
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.GBEnvironment;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
+
+import static org.junit.Assert.assertNotNull;
+import static nodomain.freeyourgadget.gadgetbridge.Logging.PROP_LOGFILES_DIR;
+
+/**
+ * Base class for all testcases in Gadgetbridge that are supposed to run locally
+ * with robolectric.
+ *
+ * Important: To run them, create a run configuration and execute them in the Gadgetbridge/app/
+ * directory.
+ */
+@RunWith(RobolectricTestRunner.class)
+@Config(sdk = 21)
+public abstract class TestBase {
+    protected static File logFilesDir;
+
+    protected GBApplication app = (GBApplication) RuntimeEnvironment.application;
+    protected DaoSession daoSession;
+    protected DBHandler dbHandler;
+
+    // Make sure logging is set up for all testcases, so that we can debug problems
+    @BeforeClass
+    public static void setupSuite() throws Exception {
+        GBEnvironment.setupEnvironment(GBEnvironment.createLocalTestEnvironment());
+
+        // print everything going to android.util.Log to System.out
+        System.setProperty("robolectric.logging", "stdout");
+
+        // properties might be preconfigured in build.gradle because of test ordering problems
+        String logDir = System.getProperty(PROP_LOGFILES_DIR);
+        if (logDir != null) {
+            logFilesDir = new File(logDir);
+        } else {
+            logFilesDir = FileUtils.createTempDir("logfiles");
+            System.setProperty(PROP_LOGFILES_DIR, logFilesDir.getAbsolutePath());
+        }
+
+        if (System.getProperty(ContextInitializer.CONFIG_FILE_PROPERTY) == null) {
+            File workingDir = new File(System.getProperty("user.dir"));
+            File configFile = new File(workingDir, "src/main/assets/logback.xml");
+            System.out.println(configFile.getAbsolutePath());
+            System.setProperty(ContextInitializer.CONFIG_FILE_PROPERTY, configFile.getAbsolutePath());
+        }
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        app = (GBApplication) RuntimeEnvironment.application;
+        assertNotNull(app);
+        assertNotNull(getContext());
+        app.setupDatabase();
+        dbHandler = GBApplication.acquireDB();
+        daoSession = dbHandler.getDaoSession();
+        assertNotNull(daoSession);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        dbHandler.closeDb();
+        GBApplication.releaseDB();
+    }
+
+    protected GBDevice createDummyGDevice(String macAddress) {
+        GBDevice dummyGBDevice = new GBDevice(macAddress, "Testie", "Tesie Alias", "Test Folder", DeviceType.TEST);
+        dummyGBDevice.setFirmwareVersion("1.2.3");
+        dummyGBDevice.setModel("4.0");
+        return dummyGBDevice;
+    }
+
+    protected Context getContext() {
+        return app;
+    }
+}
diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/test/Tryout.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/test/Tryout.java
new file mode 100644
index 0000000..b149f39
--- /dev/null
+++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/test/Tryout.java
@@ -0,0 +1,42 @@
+package nodomain.freeyourgadget.gadgetbridge.test;
+
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.GregorianCalendar;
+
+import nodomain.freeyourgadget.gadgetbridge.Logging;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandDateConverter;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
+import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
+
+/**
+ * A simple class for trying out things, not actually testing something.
+ */
+public class Tryout extends TestBase {
+    private static final Logger LOG = LoggerFactory.getLogger(Tryout.class);
+
+    @Test
+    public void blah() {
+        int v = 1 << 7 | 1 << 2;
+        byte b = (byte) v;
+        LOG.info("v: " + v);
+        Logging.logBytes(LOG, new byte[] { b });
+
+        byte[] bs = new byte[] {(byte) 0xf0,0x28,0x00,0x00 };
+        LOG.warn("uint32: " + BLETypeConversions.toUint32(bs));
+        LOG.warn("uint16: " + BLETypeConversions.toUint16(bs));
+
+    }
+
+    @Test
+    public void testCalendarBytes() {
+        GregorianCalendar calendar = MiBandDateConverter.createCalendar();
+        byte[] bytes = MiBandDateConverter.calendarToRawBytes(calendar,"fake");
+        LOG.info("Calender: " + DateTimeUtils.formatDateTime(calendar.getTime()));
+        Logging.logBytes(LOG, bytes);
+    }
+
+
+}
diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/util/EmojiConverterTest.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/util/EmojiConverterTest.java
new file mode 100644
index 0000000..6e07e43
--- /dev/null
+++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/util/EmojiConverterTest.java
@@ -0,0 +1,53 @@
+package nodomain.freeyourgadget.gadgetbridge.util;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+import org.robolectric.RuntimeEnvironment;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import nodomain.freeyourgadget.gadgetbridge.test.TestBase;
+
+public class EmojiConverterTest extends TestBase {
+    @Test
+    public void testConvert() {
+        final Map<String, String> snippets = new HashMap<String, String>() {{
+            put(
+                    "no emoji",
+                    "no emoji"
+            );
+
+            put(
+                    "Hello! \uD83D\uDE0A",
+                    "Hello! :-)"
+            );
+
+            put(
+                    "\uD83D\uDE1B hi",
+                    ":-P hi"
+            );
+
+            put(
+                    "\uD83D\uDC7B\uD83D\uDC80",
+                    ":ghost::skull:"
+            );
+
+            put(
+                    "also \uD83D\uDE36 with words \uD83D\uDCAA\uD83C\uDFFC in-between \uD83D\uDE09",
+                    "also :no_mouth: with words :muscle: in-between ;-)"
+            );
+        }};
+
+        for (final Map.Entry<String, String> e : snippets.entrySet()) {
+            assertEquals(
+                    e.getValue(),
+                    EmojiConverter.convertUnicodeEmojiToAscii(
+                            e.getKey(),
+                            RuntimeEnvironment.getApplication()
+                    )
+            );
+        }
+    }
+}
diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/util/ImportExportSharedPreferencesTest.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/util/ImportExportSharedPreferencesTest.java
new file mode 100644
index 0000000..074e30e
--- /dev/null
+++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/util/ImportExportSharedPreferencesTest.java
@@ -0,0 +1,65 @@
+package nodomain.freeyourgadget.gadgetbridge.util;
+
+import static org.junit.Assert.*;
+import static org.junit.Assert.assertEquals;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+
+import org.junit.Test;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import nodomain.freeyourgadget.gadgetbridge.test.TestBase;
+
+public class ImportExportSharedPreferencesTest extends TestBase {
+    @Test
+    public void testExportImport() throws Exception {
+        final SharedPreferences sharedPreferences = getContext().getSharedPreferences("testExportImport", Context.MODE_PRIVATE);
+
+        assertEquals(0, sharedPreferences.getAll().size());
+
+        final HashSet<String> EXPECTED_SET = new HashSet<>(Arrays.asList(
+                "item 1",
+                "item 2"
+        ));
+
+        sharedPreferences.edit()
+                .putString("a_string", "Hello")
+                .putBoolean("a_boolean", true)
+                .putLong("a_long", 42)
+                .putInt("an_int", 1337)
+                .putFloat("a_float", 7f)
+                .putStringSet("bip_display_items", EXPECTED_SET).commit();
+
+        assertEquals(6, sharedPreferences.getAll().size());
+
+        final File file = File.createTempFile("gb-ImportExportSharedPreferencesTest", "xml");
+        file.deleteOnExit();
+        ImportExportSharedPreferences.exportToFile(sharedPreferences, file);
+
+        sharedPreferences.edit()
+                .putInt("an_int", 3)
+                .putString("this extra string", "should be gone after import")
+                .commit();
+
+        assertEquals(7, sharedPreferences.getAll().size());
+        ImportExportSharedPreferences.importFromFile(sharedPreferences, file);
+
+        assertEquals(6, sharedPreferences.getAll().size());
+        assertEquals("Hello", sharedPreferences.getString("a_string", null));
+        assertTrue(sharedPreferences.getBoolean("a_boolean", false));
+        assertEquals(42, sharedPreferences.getLong("a_long", 0));
+        assertEquals(1337, sharedPreferences.getInt("an_int", 0));
+        assertEquals(7f, sharedPreferences.getFloat("a_float", 0), 0.01);
+
+        final Set<String> actualSet = sharedPreferences.getStringSet("bip_display_items", Collections.emptySet());
+        assertEquals(EXPECTED_SET.size(), actualSet.size());
+        assertTrue(EXPECTED_SET.containsAll(actualSet));
+        assertTrue(actualSet.containsAll(EXPECTED_SET));
+    }
+}
diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/util/RangeMapTest.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/util/RangeMapTest.java
new file mode 100644
index 0000000..e6259c3
--- /dev/null
+++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/util/RangeMapTest.java
@@ -0,0 +1,51 @@
+package nodomain.freeyourgadget.gadgetbridge.util;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+
+import nodomain.freeyourgadget.gadgetbridge.test.TestBase;
+
+public class RangeMapTest extends TestBase {
+    @Test
+    public void testLowerBound() {
+        final RangeMap<Integer, Integer> map = new RangeMap<>();
+        assertEquals(0, map.size());
+        assertNull(map.get(0));
+
+        map.put(10, 20);
+        assertNull(map.get(0));
+        assertEquals(20, map.get(10).intValue());
+        assertEquals(20, map.get(20).intValue());
+
+        map.put(20, 30);
+        map.put(30, 40);
+        assertNull(map.get(0));
+        assertEquals(20, map.get(10).intValue());
+        assertEquals(20, map.get(15).intValue());
+        assertEquals(30, map.get(20).intValue());
+        assertEquals(30, map.get(25).intValue());
+        assertEquals(40, map.get(30).intValue());
+    }
+
+    @Test
+    public void testUpperBound() {
+        final RangeMap<Integer, Integer> map = new RangeMap<>(RangeMap.Mode.UPPER_BOUND);
+        assertEquals(0, map.size());
+        assertNull(map.get(0));
+
+        map.put(10, 20);
+        assertNull(map.get(20));
+        assertEquals(20, map.get(10).intValue());
+        assertEquals(20, map.get(0).intValue());
+
+        map.put(20, 30);
+        map.put(30, 40);
+        assertNull(map.get(50));
+        assertEquals(40, map.get(30).intValue());
+        assertEquals(40, map.get(25).intValue());
+        assertEquals(30, map.get(20).intValue());
+        assertEquals(30, map.get(15).intValue());
+        assertEquals(20, map.get(10).intValue());
+    }
+}
diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/util/gpx/GPXParserTest.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/util/gpx/GPXParserTest.java
new file mode 100644
index 0000000..14062f2
--- /dev/null
+++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/util/gpx/GPXParserTest.java
@@ -0,0 +1,82 @@
+package nodomain.freeyourgadget.gadgetbridge.util.gpx;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.text.DecimalFormat;
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.model.GPSCoordinate;
+import nodomain.freeyourgadget.gadgetbridge.test.TestBase;
+import nodomain.freeyourgadget.gadgetbridge.util.gpx.model.GpxFile;
+import nodomain.freeyourgadget.gadgetbridge.util.gpx.model.GpxTrackPoint;
+
+import static org.hamcrest.Matchers.anyOf;
+import static org.hamcrest.Matchers.is;
+
+public class GPXParserTest extends TestBase {
+
+    @Test
+    public void shouldReadGPXCorrectly() throws IOException, GpxParseException {
+        try (final InputStream inputStream = getClass().getResourceAsStream("/gpx-exporter-test-SampleTrack.gpx")) {
+            GpxParser gpxParser = new GpxParser(inputStream);
+            List<GpxTrackPoint> trackPoints = gpxParser.getGpxFile().getPoints();
+            Assert.assertEquals(trackPoints.size(), 14);
+            DecimalFormat df = new DecimalFormat("###.##");
+            for (GPSCoordinate tp : trackPoints) {
+                Assert.assertEquals(df.format(tp.getLongitude()), "-68.2");
+                Assert.assertEquals(df.format(tp.getLatitude()), "44.15");
+                Assert.assertThat(df.format(tp.getAltitude()), anyOf(is("40"), is("46")));
+            }
+            Assert.assertEquals(
+                    new GpxTrackPoint(-68.200293, 44.152462, 40, new Date(1546300800000L)),
+                    trackPoints.get(0)
+            );
+        }
+    }
+
+    @Test
+    public void shouldParseMultipleSegments() throws IOException, GpxParseException, ParseException {
+        try (final InputStream inputStream = getClass().getResourceAsStream("/gpx-parser-test-multiple-segments.gpx")) {
+            final GpxParser gpxParser = new GpxParser(inputStream);
+            final GpxFile gpxFile = gpxParser.getGpxFile();
+            Assert.assertEquals(1, gpxFile.getTracks().size());
+            Assert.assertEquals(2, gpxFile.getTracks().get(0).getTrackSegments().size());
+
+            final List<GpxTrackPoint> segment1 = new ArrayList<GpxTrackPoint>() {{
+                add(new GpxTrackPoint(-8.2695876, -70.6666343, 790.0, new Date(1680969788000L), 123));
+                add(new GpxTrackPoint(-8.2653274, -70.6670617, 296.0, new Date(1680970639000L), 56));
+            }};
+
+            final List<GpxTrackPoint> segment2 = new ArrayList<GpxTrackPoint>() {{
+                add(new GpxTrackPoint(-8.2653274, -70.6670617, 205.0, new Date(1680971684000L), 85));
+                add(new GpxTrackPoint(-8.2695876, -70.6666343, 209.0, new Date(1680973017000L), 150));
+            }};
+
+            Assert.assertEquals(gpxFile.getTracks().get(0).getTrackSegments().get(0).getTrackPoints(), segment1);
+            Assert.assertEquals(gpxFile.getTracks().get(0).getTrackSegments().get(1).getTrackPoints(), segment2);
+        }
+    }
+
+    @Test
+    public void shouldParseOutOfOrder() throws IOException, GpxParseException {
+        try (final InputStream inputStream = getClass().getResourceAsStream("/gpx-parser-test-order.gpx")) {
+            final GpxParser gpxParser = new GpxParser(inputStream);
+            final GpxFile gpxFile = gpxParser.getGpxFile();
+            Assert.assertEquals(1, gpxFile.getTracks().size());
+            Assert.assertEquals(1, gpxFile.getTracks().get(0).getTrackSegments().size());
+
+            final List<GpxTrackPoint> segment1 = new ArrayList<GpxTrackPoint>() {{
+                add(new GpxTrackPoint(-8.2695876, -70.6666343, 790.0, new Date(1680969788000L), 123));
+                add(new GpxTrackPoint(-8.2653274, -70.6670617, 296.0, new Date(1680970639000L), 56));
+            }};
+
+            Assert.assertEquals(gpxFile.getTracks().get(0).getTrackSegments().get(0).getTrackPoints(), segment1);
+        }
+    }
+}
diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/util/language/LanguageUtilsTest.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/util/language/LanguageUtilsTest.java
new file mode 100644
index 0000000..9c2d528
--- /dev/null
+++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/util/language/LanguageUtilsTest.java
@@ -0,0 +1,305 @@
+package nodomain.freeyourgadget.gadgetbridge.util.language;
+
+import android.content.SharedPreferences;
+
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.test.TestBase;
+import nodomain.freeyourgadget.gadgetbridge.util.language.impl.CzechTransliterator;
+import nodomain.freeyourgadget.gadgetbridge.util.language.impl.ExtendedAsciiTransliterator;
+import nodomain.freeyourgadget.gadgetbridge.util.language.impl.FlattenToAsciiTransliterator;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_TRANSLITERATION_LANGUAGES;
+
+/**
+ * Tests LanguageUtils
+ */
+public class LanguageUtilsTest extends TestBase {
+
+    private GBDevice dummyGBDevice;
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        dummyGBDevice = createDummyGDevice("00:00:00:00:10");
+    }
+
+
+    @Test
+    public void testStringTransliterateCyrillic() throws Exception {
+        final Transliterator transliterator = LanguageUtils.getTransliterator("russian");
+
+        // input with cyrillic and diacritic letters
+        String input = "袩褉玫褋褌芯 褌械泻膷褌";
+        String output = transliterator.transliterate(input);
+        String result = "Prosto tekct";
+
+        assertEquals("Transliteration failed", result, output);
+    }
+
+    @Test
+    public void testStringTransliterateSerbian() throws Exception {
+        final Transliterator transliterator = LanguageUtils.getTransliterator("serbian");
+
+        final Map<String, String> tests = new LinkedHashMap<String, String>() {{
+            put("孝褏械 q懈褑泻 斜褉芯薪 褎芯x 褬褍屑锌械写 芯胁械褉 褌褏械 谢邪蟹* 写芯谐", "The qick bron fox jumped over the laz* dog");
+            put("小褉锌褋泻邪 褯懈褉懈谢懈褑邪", "Srpska cirilica");
+            put("Novak 膼okovi膰", "Novak Djokovic");
+            put("袕, 袏 and 袎", "Dz, Nj and Lj");
+            put("袎褍褭邪褔泻邪", "Ljuljacka");
+            put("袧邪泻芯胁邪褮", "Nakovanj");
+            put("袣邪褔泻邪胁邪褭", "Kackavalj");
+            put("效邪褔邪泻", "Cacak");
+            put("效, 褔", "C, c");
+            put("袐, 褯", "C, c");
+            put("袞, 卸", "Z, z");
+            put("楔, 褕", "S, s");
+            put("袀, 褣", "Dj, dj");
+            put("袕, 褵", "Dz, dz");
+            put("袏, 褮", "Nj, nj");
+            put("袎, 褭", "Lj, lj");
+        }};
+
+        for (final Map.Entry<String, String> e : tests.entrySet()) {
+            assertEquals("Transliteration failed for " + e.getKey(), e.getValue(), transliterator.transliterate(e.getKey()));
+        }
+    }
+
+    @Test
+    public void testStringTransliterateHebrew() throws Exception {
+        final Transliterator transliterator = LanguageUtils.getTransliterator("hebrew");
+
+        String input = "讘讚讬拽讛 注讘专讬转";
+        String output = transliterator.transliterate(input);
+        String result = "bdykh 'bryth";
+
+        assertEquals("Transliteration failed", result, output);
+    }
+
+    @Test
+    public void testStringTransliterateArabic() {
+        final Transliterator transliterator = LanguageUtils.getTransliterator("arabic");
+
+        String pangram = "賳氐 丨賰賷賲 賱賴 爻乇 賯丕胤毓 賵匕賵 卮兀賳 毓馗賷賲 賲賰鬲賵亘 毓賱賶 孬賵亘 兀禺囟乇 賵賲睾賱賮 亘噩賱丿 兀夭乇賯";
+        String pangramExpected = "n9 7kym lh sr qa63 wthw sh2n 36'ym mktwb 3la thwb 259'r wm3'lf bjld 2zrq";
+        String pangramActual = transliterator.transliterate(pangram);
+        assertEquals("Arabic pangram transliteration failed", pangramExpected, pangramActual);
+
+        String taMarbutah = "锖�";
+        String taMarbutahExpected = "";
+        String taMarbutahActual = transliterator.transliterate(taMarbutah);
+        assertEquals("ta marbutah transliteration failed", taMarbutahExpected, taMarbutahActual);
+
+        String hamza = "亍兀丐廿卅丌";
+        String hamzaExpected = "222222";
+        String hamzaActual = transliterator.transliterate(hamza);
+        assertEquals("hamza transliteration failed", hamzaExpected, hamzaActual);
+
+        String easternArabicNumeralsArabic = "贍佟佗伲伽佶佴侑侉侃";
+        String easternArabicNumeralsExpected = "0123456789";
+        assertEquals("Eastern Arabic numerals (Arabic) failed", easternArabicNumeralsExpected,
+                transliterator.transliterate(easternArabicNumeralsArabic));
+    }
+
+    public void testStringTransliteratePersian() {
+        final Transliterator transliterator = LanguageUtils.getTransliterator("persian");
+
+        String farsi = "诏趩倬跇";
+        String farsiExpected = "gchpzh";
+        String farsiActual = transliterator.transliterate(farsi);
+        assertEquals("Farsi transiteration failed", farsiExpected, farsiActual);
+
+        String easternArabicNumeralsFarsi = "郯郾鄄鄢鄞鄣鄱鄯鄹酃";
+        String easternArabicNumeralsExpected = "0123456789";
+
+        assertEquals("Eastern Arabic numerals (Farsi) failed", easternArabicNumeralsExpected,
+                transliterator.transliterate(easternArabicNumeralsFarsi));
+    }
+
+    @Test
+    public void testStringTransliterateBengali() throws Exception {
+        final Transliterator transliterator = LanguageUtils.getTransliterator("bengali");
+
+        // input with cyrillic and diacritic letters
+        String[] inputs = {"唳呧Θ唳苦Π唰佮Ζ唰嵿Η", "唳唳溹唳炧唳ㄠΟ唳距Δ唰嵿Π唳� 唳氞Σ唳涏 唳氞Σ唳啷�", "唳嗋Ξ唳� 唳膏Μ 唳︵唳栢唳多唳ㄠ 唳曕唳粪唳 唳椸唰熰 唳曕Π唳� 唳唳權Σ唳距 唳氞唰庎唳距Π!",
+                "唳嗋Ξ唳距Π 唳溹唳 唳曕唳� is so bad! 唳曕 唳嗋Π 唳Σ唳!"};
+        String[] outputs = {"oniruddho", "biggaanJaatraa cholchhe cholbe.",
+                "aami sob dekheshune kkhepe giye kori baanglaay chitkaar!",
+                "aamaar jaabhaa koD is so bad! kii aar bolbo!"};
+
+        String result;
+
+        for (int i = 0; i < inputs.length; i++) {
+            result = transliterator.transliterate(inputs[i]);
+            assertEquals("Transliteration failed", outputs[i], result);
+        }
+    }
+
+    @Test
+    public void testStringTransliterateKorean() {
+        final Transliterator transliterator = LanguageUtils.getTransliterator("korean");
+
+        // A familiar phrase with no special provisions.
+        String hello = "鞎堧厱頃橃劯鞖�";
+        String helloExpected = "annyeonghaseyo";
+        String helloActual = transliterator.transliterate(hello);
+        assertEquals("Korean hello transliteration failed", helloExpected, helloActual);
+
+        // Korean pangram. Includes some ASCII punctuation which should not be changed by
+        // transliteration.
+        //
+        // Translation: "Chocolate!? What I wanted was some rice puffs and clothes." "Child, why are
+        // you complaining again?"
+        String pangram = "\"鞗� 齑堨綔毽�? 鞝滉皜 鞗愴枅雿� 瓯� 牖ロ妧旮� 飕愲仈瓿� 鞚橂鞓堨殧.\" \"鞏橃暭, 鞕� 霕� 攵堩弶?\"";
+        String pangramExpected = "\"wen chokollit? jega wonhaetdeon geon ppeongtwigi jjoekkeumgwa uiryuyeyo.\" \"yaeya, wae tto bulpyeong?\"";
+        String pangramActual = transliterator.transliterate(pangram);
+        assertEquals("Korean pangram transliteration failed", pangramExpected, pangramActual);
+
+        // Several words excercising special provisions, from Wikipedia.
+        String special = "膦嬯碃, 雴撾嫟, 鞛№榾, 雮踌";
+        String specialExpected = "joko, nota, japhyeo, nachi";
+        String specialActual = transliterator.transliterate(special);
+        assertEquals("Korean special provisions transliteration failed", specialExpected, specialActual);
+
+        // Isolated jamo.
+        String isolatedJamo = "銋嬨厠銋�";
+        String isolatedJamoExpected = "kkk";
+        String isolatedJamoActual = transliterator.transliterate(isolatedJamo);
+        assertEquals("Korean isolated jamo transliteration failed", isolatedJamoExpected, isolatedJamoActual);
+
+        // Korean transliteration shouldn't touch non-Hangul composites.
+        String german = "sch枚n";
+        String germanExpected = german;
+        String germanActual = transliterator.transliterate(german);
+        assertEquals("Korean transliteration modified a non-Hangul composite", germanExpected, germanActual);
+    }
+
+    @Test
+    public void testStringTransliterateLatvian() {
+        final Transliterator transliterator = LanguageUtils.getTransliterator("latvian");
+
+        String input = "膩 膷 膿 模 墨 姆 募 艈 拧 奴 啪 膧 膶 膾 蘑 莫 亩 幕 艆 艩 弄 沤";
+        String output = transliterator.transliterate(input);
+        String expected = "a c e g i k l n s u z A C E G I K L N S U Z";
+        assertEquals("latvian translation failed", expected, output);
+
+        input = "a膩a c膷c e膿e g模g i墨i k姆k l募l n艈n s拧s u奴u z啪z A膧A C膶C E膾E G蘑G I莫I K亩K L幕L N艆N S艩S U弄U Z沤Z";
+        output = transliterator.transliterate(input);
+        expected = "aaa ccc eee ggg iii kkk lll nnn sss uuu zzz AAA CCC EEE GGG III KKK LLL NNN SSS UUU ZZZ";
+        assertEquals("latvian translation failed", expected, output);
+    }
+
+    @Test
+    public void testStringTransliterateLithuanian() {
+        final Transliterator transliterator = LanguageUtils.getTransliterator("lithuanian");
+
+        String input = "膮 膷 臋 臈 寞 拧 懦 奴 啪";
+        String output = transliterator.transliterate(input);
+        String expected = "a c e e i s u u z";
+        assertEquals("lithuanian translation failed", expected, output);
+
+        input = "a膮a c膷c e臋e e臈e i寞i s拧s u懦u u奴u z啪z";
+        output = transliterator.transliterate(input);
+        expected = "aaa ccc eee eee iii sss uuu uuu zzz";
+        assertEquals("lithuanian translation failed", expected, output);
+    }
+
+    @Test
+    public void testStringTransliterateGeorgian() {
+        final Transliterator transliterator = LanguageUtils.getTransliterator("georgian");
+
+        String input = "醿� 醿� 醿� 醿� 醿� 醿� 醿� 醿� 醿�";
+        String output = transliterator.transliterate(input);
+        String expected = "a b t T J y w W h";
+        assertEquals("georgian transliteration failed", expected, output);
+    }
+
+    @Test
+    public void testStringTransliterateHungarian() {
+        final Transliterator transliterator = LanguageUtils.getTransliterator("hungarian");
+
+        String input = "谩 茅 铆 贸 枚 艖 眉 疟";
+        String output = transliterator.transliterate(input);
+        String expected = "a e i o o o u u";
+        assertEquals("hungarian transliteration failed", expected, output);
+    }
+
+    @Test
+    public void testStringTransliterateCommonSymbols() {
+        final Transliterator transliterator = LanguageUtils.getTransliterator("common_symbols");
+
+        String input = "漏 庐 鈩� 掳 芦 鈥� 禄 鈥� 鈥� 銆� 虏 鲁 鈧�";
+        String output = transliterator.transliterate(input);
+        String expected = "(c) (r) (tm) * < - > \" \" ~ 2 3 5";
+        assertEquals("common symbols translation failed", expected, output);
+
+        input = "a漏a b庐b c鈩 d掳d e芦e f鈥揻 g禄g h鈥渉 i鈥漣 j銆渏 k虏k l鲁l m鈧卪";
+        output = transliterator.transliterate(input);
+        expected = "a(c)a b(r)b c(tm)c d*d e<e f-f g>g h\"h i\"i j~j k2k l3l m5m";
+        assertEquals("common symbols translation failed", expected, output);
+    }
+
+    @Test
+    public void testStringTransliterateCroatian() {
+        final Transliterator transliterator = LanguageUtils.getTransliterator("croatian");
+
+        String input = "膷 膰 膽 拧 啪";
+        String output = transliterator.transliterate(input);
+        String expected = "c c d s z";
+        assertEquals("croatian transliteration failed", expected, output);
+    }
+
+    @Test
+    public void testFlattenToAscii() throws Exception {
+        final FlattenToAsciiTransliterator transliterator = new FlattenToAsciiTransliterator();
+        String input = "盲 葯 葲 膬 铿乶e";
+        String output = transliterator.transliterate(input);
+        String expected = "a s t a fine";
+        assertEquals("flatten to ascii transliteration failed", expected, output);
+    }
+
+    @Test
+    public void testMultitransliterator() throws Exception {
+        final MultiTransliterator multiTransliterator = new MultiTransliterator(Arrays.asList(
+                new CzechTransliterator(),
+                new ExtendedAsciiTransliterator(),
+                new FlattenToAsciiTransliterator()
+        ));
+        assertEquals("Zlutoucky kun upel \"dabelske\" \"ody\"", multiTransliterator.transliterate("沤lu钮ou膷k媒 k暖艌 煤p臎l 禄膹谩belsk茅芦 鈥灻砫y鈥�"));
+        assertEquals("300 Kc", multiTransliterator.transliterate("300\u00A0K膷"));
+    }
+
+    @Test
+    public void testTransliterateOption() throws Exception {
+        enableTransliteration(false);
+        assertNull("Transliteration option fail! Expected 'Off' by default, but result is 'On'",
+                getTransliteration());
+
+        enableTransliteration(true);
+        assertNotNull("Transliteration option fail! Expected 'On', but result is 'Off'", getTransliteration());
+    }
+
+    private void enableTransliteration(boolean enable) {
+        SharedPreferences devicePrefs = GBApplication.getDeviceSpecificSharedPrefs(dummyGBDevice.getAddress());
+        SharedPreferences.Editor editor = devicePrefs.edit();
+        if (enable) {
+            editor.putString(PREF_TRANSLITERATION_LANGUAGES, "extended_ascii,scandinavian,german,russian,hebrew,greek,ukranian,arabic,persian,lithuanian,polish,estonian,icelandic,czech,turkish,bengali,korean,georgian,croatian");
+        } else {
+            editor.remove(PREF_TRANSLITERATION_LANGUAGES);
+        }
+        editor.apply();
+    }
+
+    private Transliterator getTransliteration() {
+        return LanguageUtils.getTransliterator(dummyGBDevice);
+    }
+}
diff --git a/app/src/test/resources/ActivityDetailsDump1.txt b/app/src/test/resources/ActivityDetailsDump1.txt
new file mode 100644
index 0000000..f5c3345
--- /dev/null
+++ b/app/src/test/resources/ActivityDetailsDump1.txt
@@ -0,0 +1,861 @@
+0x0 0x1 0x7 0x47 0x0 0x0 0x0 0x0 0x0 0x1 0x8 0x46 0x0 0x0 0x0 0x0 0x0
+0x1 0x1 0xa 0x43 0x0 0x0 0x0 0x0 0x0 0x1 0xc 0x46 0x0 0x0 0x0 0x0 0x0
+0x2 0x1 0xd 0x44 0x0 0x0 0x0 0x0 0x0 0x1 0xf 0x3e 0x0 0x0 0x0 0x0 0x0
+0x3 0x1 0x11 0x3b 0x0 0x0 0x0 0x0 0x0 0x1 0x12 0x3e 0x0 0x0 0x0 0x0 0x0
+0x4 0x1 0x17 0x3d 0x0 0x0 0x0 0x0 0x0 0x1 0x18 0x3f 0x0 0x0 0x0 0x0 0x0
+0x5 0x1 0x1b 0x41 0x0 0x0 0x0 0x0 0x0 0x1 0x1d 0x42 0x0 0x0 0x0 0x0 0x0
+0x6 0x1 0x20 0x43 0x0 0x0 0x0 0x0 0x0 0x1 0x21 0x44 0x0 0x0 0x0 0x0 0x0
+0x7 0x1 0x22 0x45 0x0 0x0 0x0 0x0 0x0 0x0 0x23 0x0 0x0 0x0 0x0 0x0 0x0
+0x8 0x0 0x23 0x79 0xff 0xcd 0x0 0x0 0x0 0x0 0x23 0x88 0xff 0xc3 0x0 0x0 0x0
+0x9 0x0 0x23 0xbf 0xff 0xeb 0x0 0x0 0x0 0x0 0x23 0xec 0xff 0x4 0x1 0x0 0x0
+0xa 0x0 0x23 0x5 0x0 0x4 0x1 0x0 0x0 0x0 0x23 0xf6 0xff 0x4f 0x1 0x0 0x0
+0xb 0x0 0x23 0xec 0xff 0x18 0x1 0x0 0x0 0x0 0x23 0xf1 0xff 0xff 0x0 0x0 0x0
+0xc 0x0 0x23 0xf1 0xff 0xf5 0x0 0x0 0x0 0x0 0x23 0xe2 0xff 0xa0 0x0 0x0 0x0
+0xd 0x0 0x23 0x9c 0xff 0xeb 0x0 0x0 0x0 0x0 0x23 0xf1 0xff 0xeb 0x0 0x0 0x0
+0xe 0x0 0x23 0xfb 0xff 0xe 0x1 0x0 0x0 0x0 0x23 0xf6 0xff 0x13 0x1 0x0 0x0
+0xf 0x0 0x23 0xfb 0xff 0x68 0x1 0x0 0x0 0x0 0x23 0xe2 0xff 0x18 0x1 0x0 0x0
+0x10 0x0 0x23 0xb5 0xff 0xd2 0x0 0x0 0x0 0x0 0x23 0x97 0xff 0x87 0x0 0x0 0x0
+0x11 0x0 0x23 0xba 0xff 0xa5 0x0 0x0 0x0 0x0 0x23 0xe2 0xff 0xfa 0x0 0x0 0x0
+0x12 0x0 0x23 0xe2 0xff 0xb9 0x0 0x0 0x0 0x0 0x23 0xfb 0xff 0x4 0x1 0x0 0x0
+0x13 0x0 0x23 0xec 0xff 0xa5 0x0 0x0 0x0 0x0 0x23 0xf1 0xff 0x2c 0x1 0x0 0x0
+0x14 0x0 0x23 0x5 0x0 0xe 0x1 0x0 0x0 0x0 0x23 0xec 0xff 0x9b 0x0 0x0 0x0
+0x15 0x0 0x23 0xf1 0xff 0xa5 0x0 0x0 0x0 0x0 0x23 0xf6 0xff 0xb4 0x0 0x0 0x0
+0x16 0x0 0x23 0xec 0xff 0xc3 0x0 0x0 0x0 0x0 0x23 0x7e 0xff 0xd2 0x0 0x0 0x0
+0x17 0x6 0x23 0x81 0x0 0xd2 0x0 0x0 0x0 0x0 0x24 0xd3 0xff 0x7d 0x0 0x0 0x0
+0x18 0x6 0x24 0x81 0x0 0x7d 0x0 0x0 0x0 0x0 0x25 0xc1 0xff 0xb1 0x0 0x0 0x0
+0x19 0x6 0x25 0x81 0x0 0xb1 0x0 0x0 0x0 0x0 0x26 0xca 0xff 0xc9 0x0 0x0 0x0
+0x1a 0x6 0x26 0x81 0x0 0xc9 0x0 0x0 0x0 0x0 0x27 0xd7 0xff 0xdd 0x0 0x0 0x0
+0x1b 0x6 0x27 0x81 0x0 0xdd 0x0 0x0 0x0 0x1 0x27 0x46 0x0 0x0 0x0 0x0 0x0
+0x1c 0x0 0x28 0xe4 0xff 0xf8 0x0 0x0 0x0 0x6 0x28 0x81 0x0 0xf8 0x0 0x0 0x0
+0x1d 0x0 0x29 0xef 0xff 0xf 0x1 0x0 0x0 0x6 0x29 0x81 0x0 0xf 0x1 0x0 0x0
+0x1e 0x1 0x29 0x43 0x0 0x0 0x0 0x0 0x0 0x0 0x2a 0xf1 0xff 0x2 0x1 0x0 0x0
+0x1f 0x6 0x2a 0x81 0x0 0x2 0x1 0x0 0x0 0x1 0x2a 0x45 0x0 0x0 0x0 0x0 0x0
+0x20 0x0 0x2b 0xeb 0xff 0xf4 0x0 0xfe 0xff 0x6 0x2b 0x81 0x0 0xf4 0x0 0xfe 0xff
+0x21 0x0 0x2c 0xe8 0xff 0xdc 0x0 0x0 0x0 0x6 0x2c 0x81 0x0 0xdc 0x0 0x0 0x0
+0x22 0x0 0x2d 0xe9 0xff 0xca 0x0 0x0 0x0 0x6 0x2d 0x81 0x0 0xca 0x0 0x0 0x0
+0x23 0x0 0x2e 0xeb 0xff 0xc9 0x0 0x0 0x0 0x6 0x2e 0x81 0x0 0xc9 0x0 0x0 0x0
+0x24 0x1 0x2e 0x46 0x0 0x0 0x0 0x0 0x0 0x0 0x2f 0xed 0xff 0xcb 0x0 0x0 0x0
+0x25 0x6 0x2f 0x81 0x0 0xcb 0x0 0x0 0x0 0x1 0x2f 0x47 0x0 0x0 0x0 0x0 0x0
+0x26 0x0 0x30 0xf0 0xff 0xc5 0x0 0x0 0x0 0x6 0x30 0x81 0x0 0xc5 0x0 0x0 0x0
+0x27 0x0 0x31 0xf2 0xff 0xb7 0x0 0x0 0x0 0x6 0x31 0x81 0x0 0xb7 0x0 0x0 0x0
+0x28 0x0 0x32 0xf1 0xff 0xb1 0x0 0x0 0x0 0x6 0x32 0x81 0x0 0xb1 0x0 0x0 0x0
+0x29 0x0 0x33 0xf7 0xff 0xb5 0x0 0x0 0x0 0x6 0x33 0x81 0x0 0xb5 0x0 0x0 0x0
+0x2a 0x1 0x33 0x49 0x0 0x0 0x0 0x0 0x0 0x0 0x34 0xfa 0xff 0xc8 0x0 0x0 0x0
+0x2b 0x6 0x34 0x81 0x0 0xc8 0x0 0x0 0x0 0x1 0x34 0x4a 0x0 0x0 0x0 0x0 0x0
+0x2c 0x0 0x35 0xf7 0xff 0xda 0x0 0x0 0x0 0x6 0x35 0x81 0x0 0xda 0x0 0x0 0x0
+0x2d 0x1 0x35 0x4b 0x0 0x0 0x0 0x0 0x0 0x0 0x36 0xf3 0xff 0xdb 0x0 0x0 0x0
+0x2e 0x6 0x36 0x81 0x0 0xdb 0x0 0x0 0x0 0x0 0x37 0xe9 0xff 0xed 0x0 0x0 0x0
+0x2f 0x6 0x37 0x81 0x0 0xed 0x0 0x0 0x0 0x1 0x38 0x4c 0x0 0x0 0x0 0x0 0x0
+0x30 0x0 0x38 0xe9 0xff 0xf6 0x0 0x0 0x0 0x6 0x38 0x81 0x0 0xf6 0x0 0x0 0x0
+0x31 0x0 0x39 0xeb 0xff 0xe4 0x0 0xfe 0xff 0x6 0x39 0x81 0x0 0xe4 0x0 0xfe 0xff
+0x32 0x1 0x39 0x4d 0x0 0x0 0x0 0x0 0x0 0x0 0x3a 0xef 0xff 0xbd 0x0 0x0 0x0
+0x33 0x6 0x3a 0x81 0x0 0xbd 0x0 0x0 0x0 0x1 0x3a 0x4e 0x0 0x0 0x0 0x0 0x0
+0x34 0x0 0x3c 0xed 0xff 0xcc 0x0 0x0 0x0 0x6 0x3c 0x81 0x0 0xcc 0x0 0x0 0x0
+0x35 0x1 0x3d 0x4f 0x0 0x0 0x0 0x0 0x0 0x1 0x3e 0x50 0x0 0x0 0x0 0x0 0x0
+0x36 0x0 0x3e 0x1 0x0 0xcd 0x0 0x0 0x0 0x6 0x3e 0x81 0x0 0xcd 0x0 0x0 0x0
+0x37 0x0 0x3f 0x14 0x0 0x91 0x0 0x0 0x0 0x6 0x3f 0x81 0x0 0x91 0x0 0x0 0x0
+0x38 0x1 0x3f 0x51 0x0 0x0 0x0 0x0 0x0 0x0 0x41 0x86 0x1 0x6e 0x0 0x0 0x0
+0x39 0x6 0x41 0x81 0x0 0x6e 0x0 0x0 0x0 0x1 0x41 0x52 0x0 0x0 0x0 0x0 0x0
+0x3a 0x0 0x42 0x5e 0x1 0x55 0x0 0x0 0x0 0x6 0x42 0x81 0x0 0x55 0x0 0x0 0x0
+0x3b 0x0 0x43 0x36 0x1 0x48 0x0 0x0 0x0 0x6 0x43 0x81 0x0 0x48 0x0 0x0 0x0
+0x3c 0x0 0x44 0x31 0x1 0x36 0x0 0x0 0x0 0x6 0x44 0x81 0x0 0x36 0x0 0x0 0x0
+0x3d 0x1 0x44 0x53 0x0 0x0 0x0 0x0 0x0 0x0 0x45 0x3d 0x1 0x2f 0x0 0x0 0x0
+0x3e 0x6 0x45 0x81 0x0 0x2f 0x0 0x0 0x0 0x0 0x46 0x35 0x1 0x38 0x0 0x0 0x0
+0x3f 0x6 0x46 0x81 0x0 0x38 0x0 0x0 0x0 0x1 0x46 0x54 0x0 0x0 0x0 0x0 0x0
+0x40 0x0 0x47 0x76 0x1 0x37 0x0 0x0 0x0 0x6 0x47 0x81 0x0 0x37 0x0 0x0 0x0
+0x41 0x0 0x48 0xbd 0x1 0x32 0x0 0x0 0x0 0x6 0x48 0x81 0x0 0x32 0x0 0x0 0x0
+0x42 0x1 0x49 0x55 0x0 0x0 0x0 0x0 0x0 0x0 0x49 0xb0 0x1 0xd 0x0 0x0 0x0
+0x43 0x6 0x49 0x81 0x0 0xd 0x0 0x0 0x0 0x0 0x4a 0x9b 0x1 0x1a 0x0 0x0 0x0
+0x44 0x6 0x4a 0x81 0x0 0x1a 0x0 0x0 0x0 0x0 0x4b 0x7a 0x1 0x1c 0x0 0xfe 0xff
+0x45 0x6 0x4b 0x81 0x0 0x1c 0x0 0xfe 0xff 0x0 0x4c 0x53 0x1 0x34 0x0 0x0 0x0
+0x46 0x6 0x4c 0x81 0x0 0x34 0x0 0x0 0x0 0x1 0x4c 0x56 0x0 0x0 0x0 0x0 0x0
+0x47 0x0 0x4d 0x24 0x1 0xfe 0xff 0x0 0x0 0x6 0x4d 0x81 0x0 0xfe 0xff 0x0 0x0
+0x48 0x1 0x4e 0x55 0x0 0x0 0x0 0x0 0x0 0x0 0x4f 0x3c 0x1 0xff 0xff 0x0 0x0
+0x49 0x6 0x4f 0x81 0x0 0xff 0xff 0x0 0x0 0x0 0x51 0x5 0x1 0x2a 0x0 0x0 0x0
+0x4a 0x6 0x51 0x81 0x0 0x2a 0x0 0x0 0x0 0x1 0x52 0x54 0x0 0x0 0x0 0x0 0x0
+0x4b 0x0 0x53 0xd5 0x0 0x77 0x0 0x0 0x0 0x6 0x53 0x81 0x0 0x77 0x0 0x0 0x0
+0x4c 0x0 0x54 0x86 0x0 0x89 0x0 0x0 0x0 0x6 0x54 0x81 0x0 0x89 0x0 0x0 0x0
+0x4d 0x1 0x55 0x53 0x0 0x0 0x0 0x0 0x0 0x0 0x55 0x59 0x0 0xa3 0x0 0x0 0x0
+0x4e 0x6 0x55 0x95 0x0 0xa3 0x0 0x0 0x0 0x0 0x56 0x3c 0x0 0xc2 0x0 0x0 0x0
+0x4f 0x6 0x56 0x95 0x0 0xc2 0x0 0x0 0x0 0x1 0x56 0x4f 0x0 0x0 0x0 0x0 0x0
+0x50 0x0 0x57 0x23 0x0 0xe8 0x0 0x0 0x0 0x6 0x57 0x95 0x0 0xe8 0x0 0x0 0x0
+0x51 0x1 0x57 0x50 0x0 0x0 0x0 0x0 0x0 0x0 0x58 0x11 0x0 0x5 0x1 0x0 0x0
+0x52 0x6 0x58 0x95 0x0 0x5 0x1 0x0 0x0 0x0 0x59 0xfc 0xff 0xf 0x1 0x0 0x0
+0x53 0x6 0x59 0x95 0x0 0xf 0x1 0x0 0x0 0x1 0x5a 0x51 0x0 0x0 0x0 0x0 0x0
+0x54 0x0 0x5a 0xed 0xff 0x25 0x1 0x0 0x0 0x6 0x5a 0x95 0x0 0x25 0x1 0x0 0x0
+0x55 0x0 0x5b 0xe7 0xff 0x32 0x1 0x0 0x0 0x6 0x5b 0x95 0x0 0x32 0x1 0x0 0x0
+0x56 0x0 0x5c 0xb8 0xff 0xa5 0x1 0x0 0x0 0x6 0x5c 0x95 0x0 0xa5 0x1 0x0 0x0
+0x57 0x0 0x5d 0xc0 0xff 0x7c 0x1 0x0 0x0 0x6 0x5d 0x95 0x0 0x7c 0x1 0x0 0x0
+0x58 0x0 0x5e 0xde 0xff 0x3e 0x1 0x0 0x0 0x6 0x5e 0x82 0x0 0x3e 0x1 0x0 0x0
+0x59 0x1 0x5f 0x52 0x0 0x0 0x0 0x0 0x0 0x0 0x5f 0xd8 0xff 0x46 0x1 0x0 0x0
+0x5a 0x6 0x5f 0x82 0x0 0x46 0x1 0x0 0x0 0x1 0x60 0x53 0x0 0x0 0x0 0x0 0x0
+0x5b 0x0 0x60 0xd3 0xff 0x39 0x1 0x0 0x0 0x6 0x60 0x82 0x0 0x39 0x1 0x0 0x0
+0x5c 0x0 0x61 0xc9 0xff 0x1f 0x1 0x0 0x0 0x6 0x61 0x82 0x0 0x1f 0x1 0x0 0x0
+0x5d 0x1 0x61 0x55 0x0 0x0 0x0 0x0 0x0 0x0 0x63 0xbf 0xff 0xdd 0x0 0x0 0x0
+0x5e 0x6 0x63 0x82 0x0 0xdd 0x0 0x0 0x0 0x1 0x63 0x56 0x0 0x0 0x0 0x0 0x0
+0x5f 0x1 0x66 0x57 0x0 0x0 0x0 0x0 0x0 0x0 0x6a 0xea 0xff 0xc6 0x0 0x0 0x0
+0x60 0x6 0x6a 0x82 0x0 0xc6 0x0 0x0 0x0 0x1 0x6b 0x56 0x0 0x0 0x0 0x0 0x0
+0x61 0x0 0x6c 0x59 0x0 0x9f 0x0 0x0 0x0 0x6 0x6c 0x96 0x0 0x9f 0x0 0x0 0x0
+0x62 0x1 0x6d 0x55 0x0 0x0 0x0 0x0 0x0 0x0 0x6e 0x24 0x1 0x61 0x0 0x0 0x0
+0x63 0x6 0x6e 0x96 0x0 0x61 0x0 0x0 0x0 0x1 0x6e 0x54 0x0 0x0 0x0 0x0 0x0
+0x64 0x0 0x6f 0x39 0x1 0x24 0x0 0x0 0x0 0x6 0x6f 0x96 0x0 0x24 0x0 0x0 0x0
+0x65 0x1 0x70 0x53 0x0 0x0 0x0 0x0 0x0 0x0 0x70 0x5b 0x1 0xfb 0xff 0x0 0x0
+0x66 0x6 0x70 0x96 0x0 0xfb 0xff 0x0 0x0 0x0 0x71 0x7f 0x1 0xe0 0xff 0x0 0x0
+0x67 0x6 0x71 0x96 0x0 0xe0 0xff 0x0 0x0 0x0 0x72 0xa3 0x1 0xd4 0xff 0x0 0x0
+0x68 0x6 0x72 0x96 0x0 0xd4 0xff 0x0 0x0 0x1 0x72 0x54 0x0 0x0 0x0 0x0 0x0
+0x69 0x0 0x73 0xc2 0x1 0xcf 0xff 0x0 0x0 0x6 0x73 0x96 0x0 0xcf 0xff 0x0 0x0
+0x6a 0x0 0x74 0xd8 0x1 0xc7 0xff 0x0 0x0 0x6 0x74 0x96 0x0 0xc7 0xff 0x0 0x0
+0x6b 0x1 0x74 0x55 0x0 0x0 0x0 0x0 0x0 0x0 0x75 0xe6 0x1 0xbe 0xff 0x0 0x0
+0x6c 0x6 0x75 0x96 0x0 0xbe 0xff 0x0 0x0 0x0 0x76 0xeb 0x1 0xb2 0xff 0x0 0x0
+0x6d 0x6 0x76 0x96 0x0 0xb2 0xff 0x0 0x0 0x1 0x77 0x56 0x0 0x0 0x0 0x0 0x0
+0x6e 0x0 0x77 0x8a 0x2 0x70 0xff 0x0 0x0 0x6 0x77 0x96 0x0 0x70 0xff 0x0 0x0
+0x6f 0x0 0x78 0x92 0x2 0x9a 0xff 0x0 0x0 0x6 0x78 0x96 0x0 0x9a 0xff 0x0 0x0
+0x70 0x1 0x78 0x57 0x0 0x0 0x0 0x0 0x0 0x0 0x79 0x7c 0x2 0xb9 0xff 0x0 0x0
+0x71 0x6 0x79 0x96 0x0 0xb9 0xff 0x0 0x0 0x0 0x7a 0x59 0x2 0xb9 0xff 0x0 0x0
+0x72 0x6 0x7a 0x83 0x0 0xb9 0xff 0x0 0x0 0x0 0x7b 0x33 0x2 0xa3 0xff 0x0 0x0
+0x73 0x6 0x7b 0x83 0x0 0xa3 0xff 0x0 0x0 0x0 0x7c 0x27 0x2 0xac 0xff 0x0 0x0
+0x74 0x6 0x7c 0x83 0x0 0xac 0xff 0x0 0x0 0x1 0x7d 0x56 0x0 0x0 0x0 0x0 0x0
+0x75 0x0 0x7d 0x29 0x2 0xb1 0xff 0x0 0x0 0x6 0x7d 0x83 0x0 0xb1 0xff 0x0 0x0
+0x76 0x0 0x7e 0x16 0x2 0xa9 0xff 0x2 0x0 0x6 0x7e 0x83 0x0 0xa9 0xff 0x2 0x0
+0x77 0x1 0x7e 0x55 0x0 0x0 0x0 0x0 0x0 0x0 0x7f 0xd 0x2 0xaf 0xff 0x0 0x0
+0x78 0x6 0x7f 0x83 0x0 0xaf 0xff 0x0 0x0 0x0 0x80 0x0 0x2 0xb5 0xff 0x0 0x0
+0x79 0x6 0x80 0x70 0x0 0xb5 0xff 0x0 0x0 0x1 0x80 0x54 0x0 0x0 0x0 0x0 0x0
+0x7a 0x0 0x81 0x7 0x2 0xb7 0xff 0x0 0x0 0x6 0x81 0x70 0x0 0xb7 0xff 0x0 0x0
+0x7b 0x0 0x82 0xa 0x2 0xc2 0xff 0x0 0x0 0x6 0x82 0x70 0x0 0xc2 0xff 0x0 0x0
+0x7c 0x4 0x83 0x83 0x0 0x15 0x0 0x0 0x0 0x0 0x83 0x6 0x2 0xc6 0xff 0x0 0x0
+0x7d 0x6 0x83 0x70 0x0 0xc6 0xff 0x0 0x0 0x0 0x84 0x0 0x2 0xc2 0xff 0x0 0x0
+0x7e 0x6 0x84 0x70 0x0 0xc2 0xff 0x0 0x0 0x0 0x85 0x9 0x2 0xba 0xff 0x0 0x0
+0x7f 0x6 0x85 0x70 0x0 0xba 0xff 0x0 0x0 0x1 0x85 0x54 0x0 0x0 0x0 0x0 0x0
+0x80 0x0 0x86 0x8 0x2 0xb6 0xff 0x0 0x0 0x6 0x86 0x70 0x0 0xb6 0xff 0x0 0x0
+0x81 0x0 0x87 0xfe 0x1 0xaf 0xff 0x0 0x0 0x6 0x87 0x70 0x0 0xaf 0xff 0x0 0x0
+0x82 0x1 0x88 0x55 0x0 0x0 0x0 0x0 0x0 0x0 0x88 0xfc 0x1 0xae 0xff 0x0 0x0
+0x83 0x6 0x88 0x70 0x0 0xae 0xff 0x0 0x0 0x0 0x89 0xf3 0x1 0xad 0xff 0x0 0x0
+0x84 0x6 0x89 0x70 0x0 0xad 0xff 0x0 0x0 0x0 0x8a 0xe4 0x1 0xae 0xff 0x0 0x0
+0x85 0x6 0x8a 0x70 0x0 0xae 0xff 0x0 0x0 0x1 0x8a 0x56 0x0 0x0 0x0 0x0 0x0
+0x86 0x0 0x8b 0xd3 0x1 0xad 0xff 0x0 0x0 0x6 0x8b 0x70 0x0 0xad 0xff 0x0 0x0
+0x87 0x0 0x8c 0xcd 0x1 0xb3 0xff 0x0 0x0 0x6 0x8c 0x70 0x0 0xb3 0xff 0x0 0x0
+0x88 0x1 0x8d 0x57 0x0 0x0 0x0 0x0 0x0 0x0 0x8d 0xcf 0x1 0xae 0xff 0x0 0x0
+0x89 0x6 0x8d 0x70 0x0 0xae 0xff 0x0 0x0 0x0 0x8e 0xdf 0x1 0xb0 0xff 0x0 0x0
+0x8a 0x6 0x8e 0x70 0x0 0xb0 0xff 0x0 0x0 0x0 0x8f 0xec 0x1 0xb9 0xff 0x2 0x0
+0x8b 0x6 0x8f 0x70 0x0 0xb9 0xff 0x2 0x0 0x0 0x90 0xfa 0x1 0xb5 0xff 0x0 0x0
+0x8c 0x6 0x90 0x70 0x0 0xb5 0xff 0x0 0x0 0x1 0x90 0x59 0x0 0x0 0x0 0x0 0x0
+0x8d 0x0 0x91 0x1 0x2 0xcd 0xff 0x0 0x0 0x6 0x91 0x70 0x0 0xcd 0xff 0x0 0x0
+0x8e 0x0 0x92 0xa 0x2 0xda 0xff 0x0 0x0 0x6 0x92 0x70 0x0 0xda 0xff 0x0 0x0
+0x8f 0x0 0x93 0xe 0x2 0xd7 0xff 0x0 0x0 0x6 0x93 0x70 0x0 0xd7 0xff 0x0 0x0
+0x90 0x1 0x94 0x5a 0x0 0x0 0x0 0x0 0x0 0x0 0x94 0x15 0x2 0xda 0xff 0x0 0x0
+0x91 0x6 0x94 0x70 0x0 0xda 0xff 0x0 0x0 0x0 0x95 0x13 0x2 0xed 0xff 0x0 0x0
+0x92 0x6 0x95 0x70 0x0 0xed 0xff 0x0 0x0 0x1 0x95 0x5b 0x0 0x0 0x0 0x0 0x0
+0x93 0x0 0x96 0xe1 0x1 0xf4 0xff 0x0 0x0 0x6 0x96 0x70 0x0 0xf4 0xff 0x0 0x0
+0x94 0x1 0x96 0x5c 0x0 0x0 0x0 0x0 0x0 0x0 0x97 0x38 0x2 0x4 0x0 0x0 0x0
+0x95 0x6 0x97 0x70 0x0 0x4 0x0 0x0 0x0 0x0 0x98 0x2f 0x2 0x13 0x0 0x0 0x0
+0x96 0x6 0x98 0x5d 0x0 0x13 0x0 0x0 0x0 0x1 0x99 0x5d 0x0 0x0 0x0 0x0 0x0
+0x97 0x0 0x99 0x2a 0x2 0x23 0x0 0x0 0x0 0x6 0x99 0x5d 0x0 0x23 0x0 0x0 0x0
+0x98 0x0 0x9a 0x28 0x2 0x25 0x0 0x0 0x0 0x6 0x9a 0x5d 0x0 0x25 0x0 0x0 0x0
+0x99 0x1 0x9a 0x5e 0x0 0x0 0x0 0x0 0x0 0x0 0x9b 0x22 0x2 0x2c 0x0 0x0 0x0
+0x9a 0x6 0x9b 0x5d 0x0 0x2c 0x0 0x0 0x0 0x1 0x9b 0x5f 0x0 0x0 0x0 0x0 0x0
+0x9b 0x0 0x9c 0x18 0x2 0x34 0x0 0x0 0x0 0x6 0x9c 0x5d 0x0 0x34 0x0 0x0 0x0
+0x9c 0x0 0x9d 0xe3 0x1 0x38 0x0 0x0 0x0 0x6 0x9d 0x5d 0x0 0x38 0x0 0x0 0x0
+0x9d 0x1 0x9e 0x60 0x0 0x0 0x0 0x0 0x0 0x0 0x9e 0x2f 0x2 0x50 0x0 0x0 0x0
+0x9e 0x6 0x9e 0x5d 0x0 0x50 0x0 0x0 0x0 0x1 0x9f 0x61 0x0 0x0 0x0 0x0 0x0
+0x9f 0x0 0x9f 0x23 0x2 0x5a 0x0 0x0 0x0 0x6 0x9f 0x5d 0x0 0x5a 0x0 0x0 0x0
+0xa0 0x0 0xa0 0x1d 0x2 0x6c 0x0 0x0 0x0 0x6 0xa0 0x5d 0x0 0x6c 0x0 0x0 0x0
+0xa1 0x1 0xa0 0x60 0x0 0x0 0x0 0x0 0x0 0x0 0xa1 0x15 0x2 0x72 0x0 0x0 0x0
+0xa2 0x6 0xa1 0x5d 0x0 0x72 0x0 0x0 0x0 0x0 0xa2 0xe 0x2 0x7f 0x0 0x0 0x0
+0xa3 0x6 0xa2 0x5d 0x0 0x7f 0x0 0x0 0x0 0x0 0xa3 0x6 0x2 0x88 0x0 0x0 0x0
+0xa4 0x6 0xa3 0x5d 0x0 0x88 0x0 0x0 0x0 0x1 0xa4 0x61 0x0 0x0 0x0 0x0 0x0
+0xa5 0x0 0xa4 0xfd 0x1 0x96 0x0 0x0 0x0 0x6 0xa4 0x5d 0x0 0x96 0x0 0x0 0x0
+0xa6 0x0 0xa5 0xc2 0x1 0x91 0x0 0x0 0x0 0x6 0xa5 0x5d 0x0 0x91 0x0 0x0 0x0
+0xa7 0x0 0xa6 0x0 0x2 0xaf 0x0 0x0 0x0 0x6 0xa6 0x5d 0x0 0xaf 0x0 0x0 0x0
+0xa8 0x0 0xa7 0xe4 0x1 0xb2 0x0 0x0 0x0 0x6 0xa7 0x5d 0x0 0xb2 0x0 0x0 0x0
+0xa9 0x1 0xa7 0x62 0x0 0x0 0x0 0x0 0x0 0x0 0xa8 0xc9 0x1 0xb3 0x0 0x0 0x0
+0xaa 0x6 0xa8 0x5d 0x0 0xb3 0x0 0x0 0x0 0x1 0xa9 0x61 0x0 0x0 0x0 0x0 0x0
+0xab 0x0 0xa9 0xaf 0x1 0xb6 0x0 0x2 0x0 0x6 0xa9 0x5d 0x0 0xb6 0x0 0x2 0x0
+0xac 0x0 0xaa 0xa2 0x1 0xbc 0x0 0x0 0x0 0x6 0xaa 0x5d 0x0 0xbc 0x0 0x0 0x0
+0xad 0x0 0xab 0x69 0x1 0xae 0x0 0x0 0x0 0x6 0xab 0x5d 0x0 0xae 0x0 0x0 0x0
+0xae 0x0 0xac 0x93 0x1 0xcf 0x0 0x0 0x0 0x6 0xac 0x5d 0x0 0xcf 0x0 0x0 0x0
+0xaf 0x1 0xac 0x62 0x0 0x0 0x0 0x0 0x0 0x0 0xad 0x4c 0x1 0xb8 0x0 0x0 0x0
+0xb0 0x6 0xad 0x5d 0x0 0xb8 0x0 0x0 0x0 0x1 0xad 0x61 0x0 0x0 0x0 0x0 0x0
+0xb1 0x0 0xae 0x43 0x1 0xc0 0x0 0x0 0x0 0x6 0xae 0x5d 0x0 0xc0 0x0 0x0 0x0
+0xb2 0x0 0xaf 0x60 0x1 0xdd 0x0 0x0 0x0 0x6 0xaf 0x5d 0x0 0xdd 0x0 0x0 0x0
+0xb3 0x0 0xb0 0x48 0x1 0xd5 0x0 0x0 0x0 0x6 0xb0 0x5d 0x0 0xd5 0x0 0x0 0x0
+0xb4 0x0 0xb1 0x3f 0x1 0xe0 0x0 0x0 0x0 0x6 0xb1 0x5d 0x0 0xe0 0x0 0x0 0x0
+0xb5 0x0 0xb2 0x1a 0x1 0xd4 0x0 0x0 0x0 0x6 0xb2 0x5d 0x0 0xd4 0x0 0x0 0x0
+0xb6 0x1 0xb2 0x61 0x0 0x0 0x0 0x0 0x0 0x0 0xb3 0x26 0x1 0xe7 0x0 0x0 0x0
+0xb7 0x6 0xb3 0x5d 0x0 0xe7 0x0 0x0 0x0 0x0 0xb4 0x47 0x1 0x4 0x1 0x0 0x0
+0xb8 0x6 0xb4 0x5d 0x0 0x4 0x1 0x0 0x0 0x0 0xb5 0x3c 0x1 0xff 0x0 0x0 0x0
+0xb9 0x6 0xb5 0x5d 0x0 0xff 0x0 0x0 0x0 0x1 0xb6 0x60 0x0 0x0 0x0 0x0 0x0
+0xba 0x0 0xb6 0x2f 0x1 0xf8 0x0 0x0 0x0 0x6 0xb6 0x5d 0x0 0xf8 0x0 0x0 0x0
+0xbb 0x0 0xb7 0x32 0x1 0xf9 0x0 0x0 0x0 0x6 0xb7 0x5d 0x0 0xf9 0x0 0x0 0x0
+0xbc 0x5 0xb7 0xb7 0x0 0xf9 0x0 0x0 0x0 0x1 0xb7 0x61 0x0 0x0 0x0 0x0 0x0
+0xbd 0x0 0xb8 0x32 0x1 0xf2 0x0 0x0 0x0 0x6 0xb8 0x5d 0x0 0xf2 0x0 0x0 0x0
+0xbe 0x0 0xb9 0x25 0x1 0xee 0x0 0x0 0x0 0x6 0xb9 0x5d 0x0 0xee 0x0 0x0 0x0
+0xbf 0x0 0xba 0x14 0x1 0xe3 0x0 0x0 0x0 0x6 0xba 0x5d 0x0 0xe3 0x0 0x0 0x0
+0xc0 0x1 0xbb 0x60 0x0 0x0 0x0 0x0 0x0 0x0 0xbb 0x0 0x1 0xd3 0x0 0x0 0x0
+0xc1 0x6 0xbb 0x5d 0x0 0xd3 0x0 0x0 0x0 0x0 0xbc 0xe4 0x0 0xbe 0x0 0x0 0x0
+0xc2 0x6 0xbc 0x5d 0x0 0xbe 0x0 0x0 0x0 0x1 0xbc 0x61 0x0 0x0 0x0 0x0 0x0
+0xc3 0x0 0xbd 0xc7 0x0 0xaa 0x0 0x0 0x0 0x6 0xbd 0x5d 0x0 0xaa 0x0 0x0 0x0
+0xc4 0x1 0xbd 0x62 0x0 0x0 0x0 0x0 0x0 0x0 0xbe 0xa0 0x0 0x8c 0x0 0x0 0x0
+0xc5 0x6 0xbe 0x5d 0x0 0x8c 0x0 0x0 0x0 0x0 0xbf 0xbc 0x0 0xa3 0x0 0x0 0x0
+0xc6 0x6 0xbf 0x5d 0x0 0xa3 0x0 0x0 0x0 0x1 0xc0 0x63 0x0 0x0 0x0 0x0 0x0
+0xc7 0x0 0xc0 0xbf 0x0 0xa4 0x0 0x0 0x0 0x6 0xc0 0x5d 0x0 0xa4 0x0 0x0 0x0
+0xc8 0x0 0xc1 0xbe 0x0 0x9f 0x0 0x0 0x0 0x6 0xc1 0x5d 0x0 0x9f 0x0 0x0 0x0
+0xc9 0x0 0xc2 0xb4 0x0 0x93 0x0 0x0 0x0 0x6 0xc2 0x5d 0x0 0x93 0x0 0x0 0x0
+0xca 0x1 0xc2 0x5c 0x0 0x0 0x0 0x0 0x0 0x0 0xc3 0xc6 0x0 0xa1 0x0 0x0 0x0
+0xcb 0x6 0xc3 0x5d 0x0 0xa1 0x0 0x0 0x0 0x0 0xc4 0xd0 0x0 0xa6 0x0 0x0 0x0
+0xcc 0x6 0xc4 0x5d 0x0 0xa6 0x0 0x0 0x0 0x1 0xc4 0x5b 0x0 0x0 0x0 0x0 0x0
+0xcd 0x0 0xc5 0xe8 0x0 0xb9 0x0 0x0 0x0 0x6 0xc5 0x5d 0x0 0xb9 0x0 0x0 0x0
+0xce 0x1 0xc6 0x5a 0x0 0x0 0x0 0x0 0x0 0x0 0xc6 0xe7 0x0 0xb7 0x0 0x0 0x0
+0xcf 0x6 0xc6 0x5d 0x0 0xb7 0x0 0x0 0x0 0x1 0xc7 0x5f 0x0 0x0 0x0 0x0 0x0
+0xd0 0x0 0xc7 0xe9 0x0 0xba 0x0 0x0 0x0 0x6 0xc7 0x5d 0x0 0xba 0x0 0x0 0x0
+0xd1 0x0 0xc8 0xf5 0x0 0xc6 0x0 0x0 0x0 0x6 0xc8 0x5d 0x0 0xc6 0x0 0x0 0x0
+0xd2 0x0 0xc9 0x2 0x1 0xcf 0x0 0x0 0x0 0x6 0xc9 0x5d 0x0 0xcf 0x0 0x0 0x0
+0xd3 0x1 0xc9 0x5e 0x0 0x0 0x0 0x0 0x0 0x0 0xca 0x13 0x1 0xdd 0x0 0x0 0x0
+0xd4 0x6 0xca 0x5d 0x0 0xdd 0x0 0x0 0x0 0x1 0xcb 0x5d 0x0 0x0 0x0 0x0 0x0
+0xd5 0x0 0xcb 0x18 0x1 0xdf 0x0 0x0 0x0 0x6 0xcb 0x5d 0x0 0xdf 0x0 0x0 0x0
+0xd6 0x1 0xcc 0x56 0x0 0x0 0x0 0x0 0x0 0x0 0xcc 0x27 0x1 0xea 0x0 0x0 0x0
+0xd7 0x6 0xcc 0x5d 0x0 0xea 0x0 0x0 0x0 0x0 0xcd 0x2a 0x1 0xed 0x0 0x0 0x0
+0xd8 0x6 0xcd 0x5d 0x0 0xed 0x0 0x0 0x0 0x0 0xce 0x35 0x1 0xf3 0x0 0x0 0x0
+0xd9 0x6 0xce 0x5d 0x0 0xf3 0x0 0x0 0x0 0x1 0xce 0x55 0x0 0x0 0x0 0x0 0x0
+0xda 0x0 0xcf 0x44 0x1 0xfe 0x0 0x0 0x0 0x6 0xcf 0x5d 0x0 0xfe 0x0 0x0 0x0
+0xdb 0x1 0xcf 0x44 0x0 0x0 0x0 0x0 0x0 0x0 0xd0 0x4f 0x1 0x9 0x1 0x0 0x0
+0xdc 0x6 0xd0 0x5d 0x0 0x9 0x1 0x0 0x0 0x1 0xd1 0x4a 0x0 0x0 0x0 0x0 0x0
+0xdd 0x0 0xd1 0x37 0x1 0xf2 0x0 0x0 0x0 0x6 0xd1 0x5d 0x0 0xf2 0x0 0x0 0x0
+0xde 0x0 0xd2 0x71 0x1 0x1e 0x1 0x0 0x0 0x6 0xd2 0x5d 0x0 0x1e 0x1 0x0 0x0
+0xdf 0x0 0xd3 0x42 0x1 0xf8 0x0 0xfe 0xff 0x6 0xd3 0x5d 0x0 0xf8 0x0 0xfe 0xff
+0xe0 0x1 0xd3 0x49 0x0 0x0 0x0 0x0 0x0 0x0 0xd4 0x59 0x1 0x8 0x1 0x0 0x0
+0xe1 0x6 0xd4 0x5d 0x0 0x8 0x1 0x0 0x0 0x1 0xd4 0x48 0x0 0x0 0x0 0x0 0x0
+0xe2 0x0 0xd5 0x75 0x1 0x22 0x1 0x0 0x0 0x6 0xd5 0x5d 0x0 0x22 0x1 0x0 0x0
+0xe3 0x1 0xd5 0x47 0x0 0x0 0x0 0x0 0x0 0x0 0xd6 0x64 0x1 0x10 0x1 0x0 0x0
+0xe4 0x6 0xd6 0x5d 0x0 0x10 0x1 0x0 0x0 0x0 0xd7 0x38 0x1 0xec 0x0 0xff 0xff
+0xe5 0x6 0xd7 0x5d 0x0 0xec 0x0 0xff 0xff 0x1 0xd8 0x48 0x0 0x0 0x0 0x0 0x0
+0xe6 0x0 0xd8 0x66 0x1 0x14 0x1 0xff 0xff 0x6 0xd8 0x5d 0x0 0x14 0x1 0xff 0xff
+0xe7 0x0 0xd9 0x58 0x1 0xc 0x1 0x0 0x0 0x6 0xd9 0x5d 0x0 0xc 0x1 0x0 0x0
+0xe8 0x1 0xd9 0x49 0x0 0x0 0x0 0x0 0x0 0x0 0xda 0x3b 0x1 0xf6 0x0 0x0 0x0
+0xe9 0x6 0xda 0x5d 0x0 0xf6 0x0 0x0 0x0 0x0 0xdb 0x53 0x1 0xa 0x1 0x0 0x0
+0xea 0x6 0xdb 0x5d 0x0 0xa 0x1 0x0 0x0 0x0 0xdc 0x38 0x1 0xf5 0x0 0x0 0x0
+0xeb 0x6 0xdc 0x5d 0x0 0xf5 0x0 0x0 0x0 0x4 0xdd 0x5a 0x0 0x15 0x0 0x0 0x0
+0xec 0x1 0xdd 0x4a 0x0 0x0 0x0 0x0 0x0 0x0 0xdd 0x7 0x1 0xd5 0x0 0x0 0x0
+0xed 0x6 0xdd 0x5d 0x0 0xd5 0x0 0x0 0x0 0x0 0xde 0x11 0x1 0xdb 0x0 0xfe 0xff
+0xee 0x6 0xde 0x5d 0x0 0xdb 0x0 0xfe 0xff 0x1 0xde 0x4b 0x0 0x0 0x0 0x0 0x0
+0xef 0x0 0xdf 0x7 0x1 0xd1 0x0 0x0 0x0 0x6 0xdf 0x5d 0x0 0xd1 0x0 0x0 0x0
+0xf0 0x1 0xdf 0x4c 0x0 0x0 0x0 0x0 0x0 0x0 0xe0 0xc 0x1 0xd4 0x0 0x0 0x0
+0xf1 0x6 0xe0 0x5d 0x0 0xd4 0x0 0x0 0x0 0x0 0xe1 0xd6 0x0 0xa9 0x0 0x0 0x0
+0xf2 0x6 0xe1 0x5d 0x0 0xa9 0x0 0x0 0x0 0x1 0xe2 0x4d 0x0 0x0 0x0 0x0 0x0
+0xf3 0x0 0xe2 0xdb 0x0 0xac 0x0 0x0 0x0 0x6 0xe2 0x5d 0x0 0xac 0x0 0x0 0x0
+0xf4 0x0 0xe4 0x2d 0x1 0xad 0x0 0x0 0x0 0x6 0xe4 0x5d 0x0 0xad 0x0 0x0 0x0
+0xf5 0x1 0xe4 0x4e 0x0 0x0 0x0 0x0 0x0 0x0 0xe6 0xf3 0x0 0x8b 0x0 0x0 0x0
+0xf6 0x6 0xe6 0x5d 0x0 0x8b 0x0 0x0 0x0 0x1 0xe6 0x4f 0x0 0x0 0x0 0x0 0x0
+0xf7 0x1 0xe8 0x51 0x0 0x0 0x0 0x0 0x0 0x1 0xeb 0x52 0x0 0x0 0x0 0x0 0x0
+0xf8 0x1 0xee 0x53 0x0 0x0 0x0 0x0 0x0 0x0 0xf0 0xf7 0x0 0x87 0x0 0x0 0x0
+0xf9 0x6 0xf0 0x74 0x0 0x87 0x0 0x0 0x0 0x1 0xf0 0x54 0x0 0x0 0x0 0x0 0x0
+0xfa 0x0 0xf2 0xd8 0x0 0x7f 0x0 0x0 0x0 0x6 0xf2 0x8c 0x0 0x7f 0x0 0x0 0x0
+0xfb 0x1 0xf2 0x55 0x0 0x0 0x0 0x0 0x0 0x0 0xf4 0xcb 0x0 0x7c 0x0 0x0 0x0
+0xfc 0x6 0xf4 0x8c 0x0 0x7c 0x0 0x0 0x0 0x1 0xf5 0x56 0x0 0x0 0x0 0x0 0x0
+0xfd 0x0 0xf6 0x1c 0x1 0x75 0x0 0x0 0x0 0x6 0xf6 0xa1 0x0 0x75 0x0 0x0 0x0
+0xfe 0x1 0xf6 0x58 0x0 0x0 0x0 0x0 0x0 0x1 0xf7 0x57 0x0 0x0 0x0 0x0 0x0
+0xff 0x0 0xf8 0x67 0x1 0x3a 0x0 0x2 0x0 0x6 0xf8 0xa1 0x0 0x3a 0x0 0x2 0x0
+0x0 0x0 0xf9 0x24 0x1 0x2 0x0 0x0 0x0 0x6 0xf9 0xa1 0x0 0x2 0x0 0x0 0x0
+0x1 0x1 0xfa 0x58 0x0 0x0 0x0 0x0 0x0 0x0 0xfa 0x19 0x1 0xd6 0xff 0x0 0x0
+0x2 0x6 0xfa 0xa1 0x0 0xd6 0xff 0x0 0x0 0x0 0xfb 0x1e 0x1 0xb9 0xff 0x0 0x0
+0x3 0x6 0xfb 0xa1 0x0 0xb9 0xff 0x0 0x0 0x0 0xfc 0x22 0x1 0xa6 0xff 0x0 0x0
+0x4 0x6 0xfc 0xa1 0x0 0xa6 0xff 0x0 0x0 0x1 0xfc 0x59 0x0 0x0 0x0 0x0 0x0
+0x5 0x0 0xfd 0x68 0x1 0x47 0xff 0x0 0x0 0x6 0xfd 0xa1 0x0 0x47 0xff 0x0 0x0
+0x6 0x0 0xfe 0x3e 0x1 0x58 0xff 0x0 0x0 0x6 0xfe 0xa1 0x0 0x58 0xff 0x0 0x0
+0x7 0x0 0xff 0x32 0x1 0x5f 0xff 0x0 0x0 0x6 0xff 0xa1 0x0 0x5f 0xff 0x0 0x0
+0x8 0x0 0x0 0x19 0x1 0x67 0xff 0x0 0x0 0x6 0x0 0xa1 0x0 0x67 0xff 0x0 0x0
+0x9 0x0 0x1 0xff 0x0 0x7f 0xff 0x0 0x0 0x6 0x1 0xa1 0x0 0x7f 0xff 0x0 0x0
+0xa 0x1 0x1 0x5a 0x0 0x0 0x0 0x0 0x0 0x0 0x2 0xf5 0x0 0x92 0xff 0x0 0x0
+0xb 0x6 0x2 0xa1 0x0 0x92 0xff 0x0 0x0 0x1 0x3 0x57 0x0 0x0 0x0 0x0 0x0
+0xc 0x0 0x4 0xe1 0x0 0x9f 0xff 0xfe 0xff 0x6 0x4 0xa1 0x0 0x9f 0xff 0xfe 0xff
+0xd 0x1 0x5 0x58 0x0 0x0 0x0 0x0 0x0 0x1 0xb 0x59 0x0 0x0 0x0 0x0 0x0
+0xe 0x0 0xc 0xa3 0x0 0x91 0xff 0x0 0x0 0x6 0xc 0xb8 0x0 0x91 0xff 0x0 0x0
+0xf 0x1 0xd 0x5a 0x0 0x0 0x0 0x0 0x0 0x1 0xe 0x60 0x0 0x0 0x0 0x0 0x0
+0x10 0x1 0x10 0x61 0x0 0x0 0x0 0x0 0x0 0x1 0x13 0x68 0x0 0x0 0x0 0x0 0x0
+0x11 0x1 0x14 0x6d 0x0 0x0 0x0 0x0 0x0 0x1 0x17 0x6c 0x0 0x0 0x0 0x0 0x0
+0x12 0x1 0x18 0x65 0x0 0x0 0x0 0x0 0x0 0x1 0x19 0x6a 0x0 0x0 0x0 0x0 0x0
+0x13 0x0 0x1b 0xe5 0x0 0xa0 0xff 0x0 0x0 0x6 0x1b 0x54 0x1 0xa0 0xff 0x0 0x0
+0x14 0x1 0x1c 0x6c 0x0 0x0 0x0 0x0 0x0 0x0 0x1d 0x12 0x1 0xa6 0xff 0x0 0x0
+0x15 0x6 0x1d 0x54 0x1 0xa6 0xff 0x0 0x0 0x1 0x1d 0x6d 0x0 0x0 0x0 0x0 0x0
+0x16 0x0 0x1e 0x10 0x1 0xaa 0xff 0x0 0x0 0x6 0x1e 0x6b 0x1 0xaa 0xff 0x0 0x0
+0x17 0x1 0x1e 0x69 0x0 0x0 0x0 0x0 0x0 0x0 0x1f 0x23 0x1 0xb2 0xff 0x0 0x0
+0x18 0x6 0x1f 0x6b 0x1 0xb2 0xff 0x0 0x0 0x0 0x20 0x46 0x1 0xac 0xff 0x0 0x0
+0x19 0x6 0x20 0x6b 0x1 0xac 0xff 0x0 0x0 0x1 0x21 0x6b 0x0 0x0 0x0 0x0 0x0
+0x1a 0x0 0x21 0x66 0x1 0xa7 0xff 0x0 0x0 0x6 0x21 0x4c 0x1 0xa7 0xff 0x0 0x0
+0x1b 0x1 0x22 0x6e 0x0 0x0 0x0 0x0 0x0 0x0 0x22 0x8f 0x1 0x9f 0xff 0x0 0x0
+0x1c 0x6 0x22 0x36 0x1 0x9f 0xff 0x0 0x0 0x0 0x23 0xdf 0x1 0x8b 0xff 0x0 0x0
+0x1d 0x6 0x23 0x36 0x1 0x8b 0xff 0x0 0x0 0x1 0x23 0x70 0x0 0x0 0x0 0x0 0x0
+0x1e 0x0 0x24 0x92 0x1 0x9d 0xff 0xff 0xff 0x6 0x24 0x19 0x1 0x9d 0xff 0xff 0xff
+0x1f 0x0 0x25 0x82 0x1 0xa1 0xff 0x0 0x0 0x6 0x25 0x2 0x1 0xa1 0xff 0x0 0x0
+0x20 0x1 0x25 0x6a 0x0 0x0 0x0 0x0 0x0 0x0 0x26 0x2a 0x1 0xb7 0xff 0x0 0x0
+0x21 0x6 0x26 0xef 0x0 0xb7 0xff 0x0 0x0 0x1 0x27 0x67 0x0 0x0 0x0 0x0 0x0
+0x22 0x0 0x27 0x6 0x1 0xbf 0xff 0x0 0x0 0x6 0x27 0xef 0x0 0xbf 0xff 0x0 0x0
+0x23 0x1 0x28 0x6e 0x0 0x0 0x0 0x0 0x0 0x0 0x29 0x1b 0x1 0xb9 0xff 0x0 0x0
+0x24 0x6 0x29 0xd8 0x0 0xb9 0xff 0x0 0x0 0x1 0x2a 0x74 0x0 0x0 0x0 0x0 0x0
+0x25 0x1 0x2b 0x70 0x0 0x0 0x0 0x0 0x0 0x0 0x2c 0xf9 0x0 0xbc 0xff 0x0 0x0
+0x26 0x6 0x2c 0xd8 0x0 0xbc 0xff 0x0 0x0 0x1 0x2d 0x72 0x0 0x0 0x0 0x0 0x0
+0x27 0x1 0x2f 0x6b 0x0 0x0 0x0 0x0 0x0 0x0 0x30 0xa5 0x0 0x86 0xff 0x0 0x0
+0x28 0x6 0x30 0xeb 0x0 0x86 0xff 0x0 0x0 0x1 0x30 0x64 0x0 0x0 0x0 0x0 0x0
+0x29 0x1 0x32 0x63 0x0 0x0 0x0 0x0 0x0 0x1 0x34 0x5c 0x0 0x0 0x0 0x0 0x0
+0x2a 0x1 0x35 0x57 0x0 0x0 0x0 0x0 0x0 0x1 0x36 0x54 0x0 0x0 0x0 0x0 0x0
+0x2b 0x0 0x38 0x78 0x0 0x7b 0xff 0x0 0x0 0x6 0x38 0x30 0x1 0x7b 0xff 0x0 0x0
+0x2c 0x1 0x39 0x4d 0x0 0x0 0x0 0x0 0x0 0x1 0x3a 0x4c 0x0 0x0 0x0 0x0 0x0
+0x2d 0x1 0x3b 0x45 0x0 0x0 0x0 0x0 0x0 0x1 0x3e 0x47 0x0 0x0 0x0 0x0 0x0
+0x2e 0x0 0x3f 0xbe 0x0 0xbb 0xff 0x0 0x0 0x6 0x3f 0x64 0x1 0xbb 0xff 0x0 0x0
+0x2f 0x1 0x3f 0x46 0x0 0x0 0x0 0x0 0x0 0x1 0x40 0x45 0x0 0x0 0x0 0x0 0x0
+0x30 0x0 0x41 0xd4 0x0 0xc4 0xff 0x0 0x0 0x6 0x41 0x81 0x1 0xc4 0xff 0x0 0x0
+0x31 0x1 0x43 0x44 0x0 0x0 0x0 0x0 0x0 0x0 0x43 0x37 0x1 0xb0 0xff 0x0 0x0
+0x32 0x6 0x43 0x97 0x1 0xb0 0xff 0x0 0x0 0x1 0x44 0x46 0x0 0x0 0x0 0x0 0x0
+0x33 0x0 0x44 0x39 0x1 0xab 0xff 0x0 0x0 0x6 0x44 0x97 0x1 0xab 0xff 0x0 0x0
+0x34 0x0 0x45 0x53 0x1 0x9f 0xff 0x0 0x0 0x6 0x45 0x97 0x1 0x9f 0xff 0x0 0x0
+0x35 0x1 0x45 0x48 0x0 0x0 0x0 0x0 0x0 0x0 0x46 0x74 0x1 0x89 0xff 0x0 0x0
+0x36 0x6 0x46 0x7b 0x1 0x89 0xff 0x0 0x0 0x0 0x47 0x6c 0x1 0x90 0xff 0x0 0x0
+0x37 0x6 0x47 0x5c 0x1 0x90 0xff 0x0 0x0 0x1 0x48 0x47 0x0 0x0 0x0 0x0 0x0
+0x38 0x0 0x48 0x4e 0x1 0x99 0xff 0x0 0x0 0x6 0x48 0x3f 0x1 0x99 0xff 0x0 0x0
+0x39 0x0 0x49 0x6d 0x1 0x95 0xff 0x0 0x0 0x6 0x49 0x25 0x1 0x95 0xff 0x0 0x0
+0x3a 0x1 0x4a 0x48 0x0 0x0 0x0 0x0 0x0 0x1 0x4c 0x49 0x0 0x0 0x0 0x0 0x0
+0x3b 0x1 0x4e 0x4a 0x0 0x0 0x0 0x0 0x0 0x0 0x4e 0x4a 0x6 0xc3 0xfd 0x0 0x0
+0x3c 0x6 0x4e 0xdf 0x0 0xc3 0xfd 0x0 0x0 0x1 0x4f 0x4b 0x0 0x0 0x0 0x0 0x0
+0x3d 0x0 0x4f 0x62 0x4 0x58 0xfe 0x0 0x0 0x6 0x4f 0xdf 0x0 0x58 0xfe 0x0 0x0
+0x3e 0x0 0x50 0x44 0x3 0xaa 0xfe 0x0 0x0 0x6 0x50 0xdf 0x0 0xaa 0xfe 0x0 0x0
+0x3f 0x0 0x51 0x95 0x2 0xe2 0xfe 0x0 0x0 0x6 0x51 0xc1 0x0 0xe2 0xfe 0x0 0x0
+0x40 0x1 0x51 0x4c 0x0 0x0 0x0 0x0 0x0 0x0 0x52 0x24 0x2 0x8 0xff 0x0 0x0
+0x41 0x6 0x52 0xc1 0x0 0x8 0xff 0x0 0x0 0x1 0x52 0x4d 0x0 0x0 0x0 0x0 0x0
+0x42 0x0 0x53 0xcd 0x1 0x2c 0xff 0x0 0x0 0x6 0x53 0xa4 0x0 0x2c 0xff 0x0 0x0
+0x43 0x0 0x54 0x96 0x1 0x4b 0xff 0x0 0x0 0x6 0x54 0xa4 0x0 0x4b 0xff 0x0 0x0
+0x44 0x0 0x55 0x66 0x1 0x5d 0xff 0x0 0x0 0x6 0x55 0xa4 0x0 0x5d 0xff 0x0 0x0
+0x45 0x0 0x56 0x44 0x1 0x6e 0xff 0x0 0x0 0x6 0x56 0xa4 0x0 0x6e 0xff 0x0 0x0
+0x46 0x0 0x57 0x1c 0x1 0x7b 0xff 0x1 0x0 0x6 0x57 0x90 0x0 0x7b 0xff 0x1 0x0
+0x47 0x1 0x57 0x4e 0x0 0x0 0x0 0x0 0x0 0x0 0x58 0xfc 0x0 0x8a 0xff 0x0 0x0
+0x48 0x6 0x58 0x90 0x0 0x8a 0xff 0x0 0x0 0x1 0x59 0x4f 0x0 0x0 0x0 0x0 0x0
+0x49 0x0 0x59 0xe2 0x0 0x97 0xff 0x0 0x0 0x6 0x59 0x90 0x0 0x97 0xff 0x0 0x0
+0x4a 0x0 0x5a 0xd8 0x0 0x9d 0xff 0x0 0x0 0x6 0x5a 0x90 0x0 0x9d 0xff 0x0 0x0
+0x4b 0x0 0x5b 0xc6 0x0 0xa5 0xff 0x0 0x0 0x6 0x5b 0x90 0x0 0xa5 0xff 0x0 0x0
+0x4c 0x1 0x5b 0x50 0x0 0x0 0x0 0x0 0x0 0x1 0x5c 0x57 0x0 0x0 0x0 0x0 0x0
+0x4d 0x0 0x5d 0xef 0x0 0x92 0xff 0x0 0x0 0x6 0x5d 0x90 0x0 0x92 0xff 0x0 0x0
+0x4e 0x1 0x5d 0x50 0x0 0x0 0x0 0x0 0x0 0x1 0x60 0x4f 0x0 0x0 0x0 0x0 0x0
+0x4f 0x0 0x61 0xdb 0x0 0x9d 0xff 0x0 0x0 0x6 0x61 0x90 0x0 0x9d 0xff 0x0 0x0
+0x50 0x1 0x61 0x4e 0x0 0x0 0x0 0x0 0x0 0x1 0x62 0x48 0x0 0x0 0x0 0x0 0x0
+0x51 0x1 0x65 0x41 0x0 0x0 0x0 0x0 0x0 0x1 0x66 0x43 0x0 0x0 0x0 0x0 0x0
+0x52 0x1 0x67 0x44 0x0 0x0 0x0 0x0 0x0 0x1 0x6a 0x4b 0x0 0x0 0x0 0x0 0x0
+0x53 0x1 0x6b 0x4c 0x0 0x0 0x0 0x0 0x0 0x1 0x6c 0x47 0x0 0x0 0x0 0x0 0x0
+0x54 0x1 0x6e 0x4e 0x0 0x0 0x0 0x0 0x0 0x1 0x70 0x51 0x0 0x0 0x0 0x0 0x0
+0x55 0x1 0x71 0x52 0x0 0x0 0x0 0x0 0x0 0x0 0x72 0xc5 0x0 0x99 0xff 0x1 0x0
+0x56 0x6 0x72 0x13 0x1 0x99 0xff 0x1 0x0 0x0 0x74 0xec 0x0 0xc2 0xff 0x1 0x0
+0x57 0x6 0x74 0x2f 0x1 0xc2 0xff 0x1 0x0 0x1 0x74 0x50 0x0 0x0 0x0 0x0 0x0
+0x58 0x1 0x76 0x52 0x0 0x0 0x0 0x0 0x0 0x0 0x76 0x2d 0x1 0x2f 0x0 0x0 0x0
+0x59 0x6 0x76 0x44 0x1 0x2f 0x0 0x0 0x0 0x0 0x77 0xeb 0x0 0x65 0x0 0x0 0x0
+0x5a 0x6 0x77 0x44 0x1 0x65 0x0 0x0 0x0 0x0 0x78 0xe9 0x0 0x67 0x0 0x0 0x0
+0x5b 0x6 0x78 0x44 0x1 0x67 0x0 0x0 0x0 0x1 0x78 0x51 0x0 0x0 0x0 0x0 0x0
+0x5c 0x0 0x79 0xdb 0x0 0xae 0x0 0x0 0x0 0x6 0x79 0x44 0x1 0xae 0x0 0x0 0x0
+0x5d 0x1 0x79 0x50 0x0 0x0 0x0 0x0 0x0 0x0 0x7a 0xe5 0x0 0xf4 0x0 0x2 0x0
+0x5e 0x6 0x7a 0x26 0x1 0xf4 0x0 0x2 0x0 0x0 0x7b 0xe3 0x0 0xfc 0x0 0x0 0x0
+0x5f 0x6 0x7b 0x13 0x1 0xfc 0x0 0x0 0x0 0x0 0x7c 0xc2 0x0 0xd 0x1 0x0 0x0
+0x60 0x6 0x7c 0xff 0x0 0xd 0x1 0x0 0x0 0x0 0x7d 0xc2 0x0 0x4c 0x1 0x0 0x0
+0x61 0x6 0x7d 0xff 0x0 0x4c 0x1 0x0 0x0 0x1 0x7d 0x4f 0x0 0x0 0x0 0x0 0x0
+0x62 0x0 0x7e 0x96 0x0 0x43 0x1 0x0 0x0 0x6 0x7e 0xe7 0x0 0x43 0x1 0x0 0x0
+0x63 0x1 0x7e 0x52 0x0 0x0 0x0 0x0 0x0 0x0 0x7f 0x48 0x0 0x5d 0x1 0x0 0x0
+0x64 0x6 0x7f 0xd4 0x0 0x5d 0x1 0x0 0x0 0x1 0x7f 0x55 0x0 0x0 0x0 0x0 0x0
+0x65 0x0 0x80 0xe8 0xff 0x63 0x1 0x0 0x0 0x6 0x80 0xd4 0x0 0x63 0x1 0x0 0x0
+0x66 0x0 0x81 0x9a 0xff 0x7c 0x1 0x0 0x0 0x6 0x81 0xb5 0x0 0x7c 0x1 0x0 0x0
+0x67 0x1 0x82 0x56 0x0 0x0 0x0 0x0 0x0 0x0 0x82 0x69 0xff 0x60 0x1 0x0 0x0
+0x68 0x6 0x82 0xb5 0x0 0x60 0x1 0x0 0x0 0x0 0x83 0x67 0xff 0x2b 0x1 0x0 0x0
+0x69 0x6 0x83 0xb5 0x0 0x2b 0x1 0x0 0x0 0x1 0x83 0x57 0x0 0x0 0x0 0x0 0x0
+0x6a 0x0 0x84 0x5f 0xff 0x1f 0x1 0x0 0x0 0x6 0x84 0x9f 0x0 0x1f 0x1 0x0 0x0
+0x6b 0x1 0x84 0x53 0x0 0x0 0x0 0x0 0x0 0x0 0x85 0x62 0xff 0xf 0x1 0x0 0x0
+0x6c 0x6 0x85 0x9f 0x0 0xf 0x1 0x0 0x0 0x0 0x86 0x6a 0xff 0xf8 0x0 0x0 0x0
+0x6d 0x6 0x86 0x9f 0x0 0xf8 0x0 0x0 0x0 0x1 0x87 0x51 0x0 0x0 0x0 0x0 0x0
+0x6e 0x0 0x87 0xd6 0xfe 0xca 0x1 0x0 0x0 0x6 0x87 0x9f 0x0 0xca 0x1 0x0 0x0
+0x6f 0x1 0x88 0x4b 0x0 0x0 0x0 0x0 0x0 0x0 0x88 0xba 0xff 0x7d 0x0 0x0 0x0
+0x70 0x6 0x88 0x8c 0x0 0x7d 0x0 0x0 0x0 0x0 0x89 0xc1 0xff 0x96 0x0 0x0 0x0
+0x71 0x6 0x89 0x8c 0x0 0x96 0x0 0x0 0x0 0x1 0x89 0x4c 0x0 0x0 0x0 0x0 0x0
+0x72 0x0 0x8a 0xc5 0xff 0xb1 0x0 0x0 0x0 0x6 0x8a 0x8c 0x0 0xb1 0x0 0x0 0x0
+0x73 0x0 0x8b 0xcf 0xff 0xbf 0x0 0x0 0x0 0x6 0x8b 0x8c 0x0 0xbf 0x0 0x0 0x0
+0x74 0x0 0x8c 0xda 0xff 0xce 0x0 0x0 0x0 0x6 0x8c 0x8c 0x0 0xce 0x0 0x0 0x0
+0x75 0x1 0x8d 0x47 0x0 0x0 0x0 0x0 0x0 0x0 0x8d 0xd6 0xff 0xd0 0x0 0x0 0x0
+0x76 0x6 0x8d 0x8c 0x0 0xd0 0x0 0x0 0x0 0x0 0x8e 0xd8 0xff 0xd6 0x0 0x0 0x0
+0x77 0x6 0x8e 0x8c 0x0 0xd6 0x0 0x0 0x0 0x1 0x8e 0x43 0x0 0x0 0x0 0x0 0x0
+0x78 0x0 0x8f 0xe7 0xff 0xe3 0x0 0x0 0x0 0x6 0x8f 0x8c 0x0 0xe3 0x0 0x0 0x0
+0x79 0x0 0x90 0xfe 0xff 0xee 0x0 0x1 0x0 0x6 0x90 0x8c 0x0 0xee 0x0 0x1 0x0
+0x7a 0x1 0x90 0x41 0x0 0x0 0x0 0x0 0x0 0x0 0x91 0x10 0x0 0xf9 0x0 0x0 0x0
+0x7b 0x6 0x91 0x8c 0x0 0xf9 0x0 0x0 0x0 0x1 0x92 0x3f 0x0 0x0 0x0 0x0 0x0
+0x7c 0x0 0x92 0x17 0x0 0xe 0x1 0x0 0x0 0x6 0x92 0x8c 0x0 0xe 0x1 0x0 0x0
+0x7d 0x1 0x93 0x45 0x0 0x0 0x0 0x0 0x0 0x0 0x93 0x1b 0x0 0x22 0x1 0x1 0x0
+0x7e 0x6 0x93 0x8c 0x0 0x22 0x1 0x1 0x0 0x0 0x94 0x24 0x0 0x54 0x1 0x0 0x0
+0x7f 0x6 0x94 0x8c 0x0 0x54 0x1 0x0 0x0 0x0 0x95 0x1c 0x0 0x33 0x1 0x0 0x0
+0x80 0x6 0x95 0x8c 0x0 0x33 0x1 0x0 0x0 0x1 0x95 0x48 0x0 0x0 0x0 0x0 0x0
+0x81 0x0 0x96 0x29 0x0 0x49 0x1 0x0 0x0 0x6 0x96 0x8c 0x0 0x49 0x1 0x0 0x0
+0x82 0x1 0x96 0x47 0x0 0x0 0x0 0x0 0x0 0x0 0x97 0x2e 0x0 0x56 0x1 0x0 0x0
+0x83 0x6 0x97 0x8c 0x0 0x56 0x1 0x0 0x0 0x1 0x98 0x4e 0x0 0x0 0x0 0x0 0x0
+0x84 0x0 0x98 0x2e 0x0 0x5f 0x1 0x0 0x0 0x6 0x98 0x77 0x0 0x5f 0x1 0x0 0x0
+0x85 0x0 0x99 0x2a 0x0 0x86 0x1 0x0 0x0 0x6 0x99 0x77 0x0 0x86 0x1 0x0 0x0
+0x86 0x4 0x99 0xbc 0x0 0x86 0x1 0x0 0x0 0x0 0x9a 0x21 0x0 0x5c 0x1 0x0 0x0
+0x87 0x6 0x9a 0x77 0x0 0x5c 0x1 0x0 0x0 0x1 0x9a 0x4f 0x0 0x0 0x0 0x0 0x0
+0x88 0x0 0x9b 0x26 0x0 0x6e 0x1 0x0 0x0 0x6 0x9b 0x77 0x0 0x6e 0x1 0x0 0x0
+0x89 0x1 0x9b 0x50 0x0 0x0 0x0 0x0 0x0 0x0 0x9c 0x2f 0x0 0x7d 0x1 0x0 0x0
+0x8a 0x6 0x9c 0x77 0x0 0x7d 0x1 0x0 0x0 0x1 0x9d 0x52 0x0 0x0 0x0 0x0 0x0
+0x8b 0x0 0x9d 0x43 0x0 0x7f 0x1 0x0 0x0 0x6 0x9d 0x77 0x0 0x7f 0x1 0x0 0x0
+0x8c 0x0 0x9e 0x92 0x0 0x82 0x1 0x0 0x0 0x6 0x9e 0x77 0x0 0x82 0x1 0x0 0x0
+0x8d 0x1 0x9f 0x53 0x0 0x0 0x0 0x0 0x0 0x0 0x9f 0xba 0x0 0x87 0x1 0x0 0x0
+0x8e 0x6 0x9f 0x77 0x0 0x87 0x1 0x0 0x0 0x0 0xa0 0xe4 0x0 0xae 0x1 0x0 0x0
+0x8f 0x6 0xa0 0x77 0x0 0xae 0x1 0x0 0x0 0x1 0xa0 0x54 0x0 0x0 0x0 0x0 0x0
+0x90 0x0 0xa1 0xd6 0x0 0x77 0x1 0x0 0x0 0x6 0xa1 0x63 0x0 0x77 0x1 0x0 0x0
+0x91 0x1 0xa1 0x55 0x0 0x0 0x0 0x0 0x0 0x0 0xa2 0xe5 0x0 0x81 0x1 0x0 0x0
+0x92 0x6 0xa2 0x63 0x0 0x81 0x1 0x0 0x0 0x0 0xa3 0xeb 0x0 0x89 0x1 0x0 0x0
+0x93 0x6 0xa3 0x63 0x0 0x89 0x1 0x0 0x0 0x1 0xa4 0x54 0x0 0x0 0x0 0x0 0x0
+0x94 0x0 0xa4 0xf2 0x0 0x91 0x1 0x0 0x0 0x6 0xa4 0x63 0x0 0x91 0x1 0x0 0x0
+0x95 0x0 0xa5 0xef 0x0 0x98 0x1 0x1 0x0 0x6 0xa5 0x63 0x0 0x98 0x1 0x1 0x0
+0x96 0x1 0xa5 0x55 0x0 0x0 0x0 0x0 0x0 0x0 0xa6 0xef 0x0 0x99 0x1 0x0 0x0
+0x97 0x6 0xa6 0x63 0x0 0x99 0x1 0x0 0x0 0x1 0xa6 0x54 0x0 0x0 0x0 0x0 0x0
+0x98 0x0 0xa7 0xed 0x0 0x9a 0x1 0x0 0x0 0x6 0xa7 0x63 0x0 0x9a 0x1 0x0 0x0
+0x99 0x5 0xa7 0xf0 0x0 0x9a 0x1 0x0 0x0 0x0 0xa8 0xec 0x0 0x9a 0x1 0x0 0x0
+0x9a 0x6 0xa8 0x63 0x0 0x9a 0x1 0x0 0x0 0x1 0xa9 0x4d 0x0 0x0 0x0 0x0 0x0
+0x9b 0x0 0xa9 0xa 0x1 0xc2 0x1 0x0 0x0 0x6 0xa9 0x63 0x0 0xc2 0x1 0x0 0x0
+0x9c 0x1 0xaa 0x49 0x0 0x0 0x0 0x0 0x0 0x0 0xaa 0xeb 0x0 0x86 0x1 0x0 0x0
+0x9d 0x6 0xaa 0x63 0x0 0x86 0x1 0x0 0x0 0x0 0xab 0x9 0x1 0xb5 0x1 0x0 0x0
+0x9e 0x6 0xab 0x63 0x0 0xb5 0x1 0x0 0x0 0x1 0xab 0x4a 0x0 0x0 0x0 0x0 0x0
+0x9f 0x0 0xac 0xe9 0x0 0x7d 0x1 0x0 0x0 0x6 0xac 0x63 0x0 0x7d 0x1 0x0 0x0
+0xa0 0x0 0xad 0xf1 0x0 0x89 0x1 0x0 0x0 0x6 0xad 0x50 0x0 0x89 0x1 0x0 0x0
+0xa1 0x1 0xad 0x51 0x0 0x0 0x0 0x0 0x0 0x0 0xae 0xf1 0x0 0x8f 0x1 0x0 0x0
+0xa2 0x6 0xae 0x50 0x0 0x8f 0x1 0x0 0x0 0x1 0xaf 0x52 0x0 0x0 0x0 0x0 0x0
+0xa3 0x0 0xaf 0x7 0x1 0xb8 0x1 0x0 0x0 0x6 0xaf 0x50 0x0 0xb8 0x1 0x0 0x0
+0xa4 0x0 0xb0 0xf7 0x0 0xa1 0x1 0x0 0x0 0x6 0xb0 0x50 0x0 0xa1 0x1 0x0 0x0
+0xa5 0x1 0xb0 0x51 0x0 0x0 0x0 0x0 0x0 0x0 0xb1 0xd5 0x0 0x6c 0x1 0x0 0x0
+0xa6 0x6 0xb1 0x50 0x0 0x6c 0x1 0x0 0x0 0x0 0xb2 0xf4 0x0 0x9e 0x1 0x0 0x0
+0xa7 0x6 0xb2 0x50 0x0 0x9e 0x1 0x0 0x0 0x1 0xb2 0x54 0x0 0x0 0x0 0x0 0x0
+0xa8 0x0 0xb3 0xed 0x0 0x92 0x1 0x0 0x0 0x6 0xb3 0x50 0x0 0x92 0x1 0x0 0x0
+0xa9 0x1 0xb3 0x57 0x0 0x0 0x0 0x0 0x0 0x0 0xb4 0xd5 0x0 0x69 0x1 0x0 0x0
+0xaa 0x6 0xb4 0x50 0x0 0x69 0x1 0x0 0x0 0x1 0xb5 0x56 0x0 0x0 0x0 0x0 0x0
+0xab 0x0 0xb5 0xe2 0x0 0x7f 0x1 0x0 0x0 0x6 0xb5 0x50 0x0 0x7f 0x1 0x0 0x0
+0xac 0x0 0xb6 0xde 0x0 0x80 0x1 0x0 0x0 0x6 0xb6 0x50 0x0 0x80 0x1 0x0 0x0
+0xad 0x0 0xb7 0xe1 0x0 0x83 0x1 0x0 0x0 0x6 0xb7 0x50 0x0 0x83 0x1 0x0 0x0
+0xae 0x0 0xb8 0xdd 0x0 0x7b 0x1 0x0 0x0 0x6 0xb8 0x50 0x0 0x7b 0x1 0x0 0x0
+0xaf 0x1 0xb8 0x54 0x0 0x0 0x0 0x0 0x0 0x0 0xb9 0xf2 0x0 0x9c 0x1 0x0 0x0
+0xb0 0x6 0xb9 0x50 0x0 0x9c 0x1 0x0 0x0 0x1 0xba 0x56 0x0 0x0 0x0 0x0 0x0
+0xb1 0x0 0xba 0xd6 0x0 0x61 0x1 0x0 0x0 0x6 0xba 0x50 0x0 0x61 0x1 0x0 0x0
+0xb2 0x0 0xbb 0xf8 0x0 0x94 0x1 0x0 0x0 0x6 0xbb 0x50 0x0 0x94 0x1 0x0 0x0
+0xb3 0x0 0xbc 0xd7 0x0 0x60 0x1 0x0 0x0 0x6 0xbc 0x50 0x0 0x60 0x1 0x0 0x0
+0xb4 0x0 0xbd 0xfb 0x0 0x96 0x1 0x0 0x0 0x6 0xbd 0x50 0x0 0x96 0x1 0x0 0x0
+0xb5 0x0 0xbe 0xf2 0x0 0x8c 0x1 0x0 0x0 0x6 0xbe 0x50 0x0 0x8c 0x1 0x0 0x0
+0xb6 0x1 0xbe 0x58 0x0 0x0 0x0 0x0 0x0 0x0 0xbf 0xd6 0x0 0x61 0x1 0x0 0x0
+0xb7 0x6 0xbf 0x50 0x0 0x61 0x1 0x0 0x0 0x0 0xc0 0xe0 0x0 0x73 0x1 0x0 0x0
+0xb8 0x6 0xc0 0x50 0x0 0x73 0x1 0x0 0x0 0x0 0xc1 0xe1 0x0 0x78 0x1 0x0 0x0
+0xb9 0x6 0xc1 0x50 0x0 0x78 0x1 0x0 0x0 0x0 0xc2 0xe1 0x0 0x77 0x1 0x0 0x0
+0xba 0x6 0xc2 0x50 0x0 0x77 0x1 0x0 0x0 0x1 0xc2 0x5a 0x0 0x0 0x0 0x0 0x0
+0xbb 0x0 0xc3 0xe0 0x0 0x75 0x1 0x0 0x0 0x6 0xc3 0x50 0x0 0x75 0x1 0x0 0x0
+0xbc 0x1 0xc3 0x5b 0x0 0x0 0x0 0x0 0x0 0x0 0xc4 0xe3 0x0 0x7d 0x1 0x0 0x0
+0xbd 0x6 0xc4 0x50 0x0 0x7d 0x1 0x0 0x0 0x0 0xc5 0xe5 0x0 0x7f 0x1 0x0 0x0
+0xbe 0x6 0xc5 0x50 0x0 0x7f 0x1 0x0 0x0 0x1 0xc6 0x5c 0x0 0x0 0x0 0x0 0x0
+0xbf 0x0 0xc6 0xe4 0x0 0x7e 0x1 0x0 0x0 0x6 0xc6 0x50 0x0 0x7e 0x1 0x0 0x0
+0xc0 0x0 0xc7 0xe5 0x0 0x7f 0x1 0x0 0x0 0x6 0xc7 0x50 0x0 0x7f 0x1 0x0 0x0
+0xc1 0x1 0xc7 0x5d 0x0 0x0 0x0 0x0 0x0 0x0 0xc8 0xfb 0x0 0xa6 0x1 0x0 0x0
+0xc2 0x6 0xc8 0x50 0x0 0xa6 0x1 0x0 0x0 0x1 0xc8 0x5e 0x0 0x0 0x0 0x0 0x0
+0xc3 0x0 0xc9 0xd0 0x0 0x6e 0x1 0x0 0x0 0x6 0xc9 0x50 0x0 0x6e 0x1 0x0 0x0
+0xc4 0x0 0xca 0xdc 0x0 0x7a 0x1 0x0 0x0 0x6 0xca 0x50 0x0 0x7a 0x1 0x0 0x0
+0xc5 0x1 0xcb 0x5f 0x0 0x0 0x0 0x0 0x0 0x0 0xcb 0xe0 0x0 0x7d 0x1 0x0 0x0
+0xc6 0x6 0xcb 0x50 0x0 0x7d 0x1 0x0 0x0 0x1 0xcc 0x60 0x0 0x0 0x0 0x0 0x0
+0xc7 0x0 0xcc 0xe2 0x0 0x7f 0x1 0x0 0x0 0x6 0xcc 0x50 0x0 0x7f 0x1 0x0 0x0
+0xc8 0x0 0xcd 0xe4 0x0 0x80 0x1 0x0 0x0 0x6 0xcd 0x50 0x0 0x80 0x1 0x0 0x0
+0xc9 0x1 0xcd 0x61 0x0 0x0 0x0 0x0 0x0 0x0 0xce 0xe4 0x0 0x81 0x1 0x0 0x0
+0xca 0x6 0xce 0x50 0x0 0x81 0x1 0x0 0x0 0x0 0xcf 0xe2 0x0 0x7e 0x1 0x0 0x0
+0xcb 0x6 0xcf 0x50 0x0 0x7e 0x1 0x0 0x0 0x1 0xd0 0x60 0x0 0x0 0x0 0x0 0x0
+0xcc 0x0 0xd0 0xf6 0x0 0x9e 0x1 0x0 0x0 0x6 0xd0 0x50 0x0 0x9e 0x1 0x0 0x0
+0xcd 0x0 0xd1 0xd1 0x0 0x60 0x1 0x0 0x0 0x6 0xd1 0x50 0x0 0x60 0x1 0x0 0x0
+0xce 0x0 0xd2 0xd2 0x0 0x61 0x1 0x0 0x0 0x6 0xd2 0x50 0x0 0x61 0x1 0x0 0x0
+0xcf 0x1 0xd2 0x5f 0x0 0x0 0x0 0x0 0x0 0x0 0xd3 0xd5 0x0 0x67 0x1 0x0 0x0
+0xd0 0x6 0xd3 0x50 0x0 0x67 0x1 0x0 0x0 0x0 0xd4 0xec 0x0 0x8d 0x1 0x0 0x0
+0xd1 0x6 0xd4 0x50 0x0 0x8d 0x1 0x0 0x0 0x1 0xd4 0x5e 0x0 0x0 0x0 0x0 0x0
+0xd2 0x0 0xd5 0xc9 0x0 0x53 0x1 0x0 0x0 0x6 0xd5 0x50 0x0 0x53 0x1 0x0 0x0
+0xd3 0x1 0xd6 0x5c 0x0 0x0 0x0 0x0 0x0 0x0 0xd6 0xcd 0x0 0x58 0x1 0x0 0x0
+0xd4 0x6 0xd6 0x50 0x0 0x58 0x1 0x0 0x0 0x1 0xd7 0x5b 0x0 0x0 0x0 0x0 0x0
+0xd5 0x0 0xd7 0xca 0x0 0x54 0x1 0x0 0x0 0x6 0xd7 0x50 0x0 0x54 0x1 0x0 0x0
+0xd6 0x0 0xd8 0xd2 0x0 0x5e 0x1 0x0 0x0 0x6 0xd8 0x50 0x0 0x5e 0x1 0x0 0x0
+0xd7 0x0 0xd9 0x2c 0x1 0x15 0x1 0x0 0x0 0x6 0xd9 0x50 0x0 0x15 0x1 0x0 0x0
+0xd8 0x0 0xda 0x60 0x1 0x1c 0x1 0x0 0x0 0x6 0xda 0x50 0x0 0x1c 0x1 0x0 0x0
+0xd9 0x1 0xda 0x55 0x0 0x0 0x0 0x0 0x0 0x0 0xdb 0x5d 0x1 0xd6 0x0 0x0 0x0
+0xda 0x6 0xdb 0x50 0x0 0xd6 0x0 0x0 0x0 0x4 0xdc 0x43 0x0 0xd6 0x0 0x0 0x0
+0xdb 0x1 0xdc 0x58 0x0 0x0 0x0 0x0 0x0 0x0 0xdc 0x9c 0x1 0xc8 0x0 0x0 0x0
+0xdc 0x6 0xdc 0x50 0x0 0xc8 0x0 0x0 0x0 0x0 0xdd 0x70 0x1 0x8e 0x0 0x0 0x0
+0xdd 0x6 0xdd 0x50 0x0 0x8e 0x0 0x0 0x0 0x0 0xde 0x83 0x1 0x86 0x0 0x0 0x0
+0xde 0x6 0xde 0x50 0x0 0x86 0x0 0x0 0x0 0x0 0xdf 0x93 0x1 0x86 0x0 0x0 0x0
+0xdf 0x6 0xdf 0x50 0x0 0x86 0x0 0x0 0x0 0x1 0xdf 0x57 0x0 0x0 0x0 0x0 0x0
+0xe0 0x0 0xe0 0x74 0x1 0x7d 0x0 0x0 0x0 0x6 0xe0 0x50 0x0 0x7d 0x0 0x0 0x0
+0xe1 0x0 0xe1 0x2d 0x1 0x7c 0x0 0x0 0x0 0x6 0xe1 0x50 0x0 0x7c 0x0 0x0 0x0
+0xe2 0x0 0xe3 0xf0 0x0 0xa5 0x0 0xff 0xff 0x6 0xe3 0x50 0x0 0xa5 0x0 0xff 0xff
+0xe3 0x1 0xe4 0x58 0x0 0x0 0x0 0x0 0x0 0x0 0xe4 0x73 0x0 0x99 0x0 0x0 0x0
+0xe4 0x6 0xe4 0x50 0x0 0x99 0x0 0x0 0x0 0x0 0xe6 0x14 0x0 0xb5 0x0 0x0 0x0
+0xe5 0x6 0xe6 0x50 0x0 0xb5 0x0 0x0 0x0 0x0 0xe8 0xfb 0xff 0xbf 0x0 0x0 0x0
+0xe6 0x6 0xe8 0x50 0x0 0xbf 0x0 0x0 0x0 0x1 0xe9 0x59 0x0 0x0 0x0 0x0 0x0
+0xe7 0x0 0xea 0x8 0x0 0xd5 0x0 0x0 0x0 0x6 0xea 0x50 0x0 0xd5 0x0 0x0 0x0
+0xe8 0x1 0xec 0x58 0x0 0x0 0x0 0x0 0x0 0x0 0xee 0x68 0x0 0xae 0x0 0x0 0x0
+0xe9 0x6 0xee 0x64 0x0 0xae 0x0 0x0 0x0 0x0 0xf0 0xc5 0x0 0x5e 0x0 0x0 0x0
+0xea 0x6 0xf0 0x64 0x0 0x5e 0x0 0x0 0x0 0x1 0xf1 0x59 0x0 0x0 0x0 0x0 0x0
+0xeb 0x0 0xf2 0x1b 0x1 0x2f 0x0 0x1 0x0 0x6 0xf2 0x64 0x0 0x2f 0x0 0x1 0x0
+0xec 0x1 0xf2 0x58 0x0 0x0 0x0 0x0 0x0 0x0 0xf3 0x15 0x1 0x15 0x0 0x0 0x0
+0xed 0x6 0xf3 0x64 0x0 0x15 0x0 0x0 0x0 0x0 0xf4 0x2a 0x1 0x8 0x0 0x0 0x0
+0xee 0x6 0xf4 0x76 0x0 0x8 0x0 0x0 0x0 0x1 0xf5 0x59 0x0 0x0 0x0 0x0 0x0
+0xef 0x0 0xf5 0x6c 0x1 0x13 0x0 0x0 0x0 0x6 0xf5 0x76 0x0 0x13 0x0 0x0 0x0
+0xf0 0x0 0xf6 0x63 0x1 0xa 0x0 0x0 0x0 0x6 0xf6 0x76 0x0 0xa 0x0 0x0 0x0
+0xf1 0x0 0xf7 0x8d 0x1 0x2 0x0 0x0 0x0 0x6 0xf7 0x76 0x0 0x2 0x0 0x0 0x0
+0xf2 0x1 0xf7 0x5a 0x0 0x0 0x0 0x0 0x0 0x0 0xf8 0xa5 0x1 0xf7 0xff 0x0 0x0
+0xf3 0x6 0xf8 0x76 0x0 0xf7 0xff 0x0 0x0 0x0 0xf9 0x22 0x2 0xd6 0xff 0x0 0x0
+0xf4 0x6 0xf9 0x76 0x0 0xd6 0xff 0x0 0x0 0x1 0xfa 0x57 0x0 0x0 0x0 0x0 0x0
+0xf5 0x0 0xfa 0x1 0x2 0xdf 0xff 0x0 0x0 0x6 0xfa 0x76 0x0 0xdf 0xff 0x0 0x0
+0xf6 0x1 0xfb 0x54 0x0 0x0 0x0 0x0 0x0 0x0 0xfb 0xd2 0x1 0xe8 0xff 0x0 0x0
+0xf7 0x6 0xfb 0x76 0x0 0xe8 0xff 0x0 0x0 0x0 0xfc 0xaf 0x1 0xf4 0xff 0x0 0x0
+0xf8 0x6 0xfc 0x76 0x0 0xf4 0xff 0x0 0x0 0x1 0xfc 0x57 0x0 0x0 0x0 0x0 0x0
+0xf9 0x0 0xfd 0x93 0x1 0x4 0x0 0x0 0x0 0x6 0xfd 0x76 0x0 0x4 0x0 0x0 0x0
+0xfa 0x0 0xfe 0x8a 0x1 0x7 0x0 0x0 0x0 0x6 0xfe 0x76 0x0 0x7 0x0 0x0 0x0
+0xfb 0x0 0xff 0x6f 0x1 0x7 0x0 0x0 0x0 0x6 0xff 0x76 0x0 0x7 0x0 0x0 0x0
+0xfc 0x1 0x0 0x58 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x5a 0x1 0x5 0x0 0x0 0x0
+0xfd 0x6 0x0 0x76 0x0 0x5 0x0 0x0 0x0 0x1 0x1 0x57 0x0 0x0 0x0 0x0 0x0
+0xfe 0x0 0x1 0x22 0x1 0x1 0x0 0x0 0x0 0x6 0x1 0x76 0x0 0x1 0x0 0x0 0x0
+0xff 0x0 0x3 0x20 0x1 0x5 0x0 0x0 0x0 0x6 0x3 0x76 0x0 0x5 0x0 0x0 0x0
+0x0 0x1 0x3 0x58 0x0 0x0 0x0 0x0 0x0 0x1 0x4 0x59 0x0 0x0 0x0 0x0 0x0
+0x1 0x0 0x8 0xfe 0x0 0x1b 0x0 0x0 0x0 0x6 0x8 0x76 0x0 0x1b 0x0 0x0 0x0
+0x2 0x1 0x9 0x59 0x0 0x0 0x0 0x0 0x0 0x1 0xe 0x58 0x0 0x0 0x0 0x0 0x0
+0x3 0x0 0xf 0xc0 0x0 0x53 0x0 0x0 0x0 0x6 0xf 0xa5 0x0 0x53 0x0 0x0 0x0
+0x4 0x0 0x11 0x5d 0x0 0x7f 0x0 0x0 0x0 0x6 0x11 0xa5 0x0 0x7f 0x0 0x0 0x0
+0x5 0x1 0x15 0x58 0x0 0x0 0x0 0x0 0x0 0x0 0x16 0x16 0x0 0xb7 0x0 0x0 0x0
+0x6 0x6 0x16 0xb9 0x0 0xb7 0x0 0x0 0x0 0x1 0x16 0x59 0x0 0x0 0x0 0x0 0x0
+0x7 0x0 0x16 0x1 0x0 0xa4 0x0 0x0 0x0 0x6 0x16 0xb9 0x0 0xa4 0x0 0x0 0x0
+0x8 0x0 0x17 0x14 0x0 0xe6 0x0 0x0 0x0 0x6 0x17 0xb9 0x0 0xe6 0x0 0x0 0x0
+0x9 0x1 0x17 0x58 0x0 0x0 0x0 0x0 0x0 0x0 0x18 0x14 0x0 0xff 0x0 0x0 0x0
+0xa 0x6 0x18 0xb9 0x0 0xff 0x0 0x0 0x0 0x1 0x19 0x57 0x0 0x0 0x0 0x0 0x0
+0xb 0x0 0x19 0xd 0x0 0xe3 0x0 0x0 0x0 0x6 0x19 0xb9 0x0 0xe3 0x0 0x0 0x0
+0xc 0x0 0x1a 0xe 0x0 0xef 0x0 0xff 0xff 0x6 0x1a 0xb9 0x0 0xef 0x0 0xff 0xff
+0xd 0x0 0x1b 0x13 0x0 0xe5 0x0 0x0 0x0 0x6 0x1b 0xb9 0x0 0xe5 0x0 0x0 0x0
+0xe 0x0 0x1c 0xc 0x0 0xca 0x0 0x0 0x0 0x6 0x1c 0xb9 0x0 0xca 0x0 0x0 0x0
+0xf 0x0 0x1d 0xff 0xff 0xc4 0x0 0x0 0x0 0x6 0x1d 0xb9 0x0 0xc4 0x0 0x0 0x0
+0x10 0x1 0x1d 0x56 0x0 0x0 0x0 0x0 0x0 0x0 0x1e 0x9 0x0 0xe2 0x0 0x0 0x0
+0x11 0x6 0x1e 0xb9 0x0 0xe2 0x0 0x0 0x0 0x1 0x1f 0x54 0x0 0x0 0x0 0x0 0x0
+0x12 0x0 0x1f 0xc 0x0 0xd7 0x0 0x0 0x0 0x6 0x1f 0xa6 0x0 0xd7 0x0 0x0 0x0
+0x13 0x0 0x20 0x11 0x0 0xd2 0x0 0x0 0x0 0x6 0x20 0xa6 0x0 0xd2 0x0 0x0 0x0
+0x14 0x0 0x21 0x12 0x0 0xb7 0x0 0x0 0x0 0x6 0x21 0xa6 0x0 0xb7 0x0 0x0 0x0
+0x15 0x0 0x22 0xc 0x0 0xc2 0x0 0x0 0x0 0x6 0x22 0xa6 0x0 0xc2 0x0 0x0 0x0
+0x16 0x1 0x22 0x53 0x0 0x0 0x0 0x0 0x0 0x0 0x23 0x6 0x0 0xc5 0x0 0x0 0x0
+0x17 0x6 0x23 0xa6 0x0 0xc5 0x0 0x0 0x0 0x1 0x23 0x54 0x0 0x0 0x0 0x0 0x0
+0x18 0x0 0x24 0xff 0xff 0xc9 0x0 0x0 0x0 0x6 0x24 0xa6 0x0 0xc9 0x0 0x0 0x0
+0x19 0x1 0x25 0x53 0x0 0x0 0x0 0x0 0x0 0x0 0x25 0xfe 0xff 0xdf 0x0 0x0 0x0
+0x1a 0x6 0x25 0xa6 0x0 0xdf 0x0 0x0 0x0 0x0 0x26 0x4 0x0 0xc2 0x0 0x0 0x0
+0x1b 0x6 0x26 0xa6 0x0 0xc2 0x0 0x0 0x0 0x0 0x27 0x8 0x0 0xc2 0x0 0x0 0x0
+0x1c 0x6 0x27 0xa6 0x0 0xc2 0x0 0x0 0x0 0x1 0x27 0x52 0x0 0x0 0x0 0x0 0x0
+0x1d 0x0 0x28 0xe 0x0 0xd7 0x0 0x0 0x0 0x6 0x28 0xa6 0x0 0xd7 0x0 0x0 0x0
+0x1e 0x0 0x29 0x10 0x0 0xbb 0x0 0x0 0x0 0x6 0x29 0xa6 0x0 0xbb 0x0 0x0 0x0
+0x1f 0x1 0x29 0x53 0x0 0x0 0x0 0x0 0x0 0x0 0x2a 0x11 0x0 0xc4 0x0 0x0 0x0
+0x20 0x6 0x2a 0xa6 0x0 0xc4 0x0 0x0 0x0 0x0 0x2b 0x13 0x0 0xd5 0x0 0x0 0x0
+0x21 0x6 0x2b 0xa6 0x0 0xd5 0x0 0x0 0x0 0x1 0x2c 0x54 0x0 0x0 0x0 0x0 0x0
+0x22 0x0 0x2c 0xe 0x0 0xb6 0x0 0x0 0x0 0x6 0x2c 0xa6 0x0 0xb6 0x0 0x0 0x0
+0x23 0x0 0x2d 0x9 0x0 0xbc 0x0 0x0 0x0 0x6 0x2d 0xa6 0x0 0xbc 0x0 0x0 0x0
+0x24 0x0 0x2e 0x5 0x0 0xbb 0x0 0x0 0x0 0x6 0x2e 0xa6 0x0 0xbb 0x0 0x0 0x0
+0x25 0x0 0x2f 0x3 0x0 0xd5 0x0 0x0 0x0 0x6 0x2f 0xa6 0x0 0xd5 0x0 0x0 0x0
+0x26 0x0 0x30 0xfd 0xff 0xd6 0x0 0x0 0x0 0x6 0x30 0xa6 0x0 0xd6 0x0 0x0 0x0
+0x27 0x1 0x31 0x55 0x0 0x0 0x0 0x0 0x0 0x0 0x31 0xfc 0xff 0xbe 0x0 0x0 0x0
+0x28 0x6 0x31 0x94 0x0 0xbe 0x0 0x0 0x0 0x0 0x32 0xfd 0xff 0xcd 0x0 0x0 0x0
+0x29 0x6 0x32 0x94 0x0 0xcd 0x0 0x0 0x0 0x1 0x32 0x56 0x0 0x0 0x0 0x0 0x0
+0x2a 0x0 0x33 0xfc 0xff 0xd7 0x0 0x0 0x0 0x6 0x33 0x94 0x0 0xd7 0x0 0x0 0x0
+0x2b 0x0 0x34 0xfb 0xff 0xc8 0x0 0xff 0xff 0x6 0x34 0x94 0x0 0xc8 0x0 0xff 0xff
+0x2c 0x0 0x35 0xff 0xff 0xb4 0x0 0x0 0x0 0x6 0x35 0x94 0x0 0xb4 0x0 0x0 0x0
+0x2d 0x1 0x36 0x57 0x0 0x0 0x0 0x0 0x0 0x1 0x37 0x58 0x0 0x0 0x0 0x0 0x0
+0x2e 0x0 0x37 0xf8 0xff 0xb6 0x0 0x0 0x0 0x6 0x37 0x94 0x0 0xb6 0x0 0x0 0x0
+0x2f 0x1 0x38 0x59 0x0 0x0 0x0 0x0 0x0 0x0 0x39 0xf9 0xff 0xa1 0x0 0x0 0x0
+0x30 0x6 0x39 0x94 0x0 0xa1 0x0 0x0 0x0 0x1 0x3a 0x5a 0x0 0x0 0x0 0x0 0x0
+0x31 0x1 0x3c 0x5c 0x0 0x0 0x0 0x0 0x0 0x1 0x3d 0x5b 0x0 0x0 0x0 0x0 0x0
+0x32 0x0 0x3d 0xd 0x0 0x94 0x0 0x0 0x0 0x6 0x3d 0x94 0x0 0x94 0x0 0x0 0x0
+0x33 0x1 0x40 0x62 0x0 0x0 0x0 0x0 0x0 0x1 0x42 0x61 0x0 0x0 0x0 0x0 0x0
+0x34 0x1 0x44 0x68 0x0 0x0 0x0 0x0 0x0 0x0 0x45 0xbf 0xff 0x84 0x0 0x0 0x0
+0x35 0x6 0x45 0xc0 0x0 0x84 0x0 0x0 0x0 0x1 0x45 0x6e 0x0 0x0 0x0 0x0 0x0
+0x36 0x1 0x47 0x66 0x0 0x0 0x0 0x0 0x0 0x0 0x47 0x74 0xff 0x56 0x0 0x0 0x0
+0x37 0x6 0x47 0xdb 0x0 0x56 0x0 0x0 0x0 0x0 0x49 0xd7 0xfe 0x3a 0x0 0x0 0x0
+0x38 0x6 0x49 0xdb 0x0 0x3a 0x0 0x0 0x0 0x1 0x49 0x68 0x0 0x0 0x0 0x0 0x0
+0x39 0x0 0x4a 0xa 0xff 0x60 0x0 0x0 0x0 0x6 0x4a 0xf1 0x0 0x60 0x0 0x0 0x0
+0x3a 0x1 0x4b 0x61 0x0 0x0 0x0 0x0 0x0 0x0 0x4c 0xdd 0xfe 0x3f 0x0 0x0 0x0
+0x3b 0x6 0x4c 0xf1 0x0 0x3f 0x0 0x0 0x0 0x1 0x4e 0x60 0x0 0x0 0x0 0x0 0x0
+0x3c 0x0 0x4e 0xd6 0xfe 0x3d 0x0 0x0 0x0 0x6 0x4e 0xf1 0x0 0x3d 0x0 0x0 0x0
+0x3d 0x1 0x4f 0x59 0x0 0x0 0x0 0x0 0x0 0x0 0x50 0xf6 0xfe 0x24 0x0 0x0 0x0
+0x3e 0x6 0x50 0xf1 0x0 0x24 0x0 0x0 0x0 0x1 0x50 0x54 0x0 0x0 0x0 0x0 0x0
+0x3f 0x0 0x52 0x8 0xff 0x26 0x0 0x0 0x0 0x6 0x52 0xf1 0x0 0x26 0x0 0x0 0x0
+0x40 0x1 0x53 0x51 0x0 0x0 0x0 0x0 0x0 0x1 0x54 0x4e 0x0 0x0 0x0 0x0 0x0
+0x41 0x0 0x55 0x7 0xff 0x40 0x0 0x0 0x0 0x6 0x55 0xf1 0x0 0x40 0x0 0x0 0x0
+0x42 0x1 0x55 0x4f 0x0 0x0 0x0 0x0 0x0 0x0 0x57 0xe 0xff 0x20 0x0 0x0 0x0
+0x43 0x6 0x57 0x6 0x1 0x20 0x0 0x0 0x0 0x1 0x58 0x52 0x0 0x0 0x0 0x0 0x0
+0x44 0x0 0x59 0xb5 0xfe 0x12 0x0 0x0 0x0 0x6 0x59 0x6 0x1 0x12 0x0 0x0 0x0
+0x45 0x0 0x5a 0xc4 0xfe 0xb 0x0 0x0 0x0 0x6 0x5a 0x6 0x1 0xb 0x0 0x0 0x0
+0x46 0x1 0x5a 0x53 0x0 0x0 0x0 0x0 0x0 0x0 0x5b 0xbb 0xfe 0xb 0x0 0x2 0x0
+0x47 0x6 0x5b 0x6 0x1 0xb 0x0 0x2 0x0 0x0 0x5c 0xac 0xfe 0xd 0x0 0x0 0x0
+0x48 0x6 0x5c 0x6 0x1 0xd 0x0 0x0 0x0 0x1 0x5c 0x54 0x0 0x0 0x0 0x0 0x0
+0x49 0x0 0x5d 0x2c 0xfe 0x1a 0x0 0x0 0x0 0x6 0x5d 0x6 0x1 0x1a 0x0 0x0 0x0
+0x4a 0x1 0x5e 0x56 0x0 0x0 0x0 0x0 0x0 0x0 0x5e 0x44 0xfe 0x22 0x0 0x0 0x0
+0x4b 0x6 0x5e 0xf2 0x0 0x22 0x0 0x0 0x0 0x1 0x5f 0x55 0x0 0x0 0x0 0x0 0x0
+0x4c 0x0 0x5f 0x76 0xfe 0x27 0x0 0x0 0x0 0x6 0x5f 0xf2 0x0 0x27 0x0 0x0 0x0
+0x4d 0x0 0x60 0xa2 0xfe 0x26 0x0 0x0 0x0 0x6 0x60 0xf2 0x0 0x26 0x0 0x0 0x0
+0x4e 0x0 0x61 0xd2 0xfe 0x1d 0x0 0x0 0x0 0x6 0x61 0xd9 0x0 0x1d 0x0 0x0 0x0
+0x4f 0x1 0x62 0x54 0x0 0x0 0x0 0x0 0x0 0x0 0x63 0xc6 0xfe 0x19 0x0 0x0 0x0
+0x50 0x6 0x63 0xd9 0x0 0x19 0x0 0x0 0x0 0x1 0x64 0x55 0x0 0x0 0x0 0x0 0x0
+0x51 0x1 0x66 0x56 0x0 0x0 0x0 0x0 0x0 0x1 0x67 0x57 0x0 0x0 0x0 0x0 0x0
+0x52 0x1 0x68 0x58 0x0 0x0 0x0 0x0 0x0 0x1 0x6b 0x59 0x0 0x0 0x0 0x0 0x0
+0x53 0x0 0x6b 0x2c 0xff 0x4a 0x0 0x0 0x0 0x6 0x6b 0xf3 0x0 0x4a 0x0 0x0 0x0
+0x54 0x1 0x6d 0x5b 0x0 0x0 0x0 0x0 0x0 0x1 0x6e 0x5a 0x0 0x0 0x0 0x0 0x0
+0x55 0x1 0x73 0x58 0x0 0x0 0x0 0x0 0x0 0x0 0x74 0xe5 0xfe 0x37 0x0 0x0 0x0
+0x56 0x6 0x74 0x3a 0x1 0x37 0x0 0x0 0x0 0x1 0x75 0x59 0x0 0x0 0x0 0x0 0x0
+0x57 0x1 0x76 0x58 0x0 0x0 0x0 0x0 0x0 0x0 0x77 0xee 0xfe 0x5 0x0 0x0 0x0
+0x58 0x6 0x77 0x56 0x1 0x5 0x0 0x0 0x0 0x1 0x78 0x59 0x0 0x0 0x0 0x0 0x0
+0x59 0x1 0x7a 0x58 0x0 0x0 0x0 0x0 0x0 0x1 0x7c 0x59 0x0 0x0 0x0 0x0 0x0
+0x5a 0x1 0x80 0x5a 0x0 0x0 0x0 0x0 0x0 0x1 0x81 0x59 0x0 0x0 0x0 0x0 0x0
+0x5b 0x1 0x84 0x5a 0x0 0x0 0x0 0x0 0x0 0x0 0x84 0xf 0x0 0xf8 0xff 0x0 0x0
+0x5c 0x6 0x84 0xfc 0x1 0xf8 0xff 0x0 0x0 0x1 0x85 0x5b 0x0 0x0 0x0 0x0 0x0
+0x5d 0x1 0x86 0x5a 0x0 0x0 0x0 0x0 0x0 0x0 0x86 0x9e 0x0 0xeb 0xff 0x0 0x0
+0x5e 0x6 0x86 0x13 0x2 0xeb 0xff 0x0 0x0 0x1 0x88 0x5b 0x0 0x0 0x0 0x0 0x0
+0x5f 0x0 0x89 0xbf 0x0 0xf0 0xff 0x0 0x0 0x6 0x89 0x13 0x2 0xf0 0xff 0x0 0x0
+0x60 0x1 0x8a 0x5a 0x0 0x0 0x0 0x0 0x0 0x1 0x8b 0x5b 0x0 0x0 0x0 0x0 0x0
+0x61 0x1 0x8d 0x5c 0x0 0x0 0x0 0x0 0x0 0x1 0x8e 0x5b 0x0 0x0 0x0 0x0 0x0
+0x62 0x0 0x91 0xea 0xff 0xf6 0xff 0x0 0x0 0x6 0x91 0x57 0x2 0xf6 0xff 0x0 0x0
+0x63 0x1 0x92 0x5c 0x0 0x0 0x0 0x0 0x0 0x1 0x93 0x5d 0x0 0x0 0x0 0x0 0x0
+0x64 0x1 0x98 0x5e 0x0 0x0 0x0 0x0 0x0 0x1 0x99 0x5f 0x0 0x0 0x0 0x0 0x0
+0x65 0x1 0x9c 0x5e 0x0 0x0 0x0 0x0 0x0 0x1 0x9d 0x5f 0x0 0x0 0x0 0x0 0x0
+0x66 0x1 0xa1 0x60 0x0 0x0 0x0 0x0 0x0 0x1 0xa2 0x61 0x0 0x0 0x0 0x0 0x0
+0x67 0x1 0xa3 0x63 0x0 0x0 0x0 0x0 0x0 0x1 0xa5 0x64 0x0 0x0 0x0 0x0 0x0
+0x68 0x1 0xaa 0x63 0x0 0x0 0x0 0x0 0x0 0x1 0xad 0x61 0x0 0x0 0x0 0x0 0x0
+0x69 0x1 0xaf 0x5f 0x0 0x0 0x0 0x0 0x0 0x1 0xb0 0x5d 0x0 0x0 0x0 0x0 0x0
+0x6a 0x1 0xb1 0x5c 0x0 0x0 0x0 0x0 0x0 0x0 0xb3 0x85 0xff 0xfb 0xff 0x0 0x0
+0x6b 0x6 0xb3 0xa8 0x6 0xfb 0xff 0x0 0x0 0x1 0xb4 0x5b 0x0 0x0 0x0 0x0 0x0
+0x6c 0x1 0xb6 0x59 0x0 0x0 0x0 0x0 0x0 0x1 0xb9 0x58 0x0 0x0 0x0 0x0 0x0
+0x6d 0x1 0xba 0x57 0x0 0x0 0x0 0x0 0x0 0x1 0xbe 0x58 0x0 0x0 0x0 0x0 0x0
+0x6e 0x1 0xbf 0x59 0x0 0x0 0x0 0x0 0x0 0x1 0xc0 0x5a 0x0 0x0 0x0 0x0 0x0
+0x6f 0x1 0xc3 0x5b 0x0 0x0 0x0 0x0 0x0 0x0 0xc3 0x30 0x0 0xec 0xff 0x0 0x0
+0x70 0x6 0xc3 0x0 0x0 0xec 0xff 0x0 0x0 0x1 0xc4 0x5d 0x0 0x0 0x0 0x0 0x0
+0x71 0x1 0xc7 0x5e 0x0 0x0 0x0 0x0 0x0 0x0 0xc8 0x8b 0x0 0xda 0xff 0x0 0x0
+0x72 0x6 0xc8 0x0 0x0 0xda 0xff 0x0 0x0 0x1 0xc9 0x5d 0x0 0x0 0x0 0x0 0x0
+0x73 0x1 0xcc 0x5e 0x0 0x0 0x0 0x0 0x0 0x0 0xcc 0xc0 0x0 0xeb 0xff 0x0 0x0
+0x74 0x6 0xcc 0xf0 0x6 0xeb 0xff 0x0 0x0 0x1 0xcf 0x5d 0x0 0x0 0x0 0x0 0x0
+0x75 0x0 0xd0 0xd8 0x0 0xeb 0xff 0x0 0x0 0x6 0xd0 0xc4 0x5 0xeb 0xff 0x0 0x0
+0x76 0x1 0xd1 0x5c 0x0 0x0 0x0 0x0 0x0 0x1 0xd4 0x5d 0x0 0x0 0x0 0x0 0x0
+0x77 0x0 0xd4 0xe4 0x0 0xfd 0xff 0x0 0x0 0x6 0xd4 0xc 0x5 0xfd 0xff 0x0 0x0
+0x78 0x1 0xd6 0x5e 0x0 0x0 0x0 0x0 0x0 0x1 0xd7 0x5f 0x0 0x0 0x0 0x0 0x0
+0x79 0x0 0xd7 0xed 0x0 0xf7 0xff 0x0 0x0 0x6 0xd7 0x82 0x4 0xf7 0xff 0x0 0x0
+0x7a 0x1 0xd8 0x60 0x0 0x0 0x0 0x0 0x0 0x0 0xda 0xec 0x0 0xef 0xff 0x0 0x0
+0x7b 0x6 0xda 0x1b 0x4 0xef 0xff 0x0 0x0 0x1 0xdb 0x62 0x0 0x0 0x0 0x0 0x0
+0x7c 0x1 0xdc 0x63 0x0 0x0 0x0 0x0 0x0 0x1 0xdd 0x64 0x0 0x0 0x0 0x0 0x0
+0x7d 0x0 0xdd 0xeb 0x0 0xef 0xff 0x0 0x0 0x6 0xdd 0xa5 0x3 0xef 0xff 0x0 0x0
+0x7e 0x1 0xe1 0x65 0x0 0x0 0x0 0x0 0x0 0x0 0xe1 0xff 0x0 0xef 0xff 0x0 0x0
+0x7f 0x6 0xe1 0x52 0x3 0xef 0xff 0x0 0x0 0x1 0xe2 0x64 0x0 0x0 0x0 0x0 0x0
+0x80 0x0 0xe5 0xe6 0x0 0xe3 0xff 0x0 0x0 0x6 0xe5 0x27 0x3 0xe3 0xff 0x0 0x0
+0x81 0x1 0xe6 0x63 0x0 0x0 0x0 0x0 0x0 0x0 0xe9 0xfa 0x0 0xcc 0xff 0x0 0x0
+0x82 0x6 0xe9 0x27 0x3 0xcc 0xff 0x0 0x0 0x1 0xeb 0x62 0x0 0x0 0x0 0x0 0x0
+0x83 0x0 0xed 0xee 0x0 0xfa 0xff 0x0 0x0 0x6 0xed 0x14 0x3 0xfa 0xff 0x0 0x0
+0x84 0x1 0xee 0x64 0x0 0x0 0x0 0x0 0x0 0x1 0xf0 0x65 0x0 0x0 0x0 0x0 0x0
+0x85 0x0 0xf2 0xfe 0x0 0xf 0x0 0x0 0x0 0x6 0xf2 0x14 0x3 0xf 0x0 0x0 0x0
+0x86 0x1 0xf3 0x66 0x0 0x0 0x0 0x0 0x0 0x1 0xf4 0x67 0x0 0x0 0x0 0x0 0x0
+0x87 0x1 0xf6 0x69 0x0 0x0 0x0 0x0 0x0 0x0 0xf7 0xef 0x0 0xf3 0xff 0xfe 0xff
+0x88 0x6 0xf7 0x14 0x3 0xf3 0xff 0xfe 0xff 0x1 0xf8 0x6a 0x0 0x0 0x0 0x0 0x0
+0x89 0x1 0xf9 0x6b 0x0 0x0 0x0 0x0 0x0 0x1 0xfa 0x6d 0x0 0x0 0x0 0x0 0x0
+0x8a 0x0 0xfb 0xe6 0x0 0xd7 0xff 0x0 0x0 0x6 0xfb 0x14 0x3 0xd7 0xff 0x0 0x0
+0x8b 0x1 0xfc 0x6f 0x0 0x0 0x0 0x0 0x0 0x1 0xfd 0x70 0x0 0x0 0x0 0x0 0x0
+0x8c 0x0 0xfe 0xe3 0x0 0xe6 0xff 0x0 0x0 0x6 0xfe 0x14 0x3 0xe6 0xff 0x0 0x0
+0x8d 0x1 0xff 0x71 0x0 0x0 0x0 0x0 0x0 0x1 0x2 0x72 0x0 0x0 0x0 0x0 0x0
+0x8e 0x0 0x3 0xe7 0x0 0xda 0xff 0x0 0x0 0x6 0x3 0x14 0x3 0xda 0xff 0x0 0x0
+0x8f 0x1 0x4 0x73 0x0 0x0 0x0 0x0 0x0 0x1 0x6 0x72 0x0 0x0 0x0 0x0 0x0
+0x90 0x0 0x7 0xfb 0x0 0xfc 0xff 0x0 0x0 0x6 0x7 0x14 0x3 0xfc 0xff 0x0 0x0
+0x91 0x1 0x7 0x71 0x0 0x0 0x0 0x0 0x0 0x1 0x8 0x70 0x0 0x0 0x0 0x0 0x0
+0x92 0x0 0xb 0xee 0x0 0xd 0x0 0x0 0x0 0x6 0xb 0x14 0x3 0xd 0x0 0x0 0x0
+0x93 0x1 0xb 0x71 0x0 0x0 0x0 0x0 0x0 0x1 0xc 0x70 0x0 0x0 0x0 0x0 0x0
+0x94 0x1 0xd 0x6f 0x0 0x0 0x0 0x0 0x0 0x0 0xe 0xef 0x0 0x13 0x0 0x0 0x0
+0x95 0x6 0xe 0x14 0x3 0x13 0x0 0x0 0x0 0x1 0x10 0x71 0x0 0x0 0x0 0x0 0x0
+0x96 0x1 0x11 0x70 0x0 0x0 0x0 0x0 0x0 0x1 0x12 0x71 0x0 0x0 0x0 0x0 0x0
+0x97 0x0 0x14 0x6 0x1 0xf1 0xff 0x0 0x0 0x6 0x14 0x1 0x3 0xf1 0xff 0x0 0x0
+0x98 0x1 0x15 0x72 0x0 0x0 0x0 0x0 0x0 0x1 0x17 0x73 0x0 0x0 0x0 0x0 0x0
+0x99 0x0 0x18 0x7 0x1 0xa 0x0 0xff 0xff 0x6 0x18 0x1 0x3 0xa 0x0 0xff 0xff
+0x9a 0x5 0x18 0x71 0x1 0xa 0x0 0xff 0xff 0x1 0x1b 0x75 0x0 0x0 0x0 0x0 0x0
+0x9b 0x0 0x1b 0x1 0x1 0x7 0x0 0x0 0x0 0x6 0x1b 0xd2 0x2 0x7 0x0 0x0 0x0
+0x9c 0x1 0x1c 0x74 0x0 0x0 0x0 0x0 0x0 0x0 0x1f 0xc 0x1 0x4 0x0 0x0 0x0
+0x9d 0x6 0x1f 0xba 0x2 0x4 0x0 0x0 0x0 0x1 0x1f 0x76 0x0 0x0 0x0 0x0 0x0
+0x9e 0x1 0x21 0x75 0x0 0x0 0x0 0x0 0x0 0x1 0x23 0x76 0x0 0x0 0x0 0x0 0x0
+0x9f 0x0 0x24 0xc7 0x0 0xb4 0xff 0x0 0x0 0x6 0x24 0xba 0x2 0xb4 0xff 0x0 0x0
+0xa0 0x1 0x24 0x75 0x0 0x0 0x0 0x0 0x0 0x0 0x27 0x78 0x0 0x8a 0xff 0x0 0x0
+0xa1 0x6 0x27 0xba 0x2 0x8a 0xff 0x0 0x0 0x1 0x28 0x74 0x0 0x0 0x0 0x0 0x0
+0xa2 0x1 0x29 0x73 0x0 0x0 0x0 0x0 0x0 0x0 0x2a 0x4b 0x0 0x7a 0xff 0x0 0x0
+0xa3 0x6 0x2a 0xba 0x2 0x7a 0xff 0x0 0x0 0x1 0x2a 0x72 0x0 0x0 0x0 0x0 0x0
+0xa4 0x0 0x2d 0x37 0x0 0x6d 0xff 0x0 0x0 0x6 0x2d 0xa1 0x2 0x6d 0xff 0x0 0x0
+0xa5 0x1 0x2d 0x71 0x0 0x0 0x0 0x0 0x0 0x1 0x2e 0x70 0x0 0x0 0x0 0x0 0x0
+0xa6 0x0 0x30 0x34 0x0 0x67 0xff 0x0 0x0 0x6 0x30 0x85 0x2 0x67 0xff 0x0 0x0
+0xa7 0x1 0x32 0x6f 0x0 0x0 0x0 0x0 0x0 0x0 0x33 0x35 0x0 0x61 0xff 0x0 0x0
+0xa8 0x6 0x33 0x70 0x2 0x61 0xff 0x0 0x0 0x1 0x33 0x70 0x0 0x0 0x0 0x0 0x0
+0xa9 0x0 0x36 0x26 0x0 0x6a 0xff 0x0 0x0 0x6 0x36 0x5e 0x2 0x6a 0xff 0x0 0x0
+0xaa 0x1 0x37 0x6f 0x0 0x0 0x0 0x0 0x0 0x0 0x39 0x28 0x0 0x68 0xff 0x0 0x0
+0xab 0x6 0x39 0x4b 0x2 0x68 0xff 0x0 0x0 0x1 0x39 0x6e 0x0 0x0 0x0 0x0 0x0
+0xac 0x1 0x3b 0x6d 0x0 0x0 0x0 0x0 0x0 0x0 0x3c 0x24 0x0 0x66 0xff 0x0 0x0
+0xad 0x6 0x3c 0x4b 0x2 0x66 0xff 0x0 0x0 0x1 0x3d 0x6b 0x0 0x0 0x0 0x0 0x0
+0xae 0x0 0x3f 0x26 0x0 0x66 0xff 0x0 0x0 0x6 0x3f 0x4b 0x2 0x66 0xff 0x0 0x0
+0xaf 0x1 0x40 0x6a 0x0 0x0 0x0 0x0 0x0 0x1 0x41 0x68 0x0 0x0 0x0 0x0 0x0
+0xb0 0x0 0x42 0x1f 0x0 0x54 0xff 0x0 0x0 0x6 0x42 0x36 0x2 0x54 0xff 0x0 0x0
+0xb1 0x1 0x43 0x67 0x0 0x0 0x0 0x0 0x0 0x0 0x45 0xf 0x0 0x4d 0xff 0x0 0x0
+0xb2 0x6 0x45 0x36 0x2 0x4d 0xff 0x0 0x0 0x1 0x45 0x68 0x0 0x0 0x0 0x0 0x0
+0xb3 0x1 0x46 0x69 0x0 0x0 0x0 0x0 0x0 0x0 0x48 0xd 0x0 0x68 0xff 0x0 0x0
+0xb4 0x6 0x48 0x23 0x2 0x68 0xff 0x0 0x0 0x0 0x4b 0x13 0x0 0x62 0xff 0x0 0x0
+0xb5 0x6 0x4b 0x23 0x2 0x62 0xff 0x0 0x0 0x1 0x4b 0x6a 0x0 0x0 0x0 0x0 0x0
+0xb6 0x1 0x4c 0x6c 0x0 0x0 0x0 0x0 0x0 0x0 0x4e 0x0 0x0 0x4d 0xff 0x0 0x0
+0xb7 0x6 0x4e 0x23 0x2 0x4d 0xff 0x0 0x0 0x1 0x50 0x6e 0x0 0x0 0x0 0x0 0x0
+0xb8 0x0 0x51 0xf4 0xff 0x5b 0xff 0x0 0x0 0x6 0x51 0x23 0x2 0x5b 0xff 0x0 0x0
+0xb9 0x1 0x51 0x6d 0x0 0x0 0x0 0x0 0x0 0x0 0x54 0x1 0x0 0x63 0xff 0x0 0x0
+0xba 0x6 0x54 0x23 0x2 0x63 0xff 0x0 0x0 0x1 0x54 0x6e 0x0 0x0 0x0 0x0 0x0
+0xbb 0x1 0x55 0x6f 0x0 0x0 0x0 0x0 0x0 0x0 0x57 0xf6 0xff 0x6e 0xff 0x0 0x0
+0xbc 0x6 0x57 0x23 0x2 0x6e 0xff 0x0 0x0 0x1 0x5a 0x6e 0x0 0x0 0x0 0x0 0x0
+0xbd 0x0 0x5c 0xe5 0xff 0x64 0xff 0x0 0x0 0x6 0x5c 0x36 0x2 0x64 0xff 0x0 0x0
+0xbe 0x1 0x5d 0x71 0x0 0x0 0x0 0x0 0x0 0x0 0x5f 0x1a 0x0 0x71 0xff 0x0 0x0
+0xbf 0x6 0x5f 0x4e 0x2 0x71 0xff 0x0 0x0 0x0 0x62 0xb 0x0 0x5c 0xff 0x0 0x0
+0xc0 0x6 0x62 0x4e 0x2 0x5c 0xff 0x0 0x0 0x1 0x62 0x70 0x0 0x0 0x0 0x0 0x0
+0xc1 0x1 0x64 0x71 0x0 0x0 0x0 0x0 0x0 0x0 0x65 0xf1 0xff 0x5e 0xff 0x0 0x0
+0xc2 0x6 0x65 0x4e 0x2 0x5e 0xff 0x0 0x0 0x1 0x65 0x72 0x0 0x0 0x0 0x0 0x0
+0xc3 0x1 0x67 0x70 0x0 0x0 0x0 0x0 0x0 0x0 0x68 0x2 0x0 0x67 0xff 0x0 0x0
+0xc4 0x6 0x68 0x3b 0x2 0x67 0xff 0x0 0x0 0x1 0x68 0x6e 0x0 0x0 0x0 0x0 0x0
+0xc5 0x0 0x6b 0x16 0x0 0x59 0xff 0x0 0x0 0x6 0x6b 0x3b 0x2 0x59 0xff 0x0 0x0
+0xc6 0x1 0x6c 0x6f 0x0 0x0 0x0 0x0 0x0 0x0 0x6e 0x23 0x0 0x6c 0xff 0x0 0x0
+0xc7 0x6 0x6e 0x3b 0x2 0x6c 0xff 0x0 0x0 0x1 0x71 0x6e 0x0 0x0 0x0 0x0 0x0
+0xc8 0x0 0x72 0x3a 0x0 0x57 0xff 0x0 0x0 0x6 0x72 0x27 0x2 0x57 0xff 0x0 0x0
+0xc9 0x1 0x72 0x70 0x0 0x0 0x0 0x0 0x0 0x0 0x75 0x16 0x0 0x5b 0xff 0x0 0x0
+0xca 0x6 0x75 0x27 0x2 0x5b 0xff 0x0 0x0 0x1 0x76 0x6f 0x0 0x0 0x0 0x0 0x0
+0xcb 0x1 0x77 0x6e 0x0 0x0 0x0 0x0 0x0 0x0 0x78 0xf7 0xff 0x5b 0xff 0x0 0x0
+0xcc 0x6 0x78 0x27 0x2 0x5b 0xff 0x0 0x0 0x4 0x78 0x9c 0x1 0x5b 0xff 0x0 0x0
+0xcd 0x1 0x78 0x6d 0x0 0x0 0x0 0x0 0x0 0x0 0x7b 0xf2 0xff 0x6d 0xff 0x0 0x0
+0xce 0x6 0x7b 0x27 0x2 0x6d 0xff 0x0 0x0 0x1 0x7c 0x6c 0x0 0x0 0x0 0x0 0x0
+0xcf 0x1 0x7d 0x6b 0x0 0x0 0x0 0x0 0x0 0x0 0x7e 0xef 0xff 0x64 0xff 0x0 0x0
+0xd0 0x6 0x7e 0x27 0x2 0x64 0xff 0x0 0x0 0x1 0x7f 0x6a 0x0 0x0 0x0 0x0 0x0
+0xd1 0x0 0x82 0xca 0xff 0x58 0xff 0x0 0x0 0x6 0x82 0x27 0x2 0x58 0xff 0x0 0x0
+0xd2 0x1 0x82 0x69 0x0 0x0 0x0 0x0 0x0 0x1 0x84 0x6a 0x0 0x0 0x0 0x0 0x0
+0xd3 0x0 0x86 0xce 0xff 0x57 0xff 0x0 0x0 0x6 0x86 0x27 0x2 0x57 0xff 0x0 0x0
+0xd4 0x0 0x89 0xfe 0xff 0x65 0xff 0x0 0x0 0x6 0x89 0x27 0x2 0x65 0xff 0x0 0x0
+0xd5 0x1 0x89 0x6b 0x0 0x0 0x0 0x0 0x0 0x1 0x8a 0x6c 0x0 0x0 0x0 0x0 0x0
+0xd6 0x1 0x8c 0x6d 0x0 0x0 0x0 0x0 0x0 0x0 0x8d 0x13 0x0 0x68 0xff 0x0 0x0
+0xd7 0x6 0x8d 0x27 0x2 0x68 0xff 0x0 0x0 0x1 0x8e 0x6c 0x0 0x0 0x0 0x0 0x0
+0xd8 0x1 0x8f 0x6b 0x0 0x0 0x0 0x0 0x0 0x0 0x91 0xf0 0xff 0x6b 0xff 0x0 0x0
+0xd9 0x6 0x91 0x54 0x2 0x6b 0xff 0x0 0x0 0x0 0x94 0xee 0xff 0x6b 0xff 0x0 0x0
+0xda 0x6 0x94 0x54 0x2 0x6b 0xff 0x0 0x0 0x1 0x94 0x6c 0x0 0x0 0x0 0x0 0x0
+0xdb 0x0 0x97 0xfc 0xff 0x6f 0xff 0x0 0x0 0x6 0x97 0x54 0x2 0x6f 0xff 0x0 0x0
+0xdc 0x1 0x98 0x6d 0x0 0x0 0x0 0x0 0x0 0x1 0x99 0x6e 0x0 0x0 0x0 0x0 0x0
+0xdd 0x0 0x9a 0x11 0x0 0x6f 0xff 0x0 0x0 0x6 0x9a 0x54 0x2 0x6f 0xff 0x0 0x0
+0xde 0x1 0x9a 0x70 0x0 0x0 0x0 0x0 0x0 0x1 0x9d 0x71 0x0 0x0 0x0 0x0 0x0
+0xdf 0x0 0x9d 0x25 0x0 0x72 0xff 0x0 0x0 0x6 0x9d 0x54 0x2 0x72 0xff 0x0 0x0
+0xe0 0x1 0x9e 0x72 0x0 0x0 0x0 0x0 0x0 0x0 0xa1 0xed 0xff 0x70 0xff 0x0 0x0
+0xe1 0x6 0xa1 0x54 0x2 0x70 0xff 0x0 0x0 0x1 0xa2 0x73 0x0 0x0 0x0 0x0 0x0
+0xe2 0x1 0xa3 0x74 0x0 0x0 0x0 0x0 0x0 0x1 0xa4 0x73 0x0 0x0 0x0 0x0 0x0
+0xe3 0x1 0xa6 0x72 0x0 0x0 0x0 0x0 0x0 0x1 0xa7 0x70 0x0 0x0 0x0 0x0 0x0
+0xe4 0x1 0xab 0x6e 0x0 0x0 0x0 0x0 0x0 0x1 0xac 0x6d 0x0 0x0 0x0 0x0 0x0
+0xe5 0x0 0xad 0x60 0x0 0xcc 0xff 0x0 0x0 0x6 0xad 0xfb 0x2 0xcc 0xff 0x0 0x0
+0xe6 0x1 0xaf 0x6e 0x0 0x0 0x0 0x0 0x0 0x1 0xb5 0x6f 0x0 0x0 0x0 0x0 0x0
+0xe7 0x1 0xb6 0x70 0x0 0x0 0x0 0x0 0x0 0x1 0xb8 0x6f 0x0 0x0 0x0 0x0 0x0
+0xe8 0x1 0xbd 0x70 0x0 0x0 0x0 0x0 0x0 0x1 0xc0 0x6f 0x0 0x0 0x0 0x0 0x0
+0xe9 0x1 0xc2 0x68 0x0 0x0 0x0 0x0 0x0 0x1 0xc3 0x67 0x0 0x0 0x0 0x0 0x0
+0xea 0x1 0xc4 0x66 0x0 0x0 0x0 0x0 0x0 0x1 0xc7 0x5f 0x0 0x0 0x0 0x0 0x0
+0xeb 0x1 0xc8 0x5e 0x0 0x0 0x0 0x0 0x0 0x1 0xc9 0x59 0x0 0x0 0x0 0x0 0x0
+0xec 0x1 0xcc 0x58 0x0 0x0 0x0 0x0 0x0 0x1 0xcd 0x57 0x0 0x0 0x0 0x0 0x0
+0xed 0x1 0xce 0x54 0x0 0x0 0x0 0x0 0x0 0x1 0xd1 0x4d 0x0 0x0 0x0 0x0 0x0
+0xee 0x1 0xd2 0x4c 0x0 0x0 0x0 0x0 0x0 0x1 0xd3 0x4a 0x0 0x0 0x0 0x0 0x0
+0xef 0x1 0xd8 0x49 0x0 0x0 0x0 0x0 0x0 0x1 0xda 0x4a 0x0 0x0 0x0 0x0 0x0
+0xf0 0x1 0xdc 0x51 0x0 0x0 0x0 0x0 0x0 0x1 0xdd 0x4a 0x0 0x0 0x0 0x0 0x0
+0xf1 0x1 0xdf 0x51 0x0 0x0 0x0 0x0 0x0 0x1 0xe0 0x52 0x0 0x0 0x0 0x0 0x0
+0xf2 0x1 0xe2 0x53 0x0 0x0 0x0 0x0 0x0 0x1 0xe4 0x5a 0x0 0x0 0x0 0x0 0x0
+0xf3 0x1 0xe5 0x56 0x0 0x0 0x0 0x0 0x0 0x1 0xe7 0x54 0x0 0x0 0x0 0x0 0x0
+0xf4 0x1 0xe9 0x5a 0x0 0x0 0x0 0x0 0x0 0x1 0xea 0x58 0x0 0x0 0x0 0x0 0x0
+0xf5 0x1 0xeb 0x56 0x0 0x0 0x0 0x0 0x0 0x1 0xee 0x54 0x0 0x0 0x0 0x0 0x0
+0xf6 0x1 0xef 0x55 0x0 0x0 0x0 0x0 0x0 0x1 0xf4 0x56 0x0 0x0 0x0 0x0 0x0
+0xf7 0x1 0xf5 0x57 0x0 0x0 0x0 0x0 0x0 0x1 0xf8 0x58 0x0 0x0 0x0 0x0 0x0
+0xf8 0x1 0xfa 0x5a 0x0 0x0 0x0 0x0 0x0 0x1 0xff 0x59 0x0 0x0 0x0 0x0 0x0
+0xf9 0x1 0x1 0x55 0x0 0x0 0x0 0x0 0x0 0x1 0x3 0x58 0x0 0x0 0x0 0x0 0x0
+0xfa 0x1 0x4 0x59 0x0 0x0 0x0 0x0 0x0 0x1 0x6 0x60 0x0 0x0 0x0 0x0 0x0
+0xfb 0x1 0x7 0x61 0x0 0x0 0x0 0x0 0x0 0x1 0x9 0x68 0x0 0x0 0x0 0x0 0x0
+0xfc 0x1 0xc 0x6e 0x0 0x0 0x0 0x0 0x0 0x1 0x10 0x6f 0x0 0x0 0x0 0x0 0x0
+0xfd 0x1 0x11 0x6e 0x0 0x0 0x0 0x0 0x0 0x1 0x12 0x6f 0x0 0x0 0x0 0x0 0x0
+0xfe 0x1 0x15 0x70 0x0 0x0 0x0 0x0 0x0 0x1 0x16 0x6f 0x0 0x0 0x0 0x0 0x0
+0xff 0x1 0x17 0x6e 0x0 0x0 0x0 0x0 0x0 0x1 0x1c 0x6d 0x0 0x0 0x0 0x0 0x0
+0x0 0x1 0x1f 0x6b 0x0 0x0 0x0 0x0 0x0 0x1 0x21 0x6a 0x0 0x0 0x0 0x0 0x0
+0x1 0x1 0x25 0x6b 0x0 0x0 0x0 0x0 0x0 0x1 0x26 0x6c 0x0 0x0 0x0 0x0 0x0
+0x2 0x1 0x28 0x6e 0x0 0x0 0x0 0x0 0x0 0x1 0x2a 0x6f 0x0 0x0 0x0 0x0 0x0
+0x3 0x1 0x2e 0x6e 0x0 0x0 0x0 0x0 0x0 0x1 0x30 0x6d 0x0 0x0 0x0 0x0 0x0
+0x4 0x1 0x32 0x6b 0x0 0x0 0x0 0x0 0x0 0x1 0x33 0x6a 0x0 0x0 0x0 0x0 0x0
+0x5 0x1 0x34 0x69 0x0 0x0 0x0 0x0 0x0 0x1 0x38 0x68 0x0 0x0 0x0 0x0 0x0
+0x6 0x1 0x39 0x67 0x0 0x0 0x0 0x0 0x0 0x1 0x3d 0x66 0x0 0x0 0x0 0x0 0x0
+0x7 0x1 0x3e 0x67 0x0 0x0 0x0 0x0 0x0 0x1 0x41 0x6e 0x0 0x0 0x0 0x0 0x0
+0x8 0x1 0x42 0x6f 0x0 0x0 0x0 0x0 0x0 0x1 0x43 0x68 0x0 0x0 0x0 0x0 0x0
+0x9 0x0 0x44 0x9f 0x2 0x56 0xfe 0x0 0x0 0x6 0x44 0x4 0x4 0x56 0xfe 0x0 0x0
+0xa 0x0 0x45 0xfd 0xff 0x2 0x0 0x1 0x0 0x6 0x45 0x4 0x4 0x2 0x0 0x1 0x0
+0xb 0x1 0x46 0x67 0x0 0x0 0x0 0x0 0x0 0x1 0x47 0x6c 0x0 0x0 0x0 0x0 0x0
+0xc 0x1 0x48 0x6b 0x0 0x0 0x0 0x0 0x0 0x1 0x4a 0x6a 0x0 0x0 0x0 0x0 0x0
+0xd 0x1 0x4c 0x67 0x0 0x0 0x0 0x0 0x0 0x1 0x4d 0x68 0x0 0x0 0x0 0x0 0x0
+0xe 0x1 0x4f 0x6b 0x0 0x0 0x0 0x0 0x0 0x1 0x50 0x6a 0x0 0x0 0x0 0x0 0x0
+0xf 0x1 0x52 0x69 0x0 0x0 0x0 0x0 0x0 0x1 0x54 0x68 0x0 0x0 0x0 0x0 0x0
+0x10 0x1 0x59 0x68 0x0 0x0 0x0 0x0 0x0 0x1 0x5a 0x67 0x0 0x0 0x0 0x0 0x0
+0x11 0x1 0x5b 0x66 0x0 0x0 0x0 0x0 0x0 0x1 0x5e 0x65 0x0 0x0 0x0 0x0 0x0
+0x12 0x1 0x5f 0x64 0x0 0x0 0x0 0x0 0x0 0x1 0x64 0x63 0x0 0x0 0x0 0x0 0x0
+0x13 0x1 0x68 0x64 0x0 0x0 0x0 0x0 0x0 0x1 0x69 0x65 0x0 0x0 0x0 0x0 0x0
+0x14 0x1 0x6e 0x66 0x0 0x0 0x0 0x0 0x0 0x1 0x6f 0x68 0x0 0x0 0x0 0x0 0x0
+0x15 0x1 0x74 0x68 0x0 0x0 0x0 0x0 0x0 0x1 0x76 0x67 0x0 0x0 0x0 0x0 0x0
+0x16 0x1 0x77 0x66 0x0 0x0 0x0 0x0 0x0 0x1 0x7c 0x65 0x0 0x0 0x0 0x0 0x0
+0x17 0x1 0x7e 0x66 0x0 0x0 0x0 0x0 0x0 0x1 0x85 0x64 0x0 0x0 0x0 0x0 0x0
+0x18 0x1 0x86 0x65 0x0 0x0 0x0 0x0 0x0 0x1 0x87 0x64 0x0 0x0 0x0 0x0 0x0
+0x19 0x1 0x8a 0x63 0x0 0x0 0x0 0x0 0x0 0x1 0x8c 0x61 0x0 0x0 0x0 0x0 0x0
+0x1a 0x1 0x90 0x60 0x0 0x0 0x0 0x0 0x0 0x1 0x91 0x5f 0x0 0x0 0x0 0x0 0x0
+0x1b 0x1 0x93 0x5e 0x0 0x0 0x0 0x0 0x0 0x1 0x95 0x5d 0x0 0x0 0x0 0x0 0x0
+0x1c 0x1 0x96 0x5e 0x0 0x0 0x0 0x0 0x0 0x1 0x9b 0x5d 0x0 0x0 0x0 0x0 0x0
+0x1d 0x1 0x9d 0x5f 0x0 0x0 0x0 0x0 0x0 0x1 0x9e 0x5e 0x0 0x0 0x0 0x0 0x0
+0x1e 0x1 0xa0 0x65 0x0 0x0 0x0 0x0 0x0 0x1 0xa2 0x5e 0x0 0x0 0x0 0x0 0x0
+0x1f 0x1 0xa3 0x5d 0x0 0x0 0x0 0x0 0x0 0x1 0xa5 0x56 0x0 0x0 0x0 0x0 0x0
+0x20 0x1 0xa7 0x52 0x0 0x0 0x0 0x0 0x0 0x1 0xa8 0x51 0x0 0x0 0x0 0x0 0x0
+0x21 0x1 0xa9 0x4e 0x0 0x0 0x0 0x0 0x0 0x1 0xac 0x4d 0x0 0x0 0x0 0x0 0x0
+0x22 0x1 0xad 0x4c 0x0 0x0 0x0 0x0 0x0 0x1 0xae 0x4b 0x0 0x0 0x0 0x0 0x0
+0x23 0x1 0xb1 0x4a 0x0 0x0 0x0 0x0 0x0 0x1 0xb2 0x4c 0x0 0x0 0x0 0x0 0x0
+0x24 0x1 0xb3 0x4b 0x0 0x0 0x0 0x0 0x0 0x1 0xb6 0x4a 0x0 0x0 0x0 0x0 0x0
+0x25 0x1 0xb8 0x4b 0x0 0x0 0x0 0x0 0x0 0x1 0xba 0x4c 0x0 0x0 0x0 0x0 0x0
+0x26 0x1 0xbc 0x4d 0x0 0x0 0x0 0x0 0x0 0x1 0xbd 0x4e 0x0 0x0 0x0 0x0 0x0
+0x27 0x1 0xc1 0x50 0x0 0x0 0x0 0x0 0x0 0x1 0xc2 0x51 0x0 0x0 0x0 0x0 0x0
+0x28 0x1 0xc4 0x52 0x0 0x0 0x0 0x0 0x0 0x1 0xc7 0x53 0x0 0x0 0x0 0x0 0x0
+0x29 0x1 0xc9 0x54 0x0 0x0 0x0 0x0 0x0 0x1 0xce 0x55 0x0 0x0 0x0 0x0 0x0
+0x2a 0x1 0xcf 0x56 0x0 0x0 0x0 0x0 0x0 0x1 0xd0 0x55 0x0 0x0 0x0 0x0 0x0
+0x2b 0x1 0xd3 0x56 0x0 0x0 0x0 0x0 0x0 0x1 0xd4 0x57 0x0 0x0 0x0 0x0 0x0
+0x2c 0x1 0xd5 0x54 0x0 0x0 0x0 0x0 0x0 0x1 0xd8 0x53 0x0 0x0 0x0 0x0 0x0
+0x2d 0x1 0xd9 0x55 0x0 0x0 0x0 0x0 0x0 0x1 0xdd 0x54 0x0 0x0 0x0 0x0 0x0
+0x2e 0x1 0xdf 0x55 0x0 0x0 0x0 0x0 0x0 0x1 0xe1 0x56 0x0 0x0 0x0 0x0 0x0
+0x2f 0x1 0xe3 0x57 0x0 0x0 0x0 0x0 0x0 0x1 0xe6 0x59 0x0 0x0 0x0 0x0 0x0
+0x30 0x1 0xe8 0x5a 0x0 0x0 0x0 0x0 0x0 0x1 0xe9 0x5b 0x0 0x0 0x0 0x0 0x0
+0x31 0x1 0xeb 0x5a 0x0 0x0 0x0 0x0 0x0 0x1 0xec 0x53 0x0 0x0 0x0 0x0 0x0
+0x32 0x1 0xee 0x52 0x0 0x0 0x0 0x0 0x0 0x1 0xf0 0x51 0x0 0x0 0x0 0x0 0x0
+0x33 0x1 0xf1 0x4a 0x0 0x0 0x0 0x0 0x0 0x1 0xf3 0x49 0x0 0x0 0x0 0x0 0x0
+0x34 0x1 0xf5 0x42 0x0 0x0 0x0 0x0 0x0 0x1 0xf6 0x43 0x0 0x0 0x0 0x0 0x0
+0x35 0x1 0xf7 0x44 0x0 0x0 0x0 0x0 0x0 0x1 0xfa 0x4b 0x0 0x0 0x0 0x0 0x0
+0x36 0x1 0xfb 0x51 0x0 0x0 0x0 0x0 0x0 0x1 0xfc 0x50 0x0 0x0 0x0 0x0 0x0
+0x37 0x1 0xff 0x4f 0x0 0x0 0x0 0x0 0x0 0x1 0x0 0x50 0x0 0x0 0x0 0x0 0x0
+0x38 0x1 0x1 0x51 0x0 0x0 0x0 0x0 0x0 0x1 0x5 0x50 0x0 0x0 0x0 0x0 0x0
+0x39 0x1 0x8 0x53 0x0 0x0 0x0 0x0 0x0 0x1 0xa 0x51 0x0 0x0 0x0 0x0 0x0
+0x3a 0x1 0xf 0x51 0x0 0x0 0x0 0x0 0x0 0x1 0x12 0x53 0x0 0x0 0x0 0x0 0x0
+0x3b 0x1 0x13 0x5a 0x0 0x0 0x0 0x0 0x0 0x1 0x15 0x5f 0x0 0x0 0x0 0x0 0x0
+0x3c 0x1 0x17 0x64 0x0 0x0 0x0 0x0 0x0 0x1 0x18 0x5e 0x0 0x0 0x0 0x0 0x0
+0x3d 0x1 0x19 0x5f 0x0 0x0 0x0 0x0 0x0 0x1 0x1c 0x60 0x0 0x0 0x0 0x0 0x0
+0x3e 0x1 0x1e 0x5f 0x0 0x0 0x0 0x0 0x0 0x1 0x21 0x5d 0x0 0x0 0x0 0x0 0x0
+0x3f 0x1 0x22 0x5c 0x0 0x0 0x0 0x0 0x0 0x1 0x23 0x5d 0x0 0x0 0x0 0x0 0x0
+0x40 0x1 0x26 0x5e 0x0 0x0 0x0 0x0 0x0 0x1 0x27 0x5f 0x0 0x0 0x0 0x0 0x0
+0x41 0x1 0x2b 0x5e 0x0 0x0 0x0 0x0 0x0 0x1 0x2f 0x5d 0x0 0x0 0x0 0x0 0x0
+0x42 0x1 0x31 0x5c 0x0 0x0 0x0 0x0 0x0 0x1 0x32 0x5a 0x0 0x0 0x0 0x0 0x0
+0x43 0x1 0x34 0x5b 0x0 0x0 0x0 0x0 0x0 0x1 0x35 0x5c 0x0 0x0 0x0 0x0 0x0
+0x44 0x1 0x37 0x5d 0x0 0x0 0x0 0x0 0x0 0x1 0x39 0x5e 0x0 0x0 0x0 0x0 0x0
+0x45 0x1 0x3a 0x5f 0x0 0x0 0x0 0x0 0x0 0x1 0x3b 0x61 0x0 0x0 0x0 0x0 0x0
+0x46 0x1 0x3e 0x63 0x0 0x0 0x0 0x0 0x0 0x1 0x3f 0x64 0x0 0x0 0x0 0x0 0x0
+0x47 0x1 0x43 0x63 0x0 0x0 0x0 0x0 0x0 0x1 0x44 0x64 0x0 0x0 0x0 0x0 0x0
+0x48 0x1 0x45 0x63 0x0 0x0 0x0 0x0 0x0 0x1 0x47 0x62 0x0 0x0 0x0 0x0 0x0
+0x49 0x1 0x49 0x61 0x0 0x0 0x0 0x0 0x0 0x1 0x4e 0x60 0x0 0x0 0x0 0x0 0x0
+0x4a 0x1 0x4f 0x61 0x0 0x0 0x0 0x0 0x0 0x1 0x51 0x60 0x0 0x0 0x0 0x0 0x0
+0x4b 0x1 0x52 0x61 0x0 0x0 0x0 0x0 0x0 0x1 0x56 0x62 0x0 0x0 0x0 0x0 0x0
+0x4c 0x1 0x57 0x63 0x0 0x0 0x0 0x0 0x0 0x1 0x58 0x62 0x0 0x0 0x0 0x0 0x0
+0x4d 0x1 0x5b 0x64 0x0 0x0 0x0 0x0 0x0 0x1 0x5d 0x65 0x0 0x0 0x0 0x0 0x0
+0x4e 0x1 0x60 0x66 0x0 0x0 0x0 0x0 0x0 0x1 0x62 0x67 0x0 0x0 0x0 0x0 0x0
+0x4f 0x1 0x65 0x66 0x0 0x0 0x0 0x0 0x0 0x1 0x66 0x60 0x0 0x0 0x0 0x0 0x0
+0x50 0x1 0x67 0x5a 0x0 0x0 0x0 0x0 0x0 0x1 0x69 0x57 0x0 0x0 0x0 0x0 0x0
+0x51 0x1 0x6b 0x50 0x0 0x0 0x0 0x0 0x0 0x1 0x6c 0x57 0x0 0x0 0x0 0x0 0x0
+0x52 0x1 0x6e 0x56 0x0 0x0 0x0 0x0 0x0 0x1 0x71 0x57 0x0 0x0 0x0 0x0 0x0
+0x53 0x1 0x73 0x58 0x0 0x0 0x0 0x0 0x0 0x1 0x74 0x5b 0x0 0x0 0x0 0x0 0x0
+0x54 0x1 0x78 0x5a 0x0 0x0 0x0 0x0 0x0 0x1 0x7d 0x5a 0x0 0x0 0x0 0x0 0x0
+0x55 0x1 0x82 0x5a 0x0 0x0 0x0 0x0 0x0 0x1 0x83 0x5b 0x0 0x0 0x0 0x0 0x0
+0x56 0x1 0x87 0x5a 0x0 0x0 0x0 0x0 0x0 0x1 0x88 0x59 0x0 0x0 0x0 0x0 0x0
+0x57 0x1 0x8b 0x58 0x0 0x0 0x0 0x0 0x0 0x1 0x8d 0x57 0x0 0x0 0x0 0x0 0x0
+0x58 0x1 0x8e 0x56 0x0 0x0 0x0 0x0 0x0 0x1 0x90 0x57 0x0 0x0 0x0 0x0 0x0
+0x59 0x1 0x93 0x55 0x0 0x0 0x0 0x0 0x0 0x1 0x96 0x54 0x0 0x0 0x0 0x0 0x0
+0x5a 0x1 0x98 0x55 0x0 0x0 0x0 0x0 0x0 0x1 0x9c 0x56 0x0 0x0 0x0 0x0 0x0
+0x5b 0x1 0x9f 0x57 0x0 0x0 0x0 0x0 0x0 0x1 0xa0 0x58 0x0 0x0 0x0 0x0 0x0
+0x5c 0x1 0xa4 0x59 0x0 0x0 0x0 0x0 0x0 0x2 0xa8 0x0 0x0 0x0 0x0 0x0 0x0
diff --git a/app/src/test/resources/GPXExporterTest-SampleTracks.csv b/app/src/test/resources/GPXExporterTest-SampleTracks.csv
new file mode 100644
index 0000000..c66cd70
--- /dev/null
+++ b/app/src/test/resources/GPXExporterTest-SampleTracks.csv
@@ -0,0 +1,14 @@
+-68.200293	44.152462	40	2019-01-01T00:00:00Z
+-68.20027	44.15246	40	2019-01-01T00:00:01Z
+-68.20026	44.152462	40	2019-01-01T00:00:02Z
+-68.200242	44.152493	40	2019-01-01T00:00:03Z
+-68.200237	44.152528	40	2019-01-01T00:00:04Z
+-68.200232	44.152567	40	2019-01-01T00:00:05Z
+-68.200248	44.152612	40	2019-01-01T00:00:06Z
+-68.200253	44.152657	40	2019-01-01T00:00:07Z
+-68.200245	44.152675	46	2019-01-01T00:00:08Z
+-68.200232	44.152695	46	2019-01-01T00:00:09Z
+-68.200215	44.15272	46	2019-01-01T00:00:10Z
+-68.200205	44.152753	46	2019-01-01T00:00:11Z
+-68.200197	44.152808	46	2019-01-01T00:00:12Z
+-68.200203	44.152877	46	2019-01-01T00:00:13Z
\ No newline at end of file
diff --git a/app/src/test/resources/GPXExporterTest-SampleTracksHR.csv b/app/src/test/resources/GPXExporterTest-SampleTracksHR.csv
new file mode 100644
index 0000000..47d55ea
--- /dev/null
+++ b/app/src/test/resources/GPXExporterTest-SampleTracksHR.csv
@@ -0,0 +1,14 @@
+-68.73127	43.101062	40	91	2019-01-01T00:00:00Z
+-68.73126	43.101093	40	91	2019-01-01T00:00:01Z
+-68.731242	43.101128	40	91	2019-01-01T00:00:02Z
+-68.731237	43.101167	40	91	2019-01-01T00:00:03Z
+-68.731232	43.101212	40	91	2019-01-01T00:00:04Z
+-68.731248	43.101257	40	92	2019-01-01T00:00:05Z
+-68.731253	43.101275	40	92	2019-01-01T00:00:06Z
+-68.731245	43.101295	46	93	2019-01-01T00:00:07Z
+-68.731232	43.10132	46	94	2019-01-01T00:00:08Z
+-68.731215	43.101353	46	95	2019-01-01T00:00:09Z
+-68.731205	43.101408	46	95	2019-01-01T00:00:10Z
+-68.731197	43.101477	46	95	2019-01-01T00:00:11Z
+-68.731203	43.10152	46	96	2019-01-01T00:00:12Z
+-68.73121	43.101565	45	96	2019-01-01T00:00:13Z
diff --git a/app/src/test/resources/gpx-exporter-test-SampleTrack.gpx b/app/src/test/resources/gpx-exporter-test-SampleTrack.gpx
new file mode 100644
index 0000000..6fcf1c6
--- /dev/null
+++ b/app/src/test/resources/gpx-exporter-test-SampleTrack.gpx
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<gpx xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:gpxtpx="http://www.garmin.com/xmlschemas/TrackPointExtension/v1" xmlns="http://www.topografix.com/GPX/1/1" xmlns:opentracks="http://opentracksapp.com/xmlschemas/v1" version="1.1" creator="Gadgetbridge Test" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd http://opentracksapp.com/xmlschemas/v1 http://opentracksapp.com/xmlschemas/OpenTracks_v1.xsd">
+  <metadata>
+    <name>Test Track</name>
+    <author>
+      <name>Test User</name>
+    </author>
+    <time>2021-01-16T23:27:34+01:00</time>
+  </metadata>
+  <trk>
+    <extensions>
+      <opentracks:trackid>0555e104-e8ea-4e10-b3e9-8b58abae257f</opentracks:trackid>
+    </extensions>
+    <trkseg>
+      <trkpt lon="-68.200293" lat="44.152462">
+        <ele>40.000000</ele>
+        <time>2019-01-01T00:00:00Z</time>
+      </trkpt>
+      <trkpt lon="-68.200270" lat="44.152460">
+        <ele>40.000000</ele>
+        <time>2019-01-01T00:00:01Z</time>
+      </trkpt>
+      <trkpt lon="-68.200260" lat="44.152462">
+        <ele>40.000000</ele>
+        <time>2019-01-01T00:00:02Z</time>
+      </trkpt>
+      <trkpt lon="-68.200242" lat="44.152493">
+        <ele>40.000000</ele>
+        <time>2019-01-01T00:00:03Z</time>
+      </trkpt>
+      <trkpt lon="-68.200237" lat="44.152528">
+        <ele>40.000000</ele>
+        <time>2019-01-01T00:00:04Z</time>
+      </trkpt>
+      <trkpt lon="-68.200232" lat="44.152567">
+        <ele>40.000000</ele>
+        <time>2019-01-01T00:00:05Z</time>
+      </trkpt>
+      <trkpt lon="-68.200248" lat="44.152612">
+        <ele>40.000000</ele>
+        <time>2019-01-01T00:00:06Z</time>
+      </trkpt>
+      <trkpt lon="-68.200253" lat="44.152657">
+        <ele>40.000000</ele>
+        <time>2019-01-01T00:00:07Z</time>
+      </trkpt>
+      <trkpt lon="-68.200245" lat="44.152675">
+        <ele>46.000000</ele>
+        <time>2019-01-01T00:00:08Z</time>
+      </trkpt>
+      <trkpt lon="-68.200232" lat="44.152695">
+        <ele>46.000000</ele>
+        <time>2019-01-01T00:00:09Z</time>
+      </trkpt>
+      <trkpt lon="-68.200215" lat="44.152720">
+        <ele>46.000000</ele>
+        <time>2019-01-01T00:00:10Z</time>
+      </trkpt>
+      <trkpt lon="-68.200205" lat="44.152753">
+        <ele>46.000000</ele>
+        <time>2019-01-01T00:00:11Z</time>
+      </trkpt>
+      <trkpt lon="-68.200197" lat="44.152808">
+        <ele>46.000000</ele>
+        <time>2019-01-01T00:00:12Z</time>
+      </trkpt>
+      <trkpt lon="-68.200203" lat="44.152877">
+        <ele>46.000000</ele>
+        <time>2019-01-01T00:00:13Z</time>
+      </trkpt>
+    </trkseg>
+  </trk>
+</gpx>
diff --git a/app/src/test/resources/gpx-parser-test-multiple-segments.gpx b/app/src/test/resources/gpx-parser-test-multiple-segments.gpx
new file mode 100644
index 0000000..7ff6ca1
--- /dev/null
+++ b/app/src/test/resources/gpx-parser-test-multiple-segments.gpx
@@ -0,0 +1,57 @@
+<?xml version='1.0' encoding='UTF-8' standalone='yes'?>
+<gpx version="1.1" creator="GBApplication.app().getNameAndVersion()"
+    xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd
+                        http://opentracksapp.com/xmlschemas/v1 http://opentracksapp.com/xmlschemas/OpenTracks_v1.xsd"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xmlns:gpxtpx="http://www.garmin.com/xmlschemas/TrackPointExtension/v1"
+    xmlns="http://www.topografix.com/GPX/1/1"
+    xmlns:opentracks="http://opentracksapp.com/xmlschemas/v1">
+    <metadata>
+        <name>Test Multiple Segments</name>
+        <author />
+        <time>2023-04-09T17:17:52+01:00</time>
+    </metadata>
+    <trk>
+        <trkseg>
+            <trkpt lon="-8.2695876" lat="-70.6666343">
+                <ele>790.000000</ele>
+                <time>2023-04-08T16:03:08Z</time>
+                <extensions>
+                    <gpxtpx:TrackPointExtension>
+                        <gpxtpx:hr>123</gpxtpx:hr>
+                    </gpxtpx:TrackPointExtension>
+                </extensions>
+            </trkpt>
+            <trkpt lon="-8.2653274" lat="-70.6670617">
+                <ele>296.000000</ele>
+                <time>2023-04-08T16:17:19Z</time>
+                <extensions>
+                    <gpxtpx:TrackPointExtension>
+                        <gpxtpx:hr>56</gpxtpx:hr>
+                    </gpxtpx:TrackPointExtension>
+                </extensions>
+            </trkpt>
+        </trkseg>
+        <trkseg/>
+        <trkseg>
+            <trkpt lon="-8.2653274" lat="-70.6670617">
+                <time>2023-04-08T16:34:44Z</time>
+                <ele>205.000000</ele>
+                <extensions>
+                    <gpxtpx:TrackPointExtension>
+                        <gpxtpx:hr>85</gpxtpx:hr>
+                    </gpxtpx:TrackPointExtension>
+                </extensions>
+            </trkpt>
+            <trkpt lon="-8.2695876" lat="-70.6666343">
+                <time>2023-04-08T16:56:57Z</time>
+                <ele>209.000000</ele>
+                <extensions>
+                    <gpxtpx:TrackPointExtension>
+                        <gpxtpx:hr>150</gpxtpx:hr>
+                    </gpxtpx:TrackPointExtension>
+                </extensions>
+            </trkpt>
+        </trkseg>
+    </trk>
+</gpx>
diff --git a/app/src/test/resources/gpx-parser-test-order.gpx b/app/src/test/resources/gpx-parser-test-order.gpx
new file mode 100644
index 0000000..f45eceb
--- /dev/null
+++ b/app/src/test/resources/gpx-parser-test-order.gpx
@@ -0,0 +1,36 @@
+<?xml version='1.0' encoding='UTF-8' standalone='yes'?>
+<gpx version="1.1" creator="GBApplication.app().getNameAndVersion()"
+    xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd
+                        http://opentracksapp.com/xmlschemas/v1 http://opentracksapp.com/xmlschemas/OpenTracks_v1.xsd"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xmlns:gpxtpx="http://www.garmin.com/xmlschemas/TrackPointExtension/v1"
+    xmlns="http://www.topografix.com/GPX/1/1"
+    xmlns:opentracks="http://opentracksapp.com/xmlschemas/v1">
+    <metadata>
+        <name>Order Test</name>
+        <author />
+        <time>2023-04-07T16:15:20+01:00</time>
+    </metadata>
+    <trk>
+        <trkseg>
+            <trkpt lon="-8.2695876" lat="-70.6666343">
+                <ele>790.000000</ele>
+                <time>2023-04-08T16:03:08Z</time>
+                <extensions>
+                    <gpxtpx:TrackPointExtension>
+                        <gpxtpx:hr>123</gpxtpx:hr>
+                    </gpxtpx:TrackPointExtension>
+                </extensions>
+            </trkpt>
+            <trkpt lon="-8.2653274" lat="-70.6670617">
+                <time>2023-04-08T16:17:19Z</time>
+                <ele>296.000000</ele>
+                <extensions>
+                    <gpxtpx:TrackPointExtension>
+                        <gpxtpx:hr>56</gpxtpx:hr>
+                    </gpxtpx:TrackPointExtension>
+                </extensions>
+            </trkpt>
+        </trkseg>
+    </trk>
+</gpx>
diff --git a/app/src/test/resources/gpx.xsd b/app/src/test/resources/gpx.xsd
new file mode 100644
index 0000000..6ffd92d
--- /dev/null
+++ b/app/src/test/resources/gpx.xsd
@@ -0,0 +1,784 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://www.topografix.com/GPX/1/1" targetNamespace="http://www.topografix.com/GPX/1/1" elementFormDefault="qualified">
+
+<xsd:annotation>
+ <xsd:documentation>
+  GPX schema version 1.1 - For more information on GPX and this schema, visit http://www.topografix.com/gpx.asp
+
+  GPX uses the following conventions: all coordinates are relative to the WGS84 datum.  All measurements are in metric units.
+ </xsd:documentation>
+</xsd:annotation>
+
+  <xsd:element name="gpx" type="gpxType">
+    <xsd:annotation>
+      <xsd:documentation>
+		GPX is the root element in the XML file.
+	  </xsd:documentation>
+	</xsd:annotation>
+  </xsd:element>
+
+  <xsd:complexType name="gpxType">
+    <xsd:annotation>
+      <xsd:documentation>
+		GPX documents contain a metadata header, followed by waypoints, routes, and tracks.  You can add your own elements
+		to the extensions section of the GPX document.
+	  </xsd:documentation>
+	</xsd:annotation>
+	<xsd:sequence>
+	 <xsd:element name="metadata" type="metadataType" minOccurs="0">
+	  <xsd:annotation>
+	   <xsd:documentation>
+		Metadata about the file.
+	   </xsd:documentation>
+	  </xsd:annotation>
+	 </xsd:element>
+	 <xsd:element name="wpt" type="wptType" minOccurs="0" maxOccurs="unbounded">
+	  <xsd:annotation>
+	   <xsd:documentation>
+		A list of waypoints.
+	   </xsd:documentation>
+	  </xsd:annotation>
+	 </xsd:element>
+	 <xsd:element name="rte" type="rteType" minOccurs="0" maxOccurs="unbounded">
+	  <xsd:annotation>
+	   <xsd:documentation>
+		A list of routes.
+	   </xsd:documentation>
+	  </xsd:annotation>
+	 </xsd:element>
+	 <xsd:element name="trk" type="trkType" minOccurs="0" maxOccurs="unbounded">
+	  <xsd:annotation>
+	   <xsd:documentation>
+		A list of tracks.
+	   </xsd:documentation>
+	  </xsd:annotation>
+	 </xsd:element>
+	 <xsd:element name="extensions" type="extensionsType" minOccurs="0">
+      <xsd:annotation>
+       <xsd:documentation>
+		You can add extend GPX by adding your own elements from another schema here.
+	   </xsd:documentation>
+	  </xsd:annotation>
+	 </xsd:element>
+	</xsd:sequence>
+
+	<xsd:attribute name="version" type="xsd:string" use="required" fixed="1.1">
+     <xsd:annotation>
+      <xsd:documentation>
+		You must include the version number in your GPX document.
+	  </xsd:documentation>
+	 </xsd:annotation>
+	</xsd:attribute>
+	<xsd:attribute name="creator" type="xsd:string" use="required">
+     <xsd:annotation>
+      <xsd:documentation>
+		You must include the name or URL of the software that created your GPX document.  This allows others to
+		inform the creator of a GPX instance document that fails to validate.
+	  </xsd:documentation>
+	 </xsd:annotation>
+	</xsd:attribute>
+  </xsd:complexType>
+
+  <xsd:complexType name="metadataType">
+    <xsd:annotation>
+      <xsd:documentation>
+		Information about the GPX file, author, and copyright restrictions goes in the metadata section.  Providing rich,
+		meaningful information about your GPX files allows others to search for and use your GPS data.
+	  </xsd:documentation>
+	</xsd:annotation>
+    <xsd:sequence>	<!-- elements must appear in this order -->
+     <xsd:element name="name" type="xsd:string" minOccurs="0">
+      <xsd:annotation>
+       <xsd:documentation>
+		The name of the GPX file.
+	   </xsd:documentation>
+	  </xsd:annotation>
+	 </xsd:element>
+     <xsd:element name="desc" type="xsd:string" minOccurs="0">
+      <xsd:annotation>
+       <xsd:documentation>
+		A description of the contents of the GPX file.
+	   </xsd:documentation>
+	  </xsd:annotation>
+	 </xsd:element>
+     <xsd:element name="author" type="personType" minOccurs="0">
+      <xsd:annotation>
+       <xsd:documentation>
+		The person or organization who created the GPX file.
+	   </xsd:documentation>
+	  </xsd:annotation>
+	 </xsd:element>
+     <xsd:element name="copyright" type="copyrightType" minOccurs="0">
+      <xsd:annotation>
+       <xsd:documentation>
+		Copyright and license information governing use of the file.
+	   </xsd:documentation>
+	  </xsd:annotation>
+	 </xsd:element>
+     <xsd:element name="link" type="linkType" minOccurs="0" maxOccurs="unbounded">
+      <xsd:annotation>
+       <xsd:documentation>
+		URLs associated with the location described in the file.
+	   </xsd:documentation>
+	  </xsd:annotation>
+	 </xsd:element>
+     <xsd:element name="time" type="xsd:dateTime" minOccurs="0">
+      <xsd:annotation>
+       <xsd:documentation>
+		The creation date of the file.
+	   </xsd:documentation>
+	  </xsd:annotation>
+	 </xsd:element>
+     <xsd:element name="keywords" type="xsd:string" minOccurs="0">
+      <xsd:annotation>
+       <xsd:documentation>
+		Keywords associated with the file.  Search engines or databases can use this information to classify the data.
+	   </xsd:documentation>
+	  </xsd:annotation>
+	 </xsd:element>
+     <xsd:element name="bounds" type="boundsType" minOccurs="0">
+      <xsd:annotation>
+       <xsd:documentation>
+		Minimum and maximum coordinates which describe the extent of the coordinates in the file.
+	   </xsd:documentation>
+	  </xsd:annotation>
+	 </xsd:element>
+
+	 <xsd:element name="extensions" type="extensionsType" minOccurs="0">
+      <xsd:annotation>
+       <xsd:documentation>
+		You can add extend GPX by adding your own elements from another schema here.
+	   </xsd:documentation>
+	  </xsd:annotation>
+	 </xsd:element>
+    </xsd:sequence>
+  </xsd:complexType>
+
+  <xsd:complexType name="wptType">
+    <xsd:annotation>
+      <xsd:documentation>
+		wpt represents a waypoint, point of interest, or named feature on a map.
+	  </xsd:documentation>
+	</xsd:annotation>
+    <xsd:sequence>	<!-- elements must appear in this order -->
+	  <!-- Position info -->
+      <xsd:element name="ele" type="xsd:decimal" minOccurs="0">
+		<xsd:annotation>
+		  <xsd:documentation>
+			Elevation (in meters) of the point.
+		  </xsd:documentation>
+		</xsd:annotation>
+	  </xsd:element>
+      <xsd:element name="time" type="xsd:dateTime" minOccurs="0">
+		<xsd:annotation>
+		  <xsd:documentation>
+			Creation/modification timestamp for element. Date and time in are in Univeral Coordinated Time (UTC), not local time! Conforms to ISO 8601 specification for date/time representation. Fractional seconds are allowed for millisecond timing in tracklogs. 
+		  </xsd:documentation>
+		</xsd:annotation>
+	  </xsd:element>
+	  <xsd:element name="magvar" type="degreesType" minOccurs="0">
+		<xsd:annotation>
+		  <xsd:documentation>
+			Magnetic variation (in degrees) at the point
+		  </xsd:documentation>
+		</xsd:annotation>
+	  </xsd:element>
+	  <xsd:element name="geoidheight" type="xsd:decimal" minOccurs="0">
+		<xsd:annotation>
+		  <xsd:documentation>
+			Height (in meters) of geoid (mean sea level) above WGS84 earth ellipsoid.  As defined in NMEA GGA message.
+		  </xsd:documentation>
+		</xsd:annotation>
+	  </xsd:element>
+
+	  <!-- Description info -->
+	  <xsd:element name="name" type="xsd:string" minOccurs="0">
+		<xsd:annotation>
+		  <xsd:documentation>
+			The GPS name of the waypoint. This field will be transferred to and from the GPS. GPX does not place restrictions on the length of this field or the characters contained in it. It is up to the receiving application to validate the field before sending it to the GPS.
+		  </xsd:documentation>
+		</xsd:annotation>
+	  </xsd:element>
+	  <xsd:element name="cmt" type="xsd:string" minOccurs="0">
+		<xsd:annotation>
+		  <xsd:documentation>
+			GPS waypoint comment. Sent to GPS as comment. 
+		  </xsd:documentation>
+		</xsd:annotation>
+	  </xsd:element>
+	  <xsd:element name="desc" type="xsd:string" minOccurs="0">
+		<xsd:annotation>
+		  <xsd:documentation>
+			A text description of the element. Holds additional information about the element intended for the user, not the GPS.
+		  </xsd:documentation>
+		</xsd:annotation>
+	  </xsd:element>
+	  <xsd:element name="src" type="xsd:string" minOccurs="0">
+		<xsd:annotation>
+		  <xsd:documentation>
+			Source of data. Included to give user some idea of reliability and accuracy of data.  "Garmin eTrex", "USGS quad Boston North", e.g.
+		  </xsd:documentation>
+		</xsd:annotation>
+	  </xsd:element>
+      <xsd:element name="link" type="linkType" minOccurs="0" maxOccurs="unbounded">
+		<xsd:annotation>
+		  <xsd:documentation>
+			Link to additional information about the waypoint.
+		  </xsd:documentation>
+		</xsd:annotation>
+	  </xsd:element>
+	  <xsd:element name="sym" type="xsd:string" minOccurs="0">
+		<xsd:annotation>
+		  <xsd:documentation>
+			Text of GPS symbol name. For interchange with other programs, use the exact spelling of the symbol as displayed on the GPS.  If the GPS abbreviates words, spell them out.
+		  </xsd:documentation>
+		</xsd:annotation>
+	  </xsd:element>
+	  <xsd:element name="type" type="xsd:string" minOccurs="0">
+		<xsd:annotation>
+		  <xsd:documentation>
+			Type (classification) of the waypoint.
+		  </xsd:documentation>
+		</xsd:annotation>
+	  </xsd:element>
+
+	  <!-- Accuracy info -->
+	  <xsd:element name="fix" type="fixType" minOccurs="0">
+		<xsd:annotation>
+		  <xsd:documentation>
+			Type of GPX fix.
+		  </xsd:documentation>
+		</xsd:annotation>
+	  </xsd:element>
+	  <xsd:element name="sat" type="xsd:nonNegativeInteger" minOccurs="0">
+		<xsd:annotation>
+		  <xsd:documentation>
+			Number of satellites used to calculate the GPX fix.
+		  </xsd:documentation>
+		</xsd:annotation>
+	  </xsd:element>
+	  <xsd:element name="hdop" type="xsd:decimal" minOccurs="0">
+		<xsd:annotation>
+		  <xsd:documentation>
+			Horizontal dilution of precision.
+		  </xsd:documentation>
+		</xsd:annotation>
+	  </xsd:element>
+	  <xsd:element name="vdop" type="xsd:decimal" minOccurs="0">
+		<xsd:annotation>
+		  <xsd:documentation>
+			Vertical dilution of precision.
+		  </xsd:documentation>
+		</xsd:annotation>
+	  </xsd:element>
+	  <xsd:element name="pdop" type="xsd:decimal" minOccurs="0">
+		<xsd:annotation>
+		  <xsd:documentation>
+			Position dilution of precision.
+		  </xsd:documentation>
+		</xsd:annotation>
+	  </xsd:element>
+	  <xsd:element name="ageofdgpsdata" type="xsd:decimal" minOccurs="0">
+		<xsd:annotation>
+		  <xsd:documentation>
+			Number of seconds since last DGPS update.
+		  </xsd:documentation>
+		</xsd:annotation>
+	  </xsd:element>
+	  <xsd:element name="dgpsid" type="dgpsStationType" minOccurs="0">
+		<xsd:annotation>
+		  <xsd:documentation>
+			ID of DGPS station used in differential correction.
+		  </xsd:documentation>
+		</xsd:annotation>
+	  </xsd:element>
+
+	 <xsd:element name="extensions" type="extensionsType" minOccurs="0">
+      <xsd:annotation>
+       <xsd:documentation>
+		You can add extend GPX by adding your own elements from another schema here.
+	   </xsd:documentation>
+	  </xsd:annotation>
+	 </xsd:element>
+    </xsd:sequence>
+
+    <xsd:attribute name="lat" type="latitudeType" use="required">
+	 <xsd:annotation>
+	  <xsd:documentation>
+		The latitude of the point.  This is always in decimal degrees, and always in WGS84 datum.
+	  </xsd:documentation>
+	 </xsd:annotation>
+	</xsd:attribute>
+    <xsd:attribute name="lon" type="longitudeType" use="required">
+	 <xsd:annotation>
+	  <xsd:documentation>
+      The longitude of the point.  This is always in decimal degrees, and always in WGS84 datum.
+    </xsd:documentation>
+	 </xsd:annotation>
+	</xsd:attribute>
+  </xsd:complexType>
+
+  <xsd:complexType name="rteType">
+    <xsd:annotation>
+      <xsd:documentation>
+		rte represents route - an ordered list of waypoints representing a series of turn points leading to a destination.
+	  </xsd:documentation>
+	</xsd:annotation>
+    <xsd:sequence>
+      <xsd:element name="name" type="xsd:string" minOccurs="0">
+		<xsd:annotation>
+		  <xsd:documentation>
+			GPS name of route.
+		  </xsd:documentation>
+		</xsd:annotation>
+	  </xsd:element>
+	  <xsd:element name="cmt" type="xsd:string" minOccurs="0">
+		<xsd:annotation>
+		  <xsd:documentation>
+			GPS comment for route.
+		  </xsd:documentation>
+		</xsd:annotation>
+	  </xsd:element>
+      <xsd:element name="desc" type="xsd:string" minOccurs="0">
+		<xsd:annotation>
+		  <xsd:documentation>
+			Text description of route for user.  Not sent to GPS.
+		  </xsd:documentation>
+		</xsd:annotation>
+	  </xsd:element>
+	  <xsd:element name="src" type="xsd:string" minOccurs="0">
+		<xsd:annotation>
+		  <xsd:documentation>
+			Source of data. Included to give user some idea of reliability and accuracy of data.
+		  </xsd:documentation>
+		</xsd:annotation>
+	  </xsd:element>
+      <xsd:element name="link" type="linkType" minOccurs="0" maxOccurs="unbounded">
+		<xsd:annotation>
+		  <xsd:documentation>
+			Links to external information about the route.
+		  </xsd:documentation>
+		</xsd:annotation>
+	  </xsd:element>
+	  <xsd:element name="number" type="xsd:nonNegativeInteger" minOccurs="0">
+		<xsd:annotation>
+		  <xsd:documentation>
+			GPS route number.
+		  </xsd:documentation>
+		</xsd:annotation>
+	  </xsd:element>
+	  <xsd:element name="type" type="xsd:string" minOccurs="0">
+		<xsd:annotation>
+		  <xsd:documentation>
+			Type (classification) of route.
+		  </xsd:documentation>
+		</xsd:annotation>
+	  </xsd:element>
+
+	 <xsd:element name="extensions" type="extensionsType" minOccurs="0">
+      <xsd:annotation>
+       <xsd:documentation>
+		You can add extend GPX by adding your own elements from another schema here.
+	   </xsd:documentation>
+	  </xsd:annotation>
+	 </xsd:element>
+ 
+      <xsd:element name="rtept" type="wptType" minOccurs="0" maxOccurs="unbounded">
+	  <xsd:annotation>
+	   <xsd:documentation>
+		A list of route points.
+	   </xsd:documentation>
+	  </xsd:annotation>
+	 </xsd:element>
+    </xsd:sequence>
+  </xsd:complexType>
+
+  <xsd:complexType name="trkType">
+    <xsd:annotation>
+      <xsd:documentation>
+		trk represents a track - an ordered list of points describing a path.
+	  </xsd:documentation>
+	</xsd:annotation>
+    <xsd:sequence>
+      <xsd:element name="name" type="xsd:string" minOccurs="0">
+		<xsd:annotation>
+		  <xsd:documentation>
+			GPS name of track.
+		  </xsd:documentation>
+		</xsd:annotation>
+	  </xsd:element>
+	  <xsd:element name="cmt" type="xsd:string" minOccurs="0">
+		<xsd:annotation>
+		  <xsd:documentation>
+			GPS comment for track.
+		  </xsd:documentation>
+		</xsd:annotation>
+	  </xsd:element>
+      <xsd:element name="desc" type="xsd:string" minOccurs="0">
+		<xsd:annotation>
+		  <xsd:documentation>
+			User description of track.
+		  </xsd:documentation>
+		</xsd:annotation>
+	  </xsd:element>
+	  <xsd:element name="src" type="xsd:string" minOccurs="0">
+		<xsd:annotation>
+		  <xsd:documentation>
+			Source of data. Included to give user some idea of reliability and accuracy of data.
+		  </xsd:documentation>
+		</xsd:annotation>
+	  </xsd:element>
+      <xsd:element name="link" type="linkType" minOccurs="0" maxOccurs="unbounded">
+		<xsd:annotation>
+		  <xsd:documentation>
+			Links to external information about track.
+		  </xsd:documentation>
+		</xsd:annotation>
+	  </xsd:element>
+	  <xsd:element name="number" type="xsd:nonNegativeInteger" minOccurs="0">
+		<xsd:annotation>
+		  <xsd:documentation>
+			GPS track number.
+		  </xsd:documentation>
+		</xsd:annotation>
+	  </xsd:element>
+	  <xsd:element name="type" type="xsd:string" minOccurs="0">
+		<xsd:annotation>
+		  <xsd:documentation>
+			Type (classification) of track.
+		  </xsd:documentation>
+		</xsd:annotation>
+	  </xsd:element>
+
+	 <xsd:element name="extensions" type="extensionsType" minOccurs="0">
+      <xsd:annotation>
+       <xsd:documentation>
+		You can add extend GPX by adding your own elements from another schema here.
+	   </xsd:documentation>
+	  </xsd:annotation>
+	 </xsd:element>
+  
+     <xsd:element name="trkseg" type="trksegType" minOccurs="0" maxOccurs="unbounded">
+      <xsd:annotation>
+       <xsd:documentation>
+		A Track Segment holds a list of Track Points which are logically connected in order. To represent a single GPS track where GPS reception was lost, or the GPS receiver was turned off, start a new Track Segment for each continuous span of track data.
+	   </xsd:documentation>
+	  </xsd:annotation>
+	 </xsd:element>
+    </xsd:sequence>
+  </xsd:complexType>
+ 
+  <xsd:complexType name="extensionsType">
+   <xsd:annotation>
+    <xsd:documentation>
+	 You can add extend GPX by adding your own elements from another schema here.
+    </xsd:documentation>
+   </xsd:annotation>
+    <xsd:sequence>
+	 <xsd:any namespace="##other" processContents="lax" minOccurs="0" maxOccurs="unbounded">
+	   <xsd:annotation>
+		<xsd:documentation>
+		 You can add extend GPX by adding your own elements from another schema here.
+		</xsd:documentation>
+	   </xsd:annotation>
+	 </xsd:any>
+    </xsd:sequence>
+  </xsd:complexType>
+
+  <xsd:complexType name="trksegType">
+   <xsd:annotation>
+    <xsd:documentation>
+ 	 A Track Segment holds a list of Track Points which are logically connected in order. To represent a single GPS track where GPS reception was lost, or the GPS receiver was turned off, start a new Track Segment for each continuous span of track data.
+    </xsd:documentation>
+   </xsd:annotation>
+   <xsd:sequence>	<!-- elements must appear in this order -->
+	 <xsd:element name="trkpt" type="wptType" minOccurs="0" maxOccurs="unbounded">
+      <xsd:annotation>
+       <xsd:documentation>
+		A Track Point holds the coordinates, elevation, timestamp, and metadata for a single point in a track.
+	   </xsd:documentation>
+	  </xsd:annotation>
+	 </xsd:element>
+
+	 <xsd:element name="extensions" type="extensionsType" minOccurs="0">
+      <xsd:annotation>
+       <xsd:documentation>
+		You can add extend GPX by adding your own elements from another schema here.
+	   </xsd:documentation>
+	  </xsd:annotation>
+	 </xsd:element>
+    </xsd:sequence>
+  </xsd:complexType>
+
+  <xsd:complexType name="copyrightType">
+   <xsd:annotation>
+    <xsd:documentation>
+	 Information about the copyright holder and any license governing use of this file.  By linking to an appropriate license,
+	 you may place your data into the public domain or grant additional usage rights.
+    </xsd:documentation>
+   </xsd:annotation>
+   <xsd:sequence>	<!-- elements must appear in this order -->
+    <xsd:element name="year" type="xsd:gYear" minOccurs="0">
+	 <xsd:annotation>
+	  <xsd:documentation>
+		Year of copyright.
+	  </xsd:documentation>
+	 </xsd:annotation>
+	</xsd:element>
+    <xsd:element name="license" type="xsd:anyURI" minOccurs="0">
+	 <xsd:annotation>
+	  <xsd:documentation>
+		Link to external file containing license text.
+	  </xsd:documentation>
+	 </xsd:annotation>
+	</xsd:element>
+   </xsd:sequence>
+   <xsd:attribute name="author" type="xsd:string" use="required">
+	 <xsd:annotation>
+	  <xsd:documentation>
+		Copyright holder (TopoSoft, Inc.)
+	  </xsd:documentation>
+	 </xsd:annotation>
+	</xsd:attribute>
+  </xsd:complexType>
+
+  <xsd:complexType name="linkType">
+   <xsd:annotation>
+    <xsd:documentation>
+	 A link to an external resource (Web page, digital photo, video clip, etc) with additional information.
+    </xsd:documentation>
+   </xsd:annotation>
+   <xsd:sequence>	<!-- elements must appear in this order -->
+    <xsd:element name="text" type="xsd:string" minOccurs="0">
+	 <xsd:annotation>
+	  <xsd:documentation>
+		Text of hyperlink.
+	  </xsd:documentation>
+	 </xsd:annotation>
+	</xsd:element>
+    <xsd:element name="type" type="xsd:string" minOccurs="0">
+	 <xsd:annotation>
+	  <xsd:documentation>
+		Mime type of content (image/jpeg)
+	  </xsd:documentation>
+	 </xsd:annotation>
+	</xsd:element>
+   </xsd:sequence>
+   <xsd:attribute name="href" type="xsd:anyURI" use="required">
+	 <xsd:annotation>
+	  <xsd:documentation>
+		URL of hyperlink.
+	  </xsd:documentation>
+	 </xsd:annotation>
+	</xsd:attribute>
+  </xsd:complexType>
+
+  <xsd:complexType name="emailType">
+   <xsd:annotation>
+    <xsd:documentation>
+	 An email address.  Broken into two parts (id and domain) to help prevent email harvesting.
+    </xsd:documentation>
+   </xsd:annotation>
+   <xsd:attribute name="id" type="xsd:string" use="required">
+	 <xsd:annotation>
+	  <xsd:documentation>
+		id half of email address (billgates2004)
+	  </xsd:documentation>
+	 </xsd:annotation>
+	</xsd:attribute>
+   <xsd:attribute name="domain" type="xsd:string" use="required">
+	 <xsd:annotation>
+	  <xsd:documentation>
+		domain half of email address (hotmail.com)
+	  </xsd:documentation>
+	 </xsd:annotation>
+	</xsd:attribute>
+  </xsd:complexType>
+
+  <xsd:complexType name="personType">
+   <xsd:annotation>
+    <xsd:documentation>
+	 A person or organization.
+    </xsd:documentation>
+   </xsd:annotation>
+    <xsd:sequence>	<!-- elements must appear in this order -->
+      <xsd:element name="name" type="xsd:string" minOccurs="0">
+	 <xsd:annotation>
+	  <xsd:documentation>
+		Name of person or organization.
+	  </xsd:documentation>
+	 </xsd:annotation>
+	</xsd:element>
+      <xsd:element name="email" type="emailType" minOccurs="0">
+	 <xsd:annotation>
+	  <xsd:documentation>
+		Email address.
+	  </xsd:documentation>
+	 </xsd:annotation>
+	</xsd:element>
+      <xsd:element name="link" type="linkType" minOccurs="0">
+	 <xsd:annotation>
+	  <xsd:documentation>
+		Link to Web site or other external information about person.
+	  </xsd:documentation>
+	 </xsd:annotation>
+	</xsd:element>
+	</xsd:sequence>
+  </xsd:complexType>
+
+  <xsd:complexType name="ptType">
+   <xsd:annotation>
+    <xsd:documentation>
+	 A geographic point with optional elevation and time.  Available for use by other schemas.
+    </xsd:documentation>
+   </xsd:annotation>
+   <xsd:sequence>	<!-- elements must appear in this order -->
+    <xsd:element name="ele" type="xsd:decimal" minOccurs="0">
+	 <xsd:annotation>
+	  <xsd:documentation>
+		The elevation (in meters) of the point.
+	  </xsd:documentation>
+	 </xsd:annotation>
+	</xsd:element>
+    <xsd:element name="time" type="xsd:dateTime" minOccurs="0">
+	 <xsd:annotation>
+	  <xsd:documentation>
+		The time that the point was recorded.
+	  </xsd:documentation>
+	 </xsd:annotation>
+	</xsd:element>
+   </xsd:sequence>
+    <xsd:attribute name="lat" type="latitudeType" use="required">
+	 <xsd:annotation>
+	  <xsd:documentation>
+		The latitude of the point.  Decimal degrees, WGS84 datum.
+	  </xsd:documentation>
+	 </xsd:annotation>
+	</xsd:attribute>
+    <xsd:attribute name="lon" type="longitudeType" use="required">
+	 <xsd:annotation>
+	  <xsd:documentation>
+		The latitude of the point.  Decimal degrees, WGS84 datum.
+	  </xsd:documentation>
+	 </xsd:annotation>
+	</xsd:attribute>
+  </xsd:complexType>
+
+  <xsd:complexType name="ptsegType">
+   <xsd:annotation>
+    <xsd:documentation>
+	 An ordered sequence of points.  (for polygons or polylines, e.g.)
+    </xsd:documentation>
+   </xsd:annotation>
+   <xsd:sequence>	<!-- elements must appear in this order -->
+	 <xsd:element name="pt" type="ptType" minOccurs="0" maxOccurs="unbounded">
+	   <xsd:annotation>
+		<xsd:documentation>
+		 Ordered list of geographic points.
+		</xsd:documentation>
+	   </xsd:annotation>
+	 </xsd:element>
+   </xsd:sequence>
+  </xsd:complexType>
+
+  <xsd:complexType name="boundsType">
+   <xsd:annotation>
+    <xsd:documentation>
+	 Two lat/lon pairs defining the extent of an element.
+    </xsd:documentation>
+   </xsd:annotation>
+    <xsd:attribute name="minlat" type="latitudeType" use="required">
+	 <xsd:annotation>
+	  <xsd:documentation>
+		The minimum latitude.
+	  </xsd:documentation>
+	 </xsd:annotation>
+	</xsd:attribute>
+    <xsd:attribute name="minlon" type="longitudeType" use="required">
+	 <xsd:annotation>
+	  <xsd:documentation>
+		The minimum longitude.
+	  </xsd:documentation>
+	 </xsd:annotation>
+	</xsd:attribute>
+    <xsd:attribute name="maxlat" type="latitudeType" use="required">
+	 <xsd:annotation>
+	  <xsd:documentation>
+		The maximum latitude.
+	  </xsd:documentation>
+	 </xsd:annotation>
+	</xsd:attribute>
+    <xsd:attribute name="maxlon" type="longitudeType" use="required">
+	 <xsd:annotation>
+	  <xsd:documentation>
+		The maximum longitude.
+	  </xsd:documentation>
+	 </xsd:annotation>
+	</xsd:attribute>
+  </xsd:complexType>
+
+
+  <xsd:simpleType name="latitudeType">
+	 <xsd:annotation>
+	  <xsd:documentation>
+		The latitude of the point.  Decimal degrees, WGS84 datum.
+	  </xsd:documentation>
+	 </xsd:annotation>
+    <xsd:restriction base="xsd:decimal">
+      <xsd:minInclusive value="-90.0"/>
+      <xsd:maxInclusive value="90.0"/>
+    </xsd:restriction>
+  </xsd:simpleType>
+
+  <xsd:simpleType name="longitudeType">
+	 <xsd:annotation>
+	  <xsd:documentation>
+		The longitude of the point.  Decimal degrees, WGS84 datum.
+	  </xsd:documentation>
+	 </xsd:annotation>
+    <xsd:restriction base="xsd:decimal">
+      <xsd:minInclusive value="-180.0"/>
+      <xsd:maxExclusive value="180.0"/>
+    </xsd:restriction>
+  </xsd:simpleType>
+
+  <xsd:simpleType name="degreesType">
+	 <xsd:annotation>
+	  <xsd:documentation>
+		Used for bearing, heading, course.  Units are decimal degrees, true (not magnetic).
+	  </xsd:documentation>
+	 </xsd:annotation>
+    <xsd:restriction base="xsd:decimal">
+      <xsd:minInclusive value="0.0"/>
+      <xsd:maxExclusive value="360.0"/>
+    </xsd:restriction>
+  </xsd:simpleType>
+
+  <xsd:simpleType name="fixType">
+	 <xsd:annotation>
+	  <xsd:documentation>
+		Type of GPS fix.  none means GPS had no fix.  To signify "the fix info is unknown, leave out fixType entirely. pps = military signal used
+	  </xsd:documentation>
+	 </xsd:annotation>
+    <xsd:restriction base="xsd:string">
+      <xsd:enumeration value="none"/>
+      <xsd:enumeration value="2d"/>
+      <xsd:enumeration value="3d"/>
+      <xsd:enumeration value="dgps"/>
+      <xsd:enumeration value="pps"/>
+    </xsd:restriction>
+  </xsd:simpleType>
+
+  <xsd:simpleType name="dgpsStationType">
+   <xsd:annotation>
+    <xsd:documentation>
+	 Represents a differential GPS station.
+    </xsd:documentation>
+   </xsd:annotation>
+    <xsd:restriction base="xsd:integer">
+      <xsd:minInclusive value="0"/>
+      <xsd:maxInclusive value="1023"/>
+    </xsd:restriction>
+  </xsd:simpleType>
+
+</xsd:schema>
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..d9321cf
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,28 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+    repositories {
+        mavenCentral()
+        google()
+        maven {
+            url 'https://plugins.gradle.org/m2/'
+        }
+    }
+    dependencies {
+        classpath 'com.android.tools.build:gradle:8.6.1'
+
+        classpath 'com.google.protobuf:protobuf-gradle-plugin:0.9.4'
+        // NOTE: Do not place your application dependencies here; they belong
+        // in the individual module build.gradle files
+    }
+}
+
+allprojects {
+    repositories {
+        mavenCentral()
+        maven {
+            url 'https://jitpack.io'
+        }
+        google()
+    }
+}
diff --git a/external/build_fossil_hr_gbapps.sh b/external/build_fossil_hr_gbapps.sh
new file mode 100644
index 0000000..78346c7
--- /dev/null
+++ b/external/build_fossil_hr_gbapps.sh
@@ -0,0 +1,29 @@
+#!/bin/bash
+pushd jerryscript
+gcc_version="$(gcc -v 2>&1 | grep -oe '^gcc version [0-9][0-9\.]*[0-9]' | sed 's|^.* ||;s|\..*||')"
+(( gcc_version > 11 )) && git apply ../patches/jerryscript-gcc-12-build-fix.patch
+python3 tools/build.py --jerry-cmdline-snapshot ON
+popd
+pushd fossil-hr-gbapps/watchface
+export jerry=../../jerryscript/build/bin/jerry-snapshot
+$jerry generate -f '' open_source_watchface.js -o openSourceWatchface.bin
+$jerry generate -f '' widget_date.js -o widgetDate.bin
+$jerry generate -f '' widget_weather.js -o widgetWeather.bin
+$jerry generate -f '' widget_steps.js -o widgetSteps.bin
+$jerry generate -f '' widget_hr.js -o widgetHR.bin
+$jerry generate -f '' widget_battery.js -o widgetBattery.bin
+$jerry generate -f '' widget_calories.js -o widgetCalories.bin
+$jerry generate -f '' widget_2nd_tz.js -o widget2ndTZ.bin
+$jerry generate -f '' widget_activemins.js -o widgetActiveMins.bin
+$jerry generate -f '' widget_chanceofrain.js -o widgetChanceOfRain.bin
+$jerry generate -f '' widget_uv.js -o widgetUV.bin
+$jerry generate -f '' widget_spo2.js -o widgetSpO2.bin
+$jerry generate -f '' widget_custom.js -o widgetCustom.bin
+popd
+mv fossil-hr-gbapps/watchface/*.bin ../app/src/main/assets/fossil_hr/
+pushd fossil-hr-gbapps/navigationApp
+mkdir -p build/files/{code,config,display_name,icons,layout}
+$jerry generate -f '' app.js -o build/files/code/navigationApp
+python3 ../../pack.py -i build/ -o navigationApp.wapp
+popd
+mv fossil-hr-gbapps/navigationApp/navigationApp.wapp ../app/src/main/assets/fossil_hr/
diff --git a/external/pack.py b/external/pack.py
new file mode 100644
index 0000000..4326724
--- /dev/null
+++ b/external/pack.py
@@ -0,0 +1,124 @@
+# File downloaded from https://github.com/dakhnod/Fossil-HR-SDK/
+
+import sys
+import os
+import json
+import crc32c
+import getopt
+
+class Packer:
+    def __init__(self):
+        self.file_block = bytearray()
+
+    def put_int(self, content, length=4):
+        self.file_block.extend(content.to_bytes(length, 'little'))
+
+    def pack(self, input_dir_path, output_file_path):
+        start_path = os.getcwd()
+
+        if not os.path.isdir(input_dir_path):
+            print('cannot find dir %s' % input_dir_path)
+            exit()
+        os.chdir(input_dir_path)
+
+        with open('app.json', 'r') as json_file:
+            app_meta = json.load(json_file)
+
+        os.chdir('files')
+
+        all_files = []
+        dir_sizes = {}
+
+        for files_dir_list in [('code', False), ('icons', False), ('layout', True), ('display_name', True), ('config', True)]:
+            dir_size = 0
+            files_dir = files_dir_list[0]
+            append_null = files_dir_list[1]
+            files = os.listdir(files_dir)
+            os.chdir(files_dir)
+            for file in sorted(files):
+                print(f'packing {file}')
+                with open(file, 'rb')as f:
+                    contents = bytearray(f.read())
+                    if append_null:
+                        contents.append(0)
+                    file_size = contents.__len__()
+                    all_files.append({
+                        'filename': file,
+                        'contents': contents,
+                        'size': file_size
+                    })
+                    dir_size = dir_size + file_size + file.__len__() + 4 # null byte + size bytes
+            os.chdir(os.pardir)
+            dir_sizes[files_dir] = dir_size
+
+        offset_code = 88
+        offset_icons = offset_code + dir_sizes['code']
+        offset_layout = offset_icons + dir_sizes['icons']
+        offset_display_name = offset_layout + dir_sizes['layout']
+        offset_config = offset_display_name + dir_sizes['display_name']
+        offset_file_end = offset_config + dir_sizes['config']
+
+        self.file_block.extend([int(octet) for octet in app_meta['version'].split('.')])
+
+        self.put_int(0)
+        self.put_int(0)
+        self.put_int(offset_code)
+        self.put_int(offset_icons)
+        self.put_int(offset_layout)
+        self.put_int(offset_display_name)
+        self.put_int(offset_display_name)
+        self.put_int(offset_config)
+        self.put_int(offset_file_end)
+        self.put_int(0)
+        self.put_int(0)
+        self.put_int(0)
+        self.put_int(0)
+        self.put_int(0)
+        self.put_int(0)
+        self.put_int(0)
+        self.put_int(0)
+        self.put_int(0)
+
+        for file in all_files:
+            filename = file['filename']
+            self.put_int(filename.__len__() + 1, 1)
+            self.file_block.extend(filename.encode('utf-8'))
+            self.put_int(0, 1) # null byte ending
+            self.put_int(file['size'], 2)
+            self.file_block.extend(file['contents'])
+
+        os.chdir(start_path)
+
+        identifier = all_files[0]['filename']
+
+        full_file = bytearray()
+        full_file.extend([0xFE, 0x15]) # file handle
+        full_file.extend([0x03, 0x00]) # file version
+        full_file.extend(int(0).to_bytes(4, 'little')) # file offset
+        full_file.extend(self.file_block.__len__().to_bytes(4, 'little')) # file size
+        full_file.extend(self.file_block)
+        full_file.extend(crc32c.crc32c(self.file_block).to_bytes(4, 'little'))
+
+        if output_file_path is None:
+            output_file_path = identifier
+
+        with open(output_file_path, 'wb') as output_file:
+            output_file.write(full_file)
+
+
+
+def main():
+    packer = Packer()
+    input_dir_path = None
+    output_file_path = None
+    args, remainder = getopt.getopt(sys.argv[1:], 'i:o:', ['input=', 'output='])
+    for key, value in args:
+        if key in ['-i', '--input']:
+            input_dir_path = value
+        elif key in ['-o', '--output']:
+            output_file_path = value
+    packer.pack(input_dir_path, output_file_path)
+
+
+if __name__ == '__main__':
+    main()
diff --git a/external/patches/jerryscript-gcc-12-build-fix.patch b/external/patches/jerryscript-gcc-12-build-fix.patch
new file mode 100644
index 0000000..943c7b8
--- /dev/null
+++ b/external/patches/jerryscript-gcc-12-build-fix.patch
@@ -0,0 +1,35 @@
+diff --git a/jerry-ext/handler/handler-register.c b/jerry-ext/handler/handler-register.c
+index ad35d8c3..d808061f 100644
+--- a/jerry-ext/handler/handler-register.c
++++ b/jerry-ext/handler/handler-register.c
+@@ -63,7 +63,13 @@ jerryx_set_properties (const jerry_value_t target_object, /**< target object */
+ {
+ #define JERRYX_SET_PROPERTIES_RESULT(VALUE, IDX) ((jerryx_register_result) { VALUE, IDX })
+   uint32_t idx = 0;
+-  for (; ((entries + idx) != NULL) && (entries[idx].name != NULL); idx++)
++
++  if (entries == NULL)
++  {
++    return JERRYX_SET_PROPERTIES_RESULT (jerry_create_undefined (), 0);
++  }
++
++  for (; (entries[idx].name != NULL); idx++)
+   {
+     const jerryx_property_entry *entry = &entries[idx];
+
+@@ -99,9 +105,12 @@ void
+ jerryx_release_property_entry (const jerryx_property_entry entries[], /**< list of property entries */
+                                const jerryx_register_result register_result) /**< previous result of registration */
+ {
+-  for (uint32_t idx = register_result.registered;
+-       ((entries + idx) != NULL) && (entries[idx].name != NULL);
+-       idx++)
++  if (entries == NULL)
++  {
++    return;
++  }
++
++  for (uint32_t idx = register_result.registered; entries[idx].name != NULL; idx++)
+   {
+     jerry_release_value (entries[idx].value);
+   }
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..e4cc593
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,25 @@
+# Project-wide Gradle settings.
+
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+# Default value: -Xmx10248m -XX:MaxPermSize=256m
+# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
+org.gradle.jvmargs=-Xmx2048m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
+
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
+
+android.useAndroidX=true
+android.enableJetifier=false
+
+# FIXME: This optimizes away some classes it should not - see #3853
+android.enableR8.fullMode=false
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..a4b76b9
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..df97d72
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,7 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
+networkTimeout=10000
+validateDistributionUrl=true
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
new file mode 100644
index 0000000..f5feea6
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,252 @@
+#!/bin/sh
+
+#
+# Copyright 漏 2015-2021 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+##############################################################################
+#
+#   Gradle start up script for POSIX generated by Gradle.
+#
+#   Important for running:
+#
+#   (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+#       noncompliant, but you have some other compliant shell such as ksh or
+#       bash, then to run this script, type that shell name before the whole
+#       command line, like:
+#
+#           ksh Gradle
+#
+#       Busybox and similar reduced shells will NOT work, because this script
+#       requires all of these POSIX shell features:
+#         * functions;
+#         * expansions 芦$var禄, 芦${var}禄, 芦${var:-default}禄, 芦${var+SET}禄,
+#           芦${var#prefix}禄, 芦${var%suffix}禄, and 芦$( cmd )禄;
+#         * compound commands having a testable exit status, especially 芦case禄;
+#         * various built-in commands including 芦command禄, 芦set禄, and 芦ulimit禄.
+#
+#   Important for patching:
+#
+#   (2) This script targets any POSIX shell, so it avoids extensions provided
+#       by Bash, Ksh, etc; in particular arrays are avoided.
+#
+#       The "traditional" practice of packing multiple parameters into a
+#       space-separated string is a well documented source of bugs and security
+#       problems, so this is (mostly) avoided, by progressively accumulating
+#       options in "$@", and eventually passing that to Java.
+#
+#       Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+#       and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+#       see the in-line comments for details.
+#
+#       There are tweaks for specific operating systems such as AIX, CygWin,
+#       Darwin, MinGW, and NonStop.
+#
+#   (3) This script is generated from the Groovy template
+#       https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+#       within the Gradle project.
+#
+#       You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+    APP_HOME=${app_path%"${app_path##*/}"}  # leaves a trailing /; empty if no leading path
+    [ -h "$app_path" ]
+do
+    ls=$( ls -ld "$app_path" )
+    link=${ls#*' -> '}
+    case $link in             #(
+      /*)   app_path=$link ;; #(
+      *)    app_path=$APP_HOME$link ;;
+    esac
+done
+
+# This is normally unused
+# shellcheck disable=SC2034
+APP_BASE_NAME=${0##*/}
+# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
+APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
+' "$PWD" ) || exit
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+    echo "$*"
+} >&2
+
+die () {
+    echo
+    echo "$*"
+    echo
+    exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in                #(
+  CYGWIN* )         cygwin=true  ;; #(
+  Darwin* )         darwin=true  ;; #(
+  MSYS* | MINGW* )  msys=true    ;; #(
+  NONSTOP* )        nonstop=true ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD=$JAVA_HOME/jre/sh/java
+    else
+        JAVACMD=$JAVA_HOME/bin/java
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD=java
+    if ! command -v java >/dev/null 2>&1
+    then
+        die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+    case $MAX_FD in #(
+      max*)
+        # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+        # shellcheck disable=SC2039,SC3045
+        MAX_FD=$( ulimit -H -n ) ||
+            warn "Could not query maximum file descriptor limit"
+    esac
+    case $MAX_FD in  #(
+      '' | soft) :;; #(
+      *)
+        # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+        # shellcheck disable=SC2039,SC3045
+        ulimit -n "$MAX_FD" ||
+            warn "Could not set maximum file descriptor limit to $MAX_FD"
+    esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+#   * args from the command line
+#   * the main class name
+#   * -classpath
+#   * -D...appname settings
+#   * --module-path (only if needed)
+#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+    APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+    CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
+
+    JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    for arg do
+        if
+            case $arg in                                #(
+              -*)   false ;;                            # don't mess with options #(
+              /?*)  t=${arg#/} t=/${t%%/*}              # looks like a POSIX filepath
+                    [ -e "$t" ] ;;                      #(
+              *)    false ;;
+            esac
+        then
+            arg=$( cygpath --path --ignore --mixed "$arg" )
+        fi
+        # Roll the args list around exactly as many times as the number of
+        # args, so each arg winds up back in the position where it started, but
+        # possibly modified.
+        #
+        # NB: a `for` loop captures its iteration list before it begins, so
+        # changing the positional parameters here affects neither the number of
+        # iterations, nor the values presented in `arg`.
+        shift                   # remove old arg
+        set -- "$@" "$arg"      # push replacement arg
+    done
+fi
+
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Collect all arguments for the java command:
+#   * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
+#     and any embedded shellness will be escaped.
+#   * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
+#     treated as '${Hostname}' itself on the command line.
+
+set -- \
+        "-Dorg.gradle.appname=$APP_BASE_NAME" \
+        -classpath "$CLASSPATH" \
+        org.gradle.wrapper.GradleWrapperMain \
+        "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+    die "xargs is not available"
+fi
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+#   readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+#   set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+        printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+        xargs -n1 |
+        sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+        tr '\n' ' '
+    )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..9d21a21
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,94 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem      https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+@rem SPDX-License-Identifier: Apache-2.0
+@rem
+
+@if "%DEBUG%"=="" @echo off
+@rem ##########################################################################
+@rem
+@rem  Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%"=="" set DIRNAME=.
+@rem This is normally unused
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if %ERRORLEVEL% equ 0 goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if %ERRORLEVEL% equ 0 goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/renovate.json b/renovate.json
new file mode 100644
index 0000000..d3fbba9
--- /dev/null
+++ b/renovate.json
@@ -0,0 +1,46 @@
+{
+  "$schema": "https://docs.renovatebot.com/renovate-schema.json",
+  "extends": [
+    "config:recommended"
+  ],
+  "labels": [
+    "dependency"
+  ],
+  "ignoreDeps": [
+    "com.github.Freeyourgadget:greendao"
+  ],
+  "packageRules": [
+    {
+      "groupName": "protobuf",
+      "matchPackagePrefixes": [
+        "com.google.protobuf"
+      ]
+    },
+    {
+      "groupName": "androidx.camera",
+      "matchPackagePrefixes": [
+        "androidx.camera"
+      ]
+    },
+    {
+      "groupName": "android-emojify",
+      "matchPackagePrefixes": [
+        "com.github.wax911.android-emojify"
+      ]
+    },
+    {
+      "description": "solarpositioning v2 needs java 17, we need to stay on v0",
+      "matchPackagePrefixes": [
+        "net.e175.klaus:solarpositioning"
+      ],
+      "allowedVersions": "<2.0"
+    },
+    {
+      "description": "We're running a fork of the greendao v2 generator, so we must always use the corresponding core lib",
+      "matchPackagePrefixes": [
+        "org.greenrobot:greendao"
+      ],
+      "allowedVersions": "<3.0"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..6579a81
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1 @@
+include ':app', ':GBDaoGenerator'
diff --git a/src/banglejs/fastlane/metadata/android/cz-CZ/full_description.txt b/src/banglejs/fastlane/metadata/android/cz-CZ/full_description.txt
new file mode 100644
index 0000000..390a3b6
--- /dev/null
+++ b/src/banglejs/fastlane/metadata/android/cz-CZ/full_description.txt
@@ -0,0 +1,6 @@
+Nainstalujte si tuto aplikaci pro p艡铆jem notifikac铆, zpr谩v a ozn谩men铆 o hovorech z telefonu Android na chytr媒ch hodink谩ch Bangle.js. 
+
+* P艡ij铆mejte notifikace, textov茅 zpr谩vy a ozn谩men铆 o hovorech na Bangle.js 
+* Zvolte, chcete-li p艡ijmout nebo odm铆tnout hovor, 膷i dokonce odpov臎d臎t na p艡ijat茅 textov茅 zpr谩vy
+* Bangle.js aplikace mohou p艡es telefon p艡istupovat k internetu (ve v媒choz铆m nastaven铆 zak谩z谩no) 
+* Bangle.js aplikace mohou odes铆lat Android Intent zpr谩vy a mohou b媒t 艡铆zeny zpr谩vami (Intents) odeslan媒mi aplikacemi, jako je nap艡铆klad Tasker (ve v媒choz铆m nastaven铆 zak谩z谩no)
diff --git a/src/banglejs/fastlane/metadata/android/cz-CZ/short_description.txt b/src/banglejs/fastlane/metadata/android/cz-CZ/short_description.txt
new file mode 100644
index 0000000..72dc79d
--- /dev/null
+++ b/src/banglejs/fastlane/metadata/android/cz-CZ/short_description.txt
@@ -0,0 +1 @@
+Doprovodn谩 Android aplikace pro Bangle.js postaven谩 na projektu Gadgetbridge.
diff --git a/src/banglejs/fastlane/metadata/android/en-US/changelogs b/src/banglejs/fastlane/metadata/android/en-US/changelogs
new file mode 100644
index 0000000..1ed4038
--- /dev/null
+++ b/src/banglejs/fastlane/metadata/android/en-US/changelogs
@@ -0,0 +1 @@
+../../../../../mainline/fastlane/metadata/android/en-US/changelogs
\ No newline at end of file
diff --git a/src/banglejs/fastlane/metadata/android/en-US/full_description.txt b/src/banglejs/fastlane/metadata/android/en-US/full_description.txt
new file mode 100644
index 0000000..6530ecd
--- /dev/null
+++ b/src/banglejs/fastlane/metadata/android/en-US/full_description.txt
@@ -0,0 +1,6 @@
+Install this app to receive notifications, messages and call notifications from your Android phone on your Bangle.js smart watch.
+
+* Receive notifications, text messages, and call notifications on Bangle.js
+* Choose to accept/decline calls, or even answer received text messages
+* Bangle.js apps can access the internet via your phone (disabled by default)
+* Bangle.js apps can send Android Intents and can be controlled by Intents sent by apps like Tasker (disabled by default)
diff --git a/src/banglejs/fastlane/metadata/android/en-US/images/icon.png b/src/banglejs/fastlane/metadata/android/en-US/images/icon.png
new file mode 100644
index 0000000..83f372d
--- /dev/null
+++ b/src/banglejs/fastlane/metadata/android/en-US/images/icon.png
Binary files differ
diff --git a/src/banglejs/fastlane/metadata/android/en-US/images/phoneScreenshots/10-MainScreen.png b/src/banglejs/fastlane/metadata/android/en-US/images/phoneScreenshots/10-MainScreen.png
new file mode 100644
index 0000000..3914e76
--- /dev/null
+++ b/src/banglejs/fastlane/metadata/android/en-US/images/phoneScreenshots/10-MainScreen.png
Binary files differ
diff --git a/src/banglejs/fastlane/metadata/android/en-US/images/phoneScreenshots/B1-AppManager.png b/src/banglejs/fastlane/metadata/android/en-US/images/phoneScreenshots/B1-AppManager.png
new file mode 100644
index 0000000..39fdc5d
--- /dev/null
+++ b/src/banglejs/fastlane/metadata/android/en-US/images/phoneScreenshots/B1-AppManager.png
Binary files differ
diff --git a/src/banglejs/fastlane/metadata/android/en-US/short_description.txt b/src/banglejs/fastlane/metadata/android/en-US/short_description.txt
new file mode 100644
index 0000000..ba98fc6
--- /dev/null
+++ b/src/banglejs/fastlane/metadata/android/en-US/short_description.txt
@@ -0,0 +1 @@
+Android companion app for Bangle.js built on top of the Gadgetbridge project.
diff --git a/src/banglejs/fastlane/metadata/android/en-US/title.txt b/src/banglejs/fastlane/metadata/android/en-US/title.txt
new file mode 100644
index 0000000..394cc9d
--- /dev/null
+++ b/src/banglejs/fastlane/metadata/android/en-US/title.txt
@@ -0,0 +1 @@
+Bangle.js Gadgetbridge
diff --git a/src/banglejs/fastlane/metadata/android/pt-PT/full_description.txt b/src/banglejs/fastlane/metadata/android/pt-PT/full_description.txt
new file mode 100644
index 0000000..5987eab
--- /dev/null
+++ b/src/banglejs/fastlane/metadata/android/pt-PT/full_description.txt
@@ -0,0 +1,6 @@
+Instala esta aplica莽茫o para receber notifica莽玫es, mensagens e notifica莽玫es de chamadas do teu telem贸vel no teu smartwatch Bangle.js.
+
+* Recebe notifica莽玫es, mensagens de texto, e notifica莽玫es de chamadas no Bangle.js
+* Escolhe aceitar/rejeitar chamadas, ou mesmo responder a mensagens de texto
+* Aplica莽玫es Bangle.js podem aceder 脿 internet atrav茅s do teu telem贸vel (desativado por predefini莽茫o)
+* Aplica莽玫es Bangle.js podem enviar Intents Android, e ser controladas por Intents enviados por aplica莽玫es como Tasker (desativado por predefini莽茫o)
diff --git a/src/banglejs/fastlane/metadata/android/pt-PT/short_description.txt b/src/banglejs/fastlane/metadata/android/pt-PT/short_description.txt
new file mode 100644
index 0000000..651b1f0
--- /dev/null
+++ b/src/banglejs/fastlane/metadata/android/pt-PT/short_description.txt
@@ -0,0 +1 @@
+Aplica莽茫o Android para Bangle.js, construida com base no projecto Gadgetbridge.
diff --git a/src/mainline/fastlane/metadata/android/cs-CZ/full_description.txt b/src/mainline/fastlane/metadata/android/cs-CZ/full_description.txt
new file mode 100644
index 0000000..9503093
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/cs-CZ/full_description.txt
@@ -0,0 +1,10 @@
+Pou啪铆vejte sv茅 chytr茅 hodinky, fitness tracker a dal拧铆 Bluetooth za艡铆zen铆 bez p暖vodn铆 aplikace s uzav艡en媒m zdrojov媒m k贸dem, bez nutnosti vytv谩艡et si 煤膷et a p艡en谩拧et jak谩koli data na servery dodavatele.
+脷pln媒 seznam podporovan媒ch za艡铆zen铆 naleznete na na拧铆 domovsk茅 str谩nce nebo README.md v na拧em git 煤lo啪i拧ti. 
+
+Podporovan茅 funkce (v z谩vislosti na za艡铆zen铆):
+
+- notifikace a啪 na z谩p臎st铆
+- sb臎r dat ze senzor暖 za艡铆zen铆
+- ovl谩d谩n铆 p艡ehr谩v谩n铆 hudby na va拧em android za艡铆zen铆
+- p艡edpov臎膹 po膷as铆 
+- informace o sm臎ru trasy cesty
diff --git a/src/mainline/fastlane/metadata/android/cs-CZ/short_description.txt b/src/mainline/fastlane/metadata/android/cs-CZ/short_description.txt
new file mode 100644
index 0000000..6161572
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/cs-CZ/short_description.txt
@@ -0,0 +1 @@
+Pou啪铆vejte chytr茅 hodinky a dal拧铆 Bluetooth za艡铆zen铆, data z暖st谩vaj铆 u v谩s.
diff --git a/src/mainline/fastlane/metadata/android/de-DE/full_description.txt b/src/mainline/fastlane/metadata/android/de-DE/full_description.txt
new file mode 100644
index 0000000..e56039e
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/de-DE/full_description.txt
@@ -0,0 +1,8 @@
+Nutze deine Pebble/Mi Band/Amazfit Bip/Hplus, ohne die propriet盲re App des Herstellers, ohne ein Benutzerkonto zu erstellen, und ohne irgendwelche Daten an die Server des Herstellers zu senden.
+
+Du kannst Benachrichtigungen an deinem Handgelenk erhalten, sowie (je nach Ger盲t):
+- Daten der Ger盲tesensoren sammeln
+- Die Musikwiedergabe auf deinem Android Ger盲t steuern
+- Wettervorhersagen angezeigt bekommen
+- Navigiert werden
+
diff --git a/src/mainline/fastlane/metadata/android/de-DE/images/featureGraphic.jpg b/src/mainline/fastlane/metadata/android/de-DE/images/featureGraphic.jpg
new file mode 100644
index 0000000..81fe492
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/de-DE/images/featureGraphic.jpg
Binary files differ
diff --git a/src/mainline/fastlane/metadata/android/de-DE/images/icon.png b/src/mainline/fastlane/metadata/android/de-DE/images/icon.png
new file mode 100644
index 0000000..1c8dcb3
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/de-DE/images/icon.png
Binary files differ
diff --git a/src/mainline/fastlane/metadata/android/de-DE/short_description.txt b/src/mainline/fastlane/metadata/android/de-DE/short_description.txt
new file mode 100644
index 0000000..ce313c9
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/de-DE/short_description.txt
@@ -0,0 +1 @@
+Nutze deine Smartwatch (und andere BT Ger盲te) und behalte deine Daten f眉r dich!
diff --git a/src/mainline/fastlane/metadata/android/de-DE/title.txt b/src/mainline/fastlane/metadata/android/de-DE/title.txt
new file mode 100644
index 0000000..e7ee542
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/de-DE/title.txt
@@ -0,0 +1 @@
+Gadgetbridge
\ No newline at end of file
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/100.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/100.txt
new file mode 100644
index 0000000..a07b7d7
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/100.txt
@@ -0,0 +1,6 @@
+* Amazfit Bip: Various fixes regarding weather, add condition string support for FW 0.0.8.74
+* Amazfit Bip: enable caller display in later firmwares
+* Amazfit Bip: initial firmware update support (EXPERIMENTAL, AT YOUR OWN RISK)
+* Re-enable improved speed zones tab
+* Probably fix crash with certain music players
+* Improve theme and add changelog icon
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/101.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/101.txt
new file mode 100644
index 0000000..e645d44
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/101.txt
@@ -0,0 +1,12 @@
+* Initial NO.1 F1 support
+* Initial Teclast H30 support
+* Amazfit Bip: Display GPS firmware version
+* Amazfit Bip: Fix E-Mail notifications
+* Amazfit Bip: Fix call notification with unknown caller
+* Amazfit Bip: Fix crash when weather is updated and device reconnecting
+* Mi2/Bip: Fix crash when synchronizing calendar to alarms
+* Pebble: Fix crash when taking screenshots on Android 8.0 (Oreo)
+* Pebble: Support some google app icons
+* Pebble: try to support spotify
+* Mi Band 2: Support configurable button actions
+* Fix language being reset to system default
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/102.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/102.txt
new file mode 100644
index 0000000..64f4567
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/102.txt
@@ -0,0 +1,7 @@
+* Initial support for EXRIZU K8 (HPlus variant)
+* Amazfit Bip: fix long messages not being displayed at all
+* Mi Band 2: Support multiple button actions
+* NO.1 F1: Fetch sleep data
+* NO.1 F1: Heart rate support
+* Pebble: Support controlling the current active media playback application
+* Fix suspended activities coming to front when rotating the screen
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/103.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/103.txt
new file mode 100644
index 0000000..e0c126a
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/103.txt
@@ -0,0 +1,6 @@
+* Amazfit Bip: Support flashing CEP and ALM files for A-GPS
+* Amazfit Bip: Initial experimental support for fetching logs from the watch
+* Mi2/Bip: Send user info to the device (fixes calories and distance display)
+* Mi2/Bip: Fix firmware update progressbar being stuck at the end
+* Pebble/Bip: Support more notification icons
+* Pebble: Automatically determine color for unknown notifications on Pebble Time
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/104.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/104.txt
new file mode 100644
index 0000000..081dc69
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/104.txt
@@ -0,0 +1 @@
+* Amazfit Bip: Auto-switch language on connect (English, Simplified Chinese, Traditional Chinese), requires FW 0.0.9.14+
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/105.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/105.txt
new file mode 100644
index 0000000..fb358ec
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/105.txt
@@ -0,0 +1,5 @@
+* Mi2/Bip: Fix sleep detection for newer firmwares
+* Mi2/Bip: Fix ancient bug resulting in wrong activity data at the beginning in diagrams and aggregate data
+* No.1 F1: Support setting time format and distance units (metric/imperial)
+* Pebble: Support setting distance units to miles for Health (need to reactivate Health in App Manager after toggling)
+* HPlus: Make changing distance unit system effective immediately on toggling
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/106.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/106.txt
new file mode 100644
index 0000000..3789f96
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/106.txt
@@ -0,0 +1 @@
+* Mi2/Bip: Support setting distance units (metric/imperial)
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/107.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/107.txt
new file mode 100644
index 0000000..1d23aea
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/107.txt
@@ -0,0 +1,4 @@
+* Amazfit Bip: Fix non-working notifications from Outlook, Yahoo Mail and GMail
+* HPlus: Fix Unicode encoding
+* No.1 F1: Alarms support
+* No.1 F1: Show data fetching progress
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/108.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/108.txt
new file mode 100644
index 0000000..5861bbc
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/108.txt
@@ -0,0 +1,6 @@
+* Pebble: Experimental support for background javascript, allows weather and other features for watchapps without special Gadgetbridge support
+* Add experimental support for Amazfit Cor and Mi Band HRX (no firmware update on the latter)
+* Mi Band 2: Support more icons and textual notifications for more apps
+* Add some quick action buttons to Gadgetbridge's notification
+* Add transliteration support for ukrainian cyrillic characters
+* Fix annoying toast in Mi Band settings
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/109.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/109.txt
new file mode 100644
index 0000000..d3ea035
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/109.txt
@@ -0,0 +1 @@
+* Mi Band 2: Fix being detected as Amazfit Bip which lead to various problems especially on newly paired devices
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/110.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/110.txt
new file mode 100644
index 0000000..27e6efa
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/110.txt
@@ -0,0 +1,5 @@
+* Charts: Add setting to disable swiping charts left/right and some UI changes
+* Pebble: Use the configured unit system also for system weather app
+* Mi Band 2: Fix HR being absent in charts
+* Amazfit Bip: Allow manual language selection in settings
+* Amazfit Cor: Fix firmware update 
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/111.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/111.txt
new file mode 100644
index 0000000..a24ee1b
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/111.txt
@@ -0,0 +1,3 @@
+* Amazfit Bip: Allow flashing watchfaces
+* Amazfit Cor: Fix flashing new .res files
+* Mi Band 2/HRX/Bip/Cor: Try to fix stuck activity sync
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/112.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/112.txt
new file mode 100644
index 0000000..b8001fe
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/112.txt
@@ -0,0 +1,4 @@
+* Mi Band 2/Bip/Cor: Whole day HR support
+* Mi Band 2/Bip/Cor: Prevent writing a lot of HR samples to the database when not using the live activity feature
+* Pebble: Fix some nasty crashes which occur since 0.22.0
+* Workaround for non-working notifications from wechat and outlook
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/113.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/113.txt
new file mode 100644
index 0000000..2461690
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/113.txt
@@ -0,0 +1,4 @@
+* Unlock Teclast H10 support using the same code as H30
+* Amazfit Bip: Fix installation of 0.1.0.11 Firmware
+* Amazfit Bip/Cor: Send three days of weather forecast including (untranslated) conditions
+* Workaround for a crash on Android 4.4 when connecting
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/114.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/114.txt
new file mode 100644
index 0000000..44cec3c
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/114.txt
@@ -0,0 +1,3 @@
+* Initial support for LineageOS/CyanogenMod weather provider
+* Amazfit Bip/Cor: Support for current weather temperature
+* Amazfit Bip/Cor: Display firmware version and type also for non-whitelisted firmware files
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/115.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/115.txt
new file mode 100644
index 0000000..fba3fc3
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/115.txt
@@ -0,0 +1,3 @@
+* Initial support for Omnijaws weather service
+* Amazfit Bip: Allow installation of latest gps firmware
+* Amazfit Cor: Fixes for installing newer firmware versions
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/116.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/116.txt
new file mode 100644
index 0000000..bb04462
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/116.txt
@@ -0,0 +1,2 @@
+* Mi Band 1S: Fix sync problem with firmware 4.16.11.15 (probably also Mi Band 1.0.15.0 and Mi Band 1A 5.16.11.15)
+* Amazfit Cor: Fix problem with firmware >=1.0.6.27 being detected as Mi Band 2
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/117.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/117.txt
new file mode 100644
index 0000000..86242b1
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/117.txt
@@ -0,0 +1,9 @@
+* Fix logs sometimes not containing stacktraces
+* Support periodic database export
+* Support transliteration for Arabic and Farsi
+* Try to make alarm details scrollable (for small devices)
+* Amazfit Bip: Implement find phone feature
+* Amazfit Bip: Support flashing latest GPS firmware
+* Amazfit Cor: Support flashing latest firmware
+* Pebble: Fix crash with experimental background javascript
+* Charts: Several fixes to the MPAndroidChart library
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/118.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/118.txt
new file mode 100644
index 0000000..b708a34
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/118.txt
@@ -0,0 +1,2 @@
+* Amazfit Bip: prevent menu icons from vanishing when using firmware 0.1.0.51
+* Pebble: "find phone" feature for upcoming pebble helper app
\ No newline at end of file
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/119.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/119.txt
new file mode 100644
index 0000000..c85172f
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/119.txt
@@ -0,0 +1,4 @@
+* Fix crash when changing the periodic database export interval
+* Amazfit Bip: Allow fonts and new res format to be flashed
+* Amazfit Cor: Allow new res format to be flashed
+* Pebble: Background js fixes
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/120.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/120.txt
new file mode 100644
index 0000000..16293e6
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/120.txt
@@ -0,0 +1,5 @@
+* Charts: Try to fix another crash
+* Pebble: Fix weather for some watchfaces when using background JS
+* Amazfit Cor: Allow watchfaces to be flashed (untested)
+* Amazfit Bip: Better detection for flashable font types
+* Fix number only privacy option
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/121.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/121.txt
new file mode 100644
index 0000000..0d3d38c
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/121.txt
@@ -0,0 +1 @@
+* Amazfit Bip: Fix language setting on new firmwares
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/122.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/122.txt
new file mode 100644
index 0000000..1c830d2
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/122.txt
@@ -0,0 +1,6 @@
+* Fix crash in settings activity with export location
+* Fix notification deletion regression
+* Add '艁' and '艂' to transliteration map
+* Omnijaws Weather: correctly pick today's' min and max temperature
+* Fix alarm details activity on small screen
+* Pebble: mimic online check of TrekVolle when using background js
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/123.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/123.txt
new file mode 100644
index 0000000..def374d
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/123.txt
@@ -0,0 +1,3 @@
+* Display the chat icon for notifications coming from Kontalk and Antox
+* Pebble: Fix for background js which try to send floats (e.g. TrekVolle)
+* Mi Band 2: Change the way vibration patterns work, also fixes problems with missing text on newer firmwares
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/124.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/124.txt
new file mode 100644
index 0000000..d0256d9
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/124.txt
@@ -0,0 +1,6 @@
+* Initial support for Xwatch
+* Move the connected device to top in control center
+* Add adaptive launcher icon for Android 8.x
+* No longer plot heart rate graph when device was detected as not worn
+* Pebble: Small fixes for background js (e.g. Pebble-Casio-WV58DE)
+* Pebble: native (non bg js) support for weather in Simply Light watchface
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/125.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/125.txt
new file mode 100644
index 0000000..a5a1059
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/125.txt
@@ -0,0 +1,4 @@
+* Amazfit Cor: Try to send weather location instead of AQI
+* Amazfit Bip: Support setting start end end time for background light when lifting the arm
+* Pebble: various fixes and improvemnets for background javascript
+* Explicity ask for RECEIVE_SMS permission to fix problems with Android 8
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/126.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/126.txt
new file mode 100644
index 0000000..a8c3986
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/126.txt
@@ -0,0 +1,2 @@
+* Amazfit Bip: Initial support for GPS tracks
+* Pebble: Wind speed/direction support and bugfixes for weather when using background javascript
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/127.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/127.txt
new file mode 100644
index 0000000..dd694e5
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/127.txt
@@ -0,0 +1 @@
+* Fix crashes and connection problems on Android 6 and lower
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/128.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/128.txt
new file mode 100644
index 0000000..2e91666
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/128.txt
@@ -0,0 +1 @@
+* Amazfit Bip: Time and timezone fixes for Android <=6 when exporting GPX
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/129.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/129.txt
new file mode 100644
index 0000000..00b3bc2
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/129.txt
@@ -0,0 +1,4 @@
+* Amazfit Bip: Add proper mime type to shared gpx files
+* Amazfit Bip: allow to set displayed menu items
+* Amazfit Bip: fix fetching logs from device via debug menu
+* Amazfit Bip: Raise .res limit to 700000 bytes for modded files
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/130.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/130.txt
new file mode 100644
index 0000000..b58bfdb
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/130.txt
@@ -0,0 +1,4 @@
+* Fix a bug with Toasts appearing every time a notification arrives when bluetooth is disabled
+* Pebble 2: Add optional GATT client only mode that might help with connection stability
+* Amazfit Cor: Fix detection of newer firmwares
+* Mi Band 2: Fix text notifcations not appearing with short vibration patterns
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/131.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/131.txt
new file mode 100644
index 0000000..8e065aa
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/131.txt
@@ -0,0 +1,5 @@
+* Fix autoreconnect at boot on recent Android versions
+* Bluetooth connection is more stable on Oreo
+* Potentially fix the watch continuously vibrating after call pickup
+* Amazfit Bip: Add setting to configure shortcuts (swipe to right from watchface)
+* Recognize Q8 as a HPlus device
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/132.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/132.txt
new file mode 100644
index 0000000..d25fb95
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/132.txt
@@ -0,0 +1,11 @@
+* Initial support for Mi Band 3 (largely untested, needs to be connected to Mi Fit once)
+* Option for automatic activity sync after screen unlock
+* Allow hiding activity transfer notification on Android Oreo and above
+* Allow blacklisting of pebblekit notifications for individual apps
+* Allow blacklisting all application at once
+* Forward Skype notifications to wearable even if "local only" flag is set
+* Show Gadgetbridge logo behind cards in main activity
+* Always stop BT/BTLE discovery when exiting the discovery activity
+* Amazfit Bip/Cor: Fix scheduled setting for "display on lift wrist" preference
+* Amazfit Bip/Cor: add recent firmwares to whitelist
+* Pebble: Fix a rare crash in webview
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/133.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/133.txt
new file mode 100644
index 0000000..be06947
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/133.txt
@@ -0,0 +1,5 @@
+* Pebble: Change appstore search to point to RomanPort's pebble appstore
+* Mi Band 3: Allow flashing fonts (untested)
+* Amazfit Bip: Allow flashing latest firmwares
+* Amazfit Cor: Allow flashing Bip fonts (untested)
+* Allow to limit auto fetch to a user configurable time interval
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/134.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/134.txt
new file mode 100644
index 0000000..8879424
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/134.txt
@@ -0,0 +1,7 @@
+* Initial support for ZeTime
+* Amazfit Bip/Cor: Rework firmware detection to cope with new version scheme
+* Amazfit Bip: Support setting language to Russian
+* Amazfit Cor: Support language switching on newer firmwares
+* Mi Band 3: support setting language (english and spanish tested)
+* Mi Band 3: Fix pairing
+* Mi Band 3: Send AQI to enable display of current temperature
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/135.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/135.txt
new file mode 100644
index 0000000..f71dfa7
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/135.txt
@@ -0,0 +1,5 @@
+* Fix wrong weather icon mapping in rare cases
+* Fix device discovery on Android 4.4
+* Amazfit Bip: Use UTC in gpx tracks for better compatibility with external software
+* Amazfit Bip: Add the (localized) activity type to the gpx filename
+* Amazfit Bip: Fix weather on latest firmwares
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/136.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/136.txt
new file mode 100644
index 0000000..543db23
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/136.txt
@@ -0,0 +1,17 @@
+* New Device: Initial support for ID115
+* New Device: Initial support for Lenovo Watch9
+* Show splash screen during startup
+* Vertically align device icon in main activity
+* Try to support the google clock application (untested)
+* Amazfit Cor: Allow to configure displayed menu items
+* Amazfit Cor: Support basic music control
+* Amazfit Cor: Fix flashing font files
+* Amazfit Bip: improved GPX export
+* Amazfit Bip: Fix exported GPX file names for *FAT storage
+* Amazfit Bip: Fix current weather not being displayed with later firmwares
+* Amazfit Bip/Cor: Try to fix device being sometimes stuck in connecting state
+* Mi Band 2: Put some device specific settings into its own settings category
+* Mi Band 3: Support disabling of on-device menu items
+* Mi Band 3: Support locking the Mi Band sceen (swipe up to unlock)
+* Mi Band 2/3: New icon
+* NO1 F1: Set time during initialization
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/137.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/137.txt
new file mode 100644
index 0000000..97ebb4c
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/137.txt
@@ -0,0 +1,10 @@
+* Mi Band 3: Support setting language to to German, Italian, French, Polish, Japanese, Korean (read wiki)
+* Mi Band 3: Support flashing latest RES files
+* Mi Band 3: Fix notification text not being displayed
+* Mi Band 3/Cor/Bip: Display app name when no app specific icon is available
+* Teclast: add/improve H1 and H3 watch recognition
+* Support transliteration for Lithuanian and Bengali
+* Fix BLE reconnect issues in certain conditions
+* Various fixes for display issues on small screens
+* Fix some potential NPEs
+* WIP: Display start and end of sleep in statistics
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/138.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/138.txt
new file mode 100644
index 0000000..12e961d
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/138.txt
@@ -0,0 +1,16 @@
+* Amazfit Bip + Mi Band 3: Support for right to left display (configurable) (#976)
+* Add Arabic, Bengali Farsi, Persian, Scandinavian transliteration
+* Add support for some Roidmi FM receivers
+* Mi Band 3: Allow enabling the "Workout" menu item
+* Mi Band 3: Support for night mode configuration
+* Huami devices: fix seldom activity/sports synchronization problem (#1264)
+* Preferences: Make minimum heart rate configurable (lower values will be disregarded) 
+* Preferences: Configure minimum time between notifications
+* Preferences: Group language settings
+* Attempt to fix BLE connection issues on Samsung S devices
+* Week sleep and steps charts: display balance (actual value vs. desired value) 
+* Live Activity: show current/maximum heart rate, display minute steps and total steps and more improvements
+* Live Activity: fix discrepancy between number of steps in Gadgetbridge and wearable device
+* Fix missing caller ID for incoming calls on Android 9
+* Support for easy sharing of log files via the Debug screen
+* Misc small bugfixes
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/139.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/139.txt
new file mode 100644
index 0000000..b32bc15
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/139.txt
@@ -0,0 +1,10 @@
+* Pebble: Send all wearable notification actions (not only reply)
+* Pebble: Always allow reply action even if untested features are turned off
+* Pebble: Temporarily disable broken autoremove notification feature
+* Amazfit Bip: Allow flashing latest gps firmware (Mili_dth.gps)
+* Mi Band 3/Amazfit Bip/Amazfit Cor: Send Fahrenheit if units are set to imperial
+* Roidmi 3: Fix and enable support
+* Mi Band 3/Amazfit Bip: fix find phone crash
+* Prevent re-sending old notifications to the wearable
+* Enhancement and Fixes for Bengali Transliteration
+* Disable excessive logging in RTL support
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/140.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/140.txt
new file mode 100644
index 0000000..2fa594d
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/140.txt
@@ -0,0 +1,4 @@
+* Pebble: Fix crash when no canned replies have been set
+* Pebble: Let the firmware show localized default canned replies if none have been set
+* Amazfit Bip: Fix importing GPS tracks that have been recorded with Firmware 1.1.5.02
+* Display measured hr value in debug screen
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/141.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/141.txt
new file mode 100644
index 0000000..57ff681
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/141.txt
@@ -0,0 +1,3 @@
+* Pebble: Fix a regression that caused non-working mute, open and dismiss actions
+* Fix setting language to Czech manually
+* Ignore summary notification from K-9 Mail (caused notification spamming)
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/142.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/142.txt
new file mode 100644
index 0000000..2ed6bf0
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/142.txt
@@ -0,0 +1 @@
+* Pebble: Fix crash with DISMISS and OPEN actions
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/143.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/143.txt
new file mode 100644
index 0000000..82fb5db
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/143.txt
@@ -0,0 +1,10 @@
+* Initial support for Casio GB-6900B
+* Increase number of alarms and store them per-device 
+* Support factory reset in debug activity (Mi Band 1/2/3, Bip, Cor)
+* Filter out unicode control sequences (fixes problems with Telegram and probably others)
+* Fix endless loop resulting in OOM when RTL support is enabled
+* Recoginize p鈮 as an email app
+* No longer display Android paired devices in that were not a paired with Gadgetbridge
+* Amazfit Bip: Allow flashing latest GPS firmware
+* Pebble: Native support for M7S watch face
+* No1 F1: Support for a Chinese clone
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/144.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/144.txt
new file mode 100644
index 0000000..65cf0e9
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/144.txt
@@ -0,0 +1 @@
+* Fix db deadlock on alarm migration
\ No newline at end of file
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/145.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/145.txt
new file mode 100644
index 0000000..3da2976
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/145.txt
@@ -0,0 +1,5 @@
+* Fix setting alarms under some circumstances
+* Support calls notifications for some VoIP apps
+* Mi Band 3: Enable fetching sports activities (currently only useful for flushing activities)
+* Casio: Improve stability
+* Casio: Add explicit support for GB-6900B, GB-X6900B and GB-5600B
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/147.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/147.txt
new file mode 100644
index 0000000..bd50807
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/147.txt
@@ -0,0 +1,6 @@
+* Make voip call support optional (disabled by default)
+* Amazfit Bip: GPX export corrections
+* ZeTime: Fix setting alarms
+* ZeTime: Fix wrong activity timestamps
+* ZeTime: Set HR alarm limits when changed, not only on connect
+* ZeTime: Sync preferences from the watch to Gadgetbridge settings
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/148.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/148.txt
new file mode 100644
index 0000000..f645390
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/148.txt
@@ -0,0 +1,10 @@
+* BFH-16: Initial support
+* Mi Band 2/3/Bip/Cor: Generate random per-device security keys when pairing, allow manual override to still support multiple android devices connecting to the same device
+* Mi Band 3: Add Indonesian, Thai, Arabic, Vietnamese, Portuguese, Dutch, Turkish and Ukrainian to language settings
+* Mi Band 3: Support flashing latest Japanese-Korean font
+* Amazfit Cor 2: Initial experimental support (untested)
+* Pebble: Add pebblekit extension for reopening last app
+* Casio: Bugfixes and improvements
+* Lookup contacts also in work profile
+* Fix searching in application name when blacklisting
+* Remove misleading title from database management activity when no legacy database is available
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/149.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/149.txt
new file mode 100644
index 0000000..c98a000
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/149.txt
@@ -0,0 +1,2 @@
+* Mi Band 3: Recognize "Xiaomi Band 3"
+* Amazfit Bip: Add German, Italian, French and Turkish to language settings
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/150.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/150.txt
new file mode 100644
index 0000000..8ff035b
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/150.txt
@@ -0,0 +1,5 @@
+* Mi Band 1/2/3/Bip/Cor: Migrate many settings to per-device settings (new settings icon in device card in main activity)
+* Mi Band 3: Fix setting menu items with 2.4 firmware and add support for the new timer menu
+* Amazfit Bip/Cor, Casio: Add support for muting incoming calls
+* ZeTime: Remove endless recursion in ZeTime settings
+* Recognize FairEmail notifications as generic email notifications
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/151.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/151.txt
new file mode 100644
index 0000000..54730f2
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/151.txt
@@ -0,0 +1,4 @@
+* Mi Band 1: Fix crash when entering per-device settings
+* Mi Band 3: Allow setting date format in per-device settings
+* ZeTime: Fix timestmaps
+* Fix a crash when flashing an non-whitelisted firmware while using Gadgetbridge in Spanish
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/152.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/152.txt
new file mode 100644
index 0000000..6fc4ccb
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/152.txt
@@ -0,0 +1 @@
+* Mi Band 4: Initial support (WARNING: INITIAL SETUP NEEDS MI FIT WITH ACCOUNT AND ROOT, NOT A RECOMMENDED DEVICE FOR GADGETBRIDGE)
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/153.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/153.txt
new file mode 100644
index 0000000..bfd64ad
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/153.txt
@@ -0,0 +1 @@
+* Mi Band 4: Support flashing watchfaces, res and firmware (.ft untested)
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/154.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/154.txt
new file mode 100644
index 0000000..c788869
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/154.txt
@@ -0,0 +1,5 @@
+#### Version 0.35.2
+* Mi Band 1/2: Crash when updating firmware while phone is set to Spanish
+* Mi Band 4: Enable music info support (displays now on the band)
+* Mi Band 4: Support setting date format (for built-in watchfaces)
+* Amazfit Cor 2: Try to fix empty menu on device
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/155.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/155.txt
new file mode 100644
index 0000000..e90ce49
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/155.txt
@@ -0,0 +1,13 @@
+* Initial Mijia LYWSD02 support (Smart Clock with Humidity and Temperature Sensor), just for setting the time
+* Mi Band 3/4: Allow enabling the NFC menu where supported (useless for now)
+* Mi Band 3/4, Amazfit Cor/Bip: Set language immediately when changing it (not only on connect)
+* Mi Band 3/4, Amazfir Cor/Bip: Add icons for "swimming" and "exercise"
+* Mi Band 4: Support flashing the V2 font
+* Mi Band 4: Fix weather location not being updated on the Band
+* Mi Band 4: remove unsupported DND setting from settings menu
+* Amazfit Bip/Cor: Fix resetting of last fetched date for sports activities
+* Amazfit Bip: Fix sharing GPX files for some Apps
+* Pebble: Use Rebble Store URI
+* Support LineageOS 16.0 weather provider
+* Add Averages to Charts
+* Allow togging between weekly and monthly charts
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/156.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/156.txt
new file mode 100644
index 0000000..58fdb76
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/156.txt
@@ -0,0 +1,11 @@
+* Mi Band 2/3/4, Amazfit Bip/Cor: Add setting to expose the HR sensor to 3rd party apps
+* Mi Band 4: Really fix weather location not being updated on the Band
+* Mi Band 4: Fix call notifcation not stopping when call gets answered or rejected on the phone
+* Amazfit Bip/Cor: Support for custom emoji font
+* ZeTime: Enable emoji support
+* ZeTime: Make watch language the same as the phone language by default
+* New status and alarms widget
+* Fix crash when entering notification filter settings
+* Make diagram settings accessible from charts activity
+* Add option to hide the floating plus button in the main activity
+* Fix a potential crash on Android 4.4 KitKat
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/157.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/157.txt
new file mode 100644
index 0000000..ccefaea
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/157.txt
@@ -0,0 +1,3 @@
+* Amazfit Bip: Untested support for Lite variant
+* Force Lineage OS to ask for permission when Trust is used to fix non-working incoming calls
+* Charts: List multiple sleep sessions per day
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/158.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/158.txt
new file mode 100644
index 0000000..7cefbc2
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/158.txt
@@ -0,0 +1,12 @@
+* Initial Makibes HR3 support
+* Amazfit Bip Lite: Initial working support, firmware update is disabled for now (we do not have any firmware for testing)
+* Amazfit Cor 2: Enable Emoji Font setting and 3rd party HR access
+* Find Phone now also vibration in addition to playing the ring tone
+* ID115: All settings are now per-device
+* Time format settings are now per-device for all supported devices
+* Wrist location settings are now per-device for all supported devices
+* Work around broken layout in database management activity
+* Show toast in case no app is installed which can handle GPX files
+* Mi Band 4/Amazfit Bip Lite: Trim white spaces and new lines from auth key
+* Mi Band 4/Amazfit Bip Lite: Display a toast and do not try to pair if there was no auth key supplied
+* Skip service scan if supported device could be recognized without uuids during discovery
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/159.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/159.txt
new file mode 100644
index 0000000..7aba1f7
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/159.txt
@@ -0,0 +1 @@
+* Amazfit Bip Lite: Support flashing firmware and watchfaces
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/160.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/160.txt
new file mode 100644
index 0000000..0b5b1cb
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/160.txt
@@ -0,0 +1,8 @@
+* Amazfit GTR: Initial and incomplete support, mostly untested
+* Amazfit Bip: add Portuguese to the list of selectable languages
+* Mi Band 4: Enable emoji font setting
+* Makibes HR3: Support the english version
+* Makibes HR3: Enable bluetooth pairing for working reconnection
+* Work around crash when trying to display changelog
+* Sleep detection settings: Rolling 24 hours (existing style) or Noon to noon
+* Add alternative color to heartrate in chart settings
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/161.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/161.txt
new file mode 100644
index 0000000..799a980
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/161.txt
@@ -0,0 +1,3 @@
+* Amazfit GTS: Initial and incomplete support, mostly untested
+* Add forward/backward buttons to charts for faster navigation
+* Debug: allow to reset last fetch date for Huami devices
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/162.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/162.txt
new file mode 100644
index 0000000..83de490
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/162.txt
@@ -0,0 +1,2 @@
+* Try to actively re-connect when a connection gets interrupted (interval grows up to 64 seconds)
+* Mi Band2/Amazfip Bip: Make button action settings per-device and enable for Amazfit Bip
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/163.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/163.txt
new file mode 100644
index 0000000..cbf1807
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/163.txt
@@ -0,0 +1,4 @@
+* Fossil Q Hybrid: Initial support
+* Bangle.js: Initial support
+* Reserve Alarm for Calendar feature restricted to Mi Band 1/2 and moved to per-device settings
+* New icon for App Manager
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/164.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/164.txt
new file mode 100644
index 0000000..5055314
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/164.txt
@@ -0,0 +1,4 @@
+* Mi Band/Amazfit: Recogize changes when toggling alarm on device (immediately when connected, else when connecting)
+* Mi Band/Amazfit: Fix some bugs with stuck connection when re-connecting
+* Mi Band 4: Support higher MTU for multiple times faster firmware transfer (probably also Amazfit GTR/GTS)
+* Amazfit Cor: Fix setting language to Chinese manually
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/165.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/165.txt
new file mode 100644
index 0000000..e30d71a
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/165.txt
@@ -0,0 +1,5 @@
+* JYou Y5: Initial support
+* Mi Band 2/Amazfit Bip: Redesign button actions for easy music control setup and support long presses on Bip
+* Amazfit Bip: Remove RES file limit (for BipOS)
+* Huami: Automatically toggle alarm switch when toggling on the Band/Watch while in Alarm settings in Gadetbridge
+* Recogize Pixart-Messenger as Chat App
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/166.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/166.txt
new file mode 100644
index 0000000..7910f40
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/166.txt
@@ -0,0 +1,4 @@
+* Huami: allow to have alarms without snooze feature
+* Mi Band 2: Properly stop a call notification when text notifications are disabled
+* VoIP calls: ignore notifications with only one action, assuming it is an outgoing call
+* Try to fix notifications from Business Calendar
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/167.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/167.txt
new file mode 100644
index 0000000..f9d0e37
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/167.txt
@@ -0,0 +1,6 @@
+* Initial iTag support
+* Fix indefinitely lasting bluetooth scans when location permission has not been granted
+* Try to stop incoming VoIP call notification when the call is answered
+* Vectorize some icons and add a new Mi Scale 2 icon
+* Mi Band 4: Make high MTU optional, fixes problems on some phones
+* ZeTime: Fix probably broken support (duplicate id used by Fossil)
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/168.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/168.txt
new file mode 100644
index 0000000..087149a
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/168.txt
@@ -0,0 +1,2 @@
+* Fix accepting/rejecting calls on Android 9
+* Mi Band 3/4, Amazfit Bip/Cor/GTS/GTR: Option to sync calender events as reminder
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/169.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/169.txt
new file mode 100644
index 0000000..88e4575
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/169.txt
@@ -0,0 +1,6 @@
+* Initial support for Fossil Hybrid HR (needs complicated key extraction, read wiki)
+* Fossil: Allow switching off the Q Icon and use the default Gadgetbridge icon
+* Fix VoIP call handling during DND
+* Fix find-my-phone for Android 10
+* Huami: Fix crash when calendar event desctription or title was null
+* Huami: Igore all-day events when syncing calendar events
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/170.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/170.txt
new file mode 100644
index 0000000..b2724a3
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/170.txt
@@ -0,0 +1,7 @@
+* Initial support for Amazfit Bip S (incomplete, needs the official app once to obtain the pairing key)
+* Amazift Bip Lite: Allow relaxing firmware checks to allow flashing of the regular Bip firmware (for the brave)
+* Fossil Hybrid HR: Fix notification history on newer firmwares
+* Fossil Hybrid HR: Add option to disable widget circle
+* Bangle.js: Don't set time if the option is turned off in settings
+* Bangle.js: DST and time zone fixes
+* Add Arabic-style Eastern Arabic numerals to transliteration
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/171.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/171.txt
new file mode 100644
index 0000000..9c5cddc
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/171.txt
@@ -0,0 +1,8 @@
+* Fossil Hybrid HR: Allow choosing and cropping image to be set as watch background
+* Fossil Hybrid HR: Option to draw circles around widgets
+* Fossil Hybrid HR: Experimenal firmware update support
+* Fossil Hybrid HR: Fix vibration strength setting
+* Huami: Do not display firmware information and whitelist information when flashing watchfaces
+* Huami: Disable air quality indicator on Huami devices instead of showing 0
+* Bangle.js: Change encoded char set to match Espruino's 8 bit fonts
+* Steps/Sleep averages: Skip days with zero data
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/172.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/172.txt
new file mode 100644
index 0000000..68c6a4c
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/172.txt
@@ -0,0 +1,5 @@
+* Fossil Hybrid HR: Initial support for activity tracking (no sleep yet)
+* Fossil Hybrid HR: Support setting alarms on newer firmware
+* Amazfit GTR/GTS: Fix flashing watchfaces and maybe firmware/res update (still untested)
+* Amazfit GTS: Support enabling/disabling menu items on the watch
+* Implement transliteration for Korean
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/173.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/173.txt
new file mode 100644
index 0000000..5229f43
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/173.txt
@@ -0,0 +1,14 @@
+* Initial support for WatchX(Plus)
+* Add support for Amazfit GTR Lite (untested and incomplete)
+* Fossil Hybrid HR: Fix some issues with custom widgets
+* Fossil Hybrid HR: Allow setting alarm titles and descriptions
+* Fossil Hybrid HR: Fix step data parsing
+* Amazfit GTS: Fix setting menu items with low MTU
+* Amazfit GTR: Allow setting menu item like GTS
+* ZeTime: Support setting the watch language
+* ZeTime: Support rejecting calls
+* ZeTime: Try to fix weather conditions on newer firmware
+* ZeTime: Fix could not synchronize calendar on connect
+* ZeTime: Fix calendar event time and date
+* ZeTime: Send up to 16 upcoming calendar events on connect if option is enabled
+* Allow set light/dark theme according to system settings (new default)
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/174.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/174.txt
new file mode 100644
index 0000000..2beee0e
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/174.txt
@@ -0,0 +1,6 @@
+* Amazfit Bip S: Support setting shortcuts
+* Amazfit Bip S: Fix setting display items
+* Amazfit Bip S: Fix incoming call notification
+* Huami: Fix menu items vanishing from the device when they were never configured through Gadgetbridge
+* Lenovo Watch9: Fix launch of wrong calibration activity
+* Reduce calls to onSetMusicInfo/onSetMusicState when playing music
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/175.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/175.txt
new file mode 100644
index 0000000..3d23067
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/175.txt
@@ -0,0 +1,9 @@
+* Huami: Support flashing newer GPS firmware and GPS ALM
+* Amazfit Bip S: Support music control
+* Amazfit Bip S: Support flashing firmware, res, gps firmware, watchfaces, fonts and GPS CEP
+* Amazfit Bip S: Allow setting high MTU (much faster firmware installation, default off since it does not work for some)
+* Amazfit Bip S: remove disconnect notification and button action settings (they do not work)
+* Mi Band 4 (possibly others): Fix detected RES version being always 69 for non-whitelisted res files
+* Fossil Hybrid HR: Add last notification widget
+* Try to fix vanishing incoming call information when VoIP call support is enabled
+* Allow setting device aliases (useful if you manage multiple ones of the same type)
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/176.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/176.txt
new file mode 100644
index 0000000..2e65345
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/176.txt
@@ -0,0 +1,8 @@
+* Initial support for Amazfit T-Rex
+* Amazfit Bip S: Support installation of latest .res
+* Amazfit Bip S: Support longer notification messages
+* Huami: Limit weather forecast to 7 days to fix problems with weather notificaon 0.3.11
+* Huami: Improve music playback information
+* Huami: Ensure cutting strings on UTF-8 border
+* Stop incoming call notification when VoIP calls are missed
+* Fix a crash when with Farsi translation
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/177.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/177.txt
new file mode 100644
index 0000000..54c0133
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/177.txt
@@ -0,0 +1,2 @@
+* Amazfit GTR/GTS: Fix connection issue with latest firmwares (probably other Huami devices also affected)
+* Add experimental support for TinyWeatherForecastGermany
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/178.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/178.txt
new file mode 100644
index 0000000..a850b96
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/178.txt
@@ -0,0 +1,8 @@
+* Initial support for Mi Band 5
+* Initial support for TLW64
+* Amazfit GTR/GTS: Fix broken activity data on newer firmwares
+* Big refactoring of the device discovery activity (See PR #1927 description for details)
+* Add about screen
+* New icon for Amazfit Bip
+* Avoid duplicated entries in preferred media player selection
+* Avoid a lot of crashes and improve error handling in various places
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/179.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/179.txt
new file mode 100644
index 0000000..478a272
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/179.txt
@@ -0,0 +1,10 @@
+* Initial experimental support for Pinetime-JF (not yet usable)
+* HPlus; Recognize Lemfo SG2
+* Huami: Support events forwarding via intents or direct triggering of certain actions (eg. stop music when fall asleep)
+* Huami: Add Sports Activity Detail screen from decoded sports summary values
+* Huami: Recogize and decode lot more activity (workout) types
+* Amazfit Cor/Cor2: Allow workout syncing
+* Add Sports Activity Summary filtering and statistics
+* Many icons have been re-drawn as vectors, also several new device and sports activity icons added
+* Many improvements to the Bluetooth discovery and scanning
+* Fix crash when opening GPX files
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/180.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/180.txt
new file mode 100644
index 0000000..848e7f6
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/180.txt
@@ -0,0 +1,8 @@
+* Huami: Add new activity types found in recent Bip S firmware
+* Huami: Many improvements to the activity summary view, including a global view for all devices, filtering per activity type and much more
+* Huami: Prevent generating broken elevation data when they are not sent by the device
+* Amazfit Bip S: Allow flashing more font files and GPS almanac (only cep worked before)
+* Pinetime-JF: Recognize device if it announces itself as InfiniTime
+* ZeTime: Fix weather forecast icons on older firmwares, try to send weather even if no firmware version was detected
+* HPlus: Improve Unicode, notification lenth and  weather support
+* Fix warnings and colors for AboutScreen
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/181.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/181.txt
new file mode 100644
index 0000000..8e68d6d
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/181.txt
@@ -0,0 +1,8 @@
+* Amazfit Bip S: Send sunrise and sunset on latest firmware if enabled
+* Huami: Support new firmware update protocol (fixes firmware flashing with firmware 2.1.1.50/4.1.5.55 on Amazfit Bip S)
+* Huami: Allow flashing latest GPS firmware
+* InfiniTime: Add support for music control
+* Pebble: Fix steps on home screen widget
+* Bangle.js: Fix issue where call state reporting was corrupted
+* Add charts to sport activity summary view
+* Add missing icons for new sport activity type
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/182.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/182.txt
new file mode 100644
index 0000000..e714b57
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/182.txt
@@ -0,0 +1,11 @@
+* Initial support for Sony SWR12
+* Initial support for Lefun Smart Bands
+* Initial support for Nut devices
+* InfiniTime: Improved music support for latest firmware
+* Fossil Hybrid HR: Fixes and better support for newer firmwares
+* Fossil Hybrid HR: Debug activity for dumping and sending resources to the watch
+* Huami: Improve style of sports activity lists
+* Add sport activity list tab in charts
+* Allow sharing of sports activity summaries as image (full scroll view)
+* Weather: Fix wind speed and direction not being passed properly
+* Fix find your phone feature on Android 10 (need companion device pairing)
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/183.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/183.txt
new file mode 100644
index 0000000..3c9b2f2
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/183.txt
@@ -0,0 +1,20 @@
+* Initial support for Amazfit Bip S Lite
+* Initial support for Amazfit GTR/GTS 2
+* Huami: allow sorting of shortcuts and menus (all except Mi Band 2)
+* Amazfit Band 5: Allow enabling SpO2 menu
+* Mi/Amazfit Band 5: Support shortcuts (right/left swipe)
+* Amazfit GTS: Fix firmware flashing on Firmware >=0.1.1.16
+* Amazfit GTR: Fix firmware flashing on Firmware >=1.3.7.16 or >=1.7.0.10
+* Amazfit GTR/GTS: Add missing settings menu item in preferences
+* Fossil Hybrid HR: Remove Android notifications when deleting them from the watch
+* Fossil Hybrid HR: Enable rejecting calls on newer firmwares
+* Fossil Hybrid HR: Support hands calibration
+* Fossil Hybrid HR: Support factory reset
+* InfiniTime: Improve notification support for firmware >=0.9
+* Add version to About screen
+* Show GPS track in Sport Activity detail screen
+* Add Activity List Dashboard/Summary view to charts
+* Add heart rate average to Activity and Sleep charts
+* Add intensity to Sleep charts
+* Recognize Wire messenger as a chat application
+* Add confirmation dialog for find device button
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/184.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/184.txt
new file mode 100644
index 0000000..b5d5c60
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/184.txt
@@ -0,0 +1,10 @@
+* Initial support for Casio GBX-100
+* Mi/Amazfit Band 5: Support watchface installation
+* Mi Band 5: Add missing NFC menu item
+* Casio GB-5600B/GB-6900B: Add configurable disconnect notification
+* Casio: Add support for synchronizing profile settings
+* Fossil Hybrid HR: Keep widget values after widget reload
+* Allow sorting and disabling charts/statistics tabs in per-device settings
+* Improve Sports Activities dashboard and Filter
+* Bug fixes for notification removal
+* Allow to clear activity labels
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/185.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/185.txt
new file mode 100644
index 0000000..ca9ef3a
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/185.txt
@@ -0,0 +1,11 @@
+* Amazfit Bip U: Initial support
+* Amazfit Verge Lite: Initial Support
+* Amazfit T-Rex: Add missing menu items, remove non-existent
+* Amazfit Bip S: Fix crash with notifications with only a title (GitNex does this)
+* Casio GBX-100: Add step count data and more device settings
+* Fossil Hybrid Q: Support firmware upgrade
+* Bangle.js: Support for HRM and steps activity recording
+* Huami: Add new option to properly distinguish connected advertising and 3rd party hr exposure
+* Huami: Use blue icon instead of rainbow color icon for Signal (the rainbow icon was blue in early days)
+* Complete overhaul of the daily stats widget
+* Better error message for invalid authentication keys
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/186.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/186.txt
new file mode 100644
index 0000000..1549308
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/186.txt
@@ -0,0 +1,8 @@
+* Amazfit Bip U Pro: Initial support
+* Amazfit GTS2: fix pairing
+* Amazfit GTS/GTR2: Fix incoming call display
+* Fossil Hybrid HR: avoid unnecessary widget rendering
+* A lot of Data(base) Management screen clarifications and improvements
+* Fix a crash when when forecastConditionType length is 0 in weather notification data
+* Change Do Not Disturb support to allow priority notifications
+* Fix problems when pairing some devices which require a pin to be entered
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/187.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/187.txt
new file mode 100644
index 0000000..dd2d252
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/187.txt
@@ -0,0 +1,16 @@
+* Initial support for wasp-os on nRF52 devices
+* Initial support for Zepp E
+* Initial support for Amazfit GTS 2 Mini
+* Initial support for Amazfit Neo
+* Initial support for Amazfit GTR/GTS 2e
+* Fossil Hybrid HR: Fix bug with unknown data
+* Fossil Hybrid HR: allow app management on watch from GB
+* Fossil Hybrid HR: enumerate apps on watch on every connect
+* Fossil Hybrid HR: Do not configure buttons in unauthenticated mode
+* Fossil Classic: Fix unknown wearing state
+* Fossil Classic: Allow synchronizing activity data
+* Amazfit Bip U: Fix sports activity summary
+* Huami: Add Strength Training activity type
+* Honor Imperial units settings in widget, Activity list and workouts
+* Show all eligible devices in Widget Configuration Activity
+* Also include step of not-worn samples in weekly step statistics
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/188.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/188.txt
new file mode 100644
index 0000000..a99d7e3
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/188.txt
@@ -0,0 +1,3 @@
+* Initial support for Amazfit X
+* Fix missing menu items for GTS 2 Mini (some improvements also for other GTR2/GTS2 models) 
+* Amazfit GTS2e: Fix broken support
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/189.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/189.txt
new file mode 100644
index 0000000..6c2662b
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/189.txt
@@ -0,0 +1,2 @@
+* Amazfit GTS2e: Really fix broken support
+* Amazfit Bip S Lite: Fix broken support (probably)
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/190.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/190.txt
new file mode 100644
index 0000000..af23495
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/190.txt
@@ -0,0 +1,9 @@
+* InfiniTime: Fix music control for newer firmware releases
+* InfiniTime: Support call control and notification
+* Sony SWR12: Fix broken support since 0.53.0
+* Wasp-OS: Fix crash on Android 7 and lower
+* Add activity list dashboard summary calculations
+* Add battery level logging and graph activity
+* Use distinct icons for total step and distance values in widget
+* Flip and scale GPX canvas
+* Try to fix call notification on outgoing VoIP calls
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/191.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/191.txt
new file mode 100644
index 0000000..2a22abf
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/191.txt
@@ -0,0 +1,10 @@
+* Initial support for UM25 voltage meters
+* Pebble: Remove read/dismissed notifications on watch (can be disabled)
+* Fossil Hybrid HR: Remove read/dismissed notifications on watch (can be disabled)
+* Fossil Hybrid HR: Fixes for running firmware DN1.0.2.20 or newer (disable widget features, currently not supported)
+* Fossil Hybrid HR: Fix up/back navigation and add titles to Fossil specific menus
+* Huami: Fix displaying title for calendar events
+* Use requestLegacyExternalStorage in manifest to fix file access problems in some Android 10 roms
+* Add chart to each item in activity list
+* Add scroll view to activity detail to handle landscape view
+* Add header title to activity detail
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/192.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/192.txt
new file mode 100644
index 0000000..0a46629
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/192.txt
@@ -0,0 +1,7 @@
+* Fossil Hybrid HR: Fix compatibility for oder firmware revisions
+* Amazfit Neo: Implement firmware update
+* Amazfit Neo: Support setting all menu items and fix menu cycling bug
+* Amazfit Neo: Fix notifications
+* Amazfit Neo: Support sound settings
+* Allow OpenTracks and FitoTrack to send ongoing notifications
+* Make transliteration a per-device setting
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/193.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/193.txt
new file mode 100644
index 0000000..4e961e3
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/193.txt
@@ -0,0 +1,14 @@
+* Amazfit GTR2: Improve firmware update support (still partly broken)
+* Amazfit GTR2/GTS2: Prevent emoji transliterating (Seems to support emoji)
+* Amazfit GTS2/GTR2: Fixes for setting menu items (Probably still partly broken)
+* Amazfit GTS2/GTR2: Send wind speed and sunrise/sunset
+* Fossil Hybrid HR: Send actual application icons for notification on the watch
+* Fossil Hybrid HR: Support dismissing incoming calls with a quick SMS reply
+* Huami: Remove unused calendar slots
+* Huami: Send wind speed in Beaufort for some devices
+* InfiniTime: Add support for battery info
+* InfiniTime: Support title for notifications on firmware 0.15
+* InfiniTime: Implement find device by simulating a phone call
+* Adjust Steps Charts steps values offset to account for large phone screen sizes
+* Make Activity Charts dates move as calendar months and to go to now if jumping past today
+* Make Battery Info time span jump as calendar months and not as days
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/194.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/194.txt
new file mode 100644
index 0000000..bb70274
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/194.txt
@@ -0,0 +1,7 @@
+* Initial limited support for Mi Band 6
+* Amazfit GTR2/GTS2: Fix for flashing watchfaces
+* Amazfit GTR/GTS/GTR2/GTS2/Bip S: Allow flashing AGPS bundles
+* Amazfit Neo: Add lift wrist setting
+* Fossil Hybrid HR: Restructure settings menus for calibration, file management and physical buttons
+* Fossil Hybrid HR: Hide old settings on newer firmwares
+* Improved PineTime/InfiniTime firmware DFU metadata parsing and checks
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/195.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/195.txt
new file mode 100644
index 0000000..27c1889
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/195.txt
@@ -0,0 +1,7 @@
+* Mi Band 6: Add Pomodoro to menu items
+* Mi Band 6: Support flashing firmware, res and watchfaces
+* Mi Band 6: Enable Unicode Emoji support
+* Fossil Hybrid HR: Move commute actions to device specific settings
+* Fossil Hybrid HR: Use Gadgetbridge App Manager (same as Pebble), allowing quick switching of watchfaces and caching apps for later re-(installation)
+* Huami: Send changed weight, birthday and height data to watch immediately
+* Use flexbox layout for icons in device cards, allowing line breaks for small phones
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/196.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/196.txt
new file mode 100644
index 0000000..b32047d
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/196.txt
@@ -0,0 +1,6 @@
+* Initial experimental support for Amazfit T-Rex Pro
+* InfiniTime: Try to fix firmware upgrade by fiddling with optimization rules
+* Huami: Fix lost samples bug with timezones that are have a 30 minute offset
+* Fossil Hybrid HR: Block dangerous intents by default and move some settings to developer settings submenu
+* Improved logging in bonding and tried to recover from weirdness
+* Use a separate notification channel for low battery warnings
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/197.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/197.txt
new file mode 100644
index 0000000..b06eaea
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/197.txt
@@ -0,0 +1,7 @@
+* Pebble: Fix broken app manager (regression from 0.57.1)
+* Bangle.js: Try to fix crash when attempting to load activity chart
+* Amazfit T-Rex Pro: Add barometer to menu items
+* Remove battery data when device is being removed
+* Add transliteration to PineTime
+* Debug: Allow adding fake testing devices manually
+* Reduce margin of icons in device_item to keep one row for typical amount of icons
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/198.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/198.txt
new file mode 100644
index 0000000..9253061
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/198.txt
@@ -0,0 +1,5 @@
+* InfiniTime: Support notification for battery level
+* Allow importing GPX tracks via Android Intent/Share system
+* Add option for black background to dark theme
+* BangleJS, WaspOS: Support for transliteration
+* Add missing icons to settings items
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/199.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/199.txt
new file mode 100644
index 0000000..a921928
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/199.txt
@@ -0,0 +1,6 @@
+* Initial support for SMA Q2 OSS firmware (Emeryth)
+* Fix broken UM25 support
+* Fossil Hybrid HR: Add watchface designer
+* use '_' instead ':' in exported and imported file names to fix problems with some Android versions
+* Fix applying theme as set by the system
+* Try to improve behavior when quitting Gadgetbridge
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/200.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/200.txt
new file mode 100644
index 0000000..b217d20
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/200.txt
@@ -0,0 +1,7 @@
+* Fossil Hybrid HR: Add power saving feature and many new widgets for the official Gadgetbridge watchface (battery, calories, 2nd TZ, chance of rain)
+* Fossil Hybrid HR: Support setting metric/imperial mode
+* Amazfit T-Rex Pro: Try to fix some menu items
+* Huami: Re-enable setting the timezone correctly with included DST (fixes world time)
+* Bangle.js: Add functions for pushing bitmaps
+* Bangle.js: Aadd configurable MTU
+* Add Heart Rate measurement screen, accessible via heart icon in device action icons
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/201.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/201.txt
new file mode 100644
index 0000000..a2bf031
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/201.txt
@@ -0,0 +1,2 @@
+* Mi Band 6: Support firmware 1.0.4.38 (experimental, still missing features)
+* InfiniTime: Fix null being displayed as notification title
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/202.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/202.txt
new file mode 100644
index 0000000..6b96681
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/202.txt
@@ -0,0 +1,3 @@
+* Mi Band 6: Properly support firmware 1.0.4.38
+* Mi Band 6: Add Flashlight to menu items
+* ZeTime: Fix corruption on long notifications
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/203.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/203.txt
new file mode 100644
index 0000000..caea937
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/203.txt
@@ -0,0 +1,9 @@
+* Initial support for FitPro bands
+* Mi Band 6: really fix weather on new firmware
+* Casio GBX-100: Fix connection
+* Fossil Hybrid HR: Lower battery level warning threshold to 10%
+* Add ringtine preference setting for find your phone feature
+* Fix a bug where GB_LOGFILES_DIR_IS_UNDEFINED is used as a logfile directory
+* Remove per-device preferences upon device removal (backup your keys if you do that)
+* Exclude not worn sleep times from sleep sessions
+* Add Icelandic and Czech transliteration
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/204.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/204.txt
new file mode 100644
index 0000000..8c4190e
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/204.txt
@@ -0,0 +1,9 @@
+* Initial support for Nothing Ear(1)
+* Amazfit Bip U/Pro: Fix flashing firmware and watchfaces
+* Amazfit Bip U/Pro: Fix language setting
+* Amazfit Bip U/Pro: Allow unicode emoji
+* Huami: fix supported languages list on many devices
+* Fossil Hybrid HR: Support rotation of backgrounds in watchface editor
+* UM25: Show more measured data
+* Improved notification management including blacklist or whitelist apps settings or discrete notifications with removed text
+* Fix default daily target distance from 5 to 5000 meters
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/205.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/205.txt
new file mode 100644
index 0000000..b017031
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/205.txt
@@ -0,0 +1,7 @@
+* Initial support for Galaxy Buds 2019
+* Huami: Fix syncing of data in non-DST time
+* InfiniTime: Fix wrong time zone being sent in non-DST mode
+* Amazfit Bip U Pro: Support flashing AGPS updates
+* FitPro: recognize LH716 devices
+* Add support for Casio STB-1000. Limitations of GB-5600B/GB-6900B apply
+* Prevent crash when receiving broken weather data from TinyWeatherForecastGermany
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/206.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/206.txt
new file mode 100644
index 0000000..c7785a3
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/206.txt
@@ -0,0 +1,16 @@
+* Galaxy Buds Live: Initial Support
+* Sony WH-1000XM3: Initial Support
+* Add support for Casio GBD-200 and untested support for GBD-100
+* Casio: Fix alarm handling on all devices
+* Fossil Hybrid HR: Add button for removing the watchface background image
+* Fossil Hybrid HR: Support multiple 2nd-TZ and Custom widgets
+* Fossil Hybrid HR: Add support for native DND functionality
+* Nothing Ear (1): Add multiple batteries support
+* Galaxy Buds: Add multiple batteries support
+* Roidmi: New FM Frequency selector with presets
+* Mi Band 6: Try to add Alipay to menu settings, untested
+* FitPro: Support more bands with different bluetooth names
+* Add activity info to device cards
+* Add Nekogram X to Telegram notifications
+* Move location settings out of pebble menu
+* Sort devices by alias, if available
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/207.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/207.txt
new file mode 100644
index 0000000..9d6c619
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/207.txt
@@ -0,0 +1,11 @@
+* Huami: Support native reminders
+* InfiniTime: Initial support for step counting (currently very limited by the device firmware)
+* Bangle.js: Fix Gadgetbridge crashes when playing music with some players
+* Fossil Hybrid HR: Add support for on-device paring confirmation, for watches that are in a state which makes this neccessary
+* Fossil Hybrid HR: Fix widget configuration bug
+* Mi Band 3: Support lift wrist during DND setting
+* Amazfit GTS 2 Mini: Fix language setting
+* Amazfit GTS 2 Mini: Fix setting menu items on the watch
+* Activity card: Open specific Charts tab for each activity
+* Activity Card: React to User settings, unify step length
+* Activity card: simplify the layout and only show each chart if there is data (Cleaner layout for users with multiple devices)
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/208.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/208.txt
new file mode 100644
index 0000000..88afd01
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/208.txt
@@ -0,0 +1,16 @@
+* Initial support for VESC NRF/HM10 devices
+* Initial support vor Bose QC35
+* Initial support for Sony WF-SP800N
+* Fossil Hybrid HR: Fix on-device confirmation for older firmwares
+* Sony WH-1000XM3: Fix Ambient Sound Control commands, potentially improving ANC quality
+* Sony WH-1000XM3: Read configuration from device
+* InfiniTime: Remove debug Toast and subscription to motion raw XYZ values characteristic
+* Roidmi: Fix frequency configuration on some non-english languages
+* Roidmi 3: Add support for Mojietu 3 rebrand
+* Huami: Support hiking and climbing activities, decode some more activity details
+* Amazfit GTS 2 mini: Fix notification title not appearing for non-chat apps
+* Amazfit Bip U/Pro: Disable event reminder feature, it is not supported by the device.
+* Amazfit Bip U/Pro: Allow enabling Todo List menu (feature still not supported)
+* Bangle.js: send weather condition code to device
+* Allow adding test device directly from the discovery screen
+* Keep device info on the correct device during reordering
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/209.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/209.txt
new file mode 100644
index 0000000..eb5ef85
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/209.txt
@@ -0,0 +1,16 @@
+* Amazfit Pop/Pro: Initial Support (probably the same as Bip U but has a different firmware)
+* Sony WH-1000XM4: Initial Support
+* Sony WH-1000XM3: Disable equalizer, surround and sound position while in SBC codec
+* Sony Headphones: Improve initialization on connection
+* Sony Headphones: Implement Noise Cancelling Optimizer
+* Casio: Fix accidentally disabled time synchronization and pairing of new Casio GBX/GBD-series watches
+* Fossil Hybrid HR: Improve Device Applications List handling
+* Fossil Hybrid HR: Added ability to change activity recognition settings on the watch
+* Fossil Hybrid HR: Make width of custom widget configurable
+* Fossil Hybrid HR: Disable non-configurable buttons preferences
+* Amazfip Bip U: Read alarm from the watch on connect and update in Gadgetbrige when changing alarms on the watch (might work on other Huami devices)
+* Add icon for VESC devices
+* Add commit id into About screen
+* Make debug activity notification test to persist text while switching apps
+* Add Portuguese to the list of language options
+* Update configuration button icon in app notification settings
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/210.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/210.txt
new file mode 100644
index 0000000..01d574c
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/210.txt
@@ -0,0 +1,19 @@
+* Add basic support for Casio GBD-H1000
+* Add support for Hama Fit Track 1900 - via FitPro device support
+* Add OpenTracksController for interactions with OpenTracks
+* Fossil Hybrid HR: Start/stop track in OpenTracks from GPS workout on watch
+* Fossil Hybrid HR: Try guessing new widget position
+* Fossil Hybrid HR: Allow assigning no function to a button
+* Add Huami button/device action to control fitness tracking via OpenTracksController
+* Mi Band 6: Sync alarms set on the watch like on Amazfit Bip U and others
+* Bangle.js: Handle battery charging status and fix battery chart.
+* Bangle.js: Prevent exception in case UART RX line is empty
+* Bangle.js: Add repetitions in alarm JSON
+* WaspOS: Fix battery chart.
+* WaspOS: Add condition code to weather JSON
+* XWatch: Add notifications and calls support
+* UM-25: Make cumulative values resettable
+* VESC: Fixed crash when loading a saved value
+* Allow to open Android notification settings from Notification settings
+* AutoExporter changes for better operation and troubleshooting
+* Change Nightly icons background color
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/211.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/211.txt
new file mode 100644
index 0000000..f21df9e
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/211.txt
@@ -0,0 +1,24 @@
+* Initial Support for Sony WF-1000XM3
+* Initial Support for Galaxy Buds Pro
+* Huami: Add Toggle function for Open Tracks tracking to button actions
+* Huami: Move inactivity warnings, goal notification and HR monitoring to device-specific settings
+* Mi Band 6: set time on connect
+* Mi Band 5/6, Amazfit Bip S/U/Pro: Add world clock configuration
+* Mi Band 5/6: support sensitivity setting for lift wrist configuration
+* Mi Band 5: Add support for configuring workout menu on device
+* Mi Band 4/5/6, Amazfit Bip U/Pro: Add support for vibration patterns
+* Mi Band 5: Increase number of reminder slots to 50
+* Mi Band 5/6: Add setting for HR activity monitoring, HR alerts, stress monitoring
+* Amazfit Neo: Allow to disable beeps for email notifications
+* Bangle.js: Fix incoming calls in release builds
+* Bangle.js build: Add option for enabling/disabling internet access
+* Bangle.js: Add ability to receive intents to com.banglejs.uart.tx
+* Fossil Hybrid HR: Support flexible custom menu on watch
+* Fossil Hybrid HR: Add support for native DND Call/SMS functionality
+* VESC: added battery indicator
+* UM25: Add reset option to current accumulation
+* UM25: Add notification on below current threshold
+* Fix crash when calendar is accessed but permission is denied
+* Add com.asus.asusincallui and com.samsung.android.incallui to blacklist
+* New icons for Sony overhead headphones, Sony WF 800n and Mi Band 6
+* When Gadgetbridge needs permissions, pop up a dialog asking nicely and explaining why
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/212.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/212.txt
new file mode 100644
index 0000000..7921bd7
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/212.txt
@@ -0,0 +1,4 @@
+* Huami: Fix long music track names not displaying
+* Amazfit Bip U/Pro/Band 5: Enable extended HR/stress monitoring setting
+* Pebble: Fix calendar blacklist, view and storage
+* FitPro: Fix crash, inactivity warning preference to string
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/213.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/213.txt
new file mode 100644
index 0000000..b54688a
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/213.txt
@@ -0,0 +1,47 @@
+* Multi device support (experimental), allows connecting to multiple devices simultaneously
+* Fossil Hybrid HR: Allow installation of newer watch apps
+* Fossil Hybrid HR: Allow workout app the be added as a shortcut
+* Fossil Hybrid HR: Generate watchface preview image and show it in the app manager
+* Fossil Hybrid HR: Request custom menu config on watchface initialization
+* Fossil Hybrid HR: Invert widgets color when the background image is inverted
+* Fossil Hybrid HR: Show app versions in app manager
+* Fossil Hybrid HR: Make 2nd TZ widget clock duration configurable and fix wrong offset
+* Fossil Hybrid HR: Add option to share a cached watchface/app to another app
+* Fossil Hybrid HR: Allow switching already uploaded watchfaces with Intent
+* Mi Band 6: Fix night mode on latest firmware
+* Mi Band 6: add sleep menu item (also to shortcuts)
+* Mi Band 5: Send GPS location to band during workout
+* Mi Band 5: Start fitness tracking on phone when workout starts on band
+* Mi Band 5: Fix missing Portuguese language
+* Mi Band 5: Add missing breathing shortcut
+* Mi Band 4: Add password support
+* Huami: Fix setting heart rate measurement interval on connection
+* Huami: Fix track name being replaced by album
+* Huami: Display native alarm notification
+* Huami: Fix MTU update on device connection
+* Roidmi 3: Recognize "Roidmi C BLE" as Roidmi 3
+* Bangle.js fix message REPLY option
+* Bangle.js: Keep a log of data sent from the watch, and allow it to be saved with from the debug menu
+* Bangle.js: Support for color dithered bitmaps, and converting emoji->bitmaps
+* Bangle.js: Adding built-in app-loader view. (Only available on internet-enabled builds)
+* Bangle.js: fix null pointer issue for debug messages
+* Bangle.js: Enable calendar sync for bangle
+* Bangle.js: Add icon
+* Pebble: fix configuration of some watchfaces (might break other again)
+* FitPro: add MTU based chunking, add more device names (Sunset 6, Watch7)
+* UM25: fix missing firmware version
+* Support for incoming call notification delay
+* Make calendar blacklist configurable per device
+* Support folders in device list
+* Separate device settings which are specific to the application into Set preferences in device card
+* When pairing devices with auth key requirements, only show Auth key menu related items on long press
+* Provide access to the FW/App Installer via Set preferences in device card
+* Animate card movement in device list
+* Make transliteration configurable per-language
+* Widget: do not show sleep if not recorded
+* Pop up a dialog asking about Location permissions
+* Fix sharing log files on newer android versions
+* Allow to set Bluetooth discovery scanning level to prevent freezing
+* Various UI tweaks and fixes
+* Add monochrome themed icon
+* Add device menu item to get to the FW/App Installer via an explanation activity
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/214.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/214.txt
new file mode 100644
index 0000000..55bff25
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/214.txt
@@ -0,0 +1,26 @@
+* Initial Support for Mi Band 7
+* Initial support for devices using Binary Sensor Service
+* Mi Band 4: Enable heartrate activity monitoring support
+* Mi Band 4: Enable activate display on lift sensitivity setting
+* Mi Band 6: Enable password support
+* Mi Band 4/6, Amazfit Bip U: Enable sending GPS coordinates to band during workout
+* Mi Band 4/6, Amazfit Bip U: Enable start fitness tracking on phone when workout starts on band setting
+* Amazfit Neo: Support hourly chime which was added in firmware 1.1.2.46
+* Amazfit Neo: Fix daily steps goal and notification
+* Amazfit Neo: Fix heartrate sleep detection setting
+* Amazfit Neo: Enable heartrate activity monitoring support
+* Amazfit Neo: Fix alarms setting to "unused" on connect.
+* Bangle.js: Make text as bitmaps have transparent background, and allow font size to be specified
+* Bangle.js: Allow starting Activties on the Android device
+* Fossil Hybrid HR: Add support for Hybrid HR 38mm watches
+* Fossil Hybrid HR: Add optional circle backgrounds to widgets
+* Fossil Hybrid HR: Add toggling of widgets with physical button event
+* Fossil Hybrid HR: Add missing physical button options
+* Fossil Q Hybrid: Fix config activity after multi-device merge
+* InfiniTine: Fixes for steps count sync
+* Add steps/sleep streaks screen
+* Add French transliteration
+* Refactor file logging initialization logic
+* Add alert to Log Sharing if logging has not been enabled yet
+* Fix crash on some phones for find android device (add fallback tone)
+* Fix regression since 0.68.0 with active auto connect
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/215.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/215.txt
new file mode 100644
index 0000000..d0cdf0d
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/215.txt
@@ -0,0 +1,23 @@
+* Initial support for Amazfit GTS 3
+* Initial support for Fossil Hybrid Gen6
+* Initial support for Flipper Zero
+* Huami: fix default vibration pattern
+* Huami: Enable vibration patterns for all compatible devices
+* Huami: Improve large firmware zip file handling
+* Bangle.js: Fix null pointer issue if headers not supplied for HTTP request
+* Bangle.js: Support calendar color and name
+* Mi Band 7: Fix crash if reminder, calendar or canned messages contain non-ascii characters
+* Mi Band 7: Fix NPE when acknowledging that icon was sent
+* Mi Band 7: Fix and enable firmware upgrades
+* Mi Band 7: Support for watchapps
+* Amazfit Neo: remove 1 hour heartrate interval (not supported)
+* Fossil Hybrid HR: Fix watchface redraw after powersave and after wrist flick
+* Fossil Hybrid HR: Enable configuring middle long press on FW 3.0 and newer
+* InfiniTime: Fix firmware update
+* Make heart rate measurement intervals configurable per-device
+* Add option to ignore low priority notifications
+* Fix Skype notifications
+* Prefer long notification text by default
+* Prefer big text when scraping gmail notifications
+* Do not remove newline and whitespace characters from notification content
+* Debug: Add companion device list to debug activity
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/216.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/216.txt
new file mode 100644
index 0000000..6eae22d
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/216.txt
@@ -0,0 +1,34 @@
+* Remove KitKat support, Gadgetbridge now requires Android 5.0
+* Initial support for Amazfit GTR 3
+* Initial support for SuperCars (Shell Racing Cars)
+* Huami: Add preference to overwrite band settings on connection
+* Huami: Fix crash when selecting automatic Always On Display
+* Huami: Set OpenTracks track category and icon
+* Huami: Implement proper find device
+* Huami: Change default find band vibration pattern
+* Flipper Zero: added duration to Intent API
+* Flipper Zero: fixed crash due to unregistered boradcast receiver
+* Flipper Zero: fetch firmware version from flipper
+* Fossil Hybrid HR: Correctly initialize watchface after reset or crash
+* Fossil Hybrid HR: Set OpenTracks track category and icon to workout type selected on watch
+* Fossil Hybrid HR: Allow flick_away as custom event and add move_hands event 
+* InfiniTime: Add weather support
+* Amazfit Neo: Fix world clock
+* Amazfit Neo: Fix long caller name display
+* Amazfit Neo: Remove activity tracks (unsupported)
+* Amazfit GTS 3: Fix battery drain due to unanswered weather requests
+* Mi Band 7: Fix Weather
+* Mi Band 6: Add support for workout activity types
+* Mi Band 6: Enable adding workout activity types to the "more" section
+* Amazfit GTR: Enable button actions
+* Zepp OS: Implement activity, sleep and workout fetching
+* Zepp OS: Improve firmware upgrades
+* Bangle.js: Add PATCH HTTP request type, and fix for VolleyError UnsupportedOperationException when supplying custom headers.
+* Bangle.js: Add ability to start services on the Android device via intents.
+* Bangle.js: Flags and multiple categories can now be specified for intents initiated on the watch.
+* Bangle.js: Add ability to wake and unlock the Android device via a special intent.
+* Allow 3rd party apps to set device settings
+* Re-implement C code in Java and remove Android NDK dependency entirely
+* Fix crashes on older Android versions when using some devices
+* Add support for REM sleep
+* App shortcuts support (long press on the launcher icon for directly connecting a device)
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/217.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/217.txt
new file mode 100644
index 0000000..6273484
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/217.txt
@@ -0,0 +1,4 @@
+* Try to exclude non-free stuff from shortcuts library (#2918)
+* SuperCars: fix periodicDataSender, add tricks
+* Zepp OS: Fix crash when user attempts to disable Settings display item
+* Fix crash when opening Gadgetbridge from the notification
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/218.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/218.txt
new file mode 100644
index 0000000..75b4153
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/218.txt
@@ -0,0 +1,2 @@
+* Zepp OS: Display HR zones and Training Effect in Activity Details
+* Remove shortcut feature due to non-free dependencies (#2918)
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/219.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/219.txt
new file mode 100644
index 0000000..634cbbe
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/219.txt
@@ -0,0 +1,9 @@
+* Fossil/Skagen Hybrids: Update known watch app versions
+* Skagen Hybrids: Allow firmware installation
+* Fossil Hybrid HR: Request menu config upon app connection
+* Amazfit GTS3: Fix crash when fetching workouts
+* Zepp OS: Fix fetching workouts shorter than 1 minute
+* Zepp OS: Decode workout elevation and altitude
+* Huami: Do not crash when failing to parse activity summary
+* Re-connect after update, especially useful for users of nightly releases
+* Make number of not scrollable sleep sessions lines configurable
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/220.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/220.txt
new file mode 100644
index 0000000..13c5b88
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/220.txt
@@ -0,0 +1,23 @@
+* Initial support for Amazfit GTR 4/GTS 4/GTS 4 Mini
+* Initial support for Amazfit Band 7
+* Initial support for Galaxy Buds 2
+* Initial Support for Sony WH-1000XM2/WF-1000XM4
+* Sony headphones: Fix pause when taken off
+* Sony Headphones: Fix setting surround mode
+* Zepp OS: Map strength training, basketball and cricket activity types
+* Zepp OS: Add World Clocks
+* Zepp OS: Fix notification icons larger than 56x56px
+* Zepp OS: Fix notification icons for work profile apps
+* Zepp OS: Fix notification icon for SMS
+* Zepp OS: Fix app and watchface install
+* Zepp OS: Fix NPE when no weather data is available
+* Amazfit GTR 2: Fix activate display upon lift only working when scheduled
+* Bangle.js: Fix calendar sync
+* InfiniTime: Fix weather
+* InfiniTime: Add support for local time service
+* InfiniTime: Add world clock support
+* Skagen Gen 6 Hybrid HR: Add support for 38mm watches
+* Complete rewrite of new device discovery
+* Add Intent API to trigger activity sync and DB export
+* Allow media notifications to bypass app list
+* Debug Activity: Add confirmation dialog before removing device preferences
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/221.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/221.txt
new file mode 100644
index 0000000..8fc80fc
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/221.txt
@@ -0,0 +1,23 @@
+* Initial support for Amazfit T-Rex 2
+* Initial support for AsteroidOS watches
+* Initial support for Sony LinkBuds S
+* Initial support for Galaxy Buds2 Pro
+* Initial support for SoFlow S06(just for lock and unlock, needs key)
+* Fossil/Skagen Hybrids: Fix truncation of notifications
+* Fossil/Skagen Hybrids: Fix washed out colors in imported watchfaces
+* Fossil/Skagen Hybrids: Allow launching watch apps from app manager
+* Fossil/Skagen Hybrids: Fix activity parser
+* Fossil/Skagen Hybrids: Add app/watchface downloading from watch to app manager
+* Fossil/Skagen Hybrids: Fix crash on empty or multi-byte unicode alarm texts
+* Fossil/Skagen Hybrids: Implement inactivity warnings
+* Fossil/Skagen Hybrids: Remove obsolete debug message
+* Mi Band 6: Add NFC display item
+* Zepp OS: Fix Alipay and WeChat Pay display item and shortcuts
+* Amazfit GTR 4/GTS 4: Support for AGPS Updates
+* Bangle.js: Stop sending bitmaps for common characters that already have good enough equivalents on the watch
+* Bangle.js: Stop toast warning message appearing when starting the app loader
+* Bangle.js: Increase default realtime HRM/step interval to 10 seconds
+* Bangle.js: Support additional values for GPS event
+* Sony WF-1000XM4: Fix battery updates while connected
+* Sony WF-1000XM4: Fix audio codec
+* Add Georgian Transliteration
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/222.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/222.txt
new file mode 100644
index 0000000..1029dcc
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/222.txt
@@ -0,0 +1,23 @@
+* Initial support for Amazfit GTR 3 Pro
+* Initial support for Sony WH-1000XM5
+* Amazfit Bip U: Remove alarm snooze option
+* Amazfit GTR 4 / GTS 4: Add watch Wi-Fi Hotspot and FTP Server
+* Amazfit GTR 4 / GTS 4: Perform and receive phone calls on watch
+* Amazfit GTS 2 Mini: Add missing alexa menu item
+* Bangle.js: Fix updating timezone in settings.json if the timezone is zero
+* Fossil/Skagen Hybrids: Pair watch to phone, fixes repeating confirmation request
+* Huami: Implement repeated activity fetching
+* Sony WH-1000XM4: Add speak-to-chat
+* Sony Headphones: Add button modes help
+* Zepp OS: Add shortcut cards preference
+* Zepp OS: Add support for morning updates
+* Zepp OS: Add preference to keep screen on during workout
+* Zepp OS: Add preference for camera remote
+* Zepp OS: Fix activate display upon lift wrist smart mode
+* Zepp OS: Fix Cards and MI AI display item and shortcuts
+* Zepp OS: Fix setting of control center
+* Zepp OS: Fix setting of unknown configuration values
+* Zepp OS: Set watchface from phone
+* Add Croatian transliterator
+* Fix restoring app notification/pebble blacklist preferences on import
+* Cache notifications while devices are out of range (opt-in)
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/223.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/223.txt
new file mode 100644
index 0000000..cd9f0c8
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/223.txt
@@ -0,0 +1,49 @@
+* Bangle.js: Add message size limitation to Calendar and Messages
+* Bangle.js: Add switch to control if the GPS chip should be used to locate the location
+* Bangle.js: Send more weather data to watch
+* Bangle.js: Allow an activity sample to have a timestamp
+* Bangle.js: Send last received activity timestamp on connect (to allow sync of activity samples)
+* Bangle.js: Allow connecting HW keyboard without closing app loader
+* Bangle.js: Bump flavor target SDK version to 31
+* Bangle.js: Fix convertion of emoji/unicode to bitmap without width/height
+* Bangle.js: Fix location listener not being cleaned up when waiting for reconnect
+* Bangle.js: Fix memory leak from HTTP requests
+* Bangle.js: Fix orientation changes closing app loader
+* Bangle.js: Fix return to applications management activity after having opened another window
+* Bangle.js: Set default value for GPS event interval to 1 second
+* Bangle.js: Support navigation instructions
+* Bangle.js: Escape characters that fall in the Unicode codepoint area (for Espruino ~2v18.20 and later)
+* Bangle.js: HTTP request XPath can now return Arrays
+* Fossil/Skagen Hybrids: Add support for ultraviolet index and rain probability
+* Fossil/Skagen Hybrids: Add UV index and chance of rain widgets
+* Fossil/Skagen Hybrids: Allow launching the calibration activity on any Gadgetbridge variant
+* Fossil/Skagen Hybrids: Increase accuracy of workout distance calculation
+* Fossil/Skagen Hybrids: Fix weather icons day/night status
+* InfiniTime: Fix weather expiry time
+* InfiniTime: Support navigation instructions
+* Mi Band 6: Allow making device discoverable via Bluetooth when connected
+* Mi Band 7: Add preference to display call contact information
+* Zepp OS: Add gpx route file upload
+* Zepp OS: Add screenshot support
+* Zepp OS: Add stress charts
+* Zepp OS: Add watch app logs developer option
+* Zepp OS: Display watchface and app preview on install
+* Zepp OS: Fix update operations on Zepp OS 2.1+
+* Zepp OS: Manage contacts on watch
+* Zepp OS: Start new GPX segments on pause/resume
+* Zepp OS: Support flashing zab files
+* App Manager: Fix cached apps sorting
+* App Manager: Hide drag handle if app reorder is not supported
+* App Manager: Add confirmation before deleting app
+* Add menus to share GPX, raw summary, raw details
+* Debug Activity: Allow pairing current device as companion
+* Fix some null pointer exception crashes
+* Intent API: Add command to set device mac address
+* Intent API: Add dataTypes parameter for activity sync
+* Intent API: Add debug actions for notifications and incoming calls
+* OsmAnd: Add support for navigation instructions
+* Scrape navigation instructions from Google Maps notifications
+* Fix lag when a folder has a lot of devices
+* Fix transliteration returning non-ASCII characters
+* Enable "allow high MTU" setting by default
+* Make some hardcoded english strings translatable
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/224.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/224.txt
new file mode 100644
index 0000000..893c700
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/224.txt
@@ -0,0 +1 @@
+* Fix Weather Notification integration
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/225.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/225.txt
new file mode 100644
index 0000000..7507419
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/225.txt
@@ -0,0 +1,56 @@
+* Upgrade UI to Material 3 and add dynamic colors theme
+* Initial support for Amazfit Bip 3 Pro
+* Initial support for Amazfit Cheetah Pro
+* Initial support for Bohemic Smart Bracelet
+* Initial support for Casio GW-B5600
+* Initial support for Garmin Vivomove HR
+* Initial support for Withings Steel HR
+* Experimental support for Amazfit Bip 5
+* Experimental support for Amazfit Falcon
+* Experimental support for Amazfit GTR Mini
+* Experimental support for Amazfit Cheetah (Round/Square)
+* Experimental support for Amazfit T-Rex Ultra
+* Amazfit GTS 2e: Add activate display on lift sensitivity
+* Amazfit GTR 3 / GTS 3: Enable AGPS Updates
+* Amazfit Neo: Enable PAI support
+* Bangle.js: Allow enable/disable of alarms
+* Bangle.js: Fetch activity data
+* Bangle.js: Fix GB integration when watch is not programmable
+* Bangle.js: Put JSON keys in quotes
+* Bangle.js: Reorganize device settings
+* Fossil/Skagen Hybrids: Embed custom menu in watchface, fixes lost menu on reset
+* Fossil/Skagen Hybrids: Fix unused alarms being pushed to the device
+* Fossil/Skagen Hybrids: Strip unicode characters that the watch can't display
+* Fossil/Skagen Hybrids: Sunrise/sunset follows weather location
+* Huami/Zepp OS: Add PAI charts
+* Huami/Zepp OS: Improve music info stability
+* Huami/Zepp OS: Improve reconnection and device initialization
+* Huami: Persist workout raw details even if gpx has no points
+* InfiniTime: Add heart rate measurement support
+* Mi Band 5: Fix activity fetch error toast when stress monitoring is enabled
+* Mi Band 6: Enable PAI and stress support
+* LeFun: Fix heart rate popup when measurement is triggered from phone
+* Sony WH-1000XM3/WF-SP800N: Add volume setting
+* Sony WH-1000XM5: Fix speak-to-chat enable/disable
+* Zepp OS: Add loyalty cards integration with Catima
+* Zepp OS: Enable AGPS updates for all devices
+* Zepp OS: Fix calendar sync on Zepp OS 2, send event location
+* Zepp OS: Fix reminder creation
+* Zepp OS: Fix shortcut cards setting on Zepp OS 2.1
+* Zepp OS: Fix weather, add hourly information
+* Zepp OS: Map barcode types for ITF, PDF_417 and DATA_MATRIX
+* Add preference to display changelog on startup
+* Add Termux RUN_COMMAND permission
+* Allow filtering notifications from work profile apps
+* Fix daylight saving time not being transmitted to the watch
+* Fix media button control for some applications
+* Fix notification filters by title if notification does not contain a body
+* Fix opening screenshots from notification on external apps
+* Fix reconnect delay reset after all devices are initialized
+* Fix some security error crashes when permissions are not granted
+* Fix transliteration of emoji
+* Fix transliteration of non-ASCII accented characters
+* Force gps speed calculation on some phones that do not report it correctly
+* Make application list sorting case-insensitive
+* Introduce native app shortcuts for android > 30
+* Update device settings action bar title depending on current screen
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/226.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/226.txt
new file mode 100644
index 0000000..9faba90
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/226.txt
@@ -0,0 +1,5 @@
+* Amazfit GTR Mini: Mark as not experimental
+* Bangle.js: Improve file downloads
+* Bangle.js: Fix app interfaces
+* Allow text to be shared to devices
+* Fix connection to some Amazfit devices 
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/227.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/227.txt
new file mode 100644
index 0000000..9c20dcb
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/227.txt
@@ -0,0 +1,78 @@
+* Initial support for Amazfit Balance
+* Initial support for Amazfit Active
+* Initial support for ColaCao 2021
+* Initial support for ColaCao 2023
+* Initial support for Femometer Vinca II
+* Initial support for Mijia LYWSD02MMC variant
+* Initial support for Sony Wena 3
+* Experimental support for Divoom Pixoo
+* Experimental support for Sony WF-1000XM5
+* Experimental support for Amazfit Active Edge
+* Experimental support for Mi Band 7 Pro (Xiaomi Smart Band 7 Pro)
+* Experimental support for Mi Band 8 (Xiaomi Smart Band 8)
+* Experimental support for Mi Watch Lite
+* Experimental support for Mi Watch Color Sport
+* Experimental support for Redmi Smart Band 2
+* Experimental support for Redmi Watch 3 Active
+* Experimental support for Xiaomi Watch S1 Active
+* Amazfit Band 7: Add alexa menu entries
+* Amazfit GTR 3 Pro: Fix firmware and watchface upload
+* Amazfit T-Rex: Fix activity summary parsing
+* Amazfit T-Rex Pro: Add activate display on lift sensitivity
+* AsteroidOS: Add more supported watch models
+* AsteroidOS: Fix media info
+* AsteroidOS: Fix notification dismissal
+* Bangle.js: Add loyalty cards integration with Catima
+* Bangle.js: Ensure SMS messages have src field set to "SMS Message"
+* Bangle.js: Fix GPS speed
+* Bangle.js: Improve handling of chinese characters
+* Bangle.js: Lower threshold for low battery warning
+* Bangle.js: Recover from device initialization failure
+* Casio GBX100/GBD-200: Fix first connect
+* Casio GB5600/6900/STB-1000: Fix pairing
+* Casio GDB-200: Fix notification timestamp
+* Casio GDB-200: Fixed notification categories and default category
+* Casio GDB-200: Allow preview of notification message alongside title
+* Casio GDB-200: Fixed find my phone feature
+* Intent API: Add debug action for test new function
+* Fossil/Skagen Hybrids: Add new navigation app
+* Fossil/Skagen Hybrids: Allow configuring call rejection method
+* Fossil/Skagen Hybrids: Fix some preference crashes on the nightly
+* Fossil/Skagen Hybrids: Reduce toasts on release builds
+* Fossil/Skagen Hybrids: Show device specific settings in more logical order
+* Huami: Toggle phone silent mode from band
+* Message privacy: Add mode Hide only body
+* Mijia LYWSD02: Add battery
+* Mijia LYWSD02: Add low battery notification
+* Mijia LYWSD02: Set temperature unit
+* Mijia LYWSD02: Fix battery drain while connected
+* PineTime: Display app name for VoIP app calls
+* PineTime: Honor Sync time setting on connect
+* PineTime: Improve notification handling
+* PineTime: Reduce weather memory usage
+* Withings Steel HR: Fix crash when calibrating hands on the nightly
+* Zepp OS: Add blood oxygen graph
+* Zepp OS: Add workout codes for hiking and outdoor swimming
+* Zepp OS: Allow disabling app notifications per device
+* Zepp OS: Attempt to fix activity fetch operation getting stuck
+* Zepp OS: Display swimming activity data
+* Zepp OS: Fix health settings on older Zepp OS versions
+* Zepp OS: Fix setting of unknown button press apps
+* Zepp OS: Fix sunrise and moon dates being off by local time + UTC offset
+* Zepp OS: Map hiking, outdoor swimming, climbing and table tennis activity types
+* Zepp OS: Toggle phone silent mode from band
+* Add transliteration for Latvian, Hungarian, Common Symbols
+* Allow multiple device actions to be triggered for the same event
+* Allow toggling DND through device actions
+* Autodetect OsmAnd package name and make it configurable
+* Improve ASCII transliterator
+* Make GMaps navigation handler follow the "navigation forwarding" setting
+* Support selecting enabled navigation apps
+* Allow ignore notifications from work profile apps
+* Display alias in low battery notification
+* Fix crash when pairing current device as companion
+* Fix emoji when a transliterator is enabled
+* Fix UV Index and rain probability for some weather apps
+* Improve device discovery stability and fix freezes
+* Improve Telegram and COL Reminder notifications
+* Replace old-style preference switch with Material 3 switch
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/228.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/228.txt
new file mode 100644
index 0000000..d818f95
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/228.txt
@@ -0,0 +1,28 @@
+* Initial support for Honor Band 3,4,5,6
+* Initial support for Huawei Band 4, 4 Pro, 6, 7, 3e, 4e
+* Initial support for Huawei Talk Band B6
+* Initial support for Huawei Watch GT, GT 2
+* Initial support for Mijia LYWSD03MMC
+* Initial support for Nothing Ear (2)
+* Initial support for Nothing Ear (Stick)
+* Experimental support for Honor Band 7
+* Experimental support for Redmi Watch 2 Lite
+* Experimental support for Redmi Smart Band Pro
+* Casio GBX100: Add support for snooze alarm
+* Fossil/Skagen Hybrids: Update navigationApp to 1.1
+* Huami: Fetch SpO2 on devices that support it
+* Pebble: Attempt to fix app configuration webview
+* PineTime: Add support for InfiniTime's new simple weather
+* PineTime: Fix freeze and reboot when upgrading firmware
+* Pixoo: Enable sending images (non-persistent)
+* Pixoo: Get and send alarms
+* Pixoo: Set custom device name
+* Pixoo: support "clap hands to turn off screen" and "sleep after silence" settings
+* Xiaomi: Improve activity and workout parsing
+* Xiaomi: Improve stability and fix some crashes
+* Xiaomi: Improve weather
+* Xiaomi: Parse sleep stages
+* Add a notifications channel for connection status notifications
+* Improve automatic connection to all or previous devices
+* Fix devices sometimes staying stuck in a "Connecting" state
+* Map some missing Google Maps navigation actions
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/229.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/229.txt
new file mode 100644
index 0000000..fb39859
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/229.txt
@@ -0,0 +1,23 @@
+* Initial support for Honor Magic Watch 2
+* Initial support for Mijia MHO-C303
+* Initial support for Nothing CMF Watch Pro
+* Initial support for Sony WI-SP600N
+* Experimental support for Redmi Watch 2
+* Experimental support for Xiaomi Smart Band 8 Pro
+* Experimental support for Xiaomi Watch S1 Pro
+* Experimental support for Xiaomi Watch S1
+* Experimental support for Xiaomi Watch S3
+* Galaxy Buds2 Pro: Fix recognition of some versions
+* Huawei Watch GT 2: Fix pairing
+* Redmi Smart Band Pro: Fix password digits
+* Pebble: Fix app configuration page
+* Pebble 2: Fix pairing issue
+* PineTime: Fix weather forecast on InfiniTime's new simple weather
+* Xiaomi: Fix sleep sometimes extending past the wakeup time
+* Xiaomi: Request battery level and charging state periodically
+* Xiaomi: Fix sleep stage parsing for some devices
+* Zepp OS: Improve device discovery
+* Zepp OS: Fix weather not working on some devices
+* Zepp OS: Prevent crash when installing large firmware updates
+* Fix sport activity summary group order
+* Fix reconnection to devices failing occasionally
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/230.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/230.txt
new file mode 100644
index 0000000..533e4e6
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/230.txt
@@ -0,0 +1,8 @@
+* Initial support for Huawei Watch Fit
+* Initial support for Xiaomi Redmi Watch 3
+* Fossil/Skagen Hybrids: Fix crash on multi-byte unicode characters in menu
+* Huawei: Add weather support
+* Bangle.js: Support higher MTU
+* Test Device: Add fake features and data
+* Periodically (around every 2 days) synchronize time on connected devices
+* Set alarm as used and enabled if time has changed
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/231.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/231.txt
new file mode 100644
index 0000000..01bc85f
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/231.txt
@@ -0,0 +1,28 @@
+* Initial support for Amazfit Bip 3
+* Initial support for Huawei Band 8
+* Initial support for Huawei Watch GT 4
+* Initial support for Huawei Watch Ultimate
+* Initial support for Sony LinkBuds
+* Initial support for Xiaomi Smart Band 8 Active
+* Bangle.js: Allow saving files on phone from watch
+* Bangle.js: Fix crash when file save is cancelled
+* Bangle.js: Set filename on save file dialogs
+* Bangle.js: Improve communication stability
+* Bangle.js: Sync activity tracks
+* Bangle.js: remove unwanted charaters from calendar events
+* Femometer Vinca II: Add temperature charts
+* Fossil/Skagen Hybrids: Remove activity fetching toasts and add finished signal
+* Fossil/Skagen Hybrids: Use steps instead of calories for activity intensity
+* Fossil/Skagen Hybrids: Mark device busy and show transfer notification while syncing
+* Huami/Zepp OS: Fix activity sync getting stuck sometimes
+* Mi Band 1/2: Fix vibration settings preference screens
+* Huawei: Add cycling workout type 
+* Huawei: Add smart wakeup interval
+* Pebble: Fix pairing with LE counterpart
+* Xiaomi Watch S1 Pro: Add temperature charts
+* Xiaomi: Fix sleep sync failing when sleep stages are not found
+* Xiaomi: Improve activity sync
+* Nothing CMF Watch Pro: Fix music playback status
+* Allow for device settings sub-screens
+* Device connection: Add support for scan before BLE connection
+* Misc UI improvements (alarms, chart settings)
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/232.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/232.txt
new file mode 100644
index 0000000..cd57aeb
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/232.txt
@@ -0,0 +1,77 @@
+* Experimental support for Amazfit Bip 5 Unity
+* Experimental support for Redmi Watch 4
+* Initial support for cycling sensor
+* Initial support for more Garmin watches
+* Initial support for Hama Fit6900
+* Initial support for Huawei Watch Fit 2, Watch Fit 3, Watch 4 Pro
+* Initial support for Soundcore Liberty 3 Pro
+* Introduce new Dashboard view
+* AsteroidOS: Added icons to the notifications
+* Bangle.js: Add Sleep as Android support
+* Bangle.js: Add screenshot support
+* Bangle.js: Add setting to disable notifications
+* Bangle.js: Allow wake phone when opening notification response from watch
+* Bangle.js: Fix activity intensity normalization
+* Bangle.js: Fix message reply
+* Bangle.js: Improve text rendering
+* Fossil/Skagen Hybrids: Update device settings to new structure
+* Galaxy Buds Live: Update device settings to new structure
+* Galaxy Buds 2: Fix recognition of some versions
+* HPlus: Migrate global preferences to device-specific
+* Huami: Fix reminder message encoding
+* Huawei: Add cycling workout type
+* Huawei: Add enable HeartRate and SpO2 force option
+* Huawei: Add huawei account support (pair without resetting watch)
+* Huawei: Add support for workout calories and cycling power
+* Huawei: Add remote camera shutter
+* Huawei: Ask pincode only on first connection
+* Huawei: Enable sleep detection
+* Huawei: File upload and watchface management
+* Huawei: Fix force DND support
+* Huawei: Fix long notification
+* Huawei: Fix TimeZone offset calculation
+* Huawei: Improve connection and reconnection
+* Huawei: Improve music controls
+* Huawei: Improve notification icons
+* Huawei: Improve weather, HR and SpO2 support
+* Huawei: Improve workout parsing
+* Huawei: Rework settings menu with sub-screens
+* Huawei: Send user info to device
+* Huawei: Support sending GPS to band
+* Huawei Watch GT4: Add HR and SpO support
+* Huawei Watch Ultimate: Add HR and SpO support
+* Intent API: Add broadcast on activity sync finish
+* Intent API: Added debug end call
+* Mi Band 6: Add menu items for NFC shortcuts
+* Nothing CMF Watch Pro: Add weather support
+* Nothing Earbuds: Add adjustable delay for auto-pick-up of calls
+* Nothing Earbuds: Add option to auto-reply to incoming phone calls
+* Nothing Earbuds: Add option to read aloud incoming notifications
+* Sony LinkBuds S: Enable some missing features
+* Xiaomi Smart Band 8 Active: Fix discovery
+* Xiaomi: Add swimming workout type
+* Xiaomi: Allow transliteration
+* Xiaomi: Fix barometer
+* Xiaomi: Fix notification for apps in work profile
+* Xiaomi: Fix some crashes
+* Xiaomi: Improve reconnection
+* Xiaomi: Improve sleep and activity parsing
+* Xiaomi: Improve weather support, add multiple locations
+* Xiaomi: Sync calendar event reminders
+* Zepp OS: Add support for Sleep as Android
+* Zepp OS: Sync calendar event reminders
+* Add Armenian and Serbian transliterators
+* Add GENERIC_PHONE and GENERIC_CALENDAR NotificationType handling
+* Add support for scannable-only devices
+* Fix crash when connecting on some phones
+* Fix crash when enabling bluetooth
+* Fix receiving shared gpx files
+* Fix text cutoff on all checkbox preferences
+* Format pace as mm:ss
+* Make battery threshold notifications configurable
+* Prevent some bluetooth events from starting Gadgetbridge
+* Recognize "Delta Chat" as generic chat
+* Remove deprecated general auto-reconnect preference
+* Refactor location service
+* Set navbar color to match theme
+* Simplify pairing of bonded and companion devices
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/233.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/233.txt
new file mode 100644
index 0000000..c467692
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/233.txt
@@ -0,0 +1,138 @@
+* Initial support for Anker Soundcore Liberty 4 NC
+* Initial support for CMF Buds Pro 2 / Watch Pro 2
+* Initial support for Colmi R02/R03/R06/R10 smart rings
+* Initial support for Garmin Enduro 3, Fenix 5/5 Plus/5X Plus/6/6S Sapphire/7/8, Forerunner 165/255/255S Music/245 Music/265S/955/965, Venu/Venu Sq/Venu Sq 2/Venu 2S, Vivoactive 3, Vivomove Trend, Vivosport
+* Initial support for Huawei Watch 3 / 3 Pro / 4 Pro / D2 / GT 3 SE / GT 5 / GT 5 Pro / GT Cyber / GT Runner
+* Initial support for Honor Watch GS 3 / Watch GS Pro
+* Initial support for IKEA desk controller
+* Initial support for Moondrop Space Travel
+* Initial support for Mijia XMWSDJ04MMC
+* Initial support for Mi Smart Scale 2
+* Initial support for Sony WF-C500 / WF-C700N / WI-C100
+* Initial support for Soundcore Motion 300
+* Initial support for Vivitar HR & BP Monitor Activity Tracker
+* Experimental support for Amazfit T-Rex 3
+* Experimental support for Redmi Watch 5 Active
+* Experimental support for Xiaomi Smart Band 9
+* Experimental support for Xiaomi Watch S3
+* Add all languages supported in weblate
+* Add BLE intent API
+* Add configuration for calendar lookahead
+* Add month and day to date of birth
+* Add more activity types (CMF, Garmin, Huawei, Zepp OS)
+* Allow configuration of notification times
+* Allow syncing birthdays with calendar events
+* Amazfit GTR 2: Enable PAI support
+* AsteroidOS: Fix missing weather day and set-time on connection
+* Bangle.js: Add canned responses
+* Bangle.js: Fix calendar sync setting
+* Bangle.js: Fix distance in activity details
+* Bluetooth Intent API: Add disconnect action
+* Casio GW-B5600: Alarms, find phone, reminders, watch settings
+* Casio: Fix notifications on long messages
+* Change device icons to use theme colors
+* Charts: Add button to pick date
+* Charts: Add charts for HRV, body energy, heart rate, steps, VO2 max, weight
+* Charts: Fix heart rate charts when min is set to 0
+* Charts: Re-design sleep, stress, PAI, workout details
+* Charts: Use HR from workout track file if available
+* CMF Watch Pro 2: Negotiate authentication key
+* CMF Watch Pro: Fix activity transfer
+* Cycling sensor: added live data view
+* Cycling sensor: Improve cycling data display
+* Dashboard: Add new widgets for stress, HRV, body energy
+* Dashboard: Add option to show yesterday's data in Today widget
+* Dashboard: Improve widget gauge resolution
+* Data Management: Add file manager
+* Data Management: Allow browse folders, open and share files
+* Data Management: Allow full backup/restore from a zip file
+* Data Management: Fix import of some preference from a backup
+* Fix activity charts generation for devices that do not report intensity
+* Fix crash when companion pairing
+* Fix discovery of connected devices
+* Fix emoji when some connected device does not support them
+* Fix language not being respected in some situations
+* Fix media controls not working for some apps
+* Fix notification text not being sent for some apps
+* Fix reconnection when device connects back during BLE scan
+* Fossil HR: Fix crash on disconnection
+* Fossil HR: Minor watchface fixes
+* Garmin: Display awake time during sleep
+* Garmin: Display HRV and body energy
+* Garmin: Display resting heart rate
+* Garmin: Fetch SKIP_TEMP files
+* Garmin: Fix agps upload for some URLs
+* Garmin: Fix all-day events
+* Garmin: Fix auto-activity fetch on some devices
+* Garmin: Fix canned replies reset to defaults
+* Garmin: Fix crash on call with privacy mode on
+* Garmin: Fix crash on timezones without DST
+* Garmin: Fix daily weather missing current day
+* Garmin: Fix weather temperature and speed units
+* Garmin: Improve activity, sleep and workout parsing
+* Garmin: Infer sleep time for devices that do not send sleep stages
+* Garmin: Manual HR measurements and live activity
+* Garmin: Map some unknown realtime settings
+* Garmin: Parse workout physiological metrics, strength training workout sets
+* Garmin: Re-parse workout summary when opening details page
+* Garmin: Upload gpx and workout fit files to watch
+* Garmin: Use distance and calories provided by the watch
+* Garmin: View and share gpx files
+* Huami: Fetch workouts during normal sync
+* Huami: Migrate all device settings to sub-screens
+* Huawei Band 9: Improved support
+* Huawei: Add battery polling
+* Huawei: Basic support for the installation of the applications
+* Huawei: Calendar sync support
+* Huawei: Contacts uploading support
+* Huawei: Continuous skin temperature measurement switch
+* Huawei: Enable emoji for HarmonyOS watches
+* Huawei: Fix crash when notification has no text
+* Huawei: Fix initialization issues on some watches
+* Huawei: Fix notifications for Huawei Band 4e
+* Huawei: Fix some reconnection issues
+* Huawei: Fix watchface upload, activity sync, event alarms, weather for some devices
+* Huawei: Fix workout altitude, pace, workout re-parsing
+* Huawei: Improve device initialization
+* Huawei: Improve watchface install support
+* Huawei: Improve weather support
+* Huawei: Initial ephemeris update support
+* Huawei: Map more workout types
+* Huawei: Music upload support
+* Huawei: Provide an activity sample every minute
+* Huawei: Re-parse workout details when opening details
+* Huawei: Send default HR zones
+* Huawei: Workout GPS synchronization
+* Huawei: Simple TruSleep support
+* Huawei: Use distance and calories provided by the watch
+* Improve calendar change detection
+* Mi Band: Migrate global preferences to device-specific
+* Mi Composition Scale: Add alternative bluetooth name
+* Mi Composition Scale: Persist and display weight samples
+* Mijia LYWSD/XMWSDJ: Add comfort level preference
+* Pebble: Migrate global preferences to device-specific
+* Redmi Smart Band Pro: Fix crash on connection and activity sync issues
+* Sony Headphones: Enable read aloud incoming notifications and auto call pickup
+* UI: Add new activity icons
+* UI: Fix changelog on device rotation
+* UI: Fix HR samples displayed on wrong device
+* UI: Fix light navbar buttons on light themes for Android 8+
+* UI: Fix pull-down to refresh for some devices
+* UI: Improvements for large screen resolutions, font sizes, landscape
+* UI: Reduce stutters on device changes / data fetch / scrolling
+* UI: Refactor preferences screen
+* UM25C: Fix some disconnection issues
+* Use default system TTS language
+* Xiaomi Protobuf: Allow re-parse activity from storage
+* Xiaomi Protobuf: Enable watchface upload for all devices
+* Xiaomi Protobuf: Show watchface preview
+* Xiaomi Protobuf: Fix watchface install on some watches
+* Xiaomi Protobuf: Fix deleting first widget screen
+* Xiaomi Protobuf: Fix naps
+* Xiaomi Protobuf: Improve workout parsing for some devices
+* Zepp OS 3: Fix file transfer (notification icons, gpx upload, agps updates)
+* Zepp OS 3.5 / 4: Fix shortcuts, shortcut cards, menu items
+* Zepp OS: Add VO2 Max support
+* Zepp OS: Display resting heart rate
+* Zepp OS: Fix reminder creation in some cases
+* ZeTime: Migrate global preferences to device-specific
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/234.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/234.txt
new file mode 100644
index 0000000..de4a875
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/234.txt
@@ -0,0 +1,4 @@
+* Huawei: Improve activity parsing
+* Huawei Watch GT: Fix connection failure
+* Withings: Fix crash on connection
+* Improve Armenian transliterator for mixed-case words
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/235.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/235.txt
new file mode 100644
index 0000000..c7d6d62
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/235.txt
@@ -0,0 +1,57 @@
+* Add first start onboarding screen
+* Initial support for Bowers and Wilkins P Series
+* Initial support for Casio ECB-S100
+* Initial support for Colmi R09
+* Initial support for Freebuds 5i
+* Initial support for Garmin Fenix 6S Pro / 7X, Forerunner 55 / 235 / 620, Instinct 2
+* Initial support for Huawei Band 2 / 2 Pro / 3 Pro
+* Initial support for Oppo Enco Air / Air2
+* Initial support for Realme Buds T110
+* Initial support for Redmi Buds 5 Pro
+* Initial support for Xiaomi Smart Band 9 Pro
+* Initial support for Marstek B2500
+* Add calories charts and widgets
+* Add more workout icons
+* About screen: Copy build details on tap
+* Amazfit Bip 3 Pro: Fix title and sender on some notifications
+* Bangle.js: Fix calendar sync
+* Bangle.js: Fix call notification in Turkish locale
+* Bangle.js: Support sending activity type from Bangle
+* Casio GBX-100: Fix notification title
+* Charts: Display HR measurement gaps
+* Colmi R0x: Fix occasional crash on disconnection
+* Fix crash in some chart pages
+* Fix heart rate charts average and maximum value
+* Fix imperial unit on steps charts
+* Fix notifications after a notification is received with a time in the future
+* Fossil/Skagen Hybrids: Add SpO2 support
+* Fossil/Skagen Hybrids: Fix erroneous watchface downgrade
+* Garmin: Add intensity minutes, respiratory rate, sleep score
+* Garmin: Display AGPS age
+* Garmin: Fix weather temperature conversion to celsius
+* Garmin: Persist sleep score and metabolic rate
+* Garmin: Send notification pictures
+* Huawei Band 3 pro: Fix notifications
+* Huawei: Add HR zones configuration for non-P2P devices
+* Huawei: Add temperature chart
+* Huawei: Allow more languages to be set on the watch
+* Huawei: Display active calories
+* Huawei: Display high-resolution heart rate
+* Huawei: Fix watchface upload for some watches
+* Huawei: Improve activity parsing
+* Huawei: Music management
+* Huawei: Send weather error if there is no data
+* Huawei: Sync blood pressure if supported
+* Huawei: Sync skin temperature
+* Restore sleep balance on weekly and monthly charts
+* Test device: Add dummy activities
+* Workout page: Add colors to HR zones
+* Xiaomi Smart Band 8 Active: Fix freestyle and walking workout parsing 
+* Xiaomi Smart Band 9: Fix outdoor cycling parsing
+* Xiaomi SPPv2: Fix message processing getting stuck after exception
+* Xiaomi-protobuf: Add resting heart rate
+* Xiaomi-protobuf: Fix activity sync stuck on duplicated or invalid files
+* Xiaomi-protobuf: Improve workout parsing
+* Xiaomi-protobuf: Persist RR intervals during sleep
+* Zepp OS: Add sleep respiratory rate chart
+* Zepp OS: Send notification pictures
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/96.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/96.txt
new file mode 100644
index 0000000..ae41ca1
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/96.txt
@@ -0,0 +1,8 @@
+* Pebble: Fix crash when calendar access permission has been denied
+* Pebble: Fix wrong timestamps with Morpheuz running on Firmware >=3
+* Mi Band 2: Improve reliability when fetching activity data
+* HPlus: Fix intensity calculation without continuous connectivity
+* HPlus: Fix Unicode handling
+* HPlus: Initial not work detection
+* Fix memory leak
+* Only show Realtime Chart on devices supporting it
\ No newline at end of file
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/97.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/97.txt
new file mode 100644
index 0000000..fb39cf2
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/97.txt
@@ -0,0 +1,6 @@
+* Replace or relicense CC-NC licensed icons to satisfy F-Droid
+* Mi Band 2: Make infos to display on the Band configurable
+* Mi Band 2: Support wrist rotation to switch info setting
+* Mi Band 2: Support goal notification setting
+* Mi Band 2: Support do not disturb setting
+* Mi Band 2: Support inactivity warning setting 
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/98.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/98.txt
new file mode 100644
index 0000000..a89c916
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/98.txt
@@ -0,0 +1,9 @@
+* Initial Amazfit Bip support (WIP)
+* Various themeing fixes
+* Add workaround for blacklist not properly persisting
+* Handle resetting language to default properly
+* Pebble: Pass booleans from Javascript Appmessage correctly
+* Pebble: Make local configuration pages work on most recent webview implementation
+* Pebble: Allow to blacklist calendars
+* Add Greek and German transliteration support
+* Various visual improvements to charts
diff --git a/src/mainline/fastlane/metadata/android/en-US/changelogs/99.txt b/src/mainline/fastlane/metadata/android/en-US/changelogs/99.txt
new file mode 100644
index 0000000..f22831b
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/changelogs/99.txt
@@ -0,0 +1,2 @@
+* Amazfit Bip: Support icons and text body for notifications
+* Mi Band: Fix setting smart alarms
diff --git a/src/mainline/fastlane/metadata/android/en-US/full_description.txt b/src/mainline/fastlane/metadata/android/en-US/full_description.txt
new file mode 100644
index 0000000..22c5f51
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/full_description.txt
@@ -0,0 +1,9 @@
+Use your smart watch, fitness tracker, and other bluetooth devices without the vendor's closed source application and without the need to create an account and transmit any of your data to the vendor's servers.
+For a full list of supported device, please refer to our Homepage or README.md in our git repository.
+
+You can get notifications on your wrist and (depending on the device):
+
+- collect data from the device sensors
+- control music playing on your android device
+- see the weather 
+- get directions
\ No newline at end of file
diff --git a/src/mainline/fastlane/metadata/android/en-US/images/featureGraphic.jpg b/src/mainline/fastlane/metadata/android/en-US/images/featureGraphic.jpg
new file mode 100644
index 0000000..81fe492
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/images/featureGraphic.jpg
Binary files differ
diff --git a/src/mainline/fastlane/metadata/android/en-US/images/icon.png b/src/mainline/fastlane/metadata/android/en-US/images/icon.png
new file mode 100644
index 0000000..1c8dcb3
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/images/icon.png
Binary files differ
diff --git a/src/mainline/fastlane/metadata/android/en-US/images/phoneScreenshots/10-Dashboard.png b/src/mainline/fastlane/metadata/android/en-US/images/phoneScreenshots/10-Dashboard.png
new file mode 100644
index 0000000..c2bb762
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/images/phoneScreenshots/10-Dashboard.png
Binary files differ
diff --git a/src/mainline/fastlane/metadata/android/en-US/images/phoneScreenshots/11-Devices.png b/src/mainline/fastlane/metadata/android/en-US/images/phoneScreenshots/11-Devices.png
new file mode 100644
index 0000000..c6448d7
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/images/phoneScreenshots/11-Devices.png
Binary files differ
diff --git a/src/mainline/fastlane/metadata/android/en-US/images/phoneScreenshots/20-Activity.png b/src/mainline/fastlane/metadata/android/en-US/images/phoneScreenshots/20-Activity.png
new file mode 100644
index 0000000..f5c154e
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/images/phoneScreenshots/20-Activity.png
Binary files differ
diff --git a/src/mainline/fastlane/metadata/android/en-US/images/phoneScreenshots/40-SleepPerDay.png b/src/mainline/fastlane/metadata/android/en-US/images/phoneScreenshots/40-SleepPerDay.png
new file mode 100644
index 0000000..ba80623
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/images/phoneScreenshots/40-SleepPerDay.png
Binary files differ
diff --git a/src/mainline/fastlane/metadata/android/en-US/images/phoneScreenshots/41-SleepPerWeek.png b/src/mainline/fastlane/metadata/android/en-US/images/phoneScreenshots/41-SleepPerWeek.png
new file mode 100644
index 0000000..f0feb36
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/images/phoneScreenshots/41-SleepPerWeek.png
Binary files differ
diff --git a/src/mainline/fastlane/metadata/android/en-US/images/phoneScreenshots/50-StepsPerDay.png b/src/mainline/fastlane/metadata/android/en-US/images/phoneScreenshots/50-StepsPerDay.png
new file mode 100644
index 0000000..dcebda5
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/images/phoneScreenshots/50-StepsPerDay.png
Binary files differ
diff --git a/src/mainline/fastlane/metadata/android/en-US/images/phoneScreenshots/51-StepsPerWeek.png b/src/mainline/fastlane/metadata/android/en-US/images/phoneScreenshots/51-StepsPerWeek.png
new file mode 100644
index 0000000..7e3f04c
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/images/phoneScreenshots/51-StepsPerWeek.png
Binary files differ
diff --git a/src/mainline/fastlane/metadata/android/en-US/images/phoneScreenshots/52-StepsPerMonth.png b/src/mainline/fastlane/metadata/android/en-US/images/phoneScreenshots/52-StepsPerMonth.png
new file mode 100644
index 0000000..eb8cd1e
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/images/phoneScreenshots/52-StepsPerMonth.png
Binary files differ
diff --git a/src/mainline/fastlane/metadata/android/en-US/images/phoneScreenshots/60-Stress.png b/src/mainline/fastlane/metadata/android/en-US/images/phoneScreenshots/60-Stress.png
new file mode 100644
index 0000000..c6890c8
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/images/phoneScreenshots/60-Stress.png
Binary files differ
diff --git a/src/mainline/fastlane/metadata/android/en-US/images/phoneScreenshots/70-LiveActivity.png b/src/mainline/fastlane/metadata/android/en-US/images/phoneScreenshots/70-LiveActivity.png
new file mode 100644
index 0000000..9946873
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/images/phoneScreenshots/70-LiveActivity.png
Binary files differ
diff --git a/src/mainline/fastlane/metadata/android/en-US/images/phoneScreenshots/70-SportActivities.png b/src/mainline/fastlane/metadata/android/en-US/images/phoneScreenshots/70-SportActivities.png
new file mode 100644
index 0000000..e3b6960
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/images/phoneScreenshots/70-SportActivities.png
Binary files differ
diff --git a/src/mainline/fastlane/metadata/android/en-US/images/phoneScreenshots/71-SportActivitiesFilter.png b/src/mainline/fastlane/metadata/android/en-US/images/phoneScreenshots/71-SportActivitiesFilter.png
new file mode 100644
index 0000000..58c82a7
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/images/phoneScreenshots/71-SportActivitiesFilter.png
Binary files differ
diff --git a/src/mainline/fastlane/metadata/android/en-US/images/phoneScreenshots/72-SportActivitiesSummary.png b/src/mainline/fastlane/metadata/android/en-US/images/phoneScreenshots/72-SportActivitiesSummary.png
new file mode 100644
index 0000000..cd158b7
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/images/phoneScreenshots/72-SportActivitiesSummary.png
Binary files differ
diff --git a/src/mainline/fastlane/metadata/android/en-US/short_description.txt b/src/mainline/fastlane/metadata/android/en-US/short_description.txt
new file mode 100644
index 0000000..ec99d8c
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/short_description.txt
@@ -0,0 +1 @@
+Use your smart watch and other bluetooth devices and keep your data private!
diff --git a/src/mainline/fastlane/metadata/android/en-US/title.txt b/src/mainline/fastlane/metadata/android/en-US/title.txt
new file mode 100644
index 0000000..e7ee542
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/en-US/title.txt
@@ -0,0 +1 @@
+Gadgetbridge
\ No newline at end of file
diff --git a/src/mainline/fastlane/metadata/android/it-IT/full_description.txt b/src/mainline/fastlane/metadata/android/it-IT/full_description.txt
new file mode 100644
index 0000000..0d6bddd
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/it-IT/full_description.txt
@@ -0,0 +1,8 @@
+Utilizza il tuo dispositivo Pebble/Mi Band/Amazfit Bip/Hplus senza dipendere dall'applicazione proprietaria del vendor e senza bisogno di creare accounts e trasferire i tuoi dati altrove.
+
+Vedi le notifiche direttamente sul tuo polso, e inoltre (a seconda del dispositivo):
+
+- memorizza i dati della tua attivit脿 fisica dai sensori
+- controlla le app per riprodurre musica sul tuo dispositivo android
+- vedi le condizioni meteorologiche
+- vedi le indicazioni stradali
\ No newline at end of file
diff --git a/src/mainline/fastlane/metadata/android/it-IT/short_description.txt b/src/mainline/fastlane/metadata/android/it-IT/short_description.txt
new file mode 100644
index 0000000..c1566b6
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/it-IT/short_description.txt
@@ -0,0 +1 @@
+Usa il tuo Pebble/Bip/Miband/Hplus/[altri] e mantieni i tuoi dati privati!
\ No newline at end of file
diff --git a/src/mainline/fastlane/metadata/android/it-IT/title.txt b/src/mainline/fastlane/metadata/android/it-IT/title.txt
new file mode 100644
index 0000000..e7ee542
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/it-IT/title.txt
@@ -0,0 +1 @@
+Gadgetbridge
\ No newline at end of file
diff --git a/src/mainline/fastlane/metadata/android/pt-PT/full_description.txt b/src/mainline/fastlane/metadata/android/pt-PT/full_description.txt
new file mode 100644
index 0000000..e84b7a1
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/pt-PT/full_description.txt
@@ -0,0 +1,10 @@
+Usa o teu smartwatch, pulseira, e outros dispositivos bluetooth sem depender da aplica莽茫o oficial propriet谩ria, e sem criar conta ou enviar os teus dados para servidores externos.
+
+Para uma lista completa de todos os dispositivos suportados, por favor consulta a nossa p谩gina oficial ou o README.md no reposit贸rio git.
+
+Podes receber notifica莽玫es no teu pulso e (dependendo do dispositivo):
+
+- Obter dados dos sensores do dispositivo
+- Controlar a m煤sica do teu telem贸vel
+- Ver a meteorologia
+- Obter dire莽玫es
diff --git a/src/mainline/fastlane/metadata/android/pt-PT/short_description.txt b/src/mainline/fastlane/metadata/android/pt-PT/short_description.txt
new file mode 100644
index 0000000..59926ec
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/pt-PT/short_description.txt
@@ -0,0 +1 @@
+Usa o teu smartwatch e dispositivos bluetooth, e mant茅m os teus dados privados!
diff --git a/src/mainline/fastlane/metadata/android/ru/full_description.txt b/src/mainline/fastlane/metadata/android/ru/full_description.txt
new file mode 100644
index 0000000..5ffe68e
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/ru/full_description.txt
@@ -0,0 +1,9 @@
+袠褋锌芯谢褜蟹褍泄褌械 褋胁芯懈 褍屑薪褘械 褔邪褋褘, 褎懈褌薪械褋-褌褉械泻械褉 懈 写褉褍谐懈械 褍褋褌褉芯泄褋褌胁邪 Bluetooth 斜械蟹 锌褉懈谢芯卸械薪懈褟 锌褉芯懈蟹胁芯写懈褌械谢褟 褋 蟹邪泻褉褘褌褘屑 懈褋褏芯写薪褘屑 泻芯写芯屑 懈 斜械蟹 薪械芯斜褏芯写懈屑芯褋褌懈 褋芯蟹写邪胁邪褌褜 褍褔械褌薪褍褞 蟹邪锌懈褋褜 懈 锌械褉械写邪胁邪褌褜 泻邪泻懈械-谢懈斜芯 写邪薪薪褘械 薪邪 褋械褉胁械褉褘 锌褉芯懈蟹胁芯写懈褌械谢褟.
+袩芯谢薪褘泄 褋锌懈褋芯泻 锌芯写写械褉卸懈胁邪械屑褘褏 褍褋褌褉芯泄褋褌胁 屑芯卸薪芯 薪邪泄褌懈 薪邪 薪邪褕械泄 写芯屑邪褕薪械泄 褋褌褉邪薪懈褑械 懈谢懈 胁 褎邪泄谢械 README.md 胁 薪邪褕械屑 褉械锌芯蟹懈褌芯褉懈懈 git.
+
+袙褘 屑芯卸械褌械 锌芯谢褍褔邪褌褜 褍胁械写芯屑谢械薪懈褟 薪邪 褋胁芯械 蟹邪锌褟褋褌褜械 懈 (胁 蟹邪胁懈褋懈屑芯褋褌懈 芯褌 褍褋褌褉芯泄褋褌胁邪):
+
+- 褋芯斜懈褉邪褌褜 写邪薪薪褘械 褋 写邪褌褔懈泻芯胁 褍褋褌褉芯泄褋褌胁邪
+- 褍锌褉邪胁谢褟褌褜 屑褍蟹褘泻芯泄 胁芯褋锌褉芯懈蟹胁芯写懈屑芯泄 薪邪 胁邪褕械屑 褍褋褌褉芯泄褋褌胁械 Android
+- 褋屑芯褌褉械褌褜 锌芯谐芯写褍
+- 锌芯谢褍褔邪褌褜 薪邪锌褉邪胁谢械薪懈械 泻褍写邪 懈写褌懈
\ No newline at end of file
diff --git a/src/mainline/fastlane/metadata/android/ru/short_description.txt b/src/mainline/fastlane/metadata/android/ru/short_description.txt
new file mode 100644
index 0000000..659bcd4
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/ru/short_description.txt
@@ -0,0 +1 @@
+袠褋锌芯谢褜蟹褍泄褌械 褋胁芯懈 褍屑薪褘械 褔邪褋褘 懈 写褉褍谐懈械 褍褋褌褉芯泄褋褌胁邪 Bluetooth 懈 褋芯褏褉邪薪褟泄褌械 泻芯薪褎懈写械薪褑懈邪谢褜薪芯褋褌褜 褋胁芯懈褏 写邪薪薪褘褏!
\ No newline at end of file
diff --git a/src/mainline/fastlane/metadata/android/ru/title.txt b/src/mainline/fastlane/metadata/android/ru/title.txt
new file mode 100644
index 0000000..e7ee542
--- /dev/null
+++ b/src/mainline/fastlane/metadata/android/ru/title.txt
@@ -0,0 +1 @@
+Gadgetbridge
\ No newline at end of file
diff --git a/src/mainline/fastlane/take_app_screenshots.sh b/src/mainline/fastlane/take_app_screenshots.sh
new file mode 100644
index 0000000..fc1c901
--- /dev/null
+++ b/src/mainline/fastlane/take_app_screenshots.sh
@@ -0,0 +1,646 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+#
+#    Copyright (C) 2024 ysfchn
+#
+#    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 <http://www.gnu.org/licenses/>.
+#
+
+# Path of the ADB binary to use for ADB commands. If not set, "adb" will
+# be used as the binary, meaning it will search from the PATH. Alternatively,
+# if "ANDROID_HOME" variable exists, it will be used as fallback to search
+# for the ADB binary.
+ADB_BINARY="${ADB_BINARY:-"adb"}"
+
+# Package name of Gadgetbridge. Wildcards are also allowed, in this
+# case, will look for a matching package names. If there are more than
+# 1 result, the script will not continue since we can't determine which
+# Gadgetbridge flavor on the device should be used for screenshots.
+#
+# If there is only one Gadgetbridge flavor installed on the device, this
+# variable can be kept this way, so it can launch whetever it is a Nightly 
+# or not.
+PACKAGE_NAME="${PACKAGE_NAME:-"nodomain.freeyourgadget.gadgetbridge.*"}"
+
+# Additional commands that will be executed after disabling demo UI to revert
+# the device in its original state (as in before running this script.)
+REVERT_COMMANDS=""
+
+# Where to save screenshots, relative to the folder 
+# where this script is located. Don't prefix with "./".
+OUTPUT_DIR="metadata/android/en-US/images/phoneScreenshots"
+
+# Where to look for pngquant to optimize the taken screenshots,
+# if pngquant is not available in given path, it will be ignored, so this is
+# optional.
+PNGQUANT_BIN="${PNGQUANT_BIN:-"pngquant"}"
+
+# Displayed time on the screenshots in HH:MM format. It will be applied inside
+# Android Demo Mode, so it won't change the actual Android system time.
+#
+# Set this value to "version" to automatically construct a time by reading 
+# the current Gadgetbridge version code and converting it to minutes.
+#
+# SYSTEM_CLOCK="version"
+SYSTEM_CLOCK="10:00"
+
+# Set to 1 for debugging input actions, if set to "1", it won't run any 
+# commands that causes any modifications (such as changing the theme, 
+# enabling demo UI etc.) to the system.
+DEBUG="${DEBUG:-0}"
+
+# ---------------------------------------------------------------------
+# Utility
+# ---------------------------------------------------------------------
+
+# Prefix output directory with current file path
+OUTPUT_DIR="$(dirname $(realpath -s $0))/${OUTPUT_DIR}"
+
+# Append semicolon for seperating new commands
+if [ -n "${REVERT_COMMANDS}" ]; then
+    REVERT_COMMANDS+=";" 
+fi
+
+# Read the version code of Gagdetbridge and convert it to HHMM format, 
+# which will be set as the Android demo UI clock, 
+# so it can hint which version of Gadgetbridge is being used 
+# while taking screenshots.
+#
+# In some cases when the version code couldn't be read 
+# or the constructed clock is invalid, it will fallback to 10:00.
+get_clock() {
+    if [ "${SYSTEM_CLOCK}" = "version" ]
+    then
+        # As how it is in build.gradle file.
+        version_code=$(git rev-list HEAD --count)
+        date --date "@${version_code}" -u +%M%S 2>/dev/null || echo "1000"
+    else
+        echo "${SYSTEM_CLOCK}" | sed "s/[^0-9]//g"
+    fi
+}
+
+# Try to find the "adb" binary. First, look for "adb" specified in "ADB_BINARY" 
+# variable. If variable is not specified, it will look for "adb" in PATH, 
+# otherwise, check for "ANDROID_HOME" variable.
+#
+# It will print the path of the ADB binary to stdout if ADB is found, otherwise
+# write to stderr and exit, since we can't continue without ADB.
+locate_adb() {
+    if [ ! -x "$(command -v ${ADB_BINARY})" ]
+    then
+        # Also try Android SDK environment variable.
+        # https://developer.android.com/tools/variables#envar
+        if [ -n "${ANDROID_HOME:-}" ]
+        then
+            if [ -x "${ANDROID_HOME}/platform-tools/adb" ]
+            then
+                echo "ADB found at: ${ANDROID_HOME}/platform-tools/adb" >&2
+                echo "${ANDROID_HOME}/platform-tools/adb"
+                return 0
+            fi
+        fi
+        echo "ADB couldn't be found in '${ADB_BINARY}', exiting..." >&2
+        exit 1
+    else
+        echo "ADB found at: ${ADB_BINARY}" >&2
+        echo "${ADB_BINARY}"
+        return 0
+    fi
+}
+
+# Check if required packages are installed, and fail if its not.
+check_deps() {
+    if [ ! -x "$(command -v xmlstarlet)" ]
+    then
+        echo "'xmlstarlet' is an additional package that is required"
+        echo "by this script. It may be available in your system package"
+        echo "manager. Exiting..."
+        exit 1
+    fi
+}
+
+# Locate the ADB (see above function) and check for connected devices. If device
+# count is 1, print the ADB path with a "-s DEVICE_SERIAL" suffix to stdout, so 
+# we can make sure that ADB commands throughout the script will be executed only 
+# for the specified device, serving as a safeguard.
+# (e.g. "adb -s DEVICE_SERIAL shell echo Hello!")
+#
+# If deivce count is not equal to 1, exit and tell the reason to stderr.
+get_adb_command() {
+    adb_path=$(locate_adb)
+    device_list="$("${adb_path}" devices | sed -n "s/^\([0-9a-zA-Z]*\)\s*device$/\1/p")"
+    device_count="$(echo -n "${device_list}" | grep -c '^')"
+    # Make sure there is an only single device connected.
+    if [ $device_count != 1 ]
+    then
+        echo "" >&2
+        echo "There must be an exactly only one device that connected to the ADB," >&2
+        echo "but found $device_count devices instead! Exiting..." >&2
+        exit 1
+    fi
+    # Construct a command line to be used in next commands.
+    echo "${adb_path} -s ${device_list}"
+}
+
+# ---------------------------------------------------------------------
+# ADB Commands
+# ---------------------------------------------------------------------
+
+# Changes the system theme programmatically.
+# First it applies the light mode, and second, (if API level is >= 33),
+# changes system colors.
+set_system_theme() {
+    adb_prefix="${1}"
+
+    if [ "${DEBUG}" -eq "1" ]; then
+        echo "Theme: Skipped because of debug"
+        return
+    fi
+
+    # Read the existing night mode value on the system, save the original
+    # value for reverting it later. And change system theme to light mode.
+    # Run "adb shell cmd uimode night help" for reference.
+    current_night_mode="$(${adb_prefix} shell cmd uimode night | sed "s/Night mode: //")"
+    case "${current_night_mode}" in
+        "no")
+            echo "Theme: Already set to light, skipped"
+            ;;
+        "yes" | "auto" | "custom_bedtime" | "custom_schedule")
+            REVERT_COMMANDS+="${adb_prefix} shell cmd uimode night ${current_night_mode} 1>/dev/null;"
+            echo "Theme: Switching to light from dark"
+            ${adb_prefix} shell cmd uimode night no 1>/dev/null
+            ;;
+        *)
+            echo "Theme: Unsupported theme value, skipped"
+    esac
+
+    # For Android 13 and above, change the system colors programmatically to 
+    # a red-ish color to better match with Gadgetbridge branding.
+    if [ "$(${adb_prefix} shell getprop ro.build.version.sdk)" -ge "33" ]; then
+        echo "Theme: Setting Dynamic color"
+        # Reference for color properties:
+        # https://source.android.com/docs/core/display/dynamic-color
+        theme_properties='{
+            "android.theme.customization.system_palette":"FFB2B5",
+            "android.theme.customization.accent_color":"FFB2B5",
+            "android.theme.customization.theme_style":"RAINBOW"
+        }'
+        original_theme="$(${adb_prefix} shell settings get secure theme_customization_overlay_packages | tr -d "[:space:]" | tr "\"" "\\\"")"
+        echo "${original_theme}"
+        # TODO: Reverting the colors currently doesn't work beause of the characters getting escaped somehow in ADB
+        # REVERT_COMMANDS+="${adb_prefix} shell settings put secure theme_customization_overlay_packages ""'"${original_theme}"'"
+        ${adb_prefix} shell settings put secure theme_customization_overlay_packages "'"$(echo "${theme_properties}" | tr -d "[:space:]" | tr "\"" "\\\"")"'"
+    fi
+}
+
+# Enables Android Demo Mode and mock status bar icons.
+#
+# See the reference for list of properties used below:
+# https://android.googlesource.com/platform/frameworks/base/+/master/packages/SystemUI/docs/demo_mode.md
+#
+# Note that the demo UI may a little bit problematic, e.g. when network 
+# signal level is faked to 4, the signal level might be changed still
+# if the actual signal level has changed even if demo UI is shown at the
+# moment, so better to take screenshots quickly as possible before status
+# icons are changed.
+set_status_bar() {
+    adb_prefix="${1}"
+
+    if [ "${DEBUG}" -eq "1" ]; then
+        echo "Demo mode: Skipped because of debug"
+        return
+    fi
+
+    echo "Demo mode: Enabling"
+    ${adb_prefix} shell settings put global sysui_demo_allowed 1
+
+    echo "Demo mode: Setting status icons"
+
+    # Enter the demo mode
+    ${adb_prefix} shell am broadcast -a com.android.systemui.demo --es command enter 1>/dev/null
+
+    # Wi-Fi and mobile data for each SIM slot
+    # (couldn't manage to turn off the second slot for dual SIM)
+    ${adb_prefix} shell am broadcast -a com.android.systemui.demo --es command network \
+        --es wifi show --es level 4 1>/dev/null
+    ${adb_prefix} shell am broadcast -a com.android.systemui.demo --es command network \
+        --es mobile show --es level 4 1>/dev/null
+
+    # Battery
+    ${adb_prefix} shell am broadcast -a com.android.systemui.demo --es command battery \
+        --es level 100 --es plugged false --es powersave false 1>/dev/null
+
+    # Explictly disable status icons
+    ${adb_prefix} shell am broadcast -a com.android.systemui.demo --es command status \
+        --es volume hide \
+        --es bluetooth hide \
+        --es location hide \
+        --es alarm hide \
+        --es sync hide \
+        --es tty hide \
+        --es eri hide \
+        --es mute hide \
+        --es speakerphone hide 1>/dev/null
+
+    # No notification icons
+    ${adb_prefix} shell am broadcast -a com.android.systemui.demo --es command notifications \
+        --es visible false 1>/dev/null
+
+    # Set system clock
+    ${adb_prefix} shell am broadcast -a com.android.systemui.demo --es command clock \
+        --es hhmm $(get_clock) 1>/dev/null
+
+    # Also disable demo UI itself when reverting the preferences.
+    REVERT_COMMANDS+="${adb_prefix} shell am broadcast -a com.android.systemui.demo --es command exit 1>/dev/null;"
+    REVERT_COMMANDS+="${adb_prefix} shell settings put global sysui_demo_allowed 0;"
+}
+
+# In addition to the Android Demo Mode (as above), we can mock more system
+# status icons with LineageOS (or ROMs that based on LineageOS) specific 
+# settings, so we can keep the AOSP-like look for screenshots.
+#
+# This function will be executed for all devices regardless of their ROM,
+# since this function already checks if running under LineageOS, and if it
+# is not, it is a no-op.
+set_status_bar_lineage_os() {
+    adb_prefix="${1}"
+
+    if [ "${DEBUG}" -eq "1" ]; then
+        echo "LineageOS: Skipped because of debug"
+        return
+    fi
+
+    # As its name implies, "ro.lineage.device" property is found on
+    # LineageOS (or based on it) ROMs, so we check for its existence.
+    if [ -n "$(${adb_prefix} shell getprop ro.lineage.device)" ]
+    then
+        echo "LineageOS: Setting ROM-specific icons"
+
+        # LineageOS allows changing the battery icon style, so we set it
+        # to portrait and hide the battery percent temporarily. Note the
+        # "--lineage" flag here, since it is a LineageOS addition, not
+        # Android's, so it won't work without that flag.
+        #
+        # See also for all system properties:
+        # https://lineageos.github.io/android_lineage-sdk/reference/lineageos/providers/LineageSettings.System.html
+
+        battery_style="$(${adb_prefix} shell settings get --lineage system status_bar_battery_style)"
+        battery_percent="$(${adb_prefix} shell settings get --lineage system status_bar_show_battery_percent)"
+
+        # Save the original values to a variable, so we can execute these commands after
+        # screenshots are taken to revert the changed system preferences to its original state.
+        REVERT_COMMANDS+="${adb_prefix} shell settings put --lineage system status_bar_battery_style ${battery_style};"
+        REVERT_COMMANDS+="${adb_prefix} shell settings put --lineage system status_bar_show_battery_percent ${battery_percent};"
+
+        ${adb_prefix} shell settings put --lineage system status_bar_battery_style 0
+        ${adb_prefix} shell settings put --lineage system status_bar_show_battery_percent 0
+    else
+        echo "LineageOS: Not a LineageOS ROM, skipped"
+    fi
+}
+
+# Filters the installed packages on the device and prints the matched
+# Gadgetbridge package name that specified in "PACKAGE_NAME" variable.
+get_gadgetbridge_app() {
+    adb_prefix="${1}"
+
+    # Convert the "PACKAGE_NAME" variable to a valid regex format, change periods to
+    # literal periods, and change wildcards to a valid wildcards.
+    package_filter="$(echo "${PACKAGE_NAME}" | tr "." "\\." | tr "*" ".*")"
+
+    # List installed & enabled ("-e" flag) packages, and find the packages that
+    # matches with the pattern.
+    packages="$(${adb_prefix} shell cmd package list packages -e | sed -rn "s/^package:(${package_filter})/\1/p")"
+    package_count="$(echo -n "${packages}" | grep -c '^')"
+
+    # There can be multiple Gadgetbridge flavors installed on the device, since we
+    # can't determine which one should be launched, we exit.
+    if [ $package_count != 1 ]
+    then
+        echo "" >&2
+        echo "There are none or more than one Gadgetbridge flavors found on the device," >&2
+        echo "since it can't be determined which Gadgetbridge flavor should be used, " >&2
+        echo "you will need to specifically set the preferred flavor with PACKAGE_NAME" >&2
+        echo "variable to continue!" >&2
+        exit 1
+    fi
+
+    echo "Chosen app: ${packages}" >&2
+    echo "${packages}"
+}
+
+# ---------------------------------------------------------------------
+# Functions
+# ---------------------------------------------------------------------
+
+optimize_screenshots() {
+    if [ -x "$(command -v ${PNGQUANT_BIN})" ]
+    then
+        echo "Optimize: Running pngquant"
+        "$(command -v ${PNGQUANT_BIN})" --strip --force --ext .png $(find "${OUTPUT_DIR}" -name "*.png")
+        echo "Optimize: Done"
+    else
+        echo "Optimize: Couldn't find pngquant at '${PNGQUANT_BIN}', skipped"
+    fi
+}
+
+# Takes a screenshot of the screen and saves to a given name. Optionally,
+# it also can be prompted to pressing a key before taking a screenshot.
+grab_screenshot() {
+    adb_prefix="${1}"
+    file_name="${2}"
+    wait_for_key_prompt="${3:-}"
+
+    if [ -n "${wait_for_key_prompt}" ]; then
+        read -s -r -N 1 -p "[screenshot]: Waiting for '${file_name}' - ${wait_for_key_prompt} ** [Press any key to continue]"
+        echo ""
+    fi
+    ${adb_prefix} shell screencap -p > "${OUTPUT_DIR}/${file_name}"
+    # Play a bell sound to indicate the screenshot has been saved.
+    echo -en '\a'
+    echo "[screenshot]: Took '${file_name}'."
+}
+
+# Gets the XY touch positions of a UI element that hinted by a specific
+# name by filtering from the layout data. The layout can be obtained by 
+# "dump_screen_layout" function. 
+get_position() {
+    dump_output="${1}"
+    name="${2}"
+
+    # There can be multiple elements that matching the same "name", so
+    # we limit to printing one line only by specifying which n-th element
+    # to return. If not given, will print the coordinates of the first
+    # matching (1st) element.
+    index="${3:-1}"
+    echo "${dump_output}" | sed -rn "s/name:${name},position:([0-9]+):([0-9]+)/\1 \2/p" | sed -n ${index}p
+}
+
+launch_gadgetbridge() {
+    adb_prefix="${1}"
+    package_name="${2}"
+
+    # Check if Gadgetbridge is launched already, and warn the user to close
+    # Gadgetbridge, so we can start in the home screen when we launch
+    # Gadgetbridge.
+    status=$(${adb_prefix} shell dumpsys window windows \
+        | grep -E 'mCurrentFocus|mFocusedApp|mInputMethodTarget|mSurface' \
+        | grep -qs "${package_name}" || echo $?)
+    
+    if [ -z $status ]; then
+        echo "[!] Gadgetbridge is currently shown on the device. Close it " >&2
+        echo "(and delete it from the recents) and press any key to continue." >&2
+        read -s -r -N 1
+        launch_gadgetbridge "${adb_prefix}" "${package_name}"
+    else
+        echo "Launching Gadgetbridge..."
+
+        # Launch Gadgetbridge with monkey command, since it doesn't require us
+        # to specify the activity class name along with the package name.
+        ${adb_prefix} shell monkey -p "${package_name}" 1 1 1>/dev/null 2>&1
+
+        # Give it some time before continuing.
+        sleep 2
+    fi
+}
+
+# Creates a dump of the current layout that is shown on the device screen.
+# So this allow us to programmatically get touch positions of UI elements.
+# 
+# It will output a string that contains element descriptions and their
+# X/Y positions, for each valid element:
+# "name:Open navigation drawer,position:70:159"
+dump_screen_layout() {
+    adb_prefix="${1}"
+
+    # "uiautomator" service prints a XML document, but it also prints a 
+    # "UI hierchary dumped to..." message in the end, so we strip this
+    # message from the string to make it a valid XML text.
+    char_count=$(echo "UI hierchary dumped to: /dev/tty" | wc -c)
+    xml_document="$(${adb_prefix} exec-out uiautomator dump /dev/tty | head -c -${char_count})"
+
+    # XPath to list UI nodes listed in the current screen (we specifically
+    # only list nodes in the Gadgetbridge app to be explicit), and print
+    # their bound positions ("bounds") and UI names ("content-desc"), so we
+    # can filter elements by their names.
+    package_name="$(echo "${PACKAGE_NAME}" | sed "s/*$//")"
+    xml_path="./hierarchy/node[contains(@package, \"${package_name}\") and @class=\"android.widget.FrameLayout\"]//node/@*[name() = \"bounds\" or name() = \"content-desc\"]"
+
+    index=0
+    previous_ui_name=""
+    while IFS="" read -r line || [ -n "$line" ]
+    do
+        # The output from XPath query, we will have attribute values printed
+        # one by one, so since we look for both "bounds" and "content-desc" attribute,
+        # it will print:
+        # - bounds
+        # - content-desc
+        # - bounds
+        # - content-desc
+        #
+        # So to group each node, we check if it is divisible by 2, the count of the
+        # attributes that we are listing.
+        if [ "$(( $index % 2 ))" -eq 0 ]; then
+            # This will be the "content-desc", so we pass it as-is.
+            previous_ui_name="${line}"
+        else
+            # This will be the "bounds", so we convert this value to a X/Y positions.
+            # Each bound value will contain 4 points, START_X, START_Y, END_X and END_Y,
+            # and is formatted as: "[0,0][1080,2219]", since we only need a single position
+            # to touch it, we calculate the center of both X and Y here.
+            #
+            # To read the values, we need to convert these bounds to a Bash array,
+            # so remove all comma and square bracket characters to only leave the numbers.
+            # "[0,0][1080,2219]" -> array of 4: "0 0 1080 2219"
+            bounds=($(echo "${line}" | sed "s/^\[//; s/\[/ /g; s/\]//g; s/,/ /g"))
+
+            # Now we can calculate the center position of the element.
+            # X_CENTER = X_START + ((X_END - X_START) / 2)
+            # Y_CENTER = Y_START + ((Y_END - Y_START) / 2)
+            center_x=$((( ${bounds[0]} + ((${bounds[2]} - ${bounds[0]}) / 2) )))
+            center_y=$((( ${bounds[1]} + ((${bounds[3]} - ${bounds[1]}) / 2) )))
+
+            # Only output if there is a UI name for the element.
+            if [ -n "${previous_ui_name}" ]
+            then
+                echo "name:${previous_ui_name},position:${center_x}:${center_y}"
+            fi
+        fi
+        index=$(( index + 1 ))
+    done < <(echo "${xml_document}" | xmlstarlet sel --template --value-of "${xml_path}")
+    if [ $index -eq 0 ]
+    then
+        echo "[!] Couldn't find valid elements on the screen, is Gadgetbridge currently shown on the screen?" >&2
+    fi
+}
+
+perform_input() {
+    adb_prefix="${1}"
+    input_type="${2}"
+
+    case "${input_type}" in
+        # Swipe for a specific direction.
+        "swipe_left" | "swipe_right")
+            # Get screen dimensions.
+            dimensions="$(${adb_prefix} shell wm size | rev | cut -d " " -f 1 | rev)"
+            screen_x=$(echo "$dimensions" | cut -d "x" -f 1)
+            screen_y=$(echo "$dimensions" | cut -d "x" -f 2)
+            # If swiping to the left, swipe to the left edge of the screen.
+            swipe_x=0
+            # Or if swiping to the right, swipe to the right edge of the screen.
+            if [ "${input_type}" = "swipe_right" ]; then
+                swipe_x=$screen_x
+            fi
+            # Perform the swipe (starting form the center of the screen) 
+            # to given direction in 100 milliseconds.
+            ${adb_prefix} shell input swipe $((screen_x / 2)) $((screen_y / 2)) $swipe_x $((screen_y / 2)) 100
+            sleep 1
+            ;;
+        # Send "back" button key.
+        "go_back")
+            # 111 = KEYCODE_ESCAPE
+            # 4 = KEYCODE_BACK
+            ${adb_prefix} shell input keyevent 4
+            sleep 0.5
+            ;;
+        # Touch to a UI element which hinted with a given element name.
+        "touch_element")
+            element_name="${3}"
+            xml_layout="$(dump_screen_layout "${adb_prefix}")"
+            element_index="${4:-1}"
+            # Get the X/Y position of given element name, and touch it.
+            ${adb_prefix} shell input tap $(get_position "${xml_layout}" "${element_name}" "${element_index}")
+            sleep 0.5
+            ;;
+        *)
+            echo "[!] Unsupported input type!"
+            exit 1
+    esac
+}
+
+# Open Gadgetbridge and start taking screenshots of different screens.
+# To find out the touch positions;
+# Developer Options > Under "Input" category > Enable "Pointer location"
+#
+# TODO: Calculate the exact position from screen density and dimensions to make X/Y positions adaptive with other screens.
+take_screenshots() {
+    adb_prefix="${1}"
+
+    echo; echo "! - If system preferences are not reverted automatically after exiting, you can manually run these commands:"
+    echo "${REVERT_COMMANDS}"
+    echo; echo "Taking screenshots, it might take some time;"
+
+    # Dashboard
+    perform_input "${adb_prefix}" "swipe_right"
+    grab_screenshot "${adb_prefix}" "10-Dashboard.png" "Change the day or keep today"
+
+    # Calendar
+    perform_input "${adb_prefix}" "touch_element" "Calendar"
+    grab_screenshot "${adb_prefix}" "30-Calendar.png" "Change the month of keep as-is"
+    perform_input "${adb_prefix}" "go_back"
+
+    # Devices
+    perform_input "${adb_prefix}" "swipe_left"
+    grab_screenshot "${adb_prefix}" "11-Devices.png"
+
+    # Activity chart
+    perform_input "${adb_prefix}" "touch_element" "Your activity"
+    grab_screenshot "${adb_prefix}" "20-Activity.png" "Adjust the chart"
+
+    # Sleep chart (Day)
+    perform_input "${adb_prefix}" "touch_element" "Sleep"
+    perform_input "${adb_prefix}" "touch_element" "Day"
+    grab_screenshot "${adb_prefix}" "40-SleepPerDay.png" "Change the day or keep today"
+
+    # Sleep chart (Week)
+    perform_input "${adb_prefix}" "touch_element" "Week"
+    grab_screenshot "${adb_prefix}" "41-SleepPerWeek.png" "Change the day or keep today"
+
+    # Steps chart (Day)
+    perform_input "${adb_prefix}" "touch_element" "Steps"
+    perform_input "${adb_prefix}" "touch_element" "Day"
+    grab_screenshot "${adb_prefix}" "50-StepsPerDay.png" "Change the day or keep today"
+
+    # Steps chart (Week)
+    perform_input "${adb_prefix}" "touch_element" "Week"
+    grab_screenshot "${adb_prefix}" "51-StepsPerWeek.png" "Change the day or keep today"
+
+    # Steps chart (Month)
+    perform_input "${adb_prefix}" "touch_element" "Month"
+    grab_screenshot "${adb_prefix}" "52-StepsPerMonth.png" "Change the day or keep today"
+
+    # Stress
+    perform_input "${adb_prefix}" "touch_element" "Stress"
+    grab_screenshot "${adb_prefix}" "60-Stress.png" "Change the day or keep today"
+
+    # Live activity
+    perform_input "${adb_prefix}" "touch_element" "Live activity"
+    grab_screenshot "${adb_prefix}" "70-LiveActivity.png" "Start your activity on the watch"
+
+    # Back to the home screen
+    perform_input "${adb_prefix}" "go_back"
+
+    # Sport activities
+    perform_input "${adb_prefix}" "touch_element" "Your activity tracks"
+    grab_screenshot "${adb_prefix}" "70-SportActivities.png"
+
+    # Sport activity filter
+    perform_input "${adb_prefix}" "touch_element" "Sports Activities Filter"
+    grab_screenshot "${adb_prefix}" "71-SportActivitiesFilter.png"
+
+    # Back to the sport activities screen
+    perform_input "${adb_prefix}" "go_back"
+
+    # Sport activity summary
+    perform_input "${adb_prefix}" "touch_element" "Device image" 10
+    grab_screenshot "${adb_prefix}" "72-SportActivitiesSummary.png"
+}
+
+# Disable demo UI, and revert all applied settings by eval-ing all
+# revert commands that is saved before.
+revert() {
+    if [ -n "${REVERT_COMMANDS}" ]; then
+        echo; echo "[*] Reverting modifications..."
+    fi
+    eval "${REVERT_COMMANDS}"
+    exit 0
+}
+
+start() {
+    check_deps
+
+    echo "[*] Searching for devices..."
+    adb_command=$(get_adb_command)
+    use_package=$(get_gadgetbridge_app "${adb_command}")
+
+    echo; echo "[*] Applying preferences..."
+    set_system_theme "${adb_command}"
+    set_status_bar_lineage_os "${adb_command}"
+    set_status_bar "${adb_command}"
+
+    launch_gadgetbridge "${adb_command}" "${use_package}"
+
+    echo; echo "[*] Taking screenshots..."
+    take_screenshots "${adb_command}"
+    optimize_screenshots
+}
+
+# Run "revert" function to revert everything if received an SIGINT or SIGTERM signal.
+# Also with "EXIT" signal, run this function whenever the script exits.
+trap revert SIGINT SIGTERM EXIT
+
+start
\ No newline at end of file

--
Gitblit v1.9.1